@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.
@@ -0,0 +1,450 @@
1
+ import { database, createKernel, limiter } from './chunk-UCWOCNTI.js';
2
+ import { readdirSync, existsSync, readFileSync, statSync } from 'fs';
3
+ import { join, dirname, relative } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+
6
+ // src/testing/booting.ts
7
+ var Booting = {
8
+ tables: (plugins) => {
9
+ return Object.fromEntries(plugins.map((plugin) => [plugin.name, plugin.definition.tables ?? {}]));
10
+ },
11
+ migrations: (plugins) => {
12
+ return plugins.filter((plugin) => plugin.definition.migrations !== void 0).map((plugin) => ({ plugin: plugin.name, from: plugin.definition.migrations }));
13
+ }
14
+ };
15
+ var TAKES = /* @__PURE__ */ new Set(["plugins", "config", "answers", "outbox", "schedule", "now"]);
16
+ async function booting(given) {
17
+ const unknown = Object.keys(given).filter((key) => !TAKES.has(key));
18
+ if (unknown.length > 0) {
19
+ throw new TypeError(
20
+ `booting was given ${unknown.map((key) => `"${key}"`).join(", ")}, which it does not take. It takes ${[...TAKES].join(", ")}.`
21
+ );
22
+ }
23
+ const store = database({ file: ":memory:", tables: Booting.tables(given.plugins) });
24
+ store.migrate(Booting.migrations(given.plugins));
25
+ const said = [];
26
+ const called = [];
27
+ const heard = [];
28
+ const listening = {
29
+ name: "testing-ears",
30
+ definition: {
31
+ version: "1.0.0",
32
+ describe: "Records every event, for a test to read.",
33
+ // Only what the given plugins declare: an ear on an event nobody
34
+ // publishes fails the boot, blaming a plugin the author never
35
+ // wrote and cannot find.
36
+ listens: Object.fromEntries(
37
+ given.plugins.flatMap(
38
+ (plugin) => Object.keys(plugin.definition.emits ?? {}).map((event) => [event, {
39
+ describe: `Records ${event}.`,
40
+ // The plugin recorded is the one that declared the
41
+ // event, not this one: ctx.name here is always the
42
+ // listener, which tells a test nothing.
43
+ handle: (payload) => {
44
+ heard.push({ plugin: plugin.name, event, payload });
45
+ }
46
+ }])
47
+ )
48
+ )
49
+ }
50
+ };
51
+ const dial = (call) => {
52
+ called.push({ method: call.method, url: call.url, body: call.body, headers: call.headers });
53
+ return Promise.resolve(given.answers?.(call) ?? {});
54
+ };
55
+ const keeping = given.outbox === true ? store.outbox?.() : void 0;
56
+ const later = given.schedule === true ? store.schedule?.() : void 0;
57
+ const scoping = given.plugins.some((plugin) => plugin.definition.scope !== void 0);
58
+ const kernel = createKernel({
59
+ plugins: [...given.plugins, listening],
60
+ ...keeping !== void 0 && { outbox: keeping },
61
+ ...later !== void 0 && { schedule: later },
62
+ ...scoping && store.narrowing !== void 0 && { narrow: store.narrowing() },
63
+ ...given.now !== void 0 && { now: given.now },
64
+ // Never a beat in a test: a job that fires on its own turns an
65
+ // assertion into a race with an interval nobody controls.
66
+ beat: 24 * 60 * 60 * 1e3,
67
+ db: store,
68
+ dial,
69
+ budget: limiter(),
70
+ config: given.config ?? {},
71
+ log: (level, plugin, line, about) => {
72
+ said.push({ level, plugin, line, ...about });
73
+ }
74
+ });
75
+ await kernel.start();
76
+ return {
77
+ kernel,
78
+ store,
79
+ said,
80
+ called: () => [...called],
81
+ heard: () => [...heard],
82
+ due: () => kernel.due(),
83
+ settled: async () => {
84
+ for (let turn = 0; turn < 4; turn += 1) {
85
+ await new Promise((keep) => {
86
+ setTimeout(keep, 0);
87
+ });
88
+ }
89
+ },
90
+ stop: async () => {
91
+ await kernel.stop();
92
+ store.close();
93
+ }
94
+ };
95
+ }
96
+ function calling(permissions = [], id = "11111111-1111-4111-8111-111111111111", claims = {}) {
97
+ return { id, permissions, claims };
98
+ }
99
+ function boundaries(root) {
100
+ const names = readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
101
+ const contracts = names.filter((name) => existsSync(join(root, name, "plugin.ts")));
102
+ const missing2 = names.filter((name) => !contracts.includes(name)).map((name) => ({
103
+ rule: "contract",
104
+ message: `"${name}" is a plugin folder with no plugin.ts. Add its contract, or remove the folder.`
105
+ }));
106
+ const plugins = contracts.map((name) => read(root, name, contracts));
107
+ return [...missing2, ...undeclared(needed(plugins)), ...deep(needed(plugins)), ...cycles(plugins)];
108
+ }
109
+ function needed(plugins) {
110
+ const answers = new Map(plugins.map((one) => [one.name, one.answers]));
111
+ const declared2 = new Map(plugins.map((one) => [one.name, one.declared]));
112
+ return plugins.map((one) => {
113
+ const reached = /* @__PURE__ */ new Set([...one.answers, ...one.declared]);
114
+ const walking = [...reached];
115
+ while (walking.length > 0) {
116
+ const next = walking.pop();
117
+ for (const set of [answers.get(next), declared2.get(next)]) {
118
+ for (const further of set ?? []) {
119
+ if (further !== one.name && !reached.has(further)) {
120
+ reached.add(further);
121
+ walking.push(further);
122
+ }
123
+ }
124
+ }
125
+ }
126
+ return { ...one, answers: reached };
127
+ });
128
+ }
129
+ function read(root, name, names) {
130
+ const others = new Set(names.filter((one) => one !== name));
131
+ const contract = readFileSync(join(root, name, "plugin.ts"), "utf8");
132
+ const found = /dependsOn:\s*\[([^\]]*)\]/.exec(contract);
133
+ const answering = /* @__PURE__ */ new Set();
134
+ for (const block of [/listens:\s*\{/, /participates:\s*\{/]) {
135
+ const at = block.exec(contract);
136
+ if (at === null) {
137
+ continue;
138
+ }
139
+ for (const key of contract.slice(at.index).matchAll(/"([a-z0-9-]+)\.[^"]+":/g)) {
140
+ if (others.has(key[1])) {
141
+ answering.add(key[1]);
142
+ }
143
+ }
144
+ }
145
+ return {
146
+ name,
147
+ declared: new Set(found === null ? [] : [...found[1].matchAll(/"([^"]+)"/g)].map((one) => one[1])),
148
+ answers: answering,
149
+ crossings: files(root, name).flatMap(({ path, source }) => crossings(name, path, source, others))
150
+ };
151
+ }
152
+ function files(root, name) {
153
+ const at = join(root, name);
154
+ return readdirSync(at, { withFileTypes: true, recursive: true }).filter((entry) => entry.isFile() && /\.tsx?$/.test(entry.name)).map((entry) => {
155
+ const path = join(entry.parentPath, entry.name);
156
+ return { path: path.replace(`${at}/`, ""), source: readFileSync(path, "utf8") };
157
+ });
158
+ }
159
+ function crossings(name, path, source, others) {
160
+ return [...source.matchAll(/from\s+"([^"]+)"/g)].flatMap((match) => {
161
+ const specifier = match[1];
162
+ const alias = /^@plugins\/([^/]+)/.exec(specifier);
163
+ if (alias !== null && others.has(alias[1])) {
164
+ return [{ from: path, to: alias[1], specifier }];
165
+ }
166
+ if (!specifier.startsWith(".")) {
167
+ return [];
168
+ }
169
+ const parts = [name, ...path.split("/").slice(0, -1), ...specifier.split("/")];
170
+ const walked = [];
171
+ for (const part of parts) {
172
+ if (part === "..") {
173
+ walked.pop();
174
+ } else if (part !== ".") {
175
+ walked.push(part);
176
+ }
177
+ }
178
+ const target = walked[0];
179
+ return target !== void 0 && others.has(target) ? [{ from: path, to: target, specifier }] : [];
180
+ });
181
+ }
182
+ function undeclared(plugins) {
183
+ return plugins.flatMap(
184
+ (one) => one.crossings.filter((crossing) => {
185
+ if (one.declared.has(crossing.to)) {
186
+ return false;
187
+ }
188
+ return !(tested(crossing.from) && one.answers.has(crossing.to) && crossing.specifier === `@plugins/${crossing.to}/plugin`);
189
+ }).map((crossing) => ({
190
+ rule: "undeclared",
191
+ message: `${one.name}/${crossing.from} imports "${crossing.specifier}" without declaring "${crossing.to}" in dependsOn.`
192
+ }))
193
+ );
194
+ }
195
+ function tested(path) {
196
+ return /(^|\/)tests?\//.test(path) || /\.test\.tsx?$/.test(path);
197
+ }
198
+ function deep(plugins) {
199
+ return plugins.flatMap(
200
+ (one) => one.crossings.filter((crossing) => {
201
+ if (crossing.specifier === `@plugins/${crossing.to}`) {
202
+ return false;
203
+ }
204
+ return !(tested(crossing.from) && (one.declared.has(crossing.to) || one.answers.has(crossing.to)) && crossing.specifier === `@plugins/${crossing.to}/plugin`);
205
+ }).map((crossing) => ({
206
+ rule: "deep",
207
+ message: `${one.name}/${crossing.from} reaches "${crossing.specifier}" instead of "@plugins/${crossing.to}".`
208
+ }))
209
+ );
210
+ }
211
+ function cycles(plugins) {
212
+ const edges = new Map(plugins.map((one) => [one.name, new Set(one.crossings.map((crossing) => crossing.to))]));
213
+ const found = [];
214
+ const walking = /* @__PURE__ */ new Set();
215
+ const done = /* @__PURE__ */ new Set();
216
+ function walk2(name, trail) {
217
+ if (done.has(name)) {
218
+ return;
219
+ }
220
+ if (walking.has(name)) {
221
+ found.push({
222
+ rule: "cycle",
223
+ message: `Plugins import each other in a loop: ${[...trail.slice(trail.indexOf(name)), name].join(" -> ")}.`
224
+ });
225
+ return;
226
+ }
227
+ walking.add(name);
228
+ for (const target of edges.get(name) ?? []) {
229
+ walk2(target, [...trail, name]);
230
+ }
231
+ walking.delete(name);
232
+ done.add(name);
233
+ }
234
+ for (const one of plugins) {
235
+ walk2(one.name, []);
236
+ }
237
+ return found;
238
+ }
239
+ var LIMIT = 1800;
240
+ function oversized(root, limit = LIMIT) {
241
+ if (!existsSync(root)) {
242
+ return [];
243
+ }
244
+ return readdirSync(root, { withFileTypes: true, recursive: true }).filter((entry) => {
245
+ return entry.isFile() && entry.name.endsWith(".md") && !entry.parentPath.includes("progress");
246
+ }).map((entry) => {
247
+ const path = join(entry.parentPath, entry.name);
248
+ return { path, size: readFileSync(path, "utf8").length };
249
+ }).filter((doc) => {
250
+ return doc.size > limit;
251
+ });
252
+ }
253
+ function missing(root, required) {
254
+ return required.filter((path) => {
255
+ try {
256
+ return readFileSync(join(root, path), "utf8").trim().length === 0;
257
+ } catch {
258
+ return true;
259
+ }
260
+ });
261
+ }
262
+ function unexplained(plugins) {
263
+ if (!existsSync(plugins)) {
264
+ return [];
265
+ }
266
+ return readdirSync(plugins, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => {
267
+ try {
268
+ return readFileSync(join(plugins, name, "usage.md"), "utf8").trim().length === 0;
269
+ } catch {
270
+ return true;
271
+ }
272
+ });
273
+ }
274
+ function undocumented(contract, procedure) {
275
+ const shape = /export type Definition[\s\S]*?\n\};/.exec(contract)?.[0] ?? "";
276
+ const keys = [...shape.matchAll(/^\s{4}([a-zA-Z]+)\??:/gm)].map((match) => {
277
+ return match[1] ?? "";
278
+ });
279
+ return keys.filter((key) => {
280
+ return !procedure.includes(`\`${key}\``);
281
+ });
282
+ }
283
+ function wiring(root, apart = true) {
284
+ const sources = walk(root).filter((file) => !tested2(file)).map((file) => [file, withoutComments(readFileSync(file, "utf8"))]);
285
+ const unread = [];
286
+ for (const [file, source] of sources) {
287
+ const owns = apart ? within(sources, file) : sources;
288
+ for (const { shape, field } of declared(source)) {
289
+ if (!reads(field, owns, file)) {
290
+ unread.push({ file: relative(root, file), shape, field });
291
+ }
292
+ }
293
+ }
294
+ return unread;
295
+ }
296
+ function within(sources, file) {
297
+ const at = file.lastIndexOf("/src/plugins/");
298
+ if (at === -1) {
299
+ return [...sources];
300
+ }
301
+ const plugin = file.slice(0, file.indexOf("/", at + "/src/plugins/".length) + 1);
302
+ return sources.filter(([one]) => one.startsWith(plugin));
303
+ }
304
+ function tested2(file) {
305
+ return /(^|\/)tests?\//.test(file) || /\.test\.tsx?$/.test(file);
306
+ }
307
+ function withoutComments(source) {
308
+ return source.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1");
309
+ }
310
+ function walk(path) {
311
+ if (!existsSync(path)) {
312
+ return [];
313
+ }
314
+ const found = [];
315
+ for (const entry of readdirSync(path)) {
316
+ const full = join(path, entry);
317
+ if (statSync(full).isDirectory()) {
318
+ found.push(...walk(full));
319
+ continue;
320
+ }
321
+ if (/\.tsx?$/.test(entry)) {
322
+ found.push(full);
323
+ }
324
+ }
325
+ return found;
326
+ }
327
+ function declared(source) {
328
+ const found = [];
329
+ for (const shape of source.matchAll(/export\s+(?:type\s+(\w+)\s*=\s*\{|interface\s+(\w+)[^{]*\{)/g)) {
330
+ const name = shape[1] ?? shape[2] ?? "";
331
+ const from = (shape.index ?? 0) + shape[0].length;
332
+ const body = withoutParameters(source.slice(from, closes(source, from)));
333
+ for (const field of body.matchAll(/(?:^|[;,{\n])\s*(?:readonly\s+)?(\w+)\s*\??\s*:/g)) {
334
+ found.push({ shape: name, field: field[1] ?? "" });
335
+ }
336
+ }
337
+ return found;
338
+ }
339
+ function closes(source, from) {
340
+ let depth = 1;
341
+ let at = from;
342
+ while (at < source.length && depth > 0) {
343
+ if (source[at] === "{") {
344
+ depth += 1;
345
+ }
346
+ if (source[at] === "}") {
347
+ depth -= 1;
348
+ }
349
+ at += 1;
350
+ }
351
+ return at - 1;
352
+ }
353
+ function withoutParameters(body) {
354
+ let out = "";
355
+ let depth = 0;
356
+ for (const character of body) {
357
+ if (character === "(") {
358
+ depth += 1;
359
+ }
360
+ if (depth === 0) {
361
+ out += character;
362
+ }
363
+ if (character === ")") {
364
+ depth = Math.max(0, depth - 1);
365
+ }
366
+ }
367
+ return out;
368
+ }
369
+ function reads(field, sources, where) {
370
+ const patterns = [
371
+ new RegExp(`\\.${field}\\b`),
372
+ new RegExp(`\\b${field}\\s*[,}]`),
373
+ new RegExp(`\\b${field}\\s*:`),
374
+ new RegExp(`\\[["']${field}["']\\]`),
375
+ new RegExp(`["']${field}["']`)
376
+ ];
377
+ return sources.some(([file, source]) => {
378
+ const searched = file === where ? withoutShapes(source) : source;
379
+ return patterns.some((pattern) => pattern.test(searched));
380
+ });
381
+ }
382
+ function withoutShapes(source) {
383
+ let out = "";
384
+ let at = 0;
385
+ for (const shape of source.matchAll(/export\s+(?:type\s+\w+\s*=\s*|interface\s+\w+[^{]*)\{/g)) {
386
+ const from = (shape.index ?? 0) + shape[0].length;
387
+ out += source.slice(at, shape.index);
388
+ at = closes(source, from) + 1;
389
+ }
390
+ return out + source.slice(at);
391
+ }
392
+
393
+ // src/testing/project.ts
394
+ var CONTRACT = join(dirname(fileURLToPath(import.meta.url)), "..", "plugins", "kernel", "internal", "contract.ts");
395
+ var Project = {
396
+ required: ["#docs/usage.md", "#docs/stack.md", "#docs/architecture.md", "README.md"],
397
+ checks: (checking = {}) => {
398
+ const root = checking.root ?? process.cwd();
399
+ const docs = checking.docs ?? join(root, "#docs");
400
+ const limit = checking.limit ?? 1800;
401
+ return [
402
+ ...Project.boundaries(checking.plugins ?? join(root, "src", "plugins")),
403
+ ...Project.wiring(checking.plugins ?? join(root, "src", "plugins")),
404
+ // Utils shared between plugins are checked too: a field nothing
405
+ // reads is the same defect wherever it is declared, and code no
406
+ // plugin owns is code nobody notices going stale.
407
+ ...Project.wiring(checking.utils ?? join(root, "src", "utils"), false),
408
+ ...Project.unexplained(checking.plugins ?? join(root, "src", "plugins")),
409
+ ...Project.docs(root, docs, checking.required ?? Project.required, limit),
410
+ ...Project.contract(checking.procedure ?? join(docs, "procedures", "plugin", "contract.md"))
411
+ ];
412
+ },
413
+ boundaries: (at) => {
414
+ return boundaries(at).map((wrong) => ({ check: "boundaries", message: wrong.message }));
415
+ },
416
+ wiring: (at, apart = true) => {
417
+ return wiring(at, apart).map((unread) => ({
418
+ check: "wiring",
419
+ message: `${unread.file}: ${unread.shape}.${unread.field} is declared and nothing reads it.`
420
+ }));
421
+ },
422
+ unexplained: (at) => {
423
+ return unexplained(at).map((name) => ({
424
+ check: "unexplained",
425
+ message: `"${name}" has no usage.md. A plugin nobody can read is one nobody can depend on.`
426
+ }));
427
+ },
428
+ docs: (root, at, required, limit) => {
429
+ return [
430
+ ...oversized(at, limit).map((doc) => ({
431
+ check: "oversized",
432
+ message: `${doc.path.replace(`${root}/`, "")} is ${String(doc.size)} characters, over ${String(limit)}.`
433
+ })),
434
+ ...missing(root, required).map((path) => ({
435
+ check: "missing",
436
+ message: `${path} is absent or says nothing.`
437
+ }))
438
+ ];
439
+ },
440
+ contract: (procedure) => {
441
+ return undocumented(readFileSync(CONTRACT, "utf8"), readFileSync(procedure, "utf8")).map((key) => ({
442
+ check: "undocumented",
443
+ message: `The contract accepts "${key}" and ${procedure.split("/").slice(-1).join("")} never names it.`
444
+ }));
445
+ }
446
+ };
447
+
448
+ export { Booting, Project, booting, boundaries, calling, missing, oversized, undocumented, unexplained, wiring };
449
+ //# sourceMappingURL=testing.js.map
450
+ //# sourceMappingURL=testing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/testing/booting.ts","../src/testing/boundaries.ts","../src/testing/docs.ts","../src/testing/wiring.ts","../src/testing/project.ts"],"names":["missing","declared","walk","existsSync","readdirSync","join","readFileSync","tested"],"mappings":"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","file":"testing.js","sourcesContent":["import { database } from \"../plugins/database/api\";\nimport { limiter } from \"../plugins/guard/api\";\nimport { createKernel } from \"../plugins/kernel/api\";\n\nimport type { Handle, Store } from \"../plugins/database/api\";\nimport type { Caller, Dialer, Kernel, Outbound, Plugin } from \"../plugins/kernel/api\";\n\nexport type Said = {\n level: string;\n plugin: string;\n line: string;\n} & Readonly<Record<string, unknown>>;\n\n/** One outbound call, as a test sees it. */\nexport type Called = {\n method: string;\n url: string;\n body: unknown;\n headers: Readonly<Record<string, string>> | undefined;\n};\n\nexport type Booting = {\n plugins: readonly Plugin[];\n config?: Readonly<Record<string, unknown>>;\n answers?: (call: Outbound) => unknown;\n\n /**\n * Whether events are kept until a listener has heard them, as\n * `start({ outbox: true })` does.\n *\n * A test proving a listener survives hearing the same event twice needs\n * the same machinery the deployment runs, or it is proving something\n * else.\n */\n outbox?: boolean;\n\n /**\n * Whether a plugin may ask for work later, as `start({ schedule: true })`.\n *\n * A test drives it with `due()` rather than a beat: waiting a real second\n * to watch a job run is a slow test that fails on a busy machine.\n */\n schedule?: boolean;\n\n /** What the clock answers, so a test can reach tomorrow. */\n now?: () => number;\n};\n\n/** One event, as a test sees it. */\nexport type Heard = {\n plugin: string;\n event: string;\n payload: unknown;\n};\n\nexport type Booted = {\n kernel: Kernel;\n store: Store<Handle>;\n said: Said[];\n called: () => Called[];\n\n /**\n * Every event emitted since boot, in order.\n *\n * A plugin with no listener still emits, and proving that it did would\n * otherwise mean writing a plugin whose only purpose is to hear. This\n * listens to everything declared, so a test asserts on the emit itself.\n */\n heard: () => Heard[];\n\n /**\n * Waits until every listener an emit started has finished.\n *\n * `emit` returns void and a listener runs after the caller, so a test\n * that reads straight after emitting reads the state from before it.\n * Nothing in a plugin ever needs this; a test that asserts on what a\n * listener did always does.\n */\n settled: () => Promise<void>;\n\n /**\n * Runs whatever the schedule says is due, once.\n *\n * A test moves its own clock forward and asks, rather than waiting for a\n * beat: what is being proved is that the work runs at its moment, not\n * that an interval fired.\n */\n due: () => Promise<void>;\n\n stop: () => Promise<void>;\n};\n\nexport const Booting = {\n tables: (plugins: readonly Plugin[]): Readonly<Record<string, Readonly<Record<string, unknown>>>> =>\n {\n return Object.fromEntries(plugins.map((plugin) => [plugin.name, plugin.definition.tables ?? {}]));\n },\n\n migrations: (plugins: readonly Plugin[]): { plugin: string; from: string }[] =>\n {\n return plugins\n .filter((plugin) => plugin.definition.migrations !== undefined)\n .map((plugin) => ({ plugin: plugin.name, from: plugin.definition.migrations as string }));\n },\n};\n\n/** Everything `booting` knows how to be given. */\nconst TAKES: ReadonlySet<string> = new Set([\"plugins\", \"config\", \"answers\", \"outbox\", \"schedule\", \"now\"]);\n\nexport async function booting(given: Booting): Promise<Booted>\n{\n // Refused rather than ignored: a key that looks like it worked is how an\n // author spends an afternoon on a test that was never wired to anything.\n const unknown = Object.keys(given).filter((key) => !TAKES.has(key));\n\n if (unknown.length > 0)\n {\n throw new TypeError(\n `booting was given ${unknown.map((key) => `\"${key}\"`).join(\", \")}, which it does not take. It takes ${[...TAKES].join(\", \")}.`,\n );\n }\n\n const store = database({ file: \":memory:\", tables: Booting.tables(given.plugins) });\n\n store.migrate(Booting.migrations(given.plugins));\n\n const said: Said[] = [];\n const called: Called[] = [];\n const heard: Heard[] = [];\n\n // A plugin that hears everything, added to the ones under test. Named so\n // it cannot collide with a real one, and declared as listening to every\n // event the given plugins publish.\n const listening: Plugin = {\n name: \"testing-ears\",\n definition: {\n version: \"1.0.0\",\n describe: \"Records every event, for a test to read.\",\n // Only what the given plugins declare: an ear on an event nobody\n // publishes fails the boot, blaming a plugin the author never\n // wrote and cannot find.\n listens: Object.fromEntries(\n given.plugins.flatMap((plugin) =>\n Object.keys(plugin.definition.emits ?? {}).map((event) => [event, {\n describe: `Records ${event}.`,\n\n // The plugin recorded is the one that declared the\n // event, not this one: ctx.name here is always the\n // listener, which tells a test nothing.\n handle: (payload: never): void =>\n {\n heard.push({ plugin: plugin.name, event, payload });\n },\n }]),\n ),\n ),\n },\n };\n\n const dial: Dialer = (call) =>\n {\n called.push({ method: call.method, url: call.url, body: call.body, headers: call.headers });\n\n return Promise.resolve(given.answers?.(call) ?? {});\n };\n\n const keeping = given.outbox === true ? store.outbox?.() : undefined;\n const later = given.schedule === true ? store.schedule?.() : undefined;\n const scoping = given.plugins.some((plugin) => plugin.definition.scope !== undefined);\n\n const kernel = createKernel({\n plugins: [...given.plugins, listening],\n ...(keeping !== undefined && { outbox: keeping }),\n ...(later !== undefined && { schedule: later }),\n ...(scoping && store.narrowing !== undefined && { narrow: store.narrowing() }),\n ...(given.now !== undefined && { now: given.now }),\n\n // Never a beat in a test: a job that fires on its own turns an\n // assertion into a race with an interval nobody controls.\n beat: 24 * 60 * 60 * 1000,\n db: store,\n dial,\n budget: limiter(),\n config: given.config ?? {},\n log: (level, plugin, line, about) =>\n {\n said.push({ level, plugin, line, ...about });\n },\n });\n\n await kernel.start();\n\n return {\n kernel,\n store,\n said,\n called: () => [...called],\n heard: () => [...heard],\n\n due: () => kernel.due(),\n\n settled: async (): Promise<void> =>\n {\n // Two turns, not one: a listener that writes hands its work to\n // the store's queue, and a chain of two listeners needs the\n // second to start before the first is done.\n for (let turn = 0; turn < 4; turn += 1)\n {\n await new Promise((keep) => { setTimeout(keep, 0); });\n }\n },\n stop: async (): Promise<void> =>\n {\n await kernel.stop();\n store.close();\n },\n };\n}\n\n/**\n * A caller a test controls.\n *\n * `claims` is what the project decided a caller carries: a tenant, a role, a\n * plan. The kernel never reads it, so a test proving that one tenant cannot\n * reach another's rows has to be able to say who this caller belongs to.\n */\nexport function calling(\n permissions: readonly string[] = [],\n id = \"11111111-1111-4111-8111-111111111111\",\n claims: Readonly<Record<string, unknown>> = {},\n): Caller\n{\n return { id, permissions, claims };\n}\n","import { existsSync, readFileSync, readdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport type Crossing = {\n from: string;\n to: string;\n specifier: string;\n};\n\nexport type Wrong = {\n rule: \"undeclared\" | \"deep\" | \"cycle\" | \"contract\";\n message: string;\n};\n\ntype Read = {\n name: string;\n declared: Set<string>;\n\n /**\n * Plugins whose events or hooks this one answers.\n *\n * Not dependencies: hearing is not depending, and the kernel adds no edge\n * for it. But a test still has to boot the plugin that emits, or there is\n * nothing to hear, so a test may name its contract exactly as a test of a\n * dependency may.\n */\n answers: Set<string>;\n\n crossings: Crossing[];\n};\n\nexport function boundaries(root: string): Wrong[]\n{\n const names = readdirSync(root, { withFileTypes: true })\n .filter((entry) => entry.isDirectory())\n .map((entry) => entry.name);\n\n // A folder with no contract is reported, not thrown: a half-built plugin\n // is the commonest reason to run this check, and a raw ENOENT naming a\n // path inside the kit tells its author nothing about their own folder.\n const contracts = names.filter((name) => existsSync(join(root, name, \"plugin.ts\")));\n\n const missing: Wrong[] = names\n .filter((name) => !contracts.includes(name))\n .map((name) => ({\n rule: \"contract\" as const,\n message: `\"${name}\" is a plugin folder with no plugin.ts. Add its contract, or remove the folder.`,\n }));\n\n const plugins = contracts.map((name) => read(root, name, contracts));\n\n return [...missing, ...undeclared(needed(plugins)), ...deep(needed(plugins)), ...cycles(plugins)];\n}\n\n/**\n * What each plugin's tests must boot, following the chain.\n *\n * A listener has to boot what it hears. But that emitter may itself be a\n * listener, and cannot start without the plugin *it* hears: a test two hops\n * down the chain has to boot all three. Refusing that made the checker decide\n * an architecture, which is backwards.\n *\n * Dependencies are not widened this way: only what a test has to assemble.\n */\nfunction needed(plugins: readonly Read[]): Read[]\n{\n const answers = new Map(plugins.map((one) => [one.name, one.answers]));\n const declared = new Map(plugins.map((one) => [one.name, one.declared]));\n\n return plugins.map((one) =>\n {\n // Seeded from both: a test boots what this plugin hears *and* what it\n // depends on, and then whatever those need in turn. Seeding only from\n // what it hears leaves a three-deep dependency chain untestable\n // without writing a dependency that is not one.\n const reached = new Set([...one.answers, ...one.declared]);\n const walking = [...reached];\n\n while (walking.length > 0)\n {\n const next = walking.pop() as string;\n\n for (const set of [answers.get(next), declared.get(next)])\n {\n for (const further of set ?? [])\n {\n if (further !== one.name && !reached.has(further))\n {\n reached.add(further);\n walking.push(further);\n }\n }\n }\n }\n\n return { ...one, answers: reached };\n });\n}\n\nfunction read(root: string, name: string, names: readonly string[]): Read\n{\n const others = new Set(names.filter((one) => one !== name));\n const contract = readFileSync(join(root, name, \"plugin.ts\"), \"utf8\");\n const found = /dependsOn:\\s*\\[([^\\]]*)\\]/.exec(contract);\n\n // An event or hook key is \"<plugin>.<something>\", so what a plugin\n // answers is the first segment of every key it listens to or joins.\n const answering = new Set<string>();\n\n for (const block of [/listens:\\s*\\{/, /participates:\\s*\\{/])\n {\n const at = block.exec(contract);\n\n if (at === null)\n {\n continue;\n }\n\n for (const key of contract.slice(at.index).matchAll(/\"([a-z0-9-]+)\\.[^\"]+\":/g))\n {\n if (others.has(key[1]!))\n {\n answering.add(key[1]!);\n }\n }\n }\n\n return {\n name,\n declared: new Set(found === null ? [] : [...found[1]!.matchAll(/\"([^\"]+)\"/g)].map((one) => one[1]!)),\n answers: answering,\n crossings: files(root, name).flatMap(({ path, source }) => crossings(name, path, source, others)),\n };\n}\n\nfunction files(root: string, name: string): { path: string; source: string }[]\n{\n const at = join(root, name);\n\n return readdirSync(at, { withFileTypes: true, recursive: true })\n .filter((entry) => entry.isFile() && /\\.tsx?$/.test(entry.name))\n .map((entry) =>\n {\n const path = join(entry.parentPath, entry.name);\n\n return { path: path.replace(`${at}/`, \"\"), source: readFileSync(path, \"utf8\") };\n });\n}\n\n// A specifier is resolved against the file that wrote it rather than matched as\n// text: \"../../other/thing\" reaches the same private file an alias would, and a\n// rule reading the alias alone calls that clean.\nfunction crossings(name: string, path: string, source: string, others: ReadonlySet<string>): Crossing[]\n{\n return [...source.matchAll(/from\\s+\"([^\"]+)\"/g)].flatMap((match) =>\n {\n const specifier = match[1]!;\n const alias = /^@plugins\\/([^/]+)/.exec(specifier);\n\n if (alias !== null && others.has(alias[1]!))\n {\n return [{ from: path, to: alias[1]!, specifier }];\n }\n\n if (!specifier.startsWith(\".\"))\n {\n return [];\n }\n\n const parts = [name, ...path.split(\"/\").slice(0, -1), ...specifier.split(\"/\")];\n const walked: string[] = [];\n\n for (const part of parts)\n {\n if (part === \"..\")\n {\n walked.pop();\n }\n else if (part !== \".\")\n {\n walked.push(part);\n }\n }\n\n const target = walked[0];\n\n return target !== undefined && others.has(target) ? [{ from: path, to: target, specifier }] : [];\n });\n}\n\nfunction undeclared(plugins: readonly Read[]): Wrong[]\n{\n return plugins.flatMap((one) =>\n one.crossings\n .filter((crossing) =>\n {\n if (one.declared.has(crossing.to))\n {\n return false;\n }\n\n // A test of a listener boots what it listens to. That is not\n // a dependency, and writing one to satisfy this check would\n // put a lie in the contract.\n return !(tested(crossing.from)\n && one.answers.has(crossing.to)\n && crossing.specifier === `@plugins/${crossing.to}/plugin`);\n })\n .map((crossing) => ({\n rule: \"undeclared\" as const,\n message: `${one.name}/${crossing.from} imports \"${crossing.specifier}\" without declaring \"${crossing.to}\" in dependsOn.`,\n })),\n );\n}\n\n/**\n * Whether a file is a test, which may reach one file more than the rest.\n *\n * A plugin with a dependency has to boot it to test itself, and a contract is\n * not reachable through `index.ts`: what a consumer imports is the public API,\n * and what a kernel takes is the plugin. Refusing this left `tests.md`'s \"a\n * plugin tests itself in its own tests/\" impossible for anything with a\n * dependency.\n */\nfunction tested(path: string): boolean\n{\n return /(^|\\/)tests?\\//.test(path) || /\\.test\\.tsx?$/.test(path);\n}\n\nfunction deep(plugins: readonly Read[]): Wrong[]\n{\n return plugins.flatMap((one) =>\n one.crossings\n .filter((crossing) =>\n {\n if (crossing.specifier === `@plugins/${crossing.to}`)\n {\n return false;\n }\n\n // A test may name a declared dependency's contract, and only\n // its contract: everything below it is still private.\n return !(tested(crossing.from)\n && (one.declared.has(crossing.to) || one.answers.has(crossing.to))\n && crossing.specifier === `@plugins/${crossing.to}/plugin`);\n })\n .map((crossing) => ({\n rule: \"deep\" as const,\n message: `${one.name}/${crossing.from} reaches \"${crossing.specifier}\" instead of \"@plugins/${crossing.to}\".`,\n })),\n );\n}\n\nfunction cycles(plugins: readonly Read[]): Wrong[]\n{\n const edges = new Map(plugins.map((one) => [one.name, new Set(one.crossings.map((crossing) => crossing.to))]));\n const found: Wrong[] = [];\n const walking = new Set<string>();\n const done = new Set<string>();\n\n function walk(name: string, trail: readonly string[]): void\n {\n if (done.has(name))\n {\n return;\n }\n\n if (walking.has(name))\n {\n found.push({\n rule: \"cycle\",\n message: `Plugins import each other in a loop: ${[...trail.slice(trail.indexOf(name)), name].join(\" -> \")}.`,\n });\n\n return;\n }\n\n walking.add(name);\n\n for (const target of edges.get(name) ?? [])\n {\n walk(target, [...trail, name]);\n }\n\n walking.delete(name);\n done.add(name);\n }\n\n for (const one of plugins)\n {\n walk(one.name, []);\n }\n\n return found;\n}\n","import { existsSync, readFileSync, readdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport type Oversized = {\n path: string;\n size: number;\n};\n\nexport type Undocumented = {\n key: string;\n};\n\nconst LIMIT = 1800;\n\n// A contract nobody can read in one sitting is a contract nobody reads. What\n// grows past this is two documents, or a rule that belongs in code.\nexport function oversized(root: string, limit: number = LIMIT): Oversized[]\n{\n if (!existsSync(root))\n {\n return [];\n }\n\n return readdirSync(root, { withFileTypes: true, recursive: true })\n .filter((entry) =>\n {\n return entry.isFile() && entry.name.endsWith(\".md\") && !entry.parentPath.includes(\"progress\");\n })\n .map((entry) =>\n {\n const path = join(entry.parentPath, entry.name);\n\n return { path, size: readFileSync(path, \"utf8\").length };\n })\n .filter((doc) =>\n {\n return doc.size > limit;\n });\n}\n\n// A document that is present but empty reads as done and says nothing, which\n// is worse than one that is missing and obviously so.\nexport function missing(root: string, required: readonly string[]): string[]\n{\n return required.filter((path) =>\n {\n try\n {\n return readFileSync(join(root, path), \"utf8\").trim().length === 0;\n }\n catch\n {\n return true;\n }\n });\n}\n\n/**\n * Plugins that describe themselves nowhere.\n *\n * A plugin is a capability someone else has to understand before they can\n * depend on it, and its contract says what crosses the boundary rather than\n * why anyone would want it. A folder with no `usage.md` is one nobody can\n * decide about without reading its source.\n */\nexport function unexplained(plugins: string): string[]\n{\n if (!existsSync(plugins))\n {\n return [];\n }\n\n return readdirSync(plugins, { withFileTypes: true })\n .filter((entry) => entry.isDirectory())\n .map((entry) => entry.name)\n .filter((name) =>\n {\n try\n {\n return readFileSync(join(plugins, name, \"usage.md\"), \"utf8\").trim().length === 0;\n }\n catch\n {\n return true;\n }\n });\n}\n\n// Every key the contract accepts is named in the procedure that explains it.\n// A key added to one and not the other is how a document starts lying.\nexport function undocumented(contract: string, procedure: string): string[]\n{\n const shape = /export type Definition[\\s\\S]*?\\n\\};/.exec(contract)?.[0] ?? \"\";\n const keys = [...shape.matchAll(/^\\s{4}([a-zA-Z]+)\\??:/gm)].map((match) =>\n {\n return match[1] ?? \"\";\n });\n\n return keys.filter((key) =>\n {\n return !procedure.includes(`\\`${key}\\``);\n });\n}\n","import { readFileSync, readdirSync, existsSync, statSync } from \"node:fs\";\nimport { join, relative } from \"node:path\";\n\nexport type Unread = {\n file: string;\n shape: string;\n field: string;\n};\n\n/**\n * Fields a contract declares that nothing in production reads.\n *\n * Tests and comments are excluded from what counts as a read. Counting them\n * is what made this check certify rather than check: a field kept alive by a\n * fixture is a field the document promises and no code honours, which is the\n * defect this exists to catch.\n */\nexport function wiring(root: string, apart = true): Unread[]\n{\n const sources = walk(root)\n .filter((file) => !tested(file))\n .map((file): [string, string] => [file, withoutComments(readFileSync(file, \"utf8\"))]);\n\n const unread: Unread[] = [];\n\n for (const [file, source] of sources)\n {\n // Only the plugin that declared it can read it, so only its files are\n // searched. Searching every plugin makes the check weaker the more\n // plugins there are: a field called `id` or `status` is read\n // somewhere in any codebase, and would be certified everywhere.\n const owns = apart ? within(sources, file) : sources;\n\n for (const { shape, field } of declared(source))\n {\n if (!reads(field, owns, file))\n {\n unread.push({ file: relative(root, file), shape, field });\n }\n }\n }\n\n return unread;\n}\n\n/**\n * The files of the plugin a file belongs to, and no others.\n *\n * A plugin cannot read another's types, so a field read only elsewhere is a\n * field nothing honours. Searching every plugin makes the check weaker the\n * more there are: `id` or `status` occurs somewhere in any codebase, and\n * would be certified everywhere it appears.\n */\nfunction within(sources: readonly [string, string][], file: string): [string, string][]\n{\n const at = file.lastIndexOf(\"/src/plugins/\");\n\n if (at === -1)\n {\n return [...sources];\n }\n\n const plugin = file.slice(0, file.indexOf(\"/\", at + \"/src/plugins/\".length) + 1);\n\n return sources.filter(([one]) => one.startsWith(plugin));\n}\n\n/** Whether a file is a test rather than the code a contract is honoured by. */\nfunction tested(file: string): boolean\n{\n return /(^|\\/)tests?\\//.test(file) || /\\.test\\.tsx?$/.test(file);\n}\n\n/**\n * The source with comments removed.\n *\n * A name mentioned in a comment is a name nothing consumes: \"someday we will\n * enforce `limit`\" is exactly the shape this check exists to refuse.\n */\nfunction withoutComments(source: string): string\n{\n return source\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, \" \")\n .replace(/(^|[^:])\\/\\/[^\\n]*/g, \"$1\");\n}\n\nfunction walk(path: string): string[]\n{\n if (!existsSync(path))\n {\n return [];\n }\n\n const found: string[] = [];\n\n for (const entry of readdirSync(path))\n {\n const full = join(path, entry);\n\n if (statSync(full).isDirectory())\n {\n found.push(...walk(full));\n continue;\n }\n\n if (/\\.tsx?$/.test(entry))\n {\n found.push(full);\n }\n }\n\n return found;\n}\n\n// A contract is what crosses a boundary, so only exported shapes count: an\n// internal type is read by whoever wrote it or it would not compile.\nfunction declared(source: string): { shape: string; field: string }[]\n{\n const found: { shape: string; field: string }[] = [];\n\n for (const shape of source.matchAll(/export\\s+(?:type\\s+(\\w+)\\s*=\\s*\\{|interface\\s+(\\w+)[^{]*\\{)/g))\n {\n const name = shape[1] ?? shape[2] ?? \"\";\n const from = (shape.index ?? 0) + shape[0].length;\n const body = withoutParameters(source.slice(from, closes(source, from)));\n\n for (const field of body.matchAll(/(?:^|[;,{\\n])\\s*(?:readonly\\s+)?(\\w+)\\s*\\??\\s*:/g))\n {\n found.push({ shape: name, field: field[1] ?? \"\" });\n }\n }\n\n return found;\n}\n\n// Where the brace opened at `from` closes. Walking counts nested shapes as\n// part of the same contract; stopping at the first \"}\" would miss their fields.\nfunction closes(source: string, from: number): number\n{\n let depth = 1;\n let at = from;\n\n while (at < source.length && depth > 0)\n {\n if (source[at] === \"{\")\n {\n depth += 1;\n }\n\n if (source[at] === \"}\")\n {\n depth -= 1;\n }\n\n at += 1;\n }\n\n return at - 1;\n}\n\n// A parameter inside a function type is not a field: `debug: (line, about?: X)\n// => void` declares one name, and \"about\" is positional. Counting it reports a\n// defect where there is none.\nfunction withoutParameters(body: string): string\n{\n let out = \"\";\n let depth = 0;\n\n for (const character of body)\n {\n if (character === \"(\")\n {\n depth += 1;\n }\n\n if (depth === 0)\n {\n out += character;\n }\n\n if (character === \")\")\n {\n depth = Math.max(0, depth - 1);\n }\n }\n\n return out;\n}\n\n// Property access, destructuring, an object literal built from it, a string\n// key. A name in none of those is a name nothing consumes.\nfunction reads(field: string, sources: readonly [string, string][], where: string): boolean\n{\n const patterns = [\n new RegExp(`\\\\.${field}\\\\b`),\n new RegExp(`\\\\b${field}\\\\s*[,}]`),\n new RegExp(`\\\\b${field}\\\\s*:`),\n new RegExp(`\\\\[[\"']${field}[\"']\\\\]`),\n new RegExp(`[\"']${field}[\"']`),\n ];\n\n return sources.some(([file, source]) =>\n {\n const searched = file === where ? withoutShapes(source) : source;\n\n return patterns.some((pattern) => pattern.test(searched));\n });\n}\n\n// The declaration itself is not a read. Shapes are stripped by walking braces,\n// never by matching to the next \"}\": a regex doing that runs past the end of\n// the type and swallows the code below it.\nfunction withoutShapes(source: string): string\n{\n let out = \"\";\n let at = 0;\n\n for (const shape of source.matchAll(/export\\s+(?:type\\s+\\w+\\s*=\\s*|interface\\s+\\w+[^{]*)\\{/g))\n {\n const from = (shape.index ?? 0) + shape[0].length;\n\n out += source.slice(at, shape.index);\n at = closes(source, from) + 1;\n }\n\n return out + source.slice(at);\n}\n","import { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { boundaries } from \"./boundaries\";\nimport { missing, oversized, undocumented, unexplained } from \"./docs\";\nimport { wiring } from \"./wiring\";\n\nexport type Wrong = {\n check: \"boundaries\" | \"wiring\" | \"oversized\" | \"missing\" | \"unexplained\" | \"undocumented\";\n message: string;\n};\n\nexport type Checking = {\n root?: string;\n plugins?: string;\n\n /** Where pure code shared between plugins lives. */\n utils?: string;\n docs?: string;\n required?: readonly string[];\n procedure?: string;\n limit?: number;\n};\n\nconst CONTRACT = join(dirname(fileURLToPath(import.meta.url)), \"..\", \"plugins\", \"kernel\", \"internal\", \"contract.ts\");\n\nexport const Project = {\n required: [\"#docs/usage.md\", \"#docs/stack.md\", \"#docs/architecture.md\", \"README.md\"] as const,\n\n checks: (checking: Checking = {}): Wrong[] =>\n {\n const root = checking.root ?? process.cwd();\n const docs = checking.docs ?? join(root, \"#docs\");\n const limit = checking.limit ?? 1800;\n\n return [\n ...Project.boundaries(checking.plugins ?? join(root, \"src\", \"plugins\")),\n ...Project.wiring(checking.plugins ?? join(root, \"src\", \"plugins\")),\n\n // Utils shared between plugins are checked too: a field nothing\n // reads is the same defect wherever it is declared, and code no\n // plugin owns is code nobody notices going stale.\n ...Project.wiring(checking.utils ?? join(root, \"src\", \"utils\"), false),\n ...Project.unexplained(checking.plugins ?? join(root, \"src\", \"plugins\")),\n ...Project.docs(root, docs, checking.required ?? Project.required, limit),\n ...Project.contract(checking.procedure ?? join(docs, \"procedures\", \"plugin\", \"contract.md\")),\n ];\n },\n\n boundaries: (at: string): Wrong[] =>\n {\n return boundaries(at).map((wrong) => ({ check: \"boundaries\" as const, message: wrong.message }));\n },\n\n wiring: (at: string, apart = true): Wrong[] =>\n {\n return wiring(at, apart).map((unread) => ({\n check: \"wiring\" as const,\n message: `${unread.file}: ${unread.shape}.${unread.field} is declared and nothing reads it.`,\n }));\n },\n\n unexplained: (at: string): Wrong[] =>\n {\n return unexplained(at).map((name) => ({\n check: \"unexplained\" as const,\n message: `\"${name}\" has no usage.md. A plugin nobody can read is one nobody can depend on.`,\n }));\n },\n\n docs: (root: string, at: string, required: readonly string[], limit: number): Wrong[] =>\n {\n return [\n ...oversized(at, limit).map((doc) => ({\n check: \"oversized\" as const,\n message: `${doc.path.replace(`${root}/`, \"\")} is ${String(doc.size)} characters, over ${String(limit)}.`,\n })),\n ...missing(root, required).map((path) => ({\n check: \"missing\" as const,\n message: `${path} is absent or says nothing.`,\n })),\n ];\n },\n\n contract: (procedure: string): Wrong[] =>\n {\n return undocumented(readFileSync(CONTRACT, \"utf8\"), readFileSync(procedure, \"utf8\")).map((key) => ({\n check: \"undocumented\" as const,\n message: `The contract accepts \"${key}\" and ${procedure.split(\"/\").slice(-1).join(\"\")} never names it.`,\n }));\n },\n};\n"]}
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "@onetype/stack-api-kit",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "sideEffects": false,
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./dist/index.js"
10
+ },
11
+ "./testing": {
12
+ "types": "./dist/testing.d.ts",
13
+ "import": "./dist/testing.js"
14
+ }
15
+ },
16
+ "scripts": {
17
+ "check": "tools/check.sh",
18
+ "test": "vitest run",
19
+ "typecheck": "tsc --noEmit",
20
+ "boundaries": "node tools/boundaries.mjs",
21
+ "docs": "node tools/docs.mjs",
22
+ "build": "tsup",
23
+ "prepublishOnly": "tools/check.sh && tsup"
24
+ },
25
+ "dependencies": {
26
+ "zod": "^4.5.4"
27
+ },
28
+ "peerDependencies": {
29
+ "better-sqlite3": ">=13",
30
+ "drizzle-orm": ">=0.45",
31
+ "hono": ">=4"
32
+ },
33
+ "devDependencies": {
34
+ "@types/better-sqlite3": "^7.6.13",
35
+ "@types/node": "^26.4.0",
36
+ "better-sqlite3": "^13.0.3",
37
+ "drizzle-orm": "^0.45.2",
38
+ "hono": "^4.13.5",
39
+ "tsup": "^8.5.1",
40
+ "typescript": "^5.7.0",
41
+ "vitest": "^4.1.11"
42
+ },
43
+ "description": "A kernel that holds plugins apart: contracts, routes, events, hooks, permissions and scope, refused at startup rather than in review.",
44
+ "license": "MIT",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/onetype-ai/stack-api-kit.git"
48
+ },
49
+ "homepage": "https://github.com/onetype-ai/stack-api-kit#readme",
50
+ "bugs": {
51
+ "url": "https://github.com/onetype-ai/stack-api-kit/issues"
52
+ },
53
+ "keywords": [
54
+ "plugins",
55
+ "kernel",
56
+ "api",
57
+ "hono",
58
+ "drizzle",
59
+ "sqlite",
60
+ "typescript",
61
+ "multi-tenant"
62
+ ],
63
+ "engines": {
64
+ "node": ">=22"
65
+ },
66
+ "files": [
67
+ "dist",
68
+ "README.md"
69
+ ],
70
+ "main": "./dist/index.js",
71
+ "types": "./dist/index.d.ts",
72
+ "publishConfig": {
73
+ "access": "public"
74
+ }
75
+ }