velocious 1.0.502 → 1.0.503
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 +2 -0
- package/build/src/application.d.ts.map +1 -1
- package/build/src/application.js +3 -1
- package/build/src/sync/sync-change-fanout.d.ts +71 -0
- package/build/src/sync/sync-change-fanout.d.ts.map +1 -0
- package/build/src/sync/sync-change-fanout.js +54 -0
- package/build/src/sync/sync-client-types.d.ts +4 -0
- 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.map +1 -1
- package/build/src/sync/sync-client.js +7 -3
- package/build/src/sync/sync-envelope-replay-service.d.ts +15 -4
- package/build/src/sync/sync-envelope-replay-service.d.ts.map +1 -1
- package/build/src/sync/sync-envelope-replay-service.js +60 -42
- package/build/src/sync/sync-publish-suppression.d.ts +31 -0
- package/build/src/sync/sync-publish-suppression.d.ts.map +1 -0
- package/build/src/sync/sync-publish-suppression.js +54 -0
- package/build/src/sync/sync-publisher-types.d.ts +146 -0
- package/build/src/sync/sync-publisher-types.d.ts.map +1 -0
- package/build/src/sync/sync-publisher-types.js +3 -0
- package/build/src/sync/sync-publisher.d.ts +137 -0
- package/build/src/sync/sync-publisher.d.ts.map +1 -0
- package/build/src/sync/sync-publisher.js +305 -0
- package/build/src/testing/test-runner.d.ts +8 -0
- package/build/src/testing/test-runner.d.ts.map +1 -1
- package/build/src/testing/test-runner.js +64 -2
- package/build/sync/sync-change-fanout.js +58 -0
- package/build/sync/sync-client-types.js +1 -0
- package/build/sync/sync-client.js +7 -2
- package/build/sync/sync-envelope-replay-service.js +60 -41
- package/build/sync/sync-publish-suppression.js +59 -0
- package/build/sync/sync-publisher-types.js +64 -0
- package/build/sync/sync-publisher.js +351 -0
- package/build/testing/test-runner.js +69 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/application.js +2 -0
- package/src/sync/sync-change-fanout.js +58 -0
- package/src/sync/sync-client-types.js +1 -0
- package/src/sync/sync-client.js +7 -2
- package/src/sync/sync-envelope-replay-service.js +60 -41
- package/src/sync/sync-publish-suppression.js +59 -0
- package/src/sync/sync-publisher-types.js +64 -0
- package/src/sync/sync-publisher.js +351 -0
- package/src/testing/test-runner.js +69 -1
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* One declarative broadcast delivered through an injected broadcaster. The
|
|
5
|
+
* replay service resolves it with replay args ({actor, applyResult, mutation,
|
|
6
|
+
* ...}) and the sync publisher with publish args
|
|
7
|
+
* ({@link import("./sync-publisher-types.js").SyncPublishBroadcastArgs}).
|
|
8
|
+
* @template [Args=Record<string, ?>]
|
|
9
|
+
* @typedef {object} DeclaredBroadcast
|
|
10
|
+
* @property {string | ((args: Args) => string)} channel - Channel name or resolver.
|
|
11
|
+
* @property {(args: Args) => Record<string, ?>} broadcastParams - Channel routing params.
|
|
12
|
+
* @property {(args: Args) => ?} body - Broadcast body.
|
|
13
|
+
* @property {(args: Args) => boolean} [when] - Optional gate; skipped when it returns false.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Upserts one sync/change row through the shared sync-model contract: an
|
|
18
|
+
* existing row for the resource identity is reassigned and re-sequenced
|
|
19
|
+
* through `advanceServerSequence()` (the change-feed sequence contract) so
|
|
20
|
+
* feed cursors pick the change up again; otherwise a new row is created
|
|
21
|
+
* (creates allocate their sequence through the model's own hooks). Shared by
|
|
22
|
+
* the replay service's model-backed persistence and the server sync
|
|
23
|
+
* publisher.
|
|
24
|
+
* @param {{attributes: Record<string, ?>, existingSync: ?, syncModel: ?}} args - Row attributes, existing row for the resource identity, and the sync model.
|
|
25
|
+
* @returns {Promise<?>} Upserted sync row.
|
|
26
|
+
*/
|
|
27
|
+
export async function upsertSyncRow({attributes, existingSync, syncModel}) {
|
|
28
|
+
if (existingSync) {
|
|
29
|
+
existingSync.assign(attributes)
|
|
30
|
+
await existingSync.advanceServerSequence()
|
|
31
|
+
await existingSync.save()
|
|
32
|
+
|
|
33
|
+
return existingSync
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return await syncModel.create(attributes)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Delivers declarative broadcasts through an injected broadcaster: each
|
|
41
|
+
* broadcast's `when` gate is checked, then channel/params/body are resolved
|
|
42
|
+
* from the caller's args. Shared by the replay service's default
|
|
43
|
+
* afterReplayMutation and the server sync publisher.
|
|
44
|
+
* @template Args
|
|
45
|
+
* @param {{args: Args, broadcaster: (broadcast: {channel: string, params: Record<string, ?>, body: ?}) => Promise<void>, broadcasts: Array<DeclaredBroadcast<Args>>}} deliveryArgs - Broadcast resolver args, broadcaster, and declared broadcasts.
|
|
46
|
+
* @returns {Promise<void>}
|
|
47
|
+
*/
|
|
48
|
+
export async function deliverDeclaredBroadcasts({args, broadcaster, broadcasts}) {
|
|
49
|
+
for (const broadcast of broadcasts) {
|
|
50
|
+
if (broadcast.when && !broadcast.when(args)) continue
|
|
51
|
+
|
|
52
|
+
await broadcaster({
|
|
53
|
+
body: broadcast.body(args),
|
|
54
|
+
channel: typeof broadcast.channel === "function" ? broadcast.channel(args) : broadcast.channel,
|
|
55
|
+
params: broadcast.broadcastParams(args)
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
* @property {import("./sync-api-client-types.js").SyncResourceConfig["findRecord"]} [findRecord] - Custom pull-apply record resolver.
|
|
44
44
|
* @property {import("./sync-api-client-types.js").SyncResourceConfig["findRecordForDelete"]} [findRecordForDelete] - Custom pull-apply delete resolver.
|
|
45
45
|
* @property {string[]} [localOnlyAttributes] - Extra local-only attributes merged with the derived primary key, createdAt/updatedAt, and sync bookkeeping attributes.
|
|
46
|
+
* @property {import("./sync-publisher-types.js").SyncPublishDeclaration} [publish] - Server-side publish declaration consumed by `SyncPublisher.fromConfiguration(...)` on the backend; ignored by the client.
|
|
46
47
|
* @property {"upsert" | ((args: {operation: "create" | "update" | "destroy", record: ?}) => string)} [syncType] - Sync type flag or mapper (see SyncClientResourceConfig).
|
|
47
48
|
* @property {boolean | Array<"create" | "update" | "destroy"> | {operations: Array<"create" | "update" | "destroy">}} [track] - Automatic mutation tracking policy; an array is shorthand for {operations}. On by default (creates and updates queue automatically); `false` opts the model out (for models written by non-user flows), `true` adds destroys.
|
|
48
49
|
* @property {(args: {operation: "create" | "update" | "destroy", record: ?}) => Record<string, ?>} [trackedData] - Custom queued-payload builder for tracked mutations.
|
package/src/sync/sync-client.js
CHANGED
|
@@ -661,11 +661,16 @@ function resourceConfigFromSyncDeclaration({declaration, modelClass, resourceTyp
|
|
|
661
661
|
throw new Error(`${resourceType} static sync must be true or a sync declaration object, got: ${String(declaration)}`)
|
|
662
662
|
}
|
|
663
663
|
|
|
664
|
-
const {afterApply, attributes, booleanAttributes, findRecord, findRecordForDelete, localOnlyAttributes, realtime, syncType, track, trackedData, ...restDeclaration} = normalizedDeclaration
|
|
664
|
+
const {afterApply, attributes, booleanAttributes, findRecord, findRecordForDelete, localOnlyAttributes, publish, realtime, syncType, track, trackedData, ...restDeclaration} = normalizedDeclaration
|
|
665
665
|
const unknownKeys = Object.keys(restDeclaration)
|
|
666
666
|
|
|
667
|
+
// `publish` is the server-side half of the shared `static sync` declaration
|
|
668
|
+
// (consumed by SyncPublisher on the backend) - the client derives nothing
|
|
669
|
+
// from it, but models declared once for both sides must stay valid here.
|
|
670
|
+
void publish
|
|
671
|
+
|
|
667
672
|
if (unknownKeys.length > 0) {
|
|
668
|
-
throw new Error(`${resourceType} static sync received unknown keys: ${unknownKeys.join(", ")} (supported: afterApply, attributes, booleanAttributes, findRecord, findRecordForDelete, localOnlyAttributes, realtime, syncType, track, trackedData)`)
|
|
673
|
+
throw new Error(`${resourceType} static sync received unknown keys: ${unknownKeys.join(", ")} (supported: afterApply, attributes, booleanAttributes, findRecord, findRecordForDelete, localOnlyAttributes, publish, realtime, syncType, track, trackedData)`)
|
|
669
674
|
}
|
|
670
675
|
if (syncType !== undefined && typeof syncType !== "function" && syncType !== "upsert") {
|
|
671
676
|
throw new Error(`${resourceType} static sync syncType must be a function or the string "upsert", got: ${String(syncType)}`)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
+
import {deliverDeclaredBroadcasts, upsertSyncRow} from "./sync-change-fanout.js"
|
|
4
|
+
import {markServerApply} from "./sync-publish-suppression.js"
|
|
3
5
|
import {resolveFrontendModelResourceClass} from "../frontend-models/resource-definition.js"
|
|
4
6
|
import SyncReplayUpsertApplier from "./sync-replay-upsert-applier.js"
|
|
5
7
|
import {ValidationError} from "../database/record/index.js"
|
|
@@ -485,7 +487,11 @@ export default class SyncEnvelopeReplayService {
|
|
|
485
487
|
}
|
|
486
488
|
|
|
487
489
|
/**
|
|
488
|
-
* Applies a routed delete mutation.
|
|
490
|
+
* Applies a routed delete mutation. The record is marked as a server apply
|
|
491
|
+
* for the duration of the replay-owned destroy - an active SyncPublisher
|
|
492
|
+
* never publishes the replayed delete a second time (the replay owns its
|
|
493
|
+
* own persist and broadcasts), while later server-side writes to the same
|
|
494
|
+
* instance publish normally again.
|
|
489
495
|
* @param {object} args - Options.
|
|
490
496
|
* @param {import("./sync-envelope-replay-service.js").SyncReplayMutation} args.mutation - Normalized replay mutation.
|
|
491
497
|
* @param {import("../frontend-model-resource/base-resource.js").default} args.resource - Routed resource instance.
|
|
@@ -496,7 +502,13 @@ export default class SyncEnvelopeReplayService {
|
|
|
496
502
|
|
|
497
503
|
if (!record) return {created: false, deleted: false, record: null}
|
|
498
504
|
|
|
499
|
-
|
|
505
|
+
const releaseServerApply = markServerApply(record)
|
|
506
|
+
|
|
507
|
+
try {
|
|
508
|
+
await record.destroy()
|
|
509
|
+
} finally {
|
|
510
|
+
releaseServerApply()
|
|
511
|
+
}
|
|
500
512
|
|
|
501
513
|
return {created: false, deleted: true, record}
|
|
502
514
|
}
|
|
@@ -506,8 +518,12 @@ export default class SyncEnvelopeReplayService {
|
|
|
506
518
|
* assigned and saved onto the found record (the record layer owns value
|
|
507
519
|
* casting and validation), and missing records are created with the
|
|
508
520
|
* client-generated primary key plus a save-then-check membership check.
|
|
509
|
-
*
|
|
510
|
-
*
|
|
521
|
+
* Written records are marked as server applies for the duration of the
|
|
522
|
+
* replay-owned write - an active SyncPublisher never publishes the replayed
|
|
523
|
+
* mutation a second time (the replay owns its own persist and broadcasts),
|
|
524
|
+
* while later server-side writes to the same instance publish normally
|
|
525
|
+
* again. Model validation failures become client-safe per-sync failures
|
|
526
|
+
* carrying the translated validation message.
|
|
511
527
|
* @param {object} args - Options.
|
|
512
528
|
* @param {Record<string, ?>} args.context - Replay context.
|
|
513
529
|
* @param {import("./sync-envelope-replay-service.js").SyncReplayMutation} args.mutation - Normalized replay mutation.
|
|
@@ -523,8 +539,14 @@ export default class SyncEnvelopeReplayService {
|
|
|
523
539
|
let created = false
|
|
524
540
|
|
|
525
541
|
if (existingRecord) {
|
|
526
|
-
existingRecord
|
|
527
|
-
|
|
542
|
+
const releaseServerApply = markServerApply(existingRecord)
|
|
543
|
+
|
|
544
|
+
try {
|
|
545
|
+
existingRecord.assign(attributes)
|
|
546
|
+
await this.saveRoutedReplayRecord(existingRecord)
|
|
547
|
+
} finally {
|
|
548
|
+
releaseServerApply()
|
|
549
|
+
}
|
|
528
550
|
} else {
|
|
529
551
|
record = await this.createRoutedReplayRecord({attributes, mutation, resource})
|
|
530
552
|
created = true
|
|
@@ -588,7 +610,10 @@ export default class SyncEnvelopeReplayService {
|
|
|
588
610
|
}
|
|
589
611
|
|
|
590
612
|
/**
|
|
591
|
-
* Creates the routed record with the client-generated primary key
|
|
613
|
+
* Creates the routed record with the client-generated primary key (marked
|
|
614
|
+
* as a server apply for the duration of the create - including the
|
|
615
|
+
* membership-check compensation destroy - so an active SyncPublisher never
|
|
616
|
+
* publishes the replayed create a second time), then
|
|
592
617
|
* verifies create-scope membership when an ability is configured: records
|
|
593
618
|
* outside the ability's create scope are destroyed again and fail the sync
|
|
594
619
|
* with the resource-declared reason. A record that already exists outside
|
|
@@ -611,33 +636,39 @@ export default class SyncEnvelopeReplayService {
|
|
|
611
636
|
})
|
|
612
637
|
}
|
|
613
638
|
|
|
614
|
-
|
|
615
|
-
|
|
639
|
+
await ModelClass.ensureInitialized()
|
|
640
|
+
|
|
641
|
+
const record = new ModelClass({[primaryKey]: mutation.resourceId, ...attributes})
|
|
642
|
+
const releaseServerApply = markServerApply(record)
|
|
616
643
|
|
|
617
644
|
try {
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
645
|
+
try {
|
|
646
|
+
await record.save()
|
|
647
|
+
} catch (error) {
|
|
648
|
+
throw this.routedReplaySaveError(error)
|
|
649
|
+
}
|
|
622
650
|
|
|
623
|
-
|
|
651
|
+
const ability = resource.ability
|
|
624
652
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
653
|
+
if (ability) {
|
|
654
|
+
const memberIds = await ModelClass
|
|
655
|
+
.accessibleFor(resource.syncAbilityAction("create"), ability)
|
|
656
|
+
.where({[primaryKey]: record.id()})
|
|
657
|
+
.pluck(primaryKey)
|
|
630
658
|
|
|
631
|
-
|
|
632
|
-
|
|
659
|
+
if (memberIds.length === 0) {
|
|
660
|
+
await record.destroy()
|
|
633
661
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
662
|
+
throw VelociousError.safe(`Sync create denied for: ${mutation.resourceType}.`, {
|
|
663
|
+
code: resource.syncAuthorizationFailureReason({action: "create", mutation}) || "access-denied"
|
|
664
|
+
})
|
|
665
|
+
}
|
|
637
666
|
}
|
|
638
|
-
}
|
|
639
667
|
|
|
640
|
-
|
|
668
|
+
return record
|
|
669
|
+
} finally {
|
|
670
|
+
releaseServerApply()
|
|
671
|
+
}
|
|
641
672
|
}
|
|
642
673
|
|
|
643
674
|
/**
|
|
@@ -709,13 +740,9 @@ export default class SyncEnvelopeReplayService {
|
|
|
709
740
|
const existingClientUpdatedAt = this.existingReplaySyncClientUpdatedAt(existingSync)
|
|
710
741
|
|
|
711
742
|
if (existingClientUpdatedAt && mutation.clientUpdatedAt <= existingClientUpdatedAt) return
|
|
712
|
-
|
|
713
|
-
existingSync.assign(attributes)
|
|
714
|
-
await existingSync.advanceServerSequence()
|
|
715
|
-
await existingSync.save()
|
|
716
|
-
} else {
|
|
717
|
-
await this.syncModel.create(attributes)
|
|
718
743
|
}
|
|
744
|
+
|
|
745
|
+
await upsertSyncRow({attributes, existingSync, syncModel: this.syncModel})
|
|
719
746
|
}
|
|
720
747
|
|
|
721
748
|
/**
|
|
@@ -748,15 +775,7 @@ export default class SyncEnvelopeReplayService {
|
|
|
748
775
|
// would fan out stale side effects (or crash on the default null applyResult).
|
|
749
776
|
if (!args.shouldApply) return
|
|
750
777
|
|
|
751
|
-
|
|
752
|
-
if (broadcast.when && !broadcast.when(args)) continue
|
|
753
|
-
|
|
754
|
-
await this.broadcaster({
|
|
755
|
-
body: broadcast.body(args),
|
|
756
|
-
channel: typeof broadcast.channel === "function" ? broadcast.channel(args) : broadcast.channel,
|
|
757
|
-
params: broadcast.broadcastParams(args)
|
|
758
|
-
})
|
|
759
|
-
}
|
|
778
|
+
await deliverDeclaredBroadcasts({args, broadcaster: this.broadcaster, broadcasts: this.broadcasts})
|
|
760
779
|
}
|
|
761
780
|
}
|
|
762
781
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Records currently being written from an already-synced source (sync replay
|
|
5
|
+
* applies, importers applying device-origin data). Module-scoped so the
|
|
6
|
+
* framework's replay apply can mark records without holding a publisher
|
|
7
|
+
* instance.
|
|
8
|
+
* @type {WeakSet<object>}
|
|
9
|
+
*/
|
|
10
|
+
const serverApplyRecords = new WeakSet()
|
|
11
|
+
|
|
12
|
+
let withoutPublishingDepth = 0
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Marks one record as being written from an already-synced source so server
|
|
16
|
+
* publish callbacks skip it (record-precise suppression). The framework's
|
|
17
|
+
* routed sync replay apply uses this internally around every applied write, so
|
|
18
|
+
* replayed device mutations never publish a second, server-origin sync change.
|
|
19
|
+
* @param {?} record - Server model record about to be written.
|
|
20
|
+
* @returns {() => void} Release callback re-enabling publishing for the record.
|
|
21
|
+
*/
|
|
22
|
+
export function markServerApply(record) {
|
|
23
|
+
serverApplyRecords.add(record)
|
|
24
|
+
|
|
25
|
+
return () => serverApplyRecords.delete(record)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Runs a callback with server publish callbacks suppressed - for code applying
|
|
30
|
+
* already-synced data outside the framework's replay apply (importers,
|
|
31
|
+
* backfills applying device-origin rows). Suppression covers the whole async
|
|
32
|
+
* duration of the callback (nested calls stack) and is process-wide while it
|
|
33
|
+
* runs: mutations from concurrently running requests are also suppressed for
|
|
34
|
+
* that window, so prefer `markServerApply(record)` when other flows can
|
|
35
|
+
* interleave.
|
|
36
|
+
* @template T
|
|
37
|
+
* @param {() => Promise<T> | T} callback - Work whose model writes should not publish sync changes.
|
|
38
|
+
* @returns {Promise<T>} The callback result.
|
|
39
|
+
*/
|
|
40
|
+
export async function withoutPublishing(callback) {
|
|
41
|
+
withoutPublishingDepth++
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
return await callback()
|
|
45
|
+
} finally {
|
|
46
|
+
withoutPublishingDepth--
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Whether server publishing is currently suppressed for a record: either the
|
|
52
|
+
* record was marked as a server apply (`markServerApply`) or a
|
|
53
|
+
* `withoutPublishing` callback is running.
|
|
54
|
+
* @param {?} record - Server model record being written.
|
|
55
|
+
* @returns {boolean} Whether publishing is suppressed for the record.
|
|
56
|
+
*/
|
|
57
|
+
export function isPublishingSuppressed(record) {
|
|
58
|
+
return withoutPublishingDepth > 0 || serverApplyRecords.has(record)
|
|
59
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Args resolved against a publish declaration's broadcasts after a published
|
|
5
|
+
* server-side mutation commits.
|
|
6
|
+
* @typedef {object} SyncPublishBroadcastArgs
|
|
7
|
+
* @property {Record<string, ?>} data - Payload snapshotted through the declaration's `serialize(record)` at mutation time.
|
|
8
|
+
* @property {"create" | "update" | "destroy"} operation - Mutation operation that published.
|
|
9
|
+
* @property {?} record - Mutated server model record.
|
|
10
|
+
* @property {string} resourceId - Published resource id.
|
|
11
|
+
* @property {string} resourceType - Published resource type.
|
|
12
|
+
* @property {?} syncRow - Upserted sync/change row.
|
|
13
|
+
* @property {string} syncType - Published sync type ("update" for creates/updates, "delete" for destroys).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* One declarative broadcast fanned out after a published server-side mutation
|
|
18
|
+
* commits — same shape the replay service's injected broadcaster consumes.
|
|
19
|
+
* @typedef {object} SyncPublishBroadcast
|
|
20
|
+
* @property {string | ((args: SyncPublishBroadcastArgs) => string)} channel - Channel name or resolver.
|
|
21
|
+
* @property {(args: SyncPublishBroadcastArgs) => Record<string, ?>} broadcastParams - Channel routing params.
|
|
22
|
+
* @property {(args: SyncPublishBroadcastArgs) => ?} body - Broadcast body.
|
|
23
|
+
* @property {(args: SyncPublishBroadcastArgs) => boolean} [when] - Optional gate; skipped when it returns false.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Server-side publish declaration on a model's `static sync`, consumed by
|
|
28
|
+
* `SyncPublisher.fromConfiguration(...)`. Publishing is on for models
|
|
29
|
+
* declaring it (server-side creates and updates write to the sync change
|
|
30
|
+
* feed and broadcast automatically once their transaction commits);
|
|
31
|
+
* `publish: false` opts a model out explicitly.
|
|
32
|
+
* @typedef {object} SyncPublishDeclarationConfig
|
|
33
|
+
* @property {(record: ?) => Record<string, ?> | Promise<Record<string, ?>>} serialize - Builds the published payload snapshot from the mutated record (snapshotted at mutation time).
|
|
34
|
+
* @property {(record: ?) => string | number | null | Promise<string | number | null>} [eventId] - Resolves the event scope persisted to the sync row's event_id column.
|
|
35
|
+
* @property {SyncPublishBroadcast[]} [broadcasts] - Declarative broadcasts fanned out after the sync row is upserted.
|
|
36
|
+
* @property {Array<"create" | "update" | "destroy">} [operations] - Published operations. Defaults to creates and updates; destroys are opt-in because a server destroy is often cleanup rather than a synced delete.
|
|
37
|
+
* @property {string} [resourceType] - Published resource type. Defaults to the model name.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/** @typedef {false | SyncPublishDeclarationConfig} SyncPublishDeclaration */
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Options for building a sync publisher. Published resources are derived from
|
|
44
|
+
* the configuration's registered models (`static sync` publish declarations).
|
|
45
|
+
* @typedef {object} SyncPublisherOptions
|
|
46
|
+
* @property {string} [actorForeignKeyColumn] - Sync model column linking rows to a device actor. Published server-origin rows set it to null (no device to echo). Defaults to "authentication_token_id".
|
|
47
|
+
* @property {(broadcast: {channel: string, params: Record<string, ?>, body: ?}) => Promise<void>} [broadcaster] - Delivers declared broadcasts. Defaults to the configuration's channel broadcast.
|
|
48
|
+
* @property {import("../configuration.js").default} [configuration] - Configuration owning the registered models. Defaults to the current configuration.
|
|
49
|
+
* @property {(error: Error) => void} [onError] - Reports post-commit publish failures. Defaults to loud logging.
|
|
50
|
+
* @property {?} [syncModel] - Sync/change model override. Defaults to the registered "Sync" model.
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Internal derived publish policy for one resource — not an app-facing API.
|
|
55
|
+
* @typedef {object} SyncPublisherResourceConfig
|
|
56
|
+
* @property {SyncPublishBroadcast[] | undefined} broadcasts - Declared broadcasts.
|
|
57
|
+
* @property {SyncPublishDeclarationConfig["eventId"] | undefined} eventId - Event scope resolver.
|
|
58
|
+
* @property {?} modelClass - Server model class for this resource.
|
|
59
|
+
* @property {Array<"create" | "update" | "destroy">} operations - Published operations.
|
|
60
|
+
* @property {string} resourceType - Published resource type.
|
|
61
|
+
* @property {SyncPublishDeclarationConfig["serialize"]} serialize - Payload snapshot builder.
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
export {}
|