@acodeninja/persist 3.0.0-next.8 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +60 -4
  2. package/docs/code-quirks.md +14 -14
  3. package/docs/defining-models.md +61 -0
  4. package/docs/http.openapi.yml +138 -0
  5. package/docs/{model-property-types.md → model-properties.md} +76 -43
  6. package/docs/models-as-properties.md +46 -46
  7. package/docs/search-queries.md +11 -13
  8. package/docs/storage-engines.md +19 -35
  9. package/docs/structured-queries.md +59 -48
  10. package/docs/transactions.md +6 -7
  11. package/exports/storage/http.js +3 -0
  12. package/exports/storage/s3.js +3 -0
  13. package/jest.config.cjs +8 -12
  14. package/package.json +2 -2
  15. package/src/Connection.js +750 -0
  16. package/src/Persist.js +29 -30
  17. package/src/Schema.js +175 -0
  18. package/src/{Query.js → data/FindIndex.js} +40 -24
  19. package/src/{type → data}/Model.js +95 -55
  20. package/src/data/Property.js +21 -0
  21. package/src/data/SearchIndex.js +106 -0
  22. package/src/{type/complex → data/properties}/ArrayType.js +5 -3
  23. package/src/{type/simple → data/properties}/BooleanType.js +3 -3
  24. package/src/{type/complex → data/properties}/CustomType.js +5 -5
  25. package/src/{type/simple → data/properties}/DateType.js +4 -4
  26. package/src/{type/simple → data/properties}/NumberType.js +3 -3
  27. package/src/{type/resolved → data/properties}/ResolvedType.js +3 -2
  28. package/src/{type/resolved → data/properties}/SlugType.js +1 -1
  29. package/src/{type/simple → data/properties}/StringType.js +3 -3
  30. package/src/{type → data/properties}/Type.js +13 -3
  31. package/src/engine/storage/HTTPStorageEngine.js +149 -253
  32. package/src/engine/storage/S3StorageEngine.js +108 -195
  33. package/src/engine/storage/StorageEngine.js +131 -549
  34. package/exports/engine/storage/file.js +0 -3
  35. package/exports/engine/storage/http.js +0 -3
  36. package/exports/engine/storage/s3.js +0 -3
  37. package/src/SchemaCompiler.js +0 -196
  38. package/src/Transactions.js +0 -145
  39. package/src/engine/StorageEngine.js +0 -350
  40. package/src/engine/storage/FileStorageEngine.js +0 -213
  41. package/src/type/index.js +0 -32
package/README.md CHANGED
@@ -9,18 +9,74 @@ A JSON based data modelling and persistence library with alternate storage mecha
9
9
 
10
10
  [![DeepSource](https://app.deepsource.com/gh/acodeninja/persist.svg/?label=active+issues&show_trend=true&token=Vd8_PJuRwwoq4_uBJ0_ymc06)](https://app.deepsource.com/gh/acodeninja/persist/)
11
11
  [![DeepSource](https://app.deepsource.com/gh/acodeninja/persist.svg/?label=code+coverage&show_trend=true&token=Vd8_PJuRwwoq4_uBJ0_ymc06)](https://app.deepsource.com/gh/acodeninja/persist/)
12
+ ![CodeRabbit PR Reviews](https://img.shields.io/coderabbit/prs/github/acodeninja/persist?utm_source=oss&utm_medium=github&utm_campaign=acodeninja%2Fpersist&labelColor=171717&color=FF570A&link=https%3A%2F%2Fcoderabbit.ai&label=CodeRabbit+Reviews)
12
13
 
13
14
  ## Features
14
15
 
15
16
  - Data modelling with relationships
16
17
  - Data validation
17
- - Data querying
18
- - Fuzzy search
19
- - Storage with: S3, HTTP and Filesystem
18
+ - Data queries and fuzzy search
19
+ - Store data using AWS S3 or HTTP APIs
20
+
21
+ ## Terms
22
+
23
+ `model`
24
+ : Defines the shape of an entity's data within your application.
25
+
26
+ `property`
27
+ : Analogous to a data type, allows defining the type of properties associated with a `model`.
28
+
29
+ `connection`
30
+ : Abstraction layer that establishes a connection to the configured storage engine and supports CRUD and query functionality.
31
+
32
+ `engine`
33
+ : An engine allows a connection to send instructions to a given service, it may support anything from a RESTFul HTTP API to a cloud service like S3 or DynamoDB.
34
+
35
+ ## Usage
36
+
37
+ ### Models
38
+
39
+ ```javascript
40
+ import Persist from '@acodeninja/persist';
41
+
42
+ class Person extends Persist.Model {
43
+ static {
44
+ Person.name = Persist.Property.String.required;
45
+ Person.dateOfBirth = Persist.Property.Date.required;
46
+ Person.height = Persist.Property.Number.required;
47
+ Person.isStudent = Persist.Property.Boolean.required;
48
+ }
49
+ }
50
+ ```
51
+
52
+ ### Storage
53
+
54
+ ```javascript
55
+ import Persist from '@acodeninja/persist';
56
+ import {S3Client} from "@aws-sdk/client-s3";
57
+ import S3StorageEngine from '@acodeninja/persist/storage/s3';
58
+
59
+ const engine = new S3StorageEngine({
60
+ bucket: 'person-storage',
61
+ client: new S3Client(),
62
+ });
63
+
64
+ const connection = Persist.registerConnection('people', engine, [Person]);
65
+
66
+ const person = new Person({
67
+ name: 'Joe Bloggs',
68
+ dateOfBirth: new Date('1993-04-02T00:00:00.000Z'),
69
+ height: 1.85,
70
+ isStudent: true,
71
+ });
72
+
73
+ await connection.put(person);
74
+ ```
20
75
 
21
76
  ## Find out more
22
77
 
23
- - [Model Property Types](./docs/model-property-types.md)
78
+ - [Defining Models](./docs/defining-models.md)
79
+ - [Model Property Types](./docs/model-properties.md)
24
80
  - [Models as Properties](./docs/models-as-properties.md)
25
81
  - [Structured Queries](./docs/structured-queries.md)
26
82
  - [Search Queries](./docs/search-queries.md)
@@ -9,15 +9,15 @@ When you bundle or minify JavaScript code for production, class names are often
9
9
  To avoid this problem, you have two options:
10
10
 
11
11
  1. Disable class name mangling in your minifier.
12
- 2. Use `this.setMinifiedName(name)` to manually specify the model's name.
12
+ 2. Use `this.withName(name)` to manually specify the model's name.
13
13
 
14
14
  ```javascript
15
15
  import Persist from "@acodeninja/persist";
16
16
 
17
- export class Person extends Persist.Type.Model {
17
+ export class Person extends Persist.Model {
18
18
  static {
19
- this.setMinifiedName('Person');
20
- this.name = Persist.Type.String.required;
19
+ Person.withName('Person');
20
+ Person.name = Persist.Property.String.required;
21
21
  }
22
22
  }
23
23
  ```
@@ -37,17 +37,17 @@ To avoid these errors, always define model relationships using arrow functions.
37
37
  ```javascript
38
38
  import Persist from "@acodeninja/persist";
39
39
 
40
- export class Person extends Persist.Type.Model {
40
+ export class Person extends Persist.Model {
41
41
  static {
42
- this.address = () => Address;
42
+ Person.address = () => Address;
43
43
  }
44
44
  }
45
45
 
46
- export class Address extends Persist.Type.Model {
46
+ export class Address extends Persist.Model {
47
47
  static {
48
- this.person = () => Person;
49
- this.address = Persist.Type.String.required;
50
- this.postcode = Persist.Type.String.required;
48
+ Address.person = () => Person;
49
+ Address.address = Persist.Property.String.required;
50
+ Address.postcode = Persist.Property.String.required;
51
51
  }
52
52
  }
53
53
  ```
@@ -60,12 +60,12 @@ When implementing thee `HTTP` engine for code that runs in the web browser, you
60
60
 
61
61
  ```javascript
62
62
  import Persist from "@acodeninja/persist";
63
- import HTTPStorageEngine from "@acodeninja/persist/engine/storage/http";
63
+ import HTTPStorageEngine from "@acodeninja/persist/storage/http";
64
64
 
65
- Persist.addEngine('remote', HTTPStorageEngine, {
66
- host: 'https://api.example.com',
65
+ Persist.registerConnection('people', new HTTPStorageEngine({
66
+ baseUrl: 'https://api.example.com',
67
67
  fetch: fetch.bind(window),
68
- });
68
+ }));
69
69
  ```
70
70
 
71
71
  This will ensure that `fetch` can access the window context which is required for it to function.
@@ -0,0 +1,61 @@
1
+ # Defining Models
2
+
3
+ Persist allows defining models as JavaScript classes and uses static attributes for the properties of each model, this allows for validation and type coercion when saving and retrieving data.
4
+
5
+ ## Defining a Model
6
+
7
+ The most basic model is a JavaScript class that extends `Persist.Model`.
8
+
9
+ ```javascript
10
+ class BasicModel extends Persist.Model {
11
+ }
12
+ ```
13
+
14
+ A model will always have an `id` property that is randomly generated using [ULID](https://github.com/ulid/spec).
15
+
16
+ ```javascript
17
+ const basic = new BasicModel();
18
+
19
+ console.log(basic.id) // BasicModel/01ARZ3NDEKTSV4RRFFQ69G5FAV
20
+ ```
21
+
22
+ ### Adding properties
23
+
24
+ Model properties can be added to a model using `Persist.Property.*`.
25
+
26
+ ```javascript
27
+ import Persist from '@acodeninja/persist';
28
+
29
+ class Person extends Persist.Model {
30
+ static {
31
+ Person.name = Persist.Property.String.required;
32
+ Person.dateOfBirth = Persist.Property.Date.required;
33
+ Person.height = Persist.Property.Number.required;
34
+ Person.isStudent = Persist.Property.Boolean.required;
35
+ }
36
+ }
37
+ ```
38
+
39
+ For a full list of available property types see [Model Property Types](./model-properties).
40
+
41
+ ### Linking Models
42
+
43
+ Models can be linked to other models by declaring them as properties.
44
+
45
+ ```javascript
46
+ class Address extends Persist.Model {
47
+ static {
48
+ Address.address = Persist.Property.String.required;
49
+ Address.postcode = Persist.Property.String.required;
50
+ }
51
+ }
52
+
53
+ class Person extends Persist.Model {
54
+ static {
55
+ Person.name = Persist.Property.String.required;
56
+ Person.address = Address;
57
+ }
58
+ }
59
+ ```
60
+
61
+ For a look at the ways that models can be linked check out [Models as Properties](./models-as-properties.md).
@@ -0,0 +1,138 @@
1
+ openapi: 3.1.0
2
+
3
+ info:
4
+ title: Persist Generic HTTP API
5
+ version: 1.0.0
6
+
7
+ paths:
8
+ /{model}/{id}:
9
+ get:
10
+ summary: Retrieve an existing model instance.
11
+ parameters:
12
+ - in: path
13
+ name: model
14
+ schema:
15
+ type: string
16
+ required: true
17
+ description: Name of the model
18
+ - in: path
19
+ name: id
20
+ schema:
21
+ type: string
22
+ required: true
23
+ description: Unique identifier for a model
24
+ responses:
25
+ '200':
26
+ description: Successful update operation
27
+ content:
28
+ application/json:
29
+ schema:
30
+ $ref: '#/components/schemas/Model'
31
+ '404':
32
+ description: Model not found
33
+ put:
34
+ summary: Create or update an existing model instance.
35
+ parameters:
36
+ - in: path
37
+ name: model
38
+ schema:
39
+ type: string
40
+ required: true
41
+ description: Name of the model
42
+ - in: path
43
+ name: id
44
+ schema:
45
+ type: string
46
+ required: true
47
+ description: Unique identifier for a model
48
+ requestBody:
49
+ description: Update or create a model instance.
50
+ content:
51
+ application/json:
52
+ schema:
53
+ $ref: '#/components/schemas/Model'
54
+ required: true
55
+ responses:
56
+ '200':
57
+ description: Successful update operation
58
+ '201':
59
+ description: Successful create operation
60
+ '422':
61
+ description: Validation exception
62
+ delete:
63
+ summary: Delete an instance of a model
64
+ parameters:
65
+ - in: path
66
+ name: model
67
+ schema:
68
+ type: string
69
+ required: true
70
+ description: Name of the model
71
+ - in: path
72
+ name: id
73
+ schema:
74
+ type: string
75
+ required: true
76
+ description: Unique identifier for a model
77
+ responses:
78
+ '204':
79
+ description: Successful operation
80
+ '422':
81
+ description: Validation exception
82
+ /{model}:
83
+ get:
84
+ summary: Retrieve an index of the current models.
85
+ parameters:
86
+ - in: path
87
+ name: model
88
+ schema:
89
+ type: string
90
+ required: true
91
+ description: Name of the model
92
+ responses:
93
+ '200':
94
+ description: Successful update operation
95
+ content:
96
+ application/json:
97
+ schema:
98
+ $ref: '#/components/schemas/ModelIndex'
99
+ '404':
100
+ description: Model not found
101
+ /{model}/search:
102
+ get:
103
+ summary: Retrieve a search index of the current models.
104
+ parameters:
105
+ - in: path
106
+ name: model
107
+ schema:
108
+ type: string
109
+ required: true
110
+ description: Name of the model
111
+ responses:
112
+ '200':
113
+ description: Successful update operation
114
+ content:
115
+ application/json:
116
+ schema:
117
+ $ref: '#/components/schemas/ModelIndex'
118
+ '404':
119
+ description: Model not found
120
+
121
+ components:
122
+ schemas:
123
+ Model:
124
+ required:
125
+ - id
126
+ type: object
127
+ additionalProperties: true
128
+ properties:
129
+ id:
130
+ type: string
131
+ ModelIndex:
132
+ properties:
133
+ "{model}/{id}":
134
+ type: object
135
+ additionalProperties: true
136
+ properties:
137
+ id:
138
+ type: string
@@ -1,4 +1,4 @@
1
- # Model Property Types
1
+ # Model Properties
2
2
 
3
3
  Persist uses a type definition for the properties of each model, this allows for validation and type coercion when saving and retrieving data.
4
4
 
@@ -11,103 +11,105 @@ Properties can be defined on a model by setting static properties to the value o
11
11
  ```javascript
12
12
  import Persist from '@acodeninja/persist';
13
13
 
14
- class Person extends Persist.Type.Model {
14
+ class Person extends Persist.Model {
15
15
  static {
16
- this.firstName = Persist.Type.String;
17
- this.lastName = Persist.Type.String;
16
+ Person.firstName = Persist.Property.String;
17
+ Person.lastName = Persist.Property.String;
18
18
  }
19
19
  }
20
20
  ```
21
21
 
22
- ## Simple Types
22
+ ## Simple Properties
23
23
 
24
- ### `Persist.Type.String`
24
+ ### `Persist.Property.String`
25
25
 
26
26
  Use the `String` type for model properties that should store a [string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String). The `String` type also supports the `.required` modifier to ensure that when the model is persisted a value must exist for it.
27
27
 
28
28
  ```javascript
29
29
  import Persist from '@acodeninja/persist';
30
30
 
31
- class Person extends Persist.Type.Model {
31
+ class Person extends Persist.Model {
32
32
  static {
33
- this.firstName = Persist.Type.String;
34
- this.lastName = Persist.Type.String.required;
33
+ Person.firstName = Persist.Property.String;
34
+ Person.lastName = Persist.Property.String.required;
35
35
  }
36
36
  }
37
37
  ```
38
38
 
39
- ### `Persist.Type.Boolean`
39
+ ### `Persist.Property.Boolean`
40
40
 
41
41
  Use the `Boolean` type for model properties that should store a [boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean). The `Boolean` type also supports the `.required` modifier to ensure that when the model is persisted a value must exist for it.
42
42
 
43
43
  ```javascript
44
44
  import Persist from '@acodeninja/persist';
45
45
 
46
- class Person extends Persist.Type.Model {
46
+ class Person extends Persist.Model {
47
47
  static {
48
- this.markettingEmailsActive = Persist.Type.Boolean;
49
- this.accountActive = Persist.Type.Boolean.required;
48
+ Person.marketingEmailsActive = Persist.Property.Boolean;
49
+ Person.accountActive = Persist.Property.Boolean.required;
50
50
  }
51
51
  }
52
52
  ```
53
53
 
54
- ### `Persist.Type.Number`
54
+ ### `Persist.Property.Number`
55
55
 
56
56
  Use the `Number` type for model properties that should store a [number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number). The `Number` type also supports the `.required` modifier to ensure that when the model is persisted a value must exist for it.
57
57
 
58
58
  ```javascript
59
59
  import Persist from '@acodeninja/persist';
60
60
 
61
- class Person extends Persist.Type.Model {
61
+ class Person extends Persist.Model {
62
62
  static {
63
- this.loginToken = Persist.Type.Number;
64
- this.accountId = Persist.Type.Number.required;
63
+ Person.loginToken = Persist.Property.Number;
64
+ Person.accountId = Persist.Property.Number.required;
65
65
  }
66
66
  }
67
67
  ```
68
68
 
69
- ### `Persist.Type.Date`
69
+ ### `Persist.Property.Date`
70
70
 
71
71
  Use the `Date` type for model properties that should store a [date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date). The `Date` type also supports the `.required` modifier to ensure that when the model is persisted a value must exist for it.
72
72
 
73
+ A property of `Date` will serialise to an ISO 8601 date format like `2011-10-05T14:48:00.000Z` when `.toData` is called on a model.
74
+
73
75
  ```javascript
74
76
  import Persist from '@acodeninja/persist';
75
77
 
76
- class Person extends Persist.Type.Model {
78
+ class Person extends Persist.Model {
77
79
  static {
78
- this.lastLogin = Persist.Type.Date;
79
- this.createdAt = Persist.Type.Date.required;
80
+ Person.lastLogin = Persist.Property.Date;
81
+ Person.createdAt = Persist.Property.Date.required;
80
82
  }
81
83
  }
82
84
  ```
83
85
 
84
- ## Complex Types
86
+ ## Complex Properties
85
87
 
86
- ### `Persist.Type.Array.of(type)`
88
+ ### `Persist.Property.Array.of(type)`
87
89
 
88
90
  Use the `Array` type for model properties that should store an array of another type or model. The `Array` type also supports the `.required` modifier to ensure that when the model is persisted a value must exist for it.
89
91
 
90
92
  ```javascript
91
93
  import Persist from '@acodeninja/persist';
92
94
 
93
- class Person extends Persist.Type.Model {
95
+ class Person extends Persist.Model {
94
96
  static {
95
- this.failedLoginAttempts = Persist.Type.Array.of(Persist.Type.Date);
96
- this.fullName = Persist.Type.Array.of(Persist.Type.String).required;
97
+ Person.failedLoginAttempts = Persist.Property.Array.of(Persist.Property.Date);
98
+ Person.fullName = Persist.Property.Array.of(Persist.Property.String).required;
97
99
  }
98
100
  }
99
101
  ```
100
102
 
101
- ### `Persist.Type.Custom.of(schema)`
103
+ ### `Persist.Property.Custom.of(schema)`
102
104
 
103
105
  Use the `Custom` type for model properties that should store a custom [json-schema draft-07](https://json-schema.org/draft-07/json-schema-hypermedia) object. You can also use any formats defined by the [`avj-formats`](https://ajv.js.org/packages/ajv-formats.html) library. The `Custom` type also supports the `.required` modifier to ensure that when the model is persisted a value must exist for it.
104
106
 
105
107
  ```javascript
106
108
  import Persist from '@acodeninja/persist';
107
109
 
108
- class Person extends Persist.Type.Model {
110
+ class Person extends Persist.Model {
109
111
  static {
110
- this.address = Persist.Type.Custom.of({
112
+ Person.address = Persist.Property.Custom.of({
111
113
  type: 'object',
112
114
  additionalProperties: false,
113
115
  required: ['line1', 'city', 'postcode'],
@@ -125,21 +127,21 @@ class Person extends Persist.Type.Model {
125
127
  }
126
128
  ```
127
129
 
128
- ## Resolved Types
130
+ ## Resolved Properties
129
131
 
130
132
  Resolved types are different from other types in that they do not directly store data themselves, rather they perform an action on another property of the model.
131
133
 
132
- ### `Persist.Type.Resolved.Slug.of(property)`
134
+ ### `Persist.Property.Resolved.Slug.of(property)`
133
135
 
134
136
  Use the `Slug` type for model properties that should have a slug version of another properties value. The `Custom` type also supports the `.required` modifier to ensure that when the model is persisted a value must exist for it.
135
137
 
136
138
  ```javascript
137
139
  import Persist from '@acodeninja/persist';
138
140
 
139
- class Page extends Persist.Type.Model {
141
+ class Page extends Persist.Model {
140
142
  static {
141
- this.title = Persist.Type.String;
142
- this.slug = Persist.Type.Resolved.Slug.of('title');
143
+ Page.title = Persist.Property.String;
144
+ Page.slug = Persist.Property.Resolved.Slug.of('title');
143
145
  }
144
146
  }
145
147
 
@@ -158,16 +160,47 @@ Models and most types support a modifier, this will alter the validation and per
158
160
  Most types support the `.required` modifier, which will alter validation to enforce the presence of the property when saving data.
159
161
 
160
162
  ```javascript
161
- class RequiredStringModel extends Persist.Type.Model {
163
+ class RequiredStringModel extends Persist.Model {
162
164
  static {
163
- this.requiredString = Type.String.required;
164
- this.requiredNumber = Type.Number.required;
165
- this.requiredBoolean = Type.Boolean.required;
166
- this.requiredDate = Type.Date.required;
167
- this.requiredArrayOfString = Type.Array.of(Type.String).required;
168
- this.requiredArrayOfNumber = Type.Array.of(Type.Number).required;
169
- this.requiredArrayOfBoolean = Type.Array.of(Type.Boolean).required;
170
- this.requiredArrayOfDate = Type.Array.of(Type.Date).required;
165
+ RequiredStringModel.requiredString = Persist.Property.String.required;
166
+ RequiredStringModel.requiredNumber = Persist.Property.Number.required;
167
+ RequiredStringModel.requiredBoolean = Persist.Property.Boolean.required;
168
+ RequiredStringModel.requiredDate = Persist.Property.Date.required;
169
+ RequiredStringModel.requiredArrayOfString = Persist.Property.Array.of(Persist.Property.String).required;
170
+ RequiredStringModel.requiredArrayOfNumber = Persist.Property.Array.of(Persist.Property.Number).required;
171
+ RequiredStringModel.requiredArrayOfBoolean = Persist.Property.Array.of(Persist.Property.Boolean).required;
172
+ RequiredStringModel.requiredArrayOfDate = Persist.Property.Array.of(Persist.Property.Date).required;
171
173
  }
172
174
  }
173
175
  ```
176
+
177
+ ## Custom Property Types
178
+
179
+ Under the hood, model validation uses the [ajv](https://ajv.js.org/) library with [ajv-formats](https://ajv.js.org/packages/ajv-formats.html) included. Because of this, you can create your own property types.
180
+
181
+ Say you want to attach an IPv4 address to your models. The following type class can accomplish this.
182
+
183
+ ```javascript
184
+ import Persist from '@acodeninja/persist';
185
+
186
+ class IPv4Type extends Persist.Property.Type {
187
+ static {
188
+ // Set the type of the property to string
189
+ IPv4Type._type = 'string';
190
+ // Use the ajv extended format "ipv4"
191
+ IPv4Type._format = 'ipv4';
192
+ // Ensure that even when minified, the name of the constructor is IPv4
193
+ Object.defineProperty(IPv4Type, 'name', {value: 'IPv4'});
194
+ }
195
+ }
196
+ ```
197
+
198
+ This type can then be used in any model as needed.
199
+
200
+ ```javascript
201
+ import Persist from '@acodeninja/persist';
202
+
203
+ class StaticIP extends Persist.Model {
204
+ static ip = IPv4Type.required;
205
+ }
206
+ ```