velocious 1.0.520 → 1.0.522

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 (32) hide show
  1. package/README.md +4 -2
  2. package/build/background-jobs/store.js +40 -0
  3. package/build/configuration-types.js +18 -4
  4. package/build/database/drivers/base.js +13 -0
  5. package/build/database/drivers/mysql/index.js +1 -0
  6. package/build/src/background-jobs/store.d.ts +14 -0
  7. package/build/src/background-jobs/store.d.ts.map +1 -1
  8. package/build/src/background-jobs/store.js +36 -1
  9. package/build/src/configuration-types.d.ts +36 -7
  10. package/build/src/configuration-types.d.ts.map +1 -1
  11. package/build/src/configuration-types.js +19 -5
  12. package/build/src/database/drivers/base.d.ts +12 -0
  13. package/build/src/database/drivers/base.d.ts.map +1 -1
  14. package/build/src/database/drivers/base.js +13 -1
  15. package/build/src/database/drivers/mysql/index.d.ts.map +1 -1
  16. package/build/src/database/drivers/mysql/index.js +2 -1
  17. package/build/src/tenants/tenant-aggregator.d.ts +180 -0
  18. package/build/src/tenants/tenant-aggregator.d.ts.map +1 -0
  19. package/build/src/tenants/tenant-aggregator.js +285 -0
  20. package/build/src/tenants/tenant.d.ts +13 -0
  21. package/build/src/tenants/tenant.d.ts.map +1 -1
  22. package/build/src/tenants/tenant.js +17 -1
  23. package/build/tenants/tenant-aggregator.js +351 -0
  24. package/build/tenants/tenant.js +17 -0
  25. package/build/tsconfig.tsbuildinfo +1 -1
  26. package/package.json +1 -1
  27. package/src/background-jobs/store.js +40 -0
  28. package/src/configuration-types.js +18 -4
  29. package/src/database/drivers/base.js +13 -0
  30. package/src/database/drivers/mysql/index.js +1 -0
  31. package/src/tenants/tenant-aggregator.js +351 -0
  32. package/src/tenants/tenant.js +17 -0
@@ -0,0 +1,351 @@
1
+ // @ts-check
2
+
3
+ import Current from "../current.js"
4
+
5
+ /**
6
+ * @typedef {"SUM" | "COUNT" | "MAX" | "MIN"} AggregateOperation
7
+ */
8
+
9
+ /**
10
+ * @typedef {AggregateOperation | {op: AggregateOperation, column: string}} AggregateSpec
11
+ */
12
+
13
+ /**
14
+ * @typedef {object} SubqueryContext
15
+ * @property {(tableName: string) => string} table - Quotes a table for this tenant. In a cross-database `UNION ALL` it returns a `database`.`table` qualified identifier; when each tenant runs on its own connection it returns the plain quoted table name. Use it for every table the subquery reads so the same subquery works on both execution paths.
16
+ * @property {((value: ?) => string) & {list: (values: Array<?>) => string}} quote - Quotes a value for the active connection. `quote.list(values)` quotes and comma-joins an array for `IN (...)` clauses.
17
+ * @property {object} tenant - The tenant descriptor this subquery is being built for.
18
+ * @property {import("../database/drivers/base.js").default} connection - The driver the query will run on.
19
+ */
20
+
21
+ /**
22
+ * @typedef {object} TenantAggregateOptions
23
+ * @property {string} identifier - Database identifier whose tenants are aggregated (for example `"projectTenant"`).
24
+ * @property {object[]} [tenants] - Explicit tenant descriptors to aggregate. Defaults to every tenant the identifier's provider `listTenants` returns.
25
+ * @property {(tenant: ?) => boolean} [filter] - Optional filter applied to the resolved tenant list.
26
+ * @property {string[]} [keyColumns] - Columns the aggregate is grouped by (for example `["docker_server_id"]`). Empty means a single grand-total row.
27
+ * @property {Record<string, AggregateSpec>} aggregates - Output column name to aggregate. `"SUM"` is shorthand for `{op: "SUM", column: <output name>}`; use `{op: "COUNT", column: "*"}` for `COUNT(*)`.
28
+ * @property {(context: SubqueryContext) => string} subquery - Builds one tenant's inner `SELECT`, which must select every `keyColumns` entry plus every aggregate source column.
29
+ * @property {import("../configuration.js").default} [configuration] - Configuration to run against. Defaults to the current configuration.
30
+ */
31
+
32
+ /**
33
+ * @typedef {{tenant: object, database: string | undefined, serverKey: string}} ResolvedTenant
34
+ */
35
+
36
+ const AGGREGATE_OPERATIONS = new Set(["SUM", "COUNT", "MAX", "MIN"])
37
+ const DERIVED_TABLE_ALIAS = "velocious_tenant_aggregate"
38
+
39
+ /**
40
+ * Runs one aggregate query across many tenant databases and merges the result. Tenant databases may
41
+ * be co-located on the default server or spread across other servers, and they can appear or
42
+ * disappear at runtime; this resolves the live tenant list, groups tenants by the server they live
43
+ * on, and — per server — emits a single cross-database `UNION ALL` when the driver supports qualified
44
+ * cross-database references (MySQL/MSSQL) or falls back to one query per tenant otherwise
45
+ * (PostgreSQL/SQLite). Results from every server are merged with the aggregate's own operation, so
46
+ * callers get one combined result set regardless of how the tenants are distributed. Reached through
47
+ * `Tenant.aggregateAcross`.
48
+ */
49
+ export default class TenantAggregator {
50
+ /**
51
+ * Prepares an aggregate run, normalizing the aggregate specs up front.
52
+ * @param {TenantAggregateOptions} options - Aggregate configuration.
53
+ */
54
+ constructor(options) {
55
+ this.options = options
56
+ this.configuration = options.configuration ?? Current.configuration()
57
+ this.keyColumns = options.keyColumns ?? []
58
+ this.aggregates = this._normalizeAggregates(options.aggregates)
59
+ }
60
+
61
+ /**
62
+ * Resolves the tenant list, runs the aggregate per server, and merges everything.
63
+ * @returns {Promise<Array<Record<string, ?>>>} - One merged row per distinct key-column combination.
64
+ */
65
+ async run() {
66
+ const resolvedTenants = await this._resolveTenants()
67
+
68
+ if (resolvedTenants.length === 0) return []
69
+
70
+ /** @type {Array<Record<string, ?>>} */
71
+ const rows = []
72
+
73
+ for (const group of this._groupByServer(resolvedTenants)) {
74
+ const groupRows = await this._runForServer(group)
75
+
76
+ rows.push(...groupRows)
77
+ }
78
+
79
+ return this._mergeRows(rows)
80
+ }
81
+
82
+ /**
83
+ * Resolves the tenant list (explicit or from the provider), then maps each to its server and
84
+ * database via the tenant database resolver.
85
+ * @returns {Promise<ResolvedTenant[]>} - Tenants to aggregate, each resolved to its server + database.
86
+ */
87
+ async _resolveTenants() {
88
+ const tenants = this.options.tenants ?? await this._listProviderTenants()
89
+ const filtered = this.options.filter ? tenants.filter(this.options.filter) : tenants
90
+
91
+ return filtered.map((tenant) => {
92
+ const databaseConfiguration = this.configuration.resolveDatabaseConfiguration(this.options.identifier, tenant)
93
+
94
+ // The server key groups tenants that share a connection endpoint so co-located tenants can be
95
+ // UNION-ed on one connection. It only needs to be exact for the cross-database `UNION ALL`
96
+ // path, which is MySQL-only, where host/port/username/type fully identify the server. Every
97
+ // other driver takes the fan-out path (one connection per tenant), so a coarse key there just
98
+ // groups tenants that are then queried individually anyway — it can never route a query to the
99
+ // wrong server.
100
+ return {
101
+ database: databaseConfiguration.database,
102
+ serverKey: JSON.stringify([
103
+ databaseConfiguration.type ?? null,
104
+ databaseConfiguration.host ?? null,
105
+ databaseConfiguration.port ?? null,
106
+ databaseConfiguration.username ?? null
107
+ ]),
108
+ tenant
109
+ }
110
+ })
111
+ }
112
+
113
+ /**
114
+ * Lists every tenant for the identifier through its provider's `listTenants` hook.
115
+ * @returns {Promise<object[]>} - Every tenant the provider lists for the identifier.
116
+ */
117
+ async _listProviderTenants() {
118
+ const provider = this.configuration.getTenantDatabaseProvider(this.options.identifier)
119
+ const listedTenants = await this.configuration.ensureConnections(
120
+ {name: `Tenant.aggregateAcross: ${this.options.identifier}`},
121
+ async () => await provider.listTenants({configuration: this.configuration, identifier: this.options.identifier})
122
+ )
123
+
124
+ if (!Array.isArray(listedTenants)) {
125
+ throw new Error(`Tenant database provider for ${this.options.identifier} must return an array from listTenants`)
126
+ }
127
+
128
+ return listedTenants
129
+ }
130
+
131
+ /**
132
+ * Groups tenants by the server they live on so co-located tenants can share one query.
133
+ * @param {ResolvedTenant[]} resolvedTenants - Tenants resolved to their server.
134
+ * @returns {ResolvedTenant[][]} - Tenants grouped by the server they live on.
135
+ */
136
+ _groupByServer(resolvedTenants) {
137
+ /** @type {Map<string, ResolvedTenant[]>} */
138
+ const groups = new Map()
139
+
140
+ for (const resolvedTenant of resolvedTenants) {
141
+ const group = groups.get(resolvedTenant.serverKey)
142
+
143
+ if (group) {
144
+ group.push(resolvedTenant)
145
+ } else {
146
+ groups.set(resolvedTenant.serverKey, [resolvedTenant])
147
+ }
148
+ }
149
+
150
+ return Array.from(groups.values())
151
+ }
152
+
153
+ /**
154
+ * Runs the aggregate for one server's tenants, using a single cross-database `UNION ALL` when the
155
+ * driver supports it or one query per tenant otherwise. The driver capability is probed in its own
156
+ * connection scope that is released before the fan-out runs, so a `max: 1` tenant pool is never
157
+ * asked for a second connection while the first is still held (which would deadlock).
158
+ * @param {ResolvedTenant[]} group - Tenants sharing one server.
159
+ * @returns {Promise<Array<Record<string, ?>>>} - Rows produced for this server.
160
+ */
161
+ async _runForServer(group) {
162
+ const [firstTenant] = group
163
+ const supportsCrossDatabaseReferences = await this._withTenant(
164
+ firstTenant.tenant,
165
+ async (connections) => connections[this.options.identifier].supportsCrossDatabaseReferences()
166
+ )
167
+
168
+ if (supportsCrossDatabaseReferences) {
169
+ return await this._withTenant(firstTenant.tenant, async (connections) => {
170
+ const connection = connections[this.options.identifier]
171
+
172
+ return await connection.query(this.buildAggregateSql({connection, entries: group, qualified: true}))
173
+ })
174
+ }
175
+
176
+ /** @type {Array<Record<string, ?>>} */
177
+ const rows = []
178
+
179
+ for (const resolvedTenant of group) {
180
+ const tenantRows = await this._withTenant(resolvedTenant.tenant, async (connections) => {
181
+ const connection = connections[this.options.identifier]
182
+
183
+ return await connection.query(this.buildAggregateSql({connection, entries: [resolvedTenant], qualified: false}))
184
+ })
185
+
186
+ rows.push(...tenantRows)
187
+ }
188
+
189
+ return rows
190
+ }
191
+
192
+ /**
193
+ * Runs `callback` inside a tenant's context with its connections established, mirroring
194
+ * `Tenant.with` without importing it (which would create an import cycle).
195
+ * @template T
196
+ * @param {object} tenant - Tenant descriptor to switch into.
197
+ * @param {(connections: Record<string, import("../database/drivers/base.js").default>) => Promise<T>} callback - Operation to run with the tenant's active connections.
198
+ * @returns {Promise<T>} - Callback result from within the tenant context.
199
+ */
200
+ async _withTenant(tenant, callback) {
201
+ return await this.configuration.runWithTenant(tenant, async () => await this.configuration.ensureConnections(callback))
202
+ }
203
+
204
+ /**
205
+ * Builds the aggregate SQL: an outer `GROUP BY` over a `UNION ALL` of each entry's subquery.
206
+ * @param {{connection: import("../database/drivers/base.js").default, entries: ResolvedTenant[], qualified: boolean}} args - Connection, tenants, and whether to qualify tables with their database name.
207
+ * @returns {string} - Executable aggregate SQL.
208
+ */
209
+ buildAggregateSql({connection, entries, qualified}) {
210
+ const options = connection.options()
211
+ const quote = this._buildQuote(connection)
212
+ const subqueries = entries.map((entry) => {
213
+ if (qualified && !entry.database) {
214
+ throw new Error(`Cannot build a cross-database query for a tenant without a resolved database name (identifier: ${this.options.identifier}).`)
215
+ }
216
+
217
+ const table = (/** @type {string} */ tableName) => qualified
218
+ ? `${options.quoteDatabaseName(/** @type {string} */ (entry.database))}.${options.quoteTableName(tableName)}`
219
+ : options.quoteTableName(tableName)
220
+
221
+ return this.options.subquery({connection, quote, table, tenant: entry.tenant})
222
+ })
223
+
224
+ const selectParts = this.keyColumns.map((keyColumn) => options.quoteColumnName(keyColumn))
225
+
226
+ for (const [name, spec] of Object.entries(this.aggregates)) {
227
+ const aggregateArgument = spec.column === "*" ? "*" : options.quoteColumnName(spec.column)
228
+
229
+ selectParts.push(`${spec.op}(${aggregateArgument}) AS ${options.quoteColumnName(name)}`)
230
+ }
231
+
232
+ const unionSql = subqueries.map((subquery) => `SELECT * FROM (${subquery}) ${options.quoteTableName(`${DERIVED_TABLE_ALIAS}_source`)}`).join("\nUNION ALL\n")
233
+ const groupBySql = this.keyColumns.length > 0
234
+ ? `\nGROUP BY ${this.keyColumns.map((keyColumn) => options.quoteColumnName(keyColumn)).join(", ")}`
235
+ : ""
236
+
237
+ return `SELECT ${selectParts.join(", ")}\nFROM (\n${unionSql}\n) ${options.quoteTableName(DERIVED_TABLE_ALIAS)}${groupBySql}`
238
+ }
239
+
240
+ /**
241
+ * Merges rows from every server by combining each aggregate with its own operation. A `NULL`
242
+ * aggregate value (an empty tenant's `SUM`/`MAX`/`MIN` returns `NULL` on the fan-out path) is
243
+ * treated as "no contribution" and skipped, not coerced to `0` — otherwise an empty tenant would
244
+ * drag a `MAX` of negatives or a `MIN` of positives to `0`. A key whose every tenant contributed
245
+ * `NULL` stays `NULL`, matching SQL aggregate semantics over no rows.
246
+ * @param {Array<Record<string, ?>>} rows - Rows collected from all servers/tenants.
247
+ * @returns {Array<Record<string, ?>>} - One merged row per distinct key-column combination.
248
+ */
249
+ _mergeRows(rows) {
250
+ /** @type {Map<string, Record<string, ?>>} */
251
+ const merged = new Map()
252
+
253
+ for (const row of rows) {
254
+ const mapKey = JSON.stringify(this.keyColumns.map((keyColumn) => row[keyColumn]))
255
+ let accumulator = merged.get(mapKey)
256
+
257
+ if (!accumulator) {
258
+ accumulator = {}
259
+
260
+ for (const keyColumn of this.keyColumns) {
261
+ accumulator[keyColumn] = row[keyColumn]
262
+ }
263
+
264
+ for (const name of Object.keys(this.aggregates)) {
265
+ accumulator[name] = null
266
+ }
267
+
268
+ merged.set(mapKey, accumulator)
269
+ }
270
+
271
+ for (const [name, spec] of Object.entries(this.aggregates)) {
272
+ const rawValue = row[name]
273
+
274
+ if (rawValue === null || rawValue === undefined) continue
275
+
276
+ const value = this._toExactNumber(rawValue)
277
+
278
+ accumulator[name] = accumulator[name] === null ? value : this._combine(spec.op, accumulator[name], value)
279
+ }
280
+ }
281
+
282
+ return Array.from(merged.values())
283
+ }
284
+
285
+ /**
286
+ * Combines two non-null per-server aggregate values with the aggregate's own operation.
287
+ * @param {AggregateOperation} op - Aggregate operation.
288
+ * @param {number} current - Accumulated value.
289
+ * @param {number} value - Incoming per-server value.
290
+ * @returns {number} - Combined value.
291
+ */
292
+ _combine(op, current, value) {
293
+ if (op === "MAX") return Math.max(current, value)
294
+ if (op === "MIN") return Math.min(current, value)
295
+
296
+ return current + value
297
+ }
298
+
299
+ /**
300
+ * Converts a driver-returned aggregate value to a number, failing loudly rather than silently
301
+ * losing precision. Drivers return exact integer aggregates (MySQL `SUM`/`COUNT`, PostgreSQL
302
+ * `bigint`) as strings; an integer beyond `Number.MAX_SAFE_INTEGER` cannot be represented exactly
303
+ * as a JS number, so cross-server merging would corrupt the result — throw instead. (Fractional
304
+ * `DECIMAL`/`NUMERIC` values are still subject to normal floating-point representation.)
305
+ * @param {?} rawValue - Value returned by the driver for an aggregate column.
306
+ * @returns {number} - The value as a number.
307
+ */
308
+ _toExactNumber(rawValue) {
309
+ const value = Number(rawValue)
310
+
311
+ if (typeof rawValue === "string" && /^-?\d+$/.test(rawValue.trim()) && !Number.isSafeInteger(value)) {
312
+ throw new Error(`Aggregate value ${rawValue} exceeds the safe-integer range and cannot be merged without losing precision.`)
313
+ }
314
+
315
+ return value
316
+ }
317
+
318
+ /**
319
+ * Builds the value quoter passed to subqueries, with a `.list` helper for `IN (...)` clauses.
320
+ * @param {import("../database/drivers/base.js").default} connection - Driver whose quoting is used.
321
+ * @returns {((value: ?) => string) & {list: (values: Array<?>) => string}} - Value quoter with a `.list` helper.
322
+ */
323
+ _buildQuote(connection) {
324
+ return Object.assign(
325
+ (/** @type {?} */ value) => String(connection.quote(value)),
326
+ {list: (/** @type {Array<?>} */ values) => values.map((value) => String(connection.quote(value))).join(", ")}
327
+ )
328
+ }
329
+
330
+ /**
331
+ * Normalizes the aggregate specs (string shorthand or object) and validates each operation.
332
+ * @param {Record<string, AggregateSpec>} aggregates - Raw aggregate specs.
333
+ * @returns {Record<string, {op: AggregateOperation, column: string}>} - Normalized aggregate specs.
334
+ */
335
+ _normalizeAggregates(aggregates) {
336
+ /** @type {Record<string, {op: AggregateOperation, column: string}>} */
337
+ const normalized = {}
338
+
339
+ for (const [name, spec] of Object.entries(aggregates)) {
340
+ const op = /** @type {AggregateOperation} */ ((typeof spec === "string" ? spec : spec.op).toUpperCase())
341
+
342
+ if (!AGGREGATE_OPERATIONS.has(op)) {
343
+ throw new Error(`Unsupported aggregate operation for ${name}: ${op}. Supported: ${Array.from(AGGREGATE_OPERATIONS).join(", ")}.`)
344
+ }
345
+
346
+ normalized[name] = {column: typeof spec === "string" ? name : spec.column, op}
347
+ }
348
+
349
+ return normalized
350
+ }
351
+ }
@@ -2,6 +2,7 @@
2
2
 
3
3
  import Current from "../current.js"
4
4
  import {dropTenantDatabase} from "./default-tenant-database-provisioning.js"
5
+ import TenantAggregator from "./tenant-aggregator.js"
5
6
  import TenantIterator from "./tenant-iterator.js"
6
7
 
7
8
  /**
@@ -74,6 +75,22 @@ export default class Tenant {
74
75
  })
75
76
  }
76
77
 
78
+ /**
79
+ * Runs one aggregate query across many tenant databases and returns the merged result. The
80
+ * tenants may be co-located on the default server or spread across other servers, and can be
81
+ * created or dropped at runtime; the live tenant list is resolved (from `tenants` or the
82
+ * provider's `listTenants`), grouped by server, aggregated with a single cross-database
83
+ * `UNION ALL` where the driver supports it (MySQL/MSSQL) or one query per tenant otherwise
84
+ * (PostgreSQL/SQLite), and merged with each aggregate's own operation. The caller writes only one
85
+ * per-tenant subquery and declares the key columns and aggregates; see
86
+ * {@link import("./tenant-aggregator.js").TenantAggregateOptions}.
87
+ * @param {import("./tenant-aggregator.js").TenantAggregateOptions} options - Aggregate configuration.
88
+ * @returns {Promise<Array<Record<string, ?>>>} - One merged row per distinct key-column combination.
89
+ */
90
+ static async aggregateAcross(options) {
91
+ return await new TenantAggregator(options).run()
92
+ }
93
+
77
94
  /**
78
95
  * Drops one tenant's database/schema through the provider's `dropDatabase` hook.
79
96
  * @param {{identifier: string, tenant: object, configuration?: import("../configuration.js").default}} args - Tenant descriptor and database identifier to drop.
@@ -1 +1 @@
1
- {"root":["../index.js","../bin/velocious.js","../src/application.js","../src/configuration-resolver.js","../src/configuration-types.js","../src/configuration.js","../src/controller.js","../src/current-configuration.js","../src/current.js","../src/error-logger.js","../src/frontend-model-controller.js","../src/initializer.js","../src/logger.js","../src/mailer.js","../src/record-payload-values.js","../src/time-zone.js","../src/velocious-error.js","../src/authorization/ability.js","../src/authorization/base-resource.js","../src/background-jobs/client.js","../src/background-jobs/cron-expression.js","../src/background-jobs/forked-runner-child.js","../src/background-jobs/job-record.js","../src/background-jobs/job-registry.js","../src/background-jobs/job-runner.js","../src/background-jobs/job.js","../src/background-jobs/json-socket.js","../src/background-jobs/main.js","../src/background-jobs/normalize-error.js","../src/background-jobs/scheduler.js","../src/background-jobs/socket-request.js","../src/background-jobs/status-reporter.js","../src/background-jobs/store.js","../src/background-jobs/types.js","../src/background-jobs/worker.js","../src/background-jobs/web/authorization.js","../src/background-jobs/web/controller.js","../src/background-jobs/web/index.js","../src/background-jobs/web/path-matcher.js","../src/background-jobs/web/registry.js","../src/beacon/client.js","../src/beacon/in-process-broker.js","../src/beacon/in-process-client.js","../src/beacon/server.js","../src/beacon/types.js","../src/cli/base-command.js","../src/cli/browser-cli.js","../src/cli/index.js","../src/cli/tenant-database-command-helper.js","../src/cli/use-browser-cli.js","../src/cli/commands/background-jobs-main.js","../src/cli/commands/background-jobs-runner.js","../src/cli/commands/background-jobs-worker.js","../src/cli/commands/beacon.js","../src/cli/commands/console.js","../src/cli/commands/init.js","../src/cli/commands/routes.js","../src/cli/commands/run-script.js","../src/cli/commands/runner.js","../src/cli/commands/server.js","../src/cli/commands/test.js","../src/cli/commands/db/base-command.js","../src/cli/commands/db/create.js","../src/cli/commands/db/drop.js","../src/cli/commands/db/migrate.js","../src/cli/commands/db/reset.js","../src/cli/commands/db/rollback.js","../src/cli/commands/db/seed.js","../src/cli/commands/db/schema/dump.js","../src/cli/commands/db/schema/load.js","../src/cli/commands/db/tenants/check.js","../src/cli/commands/db/tenants/create.js","../src/cli/commands/db/tenants/drop.js","../src/cli/commands/db/tenants/migrate.js","../src/cli/commands/destroy/migration.js","../src/cli/commands/generate/base-models.js","../src/cli/commands/generate/frontend-models.js","../src/cli/commands/generate/migration.js","../src/cli/commands/generate/model.js","../src/cli/commands/lint/relationships.js","../src/database/advisory-lock-runner.js","../src/database/annotations-async-hooks.js","../src/database/annotations.js","../src/database/column-types.js","../src/database/datetime-storage.js","../src/database/handler.js","../src/database/initializer-from-require-context.js","../src/database/live-query.js","../src/database/migrations-ledger.js","../src/database/migrator.js","../src/database/record-changes.js","../src/database/use-database.js","../src/database/use-live-query.js","../src/database/drivers/base-column.js","../src/database/drivers/base-columns-index.js","../src/database/drivers/base-foreign-key.js","../src/database/drivers/base-table.js","../src/database/drivers/base.js","../src/database/drivers/mssql/column.js","../src/database/drivers/mssql/columns-index.js","../src/database/drivers/mssql/connect-connection.js","../src/database/drivers/mssql/foreign-key.js","../src/database/drivers/mssql/index.js","../src/database/drivers/mssql/options.js","../src/database/drivers/mssql/query-parser.js","../src/database/drivers/mssql/structure-sql.js","../src/database/drivers/mssql/table.js","../src/database/drivers/mssql/sql/alter-table.js","../src/database/drivers/mssql/sql/create-database.js","../src/database/drivers/mssql/sql/create-index.js","../src/database/drivers/mssql/sql/create-table.js","../src/database/drivers/mssql/sql/delete.js","../src/database/drivers/mssql/sql/drop-database.js","../src/database/drivers/mssql/sql/drop-table.js","../src/database/drivers/mssql/sql/insert.js","../src/database/drivers/mssql/sql/remove-index.js","../src/database/drivers/mssql/sql/update.js","../src/database/drivers/mssql/sql/upsert.js","../src/database/drivers/mysql/column.js","../src/database/drivers/mysql/columns-index.js","../src/database/drivers/mysql/foreign-key.js","../src/database/drivers/mysql/index.js","../src/database/drivers/mysql/options.js","../src/database/drivers/mysql/query-parser.js","../src/database/drivers/mysql/query.js","../src/database/drivers/mysql/structure-sql.js","../src/database/drivers/mysql/table.js","../src/database/drivers/mysql/sql/alter-table.js","../src/database/drivers/mysql/sql/create-database.js","../src/database/drivers/mysql/sql/create-index.js","../src/database/drivers/mysql/sql/create-table.js","../src/database/drivers/mysql/sql/delete.js","../src/database/drivers/mysql/sql/drop-database.js","../src/database/drivers/mysql/sql/drop-table.js","../src/database/drivers/mysql/sql/insert.js","../src/database/drivers/mysql/sql/remove-index.js","../src/database/drivers/mysql/sql/update.js","../src/database/drivers/mysql/sql/upsert.js","../src/database/drivers/pgsql/column.js","../src/database/drivers/pgsql/columns-index.js","../src/database/drivers/pgsql/foreign-key.js","../src/database/drivers/pgsql/index.js","../src/database/drivers/pgsql/options.js","../src/database/drivers/pgsql/query-parser.js","../src/database/drivers/pgsql/structure-sql.js","../src/database/drivers/pgsql/table.js","../src/database/drivers/pgsql/sql/alter-table.js","../src/database/drivers/pgsql/sql/create-database.js","../src/database/drivers/pgsql/sql/create-index.js","../src/database/drivers/pgsql/sql/create-table.js","../src/database/drivers/pgsql/sql/delete.js","../src/database/drivers/pgsql/sql/drop-database.js","../src/database/drivers/pgsql/sql/drop-table.js","../src/database/drivers/pgsql/sql/insert.js","../src/database/drivers/pgsql/sql/remove-index.js","../src/database/drivers/pgsql/sql/update.js","../src/database/drivers/pgsql/sql/upsert.js","../src/database/drivers/sqlite/base.js","../src/database/drivers/sqlite/column.js","../src/database/drivers/sqlite/columns-index.js","../src/database/drivers/sqlite/connection-sql-js.js","../src/database/drivers/sqlite/foreign-key.js","../src/database/drivers/sqlite/index.js","../src/database/drivers/sqlite/index.native.js","../src/database/drivers/sqlite/index.web.js","../src/database/drivers/sqlite/options.js","../src/database/drivers/sqlite/query-parser.js","../src/database/drivers/sqlite/query.js","../src/database/drivers/sqlite/query.native.js","../src/database/drivers/sqlite/query.web.js","../src/database/drivers/sqlite/structure-sql.js","../src/database/drivers/sqlite/table-rebuilder.js","../src/database/drivers/sqlite/table.js","../src/database/drivers/sqlite/web-persistence.js","../src/database/drivers/sqlite/sql/alter-table.js","../src/database/drivers/sqlite/sql/create-index.js","../src/database/drivers/sqlite/sql/create-table.js","../src/database/drivers/sqlite/sql/delete.js","../src/database/drivers/sqlite/sql/drop-table.js","../src/database/drivers/sqlite/sql/insert.js","../src/database/drivers/sqlite/sql/remove-index.js","../src/database/drivers/sqlite/sql/update.js","../src/database/drivers/sqlite/sql/upsert.js","../src/database/drivers/structure-sql/utils.js","../src/database/migration/index.js","../src/database/migrator/files-finder.js","../src/database/migrator/types.js","../src/database/pool/async-tracked-multi-connection.js","../src/database/pool/base-methods-forward.js","../src/database/pool/base.js","../src/database/pool/single-multi-use.js","../src/database/query/alter-table-base.js","../src/database/query/base.js","../src/database/query/create-database-base.js","../src/database/query/create-index-base.js","../src/database/query/create-table-base.js","../src/database/query/delete-base.js","../src/database/query/drop-database-base.js","../src/database/query/drop-table-base.js","../src/database/query/from-base.js","../src/database/query/from-plain.js","../src/database/query/from-table.js","../src/database/query/index.js","../src/database/query/insert-base.js","../src/database/query/join-base.js","../src/database/query/join-object.js","../src/database/query/join-plain.js","../src/database/query/join-tracker.js","../src/database/query/model-class-query.js","../src/database/query/order-base.js","../src/database/query/order-column.js","../src/database/query/order-plain.js","../src/database/query/preloader.js","../src/database/query/query-data.js","../src/database/query/remove-index-base.js","../src/database/query/select-base.js","../src/database/query/select-plain.js","../src/database/query/select-table-and-column.js","../src/database/query/update-base.js","../src/database/query/upsert-base.js","../src/database/query/where-base.js","../src/database/query/where-combinator.js","../src/database/query/where-hash.js","../src/database/query/where-model-class-hash.js","../src/database/query/where-not.js","../src/database/query/where-plain.js","../src/database/query/with-count.js","../src/database/query/preloader/belongs-to.js","../src/database/query/preloader/ensure-model-class-initialized.js","../src/database/query/preloader/has-many.js","../src/database/query/preloader/has-one.js","../src/database/query/preloader/selection.js","../src/database/query-parser/base-query-parser.js","../src/database/query-parser/from-parser.js","../src/database/query-parser/group-parser.js","../src/database/query-parser/joins-parser.js","../src/database/query-parser/limit-parser.js","../src/database/query-parser/options.js","../src/database/query-parser/order-parser.js","../src/database/query-parser/select-parser.js","../src/database/query-parser/where-parser.js","../src/database/record/acts-as-list.js","../src/database/record/auditing.js","../src/database/record/counter-cache-magnitude.js","../src/database/record/index.js","../src/database/record/record-not-found-error.js","../src/database/record/state-machine.js","../src/database/record/user-module.js","../src/database/record/validation-messages.js","../src/database/record/attachments/attachment-record.js","../src/database/record/attachments/download.js","../src/database/record/attachments/handle.js","../src/database/record/attachments/normalize-input.js","../src/database/record/attachments/store.js","../src/database/record/attachments/storage-drivers/filesystem.js","../src/database/record/attachments/storage-drivers/native.js","../src/database/record/attachments/storage-drivers/s3.js","../src/database/record/instance-relationships/base.js","../src/database/record/instance-relationships/belongs-to.js","../src/database/record/instance-relationships/has-many.js","../src/database/record/instance-relationships/has-one.js","../src/database/record/relationships/base.js","../src/database/record/relationships/belongs-to.js","../src/database/record/relationships/has-many.js","../src/database/record/relationships/has-one.js","../src/database/record/validators/base.js","../src/database/record/validators/format.js","../src/database/record/validators/length.js","../src/database/record/validators/presence.js","../src/database/record/validators/uniqueness.js","../src/database/table-data/index.js","../src/database/table-data/table-column.js","../src/database/table-data/table-foreign-key.js","../src/database/table-data/table-index.js","../src/database/table-data/table-reference.js","../src/database/tenants/data-copier.js","../src/database/tenants/schema-cloner.js","../src/database/tenants/tenant-table-plan.js","../src/environment-handlers/base.js","../src/environment-handlers/browser.js","../src/environment-handlers/node.js","../src/environment-handlers/node/cli/commands/background-jobs-main.js","../src/environment-handlers/node/cli/commands/background-jobs-runner.js","../src/environment-handlers/node/cli/commands/background-jobs-worker.js","../src/environment-handlers/node/cli/commands/beacon.js","../src/environment-handlers/node/cli/commands/cli-command-context.js","../src/environment-handlers/node/cli/commands/console.js","../src/environment-handlers/node/cli/commands/init.js","../src/environment-handlers/node/cli/commands/routes.js","../src/environment-handlers/node/cli/commands/run-script.js","../src/environment-handlers/node/cli/commands/runner.js","../src/environment-handlers/node/cli/commands/server.js","../src/environment-handlers/node/cli/commands/test.js","../src/environment-handlers/node/cli/commands/db/seed.js","../src/environment-handlers/node/cli/commands/db/schema/dump.js","../src/environment-handlers/node/cli/commands/db/schema/load.js","../src/environment-handlers/node/cli/commands/destroy/migration.js","../src/environment-handlers/node/cli/commands/generate/base-models.js","../src/environment-handlers/node/cli/commands/generate/frontend-models.js","../src/environment-handlers/node/cli/commands/generate/generated-file-banner.js","../src/environment-handlers/node/cli/commands/generate/migration.js","../src/environment-handlers/node/cli/commands/generate/model.js","../src/environment-handlers/node/cli/commands/lint/relationships.js","../src/error-reporting/request-details.js","../src/frontend-model-resource/base-resource.js","../src/frontend-model-resource/velocious-attachment-resource.js","../src/frontend-models/base.js","../src/frontend-models/built-in-resources.js","../src/frontend-models/clear-pending-debounced-callback.js","../src/frontend-models/event-hook-models.js","../src/frontend-models/model-registry.js","../src/frontend-models/outgoing-event-buffer.js","../src/frontend-models/preloader.js","../src/frontend-models/query.js","../src/frontend-models/resource-config-validation.js","../src/frontend-models/resource-definition.js","../src/frontend-models/transport-serialization.js","../src/frontend-models/use-created-event.js","../src/frontend-models/use-destroyed-event.js","../src/frontend-models/use-model-class-event.js","../src/frontend-models/use-updated-event.js","../src/frontend-models/websocket-channel.js","../src/frontend-models/websocket-publishers.js","../src/http-client/header.js","../src/http-client/index.js","../src/http-client/request.js","../src/http-client/response.js","../src/http-client/websocket-client.js","../src/http-server/cookie.js","../src/http-server/development-reloader.js","../src/http-server/index.js","../src/http-server/remote-address.js","../src/http-server/server-client.js","../src/http-server/server-lock.js","../src/http-server/websocket-channel-subscribers.js","../src/http-server/websocket-channel.js","../src/http-server/websocket-connection.js","../src/http-server/websocket-event-log-store.js","../src/http-server/websocket-events-host.js","../src/http-server/websocket-events.js","../src/http-server/client/index.js","../src/http-server/client/params-to-object.js","../src/http-server/client/request-parser.js","../src/http-server/client/request-runner.js","../src/http-server/client/request-timing.js","../src/http-server/client/request.js","../src/http-server/client/response.js","../src/http-server/client/websocket-request.js","../src/http-server/client/websocket-session.js","../src/http-server/client/request-buffer/form-data-part.js","../src/http-server/client/request-buffer/header.js","../src/http-server/client/request-buffer/index.js","../src/http-server/client/uploaded-file/memory-uploaded-file.js","../src/http-server/client/uploaded-file/temporary-uploaded-file.js","../src/http-server/client/uploaded-file/uploaded-file.js","../src/http-server/worker-handler/channel-subscriber-dispatch.js","../src/http-server/worker-handler/in-process.js","../src/http-server/worker-handler/index.js","../src/http-server/worker-handler/worker-script.js","../src/http-server/worker-handler/worker-thread.js","../src/jobs/mail-delivery.js","../src/jobs/prune-terminal-background-jobs.js","../src/logger/base-logger.js","../src/logger/console-logger.js","../src/logger/file-logger.js","../src/logger/outputs/array-output.js","../src/logger/outputs/console-output.js","../src/logger/outputs/file-output.js","../src/logger/outputs/stdout-output.js","../src/mailer/base.js","../src/mailer/delivery.js","../src/mailer/index.js","../src/mailer/backends/smtp.js","../src/packages/velocious-package.js","../src/plugins/sqljs-wasm-route-controller.js","../src/plugins/sqljs-wasm-route.js","../src/routes/app-routes.js","../src/routes/base-route.js","../src/routes/basic-route.js","../src/routes/get-route.js","../src/routes/index.js","../src/routes/namespace-route.js","../src/routes/plugin-routes.js","../src/routes/post-route.js","../src/routes/resolver.js","../src/routes/resource-route.js","../src/routes/root-route.js","../src/routes/built-in/api-manifest/controller.js","../src/routes/built-in/debug/controller.js","../src/routes/built-in/errors/controller.js","../src/routes/hooks/frontend-model-command-route-hook.js","../src/sync/conflict-strategy.js","../src/sync/device-identity.js","../src/sync/local-mutation-log.js","../src/sync/offline-grant.js","../src/sync/peer-mutation-bundle.js","../src/sync/query-scope.js","../src/sync/server-change-feed.js","../src/sync/server-sequence-allocator.js","../src/sync/stable-json.js","../src/sync/sync-api-client-types.js","../src/sync/sync-api-client.js","../src/sync/sync-api-controller.js","../src/sync/sync-change-fanout.js","../src/sync/sync-channel-name.js","../src/sync/sync-client-registry.js","../src/sync/sync-client-types.js","../src/sync/sync-client.js","../src/sync/sync-envelope-replay-service.js","../src/sync/sync-model-change-feed-service.js","../src/sync/sync-publish-suppression.js","../src/sync/sync-publisher-types.js","../src/sync/sync-publisher.js","../src/sync/sync-realtime-bridge.js","../src/sync/sync-replay-upsert-applier.js","../src/sync/sync-resource-base.js","../src/sync/sync-scope-attributes.js","../src/sync/sync-scope-store.js","../src/sync/sync-websocket-channel.js","../src/tenants/default-tenant-database-provisioning.js","../src/tenants/tenant-iterator.js","../src/tenants/tenant.js","../src/testing/base-expect.js","../src/testing/browser-frontend-model-event-hook-scenarios.js","../src/testing/browser-test-app.js","../src/testing/expect-to-change.js","../src/testing/expect-utils.js","../src/testing/expect.js","../src/testing/request-client.js","../src/testing/test-files-finder.js","../src/testing/test-filter-parser.js","../src/testing/test-runner.js","../src/testing/test-suite-splitter.js","../src/testing/test.js","../src/types/external-modules.d.ts","../src/utils/backtrace-cleaner-node.js","../src/utils/backtrace-cleaner.js","../src/utils/deburr-column-name.js","../src/utils/event-emitter.js","../src/utils/file-exists.js","../src/utils/format-value.js","../src/utils/is-date.js","../src/utils/model-scope.js","../src/utils/nest-callbacks.js","../src/utils/plain-object.js","../src/utils/ransack.js","../src/utils/rest-args-error.js","../src/utils/singularize-model-name.js","../src/utils/split-sql-statements.js","../src/utils/to-import-specifier.js","../src/utils/with-tracked-stack-async-hooks.js","../src/utils/with-tracked-stack.js"],"version":"6.0.3"}
1
+ {"root":["../index.js","../bin/velocious.js","../src/application.js","../src/configuration-resolver.js","../src/configuration-types.js","../src/configuration.js","../src/controller.js","../src/current-configuration.js","../src/current.js","../src/error-logger.js","../src/frontend-model-controller.js","../src/initializer.js","../src/logger.js","../src/mailer.js","../src/record-payload-values.js","../src/time-zone.js","../src/velocious-error.js","../src/authorization/ability.js","../src/authorization/base-resource.js","../src/background-jobs/client.js","../src/background-jobs/cron-expression.js","../src/background-jobs/forked-runner-child.js","../src/background-jobs/job-record.js","../src/background-jobs/job-registry.js","../src/background-jobs/job-runner.js","../src/background-jobs/job.js","../src/background-jobs/json-socket.js","../src/background-jobs/main.js","../src/background-jobs/normalize-error.js","../src/background-jobs/scheduler.js","../src/background-jobs/socket-request.js","../src/background-jobs/status-reporter.js","../src/background-jobs/store.js","../src/background-jobs/types.js","../src/background-jobs/worker.js","../src/background-jobs/web/authorization.js","../src/background-jobs/web/controller.js","../src/background-jobs/web/index.js","../src/background-jobs/web/path-matcher.js","../src/background-jobs/web/registry.js","../src/beacon/client.js","../src/beacon/in-process-broker.js","../src/beacon/in-process-client.js","../src/beacon/server.js","../src/beacon/types.js","../src/cli/base-command.js","../src/cli/browser-cli.js","../src/cli/index.js","../src/cli/tenant-database-command-helper.js","../src/cli/use-browser-cli.js","../src/cli/commands/background-jobs-main.js","../src/cli/commands/background-jobs-runner.js","../src/cli/commands/background-jobs-worker.js","../src/cli/commands/beacon.js","../src/cli/commands/console.js","../src/cli/commands/init.js","../src/cli/commands/routes.js","../src/cli/commands/run-script.js","../src/cli/commands/runner.js","../src/cli/commands/server.js","../src/cli/commands/test.js","../src/cli/commands/db/base-command.js","../src/cli/commands/db/create.js","../src/cli/commands/db/drop.js","../src/cli/commands/db/migrate.js","../src/cli/commands/db/reset.js","../src/cli/commands/db/rollback.js","../src/cli/commands/db/seed.js","../src/cli/commands/db/schema/dump.js","../src/cli/commands/db/schema/load.js","../src/cli/commands/db/tenants/check.js","../src/cli/commands/db/tenants/create.js","../src/cli/commands/db/tenants/drop.js","../src/cli/commands/db/tenants/migrate.js","../src/cli/commands/destroy/migration.js","../src/cli/commands/generate/base-models.js","../src/cli/commands/generate/frontend-models.js","../src/cli/commands/generate/migration.js","../src/cli/commands/generate/model.js","../src/cli/commands/lint/relationships.js","../src/database/advisory-lock-runner.js","../src/database/annotations-async-hooks.js","../src/database/annotations.js","../src/database/column-types.js","../src/database/datetime-storage.js","../src/database/handler.js","../src/database/initializer-from-require-context.js","../src/database/live-query.js","../src/database/migrations-ledger.js","../src/database/migrator.js","../src/database/record-changes.js","../src/database/use-database.js","../src/database/use-live-query.js","../src/database/drivers/base-column.js","../src/database/drivers/base-columns-index.js","../src/database/drivers/base-foreign-key.js","../src/database/drivers/base-table.js","../src/database/drivers/base.js","../src/database/drivers/mssql/column.js","../src/database/drivers/mssql/columns-index.js","../src/database/drivers/mssql/connect-connection.js","../src/database/drivers/mssql/foreign-key.js","../src/database/drivers/mssql/index.js","../src/database/drivers/mssql/options.js","../src/database/drivers/mssql/query-parser.js","../src/database/drivers/mssql/structure-sql.js","../src/database/drivers/mssql/table.js","../src/database/drivers/mssql/sql/alter-table.js","../src/database/drivers/mssql/sql/create-database.js","../src/database/drivers/mssql/sql/create-index.js","../src/database/drivers/mssql/sql/create-table.js","../src/database/drivers/mssql/sql/delete.js","../src/database/drivers/mssql/sql/drop-database.js","../src/database/drivers/mssql/sql/drop-table.js","../src/database/drivers/mssql/sql/insert.js","../src/database/drivers/mssql/sql/remove-index.js","../src/database/drivers/mssql/sql/update.js","../src/database/drivers/mssql/sql/upsert.js","../src/database/drivers/mysql/column.js","../src/database/drivers/mysql/columns-index.js","../src/database/drivers/mysql/foreign-key.js","../src/database/drivers/mysql/index.js","../src/database/drivers/mysql/options.js","../src/database/drivers/mysql/query-parser.js","../src/database/drivers/mysql/query.js","../src/database/drivers/mysql/structure-sql.js","../src/database/drivers/mysql/table.js","../src/database/drivers/mysql/sql/alter-table.js","../src/database/drivers/mysql/sql/create-database.js","../src/database/drivers/mysql/sql/create-index.js","../src/database/drivers/mysql/sql/create-table.js","../src/database/drivers/mysql/sql/delete.js","../src/database/drivers/mysql/sql/drop-database.js","../src/database/drivers/mysql/sql/drop-table.js","../src/database/drivers/mysql/sql/insert.js","../src/database/drivers/mysql/sql/remove-index.js","../src/database/drivers/mysql/sql/update.js","../src/database/drivers/mysql/sql/upsert.js","../src/database/drivers/pgsql/column.js","../src/database/drivers/pgsql/columns-index.js","../src/database/drivers/pgsql/foreign-key.js","../src/database/drivers/pgsql/index.js","../src/database/drivers/pgsql/options.js","../src/database/drivers/pgsql/query-parser.js","../src/database/drivers/pgsql/structure-sql.js","../src/database/drivers/pgsql/table.js","../src/database/drivers/pgsql/sql/alter-table.js","../src/database/drivers/pgsql/sql/create-database.js","../src/database/drivers/pgsql/sql/create-index.js","../src/database/drivers/pgsql/sql/create-table.js","../src/database/drivers/pgsql/sql/delete.js","../src/database/drivers/pgsql/sql/drop-database.js","../src/database/drivers/pgsql/sql/drop-table.js","../src/database/drivers/pgsql/sql/insert.js","../src/database/drivers/pgsql/sql/remove-index.js","../src/database/drivers/pgsql/sql/update.js","../src/database/drivers/pgsql/sql/upsert.js","../src/database/drivers/sqlite/base.js","../src/database/drivers/sqlite/column.js","../src/database/drivers/sqlite/columns-index.js","../src/database/drivers/sqlite/connection-sql-js.js","../src/database/drivers/sqlite/foreign-key.js","../src/database/drivers/sqlite/index.js","../src/database/drivers/sqlite/index.native.js","../src/database/drivers/sqlite/index.web.js","../src/database/drivers/sqlite/options.js","../src/database/drivers/sqlite/query-parser.js","../src/database/drivers/sqlite/query.js","../src/database/drivers/sqlite/query.native.js","../src/database/drivers/sqlite/query.web.js","../src/database/drivers/sqlite/structure-sql.js","../src/database/drivers/sqlite/table-rebuilder.js","../src/database/drivers/sqlite/table.js","../src/database/drivers/sqlite/web-persistence.js","../src/database/drivers/sqlite/sql/alter-table.js","../src/database/drivers/sqlite/sql/create-index.js","../src/database/drivers/sqlite/sql/create-table.js","../src/database/drivers/sqlite/sql/delete.js","../src/database/drivers/sqlite/sql/drop-table.js","../src/database/drivers/sqlite/sql/insert.js","../src/database/drivers/sqlite/sql/remove-index.js","../src/database/drivers/sqlite/sql/update.js","../src/database/drivers/sqlite/sql/upsert.js","../src/database/drivers/structure-sql/utils.js","../src/database/migration/index.js","../src/database/migrator/files-finder.js","../src/database/migrator/types.js","../src/database/pool/async-tracked-multi-connection.js","../src/database/pool/base-methods-forward.js","../src/database/pool/base.js","../src/database/pool/single-multi-use.js","../src/database/query/alter-table-base.js","../src/database/query/base.js","../src/database/query/create-database-base.js","../src/database/query/create-index-base.js","../src/database/query/create-table-base.js","../src/database/query/delete-base.js","../src/database/query/drop-database-base.js","../src/database/query/drop-table-base.js","../src/database/query/from-base.js","../src/database/query/from-plain.js","../src/database/query/from-table.js","../src/database/query/index.js","../src/database/query/insert-base.js","../src/database/query/join-base.js","../src/database/query/join-object.js","../src/database/query/join-plain.js","../src/database/query/join-tracker.js","../src/database/query/model-class-query.js","../src/database/query/order-base.js","../src/database/query/order-column.js","../src/database/query/order-plain.js","../src/database/query/preloader.js","../src/database/query/query-data.js","../src/database/query/remove-index-base.js","../src/database/query/select-base.js","../src/database/query/select-plain.js","../src/database/query/select-table-and-column.js","../src/database/query/update-base.js","../src/database/query/upsert-base.js","../src/database/query/where-base.js","../src/database/query/where-combinator.js","../src/database/query/where-hash.js","../src/database/query/where-model-class-hash.js","../src/database/query/where-not.js","../src/database/query/where-plain.js","../src/database/query/with-count.js","../src/database/query/preloader/belongs-to.js","../src/database/query/preloader/ensure-model-class-initialized.js","../src/database/query/preloader/has-many.js","../src/database/query/preloader/has-one.js","../src/database/query/preloader/selection.js","../src/database/query-parser/base-query-parser.js","../src/database/query-parser/from-parser.js","../src/database/query-parser/group-parser.js","../src/database/query-parser/joins-parser.js","../src/database/query-parser/limit-parser.js","../src/database/query-parser/options.js","../src/database/query-parser/order-parser.js","../src/database/query-parser/select-parser.js","../src/database/query-parser/where-parser.js","../src/database/record/acts-as-list.js","../src/database/record/auditing.js","../src/database/record/counter-cache-magnitude.js","../src/database/record/index.js","../src/database/record/record-not-found-error.js","../src/database/record/state-machine.js","../src/database/record/user-module.js","../src/database/record/validation-messages.js","../src/database/record/attachments/attachment-record.js","../src/database/record/attachments/download.js","../src/database/record/attachments/handle.js","../src/database/record/attachments/normalize-input.js","../src/database/record/attachments/store.js","../src/database/record/attachments/storage-drivers/filesystem.js","../src/database/record/attachments/storage-drivers/native.js","../src/database/record/attachments/storage-drivers/s3.js","../src/database/record/instance-relationships/base.js","../src/database/record/instance-relationships/belongs-to.js","../src/database/record/instance-relationships/has-many.js","../src/database/record/instance-relationships/has-one.js","../src/database/record/relationships/base.js","../src/database/record/relationships/belongs-to.js","../src/database/record/relationships/has-many.js","../src/database/record/relationships/has-one.js","../src/database/record/validators/base.js","../src/database/record/validators/format.js","../src/database/record/validators/length.js","../src/database/record/validators/presence.js","../src/database/record/validators/uniqueness.js","../src/database/table-data/index.js","../src/database/table-data/table-column.js","../src/database/table-data/table-foreign-key.js","../src/database/table-data/table-index.js","../src/database/table-data/table-reference.js","../src/database/tenants/data-copier.js","../src/database/tenants/schema-cloner.js","../src/database/tenants/tenant-table-plan.js","../src/environment-handlers/base.js","../src/environment-handlers/browser.js","../src/environment-handlers/node.js","../src/environment-handlers/node/cli/commands/background-jobs-main.js","../src/environment-handlers/node/cli/commands/background-jobs-runner.js","../src/environment-handlers/node/cli/commands/background-jobs-worker.js","../src/environment-handlers/node/cli/commands/beacon.js","../src/environment-handlers/node/cli/commands/cli-command-context.js","../src/environment-handlers/node/cli/commands/console.js","../src/environment-handlers/node/cli/commands/init.js","../src/environment-handlers/node/cli/commands/routes.js","../src/environment-handlers/node/cli/commands/run-script.js","../src/environment-handlers/node/cli/commands/runner.js","../src/environment-handlers/node/cli/commands/server.js","../src/environment-handlers/node/cli/commands/test.js","../src/environment-handlers/node/cli/commands/db/seed.js","../src/environment-handlers/node/cli/commands/db/schema/dump.js","../src/environment-handlers/node/cli/commands/db/schema/load.js","../src/environment-handlers/node/cli/commands/destroy/migration.js","../src/environment-handlers/node/cli/commands/generate/base-models.js","../src/environment-handlers/node/cli/commands/generate/frontend-models.js","../src/environment-handlers/node/cli/commands/generate/generated-file-banner.js","../src/environment-handlers/node/cli/commands/generate/migration.js","../src/environment-handlers/node/cli/commands/generate/model.js","../src/environment-handlers/node/cli/commands/lint/relationships.js","../src/error-reporting/request-details.js","../src/frontend-model-resource/base-resource.js","../src/frontend-model-resource/velocious-attachment-resource.js","../src/frontend-models/base.js","../src/frontend-models/built-in-resources.js","../src/frontend-models/clear-pending-debounced-callback.js","../src/frontend-models/event-hook-models.js","../src/frontend-models/model-registry.js","../src/frontend-models/outgoing-event-buffer.js","../src/frontend-models/preloader.js","../src/frontend-models/query.js","../src/frontend-models/resource-config-validation.js","../src/frontend-models/resource-definition.js","../src/frontend-models/transport-serialization.js","../src/frontend-models/use-created-event.js","../src/frontend-models/use-destroyed-event.js","../src/frontend-models/use-model-class-event.js","../src/frontend-models/use-updated-event.js","../src/frontend-models/websocket-channel.js","../src/frontend-models/websocket-publishers.js","../src/http-client/header.js","../src/http-client/index.js","../src/http-client/request.js","../src/http-client/response.js","../src/http-client/websocket-client.js","../src/http-server/cookie.js","../src/http-server/development-reloader.js","../src/http-server/index.js","../src/http-server/remote-address.js","../src/http-server/server-client.js","../src/http-server/server-lock.js","../src/http-server/websocket-channel-subscribers.js","../src/http-server/websocket-channel.js","../src/http-server/websocket-connection.js","../src/http-server/websocket-event-log-store.js","../src/http-server/websocket-events-host.js","../src/http-server/websocket-events.js","../src/http-server/client/index.js","../src/http-server/client/params-to-object.js","../src/http-server/client/request-parser.js","../src/http-server/client/request-runner.js","../src/http-server/client/request-timing.js","../src/http-server/client/request.js","../src/http-server/client/response.js","../src/http-server/client/websocket-request.js","../src/http-server/client/websocket-session.js","../src/http-server/client/request-buffer/form-data-part.js","../src/http-server/client/request-buffer/header.js","../src/http-server/client/request-buffer/index.js","../src/http-server/client/uploaded-file/memory-uploaded-file.js","../src/http-server/client/uploaded-file/temporary-uploaded-file.js","../src/http-server/client/uploaded-file/uploaded-file.js","../src/http-server/worker-handler/channel-subscriber-dispatch.js","../src/http-server/worker-handler/in-process.js","../src/http-server/worker-handler/index.js","../src/http-server/worker-handler/worker-script.js","../src/http-server/worker-handler/worker-thread.js","../src/jobs/mail-delivery.js","../src/jobs/prune-terminal-background-jobs.js","../src/logger/base-logger.js","../src/logger/console-logger.js","../src/logger/file-logger.js","../src/logger/outputs/array-output.js","../src/logger/outputs/console-output.js","../src/logger/outputs/file-output.js","../src/logger/outputs/stdout-output.js","../src/mailer/base.js","../src/mailer/delivery.js","../src/mailer/index.js","../src/mailer/backends/smtp.js","../src/packages/velocious-package.js","../src/plugins/sqljs-wasm-route-controller.js","../src/plugins/sqljs-wasm-route.js","../src/routes/app-routes.js","../src/routes/base-route.js","../src/routes/basic-route.js","../src/routes/get-route.js","../src/routes/index.js","../src/routes/namespace-route.js","../src/routes/plugin-routes.js","../src/routes/post-route.js","../src/routes/resolver.js","../src/routes/resource-route.js","../src/routes/root-route.js","../src/routes/built-in/api-manifest/controller.js","../src/routes/built-in/debug/controller.js","../src/routes/built-in/errors/controller.js","../src/routes/hooks/frontend-model-command-route-hook.js","../src/sync/conflict-strategy.js","../src/sync/device-identity.js","../src/sync/local-mutation-log.js","../src/sync/offline-grant.js","../src/sync/peer-mutation-bundle.js","../src/sync/query-scope.js","../src/sync/server-change-feed.js","../src/sync/server-sequence-allocator.js","../src/sync/stable-json.js","../src/sync/sync-api-client-types.js","../src/sync/sync-api-client.js","../src/sync/sync-api-controller.js","../src/sync/sync-change-fanout.js","../src/sync/sync-channel-name.js","../src/sync/sync-client-registry.js","../src/sync/sync-client-types.js","../src/sync/sync-client.js","../src/sync/sync-envelope-replay-service.js","../src/sync/sync-model-change-feed-service.js","../src/sync/sync-publish-suppression.js","../src/sync/sync-publisher-types.js","../src/sync/sync-publisher.js","../src/sync/sync-realtime-bridge.js","../src/sync/sync-replay-upsert-applier.js","../src/sync/sync-resource-base.js","../src/sync/sync-scope-attributes.js","../src/sync/sync-scope-store.js","../src/sync/sync-websocket-channel.js","../src/tenants/default-tenant-database-provisioning.js","../src/tenants/tenant-aggregator.js","../src/tenants/tenant-iterator.js","../src/tenants/tenant.js","../src/testing/base-expect.js","../src/testing/browser-frontend-model-event-hook-scenarios.js","../src/testing/browser-test-app.js","../src/testing/expect-to-change.js","../src/testing/expect-utils.js","../src/testing/expect.js","../src/testing/request-client.js","../src/testing/test-files-finder.js","../src/testing/test-filter-parser.js","../src/testing/test-runner.js","../src/testing/test-suite-splitter.js","../src/testing/test.js","../src/types/external-modules.d.ts","../src/utils/backtrace-cleaner-node.js","../src/utils/backtrace-cleaner.js","../src/utils/deburr-column-name.js","../src/utils/event-emitter.js","../src/utils/file-exists.js","../src/utils/format-value.js","../src/utils/is-date.js","../src/utils/model-scope.js","../src/utils/nest-callbacks.js","../src/utils/plain-object.js","../src/utils/ransack.js","../src/utils/rest-args-error.js","../src/utils/singularize-model-name.js","../src/utils/split-sql-statements.js","../src/utils/to-import-specifier.js","../src/utils/with-tracked-stack-async-hooks.js","../src/utils/with-tracked-stack.js"],"version":"6.0.3"}
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "velocious": "build/bin/velocious.js"
4
4
  },
5
5
  "name": "velocious",
6
- "version": "1.0.520",
6
+ "version": "1.0.522",
7
7
  "main": "build/index.js",
8
8
  "types": "build/index.d.ts",
9
9
  "files": [
@@ -225,6 +225,12 @@ export default class BackgroundJobsStore {
225
225
  query = query.where({execution_mode: executionModes})
226
226
  }
227
227
 
228
+ if (scheduledAtOperator === "<=") {
229
+ const priorityOrder = this._queuePriorityOrderSql(db)
230
+
231
+ if (priorityOrder) query = query.order(`${priorityOrder} DESC`)
232
+ }
233
+
228
234
  query = query
229
235
  .order("scheduled_at_ms ASC")
230
236
  .order("created_at_ms ASC")
@@ -238,6 +244,40 @@ export default class BackgroundJobsStore {
238
244
  return this._normalizeJobRow(row)
239
245
  }
240
246
 
247
+ /**
248
+ * Builds a raw SQL ORDER BY expression ranking queued jobs by their queue's
249
+ * configured priority (`backgroundJobs.queues[queue].priority`, default `0`),
250
+ * so the dispatcher picks higher-priority queues first regardless of enqueue
251
+ * order. Only applied to the dispatch path (`scheduledAtOperator === "<="`);
252
+ * the future-scheduled lookup must stay strictly time-ordered. Composes with
253
+ * the concurrency EXISTS filter: a higher-priority queue already at its cap is
254
+ * filtered out, so dispatch falls through to the next eligible lower-priority
255
+ * job. Returns null when no queue configures a non-zero priority so the plain
256
+ * FIFO ordering is left untouched (and no needless filesort is introduced).
257
+ * @param {import("../database/drivers/base.js").default} db - Database connection.
258
+ * @returns {string | null} - Raw SQL CASE expression, or null when no queue is prioritized.
259
+ */
260
+ _queuePriorityOrderSql(db) {
261
+ const queues = this.configuration.getBackgroundJobsConfig().queues || {}
262
+ /** @type {Array<[string, number]>} */
263
+ const prioritized = []
264
+
265
+ for (const [queue, queueConfig] of Object.entries(queues)) {
266
+ const priority = queueConfig?.priority
267
+
268
+ if (Number.isFinite(priority) && Number(priority) !== 0) prioritized.push([queue, Number(priority)])
269
+ }
270
+
271
+ if (prioritized.length === 0) return null
272
+
273
+ const queueColumn = db.quoteColumn("queue")
274
+ const whens = prioritized
275
+ .map(([queue, priority]) => `WHEN ${db.quote(queue)} THEN ${priority}`)
276
+ .join(" ")
277
+
278
+ return `CASE COALESCE(${queueColumn}, ${db.quote(DEFAULT_QUEUE)}) ${whens} ELSE 0 END`
279
+ }
280
+
241
281
  /**
242
282
  * Runs get job.
243
283
  * @param {string} jobId - Job id.
@@ -164,15 +164,29 @@
164
164
  * allowed to keep in flight. Default: `4`. This is a per-worker safety cap;
165
165
  * for workload-shaped limits use per-queue caps (`queues`) instead, which are
166
166
  * enforced cluster-wide and are immune to duplicate worker processes.
167
- * @property {Record<string, {maxConcurrent?: number}>} [queues] - Per-queue
168
- * concurrency caps, Sidekiq-style. A job declares its queue (static `queue` on
169
- * the job class, or the `queue` enqueue option; defaults to `"default"`), and
167
+ * @property {Record<string, {maxConcurrent?: number, priority?: number}>} [queues] - Per-queue
168
+ * concurrency caps and dispatch priorities, Sidekiq-style. A job declares its queue (static
169
+ * `queue` on the job class, or the `queue` enqueue option; defaults to `"default"`), and
170
170
  * `queues[name].maxConcurrent` bounds how many jobs from that queue may be
171
171
  * in flight across the whole cluster (enforced via durable per-key
172
172
  * concurrency, so it holds regardless of how many worker processes run).
173
173
  * Size each queue to its workload: I/O-bound queues (e.g. build runners
174
174
  * waiting on remote Docker servers) can run far above the core count, while
175
- * CPU-bound queues should stay near it. Default: `{}` (no queue caps).
175
+ * CPU-bound queues should stay near it. `queues[name].priority` (default `0`)
176
+ * makes the main process dispatch higher-priority queues before lower-priority
177
+ * ones regardless of enqueue order, so a small time-critical queue can never be
178
+ * starved by a flood of low-priority work sharing a worker pool. Unlike
179
+ * Sidekiq's strict queue ordering, priority composes with the per-queue caps:
180
+ * a higher-priority queue that is already at its `maxConcurrent` is skipped and
181
+ * dispatch falls through to the next eligible lower-priority job, so a busy
182
+ * high-priority queue does not block everything else. This fallthrough is a
183
+ * property of the queue-derived cap; a job that supplies its own explicit
184
+ * `concurrencyKey`/`maxConcurrency` bypasses the queue cap entirely (an
185
+ * explicit key always wins — see above) and is bounded only by that key, so it
186
+ * is not held back by the queue's cap and priority simply orders it normally.
187
+ * Priorities may be any number, including negative to sink a queue below the
188
+ * default. Jobs within the same priority keep FIFO (`scheduled_at`, then
189
+ * `created_at`) order. Default: `{}` (no queue caps, all queues priority `0`).
176
190
  * @property {BackgroundJobsDispatchStrategy} [dispatchStrategy] - How the main process
177
191
  * detects new work. Defaults to `"beacon"` (event-driven). Set to `"polling"`
178
192
  * to restore the legacy fixed-interval poll.