@jantstack/adonis-authz 2.5.0-alpha.3 → 2.5.0-alpha.4
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 +414 -35
- package/build/commands/authz_catalog_diff.d.ts +0 -1
- package/build/commands/authz_catalog_diff.js +0 -1
- package/build/commands/authz_catalog_prune_orphans.d.ts +0 -1
- package/build/commands/authz_catalog_prune_orphans.js +0 -1
- package/build/commands/authz_catalog_sync.d.ts +0 -1
- package/build/commands/authz_catalog_sync.js +0 -1
- package/build/commands/authz_freeze.d.ts +0 -1
- package/build/commands/authz_freeze.js +0 -1
- package/build/commands/authz_reconcile.d.ts +0 -1
- package/build/commands/authz_reconcile.js +0 -1
- package/build/commands/authz_relations_reconcile.d.ts +0 -1
- package/build/commands/authz_relations_reconcile.js +56 -8
- package/build/commands/authz_scopes_relay.d.ts +0 -1
- package/build/commands/authz_scopes_relay.js +49 -5
- package/build/commands/authz_scopes_requeue.d.ts +0 -1
- package/build/commands/authz_scopes_requeue.js +14 -2
- package/build/commands/authz_unfreeze.d.ts +0 -1
- package/build/commands/authz_unfreeze.js +0 -1
- package/build/commands/main.d.ts +0 -1
- package/build/commands/main.js +0 -1
- package/build/commands/openfga_provision.d.ts +10 -1
- package/build/commands/openfga_provision.js +43 -3
- package/build/configure.d.ts +0 -1
- package/build/configure.js +0 -1
- package/build/index.d.ts +7 -5
- package/build/index.js +10 -3
- package/build/providers/authz_provider.d.ts +0 -1
- package/build/providers/authz_provider.js +0 -1
- package/build/services/main.d.ts +0 -1
- package/build/services/main.js +0 -1
- package/build/services/relations.d.ts +0 -1
- package/build/services/relations.js +0 -1
- package/build/src/catalog/catalog.d.ts +0 -1
- package/build/src/catalog/catalog.js +0 -1
- package/build/src/catalog/catalog_cache.d.ts +13 -1
- package/build/src/catalog/catalog_cache.js +25 -1
- package/build/src/clock.d.ts +0 -1
- package/build/src/clock.js +0 -1
- package/build/src/define_config.d.ts +0 -1
- package/build/src/define_config.js +24 -1
- package/build/src/drivers/database_driver.d.ts +6 -1
- package/build/src/drivers/database_driver.js +22 -2
- package/build/src/drivers/database_relations_driver.d.ts +11 -5
- package/build/src/drivers/database_relations_driver.js +90 -18
- package/build/src/drivers/openfga_driver.d.ts +159 -26
- package/build/src/drivers/openfga_driver.js +882 -199
- package/build/src/drivers/openfga_facts.d.ts +91 -1
- package/build/src/drivers/openfga_facts.js +277 -3
- package/build/src/drivers/openfga_relations_driver.d.ts +41 -13
- package/build/src/drivers/openfga_relations_driver.js +422 -228
- package/build/src/errors.d.ts +121 -1
- package/build/src/errors.js +153 -1
- package/build/src/expiry.d.ts +51 -2
- package/build/src/expiry.js +96 -2
- package/build/src/freeze.d.ts +0 -1
- package/build/src/freeze.js +0 -1
- package/build/src/hierarchical_resolver.d.ts +0 -1
- package/build/src/hierarchical_resolver.js +0 -1
- package/build/src/http/app_access_middleware.d.ts +0 -1
- package/build/src/http/app_access_middleware.js +0 -1
- package/build/src/http/resource_access_middleware.d.ts +0 -1
- package/build/src/http/resource_access_middleware.js +0 -1
- package/build/src/identity.d.ts +2 -1
- package/build/src/identity.js +4 -1
- package/build/src/manager.d.ts +0 -1
- package/build/src/manager.js +156 -28
- package/build/src/memoize_ancestors.d.ts +0 -1
- package/build/src/memoize_ancestors.js +0 -1
- package/build/src/models/authz_assignment.d.ts +5 -6
- package/build/src/models/authz_assignment.js +0 -1
- package/build/src/models/authz_deny.d.ts +5 -6
- package/build/src/models/authz_deny.js +0 -1
- package/build/src/models/authz_permission.d.ts +5 -6
- package/build/src/models/authz_permission.js +0 -1
- package/build/src/models/authz_role.d.ts +5 -6
- package/build/src/models/authz_role.js +0 -1
- package/build/src/models/authz_role_permission.d.ts +5 -6
- package/build/src/models/authz_role_permission.js +0 -1
- package/build/src/openfga.d.ts +2 -3
- package/build/src/openfga.js +4 -2
- package/build/src/reconcile.d.ts +0 -1
- package/build/src/reconcile.js +0 -1
- package/build/src/relation_partition_trigger.d.ts +0 -1
- package/build/src/relation_partition_trigger.js +0 -1
- package/build/src/relations/check_many.d.ts +0 -1
- package/build/src/relations/check_many.js +0 -1
- package/build/src/relations/define_relations_config.d.ts +25 -3
- package/build/src/relations/define_relations_config.js +8 -3
- package/build/src/relations/deny_reach.d.ts +102 -0
- package/build/src/relations/deny_reach.js +230 -0
- package/build/src/relations/group_depth.d.ts +215 -0
- package/build/src/relations/group_depth.js +453 -0
- package/build/src/relations/manager.d.ts +14 -7
- package/build/src/relations/manager.js +60 -15
- package/build/src/relations/page.d.ts +19 -0
- package/build/src/relations/page.js +74 -0
- package/build/src/relations/reconcile.d.ts +58 -4
- package/build/src/relations/reconcile.js +266 -12
- package/build/src/relations/tuple_key.d.ts +0 -1
- package/build/src/relations/tuple_key.js +0 -1
- package/build/src/relations/tuple_order.d.ts +5 -4
- package/build/src/relations/tuple_order.js +5 -4
- package/build/src/relations_config_store.d.ts +57 -2
- package/build/src/relations_config_store.js +155 -5
- package/build/src/relations_reconcile.d.ts +0 -1
- package/build/src/relations_reconcile.js +0 -1
- package/build/src/scope_outbox.d.ts +0 -1
- package/build/src/scope_outbox.js +0 -1
- package/build/src/shared/backend_guard.d.ts +52 -1
- package/build/src/shared/backend_guard.js +164 -2
- package/build/src/shared/sql_expiry.d.ts +0 -1
- package/build/src/shared/sql_expiry.js +0 -1
- package/build/src/shared/transaction_guard.d.ts +0 -1
- package/build/src/shared/transaction_guard.js +0 -1
- package/build/src/sql_descendants.d.ts +0 -1
- package/build/src/sql_descendants.js +0 -1
- package/build/src/testing/contract.d.ts +7 -1
- package/build/src/testing/contract.js +77 -1
- package/build/src/testing/main.d.ts +0 -1
- package/build/src/testing/main.js +0 -1
- package/build/src/testing/migration_contract.d.ts +0 -1
- package/build/src/testing/migration_contract.js +0 -1
- package/build/src/testing/relations_contract.d.ts +7 -1
- package/build/src/testing/relations_contract.js +174 -7
- package/build/src/testing/relations_reconcile_contract.d.ts +0 -1
- package/build/src/testing/relations_reconcile_contract.js +0 -1
- package/build/src/testing/scope_outbox_contract.d.ts +0 -1
- package/build/src/testing/scope_outbox_contract.js +0 -1
- package/build/src/testing/scope_tree.d.ts +0 -1
- package/build/src/testing/scope_tree.js +0 -1
- package/build/src/testing/test_clock.d.ts +0 -1
- package/build/src/testing/test_clock.js +0 -1
- package/build/src/traits/authz_scopes.d.ts +0 -1
- package/build/src/traits/authz_scopes.js +0 -1
- package/build/src/traits/has_uuid.d.ts +5 -6
- package/build/src/traits/has_uuid.js +0 -1
- package/build/src/types.d.ts +89 -14
- package/build/src/types.js +4 -1
- package/build/stubs/config/authorization.stub +22 -6
- package/build/stubs/main.d.ts +0 -1
- package/build/stubs/main.js +0 -1
- package/package.json +4 -3
- package/build/commands/authz_catalog_diff.d.ts.map +0 -1
- package/build/commands/authz_catalog_diff.js.map +0 -1
- package/build/commands/authz_catalog_prune_orphans.d.ts.map +0 -1
- package/build/commands/authz_catalog_prune_orphans.js.map +0 -1
- package/build/commands/authz_catalog_sync.d.ts.map +0 -1
- package/build/commands/authz_catalog_sync.js.map +0 -1
- package/build/commands/authz_freeze.d.ts.map +0 -1
- package/build/commands/authz_freeze.js.map +0 -1
- package/build/commands/authz_reconcile.d.ts.map +0 -1
- package/build/commands/authz_reconcile.js.map +0 -1
- package/build/commands/authz_relations_reconcile.d.ts.map +0 -1
- package/build/commands/authz_relations_reconcile.js.map +0 -1
- package/build/commands/authz_scopes_relay.d.ts.map +0 -1
- package/build/commands/authz_scopes_relay.js.map +0 -1
- package/build/commands/authz_scopes_requeue.d.ts.map +0 -1
- package/build/commands/authz_scopes_requeue.js.map +0 -1
- package/build/commands/authz_unfreeze.d.ts.map +0 -1
- package/build/commands/authz_unfreeze.js.map +0 -1
- package/build/commands/main.d.ts.map +0 -1
- package/build/commands/main.js.map +0 -1
- package/build/commands/openfga_provision.d.ts.map +0 -1
- package/build/commands/openfga_provision.js.map +0 -1
- package/build/configure.d.ts.map +0 -1
- package/build/configure.js.map +0 -1
- package/build/index.d.ts.map +0 -1
- package/build/index.js.map +0 -1
- package/build/providers/authz_provider.d.ts.map +0 -1
- package/build/providers/authz_provider.js.map +0 -1
- package/build/services/main.d.ts.map +0 -1
- package/build/services/main.js.map +0 -1
- package/build/services/relations.d.ts.map +0 -1
- package/build/services/relations.js.map +0 -1
- package/build/src/catalog/catalog.d.ts.map +0 -1
- package/build/src/catalog/catalog.js.map +0 -1
- package/build/src/catalog/catalog_cache.d.ts.map +0 -1
- package/build/src/catalog/catalog_cache.js.map +0 -1
- package/build/src/clock.d.ts.map +0 -1
- package/build/src/clock.js.map +0 -1
- package/build/src/define_config.d.ts.map +0 -1
- package/build/src/define_config.js.map +0 -1
- package/build/src/drivers/database_driver.d.ts.map +0 -1
- package/build/src/drivers/database_driver.js.map +0 -1
- package/build/src/drivers/database_relations_driver.d.ts.map +0 -1
- package/build/src/drivers/database_relations_driver.js.map +0 -1
- package/build/src/drivers/openfga_driver.d.ts.map +0 -1
- package/build/src/drivers/openfga_driver.js.map +0 -1
- package/build/src/drivers/openfga_facts.d.ts.map +0 -1
- package/build/src/drivers/openfga_facts.js.map +0 -1
- package/build/src/drivers/openfga_relations_driver.d.ts.map +0 -1
- package/build/src/drivers/openfga_relations_driver.js.map +0 -1
- package/build/src/errors.d.ts.map +0 -1
- package/build/src/errors.js.map +0 -1
- package/build/src/expiry.d.ts.map +0 -1
- package/build/src/expiry.js.map +0 -1
- package/build/src/freeze.d.ts.map +0 -1
- package/build/src/freeze.js.map +0 -1
- package/build/src/hierarchical_resolver.d.ts.map +0 -1
- package/build/src/hierarchical_resolver.js.map +0 -1
- package/build/src/http/app_access_middleware.d.ts.map +0 -1
- package/build/src/http/app_access_middleware.js.map +0 -1
- package/build/src/http/resource_access_middleware.d.ts.map +0 -1
- package/build/src/http/resource_access_middleware.js.map +0 -1
- package/build/src/identity.d.ts.map +0 -1
- package/build/src/identity.js.map +0 -1
- package/build/src/manager.d.ts.map +0 -1
- package/build/src/manager.js.map +0 -1
- package/build/src/memoize_ancestors.d.ts.map +0 -1
- package/build/src/memoize_ancestors.js.map +0 -1
- package/build/src/models/authz_assignment.d.ts.map +0 -1
- package/build/src/models/authz_assignment.js.map +0 -1
- package/build/src/models/authz_deny.d.ts.map +0 -1
- package/build/src/models/authz_deny.js.map +0 -1
- package/build/src/models/authz_permission.d.ts.map +0 -1
- package/build/src/models/authz_permission.js.map +0 -1
- package/build/src/models/authz_role.d.ts.map +0 -1
- package/build/src/models/authz_role.js.map +0 -1
- package/build/src/models/authz_role_permission.d.ts.map +0 -1
- package/build/src/models/authz_role_permission.js.map +0 -1
- package/build/src/openfga.d.ts.map +0 -1
- package/build/src/openfga.js.map +0 -1
- package/build/src/reconcile.d.ts.map +0 -1
- package/build/src/reconcile.js.map +0 -1
- package/build/src/relation_partition_trigger.d.ts.map +0 -1
- package/build/src/relation_partition_trigger.js.map +0 -1
- package/build/src/relations/check_many.d.ts.map +0 -1
- package/build/src/relations/check_many.js.map +0 -1
- package/build/src/relations/define_relations_config.d.ts.map +0 -1
- package/build/src/relations/define_relations_config.js.map +0 -1
- package/build/src/relations/manager.d.ts.map +0 -1
- package/build/src/relations/manager.js.map +0 -1
- package/build/src/relations/reconcile.d.ts.map +0 -1
- package/build/src/relations/reconcile.js.map +0 -1
- package/build/src/relations/tuple_key.d.ts.map +0 -1
- package/build/src/relations/tuple_key.js.map +0 -1
- package/build/src/relations/tuple_order.d.ts.map +0 -1
- package/build/src/relations/tuple_order.js.map +0 -1
- package/build/src/relations_config_store.d.ts.map +0 -1
- package/build/src/relations_config_store.js.map +0 -1
- package/build/src/relations_reconcile.d.ts.map +0 -1
- package/build/src/relations_reconcile.js.map +0 -1
- package/build/src/scope_outbox.d.ts.map +0 -1
- package/build/src/scope_outbox.js.map +0 -1
- package/build/src/shared/backend_guard.d.ts.map +0 -1
- package/build/src/shared/backend_guard.js.map +0 -1
- package/build/src/shared/sql_expiry.d.ts.map +0 -1
- package/build/src/shared/sql_expiry.js.map +0 -1
- package/build/src/shared/transaction_guard.d.ts.map +0 -1
- package/build/src/shared/transaction_guard.js.map +0 -1
- package/build/src/sql_descendants.d.ts.map +0 -1
- package/build/src/sql_descendants.js.map +0 -1
- package/build/src/testing/contract.d.ts.map +0 -1
- package/build/src/testing/contract.js.map +0 -1
- package/build/src/testing/main.d.ts.map +0 -1
- package/build/src/testing/main.js.map +0 -1
- package/build/src/testing/migration_contract.d.ts.map +0 -1
- package/build/src/testing/migration_contract.js.map +0 -1
- package/build/src/testing/relations_contract.d.ts.map +0 -1
- package/build/src/testing/relations_contract.js.map +0 -1
- package/build/src/testing/relations_reconcile_contract.d.ts.map +0 -1
- package/build/src/testing/relations_reconcile_contract.js.map +0 -1
- package/build/src/testing/scope_outbox_contract.d.ts.map +0 -1
- package/build/src/testing/scope_outbox_contract.js.map +0 -1
- package/build/src/testing/scope_tree.d.ts.map +0 -1
- package/build/src/testing/scope_tree.js.map +0 -1
- package/build/src/testing/test_clock.d.ts.map +0 -1
- package/build/src/testing/test_clock.js.map +0 -1
- package/build/src/traits/authz_scopes.d.ts.map +0 -1
- package/build/src/traits/authz_scopes.js.map +0 -1
- package/build/src/traits/has_uuid.d.ts.map +0 -1
- package/build/src/traits/has_uuid.js.map +0 -1
- package/build/src/types.d.ts.map +0 -1
- package/build/src/types.js.map +0 -1
- package/build/stubs/main.d.ts.map +0 -1
- package/build/stubs/main.js.map +0 -1
|
@@ -420,6 +420,31 @@ export class CatalogCache {
|
|
|
420
420
|
}
|
|
421
421
|
return this.#reload();
|
|
422
422
|
}
|
|
423
|
+
/**
|
|
424
|
+
* **La foto para una ESCRITURA** (lote C · carreras, resolución 2 del juez = K4): contrasta la versión con la fila
|
|
425
|
+
* SIEMPRE, ignore o no la ventana `{ everyMs }`. Con `'always'` es exactamente `view()`. La ventana es para DECIDIR
|
|
426
|
+
* (invariante 14: una pregunta puede responder con un catálogo de hasta `everyMs` de antigüedad); una escritura del driver
|
|
427
|
+
* deja ESTADO en el store que no caduca —el barrido de `moved`, el settle, la proyección—, y con un memo rancio un rol
|
|
428
|
+
* recién definido en otro proceso no se barre (fail-open persistente medido, K4). Coste: el SELECT por PK de `'always'`.
|
|
429
|
+
*
|
|
430
|
+
* **Nunca se suma a una comprobación ni a una carga EN VUELO** (asentar · cierre 2, ⚪ 6 / O-6 del auditor): la que ya
|
|
431
|
+
* estaba en marcha pudo leer la fila ANTES de la escritura que precede a esta llamada (la propia, o la de otro proceso
|
|
432
|
+
* que la escritura acaba de observar) y serviría esa foto. Se espera la carga en vuelo y se lee la versión otra vez;
|
|
433
|
+
* una carga que arranca DESPUÉS de esta llamada sí se comparte (ya es posterior).
|
|
434
|
+
*/
|
|
435
|
+
async freshView() {
|
|
436
|
+
if (this.#loading)
|
|
437
|
+
await this.#loading.catch(() => undefined);
|
|
438
|
+
const current = this.#view;
|
|
439
|
+
if (!current || !this.#isFresh())
|
|
440
|
+
return this.#reload();
|
|
441
|
+
const dbVersion = await this.#readVersion();
|
|
442
|
+
if (this.#view === current && this.#isFresh() && dbVersion === current.version) {
|
|
443
|
+
this.#checkedAt = this.#now();
|
|
444
|
+
return current;
|
|
445
|
+
}
|
|
446
|
+
return this.#reload(dbVersion);
|
|
447
|
+
}
|
|
423
448
|
/** Una carga compartida; `knownVersion` es la fila recién leída por una revalidación (se ahorra releerla). */
|
|
424
449
|
#reload(knownVersion) {
|
|
425
450
|
if (!this.#loading) {
|
|
@@ -663,4 +688,3 @@ function buildCatalogView(permissions, roles, links, loadedAt, version) {
|
|
|
663
688
|
version,
|
|
664
689
|
};
|
|
665
690
|
}
|
|
666
|
-
//# sourceMappingURL=catalog_cache.js.map
|
package/build/src/clock.d.ts
CHANGED
|
@@ -21,4 +21,3 @@ export type Clock = () => Date;
|
|
|
21
21
|
export declare const systemClock: Clock;
|
|
22
22
|
/** `now` tiene que ser una función; se valida al construir (500 `E_AUTHZ_CONFIG` es del llamante). */
|
|
23
23
|
export declare function isClock(value: unknown): value is Clock;
|
|
24
|
-
//# sourceMappingURL=clock.d.ts.map
|
package/build/src/clock.js
CHANGED
|
@@ -77,12 +77,36 @@ function assertConfigBlock(value, path) {
|
|
|
77
77
|
`una clave conocida no es «nada»: se ignoraría en silencio (plan 2.5 · B2).`);
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* **Lápidas: claves que NO existen aquí pero que la letra de una decisión firmada nombra así** (cierre-2 de R1 · D1 (a),
|
|
82
|
+
* decisión del dueño 2026-10-20: «`relations.maxGroupDepth` ⇒ 500 con mensaje que apunta a `defineRelationsConfig`»).
|
|
83
|
+
* La decisión (5) escribió `relations.maxGroupDepth`; el mando vive en `defineRelationsConfig` (la config que capturan
|
|
84
|
+
* las factories de los drivers, `manager.driver()` y `runRelationsReconcile`) y se lee como `relations.config.maxGroupDepth`.
|
|
85
|
+
* Sin alias (una clave hermana se validaría y no gobernaría tres de las cuatro rutas): la ruta literal es el MISMO 500
|
|
86
|
+
* de clave desconocida, con la receta del sitio bueno en vez de la lista de claves.
|
|
87
|
+
*/
|
|
88
|
+
const MISPLACED_CONFIG_KEYS = Object.freeze({
|
|
89
|
+
'relations.maxGroupDepth': "defineConfig: la clave 'relations.maxGroupDepth' no existe. El techo de anidación de grupos va en la config de " +
|
|
90
|
+
'RELACIONES: defineRelationsConfig({ objectTypes, maxGroupDepth, database: { enforceGroupDepth } }), y ese objeto en ' +
|
|
91
|
+
'relations.config. Es la config que capturan los drivers (las factories de relations.drivers), manager.driver() y ' +
|
|
92
|
+
'runRelationsReconcile (authz:relations:reconcile): una clave hermana de relations no llegaría a ninguno y el límite ' +
|
|
93
|
+
'no se aplicaría.',
|
|
94
|
+
'relations.enforceGroupDepth': "defineConfig: la clave 'relations.enforceGroupDepth' no existe. El opt-in del techo de anidación de grupos en " +
|
|
95
|
+
'database va en la config de RELACIONES: defineRelationsConfig({ objectTypes, maxGroupDepth, database: { ' +
|
|
96
|
+
'enforceGroupDepth: true } }), y ese objeto en relations.config (la que capturan los drivers).',
|
|
97
|
+
});
|
|
80
98
|
function assertKnownConfigKeys(value, known, path) {
|
|
81
99
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
82
100
|
return;
|
|
83
101
|
for (const key of Object.keys(value)) {
|
|
84
102
|
if (known.includes(key))
|
|
85
103
|
continue;
|
|
104
|
+
// Cierre-3 · R-6: `Object.hasOwn` — una clave `constructor`/`toString` (o un `__proto__` propio de un JSON) leía el
|
|
105
|
+
// prototipo de la tabla y el 500 llevaba de mensaje `function Object() { [native code] }`.
|
|
106
|
+
const dotted = path === '' ? key : `${path}.${key}`;
|
|
107
|
+
const tombstone = Object.hasOwn(MISPLACED_CONFIG_KEYS, dotted) ? MISPLACED_CONFIG_KEYS[dotted] : undefined;
|
|
108
|
+
if (tombstone !== undefined)
|
|
109
|
+
throw new AuthorizationConfigError(tombstone);
|
|
86
110
|
const where = path === '' ? `'${key}' en la raíz` : `'${path}.${key}'`;
|
|
87
111
|
throw new AuthorizationConfigError(`defineConfig: la clave ${where} no existe (claves válidas en ${path === '' ? 'la raíz' : path}: ${known.join(', ')}). ` +
|
|
88
112
|
`Ninguna clave del config se descarta en silencio (plan 2.5 · B2): un typo ('requireWithing') sería una ` +
|
|
@@ -121,4 +145,3 @@ export function assertAuthorizationConfigShape(config) {
|
|
|
121
145
|
assertKnownConfigKeys(c[block], AUTHORIZATION_CONFIG_KEYS[block], block);
|
|
122
146
|
}
|
|
123
147
|
}
|
|
124
|
-
//# sourceMappingURL=define_config.js.map
|
|
@@ -204,6 +204,12 @@ export declare class DatabaseAuthorizationDriver implements AuthorizationDriver
|
|
|
204
204
|
*/
|
|
205
205
|
readonly catalog: CatalogCache;
|
|
206
206
|
constructor(options?: DatabaseDriverOptions);
|
|
207
|
+
/**
|
|
208
|
+
* **El catálogo de una ESCRITURA** (K4; asentar · cierre 2, D-1): con la versión contrastada en fresco SIEMPRE
|
|
209
|
+
* (`CatalogCache.freshView`), ignore o no la ventana `{ everyMs }`. Un `catalog` inyectado que no lo traiga cae en
|
|
210
|
+
* `view()` (el contrato del memo es suyo). Gemelo de `OpenFgaAuthorizationDriver.writeCatalog`.
|
|
211
|
+
*/
|
|
212
|
+
private writeCatalog;
|
|
207
213
|
/**
|
|
208
214
|
* Vista de este driver con OTRO resolutor de la cadena y el mismo estado
|
|
209
215
|
* (conexión, memo del catálogo, deadline). Es lo que usa
|
|
@@ -393,4 +399,3 @@ export declare class DatabaseAuthorizationDriver implements AuthorizationDriver
|
|
|
393
399
|
*/
|
|
394
400
|
private eachReconcileRow;
|
|
395
401
|
}
|
|
396
|
-
//# sourceMappingURL=database_driver.d.ts.map
|
|
@@ -327,12 +327,24 @@ export class DatabaseAuthorizationDriver {
|
|
|
327
327
|
// `resolveChain` y la barrera del freeze (autoridad). Un despliegue con
|
|
328
328
|
// pool 1 lo declara `false` (ver `DatabaseDriverOptions`).
|
|
329
329
|
transactionalWrites: this.transactionalWrites,
|
|
330
|
+
// Estrategia 2.5.0 · firma 4 (D15): `false` EXPLÍCITO — `database` es el driver ESTABLE de 2.5.0 (el experimental
|
|
331
|
+
// es `openfga`). Explícito para que la copia de `authorization.capabilities()` y este objeto digan lo mismo.
|
|
332
|
+
experimental: false,
|
|
330
333
|
});
|
|
331
334
|
assertCatalogOptions('DatabaseAuthorizationDriver', options);
|
|
332
335
|
this.catalog =
|
|
333
336
|
options.catalog ??
|
|
334
337
|
new CatalogCache({ driver: 'database', timeoutMs: this.timeoutMs, revalidate: options.catalogRevalidate });
|
|
335
338
|
}
|
|
339
|
+
/**
|
|
340
|
+
* **El catálogo de una ESCRITURA** (K4; asentar · cierre 2, D-1): con la versión contrastada en fresco SIEMPRE
|
|
341
|
+
* (`CatalogCache.freshView`), ignore o no la ventana `{ everyMs }`. Un `catalog` inyectado que no lo traiga cae en
|
|
342
|
+
* `view()` (el contrato del memo es suyo). Gemelo de `OpenFgaAuthorizationDriver.writeCatalog`.
|
|
343
|
+
*/
|
|
344
|
+
writeCatalog() {
|
|
345
|
+
const catalog = this.catalog;
|
|
346
|
+
return typeof catalog.freshView === 'function' ? catalog.freshView() : catalog.view();
|
|
347
|
+
}
|
|
336
348
|
/**
|
|
337
349
|
* Vista de este driver con OTRO resolutor de la cadena y el mismo estado
|
|
338
350
|
* (conexión, memo del catálogo, deadline). Es lo que usa
|
|
@@ -610,7 +622,11 @@ export class DatabaseAuthorizationDriver {
|
|
|
610
622
|
// a lo sumo uno es visible ahí y los demás serían filas huérfanas; el
|
|
611
623
|
// scope puede no existir ya para el árbol (D8), así que no se filtra por
|
|
612
624
|
// visibilidad — quitar nunca concede.
|
|
613
|
-
|
|
625
|
+
// Asentar · cierre 2 (D-1 del re-ataque; gemelo del 🟡 5 de `openfga`, K4): el catálogo de una ESCRITURA, con la
|
|
626
|
+
// versión en fresco. Con el memo de `{ everyMs }` un local recién definido en otro proceso que ensombrece al global
|
|
627
|
+
// no salía en `rolesToRevoke`: el DELETE no tocaba su fila y la asignación local seguía concediendo (un «revocado»
|
|
628
|
+
// falso, mudo y persistente).
|
|
629
|
+
const named = rolesToRevoke(await this.writeCatalog(), role, scope);
|
|
614
630
|
// Sin cadena con la que canonizar, TODAS las ortografías de las que el
|
|
615
631
|
// uuid puede ser alias (3b-8 · A4, mismo fan-out que `scopes.detached`):
|
|
616
632
|
// con una sola, un alias hacía del DELETE un no-op silencioso y la fila
|
|
@@ -1141,6 +1157,11 @@ export class DatabaseAuthorizationDriver {
|
|
|
1141
1157
|
scope_type: target.type,
|
|
1142
1158
|
scope_uuid: toDbScopeUuid(target),
|
|
1143
1159
|
};
|
|
1160
|
+
// Cierre-2 de L11 (C1): un deny que el origen guarda con una condición ajena se migra como deny (fail-closed:
|
|
1161
|
+
// perderlo concedería) pero se DECLARA: `invalid-condition` es deriva y el operador decide.
|
|
1162
|
+
if (fact.invalidCondition !== undefined) {
|
|
1163
|
+
note('deny', 'invalid-condition', `${fact.detail} (${fact.invalidCondition})`);
|
|
1164
|
+
}
|
|
1144
1165
|
usableFacts += 1;
|
|
1145
1166
|
const denyId = denyKey(row);
|
|
1146
1167
|
if (wantedDenies.has(denyId))
|
|
@@ -1309,4 +1330,3 @@ export class DatabaseAuthorizationDriver {
|
|
|
1309
1330
|
}
|
|
1310
1331
|
}
|
|
1311
1332
|
}
|
|
1312
|
-
//# sourceMappingURL=database_driver.js.map
|
|
@@ -88,6 +88,13 @@ export declare class DatabaseRelationsDriver implements RelationsDriver {
|
|
|
88
88
|
deny(subject: RelSubject, relation: string, object: RelObject, partition: ScopeRef, options?: RelationDenyOptions): Promise<void>;
|
|
89
89
|
/** `removeDeny`: borra la tupla `denied_<rel>`. Sobre CUALQUIER tipo declarado (D8/R2-4 b). No-op si no está. */
|
|
90
90
|
removeDeny(subject: RelSubject, relation: string, object: RelObject, partition: ScopeRef, options?: RelationDenyOptions): Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* **El techo de anidación de grupos de ESTE driver** (lote «antes de RC» · R1, decisión del dueño 2026-10-17 (5)):
|
|
93
|
+
* `database` no tiene techo técnico (la CTE deduplica y termina con ciclos), así que es `null` SALVO el opt-in
|
|
94
|
+
* explícito `database.enforceGroupDepth: true`, que aplica el `maxGroupDepth` de la config (obligatorio y numérico
|
|
95
|
+
* con el opt-in: lo valida `defineRelationsConfig` al arrancar).
|
|
96
|
+
*/
|
|
97
|
+
groupDepthLimit(): Promise<number | null>;
|
|
91
98
|
unrelate(subject: RelSubject, relation: string, object: RelObject, partition: ScopeRef, options?: RelationWriteOptions): Promise<void>;
|
|
92
99
|
/**
|
|
93
100
|
* **F-05 en las LECTURAS y las PURGAS del driver** (2.4.0-alpha.3, cierre
|
|
@@ -117,14 +124,14 @@ export declare class DatabaseRelationsDriver implements RelationsDriver {
|
|
|
117
124
|
* misma función que el doble y `openfga` cuando alinea su `batchCheck`). Tope, `[]` y F-05 por ítem como `checkMany`.
|
|
118
125
|
*/
|
|
119
126
|
decideMany(subject: RelSubject, relation: string, objects: RelObject[], partition: ScopeRef): Promise<RelationDecision[]>;
|
|
120
|
-
listObjects(subject: RelSubject, relation: string, objectType: string, partition: ScopeRef
|
|
121
|
-
listSubjects(relation: string, object: RelObject, partition: ScopeRef
|
|
127
|
+
listObjects(subject: RelSubject, relation: string, objectType: string, partition: ScopeRef): Promise<RelationObjectsPage>;
|
|
128
|
+
listSubjects(relation: string, object: RelObject, partition: ScopeRef): Promise<RelationSubjectsPage>;
|
|
122
129
|
/**
|
|
123
130
|
* **`listDenies`** (lote D · D1): los sujetos con deny DIRECTO de `relation` sobre `object` —hechos, como
|
|
124
131
|
* `listSubjects` sobre la relación almacenada `denied_<rel>`; no resta ni deriva—. Sobre CUALQUIER tipo declarado
|
|
125
132
|
* (D8/R2-4 b: con el opt-in retirado, los denies vivos se siguen pudiendo listar y borrar).
|
|
126
133
|
*/
|
|
127
|
-
listDenies(relation: string, object: RelObject, partition: ScopeRef
|
|
134
|
+
listDenies(relation: string, object: RelObject, partition: ScopeRef): Promise<RelationSubjectsPage>;
|
|
128
135
|
/**
|
|
129
136
|
* **`denyPhase` — ¿admite este driver escrituras de deny en el tipo?** (lote D · D2, R2-5): la fase del opt-in EN
|
|
130
137
|
* LA CONFIG DE ESTE DRIVER (`'write'`, `'read'` o `null`). F-05 estricto del par (422), cero sentencias: es
|
|
@@ -140,7 +147,7 @@ export declare class DatabaseRelationsDriver implements RelationsDriver {
|
|
|
140
147
|
* directos): en un grupo con `member(g1#member, g2)`, `membersOf(g2)` trae a
|
|
141
148
|
* los holders de g1 y `listSubjects(member, g2)` solo el userset `g1#member`.
|
|
142
149
|
*/
|
|
143
|
-
membersOf(object: RelObject, relation: string, partition: ScopeRef
|
|
150
|
+
membersOf(object: RelObject, relation: string, partition: ScopeRef): Promise<RelationSubjectsPage>;
|
|
144
151
|
purgeObject(object: RelObject, partition: ScopeRef, options?: RelationTransactionOptions): Promise<void>;
|
|
145
152
|
purgeSubject(subject: RelSubject, partition: ScopeRef, options?: RelationTransactionOptions): Promise<void>;
|
|
146
153
|
/**
|
|
@@ -184,4 +191,3 @@ export declare class DatabaseRelationsDriver implements RelationsDriver {
|
|
|
184
191
|
*/
|
|
185
192
|
enumerateRelationsMany(partitions: ScopeRef[], page?: RelationPage): Promise<RelationTuplePage>;
|
|
186
193
|
}
|
|
187
|
-
//# sourceMappingURL=database_relations_driver.d.ts.map
|
|
@@ -29,13 +29,15 @@ import { AuthorizationBackendTimeoutError, AuthorizationConfigError, InvalidIden
|
|
|
29
29
|
import { assertPartition, assertSubject, assertExpiresAt, partitionKey } from '../identity.js';
|
|
30
30
|
import { relationPartitionTrigger, relationPartitionTriggerDrops } from '../relation_partition_trigger.js';
|
|
31
31
|
import { isClock, systemClock } from '../clock.js';
|
|
32
|
-
import { resolveGrantExpiry, sameInstant } from '../expiry.js';
|
|
32
|
+
import { isActiveExpiry, resolveGrantExpiry, sameInstant } from '../expiry.js';
|
|
33
|
+
import { assertGroupDepthSettings, groupDepthLimitError, groupNestingVerdict, isGroupNesting, plannedByReconcile } from '../relations/group_depth.js';
|
|
33
34
|
import { sqlExpiryCodec } from '../shared/sql_expiry.js';
|
|
34
35
|
import { guardSql, isDeadlock, isUniqueViolation, withDeadline } from '../shared/backend_guard.js';
|
|
35
36
|
import { assertCallerTransaction } from '../shared/transaction_guard.js';
|
|
36
37
|
import { isRelUserset } from '../types.js';
|
|
37
38
|
import { assertRelationDeclared, assertObjectTypeDeclared, assertUsersetSubject, assertRelationDenyable, assertDenySubject, assertDenyNoExpiry, cutPathAllows, decideFrom, deniableAbove, deniedRelationOf, storedRelationKind, denyPhaseOf, relationsDown, RELATION_DENY_PREFIX, } from '../relations/define_relations_config.js';
|
|
38
39
|
import { assertCheckManyObjects, distinctObjects, expandByPosition } from '../relations/check_many.js';
|
|
40
|
+
import { assertEnumeratePage, assertNoListPage, enumerateLimit } from '../relations/page.js';
|
|
39
41
|
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
40
42
|
/**
|
|
41
43
|
* **Cota de candidatos de `membersOf` cuando el objeto tiene un deny vigente** (lote D · cierre 1 de D1, 🟡 4): con deny
|
|
@@ -58,9 +60,6 @@ const GROUP_MEMBER = 'member';
|
|
|
58
60
|
* userset puede colisionar con ella.
|
|
59
61
|
*/
|
|
60
62
|
const HOLDER_RELATION = '';
|
|
61
|
-
/** Tamaño de página por defecto y tope de `enumerateRelations` (origen de reconcile). */
|
|
62
|
-
const DEFAULT_ENUMERATE_LIMIT = 100;
|
|
63
|
-
const MAX_ENUMERATE_LIMIT = 1_000;
|
|
64
63
|
/** Formato de id de relación: minúsculas, dígitos y `._-`; sin separadores del store. */
|
|
65
64
|
const RELATION_ID_FORMAT = /^[a-z0-9_.-]+$/;
|
|
66
65
|
const DIALECTS = {
|
|
@@ -116,6 +115,9 @@ export class DatabaseRelationsDriver {
|
|
|
116
115
|
#database;
|
|
117
116
|
#expiryCodec;
|
|
118
117
|
constructor(config, options = {}, database = db) {
|
|
118
|
+
// Cierre de R1 · 🟡 4 del auditor: el driver RE-VALIDA el techo de la config (una config extendida con spread no pasó
|
|
119
|
+
// por `defineRelationsConfig`; `NaN` o `enforceGroupDepth: true` sin número apagaban el gate en silencio). 500.
|
|
120
|
+
assertGroupDepthSettings(config.maxGroupDepth, config.enforceGroupDepth, 'DatabaseRelationsDriver');
|
|
119
121
|
this.#config = config;
|
|
120
122
|
this.#connectionName = options.connection;
|
|
121
123
|
this.#timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
@@ -137,6 +139,8 @@ export class DatabaseRelationsDriver {
|
|
|
137
139
|
// Cierre 2 de D2 · 🟠 N1: renovar (otra caducidad) es delete + insert en UNA transacción (`#upsert`): ningún
|
|
138
140
|
// instante sin la tupla, así que una membresía que transporta un deny se renueva sin ventana.
|
|
139
141
|
atomicRenewal: true,
|
|
142
|
+
// Estrategia 2.5.0 · firma 4 (D15): `false` EXPLÍCITO — `database` es el driver ESTABLE de 2.5.0.
|
|
143
|
+
experimental: false,
|
|
140
144
|
});
|
|
141
145
|
}
|
|
142
146
|
/**
|
|
@@ -367,6 +371,54 @@ export class DatabaseRelationsDriver {
|
|
|
367
371
|
this.#assertObject(object);
|
|
368
372
|
await this.#deleteTuple('removeDeny', subject, deniedRelationOf(relation), object, partition, options);
|
|
369
373
|
}
|
|
374
|
+
/**
|
|
375
|
+
* **El techo de anidación de grupos de ESTE driver** (lote «antes de RC» · R1, decisión del dueño 2026-10-17 (5)):
|
|
376
|
+
* `database` no tiene techo técnico (la CTE deduplica y termina con ciclos), así que es `null` SALVO el opt-in
|
|
377
|
+
* explícito `database.enforceGroupDepth: true`, que aplica el `maxGroupDepth` de la config (obligatorio y numérico
|
|
378
|
+
* con el opt-in: lo valida `defineRelationsConfig` al arrancar).
|
|
379
|
+
*/
|
|
380
|
+
async groupDepthLimit() {
|
|
381
|
+
return this.#groupDepthLimit();
|
|
382
|
+
}
|
|
383
|
+
#groupDepthLimit() {
|
|
384
|
+
const { maxGroupDepth, enforceGroupDepth } = assertGroupDepthSettings(this.#config.maxGroupDepth, this.#config.enforceGroupDepth, 'DatabaseRelationsDriver');
|
|
385
|
+
return enforceGroupDepth && typeof maxGroupDepth === 'number' ? maxGroupDepth : null;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* **El gate de R1 en `database`** (opt-in): la arista `group:<parent>#member@group:<child>#member` que se va a escribir
|
|
389
|
+
* VIVA no puede cerrar un ciclo ni dejar un camino de grupos anidados más largo que el techo. El grafo se LEE POR EL
|
|
390
|
+
* MISMO CLIENTE QUE ESCRIBE (`trx`: la transacción del llamante con `{ transaction }`, o la interna de la escritura) —
|
|
391
|
+
* T-6 del tester: leído por la conexión del motor, un llamante que en UNA transacción escribe `A⊂B` y luego `B⊂C`
|
|
392
|
+
* pasaría las dos (la segunda no ve la primera, sin confirmar). Lo que esto NO cierra, declarado: dos transacciones
|
|
393
|
+
* CONCURRENTES que escriben cada una media cadena (cada una ve su foto, sin cerrojo) — la carrera entre procesos.
|
|
394
|
+
* Una sentencia por nivel del BFS (y por cada 500 grupos del nivel), con la caducidad ESTRICTA de la lectura.
|
|
395
|
+
*/
|
|
396
|
+
async #assertGroupNesting(trx, operation, partitionKey, edge, now, limit) {
|
|
397
|
+
const neighbours = async (groups, column) => {
|
|
398
|
+
const out = [];
|
|
399
|
+
for (let i = 0; i < groups.length; i += 500) {
|
|
400
|
+
const chunk = groups.slice(i, i + 500);
|
|
401
|
+
const rows = await this.#sql(`${operation}.groupDepth`, () => this.#whereActive(trx
|
|
402
|
+
.from(RELATIONS_TABLE)
|
|
403
|
+
.where('partition_key', partitionKey)
|
|
404
|
+
.where('object_type', GROUP_TYPE)
|
|
405
|
+
.where('relation', GROUP_MEMBER)
|
|
406
|
+
.where('subject_type', GROUP_TYPE)
|
|
407
|
+
.where('subject_relation', GROUP_MEMBER)
|
|
408
|
+
.whereIn(column, chunk), now).select({ child: 'subject_uuid', parent: 'object_uuid' }));
|
|
409
|
+
for (const row of rows)
|
|
410
|
+
out.push({ child: row.child, parent: row.parent });
|
|
411
|
+
}
|
|
412
|
+
return out;
|
|
413
|
+
};
|
|
414
|
+
const verdict = await groupNestingVerdict(edge, limit, {
|
|
415
|
+
parentsOf: (groups) => neighbours(groups, 'subject_uuid'),
|
|
416
|
+
childrenOf: (groups) => neighbours(groups, 'object_uuid'),
|
|
417
|
+
});
|
|
418
|
+
if (verdict.kind !== 'ok') {
|
|
419
|
+
throw groupDepthLimitError(verdict, `group:${edge.parent}#member@group:${edge.child}#member`, limit);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
370
422
|
/** El check-then-delete-insert idempotente de `relate`/`deny` sobre la relación ALMACENADA. */
|
|
371
423
|
async #upsert(operation, subject, relation, object, partition, requested, options) {
|
|
372
424
|
const partitionKey = this.#keyOf(partition);
|
|
@@ -399,10 +451,21 @@ export class DatabaseRelationsDriver {
|
|
|
399
451
|
// Los tres estados (invariante 10): omitido preserva la VIGENTE (una
|
|
400
452
|
// caducada revive sin caducidad), null la quita, Date la fija.
|
|
401
453
|
const expiresAt = resolveGrantExpiry(previous, requested, now);
|
|
454
|
+
// Idempotente (invariante 6): la misma caducidad no reescribe nada — y va ANTES del gate de R1: un `relate`
|
|
455
|
+
// repetido de una arista preexistente (sembrada antes de alpha.4 o con el gate apagado) sigue siendo un no-op.
|
|
456
|
+
if (current && sameInstant(previous, expiresAt))
|
|
457
|
+
return;
|
|
458
|
+
// R1 (opt-in en `database`): una arista de anidación que queda VIVA —nueva, renovada o revivida— pasa el gate,
|
|
459
|
+
// leyendo el grafo por ESTE `trx` (T-6). Una vencida no conecta nada (la misma regla que la lectura). Antes del
|
|
460
|
+
// DELETE: un 422 no deja la arista vieja borrada.
|
|
461
|
+
const limit = this.#groupDepthLimit();
|
|
462
|
+
if (limit !== null &&
|
|
463
|
+
isGroupNesting(subject, relation, object) &&
|
|
464
|
+
isActiveExpiry(expiresAt, now) &&
|
|
465
|
+
!plannedByReconcile(options, partition, { child: subject.object.id, parent: object.id })) {
|
|
466
|
+
await this.#assertGroupNesting(trx, operation, partitionKey, { child: subject.object.id, parent: object.id }, now, limit);
|
|
467
|
+
}
|
|
402
468
|
if (current) {
|
|
403
|
-
// Idempotente (invariante 6): la misma caducidad no reescribe nada.
|
|
404
|
-
if (sameInstant(previous, expiresAt))
|
|
405
|
-
return;
|
|
406
469
|
// **INSERT/DELETE-ONLY (decisión (c))**: cambiar la caducidad es
|
|
407
470
|
// BORRAR la fila e INSERTAR otra —nunca un UPDATE—; la fila nueva
|
|
408
471
|
// tiene otro `uuid`, que es lo que lo hace observable. Dentro de la
|
|
@@ -592,10 +655,12 @@ export class DatabaseRelationsDriver {
|
|
|
592
655
|
}
|
|
593
656
|
return { distinct, index, facts };
|
|
594
657
|
}
|
|
595
|
-
async listObjects(subject, relation, objectType, partition
|
|
658
|
+
async listObjects(subject, relation, objectType, partition) {
|
|
596
659
|
assertRelationDeclared(this.#config, { type: objectType }, relation);
|
|
597
660
|
assertPartition(partition);
|
|
598
661
|
this.#assertId('el tipo de objeto', objectType, RELATION_ID_MAX);
|
|
662
|
+
// 7.4: sin página (un quinto argumento ≠ undefined ⇒ 500 con cero sentencias), después de F-05 y la partición.
|
|
663
|
+
assertNoListPage('listObjects', arguments[4], 'database');
|
|
599
664
|
const partitionKey = this.#keyOf(partition);
|
|
600
665
|
const s = this.#subjectColumns(subject, partitionKey);
|
|
601
666
|
// Lote D · D-15: `listObjects` RESTA (la misma regla de corte que `check`, objeto a objeto).
|
|
@@ -630,14 +695,15 @@ export class DatabaseRelationsDriver {
|
|
|
630
695
|
.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
631
696
|
return { objects };
|
|
632
697
|
}
|
|
633
|
-
async listSubjects(relation, object, partition
|
|
698
|
+
async listSubjects(relation, object, partition) {
|
|
634
699
|
assertRelationDeclared(this.#config, object, relation);
|
|
635
700
|
assertPartition(partition);
|
|
636
701
|
this.#assertObject(object);
|
|
637
|
-
|
|
702
|
+
assertNoListPage('listSubjects', arguments[3], 'database');
|
|
703
|
+
return this.#directSubjects('listSubjects', relation, object, partition);
|
|
638
704
|
}
|
|
639
705
|
/** Los sujetos DIRECTOS vigentes de UNA relación almacenada (`listSubjects`; `listDenies` con `denied_<rel>`). */
|
|
640
|
-
async #directSubjects(operation, relation, object, partition
|
|
706
|
+
async #directSubjects(operation, relation, object, partition) {
|
|
641
707
|
const partitionKey = this.#keyOf(partition);
|
|
642
708
|
// DIRECTOS del relation EXACTO (invariante 7): ni transitivo (eso es
|
|
643
709
|
// `membersOf`) ni derivado por includes. `usersetSubjects: true` ⇒ los
|
|
@@ -657,11 +723,12 @@ export class DatabaseRelationsDriver {
|
|
|
657
723
|
* `listSubjects` sobre la relación almacenada `denied_<rel>`; no resta ni deriva—. Sobre CUALQUIER tipo declarado
|
|
658
724
|
* (D8/R2-4 b: con el opt-in retirado, los denies vivos se siguen pudiendo listar y borrar).
|
|
659
725
|
*/
|
|
660
|
-
async listDenies(relation, object, partition
|
|
726
|
+
async listDenies(relation, object, partition) {
|
|
661
727
|
assertRelationDeclared(this.#config, object, relation);
|
|
662
728
|
assertPartition(partition);
|
|
663
729
|
this.#assertObject(object);
|
|
664
|
-
|
|
730
|
+
assertNoListPage('listDenies', arguments[3], 'database');
|
|
731
|
+
return this.#directSubjects('listDenies', deniedRelationOf(relation), object, partition);
|
|
665
732
|
}
|
|
666
733
|
/**
|
|
667
734
|
* **`denyPhase` — ¿admite este driver escrituras de deny en el tipo?** (lote D · D2, R2-5): la fase del opt-in EN
|
|
@@ -679,12 +746,13 @@ export class DatabaseRelationsDriver {
|
|
|
679
746
|
* directos): en un grupo con `member(g1#member, g2)`, `membersOf(g2)` trae a
|
|
680
747
|
* los holders de g1 y `listSubjects(member, g2)` solo el userset `g1#member`.
|
|
681
748
|
*/
|
|
682
|
-
async membersOf(object, relation, partition
|
|
749
|
+
async membersOf(object, relation, partition) {
|
|
683
750
|
// Cierre-2 · 🟡 3: F-05 (par, ESTRICTA) también en `membersOf`, la octava
|
|
684
751
|
// operación del puerto; antes solo `#assertObject` (gramática).
|
|
685
752
|
assertRelationDeclared(this.#config, object, relation);
|
|
686
753
|
assertPartition(partition);
|
|
687
754
|
this.#assertObject(object);
|
|
755
|
+
assertNoListPage('membersOf', arguments[3], 'database');
|
|
688
756
|
const partitionKey = this.#keyOf(partition);
|
|
689
757
|
const relations = this.#expandDown(object.type, relation);
|
|
690
758
|
const meta = this.#dialectMeta();
|
|
@@ -844,9 +912,12 @@ export class DatabaseRelationsDriver {
|
|
|
844
912
|
*/
|
|
845
913
|
async enumerateRelations(partition, page) {
|
|
846
914
|
assertPartition(partition);
|
|
915
|
+
// 7.4 (injerto 4, H2/H3): limit entero ≥ 1 y after = un uuid canónico (el cursor que ESTE driver emite) o 422,
|
|
916
|
+
// con cero sentencias; un limit por encima del techo se recorta en #enumerateRows.
|
|
917
|
+
const checked = assertEnumeratePage('enumerateRelations', page, 'uuid', 'database');
|
|
847
918
|
const wanted = new Map();
|
|
848
919
|
wanted.set(partitionKey(partition), partition);
|
|
849
|
-
return this.#enumerateRows('enumerateRelations', wanted,
|
|
920
|
+
return this.#enumerateRows('enumerateRelations', wanted, checked);
|
|
850
921
|
}
|
|
851
922
|
/**
|
|
852
923
|
* **`enumerateRelationsMany` — K particiones en UNA consulta** (2.4.0-alpha.8
|
|
@@ -875,7 +946,8 @@ export class DatabaseRelationsDriver {
|
|
|
875
946
|
if (!wanted.has(key))
|
|
876
947
|
wanted.set(key, partition);
|
|
877
948
|
}
|
|
878
|
-
|
|
949
|
+
const checked = assertEnumeratePage('enumerateRelationsMany', page, 'uuid', 'database');
|
|
950
|
+
return this.#enumerateRows('enumerateRelationsMany', wanted, checked);
|
|
879
951
|
}
|
|
880
952
|
/**
|
|
881
953
|
* La consulta de `enumerateRelations`/`enumerateRelationsMany`: los hechos
|
|
@@ -887,7 +959,8 @@ export class DatabaseRelationsDriver {
|
|
|
887
959
|
async #enumerateRows(operation, wanted, page) {
|
|
888
960
|
const partitionKeys = [...wanted.keys()];
|
|
889
961
|
const declaredTypes = [GROUP_TYPE, ...this.#config.objectTypes.map((t) => t.type)];
|
|
890
|
-
|
|
962
|
+
// La COPIA validada en la entrada (assertEnumeratePage), no la página del llamante: aquí solo el default y el RECORTE al techo, declarado en «Limits».
|
|
963
|
+
const limit = enumerateLimit(page);
|
|
891
964
|
const after = page?.after;
|
|
892
965
|
const rows = await this.#sql(operation, () => {
|
|
893
966
|
let q = this.#connection()
|
|
@@ -959,4 +1032,3 @@ function rowsOf(result) {
|
|
|
959
1032
|
}
|
|
960
1033
|
return result?.rows ?? [];
|
|
961
1034
|
}
|
|
962
|
-
//# sourceMappingURL=database_relations_driver.js.map
|