@abapify/speci 0.4.30 → 0.4.33

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index-CTc5gKBG.d.mts","names":[],"sources":["../src/rest/server/types.ts","../src/rest/server/create-server.ts"],"mappings":";;;;UAWiB;EACf;EACA;EACA,SAAS;EACT;EACA,OAAO;;;;;UAMQ;EACf;EACA,SAAS;EACT;;;;;;;;UASe;;EAEf;;EAGA;;EAGA;;EAGA;;EAGA,aAAa;;EAGb,iBAAiB,eAAe;;EAGhC,iBAAiB;;EAGjB,kBAAkB;;;;;;;KAQR,iBACV,SAAS,eACT,SAAS,iBACN,QAAQ,kBAAkB;;;;UAKd;;EAEf,OAAO;;EAGP,QAAQ;;EAGR,eAAe,gBAAgB;;;;;UAMhB;;;;;;EAMf;;;;;UAMe;;EAEf,QAAQ;;;;;EAMR,MACE,gBACA,aACA;IACG,OAAO;IAAiB,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;iBCwDvB,aAAa,UAAU,qBACrC,UAAU,IACT"}
1
+ {"version":3,"file":"index-CTc5gKBG.d.mts","names":[],"sources":["../src/rest/server/types.ts","../src/rest/server/create-server.ts"],"mappings":";;;;UAWiB;EACf;EACA;EACA,SAAS;EACT;EACA,OAAO;;;;;UAMQ;EACf;EACA,SAAS;EACT;;;;;;;;UASe;;EAEf;;EAGA;;EAGA;;EAGA;;EAGA,aAAa;;EAGb,iBAAiB,eAAe;;EAGhC,iBAAiB;;EAGjB,kBAAkB;;;;;;;KAQR,iBACV,SAAS,eACT,SAAS,iBACN,QAAQ,kBAAkB;;;;UAKd;;EAEf,OAAO;;EAGP,QAAQ;;EAGR,eAAe,gBAAgB;;;;;UAMhB;;;;;;EAMf;;;;;UAMe;;EAEf,QAAQ;;;;;EAMR,MACE,gBACA,aACA;IACG,OAAO;IAAiB,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;iBC0HvB,aAAa,UAAU,qBACrC,UAAU,IACT"}
@@ -1,4 +1,4 @@
1
- import { t as createServer } from "../server-D931GRUe.mjs";
1
+ import { t as createServer } from "../server-WoAHqGtU.mjs";
2
2
 
3
3
  // src/rest/types.ts
4
4
  /**
@@ -1,3 +1,3 @@
1
- import { t as createServer } from "../../server-D931GRUe.mjs";
1
+ import { t as createServer } from "../../server-WoAHqGtU.mjs";
2
2
 
3
3
  export { createServer };
@@ -63,6 +63,43 @@ function buildPathInfo(resolvedPath) {
63
63
  paramNames
64
64
  };
65
65
  }
66
+ function routeFromEndpoint({ descriptor }) {
67
+ const resolvedPath = descriptor.path || "";
68
+ const { regex, template, paramNames } = buildPathInfo(resolvedPath);
69
+ const bodySchema = descriptor.body && isSerializable(descriptor.body) ? descriptor.body : void 0;
70
+ const responseSchemas = {};
71
+ if (descriptor.responses) {
72
+ for (const [status, schema] of Object.entries(descriptor.responses)) if (isSerializable(schema)) responseSchemas[Number(status)] = schema;
73
+ }
74
+ return {
75
+ method: descriptor.method,
76
+ path: resolvedPath,
77
+ pathTemplate: template,
78
+ pathParamNames: paramNames,
79
+ bodySchema,
80
+ responseSchemas,
81
+ requestHeaders: descriptor.headers,
82
+ _regex: regex
83
+ };
84
+ }
85
+ function matchRoute(routes, method, url, basePath) {
86
+ const pathname = url.split("?")[0];
87
+ const relativePath = basePath && pathname.startsWith(basePath) ? pathname.slice(basePath.length) || "/" : pathname;
88
+ const targetMethod = method.toUpperCase();
89
+ for (const route of routes) {
90
+ if (route.method !== targetMethod) continue;
91
+ const match = relativePath.match(route._regex);
92
+ if (match) {
93
+ const params = {};
94
+ for (let i = 0; i < route.pathParamNames.length; i++) params[route.pathParamNames[i]] = match[i + 1] || "";
95
+ return {
96
+ route,
97
+ params
98
+ };
99
+ }
100
+ }
101
+ return null;
102
+ }
66
103
  /**
67
104
  * Create server-ready route definitions from a REST contract.
68
105
  *
@@ -87,49 +124,12 @@ function buildPathInfo(resolvedPath) {
87
124
  * ```
88
125
  */
89
126
  function createServer(contract) {
90
- const routes = walkContract(contract, []).map(({ operation, descriptor }) => {
91
- const resolvedPath = descriptor.path || "";
92
- const { regex, template, paramNames } = buildPathInfo(resolvedPath);
93
- const bodySchema = descriptor.body && isSerializable(descriptor.body) ? descriptor.body : void 0;
94
- const responseSchemas = {};
95
- if (descriptor.responses) {
96
- for (const [status, schema] of Object.entries(descriptor.responses)) if (isSerializable(schema)) responseSchemas[Number(status)] = schema;
97
- }
98
- return {
99
- method: descriptor.method,
100
- path: resolvedPath,
101
- pathTemplate: template,
102
- pathParamNames: paramNames,
103
- bodySchema,
104
- responseSchemas,
105
- requestHeaders: descriptor.headers,
106
- _regex: regex
107
- };
108
- });
127
+ const routes = walkContract(contract, []).map(routeFromEndpoint);
109
128
  return {
110
129
  routes,
111
- match(method, url, basePath = "") {
112
- const pathname = url.split("?")[0];
113
- let relativePath = pathname;
114
- if (basePath && pathname.startsWith(basePath)) relativePath = pathname.slice(basePath.length) || "/";
115
- const targetMethod = method.toUpperCase();
116
- for (const route of routes) {
117
- if (route.method !== targetMethod) continue;
118
- const routeWithRegex = route;
119
- const match = relativePath.match(routeWithRegex._regex);
120
- if (match) {
121
- const params = {};
122
- for (let i = 0; i < route.pathParamNames.length; i++) params[route.pathParamNames[i]] = match[i + 1] || "";
123
- return {
124
- route,
125
- params
126
- };
127
- }
128
- }
129
- return null;
130
- }
130
+ match: (method, url, basePath = "") => matchRoute(routes, method, url, basePath)
131
131
  };
132
132
  }
133
133
 
134
134
  export { createServer as t };
135
- //# sourceMappingURL=server-D931GRUe.mjs.map
135
+ //# sourceMappingURL=server-WoAHqGtU.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"server-D931GRUe.mjs","names":[],"sources":["../src/rest/server/create-server.ts"],"sourcesContent":["/**\n * Speci REST - Server Generator\n *\n * Creates server-ready route definitions from REST contracts.\n *\n * This is the server-side counterpart of createClient. It walks a contract\n * tree, extracts endpoint definitions (method, path, body schema, response\n * schemas), and returns structured route definitions that can be used to\n * build HTTP servers with any framework (node:http, Express, Hono, etc.).\n *\n * @example\n * ```typescript\n * import { createServer } from '@abapify/speci/rest';\n * import { adtContract } from '@abapify/adt-contracts';\n *\n * const server = createServer(adtContract);\n *\n * // Use with node:http\n * import { createServer as httpCreate } from 'node:http';\n *\n * httpCreate((req, res) => {\n * const match = server.match(req.method!, req.url!);\n * if (match) {\n * // Forward to downstream SAP, handle JSON↔XML conversion, etc.\n * }\n * });\n * ```\n */\n\nimport type { OperationFunction } from '../../core/types';\nimport type { RestEndpointDescriptor, Serializable } from '../types';\nimport type { RouteDefinition, ServerRoutes } from './types';\n\n/** Sentinel value used to mark parameter positions in resolved paths */\nconst PARAM_MARKER = '\\x00';\n\n/**\n * Check if a value is a RestEndpointDescriptor (has method and path)\n */\nfunction isEndpointDescriptor(value: unknown): value is RestEndpointDescriptor {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'method' in value &&\n 'path' in value &&\n 'responses' in value\n );\n}\n\n/**\n * Check if a schema is Serializable (has parse method)\n */\nfunction isSerializable(schema: unknown): schema is Serializable {\n return (\n typeof schema === 'object' &&\n schema !== null &&\n typeof (schema as any).parse === 'function'\n );\n}\n\n/**\n * Walk a contract tree and extract all endpoint definitions.\n *\n * Calls each operation function with a sentinel value to get the resolved\n * path, then replaces the sentinel with regex capture groups for matching.\n */\nfunction tryExtractEndpoint(\n obj: OperationFunction,\n): { operation: OperationFunction; descriptor: RestEndpointDescriptor } | null {\n try {\n const paramCount = obj.length || 1;\n const sentinelArgs = Array.from({ length: paramCount }, () => PARAM_MARKER);\n const descriptor = obj(...sentinelArgs);\n if (isEndpointDescriptor(descriptor)) {\n return { operation: obj, descriptor };\n }\n } catch {\n // Some functions may need different args - skip\n }\n return null;\n}\n\nfunction walkContract(\n obj: any,\n parentPath: string[],\n): Array<{ operation: OperationFunction; descriptor: RestEndpointDescriptor }> {\n if (typeof obj === 'function') {\n const result = tryExtractEndpoint(obj as OperationFunction);\n return result ? [result] : [];\n }\n\n if (typeof obj === 'object' && obj !== null) {\n const endpoints: Array<{\n operation: OperationFunction;\n descriptor: RestEndpointDescriptor;\n }> = [];\n for (const [key, value] of Object.entries(obj)) {\n endpoints.push(...walkContract(value, [...parentPath, key]));\n }\n return endpoints;\n }\n\n return [];\n}\n\n/**\n * Convert a resolved path with sentinel markers into a regex pattern.\n *\n * Also extracts path parameter names from the original function's parameter list.\n *\n * @example\n * // For path '/users/\\x00/posts/\\x00' with param names ['userId', 'postId']\n * // Returns: /\\/users\\/([^/]+)\\/posts\\/([^/]+)/\n */\nfunction buildPathInfo(resolvedPath: string): {\n regex: RegExp;\n template: string;\n paramNames: string[];\n} {\n const sentinelCount = resolvedPath.split(PARAM_MARKER).length - 1;\n\n // Generate parameter names (p1, p2, ... if we don't know the real names)\n const paramNames = Array.from(\n { length: sentinelCount },\n (_, i) => `p${i + 1}`,\n );\n\n // Build regex by replacing sentinels with capture groups\n const escaped = resolvedPath.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const regexStr = escaped.split(PARAM_MARKER).join('([^/]+)');\n\n // Build template by replacing sentinels with ${paramName} syntax\n let template = resolvedPath;\n for (const name of paramNames) {\n template = template.replace(PARAM_MARKER, `\\${${name}}`);\n }\n\n return {\n regex: new RegExp(`^${regexStr}$`),\n template,\n paramNames,\n };\n}\n\n/**\n * Create server-ready route definitions from a REST contract.\n *\n * Walks the contract tree, extracts endpoint definitions, and returns\n * structured route definitions with:\n * - HTTP method and path\n * - Body schema for request conversion (JSON→XML)\n * - Response schemas for response conversion (XML→JSON)\n * - Path parameter extraction\n *\n * @example\n * ```typescript\n * const server = createServer(adtContract);\n *\n * // Match an incoming request\n * const match = server.match('GET', '/sap/bc/adt/cts/transportrequests/DEVK900001');\n * if (match) {\n * console.log(match.route.method); // 'GET'\n * console.log(match.params); // { p1: 'devk900001' }\n * console.log(match.route.responseSchemas[200]); // Serializable schema\n * }\n * ```\n */\nexport function createServer<T extends Record<string, any>>(\n contract: T,\n): ServerRoutes {\n const endpoints = walkContract(contract, []);\n\n const routes: RouteDefinition[] = endpoints.map(\n ({ operation, descriptor }) => {\n const resolvedPath = descriptor.path || '';\n\n const { regex, template, paramNames } = buildPathInfo(resolvedPath);\n\n // Extract body schema\n const bodySchema =\n descriptor.body && isSerializable(descriptor.body)\n ? descriptor.body\n : undefined;\n\n // Extract response schemas (only those that are Serializable)\n const responseSchemas: Record<number, Serializable> = {};\n if (descriptor.responses) {\n for (const [status, schema] of Object.entries(descriptor.responses)) {\n if (isSerializable(schema)) {\n responseSchemas[Number(status)] = schema;\n }\n }\n }\n\n return {\n method: descriptor.method,\n path: resolvedPath,\n pathTemplate: template,\n pathParamNames: paramNames,\n bodySchema,\n responseSchemas,\n requestHeaders: descriptor.headers as\n | Record<string, string>\n | undefined,\n _regex: regex,\n } as RouteDefinition & { _regex: RegExp };\n },\n );\n\n return {\n routes,\n\n match(\n method: string,\n url: string,\n basePath = '',\n ): { route: RouteDefinition; params: Record<string, string> } | null {\n const pathname = url.split('?')[0];\n let relativePath = pathname;\n if (basePath && pathname.startsWith(basePath)) {\n relativePath = pathname.slice(basePath.length) || '/';\n }\n\n const targetMethod = method.toUpperCase();\n for (const route of routes) {\n if (route.method !== targetMethod) continue;\n\n const routeWithRegex = route as RouteDefinition & { _regex: RegExp };\n const match = relativePath.match(routeWithRegex._regex);\n if (match) {\n const params: Record<string, string> = {};\n for (let i = 0; i < route.pathParamNames.length; i++) {\n params[route.pathParamNames[i]] = match[i + 1] || '';\n }\n return { route, params };\n }\n }\n\n return null;\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"server-WoAHqGtU.mjs","names":[],"sources":["../src/rest/server/create-server.ts"],"sourcesContent":["/**\n * Speci REST - Server Generator\n *\n * Creates server-ready route definitions from REST contracts.\n *\n * This is the server-side counterpart of createClient. It walks a contract\n * tree, extracts endpoint definitions (method, path, body schema, response\n * schemas), and returns structured route definitions that can be used to\n * build HTTP servers with any framework (node:http, Express, Hono, etc.).\n *\n * @example\n * ```typescript\n * import { createServer } from '@abapify/speci/rest';\n * import { adtContract } from '@abapify/adt-contracts';\n *\n * const server = createServer(adtContract);\n *\n * // Use with node:http\n * import { createServer as httpCreate } from 'node:http';\n *\n * httpCreate((req, res) => {\n * const match = server.match(req.method!, req.url!);\n * if (match) {\n * // Forward to downstream SAP, handle JSON↔XML conversion, etc.\n * }\n * });\n * ```\n */\n\nimport type { OperationFunction } from '../../core/types';\nimport type { RestEndpointDescriptor, Serializable } from '../types';\nimport type { RouteDefinition, ServerRoutes } from './types';\n\n/** Sentinel value used to mark parameter positions in resolved paths */\nconst PARAM_MARKER = '\\x00';\n\n/**\n * Check if a value is a RestEndpointDescriptor (has method and path)\n */\nfunction isEndpointDescriptor(value: unknown): value is RestEndpointDescriptor {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'method' in value &&\n 'path' in value &&\n 'responses' in value\n );\n}\n\n/**\n * Check if a schema is Serializable (has parse method)\n */\nfunction isSerializable(schema: unknown): schema is Serializable {\n return (\n typeof schema === 'object' &&\n schema !== null &&\n typeof (schema as any).parse === 'function'\n );\n}\n\n/**\n * Walk a contract tree and extract all endpoint definitions.\n *\n * Calls each operation function with a sentinel value to get the resolved\n * path, then replaces the sentinel with regex capture groups for matching.\n */\nfunction tryExtractEndpoint(\n obj: OperationFunction,\n): { operation: OperationFunction; descriptor: RestEndpointDescriptor } | null {\n try {\n const paramCount = obj.length || 1;\n const sentinelArgs = Array.from({ length: paramCount }, () => PARAM_MARKER);\n const descriptor = obj(...sentinelArgs);\n if (isEndpointDescriptor(descriptor)) {\n return { operation: obj, descriptor };\n }\n } catch {\n // Some functions may need different args - skip\n }\n return null;\n}\n\nfunction walkContract(\n obj: any,\n parentPath: string[],\n): Array<{ operation: OperationFunction; descriptor: RestEndpointDescriptor }> {\n if (typeof obj === 'function') {\n const result = tryExtractEndpoint(obj as OperationFunction);\n return result ? [result] : [];\n }\n\n if (typeof obj === 'object' && obj !== null) {\n const endpoints: Array<{\n operation: OperationFunction;\n descriptor: RestEndpointDescriptor;\n }> = [];\n for (const [key, value] of Object.entries(obj)) {\n endpoints.push(...walkContract(value, [...parentPath, key]));\n }\n return endpoints;\n }\n\n return [];\n}\n\n/**\n * Convert a resolved path with sentinel markers into a regex pattern.\n *\n * Also extracts path parameter names from the original function's parameter list.\n *\n * @example\n * // For path '/users/\\x00/posts/\\x00' with param names ['userId', 'postId']\n * // Returns: /\\/users\\/([^/]+)\\/posts\\/([^/]+)/\n */\nfunction buildPathInfo(resolvedPath: string): {\n regex: RegExp;\n template: string;\n paramNames: string[];\n} {\n const sentinelCount = resolvedPath.split(PARAM_MARKER).length - 1;\n\n // Generate parameter names (p1, p2, ... if we don't know the real names)\n const paramNames = Array.from(\n { length: sentinelCount },\n (_, i) => `p${i + 1}`,\n );\n\n // Build regex by replacing sentinels with capture groups\n const escaped = resolvedPath.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const regexStr = escaped.split(PARAM_MARKER).join('([^/]+)');\n\n // Build template by replacing sentinels with ${paramName} syntax\n let template = resolvedPath;\n for (const name of paramNames) {\n template = template.replace(PARAM_MARKER, `\\${${name}}`);\n }\n\n return {\n regex: new RegExp(`^${regexStr}$`),\n template,\n paramNames,\n };\n}\n\ntype WalkedEndpoint = {\n operation: OperationFunction;\n descriptor: RestEndpointDescriptor;\n};\n\nfunction routeFromEndpoint({\n descriptor,\n}: WalkedEndpoint): RouteDefinition & { _regex: RegExp } {\n const resolvedPath = descriptor.path || '';\n const { regex, template, paramNames } = buildPathInfo(resolvedPath);\n\n const bodySchema =\n descriptor.body && isSerializable(descriptor.body)\n ? descriptor.body\n : undefined;\n\n const responseSchemas: Record<number, Serializable> = {};\n if (descriptor.responses) {\n for (const [status, schema] of Object.entries(descriptor.responses)) {\n if (isSerializable(schema)) {\n responseSchemas[Number(status)] = schema;\n }\n }\n }\n\n return {\n method: descriptor.method,\n path: resolvedPath,\n pathTemplate: template,\n pathParamNames: paramNames,\n bodySchema,\n responseSchemas,\n requestHeaders: descriptor.headers as Record<string, string> | undefined,\n _regex: regex,\n } as RouteDefinition & { _regex: RegExp };\n}\n\nfunction matchRoute(\n routes: RouteDefinition[],\n method: string,\n url: string,\n basePath: string,\n): { route: RouteDefinition; params: Record<string, string> } | null {\n const pathname = url.split('?')[0];\n const relativePath =\n basePath && pathname.startsWith(basePath)\n ? pathname.slice(basePath.length) || '/'\n : pathname;\n\n const targetMethod = method.toUpperCase();\n for (const route of routes) {\n if (route.method !== targetMethod) continue;\n const match = relativePath.match(\n (route as RouteDefinition & { _regex: RegExp })._regex,\n );\n if (match) {\n const params: Record<string, string> = {};\n for (let i = 0; i < route.pathParamNames.length; i++) {\n params[route.pathParamNames[i]] = match[i + 1] || '';\n }\n return { route, params };\n }\n }\n return null;\n}\n\n/**\n * Create server-ready route definitions from a REST contract.\n *\n * Walks the contract tree, extracts endpoint definitions, and returns\n * structured route definitions with:\n * - HTTP method and path\n * - Body schema for request conversion (JSON→XML)\n * - Response schemas for response conversion (XML→JSON)\n * - Path parameter extraction\n *\n * @example\n * ```typescript\n * const server = createServer(adtContract);\n *\n * // Match an incoming request\n * const match = server.match('GET', '/sap/bc/adt/cts/transportrequests/DEVK900001');\n * if (match) {\n * console.log(match.route.method); // 'GET'\n * console.log(match.params); // { p1: 'devk900001' }\n * console.log(match.route.responseSchemas[200]); // Serializable schema\n * }\n * ```\n */\nexport function createServer<T extends Record<string, any>>(\n contract: T,\n): ServerRoutes {\n const routes: RouteDefinition[] = walkContract(contract, []).map(\n routeFromEndpoint,\n );\n\n return {\n routes,\n match: (method, url, basePath = '') =>\n matchRoute(routes, method, url, basePath),\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abapify/speci",
3
- "version": "0.4.30",
3
+ "version": "0.4.33",
4
4
  "description": "Minimal arrow-function-based contract specification system for TypeScript",
5
5
  "license": "MIT",
6
6
  "publishConfig": {