@onetype/stack-api-kit 1.0.0 → 1.0.1

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,393 @@
1
+ import type { z } from "zod";
2
+
3
+ /** Anything declared carries a sentence saying what it is for. */
4
+ export type Described = {
5
+ describe: string;
6
+ };
7
+
8
+ /** A declaration whose payload is checked before it reaches anyone. */
9
+ export type Schematic = Described & {
10
+ schema: z.ZodType;
11
+ };
12
+
13
+ /** What a plugin may do, named so a project can grant it. */
14
+ export type Permission = Described;
15
+
16
+ /** An event a plugin publishes. Delivered after the work it announces. */
17
+ export type Event = Schematic;
18
+
19
+ /**
20
+ * What a listener does when an event arrives.
21
+ *
22
+ * `payload` is `unknown`, never `never`: a handler typed `(payload: never)`
23
+ * accepts any annotation its author writes, because of contravariance, so the
24
+ * compiler endorses a claim about a completely different schema.
25
+ */
26
+ export type Listener<Context, Payload = unknown> = Described & {
27
+ handle: (payload: Payload, ctx: Context) => void | Promise<void>;
28
+ };
29
+
30
+ /** A point where a plugin may refuse what is about to happen. */
31
+ export type Hook = Schematic;
32
+
33
+ /** What a participant answers: nothing to allow, a reason to refuse. */
34
+ export type Participant<Context, Payload = unknown> = Described & {
35
+ handle: (payload: Payload, ctx: Context) => string | undefined | Promise<string | undefined>;
36
+ };
37
+
38
+ /** Something a plugin can be asked to do, behind the permissions it names. */
39
+ export type Command<Context, Input extends z.ZodType = z.ZodType> = Schematic & {
40
+ schema: Input;
41
+ requires?: readonly string[];
42
+ run: (input: z.infer<Input>, ctx: Context) => void | Promise<void>;
43
+ };
44
+
45
+ /** The verbs a route may answer. */
46
+ export type Method = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
47
+
48
+ /**
49
+ * One endpoint.
50
+ *
51
+ * `input` and `output` are both required, and neither is optional sugar.
52
+ * `input` is the only thing a handler ever reads: what arrived unparsed does
53
+ * not reach it. `output` is a whitelist of what may leave, so a column added
54
+ * to a table tomorrow does not appear in a response by itself.
55
+ */
56
+ export type Route<Context, Input extends z.ZodType = z.ZodType> = Described & {
57
+ method: Method;
58
+ path: string;
59
+ input: Input;
60
+ output: z.ZodType;
61
+ requires?: readonly string[];
62
+
63
+ /**
64
+ * Whether an unauthenticated caller may reach this.
65
+ *
66
+ * Absent means no. A route is closed until it says otherwise, so
67
+ * forgetting to think about it fails shut.
68
+ */
69
+ public?: boolean;
70
+
71
+ /** Requests per window for one caller, when this route needs its own. */
72
+ limit?: { requests: number; seconds: number };
73
+
74
+ /**
75
+ * Request headers this route reads, lowercase.
76
+ *
77
+ * Named rather than handed the lot: a handler that can read any header
78
+ * can read the cookie carrying the session, and a log of its input then
79
+ * carries a credential. What is not named does not arrive.
80
+ */
81
+ reads?: readonly string[];
82
+
83
+ /**
84
+ * What answers the request.
85
+ *
86
+ * `input` is what the route's own schema parsed, so a handler reads its
87
+ * fields without a cast: nothing that failed the schema reaches here.
88
+ * Return a value for a 200, or an `Answered` to say the status and
89
+ * headers as well.
90
+ */
91
+ handle: (input: z.infer<Input>, ctx: Context) => unknown | Promise<unknown>;
92
+ };
93
+
94
+ /**
95
+ * A listener, participant or command, whatever payload it was written for.
96
+ *
97
+ * Each is written against the schema it answers and stored beside others
98
+ * written against different ones, so a list holds "some listener" rather than
99
+ * one shape. Sound because the kernel parses before it calls.
100
+ */
101
+ export type Heard<Context> = Described & {
102
+ handle: (payload: never, ctx: Context) => void | Promise<void>;
103
+ };
104
+
105
+ export type Joined<Context> = Described & {
106
+ handle: (payload: never, ctx: Context) => string | undefined | Promise<string | undefined>;
107
+ };
108
+
109
+ export type Run<Context> = Schematic & {
110
+ requires?: readonly string[];
111
+ run: (input: never, ctx: Context) => void | Promise<void>;
112
+ };
113
+
114
+ /**
115
+ * One route, whatever its input schema.
116
+ *
117
+ * A route is written against its own schema and stored beside routes written
118
+ * against others, so what a list holds is "some route", not one shape. This
119
+ * says that without reaching for `any`.
120
+ */
121
+ export type Endpoint<Context> = Omit<Route<Context, z.ZodType>, "input" | "handle"> & {
122
+ input: z.ZodType;
123
+ handle: (input: never, ctx: Context) => unknown | Promise<unknown>;
124
+ };
125
+
126
+ /** Where a plugin's lines go. The project decides. */
127
+ export type Logger = {
128
+ debug: (line: string, about?: Readonly<Record<string, unknown>>) => void;
129
+ info: (line: string, about?: Readonly<Record<string, unknown>>) => void;
130
+ warn: (line: string, about?: Readonly<Record<string, unknown>>) => void;
131
+ error: (line: string, about?: Readonly<Record<string, unknown>>) => void;
132
+ };
133
+
134
+ /** Who is calling, as whatever the project decided that means. */
135
+ export type Caller = {
136
+ /** Stable identity, or undefined when nobody is signed in. */
137
+ id: string | undefined;
138
+
139
+ /** What this caller may do. The project fills it; the kernel enforces it. */
140
+ permissions: readonly string[];
141
+
142
+ /** What the project attached: a tenant, a role, a session. Opaque here. */
143
+ claims: Readonly<Record<string, unknown>>;
144
+ };
145
+
146
+ /** One outbound call, to a host the plugin declared. */
147
+ export type Outbound = {
148
+ method: Method;
149
+ url: string;
150
+ body?: unknown;
151
+ headers?: Readonly<Record<string, string>> | undefined;
152
+ signal?: AbortSignal | undefined;
153
+ };
154
+
155
+ /** What every plugin function receives. */
156
+ export type Context<Config = unknown, Services = unknown, Db = unknown> = {
157
+ name: string;
158
+ config: Config;
159
+ services: Services;
160
+
161
+ log: Logger;
162
+
163
+ /**
164
+ * What time it is, in milliseconds.
165
+ *
166
+ * Reached through the context rather than `Date.now()` so a test can pin
167
+ * it: what happens tomorrow is otherwise only testable by moving the
168
+ * machine's clock, which every other test in the process then shares.
169
+ */
170
+ now: () => number;
171
+
172
+ /** Who is calling. Absent outside a request, as in setup. */
173
+ caller: Caller | undefined;
174
+
175
+ /**
176
+ * The request headers this route declared in `reads`, lowercase.
177
+ *
178
+ * Empty outside a request, and empty for anything the route did not
179
+ * name.
180
+ */
181
+ headers: Readonly<Record<string, string>>;
182
+
183
+ /**
184
+ * This plugin's own tables.
185
+ *
186
+ * The handle carries only what this plugin declared, so a query naming
187
+ * another plugin's table does not compile. The connection underneath is
188
+ * shared, so the boundary is the compiler's rather than the database's.
189
+ */
190
+ db: Db;
191
+
192
+ /**
193
+ * Runs one query outside a transaction, in its turn.
194
+ *
195
+ * A query issued while another request's transaction is parked on an
196
+ * await joins that transaction and dies with its rollback, having told
197
+ * its caller it succeeded. Reads are safe without this; a write is not.
198
+ */
199
+ write: <Made>(run: () => Promise<Made>) => Promise<Made>;
200
+
201
+ /**
202
+ * Runs work in one transaction, rolled back if it throws.
203
+ *
204
+ * The callback is handed a context of its own, not just a handle: what it
205
+ * emits waits for the commit, and a `tx` inside it joins this one
206
+ * rather than opening a second. A caller that used the outer `ctx` would
207
+ * be writing outside the transaction it just opened.
208
+ */
209
+ tx: <Made>(run: (ctx: Context<Config, Services, Db>) => Promise<Made>) => Promise<Made>;
210
+
211
+ /** Calls a host this plugin declared in `outbound`. */
212
+ fetch: (call: Outbound) => Promise<unknown>;
213
+
214
+ events: {
215
+ /**
216
+ * Announces what happened. Inside a transaction it waits and is sent
217
+ * after the commit: an event about work that rolled back is a lie.
218
+ */
219
+ emit: (event: string, payload: unknown) => void;
220
+ };
221
+
222
+ hooks: {
223
+ /** Runs a hook and answers the first refusal, or undefined. */
224
+ run: (hook: string, payload: unknown) => Promise<string | undefined>;
225
+ };
226
+
227
+ permissions: {
228
+ has: (permission: string) => boolean;
229
+ all: (permissions: readonly string[]) => boolean;
230
+
231
+ /** What the project attached to this caller, unread by the kernel. */
232
+ claims: () => Readonly<Record<string, unknown>>;
233
+ };
234
+
235
+ commands: {
236
+ run: (command: string, input: unknown) => Promise<void>;
237
+
238
+ /**
239
+ * Runs one later, in seconds from now.
240
+ *
241
+ * Only a command this plugin declares, and it runs with no caller:
242
+ * whatever it needs to know about whose work it is travels in the
243
+ * input, exactly as an event's payload does.
244
+ *
245
+ * Asked for inside a transaction, it is written by that transaction
246
+ * and rolls back with it. A command that throws is tried again.
247
+ */
248
+ later: (command: string, input: unknown, inSeconds: number) => void;
249
+ };
250
+
251
+ /**
252
+ * Takes ownership of something that outlives a request.
253
+ *
254
+ * Services are built per request, because one holding a caller would
255
+ * answer the next request as the previous one. A connection is the
256
+ * opposite: opened once in `setup`, used by every request, closed in
257
+ * `teardown`. This is where it lives, one per plugin, and no plugin
258
+ * reaches another's.
259
+ */
260
+ owns: <Owned>(owned: Owned) => Owned;
261
+
262
+ /** What this plugin took ownership of, or undefined before `setup` did. */
263
+ owned: <Owned>() => Owned | undefined;
264
+
265
+ /**
266
+ * What narrows every read of a table this plugin declared a `scope` for.
267
+ *
268
+ * Answers the caller's value for the declared claim, refusing when there
269
+ * is none. Nothing makes a query call this: one that forgets reads every
270
+ * scope's rows and compiles, which is why every scoped read is tested
271
+ * with a stranger's id.
272
+ *
273
+ * ```ts
274
+ * .where(and(eq(items.id, id), ctx.scoped("items")))
275
+ * ```
276
+ */
277
+ scoped: <Condition = unknown>(table: string) => Condition;
278
+
279
+ /**
280
+ * The row's scope column, filled from the caller.
281
+ *
282
+ * A condition narrows a read, and an insert has no condition: without
283
+ * this, a caller in one tenant can write a row stamped with another's.
284
+ * Spread it over what you are writing so the column is not yours to
285
+ * remember, or to get wrong.
286
+ *
287
+ * ```ts
288
+ * .values({ ...row, ...ctx.stamped("items") })
289
+ * ```
290
+ */
291
+ stamped: (table: string) => Readonly<Record<string, string>>;
292
+
293
+ /**
294
+ * The same plugin, acting for the scope this names.
295
+ *
296
+ * A listener runs on nobody's behalf, so `scoped` and `stamped` refuse
297
+ * there, and every method they reach refuses with them. This says whose
298
+ * work the payload announced, so the ordinary path works instead of a
299
+ * second unscoped one written beside it.
300
+ *
301
+ * ```ts
302
+ * handle: (gone, ctx) => Orders.dropFor(ctx.forScope(gone.shopId), gone.id)
303
+ * ```
304
+ *
305
+ * Refused where a caller already exists: inside a request the scope is
306
+ * decided by who is asking, and choosing another there is how a caller
307
+ * reaches rows that are not theirs.
308
+ */
309
+ forScope: (claim: string) => Context<Config, Services, Db>;
310
+
311
+ /** Another plugin's services, by name. Only what `dependsOn` names. */
312
+ use: <Reached>(plugin: string) => Reached;
313
+ };
314
+
315
+ /**
316
+ * Blocks inference at this position.
317
+ *
318
+ * Services is inferred from what `services` returns and from nowhere else. A
319
+ * callback taking a context would otherwise be a second inference site, and
320
+ * two candidates for one parameter resolve to unknown.
321
+ */
322
+ type Given<Made> = NoInfer<Made>;
323
+
324
+ /** Everything a plugin declares about itself. */
325
+ export type Definition<
326
+ Schema extends z.ZodType = z.ZodType,
327
+ Services = unknown,
328
+ Db = unknown,
329
+ > = Described & {
330
+ version: string;
331
+ dependsOn?: readonly string[];
332
+ config?: Schema;
333
+
334
+ permissions?: Readonly<Record<string, Permission>>;
335
+
336
+ /** This plugin's tables, in its own namespace. Nobody else reads them. */
337
+ tables?: Readonly<Record<string, unknown>>;
338
+
339
+ /**
340
+ * Which claim decides whose rows these are, and where each table carries
341
+ * it.
342
+ *
343
+ * The kit knows nothing about tenants: it does not know what one is, what
344
+ * the claim means, or whether a project has any. What it knows, once this
345
+ * is declared, is that a read of a named table without that column is a
346
+ * read of somebody else's rows, so `ctx.db` stops handing one out and
347
+ * `ctx.scoped` hands out the query already narrowed.
348
+ *
349
+ * Declaring it also decides the failure: a caller carrying no such claim
350
+ * is refused rather than defaulted, because a default tenant is
351
+ * everybody's tenant.
352
+ */
353
+ scope?: {
354
+ describe: string;
355
+ claim: string;
356
+ tables: Readonly<Record<string, string>>;
357
+ };
358
+
359
+ /** Where its migrations live, run in dependency order before setup. */
360
+ migrations?: string;
361
+
362
+ /** Hosts this plugin may call. Anything else is refused before it dials. */
363
+ outbound?: readonly string[];
364
+
365
+ services?: (ctx: Context<z.infer<Schema>, never, Db>) => Services;
366
+
367
+ /**
368
+ * The endpoints this plugin answers.
369
+ *
370
+ * Each route carries its own input schema, so `handle` reads what that
371
+ * schema parsed rather than `unknown`. A handler taking a narrower input
372
+ * is sound here precisely because the kernel parses before it calls: what
373
+ * failed the schema never arrives.
374
+ */
375
+ routes?: readonly Endpoint<Context<z.infer<Schema>, Given<Services>, Db>>[];
376
+
377
+ emits?: Readonly<Record<string, Event>>;
378
+ listens?: Readonly<Record<string, Heard<Context<z.infer<Schema>, Given<Services>, Db>>>>;
379
+
380
+ hooks?: Readonly<Record<string, Hook>>;
381
+ participates?: Readonly<Record<string, Joined<Context<z.infer<Schema>, Given<Services>, Db>>>>;
382
+
383
+ commands?: Readonly<Record<string, Run<Context<z.infer<Schema>, Given<Services>, Db>>>>;
384
+
385
+ setup?: (ctx: Context<z.infer<Schema>, Given<Services>, Db>) => void | Promise<void>;
386
+ teardown?: (ctx: Context<z.infer<Schema>, Given<Services>, Db>) => void | Promise<void>;
387
+ };
388
+
389
+ /** A plugin: its name, and what it declared. */
390
+ export type Plugin = {
391
+ name: string;
392
+ definition: Definition;
393
+ };
package/dist/testing.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { database, createKernel, limiter } from './chunk-UCWOCNTI.js';
2
- import { readdirSync, existsSync, readFileSync, statSync } from 'fs';
2
+ import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
3
3
  import { join, dirname, relative } from 'path';
4
4
  import { fileURLToPath } from 'url';
5
5
 
@@ -391,7 +391,10 @@ function withoutShapes(source) {
391
391
  }
392
392
 
393
393
  // src/testing/project.ts
394
- var CONTRACT = join(dirname(fileURLToPath(import.meta.url)), "..", "plugins", "kernel", "internal", "contract.ts");
394
+ var CONTRACT = [
395
+ join(dirname(fileURLToPath(import.meta.url)), "..", "plugins", "kernel", "internal", "contract.ts"),
396
+ join(dirname(fileURLToPath(import.meta.url)), "contract.ts")
397
+ ].find((path) => existsSync(path)) ?? "";
395
398
  var Project = {
396
399
  required: ["#docs/usage.md", "#docs/stack.md", "#docs/architecture.md", "README.md"],
397
400
  checks: (checking = {}) => {
@@ -1 +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"]}
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 { existsSync, 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\n/**\n * The kit's own contract, read to list the keys a plugin may declare.\n *\n * Two places because there are two shapes: the source tree when a project\n * links this package, and beside the bundle when it installed it.\n */\nconst CONTRACT = [\n join(dirname(fileURLToPath(import.meta.url)), \"..\", \"plugins\", \"kernel\", \"internal\", \"contract.ts\"),\n join(dirname(fileURLToPath(import.meta.url)), \"contract.ts\"),\n].find((path) => existsSync(path)) ?? \"\";\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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onetype/stack-api-kit",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "exports": {