@twin.org/entity-storage-service 0.0.3-next.8 → 0.9.0-next.1
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/dist/es/entities/schemaVersion.js +39 -0
- package/dist/es/entities/schemaVersion.js.map +1 -0
- package/dist/es/entityStorageRoutes.js +176 -4
- package/dist/es/entityStorageRoutes.js.map +1 -1
- package/dist/es/entityStorageService.js +44 -49
- package/dist/es/entityStorageService.js.map +1 -1
- package/dist/es/index.js +6 -1
- package/dist/es/index.js.map +1 -1
- package/dist/es/models/IEntityStorageRoutesExamples.js.map +1 -1
- package/dist/es/models/ISchemaVersionServiceConfig.js +4 -0
- package/dist/es/models/ISchemaVersionServiceConfig.js.map +1 -0
- package/dist/es/models/ISchemaVersionServiceConstructorOptions.js +2 -0
- package/dist/es/models/ISchemaVersionServiceConstructorOptions.js.map +1 -0
- package/dist/es/schema.js +11 -0
- package/dist/es/schema.js.map +1 -0
- package/dist/es/schemaVersionService.js +356 -0
- package/dist/es/schemaVersionService.js.map +1 -0
- package/dist/types/entities/schemaVersion.d.ts +19 -0
- package/dist/types/entityStorageRoutes.d.ts +33 -1
- package/dist/types/entityStorageService.d.ts +38 -3
- package/dist/types/index.d.ts +6 -1
- package/dist/types/models/IEntityStorageRoutesExamples.d.ts +8 -1
- package/dist/types/models/ISchemaVersionServiceConfig.d.ts +9 -0
- package/dist/types/models/ISchemaVersionServiceConstructorOptions.d.ts +15 -0
- package/dist/types/schema.d.ts +4 -0
- package/dist/types/schemaVersionService.d.ts +52 -0
- package/docs/changelog.md +561 -51
- package/docs/open-api/spec.json +439 -1
- package/docs/reference/classes/EntityStorageService.md +117 -3
- package/docs/reference/classes/SchemaVersion.md +39 -0
- package/docs/reference/classes/SchemaVersionService.md +103 -0
- package/docs/reference/functions/entityStorageCount.md +31 -0
- package/docs/reference/functions/entityStorageEmpty.md +31 -0
- package/docs/reference/functions/entityStorageRemoveBatch.md +31 -0
- package/docs/reference/functions/entityStorageSetBatch.md +31 -0
- package/docs/reference/functions/initSchema.md +9 -0
- package/docs/reference/index.md +9 -0
- package/docs/reference/interfaces/IEntityStorageRoutesExamples.md +16 -0
- package/docs/reference/interfaces/ISchemaVersionServiceConfig.md +11 -0
- package/docs/reference/interfaces/ISchemaVersionServiceConstructorOptions.md +25 -0
- package/locales/en.json +17 -2
- package/package.json +10 -9
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
// Copyright 2026 IOTA Stiftung.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
3
|
+
import { ComponentFactory, GeneralError, Is } from "@twin.org/core";
|
|
4
|
+
import { EntitySchemaFactory, EntitySchemaHelper } from "@twin.org/entity";
|
|
5
|
+
import { EntityStorageConnectorFactory, MigrationHelper, SchemaMigrationFactory } from "@twin.org/entity-storage-models";
|
|
6
|
+
import { SchemaVersion } from "./entities/schemaVersion.js";
|
|
7
|
+
/**
|
|
8
|
+
* Service that checks and applies entity schema migrations at every node start-up.
|
|
9
|
+
*
|
|
10
|
+
* This service must be the first entry in coreTypeInitialisers.json. The engine iterates that
|
|
11
|
+
* array in order to determine start sequence — there is no engine-level priority mechanism, so
|
|
12
|
+
* registration position is the only guarantee that start() runs before any other service.
|
|
13
|
+
* By the time start() is called, all component bootstraps have completed (every table already
|
|
14
|
+
* exists) and EntitySchemaFactory / EntityStorageConnectorFactory are fully populated with every
|
|
15
|
+
* registered schema and connector.
|
|
16
|
+
*
|
|
17
|
+
* Migration mechanics: old schema versions are registered in EntitySchemaFactory by naming
|
|
18
|
+
* convention — current schema = "MyEntity", first history = "MyEntityV0", second = "MyEntityV1".
|
|
19
|
+
* The service groups schemas by base name (strips the trailing V number suffix) and resolves the
|
|
20
|
+
* migration chain automatically by diffing consecutive versioned schemas. For steps that require
|
|
21
|
+
* property renames or a custom transform hook, register an optional ISchemaMigration entry in
|
|
22
|
+
* SchemaMigrationFactory under the key "Base_from_to" (e.g. "MyEntity_0_1").
|
|
23
|
+
*
|
|
24
|
+
* Crash-window note: finalizeMigration and the subsequent version-record write are two
|
|
25
|
+
* separate operations. If the process dies between them the next boot re-runs the chain
|
|
26
|
+
* over already-migrated data. applyEntityTransform is NOT idempotent for structural changes
|
|
27
|
+
* (newly-added optional fields would be dropped on re-run). A transaction spanning both
|
|
28
|
+
* writes is a precondition for production; track this in the concurrency follow-up.
|
|
29
|
+
*/
|
|
30
|
+
export class SchemaVersionService {
|
|
31
|
+
/**
|
|
32
|
+
* Runtime name for the class.
|
|
33
|
+
*/
|
|
34
|
+
static CLASS_NAME = "SchemaVersionService";
|
|
35
|
+
/**
|
|
36
|
+
* Regex to detect a versioned schema name and extract the base name and version number.
|
|
37
|
+
* Matches names like "MyEntityV0", "AuditableItemGraphV2", etc.
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
static _VERSION_SUFFIX_RE = /^(.+)V(\d+)$/;
|
|
41
|
+
/**
|
|
42
|
+
* The connector used to read and write SchemaVersion records.
|
|
43
|
+
* Not readonly because finalizeMigration may return a replacement connector object.
|
|
44
|
+
* @internal
|
|
45
|
+
*/
|
|
46
|
+
_versionConnector;
|
|
47
|
+
/**
|
|
48
|
+
* Optional config passed through constructor options.
|
|
49
|
+
* @internal
|
|
50
|
+
*/
|
|
51
|
+
_config;
|
|
52
|
+
/**
|
|
53
|
+
* Create a new SchemaVersionService.
|
|
54
|
+
* @param options Optional constructor options.
|
|
55
|
+
*/
|
|
56
|
+
constructor(options) {
|
|
57
|
+
this._versionConnector = EntityStorageConnectorFactory.get(options?.schemaVersionStorageType ?? "schema-version");
|
|
58
|
+
this._config = options?.config;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Returns the class name.
|
|
62
|
+
* @returns The class name.
|
|
63
|
+
*/
|
|
64
|
+
className() {
|
|
65
|
+
return SchemaVersionService.CLASS_NAME;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Reads all registered entity schemas, groups versioned schemas by base name, reads the
|
|
69
|
+
* full schemaVersion table in one pass, then orchestrates chain migrations for any schema
|
|
70
|
+
* whose stored version is behind the current version declared in EntitySchemaFactory.
|
|
71
|
+
* SchemaVersion itself is processed first so the version store is migrated before any
|
|
72
|
+
* version records are written for other schemas.
|
|
73
|
+
*
|
|
74
|
+
* Runs after all component bootstraps, so every managed table already exists.
|
|
75
|
+
* @param nodeLoggingComponentType An optional logging component type.
|
|
76
|
+
*/
|
|
77
|
+
async start(nodeLoggingComponentType) {
|
|
78
|
+
const logging = ComponentFactory.getIfExists(nodeLoggingComponentType);
|
|
79
|
+
const migrationOptions = {
|
|
80
|
+
batchSize: this._config?.batchSize,
|
|
81
|
+
onProgress: async (progressItem, itemTotal, itemIndex) => {
|
|
82
|
+
await this.logProgress(logging, progressItem, itemTotal, itemIndex);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
// 1. Collect all registered schema names and partition into current vs historical.
|
|
86
|
+
const allNames = EntitySchemaFactory.names();
|
|
87
|
+
const historicalByBase = new Map();
|
|
88
|
+
const currentSchemas = new Map();
|
|
89
|
+
for (const name of allNames) {
|
|
90
|
+
const match = SchemaVersionService._VERSION_SUFFIX_RE.exec(name);
|
|
91
|
+
if (match) {
|
|
92
|
+
const baseName = match[1];
|
|
93
|
+
const version = Number.parseInt(match[2], 10);
|
|
94
|
+
let versions = historicalByBase.get(baseName);
|
|
95
|
+
if (!versions) {
|
|
96
|
+
versions = new Map();
|
|
97
|
+
historicalByBase.set(baseName, versions);
|
|
98
|
+
}
|
|
99
|
+
versions.set(version, EntitySchemaFactory.get(name));
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
currentSchemas.set(name, EntitySchemaFactory.get(name));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// 2. Read ALL stored version records, paging through the full table.
|
|
106
|
+
const storedVersions = new Map();
|
|
107
|
+
let cursor;
|
|
108
|
+
do {
|
|
109
|
+
const queryResult = await this._versionConnector.query(undefined, undefined, undefined, cursor);
|
|
110
|
+
for (const record of queryResult.entities ?? []) {
|
|
111
|
+
if (Is.object(record)) {
|
|
112
|
+
storedVersions.set(record.schemaName, record.version);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
cursor = queryResult.cursor;
|
|
116
|
+
} while (Is.stringValue(cursor));
|
|
117
|
+
// 3. Process SchemaVersion first so the version store itself is fully migrated
|
|
118
|
+
// before any version records are written for other schemas.
|
|
119
|
+
const schemaVersionName = "SchemaVersion";
|
|
120
|
+
const schemaVersionSchema = currentSchemas.get(schemaVersionName);
|
|
121
|
+
if (schemaVersionSchema) {
|
|
122
|
+
currentSchemas.delete(schemaVersionName);
|
|
123
|
+
await this.processSchema(schemaVersionName, schemaVersionSchema, storedVersions, historicalByBase.get(schemaVersionName), migrationOptions, nodeLoggingComponentType, logging);
|
|
124
|
+
}
|
|
125
|
+
// 4. Process all remaining schemas.
|
|
126
|
+
for (const [schemaName, schema] of currentSchemas) {
|
|
127
|
+
await this.processSchema(schemaName, schema, storedVersions, historicalByBase.get(schemaName), migrationOptions, nodeLoggingComponentType, logging);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Checks and applies any pending migration for a single entity schema.
|
|
132
|
+
* Extracted to avoid continue statements in the outer loop.
|
|
133
|
+
* @param schemaName The base schema name.
|
|
134
|
+
* @param schema The current schema definition.
|
|
135
|
+
* @param storedVersions The full map of stored version records.
|
|
136
|
+
* @param history The versioned-schema map for this schema (historicalByBase.get(schemaName)), or undefined if none exist.
|
|
137
|
+
* @param migrationOptions The migration options to pass through to MigrationHelper.
|
|
138
|
+
* @param loggingComponentType The optional component type to use for logging the migration progress.
|
|
139
|
+
* @param logging An optional logging component to pass through to MigrationHelper for migration progress logging.
|
|
140
|
+
* @internal
|
|
141
|
+
*/
|
|
142
|
+
async processSchema(schemaName, schema, storedVersions, history, migrationOptions, loggingComponentType, logging) {
|
|
143
|
+
const currentVersion = EntitySchemaHelper.getVersion(schema);
|
|
144
|
+
// Find the entity-storage connector whose schema type matches this schema name.
|
|
145
|
+
// For SchemaVersion itself, use the injected connector directly rather than re-discovering
|
|
146
|
+
// it through the factory, which could resolve a different instance than _versionConnector.
|
|
147
|
+
const connectorEntry = schemaName === "SchemaVersion"
|
|
148
|
+
? { connector: this._versionConnector, factoryKey: undefined }
|
|
149
|
+
: this.findConnector(schemaName);
|
|
150
|
+
if (!connectorEntry) {
|
|
151
|
+
// No connector registered for this schema — nothing to migrate.
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const { connector, factoryKey } = connectorEntry;
|
|
155
|
+
// Resolve the stored version, applying the backwards-compat baseline when no record exists.
|
|
156
|
+
const stored = storedVersions.get(schemaName);
|
|
157
|
+
let resolvedStoredVersion;
|
|
158
|
+
if (stored === undefined) {
|
|
159
|
+
// No version record: treat as v0 regardless of whether the table has data.
|
|
160
|
+
// On SQL connectors the table may have a stale column structure even when empty;
|
|
161
|
+
// running the chain over zero rows still calls finalizeMigration, which reconciles
|
|
162
|
+
// the table shape via a connector swap.
|
|
163
|
+
// Deployment precondition: any pre-existing data is genuinely at v0. A deployment
|
|
164
|
+
// that hand-applied a later schema before this service was introduced would be
|
|
165
|
+
// incorrectly replayed v0→…→current and should be seeded with an explicit record.
|
|
166
|
+
resolvedStoredVersion = 0;
|
|
167
|
+
await this.writeVersion(schemaName, 0);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
resolvedStoredVersion = stored;
|
|
171
|
+
}
|
|
172
|
+
// No-op: stored version already matches current.
|
|
173
|
+
if (resolvedStoredVersion === currentVersion) {
|
|
174
|
+
await logging?.log({
|
|
175
|
+
source: SchemaVersionService.CLASS_NAME,
|
|
176
|
+
level: "info",
|
|
177
|
+
message: "noMigrationRequired",
|
|
178
|
+
data: {
|
|
179
|
+
schemaName,
|
|
180
|
+
version: resolvedStoredVersion
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
await logging?.log({
|
|
186
|
+
source: SchemaVersionService.CLASS_NAME,
|
|
187
|
+
level: "info",
|
|
188
|
+
message: "migrationRequired",
|
|
189
|
+
data: {
|
|
190
|
+
schemaName,
|
|
191
|
+
from: currentVersion,
|
|
192
|
+
to: resolvedStoredVersion
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
// Downgrade — not supported.
|
|
196
|
+
if (resolvedStoredVersion > currentVersion) {
|
|
197
|
+
throw new GeneralError(SchemaVersionService.CLASS_NAME, "storedVersionNewer", {
|
|
198
|
+
schemaName,
|
|
199
|
+
stored: resolvedStoredVersion,
|
|
200
|
+
current: currentVersion
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
// Migration is needed. If the connector does not support it, throw immediately so
|
|
204
|
+
// the problem surfaces at boot rather than at runtime when writes hit the wrong table shape.
|
|
205
|
+
if (!("createTargetConnector" in connector)) {
|
|
206
|
+
throw new GeneralError(SchemaVersionService.CLASS_NAME, "connectorNotMigrationCapable", {
|
|
207
|
+
schemaName,
|
|
208
|
+
stored: resolvedStoredVersion,
|
|
209
|
+
current: currentVersion
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
const migrationConnector = connector;
|
|
213
|
+
// Upgrade — resolve and run the chain.
|
|
214
|
+
const steps = [];
|
|
215
|
+
for (let v = resolvedStoredVersion; v < currentVersion; v++) {
|
|
216
|
+
const fromSchema = history?.get(v);
|
|
217
|
+
if (!fromSchema) {
|
|
218
|
+
throw new GeneralError(SchemaVersionService.CLASS_NAME, "noMigrationStep", {
|
|
219
|
+
schemaName,
|
|
220
|
+
stored: resolvedStoredVersion,
|
|
221
|
+
current: currentVersion,
|
|
222
|
+
missingFromVersion: v,
|
|
223
|
+
missingToVersion: v + 1
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
const toSchema = v + 1 < currentVersion ? history?.get(v + 1) : schema;
|
|
227
|
+
if (!toSchema) {
|
|
228
|
+
throw new GeneralError(SchemaVersionService.CLASS_NAME, "noMigrationStepTarget", {
|
|
229
|
+
schemaName,
|
|
230
|
+
stored: resolvedStoredVersion,
|
|
231
|
+
current: currentVersion,
|
|
232
|
+
missingFromVersion: v,
|
|
233
|
+
missingToVersion: v + 1
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
const overrideKey = `${schemaName}_${v}_${v + 1}`;
|
|
237
|
+
const override = SchemaMigrationFactory.getIfExists(overrideKey);
|
|
238
|
+
steps.push({
|
|
239
|
+
fromProperties: fromSchema.properties ?? [],
|
|
240
|
+
toProperties: toSchema.properties ?? [],
|
|
241
|
+
renames: override?.renames,
|
|
242
|
+
transformEntityProperty: override?.transformEntityProperty
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
const { finalConnector } = await MigrationHelper.migrateWithChain(migrationConnector, schemaName, steps, migrationOptions, loggingComponentType);
|
|
246
|
+
// Some connectors (e.g. in-memory) return a brand-new object from finalizeMigration
|
|
247
|
+
// rather than mutating the source in place. Re-register the factory entry so that any
|
|
248
|
+
// subsequent EntityStorageConnectorFactory.get() call returns the migrated instance.
|
|
249
|
+
if (finalConnector !== connector) {
|
|
250
|
+
if (factoryKey) {
|
|
251
|
+
EntityStorageConnectorFactory.register(factoryKey, () => finalConnector);
|
|
252
|
+
}
|
|
253
|
+
// For SchemaVersion keep _versionConnector in sync so writeVersion below uses
|
|
254
|
+
// the migrated instance.
|
|
255
|
+
if (schemaName === "SchemaVersion") {
|
|
256
|
+
this._versionConnector = finalConnector;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// Advance the stored version only after finalizeMigration has succeeded.
|
|
260
|
+
// See crash-window note in the class comment.
|
|
261
|
+
await this.writeVersion(schemaName, currentVersion);
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Upserts a SchemaVersion record for the given schema name.
|
|
265
|
+
* @param schemaName The schema type name.
|
|
266
|
+
* @param version The version to record.
|
|
267
|
+
* @internal
|
|
268
|
+
*/
|
|
269
|
+
async writeVersion(schemaName, version) {
|
|
270
|
+
await this._versionConnector.set({
|
|
271
|
+
schemaName,
|
|
272
|
+
version,
|
|
273
|
+
updatedAt: new Date().toISOString()
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Searches EntityStorageConnectorFactory for the connector whose registered schema type
|
|
278
|
+
* matches the given schema name.
|
|
279
|
+
* @param schemaName The entity type name to look up.
|
|
280
|
+
* @returns The matching connector and its factory key, or undefined if none is registered.
|
|
281
|
+
* @internal
|
|
282
|
+
*/
|
|
283
|
+
findConnector(schemaName) {
|
|
284
|
+
for (const name of EntityStorageConnectorFactory.names()) {
|
|
285
|
+
try {
|
|
286
|
+
const connector = EntityStorageConnectorFactory.get(name);
|
|
287
|
+
if (connector.getSchema?.().type === schemaName) {
|
|
288
|
+
return { connector, factoryKey: name };
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
// Connector not yet created or registration issue — skip.
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return undefined;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Logs migration progress using the provided logging component, if available.
|
|
299
|
+
* @param logging The logging component to use for logging progress, if available.
|
|
300
|
+
* @param progressItem The progress item being updated.
|
|
301
|
+
* @param itemTotal The total number of items to process for this progress item.
|
|
302
|
+
* @param itemIndex The index of the current item being processed for this progress item.
|
|
303
|
+
* @internal
|
|
304
|
+
*/
|
|
305
|
+
async logProgress(logging, progressItem, itemTotal, itemIndex) {
|
|
306
|
+
if (progressItem === "partitionStart") {
|
|
307
|
+
await logging?.log({
|
|
308
|
+
source: SchemaVersionService.CLASS_NAME,
|
|
309
|
+
level: "info",
|
|
310
|
+
message: "partitionStart",
|
|
311
|
+
data: { progressItem, itemTotal, itemIndex }
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
else if (progressItem === "partitionProgress") {
|
|
315
|
+
await logging?.log({
|
|
316
|
+
source: SchemaVersionService.CLASS_NAME,
|
|
317
|
+
level: "info",
|
|
318
|
+
message: "partitionProgress",
|
|
319
|
+
data: { progressItem, itemTotal, itemIndex }
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
else if (progressItem === "partitionEnd") {
|
|
323
|
+
await logging?.log({
|
|
324
|
+
source: SchemaVersionService.CLASS_NAME,
|
|
325
|
+
level: "info",
|
|
326
|
+
message: "partitionEnd",
|
|
327
|
+
data: { progressItem, itemTotal, itemIndex }
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
else if (progressItem === "partitionItemsStart") {
|
|
331
|
+
await logging?.log({
|
|
332
|
+
source: SchemaVersionService.CLASS_NAME,
|
|
333
|
+
level: "info",
|
|
334
|
+
message: "partitionItemsStart",
|
|
335
|
+
data: { progressItem, itemTotal, itemIndex }
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
else if (progressItem === "partitionItemsProgress") {
|
|
339
|
+
await logging?.log({
|
|
340
|
+
source: SchemaVersionService.CLASS_NAME,
|
|
341
|
+
level: "info",
|
|
342
|
+
message: "partitionItemsProgress",
|
|
343
|
+
data: { progressItem, itemTotal, itemIndex }
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
else if (progressItem === "partitionItemsEnd") {
|
|
347
|
+
await logging?.log({
|
|
348
|
+
source: SchemaVersionService.CLASS_NAME,
|
|
349
|
+
level: "info",
|
|
350
|
+
message: "partitionItemsEnd",
|
|
351
|
+
data: { progressItem, itemTotal, itemIndex }
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
//# sourceMappingURL=schemaVersionService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemaVersionService.js","sourceRoot":"","sources":["../../src/schemaVersionService.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { ComponentFactory, GeneralError, Is, type IComponent } from \"@twin.org/core\";\nimport { EntitySchemaFactory, EntitySchemaHelper, type IEntitySchema } from \"@twin.org/entity\";\nimport {\n\tEntityStorageConnectorFactory,\n\ttype IEntityStorageConnector,\n\ttype IEntityStorageMigrationConnector,\n\ttype IMigrationOptions,\n\ttype IResolvedMigrationStep,\n\tMigrationHelper,\n\tSchemaMigrationFactory\n} from \"@twin.org/entity-storage-models\";\nimport type { ILoggingComponent } from \"@twin.org/logging-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { SchemaVersion } from \"./entities/schemaVersion.js\";\nimport type { ISchemaVersionServiceConfig } from \"./models/ISchemaVersionServiceConfig.js\";\nimport type { ISchemaVersionServiceConstructorOptions } from \"./models/ISchemaVersionServiceConstructorOptions.js\";\n\n/**\n * Service that checks and applies entity schema migrations at every node start-up.\n *\n * This service must be the first entry in coreTypeInitialisers.json. The engine iterates that\n * array in order to determine start sequence — there is no engine-level priority mechanism, so\n * registration position is the only guarantee that start() runs before any other service.\n * By the time start() is called, all component bootstraps have completed (every table already\n * exists) and EntitySchemaFactory / EntityStorageConnectorFactory are fully populated with every\n * registered schema and connector.\n *\n * Migration mechanics: old schema versions are registered in EntitySchemaFactory by naming\n * convention — current schema = \"MyEntity\", first history = \"MyEntityV0\", second = \"MyEntityV1\".\n * The service groups schemas by base name (strips the trailing V number suffix) and resolves the\n * migration chain automatically by diffing consecutive versioned schemas. For steps that require\n * property renames or a custom transform hook, register an optional ISchemaMigration entry in\n * SchemaMigrationFactory under the key \"Base_from_to\" (e.g. \"MyEntity_0_1\").\n *\n * Crash-window note: finalizeMigration and the subsequent version-record write are two\n * separate operations. If the process dies between them the next boot re-runs the chain\n * over already-migrated data. applyEntityTransform is NOT idempotent for structural changes\n * (newly-added optional fields would be dropped on re-run). A transaction spanning both\n * writes is a precondition for production; track this in the concurrency follow-up.\n */\nexport class SchemaVersionService implements IComponent {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<SchemaVersionService>();\n\n\t/**\n\t * Regex to detect a versioned schema name and extract the base name and version number.\n\t * Matches names like \"MyEntityV0\", \"AuditableItemGraphV2\", etc.\n\t * @internal\n\t */\n\tprivate static readonly _VERSION_SUFFIX_RE = /^(.+)V(\\d+)$/;\n\n\t/**\n\t * The connector used to read and write SchemaVersion records.\n\t * Not readonly because finalizeMigration may return a replacement connector object.\n\t * @internal\n\t */\n\tprivate _versionConnector: IEntityStorageConnector<SchemaVersion>;\n\n\t/**\n\t * Optional config passed through constructor options.\n\t * @internal\n\t */\n\tprivate readonly _config?: ISchemaVersionServiceConfig;\n\n\t/**\n\t * Create a new SchemaVersionService.\n\t * @param options Optional constructor options.\n\t */\n\tconstructor(options?: ISchemaVersionServiceConstructorOptions) {\n\t\tthis._versionConnector = EntityStorageConnectorFactory.get(\n\t\t\toptions?.schemaVersionStorageType ?? \"schema-version\"\n\t\t);\n\t\tthis._config = options?.config;\n\t}\n\n\t/**\n\t * Returns the class name.\n\t * @returns The class name.\n\t */\n\tpublic className(): string {\n\t\treturn SchemaVersionService.CLASS_NAME;\n\t}\n\n\t/**\n\t * Reads all registered entity schemas, groups versioned schemas by base name, reads the\n\t * full schemaVersion table in one pass, then orchestrates chain migrations for any schema\n\t * whose stored version is behind the current version declared in EntitySchemaFactory.\n\t * SchemaVersion itself is processed first so the version store is migrated before any\n\t * version records are written for other schemas.\n\t *\n\t * Runs after all component bootstraps, so every managed table already exists.\n\t * @param nodeLoggingComponentType An optional logging component type.\n\t */\n\tpublic async start(nodeLoggingComponentType?: string): Promise<void> {\n\t\tconst logging = ComponentFactory.getIfExists<ILoggingComponent>(nodeLoggingComponentType);\n\n\t\tconst migrationOptions: IMigrationOptions = {\n\t\t\tbatchSize: this._config?.batchSize,\n\t\t\tonProgress: async (progressItem, itemTotal, itemIndex) => {\n\t\t\t\tawait this.logProgress(logging, progressItem, itemTotal, itemIndex);\n\t\t\t}\n\t\t};\n\n\t\t// 1. Collect all registered schema names and partition into current vs historical.\n\t\tconst allNames = EntitySchemaFactory.names();\n\n\t\tconst historicalByBase = new Map<string, Map<number, IEntitySchema>>();\n\t\tconst currentSchemas = new Map<string, IEntitySchema>();\n\n\t\tfor (const name of allNames) {\n\t\t\tconst match = SchemaVersionService._VERSION_SUFFIX_RE.exec(name);\n\t\t\tif (match) {\n\t\t\t\tconst baseName = match[1];\n\t\t\t\tconst version = Number.parseInt(match[2], 10);\n\t\t\t\tlet versions = historicalByBase.get(baseName);\n\t\t\t\tif (!versions) {\n\t\t\t\t\tversions = new Map();\n\t\t\t\t\thistoricalByBase.set(baseName, versions);\n\t\t\t\t}\n\t\t\t\tversions.set(version, EntitySchemaFactory.get(name));\n\t\t\t} else {\n\t\t\t\tcurrentSchemas.set(name, EntitySchemaFactory.get(name));\n\t\t\t}\n\t\t}\n\n\t\t// 2. Read ALL stored version records, paging through the full table.\n\t\tconst storedVersions = new Map<string, number>();\n\t\tlet cursor: string | undefined;\n\t\tdo {\n\t\t\tconst queryResult = await this._versionConnector.query(\n\t\t\t\tundefined,\n\t\t\t\tundefined,\n\t\t\t\tundefined,\n\t\t\t\tcursor\n\t\t\t);\n\t\t\tfor (const record of queryResult.entities ?? []) {\n\t\t\t\tif (Is.object<SchemaVersion>(record)) {\n\t\t\t\t\tstoredVersions.set(record.schemaName, record.version);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcursor = queryResult.cursor;\n\t\t} while (Is.stringValue(cursor));\n\n\t\t// 3. Process SchemaVersion first so the version store itself is fully migrated\n\t\t// before any version records are written for other schemas.\n\t\tconst schemaVersionName = nameof(SchemaVersion);\n\t\tconst schemaVersionSchema = currentSchemas.get(schemaVersionName);\n\t\tif (schemaVersionSchema) {\n\t\t\tcurrentSchemas.delete(schemaVersionName);\n\t\t\tawait this.processSchema(\n\t\t\t\tschemaVersionName,\n\t\t\t\tschemaVersionSchema,\n\t\t\t\tstoredVersions,\n\t\t\t\thistoricalByBase.get(schemaVersionName),\n\t\t\t\tmigrationOptions,\n\t\t\t\tnodeLoggingComponentType,\n\t\t\t\tlogging\n\t\t\t);\n\t\t}\n\n\t\t// 4. Process all remaining schemas.\n\t\tfor (const [schemaName, schema] of currentSchemas) {\n\t\t\tawait this.processSchema(\n\t\t\t\tschemaName,\n\t\t\t\tschema,\n\t\t\t\tstoredVersions,\n\t\t\t\thistoricalByBase.get(schemaName),\n\t\t\t\tmigrationOptions,\n\t\t\t\tnodeLoggingComponentType,\n\t\t\t\tlogging\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Checks and applies any pending migration for a single entity schema.\n\t * Extracted to avoid continue statements in the outer loop.\n\t * @param schemaName The base schema name.\n\t * @param schema The current schema definition.\n\t * @param storedVersions The full map of stored version records.\n\t * @param history The versioned-schema map for this schema (historicalByBase.get(schemaName)), or undefined if none exist.\n\t * @param migrationOptions The migration options to pass through to MigrationHelper.\n\t * @param loggingComponentType The optional component type to use for logging the migration progress.\n\t * @param logging An optional logging component to pass through to MigrationHelper for migration progress logging.\n\t * @internal\n\t */\n\tprivate async processSchema(\n\t\tschemaName: string,\n\t\tschema: IEntitySchema,\n\t\tstoredVersions: Map<string, number>,\n\t\thistory: Map<number, IEntitySchema> | undefined,\n\t\tmigrationOptions: IMigrationOptions,\n\t\tloggingComponentType: string | undefined,\n\t\tlogging: ILoggingComponent | undefined\n\t): Promise<void> {\n\t\tconst currentVersion = EntitySchemaHelper.getVersion(schema);\n\n\t\t// Find the entity-storage connector whose schema type matches this schema name.\n\t\t// For SchemaVersion itself, use the injected connector directly rather than re-discovering\n\t\t// it through the factory, which could resolve a different instance than _versionConnector.\n\t\tconst connectorEntry =\n\t\t\tschemaName === nameof(SchemaVersion)\n\t\t\t\t? { connector: this._versionConnector as IEntityStorageConnector, factoryKey: undefined }\n\t\t\t\t: this.findConnector(schemaName);\n\t\tif (!connectorEntry) {\n\t\t\t// No connector registered for this schema — nothing to migrate.\n\t\t\treturn;\n\t\t}\n\t\tconst { connector, factoryKey } = connectorEntry;\n\n\t\t// Resolve the stored version, applying the backwards-compat baseline when no record exists.\n\t\tconst stored = storedVersions.get(schemaName);\n\t\tlet resolvedStoredVersion: number;\n\n\t\tif (stored === undefined) {\n\t\t\t// No version record: treat as v0 regardless of whether the table has data.\n\t\t\t// On SQL connectors the table may have a stale column structure even when empty;\n\t\t\t// running the chain over zero rows still calls finalizeMigration, which reconciles\n\t\t\t// the table shape via a connector swap.\n\t\t\t// Deployment precondition: any pre-existing data is genuinely at v0. A deployment\n\t\t\t// that hand-applied a later schema before this service was introduced would be\n\t\t\t// incorrectly replayed v0→…→current and should be seeded with an explicit record.\n\t\t\tresolvedStoredVersion = 0;\n\t\t\tawait this.writeVersion(schemaName, 0);\n\t\t} else {\n\t\t\tresolvedStoredVersion = stored;\n\t\t}\n\n\t\t// No-op: stored version already matches current.\n\t\tif (resolvedStoredVersion === currentVersion) {\n\t\t\tawait logging?.log({\n\t\t\t\tsource: SchemaVersionService.CLASS_NAME,\n\t\t\t\tlevel: \"info\",\n\t\t\t\tmessage: \"noMigrationRequired\",\n\t\t\t\tdata: {\n\t\t\t\t\tschemaName,\n\t\t\t\t\tversion: resolvedStoredVersion\n\t\t\t\t}\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tawait logging?.log({\n\t\t\tsource: SchemaVersionService.CLASS_NAME,\n\t\t\tlevel: \"info\",\n\t\t\tmessage: \"migrationRequired\",\n\t\t\tdata: {\n\t\t\t\tschemaName,\n\t\t\t\tfrom: currentVersion,\n\t\t\t\tto: resolvedStoredVersion\n\t\t\t}\n\t\t});\n\n\t\t// Downgrade — not supported.\n\t\tif (resolvedStoredVersion > currentVersion) {\n\t\t\tthrow new GeneralError(SchemaVersionService.CLASS_NAME, \"storedVersionNewer\", {\n\t\t\t\tschemaName,\n\t\t\t\tstored: resolvedStoredVersion,\n\t\t\t\tcurrent: currentVersion\n\t\t\t});\n\t\t}\n\n\t\t// Migration is needed. If the connector does not support it, throw immediately so\n\t\t// the problem surfaces at boot rather than at runtime when writes hit the wrong table shape.\n\t\tif (!(\"createTargetConnector\" in connector)) {\n\t\t\tthrow new GeneralError(SchemaVersionService.CLASS_NAME, \"connectorNotMigrationCapable\", {\n\t\t\t\tschemaName,\n\t\t\t\tstored: resolvedStoredVersion,\n\t\t\t\tcurrent: currentVersion\n\t\t\t});\n\t\t}\n\n\t\tconst migrationConnector = connector as IEntityStorageMigrationConnector;\n\n\t\t// Upgrade — resolve and run the chain.\n\t\tconst steps: IResolvedMigrationStep[] = [];\n\n\t\tfor (let v = resolvedStoredVersion; v < currentVersion; v++) {\n\t\t\tconst fromSchema = history?.get(v);\n\t\t\tif (!fromSchema) {\n\t\t\t\tthrow new GeneralError(SchemaVersionService.CLASS_NAME, \"noMigrationStep\", {\n\t\t\t\t\tschemaName,\n\t\t\t\t\tstored: resolvedStoredVersion,\n\t\t\t\t\tcurrent: currentVersion,\n\t\t\t\t\tmissingFromVersion: v,\n\t\t\t\t\tmissingToVersion: v + 1\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst toSchema = v + 1 < currentVersion ? history?.get(v + 1) : schema;\n\t\t\tif (!toSchema) {\n\t\t\t\tthrow new GeneralError(SchemaVersionService.CLASS_NAME, \"noMigrationStepTarget\", {\n\t\t\t\t\tschemaName,\n\t\t\t\t\tstored: resolvedStoredVersion,\n\t\t\t\t\tcurrent: currentVersion,\n\t\t\t\t\tmissingFromVersion: v,\n\t\t\t\t\tmissingToVersion: v + 1\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst overrideKey = `${schemaName}_${v}_${v + 1}`;\n\t\t\tconst override = SchemaMigrationFactory.getIfExists(overrideKey);\n\n\t\t\tsteps.push({\n\t\t\t\tfromProperties: fromSchema.properties ?? [],\n\t\t\t\ttoProperties: toSchema.properties ?? [],\n\t\t\t\trenames: override?.renames,\n\t\t\t\ttransformEntityProperty: override?.transformEntityProperty\n\t\t\t});\n\t\t}\n\n\t\tconst { finalConnector } = await MigrationHelper.migrateWithChain(\n\t\t\tmigrationConnector,\n\t\t\tschemaName,\n\t\t\tsteps,\n\t\t\tmigrationOptions,\n\t\t\tloggingComponentType\n\t\t);\n\n\t\t// Some connectors (e.g. in-memory) return a brand-new object from finalizeMigration\n\t\t// rather than mutating the source in place. Re-register the factory entry so that any\n\t\t// subsequent EntityStorageConnectorFactory.get() call returns the migrated instance.\n\t\tif (finalConnector !== connector) {\n\t\t\tif (factoryKey) {\n\t\t\t\tEntityStorageConnectorFactory.register(factoryKey, () => finalConnector);\n\t\t\t}\n\t\t\t// For SchemaVersion keep _versionConnector in sync so writeVersion below uses\n\t\t\t// the migrated instance.\n\t\t\tif (schemaName === nameof(SchemaVersion)) {\n\t\t\t\tthis._versionConnector = finalConnector as IEntityStorageConnector<SchemaVersion>;\n\t\t\t}\n\t\t}\n\n\t\t// Advance the stored version only after finalizeMigration has succeeded.\n\t\t// See crash-window note in the class comment.\n\t\tawait this.writeVersion(schemaName, currentVersion);\n\t}\n\n\t/**\n\t * Upserts a SchemaVersion record for the given schema name.\n\t * @param schemaName The schema type name.\n\t * @param version The version to record.\n\t * @internal\n\t */\n\tprivate async writeVersion(schemaName: string, version: number): Promise<void> {\n\t\tawait this._versionConnector.set({\n\t\t\tschemaName,\n\t\t\tversion,\n\t\t\tupdatedAt: new Date().toISOString()\n\t\t});\n\t}\n\n\t/**\n\t * Searches EntityStorageConnectorFactory for the connector whose registered schema type\n\t * matches the given schema name.\n\t * @param schemaName The entity type name to look up.\n\t * @returns The matching connector and its factory key, or undefined if none is registered.\n\t * @internal\n\t */\n\tprivate findConnector(\n\t\tschemaName: string\n\t): { connector: IEntityStorageConnector; factoryKey: string } | undefined {\n\t\tfor (const name of EntityStorageConnectorFactory.names()) {\n\t\t\ttry {\n\t\t\t\tconst connector = EntityStorageConnectorFactory.get(name);\n\t\t\t\tif (connector.getSchema?.().type === schemaName) {\n\t\t\t\t\treturn { connector, factoryKey: name };\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Connector not yet created or registration issue — skip.\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Logs migration progress using the provided logging component, if available.\n\t * @param logging The logging component to use for logging progress, if available.\n\t * @param progressItem The progress item being updated.\n\t * @param itemTotal The total number of items to process for this progress item.\n\t * @param itemIndex The index of the current item being processed for this progress item.\n\t * @internal\n\t */\n\tprivate async logProgress(\n\t\tlogging: ILoggingComponent | undefined,\n\t\tprogressItem: string,\n\t\titemTotal: number,\n\t\titemIndex: number\n\t): Promise<void> {\n\t\tif (progressItem === \"partitionStart\") {\n\t\t\tawait logging?.log({\n\t\t\t\tsource: SchemaVersionService.CLASS_NAME,\n\t\t\t\tlevel: \"info\",\n\t\t\t\tmessage: \"partitionStart\",\n\t\t\t\tdata: { progressItem, itemTotal, itemIndex }\n\t\t\t});\n\t\t} else if (progressItem === \"partitionProgress\") {\n\t\t\tawait logging?.log({\n\t\t\t\tsource: SchemaVersionService.CLASS_NAME,\n\t\t\t\tlevel: \"info\",\n\t\t\t\tmessage: \"partitionProgress\",\n\t\t\t\tdata: { progressItem, itemTotal, itemIndex }\n\t\t\t});\n\t\t} else if (progressItem === \"partitionEnd\") {\n\t\t\tawait logging?.log({\n\t\t\t\tsource: SchemaVersionService.CLASS_NAME,\n\t\t\t\tlevel: \"info\",\n\t\t\t\tmessage: \"partitionEnd\",\n\t\t\t\tdata: { progressItem, itemTotal, itemIndex }\n\t\t\t});\n\t\t} else if (progressItem === \"partitionItemsStart\") {\n\t\t\tawait logging?.log({\n\t\t\t\tsource: SchemaVersionService.CLASS_NAME,\n\t\t\t\tlevel: \"info\",\n\t\t\t\tmessage: \"partitionItemsStart\",\n\t\t\t\tdata: { progressItem, itemTotal, itemIndex }\n\t\t\t});\n\t\t} else if (progressItem === \"partitionItemsProgress\") {\n\t\t\tawait logging?.log({\n\t\t\t\tsource: SchemaVersionService.CLASS_NAME,\n\t\t\t\tlevel: \"info\",\n\t\t\t\tmessage: \"partitionItemsProgress\",\n\t\t\t\tdata: { progressItem, itemTotal, itemIndex }\n\t\t\t});\n\t\t} else if (progressItem === \"partitionItemsEnd\") {\n\t\t\tawait logging?.log({\n\t\t\t\tsource: SchemaVersionService.CLASS_NAME,\n\t\t\t\tlevel: \"info\",\n\t\t\t\tmessage: \"partitionItemsEnd\",\n\t\t\t\tdata: { progressItem, itemTotal, itemIndex }\n\t\t\t});\n\t\t}\n\t}\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks the currently applied schema version for each managed entity schema.
|
|
3
|
+
* One record per schema name. Written once on first boot, then updated after
|
|
4
|
+
* each successful migration.
|
|
5
|
+
*/
|
|
6
|
+
export declare class SchemaVersion {
|
|
7
|
+
/**
|
|
8
|
+
* The entity schema type name — primary key.
|
|
9
|
+
*/
|
|
10
|
+
schemaName: string;
|
|
11
|
+
/**
|
|
12
|
+
* The currently deployed version of this schema.
|
|
13
|
+
*/
|
|
14
|
+
version: number;
|
|
15
|
+
/**
|
|
16
|
+
* ISO 8601 timestamp of the last version write.
|
|
17
|
+
*/
|
|
18
|
+
updatedAt: string;
|
|
19
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type IHttpRequestContext, type INoContentResponse, type IRestRoute, type ITag } from "@twin.org/api-models";
|
|
2
|
-
import type { IEntityStorageGetRequest, IEntityStorageGetResponse, IEntityStorageListRequest, IEntityStorageListResponse, IEntityStorageRemoveRequest, IEntityStorageSetRequest } from "@twin.org/entity-storage-models";
|
|
2
|
+
import type { IEntityStorageCountRequest, IEntityStorageCountResponse, IEntityStorageEmptyRequest, IEntityStorageGetRequest, IEntityStorageGetResponse, IEntityStorageListRequest, IEntityStorageListResponse, IEntityStorageRemoveBatchRequest, IEntityStorageRemoveRequest, IEntityStorageSetBatchRequest, IEntityStorageSetRequest } from "@twin.org/entity-storage-models";
|
|
3
3
|
import type { IEntityStorageRoutesExamples } from "./models/IEntityStorageRoutesExamples.js";
|
|
4
4
|
/**
|
|
5
5
|
* The tag to associate with the routes.
|
|
@@ -28,6 +28,22 @@ export declare function generateRestRoutesEntityStorage(baseRouteName: string, c
|
|
|
28
28
|
* @returns The response object with additional http response properties.
|
|
29
29
|
*/
|
|
30
30
|
export declare function entityStorageSet(httpRequestContext: IHttpRequestContext, componentName: string, request: IEntityStorageSetRequest): Promise<INoContentResponse>;
|
|
31
|
+
/**
|
|
32
|
+
* Set multiple entries in entity storage.
|
|
33
|
+
* @param httpRequestContext The request context for the API.
|
|
34
|
+
* @param componentName The name of the component to use in the routes.
|
|
35
|
+
* @param request The request.
|
|
36
|
+
* @returns The response object with additional http response properties.
|
|
37
|
+
*/
|
|
38
|
+
export declare function entityStorageSetBatch(httpRequestContext: IHttpRequestContext, componentName: string, request: IEntityStorageSetBatchRequest): Promise<INoContentResponse>;
|
|
39
|
+
/**
|
|
40
|
+
* Remove all entries from entity storage.
|
|
41
|
+
* @param httpRequestContext The request context for the API.
|
|
42
|
+
* @param componentName The name of the component to use in the routes.
|
|
43
|
+
* @param request The request.
|
|
44
|
+
* @returns The response object with additional http response properties.
|
|
45
|
+
*/
|
|
46
|
+
export declare function entityStorageEmpty(httpRequestContext: IHttpRequestContext, componentName: string, request: IEntityStorageEmptyRequest): Promise<INoContentResponse>;
|
|
31
47
|
/**
|
|
32
48
|
* Get the entry from entity storage.
|
|
33
49
|
* @param httpRequestContext The request context for the API.
|
|
@@ -52,3 +68,19 @@ export declare function entityStorageRemove(httpRequestContext: IHttpRequestCont
|
|
|
52
68
|
* @returns The response object with additional http response properties.
|
|
53
69
|
*/
|
|
54
70
|
export declare function entityStorageList(httpRequestContext: IHttpRequestContext, componentName: string, request: IEntityStorageListRequest): Promise<IEntityStorageListResponse>;
|
|
71
|
+
/**
|
|
72
|
+
* Count the entries in entity storage.
|
|
73
|
+
* @param httpRequestContext The request context for the API.
|
|
74
|
+
* @param componentName The name of the component to use in the routes.
|
|
75
|
+
* @param request The request.
|
|
76
|
+
* @returns The response object with additional http response properties.
|
|
77
|
+
*/
|
|
78
|
+
export declare function entityStorageCount(httpRequestContext: IHttpRequestContext, componentName: string, request: IEntityStorageCountRequest): Promise<IEntityStorageCountResponse>;
|
|
79
|
+
/**
|
|
80
|
+
* Remove multiple entries from entity storage by id.
|
|
81
|
+
* @param httpRequestContext The request context for the API.
|
|
82
|
+
* @param componentName The name of the component to use in the routes.
|
|
83
|
+
* @param request The request.
|
|
84
|
+
* @returns The response object with additional http response properties.
|
|
85
|
+
*/
|
|
86
|
+
export declare function entityStorageRemoveBatch(httpRequestContext: IHttpRequestContext, componentName: string, request: IEntityStorageRemoveBatchRequest): Promise<INoContentResponse>;
|
|
@@ -22,22 +22,57 @@ export declare class EntityStorageService<T = any> implements IEntityStorageComp
|
|
|
22
22
|
/**
|
|
23
23
|
* Set an entity.
|
|
24
24
|
* @param entity The entity to set.
|
|
25
|
+
* @param conditions The optional conditions to match for the entities.
|
|
25
26
|
* @returns The id of the entity.
|
|
26
27
|
*/
|
|
27
|
-
set(entity: T
|
|
28
|
+
set(entity: T, conditions?: {
|
|
29
|
+
property: keyof T;
|
|
30
|
+
value: unknown;
|
|
31
|
+
}[]): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Set multiple entities in a batch.
|
|
34
|
+
* @param entities The entities to set.
|
|
35
|
+
* @returns Nothing.
|
|
36
|
+
*/
|
|
37
|
+
setBatch(entities: T[]): Promise<void>;
|
|
28
38
|
/**
|
|
29
39
|
* Get an entity.
|
|
30
40
|
* @param id The id of the entity to get, or the index value if secondaryIndex is set.
|
|
31
41
|
* @param secondaryIndex Get the item using a secondary index.
|
|
42
|
+
* @param conditions The optional conditions to match for the entities.
|
|
32
43
|
* @returns The object if it can be found or undefined.
|
|
33
44
|
*/
|
|
34
|
-
get(id: string, secondaryIndex?: keyof T
|
|
45
|
+
get(id: string, secondaryIndex?: keyof T, conditions?: {
|
|
46
|
+
property: keyof T;
|
|
47
|
+
value: unknown;
|
|
48
|
+
}[]): Promise<T | undefined>;
|
|
35
49
|
/**
|
|
36
50
|
* Remove the entity.
|
|
37
51
|
* @param id The id of the entity to remove.
|
|
52
|
+
* @param conditions The optional conditions to match for the entities.
|
|
53
|
+
* @returns Nothing.
|
|
54
|
+
*/
|
|
55
|
+
remove(id: string, conditions?: {
|
|
56
|
+
property: keyof T;
|
|
57
|
+
value: unknown;
|
|
58
|
+
}[]): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Remove multiple entities by id.
|
|
61
|
+
* @param ids The ids of the entities to remove.
|
|
38
62
|
* @returns Nothing.
|
|
39
63
|
*/
|
|
40
|
-
|
|
64
|
+
removeBatch(ids: string[]): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Remove all entities from the storage.
|
|
67
|
+
* @returns Nothing.
|
|
68
|
+
*/
|
|
69
|
+
empty(): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Count all the entities which match the conditions.
|
|
72
|
+
* @param conditions The optional conditions to match for the entities.
|
|
73
|
+
* @returns The total count of entities in the storage.
|
|
74
|
+
*/
|
|
75
|
+
count(conditions?: EntityCondition<T>): Promise<number>;
|
|
41
76
|
/**
|
|
42
77
|
* Query all the entities which match the conditions.
|
|
43
78
|
* @param conditions The conditions to match for the entities.
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
export * from "./entities/schemaVersion.js";
|
|
1
2
|
export * from "./entityStorageRoutes.js";
|
|
2
3
|
export * from "./entityStorageService.js";
|
|
3
|
-
export * from "./models/IEntityStorageServiceConfig.js";
|
|
4
4
|
export * from "./models/IEntityStorageRoutesExamples.js";
|
|
5
|
+
export * from "./models/IEntityStorageServiceConfig.js";
|
|
5
6
|
export * from "./models/IEntityStorageServiceConstructorOptions.js";
|
|
7
|
+
export * from "./models/ISchemaVersionServiceConfig.js";
|
|
8
|
+
export * from "./models/ISchemaVersionServiceConstructorOptions.js";
|
|
6
9
|
export * from "./restEntryPoints.js";
|
|
10
|
+
export * from "./schema.js";
|
|
11
|
+
export * from "./schemaVersionService.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IRestRouteRequestExample, IRestRouteResponseExample } from "@twin.org/api-models";
|
|
2
|
-
import type { IEntityStorageGetRequest, IEntityStorageGetResponse, IEntityStorageListRequest, IEntityStorageListResponse, IEntityStorageRemoveRequest, IEntityStorageSetRequest } from "@twin.org/entity-storage-models";
|
|
2
|
+
import type { IEntityStorageCountRequest, IEntityStorageCountResponse, IEntityStorageGetRequest, IEntityStorageGetResponse, IEntityStorageListRequest, IEntityStorageListResponse, IEntityStorageRemoveRequest, IEntityStorageSetRequest } from "@twin.org/entity-storage-models";
|
|
3
3
|
/**
|
|
4
4
|
* Examples for the entity storage routes.
|
|
5
5
|
*/
|
|
@@ -30,4 +30,11 @@ export interface IEntityStorageRoutesExamples {
|
|
|
30
30
|
requestExamples: IRestRouteRequestExample<IEntityStorageListRequest>[];
|
|
31
31
|
responseExamples: IRestRouteResponseExample<IEntityStorageListResponse>[];
|
|
32
32
|
};
|
|
33
|
+
/**
|
|
34
|
+
* Examples for the count route.
|
|
35
|
+
*/
|
|
36
|
+
count?: {
|
|
37
|
+
requestExamples: IRestRouteRequestExample<IEntityStorageCountRequest>[];
|
|
38
|
+
responseExamples: IRestRouteResponseExample<IEntityStorageCountResponse>[];
|
|
39
|
+
};
|
|
33
40
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ISchemaVersionServiceConfig } from "./ISchemaVersionServiceConfig.js";
|
|
2
|
+
/**
|
|
3
|
+
* Constructor options for SchemaVersionService.
|
|
4
|
+
*/
|
|
5
|
+
export interface ISchemaVersionServiceConstructorOptions {
|
|
6
|
+
/**
|
|
7
|
+
* The version storage type.
|
|
8
|
+
* @default schema-version
|
|
9
|
+
*/
|
|
10
|
+
schemaVersionStorageType?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Optional config.
|
|
13
|
+
*/
|
|
14
|
+
config?: ISchemaVersionServiceConfig;
|
|
15
|
+
}
|