velocious 1.0.652 → 1.0.654

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 (34) hide show
  1. package/README.md +14 -1
  2. package/build/database/query/model-class-query.js +3 -1
  3. package/build/database/query/where-hash.js +12 -1
  4. package/build/database/query/where-in.js +48 -0
  5. package/build/database/query/where-model-class-hash.js +34 -1
  6. package/build/environment-handlers/node/cli/commands/test.js +2 -1
  7. package/build/src/database/query/model-class-query.js +5 -2
  8. package/build/src/database/query/where-hash.d.ts.map +1 -1
  9. package/build/src/database/query/where-hash.js +14 -2
  10. package/build/src/database/query/where-in.d.ts +22 -0
  11. package/build/src/database/query/where-in.d.ts.map +1 -0
  12. package/build/src/database/query/where-in.js +40 -0
  13. package/build/src/database/query/where-model-class-hash.d.ts +11 -0
  14. package/build/src/database/query/where-model-class-hash.d.ts.map +1 -1
  15. package/build/src/database/query/where-model-class-hash.js +33 -2
  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 +4 -2
  18. package/build/src/testing/test-runner.d.ts +23 -0
  19. package/build/src/testing/test-runner.d.ts.map +1 -1
  20. package/build/src/testing/test-runner.js +29 -2
  21. package/build/src/testing/velocious-runner-reporter.d.ts +7 -0
  22. package/build/src/testing/velocious-runner-reporter.d.ts.map +1 -1
  23. package/build/src/testing/velocious-runner-reporter.js +54 -12
  24. package/build/testing/test-runner.js +33 -1
  25. package/build/testing/velocious-runner-reporter.js +52 -11
  26. package/package.json +4 -4
  27. package/scripts/browser-test-session.js +21 -23
  28. package/src/database/query/model-class-query.js +3 -1
  29. package/src/database/query/where-hash.js +12 -1
  30. package/src/database/query/where-in.js +48 -0
  31. package/src/database/query/where-model-class-hash.js +34 -1
  32. package/src/environment-handlers/node/cli/commands/test.js +2 -1
  33. package/src/testing/test-runner.js +33 -1
  34. package/src/testing/velocious-runner-reporter.js +52 -11
package/README.md CHANGED
@@ -178,7 +178,7 @@ Baselines are generated against a fresh checkout (no generated dummy `configurat
178
178
  # Testing
179
179
 
180
180
  Application tests may import the testing DSL from the independent public package.
181
- `@velocious/testing` `0.0.9` is the declaration and execution engine. Compatible
181
+ `@velocious/testing` `0.0.12` is the declaration and execution engine. Compatible
182
182
  installed copies share one protocol-1/schema-3 default registry. Velocious adapts each
183
183
  package-owned attempt with its database, request, profiling, and cleanup behavior; the
184
184
  existing Velocious facade exports the same declaration DSL and remains supported.
@@ -327,6 +327,8 @@ Use `consoleOutput: "live"` to preserve the previous passthrough behavior where
327
327
 
328
328
  Listen for attempt and retry events if you need to reset shared state after a failed attempt or log retry lifecycle details. `testAttemptFailed` fires after every failed attempt, including the final failed attempt when no retries remain. `testRetrying` only fires before a retry, and `testFailed` only fires after retries are exhausted.
329
329
 
330
+ A shared resource owner may throw an error carrying `terminalResource: {scope: "run", name: "shared-resource"}`. With a compatible `@velocious/testing` runner, Velocious preserves this contract through causes and aggregate errors, reports the originating failure with full causal stacks, suppresses retries, and emits `testNotRun` for later selected cases instead of running their callbacks. `testNotRun` receives `{configuration, test, testRunner}`; `test` has `status: "not-run"`, `fullName`, `location`, and the originating `reason`. `testRunner.getNotRunTests()` and `getNotRunTestDetails()` expose this separate accounting. Entered suites clean up once; the CLI exits unsuccessfully even for terminal setup with zero executed cases. Unmatched filters retain their explanatory message and `no-tests` profile status, independently of terminal setup failures. This does not restart a browser or cancel already-issued work. Upgrade the shared runner and this reporter adapter together before enabling terminal resource owners; earlier shared runners ignore the marker. See [terminal resource reporting](docs/terminal-resource-reporting.md).
331
+
330
332
  ```js
331
333
  import {testEvents} from "velocious/build/src/testing/test.js"
332
334
 
@@ -1683,9 +1685,20 @@ const tasks = await Task.all().toArray()
1683
1685
 
1684
1686
  ### Filtering
1685
1687
 
1688
+ Use `{in: [...]}` for explicit null-aware membership. Mixed lists match either
1689
+ IN members or SQL NULL, grouped locally so sibling and chained filters still
1690
+ apply. Null-only lists use IS NULL; empty lists match nothing. Direct arrays keep
1691
+ their existing driver-specific behavior, including string-null quoting and
1692
+ column collation. See [Query filtering](docs/query-filtering.md) for
1693
+ validation, normalization, negation, relationships and raw-query boundaries.
1694
+
1686
1695
  ```js
1687
1696
  const tasks = await Task.where({status: "open"}).toArray()
1688
1697
 
1698
+ const nullOrManualTasks = await Task
1699
+ .where({projectId: project.id(), description: {in: [null, "manual"]}})
1700
+ .toArray()
1701
+
1689
1702
  const tasksForActiveProjects = await Task.where({
1690
1703
  project: {projectDetail: {isActive: true}}
1691
1704
  }).toArray()
@@ -1468,7 +1468,9 @@ function splitWhereHash({hash, modelClass}) {
1468
1468
  const isNested = isPlainObject(value)
1469
1469
  const relationship = getRelationshipByName(modelClass, key)
1470
1470
 
1471
- if (isNested) {
1471
+ if (isNested && !relationship && resolveColumnName(modelClass, key)) {
1472
+ resolvedHash[key] = value
1473
+ } else if (isNested) {
1472
1474
  if (relationship) {
1473
1475
  const rawTargetModelClass = relationship.getTargetModelClass()
1474
1476
  if (!rawTargetModelClass) {
@@ -1,6 +1,7 @@
1
1
  // @ts-check
2
2
 
3
3
  import WhereBase from "./where-base.js"
4
+ import WhereIn from "./where-in.js"
4
5
 
5
6
  /**
6
7
  * VelociousDatabaseQueryWhereHash class.
@@ -50,7 +51,17 @@ export default class VelociousDatabaseQueryWhereHash extends WhereBase {
50
51
  if (index > 0) sql += " AND "
51
52
  sql += "1=0"
52
53
  } else if (!Array.isArray(whereValue) && whereValue !== null && typeof whereValue == "object") {
53
- sql += this._whereSQLFromHash(whereValue, whereKey, index)
54
+ if (tableName && "in" in whereValue) {
55
+ if (index > 0) sql += " AND "
56
+
57
+ sql += WhereIn.toSql({
58
+ columnSql: `${options.quoteTableName(tableName)}.${options.quoteColumnName(whereKey)}`,
59
+ options,
60
+ values: WhereIn.values(whereValue)
61
+ })
62
+ } else {
63
+ sql += this._whereSQLFromHash(whereValue, whereKey, index)
64
+ }
54
65
  } else {
55
66
  if (index > 0) sql += " AND "
56
67
 
@@ -0,0 +1,48 @@
1
+ // @ts-check
2
+
3
+ /** @typedef {string | number | boolean | null} InValue */
4
+
5
+ export default class WhereIn {
6
+ /**
7
+ * Validates an explicit membership descriptor at a column boundary.
8
+ * @param {unknown} condition - Untrusted column condition, narrowed before use.
9
+ * @returns {InValue[]} - Validated members in a new array.
10
+ */
11
+ static values(condition) {
12
+ if (condition === null || typeof condition !== "object" ||
13
+ !Object.hasOwn(condition, "in") || !("in" in condition) ||
14
+ Reflect.ownKeys(condition).length !== 1 || !Array.isArray(condition.in)) {
15
+ throw new Error("Invalid IN condition: expected an object with only an own 'in' array")
16
+ }
17
+
18
+ /** @type {InValue[]} */
19
+ const values = []
20
+
21
+ for (const value of condition.in) {
22
+ if (value !== null && typeof value !== "string" && typeof value !== "boolean" &&
23
+ !(typeof value === "number" && Number.isFinite(value))) {
24
+ throw new Error("Invalid IN condition: members must be strings, finite numbers, booleans or null")
25
+ }
26
+
27
+ values.push(value)
28
+ }
29
+
30
+ return values
31
+ }
32
+
33
+ /**
34
+ * Renders membership without letting its null branch escape sibling filters.
35
+ * @param {{columnSql: string, inColumnSql?: string, values: InValue[], options: import("../query-parser/options.js").default}} args - Quoted column operands, normalized members and driver quoting.
36
+ * @returns {string} - Complete membership predicate.
37
+ */
38
+ static toSql({columnSql, inColumnSql = columnSql, values, options}) {
39
+ const nonNullValues = values.filter((value) => value !== null)
40
+ const includesNull = values.includes(null)
41
+
42
+ if (nonNullValues.length === 0) return includesNull ? `${columnSql} IS NULL` : "1=0"
43
+
44
+ const membershipSql = `${inColumnSql} IN (${nonNullValues.map((value) => options.quote(value)).join(", ")})`
45
+
46
+ return includesNull ? `(${membershipSql} OR ${columnSql} IS NULL)` : membershipSql
47
+ }
48
+ }
@@ -3,6 +3,7 @@
3
3
  import * as inflection from "inflection"
4
4
  import {isPlainObject} from "is-plain-object"
5
5
  import WhereBase from "./where-base.js"
6
+ import WhereIn from "./where-in.js"
6
7
 
7
8
  /**
8
9
  * No match.
@@ -378,6 +379,34 @@ export default class VelociousDatabaseQueryWhereModelClassHash extends WhereBase
378
379
  return normalized
379
380
  }
380
381
 
382
+ /**
383
+ * Normalizes explicit membership through the model's column metadata.
384
+ * @param {{modelClass: typeof import("../record/index.js").default, columnName: string, tableName?: string, condition: unknown}} args - Resolved column and untrusted membership descriptor.
385
+ * @returns {string} - Complete column membership predicate.
386
+ */
387
+ _whereSQLFromInCondition({modelClass, columnName, tableName, condition}) {
388
+ const options = this.getOptions()
389
+ const values = WhereIn.values(condition)
390
+ const normalizedValues = this._normalizeSqliteBooleanValue({columnName, modelClass, value: values})
391
+ /** @type {import("./where-in.js").InValue[] | typeof NO_MATCH} */
392
+ const typedValues = this._normalizeValueForColumnType({columnName, modelClass, value: normalizedValues})
393
+
394
+ if (typedValues === NO_MATCH) return "1=0"
395
+
396
+ const columnSql = tableName
397
+ ? `${options.quoteTableName(tableName)}.${options.quoteColumnName(columnName)}`
398
+ : options.quoteColumnName(columnName)
399
+ const columnType = modelClass.getColumnTypeByName(columnName)
400
+ const castText = this.getQuery().driver.getType() === "mssql" && columnType?.toLowerCase() === "text"
401
+
402
+ return WhereIn.toSql({
403
+ columnSql,
404
+ inColumnSql: castText ? `CAST(${columnSql} AS NVARCHAR(MAX))` : columnSql,
405
+ options,
406
+ values: typedValues
407
+ })
408
+ }
409
+
381
410
  /**
382
411
  * Runs where sqlfrom hash.
383
412
  * @param {WhereHash} hash - Hash.
@@ -400,7 +429,11 @@ export default class VelociousDatabaseQueryWhereModelClassHash extends WhereBase
400
429
  : null
401
430
  const resolvedColumnName = this._resolveColumnName(modelClass, whereKey)
402
431
 
403
- if (relationship && tuples) {
432
+ if (resolvedColumnName && !relationship && isPlainObject(whereValue)) {
433
+ if (index > 0) sql += " AND "
434
+
435
+ sql += this._whereSQLFromInCondition({columnName: resolvedColumnName, condition: whereValue, modelClass, tableName})
436
+ } else if (relationship && tuples) {
404
437
  if (index > 0) sql += " AND "
405
438
 
406
439
  const rawTargetModelClass = relationship.getTargetModelClass()
@@ -203,7 +203,7 @@ export default class VelociousCliCommandsTest extends BaseCommand {
203
203
  const hasExampleFilters = examplePatterns.length > 0
204
204
  const hasTagFilters = includeTags.length > 0 || effectiveExcludeTagCount > 0
205
205
 
206
- if ((hasTagFilters || hasLineFilters || hasExampleFilters) && executedTests === 0) {
206
+ if ((hasTagFilters || hasLineFilters || hasExampleFilters) && testRunner.hasNoMatches()) {
207
207
  console.error(picocolors.red("\nNo tests matched the provided filters"))
208
208
  await finalizeProfile("no-tests")
209
209
  process.exit(1)
@@ -247,6 +247,7 @@ export default class VelociousCliCommandsTest extends BaseCommand {
247
247
  }
248
248
  }
249
249
 
250
+ if (testRunner.getNotRunTests() > 0) console.error(`${testRunner.getNotRunTests()} tests not run because a shared resource failed`)
250
251
  console.error(picocolors.red(`\nTest run failed with ${testRunner.getFailedTests()} failed tests and ${testRunner.getSuccessfulTests()} successfull`))
251
252
  await finalizeProfile("failed")
252
253
  process.exit(1)