velocious 1.0.666 → 1.0.668
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 +40 -11
- package/build/background-jobs/adapter-client.js +22 -0
- package/build/background-jobs/adapter.js +15 -0
- package/build/background-jobs/client.js +147 -1
- package/build/background-jobs/job-semantics.js +30 -0
- package/build/background-jobs/job.js +26 -1
- package/build/background-jobs/local-adapter.js +36 -8
- package/build/background-jobs/local-store.js +410 -39
- package/build/background-jobs/main.js +69 -2
- package/build/background-jobs/platform-job.js +26 -1
- package/build/background-jobs/runtime.js +58 -0
- package/build/background-jobs/store.js +374 -6
- package/build/background-jobs/types.js +27 -2
- package/build/background-jobs/web/controller.js +1 -0
- package/build/configuration.js +30 -6
- package/build/database/drivers/mssql/column.js +2 -2
- package/build/database/drivers/mssql/table.js +19 -5
- package/build/database/pool/async-tracked-multi-connection.js +2 -1
- package/build/database/query/alter-table-base.js +8 -1
- package/build/database/record/attachments/store.js +2 -2
- package/build/environment-handlers/node/cli/commands/test/timing-manifest/merge.js +2 -48
- package/build/environment-handlers/node/cli/commands/test.js +17 -69
- package/build/frontend-model-controller.js +22 -2
- package/build/frontend-models/websocket-channel.js +18 -0
- package/build/http-server/client/websocket-session.js +41 -4
- package/build/http-server/websocket-channel.js +14 -0
- package/build/http-server/websocket-event-log-store.js +85 -12
- package/build/http-server/websocket-events-host.js +17 -7
- package/build/src/background-jobs/adapter-client.d.ts +17 -0
- package/build/src/background-jobs/adapter-client.d.ts.map +1 -1
- package/build/src/background-jobs/adapter-client.js +19 -1
- package/build/src/background-jobs/adapter.d.ts +15 -0
- package/build/src/background-jobs/adapter.d.ts.map +1 -1
- package/build/src/background-jobs/adapter.js +14 -1
- package/build/src/background-jobs/client.d.ts +17 -0
- package/build/src/background-jobs/client.d.ts.map +1 -1
- package/build/src/background-jobs/client.js +129 -1
- package/build/src/background-jobs/job-semantics.d.ts +18 -0
- package/build/src/background-jobs/job-semantics.d.ts.map +1 -1
- package/build/src/background-jobs/job-semantics.js +28 -1
- package/build/src/background-jobs/job.d.ts +15 -0
- package/build/src/background-jobs/job.d.ts.map +1 -1
- package/build/src/background-jobs/job.js +21 -2
- package/build/src/background-jobs/local-adapter.d.ts +23 -8
- package/build/src/background-jobs/local-adapter.d.ts.map +1 -1
- package/build/src/background-jobs/local-adapter.js +35 -9
- package/build/src/background-jobs/local-store.d.ts +120 -5
- package/build/src/background-jobs/local-store.d.ts.map +1 -1
- package/build/src/background-jobs/local-store.js +366 -40
- package/build/src/background-jobs/main.d.ts +24 -2
- package/build/src/background-jobs/main.d.ts.map +1 -1
- package/build/src/background-jobs/main.js +68 -3
- package/build/src/background-jobs/platform-job.d.ts +15 -0
- package/build/src/background-jobs/platform-job.d.ts.map +1 -1
- package/build/src/background-jobs/platform-job.js +19 -2
- package/build/src/background-jobs/runtime.d.ts +39 -0
- package/build/src/background-jobs/runtime.d.ts.map +1 -1
- package/build/src/background-jobs/runtime.js +49 -1
- package/build/src/background-jobs/store.d.ts +100 -1
- package/build/src/background-jobs/store.d.ts.map +1 -1
- package/build/src/background-jobs/store.js +334 -8
- package/build/src/background-jobs/types.d.ts +97 -4
- package/build/src/background-jobs/types.d.ts.map +1 -1
- package/build/src/background-jobs/types.js +28 -3
- package/build/src/background-jobs/web/controller.d.ts.map +1 -1
- package/build/src/background-jobs/web/controller.js +2 -1
- package/build/src/configuration.d.ts +18 -1
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +28 -6
- package/build/src/database/drivers/mssql/column.d.ts.map +1 -1
- package/build/src/database/drivers/mssql/column.js +2 -2
- package/build/src/database/drivers/mssql/table.d.ts.map +1 -1
- package/build/src/database/drivers/mssql/table.js +19 -5
- package/build/src/database/pool/async-tracked-multi-connection.d.ts.map +1 -1
- package/build/src/database/pool/async-tracked-multi-connection.js +3 -2
- package/build/src/database/query/alter-table-base.d.ts.map +1 -1
- package/build/src/database/query/alter-table-base.js +9 -2
- package/build/src/database/record/attachments/store.js +3 -3
- package/build/src/environment-handlers/node/cli/commands/test/timing-manifest/merge.d.ts.map +1 -1
- package/build/src/environment-handlers/node/cli/commands/test/timing-manifest/merge.js +3 -47
- package/build/src/environment-handlers/node/cli/commands/test.d.ts +2 -28
- package/build/src/environment-handlers/node/cli/commands/test.d.ts.map +1 -1
- package/build/src/environment-handlers/node/cli/commands/test.js +10 -61
- package/build/src/frontend-model-controller.d.ts.map +1 -1
- package/build/src/frontend-model-controller.js +18 -3
- package/build/src/frontend-models/websocket-channel.d.ts +9 -0
- package/build/src/frontend-models/websocket-channel.d.ts.map +1 -1
- package/build/src/frontend-models/websocket-channel.js +16 -1
- package/build/src/http-server/client/websocket-session.d.ts +25 -2
- package/build/src/http-server/client/websocket-session.d.ts.map +1 -1
- package/build/src/http-server/client/websocket-session.js +40 -5
- package/build/src/http-server/websocket-channel.d.ts +11 -0
- package/build/src/http-server/websocket-channel.d.ts.map +1 -1
- package/build/src/http-server/websocket-channel.js +14 -1
- package/build/src/http-server/websocket-event-log-store.d.ts +60 -41
- package/build/src/http-server/websocket-event-log-store.d.ts.map +1 -1
- package/build/src/http-server/websocket-event-log-store.js +79 -12
- package/build/src/http-server/websocket-events-host.d.ts +10 -3
- package/build/src/http-server/websocket-events-host.d.ts.map +1 -1
- package/build/src/http-server/websocket-events-host.js +17 -8
- package/build/src/testing/test-files-finder.d.ts +17 -117
- package/build/src/testing/test-files-finder.d.ts.map +1 -1
- package/build/src/testing/test-files-finder.js +29 -294
- package/build/src/testing/test-filter-parser.d.ts +32 -22
- package/build/src/testing/test-filter-parser.d.ts.map +1 -1
- package/build/src/testing/test-filter-parser.js +25 -217
- package/build/src/testing/test-profile-activity.d.ts +1 -6
- package/build/src/testing/test-profile-activity.d.ts.map +1 -1
- package/build/src/testing/test-profile-activity.js +2 -12
- package/build/src/testing/test-profile-output.d.ts +1 -40
- package/build/src/testing/test-profile-output.d.ts.map +1 -1
- package/build/src/testing/test-profile-output.js +2 -217
- package/build/src/testing/test-profiler.d.ts +15 -659
- package/build/src/testing/test-profiler.d.ts.map +1 -1
- package/build/src/testing/test-profiler.js +35 -833
- package/build/src/testing/test-runner.d.ts +10 -1
- package/build/src/testing/test-runner.d.ts.map +1 -1
- package/build/src/testing/test-runner.js +26 -4
- package/build/src/testing/test-suite-splitter.d.ts +1 -128
- package/build/src/testing/test-suite-splitter.d.ts.map +1 -1
- package/build/src/testing/test-suite-splitter.js +2 -259
- package/build/src/testing/timing-manifest.d.ts +5 -93
- package/build/src/testing/timing-manifest.d.ts.map +1 -1
- package/build/src/testing/timing-manifest.js +4 -283
- package/build/src/testing/velocious-attempt-executor.d.ts.map +1 -1
- package/build/src/testing/velocious-attempt-executor.js +8 -2
- package/build/testing/test-files-finder.js +32 -343
- package/build/testing/test-filter-parser.js +29 -248
- package/build/testing/test-profile-activity.js +1 -12
- package/build/testing/test-profile-output.js +8 -253
- package/build/testing/test-profiler.js +38 -895
- package/build/testing/test-runner.js +33 -9
- package/build/testing/test-suite-splitter.js +1 -301
- package/build/testing/timing-manifest.js +10 -344
- package/build/testing/velocious-attempt-executor.js +7 -1
- package/package.json +3 -3
- package/scripts/test-browser.js +30 -6
- package/scripts/verify-docker-dev-environment.js +13 -1
- package/src/background-jobs/adapter-client.js +22 -0
- package/src/background-jobs/adapter.js +15 -0
- package/src/background-jobs/client.js +147 -1
- package/src/background-jobs/job-semantics.js +30 -0
- package/src/background-jobs/job.js +26 -1
- package/src/background-jobs/local-adapter.js +36 -8
- package/src/background-jobs/local-store.js +410 -39
- package/src/background-jobs/main.js +69 -2
- package/src/background-jobs/platform-job.js +26 -1
- package/src/background-jobs/runtime.js +58 -0
- package/src/background-jobs/store.js +374 -6
- package/src/background-jobs/types.js +27 -2
- package/src/background-jobs/web/controller.js +1 -0
- package/src/configuration.js +30 -6
- package/src/database/drivers/mssql/column.js +2 -2
- package/src/database/drivers/mssql/table.js +19 -5
- package/src/database/pool/async-tracked-multi-connection.js +2 -1
- package/src/database/query/alter-table-base.js +8 -1
- package/src/database/record/attachments/store.js +2 -2
- package/src/environment-handlers/node/cli/commands/test/timing-manifest/merge.js +2 -48
- package/src/environment-handlers/node/cli/commands/test.js +17 -69
- package/src/frontend-model-controller.js +22 -2
- package/src/frontend-models/websocket-channel.js +18 -0
- package/src/http-server/client/websocket-session.js +41 -4
- package/src/http-server/websocket-channel.js +14 -0
- package/src/http-server/websocket-event-log-store.js +85 -12
- package/src/http-server/websocket-events-host.js +17 -7
- package/src/testing/test-files-finder.js +32 -343
- package/src/testing/test-filter-parser.js +29 -248
- package/src/testing/test-profile-activity.js +1 -12
- package/src/testing/test-profile-output.js +8 -253
- package/src/testing/test-profiler.js +38 -895
- package/src/testing/test-runner.js +33 -9
- package/src/testing/test-suite-splitter.js +1 -301
- package/src/testing/timing-manifest.js +10 -344
- package/src/testing/velocious-attempt-executor.js +7 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import BaseTable from "../base-table.js"
|
|
4
4
|
import Column from "./column.js"
|
|
5
5
|
import ColumnsIndex from "./columns-index.js"
|
|
6
|
-
import {digg} from "diggerize"
|
|
6
|
+
import { digg } from "diggerize"
|
|
7
7
|
import ForeignKey from "./foreign-key.js"
|
|
8
8
|
import { normalizeIndexMetadataRow } from "../index-metadata.js"
|
|
9
9
|
|
|
@@ -67,10 +67,24 @@ export default class VelociousDatabaseDriversMssqlTable extends BaseTable {
|
|
|
67
67
|
return await this.getDriver()._cachedTableSchemaMetadata(this.getName(), "columns", async () => {
|
|
68
68
|
const result = await this.driver.query(`
|
|
69
69
|
SELECT
|
|
70
|
-
|
|
71
|
-
COLUMNPROPERTY(object_id(TABLE_SCHEMA + '.' + TABLE_NAME), COLUMN_NAME, 'IsIdentity') AS isIdentity
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
columns.*,
|
|
71
|
+
COLUMNPROPERTY(object_id(columns.TABLE_SCHEMA + '.' + columns.TABLE_NAME), columns.COLUMN_NAME, 'IsIdentity') AS isIdentity,
|
|
72
|
+
CASE WHEN keyColumnUsage.COLUMN_NAME IS NOT NULL THEN 1 ELSE 0 END AS isPrimaryKey
|
|
73
|
+
FROM [INFORMATION_SCHEMA].[COLUMNS] AS columns
|
|
74
|
+
LEFT JOIN [INFORMATION_SCHEMA].[TABLE_CONSTRAINTS] AS tableConstraints ON
|
|
75
|
+
tableConstraints.CONSTRAINT_TYPE = 'PRIMARY KEY' AND
|
|
76
|
+
tableConstraints.TABLE_CATALOG = columns.TABLE_CATALOG AND
|
|
77
|
+
tableConstraints.TABLE_SCHEMA = columns.TABLE_SCHEMA AND
|
|
78
|
+
tableConstraints.TABLE_NAME = columns.TABLE_NAME
|
|
79
|
+
LEFT JOIN [INFORMATION_SCHEMA].[KEY_COLUMN_USAGE] AS keyColumnUsage ON
|
|
80
|
+
keyColumnUsage.CONSTRAINT_CATALOG = tableConstraints.CONSTRAINT_CATALOG AND
|
|
81
|
+
keyColumnUsage.CONSTRAINT_SCHEMA = tableConstraints.CONSTRAINT_SCHEMA AND
|
|
82
|
+
keyColumnUsage.CONSTRAINT_NAME = tableConstraints.CONSTRAINT_NAME AND
|
|
83
|
+
keyColumnUsage.TABLE_CATALOG = columns.TABLE_CATALOG AND
|
|
84
|
+
keyColumnUsage.TABLE_SCHEMA = columns.TABLE_SCHEMA AND
|
|
85
|
+
keyColumnUsage.TABLE_NAME = columns.TABLE_NAME AND
|
|
86
|
+
keyColumnUsage.COLUMN_NAME = columns.COLUMN_NAME
|
|
87
|
+
WHERE columns.TABLE_NAME = ${this.driver.quote(this.getName())}
|
|
74
88
|
`)
|
|
75
89
|
const columns = []
|
|
76
90
|
|
|
@@ -998,7 +998,8 @@ export default class VelociousDatabasePoolAsyncTrackedMultiConnection extends Ba
|
|
|
998
998
|
|
|
999
999
|
if (!actualCallback) throw new Error("withConnection requires a callback")
|
|
1000
1000
|
|
|
1001
|
-
const
|
|
1001
|
+
const connectionContextSuppressed = this.asyncLocalStorage.getStore() === SUPPRESSED_CONNECTION_CONTEXT
|
|
1002
|
+
const testSharedConnection = connectionContextSuppressed ? undefined : this.activeTestSharedConnection()
|
|
1002
1003
|
if (testSharedConnection && this.connectionMatchesCurrentConfiguration(testSharedConnection)) {
|
|
1003
1004
|
return await this.asyncLocalStorage.run(testSharedConnection.getIdSeq(), async () => {
|
|
1004
1005
|
return await actualCallback(testSharedConnection)
|
|
@@ -33,6 +33,7 @@ export default class VelociousDatabaseQueryAlterTableBase extends QueryBase {
|
|
|
33
33
|
let sql = `ALTER TABLE ${options.quoteTableName(tableData.getName())} `
|
|
34
34
|
let actionCount = 0
|
|
35
35
|
let previousActionWasMssqlColumnAdd = false
|
|
36
|
+
let previousActionWasMssqlColumnDrop = false
|
|
36
37
|
|
|
37
38
|
for (const column of tableData.getColumns()) {
|
|
38
39
|
if (actionCount > 0) sql += ", "
|
|
@@ -46,6 +47,7 @@ export default class VelociousDatabaseQueryAlterTableBase extends QueryBase {
|
|
|
46
47
|
|
|
47
48
|
sql += column.getSQL({driver: this.getDriver(), forAlterTable: false})
|
|
48
49
|
previousActionWasMssqlColumnAdd = databaseType == "mssql"
|
|
50
|
+
previousActionWasMssqlColumnDrop = false
|
|
49
51
|
} else if (column.getNewName()) {
|
|
50
52
|
const newColumnName = column.getNewName()
|
|
51
53
|
|
|
@@ -53,9 +55,13 @@ export default class VelociousDatabaseQueryAlterTableBase extends QueryBase {
|
|
|
53
55
|
|
|
54
56
|
sql += `RENAME COLUMN ${options.quoteColumnName(column.getName())} TO ${options.quoteColumnName(newColumnName)}`
|
|
55
57
|
previousActionWasMssqlColumnAdd = false
|
|
58
|
+
previousActionWasMssqlColumnDrop = false
|
|
56
59
|
} else if (column.getDropColumn()) {
|
|
57
|
-
sql +=
|
|
60
|
+
if (databaseType != "mssql" || !previousActionWasMssqlColumnDrop) sql += "DROP COLUMN "
|
|
61
|
+
|
|
62
|
+
sql += options.quoteColumnName(column.getName())
|
|
58
63
|
previousActionWasMssqlColumnAdd = false
|
|
64
|
+
previousActionWasMssqlColumnDrop = databaseType == "mssql"
|
|
59
65
|
} else {
|
|
60
66
|
if (databaseType == "mssql" || databaseType == "pgsql") {
|
|
61
67
|
sql += "ALTER COLUMN "
|
|
@@ -65,6 +71,7 @@ export default class VelociousDatabaseQueryAlterTableBase extends QueryBase {
|
|
|
65
71
|
|
|
66
72
|
sql += column.getSQL({driver: this.getDriver(), forAlterTable: true})
|
|
67
73
|
previousActionWasMssqlColumnAdd = false
|
|
74
|
+
previousActionWasMssqlColumnDrop = false
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
import {createHash} from "node:crypto"
|
|
4
3
|
import UUID from "pure-uuid"
|
|
5
4
|
import TableData from "../../table-data/index.js"
|
|
6
5
|
import TableIndex from "../../table-data/table-index.js"
|
|
7
6
|
import {modelPrimaryKeyCacheKey} from "../../../utils/model-primary-key.js"
|
|
7
|
+
import sha256Hex from "../../../utils/sha256-hex.js"
|
|
8
8
|
import normalizeRecordAttachmentInput from "./normalize-input.js"
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -44,7 +44,7 @@ function attachmentRecordId(model) {
|
|
|
44
44
|
* @returns {string} - SHA-256 digest.
|
|
45
45
|
*/
|
|
46
46
|
function attachmentRecordIdDigest(recordId) {
|
|
47
|
-
return
|
|
47
|
+
return sha256Hex(recordId)
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
import BaseCommand from "../../../../../../cli/base-command.js"
|
|
4
|
+
import { parseTimingManifestMergeArguments as parsePackageTimingManifestMergeArguments } from "@velocious/testing/node"
|
|
4
5
|
import fs from "node:fs/promises"
|
|
5
|
-
import path from "node:path"
|
|
6
6
|
import { writeTimingManifest } from "../../../../../../testing/test-profile-output.js"
|
|
7
7
|
import { mergeTestProfileTimingManifests } from "../../../../../../testing/timing-manifest.js"
|
|
8
8
|
|
|
@@ -51,24 +51,6 @@ export default class TestTimingManifestMerge extends BaseCommand {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
/**
|
|
55
|
-
* Recognizes one output option spelling.
|
|
56
|
-
* @param {string} argument - Current argument.
|
|
57
|
-
* @param {string | undefined} nextArgument - Following argument.
|
|
58
|
-
* @returns {{matched: boolean, skipNext: boolean, value: string | undefined}} - Parsed output option.
|
|
59
|
-
*/
|
|
60
|
-
function timingManifestOutputArgument(argument, nextArgument) {
|
|
61
|
-
if (argument === "--output") {
|
|
62
|
-
return {matched: true, skipNext: true, value: nextArgument}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (argument.startsWith("--output=")) {
|
|
66
|
-
return {matched: true, skipNext: false, value: argument.slice("--output=".length)}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return {matched: false, skipNext: false, value: undefined}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
54
|
/**
|
|
73
55
|
* Parses strict merge arguments and resolves their paths.
|
|
74
56
|
* @param {string[]} processArgs - Raw CLI arguments, including command name.
|
|
@@ -76,33 +58,5 @@ function timingManifestOutputArgument(argument, nextArgument) {
|
|
|
76
58
|
* @returns {TimingManifestMergeArguments} - Validated resolved paths.
|
|
77
59
|
*/
|
|
78
60
|
export function parseTimingManifestMergeArguments(processArgs, cwd) {
|
|
79
|
-
|
|
80
|
-
const inputPaths = []
|
|
81
|
-
let outputPath
|
|
82
|
-
|
|
83
|
-
for (let index = 1; index < processArgs.length; index++) {
|
|
84
|
-
const argument = processArgs[index]
|
|
85
|
-
const outputArgument = timingManifestOutputArgument(argument, processArgs[index + 1])
|
|
86
|
-
|
|
87
|
-
if (outputArgument.matched) {
|
|
88
|
-
if (!outputArgument.value || outputArgument.value.startsWith("-")) throw new Error("Missing value for --output")
|
|
89
|
-
if (outputPath) throw new Error("--output may only be provided once")
|
|
90
|
-
outputPath = path.resolve(cwd, outputArgument.value)
|
|
91
|
-
if (outputArgument.skipNext) index++
|
|
92
|
-
continue
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (argument.startsWith("-")) throw new Error(`Unknown argument for ${commandName}: ${argument}`)
|
|
96
|
-
inputPaths.push(path.resolve(cwd, argument))
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (!outputPath) throw new Error("--output is required")
|
|
100
|
-
if (inputPaths.length === 0) throw new Error("At least one rich test profile input is required")
|
|
101
|
-
|
|
102
|
-
const uniqueInputPaths = new Set(inputPaths)
|
|
103
|
-
|
|
104
|
-
if (uniqueInputPaths.size !== inputPaths.length) throw new Error("Each rich test profile input must be provided once")
|
|
105
|
-
if (uniqueInputPaths.has(outputPath)) throw new Error("Timing manifest output must not overwrite an input profile")
|
|
106
|
-
|
|
107
|
-
return {inputPaths, outputPath}
|
|
61
|
+
return parsePackageTimingManifestMergeArguments(processArgs.slice(1), {cwd})
|
|
108
62
|
}
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
import BaseCommand from "../../../../cli/base-command.js"
|
|
4
|
-
import fs from "fs/promises"
|
|
5
4
|
import path from "node:path"
|
|
6
5
|
import picocolors from "picocolors"
|
|
7
6
|
import TestFilesFinder from "../../../../testing/test-files-finder.js"
|
|
8
7
|
import TestProfiler from "../../../../testing/test-profiler.js"
|
|
9
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
formatTestProfileSummary,
|
|
10
|
+
loadTimingManifest,
|
|
11
|
+
resolveTestProfileOptions,
|
|
12
|
+
writeTestProfileOutputs
|
|
13
|
+
} from "../../../../testing/test-profile-output.js"
|
|
10
14
|
import TestRunner from "../../../../testing/test-runner.js"
|
|
11
15
|
import TestSuiteSplitter from "../../../../testing/test-suite-splitter.js"
|
|
12
16
|
import { normalizeExamplePatterns, parseFilters } from "../../../../testing/test-filter-parser.js"
|
|
13
17
|
import {
|
|
14
18
|
canonicalTimingManifestPath,
|
|
15
|
-
timingManifestFileSetHash
|
|
16
|
-
validateTimingManifest
|
|
19
|
+
timingManifestFileSetHash
|
|
17
20
|
} from "../../../../testing/timing-manifest.js"
|
|
18
21
|
import { prepareSourcePeerPackage } from "../../source-peer-package.js"
|
|
19
22
|
|
|
@@ -44,8 +47,11 @@ export default class VelociousCliCommandsTest extends BaseCommand {
|
|
|
44
47
|
groupNumber,
|
|
45
48
|
profile,
|
|
46
49
|
profileJsonPath,
|
|
50
|
+
retries,
|
|
51
|
+
setupFiles,
|
|
47
52
|
timingManifestPath,
|
|
48
|
-
timingManifestOutputPath
|
|
53
|
+
timingManifestOutputPath,
|
|
54
|
+
timeoutMs
|
|
49
55
|
} = parseFilters(this.processArgs || [])
|
|
50
56
|
const profileOptions = resolveTestProfileOptions({
|
|
51
57
|
cwd: process.cwd(),
|
|
@@ -70,7 +76,7 @@ export default class VelociousCliCommandsTest extends BaseCommand {
|
|
|
70
76
|
|
|
71
77
|
/**
|
|
72
78
|
* Finalizes requested outputs once for every command outcome.
|
|
73
|
-
* @param {
|
|
79
|
+
* @param {import("@velocious/testing/node").TestProfileStatus} status - Run status.
|
|
74
80
|
* @returns {Promise<void>} - Resolves after requested outputs are written.
|
|
75
81
|
*/
|
|
76
82
|
const finalizeProfile = async (status) => {
|
|
@@ -157,7 +163,10 @@ export default class VelociousCliCommandsTest extends BaseCommand {
|
|
|
157
163
|
testFiles,
|
|
158
164
|
lineFilters: testFilesFinder.getLineFiltersByFile(),
|
|
159
165
|
examplePatterns: normalizeExamplePatterns(examplePatterns),
|
|
160
|
-
profiler
|
|
166
|
+
profiler,
|
|
167
|
+
retries,
|
|
168
|
+
setupFiles: setupFiles.map((setupFile) => path.resolve(process.cwd(), setupFile)),
|
|
169
|
+
timeoutMs
|
|
161
170
|
})
|
|
162
171
|
const activeTestRunner = testRunner
|
|
163
172
|
let signalHandled = false
|
|
@@ -272,68 +281,7 @@ export default class VelociousCliCommandsTest extends BaseCommand {
|
|
|
272
281
|
}
|
|
273
282
|
}
|
|
274
283
|
|
|
275
|
-
|
|
276
|
-
* Resolves and validates profiling paths before test discovery starts.
|
|
277
|
-
* @param {object} args - Raw profiling options.
|
|
278
|
-
* @param {string} args.cwd - Command working directory.
|
|
279
|
-
* @param {boolean} args.profile - Whether console profiling was requested.
|
|
280
|
-
* @param {string} [args.profileJsonPath] - Rich profile output path.
|
|
281
|
-
* @param {string} [args.timingManifestPath] - Timing manifest input path.
|
|
282
|
-
* @param {string} [args.timingManifestOutputPath] - Timing manifest output path.
|
|
283
|
-
* @returns {{profile: boolean, profileJsonPath: string | undefined, timingManifestPath: string | undefined, timingManifestOutputPath: string | undefined}} - Resolved profiling options.
|
|
284
|
-
*/
|
|
285
|
-
export function resolveTestProfileOptions({cwd, profile, profileJsonPath, timingManifestPath, timingManifestOutputPath}) {
|
|
286
|
-
const resolvedProfileJsonPath = profileJsonPath ? path.resolve(cwd, profileJsonPath) : undefined
|
|
287
|
-
const resolvedTimingManifestPath = timingManifestPath ? path.resolve(cwd, timingManifestPath) : undefined
|
|
288
|
-
const resolvedTimingManifestOutputPath = timingManifestOutputPath
|
|
289
|
-
? path.resolve(cwd, timingManifestOutputPath)
|
|
290
|
-
: undefined
|
|
291
|
-
|
|
292
|
-
if (resolvedProfileJsonPath && resolvedTimingManifestOutputPath && resolvedProfileJsonPath === resolvedTimingManifestOutputPath) {
|
|
293
|
-
throw new Error("Test profiling output paths must be different")
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (resolvedTimingManifestPath && (
|
|
297
|
-
resolvedProfileJsonPath === resolvedTimingManifestPath ||
|
|
298
|
-
resolvedTimingManifestOutputPath === resolvedTimingManifestPath
|
|
299
|
-
)) {
|
|
300
|
-
throw new Error("Test profiling outputs must not overwrite --timing-manifest input")
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
return {
|
|
304
|
-
profile: profile || Boolean(resolvedProfileJsonPath || resolvedTimingManifestOutputPath),
|
|
305
|
-
profileJsonPath: resolvedProfileJsonPath,
|
|
306
|
-
timingManifestPath: resolvedTimingManifestPath,
|
|
307
|
-
timingManifestOutputPath: resolvedTimingManifestOutputPath
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* Loads and validates an explicitly supplied plain JSON timing manifest.
|
|
313
|
-
* @param {string | undefined} timingManifestPath - Timing manifest path.
|
|
314
|
-
* @returns {Promise<Record<string, number> | undefined>} - Canonical manifest, or undefined when not requested.
|
|
315
|
-
*/
|
|
316
|
-
export async function loadTimingManifest(timingManifestPath) {
|
|
317
|
-
if (!timingManifestPath) return undefined
|
|
318
|
-
|
|
319
|
-
let content
|
|
320
|
-
|
|
321
|
-
try {
|
|
322
|
-
content = await fs.readFile(timingManifestPath, "utf8")
|
|
323
|
-
} catch (error) {
|
|
324
|
-
throw new Error(`Failed to read timing manifest: ${timingManifestPath}`, {cause: error})
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
let parsed
|
|
328
|
-
|
|
329
|
-
try {
|
|
330
|
-
parsed = JSON.parse(content)
|
|
331
|
-
} catch (error) {
|
|
332
|
-
throw new Error(`Failed to parse timing manifest: ${timingManifestPath}`, {cause: error})
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
return validateTimingManifest(parsed, {source: `Timing manifest ${timingManifestPath}`})
|
|
336
|
-
}
|
|
284
|
+
export { loadTimingManifest, resolveTestProfileOptions }
|
|
337
285
|
|
|
338
286
|
/**
|
|
339
287
|
* Resolves how many slowest tests to report from the `VELOCIOUS_SLOW_TEST_COUNT`
|
|
@@ -4456,16 +4456,36 @@ export default class FrontendModelController extends Controller {
|
|
|
4456
4456
|
|
|
4457
4457
|
try {
|
|
4458
4458
|
let indexPayload = {}
|
|
4459
|
+
let hasLegacyAuthenticationToken = false
|
|
4460
|
+
let legacyAuthenticationToken
|
|
4459
4461
|
|
|
4460
4462
|
if (commandType === "index") {
|
|
4463
|
+
let indexPayloadInput = payload
|
|
4464
|
+
|
|
4465
|
+
if (isPlainObject(payload) && Object.hasOwn(payload, "authenticationToken")) {
|
|
4466
|
+
const {authenticationToken, ...payloadWithoutAuthenticationToken} = payload
|
|
4467
|
+
|
|
4468
|
+
hasLegacyAuthenticationToken = true
|
|
4469
|
+
legacyAuthenticationToken = authenticationToken
|
|
4470
|
+
indexPayloadInput = payloadWithoutAuthenticationToken
|
|
4471
|
+
}
|
|
4472
|
+
|
|
4461
4473
|
try {
|
|
4462
|
-
indexPayload = assertFrontendModelIndexPayload(
|
|
4474
|
+
indexPayload = assertFrontendModelIndexPayload(indexPayloadInput)
|
|
4463
4475
|
} catch (error) {
|
|
4464
4476
|
throwFrontendModelQueryErrorForParserError(error)
|
|
4465
4477
|
}
|
|
4466
4478
|
}
|
|
4467
4479
|
|
|
4468
|
-
|
|
4480
|
+
let requestContext = captureFrontendModelRemoteRequestContext(requestEntry?.requestContext)
|
|
4481
|
+
|
|
4482
|
+
if (hasLegacyAuthenticationToken && !Object.hasOwn(requestContext, "authenticationToken")) {
|
|
4483
|
+
requestContext = captureFrontendModelRemoteRequestContext({
|
|
4484
|
+
...requestContext,
|
|
4485
|
+
authenticationToken: legacyAuthenticationToken
|
|
4486
|
+
})
|
|
4487
|
+
}
|
|
4488
|
+
|
|
4469
4489
|
let responsePayload
|
|
4470
4490
|
|
|
4471
4491
|
if (isBuiltInCommand) {
|
|
@@ -389,6 +389,24 @@ export default class FrontendModelWebsocketChannel extends VelociousWebsocketCha
|
|
|
389
389
|
return broadcastParams?.model === this._modelName()
|
|
390
390
|
}
|
|
391
391
|
|
|
392
|
+
/**
|
|
393
|
+
* Drops the server-only destroy-authorization snapshot before replay
|
|
394
|
+
* persistence. The snapshot is what makes replayed destroy events
|
|
395
|
+
* require a client resync, and the pre-delete row it captures must
|
|
396
|
+
* never be stored.
|
|
397
|
+
* @param {Record<string, import("./query.js").FrontendModelTransportValue> | null | undefined} broadcastParams - Params from `broadcastToChannel`.
|
|
398
|
+
* @returns {Record<string, import("./query.js").FrontendModelTransportValue> | null} - Persistable routing params.
|
|
399
|
+
*/
|
|
400
|
+
static replayableBroadcastParams(broadcastParams) {
|
|
401
|
+
if (!broadcastParams) return null
|
|
402
|
+
|
|
403
|
+
const replayableParams = {...broadcastParams}
|
|
404
|
+
|
|
405
|
+
delete replayableParams.destroyAuthorizationRecord
|
|
406
|
+
|
|
407
|
+
return replayableParams
|
|
408
|
+
}
|
|
409
|
+
|
|
392
410
|
/**
|
|
393
411
|
* Runs debug snapshot.
|
|
394
412
|
* @returns {Record<string, ReturnType<typeof JSON.parse>>} Debug-safe subscription details.
|
|
@@ -1843,8 +1843,13 @@ export default class VelociousHttpServerClientWebsocketSession {
|
|
|
1843
1843
|
|
|
1844
1844
|
/**
|
|
1845
1845
|
* Replays missed events from the persistent event-log store for a
|
|
1846
|
-
* channel subscription that provided `lastEventId`.
|
|
1847
|
-
*
|
|
1846
|
+
* channel subscription that provided `lastEventId`. Delivery is
|
|
1847
|
+
* stream-scoped: each persisted event's broadcast params are
|
|
1848
|
+
* re-applied through the subscription's `matches()` — the same routing
|
|
1849
|
+
* decision live delivery uses — so the subscription only replays events
|
|
1850
|
+
* from its own stream of the channel. A checkpoint that belongs to a
|
|
1851
|
+
* different stream (or is no longer retained) yields
|
|
1852
|
+
* `channel-replay-gap` instead of cross-stream replay.
|
|
1848
1853
|
* @param {object} args - Options.
|
|
1849
1854
|
* @param {string} args.channelType - Channel type name (event-log key).
|
|
1850
1855
|
* @param {string} args.lastEventId - Client's last-seen event id.
|
|
@@ -1858,7 +1863,7 @@ export default class VelociousHttpServerClientWebsocketSession {
|
|
|
1858
1863
|
|
|
1859
1864
|
const checkpoint = await store.getEventById({channel: channelType, id: lastEventId})
|
|
1860
1865
|
|
|
1861
|
-
if (!checkpoint) {
|
|
1866
|
+
if (!checkpoint || !this._replayEventMatchesSubscription({channelType, event: checkpoint, subscription})) {
|
|
1862
1867
|
this.sendJson({
|
|
1863
1868
|
type: "channel-replay-gap",
|
|
1864
1869
|
subscriptionId: subscription.subscriptionId,
|
|
@@ -1880,6 +1885,8 @@ export default class VelociousHttpServerClientWebsocketSession {
|
|
|
1880
1885
|
for (const event of events) {
|
|
1881
1886
|
if (subscription.isClosed()) break
|
|
1882
1887
|
|
|
1888
|
+
if (!this._replayEventMatchesSubscription({channelType, event, subscription})) continue
|
|
1889
|
+
|
|
1883
1890
|
if (await subscription._requiresReplayGap(event.payload)) {
|
|
1884
1891
|
this.sendJson({
|
|
1885
1892
|
type: "channel-replay-gap",
|
|
@@ -1891,11 +1898,41 @@ export default class VelociousHttpServerClientWebsocketSession {
|
|
|
1891
1898
|
|
|
1892
1899
|
await subscription.deliverBroadcast(
|
|
1893
1900
|
/** @type {import("../websocket-channel.js").WebsocketJsonValue} */ (event.payload),
|
|
1894
|
-
{
|
|
1901
|
+
{
|
|
1902
|
+
...(event.params !== null ? {broadcastParams: event.params} : {}),
|
|
1903
|
+
eventId: event.id
|
|
1904
|
+
}
|
|
1895
1905
|
)
|
|
1896
1906
|
}
|
|
1897
1907
|
}
|
|
1898
1908
|
|
|
1909
|
+
/**
|
|
1910
|
+
* Whether a persisted replay event belongs to this subscription's
|
|
1911
|
+
* stream. A `matches()` failure means the stream membership cannot be
|
|
1912
|
+
* proven, so the event is treated as not matching — the same isolation
|
|
1913
|
+
* live delivery applies to a broken `matches()`.
|
|
1914
|
+
* @param {object} args - Options.
|
|
1915
|
+
* @param {string} args.channelType - Channel type name.
|
|
1916
|
+
* @param {{params: Record<string, ReturnType<typeof JSON.parse>> | null}} args.event - Persisted replay event.
|
|
1917
|
+
* @param {import("../websocket-channel.js").default} args.subscription - Live subscription.
|
|
1918
|
+
* @returns {boolean} - Whether the event belongs to the subscription's stream.
|
|
1919
|
+
*/
|
|
1920
|
+
_replayEventMatchesSubscription({channelType, event, subscription}) {
|
|
1921
|
+
try {
|
|
1922
|
+
return Boolean(subscription.matches(event.params || {}))
|
|
1923
|
+
} catch (caughtError) {
|
|
1924
|
+
const error = this._reportUnexpectedDispatchError(caughtError, {
|
|
1925
|
+
channelType,
|
|
1926
|
+
stage: "websocket-channel-replay",
|
|
1927
|
+
subscriptionId: subscription.subscriptionId
|
|
1928
|
+
})
|
|
1929
|
+
|
|
1930
|
+
this.logger.error(() => [`Websocket replay subscription ${subscription.subscriptionId} matches() threw`, error])
|
|
1931
|
+
|
|
1932
|
+
return false
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1899
1936
|
/**
|
|
1900
1937
|
* Handles `{type: "channel-unsubscribe"}` from the client — calls
|
|
1901
1938
|
* `unsubscribed()` and sends `channel-unsubscribed`.
|
|
@@ -94,6 +94,20 @@ export default class VelociousWebsocketChannel {
|
|
|
94
94
|
*/
|
|
95
95
|
matches(..._broadcastArgs) { return true }
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Returns the broadcast params that may be persisted for replay.
|
|
99
|
+
* Persisted params are re-applied through `matches(broadcastParams)`
|
|
100
|
+
* when replaying missed events, so they must be JSON-serializable and
|
|
101
|
+
* safe to store. Override when `broadcastParams` carries server-only
|
|
102
|
+
* values that must never reach the event log (e.g. authorization
|
|
103
|
+
* snapshots).
|
|
104
|
+
* @param {WebsocketParams | null | undefined} broadcastParams - Params passed to `broadcastToChannel`.
|
|
105
|
+
* @returns {WebsocketParams | null} - Params to persist, or null to store none.
|
|
106
|
+
*/
|
|
107
|
+
static replayableBroadcastParams(broadcastParams) {
|
|
108
|
+
return broadcastParams ?? null
|
|
109
|
+
}
|
|
110
|
+
|
|
97
111
|
/**
|
|
98
112
|
* Whether replaying a persisted broadcast would require a client resync.
|
|
99
113
|
* Subclasses override this when replay storage deliberately omits metadata
|