@fabrika/app 0.8.2 → 0.9.0-cloudflare.2

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 (201) hide show
  1. package/package.json +40 -24
  2. package/skill/SKILL.md +29 -3
  3. package/skill/references/app-setup.md +29 -11
  4. package/skill/references/dev.md +2 -1
  5. package/skill/references/endpoint.md +12 -3
  6. package/skill/references/errors.md +39 -35
  7. package/skill/references/files.md +4 -2
  8. package/skill/references/jobs.md +9 -0
  9. package/skill/references/table-and-migration.md +19 -2
  10. package/skill/references/tests.md +33 -1
  11. package/skill/references/webhooks-links-connectors.md +1 -1
  12. package/src/build-recipe.ts +198 -0
  13. package/src/build.ts +60 -4
  14. package/src/bun.ts +33 -8
  15. package/src/cloudflare/application.ts +352 -0
  16. package/src/cloudflare/bindings.ts +159 -0
  17. package/src/cloudflare/names.ts +16 -0
  18. package/src/cloudflare/ops.ts +246 -0
  19. package/src/cloudflare/r2-files.ts +172 -0
  20. package/src/cloudflare.ts +99 -146
  21. package/src/contract/app-id.ts +5 -0
  22. package/src/contract/artifact-schema.ts +8 -1
  23. package/src/contract/build.ts +68 -11
  24. package/src/contract/compile.ts +1 -0
  25. package/src/contract/context.ts +9 -0
  26. package/src/contract/declaration.ts +2 -0
  27. package/src/contract/define.ts +28 -2
  28. package/src/contract/group.ts +10 -1
  29. package/src/contract/kit.ts +6 -2
  30. package/src/contract/platform.ts +3 -2
  31. package/src/contract/vocabulary.ts +26 -8
  32. package/src/dev/changes.ts +35 -0
  33. package/src/dev/contract-digest.ts +17 -0
  34. package/src/dev/index.ts +11 -0
  35. package/src/dev/launcher.ts +713 -0
  36. package/src/dev/output.ts +72 -0
  37. package/src/dev/wire.ts +136 -0
  38. package/src/disk-assets.ts +8 -3
  39. package/src/files/archive.ts +2 -2
  40. package/src/files/job-names.ts +12 -0
  41. package/src/files/kinds.ts +19 -7
  42. package/src/files/scan.ts +3 -4
  43. package/src/files/streams.ts +3 -1
  44. package/src/implement/anonymous.ts +11 -1
  45. package/src/implement/boot.ts +70 -7
  46. package/src/implement/bounded.ts +21 -2
  47. package/src/implement/capability.ts +18 -3
  48. package/src/implement/dispatch.ts +251 -147
  49. package/src/implement/egress.ts +341 -8
  50. package/src/implement/failures.ts +17 -1
  51. package/src/implement/faults.ts +1 -0
  52. package/src/implement/index.ts +1 -0
  53. package/src/implement/invocation.ts +18 -9
  54. package/src/implement/principal.ts +25 -2
  55. package/src/implement/terminals.ts +93 -0
  56. package/src/implement/workload.ts +24 -13
  57. package/src/jobs/report.ts +5 -1
  58. package/src/jobs/runner.ts +40 -19
  59. package/src/jobs/schedule.ts +7 -1
  60. package/src/jobs/scrub.ts +14 -1
  61. package/src/spa-fallback.ts +12 -0
  62. package/src/testing/endpoint-access.ts +307 -152
  63. package/src/testing/isolation.ts +8 -1
  64. package/src/testing/kit.ts +20 -2
  65. package/src/testing/proxy.ts +5 -5
  66. package/types/src/agent-skill.d.ts +29 -0
  67. package/types/src/build-recipe.d.ts +70 -0
  68. package/types/src/build.d.ts +29 -0
  69. package/types/src/bun.d.ts +66 -0
  70. package/types/src/client/caller.d.ts +19 -0
  71. package/types/src/client/contract.d.ts +131 -0
  72. package/types/src/client/error-reporting.d.ts +66 -0
  73. package/types/src/client/index.d.ts +4 -0
  74. package/types/src/client/rpc.d.ts +14 -0
  75. package/types/src/client/share.d.ts +2 -0
  76. package/types/src/client/transport.d.ts +42 -0
  77. package/types/src/cloudflare/application.d.ts +42 -0
  78. package/types/src/cloudflare/bindings.d.ts +33 -0
  79. package/types/src/cloudflare/names.d.ts +11 -0
  80. package/types/src/cloudflare/ops.d.ts +46 -0
  81. package/types/src/cloudflare/r2-files.d.ts +9 -0
  82. package/types/src/cloudflare.d.ts +42 -0
  83. package/types/src/contract/app-id.d.ts +2 -0
  84. package/types/src/contract/artifact-schema.d.ts +8 -0
  85. package/types/src/contract/attachment.d.ts +41 -0
  86. package/types/src/contract/build.d.ts +12 -0
  87. package/types/src/contract/compile.d.ts +11 -0
  88. package/types/src/contract/connect.d.ts +51 -0
  89. package/types/src/contract/context.d.ts +510 -0
  90. package/types/src/contract/declaration.d.ts +307 -0
  91. package/types/src/contract/defaults.d.ts +208 -0
  92. package/types/src/contract/define.d.ts +376 -0
  93. package/types/src/contract/file.d.ts +44 -0
  94. package/types/src/contract/group.d.ts +282 -0
  95. package/types/src/contract/handled.d.ts +18 -0
  96. package/types/src/contract/index.d.ts +19 -0
  97. package/types/src/contract/kit.d.ts +211 -0
  98. package/types/src/contract/platform.d.ts +9 -0
  99. package/types/src/contract/problems.d.ts +14 -0
  100. package/types/src/contract/redirect.d.ts +25 -0
  101. package/types/src/contract/schema-from-shape.d.ts +10 -0
  102. package/types/src/contract/schema-shape.d.ts +5 -0
  103. package/types/src/contract/snapshot-source.d.ts +15 -0
  104. package/types/src/contract/token.d.ts +58 -0
  105. package/types/src/contract/vocabulary.d.ts +32 -0
  106. package/types/src/contract/webhook-binding.d.ts +24 -0
  107. package/types/src/contract/webhook-scheme.d.ts +23 -0
  108. package/types/src/dev/changes.d.ts +7 -0
  109. package/types/src/dev/contract-digest.d.ts +1 -0
  110. package/types/src/dev/index.d.ts +2 -0
  111. package/types/src/dev/launcher.d.ts +32 -0
  112. package/types/src/dev/output.d.ts +28 -0
  113. package/types/src/dev/wire.d.ts +126 -0
  114. package/types/src/disk-assets.d.ts +6 -0
  115. package/types/src/errors.d.ts +46 -0
  116. package/types/src/fetch.d.ts +7 -0
  117. package/types/src/files/archive.d.ts +26 -0
  118. package/types/src/files/clamscan.d.ts +32 -0
  119. package/types/src/files/handle.d.ts +106 -0
  120. package/types/src/files/job-names.d.ts +9 -0
  121. package/types/src/files/kinds.d.ts +101 -0
  122. package/types/src/files/scan.d.ts +78 -0
  123. package/types/src/files/sniff.d.ts +7 -0
  124. package/types/src/files/streams.d.ts +17 -0
  125. package/types/src/files/treatment.d.ts +18 -0
  126. package/types/src/from-row.d.ts +20 -0
  127. package/types/src/honeypot.d.ts +2 -0
  128. package/types/src/implement/anonymous.d.ts +28 -0
  129. package/types/src/implement/attachment.d.ts +14 -0
  130. package/types/src/implement/boot.d.ts +120 -0
  131. package/types/src/implement/bounded.d.ts +32 -0
  132. package/types/src/implement/browser-config.d.ts +19 -0
  133. package/types/src/implement/capability.d.ts +144 -0
  134. package/types/src/implement/challenge.d.ts +75 -0
  135. package/types/src/implement/client-address.d.ts +11 -0
  136. package/types/src/implement/connect.d.ts +67 -0
  137. package/types/src/implement/crashes.d.ts +31 -0
  138. package/types/src/implement/dispatch.d.ts +221 -0
  139. package/types/src/implement/egress.d.ts +49 -0
  140. package/types/src/implement/environment.d.ts +14 -0
  141. package/types/src/implement/failures.d.ts +39 -0
  142. package/types/src/implement/faults.d.ts +45 -0
  143. package/types/src/implement/headers.d.ts +7 -0
  144. package/types/src/implement/implement.d.ts +58 -0
  145. package/types/src/implement/index.d.ts +25 -0
  146. package/types/src/implement/invocation.d.ts +125 -0
  147. package/types/src/implement/membership.d.ts +201 -0
  148. package/types/src/implement/multipart.d.ts +36 -0
  149. package/types/src/implement/params.d.ts +7 -0
  150. package/types/src/implement/principal.d.ts +57 -0
  151. package/types/src/implement/project.d.ts +11 -0
  152. package/types/src/implement/public.d.ts +61 -0
  153. package/types/src/implement/serve.d.ts +25 -0
  154. package/types/src/implement/terminals.d.ts +33 -0
  155. package/types/src/implement/tokens.d.ts +49 -0
  156. package/types/src/implement/types.d.ts +243 -0
  157. package/types/src/implement/urls.d.ts +15 -0
  158. package/types/src/implement/verifiers.d.ts +19 -0
  159. package/types/src/implement/webhook.d.ts +55 -0
  160. package/types/src/implement/workload.d.ts +56 -0
  161. package/types/src/implement/xlsx.d.ts +6 -0
  162. package/types/src/index.d.ts +12 -0
  163. package/types/src/jobs/exception.d.ts +40 -0
  164. package/types/src/jobs/mail.d.ts +67 -0
  165. package/types/src/jobs/report.d.ts +56 -0
  166. package/types/src/jobs/runner.d.ts +154 -0
  167. package/types/src/jobs/schedule.d.ts +17 -0
  168. package/types/src/jobs/scrub.d.ts +12 -0
  169. package/types/src/jobs/types.d.ts +98 -0
  170. package/types/src/react/caller.d.ts +30 -0
  171. package/types/src/react/errors.d.ts +40 -0
  172. package/types/src/react/hooks.d.ts +110 -0
  173. package/types/src/react/index.d.ts +5 -0
  174. package/types/src/react/provider.d.ts +58 -0
  175. package/types/src/react/store.d.ts +68 -0
  176. package/types/src/react/testing.d.ts +28 -0
  177. package/types/src/react/types.d.ts +33 -0
  178. package/types/src/rpc/builder.d.ts +52 -0
  179. package/types/src/rpc/contract.d.ts +23 -0
  180. package/types/src/rpc/dispatcher.d.ts +11 -0
  181. package/types/src/rpc/types.d.ts +62 -0
  182. package/types/src/service/define.d.ts +42 -0
  183. package/types/src/service/index.d.ts +3 -0
  184. package/types/src/service/serve.d.ts +28 -0
  185. package/types/src/spa-fallback.d.ts +2 -0
  186. package/types/src/standard-schema.d.ts +55 -0
  187. package/types/src/testing/caller.d.ts +95 -0
  188. package/types/src/testing/challenge.d.ts +11 -0
  189. package/types/src/testing/dev.d.ts +97 -0
  190. package/types/src/testing/endpoint-access.d.ts +131 -0
  191. package/types/src/testing/index.d.ts +12 -0
  192. package/types/src/testing/isolation.d.ts +3 -0
  193. package/types/src/testing/kit.d.ts +187 -0
  194. package/types/src/testing/membership.d.ts +132 -0
  195. package/types/src/testing/policy-isolation.d.ts +113 -0
  196. package/types/src/testing/preload.d.ts +1 -0
  197. package/types/src/testing/proxy.d.ts +40 -0
  198. package/types/src/testing/runner.d.ts +8 -0
  199. package/types/src/testing/sample-file.d.ts +5 -0
  200. package/types/src/testing/status.d.ts +4 -0
  201. package/types/src/undumpable.d.ts +5 -0
@@ -16,13 +16,16 @@ import {
16
16
  FABRIKA_IAM_ISSUER,
17
17
  FABRIKA_TOKEN_KEYS,
18
18
  type IamMembershipRpc,
19
+ type IamTerminalRpc,
19
20
  isNormalizedMailbox,
20
21
  type Jwks,
21
22
  MAX_ACCESS_TOKEN_BYTES,
23
+ MAX_APPLICATION_TOKEN_BYTES,
22
24
  normalizeEmailIdentity,
23
25
  type PermissionEntry,
24
26
  PROXY_TOKEN_HEADER,
25
27
  SESSION_COOKIE,
28
+ type TerminalFailure,
26
29
  TOKEN_ALG,
27
30
  UNTRUSTED_FORWARD_HEADERS,
28
31
  workloadKeyVariable,
@@ -78,6 +81,14 @@ import {
78
81
  import { endpointProxy } from './proxy'
79
82
 
80
83
  export const TEST_ISSUER = 'https://iam.test'
84
+
85
+ /** The kit's IAM without a terminal: no identification is open, so every one is `not_found`. */
86
+ const noIdentification = (): Promise<TerminalFailure> => Promise.resolve({ ok: false, reason: 'not_found' })
87
+ const NO_IDENTIFICATIONS: IamTerminalRpc = Object.freeze({
88
+ inspectIdentification: noIdentification,
89
+ assertIdentity: noIdentification,
90
+ cancelIdentification: noIdentification,
91
+ })
81
92
  export const TEST_ENVIRONMENT = 'test'
82
93
  const KEY_ID = 'test-key'
83
94
  const TOKEN_TTL_SECONDS = 300
@@ -112,6 +123,11 @@ export interface TestKitOptions {
112
123
  * kit's proxy obtains each delegation grant from it. Without it the kit's IAM answers them under IAM's rules.
113
124
  */
114
125
  readonly membership?: IamMembershipRpc
126
+ /**
127
+ * IAM's identifications at a terminal (ADR-0067), for a contract that declares one. Without it the kit's IAM knows no
128
+ * open identification: `inspect` answers null and `assert` `not_found`.
129
+ */
130
+ readonly terminals?: IamTerminalRpc
115
131
  /**
116
132
  * Each import's owner, by the import's name: its origin and edge key, which the platform writes as the import's
117
133
  * framework variables, and how a request reaches it — usually the owner kit's `fetch`.
@@ -428,6 +444,7 @@ export function bootTestKit<C extends AnyContract>(
428
444
  ...dev,
429
445
  ...(options.revealHandlerErrors === undefined ? {} : { revealHandlerErrors: options.revealHandlerErrors }),
430
446
  membership,
447
+ terminals: options.terminals ?? (artifact.terminal === undefined ? undefined : NO_IDENTIFICATIONS),
431
448
  connectBindings,
432
449
  workloadTokens,
433
450
  mailStandIn: options.mail === 'none' ? 'none' : mail,
@@ -523,7 +540,7 @@ export function bootTestKit<C extends AnyContract>(
523
540
 
524
541
  const scope = <D extends DimensionOf<C>>(dimension: D, value: string): TestScope<D> => Object.freeze({ type: dimension, value })
525
542
 
526
- /** The caller's token, or why IAM signs none: a disabled principal, or a token above `MAX_ACCESS_TOKEN_BYTES`. */
543
+ /** The caller's token, or why IAM signs none: a disabled principal, or a token above IAM's bound for it. */
527
544
  async function mint(caller: TestCaller): Promise<{ readonly ok: true; readonly token: string; readonly expiresAt: number } | MintRefusal> {
528
545
  if (caller.disabled) return { ok: false, reason: 'disabled' }
529
546
  const now = Math.floor(Date.now() / 1000)
@@ -542,7 +559,8 @@ export function bootTestKit<C extends AnyContract>(
542
559
  })
543
560
  const { privateKey } = await signing
544
561
  const token = await new SignJWT({ ...claims }).setProtectedHeader({ alg: TOKEN_ALG, kid: KEY_ID }).sign(privateKey)
545
- if (token.length > MAX_ACCESS_TOKEN_BYTES) return { ok: false, reason: 'token_too_large' }
562
+ // IAM bounds a workload's own token as any installation token, and a token the proxy injects at the application's.
563
+ if (token.length > (caller.app === null ? MAX_APPLICATION_TOKEN_BYTES : MAX_ACCESS_TOKEN_BYTES)) return { ok: false, reason: 'token_too_large' }
546
564
  byToken.set(token, { caller, expiresAt: claims.exp })
547
565
  return { ok: true, token, expiresAt: claims.exp }
548
566
  }
@@ -19,7 +19,7 @@ import {
19
19
  type TokenCache,
20
20
  } from '@fabrika/proxy-core'
21
21
  import { applyCrossOriginHeaders, crossOriginHeaders } from '@fabrika/proxy-core/headers'
22
- import { contractProxyTable } from '@fabrika/proxy-core/manifest'
22
+ import { contractProxyEntry, contractProxyTable } from '@fabrika/proxy-core/manifest'
23
23
 
24
24
  /** What the proxy did with a request: answered it itself, or forwarded it with what only the proxy may set. */
25
25
  export type ProxyOutcome =
@@ -66,14 +66,14 @@ const ASSERTED_HEADERS = [PROXY_TOKEN_HEADER, REQUEST_ID_HEADER, ENDPOINT_HEADER
66
66
  export function endpointProxy(options: EndpointProxyOptions): TestProxy {
67
67
  const verify = createVerifyService({
68
68
  manifest: {
69
- apps: [{
69
+ apps: [contractProxyEntry({
70
70
  id: options.appId,
71
71
  hosts: [options.host],
72
72
  upstream: 'app',
73
73
  scheme: 'https',
74
- ...contractProxyTable(options.artifact),
75
- ...(options.crossOrigins === undefined || options.crossOrigins.length === 0 ? {} : { crossOrigins: [...options.crossOrigins] }),
76
- }],
74
+ table: contractProxyTable(options.artifact),
75
+ ...(options.crossOrigins === undefined ? {} : { crossOrigins: options.crossOrigins }),
76
+ })],
77
77
  },
78
78
  iam: options.iam,
79
79
  issuer: options.issuer,
@@ -0,0 +1,29 @@
1
+ export declare const APP_AGENT_SKILL_NAME = "fabrika-app";
2
+ export interface AppAgentSkillReference {
3
+ /** The file's path in the skill directory, `references/<topic>.md`: the path `SKILL.md` names it by. */
4
+ readonly path: string;
5
+ /** The topic `get_app_rules` answers it under: its file name without `.md`. */
6
+ readonly topic: string;
7
+ readonly text: string;
8
+ }
9
+ export interface AppAgentSkill {
10
+ /** The `version` of the frontmatter: the version of the `@fabrika/app` that ships it. */
11
+ readonly version: string;
12
+ /** The whole `SKILL.md`, frontmatter included: what a skill directory holds. */
13
+ readonly text: string;
14
+ /** `SKILL.md` without the frontmatter. */
15
+ readonly body: string;
16
+ /** The reference files, by path. */
17
+ readonly references: readonly AppAgentSkillReference[];
18
+ }
19
+ /** The skill of this package. */
20
+ export declare function readAppAgentSkill(): AppAgentSkill;
21
+ /** The skill in `skillDir`, the `skill/` directory of an installed `@fabrika/app`. */
22
+ export declare function readAppAgentSkillAt(skillDir: string): AppAgentSkill;
23
+ /**
24
+ * The skill of the `@fabrika/app` an application installed: the nearest `node_modules/@fabrika/app` from `appRoot`
25
+ * up, as Bun resolves the package. `null` when the application has not installed it.
26
+ */
27
+ export declare function installedAppAgentSkill(appRoot: string): AppAgentSkill | null;
28
+ /** The `AGENTS.md` of an application repository: the skill's body, then each reference file. */
29
+ export declare function renderAgentsFile(skill: AppAgentSkill): string;
@@ -0,0 +1,70 @@
1
+ export { CONTRACT_APP_ID_PATTERN } from './contract/app-id.js';
2
+ export { FILE_SCAN_JOB, FILE_SWEEP_JOB } from './files/job-names.js';
3
+ /** The TypeScript release of the platform's type check; the application's own `typescript` is not run. */
4
+ export declare const PLATFORM_TYPESCRIPT_VERSION = "5.9.3";
5
+ /** A `c.cron` job name, which a crontab line carries unquoted. */
6
+ export declare const CONTRACT_JOB_NAME_PATTERN: RegExp;
7
+ /** Five cron fields of digits and `*`, `/`, `,`, `-` only. */
8
+ export declare const CRON_SCHEDULE_PATTERN: RegExp;
9
+ /** What one contract application's recipe depends on. `contractRecipe` refuses a value that a shell would read as more. */
10
+ export interface ContractRecipeInput {
11
+ readonly appId: string;
12
+ /** The `@fabrika/check` release whose reader read the application (ADR-0062). */
13
+ readonly checkRelease: string;
14
+ /** The absolute directory the build runs in: the checker reads the source there. */
15
+ readonly sourceRoot: string;
16
+ /** The absolute directory the built application runs from: each generated script starts there. */
17
+ readonly runtimeRoot: string;
18
+ /** With a database, every container migrates and checks its schema before the server starts. */
19
+ readonly database: boolean;
20
+ /** Whether the build uploads its source maps, last and never failing the build. */
21
+ readonly sourceMaps: boolean;
22
+ readonly jobs: readonly {
23
+ readonly name: string;
24
+ readonly schedule: string;
25
+ }[];
26
+ /** The contract declares a file collection: the sweep of removed files is scheduled. */
27
+ readonly files: boolean;
28
+ /** A file collection scans its uploads: the scan pass is scheduled. */
29
+ readonly scan: boolean;
30
+ }
31
+ /** One build step: a command run in the source root, or a generated file written there, line by line. */
32
+ export type ContractBuildStep = {
33
+ readonly run: string;
34
+ } | {
35
+ readonly write: string;
36
+ readonly lines: readonly string[];
37
+ };
38
+ /** One check of the build: a command run in the source root that fails the build. */
39
+ export interface ContractCheck {
40
+ readonly name: 'tests' | 'tsc' | 'fabrika-check';
41
+ readonly command: string;
42
+ }
43
+ export interface ContractRecipe {
44
+ /** The steps after the authored SPA commands, in order. */
45
+ readonly build: readonly ContractBuildStep[];
46
+ /** The checks among `build`, in its order. */
47
+ readonly checks: readonly ContractCheck[];
48
+ /** The generated scripts every container runs before `start`, in order; any failure stops the activation. */
49
+ readonly init: readonly string[];
50
+ /** The generated script that starts the server, which only `exec`s it. */
51
+ readonly start: string;
52
+ /** The command the start script `exec`s, run from the runtime root. */
53
+ readonly serve: readonly string[];
54
+ /** The command that migrates the application's database, run from the runtime root. */
55
+ readonly migrate: readonly string[];
56
+ /** The jobs to schedule, each on one container; a command runs from the runtime root. */
57
+ readonly jobs: readonly {
58
+ readonly schedule: string;
59
+ readonly command: string;
60
+ }[];
61
+ }
62
+ /**
63
+ * The recipe of one contract application. The application's tests run first: they are authored code, so everything
64
+ * after them is reinstalled, regenerated or checked again. They run `--isolate`, as the application skill runs them: a
65
+ * test file's DOM globals otherwise leak into the next file. `tsc` runs before the checker, as in the skill's loop: the
66
+ * usual quick fix of a type error is a cast or an `any`, which only the checker refuses, so the checker is the last word
67
+ * on the code. The build has no database, so the schema checks run as init scripts, after the migrations and before the
68
+ * readiness window opens.
69
+ */
70
+ export declare function contractRecipe(input: ContractRecipeInput): ContractRecipe;
@@ -0,0 +1,29 @@
1
+ /** An application root with this file is a contract application. The paths equal `@fabrika/check`'s layout. */
2
+ export declare const CONTRACT_FILE = "fabrika.contract.ts";
3
+ /** Relative to the application root. */
4
+ export declare const GENERATED_ENTRY_FILE = "fabrika/entry.generated.ts";
5
+ /** The module whose default export is `serve(contract, …)`, relative to the application root. */
6
+ export declare const IMPLEMENTATION_FILE = "src/app.ts";
7
+ /** The application's migrations as a module, relative to the application root: a Worker reads no file system. */
8
+ export declare const GENERATED_MIGRATIONS_FILE = "fabrika/migrations.generated.ts";
9
+ /** Where an application keeps its migrations, relative to its root; `fabrika db new` writes there. */
10
+ export declare const MIGRATIONS_DIRECTORY = "db/migrations";
11
+ export type EntryRuntime = 'bun' | 'cloudflare';
12
+ export * from './build-recipe.js';
13
+ export { CLOUDFLARE_APPLICATION_BINDING, CLOUDFLARE_APPLICATION_CLASS, CLOUDFLARE_ASSETS_BINDING, CLOUDFLARE_FILES_BINDING, } from './cloudflare/names.js';
14
+ /** The entrypoint source for one runtime. */
15
+ export declare function generatedEntry(runtime: EntryRuntime): string;
16
+ /** Writes the entrypoint under `root` and returns its path; the Cloudflare one also writes the migrations module it imports. */
17
+ export declare function writeGeneratedEntry(root: string, runtime: EntryRuntime): Promise<string>;
18
+ /** The application's migrations under `root`, every `.sql` file of `db/migrations` by name; none without the directory. */
19
+ export declare function applicationMigrations(root: string): Promise<readonly {
20
+ readonly name: string;
21
+ readonly sql: string;
22
+ }[]>;
23
+ /** The migrations module source: the files' names and texts, verbatim, so their checksums stay the files'. */
24
+ export declare function generatedMigrations(migrations: readonly {
25
+ readonly name: string;
26
+ readonly sql: string;
27
+ }[]): string;
28
+ /** Writes the migrations module under `root` and returns its path. */
29
+ export declare function writeGeneratedMigrations(root: string): Promise<string>;
@@ -0,0 +1,66 @@
1
+ import type { FileObjectStore } from '@fabrika/platform';
2
+ import type { FetchService, RequestExecutionContext } from './fetch.js';
3
+ import { type IamKeySource } from './implement/boot.js';
4
+ import { type CrashEvents } from './implement/crashes.js';
5
+ import { type ContractImplementation } from './implement/implement.js';
6
+ import { type JobRunOptions, type JobRunResult } from './jobs/runner.js';
7
+ export { diskAssets } from './disk-assets.js';
8
+ export { clamscanArguments, type ClamscanOptions, clamscanScanner } from './files/clamscan.js';
9
+ export { markProductionProcess } from '@fabrika/db/production';
10
+ export interface ManagedBunHandlerOptions {
11
+ /** Required sink for rejected background tasks. Do not log raw errors or secret-bearing values. */
12
+ onBackgroundError(error: unknown): void;
13
+ executionContext?: never;
14
+ }
15
+ export interface ExternalBunHandlerOptions {
16
+ /** Existing supervised process context. Its owner remains responsible for draining it. */
17
+ executionContext: RequestExecutionContext;
18
+ onBackgroundError?: never;
19
+ }
20
+ export type BunHandlerOptions = ManagedBunHandlerOptions | ExternalBunHandlerOptions;
21
+ export interface BunHandler {
22
+ fetch(request: Request): Promise<Response>;
23
+ /** Wait until every task registered through `waitUntil()` has settled. */
24
+ drain(): Promise<void>;
25
+ }
26
+ export declare function createBunHandler<Env>(app: FetchService<Env>, env: Env, options: BunHandlerOptions): BunHandler;
27
+ /** IAM's public key set at the issuer's `/.well-known/jwks.json`, for a process with no IAM binding. */
28
+ export declare function issuerJwks(issuer: unknown): IamKeySource;
29
+ export interface ContractServer {
30
+ /** The port the server listens on; `PORT=0` picks a free one. */
31
+ readonly port: number;
32
+ /** Stops accepting requests and waits for the ones in flight. */
33
+ stop(): Promise<void>;
34
+ }
35
+ /**
36
+ * The stored files' object storage (Zerops object storage, path-style), when the platform wrote its variables;
37
+ * a partial set stops the boot. The credentials are never quoted in a message.
38
+ */
39
+ export declare function openFileStorage(framework: Readonly<Record<string, string>>): FileObjectStore | undefined;
40
+ /** The application module, imported only after the environment was captured: the generated entrypoint's `import()`. */
41
+ export type ImplementationLoader = () => Promise<{
42
+ readonly default: ContractImplementation;
43
+ }>;
44
+ export interface ContractServerOptions {
45
+ /** Where the process hears of an uncaught exception or an unhandled rejection; `process` by default. */
46
+ readonly crashes?: CrashEvents;
47
+ /** What posts a report to Operations' ingest; `fetch` by default. */
48
+ readonly operationsFetch?: (request: Request) => Promise<Response>;
49
+ }
50
+ /**
51
+ * Captures the framework's variables and scrubs them from the process, then imports the application, boots it and
52
+ * serves it on `PORT` (default 3000), on every interface: the project's private network is how the proxy reaches
53
+ * it. Called only by the generated entrypoint, with `process.env`. SIGTERM drains. Given the provisioning credential,
54
+ * it is the launcher instead: it never loads the application and re-runs the entrypoint as the runtime role.
55
+ * With an Operations DSN, a startup that throws is reported before it is rethrown, and so is a crash once it serves.
56
+ */
57
+ export declare function serveContract(load: ImplementationLoader, env: Readonly<Record<string, string | undefined>>, options?: ContractServerOptions): Promise<ContractServer>;
58
+ /** The application module at `path`, imported when the loader runs; its default export must be what `serve` returned. */
59
+ export declare function implementationAt(path: string): ImplementationLoader;
60
+ /**
61
+ * One run of the job `name` (`fabrika job run <name>`, the crontab's command): the framework's variables are captured
62
+ * and scrubbed before the application loads, exactly as `serveContract` does, then `boot` binds the job and the run
63
+ * takes its lease. The connection closes when the run ends. Given the provisioning credential, it re-runs the command
64
+ * as the runtime role, as `serveContract` does.
65
+ */
66
+ export declare function runContractJob(load: ImplementationLoader, env: Readonly<Record<string, string | undefined>>, name: string, options?: JobRunOptions): Promise<JobRunResult>;
@@ -0,0 +1,19 @@
1
+ import { z } from 'zod/v4';
2
+ declare const PageCallerSchema: z.ZodObject<{
3
+ label: z.ZodString;
4
+ email: z.ZodString;
5
+ actions: z.ZodObject<{
6
+ /** The actions held as a global grant, so in every value. */
7
+ app: z.ZodArray<z.ZodString>;
8
+ /** Each scope value the person holds something in, with those actions; sorted by dimension, then value. */
9
+ scopes: z.ZodArray<z.ZodObject<{
10
+ type: z.ZodString;
11
+ value: z.ZodString;
12
+ actions: z.ZodArray<z.ZodString>;
13
+ }, z.core.$strict>>;
14
+ }, z.core.$strict>;
15
+ }, z.core.$strict>;
16
+ export type PageCaller = z.infer<typeof PageCallerSchema>;
17
+ /** The caller document as the framework answers it; throws on any other value. */
18
+ export declare function parsePageCaller(value: unknown): PageCaller;
19
+ export {};
@@ -0,0 +1,131 @@
1
+ import { z } from 'zod/v4';
2
+ import { type AttachmentBrand } from '../contract/attachment';
3
+ import { type ProcedureType } from '../contract/declaration';
4
+ import type { AnyContract, InputSchema, Procedure, Schema } from '../contract/define';
5
+ import { type ClientInput, type FileBrand } from '../contract/file';
6
+ import { type BounceOption, type RpcFetch } from './transport';
7
+ type SurfaceOf<C> = C extends {
8
+ readonly surface: infer S;
9
+ } ? S : never;
10
+ /** The mounts a client calls: RPC mounts and link mounts. */
11
+ export type ClientMount<C> = {
12
+ [M in keyof SurfaceOf<C>]: SurfaceOf<C>[M] extends {
13
+ readonly kind: 'rpc' | 'link';
14
+ } ? M : never;
15
+ }[keyof SurfaceOf<C>] & string;
16
+ /**
17
+ * One procedure: its input as the caller writes it (defaults optional), its output as parsed, and the output schema
18
+ * it parses with (`@fabrika/app/react` reads a cached answer back through it).
19
+ */
20
+ export type ProcedureCall<I, O extends Schema> = ([I] extends [InputSchema] ? (input: ClientInput<z.input<I>>) => Promise<z.output<O>> : (input?: Record<never, never>) => Promise<z.output<O>>) & {
21
+ readonly output: O;
22
+ };
23
+ export type ClientTree<T> = {
24
+ readonly [K in keyof T]: T[K] extends Procedure<'query' | 'mutation', infer I, infer O, unknown> ? O extends AttachmentBrand ? AttachmentProcedure<I> : O extends FileBrand<string> ? FileProcedure<I> : ProcedureCall<I, O> : ClientTree<T[K]>;
25
+ };
26
+ type MountOf<C, M> = SurfaceOf<C>[M & keyof SurfaceOf<C>];
27
+ /** One function per procedure, and an `AttachmentProcedure` or `FileProcedure` per procedure that answers a file. */
28
+ export type ContractClient<C, M> = MountOf<C, M> extends {
29
+ readonly procedures: infer T;
30
+ } ? ClientTree<T> : never;
31
+ /** The answer of a client procedure: `ResultOf<typeof api.tasks.get>`; a procedure that answers a file, a `DownloadedFile`. */
32
+ export type ResultOf<F> = F extends (...args: never[]) => Promise<infer R> ? R : F extends AttachmentProcedure<unknown> | FileProcedure<unknown> ? DownloadedFile : never;
33
+ /** The input of a client procedure as the caller writes it: `InputOf<typeof api.tasks.create>`. */
34
+ export type InputOf<F> = F extends (...args: infer A) => Promise<unknown> ? A[0] : F extends AttachmentProcedure<infer I> | FileProcedure<infer I> ? DownloadInput<I>[0] : never;
35
+ export interface ClientOptions {
36
+ /** The application's origin; procedure paths are joined to it. Defaults to the page's own origin. */
37
+ readonly baseUrl?: string;
38
+ /** Override `fetch` (tests, a script). Defaults to the global `fetch`. */
39
+ readonly fetch?: RpcFetch;
40
+ /**
41
+ * A `px_` key, sent as `Authorization: Bearer`. A request with a key carries no cookie. In a browser the key goes
42
+ * only to the page's own origin.
43
+ */
44
+ readonly key?: string;
45
+ /** On a 401 that carries a login URL, send the browser to sign in instead of throwing. */
46
+ readonly bounceOnAuth?: BounceOption;
47
+ }
48
+ /** A link mount's caller is the holder of its share key, which every request presents. */
49
+ export interface LinkClientOptions extends ClientOptions {
50
+ readonly key: string;
51
+ }
52
+ type OptionsFor<C, M> = SurfaceOf<C>[M & keyof SurfaceOf<C>] extends {
53
+ readonly kind: 'link';
54
+ } ? [options: LinkClientOptions] : [options?: ClientOptions];
55
+ /**
56
+ * What a value of a client tree is, kept beside it so the value stays a plain function or record: a procedure's
57
+ * `<mount>.<path>`, its type and input schema, or a group's prefix. `@fabrika/app/react` keys its cache by it.
58
+ */
59
+ export interface ClientNode {
60
+ readonly mount: string;
61
+ /** The dotted path below the mount; `''` for the client itself. */
62
+ readonly path: string;
63
+ readonly procedure: {
64
+ readonly type: ProcedureType;
65
+ readonly input: Schema | null;
66
+ } | null;
67
+ }
68
+ /** The node a value of a client tree is, or `null` for any other value. */
69
+ export declare function clientNodeOf(value: unknown): ClientNode | null;
70
+ /**
71
+ * The URL a page's ALTCHA widget fetches the challenge of `procedure` from (`GET /__fabrika/challenge/<action>`), on the
72
+ * client's `baseUrl`. Throws for a procedure whose `admission.challenge` is not `provider: 'altcha'`.
73
+ */
74
+ export declare function challengeUrl(procedure: object): string;
75
+ declare const attachmentInput: unique symbol;
76
+ /**
77
+ * An attachment procedure of a client: not a function, because its answer is a file. `download(api.<procedure>,
78
+ * input)` fetches and saves it.
79
+ */
80
+ export interface AttachmentProcedure<I> {
81
+ /** `<mount>.<path>` */
82
+ readonly id: string;
83
+ /** The file name the contract declares. */
84
+ readonly filename: string;
85
+ /** Carries the input schema for `download`; never present at runtime. */
86
+ readonly [attachmentInput]?: I;
87
+ }
88
+ /**
89
+ * A procedure whose output is a file (`file('<kind>')`): not a function, because its answer is a file.
90
+ * `download(api.<procedure>, input)` fetches it with the caller's credential, and a GET one's `url(input)` is where a
91
+ * same-origin page links to it (`<img src>`, `<a download>`).
92
+ */
93
+ export interface FileProcedure<I> {
94
+ /** `<mount>.<path>` */
95
+ readonly id: string;
96
+ /**
97
+ * The URL of the file a GET procedure answers, for a page signed in on the same origin. A client with a key has none:
98
+ * a key never goes into a URL, so it downloads instead.
99
+ */
100
+ url(...input: DownloadInput<I>): string;
101
+ /** Carries the input schema for `download`; never present at runtime. */
102
+ readonly [attachmentInput]?: I;
103
+ }
104
+ /** A downloaded file: a `File`, and the page's offer to save it. */
105
+ export declare class DownloadedFile extends File {
106
+ /** The file as a `Blob`. */
107
+ blob(): Blob;
108
+ /** Offers the file as a download under its name; nothing where there is no page. */
109
+ save(): void;
110
+ }
111
+ /** The mount and path of an attachment procedure of a client, or `null` for any other value. */
112
+ export declare function attachmentAt(value: unknown): {
113
+ readonly mount: string;
114
+ readonly path: string;
115
+ } | null;
116
+ /** The input `download` takes: the procedure's, or none. */
117
+ export type DownloadInput<I> = [I] extends [InputSchema] ? [input: z.input<I>] : [input?: Record<never, never>];
118
+ /**
119
+ * Calls a procedure that answers a file (an attachment, or a `file('<kind>')` output) with the caller's credential, and
120
+ * answers the file: `blob()`, `stream()`, `save()`. An attachment is also offered as a download in a browser, under the
121
+ * declared name. A refusal is an `RpcError`.
122
+ */
123
+ export declare function download<I>(procedure: AttachmentProcedure<I> | FileProcedure<I>, ...input: DownloadInput<I>): Promise<DownloadedFile>;
124
+ /** `download` of any file procedure of a client, its input unchecked: the provider's `useDownload` runs it. */
125
+ export declare function downloadFile(procedure: object, input: unknown): Promise<DownloadedFile>;
126
+ /**
127
+ * The client of mount `mount` of `contract`: one function per declared procedure. A link mount's client sends its
128
+ * share key as a bearer, so `options.key` is required there.
129
+ */
130
+ export declare function createClient<const C extends AnyContract, const M extends ClientMount<C>>(contract: C, mount: M, ...rest: OptionsFor<C, M>): ContractClient<C, M>;
131
+ export {};
@@ -0,0 +1,66 @@
1
+ import type { RpcFetch } from './transport';
2
+ /** One stack frame in Sentry's shape, the file a script URL. */
3
+ export interface BrowserFrame {
4
+ readonly filename: string;
5
+ readonly function?: string;
6
+ readonly lineno: number;
7
+ readonly colno: number;
8
+ /** A frame of a script of the page's own origin. */
9
+ readonly in_app: boolean;
10
+ }
11
+ /** Where the page reports, read from its browser config. */
12
+ export interface ReportingTarget {
13
+ /** The ingest's envelope endpoint, authenticated by the DSN's public key in its query, as the Sentry SDK sends it. */
14
+ readonly envelopeUrl: string;
15
+ readonly release: string | null;
16
+ readonly environment: string | null;
17
+ }
18
+ /** What the reporter listens on and reads the page's address from: `window` in a page. */
19
+ export interface ReportingPage {
20
+ addEventListener(type: 'error' | 'unhandledrejection', listener: (event: Event) => void): void;
21
+ removeEventListener(type: 'error' | 'unhandledrejection', listener: (event: Event) => void): void;
22
+ readonly location: {
23
+ readonly origin: string;
24
+ readonly pathname: string;
25
+ };
26
+ }
27
+ /** A page that keeps failing stops reporting here; Operations rate-limits the whole source, not one page. */
28
+ export declare const MAX_REPORTS_PER_PAGE = 20;
29
+ /**
30
+ * The envelope endpoint of an ingest DSN `https://<key>@<host>/<project>`, or `null` for any other value. The server's
31
+ * reporter parses the same DSN (`jobs/report.ts`); it is server code, so a page does not import it.
32
+ */
33
+ export declare function envelopeUrlOf(dsn: string): string | null;
34
+ /** The reporting target the browser config names, or `null` when it names no usable DSN. */
35
+ export declare function reportingTargetOf(config: unknown): ReportingTarget | null;
36
+ /**
37
+ * The frames of a V8, SpiderMonkey or JavaScriptCore `stack`, oldest call first as Sentry orders them, the
38
+ * `MAX_FRAMES` nearest the throw kept. A frame whose file is no URL (an `eval`, a native function) is left out.
39
+ */
40
+ export declare function parseBrowserStack(stack: string, origin: string): BrowserFrame[];
41
+ type Mechanism = 'onerror' | 'onunhandledrejection';
42
+ interface ReportedException {
43
+ readonly type: string;
44
+ readonly value: string;
45
+ readonly mechanism: {
46
+ readonly type: Mechanism;
47
+ readonly handled: false;
48
+ };
49
+ readonly stacktrace?: {
50
+ readonly frames: readonly BrowserFrame[];
51
+ };
52
+ }
53
+ /** The envelope of one error event. The page's address goes without its query and fragment, which may carry a key. */
54
+ export declare function browserEnvelope(exception: ReportedException, target: ReportingTarget, page: ReportingPage['location'], now: Date): string;
55
+ /**
56
+ * Listen for the page's uncaught errors and unhandled rejections and send each to `target`; answers the uninstall.
57
+ * An error equal to the one just sent is not sent again, and the page sends at most `MAX_REPORTS_PER_PAGE`. A failed
58
+ * delivery is dropped: reporting never throws into the page.
59
+ */
60
+ export declare function installErrorReporting(page: ReportingPage, target: ReportingTarget, send: RpcFetch): () => void;
61
+ /**
62
+ * Read the browser config at `configUrl` and install reporting when it names a DSN; answers the uninstall, or `null`
63
+ * when nothing was installed. A missing, refused or unreadable config (local development, a test) installs nothing.
64
+ */
65
+ export declare function startErrorReporting(configUrl: string, fetch: RpcFetch, page: ReportingPage): Promise<(() => void) | null>;
66
+ export {};
@@ -0,0 +1,4 @@
1
+ export { type PageCaller, parsePageCaller } from './caller';
2
+ export { type AttachmentProcedure, challengeUrl, type ClientMount, type ClientOptions, type ClientTree, type ContractClient, createClient, download, DownloadedFile, type DownloadInput, type FileProcedure, type InputOf, type LinkClientOptions, type ProcedureCall, type ResultOf, } from './contract';
3
+ export { shareKey } from './share';
4
+ export { type BounceOption, RpcError, type RpcFetch } from './transport';
@@ -0,0 +1,14 @@
1
+ import type { InferRpcClient } from '../rpc/contract.js';
2
+ import { type BounceOption, type RpcFetch } from './transport.js';
3
+ export interface RpcClientOptions {
4
+ /** Endpoint the RPC proxy POSTs to (e.g. `/rpc`). */
5
+ readonly baseUrl: string;
6
+ /**
7
+ * Auto-redirect to SSO on a sign-in 401 (`type: 'auth'` + `loginUrl`) instead of throwing.
8
+ * `true` uses sane defaults; pass `{ sessionKey, windowMs }` to tune the re-entry guard.
9
+ */
10
+ readonly bounceOnAuth?: BounceOption;
11
+ /** Override `fetch` (tests / non-browser). Defaults to the global `fetch`. */
12
+ readonly fetch?: RpcFetch;
13
+ }
14
+ export declare function createRpcClient<TApi>(opts: RpcClientOptions): InferRpcClient<TApi>;
@@ -0,0 +1,2 @@
1
+ /** The key the share link carried, or `null` outside a share SPA or when the link had none. */
2
+ export declare function shareKey(): string | null;
@@ -0,0 +1,42 @@
1
+ export declare class RpcError extends Error {
2
+ readonly type: string;
3
+ readonly issues?: unknown;
4
+ readonly loginUrl?: string;
5
+ readonly httpStatus?: number;
6
+ constructor(payload: {
7
+ type: string;
8
+ message: string;
9
+ issues?: unknown;
10
+ loginUrl?: string;
11
+ httpStatus?: number;
12
+ });
13
+ }
14
+ /** Minimal Fetch API surface required by the RPC client. */
15
+ export type RpcFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
16
+ /**
17
+ * Auto-redirect to SSO on a sign-in 401 (`type: 'auth'` + `loginUrl`) instead of throwing.
18
+ * `true` uses sane defaults; pass `{ sessionKey, windowMs }` to tune the re-entry guard.
19
+ */
20
+ export type BounceOption = boolean | {
21
+ sessionKey?: string;
22
+ windowMs?: number;
23
+ };
24
+ export interface BounceConfig {
25
+ readonly sessionKey: string;
26
+ readonly windowMs: number;
27
+ }
28
+ export declare function resolveBounce(scope: string, opt: BounceOption | undefined): BounceConfig | null;
29
+ /**
30
+ * Sends one request and answers the envelope's `result`; every failure is an `RpcError`, and so is every answer that is
31
+ * not 2xx, whatever its body holds. `init` builds the request inside the guard, so an input that does not serialize
32
+ * rejects like a failed request instead of throwing.
33
+ */
34
+ export declare function exchange(doFetch: RpcFetch, url: string, init: () => RequestInit, bounce: BounceConfig | null): Promise<unknown>;
35
+ /**
36
+ * Sends one request whose success is a file and answers its bytes and its `Content-Disposition`; a failure is read from
37
+ * its `{ error }` envelope.
38
+ */
39
+ export declare function exchangeFile(doFetch: RpcFetch, url: string, init: () => RequestInit, bounce: BounceConfig | null): Promise<{
40
+ readonly body: Blob;
41
+ readonly disposition: string | null;
42
+ }>;
@@ -0,0 +1,42 @@
1
+ import { type SqliteStorage } from '@fabrika/db/sqlite';
2
+ import { type CrashEvents } from '../implement/crashes.js';
3
+ import type { ContractImplementation } from '../implement/implement.js';
4
+ import { type OpsStorage } from './ops.js';
5
+ /** The application module, imported only after the environment was captured: the generated entrypoint's `import()`. */
6
+ export type ImplementationLoader = () => Promise<{
7
+ readonly default: ContractImplementation;
8
+ }>;
9
+ /** One of the application's SQLite migrations, as `fabrika app build` bundles them from `db/migrations`. */
10
+ export interface ApplicationMigration {
11
+ readonly name: string;
12
+ readonly sql: string;
13
+ }
14
+ export interface ContractWorkerOptions {
15
+ /** The application's own SQLite migrations, applied as the bundle `app` after the framework's `fabrika`. */
16
+ readonly migrations?: readonly ApplicationMigration[];
17
+ /** Where the isolate hears of an unhandled rejection; the global `unhandledrejection` event by default. */
18
+ readonly rejections?: CrashEvents;
19
+ /** What posts a report to Operations' ingest; `fetch` by default. */
20
+ readonly operationsFetch?: (request: Request) => Promise<Response>;
21
+ }
22
+ /**
23
+ * Marks the Worker's minute tick, with its `scheduledTime`. The Worker's `fetch` removes it from every request it
24
+ * forwards, so only its own `scheduled` handler reaches the tick.
25
+ */
26
+ export declare const TICK_HEADER = "X-Fabrika-Tick";
27
+ /** What the object needs of its `ctx.storage`; a Durable Object's `DurableObjectStorage` fits it. */
28
+ export type ApplicationStorage = SqliteStorage & OpsStorage;
29
+ /** What the object needs of its `DurableObjectState`. */
30
+ export interface ApplicationState {
31
+ readonly storage: ApplicationStorage;
32
+ blockConcurrencyWhile<T>(callback: () => Promise<T>): Promise<T>;
33
+ waitUntil(promise: Promise<unknown>): void;
34
+ }
35
+ /** What the object classes of this isolate boot: the generated entrypoint's `createContractWorker` registers it. */
36
+ export declare function registerApplication(load: ImplementationLoader, options: ContractWorkerOptions): void;
37
+ /** The application environment's one object. The Worker's generated entrypoint exports it as `FabrikaApplication`. */
38
+ export declare class FabrikaApplication {
39
+ #private;
40
+ constructor(state: ApplicationState, env: Readonly<Record<string, unknown>>);
41
+ fetch(request: Request): Promise<Response>;
42
+ }