velocious 1.0.580 → 1.0.582

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 (65) hide show
  1. package/README.md +18 -1
  2. package/build/cli/command-arguments.js +45 -0
  3. package/build/cli/index.js +10 -0
  4. package/build/configuration-types.js +1 -0
  5. package/build/database/drivers/base.js +134 -13
  6. package/build/database/generation-context.js +121 -0
  7. package/build/database/initializer-from-require-context.js +7 -0
  8. package/build/environment-handlers/node/cli/commands/db/schema/dump.js +25 -0
  9. package/build/environment-handlers/node/cli/commands/db/schema/load.js +37 -8
  10. package/build/environment-handlers/node/cli/commands/generate/base-models.js +82 -13
  11. package/build/environment-handlers/node/cli/commands/test.js +21 -2
  12. package/build/frontend-model-controller.js +131 -28
  13. package/build/src/cli/command-arguments.d.ts +27 -0
  14. package/build/src/cli/command-arguments.d.ts.map +1 -0
  15. package/build/src/cli/command-arguments.js +38 -0
  16. package/build/src/cli/index.d.ts.map +1 -1
  17. package/build/src/cli/index.js +9 -1
  18. package/build/src/configuration-types.d.ts +8 -0
  19. package/build/src/configuration-types.d.ts.map +1 -1
  20. package/build/src/configuration-types.js +2 -1
  21. package/build/src/database/drivers/base.d.ts +32 -0
  22. package/build/src/database/drivers/base.d.ts.map +1 -1
  23. package/build/src/database/drivers/base.js +109 -17
  24. package/build/src/database/generation-context.d.ts +62 -0
  25. package/build/src/database/generation-context.d.ts.map +1 -0
  26. package/build/src/database/generation-context.js +102 -0
  27. package/build/src/database/initializer-from-require-context.d.ts.map +1 -1
  28. package/build/src/database/initializer-from-require-context.js +6 -1
  29. package/build/src/environment-handlers/node/cli/commands/db/schema/dump.d.ts.map +1 -1
  30. package/build/src/environment-handlers/node/cli/commands/db/schema/dump.js +22 -1
  31. package/build/src/environment-handlers/node/cli/commands/db/schema/load.d.ts +11 -0
  32. package/build/src/environment-handlers/node/cli/commands/db/schema/load.d.ts.map +1 -1
  33. package/build/src/environment-handlers/node/cli/commands/db/schema/load.js +33 -7
  34. package/build/src/environment-handlers/node/cli/commands/generate/base-models.d.ts +14 -0
  35. package/build/src/environment-handlers/node/cli/commands/generate/base-models.d.ts.map +1 -1
  36. package/build/src/environment-handlers/node/cli/commands/generate/base-models.js +371 -308
  37. package/build/src/environment-handlers/node/cli/commands/test.d.ts +6 -0
  38. package/build/src/environment-handlers/node/cli/commands/test.d.ts.map +1 -1
  39. package/build/src/environment-handlers/node/cli/commands/test.js +22 -3
  40. package/build/src/frontend-model-controller.d.ts +40 -0
  41. package/build/src/frontend-model-controller.d.ts.map +1 -1
  42. package/build/src/frontend-model-controller.js +116 -28
  43. package/build/src/testing/test-filter-parser.d.ts +5 -0
  44. package/build/src/testing/test-filter-parser.d.ts.map +1 -1
  45. package/build/src/testing/test-filter-parser.js +19 -2
  46. package/build/src/testing/test-suite-splitter.d.ts +16 -1
  47. package/build/src/testing/test-suite-splitter.d.ts.map +1 -1
  48. package/build/src/testing/test-suite-splitter.js +38 -3
  49. package/build/testing/test-filter-parser.js +21 -1
  50. package/build/testing/test-suite-splitter.js +44 -2
  51. package/build/tsconfig.tsbuildinfo +1 -1
  52. package/package.json +3 -1
  53. package/src/cli/command-arguments.js +45 -0
  54. package/src/cli/index.js +10 -0
  55. package/src/configuration-types.js +1 -0
  56. package/src/database/drivers/base.js +134 -13
  57. package/src/database/generation-context.js +121 -0
  58. package/src/database/initializer-from-require-context.js +7 -0
  59. package/src/environment-handlers/node/cli/commands/db/schema/dump.js +25 -0
  60. package/src/environment-handlers/node/cli/commands/db/schema/load.js +37 -8
  61. package/src/environment-handlers/node/cli/commands/generate/base-models.js +82 -13
  62. package/src/environment-handlers/node/cli/commands/test.js +21 -2
  63. package/src/frontend-model-controller.js +131 -28
  64. package/src/testing/test-filter-parser.js +21 -1
  65. package/src/testing/test-suite-splitter.js +44 -2
package/README.md CHANGED
@@ -8,6 +8,7 @@
8
8
  * Built-in record auditing for model lifecycle changes (see [docs/auditing.md](docs/auditing.md))
9
9
  * Declarative state machines for models, with typed event methods generated into the base model (see [docs/state-machine.md](docs/state-machine.md))
10
10
  * Migrations for schema changes and UTC datetime storage (see [docs/database-migrations.md](docs/database-migrations.md))
11
+ * Tenant-selected base-model and structure generation with one immutable, fail-closed physical database context; tenant-only model metadata initializes only after that context is active (see [docs/tenant-selected-database-generation.md](docs/tenant-selected-database-generation.md))
11
12
  * External packages (engines) that contribute data models, frontend-model resources and migrations to a consuming app (see [docs/packages.md](docs/packages.md))
12
13
  * Optional Rampway-owned durable deployment control plane mounted through the standard routes DSL on Velocious 1.0.577 or newer (see [docs/rampway-integration.md](docs/rampway-integration.md))
13
14
  * Controllers and views for HTTP endpoints
@@ -289,10 +290,26 @@ npx velocious test --groups=4 --group-number=2
289
290
 
290
291
  # Combine with tags
291
292
  npx velocious test --groups=3 --group-number=1 --tag fast
293
+
294
+ # Prefer recorded file durations when balancing groups
295
+ npx velocious test --groups=4 --group-number=1 --timing-manifest=tmp/test-timings.json
292
296
  ```
293
297
 
294
298
  Files are distributed using a greedy load-balancing algorithm. Each file is
295
- weighted by its spec directory (`system/` = 20, `frontend-models/` = 10,
299
+ primarily weighted by a positive finite duration from the optional timing
300
+ manifest. Manifest keys are normalized project-relative test paths using `/`
301
+ separators:
302
+
303
+ ```json
304
+ {
305
+ "spec/system/sign-in-spec.js": 42.7,
306
+ "spec/controller/accounts-spec.js": 8.1
307
+ }
308
+ ```
309
+
310
+ Files absent from the manifest, unknown files, invalid duration values, and
311
+ unreadable or malformed JSON all fall back to the existing deterministic
312
+ heuristic. The heuristic weights files by spec directory (`system/` = 20, `frontend-models/` = 10,
296
313
  `controller/` = 3, default = 1) with a 2x multiplier for `.browser-spec.js`
297
314
  files. The heaviest files are assigned first to the group with the least
298
315
  accumulated weight, producing balanced wall-clock times across groups.
@@ -0,0 +1,45 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * @typedef {object} CommandArgumentDefinition
5
+ * @property {string[]} [booleanOptions] - Flags that do not accept a value.
6
+ * @property {string[]} [valueOptions] - Flags that require one value.
7
+ */
8
+
9
+ /**
10
+ * Parses and validates one command's arguments.
11
+ * @param {object} args - Parser arguments.
12
+ * @param {CommandArgumentDefinition} args.definition - Accepted command options.
13
+ * @param {string[]} args.processArgs - Raw command arguments including the command name.
14
+ * @returns {Record<string, string | boolean>} - Values keyed by long option name without `--`.
15
+ */
16
+ export default function commandArguments({definition, processArgs}) {
17
+ const commandName = processArgs[0] || "command"
18
+ const booleanOptions = new Set(definition.booleanOptions || [])
19
+ const valueOptions = new Set(definition.valueOptions || [])
20
+ /** @type {Record<string, string | boolean>} */
21
+ const parsed = {}
22
+
23
+ for (let index = 1; index < processArgs.length; index++) {
24
+ const argument = processArgs[index]
25
+
26
+ if (booleanOptions.has(argument)) {
27
+ parsed[argument.slice(2)] = true
28
+ continue
29
+ }
30
+
31
+ if (valueOptions.has(argument)) {
32
+ const value = processArgs[index + 1]
33
+
34
+ if (!value || value.startsWith("-")) throw new Error(`Missing value for ${argument}`)
35
+
36
+ parsed[argument.slice(2)] = value
37
+ index++
38
+ continue
39
+ }
40
+
41
+ throw new Error(`Unknown argument for ${commandName}: ${argument}`)
42
+ }
43
+
44
+ return parsed
45
+ }
@@ -1,5 +1,7 @@
1
1
  // @ts-check
2
2
 
3
+ const COMMAND_VALUE_OPTIONS = new Set(["--tenant"])
4
+
3
5
  export default class VelociousCli {
4
6
  /**
5
7
  * Runs constructor.
@@ -94,6 +96,7 @@ export default class VelociousCli {
94
96
  * Current group.
95
97
  * @type {string[]} */
96
98
  let currentGroup = []
99
+ let expectsOptionValue = false
97
100
 
98
101
  for (const processArg of processArgs) {
99
102
  if (currentGroup.length == 0) {
@@ -103,11 +106,18 @@ export default class VelociousCli {
103
106
  continue
104
107
  }
105
108
 
109
+ if (expectsOptionValue) {
110
+ currentGroup.push(processArg)
111
+ expectsOptionValue = false
112
+ continue
113
+ }
114
+
106
115
  if (!processArg.startsWith("-") && commandNames.has(processArg)) {
107
116
  groups.push(currentGroup)
108
117
  currentGroup = [processArg]
109
118
  } else {
110
119
  currentGroup.push(processArg)
120
+ expectsOptionValue = COMMAND_VALUE_OPTIONS.has(processArg)
111
121
  }
112
122
  }
113
123
 
@@ -661,6 +661,7 @@
661
661
  /**
662
662
  * @typedef {object} TenantDatabaseProviderType
663
663
  * @property {(args: {configuration: import("./configuration.js").default, identifier: string}) => Array<ReturnType<typeof JSON.parse>> | Promise<Array<ReturnType<typeof JSON.parse>>>} listTenants - Lists tenants that should be created, checked, or migrated for this database identifier.
664
+ * @property {(args: {configuration: import("./configuration.js").default, identifier: string}) => ReturnType<typeof JSON.parse> | void | Promise<ReturnType<typeof JSON.parse> | void>} [resolveGenerationTenant] - Resolves one explicit tenant descriptor for schema/base-model generation without enumerating lifecycle tenants.
664
665
  * @property {(args: {configuration: import("./configuration.js").default, identifier: string}) => Array<ReturnType<typeof JSON.parse>> | Promise<Array<ReturnType<typeof JSON.parse>>>} [listRestrictTenants] - Lists existing tenants that should be checked for dependent restrict destroys. Defaults to listTenants.
665
666
  * @property {(args: {configuration: import("./configuration.js").default, databaseConfiguration: DatabaseConfigurationType, identifier: string, tenant: ReturnType<typeof JSON.parse>}) => void | Promise<void>} [createDatabase] - Creates the tenant database/schema for one tenant.
666
667
  * @property {(args: {configuration: import("./configuration.js").default, databaseConfiguration: DatabaseConfigurationType, identifier: string, tenant: ReturnType<typeof JSON.parse>}) => void | Promise<void>} [dropDatabase] - Drops the tenant database/schema for one tenant.
@@ -118,6 +118,14 @@
118
118
  * @property {string[]} updateColumns - Columns to update on conflict.
119
119
  */
120
120
 
121
+ /**
122
+ * SqlTokenResult type.
123
+ * @typedef {object} SqlTokenResult
124
+ * @property {boolean} incomplete - Whether the scan hit its bound before finishing trivia/token parsing.
125
+ * @property {string | undefined} token - Lowercased token when parsing completed; undefined when no token was found.
126
+ * @property {number} index - Index immediately after the parsed token or trivia.
127
+ */
128
+
121
129
  import BacktraceCleaner from "../../utils/backtrace-cleaner.js"
122
130
  import { getDatabaseAnnotations } from "../annotations.js"
123
131
  import { formatDateForDatabase } from "../datetime-storage.js"
@@ -134,6 +142,11 @@ import TableForeignKey from "../table-data/table-foreign-key.js"
134
142
  import wait from "awaitery/build/wait.js"
135
143
  import {optionalPositiveInteger} from "typanic"
136
144
 
145
+ /** Maximum characters inspected when building the debug SQL preview. */
146
+ const SQL_PREVIEW_SCAN_LIMIT = 4096
147
+ /** Maximum characters inspected when deciding whether a statement invalidates schema metadata. */
148
+ const SCHEMA_INVALIDATION_SCAN_LIMIT = 8192
149
+
137
150
  /**
138
151
  * Marks a callback failure that happened after the owning transaction was durably committed.
139
152
  * The public transaction boundary unwraps it before deadlock classification.
@@ -1604,13 +1617,25 @@ export default class VelociousDatabaseDriversBase {
1604
1617
  }
1605
1618
  }
1606
1619
 
1620
+ /**
1621
+ * Returns a bounded prefix of `sql` for lightweight diagnostic scanning.
1622
+ * @param {string} sql - SQL string.
1623
+ * @param {number} limit - Maximum code units to inspect.
1624
+ * @returns {string} - Prefix of `sql`.
1625
+ */
1626
+ _diagnosticSqlPrefix(sql, limit) {
1627
+ return sql.length <= limit ? sql : sql.slice(0, limit)
1628
+ }
1629
+
1607
1630
  /**
1608
1631
  * Runs debug sql preview.
1609
1632
  * @param {string} sql - SQL to preview.
1610
1633
  * @returns {string} - Normalized truncated SQL preview for diagnostics.
1611
1634
  */
1612
1635
  _debugSqlPreview(sql) {
1613
- return sql
1636
+ const prefix = this._diagnosticSqlPrefix(sql, SQL_PREVIEW_SCAN_LIMIT)
1637
+
1638
+ return prefix
1614
1639
  .replace(/\s+/g, " ")
1615
1640
  .trim()
1616
1641
  .slice(0, 500)
@@ -1664,25 +1689,121 @@ export default class VelociousDatabaseDriversBase {
1664
1689
  .replace(/"/g, "'")
1665
1690
  }
1666
1691
 
1692
+ /**
1693
+ * Reads the next SQL token starting at `startIndex`, skipping leading trivia
1694
+ * (BOM, whitespace, block comments, line comments). If the scan cannot finish
1695
+ * skipping trivia before `limit`, the result is marked incomplete so callers
1696
+ * can conservatively treat the statement as schema-invalidating.
1697
+ * @param {string} sql - SQL string.
1698
+ * @param {number} startIndex - Index to start scanning.
1699
+ * @param {number} limit - Maximum absolute index to scan while skipping leading trivia.
1700
+ * @returns {SqlTokenResult} - Token result.
1701
+ */
1702
+ _readSqlToken(sql, startIndex, limit) {
1703
+ let i = startIndex
1704
+ const len = sql.length
1705
+
1706
+ while (i < len && i < limit) {
1707
+ const char = sql[i]
1708
+
1709
+ if (char === "\ufeff" || /\s/.test(char)) {
1710
+ i++
1711
+ continue
1712
+ }
1713
+
1714
+ if (char === "/" && sql[i + 1] === "*") {
1715
+ const close = sql.indexOf("*/", i + 2)
1716
+
1717
+ if (close === -1 || close + 2 > limit) {
1718
+ return {incomplete: true, index: i, token: undefined}
1719
+ }
1720
+
1721
+ i = close + 2
1722
+ continue
1723
+ }
1724
+
1725
+ if (char === "-" && sql[i + 1] === "-") {
1726
+ const newline = sql.indexOf("\n", i + 2)
1727
+
1728
+ if (newline === -1) {
1729
+ return {incomplete: false, index: len, token: undefined}
1730
+ }
1731
+
1732
+ if (newline + 1 > limit) {
1733
+ return {incomplete: true, index: i, token: undefined}
1734
+ }
1735
+
1736
+ i = newline + 1
1737
+ continue
1738
+ }
1739
+
1740
+ let token = ""
1741
+
1742
+ while (i < len) {
1743
+ const c = sql[i]
1744
+
1745
+ if (/\s/.test(c) || c === "\ufeff") break
1746
+ if (c === "/" && sql[i + 1] === "*") break
1747
+ if (c === "-" && sql[i + 1] === "-") break
1748
+
1749
+ token += c
1750
+ i++
1751
+ }
1752
+
1753
+ return {incomplete: false, token: token.toLowerCase(), index: i}
1754
+ }
1755
+
1756
+ if (i >= len) {
1757
+ return {incomplete: false, index: len, token: undefined}
1758
+ }
1759
+
1760
+ return {incomplete: true, index: i, token: undefined}
1761
+ }
1762
+
1667
1763
  /**
1668
1764
  * Runs schema cache invalidating sql.
1669
1765
  * @param {string} sql - SQL string.
1670
1766
  * @returns {boolean} - Whether the SQL should invalidate schema metadata.
1671
1767
  */
1672
1768
  _schemaCacheInvalidatingSql(sql) {
1673
- const normalized = sql
1674
- .trim()
1675
- .replace(/^\ufeff/, "")
1676
- .replace(/\/\*[\s\S]*?\*\//g, " ")
1677
- .replace(/--[^\n]*(\n|$)/g, " ")
1678
- .replace(/\s+/g, " ")
1679
- .toLowerCase()
1769
+ const first = this._readSqlToken(sql, 0, SCHEMA_INVALIDATION_SCAN_LIMIT)
1680
1770
 
1681
- if (!normalized) return false
1682
- if (/^(create|alter|drop|rename)\b/.test(normalized)) return true
1683
- if (/^comment\s+on\b/.test(normalized)) return true
1684
- if (/^exec(?:ute)?\s+sp_rename\b/.test(normalized)) return true
1685
- if (/^if\b[\s\S]*\bbegin\s+(create|alter|drop|rename)\b/.test(normalized)) return true
1771
+ if (first.incomplete) return true
1772
+
1773
+ const firstToken = first.token
1774
+
1775
+ if (!firstToken) return false
1776
+ if (/^(create|alter|drop|rename)$/.test(firstToken)) return true
1777
+
1778
+ if (firstToken === "comment") {
1779
+ const next = this._readSqlToken(sql, first.index, SCHEMA_INVALIDATION_SCAN_LIMIT)
1780
+
1781
+ return next.incomplete || next.token === "on"
1782
+ }
1783
+
1784
+ if (firstToken === "exec" || firstToken === "execute") {
1785
+ const next = this._readSqlToken(sql, first.index, SCHEMA_INVALIDATION_SCAN_LIMIT)
1786
+
1787
+ return next.incomplete || next.token === "sp_rename"
1788
+ }
1789
+
1790
+ if (firstToken === "if") {
1791
+ let index = first.index
1792
+
1793
+ while (true) {
1794
+ const result = this._readSqlToken(sql, index, SCHEMA_INVALIDATION_SCAN_LIMIT)
1795
+
1796
+ if (result.incomplete) return true
1797
+ if (!result.token) return false
1798
+ if (result.token === "begin") {
1799
+ const ddlResult = this._readSqlToken(sql, result.index, SCHEMA_INVALIDATION_SCAN_LIMIT)
1800
+
1801
+ return ddlResult.incomplete || /^(create|alter|drop|rename)$/.test(ddlResult.token || "")
1802
+ }
1803
+
1804
+ index = result.index
1805
+ }
1806
+ }
1686
1807
 
1687
1808
  return false
1688
1809
  }
@@ -0,0 +1,121 @@
1
+ // @ts-check
2
+
3
+ import Tenant from "../tenants/tenant.js"
4
+
5
+ /**
6
+ * Immutable selection of one logical tenant database and one provider-resolved
7
+ * physical tenant. Schema tools can share this contract without ambient or
8
+ * process-global selection state.
9
+ */
10
+ export default class DatabaseGenerationContext {
11
+ /**
12
+ * Resolves one tenant-only database from its provider and captures its physical identity.
13
+ * @param {object} args - Selection arguments.
14
+ * @param {import("../configuration.js").default} args.configuration - Owning configuration.
15
+ * @param {string} args.databaseIdentifier - Logical tenant-only database identifier.
16
+ * @returns {Promise<DatabaseGenerationContext>} - Immutable selected database context.
17
+ */
18
+ static async resolve({configuration, databaseIdentifier}) {
19
+ const databaseConfiguration = configuration.getDatabaseConfiguration()[databaseIdentifier]
20
+
21
+ if (!databaseConfiguration) {
22
+ throw new Error(`No such tenant database identifier configured: ${databaseIdentifier}`)
23
+ }
24
+ if (!databaseConfiguration.tenantOnly) {
25
+ throw new Error(`Database identifier ${databaseIdentifier} is not configured with tenantOnly: true`)
26
+ }
27
+ if (configuration.getDisabledDatabaseIdentifiers().has(databaseIdentifier)) {
28
+ throw new Error(`Tenant database identifier ${databaseIdentifier} is disabled by VELOCIOUS_DISABLED_DATABASE_IDENTIFIERS`)
29
+ }
30
+
31
+ const provider = configuration.getTenantDatabaseProvider(databaseIdentifier)
32
+
33
+ await configuration.initialize({type: "database-generation"})
34
+
35
+ const tenants = await configuration.ensureConnections({name: `Resolve database generation context: ${databaseIdentifier}`}, async () => {
36
+ if (provider.resolveGenerationTenant) {
37
+ const tenant = await provider.resolveGenerationTenant({configuration, identifier: databaseIdentifier})
38
+
39
+ return tenant === undefined ? [] : [tenant]
40
+ }
41
+
42
+ return await provider.listTenants({configuration, identifier: databaseIdentifier})
43
+ })
44
+
45
+ if (!Array.isArray(tenants)) {
46
+ throw new Error(`Tenant database provider for ${databaseIdentifier} must return an array from listTenants`)
47
+ }
48
+ if (tenants.length === 0) {
49
+ throw new Error(`Tenant database selection ${databaseIdentifier} resolved no tenants`)
50
+ }
51
+ if (tenants.length !== 1) {
52
+ throw new Error(`Tenant database selection ${databaseIdentifier} is ambiguous: provider returned ${tenants.length} tenants`)
53
+ }
54
+
55
+ const tenant = tenants[0]
56
+
57
+ if (!tenant || typeof tenant !== "object" || Array.isArray(tenant)) {
58
+ throw new Error(`Tenant database selection ${databaseIdentifier} returned an invalid tenant descriptor`)
59
+ }
60
+
61
+ const handle = Tenant.handle(tenant, configuration)
62
+
63
+ // Resolve now so an inactive/stale descriptor fails before a selected
64
+ // schema connection can be checked out or read.
65
+ handle.databaseConfiguration(databaseIdentifier)
66
+
67
+ return new DatabaseGenerationContext({configuration, databaseIdentifier, handle})
68
+ }
69
+
70
+ /**
71
+ * Runs constructor.
72
+ * @param {object} args - Captured selection.
73
+ * @param {import("../configuration.js").default} args.configuration - Owning configuration.
74
+ * @param {string} args.databaseIdentifier - Logical database identifier.
75
+ * @param {ReturnType<typeof Tenant.handle>} args.handle - Captured tenant handle.
76
+ */
77
+ constructor({configuration, databaseIdentifier, handle}) {
78
+ this._configuration = configuration
79
+ this._databaseIdentifier = databaseIdentifier
80
+ this._handle = handle
81
+
82
+ Object.freeze(this)
83
+ }
84
+
85
+ /**
86
+ * Returns the captured logical database identifier.
87
+ * @returns {string} - Captured logical database identifier.
88
+ */
89
+ databaseIdentifier() { return this._databaseIdentifier }
90
+
91
+ /**
92
+ * Returns the captured physical database configuration.
93
+ * @returns {import("../configuration-types.js").DatabaseConfigurationType} - Captured physical database configuration.
94
+ */
95
+ databaseConfiguration() { return this._handle.databaseConfiguration(this._databaseIdentifier) }
96
+
97
+ /**
98
+ * Returns the captured tenant descriptor.
99
+ * @returns {ReturnType<ReturnType<typeof Tenant.handle>["tenant"]>} - Captured immutable tenant descriptor.
100
+ */
101
+ tenant() { return this._handle.tenant() }
102
+
103
+ /**
104
+ * Runs work on one connection pinned to the captured physical database.
105
+ * @template T
106
+ * @param {object} args - Work arguments.
107
+ * @param {(connection: import("./drivers/base.js").default) => Promise<T>} args.callback - Selected database work.
108
+ * @param {string} args.name - Checkout name.
109
+ * @returns {Promise<T>} - Callback result.
110
+ */
111
+ async run({callback, name}) {
112
+ return await this._configuration.runWithTenant(this.tenant(), async () => {
113
+ return await this._configuration.withDatabaseOperation({
114
+ databaseConfiguration: this.databaseConfiguration(),
115
+ databaseIdentifier: this.databaseIdentifier(),
116
+ name,
117
+ tenant: this.tenant()
118
+ }, async (operation) => await callback(operation.connection()))
119
+ })
120
+ }
121
+ }
@@ -43,6 +43,13 @@ export default class VelociousDatabaseInitializerFromRequireContext {
43
43
 
44
44
  if (!modelClass) throw new Error(`Model wasn't exported from: ${fileName}`)
45
45
 
46
+ const configuredDatabase = configuration.getDatabaseConfiguration()[modelClass.getConfiguredDatabaseIdentifier()]
47
+
48
+ if (configuredDatabase?.tenantOnly && !configuration.isDatabaseIdentifierActive(modelClass.getConfiguredDatabaseIdentifier())) {
49
+ modelClass.registerRecordClass({configuration})
50
+ continue
51
+ }
52
+
46
53
  if (!modelClass.getEagerLoadRecordMetadata()) {
47
54
  modelClass.registerRecordClass({configuration})
48
55
  await this._bestEffortInitializeDeferredModel({configuration, modelClass})
@@ -1,4 +1,6 @@
1
1
  import BaseCommand from "../../../../../../cli/base-command.js"
2
+ import commandArguments from "../../../../../../cli/command-arguments.js"
3
+ import DatabaseGenerationContext from "../../../../../../database/generation-context.js"
2
4
  import fileExists from "../../../../../../utils/file-exists.js"
3
5
  import path from "path"
4
6
 
@@ -8,6 +10,29 @@ export default class DbSchemaDump extends BaseCommand {
8
10
  * Runs execute.
9
11
  * @returns {Promise<void>} */
10
12
  async execute() {
13
+ const parsedArguments = commandArguments({
14
+ definition: {valueOptions: ["--tenant"]},
15
+ processArgs: this.processArgs || []
16
+ })
17
+ const tenantDatabaseIdentifier = parsedArguments.tenant
18
+
19
+ if (typeof tenantDatabaseIdentifier === "string") {
20
+ const context = await DatabaseGenerationContext.resolve({
21
+ configuration: this.getConfiguration(),
22
+ databaseIdentifier: tenantDatabaseIdentifier
23
+ })
24
+
25
+ await context.run({name: "DB selected tenant schema dump", callback: async (db) => {
26
+ const dbs = {[context.databaseIdentifier()]: db}
27
+ const shouldGenerate = await this.shouldGenerateStructureSql({dbs})
28
+
29
+ if (!shouldGenerate) return
30
+
31
+ await this.getEnvironmentHandler().afterMigrations({dbs, reason: "schemaDump"})
32
+ }})
33
+ return
34
+ }
35
+
11
36
  await this.getConfiguration().ensureConnections({name: "DB schema dump"}, async (dbs) => {
12
37
  const shouldGenerate = await this.shouldGenerateStructureSql({dbs})
13
38
 
@@ -1,4 +1,6 @@
1
1
  import BaseCommand from "../../../../../../cli/base-command.js"
2
+ import commandArguments from "../../../../../../cli/command-arguments.js"
3
+ import DatabaseGenerationContext from "../../../../../../database/generation-context.js"
2
4
  import fs from "fs/promises"
3
5
  import path from "path"
4
6
  import StructureSqlLoader from "../../../../../../database/structure-sql-loader.js"
@@ -9,17 +11,44 @@ export default class DbSchemaLoad extends BaseCommand {
9
11
  * Runs execute.
10
12
  * @returns {Promise<void>} */
11
13
  async execute() {
12
- await this.getConfiguration().ensureConnections({name: "DB schema load"}, async (dbs) => {
13
- const dbDir = path.join(this.directory(), "db")
14
- const loader = new StructureSqlLoader()
14
+ const parsedArguments = commandArguments({
15
+ definition: {valueOptions: ["--tenant"]},
16
+ processArgs: this.processArgs || []
17
+ })
18
+ const tenantDatabaseIdentifier = parsedArguments.tenant
15
19
 
16
- for (const identifier of Object.keys(dbs)) {
17
- const db = dbs[identifier]
18
- const structureFilePath = path.join(dbDir, `structure-${identifier}.sql`)
19
- const structureSql = await fs.readFile(structureFilePath, "utf8")
20
+ if (typeof tenantDatabaseIdentifier === "string") {
21
+ const context = await DatabaseGenerationContext.resolve({
22
+ configuration: this.getConfiguration(),
23
+ databaseIdentifier: tenantDatabaseIdentifier
24
+ })
20
25
 
21
- await loader.load({db, structureSql})
26
+ await context.run({name: "DB selected tenant schema load", callback: async (db) => {
27
+ await this.loadStructureSql({db, identifier: context.databaseIdentifier()})
28
+ }})
29
+ return
30
+ }
31
+
32
+ await this.getConfiguration().ensureConnections({name: "DB schema load"}, async (dbs) => {
33
+ for (const identifier of Object.keys(dbs)) {
34
+ await this.loadStructureSql({db: dbs[identifier], identifier})
22
35
  }
23
36
  })
24
37
  }
38
+
39
+ /**
40
+ * Loads one identifier's explicit structure file into one selected connection.
41
+ * @param {object} args - Load arguments.
42
+ * @param {import("../../../../../../database/drivers/base.js").default} args.db - Target connection.
43
+ * @param {string} args.identifier - Logical database identifier used in the file name.
44
+ * @returns {Promise<void>} - Resolves after loading.
45
+ */
46
+ async loadStructureSql({db, identifier}) {
47
+ const dbDir = path.join(this.directory(), "db")
48
+ const loader = new StructureSqlLoader()
49
+ const structureFilePath = path.join(dbDir, `structure-${identifier}.sql`)
50
+ const structureSql = await fs.readFile(structureFilePath, "utf8")
51
+
52
+ await loader.load({db, structureSql})
53
+ }
25
54
  }