velocious 1.0.667 → 1.0.669

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 (112) hide show
  1. package/README.md +34 -9
  2. package/build/configuration.js +23 -1
  3. package/build/environment-handlers/node/cli/commands/test/timing-manifest/merge.js +2 -48
  4. package/build/environment-handlers/node/cli/commands/test.js +17 -69
  5. package/build/frontend-models/websocket-channel.js +18 -0
  6. package/build/http-server/client/websocket-session.js +41 -4
  7. package/build/http-server/websocket-channel.js +14 -0
  8. package/build/http-server/websocket-event-log-store.js +85 -12
  9. package/build/http-server/websocket-events-host.js +14 -6
  10. package/build/src/configuration.d.ts +18 -1
  11. package/build/src/configuration.d.ts.map +1 -1
  12. package/build/src/configuration.js +22 -2
  13. package/build/src/environment-handlers/node/cli/commands/test/timing-manifest/merge.d.ts.map +1 -1
  14. package/build/src/environment-handlers/node/cli/commands/test/timing-manifest/merge.js +3 -47
  15. package/build/src/environment-handlers/node/cli/commands/test.d.ts +2 -28
  16. package/build/src/environment-handlers/node/cli/commands/test.d.ts.map +1 -1
  17. package/build/src/environment-handlers/node/cli/commands/test.js +10 -61
  18. package/build/src/frontend-models/websocket-channel.d.ts +9 -0
  19. package/build/src/frontend-models/websocket-channel.d.ts.map +1 -1
  20. package/build/src/frontend-models/websocket-channel.js +16 -1
  21. package/build/src/http-server/client/websocket-session.d.ts +25 -2
  22. package/build/src/http-server/client/websocket-session.d.ts.map +1 -1
  23. package/build/src/http-server/client/websocket-session.js +40 -5
  24. package/build/src/http-server/websocket-channel.d.ts +11 -0
  25. package/build/src/http-server/websocket-channel.d.ts.map +1 -1
  26. package/build/src/http-server/websocket-channel.js +14 -1
  27. package/build/src/http-server/websocket-event-log-store.d.ts +60 -41
  28. package/build/src/http-server/websocket-event-log-store.d.ts.map +1 -1
  29. package/build/src/http-server/websocket-event-log-store.js +79 -12
  30. package/build/src/http-server/websocket-events-host.d.ts +10 -3
  31. package/build/src/http-server/websocket-events-host.d.ts.map +1 -1
  32. package/build/src/http-server/websocket-events-host.js +14 -7
  33. package/build/src/testing/expect-utils.d.ts +1 -51
  34. package/build/src/testing/expect-utils.d.ts.map +1 -1
  35. package/build/src/testing/expect-utils.js +2 -205
  36. package/build/src/testing/expect.d.ts +1 -171
  37. package/build/src/testing/expect.d.ts.map +1 -1
  38. package/build/src/testing/expect.js +2 -667
  39. package/build/src/testing/test-files-finder.d.ts +13 -116
  40. package/build/src/testing/test-files-finder.d.ts.map +1 -1
  41. package/build/src/testing/test-files-finder.js +26 -296
  42. package/build/src/testing/test-filter-parser.d.ts +32 -22
  43. package/build/src/testing/test-filter-parser.d.ts.map +1 -1
  44. package/build/src/testing/test-filter-parser.js +25 -217
  45. package/build/src/testing/test-profile-activity.d.ts +1 -6
  46. package/build/src/testing/test-profile-activity.d.ts.map +1 -1
  47. package/build/src/testing/test-profile-activity.js +2 -12
  48. package/build/src/testing/test-profile-output.d.ts +1 -40
  49. package/build/src/testing/test-profile-output.d.ts.map +1 -1
  50. package/build/src/testing/test-profile-output.js +2 -217
  51. package/build/src/testing/test-profiler.d.ts +15 -659
  52. package/build/src/testing/test-profiler.d.ts.map +1 -1
  53. package/build/src/testing/test-profiler.js +35 -833
  54. package/build/src/testing/test-runner.d.ts +10 -1
  55. package/build/src/testing/test-runner.d.ts.map +1 -1
  56. package/build/src/testing/test-runner.js +26 -4
  57. package/build/src/testing/test-suite-splitter.d.ts +1 -128
  58. package/build/src/testing/test-suite-splitter.d.ts.map +1 -1
  59. package/build/src/testing/test-suite-splitter.js +2 -259
  60. package/build/src/testing/test.d.ts +1 -13
  61. package/build/src/testing/test.d.ts.map +1 -1
  62. package/build/src/testing/test.js +2 -14
  63. package/build/src/testing/timing-manifest.d.ts +5 -93
  64. package/build/src/testing/timing-manifest.d.ts.map +1 -1
  65. package/build/src/testing/timing-manifest.js +4 -283
  66. package/build/src/testing/velocious-attempt-executor.d.ts.map +1 -1
  67. package/build/src/testing/velocious-attempt-executor.js +8 -2
  68. package/build/testing/expect-utils.js +1 -242
  69. package/build/testing/expect.js +1 -760
  70. package/build/testing/test-files-finder.js +29 -345
  71. package/build/testing/test-filter-parser.js +29 -248
  72. package/build/testing/test-profile-activity.js +1 -12
  73. package/build/testing/test-profile-output.js +8 -253
  74. package/build/testing/test-profiler.js +38 -895
  75. package/build/testing/test-runner.js +33 -9
  76. package/build/testing/test-suite-splitter.js +1 -301
  77. package/build/testing/test.js +3 -14
  78. package/build/testing/timing-manifest.js +10 -344
  79. package/build/testing/velocious-attempt-executor.js +7 -1
  80. package/package.json +3 -4
  81. package/scripts/test-browser.js +29 -6
  82. package/scripts/verify-docker-dev-environment.js +13 -1
  83. package/src/configuration.js +23 -1
  84. package/src/environment-handlers/node/cli/commands/test/timing-manifest/merge.js +2 -48
  85. package/src/environment-handlers/node/cli/commands/test.js +17 -69
  86. package/src/frontend-models/websocket-channel.js +18 -0
  87. package/src/http-server/client/websocket-session.js +41 -4
  88. package/src/http-server/websocket-channel.js +14 -0
  89. package/src/http-server/websocket-event-log-store.js +85 -12
  90. package/src/http-server/websocket-events-host.js +14 -6
  91. package/src/testing/expect-utils.js +1 -242
  92. package/src/testing/expect.js +1 -760
  93. package/src/testing/test-files-finder.js +29 -345
  94. package/src/testing/test-filter-parser.js +29 -248
  95. package/src/testing/test-profile-activity.js +1 -12
  96. package/src/testing/test-profile-output.js +8 -253
  97. package/src/testing/test-profiler.js +38 -895
  98. package/src/testing/test-runner.js +33 -9
  99. package/src/testing/test-suite-splitter.js +1 -301
  100. package/src/testing/test.js +3 -14
  101. package/src/testing/timing-manifest.js +10 -344
  102. package/src/testing/velocious-attempt-executor.js +7 -1
  103. package/build/src/testing/base-expect.d.ts +0 -15
  104. package/build/src/testing/base-expect.d.ts.map +0 -1
  105. package/build/src/testing/base-expect.js +0 -16
  106. package/build/src/testing/expect-to-change.d.ts +0 -32
  107. package/build/src/testing/expect-to-change.d.ts.map +0 -1
  108. package/build/src/testing/expect-to-change.js +0 -46
  109. package/build/testing/base-expect.js +0 -17
  110. package/build/testing/expect-to-change.js +0 -55
  111. package/src/testing/base-expect.js +0 -17
  112. package/src/testing/expect-to-change.js +0 -55
@@ -1,14 +1,3 @@
1
1
  // @ts-check
2
2
 
3
- /**
4
- * Validates a low-cardinality activity label suitable for profile output.
5
- * @param {string} name - Activity name.
6
- * @returns {string} - Validated name.
7
- */
8
- export function validateTestActivityName(name) {
9
- if (typeof name !== "string" || !/^[a-z][a-z0-9]*(?:[._:-][a-z0-9]+)*$/.test(name) || name.length > 64) {
10
- throw new Error("Test profile activity name must be a lowercase identifier of at most 64 characters")
11
- }
12
-
13
- return name
14
- }
3
+ export { validateTestActivityName } from "@velocious/testing/profiling"
@@ -1,255 +1,10 @@
1
1
  // @ts-check
2
2
 
3
- import fs from "node:fs/promises"
4
- import path from "node:path"
5
- import { roundProfileDuration } from "./test-profiler.js"
6
- import { validateTimingManifest } from "./timing-manifest.js"
7
-
8
- const FORBIDDEN_PROFILE_FIELDS = new Set([
9
- "bind",
10
- "binds",
11
- "checkoutname",
12
- "credential",
13
- "credentials",
14
- "databasename",
15
- "error",
16
- "host",
17
- "password",
18
- "reusekey",
19
- "sql",
20
- "stack",
21
- "tenant",
22
- "username"
23
- ])
24
- const PHASE_ORDER = [
25
- "discovery",
26
- "imports",
27
- "testing config/global setup",
28
- "beforeAll",
29
- "beforeEach",
30
- "test body",
31
- "afterEach",
32
- "afterAll",
33
- "custom",
34
- "runner overhead",
35
- "total"
36
- ]
37
- let atomicWriteSequence = 0
38
-
39
- /**
40
- * Recursively rejects fields that are forbidden from rich profile output.
41
- * @param {ReturnType<typeof JSON.parse>} value - Profile value.
42
- * @param {string} [pathName] - Diagnostic field path.
43
- * @returns {void}
44
- */
45
- function assertProfilePrivacy(value, pathName = "profile") {
46
- if (!value || typeof value !== "object") return
47
-
48
- if (Array.isArray(value)) {
49
- for (let index = 0; index < value.length; index++) {
50
- assertProfilePrivacy(value[index], `${pathName}[${index}]`)
51
- }
52
- return
53
- }
54
-
55
- for (const [key, childValue] of Object.entries(value)) {
56
- if (FORBIDDEN_PROFILE_FIELDS.has(key.toLowerCase())) {
57
- throw new Error(`Forbidden profile field: ${pathName}.${key}`)
58
- }
59
-
60
- assertProfilePrivacy(childValue, `${pathName}.${key}`)
61
- }
62
- }
63
-
64
- /**
65
- * Recursively rounds duration fields and rejects invalid duration values.
66
- * @param {ReturnType<typeof JSON.parse>} value - Profile value.
67
- * @param {string | undefined} [parentKey] - Parent field name.
68
- * @returns {void}
69
- */
70
- function normalizeDurations(value, parentKey) {
71
- if (!value || typeof value !== "object") return
72
-
73
- if (Array.isArray(value)) {
74
- for (const childValue of value) normalizeDurations(childValue, parentKey)
75
- return
76
- }
77
-
78
- for (const [key, childValue] of Object.entries(value)) {
79
- const durationField = key.endsWith("Ms") || parentKey === "cpuMs"
80
-
81
- if (durationField && typeof childValue === "number") {
82
- if (typeof childValue !== "number" || !Number.isFinite(childValue) || childValue < 0) {
83
- throw new Error(`Invalid test profile duration field: ${key}`)
84
- }
85
-
86
- value[key] = roundProfileDuration(childValue)
87
- continue
88
- }
89
-
90
- if (key.endsWith("Ms") && key !== "cpuMs") {
91
- throw new Error(`Invalid test profile duration field: ${key}`)
92
- }
93
-
94
- normalizeDurations(childValue, key)
95
- }
96
- }
97
-
98
- /**
99
- * Returns a normalized JSON-safe rich profile.
100
- * @param {ReturnType<typeof JSON.parse>} profile - Rich profile document.
101
- * @returns {ReturnType<typeof JSON.parse>} - Normalized document.
102
- */
103
- function normalizedProfile(profile) {
104
- if (profile?.schema !== "velocious.test-profile" || profile?.schemaVersion !== 1) {
105
- throw new Error("Invalid Velocious test profile schema")
106
- }
107
-
108
- assertProfilePrivacy(profile)
109
- const normalized = JSON.parse(JSON.stringify(profile))
110
-
111
- normalizeDurations(normalized)
112
- return normalized
113
- }
114
-
115
- /**
116
- * Writes text through a same-directory temporary file and atomic rename.
117
- * @param {string} outputPath - Final output path.
118
- * @param {string} content - Complete output content.
119
- * @returns {Promise<void>} - Resolves after rename.
120
- */
121
- async function atomicWrite(outputPath, content) {
122
- const directory = path.dirname(outputPath)
123
- const sequence = ++atomicWriteSequence
124
- const temporaryPath = path.join(directory, `.${path.basename(outputPath)}.${process.pid}.${sequence}.tmp`)
125
- let temporaryFileCreated = false
126
-
127
- await fs.mkdir(directory, {recursive: true})
128
-
129
- try {
130
- await fs.writeFile(temporaryPath, content, {encoding: "utf8", flag: "wx"})
131
- temporaryFileCreated = true
132
- await fs.rename(temporaryPath, outputPath)
133
- } catch (error) {
134
- if (temporaryFileCreated) {
135
- try {
136
- await fs.unlink(temporaryPath)
137
- } catch (cleanupError) {
138
- if (!(cleanupError instanceof Error) || !("code" in cleanupError) || cleanupError.code !== "ENOENT") {
139
- throw new AggregateError(
140
- [error, cleanupError],
141
- `Failed to write and clean up test profile output: ${outputPath}`,
142
- {cause: cleanupError}
143
- )
144
- }
145
- }
146
- }
147
-
148
- throw error
149
- }
150
- }
151
-
152
- /**
153
- * Creates a sorted plain timing manifest.
154
- * @param {ReturnType<typeof JSON.parse>} profile - Rich profile document.
155
- * @returns {Record<string, number>} - Sorted file-duration map.
156
- */
157
- export function timingManifestFromProfile(profile) {
158
- /** @type {Record<string, number>} */
159
- const manifest = {}
160
- const validatedManifest = validateTimingManifest(profile.timingManifest || {}, {source: "Test profile timing manifest"})
161
-
162
- for (const [filePath, durationMs] of Object.entries(validatedManifest)) {
163
- manifest[filePath] = roundProfileDuration(durationMs)
164
- }
165
-
166
- return manifest
167
- }
168
-
169
- /**
170
- * Atomically writes a canonical splitter-compatible timing manifest.
171
- * @param {object} args - Output arguments.
172
- * @param {string} args.outputPath - Final output path.
173
- * @param {Record<string, number>} args.timingManifest - Validated or candidate timing manifest.
174
- * @returns {Promise<void>} - Resolves after atomic replacement.
175
- */
176
- export async function writeTimingManifest({outputPath, timingManifest}) {
177
- const normalized = validateTimingManifest(timingManifest)
178
- /** @type {Record<string, number>} */
179
- const rounded = {}
180
-
181
- for (const [filePath, durationMs] of Object.entries(normalized)) {
182
- rounded[filePath] = roundProfileDuration(durationMs)
183
- }
184
-
185
- await atomicWrite(outputPath, `${JSON.stringify(rounded, null, 2)}\n`)
186
- }
187
-
188
- /**
189
- * Atomically writes requested test profile outputs.
190
- * @param {object} args - Output options.
191
- * @param {ReturnType<typeof JSON.parse>} args.profile - Rich profile document.
192
- * @param {string} [args.profileJsonPath] - Rich JSON path.
193
- * @param {string} [args.timingManifestOutputPath] - Plain timing manifest path.
194
- * @returns {Promise<void>} - Resolves after all requested writes.
195
- */
196
- export async function writeTestProfileOutputs({profile, profileJsonPath, timingManifestOutputPath}) {
197
- const normalized = normalizedProfile(profile)
198
- const timingManifest = timingManifestFromProfile(normalized)
199
-
200
- normalized.timingManifest = timingManifest
201
-
202
- if (profileJsonPath) {
203
- await atomicWrite(profileJsonPath, `${JSON.stringify(normalized, null, 2)}\n`)
204
- }
205
-
206
- if (timingManifestOutputPath) {
207
- await writeTimingManifest({outputPath: timingManifestOutputPath, timingManifest})
208
- }
209
- }
210
-
211
- /**
212
- * Formats a compact Benchmark-style console summary.
213
- * @param {ReturnType<typeof JSON.parse>} profile - Rich profile document.
214
- * @param {{profileJsonPath?: string, timingManifestOutputPath?: string}} [outputs] - Written output paths.
215
- * @returns {string} - Console summary.
216
- */
217
- export function formatTestProfileSummary(profile, outputs = {}) {
218
- const lines = [
219
- "Test profile",
220
- "Phase".padEnd(31) + "Count".padStart(5) + "Real ms".padStart(13) + "CPU ms".padStart(13)
221
- ]
222
-
223
- for (const phase of PHASE_ORDER) {
224
- const aggregate = profile.phases?.[phase]
225
-
226
- if (!aggregate) continue
227
-
228
- lines.push(
229
- phase.padEnd(31) +
230
- String(aggregate.count).padStart(5) +
231
- aggregate.totalMs.toFixed(3).padStart(13) +
232
- aggregate.cpuMs.total.toFixed(3).padStart(13)
233
- )
234
- }
235
-
236
- if (profile.pools?.length > 0) {
237
- lines.push("Pools")
238
-
239
- for (const pool of profile.pools) {
240
- lines.push(
241
- `Pool ${pool.identifier}: created=${pool.connectionCreation.count}` +
242
- ` failed=${pool.connectionCreation.failedCount}` +
243
- ` wait=${pool.checkoutWait.count}/${pool.checkoutWait.totalMs.toFixed(3)}ms` +
244
- ` timeouts=${pool.checkoutTimeoutCount}` +
245
- ` reaped=${pool.idleReap.disposalCount}` +
246
- ` peak=${pool.peakLiveConnections}`
247
- )
248
- }
249
- }
250
-
251
- if (outputs.profileJsonPath) lines.push(`Rich JSON: ${outputs.profileJsonPath}`)
252
- if (outputs.timingManifestOutputPath) lines.push(`Timing manifest: ${outputs.timingManifestOutputPath}`)
253
-
254
- return lines.join("\n")
255
- }
3
+ export {
4
+ formatTestProfileSummary,
5
+ loadTimingManifest,
6
+ resolveTestProfileOptions,
7
+ timingManifestFromProfile,
8
+ writeTestProfileOutputs,
9
+ writeTimingManifest
10
+ } from "@velocious/testing/node"