velocious 1.0.489 → 1.0.492
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/build/application.js +3 -0
- package/build/cli/commands/db/tenants/create.js +7 -5
- package/build/cli/commands/db/tenants/drop.js +6 -6
- package/build/configuration-types.js +8 -0
- package/build/configuration.js +29 -0
- package/build/database/query/index.js +30 -0
- package/build/database/query/model-class-query.js +17 -0
- package/build/database/record/index.js +16 -0
- package/build/src/application.d.ts.map +1 -1
- package/build/src/application.js +3 -1
- package/build/src/cli/commands/db/tenants/create.d.ts.map +1 -1
- package/build/src/cli/commands/db/tenants/create.js +8 -5
- package/build/src/cli/commands/db/tenants/drop.d.ts +1 -1
- package/build/src/cli/commands/db/tenants/drop.d.ts.map +1 -1
- package/build/src/cli/commands/db/tenants/drop.js +7 -6
- package/build/src/configuration-types.d.ts +24 -0
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +8 -1
- package/build/src/configuration.d.ts +6 -0
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +26 -1
- package/build/src/database/query/index.d.ts +25 -0
- package/build/src/database/query/index.d.ts.map +1 -1
- package/build/src/database/query/index.js +26 -1
- package/build/src/database/query/model-class-query.d.ts +10 -0
- package/build/src/database/query/model-class-query.d.ts.map +1 -1
- package/build/src/database/query/model-class-query.js +16 -1
- package/build/src/database/record/index.d.ts +7 -0
- package/build/src/database/record/index.d.ts.map +1 -1
- package/build/src/database/record/index.js +15 -1
- package/build/src/sync/query-scope.d.ts +18 -0
- package/build/src/sync/query-scope.d.ts.map +1 -0
- package/build/src/sync/query-scope.js +77 -0
- package/build/src/sync/server-change-feed.d.ts.map +1 -1
- package/build/src/sync/server-change-feed.js +2 -24
- package/build/src/sync/stable-json.d.ts +9 -0
- package/build/src/sync/stable-json.d.ts.map +1 -0
- package/build/src/sync/stable-json.js +27 -0
- package/build/src/sync/sync-api-client.d.ts +185 -5
- package/build/src/sync/sync-api-client.d.ts.map +1 -1
- package/build/src/sync/sync-api-client.js +309 -17
- package/build/src/sync/sync-api-controller.d.ts +8 -0
- package/build/src/sync/sync-api-controller.d.ts.map +1 -1
- package/build/src/sync/sync-api-controller.js +17 -1
- package/build/src/sync/sync-client-registry.d.ts +12 -0
- package/build/src/sync/sync-client-registry.d.ts.map +1 -0
- package/build/src/sync/sync-client-registry.js +27 -0
- package/build/src/sync/sync-client-types.d.ts +127 -0
- package/build/src/sync/sync-client-types.d.ts.map +1 -0
- package/build/src/sync/sync-client-types.js +3 -0
- package/build/src/sync/sync-client.d.ts +176 -0
- package/build/src/sync/sync-client.d.ts.map +1 -0
- package/build/src/sync/sync-client.js +380 -0
- package/build/src/sync/sync-envelope-replay-service.d.ts +38 -4
- package/build/src/sync/sync-envelope-replay-service.d.ts.map +1 -1
- package/build/src/sync/sync-envelope-replay-service.js +73 -6
- package/build/src/sync/sync-resource-base.d.ts +98 -0
- package/build/src/sync/sync-resource-base.d.ts.map +1 -0
- package/build/src/sync/sync-resource-base.js +122 -0
- package/build/src/sync/sync-scope-store.d.ts +128 -0
- package/build/src/sync/sync-scope-store.d.ts.map +1 -0
- package/build/src/sync/sync-scope-store.js +282 -0
- package/build/src/tenants/default-tenant-database-provisioning.d.ts +21 -0
- package/build/src/tenants/default-tenant-database-provisioning.d.ts.map +1 -0
- package/build/src/tenants/default-tenant-database-provisioning.js +106 -0
- package/build/src/tenants/tenant.d.ts.map +1 -1
- package/build/src/tenants/tenant.js +6 -5
- package/build/sync/query-scope.js +84 -0
- package/build/sync/server-change-feed.js +1 -25
- package/build/sync/stable-json.js +28 -0
- package/build/sync/sync-api-client.js +331 -17
- package/build/sync/sync-api-controller.js +20 -0
- package/build/sync/sync-client-registry.js +30 -0
- package/build/sync/sync-client-types.js +41 -0
- package/build/sync/sync-client.js +433 -0
- package/build/sync/sync-envelope-replay-service.js +77 -5
- package/build/sync/sync-resource-base.js +143 -0
- package/build/sync/sync-scope-store.js +327 -0
- package/build/tenants/default-tenant-database-provisioning.js +124 -0
- package/build/tenants/tenant.js +5 -5
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/application.js +3 -0
- package/src/cli/commands/db/tenants/create.js +7 -5
- package/src/cli/commands/db/tenants/drop.js +6 -6
- package/src/configuration-types.js +8 -0
- package/src/configuration.js +29 -0
- package/src/database/query/index.js +30 -0
- package/src/database/query/model-class-query.js +17 -0
- package/src/database/record/index.js +16 -0
- package/src/sync/query-scope.js +84 -0
- package/src/sync/server-change-feed.js +1 -25
- package/src/sync/stable-json.js +28 -0
- package/src/sync/sync-api-client.js +331 -17
- package/src/sync/sync-api-controller.js +20 -0
- package/src/sync/sync-client-registry.js +30 -0
- package/src/sync/sync-client-types.js +41 -0
- package/src/sync/sync-client.js +433 -0
- package/src/sync/sync-envelope-replay-service.js +77 -5
- package/src/sync/sync-resource-base.js +143 -0
- package/src/sync/sync-scope-store.js +327 -0
- package/src/tenants/default-tenant-database-provisioning.js +124 -0
- package/src/tenants/tenant.js +5 -5
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
import BaseCommand from "../../../base-command.js"
|
|
4
|
+
import {createTenantDatabase} from "../../../../tenants/default-tenant-database-provisioning.js"
|
|
4
5
|
import TenantDatabaseCommandHelper from "../../../tenant-database-command-helper.js"
|
|
5
6
|
|
|
6
7
|
export default class DbTenantsCreate extends BaseCommand {
|
|
@@ -14,13 +15,14 @@ export default class DbTenantsCreate extends BaseCommand {
|
|
|
14
15
|
identifier: this.processArgs?.[1]
|
|
15
16
|
})
|
|
16
17
|
const provider = helper.provider
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
// Providers may customize creation, but the generic driver-agnostic provisioning
|
|
19
|
+
// is the framework default so apps do not reimplement it.
|
|
20
|
+
const createDatabase = typeof provider.createDatabase === "function"
|
|
21
|
+
? provider.createDatabase.bind(provider)
|
|
22
|
+
: createTenantDatabase
|
|
21
23
|
|
|
22
24
|
const tenantCount = await helper.eachTenant(async ({databaseConfiguration, tenant}) => {
|
|
23
|
-
await
|
|
25
|
+
await createDatabase({
|
|
24
26
|
configuration: this.getConfiguration(),
|
|
25
27
|
databaseConfiguration,
|
|
26
28
|
identifier: helper.identifier,
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
import BaseCommand from "../../../base-command.js"
|
|
4
|
+
import {dropTenantDatabase} from "../../../../tenants/default-tenant-database-provisioning.js"
|
|
4
5
|
import TenantDatabaseCommandHelper from "../../../tenant-database-command-helper.js"
|
|
5
6
|
|
|
6
7
|
export default class DbTenantsDrop extends BaseCommand {
|
|
7
8
|
/**
|
|
8
9
|
* Drops the tenant database/schema for every listed tenant through the provider's
|
|
9
|
-
* `dropDatabase` hook.
|
|
10
|
+
* `dropDatabase` hook, or the framework default when the provider defines none.
|
|
10
11
|
* @returns {Promise<{identifier: string, tenantCount: number} | void>} - Result in test mode.
|
|
11
12
|
*/
|
|
12
13
|
async execute() {
|
|
@@ -15,13 +16,12 @@ export default class DbTenantsDrop extends BaseCommand {
|
|
|
15
16
|
identifier: this.processArgs?.[1]
|
|
16
17
|
})
|
|
17
18
|
const provider = helper.provider
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
19
|
+
const dropDatabase = typeof provider.dropDatabase === "function"
|
|
20
|
+
? provider.dropDatabase.bind(provider)
|
|
21
|
+
: dropTenantDatabase
|
|
22
22
|
|
|
23
23
|
const tenantCount = await helper.eachTenant(async ({databaseConfiguration, tenant}) => {
|
|
24
|
-
await
|
|
24
|
+
await dropDatabase({
|
|
25
25
|
configuration: this.getConfiguration(),
|
|
26
26
|
databaseConfiguration,
|
|
27
27
|
identifier: helper.identifier,
|
|
@@ -355,9 +355,17 @@
|
|
|
355
355
|
* @property {Record<string, FrontendModelSyncJsonValue>} [policy] - Deterministic non-secret policy inputs included in the policy hash only.
|
|
356
356
|
*/
|
|
357
357
|
|
|
358
|
+
/**
|
|
359
|
+
* Velocious sync API endpoint configuration.
|
|
360
|
+
* @typedef {object} VelociousSyncApiConfiguration
|
|
361
|
+
* @property {string} [mountPath] - Mount path for the sync endpoints. Defaults to "/velocious/sync".
|
|
362
|
+
* @property {FrontendModelResourceClassType} resourceClass - App sync resource class served by the auto-mounted sync endpoints.
|
|
363
|
+
*/
|
|
364
|
+
|
|
358
365
|
/**
|
|
359
366
|
* Velocious sync configuration.
|
|
360
367
|
* @typedef {object} VelociousSyncConfiguration
|
|
368
|
+
* @property {VelociousSyncApiConfiguration} [api] - Auto-mounts the Velocious sync changes/replay endpoints for this resource class.
|
|
361
369
|
* @property {import("./sync/device-identity.js").SyncJsonWebKey | null} [deviceCertificateBackendPublicKey] - Public backend key used to verify offline device certificates for sync replay.
|
|
362
370
|
* @property {number} [changeFeedRetentionSize] - Number of accepted server changes retained before clients must refresh from snapshot.
|
|
363
371
|
* @property {Array<import("./sync/offline-grant.js").OfflineGrantSigningKey>} offlineGrantSigningKeys - Signing keys used to issue and verify offline grants. Secrets must never be exposed to clients.
|
package/src/configuration.js
CHANGED
|
@@ -424,6 +424,7 @@ export default class VelociousConfiguration {
|
|
|
424
424
|
* @returns {import("./configuration-types.js").VelociousSyncConfiguration} - Normalized sync configuration.
|
|
425
425
|
*/
|
|
426
426
|
_normalizeSyncConfiguration(sync) {
|
|
427
|
+
const api = sync?.api
|
|
427
428
|
const deviceCertificateBackendPublicKey = sync?.deviceCertificateBackendPublicKey || null
|
|
428
429
|
const changeFeedRetentionSize = sync?.changeFeedRetentionSize
|
|
429
430
|
const offlineGrantSigningKeys = sync?.offlineGrantSigningKeys || []
|
|
@@ -441,6 +442,7 @@ export default class VelociousConfiguration {
|
|
|
441
442
|
}
|
|
442
443
|
|
|
443
444
|
return {
|
|
445
|
+
api: this._normalizeSyncApiConfiguration(api),
|
|
444
446
|
changeFeedRetentionSize: changeFeedRetentionSize || 10000,
|
|
445
447
|
deviceCertificateBackendPublicKey,
|
|
446
448
|
offlineGrantSigningKeys: offlineGrantSigningKeys.map((key) => normalizeOfflineGrantSigningKey(key)),
|
|
@@ -448,6 +450,33 @@ export default class VelociousConfiguration {
|
|
|
448
450
|
}
|
|
449
451
|
}
|
|
450
452
|
|
|
453
|
+
/**
|
|
454
|
+
* Normalizes sync API endpoint configuration.
|
|
455
|
+
* @param {import("./configuration-types.js").VelociousSyncApiConfiguration | undefined} api - Sync API configuration.
|
|
456
|
+
* @returns {import("./configuration-types.js").VelociousSyncApiConfiguration | undefined} - Normalized sync API configuration.
|
|
457
|
+
*/
|
|
458
|
+
_normalizeSyncApiConfiguration(api) {
|
|
459
|
+
if (api === undefined || api === null) return undefined
|
|
460
|
+
|
|
461
|
+
if (typeof api !== "object" || Array.isArray(api)) {
|
|
462
|
+
throw new Error("sync.api must be an object with a resourceClass")
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const {mountPath, resourceClass} = api
|
|
466
|
+
|
|
467
|
+
if (typeof resourceClass !== "function") {
|
|
468
|
+
throw new Error(`sync.api.resourceClass must be a resource class, got: ${String(resourceClass)}`)
|
|
469
|
+
}
|
|
470
|
+
if (!resourceClass.ModelClass) {
|
|
471
|
+
throw new Error(`sync.api.resourceClass ${resourceClass.name} must define static ModelClass`)
|
|
472
|
+
}
|
|
473
|
+
if (mountPath !== undefined && (typeof mountPath !== "string" || !mountPath.startsWith("/"))) {
|
|
474
|
+
throw new Error(`sync.api.mountPath must start with '/', got: ${String(mountPath)}`)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return {mountPath, resourceClass}
|
|
478
|
+
}
|
|
479
|
+
|
|
451
480
|
/**
|
|
452
481
|
* Runs get database configuration.
|
|
453
482
|
* @returns {Record<string, import("./configuration-types.js").DatabaseConfigurationType>} - The database configuration.
|
|
@@ -235,18 +235,48 @@ export default class VelociousDatabaseQuery {
|
|
|
235
235
|
return this._groups
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
+
/**
|
|
239
|
+
* Runs get joins.
|
|
240
|
+
* @returns {Array<?>} - The joins.
|
|
241
|
+
*/
|
|
242
|
+
getJoins() { return this._joins }
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Runs get limit.
|
|
246
|
+
* @returns {number | null} - The limit.
|
|
247
|
+
*/
|
|
248
|
+
getLimit() { return this._limit }
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Runs get offset.
|
|
252
|
+
* @returns {number | null} - The offset.
|
|
253
|
+
*/
|
|
254
|
+
getOffset() { return this._offset }
|
|
255
|
+
|
|
238
256
|
/**
|
|
239
257
|
* Runs get options.
|
|
240
258
|
* @returns {import("../query-parser/options.js").default} - The options options.
|
|
241
259
|
*/
|
|
242
260
|
getOptions() { return this.driver.options() }
|
|
243
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Runs get orders.
|
|
264
|
+
* @returns {Array<?>} - The orders.
|
|
265
|
+
*/
|
|
266
|
+
getOrders() { return this._orders }
|
|
267
|
+
|
|
244
268
|
/**
|
|
245
269
|
* Runs get selects.
|
|
246
270
|
* @returns {Array<import("./select-base.js").default>} - The selects.
|
|
247
271
|
*/
|
|
248
272
|
getSelects() { return this._selects }
|
|
249
273
|
|
|
274
|
+
/**
|
|
275
|
+
* Runs get wheres.
|
|
276
|
+
* @returns {Array<import("./where-base.js").default>} - The wheres.
|
|
277
|
+
*/
|
|
278
|
+
getWheres() { return this._wheres }
|
|
279
|
+
|
|
250
280
|
/**
|
|
251
281
|
* Runs from.
|
|
252
282
|
* @param {string|import("./from-base.js").default} from - From.
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import {incorporate} from "incorporator"
|
|
4
4
|
import * as inflection from "inflection"
|
|
5
5
|
import {isPlainObject} from "is-plain-object"
|
|
6
|
+
import {currentSyncClient} from "../../sync/sync-client-registry.js"
|
|
6
7
|
import Logger from "../../logger.js"
|
|
7
8
|
import Preloader from "./preloader.js"
|
|
8
9
|
import {normalizeQueryDataSpec, runQueryData} from "./query-data.js"
|
|
@@ -1114,6 +1115,22 @@ export default class VelociousDatabaseQueryModelClassQuery extends DatabaseQuery
|
|
|
1114
1115
|
queryLogName(operation) {
|
|
1115
1116
|
return `${this.getModelClass().name} ${operation}`
|
|
1116
1117
|
}
|
|
1118
|
+
|
|
1119
|
+
/**
|
|
1120
|
+
* Declares this query as a sync scope on the current sync client.
|
|
1121
|
+
* @returns {Promise<?>} - Declared scope and pull result.
|
|
1122
|
+
*/
|
|
1123
|
+
async sync() {
|
|
1124
|
+
return await currentSyncClient().sync(this)
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
/**
|
|
1128
|
+
* Deactivates this query's sync scope on the current sync client.
|
|
1129
|
+
* @returns {Promise<void>} - Resolves when the scope is deactivated.
|
|
1130
|
+
*/
|
|
1131
|
+
async unsync() {
|
|
1132
|
+
await currentSyncClient().unsync(this)
|
|
1133
|
+
}
|
|
1117
1134
|
}
|
|
1118
1135
|
|
|
1119
1136
|
/**
|
|
@@ -535,6 +535,22 @@ class VelociousDatabaseRecord {
|
|
|
535
535
|
callbacks[callbackName].push(callback)
|
|
536
536
|
}
|
|
537
537
|
|
|
538
|
+
/**
|
|
539
|
+
* Runs unregister lifecycle callback.
|
|
540
|
+
* @param {"afterCreate" | "afterDestroy" | "afterSave" | "afterUpdate" | "beforeCreate" | "beforeDestroy" | "beforeSave" | "beforeUpdate" | "beforeValidation"} callbackName - Callback type.
|
|
541
|
+
* @param {LifecycleCallbackType} callback - Previously registered callback.
|
|
542
|
+
* @returns {void}
|
|
543
|
+
*/
|
|
544
|
+
static unregisterLifecycleCallback(callbackName, callback) {
|
|
545
|
+
const callbacks = this.getLifecycleCallbacksMap()[callbackName]
|
|
546
|
+
|
|
547
|
+
if (!callbacks) return
|
|
548
|
+
|
|
549
|
+
const callbackIndex = callbacks.indexOf(callback)
|
|
550
|
+
|
|
551
|
+
if (callbackIndex >= 0) callbacks.splice(callbackIndex, 1)
|
|
552
|
+
}
|
|
553
|
+
|
|
538
554
|
/**
|
|
539
555
|
* Runs before validation.
|
|
540
556
|
* @template R
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import stableJsonStringify from "./stable-json.js"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Serializes a model query into a transportable sync scope.
|
|
7
|
+
*
|
|
8
|
+
* Only plain attribute equality conditions are supported: the scope must be
|
|
9
|
+
* expressible as `{resourceType, conditions}` so servers can match it against
|
|
10
|
+
* their change feeds. Anything else (raw SQL, negations, joins, orders,
|
|
11
|
+
* limits, offsets, groups) fails loudly.
|
|
12
|
+
* @param {import("../database/query/model-class-query.js").default<?>} query - Model query declaring the sync scope.
|
|
13
|
+
* @returns {import("./sync-client-types.js").SerializedSyncScope} Serialized sync scope.
|
|
14
|
+
*/
|
|
15
|
+
export function serializedScopeFromQuery(query) {
|
|
16
|
+
if (query.getJoins().length > 0) throw new Error("sync(query) does not support joins")
|
|
17
|
+
if (query.getOrders().length > 0) throw new Error("sync(query) does not support orders")
|
|
18
|
+
if (query.getLimit() !== null && query.getLimit() !== undefined) throw new Error("sync(query) does not support limit")
|
|
19
|
+
if (query.getOffset() !== null && query.getOffset() !== undefined) throw new Error("sync(query) does not support offset")
|
|
20
|
+
if (query.getGroups().length > 0) throw new Error("sync(query) does not support groups")
|
|
21
|
+
|
|
22
|
+
const modelClass = query.getModelClass()
|
|
23
|
+
const conditions = /** @type {Record<string, ?>} */ ({})
|
|
24
|
+
|
|
25
|
+
for (const where of query.getWheres()) {
|
|
26
|
+
const whereHash = /** @type {{hash?: Record<string, ?>}} */ (where).hash
|
|
27
|
+
|
|
28
|
+
if (!whereHash || typeof whereHash !== "object" || Array.isArray(whereHash) || /** @type {{where?: ?}} */ (where).where) {
|
|
29
|
+
throw new Error(`sync(query) only supports plain attribute conditions, got: ${where.constructor.name}`)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
for (const [attributeName, value] of Object.entries(whereHash)) {
|
|
33
|
+
const conditionValue = scalarConditionValue(attributeName, value)
|
|
34
|
+
|
|
35
|
+
if (attributeName in conditions && stableJsonStringify(conditions[attributeName]) !== stableJsonStringify(conditionValue)) {
|
|
36
|
+
throw new Error(`sync(query) got conflicting conditions for: ${attributeName}`)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
conditions[attributeName] = conditionValue
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {conditions, resourceType: modelClass.getModelName()}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Returns a stable canonical key identifying a sync scope.
|
|
48
|
+
* @param {import("./sync-client-types.js").SerializedSyncScope} scope - Serialized sync scope.
|
|
49
|
+
* @returns {string} Stable scope key.
|
|
50
|
+
*/
|
|
51
|
+
export function scopeKey(scope) {
|
|
52
|
+
return `${scope.resourceType}:${stableJsonStringify(scope.conditions)}`
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Validates one scope condition value as a scalar or array of scalars.
|
|
57
|
+
* @param {string} attributeName - Condition attribute name for error messages.
|
|
58
|
+
* @param {?} value - Condition value.
|
|
59
|
+
* @returns {?} Validated condition value.
|
|
60
|
+
*/
|
|
61
|
+
function scalarConditionValue(attributeName, value) {
|
|
62
|
+
if (Array.isArray(value)) {
|
|
63
|
+
for (const item of value) validateScalar(attributeName, item)
|
|
64
|
+
|
|
65
|
+
return value
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
validateScalar(attributeName, value)
|
|
69
|
+
|
|
70
|
+
return value
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Validates one scalar condition value.
|
|
75
|
+
* @param {string} attributeName - Condition attribute name for error messages.
|
|
76
|
+
* @param {?} value - Condition value.
|
|
77
|
+
* @returns {void}
|
|
78
|
+
*/
|
|
79
|
+
function validateScalar(attributeName, value) {
|
|
80
|
+
if (value === null) return
|
|
81
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return
|
|
82
|
+
|
|
83
|
+
throw new Error(`sync(query) condition values must be scalar, got ${typeof value} for: ${attributeName}`)
|
|
84
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import {randomUUID} from "crypto"
|
|
4
4
|
import TableData from "../database/table-data/index.js"
|
|
5
|
+
import stableJsonStringify from "./stable-json.js"
|
|
5
6
|
|
|
6
7
|
const DEFAULT_RETENTION_SIZE = 10000
|
|
7
8
|
const DEFAULT_PAGE_SIZE = 100
|
|
@@ -464,31 +465,6 @@ function scopesEqual(changeScope, requestedScope) {
|
|
|
464
465
|
return stableJsonStringify(changeScope || null) === stableJsonStringify(requestedScope)
|
|
465
466
|
}
|
|
466
467
|
|
|
467
|
-
/**
|
|
468
|
-
* Stable JSON serialization for persisted sync scopes.
|
|
469
|
-
* @param {?} value - JSON-compatible value.
|
|
470
|
-
* @returns {string} - Stable JSON representation.
|
|
471
|
-
*/
|
|
472
|
-
function stableJsonStringify(value) {
|
|
473
|
-
return JSON.stringify(stableJsonValue(value))
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
/**
|
|
477
|
-
* Produces a recursively key-sorted JSON value.
|
|
478
|
-
* @param {?} value - JSON-compatible value.
|
|
479
|
-
* @returns {?} - Stable JSON-compatible value.
|
|
480
|
-
*/
|
|
481
|
-
function stableJsonValue(value) {
|
|
482
|
-
if (Array.isArray(value)) return value.map((item) => stableJsonValue(item))
|
|
483
|
-
if (!value || typeof value !== "object") return value
|
|
484
|
-
|
|
485
|
-
return Object.keys(value).sort().reduce((memo, key) => {
|
|
486
|
-
memo[key] = stableJsonValue(value[key])
|
|
487
|
-
|
|
488
|
-
return memo
|
|
489
|
-
}, /** @type {Record<string, ?>} */ ({}))
|
|
490
|
-
}
|
|
491
|
-
|
|
492
468
|
/**
|
|
493
469
|
* @typedef {object} ServerChangeFeedEntry
|
|
494
470
|
* @property {string | null} actorDeviceId - Signed mutation actor device id when available.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Serializes a JSON-compatible value with recursively sorted object keys, so
|
|
5
|
+
* equal values always produce byte-identical strings (used for sync scope and
|
|
6
|
+
* change-feed identity comparisons).
|
|
7
|
+
* @param {?} value - JSON-compatible value.
|
|
8
|
+
* @returns {string} - Stable JSON string.
|
|
9
|
+
*/
|
|
10
|
+
export default function stableJsonStringify(value) {
|
|
11
|
+
return JSON.stringify(stableJsonValue(value))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Produces a recursively key-sorted JSON value.
|
|
16
|
+
* @param {?} value - JSON-compatible value.
|
|
17
|
+
* @returns {?} - Stable JSON-compatible value.
|
|
18
|
+
*/
|
|
19
|
+
function stableJsonValue(value) {
|
|
20
|
+
if (Array.isArray(value)) return value.map((item) => stableJsonValue(item))
|
|
21
|
+
if (!value || typeof value !== "object") return value
|
|
22
|
+
|
|
23
|
+
return Object.keys(value).sort().reduce((memo, key) => {
|
|
24
|
+
memo[key] = stableJsonValue(value[key])
|
|
25
|
+
|
|
26
|
+
return memo
|
|
27
|
+
}, /** @type {Record<string, ?>} */ ({}))
|
|
28
|
+
}
|