@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,654 @@
1
+ import { z } from 'zod';
2
+ import { drizzle } from 'drizzle-orm/better-sqlite3';
3
+
4
+ /** Anything declared carries a sentence saying what it is for. */
5
+ type Described = {
6
+ describe: string;
7
+ };
8
+ /** A declaration whose payload is checked before it reaches anyone. */
9
+ type Schematic = Described & {
10
+ schema: z.ZodType;
11
+ };
12
+ /** What a plugin may do, named so a project can grant it. */
13
+ type Permission = Described;
14
+ /** An event a plugin publishes. Delivered after the work it announces. */
15
+ type Event = Schematic;
16
+ /**
17
+ * What a listener does when an event arrives.
18
+ *
19
+ * `payload` is `unknown`, never `never`: a handler typed `(payload: never)`
20
+ * accepts any annotation its author writes, because of contravariance, so the
21
+ * compiler endorses a claim about a completely different schema.
22
+ */
23
+ type Listener<Context, Payload = unknown> = Described & {
24
+ handle: (payload: Payload, ctx: Context) => void | Promise<void>;
25
+ };
26
+ /** A point where a plugin may refuse what is about to happen. */
27
+ type Hook = Schematic;
28
+ /** What a participant answers: nothing to allow, a reason to refuse. */
29
+ type Participant<Context, Payload = unknown> = Described & {
30
+ handle: (payload: Payload, ctx: Context) => string | undefined | Promise<string | undefined>;
31
+ };
32
+ /** Something a plugin can be asked to do, behind the permissions it names. */
33
+ type Command<Context, Input extends z.ZodType = z.ZodType> = Schematic & {
34
+ schema: Input;
35
+ requires?: readonly string[];
36
+ run: (input: z.infer<Input>, ctx: Context) => void | Promise<void>;
37
+ };
38
+ /** The verbs a route may answer. */
39
+ type Method = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
40
+ /**
41
+ * One endpoint.
42
+ *
43
+ * `input` and `output` are both required, and neither is optional sugar.
44
+ * `input` is the only thing a handler ever reads: what arrived unparsed does
45
+ * not reach it. `output` is a whitelist of what may leave, so a column added
46
+ * to a table tomorrow does not appear in a response by itself.
47
+ */
48
+ type Route<Context, Input extends z.ZodType = z.ZodType> = Described & {
49
+ method: Method;
50
+ path: string;
51
+ input: Input;
52
+ output: z.ZodType;
53
+ requires?: readonly string[];
54
+ /**
55
+ * Whether an unauthenticated caller may reach this.
56
+ *
57
+ * Absent means no. A route is closed until it says otherwise, so
58
+ * forgetting to think about it fails shut.
59
+ */
60
+ public?: boolean;
61
+ /** Requests per window for one caller, when this route needs its own. */
62
+ limit?: {
63
+ requests: number;
64
+ seconds: number;
65
+ };
66
+ /**
67
+ * Request headers this route reads, lowercase.
68
+ *
69
+ * Named rather than handed the lot: a handler that can read any header
70
+ * can read the cookie carrying the session, and a log of its input then
71
+ * carries a credential. What is not named does not arrive.
72
+ */
73
+ reads?: readonly string[];
74
+ /**
75
+ * What answers the request.
76
+ *
77
+ * `input` is what the route's own schema parsed, so a handler reads its
78
+ * fields without a cast: nothing that failed the schema reaches here.
79
+ * Return a value for a 200, or an `Answered` to say the status and
80
+ * headers as well.
81
+ */
82
+ handle: (input: z.infer<Input>, ctx: Context) => unknown | Promise<unknown>;
83
+ };
84
+ /**
85
+ * A listener, participant or command, whatever payload it was written for.
86
+ *
87
+ * Each is written against the schema it answers and stored beside others
88
+ * written against different ones, so a list holds "some listener" rather than
89
+ * one shape. Sound because the kernel parses before it calls.
90
+ */
91
+ type Heard<Context> = Described & {
92
+ handle: (payload: never, ctx: Context) => void | Promise<void>;
93
+ };
94
+ type Joined<Context> = Described & {
95
+ handle: (payload: never, ctx: Context) => string | undefined | Promise<string | undefined>;
96
+ };
97
+ type Run<Context> = Schematic & {
98
+ requires?: readonly string[];
99
+ run: (input: never, ctx: Context) => void | Promise<void>;
100
+ };
101
+ /**
102
+ * One route, whatever its input schema.
103
+ *
104
+ * A route is written against its own schema and stored beside routes written
105
+ * against others, so what a list holds is "some route", not one shape. This
106
+ * says that without reaching for `any`.
107
+ */
108
+ type Endpoint<Context> = Omit<Route<Context, z.ZodType>, "input" | "handle"> & {
109
+ input: z.ZodType;
110
+ handle: (input: never, ctx: Context) => unknown | Promise<unknown>;
111
+ };
112
+ /** Where a plugin's lines go. The project decides. */
113
+ type Logger = {
114
+ debug: (line: string, about?: Readonly<Record<string, unknown>>) => void;
115
+ info: (line: string, about?: Readonly<Record<string, unknown>>) => void;
116
+ warn: (line: string, about?: Readonly<Record<string, unknown>>) => void;
117
+ error: (line: string, about?: Readonly<Record<string, unknown>>) => void;
118
+ };
119
+ /** Who is calling, as whatever the project decided that means. */
120
+ type Caller = {
121
+ /** Stable identity, or undefined when nobody is signed in. */
122
+ id: string | undefined;
123
+ /** What this caller may do. The project fills it; the kernel enforces it. */
124
+ permissions: readonly string[];
125
+ /** What the project attached: a tenant, a role, a session. Opaque here. */
126
+ claims: Readonly<Record<string, unknown>>;
127
+ };
128
+ /** One outbound call, to a host the plugin declared. */
129
+ type Outbound = {
130
+ method: Method;
131
+ url: string;
132
+ body?: unknown;
133
+ headers?: Readonly<Record<string, string>> | undefined;
134
+ signal?: AbortSignal | undefined;
135
+ };
136
+ /** What every plugin function receives. */
137
+ type Context<Config = unknown, Services = unknown, Db = unknown> = {
138
+ name: string;
139
+ config: Config;
140
+ services: Services;
141
+ log: Logger;
142
+ /**
143
+ * What time it is, in milliseconds.
144
+ *
145
+ * Reached through the context rather than `Date.now()` so a test can pin
146
+ * it: what happens tomorrow is otherwise only testable by moving the
147
+ * machine's clock, which every other test in the process then shares.
148
+ */
149
+ now: () => number;
150
+ /** Who is calling. Absent outside a request, as in setup. */
151
+ caller: Caller | undefined;
152
+ /**
153
+ * The request headers this route declared in `reads`, lowercase.
154
+ *
155
+ * Empty outside a request, and empty for anything the route did not
156
+ * name.
157
+ */
158
+ headers: Readonly<Record<string, string>>;
159
+ /**
160
+ * This plugin's own tables.
161
+ *
162
+ * The handle carries only what this plugin declared, so a query naming
163
+ * another plugin's table does not compile. The connection underneath is
164
+ * shared, so the boundary is the compiler's rather than the database's.
165
+ */
166
+ db: Db;
167
+ /**
168
+ * Runs one query outside a transaction, in its turn.
169
+ *
170
+ * A query issued while another request's transaction is parked on an
171
+ * await joins that transaction and dies with its rollback, having told
172
+ * its caller it succeeded. Reads are safe without this; a write is not.
173
+ */
174
+ write: <Made>(run: () => Promise<Made>) => Promise<Made>;
175
+ /**
176
+ * Runs work in one transaction, rolled back if it throws.
177
+ *
178
+ * The callback is handed a context of its own, not just a handle: what it
179
+ * emits waits for the commit, and a `tx` inside it joins this one
180
+ * rather than opening a second. A caller that used the outer `ctx` would
181
+ * be writing outside the transaction it just opened.
182
+ */
183
+ tx: <Made>(run: (ctx: Context<Config, Services, Db>) => Promise<Made>) => Promise<Made>;
184
+ /** Calls a host this plugin declared in `outbound`. */
185
+ fetch: (call: Outbound) => Promise<unknown>;
186
+ events: {
187
+ /**
188
+ * Announces what happened. Inside a transaction it waits and is sent
189
+ * after the commit: an event about work that rolled back is a lie.
190
+ */
191
+ emit: (event: string, payload: unknown) => void;
192
+ };
193
+ hooks: {
194
+ /** Runs a hook and answers the first refusal, or undefined. */
195
+ run: (hook: string, payload: unknown) => Promise<string | undefined>;
196
+ };
197
+ permissions: {
198
+ has: (permission: string) => boolean;
199
+ all: (permissions: readonly string[]) => boolean;
200
+ /** What the project attached to this caller, unread by the kernel. */
201
+ claims: () => Readonly<Record<string, unknown>>;
202
+ };
203
+ commands: {
204
+ run: (command: string, input: unknown) => Promise<void>;
205
+ /**
206
+ * Runs one later, in seconds from now.
207
+ *
208
+ * Only a command this plugin declares, and it runs with no caller:
209
+ * whatever it needs to know about whose work it is travels in the
210
+ * input, exactly as an event's payload does.
211
+ *
212
+ * Asked for inside a transaction, it is written by that transaction
213
+ * and rolls back with it. A command that throws is tried again.
214
+ */
215
+ later: (command: string, input: unknown, inSeconds: number) => void;
216
+ };
217
+ /**
218
+ * Takes ownership of something that outlives a request.
219
+ *
220
+ * Services are built per request, because one holding a caller would
221
+ * answer the next request as the previous one. A connection is the
222
+ * opposite: opened once in `setup`, used by every request, closed in
223
+ * `teardown`. This is where it lives, one per plugin, and no plugin
224
+ * reaches another's.
225
+ */
226
+ owns: <Owned>(owned: Owned) => Owned;
227
+ /** What this plugin took ownership of, or undefined before `setup` did. */
228
+ owned: <Owned>() => Owned | undefined;
229
+ /**
230
+ * What narrows every read of a table this plugin declared a `scope` for.
231
+ *
232
+ * Answers the caller's value for the declared claim, refusing when there
233
+ * is none. Nothing makes a query call this: one that forgets reads every
234
+ * scope's rows and compiles, which is why every scoped read is tested
235
+ * with a stranger's id.
236
+ *
237
+ * ```ts
238
+ * .where(and(eq(items.id, id), ctx.scoped("items")))
239
+ * ```
240
+ */
241
+ scoped: <Condition = unknown>(table: string) => Condition;
242
+ /**
243
+ * The row's scope column, filled from the caller.
244
+ *
245
+ * A condition narrows a read, and an insert has no condition: without
246
+ * this, a caller in one tenant can write a row stamped with another's.
247
+ * Spread it over what you are writing so the column is not yours to
248
+ * remember, or to get wrong.
249
+ *
250
+ * ```ts
251
+ * .values({ ...row, ...ctx.stamped("items") })
252
+ * ```
253
+ */
254
+ stamped: (table: string) => Readonly<Record<string, string>>;
255
+ /**
256
+ * The same plugin, acting for the scope this names.
257
+ *
258
+ * A listener runs on nobody's behalf, so `scoped` and `stamped` refuse
259
+ * there, and every method they reach refuses with them. This says whose
260
+ * work the payload announced, so the ordinary path works instead of a
261
+ * second unscoped one written beside it.
262
+ *
263
+ * ```ts
264
+ * handle: (gone, ctx) => Orders.dropFor(ctx.forScope(gone.shopId), gone.id)
265
+ * ```
266
+ *
267
+ * Refused where a caller already exists: inside a request the scope is
268
+ * decided by who is asking, and choosing another there is how a caller
269
+ * reaches rows that are not theirs.
270
+ */
271
+ forScope: (claim: string) => Context<Config, Services, Db>;
272
+ /** Another plugin's services, by name. Only what `dependsOn` names. */
273
+ use: <Reached>(plugin: string) => Reached;
274
+ };
275
+ /**
276
+ * Blocks inference at this position.
277
+ *
278
+ * Services is inferred from what `services` returns and from nowhere else. A
279
+ * callback taking a context would otherwise be a second inference site, and
280
+ * two candidates for one parameter resolve to unknown.
281
+ */
282
+ type Given<Made> = NoInfer<Made>;
283
+ /** Everything a plugin declares about itself. */
284
+ type Definition<Schema extends z.ZodType = z.ZodType, Services = unknown, Db = unknown> = Described & {
285
+ version: string;
286
+ dependsOn?: readonly string[];
287
+ config?: Schema;
288
+ permissions?: Readonly<Record<string, Permission>>;
289
+ /** This plugin's tables, in its own namespace. Nobody else reads them. */
290
+ tables?: Readonly<Record<string, unknown>>;
291
+ /**
292
+ * Which claim decides whose rows these are, and where each table carries
293
+ * it.
294
+ *
295
+ * The kit knows nothing about tenants: it does not know what one is, what
296
+ * the claim means, or whether a project has any. What it knows, once this
297
+ * is declared, is that a read of a named table without that column is a
298
+ * read of somebody else's rows, so `ctx.db` stops handing one out and
299
+ * `ctx.scoped` hands out the query already narrowed.
300
+ *
301
+ * Declaring it also decides the failure: a caller carrying no such claim
302
+ * is refused rather than defaulted, because a default tenant is
303
+ * everybody's tenant.
304
+ */
305
+ scope?: {
306
+ describe: string;
307
+ claim: string;
308
+ tables: Readonly<Record<string, string>>;
309
+ };
310
+ /** Where its migrations live, run in dependency order before setup. */
311
+ migrations?: string;
312
+ /** Hosts this plugin may call. Anything else is refused before it dials. */
313
+ outbound?: readonly string[];
314
+ services?: (ctx: Context<z.infer<Schema>, never, Db>) => Services;
315
+ /**
316
+ * The endpoints this plugin answers.
317
+ *
318
+ * Each route carries its own input schema, so `handle` reads what that
319
+ * schema parsed rather than `unknown`. A handler taking a narrower input
320
+ * is sound here precisely because the kernel parses before it calls: what
321
+ * failed the schema never arrives.
322
+ */
323
+ routes?: readonly Endpoint<Context<z.infer<Schema>, Given<Services>, Db>>[];
324
+ emits?: Readonly<Record<string, Event>>;
325
+ listens?: Readonly<Record<string, Heard<Context<z.infer<Schema>, Given<Services>, Db>>>>;
326
+ hooks?: Readonly<Record<string, Hook>>;
327
+ participates?: Readonly<Record<string, Joined<Context<z.infer<Schema>, Given<Services>, Db>>>>;
328
+ commands?: Readonly<Record<string, Run<Context<z.infer<Schema>, Given<Services>, Db>>>>;
329
+ setup?: (ctx: Context<z.infer<Schema>, Given<Services>, Db>) => void | Promise<void>;
330
+ teardown?: (ctx: Context<z.infer<Schema>, Given<Services>, Db>) => void | Promise<void>;
331
+ };
332
+ /** A plugin: its name, and what it declared. */
333
+ type Plugin = {
334
+ name: string;
335
+ definition: Definition;
336
+ };
337
+
338
+ type Failure = {
339
+ event: string;
340
+ plugin: string;
341
+ error: unknown;
342
+ at: number;
343
+ };
344
+
345
+ /** What decides whether one caller has any budget left on one route. */
346
+ type Budget = {
347
+ take: (key: string, window: {
348
+ requests: number;
349
+ seconds: number;
350
+ }) => {
351
+ allowed: boolean;
352
+ resetsIn: number;
353
+ };
354
+ };
355
+ /** One request, as it reaches the kernel. */
356
+ type Incoming = {
357
+ method: Method;
358
+ path: string;
359
+ input: unknown;
360
+ caller?: Caller | undefined;
361
+ /** The request's headers, lowercase. A route sees only what it declared. */
362
+ headers?: Readonly<Record<string, string>> | undefined;
363
+ /**
364
+ * Where it came from, when nobody is signed in: an address, a key, or
365
+ * whatever the project counts anonymous callers by. Only a rate limit
366
+ * reads it.
367
+ */
368
+ from?: string | undefined;
369
+ };
370
+ /** What the kernel answers: a status, and a body already safe to send. */
371
+ type Outgoing = {
372
+ status: number;
373
+ body: unknown;
374
+ headers?: Readonly<Record<string, string>>;
375
+ };
376
+
377
+ /**
378
+ * What the kernel needs to reach storage.
379
+ *
380
+ * A shape rather than a driver: the kernel never imports a database, so a
381
+ * project chooses one and a test passes its own. Narrower than what
382
+ * `database()` returns, which also opens, migrates and closes: the kernel
383
+ * needs none of that and should not be able to do it.
384
+ */
385
+ type Storage = {
386
+ /** One plugin's own handle. What it holds is the project's business. */
387
+ of: (plugin: string) => unknown;
388
+ /** Runs work in one transaction, rolled back if it throws. */
389
+ tx: <Made>(plugin: string, run: (db: unknown) => Promise<Made>) => Promise<Made>;
390
+ /**
391
+ * Runs work that is not in a transaction, but never during someone
392
+ * else's. Optional, so a project may pass a store that needs no such
393
+ * ordering.
394
+ */
395
+ write?: <Made>(run: () => Promise<Made>) => Promise<Made>;
396
+ /** Whether a transaction is open right now. For diagnosis. */
397
+ inTransaction?: () => boolean;
398
+ };
399
+ /** One event, as it waits to be delivered. */
400
+ type Announcement = {
401
+ id: string;
402
+ plugin: string;
403
+ name: string;
404
+ payload: unknown;
405
+ };
406
+ /**
407
+ * Where events wait, so one is never lost between a commit and its delivery.
408
+ *
409
+ * Without this, an event lives only in memory: the work commits, the process
410
+ * dies, and the listener is never called by anything. Nobody is told, because
411
+ * the emitter was told nothing to begin with.
412
+ *
413
+ * `keep` runs inside the emitting transaction, so an event is written exactly
414
+ * when the work it announces is, and rolled back with it. `sent` runs after
415
+ * delivery. Anything still kept at startup was interrupted, and `waiting`
416
+ * hands it back to be delivered again.
417
+ *
418
+ * Delivery is therefore at least once, never exactly once: a listener that
419
+ * writes must be able to run twice on one event without doubling anything.
420
+ */
421
+ type Outbox = {
422
+ /** Writes events inside the transaction that emitted them. */
423
+ keep: (db: unknown, announcements: readonly Announcement[]) => void;
424
+ /** Marks one delivered. */
425
+ sent: (id: string) => Promise<void>;
426
+ /** What was kept but never marked sent. Read once, at startup. */
427
+ waiting: () => Promise<readonly Announcement[]>;
428
+ };
429
+ /** One command waiting for its moment. */
430
+ type Scheduled = {
431
+ id: string;
432
+ plugin: string;
433
+ command: string;
434
+ input: unknown;
435
+ at: number;
436
+ attempts: number;
437
+ };
438
+ /**
439
+ * Where work waits until it is time.
440
+ *
441
+ * The kernel has no clock of its own and no timer: it asks `due` on a beat
442
+ * the project set, runs what it is handed, and says how it went. Everything
443
+ * that has to survive a restart lives in the database, so a process that
444
+ * stops between taking a job and finishing it leaves the job takeable again.
445
+ *
446
+ * `take` is what makes one process pick up a job and not another: it must
447
+ * claim and return in one step, or two processes run the same work.
448
+ */
449
+ type Schedule = {
450
+ /** Writes one, inside the transaction that asked for it when there is one. */
451
+ keep: (db: unknown, job: Scheduled) => void;
452
+ /** Claims what is due, at most `limit`, marking each taken. */
453
+ take: (now: number, limit: number) => Promise<readonly Scheduled[]>;
454
+ /** It ran. Forget it. */
455
+ done: (id: string) => Promise<void>;
456
+ /** It threw. Put it back for `at`, having counted the attempt. */
457
+ failed: (id: string, at: number) => Promise<void>;
458
+ /** It threw too many times. Stop trying. */
459
+ gaveUp: (id: string) => Promise<void>;
460
+ };
461
+ /**
462
+ * How a scope becomes a condition the database understands.
463
+ *
464
+ * The kernel imports no driver, so it cannot build one: it knows which table
465
+ * and which value, and the project turns that into whatever its store speaks.
466
+ */
467
+ type Narrowing = (table: string, column: string, value: string) => unknown;
468
+ /** What the kernel needs to call another server. */
469
+ type Dialer = (call: Outbound) => Promise<unknown>;
470
+
471
+ /** Where a line goes. The project decides; a plugin never writes directly. */
472
+ type Log = (level: "debug" | "info" | "warn" | "error", plugin: string, line: string, about?: Readonly<Record<string, unknown>>) => void;
473
+ /** What a project gives the kernel. */
474
+ type Options = {
475
+ plugins: readonly Plugin[];
476
+ config?: Readonly<Record<string, unknown>>;
477
+ db?: Storage;
478
+ dial?: Dialer;
479
+ log?: Log;
480
+ /**
481
+ * What counts requests against a route's declared budget.
482
+ *
483
+ * Omit it and a `limit` is inert, which is why `start` says so rather
484
+ * than letting a declared budget quietly enforce nothing.
485
+ */
486
+ budget?: Budget;
487
+ /**
488
+ * Where events wait between the transaction that emitted them and the
489
+ * listener that hears them.
490
+ *
491
+ * Without one, an event that is emitted lives only in memory: the work
492
+ * commits, the process stops, and nothing ever calls the listener. With
493
+ * one, delivery is at least once, so a listener that writes must survive
494
+ * being called twice.
495
+ */
496
+ outbox?: Outbox;
497
+ /**
498
+ * What the current time is, in milliseconds.
499
+ *
500
+ * A test pins it to make tomorrow reachable without moving the machine's
501
+ * clock, which every other test in the process would then share.
502
+ */
503
+ now?: () => number;
504
+ /**
505
+ * Where work waits until it is time, and how often to look.
506
+ *
507
+ * Without one, `ctx.commands.later` refuses: a plugin that can ask for
508
+ * later work in a deployment that cannot run it would be told nothing.
509
+ */
510
+ schedule?: Schedule;
511
+ /** How often to ask the schedule what is due, in milliseconds. */
512
+ beat?: number;
513
+ /**
514
+ * How many times a scheduled command may throw before it is abandoned.
515
+ *
516
+ * Eight by default, which is roughly four minutes of backing off. A job
517
+ * that has failed that often is failing on something a retry will not
518
+ * fix, and the line saying it gave up is worth more than the ninth try.
519
+ */
520
+ attempts?: number;
521
+ /**
522
+ * How a declared scope becomes a condition the store understands.
523
+ *
524
+ * The kernel imports no driver, so a project that declares a scope also
525
+ * says how to narrow by it.
526
+ */
527
+ narrow?: Narrowing;
528
+ /**
529
+ * How long a hook participant has to answer, in milliseconds.
530
+ *
531
+ * A participant that never answers holds the request open, and a throw is
532
+ * already a refusal, so silence is treated as one too.
533
+ */
534
+ patience?: number;
535
+ };
536
+ /** A route, and the plugin it came from. */
537
+ type Registered = {
538
+ plugin: string;
539
+ method: Method;
540
+ path: string;
541
+ describe: string;
542
+ requires: readonly string[];
543
+ public: boolean;
544
+ limit: {
545
+ requests: number;
546
+ seconds: number;
547
+ } | undefined;
548
+ /** The request headers this route declared it reads, lowercase. */
549
+ reads: readonly string[];
550
+ };
551
+ /** What a project holds after createKernel. */
552
+ type Kernel = {
553
+ start: () => Promise<void>;
554
+ stop: () => Promise<void>;
555
+ started: () => boolean;
556
+ routes: () => readonly Registered[];
557
+ handle: (incoming: Incoming) => Promise<Outgoing>;
558
+ context: (plugin: string, caller?: Caller) => Context;
559
+ events: {
560
+ failures: () => readonly Failure[];
561
+ };
562
+ /**
563
+ * Runs whatever the schedule says is due, once, and waits for it.
564
+ *
565
+ * What the beat does on a timer, asked for. A test moves its clock and
566
+ * calls this instead of waiting a real second for an interval it does not
567
+ * control.
568
+ */
569
+ due: () => Promise<void>;
570
+ run: (command: string, input: unknown, caller?: Caller) => Promise<void>;
571
+ };
572
+ /**
573
+ * Builds a kernel from what the plugins declared.
574
+ *
575
+ * Nothing runs here: `start` validates first, and either brings up every
576
+ * plugin or throws. A half-started kernel behaves according to where it
577
+ * stopped, which is not a state anyone can reason about.
578
+ */
579
+ declare function createKernel(options: Options): Kernel;
580
+
581
+ /** What opening a database needs to know. */
582
+ type Opening = {
583
+ /** A path, or ":memory:" for one that lives as long as the process. */
584
+ file: string;
585
+ /** How long a writer waits for another to finish, in milliseconds. */
586
+ busyMs?: number;
587
+ /** Whether to keep the write-ahead log. Off for :memory:, which has none. */
588
+ wal?: boolean;
589
+ };
590
+
591
+ /** Where one plugin keeps its migrations. */
592
+ type Source = {
593
+ plugin: string;
594
+ from: string;
595
+ };
596
+ /** One migration file, as it sits on disk. */
597
+ type Step = {
598
+ plugin: string;
599
+ name: string;
600
+ sql: string;
601
+ hash: string;
602
+ };
603
+ /** What went wrong, in a sentence naming the file. */
604
+ declare class MigrationFault extends Error {
605
+ readonly plugin: string;
606
+ readonly step: string | undefined;
607
+ constructor(message: string, plugin: string, step?: string);
608
+ }
609
+
610
+ type Tables = Readonly<Record<string, unknown>>;
611
+ type Handle = ReturnType<typeof drizzle>;
612
+
613
+ /** What building a store needs: where the file is, and who owns what. */
614
+ type Settings = Opening & {
615
+ tables: Readonly<Record<string, Tables>>;
616
+ };
617
+ /**
618
+ * What a project holds after opening a database.
619
+ *
620
+ * Wider than the kernel's `Storage`: this one also migrates and closes, which
621
+ * a plugin has no business doing and the kernel never asks for.
622
+ */
623
+ /**
624
+ * What `start` needs of a database, whichever one it is.
625
+ *
626
+ * Wider than the kernel's `Storage`: this one also migrates and closes, which
627
+ * a plugin has no business doing and the kernel never asks for. `of` answers
628
+ * `unknown` because the kit does not know what database it was given; a
629
+ * plugin names the shape it expects through `definePlugin.over`.
630
+ */
631
+ type Store<Handed = unknown> = {
632
+ of: (plugin: string) => Handed;
633
+ /** An outbox in this same database, when the store can hold one. */
634
+ outbox?: () => Outbox;
635
+ /** A schedule in this same database, for work asked for later. */
636
+ schedule?: () => Schedule;
637
+ /** How a declared scope becomes a condition over the tables it was given. */
638
+ narrowing?: () => Narrowing;
639
+ tx: <Made>(plugin: string, run: (db: unknown) => Promise<Made>) => Promise<Made>;
640
+ write: <Made>(run: () => Promise<Made>) => Promise<Made>;
641
+ inTransaction: () => boolean;
642
+ migrate: (sources: readonly Source[]) => Step[];
643
+ close: () => void;
644
+ };
645
+
646
+ /**
647
+ * Opens a database and holds one handle per plugin over it.
648
+ *
649
+ * Built by the project rather than reached for: two stores can exist in one
650
+ * process without seeing each other, which is what a test needs.
651
+ */
652
+ declare function database(settings: Settings): Store<Handle>;
653
+
654
+ export { type Announcement as A, type Budget as B, type Context as C, type Definition as D, type Endpoint as E, type Failure as F, type Handle as H, type Incoming as I, type Joined as J, type Kernel as K, type Listener as L, type Method as M, type Narrowing as N, type Outbox as O, type Participant as P, type Route as R, type Schedule as S, type Tables as T, type Command as a, type Plugin as b, type Caller as c, type Outbound as d, type Store as e, type Opening as f, type Dialer as g, type Logger as h, type Described as i, type Event as j, type Heard as k, type Hook as l, type Log as m, MigrationFault as n, type Options as o, type Outgoing as p, type Permission as q, type Registered as r, type Run as s, type Scheduled as t, type Schematic as u, type Source as v, type Step as w, type Storage as x, createKernel as y, database as z };