velocious 1.0.562 → 1.0.564
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/build/background-jobs/cron-expression.js +12 -13
- package/build/background-jobs/main.js +6 -6
- package/build/background-jobs/runner-graceful-shutdown.js +2 -3
- package/build/background-jobs/scheduler.js +3 -3
- package/build/background-jobs/worker.js +8 -9
- package/build/beacon/client.js +3 -4
- package/build/configuration-types.js +1 -0
- package/build/configuration.js +44 -3
- package/build/database/drivers/sqlite/web-persistence.js +2 -5
- package/build/database/pool/async-tracked-multi-connection.js +65 -15
- package/build/database/pool/base.js +23 -7
- package/build/database/query/index.js +21 -22
- package/build/database/query/model-class-query.js +5 -6
- package/build/database/record/attachments/normalize-input.js +9 -10
- package/build/database/record/attachments/store.js +2 -2
- package/build/database/record/auditing.js +7 -8
- package/build/database/record/index.js +4 -5
- package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +1 -2
- package/build/frontend-model-controller.js +49 -56
- package/build/frontend-models/base.js +17 -19
- package/build/frontend-models/outgoing-event-buffer.js +3 -4
- package/build/frontend-models/query.js +40 -44
- package/build/frontend-models/resource-definition.js +7 -8
- package/build/frontend-models/transport-serialization.js +10 -12
- package/build/frontend-models/websocket-channel.js +12 -13
- package/build/http-server/client/websocket-session.js +7 -8
- package/build/http-server/client-delivery-queue.js +167 -0
- package/build/http-server/server-client.js +9 -0
- package/build/http-server/websocket-event-log-store.js +4 -6
- package/build/http-server/websocket-events-host.js +42 -12
- package/build/http-server/worker-handler/in-process.js +53 -14
- package/build/http-server/worker-handler/index.js +88 -18
- package/build/http-server/worker-handler/worker-thread.js +2 -2
- package/build/plugins/sqljs-wasm-route.js +3 -5
- package/build/src/background-jobs/cron-expression.d.ts +0 -12
- package/build/src/background-jobs/cron-expression.d.ts.map +1 -1
- package/build/src/background-jobs/cron-expression.js +13 -13
- package/build/src/background-jobs/main.d.ts.map +1 -1
- package/build/src/background-jobs/main.js +7 -7
- package/build/src/background-jobs/runner-graceful-shutdown.d.ts +0 -6
- package/build/src/background-jobs/runner-graceful-shutdown.d.ts.map +1 -1
- package/build/src/background-jobs/runner-graceful-shutdown.js +3 -3
- package/build/src/background-jobs/scheduler.d.ts +0 -3
- package/build/src/background-jobs/scheduler.d.ts.map +1 -1
- package/build/src/background-jobs/scheduler.js +4 -4
- package/build/src/background-jobs/worker.d.ts +0 -8
- package/build/src/background-jobs/worker.d.ts.map +1 -1
- package/build/src/background-jobs/worker.js +9 -9
- package/build/src/beacon/client.d.ts +0 -4
- package/build/src/beacon/client.d.ts.map +1 -1
- package/build/src/beacon/client.js +4 -4
- package/build/src/configuration-types.d.ts +8 -0
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +2 -1
- package/build/src/configuration.d.ts +34 -1
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +40 -4
- package/build/src/database/drivers/sqlite/web-persistence.d.ts +0 -14
- package/build/src/database/drivers/sqlite/web-persistence.d.ts.map +1 -1
- package/build/src/database/drivers/sqlite/web-persistence.js +3 -3
- package/build/src/database/pool/async-tracked-multi-connection.d.ts +29 -1
- package/build/src/database/pool/async-tracked-multi-connection.d.ts.map +1 -1
- package/build/src/database/pool/async-tracked-multi-connection.js +57 -15
- package/build/src/database/pool/base.d.ts +53 -4
- package/build/src/database/pool/base.d.ts.map +1 -1
- package/build/src/database/pool/base.js +23 -6
- package/build/src/database/query/index.d.ts +18 -39
- package/build/src/database/query/index.d.ts.map +1 -1
- package/build/src/database/query/index.js +22 -22
- package/build/src/database/query/model-class-query.d.ts +0 -5
- package/build/src/database/query/model-class-query.d.ts.map +1 -1
- package/build/src/database/query/model-class-query.js +6 -6
- package/build/src/database/record/attachments/normalize-input.d.ts +0 -9
- package/build/src/database/record/attachments/normalize-input.d.ts.map +1 -1
- package/build/src/database/record/attachments/normalize-input.js +10 -10
- package/build/src/database/record/attachments/store.d.ts.map +1 -1
- package/build/src/database/record/attachments/store.js +2 -2
- package/build/src/database/record/auditing.d.ts +17 -24
- package/build/src/database/record/auditing.d.ts.map +1 -1
- package/build/src/database/record/auditing.js +8 -8
- package/build/src/database/record/index.d.ts +4 -4
- package/build/src/database/record/index.d.ts.map +1 -1
- package/build/src/database/record/index.js +5 -5
- package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts +0 -39
- package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts.map +1 -1
- package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.js +2 -2
- package/build/src/frontend-model-controller.d.ts +1 -1
- package/build/src/frontend-model-controller.d.ts.map +1 -1
- package/build/src/frontend-model-controller.js +50 -50
- package/build/src/frontend-models/base.d.ts.map +1 -1
- package/build/src/frontend-models/base.js +18 -18
- package/build/src/frontend-models/outgoing-event-buffer.d.ts.map +1 -1
- package/build/src/frontend-models/outgoing-event-buffer.js +3 -3
- package/build/src/frontend-models/query.d.ts +56 -37
- package/build/src/frontend-models/query.d.ts.map +1 -1
- package/build/src/frontend-models/query.js +39 -39
- package/build/src/frontend-models/resource-definition.d.ts +0 -7
- package/build/src/frontend-models/resource-definition.d.ts.map +1 -1
- package/build/src/frontend-models/resource-definition.js +8 -8
- package/build/src/frontend-models/transport-serialization.d.ts +0 -10
- package/build/src/frontend-models/transport-serialization.d.ts.map +1 -1
- package/build/src/frontend-models/transport-serialization.js +11 -11
- package/build/src/frontend-models/websocket-channel.d.ts +0 -12
- package/build/src/frontend-models/websocket-channel.d.ts.map +1 -1
- package/build/src/frontend-models/websocket-channel.js +13 -13
- package/build/src/http-server/client/websocket-session.d.ts.map +1 -1
- package/build/src/http-server/client/websocket-session.js +8 -8
- package/build/src/http-server/client-delivery-queue.d.ts +121 -0
- package/build/src/http-server/client-delivery-queue.d.ts.map +1 -0
- package/build/src/http-server/client-delivery-queue.js +152 -0
- package/build/src/http-server/server-client.d.ts +6 -0
- package/build/src/http-server/server-client.d.ts.map +1 -1
- package/build/src/http-server/server-client.js +10 -1
- package/build/src/http-server/websocket-event-log-store.d.ts +0 -14
- package/build/src/http-server/websocket-event-log-store.d.ts.map +1 -1
- package/build/src/http-server/websocket-event-log-store.js +5 -5
- package/build/src/http-server/websocket-events-host.d.ts +15 -4
- package/build/src/http-server/websocket-events-host.d.ts.map +1 -1
- package/build/src/http-server/websocket-events-host.js +39 -13
- package/build/src/http-server/worker-handler/in-process.d.ts +19 -1
- package/build/src/http-server/worker-handler/in-process.d.ts.map +1 -1
- package/build/src/http-server/worker-handler/in-process.js +50 -14
- package/build/src/http-server/worker-handler/index.d.ts +36 -4
- package/build/src/http-server/worker-handler/index.d.ts.map +1 -1
- package/build/src/http-server/worker-handler/index.js +82 -18
- package/build/src/http-server/worker-handler/worker-thread.js +3 -3
- package/build/src/plugins/sqljs-wasm-route.d.ts.map +1 -1
- package/build/src/plugins/sqljs-wasm-route.js +4 -4
- package/build/src/sync/conflict-strategy.d.ts +0 -15
- package/build/src/sync/conflict-strategy.d.ts.map +1 -1
- package/build/src/sync/conflict-strategy.js +3 -3
- package/build/src/sync/device-identity.d.ts +0 -45
- package/build/src/sync/device-identity.d.ts.map +1 -1
- package/build/src/sync/device-identity.js +4 -4
- package/build/src/sync/local-mutation-log.d.ts +0 -41
- package/build/src/sync/local-mutation-log.d.ts.map +1 -1
- package/build/src/sync/local-mutation-log.js +9 -9
- package/build/src/sync/offline-grant.d.ts +0 -26
- package/build/src/sync/offline-grant.d.ts.map +1 -1
- package/build/src/sync/offline-grant.js +4 -4
- package/build/src/sync/peer-mutation-bundle.d.ts +0 -14
- package/build/src/sync/peer-mutation-bundle.d.ts.map +1 -1
- package/build/src/sync/peer-mutation-bundle.js +3 -3
- package/build/src/sync/server-change-feed.d.ts.map +1 -1
- package/build/src/sync/server-change-feed.js +33 -33
- package/build/src/sync/sync-api-client.d.ts +0 -9
- package/build/src/sync/sync-api-client.d.ts.map +1 -1
- package/build/src/sync/sync-api-client.js +2 -2
- package/build/src/sync/sync-envelope-replay-service.d.ts +38 -21
- package/build/src/sync/sync-envelope-replay-service.d.ts.map +1 -1
- package/build/src/sync/sync-envelope-replay-service.js +18 -18
- package/build/src/sync/sync-resource-base.d.ts +0 -7
- package/build/src/sync/sync-resource-base.d.ts.map +1 -1
- package/build/src/sync/sync-resource-base.js +2 -2
- package/build/src/sync/sync-scope-store.d.ts +0 -9
- package/build/src/sync/sync-scope-store.d.ts.map +1 -1
- package/build/src/sync/sync-scope-store.js +10 -10
- package/build/src/testing/test-filter-parser.d.ts +0 -10
- package/build/src/testing/test-filter-parser.d.ts.map +1 -1
- package/build/src/testing/test-filter-parser.js +11 -11
- package/build/src/testing/test-runner.d.ts +25 -91
- package/build/src/testing/test-runner.d.ts.map +1 -1
- package/build/src/testing/test-runner.js +113 -98
- package/build/src/testing/test.d.ts +8 -0
- package/build/src/testing/test.d.ts.map +1 -1
- package/build/src/testing/test.js +9 -9
- package/build/src/utils/backtrace-cleaner.d.ts.map +1 -1
- package/build/src/utils/backtrace-cleaner.js +9 -9
- package/build/src/utils/model-scope.d.ts +0 -8
- package/build/src/utils/model-scope.d.ts.map +1 -1
- package/build/src/utils/model-scope.js +3 -3
- package/build/src/utils/ransack.d.ts +52 -19
- package/build/src/utils/ransack.d.ts.map +1 -1
- package/build/src/utils/ransack.js +26 -26
- package/build/sync/conflict-strategy.js +3 -6
- package/build/sync/device-identity.js +4 -9
- package/build/sync/local-mutation-log.js +9 -14
- package/build/sync/offline-grant.js +4 -7
- package/build/sync/peer-mutation-bundle.js +2 -4
- package/build/sync/server-change-feed.js +32 -34
- package/build/sync/sync-api-client.js +1 -2
- package/build/sync/sync-envelope-replay-service.js +17 -19
- package/build/sync/sync-resource-base.js +1 -2
- package/build/sync/sync-scope-store.js +9 -10
- package/build/testing/test-filter-parser.js +10 -11
- package/build/testing/test-runner.js +116 -108
- package/build/testing/test.js +8 -9
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/utils/backtrace-cleaner.js +8 -9
- package/build/utils/model-scope.js +3 -4
- package/build/utils/ransack.js +27 -34
- package/package.json +3 -1
- package/scripts/eslint-rules/typedefs-first.js +67 -0
- package/scripts/prewarm-chromedriver.js +26 -0
- package/src/background-jobs/cron-expression.js +12 -13
- package/src/background-jobs/main.js +6 -6
- package/src/background-jobs/runner-graceful-shutdown.js +2 -3
- package/src/background-jobs/scheduler.js +3 -3
- package/src/background-jobs/worker.js +8 -9
- package/src/beacon/client.js +3 -4
- package/src/configuration-types.js +1 -0
- package/src/configuration.js +44 -3
- package/src/database/drivers/sqlite/web-persistence.js +2 -5
- package/src/database/pool/async-tracked-multi-connection.js +65 -15
- package/src/database/pool/base.js +23 -7
- package/src/database/query/index.js +21 -22
- package/src/database/query/model-class-query.js +5 -6
- package/src/database/record/attachments/normalize-input.js +9 -10
- package/src/database/record/attachments/store.js +2 -2
- package/src/database/record/auditing.js +7 -8
- package/src/database/record/index.js +4 -5
- package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +1 -2
- package/src/frontend-model-controller.js +49 -56
- package/src/frontend-models/base.js +17 -19
- package/src/frontend-models/outgoing-event-buffer.js +3 -4
- package/src/frontend-models/query.js +40 -44
- package/src/frontend-models/resource-definition.js +7 -8
- package/src/frontend-models/transport-serialization.js +10 -12
- package/src/frontend-models/websocket-channel.js +12 -13
- package/src/http-server/client/websocket-session.js +7 -8
- package/src/http-server/client-delivery-queue.js +167 -0
- package/src/http-server/server-client.js +9 -0
- package/src/http-server/websocket-event-log-store.js +4 -6
- package/src/http-server/websocket-events-host.js +42 -12
- package/src/http-server/worker-handler/in-process.js +53 -14
- package/src/http-server/worker-handler/index.js +88 -18
- package/src/http-server/worker-handler/worker-thread.js +2 -2
- package/src/plugins/sqljs-wasm-route.js +3 -5
- package/src/sync/conflict-strategy.js +3 -6
- package/src/sync/device-identity.js +4 -9
- package/src/sync/local-mutation-log.js +9 -14
- package/src/sync/offline-grant.js +4 -7
- package/src/sync/peer-mutation-bundle.js +2 -4
- package/src/sync/server-change-feed.js +32 -34
- package/src/sync/sync-api-client.js +1 -2
- package/src/sync/sync-envelope-replay-service.js +17 -19
- package/src/sync/sync-resource-base.js +1 -2
- package/src/sync/sync-scope-store.js +9 -10
- package/src/testing/test-filter-parser.js +10 -11
- package/src/testing/test-runner.js +116 -108
- package/src/testing/test.js +8 -9
- package/src/utils/backtrace-cleaner.js +8 -9
- package/src/utils/model-scope.js +3 -4
- package/src/utils/ransack.js +27 -34
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
import Client from "../client/index.js"
|
|
4
|
+
import ClientDeliveryQueue from "../client-delivery-queue.js"
|
|
4
5
|
import dispatchChannelSubscribers from "./channel-subscriber-dispatch.js"
|
|
5
6
|
import Logger from "../../logger.js"
|
|
6
7
|
import websocketEventsHost from "../websocket-events-host.js"
|
|
@@ -23,7 +24,7 @@ export default class VelociousHttpServerInProcessHandler {
|
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Narrows the runtime value to the documented type.
|
|
26
|
-
* @type {Record<number, {httpClient: Client, serverClient: import("../server-client.js").default}>} */
|
|
27
|
+
* @type {Record<number, {deliveryQueue: ClientDeliveryQueue, httpClient: Client, serverClient: import("../server-client.js").default}>} */
|
|
27
28
|
this.clients = {}
|
|
28
29
|
|
|
29
30
|
/** @type {Set<Promise<void>>} */
|
|
@@ -56,25 +57,36 @@ export default class VelociousHttpServerInProcessHandler {
|
|
|
56
57
|
remoteAddress: serverClient.remoteAddress
|
|
57
58
|
})
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
60
|
+
const {maxBytes, maxFrames} = this.configuration.getWebsocketOutboundQueueLimits()
|
|
61
|
+
const deliveryQueue = new ClientDeliveryQueue({
|
|
62
|
+
clientCount,
|
|
63
|
+
maxBytes,
|
|
64
|
+
maxFrames,
|
|
65
|
+
onOverflow: (error) => {
|
|
66
|
+
deliveryQueue.destroy()
|
|
67
|
+
serverClient.destroy(error)
|
|
68
|
+
this._reportOutboundQueueOverflow({clientCount, error})
|
|
69
|
+
}
|
|
70
|
+
})
|
|
67
71
|
|
|
68
|
-
httpClient.events.on("output", (output) => {
|
|
72
|
+
httpClient.events.on("output", (output, {websocketFrame = false} = {}) => {
|
|
69
73
|
if (output !== null && output !== undefined) {
|
|
70
|
-
|
|
74
|
+
const delivery = () => serverClient.send(output)
|
|
75
|
+
const queued = websocketFrame
|
|
76
|
+
? deliveryQueue.enqueueFrame({
|
|
77
|
+
byteLength: typeof output === "string" ? Buffer.byteLength(output) : output.byteLength,
|
|
78
|
+
delivery
|
|
79
|
+
})
|
|
80
|
+
: deliveryQueue.enqueueControl(delivery)
|
|
81
|
+
|
|
82
|
+
void queued.catch((error) => {
|
|
71
83
|
this.logger.error(() => ["Failed to deliver client output", {clientCount}, error])
|
|
72
84
|
})
|
|
73
85
|
}
|
|
74
86
|
})
|
|
75
87
|
|
|
76
88
|
httpClient.events.on("file", ({filePath, sendBody, settle}) => {
|
|
77
|
-
void
|
|
89
|
+
void deliveryQueue.enqueueControl(async () => {
|
|
78
90
|
await settle(await serverClient.sendFile(filePath, sendBody))
|
|
79
91
|
}).catch((error) => {
|
|
80
92
|
this.logger.error(() => ["Failed to deliver file response", {clientCount, filePath}, error])
|
|
@@ -83,11 +95,12 @@ export default class VelociousHttpServerInProcessHandler {
|
|
|
83
95
|
})
|
|
84
96
|
|
|
85
97
|
httpClient.events.on("close", () => {
|
|
86
|
-
void
|
|
98
|
+
void deliveryQueue.enqueueControl(() => serverClient.end())
|
|
87
99
|
.finally(() => delete this.clients[clientCount])
|
|
88
100
|
})
|
|
89
101
|
|
|
90
102
|
serverClient.events.on("close", () => {
|
|
103
|
+
deliveryQueue.destroy()
|
|
91
104
|
const cleanup = httpClient.abortPendingFileResponses()
|
|
92
105
|
.catch((error) => {
|
|
93
106
|
this.logger.warn("Failed to abort file responses after client close", error)
|
|
@@ -100,7 +113,7 @@ export default class VelociousHttpServerInProcessHandler {
|
|
|
100
113
|
this.pendingClientCloseCleanups.add(cleanup)
|
|
101
114
|
})
|
|
102
115
|
|
|
103
|
-
this.clients[clientCount] = {httpClient, serverClient}
|
|
116
|
+
this.clients[clientCount] = {deliveryQueue, httpClient, serverClient}
|
|
104
117
|
|
|
105
118
|
// Create a message-port shim so ServerClient.onSocketData can route data
|
|
106
119
|
// to the in-process HTTP Client without needing a real worker thread.
|
|
@@ -118,6 +131,24 @@ export default class VelociousHttpServerInProcessHandler {
|
|
|
118
131
|
serverClient.listen()
|
|
119
132
|
}
|
|
120
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Reports a per-client outbound queue overflow.
|
|
136
|
+
* @param {object} args - Overflow details.
|
|
137
|
+
* @param {number} args.clientCount - Affected client.
|
|
138
|
+
* @param {Error} args.error - Overflow error.
|
|
139
|
+
* @returns {void}
|
|
140
|
+
*/
|
|
141
|
+
_reportOutboundQueueOverflow({clientCount, error}) {
|
|
142
|
+
const errorPayload = {
|
|
143
|
+
context: {clientCount, websocketOutboundQueueOverflow: true, workerCount: this.workerCount},
|
|
144
|
+
error
|
|
145
|
+
}
|
|
146
|
+
const errorEvents = this.configuration.getErrorEvents()
|
|
147
|
+
|
|
148
|
+
errorEvents.emit("framework-error", errorPayload)
|
|
149
|
+
errorEvents.emit("all-error", {...errorPayload, errorType: "framework-error"})
|
|
150
|
+
}
|
|
151
|
+
|
|
121
152
|
/**
|
|
122
153
|
* Runs stop.
|
|
123
154
|
* @returns {Promise<void>} */
|
|
@@ -158,6 +189,14 @@ export default class VelociousHttpServerInProcessHandler {
|
|
|
158
189
|
return this.configuration._broadcastToChannelLocal(channel, broadcastParams, body, {eventId})
|
|
159
190
|
}
|
|
160
191
|
|
|
192
|
+
/**
|
|
193
|
+
* Gets the configuration-wide V2 broadcast target shared by in-process handlers.
|
|
194
|
+
* @returns {import("../../configuration.js").default} - Shared configuration target.
|
|
195
|
+
*/
|
|
196
|
+
websocketV2BroadcastDispatchKey() {
|
|
197
|
+
return this.configuration
|
|
198
|
+
}
|
|
199
|
+
|
|
161
200
|
/**
|
|
162
201
|
* Runs dispatch websocket event.
|
|
163
202
|
* @param {object} args - Options object.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import {ensureError} from "typanic"
|
|
4
4
|
import Logger from "../../logger.js"
|
|
5
|
+
import ClientDeliveryQueue from "../client-delivery-queue.js"
|
|
5
6
|
import {Worker} from "worker_threads"
|
|
6
7
|
import websocketEventsHost from "../websocket-events-host.js"
|
|
7
8
|
|
|
@@ -59,7 +60,7 @@ export default class VelociousHttpServerWorker {
|
|
|
59
60
|
* @type {Map<number, {resolve: (snapshot: Record<string, ?>) => void}>} */
|
|
60
61
|
this._debugSnapshotRequests = new Map()
|
|
61
62
|
|
|
62
|
-
/** @type {Map<number,
|
|
63
|
+
/** @type {Map<number, ClientDeliveryQueue>} */
|
|
63
64
|
this._clientDeliveryQueues = new Map()
|
|
64
65
|
}
|
|
65
66
|
|
|
@@ -117,6 +118,7 @@ export default class VelociousHttpServerWorker {
|
|
|
117
118
|
if (!this.clients[clientCount] && !this._clientDeliveryQueues.has(clientCount)) return
|
|
118
119
|
|
|
119
120
|
delete this.clients[clientCount]
|
|
121
|
+
this._clientDeliveryQueues.get(clientCount)?.destroy()
|
|
120
122
|
this._clientDeliveryQueues.delete(clientCount)
|
|
121
123
|
this.worker?.postMessage({command: "clientAbort", clientCount})
|
|
122
124
|
}
|
|
@@ -163,6 +165,10 @@ export default class VelociousHttpServerWorker {
|
|
|
163
165
|
_closeAllClients() {
|
|
164
166
|
const clients = Object.values(this.clients)
|
|
165
167
|
this.clients = {}
|
|
168
|
+
const deliveryQueues = this._clientDeliveryQueues
|
|
169
|
+
this._clientDeliveryQueues = new Map()
|
|
170
|
+
|
|
171
|
+
for (const queue of deliveryQueues.values()) queue.destroy()
|
|
166
172
|
|
|
167
173
|
for (const client of clients) {
|
|
168
174
|
try {
|
|
@@ -182,6 +188,7 @@ export default class VelociousHttpServerWorker {
|
|
|
182
188
|
* @param {string} [data.filePath] - File path.
|
|
183
189
|
* @param {boolean} [data.sendBody] - Whether to send the file body.
|
|
184
190
|
* @param {number} [data.transferId] - File transfer id.
|
|
191
|
+
* @param {boolean} [data.websocketFrame] - Whether output is a completed WebSocket frame.
|
|
185
192
|
* @param {string} [data.channel] - Channel name.
|
|
186
193
|
* @param {number} [data.requestId] - Debug request id.
|
|
187
194
|
* @param {Record<string, ?>} [data.snapshot] - Worker debug snapshot.
|
|
@@ -219,7 +226,11 @@ export default class VelociousHttpServerWorker {
|
|
|
219
226
|
if (output !== null && output !== undefined) {
|
|
220
227
|
const outputLength = typeof output === "string" ? output.length : output.byteLength
|
|
221
228
|
|
|
222
|
-
|
|
229
|
+
const delivery = data.websocketFrame === true
|
|
230
|
+
? this.enqueueClientFrame(client, output)
|
|
231
|
+
: this.enqueueClientControl(client, () => client.send(output))
|
|
232
|
+
|
|
233
|
+
void delivery.then(() => {
|
|
223
234
|
this.logger.debug(() => ["Client output delivered", {
|
|
224
235
|
clientCount,
|
|
225
236
|
outputLength,
|
|
@@ -243,7 +254,7 @@ export default class VelociousHttpServerWorker {
|
|
|
243
254
|
return
|
|
244
255
|
}
|
|
245
256
|
|
|
246
|
-
void this.
|
|
257
|
+
void this.enqueueClientControl(client, async () => {
|
|
247
258
|
const result = await client.sendFile(filePath, sendBody !== false)
|
|
248
259
|
|
|
249
260
|
this.worker?.postMessage({command: "clientFileResult", result, transferId})
|
|
@@ -260,7 +271,7 @@ export default class VelociousHttpServerWorker {
|
|
|
260
271
|
return
|
|
261
272
|
}
|
|
262
273
|
|
|
263
|
-
void this.
|
|
274
|
+
void this.enqueueClientControl(client, () => client.end())
|
|
264
275
|
.finally(() => delete this.clients[client.clientCount])
|
|
265
276
|
} else if (command == "debugSnapshot") {
|
|
266
277
|
const {requestId, snapshot} = data
|
|
@@ -289,7 +300,12 @@ export default class VelociousHttpServerWorker {
|
|
|
289
300
|
throw new Error("Worker websocket v2-broadcast channel must be a string")
|
|
290
301
|
}
|
|
291
302
|
|
|
292
|
-
websocketEventsHost.broadcastV2({
|
|
303
|
+
websocketEventsHost.broadcastV2({
|
|
304
|
+
body,
|
|
305
|
+
broadcastParams: broadcastParams || {},
|
|
306
|
+
channel,
|
|
307
|
+
configuration: this.configuration
|
|
308
|
+
})
|
|
293
309
|
} else {
|
|
294
310
|
throw new Error(`Unknown command: ${command}`)
|
|
295
311
|
}
|
|
@@ -297,24 +313,70 @@ export default class VelociousHttpServerWorker {
|
|
|
297
313
|
|
|
298
314
|
/**
|
|
299
315
|
* Preserves socket output ordering for one client.
|
|
300
|
-
* @param {
|
|
316
|
+
* @param {import("../server-client.js").default} client - Client instance.
|
|
317
|
+
* @param {string | Uint8Array} output - Complete output buffer.
|
|
318
|
+
* @returns {Promise<void>} - Queued delivery.
|
|
319
|
+
*/
|
|
320
|
+
enqueueClientFrame(client, output) {
|
|
321
|
+
const byteLength = typeof output === "string" ? Buffer.byteLength(output) : output.byteLength
|
|
322
|
+
|
|
323
|
+
return this._deliveryQueueFor(client).enqueueFrame({
|
|
324
|
+
byteLength,
|
|
325
|
+
delivery: () => client.send(output)
|
|
326
|
+
})
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Preserves ordering for a delivery that retains no complete output frame.
|
|
331
|
+
* @param {import("../server-client.js").default} client - Client instance.
|
|
301
332
|
* @param {() => Promise<void>} delivery - Delivery operation.
|
|
302
333
|
* @returns {Promise<void>} - Queued delivery.
|
|
303
334
|
*/
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
.catch(() => {})
|
|
308
|
-
.then(delivery)
|
|
309
|
-
|
|
310
|
-
this._clientDeliveryQueues.set(clientCount, queued)
|
|
311
|
-
const clearQueue = () => {
|
|
312
|
-
if (this._clientDeliveryQueues.get(clientCount) === queued) this._clientDeliveryQueues.delete(clientCount)
|
|
313
|
-
}
|
|
335
|
+
enqueueClientControl(client, delivery) {
|
|
336
|
+
return this._deliveryQueueFor(client).enqueueControl(delivery)
|
|
337
|
+
}
|
|
314
338
|
|
|
315
|
-
|
|
339
|
+
/**
|
|
340
|
+
* Gets or creates one client's delivery queue.
|
|
341
|
+
* @param {import("../server-client.js").default} client - Client instance.
|
|
342
|
+
* @returns {ClientDeliveryQueue} - Client-owned delivery queue.
|
|
343
|
+
*/
|
|
344
|
+
_deliveryQueueFor(client) {
|
|
345
|
+
const existing = this._clientDeliveryQueues.get(client.clientCount)
|
|
346
|
+
if (existing) return existing
|
|
347
|
+
|
|
348
|
+
const {maxBytes, maxFrames} = this.configuration.getWebsocketOutboundQueueLimits()
|
|
349
|
+
const queue = new ClientDeliveryQueue({
|
|
350
|
+
clientCount: client.clientCount,
|
|
351
|
+
maxBytes,
|
|
352
|
+
maxFrames,
|
|
353
|
+
onOverflow: (error) => {
|
|
354
|
+
queue.destroy()
|
|
355
|
+
client.destroy(error)
|
|
356
|
+
this._reportOutboundQueueOverflow({clientCount: client.clientCount, error})
|
|
357
|
+
}
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
this._clientDeliveryQueues.set(client.clientCount, queue)
|
|
361
|
+
return queue
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Reports a per-client outbound queue overflow.
|
|
366
|
+
* @param {object} args - Overflow details.
|
|
367
|
+
* @param {number} args.clientCount - Affected client.
|
|
368
|
+
* @param {Error} args.error - Overflow error.
|
|
369
|
+
* @returns {void}
|
|
370
|
+
*/
|
|
371
|
+
_reportOutboundQueueOverflow({clientCount, error}) {
|
|
372
|
+
const errorPayload = {
|
|
373
|
+
context: {clientCount, websocketOutboundQueueOverflow: true, workerCount: this.workerCount},
|
|
374
|
+
error
|
|
375
|
+
}
|
|
376
|
+
const errorEvents = this.configuration.getErrorEvents()
|
|
316
377
|
|
|
317
|
-
|
|
378
|
+
errorEvents.emit("framework-error", errorPayload)
|
|
379
|
+
errorEvents.emit("all-error", {...errorPayload, errorType: "framework-error"})
|
|
318
380
|
}
|
|
319
381
|
|
|
320
382
|
/**
|
|
@@ -410,6 +472,14 @@ export default class VelociousHttpServerWorker {
|
|
|
410
472
|
this.worker.postMessage({body, broadcastParams, channel, command: "websocketV2Broadcast", eventId, createdAt})
|
|
411
473
|
}
|
|
412
474
|
|
|
475
|
+
/**
|
|
476
|
+
* Gets this worker's isolated V2 broadcast target.
|
|
477
|
+
* @returns {VelociousHttpServerWorker} - This worker handler.
|
|
478
|
+
*/
|
|
479
|
+
websocketV2BroadcastDispatchKey() {
|
|
480
|
+
return this
|
|
481
|
+
}
|
|
482
|
+
|
|
413
483
|
/**
|
|
414
484
|
* Runs register with events host.
|
|
415
485
|
* @returns {void} */
|
|
@@ -154,8 +154,8 @@ export default class VelociousHttpServerWorkerHandlerWorkerThread {
|
|
|
154
154
|
remoteAddress
|
|
155
155
|
})
|
|
156
156
|
|
|
157
|
-
client.events.on("output", (output) => {
|
|
158
|
-
this.parentPort.postMessage({command: "clientOutput", clientCount, output})
|
|
157
|
+
client.events.on("output", (output, {websocketFrame = false} = {}) => {
|
|
158
|
+
this.parentPort.postMessage({command: "clientOutput", clientCount, output, websocketFrame})
|
|
159
159
|
})
|
|
160
160
|
|
|
161
161
|
client.events.on("file", ({filePath, sendBody, settle}) => {
|
|
@@ -2,23 +2,21 @@ import {createRequire} from "node:module"
|
|
|
2
2
|
import path from "node:path"
|
|
3
3
|
import SqlJsWasmRouteController from "./sqljs-wasm-route-controller.js"
|
|
4
4
|
|
|
5
|
-
const require = createRequire(import.meta.url)
|
|
6
|
-
const sqlJsEntryPath = require.resolve("sql.js")
|
|
7
|
-
const sqlJsDistDirectory = path.dirname(sqlJsEntryPath)
|
|
8
|
-
|
|
9
5
|
/**
|
|
10
6
|
* InstallSqlJsWasmRouteArgs type.
|
|
11
7
|
* @typedef {object} InstallSqlJsWasmRouteArgs
|
|
12
8
|
* @property {import("../configuration.js").default} configuration - Velocious configuration instance.
|
|
13
9
|
* @property {string} [routePrefix] - Route prefix used for sql.js asset serving.
|
|
14
10
|
*/
|
|
15
|
-
|
|
16
11
|
/**
|
|
17
12
|
* SqlJsLocateFileFromBackendArgs type.
|
|
18
13
|
* @typedef {object} SqlJsLocateFileFromBackendArgs
|
|
19
14
|
* @property {string} backendBaseUrl - Backend base URL (for example `https://api.example.com`).
|
|
20
15
|
* @property {string} [routePrefix] - Route prefix used for sql.js asset serving.
|
|
21
16
|
*/
|
|
17
|
+
const require = createRequire(import.meta.url)
|
|
18
|
+
const sqlJsEntryPath = require.resolve("sql.js")
|
|
19
|
+
const sqlJsDistDirectory = path.dirname(sqlJsEntryPath)
|
|
22
20
|
|
|
23
21
|
/**
|
|
24
22
|
* Runs normalize route prefix.
|
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
|
|
3
|
-
const CONFLICT_STRATEGIES = new Set(["optimisticVersion", "serverWins", "lastWriterWins", "fieldThreeWay", "appendOnly"])
|
|
4
|
-
|
|
5
1
|
/**
|
|
6
2
|
* @typedef {null | string | number | boolean | unknown[] | Record<string, unknown>} SyncJsonValue
|
|
7
3
|
*/
|
|
8
|
-
|
|
9
4
|
/**
|
|
10
5
|
* @typedef {object} SyncConflictRecord
|
|
11
6
|
* @property {Record<string, SyncJsonValue>} attributes - Record attributes.
|
|
12
7
|
* @property {string | number | boolean | null} [version] - Record version value.
|
|
13
8
|
*/
|
|
14
|
-
|
|
15
9
|
/**
|
|
16
10
|
* @typedef {object} SyncConflictResult
|
|
17
11
|
* @property {Record<string, SyncJsonValue>} [attributes] - Attributes to apply when replay may continue.
|
|
@@ -19,6 +13,9 @@ const CONFLICT_STRATEGIES = new Set(["optimisticVersion", "serverWins", "lastWri
|
|
|
19
13
|
* @property {"applied" | "conflict" | "rejected"} status - Conflict decision.
|
|
20
14
|
* @property {string} strategy - Strategy that produced the decision.
|
|
21
15
|
*/
|
|
16
|
+
// @ts-check
|
|
17
|
+
|
|
18
|
+
const CONFLICT_STRATEGIES = new Set(["optimisticVersion", "serverWins", "lastWriterWins", "fieldThreeWay", "appendOnly"])
|
|
22
19
|
|
|
23
20
|
/**
|
|
24
21
|
* Evaluates a replay mutation against server/base state using a sync conflict strategy.
|
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
|
|
3
|
-
const ECDSA_P256_SHA256_ALGORITHM = "ECDSA-P256-SHA256"
|
|
4
|
-
const ECDSA_P256_SHA256_SIGNATURE_PREFIX = "ecdsa-p256-sha256-"
|
|
5
|
-
|
|
6
1
|
/**
|
|
7
2
|
* JSON Web Key used by the sync signing helpers.
|
|
8
3
|
* @typedef {import("node:crypto").webcrypto.JsonWebKey} SyncJsonWebKey
|
|
9
4
|
*/
|
|
10
|
-
|
|
11
5
|
/**
|
|
12
6
|
* Backend-signed device certificate payload.
|
|
13
7
|
* @typedef {object} DeviceCertificatePayload
|
|
@@ -18,7 +12,6 @@ const ECDSA_P256_SHA256_SIGNATURE_PREFIX = "ecdsa-p256-sha256-"
|
|
|
18
12
|
* @property {string} expiresAt - ISO timestamp after which the certificate is invalid.
|
|
19
13
|
* @property {string} issuedAt - ISO timestamp when the backend issued the certificate.
|
|
20
14
|
*/
|
|
21
|
-
|
|
22
15
|
/**
|
|
23
16
|
* Backend-signed device certificate envelope.
|
|
24
17
|
* @typedef {object} DeviceCertificate
|
|
@@ -26,7 +19,6 @@ const ECDSA_P256_SHA256_SIGNATURE_PREFIX = "ecdsa-p256-sha256-"
|
|
|
26
19
|
* @property {DeviceCertificatePayload} certificate - Certificate payload.
|
|
27
20
|
* @property {string} signature - Backend signature over the certificate payload.
|
|
28
21
|
*/
|
|
29
|
-
|
|
30
22
|
/**
|
|
31
23
|
* Sync mutation payload signed by a device.
|
|
32
24
|
* @typedef {object} SyncMutation
|
|
@@ -43,7 +35,6 @@ const ECDSA_P256_SHA256_SIGNATURE_PREFIX = "ecdsa-p256-sha256-"
|
|
|
43
35
|
* @property {Record<string, import("../configuration-types.js").FrontendModelSyncJsonValue>} [payload] - Domain command payload.
|
|
44
36
|
* @property {string} policyHash - Sync policy hash the mutation was checked against.
|
|
45
37
|
*/
|
|
46
|
-
|
|
47
38
|
/**
|
|
48
39
|
* Device-signed mutation envelope.
|
|
49
40
|
* @typedef {object} SignedSyncMutation
|
|
@@ -52,6 +43,10 @@ const ECDSA_P256_SHA256_SIGNATURE_PREFIX = "ecdsa-p256-sha256-"
|
|
|
52
43
|
* @property {SyncMutation} mutation - Mutation payload.
|
|
53
44
|
* @property {string} signature - Device signature over the mutation envelope.
|
|
54
45
|
*/
|
|
46
|
+
// @ts-check
|
|
47
|
+
|
|
48
|
+
const ECDSA_P256_SHA256_ALGORITHM = "ECDSA-P256-SHA256"
|
|
49
|
+
const ECDSA_P256_SHA256_SIGNATURE_PREFIX = "ecdsa-p256-sha256-"
|
|
55
50
|
|
|
56
51
|
/**
|
|
57
52
|
* Generates an ECDSA P-256 keypair exported as JWKs.
|
|
@@ -1,19 +1,8 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
|
|
3
|
-
const DEFAULT_STORAGE_KEY = "velocious.sync.localMutationLog"
|
|
4
|
-
const PENDING_STATUS_VALUES = /** @type {LocalMutationStatus[]} */ (["pending", "applied-locally", "peer-applied"])
|
|
5
|
-
const PENDING_STATUSES = new Set(PENDING_STATUS_VALUES)
|
|
6
|
-
const MUTATION_STATUSES = new Set([...PENDING_STATUSES, "conflict", "rejected", "synced"])
|
|
7
|
-
const TERMINAL_STATUSES = new Set(["rejected", "synced"])
|
|
8
|
-
/** @type {Map<string, Promise<unknown>>} */
|
|
9
|
-
const STORAGE_KEY_LOCKS = new Map()
|
|
10
|
-
|
|
11
1
|
/**
|
|
12
2
|
* Local mutation log record query options.
|
|
13
3
|
* @typedef {object} LocalMutationLogRecordsOptions
|
|
14
4
|
* @property {LocalMutationStatus[]} [statuses] - Optional status filter.
|
|
15
5
|
*/
|
|
16
|
-
|
|
17
6
|
/**
|
|
18
7
|
* Local mutation log row-oriented storage adapter.
|
|
19
8
|
*
|
|
@@ -28,19 +17,16 @@ const STORAGE_KEY_LOCKS = new Map()
|
|
|
28
17
|
* @property {(storageKey: string, options?: LocalMutationLogRecordsOptions) => Promise<LocalMutationLogRecord[]> | LocalMutationLogRecord[]} records - Reads log records.
|
|
29
18
|
* @property {(storageKey: string, record: LocalMutationLogRecord) => Promise<void> | void} updateRecord - Replaces one log record.
|
|
30
19
|
*/
|
|
31
|
-
|
|
32
20
|
/**
|
|
33
21
|
* Local sync mutation dependency metadata.
|
|
34
22
|
* @typedef {object} LocalMutationDependency
|
|
35
23
|
* @property {string} clientMutationId - Client mutation id this mutation depends on.
|
|
36
24
|
* @property {string} model - Dependent model/resource name.
|
|
37
25
|
*/
|
|
38
|
-
|
|
39
26
|
/**
|
|
40
27
|
* Local mutation log status.
|
|
41
28
|
* @typedef {"pending" | "applied-locally" | "peer-applied" | "conflict" | "rejected" | "synced"} LocalMutationStatus
|
|
42
29
|
* */
|
|
43
|
-
|
|
44
30
|
/**
|
|
45
31
|
* Local mutation log record.
|
|
46
32
|
* @typedef {object} LocalMutationLogRecord
|
|
@@ -53,6 +39,15 @@ const STORAGE_KEY_LOCKS = new Map()
|
|
|
53
39
|
* @property {Record<string, import("../configuration-types.js").FrontendModelSyncJsonValue>} [syncResult] - Backend replay/result metadata.
|
|
54
40
|
* @property {string} updatedAt - ISO timestamp when the record was last changed.
|
|
55
41
|
*/
|
|
42
|
+
// @ts-check
|
|
43
|
+
|
|
44
|
+
const DEFAULT_STORAGE_KEY = "velocious.sync.localMutationLog"
|
|
45
|
+
const PENDING_STATUS_VALUES = /** @type {LocalMutationStatus[]} */ (["pending", "applied-locally", "peer-applied"])
|
|
46
|
+
const PENDING_STATUSES = new Set(PENDING_STATUS_VALUES)
|
|
47
|
+
const MUTATION_STATUSES = new Set([...PENDING_STATUSES, "conflict", "rejected", "synced"])
|
|
48
|
+
const TERMINAL_STATUSES = new Set(["rejected", "synced"])
|
|
49
|
+
/** @type {Map<string, Promise<unknown>>} */
|
|
50
|
+
const STORAGE_KEY_LOCKS = new Map()
|
|
56
51
|
|
|
57
52
|
/** Client-side append-only sync mutation log with pluggable persistent storage. */
|
|
58
53
|
export default class LocalMutationLog {
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
|
|
3
|
-
const OFFLINE_GRANT_SIGNATURE_ALGORITHM = "HS256"
|
|
4
|
-
const OFFLINE_GRANT_SIGNATURE_PREFIX = "hmac-sha256-"
|
|
5
|
-
|
|
6
1
|
/**
|
|
7
2
|
* Signed offline grant envelope.
|
|
8
3
|
* @typedef {object} SignedOfflineGrant
|
|
@@ -11,7 +6,6 @@ const OFFLINE_GRANT_SIGNATURE_PREFIX = "hmac-sha256-"
|
|
|
11
6
|
* @property {string} keyId - Signing key id.
|
|
12
7
|
* @property {string} signature - Hex HMAC signature with a hmac-sha256 prefix.
|
|
13
8
|
*/
|
|
14
|
-
|
|
15
9
|
/**
|
|
16
10
|
* Backend-issued offline grant payload.
|
|
17
11
|
* @typedef {object} OfflineGrant
|
|
@@ -23,7 +17,6 @@ const OFFLINE_GRANT_SIGNATURE_PREFIX = "hmac-sha256-"
|
|
|
23
17
|
* @property {Record<string, import("../configuration-types.js").FrontendModelSyncJsonValue>} scopes - Materialized grant scopes.
|
|
24
18
|
* @property {string} userId - Actor user id allowed to use the grant.
|
|
25
19
|
*/
|
|
26
|
-
|
|
27
20
|
/**
|
|
28
21
|
* Offline grant signing key.
|
|
29
22
|
* @typedef {object} OfflineGrantSigningKey
|
|
@@ -31,6 +24,10 @@ const OFFLINE_GRANT_SIGNATURE_PREFIX = "hmac-sha256-"
|
|
|
31
24
|
* @property {string} id - Public key id included in signed grant envelopes.
|
|
32
25
|
* @property {string} secret - Private HMAC secret. Never expose this to clients.
|
|
33
26
|
*/
|
|
27
|
+
// @ts-check
|
|
28
|
+
|
|
29
|
+
const OFFLINE_GRANT_SIGNATURE_ALGORITHM = "HS256"
|
|
30
|
+
const OFFLINE_GRANT_SIGNATURE_PREFIX = "hmac-sha256-"
|
|
34
31
|
|
|
35
32
|
/**
|
|
36
33
|
* Creates a signed offline grant envelope.
|
|
@@ -2,9 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import {createSignedMutation, mutationIdempotencyKey, verifySignedMutation} from "./device-identity.js"
|
|
4
4
|
|
|
5
|
-
const PEER_MUTATION_BUNDLE_FORMAT = "velocious.sync.peer-mutation-bundle.v1"
|
|
6
|
-
const EXPORTABLE_STATUSES = new Set(["pending", "applied-locally", "conflict"])
|
|
7
|
-
|
|
8
5
|
/**
|
|
9
6
|
* Peer mutation bundle exported by a device for offline/P2P transfer.
|
|
10
7
|
* @typedef {object} PeerMutationBundle
|
|
@@ -12,7 +9,6 @@ const EXPORTABLE_STATUSES = new Set(["pending", "applied-locally", "conflict"])
|
|
|
12
9
|
* @property {"velocious.sync.peer-mutation-bundle.v1"} format - Bundle format identifier.
|
|
13
10
|
* @property {PeerMutationBundleEntry[]} mutations - Signed mutations in local sequence order.
|
|
14
11
|
*/
|
|
15
|
-
|
|
16
12
|
/**
|
|
17
13
|
* One peer mutation bundle entry.
|
|
18
14
|
* @typedef {object} PeerMutationBundleEntry
|
|
@@ -20,6 +16,8 @@ const EXPORTABLE_STATUSES = new Set(["pending", "applied-locally", "conflict"])
|
|
|
20
16
|
* @property {number} [localSequence] - Exporting device's local mutation sequence.
|
|
21
17
|
* @property {import("./device-identity.js").SignedSyncMutation} signedMutation - Device-signed mutation envelope.
|
|
22
18
|
*/
|
|
19
|
+
const PEER_MUTATION_BUNDLE_FORMAT = "velocious.sync.peer-mutation-bundle.v1"
|
|
20
|
+
const EXPORTABLE_STATUSES = new Set(["pending", "applied-locally", "conflict"])
|
|
23
21
|
|
|
24
22
|
/**
|
|
25
23
|
* Exports local non-terminal mutations as a signed peer-transfer bundle.
|
|
@@ -4,6 +4,38 @@ import {randomUUID} from "crypto"
|
|
|
4
4
|
import TableData from "../database/table-data/index.js"
|
|
5
5
|
import stableJsonStringify from "./stable-json.js"
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {object} ServerChangeFeedEntry
|
|
9
|
+
* @property {string | null} actorDeviceId - Signed mutation actor device id when available.
|
|
10
|
+
* @property {string | null} actorUserId - Signed mutation actor user id when available.
|
|
11
|
+
* @property {Record<string, ?> | null} attributes - Serialized mutation attributes.
|
|
12
|
+
* @property {string} createdAt - Server change creation timestamp.
|
|
13
|
+
* @property {string} id - Server change id.
|
|
14
|
+
* @property {string | null} idempotencyKey - Mutation idempotency key when available.
|
|
15
|
+
* @property {string} model - Frontend model name.
|
|
16
|
+
* @property {string} operation - Mutation operation.
|
|
17
|
+
* @property {Record<string, ?> | null} payload - Serialized mutation payload.
|
|
18
|
+
* @property {string | null} recordId - Changed record id when known.
|
|
19
|
+
* @property {Record<string, ?> | null} response - Command response payload.
|
|
20
|
+
* @property {Record<string, ?> | null} scope - Offline grant scope.
|
|
21
|
+
* @property {number} serverSequence - Monotonic server sequence.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {object} ServerChangeFeedRow
|
|
25
|
+
* @property {string | null} actor_device_id - Actor device id.
|
|
26
|
+
* @property {string | null} actor_user_id - Actor user id.
|
|
27
|
+
* @property {string | null} attributes_json - Attributes JSON.
|
|
28
|
+
* @property {Date | string} created_at - Creation time.
|
|
29
|
+
* @property {string} id - Entry id.
|
|
30
|
+
* @property {string | null} idempotency_key - Mutation idempotency key.
|
|
31
|
+
* @property {string} model - Frontend model name.
|
|
32
|
+
* @property {string} operation - Mutation operation.
|
|
33
|
+
* @property {string | null} payload_json - Mutation payload JSON.
|
|
34
|
+
* @property {string | null} record_id - Record id.
|
|
35
|
+
* @property {string | null} response_json - Response JSON.
|
|
36
|
+
* @property {string | null} scope_json - Scope JSON.
|
|
37
|
+
* @property {number | string} server_sequence - Server sequence.
|
|
38
|
+
*/
|
|
7
39
|
const DEFAULT_RETENTION_SIZE = 10000
|
|
8
40
|
const DEFAULT_PAGE_SIZE = 100
|
|
9
41
|
const MAX_PAGE_SIZE = 1000
|
|
@@ -464,37 +496,3 @@ function scopesEqual(changeScope, requestedScope) {
|
|
|
464
496
|
|
|
465
497
|
return stableJsonStringify(changeScope || null) === stableJsonStringify(requestedScope)
|
|
466
498
|
}
|
|
467
|
-
|
|
468
|
-
/**
|
|
469
|
-
* @typedef {object} ServerChangeFeedEntry
|
|
470
|
-
* @property {string | null} actorDeviceId - Signed mutation actor device id when available.
|
|
471
|
-
* @property {string | null} actorUserId - Signed mutation actor user id when available.
|
|
472
|
-
* @property {Record<string, ?> | null} attributes - Serialized mutation attributes.
|
|
473
|
-
* @property {string} createdAt - Server change creation timestamp.
|
|
474
|
-
* @property {string} id - Server change id.
|
|
475
|
-
* @property {string | null} idempotencyKey - Mutation idempotency key when available.
|
|
476
|
-
* @property {string} model - Frontend model name.
|
|
477
|
-
* @property {string} operation - Mutation operation.
|
|
478
|
-
* @property {Record<string, ?> | null} payload - Serialized mutation payload.
|
|
479
|
-
* @property {string | null} recordId - Changed record id when known.
|
|
480
|
-
* @property {Record<string, ?> | null} response - Command response payload.
|
|
481
|
-
* @property {Record<string, ?> | null} scope - Offline grant scope.
|
|
482
|
-
* @property {number} serverSequence - Monotonic server sequence.
|
|
483
|
-
*/
|
|
484
|
-
|
|
485
|
-
/**
|
|
486
|
-
* @typedef {object} ServerChangeFeedRow
|
|
487
|
-
* @property {string | null} actor_device_id - Actor device id.
|
|
488
|
-
* @property {string | null} actor_user_id - Actor user id.
|
|
489
|
-
* @property {string | null} attributes_json - Attributes JSON.
|
|
490
|
-
* @property {Date | string} created_at - Creation time.
|
|
491
|
-
* @property {string} id - Entry id.
|
|
492
|
-
* @property {string | null} idempotency_key - Mutation idempotency key.
|
|
493
|
-
* @property {string} model - Frontend model name.
|
|
494
|
-
* @property {string} operation - Mutation operation.
|
|
495
|
-
* @property {string | null} payload_json - Mutation payload JSON.
|
|
496
|
-
* @property {string | null} record_id - Record id.
|
|
497
|
-
* @property {string | null} response_json - Response JSON.
|
|
498
|
-
* @property {string | null} scope_json - Scope JSON.
|
|
499
|
-
* @property {number | string} server_sequence - Server sequence.
|
|
500
|
-
*/
|
|
@@ -4,8 +4,6 @@ import {optionalBoolean, optionalInteger} from "typanic"
|
|
|
4
4
|
|
|
5
5
|
import recordChanges from "../database/record-changes.js"
|
|
6
6
|
|
|
7
|
-
const syncTaskPromises = new Map()
|
|
8
|
-
|
|
9
7
|
/** @typedef {import("./sync-api-client-types.js").SyncChangeApplyResult} SyncChangeApplyResult */
|
|
10
8
|
/** @typedef {import("./sync-api-client-types.js").SyncChangeEnvelope} SyncChangeEnvelope */
|
|
11
9
|
/** @typedef {import("./sync-api-client-types.js").SyncChangesRequest} SyncChangesRequest */
|
|
@@ -15,6 +13,7 @@ const syncTaskPromises = new Map()
|
|
|
15
13
|
/** @typedef {import("./sync-api-client-types.js").SyncReplayItem} SyncReplayItem */
|
|
16
14
|
/** @typedef {import("./sync-api-client-types.js").SyncReplayResponse} SyncReplayResponse */
|
|
17
15
|
/** @typedef {import("./sync-api-client-types.js").SyncResourceConfig} SyncResourceConfig */
|
|
16
|
+
const syncTaskPromises = new Map()
|
|
18
17
|
|
|
19
18
|
/**
|
|
20
19
|
* Generic client-side helper for replaying pending sync envelopes through the
|