velocious 1.0.479 → 1.0.480

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/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "velocious": "build/bin/velocious.js"
4
4
  },
5
5
  "name": "velocious",
6
- "version": "1.0.479",
6
+ "version": "1.0.480",
7
7
  "main": "build/index.js",
8
8
  "types": "build/index.d.ts",
9
9
  "files": [
@@ -475,6 +475,7 @@
475
475
  /**
476
476
  * @typedef {object} TenantDatabaseProviderType
477
477
  * @property {function({configuration: import("./configuration.js").default, identifier: string}) : Array<?> | Promise<Array<?>>} listTenants - Lists tenants that should be created, checked, or migrated for this database identifier.
478
+ * @property {function({configuration: import("./configuration.js").default, identifier: string}) : Array<?> | Promise<Array<?>>} [listRestrictTenants] - Lists existing tenants that should be checked for dependent restrict destroys. Defaults to listTenants.
478
479
  * @property {function({configuration: import("./configuration.js").default, databaseConfiguration: DatabaseConfigurationType, identifier: string, tenant: ?}) : void | Promise<void>} [createDatabase] - Creates the tenant database/schema for one tenant.
479
480
  * @property {function({configuration: import("./configuration.js").default, databaseConfiguration: DatabaseConfigurationType, identifier: string, tenant: ?}) : void | Promise<void>} [dropDatabase] - Drops the tenant database/schema for one tenant.
480
481
  * @property {function({configuration: import("./configuration.js").default, databaseConfiguration: DatabaseConfigurationType, identifier: string, tenant: ?}) : void | Promise<void>} [checkTenant] - Checks one tenant database before generic connection validation.
@@ -17,6 +17,13 @@
17
17
  * @typedef {{new (changes?: Record<string, unknown>): T}} ModelConstructor
18
18
  */
19
19
 
20
+ /**
21
+ * RestrictInstanceRelationship type.
22
+ * @typedef {import("./instance-relationships/base.js").default & {query: () => ModelClassQuery<typeof VelociousDatabaseRecord>}} RestrictInstanceRelationship
23
+ */
24
+
25
+ /** @typedef {import("../../configuration-types.js").TenantDatabaseProviderType} TenantDatabaseProviderType */
26
+
20
27
  import timeout from "awaitery/build/timeout.js"
21
28
  import BelongsToInstanceRelationship from "./instance-relationships/belongs-to.js"
22
29
  import BelongsToRelationship from "./relationships/belongs-to.js"
@@ -1628,6 +1635,14 @@ class VelociousDatabaseRecord {
1628
1635
  this._tenantDatabaseIdentifierResolver = databaseIdentifierOrResolver
1629
1636
  }
1630
1637
 
1638
+ /**
1639
+ * Runs has tenant database identifier resolver.
1640
+ * @returns {boolean} - Whether this model resolves its database from the current tenant.
1641
+ */
1642
+ static hasTenantDatabaseIdentifierResolver() {
1643
+ return Boolean(this._tenantDatabaseIdentifierResolver)
1644
+ }
1645
+
1631
1646
  /**
1632
1647
  * Runs get tenant database identifier.
1633
1648
  * @param {?} [tenant] - Tenant override.
@@ -3359,6 +3374,148 @@ class VelociousDatabaseRecord {
3359
3374
  return this.getModelClass().connection()
3360
3375
  }
3361
3376
 
3377
+ /**
3378
+ * Counts dependent records for a `dependent: "restrict"` relationship.
3379
+ * @param {RestrictInstanceRelationship} instanceRelationship - Relationship instance to count.
3380
+ * @returns {Promise<number>} - Dependent row count.
3381
+ */
3382
+ async _dependentRestrictCount(instanceRelationship) {
3383
+ const TargetModelClass = instanceRelationship.getTargetModelClass()
3384
+
3385
+ if (!TargetModelClass || !TargetModelClass.hasTenantDatabaseIdentifierResolver()) {
3386
+ return await instanceRelationship.query().count()
3387
+ }
3388
+
3389
+ if (this.getModelClass().hasTenantDatabaseIdentifierResolver()) {
3390
+ return await instanceRelationship.query().count()
3391
+ }
3392
+
3393
+ return await this._dependentRestrictTenantCount(instanceRelationship, TargetModelClass)
3394
+ }
3395
+
3396
+ /**
3397
+ * Counts tenant-scoped dependent records across all provider-listed tenants.
3398
+ * @param {RestrictInstanceRelationship} instanceRelationship - Relationship instance to count.
3399
+ * @param {typeof VelociousDatabaseRecord} TargetModelClass - Related model class.
3400
+ * @returns {Promise<number>} - Dependent row count.
3401
+ */
3402
+ async _dependentRestrictTenantCount(instanceRelationship, TargetModelClass) {
3403
+ const configuration = this.getModelClass()._getConfiguration()
3404
+ const tenantDatabaseProviders = configuration.getTenantDatabaseProviders()
3405
+ const providerEntries = Object.entries(tenantDatabaseProviders)
3406
+ const targetIdentifier = TargetModelClass.getTenantDatabaseIdentifier(null)
3407
+
3408
+ if (providerEntries.length == 0) {
3409
+ throw new Error(`Cannot check dependent ${instanceRelationship.getRelationship().getRelationshipName()} because ${TargetModelClass.getModelName()} switches tenant databases but no tenant database providers are configured`)
3410
+ }
3411
+
3412
+ if (targetIdentifier) {
3413
+ const provider = tenantDatabaseProviders[targetIdentifier]
3414
+
3415
+ if (!provider) {
3416
+ throw new Error(`Cannot check dependent ${instanceRelationship.getRelationship().getRelationshipName()} because ${TargetModelClass.getModelName()} switches tenant database ${targetIdentifier} but no tenant database provider is configured for ${targetIdentifier}`)
3417
+ }
3418
+
3419
+ return await this._dependentRestrictProviderCount(instanceRelationship, TargetModelClass, targetIdentifier, provider)
3420
+ }
3421
+
3422
+ let matchingProviderSeen = false
3423
+
3424
+ for (const [identifier, provider] of providerEntries) {
3425
+ const tenants = await this._dependentRestrictProviderTenants(instanceRelationship, TargetModelClass, identifier, provider)
3426
+
3427
+ for (const tenant of tenants) {
3428
+ if (TargetModelClass.getTenantDatabaseIdentifier(tenant) != identifier) {
3429
+ continue
3430
+ }
3431
+
3432
+ matchingProviderSeen = true
3433
+
3434
+ const count = await configuration.runWithTenant(tenant, async () => {
3435
+ if (!configuration.isDatabaseIdentifierActive(identifier)) {
3436
+ throw new Error(`Tenant database identifier ${identifier} is inactive while checking dependent ${instanceRelationship.getRelationship().getRelationshipName()}`)
3437
+ }
3438
+
3439
+ return await configuration.ensureConnections({name: `Dependent restrict count: ${TargetModelClass.getModelName()}`}, async () => {
3440
+ return await instanceRelationship.query().count()
3441
+ })
3442
+ })
3443
+
3444
+ if (count > 0) return count
3445
+ }
3446
+ }
3447
+
3448
+ if (!matchingProviderSeen) {
3449
+ throw new Error(`Cannot check dependent ${instanceRelationship.getRelationship().getRelationshipName()} because no tenant database provider matched ${TargetModelClass.getModelName()}`)
3450
+ }
3451
+
3452
+ return 0
3453
+ }
3454
+
3455
+ /**
3456
+ * Counts tenant-scoped dependent records for one configured tenant provider.
3457
+ * @param {RestrictInstanceRelationship} instanceRelationship - Relationship instance to count.
3458
+ * @param {typeof VelociousDatabaseRecord} TargetModelClass - Related model class.
3459
+ * @param {string} identifier - Tenant database identifier.
3460
+ * @param {TenantDatabaseProviderType} provider - Tenant database provider.
3461
+ * @returns {Promise<number>} - Dependent row count.
3462
+ */
3463
+ async _dependentRestrictProviderCount(instanceRelationship, TargetModelClass, identifier, provider) {
3464
+ const configuration = this.getModelClass()._getConfiguration()
3465
+ const tenants = await this._dependentRestrictProviderTenants(instanceRelationship, TargetModelClass, identifier, provider)
3466
+
3467
+ for (const tenant of tenants) {
3468
+ const count = await configuration.runWithTenant(tenant, async () => {
3469
+ if (!configuration.isDatabaseIdentifierActive(identifier)) {
3470
+ throw new Error(`Tenant database identifier ${identifier} is inactive while checking dependent ${instanceRelationship.getRelationship().getRelationshipName()}`)
3471
+ }
3472
+
3473
+ return await configuration.ensureConnections({name: `Dependent restrict count: ${TargetModelClass.getModelName()}`}, async () => {
3474
+ return await instanceRelationship.query().count()
3475
+ })
3476
+ })
3477
+
3478
+ if (count > 0) return count
3479
+ }
3480
+
3481
+ return 0
3482
+ }
3483
+
3484
+ /**
3485
+ * Lists restrict-check tenants for one configured tenant provider.
3486
+ * @param {RestrictInstanceRelationship} instanceRelationship - Relationship instance to count.
3487
+ * @param {typeof VelociousDatabaseRecord} TargetModelClass - Related model class.
3488
+ * @param {string} identifier - Tenant database identifier.
3489
+ * @param {TenantDatabaseProviderType} provider - Tenant database provider.
3490
+ * @returns {Promise<Array<?>>} - Listed tenant objects.
3491
+ */
3492
+ async _dependentRestrictProviderTenants(instanceRelationship, TargetModelClass, identifier, provider) {
3493
+ const configuration = this.getModelClass()._getConfiguration()
3494
+ const listTenants = typeof provider.listRestrictTenants == "function"
3495
+ ? provider.listRestrictTenants
3496
+ : provider.listTenants
3497
+ const listTenantsMethodName = typeof provider.listRestrictTenants == "function"
3498
+ ? "listRestrictTenants"
3499
+ : "listTenants"
3500
+
3501
+ if (typeof listTenants != "function") {
3502
+ throw new Error(`Tenant database provider for ${identifier} must define listTenants or listRestrictTenants before dependent restrict can check ${instanceRelationship.getRelationship().getRelationshipName()}`)
3503
+ }
3504
+
3505
+ const tenants = await configuration.ensureConnections({name: `Dependent restrict tenants: ${TargetModelClass.getModelName()}`}, async () => {
3506
+ return await listTenants({
3507
+ configuration,
3508
+ identifier
3509
+ })
3510
+ })
3511
+
3512
+ if (!Array.isArray(tenants)) {
3513
+ throw new Error(`Tenant database provider for ${identifier} must return an array from ${listTenantsMethodName}`)
3514
+ }
3515
+
3516
+ return tenants
3517
+ }
3518
+
3362
3519
  /**
3363
3520
  * Destroys the record in the database and all of its dependent records.
3364
3521
  * @returns {Promise<void>} - Resolves when complete.
@@ -3368,8 +3525,8 @@ class VelociousDatabaseRecord {
3368
3525
 
3369
3526
  for (const relationship of this.getModelClass().getRelationships()) {
3370
3527
  if (relationship.getDependent() == "restrict") {
3371
- const instanceRelationship = /** @type {?} */ (this.getRelationshipByName(relationship.getRelationshipName()))
3372
- const count = await instanceRelationship.query().count()
3528
+ const instanceRelationship = /** @type {RestrictInstanceRelationship} */ (this.getRelationshipByName(relationship.getRelationshipName()))
3529
+ const count = await this._dependentRestrictCount(instanceRelationship)
3373
3530
 
3374
3531
  if (count > 0) {
3375
3532
  throw new Error(`Cannot delete record because dependent ${relationship.getRelationshipName()} exist`)