velocious 1.0.621 → 1.0.623
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 +7 -3
- 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/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-envelope-replay-service.d.ts +3 -2
- package/build/src/sync/sync-envelope-replay-service.d.ts.map +1 -1
- package/build/src/sync/sync-envelope-replay-service.js +18 -11
- 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 +9 -0
- package/build/src/testing/shared-transaction-connection-coordinator.d.ts.map +1 -1
- package/build/src/testing/shared-transaction-connection-coordinator.js +15 -1
- 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-envelope-replay-service.js +17 -10
- package/build/sync/sync-realtime-bridge.js +7 -1
- package/build/testing/shared-transaction-connection-coordinator.js +16 -0
- package/build/testing/test-runner.js +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +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-envelope-replay-service.js +17 -10
- package/src/sync/sync-realtime-bridge.js +7 -1
- package/src/testing/shared-transaction-connection-coordinator.js +16 -0
- 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)`)
|
|
@@ -798,18 +798,24 @@ export default class SyncEnvelopeReplayService {
|
|
|
798
798
|
if (!existingRecord || mutation.syncType === "create") return null
|
|
799
799
|
if (mutation.baseVersion === undefined || mutation.baseVersion === null) return null
|
|
800
800
|
|
|
801
|
+
const ModelClass = resource.modelClass()
|
|
802
|
+
const primaryKey = ModelClass.primaryKey()
|
|
803
|
+
const primaryKeyAttribute = ModelClass.resolveAttributeName(primaryKey)
|
|
801
804
|
const versionAttribute = this.conflictStrategy.versionAttribute
|
|
802
|
-
const
|
|
805
|
+
const versionAttributeName = ModelClass.resolveAttributeName(versionAttribute)
|
|
806
|
+
|
|
807
|
+
if (!primaryKeyAttribute) throw new Error(`Couldn't resolve primary key attribute: ${primaryKey}`)
|
|
808
|
+
if (!versionAttributeName) throw new Error(`Couldn't resolve version attribute: ${versionAttribute}`)
|
|
809
|
+
|
|
810
|
+
const serverVersion = normalizeConflictValue(existingRecord.readAttribute(versionAttributeName))
|
|
803
811
|
|
|
804
812
|
if (stableJsonStringify(serverVersion) === stableJsonStringify(mutation.baseVersion)) return null
|
|
805
813
|
|
|
806
|
-
const ModelClass = resource.modelClass()
|
|
807
|
-
const primaryKey = ModelClass.primaryKey()
|
|
808
814
|
const serializedAffectedAttributes = await this.serializedRoutedConflictAttributes({attributes, existingRecord, resource})
|
|
809
815
|
const serverAttributes = {
|
|
810
816
|
...serializedAffectedAttributes,
|
|
811
|
-
[
|
|
812
|
-
[
|
|
817
|
+
[primaryKeyAttribute]: existingRecord.readAttribute(primaryKeyAttribute),
|
|
818
|
+
[versionAttributeName]: serverVersion
|
|
813
819
|
}
|
|
814
820
|
|
|
815
821
|
const serverRecord = {
|
|
@@ -842,8 +848,9 @@ export default class SyncEnvelopeReplayService {
|
|
|
842
848
|
* attribute contract. Writable-but-hidden fields are omitted, while custom
|
|
843
849
|
* `<attribute>Attribute(model)` serializers and model accessors remain the
|
|
844
850
|
* source of frontend-visible values (Date values are kept raw so the normal
|
|
845
|
-
* frontend-model transport serializer can emit its date marker).
|
|
846
|
-
* model attribute
|
|
851
|
+
* frontend-model transport serializer can emit its date marker). Projected
|
|
852
|
+
* keys use canonical model attribute names even when the mutation used a
|
|
853
|
+
* database-column alias. The full model attribute hash is never exposed.
|
|
847
854
|
* @param {object} args - Projection args.
|
|
848
855
|
* @param {Record<string, ReturnType<typeof JSON.parse>>} args.attributes - Permitted affected mutation attributes.
|
|
849
856
|
* @param {import("../database/record/index.js").default} args.existingRecord - Authorized server record.
|
|
@@ -886,7 +893,7 @@ export default class SyncEnvelopeReplayService {
|
|
|
886
893
|
const resourceAttribute = resource.resourceMethod(`${attributeName}Attribute`)
|
|
887
894
|
|
|
888
895
|
if (resourceAttribute) {
|
|
889
|
-
serializedAttributes[
|
|
896
|
+
serializedAttributes[attributeName] = await resourceAttribute.method.call(resourceAttribute.resource, existingRecord)
|
|
890
897
|
continue
|
|
891
898
|
}
|
|
892
899
|
|
|
@@ -894,9 +901,9 @@ export default class SyncEnvelopeReplayService {
|
|
|
894
901
|
const attributeMethod = recordMethods[attributeName]
|
|
895
902
|
|
|
896
903
|
if (typeof attributeMethod === "function") {
|
|
897
|
-
serializedAttributes[
|
|
904
|
+
serializedAttributes[attributeName] = await attributeMethod.call(existingRecord)
|
|
898
905
|
} else {
|
|
899
|
-
serializedAttributes[
|
|
906
|
+
serializedAttributes[attributeName] = existingRecord.readAttribute(attributeName)
|
|
900
907
|
}
|
|
901
908
|
}
|
|
902
909
|
|
|
@@ -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})
|
|
@@ -157,3 +157,19 @@ export async function coordinateSharedTransactionConnection(connection, callback
|
|
|
157
157
|
return await coordinateRootSharedTransactionConnection(connection, registration, callback)
|
|
158
158
|
}))
|
|
159
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
|
|