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
|
@@ -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
|
+
}
|
|
@@ -90,6 +90,7 @@
|
|
|
90
90
|
* @typedef {object} SyncClientOptions
|
|
91
91
|
* @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.
|
|
92
92
|
* @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.
|
|
93
|
+
* @property {import("../remote-request-context.js").RemoteRequestContext} [requestContext] - Immutable scalar context captured for this client and sent with every pull, replay, and realtime subscription.
|
|
93
94
|
* @property {import("./sync-scope-store.js").default} [scopeStore] - Scope store override (tests).
|
|
94
95
|
* @property {ReturnType<typeof JSON.parse>} [syncModel] - Pending-sync model override. Defaults to the registered "Sync" model.
|
|
95
96
|
* @property {string} [databaseIdentifier] - Tenant database identifier; required with tenantHandle.
|
|
@@ -109,6 +110,7 @@
|
|
|
109
110
|
* @property {(payload: import("./sync-api-client-types.js").SyncChangesRequest & {scope: SerializedSyncScope}) => Promise<import("./sync-api-client-types.js").SyncChangesResponse>} postChanges - Posts one changes request.
|
|
110
111
|
* @property {(payload: {authenticationToken: string, syncs: Array<Record<string, ReturnType<typeof JSON.parse>>>}) => Promise<import("./sync-api-client-types.js").SyncReplayResponse>} postReplay - Posts one replay request.
|
|
111
112
|
* @property {import("../configuration-types.js").VelociousSyncClientRealtimeConfiguration} [realtime] - Realtime push configuration consumed by `subscribeRealtime(...)`.
|
|
113
|
+
* @property {import("../remote-request-context.js").RemoteRequestContext} requestContext - Immutable scalar context captured for this client.
|
|
112
114
|
* @property {Record<string, SyncClientResourceConfig>} resources - Derived resource policies keyed by resource/model name.
|
|
113
115
|
* @property {ReturnType<typeof JSON.parse>} syncModel - Local pending-sync model class.
|
|
114
116
|
* @property {string} [databaseIdentifier] - Tenant-scoped database identifier.
|
package/src/sync/sync-client.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import Configuration from "../configuration.js"
|
|
4
4
|
import {isBooleanColumnType} from "../database/column-types.js"
|
|
5
5
|
import Logger from "../logger.js"
|
|
6
|
+
import {captureRemoteRequestContext, mergeRemoteRequestContext} from "../remote-request-context.js"
|
|
6
7
|
import restArgsError from "../utils/rest-args-error.js"
|
|
7
8
|
import VelociousWebsocketClient from "../http-client/websocket-client.js"
|
|
8
9
|
|
|
@@ -28,6 +29,20 @@ const DEFAULT_TRACKED_OPERATIONS = ["create", "update"]
|
|
|
28
29
|
/** Attribute names treated as client-local sync bookkeeping when deriving localOnlyAttributes. */
|
|
29
30
|
const LOCAL_BOOKKEEPING_ATTRIBUTE_NAMES = ["createdAt", "updatedAt", "lastSyncChangeAt"]
|
|
30
31
|
|
|
32
|
+
const SYNC_REQUEST_RESERVED_KEYS = [
|
|
33
|
+
"afterId",
|
|
34
|
+
"afterServerSequence",
|
|
35
|
+
"afterUpdatedAt",
|
|
36
|
+
"authenticationToken",
|
|
37
|
+
"limit",
|
|
38
|
+
"scope",
|
|
39
|
+
"syncs",
|
|
40
|
+
"upstreamRefresh",
|
|
41
|
+
"upToId",
|
|
42
|
+
"upToServerSequence",
|
|
43
|
+
"upToUpdatedAt"
|
|
44
|
+
]
|
|
45
|
+
|
|
31
46
|
/** @type {WeakMap<Configuration, SyncClient>} */
|
|
32
47
|
const syncClientsByConfiguration = new WeakMap()
|
|
33
48
|
|
|
@@ -56,11 +71,15 @@ export default class SyncClient {
|
|
|
56
71
|
* @param {import("./sync-client-types.js").SyncClientOptions} [options] - Optional overrides.
|
|
57
72
|
*/
|
|
58
73
|
constructor(options = {}) {
|
|
59
|
-
const {configuration = Configuration.current(), databaseIdentifier, legacyCursor, scopeStore, syncModel, tenantHandle, ...restOptions} = options
|
|
74
|
+
const {configuration = Configuration.current(), databaseIdentifier, legacyCursor, requestContext, scopeStore, syncModel, tenantHandle, ...restOptions} = options
|
|
60
75
|
|
|
61
76
|
restArgsError(restOptions)
|
|
62
77
|
|
|
63
78
|
const clientConfiguration = configuration.getSyncConfiguration().client
|
|
79
|
+
const capturedRequestContext = captureRemoteRequestContext(requestContext, {
|
|
80
|
+
label: "Sync client request context",
|
|
81
|
+
reservedKeys: SYNC_REQUEST_RESERVED_KEYS
|
|
82
|
+
})
|
|
64
83
|
|
|
65
84
|
if (!clientConfiguration) {
|
|
66
85
|
throw new Error("SyncClient requires a sync.client configuration block: new Configuration({sync: {client: {authenticationToken, transport}}})")
|
|
@@ -121,9 +140,10 @@ export default class SyncClient {
|
|
|
121
140
|
isOnline: clientConfiguration.isOnline,
|
|
122
141
|
legacyCursor,
|
|
123
142
|
onError: clientConfiguration.onError,
|
|
124
|
-
postChanges: transportPoster({path: `${clientConfiguration.mountPath}/changes`, transport: clientConfiguration.transport}),
|
|
125
|
-
postReplay: transportPoster({path: `${clientConfiguration.mountPath}/replay`, transport: clientConfiguration.transport}),
|
|
143
|
+
postChanges: transportPoster({path: `${clientConfiguration.mountPath}/changes`, requestContext: capturedRequestContext, transport: clientConfiguration.transport}),
|
|
144
|
+
postReplay: transportPoster({path: `${clientConfiguration.mountPath}/replay`, requestContext: capturedRequestContext, transport: clientConfiguration.transport}),
|
|
126
145
|
realtime: clientConfiguration.realtime,
|
|
146
|
+
requestContext: capturedRequestContext,
|
|
127
147
|
resources,
|
|
128
148
|
syncModel: resolvedSyncModel,
|
|
129
149
|
tenantHandle,
|
|
@@ -1267,12 +1287,17 @@ function normalizedTrack(track) {
|
|
|
1267
1287
|
|
|
1268
1288
|
/**
|
|
1269
1289
|
* Builds a framework-owned sync endpoint POSTer over the configured transport.
|
|
1270
|
-
* @param {{path: string, transport: import("../configuration-types.js").VelociousSyncClientTransport}} args - Poster args.
|
|
1290
|
+
* @param {{path: string, requestContext: import("../remote-request-context.js").RemoteRequestContext, transport: import("../configuration-types.js").VelociousSyncClientTransport}} args - Poster args.
|
|
1271
1291
|
* @returns {(payload: Record<string, ReturnType<typeof JSON.parse>>) => Promise<ReturnType<typeof JSON.parse>>} Sync endpoint POSTer.
|
|
1272
1292
|
*/
|
|
1273
|
-
function transportPoster({path, transport}) {
|
|
1293
|
+
function transportPoster({path, requestContext, transport}) {
|
|
1274
1294
|
return async (payload) => {
|
|
1275
|
-
const
|
|
1295
|
+
const requestPayload = mergeRemoteRequestContext({
|
|
1296
|
+
context: requestContext,
|
|
1297
|
+
label: "Sync client request context",
|
|
1298
|
+
params: payload
|
|
1299
|
+
})
|
|
1300
|
+
const response = await transport.post(path, requestPayload)
|
|
1276
1301
|
|
|
1277
1302
|
if (!response || typeof response.json !== "function") {
|
|
1278
1303
|
throw new Error(`sync.client transport.post must resolve to a response with a json() method for ${path} (like the frontend-model websocket client)`)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
import recordChanges from "../database/record-changes.js"
|
|
4
|
+
import {mergeRemoteRequestContext} from "../remote-request-context.js"
|
|
4
5
|
|
|
5
6
|
import SyncApiClient from "./sync-api-client.js"
|
|
6
7
|
import {VELOCIOUS_SYNC_CHANNEL} from "./sync-channel-name.js"
|
|
@@ -141,10 +142,15 @@ export default class SyncRealtimeBridge {
|
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
const resourceType = channelDescriptor.resourceType ?? null
|
|
145
|
+
const params = mergeRemoteRequestContext({
|
|
146
|
+
context: this.syncClient.config.requestContext,
|
|
147
|
+
label: "Sync client request context",
|
|
148
|
+
params: {...channelDescriptor.params, authenticationToken}
|
|
149
|
+
})
|
|
144
150
|
const subscription = client.subscribeChannel(channelDescriptor.channel, {
|
|
145
151
|
onMessage: (body) => this.enqueueApply({body, resourceType}),
|
|
146
152
|
onResume: () => this.schedulePull(),
|
|
147
|
-
params
|
|
153
|
+
params
|
|
148
154
|
})
|
|
149
155
|
|
|
150
156
|
channels.push({channel: channelDescriptor.channel, resourceType, subscription})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
/** @typedef {{
|
|
3
|
+
/** @typedef {{ownedQueue: Promise<void>}} CoordinatorOwnerScope */
|
|
4
|
+
/** @typedef {{connectionScope: CoordinatorOwnerScope, coordinator: (callback: () => Promise<unknown>) => Promise<unknown>, owner: symbol, ownerScopes: Map<symbol, CoordinatorOwnerScope>}} CoordinatorRegistration */
|
|
4
5
|
|
|
5
6
|
/** @type {WeakMap<object, CoordinatorRegistration>} */
|
|
6
7
|
const coordinators = new WeakMap()
|
|
@@ -21,43 +22,47 @@ async function inactiveCoordinator(callback) {
|
|
|
21
22
|
* @template T
|
|
22
23
|
* @param {import("../database/drivers/base.js").default} connection - Parent physical connection.
|
|
23
24
|
* @param {CoordinatorRegistration} registration - Physical connection registration.
|
|
25
|
+
* @param {CoordinatorOwnerScope} ownerScope - Queue inherited from the current owner.
|
|
24
26
|
* @param {() => Promise<T>} callback - Owned operation.
|
|
27
|
+
* @param {string} [ownerDescription] - Async owner description.
|
|
25
28
|
* @returns {Promise<T>} - Operation result.
|
|
26
29
|
*/
|
|
27
|
-
async function coordinateOwnedSharedTransactionConnection(connection, registration, callback) {
|
|
28
|
-
const previous =
|
|
30
|
+
async function coordinateOwnedSharedTransactionConnection(connection, registration, ownerScope, callback, ownerDescription = "shared-transaction-owned-operation") {
|
|
31
|
+
const previous = ownerScope.ownedQueue
|
|
29
32
|
/**
|
|
30
33
|
* Releases the next owned sibling operation.
|
|
31
34
|
* @type {() => void}
|
|
32
35
|
*/
|
|
33
36
|
let release = () => {}
|
|
34
37
|
|
|
35
|
-
|
|
38
|
+
ownerScope.ownedQueue = new Promise((resolve) => { release = resolve })
|
|
36
39
|
await previous
|
|
37
|
-
const operationOwner = Symbol(
|
|
40
|
+
const operationOwner = Symbol(ownerDescription)
|
|
38
41
|
const environmentHandler = connection.configuration.getEnvironmentHandler()
|
|
42
|
+
const operationScope = {ownedQueue: Promise.resolve()}
|
|
39
43
|
|
|
40
|
-
registration.
|
|
44
|
+
registration.ownerScopes.set(operationOwner, operationScope)
|
|
41
45
|
try {
|
|
42
46
|
return await environmentHandler.runWithSharedTransactionCoordinatorOwner(connection, operationOwner, callback)
|
|
43
47
|
} finally {
|
|
44
|
-
|
|
48
|
+
await drainOwnedSharedTransactionConnections(operationScope)
|
|
49
|
+
registration.ownerScopes.delete(operationOwner)
|
|
45
50
|
release()
|
|
46
51
|
}
|
|
47
52
|
}
|
|
48
53
|
|
|
49
54
|
/**
|
|
50
55
|
* Drains all inherited operations admitted before the root owner is revoked.
|
|
51
|
-
* @param {
|
|
56
|
+
* @param {CoordinatorOwnerScope} ownerScope - Owner scope to drain.
|
|
52
57
|
* @returns {Promise<void>} - Resolves when the owned queue stops advancing.
|
|
53
58
|
*/
|
|
54
|
-
async function drainOwnedSharedTransactionConnections(
|
|
59
|
+
async function drainOwnedSharedTransactionConnections(ownerScope) {
|
|
55
60
|
let tail
|
|
56
61
|
|
|
57
62
|
do {
|
|
58
|
-
tail =
|
|
63
|
+
tail = ownerScope.ownedQueue
|
|
59
64
|
await tail
|
|
60
|
-
} while (tail !==
|
|
65
|
+
} while (tail !== ownerScope.ownedQueue)
|
|
61
66
|
}
|
|
62
67
|
|
|
63
68
|
/**
|
|
@@ -69,17 +74,13 @@ async function drainOwnedSharedTransactionConnections(registration) {
|
|
|
69
74
|
* @returns {Promise<T>} - Operation result.
|
|
70
75
|
*/
|
|
71
76
|
async function coordinateRootSharedTransactionConnection(connection, registration, callback) {
|
|
72
|
-
await
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
} finally {
|
|
80
|
-
await drainOwnedSharedTransactionConnections(registration)
|
|
81
|
-
registration.rootOwners.delete(rootOwner)
|
|
82
|
-
}
|
|
77
|
+
return await coordinateOwnedSharedTransactionConnection(
|
|
78
|
+
connection,
|
|
79
|
+
registration,
|
|
80
|
+
registration.connectionScope,
|
|
81
|
+
callback,
|
|
82
|
+
"shared-transaction-root-operation"
|
|
83
|
+
)
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
/**
|
|
@@ -96,7 +97,12 @@ export function setSharedTransactionCoordinator(connection, coordinator) {
|
|
|
96
97
|
registration.coordinator = coordinator
|
|
97
98
|
registration.owner = owner
|
|
98
99
|
} else {
|
|
99
|
-
registration = {
|
|
100
|
+
registration = {
|
|
101
|
+
connectionScope: {ownedQueue: Promise.resolve()},
|
|
102
|
+
coordinator,
|
|
103
|
+
owner,
|
|
104
|
+
ownerScopes: new Map()
|
|
105
|
+
}
|
|
100
106
|
connectionRegistrations.set(connection, registration)
|
|
101
107
|
}
|
|
102
108
|
|
|
@@ -135,12 +141,14 @@ export async function coordinateSharedTransactionConnection(connection, callback
|
|
|
135
141
|
|
|
136
142
|
const environmentHandler = connection.configuration.getEnvironmentHandler()
|
|
137
143
|
const currentOwner = environmentHandler.getSharedTransactionCoordinatorOwner(connection)
|
|
144
|
+
const currentOwnerScope = currentOwner ? registration.ownerScopes.get(currentOwner) : undefined
|
|
138
145
|
|
|
139
|
-
if (
|
|
140
|
-
|
|
141
|
-
|
|
146
|
+
if (currentOwnerScope) {
|
|
147
|
+
return await coordinateOwnedSharedTransactionConnection(connection, registration, currentOwnerScope, callback)
|
|
148
|
+
}
|
|
149
|
+
if (!activeRegistration) {
|
|
150
|
+
return await coordinateOwnedSharedTransactionConnection(connection, registration, registration.connectionScope, callback)
|
|
142
151
|
}
|
|
143
|
-
if (!activeRegistration) return await coordinateOwnedSharedTransactionConnection(connection, registration, callback)
|
|
144
152
|
if (operationOwner === registration.owner) {
|
|
145
153
|
return await coordinateRootSharedTransactionConnection(connection, registration, callback)
|
|
146
154
|
}
|
|
@@ -149,3 +157,19 @@ export async function coordinateSharedTransactionConnection(connection, callback
|
|
|
149
157
|
return await coordinateRootSharedTransactionConnection(connection, registration, callback)
|
|
150
158
|
}))
|
|
151
159
|
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Runs physical query work without inheriting this connection's coordinator owner.
|
|
163
|
+
* Unregistered connections have no coordinator ownership to clear.
|
|
164
|
+
* @template T
|
|
165
|
+
* @param {import("../database/drivers/base.js").default} connection - Physical connection.
|
|
166
|
+
* @param {() => Promise<T>} callback - Physical query work.
|
|
167
|
+
* @returns {Promise<T>} - Callback result.
|
|
168
|
+
*/
|
|
169
|
+
export async function runWithoutSharedTransactionCoordinatorOwner(connection, callback) {
|
|
170
|
+
if (!connectionRegistrations.has(connection)) return await callback()
|
|
171
|
+
|
|
172
|
+
const environmentHandler = connection.configuration.getEnvironmentHandler()
|
|
173
|
+
|
|
174
|
+
return await environmentHandler.runWithoutSharedTransactionCoordinatorOwner(connection, callback)
|
|
175
|
+
}
|
|
@@ -1436,7 +1436,7 @@ export default class TestRunner {
|
|
|
1436
1436
|
testSharedConnectionsActive = true
|
|
1437
1437
|
|
|
1438
1438
|
try {
|
|
1439
|
-
if (testArgs.databaseCleaning?.transaction === true) {
|
|
1439
|
+
if (testArgs.databaseCleaning?.transaction === true || testArgs.type == "request") {
|
|
1440
1440
|
sharedTransactionBrokerPreparation = await this.prepareSharedTransactionBroker()
|
|
1441
1441
|
}
|
|
1442
1442
|
|