velocious 1.0.593 → 1.0.595

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 (136) hide show
  1. package/README.md +3 -2
  2. package/build/configuration.js +109 -14
  3. package/build/database/drivers/base.js +30 -0
  4. package/build/database/migrator.js +90 -24
  5. package/build/database/operation.js +55 -4
  6. package/build/database/query/join-object.js +7 -4
  7. package/build/database/query/model-class-query.js +38 -7
  8. package/build/database/query/preloader/belongs-to.js +9 -6
  9. package/build/database/query/preloader/has-many.js +25 -14
  10. package/build/database/query/preloader/has-one.js +7 -4
  11. package/build/database/query/preloader/query-for-model.js +16 -1
  12. package/build/database/query/preloader.js +6 -4
  13. package/build/database/query/query-data.js +3 -3
  14. package/build/database/query/where-model-class-hash.js +8 -4
  15. package/build/database/query/with-count.js +4 -3
  16. package/build/database/record/index.js +148 -5
  17. package/build/database/record/instance-relationships/base.js +13 -0
  18. package/build/database/record/instance-relationships/belongs-to.js +1 -1
  19. package/build/database/record/instance-relationships/has-many.js +5 -2
  20. package/build/database/record/instance-relationships/has-one.js +1 -1
  21. package/build/database/record/relationships/base.js +17 -0
  22. package/build/database/use-database.js +81 -28
  23. package/build/src/configuration.d.ts +31 -4
  24. package/build/src/configuration.d.ts.map +1 -1
  25. package/build/src/configuration.js +88 -17
  26. package/build/src/database/drivers/base.d.ts +11 -0
  27. package/build/src/database/drivers/base.d.ts.map +1 -1
  28. package/build/src/database/drivers/base.js +28 -1
  29. package/build/src/database/migrator.d.ts +34 -0
  30. package/build/src/database/migrator.d.ts.map +1 -1
  31. package/build/src/database/migrator.js +83 -23
  32. package/build/src/database/operation.d.ts +15 -1
  33. package/build/src/database/operation.d.ts.map +1 -1
  34. package/build/src/database/operation.js +52 -5
  35. package/build/src/database/query/join-object.d.ts.map +1 -1
  36. package/build/src/database/query/join-object.js +7 -5
  37. package/build/src/database/query/model-class-query.d.ts +6 -0
  38. package/build/src/database/query/model-class-query.d.ts.map +1 -1
  39. package/build/src/database/query/model-class-query.js +33 -8
  40. package/build/src/database/query/preloader/belongs-to.d.ts.map +1 -1
  41. package/build/src/database/query/preloader/belongs-to.js +9 -7
  42. package/build/src/database/query/preloader/has-many.d.ts.map +1 -1
  43. package/build/src/database/query/preloader/has-many.js +23 -16
  44. package/build/src/database/query/preloader/has-one.d.ts.map +1 -1
  45. package/build/src/database/query/preloader/has-one.js +7 -5
  46. package/build/src/database/query/preloader/query-for-model.d.ts +8 -0
  47. package/build/src/database/query/preloader/query-for-model.d.ts.map +1 -1
  48. package/build/src/database/query/preloader/query-for-model.js +15 -2
  49. package/build/src/database/query/preloader.d.ts.map +1 -1
  50. package/build/src/database/query/preloader.js +6 -5
  51. package/build/src/database/query/query-data.js +4 -4
  52. package/build/src/database/query/where-model-class-hash.d.ts.map +1 -1
  53. package/build/src/database/query/where-model-class-hash.js +7 -5
  54. package/build/src/database/query/with-count.js +5 -4
  55. package/build/src/database/record/index.d.ts +95 -0
  56. package/build/src/database/record/index.d.ts.map +1 -1
  57. package/build/src/database/record/index.js +126 -6
  58. package/build/src/database/record/instance-relationships/base.d.ts +6 -0
  59. package/build/src/database/record/instance-relationships/base.d.ts.map +1 -1
  60. package/build/src/database/record/instance-relationships/base.js +12 -1
  61. package/build/src/database/record/instance-relationships/belongs-to.js +2 -2
  62. package/build/src/database/record/instance-relationships/has-many.d.ts.map +1 -1
  63. package/build/src/database/record/instance-relationships/has-many.js +6 -3
  64. package/build/src/database/record/instance-relationships/has-one.js +2 -2
  65. package/build/src/database/record/relationships/base.d.ts +11 -0
  66. package/build/src/database/record/relationships/base.d.ts.map +1 -1
  67. package/build/src/database/record/relationships/base.js +15 -1
  68. package/build/src/database/use-database.d.ts +33 -5
  69. package/build/src/database/use-database.d.ts.map +1 -1
  70. package/build/src/database/use-database.js +81 -30
  71. package/build/src/sync/server-change-feed.d.ts +14 -2
  72. package/build/src/sync/server-change-feed.d.ts.map +1 -1
  73. package/build/src/sync/server-change-feed.js +50 -10
  74. package/build/src/sync/sync-scope-store.d.ts +13 -2
  75. package/build/src/sync/sync-scope-store.d.ts.map +1 -1
  76. package/build/src/sync/sync-scope-store.js +48 -7
  77. package/build/src/tenants/frontend-tenant-sqlite-lifecycle.d.ts +43 -1
  78. package/build/src/tenants/frontend-tenant-sqlite-lifecycle.d.ts.map +1 -1
  79. package/build/src/tenants/frontend-tenant-sqlite-lifecycle.js +141 -17
  80. package/build/src/tenants/tenant-handle.d.ts +36 -0
  81. package/build/src/tenants/tenant-handle.d.ts.map +1 -1
  82. package/build/src/tenants/tenant-handle.js +73 -3
  83. package/build/src/testing/browser-test-app.js +3 -1
  84. package/build/src/testing/browser-use-database-hook-scenarios.d.ts +16 -0
  85. package/build/src/testing/browser-use-database-hook-scenarios.d.ts.map +1 -0
  86. package/build/src/testing/browser-use-database-hook-scenarios.js +182 -0
  87. package/build/src/testing/test-runner.d.ts.map +1 -1
  88. package/build/src/testing/test-runner.js +9 -8
  89. package/build/src/utils/model-scope.d.ts +2 -2
  90. package/build/src/utils/model-scope.d.ts.map +1 -1
  91. package/build/src/utils/model-scope.js +5 -3
  92. package/build/src/utils/ransack.d.ts.map +1 -1
  93. package/build/src/utils/ransack.js +5 -3
  94. package/build/sync/server-change-feed.js +58 -8
  95. package/build/sync/sync-scope-store.js +55 -5
  96. package/build/tenants/frontend-tenant-sqlite-lifecycle.js +150 -15
  97. package/build/tenants/tenant-handle.js +90 -6
  98. package/build/testing/browser-test-app.js +2 -0
  99. package/build/testing/browser-use-database-hook-scenarios.js +204 -0
  100. package/build/testing/test-runner.js +8 -7
  101. package/build/tsconfig.tsbuildinfo +1 -1
  102. package/build/utils/model-scope.js +5 -2
  103. package/build/utils/ransack.js +5 -2
  104. package/package.json +2 -1
  105. package/scripts/browser-test-session.js +60 -0
  106. package/scripts/test-browser.js +7 -7
  107. package/src/configuration.js +109 -14
  108. package/src/database/drivers/base.js +30 -0
  109. package/src/database/migrator.js +90 -24
  110. package/src/database/operation.js +55 -4
  111. package/src/database/query/join-object.js +7 -4
  112. package/src/database/query/model-class-query.js +38 -7
  113. package/src/database/query/preloader/belongs-to.js +9 -6
  114. package/src/database/query/preloader/has-many.js +25 -14
  115. package/src/database/query/preloader/has-one.js +7 -4
  116. package/src/database/query/preloader/query-for-model.js +16 -1
  117. package/src/database/query/preloader.js +6 -4
  118. package/src/database/query/query-data.js +3 -3
  119. package/src/database/query/where-model-class-hash.js +8 -4
  120. package/src/database/query/with-count.js +4 -3
  121. package/src/database/record/index.js +148 -5
  122. package/src/database/record/instance-relationships/base.js +13 -0
  123. package/src/database/record/instance-relationships/belongs-to.js +1 -1
  124. package/src/database/record/instance-relationships/has-many.js +5 -2
  125. package/src/database/record/instance-relationships/has-one.js +1 -1
  126. package/src/database/record/relationships/base.js +17 -0
  127. package/src/database/use-database.js +81 -28
  128. package/src/sync/server-change-feed.js +58 -8
  129. package/src/sync/sync-scope-store.js +55 -5
  130. package/src/tenants/frontend-tenant-sqlite-lifecycle.js +150 -15
  131. package/src/tenants/tenant-handle.js +90 -6
  132. package/src/testing/browser-test-app.js +2 -0
  133. package/src/testing/browser-use-database-hook-scenarios.js +204 -0
  134. package/src/testing/test-runner.js +8 -7
  135. package/src/utils/model-scope.js +5 -2
  136. package/src/utils/ransack.js +5 -2
@@ -1,11 +1,13 @@
1
1
  // @ts-check
2
2
 
3
+ import Migrator from "../database/migrator.js"
4
+
3
5
  /**
4
6
  * TenantDescriptorValue type.
5
7
  * @typedef {null | boolean | number | string | TenantDescriptorValue[] | {[key: string]: TenantDescriptorValue}} TenantDescriptorValue
6
8
  */
7
9
  /** @typedef {{[key: string]: TenantDescriptorValue}} TenantDescriptor */
8
- /** @typedef {{databaseIdentifier: string, dirty: boolean, lastUsed: number, pinCount: number, state: "closed" | "closing" | "deleting" | "open" | "opening"}} TenantSqliteLifecycleSnapshot */
10
+ /** @typedef {{databaseIdentifier: string, dirty: boolean, lastUsed: number, pinCount: number, ready: boolean, schemaGeneration: string | undefined, state: "closed" | "closing" | "deleting" | "open" | "opening"}} TenantSqliteLifecycleSnapshot */
9
11
 
10
12
  /**
11
13
  * Returns a readable path for a captured descriptor/configuration value.
@@ -204,6 +206,17 @@ export default class TenantHandle {
204
206
  return this._tenant
205
207
  }
206
208
 
209
+ /**
210
+ * Rejects using this handle with a different Configuration instance.
211
+ * @param {import("../configuration.js").default} configuration - Expected owner.
212
+ * @returns {void}
213
+ */
214
+ assertConfiguration(configuration) {
215
+ if (configuration !== this._configuration) {
216
+ throw new Error("Tenant handle belongs to a different Velocious configuration")
217
+ }
218
+ }
219
+
207
220
  /**
208
221
  * Returns the captured physical configuration for an active identifier.
209
222
  * @param {string} databaseIdentifier - Logical database identifier.
@@ -229,6 +242,58 @@ export default class TenantHandle {
229
242
  return await this._configuration.getFrontendTenantSqliteLifecycle().open(databaseIdentifier, this.databaseConfiguration(databaseIdentifier))
230
243
  }
231
244
 
245
+ /**
246
+ * Opens, migrates, and initializes record metadata for one captured physical
247
+ * tenant SQLite database and application schema generation.
248
+ * @param {object} options - Initialization options.
249
+ * @param {string} options.databaseIdentifier - Tenant-only logical database identifier.
250
+ * @param {import("../database/migrator/types.js").RequireMigrationContextType} options.migrations - Frontend migration require context.
251
+ * @param {string} options.schemaGeneration - Stable application schema generation.
252
+ * @returns {Promise<Readonly<TenantSqliteLifecycleSnapshot>>} - Ready lifecycle snapshot.
253
+ */
254
+ async initialize({databaseIdentifier, migrations, schemaGeneration}) {
255
+ if (!migrations || typeof migrations !== "function" || typeof migrations.keys !== "function") {
256
+ throw new TypeError("TenantHandle.initialize requires a migrations require context")
257
+ }
258
+
259
+ const databaseConfiguration = this.databaseConfiguration(databaseIdentifier)
260
+ const lifecycle = this._configuration.getFrontendTenantSqliteLifecycle()
261
+
262
+ return await lifecycle.initialize(databaseIdentifier, databaseConfiguration, schemaGeneration, async () => {
263
+ await this._databaseOperation({
264
+ databaseIdentifier,
265
+ name: `Initialize frontend tenant database: ${databaseIdentifier}`,
266
+ requireReady: false,
267
+ schemaGeneration
268
+ }, async (operation) => {
269
+ const migrator = new Migrator({configuration: this._configuration, databaseIdentifiers: [databaseIdentifier]})
270
+
271
+ operation.connection().clearSchemaCache()
272
+ await migrator.migrateRequireContextForDatabase({
273
+ databaseConfiguration,
274
+ databaseIdentifier,
275
+ db: operation.connection(),
276
+ requireContext: migrations
277
+ })
278
+ await this._configuration.initializeModels({type: "frontend-tenant"})
279
+
280
+ for (const modelClass of Object.values(this._configuration.getModelClasses())) {
281
+ if (modelClass.getDatabaseIdentifier({tenant: this._tenant}) !== databaseIdentifier) continue
282
+ const table = await operation.connection().getTableByName(modelClass.tableName(), {throwError: false})
283
+
284
+ if (!table && !modelClass.getEagerLoadRecordMetadata()) continue
285
+ if (Object.keys(modelClass.getTranslationsMap()).length > 0) {
286
+ const translationsTable = await operation.connection().getTableByName(modelClass.getTranslationsTableName(), {throwError: false})
287
+
288
+ if (!translationsTable && !modelClass.getEagerLoadRecordMetadata()) continue
289
+ }
290
+
291
+ await operation.ensureModelInitialized(modelClass)
292
+ }
293
+ })
294
+ })
295
+ }
296
+
232
297
  /**
233
298
  * Flushes this captured SQLite identity.
234
299
  * @param {{databaseIdentifier: string}} options - Lifecycle options.
@@ -290,13 +355,32 @@ export default class TenantHandle {
290
355
  * @returns {Promise<T>} - Callback result.
291
356
  */
292
357
  async databaseOperation({databaseIdentifier, name = "TenantHandle.databaseOperation"}, callback) {
358
+ return await this._databaseOperation({databaseIdentifier, name, requireReady: true}, callback)
359
+ }
360
+
361
+ /**
362
+ * Runs a captured operation with explicit readiness policy. Initialization is
363
+ * the only caller allowed to enter an unready schema generation.
364
+ * @template T
365
+ * @param {{databaseIdentifier: string, name: string, requireReady: boolean, schemaGeneration?: string}} options - Internal operation options.
366
+ * @param {(operation: import("../database/operation.js").default) => Promise<T>} callback - Operation callback.
367
+ * @returns {Promise<T>} - Callback result.
368
+ */
369
+ async _databaseOperation({databaseIdentifier, name, requireReady, schemaGeneration}, callback) {
293
370
  const databaseConfiguration = this.databaseConfiguration(databaseIdentifier)
294
- return await this._configuration.getFrontendTenantSqliteLifecycle().databaseOperation(databaseIdentifier, databaseConfiguration, async () => await this._configuration.withDatabaseOperation({
295
- databaseConfiguration,
371
+
372
+ return await this._configuration.getFrontendTenantSqliteLifecycle().databaseOperation(
296
373
  databaseIdentifier,
297
- name,
298
- tenant: this._tenant
299
- }, callback))
374
+ databaseConfiguration,
375
+ {requireReady, schemaGeneration},
376
+ async (operationSchemaGeneration) => await this._configuration.withDatabaseOperation({
377
+ databaseConfiguration,
378
+ databaseIdentifier,
379
+ name,
380
+ schemaGeneration: operationSchemaGeneration,
381
+ tenant: this._tenant
382
+ }, callback)
383
+ )
300
384
  }
301
385
 
302
386
  /**
@@ -3,6 +3,7 @@
3
3
  import SystemTestBrowserHelper from "system-testing/build/system-test-browser-helper.js"
4
4
  import BrowserEnvironmentHandler from "../environment-handlers/browser.js"
5
5
  import runFrontendModelEventHookScenario from "./browser-frontend-model-event-hook-scenarios.js"
6
+ import runUseDatabaseSelectionTransitionScenario from "./browser-use-database-hook-scenarios.js"
6
7
 
7
8
  const root = document.getElementById("root") || (() => {
8
9
  const element = document.createElement("div")
@@ -28,5 +29,6 @@ systemTestBrowserHelper.enableOnBrowser()
28
29
  Object.assign(globalThis, {velociousBrowserTest: {
29
30
  BrowserEnvironmentHandler,
30
31
  runFrontendModelEventHookScenario,
32
+ runUseDatabaseSelectionTransitionScenario,
31
33
  systemTestBrowserHelper
32
34
  }})
@@ -0,0 +1,204 @@
1
+ // @ts-check
2
+
3
+ import React, { act } from "react"
4
+ import {createRoot} from "react-dom/client"
5
+
6
+ import Configuration, {CurrentConfigurationNotSetError} from "../configuration.js"
7
+ import BrowserEnvironmentHandler from "../environment-handlers/browser.js"
8
+ import Migration from "../database/migration/index.js"
9
+ import SingleMultiUsePool from "../database/pool/single-multi-use.js"
10
+ import SqliteWebDriver from "../database/drivers/sqlite/index.web.js"
11
+ import TenantHandle from "../tenants/tenant-handle.js"
12
+ import useDatabase from "../database/use-database.js"
13
+
14
+ /**
15
+ * Returns the current configuration without requiring one to exist.
16
+ * @returns {Configuration | undefined} - Current configuration when installed.
17
+ */
18
+ function currentConfigurationOrUndefined() {
19
+ try {
20
+ return Configuration.current()
21
+ } catch (error) {
22
+ if (!(error instanceof CurrentConfigurationNotSetError)) throw error
23
+
24
+ return undefined
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Builds the scoped browser configuration used by the real hook scenario.
30
+ * @returns {Configuration} - Browser test configuration.
31
+ */
32
+ function buildHookConfiguration() {
33
+ return new Configuration({
34
+ database: {
35
+ test: {
36
+ projectTenant: {
37
+ driver: SqliteWebDriver,
38
+ getConnection: async () => { throw new Error("Hook scenario must not open a physical database") },
39
+ migrations: true,
40
+ name: "use-database-hook-template",
41
+ poolType: SingleMultiUsePool,
42
+ tenantOnly: true,
43
+ type: "sqlite"
44
+ }
45
+ }
46
+ },
47
+ directory: "/use-database-hook-scenario",
48
+ environment: "test",
49
+ environmentHandler: new BrowserEnvironmentHandler(),
50
+ initializeModels: async () => {},
51
+ locale: "en",
52
+ localeFallbacks: {en: ["en"]},
53
+ locales: ["en"],
54
+ tenantDatabaseResolver: ({identifier, tenant}) => {
55
+ if (identifier !== "projectTenant" || !tenant || typeof tenant !== "object") return
56
+
57
+ return {name: `use-database-hook-${String(/** @type {{slug?: string}} */ (tenant).slug)}`}
58
+ }
59
+ })
60
+ }
61
+
62
+ /**
63
+ * Flushes real React hook transitions and reports synchronous selection state,
64
+ * inline loader stability, and stale completion suppression.
65
+ * @returns {Promise<{firstChangedLoaded: boolean, initialLoaded: boolean, loaderCalls: string[], loadedAfterInlineLoaderRerender: boolean, thirdAfterStaleCompletion: {error: string | null, loaded: boolean}}>} - Render observations.
66
+ */
67
+ export default async function runUseDatabaseSelectionTransitionScenario() {
68
+ const container = document.createElement("div")
69
+ document.body.appendChild(container)
70
+ const root = createRoot(container)
71
+ /** @type {{error: string | null, loaded: boolean, selection: "first" | "second" | "third"}[]} */
72
+ const renders = []
73
+ /** @type {string[]} */
74
+ const loaderCalls = []
75
+ /**
76
+ * Releases the deliberately stale second initialization.
77
+ * @type {() => void}
78
+ */
79
+ let releaseSecondInitialization = () => {}
80
+ const secondInitializationRelease = new Promise((resolve) => { releaseSecondInitialization = () => resolve(undefined) })
81
+ /**
82
+ * Signals that the deliberately stale second initialization started.
83
+ * @type {() => void}
84
+ */
85
+ let signalSecondInitialization = () => {}
86
+ const secondInitializationStarted = new Promise((resolve) => { signalSecondInitialization = () => resolve(undefined) })
87
+
88
+ class HookTenantHandle extends TenantHandle {
89
+ /**
90
+ * Runs test initialization without touching a physical database.
91
+ * @param {{migrations: import("../database/migrator/types.js").RequireMigrationContextType}} args - Hook initialization arguments.
92
+ * @returns {Promise<Readonly<ReturnType<TenantHandle["inspect"]>>>} - Ready test snapshot.
93
+ */
94
+ async initialize({migrations}) {
95
+ loaderCalls.push(migrations.id)
96
+
97
+ if (this.tenant().slug === "second") {
98
+ signalSecondInitialization()
99
+ await secondInitializationRelease
100
+ throw new Error("SECOND_SELECTION_FAILED")
101
+ }
102
+
103
+ return Object.freeze({databaseIdentifier: "projectTenant", dirty: false, lastUsed: 0, pinCount: 0, ready: true, schemaGeneration: "generation-1", state: "open"})
104
+ }
105
+ }
106
+ const previousConfiguration = currentConfigurationOrUndefined()
107
+ const restorationConfiguration = previousConfiguration || buildHookConfiguration()
108
+ const configuration = buildHookConfiguration()
109
+
110
+ configuration.setCurrent()
111
+
112
+ const firstHandle = new HookTenantHandle({configuration, tenant: {slug: "first"}})
113
+ const secondHandle = new HookTenantHandle({configuration, tenant: {slug: "second"}})
114
+ const thirdHandle = new HookTenantHandle({configuration, tenant: {slug: "third"}})
115
+ const renderCounts = {first: 0, second: 0, third: 0}
116
+
117
+ /**
118
+ * Runs hook probe.
119
+ * @param {{selection: "first" | "second" | "third", tenantHandle: HookTenantHandle}} props - Probe props.
120
+ * @returns {React.ReactElement} - Empty host element.
121
+ */
122
+ function HookProbe({selection, tenantHandle}) {
123
+ const state = useDatabase({
124
+ databaseIdentifier: "projectTenant",
125
+ migrationsRequireContextCallback: async () => {
126
+ /**
127
+ * Looks up an empty migration context entry recreated by every probe render.
128
+ * @param {string} fileName - Requested migration file.
129
+ * @returns {{default: typeof Migration}} - Placeholder migration module.
130
+ * @type {import("../database/migrator/types.js").RequireMigrationContextType}
131
+ */
132
+ const emptyMigrations = (fileName) => {
133
+ if (!fileName) throw new Error("Migration context lookup requires a file name")
134
+
135
+ return {default: Migration}
136
+ }
137
+
138
+ emptyMigrations.keys = () => []
139
+ emptyMigrations.id = selection
140
+
141
+ return emptyMigrations
142
+ },
143
+ schemaGeneration: "generation-1",
144
+ tenantHandle
145
+ })
146
+
147
+ renderCounts[selection]++
148
+ if (renderCounts[selection] > 20) throw new Error(`useDatabase render loop for ${selection}`)
149
+ renders.push({error: state.error?.message || null, loaded: state.loaded, selection})
150
+
151
+ return React.createElement("div")
152
+ }
153
+
154
+ try {
155
+ await act(async () => {
156
+ root.render(React.createElement(HookProbe, {selection: "first", tenantHandle: firstHandle}))
157
+ })
158
+
159
+ const initialRender = renders.at(-1)
160
+
161
+ await act(async () => {
162
+ root.render(React.createElement(HookProbe, {selection: "first", tenantHandle: firstHandle}))
163
+ })
164
+
165
+ const loadedAfterInlineLoaderRerender = renders.at(-1)?.loaded
166
+
167
+ act(() => {
168
+ root.render(React.createElement(HookProbe, {selection: "second", tenantHandle: secondHandle}))
169
+ })
170
+
171
+ const firstChangedRender = renders.find((render) => render.selection === "second")
172
+ await secondInitializationStarted
173
+
174
+ await act(async () => {
175
+ root.render(React.createElement(HookProbe, {selection: "third", tenantHandle: thirdHandle}))
176
+ })
177
+
178
+ releaseSecondInitialization()
179
+ await act(async () => { await secondInitializationRelease })
180
+
181
+ const thirdAfterStaleCompletion = renders.at(-1)
182
+
183
+ if (!initialRender || !firstChangedRender || !thirdAfterStaleCompletion || loadedAfterInlineLoaderRerender === undefined) {
184
+ throw new Error("useDatabase hook scenario did not render every selection")
185
+ }
186
+
187
+ return {
188
+ firstChangedLoaded: firstChangedRender.loaded,
189
+ initialLoaded: initialRender.loaded,
190
+ loaderCalls,
191
+ loadedAfterInlineLoaderRerender,
192
+ thirdAfterStaleCompletion: {
193
+ error: thirdAfterStaleCompletion.error,
194
+ loaded: thirdAfterStaleCompletion.loaded
195
+ }
196
+ }
197
+ } finally {
198
+ releaseSecondInitialization()
199
+ await act(async () => { root.unmount() })
200
+ container.remove()
201
+ restorationConfiguration.setCurrent()
202
+ await configuration.closeDatabaseConnections()
203
+ }
204
+ }
@@ -1456,14 +1456,15 @@ export default class TestRunner {
1456
1456
  * @type {Record<ConsoleMethodName, (...args: Array<ReturnType<typeof JSON.parse>>) => void>} */
1457
1457
  const consoleObject = /** @type {Record<ConsoleMethodName, (...args: Array<ReturnType<typeof JSON.parse>>) => void>} */ (console)
1458
1458
  /**
1459
- * Original console methods.
1459
+ * Original console methods captured as direct references so stopping restores
1460
+ * the exact method that was installed at capture start.
1460
1461
  * @type {Record<ConsoleMethodName, (...args: Array<ReturnType<typeof JSON.parse>>) => void>} */
1461
1462
  const originalConsoleMethods = {
1462
- debug: consoleObject.debug.bind(console),
1463
- error: consoleObject.error.bind(console),
1464
- info: consoleObject.info.bind(console),
1465
- log: consoleObject.log.bind(console),
1466
- warn: consoleObject.warn.bind(console)
1463
+ debug: consoleObject.debug,
1464
+ error: consoleObject.error,
1465
+ info: consoleObject.info,
1466
+ log: consoleObject.log,
1467
+ warn: consoleObject.warn
1467
1468
  }
1468
1469
  let stopped = false
1469
1470
  let outputText = ""
@@ -1473,7 +1474,7 @@ export default class TestRunner {
1473
1474
  lines.push(`[${new Date().toISOString()}] [${methodName}] ${format(...args)}`)
1474
1475
 
1475
1476
  if (passthrough) {
1476
- originalConsoleMethods[methodName](...args)
1477
+ originalConsoleMethods[methodName].apply(consoleObject, args)
1477
1478
  }
1478
1479
  }
1479
1480
  }
@@ -15,17 +15,20 @@ const MODEL_SCOPE_DESCRIPTOR_MARKER = "velociousModelScopeDescriptor"
15
15
  * @param {object} args - Definition arguments.
16
16
  * @param {(...args: Array<ReturnType<typeof JSON.parse>>) => ReturnType<typeof JSON.parse>} args.callback - Scope callback.
17
17
  * @param {typeof import("../database/record/index.js").default | import("../frontend-models/base.js").FrontendModelClass} args.modelClass - Owning model class.
18
- * @param {() => ReturnType<typeof JSON.parse>} args.startQuery - Factory that returns a fresh query for the owning model class.
18
+ * @param {(modelClass?: typeof import("../database/record/index.js").default | import("../frontend-models/base.js").FrontendModelClass) => ReturnType<typeof JSON.parse>} args.startQuery - Factory that returns a fresh query for the invoked model class.
19
19
  * @returns {((...args: Array<ReturnType<typeof JSON.parse>>) => ReturnType<typeof JSON.parse>) & {scope: (...args: Array<ReturnType<typeof JSON.parse>>) => ModelScopeDescriptor}} - Scope helper.
20
20
  */
21
21
  export function defineModelScope({callback, modelClass, startQuery}) {
22
22
  /**
23
23
  * Runs defined scope.
24
+ * @this {typeof import("../database/record/index.js").default | import("../frontend-models/base.js").FrontendModelClass | undefined}
24
25
  * @param {...ReturnType<typeof JSON.parse>} scopeArgs - Scope arguments.
25
26
  * @returns {ReturnType<typeof JSON.parse>} - Scoped root query.
26
27
  */
27
28
  function definedScope(...scopeArgs) {
28
- return startQuery().scope(definedScope.scope(...scopeArgs))
29
+ const invokedModelClass = typeof this === "function" ? this : modelClass
30
+
31
+ return startQuery(invokedModelClass).scope(definedScope.scope(...scopeArgs))
29
32
  }
30
33
 
31
34
  /**
@@ -572,9 +572,12 @@ function backendRelationshipEntries(modelClass) {
572
572
 
573
573
  if (typeof relationship.isPolymorphic === "function" && relationship.isPolymorphic()) continue
574
574
 
575
- const targetModelClass = relationship.getTargetModelClass()
575
+ const rawTargetModelClass = relationship.getTargetModelClass()
576
576
 
577
- if (!targetModelClass) continue
577
+ if (!rawTargetModelClass) continue
578
+
579
+ const targetModelClass = /** @type {typeof import("../database/record/index.js").default} */ (modelClass)
580
+ .bindRecordMetadataModelClass(rawTargetModelClass)
578
581
 
579
582
  entries[relationshipName] = {targetModelClass}
580
583
  }