velocious 1.0.563 → 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 +2 -0
- 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/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 +4 -5
- package/build/http-server/client-delivery-queue.js +7 -8
- package/build/http-server/websocket-event-log-store.js +4 -6
- 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/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 +5 -5
- package/build/src/http-server/client-delivery-queue.d.ts +7 -7
- package/build/src/http-server/client-delivery-queue.d.ts.map +1 -1
- package/build/src/http-server/client-delivery-queue.js +8 -8
- 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/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/utils/backtrace-cleaner.js +8 -9
- package/build/utils/model-scope.js +3 -4
- package/build/utils/ransack.js +27 -34
- package/package.json +2 -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/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 +4 -5
- package/src/http-server/client-delivery-queue.js +7 -8
- package/src/http-server/websocket-event-log-store.js +4 -6
- 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,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
|
|
@@ -7,6 +7,23 @@ import SyncReplayUpsertApplier from "./sync-replay-upsert-applier.js"
|
|
|
7
7
|
import {ValidationError} from "../database/record/index.js"
|
|
8
8
|
import VelociousError from "../velocious-error.js"
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Resolved routed-resource registration for one replay resource type.
|
|
12
|
+
* @typedef {object} SyncReplayResourceRegistration
|
|
13
|
+
* @property {string} modelName - Effective frontend model name.
|
|
14
|
+
* @property {import("../configuration-types.js").FrontendModelResourceClassType} resourceClass - Routed resource class.
|
|
15
|
+
* @property {import("../configuration-types.js").NormalizedFrontendModelResourceConfiguration | null} resourceConfiguration - Normalized resource configuration when registry-resolved.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {object} SyncReplayMutation
|
|
19
|
+
* @property {Date} clientUpdatedAt - Client-side mutation timestamp.
|
|
20
|
+
* @property {Record<string, ?>} data - Parsed mutation payload.
|
|
21
|
+
* @property {?} id - Client sync row id for per-sync responses.
|
|
22
|
+
* @property {string} resourceId - Resource id as a string.
|
|
23
|
+
* @property {string} resourceType - Resource/model name.
|
|
24
|
+
* @property {string} serializedData - JSON serialized mutation payload.
|
|
25
|
+
* @property {string} syncType - Sync operation type.
|
|
26
|
+
*/
|
|
10
27
|
/**
|
|
11
28
|
* One declarative broadcast fanned out after a mutation applies.
|
|
12
29
|
* @typedef {object} SyncReplayBroadcast
|
|
@@ -778,22 +795,3 @@ export default class SyncEnvelopeReplayService {
|
|
|
778
795
|
await deliverDeclaredBroadcasts({args, broadcaster: this.broadcaster, broadcasts: this.broadcasts})
|
|
779
796
|
}
|
|
780
797
|
}
|
|
781
|
-
|
|
782
|
-
/**
|
|
783
|
-
* Resolved routed-resource registration for one replay resource type.
|
|
784
|
-
* @typedef {object} SyncReplayResourceRegistration
|
|
785
|
-
* @property {string} modelName - Effective frontend model name.
|
|
786
|
-
* @property {import("../configuration-types.js").FrontendModelResourceClassType} resourceClass - Routed resource class.
|
|
787
|
-
* @property {import("../configuration-types.js").NormalizedFrontendModelResourceConfiguration | null} resourceConfiguration - Normalized resource configuration when registry-resolved.
|
|
788
|
-
*/
|
|
789
|
-
|
|
790
|
-
/**
|
|
791
|
-
* @typedef {object} SyncReplayMutation
|
|
792
|
-
* @property {Date} clientUpdatedAt - Client-side mutation timestamp.
|
|
793
|
-
* @property {Record<string, ?>} data - Parsed mutation payload.
|
|
794
|
-
* @property {?} id - Client sync row id for per-sync responses.
|
|
795
|
-
* @property {string} resourceId - Resource id as a string.
|
|
796
|
-
* @property {string} resourceType - Resource/model name.
|
|
797
|
-
* @property {string} serializedData - JSON serialized mutation payload.
|
|
798
|
-
* @property {string} syncType - Sync operation type.
|
|
799
|
-
*/
|
|
@@ -7,8 +7,6 @@ import SyncEnvelopeReplayService from "./sync-envelope-replay-service.js"
|
|
|
7
7
|
import SyncModelChangeFeedService from "./sync-model-change-feed-service.js"
|
|
8
8
|
import VelociousError from "../velocious-error.js"
|
|
9
9
|
|
|
10
|
-
const QUICK_SEARCH_COLUMN = "quickSearch"
|
|
11
|
-
|
|
12
10
|
/**
|
|
13
11
|
* Optional client-declared sync scope carried on a changes request.
|
|
14
12
|
* @typedef {object} SerializedChangesScope
|
|
@@ -16,6 +14,7 @@ const QUICK_SEARCH_COLUMN = "quickSearch"
|
|
|
16
14
|
* @property {string | null} resourceType - Client resource/model name the scope was declared for, or null for the all-types (user) scope: one scope covering every resource type this resource authorizes for the caller, so a sync authorizes once however many types it serves.
|
|
17
15
|
* @property {string[] | null} resourceTypes - For the all-types scope, the resource types the client can apply. A cheap delivery/type filter only - it narrows, never widens, what the app's authorization already allows. Null for a type-declared scope.
|
|
18
16
|
*/
|
|
17
|
+
const QUICK_SEARCH_COLUMN = "quickSearch"
|
|
19
18
|
|
|
20
19
|
/**
|
|
21
20
|
* Base resource for Velocious sync endpoints.
|
|
@@ -6,6 +6,15 @@ import UUID from "pure-uuid"
|
|
|
6
6
|
import {scopeKey} from "./query-scope.js"
|
|
7
7
|
import stableJsonStringify from "./stable-json.js"
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {object} SyncScopeRow
|
|
11
|
+
* @property {Record<string, ?>} conditions - Scope attribute conditions.
|
|
12
|
+
* @property {string | null} cursorPayload - Persisted cursor JSON payload.
|
|
13
|
+
* @property {string} id - Scope row id.
|
|
14
|
+
* @property {string | null} resourceType - Scope resource/model name, or null for the all-types (user) scope.
|
|
15
|
+
* @property {string} scopeDigest - Fixed-size deterministic digest of the canonical scope key.
|
|
16
|
+
* @property {string} state - Scope state ("active" or "removed").
|
|
17
|
+
*/
|
|
9
18
|
const TABLE_NAME = "velocious_sync_scopes"
|
|
10
19
|
const SCOPE_DIGEST_PREFIX = "velocious-sync-scope:"
|
|
11
20
|
|
|
@@ -19,16 +28,6 @@ function scopeDigestForScope(scope) {
|
|
|
19
28
|
return new UUID(5, "ns:URL", `${SCOPE_DIGEST_PREFIX}${scopeKey(scope)}`).format()
|
|
20
29
|
}
|
|
21
30
|
|
|
22
|
-
/**
|
|
23
|
-
* @typedef {object} SyncScopeRow
|
|
24
|
-
* @property {Record<string, ?>} conditions - Scope attribute conditions.
|
|
25
|
-
* @property {string | null} cursorPayload - Persisted cursor JSON payload.
|
|
26
|
-
* @property {string} id - Scope row id.
|
|
27
|
-
* @property {string | null} resourceType - Scope resource/model name, or null for the all-types (user) scope.
|
|
28
|
-
* @property {string} scopeDigest - Fixed-size deterministic digest of the canonical scope key.
|
|
29
|
-
* @property {string} state - Scope state ("active" or "removed").
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
31
|
/**
|
|
33
32
|
* Framework-owned local persistence for declared sync scopes and their cursors.
|
|
34
33
|
*
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ParseFiltersResult type.
|
|
3
|
+
* @typedef {object} ParseFiltersResult
|
|
4
|
+
* @property {string[]} includeTags - Tags to include.
|
|
5
|
+
* @property {string[]} excludeTags - Tags to exclude.
|
|
6
|
+
* @property {string[]} examplePatterns - Example name patterns.
|
|
7
|
+
* @property {string[]} filteredProcessArgs - Remaining process args with filter flags removed.
|
|
8
|
+
* @property {number | undefined} groups - Total number of groups for test splitting.
|
|
9
|
+
* @property {number | undefined} groupNumber - Which group to run (1-indexed).
|
|
10
|
+
*/
|
|
1
11
|
// @ts-check
|
|
2
12
|
|
|
3
13
|
const INCLUDE_TAG_FLAGS = new Set(["--tag", "--include-tag", "-t"])
|
|
@@ -50,17 +60,6 @@ export function normalizeExamplePatterns(patterns) {
|
|
|
50
60
|
return normalized
|
|
51
61
|
}
|
|
52
62
|
|
|
53
|
-
/**
|
|
54
|
-
* ParseFiltersResult type.
|
|
55
|
-
* @typedef {object} ParseFiltersResult
|
|
56
|
-
* @property {string[]} includeTags - Tags to include.
|
|
57
|
-
* @property {string[]} excludeTags - Tags to exclude.
|
|
58
|
-
* @property {string[]} examplePatterns - Example name patterns.
|
|
59
|
-
* @property {string[]} filteredProcessArgs - Remaining process args with filter flags removed.
|
|
60
|
-
* @property {number | undefined} groups - Total number of groups for test splitting.
|
|
61
|
-
* @property {number | undefined} groupNumber - Which group to run (1-indexed).
|
|
62
|
-
*/
|
|
63
|
-
|
|
64
63
|
/**
|
|
65
64
|
* Runs the parseFilters helper.
|
|
66
65
|
* @param {string[]} processArgs - Process args.
|
|
@@ -13,6 +13,87 @@ import {testConfig, testEvents, tests} from "./test.js"
|
|
|
13
13
|
import {pathToFileURL} from "url"
|
|
14
14
|
import {clearDeliveries} from "../mailer.js"
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* ConsoleMethodName type.
|
|
18
|
+
* @typedef {"log" | "info" | "warn" | "error" | "debug"} ConsoleMethodName */
|
|
19
|
+
/**
|
|
20
|
+
* AttemptConsoleOutput type.
|
|
21
|
+
* @typedef {object} AttemptConsoleOutput
|
|
22
|
+
* @property {number} attemptNumber - Attempt number.
|
|
23
|
+
* @property {string} output - Captured console output.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* TestArgs type.
|
|
27
|
+
* @typedef {object} TestArgs
|
|
28
|
+
* @property {Application} [application] - Application instance for integration tests.
|
|
29
|
+
* @property {RequestClient} [client] - HTTP client for request tests.
|
|
30
|
+
* @property {object} [databaseCleaning] - Database cleanup options for tests.
|
|
31
|
+
* @property {boolean} [databaseCleaning.transaction] - Use transactions to rollback between tests.
|
|
32
|
+
* @property {boolean} [databaseCleaning.truncate] - Truncate tables between tests.
|
|
33
|
+
* @property {boolean} [databaseCleaning.truncateBefore] - Truncate tables before each test, in addition to the default cleanup.
|
|
34
|
+
* @property {boolean} [focus] - Whether this test is focused.
|
|
35
|
+
* @property {() => (void|Promise<void>)} [function] - Test callback function.
|
|
36
|
+
* @property {number} [retry] - Number of retries when a test fails.
|
|
37
|
+
* @property {string[] | string} [tags] - Tags for filtering.
|
|
38
|
+
* @property {number} [timeoutSeconds] - Timeout in seconds for the test.
|
|
39
|
+
* @property {string} [type] - Test type identifier.
|
|
40
|
+
*/
|
|
41
|
+
/**
|
|
42
|
+
* TestData type.
|
|
43
|
+
* @typedef {object} TestData
|
|
44
|
+
* @property {TestArgs} args - Arguments passed to the test.
|
|
45
|
+
* @property {string} [filePath] - Source file path.
|
|
46
|
+
* @property {number} [line] - Source line number.
|
|
47
|
+
* @property {function(TestArgs) : (void|Promise<void>)} function - Test callback to execute.
|
|
48
|
+
*/
|
|
49
|
+
/**
|
|
50
|
+
* FailedTestDetail type.
|
|
51
|
+
* @typedef {object} FailedTestDetail
|
|
52
|
+
* @property {string} fullDescription - Full test description.
|
|
53
|
+
* @property {string} [filePath] - Source file path.
|
|
54
|
+
* @property {number} [line] - Source line number.
|
|
55
|
+
* @property {?} error - Failure error.
|
|
56
|
+
* @property {string} [consoleOutput] - Captured console output while test ran.
|
|
57
|
+
* @property {string} [consoleLogPath] - Saved console log path.
|
|
58
|
+
*/
|
|
59
|
+
/**
|
|
60
|
+
* ActiveAfterAllScopeEntry type.
|
|
61
|
+
* @typedef {object} ActiveAfterAllScopeEntry
|
|
62
|
+
* @property {TestsArgument} tests - Scope test tree.
|
|
63
|
+
* @property {boolean} afterAllsRun - Whether cleanup hooks have run.
|
|
64
|
+
*/
|
|
65
|
+
/**
|
|
66
|
+
* Defines this typedef.
|
|
67
|
+
* @typedef {function({configuration: import("../configuration.js").default, testArgs: TestArgs, testData: TestData}) : (void|Promise<void>)} AfterBeforeEachCallbackType
|
|
68
|
+
*/
|
|
69
|
+
/**
|
|
70
|
+
* AfterBeforeEachCallbackObjectType type.
|
|
71
|
+
* @typedef {object} AfterBeforeEachCallbackObjectType
|
|
72
|
+
* @property {AfterBeforeEachCallbackType} callback - Hook callback to execute.
|
|
73
|
+
*/
|
|
74
|
+
/**
|
|
75
|
+
* Defines this typedef.
|
|
76
|
+
* @typedef {function({configuration: import("../configuration.js").default}) : (void|Promise<void>)} BeforeAfterAllCallbackType
|
|
77
|
+
*/
|
|
78
|
+
/**
|
|
79
|
+
* BeforeAfterAllCallbackObjectType type.
|
|
80
|
+
* @typedef {object} BeforeAfterAllCallbackObjectType
|
|
81
|
+
* @property {BeforeAfterAllCallbackType} callback - Hook callback to execute.
|
|
82
|
+
*/
|
|
83
|
+
/**
|
|
84
|
+
* TestsArgument type.
|
|
85
|
+
* @typedef {object} TestsArgument
|
|
86
|
+
* @property {Record<string, TestData>} args - Arguments keyed by test description.
|
|
87
|
+
* @property {boolean} [anyTestsFocussed] - Whether any tests in the tree are focused.
|
|
88
|
+
* @property {AfterBeforeEachCallbackObjectType[]} afterEaches - After-each hooks for this scope.
|
|
89
|
+
* @property {BeforeAfterAllCallbackObjectType[]} afterAlls - After-all hooks for this scope.
|
|
90
|
+
* @property {BeforeAfterAllCallbackObjectType[]} beforeAlls - Before-all hooks for this scope.
|
|
91
|
+
* @property {AfterBeforeEachCallbackObjectType[]} beforeEaches - Before-each hooks for this scope.
|
|
92
|
+
* @property {string} [filePath] - Source file path.
|
|
93
|
+
* @property {number} [line] - Source line number.
|
|
94
|
+
* @property {Record<string, TestData>} tests - A unique identifier for the node.
|
|
95
|
+
* @property {Record<string, TestsArgument>} subs - Optional child nodes. Each item is another `Node`, allowing recursion.
|
|
96
|
+
*/
|
|
16
97
|
/**
|
|
17
98
|
* Marks the error thrown by {@link runWithTimeout} so the caller can tell a
|
|
18
99
|
* lifecycle timeout (the promise is still running detached) apart from an
|
|
@@ -85,22 +166,11 @@ function awaitSettledOrGrace(lifecycle, graceMs) {
|
|
|
85
166
|
})
|
|
86
167
|
}
|
|
87
168
|
|
|
88
|
-
/**
|
|
89
|
-
* ConsoleMethodName type.
|
|
90
|
-
* @typedef {"log" | "info" | "warn" | "error" | "debug"} ConsoleMethodName */
|
|
91
|
-
|
|
92
169
|
/**
|
|
93
170
|
* Captured console methods.
|
|
94
171
|
* @type {ConsoleMethodName[]} */
|
|
95
172
|
const CAPTURED_CONSOLE_METHODS = ["log", "info", "warn", "error", "debug"]
|
|
96
173
|
|
|
97
|
-
/**
|
|
98
|
-
* AttemptConsoleOutput type.
|
|
99
|
-
* @typedef {object} AttemptConsoleOutput
|
|
100
|
-
* @property {number} attemptNumber - Attempt number.
|
|
101
|
-
* @property {string} output - Captured console output.
|
|
102
|
-
*/
|
|
103
|
-
|
|
104
174
|
/**
|
|
105
175
|
* Runs to file slug.
|
|
106
176
|
* @param {string} value - Value to sanitize.
|
|
@@ -114,87 +184,6 @@ function toFileSlug(value) {
|
|
|
114
184
|
.slice(0, 80) || "failed-test"
|
|
115
185
|
}
|
|
116
186
|
|
|
117
|
-
/**
|
|
118
|
-
* TestArgs type.
|
|
119
|
-
* @typedef {object} TestArgs
|
|
120
|
-
* @property {Application} [application] - Application instance for integration tests.
|
|
121
|
-
* @property {RequestClient} [client] - HTTP client for request tests.
|
|
122
|
-
* @property {object} [databaseCleaning] - Database cleanup options for tests.
|
|
123
|
-
* @property {boolean} [databaseCleaning.transaction] - Use transactions to rollback between tests.
|
|
124
|
-
* @property {boolean} [databaseCleaning.truncate] - Truncate tables between tests.
|
|
125
|
-
* @property {boolean} [databaseCleaning.truncateBefore] - Truncate tables before each test, in addition to the default cleanup.
|
|
126
|
-
* @property {boolean} [focus] - Whether this test is focused.
|
|
127
|
-
* @property {() => (void|Promise<void>)} [function] - Test callback function.
|
|
128
|
-
* @property {number} [retry] - Number of retries when a test fails.
|
|
129
|
-
* @property {string[] | string} [tags] - Tags for filtering.
|
|
130
|
-
* @property {number} [timeoutSeconds] - Timeout in seconds for the test.
|
|
131
|
-
* @property {string} [type] - Test type identifier.
|
|
132
|
-
*/
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* TestData type.
|
|
136
|
-
* @typedef {object} TestData
|
|
137
|
-
* @property {TestArgs} args - Arguments passed to the test.
|
|
138
|
-
* @property {string} [filePath] - Source file path.
|
|
139
|
-
* @property {number} [line] - Source line number.
|
|
140
|
-
* @property {function(TestArgs) : (void|Promise<void>)} function - Test callback to execute.
|
|
141
|
-
*/
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* FailedTestDetail type.
|
|
145
|
-
* @typedef {object} FailedTestDetail
|
|
146
|
-
* @property {string} fullDescription - Full test description.
|
|
147
|
-
* @property {string} [filePath] - Source file path.
|
|
148
|
-
* @property {number} [line] - Source line number.
|
|
149
|
-
* @property {?} error - Failure error.
|
|
150
|
-
* @property {string} [consoleOutput] - Captured console output while test ran.
|
|
151
|
-
* @property {string} [consoleLogPath] - Saved console log path.
|
|
152
|
-
*/
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* ActiveAfterAllScopeEntry type.
|
|
156
|
-
* @typedef {object} ActiveAfterAllScopeEntry
|
|
157
|
-
* @property {TestsArgument} tests - Scope test tree.
|
|
158
|
-
* @property {boolean} afterAllsRun - Whether cleanup hooks have run.
|
|
159
|
-
*/
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Defines this typedef.
|
|
163
|
-
* @typedef {function({configuration: import("../configuration.js").default, testArgs: TestArgs, testData: TestData}) : (void|Promise<void>)} AfterBeforeEachCallbackType
|
|
164
|
-
*/
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* AfterBeforeEachCallbackObjectType type.
|
|
168
|
-
* @typedef {object} AfterBeforeEachCallbackObjectType
|
|
169
|
-
* @property {AfterBeforeEachCallbackType} callback - Hook callback to execute.
|
|
170
|
-
*/
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Defines this typedef.
|
|
174
|
-
* @typedef {function({configuration: import("../configuration.js").default}) : (void|Promise<void>)} BeforeAfterAllCallbackType
|
|
175
|
-
*/
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* BeforeAfterAllCallbackObjectType type.
|
|
179
|
-
* @typedef {object} BeforeAfterAllCallbackObjectType
|
|
180
|
-
* @property {BeforeAfterAllCallbackType} callback - Hook callback to execute.
|
|
181
|
-
*/
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* TestsArgument type.
|
|
185
|
-
* @typedef {object} TestsArgument
|
|
186
|
-
* @property {Record<string, TestData>} args - Arguments keyed by test description.
|
|
187
|
-
* @property {boolean} [anyTestsFocussed] - Whether any tests in the tree are focused.
|
|
188
|
-
* @property {AfterBeforeEachCallbackObjectType[]} afterEaches - After-each hooks for this scope.
|
|
189
|
-
* @property {BeforeAfterAllCallbackObjectType[]} afterAlls - After-all hooks for this scope.
|
|
190
|
-
* @property {BeforeAfterAllCallbackObjectType[]} beforeAlls - Before-all hooks for this scope.
|
|
191
|
-
* @property {AfterBeforeEachCallbackObjectType[]} beforeEaches - Before-each hooks for this scope.
|
|
192
|
-
* @property {string} [filePath] - Source file path.
|
|
193
|
-
* @property {number} [line] - Source line number.
|
|
194
|
-
* @property {Record<string, TestData>} tests - A unique identifier for the node.
|
|
195
|
-
* @property {Record<string, TestsArgument>} subs - Optional child nodes. Each item is another `Node`, allowing recursion.
|
|
196
|
-
*/
|
|
197
|
-
|
|
198
187
|
export default class TestRunner {
|
|
199
188
|
/**
|
|
200
189
|
* Narrows the runtime value to the documented type.
|
|
@@ -539,17 +528,18 @@ export default class TestRunner {
|
|
|
539
528
|
}
|
|
540
529
|
|
|
541
530
|
/**
|
|
542
|
-
*
|
|
543
|
-
*
|
|
544
|
-
*
|
|
545
|
-
*
|
|
546
|
-
*
|
|
547
|
-
*
|
|
548
|
-
* @returns {void}
|
|
531
|
+
* Registers each non-tenant per-test connection as a dynamic candidate for in-process
|
|
532
|
+
* request sharing. The pool evaluates transaction state when each request is dispatched,
|
|
533
|
+
* so a transaction started or ended during a hook callback takes effect immediately.
|
|
534
|
+
* Inactive and tenant-only connections remain independently pooled. Pair with
|
|
535
|
+
* {@link clearTestSharedConnections} in a finally.
|
|
536
|
+
* @returns {{pool: import("../database/pool/base.js").default, registration: import("../database/pool/base.js").TestSharedConnectionRegistration}[]} - Lifecycle-owned registrations.
|
|
549
537
|
*/
|
|
550
538
|
activateTestSharedConnections() {
|
|
551
539
|
const configuration = this.getConfiguration()
|
|
552
540
|
const currentConnections = configuration.getCurrentConnections()
|
|
541
|
+
/** @type {{pool: import("../database/pool/base.js").default, registration: import("../database/pool/base.js").TestSharedConnectionRegistration}[]} */
|
|
542
|
+
const registrations = []
|
|
553
543
|
|
|
554
544
|
for (const identifier of Object.keys(currentConnections)) {
|
|
555
545
|
const pool = configuration.getDatabasePool(identifier)
|
|
@@ -561,16 +551,33 @@ export default class TestRunner {
|
|
|
561
551
|
if (pool.getConfiguration().tenantOnly) {
|
|
562
552
|
continue
|
|
563
553
|
}
|
|
564
|
-
|
|
554
|
+
|
|
555
|
+
const connection = currentConnections[identifier]
|
|
556
|
+
|
|
557
|
+
const registration = pool.setTestSharedConnectionProvider(() => {
|
|
558
|
+
return connection.insideTransaction() ? connection : undefined
|
|
559
|
+
})
|
|
560
|
+
|
|
561
|
+
if (registration) registrations.push({pool, registration})
|
|
565
562
|
}
|
|
563
|
+
|
|
564
|
+
return registrations
|
|
566
565
|
}
|
|
567
566
|
|
|
568
567
|
/**
|
|
569
568
|
* Clears the in-process test shared connection on every configured pool. Idempotent and
|
|
570
569
|
* safe to call when none was set.
|
|
570
|
+
* @param {{pool: import("../database/pool/base.js").default, registration: import("../database/pool/base.js").TestSharedConnectionRegistration}[]} [registrations] - Lifecycle-owned registrations to clear conditionally.
|
|
571
571
|
* @returns {void}
|
|
572
572
|
*/
|
|
573
|
-
clearTestSharedConnections() {
|
|
573
|
+
clearTestSharedConnections(registrations) {
|
|
574
|
+
if (registrations) {
|
|
575
|
+
for (const {pool, registration} of registrations) {
|
|
576
|
+
pool.clearTestSharedConnection(registration)
|
|
577
|
+
}
|
|
578
|
+
return
|
|
579
|
+
}
|
|
580
|
+
|
|
574
581
|
const configuration = this.getConfiguration()
|
|
575
582
|
|
|
576
583
|
for (const identifier of configuration.getDatabaseIdentifiers()) {
|
|
@@ -976,6 +983,8 @@ export default class TestRunner {
|
|
|
976
983
|
* still wait for it to settle after runWithTimeout has abandoned it.
|
|
977
984
|
* @type {Promise<?> | undefined} */
|
|
978
985
|
let testLifecycle
|
|
986
|
+
/** @type {{pool: import("../database/pool/base.js").default, registration: import("../database/pool/base.js").TestSharedConnectionRegistration}[]} */
|
|
987
|
+
let testSharedConnectionRegistrations = []
|
|
979
988
|
/**
|
|
980
989
|
* Shared mutable flag so the catch block can suppress the
|
|
981
990
|
* `_successfulTests` increment inside the still-detached lifecycle:
|
|
@@ -1002,12 +1011,11 @@ export default class TestRunner {
|
|
|
1002
1011
|
// stale async-context pin and force every later test onto a fresh checkout,
|
|
1003
1012
|
// breaking isolation).
|
|
1004
1013
|
await this.getConfiguration().ensureConnections({name: `Test: ${testDescription}`}, async () => {
|
|
1005
|
-
//
|
|
1006
|
-
//
|
|
1007
|
-
//
|
|
1008
|
-
// pools they inspect directly. Cleared after afterEach either way.
|
|
1014
|
+
// Register dynamic candidates before application hooks so transaction
|
|
1015
|
+
// state changes made during a hook are visible to a request dispatched
|
|
1016
|
+
// by that same callback.
|
|
1009
1017
|
if (testArgs.type == "request") {
|
|
1010
|
-
this.activateTestSharedConnections()
|
|
1018
|
+
testSharedConnectionRegistrations = this.activateTestSharedConnections()
|
|
1011
1019
|
}
|
|
1012
1020
|
|
|
1013
1021
|
try {
|
|
@@ -1035,7 +1043,7 @@ export default class TestRunner {
|
|
|
1035
1043
|
}
|
|
1036
1044
|
}
|
|
1037
1045
|
} finally {
|
|
1038
|
-
this.clearTestSharedConnections()
|
|
1046
|
+
this.clearTestSharedConnections(testSharedConnectionRegistrations)
|
|
1039
1047
|
}
|
|
1040
1048
|
})
|
|
1041
1049
|
})
|
|
@@ -1077,7 +1085,7 @@ export default class TestRunner {
|
|
|
1077
1085
|
// ensureConnections before activateTestSharedConnections() replaces
|
|
1078
1086
|
// it. Clear it here so the next test checks out a fresh connection.
|
|
1079
1087
|
// Idempotent when the lifecycle did settle and already cleared.
|
|
1080
|
-
this.clearTestSharedConnections()
|
|
1088
|
+
this.clearTestSharedConnections(testSharedConnectionRegistrations)
|
|
1081
1089
|
}
|
|
1082
1090
|
|
|
1083
1091
|
willRetry = retriesUsed < retryCount
|