@jantstack/adonis-authz 1.1.0 → 2.0.0-alpha.1
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/LICENSE +1 -1
- package/README.md +417 -58
- package/build/commands/authz_catalog_diff.d.ts +28 -0
- package/build/commands/authz_catalog_diff.d.ts.map +1 -0
- package/build/commands/authz_catalog_diff.js +67 -0
- package/build/commands/authz_catalog_diff.js.map +1 -0
- package/build/commands/authz_catalog_sync.d.ts +20 -0
- package/build/commands/authz_catalog_sync.d.ts.map +1 -0
- package/build/commands/authz_catalog_sync.js +58 -0
- package/build/commands/authz_catalog_sync.js.map +1 -0
- package/build/commands/main.d.ts +2 -0
- package/build/commands/main.d.ts.map +1 -1
- package/build/commands/main.js +2 -0
- package/build/commands/main.js.map +1 -1
- package/build/commands/openfga_import.d.ts +8 -2
- package/build/commands/openfga_import.d.ts.map +1 -1
- package/build/commands/openfga_import.js +29 -6
- package/build/commands/openfga_import.js.map +1 -1
- package/build/commands/openfga_provision.d.ts.map +1 -1
- package/build/commands/openfga_provision.js +2 -1
- package/build/commands/openfga_provision.js.map +1 -1
- package/build/index.d.ts +45 -6
- package/build/index.d.ts.map +1 -1
- package/build/index.js +37 -4
- package/build/index.js.map +1 -1
- package/build/src/catalog.d.ts +249 -2
- package/build/src/catalog.d.ts.map +1 -1
- package/build/src/catalog.js +709 -59
- package/build/src/catalog.js.map +1 -1
- package/build/src/catalog_cache.d.ts +300 -0
- package/build/src/catalog_cache.d.ts.map +1 -0
- package/build/src/catalog_cache.js +656 -0
- package/build/src/catalog_cache.js.map +1 -0
- package/build/src/clock.d.ts +24 -0
- package/build/src/clock.d.ts.map +1 -0
- package/build/src/clock.js +7 -0
- package/build/src/clock.js.map +1 -0
- package/build/src/define_config.d.ts +102 -6
- package/build/src/define_config.d.ts.map +1 -1
- package/build/src/define_config.js.map +1 -1
- package/build/src/drivers/backend_guard.d.ts +92 -0
- package/build/src/drivers/backend_guard.d.ts.map +1 -0
- package/build/src/drivers/backend_guard.js +221 -0
- package/build/src/drivers/backend_guard.js.map +1 -0
- package/build/src/drivers/database_driver.d.ts +242 -15
- package/build/src/drivers/database_driver.d.ts.map +1 -1
- package/build/src/drivers/database_driver.js +692 -127
- package/build/src/drivers/database_driver.js.map +1 -1
- package/build/src/drivers/openfga_driver.d.ts +341 -27
- package/build/src/drivers/openfga_driver.d.ts.map +1 -1
- package/build/src/drivers/openfga_driver.js +1020 -271
- package/build/src/drivers/openfga_driver.js.map +1 -1
- package/build/src/drivers/sql_expiry.d.ts +53 -0
- package/build/src/drivers/sql_expiry.d.ts.map +1 -0
- package/build/src/drivers/sql_expiry.js +66 -0
- package/build/src/drivers/sql_expiry.js.map +1 -0
- package/build/src/errors.d.ts +333 -0
- package/build/src/errors.d.ts.map +1 -1
- package/build/src/errors.js +352 -0
- package/build/src/errors.js.map +1 -1
- package/build/src/expiry.d.ts +27 -0
- package/build/src/expiry.d.ts.map +1 -0
- package/build/src/expiry.js +50 -0
- package/build/src/expiry.js.map +1 -0
- package/build/src/hierarchical_resolver.d.ts +56 -0
- package/build/src/hierarchical_resolver.d.ts.map +1 -0
- package/build/src/hierarchical_resolver.js +87 -0
- package/build/src/hierarchical_resolver.js.map +1 -0
- package/build/src/identity.d.ts +155 -0
- package/build/src/identity.d.ts.map +1 -0
- package/build/src/identity.js +359 -0
- package/build/src/identity.js.map +1 -0
- package/build/src/manager.d.ts +225 -7
- package/build/src/manager.d.ts.map +1 -1
- package/build/src/manager.js +1670 -23
- package/build/src/manager.js.map +1 -1
- package/build/src/memoize_ancestors.d.ts +23 -0
- package/build/src/memoize_ancestors.d.ts.map +1 -0
- package/build/src/memoize_ancestors.js +42 -0
- package/build/src/memoize_ancestors.js.map +1 -0
- package/build/src/middleware/app_access_middleware.d.ts +8 -6
- package/build/src/middleware/app_access_middleware.d.ts.map +1 -1
- package/build/src/middleware/app_access_middleware.js +10 -26
- package/build/src/middleware/app_access_middleware.js.map +1 -1
- package/build/src/models/authz_assignment.d.ts +5 -5
- package/build/src/models/authz_assignment.d.ts.map +1 -1
- package/build/src/models/authz_deny.d.ts +5 -5
- package/build/src/models/authz_deny.d.ts.map +1 -1
- package/build/src/models/authz_permission.d.ts +11 -5
- package/build/src/models/authz_permission.d.ts.map +1 -1
- package/build/src/models/authz_permission.js +4 -0
- package/build/src/models/authz_permission.js.map +1 -1
- package/build/src/models/authz_role.d.ts +13 -6
- package/build/src/models/authz_role.d.ts.map +1 -1
- package/build/src/models/authz_role.js +6 -1
- package/build/src/models/authz_role.js.map +1 -1
- package/build/src/models/authz_role_permission.d.ts +5 -5
- package/build/src/models/authz_role_permission.d.ts.map +1 -1
- package/build/src/openfga.d.ts +13 -0
- package/build/src/openfga.d.ts.map +1 -0
- package/build/src/openfga.js +12 -0
- package/build/src/openfga.js.map +1 -0
- package/build/src/sql_descendants.d.ts +51 -0
- package/build/src/sql_descendants.d.ts.map +1 -0
- package/build/src/sql_descendants.js +129 -0
- package/build/src/sql_descendants.js.map +1 -0
- package/build/src/testing/contract.d.ts +138 -7
- package/build/src/testing/contract.d.ts.map +1 -1
- package/build/src/testing/contract.js +2946 -24
- package/build/src/testing/contract.js.map +1 -1
- package/build/src/testing/main.d.ts +4 -2
- package/build/src/testing/main.d.ts.map +1 -1
- package/build/src/testing/main.js +2 -1
- package/build/src/testing/main.js.map +1 -1
- package/build/src/testing/scope_tree.d.ts +65 -0
- package/build/src/testing/scope_tree.d.ts.map +1 -0
- package/build/src/testing/scope_tree.js +145 -0
- package/build/src/testing/scope_tree.js.map +1 -0
- package/build/src/traits/authz_scopes.d.ts +30 -6
- package/build/src/traits/authz_scopes.d.ts.map +1 -1
- package/build/src/traits/authz_scopes.js +30 -18
- package/build/src/traits/authz_scopes.js.map +1 -1
- package/build/src/traits/has_uuid.d.ts +5 -5
- package/build/src/traits/has_uuid.d.ts.map +1 -1
- package/build/src/types.d.ts +530 -28
- package/build/src/types.d.ts.map +1 -1
- package/build/src/types.js +10 -4
- package/build/src/types.js.map +1 -1
- package/build/stubs/config/app_acl.stub +4 -2
- package/build/stubs/config/authorization.stub +66 -12
- package/build/stubs/migration.stub +57 -13
- package/package.json +11 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authz_role.js","sourceRoot":"","sources":["../../../src/models/authz_role.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAChC,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAEnE,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAA;AAChD,OAAO,eAAe,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AAE/C
|
|
1
|
+
{"version":3,"file":"authz_role.js","sourceRoot":"","sources":["../../../src/models/authz_role.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAChC,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAEnE,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAA;AAChD,OAAO,eAAe,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AAE/C;;;;GAIG;AACH,MAAM,CAAC,OAAO,OAAO,SAAU,SAAQ,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC;IACzD,MAAM,CAAC,KAAK,GAAG,aAAa,CAAA;IAC5B,MAAM,CAAC,UAAU,GAAG,MAAM,CAAA;;AAGzB;IADP,MAAM,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;;uCACR;AAGZ;IADP,MAAM,EAAE;;uCACW;AAGZ;IADP,MAAM,EAAE;;uCACW;AAGZ;IADP,MAAM,EAAE;;8CACyB;AAG1B;IADP,MAAM,CAAC,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC;;4CACZ;AAIjB;IADP,MAAM,EAAE;;uCACW;AAQZ;IADP,MAAM,CAAC,EAAE,UAAU,EAAE,iBAAiB,EAAE,CAAC;;gDACb;AAUrB;IARP,UAAU,CAAC,GAAG,EAAE,CAAC,eAAe,EAAE;QACjC,UAAU,EAAE,wBAAwB;QACpC,QAAQ,EAAE,MAAM;QAChB,eAAe,EAAE,WAAW;QAC5B,UAAU,EAAE,MAAM;QAClB,sBAAsB,EAAE,iBAAiB;QACzC,eAAe,EAAE,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,KAAK,EAAE;KAC/D,CAAC;;8CACqD;AAG/C;IADP,MAAM,CAAC,QAAQ,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC;8BAC7C,QAAQ;4CAAA;AAGnB;IADP,MAAM,CAAC,QAAQ,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC;8BAC/D,QAAQ;4CAAA"}
|
|
@@ -62,15 +62,18 @@ declare const AuthzRolePermission_base: {
|
|
|
62
62
|
selfAssignPrimaryKey: boolean;
|
|
63
63
|
generateUuid(model: any): void;
|
|
64
64
|
nextUuid(): string;
|
|
65
|
+
all: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, options?: import("@adonisjs/lucid/types/model").ModelAdapterOptions) => Promise<InstanceType<T>[]>;
|
|
65
66
|
find: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, value: any, options?: import("@adonisjs/lucid/types/model").ModelAdapterOptions) => Promise<null | InstanceType<T>>;
|
|
67
|
+
table: string;
|
|
68
|
+
connection?: string | undefined;
|
|
69
|
+
first: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, options?: import("@adonisjs/lucid/types/model").ModelAdapterOptions) => Promise<null | InstanceType<T>>;
|
|
70
|
+
truncate: (cascade?: boolean) => Promise<void>;
|
|
66
71
|
readonly booted: boolean;
|
|
67
72
|
$columnsDefinitions: Map<string, import("@adonisjs/lucid/types/model").ModelColumnOptions>;
|
|
68
73
|
$relationsDefinitions: Map<string, import("@adonisjs/lucid/types/relations").RelationshipsContract>;
|
|
69
74
|
$computedDefinitions: Map<string, import("@adonisjs/lucid/types/model").ComputedOptions>;
|
|
70
75
|
primaryKey: string;
|
|
71
|
-
connection?: string | undefined;
|
|
72
76
|
namingStrategy: import("@adonisjs/lucid/types/model").NamingStrategyContract;
|
|
73
|
-
table: string;
|
|
74
77
|
$adapter: import("@adonisjs/lucid/types/model").AdapterContract;
|
|
75
78
|
useAdapter: (adapter: import("@adonisjs/lucid/types/model").AdapterContract) => void;
|
|
76
79
|
$hooks: import("@poppinss/hooks").default<any>;
|
|
@@ -128,7 +131,6 @@ declare const AuthzRolePermission_base: {
|
|
|
128
131
|
<T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, clause: Record<string, unknown>, options?: import("@adonisjs/lucid/types/model").ModelAdapterOptions): Promise<InstanceType<T>[]>;
|
|
129
132
|
<T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, key: string, value: any, options?: import("@adonisjs/lucid/types/model").ModelAdapterOptions): Promise<InstanceType<T>[]>;
|
|
130
133
|
};
|
|
131
|
-
first: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, options?: import("@adonisjs/lucid/types/model").ModelAdapterOptions) => Promise<null | InstanceType<T>>;
|
|
132
134
|
firstOrFail: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, options?: import("@adonisjs/lucid/types/model").ModelAdapterOptions) => Promise<InstanceType<T>>;
|
|
133
135
|
findMany: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, value: any[], options?: import("@adonisjs/lucid/types/model").ModelAdapterOptions) => Promise<InstanceType<T>[]>;
|
|
134
136
|
firstOrNew: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, searchPayload: Partial<import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>>, savePayload?: Partial<import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>>, options?: import("@adonisjs/lucid/types/model").ModelAssignOptions) => Promise<InstanceType<T>>;
|
|
@@ -137,10 +139,8 @@ declare const AuthzRolePermission_base: {
|
|
|
137
139
|
fetchOrNewUpMany: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, predicate: keyof import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>> | (keyof import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>)[], payload: Partial<import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>>[], options?: import("@adonisjs/lucid/types/model").ModelAssignOptions) => Promise<InstanceType<T>[]>;
|
|
138
140
|
fetchOrCreateMany: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, predicate: keyof import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>> | (keyof import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>)[], payload: Partial<import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>>[], options?: import("@adonisjs/lucid/types/model").ModelAssignOptions) => Promise<InstanceType<T>[]>;
|
|
139
141
|
updateOrCreateMany: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, predicate: keyof import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>> | (keyof import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>)[], payload: Partial<import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>>[], options?: import("@adonisjs/lucid/types/model").ModelAssignOptions) => Promise<InstanceType<T>[]>;
|
|
140
|
-
all: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, options?: import("@adonisjs/lucid/types/model").ModelAdapterOptions) => Promise<InstanceType<T>[]>;
|
|
141
142
|
query: <Model extends import("@adonisjs/lucid/types/model").LucidModel, Result = InstanceType<Model>>(this: Model, options?: import("@adonisjs/lucid/types/model").ModelAdapterOptions) => import("@adonisjs/lucid/types/model").ModelQueryBuilderContract<Model, Result>;
|
|
142
143
|
transaction: import("@adonisjs/lucid/types/database").TransactionFn;
|
|
143
|
-
truncate: (cascade?: boolean) => Promise<void>;
|
|
144
144
|
} & import("@adonisjs/lucid/types/model").LucidModel;
|
|
145
145
|
/** Pivot rol ↔ permiso del motor de autorización propio. */
|
|
146
146
|
export default class AuthzRolePermission extends AuthzRolePermission_base {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authz_role_permission.d.ts","sourceRoot":"","sources":["../../../src/models/authz_role_permission.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA
|
|
1
|
+
{"version":3,"file":"authz_role_permission.d.ts","sourceRoot":"","sources":["../../../src/models/authz_role_permission.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oZAsBoxtB,gDAAiD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAjBr2tB,4DAA4D;AAC5D,MAAM,CAAC,OAAO,OAAO,mBAAoB,SAAQ,wBAA2B;IAC1E,OAAc,KAAK,SAA2B;IAC9C,OAAc,UAAU,SAAS;IAGzB,IAAI,EAAE,MAAM,CAAA;IAGZ,QAAQ,EAAE,MAAM,CAAA;IAGhB,cAAc,EAAE,MAAM,CAAA;IAGtB,SAAS,EAAE,QAAQ,CAAA;CAC5B"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entrada del subpath `@jantstack/adonis-authz/openfga` — TODO lo que tira de
|
|
3
|
+
* `@openfga/sdk` (peer opcional) vive detrás de esta puerta (D9):
|
|
4
|
+
*
|
|
5
|
+
* const { OpenFgaAuthorizationDriver } = await import('@jantstack/adonis-authz/openfga')
|
|
6
|
+
*
|
|
7
|
+
* La entrada principal (`@jantstack/adonis-authz`) no importa este módulo ni
|
|
8
|
+
* el driver: un consumidor solo-database arranca sin el SDK instalado, y
|
|
9
|
+
* `scripts/check_purity.mjs` (regla 3) lo vigila en cada build.
|
|
10
|
+
*/
|
|
11
|
+
export { OpenFgaAuthorizationDriver, openFgaAuthorizationModel, provisionOpenFgaStore, importAuthzFactsToOpenFga, assertHolderTypes, parseBindingId, correlateBatchResults, isDuplicateWrite, DEFAULT_TIMEOUT_MS, } from './drivers/openfga_driver.js';
|
|
12
|
+
export type { HolderTypeMap, OpenFgaDriverOptions, ImportFactsOptions, ImportFactsResult, } from './drivers/openfga_driver.js';
|
|
13
|
+
//# sourceMappingURL=openfga.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openfga.d.ts","sourceRoot":"","sources":["../../src/openfga.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACL,0BAA0B,EAC1B,yBAAyB,EACzB,qBAAqB,EACrB,yBAAyB,EACzB,iBAAiB,EACjB,cAAc,EACd,qBAAqB,EACrB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,6BAA6B,CAAA;AACpC,YAAY,EACV,aAAa,EACb,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,6BAA6B,CAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entrada del subpath `@jantstack/adonis-authz/openfga` — TODO lo que tira de
|
|
3
|
+
* `@openfga/sdk` (peer opcional) vive detrás de esta puerta (D9):
|
|
4
|
+
*
|
|
5
|
+
* const { OpenFgaAuthorizationDriver } = await import('@jantstack/adonis-authz/openfga')
|
|
6
|
+
*
|
|
7
|
+
* La entrada principal (`@jantstack/adonis-authz`) no importa este módulo ni
|
|
8
|
+
* el driver: un consumidor solo-database arranca sin el SDK instalado, y
|
|
9
|
+
* `scripts/check_purity.mjs` (regla 3) lo vigila en cada build.
|
|
10
|
+
*/
|
|
11
|
+
export { OpenFgaAuthorizationDriver, openFgaAuthorizationModel, provisionOpenFgaStore, importAuthzFactsToOpenFga, assertHolderTypes, parseBindingId, correlateBatchResults, isDuplicateWrite, DEFAULT_TIMEOUT_MS, } from './drivers/openfga_driver.js';
|
|
12
|
+
//# sourceMappingURL=openfga.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openfga.js","sourceRoot":"","sources":["../../src/openfga.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACL,0BAA0B,EAC1B,yBAAyB,EACzB,qBAAqB,EACrB,yBAAyB,EACzB,iBAAiB,EACjB,cAAc,EACd,qBAAqB,EACrB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,6BAA6B,CAAA"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ScopeDescendantsResolver } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* `descendantsOf` opt-in sobre una tabla del consumidor con columna padre
|
|
4
|
+
* (2.1, B2). Genera UNA CTE recursiva (`WITH RECURSIVE`), la misma para
|
|
5
|
+
* PostgreSQL, SQLite y MySQL 8 (2.5 · J3: solo cambia el carácter con el que
|
|
6
|
+
* se citan los identificadores); cualquier otro dialecto lanza 500
|
|
7
|
+
* `E_AUTHZ_UNSUPPORTED_DIALECT` en la primera llamada hasta que la suite lo
|
|
8
|
+
* observe. El paquete sigue sin conocer el árbol del consumidor: aquí solo
|
|
9
|
+
* se le dice qué tabla y qué columnas lo guardan.
|
|
10
|
+
*
|
|
11
|
+
* scopes: {
|
|
12
|
+
* resolveChain,
|
|
13
|
+
* descendantsOf: sqlDescendantsOf({
|
|
14
|
+
* table: 'organization_nodes', uuidColumn: 'uuid', parentColumn: 'parent_uuid', typeColumn: 'kind',
|
|
15
|
+
* }),
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* Cotas: la consulta lee como mucho `maxNodes + 1` filas (`LIMIT`) y no baja
|
|
19
|
+
* más de `maxNodes + 1` niveles, así que un ciclo en la tabla termina Y se
|
|
20
|
+
* nota: un ciclo alcanzable produce al menos una fila por nivel, es decir
|
|
21
|
+
* más de `maxNodes` ⇒ 422 `E_AUTHZ_TOO_MANY_SCOPES` con «posible ciclo» en
|
|
22
|
+
* el mensaje (con `depth < maxNodes` un ciclo de dos nodos devolvía
|
|
23
|
+
* `maxNodes - 1` filas duplicadas en silencio). No hay segunda barrera por
|
|
24
|
+
* uuid repetido: con esta profundidad un ciclo NUNCA cabe en `maxNodes`
|
|
25
|
+
* filas, así que era código muerto (G1, CR6). Nunca una lista parcial. Un
|
|
26
|
+
* scope que no está en la tabla ⇒ `null`. `scopeType` pasa por la gramática
|
|
27
|
+
* de identidad al construir (auditor 11).
|
|
28
|
+
*/
|
|
29
|
+
export interface SqlDescendantsOptions {
|
|
30
|
+
table: string;
|
|
31
|
+
uuidColumn: string;
|
|
32
|
+
/** Columna con el uuid del padre; `NULL` = el nodo cuelga de `app`. */
|
|
33
|
+
parentColumn: string;
|
|
34
|
+
/** Columna con el `ScopeRef.type` de cada fila. Excluyente con `scopeType`. */
|
|
35
|
+
typeColumn?: string;
|
|
36
|
+
/** Tipo fijo de todos los nodos de la tabla (sin columna de tipo). Excluyente con `typeColumn`. */
|
|
37
|
+
scopeType?: string;
|
|
38
|
+
/** Cota propia del helper: se aplica `min(maxNodes del llamante, esta)`. */
|
|
39
|
+
maxNodes?: number;
|
|
40
|
+
/** Nombre de la conexión Lucid (default: la primaria). */
|
|
41
|
+
connection?: string;
|
|
42
|
+
/** Deadline de cada consulta en ms (default 5000): vencido ⇒ 503. */
|
|
43
|
+
timeoutMs?: number;
|
|
44
|
+
}
|
|
45
|
+
/** Lo que necesita del `db` de Lucid; inyectable para probar el dialecto sin servidor. */
|
|
46
|
+
interface DatabaseLike {
|
|
47
|
+
connection(name?: string): any;
|
|
48
|
+
}
|
|
49
|
+
export declare function sqlDescendantsOf(options: SqlDescendantsOptions, database?: DatabaseLike): ScopeDescendantsResolver;
|
|
50
|
+
export {};
|
|
51
|
+
//# sourceMappingURL=sql_descendants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql_descendants.d.ts","sourceRoot":"","sources":["../../src/sql_descendants.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,wBAAwB,EAAY,MAAM,YAAY,CAAA;AAEpE;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,uEAAuE;IACvE,YAAY,EAAE,MAAM,CAAA;IACpB,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,mGAAmG;IACnG,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAuBD,0FAA0F;AAC1F,UAAU,YAAY;IACpB,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,GAAG,CAAA;CAC/B;AAED,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,qBAAqB,EAC9B,QAAQ,GAAE,YAAiB,GAC1B,wBAAwB,CAiH1B"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import db from '@adonisjs/lucid/services/db';
|
|
2
|
+
import { AuthorizationConfigError, TooManyScopesError, UnsupportedDialectError } from './errors.js';
|
|
3
|
+
import { assertScope, assertScopeType } from './identity.js';
|
|
4
|
+
import { guardSql } from './drivers/backend_guard.js';
|
|
5
|
+
import { APP_SCOPE_TYPE } from './types.js';
|
|
6
|
+
const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
7
|
+
/** Dialecto → carácter con el que cita identificadores (MySQL: backtick; el resto: comilla doble ANSI). */
|
|
8
|
+
const QUOTE_BY_DIALECT = {
|
|
9
|
+
'postgres': '"',
|
|
10
|
+
'sqlite3': '"',
|
|
11
|
+
'better-sqlite3': '"',
|
|
12
|
+
'mysql': '`',
|
|
13
|
+
'mysql2': '`',
|
|
14
|
+
};
|
|
15
|
+
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
16
|
+
function identifier(kind, value) {
|
|
17
|
+
if (typeof value !== 'string' || !IDENTIFIER.test(value)) {
|
|
18
|
+
throw new AuthorizationConfigError(`sqlDescendantsOf: ${kind} '${String(value)}' no es un identificador SQL simple (letras, dígitos y _); ` +
|
|
19
|
+
`nada que no lo sea se interpola en la consulta.`);
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
export function sqlDescendantsOf(options, database = db) {
|
|
24
|
+
const table = identifier('table', options.table);
|
|
25
|
+
const uuidCol = identifier('uuidColumn', options.uuidColumn);
|
|
26
|
+
const parentCol = identifier('parentColumn', options.parentColumn);
|
|
27
|
+
if ((options.typeColumn === undefined) === (options.scopeType === undefined)) {
|
|
28
|
+
throw new AuthorizationConfigError('sqlDescendantsOf: declara exactamente uno de typeColumn (el tipo viene de la fila) o scopeType (tipo fijo).');
|
|
29
|
+
}
|
|
30
|
+
const typeCol = options.typeColumn === undefined ? null : identifier('typeColumn', options.typeColumn);
|
|
31
|
+
const fixedType = options.scopeType ?? null;
|
|
32
|
+
if (fixedType !== null) {
|
|
33
|
+
// Es identidad de scope (minúsculas, ≤ 20, sin separadores) y nunca la
|
|
34
|
+
// raíz: `app` no tiene fila ni uuid.
|
|
35
|
+
try {
|
|
36
|
+
assertScopeType(fixedType);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
throw new AuthorizationConfigError(`sqlDescendantsOf: scopeType inválido: ${error.message}`);
|
|
40
|
+
}
|
|
41
|
+
if (fixedType === APP_SCOPE_TYPE) {
|
|
42
|
+
throw new AuthorizationConfigError("sqlDescendantsOf: scopeType no puede ser 'app' (la raíz no tiene fila)");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (options.maxNodes !== undefined && !(Number.isInteger(options.maxNodes) && options.maxNodes >= 1)) {
|
|
46
|
+
throw new AuthorizationConfigError(`sqlDescendantsOf: maxNodes debe ser un entero >= 1 (llegó ${String(options.maxNodes)})`);
|
|
47
|
+
}
|
|
48
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
49
|
+
const connectionName = options.connection;
|
|
50
|
+
/** El SQL por carácter de cita (se construye una vez por dialecto). */
|
|
51
|
+
const statements = new Map();
|
|
52
|
+
function statementsFor(quote) {
|
|
53
|
+
let built = statements.get(quote);
|
|
54
|
+
if (built)
|
|
55
|
+
return built;
|
|
56
|
+
const q = (name) => `${quote}${name}${quote}`;
|
|
57
|
+
const typeSelect = typeCol ? `t.${q(typeCol)}` : 'NULL';
|
|
58
|
+
// MySQL corta una CTE recursiva a `cte_max_recursion_depth` iteraciones
|
|
59
|
+
// (1000 por defecto) con el error 3636: con una cota mayor, un ciclo
|
|
60
|
+
// salía como 503 «backend caído» en vez del 422 «posible ciclo» del
|
|
61
|
+
// contrato (2.5 · J3, hallazgo). El hint `SET_VAR` sube el tope SOLO para
|
|
62
|
+
// esta sentencia, a la cota + 1 que la propia consulta ya impone con
|
|
63
|
+
// `depth < ?`; el placeholder `%DEPTH%` se rellena con ese entero (nunca
|
|
64
|
+
// con entrada del consumidor). Los demás dialectos no tienen tope.
|
|
65
|
+
const hint = quote === '`' ? '/*+ SET_VAR(cte_max_recursion_depth = %DEPTH%) */ ' : '';
|
|
66
|
+
built = {
|
|
67
|
+
exists: `SELECT 1 AS ${q('ok')} FROM ${q(table)} t WHERE %WHERE% LIMIT 1`,
|
|
68
|
+
walk: `WITH RECURSIVE ${q('authz_descendants')}(${q('uuid')}, ${q('type')}, ${q('depth')}) AS (` +
|
|
69
|
+
` SELECT t.${q(uuidCol)}, ${typeSelect}, 1 FROM ${q(table)} t WHERE ` +
|
|
70
|
+
`%ANCHOR%` +
|
|
71
|
+
` UNION ALL` +
|
|
72
|
+
` SELECT t.${q(uuidCol)}, ${typeSelect}, d.${q('depth')} + 1 FROM ${q(table)} t` +
|
|
73
|
+
` JOIN ${q('authz_descendants')} d ON t.${q(parentCol)} = d.${q('uuid')} WHERE d.${q('depth')} < ?` +
|
|
74
|
+
`) SELECT ${hint}${q('uuid')}, ${q('type')} FROM ${q('authz_descendants')} LIMIT ?`,
|
|
75
|
+
};
|
|
76
|
+
statements.set(quote, built);
|
|
77
|
+
return built;
|
|
78
|
+
}
|
|
79
|
+
return async (scope, { maxNodes }) => {
|
|
80
|
+
assertScope(scope);
|
|
81
|
+
const cap = Math.min(maxNodes, options.maxNodes ?? Infinity);
|
|
82
|
+
if (!(Number.isInteger(cap) && cap >= 1)) {
|
|
83
|
+
throw new AuthorizationConfigError(`sqlDescendantsOf: maxNodes debe ser un entero >= 1 (llegó ${String(maxNodes)})`);
|
|
84
|
+
}
|
|
85
|
+
const connection = database.connection(connectionName);
|
|
86
|
+
const dialect = connection?.dialect?.name ?? connection?.client?.driverName ?? 'desconocido';
|
|
87
|
+
const quote = QUOTE_BY_DIALECT[dialect];
|
|
88
|
+
if (!quote) {
|
|
89
|
+
throw new UnsupportedDialectError(`sqlDescendantsOf: dialecto '${dialect}' sin observación en la suite (hoy: PostgreSQL, MySQL 8 y SQLite). ` +
|
|
90
|
+
`Implementa scopes.descendantsOf a mano para este motor.`);
|
|
91
|
+
}
|
|
92
|
+
const q = (name) => `${quote}${name}${quote}`;
|
|
93
|
+
const sql = statementsFor(quote);
|
|
94
|
+
const query = (operation, text, bindings) => guardSql('sqlDescendantsOf', operation, timeoutMs, () => connection.rawQuery(text, bindings));
|
|
95
|
+
// Forma del resultado crudo por cliente: pg `{ rows }`, sqlite `rows[]`,
|
|
96
|
+
// mysql2 `[rows[], fields[]]` (2.5 · J3).
|
|
97
|
+
const rowsOf = (result) => {
|
|
98
|
+
if (Array.isArray(result)) {
|
|
99
|
+
return result.length === 2 && Array.isArray(result[0]) && Array.isArray(result[1]) ? result[0] : result;
|
|
100
|
+
}
|
|
101
|
+
return result?.rows ?? [];
|
|
102
|
+
};
|
|
103
|
+
let anchor;
|
|
104
|
+
let bindings;
|
|
105
|
+
if (scope.type === APP_SCOPE_TYPE) {
|
|
106
|
+
anchor = `t.${q(parentCol)} IS NULL`;
|
|
107
|
+
bindings = [];
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
if (fixedType !== null && scope.type !== fixedType)
|
|
111
|
+
return null;
|
|
112
|
+
const where = `t.${q(uuidCol)} = ?` + (typeCol ? ` AND t.${q(typeCol)} = ?` : '');
|
|
113
|
+
const args = typeCol ? [scope.uuid, scope.type] : [scope.uuid];
|
|
114
|
+
const exists = rowsOf(await query('descendantsOf.exists', sql.exists.replace('%WHERE%', where), args));
|
|
115
|
+
if (exists.length === 0)
|
|
116
|
+
return null;
|
|
117
|
+
anchor = `t.${q(parentCol)} = ?`;
|
|
118
|
+
bindings = [scope.uuid];
|
|
119
|
+
}
|
|
120
|
+
const walk = sql.walk.replace('%ANCHOR%', anchor).replace('%DEPTH%', String(cap + 2));
|
|
121
|
+
const rows = rowsOf(await query('descendantsOf', walk, [...bindings, cap + 1, cap + 1]));
|
|
122
|
+
if (rows.length > cap) {
|
|
123
|
+
throw new TooManyScopesError(`sqlDescendantsOf: ${scope.type}:${scope.uuid ?? ''} tiene más de ${cap} descendientes — o la tabla tiene un ` +
|
|
124
|
+
`posible ciclo (un ciclo alcanzable produce una fila por nivel hasta la cota); no se devuelve una lista parcial.`);
|
|
125
|
+
}
|
|
126
|
+
return rows.map((row) => ({ type: typeCol ? row.type : fixedType, uuid: row.uuid }));
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=sql_descendants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql_descendants.js","sourceRoot":"","sources":["../../src/sql_descendants.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,27 +1,158 @@
|
|
|
1
1
|
import '@japa/assert';
|
|
2
|
+
import type { Assert } from '@japa/assert';
|
|
2
3
|
import type { AuthorizationDriver, CatalogSpec } from '../types.js';
|
|
4
|
+
import type { ContractScopeTree } from './scope_tree.js';
|
|
3
5
|
/**
|
|
4
6
|
* Suite de contrato del sistema de autorización — EL JUEZ.
|
|
5
7
|
*
|
|
6
8
|
* Cualquier driver (database, openfga, custom del consumidor) debe pasar
|
|
7
9
|
* estos casos sin modificarlos: son la definición ejecutable de la semántica
|
|
8
|
-
* documentada en
|
|
10
|
+
* documentada en `../types.js`. Para enchufar un driver:
|
|
9
11
|
*
|
|
10
12
|
* runAuthorizationDriverContract({
|
|
11
13
|
* name: 'mi-driver',
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
14
|
+
* level: '2.0', // omitido = 'core': solo los casos de 1.x
|
|
15
|
+
* capabilities: { hierarchyFacts: false, transactions: false, truncationSignal: false,
|
|
16
|
+
* singleCheckAuthorize: false, injectableClock: false },
|
|
17
|
+
* seedCatalog: (catalog) => ..., // materializa roles/permisos
|
|
18
|
+
* makeDriver: (tree) => new MiDriver({ resolveChain: ... }), // recibe el árbol
|
|
19
|
+
* cleanup: () => ..., // borra hechos + catálogo entre tests
|
|
15
20
|
* })
|
|
16
21
|
*
|
|
22
|
+
* Llámala en el TOP LEVEL del spec (no dentro de un `test` ni de un hook):
|
|
23
|
+
* Japa registra los grupos al importar el archivo y descarta los tardíos.
|
|
24
|
+
*
|
|
17
25
|
* `seedCatalog`/`cleanup` son del harness (no del contrato) porque cada
|
|
18
26
|
* backend materializa el catálogo a su manera (tablas propias, FGA model...).
|
|
27
|
+
*
|
|
28
|
+
* El ÁRBOL de scopes también es del harness (`makeTree`, por defecto
|
|
29
|
+
* `memoryScopeTree()`): la suite lo construye caso a caso con `tree.attach` y
|
|
30
|
+
* el driver lo ve a través de lo que el harness le inyecte (un
|
|
31
|
+
* `resolveChain` que lo camina, o hechos en el backend). Así el
|
|
32
|
+
* invariante 1 se prueba con cadenas reales y no con el resolutor plano.
|
|
33
|
+
*/
|
|
34
|
+
/**
|
|
35
|
+
* Lo que un driver DECLARA poder hacer. Cada capacidad tiene en la suite un
|
|
36
|
+
* par de casos `{ whenTrue, whenFalse }` — nunca un `skip`. Hoy hay par para
|
|
37
|
+
* `truncationSignal: false` (con `exhaustiveLists`) y `hierarchyFacts: false`;
|
|
38
|
+
* los demás llegan con su fase, y mientras tanto declararlos `true` hace que
|
|
39
|
+
* la suite lance al registrarse: una promesa sin juez no pasa.
|
|
19
40
|
*/
|
|
41
|
+
export interface DriverCapabilities {
|
|
42
|
+
/** El driver materializa el árbol como hechos propios (Fase 3b). */
|
|
43
|
+
hierarchyFacts: boolean;
|
|
44
|
+
/** Acepta `{ trx }` externa en las escrituras (Fase 2.5). */
|
|
45
|
+
transactions: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Los `list*` lanzan si el backend trunca. Ningún driver del paquete trunca
|
|
48
|
+
* (L0.7 se cerró enumerando con `Read` paginado), así que no hay caso para
|
|
49
|
+
* `true`: es el par que un driver de terceros con backend con tope tendría
|
|
50
|
+
* que traer.
|
|
51
|
+
*/
|
|
52
|
+
truncationSignal: boolean;
|
|
53
|
+
/** `authorize` = una sola llamada al backend (modo facts, Fase 3b). */
|
|
54
|
+
singleCheckAuthorize: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* El driver acepta un reloj inyectado (2.5 · J1): `withClock(now)` en el
|
|
57
|
+
* puerto devuelve una vista que evalúa la caducidad con ESE `now` (SQL,
|
|
58
|
+
* `current_time` de FGA, filtros en cliente y los tres estados del
|
|
59
|
+
* re-grant). Con `true` el juez fija el instante y observa la caducidad
|
|
60
|
+
* exacta (T−1 ms concede, T no) sin dormir; con `false` solo puede
|
|
61
|
+
* observarla en tiempo real (el caso de los tres estados espera 1,5 s).
|
|
62
|
+
*/
|
|
63
|
+
injectableClock: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Los `list*` devuelven TODO sin tope. Es la verdad de los dos drivers del
|
|
66
|
+
* paquete: `database` (SQL sin límite) y `openfga` (enumeración por `Read`
|
|
67
|
+
* paginado, L0.7). Con `false` el harness debe dar `limits.listMaxResults`
|
|
68
|
+
* y el juez prueba solo la frontera exacta: es la declaración honesta de
|
|
69
|
+
* un driver de terceros cuyo backend trunca y que no lo señala.
|
|
70
|
+
*/
|
|
71
|
+
exhaustiveLists: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* El driver implementa el método opcional `listDenies` (2.1). Solo tiene
|
|
74
|
+
* casos en `level: '2.1'` (2E · I5): con `true` se juzgan `listDenies`,
|
|
75
|
+
* `effectivePermissions`, `authorizedScopes` y la versión compartida del
|
|
76
|
+
* catálogo; con `false` se juzga que esas primitivas lo digan (500
|
|
77
|
+
* `E_AUTHZ_UNSUPPORTED` nombrándolo) en vez de simular «sin denies». En un
|
|
78
|
+
* harness `core`/`'2.0'` no hay caso que lo observe: declara `false`.
|
|
79
|
+
*/
|
|
80
|
+
listDenies: boolean;
|
|
81
|
+
/**
|
|
82
|
+
* `purgeRole(roleUuid)` está implementado de verdad (3B · B4): revoca
|
|
83
|
+
* todas las asignaciones del rol en todos los scopes, borra sus vínculos
|
|
84
|
+
* y el rol, atómicamente, y recrear el slug no revive nada. Solo tiene
|
|
85
|
+
* casos en `level: '2.2'`: con `true` se juzga la purga; con `false` se
|
|
86
|
+
* juzga que el driver lo DIGA (500 `E_AUTHZ_UNSUPPORTED`) y que no deje
|
|
87
|
+
* nada a medias — el driver `openfga` del paquete hasta 3b (`facts` +
|
|
88
|
+
* `reconcile`): sus bindings no son enumerables por rol sin leer el
|
|
89
|
+
* store entero. En un harness por debajo de `'2.2'` declara `false`.
|
|
90
|
+
*/
|
|
91
|
+
purgeRole: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Las escrituras del CATÁLOGO se serializan de verdad en el motor (3E ·
|
|
94
|
+
* R2, tester): dos `defineScopedRole` del mismo `(slug, nivel)` en
|
|
95
|
+
* paralelo terminan con EXACTAMENTE uno confirmado y el otro con 422
|
|
96
|
+
* `E_AUTHZ_CATALOG_CONFLICT` — nunca un fallo de backend. Es lo que da el
|
|
97
|
+
* cerrojo sobre la fila de `authz_catalog_version` (3D · M2) en PostgreSQL
|
|
98
|
+
* y MySQL, y es la promesa que el README hace.
|
|
99
|
+
*
|
|
100
|
+
* Con `false` (SQLite, que serializa bloqueando la BASE ENTERA) el juez
|
|
101
|
+
* solo puede exigir lo innegociable: nunca dos ganadores y el perdedor no
|
|
102
|
+
* escribe — su transacción puede morir con `SQLITE_BUSY`, que es un 503
|
|
103
|
+
* legítimo. Declararlo `true` en un motor así convierte un flake en un
|
|
104
|
+
* fallo; declararlo `false` en PG/MySQL deja pasar un mutante que
|
|
105
|
+
* convierte la colisión en 503. Solo tiene casos en `level: '2.2'`.
|
|
106
|
+
*/
|
|
107
|
+
serializedCatalogWrites: boolean;
|
|
108
|
+
}
|
|
109
|
+
export type ContractLevel = 'core' | '2.0' | '2.1' | '2.2';
|
|
20
110
|
export interface DriverContractHarness {
|
|
21
111
|
name: string;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
112
|
+
/** `core` (default): los casos de 1.x. `'2.0'`: además los de la Fase 0/1. `'2.1'`: además las primitivas de la Fase 2. `'2.2'`: además los roles locales a un scope (Fase 3). */
|
|
113
|
+
level?: ContractLevel;
|
|
114
|
+
capabilities: DriverCapabilities;
|
|
115
|
+
/** Tope de resultados del backend de test. Obligatorio con `exhaustiveLists: false`. */
|
|
116
|
+
limits?: {
|
|
117
|
+
listMaxResults?: number;
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* Materializa el catálogo del juez. Devuelve lo que quiera (3E: el
|
|
121
|
+
* `syncAuthzCatalog` del paquete devuelve su reporte): el juez no lo mira.
|
|
122
|
+
*/
|
|
123
|
+
seedCatalog(catalog: CatalogSpec): Promise<unknown>;
|
|
124
|
+
/** Árbol que usará la suite. Default: `memoryScopeTree()`. */
|
|
125
|
+
makeTree?(): Promise<ContractScopeTree>;
|
|
126
|
+
makeDriver(tree: ContractScopeTree): AuthorizationDriver | Promise<AuthorizationDriver>;
|
|
127
|
+
/**
|
|
128
|
+
* OTRA instancia del driver sobre el MISMO backend de hechos y con su
|
|
129
|
+
* propio memo del catálogo — lo que sería el mismo driver en otro proceso
|
|
130
|
+
* (2D · F1). Default: si el driver expone `catalog` (`CatalogCache`, los
|
|
131
|
+
* dos del paquete), una vista por prototipo con un memo nuevo; si no, el
|
|
132
|
+
* mismo driver (un driver que lea el catálogo en vivo pasa igual).
|
|
133
|
+
*/
|
|
134
|
+
makeTwin?(driver: AuthorizationDriver, tree: ContractScopeTree): AuthorizationDriver | Promise<AuthorizationDriver>;
|
|
135
|
+
cleanup(): Promise<void>;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Lo mínimo que el juez necesita del runner. En producción es el `test` de
|
|
139
|
+
* Japa; en `tests/contract_harness.spec.ts` es una API falsa que solo anota
|
|
140
|
+
* títulos, porque Japa no permite registrar grupos desde un test en marcha y
|
|
141
|
+
* la regla de capacidades hay que poder probarla.
|
|
142
|
+
*/
|
|
143
|
+
export interface ContractTestApi {
|
|
144
|
+
group(title: string, define: (group: {
|
|
145
|
+
each: {
|
|
146
|
+
setup(fn: () => Promise<void>): unknown;
|
|
147
|
+
};
|
|
148
|
+
teardown(fn: () => Promise<void>): unknown;
|
|
149
|
+
}) => void): unknown;
|
|
150
|
+
test(title: string, fn: (ctx: {
|
|
151
|
+
assert: Assert;
|
|
152
|
+
}) => Promise<void>): {
|
|
153
|
+
timeout(ms: number): unknown;
|
|
154
|
+
} | undefined | void;
|
|
25
155
|
}
|
|
26
156
|
export declare function runAuthorizationDriverContract(harness: DriverContractHarness): void;
|
|
157
|
+
export declare function registerAuthorizationDriverContract(harness: DriverContractHarness, api: ContractTestApi): void;
|
|
27
158
|
//# sourceMappingURL=contract.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../../src/testing/contract.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../../src/testing/contract.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAA;AACrB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAG1C,OAAO,KAAK,EACV,mBAAmB,EAEnB,WAAW,EAKZ,MAAM,aAAa,CAAA;AAQpB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAExD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,oEAAoE;IACpE,cAAc,EAAE,OAAO,CAAA;IACvB,6DAA6D;IAC7D,YAAY,EAAE,OAAO,CAAA;IACrB;;;;;OAKG;IACH,gBAAgB,EAAE,OAAO,CAAA;IACzB,uEAAuE;IACvE,oBAAoB,EAAE,OAAO,CAAA;IAC7B;;;;;;;OAOG;IACH,eAAe,EAAE,OAAO,CAAA;IACxB;;;;;;OAMG;IACH,eAAe,EAAE,OAAO,CAAA;IACxB;;;;;;;OAOG;IACH,UAAU,EAAE,OAAO,CAAA;IACnB;;;;;;;;;OASG;IACH,SAAS,EAAE,OAAO,CAAA;IAClB;;;;;;;;;;;;;;OAcG;IACH,uBAAuB,EAAE,OAAO,CAAA;CACjC;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAA;AAE1D,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAA;IACZ,kLAAkL;IAClL,KAAK,CAAC,EAAE,aAAa,CAAA;IACrB,YAAY,EAAE,kBAAkB,CAAA;IAChC,wFAAwF;IACxF,MAAM,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACpC;;;OAGG;IACH,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACnD,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAAA;IACvC,UAAU,CAAC,IAAI,EAAE,iBAAiB,GAAG,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAA;IACvF;;;;;;OAMG;IACH,QAAQ,CAAC,CAAC,MAAM,EAAE,mBAAmB,EAAE,IAAI,EAAE,iBAAiB,GAAG,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAA;IACnH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACzB;AAiPD;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,CACH,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,CAAC,KAAK,EAAE;QACd,IAAI,EAAE;YAAE,KAAK,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAA;SAAE,CAAA;QACjD,QAAQ,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAA;KAC3C,KAAK,IAAI,GACT,OAAO,CAAA;IACV,IAAI,CACF,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,CAAC,GAAG,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAC7C;QAAE,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,GAAG,SAAS,GAAG,IAAI,CAAA;CACvD;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,qBAAqB,QAK5E;AAED,wBAAgB,mCAAmC,CACjD,OAAO,EAAE,qBAAqB,EAC9B,GAAG,EAAE,eAAe,QAuyGrB"}
|