UNPKG

51.1 kBMarkdownView Raw
1<img src="https://raw.githubusercontent.com/gladjs/glad/master/.assets/glad-banner.png" height="160px" width="712px" align="center">
2
3[![Code Climate](https://codeclimate.com/github/gladjs/glad/badges/gpa.svg)](https://codeclimate.com/github/gladjs/glad)
4[![Build Status](https://travis-ci.org/gladjs/glad.svg?branch=master)](https://travis-ci.org/gladjs/glad)
5[![Coverage Status](https://coveralls.io/repos/github/gladjs/glad/badge.svg?branch=master)](https://coveralls.io/github/gladjs/glad?branch=master)
6
7[![NPM](https://nodei.co/npm/glad.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/glad/)
8
9
10<div style="clear:both;"></div>
11
12<h2>Required</h2>
13
14* [Node >= 6.5](http://nodejs.org/) *(with NPM)*
15* [Redis >= 3.0.2](http://redis.io/)
16
17*Requires Redis 3.0.2 or greater because caching uses the [XX option of ZADD](https://redis.io/commands/zadd#zadd-options-redis-302-or-greater).*
18<br>
19*Requires Node 6.5 or greater because many features of ES6 are used.*
20<br>
21*You can still use Glad versions less than 1.0.0 with older versions of Node/IOJS*
22
23<br>
24
25## What is Glad?
26Glad is a Node JS (MC / MVC) framework built on top of Express that aims to make common things that developers need available in an easy and intuitive manner.
27
28#### Main features.
29- REST-MC or MVC Workflow
30- Endpoint access policies
31- Class based controllers
32- Intuitive routing with route based body parsing and policies
33- Socket.io integration & Websocket routing with route based policies available
34- Built in session management
35- LRU/LFU controller action caching
36- General caching
37- Sane project directory structure
38- Works with any database
39- High test coverage
40- Leverages the Express ecosystem so you can too.
41- REST endpoint generation with Glad CLI
42- Utilities such as tokenization, number conversions, object tools, array tools, date tools and other various utilities.
43- Console Access to the server process with Glad CLI
44
45
46Out of the box, you get a Model/Controller framework which is great for building REST APIs for either client rendered apps
47such as Ember, Angular, React/Redux etc... or API Service development.
48Furthermore, Glad supports MVC and comes bundled with Pug (formerly Jade). Under the hood, view rendering is handled by Express. This gives you the utility of the Express eco-system right at your fingertips.
49
50If you are familiar with Node JS, then getting started with Glad is absolutely painless. If you are new to Node JS, then you should move along quite nicely and Glad will kick start your project in superhero fashion. In fact, you can get a REST API up and running in a matter of minutes after initializing your project.
51
52<br>
53
54## Installation
55
56The recommended way to install Glad is using Glad CLI. However, it is not necessary, but again highly reccomended. Previous versions of glad included the CLI. In versions >= 1.0.0, the CLI has been abstracted out to a separate package.
57
58#### Installing Glad CLI
59
60`npm install -g glad-cli`
61
62#### Installing Glad without the CLI
63`npm install glad --save`
64
65<br/>
66
67#### Creating a new project using Glad CLI
68
69Glad CLI has many options for initializing a project. The CLI supports several different databases including MongoDB, Postgresql, MySQL. In addition, Glad CLI supports either Mongoose, Waterline, or usage without an ORM/ODM. You can read the documentation for Glad-CLI [here.](https://github.com/gladjs/glad-cli/blob/master/README.md)
70
71**Example using MongoDB & Mongoose**
72- Create a new folder for your project
73- `cd /path/to/new-folder`
74- `glad init --odm=mongoose`
75
76**Example using Postgresql & Waterline**
77- Create a new folder for your project
78- `cd /path/to/new-folder`
79- `glad init --odm=waterline --adapter=sails-postgresql`
80
81#### Creating a project without Glad-CLI
82
83- Create a new folder for your project
84- `cd /path/to/new-folder`
85- `npm install glad --save`
86
87<br/>
88
89# Running / Development
90
91Let us begin with a brief overview of how Glad.JS works by taking a high level look at the request lifecycle.
92
93- A request comes in to `/foo`
94- The router locates the `/foo` route.
95- The router checks the route entry to see if there is a specific body parser it should use for this request. If not, it uses the default body parser defined in your `config.js` file. Either way, the body gets parsed.
96- The router then checks to see if there is a policy specified on the route entry.
97- If there is a policy, it routes the request to the policy and if it is accepted, the policy hands the request off to the correct controller/action combo.
98- If there is no policy, The router routes the request off to the correct controller/action combo.
99
100There's quite a lot happening behind the scenes to facilitate this flow, but that's basically the gist of it. You have routes, models, controllers, and (if you'd like) views. Routes point to controllers, controllers pull in models and complete the request with various types of data/views.
101
102Now lets take a look at getting set up. After installation, it's a good idea to take a look at the config file and make any neccesary adjustments. The config file contains all of the settings you'll need to change when either developing or deploying your application. It's safe to assume that you might want to read environment variables or dynamically change different configuration options based on any number of factors. You'll most likely want to do that here.
103
104
105### An overview of the config file
106```js
107{
108
109 port : 4242, // <-- The port that the http server will listen to
110
111 host : '0.0.0.0', // <-- The host that the http server will listen on, (Can be /tmp/sock)
112
113 logHTTP: true, // <-- Log HTTP request info to stdout
114
115 defaultBodyParser : { // <-- The default body parser
116 limit : '0.3mb',
117 type : 'json'
118 },
119
120 exposeModelsGlobally : true, // <-- Makes your models available in the global namespace
121
122 redis : {
123 host: "localhost", // <-- The Redis Host
124 port: 6379 // <-- The Redis Port
125 },
126
127 cookie : {
128 name : 'glad.sid', // <-- The name of your cookie
129 secret: 'change-this', // <-- The cookie secret
130 maxAge : 3600000 * 24, // <-- Cookie expiration date
131 },
132
133 session : {
134 storage: 'redis' // <-- Where sessions are stored. See the section on Sessions for more options
135 },
136
137 defaultViewEngine : 'pug', // <-- The default view engine to use
138
139 orm: "mongoose" // <-- This tells Glad CLI to use the mongoose blueprint when generating APIs
140
141}
142```
143
144<br>
145
146Next we'll have a look at policies. A policy is an asynchronous function that receives the request & response objects as well as accept and reject methods.
147If accept is invoked, the request gets routed to the controller action. If reject is invoked, the request is terminated and the **onFailure** method of the policies object is called. The reject method takes an optional argument. This argument can be anything, but it can be useful to extend the onFailure method.
148Maybe you want to pass functions into the **onFailure** method and have a default responder combined with custom responders. See `fig.p-1` for an example of this.
149
150### Policies
151
152```js
153 module.exports = {
154
155 /*
156 The onFailure method is a default that you can set for denying requests.
157 This method receives the req & res objects, as well as an optional rejection reason that you pass to the reject method.
158 */
159 onFailure (req, res, rejectMessage) {
160 res.status(403).json({error: rejectMessage});
161 },
162
163 /*
164 This method is intentionally overly verbose
165 to make it clear what is happening.
166 */
167 authenticated (req, res, accept, reject) {
168 if (req.session) {
169 if (req.session.authenticated) { // <--- What value on the session says they are logged in?
170 accept(); // Accept the request. All is good
171 } else {
172 reject("You must be logged in to do that"); // Reject the request. This will end up calling the above on failure method.
173 }
174 } else {
175 reject();
176 }
177 },
178
179 // <--- Add additional policies if needed. Examples below....
180 isDeveloper (req, res, accept, reject) {
181 if (req.session && req.session.developer) {
182 accept();
183 } else {
184 reject("You are not allowed to access this API");
185 }
186 },
187
188 // Note: This policy requires you to follow the convention /api/resource/:id
189 resourceOwnerOrAdmin (req, res, accept, reject) {
190
191 if (!req.params) {
192 return reject("Incorrect Parameters: Missing Parameters");
193 } else if (!req.params.id) {
194 return reject("Incorrect Parameters: Missing ID");
195 }
196
197 if (req.session && req.session.authenticated) {
198 if (req.session.user.admin) {
199 accept();
200 } else if (req.session.user.id === req.params.id) {
201 accept();
202 } else {
203 reject("You don't have access to this content");
204 }
205 } else {
206 reject("You must be logged in to do that.");
207 }
208 }
209 };
210```
211
212`fig.p-1` An example of a flexible approach:
213```js
214module.exports = {
215 onFailure (req, res, custom = false) {
216 if (custom && typeof custom === 'function') {
217 custom();
218 } else {
219 res.status(403).end()
220 }
221 },
222
223 authenticated (req, res, accept, reject) {
224 if (req.session.authenticated) {
225 accept();
226 } else {
227 reject(() => res.status(403).json({error: 'You must be logged in to do that'}));
228 }
229 },
230
231 never (req, res, accept, reject) {
232 reject();
233 }
234}
235```
236
237<br>
238
239Moving right along to routing. A route file is an object and the keys consist of HTTP verbs. (Any valid HTTP verb can be used) Each verb is an array route entries. This is how the router knows what controller action to route the request to, what policy to check, and if there is a specific body parser to use or the default. Each controller in your app will have it's own route file. You can think of it as resource based routing. Given a resource called `user` you should have the following files.
240
241- controllers/user.js
242- models/user.js
243- routes/user.js
244
245If you plan to render views, you should also have `views/user/some-view.pug`
246
247**A route entry**
248* path : matching url
249* action : the controller method to call when this route is matched
250* policy : the policy method to call in order to determine if the action is allowed. * see policies.js
251* bodyParser: A body parser configuration *(See Below)*
252
253**A resource**
254- model
255- route
256- controller
257- view folder
258
259<br>
260
261## Sessions
262
263Glad will automatically handle sessions for you.
264However, sometimes you may not want this, or you have a different use case.
265If you remove the session object key from `config.js`, Glad will not handle sessions for you.
266It will be up to you to implement it on your own.
267You would likely want to do this in `middleware.js`.
268You can also do a hybrid implementation where glad will handle the session in some cases, and not in others.
269In order to accomplish this, you will need to create a file in your App's root directory called session.js.
270This file should export a method that receives `req, res, next` and returns a Promise. If you resolve the promise with true, Glad will use it's session middleware, if you resolve with false, Glad will not use session middleware for the request. This allows you to choose whether or not you want to use a session on a request by request basis. It's important that if you do resolve with false, that you invoke `next()`, otherwise the middleware chain will halt and the request will stall then timeout. Futhermore, if you resolve with true, do not invoke `next()`.
271
272#### TLDR:
273
274- if you create a file named session.js, Glad will route the middleware through it first.
275- session.js must return a Promise
276- resolving true tells Glad to implement the session.
277- resolving false tells Glad not to implement the session.
278- invoking `next()` and resolving with `true` is a bad idea.
279- not invoking `next()` and resolving with `false` will break your App.
280- if you don't create the session.js file, Glad will handle sessions normally.
281- If you don't want to use sessions at all, remove the session object key from `config.js`
282
283<br>
284
285## Routing
286
287In the routes folder file you will find your routes. The routes object is organized by request method.
288
289```
290 module.exports = {
291 GET: [{
292 path: '/users', // <--- what url does this entry match?
293 action: 'GET', // <--- what controller method should handle this request?
294 policy: 'authenticated' // <--- what policy applies to this route?
295 },{
296 path: '/users/:id',
297 action: 'findOne',
298 policy: 'resourceOwnerOrAdmin' // <--- Not built in, but in the policies example above.
299 }],
300
301 POST: [{
302 path: '/users',
303 action: 'POST',
304 policy: 'authenticated'
305 }],
306
307 PUT: [{
308 path: '/users/:id',
309 action: 'PUT',
310 policy: 'authenticated'
311 }],
312
313 DELETE: [{
314 path: '/users/:id',
315 action: 'DELETE',
316 policy: 'authenticated'
317 }]
318 }
319```
320
321If you need to override the default body parser or the max body limit you can do this per endpoint.
322
323```
324bodyParser : {
325 limit : '10kb',
326 parser : 'json'
327}
328```
329
330You can also use a custom body parser.
331
332As an example, maybe you're using multer for file uploads etc...
333
334```
335bodyParser : {
336 custom: multer({ dest: '/tmp/uploads/' }).single('file')
337}
338```
339
340Or not...
341```
342bodyParser : {
343 custom (req, res, next) {
344 ... do stuff
345 next();
346 }
347}
348```
349
350<br><br>
351
352# The Controller
353
354Each request creates a new instance of your controller. Your controller should always extend the **GladController**.
355
356Methods / Objects available using `this` from within an action.
357
358| method / object | Short Description |
359| :-------------- |:------------------|
360| cache | Caching class |
361| actionCache | Action caching store |
362| cacheStore | Current request's cache store |
363| req | Express request object |
364| res | Express response object |
365| params | Alias for `this.req.params` |
366| body | Alias for `this.req.body` |
367| query | Alias for `this.req.query` |
368| redisClient | Redis Connection |
369| permit | A method that removes non-whitelisted objects. Nested Objects are not allowed without using dot notation to specify the nested keys. |
370| deepPermit | A method that removes non-whitelisted objects. Nested Objects are allowed. |
371| permitted | Boolean, has this action called permit. This may be useful to enforce permitting. |
372| render | Renders a view. |
373
374## Methods / Objects in detail
375
376### cache
377
378Actions can be cached in two ways. Using the callback method or the chainable method.
379
380**The callback method**
381
382When using the callback method you tell the controller that you want to cache the result of this method for next time.
383You will provide some configuration regarding what type of strategy you'd like to use, as well as the max number of items that this particular
384method is allowed to store in cache. This allows you to better understand how much data you'll be putting into your caches, and allows you to
385favor certain methods over other (maybe less important) methods. The available named strategies are `"LRU"` and `"LFU"` for controller actions.
386If you would prefer to implement your own strategy, you can specify that by providing a additional parameters called score and increment. For LRU cache the scoring function is simply `() => new Date().getTime()` with `increment : false`. For LFU it is `() => 1` with `increment : true`.
387
388Now to the callback. Below you can see that we are sending in a callback that contains our code to generate the data we need to send back to the client.
389It will receive a caching function that you should call once your final data is generated.
390The first time this request happens, it runs your code to generate the response. The next time, it will not run the callback. Instead it will get the data from redis.
391
392```javascript
393 GET () {
394 this.cache({ max: 200, strategy: 'LFU' }, cache => {
395 SomeModel.find()
396 .limit(15)
397 .then(users => this.res.json(users) && cache(users))
398 .catch(err => this.error(err))
399 });
400 }
401```
402
403It is worth noting that a cache hit is defined by the url including any query parameters. So '/api/widgets?foo=1&bar=2' !== `/api/widgets?foo=1`.
404
405##### The chain-able method.
406
407Just like the callback method, the code that runs to generate the data you need to send back to the client only gets executed on a cache:miss. In this case, it's a callback sent into the miss method. You also have an optional hit method that receives the cached data so that you can determine how you'd like to respond. (This could be useful when you'll be rendering a view with the data)
408
409```javascript
410this.cache({ max: 100, strategy: 'LRU' })
411 .miss(cache => {
412 Products.find({category: 'widgets'}).exec( (err, widgets) => {
413 this.res.status(200).json(widgets);
414 cache(widgets);
415 });
416 })
417 .hit(data => this.res.status(200).json(data))
418 .exec();
419```
420
421
422##### How To?
423
424What if i'm not using json? `this.cache({ max: 2, strategy: 'LFU', type: 'html' })` you can pass in an additional parameter called type. This can be any type that express understands.
425
426How do I enable caching in development? You can enable caching in development by either using the cache flags `--enable-cache` when starting up your server or console. Or you can do it in an initializer by setting `Glad.cache.disabled = false`
427
428How do I disable caching in non-development environments? You can disable caching in development by either using the cache flags `--disable-cache` when starting up your server or console. Or you can do it in an initializer by setting `Glad.cache.disabled = true`
429
430How do I progromatically enable or disable Glad caching based on something else, like an environment variable? You can set `Glad.cache.disabled` anywhere you want based off of anything. (The preferred way is to do this in an initializer)
431
432<br/>
433
434### actionCache
435
436The **actionCache** method retrieves the cache store used for a specific method on your controller. Since controller methods have their own separate namespaced cache, you will need to look up an action's cache namespace if you want to operate on it from a different action. This is especially useful if you need to drop/re-warm a cache when data changes. As an example, let's say that you have a controller setup for widgets, and when you HTTP PUT a widget, you want to remove that item from the cache. Let's also assume that `FindOne` handles the GET requests for a specific widget, and the GET method handles GET requests for an array of widgets.
437
438From the PUT action on the controller
439
440```js
441this.actionCache('FindOne').del('/widgets/12').then(..do stuff);
442this.actionCache('GET').del('/widgets').then(..do stuff);
443```
444
445### cacheStore
446
447The **cacheStore** object is an instance of **ControllerCache** delegated to the current controller instance.
448This means that `this.cacheStore` only operates on the current action's cache namespace.
449
450API: All methods return a **Promise**.
451
452| method | Description |
453| --- | ---|
454| `set(key, value, maxAge)` | set value for the given key, marking it as the most recently accessed one. Keys should be strings, values will be - - - JSON.stringified. The optional maxAge overrides for this specific key the global expiration of the cache. |
455|`get(key)` | resolve to the value stored in the cache for the given key or null if not present. If present, the key will be marked as the most recently accessed one.|
456| `getOrSet(key, fn, maxAge)` | resolve to the value stored in the cache for the given key. If not present, execute fn, save the result in the cache and return it. fn should be a no args function that returns a value or a promise. If maxAge is passed, it will be used only if the key is not already in the cache.|
457| `peek(key)` | resolve to the value stored in the cache for the given key, without changing its last accessed time.|
458| `del(key)` | removes the item from the cache. |
459| `reset()` | empties the cache. |
460| `has(key)` | resolves to true if the given key is present in the cache. |
461| `keys()` | resolves to an array of keys in the cache, sorted from most to least recently accessed. |
462| `values()` | resolves to an array of values in the cache, sorted from most to least recently accessed. |
463| `count()` | resolves to the number of items currently in the cache. |
464
465
466### req / res / params / query / body
467
468See [Express Request](https://expressjs.com/en/api.html#req)
469<br/>
470See [Express Response](https://expressjs.com/en/api.html#res)
471<br/>
472See [req.params](https://expressjs.com/en/api.html#req.params)
473<br/>
474See [req.query](https://expressjs.com/en/api.html#req.query)
475<br/>
476See [req.body](https://expressjs.com/en/api.html#req.body)
477
478### redisClient
479
480Initialized redis client. See [redis package on NPM](https://www.npmjs.com/package/redis)
481
482### permit / deepPermit / permitted
483
484Whitelist allowable data in a request body. This is a good idea if you are mass assigning things.
485
486Ex: Shallow
487
488```js
489this.req.body = { name: 'Fooze', admin: true };
490this.permit('name', 'email', 'phone');
491// this.req.body === { name: 'Fooze' }
492```
493
494Ex: Deep
495```js
496this.req.body = { user: { name: 'Fooze', admin: true } };
497this.permit('user.name', 'user.email', 'user.phone');
498// this.req.body === { user: { name: 'Fooze' } }
499```
500
501- You must be specific when the key contains an object.
502- You cannot permit the whole user object at once. In order to permit "sub documents" you need to use the **deepPermit** method instead. This is intentional because it can defeat the purpose of **permit** when you permit a subdocument that could potentially contain things that shouldn't be allowed.
503
504After calling permit, `this.permitted` will be true.
505
506
507### render
508
509The render method is an enhanced version of Express' **res.render**. Calling `this.render` from a controller automatically looks in the correct folder for the view file. As an example, let's say you have a controller for a resource called widgets. This means that there should be a folder in the views directory called widgets. Invoking `this.render('my-widget', {data: 'stuff'})` will render the view located at `views/widgets/my-widget.pug`.
510
511
512<br><br>
513
514
515## Web Sockets / Real Time API
516<img src="https://socket.io/assets/img/logo.svg" height="35px">
517
518Glad comes with full support for websockets and an intuitive convention in dealing with it.
519
520**Features**
521- Session integration
522- Routing
523- Policies
524
525There should be a directory in your project root called websockets. The websockets directory consists of a few files.
526- router.js
527- policies.js
528
529The router works very much like the Glad HTTP router. Each entry must specify an event and an action. The policy is optional.
530The action is a function that will be invoked when a connection sends a message to the given event. Furthermore the value of `this`
531in the action will be the instance of SocketIo.
532
533```javascript
534module.exports = [{
535 event: 'hello',
536 action (message, connection) {
537 connection.emit('hello', message);
538 },
539 policy : 'canSayHello'
540},{
541 event: 'subscribeToRomm',
542 action (room, connection) {
543 socket.join(room);
544 },
545 policy: 'loggedIn'
546}];
547```
548For illustrative purposes, the action method is defined in the example above. However, you may find that it's good to create another file(s) in the sockets directory that will define your handlers, like below.
549
550```javascript
551
552let chats = require('./chats');
553
554module.exports = [{
555 event: 'hello',
556 action: chats.hello,
557 policy : 'canSayHello'
558},{
559 event: 'subscribeToRomm',
560 action: chats.joinRoom,
561 policy: 'loggedIn'
562}];
563```
564
565
566Policies are also similar to the HTTP policies. They receive two arguments: the connection that sent the message and an accept method.
567If you choose not to call the accept method, then it is rejected by default.
568
569```javascript
570module.exports = {
571 canSayHello (connection, accept) {
572 if (connection.request.session.loggedIn) { // By default glad handles syncing up the session to the socket connection.
573 accept();
574 }
575 }
576};
577```
578
579## Envrironment
580
581#### Set the environment
582
583Create an environment variable on your machine called `GLAD_ENV` OR `NODE_ENV`.
584
585Glad will first check GLAD_ENV. If it does not exist, it will set GLAD_ENV to the value of NODE_ENV. After that, Glad will only take GLAD_ENV into account. Glad will keep GLAD_ENV and NODE_ENV in sync for the process. for example if you set `GLAD_ENV` it will set `NODE_ENV` to `GLAD_ENV` and vice versa. If you have not set either, it sets both to `development`
586
587
588## Flags
589
590#### Only
591
592 `--only=[controller|controllers,controllers]`
593 Using the only flag, you can spin up a server that only binds routes for a specific controller or a group of controllers.
594
595 As an example, `glad s --only=posts` would launch your app and allow traffic to routes handled by the posts controller.
596
597 If you would like to launch a group of controllers, you would simply comma separate the controllers such as `glad s --only=posts,users`.
598
599 The convention is such that you provide the lowercase filename of your controller(s) that should be enabled.
600
601 If you are not using Glad CLI, this will still work with the node binary.
602
603 `node index.js --only=posts`
604
605#### Server Flags
606
607`--port` | `--host` | `--sock` | `--backlog`
608Using the server flags you can over ride the values in your config.js file.
609
610#### Cache Flags
611`--enable-cache` | `--disable-cache`
612Using the caching flags you can disable caching for non-development environments or enable cache on development environments
613
614
615## Classes / Helpers / Tools
616
617Glad comes with lots of helpful utilities.
618
619### intermission
620
621> Added in 1.1.4
622
623Glad.intermission is a great way to space out operations (asynchronous). For example, let's say that you want to make an external API call for every model that a query returned, but the API specifies that you should only make 1 request per second to it. You can use Glad.itermission to accomplish this.
624
625```
626 Model.find(...).then(async records => {
627
628 let i = 0;
629 let count = records.length;
630
631 for (i; i < count; i +=1) {
632 await someApi.lookupAndDoStuff(records[i].id);
633 await Glad.intermission(1000);
634 }
635
636 })
637
638```
639
640The above will wait 1 second between each iteration of the for loop.
641
642### String
643
644Assume `string === Glad.string` for the examples below.
645
646- color
647 - Produces a colorized string for stdout. `string.color('Oh No', 'red')`
648 - Can be any color name supported by chalk.js
649
650- deburr
651 - converts unicode
652
653 `string.deburr('♥') === 'love'`
654
655 - converts short symbols
656
657 `string.deburr('<3') === 'love'`
658
659 `string.deburr('released w/ stuff') === 'released with stuff'`
660
661 `string.deburr('live && loud') === live and loud'`
662
663 `string.deburr(':) || sad') === 'smile or sad'`
664
665 - converts Latin characters
666
667 `string.deburr('À') === 'A'` <i>See fig s1 at the bottom of the page for a list of all supported characters</i>
668
669 - converts Greek characters
670
671 `string.deburr('α') === 'a'` <i>See fig s2 at the bottom of the page for a list of all supported characters</i>
672
673 - converts Turkish characters
674
675 `string.deburr('ş') === 's'` <i>See fig s3 at the bottom of the page for a list of all supported characters</i>
676
677 - converts Russian characters
678
679 `string.deburr('ф') === 'f'` <i>See fig s4 at the bottom of the page for a list of all supported characters</i>
680
681- slugify
682
683 Creates a normalized slug from a string
684
685 `string.slugify('San Francisco, CA') === 'san-francisco-ca'`
686
687 `string.slugify('The brown fox w/ βeta') === 'the-brown-fox-with-beta'`
688
689 `string.slugify('good news so you can :)') === 'good-news-so-you-can-smile'`
690
691 If you need to create a multi-variable slug such as `first_name + " " + last_name` etc... consider allowing slugify to do the concatenation for you. `slugify` can receive "n" arguments which become concatenated to form additional segments of the slug.
692
693 `string.slugify('how to create stuff', 'June 17 2017') === 'how-to-create-stuff-june-17-2017'`
694
695- camelize
696
697 `string.camelize("fooze-barz") === 'foozeBarz'`
698
699
700- titleize
701
702 `string.titelize("fooze barz") === 'Fooze Barz'`
703
704- slasherize
705
706 `string.slasherize("fooze barz") === "fooze/barz"`
707
708- reverseSlasherize
709
710 `string.reverseSlasherize("fooze barz") === "barz/fooze"`
711
712- underscore
713
714 `string.underscore("fooze barz") === "fooze_barz"`
715
716
717- cleanSpaces
718
719 `string.cleanSpaces("fooze barz") === "fooze barz"
720
721- endsWith
722
723 `string.endsWith("fooze barz fooze", "fooze") === true`
724
725 `string.endsWith("fooze barz", "fooze") === false`
726
727
728- escape
729
730 `string.escape("fred, barney, & pebbles") === "fred, barney, &amp; pebbles"
731
732
733- unescape
734
735 `string.unescape("fred, barney, &amp; pebbles") === "fred, barney, & pebbles"`
736
737- escapeRegExp
738
739 escapes a string for use in regexp
740
741 `string.escapeRegExp('[lodash](https://lodash.com/)') === "\\[lodash\\]\\(https://lodash\\.com/\\)"`
742
743
744- repeat
745
746 `string.repeat("glad", 2) === "gladglad"`
747
748
749- startsWith
750
751 `string.startsWith("fooze barz", "fooze") === true`
752
753 `string.startsWith("x fooze barz", "fooze") === false`
754
755
756- words
757
758 split a string based on words
759
760 `string.words('fred, barney, & pebbles') === ['fred', 'barney', 'pebbles']`
761
762 split a string based on words and keep elements by regexp
763
764 `string.words('fred, barney, & pebbles', /[^, ]+/g) === ['fred', 'barney', '&', 'pebbles']`
765
766- sentenceCase
767
768 Capitalizes letters at the beginning of a string, and after periods.
769
770### Objects
771
772Assume object refences Glad.object
773
774- get
775
776 Pick a value from an object
777 ```
778 let o = {foo: {bar : 'foobar' }};
779 object.get(o, 'foo.bar') => 'foobar'
780 ```
781
782- extend
783
784 Extend `object at argument 1` with `object at argument 2,3,4,n`. The order of extension is from left to right. Duplicate values will be taken from the right-most arguments.
785
786 ```
787 let src = {one: 1};
788 let ext = {two: 2};
789 assert.deepEqual(object.extend(src, ext), {one: 1, two: 2})
790 ```
791
792 ```
793 let src = {one: 1};
794 let ext = {two: 2};
795 let ext2 = {three: 3};
796 assert.deepEqual(object.extend(src, ext, ext2), {one: 1, two: 2, three: 3});
797 ```
798
799 ```
800 let src = {one: 1};
801 let ext = {one: 2};
802 let ext2 = {three: 3};
803 assert.deepEqual(object.extend(src, ext, ext2), {one: 2, three: 3});
804 ```
805
806 ```
807 let src = {one: 1};
808 let ext = {two: 2};
809 let ext2 = {two: 3};
810 let ext3 = {two: 4};
811 assert.deepEqual(object.extend(src, ext, ext2, ext3), {one: 1, two: 4});
812 ```
813
814- hasPath
815
816 return a boolean value if an object contains a path
817
818 ```
819 let src = {one: 1};
820 assert.equal(object.hasPath(src, 'one'), true);
821 assert.equal(object.hasPath(src, 'one.value.at.nowhere'), false);
822 ```
823
824- clone
825
826 return a new object
827
828 ```
829 let src = {one: 1, two: {a: 1}};
830 assert.deepEqual(object.clone(src), {one: 1, two: {a: 1}});
831 ```
832
833 new object should not be passed by reference
834
835 ```
836 let src = {one: 1, two: {a: 1} };
837 let src2 = object.clone(src);
838 src2.two.a = 3;
839 assert.equal(src.two.a, 1);
840 assert.equal(src2.two.a, 3);
841 ```
842
843- set
844
845 set a value at the given path (null/undefined safe)
846
847 ```
848 let src = {};
849 object.set(src, 'foo', 1);
850 assert.equal(src.foo, 1);
851 ```
852
853 ```
854 let src = {};
855 object.set(src, 'foo.bar.baz.x', 1);
856 assert.equal(src.foo.bar.baz.x, 1);
857 ```
858
859- arrayToObject
860
861 convert an array of arrays to an object
862
863 ```
864 let arr = [['a', 1], ['b', 2]];
865 let o = object.arrayToObject(arr);
866 assert.equal(o.a, 1);
867 assert.equal(o.b, 2);
868 ```
869
870- invert
871
872 invert an object
873
874 ```
875 let o = {a: 'A', b: 'B'};
876 object.invert(o);
877 assert.deepEqual(o, {A: 'a', B: 'b'});
878 assert.equal(o.a, undefined);
879 ```
880
881- createShallowInvertedClone
882
883 Creates an inverted shallow clone of an object. This is particulary useful when creating data maps. As an example say that you want an Object called usStatesToAbbreviations that acts as a map of U.S States to their respective abbreviation. It would be easy to create the inverse object from it via `createShallowInvertedClone`, which would generate a map of U.S State Abbreviations to their respective State.
884
885 ```
886 let o = {a: 'A', b: 'B'};
887 let x = createShallowInvertedClone(o); // Glad.object.createShallowInvertedClone
888 assert.deepEqual(x, {A: 'a', B: 'b'});
889 assert.equal(x.a, undefined); // Non-inverted keys should not be present
890 assert.deepEqual(o, {a: 'A', b: 'B'}); // Original should be non-inverted
891 ```
892
893- select
894
895 select keys from an object (creates a new object)
896
897 ```
898 let o = {name: 'fred', email: 'me@mail.com', password: 'secretSquirrel'};
899 let fields = object.select(o, 'name', 'email');
900 assert.deepEqual(fields, {name: 'fred', email: 'me@mail.com'});
901 ```
902
903 select keys from an object via Array
904
905 ```
906 let o = {name: 'fred', email: 'me@mail.com', password: 'secretSquirrel'};
907 let fields = object.select(o, ['name', 'email']);
908 assert.deepEqual(fields, {name: 'fred', email: 'me@mail.com'});
909 ```
910
911- drop
912
913 drop keys from an object
914
915 ```
916 let o = { name: 'fred', email: 'me@mail.com', password: 'secretSquirrel' };
917 object.drop(o, 'password');
918 assert.deepEqual(o, {name: 'fred', email: 'me@mail.com'});
919 ```
920
921 drop keys from an object via Array
922
923 ```
924 let o = { name: 'fred', email: 'me@mail.com', password: 'secretSquirrel'
925 object.drop(o, ['password']);
926 assert.deepEqual(o, {name: 'fred', email: 'me@mail.com'});
927 });
928 ```
929
930- selectCombination
931
932 select values from multiple objects and create a new one
933
934 ```
935 let a = {
936 name: 'fred',
937 email: 'me@mail.com',
938 password: 'secretSquirrel'
939 };
940 let b = { sid: '8372487234', last_visit: new Date()};
941 let c = { likes : 'stuff', knows: 'things'};
942 let o = object.selectCombination([a, b, c], 'name', 'email', 'last_visit', 'likes', 'knows');
943 assert.deepEqual(o, { name: 'fred', email: 'me@mail.com', last_visit: b.last_visit, likes : 'stuff', knows: 'things'});
944 });
945 ```
946
947- format
948
949 Create a new object from an existing one, but reformat the keys.
950 Individual arguments such as 'email' or 'name' copy the value for the respective key to the new object maintaining the same key name. In order to map a value from the existing array to a new key on the new object, you use an array. [`destination path`, `source path`].
951
952 ```
953 let a = {
954 name: 'fred',
955 email: 'me@mail.com',
956 data: {
957 stuff: { a: 'a', b: 'b'},
958 more : { c: 'value'}
959 }
960 };
961
962 let o = object.format(a, 'name', 'email', ['stuff', 'data.stuff.a' ], ['value', 'data.more.c']);
963
964 assert.deepEqual(o, {
965 name: 'fred',
966 email: 'me@mail.com',
967 stuff: 'a',
968 value : 'value'
969 });
970
971 });
972 ```
973
974 Format the keys using an array
975
976 ```
977 let a = {
978 name: 'fred',
979 email: 'me@mail.com',
980 data: {
981 stuff: { a: 'a', b: 'b'},
982 more : { c: 'value'}
983 }
984 };
985
986 let o = object.format(a, [
987 'name',
988 'email',
989 ['stuff', 'data.stuff.a' ],
990 ['value', 'data.more.c']
991 ]);
992
993 assert.deepEqual(o, {
994 name: 'fred',
995 email: 'me@mail.com',
996 stuff: 'a',
997 value : 'value'
998 });
999
1000 });
1001 ```
1002
1003- explode
1004
1005 Explode an object
1006
1007 ```
1008
1009 let row = {
1010 'id': 2,
1011 'contact.name.first': 'John',
1012 'contact.name.last': 'Doe',
1013 'contact.email': 'example@gmail.com',
1014 'contact.info.about.me': 'classified',
1015 'devices.0': 'mobile',
1016 'devices.1': 'laptop',
1017 'some.other.things.0': 'this',
1018 'some.other.things.1': 'that',
1019 'some.other.stuff.0.key': 'stuff'
1020 };
1021
1022 object.explode(row);
1023
1024 assert.deepEqual(row, {
1025 "id": 2,
1026 "contact": {
1027 "name": {
1028 "first": "John",
1029 "last": "Doe"
1030 },
1031 "email": "example@gmail.com",
1032 "info": {
1033 "about": {
1034 "me": "classified"
1035 }
1036 }
1037 },
1038 "devices": [
1039 "mobile",
1040 "laptop"
1041 ],
1042 "some": {
1043 "other": {
1044 "things": [
1045 "this",
1046 "that"
1047 ],
1048 stuff : [{
1049 key : "stuff"
1050 }]
1051 }
1052 }
1053 });
1054 });
1055 ```
1056
1057### Number
1058
1059Assume number is Glad.number
1060
1061- Time Constants
1062 ```
1063 assert.equal(number.SECOND, 1000);
1064 assert.equal(number.MINUTE, 1000 * 60);
1065 assert.equal(number.HOUR, 1000 * 60 * 60);
1066 assert.equal(number.DAY, 1000 * 60 * 60 * 24);
1067 ```
1068
1069- parse
1070
1071 Strip off weird stuff
1072
1073 ```
1074 assert.equal(number.parse("$34.72"), 34.72);
1075 assert.equal(number.parse("65.323%"), 65.323);
1076 assert.equal(number.parse("65%"), 65);
1077
1078 // RESPECTS NEGATIVE NUMBERS
1079 assert.equal(number.parse("-65%"), -65);
1080 assert.equal(number.parse("-$65.34"), -65.34);
1081 assert.equal(number.parse("-78.32-"), -78.32);
1082 ```
1083
1084
1085- random
1086
1087 generate a random number between x,y
1088
1089 ```
1090 let rand = number.random(5,10);
1091 assert.equal(rand <= 10, true);
1092 assert.equal(rand >= 5, true);
1093 ```
1094
1095- withDelimiter
1096
1097 Simply format a number with commas and decimals
1098
1099 ```
1100 assert.equal(number.withDelimiter(4), '4.00');
1101 assert.equal(number.withDelimiter(45), '45.00');
1102 assert.equal(number.withDelimiter(450), '450.00');
1103 assert.equal(number.withDelimiter(4500), '4,500.00');
1104 assert.equal(number.withDelimiter(45000), '45,000.00');
1105 assert.equal(number.withDelimiter(450000), '450,000.00');
1106 assert.equal(number.withDelimiter(4500000), '4,500,000.00');
1107 assert.equal(number.withDelimiter(45000000), '45,000,000.00');
1108 assert.equal(number.withDelimiter(450000000), '450,000,000.00');
1109 assert.equal(number.withDelimiter(4500000000), '4,500,000,000.00');
1110 assert.equal(number.withDelimiter(45000000000), '45,000,000,000.00');
1111 assert.equal(number.withDelimiter(450000000000), '450,000,000,000.00');
1112 assert.equal(number.withDelimiter(4500000000000), '4,500,000,000,000.00');
1113 assert.equal(number.withDelimiter(45000000000000), '45,000,000,000,000.00');
1114 assert.equal(number.withDelimiter(450000000000000), '450,000,000,000,000.00');
1115 assert.equal(number.withDelimiter(99e19), '990,000,000,000,000,000,000.00');
1116 ```
1117
1118- toAbbr
1119
1120 Abbreviate a number
1121
1122 ```
1123 assert.equal(number.toAbbr(45000), '45k');
1124 assert.equal(number.toAbbr(450000), '450k');
1125 assert.equal(number.toAbbr(4500000), '4.5m');
1126 assert.equal(number.toAbbr(45000000), '45m');
1127 assert.equal(number.toAbbr(450000000), '450m');
1128 assert.equal(number.toAbbr(4500000000), '4.5b');
1129 assert.equal(number.toAbbr(45000000000), '45b');
1130 assert.equal(number.toAbbr(450000000000), '450b');
1131 assert.equal(number.toAbbr(450), '450');
1132 assert.equal(number.toAbbr(4500), '4.5k');
1133 ```
1134
1135- `toData(bytes)`
1136
1137 format a number in data units
1138
1139 ```
1140 assert.equal(number.toData(126.02 * 1000), '126.0 kB');
1141 assert.equal(number.toData(126.32 * 1000), '126.3 kB');
1142 assert.equal(number.toData(126.32 * 1000 * 1000), '126.3 MB');
1143 assert.equal(number.toData(126.32 * Math.pow(1000, 3)), '126.3 GB');
1144 assert.equal(number.toData(126.32 * Math.pow(1000, 4)), '126.3 TB');
1145 assert.equal(number.toData(126.32 * Math.pow(1000, 5)), '126.3 PB');
1146 assert.equal(number.toData(126.32 * Math.pow(1000, 6)), '126.3 EB');
1147 assert.equal(number.toData(126.32 * Math.pow(1000, 7)), '126.3 ZB');
1148 assert.equal(number.toData(126.32 * Math.pow(1000, 8)), '126.3 YB');
1149 ```
1150
1151- `toTime(seconds, returnArray = false)`
1152
1153 format a number in time
1154
1155 ```
1156 const HOUR = 60 * 60;
1157 const DAY = 24 * HOUR;
1158
1159 assert.equal(number.toTime(50), '50 sec');
1160 assert.equal(number.toTime(60), '1 min');
1161 assert.equal(number.toTime(HOUR), '1 hr');
1162 assert.equal(number.toTime(DAY), '1 day');
1163 assert.equal(number.toTime(DAY * 30), '30 days');
1164 assert.equal(number.toTime( (DAY * 2) + 10), '2 days 10 sec');
1165 assert.equal(number.toTime( (DAY * 2) + (HOUR * 2) + 32), '2 days 2 hr 32 sec');
1166 ```
1167
1168 format a number in time and return an array
1169
1170 ```
1171 assert.deepEqual(number.toTime(50, true), [0, 0, 0, 50]);
1172 assert.deepEqual(number.toTime(60, true), [0, 0, 1, 0]);
1173 assert.deepEqual(number.toTime(HOUR, true), [0, 1, 0, 0]);
1174 assert.deepEqual(number.toTime(DAY, true), [1, 0, 0, 0]);
1175 assert.deepEqual(number.toTime(DAY * 30, true), [30, 0, 0, 0]);
1176 assert.deepEqual(number.toTime( (DAY * 2) + 10, true), [2, 0, 0, 10]);
1177 assert.deepEqual(number.toTime( (DAY * 2) + (HOUR * 2) + 32, true), [2, 2, 0, 32]);
1178 ```
1179
1180- `toCurrency(number, precision, decimal, comma)`
1181
1182 format a number in USD currency (convienience method)
1183
1184 ```
1185 assert.equal(number.toCurrency(240.658), '$240.66');
1186 assert.equal(number.toCurrency(-376240.658), '$-376,240.66');
1187 ```
1188
1189 If you need to format a number in other currencies, use the NumberFormatter Class.
1190
1191 ```
1192 let toGBP = new Glad.number.NumberFormatter("£", false, 2, ',', '.');
1193 assert.equal(toGBP(1234567.89), '£1.234.567,89');
1194 ```
1195
1196- `toPercent(number, comma = ",", decimal = ".")`
1197
1198 format a number as a percentage
1199
1200 ```
1201 assert.equal(number.toPercent(43.47576353), '43.48%');
1202 assert.equal(number.toPercent(43.47576353, 4), '43.4758%');
1203 assert.equal(number.toPercent(43873.47581765327, 4, '*', '\''), "43'873*4758%");
1204 assert.equal(number.toPercent(-43.47576353), '-43.48%');
1205 assert.equal(number.toPercent(-43.47576353, 4), '-43.4758%');
1206 ```
1207
1208
1209- `toPhone(number = "", formatAreaCode = false, extension = false)`
1210
1211 format a number as a phone number
1212 ```
1213 assert.equal(number.toPhone(9255551212), "925-555-1212");
1214 assert.equal(number.toPhone('9255551212'), "925-555-1212");
1215 assert.equal(number.toPhone(9255551212, true), "(925) 555-1212");
1216 assert.equal(number.toPhone(9255551212, true, 4528), "(925) 555-1212 x4528");
1217 ```
1218
1219### imports
1220Imports is a require alias with some nifty features specifically for Glad. When using the imports method, paths are imported relative to your working directory.
1221
1222Examples: (Assume imports is Glad.imports)
1223
1224Both controllers and models can be imported without pluralization
1225
1226`imports('UserModel')` => `require('./models/user')`
1227
1228Importing the widget model from within the directory `queries/widgets/find-by-name`
1229
1230`imports('UserModel')` => `require('../../models/user')`
1231
1232Importing using reverse camelize
1233
1234`imports('UserQueryMocksTest')` => `require('./test/mocks/query/user')`
1235
1236Importing normally from within a sub-directory
1237
1238`imports('classes/fooze')` => `require('../../classes/fooze')`
1239
1240### Dates
1241
1242Glad's Date class allows you to create different instances to use for different timezone offsets. To use Glad's Date class you'll need to install moment. `npm install moment --save`
1243
1244For each example below assume these variables are created.
1245
1246```
1247let moment = require('moment');
1248let utcDate = new Glad.Date(moment);
1249let pstDate = new Glad.Date(moment, -8);
1250```
1251When creating an inistance of Glad.Date, the first argument is moment, and the 2nd is the offset to use. All methods return a Javascript Date Object.
1252
1253Also assume that the current Date is January 1st 1970 12:00am
1254
1255- monthsFromNow
1256 ```
1257 utcDate.monthsFromNow(1) => 1970-02-01T00:00:00.000Z
1258 utcDate.monthsFromNow(12) => 1971-01-01T00:00:00.000Z
1259 utcDate.monthsFromNow(24) => 1972-01-01T00:00:00.000Z
1260 ```
1261
1262
1263- weeksFromNow
1264
1265 ```
1266 utcDate.weeksFromNow(1) => 1970-01-08T00:00:00.000Z
1267 ```
1268
1269- daysFromNow
1270
1271 ```
1272 utcDate.daysFromNow(1) => 1970-01-02T00:00:00.000Z
1273 utcDate.daysFromNow(10) => 1970-01-11T00:00:00.000Z
1274 ```
1275
1276
1277- hoursFromNow
1278
1279 ```
1280 utcDate.hoursFromNow(1) => 1970-01-01T01:00:00.000Z')
1281 ```
1282
1283- minutesFromNow
1284
1285 ```
1286 utcDate.minutesFromNow(1) => 1970-01-01T00:01:00.000Z
1287 utcDate.minutesFromNow(14) => 1970-01-01T00:14:00.000Z
1288 ```
1289
1290- secondsFromNow
1291
1292 ```
1293 utcDate.secondsFromNow(1) => 1970-01-01T00:00:01.000Z
1294 utcDate.secondsFromNow(10) => 1970-01-01T00:00:10.000Z
1295 utcDate.secondsFromNow(61) => 1970-01-01T00:01:01.000Z
1296 ```
1297
1298- startOfSecond
1299
1300 Supply a Date Object and this will return a new date object with milliseconds zeroed out.
1301
1302
1303- startOfMinute
1304
1305 Supply a Date Object and this will return a new date object with milliseconds and seconds zeroed out.
1306
1307- startOfHour
1308
1309 Supply a Date Object and this will return a new date object with milliseconds, seconds and minutes zeroed out.
1310
1311- startOfDay
1312
1313 Supply a Date Object and this will return a new date object with milliseconds, seconds, minutes, and hours zeroed out.
1314
1315
1316
1317- startOfWeek
1318
1319 Supply a Date Object and this will return a new date object set to the beginning of the week that the supplied date landed in.
1320 ```
1321 let testDate = utcDate.daysFromNow(22);
1322 let startOfWeek = utcDate.startOfWeek(testDate);
1323 startOfWeek.toISOString() => 1970-01-18T00:00:00.000Z
1324 ```
1325
1326- startOfMonth
1327
1328 Supply a Date Object and this will return a new date object set to the beginning of the month that the supplied date landed in.
1329 ```
1330 let testDate = utcDate.daysFromNow(22);
1331 utcDate.startOfMonth(testDate) => 1970-01-01T00:00:00.000Z
1332 ```
1333
1334- startOfQuarter
1335
1336 Supply a Date Object and this will return a new date object set to the beginning of the quarter that the supplied date landed in.
1337
1338 ```
1339 let testDate = utcDate.daysFromNow(2);
1340 utcDate.startOfQuarter(testDate) => 1970-01-01T00:00:00.000Z
1341 ```
1342
1343 ```
1344 let testDate = utcDate.daysFromNow(100);
1345 utcDate.startOfQuarter(testDate) => 1970-04-01T00:00:00.000Z
1346 ```
1347
1348 ```
1349 let testDate = utcDate.daysFromNow(270);
1350 utcDate.startOfQuarter(testDate) => 1970-07-01T00:00:00.000Z
1351 ```
1352
1353 ```
1354 let testDate = utcDate.daysFromNow(300);
1355 utcDate.startOfQuarter(testDate) => 1970-10-01T00:00:00.000Z
1356 ```
1357
1358- startOfYear
1359
1360 Supply a Date Object and this will return a new date object set to the beginning of the year that the supplied date landed in.
1361
1362 ```
1363 let testDate = utcDate.daysFromNow(300);
1364 utcDate.startOfYear(testDate) => 1970-01-01T00:00:00.000Z
1365 ```
1366
1367 ```
1368 let testDate = utcDate.daysFromNow(366);
1369 utcDate.startOfYear(testDate) => 1971-01-01T00:00:00.000Z
1370 ```
1371
1372### Tokenization
1373
1374The examples below assume the following constants are declared.
1375
1376```
1377const { generate, create, timeCoded, timeDecoded } = Glad.token;
1378```
1379
1380** Uniqueness **
1381
1382The probability is extremely low that duplicate tokens will be created if you are generating long enough tokens. However, it can happen.
1383
1384Uniqueness is not built in. It's up to you to ensure uniqueness. If uniqueness is crucial to your use case, please use something more robust or add to the token using unique things such as a database record id.
1385
1386** Radix **
1387
1388The default Radix is url safe so your tokens can safely be used in URLs.
1389`ABCDEFGHIJKLMNOPQRSTUVWXYZ-0987654321_abcdefghijklmnopqrstuvwxyz`
1390
1391You can use your own radix or view the ones available under `Glad.token.radixes`.
1392
1393** create a 6 character token **
1394
1395`generate(6);`
1396
1397** create a 12 character token **
1398
1399`generate(12);`
1400
1401
1402** create a new tokenizer from a provided radix **
1403
1404```
1405let myTokenizer = create('0123456789');
1406myTokenizer.generate(6) => 6 characters, only digits
1407```
1408
1409** create a TimeEncoded Token **
1410
1411 `let timeToken = timeCoded()`
1412
1413** decode a TimeEncoded Token **
1414
1415 `let tokenCreatedAt = timeDecoded(timeToken)`
1416
1417
1418
1419---
1420
1421** fig s1 ** - Supported Latin Chars
1422```
1423'À' => 'A'
1424'Á' => 'A'
1425'Â' => 'A'
1426'Ã' => 'A'
1427'Ä' => 'A'
1428'Å' => 'A'
1429'Æ' => 'Ae'
1430'Ç' => 'C'
1431'È' => 'E'
1432'É' => 'E'
1433'Ê' => 'E'
1434'Ë' => 'E'
1435'Ì' => 'I'
1436'Í' => 'I'
1437'Î' => 'I'
1438'Ï' => 'I'
1439'Ð' => 'D'
1440'Ñ' => 'N'
1441'Ò' => 'O'
1442'Ó' => 'O'
1443'Ô' => 'O'
1444'Õ' => 'O'
1445'Ö' => 'O'
1446'Ő' => 'O'
1447'Ø' => 'O'
1448'Ù' => 'U'
1449'Ú' => 'U'
1450'Û' => 'U'
1451'Ü' => 'U'
1452'Ű' => 'U'
1453'Ý' => 'Y'
1454'Þ' => 'Th'
1455'ß' => 'ss'
1456'à' => 'a'
1457'á' => 'a',
1458'â' => 'a'
1459'ã' => 'a'
1460'ä' => 'a'
1461'å' => 'a'
1462'æ' => 'ae'
1463'ç' => 'c'
1464'è' => 'e',
1465'é' => 'e'
1466'ê' => 'e'
1467'ë' => 'e'
1468'ì' => 'i'
1469'í' => 'i'
1470'î' => 'i'
1471'ï' => 'i'
1472'ð' => 'd'
1473'ñ' => 'n'
1474'ò' => 'o'
1475'ó' => 'o'
1476'ô' => 'o'
1477'õ' => 'o'
1478'ö' => 'o'
1479'ő' => 'o'
1480'ø' => 'o'
1481'ù' => 'u'
1482'ú' => 'u'
1483'û' => 'u'
1484'ü' => 'u'
1485'ű' => 'u'
1486'ý' => 'y'
1487'þ' => 'th'
1488'ÿ' => 'y'
1489```
1490
1491fig s2 Greek character conversion
1492```
1493'α' => 'a'
1494'β' => 'b'
1495'γ' => 'g'
1496'δ' => 'd'
1497'ε' => 'e'
1498'ζ' => 'z'
1499'η' => 'h'
1500'θ' => '8'
1501'ι' => 'i'
1502'κ' => 'k'
1503'λ' => 'l'
1504'μ' => 'm'
1505'ν' => 'n'
1506'ξ' => '3'
1507'ο' => 'o'
1508'π' => 'p'
1509'ρ' => 'r'
1510'σ' => 's'
1511'τ' => 't'
1512'υ' => 'y'
1513'φ' => 'f'
1514'χ' => 'x'
1515'ψ' => 'ps'
1516'ω' => 'w'
1517'ά' => 'a'
1518'έ' => 'e'
1519'ί' => 'i'
1520'ό' => 'o'
1521'ύ' => 'y'
1522'ή' => 'h'
1523'ώ' => 'w'
1524'ς' => 's'
1525'ϊ' => 'i'
1526'ΰ' => 'y'
1527'ϋ' => 'y'
1528'ΐ' => 'i'
1529'Α' => 'A'
1530'Β' => 'B'
1531'Γ' => 'G'
1532'Δ' => 'D'
1533'Ε' => 'E'
1534'Ζ' => 'Z'
1535'Η' => 'H'
1536'Θ' => '8'
1537'Ι' => 'I'
1538'Κ' => 'K'
1539'Λ' => 'L'
1540'Μ' => 'M'
1541'Ν' => 'N'
1542'Ξ' => '3'
1543'Ο' => 'O'
1544'Π' => 'P'
1545'Ρ' => 'R'
1546'Σ' => 'S'
1547'Τ' => 'T'
1548'Υ' => 'Y'
1549'Φ' => 'F'
1550'Χ' => 'X'
1551'Ψ' => 'PS'
1552'Ω' => 'W'
1553'Ά' => 'A'
1554'Έ' => 'E'
1555'Ί' => 'I'
1556'Ό' => 'O'
1557'Ύ' => 'Y'
1558'Ή' => 'H'
1559'Ώ' => 'W'
1560'Ϊ' => 'I'
1561'Ϋ' => 'Y
1562```
1563
1564fig s3 Turkish character conversion
1565```
1566'ş' => 's'
1567'Ş' => 'S'
1568'ı' => 'i'
1569'İ' => 'I'
1570'ğ' => 'g'
1571'Ğ' => 'G'
1572```
1573
1574fig s4 Russian character conversion
1575```
1576'а' => 'a'
1577'б' => 'b'
1578'в' => 'v'
1579'г' => 'g'
1580'д' => 'd'
1581'е' => 'e'
1582'ё' => 'yo'
1583'ж' => 'zh'
1584'з' => 'z'
1585'и' => 'i'
1586'й' => 'j'
1587'к' => 'k'
1588'л' => 'l'
1589'м' => 'm'
1590'н' => 'n'
1591'о' => 'o'
1592'п' => 'p'
1593'р' => 'r'
1594'с' => 's'
1595'т' => 't'
1596'у' => 'u'
1597'ф' => 'f'
1598'х' => 'h'
1599'ц' => 'c',
1600'ч' => 'ch'
1601'ш' => 'sh'
1602'щ' => 'sh'
1603'ъ' => 'u'
1604'ы' => 'y'
1605'э' => 'e'
1606'ю' => 'yu'
1607'я' => 'ya'
1608'А' => 'A'
1609'Б' => 'B'
1610'В' => 'V'
1611'Г' => 'G'
1612'Д' => 'D'
1613'Е' => 'E'
1614'Ё' => 'Yo'
1615'Ж' => 'Zh'
1616'З' => 'Z'
1617'И' => 'I'
1618'Й' => 'J'
1619'К' => 'K'
1620'Л' => 'L'
1621'М' => 'M'
1622'Н' => 'N'
1623'О' => 'O'
1624'П' => 'P'
1625'Р' => 'R'
1626'С' => 'S'
1627'Т' => 'T'
1628'У' => 'U'
1629'Ф' => 'F'
1630'Х' => 'H'
1631'Ц' => 'C'
1632'Ч' => 'Ch'
1633'Ш' => 'Sh'
1634'Щ' => 'Sh'
1635'Ъ' => 'U'
1636'Ы' => 'Y'
1637'Ь' => 'Ь'
1638'Э' => 'E'
1639'Ю' => 'Yu'
1640'Я' => 'Ya'
1641```
1642<br>
1643
1644## Using Debug.js
1645
1646Running `DEBUG=glad* node index.js` will log most of the Glad.js internals.
1647
1648## Other
1649
1650#### How should I handle Cache Warming?
1651
1652Sometimes you'll want to warm your controller#action caches.
1653This can be achieved a few ways.
1654
1655#### Using The API Itself
1656
1657Start your server, then automate requests to it by using a library such as unirest in conjunction with glad-cli's run command. Below is an example of what warming some popular city metrics might look like.
1658
1659```js
1660const unirest = require('unirest');
1661
1662module.exports = function () {
1663 CityMetric.find()
1664 .sort('popularity')
1665 .select({slug: 1})
1666 .limit(500)
1667 .then(cities => {
1668 const promises = cities.map(city => {
1669 return new Promise( (resolve, reject) => {
1670 return unirest.get(`http://some-url-or-localhost/api/city-metrics/${city.slug}`).end(resolve);
1671 });
1672 });
1673 Promise.all(promises).then(() => process.exit(0));
1674 });
1675};
1676```
1677
1678 ```sh
1679 # Execute the run command with the path to the warming script.
1680 glad run warming/channel-metrics
1681 ```
1682
1683 #### Working directly with redis or Glad.cache
1684
1685If you would prefer to warm your caches via working directly with Redis, or `Glad.cache`, you can do that as well. However, we won't cover this topic right here. If you want to work directly with redis, please read about this in the redis documentation. If you would like to read about how to work with Glad.cache, please read the section pertaining to this.
1686
\No newline at end of file