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
|
@@ -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
|
/**
|
|
@@ -18,19 +18,23 @@ export function hasManyThroughTargetForeignKey(relationship, throughModelClass,
|
|
|
18
18
|
// (e.g. a default plus an alternate), so picking the first match would otherwise be ambiguous.
|
|
19
19
|
const explicitForeignKey = relationship.getExplicitForeignKey()
|
|
20
20
|
|
|
21
|
-
if (explicitForeignKey)
|
|
21
|
+
if (explicitForeignKey) {
|
|
22
|
+
return targetModelClass.getAttributeNameToColumnNameMap()[explicitForeignKey] || explicitForeignKey
|
|
23
|
+
}
|
|
22
24
|
|
|
23
25
|
for (const targetRelationship of targetModelClass.getRelationships()) {
|
|
24
26
|
if (targetRelationship.getType() != "belongsTo") continue
|
|
25
27
|
|
|
26
28
|
const relationshipTargetModelClass = targetRelationship.getTargetModelClass()
|
|
27
29
|
|
|
28
|
-
if (relationshipTargetModelClass
|
|
29
|
-
|
|
30
|
+
if (!relationshipTargetModelClass) continue
|
|
31
|
+
|
|
32
|
+
if (relationshipTargetModelClass.canonicalRecordMetadataModelClass() === throughModelClass.canonicalRecordMetadataModelClass()) {
|
|
33
|
+
return targetRelationship.getForeignKeyForModelClasses({modelClass: targetModelClass, targetModelClass: throughModelClass})
|
|
30
34
|
}
|
|
31
35
|
}
|
|
32
36
|
|
|
33
|
-
return relationship.
|
|
37
|
+
return relationship.getForeignKeyForModelClasses({modelClass: throughModelClass, targetModelClass})
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
export default class VelociousDatabaseQueryPreloaderHasMany {
|
|
@@ -107,15 +111,19 @@ export default class VelociousDatabaseQueryPreloaderHasMany {
|
|
|
107
111
|
}
|
|
108
112
|
|
|
109
113
|
const throughRelationshipName = /** @type {string} */ (this.relationship.through)
|
|
110
|
-
const parentModelClass = this.
|
|
114
|
+
const parentModelClass = this.models[0].getModelClass()
|
|
111
115
|
const throughRelationship = parentModelClass.getRelationshipByName(throughRelationshipName)
|
|
112
|
-
const
|
|
116
|
+
const rawThroughModelClass = throughRelationship.getTargetModelClass()
|
|
117
|
+
|
|
118
|
+
if (!rawThroughModelClass) throw new Error(`Through relationship ${throughRelationshipName} has no target model class`)
|
|
113
119
|
|
|
114
|
-
|
|
120
|
+
const throughModelClass = bindPreloadModelClass(this.models, rawThroughModelClass)
|
|
115
121
|
|
|
116
|
-
const
|
|
122
|
+
const rawTargetModelClass = this.relationship.getTargetModelClass()
|
|
117
123
|
|
|
118
|
-
if (!
|
|
124
|
+
if (!rawTargetModelClass) throw new Error("No target model class could be gotten from relationship")
|
|
125
|
+
|
|
126
|
+
const targetModelClass = bindPreloadModelClass(this.models, rawTargetModelClass)
|
|
119
127
|
|
|
120
128
|
const targetForeignKey = hasManyThroughTargetForeignKey(this.relationship, throughModelClass, targetModelClass)
|
|
121
129
|
const {modelsToLoad, satisfiedTargets} = this._partition(targetModelClass, [targetForeignKey])
|
|
@@ -127,7 +135,7 @@ export default class VelociousDatabaseQueryPreloaderHasMany {
|
|
|
127
135
|
await ensureModelClassInitialized(throughModelClass, configuration, modelsToLoad[0])
|
|
128
136
|
await ensureModelClassInitialized(targetModelClass, configuration, modelsToLoad[0])
|
|
129
137
|
|
|
130
|
-
const throughForeignKey = throughRelationship.
|
|
138
|
+
const throughForeignKey = throughRelationship.getForeignKeyForModelClasses({modelClass: parentModelClass, targetModelClass: throughModelClass})
|
|
131
139
|
|
|
132
140
|
/**
|
|
133
141
|
* Models primary key values.
|
|
@@ -260,16 +268,19 @@ export default class VelociousDatabaseQueryPreloaderHasMany {
|
|
|
260
268
|
* @returns {Promise<import("../../record/index.js").default[]>} - Loaded target models.
|
|
261
269
|
*/
|
|
262
270
|
async _runDirect() {
|
|
263
|
-
const foreignKey = this.relationship.getForeignKey()
|
|
264
271
|
const primaryKey = this.relationship.getPrimaryKey()
|
|
265
272
|
|
|
266
273
|
if (!primaryKey) {
|
|
267
274
|
throw new Error(`${this.relationship.getModelClass().name}#${this.relationship.getRelationshipName()} doesn't have a primary key`)
|
|
268
275
|
}
|
|
269
276
|
|
|
270
|
-
const
|
|
277
|
+
const rawTargetModelClass = this.relationship.getTargetModelClass()
|
|
278
|
+
|
|
279
|
+
if (!rawTargetModelClass) throw new Error("No target model class could be gotten from relationship")
|
|
271
280
|
|
|
272
|
-
|
|
281
|
+
const sourceModelClass = this.models[0].getModelClass()
|
|
282
|
+
const targetModelClass = bindPreloadModelClass(this.models, rawTargetModelClass)
|
|
283
|
+
const foreignKey = this.relationship.getForeignKeyForModelClasses({modelClass: sourceModelClass, targetModelClass})
|
|
273
284
|
|
|
274
285
|
const {modelsToLoad, satisfiedTargets} = this._partition(targetModelClass, [foreignKey])
|
|
275
286
|
|
|
@@ -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 VelociousDatabaseQueryPreloaderHasOne {
|
|
@@ -32,13 +32,16 @@ export default class VelociousDatabaseQueryPreloaderHasOne {
|
|
|
32
32
|
* @type {Record<number | string, Array<import("../../record/index.js").default>>} */
|
|
33
33
|
const modelsByPrimaryKeyValue = {}
|
|
34
34
|
|
|
35
|
-
const foreignKey = this.relationship.getForeignKey()
|
|
36
35
|
const primaryKey = this.relationship.getPrimaryKey()
|
|
37
36
|
const relationshipName = this.relationship.getRelationshipName()
|
|
38
37
|
|
|
39
|
-
const
|
|
38
|
+
const rawTargetModelClass = this.relationship.getTargetModelClass()
|
|
40
39
|
|
|
41
|
-
if (!
|
|
40
|
+
if (!rawTargetModelClass) throw new Error("No target model class could be gotten from relationship")
|
|
41
|
+
|
|
42
|
+
const sourceModelClass = this.models[0].getModelClass()
|
|
43
|
+
const targetModelClass = bindPreloadModelClass(this.models, rawTargetModelClass)
|
|
44
|
+
const foreignKey = this.relationship.getForeignKeyForModelClasses({modelClass: sourceModelClass, targetModelClass})
|
|
42
45
|
|
|
43
46
|
/**
|
|
44
47
|
* Preload collections.
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Binds a preload target to the source records' physical database generation.
|
|
5
|
+
* @template {typeof import("../../record/index.js").default} MC
|
|
6
|
+
* @param {import("../../record/index.js").default[]} models - Source records.
|
|
7
|
+
* @param {MC} ModelClass - Target model class.
|
|
8
|
+
* @returns {MC} - Generation-bound target model class.
|
|
9
|
+
*/
|
|
10
|
+
export function bindPreloadModelClass(models, ModelClass) {
|
|
11
|
+
const sourceModel = models[0]
|
|
12
|
+
|
|
13
|
+
if (!sourceModel) throw new Error("Cannot bind a preload model class without source records")
|
|
14
|
+
|
|
15
|
+
return /** @type {MC} */ (sourceModel.getModelClass().bindRecordMetadataModelClass(ModelClass))
|
|
16
|
+
}
|
|
17
|
+
|
|
3
18
|
/**
|
|
4
19
|
* Builds a target query preserving the explicit database operation owned by
|
|
5
20
|
* the source records.
|
|
@@ -13,5 +28,5 @@ export default function preloadQueryForModel(models, ModelClass) {
|
|
|
13
28
|
|
|
14
29
|
if (!sourceModel) throw new Error("Cannot build a preload query without source records")
|
|
15
30
|
|
|
16
|
-
return sourceModel.queryForModel(ModelClass)
|
|
31
|
+
return sourceModel.queryForModel(bindPreloadModelClass(models, ModelClass))
|
|
17
32
|
}
|
|
@@ -88,7 +88,7 @@ export default class VelociousDatabaseQueryPreloader {
|
|
|
88
88
|
static async preload(models, queryOrSpec, {force = false} = {}) {
|
|
89
89
|
if (models.length == 0) return
|
|
90
90
|
|
|
91
|
-
const modelClass =
|
|
91
|
+
const modelClass = models[0].getModelClass()
|
|
92
92
|
const isQuery = Boolean(queryOrSpec) && typeof queryOrSpec == "object" && "_preload" in queryOrSpec
|
|
93
93
|
// Reuse the query builder's preload/select normalization for raw specs
|
|
94
94
|
// instead of duplicating it here.
|
|
@@ -176,15 +176,17 @@ export default class VelociousDatabaseQueryPreloader {
|
|
|
176
176
|
|
|
177
177
|
if (models.length == 0) continue
|
|
178
178
|
|
|
179
|
-
const targetModelClass = configuration.getModelClass(className)
|
|
179
|
+
const targetModelClass = this.modelClass.bindRecordMetadataModelClass(configuration.getModelClass(className))
|
|
180
180
|
const preloader = new VelociousDatabaseQueryPreloader({modelClass: targetModelClass, models, preload: normalizedPreload, selection: this.selection})
|
|
181
181
|
|
|
182
182
|
await preloader.run()
|
|
183
183
|
}
|
|
184
184
|
} else {
|
|
185
|
-
const
|
|
185
|
+
const rawTargetModelClass = relationship.getTargetModelClass()
|
|
186
186
|
|
|
187
|
-
if (!
|
|
187
|
+
if (!rawTargetModelClass) throw new Error("No target model class could be gotten from relationship")
|
|
188
|
+
|
|
189
|
+
const targetModelClass = this.modelClass.bindRecordMetadataModelClass(rawTargetModelClass)
|
|
188
190
|
|
|
189
191
|
const preloader = new VelociousDatabaseQueryPreloader({modelClass: targetModelClass, models: targetModels, preload: normalizedPreload, selection: this.selection})
|
|
190
192
|
|
|
@@ -153,13 +153,13 @@ function resolveTargetModelClass(rootModelClass, chain) {
|
|
|
153
153
|
|
|
154
154
|
for (const segment of chain) {
|
|
155
155
|
const relationship = modelClass.getRelationshipByName(segment)
|
|
156
|
-
const
|
|
156
|
+
const rawTarget = relationship.getTargetModelClass()
|
|
157
157
|
|
|
158
|
-
if (!
|
|
158
|
+
if (!rawTarget) {
|
|
159
159
|
throw new Error(`queryData: could not resolve target model for ${modelClass.name}#${segment}`)
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
modelClass =
|
|
162
|
+
modelClass = modelClass.bindRecordMetadataModelClass(rawTarget)
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
return modelClass
|
|
@@ -403,9 +403,11 @@ export default class VelociousDatabaseQueryWhereModelClassHash extends WhereBase
|
|
|
403
403
|
if (relationship && tuples) {
|
|
404
404
|
if (index > 0) sql += " AND "
|
|
405
405
|
|
|
406
|
-
const
|
|
406
|
+
const rawTargetModelClass = relationship.getTargetModelClass()
|
|
407
407
|
|
|
408
|
-
if (!
|
|
408
|
+
if (!rawTargetModelClass) throw new Error(`Relationship "${whereKey}" for ${modelClass.name} has no target model class`)
|
|
409
|
+
|
|
410
|
+
const targetModelClass = modelClass.bindRecordMetadataModelClass(rawTargetModelClass)
|
|
409
411
|
|
|
410
412
|
const nestedPath = path.concat([whereKey])
|
|
411
413
|
const nestedTableName = modelQuery.getTableReferenceForJoin(...nestedPath)
|
|
@@ -431,9 +433,11 @@ export default class VelociousDatabaseQueryWhereModelClassHash extends WhereBase
|
|
|
431
433
|
throw new Error(`Unknown relationship "${whereKey}" for ${modelClass.name}`)
|
|
432
434
|
}
|
|
433
435
|
|
|
434
|
-
const
|
|
436
|
+
const rawTargetModelClass = relationship.getTargetModelClass()
|
|
437
|
+
|
|
438
|
+
if (!rawTargetModelClass) throw new Error(`Relationship "${whereKey}" for ${modelClass.name} has no target model class`)
|
|
435
439
|
|
|
436
|
-
|
|
440
|
+
const targetModelClass = modelClass.bindRecordMetadataModelClass(rawTargetModelClass)
|
|
437
441
|
|
|
438
442
|
const nestedHash = /** @type {WhereHash} */ (whereValue)
|
|
439
443
|
const nestedPath = path.concat([whereKey])
|
|
@@ -155,13 +155,14 @@ function queryForEntry({entry, modelClass, sourceModel}) {
|
|
|
155
155
|
throw new Error(`withCount currently supports only hasMany relationships; ${modelClass.name}#${entry.relationshipName} is ${relationship.type}`)
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
const
|
|
158
|
+
const rawTargetModelClass = relationship.getTargetModelClass()
|
|
159
159
|
|
|
160
|
-
if (!
|
|
160
|
+
if (!rawTargetModelClass) {
|
|
161
161
|
throw new Error(`withCount: could not resolve target model for ${modelClass.name}#${entry.relationshipName}`)
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
const
|
|
164
|
+
const targetModelClass = modelClass.bindRecordMetadataModelClass(rawTargetModelClass)
|
|
165
|
+
const foreignKey = relationship.getForeignKeyForModelClasses({modelClass, targetModelClass})
|
|
165
166
|
/**
|
|
166
167
|
* Mandatory cohort conditions.
|
|
167
168
|
* @type {Record<string, ReturnType<typeof JSON.parse>>} */
|
|
@@ -24,6 +24,11 @@
|
|
|
24
24
|
|
|
25
25
|
/** @typedef {import("../../configuration-types.js").TenantDatabaseProviderType} TenantDatabaseProviderType */
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Schema metadata cached for one record class and physical database generation.
|
|
29
|
+
* @typedef {boolean | null | string | undefined | Promise<void> | string[] | import("../drivers/base-column.js").default[] | import("../drivers/base-table.js").default | Record<string, string> | Record<string, string | undefined> | Record<string, import("../drivers/base-column.js").default>} RecordMetadataValue
|
|
30
|
+
*/
|
|
31
|
+
|
|
27
32
|
import AdvisoryLockRunner, {AdvisoryLockBusyError, AdvisoryLockHoldTimeoutError, AdvisoryLockTimeoutError} from "../advisory-lock-runner.js"
|
|
28
33
|
import BelongsToInstanceRelationship from "./instance-relationships/belongs-to.js"
|
|
29
34
|
import BelongsToRelationship from "./relationships/belongs-to.js"
|
|
@@ -73,6 +78,39 @@ const declaredBooleanTruthyValues = new Set([1, true, "1"])
|
|
|
73
78
|
/** Stored values that a declared `"boolean"` cast reads back as `false`. */
|
|
74
79
|
const declaredBooleanFalsyValues = new Set([0, false, "0"])
|
|
75
80
|
|
|
81
|
+
/** Static record metadata fields isolated per physical database/schema generation. */
|
|
82
|
+
const recordMetadataPropertyNames = new Set([
|
|
83
|
+
"_attributeNameToColumnName",
|
|
84
|
+
"_columnNameToAttributeName",
|
|
85
|
+
"_columnNames",
|
|
86
|
+
"_columns",
|
|
87
|
+
"_columnsAsHash",
|
|
88
|
+
"_columnTypeByName",
|
|
89
|
+
"_databaseType",
|
|
90
|
+
"_initialized",
|
|
91
|
+
"_initializeRecordPromise",
|
|
92
|
+
"_table"
|
|
93
|
+
])
|
|
94
|
+
|
|
95
|
+
/** @type {WeakMap<typeof import("./index.js").default, Map<string, Map<string, RecordMetadataValue>>>} */
|
|
96
|
+
const recordMetadataValuesByModel = new WeakMap()
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Returns the generation-keyed metadata store owned by one canonical model.
|
|
100
|
+
* @param {typeof import("./index.js").default} modelClass - Canonical model class.
|
|
101
|
+
* @returns {Map<string, Map<string, RecordMetadataValue>>} - Metadata store.
|
|
102
|
+
*/
|
|
103
|
+
function recordMetadataValuesFor(modelClass) {
|
|
104
|
+
let values = recordMetadataValuesByModel.get(modelClass)
|
|
105
|
+
|
|
106
|
+
if (!values) {
|
|
107
|
+
values = new Map()
|
|
108
|
+
recordMetadataValuesByModel.set(modelClass, values)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return values
|
|
112
|
+
}
|
|
113
|
+
|
|
76
114
|
class ValidationError extends Error {
|
|
77
115
|
/**
|
|
78
116
|
* Narrows the runtime value to the documented type.
|
|
@@ -236,6 +274,15 @@ class VelociousDatabaseRecord {
|
|
|
236
274
|
* @type {Promise<void> | null | undefined} */
|
|
237
275
|
static _initializeRecordPromise
|
|
238
276
|
|
|
277
|
+
/** @type {typeof VelociousDatabaseRecord | undefined} Canonical model class exposed only by an operation-bound metadata proxy. */
|
|
278
|
+
static _recordMetadataModelClass
|
|
279
|
+
|
|
280
|
+
/** @type {((modelClass: typeof VelociousDatabaseRecord) => typeof VelociousDatabaseRecord) | undefined} Binds related generated model classes to the same operation metadata generation. */
|
|
281
|
+
static _recordMetadataBinder
|
|
282
|
+
|
|
283
|
+
/** @type {import("../operation.js").default | undefined} Operation exposed only by a constructing metadata proxy. */
|
|
284
|
+
static _recordMetadataOperation
|
|
285
|
+
|
|
239
286
|
/**
|
|
240
287
|
* Narrows the runtime value to the documented type.
|
|
241
288
|
* @type {boolean | undefined} */
|
|
@@ -360,10 +407,32 @@ class VelociousDatabaseRecord {
|
|
|
360
407
|
return defineModelScope({
|
|
361
408
|
callback,
|
|
362
409
|
modelClass: this,
|
|
363
|
-
startQuery: () =>
|
|
410
|
+
startQuery: (modelClass = this) => {
|
|
411
|
+
// This backend scope factory can only be invoked through a DatabaseRecord class.
|
|
412
|
+
const BackendModelClass = /** @type {typeof VelociousDatabaseRecord} */ (modelClass)
|
|
413
|
+
|
|
414
|
+
return BackendModelClass._newQuery()
|
|
415
|
+
}
|
|
364
416
|
})
|
|
365
417
|
}
|
|
366
418
|
|
|
419
|
+
/**
|
|
420
|
+
* Returns the application model class behind an operation-bound metadata view.
|
|
421
|
+
* @returns {typeof VelociousDatabaseRecord} - Canonical model class.
|
|
422
|
+
*/
|
|
423
|
+
static canonicalRecordMetadataModelClass() {
|
|
424
|
+
return this._recordMetadataModelClass || this
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Binds a relationship target to this model class's metadata generation.
|
|
429
|
+
* @param {typeof VelociousDatabaseRecord} modelClass - Relationship target.
|
|
430
|
+
* @returns {typeof VelociousDatabaseRecord} - Generation-bound target, or the unchanged target for legacy queries.
|
|
431
|
+
*/
|
|
432
|
+
static bindRecordMetadataModelClass(modelClass) {
|
|
433
|
+
return this._recordMetadataBinder ? this._recordMetadataBinder(modelClass) : modelClass
|
|
434
|
+
}
|
|
435
|
+
|
|
367
436
|
static getColumnNameToAttributeNameMap() {
|
|
368
437
|
if (!this._columnNameToAttributeName) {
|
|
369
438
|
/**
|
|
@@ -1470,6 +1539,68 @@ class VelociousDatabaseRecord {
|
|
|
1470
1539
|
this._columnTypeByName = undefined
|
|
1471
1540
|
this._attributeNameToColumnName = undefined
|
|
1472
1541
|
this._columnNameToAttributeName = undefined
|
|
1542
|
+
|
|
1543
|
+
if (!this._recordMetadataModelClass) this.clearRecordMetadataValues()
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
/**
|
|
1547
|
+
* Static fields that belong to one physical database/schema generation.
|
|
1548
|
+
* @returns {Set<string>} - Metadata property names.
|
|
1549
|
+
*/
|
|
1550
|
+
static recordMetadataPropertyNames() {
|
|
1551
|
+
return recordMetadataPropertyNames
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
/**
|
|
1555
|
+
* Reads one operation-bound metadata field.
|
|
1556
|
+
* @param {string} metadataKey - Physical database and schema generation key.
|
|
1557
|
+
* @param {string} property - Static metadata property.
|
|
1558
|
+
* @returns {RecordMetadataValue} - Stored metadata value.
|
|
1559
|
+
*/
|
|
1560
|
+
static recordMetadataValue(metadataKey, property) {
|
|
1561
|
+
return recordMetadataValuesFor(this).get(metadataKey)?.get(property)
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
/**
|
|
1565
|
+
* Writes one operation-bound metadata field.
|
|
1566
|
+
* @param {string} metadataKey - Physical database and schema generation key.
|
|
1567
|
+
* @param {string} property - Static metadata property.
|
|
1568
|
+
* @param {RecordMetadataValue} value - Metadata value.
|
|
1569
|
+
* @returns {void}
|
|
1570
|
+
*/
|
|
1571
|
+
static setRecordMetadataValue(metadataKey, property, value) {
|
|
1572
|
+
let values = recordMetadataValuesFor(this).get(metadataKey)
|
|
1573
|
+
|
|
1574
|
+
if (!values) {
|
|
1575
|
+
values = new Map()
|
|
1576
|
+
recordMetadataValuesFor(this).set(metadataKey, values)
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
values.set(property, value)
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
/** Clears every tenant/generation metadata snapshot for this model. */
|
|
1583
|
+
static clearRecordMetadataValues() {
|
|
1584
|
+
recordMetadataValuesByModel.delete(this)
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
/**
|
|
1588
|
+
* Clears snapshots whose key belongs to one physical database identity.
|
|
1589
|
+
* @param {string} databaseIdentity - Logical identifier plus pool reuse key.
|
|
1590
|
+
* @returns {void}
|
|
1591
|
+
*/
|
|
1592
|
+
static clearRecordMetadataValuesForDatabaseIdentity(databaseIdentity) {
|
|
1593
|
+
const values = recordMetadataValuesByModel.get(this)
|
|
1594
|
+
|
|
1595
|
+
if (!values) return
|
|
1596
|
+
|
|
1597
|
+
const metadataPrefix = `${databaseIdentity.length}:${databaseIdentity}:`
|
|
1598
|
+
|
|
1599
|
+
for (const metadataKey of values.keys()) {
|
|
1600
|
+
if (metadataKey.startsWith(metadataPrefix)) values.delete(metadataKey)
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
if (values.size === 0) recordMetadataValuesByModel.delete(this)
|
|
1473
1604
|
}
|
|
1474
1605
|
|
|
1475
1606
|
/**
|
|
@@ -1484,8 +1615,10 @@ class VelociousDatabaseRecord {
|
|
|
1484
1615
|
if (!configuration) throw new Error(`No configuration given for ${this.name}`)
|
|
1485
1616
|
|
|
1486
1617
|
this.resetRecordMetadata()
|
|
1487
|
-
this.
|
|
1488
|
-
|
|
1618
|
+
const modelClass = this._recordMetadataModelClass || this
|
|
1619
|
+
|
|
1620
|
+
modelClass._configuration = configuration
|
|
1621
|
+
configuration.registerModelClass(modelClass)
|
|
1489
1622
|
}
|
|
1490
1623
|
|
|
1491
1624
|
/**
|
|
@@ -1637,7 +1770,10 @@ class VelociousDatabaseRecord {
|
|
|
1637
1770
|
|
|
1638
1771
|
if (!locales) throw new Error("Locales hasn't been set in the configuration")
|
|
1639
1772
|
|
|
1640
|
-
|
|
1773
|
+
const TranslationClass = this.getTranslationClass()
|
|
1774
|
+
const BoundTranslationClass = this._recordMetadataBinder ? this._recordMetadataBinder(TranslationClass) : TranslationClass
|
|
1775
|
+
|
|
1776
|
+
await BoundTranslationClass.initializeRecord({
|
|
1641
1777
|
configuration: this._getConfiguration(),
|
|
1642
1778
|
connection
|
|
1643
1779
|
})
|
|
@@ -1825,6 +1961,8 @@ class VelociousDatabaseRecord {
|
|
|
1825
1961
|
getModelClass() {
|
|
1826
1962
|
const modelClass = /** @type {typeof VelociousDatabaseRecord} */ (this.constructor)
|
|
1827
1963
|
|
|
1964
|
+
if (this._databaseOperation) return this._databaseOperation.modelClass(modelClass)
|
|
1965
|
+
|
|
1828
1966
|
return modelClass
|
|
1829
1967
|
}
|
|
1830
1968
|
|
|
@@ -3092,8 +3230,10 @@ class VelociousDatabaseRecord {
|
|
|
3092
3230
|
* @returns {ModelClassQuery<MC>} - The new query.
|
|
3093
3231
|
*/
|
|
3094
3232
|
static _newQuery(args = {}) {
|
|
3095
|
-
const {driver
|
|
3233
|
+
const {driver: givenDriver, operation: givenOperation, ...restArgs} = args
|
|
3096
3234
|
restArgsError(restArgs)
|
|
3235
|
+
const operation = givenOperation || this._recordMetadataOperation
|
|
3236
|
+
const driver = givenDriver || (operation ? operation.connection() : () => this.connection())
|
|
3097
3237
|
this._assertHasBeenInitialized()
|
|
3098
3238
|
const handler = new Handler()
|
|
3099
3239
|
const query = new ModelClassQuery({
|
|
@@ -3450,6 +3590,9 @@ class VelociousDatabaseRecord {
|
|
|
3450
3590
|
* @param {WriteAttributes} changes - Changes.
|
|
3451
3591
|
*/
|
|
3452
3592
|
constructor(changes = /** @type {WriteAttributes} */ ({})) {
|
|
3593
|
+
const ModelClass = /** @type {typeof VelociousDatabaseRecord} */ (new.target)
|
|
3594
|
+
|
|
3595
|
+
this._databaseOperation = ModelClass._recordMetadataOperation
|
|
3453
3596
|
this.getModelClass()._assertHasBeenInitialized()
|
|
3454
3597
|
this._attributes = {}
|
|
3455
3598
|
this._changes = {}
|
|
@@ -62,6 +62,19 @@ export default class VelociousDatabaseRecordBaseInstanceRelationship {
|
|
|
62
62
|
throw new Error("'build' not implemented")
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Resolves a relationship target through the source record's operation and
|
|
67
|
+
* metadata generation before construction.
|
|
68
|
+
* @returns {TMC | undefined} - Bound target model class.
|
|
69
|
+
*/
|
|
70
|
+
getBoundTargetModelClass() {
|
|
71
|
+
const targetModelClass = this.getTargetModelClass()
|
|
72
|
+
|
|
73
|
+
if (!targetModelClass) return undefined
|
|
74
|
+
|
|
75
|
+
return /** @type {TMC} */ (this.getModel().getModelClass().bindRecordMetadataModelClass(targetModelClass))
|
|
76
|
+
}
|
|
77
|
+
|
|
65
78
|
/**
|
|
66
79
|
* Runs get auto save.
|
|
67
80
|
* @returns {boolean | undefined} Whether the relationship should be auto-saved before saving the parent model
|
|
@@ -23,7 +23,7 @@ export default class VelociousDatabaseRecordBelongsToInstanceRelationship extend
|
|
|
23
23
|
* @returns {InstanceType<TMC>} - The build.
|
|
24
24
|
*/
|
|
25
25
|
build(data) {
|
|
26
|
-
const TargetModelClass =
|
|
26
|
+
const TargetModelClass = this.getBoundTargetModelClass()
|
|
27
27
|
|
|
28
28
|
if (!TargetModelClass) throw new Error("Can't build a new record without a target model")
|
|
29
29
|
|
|
@@ -25,7 +25,7 @@ export default class VelociousDatabaseRecordHasManyInstanceRelationship extends
|
|
|
25
25
|
*/
|
|
26
26
|
build(data) {
|
|
27
27
|
// Spawn new model of the targeted class
|
|
28
|
-
const targetModelClass = this.
|
|
28
|
+
const targetModelClass = this.getBoundTargetModelClass()
|
|
29
29
|
|
|
30
30
|
if (!targetModelClass) throw new Error("Can't build a new record without a taget model class")
|
|
31
31
|
|
|
@@ -58,7 +58,10 @@ export default class VelociousDatabaseRecordHasManyInstanceRelationship extends
|
|
|
58
58
|
const parentModel = this.getModel()
|
|
59
59
|
|
|
60
60
|
if (parentModel.isPersisted()) {
|
|
61
|
-
const foreignKeyName = this.
|
|
61
|
+
const foreignKeyName = this.getRelationship().getForeignKeyForModelClasses({
|
|
62
|
+
modelClass: parentModel.getModelClass(),
|
|
63
|
+
targetModelClass
|
|
64
|
+
})
|
|
62
65
|
const columnNameMap = targetModelClass.getColumnNameToAttributeNameMap()
|
|
63
66
|
const foreignKeyAttributeName = columnNameMap[foreignKeyName]
|
|
64
67
|
if (!foreignKeyAttributeName) throw new Error(`Unknown foreign key attribute name for ${foreignKeyName}`)
|
|
@@ -28,7 +28,7 @@ export default class VelociousDatabaseRecordHasOneInstanceRelationship extends B
|
|
|
28
28
|
* @returns {InstanceType<TMC>} - The build.
|
|
29
29
|
*/
|
|
30
30
|
build(data) {
|
|
31
|
-
const TargetModelClass =
|
|
31
|
+
const TargetModelClass = this.getBoundTargetModelClass()
|
|
32
32
|
|
|
33
33
|
if (!TargetModelClass) throw new Error("Can't build a new record without a target model class")
|
|
34
34
|
|
|
@@ -117,6 +117,23 @@ export default class VelociousDatabaseRecordBaseRelationship {
|
|
|
117
117
|
throw new Error("getForeignKey not implemented")
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Resolves an attribute-form foreign key through the model class that owns the physical column.
|
|
122
|
+
* @param {object} args - Bound relationship model classes.
|
|
123
|
+
* @param {typeof import("../index.js").default} args.modelClass - Relationship source model class.
|
|
124
|
+
* @param {typeof import("../index.js").default} args.targetModelClass - Relationship target model class.
|
|
125
|
+
* @returns {string} - Physical foreign-key column name.
|
|
126
|
+
*/
|
|
127
|
+
getForeignKeyForModelClasses({modelClass, targetModelClass}) {
|
|
128
|
+
this.getForeignKey()
|
|
129
|
+
|
|
130
|
+
if (!this.foreignKey) throw new Error(`Relationship ${this.modelClass.name}#${this.relationshipName} did not resolve a foreign key`)
|
|
131
|
+
|
|
132
|
+
const foreignKeyModelClass = this.getType() === "belongsTo" ? modelClass : targetModelClass
|
|
133
|
+
|
|
134
|
+
return foreignKeyModelClass.getAttributeNameToColumnNameMap()[this.foreignKey] || this.foreignKey
|
|
135
|
+
}
|
|
136
|
+
|
|
120
137
|
/**
|
|
121
138
|
* Runs get inverse of.
|
|
122
139
|
* @abstract
|