@api-client/core 0.20.1 → 0.20.2
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/build/src/modeling/RuntimeApiModel.d.ts +14 -18
- package/build/src/modeling/RuntimeApiModel.d.ts.map +1 -1
- package/build/src/modeling/RuntimeApiModel.js +14 -52
- package/build/src/modeling/RuntimeApiModel.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/modeling/RuntimeApiModel.ts +19 -61
- package/tests/unit/modeling/RuntimeApiModel.spec.ts +40 -108
|
@@ -5,7 +5,7 @@ import type { ExposedEntity } from './ExposedEntity.js';
|
|
|
5
5
|
import type { Action } from './actions/Action.js';
|
|
6
6
|
import type { DomainEntity } from './DomainEntity.js';
|
|
7
7
|
import type { DomainProperty } from './DomainProperty.js';
|
|
8
|
-
import { AccessRule
|
|
8
|
+
import { AccessRule } from './rules/AccessRule.js';
|
|
9
9
|
/**
|
|
10
10
|
* Identifies a specific exposed entity and its action kind.
|
|
11
11
|
*/
|
|
@@ -31,11 +31,21 @@ export interface RuntimeApiModelSchema extends ApiModelSchema {
|
|
|
31
31
|
routingMap: RoutingMap;
|
|
32
32
|
}
|
|
33
33
|
export interface RuntimeResolvedAction {
|
|
34
|
-
|
|
34
|
+
exposure: ExposedEntity;
|
|
35
|
+
entity: DomainEntity;
|
|
35
36
|
action: Action;
|
|
36
37
|
params: Record<string, string>;
|
|
37
38
|
}
|
|
38
39
|
export type RuleEvaluator = (rule: AccessRule) => Promise<boolean | undefined> | boolean | undefined;
|
|
40
|
+
export interface PhaseRules {
|
|
41
|
+
permissionRules: AccessRule[];
|
|
42
|
+
mandatoryRules: AccessRule[];
|
|
43
|
+
}
|
|
44
|
+
export interface ActionRulesCache {
|
|
45
|
+
preFetch: PhaseRules;
|
|
46
|
+
fetch: PhaseRules;
|
|
47
|
+
postFetch: PhaseRules;
|
|
48
|
+
}
|
|
39
49
|
/**
|
|
40
50
|
* An optimized API Model subclass designed for fast runtime lookups.
|
|
41
51
|
* It pre-compiles the RoutingMap into a radix tree for O(log N) or faster endpoint resolution.
|
|
@@ -71,23 +81,9 @@ export declare class RuntimeApiModel extends ApiModel {
|
|
|
71
81
|
*/
|
|
72
82
|
lookupAction(method: string, path: string): RuntimeResolvedAction | undefined;
|
|
73
83
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* The evaluation process follows two phases per execution phase (PRE_FETCH, FETCH, POST_FETCH):
|
|
77
|
-
* 1. Mandatory Phase: All rules marked as `mandatory: true` across all levels must return true.
|
|
78
|
-
* If any fail, the request is immediately rejected.
|
|
79
|
-
* 2. Permission Phase: Evaluation follows the hierarchy from most specific to most general:
|
|
80
|
-
* Action -> Endpoint (ExposedEntity) -> API Model.
|
|
81
|
-
* If an explicit allow (true) or deny (false) is hit, evaluation stops and returns the result.
|
|
82
|
-
* If no rules match (all return undefined), the request is rejected by default.
|
|
83
|
-
*
|
|
84
|
-
* @param action The resolved action to evaluate.
|
|
85
|
-
* @param evaluator A callback that evaluates a single rule. Should return true (allow),
|
|
86
|
-
* false (deny), or undefined (no hit).
|
|
87
|
-
* @param phase The execution phase to evaluate.
|
|
88
|
-
* @returns A promise that resolves to true if access is granted, false if denied.
|
|
84
|
+
* Retrieves the precomputed and shadowed effective rules for a given action.
|
|
89
85
|
*/
|
|
90
|
-
|
|
86
|
+
getEffectiveRules(action: RuntimeResolvedAction): ActionRulesCache;
|
|
91
87
|
toJSON(): RuntimeApiModelSchema;
|
|
92
88
|
}
|
|
93
89
|
//# sourceMappingURL=RuntimeApiModel.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RuntimeApiModel.d.ts","sourceRoot":"","sources":["../../../src/modeling/RuntimeApiModel.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAA;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAEjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACzD,OAAO,EAAE,UAAU,
|
|
1
|
+
{"version":3,"file":"RuntimeApiModel.d.ts","sourceRoot":"","sources":["../../../src/modeling/RuntimeApiModel.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAA;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAEjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACzD,OAAO,EAAE,UAAU,EAA4B,MAAM,uBAAuB,CAAA;AAG5E;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,gBAAgB,EAAE,MAAM,CAAA;IACxB,UAAU,EAAE,UAAU,CAAA;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,WAAW,CAAA;CACpB;AAED;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,EAAE,CAAC,CAAA;AAE1D;;GAEG;AACH,MAAM,WAAW,qBAAsB,SAAQ,cAAc;IAC3D,UAAU,EAAE,UAAU,CAAA;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,aAAa,CAAA;IACvB,MAAM,EAAE,YAAY,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC/B;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,GAAG,OAAO,GAAG,SAAS,CAAA;AAEpG,MAAM,WAAW,UAAU;IACzB,eAAe,EAAE,UAAU,EAAE,CAAA;IAC7B,cAAc,EAAE,UAAU,EAAE,CAAA;CAC7B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,UAAU,CAAA;IACpB,KAAK,EAAE,UAAU,CAAA;IACjB,SAAS,EAAE,UAAU,CAAA;CACtB;AAED;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,QAAQ;;IAY3C;;OAEG;IACH,cAAc,EAAE;QACd,IAAI,CAAC,EAAE,YAAY,CAAA;KACpB,CAAK;IAEN;;OAEG;IACH,gBAAgB,EAAE;QAChB,QAAQ,CAAC,EAAE,cAAc,CAAA;QACzB,QAAQ,CAAC,EAAE,cAAc,CAAA;QACzB,IAAI,CAAC,EAAE,cAAc,CAAA;KACtB,CAAK;IAYN;;;OAGG;IACH,IAAW,iBAAiB,IAAI,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC,CAElE;gBAEW,MAAM,EAAE,qBAAqB,EAAE,YAAY,EAAE,gBAAgB;IAoJzE;;;;;;OAMG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,qBAAqB,GAAG,SAAS;IA0C7E;;OAEG;IACH,iBAAiB,CAAC,MAAM,EAAE,qBAAqB,GAAG,gBAAgB;IASzD,MAAM,IAAI,qBAAqB;CAazC"}
|
|
@@ -191,73 +191,35 @@ export class RuntimeApiModel extends ApiModel {
|
|
|
191
191
|
return undefined;
|
|
192
192
|
}
|
|
193
193
|
const params = exec(path, matchedRoute);
|
|
194
|
-
const
|
|
195
|
-
if (!
|
|
196
|
-
|
|
194
|
+
const exposure = this.exposes.get(def.lookup.exposedEntityKey);
|
|
195
|
+
if (!exposure) {
|
|
196
|
+
throw new Exception('Missing exposure ' + def.lookup.exposedEntityKey, { code: 'API_MODEL_ERROR', status: 500 });
|
|
197
197
|
}
|
|
198
|
-
const action =
|
|
198
|
+
const action = exposure.actions.find((a) => a.kind === def.lookup.actionKind);
|
|
199
199
|
if (!action) {
|
|
200
|
-
|
|
200
|
+
throw new Exception('Missing action ' + def.lookup.actionKind, { code: 'API_MODEL_ERROR', status: 500 });
|
|
201
|
+
}
|
|
202
|
+
const entity = this.domain?.findEntity(exposure.entity.key, exposure.entity.domain);
|
|
203
|
+
if (!entity) {
|
|
204
|
+
throw new Exception('Missing entity ' + exposure.entity.key, { code: 'API_MODEL_ERROR', status: 500 });
|
|
201
205
|
}
|
|
202
206
|
return {
|
|
207
|
+
exposure,
|
|
203
208
|
entity,
|
|
204
209
|
action,
|
|
205
210
|
params,
|
|
206
211
|
};
|
|
207
212
|
}
|
|
208
213
|
/**
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
* The evaluation process follows two phases per execution phase (PRE_FETCH, FETCH, POST_FETCH):
|
|
212
|
-
* 1. Mandatory Phase: All rules marked as `mandatory: true` across all levels must return true.
|
|
213
|
-
* If any fail, the request is immediately rejected.
|
|
214
|
-
* 2. Permission Phase: Evaluation follows the hierarchy from most specific to most general:
|
|
215
|
-
* Action -> Endpoint (ExposedEntity) -> API Model.
|
|
216
|
-
* If an explicit allow (true) or deny (false) is hit, evaluation stops and returns the result.
|
|
217
|
-
* If no rules match (all return undefined), the request is rejected by default.
|
|
218
|
-
*
|
|
219
|
-
* @param action The resolved action to evaluate.
|
|
220
|
-
* @param evaluator A callback that evaluates a single rule. Should return true (allow),
|
|
221
|
-
* false (deny), or undefined (no hit).
|
|
222
|
-
* @param phase The execution phase to evaluate.
|
|
223
|
-
* @returns A promise that resolves to true if access is granted, false if denied.
|
|
214
|
+
* Retrieves the precomputed and shadowed effective rules for a given action.
|
|
224
215
|
*/
|
|
225
|
-
|
|
216
|
+
getEffectiveRules(action) {
|
|
226
217
|
let cachedRules = this.#actionRulesCache.get(action.action);
|
|
227
218
|
if (!cachedRules) {
|
|
228
|
-
|
|
229
|
-
cachedRules = this.#computeEffectiveRules(action.action, action.entity);
|
|
219
|
+
cachedRules = this.#computeEffectiveRules(action.action, action.exposure);
|
|
230
220
|
this.#actionRulesCache.set(action.action, cachedRules);
|
|
231
221
|
}
|
|
232
|
-
|
|
233
|
-
if (phase === AccessRuleExecutionPhase.POST_FETCH) {
|
|
234
|
-
rulesForPhase = cachedRules.postFetch;
|
|
235
|
-
}
|
|
236
|
-
else if (phase === AccessRuleExecutionPhase.FETCH) {
|
|
237
|
-
rulesForPhase = cachedRules.fetch;
|
|
238
|
-
}
|
|
239
|
-
else {
|
|
240
|
-
rulesForPhase = cachedRules.preFetch;
|
|
241
|
-
}
|
|
242
|
-
// Step 1: Mandatory Phase
|
|
243
|
-
for (const rule of rulesForPhase.mandatoryRules) {
|
|
244
|
-
const result = await evaluator(rule);
|
|
245
|
-
if (result !== true) {
|
|
246
|
-
return false; // Immediately reject if any mandatory rule fails
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
// Step 2-4: Permission Phase
|
|
250
|
-
for (const rule of rulesForPhase.permissionRules) {
|
|
251
|
-
const result = await evaluator(rule);
|
|
252
|
-
if (result === true) {
|
|
253
|
-
return true;
|
|
254
|
-
}
|
|
255
|
-
if (result === false) {
|
|
256
|
-
return false;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
// Default: no hit
|
|
260
|
-
return false;
|
|
222
|
+
return cachedRules;
|
|
261
223
|
}
|
|
262
224
|
toJSON() {
|
|
263
225
|
const base = super.toJSON();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RuntimeApiModel.js","sourceRoot":"","sources":["../../../src/modeling/RuntimeApiModel.ts"],"names":[],"mappings":"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","sourcesContent":["import { match, parse, exec, type RouteToken } from '@poppinss/matchit'\nimport { ApiModel, type ApiModelSchema } from './ApiModel.js'\nimport type { DataDomainSchema } from './DataDomain.js'\nimport type { ActionKind } from './actions/index.js'\nimport type { ExposedEntity } from './ExposedEntity.js'\nimport type { Action } from './actions/Action.js'\nimport { SemanticType } from './Semantics.js'\nimport type { DomainEntity } from './DomainEntity.js'\nimport type { DomainProperty } from './DomainProperty.js'\nimport { AccessRule, AccessRuleExecutionPhase } from './rules/AccessRule.js'\nimport { Exception } from '../exceptions/exception.js'\n\n/**\n * Identifies a specific exposed entity and its action kind.\n */\nexport interface RouteLookup {\n exposedEntityKey: string\n actionKind: ActionKind\n}\n\n/**\n * A routing definition mapping a path to an action lookup.\n */\nexport interface RouteDefinition {\n path: string\n lookup: RouteLookup\n}\n\n/**\n * A map of HTTP methods to their route definitions.\n */\nexport type RoutingMap = Record<string, RouteDefinition[]>\n\n/**\n * Schema for an API Model optimized for runtime routing.\n */\nexport interface RuntimeApiModelSchema extends ApiModelSchema {\n routingMap: RoutingMap\n}\n\nexport interface RuntimeResolvedAction {\n entity: ExposedEntity\n action: Action\n params: Record<string, string>\n}\n\nexport type RuleEvaluator = (rule: AccessRule) => Promise<boolean | undefined> | boolean | undefined\n\ninterface PhaseRules {\n permissionRules: AccessRule[]\n mandatoryRules: AccessRule[]\n}\n\ninterface ActionRulesCache {\n preFetch: PhaseRules\n fetch: PhaseRules\n postFetch: PhaseRules\n}\n\n/**\n * An optimized API Model subclass designed for fast runtime lookups.\n * It pre-compiles the RoutingMap into a radix tree for O(log N) or faster endpoint resolution.\n */\nexport class RuntimeApiModel extends ApiModel {\n /**\n * The parsed radix tree for fast routing.\n * Method -> ParsedRoutes\n */\n #routes = new Map<string, RouteToken[][]>()\n\n /**\n * Quick map from matchit parsed route format to our RouteDefinition for returning results\n */\n #definitions = new WeakMap<RouteToken[], RouteDefinition>()\n\n /**\n * Cached references to commonly used entities for fast runtime lookup.\n */\n cachedEntities: {\n user?: DomainEntity\n } = {}\n\n /**\n * Cached references to commonly used properties for fast runtime lookup.\n */\n cachedProperties: {\n username?: DomainProperty\n password?: DomainProperty\n role?: DomainProperty\n } = {}\n\n /**\n * Cached access rules for each action to avoid runtime computation overhead.\n */\n #actionRulesCache = new WeakMap<Action, ActionRulesCache>()\n\n /**\n * Cached session properties for fast runtime lookup.\n */\n readonly #sessionProperties = new Map<string, DomainProperty>()\n\n /**\n * Returns a readonly map of session properties.\n * Note, it creates a copy of the cached map to prevent modification of the internal state.\n */\n public get sessionProperties(): ReadonlyMap<string, DomainProperty> {\n return new Map(this.#sessionProperties)\n }\n\n constructor(schema: RuntimeApiModelSchema, domainSchema: DataDomainSchema) {\n super(schema, domainSchema)\n\n if (schema.routingMap) {\n this.#initializeRouter(schema.routingMap)\n }\n\n this.#cacheEntitiesAndProperties()\n this.#precomputeAccessRules()\n this.#precomputeSessionProperties()\n }\n\n #precomputeSessionProperties(): void {\n if (!this.session || !this.domain) {\n return\n }\n for (const prop of this.session.properties) {\n const domainProperty = this.domain.findProperty(prop.key, prop.domain)\n if (!domainProperty) {\n throw new Exception(`Session property ${prop.key} not found in domain`)\n }\n this.#sessionProperties.set(prop.key, domainProperty)\n }\n }\n\n #precomputeAccessRules(): void {\n for (const entity of this.exposes.values()) {\n for (const action of entity.actions) {\n this.#actionRulesCache.set(action, this.#computeEffectiveRules(action, entity))\n }\n }\n }\n\n #computeEffectiveRules(action: Action, entity: ExposedEntity): ActionRulesCache {\n const hierarchy: AccessRule[][] = []\n\n if (action.accessRule && action.accessRule.length > 0) {\n hierarchy.push(action.accessRule)\n }\n\n let currentEntity: ExposedEntity | undefined = entity\n while (currentEntity) {\n if (currentEntity.accessRule && currentEntity.accessRule.length > 0) {\n hierarchy.push(currentEntity.accessRule)\n }\n currentEntity = currentEntity.parent ? this.exposes.get(currentEntity.parent.key) : undefined\n }\n\n if (this.accessRule && this.accessRule.length > 0) {\n hierarchy.push(this.accessRule)\n }\n\n const result: ActionRulesCache = {\n preFetch: {\n permissionRules: [],\n mandatoryRules: [],\n },\n fetch: {\n permissionRules: [],\n mandatoryRules: [],\n },\n postFetch: {\n permissionRules: [],\n mandatoryRules: [],\n },\n }\n\n const seenTypes = new Set<string>()\n\n for (const rulesLevel of hierarchy) {\n const typesInThisLevel = new Set<string>()\n for (const rule of rulesLevel) {\n const phase = rule.metadata[action.kind as keyof typeof rule.metadata]\n\n // If the rule does not specify an execution phase for this action kind, it is not evaluated\n if (!phase) {\n continue\n }\n\n // Shadowing: If a rule of the same type was defined closer to the action, ignore this one.\n if (seenTypes.has(rule.type)) {\n continue\n }\n typesInThisLevel.add(rule.type)\n\n let bucket\n if (phase === AccessRuleExecutionPhase.POST_FETCH) {\n bucket = result.postFetch\n } else if (phase === AccessRuleExecutionPhase.FETCH) {\n bucket = result.fetch\n } else {\n bucket = result.preFetch\n }\n\n if (rule.mandatory) {\n bucket.mandatoryRules.push(rule)\n } else {\n bucket.permissionRules.push(rule)\n }\n }\n for (const type of typesInThisLevel) {\n seenTypes.add(type)\n }\n }\n\n return result\n }\n\n #cacheEntitiesAndProperties(): void {\n if (!this.user || !this.domain) {\n return\n }\n\n const userEntity = this.domain.findEntity(this.user.key, this.user.domain)\n if (!userEntity) {\n return\n }\n\n this.cachedEntities.user = userEntity\n\n for (const prop of userEntity.properties) {\n if (prop.hasSemantic(SemanticType.Username)) {\n this.cachedProperties.username = prop\n }\n if (prop.hasSemantic(SemanticType.Password)) {\n this.cachedProperties.password = prop\n }\n if (prop.hasSemantic(SemanticType.UserRole)) {\n this.cachedProperties.role = prop\n }\n }\n }\n\n #initializeRouter(routingMap: RoutingMap): void {\n for (const [method, definitions] of Object.entries(routingMap)) {\n const parsedRoutes = []\n for (const def of definitions) {\n // matchit's parse() transforms a route string into an object representation.\n // It expects `:param` syntax, while our API models use OpenAPI `{param}` syntax.\n const matchitPath = def.path.replace(/\\{([^}]+)\\}/g, ':$1')\n const parsed = parse(matchitPath)\n this.#definitions.set(parsed, def)\n parsedRoutes.push(parsed)\n }\n this.#routes.set(method.toUpperCase(), parsedRoutes)\n }\n }\n\n /**\n * Looks up the corresponding exposed entity and action for a given request.\n *\n * @param method The HTTP method (e.g., 'GET', 'POST').\n * @param path The request path (e.g., '/users/123').\n * @returns An object with the entity, action, and extracted path parameters if a match is found.\n */\n lookupAction(method: string, path: string): RuntimeResolvedAction | undefined {\n const parsedRoutes = this.#routes.get(method.toUpperCase())\n if (!parsedRoutes) {\n return undefined\n }\n\n // `match` returns the matching parsed route (which is an array of segments), or empty array if no match\n const matchedRoute = match(path, parsedRoutes)\n if (!matchedRoute || matchedRoute.length === 0) {\n return undefined\n }\n\n const def = this.#definitions.get(matchedRoute)\n if (!def) {\n return undefined\n }\n\n const params = exec(path, matchedRoute)\n\n const entity = this.exposes.get(def.lookup.exposedEntityKey)\n if (!entity) {\n return undefined\n }\n\n const action = entity.actions.find((a) => a.kind === def.lookup.actionKind)\n if (!action) {\n return undefined\n }\n\n return {\n entity,\n action,\n params,\n }\n }\n\n /**\n * Evaluates access rules for a given action and phase.\n *\n * The evaluation process follows two phases per execution phase (PRE_FETCH, FETCH, POST_FETCH):\n * 1. Mandatory Phase: All rules marked as `mandatory: true` across all levels must return true.\n * If any fail, the request is immediately rejected.\n * 2. Permission Phase: Evaluation follows the hierarchy from most specific to most general:\n * Action -> Endpoint (ExposedEntity) -> API Model.\n * If an explicit allow (true) or deny (false) is hit, evaluation stops and returns the result.\n * If no rules match (all return undefined), the request is rejected by default.\n *\n * @param action The resolved action to evaluate.\n * @param evaluator A callback that evaluates a single rule. Should return true (allow),\n * false (deny), or undefined (no hit).\n * @param phase The execution phase to evaluate.\n * @returns A promise that resolves to true if access is granted, false if denied.\n */\n async evaluateAccess(\n action: RuntimeResolvedAction,\n evaluator: RuleEvaluator,\n phase: AccessRuleExecutionPhase\n ): Promise<boolean> {\n let cachedRules = this.#actionRulesCache.get(action.action)\n if (!cachedRules) {\n // Fallback if somehow action is not cached (e.g. dynamically added after initialization or in tests)\n cachedRules = this.#computeEffectiveRules(action.action, action.entity)\n this.#actionRulesCache.set(action.action, cachedRules)\n }\n\n let rulesForPhase\n if (phase === AccessRuleExecutionPhase.POST_FETCH) {\n rulesForPhase = cachedRules.postFetch\n } else if (phase === AccessRuleExecutionPhase.FETCH) {\n rulesForPhase = cachedRules.fetch\n } else {\n rulesForPhase = cachedRules.preFetch\n }\n\n // Step 1: Mandatory Phase\n for (const rule of rulesForPhase.mandatoryRules) {\n const result = await evaluator(rule)\n if (result !== true) {\n return false // Immediately reject if any mandatory rule fails\n }\n }\n\n // Step 2-4: Permission Phase\n for (const rule of rulesForPhase.permissionRules) {\n const result = await evaluator(rule)\n if (result === true) {\n return true\n }\n if (result === false) {\n return false\n }\n }\n\n // Default: no hit\n return false\n }\n\n override toJSON(): RuntimeApiModelSchema {\n const base = super.toJSON() as RuntimeApiModelSchema\n\n const routingMap: RoutingMap = {}\n for (const [method, parsedRoutes] of this.#routes.entries()) {\n routingMap[method] = parsedRoutes.map((pr) => this.#definitions.get(pr) as RouteDefinition)\n }\n\n return {\n ...base,\n routingMap,\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"RuntimeApiModel.js","sourceRoot":"","sources":["../../../src/modeling/RuntimeApiModel.ts"],"names":[],"mappings":"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","sourcesContent":["import { match, parse, exec, type RouteToken } from '@poppinss/matchit'\nimport { ApiModel, type ApiModelSchema } from './ApiModel.js'\nimport type { DataDomainSchema } from './DataDomain.js'\nimport type { ActionKind } from './actions/index.js'\nimport type { ExposedEntity } from './ExposedEntity.js'\nimport type { Action } from './actions/Action.js'\nimport { SemanticType } from './Semantics.js'\nimport type { DomainEntity } from './DomainEntity.js'\nimport type { DomainProperty } from './DomainProperty.js'\nimport { AccessRule, AccessRuleExecutionPhase } from './rules/AccessRule.js'\nimport { Exception } from '../exceptions/exception.js'\n\n/**\n * Identifies a specific exposed entity and its action kind.\n */\nexport interface RouteLookup {\n exposedEntityKey: string\n actionKind: ActionKind\n}\n\n/**\n * A routing definition mapping a path to an action lookup.\n */\nexport interface RouteDefinition {\n path: string\n lookup: RouteLookup\n}\n\n/**\n * A map of HTTP methods to their route definitions.\n */\nexport type RoutingMap = Record<string, RouteDefinition[]>\n\n/**\n * Schema for an API Model optimized for runtime routing.\n */\nexport interface RuntimeApiModelSchema extends ApiModelSchema {\n routingMap: RoutingMap\n}\n\nexport interface RuntimeResolvedAction {\n exposure: ExposedEntity\n entity: DomainEntity\n action: Action\n params: Record<string, string>\n}\n\nexport type RuleEvaluator = (rule: AccessRule) => Promise<boolean | undefined> | boolean | undefined\n\nexport interface PhaseRules {\n permissionRules: AccessRule[]\n mandatoryRules: AccessRule[]\n}\n\nexport interface ActionRulesCache {\n preFetch: PhaseRules\n fetch: PhaseRules\n postFetch: PhaseRules\n}\n\n/**\n * An optimized API Model subclass designed for fast runtime lookups.\n * It pre-compiles the RoutingMap into a radix tree for O(log N) or faster endpoint resolution.\n */\nexport class RuntimeApiModel extends ApiModel {\n /**\n * The parsed radix tree for fast routing.\n * Method -> ParsedRoutes\n */\n #routes = new Map<string, RouteToken[][]>()\n\n /**\n * Quick map from matchit parsed route format to our RouteDefinition for returning results\n */\n #definitions = new WeakMap<RouteToken[], RouteDefinition>()\n\n /**\n * Cached references to commonly used entities for fast runtime lookup.\n */\n cachedEntities: {\n user?: DomainEntity\n } = {}\n\n /**\n * Cached references to commonly used properties for fast runtime lookup.\n */\n cachedProperties: {\n username?: DomainProperty\n password?: DomainProperty\n role?: DomainProperty\n } = {}\n\n /**\n * Cached access rules for each action to avoid runtime computation overhead.\n */\n #actionRulesCache = new WeakMap<Action, ActionRulesCache>()\n\n /**\n * Cached session properties for fast runtime lookup.\n */\n readonly #sessionProperties = new Map<string, DomainProperty>()\n\n /**\n * Returns a readonly map of session properties.\n * Note, it creates a copy of the cached map to prevent modification of the internal state.\n */\n public get sessionProperties(): ReadonlyMap<string, DomainProperty> {\n return new Map(this.#sessionProperties)\n }\n\n constructor(schema: RuntimeApiModelSchema, domainSchema: DataDomainSchema) {\n super(schema, domainSchema)\n\n if (schema.routingMap) {\n this.#initializeRouter(schema.routingMap)\n }\n\n this.#cacheEntitiesAndProperties()\n this.#precomputeAccessRules()\n this.#precomputeSessionProperties()\n }\n\n #precomputeSessionProperties(): void {\n if (!this.session || !this.domain) {\n return\n }\n for (const prop of this.session.properties) {\n const domainProperty = this.domain.findProperty(prop.key, prop.domain)\n if (!domainProperty) {\n throw new Exception(`Session property ${prop.key} not found in domain`)\n }\n this.#sessionProperties.set(prop.key, domainProperty)\n }\n }\n\n #precomputeAccessRules(): void {\n for (const entity of this.exposes.values()) {\n for (const action of entity.actions) {\n this.#actionRulesCache.set(action, this.#computeEffectiveRules(action, entity))\n }\n }\n }\n\n #computeEffectiveRules(action: Action, entity: ExposedEntity): ActionRulesCache {\n const hierarchy: AccessRule[][] = []\n\n if (action.accessRule && action.accessRule.length > 0) {\n hierarchy.push(action.accessRule)\n }\n\n let currentEntity: ExposedEntity | undefined = entity\n while (currentEntity) {\n if (currentEntity.accessRule && currentEntity.accessRule.length > 0) {\n hierarchy.push(currentEntity.accessRule)\n }\n currentEntity = currentEntity.parent ? this.exposes.get(currentEntity.parent.key) : undefined\n }\n\n if (this.accessRule && this.accessRule.length > 0) {\n hierarchy.push(this.accessRule)\n }\n\n const result: ActionRulesCache = {\n preFetch: {\n permissionRules: [],\n mandatoryRules: [],\n },\n fetch: {\n permissionRules: [],\n mandatoryRules: [],\n },\n postFetch: {\n permissionRules: [],\n mandatoryRules: [],\n },\n }\n\n const seenTypes = new Set<string>()\n\n for (const rulesLevel of hierarchy) {\n const typesInThisLevel = new Set<string>()\n for (const rule of rulesLevel) {\n const phase = rule.metadata[action.kind as keyof typeof rule.metadata]\n\n // If the rule does not specify an execution phase for this action kind, it is not evaluated\n if (!phase) {\n continue\n }\n\n // Shadowing: If a rule of the same type was defined closer to the action, ignore this one.\n if (seenTypes.has(rule.type)) {\n continue\n }\n typesInThisLevel.add(rule.type)\n\n let bucket\n if (phase === AccessRuleExecutionPhase.POST_FETCH) {\n bucket = result.postFetch\n } else if (phase === AccessRuleExecutionPhase.FETCH) {\n bucket = result.fetch\n } else {\n bucket = result.preFetch\n }\n\n if (rule.mandatory) {\n bucket.mandatoryRules.push(rule)\n } else {\n bucket.permissionRules.push(rule)\n }\n }\n for (const type of typesInThisLevel) {\n seenTypes.add(type)\n }\n }\n\n return result\n }\n\n #cacheEntitiesAndProperties(): void {\n if (!this.user || !this.domain) {\n return\n }\n\n const userEntity = this.domain.findEntity(this.user.key, this.user.domain)\n if (!userEntity) {\n return\n }\n\n this.cachedEntities.user = userEntity\n\n for (const prop of userEntity.properties) {\n if (prop.hasSemantic(SemanticType.Username)) {\n this.cachedProperties.username = prop\n }\n if (prop.hasSemantic(SemanticType.Password)) {\n this.cachedProperties.password = prop\n }\n if (prop.hasSemantic(SemanticType.UserRole)) {\n this.cachedProperties.role = prop\n }\n }\n }\n\n #initializeRouter(routingMap: RoutingMap): void {\n for (const [method, definitions] of Object.entries(routingMap)) {\n const parsedRoutes = []\n for (const def of definitions) {\n // matchit's parse() transforms a route string into an object representation.\n // It expects `:param` syntax, while our API models use OpenAPI `{param}` syntax.\n const matchitPath = def.path.replace(/\\{([^}]+)\\}/g, ':$1')\n const parsed = parse(matchitPath)\n this.#definitions.set(parsed, def)\n parsedRoutes.push(parsed)\n }\n this.#routes.set(method.toUpperCase(), parsedRoutes)\n }\n }\n\n /**\n * Looks up the corresponding exposed entity and action for a given request.\n *\n * @param method The HTTP method (e.g., 'GET', 'POST').\n * @param path The request path (e.g., '/users/123').\n * @returns An object with the entity, action, and extracted path parameters if a match is found.\n */\n lookupAction(method: string, path: string): RuntimeResolvedAction | undefined {\n const parsedRoutes = this.#routes.get(method.toUpperCase())\n if (!parsedRoutes) {\n return undefined\n }\n\n // `match` returns the matching parsed route (which is an array of segments), or empty array if no match\n const matchedRoute = match(path, parsedRoutes)\n if (!matchedRoute || matchedRoute.length === 0) {\n return undefined\n }\n\n const def = this.#definitions.get(matchedRoute)\n if (!def) {\n return undefined\n }\n\n const params = exec(path, matchedRoute)\n\n const exposure = this.exposes.get(def.lookup.exposedEntityKey)\n if (!exposure) {\n throw new Exception('Missing exposure ' + def.lookup.exposedEntityKey, { code: 'API_MODEL_ERROR', status: 500 })\n }\n\n const action = exposure.actions.find((a) => a.kind === def.lookup.actionKind)\n if (!action) {\n throw new Exception('Missing action ' + def.lookup.actionKind, { code: 'API_MODEL_ERROR', status: 500 })\n }\n\n const entity = this.domain?.findEntity(exposure.entity.key, exposure.entity.domain)\n if (!entity) {\n throw new Exception('Missing entity ' + exposure.entity.key, { code: 'API_MODEL_ERROR', status: 500 })\n }\n\n return {\n exposure,\n entity,\n action,\n params,\n }\n }\n\n /**\n * Retrieves the precomputed and shadowed effective rules for a given action.\n */\n getEffectiveRules(action: RuntimeResolvedAction): ActionRulesCache {\n let cachedRules = this.#actionRulesCache.get(action.action)\n if (!cachedRules) {\n cachedRules = this.#computeEffectiveRules(action.action, action.exposure)\n this.#actionRulesCache.set(action.action, cachedRules)\n }\n return cachedRules\n }\n\n override toJSON(): RuntimeApiModelSchema {\n const base = super.toJSON() as RuntimeApiModelSchema\n\n const routingMap: RoutingMap = {}\n for (const [method, parsedRoutes] of this.#routes.entries()) {\n routingMap[method] = parsedRoutes.map((pr) => this.#definitions.get(pr) as RouteDefinition)\n }\n\n return {\n ...base,\n routingMap,\n }\n }\n}\n"]}
|