velocious 1.0.620 → 1.0.622
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 +6 -2
- package/build/background-jobs/store.js +1 -1
- package/build/database/drivers/base.js +20 -3
- package/build/database/drivers/mssql/index.js +42 -36
- package/build/environment-handlers/base.js +9 -0
- package/build/environment-handlers/node.js +16 -0
- package/build/frontend-model-controller.js +20 -10
- package/build/frontend-models/base.js +90 -25
- package/build/frontend-models/remote-request-context.js +29 -0
- package/build/http-client/websocket-client.js +32 -0
- package/build/http-server/client/websocket-session.js +4 -1
- package/build/remote-request-context.js +97 -0
- package/build/src/background-jobs/store.js +1 -1
- package/build/src/database/drivers/base.d.ts +8 -0
- package/build/src/database/drivers/base.d.ts.map +1 -1
- package/build/src/database/drivers/base.js +16 -4
- package/build/src/database/drivers/mssql/index.d.ts.map +1 -1
- package/build/src/database/drivers/mssql/index.js +47 -41
- package/build/src/environment-handlers/base.d.ts +8 -0
- package/build/src/environment-handlers/base.d.ts.map +1 -1
- package/build/src/environment-handlers/base.js +9 -1
- package/build/src/environment-handlers/node.d.ts +8 -0
- package/build/src/environment-handlers/node.d.ts.map +1 -1
- package/build/src/environment-handlers/node.js +15 -1
- package/build/src/frontend-model-controller.d.ts +3 -1
- package/build/src/frontend-model-controller.d.ts.map +1 -1
- package/build/src/frontend-model-controller.js +11 -7
- package/build/src/frontend-models/base.d.ts +4 -0
- package/build/src/frontend-models/base.d.ts.map +1 -1
- package/build/src/frontend-models/base.js +78 -25
- package/build/src/frontend-models/remote-request-context.d.ts +15 -0
- package/build/src/frontend-models/remote-request-context.d.ts.map +1 -0
- package/build/src/frontend-models/remote-request-context.js +26 -0
- package/build/src/http-client/websocket-client.d.ts +8 -0
- package/build/src/http-client/websocket-client.d.ts.map +1 -1
- package/build/src/http-client/websocket-client.js +31 -1
- package/build/src/http-server/client/websocket-session.d.ts.map +1 -1
- package/build/src/http-server/client/websocket-session.js +4 -2
- package/build/src/remote-request-context.d.ts +34 -0
- package/build/src/remote-request-context.d.ts.map +1 -0
- package/build/src/remote-request-context.js +83 -0
- package/build/src/sync/sync-client-types.d.ts +10 -0
- package/build/src/sync/sync-client-types.d.ts.map +1 -1
- package/build/src/sync/sync-client-types.js +3 -1
- package/build/src/sync/sync-client.d.ts.map +1 -1
- package/build/src/sync/sync-client.js +31 -7
- package/build/src/sync/sync-realtime-bridge.d.ts.map +1 -1
- package/build/src/sync/sync-realtime-bridge.js +8 -2
- package/build/src/testing/shared-transaction-connection-coordinator.d.ts +14 -3
- package/build/src/testing/shared-transaction-connection-coordinator.d.ts.map +1 -1
- package/build/src/testing/shared-transaction-connection-coordinator.js +44 -30
- package/build/src/testing/test-runner.js +2 -2
- package/build/sync/sync-client-types.js +2 -0
- package/build/sync/sync-client.js +31 -6
- package/build/sync/sync-realtime-bridge.js +7 -1
- package/build/testing/shared-transaction-connection-coordinator.js +51 -27
- package/build/testing/test-runner.js +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/background-jobs/store.js +1 -1
- package/src/database/drivers/base.js +20 -3
- package/src/database/drivers/mssql/index.js +42 -36
- package/src/environment-handlers/base.js +9 -0
- package/src/environment-handlers/node.js +16 -0
- package/src/frontend-model-controller.js +20 -10
- package/src/frontend-models/base.js +90 -25
- package/src/frontend-models/remote-request-context.js +29 -0
- package/src/http-client/websocket-client.js +32 -0
- package/src/http-server/client/websocket-session.js +4 -1
- package/src/remote-request-context.js +97 -0
- package/src/sync/sync-client-types.js +2 -0
- package/src/sync/sync-client.js +31 -6
- package/src/sync/sync-realtime-bridge.js +7 -1
- package/src/testing/shared-transaction-connection-coordinator.js +51 -27
- package/src/testing/test-runner.js +1 -1
|
@@ -26,6 +26,8 @@ export default class VelociousWebsocketClient extends SnapReqWebSocketClient {
|
|
|
26
26
|
this.reconnectGeneration = 0
|
|
27
27
|
/** @type {Set<Promise<void>>} */
|
|
28
28
|
this.runningReconnectTasks = new Set()
|
|
29
|
+
/** @type {Promise<void> | null} */
|
|
30
|
+
this.gracefulClosePromise = null
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
/**
|
|
@@ -55,6 +57,36 @@ export default class VelociousWebsocketClient extends SnapReqWebSocketClient {
|
|
|
55
57
|
}
|
|
56
58
|
}
|
|
57
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Closes the WebSocket as a normal shutdown so the server permanently
|
|
62
|
+
* releases resumable session state.
|
|
63
|
+
* @returns {Promise<void>} - Resolves once closed.
|
|
64
|
+
*/
|
|
65
|
+
async close() {
|
|
66
|
+
if (this.gracefulClosePromise) return await this.gracefulClosePromise
|
|
67
|
+
|
|
68
|
+
this.autoReconnect = false
|
|
69
|
+
const socket = this.socket
|
|
70
|
+
const closePromise = (async () => {
|
|
71
|
+
if (socket && socket.readyState === socket.OPEN) {
|
|
72
|
+
await new Promise((resolve) => {
|
|
73
|
+
socket.addEventListener("close", () => resolve(undefined), {once: true})
|
|
74
|
+
socket.close(1000)
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await super.close()
|
|
79
|
+
})()
|
|
80
|
+
|
|
81
|
+
this.gracefulClosePromise = closePromise
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
await closePromise
|
|
85
|
+
} finally {
|
|
86
|
+
if (this.gracefulClosePromise === closePromise) this.gracefulClosePromise = null
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
58
90
|
/**
|
|
59
91
|
* Stops reconnect, drains work that already passed SnapReq's reconnect guard,
|
|
60
92
|
* and clears state changed by a stale attempt while it settled.
|
|
@@ -39,6 +39,7 @@ const WEBSOCKET_OPCODE_CLOSE = 0x8
|
|
|
39
39
|
const WEBSOCKET_OPCODE_PING = 0x9
|
|
40
40
|
const WEBSOCKET_OPCODE_PONG = 0xA
|
|
41
41
|
|
|
42
|
+
const WEBSOCKET_CLOSE_NORMAL = 1000
|
|
42
43
|
const WEBSOCKET_CLOSE_POLICY_VIOLATION = 1008
|
|
43
44
|
const WEBSOCKET_INBOUND_BACKLOG_CLOSE_REASON = "Inbound message backlog exceeded"
|
|
44
45
|
const WEBSOCKET_MAX_CLOSE_REASON_BYTES = 123
|
|
@@ -785,8 +786,10 @@ export default class VelociousHttpServerClientWebsocketSession {
|
|
|
785
786
|
}
|
|
786
787
|
|
|
787
788
|
if (opcode === WEBSOCKET_OPCODE_CLOSE) {
|
|
789
|
+
const allowResume = payload.length < 2 || payload.readUInt16BE(0) !== WEBSOCKET_CLOSE_NORMAL
|
|
790
|
+
|
|
788
791
|
this.sendGoodbye(this.client)
|
|
789
|
-
this._handleClose()
|
|
792
|
+
this._handleClose({allowResume})
|
|
790
793
|
continue
|
|
791
794
|
}
|
|
792
795
|
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import VelociousError from "./velocious-error.js"
|
|
4
|
+
import isPlainObject from "./utils/plain-object.js"
|
|
5
|
+
|
|
6
|
+
/** @typedef {Readonly<Record<string, string | number | boolean>>} RemoteRequestContext */
|
|
7
|
+
|
|
8
|
+
const UNSAFE_CONTEXT_KEYS = new Set(["__proto__", "constructor", "prototype"])
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Captures and validates immutable scalar context for one remote operation.
|
|
12
|
+
* @param {ReturnType<typeof JSON.parse> | undefined} value - Configured context value.
|
|
13
|
+
* @param {object} [args] - Validation options.
|
|
14
|
+
* @param {string} [args.label] - Context label used in errors.
|
|
15
|
+
* @param {Iterable<string>} [args.reservedKeys] - Framework-owned keys unavailable to context.
|
|
16
|
+
* @returns {RemoteRequestContext} Frozen context snapshot.
|
|
17
|
+
*/
|
|
18
|
+
export function captureRemoteRequestContext(value, {label = "Remote request context", reservedKeys = []} = {}) {
|
|
19
|
+
if (value === undefined || value === null) return Object.freeze({})
|
|
20
|
+
|
|
21
|
+
if (!isPlainObject(value)) {
|
|
22
|
+
throw remoteRequestContextError(`${label} must be a plain object of scalar values`)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const reservedKeySet = new Set(reservedKeys)
|
|
26
|
+
/** @type {Record<string, string | number | boolean>} */
|
|
27
|
+
const context = {}
|
|
28
|
+
|
|
29
|
+
for (const key of Object.keys(value).sort()) {
|
|
30
|
+
if (!key.trim()) throw remoteRequestContextError(`${label} keys must be non-blank strings`)
|
|
31
|
+
if (UNSAFE_CONTEXT_KEYS.has(key) || reservedKeySet.has(key)) {
|
|
32
|
+
throw remoteRequestContextError(`${label} key ${JSON.stringify(key)} is reserved by the framework`)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const contextValue = value[key]
|
|
36
|
+
|
|
37
|
+
if (!remoteRequestContextScalar(contextValue)) {
|
|
38
|
+
throw remoteRequestContextError(`${label} key ${JSON.stringify(key)} must contain a string, finite number, or boolean scalar`)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
context[key] = contextValue
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return Object.freeze(context)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Merges captured context into framework request params without ambiguity.
|
|
49
|
+
* @template {Record<string, ReturnType<typeof JSON.parse>>} TParams
|
|
50
|
+
* @param {object} args - Merge arguments.
|
|
51
|
+
* @param {RemoteRequestContext} args.context - Captured context.
|
|
52
|
+
* @param {string} [args.label] - Context label used in errors.
|
|
53
|
+
* @param {TParams} args.params - Framework-owned request params.
|
|
54
|
+
* @returns {TParams & RemoteRequestContext} Merged params, or the original params when unscoped.
|
|
55
|
+
*/
|
|
56
|
+
export function mergeRemoteRequestContext({context, label = "Remote request context", params}) {
|
|
57
|
+
const contextKeys = Object.keys(context)
|
|
58
|
+
|
|
59
|
+
if (contextKeys.length === 0) return params
|
|
60
|
+
|
|
61
|
+
for (const key of contextKeys) {
|
|
62
|
+
if (Object.hasOwn(params, key)) {
|
|
63
|
+
throw remoteRequestContextError(`${label} key ${JSON.stringify(key)} is reserved by the request payload`)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {...params, ...context}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Returns a stable identity for an immutable captured context.
|
|
72
|
+
* @param {RemoteRequestContext} context - Captured context.
|
|
73
|
+
* @returns {string} Stable serialized key.
|
|
74
|
+
*/
|
|
75
|
+
export function remoteRequestContextKey(context) {
|
|
76
|
+
return JSON.stringify(context)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Checks whether a value is a supported request-context scalar.
|
|
81
|
+
* @param {ReturnType<typeof JSON.parse>} value - Candidate scalar.
|
|
82
|
+
* @returns {value is string | number | boolean} Whether the value is supported.
|
|
83
|
+
*/
|
|
84
|
+
function remoteRequestContextScalar(value) {
|
|
85
|
+
if (["string", "boolean"].includes(typeof value)) return true
|
|
86
|
+
|
|
87
|
+
return typeof value === "number" && Number.isFinite(value)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Builds a client-safe request-context validation error.
|
|
92
|
+
* @param {string} message - Safe validation message.
|
|
93
|
+
* @returns {VelociousError} Validation error.
|
|
94
|
+
*/
|
|
95
|
+
function remoteRequestContextError(message) {
|
|
96
|
+
return VelociousError.safe(message, {code: "remote-request-context-invalid", errorType: "validation_error"})
|
|
97
|
+
}
|