velocious 1.0.549 → 1.0.552

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 (56) hide show
  1. package/README.md +13 -0
  2. package/build/background-jobs/json-socket.js +25 -0
  3. package/build/background-jobs/socket-request.js +48 -6
  4. package/build/background-jobs/status-reporter.js +14 -2
  5. package/build/configuration-types.js +1 -0
  6. package/build/database/advisory-lock-runner.js +17 -8
  7. package/build/database/drivers/base.js +12 -0
  8. package/build/database/drivers/mssql/index.js +52 -3
  9. package/build/database/drivers/mysql/index.js +34 -0
  10. package/build/database/structure-sql-loader.js +12 -6
  11. package/build/src/background-jobs/json-socket.d.ts +20 -0
  12. package/build/src/background-jobs/json-socket.d.ts.map +1 -1
  13. package/build/src/background-jobs/json-socket.js +25 -1
  14. package/build/src/background-jobs/socket-request.d.ts +13 -1
  15. package/build/src/background-jobs/socket-request.d.ts.map +1 -1
  16. package/build/src/background-jobs/socket-request.js +44 -7
  17. package/build/src/background-jobs/status-reporter.d.ts +12 -1
  18. package/build/src/background-jobs/status-reporter.d.ts.map +1 -1
  19. package/build/src/background-jobs/status-reporter.js +14 -3
  20. package/build/src/configuration-types.d.ts +5 -0
  21. package/build/src/configuration-types.d.ts.map +1 -1
  22. package/build/src/configuration-types.js +2 -1
  23. package/build/src/database/advisory-lock-runner.d.ts +25 -8
  24. package/build/src/database/advisory-lock-runner.d.ts.map +1 -1
  25. package/build/src/database/advisory-lock-runner.js +18 -9
  26. package/build/src/database/drivers/base.d.ts +9 -0
  27. package/build/src/database/drivers/base.d.ts.map +1 -1
  28. package/build/src/database/drivers/base.js +12 -1
  29. package/build/src/database/drivers/mssql/index.d.ts +9 -0
  30. package/build/src/database/drivers/mssql/index.d.ts.map +1 -1
  31. package/build/src/database/drivers/mssql/index.js +45 -2
  32. package/build/src/database/drivers/mysql/index.d.ts.map +1 -1
  33. package/build/src/database/drivers/mysql/index.js +35 -1
  34. package/build/src/database/structure-sql-loader.d.ts.map +1 -1
  35. package/build/src/database/structure-sql-loader.js +14 -8
  36. package/build/src/testing/test.d.ts +2 -1
  37. package/build/src/testing/test.d.ts.map +1 -1
  38. package/build/src/testing/test.js +3 -2
  39. package/build/src/testing/wait-for-event.d.ts +46 -0
  40. package/build/src/testing/wait-for-event.d.ts.map +1 -0
  41. package/build/src/testing/wait-for-event.js +64 -0
  42. package/build/testing/test.js +2 -1
  43. package/build/testing/wait-for-event.js +72 -0
  44. package/build/tsconfig.tsbuildinfo +1 -1
  45. package/package.json +2 -2
  46. package/src/background-jobs/json-socket.js +25 -0
  47. package/src/background-jobs/socket-request.js +48 -6
  48. package/src/background-jobs/status-reporter.js +14 -2
  49. package/src/configuration-types.js +1 -0
  50. package/src/database/advisory-lock-runner.js +17 -8
  51. package/src/database/drivers/base.js +12 -0
  52. package/src/database/drivers/mssql/index.js +52 -3
  53. package/src/database/drivers/mysql/index.js +34 -0
  54. package/src/database/structure-sql-loader.js +12 -6
  55. package/src/testing/test.js +2 -1
  56. package/src/testing/wait-for-event.js +72 -0
@@ -190,6 +190,10 @@ export default class VelociousDatabaseDriversMysql extends Base{
190
190
 
191
191
  if ("username" in args) connectArgs["user"] = args["username"]
192
192
  if ("charset" in args) connectArgs["charset"] = args["charset"]
193
+ // Opt-in only. Lets a whole structure SQL dump run in one round-trip via
194
+ // {@link execStructureScript}; off by default so ordinary queries keep rejecting
195
+ // stacked statements.
196
+ if ("multipleStatements" in args) connectArgs["multipleStatements"] = Boolean(digg(args, "multipleStatements"))
193
197
 
194
198
  return connectArgs
195
199
  }
@@ -417,6 +421,36 @@ export default class VelociousDatabaseDriversMysql extends Base{
417
421
  })
418
422
  }
419
423
 
424
+ /**
425
+ * Executes a full multi-statement structure SQL script in one round-trip when the
426
+ * connection was configured with `multipleStatements: true`. Runs on the pooled
427
+ * connection so the caller's `SET FOREIGN_KEY_CHECKS = 0` applies. Returns false so
428
+ * the caller runs statements individually when multi-statement queries are off.
429
+ * @param {string} structureSql - Full multi-statement structure SQL.
430
+ * @returns {Promise<boolean>} - Whether the script was executed as one batch.
431
+ */
432
+ async execStructureScript(structureSql) {
433
+ if (!this.getArgs().multipleStatements) return false
434
+
435
+ // The batched pool call below bypasses Base#query, so re-run the same read-only
436
+ // write guard the per-statement path applies before executing the dump.
437
+ this._assertWritableQuery(structureSql)
438
+
439
+ if (!this.pool) await this.connect()
440
+ if (!this.pool) throw new Error("MySQL pool failed to initialize")
441
+
442
+ const pool = this.pool
443
+
444
+ await new Promise((resolve, reject) => {
445
+ pool.query(structureSql, (error) => {
446
+ if (error) reject(error)
447
+ else resolve(undefined)
448
+ })
449
+ })
450
+
451
+ return true
452
+ }
453
+
420
454
  /**
421
455
  * Runs query to sql.
422
456
  * @param {import("../../query/index.js").default} query - Query instance.
@@ -31,17 +31,23 @@ export default class StructureSqlLoader {
31
31
  await db.disableForeignKeys()
32
32
 
33
33
  try {
34
- if (executableConnection) {
35
- await executableConnection.exec(structureSql)
36
- } else {
37
- for (const statement of statements) {
38
- await db.query(statement)
34
+ // Prefer a single round-trip for the whole dump: a driver-native multi-statement
35
+ // batch (e.g. MySQL with `multipleStatements`) first, then a native `exec`
36
+ // connection (e.g. SQLite), and finally per-statement execution. Running the
37
+ // whole dump at once is far faster than issuing every CREATE separately.
38
+ if (!await db.execStructureScript(structureSql)) {
39
+ if (executableConnection) {
40
+ await executableConnection.exec(structureSql)
41
+ } else {
42
+ for (const statement of statements) {
43
+ await db.query(statement)
44
+ }
39
45
  }
40
46
  }
41
47
  } finally {
42
48
  await db.enableForeignKeys()
43
49
 
44
- // The native `exec` path mutates the schema outside `Base#query`, so the
50
+ // The batch / native `exec` paths mutate the schema outside `Base#query`, so the
45
51
  // usual post-DDL schema-cache invalidation never runs. Clear it here so a
46
52
  // caller that read schema metadata before provisioning (e.g. an empty table
47
53
  // list) does not keep seeing the pre-load schema afterwards. Harmless for the
@@ -4,6 +4,7 @@ import path from "path"
4
4
  import {fileURLToPath} from "url"
5
5
  import EventEmitter from "../utils/event-emitter.js"
6
6
  import Expect from "./expect.js"
7
+ import waitForEvent from "./wait-for-event.js"
7
8
  import {arrayContaining, objectContaining} from "./expect-utils.js"
8
9
 
9
10
  /**
@@ -351,7 +352,7 @@ globalThis.fit = fit
351
352
  globalThis.testEvents = testEvents
352
353
  globalThis.configureTests = configureTests
353
354
 
354
- export {afterAll, afterEach, beforeAll, beforeEach, configureTests, describe, expect, fit, it, arrayContaining, objectContaining, testConfig, testEvents, tests}
355
+ export {afterAll, afterEach, beforeAll, beforeEach, configureTests, describe, expect, fit, it, arrayContaining, objectContaining, testConfig, testEvents, tests, waitForEvent}
355
356
  /**
356
357
  * VelociousTestConfig type.
357
358
  * @typedef {object} VelociousTestConfig
@@ -0,0 +1,72 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * @typedef {object} WaitForEventOptions
5
+ * @property {number} [timeoutMs] Timeout in milliseconds (default: 5000).
6
+ * @property {(...args: Array<?>) => boolean} [filter] Only resolve when this predicate returns true for the emitted arguments.
7
+ */
8
+
9
+ /**
10
+ * @typedef {object} EventEmitterLike
11
+ * @property {(event: string, listener: (...args: Array<?>) => void) => void} on Registers a listener.
12
+ * @property {(event: string, listener: (...args: Array<?>) => void) => void} off Removes a listener.
13
+ */
14
+
15
+ /**
16
+ * Resolves the moment `eventName` fires on `emitter` — optionally only when `filter`
17
+ * matches the emitted arguments — instead of sleeping a fixed duration. Rejects with a
18
+ * timeout error if the event does not fire within `timeoutMs`. The listener is always
19
+ * removed (on resolve and on timeout).
20
+ *
21
+ * Use this to await a real signal (a background job finishing, a model update, a
22
+ * websocket message) rather than guessing a delay. To poll an arbitrary condition
23
+ * instead of a discrete event, use awaitery's `waitFor`.
24
+ * @param {EventEmitterLike} emitter - Event emitter exposing `on`/`off` (Node EventEmitter, eventemitter3, velocious `testEvents`, ...).
25
+ * @param {string} eventName - The event to wait for.
26
+ * @param {WaitForEventOptions} [options] - Options.
27
+ * @returns {Promise<?>} - Resolves with the single emitted argument, an array when the event emits multiple, or undefined when it emits none.
28
+ */
29
+ export default function waitForEvent(emitter, eventName, options = {}) {
30
+ const {timeoutMs = 5000, filter} = options
31
+
32
+ return new Promise((resolve, reject) => {
33
+ /** @type {ReturnType<typeof setTimeout>} */
34
+ let timer
35
+
36
+ /**
37
+ * Resolves the wait when a matching event fires, removing itself first. A filter
38
+ * that throws (e.g. it assumes an event shape an intermediate emission doesn't
39
+ * have) rejects immediately rather than leaving the waiter pending until timeout.
40
+ * @param {...?} args - Emitted arguments.
41
+ * @returns {void}
42
+ */
43
+ const listener = (...args) => {
44
+ if (filter) {
45
+ let matched
46
+
47
+ try {
48
+ matched = filter(...args)
49
+ } catch (error) {
50
+ clearTimeout(timer)
51
+ emitter.off(eventName, listener)
52
+ reject(error)
53
+
54
+ return
55
+ }
56
+
57
+ if (!matched) return
58
+ }
59
+
60
+ clearTimeout(timer)
61
+ emitter.off(eventName, listener)
62
+ resolve(args.length > 1 ? args : args[0])
63
+ }
64
+
65
+ timer = setTimeout(() => {
66
+ emitter.off(eventName, listener)
67
+ reject(new Error(`Timed out after ${timeoutMs}ms waiting for event ${JSON.stringify(eventName)}`))
68
+ }, timeoutMs)
69
+
70
+ emitter.on(eventName, listener)
71
+ })
72
+ }