@onetype/stack-api-kit 1.0.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/dist/index.js ADDED
@@ -0,0 +1,381 @@
1
+ import { database, limiter, createKernel } from './chunk-UCWOCNTI.js';
2
+ export { Answered, KernelFault, MigrationFault, Refusal, answer, createKernel, database, defineCommand, defineListener, defineParticipant, definePlugin, defineRoute, limiter, narrowing, outbox, same, schedule } from './chunk-UCWOCNTI.js';
3
+ import { Hono } from 'hono';
4
+
5
+ // src/plugins/http/internal/headers.ts
6
+ var always = {
7
+ "x-content-type-options": "nosniff",
8
+ "x-frame-options": "DENY",
9
+ "referrer-policy": "no-referrer",
10
+ "cache-control": "no-store",
11
+ "content-security-policy": "default-src 'none'; frame-ancestors 'none'"
12
+ };
13
+
14
+ // src/plugins/http/internal/origin.ts
15
+ function cors(allowing, origin) {
16
+ if (origin === void 0 || !allowing.origins.includes(origin)) {
17
+ return { vary: "Origin" };
18
+ }
19
+ return {
20
+ "access-control-allow-origin": origin,
21
+ "access-control-allow-credentials": "true",
22
+ "access-control-allow-methods": allowing.methods.join(", "),
23
+ "access-control-allow-headers": allowing.headers.join(", "),
24
+ "access-control-max-age": String(allowing.maxAge),
25
+ vary: "Origin"
26
+ };
27
+ }
28
+
29
+ // src/plugins/http/internal/input.ts
30
+ var UNSAFE = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
31
+ function input(carried) {
32
+ const merged = {};
33
+ if (carried.body !== null && typeof carried.body === "object" && !Array.isArray(carried.body)) {
34
+ for (const [key, value] of Object.entries(carried.body)) {
35
+ if (UNSAFE.has(key)) {
36
+ continue;
37
+ }
38
+ merged[key] = value;
39
+ }
40
+ }
41
+ for (const [key, values] of Object.entries(carried.query)) {
42
+ if (UNSAFE.has(key)) {
43
+ continue;
44
+ }
45
+ merged[key] = values.length === 1 ? values[0] : values;
46
+ }
47
+ for (const [key, value] of Object.entries(carried.params)) {
48
+ if (UNSAFE.has(key)) {
49
+ continue;
50
+ }
51
+ merged[key] = value;
52
+ }
53
+ return merged;
54
+ }
55
+
56
+ // src/plugins/http/internal/serve.ts
57
+ var CARRIES = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
58
+ function identifier(sent) {
59
+ return sent !== void 0 && /^[A-Za-z0-9_-]{1,64}$/.test(sent) ? sent : crypto.randomUUID();
60
+ }
61
+ function named(c, reads) {
62
+ const reading = {};
63
+ for (const name of reads) {
64
+ const value = c.req.header(name);
65
+ if (value !== void 0) {
66
+ reading[name] = value;
67
+ }
68
+ }
69
+ return reading;
70
+ }
71
+ function matching(answering, path) {
72
+ const direct = answering.get(path);
73
+ if (direct !== void 0) {
74
+ return direct;
75
+ }
76
+ const asked = path.split("/");
77
+ for (const [declared, methods] of answering) {
78
+ const parts = declared.split("/");
79
+ if (parts.length !== asked.length) {
80
+ continue;
81
+ }
82
+ if (parts.every((part, at) => part.startsWith(":") || part === asked[at])) {
83
+ return methods;
84
+ }
85
+ }
86
+ return void 0;
87
+ }
88
+ function serve(serving) {
89
+ const app = new Hono();
90
+ const bodyBytes = serving.bodyBytes ?? 1e6;
91
+ const allowing = {
92
+ origins: serving.origins ?? [],
93
+ methods: serving.methods ?? ["GET", "POST", "PUT", "PATCH", "DELETE"],
94
+ headers: serving.headers ?? ["content-type", "authorization"],
95
+ maxAge: serving.maxAge ?? 600
96
+ };
97
+ const requestIds = /* @__PURE__ */ new WeakMap();
98
+ app.use("*", async (c, next) => {
99
+ const requestId = identifier(c.req.header("x-request-id"));
100
+ requestIds.set(c.req.raw, requestId);
101
+ await next();
102
+ for (const [name, value] of Object.entries(always)) {
103
+ c.header(name, value);
104
+ }
105
+ for (const [name, value] of Object.entries(cors(allowing, c.req.header("origin")))) {
106
+ if (name === "access-control-allow-methods" && c.req.method === "OPTIONS" && c.res.headers.has(name)) {
107
+ continue;
108
+ }
109
+ c.header(name, value);
110
+ }
111
+ c.header("x-request-id", requestId);
112
+ });
113
+ const answering = /* @__PURE__ */ new Map();
114
+ app.get("/live", (c) => c.json({ live: true }));
115
+ app.get("/ready", (c) => {
116
+ const ready = serving.kernel.started();
117
+ return c.json({ ready }, ready ? 200 : 503);
118
+ });
119
+ for (const route of serving.kernel.routes()) {
120
+ const methods = answering.get(route.path) ?? /* @__PURE__ */ new Set();
121
+ methods.add(route.method);
122
+ answering.set(route.path, methods);
123
+ }
124
+ app.options("*", (c) => {
125
+ const methods = matching(answering, c.req.path);
126
+ if (methods === void 0) {
127
+ return c.json({ code: "NOT_FOUND", message: "No such route." }, 404);
128
+ }
129
+ c.header("access-control-allow-methods", [...methods].sort().join(", "));
130
+ return c.body(null, 204);
131
+ });
132
+ for (const route of serving.kernel.routes()) {
133
+ const path = route.path.replace(/:([a-z0-9-]+)/g, ":$1");
134
+ app.on(route.method, path, async (c) => {
135
+ const requestId = requestIds.get(c.req.raw) ?? "";
136
+ let caller;
137
+ try {
138
+ caller = await serving.identify?.(c);
139
+ } catch (cause) {
140
+ serving.log?.("warn", "identify threw", { requestId, error: cause instanceof Error ? cause.message : String(cause) });
141
+ return c.json({ code: "UNAUTHENTICATED", message: "This request needs to be signed in." }, 401);
142
+ }
143
+ let body;
144
+ if (CARRIES.has(route.method)) {
145
+ const claimed = Number(c.req.header("content-length") ?? "0");
146
+ if (Number.isFinite(claimed) && claimed > bodyBytes) {
147
+ return c.json({ code: "TOO_LARGE", message: "The request body is too large." }, 413);
148
+ }
149
+ const raw = new Uint8Array(await c.req.arrayBuffer());
150
+ if (raw.byteLength > bodyBytes) {
151
+ return c.json({ code: "TOO_LARGE", message: "The request body is too large." }, 413);
152
+ }
153
+ if (raw.byteLength > 0) {
154
+ try {
155
+ body = JSON.parse(new TextDecoder().decode(raw));
156
+ } catch {
157
+ return c.json({ code: "INVALID_JSON", message: "The request body is not valid JSON." }, 400);
158
+ }
159
+ }
160
+ }
161
+ const answer2 = await serving.kernel.handle({
162
+ method: route.method,
163
+ path: route.path,
164
+ input: input({ params: c.req.param(), query: c.req.queries(), body }),
165
+ caller,
166
+ headers: named(c, route.reads),
167
+ ...serving.from !== void 0 && { from: serving.from(c) }
168
+ });
169
+ if (answer2.status >= 500) {
170
+ serving.log?.("error", `${route.method} ${route.path} failed`, { requestId, plugin: route.plugin });
171
+ }
172
+ for (const [name, value] of Object.entries(answer2.headers ?? {})) {
173
+ c.header(name, value);
174
+ }
175
+ return c.json(answer2.body, answer2.status);
176
+ });
177
+ }
178
+ app.notFound((c) => {
179
+ return c.json({ code: "NOT_FOUND", message: "No such route." }, 404);
180
+ });
181
+ app.onError((cause, c) => {
182
+ serving.log?.("error", "the server threw outside a route", {
183
+ requestId: requestIds.get(c.req.raw) ?? "",
184
+ error: cause instanceof Error ? cause.message : String(cause),
185
+ ...cause instanceof Error && cause.stack !== void 0 && { stack: cause.stack }
186
+ });
187
+ return c.json({ code: "INTERNAL", message: "The request could not be completed." }, 500);
188
+ });
189
+ return app;
190
+ }
191
+
192
+ // src/plugins/outbound/internal/dial.ts
193
+ var OutboundFault = class extends Error {
194
+ code;
195
+ status;
196
+ constructor(code, message, status, cause) {
197
+ super(message, cause === void 0 ? void 0 : { cause });
198
+ this.name = "OutboundFault";
199
+ this.code = code;
200
+ this.status = status;
201
+ }
202
+ };
203
+ function binary(body) {
204
+ return body instanceof Uint8Array || body instanceof ArrayBuffer || body instanceof Blob || body instanceof FormData || body instanceof URLSearchParams;
205
+ }
206
+ function sendable(body) {
207
+ return binary(body) ? body : JSON.stringify(body);
208
+ }
209
+ function dial(dialing = {}) {
210
+ const timeoutMs = dialing.timeoutMs ?? 1e4;
211
+ const maxBytes = dialing.maxBytes ?? 5e6;
212
+ return async (call) => {
213
+ const stopper = new AbortController();
214
+ const timer = setTimeout(() => stopper.abort(), timeoutMs);
215
+ const cancel = () => {
216
+ stopper.abort();
217
+ };
218
+ call.signal?.addEventListener("abort", cancel);
219
+ try {
220
+ const response = await fetch(call.url, {
221
+ method: call.method,
222
+ signal: stopper.signal,
223
+ // A redirect is how a permitted host hands a request to one
224
+ // that was never declared, so the whitelist has to hold here
225
+ // too: the kernel only saw the first url.
226
+ redirect: "error",
227
+ headers: {
228
+ accept: "application/json",
229
+ ...call.body !== void 0 && !binary(call.body) && { "content-type": "application/json" },
230
+ ...dialing.headers?.(),
231
+ ...call.headers
232
+ },
233
+ ...call.body !== void 0 && { body: sendable(call.body) }
234
+ });
235
+ const text = await read(response, maxBytes);
236
+ if (!response.ok) {
237
+ throw new OutboundFault("STATUS", `The call was refused with status ${response.status}.`, response.status);
238
+ }
239
+ if (text === "") {
240
+ return void 0;
241
+ }
242
+ try {
243
+ return JSON.parse(text);
244
+ } catch (cause) {
245
+ throw new OutboundFault("MALFORMED", "The answer was not valid JSON.", response.status, cause);
246
+ }
247
+ } catch (cause) {
248
+ throw shape(cause, call, stopper, timeoutMs);
249
+ } finally {
250
+ clearTimeout(timer);
251
+ call.signal?.removeEventListener("abort", cancel);
252
+ }
253
+ };
254
+ }
255
+ async function read(response, maxBytes) {
256
+ const reader = response.body?.getReader();
257
+ if (reader === void 0) {
258
+ return "";
259
+ }
260
+ const chunks = [];
261
+ let size = 0;
262
+ for (; ; ) {
263
+ const { done, value } = await reader.read();
264
+ if (done) {
265
+ break;
266
+ }
267
+ size += value.length;
268
+ if (size > maxBytes) {
269
+ await reader.cancel();
270
+ throw new OutboundFault("TOO_LARGE", `The answer went past ${maxBytes} bytes.`);
271
+ }
272
+ chunks.push(value);
273
+ }
274
+ return new TextDecoder().decode(join(chunks, size));
275
+ }
276
+ function join(chunks, size) {
277
+ const whole = new Uint8Array(size);
278
+ let at = 0;
279
+ for (const chunk of chunks) {
280
+ whole.set(chunk, at);
281
+ at += chunk.length;
282
+ }
283
+ return whole;
284
+ }
285
+ function shape(cause, call, stopper, timeoutMs) {
286
+ if (cause instanceof OutboundFault) {
287
+ return cause;
288
+ }
289
+ if (call.signal?.aborted === true) {
290
+ return new OutboundFault("ABORTED", "The call was cancelled by the caller.", void 0, cause);
291
+ }
292
+ if (stopper.signal.aborted) {
293
+ return new OutboundFault("TIMEOUT", `The call did not answer within ${timeoutMs}ms.`, void 0, cause);
294
+ }
295
+ return new OutboundFault("NETWORK", "The call could not reach the host.", void 0, cause);
296
+ }
297
+
298
+ // src/plugins/mount/internal/discover.ts
299
+ function discover(found) {
300
+ return Object.entries(found).map(([path, module]) => {
301
+ if (module.default === void 0) {
302
+ throw new Error(`${path} must default-export a definePlugin(...) result.`);
303
+ }
304
+ return module.default;
305
+ }).sort((first, second) => first.name.localeCompare(second.name));
306
+ }
307
+
308
+ // src/plugins/mount/internal/start.ts
309
+ async function start(starting) {
310
+ const log = starting.log;
311
+ const given = starting.database;
312
+ const ready = typeof given.tx === "function";
313
+ const store = ready ? given : database({
314
+ ...given,
315
+ tables: Object.fromEntries(
316
+ starting.plugins.filter((plugin) => plugin.definition.tables !== void 0).map((plugin) => [plugin.name, plugin.definition.tables])
317
+ )
318
+ });
319
+ const migrations = starting.plugins.filter((plugin) => plugin.definition.migrations !== void 0).map((plugin) => ({ plugin: plugin.name, from: plugin.definition.migrations }));
320
+ if (typeof store.migrate !== "function" || typeof store.close !== "function") {
321
+ throw new TypeError(
322
+ "The store given to start() answers tx and of, but not migrate and close. start() owns the whole lifetime of a database, so it needs both: migrate before any plugin runs, close after every one has stopped. Add them, or build the kernel yourself with createKernel, which asks only for tx and of."
323
+ );
324
+ }
325
+ const ran = store.migrate(migrations);
326
+ if (ran.length > 0) {
327
+ log?.info("migrations applied", { count: ran.length, steps: ran.map((step) => `${step.plugin}/${step.name}`) });
328
+ }
329
+ const counting = starting.budget === void 0 ? limiter() : void 0;
330
+ const budget = starting.budget ?? counting ?? limiter();
331
+ const sweeping = counting === void 0 ? void 0 : setInterval(() => void counting.sweep(), 6e4);
332
+ sweeping?.unref?.();
333
+ const keeping = starting.outbox === true ? store.outbox?.() : void 0;
334
+ const later = starting.schedule === true ? store.schedule?.() : void 0;
335
+ const scoping = starting.plugins.some((plugin) => plugin.definition.scope !== void 0);
336
+ const kernel = createKernel({
337
+ plugins: starting.plugins,
338
+ db: store,
339
+ ...keeping !== void 0 && { outbox: keeping },
340
+ ...later !== void 0 && { schedule: later },
341
+ ...scoping && store.narrowing !== void 0 && { narrow: store.narrowing() },
342
+ budget,
343
+ dial: typeof starting.outbound === "function" ? starting.outbound : dial(starting.outbound ?? {}),
344
+ ...starting.config !== void 0 && { config: starting.config },
345
+ ...log !== void 0 && {
346
+ log: (level, plugin, line, about) => {
347
+ log[level](`${plugin}: ${line}`, about);
348
+ }
349
+ }
350
+ });
351
+ await kernel.start();
352
+ log?.info("kernel started", { plugins: starting.plugins.length, routes: kernel.routes().length });
353
+ const identify = starting.identify?.(kernel);
354
+ const app = serve({
355
+ kernel,
356
+ ...identify !== void 0 && { identify },
357
+ ...starting.http ?? {},
358
+ ...log !== void 0 && {
359
+ log: (level, line, about) => {
360
+ log[level](line, about);
361
+ }
362
+ }
363
+ });
364
+ return {
365
+ kernel,
366
+ store,
367
+ app,
368
+ fetch: app.fetch,
369
+ stop: async () => {
370
+ if (sweeping !== void 0) {
371
+ clearInterval(sweeping);
372
+ }
373
+ await kernel.stop();
374
+ store.close();
375
+ }
376
+ };
377
+ }
378
+
379
+ export { OutboundFault, always, dial, discover, identifier, serve, start };
380
+ //# sourceMappingURL=index.js.map
381
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/plugins/http/internal/headers.ts","../src/plugins/http/internal/origin.ts","../src/plugins/http/internal/input.ts","../src/plugins/http/internal/serve.ts","../src/plugins/outbound/internal/dial.ts","../src/plugins/mount/internal/discover.ts","../src/plugins/mount/internal/start.ts"],"names":["answer"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * What every response carries, whatever it answers.\n *\n * An API serves data, not documents, so these are the ones that still mean\n * something: the browser protections that matter for a page are set by\n * whatever serves the page.\n *\n * - nosniff stops a browser guessing a content type it was already told.\n * - DENY in frame-options and frame-ancestors 'none' keeps an error page out\n * of someone else's iframe.\n * - no-store keeps an authenticated answer out of a shared cache. An API\n * response is per-caller, and a cache that kept one would hand it to the\n * next.\n * - no-referrer keeps a path with an id in it from reaching another origin.\n */\nexport const always: Readonly<Record<string, string>> = {\n \"x-content-type-options\": \"nosniff\",\n \"x-frame-options\": \"DENY\",\n \"referrer-policy\": \"no-referrer\",\n \"cache-control\": \"no-store\",\n \"content-security-policy\": \"default-src 'none'; frame-ancestors 'none'\",\n};\n","/**\n * Which cross-origin callers are answered, and what they are allowed.\n *\n * A whitelist rather than a reflection: echoing back whatever Origin arrived,\n * which is what `*` with credentials amounts to, is every site the caller has\n * open being allowed to spend their session.\n */\nexport type Allowing = {\n origins: readonly string[];\n methods: readonly string[];\n headers: readonly string[];\n maxAge: number;\n};\n\n/**\n * The CORS headers for one request.\n *\n * `vary` is set whether or not the origin is allowed: the answer differs by\n * Origin either way, and a cache keyed without it would hand one site's\n * allowance to another.\n */\nexport function cors(allowing: Allowing, origin: string | undefined): Readonly<Record<string, string>>\n{\n if (origin === undefined || !allowing.origins.includes(origin))\n {\n return { vary: \"Origin\" };\n }\n\n return {\n \"access-control-allow-origin\": origin,\n \"access-control-allow-credentials\": \"true\",\n \"access-control-allow-methods\": allowing.methods.join(\", \"),\n \"access-control-allow-headers\": allowing.headers.join(\", \"),\n \"access-control-max-age\": String(allowing.maxAge),\n vary: \"Origin\",\n };\n}\n","/** What one request carries, before any schema has looked at it. */\nexport type Carried = {\n params: Readonly<Record<string, string>>;\n query: Readonly<Record<string, string[]>>;\n body: unknown;\n};\n\nconst UNSAFE: ReadonlySet<string> = new Set([\"__proto__\", \"constructor\", \"prototype\"]);\n\n/**\n * One object for a route's input schema to judge.\n *\n * Path parameters win over query, and query over body, because a path\n * parameter is the one part of a request a router already matched: a body\n * claiming a different `id` than the path it was sent to is either confused\n * or deliberate, and either way the path is the request.\n *\n * Nothing is coerced here. A schema saying a page is a number is the thing\n * that turns \"2\" into 2, and it is the only thing that should: a converter\n * ahead of the schema decides what \"0x10\" or \"\" mean before the schema that\n * owns the field gets a say.\n */\nexport function input(carried: Carried): Record<string, unknown>\n{\n const merged: Record<string, unknown> = {};\n\n if (carried.body !== null && typeof carried.body === \"object\" && !Array.isArray(carried.body))\n {\n for (const [key, value] of Object.entries(carried.body as Record<string, unknown>))\n {\n // Prototype pollution: a body naming __proto__ reaches\n // Object.prototype through a plain assignment, and every object\n // in the process changes shape.\n if (UNSAFE.has(key))\n {\n continue;\n }\n\n merged[key] = value;\n }\n }\n\n // A query parameter arrives as a list, because a caller may repeat it.\n // One value is handed over as that value: a schema saying `z.string()`\n // could otherwise never match `?q=hello`, which made every query\n // parameter unusable unless its schema expected an array.\n for (const [key, values] of Object.entries(carried.query))\n {\n if (UNSAFE.has(key))\n {\n continue;\n }\n\n merged[key] = values.length === 1 ? values[0] : values;\n }\n\n for (const [key, value] of Object.entries(carried.params))\n {\n if (UNSAFE.has(key))\n {\n continue;\n }\n\n merged[key] = value;\n }\n\n return merged;\n}\n","import { Hono } from \"hono\";\nimport type { Context as HonoContext } from \"hono\";\n\nimport type { Caller, Kernel, Method } from \"../../kernel/api\";\nimport { always } from \"./headers\";\nimport { cors, type Allowing } from \"./origin\";\nimport { input } from \"./input\";\n\n/** What serving needs to know. */\nexport type Serving = {\n kernel: Kernel;\n\n /**\n * Who is calling. The project owns this entirely: a cookie, a bearer\n * token, a header, whatever it decided a session is.\n *\n * Throwing answers 401. Returning undefined is an anonymous caller, which\n * only a public route accepts.\n */\n identify?: ((c: HonoContext) => Caller | undefined | Promise<Caller | undefined>) | undefined;\n\n /**\n * What to count an anonymous caller by, for a rate limit: an address, an\n * api key, whatever the deployment can trust. Reading a forwarded header\n * blindly lets anyone spend anyone's budget, so the project decides.\n */\n from?: ((c: HonoContext) => string) | undefined;\n\n origins?: readonly string[];\n methods?: readonly string[];\n headers?: readonly string[];\n maxAge?: number;\n\n /** The largest body accepted, before it is parsed. */\n bodyBytes?: number;\n\n /** Where a line goes. */\n log?: ((level: \"info\" | \"warn\" | \"error\", line: string, about?: Readonly<Record<string, unknown>>) => void) | undefined;\n};\n\n/** Which methods a caller may send a body with, and we will read one from. */\nconst CARRIES: ReadonlySet<string> = new Set([\"POST\", \"PUT\", \"PATCH\", \"DELETE\"]);\n\n// Kept only when it is safe to write down: an id carrying a newline is how\n// one request writes a second line into the log and calls it whatever it\n// likes.\nexport function identifier(sent: string | undefined): string\n{\n return sent !== undefined && /^[A-Za-z0-9_-]{1,64}$/.test(sent) ? sent : crypto.randomUUID();\n}\n\n/**\n * Builds the Hono app the kernel's routes are mounted on.\n *\n * Everything crossing into the process crosses here, which is why the limits\n * live here: a plugin that had to remember to bound a body is one that will\n * forget once.\n */\n/** The headers a route declared, read off the request in lowercase. */\nfunction named(c: HonoContext, reads: readonly string[]): Readonly<Record<string, string>>\n{\n const reading: Record<string, string> = {};\n\n for (const name of reads)\n {\n const value = c.req.header(name);\n\n if (value !== undefined)\n {\n reading[name] = value;\n }\n }\n\n return reading;\n}\n\n/** Which methods a declared path answers, matching `:param` segments. */\nfunction matching(answering: ReadonlyMap<string, Set<string>>, path: string): Set<string> | undefined\n{\n const direct = answering.get(path);\n\n if (direct !== undefined)\n {\n return direct;\n }\n\n const asked = path.split(\"/\");\n\n for (const [declared, methods] of answering)\n {\n const parts = declared.split(\"/\");\n\n if (parts.length !== asked.length)\n {\n continue;\n }\n\n if (parts.every((part, at) => part.startsWith(\":\") || part === asked[at]))\n {\n return methods;\n }\n }\n\n return undefined;\n}\n\nexport function serve(serving: Serving): Hono\n{\n const app = new Hono();\n const bodyBytes = serving.bodyBytes ?? 1_000_000;\n const allowing: Allowing = {\n origins: serving.origins ?? [],\n methods: serving.methods ?? [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"],\n headers: serving.headers ?? [\"content-type\", \"authorization\"],\n maxAge: serving.maxAge ?? 600,\n };\n\n const requestIds = new WeakMap<Request, string>();\n\n app.use(\"*\", async (c, next) =>\n {\n const requestId = identifier(c.req.header(\"x-request-id\"));\n\n requestIds.set(c.req.raw, requestId);\n\n await next();\n\n for (const [name, value] of Object.entries(always))\n {\n c.header(name, value);\n }\n\n for (const [name, value] of Object.entries(cors(allowing, c.req.header(\"origin\"))))\n {\n // A preflight already said which methods its own path answers.\n if (name === \"access-control-allow-methods\" && c.req.method === \"OPTIONS\" && c.res.headers.has(name))\n {\n continue;\n }\n\n c.header(name, value);\n }\n\n c.header(\"x-request-id\", requestId);\n });\n\n // A preflight is answered only for a path some route declared, and only\n // for the methods that path actually answers: approving one for a route\n // that does not exist tells a browser it may send what nothing will take,\n // and maps out the surface for anyone asking.\n const answering = new Map<string, Set<string>>();\n\n // Two questions a deployment asks, and they are not the same one. Live\n // says the process is up, which is what decides a restart. Ready says the\n // kernel started, its migrations ran and its plugins are up, which is\n // what decides whether traffic may arrive. A process that answers live\n // but not ready is one that should be left alone to finish starting,\n // never killed and never sent a request.\n app.get(\"/live\", (c) => c.json({ live: true }));\n\n app.get(\"/ready\", (c) =>\n {\n const ready = serving.kernel.started();\n\n return c.json({ ready }, ready ? 200 : 503);\n });\n\n for (const route of serving.kernel.routes())\n {\n const methods = answering.get(route.path) ?? new Set<string>();\n\n methods.add(route.method);\n answering.set(route.path, methods);\n }\n\n app.options(\"*\", (c) =>\n {\n const methods = matching(answering, c.req.path);\n\n if (methods === undefined)\n {\n return c.json({ code: \"NOT_FOUND\", message: \"No such route.\" }, 404);\n }\n\n c.header(\"access-control-allow-methods\", [...methods].sort().join(\", \"));\n\n return c.body(null, 204);\n });\n\n for (const route of serving.kernel.routes())\n {\n const path = route.path.replace(/:([a-z0-9-]+)/g, \":$1\");\n\n app.on(route.method, path, async (c) =>\n {\n const requestId = requestIds.get(c.req.raw) ?? \"\";\n\n let caller: Caller | undefined;\n\n try\n {\n caller = await serving.identify?.(c);\n }\n catch (cause)\n {\n // Whatever went wrong reading a session, the caller is not\n // signed in. A 500 here would turn an expired token into an\n // outage, and tell whoever sent it that it was interesting.\n serving.log?.(\"warn\", \"identify threw\", { requestId, error: cause instanceof Error ? cause.message : String(cause) });\n\n return c.json({ code: \"UNAUTHENTICATED\", message: \"This request needs to be signed in.\" }, 401);\n }\n\n let body: unknown;\n\n if (CARRIES.has(route.method))\n {\n const claimed = Number(c.req.header(\"content-length\") ?? \"0\");\n\n if (Number.isFinite(claimed) && claimed > bodyBytes)\n {\n return c.json({ code: \"TOO_LARGE\", message: \"The request body is too large.\" }, 413);\n }\n\n // Read as bytes, and counted as bytes. `content-length` is\n // what the caller claimed and a chunked request sends none;\n // measuring the decoded string instead counts UTF-16 units,\n // so a body of Japanese passes a limit three times smaller\n // than what actually arrived.\n const raw = new Uint8Array(await c.req.arrayBuffer());\n\n if (raw.byteLength > bodyBytes)\n {\n return c.json({ code: \"TOO_LARGE\", message: \"The request body is too large.\" }, 413);\n }\n\n if (raw.byteLength > 0)\n {\n try\n {\n body = JSON.parse(new TextDecoder().decode(raw));\n }\n catch\n {\n return c.json({ code: \"INVALID_JSON\", message: \"The request body is not valid JSON.\" }, 400);\n }\n }\n }\n\n const answer = await serving.kernel.handle({\n method: route.method as Method,\n path: route.path,\n input: input({ params: c.req.param(), query: c.req.queries() as Record<string, string[]>, body }),\n caller,\n headers: named(c, route.reads),\n ...(serving.from !== undefined && { from: serving.from(c) }),\n });\n\n if (answer.status >= 500)\n {\n serving.log?.(\"error\", `${route.method} ${route.path} failed`, { requestId, plugin: route.plugin });\n }\n\n for (const [name, value] of Object.entries(answer.headers ?? {}))\n {\n c.header(name, value);\n }\n\n return c.json(answer.body as Record<string, unknown>, answer.status as 200);\n });\n }\n\n app.notFound((c) =>\n {\n return c.json({ code: \"NOT_FOUND\", message: \"No such route.\" }, 404);\n });\n\n app.onError((cause, c) =>\n {\n serving.log?.(\"error\", \"the server threw outside a route\", {\n requestId: requestIds.get(c.req.raw) ?? \"\",\n error: cause instanceof Error ? cause.message : String(cause),\n ...(cause instanceof Error && cause.stack !== undefined && { stack: cause.stack }),\n });\n\n return c.json({ code: \"INTERNAL\", message: \"The request could not be completed.\" }, 500);\n });\n\n return app;\n}\n","import type { Outbound } from \"../../kernel/api\";\n\nexport type Dialing = {\n timeoutMs?: number;\n maxBytes?: number;\n headers?: (() => Readonly<Record<string, string>>) | undefined;\n};\n\nexport class OutboundFault extends Error\n{\n readonly code: \"TIMEOUT\" | \"ABORTED\" | \"NETWORK\" | \"TOO_LARGE\" | \"MALFORMED\" | \"STATUS\";\n\n readonly status: number | undefined;\n\n constructor(code: OutboundFault[\"code\"], message: string, status?: number, cause?: unknown)\n {\n super(message, cause === undefined ? undefined : { cause });\n\n this.name = \"OutboundFault\";\n this.code = code;\n this.status = status;\n }\n}\n\n/**\n * Whether a body is already bytes.\n *\n * JSON.stringify turns a Uint8Array into {\"0\":1,\"1\":2}, which is neither what\n * the caller wrote nor anything a binary protocol accepts, and it does it\n * silently. A protobuf frame reaches the wire as a frame.\n */\nfunction binary(body: unknown): body is Uint8Array | ArrayBuffer | Blob | FormData | URLSearchParams\n{\n return body instanceof Uint8Array\n || body instanceof ArrayBuffer\n || body instanceof Blob\n || body instanceof FormData\n || body instanceof URLSearchParams;\n}\n\n/** What goes on the wire: bytes as they are, everything else as JSON. */\nfunction sendable(body: unknown): Uint8Array | ArrayBuffer | Blob | FormData | URLSearchParams | string\n{\n return binary(body) ? body : JSON.stringify(body);\n}\n\n// The kernel checks the host against what the plugin declared; this only\n// carries the call out, and bounds what comes back.\nexport function dial(dialing: Dialing = {})\n{\n const timeoutMs = dialing.timeoutMs ?? 10_000;\n const maxBytes = dialing.maxBytes ?? 5_000_000;\n\n return async (call: Outbound): Promise<unknown> =>\n {\n const stopper = new AbortController();\n const timer = setTimeout(() => stopper.abort(), timeoutMs);\n const cancel = (): void =>\n {\n stopper.abort();\n };\n\n call.signal?.addEventListener(\"abort\", cancel);\n\n try\n {\n const response = await fetch(call.url, {\n method: call.method,\n signal: stopper.signal,\n\n // A redirect is how a permitted host hands a request to one\n // that was never declared, so the whitelist has to hold here\n // too: the kernel only saw the first url.\n redirect: \"error\",\n\n headers: {\n accept: \"application/json\",\n ...(call.body !== undefined && !binary(call.body) && { \"content-type\": \"application/json\" }),\n ...dialing.headers?.(),\n ...call.headers,\n },\n ...(call.body !== undefined && { body: sendable(call.body) }),\n });\n\n const text = await read(response, maxBytes);\n\n if (!response.ok)\n {\n throw new OutboundFault(\"STATUS\", `The call was refused with status ${response.status}.`, response.status);\n }\n\n if (text === \"\")\n {\n return undefined;\n }\n\n try\n {\n return JSON.parse(text);\n }\n catch (cause)\n {\n throw new OutboundFault(\"MALFORMED\", \"The answer was not valid JSON.\", response.status, cause);\n }\n }\n catch (cause)\n {\n throw shape(cause, call, stopper, timeoutMs);\n }\n finally\n {\n clearTimeout(timer);\n call.signal?.removeEventListener(\"abort\", cancel);\n }\n };\n}\n\n// Read in chunks rather than at once: content-length is what the other side\n// claimed, and a body that keeps arriving is how one call takes the process\n// down.\nasync function read(response: Response, maxBytes: number): Promise<string>\n{\n const reader = response.body?.getReader();\n\n if (reader === undefined)\n {\n return \"\";\n }\n\n const chunks: Uint8Array[] = [];\n\n let size = 0;\n\n for (;;)\n {\n const { done, value } = await reader.read();\n\n if (done)\n {\n break;\n }\n\n size += value.length;\n\n if (size > maxBytes)\n {\n await reader.cancel();\n\n throw new OutboundFault(\"TOO_LARGE\", `The answer went past ${maxBytes} bytes.`);\n }\n\n chunks.push(value);\n }\n\n return new TextDecoder().decode(join(chunks, size));\n}\n\nfunction join(chunks: readonly Uint8Array[], size: number): Uint8Array\n{\n const whole = new Uint8Array(size);\n\n let at = 0;\n\n for (const chunk of chunks)\n {\n whole.set(chunk, at);\n at += chunk.length;\n }\n\n return whole;\n}\n\n// The caller's own abort and our timeout both surface as one AbortError, and\n// they are not the same event.\nfunction shape(cause: unknown, call: Outbound, stopper: AbortController, timeoutMs: number): unknown\n{\n if (cause instanceof OutboundFault)\n {\n return cause;\n }\n\n if (call.signal?.aborted === true)\n {\n return new OutboundFault(\"ABORTED\", \"The call was cancelled by the caller.\", undefined, cause);\n }\n\n if (stopper.signal.aborted)\n {\n return new OutboundFault(\"TIMEOUT\", `The call did not answer within ${timeoutMs}ms.`, undefined, cause);\n }\n\n return new OutboundFault(\"NETWORK\", \"The call could not reach the host.\", undefined, cause);\n}\n","import type { Plugin } from \"../../kernel/api\";\n\nexport type Found = Readonly<Record<string, { default?: Plugin }>>;\n\n// Discovery from the filesystem with no list to maintain: adding a plugin is\n// a folder, and forgetting to register it is not a failure mode. Sorted so\n// one set is always one order, whatever the loader walked first.\nexport function discover(found: Found): Plugin[]\n{\n return Object.entries(found)\n .map(([path, module]) =>\n {\n if (module.default === undefined)\n {\n throw new Error(`${path} must default-export a definePlugin(...) result.`);\n }\n\n return module.default;\n })\n .sort((first, second) => first.name.localeCompare(second.name));\n}\n","import { database } from \"../../database/api\";\nimport { limiter } from \"../../guard/api\";\nimport { createKernel } from \"../../kernel/api\";\nimport { dial } from \"../../outbound/api\";\nimport { serve } from \"../../http/api\";\nimport type { Opening, Store } from \"../../database/api\";\nimport type { Started, Starting } from \"../api\";\n\n// The order is the point: the database opens and migrates before any plugin\n// runs, the kernel validates before any plugin acts, and the server is built\n// last, from routes that are already known to be sound.\nexport async function start(starting: Starting): Promise<Started>\n{\n const log = starting.log;\n\n // A store the project built, or one opened here from a path. Told apart\n // by what it answers to, not by a flag: a Store has methods, an Opening\n // has a file.\n const given = starting.database;\n const ready = typeof (given as { tx?: unknown }).tx === \"function\";\n\n const store = ready\n ? given as Store\n : database({\n ...given as Opening,\n tables: Object.fromEntries(\n starting.plugins\n .filter((plugin) => plugin.definition.tables !== undefined)\n .map((plugin) => [plugin.name, plugin.definition.tables as Readonly<Record<string, unknown>>]),\n ),\n });\n\n const migrations = starting.plugins\n .filter((plugin) => plugin.definition.migrations !== undefined)\n .map((plugin) => ({ plugin: plugin.name, from: plugin.definition.migrations as string }));\n\n if (typeof store.migrate !== \"function\" || typeof store.close !== \"function\")\n {\n throw new TypeError(\n \"The store given to start() answers tx and of, but not migrate and close. start() owns the whole lifetime of a database, so it needs both: migrate before any plugin runs, close after every one has stopped. Add them, or build the kernel yourself with createKernel, which asks only for tx and of.\",\n );\n }\n\n const ran = store.migrate(migrations);\n\n if (ran.length > 0)\n {\n log?.info(\"migrations applied\", { count: ran.length, steps: ran.map((step) => `${step.plugin}/${step.name}`) });\n }\n\n // Every route's declared limit is enforced by this one, so a plugin\n // cannot turn off its own: it never holds it.\n // A budget the project passed counts wherever it likes, which is what a\n // deployment of more than one process needs. The kit's own counts here,\n // and only what it counted can it sweep.\n const counting = starting.budget === undefined ? limiter() : undefined;\n const budget = starting.budget ?? counting ?? limiter();\n\n const sweeping = counting === undefined ? undefined : setInterval(() => void counting.sweep(), 60_000);\n\n sweeping?.unref?.();\n\n // Reached through the store's own connection, so a kept event and the\n // work it announces are written by one transaction.\n const keeping = starting.outbox === true ? store.outbox?.() : undefined;\n const later = starting.schedule === true ? store.schedule?.() : undefined;\n\n // Given whenever any plugin declares a scope: the kernel refuses to\n // narrow without it, and a plugin declaring one and finding nothing to\n // narrow by would be a scope that does not scope.\n const scoping = starting.plugins.some((plugin) => plugin.definition.scope !== undefined);\n\n const kernel = createKernel({\n plugins: starting.plugins,\n db: store,\n ...(keeping !== undefined && { outbox: keeping }),\n ...(later !== undefined && { schedule: later }),\n ...(scoping && store.narrowing !== undefined && { narrow: store.narrowing() }),\n budget,\n dial: typeof starting.outbound === \"function\" ? starting.outbound : dial(starting.outbound ?? {}),\n ...(starting.config !== undefined && { config: starting.config }),\n ...(log !== undefined && {\n log: (level, plugin, line, about) =>\n {\n log[level](`${plugin}: ${line}`, about);\n },\n }),\n });\n\n await kernel.start();\n\n log?.info(\"kernel started\", { plugins: starting.plugins.length, routes: kernel.routes().length });\n\n // Built after the kernel started, so what identifies a caller can reach\n // the plugin holding the sessions.\n const identify = starting.identify?.(kernel);\n\n const app = serve({\n kernel,\n ...(identify !== undefined && { identify }),\n ...(starting.http ?? {}),\n ...(log !== undefined && {\n log: (level, line, about) =>\n {\n log[level](line, about);\n },\n }),\n });\n\n return {\n kernel,\n store,\n app,\n fetch: app.fetch,\n\n stop: async (): Promise<void> =>\n {\n if (sweeping !== undefined)\n {\n clearInterval(sweeping);\n }\n\n await kernel.stop();\n store.close();\n },\n };\n}\n"]}
@@ -0,0 +1,165 @@
1
+ import { K as Kernel, e as Store, H as Handle, b as Plugin, d as Outbound, c as Caller } from './api-BZrn0c9T.js';
2
+ import 'zod';
3
+ import 'drizzle-orm/better-sqlite3';
4
+
5
+ type Said = {
6
+ level: string;
7
+ plugin: string;
8
+ line: string;
9
+ } & Readonly<Record<string, unknown>>;
10
+ /** One outbound call, as a test sees it. */
11
+ type Called = {
12
+ method: string;
13
+ url: string;
14
+ body: unknown;
15
+ headers: Readonly<Record<string, string>> | undefined;
16
+ };
17
+ /** One event, as a test sees it. */
18
+ type Heard = {
19
+ plugin: string;
20
+ event: string;
21
+ payload: unknown;
22
+ };
23
+ type Booted = {
24
+ kernel: Kernel;
25
+ store: Store<Handle>;
26
+ said: Said[];
27
+ called: () => Called[];
28
+ /**
29
+ * Every event emitted since boot, in order.
30
+ *
31
+ * A plugin with no listener still emits, and proving that it did would
32
+ * otherwise mean writing a plugin whose only purpose is to hear. This
33
+ * listens to everything declared, so a test asserts on the emit itself.
34
+ */
35
+ heard: () => Heard[];
36
+ /**
37
+ * Waits until every listener an emit started has finished.
38
+ *
39
+ * `emit` returns void and a listener runs after the caller, so a test
40
+ * that reads straight after emitting reads the state from before it.
41
+ * Nothing in a plugin ever needs this; a test that asserts on what a
42
+ * listener did always does.
43
+ */
44
+ settled: () => Promise<void>;
45
+ /**
46
+ * Runs whatever the schedule says is due, once.
47
+ *
48
+ * A test moves its own clock forward and asks, rather than waiting for a
49
+ * beat: what is being proved is that the work runs at its moment, not
50
+ * that an interval fired.
51
+ */
52
+ due: () => Promise<void>;
53
+ stop: () => Promise<void>;
54
+ };
55
+ type Booting = {
56
+ plugins: readonly Plugin[];
57
+ config?: Readonly<Record<string, unknown>>;
58
+ answers?: (call: Outbound) => unknown;
59
+ /**
60
+ * Whether events are kept until a listener has heard them, as
61
+ * `start({ outbox: true })` does.
62
+ *
63
+ * A test proving a listener survives hearing the same event twice needs
64
+ * the same machinery the deployment runs, or it is proving something
65
+ * else.
66
+ */
67
+ outbox?: boolean;
68
+ /**
69
+ * Whether a plugin may ask for work later, as `start({ schedule: true })`.
70
+ *
71
+ * A test drives it with `due()` rather than a beat: waiting a real second
72
+ * to watch a job run is a slow test that fails on a busy machine.
73
+ */
74
+ schedule?: boolean;
75
+ /** What the clock answers, so a test can reach tomorrow. */
76
+ now?: () => number;
77
+ };
78
+ declare const Booting: {
79
+ tables: (plugins: readonly Plugin[]) => Readonly<Record<string, Readonly<Record<string, unknown>>>>;
80
+ migrations: (plugins: readonly Plugin[]) => {
81
+ plugin: string;
82
+ from: string;
83
+ }[];
84
+ };
85
+ declare function booting(given: Booting): Promise<Booted>;
86
+ /**
87
+ * A caller a test controls.
88
+ *
89
+ * `claims` is what the project decided a caller carries: a tenant, a role, a
90
+ * plan. The kernel never reads it, so a test proving that one tenant cannot
91
+ * reach another's rows has to be able to say who this caller belongs to.
92
+ */
93
+ declare function calling(permissions?: readonly string[], id?: string, claims?: Readonly<Record<string, unknown>>): Caller;
94
+
95
+ type Crossing = {
96
+ from: string;
97
+ to: string;
98
+ specifier: string;
99
+ };
100
+ type Wrong$1 = {
101
+ rule: "undeclared" | "deep" | "cycle" | "contract";
102
+ message: string;
103
+ };
104
+ declare function boundaries(root: string): Wrong$1[];
105
+
106
+ type Oversized = {
107
+ path: string;
108
+ size: number;
109
+ };
110
+ type Undocumented = {
111
+ key: string;
112
+ };
113
+ declare function oversized(root: string, limit?: number): Oversized[];
114
+ declare function missing(root: string, required: readonly string[]): string[];
115
+ /**
116
+ * Plugins that describe themselves nowhere.
117
+ *
118
+ * A plugin is a capability someone else has to understand before they can
119
+ * depend on it, and its contract says what crosses the boundary rather than
120
+ * why anyone would want it. A folder with no `usage.md` is one nobody can
121
+ * decide about without reading its source.
122
+ */
123
+ declare function unexplained(plugins: string): string[];
124
+ declare function undocumented(contract: string, procedure: string): string[];
125
+
126
+ type Wrong = {
127
+ check: "boundaries" | "wiring" | "oversized" | "missing" | "unexplained" | "undocumented";
128
+ message: string;
129
+ };
130
+ type Checking = {
131
+ root?: string;
132
+ plugins?: string;
133
+ /** Where pure code shared between plugins lives. */
134
+ utils?: string;
135
+ docs?: string;
136
+ required?: readonly string[];
137
+ procedure?: string;
138
+ limit?: number;
139
+ };
140
+ declare const Project: {
141
+ required: readonly ["#docs/usage.md", "#docs/stack.md", "#docs/architecture.md", "README.md"];
142
+ checks: (checking?: Checking) => Wrong[];
143
+ boundaries: (at: string) => Wrong[];
144
+ wiring: (at: string, apart?: boolean) => Wrong[];
145
+ unexplained: (at: string) => Wrong[];
146
+ docs: (root: string, at: string, required: readonly string[], limit: number) => Wrong[];
147
+ contract: (procedure: string) => Wrong[];
148
+ };
149
+
150
+ type Unread = {
151
+ file: string;
152
+ shape: string;
153
+ field: string;
154
+ };
155
+ /**
156
+ * Fields a contract declares that nothing in production reads.
157
+ *
158
+ * Tests and comments are excluded from what counts as a read. Counting them
159
+ * is what made this check certify rather than check: a field kept alive by a
160
+ * fixture is a field the document promises and no code honours, which is the
161
+ * defect this exists to catch.
162
+ */
163
+ declare function wiring(root: string, apart?: boolean): Unread[];
164
+
165
+ export { type Booted, Booting, Booting as Boots, type Called, Caller, type Checking, type Wrong$1 as Crossed, type Crossing, Outbound, type Oversized, Project, type Said, type Undocumented, type Unread, type Wrong, booting, boundaries, calling, missing, oversized, undocumented, unexplained, wiring };