@api-client/core 0.20.1 → 0.20.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@api-client/core",
3
3
  "description": "The API Client's core client library. Works in NodeJS and in a ES enabled browser.",
4
- "version": "0.20.1",
4
+ "version": "0.20.3",
5
5
  "license": "UNLICENSED",
6
6
  "exports": {
7
7
  "./browser.js": {
@@ -154,7 +154,8 @@
154
154
  "build:node": "tsc --project tsconfig.node.json",
155
155
  "build": "npm run build:ts && npm run copy:assets",
156
156
  "prepare": "husky && npm run build:ts",
157
- "tsc": "tsc",
157
+ "tsc": "tsc --project tsconfig.json",
158
+ "typecheck": "tsc --noEmit --project tsconfig.json",
158
159
  "tsc:tests": "tsc --project tsconfig.browser.json",
159
160
  "tsc:watch": "tsc --watch --project tsconfig.json",
160
161
  "test:browser": "wtr --playwright --browsers chromium",
@@ -39,19 +39,20 @@ export interface RuntimeApiModelSchema extends ApiModelSchema {
39
39
  }
40
40
 
41
41
  export interface RuntimeResolvedAction {
42
- entity: ExposedEntity
42
+ exposure: ExposedEntity
43
+ entity: DomainEntity
43
44
  action: Action
44
45
  params: Record<string, string>
45
46
  }
46
47
 
47
48
  export type RuleEvaluator = (rule: AccessRule) => Promise<boolean | undefined> | boolean | undefined
48
49
 
49
- interface PhaseRules {
50
+ export interface PhaseRules {
50
51
  permissionRules: AccessRule[]
51
52
  mandatoryRules: AccessRule[]
52
53
  }
53
54
 
54
- interface ActionRulesCache {
55
+ export interface ActionRulesCache {
55
56
  preFetch: PhaseRules
56
57
  fetch: PhaseRules
57
58
  postFetch: PhaseRules
@@ -281,17 +282,23 @@ export class RuntimeApiModel extends ApiModel {
281
282
 
282
283
  const params = exec(path, matchedRoute)
283
284
 
284
- const entity = this.exposes.get(def.lookup.exposedEntityKey)
285
- if (!entity) {
286
- return undefined
285
+ const exposure = this.exposes.get(def.lookup.exposedEntityKey)
286
+ if (!exposure) {
287
+ throw new Exception('Missing exposure ' + def.lookup.exposedEntityKey, { code: 'API_MODEL_ERROR', status: 500 })
287
288
  }
288
289
 
289
- const action = entity.actions.find((a) => a.kind === def.lookup.actionKind)
290
+ const action = exposure.actions.find((a) => a.kind === def.lookup.actionKind)
290
291
  if (!action) {
291
- return undefined
292
+ throw new Exception('Missing action ' + def.lookup.actionKind, { code: 'API_MODEL_ERROR', status: 500 })
293
+ }
294
+
295
+ const entity = this.domain?.findEntity(exposure.entity.key, exposure.entity.domain)
296
+ if (!entity) {
297
+ throw new Exception('Missing entity ' + exposure.entity.key, { code: 'API_MODEL_ERROR', status: 500 })
292
298
  }
293
299
 
294
300
  return {
301
+ exposure,
295
302
  entity,
296
303
  action,
297
304
  params,
@@ -299,64 +306,15 @@ export class RuntimeApiModel extends ApiModel {
299
306
  }
300
307
 
301
308
  /**
302
- * Evaluates access rules for a given action and phase.
303
- *
304
- * The evaluation process follows two phases per execution phase (PRE_FETCH, FETCH, POST_FETCH):
305
- * 1. Mandatory Phase: All rules marked as `mandatory: true` across all levels must return true.
306
- * If any fail, the request is immediately rejected.
307
- * 2. Permission Phase: Evaluation follows the hierarchy from most specific to most general:
308
- * Action -> Endpoint (ExposedEntity) -> API Model.
309
- * If an explicit allow (true) or deny (false) is hit, evaluation stops and returns the result.
310
- * If no rules match (all return undefined), the request is rejected by default.
311
- *
312
- * @param action The resolved action to evaluate.
313
- * @param evaluator A callback that evaluates a single rule. Should return true (allow),
314
- * false (deny), or undefined (no hit).
315
- * @param phase The execution phase to evaluate.
316
- * @returns A promise that resolves to true if access is granted, false if denied.
309
+ * Retrieves the precomputed and shadowed effective rules for a given action.
317
310
  */
318
- async evaluateAccess(
319
- action: RuntimeResolvedAction,
320
- evaluator: RuleEvaluator,
321
- phase: AccessRuleExecutionPhase
322
- ): Promise<boolean> {
311
+ getEffectiveRules(action: RuntimeResolvedAction): ActionRulesCache {
323
312
  let cachedRules = this.#actionRulesCache.get(action.action)
324
313
  if (!cachedRules) {
325
- // Fallback if somehow action is not cached (e.g. dynamically added after initialization or in tests)
326
- cachedRules = this.#computeEffectiveRules(action.action, action.entity)
314
+ cachedRules = this.#computeEffectiveRules(action.action, action.exposure)
327
315
  this.#actionRulesCache.set(action.action, cachedRules)
328
316
  }
329
-
330
- let rulesForPhase
331
- if (phase === AccessRuleExecutionPhase.POST_FETCH) {
332
- rulesForPhase = cachedRules.postFetch
333
- } else if (phase === AccessRuleExecutionPhase.FETCH) {
334
- rulesForPhase = cachedRules.fetch
335
- } else {
336
- rulesForPhase = cachedRules.preFetch
337
- }
338
-
339
- // Step 1: Mandatory Phase
340
- for (const rule of rulesForPhase.mandatoryRules) {
341
- const result = await evaluator(rule)
342
- if (result !== true) {
343
- return false // Immediately reject if any mandatory rule fails
344
- }
345
- }
346
-
347
- // Step 2-4: Permission Phase
348
- for (const rule of rulesForPhase.permissionRules) {
349
- const result = await evaluator(rule)
350
- if (result === true) {
351
- return true
352
- }
353
- if (result === false) {
354
- return false
355
- }
356
- }
357
-
358
- // Default: no hit
359
- return false
317
+ return cachedRules
360
318
  }
361
319
 
362
320
  override toJSON(): RuntimeApiModelSchema {