velocious 1.0.714 → 1.0.715

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 (49) hide show
  1. package/build/background-jobs/local-store.js +3 -0
  2. package/build/background-jobs/store.js +77 -1
  3. package/build/background-jobs/types.js +2 -0
  4. package/build/database/drivers/base.js +60 -0
  5. package/build/database/drivers/mssql/index.js +59 -0
  6. package/build/database/drivers/mysql/index.js +51 -0
  7. package/build/database/drivers/pgsql/index.js +61 -0
  8. package/build/database/initializer-from-require-context.js +31 -7
  9. package/build/database/schema-metadata-prefetcher.js +108 -0
  10. package/build/src/background-jobs/local-store.d.ts.map +1 -1
  11. package/build/src/background-jobs/local-store.js +4 -1
  12. package/build/src/background-jobs/store.d.ts +24 -0
  13. package/build/src/background-jobs/store.d.ts.map +1 -1
  14. package/build/src/background-jobs/store.js +66 -2
  15. package/build/src/background-jobs/types.d.ts +10 -0
  16. package/build/src/background-jobs/types.d.ts.map +1 -1
  17. package/build/src/background-jobs/types.js +3 -1
  18. package/build/src/database/drivers/base.d.ts +29 -0
  19. package/build/src/database/drivers/base.d.ts.map +1 -1
  20. package/build/src/database/drivers/base.js +55 -1
  21. package/build/src/database/drivers/mssql/index.d.ts +9 -0
  22. package/build/src/database/drivers/mssql/index.d.ts.map +1 -1
  23. package/build/src/database/drivers/mssql/index.js +54 -1
  24. package/build/src/database/drivers/mysql/index.d.ts +13 -0
  25. package/build/src/database/drivers/mysql/index.d.ts.map +1 -1
  26. package/build/src/database/drivers/mysql/index.js +46 -1
  27. package/build/src/database/drivers/pgsql/index.d.ts +12 -2
  28. package/build/src/database/drivers/pgsql/index.d.ts.map +1 -1
  29. package/build/src/database/drivers/pgsql/index.js +56 -1
  30. package/build/src/database/initializer-from-require-context.d.ts.map +1 -1
  31. package/build/src/database/initializer-from-require-context.js +26 -7
  32. package/build/src/database/schema-metadata-prefetcher.d.ts +53 -0
  33. package/build/src/database/schema-metadata-prefetcher.d.ts.map +1 -0
  34. package/build/src/database/schema-metadata-prefetcher.js +95 -0
  35. package/build/src/utils/bounded-task-runner.d.ts +26 -0
  36. package/build/src/utils/bounded-task-runner.d.ts.map +1 -0
  37. package/build/src/utils/bounded-task-runner.js +44 -0
  38. package/build/utils/bounded-task-runner.js +51 -0
  39. package/package.json +1 -1
  40. package/src/background-jobs/local-store.js +3 -0
  41. package/src/background-jobs/store.js +77 -1
  42. package/src/background-jobs/types.js +2 -0
  43. package/src/database/drivers/base.js +60 -0
  44. package/src/database/drivers/mssql/index.js +59 -0
  45. package/src/database/drivers/mysql/index.js +51 -0
  46. package/src/database/drivers/pgsql/index.js +61 -0
  47. package/src/database/initializer-from-require-context.js +31 -7
  48. package/src/database/schema-metadata-prefetcher.js +108 -0
  49. package/src/utils/bounded-task-runner.js +51 -0
@@ -0,0 +1,108 @@
1
+ // @ts-check
2
+
3
+ import Logger from "../logger.js"
4
+
5
+ /**
6
+ * Loads table and column metadata for a whole set of model classes up front.
7
+ *
8
+ * Model initialization otherwise reads each table's columns on its own, which
9
+ * turns a boot into one catalog query per model (and per HTTP worker process).
10
+ * Drivers that can answer for many tables in one statement are asked to do so;
11
+ * the resulting columns are seeded into the driver's per-table schema cache, so
12
+ * the initialization pass that follows resolves without querying at all.
13
+ */
14
+ export default class SchemaMetadataPrefetcher {
15
+ /**
16
+ * Builds a prefetcher for one configuration.
17
+ * @param {object} args - Prefetcher args.
18
+ * @param {import("../configuration.js").default} args.configuration - Configuration owning the database connections.
19
+ * @param {number} [args.concurrency] - Concurrency for drivers that can only read one table per query.
20
+ * @param {import("../logger.js").default} [args.logger] - Logger for debug output.
21
+ */
22
+ constructor({configuration, concurrency = 8, logger}) {
23
+ this.configuration = configuration
24
+ this.concurrency = concurrency
25
+ this.logger = logger || new Logger(this)
26
+ }
27
+
28
+ /**
29
+ * Prefetches column metadata for every model class that has a reachable table.
30
+ *
31
+ * Models whose table or connection is unavailable are skipped: their
32
+ * initialization stays deferred and will initialize lazily on first use, which
33
+ * is the same contract the per-model initialization applies.
34
+ * @param {{modelClasses: Array<typeof import("./record/index.js").default>}} args - Model classes to prefetch for.
35
+ * @returns {Promise<void>}
36
+ */
37
+ async prefetch({modelClasses}) {
38
+ /** @type {Map<import("./drivers/base.js").default, Map<string, import("./drivers/base-table.js").default>>} */
39
+ const tablesByDriver = new Map()
40
+
41
+ for (const modelClass of modelClasses) {
42
+ const table = await this.tableFor(modelClass)
43
+
44
+ if (!table) continue
45
+
46
+ const driver = table.getDriver()
47
+ let tables = tablesByDriver.get(driver)
48
+
49
+ if (!tables) {
50
+ tables = new Map()
51
+ tablesByDriver.set(driver, tables)
52
+ }
53
+
54
+ tables.set(table.getName(), table)
55
+
56
+ // Translated models initialize their translation class as part of the same
57
+ // startup pass, so its table has to be part of the batch as well - otherwise
58
+ // every translated model still costs its own catalog read.
59
+ const translationTable = await this.tableByName(driver, modelClass.getTranslationsTableName())
60
+
61
+ if (translationTable) tables.set(translationTable.getName(), translationTable)
62
+ }
63
+
64
+ const startedAt = Date.now()
65
+ let tableCount = 0
66
+
67
+ for (const [driver, tables] of tablesByDriver) {
68
+ await driver.prefetchTableColumns([...tables.values()], {concurrency: this.concurrency})
69
+ tableCount += tables.size
70
+ }
71
+
72
+ void this.logger.debug(() => ["schema metadata prefetched", {
73
+ databases: tablesByDriver.size,
74
+ durationMs: Date.now() - startedAt,
75
+ tables: tableCount
76
+ }])
77
+ }
78
+
79
+ /**
80
+ * Resolves a model's table without initializing its record metadata.
81
+ * @param {typeof import("./record/index.js").default} modelClass - Model class to resolve a table for.
82
+ * @returns {Promise<import("./drivers/base-table.js").default | null>} Table, or null when it is not reachable.
83
+ */
84
+ async tableFor(modelClass) {
85
+ try {
86
+ const driver = modelClass.connection({enforceTenantDatabaseScope: false})
87
+
88
+ return await this.tableByName(driver, modelClass.tableName())
89
+ } catch (error) {
90
+ // A model whose database is unreachable - or whose table is optional and
91
+ // absent - stays deferred rather than failing startup; it initializes
92
+ // lazily on first use.
93
+ void this.logger.debug(() => [`Leaving ${modelClass.name} out of schema prefetch`, error])
94
+
95
+ return null
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Looks a table up in the driver's table list without throwing when absent.
101
+ * @param {import("./drivers/base.js").default} driver - Driver owning the table.
102
+ * @param {string} tableName - Table name to resolve.
103
+ * @returns {Promise<import("./drivers/base-table.js").default | null>} Table, or null when it does not exist.
104
+ */
105
+ async tableByName(driver, tableName) {
106
+ return await driver.getTableByName(tableName, {throwError: false}) || null
107
+ }
108
+ }
@@ -0,0 +1,51 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * Runs one callback per item with a bounded number of callbacks in flight.
5
+ *
6
+ * Used where a startup pass has to touch many independent records (model
7
+ * metadata, per-table schema reads) and running them sequentially multiplies the
8
+ * round-trip latency by the item count, while running them all at once would
9
+ * open an unbounded number of database connections.
10
+ */
11
+ export default class BoundedTaskRunner {
12
+ /**
13
+ * Runs the worker for every item, keeping at most `concurrency` callbacks in
14
+ * flight and returning the results in input order.
15
+ * @template T, R
16
+ * @param {object} args - Run args.
17
+ * @param {number} [args.concurrency] - Maximum callbacks in flight.
18
+ * @param {T[]} args.items - Items to process.
19
+ * @param {(item: T, index: number) => Promise<R>} args.worker - Callback per item.
20
+ * @returns {Promise<R[]>} - Results in input order.
21
+ */
22
+ static async run({concurrency = 8, items, worker}) {
23
+ if (!Number.isInteger(concurrency) || concurrency < 1) {
24
+ throw new Error(`Concurrency must be a positive integer but got ${String(concurrency)}`)
25
+ }
26
+
27
+ /** @type {R[]} */
28
+ const results = new Array(items.length)
29
+ let nextIndex = 0
30
+
31
+ const runNext = async () => {
32
+ while (nextIndex < items.length) {
33
+ const index = nextIndex
34
+
35
+ nextIndex += 1
36
+ results[index] = await worker(items[index], index)
37
+ }
38
+ }
39
+
40
+ /** @type {Promise<void>[]} */
41
+ const runners = []
42
+
43
+ for (let index = 0; index < Math.min(concurrency, items.length); index++) {
44
+ runners.push(runNext())
45
+ }
46
+
47
+ await Promise.all(runners)
48
+
49
+ return results
50
+ }
51
+ }