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.
Files changed (174) hide show
  1. package/README.md +40 -11
  2. package/build/background-jobs/adapter-client.js +22 -0
  3. package/build/background-jobs/adapter.js +15 -0
  4. package/build/background-jobs/client.js +147 -1
  5. package/build/background-jobs/job-semantics.js +30 -0
  6. package/build/background-jobs/job.js +26 -1
  7. package/build/background-jobs/local-adapter.js +36 -8
  8. package/build/background-jobs/local-store.js +410 -39
  9. package/build/background-jobs/main.js +69 -2
  10. package/build/background-jobs/platform-job.js +26 -1
  11. package/build/background-jobs/runtime.js +58 -0
  12. package/build/background-jobs/store.js +374 -6
  13. package/build/background-jobs/types.js +27 -2
  14. package/build/background-jobs/web/controller.js +1 -0
  15. package/build/configuration.js +30 -6
  16. package/build/database/drivers/mssql/column.js +2 -2
  17. package/build/database/drivers/mssql/table.js +19 -5
  18. package/build/database/pool/async-tracked-multi-connection.js +2 -1
  19. package/build/database/query/alter-table-base.js +8 -1
  20. package/build/database/record/attachments/store.js +2 -2
  21. package/build/environment-handlers/node/cli/commands/test/timing-manifest/merge.js +2 -48
  22. package/build/environment-handlers/node/cli/commands/test.js +17 -69
  23. package/build/frontend-model-controller.js +22 -2
  24. package/build/frontend-models/websocket-channel.js +18 -0
  25. package/build/http-server/client/websocket-session.js +41 -4
  26. package/build/http-server/websocket-channel.js +14 -0
  27. package/build/http-server/websocket-event-log-store.js +85 -12
  28. package/build/http-server/websocket-events-host.js +17 -7
  29. package/build/src/background-jobs/adapter-client.d.ts +17 -0
  30. package/build/src/background-jobs/adapter-client.d.ts.map +1 -1
  31. package/build/src/background-jobs/adapter-client.js +19 -1
  32. package/build/src/background-jobs/adapter.d.ts +15 -0
  33. package/build/src/background-jobs/adapter.d.ts.map +1 -1
  34. package/build/src/background-jobs/adapter.js +14 -1
  35. package/build/src/background-jobs/client.d.ts +17 -0
  36. package/build/src/background-jobs/client.d.ts.map +1 -1
  37. package/build/src/background-jobs/client.js +129 -1
  38. package/build/src/background-jobs/job-semantics.d.ts +18 -0
  39. package/build/src/background-jobs/job-semantics.d.ts.map +1 -1
  40. package/build/src/background-jobs/job-semantics.js +28 -1
  41. package/build/src/background-jobs/job.d.ts +15 -0
  42. package/build/src/background-jobs/job.d.ts.map +1 -1
  43. package/build/src/background-jobs/job.js +21 -2
  44. package/build/src/background-jobs/local-adapter.d.ts +23 -8
  45. package/build/src/background-jobs/local-adapter.d.ts.map +1 -1
  46. package/build/src/background-jobs/local-adapter.js +35 -9
  47. package/build/src/background-jobs/local-store.d.ts +120 -5
  48. package/build/src/background-jobs/local-store.d.ts.map +1 -1
  49. package/build/src/background-jobs/local-store.js +366 -40
  50. package/build/src/background-jobs/main.d.ts +24 -2
  51. package/build/src/background-jobs/main.d.ts.map +1 -1
  52. package/build/src/background-jobs/main.js +68 -3
  53. package/build/src/background-jobs/platform-job.d.ts +15 -0
  54. package/build/src/background-jobs/platform-job.d.ts.map +1 -1
  55. package/build/src/background-jobs/platform-job.js +19 -2
  56. package/build/src/background-jobs/runtime.d.ts +39 -0
  57. package/build/src/background-jobs/runtime.d.ts.map +1 -1
  58. package/build/src/background-jobs/runtime.js +49 -1
  59. package/build/src/background-jobs/store.d.ts +100 -1
  60. package/build/src/background-jobs/store.d.ts.map +1 -1
  61. package/build/src/background-jobs/store.js +334 -8
  62. package/build/src/background-jobs/types.d.ts +97 -4
  63. package/build/src/background-jobs/types.d.ts.map +1 -1
  64. package/build/src/background-jobs/types.js +28 -3
  65. package/build/src/background-jobs/web/controller.d.ts.map +1 -1
  66. package/build/src/background-jobs/web/controller.js +2 -1
  67. package/build/src/configuration.d.ts +18 -1
  68. package/build/src/configuration.d.ts.map +1 -1
  69. package/build/src/configuration.js +28 -6
  70. package/build/src/database/drivers/mssql/column.d.ts.map +1 -1
  71. package/build/src/database/drivers/mssql/column.js +2 -2
  72. package/build/src/database/drivers/mssql/table.d.ts.map +1 -1
  73. package/build/src/database/drivers/mssql/table.js +19 -5
  74. package/build/src/database/pool/async-tracked-multi-connection.d.ts.map +1 -1
  75. package/build/src/database/pool/async-tracked-multi-connection.js +3 -2
  76. package/build/src/database/query/alter-table-base.d.ts.map +1 -1
  77. package/build/src/database/query/alter-table-base.js +9 -2
  78. package/build/src/database/record/attachments/store.js +3 -3
  79. package/build/src/environment-handlers/node/cli/commands/test/timing-manifest/merge.d.ts.map +1 -1
  80. package/build/src/environment-handlers/node/cli/commands/test/timing-manifest/merge.js +3 -47
  81. package/build/src/environment-handlers/node/cli/commands/test.d.ts +2 -28
  82. package/build/src/environment-handlers/node/cli/commands/test.d.ts.map +1 -1
  83. package/build/src/environment-handlers/node/cli/commands/test.js +10 -61
  84. package/build/src/frontend-model-controller.d.ts.map +1 -1
  85. package/build/src/frontend-model-controller.js +18 -3
  86. package/build/src/frontend-models/websocket-channel.d.ts +9 -0
  87. package/build/src/frontend-models/websocket-channel.d.ts.map +1 -1
  88. package/build/src/frontend-models/websocket-channel.js +16 -1
  89. package/build/src/http-server/client/websocket-session.d.ts +25 -2
  90. package/build/src/http-server/client/websocket-session.d.ts.map +1 -1
  91. package/build/src/http-server/client/websocket-session.js +40 -5
  92. package/build/src/http-server/websocket-channel.d.ts +11 -0
  93. package/build/src/http-server/websocket-channel.d.ts.map +1 -1
  94. package/build/src/http-server/websocket-channel.js +14 -1
  95. package/build/src/http-server/websocket-event-log-store.d.ts +60 -41
  96. package/build/src/http-server/websocket-event-log-store.d.ts.map +1 -1
  97. package/build/src/http-server/websocket-event-log-store.js +79 -12
  98. package/build/src/http-server/websocket-events-host.d.ts +10 -3
  99. package/build/src/http-server/websocket-events-host.d.ts.map +1 -1
  100. package/build/src/http-server/websocket-events-host.js +17 -8
  101. package/build/src/testing/test-files-finder.d.ts +17 -117
  102. package/build/src/testing/test-files-finder.d.ts.map +1 -1
  103. package/build/src/testing/test-files-finder.js +29 -294
  104. package/build/src/testing/test-filter-parser.d.ts +32 -22
  105. package/build/src/testing/test-filter-parser.d.ts.map +1 -1
  106. package/build/src/testing/test-filter-parser.js +25 -217
  107. package/build/src/testing/test-profile-activity.d.ts +1 -6
  108. package/build/src/testing/test-profile-activity.d.ts.map +1 -1
  109. package/build/src/testing/test-profile-activity.js +2 -12
  110. package/build/src/testing/test-profile-output.d.ts +1 -40
  111. package/build/src/testing/test-profile-output.d.ts.map +1 -1
  112. package/build/src/testing/test-profile-output.js +2 -217
  113. package/build/src/testing/test-profiler.d.ts +15 -659
  114. package/build/src/testing/test-profiler.d.ts.map +1 -1
  115. package/build/src/testing/test-profiler.js +35 -833
  116. package/build/src/testing/test-runner.d.ts +10 -1
  117. package/build/src/testing/test-runner.d.ts.map +1 -1
  118. package/build/src/testing/test-runner.js +26 -4
  119. package/build/src/testing/test-suite-splitter.d.ts +1 -128
  120. package/build/src/testing/test-suite-splitter.d.ts.map +1 -1
  121. package/build/src/testing/test-suite-splitter.js +2 -259
  122. package/build/src/testing/timing-manifest.d.ts +5 -93
  123. package/build/src/testing/timing-manifest.d.ts.map +1 -1
  124. package/build/src/testing/timing-manifest.js +4 -283
  125. package/build/src/testing/velocious-attempt-executor.d.ts.map +1 -1
  126. package/build/src/testing/velocious-attempt-executor.js +8 -2
  127. package/build/testing/test-files-finder.js +32 -343
  128. package/build/testing/test-filter-parser.js +29 -248
  129. package/build/testing/test-profile-activity.js +1 -12
  130. package/build/testing/test-profile-output.js +8 -253
  131. package/build/testing/test-profiler.js +38 -895
  132. package/build/testing/test-runner.js +33 -9
  133. package/build/testing/test-suite-splitter.js +1 -301
  134. package/build/testing/timing-manifest.js +10 -344
  135. package/build/testing/velocious-attempt-executor.js +7 -1
  136. package/package.json +3 -3
  137. package/scripts/test-browser.js +30 -6
  138. package/scripts/verify-docker-dev-environment.js +13 -1
  139. package/src/background-jobs/adapter-client.js +22 -0
  140. package/src/background-jobs/adapter.js +15 -0
  141. package/src/background-jobs/client.js +147 -1
  142. package/src/background-jobs/job-semantics.js +30 -0
  143. package/src/background-jobs/job.js +26 -1
  144. package/src/background-jobs/local-adapter.js +36 -8
  145. package/src/background-jobs/local-store.js +410 -39
  146. package/src/background-jobs/main.js +69 -2
  147. package/src/background-jobs/platform-job.js +26 -1
  148. package/src/background-jobs/runtime.js +58 -0
  149. package/src/background-jobs/store.js +374 -6
  150. package/src/background-jobs/types.js +27 -2
  151. package/src/background-jobs/web/controller.js +1 -0
  152. package/src/configuration.js +30 -6
  153. package/src/database/drivers/mssql/column.js +2 -2
  154. package/src/database/drivers/mssql/table.js +19 -5
  155. package/src/database/pool/async-tracked-multi-connection.js +2 -1
  156. package/src/database/query/alter-table-base.js +8 -1
  157. package/src/database/record/attachments/store.js +2 -2
  158. package/src/environment-handlers/node/cli/commands/test/timing-manifest/merge.js +2 -48
  159. package/src/environment-handlers/node/cli/commands/test.js +17 -69
  160. package/src/frontend-model-controller.js +22 -2
  161. package/src/frontend-models/websocket-channel.js +18 -0
  162. package/src/http-server/client/websocket-session.js +41 -4
  163. package/src/http-server/websocket-channel.js +14 -0
  164. package/src/http-server/websocket-event-log-store.js +85 -12
  165. package/src/http-server/websocket-events-host.js +17 -7
  166. package/src/testing/test-files-finder.js +32 -343
  167. package/src/testing/test-filter-parser.js +29 -248
  168. package/src/testing/test-profile-activity.js +1 -12
  169. package/src/testing/test-profile-output.js +8 -253
  170. package/src/testing/test-profiler.js +38 -895
  171. package/src/testing/test-runner.js +33 -9
  172. package/src/testing/test-suite-splitter.js +1 -301
  173. package/src/testing/timing-manifest.js +10 -344
  174. 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
- FROM [INFORMATION_SCHEMA].[COLUMNS]
73
- WHERE [TABLE_NAME] = ${this.driver.quote(this.getName())}
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 testSharedConnection = this.activeTestSharedConnection()
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 += `DROP COLUMN ${options.quoteColumnName(column.getName())}`
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 createHash("sha256").update(recordId).digest("hex")
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
- const commandName = processArgs[0] || "test:timing-manifest:merge"
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 { formatTestProfileSummary, writeTestProfileOutputs } from "../../../../testing/test-profile-output.js"
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 {string} status - Run status.
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(payload)
4474
+ indexPayload = assertFrontendModelIndexPayload(indexPayloadInput)
4463
4475
  } catch (error) {
4464
4476
  throwFrontendModelQueryErrorForParserError(error)
4465
4477
  }
4466
4478
  }
4467
4479
 
4468
- const requestContext = captureFrontendModelRemoteRequestContext(requestEntry?.requestContext)
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`. Sends each
1847
- * missed event as a `channel-message` with `replayed: true`.
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
- {eventId: event.id}
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