@abapify/speci 0.4.33 → 0.4.34
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/dist/rest/index.mjs
CHANGED
|
@@ -63,43 +63,6 @@ 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
|
-
}
|
|
103
66
|
/**
|
|
104
67
|
* Create server-ready route definitions from a REST contract.
|
|
105
68
|
*
|
|
@@ -124,12 +87,48 @@ function matchRoute(routes, method, url, basePath) {
|
|
|
124
87
|
* ```
|
|
125
88
|
*/
|
|
126
89
|
function createServer(contract) {
|
|
127
|
-
const routes = walkContract(contract, []).map(
|
|
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
|
+
});
|
|
128
109
|
return {
|
|
129
110
|
routes,
|
|
130
|
-
match
|
|
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 match = route._regex.exec(relativePath);
|
|
119
|
+
if (match) {
|
|
120
|
+
const params = {};
|
|
121
|
+
for (let i = 0; i < route.pathParamNames.length; i++) params[route.pathParamNames[i]] = match[i + 1] || "";
|
|
122
|
+
return {
|
|
123
|
+
route,
|
|
124
|
+
params
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
131
130
|
};
|
|
132
131
|
}
|
|
133
132
|
|
|
134
133
|
export { createServer as t };
|
|
135
|
-
//# sourceMappingURL=server-
|
|
134
|
+
//# sourceMappingURL=server-BuLDJYvH.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-BuLDJYvH.mjs","names":["routeWithRegex"],"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 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> | 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 = routeWithRegex._regex.exec(relativePath);\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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"}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
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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"}
|