UNPKG

50 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## Flags
580
581#### Only
582
583 `--only=[controller|controllers,controllers]`
584 Using the only flag, you can spin up a server that only binds routes for a specific controller or a group of controllers.
585
586 As an example, `glad s --only=posts` would launch your app and allow traffic to routes handled by the posts controller.
587
588 If you would like to launch a group of controllers, you would simply comma separate the controllers such as `glad s --only=posts,users`.
589
590 The convention is such that you provide the lowercase filename of your controller(s) that should be enabled.
591
592 If you are not using Glad CLI, this will still work with the node binary.
593
594 `node index.js --only=posts`
595
596#### Server Flags
597
598`--port` | `--host` | `--sock` | `--backlog`
599Using the server flags you can over ride the values in your config.js file.
600
601#### Cache Flags
602`--enable-cache` | `disable-cache`
603Using the caching flags you can disable caching for non-development environments or enable cache on development environments
604
605
606## Classes / Helpers / Tools
607
608Glad comes with lots of helpful utilities.
609
610### String
611
612Assume `string === Glad.string` for the examples below.
613
614- color
615 - Produces a colorized string for stdout. `string.color('Oh No', 'red')`
616 - Can be any color name supported by chalk.js
617
618- deburr
619 - converts unicode
620
621 `string.deburr('♥') === 'love'`
622
623 - converts short symbols
624
625 `string.deburr('<3') === 'love'`
626
627 `string.deburr('released w/ stuff') === 'released with stuff'`
628
629 `string.deburr('live && loud') === live and loud'`
630
631 `string.deburr(':) || sad') === 'smile or sad'`
632
633 - converts Latin characters
634
635 `string.deburr('À') === 'A'` <i>See fig s1 at the bottom of the page for a list of all supported characters</i>
636
637 - converts Greek characters
638
639 `string.deburr('α') === 'a'` <i>See fig s2 at the bottom of the page for a list of all supported characters</i>
640
641 - converts Turkish characters
642
643 `string.deburr('ş') === 's'` <i>See fig s3 at the bottom of the page for a list of all supported characters</i>
644
645 - converts Russian characters
646
647 `string.deburr('ф') === 'f'` <i>See fig s4 at the bottom of the page for a list of all supported characters</i>
648
649- slugify
650
651 Creates a normalized slug from a string
652
653 `string.slugify('San Francisco, CA') === 'san-francisco-ca'`
654
655 `string.slugify('The brown fox w/ βeta') === 'the-brown-fox-with-beta'`
656
657 `string.slugify('good news so you can :)') === 'good-news-so-you-can-smile'`
658
659 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.
660
661 `string.slugify('how to create stuff', 'June 17 2017') === 'how-to-create-stuff-june-17-2017'`
662
663- camelize
664
665 `string.camelize("fooze-barz") === 'foozeBarz'`
666
667
668- titleize
669
670 `string.titelize("fooze barz") === 'Fooze Barz'`
671
672- slasherize
673
674 `string.slasherize("fooze barz") === "fooze/barz"`
675
676- reverseSlasherize
677
678 `string.reverseSlasherize("fooze barz") === "barz/fooze"`
679
680- underscore
681
682 `string.underscore("fooze barz") === "fooze_barz"`
683
684
685- cleanSpaces
686
687 `string.cleanSpaces("fooze barz") === "fooze barz"
688
689- endsWith
690
691 `string.endsWith("fooze barz fooze", "fooze") === true`
692
693 `string.endsWith("fooze barz", "fooze") === false`
694
695
696- escape
697
698 `string.escape("fred, barney, & pebbles") === "fred, barney, &amp; pebbles"
699
700
701- unescape
702
703 `string.unescape("fred, barney, &amp; pebbles") === "fred, barney, & pebbles"`
704
705- escapeRegExp
706
707 escapes a string for use in regexp
708
709 `string.escapeRegExp('[lodash](https://lodash.com/)') === "\\[lodash\\]\\(https://lodash\\.com/\\)"`
710
711
712- repeat
713
714 `string.repeat("glad", 2) === "gladglad"`
715
716
717- startsWith
718
719 `string.startsWith("fooze barz", "fooze") === true`
720
721 `string.startsWith("x fooze barz", "fooze") === false`
722
723
724- words
725
726 split a string based on words
727
728 `string.words('fred, barney, & pebbles') === ['fred', 'barney', 'pebbles']`
729
730 split a string based on words and keep elements by regexp
731
732 `string.words('fred, barney, & pebbles', /[^, ]+/g) === ['fred', 'barney', '&', 'pebbles']`
733
734- sentenceCase
735
736 Capitalizes letters at the beginning of a string, and after periods.
737
738### Objects
739
740Assume object refences Glad.object
741
742- get
743
744 Pick a value from an object
745 ```
746 let o = {foo: {bar : 'foobar' }};
747 object.get(o, 'foo.bar') => 'foobar'
748 ```
749
750- extend
751
752 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.
753
754 ```
755 let src = {one: 1};
756 let ext = {two: 2};
757 assert.deepEqual(object.extend(src, ext), {one: 1, two: 2})
758 ```
759
760 ```
761 let src = {one: 1};
762 let ext = {two: 2};
763 let ext2 = {three: 3};
764 assert.deepEqual(object.extend(src, ext, ext2), {one: 1, two: 2, three: 3});
765 ```
766
767 ```
768 let src = {one: 1};
769 let ext = {one: 2};
770 let ext2 = {three: 3};
771 assert.deepEqual(object.extend(src, ext, ext2), {one: 2, three: 3});
772 ```
773
774 ```
775 let src = {one: 1};
776 let ext = {two: 2};
777 let ext2 = {two: 3};
778 let ext3 = {two: 4};
779 assert.deepEqual(object.extend(src, ext, ext2, ext3), {one: 1, two: 4});
780 ```
781
782- hasPath
783
784 return a boolean value if an object contains a path
785
786 ```
787 let src = {one: 1};
788 assert.equal(object.hasPath(src, 'one'), true);
789 assert.equal(object.hasPath(src, 'one.value.at.nowhere'), false);
790 ```
791
792- clone
793
794 return a new object
795
796 ```
797 let src = {one: 1, two: {a: 1}};
798 assert.deepEqual(object.clone(src), {one: 1, two: {a: 1}});
799 ```
800
801 new object should not be passed by reference
802
803 ```
804 let src = {one: 1, two: {a: 1} };
805 let src2 = object.clone(src);
806 src2.two.a = 3;
807 assert.equal(src.two.a, 1);
808 assert.equal(src2.two.a, 3);
809 ```
810
811- set
812
813 set a value at the given path (null/undefined safe)
814
815 ```
816 let src = {};
817 object.set(src, 'foo', 1);
818 assert.equal(src.foo, 1);
819 ```
820
821 ```
822 let src = {};
823 object.set(src, 'foo.bar.baz.x', 1);
824 assert.equal(src.foo.bar.baz.x, 1);
825 ```
826
827- arrayToObject
828
829 convert an array of arrays to an object
830
831 ```
832 let arr = [['a', 1], ['b', 2]];
833 let o = object.arrayToObject(arr);
834 assert.equal(o.a, 1);
835 assert.equal(o.b, 2);
836 ```
837
838- invert
839
840 invert an object
841
842 ```
843 let o = {a: 'A', b: 'B'};
844 object.invert(o);
845 assert.deepEqual(o, {A: 'a', B: 'b'});
846 assert.equal(o.a, undefined);
847 ```
848
849- createShallowInvertedClone
850
851 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.
852
853 ```
854 let o = {a: 'A', b: 'B'};
855 let x = createShallowInvertedClone(o); // Glad.object.createShallowInvertedClone
856 assert.deepEqual(x, {A: 'a', B: 'b'});
857 assert.equal(x.a, undefined); // Non-inverted keys should not be present
858 assert.deepEqual(o, {a: 'A', b: 'B'}); // Original should be non-inverted
859 ```
860
861- select
862
863 select keys from an object (creates a new object)
864
865 ```
866 let o = {name: 'fred', email: 'me@mail.com', password: 'secretSquirrel'};
867 let fields = object.select(o, 'name', 'email');
868 assert.deepEqual(fields, {name: 'fred', email: 'me@mail.com'});
869 ```
870
871 select keys from an object via Array
872
873 ```
874 let o = {name: 'fred', email: 'me@mail.com', password: 'secretSquirrel'};
875 let fields = object.select(o, ['name', 'email']);
876 assert.deepEqual(fields, {name: 'fred', email: 'me@mail.com'});
877 ```
878
879- drop
880
881 drop keys from an object
882
883 ```
884 let o = { name: 'fred', email: 'me@mail.com', password: 'secretSquirrel' };
885 object.drop(o, 'password');
886 assert.deepEqual(o, {name: 'fred', email: 'me@mail.com'});
887 ```
888
889 drop keys from an object via Array
890
891 ```
892 let o = { name: 'fred', email: 'me@mail.com', password: 'secretSquirrel'
893 object.drop(o, ['password']);
894 assert.deepEqual(o, {name: 'fred', email: 'me@mail.com'});
895 });
896 ```
897
898- selectCombination
899
900 select values from multiple objects and create a new one
901
902 ```
903 let a = {
904 name: 'fred',
905 email: 'me@mail.com',
906 password: 'secretSquirrel'
907 };
908 let b = { sid: '8372487234', last_visit: new Date()};
909 let c = { likes : 'stuff', knows: 'things'};
910 let o = object.selectCombination([a, b, c], 'name', 'email', 'last_visit', 'likes', 'knows');
911 assert.deepEqual(o, { name: 'fred', email: 'me@mail.com', last_visit: b.last_visit, likes : 'stuff', knows: 'things'});
912 });
913 ```
914
915- format
916
917 Create a new object from an existing one, but reformat the keys.
918 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`].
919
920 ```
921 let a = {
922 name: 'fred',
923 email: 'me@mail.com',
924 data: {
925 stuff: { a: 'a', b: 'b'},
926 more : { c: 'value'}
927 }
928 };
929
930 let o = object.format(a, 'name', 'email', ['stuff', 'data.stuff.a' ], ['value', 'data.more.c']);
931
932 assert.deepEqual(o, {
933 name: 'fred',
934 email: 'me@mail.com',
935 stuff: 'a',
936 value : 'value'
937 });
938
939 });
940 ```
941
942 Format the keys using an array
943
944 ```
945 let a = {
946 name: 'fred',
947 email: 'me@mail.com',
948 data: {
949 stuff: { a: 'a', b: 'b'},
950 more : { c: 'value'}
951 }
952 };
953
954 let o = object.format(a, [
955 'name',
956 'email',
957 ['stuff', 'data.stuff.a' ],
958 ['value', 'data.more.c']
959 ]);
960
961 assert.deepEqual(o, {
962 name: 'fred',
963 email: 'me@mail.com',
964 stuff: 'a',
965 value : 'value'
966 });
967
968 });
969 ```
970
971- explode
972
973 Explode an object
974
975 ```
976
977 let row = {
978 'id': 2,
979 'contact.name.first': 'John',
980 'contact.name.last': 'Doe',
981 'contact.email': 'example@gmail.com',
982 'contact.info.about.me': 'classified',
983 'devices.0': 'mobile',
984 'devices.1': 'laptop',
985 'some.other.things.0': 'this',
986 'some.other.things.1': 'that',
987 'some.other.stuff.0.key': 'stuff'
988 };
989
990 object.explode(row);
991
992 assert.deepEqual(row, {
993 "id": 2,
994 "contact": {
995 "name": {
996 "first": "John",
997 "last": "Doe"
998 },
999 "email": "example@gmail.com",
1000 "info": {
1001 "about": {
1002 "me": "classified"
1003 }
1004 }
1005 },
1006 "devices": [
1007 "mobile",
1008 "laptop"
1009 ],
1010 "some": {
1011 "other": {
1012 "things": [
1013 "this",
1014 "that"
1015 ],
1016 stuff : [{
1017 key : "stuff"
1018 }]
1019 }
1020 }
1021 });
1022 });
1023 ```
1024
1025### Number
1026
1027Assume number is Glad.number
1028
1029- Time Constants
1030 ```
1031 assert.equal(number.SECOND, 1000);
1032 assert.equal(number.MINUTE, 1000 * 60);
1033 assert.equal(number.HOUR, 1000 * 60 * 60);
1034 assert.equal(number.DAY, 1000 * 60 * 60 * 24);
1035 ```
1036
1037- parse
1038
1039 Strip off weird stuff
1040
1041 ```
1042 assert.equal(number.parse("$34.72"), 34.72);
1043 assert.equal(number.parse("65.323%"), 65.323);
1044 assert.equal(number.parse("65%"), 65);
1045
1046 // RESPECTS NEGATIVE NUMBERS
1047 assert.equal(number.parse("-65%"), -65);
1048 assert.equal(number.parse("-$65.34"), -65.34);
1049 assert.equal(number.parse("-78.32-"), -78.32);
1050 ```
1051
1052
1053- random
1054
1055 generate a random number between x,y
1056
1057 ```
1058 let rand = number.random(5,10);
1059 assert.equal(rand <= 10, true);
1060 assert.equal(rand >= 5, true);
1061 ```
1062
1063- withDelimiter
1064
1065 Simply format a number with commas and decimals
1066
1067 ```
1068 assert.equal(number.withDelimiter(4), '4.00');
1069 assert.equal(number.withDelimiter(45), '45.00');
1070 assert.equal(number.withDelimiter(450), '450.00');
1071 assert.equal(number.withDelimiter(4500), '4,500.00');
1072 assert.equal(number.withDelimiter(45000), '45,000.00');
1073 assert.equal(number.withDelimiter(450000), '450,000.00');
1074 assert.equal(number.withDelimiter(4500000), '4,500,000.00');
1075 assert.equal(number.withDelimiter(45000000), '45,000,000.00');
1076 assert.equal(number.withDelimiter(450000000), '450,000,000.00');
1077 assert.equal(number.withDelimiter(4500000000), '4,500,000,000.00');
1078 assert.equal(number.withDelimiter(45000000000), '45,000,000,000.00');
1079 assert.equal(number.withDelimiter(450000000000), '450,000,000,000.00');
1080 assert.equal(number.withDelimiter(4500000000000), '4,500,000,000,000.00');
1081 assert.equal(number.withDelimiter(45000000000000), '45,000,000,000,000.00');
1082 assert.equal(number.withDelimiter(450000000000000), '450,000,000,000,000.00');
1083 assert.equal(number.withDelimiter(99e19), '990,000,000,000,000,000,000.00');
1084 ```
1085
1086- toAbbr
1087
1088 Abbreviate a number
1089
1090 ```
1091 assert.equal(number.toAbbr(45000), '45k');
1092 assert.equal(number.toAbbr(450000), '450k');
1093 assert.equal(number.toAbbr(4500000), '4.5m');
1094 assert.equal(number.toAbbr(45000000), '45m');
1095 assert.equal(number.toAbbr(450000000), '450m');
1096 assert.equal(number.toAbbr(4500000000), '4.5b');
1097 assert.equal(number.toAbbr(45000000000), '45b');
1098 assert.equal(number.toAbbr(450000000000), '450b');
1099 assert.equal(number.toAbbr(450), '450');
1100 assert.equal(number.toAbbr(4500), '4.5k');
1101 ```
1102
1103- `toData(bytes)`
1104
1105 format a number in data units
1106
1107 ```
1108 assert.equal(number.toData(126.02 * 1000), '126.0 kB');
1109 assert.equal(number.toData(126.32 * 1000), '126.3 kB');
1110 assert.equal(number.toData(126.32 * 1000 * 1000), '126.3 MB');
1111 assert.equal(number.toData(126.32 * Math.pow(1000, 3)), '126.3 GB');
1112 assert.equal(number.toData(126.32 * Math.pow(1000, 4)), '126.3 TB');
1113 assert.equal(number.toData(126.32 * Math.pow(1000, 5)), '126.3 PB');
1114 assert.equal(number.toData(126.32 * Math.pow(1000, 6)), '126.3 EB');
1115 assert.equal(number.toData(126.32 * Math.pow(1000, 7)), '126.3 ZB');
1116 assert.equal(number.toData(126.32 * Math.pow(1000, 8)), '126.3 YB');
1117 ```
1118
1119- `toTime(seconds, returnArray = false)`
1120
1121 format a number in time
1122
1123 ```
1124 const HOUR = 60 * 60;
1125 const DAY = 24 * HOUR;
1126
1127 assert.equal(number.toTime(50), '50 sec');
1128 assert.equal(number.toTime(60), '1 min');
1129 assert.equal(number.toTime(HOUR), '1 hr');
1130 assert.equal(number.toTime(DAY), '1 day');
1131 assert.equal(number.toTime(DAY * 30), '30 days');
1132 assert.equal(number.toTime( (DAY * 2) + 10), '2 days 10 sec');
1133 assert.equal(number.toTime( (DAY * 2) + (HOUR * 2) + 32), '2 days 2 hr 32 sec');
1134 ```
1135
1136 format a number in time and return an array
1137
1138 ```
1139 assert.deepEqual(number.toTime(50, true), [0, 0, 0, 50]);
1140 assert.deepEqual(number.toTime(60, true), [0, 0, 1, 0]);
1141 assert.deepEqual(number.toTime(HOUR, true), [0, 1, 0, 0]);
1142 assert.deepEqual(number.toTime(DAY, true), [1, 0, 0, 0]);
1143 assert.deepEqual(number.toTime(DAY * 30, true), [30, 0, 0, 0]);
1144 assert.deepEqual(number.toTime( (DAY * 2) + 10, true), [2, 0, 0, 10]);
1145 assert.deepEqual(number.toTime( (DAY * 2) + (HOUR * 2) + 32, true), [2, 2, 0, 32]);
1146 ```
1147
1148- `toCurrency(number, precision, decimal, comma)`
1149
1150 format a number in USD currency (convienience method)
1151
1152 ```
1153 assert.equal(number.toCurrency(240.658), '$240.66');
1154 assert.equal(number.toCurrency(-376240.658), '$-376,240.66');
1155 ```
1156
1157 If you need to format a number in other currencies, use the NumberFormatter Class.
1158
1159 ```
1160 let toGBP = new Glad.number.NumberFormatter("£", false, 2, ',', '.');
1161 assert.equal(toGBP(1234567.89), '£1.234.567,89');
1162 ```
1163
1164- `toPercent(number, comma = ",", decimal = ".")`
1165
1166 format a number as a percentage
1167
1168 ```
1169 assert.equal(number.toPercent(43.47576353), '43.48%');
1170 assert.equal(number.toPercent(43.47576353, 4), '43.4758%');
1171 assert.equal(number.toPercent(43873.47581765327, 4, '*', '\''), "43'873*4758%");
1172 assert.equal(number.toPercent(-43.47576353), '-43.48%');
1173 assert.equal(number.toPercent(-43.47576353, 4), '-43.4758%');
1174 ```
1175
1176
1177- `toPhone(number = "", formatAreaCode = false, extension = false)`
1178
1179 format a number as a phone number
1180 ```
1181 assert.equal(number.toPhone(9255551212), "925-555-1212");
1182 assert.equal(number.toPhone('9255551212'), "925-555-1212");
1183 assert.equal(number.toPhone(9255551212, true), "(925) 555-1212");
1184 assert.equal(number.toPhone(9255551212, true, 4528), "(925) 555-1212 x4528");
1185 ```
1186
1187### imports
1188Imports is a require alias with some nifty features specifically for Glad. When using the imports method, paths are imported relative to your working directory.
1189
1190Examples: (Assume imports is Glad.imports)
1191
1192Both controllers and models can be imported without pluralization
1193
1194`imports('UserModel')` => `require('./models/user')`
1195
1196Importing the widget model from within the directory `queries/widgets/find-by-name`
1197
1198`imports('UserModel')` => `require('../../models/user')`
1199
1200Importing using reverse camelize
1201
1202`imports('UserQueryMocksTest')` => `require('./test/mocks/query/user')`
1203
1204Importing normally from within a sub-directory
1205
1206`imports('classes/fooze')` => `require('../../classes/fooze')`
1207
1208### Dates
1209
1210Glad'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`
1211
1212For each example below assume these variables are created.
1213
1214```
1215let moment = require('moment');
1216let utcDate = new Glad.Date(moment);
1217let pstDate = new Glad.Date(moment, -8);
1218```
1219When 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.
1220
1221Also assume that the current Date is January 1st 1970 12:00am
1222
1223- monthsFromNow
1224 ```
1225 utcDate.monthsFromNow(1) => 1970-02-01T00:00:00.000Z
1226 utcDate.monthsFromNow(12) => 1971-01-01T00:00:00.000Z
1227 utcDate.monthsFromNow(24) => 1972-01-01T00:00:00.000Z
1228 ```
1229
1230
1231- weeksFromNow
1232
1233 ```
1234 utcDate.weeksFromNow(1) => 1970-01-08T00:00:00.000Z
1235 ```
1236
1237- daysFromNow
1238
1239 ```
1240 utcDate.daysFromNow(1) => 1970-01-02T00:00:00.000Z
1241 utcDate.daysFromNow(10) => 1970-01-11T00:00:00.000Z
1242 ```
1243
1244
1245- hoursFromNow
1246
1247 ```
1248 utcDate.hoursFromNow(1) => 1970-01-01T01:00:00.000Z')
1249 ```
1250
1251- minutesFromNow
1252
1253 ```
1254 utcDate.minutesFromNow(1) => 1970-01-01T00:01:00.000Z
1255 utcDate.minutesFromNow(14) => 1970-01-01T00:14:00.000Z
1256 ```
1257
1258- secondsFromNow
1259
1260 ```
1261 utcDate.secondsFromNow(1) => 1970-01-01T00:00:01.000Z
1262 utcDate.secondsFromNow(10) => 1970-01-01T00:00:10.000Z
1263 utcDate.secondsFromNow(61) => 1970-01-01T00:01:01.000Z
1264 ```
1265
1266- startOfSecond
1267
1268 Supply a Date Object and this will return a new date object with milliseconds zeroed out.
1269
1270
1271- startOfMinute
1272
1273 Supply a Date Object and this will return a new date object with milliseconds and seconds zeroed out.
1274
1275- startOfHour
1276
1277 Supply a Date Object and this will return a new date object with milliseconds, seconds and minutes zeroed out.
1278
1279- startOfDay
1280
1281 Supply a Date Object and this will return a new date object with milliseconds, seconds, minutes, and hours zeroed out.
1282
1283
1284
1285- startOfWeek
1286
1287 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.
1288 ```
1289 let testDate = utcDate.daysFromNow(22);
1290 let startOfWeek = utcDate.startOfWeek(testDate);
1291 startOfWeek.toISOString() => 1970-01-18T00:00:00.000Z
1292 ```
1293
1294- startOfMonth
1295
1296 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.
1297 ```
1298 let testDate = utcDate.daysFromNow(22);
1299 utcDate.startOfMonth(testDate) => 1970-01-01T00:00:00.000Z
1300 ```
1301
1302- startOfQuarter
1303
1304 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.
1305
1306 ```
1307 let testDate = utcDate.daysFromNow(2);
1308 utcDate.startOfQuarter(testDate) => 1970-01-01T00:00:00.000Z
1309 ```
1310
1311 ```
1312 let testDate = utcDate.daysFromNow(100);
1313 utcDate.startOfQuarter(testDate) => 1970-04-01T00:00:00.000Z
1314 ```
1315
1316 ```
1317 let testDate = utcDate.daysFromNow(270);
1318 utcDate.startOfQuarter(testDate) => 1970-07-01T00:00:00.000Z
1319 ```
1320
1321 ```
1322 let testDate = utcDate.daysFromNow(300);
1323 utcDate.startOfQuarter(testDate) => 1970-10-01T00:00:00.000Z
1324 ```
1325
1326- startOfYear
1327
1328 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.
1329
1330 ```
1331 let testDate = utcDate.daysFromNow(300);
1332 utcDate.startOfYear(testDate) => 1970-01-01T00:00:00.000Z
1333 ```
1334
1335 ```
1336 let testDate = utcDate.daysFromNow(366);
1337 utcDate.startOfYear(testDate) => 1971-01-01T00:00:00.000Z
1338 ```
1339
1340### Tokenization
1341
1342The examples below assume the following constants are declared.
1343
1344```
1345const { generate, create, timeCoded, timeDecoded } = Glad.token;
1346```
1347
1348** Uniqueness **
1349
1350The probability is extremely low that duplicate tokens will be created if you are generating long enough tokens. However, it can happen.
1351
1352Uniqueness 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.
1353
1354** Radix **
1355
1356The default Radix is url safe so your tokens can safely be used in URLs.
1357`ABCDEFGHIJKLMNOPQRSTUVWXYZ-0987654321_abcdefghijklmnopqrstuvwxyz`
1358
1359You can use your own radix or view the ones available under `Glad.token.radixes`.
1360
1361** create a 6 character token **
1362
1363`generate(6);`
1364
1365** create a 12 character token **
1366
1367`generate(12);`
1368
1369
1370** create a new tokenizer from a provided radix **
1371
1372```
1373let myTokenizer = create('0123456789');
1374myTokenizer.generate(6) => 6 characters, only digits
1375```
1376
1377** create a TimeEncoded Token **
1378
1379 `let timeToken = timeCoded()`
1380
1381** decode a TimeEncoded Token **
1382
1383 `let tokenCreatedAt = timeDecoded(timeToken)`
1384
1385
1386
1387---
1388
1389** fig s1 ** - Supported Latin Chars
1390```
1391'À' => 'A'
1392'Á' => 'A'
1393'Â' => 'A'
1394'Ã' => 'A'
1395'Ä' => 'A'
1396'Å' => 'A'
1397'Æ' => 'Ae'
1398'Ç' => 'C'
1399'È' => 'E'
1400'É' => 'E'
1401'Ê' => 'E'
1402'Ë' => 'E'
1403'Ì' => 'I'
1404'Í' => 'I'
1405'Î' => 'I'
1406'Ï' => 'I'
1407'Ð' => 'D'
1408'Ñ' => 'N'
1409'Ò' => 'O'
1410'Ó' => 'O'
1411'Ô' => 'O'
1412'Õ' => 'O'
1413'Ö' => 'O'
1414'Ő' => 'O'
1415'Ø' => 'O'
1416'Ù' => 'U'
1417'Ú' => 'U'
1418'Û' => 'U'
1419'Ü' => 'U'
1420'Ű' => 'U'
1421'Ý' => 'Y'
1422'Þ' => 'Th'
1423'ß' => 'ss'
1424'à' => 'a'
1425'á' => 'a',
1426'â' => 'a'
1427'ã' => 'a'
1428'ä' => 'a'
1429'å' => 'a'
1430'æ' => 'ae'
1431'ç' => 'c'
1432'è' => 'e',
1433'é' => 'e'
1434'ê' => 'e'
1435'ë' => 'e'
1436'ì' => 'i'
1437'í' => 'i'
1438'î' => 'i'
1439'ï' => 'i'
1440'ð' => 'd'
1441'ñ' => 'n'
1442'ò' => 'o'
1443'ó' => 'o'
1444'ô' => 'o'
1445'õ' => 'o'
1446'ö' => 'o'
1447'ő' => 'o'
1448'ø' => 'o'
1449'ù' => 'u'
1450'ú' => 'u'
1451'û' => 'u'
1452'ü' => 'u'
1453'ű' => 'u'
1454'ý' => 'y'
1455'þ' => 'th'
1456'ÿ' => 'y'
1457```
1458
1459fig s2 Greek character conversion
1460```
1461'α' => 'a'
1462'β' => 'b'
1463'γ' => 'g'
1464'δ' => 'd'
1465'ε' => 'e'
1466'ζ' => 'z'
1467'η' => 'h'
1468'θ' => '8'
1469'ι' => 'i'
1470'κ' => 'k'
1471'λ' => 'l'
1472'μ' => 'm'
1473'ν' => 'n'
1474'ξ' => '3'
1475'ο' => 'o'
1476'π' => 'p'
1477'ρ' => 'r'
1478'σ' => 's'
1479'τ' => 't'
1480'υ' => 'y'
1481'φ' => 'f'
1482'χ' => 'x'
1483'ψ' => 'ps'
1484'ω' => 'w'
1485'ά' => 'a'
1486'έ' => 'e'
1487'ί' => 'i'
1488'ό' => 'o'
1489'ύ' => 'y'
1490'ή' => 'h'
1491'ώ' => 'w'
1492'ς' => 's'
1493'ϊ' => 'i'
1494'ΰ' => 'y'
1495'ϋ' => 'y'
1496'ΐ' => 'i'
1497'Α' => 'A'
1498'Β' => 'B'
1499'Γ' => 'G'
1500'Δ' => 'D'
1501'Ε' => 'E'
1502'Ζ' => 'Z'
1503'Η' => 'H'
1504'Θ' => '8'
1505'Ι' => 'I'
1506'Κ' => 'K'
1507'Λ' => 'L'
1508'Μ' => 'M'
1509'Ν' => 'N'
1510'Ξ' => '3'
1511'Ο' => 'O'
1512'Π' => 'P'
1513'Ρ' => 'R'
1514'Σ' => 'S'
1515'Τ' => 'T'
1516'Υ' => 'Y'
1517'Φ' => 'F'
1518'Χ' => 'X'
1519'Ψ' => 'PS'
1520'Ω' => 'W'
1521'Ά' => 'A'
1522'Έ' => 'E'
1523'Ί' => 'I'
1524'Ό' => 'O'
1525'Ύ' => 'Y'
1526'Ή' => 'H'
1527'Ώ' => 'W'
1528'Ϊ' => 'I'
1529'Ϋ' => 'Y
1530```
1531
1532fig s3 Turkish character conversion
1533```
1534'ş' => 's'
1535'Ş' => 'S'
1536'ı' => 'i'
1537'İ' => 'I'
1538'ğ' => 'g'
1539'Ğ' => 'G'
1540```
1541
1542fig s4 Russian character conversion
1543```
1544'а' => 'a'
1545'б' => 'b'
1546'в' => 'v'
1547'г' => 'g'
1548'д' => 'd'
1549'е' => 'e'
1550'ё' => 'yo'
1551'ж' => 'zh'
1552'з' => 'z'
1553'и' => 'i'
1554'й' => 'j'
1555'к' => 'k'
1556'л' => 'l'
1557'м' => 'm'
1558'н' => 'n'
1559'о' => 'o'
1560'п' => 'p'
1561'р' => 'r'
1562'с' => 's'
1563'т' => 't'
1564'у' => 'u'
1565'ф' => 'f'
1566'х' => 'h'
1567'ц' => 'c',
1568'ч' => 'ch'
1569'ш' => 'sh'
1570'щ' => 'sh'
1571'ъ' => 'u'
1572'ы' => 'y'
1573'э' => 'e'
1574'ю' => 'yu'
1575'я' => 'ya'
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'Ь' => 'Ь'
1606'Э' => 'E'
1607'Ю' => 'Yu'
1608'Я' => 'Ya'
1609```
1610<br>
1611
1612## Using Debug.js
1613
1614Running `DEBUG=glad* node index.js` will log most of the Glad.js internals.
1615
1616## Other
1617
1618#### How should I handle Cache Warming?
1619
1620Sometimes you'll want to warm your controller#action caches.
1621This can be achieved a few ways.
1622
1623#### Using The API Itself
1624
1625Start 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.
1626
1627```js
1628const unirest = require('unirest');
1629
1630module.exports = function () {
1631 CityMetric.find()
1632 .sort('popularity')
1633 .select({slug: 1})
1634 .limit(500)
1635 .then(cities => {
1636 const promises = cities.map(city => {
1637 return new Promise( (resolve, reject) => {
1638 return unirest.get(`http://some-url-or-localhost/api/city-metrics/${city.slug}`).end(resolve);
1639 });
1640 });
1641 Promise.all(promises).then(() => process.exit(0));
1642 });
1643};
1644```
1645
1646 ```sh
1647 # Execute the run command with the path to the warming script.
1648 glad run warming/channel-metrics
1649 ```
1650
1651 #### Working directly with redis or Glad.cache
1652
1653If 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.
\No newline at end of file