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
|
@@ -11,6 +11,7 @@ import Logger from "../logger.js"
|
|
|
11
11
|
* @property {Date | string} created_at - Creation time.
|
|
12
12
|
* @property {string} id - Event id.
|
|
13
13
|
* @property {string} payload_json - Serialized payload.
|
|
14
|
+
* @property {string | null} params_json - Serialized broadcast params, or null when the publish carried none.
|
|
14
15
|
* @property {number | string} sequence - Sequence number.
|
|
15
16
|
*/
|
|
16
17
|
/**
|
|
@@ -18,6 +19,16 @@ import Logger from "../logger.js"
|
|
|
18
19
|
* @typedef {object} WebsocketReplayChannelRow
|
|
19
20
|
* @property {string} channel - Channel name.
|
|
20
21
|
*/
|
|
22
|
+
/**
|
|
23
|
+
* Normalized persisted websocket event.
|
|
24
|
+
* @typedef {object} WebsocketPersistedEvent
|
|
25
|
+
* @property {string} channel - Channel name.
|
|
26
|
+
* @property {string} createdAt - ISO creation time.
|
|
27
|
+
* @property {string} id - Event id.
|
|
28
|
+
* @property {Record<string, ReturnType<typeof JSON.parse>> | null} params - Persisted broadcast params, or null when the publish carried none.
|
|
29
|
+
* @property {ReturnType<typeof JSON.parse>} payload - Event payload.
|
|
30
|
+
* @property {number} sequence - Sequence number.
|
|
31
|
+
*/
|
|
21
32
|
const EVENTS_TABLE = "websocket_channel_events"
|
|
22
33
|
const REPLAY_CHANNELS_TABLE = "websocket_replay_channels"
|
|
23
34
|
const DEFAULT_RETENTION_MS = 10 * 60 * 1000
|
|
@@ -100,22 +111,40 @@ export default class VelociousHttpServerWebsocketEventLogStore {
|
|
|
100
111
|
|
|
101
112
|
/**
|
|
102
113
|
* Runs schema present.
|
|
103
|
-
* @returns {Promise<boolean>} - Whether both event-log tables
|
|
114
|
+
* @returns {Promise<boolean>} - Whether both event-log tables exist and the events table carries every required column.
|
|
104
115
|
*/
|
|
105
116
|
async _schemaPresent() {
|
|
106
117
|
return await this._withDb(async (db) =>
|
|
107
|
-
await db.tableExists(EVENTS_TABLE) && await db.tableExists(REPLAY_CHANNELS_TABLE)
|
|
118
|
+
(await db.tableExists(EVENTS_TABLE) && await db.tableExists(REPLAY_CHANNELS_TABLE))
|
|
119
|
+
&& (await this._columnPresent(db, EVENTS_TABLE, "params_json"))
|
|
108
120
|
)
|
|
109
121
|
}
|
|
110
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Runs column present.
|
|
125
|
+
* @param {import("../database/drivers/base.js").default} db - Database connection.
|
|
126
|
+
* @param {string} tableName - Table name.
|
|
127
|
+
* @param {string} columnName - Column name.
|
|
128
|
+
* @returns {Promise<boolean>} - Whether the column exists on the table.
|
|
129
|
+
*/
|
|
130
|
+
async _columnPresent(db, tableName, columnName) {
|
|
131
|
+
const tables = await db.getTables()
|
|
132
|
+
const table = tables.find((candidate) => candidate.getName() == tableName)
|
|
133
|
+
|
|
134
|
+
if (!table) return false
|
|
135
|
+
|
|
136
|
+
return (await table.getColumnByName(columnName)) !== undefined
|
|
137
|
+
}
|
|
138
|
+
|
|
111
139
|
/**
|
|
112
140
|
* Runs append event.
|
|
113
141
|
* @param {object} args - Options.
|
|
114
142
|
* @param {string} args.channel - Channel name.
|
|
115
143
|
* @param {ReturnType<typeof JSON.parse>} args.payload - Event payload.
|
|
116
|
-
* @
|
|
144
|
+
* @param {Record<string, ReturnType<typeof JSON.parse>> | null} [args.params] - Broadcast params to persist for stream-scoped replay, or null when the publish carried none.
|
|
145
|
+
* @returns {Promise<WebsocketPersistedEvent>} - Persisted event row.
|
|
117
146
|
*/
|
|
118
|
-
async appendEvent({channel, payload}) {
|
|
147
|
+
async appendEvent({channel, params = null, payload}) {
|
|
119
148
|
await this.ensureReady()
|
|
120
149
|
|
|
121
150
|
const id = randomUUID()
|
|
@@ -128,10 +157,11 @@ export default class VelociousHttpServerWebsocketEventLogStore {
|
|
|
128
157
|
channel,
|
|
129
158
|
created_at: createdAt,
|
|
130
159
|
id,
|
|
160
|
+
params_json: params === null ? null : JSON.stringify(params),
|
|
131
161
|
payload_json: JSON.stringify(payload)
|
|
132
162
|
}
|
|
133
163
|
})
|
|
134
|
-
return {channel, createdAt: createdAt.toISOString(), id, payload}
|
|
164
|
+
return {channel, createdAt: createdAt.toISOString(), id, params, payload}
|
|
135
165
|
})
|
|
136
166
|
}
|
|
137
167
|
|
|
@@ -139,8 +169,13 @@ export default class VelociousHttpServerWebsocketEventLogStore {
|
|
|
139
169
|
* Runs mark channel interested.
|
|
140
170
|
* @param {string} channel - Channel name.
|
|
141
171
|
* @returns {Promise<void>} - Resolves when the channel interest was persisted.
|
|
172
|
+
* @throws {Error} When the channel is registered live-only, which forbids replay persistence.
|
|
142
173
|
*/
|
|
143
174
|
async markChannelInterested(channel) {
|
|
175
|
+
if (this.configuration.isWebsocketChannelLiveOnly(channel)) {
|
|
176
|
+
throw new Error(`Websocket channel "${channel}" is registered live-only and cannot be marked interested in replay persistence`)
|
|
177
|
+
}
|
|
178
|
+
|
|
144
179
|
await this.ensureReady()
|
|
145
180
|
|
|
146
181
|
const interestedUntil = new Date(Date.now() + this.retentionMs)
|
|
@@ -158,6 +193,12 @@ export default class VelociousHttpServerWebsocketEventLogStore {
|
|
|
158
193
|
* @returns {Promise<boolean>} - Whether the channel should be persisted for replay.
|
|
159
194
|
*/
|
|
160
195
|
async shouldPersistChannel(channel) {
|
|
196
|
+
// A channel re-registered live-only can still hold cached or durable
|
|
197
|
+
// interest state from before the re-registration; the live-only
|
|
198
|
+
// contract must hold at the persistence decision, not only at
|
|
199
|
+
// interest marking.
|
|
200
|
+
if (this.configuration.isWebsocketChannelLiveOnly(channel)) return false
|
|
201
|
+
|
|
161
202
|
if (this._channelInterestCached(channel)) return true
|
|
162
203
|
if (this._interestedChannels.size === 0) return false
|
|
163
204
|
|
|
@@ -197,7 +238,7 @@ export default class VelociousHttpServerWebsocketEventLogStore {
|
|
|
197
238
|
* @param {object} args - Options.
|
|
198
239
|
* @param {string} args.channel - Channel name.
|
|
199
240
|
* @param {string} args.id - Event id.
|
|
200
|
-
* @returns {Promise<
|
|
241
|
+
* @returns {Promise<WebsocketPersistedEvent | null>} - Event row or null.
|
|
201
242
|
*/
|
|
202
243
|
async getEventById({channel, id}) {
|
|
203
244
|
await this.ensureReady()
|
|
@@ -237,7 +278,7 @@ export default class VelociousHttpServerWebsocketEventLogStore {
|
|
|
237
278
|
* @param {string} args.channel - Channel name.
|
|
238
279
|
* @param {number} args.sequence - Lower bound sequence.
|
|
239
280
|
* @param {number | null | undefined} [args.upToSequence] - Inclusive ceiling sequence.
|
|
240
|
-
* @returns {Promise<
|
|
281
|
+
* @returns {Promise<WebsocketPersistedEvent[]>} - Ordered events.
|
|
241
282
|
*/
|
|
242
283
|
async getEventsAfter({channel, sequence, upToSequence}) {
|
|
243
284
|
await this.ensureReady()
|
|
@@ -312,24 +353,55 @@ export default class VelociousHttpServerWebsocketEventLogStore {
|
|
|
312
353
|
* @returns {Promise<void>} - Resolves when complete.
|
|
313
354
|
*/
|
|
314
355
|
async _ensureEventsTable(db) {
|
|
315
|
-
this.logger.info("Applying websocket event-log schema")
|
|
316
|
-
|
|
317
356
|
if (await db.tableExists(EVENTS_TABLE)) {
|
|
318
|
-
this.
|
|
357
|
+
await this._ensureEventsTableColumns(db)
|
|
319
358
|
return
|
|
320
359
|
}
|
|
321
360
|
|
|
361
|
+
this.logger.info("Applying websocket event-log schema")
|
|
362
|
+
|
|
322
363
|
const eventTable = new TableData(EVENTS_TABLE, {ifNotExists: true})
|
|
323
364
|
|
|
324
365
|
eventTable.integer("sequence", {autoIncrement: true, null: false, primaryKey: true})
|
|
325
366
|
eventTable.string("id", {index: true, null: false})
|
|
326
367
|
eventTable.string("channel", {index: true, null: false})
|
|
368
|
+
eventTable.text("params_json", {null: true})
|
|
327
369
|
eventTable.text("payload_json", {null: false})
|
|
328
370
|
eventTable.datetime("created_at", {index: true, null: false})
|
|
329
371
|
|
|
330
372
|
await db.createTable(eventTable)
|
|
331
373
|
}
|
|
332
374
|
|
|
375
|
+
/**
|
|
376
|
+
* Adds columns the current schema requires to an events table created by an
|
|
377
|
+
* older framework version, so upgrades keep working without a manual ALTER.
|
|
378
|
+
* @param {import("../database/drivers/base.js").default} db - Database connection.
|
|
379
|
+
* @returns {Promise<void>} - Resolves when the events table schema is current.
|
|
380
|
+
*/
|
|
381
|
+
async _ensureEventsTableColumns(db) {
|
|
382
|
+
if (await this._columnPresent(db, EVENTS_TABLE, "params_json")) return
|
|
383
|
+
|
|
384
|
+
this.logger.info("Adding params_json column to websocket event-log table")
|
|
385
|
+
|
|
386
|
+
const tableData = new TableData(EVENTS_TABLE)
|
|
387
|
+
|
|
388
|
+
tableData.addColumn("params_json", {isNewColumn: true, null: true, type: "text"})
|
|
389
|
+
|
|
390
|
+
try {
|
|
391
|
+
for (const sql of await db.alterTableSQLs(tableData)) {
|
|
392
|
+
await db.query(sql)
|
|
393
|
+
}
|
|
394
|
+
} catch (error) {
|
|
395
|
+
// A concurrent process can add the column between the presence check
|
|
396
|
+
// and the ALTER (multi-worker or rolling upgrade); the
|
|
397
|
+
// duplicate-column failure is then the expected outcome, not a real
|
|
398
|
+
// error. Anything else re-checks as absent and rethrows.
|
|
399
|
+
if (await this._columnPresent(db, EVENTS_TABLE, "params_json")) return
|
|
400
|
+
|
|
401
|
+
throw error
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
333
405
|
/**
|
|
334
406
|
* Runs ensure replay channels table.
|
|
335
407
|
* @param {import("../database/drivers/base.js").default} db - Database connection.
|
|
@@ -352,7 +424,7 @@ export default class VelociousHttpServerWebsocketEventLogStore {
|
|
|
352
424
|
* @param {string} args.channel - Channel name.
|
|
353
425
|
* @param {import("../database/drivers/base.js").default} args.db - Database connection.
|
|
354
426
|
* @param {string} args.id - Event id.
|
|
355
|
-
* @returns {Promise<
|
|
427
|
+
* @returns {Promise<WebsocketPersistedEvent | null>} - Event row or null.
|
|
356
428
|
*/
|
|
357
429
|
async _getEventById({channel, db, id}) {
|
|
358
430
|
const rows = /** @type {WebsocketEventRow[]} */ (await db
|
|
@@ -370,7 +442,7 @@ export default class VelociousHttpServerWebsocketEventLogStore {
|
|
|
370
442
|
/**
|
|
371
443
|
* Runs normalize event row.
|
|
372
444
|
* @param {WebsocketEventRow} row - Raw row.
|
|
373
|
-
* @returns {
|
|
445
|
+
* @returns {WebsocketPersistedEvent} - Normalized row.
|
|
374
446
|
*/
|
|
375
447
|
_normalizeEventRow(row) {
|
|
376
448
|
const createdAtValue = row.created_at
|
|
@@ -379,6 +451,7 @@ export default class VelociousHttpServerWebsocketEventLogStore {
|
|
|
379
451
|
channel: row.channel,
|
|
380
452
|
createdAt: createdAtValue instanceof Date ? createdAtValue.toISOString() : new Date(createdAtValue).toISOString(),
|
|
381
453
|
id: row.id,
|
|
454
|
+
params: row.params_json === null || row.params_json === undefined ? null : JSON.parse(row.params_json),
|
|
382
455
|
payload: JSON.parse(row.payload_json),
|
|
383
456
|
sequence: Number(row.sequence)
|
|
384
457
|
}
|
|
@@ -116,7 +116,7 @@ export class VelociousHttpServerWebsocketEventsHost {
|
|
|
116
116
|
// Other channels chain onto their own tails and are not delayed.
|
|
117
117
|
this._queuePublish({
|
|
118
118
|
callback: async () => {
|
|
119
|
-
const persistedEvent = await this._persistV2EventIfNeeded({body, channel, configuration})
|
|
119
|
+
const persistedEvent = await this._persistV2EventIfNeeded({body, broadcastParams, channel, configuration})
|
|
120
120
|
const dispatchedTargets = new Set()
|
|
121
121
|
|
|
122
122
|
for (const handler of this.broadcastHandlersByConfiguration.get(configuration) || []) {
|
|
@@ -159,7 +159,9 @@ export class VelociousHttpServerWebsocketEventsHost {
|
|
|
159
159
|
|
|
160
160
|
if (configuration) {
|
|
161
161
|
queuedPublish = previousTail.then(() => configuration.withoutCurrentTestDatabaseAccessScope(() => {
|
|
162
|
-
return configuration.withoutCurrentConnectionContexts(
|
|
162
|
+
return configuration.withoutCurrentConnectionContexts(() => {
|
|
163
|
+
return configuration.runWithTestSharedConnectionContexts(callback)
|
|
164
|
+
})
|
|
163
165
|
}))
|
|
164
166
|
} else {
|
|
165
167
|
queuedPublish = previousTail.then(callback)
|
|
@@ -191,12 +193,13 @@ export class VelociousHttpServerWebsocketEventsHost {
|
|
|
191
193
|
* Runs persist v2 event if needed.
|
|
192
194
|
* @param {object} args - Options.
|
|
193
195
|
* @param {ReturnType<typeof JSON.parse>} args.body - Event body.
|
|
196
|
+
* @param {Record<string, ReturnType<typeof JSON.parse>>} args.broadcastParams - Routing filter params.
|
|
194
197
|
* @param {string} args.channel - Channel name.
|
|
195
198
|
* @param {import("../configuration.js").default} args.configuration - Originating configuration.
|
|
196
199
|
* @returns {Promise<{createdAt: string, id: string} | null>} - Persisted event metadata when storage is enabled.
|
|
197
200
|
*/
|
|
198
|
-
async _persistV2EventIfNeeded({body, channel, configuration}) {
|
|
199
|
-
return await this._persistChannelEventIfNeeded({channel, payload: body
|
|
201
|
+
async _persistV2EventIfNeeded({body, broadcastParams, channel, configuration}) {
|
|
202
|
+
return await this._persistChannelEventIfNeeded({broadcastParams, channel, configuration, payload: body})
|
|
200
203
|
}
|
|
201
204
|
|
|
202
205
|
/**
|
|
@@ -211,14 +214,18 @@ export class VelociousHttpServerWebsocketEventsHost {
|
|
|
211
214
|
}
|
|
212
215
|
|
|
213
216
|
/**
|
|
214
|
-
* Runs persist channel event if needed.
|
|
217
|
+
* Runs persist channel event if needed. Persists the broadcast's routing
|
|
218
|
+
* params (sanitized through the channel class's
|
|
219
|
+
* `replayableBroadcastParams`) so replay can re-apply `matches()` per
|
|
220
|
+
* stream instead of delivering the whole channel's log.
|
|
215
221
|
* @param {object} args - Options object.
|
|
222
|
+
* @param {Record<string, ReturnType<typeof JSON.parse>> | null} [args.broadcastParams] - Broadcast params to persist for stream-scoped replay.
|
|
216
223
|
* @param {string} args.channel - Channel name.
|
|
217
224
|
* @param {ReturnType<typeof JSON.parse>} args.payload - Payload data.
|
|
218
225
|
* @param {import("../configuration.js").default} [args.configuration] - Configuration owning the event store.
|
|
219
226
|
* @returns {Promise<{createdAt: string, id: string} | null>} - Persisted event metadata.
|
|
220
227
|
*/
|
|
221
|
-
async _persistChannelEventIfNeeded({channel,
|
|
228
|
+
async _persistChannelEventIfNeeded({broadcastParams = null, channel, configuration, payload}) {
|
|
222
229
|
const handler = this.handlers.values().next().value
|
|
223
230
|
const eventConfiguration = configuration || handler?.configuration
|
|
224
231
|
|
|
@@ -229,7 +236,10 @@ export class VelociousHttpServerWebsocketEventsHost {
|
|
|
229
236
|
|
|
230
237
|
if (!shouldPersist) return null
|
|
231
238
|
|
|
232
|
-
const
|
|
239
|
+
const ChannelClass = eventConfiguration.getWebsocketChannelClass(channel)
|
|
240
|
+
const params = ChannelClass ? ChannelClass.replayableBroadcastParams(broadcastParams) : broadcastParams
|
|
241
|
+
|
|
242
|
+
const persistedEvent = await websocketEventLogStore.appendEvent({channel, params, payload})
|
|
233
243
|
|
|
234
244
|
return {
|
|
235
245
|
createdAt: persistedEvent.createdAt,
|
|
@@ -1,364 +1,53 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
discoverTestFiles,
|
|
5
|
+
lineFiltersFromCandidates
|
|
6
|
+
} from "@velocious/testing/node"
|
|
5
7
|
|
|
6
|
-
import fileExists from "../utils/file-exists.js"
|
|
7
|
-
import Logger from "../logger.js"
|
|
8
8
|
import restArgsError from "../utils/rest-args-error.js"
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
export default class TestFilesFinder {
|
|
12
|
-
static IGNORED_NAMES = [".git", "node_modules"]
|
|
10
|
+
const VELOCIOUS_NODE_TEST_PATTERN = /(?<!\.browser)-(?:spec|test)\.(?:m)?js$/u
|
|
13
11
|
|
|
12
|
+
/** Compatibility facade for Velocious's published test-file finder path. */
|
|
13
|
+
export default class TestFilesFinder {
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
* @param {object} args -
|
|
17
|
-
* @param {string} args.directory -
|
|
18
|
-
* @param {string[]} [args.directories] -
|
|
19
|
-
* @param {
|
|
15
|
+
* Creates a Velocious-compatible package discovery adapter.
|
|
16
|
+
* @param {object} args - Discovery options.
|
|
17
|
+
* @param {string} args.directory - Discovery base directory.
|
|
18
|
+
* @param {string[]} [args.directories] - Default directories.
|
|
19
|
+
* @param {RegExp} [args.filePattern] - Test-file pattern override.
|
|
20
|
+
* @param {string[]} args.processArgs - Process arguments including the command name.
|
|
20
21
|
*/
|
|
21
|
-
constructor({directory, directories, processArgs, ...restArgs}) {
|
|
22
|
+
constructor({directory, directories, filePattern, processArgs, ...restArgs}) {
|
|
22
23
|
restArgsError(restArgs)
|
|
23
|
-
|
|
24
|
-
this.
|
|
25
|
-
this.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
this.
|
|
31
|
-
|
|
32
|
-
`${this.directory}/tests`,
|
|
33
|
-
`${this.directory}/spec`
|
|
34
|
-
]
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
this.findingCount = 0
|
|
38
|
-
this.processArgs = processArgs
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Narrows the runtime value to the documented type.
|
|
42
|
-
* @type {string[]} */
|
|
43
|
-
this.foundFiles = []
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Narrows the runtime value to the documented type.
|
|
47
|
-
* @type {Record<number, Promise<void>>} */
|
|
48
|
-
this.findingPromises = {}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Narrows the runtime value to the documented type.
|
|
52
|
-
* @type {string[]} */
|
|
53
|
-
this.testArgs = this.processArgs.filter((processArg, index) => index != 0)
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Narrows the runtime value to the documented type.
|
|
57
|
-
* @type {string[]} */
|
|
58
|
-
this.directoryArgs = []
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Narrows the runtime value to the documented type.
|
|
62
|
-
* @type {string[]} */
|
|
63
|
-
this.directoryFullPaths = []
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Narrows the runtime value to the documented type.
|
|
67
|
-
* @type {string[]} */
|
|
68
|
-
this.fileArgs = []
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Narrows the runtime value to the documented type.
|
|
72
|
-
* @type {string[]} */
|
|
73
|
-
this.explicitFiles = []
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Narrows the runtime value to the documented type.
|
|
77
|
-
* @type {Record<string, number[]>} */
|
|
78
|
-
this.lineFiltersByFile = {}
|
|
79
|
-
|
|
80
|
-
this._argsPrepared = false
|
|
24
|
+
this.directory = directory
|
|
25
|
+
this.directories = directories
|
|
26
|
+
this.filePattern = filePattern
|
|
27
|
+
this.candidates = processArgs.slice(1).filter((argument) => argument !== "--")
|
|
28
|
+
/** @type {Record<string, number[]>} */
|
|
29
|
+
this.lineFiltersByFile = lineFiltersFromCandidates({
|
|
30
|
+
cwd: directory,
|
|
31
|
+
candidates: this.candidates
|
|
32
|
+
})
|
|
81
33
|
}
|
|
82
34
|
|
|
83
35
|
/**
|
|
84
|
-
*
|
|
85
|
-
* @returns {Promise<string[]>} -
|
|
36
|
+
* Discovers test files through the package implementation.
|
|
37
|
+
* @returns {Promise<string[]>} - Discovered absolute file paths.
|
|
86
38
|
*/
|
|
87
39
|
async findTestFiles() {
|
|
88
|
-
await
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
await this.withFindingCount(async () => {
|
|
95
|
-
const hasExplicitArgs = this.directoryFullPaths.length > 0 || this.fileArgs.length > 0
|
|
96
|
-
const directoriesToScan = hasExplicitArgs ? this.directoryFullPaths : this.directories
|
|
97
|
-
|
|
98
|
-
for (const directory of directoriesToScan) {
|
|
99
|
-
if (await fileExists(directory)) {
|
|
100
|
-
await this.findTestFilesInDir(directory)
|
|
101
|
-
}
|
|
102
|
-
}
|
|
40
|
+
return await discoverTestFiles({
|
|
41
|
+
cwd: this.directory,
|
|
42
|
+
candidates: this.candidates,
|
|
43
|
+
directories: this.directories,
|
|
44
|
+
filePattern: this.filePattern || VELOCIOUS_NODE_TEST_PATTERN
|
|
103
45
|
})
|
|
104
|
-
|
|
105
|
-
await this.waitForFindingPromises()
|
|
106
|
-
|
|
107
|
-
if (this.explicitFiles.length > 0) {
|
|
108
|
-
this.foundFiles.push(...this.explicitFiles)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return Array.from(new Set(this.foundFiles))
|
|
112
46
|
}
|
|
113
47
|
|
|
114
48
|
/**
|
|
115
|
-
*
|
|
116
|
-
* @returns {Record<string, number[]>} - Line filters by file.
|
|
49
|
+
* Gets candidate-derived line filters.
|
|
50
|
+
* @returns {Record<string, number[]>} - Line filters keyed by absolute file path.
|
|
117
51
|
*/
|
|
118
52
|
getLineFiltersByFile() { return this.lineFiltersByFile }
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Runs finding promises length.
|
|
122
|
-
* @returns {number} - The ing promises length.
|
|
123
|
-
*/
|
|
124
|
-
findingPromisesLength() { return Object.keys(this.findingPromises).length }
|
|
125
|
-
|
|
126
|
-
async waitForFindingPromises() {
|
|
127
|
-
while (this.findingPromisesLength() > 0) {
|
|
128
|
-
await this.waitForFindingPromisesIteration()
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Runs wait for finding promises iteration.
|
|
134
|
-
* @returns {Promise<void>} - Resolves when complete.
|
|
135
|
-
*/
|
|
136
|
-
async waitForFindingPromisesIteration() {
|
|
137
|
-
const unfinishedPromises = []
|
|
138
|
-
|
|
139
|
-
for (const findingPromiseId in this.findingPromises) {
|
|
140
|
-
const findingPromise = this.findingPromises[findingPromiseId]
|
|
141
|
-
|
|
142
|
-
unfinishedPromises.push(findingPromise)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
await Promise.all(unfinishedPromises)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Runs with finding count.
|
|
150
|
-
* @param {() => Promise<void>} callback - Callback function.
|
|
151
|
-
* @returns {Promise<void>} - Resolves when complete.
|
|
152
|
-
*/
|
|
153
|
-
withFindingCount(callback) {
|
|
154
|
-
return new Promise((resolve) => {
|
|
155
|
-
const findingPromise = callback()
|
|
156
|
-
const findingCount = this.findingCount
|
|
157
|
-
|
|
158
|
-
this.findingCount += 1
|
|
159
|
-
this.findingPromises[findingCount] = findingPromise
|
|
160
|
-
|
|
161
|
-
findingPromise.finally(() => {
|
|
162
|
-
delete this.findingPromises[findingCount]
|
|
163
|
-
|
|
164
|
-
resolve(undefined)
|
|
165
|
-
})
|
|
166
|
-
})
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Runs find test files in dir.
|
|
171
|
-
* @param {string} dir - Dir.
|
|
172
|
-
* @returns {Promise<void>} - Resolves when complete.
|
|
173
|
-
*/
|
|
174
|
-
async findTestFilesInDir(dir) {
|
|
175
|
-
await this.withFindingCount(async () => {
|
|
176
|
-
const files = await fs.readdir(dir)
|
|
177
|
-
|
|
178
|
-
for (const file of files) {
|
|
179
|
-
if (TestFilesFinder.IGNORED_NAMES.includes(file)) {
|
|
180
|
-
continue
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const fullPath = `${dir}/${file}`
|
|
184
|
-
const localPath = fullPath.replace(`${this.directory}/`, "")
|
|
185
|
-
const isDir = (await fs.stat(fullPath)).isDirectory()
|
|
186
|
-
|
|
187
|
-
if (isDir) {
|
|
188
|
-
this.findTestFilesInDir(fullPath)
|
|
189
|
-
} else {
|
|
190
|
-
if (this.isFileMatchingRequirements(file, localPath)) {
|
|
191
|
-
this.foundFiles.push(fullPath)
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
})
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Runs is file matching requirements.
|
|
200
|
-
* @param {string} file - File.
|
|
201
|
-
* @param {string} localPath - Local path.
|
|
202
|
-
* @returns {boolean} - Whether file matching requirements.
|
|
203
|
-
*/
|
|
204
|
-
isFileMatchingRequirements(file, localPath) {
|
|
205
|
-
if (this.directoryArgs.length > 0) {
|
|
206
|
-
for (const directoryArg of this.directoryArgs) {
|
|
207
|
-
if (localPath.startsWith(directoryArg) && this.looksLikeTestFile(file)) {
|
|
208
|
-
this.logger.debug("Found test file because matching dir and looks like this file:", file)
|
|
209
|
-
return true
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
if (this.fileArgs.length > 0) {
|
|
215
|
-
for (const fileArg of this.fileArgs) {
|
|
216
|
-
if (fileArg == localPath) {
|
|
217
|
-
this.logger.debug("Found test file because matching file arg:", file)
|
|
218
|
-
return true
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (this.fileArgs.length == 0 && this.directoryArgs.length == 0 && this.looksLikeTestFile(file)) {
|
|
224
|
-
this.logger.debug("Found test file because looks like this file:", file)
|
|
225
|
-
return true
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
return false
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Runs looks like test file.
|
|
233
|
-
* @param {string} file - File.
|
|
234
|
-
* @returns {boolean} - Whether looks like test file.
|
|
235
|
-
*/
|
|
236
|
-
looksLikeTestFile(file) {
|
|
237
|
-
return Boolean(file.match(/-(spec|test)\.(m|)js$/))
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Runs prepare args.
|
|
242
|
-
* @returns {Promise<void>} - Resolves when test args are prepared.
|
|
243
|
-
*/
|
|
244
|
-
async prepareArgs() {
|
|
245
|
-
if (this._argsPrepared) return
|
|
246
|
-
|
|
247
|
-
for (const testArg of this.testArgs) {
|
|
248
|
-
if (testArg === "--") continue
|
|
249
|
-
|
|
250
|
-
const {cleanArg, line} = this.splitLineArg(testArg)
|
|
251
|
-
const forceDirectory = testArg.endsWith("/") || testArg.endsWith(path.sep)
|
|
252
|
-
const fullPath = path.isAbsolute(cleanArg) ? cleanArg : path.resolve(this.directory, cleanArg)
|
|
253
|
-
const baseName = path.basename(this.directory)
|
|
254
|
-
const hasBasePrefix = cleanArg === baseName || cleanArg.startsWith(`${baseName}/`) || cleanArg.startsWith(`${baseName}${path.sep}`)
|
|
255
|
-
const basePrefixedFullPath = (!path.isAbsolute(cleanArg) && hasBasePrefix) ? path.resolve(path.dirname(this.directory), cleanArg) : null
|
|
256
|
-
const fullPathCandidates = basePrefixedFullPath ? [basePrefixedFullPath] : [fullPath]
|
|
257
|
-
|
|
258
|
-
if (forceDirectory) {
|
|
259
|
-
const preferredLocalPath = this.toLocalPath(basePrefixedFullPath || fullPath)
|
|
260
|
-
this.directoryArgs.push(this.ensureTrailingSlash(preferredLocalPath))
|
|
261
|
-
this.directoryFullPaths.push(path.resolve(basePrefixedFullPath || fullPath))
|
|
262
|
-
continue
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
try {
|
|
266
|
-
let stats
|
|
267
|
-
let resolvedFullPath
|
|
268
|
-
|
|
269
|
-
for (const candidatePath of fullPathCandidates) {
|
|
270
|
-
try {
|
|
271
|
-
stats = await fs.stat(candidatePath)
|
|
272
|
-
resolvedFullPath = candidatePath
|
|
273
|
-
break
|
|
274
|
-
} catch {
|
|
275
|
-
// Keep searching
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if (!stats || !resolvedFullPath) throw new Error("Path not found")
|
|
280
|
-
const localPath = this.toLocalPath(resolvedFullPath)
|
|
281
|
-
|
|
282
|
-
if (stats.isDirectory()) {
|
|
283
|
-
this.directoryArgs.push(this.ensureTrailingSlash(localPath))
|
|
284
|
-
this.directoryFullPaths.push(resolvedFullPath)
|
|
285
|
-
} else if (stats.isFile()) {
|
|
286
|
-
this.fileArgs.push(localPath)
|
|
287
|
-
this.explicitFiles.push(resolvedFullPath)
|
|
288
|
-
|
|
289
|
-
if (line) {
|
|
290
|
-
this.addLineFilter(resolvedFullPath, line)
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
} catch {
|
|
294
|
-
const fallbackLocalPath = this.toLocalPath(basePrefixedFullPath || fullPath)
|
|
295
|
-
this.fileArgs.push(fallbackLocalPath)
|
|
296
|
-
|
|
297
|
-
if (line) {
|
|
298
|
-
this.addLineFilter(basePrefixedFullPath || fullPath, line)
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
this._argsPrepared = true
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* Runs add line filter.
|
|
308
|
-
* @param {string} filePath - File path.
|
|
309
|
-
* @param {number} line - Line number.
|
|
310
|
-
* @returns {void} - No return value.
|
|
311
|
-
*/
|
|
312
|
-
addLineFilter(filePath, line) {
|
|
313
|
-
const fullPath = path.resolve(filePath)
|
|
314
|
-
|
|
315
|
-
if (!this.lineFiltersByFile[fullPath]) {
|
|
316
|
-
this.lineFiltersByFile[fullPath] = []
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
if (!this.lineFiltersByFile[fullPath].includes(line)) {
|
|
320
|
-
this.lineFiltersByFile[fullPath].push(line)
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Runs split line arg.
|
|
326
|
-
* @param {string} testArg - Test arg.
|
|
327
|
-
* @returns {{cleanArg: string, line?: number}} - Cleaned arg and line.
|
|
328
|
-
*/
|
|
329
|
-
splitLineArg(testArg) {
|
|
330
|
-
const match = testArg.match(/^(.*):(\d+)$/)
|
|
331
|
-
|
|
332
|
-
if (!match) {
|
|
333
|
-
return {cleanArg: testArg}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
const line = Number(match[2])
|
|
337
|
-
|
|
338
|
-
if (!Number.isFinite(line)) {
|
|
339
|
-
return {cleanArg: testArg}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
return {cleanArg: match[1], line}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* Runs ensure trailing slash.
|
|
347
|
-
* @param {string} localPath - Local path.
|
|
348
|
-
* @returns {string} - Normalized local path with trailing slash.
|
|
349
|
-
*/
|
|
350
|
-
ensureTrailingSlash(localPath) {
|
|
351
|
-
if (localPath === "") return localPath
|
|
352
|
-
return localPath.endsWith("/") ? localPath : `${localPath}/`
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Runs to local path.
|
|
357
|
-
* @param {string} fullPath - Full path.
|
|
358
|
-
* @returns {string} - Local path relative to the base directory.
|
|
359
|
-
*/
|
|
360
|
-
toLocalPath(fullPath) {
|
|
361
|
-
const relativePath = path.relative(this.directory, fullPath)
|
|
362
|
-
return relativePath.split(path.sep).join("/")
|
|
363
|
-
}
|
|
364
53
|
}
|