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.
- package/README.md +3 -2
- package/build/configuration.js +109 -14
- package/build/database/drivers/base.js +30 -0
- package/build/database/migrator.js +90 -24
- package/build/database/operation.js +55 -4
- package/build/database/query/join-object.js +7 -4
- package/build/database/query/model-class-query.js +38 -7
- package/build/database/query/preloader/belongs-to.js +9 -6
- package/build/database/query/preloader/has-many.js +25 -14
- package/build/database/query/preloader/has-one.js +7 -4
- package/build/database/query/preloader/query-for-model.js +16 -1
- package/build/database/query/preloader.js +6 -4
- package/build/database/query/query-data.js +3 -3
- package/build/database/query/where-model-class-hash.js +8 -4
- package/build/database/query/with-count.js +4 -3
- package/build/database/record/index.js +148 -5
- package/build/database/record/instance-relationships/base.js +13 -0
- package/build/database/record/instance-relationships/belongs-to.js +1 -1
- package/build/database/record/instance-relationships/has-many.js +5 -2
- package/build/database/record/instance-relationships/has-one.js +1 -1
- package/build/database/record/relationships/base.js +17 -0
- package/build/database/use-database.js +81 -28
- package/build/src/configuration.d.ts +31 -4
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +88 -17
- package/build/src/database/drivers/base.d.ts +11 -0
- package/build/src/database/drivers/base.d.ts.map +1 -1
- package/build/src/database/drivers/base.js +28 -1
- package/build/src/database/migrator.d.ts +34 -0
- package/build/src/database/migrator.d.ts.map +1 -1
- package/build/src/database/migrator.js +83 -23
- package/build/src/database/operation.d.ts +15 -1
- package/build/src/database/operation.d.ts.map +1 -1
- package/build/src/database/operation.js +52 -5
- package/build/src/database/query/join-object.d.ts.map +1 -1
- package/build/src/database/query/join-object.js +7 -5
- package/build/src/database/query/model-class-query.d.ts +6 -0
- package/build/src/database/query/model-class-query.d.ts.map +1 -1
- package/build/src/database/query/model-class-query.js +33 -8
- package/build/src/database/query/preloader/belongs-to.d.ts.map +1 -1
- package/build/src/database/query/preloader/belongs-to.js +9 -7
- package/build/src/database/query/preloader/has-many.d.ts.map +1 -1
- package/build/src/database/query/preloader/has-many.js +23 -16
- package/build/src/database/query/preloader/has-one.d.ts.map +1 -1
- package/build/src/database/query/preloader/has-one.js +7 -5
- package/build/src/database/query/preloader/query-for-model.d.ts +8 -0
- package/build/src/database/query/preloader/query-for-model.d.ts.map +1 -1
- package/build/src/database/query/preloader/query-for-model.js +15 -2
- package/build/src/database/query/preloader.d.ts.map +1 -1
- package/build/src/database/query/preloader.js +6 -5
- package/build/src/database/query/query-data.js +4 -4
- package/build/src/database/query/where-model-class-hash.d.ts.map +1 -1
- package/build/src/database/query/where-model-class-hash.js +7 -5
- package/build/src/database/query/with-count.js +5 -4
- package/build/src/database/record/index.d.ts +95 -0
- package/build/src/database/record/index.d.ts.map +1 -1
- package/build/src/database/record/index.js +126 -6
- package/build/src/database/record/instance-relationships/base.d.ts +6 -0
- package/build/src/database/record/instance-relationships/base.d.ts.map +1 -1
- package/build/src/database/record/instance-relationships/base.js +12 -1
- package/build/src/database/record/instance-relationships/belongs-to.js +2 -2
- package/build/src/database/record/instance-relationships/has-many.d.ts.map +1 -1
- package/build/src/database/record/instance-relationships/has-many.js +6 -3
- package/build/src/database/record/instance-relationships/has-one.js +2 -2
- package/build/src/database/record/relationships/base.d.ts +11 -0
- package/build/src/database/record/relationships/base.d.ts.map +1 -1
- package/build/src/database/record/relationships/base.js +15 -1
- package/build/src/database/use-database.d.ts +33 -5
- package/build/src/database/use-database.d.ts.map +1 -1
- package/build/src/database/use-database.js +81 -30
- package/build/src/sync/server-change-feed.d.ts +14 -2
- package/build/src/sync/server-change-feed.d.ts.map +1 -1
- package/build/src/sync/server-change-feed.js +50 -10
- package/build/src/sync/sync-scope-store.d.ts +13 -2
- package/build/src/sync/sync-scope-store.d.ts.map +1 -1
- package/build/src/sync/sync-scope-store.js +48 -7
- package/build/src/tenants/frontend-tenant-sqlite-lifecycle.d.ts +43 -1
- package/build/src/tenants/frontend-tenant-sqlite-lifecycle.d.ts.map +1 -1
- package/build/src/tenants/frontend-tenant-sqlite-lifecycle.js +141 -17
- package/build/src/tenants/tenant-handle.d.ts +36 -0
- package/build/src/tenants/tenant-handle.d.ts.map +1 -1
- package/build/src/tenants/tenant-handle.js +73 -3
- package/build/src/testing/browser-test-app.js +3 -1
- package/build/src/testing/browser-use-database-hook-scenarios.d.ts +16 -0
- package/build/src/testing/browser-use-database-hook-scenarios.d.ts.map +1 -0
- package/build/src/testing/browser-use-database-hook-scenarios.js +182 -0
- package/build/src/testing/test-runner.d.ts.map +1 -1
- package/build/src/testing/test-runner.js +9 -8
- package/build/src/utils/model-scope.d.ts +2 -2
- package/build/src/utils/model-scope.d.ts.map +1 -1
- package/build/src/utils/model-scope.js +5 -3
- package/build/src/utils/ransack.d.ts.map +1 -1
- package/build/src/utils/ransack.js +5 -3
- package/build/sync/server-change-feed.js +58 -8
- package/build/sync/sync-scope-store.js +55 -5
- package/build/tenants/frontend-tenant-sqlite-lifecycle.js +150 -15
- package/build/tenants/tenant-handle.js +90 -6
- package/build/testing/browser-test-app.js +2 -0
- package/build/testing/browser-use-database-hook-scenarios.js +204 -0
- package/build/testing/test-runner.js +8 -7
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/utils/model-scope.js +5 -2
- package/build/utils/ransack.js +5 -2
- package/package.json +2 -1
- package/scripts/browser-test-session.js +60 -0
- package/scripts/test-browser.js +7 -7
- package/src/configuration.js +109 -14
- package/src/database/drivers/base.js +30 -0
- package/src/database/migrator.js +90 -24
- package/src/database/operation.js +55 -4
- package/src/database/query/join-object.js +7 -4
- package/src/database/query/model-class-query.js +38 -7
- package/src/database/query/preloader/belongs-to.js +9 -6
- package/src/database/query/preloader/has-many.js +25 -14
- package/src/database/query/preloader/has-one.js +7 -4
- package/src/database/query/preloader/query-for-model.js +16 -1
- package/src/database/query/preloader.js +6 -4
- package/src/database/query/query-data.js +3 -3
- package/src/database/query/where-model-class-hash.js +8 -4
- package/src/database/query/with-count.js +4 -3
- package/src/database/record/index.js +148 -5
- package/src/database/record/instance-relationships/base.js +13 -0
- package/src/database/record/instance-relationships/belongs-to.js +1 -1
- package/src/database/record/instance-relationships/has-many.js +5 -2
- package/src/database/record/instance-relationships/has-one.js +1 -1
- package/src/database/record/relationships/base.js +17 -0
- package/src/database/use-database.js +81 -28
- package/src/sync/server-change-feed.js +58 -8
- package/src/sync/sync-scope-store.js +55 -5
- package/src/tenants/frontend-tenant-sqlite-lifecycle.js +150 -15
- package/src/tenants/tenant-handle.js +90 -6
- package/src/testing/browser-test-app.js +2 -0
- package/src/testing/browser-use-database-hook-scenarios.js +204 -0
- package/src/testing/test-runner.js +8 -7
- package/src/utils/model-scope.js +5 -2
- 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
|
-
|
|
295
|
-
|
|
371
|
+
|
|
372
|
+
return await this._configuration.getFrontendTenantSqliteLifecycle().databaseOperation(
|
|
296
373
|
databaseIdentifier,
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
|
1463
|
-
error: consoleObject.error
|
|
1464
|
-
info: consoleObject.info
|
|
1465
|
-
log: consoleObject.log
|
|
1466
|
-
warn: consoleObject.warn
|
|
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](
|
|
1477
|
+
originalConsoleMethods[methodName].apply(consoleObject, args)
|
|
1477
1478
|
}
|
|
1478
1479
|
}
|
|
1479
1480
|
}
|
package/src/utils/model-scope.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
29
|
+
const invokedModelClass = typeof this === "function" ? this : modelClass
|
|
30
|
+
|
|
31
|
+
return startQuery(invokedModelClass).scope(definedScope.scope(...scopeArgs))
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
/**
|
package/src/utils/ransack.js
CHANGED
|
@@ -572,9 +572,12 @@ function backendRelationshipEntries(modelClass) {
|
|
|
572
572
|
|
|
573
573
|
if (typeof relationship.isPolymorphic === "function" && relationship.isPolymorphic()) continue
|
|
574
574
|
|
|
575
|
-
const
|
|
575
|
+
const rawTargetModelClass = relationship.getTargetModelClass()
|
|
576
576
|
|
|
577
|
-
if (!
|
|
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
|
}
|