@api-client/core 0.20.6 → 0.20.8
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/ApiModel.d.ts.map +1 -1
- package/build/src/modeling/ApiModel.js +9 -5
- package/build/src/modeling/ApiModel.js.map +1 -1
- package/build/src/modeling/ExposedEntity.d.ts.map +1 -1
- package/build/src/modeling/ExposedEntity.js +55 -4
- package/build/src/modeling/ExposedEntity.js.map +1 -1
- package/build/src/modeling/RuntimeApiModel.d.ts.map +1 -1
- package/build/src/modeling/RuntimeApiModel.js +6 -2
- package/build/src/modeling/RuntimeApiModel.js.map +1 -1
- package/build/src/modeling/generators/RuntimeModelGenerator.d.ts +15 -0
- package/build/src/modeling/generators/RuntimeModelGenerator.d.ts.map +1 -0
- package/build/src/modeling/generators/RuntimeModelGenerator.js +78 -0
- package/build/src/modeling/generators/RuntimeModelGenerator.js.map +1 -0
- package/build/src/modeling/helpers/endpointHelpers.d.ts +6 -1
- package/build/src/modeling/helpers/endpointHelpers.d.ts.map +1 -1
- package/build/src/modeling/helpers/endpointHelpers.js +43 -4
- package/build/src/modeling/helpers/endpointHelpers.js.map +1 -1
- package/build/src/modeling/validation/api_model_rules.d.ts.map +1 -1
- package/build/src/modeling/validation/api_model_rules.js +17 -0
- package/build/src/modeling/validation/api_model_rules.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/modeling/ApiModel.ts +9 -5
- package/src/modeling/ExposedEntity.ts +62 -4
- package/src/modeling/RuntimeApiModel.ts +7 -2
- package/src/modeling/generators/RuntimeModelGenerator.ts +79 -0
- package/src/modeling/helpers/endpointHelpers.ts +51 -4
- package/src/modeling/validation/api_model_rules.ts +19 -0
- package/tests/unit/modeling/RuntimeApiModel.spec.ts +17 -3
- package/tests/unit/modeling/api_model_expose_entity.spec.ts +2 -1
- package/tests/unit/modeling/exposed_entity.spec.ts +95 -0
- package/tests/unit/modeling/generators/OasGenerator.spec.ts +8 -8
- package/tests/unit/modeling/generators/RuntimeModelGenerator.spec.ts +192 -0
- package/tests/unit/modeling/helpers/endpointHelpers.spec.ts +10 -3
- package/tests/unit/modeling/validation/api_model_rules.spec.ts +35 -0
|
@@ -88,9 +88,13 @@ export class RuntimeApiModel extends ApiModel {
|
|
|
88
88
|
}
|
|
89
89
|
constructor(schema, domainSchema) {
|
|
90
90
|
super(schema, domainSchema);
|
|
91
|
-
if (schema.routingMap) {
|
|
92
|
-
|
|
91
|
+
if (!schema.routingMap) {
|
|
92
|
+
throw new Exception('The runtime API model must have a routing map.', {
|
|
93
|
+
code: 'E_MISSING_ROUTING_MAP',
|
|
94
|
+
help: 'Ensure that the routingMap property is defined when creating a RuntimeApiModel.',
|
|
95
|
+
});
|
|
93
96
|
}
|
|
97
|
+
this.#initializeRouter(schema.routingMap);
|
|
94
98
|
this.#cacheEntitiesAndProperties();
|
|
95
99
|
this.#precomputeAccessRules();
|
|
96
100
|
this.#precomputeSessionProperties();
|
|
@@ -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 type { DomainEntity } from './DomainEntity.js'\nimport type { DomainProperty } from './DomainProperty.js'\nimport type { DomainAssociation } from './DomainAssociation.js'\nimport { SemanticType } from './Semantics.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 /**\n * Cached properties that have at least one semantic, grouped by their entity.\n */\n readonly #semanticPropertiesCache = new Map<DomainEntity, DomainProperty[]>()\n\n /**\n * Cached properties that have at least one semantic, grouped by their entity and then by SemanticType.\n */\n readonly #propertiesBySemanticCache = new Map<DomainEntity, Map<SemanticType, DomainProperty[]>>()\n\n /**\n * Cached associations that have at least one semantic, grouped by their entity.\n */\n readonly #semanticAssociationsCache = new Map<DomainEntity, DomainAssociation[]>()\n\n /**\n * Cached associations that have at least one semantic, grouped by their entity and then by SemanticType.\n */\n readonly #associationsBySemanticCache = new Map<DomainEntity, Map<SemanticType, DomainAssociation[]>>()\n\n /**\n * Returns a readonly map of properties with semantics.\n * Note, it creates a copy of the cached map to prevent modification of the internal state.\n */\n public get semanticPropertiesCache(): ReadonlyMap<DomainEntity, DomainProperty[]> {\n return new Map(this.#semanticPropertiesCache)\n }\n\n /**\n * Returns properties for a specific entity that have a specific semantic type.\n * Provides O(1) lookup using the precomputed cache.\n */\n public getPropertiesBySemantic(entity: DomainEntity, semanticType: SemanticType): DomainProperty[] {\n const semanticMap = this.#propertiesBySemanticCache.get(entity)\n return semanticMap?.get(semanticType) || []\n }\n\n /**\n * Returns a readonly map of associations with semantics.\n * Note, it creates a copy of the cached map to prevent modification of the internal state.\n */\n public get semanticAssociationsCache(): ReadonlyMap<DomainEntity, DomainAssociation[]> {\n return new Map(this.#semanticAssociationsCache)\n }\n\n /**\n * Returns associations for a specific entity that have a specific semantic type.\n * Provides O(1) lookup using the precomputed cache.\n */\n public getAssociationsBySemantic(entity: DomainEntity, semanticType: SemanticType): DomainAssociation[] {\n const semanticMap = this.#associationsBySemanticCache.get(entity)\n return semanticMap?.get(semanticType) || []\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 this.#precomputeSemanticProperties()\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 #precomputeSemanticProperties(): void {\n if (!this.domain) {\n return\n }\n\n const processEntity = (entity: DomainEntity) => {\n if (this.#semanticPropertiesCache.has(entity)) {\n return\n }\n\n const propertiesWithSemantics: DomainProperty[] = []\n const semanticPropsMap = new Map<SemanticType, DomainProperty[]>()\n\n for (const prop of entity.withInheritedProperties()) {\n if (Array.isArray(prop.semantics) && prop.semantics.length > 0) {\n propertiesWithSemantics.push(prop)\n\n for (const semantic of prop.semantics) {\n let propsForSemantic = semanticPropsMap.get(semantic.id)\n if (!propsForSemantic) {\n propsForSemantic = []\n semanticPropsMap.set(semantic.id, propsForSemantic)\n }\n propsForSemantic.push(prop)\n }\n }\n }\n\n if (propertiesWithSemantics.length > 0) {\n this.#semanticPropertiesCache.set(entity, propertiesWithSemantics)\n this.#propertiesBySemanticCache.set(entity, semanticPropsMap)\n }\n\n const associationsWithSemantics: DomainAssociation[] = []\n const semanticAssocMap = new Map<SemanticType, DomainAssociation[]>()\n\n for (const assoc of entity.withInheritedAssociations()) {\n if (Array.isArray(assoc.semantics) && assoc.semantics.length > 0) {\n associationsWithSemantics.push(assoc)\n\n for (const semantic of assoc.semantics) {\n let assocForSemantic = semanticAssocMap.get(semantic.id)\n if (!assocForSemantic) {\n assocForSemantic = []\n semanticAssocMap.set(semantic.id, assocForSemantic)\n }\n assocForSemantic.push(assoc)\n }\n }\n }\n\n if (associationsWithSemantics.length > 0) {\n this.#semanticAssociationsCache.set(entity, associationsWithSemantics)\n this.#associationsBySemanticCache.set(entity, semanticAssocMap)\n }\n }\n\n for (const entity of this.domain.listEntities()) {\n processEntity(entity)\n }\n\n for (const entity of this.domain.listAllForeignEntities()) {\n processEntity(entity)\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.withInheritedProperties()) {\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"]}
|
|
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 type { DomainEntity } from './DomainEntity.js'\nimport type { DomainProperty } from './DomainProperty.js'\nimport type { DomainAssociation } from './DomainAssociation.js'\nimport { SemanticType } from './Semantics.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 /**\n * Cached properties that have at least one semantic, grouped by their entity.\n */\n readonly #semanticPropertiesCache = new Map<DomainEntity, DomainProperty[]>()\n\n /**\n * Cached properties that have at least one semantic, grouped by their entity and then by SemanticType.\n */\n readonly #propertiesBySemanticCache = new Map<DomainEntity, Map<SemanticType, DomainProperty[]>>()\n\n /**\n * Cached associations that have at least one semantic, grouped by their entity.\n */\n readonly #semanticAssociationsCache = new Map<DomainEntity, DomainAssociation[]>()\n\n /**\n * Cached associations that have at least one semantic, grouped by their entity and then by SemanticType.\n */\n readonly #associationsBySemanticCache = new Map<DomainEntity, Map<SemanticType, DomainAssociation[]>>()\n\n /**\n * Returns a readonly map of properties with semantics.\n * Note, it creates a copy of the cached map to prevent modification of the internal state.\n */\n public get semanticPropertiesCache(): ReadonlyMap<DomainEntity, DomainProperty[]> {\n return new Map(this.#semanticPropertiesCache)\n }\n\n /**\n * Returns properties for a specific entity that have a specific semantic type.\n * Provides O(1) lookup using the precomputed cache.\n */\n public getPropertiesBySemantic(entity: DomainEntity, semanticType: SemanticType): DomainProperty[] {\n const semanticMap = this.#propertiesBySemanticCache.get(entity)\n return semanticMap?.get(semanticType) || []\n }\n\n /**\n * Returns a readonly map of associations with semantics.\n * Note, it creates a copy of the cached map to prevent modification of the internal state.\n */\n public get semanticAssociationsCache(): ReadonlyMap<DomainEntity, DomainAssociation[]> {\n return new Map(this.#semanticAssociationsCache)\n }\n\n /**\n * Returns associations for a specific entity that have a specific semantic type.\n * Provides O(1) lookup using the precomputed cache.\n */\n public getAssociationsBySemantic(entity: DomainEntity, semanticType: SemanticType): DomainAssociation[] {\n const semanticMap = this.#associationsBySemanticCache.get(entity)\n return semanticMap?.get(semanticType) || []\n }\n\n constructor(schema: RuntimeApiModelSchema, domainSchema: DataDomainSchema) {\n super(schema, domainSchema)\n\n if (!schema.routingMap) {\n throw new Exception('The runtime API model must have a routing map.', {\n code: 'E_MISSING_ROUTING_MAP',\n help: 'Ensure that the routingMap property is defined when creating a RuntimeApiModel.',\n })\n }\n\n this.#initializeRouter(schema.routingMap)\n\n this.#cacheEntitiesAndProperties()\n this.#precomputeAccessRules()\n this.#precomputeSessionProperties()\n this.#precomputeSemanticProperties()\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 #precomputeSemanticProperties(): void {\n if (!this.domain) {\n return\n }\n\n const processEntity = (entity: DomainEntity) => {\n if (this.#semanticPropertiesCache.has(entity)) {\n return\n }\n\n const propertiesWithSemantics: DomainProperty[] = []\n const semanticPropsMap = new Map<SemanticType, DomainProperty[]>()\n\n for (const prop of entity.withInheritedProperties()) {\n if (Array.isArray(prop.semantics) && prop.semantics.length > 0) {\n propertiesWithSemantics.push(prop)\n\n for (const semantic of prop.semantics) {\n let propsForSemantic = semanticPropsMap.get(semantic.id)\n if (!propsForSemantic) {\n propsForSemantic = []\n semanticPropsMap.set(semantic.id, propsForSemantic)\n }\n propsForSemantic.push(prop)\n }\n }\n }\n\n if (propertiesWithSemantics.length > 0) {\n this.#semanticPropertiesCache.set(entity, propertiesWithSemantics)\n this.#propertiesBySemanticCache.set(entity, semanticPropsMap)\n }\n\n const associationsWithSemantics: DomainAssociation[] = []\n const semanticAssocMap = new Map<SemanticType, DomainAssociation[]>()\n\n for (const assoc of entity.withInheritedAssociations()) {\n if (Array.isArray(assoc.semantics) && assoc.semantics.length > 0) {\n associationsWithSemantics.push(assoc)\n\n for (const semantic of assoc.semantics) {\n let assocForSemantic = semanticAssocMap.get(semantic.id)\n if (!assocForSemantic) {\n assocForSemantic = []\n semanticAssocMap.set(semantic.id, assocForSemantic)\n }\n assocForSemantic.push(assoc)\n }\n }\n }\n\n if (associationsWithSemantics.length > 0) {\n this.#semanticAssociationsCache.set(entity, associationsWithSemantics)\n this.#associationsBySemanticCache.set(entity, semanticAssocMap)\n }\n }\n\n for (const entity of this.domain.listEntities()) {\n processEntity(entity)\n }\n\n for (const entity of this.domain.listAllForeignEntities()) {\n processEntity(entity)\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.withInheritedProperties()) {\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"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ApiModel, type ApiModelSchema } from '../ApiModel.js';
|
|
2
|
+
import { type RuntimeApiModelSchema } from '../RuntimeApiModel.js';
|
|
3
|
+
/**
|
|
4
|
+
* A class that takes the API model and generates a runtime-optimized API model.
|
|
5
|
+
*
|
|
6
|
+
* Note, the API model must be already validated before passing it to this generator.
|
|
7
|
+
* This class doesn't perform any validation, but it will throw an exception if the API model is invalid.
|
|
8
|
+
*/
|
|
9
|
+
export declare class RuntimeModelGenerator {
|
|
10
|
+
#private;
|
|
11
|
+
constructor(input: ApiModel | ApiModelSchema);
|
|
12
|
+
generate(): Promise<RuntimeApiModelSchema>;
|
|
13
|
+
private addRoute;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=RuntimeModelGenerator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RuntimeModelGenerator.d.ts","sourceRoot":"","sources":["../../../../src/modeling/generators/RuntimeModelGenerator.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC9D,OAAO,EAAwB,KAAK,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAExF;;;;;GAKG;AACH,qBAAa,qBAAqB;;gBAKpB,KAAK,EAAE,QAAQ,GAAG,cAAc;IAU/B,QAAQ,IAAI,OAAO,CAAC,qBAAqB,CAAC;IA2CvD,OAAO,CAAC,QAAQ;CAUjB"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { ApiModel } from '../ApiModel.js';
|
|
2
|
+
/**
|
|
3
|
+
* A class that takes the API model and generates a runtime-optimized API model.
|
|
4
|
+
*
|
|
5
|
+
* Note, the API model must be already validated before passing it to this generator.
|
|
6
|
+
* This class doesn't perform any validation, but it will throw an exception if the API model is invalid.
|
|
7
|
+
*/
|
|
8
|
+
export class RuntimeModelGenerator {
|
|
9
|
+
#apiModel;
|
|
10
|
+
#apiSchema;
|
|
11
|
+
#routingMap = {};
|
|
12
|
+
constructor(input) {
|
|
13
|
+
if (input instanceof ApiModel) {
|
|
14
|
+
this.#apiModel = input;
|
|
15
|
+
this.#apiSchema = input.toJSON();
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
this.#apiModel = new ApiModel(input);
|
|
19
|
+
this.#apiSchema = input;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async generate() {
|
|
23
|
+
if (!this.#apiModel) {
|
|
24
|
+
throw new Error('API model is not defined');
|
|
25
|
+
}
|
|
26
|
+
for (const expose of this.#apiModel.exposes.values()) {
|
|
27
|
+
const colPath = expose.getAbsoluteCollectionPath();
|
|
28
|
+
const resPath = expose.getAbsoluteResourcePath();
|
|
29
|
+
for (const action of expose.actions) {
|
|
30
|
+
switch (action.kind) {
|
|
31
|
+
case 'list':
|
|
32
|
+
if (colPath)
|
|
33
|
+
this.addRoute('GET', colPath, expose.key, action.kind);
|
|
34
|
+
break;
|
|
35
|
+
case 'create':
|
|
36
|
+
if (colPath)
|
|
37
|
+
this.addRoute('POST', colPath, expose.key, action.kind);
|
|
38
|
+
break;
|
|
39
|
+
case 'search':
|
|
40
|
+
if (colPath)
|
|
41
|
+
this.addRoute('POST', `${colPath}/search`, expose.key, action.kind);
|
|
42
|
+
break;
|
|
43
|
+
case 'read':
|
|
44
|
+
if (resPath)
|
|
45
|
+
this.addRoute('GET', resPath, expose.key, action.kind);
|
|
46
|
+
break;
|
|
47
|
+
case 'update':
|
|
48
|
+
if (resPath) {
|
|
49
|
+
const updateAction = action;
|
|
50
|
+
for (const method of updateAction.allowedMethods) {
|
|
51
|
+
this.addRoute(method, resPath, expose.key, action.kind);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
break;
|
|
55
|
+
case 'delete':
|
|
56
|
+
if (resPath)
|
|
57
|
+
this.addRoute('DELETE', resPath, expose.key, action.kind);
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
...this.#apiSchema,
|
|
64
|
+
routingMap: this.#routingMap,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
addRoute(method, path, exposedEntityKey, actionKind) {
|
|
68
|
+
const upperMethod = method.toUpperCase();
|
|
69
|
+
if (!this.#routingMap[upperMethod]) {
|
|
70
|
+
this.#routingMap[upperMethod] = [];
|
|
71
|
+
}
|
|
72
|
+
this.#routingMap[upperMethod].push({
|
|
73
|
+
path,
|
|
74
|
+
lookup: { exposedEntityKey, actionKind },
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=RuntimeModelGenerator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RuntimeModelGenerator.js","sourceRoot":"","sources":["../../../../src/modeling/generators/RuntimeModelGenerator.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ActionKind, type UpdateAction } from '../actions/index.js'\nimport { ApiModel, type ApiModelSchema } from '../ApiModel.js'\nimport { type RouteDefinition, type RuntimeApiModelSchema } from '../RuntimeApiModel.js'\n\n/**\n * A class that takes the API model and generates a runtime-optimized API model.\n *\n * Note, the API model must be already validated before passing it to this generator.\n * This class doesn't perform any validation, but it will throw an exception if the API model is invalid.\n */\nexport class RuntimeModelGenerator {\n #apiModel: ApiModel\n #apiSchema: ApiModelSchema\n #routingMap: Record<string, RouteDefinition[]> = {}\n\n constructor(input: ApiModel | ApiModelSchema) {\n if (input instanceof ApiModel) {\n this.#apiModel = input\n this.#apiSchema = input.toJSON()\n } else {\n this.#apiModel = new ApiModel(input)\n this.#apiSchema = input\n }\n }\n\n public async generate(): Promise<RuntimeApiModelSchema> {\n if (!this.#apiModel) {\n throw new Error('API model is not defined')\n }\n\n for (const expose of this.#apiModel.exposes.values()) {\n const colPath = expose.getAbsoluteCollectionPath()\n const resPath = expose.getAbsoluteResourcePath()\n for (const action of expose.actions) {\n switch (action.kind) {\n case 'list':\n if (colPath) this.addRoute('GET', colPath, expose.key, action.kind)\n break\n case 'create':\n if (colPath) this.addRoute('POST', colPath, expose.key, action.kind)\n break\n case 'search':\n if (colPath) this.addRoute('POST', `${colPath}/search`, expose.key, action.kind)\n break\n case 'read':\n if (resPath) this.addRoute('GET', resPath, expose.key, action.kind)\n break\n case 'update':\n if (resPath) {\n const updateAction = action as UpdateAction\n for (const method of updateAction.allowedMethods) {\n this.addRoute(method, resPath, expose.key, action.kind)\n }\n }\n break\n case 'delete':\n if (resPath) this.addRoute('DELETE', resPath, expose.key, action.kind)\n break\n }\n }\n }\n\n return {\n ...this.#apiSchema,\n routingMap: this.#routingMap,\n }\n }\n\n private addRoute(method: string, path: string, exposedEntityKey: string, actionKind: ActionKind): void {\n const upperMethod = method.toUpperCase()\n if (!this.#routingMap[upperMethod]) {\n this.#routingMap[upperMethod] = []\n }\n this.#routingMap[upperMethod].push({\n path,\n lookup: { exposedEntityKey, actionKind },\n })\n }\n}\n"]}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Generates a semantic parameter name for an entity.
|
|
3
|
+
* It converts snake_case, kebab-case, or PascalCase to camelCase and appends 'Id'.
|
|
4
|
+
* @param entityName The entity name (e.g. from entity.info.name)
|
|
5
|
+
*/
|
|
6
|
+
export declare function paramNameFor(entityName: string): string;
|
|
2
7
|
/**
|
|
3
8
|
* Ensures the path starts with a single leading slash and has no trailing slash (except root '/')
|
|
4
9
|
* @param path The path fragment to normalize
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"endpointHelpers.d.ts","sourceRoot":"","sources":["../../../../src/modeling/helpers/endpointHelpers.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"endpointHelpers.d.ts","sourceRoot":"","sources":["../../../../src/modeling/helpers/endpointHelpers.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CA4CvD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKvD;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAI7D"}
|
|
@@ -1,7 +1,46 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { Exception } from '../../exceptions/exception.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generates a semantic parameter name for an entity.
|
|
4
|
+
* It converts snake_case, kebab-case, or PascalCase to camelCase and appends 'Id'.
|
|
5
|
+
* @param entityName The entity name (e.g. from entity.info.name)
|
|
6
|
+
*/
|
|
7
|
+
export function paramNameFor(entityName) {
|
|
8
|
+
if (!entityName) {
|
|
9
|
+
throw new Exception('Cannot generate parameter name from an empty string.', {
|
|
10
|
+
code: 'E_INVALID_PARAM_NAME',
|
|
11
|
+
help: 'The entity name used for path parameter generation is empty.',
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
// Add a space between lowercase and uppercase letters to handle camelCase/PascalCase
|
|
15
|
+
let withSpaces = entityName.replace(/([a-z])([A-Z])/g, '$1 $2');
|
|
16
|
+
// Also split consecutive uppercase letters if followed by a lowercase (e.g. XMLHttp -> XML Http)
|
|
17
|
+
withSpaces = withSpaces.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2');
|
|
18
|
+
// Replace all non-alphanumeric characters with spaces
|
|
19
|
+
const cleanStr = withSpaces.replace(/[^a-zA-Z0-9]/g, ' ');
|
|
20
|
+
// Split by spaces and filter out empty strings
|
|
21
|
+
const words = cleanStr.split(/\s+/).filter(Boolean);
|
|
22
|
+
if (words.length === 0) {
|
|
23
|
+
throw new Exception(`Cannot generate a valid parameter name from "${entityName}".`, {
|
|
24
|
+
code: 'E_INVALID_PARAM_NAME',
|
|
25
|
+
help: 'The entity name must contain alphanumeric characters to generate a path parameter.',
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
// CamelCase: lower first word, Capitalize subsequent words
|
|
29
|
+
const camelCased = words
|
|
30
|
+
.map((word, index) => {
|
|
31
|
+
const lower = word.toLowerCase();
|
|
32
|
+
if (index === 0) {
|
|
33
|
+
return lower;
|
|
34
|
+
}
|
|
35
|
+
return lower.charAt(0).toUpperCase() + lower.slice(1);
|
|
36
|
+
})
|
|
37
|
+
.join('');
|
|
38
|
+
// Ensure it starts with a letter or underscore
|
|
39
|
+
let param = camelCased;
|
|
40
|
+
if (/^[0-9]/.test(param)) {
|
|
41
|
+
param = '_' + param;
|
|
42
|
+
}
|
|
43
|
+
return `${param}Id`;
|
|
5
44
|
}
|
|
6
45
|
/**
|
|
7
46
|
* Ensures the path starts with a single leading slash and has no trailing slash (except root '/')
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"endpointHelpers.js","sourceRoot":"","sources":["../../../../src/modeling/helpers/endpointHelpers.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"endpointHelpers.js","sourceRoot":"","sources":["../../../../src/modeling/helpers/endpointHelpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAA;AAEzD;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,UAAkB;IAC7C,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,SAAS,CAAC,sDAAsD,EAAE;YAC1E,IAAI,EAAE,sBAAsB;YAC5B,IAAI,EAAE,8DAA8D;SACrE,CAAC,CAAA;IACJ,CAAC;IAED,qFAAqF;IACrF,IAAI,UAAU,GAAG,UAAU,CAAC,OAAO,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAA;IAC/D,iGAAiG;IACjG,UAAU,GAAG,UAAU,CAAC,OAAO,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAA;IAEjE,sDAAsD;IACtD,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,eAAe,EAAE,GAAG,CAAC,CAAA;IAEzD,+CAA+C;IAC/C,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IAEnD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,SAAS,CAAC,gDAAgD,UAAU,IAAI,EAAE;YAClF,IAAI,EAAE,sBAAsB;YAC5B,IAAI,EAAE,oFAAoF;SAC3F,CAAC,CAAA;IACJ,CAAC;IAED,2DAA2D;IAC3D,MAAM,UAAU,GAAG,KAAK;SACrB,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QACnB,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAA;QAChC,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;YAChB,OAAO,KAAK,CAAA;QACd,CAAC;QACD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IACvD,CAAC,CAAC;SACD,IAAI,CAAC,EAAE,CAAC,CAAA;IAEX,+CAA+C;IAC/C,IAAI,KAAK,GAAG,UAAU,CAAA;IACtB,IAAI,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,KAAK,GAAG,GAAG,GAAG,KAAK,CAAA;IACrB,CAAC;IAED,OAAO,GAAG,KAAK,IAAI,CAAA;AACrB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;IAC3B,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,OAAO,EAAE,CAAA;IACnE,IAAI,SAAS,KAAK,GAAG;QAAE,OAAO,GAAG,CAAA;IACjC,OAAO,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;AACtC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,KAAa;IACnD,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IACvD,MAAM,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAA;IACrD,OAAO,GAAG,CAAC,GAAG,CAAC,EAAE,CAAA;AACnB,CAAC","sourcesContent":["import { Exception } from '../../exceptions/exception.js'\n\n/**\n * Generates a semantic parameter name for an entity.\n * It converts snake_case, kebab-case, or PascalCase to camelCase and appends 'Id'.\n * @param entityName The entity name (e.g. from entity.info.name)\n */\nexport function paramNameFor(entityName: string): string {\n if (!entityName) {\n throw new Exception('Cannot generate parameter name from an empty string.', {\n code: 'E_INVALID_PARAM_NAME',\n help: 'The entity name used for path parameter generation is empty.',\n })\n }\n\n // Add a space between lowercase and uppercase letters to handle camelCase/PascalCase\n let withSpaces = entityName.replace(/([a-z])([A-Z])/g, '$1 $2')\n // Also split consecutive uppercase letters if followed by a lowercase (e.g. XMLHttp -> XML Http)\n withSpaces = withSpaces.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')\n\n // Replace all non-alphanumeric characters with spaces\n const cleanStr = withSpaces.replace(/[^a-zA-Z0-9]/g, ' ')\n\n // Split by spaces and filter out empty strings\n const words = cleanStr.split(/\\s+/).filter(Boolean)\n\n if (words.length === 0) {\n throw new Exception(`Cannot generate a valid parameter name from \"${entityName}\".`, {\n code: 'E_INVALID_PARAM_NAME',\n help: 'The entity name must contain alphanumeric characters to generate a path parameter.',\n })\n }\n\n // CamelCase: lower first word, Capitalize subsequent words\n const camelCased = words\n .map((word, index) => {\n const lower = word.toLowerCase()\n if (index === 0) {\n return lower\n }\n return lower.charAt(0).toUpperCase() + lower.slice(1)\n })\n .join('')\n\n // Ensure it starts with a letter or underscore\n let param = camelCased\n if (/^[0-9]/.test(param)) {\n param = '_' + param\n }\n\n return `${param}Id`\n}\n\n/**\n * Ensures the path starts with a single leading slash and has no trailing slash (except root '/')\n * @param path The path fragment to normalize\n */\nexport function ensureLeadingSlash(path: string): string {\n const trimmed = path.trim()\n const withSlash = trimmed.startsWith('/') ? trimmed : `/${trimmed}`\n if (withSlash === '/') return '/'\n return withSlash.replace(/\\/+$/, '')\n}\n\n/**\n * Joins two absolute-like fragments ensuring single slashes between segments\n * @param left The left path fragment\n * @param right The right path fragment\n */\nexport function joinPaths(left: string, right: string): string {\n const l = left.endsWith('/') ? left.slice(0, -1) : left\n const r = right.startsWith('/') ? right : `/${right}`\n return `${l}${r}`\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api_model_rules.d.ts","sourceRoot":"","sources":["../../../../src/modeling/validation/api_model_rules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAKlD,OAAO,KAAK,EAAE,sBAAsB,EAA6B,MAAM,aAAa,CAAA;AAcpF;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,GAAG,sBAAsB,EAAE,CAiD9E;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,QAAQ,GAAG,sBAAsB,EAAE,CA+BpF;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,QAAQ,GAAG,sBAAsB,EAAE,CAyKlF;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,QAAQ,GAAG,sBAAsB,EAAE,CAuElF;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,QAAQ,GAAG,sBAAsB,EAAE,CA+C/E;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,GAAG,sBAAsB,EAAE,CAuDnH;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,GAAG,sBAAsB,EAAE,
|
|
1
|
+
{"version":3,"file":"api_model_rules.d.ts","sourceRoot":"","sources":["../../../../src/modeling/validation/api_model_rules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAKlD,OAAO,KAAK,EAAE,sBAAsB,EAA6B,MAAM,aAAa,CAAA;AAcpF;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,GAAG,sBAAsB,EAAE,CAiD9E;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,QAAQ,GAAG,sBAAsB,EAAE,CA+BpF;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,QAAQ,GAAG,sBAAsB,EAAE,CAyKlF;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,QAAQ,GAAG,sBAAsB,EAAE,CAuElF;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,QAAQ,GAAG,sBAAsB,EAAE,CA+C/E;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,GAAG,sBAAsB,EAAE,CAuDnH;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,GAAG,sBAAsB,EAAE,CAuP3G"}
|
|
@@ -548,6 +548,23 @@ export function validateExposedEntity(exposure, apiModel) {
|
|
|
548
548
|
}
|
|
549
549
|
}
|
|
550
550
|
}
|
|
551
|
+
// Branch Path Parameter Collision
|
|
552
|
+
const absoluteResourcePath = exposure.getAbsoluteResourcePath();
|
|
553
|
+
if (absoluteResourcePath) {
|
|
554
|
+
const params = [...absoluteResourcePath.matchAll(/\{([^}]+)\}/g)].map((m) => m[1]);
|
|
555
|
+
const uniqueParams = new Set(params);
|
|
556
|
+
if (uniqueParams.size !== params.length) {
|
|
557
|
+
const duplicates = params.filter((item, index) => params.indexOf(item) !== index);
|
|
558
|
+
const duplicateParam = duplicates[0];
|
|
559
|
+
issues.push({
|
|
560
|
+
code: createCode('EXPOSURE', 'DUPLICATE_PATH_PARAMETER'),
|
|
561
|
+
message: `[${entityName}]: The path parameter "{${duplicateParam}}" is duplicated in the resource path hierarchy.`,
|
|
562
|
+
suggestion: 'Change the parameter name in either this resource or its ancestor to ensure unique parameter names.',
|
|
563
|
+
severity: 'error',
|
|
564
|
+
context: { ...context, property: 'resourcePath' },
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
}
|
|
551
568
|
// Minimum Actions
|
|
552
569
|
if (!exposure.actions || exposure.actions.length === 0) {
|
|
553
570
|
issues.push({
|