@acodeninja/persist 3.0.0-next.9 → 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.
- package/README.md +60 -4
- package/docs/code-quirks.md +14 -14
- package/docs/defining-models.md +61 -0
- package/docs/http.openapi.yml +138 -0
- package/docs/{model-property-types.md → model-properties.md} +76 -43
- package/docs/models-as-properties.md +46 -46
- package/docs/search-queries.md +11 -13
- package/docs/storage-engines.md +19 -35
- package/docs/structured-queries.md +59 -48
- package/docs/transactions.md +6 -7
- package/exports/storage/http.js +3 -0
- package/exports/storage/s3.js +3 -0
- package/jest.config.cjs +8 -12
- package/package.json +2 -2
- package/src/Connection.js +750 -0
- package/src/Persist.js +29 -30
- package/src/Schema.js +175 -0
- package/src/{Query.js → data/FindIndex.js} +40 -24
- package/src/{type → data}/Model.js +95 -55
- package/src/data/Property.js +21 -0
- package/src/data/SearchIndex.js +106 -0
- package/src/{type/complex → data/properties}/ArrayType.js +5 -3
- package/src/{type/simple → data/properties}/BooleanType.js +3 -3
- package/src/{type/complex → data/properties}/CustomType.js +5 -5
- package/src/{type/simple → data/properties}/DateType.js +4 -4
- package/src/{type/simple → data/properties}/NumberType.js +3 -3
- package/src/{type/resolved → data/properties}/ResolvedType.js +3 -2
- package/src/{type/resolved → data/properties}/SlugType.js +1 -1
- package/src/{type/simple → data/properties}/StringType.js +3 -3
- package/src/{type → data/properties}/Type.js +13 -3
- package/src/engine/storage/HTTPStorageEngine.js +149 -253
- package/src/engine/storage/S3StorageEngine.js +108 -195
- package/src/engine/storage/StorageEngine.js +131 -549
- package/exports/engine/storage/file.js +0 -3
- package/exports/engine/storage/http.js +0 -3
- package/exports/engine/storage/s3.js +0 -3
- package/src/SchemaCompiler.js +0 -196
- package/src/Transactions.js +0 -145
- package/src/engine/StorageEngine.js +0 -472
- package/src/engine/storage/FileStorageEngine.js +0 -213
- package/src/type/index.js +0 -32
@@ -1,22 +1,22 @@
|
|
1
1
|
# Models as Properties
|
2
2
|
|
3
|
-
In addition to assigning basic types to model properties, you can assign entire models as properties. This allows for the creation of complex relationships between models. For information on using basic types for properties, refer to [model property types](./model-
|
3
|
+
In addition to assigning basic types to model properties, you can assign entire models as properties. This allows for the creation of complex relationships between models. For information on using basic types for properties, refer to [model property types](./model-properties.md).
|
4
4
|
|
5
5
|
We’ll explore different types of relationships between models using examples of `Person` and `Address` models, evolving the definition step by step.
|
6
6
|
|
7
7
|
```javascript
|
8
8
|
import Persist from "@acodeninja/persist";
|
9
9
|
|
10
|
-
export class Person extends Persist.
|
10
|
+
export class Person extends Persist.Model {
|
11
11
|
static {
|
12
|
-
|
12
|
+
Person.name = Persist.Property.String.required;
|
13
13
|
}
|
14
14
|
}
|
15
15
|
|
16
|
-
export class Address extends Persist.
|
16
|
+
export class Address extends Persist.Model {
|
17
17
|
static {
|
18
|
-
|
19
|
-
|
18
|
+
Address.address = Persist.Property.String.required;
|
19
|
+
Address.postcode = Persist.Property.String.required;
|
20
20
|
}
|
21
21
|
}
|
22
22
|
```
|
@@ -28,17 +28,17 @@ To define a **one-to-one** relationship between two models, set a static propert
|
|
28
28
|
```javascript
|
29
29
|
import Persist from "@acodeninja/persist";
|
30
30
|
|
31
|
-
export class Person extends Persist.
|
31
|
+
export class Person extends Persist.Model {
|
32
32
|
static {
|
33
|
-
|
34
|
-
|
33
|
+
Person.name = Persist.Property.String.required;
|
34
|
+
Person.address = () => Address;
|
35
35
|
}
|
36
36
|
}
|
37
37
|
|
38
|
-
export class Address extends Persist.
|
38
|
+
export class Address extends Persist.Model {
|
39
39
|
static {
|
40
|
-
|
41
|
-
|
40
|
+
Address.address = Persist.Property.String.required;
|
41
|
+
Address.postcode = Persist.Property.String.required;
|
42
42
|
}
|
43
43
|
}
|
44
44
|
```
|
@@ -46,7 +46,7 @@ export class Address extends Persist.Type.Model {
|
|
46
46
|
> [!IMPORTANT]
|
47
47
|
> **Why Use an Arrow Function?**
|
48
48
|
>
|
49
|
-
> The arrow function allows the model to reference another model that may not have been defined yet. Without it, you might encounter an error like `ReferenceError: Cannot access 'Address' before initialization`.
|
49
|
+
> The arrow function allows the model to reference another model that may not have been defined yet. Without it, you might encounter an error like `ReferenceError: Cannot access 'Address' before initialization`. See [Reference Errors](./code-quirks.md#reference-errors).
|
50
50
|
|
51
51
|
### Circular One-to-One Relationships
|
52
52
|
|
@@ -55,41 +55,41 @@ You can extend the previous example by allowing both models to reference each ot
|
|
55
55
|
```javascript
|
56
56
|
import Persist from "@acodeninja/persist";
|
57
57
|
|
58
|
-
export class Person extends Persist.
|
58
|
+
export class Person extends Persist.Model {
|
59
59
|
static {
|
60
|
-
|
61
|
-
|
60
|
+
Person.name = Persist.Property.String.required;
|
61
|
+
Person.address = () => Address;
|
62
62
|
}
|
63
63
|
}
|
64
64
|
|
65
|
-
export class Address extends Persist.
|
65
|
+
export class Address extends Persist.Model {
|
66
66
|
static {
|
67
|
-
|
68
|
-
|
69
|
-
|
67
|
+
Address.person = () => Person;
|
68
|
+
Address.address = Persist.Property.String.required;
|
69
|
+
Address.postcode = Persist.Property.String.required;
|
70
70
|
}
|
71
71
|
}
|
72
72
|
```
|
73
73
|
|
74
74
|
## One-to-Many Relationships
|
75
75
|
|
76
|
-
To model a **one-to-many** relationship, use `Persist.
|
76
|
+
To model a **one-to-many** relationship, use `Persist.Property.Array` to store an array of related models. For instance, if a `Person` can have multiple addresses, this is how it would be defined:
|
77
77
|
|
78
78
|
```javascript
|
79
79
|
import Persist from "@acodeninja/persist";
|
80
80
|
|
81
|
-
export class Person extends Persist.
|
81
|
+
export class Person extends Persist.Model {
|
82
82
|
static {
|
83
|
-
|
84
|
-
|
83
|
+
Person.name = Persist.Property.String.required;
|
84
|
+
Person.addresses = () => Persist.Property.Array.of(Address);
|
85
85
|
}
|
86
86
|
}
|
87
87
|
|
88
|
-
export class Address extends Persist.
|
88
|
+
export class Address extends Persist.Model {
|
89
89
|
static {
|
90
|
-
|
91
|
-
|
92
|
-
|
90
|
+
Address.person = () => Person;
|
91
|
+
Address.address = Persist.Property.String.required;
|
92
|
+
Address.postcode = Persist.Property.String.required;
|
93
93
|
}
|
94
94
|
}
|
95
95
|
```
|
@@ -103,18 +103,18 @@ In some cases, you may want to model a many-to-many relationship. For example, i
|
|
103
103
|
```javascript
|
104
104
|
import Persist from "@acodeninja/persist";
|
105
105
|
|
106
|
-
export class Person extends Persist.
|
106
|
+
export class Person extends Persist.Model {
|
107
107
|
static {
|
108
|
-
|
109
|
-
|
108
|
+
Person.name = Persist.Property.String.required;
|
109
|
+
Person.addresses = () => Persist.Property.Array.of(Address);
|
110
110
|
}
|
111
111
|
}
|
112
112
|
|
113
|
-
export class Address extends Persist.
|
113
|
+
export class Address extends Persist.Model {
|
114
114
|
static {
|
115
|
-
|
116
|
-
|
117
|
-
|
115
|
+
Address.people = () => Persist.Property.Array.of(Person);
|
116
|
+
Address.address = Persist.Property.String.required;
|
117
|
+
Address.postcode = Persist.Property.String.required;
|
118
118
|
}
|
119
119
|
}
|
120
120
|
```
|
@@ -128,26 +128,26 @@ In more complex scenarios, you may want to capture additional information about
|
|
128
128
|
```javascript
|
129
129
|
import Persist from "@acodeninja/persist";
|
130
130
|
|
131
|
-
export class Person extends Persist.
|
131
|
+
export class Person extends Persist.Model {
|
132
132
|
static {
|
133
|
-
|
134
|
-
|
133
|
+
Person.name = Persist.Property.String.required;
|
134
|
+
Person.addresses = () => Persist.Property.Array.of(Abode);
|
135
135
|
}
|
136
136
|
}
|
137
137
|
|
138
|
-
export class Abode extends Persist.
|
138
|
+
export class Abode extends Persist.Model {
|
139
139
|
static {
|
140
|
-
|
141
|
-
|
142
|
-
|
140
|
+
Abode.moveInDate = Persist.Property.Date.required;
|
141
|
+
Abode.address = () => Address;
|
142
|
+
Abode.person = () => Person;
|
143
143
|
}
|
144
144
|
}
|
145
145
|
|
146
|
-
export class Address extends Persist.
|
146
|
+
export class Address extends Persist.Model {
|
147
147
|
static {
|
148
|
-
|
149
|
-
|
150
|
-
|
148
|
+
Address.people = () => Persist.Property.Array.of(Person);
|
149
|
+
Address.address = Persist.Property.String.required;
|
150
|
+
Address.postcode = Persist.Property.String.required;
|
151
151
|
}
|
152
152
|
}
|
153
153
|
```
|
package/docs/search-queries.md
CHANGED
@@ -11,19 +11,19 @@ Let's consider the following models:
|
|
11
11
|
```javascript
|
12
12
|
import Persist from "@acodeninja/persist";
|
13
13
|
|
14
|
-
export class Person extends Persist.
|
14
|
+
export class Person extends Persist.Model {
|
15
15
|
static {
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
Person.name = Persist.Property.String.required;
|
17
|
+
Person.address = () => Address;
|
18
|
+
Person.searchProperties = () => ['name', 'address.address'];
|
19
19
|
}
|
20
20
|
}
|
21
21
|
|
22
|
-
export class Address extends Persist.
|
22
|
+
export class Address extends Persist.Model {
|
23
23
|
static {
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
Address.address = Persist.Property.String.required;
|
25
|
+
Address.postcode = Persist.Property.String.required;
|
26
|
+
Address.searchProperties = () => ['address', 'postcode'];
|
27
27
|
}
|
28
28
|
}
|
29
29
|
```
|
@@ -36,12 +36,10 @@ To search for any `Person` who lives on station road, the following search query
|
|
36
36
|
|
37
37
|
```javascript
|
38
38
|
import Persist from "@acodeninja/persist";
|
39
|
-
import Person from "./Person";
|
40
|
-
import FileStorageEngine from "@acodeninja/persist/engine/storage/file"
|
41
39
|
|
42
|
-
|
43
|
-
|
44
|
-
|
40
|
+
const connection = Persist.getConnections('people');
|
41
|
+
|
42
|
+
await connection.search(Person, 'station road');
|
45
43
|
```
|
46
44
|
|
47
45
|
This will find all matches for people who live at any address that includes `station road`.
|
package/docs/storage-engines.md
CHANGED
@@ -2,23 +2,25 @@
|
|
2
2
|
|
3
3
|
Persist makes several storage engines available for use with the library
|
4
4
|
|
5
|
-
##
|
5
|
+
## S3 Storage StorageEngine
|
6
6
|
|
7
|
-
To store models using
|
7
|
+
To store models using an S3 Bucket, use the `S3` storage engine. To use the `S3` engine you must also add the `@aws-sdk/client-s3` dependency to your `package.json` file.
|
8
8
|
|
9
9
|
```javascript
|
10
10
|
import Persist from "@acodeninja/persist";
|
11
|
-
import
|
11
|
+
import {S3Client} from "@aws-sdk/client-s3";
|
12
|
+
import S3StorageEngine from "@acodeninja/persist/storage/s3";
|
12
13
|
|
13
|
-
Persist.
|
14
|
-
|
15
|
-
|
14
|
+
const connection = Persist.registerConnection('remote', new S3StorageEngine({
|
15
|
+
bucket: 'test-bucket',
|
16
|
+
client: new S3Client(),
|
17
|
+
}));
|
16
18
|
|
17
|
-
export class Tag extends Persist.
|
18
|
-
static tag = Persist.
|
19
|
+
export class Tag extends Persist.Model {
|
20
|
+
static tag = Persist.Property.String.required;
|
19
21
|
}
|
20
22
|
|
21
|
-
await
|
23
|
+
await connection.put(new Tag({tag: 'documentation'}));
|
22
24
|
```
|
23
25
|
|
24
26
|
## HTTP Storage StorageEngine
|
@@ -27,35 +29,17 @@ To store models using an HTTP server, use the `HTTP` storage engine. When using
|
|
27
29
|
|
28
30
|
```javascript
|
29
31
|
import Persist from "@acodeninja/persist";
|
30
|
-
import HTTPStorageEngine from "@acodeninja/persist/
|
32
|
+
import HTTPStorageEngine from "@acodeninja/persist/storage/http";
|
31
33
|
|
32
|
-
Persist.
|
33
|
-
|
34
|
-
});
|
34
|
+
const connection = Persist.registerConnection('remote', new HTTPStorageEngine({
|
35
|
+
baseUrl: 'https://api.example.com',
|
36
|
+
}));
|
35
37
|
|
36
|
-
export class Tag extends Persist.
|
37
|
-
static tag = Persist.
|
38
|
+
export class Tag extends Persist.Model {
|
39
|
+
static tag = Persist.Property.String.required;
|
38
40
|
}
|
39
41
|
|
40
|
-
await
|
42
|
+
await connection.put(new Tag({tag: 'documentation'}));
|
41
43
|
```
|
42
44
|
|
43
|
-
|
44
|
-
|
45
|
-
To store models using an S3 Bucket, use the `S3` storage engine. To use the `S3` engine you must also add the `@aws-sdk/client-s3` dependency to your `package.json` file.
|
46
|
-
|
47
|
-
```javascript
|
48
|
-
import Persist from "@acodeninja/persist";
|
49
|
-
import S3StorageEngine from "@acodeninja/persist/engine/storage/s3";
|
50
|
-
|
51
|
-
Persist.addEngine('remote', S3StorageEngine, {
|
52
|
-
bucket: 'test-bucket',
|
53
|
-
client: new S3Client(),
|
54
|
-
});
|
55
|
-
|
56
|
-
export class Tag extends Persist.Type.Model {
|
57
|
-
static tag = Persist.Type.String.required;
|
58
|
-
}
|
59
|
-
|
60
|
-
await Persist.getEngine('remote', S3StorageEngine).put(new Tag({tag: 'documentation'}));
|
61
|
-
```
|
45
|
+
A generic Open API specification for an HTTP server integration with `Persist` can be found [here](./http.openapi.yml).
|
@@ -4,26 +4,27 @@ Use structured queries when you need to filter a collection of models using a se
|
|
4
4
|
|
5
5
|
## Indexing Data
|
6
6
|
|
7
|
-
To set index properties on a model, define the static function `
|
7
|
+
To set index properties on a model, define the static function `indexedProperties` as an arrow function that returns an array of fields that should be indexed for querying.
|
8
8
|
|
9
9
|
Let's consider the following models:
|
10
10
|
|
11
11
|
```javascript
|
12
12
|
import Persist from "@acodeninja/persist";
|
13
13
|
|
14
|
-
export class Person extends Persist.
|
14
|
+
export class Person extends Persist.Model {
|
15
15
|
static {
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
Person.name = Persist.Property.String.required;
|
17
|
+
Person.address = () => Address;
|
18
|
+
Person.indexedProperties = () => ['name', 'address.postcode'];
|
19
19
|
}
|
20
20
|
}
|
21
21
|
|
22
|
-
export class Address extends Persist.
|
22
|
+
export class Address extends Persist.Model {
|
23
23
|
static {
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
Address.address = Persist.Property.String.required;
|
25
|
+
Address.postcode = Persist.Property.String.required;
|
26
|
+
Address.people = () => Persist.Property.Array.of(Person)
|
27
|
+
Address.indexedProperties = () => ['postcode', 'people.[*].name'];
|
27
28
|
}
|
28
29
|
}
|
29
30
|
```
|
@@ -39,14 +40,12 @@ To query for a `Person` called `Joe Bloggs` an exact query can be written:
|
|
39
40
|
|
40
41
|
```javascript
|
41
42
|
import Persist from "@acodeninja/persist";
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
name: {$is: 'Joe Bloggs'},
|
49
|
-
});
|
43
|
+
|
44
|
+
const connection = Persist.getConnection('people');
|
45
|
+
|
46
|
+
await connection.find(Person, {
|
47
|
+
name: {$is: 'Joe Bloggs'},
|
48
|
+
});
|
50
49
|
```
|
51
50
|
|
52
51
|
## Querying Partial Matches
|
@@ -55,14 +54,30 @@ To query for a `Person` with name `Joe` a contains query can be written:
|
|
55
54
|
|
56
55
|
```javascript
|
57
56
|
import Persist from "@acodeninja/persist";
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
57
|
+
|
58
|
+
const connection = Persist.getConnection('people');
|
59
|
+
|
60
|
+
await connection.find(Person, {
|
61
|
+
name: {$contains: 'Joe'},
|
62
|
+
});
|
63
|
+
```
|
64
|
+
|
65
|
+
## Querying One-to-Many Model links
|
66
|
+
|
67
|
+
To query for all instances of `Address` with a linked `Person` with a name that contains `Joe` you can write:
|
68
|
+
|
69
|
+
```javascript
|
70
|
+
import Persist from "@acodeninja/persist";
|
71
|
+
|
72
|
+
const connection = Persist.getConnection('people');
|
73
|
+
|
74
|
+
await connection.find(Address, {
|
75
|
+
people: {
|
76
|
+
$contains: {
|
77
|
+
name: {$contains: 'Joe'},
|
78
|
+
},
|
79
|
+
},
|
80
|
+
});
|
66
81
|
```
|
67
82
|
|
68
83
|
## Querying Combination Matches
|
@@ -71,18 +86,16 @@ To query for a `Person` who lives at `SW1 1AA` a combination of contains and exa
|
|
71
86
|
|
72
87
|
```javascript
|
73
88
|
import Persist from "@acodeninja/persist";
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
$contains: {
|
82
|
-
postcode: {$is: 'SW1 1AA'},
|
83
|
-
},
|
89
|
+
|
90
|
+
const connection = Persist.getConnection('people');
|
91
|
+
|
92
|
+
await connection.find(Person, {
|
93
|
+
address: {
|
94
|
+
$contains: {
|
95
|
+
postcode: {$is: 'SW1 1AA'},
|
84
96
|
},
|
85
|
-
}
|
97
|
+
},
|
98
|
+
});
|
86
99
|
```
|
87
100
|
|
88
101
|
## Multiple Queries
|
@@ -91,17 +104,15 @@ To query for anyone called `Joe Bloggs` who lives in the `SW1` postcode area, we
|
|
91
104
|
|
92
105
|
```javascript
|
93
106
|
import Persist from "@acodeninja/persist";
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
$contains: {
|
103
|
-
postcode: {$contains: 'SW1'},
|
104
|
-
},
|
107
|
+
|
108
|
+
const connection = Persist.getConnection('people');
|
109
|
+
|
110
|
+
await connection.find(Person, {
|
111
|
+
name: {$is: 'Joe Bloggs'},
|
112
|
+
address: {
|
113
|
+
$contains: {
|
114
|
+
postcode: {$contains: 'SW1'},
|
105
115
|
},
|
106
|
-
}
|
116
|
+
},
|
117
|
+
});
|
107
118
|
```
|
package/docs/transactions.md
CHANGED
@@ -4,19 +4,18 @@ Create transactions to automatically roll back on failure.
|
|
4
4
|
|
5
5
|
```javascript
|
6
6
|
import Persist from "@acodeninja/persist";
|
7
|
-
import S3StorageEngine from "@acodeninja/persist/
|
7
|
+
import S3StorageEngine from "@acodeninja/persist/storage/s3";
|
8
8
|
|
9
|
-
Persist.
|
9
|
+
const connection = Persist.registerConnection('remote', new S3StorageEngine({
|
10
10
|
bucket: 'test-bucket',
|
11
11
|
client: new S3Client(),
|
12
|
-
|
13
|
-
});
|
12
|
+
}));
|
14
13
|
|
15
|
-
export class Tag extends Persist.
|
16
|
-
static tag = Persist.
|
14
|
+
export class Tag extends Persist.Model {
|
15
|
+
static tag = Persist.Property.String.required;
|
17
16
|
}
|
18
17
|
|
19
|
-
const transaction =
|
18
|
+
const transaction = connection.start();
|
20
19
|
|
21
20
|
await transaction.put(new Tag({tag: 'documentation'}));
|
22
21
|
await transaction.commit();
|
package/jest.config.cjs
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
/** @type {import('jest').Config} */
|
2
2
|
const config = {
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
'
|
7
|
-
'
|
3
|
+
collectCoverage: true,
|
4
|
+
coveragePathIgnorePatterns: ['test/fixtures/minified/'],
|
5
|
+
collectCoverageFrom: [
|
6
|
+
'**/*.js',
|
7
|
+
'!{node_modules,coverage,exports}/**',
|
8
|
+
'!*.config.js',
|
8
9
|
],
|
9
10
|
coverageThreshold: {
|
10
11
|
global: {
|
@@ -14,13 +15,8 @@ const config = {
|
|
14
15
|
statements: 100,
|
15
16
|
},
|
16
17
|
},
|
17
|
-
testMatch: [
|
18
|
-
|
19
|
-
],
|
20
|
-
watchPathIgnorePatterns: [
|
21
|
-
'coverage/',
|
22
|
-
'test/fixtures/minified',
|
23
|
-
],
|
18
|
+
testMatch: ['**/*.test.js'],
|
19
|
+
watchPathIgnorePatterns: ['coverage/', 'test/fixtures/minified'],
|
24
20
|
};
|
25
21
|
|
26
22
|
module.exports = config;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@acodeninja/persist",
|
3
|
-
"version": "3.0.0
|
3
|
+
"version": "3.0.0",
|
4
4
|
"description": "A JSON based data modelling and persistence module with alternate storage mechanisms.",
|
5
5
|
"type": "module",
|
6
6
|
"scripts": {
|
@@ -12,7 +12,7 @@
|
|
12
12
|
},
|
13
13
|
"exports": {
|
14
14
|
".": "./exports/default.js",
|
15
|
-
"./
|
15
|
+
"./storage/*": "./exports/storage/*.js"
|
16
16
|
},
|
17
17
|
"repository": {
|
18
18
|
"url": "https://github.com/acodeninja/persist"
|