@toride/codegen
Advanced tools
| // src/generator.ts | ||
| var SAFE_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/; | ||
| function escapeStringLiteral(s) { | ||
| return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"'); | ||
| } | ||
| function assertSafeIdentifier(s, context) { | ||
| if (!SAFE_IDENTIFIER.test(s)) { | ||
| throw new Error( | ||
| `Unsafe identifier in ${context}: "${escapeStringLiteral(s)}". Identifiers must match ${SAFE_IDENTIFIER.source}` | ||
| ); | ||
| } | ||
| } | ||
| function generateAttributeType(schema, context) { | ||
| if (typeof schema === "string") { | ||
| const primitiveMap = { | ||
| string: "string", | ||
| number: "number", | ||
| boolean: "boolean" | ||
| }; | ||
| const mapped = primitiveMap[schema]; | ||
| if (!mapped) { | ||
| throw new Error( | ||
| `Unknown attribute type "${escapeStringLiteral(schema)}" in ${context}. Supported types: ${Object.keys(primitiveMap).join(", ")}` | ||
| ); | ||
| } | ||
| return mapped; | ||
| } | ||
| if (schema.kind === "primitive") { | ||
| const primitiveMap = { | ||
| string: "string", | ||
| number: "number", | ||
| boolean: "boolean" | ||
| }; | ||
| const mapped = primitiveMap[schema.type]; | ||
| if (!mapped) { | ||
| throw new Error( | ||
| `Unknown attribute type "${escapeStringLiteral(schema.type)}" in ${context}. Supported types: ${Object.keys(primitiveMap).join(", ")}` | ||
| ); | ||
| } | ||
| return mapped; | ||
| } | ||
| if (schema.kind === "object") { | ||
| const fieldEntries = Object.entries(schema.fields); | ||
| if (fieldEntries.length === 0) { | ||
| return "{ }"; | ||
| } | ||
| const fields = fieldEntries.map(([fieldName, fieldSchema]) => { | ||
| assertSafeIdentifier(fieldName, `field "${fieldName}" in ${context}`); | ||
| return `${fieldName}: ${generateAttributeType(fieldSchema, `field "${fieldName}" in ${context}`)}`; | ||
| }).join("; "); | ||
| return `{ ${fields}; }`; | ||
| } | ||
| if (schema.kind === "array") { | ||
| const itemType = generateAttributeType(schema.items, `array items in ${context}`); | ||
| if (itemType === "string" || itemType === "number" || itemType === "boolean") { | ||
| return `${itemType}[]`; | ||
| } | ||
| return `Array<${itemType}>`; | ||
| } | ||
| throw new Error( | ||
| `Unknown attribute schema kind in ${context}. Expected "primitive", "object", or "array".` | ||
| ); | ||
| } | ||
| function generateAttributeFields(attrs, entityType, entityName) { | ||
| const fields = Object.entries(attrs).map(([k, v]) => `${k}: ${generateAttributeType(v, `attribute "${k}" in ${entityType} "${entityName}"`)}`).join("; "); | ||
| return `{ ${fields}; }`; | ||
| } | ||
| function generateTypes(policy) { | ||
| const resourceEntries = Object.entries(policy.resources); | ||
| const actorEntries = Object.entries(policy.actors); | ||
| const resourceNames = resourceEntries.map(([name]) => name); | ||
| const actorNames = actorEntries.map(([name]) => name); | ||
| const lines = []; | ||
| lines.push("// Auto-generated by @toride/codegen \u2014 do not edit"); | ||
| lines.push(""); | ||
| lines.push('import type { TorideSchema } from "toride";'); | ||
| lines.push(""); | ||
| for (const name of resourceNames) { | ||
| assertSafeIdentifier(name, "resource name"); | ||
| } | ||
| for (const [rName, block] of resourceEntries) { | ||
| for (const role of block.roles) { | ||
| assertSafeIdentifier(role, `role in resource "${rName}"`); | ||
| } | ||
| for (const perm of block.permissions) { | ||
| assertSafeIdentifier(perm, `permission in resource "${rName}"`); | ||
| } | ||
| if (block.relations) { | ||
| for (const [relName, relDef] of Object.entries(block.relations)) { | ||
| assertSafeIdentifier(relName, `relation name in resource "${rName}"`); | ||
| assertSafeIdentifier(relDef, `relation target in resource "${rName}"`); | ||
| } | ||
| } | ||
| if (block.attributes) { | ||
| for (const [attrName, attrSchema] of Object.entries(block.attributes)) { | ||
| assertSafeIdentifier(attrName, `attribute name in resource "${rName}"`); | ||
| generateAttributeType(attrSchema, `attribute "${attrName}" in resource "${rName}"`); | ||
| } | ||
| } | ||
| } | ||
| for (const [actorName, actorDecl] of actorEntries) { | ||
| assertSafeIdentifier(actorName, "actor type name"); | ||
| for (const [attrName, attrSchema] of Object.entries(actorDecl.attributes)) { | ||
| assertSafeIdentifier(attrName, `attribute name in actor "${actorName}"`); | ||
| generateAttributeType(attrSchema, `attribute "${attrName}" in actor "${actorName}"`); | ||
| } | ||
| } | ||
| const allActions = /* @__PURE__ */ new Set(); | ||
| for (const [, block] of resourceEntries) { | ||
| for (const perm of block.permissions) { | ||
| allActions.add(perm); | ||
| } | ||
| } | ||
| lines.push(`/** All action strings declared across all resources */`); | ||
| if (allActions.size === 0) { | ||
| lines.push("export type Actions = never;"); | ||
| } else { | ||
| const actionUnion = [...allActions].map((a) => `"${a}"`).join(" | "); | ||
| lines.push(`export type Actions = ${actionUnion};`); | ||
| } | ||
| lines.push(""); | ||
| lines.push(`/** All resource type names */`); | ||
| if (resourceNames.length === 0) { | ||
| lines.push("export type Resources = never;"); | ||
| } else { | ||
| const resourceUnion = resourceNames.map((r) => `"${r}"`).join(" | "); | ||
| lines.push(`export type Resources = ${resourceUnion};`); | ||
| } | ||
| lines.push(""); | ||
| lines.push(`/** All actor type names */`); | ||
| if (actorNames.length === 0) { | ||
| lines.push("export type ActorTypes = never;"); | ||
| } else { | ||
| const actorUnion = actorNames.map((a) => `"${a}"`).join(" | "); | ||
| lines.push(`export type ActorTypes = ${actorUnion};`); | ||
| } | ||
| lines.push(""); | ||
| lines.push(`/** Per-resource role types */`); | ||
| lines.push(`export type RoleMap = {`); | ||
| for (const [name, block] of resourceEntries) { | ||
| if (block.roles.length > 0) { | ||
| const roleUnion = block.roles.map((r) => `"${r}"`).join(" | "); | ||
| lines.push(` ${name}: ${roleUnion};`); | ||
| } else { | ||
| lines.push(` ${name}: never;`); | ||
| } | ||
| } | ||
| lines.push(`}`); | ||
| lines.push(""); | ||
| lines.push(`/** Per-resource permission types */`); | ||
| lines.push(`export type PermissionMap = {`); | ||
| for (const [name, block] of resourceEntries) { | ||
| if (block.permissions.length > 0) { | ||
| const permUnion = block.permissions.map((p) => `"${p}"`).join(" | "); | ||
| lines.push(` ${name}: ${permUnion};`); | ||
| } else { | ||
| lines.push(` ${name}: never;`); | ||
| } | ||
| } | ||
| lines.push(`}`); | ||
| lines.push(""); | ||
| lines.push(`/** Per-resource attribute types */`); | ||
| lines.push(`export type ResourceAttributeMap = {`); | ||
| for (const [name, block] of resourceEntries) { | ||
| if (block.attributes && Object.keys(block.attributes).length > 0) { | ||
| lines.push(` ${name}: ${generateAttributeFields(block.attributes, "resource", name)};`); | ||
| } else { | ||
| lines.push(` ${name}: Record<string, unknown>;`); | ||
| } | ||
| } | ||
| lines.push(`}`); | ||
| lines.push(""); | ||
| lines.push(`/** Per-actor attribute types */`); | ||
| lines.push(`export type ActorAttributeMap = {`); | ||
| for (const [name, actorDecl] of actorEntries) { | ||
| const attrKeys = Object.keys(actorDecl.attributes); | ||
| if (attrKeys.length > 0) { | ||
| lines.push(` ${name}: ${generateAttributeFields(actorDecl.attributes, "actor", name)};`); | ||
| } else { | ||
| lines.push(` ${name}: Record<string, unknown>;`); | ||
| } | ||
| } | ||
| lines.push(`}`); | ||
| lines.push(""); | ||
| lines.push(`/** Relation map \u2014 resource type -> relation name -> target resource type */`); | ||
| lines.push(`export type RelationMap = {`); | ||
| for (const [name, block] of resourceEntries) { | ||
| const relations = block.relations ?? {}; | ||
| const relEntries = Object.entries(relations); | ||
| if (relEntries.length === 0) { | ||
| lines.push(` ${name}: Record<string, never>;`); | ||
| } else { | ||
| lines.push(` ${name}: {`); | ||
| for (const [relName, relDef] of relEntries) { | ||
| lines.push(` ${relName}: "${relDef}";`); | ||
| } | ||
| lines.push(` };`); | ||
| } | ||
| } | ||
| lines.push(`}`); | ||
| lines.push(""); | ||
| lines.push(`/** Per-type resolver map \u2014 typed return values match resource attributes */`); | ||
| lines.push(`export type ResolverMap = {`); | ||
| lines.push(` [R in Resources]?: (`); | ||
| lines.push(` ref: { type: R; id: string; attributes?: ResourceAttributeMap[R] },`); | ||
| lines.push(` ) => Promise<ResourceAttributeMap[R]>;`); | ||
| lines.push(`};`); | ||
| lines.push(""); | ||
| lines.push(`/**`); | ||
| lines.push(` * Unified schema interface \u2014 pass this as Toride<GeneratedSchema>.`); | ||
| lines.push(` * Aggregates all type maps into the TorideSchema shape.`); | ||
| lines.push(` */`); | ||
| lines.push(`export interface GeneratedSchema extends TorideSchema {`); | ||
| lines.push(` resources: Resources;`); | ||
| lines.push(` actions: Actions;`); | ||
| lines.push(` actorTypes: ActorTypes;`); | ||
| lines.push(` permissionMap: PermissionMap;`); | ||
| lines.push(` roleMap: RoleMap;`); | ||
| lines.push(` resourceAttributeMap: ResourceAttributeMap;`); | ||
| lines.push(` actorAttributeMap: ActorAttributeMap;`); | ||
| lines.push(` relationMap: RelationMap;`); | ||
| lines.push(`}`); | ||
| lines.push(""); | ||
| return lines.join("\n"); | ||
| } | ||
| export { | ||
| generateTypes | ||
| }; |
+1
-1
| #!/usr/bin/env node | ||
| import { | ||
| generateTypes | ||
| } from "./chunk-XMLNKEBL.js"; | ||
| } from "./chunk-DSUMAUM5.js"; | ||
@@ -6,0 +6,0 @@ // src/cli.ts |
+1
-1
| import { | ||
| generateTypes | ||
| } from "./chunk-XMLNKEBL.js"; | ||
| } from "./chunk-DSUMAUM5.js"; | ||
@@ -5,0 +5,0 @@ // src/index.ts |
+2
-2
| { | ||
| "name": "@toride/codegen", | ||
| "version": "0.3.0", | ||
| "version": "0.4.0", | ||
| "description": "Code generation tools for Toride authorization engine", | ||
@@ -26,3 +26,3 @@ "type": "module", | ||
| "dependencies": { | ||
| "toride": "0.3.0" | ||
| "toride": "0.4.0" | ||
| }, | ||
@@ -29,0 +29,0 @@ "publishConfig": { |
| // src/generator.ts | ||
| var SAFE_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/; | ||
| var ATTRIBUTE_TYPE_MAP = { | ||
| string: "string", | ||
| number: "number", | ||
| boolean: "boolean" | ||
| }; | ||
| function escapeStringLiteral(s) { | ||
| return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"'); | ||
| } | ||
| function assertSafeIdentifier(s, context) { | ||
| if (!SAFE_IDENTIFIER.test(s)) { | ||
| throw new Error( | ||
| `Unsafe identifier in ${context}: "${escapeStringLiteral(s)}". Identifiers must match ${SAFE_IDENTIFIER.source}` | ||
| ); | ||
| } | ||
| } | ||
| function mapAttributeType(attrType, context) { | ||
| const mapped = ATTRIBUTE_TYPE_MAP[attrType]; | ||
| if (!mapped) { | ||
| throw new Error( | ||
| `Unknown attribute type "${escapeStringLiteral(attrType)}" in ${context}. Supported types: ${Object.keys(ATTRIBUTE_TYPE_MAP).join(", ")}` | ||
| ); | ||
| } | ||
| return mapped; | ||
| } | ||
| function generateAttributeFields(attrs, entityType, entityName) { | ||
| const fields = Object.entries(attrs).map(([k, v]) => `${k}: ${mapAttributeType(v, `attribute "${k}" in ${entityType} "${entityName}"`)}`).join("; "); | ||
| return `{ ${fields}; }`; | ||
| } | ||
| function generateTypes(policy) { | ||
| const resourceEntries = Object.entries(policy.resources); | ||
| const actorEntries = Object.entries(policy.actors); | ||
| const resourceNames = resourceEntries.map(([name]) => name); | ||
| const actorNames = actorEntries.map(([name]) => name); | ||
| const lines = []; | ||
| lines.push("// Auto-generated by @toride/codegen \u2014 do not edit"); | ||
| lines.push(""); | ||
| lines.push('import type { TorideSchema } from "toride";'); | ||
| lines.push(""); | ||
| for (const name of resourceNames) { | ||
| assertSafeIdentifier(name, "resource name"); | ||
| } | ||
| for (const [rName, block] of resourceEntries) { | ||
| for (const role of block.roles) { | ||
| assertSafeIdentifier(role, `role in resource "${rName}"`); | ||
| } | ||
| for (const perm of block.permissions) { | ||
| assertSafeIdentifier(perm, `permission in resource "${rName}"`); | ||
| } | ||
| if (block.relations) { | ||
| for (const [relName, relDef] of Object.entries(block.relations)) { | ||
| assertSafeIdentifier(relName, `relation name in resource "${rName}"`); | ||
| assertSafeIdentifier(relDef, `relation target in resource "${rName}"`); | ||
| } | ||
| } | ||
| if (block.attributes) { | ||
| for (const [attrName, attrType] of Object.entries(block.attributes)) { | ||
| assertSafeIdentifier(attrName, `attribute name in resource "${rName}"`); | ||
| mapAttributeType(attrType, `attribute "${attrName}" in resource "${rName}"`); | ||
| } | ||
| } | ||
| } | ||
| for (const [actorName, actorDecl] of actorEntries) { | ||
| assertSafeIdentifier(actorName, "actor type name"); | ||
| for (const [attrName, attrType] of Object.entries(actorDecl.attributes)) { | ||
| assertSafeIdentifier(attrName, `attribute name in actor "${actorName}"`); | ||
| mapAttributeType(attrType, `attribute "${attrName}" in actor "${actorName}"`); | ||
| } | ||
| } | ||
| const allActions = /* @__PURE__ */ new Set(); | ||
| for (const [, block] of resourceEntries) { | ||
| for (const perm of block.permissions) { | ||
| allActions.add(perm); | ||
| } | ||
| } | ||
| lines.push(`/** All action strings declared across all resources */`); | ||
| if (allActions.size === 0) { | ||
| lines.push("export type Actions = never;"); | ||
| } else { | ||
| const actionUnion = [...allActions].map((a) => `"${a}"`).join(" | "); | ||
| lines.push(`export type Actions = ${actionUnion};`); | ||
| } | ||
| lines.push(""); | ||
| lines.push(`/** All resource type names */`); | ||
| if (resourceNames.length === 0) { | ||
| lines.push("export type Resources = never;"); | ||
| } else { | ||
| const resourceUnion = resourceNames.map((r) => `"${r}"`).join(" | "); | ||
| lines.push(`export type Resources = ${resourceUnion};`); | ||
| } | ||
| lines.push(""); | ||
| lines.push(`/** All actor type names */`); | ||
| if (actorNames.length === 0) { | ||
| lines.push("export type ActorTypes = never;"); | ||
| } else { | ||
| const actorUnion = actorNames.map((a) => `"${a}"`).join(" | "); | ||
| lines.push(`export type ActorTypes = ${actorUnion};`); | ||
| } | ||
| lines.push(""); | ||
| lines.push(`/** Per-resource role types */`); | ||
| lines.push(`export interface RoleMap {`); | ||
| for (const [name, block] of resourceEntries) { | ||
| if (block.roles.length > 0) { | ||
| const roleUnion = block.roles.map((r) => `"${r}"`).join(" | "); | ||
| lines.push(` ${name}: ${roleUnion};`); | ||
| } else { | ||
| lines.push(` ${name}: never;`); | ||
| } | ||
| } | ||
| lines.push(`}`); | ||
| lines.push(""); | ||
| lines.push(`/** Per-resource permission types */`); | ||
| lines.push(`export interface PermissionMap {`); | ||
| for (const [name, block] of resourceEntries) { | ||
| if (block.permissions.length > 0) { | ||
| const permUnion = block.permissions.map((p) => `"${p}"`).join(" | "); | ||
| lines.push(` ${name}: ${permUnion};`); | ||
| } else { | ||
| lines.push(` ${name}: never;`); | ||
| } | ||
| } | ||
| lines.push(`}`); | ||
| lines.push(""); | ||
| lines.push(`/** Per-resource attribute types */`); | ||
| lines.push(`export interface ResourceAttributeMap {`); | ||
| for (const [name, block] of resourceEntries) { | ||
| if (block.attributes && Object.keys(block.attributes).length > 0) { | ||
| lines.push(` ${name}: ${generateAttributeFields(block.attributes, "resource", name)};`); | ||
| } else { | ||
| lines.push(` ${name}: Record<string, unknown>;`); | ||
| } | ||
| } | ||
| lines.push(`}`); | ||
| lines.push(""); | ||
| lines.push(`/** Per-actor attribute types */`); | ||
| lines.push(`export interface ActorAttributeMap {`); | ||
| for (const [name, actorDecl] of actorEntries) { | ||
| const attrKeys = Object.keys(actorDecl.attributes); | ||
| if (attrKeys.length > 0) { | ||
| lines.push(` ${name}: ${generateAttributeFields(actorDecl.attributes, "actor", name)};`); | ||
| } else { | ||
| lines.push(` ${name}: Record<string, unknown>;`); | ||
| } | ||
| } | ||
| lines.push(`}`); | ||
| lines.push(""); | ||
| lines.push(`/** Relation map \u2014 resource type -> relation name -> target resource type */`); | ||
| lines.push(`export interface RelationMap {`); | ||
| for (const [name, block] of resourceEntries) { | ||
| const relations = block.relations ?? {}; | ||
| const relEntries = Object.entries(relations); | ||
| if (relEntries.length === 0) { | ||
| lines.push(` ${name}: Record<string, never>;`); | ||
| } else { | ||
| lines.push(` ${name}: {`); | ||
| for (const [relName, relDef] of relEntries) { | ||
| lines.push(` ${relName}: "${relDef}";`); | ||
| } | ||
| lines.push(` };`); | ||
| } | ||
| } | ||
| lines.push(`}`); | ||
| lines.push(""); | ||
| lines.push(`/** Per-type resolver map \u2014 typed return values match resource attributes */`); | ||
| lines.push(`export type ResolverMap = {`); | ||
| lines.push(` [R in Resources]?: (`); | ||
| lines.push(` ref: { type: R; id: string; attributes?: ResourceAttributeMap[R] },`); | ||
| lines.push(` ) => Promise<ResourceAttributeMap[R]>;`); | ||
| lines.push(`};`); | ||
| lines.push(""); | ||
| lines.push(`/**`); | ||
| lines.push(` * Unified schema interface \u2014 pass this as Toride<GeneratedSchema>.`); | ||
| lines.push(` * Aggregates all type maps into the TorideSchema shape.`); | ||
| lines.push(` */`); | ||
| lines.push(`export interface GeneratedSchema extends TorideSchema {`); | ||
| lines.push(` resources: Resources;`); | ||
| lines.push(` actions: Actions;`); | ||
| lines.push(` actorTypes: ActorTypes;`); | ||
| lines.push(` permissionMap: PermissionMap;`); | ||
| lines.push(` roleMap: RoleMap;`); | ||
| lines.push(` resourceAttributeMap: ResourceAttributeMap;`); | ||
| lines.push(` actorAttributeMap: ActorAttributeMap;`); | ||
| lines.push(` relationMap: RelationMap;`); | ||
| lines.push(`}`); | ||
| lines.push(""); | ||
| return lines.join("\n"); | ||
| } | ||
| export { | ||
| generateTypes | ||
| }; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
12137
12.31%322
12.98%1
Infinity%+ Added
- Removed
Updated