@fabrika/app 0.9.0-dev.1 → 0.9.0-dev.3

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.
Files changed (134) hide show
  1. package/package.json +27 -26
  2. package/skill/SKILL.md +1 -1
  3. package/skill/references/app-setup.md +8 -8
  4. package/src/dev/changes.ts +5 -43
  5. package/src/dev/contract-digest.ts +17 -0
  6. package/src/dev/index.ts +1 -1
  7. package/src/dev/launcher.ts +116 -31
  8. package/src/dev/wire.ts +11 -1
  9. package/types/src/agent-skill.d.ts +29 -0
  10. package/types/src/build.d.ts +11 -0
  11. package/types/src/bun.d.ts +66 -0
  12. package/types/src/client/caller.d.ts +19 -0
  13. package/types/src/client/contract.d.ts +131 -0
  14. package/types/src/client/error-reporting.d.ts +66 -0
  15. package/types/src/client/index.d.ts +4 -0
  16. package/types/src/client/rpc.d.ts +14 -0
  17. package/types/src/client/share.d.ts +2 -0
  18. package/types/src/client/transport.d.ts +42 -0
  19. package/types/src/cloudflare.d.ts +49 -0
  20. package/types/src/contract/artifact-schema.d.ts +8 -0
  21. package/types/src/contract/attachment.d.ts +41 -0
  22. package/types/src/contract/build.d.ts +12 -0
  23. package/types/src/contract/compile.d.ts +11 -0
  24. package/types/src/contract/connect.d.ts +51 -0
  25. package/types/src/contract/context.d.ts +501 -0
  26. package/types/src/contract/declaration.d.ts +303 -0
  27. package/types/src/contract/defaults.d.ts +208 -0
  28. package/types/src/contract/define.d.ts +359 -0
  29. package/types/src/contract/file.d.ts +44 -0
  30. package/types/src/contract/group.d.ts +274 -0
  31. package/types/src/contract/handled.d.ts +18 -0
  32. package/types/src/contract/index.d.ts +19 -0
  33. package/types/src/contract/kit.d.ts +210 -0
  34. package/types/src/contract/platform.d.ts +9 -0
  35. package/types/src/contract/problems.d.ts +14 -0
  36. package/types/src/contract/redirect.d.ts +25 -0
  37. package/types/src/contract/schema-from-shape.d.ts +10 -0
  38. package/types/src/contract/schema-shape.d.ts +5 -0
  39. package/types/src/contract/snapshot-source.d.ts +15 -0
  40. package/types/src/contract/token.d.ts +58 -0
  41. package/types/src/contract/vocabulary.d.ts +30 -0
  42. package/types/src/contract/webhook-binding.d.ts +24 -0
  43. package/types/src/contract/webhook-scheme.d.ts +23 -0
  44. package/types/src/dev/changes.d.ts +7 -0
  45. package/types/src/dev/contract-digest.d.ts +1 -0
  46. package/types/src/dev/index.d.ts +2 -0
  47. package/types/src/dev/launcher.d.ts +32 -0
  48. package/types/src/dev/output.d.ts +22 -0
  49. package/types/src/dev/wire.d.ts +126 -0
  50. package/types/src/disk-assets.d.ts +3 -0
  51. package/types/src/errors.d.ts +46 -0
  52. package/types/src/fetch.d.ts +7 -0
  53. package/types/src/files/archive.d.ts +26 -0
  54. package/types/src/files/clamscan.d.ts +32 -0
  55. package/types/src/files/handle.d.ts +106 -0
  56. package/types/src/files/kinds.d.ts +105 -0
  57. package/types/src/files/scan.d.ts +80 -0
  58. package/types/src/files/sniff.d.ts +7 -0
  59. package/types/src/files/streams.d.ts +17 -0
  60. package/types/src/files/treatment.d.ts +18 -0
  61. package/types/src/from-row.d.ts +20 -0
  62. package/types/src/honeypot.d.ts +2 -0
  63. package/types/src/implement/anonymous.d.ts +26 -0
  64. package/types/src/implement/attachment.d.ts +14 -0
  65. package/types/src/implement/boot.d.ts +109 -0
  66. package/types/src/implement/bounded.d.ts +22 -0
  67. package/types/src/implement/browser-config.d.ts +19 -0
  68. package/types/src/implement/capability.d.ts +140 -0
  69. package/types/src/implement/challenge.d.ts +75 -0
  70. package/types/src/implement/client-address.d.ts +11 -0
  71. package/types/src/implement/connect.d.ts +67 -0
  72. package/types/src/implement/crashes.d.ts +31 -0
  73. package/types/src/implement/dispatch.d.ts +218 -0
  74. package/types/src/implement/egress.d.ts +14 -0
  75. package/types/src/implement/environment.d.ts +14 -0
  76. package/types/src/implement/failures.d.ts +34 -0
  77. package/types/src/implement/faults.d.ts +44 -0
  78. package/types/src/implement/headers.d.ts +7 -0
  79. package/types/src/implement/implement.d.ts +58 -0
  80. package/types/src/implement/index.d.ts +24 -0
  81. package/types/src/implement/invocation.d.ts +120 -0
  82. package/types/src/implement/membership.d.ts +201 -0
  83. package/types/src/implement/multipart.d.ts +36 -0
  84. package/types/src/implement/params.d.ts +7 -0
  85. package/types/src/implement/principal.d.ts +52 -0
  86. package/types/src/implement/project.d.ts +11 -0
  87. package/types/src/implement/public.d.ts +61 -0
  88. package/types/src/implement/serve.d.ts +25 -0
  89. package/types/src/implement/tokens.d.ts +49 -0
  90. package/types/src/implement/types.d.ts +243 -0
  91. package/types/src/implement/urls.d.ts +15 -0
  92. package/types/src/implement/verifiers.d.ts +19 -0
  93. package/types/src/implement/webhook.d.ts +55 -0
  94. package/types/src/implement/workload.d.ts +56 -0
  95. package/types/src/implement/xlsx.d.ts +6 -0
  96. package/types/src/index.d.ts +12 -0
  97. package/types/src/jobs/exception.d.ts +40 -0
  98. package/types/src/jobs/mail.d.ts +67 -0
  99. package/types/src/jobs/report.d.ts +56 -0
  100. package/types/src/jobs/runner.d.ts +145 -0
  101. package/types/src/jobs/schedule.d.ts +15 -0
  102. package/types/src/jobs/scrub.d.ts +12 -0
  103. package/types/src/jobs/types.d.ts +92 -0
  104. package/types/src/react/caller.d.ts +30 -0
  105. package/types/src/react/errors.d.ts +40 -0
  106. package/types/src/react/hooks.d.ts +110 -0
  107. package/types/src/react/index.d.ts +5 -0
  108. package/types/src/react/provider.d.ts +58 -0
  109. package/types/src/react/store.d.ts +68 -0
  110. package/types/src/react/testing.d.ts +28 -0
  111. package/types/src/react/types.d.ts +33 -0
  112. package/types/src/rpc/builder.d.ts +52 -0
  113. package/types/src/rpc/contract.d.ts +23 -0
  114. package/types/src/rpc/dispatcher.d.ts +11 -0
  115. package/types/src/rpc/types.d.ts +62 -0
  116. package/types/src/service/define.d.ts +42 -0
  117. package/types/src/service/index.d.ts +3 -0
  118. package/types/src/service/serve.d.ts +28 -0
  119. package/types/src/standard-schema.d.ts +55 -0
  120. package/types/src/testing/caller.d.ts +95 -0
  121. package/types/src/testing/challenge.d.ts +11 -0
  122. package/types/src/testing/dev.d.ts +97 -0
  123. package/types/src/testing/endpoint-access.d.ts +99 -0
  124. package/types/src/testing/index.d.ts +12 -0
  125. package/types/src/testing/isolation.d.ts +3 -0
  126. package/types/src/testing/kit.d.ts +182 -0
  127. package/types/src/testing/membership.d.ts +132 -0
  128. package/types/src/testing/policy-isolation.d.ts +113 -0
  129. package/types/src/testing/preload.d.ts +1 -0
  130. package/types/src/testing/proxy.d.ts +40 -0
  131. package/types/src/testing/runner.d.ts +8 -0
  132. package/types/src/testing/sample-file.d.ts +5 -0
  133. package/types/src/testing/status.d.ts +4 -0
  134. package/types/src/undumpable.d.ts +5 -0
@@ -0,0 +1,105 @@
1
+ import type { ContractArtifact, FileKindArtifact } from '@fabrika/auth-core';
2
+ import { type FileBinding, type FileColumnRef, type StoredFiles } from '@fabrika/db/framework';
3
+ import { type FileObjectStore } from '@fabrika/platform';
4
+ import type { FileRef } from '../contract/file';
5
+ import type { ImplementationDatabase } from '../implement/implement';
6
+ import type { OperationsReporter } from '../jobs/report';
7
+ import type { OpenedFile } from './handle';
8
+ import { type FileScanner } from './scan';
9
+ import { FileDownload } from './treatment';
10
+ /**
11
+ * The framework's sweep job: hourly, it deletes the files no declared column references and the pending ones past the
12
+ * grace, so none waits for the next request. A reserved name with `-`, which no `c.cron` name can take;
13
+ * `@fabrika/provider-zerops` writes it into the crontab on this schedule (`ZEROPS_FILE_SWEEP_JOB`).
14
+ */
15
+ export declare const FILE_SWEEP_JOB = "fabrika-file-sweep";
16
+ export declare const FILE_SWEEP_SCHEDULE = "41 * * * *";
17
+ export declare const FILE_SWEEP_TIMEOUT_MS: number;
18
+ /** How long a staged file waits for its first reference before the sweep may delete it. */
19
+ export declare const FILE_PENDING_GRACE_MS: number;
20
+ /** One file part of a multipart body, as the multipart reader hands it over. */
21
+ export interface FilePart {
22
+ readonly filename: string | null;
23
+ /** The media type the part declares; the bytes must be of it. */
24
+ readonly contentType: string | null;
25
+ readonly body: ReadableStream<Uint8Array>;
26
+ }
27
+ /** What `ctx.files.put` takes: the bytes, and the type they must be of. */
28
+ export interface PutOptions {
29
+ readonly type: string;
30
+ readonly name?: string;
31
+ }
32
+ /** The bytes `ctx.files.put` writes: in memory, or a stream a large file comes from. */
33
+ export type PutBody = Uint8Array | ReadableStream<Uint8Array> | Blob;
34
+ /**
35
+ * One request's or job attempt's files: the holder its file references belong to, the id a pending file it stages
36
+ * carries, and what it staged, so a known rollback deletes what nothing claimed.
37
+ */
38
+ export declare class FileWork {
39
+ #private;
40
+ readonly holder: object;
41
+ readonly request: string;
42
+ constructor(holder: object, request: string);
43
+ staged(id: string): void;
44
+ get stagedIds(): readonly string[];
45
+ }
46
+ interface Kind {
47
+ readonly name: string;
48
+ readonly artifact: FileKindArtifact;
49
+ }
50
+ export declare class StoredFileKinds {
51
+ #private;
52
+ constructor(kinds: readonly Kind[], store: FileObjectStore, files: StoredFiles, columns: readonly FileColumnRef[]);
53
+ /** Whether the contract declares kind `name`. */
54
+ has(name: string): boolean;
55
+ /** Whether any kind scans, so the scan job runs. */
56
+ get scans(): boolean;
57
+ /** What the data layer claims and checks a site's file references with. */
58
+ binding(work: FileWork): FileBinding;
59
+ /**
60
+ * One incoming file of a request's file field, staged for `work`: its declared type must be one the kind admits and
61
+ * its first bytes that type. `null` for an empty part without a file name: a file input the visitor left empty.
62
+ */
63
+ receive(work: FileWork, kind: string, part: FilePart): Promise<FileRef | null>;
64
+ /** `ctx.files.put`: a file of `kind` this work creates, staged like an incoming one; its reference is the work's. */
65
+ put(work: FileWork, kind: unknown, body: PutBody, options: PutOptions): Promise<FileRef>;
66
+ /**
67
+ * After a known rollback: every file the work staged and nothing claimed goes now. After an uncertain outcome (a lost
68
+ * commit answer, a crash) nothing is deleted here; the sweep decides from the database.
69
+ */
70
+ release(work: FileWork): Promise<void>;
71
+ /** `ctx.files.open(ref)`: the bytes of a live file, read whole, at most `maxBytes` (the kind's by default). */
72
+ open(work: FileWork, reference: unknown, options?: {
73
+ readonly maxBytes?: number;
74
+ }): Promise<OpenedFile>;
75
+ /** The answer of a procedure whose output is a file: the file the handler returned, streamed. */
76
+ download(work: FileWork, endpoint: string, kind: string, reference: unknown): Promise<FileDownload>;
77
+ /**
78
+ * The sweep: it finishes deletions a crash interrupted, adopts the migrated files a declared column references, then
79
+ * deletes, batch by batch, every file past the grace that no declared column references: its row and the record of
80
+ * its object in one transaction (the foreign key keeps a referenced one), then the object, then the record.
81
+ */
82
+ sweep(signal: AbortSignal): Promise<void>;
83
+ /** One scan pass over the quarantined files of every scanning kind: the scan job's part for kinds. */
84
+ scanPass(scanner: FileScanner | null, reporter: OperationsReporter, signal: AbortSignal): Promise<void>;
85
+ }
86
+ /** Where `ctx.files.put` and `.open` run: the site's name, its liveness check, and whether it may create a file. */
87
+ export interface StoredFileSite {
88
+ readonly where: string;
89
+ readonly check: () => void;
90
+ /** A query reads only, so it puts no file. */
91
+ readonly writes: boolean;
92
+ }
93
+ /** `ctx.files.put` and `.open` of one site, bound to its work. */
94
+ export declare function storedFilesHandle(kinds: StoredFileKinds | null, work: FileWork, site: StoredFileSite): {
95
+ readonly put: (kind: unknown, body: PutBody, options: PutOptions) => Promise<FileRef>;
96
+ readonly open: (file: unknown, options?: {
97
+ readonly maxBytes?: number;
98
+ }) => Promise<OpenedFile>;
99
+ };
100
+ /**
101
+ * The file kinds of `artifact`, or `null` when it declares none. Every reason a kind cannot be served goes into
102
+ * `problems`, so `boot` refuses to start rather than take a file it cannot check.
103
+ */
104
+ export declare function storedFileKinds(artifact: ContractArtifact, database: ImplementationDatabase | null, store: FileObjectStore | undefined, problems: string[]): StoredFileKinds | null;
105
+ export {};
@@ -0,0 +1,80 @@
1
+ import type { QuarantinedStoredFile } from '@fabrika/db/framework';
2
+ import type { FileObjectStore } from '@fabrika/platform';
3
+ import type { OperationsReporter } from '../jobs/report';
4
+ /** The reserved name of the scan job: a `c.cron` job name cannot contain `-`, so no application job takes it. */
5
+ export declare const FILE_SCAN_JOB = "fabrika-file-scan";
6
+ export declare const FILE_SCAN_SCHEDULE = "* * * * *";
7
+ export declare const SCAN_LIMITS: {
8
+ /** One pass scans at most this many files, and at most `batchBytes` of them; the next pass takes the rest. */
9
+ readonly batchFiles: 20;
10
+ readonly batchBytes: number;
11
+ /** The scanner's whole run — reading the objects, the scan, the cleanup — is stopped by then. */
12
+ readonly scanTimeoutMs: number;
13
+ /** The attempt's deadline; it outlasts the scanner's, so the verdicts are written before it. */
14
+ readonly jobTimeoutMs: number;
15
+ /** A file that got no verdict this many times is rejected. */
16
+ readonly maxAttempts: 5;
17
+ /** The wait before the next try of a file without a verdict: doubled per try, at most `retryMaxMs`. */
18
+ readonly retryBaseMs: 60000;
19
+ readonly retryMaxMs: number;
20
+ };
21
+ /** What the scanner decided about one file. Only `clean` promotes; `infected` rejects; anything else is retried. */
22
+ export type ScanVerdict = {
23
+ readonly kind: 'clean';
24
+ } | {
25
+ readonly kind: 'infected';
26
+ readonly signature: string;
27
+ } | {
28
+ readonly kind: 'unscanned';
29
+ };
30
+ /** Why a whole batch got no verdict. Fixed codes only: a scanner's own output is never reported. */
31
+ export type ScanFailure = 'scanner_missing' | 'signatures_missing' | 'signatures_stale' | 'scanner_failed' | 'timeout' | 'output_overflow';
32
+ export type ScanBatchResult = {
33
+ readonly ok: true;
34
+ readonly verdicts: ReadonlyMap<string, ScanVerdict>;
35
+ } | {
36
+ readonly ok: false;
37
+ readonly failure: ScanFailure;
38
+ };
39
+ export interface ScanObject {
40
+ /** The file's id: a UUID. */
41
+ readonly id: string;
42
+ /**
43
+ * The object's bytes. It throws when they are missing, differ from the upload's, are an archive the scanner cannot
44
+ * read in full, or `signal` aborts.
45
+ */
46
+ open(signal: AbortSignal): Promise<ReadableStream<Uint8Array>>;
47
+ }
48
+ export interface FileScanner {
49
+ /**
50
+ * A verdict per file id, or why the batch got none. A file whose `open` threw has no verdict. Stops once `signal`
51
+ * aborts. Never throws.
52
+ */
53
+ scan(objects: readonly ScanObject[], signal: AbortSignal): Promise<ScanBatchResult>;
54
+ }
55
+ /** What one scanning file kind lets the scan pass do: take its due files, and write each verdict. */
56
+ export interface ScanningKind {
57
+ readonly name: string;
58
+ readonly table: {
59
+ quarantined(limit: number, now: Date): Promise<readonly QuarantinedStoredFile[]>;
60
+ promote(id: string, sha256: string, version: string): Promise<boolean>;
61
+ deferScan(id: string, attempts: number, after: Date): Promise<boolean>;
62
+ reject(id: string): Promise<boolean>;
63
+ };
64
+ }
65
+ /** The next try of a file that has had `attempts` tries: one minute, doubled per try, at most an hour. */
66
+ export declare function retryDelayMs(attempts: number): number;
67
+ export interface ScanPassInput {
68
+ readonly collections: readonly ScanningKind[];
69
+ readonly store: FileObjectStore;
70
+ /** `null` when the runtime adapter supplies none: every quarantined file stays quarantined. */
71
+ readonly scanner: FileScanner | null;
72
+ readonly reporter: OperationsReporter;
73
+ readonly signal: AbortSignal;
74
+ readonly now?: () => Date;
75
+ }
76
+ /**
77
+ * One pass. A verdict write that fails (the lease lost, the database gone) throws, so the runner reports the run as
78
+ * failed; everything the scanner decides is reported here and never throws.
79
+ */
80
+ export declare function runScanPass(input: ScanPassInput): Promise<void>;
@@ -0,0 +1,7 @@
1
+ /** How many leading bytes an upload is judged by. */
2
+ export declare const SNIFF_BYTES = 512;
3
+ /** The ZIP-based types: a ZIP archive, and an Office Open XML document, which is one. */
4
+ export declare const ZIP_TYPES: readonly string[];
5
+ export declare const SNIFFABLE_TYPES: readonly string[];
6
+ /** Whether the first bytes of an upload are of `type`; always false for a type the framework cannot verify. */
7
+ export declare function bytesAreOfType(type: string, head: Uint8Array): boolean;
@@ -0,0 +1,17 @@
1
+ /** The client's file name, metadata only: at most 255 code points, never half a character, no control characters. */
2
+ export declare function cleanName(raw: string): string | null;
3
+ /** The first `size` bytes of `body`, and a stream that yields the whole body again. */
4
+ export declare function readHead(body: ReadableStream<Uint8Array>, size: number): Promise<{
5
+ readonly head: Uint8Array;
6
+ readonly stream: ReadableStream<Uint8Array>;
7
+ }>;
8
+ /** The body as it is stored: hashed on the way, and failed once `timeoutMs` (`null`: no timer) passes before it ends. */
9
+ export declare function digesting(body: ReadableStream<Uint8Array>, timeoutMs: number | null): {
10
+ readonly stream: ReadableStream<Uint8Array>;
11
+ digest(): string;
12
+ timedOut(): boolean;
13
+ stop(): void;
14
+ };
15
+ /** The whole body when it holds exactly `size` bytes; `null` as soon as it proves longer, or once it ends shorter. */
16
+ export declare function readExactly(body: ReadableStream<Uint8Array>, size: number): Promise<Uint8Array | null>;
17
+ export declare function isNamed(error: unknown, name: string): boolean;
@@ -0,0 +1,18 @@
1
+ /** A file the download endpoint answers. `ContractRuntime` turns it into the response, with the file treatment. */
2
+ export declare class FileDownload {
3
+ readonly body: ReadableStream<Uint8Array>;
4
+ readonly size: number;
5
+ /** The type recorded at upload, which its bytes were checked against; never a type a request sent. */
6
+ readonly contentType: string;
7
+ readonly name: string | null;
8
+ constructor(file: {
9
+ readonly body: ReadableStream<Uint8Array>;
10
+ readonly size: number;
11
+ readonly contentType: string;
12
+ readonly name: string | null;
13
+ });
14
+ }
15
+ export declare function contentDisposition(contentType: string, name: string | null): string;
16
+ export declare function fileResponse(file: FileDownload): Response;
17
+ /** An error of a download endpoint, with the file treatment in place of the header policy. */
18
+ export declare function withFileTreatment(response: Response): Response;
@@ -0,0 +1,20 @@
1
+ import { z } from 'zod/v4';
2
+ /** `companyId` → `company_id`: the column a field is read from. */
3
+ export type Snake<S extends string> = S extends `${infer Head}${infer Tail}` ? `${Head extends Lowercase<Head> ? Head : `_${Lowercase<Head>}`}${Snake<Tail>}` : S;
4
+ /** A row value a field accepts: text for a literal string (the schema checks it), a `Date` for ISO text. */
5
+ type Widened<V> = V extends string ? string | Date : V;
6
+ /** The row `fromRow` takes for `S`: one column per declared field; any other column is allowed and dropped. */
7
+ export type RowInput<S extends z.ZodObject> = {
8
+ readonly [K in keyof z.input<S> as Snake<K & string>]: Widened<z.input<S>[K]>;
9
+ };
10
+ /** The row as `schema`'s output; a row the schema refuses throws, as a handler's bug. */
11
+ export declare function fromRow<const S extends z.ZodObject>(schema: S, row: RowInput<S>): z.output<S>;
12
+ /** A list's page as a page output: each row through `fromRow`, and `next` as the list answered it. */
13
+ export declare function fromPage<const S extends z.ZodObject>(schema: S, page: {
14
+ readonly items: readonly RowInput<S>[];
15
+ readonly next: string | null;
16
+ }): {
17
+ items: z.output<S>[];
18
+ next: string | null;
19
+ };
20
+ export {};
@@ -0,0 +1,2 @@
1
+ /** Whether the hidden field was filled: `if (honeypot(input.website)) return {}`. */
2
+ export declare function honeypot(value: string | null | undefined): boolean;
@@ -0,0 +1,26 @@
1
+ import { HttpError } from '../errors';
2
+ /** The slowest body a procedure waits for, in bytes per second: its deadline is `maxBytes` at this rate, 30 s at least. */
3
+ export declare const BODY_RATE_FLOOR: number;
4
+ /** How long a body of at most `maxBytes` may take to arrive. */
5
+ export declare function bodyDeadlineMs(maxBytes: number): number;
6
+ /**
7
+ * The body as bytes, read until `maxBytes`; a longer one stops the read and is a 413. A body whose stream fails before
8
+ * its end (the client closed the connection) is the sender's 400 `aborted`, never a failure of the application.
9
+ */
10
+ export declare function readBoundedBody(request: Request, maxBytes: number, deadline?: AbortSignal): Promise<Uint8Array<ArrayBuffer>>;
11
+ /** What a request answers whose body did not arrive whole: nobody reads it, and no failure is reported. */
12
+ export declare function abortedBody(): HttpError;
13
+ /**
14
+ * Runs before any other script of the share SPA: it takes the key out of the fragment, removes the fragment from the
15
+ * address with `history.replaceState`, and keeps the key in a closure. Telemetry that reads `location` later finds
16
+ * no key; the SPA reads it through `shareKey()` of `@fabrika/app/client`.
17
+ */
18
+ export declare const LINK_BOOTSTRAP_SCRIPT = "(function () {\n\tvar fragment = window.location.hash.slice(1)\n\tvar key = /^px_[A-Za-z0-9_-]+$/.test(fragment) ? fragment : null\n\tif (window.location.hash !== '') {\n\t\twindow.history.replaceState(window.history.state, '', window.location.pathname + window.location.search)\n\t}\n\tObject.defineProperty(window, '__fabrikaShareKey', {\n\t\tvalue: function () {\n\t\t\treturn key\n\t\t},\n\t\twritable: false,\n\t\tconfigurable: false,\n\t\tenumerable: false,\n\t})\n})()\n";
19
+ /** The share SPA's bootstrap path under the link prefix. */
20
+ export declare function bootstrapPath(prefix: string): string;
21
+ export declare function bootstrapResponse(): Response;
22
+ /**
23
+ * The share SPA's document with the bootstrap as its first script: right after `<head>`, so it runs before every
24
+ * module script and every script the document loads. A response that is not HTML passes unchanged.
25
+ */
26
+ export declare function withBootstrap(response: Response, prefix: string): Promise<Response>;
@@ -0,0 +1,14 @@
1
+ import type { AttachmentShape } from '@fabrika/auth-core';
2
+ import type { Schema } from '../contract/define';
3
+ /**
4
+ * The response an attachment answers. `rows` is what the handler returned: each row is projected and validated with
5
+ * the row schema of `output` only when its turn to be encoded comes.
6
+ */
7
+ export declare function attachmentResponse(endpoint: string, shape: AttachmentShape, output: Schema, rows: unknown): Response;
8
+ /**
9
+ * The header line of the column names, then one line per row, each row read through `rowOf` only when its turn
10
+ * comes. The row count is checked before any row is read. Each field is encoded on its own, with its separator, and
11
+ * counted against `maxBytes` before the next one is read, so neither a long result nor a wide row is built past the
12
+ * limit; only then is the whole file joined.
13
+ */
14
+ export declare function encodeCsv(endpoint: string, shape: AttachmentShape, rows: unknown, rowOf: (row: unknown) => unknown): Uint8Array<ArrayBuffer>;
@@ -0,0 +1,109 @@
1
+ import { type IamMembershipRpc, type Jwks } from '@fabrika/auth-core';
2
+ import type { Database } from '@fabrika/db';
3
+ import type { EmailProviderRegistry } from '@fabrika/email';
4
+ import type { FileObjectStore } from '@fabrika/platform';
5
+ import type { RequestExecutionContext } from '../fetch';
6
+ import { type FileScanner } from '../files/scan';
7
+ import { type MailStandIn } from '../jobs/mail';
8
+ import { type ContractJobs, type HandlerErrorSink } from '../jobs/runner';
9
+ import type { ChallengeFetch, ChallengeWork } from './challenge';
10
+ import { type ConnectFetch } from './connect';
11
+ import { type SpaAssets } from './dispatch';
12
+ import type { CapturedEnvironment } from './environment';
13
+ import type { ContractImplementation } from './implement';
14
+ import { type WorkloadTokenSource } from './workload';
15
+ /** Where the token verifier gets IAM's public keys: the IAM binding on Cloudflare, the issuer's JWKS elsewhere. */
16
+ export interface IamKeySource {
17
+ getJwks(): Promise<Jwks>;
18
+ }
19
+ export interface BootOptions {
20
+ /** The variables `captureEnvironment` took before the application's modules loaded. */
21
+ readonly environment: CapturedEnvironment;
22
+ readonly iam: IamKeySource;
23
+ /** Serves the SPA mounts. Without it, a contract that declares one does not boot. */
24
+ readonly assets?: SpaAssets;
25
+ /** The application's Postgres connection, which the Bun adapter opens. Without it, an application on `@fabrika/db` does not boot. */
26
+ readonly database?: Database;
27
+ /**
28
+ * The proxy authenticates itself with `FABRIKA_PROXY_SECRET`, and the application does not boot without it. The Bun
29
+ * adapter sets it: on Zerops a namespace is one private network. On Cloudflare the service binding is the
30
+ * authenticated hop (ADR-0022 rule 1). A secret that is set is checked either way.
31
+ */
32
+ readonly requireProxySecret?: boolean;
33
+ /** The object storage of the stored files, which the Bun adapter opens. Without it, a contract with file kinds does not boot. */
34
+ readonly files?: FileObjectStore;
35
+ /**
36
+ * The malware scanner of the scan job of `scan: true` file kinds, which the Bun adapter supplies to a job run
37
+ * (`clamscanScanner`). Without it the scan job leaves every quarantined file quarantined and reports.
38
+ */
39
+ readonly scanner?: FileScanner;
40
+ /**
41
+ * IAM's membership operations: the IAM binding on Cloudflare, `HttpIamRpc` elsewhere. Without it, a contract that
42
+ * creates scopes, manages members or issues share links does not boot.
43
+ */
44
+ readonly membership?: IamMembershipRpc;
45
+ /**
46
+ * Cloudflare service bindings to the owners' proxy Workers, by binding name (`FABRIKA_CONNECT_<IMPORT>`). An import
47
+ * without one calls its owner with `fetch`.
48
+ */
49
+ readonly connectBindings?: Readonly<Record<string, ConnectFetch>>;
50
+ /**
51
+ * Where a job or a webhook exchanges its key for a token: IAM's public token exchange on Bun. Without it, a contract
52
+ * with a job or a webhook does not boot.
53
+ */
54
+ readonly workloadTokens?: WorkloadTokenSource;
55
+ /** What posts a report to Operations' ingest; `fetch` by default. */
56
+ readonly operationsFetch?: (request: Request) => Promise<Response>;
57
+ /** What asks a public procedure's challenge provider to verify a token; `fetch` by default. */
58
+ readonly challengeFetch?: ChallengeFetch;
59
+ /**
60
+ * The developer's own machine only (`fabrika app dev`): the token a challenge admits more than once, which
61
+ * Turnstile's always-passing test widget hands out for every call. Every other token stays single use.
62
+ */
63
+ readonly repeatableChallengeToken?: string;
64
+ /**
65
+ * The work of every ALTCHA challenge in place of the contract's `cost`: the test kit's and `fabrika app dev`'s
66
+ * (`MINIMAL_CHALLENGE_WORK`), so a test solves one in milliseconds. A deployed application never sets it.
67
+ */
68
+ readonly challengeWork?: ChallengeWork;
69
+ /**
70
+ * The developer's own machine only (`fabrika app dev`): what a job's handler threw, which every log of a deployed
71
+ * application withholds because it may carry a credential.
72
+ */
73
+ readonly revealHandlerErrors?: HandlerErrorSink;
74
+ /** The providers `FABRIKA_EMAIL_PROVIDER` may name for the jobs that declare `mailer: 'installation'`; the built-in ones by default. */
75
+ readonly emailProviders?: EmailProviderRegistry;
76
+ /**
77
+ * Providers added to `emailProviders` only in a local composition (a loopback `FABRIKA_IAM_ISSUER`); anywhere else
78
+ * `FABRIKA_EMAIL_PROVIDER` naming one is refused like any unknown provider. On a name both hold, `emailProviders` wins.
79
+ */
80
+ readonly localEmailProviders?: EmailProviderRegistry;
81
+ /**
82
+ * The test kit's stand-in for every mailer job, or `'none'` for an installation without mail; boot then reads neither
83
+ * `FABRIKA_EMAIL_*` nor `serve`'s `mail`.
84
+ */
85
+ readonly mailStandIn?: MailStandIn;
86
+ /** The connections the application's pool holds: a quarter of them, at least one, is the public procedures' share. */
87
+ readonly connections?: number;
88
+ /**
89
+ * `'vouched'` where the proxy's `X-Fabrika-Client-Ip` is the visitor's own address, so `ctx.client.ip` reads it. A
90
+ * runtime behind a balancer whose range the platform does not trust (Zerops, until it does) leaves it unset: the ip is
91
+ * then `null`.
92
+ */
93
+ readonly clientAddress?: 'vouched';
94
+ }
95
+ export interface ContractApp {
96
+ /** `exec` keeps a failure's report to Operations alive past the response (a Worker's `waitUntil`). */
97
+ fetch(request: Request, exec?: RequestExecutionContext): Promise<Response>;
98
+ /** The contract's `c.cron` jobs; `fabrika job run <name>` runs one. */
99
+ readonly jobs: ContractJobs;
100
+ /** Aborts every kicked run in flight; a later kick starts none; the schedule recovers their work. */
101
+ stopKicks(): void;
102
+ }
103
+ /** Everything that stops a contract application from starting, with where it is and what to do. */
104
+ export declare class BootError extends Error {
105
+ readonly name = "BootError";
106
+ readonly problems: readonly string[];
107
+ constructor(problems: readonly string[]);
108
+ }
109
+ export declare function boot(app: ContractImplementation, options: BootOptions): ContractApp;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * How long a request handler runs before its `ctx.signal` aborts and its transaction cannot commit. It equals
3
+ * `Bun.serve`'s default `idleTimeout`, which already closes a connection the server sends nothing on for that long, so
4
+ * both runtimes stop a handler at the same point.
5
+ */
6
+ export declare const REQUEST_BUDGET_MS = 10000;
7
+ /** A handler's `ctx.signal`: aborted when the client leaves (`request.signal`) or once `budgetMs` passed. */
8
+ export declare function handlerSignal(request: Request, budgetMs?: number): AbortSignal;
9
+ export interface BoundedRead {
10
+ readonly maxBytes: number;
11
+ /** `Content-Length`, when the sender declared one: a larger value is refused before anything is read. */
12
+ readonly declaredLength?: string | null;
13
+ /** Aborting it cancels the stream; the read then rejects. */
14
+ readonly signal?: AbortSignal;
15
+ }
16
+ /** The body's bytes, or `null` when it is larger than `maxBytes`. Rejects when `signal` aborts the read. */
17
+ export declare function readBoundedBytes(stream: ReadableStream<Uint8Array> | null, options: BoundedRead): Promise<Uint8Array<ArrayBuffer> | null>;
18
+ /** `body` passed on as it streams, failed once more than `maxBytes` went through; `exceeded` tells that failure apart. */
19
+ export declare function boundedStream(body: ReadableStream<Uint8Array>, maxBytes: number): {
20
+ readonly stream: ReadableStream<Uint8Array>;
21
+ exceeded(): boolean;
22
+ };
@@ -0,0 +1,19 @@
1
+ import type { ContractArtifact } from '@fabrika/auth-core';
2
+ import type { CapturedEnvironment } from './environment';
3
+ export interface BrowserConfig {
4
+ /** The Operations ingest DSN (a public key), or `null` without one. */
5
+ readonly dsn: string | null;
6
+ readonly release: string | null;
7
+ /** `FABRIKA_ENVIRONMENT`, which the page's error reports carry, or `null` when unset. */
8
+ readonly environment: string | null;
9
+ /**
10
+ * Every public procedure challenge's site key, and the variables the mount lists in `public`, by variable name. Absent when
11
+ * there is none, so the file of an application without them holds the three Operations values only.
12
+ */
13
+ readonly public?: Readonly<Record<string, string>>;
14
+ }
15
+ /**
16
+ * The browser config of each SPA mount, by mount name: the Operations settings, and the public variables the contract
17
+ * names. A named variable that is not set is a boot problem.
18
+ */
19
+ export declare function browserConfigs(artifact: ContractArtifact, environment: CapturedEnvironment, operations: Pick<BrowserConfig, 'dsn' | 'release'>, problems: string[]): ReadonlyMap<string, BrowserConfig>;
@@ -0,0 +1,140 @@
1
+ import { type AppCapability as AppCapabilityType, type CapabilityAuthority, type ContainerGrant, type LeaseFence, type Ref as RefCapability, type RowGrant, type RowRef as RowRefCapability, type Within as WithinCapability } from '@fabrika/db/capability';
2
+ /** Why a capability or a handle was refused, as a closed catalog with the fixed text a log may print. */
3
+ export declare const CAPABILITY_REFUSALS: {
4
+ readonly not_minted: "a value the framework did not mint was used as a capability; pass the ctx.target or ctx.within of this request";
5
+ readonly ended: "a capability or handle was used after its request, invocation or attempt ended";
6
+ readonly foreign_work: "a capability or handle was used from other work than the request it belongs to";
7
+ readonly wrong_target: "an IAM request was not passed this request's ctx.target";
8
+ readonly undeclared_action: "the job's declaration or its token does not carry the action";
9
+ readonly undeclared_dimension: "the job named a dimension the contract does not declare";
10
+ readonly undeclared_kick: "the site kicked a job it does not declare in kicks";
11
+ readonly undeclared_binding: "the site named a webhook it does not declare in binds";
12
+ readonly undeclared_signs: "the site signed a token of a purpose it does not declare in signs, or for longer than the purpose allows";
13
+ readonly undeclared_egress: "the site called an origin it does not declare in egress";
14
+ readonly wrong_binding: "a webhook binding was handed a capability of another kind or dimension than the webhook is bound to";
15
+ readonly missing_id: "the job named an object without an id";
16
+ readonly wrong_capability: "an operation was handed a capability of another dimension or action than it needs";
17
+ };
18
+ export type CapabilityRefusal = keyof typeof CAPABILITY_REFUSALS;
19
+ /** A capability that is forged, from a request that has ended, or from another request than the current one. */
20
+ export declare class CapabilityRefusedError extends Error {
21
+ readonly name = "CapabilityRefusedError";
22
+ /** What a log may say; the message is for the author's own run. */
23
+ readonly reason: CapabilityRefusal;
24
+ constructor(reason: CapabilityRefusal, message: string);
25
+ }
26
+ /** A `Within` that would carry more scope values than one statement may bind; the caller is refused, not truncated. */
27
+ export declare class CapabilityLimitError extends Error {
28
+ readonly name = "CapabilityLimitError";
29
+ }
30
+ /**
31
+ * One request's lifetime, or one job attempt's. A capability minted in it is usable only while it runs, and only
32
+ * inside it; a job attempt's writes also need its lease (`fence`).
33
+ */
34
+ export declare class RequestFrame {
35
+ #private;
36
+ readonly id: string;
37
+ readonly fence: LeaseFence | null;
38
+ /** An export's request: the data layer runs its every statement `READ ONLY` and refuses every write. */
39
+ readonly readOnly: boolean;
40
+ /** The endpoint the request is served by; `ctx.connect` checks it against each import's `usedBy`. */
41
+ readonly endpoint: string | null;
42
+ constructor(id: string, options?: {
43
+ readonly fence?: LeaseFence | null;
44
+ readonly readOnly?: boolean;
45
+ readonly endpoint?: string | null;
46
+ });
47
+ get ended(): boolean;
48
+ end(): void;
49
+ }
50
+ /** Run `work` as the request `frame`: every capability check inside it compares against this frame. */
51
+ export declare function runInRequest<T>(frame: RequestFrame, work: () => Promise<T>): Promise<T>;
52
+ /** Whether `frame` is the request running now. */
53
+ export declare function isActiveRequest(frame: RequestFrame): boolean;
54
+ /** Whether the request running now may only read; `@fabrika/db` asks it before every statement. */
55
+ export declare function requestIsReadOnly(): boolean;
56
+ /** The module-private token that makes a `Ref` or a `Within` constructible by the framework only. */
57
+ declare const MINT: unique symbol;
58
+ /** One authorized object for one action, in one request. `chain` holds the containers the object lies in. */
59
+ export declare class Ref<D extends string, A extends string> implements RefCapability<D, A> {
60
+ #private;
61
+ readonly '~capability': {
62
+ readonly kind: 'ref';
63
+ readonly dimension: D;
64
+ readonly action: A;
65
+ };
66
+ constructor(token: typeof MINT, issuer: object, frame: RequestFrame, dimension: D, action: A, principal: string, id: string, chain: ReadonlyMap<string, string>);
67
+ get dimension(): D;
68
+ get action(): A;
69
+ /** The object's id: the value of its dimension. */
70
+ get value(): string;
71
+ /** The containers of the object, by dimension (`workspace → w1` for a note); a copy. */
72
+ get chain(): ReadonlyMap<string, string>;
73
+ }
74
+ /**
75
+ * The values of one dimension in which the caller holds one action, in one request; `all` for a global grant. The
76
+ * containers whose grants also authorise it stay separate, so the data layer matches them against each row's chain.
77
+ */
78
+ export declare class Within<D extends string, A extends string> implements WithinCapability<D, A> {
79
+ #private;
80
+ readonly '~capability': {
81
+ readonly kind: 'within';
82
+ readonly dimension: D;
83
+ readonly action: A;
84
+ };
85
+ constructor(token: typeof MINT, issuer: object, frame: RequestFrame, dimension: D, action: A, principal: string, values: readonly string[] | 'all', containers: readonly ContainerGrant[]);
86
+ get dimension(): D;
87
+ get action(): A;
88
+ /** The values of this dimension itself. */
89
+ get values(): readonly string[] | 'all';
90
+ /** The containers in which the caller also holds the action: `organization → [o1]` for a workspace list. */
91
+ get containers(): readonly ContainerGrant[];
92
+ }
93
+ /**
94
+ * One action held as a global grant, in one request: what an `app` endpoint's `ctx.db.app` is built from. The proxy
95
+ * admitted the request only for a caller who holds the action without a scope.
96
+ */
97
+ export declare class AppCapability<A extends string> implements AppCapabilityType<A> {
98
+ #private;
99
+ readonly '~capability': {
100
+ readonly kind: 'app';
101
+ readonly action: A;
102
+ };
103
+ constructor(token: typeof MINT, issuer: object, frame: RequestFrame, action: A, principal: string);
104
+ get action(): A;
105
+ }
106
+ /**
107
+ * One row a claim leased, for one action, in one job attempt: `row.ref`, which `tx.db.on(row.ref)` finishes the row
108
+ * through. `chain` holds the row's containment columns as it was claimed.
109
+ */
110
+ export declare class RowRef<T extends string, A extends string> implements RowRefCapability<T, A> {
111
+ #private;
112
+ readonly '~capability': {
113
+ readonly kind: 'row';
114
+ readonly table: T;
115
+ readonly action: A;
116
+ };
117
+ constructor(token: typeof MINT, issuer: object, frame: RequestFrame, grant: Omit<RowGrant, 'kind'>);
118
+ get table(): string;
119
+ get action(): string;
120
+ /** The row's key. */
121
+ get value(): string;
122
+ }
123
+ /** Makes the capabilities one authority accepts; a runtime's is held by the `boot` call that made it alone. */
124
+ export interface CapabilityMint {
125
+ ref<D extends string, A extends string>(frame: RequestFrame, dimension: D, action: A, principal: string, id: string, chain: ReadonlyMap<string, string>): Ref<D, A>;
126
+ within<D extends string, A extends string>(frame: RequestFrame, dimension: D, action: A, principal: string, values: readonly string[] | 'all', containers?: readonly ContainerGrant[]): Within<D, A>;
127
+ app<A extends string>(frame: RequestFrame, action: A, principal: string): AppCapability<A>;
128
+ }
129
+ /** A mint and the one authority that accepts what it mints. */
130
+ export interface CapabilityIssuer {
131
+ readonly mint: CapabilityMint;
132
+ readonly authority: CapabilityAuthority;
133
+ }
134
+ /**
135
+ * A new issuer: its authority refuses every capability another issuer minted. Every `boot` makes one and binds its
136
+ * data layer and its deps to the authority; a test that needs a capability no access check gave makes another, whose
137
+ * capabilities reach only stores bound to that issuer's authority.
138
+ */
139
+ export declare function createCapabilityIssuer(): CapabilityIssuer;
140
+ export {};