deno.land / x / casualdb@v0.1.4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
<p align="center"> <img src="https://user-images.githubusercontent.com/6426069/82755043-bb65a700-9dee-11ea-9de4-e57476f216db.png" width="300" /></p>
<p align="center"> <sub> Simple JSON "database" for Deno with type-safety! ⚡️ </sub></p>
<p align="center"> <strong>WARNING</strong>: This project is still in beta phase. We are actively working on enhancing the API and ironing out kinks. If you find a bug or have a feature request, feel free to create an issue or contribute. 🙂</p>
![GitHub release (latest SemVer including pre-releases)](https://img.shields.io/github/v/release/campvanilla/casualdb?color=%232ecc71&include_prereleases&style=flat-square) <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->[![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors-)<!-- ALL-CONTRIBUTORS-BADGE:END -->
## Contents
* [Quick Usage](#quick-usage)* [Installation](#installation)* [API](#api)* [Inspiration](#inspiration) * [Disclaimer](#disclaimer) ⚠️* [Contributing](#contributing)
## Quick Usage
``` ts// create an interface to describe the structure of your JSONinterface Schema { posts: Array<{ id: number; title: string; views: number; }>; user: { name: string; };}
const db = new CasualDB<Schema>(); // instantiate the db, casually 🤓await db.connect("./test-db.json"); // "connect" to the db (JSON file)
// (optional) seed it with data, if starting with an empty dbawait db.seed({ posts: [ { id: 1, title: "Post 1", views: 99 }, { id: 2, title: "Post 2", views: 30 }, ], user: { name: "Camp Vanilla" },});
const posts = await db.get<Schema['posts']>('posts'); // pass the interface key in order for type-checking to work
const postTitlesByViews = ( posts .sort(['views']) // sort by views (ascending) .pick(['title']) // pick the title of every post .value() // => ['Post 2', 'Post 1']);```
## Installation
``` tsimport { CasualDB } from "https://deno.land/x/casualdb@0.1.4/mod.ts";
// create an interface to describe the structure of your JSONinterface Schema { posts: Array<{ id: number; title: string; views: number; }>; user: { name: string; };}
const db = new CasualDB<Schema>();```
Note: When running via deno, this module will require you to pass the following flags (all flags are mandatory):-
* `--allow-read` : in order to be able to **read** the JSON files* `--allow-write`: in order to be able to **write** to the JSON files* `--unstable` : this module uses the experimental Worker API in deno, and hence requires this flag* `--allow-net` : this is to enable to download of the Worker file.
If you want to always run the latest code (from the `master` branch) of this module, install via:```tsimport { CasualDB } from "https://deno.land/x/casualdb/mod.ts";```
## API
### new CasualDB<Schema>()
Returns an instance of the _CasualDB_. Passing in a interface describing your JSON data ensures that **type checking works correctly**. The following are the methods available on this class instance
* [.connect()](#casual-db-connect)* [.get()](#casual-db-get)* [.seed()](#casual-db-seed)* [.write()](#casual-db-write)
<h4 id='casual-db-connect'> <code>.connect(pathToJsonFile: string, options?: ConnectOptions)</code></h4>
Creates a _connection_ to a json file passed as parameter. Returns a promise.
ConnectOptions:
+ `bailIfNotPresent` <Boolean>: Controls whether you would like an error to be thrown if the file being connected to does not exist. Default = `false` .
``` tsawait db.connect("./test-db.json");
// or with options
await db.connect("./test-db.json", { bailIfNotPresent: true,});```
<h4 id='casual-db-get'> <code>.get<T>(jsonPath: string)</code></h4>
Fetches value from connected JSON file. Takes an object _path_ as parameter. Returns a `Promise<CollectionOperator | PrimitiveOperator>` .**Important**: For type checking to work, ensure that the Template Type <T> is provided to `.get<T>()` . If this is not provided, typescript cannot decide a _CollectionOperator_ or _PrimitiveOperator_ has been returned and hence you'd have to manually narrow it down for TS.
``` tsinterface Schema { posts: Array<{ id: number; title: string; views: number; }>; user: { name: string; };}
await db.get<Schema["posts"]>('posts'); // Returns a Promise<CollectionOperator>
// or
await db.get<Schema["posts"][number]["id"]>('posts.0.id'); // Returns a Promise<PrimitiveOperator>```
<h4 id="casual-db-seed"> <code>.seed(data: Schema)</code></h4>
Overrides the contents of the connected JSON file. This is beneficial for when you don't already have data in the file or you want to add some defaults. Returns a promise.
``` tsinterface Schema { posts: Array<{ id: number; title: string; views: number; }>; user: { name: string; };}
await db.seed({ posts: [ { id: 1, title: "Post 1", views: 99 }, { id: 2, title: "Post 2", views: 30 }, ], user: { name: "Camp Vanilla" },});```
<h4 id='casual-db-write'> <code>.write(jsonPath: string, data: any)</code></h4>
Writes the provided value to the Object path provided. Returns a promise.
``` tsawait db.write('posts', [ { id: 1, title: "Post 1", views: 99 }, { id: 2, title: "Post 2", views: 30 },]);
// or
await db.write('posts.0.title', 'Post 1');```
### PrimitiveOperator
When performing a `db.get()` on a path that returns a non-array value, the Promise resolves to an instance of `PrimitiveOperator` . The _PrimitiveOperator_ class encapsulates functions that allow you work with any non-array-like data in javascript (eg. `object` , `string` , `number` , `boolean` ). All functions that are a part of _PrimitiveOperator_ allow function chaining.
``` tsinterface Schema { posts: Array<{ id: number; title: string; views: number; }>; user: { name: string; };}
const data = await db.get<Schema["posts"]>('posts'); // ❌ Not a PrimitiveOperator as the value is going to be an array
const data = await db.get<Schema["posts"][number]>('posts.0'); // ✅ PrimitiveOperator as the value is a non-array.```
Instances of this class have the following methods:
* [.value()](#primitive-operator-value)* [.update()](#primitive-operator-update)* [.pick()](#primitive-operator-pick)
<h4 id='primitive-operator-value'> <code>.value()</code></h4>
Returns the value of the data.
``` tsconst data = await db.get<Schema["posts"][number]>('posts.0');
data.value(); // { id: 1, title: "Post 1", views: 99 }```
<h4 id='primitive-operator-update'> <code>.update<T>(updateMethod: (currentValue) => T)</code></h4>
Method to update the data. Method takes an updater-function as parameter. The updater-function will receive the value you want to update and expects a return value. The type of the updated data is inferred by the ReturnType of the updater-function.
``` tsconst data = await db.get<Schema["posts"][number]>('posts.0');
data .update((value) => ({ title: "Modified Post", })) .value(); // { id: 1, title: "Modified Post" }```
<h4 id='primitive-operator-pick'> .pick(keys: string[])</h4>
Picks and returns a subset of keys from the data. Method allows only keys present on data. If the data is not an object, method returns the data as is.
``` tsconst data = await db.get<Schema["posts"][number]>('posts.0');
data .pick(["id", "title"]) .value(); // { id: 1, title: "Post 1" }```
### CollectionOperator
When performing a `db.get()` on a path that returns an array, the Promise resolves to a instance of `CollectionOperator` . The _CollectionOperator_ class encapsulates functions that allow you work with array-like data (collection of items). All functions that are a part of _CollectionOperator_ allow function chaining.
``` tsinterface Schema { posts: Array<{ id: number; title: string; views: number; }>; user: { name: string; };}
const data = await db.get<Schema["posts"]>('posts'); // ✅ CollectionOperator as the value is an array.
const data = await db.get<Schema["posts"][number]>('posts.0'); // ❌ PrimitiveOperator as the value is a non-array.```
Instances of this class contain the following methods. All methods are chainable:
* [.value()](#collection-operator-value)* [.size()](#collection-operator-size)* [.findOne()](#collection-operator-findOne)* [.findAllAndUpdate()](#collection-operator-findAllAndUpdate)* [.findAllAndRemove()](#collection-operator-findAllAndRemove)* [.findById()](#collection-operator-findById)* [.findByIdAndRemove()](#collection-operator-findByIdAndRemove)* [.findByIdAndUpdate()](#collection-operator-findByIdAndUpdate)* [.sort()](#collection-operator-sort)* [.page()](#collection-operator-page)* [.pick()](#collection-operator-pick)
<h4 id='collection-operator-value'> <code>.value()</code></h4>
Returns the value of the data.
``` tsconst data = await db.get<Schema["posts"]>('posts');
console.log(data.value()); // [ { id: 1, title: "Post 1", views: 99 }, { id: 2, title: "Post 2", views: 30 }, ]```
<h4 id='collection-operator-size'> <code>.size()</code></h4>
Returns the length of the data.
``` tsconst data = await db.get<Schema["posts"]>('posts');
console.log(data.size()); // 2```
<h4 id='collection-operator-findOne'><code>.findOne(predicate: Object | Function => boolean)</code></h4>
Searches through the collection items and returns an item if found, else returns an instance of `PrimitiveOperator<null>` . The predicate can be of two forms:
1. An object with keys that you would like to match. The keys of the object should be a subset of the keys available on the items of the collection.2. A search-function where you can provide your custom logic and return `true` for the condition you are looking for.
Returns a `PrimitiveOperator` or `CollectionOperator` based on type of the found element.
``` tsconst data = await db.get<Schema["posts"]>('posts');
data .findOne({ id: 1 }) .value();// { id: 1, title: "Post 1", views: 99 }
// or
data .findOne((value) => { return value.id === 1 }) .value(); // { id: 1, title: "Post 1", views: 99 }```
<h4 id='collection-operator-push'><code>.push(value)</code></h4>
Push a new value into the collection. Returns a `CollectionOperator` with the updated items.
``` tsconst data = await db.get<Schema["posts"]>('posts');
data .push({ id: 3, post: 'Post 3', views: 45 }) .value(); // [ { id: 1, title: "Post 1", views: 99 }, { id: 2, title: "Post 2", views: 30 }, { id: 3, title: "Post 3", views: 45 } ]```
<h4 id='collection-operator-findAll'><code>.findAll(predicate: Object | Function => boolean)</code></h4>
Searches through the items of the collection and returns a `CollectionOperator` of all occurrences that satisfy the predicate. The predicate can be of two forms:
1. An object with keys that you would like to match. The keys of the object should be a subset of the keys available on the items of the collection.2. A search-function where you can provide your custom logic and return `true` for the condition you are looking for.
Returns a `CollectionOperator` with the subset of items.
``` tsconst data = await db.get<Schema["posts"]>('posts');
data .findAll({ title: 'Post 1' }) .value();// [{ id: 1, title: "Post 1", views: 99 }]
// or
data .findAll((value) => { return value.views > 40; }) .value(); // [{ id: 1, title: "Post 1", views: 99 },{ id: 3, title: "Post 3", views: 45 }];```
<h4 id='collection-operator-findAllAndUpdate'><code>.findAllAndUpdate(predicate: Object | Function => boolean, updateMethod: (value) => T)</code></h4>
Searches through the collection and returns a `CollectionOperator` with all occurrences that satisfy the predicate updated with the return value of the _updateMethod_. The predicate can be of two forms:
1. An object with keys that you would like to match. The keys of the object should be a subset of the keys available on the items of the collection.2. A search-function where you can provide your custom logic and return `true` for the condition you are looking for.
Returns a `CollectionOperator` with the updated array.
``` tsconst data = await db.get<Schema["posts"]>('posts');
data .findAllAndUpdate({ title: 'Post 1' }, (value) => ({ ...value, title: 'Modified Post' })) .value(); // [{ id: 1, title: "Modified Post", views: 99 },{ id: 2, title: "Post 2", views: 30 }, { id: 3, title: "Post 3", views: 45 }]
// or
data .findAllAndUpdate((value) => { return value.views > 40; }, (value) => ({ ...value, title: 'Trending Post' })) .value(); // [{ id: 1, title: "Trending Post", views: 99 }, { id: 2, title: "Post 2", views: 30 }, { id: 3, title: "Trending Post", views: 45 }];```
<h4 id='collection-operator-findAllAndRemove'><code>.findAllAndRemove(predicate: Object | Function => boolean, updateMethod: (value) => T)</code></h4>
Searches through the collection and returns a new `CollectionOperator` where all occurrences that satisfy the predicate are *omitted*. The predicate can be of two forms:
1. An object with keys that you would like to match. The keys of the object should be a subset of the keys available on the items of the collection.2. A search-function where you can provide your custom logic and return `true` for the condition you are looking for.
Returns a `CollectionOperator` with the updated array.
``` tsconst data = await db.get<Schema["posts"]>('posts');
data .findAllAndRemove({ title: 'Post 1' }) .value(); // [{ id: 2, title: "Post 2", views: 30 }, { id: 3, title: "Post 3", views: 45 }]
// or
data .findAllAndRemove((value) => value.views > 40) .value(); // [{ id: 2, title: "Post 2", views: 30 }];```
<h4 id="collection-operator-findById"><code>.findById(id: string)</code></h4>
Syntactical sugar for `.findOne({ id })` .
<h4 id="collection-operator-findByIdAndRemove"><code>.findByIdAndRemove(id: string)</code></h4>
Syntactical sugar for `.findAllAndRemove({ id })` .
<h4 id="collection-operator-findByIdAndUpdate"><code>.findByIdAndUpdate(id: string, updateMethod: (value) => T)</code></h4>
Syntactical sugar for `.findAllAndUpdate({ id }, updateMethod)` .
<h4 id="collection-operator-sort"><code>.sort(predicate: string[] | Function => boolean)</code></h4>
Sorts and returns a new sorted `CollectionOperator` instance. The comparison predicate can be one of two types:
* **an array of keys** to select for sorting the items in the collection (priority is left-right).<br /> For example, when the predicate is `['views','id']` , the method will first sort *posts* in ascending order of *views* that each post has. Any posts which have the *same* number of views, will then be sorted by `id` .* a **compare function** similar to [ `Array.prototype.sort` ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#Parameters)'s `compareFunction` .
``` tsconst posts = await db.get<Schema["posts"]>('posts');
posts .sort(['views']) .value() // [{ id: 2, title: "Post 2", views: 30 }, { id: 1, title: "Post 1", views: 99 }]
// or
posts .sort((a,b) => a.views - b.views) .value() // [{ id: 2, title: "Post 2", views: 30 }, { id: 1, title: "Post 1", views: 99 }]```
<h4 id="collection-operator-page"><code>.page(page: number, pageSize: number)</code></h4>
Returns a paginated subset of the collection.
``` tsconst posts = await db.get<Schema["posts"]>('posts');
posts .page(1, 1) .value() // [{ id: 1, title: "Post 1", views: 99 }]```
<h4 id="collection-operator-pick"><code>.pick(keys: string[])</code></h4>
Returns a `CollectionOperator` of items with each item having only the *picked* keys. Only keys present on the type of the items in the collection are allowed. If the item is not an object, this method returns an empty object ( `{}` ) for it.
``` tsconst posts = await db.get<Schema["posts"]>('posts');
posts .pick(['title']) .value() // [{ title: "Post 1" }, { title: "Post 2" }]```
## Inspiration
This project has taken inspiration from [lowdb](https://github.com/typicode/lowdb) for the concept and [mongoose](https://mongoosejs.com/) for certain parts of the `CollectionOperator` API.
It aims to simplify the process of setting up a full-fledged db when building prototypes or small-scale applications like CLI tools or toy apps for Deno.

### 🚧 ⚠️ Disclaimer ⚠️ 🚧
<a id="disclaimer"></a>
**Disclaimer** : As mentioned above, this module is best used for small-scale apps and should not be used in a large production application and you may face issues like:* concurrency management (for writes)* storing and parsing large amounts of JSON data.
## Contributing
Want to raise an issue or pull request? Do give our [Contribution Guidelines](./.github/CONTRIBUTING.md) page a read. 🤓
## Contributors ✨
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --><!-- prettier-ignore-start --><!-- markdownlint-disable --><table> <tr> <td align="center"><a href="https://abinavseelan.com/?utm_source=github&utm_medium=documentation-allcontributors&utm_content=casualdb"><img src="https://avatars2.githubusercontent.com/u/6417910?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Abinav Seelan</b></sub></a><br /><a href="https://github.com/campvanilla/casualdb/commits?author=abinavseelan" title="Code">💻</a> <a href="https://github.com/campvanilla/casualdb/commits?author=abinavseelan" title="Documentation">📖</a> <a href="#ideas-abinavseelan" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/campvanilla/casualdb/commits?author=abinavseelan" title="Tests">⚠️</a></td> <td align="center"><a href="https://aditimohanty.com/?utm_source=github&utm_medium=documentation-allcontributors&utm_content=casualdb"><img src="https://avatars3.githubusercontent.com/u/6426069?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Aditi Mohanty</b></sub></a><br /><a href="https://github.com/campvanilla/casualdb/commits?author=rheaditi" title="Code">💻</a> <a href="https://github.com/campvanilla/casualdb/commits?author=rheaditi" title="Documentation">📖</a> <a href="#ideas-rheaditi" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/campvanilla/casualdb/commits?author=rheaditi" title="Tests">⚠️</a></td> <td align="center"><a href="http://www.willterry.me"><img src="https://avatars0.githubusercontent.com/u/12277149?v=4?s=100" width="100px;" alt=""/><br /><sub><b>William Terry</b></sub></a><br /><a href="https://github.com/campvanilla/casualdb/issues?q=author%3ATezza48" title="Bug reports">🐛</a></td> <td align="center"><a href="http://yaofur.com/"><img src="https://avatars0.githubusercontent.com/u/289392?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Keith Yao</b></sub></a><br /><a href="https://github.com/campvanilla/casualdb/issues?q=author%3Akebot" title="Bug reports">🐛</a> <a href="https://github.com/campvanilla/casualdb/commits?author=kebot" title="Code">💻</a></td> <td align="center"><a href="https://github.com/jackfiszr"><img src="https://avatars.githubusercontent.com/u/7147395?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jacek Fiszer</b></sub></a><br /><a href="https://github.com/campvanilla/casualdb/commits?author=jackfiszr" title="Code">💻</a></td> </tr></table>
<!-- markdownlint-restore --><!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
casualdb
Simple JSON "database" for Deno with type-safety! ⚡️
DocumentationDocumentation
GitHub Repositorycampvanilla/casualdb
GitHub Stars
29

Version Info

Tagged at
a year ago