velocious 1.0.593 → 1.0.594
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 +68 -13
- 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 +13 -4
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +56 -16
- 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/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/tsconfig.tsbuildinfo +1 -1
- package/build/utils/model-scope.js +5 -2
- package/build/utils/ransack.js +5 -2
- package/package.json +1 -1
- package/scripts/test-browser.js +5 -1
- package/src/configuration.js +68 -13
- 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/utils/model-scope.js +5 -2
- package/src/utils/ransack.js +5 -2
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* Self-sustaining sync feeds: upstream imports triggered by the changes pull itself, with framework-owned coalescing and throttling (see [docs/sync-upstream-imports.md](docs/sync-upstream-imports.md))
|
|
21
21
|
* AwesomeTasks-shaped offline sync proof using routed resources, domain commands, signed offline grants, and peer-forwarded mutations (see the [developer guide](docs/shared-resource-sync-guide.md) and [proof](docs/awesome-tasks-offline-sync-proof.md))
|
|
22
22
|
* SQLite web persistence that automatically prefers OPFS, then IndexedDB, and migrates legacy persisted bytes when possible (see [docs/sqlite-web-persistence.md](docs/sqlite-web-persistence.md))
|
|
23
|
-
* Bounded frontend tenant SQLite handles with durable flush/close, backend-complete deletion, clean-only LRU eviction, and scoped pins (see [docs/frontend-tenant-sqlite-lifecycle.md](docs/frontend-tenant-sqlite-lifecycle.md))
|
|
23
|
+
* Bounded frontend tenant SQLite handles with independently deduplicated per-database migrations/model readiness, React lifecycle integration, durable flush/close, backend-complete deletion, clean-only LRU eviction, and scoped pins (see [docs/frontend-tenant-sqlite-lifecycle.md](docs/frontend-tenant-sqlite-lifecycle.md))
|
|
24
24
|
* Expo / Metro compatibility guidance and a real Expo export check (see [docs/expo-metro-compatibility.md](docs/expo-metro-compatibility.md))
|
|
25
25
|
* Gap-less positional lists with automatic reordering via `actsAsList`, including models with numeric, string, or UUID primary keys (see [docs/acts-as-list.md](docs/acts-as-list.md))
|
|
26
26
|
* Rails-style nested-attribute writes on frontend-model `save()` (see [docs/nested-attributes.md](docs/nested-attributes.md))
|
|
@@ -955,7 +955,8 @@ Translated models also get a `currentTranslation` `hasOne` relationship scoped t
|
|
|
955
955
|
Async class APIs initialize record metadata on first use when a model has not
|
|
956
956
|
already been initialized eagerly. See [docs/model-initialization.md](docs/model-initialization.md)
|
|
957
957
|
for the eager and lazy initialization behavior, including atomic shared bootstrap
|
|
958
|
-
and complete recovery after an eager initialization failure
|
|
958
|
+
and complete recovery after an eager initialization failure or database-connection
|
|
959
|
+
closure without overlapping stale and current bootstrap side effects.
|
|
959
960
|
|
|
960
961
|
## Lifecycle callbacks
|
|
961
962
|
|
package/build/configuration.js
CHANGED
|
@@ -302,8 +302,9 @@ export default class VelociousConfiguration {
|
|
|
302
302
|
*/
|
|
303
303
|
this._initializeModelsPromise = undefined
|
|
304
304
|
/**
|
|
305
|
-
*
|
|
306
|
-
*
|
|
305
|
+
* Current `initialize()` promise, memoized so concurrent callers await the
|
|
306
|
+
* same bootstrap. Retained across a connection close until stale bootstrap
|
|
307
|
+
* work settles, then cleared by identity before the new generation retries.
|
|
307
308
|
* @type {Promise<void> | undefined}
|
|
308
309
|
*/
|
|
309
310
|
this._initializePromise = undefined
|
|
@@ -1102,6 +1103,18 @@ export default class VelociousConfiguration {
|
|
|
1102
1103
|
}
|
|
1103
1104
|
}
|
|
1104
1105
|
|
|
1106
|
+
/**
|
|
1107
|
+
* Invalidates record metadata owned by one closed/deleted physical tenant
|
|
1108
|
+
* database while preserving every other tenant generation.
|
|
1109
|
+
* @param {string} databaseIdentity - Logical identifier plus pool reuse key.
|
|
1110
|
+
* @returns {void}
|
|
1111
|
+
*/
|
|
1112
|
+
clearRecordMetadataForDatabaseIdentity(databaseIdentity) {
|
|
1113
|
+
for (const modelClass of Object.values(this.modelClasses)) {
|
|
1114
|
+
modelClass.clearRecordMetadataValuesForDatabaseIdentity(databaseIdentity)
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1105
1118
|
/**
|
|
1106
1119
|
* Runs get database pool type.
|
|
1107
1120
|
* @param {string} identifier - Identifier.
|
|
@@ -2054,10 +2067,25 @@ export default class VelociousConfiguration {
|
|
|
2054
2067
|
* @returns {Promise<void>} - Resolves when complete.
|
|
2055
2068
|
*/
|
|
2056
2069
|
async initializeModels(args = {type: "server"}) {
|
|
2070
|
+
const modelInitializationGeneration = this._modelInitializationGeneration
|
|
2071
|
+
|
|
2057
2072
|
if (this._modelsInitialized) return
|
|
2058
|
-
if (this._initializeModelsPromise)
|
|
2073
|
+
if (this._initializeModelsPromise) {
|
|
2074
|
+
const initializeModelsPromise = this._initializeModelsPromise
|
|
2075
|
+
|
|
2076
|
+
await initializeModelsPromise
|
|
2077
|
+
|
|
2078
|
+
if (this._modelInitializationGeneration === modelInitializationGeneration && !this._modelsInitialized) {
|
|
2079
|
+
if (this._initializeModelsPromise === initializeModelsPromise) {
|
|
2080
|
+
this._initializeModelsPromise = undefined
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
return await this.initializeModels(args)
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
return
|
|
2087
|
+
}
|
|
2059
2088
|
|
|
2060
|
-
const modelInitializationGeneration = this._modelInitializationGeneration
|
|
2061
2089
|
const initializeModelsPromise = (async () => {
|
|
2062
2090
|
const shouldSkipDummyModelInitialization = process.env.VELOCIOUS_SKIP_DUMMY_MODEL_INITIALIZATION === "1"
|
|
2063
2091
|
&& process.env.VELOCIOUS_BROWSER_TESTS === "true"
|
|
@@ -2110,6 +2138,12 @@ export default class VelociousConfiguration {
|
|
|
2110
2138
|
* @returns {Promise<void>} - Resolves when complete.
|
|
2111
2139
|
*/
|
|
2112
2140
|
async initialize({type} = {type: "undefined"}) {
|
|
2141
|
+
if (this._closeDatabaseConnectionsPromise) {
|
|
2142
|
+
await this._closeDatabaseConnectionsPromise
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
const initializationGeneration = this._modelInitializationGeneration
|
|
2146
|
+
|
|
2113
2147
|
if (this._isInitialized) return
|
|
2114
2148
|
// Memoize the in-progress initialization so concurrent callers await the same
|
|
2115
2149
|
// bootstrap instead of racing. `_isInitialized` was previously set to `true`
|
|
@@ -2117,15 +2151,29 @@ export default class VelociousConfiguration {
|
|
|
2117
2151
|
// `pooledRunnerConcurrency > 1` starting several jobs on a cold child) could
|
|
2118
2152
|
// skip initialization and load models / perform a job while the first call
|
|
2119
2153
|
// was still awaiting model discovery and initializers. Mirrors connectBeacon.
|
|
2120
|
-
if (this._initializePromise)
|
|
2154
|
+
if (this._initializePromise) {
|
|
2155
|
+
const initializePromise = this._initializePromise
|
|
2121
2156
|
|
|
2122
|
-
|
|
2157
|
+
await initializePromise
|
|
2158
|
+
|
|
2159
|
+
if (this._modelInitializationGeneration === initializationGeneration && !this._isInitialized) {
|
|
2160
|
+
if (this._initializePromise === initializePromise) {
|
|
2161
|
+
this._initializePromise = undefined
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
return await this.initialize({type})
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
return
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
const initializePromise = (async () => {
|
|
2123
2171
|
await this.initializeModels({type})
|
|
2124
2172
|
|
|
2125
2173
|
// Model initialization can be invalidated by a concurrent connection close.
|
|
2126
2174
|
// If models are not ready, stop without marking the configuration initialized
|
|
2127
2175
|
// so the next caller retries a full bootstrap.
|
|
2128
|
-
if (!this._modelsInitialized) return
|
|
2176
|
+
if (this._modelInitializationGeneration !== initializationGeneration || !this._modelsInitialized) return
|
|
2129
2177
|
|
|
2130
2178
|
await this.getEnvironmentHandler().autoDiscoverResources(this)
|
|
2131
2179
|
this._mergeDiscoveredAbilityResources()
|
|
@@ -2147,22 +2195,28 @@ export default class VelociousConfiguration {
|
|
|
2147
2195
|
}
|
|
2148
2196
|
}
|
|
2149
2197
|
|
|
2150
|
-
this.
|
|
2198
|
+
if (this._modelInitializationGeneration === initializationGeneration) {
|
|
2199
|
+
this._isInitialized = true
|
|
2200
|
+
}
|
|
2151
2201
|
})()
|
|
2152
2202
|
|
|
2203
|
+
this._initializePromise = initializePromise
|
|
2204
|
+
|
|
2153
2205
|
try {
|
|
2154
|
-
await
|
|
2206
|
+
await initializePromise
|
|
2155
2207
|
} catch (error) {
|
|
2156
2208
|
// Let a later call retry a failed initialization instead of every future
|
|
2157
2209
|
// caller awaiting the same cached rejection.
|
|
2158
|
-
this._initializePromise
|
|
2210
|
+
if (this._initializePromise === initializePromise) {
|
|
2211
|
+
this._initializePromise = undefined
|
|
2212
|
+
}
|
|
2159
2213
|
throw error
|
|
2160
2214
|
}
|
|
2161
2215
|
|
|
2162
2216
|
// If the inner IIFE returned without marking the configuration initialized
|
|
2163
2217
|
// (e.g. because models were invalidated mid-bootstrap), clear the promise so
|
|
2164
2218
|
// a later call retries a full bootstrap.
|
|
2165
|
-
if (!this._isInitialized) {
|
|
2219
|
+
if (!this._isInitialized && this._initializePromise === initializePromise) {
|
|
2166
2220
|
this._initializePromise = undefined
|
|
2167
2221
|
}
|
|
2168
2222
|
}
|
|
@@ -3072,11 +3126,11 @@ export default class VelociousConfiguration {
|
|
|
3072
3126
|
* database configuration. No ambient tenant value is read during checkout or
|
|
3073
3127
|
* execution.
|
|
3074
3128
|
* @template T
|
|
3075
|
-
* @param {{databaseConfiguration: import("./configuration-types.js").DatabaseConfigurationType, databaseIdentifier: string, name?: string, tenant?: object}} options - Captured operation options.
|
|
3129
|
+
* @param {{databaseConfiguration: import("./configuration-types.js").DatabaseConfigurationType, databaseIdentifier: string, name?: string, schemaGeneration?: string, tenant?: object}} options - Captured operation options.
|
|
3076
3130
|
* @param {(operation: DatabaseOperation) => Promise<T>} callback - Operation callback.
|
|
3077
3131
|
* @returns {Promise<T>} - Callback result.
|
|
3078
3132
|
*/
|
|
3079
|
-
async withDatabaseOperation({databaseConfiguration, databaseIdentifier, name = "Configuration.withDatabaseOperation", tenant, ...restArgs}, callback) {
|
|
3133
|
+
async withDatabaseOperation({databaseConfiguration, databaseIdentifier, name = "Configuration.withDatabaseOperation", schemaGeneration, tenant, ...restArgs}, callback) {
|
|
3080
3134
|
restArgsError(restArgs)
|
|
3081
3135
|
|
|
3082
3136
|
if (!databaseIdentifier) throw new Error("Configuration.withDatabaseOperation requires a databaseIdentifier")
|
|
@@ -3095,6 +3149,7 @@ export default class VelociousConfiguration {
|
|
|
3095
3149
|
databaseIdentifier,
|
|
3096
3150
|
enforceCurrentTenantReuseKey: false,
|
|
3097
3151
|
owner,
|
|
3152
|
+
schemaGeneration,
|
|
3098
3153
|
tenant
|
|
3099
3154
|
})
|
|
3100
3155
|
|
|
@@ -195,6 +195,10 @@ export default class VelociousDatabaseDriversBase {
|
|
|
195
195
|
* Narrows the runtime value to the documented type.
|
|
196
196
|
* @type {TransactionCallbackFrame[]} */
|
|
197
197
|
_transactionCallbackFrames
|
|
198
|
+
/** @type {Promise<void>} */
|
|
199
|
+
_transactionCompletionPromise
|
|
200
|
+
/** @type {(() => void) | undefined} */
|
|
201
|
+
_resolveTransactionCompletion
|
|
198
202
|
/**
|
|
199
203
|
* Narrows the runtime value to the documented type.
|
|
200
204
|
* @type {Map<string, Promise<ReturnType<typeof JSON.parse>>>} */
|
|
@@ -231,6 +235,8 @@ export default class VelociousDatabaseDriversBase {
|
|
|
231
235
|
this.logger = new Logger(this)
|
|
232
236
|
this._transactionCallbackFrames = []
|
|
233
237
|
this._transactionsCount = 0
|
|
238
|
+
this._transactionCompletionPromise = Promise.resolve()
|
|
239
|
+
this._resolveTransactionCompletion = undefined
|
|
234
240
|
this._transactionsActionsMutex = new Mutex()
|
|
235
241
|
this._schemaCache = new Map()
|
|
236
242
|
}
|
|
@@ -1426,6 +1432,12 @@ export default class VelociousDatabaseDriversBase {
|
|
|
1426
1432
|
*/
|
|
1427
1433
|
insideTransaction() { return this._transactionsCount > 0 }
|
|
1428
1434
|
|
|
1435
|
+
/**
|
|
1436
|
+
* Returns the completion promise identifying the current outer transaction.
|
|
1437
|
+
* @returns {Promise<void>} Resolves after that transaction commits or rolls back.
|
|
1438
|
+
*/
|
|
1439
|
+
transactionCompletion() { return this._transactionCompletionPromise }
|
|
1440
|
+
|
|
1429
1441
|
/**
|
|
1430
1442
|
* Runs start transaction.
|
|
1431
1443
|
* @param {Pick<QueryOptions, "operationOwner">} [options] - Transaction ownership.
|
|
@@ -1446,6 +1458,12 @@ export default class VelociousDatabaseDriversBase {
|
|
|
1446
1458
|
|
|
1447
1459
|
await this._startTransactionAction(options)
|
|
1448
1460
|
this._transactionsCount++
|
|
1461
|
+
|
|
1462
|
+
if (this._transactionsCount === 1) {
|
|
1463
|
+
this._transactionCompletionPromise = new Promise((resolve) => {
|
|
1464
|
+
this._resolveTransactionCompletion = resolve
|
|
1465
|
+
})
|
|
1466
|
+
}
|
|
1449
1467
|
})
|
|
1450
1468
|
|
|
1451
1469
|
if (!blockingOperationLease) return
|
|
@@ -1472,9 +1490,20 @@ export default class VelociousDatabaseDriversBase {
|
|
|
1472
1490
|
await this._transactionsActionsMutex.sync(async () => {
|
|
1473
1491
|
await this._commitTransactionAction(options)
|
|
1474
1492
|
this._transactionsCount--
|
|
1493
|
+
this._resolveCompletedTransaction()
|
|
1475
1494
|
})
|
|
1476
1495
|
}
|
|
1477
1496
|
|
|
1497
|
+
/** Resolves the current outer transaction completion when it has finished. */
|
|
1498
|
+
_resolveCompletedTransaction() {
|
|
1499
|
+
if (this._transactionsCount !== 0) return
|
|
1500
|
+
|
|
1501
|
+
const resolve = this._resolveTransactionCompletion
|
|
1502
|
+
|
|
1503
|
+
this._resolveTransactionCompletion = undefined
|
|
1504
|
+
if (resolve) resolve()
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1478
1507
|
/**
|
|
1479
1508
|
* Runs commit transaction action.
|
|
1480
1509
|
* @param {Pick<QueryOptions, "operationOwner">} [options] - Transaction ownership.
|
|
@@ -2091,6 +2120,7 @@ export default class VelociousDatabaseDriversBase {
|
|
|
2091
2120
|
await this._rollbackTransactionAction(options)
|
|
2092
2121
|
} finally {
|
|
2093
2122
|
this._transactionsCount--
|
|
2123
|
+
this._resolveCompletedTransaction()
|
|
2094
2124
|
|
|
2095
2125
|
// A rolled-back transaction may have reverted DDL (e.g. a CREATE TABLE
|
|
2096
2126
|
// run lazily inside the transaction), so any cached schema metadata is
|
|
@@ -145,18 +145,75 @@ export default class VelociousDatabaseMigrator {
|
|
|
145
145
|
* @returns {Promise<void>} - Resolves when complete.
|
|
146
146
|
*/
|
|
147
147
|
async migrateFilesFromRequireContext(requireContext) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
148
|
+
const files = this.migrationsFromRequireContext(requireContext)
|
|
149
|
+
|
|
150
|
+
await this.configuration.ensureConnections({databaseIdentifiers: this.databaseIdentifiers, name: "Database migrator: migrate require-context files"}, async () => {
|
|
151
|
+
for (const migration of files) {
|
|
152
|
+
await this.runMigrationFile({
|
|
153
|
+
migration,
|
|
154
|
+
requireMigration: async () => requireContext(migration.file).default
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
await this._afterMigrations()
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Migrates exactly one already-captured physical database. This is the
|
|
164
|
+
* frontend/tenant counterpart to the ambient multi-database entrypoints:
|
|
165
|
+
* callers own the captured connection and no configuration fallback is read.
|
|
166
|
+
* @param {object} args - Captured migration arguments.
|
|
167
|
+
* @param {import("../configuration-types.js").DatabaseConfigurationType} args.databaseConfiguration - Captured physical database configuration.
|
|
168
|
+
* @param {string} args.databaseIdentifier - Logical database identifier.
|
|
169
|
+
* @param {import("./drivers/base.js").default} args.db - Captured physical connection.
|
|
170
|
+
* @param {import("./migrator/types.js").RequireMigrationContextType} args.requireContext - Frontend migration require context.
|
|
171
|
+
* @returns {Promise<number>} - Number of newly applied migrations.
|
|
172
|
+
*/
|
|
173
|
+
async migrateRequireContextForDatabase({databaseConfiguration, databaseIdentifier, db, requireContext}) {
|
|
174
|
+
if (!databaseConfiguration.migrations) return 0
|
|
175
|
+
|
|
176
|
+
await MigrationsLedger.ensureTable(db)
|
|
177
|
+
const appliedVersions = new Set(await MigrationsLedger.appliedVersions(db))
|
|
178
|
+
const migrations = this.migrationsFromRequireContext(requireContext)
|
|
179
|
+
let appliedCount = 0
|
|
180
|
+
|
|
181
|
+
for (const migration of migrations) {
|
|
182
|
+
const version = `${migration.date}`
|
|
183
|
+
|
|
184
|
+
if (appliedVersions.has(version)) continue
|
|
185
|
+
|
|
186
|
+
const MigrationClass = requireContext(migration.file).default
|
|
187
|
+
|
|
188
|
+
if (!MigrationClass || typeof MigrationClass !== "function") {
|
|
189
|
+
throw new Error(`Migration ${migration.file} must export a default migration class. Type: ${typeof MigrationClass}`)
|
|
190
|
+
}
|
|
191
|
+
if (!(MigrationClass.getDatabaseIdentifiers() || ["default"]).includes(databaseIdentifier)) continue
|
|
192
|
+
|
|
193
|
+
const migrationInstance = new MigrationClass({configuration: this.configuration, databaseIdentifier, db})
|
|
194
|
+
|
|
195
|
+
await this.runMigrationUp({migration, migrationInstance})
|
|
196
|
+
await MigrationsLedger.recordVersion(db, version)
|
|
197
|
+
appliedVersions.add(version)
|
|
198
|
+
appliedCount++
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return appliedCount
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Parses and orders migrations from a browser/native require context.
|
|
206
|
+
* @param {import("./migrator/types.js").RequireMigrationContextType} requireContext - Migration require context.
|
|
207
|
+
* @returns {import("./migrator/types.js").MigrationObjectType[]} - Ordered migrations.
|
|
208
|
+
*/
|
|
209
|
+
migrationsFromRequireContext(requireContext) {
|
|
210
|
+
const migrations = []
|
|
152
211
|
|
|
153
212
|
for (const file of requireContext.keys()) {
|
|
154
|
-
// "13,14" because somes "require-context"-npm-module deletes first character!?
|
|
155
213
|
const match = file.match(/(\d{13,14})-(.+)\.js$/)
|
|
156
214
|
|
|
157
215
|
if (!match) continue
|
|
158
216
|
|
|
159
|
-
// Fix require-context-npm-module deletes first character
|
|
160
217
|
let fileName = file
|
|
161
218
|
let dateNumber = match[1]
|
|
162
219
|
|
|
@@ -165,30 +222,39 @@ export default class VelociousDatabaseMigrator {
|
|
|
165
222
|
fileName = `2${fileName}`
|
|
166
223
|
}
|
|
167
224
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const migrationName = match[2]
|
|
171
|
-
const migrationClassName = inflection.camelize(migrationName.replaceAll("-", "_"))
|
|
172
|
-
|
|
173
|
-
files.push({
|
|
225
|
+
migrations.push({
|
|
226
|
+
date: parseInt(dateNumber),
|
|
174
227
|
file: fileName,
|
|
175
|
-
|
|
176
|
-
migrationClassName
|
|
228
|
+
migrationClassName: inflection.camelize(match[2].replaceAll("-", "_"))
|
|
177
229
|
})
|
|
178
230
|
}
|
|
179
231
|
|
|
180
|
-
|
|
232
|
+
return migrations.sort((migration1, migration2) => migration1.date - migration2.date)
|
|
233
|
+
}
|
|
181
234
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
235
|
+
/**
|
|
236
|
+
* Runs one migration's upward implementation.
|
|
237
|
+
* @param {object} args - Migration arguments.
|
|
238
|
+
* @param {import("./migrator/types.js").MigrationObjectType} args.migration - Migration descriptor.
|
|
239
|
+
* @param {import("./migration/index.js").default} args.migrationInstance - Migration instance.
|
|
240
|
+
* @returns {Promise<void>} - Resolves after the migration succeeds.
|
|
241
|
+
*/
|
|
242
|
+
async runMigrationUp({migration, migrationInstance}) {
|
|
243
|
+
try {
|
|
244
|
+
await migrationInstance.change()
|
|
245
|
+
} catch (changeError) {
|
|
246
|
+
if (!(changeError instanceof NotImplementedError)) throw changeError
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
await migrationInstance.up()
|
|
250
|
+
} catch (upError) {
|
|
251
|
+
if (upError instanceof NotImplementedError) {
|
|
252
|
+
throw new Error(`'change' or 'up' didn't exist on migration: ${migration.file}`, {cause: upError})
|
|
253
|
+
}
|
|
189
254
|
|
|
190
|
-
|
|
191
|
-
|
|
255
|
+
throw upError
|
|
256
|
+
}
|
|
257
|
+
}
|
|
192
258
|
}
|
|
193
259
|
|
|
194
260
|
/**
|
|
@@ -17,9 +17,10 @@ export default class VelociousDatabaseOperation {
|
|
|
17
17
|
* @param {import("./drivers/base.js").default} args.connection - Pinned physical connection.
|
|
18
18
|
* @param {string} args.databaseIdentifier - Singular database identifier.
|
|
19
19
|
* @param {symbol} args.owner - Opaque pool lease owner.
|
|
20
|
+
* @param {string} [args.schemaGeneration] - Tenant schema generation owning record metadata.
|
|
20
21
|
* @param {object | undefined} args.tenant - Tenant descriptor captured by the owning handle.
|
|
21
22
|
*/
|
|
22
|
-
constructor({configuration, databaseConfiguration, configurationReuseKey, connection, databaseIdentifier, enforceCurrentTenantReuseKey = true, owner, tenant}) {
|
|
23
|
+
constructor({configuration, databaseConfiguration, configurationReuseKey, connection, databaseIdentifier, enforceCurrentTenantReuseKey = true, owner, schemaGeneration, tenant}) {
|
|
23
24
|
this._active = true
|
|
24
25
|
this._configuration = configuration
|
|
25
26
|
this._databaseConfiguration = databaseConfiguration || configuration.resolveDatabaseConfiguration(databaseIdentifier, tenant)
|
|
@@ -27,7 +28,10 @@ export default class VelociousDatabaseOperation {
|
|
|
27
28
|
this._databaseIdentifier = databaseIdentifier
|
|
28
29
|
this._enforceCurrentTenantReuseKey = enforceCurrentTenantReuseKey
|
|
29
30
|
this._physicalConnection = connection
|
|
31
|
+
this._schemaGeneration = schemaGeneration
|
|
30
32
|
this._tenant = tenant
|
|
33
|
+
/** @type {WeakMap<typeof import("./record/index.js").default, typeof import("./record/index.js").default>} */
|
|
34
|
+
this._boundModelClasses = new WeakMap()
|
|
31
35
|
this._connection = new OperationConnection({
|
|
32
36
|
connection,
|
|
33
37
|
operation: this,
|
|
@@ -45,12 +49,57 @@ export default class VelociousDatabaseOperation {
|
|
|
45
49
|
this.assertActive()
|
|
46
50
|
this.assertModel(ModelClass)
|
|
47
51
|
|
|
48
|
-
return ModelClass._newQuery({
|
|
52
|
+
return this.modelClass(ModelClass)._newQuery({
|
|
49
53
|
driver: this.connection(),
|
|
50
54
|
operation: this
|
|
51
55
|
})
|
|
52
56
|
}
|
|
53
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Returns a model-class view whose schema metadata is bound to this physical
|
|
60
|
+
* database generation. Construction still produces the application's original
|
|
61
|
+
* model class, so lifecycle callbacks and model registries retain class identity.
|
|
62
|
+
* @template {typeof import("./record/index.js").default} MC
|
|
63
|
+
* @param {MC} ModelClass - Canonical model class.
|
|
64
|
+
* @returns {MC} - Operation-bound model class.
|
|
65
|
+
*/
|
|
66
|
+
modelClass(ModelClass) {
|
|
67
|
+
if (!this._schemaGeneration) return ModelClass
|
|
68
|
+
|
|
69
|
+
const canonicalModelClass = /** @type {MC} */ (ModelClass._recordMetadataModelClass || ModelClass)
|
|
70
|
+
|
|
71
|
+
const existing = this._boundModelClasses.get(canonicalModelClass)
|
|
72
|
+
|
|
73
|
+
if (existing) return /** @type {MC} */ (existing)
|
|
74
|
+
|
|
75
|
+
const databaseIdentity = this.databaseIdentity()
|
|
76
|
+
const metadataKey = `${databaseIdentity.length}:${databaseIdentity}:${this._schemaGeneration}`
|
|
77
|
+
const metadataProperties = canonicalModelClass.recordMetadataPropertyNames()
|
|
78
|
+
const boundModelClass = new Proxy(canonicalModelClass, {
|
|
79
|
+
construct: (target, args, newTarget) => Reflect.construct(target, args, newTarget),
|
|
80
|
+
get: (target, property, receiver) => {
|
|
81
|
+
if (property === "_recordMetadataModelClass") return target
|
|
82
|
+
if (property === "_recordMetadataBinder") return (/** @type {typeof import("./record/index.js").default} */ targetModelClass) => this.modelClass(targetModelClass)
|
|
83
|
+
if (property === "_recordMetadataOperation") return this
|
|
84
|
+
if (typeof property === "string" && metadataProperties.has(property)) return target.recordMetadataValue(metadataKey, property)
|
|
85
|
+
|
|
86
|
+
return Reflect.get(target, property, receiver)
|
|
87
|
+
},
|
|
88
|
+
set: (target, property, value, receiver) => {
|
|
89
|
+
if (typeof property === "string" && metadataProperties.has(property)) {
|
|
90
|
+
target.setRecordMetadataValue(metadataKey, property, value)
|
|
91
|
+
return true
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return Reflect.set(target, property, value, receiver)
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
this._boundModelClasses.set(canonicalModelClass, boundModelClass)
|
|
99
|
+
|
|
100
|
+
return /** @type {MC} */ (boundModelClass)
|
|
101
|
+
}
|
|
102
|
+
|
|
54
103
|
/**
|
|
55
104
|
* Verifies that a model belongs to this operation's configuration and database.
|
|
56
105
|
* @param {typeof import("./record/index.js").default} ModelClass - Model class to verify.
|
|
@@ -162,9 +211,11 @@ export default class VelociousDatabaseOperation {
|
|
|
162
211
|
async ensureModelInitialized(ModelClass) {
|
|
163
212
|
this.assertActive()
|
|
164
213
|
|
|
165
|
-
|
|
214
|
+
const boundModelClass = this.modelClass(ModelClass)
|
|
215
|
+
|
|
216
|
+
if (boundModelClass.isInitialized()) this.assertModel(ModelClass)
|
|
166
217
|
|
|
167
|
-
await
|
|
218
|
+
await boundModelClass.ensureInitialized({
|
|
168
219
|
configuration: this._configuration,
|
|
169
220
|
connection: this.connection()
|
|
170
221
|
})
|
|
@@ -54,12 +54,15 @@ export default class VelociousDatabaseQueryJoinObject extends JoinBase {
|
|
|
54
54
|
for (const joinKey in join) {
|
|
55
55
|
const joinValue = join[joinKey]
|
|
56
56
|
const relationship = modelClass.getRelationshipByName(joinKey)
|
|
57
|
-
const
|
|
57
|
+
const rawTargetModelClass = relationship.getTargetModelClass()
|
|
58
58
|
|
|
59
|
-
if (!
|
|
59
|
+
if (!rawTargetModelClass) {
|
|
60
60
|
throw new Error(`Relationship ${modelClass.name}#${joinKey} has no target model class`)
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
const targetModelClass = query.bindModelClass(rawTargetModelClass)
|
|
64
|
+
const foreignKey = relationship.getForeignKeyForModelClasses({modelClass, targetModelClass})
|
|
65
|
+
|
|
63
66
|
const joinPath = path.concat([joinKey])
|
|
64
67
|
const parentTableRef = query.getJoinTableReference(path)
|
|
65
68
|
const targetEntry = query._registerJoinPath(joinPath)
|
|
@@ -80,9 +83,9 @@ export default class VelociousDatabaseQueryJoinObject extends JoinBase {
|
|
|
80
83
|
|
|
81
84
|
if (relationship.getType() == "belongsTo") {
|
|
82
85
|
sql += `${conn.quoteTable(targetTableRef)}.${conn.quoteColumn(relationship.getPrimaryKey())} = `
|
|
83
|
-
sql += `${conn.quoteTable(parentTableRef)}.${conn.quoteColumn(
|
|
86
|
+
sql += `${conn.quoteTable(parentTableRef)}.${conn.quoteColumn(foreignKey)}`
|
|
84
87
|
} else if (relationship.getType() == "hasMany" || relationship.getType() == "hasOne") {
|
|
85
|
-
sql += `${conn.quoteTable(targetTableRef)}.${conn.quoteColumn(
|
|
88
|
+
sql += `${conn.quoteTable(targetTableRef)}.${conn.quoteColumn(foreignKey)} = `
|
|
86
89
|
sql += `${conn.quoteTable(parentTableRef)}.${conn.quoteColumn(relationship.getPrimaryKey())}`
|
|
87
90
|
} else {
|
|
88
91
|
throw new Error(`Unknown relationship type: ${relationship.getType()}`)
|
|
@@ -87,6 +87,22 @@ function normalizeScopePath(path) {
|
|
|
87
87
|
return [...path]
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Narrows a shared model-scope descriptor to the backend model class required by ModelClassQuery.
|
|
92
|
+
* @param {import("../../utils/model-scope.js").ModelScopeDescriptor} scopeDescriptor - Shared scope descriptor.
|
|
93
|
+
* @returns {typeof import("../record/index.js").default} - Backend scope owner.
|
|
94
|
+
*/
|
|
95
|
+
function backendScopeModelClass(scopeDescriptor) {
|
|
96
|
+
const modelClass = scopeDescriptor.modelClass
|
|
97
|
+
|
|
98
|
+
if (!("canonicalRecordMetadataModelClass" in modelClass)) {
|
|
99
|
+
throw new Error("A frontend-model scope cannot be applied to a database record query")
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// The runtime member check above narrows the shared frontend/backend descriptor boundary.
|
|
103
|
+
return /** @type {typeof import("../record/index.js").default} */ (modelClass)
|
|
104
|
+
}
|
|
105
|
+
|
|
90
106
|
/**
|
|
91
107
|
* Deep-copies a preload select map (keyed by model name with attribute arrays)
|
|
92
108
|
* so a cloned query's selections can be mutated without affecting the original.
|
|
@@ -490,6 +506,15 @@ export default class VelociousDatabaseQueryModelClassQuery extends DatabaseQuery
|
|
|
490
506
|
return this.modelClass
|
|
491
507
|
}
|
|
492
508
|
|
|
509
|
+
/**
|
|
510
|
+
* Binds a relationship target to this query's physical database generation.
|
|
511
|
+
* @param {typeof import("../record/index.js").default} modelClass - Canonical relationship target.
|
|
512
|
+
* @returns {typeof import("../record/index.js").default} - Query-bound relationship target.
|
|
513
|
+
*/
|
|
514
|
+
bindModelClass(modelClass) {
|
|
515
|
+
return this.getModelClass().bindRecordMetadataModelClass(modelClass)
|
|
516
|
+
}
|
|
517
|
+
|
|
493
518
|
/**
|
|
494
519
|
* Runs get join base path.
|
|
495
520
|
* @returns {string[]} - The join base path.
|
|
@@ -563,7 +588,7 @@ export default class VelociousDatabaseQueryModelClassQuery extends DatabaseQuery
|
|
|
563
588
|
throw new Error(`No target model class for ${modelClass.name}#${relationshipName}`)
|
|
564
589
|
}
|
|
565
590
|
|
|
566
|
-
modelClass = targetModelClass
|
|
591
|
+
modelClass = this.bindModelClass(targetModelClass)
|
|
567
592
|
}
|
|
568
593
|
|
|
569
594
|
return modelClass
|
|
@@ -642,7 +667,9 @@ export default class VelociousDatabaseQueryModelClassQuery extends DatabaseQuery
|
|
|
642
667
|
throw new Error("scope() expects a descriptor returned by defineScope(...).scope(...)")
|
|
643
668
|
}
|
|
644
669
|
|
|
645
|
-
|
|
670
|
+
const scopeModelClass = backendScopeModelClass(scopeDescriptor)
|
|
671
|
+
|
|
672
|
+
if (scopeModelClass.canonicalRecordMetadataModelClass() !== this.getModelClass().canonicalRecordMetadataModelClass()) {
|
|
646
673
|
throw new Error(`Cannot apply ${scopeDescriptor.modelClass.name} scope to ${this.getModelClass().name} query`)
|
|
647
674
|
}
|
|
648
675
|
|
|
@@ -671,7 +698,9 @@ export default class VelociousDatabaseQueryModelClassQuery extends DatabaseQuery
|
|
|
671
698
|
const fullJoinPath = this.getJoinBasePath().concat(joinPath)
|
|
672
699
|
const targetModelClass = this._resolveModelClassForJoinPath(fullJoinPath)
|
|
673
700
|
|
|
674
|
-
|
|
701
|
+
const scopeModelClass = backendScopeModelClass(scopeDescriptor)
|
|
702
|
+
|
|
703
|
+
if (scopeModelClass.canonicalRecordMetadataModelClass() !== targetModelClass.canonicalRecordMetadataModelClass()) {
|
|
675
704
|
throw new Error(`Cannot apply ${scopeDescriptor.modelClass.name} scope to join path ${fullJoinPath.join(".")} (${targetModelClass.name})`)
|
|
676
705
|
}
|
|
677
706
|
|
|
@@ -1418,11 +1447,12 @@ function splitWhereHash({hash, modelClass}) {
|
|
|
1418
1447
|
|
|
1419
1448
|
if (isNested) {
|
|
1420
1449
|
if (relationship) {
|
|
1421
|
-
const
|
|
1422
|
-
if (!
|
|
1450
|
+
const rawTargetModelClass = relationship.getTargetModelClass()
|
|
1451
|
+
if (!rawTargetModelClass) {
|
|
1423
1452
|
fallbackHash[key] = value
|
|
1424
1453
|
continue
|
|
1425
1454
|
}
|
|
1455
|
+
const targetModelClass = modelClass.bindRecordMetadataModelClass(rawTargetModelClass)
|
|
1426
1456
|
const nestedResult = splitWhereHash({hash: value, modelClass: targetModelClass})
|
|
1427
1457
|
const nestedResolvedKeys = Object.keys(nestedResult.resolvedHash)
|
|
1428
1458
|
const nestedFallbackKeys = Object.keys(nestedResult.fallbackHash)
|
|
@@ -1476,8 +1506,9 @@ function buildJoinObjectFromWhereHash({hash, modelClass}) {
|
|
|
1476
1506
|
if (!relationship) continue
|
|
1477
1507
|
|
|
1478
1508
|
if (isPlainObject(value)) {
|
|
1479
|
-
const
|
|
1480
|
-
if (!
|
|
1509
|
+
const rawTargetModelClass = relationship.getTargetModelClass()
|
|
1510
|
+
if (!rawTargetModelClass) continue
|
|
1511
|
+
const targetModelClass = modelClass.bindRecordMetadataModelClass(rawTargetModelClass)
|
|
1481
1512
|
const nestedJoinObject = buildJoinObjectFromWhereHash({hash: value, modelClass: targetModelClass})
|
|
1482
1513
|
|
|
1483
1514
|
joinObject[key] = Object.keys(nestedJoinObject).length > 0 ? nestedJoinObject : true
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import ensureModelClassInitialized from "./ensure-model-class-initialized.js"
|
|
4
4
|
import PreloaderSelection from "./selection.js"
|
|
5
|
-
import preloadQueryForModel from "./query-for-model.js"
|
|
5
|
+
import preloadQueryForModel, { bindPreloadModelClass } from "./query-for-model.js"
|
|
6
6
|
import restArgsError from "../../../utils/rest-args-error.js"
|
|
7
7
|
|
|
8
8
|
export default class VelociousDatabaseQueryPreloaderBelongsTo {
|
|
@@ -22,7 +22,8 @@ export default class VelociousDatabaseQueryPreloaderBelongsTo {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
async run() {
|
|
25
|
-
const
|
|
25
|
+
const sourceModelClass = this.models[0].getModelClass()
|
|
26
|
+
const foreignKey = this.relationship.getForeignKeyForModelClasses({modelClass: sourceModelClass, targetModelClass: sourceModelClass})
|
|
26
27
|
const primaryKey = this.relationship.getPrimaryKey()
|
|
27
28
|
const relationshipName = this.relationship.getRelationshipName()
|
|
28
29
|
|
|
@@ -30,9 +31,11 @@ export default class VelociousDatabaseQueryPreloaderBelongsTo {
|
|
|
30
31
|
return await this._runPolymorphic({foreignKey, primaryKey, relationshipName})
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
const
|
|
34
|
+
const rawTargetModelClass = this.relationship.getTargetModelClass()
|
|
34
35
|
|
|
35
|
-
if (!
|
|
36
|
+
if (!rawTargetModelClass) throw new Error("No target model class could be gotten from relationship")
|
|
37
|
+
|
|
38
|
+
const targetModelClass = bindPreloadModelClass(this.models, rawTargetModelClass)
|
|
36
39
|
|
|
37
40
|
/**
|
|
38
41
|
* Satisfied targets.
|
|
@@ -155,7 +158,7 @@ export default class VelociousDatabaseQueryPreloaderBelongsTo {
|
|
|
155
158
|
for (const model of this.models) {
|
|
156
159
|
const targetType = /** @type {string | undefined} */ (model.readColumn(typeColumn))
|
|
157
160
|
const instanceRelationship = model.getRelationshipByName(relationshipName)
|
|
158
|
-
const targetModelClass = targetType ? configuration.getModelClass(targetType) : undefined
|
|
161
|
+
const targetModelClass = targetType ? bindPreloadModelClass(this.models, configuration.getModelClass(targetType)) : undefined
|
|
159
162
|
|
|
160
163
|
if (targetModelClass && this.selection.isSatisfied({instanceRelationship, targetModelClass, mappingColumns: [primaryKey]})) {
|
|
161
164
|
const loaded = /** @type {import("../../record/index.js").default | undefined} */ (instanceRelationship.getLoadedOrUndefined())
|
|
@@ -203,7 +206,7 @@ export default class VelociousDatabaseQueryPreloaderBelongsTo {
|
|
|
203
206
|
const targetModels = []
|
|
204
207
|
|
|
205
208
|
for (const targetType in foreignKeyValuesByType) {
|
|
206
|
-
const targetModelClass = configuration.getModelClass(targetType)
|
|
209
|
+
const targetModelClass = bindPreloadModelClass(this.models, configuration.getModelClass(targetType))
|
|
207
210
|
|
|
208
211
|
await ensureModelClassInitialized(targetModelClass, configuration, this.models[0])
|
|
209
212
|
|