@awsmbiz/inbis-api 0.0.0-stage → 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/README.md CHANGED
@@ -1,3 +1,53 @@
1
- # Temporary Holding Version
1
+ # @awsmbiz/inbis-api
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ App factory for Inbis Workers: an OpenAPI-enabled Hono app with the platform's
4
+ request pipeline built in. Peer dependencies: `hono`, `@hono/zod-openapi`, `zod` (v4).
5
+
6
+ ```ts
7
+ import { createRoute, z } from "@hono/zod-openapi";
8
+ import { createApi, createRouter } from "@awsmbiz/inbis-api";
9
+ import { requireVar } from "@awsmbiz/inbis-cloudflare";
10
+
11
+ const getWidget = createRoute({
12
+ method: "get",
13
+ path: "/widgets/{id}",
14
+ request: { params: z.object({ id: z.string().uuid() }) },
15
+ responses: { 200: { description: "Widget", content: { "application/json": { schema: WidgetSchema } } } },
16
+ });
17
+
18
+ const v1 = createRouter<Env>().openapi(getWidget, async (c) => {
19
+ c.var.logger.info("widget.read", { id: c.req.valid("param").id });
20
+ return c.json(await widgets.get(c.req.valid("param").id), 200);
21
+ });
22
+
23
+ export default createApi<Env>({
24
+ info: { title: "Billing", version: "1.0.0" },
25
+ trustedOrigins: (env) => requireVar(env, "TRUSTED_ORIGINS").split(","),
26
+ readiness: { database: (c) => c.env.DB.prepare("select 1").run() },
27
+ v1,
28
+ });
29
+ ```
30
+
31
+ ## What every app gets
32
+
33
+ | Concern | Behaviour |
34
+ |---|---|
35
+ | Request IDs | Every response gets a fresh UUIDv7 `x-request-id`. An incoming `x-request-id` is ignored. |
36
+ | Correlation IDs | A well-formed incoming `x-correlation-id` (`[A-Za-z0-9._:-]{1,128}`) is propagated; otherwise the request ID is used. Both are echoed. |
37
+ | Logging | `c.var.logger` carries both IDs. One `request.completed` record per request (method, path, status, durationMs), redacted by `@awsmbiz/inbis-core`. 5xx errors are logged as `request.failed`. |
38
+ | Error envelope | Every error is `{ error: { code, message, requestId, details? } }`. `AppError` renders its status, code, message and details. Hono 4xx `HTTPException`s are mapped (malformed JSON is `400 BAD_REQUEST`). Unknown routes give `404 NOT_FOUND`. Anything else gives `500 INTERNAL_ERROR` without leaking the message. |
39
+ | Validation | Routes registered with `.openapi()` on `createRouter()` reject invalid input with `422 VALIDATION_FAILED` and `details: { target, issues: [{ path, code, message }] }`. Submitted values are never echoed. |
40
+ | CORS | Only `trustedOrigins` get `Access-Control-Allow-Origin`, with credentials. `x-request-id` and `x-correlation-id` are exposed. |
41
+ | Trusted origins | `POST`/`PUT`/`PATCH`/`DELETE` with an `Origin` that is neither the API's own nor trusted give `403 FORBIDDEN_ORIGIN` (CSRF defence). Requests without `Origin` (non-browser clients) pass. |
42
+ | Health | `GET /health` gives `{ status: "ok" }`. `GET /health/ready` runs the `readiness` checks: `200 { status: "ok", checks }` or `503 { status: "unavailable", checks }`. Check errors are logged, not returned. |
43
+ | Versioning | The `v1` router is mounted under `/v1`. |
44
+ | OpenAPI | `GET /openapi.json` serves an OpenAPI 3.1 document of all `.openapi()` routes, including the `ErrorEnvelope` schema, `422` for routes that validate input, and a `default` error response. |
45
+
46
+ The returned app keeps the `v1` router's route types, so `hc<typeof app>()` (Hono RPC) works.
47
+
48
+ Layering convention: route, then validation, then service, then repository, then database.
49
+ No business logic in route handlers.
50
+
51
+ Exports: `createApi`, `createRouter`, `ErrorEnvelopeSchema`, and the types `ApiEnv`,
52
+ `ApiVariables`, `CreateApiOptions`, `ErrorEnvelope`, `ReadinessCheck`, `TrustedOrigins`
53
+ and `ValidationIssue`.
@@ -0,0 +1,74 @@
1
+ import { Logger } from "@awsmbiz/inbis-core";
2
+ import { OpenAPIHono, z } from "@hono/zod-openapi";
3
+ import { Context, Hono, Schema } from "hono";
4
+ import { MergeSchemaPath } from "hono/types";
5
+ import "hono/utils/http-status";
6
+ /** Per-request values `createApi` puts on the Hono context (`c.var`). */
7
+ interface ApiVariables {
8
+ /** Fresh UUIDv7 for this request; echoed as `x-request-id`. */
9
+ requestId: string;
10
+ /** Caller-supplied `x-correlation-id` when well-formed, else the request ID. */
11
+ correlationId: string;
12
+ /** Logger carrying `requestId` and `correlationId`. */
13
+ logger: Logger;
14
+ }
15
+ /** Hono environment for apps built with `createApi`; `B` is the Worker `env` type. */
16
+ interface ApiEnv<B extends object = object> {
17
+ Bindings: B;
18
+ Variables: ApiVariables;
19
+ }
20
+ /** A readiness check: resolve when the dependency is usable, throw when it is not. */
21
+ type ReadinessCheck<B extends object = object> = (c: Context<ApiEnv<B>>) => unknown;
22
+ type TrustedOrigins<B extends object> = readonly string[] | ((env: B) => readonly string[]);
23
+ interface CreateApiOptions<B extends object = object, S extends Schema = Schema> {
24
+ /** OpenAPI `info` for `/openapi.json`. */
25
+ info: {
26
+ title: string;
27
+ version: string;
28
+ description?: string;
29
+ };
30
+ /**
31
+ * Browser origins allowed to call the API with credentials. Either a fixed
32
+ * list or a function of the Worker `env` (e.g. read from a variable).
33
+ */
34
+ trustedOrigins?: TrustedOrigins<B>;
35
+ /** Version 1 routes, mounted under `/v1`. Build with `createRouter()`. */
36
+ v1?: Hono<ApiEnv<B>, S>;
37
+ /**
38
+ * Named dependency checks for `/health/ready` (e.g. `{ database: (c) => ... }`).
39
+ * Each passes by returning and fails by throwing; errors are logged, not returned.
40
+ */
41
+ readiness?: Record<string, ReadinessCheck<B>>;
42
+ /** Base logger. Defaults to a console JSON logger (Workers Logs). */
43
+ logger?: Logger;
44
+ }
45
+ /**
46
+ * Creates the Hono app for an Inbis Worker. Export it as the Worker's default
47
+ * export (`export default app`).
48
+ */
49
+ export declare function createApi<B extends object = object, S extends Schema = Schema>(options: CreateApiOptions<B, S>): OpenAPIHono<ApiEnv<B>, MergeSchemaPath<S, "/v1">>;
50
+ /** The body of every error response: `{ error: { code, message, requestId, details? } }`. */
51
+ export declare const ErrorEnvelopeSchema: z.ZodObject<{
52
+ error: z.ZodObject<{
53
+ code: z.ZodString;
54
+ message: z.ZodString;
55
+ requestId: z.ZodString;
56
+ details: z.ZodOptional<z.ZodUnknown>;
57
+ }, z.core.$strip>;
58
+ }, z.core.$strip>;
59
+ type ErrorEnvelope = z.infer<typeof ErrorEnvelopeSchema>;
60
+ /**
61
+ * Creates a router for a versioned API section (e.g. `/v1`). Register routes
62
+ * with `.openapi(createRoute(...), handler)` so they are validated (invalid
63
+ * input becomes a 422 `VALIDATION_FAILED` envelope) and documented, then pass
64
+ * it to `createApi({ v1 })`.
65
+ */
66
+ export declare function createRouter<B extends object = object>(): OpenAPIHono<ApiEnv<B>>;
67
+ /** One failed check, safe to return to the caller (never includes the submitted value). */
68
+ interface ValidationIssue {
69
+ path: (string | number)[];
70
+ code: string;
71
+ message: string;
72
+ }
73
+ export type { ApiEnv, ApiVariables, CreateApiOptions, ErrorEnvelope, ReadinessCheck, TrustedOrigins, ValidationIssue };
74
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,320 @@
1
+ import { REDACTED, createId, createLogger, isAppError, isSensitiveKey, validationFailed } from "@awsmbiz/inbis-core";
2
+ import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
3
+ import { HTTPException } from "hono/http-exception";
4
+ import { cors } from "hono/cors";
5
+ // src/errors.ts
6
+ /** The body of every error response: `{ error: { code, message, requestId, details? } }`. */
7
+ const ErrorEnvelopeSchema = z.object({ error: z.object({
8
+ code: z.string().openapi({ example: "NOT_FOUND" }),
9
+ message: z.string(),
10
+ requestId: z.string(),
11
+ details: z.unknown().optional()
12
+ }) }).openapi("ErrorEnvelope");
13
+ const HTTP_CODES = {
14
+ 400: "BAD_REQUEST",
15
+ 401: "UNAUTHORIZED",
16
+ 403: "FORBIDDEN",
17
+ 404: "NOT_FOUND",
18
+ 405: "METHOD_NOT_ALLOWED",
19
+ 409: "CONFLICT",
20
+ 413: "PAYLOAD_TOO_LARGE",
21
+ 415: "UNSUPPORTED_MEDIA_TYPE",
22
+ 422: "VALIDATION_FAILED",
23
+ 429: "RATE_LIMITED"
24
+ };
25
+ /** Renders the standard error envelope with the current request ID. */
26
+ function renderError(c, error) {
27
+ const body = { error: {
28
+ code: error.code,
29
+ message: error.message,
30
+ requestId: c.var.requestId,
31
+ ...error.details === void 0 ? {} : { details: error.details }
32
+ } };
33
+ return c.json(body, error.status);
34
+ }
35
+ function classify(error) {
36
+ if (isAppError(error)) return {
37
+ status: error.status,
38
+ code: error.code,
39
+ message: error.message,
40
+ details: error.details
41
+ };
42
+ if (error instanceof HTTPException && error.status < 500) return {
43
+ status: error.status,
44
+ code: HTTP_CODES[error.status] ?? "BAD_REQUEST",
45
+ message: error.message || "The request could not be processed."
46
+ };
47
+ return {
48
+ status: 500,
49
+ code: "INTERNAL_ERROR",
50
+ message: "An unexpected error occurred."
51
+ };
52
+ }
53
+ /** `app.onError` handler: renders every thrown error as the envelope. */
54
+ function onError(error, c) {
55
+ const rendered = classify(error);
56
+ if (rendered.status >= 500) c.var.logger.error("request.failed", { error });
57
+ else c.var.logger.info("request.rejected", {
58
+ errorCode: rendered.code,
59
+ status: rendered.status
60
+ });
61
+ return renderError(c, rendered);
62
+ }
63
+ /** `app.notFound` handler. */
64
+ function onNotFound(c) {
65
+ return renderError(c, {
66
+ status: 404,
67
+ code: "NOT_FOUND",
68
+ message: "The requested resource was not found."
69
+ });
70
+ }
71
+ // src/health.ts
72
+ const LivenessSchema = z.object({ status: z.literal("ok") }).openapi("Liveness");
73
+ const ReadinessSchema = z.object({
74
+ status: z.enum(["ok", "unavailable"]),
75
+ checks: z.record(z.string(), z.enum(["ok", "failed"]))
76
+ }).openapi("Readiness");
77
+ const liveness = createRoute({
78
+ method: "get",
79
+ path: "/health",
80
+ tags: ["health"],
81
+ summary: "Liveness: the Worker is running",
82
+ responses: { 200: {
83
+ description: "Alive",
84
+ content: { "application/json": { schema: LivenessSchema } }
85
+ } }
86
+ });
87
+ const readiness = createRoute({
88
+ method: "get",
89
+ path: "/health/ready",
90
+ tags: ["health"],
91
+ summary: "Readiness: every dependency check passes",
92
+ responses: {
93
+ 200: {
94
+ description: "Ready",
95
+ content: { "application/json": { schema: ReadinessSchema } }
96
+ },
97
+ 503: {
98
+ description: "Not ready",
99
+ content: { "application/json": { schema: ReadinessSchema } }
100
+ }
101
+ }
102
+ });
103
+ /** Registers `/health` and `/health/ready`. Check failures are logged, never returned. */
104
+ function registerHealth(app, checks) {
105
+ app.openapi(liveness, (c) => c.json({ status: "ok" }, 200));
106
+ app.openapi(readiness, async (c) => {
107
+ const entries = await Promise.all(Object.entries(checks).map(async ([name, check]) => {
108
+ try {
109
+ await check(c);
110
+ return [name, "ok"];
111
+ } catch (error) {
112
+ c.var.logger.error("readiness.check_failed", {
113
+ check: name,
114
+ error
115
+ });
116
+ return [name, "failed"];
117
+ }
118
+ }));
119
+ const results = Object.fromEntries(entries);
120
+ return entries.every(([, result]) => result === "ok") ? c.json({
121
+ status: "ok",
122
+ checks: results
123
+ }, 200) : c.json({
124
+ status: "unavailable",
125
+ checks: results
126
+ }, 503);
127
+ });
128
+ }
129
+ // src/openapi.ts
130
+ const METHODS = [
131
+ "get",
132
+ "put",
133
+ "post",
134
+ "delete",
135
+ "options",
136
+ "head",
137
+ "patch",
138
+ "trace"
139
+ ];
140
+ const ERROR_CONTENT = { "application/json": { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } };
141
+ /** Adds the error envelope responses every route can return: `422` when it validates input, and `default`. */
142
+ function withErrorResponses(doc) {
143
+ for (const item of Object.values(doc.paths ?? {})) for (const method of METHODS) {
144
+ const operation = item[method];
145
+ if (operation === void 0) continue;
146
+ const responses = operation.responses ??= {};
147
+ if (operation.requestBody !== void 0 || (operation.parameters?.length ?? 0) > 0) responses["422"] ??= {
148
+ description: "The request is invalid.",
149
+ content: ERROR_CONTENT
150
+ };
151
+ responses["default"] ??= {
152
+ description: "Error",
153
+ content: ERROR_CONTENT
154
+ };
155
+ }
156
+ return doc;
157
+ }
158
+ /** Serves the generated OpenAPI 3.1 document at `/openapi.json`. */
159
+ function registerOpenApi(app, info) {
160
+ app.openAPIRegistry.register("ErrorEnvelope", ErrorEnvelopeSchema);
161
+ let cached;
162
+ app.get("/openapi.json", (c) => {
163
+ cached ??= withErrorResponses(app.getOpenAPI31Document({
164
+ openapi: "3.1.0",
165
+ info
166
+ }));
167
+ return c.json(cached);
168
+ });
169
+ }
170
+ // src/origins.ts
171
+ const SAFE_METHODS = /* @__PURE__ */ new Set([
172
+ "GET",
173
+ "HEAD",
174
+ "OPTIONS"
175
+ ]);
176
+ function normalise(origin) {
177
+ try {
178
+ return new URL(origin).origin;
179
+ } catch {
180
+ return;
181
+ }
182
+ }
183
+ function trustedSet(option, c) {
184
+ const list = typeof option === "function" ? option(c.env) : option ?? [];
185
+ return new Set(list.map(normalise).filter((o) => o !== void 0));
186
+ }
187
+ /**
188
+ * CORS for trusted origins only, with credentials. Untrusted origins get no
189
+ * `Access-Control-Allow-Origin`, so browsers cannot read the response.
190
+ */
191
+ function corsForTrustedOrigins(option) {
192
+ return cors({
193
+ origin: (origin, c) => trustedSet(option, c).has(origin) ? origin : null,
194
+ credentials: true,
195
+ allowMethods: [
196
+ "GET",
197
+ "HEAD",
198
+ "POST",
199
+ "PUT",
200
+ "PATCH",
201
+ "DELETE",
202
+ "OPTIONS"
203
+ ],
204
+ allowHeaders: [
205
+ "Content-Type",
206
+ "Authorization",
207
+ "X-Correlation-Id",
208
+ "X-Captcha-Response"
209
+ ],
210
+ exposeHeaders: ["X-Request-Id", "X-Correlation-Id"],
211
+ maxAge: 600
212
+ });
213
+ }
214
+ /**
215
+ * Rejects state-changing requests whose `Origin` is neither this API's own
216
+ * origin nor trusted, with 403 `FORBIDDEN_ORIGIN` (CSRF defence). Requests
217
+ * without an `Origin` header (non-browser clients) pass.
218
+ */
219
+ function rejectUntrustedOrigins(option) {
220
+ return async (c, next) => {
221
+ const origin = c.req.header("origin");
222
+ if (SAFE_METHODS.has(c.req.method) || origin === void 0) return next();
223
+ if (!(origin === new URL(c.req.url).origin || trustedSet(option, c).has(origin))) {
224
+ c.var.logger.warn("request.untrusted_origin", {
225
+ origin,
226
+ method: c.req.method,
227
+ path: c.req.path
228
+ });
229
+ return renderError(c, {
230
+ status: 403,
231
+ code: "FORBIDDEN_ORIGIN",
232
+ message: "Requests from this origin are not allowed."
233
+ });
234
+ }
235
+ return next();
236
+ };
237
+ }
238
+ // src/request-context.ts
239
+ const CORRELATION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
240
+ /**
241
+ * The path as logged: a segment that follows a credential-named segment
242
+ * (e.g. the token in `/reset-password/<token>`) is replaced with `[REDACTED]`.
243
+ */
244
+ function redactPath(path) {
245
+ const segments = path.split("/");
246
+ return segments.map((segment, i) => i > 0 && isSensitiveKey(segments[i - 1] ?? "") ? REDACTED : segment).join("/");
247
+ }
248
+ /**
249
+ * Assigns request/correlation IDs, a per-request logger, and logs one
250
+ * `request.completed` record per request.
251
+ */
252
+ function requestContext(baseLogger) {
253
+ return async (c, next) => {
254
+ const started = Date.now();
255
+ const requestId = createId();
256
+ const incoming = c.req.header("x-correlation-id");
257
+ const correlationId = incoming !== void 0 && CORRELATION_ID.test(incoming) ? incoming : requestId;
258
+ const logger = baseLogger.child({
259
+ requestId,
260
+ correlationId
261
+ });
262
+ c.set("requestId", requestId);
263
+ c.set("correlationId", correlationId);
264
+ c.set("logger", logger);
265
+ await next();
266
+ c.res.headers.set("x-request-id", requestId);
267
+ c.res.headers.set("x-correlation-id", correlationId);
268
+ logger.info("request.completed", {
269
+ method: c.req.method,
270
+ path: redactPath(c.req.path),
271
+ status: c.res.status,
272
+ durationMs: Date.now() - started
273
+ });
274
+ };
275
+ }
276
+ // src/validation.ts
277
+ /**
278
+ * Default hook for every OpenAPI route: turns a failed Zod validation into a
279
+ * 422 `VALIDATION_FAILED` error rendered as the standard envelope, with
280
+ * `details: { target, issues }`.
281
+ */
282
+ function validationHook(result) {
283
+ if (result.success) return void 0;
284
+ const issues = result.error.issues.map((issue) => ({
285
+ path: issue.path.map((key) => typeof key === "number" ? key : String(key)),
286
+ code: issue.code,
287
+ message: issue.message
288
+ }));
289
+ throw validationFailed(issues, { target: result.target });
290
+ }
291
+ // src/router.ts
292
+ /**
293
+ * Creates a router for a versioned API section (e.g. `/v1`). Register routes
294
+ * with `.openapi(createRoute(...), handler)` so they are validated (invalid
295
+ * input becomes a 422 `VALIDATION_FAILED` envelope) and documented, then pass
296
+ * it to `createApi({ v1 })`.
297
+ */
298
+ function createRouter() {
299
+ return new OpenAPIHono({ defaultHook: validationHook });
300
+ }
301
+ // src/create-api.ts
302
+ /**
303
+ * Creates the Hono app for an Inbis Worker. Export it as the Worker's default
304
+ * export (`export default app`).
305
+ */
306
+ function createApi(options) {
307
+ const app = createRouter();
308
+ app.use(requestContext(options.logger ?? createLogger()));
309
+ app.onError(onError);
310
+ app.notFound(onNotFound);
311
+ app.use(rejectUntrustedOrigins(options.trustedOrigins));
312
+ app.use(corsForTrustedOrigins(options.trustedOrigins));
313
+ registerHealth(app, options.readiness ?? {});
314
+ registerOpenApi(app, options.info);
315
+ if (options.v1 !== void 0) app.route("/v1", options.v1);
316
+ return app;
317
+ }
318
+ export { ErrorEnvelopeSchema, createApi, createRouter };
319
+
320
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/errors.ts","../src/health.ts","../src/openapi.ts","../src/origins.ts","../src/request-context.ts","../src/validation.ts","../src/router.ts","../src/create-api.ts"],"sourcesContent":["import { z } from \"@hono/zod-openapi\";\nimport { isAppError } from \"@awsmbiz/inbis-core\";\nimport type { Context } from \"hono\";\nimport { HTTPException } from \"hono/http-exception\";\nimport type { ContentfulStatusCode } from \"hono/utils/http-status\";\nimport type { ApiEnv } from \"./types.js\";\n\n/** The body of every error response: `{ error: { code, message, requestId, details? } }`. */\nexport const ErrorEnvelopeSchema = z\n .object({\n error: z.object({\n code: z.string().openapi({ example: \"NOT_FOUND\" }),\n message: z.string(),\n requestId: z.string(),\n details: z.unknown().optional(),\n }),\n })\n .openapi(\"ErrorEnvelope\");\n\nexport type ErrorEnvelope = z.infer<typeof ErrorEnvelopeSchema>;\n\nconst HTTP_CODES: Partial<Record<number, string>> = {\n 400: \"BAD_REQUEST\",\n 401: \"UNAUTHORIZED\",\n 403: \"FORBIDDEN\",\n 404: \"NOT_FOUND\",\n 405: \"METHOD_NOT_ALLOWED\",\n 409: \"CONFLICT\",\n 413: \"PAYLOAD_TOO_LARGE\",\n 415: \"UNSUPPORTED_MEDIA_TYPE\",\n 422: \"VALIDATION_FAILED\",\n 429: \"RATE_LIMITED\",\n};\n\ninterface Rendered {\n status: ContentfulStatusCode;\n code: string;\n message: string;\n details?: unknown;\n}\n\n/** Renders the standard error envelope with the current request ID. */\nexport function renderError<B extends object>(c: Context<ApiEnv<B>>, error: Rendered): Response {\n const body: ErrorEnvelope = {\n error: {\n code: error.code,\n message: error.message,\n requestId: c.var.requestId,\n ...(error.details === undefined ? {} : { details: error.details }),\n },\n };\n return c.json(body, error.status);\n}\n\nfunction classify(error: unknown): Rendered {\n if (isAppError(error)) {\n return {\n status: error.status as ContentfulStatusCode,\n code: error.code,\n message: error.message,\n details: error.details,\n };\n }\n if (error instanceof HTTPException && error.status < 500) {\n return {\n status: error.status,\n code: HTTP_CODES[error.status] ?? \"BAD_REQUEST\",\n message: error.message || \"The request could not be processed.\",\n };\n }\n return { status: 500, code: \"INTERNAL_ERROR\", message: \"An unexpected error occurred.\" };\n}\n\n/** `app.onError` handler: renders every thrown error as the envelope. */\nexport function onError<B extends object>(error: Error, c: Context<ApiEnv<B>>): Response {\n const rendered = classify(error);\n if (rendered.status >= 500) {\n c.var.logger.error(\"request.failed\", { error });\n } else {\n c.var.logger.info(\"request.rejected\", { errorCode: rendered.code, status: rendered.status });\n }\n return renderError(c, rendered);\n}\n\n/** `app.notFound` handler. */\nexport function onNotFound<B extends object>(c: Context<ApiEnv<B>>): Response {\n return renderError(c, {\n status: 404,\n code: \"NOT_FOUND\",\n message: \"The requested resource was not found.\",\n });\n}\n","import { createRoute, z, type OpenAPIHono } from \"@hono/zod-openapi\";\nimport type { Context } from \"hono\";\nimport type { ApiEnv } from \"./types.js\";\n\n/** A readiness check: resolve when the dependency is usable, throw when it is not. */\nexport type ReadinessCheck<B extends object = object> = (c: Context<ApiEnv<B>>) => unknown;\n\nconst LivenessSchema = z.object({ status: z.literal(\"ok\") }).openapi(\"Liveness\");\n\nconst ReadinessSchema = z\n .object({\n status: z.enum([\"ok\", \"unavailable\"]),\n checks: z.record(z.string(), z.enum([\"ok\", \"failed\"])),\n })\n .openapi(\"Readiness\");\n\nconst liveness = createRoute({\n method: \"get\",\n path: \"/health\",\n tags: [\"health\"],\n summary: \"Liveness: the Worker is running\",\n responses: {\n 200: { description: \"Alive\", content: { \"application/json\": { schema: LivenessSchema } } },\n },\n});\n\nconst readiness = createRoute({\n method: \"get\",\n path: \"/health/ready\",\n tags: [\"health\"],\n summary: \"Readiness: every dependency check passes\",\n responses: {\n 200: { description: \"Ready\", content: { \"application/json\": { schema: ReadinessSchema } } },\n 503: { description: \"Not ready\", content: { \"application/json\": { schema: ReadinessSchema } } },\n },\n});\n\n/** Registers `/health` and `/health/ready`. Check failures are logged, never returned. */\nexport function registerHealth<B extends object>(\n app: OpenAPIHono<ApiEnv<B>>,\n checks: Record<string, ReadinessCheck<B>>,\n): void {\n app.openapi(liveness, (c) => c.json({ status: \"ok\" as const }, 200));\n\n app.openapi(readiness, async (c) => {\n const entries = await Promise.all(\n Object.entries(checks).map(async ([name, check]) => {\n try {\n await check(c);\n return [name, \"ok\"] as const;\n } catch (error) {\n c.var.logger.error(\"readiness.check_failed\", { check: name, error });\n return [name, \"failed\"] as const;\n }\n }),\n );\n const results = Object.fromEntries(entries);\n const ready = entries.every(([, result]) => result === \"ok\");\n return ready\n ? c.json({ status: \"ok\" as const, checks: results }, 200)\n : c.json({ status: \"unavailable\" as const, checks: results }, 503);\n });\n}\n","import type { OpenAPIHono } from \"@hono/zod-openapi\";\nimport { ErrorEnvelopeSchema } from \"./errors.js\";\nimport type { ApiEnv } from \"./types.js\";\n\ntype Document = ReturnType<OpenAPIHono[\"getOpenAPI31Document\"]>;\ninterface Operation { requestBody?: unknown; parameters?: unknown[]; responses?: Record<string, unknown> }\n\nconst METHODS = [\"get\", \"put\", \"post\", \"delete\", \"options\", \"head\", \"patch\", \"trace\"] as const;\nconst ERROR_CONTENT = {\n \"application/json\": { schema: { $ref: \"#/components/schemas/ErrorEnvelope\" } },\n};\n\n/** Adds the error envelope responses every route can return: `422` when it validates input, and `default`. */\nfunction withErrorResponses(doc: Document): Document {\n for (const item of Object.values(doc.paths ?? {})) {\n for (const method of METHODS) {\n const operation = (item as Partial<Record<string, Operation>>)[method];\n if (operation === undefined) continue;\n const responses = (operation.responses ??= {});\n const validates = operation.requestBody !== undefined || (operation.parameters?.length ?? 0) > 0;\n if (validates) {\n responses[\"422\"] ??= { description: \"The request is invalid.\", content: ERROR_CONTENT };\n }\n responses[\"default\"] ??= { description: \"Error\", content: ERROR_CONTENT };\n }\n }\n return doc;\n}\n\n/** Serves the generated OpenAPI 3.1 document at `/openapi.json`. */\nexport function registerOpenApi<B extends object>(\n app: OpenAPIHono<ApiEnv<B>>,\n info: { title: string; version: string; description?: string },\n): void {\n app.openAPIRegistry.register(\"ErrorEnvelope\", ErrorEnvelopeSchema);\n let cached: Document | undefined;\n app.get(\"/openapi.json\", (c) => {\n cached ??= withErrorResponses(app.getOpenAPI31Document({ openapi: \"3.1.0\", info }));\n return c.json(cached);\n });\n}\n","import type { Context, MiddlewareHandler } from \"hono\";\nimport { cors } from \"hono/cors\";\nimport { renderError } from \"./errors.js\";\nimport type { ApiEnv } from \"./types.js\";\n\nexport type TrustedOrigins<B extends object> = readonly string[] | ((env: B) => readonly string[]);\n\nconst SAFE_METHODS = new Set([\"GET\", \"HEAD\", \"OPTIONS\"]);\n\nfunction normalise(origin: string): string | undefined {\n try {\n return new URL(origin).origin;\n } catch {\n return undefined;\n }\n}\n\nfunction trustedSet<B extends object>(option: TrustedOrigins<B> | undefined, c: Context<ApiEnv<B>>) {\n const list = typeof option === \"function\" ? option(c.env) : (option ?? []);\n return new Set(list.map(normalise).filter((o): o is string => o !== undefined));\n}\n\n/**\n * CORS for trusted origins only, with credentials. Untrusted origins get no\n * `Access-Control-Allow-Origin`, so browsers cannot read the response.\n */\nexport function corsForTrustedOrigins<B extends object>(\n option: TrustedOrigins<B> | undefined,\n): MiddlewareHandler<ApiEnv<B>> {\n return cors({\n origin: (origin, c) => (trustedSet(option, c as Context<ApiEnv<B>>).has(origin) ? origin : null),\n credentials: true,\n allowMethods: [\"GET\", \"HEAD\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\", \"OPTIONS\"],\n allowHeaders: [\"Content-Type\", \"Authorization\", \"X-Correlation-Id\", \"X-Captcha-Response\"],\n exposeHeaders: [\"X-Request-Id\", \"X-Correlation-Id\"],\n maxAge: 600,\n });\n}\n\n/**\n * Rejects state-changing requests whose `Origin` is neither this API's own\n * origin nor trusted, with 403 `FORBIDDEN_ORIGIN` (CSRF defence). Requests\n * without an `Origin` header (non-browser clients) pass.\n */\nexport function rejectUntrustedOrigins<B extends object>(\n option: TrustedOrigins<B> | undefined,\n): MiddlewareHandler<ApiEnv<B>> {\n return async (c, next) => {\n const origin = c.req.header(\"origin\");\n if (SAFE_METHODS.has(c.req.method) || origin === undefined) return next();\n\n const allowed = origin === new URL(c.req.url).origin || trustedSet(option, c).has(origin);\n if (!allowed) {\n c.var.logger.warn(\"request.untrusted_origin\", { origin, method: c.req.method, path: c.req.path });\n return renderError(c, {\n status: 403,\n code: \"FORBIDDEN_ORIGIN\",\n message: \"Requests from this origin are not allowed.\",\n });\n }\n return next();\n };\n}\n","import { createId, isSensitiveKey, REDACTED, type Logger } from \"@awsmbiz/inbis-core\";\nimport type { MiddlewareHandler } from \"hono\";\nimport type { ApiEnv } from \"./types.js\";\n\nconst CORRELATION_ID = /^[A-Za-z0-9._:-]{1,128}$/;\n\n/**\n * The path as logged: a segment that follows a credential-named segment\n * (e.g. the token in `/reset-password/<token>`) is replaced with `[REDACTED]`.\n */\nexport function redactPath(path: string): string {\n const segments = path.split(\"/\");\n return segments.map((segment, i) => (i > 0 && isSensitiveKey(segments[i - 1] ?? \"\") ? REDACTED : segment)).join(\"/\");\n}\n\n/**\n * Assigns request/correlation IDs, a per-request logger, and logs one\n * `request.completed` record per request.\n */\nexport function requestContext<B extends object>(baseLogger: Logger): MiddlewareHandler<ApiEnv<B>> {\n return async (c, next) => {\n const started = Date.now();\n const requestId = createId();\n const incoming = c.req.header(\"x-correlation-id\");\n const correlationId = incoming !== undefined && CORRELATION_ID.test(incoming) ? incoming : requestId;\n const logger = baseLogger.child({ requestId, correlationId });\n\n c.set(\"requestId\", requestId);\n c.set(\"correlationId\", correlationId);\n c.set(\"logger\", logger);\n\n await next();\n\n c.res.headers.set(\"x-request-id\", requestId);\n c.res.headers.set(\"x-correlation-id\", correlationId);\n logger.info(\"request.completed\", {\n method: c.req.method,\n path: redactPath(c.req.path),\n status: c.res.status,\n durationMs: Date.now() - started,\n });\n };\n}\n","import type { z } from \"@hono/zod-openapi\";\nimport { validationFailed } from \"@awsmbiz/inbis-core\";\nimport type { ValidationTargets } from \"hono\";\n\n/** One failed check, safe to return to the caller (never includes the submitted value). */\nexport interface ValidationIssue {\n path: (string | number)[];\n code: string;\n message: string;\n}\n\n/**\n * Default hook for every OpenAPI route: turns a failed Zod validation into a\n * 422 `VALIDATION_FAILED` error rendered as the standard envelope, with\n * `details: { target, issues }`.\n */\nexport function validationHook(\n result: { target: keyof ValidationTargets } & ({ success: true } | { success: false; error: z.ZodError }),\n): undefined {\n if (result.success) return undefined;\n const issues: ValidationIssue[] = result.error.issues.map((issue) => ({\n path: issue.path.map((key) => (typeof key === \"number\" ? key : String(key))),\n code: issue.code,\n message: issue.message,\n }));\n throw validationFailed(issues, { target: result.target });\n}\n","import { OpenAPIHono } from \"@hono/zod-openapi\";\nimport type { ApiEnv } from \"./types.js\";\nimport { validationHook } from \"./validation.js\";\n\n/**\n * Creates a router for a versioned API section (e.g. `/v1`). Register routes\n * with `.openapi(createRoute(...), handler)` so they are validated (invalid\n * input becomes a 422 `VALIDATION_FAILED` envelope) and documented, then pass\n * it to `createApi({ v1 })`.\n */\nexport function createRouter<B extends object = object>(): OpenAPIHono<ApiEnv<B>> {\n return new OpenAPIHono<ApiEnv<B>>({ defaultHook: validationHook });\n}\n","import type { OpenAPIHono } from \"@hono/zod-openapi\";\nimport { createLogger, type Logger } from \"@awsmbiz/inbis-core\";\nimport type { Hono, Schema } from \"hono\";\nimport type { MergeSchemaPath } from \"hono/types\";\nimport { onError, onNotFound } from \"./errors.js\";\nimport { registerHealth, type ReadinessCheck } from \"./health.js\";\nimport { registerOpenApi } from \"./openapi.js\";\nimport { corsForTrustedOrigins, rejectUntrustedOrigins, type TrustedOrigins } from \"./origins.js\";\nimport { requestContext } from \"./request-context.js\";\nimport { createRouter } from \"./router.js\";\nimport type { ApiEnv } from \"./types.js\";\n\nexport interface CreateApiOptions<B extends object = object, S extends Schema = Schema> {\n /** OpenAPI `info` for `/openapi.json`. */\n info: { title: string; version: string; description?: string };\n /**\n * Browser origins allowed to call the API with credentials. Either a fixed\n * list or a function of the Worker `env` (e.g. read from a variable).\n */\n trustedOrigins?: TrustedOrigins<B>;\n /** Version 1 routes, mounted under `/v1`. Build with `createRouter()`. */\n v1?: Hono<ApiEnv<B>, S>;\n /**\n * Named dependency checks for `/health/ready` (e.g. `{ database: (c) => ... }`).\n * Each passes by returning and fails by throwing; errors are logged, not returned.\n */\n readiness?: Record<string, ReadinessCheck<B>>;\n /** Base logger. Defaults to a console JSON logger (Workers Logs). */\n logger?: Logger;\n}\n\n/**\n * Creates the Hono app for an Inbis Worker. Export it as the Worker's default\n * export (`export default app`).\n */\nexport function createApi<B extends object = object, S extends Schema = Schema>(\n options: CreateApiOptions<B, S>,\n): OpenAPIHono<ApiEnv<B>, MergeSchemaPath<S, \"/v1\">> {\n const app = createRouter<B>();\n app.use(requestContext(options.logger ?? createLogger()));\n app.onError(onError);\n app.notFound(onNotFound);\n app.use(rejectUntrustedOrigins(options.trustedOrigins));\n app.use(corsForTrustedOrigins(options.trustedOrigins));\n\n registerHealth(app, options.readiness ?? {});\n registerOpenApi(app, options.info);\n\n if (options.v1 !== undefined) app.route(\"/v1\", options.v1);\n return app;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,57 @@
1
1
  {
2
2
  "name": "@awsmbiz/inbis-api",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.0",
4
+ "description": "OpenAPI-enabled Hono app factory for Inbis Workers: request IDs, logging, error envelope, validation, CORS, health and OpenAPI.",
5
+ "license": "UNLICENSED",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/awsmbiz/inbis-platform.git",
9
+ "directory": "packages/api"
10
+ },
11
+ "publishConfig": {
12
+ "registry": "https://registry.npmjs.org/",
13
+ "access": "public"
14
+ },
15
+ "type": "module",
16
+ "sideEffects": false,
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "default": "./dist/index.js"
24
+ },
25
+ "./package.json": "./package.json"
26
+ },
27
+ "dependencies": {
28
+ "@awsmbiz/inbis-core": "0.2.0"
29
+ },
30
+ "peerDependencies": {
31
+ "@hono/zod-openapi": "^1.6.3",
32
+ "hono": "^4.13.14",
33
+ "zod": "^4.6.7"
34
+ },
35
+ "devDependencies": {
36
+ "@arethetypeswrong/cli": "0.18.5",
37
+ "@cloudflare/vitest-plugin": "1.4.11",
38
+ "@cloudflare/workers-types": "5.20261030.1",
39
+ "@hono/zod-openapi": "1.6.3",
40
+ "@awsmbiz/inbis-eslint-config": "0.2.0",
41
+ "@awsmbiz/inbis-tsconfig": "0.2.0",
42
+ "eslint": "10.14.0",
43
+ "hono": "4.13.14",
44
+ "publint": "0.3.25",
45
+ "tsdown": "0.23.4",
46
+ "typescript": "6.0.3",
47
+ "vitest": "5.0.5",
48
+ "zod": "4.6.7"
49
+ },
50
+ "scripts": {
51
+ "build": "tsdown",
52
+ "typecheck": "tsc",
53
+ "lint": "eslint . --max-warnings 0",
54
+ "test": "vitest run",
55
+ "check:exports": "publint --strict && attw --pack . --profile esm-only"
56
+ }
6
57
  }