velocious 1.0.496 → 1.0.497
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/configuration-types.js +21 -0
- package/build/configuration.js +48 -0
- package/build/database/column-types.js +19 -0
- package/build/database/record/index.js +9 -0
- package/build/src/configuration-types.d.ts +68 -0
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +20 -1
- package/build/src/configuration.d.ts +6 -0
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +45 -1
- package/build/src/database/column-types.d.ts +8 -0
- package/build/src/database/column-types.d.ts.map +1 -0
- package/build/src/database/column-types.js +18 -0
- package/build/src/database/record/index.d.ts +8 -0
- package/build/src/database/record/index.d.ts.map +1 -1
- package/build/src/database/record/index.js +9 -1
- package/build/src/sync/sync-api-client.d.ts +4 -1
- package/build/src/sync/sync-api-client.d.ts.map +1 -1
- package/build/src/sync/sync-api-client.js +9 -2
- package/build/src/sync/sync-client-types.d.ts +88 -10
- package/build/src/sync/sync-client-types.d.ts.map +1 -1
- package/build/src/sync/sync-client-types.js +1 -1
- package/build/src/sync/sync-client.d.ts +38 -11
- package/build/src/sync/sync-client.d.ts.map +1 -1
- package/build/src/sync/sync-client.js +193 -29
- package/build/sync/sync-api-client.js +7 -1
- package/build/sync/sync-client-types.js +34 -5
- package/build/sync/sync-client.js +223 -27
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/configuration-types.js +21 -0
- package/src/configuration.js +48 -0
- package/src/database/column-types.js +19 -0
- package/src/database/record/index.js +9 -0
- package/src/sync/sync-api-client.js +7 -1
- package/src/sync/sync-client-types.js +34 -5
- package/src/sync/sync-client.js +223 -27
|
@@ -17,24 +17,53 @@
|
|
|
17
17
|
* @property {import("./sync-api-client-types.js").SyncResourceConfig["afterApply"]} [afterApply] - Post-apply hook.
|
|
18
18
|
* @property {string[]} [booleanAttributes] - Attributes coerced through sync boolean parsing when queueing.
|
|
19
19
|
* @property {string[]} [localOnlyAttributes] - Attributes stripped from queued payloads.
|
|
20
|
-
* @property {(args: {operation: "create" | "update" | "destroy", record: ?}) => string} [syncType] - Maps a mutation operation to a sync type. Defaults to the operation name with destroy mapped to "delete".
|
|
20
|
+
* @property {"upsert" | ((args: {operation: "create" | "update" | "destroy", record: ?}) => string)} [syncType] - Maps a mutation operation to a sync type. The "upsert" flag queues creates and updates as "update" rows (the server upserts by resource id) and destroys as "delete". Defaults to the operation name with destroy mapped to "delete".
|
|
21
21
|
* @property {(args: {operation: "create" | "update" | "destroy", record: ?}) => Record<string, ?>} [trackedData] - Custom queued-payload builder for tracked mutations.
|
|
22
22
|
* @property {boolean | {operations: Array<"create" | "update" | "destroy">}} [track] - Enables automatic mutation tracking through model lifecycle callbacks.
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
26
|
+
* Model-level client sync declaration read from `static sync` by
|
|
27
|
+
* `SyncClient.fromConfiguration(...)`. `true` opts the model in with all
|
|
28
|
+
* defaults; an object customizes the derived resource config.
|
|
29
|
+
* @typedef {object} ModelSyncDeclarationConfig
|
|
30
|
+
* @property {import("./sync-api-client-types.js").SyncResourceConfig["afterApply"]} [afterApply] - Post-apply hook.
|
|
31
|
+
* @property {import("./sync-api-client-types.js").SyncResourceConfig["attributes"]} [attributes] - Pull-apply attribute mapper. Required for resources that receive pulled changes.
|
|
32
|
+
* @property {string[]} [booleanAttributes] - Extra boolean attributes merged with the boolean columns derived from column types.
|
|
33
|
+
* @property {import("./sync-api-client-types.js").SyncResourceConfig["findRecord"]} [findRecord] - Custom pull-apply record resolver.
|
|
34
|
+
* @property {import("./sync-api-client-types.js").SyncResourceConfig["findRecordForDelete"]} [findRecordForDelete] - Custom pull-apply delete resolver.
|
|
35
|
+
* @property {string[]} [localOnlyAttributes] - Extra local-only attributes merged with the derived primary key, createdAt/updatedAt, and sync bookkeeping attributes.
|
|
36
|
+
* @property {"upsert" | ((args: {operation: "create" | "update" | "destroy", record: ?}) => string)} [syncType] - Sync type flag or mapper (see SyncClientResourceConfig).
|
|
37
|
+
* @property {boolean | Array<"create" | "update" | "destroy"> | {operations: Array<"create" | "update" | "destroy">}} [track] - Enables automatic mutation tracking; an array is shorthand for {operations}.
|
|
38
|
+
* @property {(args: {operation: "create" | "update" | "destroy", record: ?}) => Record<string, ?>} [trackedData] - Custom queued-payload builder for tracked mutations.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/** @typedef {boolean | ModelSyncDeclarationConfig} ModelSyncDeclaration */
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Options for building a sync client. Everything else — resources, transport
|
|
45
|
+
* POSTers, auth, connectivity, batch size — is derived from the configuration's
|
|
46
|
+
* registered models (`static sync`) and its `sync.client` block.
|
|
47
|
+
* @typedef {object} SyncClientOptions
|
|
48
|
+
* @property {import("../configuration.js").default} [configuration] - Configuration owning the registered models, the `sync.client` block, and the scope-store database. Defaults to the current configuration.
|
|
49
|
+
* @property {(args: {scope: SerializedSyncScope}) => string | null | Promise<string | null>} [legacyCursor] - Seeds a newly declared scope's cursor (e.g. from a pre-scope cursor store) so devices don't re-pull everything.
|
|
50
|
+
* @property {import("./sync-scope-store.js").default} [scopeStore] - Scope store override (tests).
|
|
51
|
+
* @property {?} [syncModel] - Pending-sync model override. Defaults to the registered "Sync" model.
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Internal derived sync client configuration built by the SyncClient
|
|
56
|
+
* constructor — not an app-facing API.
|
|
27
57
|
* @typedef {object} SyncClientConfig
|
|
28
58
|
* @property {() => string | Promise<string>} authenticationToken - Resolves the auth token sent with sync requests.
|
|
29
59
|
* @property {number} [batchSize] - Max syncs per request.
|
|
30
|
-
* @property {import("../configuration.js").default}
|
|
60
|
+
* @property {import("../configuration.js").default} configuration - Configuration owning the scope-store database.
|
|
31
61
|
* @property {() => boolean | Promise<boolean>} [isOnline] - Connectivity gate for pulls and replays. Defaults to always online.
|
|
32
62
|
* @property {(args: {scope: SerializedSyncScope}) => string | null | Promise<string | null>} [legacyCursor] - Seeds a newly declared scope's cursor (e.g. from a pre-scope cursor store) so devices don't re-pull everything.
|
|
33
63
|
* @property {(error: Error) => void} [onError] - Reports background replay/pull failures. Defaults to rethrowing.
|
|
34
64
|
* @property {(payload: import("./sync-api-client-types.js").SyncChangesRequest & {scope: SerializedSyncScope}) => Promise<import("./sync-api-client-types.js").SyncChangesResponse>} postChanges - Posts one changes request.
|
|
35
65
|
* @property {(payload: {authenticationToken: string, syncs: Array<Record<string, ?>>}) => Promise<import("./sync-api-client-types.js").SyncReplayResponse>} postReplay - Posts one replay request.
|
|
36
|
-
* @property {Record<string, SyncClientResourceConfig>} resources -
|
|
37
|
-
* @property {import("./sync-scope-store.js").default} [scopeStore] - Scope store override.
|
|
66
|
+
* @property {Record<string, SyncClientResourceConfig>} resources - Derived resource policies keyed by resource/model name.
|
|
38
67
|
* @property {?} syncModel - Local pending-sync model class.
|
|
39
68
|
*/
|
|
40
69
|
|
package/src/sync/sync-client.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
import {forcedFunction} from "typanic"
|
|
4
|
-
|
|
5
3
|
import Configuration from "../configuration.js"
|
|
4
|
+
import {isBooleanColumnType} from "../database/column-types.js"
|
|
5
|
+
import restArgsError from "../utils/rest-args-error.js"
|
|
6
6
|
|
|
7
7
|
import {serializedScopeFromQuery} from "./query-scope.js"
|
|
8
8
|
import SyncApiClient from "./sync-api-client.js"
|
|
@@ -14,44 +14,85 @@ let clientCounter = 0
|
|
|
14
14
|
/** @type {{create: "afterCreate", update: "afterUpdate", destroy: "afterDestroy"}} */
|
|
15
15
|
const TRACKED_CALLBACK_NAMES = {create: "afterCreate", destroy: "afterDestroy", update: "afterUpdate"}
|
|
16
16
|
|
|
17
|
+
/** Attribute names treated as client-local sync bookkeeping when deriving localOnlyAttributes. */
|
|
18
|
+
const LOCAL_BOOKKEEPING_ATTRIBUTE_NAMES = ["createdAt", "updatedAt", "lastSyncChangeAt"]
|
|
19
|
+
|
|
20
|
+
/** @type {WeakMap<Configuration, SyncClient>} */
|
|
21
|
+
const syncClientsByConfiguration = new WeakMap()
|
|
22
|
+
|
|
17
23
|
/**
|
|
18
24
|
* Declarative client-side sync driver.
|
|
19
25
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* and
|
|
26
|
+
* Everything is derived from the app's Velocious configuration: models declare
|
|
27
|
+
* `static sync`, transport/auth/connectivity come from the `sync.client`
|
|
28
|
+
* configuration block, and Velocious owns scope persistence, per-scope cursors,
|
|
29
|
+
* pull paging/apply, local queueing, and online-gated replay. Declare sync
|
|
30
|
+
* interest from queries:
|
|
23
31
|
*
|
|
24
|
-
*
|
|
25
|
-
* syncClient.
|
|
26
|
-
* await syncClient.sync(Event.where({partnerId}))
|
|
32
|
+
* await syncClient().start()
|
|
33
|
+
* await syncClient().sync(Event.where({partnerId}))
|
|
27
34
|
*/
|
|
28
35
|
export default class SyncClient {
|
|
29
36
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
37
|
+
* Builds the sync client by deriving everything from the app's Velocious
|
|
38
|
+
* configuration: every registered model declaring `static sync` becomes a
|
|
39
|
+
* resource with booleanAttributes derived from column types and
|
|
40
|
+
* localOnlyAttributes derived from the primary key, createdAt/updatedAt, and
|
|
41
|
+
* sync bookkeeping columns; the pending-sync model is the registered "Sync"
|
|
42
|
+
* model; transport, auth, connectivity, and error reporting come from the
|
|
43
|
+
* `sync.client` configuration block, with the framework owning the
|
|
44
|
+
* `${mountPath}/changes` and `${mountPath}/replay` POSTers.
|
|
45
|
+
* @param {import("./sync-client-types.js").SyncClientOptions} [options] - Optional overrides.
|
|
32
46
|
*/
|
|
33
|
-
constructor(
|
|
34
|
-
|
|
47
|
+
constructor(options = {}) {
|
|
48
|
+
const {configuration = Configuration.current(), legacyCursor, scopeStore, syncModel, ...restOptions} = options
|
|
35
49
|
|
|
36
|
-
|
|
37
|
-
forcedFunction(config.postReplay, "SyncClient config.postReplay")
|
|
38
|
-
forcedFunction(config.authenticationToken, "SyncClient config.authenticationToken")
|
|
50
|
+
restArgsError(restOptions)
|
|
39
51
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
52
|
+
const clientConfiguration = configuration.getSyncConfiguration().client
|
|
53
|
+
|
|
54
|
+
if (!clientConfiguration) {
|
|
55
|
+
throw new Error("SyncClient requires a sync.client configuration block: new Configuration({sync: {client: {authenticationToken, transport}}})")
|
|
43
56
|
}
|
|
44
57
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
58
|
+
const modelClasses = configuration.getModelClasses()
|
|
59
|
+
/** @type {Record<string, import("./sync-client-types.js").SyncClientResourceConfig>} */
|
|
60
|
+
const resources = {}
|
|
61
|
+
|
|
62
|
+
for (const modelClass of Object.values(modelClasses)) {
|
|
63
|
+
if (!modelClass.sync) continue
|
|
64
|
+
|
|
65
|
+
const resourceType = modelClass.getModelName()
|
|
66
|
+
|
|
67
|
+
resources[resourceType] = resourceConfigFromSyncDeclaration({declaration: modelClass.sync, modelClass, resourceType})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (Object.keys(resources).length === 0) {
|
|
71
|
+
throw new Error("SyncClient found no registered models declaring static sync - declare `static sync = true` (or a sync declaration object) on the models that should sync")
|
|
49
72
|
}
|
|
50
73
|
|
|
51
|
-
|
|
74
|
+
const resolvedSyncModel = syncModel || modelClasses.Sync
|
|
75
|
+
|
|
76
|
+
if (!resolvedSyncModel) {
|
|
77
|
+
throw new Error("SyncClient requires a registered \"Sync\" model for pending local sync rows (or pass options.syncModel)")
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** @type {import("./sync-client-types.js").SyncClientConfig} */
|
|
81
|
+
this.config = {
|
|
82
|
+
authenticationToken: clientConfiguration.authenticationToken,
|
|
83
|
+
batchSize: clientConfiguration.batchSize,
|
|
84
|
+
configuration,
|
|
85
|
+
isOnline: clientConfiguration.isOnline,
|
|
86
|
+
legacyCursor,
|
|
87
|
+
onError: clientConfiguration.onError,
|
|
88
|
+
postChanges: transportPoster({path: `${clientConfiguration.mountPath}/changes`, transport: clientConfiguration.transport}),
|
|
89
|
+
postReplay: transportPoster({path: `${clientConfiguration.mountPath}/replay`, transport: clientConfiguration.transport}),
|
|
90
|
+
resources,
|
|
91
|
+
syncModel: resolvedSyncModel
|
|
92
|
+
}
|
|
52
93
|
this._clientNumber = ++clientCounter
|
|
53
94
|
/** @type {import("./sync-scope-store.js").default | null} */
|
|
54
|
-
this._scopeStore =
|
|
95
|
+
this._scopeStore = scopeStore || null
|
|
55
96
|
/** @type {Promise<void> | null} */
|
|
56
97
|
this._scheduledReplay = null
|
|
57
98
|
/** @type {Record<string, import("./sync-api-client-types.js").SyncResourceConfig> | null} */
|
|
@@ -171,6 +212,17 @@ export default class SyncClient {
|
|
|
171
212
|
return /** @type {SyncClient} */ (currentSyncClient())
|
|
172
213
|
}
|
|
173
214
|
|
|
215
|
+
/**
|
|
216
|
+
* Builds a sync client derived from the given configuration. Alias for
|
|
217
|
+
* `new SyncClient({configuration, ...options})`.
|
|
218
|
+
* @param {Configuration} [configuration] - Configuration owning the registered models and the sync.client block. Defaults to the current configuration.
|
|
219
|
+
* @param {Omit<import("./sync-client-types.js").SyncClientOptions, "configuration">} [options] - Optional overrides.
|
|
220
|
+
* @returns {SyncClient} Sync client derived from the configuration.
|
|
221
|
+
*/
|
|
222
|
+
static fromConfiguration(configuration = Configuration.current(), options = {}) {
|
|
223
|
+
return new SyncClient({...options, configuration})
|
|
224
|
+
}
|
|
225
|
+
|
|
174
226
|
/**
|
|
175
227
|
* Declares (or re-activates) a sync scope from a model query and pulls it when online.
|
|
176
228
|
* @param {import("../database/query/model-class-query.js").default<?>} query - Query declaring the sync scope.
|
|
@@ -364,7 +416,7 @@ export default class SyncClient {
|
|
|
364
416
|
* @returns {import("./sync-scope-store.js").default} Scope store.
|
|
365
417
|
*/
|
|
366
418
|
scopeStore() {
|
|
367
|
-
this._scopeStore ||= new SyncScopeStore({configuration: this.config.configuration
|
|
419
|
+
this._scopeStore ||= new SyncScopeStore({configuration: this.config.configuration})
|
|
368
420
|
|
|
369
421
|
return this._scopeStore
|
|
370
422
|
}
|
|
@@ -390,13 +442,16 @@ export default class SyncClient {
|
|
|
390
442
|
}
|
|
391
443
|
|
|
392
444
|
/**
|
|
393
|
-
* Resolves the sync type for a mutation through the resource config.
|
|
445
|
+
* Resolves the sync type for a mutation through the resource config. The
|
|
446
|
+
* "upsert" flag queues creates and updates as "update" rows (the server
|
|
447
|
+
* upserts by resource id) and destroys as "delete" rows.
|
|
394
448
|
* @param {{operation: "create" | "update" | "destroy", record: ?, resourceConfig: import("./sync-client-types.js").SyncClientResourceConfig}} args - Mutation args.
|
|
395
449
|
* @returns {string} Sync type.
|
|
396
450
|
*/
|
|
397
451
|
defaultSyncType({operation, record, resourceConfig}) {
|
|
398
|
-
if (resourceConfig.syncType) return resourceConfig.syncType({operation, record})
|
|
452
|
+
if (typeof resourceConfig.syncType === "function") return resourceConfig.syncType({operation, record})
|
|
399
453
|
if (operation === "destroy") return "delete"
|
|
454
|
+
if (resourceConfig.syncType === "upsert") return "update"
|
|
400
455
|
|
|
401
456
|
return operation
|
|
402
457
|
}
|
|
@@ -423,6 +478,147 @@ export default class SyncClient {
|
|
|
423
478
|
}
|
|
424
479
|
}
|
|
425
480
|
|
|
481
|
+
/**
|
|
482
|
+
* Builds one resource config from a model's `static sync` declaration plus its
|
|
483
|
+
* derived column metadata.
|
|
484
|
+
* @param {{declaration: import("./sync-client-types.js").ModelSyncDeclaration, modelClass: ?, resourceType: string}} args - Declaration args.
|
|
485
|
+
* @returns {import("./sync-client-types.js").SyncClientResourceConfig} Derived resource config.
|
|
486
|
+
*/
|
|
487
|
+
function resourceConfigFromSyncDeclaration({declaration, modelClass, resourceType}) {
|
|
488
|
+
const normalizedDeclaration = declaration === true ? {} : declaration
|
|
489
|
+
|
|
490
|
+
if (!normalizedDeclaration || typeof normalizedDeclaration !== "object" || Array.isArray(normalizedDeclaration)) {
|
|
491
|
+
throw new Error(`${resourceType} static sync must be true or a sync declaration object, got: ${String(declaration)}`)
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const {afterApply, attributes, booleanAttributes, findRecord, findRecordForDelete, localOnlyAttributes, syncType, track, trackedData, ...restDeclaration} = normalizedDeclaration
|
|
495
|
+
const unknownKeys = Object.keys(restDeclaration)
|
|
496
|
+
|
|
497
|
+
if (unknownKeys.length > 0) {
|
|
498
|
+
throw new Error(`${resourceType} static sync received unknown keys: ${unknownKeys.join(", ")} (supported: afterApply, attributes, booleanAttributes, findRecord, findRecordForDelete, localOnlyAttributes, syncType, track, trackedData)`)
|
|
499
|
+
}
|
|
500
|
+
if (syncType !== undefined && typeof syncType !== "function" && syncType !== "upsert") {
|
|
501
|
+
throw new Error(`${resourceType} static sync syncType must be a function or the string "upsert", got: ${String(syncType)}`)
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const derived = derivedSyncAttributes({modelClass, resourceType})
|
|
505
|
+
|
|
506
|
+
return {
|
|
507
|
+
afterApply,
|
|
508
|
+
attributes,
|
|
509
|
+
booleanAttributes: mergedAttributeNames(derived.booleanAttributes, booleanAttributes),
|
|
510
|
+
findRecord,
|
|
511
|
+
findRecordForDelete,
|
|
512
|
+
localOnlyAttributes: mergedAttributeNames(derived.localOnlyAttributes, localOnlyAttributes),
|
|
513
|
+
modelClass,
|
|
514
|
+
syncType,
|
|
515
|
+
track: normalizedTrack(track),
|
|
516
|
+
trackedData
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Derives boolean and local-only attribute names from a model's column metadata:
|
|
522
|
+
* booleans from boolean column types; local-only from the primary key,
|
|
523
|
+
* createdAt/updatedAt, and sync bookkeeping columns.
|
|
524
|
+
* @param {{modelClass: ?, resourceType: string}} args - Derivation args.
|
|
525
|
+
* @returns {{booleanAttributes: string[], localOnlyAttributes: string[]}} Derived attribute names.
|
|
526
|
+
*/
|
|
527
|
+
function derivedSyncAttributes({modelClass, resourceType}) {
|
|
528
|
+
if (
|
|
529
|
+
typeof modelClass.getColumnNames !== "function" ||
|
|
530
|
+
typeof modelClass.getColumnNameToAttributeNameMap !== "function" ||
|
|
531
|
+
typeof modelClass.getColumnTypeByName !== "function" ||
|
|
532
|
+
typeof modelClass.primaryKey !== "function" ||
|
|
533
|
+
typeof modelClass.hasPrimaryKey !== "function"
|
|
534
|
+
) {
|
|
535
|
+
throw new Error(`${resourceType} static sync requires a Velocious model class with column metadata (getColumnNames, getColumnNameToAttributeNameMap, getColumnTypeByName, primaryKey, hasPrimaryKey)`)
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const columnNameToAttributeName = modelClass.getColumnNameToAttributeNameMap()
|
|
539
|
+
/** @type {string[]} */
|
|
540
|
+
const booleanAttributes = []
|
|
541
|
+
/** @type {string[]} */
|
|
542
|
+
const localOnlyAttributes = []
|
|
543
|
+
|
|
544
|
+
if (modelClass.hasPrimaryKey()) {
|
|
545
|
+
const primaryKeyColumn = modelClass.primaryKey()
|
|
546
|
+
|
|
547
|
+
localOnlyAttributes.push(columnNameToAttributeName[primaryKeyColumn] || primaryKeyColumn)
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
for (const columnName of modelClass.getColumnNames()) {
|
|
551
|
+
const attributeName = columnNameToAttributeName[columnName] || columnName
|
|
552
|
+
const columnType = modelClass.getColumnTypeByName(columnName)
|
|
553
|
+
|
|
554
|
+
if (LOCAL_BOOKKEEPING_ATTRIBUTE_NAMES.includes(attributeName) && !localOnlyAttributes.includes(attributeName)) {
|
|
555
|
+
localOnlyAttributes.push(attributeName)
|
|
556
|
+
}
|
|
557
|
+
if (columnType && isBooleanColumnType(columnType)) {
|
|
558
|
+
booleanAttributes.push(attributeName)
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
return {booleanAttributes, localOnlyAttributes}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Merges derived attribute names with declared extras into a sorted, duplicate-free list.
|
|
567
|
+
* @param {string[]} derived - Derived attribute names.
|
|
568
|
+
* @param {string[] | undefined} declared - Declared extra attribute names.
|
|
569
|
+
* @returns {string[]} Merged attribute names.
|
|
570
|
+
*/
|
|
571
|
+
function mergedAttributeNames(derived, declared) {
|
|
572
|
+
return [...new Set([...derived, ...(declared || [])])].sort()
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Normalizes a declaration's track value: an operations array is shorthand for
|
|
577
|
+
* the {operations} form.
|
|
578
|
+
* @param {import("./sync-client-types.js").ModelSyncDeclarationConfig["track"]} track - Declared track value.
|
|
579
|
+
* @returns {import("./sync-client-types.js").SyncClientResourceConfig["track"]} Normalized track value.
|
|
580
|
+
*/
|
|
581
|
+
function normalizedTrack(track) {
|
|
582
|
+
if (Array.isArray(track)) return {operations: track}
|
|
583
|
+
|
|
584
|
+
return track
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Builds a framework-owned sync endpoint POSTer over the configured transport.
|
|
589
|
+
* @param {{path: string, transport: import("../configuration-types.js").VelociousSyncClientTransport}} args - Poster args.
|
|
590
|
+
* @returns {(payload: Record<string, ?>) => Promise<?>} Sync endpoint POSTer.
|
|
591
|
+
*/
|
|
592
|
+
function transportPoster({path, transport}) {
|
|
593
|
+
return async (payload) => {
|
|
594
|
+
const response = await transport.post(path, payload)
|
|
595
|
+
|
|
596
|
+
if (!response || typeof response.json !== "function") {
|
|
597
|
+
throw new Error(`sync.client transport.post must resolve to a response with a json() method for ${path} (like the frontend-model websocket client)`)
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
return await response.json()
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Lazily builds (and memoizes per configuration) the sync client derived from the
|
|
606
|
+
* app's Velocious configuration and registers it as the current sync client.
|
|
607
|
+
* @param {Configuration} [configuration] - Configuration owning the registered models and the sync.client block. Defaults to the current configuration.
|
|
608
|
+
* @returns {SyncClient} Memoized sync client for the configuration.
|
|
609
|
+
*/
|
|
610
|
+
export function syncClient(configuration = Configuration.current()) {
|
|
611
|
+
let client = syncClientsByConfiguration.get(configuration)
|
|
612
|
+
|
|
613
|
+
if (!client) {
|
|
614
|
+
client = SyncClient.fromConfiguration(configuration)
|
|
615
|
+
syncClientsByConfiguration.set(configuration, client)
|
|
616
|
+
client.setCurrent()
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
return client
|
|
620
|
+
}
|
|
621
|
+
|
|
426
622
|
/**
|
|
427
623
|
* Declares a sync scope on the current sync client.
|
|
428
624
|
* @param {import("../database/query/model-class-query.js").default<?>} query - Query declaring the sync scope.
|