@ankhorage/api 0.2.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # @ankhorage/api
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 1d84b7f: Preserve the original transport request and operation binding when mapping runtime responses so stateful HTTP adapters such as Fastify can reply without shared mutable context.
8
+
9
+ ## 0.1.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 5e69acf: Establish the framework-neutral Ankhorage API runtime, operation dispatch, and transport adapter contract.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ankhorage
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,24 @@
1
+ <!-- markdownlint-disable MD013 MD033 -->
2
+ <!-- This file is generated by Paradox. Do not edit manually. -->
3
+
4
+ # @ankhorage/api
5
+
6
+ Framework-neutral executable API runtime for Ankhorage.
7
+
8
+ It consumes portable API definitions from `@ankhorage/contracts`, resolves executable operation
9
+ bindings, dispatches application handlers, and exposes a transport-adapter boundary without
10
+ depending on Fastify, Next.js, React, or another server framework.
11
+
12
+ ## Usage
13
+
14
+ ### Basic Usage
15
+
16
+ `@ankhorage/api` executes portable internal REST API definitions without coupling application
17
+ handlers to Fastify, Next.js, or another HTTP framework. Framework packages adapt requests and
18
+ responses at the transport edge while this runtime owns operation lookup and dispatch.
19
+
20
+ See `examples/basic-usage/index.ts` for the executable example.
21
+
22
+ ## Generated documentation
23
+
24
+ Generated Paradox documentation is published with package releases.
package/dist/api.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export { createApiRuntime } from "./features/execution/createApiRuntime.js";
2
+ export { createApiTransportHandler } from "./features/execution/createApiTransportHandler.js";
3
+ export { resolveApiOperationBindings } from "./features/execution/resolveApiOperationBindings.js";
4
+ export type { ApiHandler, ApiHandlerContext, ApiHandlerRegistry, ApiHandlerResponse, ApiOperationBinding, ApiRequest, ApiResponse, ApiRuntime, ApiTransportAdapter, } from "./types/api.js";
5
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAC5E,OAAO,EAAE,yBAAyB,EAAE,MAAM,mDAAmD,CAAC;AAC9F,OAAO,EAAE,2BAA2B,EAAE,MAAM,qDAAqD,CAAC;AAClG,YAAY,EACV,UAAU,EACV,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,UAAU,EACV,WAAW,EACX,UAAU,EACV,mBAAmB,GACpB,MAAM,gBAAgB,CAAC"}
package/dist/api.js ADDED
@@ -0,0 +1,4 @@
1
+ export { createApiRuntime } from "./features/execution/createApiRuntime.js";
2
+ export { createApiTransportHandler } from "./features/execution/createApiTransportHandler.js";
3
+ export { resolveApiOperationBindings } from "./features/execution/resolveApiOperationBindings.js";
4
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAC5E,OAAO,EAAE,yBAAyB,EAAE,MAAM,mDAAmD,CAAC;AAC9F,OAAO,EAAE,2BAA2B,EAAE,MAAM,qDAAqD,CAAC","sourcesContent":["export { createApiRuntime } from \"./features/execution/createApiRuntime.js\";\nexport { createApiTransportHandler } from \"./features/execution/createApiTransportHandler.js\";\nexport { resolveApiOperationBindings } from \"./features/execution/resolveApiOperationBindings.js\";\nexport type {\n ApiHandler,\n ApiHandlerContext,\n ApiHandlerRegistry,\n ApiHandlerResponse,\n ApiOperationBinding,\n ApiRequest,\n ApiResponse,\n ApiRuntime,\n ApiTransportAdapter,\n} from \"./types/api.js\";\n"]}
@@ -0,0 +1,8 @@
1
+ import type { InternalRestApiDefinition } from "@ankhorage/contracts/data";
2
+ import type { ApiHandlerRegistry, ApiRuntime } from "../../types/api.js";
3
+ /*** Create the canonical executable API runtime for one portable internal REST definition. */
4
+ export declare function createApiRuntime(input: {
5
+ readonly definition: InternalRestApiDefinition;
6
+ readonly handlers: ApiHandlerRegistry;
7
+ }): ApiRuntime;
8
+ //# sourceMappingURL=createApiRuntime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createApiRuntime.d.ts","sourceRoot":"","sources":["../../../src/features/execution/createApiRuntime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAE3E,OAAO,KAAK,EACV,kBAAkB,EAIlB,UAAU,EACX,MAAM,oBAAoB,CAAC;AAG5B,8FAA8F;AAC9F,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACtC,QAAQ,CAAC,UAAU,EAAE,yBAAyB,CAAC;IAC/C,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC;CACvC,GAAG,UAAU,CAab"}
@@ -0,0 +1,54 @@
1
+ import { resolveApiOperationBindings } from "./resolveApiOperationBindings.js";
2
+ /*** Create the canonical executable API runtime for one portable internal REST definition. */
3
+ export function createApiRuntime(input) {
4
+ const bindings = resolveApiOperationBindings(input.definition);
5
+ const bindingsByOperation = new Map(bindings.map((binding) => [binding.operationId, binding]));
6
+ return {
7
+ definition: input.definition,
8
+ bindings,
9
+ getBinding: (operationId) => bindingsByOperation.get(operationId),
10
+ dispatchAsync: async (request) => dispatchApiRequestAsync(request, bindingsByOperation, input.handlers),
11
+ };
12
+ }
13
+ /*** Dispatch one normalized request to its registered operation handler. */
14
+ async function dispatchApiRequestAsync(request, bindingsByOperation, handlers) {
15
+ const binding = bindingsByOperation.get(request.operationId);
16
+ if (!binding) {
17
+ return errorResponse(404, "operation_not_found", request.operationId);
18
+ }
19
+ if (request.method !== binding.method) {
20
+ return errorResponse(405, "method_not_allowed", request.operationId);
21
+ }
22
+ const handler = handlers[request.operationId];
23
+ if (!handler) {
24
+ return errorResponse(501, "handler_not_registered", request.operationId);
25
+ }
26
+ try {
27
+ return normalizeResponse(await handler(request, { binding }));
28
+ }
29
+ catch {
30
+ return errorResponse(500, "handler_failed", request.operationId);
31
+ }
32
+ }
33
+ /*** Normalize handler responses to the stable runtime response contract. */
34
+ function normalizeResponse(response) {
35
+ return {
36
+ status: response.status ?? 200,
37
+ headers: response.headers ?? {},
38
+ ...(response.body === undefined ? {} : { body: response.body }),
39
+ };
40
+ }
41
+ /*** Create one deterministic runtime error response. */
42
+ function errorResponse(status, code, operationId) {
43
+ return {
44
+ status,
45
+ headers: { "content-type": "application/json" },
46
+ body: {
47
+ error: {
48
+ code,
49
+ operationId,
50
+ },
51
+ },
52
+ };
53
+ }
54
+ //# sourceMappingURL=createApiRuntime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createApiRuntime.js","sourceRoot":"","sources":["../../../src/features/execution/createApiRuntime.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;AAE/E,8FAA8F;AAC9F,MAAM,UAAU,gBAAgB,CAAC,KAGhC;IACC,MAAM,QAAQ,GAAG,2BAA2B,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAC/D,MAAM,mBAAmB,GAAG,IAAI,GAAG,CACjC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAC1D,CAAC;IAEF,OAAO;QACL,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,QAAQ;QACR,UAAU,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,mBAAmB,CAAC,GAAG,CAAC,WAAW,CAAC;QACjE,aAAa,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,CAC/B,uBAAuB,CAAC,OAAO,EAAE,mBAAmB,EAAE,KAAK,CAAC,QAAQ,CAAC;KACxE,CAAC;AACJ,CAAC;AAED,4EAA4E;AAC5E,KAAK,UAAU,uBAAuB,CACpC,OAAmB,EACnB,mBAGC,EACD,QAA4B;IAE5B,MAAM,OAAO,GAAG,mBAAmB,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC7D,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,aAAa,CAAC,GAAG,EAAE,qBAAqB,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IACxE,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC;QACtC,OAAO,aAAa,CAAC,GAAG,EAAE,oBAAoB,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IACvE,CAAC;IAED,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC9C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,aAAa,CAAC,GAAG,EAAE,wBAAwB,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IAC3E,CAAC;IAED,IAAI,CAAC;QACH,OAAO,iBAAiB,CAAC,MAAM,OAAO,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;IAChE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,aAAa,CAAC,GAAG,EAAE,gBAAgB,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IACnE,CAAC;AACH,CAAC;AAED,4EAA4E;AAC5E,SAAS,iBAAiB,CAAC,QAA4B;IACrD,OAAO;QACL,MAAM,EAAE,QAAQ,CAAC,MAAM,IAAI,GAAG;QAC9B,OAAO,EAAE,QAAQ,CAAC,OAAO,IAAI,EAAE;QAC/B,GAAG,CAAC,QAAQ,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC;KAChE,CAAC;AACJ,CAAC;AAED,wDAAwD;AACxD,SAAS,aAAa,CACpB,MAAc,EACd,IAAY,EACZ,WAAmB;IAEnB,OAAO;QACL,MAAM;QACN,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE;YACJ,KAAK,EAAE;gBACL,IAAI;gBACJ,WAAW;aACZ;SACF;KACF,CAAC;AACJ,CAAC","sourcesContent":["import type { InternalRestApiDefinition } from \"@ankhorage/contracts/data\";\n\nimport type {\n ApiHandlerRegistry,\n ApiHandlerResponse,\n ApiRequest,\n ApiResponse,\n ApiRuntime,\n} from \"../../types/api.js\";\nimport { resolveApiOperationBindings } from \"./resolveApiOperationBindings.js\";\n\n/*** Create the canonical executable API runtime for one portable internal REST definition. */\nexport function createApiRuntime(input: {\n readonly definition: InternalRestApiDefinition;\n readonly handlers: ApiHandlerRegistry;\n}): ApiRuntime {\n const bindings = resolveApiOperationBindings(input.definition);\n const bindingsByOperation = new Map(\n bindings.map((binding) => [binding.operationId, binding]),\n );\n\n return {\n definition: input.definition,\n bindings,\n getBinding: (operationId) => bindingsByOperation.get(operationId),\n dispatchAsync: async (request) =>\n dispatchApiRequestAsync(request, bindingsByOperation, input.handlers),\n };\n}\n\n/*** Dispatch one normalized request to its registered operation handler. */\nasync function dispatchApiRequestAsync(\n request: ApiRequest,\n bindingsByOperation: ReadonlyMap<\n string,\n ReturnType<typeof resolveApiOperationBindings>[number]\n >,\n handlers: ApiHandlerRegistry,\n): Promise<ApiResponse> {\n const binding = bindingsByOperation.get(request.operationId);\n if (!binding) {\n return errorResponse(404, \"operation_not_found\", request.operationId);\n }\n if (request.method !== binding.method) {\n return errorResponse(405, \"method_not_allowed\", request.operationId);\n }\n\n const handler = handlers[request.operationId];\n if (!handler) {\n return errorResponse(501, \"handler_not_registered\", request.operationId);\n }\n\n try {\n return normalizeResponse(await handler(request, { binding }));\n } catch {\n return errorResponse(500, \"handler_failed\", request.operationId);\n }\n}\n\n/*** Normalize handler responses to the stable runtime response contract. */\nfunction normalizeResponse(response: ApiHandlerResponse): ApiResponse {\n return {\n status: response.status ?? 200,\n headers: response.headers ?? {},\n ...(response.body === undefined ? {} : { body: response.body }),\n };\n}\n\n/*** Create one deterministic runtime error response. */\nfunction errorResponse(\n status: number,\n code: string,\n operationId: string,\n): ApiResponse {\n return {\n status,\n headers: { \"content-type\": \"application/json\" },\n body: {\n error: {\n code,\n operationId,\n },\n },\n };\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import type { ApiOperationBinding, ApiRuntime, ApiTransportAdapter } from "../../types/api.js";
2
+ /*** Compose one framework adapter with the canonical runtime for a single operation binding. */
3
+ export declare function createApiTransportHandler<TRequest, TResponse>(runtime: ApiRuntime, adapter: ApiTransportAdapter<TRequest, TResponse>, binding: ApiOperationBinding): (request: TRequest) => Promise<TResponse>;
4
+ //# sourceMappingURL=createApiTransportHandler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createApiTransportHandler.d.ts","sourceRoot":"","sources":["../../../src/features/execution/createApiTransportHandler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,UAAU,EACV,mBAAmB,EACpB,MAAM,oBAAoB,CAAC;AAE5B,gGAAgG;AAChG,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,SAAS,EAC3D,OAAO,EAAE,UAAU,EACnB,OAAO,EAAE,mBAAmB,CAAC,QAAQ,EAAE,SAAS,CAAC,EACjD,OAAO,EAAE,mBAAmB,GAC3B,CAAC,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC,SAAS,CAAC,CAM3C"}
@@ -0,0 +1,9 @@
1
+ /*** Compose one framework adapter with the canonical runtime for a single operation binding. */
2
+ export function createApiTransportHandler(runtime, adapter, binding) {
3
+ return async (request) => {
4
+ const apiRequest = await adapter.toApiRequestAsync(request, binding);
5
+ const apiResponse = await runtime.dispatchAsync(apiRequest);
6
+ return adapter.fromApiResponseAsync(apiResponse, request, binding);
7
+ };
8
+ }
9
+ //# sourceMappingURL=createApiTransportHandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createApiTransportHandler.js","sourceRoot":"","sources":["../../../src/features/execution/createApiTransportHandler.ts"],"names":[],"mappings":"AAMA,gGAAgG;AAChG,MAAM,UAAU,yBAAyB,CACvC,OAAmB,EACnB,OAAiD,EACjD,OAA4B;IAE5B,OAAO,KAAK,EAAE,OAAO,EAAE,EAAE;QACvB,MAAM,UAAU,GAAG,MAAM,OAAO,CAAC,iBAAiB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACrE,MAAM,WAAW,GAAG,MAAM,OAAO,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;QAC5D,OAAO,OAAO,CAAC,oBAAoB,CAAC,WAAW,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IACrE,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import type {\n ApiOperationBinding,\n ApiRuntime,\n ApiTransportAdapter,\n} from \"../../types/api.js\";\n\n/*** Compose one framework adapter with the canonical runtime for a single operation binding. */\nexport function createApiTransportHandler<TRequest, TResponse>(\n runtime: ApiRuntime,\n adapter: ApiTransportAdapter<TRequest, TResponse>,\n binding: ApiOperationBinding,\n): (request: TRequest) => Promise<TResponse> {\n return async (request) => {\n const apiRequest = await adapter.toApiRequestAsync(request, binding);\n const apiResponse = await runtime.dispatchAsync(apiRequest);\n return adapter.fromApiResponseAsync(apiResponse, request, binding);\n };\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import type { InternalRestApiDefinition } from "@ankhorage/contracts/data";
2
+ import type { ApiOperationBinding } from "../../types/api.js";
3
+ /***
4
+ * Resolve one portable internal REST definition into deterministic executable operation bindings.
5
+ */
6
+ export declare function resolveApiOperationBindings(definition: InternalRestApiDefinition): readonly ApiOperationBinding[];
7
+ //# sourceMappingURL=resolveApiOperationBindings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveApiOperationBindings.d.ts","sourceRoot":"","sources":["../../../src/features/execution/resolveApiOperationBindings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,yBAAyB,EAC1B,MAAM,2BAA2B,CAAC;AAEnC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D;;GAEG;AACH,wBAAgB,2BAA2B,CACzC,UAAU,EAAE,yBAAyB,GACpC,SAAS,mBAAmB,EAAE,CAoBhC"}
@@ -0,0 +1,42 @@
1
+ /***
2
+ * Resolve one portable internal REST definition into deterministic executable operation bindings.
3
+ */
4
+ export function resolveApiOperationBindings(definition) {
5
+ return Object.values(definition.endpoints)
6
+ .flatMap((endpoint) => Object.values(endpoint.operations).map((operation) => {
7
+ const method = operation.method ?? inferMethod(operation.intent);
8
+ return {
9
+ definition,
10
+ endpoint,
11
+ operation,
12
+ operationId: operation.id,
13
+ method,
14
+ path: joinRoutePath(definition.basePath, endpoint.path, operation.path),
15
+ };
16
+ }))
17
+ .sort((left, right) => left.operationId.localeCompare(right.operationId));
18
+ }
19
+ /*** Infer the conventional HTTP method for an operation intent when the contract omits one. */
20
+ function inferMethod(intent) {
21
+ switch (intent) {
22
+ case "create":
23
+ return "POST";
24
+ case "delete":
25
+ return "DELETE";
26
+ case "read":
27
+ return "GET";
28
+ case "update":
29
+ return "PATCH";
30
+ default:
31
+ return "POST";
32
+ }
33
+ }
34
+ /*** Join API, endpoint, and operation paths into one normalized absolute route path. */
35
+ function joinRoutePath(...parts) {
36
+ const segments = parts
37
+ .filter((part) => typeof part === "string")
38
+ .flatMap((part) => part.split("/"))
39
+ .filter(Boolean);
40
+ return "/" + segments.join("/");
41
+ }
42
+ //# sourceMappingURL=resolveApiOperationBindings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveApiOperationBindings.js","sourceRoot":"","sources":["../../../src/features/execution/resolveApiOperationBindings.ts"],"names":[],"mappings":"AAOA;;GAEG;AACH,MAAM,UAAU,2BAA2B,CACzC,UAAqC;IAErC,OAAO,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC;SACvC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE,CACpB,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE;QACnD,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,IAAI,WAAW,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACjE,OAAO;YACL,UAAU;YACV,QAAQ;YACR,SAAS;YACT,WAAW,EAAE,SAAS,CAAC,EAAE;YACzB,MAAM;YACN,IAAI,EAAE,aAAa,CACjB,UAAU,CAAC,QAAQ,EACnB,QAAQ,CAAC,IAAI,EACb,SAAS,CAAC,IAAI,CACf;SAC4B,CAAC;IAClC,CAAC,CAAC,CACH;SACA,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,aAAa,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC;AAC9E,CAAC;AAED,+FAA+F;AAC/F,SAAS,WAAW,CAAC,MAAc;IACjC,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,QAAQ;YACX,OAAO,MAAM,CAAC;QAChB,KAAK,QAAQ;YACX,OAAO,QAAQ,CAAC;QAClB,KAAK,MAAM;YACT,OAAO,KAAK,CAAC;QACf,KAAK,QAAQ;YACX,OAAO,OAAO,CAAC;QACjB;YACE,OAAO,MAAM,CAAC;IAClB,CAAC;AACH,CAAC;AAED,wFAAwF;AACxF,SAAS,aAAa,CAAC,GAAG,KAAsC;IAC9D,MAAM,QAAQ,GAAG,KAAK;SACnB,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC;SAC1D,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;SAClC,MAAM,CAAC,OAAO,CAAC,CAAC;IACnB,OAAO,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClC,CAAC","sourcesContent":["import type {\n DataOperationMethod,\n InternalRestApiDefinition,\n} from \"@ankhorage/contracts/data\";\n\nimport type { ApiOperationBinding } from \"../../types/api.js\";\n\n/***\n * Resolve one portable internal REST definition into deterministic executable operation bindings.\n */\nexport function resolveApiOperationBindings(\n definition: InternalRestApiDefinition,\n): readonly ApiOperationBinding[] {\n return Object.values(definition.endpoints)\n .flatMap((endpoint) =>\n Object.values(endpoint.operations).map((operation) => {\n const method = operation.method ?? inferMethod(operation.intent);\n return {\n definition,\n endpoint,\n operation,\n operationId: operation.id,\n method,\n path: joinRoutePath(\n definition.basePath,\n endpoint.path,\n operation.path,\n ),\n } satisfies ApiOperationBinding;\n }),\n )\n .sort((left, right) => left.operationId.localeCompare(right.operationId));\n}\n\n/*** Infer the conventional HTTP method for an operation intent when the contract omits one. */\nfunction inferMethod(intent: string): DataOperationMethod {\n switch (intent) {\n case \"create\":\n return \"POST\";\n case \"delete\":\n return \"DELETE\";\n case \"read\":\n return \"GET\";\n case \"update\":\n return \"PATCH\";\n default:\n return \"POST\";\n }\n}\n\n/*** Join API, endpoint, and operation paths into one normalized absolute route path. */\nfunction joinRoutePath(...parts: readonly (string | undefined)[]): string {\n const segments = parts\n .filter((part): part is string => typeof part === \"string\")\n .flatMap((part) => part.split(\"/\"))\n .filter(Boolean);\n return \"/\" + segments.join(\"/\");\n}\n"]}
@@ -0,0 +1,43 @@
1
+ import type { DataEndpointConfig, DataOperationConfig, DataOperationMethod, InternalRestApiDefinition } from "@ankhorage/contracts/data";
2
+ export interface ApiOperationBinding {
3
+ readonly definition: InternalRestApiDefinition;
4
+ readonly endpoint: DataEndpointConfig;
5
+ readonly operation: DataOperationConfig;
6
+ readonly operationId: string;
7
+ readonly method: DataOperationMethod;
8
+ readonly path: string;
9
+ }
10
+ export interface ApiRequest {
11
+ readonly operationId: string;
12
+ readonly method: DataOperationMethod;
13
+ readonly params: Readonly<Record<string, string>>;
14
+ readonly query: Readonly<Record<string, string | readonly string[]>>;
15
+ readonly headers: Readonly<Record<string, string>>;
16
+ readonly body?: unknown;
17
+ }
18
+ export interface ApiHandlerResponse {
19
+ readonly status?: number;
20
+ readonly headers?: Readonly<Record<string, string>>;
21
+ readonly body?: unknown;
22
+ }
23
+ export interface ApiResponse {
24
+ readonly status: number;
25
+ readonly headers: Readonly<Record<string, string>>;
26
+ readonly body?: unknown;
27
+ }
28
+ export interface ApiHandlerContext {
29
+ readonly binding: ApiOperationBinding;
30
+ }
31
+ export type ApiHandler = (request: ApiRequest, context: ApiHandlerContext) => ApiHandlerResponse | Promise<ApiHandlerResponse>;
32
+ export type ApiHandlerRegistry = Readonly<Record<string, ApiHandler>>;
33
+ export interface ApiRuntime {
34
+ readonly definition: InternalRestApiDefinition;
35
+ readonly bindings: readonly ApiOperationBinding[];
36
+ dispatchAsync(request: ApiRequest): Promise<ApiResponse>;
37
+ getBinding(operationId: string): ApiOperationBinding | undefined;
38
+ }
39
+ export interface ApiTransportAdapter<TRequest, TResponse> {
40
+ toApiRequestAsync(request: TRequest, binding: ApiOperationBinding): Promise<ApiRequest>;
41
+ fromApiResponseAsync(response: ApiResponse, request: TRequest, binding: ApiOperationBinding): Promise<TResponse>;
42
+ }
43
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/types/api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,yBAAyB,EAC1B,MAAM,2BAA2B,CAAC;AAEnC,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,UAAU,EAAE,yBAAyB,CAAC;IAC/C,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC;IACtC,QAAQ,CAAC,SAAS,EAAE,mBAAmB,CAAC;IACxC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAClD,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;IACrE,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACpD,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;CACvC;AAED,MAAM,MAAM,UAAU,GAAG,CACvB,OAAO,EAAE,UAAU,EACnB,OAAO,EAAE,iBAAiB,KACvB,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;AAEtD,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;AAEtE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,UAAU,EAAE,yBAAyB,CAAC;IAC/C,QAAQ,CAAC,QAAQ,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAClD,aAAa,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IACzD,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAAC;CAClE;AAED,MAAM,WAAW,mBAAmB,CAAC,QAAQ,EAAE,SAAS;IACtD,iBAAiB,CACf,OAAO,EAAE,QAAQ,EACjB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,UAAU,CAAC,CAAC;IACvB,oBAAoB,CAClB,QAAQ,EAAE,WAAW,EACrB,OAAO,EAAE,QAAQ,EACjB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,SAAS,CAAC,CAAC;CACvB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/types/api.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n DataEndpointConfig,\n DataOperationConfig,\n DataOperationMethod,\n InternalRestApiDefinition,\n} from \"@ankhorage/contracts/data\";\n\nexport interface ApiOperationBinding {\n readonly definition: InternalRestApiDefinition;\n readonly endpoint: DataEndpointConfig;\n readonly operation: DataOperationConfig;\n readonly operationId: string;\n readonly method: DataOperationMethod;\n readonly path: string;\n}\n\nexport interface ApiRequest {\n readonly operationId: string;\n readonly method: DataOperationMethod;\n readonly params: Readonly<Record<string, string>>;\n readonly query: Readonly<Record<string, string | readonly string[]>>;\n readonly headers: Readonly<Record<string, string>>;\n readonly body?: unknown;\n}\n\nexport interface ApiHandlerResponse {\n readonly status?: number;\n readonly headers?: Readonly<Record<string, string>>;\n readonly body?: unknown;\n}\n\nexport interface ApiResponse {\n readonly status: number;\n readonly headers: Readonly<Record<string, string>>;\n readonly body?: unknown;\n}\n\nexport interface ApiHandlerContext {\n readonly binding: ApiOperationBinding;\n}\n\nexport type ApiHandler = (\n request: ApiRequest,\n context: ApiHandlerContext,\n) => ApiHandlerResponse | Promise<ApiHandlerResponse>;\n\nexport type ApiHandlerRegistry = Readonly<Record<string, ApiHandler>>;\n\nexport interface ApiRuntime {\n readonly definition: InternalRestApiDefinition;\n readonly bindings: readonly ApiOperationBinding[];\n dispatchAsync(request: ApiRequest): Promise<ApiResponse>;\n getBinding(operationId: string): ApiOperationBinding | undefined;\n}\n\nexport interface ApiTransportAdapter<TRequest, TResponse> {\n toApiRequestAsync(\n request: TRequest,\n binding: ApiOperationBinding,\n ): Promise<ApiRequest>;\n fromApiResponseAsync(\n response: ApiResponse,\n request: TRequest,\n binding: ApiOperationBinding,\n ): Promise<TResponse>;\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@ankhorage/api",
3
+ "version": "0.2.0",
4
+ "type": "module",
5
+ "description": "Framework-neutral executable API runtime and transport contract for Ankhorage.",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/ankhorage/api.git"
10
+ },
11
+ "homepage": "https://github.com/ankhorage/api#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/ankhorage/api/issues"
14
+ },
15
+ "keywords": [
16
+ "ankhorage",
17
+ "api",
18
+ "api-runtime",
19
+ "adapter",
20
+ "ports-and-adapters",
21
+ "rest",
22
+ "transport",
23
+ "typescript"
24
+ ],
25
+ "sideEffects": false,
26
+ "files": [
27
+ "dist",
28
+ "README.md",
29
+ "LICENSE",
30
+ "CHANGELOG.md"
31
+ ],
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/api.d.ts",
35
+ "import": "./dist/api.js"
36
+ },
37
+ "./package.json": "./package.json"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "engines": {
43
+ "node": ">=24"
44
+ },
45
+ "scripts": {
46
+ "build": "rm -rf dist tsconfig.tsbuildinfo && bun x tsc -p tsconfig.build.json",
47
+ "changeset": "ankhorage-changeset",
48
+ "changeset:status": "ankhorage-changeset status --since=origin/main",
49
+ "format": "ankhorage-prettier --write .",
50
+ "format:check": "ankhorage-prettier --check .",
51
+ "lint": "ankhorage-eslint . --max-warnings=0",
52
+ "lint:fix": "ankhorage-eslint . --fix --max-warnings=0",
53
+ "prepack": "bun run build",
54
+ "test": "bun test src",
55
+ "typecheck": "bun x tsc --noEmit -p tsconfig.json",
56
+ "version-packages": "ankhorage-changeset version",
57
+ "knip:check": "ankhorage-knip",
58
+ "docs": "paradox"
59
+ },
60
+ "dependencies": {
61
+ "@ankhorage/contracts": "^22.8.102"
62
+ },
63
+ "devDependencies": {
64
+ "@ankhorage/devtools": "^2.1.11",
65
+ "@types/bun": "^1.4.2",
66
+ "@types/node": "^26.6.4",
67
+ "typescript": "^6.0.3",
68
+ "@ankhorage/paradox": "^0.2.28"
69
+ },
70
+ "packageManager": "bun@1.4.2"
71
+ }