@acodeninja/persist 3.0.0-next.2 → 3.0.0-next.20
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 +6 -6
- package/docs/defining-models.md +61 -0
- package/docs/http.openapi.yml +138 -0
- package/docs/{model-property-types.md → model-properties.md} +37 -35
- package/docs/models-as-properties.md +40 -40
- package/docs/search-queries.md +3 -5
- package/docs/storage-engines.md +19 -35
- package/docs/structured-queries.md +56 -45
- 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 +631 -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 +41 -26
- package/src/data/Property.js +19 -0
- package/src/data/SearchIndex.js +106 -0
- package/src/{type/complex → data/properties}/ArrayType.js +1 -1
- package/src/{type/simple → data/properties}/BooleanType.js +1 -1
- package/src/{type/complex → data/properties}/CustomType.js +1 -1
- package/src/{type/simple → data/properties}/DateType.js +1 -1
- package/src/{type/simple → data/properties}/NumberType.js +1 -1
- package/src/{type/resolved → data/properties}/ResolvedType.js +3 -2
- package/src/{type/simple → data/properties}/StringType.js +1 -1
- package/src/{type → data/properties}/Type.js +8 -0
- package/src/engine/storage/HTTPStorageEngine.js +149 -253
- package/src/engine/storage/S3StorageEngine.js +108 -195
- package/src/engine/storage/StorageEngine.js +114 -550
- 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 -192
- package/src/Transactions.js +0 -145
- package/src/engine/StorageEngine.js +0 -250
- package/src/engine/storage/FileStorageEngine.js +0 -213
- package/src/type/index.js +0 -32
- /package/src/{type/resolved → data/properties}/SlugType.js +0 -0
@@ -1,125 +1,100 @@
|
|
1
|
-
import {
|
2
|
-
|
1
|
+
import {
|
2
|
+
DeleteObjectCommand,
|
3
|
+
GetObjectCommand,
|
4
|
+
NoSuchKey,
|
5
|
+
PutObjectCommand,
|
6
|
+
} from '@aws-sdk/client-s3';
|
7
|
+
import StorageEngine, {
|
8
|
+
MisconfiguredStorageEngineError,
|
9
|
+
ModelNotFoundStorageEngineError,
|
10
|
+
} from './StorageEngine.js';
|
3
11
|
|
4
|
-
/**
|
5
|
-
* Represents an error specific to the S3 engine operations.
|
6
|
-
* @class S3StorageEngineError
|
7
|
-
* @extends EngineError
|
8
|
-
*/
|
9
|
-
class S3StorageEngineError extends EngineError {}
|
10
|
-
|
11
|
-
/**
|
12
|
-
* Error indicating a failure when putting an object to S3.
|
13
|
-
* @class FailedPutS3StorageEngineError
|
14
|
-
* @extends S3StorageEngineError
|
15
|
-
*/
|
16
|
-
class FailedPutS3StorageEngineError extends S3StorageEngineError {}
|
17
|
-
|
18
|
-
/**
|
19
|
-
* S3StorageEngine is an extension of the StorageEngine class that provides methods for interacting with AWS S3.
|
20
|
-
* It allows for storing, retrieving, and managing model data in an S3 bucket.
|
21
|
-
*
|
22
|
-
* @class S3StorageEngine
|
23
|
-
* @extends StorageEngine
|
24
|
-
*/
|
25
12
|
class S3StorageEngine extends StorageEngine {
|
26
13
|
/**
|
27
|
-
*
|
28
|
-
*
|
29
|
-
* @param {
|
30
|
-
* @param {S3Client} [configuration.client] -
|
31
|
-
* @param {string} [configuration.bucket] - The S3 bucket to perform operations against.
|
32
|
-
* @param {string?} [configuration.prefix] - The optional prefix in the bucket to perform operations against.
|
33
|
-
* @returns {Object} The configured settings for the HTTP engine.
|
34
|
-
*/
|
35
|
-
static configure(configuration = {}) {
|
36
|
-
return super.configure(configuration);
|
37
|
-
}
|
38
|
-
|
39
|
-
/**
|
40
|
-
* Validates the S3 engine configuration to ensure necessary parameters (bucket and client) are present.
|
41
|
-
* Throws an error if the configuration is invalid.
|
42
|
-
*
|
43
|
-
* @throws {MissConfiguredError} Thrown when the configuration is missing required parameters.
|
14
|
+
* @param {Object} configuration - Configuration object containing fetch options and other settings.
|
15
|
+
* @param {string} [configuration.bucket] - Hostname and protocol of the HTTP service to use (ie: https://example.com).
|
16
|
+
* @param {string?} [configuration.prefix] - The prefix on the host to perform operations against.
|
17
|
+
* @param {S3Client} [configuration.client] - The http client that implements fetch.
|
44
18
|
*/
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
) throw new MissConfiguredError(this.configuration);
|
19
|
+
constructor(configuration) {
|
20
|
+
super(configuration);
|
21
|
+
if (!configuration?.bucket || !configuration?.client)
|
22
|
+
throw new MisconfiguredStorageEngineError('both bucket and client must be provided', this);
|
50
23
|
}
|
51
24
|
|
52
25
|
/**
|
53
|
-
* Retrieves an object from S3 by its
|
54
|
-
*
|
55
|
-
* @
|
56
|
-
* @
|
57
|
-
*
|
58
|
-
* @throws {Error} Thrown if there is an issue with the S3 client request.
|
26
|
+
* Retrieves an object from S3 by its id
|
27
|
+
* @param {string} id
|
28
|
+
* @returns {Promise<Object>}
|
29
|
+
* @throws {ModelNotFoundStorageEngineError|Error} Thrown if there is an issue with the S3 client request.
|
59
30
|
*/
|
60
|
-
|
61
|
-
const objectPath = [
|
31
|
+
async getModel(id) {
|
32
|
+
const objectPath = this.#generatePath([`${id}.json`]);
|
62
33
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
34
|
+
try {
|
35
|
+
const data = await this.configuration.client.send(new GetObjectCommand({
|
36
|
+
Bucket: this.configuration.bucket,
|
37
|
+
Key: objectPath,
|
38
|
+
}));
|
67
39
|
|
68
|
-
|
40
|
+
return JSON.parse(await data.Body.transformToString());
|
41
|
+
} catch (error) {
|
42
|
+
if (error instanceof NoSuchKey) {
|
43
|
+
throw new ModelNotFoundStorageEngineError(id);
|
44
|
+
}
|
45
|
+
throw error;
|
46
|
+
}
|
69
47
|
}
|
70
48
|
|
71
49
|
/**
|
72
|
-
*
|
73
|
-
*
|
74
|
-
* @
|
75
|
-
* @returns {Promise<void>} Resolves when the model has been deleted.
|
76
|
-
* @throws {Error} Throws if the model cannot be deleted.
|
50
|
+
* Upload an object to S3
|
51
|
+
* @param {object} model - The model object to upload.
|
52
|
+
* @returns {Promise<void>}
|
77
53
|
*/
|
78
|
-
|
79
|
-
const
|
54
|
+
async putModel(model) {
|
55
|
+
const Key = this.#generatePath([`${model.id}.json`]);
|
80
56
|
|
81
|
-
await this.configuration.client.send(new
|
57
|
+
await this.configuration.client.send(new PutObjectCommand({
|
58
|
+
Key,
|
59
|
+
Body: JSON.stringify(model),
|
82
60
|
Bucket: this.configuration.bucket,
|
83
|
-
|
61
|
+
ContentType: 'application/json',
|
84
62
|
}));
|
85
|
-
|
86
|
-
return undefined;
|
87
63
|
}
|
88
64
|
|
89
65
|
/**
|
90
|
-
*
|
91
|
-
*
|
92
|
-
* @
|
93
|
-
* @
|
94
|
-
*
|
95
|
-
* @throws {FailedPutS3StorageEngineError} Thrown if there is an error during the S3 PutObject operation.
|
66
|
+
* Delete a model by its id
|
67
|
+
* @param {string} id
|
68
|
+
* @throws {ModelNotFoundStorageEngineError|Error}
|
69
|
+
* @return Promise<void>
|
96
70
|
*/
|
97
|
-
|
98
|
-
const Key = [
|
99
|
-
|
71
|
+
async deleteModel(id) {
|
72
|
+
const Key = this.#generatePath([`${id}.json`]);
|
100
73
|
try {
|
101
|
-
await this.configuration.client.send(new
|
102
|
-
Key,
|
103
|
-
Body: JSON.stringify(model.toData()),
|
74
|
+
await this.configuration.client.send(new DeleteObjectCommand({
|
104
75
|
Bucket: this.configuration.bucket,
|
105
|
-
|
76
|
+
Key,
|
106
77
|
}));
|
107
78
|
} catch (error) {
|
108
|
-
|
79
|
+
if (error instanceof NoSuchKey) {
|
80
|
+
throw new ModelNotFoundStorageEngineError(id);
|
81
|
+
}
|
82
|
+
throw error;
|
109
83
|
}
|
110
84
|
}
|
111
85
|
|
112
86
|
/**
|
113
|
-
*
|
114
|
-
*
|
115
|
-
* @
|
116
|
-
* @
|
87
|
+
* Get a model's index data
|
88
|
+
* @param {Model.constructor} modelConstructor
|
89
|
+
* @throws MethodNotImplementedStorageEngineError
|
90
|
+
* @return Promise<object>
|
117
91
|
*/
|
118
|
-
|
92
|
+
async getIndex(modelConstructor) {
|
93
|
+
const Key = this.#generatePath([modelConstructor.name, '_index.json']);
|
119
94
|
try {
|
120
95
|
const data = await this.configuration.client.send(new GetObjectCommand({
|
121
|
-
Key: [this.configuration.prefix, model?.toString(), '_index.json'].filter(e => Boolean(e)).join('/'),
|
122
96
|
Bucket: this.configuration.bucket,
|
97
|
+
Key,
|
123
98
|
}));
|
124
99
|
|
125
100
|
return JSON.parse(await data.Body.transformToString());
|
@@ -129,128 +104,66 @@ class S3StorageEngine extends StorageEngine {
|
|
129
104
|
}
|
130
105
|
|
131
106
|
/**
|
132
|
-
*
|
133
|
-
*
|
134
|
-
* @param {
|
135
|
-
* @
|
136
|
-
* @
|
137
|
-
*/
|
138
|
-
static async putIndex(index) {
|
139
|
-
/**
|
140
|
-
* Process an index of models
|
141
|
-
* @param {string} location
|
142
|
-
* @param {Array<Model>} models
|
143
|
-
* @throws FailedPutS3StorageEngineError
|
144
|
-
* @return {Promise<void>}
|
145
|
-
*/
|
146
|
-
const processIndex = async (location, models) => {
|
147
|
-
const Key = [this.configuration.prefix, location, '_index.json'].filter(e => Boolean(e)).join('/');
|
148
|
-
const currentIndex = await this.getIndex(location);
|
149
|
-
|
150
|
-
try {
|
151
|
-
await this.configuration.client.send(new PutObjectCommand({
|
152
|
-
Key,
|
153
|
-
Bucket: this.configuration.bucket,
|
154
|
-
ContentType: 'application/json',
|
155
|
-
Body: JSON.stringify({
|
156
|
-
...currentIndex,
|
157
|
-
...Object.fromEntries(
|
158
|
-
Object.entries(models).map(([k, v]) => [k, v?.toIndexData?.() || v]),
|
159
|
-
),
|
160
|
-
}),
|
161
|
-
}));
|
162
|
-
} catch (error) {
|
163
|
-
throw new FailedPutS3StorageEngineError(`Failed to put s3://${this.configuration.bucket}/${Key}`, error);
|
164
|
-
}
|
165
|
-
};
|
166
|
-
|
167
|
-
for (const [location, models] of Object.entries(index)) {
|
168
|
-
await processIndex(location, models);
|
169
|
-
}
|
170
|
-
|
171
|
-
await processIndex(null, Object.values(index).reduce((accumulator, currentValue) => {
|
172
|
-
Object.keys(currentValue).forEach(key => {
|
173
|
-
accumulator[key] = currentValue[key];
|
174
|
-
});
|
175
|
-
return accumulator;
|
176
|
-
}, {}));
|
177
|
-
}
|
178
|
-
|
179
|
-
/**
|
180
|
-
* Retrieves the compiled search index for a specific model from S3.
|
181
|
-
*
|
182
|
-
* @param {Model.constructor} model - The model whose search index to retrieve.
|
183
|
-
* @returns {Promise<Object>} The compiled search index.
|
184
|
-
*/
|
185
|
-
static getSearchIndexCompiled(model) {
|
186
|
-
return this.configuration.client.send(new GetObjectCommand({
|
187
|
-
Key: [this.configuration.prefix, model.toString(), '_search_index.json'].join('/'),
|
188
|
-
Bucket: this.configuration.bucket,
|
189
|
-
})).then(data => data.Body.transformToString())
|
190
|
-
.then(JSON.parse);
|
191
|
-
}
|
192
|
-
|
193
|
-
/**
|
194
|
-
* Retrieves the raw (uncompiled) search index for a specific model from S3.
|
195
|
-
*
|
196
|
-
* @param {Model.constructor} model - The model whose raw search index to retrieve.
|
197
|
-
* @returns {Promise<Object>} The raw search index, or an empty object if not found.
|
107
|
+
* Put a model's index data
|
108
|
+
* @param {Model.constructor} modelConstructor
|
109
|
+
* @param {object} index
|
110
|
+
* @throws MethodNotImplementedStorageEngineError
|
111
|
+
* @return Promise<void>
|
198
112
|
*/
|
199
|
-
|
200
|
-
|
201
|
-
|
113
|
+
async putIndex(modelConstructor, index) {
|
114
|
+
const Key = this.#generatePath([modelConstructor.name, '_index.json']);
|
115
|
+
await this.configuration.client.send(new PutObjectCommand({
|
116
|
+
Key,
|
202
117
|
Bucket: this.configuration.bucket,
|
203
|
-
|
204
|
-
|
205
|
-
|
118
|
+
Body: JSON.stringify(index),
|
119
|
+
ContentType: 'application/json',
|
120
|
+
}));
|
206
121
|
}
|
207
122
|
|
208
123
|
/**
|
209
|
-
*
|
210
|
-
*
|
211
|
-
* @
|
212
|
-
* @param {Object} compiledIndex - The compiled search index data.
|
213
|
-
* @returns {Promise<void>}
|
214
|
-
*
|
215
|
-
* @throws {FailedPutS3StorageEngineError} Thrown if there is an error during the S3 PutObject operation.
|
124
|
+
* Get a model's raw search index data
|
125
|
+
* @param {Model.constructor} modelConstructor
|
126
|
+
* @return Promise<Record<string, object>>
|
216
127
|
*/
|
217
|
-
|
218
|
-
const Key = [
|
128
|
+
async getSearchIndex(modelConstructor) {
|
129
|
+
const Key = this.#generatePath([modelConstructor.name, '_search_index.json']);
|
219
130
|
|
220
131
|
try {
|
221
|
-
await this.configuration.client.send(new
|
222
|
-
Key,
|
223
|
-
Body: JSON.stringify(compiledIndex),
|
132
|
+
const data = await this.configuration.client.send(new GetObjectCommand({
|
224
133
|
Bucket: this.configuration.bucket,
|
225
|
-
|
134
|
+
Key,
|
226
135
|
}));
|
227
|
-
|
228
|
-
|
136
|
+
|
137
|
+
return JSON.parse(await data.Body.transformToString());
|
138
|
+
} catch (_error) {
|
139
|
+
return {};
|
229
140
|
}
|
230
141
|
}
|
231
142
|
|
232
143
|
/**
|
233
|
-
*
|
234
|
-
*
|
235
|
-
* @param {
|
236
|
-
* @
|
237
|
-
* @returns {Promise<void>}
|
238
|
-
*
|
239
|
-
* @throws {FailedPutS3StorageEngineError} Thrown if there is an error during the S3 PutObject operation.
|
144
|
+
* Put a model's search index data
|
145
|
+
* @param {Model.constructor} modelConstructor
|
146
|
+
* @param {Record<string, object>} index
|
147
|
+
* @return Promise<void>
|
240
148
|
*/
|
241
|
-
|
242
|
-
const Key = [
|
149
|
+
async putSearchIndex(modelConstructor, index) {
|
150
|
+
const Key = this.#generatePath([modelConstructor.name, '_search_index.json']);
|
243
151
|
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
152
|
+
await this.configuration.client.send(new PutObjectCommand({
|
153
|
+
Key,
|
154
|
+
Bucket: this.configuration.bucket,
|
155
|
+
Body: JSON.stringify(index),
|
156
|
+
ContentType: 'application/json',
|
157
|
+
}));
|
158
|
+
}
|
159
|
+
|
160
|
+
/**
|
161
|
+
* Generate an S3 prefix path
|
162
|
+
* @param {Array<string>} path
|
163
|
+
* @return {string}
|
164
|
+
*/
|
165
|
+
#generatePath(path) {
|
166
|
+
return [this.configuration.prefix].concat(path).filter(Boolean).join('/');
|
254
167
|
}
|
255
168
|
}
|
256
169
|
|