@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
@@ -0,0 +1,33 @@
1
+ import type { z } from 'zod/v4';
2
+ import type { Schema } from '../contract/define';
3
+ /** A query or mutation of a client that `createClient` built. */
4
+ export type ClientProcedure = ((input: never) => Promise<unknown>) & {
5
+ readonly output: Schema;
6
+ };
7
+ /** A group of a client tree (`api.deals`), or the client itself: every procedure under it. */
8
+ export interface ClientGroup {
9
+ readonly [name: string]: unknown;
10
+ }
11
+ /** What `invalidate` takes: one procedure, or a group for every procedure under it. */
12
+ export type InvalidateTarget = ClientProcedure | ClientGroup;
13
+ /** The input as the caller writes it; `undefined` admitted for a procedure that takes none. */
14
+ export type ProcedureInput<F extends ClientProcedure> = F extends (input: infer I) => Promise<unknown> ? I : never;
15
+ /** A procedure by its input `I` and output schema `O`: what the hooks take, so both are inferred from the call. */
16
+ export type TypedProcedure<I, O extends Schema> = ((input: I) => Promise<unknown>) & {
17
+ readonly output: O;
18
+ };
19
+ /** An input argument that may be left out when the procedure takes no input. */
20
+ export type InputArgument<I> = undefined extends I ? I | void : I;
21
+ /** The answer, as the procedure's output schema parses it. */
22
+ export type ProcedureResult<F extends ClientProcedure> = z.output<F['output']>;
23
+ /** The answer of a cursor list: `{ items, next }`. */
24
+ export interface PageAnswer<Item> {
25
+ readonly items: readonly Item[];
26
+ readonly next: string | null;
27
+ }
28
+ /** A procedure that pages: input `after`, answer `{ items, next }`. */
29
+ export type PagedProcedure<I, Item> = ((input: I) => Promise<unknown>) & {
30
+ readonly output: z.core.$ZodType<PageAnswer<Item>>;
31
+ };
32
+ /** The input of a paged procedure without its cursor: the hook sends `after` itself. */
33
+ export type PageInput<I> = undefined extends I ? Omit<NonNullable<I>, 'after'> | void : Omit<I, 'after'>;
@@ -0,0 +1,52 @@
1
+ import type { AuthContext, Scope } from '@fabrika/auth';
2
+ import type { StandardSchemaV1 } from '../standard-schema.js';
3
+ import type { AuthRequirement, Procedure, Router, RouterDef } from './types.js';
4
+ type Handler<TContext, TInput, TOutput> = (args: {
5
+ ctx: TContext;
6
+ input: TInput;
7
+ }) => TOutput | Promise<TOutput>;
8
+ /** Builder before an output schema is set — the terminal infers the output type from the handler. */
9
+ export declare class ProcedureBuilder<TContext, TInput> {
10
+ private readonly _input;
11
+ private readonly _requirements;
12
+ constructor(_input: StandardSchemaV1<unknown, TInput>, _requirements: ReadonlyArray<AuthRequirement<TContext, TInput>>);
13
+ /** Set the input schema — ANY Standard Schema validator (zod, valibot, arktype, …). Resets requirements. */
14
+ input<TNewInput>(schema: StandardSchemaV1<unknown, TNewInput>): ProcedureBuilder<TContext, TNewInput>;
15
+ /** Set an output schema → switch to the output builder (handler result validated against it). */
16
+ output<TOutput>(schema: StandardSchemaV1<unknown, TOutput>): OutputProcedureBuilder<TContext, TInput, TOutput>;
17
+ /**
18
+ * Record an authz requirement, checked BEFORE the handler runs (all must pass).
19
+ * Only available when the context carries `auth: AuthContext`.
20
+ */
21
+ require<TThis extends {
22
+ auth: AuthContext;
23
+ }>(this: ProcedureBuilder<TThis, TInput>, action: string, scopeResolver?: (input: TInput, ctx: TThis) => Scope | undefined): ProcedureBuilder<TThis, TInput>;
24
+ /** Terminal: a neutral handler (tagged `query`). Output type inferred from the return. */
25
+ handler<TOutput>(fn: Handler<TContext, TInput, TOutput>): Procedure<TContext, TInput, TOutput>;
26
+ /** Terminal: a `query`-tagged handler. Output type inferred from the return. */
27
+ query<TOutput>(fn: Handler<TContext, TInput, TOutput>): Procedure<TContext, TInput, TOutput>;
28
+ /** Terminal: a `mutation`-tagged handler. Output type inferred from the return. */
29
+ mutation<TOutput>(fn: Handler<TContext, TInput, TOutput>): Procedure<TContext, TInput, TOutput>;
30
+ }
31
+ /** Builder after `.output(schema)` — the terminal fixes the output to the schema type + validates it. */
32
+ export declare class OutputProcedureBuilder<TContext, TInput, TOutput> {
33
+ private readonly _input;
34
+ private readonly _output;
35
+ private readonly _requirements;
36
+ constructor(_input: StandardSchemaV1<unknown, TInput>, _output: StandardSchemaV1<unknown, TOutput>, _requirements: ReadonlyArray<AuthRequirement<TContext, TInput>>);
37
+ /** Record an authz requirement, checked BEFORE the handler runs (all must pass). */
38
+ require<TThis extends {
39
+ auth: AuthContext;
40
+ }>(this: OutputProcedureBuilder<TThis, TInput, TOutput>, action: string, scopeResolver?: (input: TInput, ctx: TThis) => Scope | undefined): OutputProcedureBuilder<TThis, TInput, TOutput>;
41
+ /** Terminal: a neutral handler (tagged `query`); result validated against the output schema. */
42
+ handler(fn: Handler<TContext, TInput, TOutput>): Procedure<TContext, TInput, TOutput>;
43
+ /** Terminal: a `query`-tagged handler; result validated against the output schema. */
44
+ query(fn: Handler<TContext, TInput, TOutput>): Procedure<TContext, TInput, TOutput>;
45
+ /** Terminal: a `mutation`-tagged handler; result validated against the output schema. */
46
+ mutation(fn: Handler<TContext, TInput, TOutput>): Procedure<TContext, TInput, TOutput>;
47
+ }
48
+ export declare function initRpc<TContext>(): {
49
+ procedure: ProcedureBuilder<TContext, void>;
50
+ router<TDef extends RouterDef>(def: TDef): Router<TDef>;
51
+ };
52
+ export {};
@@ -0,0 +1,23 @@
1
+ import type { InferRouterClient, Procedure, Router, RouterDef } from './types.js';
2
+ /** A type-only RPC procedure declaration for a portable API contract. */
3
+ export interface RpcProcedureContract<TInput, TOutput> {
4
+ readonly '~rpc': {
5
+ readonly input: TInput;
6
+ readonly output: TOutput;
7
+ };
8
+ }
9
+ /** Client-facing view of a portable contract. */
10
+ export type InferRpcContractClient<TContract> = TContract extends RpcProcedureContract<infer TInput, infer TOutput> ? (input: TInput) => Promise<TOutput> : TContract extends object ? {
11
+ [K in keyof TContract]: InferRpcContractClient<TContract[K]>;
12
+ } : never;
13
+ /** Client-facing view of either a server router or a portable contract. */
14
+ export type InferRpcClient<TApi> = TApi extends {
15
+ readonly _tag: 'router';
16
+ readonly _def: RouterDef;
17
+ } ? InferRouterClient<TApi> : InferRpcContractClient<TApi>;
18
+ type RpcRouterDefinition<TContext, TContract> = {
19
+ readonly [K in keyof TContract]: TContract[K] extends RpcProcedureContract<infer TInput, infer TOutput> ? Procedure<TContext, TInput, TOutput> : TContract[K] extends object ? RpcRouterFor<TContext, TContract[K]> : never;
20
+ };
21
+ /** Server router shape required to implement a portable contract. */
22
+ export type RpcRouterFor<TContext, TContract> = Router<RpcRouterDefinition<TContext, TContract>>;
23
+ export {};
@@ -0,0 +1,11 @@
1
+ import type { AnyRouter } from './types.js';
2
+ /**
3
+ * Dispatch an RPC POST. The `ctx` is already built (with its `ctx.auth`, for example)
4
+ * by the server before this is called. One request is one call,
5
+ * `{ method, input }`.
6
+ */
7
+ export declare function dispatchRpcRequest(args: {
8
+ router: AnyRouter;
9
+ ctx: unknown;
10
+ request: Request;
11
+ }): Promise<Response>;
@@ -0,0 +1,62 @@
1
+ import type { AuthContext, Scope } from '@fabrika/auth';
2
+ import type { StandardSchemaV1 } from '../standard-schema.js';
3
+ /** `query` and `mutation` are metadata today — the dispatcher treats them identically. */
4
+ export type ProcedureKind = 'query' | 'mutation';
5
+ /**
6
+ * A recorded authorization requirement. `scopeResolver` is declared as a METHOD
7
+ * (bivariant params) on purpose, so a concrete `Procedure<C, I, O>` stays
8
+ * assignable to `AnyProcedure`.
9
+ */
10
+ export interface AuthRequirement<TContext, TInput> {
11
+ readonly action: string;
12
+ scopeResolver?(input: TInput, ctx: TContext): Scope | undefined;
13
+ }
14
+ export interface Procedure<TContext, TInput, TOutput> {
15
+ readonly _tag: 'procedure';
16
+ readonly kind: ProcedureKind;
17
+ readonly input: StandardSchemaV1<unknown, TInput>;
18
+ readonly output: StandardSchemaV1<unknown, TOutput> | null;
19
+ readonly requirements: ReadonlyArray<AuthRequirement<TContext, TInput>>;
20
+ handler(args: {
21
+ ctx: TContext;
22
+ input: TInput;
23
+ }): Promise<TOutput> | TOutput;
24
+ }
25
+ export interface AnyProcedure {
26
+ readonly _tag: 'procedure';
27
+ readonly kind: ProcedureKind;
28
+ readonly input: StandardSchemaV1;
29
+ readonly output: StandardSchemaV1 | null;
30
+ readonly requirements: ReadonlyArray<AuthRequirement<unknown, unknown>>;
31
+ handler(args: {
32
+ ctx: unknown;
33
+ input: unknown;
34
+ }): Promise<unknown> | unknown;
35
+ }
36
+ export type RouterDef = {
37
+ readonly [key: string]: AnyProcedure | AnyRouter;
38
+ };
39
+ export interface Router<TDef extends RouterDef> {
40
+ readonly _tag: 'router';
41
+ readonly _def: TDef;
42
+ }
43
+ export type AnyRouter = Router<RouterDef>;
44
+ /** Re-export the auth contract used by procedure requirements. */
45
+ export type { AuthContext, Scope };
46
+ /** Server-facing view of a router: each procedure as its handler signature. */
47
+ export type InferRouter<TRouter> = TRouter extends {
48
+ _tag: 'router';
49
+ _def: infer TDef;
50
+ } ? TDef extends RouterDef ? {
51
+ [K in keyof TDef]: TDef[K] extends Procedure<infer C, infer I, infer O> ? (args: {
52
+ ctx: C;
53
+ input: I;
54
+ }) => Promise<O> | O : TDef[K] extends Router<RouterDef> ? InferRouter<TDef[K]> : never;
55
+ } : never : never;
56
+ /** Client-facing view of a router: each procedure as `(input) => Promise<output>`. */
57
+ export type InferRouterClient<TRouter> = TRouter extends {
58
+ _tag: 'router';
59
+ _def: infer TDef;
60
+ } ? TDef extends RouterDef ? {
61
+ [K in keyof TDef]: TDef[K] extends Procedure<unknown, infer I, infer O> ? (input: I) => Promise<O> : TDef[K] extends Router<RouterDef> ? InferRouterClient<TDef[K]> : never;
62
+ } : never : never;
@@ -0,0 +1,42 @@
1
+ import { type BodyAdmission, type InstallationCaller, type InstallationEndpointMethod, type InstallationEndpointRow, type InstallationEndpointTable } from '@fabrika/auth-core';
2
+ /** A row as the builder writes it; the record key of `endpoints` becomes its id. */
3
+ export type ServiceEndpointSpec = InstallationEndpointRow extends infer Row ? Row extends InstallationEndpointRow ? Omit<Row, 'id'> : never : never;
4
+ /** One installation service's table, with the row ids as a type so its handlers can be checked against them. */
5
+ export interface ServiceArtifact<Id extends string = string> extends InstallationEndpointTable {
6
+ /** The service's IAM app id; never `<app>@<env>`. */
7
+ readonly id: string;
8
+ readonly endpoints: readonly (InstallationEndpointRow & {
9
+ readonly id: Id;
10
+ })[];
11
+ }
12
+ export interface ServiceEndpointOptions {
13
+ readonly callers: readonly [InstallationCaller, ...InstallationCaller[]];
14
+ /** The request body the row admits; omitted admits none. */
15
+ readonly admission?: BodyAdmission;
16
+ /** The scopes an OAuth resource token must carry; required exactly when `callers` holds `oauth`. */
17
+ readonly scopes?: readonly string[];
18
+ }
19
+ export interface ServiceContractBuilder {
20
+ /** A liveness endpoint: anonymous, and served without `X-Fabrika-Endpoint`. */
21
+ health(method: InstallationEndpointMethod, path: string): ServiceEndpointSpec;
22
+ /** An endpoint the service authenticates itself; the proxy reads no credential and keeps `Authorization`. */
23
+ protocol(method: InstallationEndpointMethod, path: string): ServiceEndpointSpec;
24
+ /** A credentialed endpoint: a listed caller kind, and no permission (`authenticated`). */
25
+ service(method: InstallationEndpointMethod, path: string, options: ServiceEndpointOptions): ServiceEndpointSpec;
26
+ }
27
+ export interface ServiceContractDeclaration<Id extends string> {
28
+ /** `null` is a row this composition does not serve: the table has no row there, and its handler is never called. */
29
+ readonly endpoints: Readonly<Record<Id, ServiceEndpointSpec | null>>;
30
+ /** Literal prefixes a path under which reaches only a row declared under it. */
31
+ readonly reservedPrefixes: readonly string[];
32
+ }
33
+ export declare class ServiceContractError extends Error {
34
+ readonly name = "ServiceContractError";
35
+ readonly problems: readonly string[];
36
+ constructor(problems: readonly string[]);
37
+ }
38
+ /**
39
+ * Declare one installation service's endpoint table. Validated here, once: an id that is an application environment,
40
+ * an `oauth` caller without its scopes (or scopes without it), and every table the matcher would refuse.
41
+ */
42
+ export declare function defineServiceContract<const Id extends string>(id: string, declare: (s: ServiceContractBuilder) => ServiceContractDeclaration<Id>): ServiceArtifact<Id>;
@@ -0,0 +1,3 @@
1
+ export { dispatchRpcRequest } from '../rpc/dispatcher.js';
2
+ export { defineServiceContract, type ServiceArtifact, type ServiceContractBuilder, type ServiceContractDeclaration, ServiceContractError, type ServiceEndpointOptions, type ServiceEndpointSpec, } from './define.js';
3
+ export { serveService, type ServeServiceOptions, type ServiceHandler, type ServiceHandlers, ServiceHandlersError, type ServiceRequest, } from './serve.js';
@@ -0,0 +1,28 @@
1
+ import type { FetchService, RequestExecutionContext } from '../fetch.js';
2
+ import type { ServiceArtifact } from './define.js';
3
+ export interface ServiceRequest<Env> {
4
+ readonly request: Request;
5
+ /** The `:name` segments of the selected path, percent-decoded. */
6
+ readonly params: Readonly<Record<string, string>>;
7
+ readonly env: Env;
8
+ readonly exec: RequestExecutionContext;
9
+ }
10
+ export type ServiceHandler<Env> = (input: ServiceRequest<Env>) => Response | Promise<Response>;
11
+ /** One handler per declared row id, also for a row this composition left out; that one is never called. */
12
+ export type ServiceHandlers<Id extends string, Env> = {
13
+ readonly [K in Id]: ServiceHandler<Env>;
14
+ };
15
+ export interface ServeServiceOptions<Env> {
16
+ /** Map a value a handler threw to a response. Defaults to the structural error mapping. */
17
+ readonly onError?: (error: unknown, request: Request, env: Env) => Response;
18
+ }
19
+ export declare class ServiceHandlersError extends Error {
20
+ readonly name = "ServiceHandlersError";
21
+ }
22
+ /**
23
+ * Serve a service contract. Per request: a non-canonical path is a 400, a request no row selects a 404, a `service`
24
+ * row reached without the proxy's `X-Fabrika-Endpoint` for that row and its digest a 503; then the row's handler. A
25
+ * `protocol` or `health` row ignores the header, because the service authenticates it itself and it also arrives
26
+ * unfronted (ADR-0022 rule 5).
27
+ */
28
+ export declare function serveService<Id extends string, Env>(service: ServiceArtifact<Id>, handlers: ServiceHandlers<Id, Env>, options?: ServeServiceOptions<Env>): FetchService<Env>;
@@ -0,0 +1,2 @@
1
+ /** A GET or HEAD whose last path segment has no extension, or one that asks for HTML (a navigation to `/notes/v1.2`). */
2
+ export declare function isPageRoute(request: Request): boolean;
@@ -0,0 +1,55 @@
1
+ /** The Standard Schema interface. */
2
+ export interface StandardSchemaV1<Input = unknown, Output = Input> {
3
+ /** The Standard Schema properties. */
4
+ readonly '~standard': StandardSchemaV1.Props<Input, Output>;
5
+ }
6
+ export declare namespace StandardSchemaV1 {
7
+ /** The Standard Schema properties interface. */
8
+ interface Props<Input = unknown, Output = Input> {
9
+ /** The version number of the standard. */
10
+ readonly version: 1;
11
+ /** The vendor name of the schema library. */
12
+ readonly vendor: string;
13
+ /** Validates unknown input values. */
14
+ readonly validate: (value: unknown) => Result<Output> | Promise<Result<Output>>;
15
+ /** Inferred types associated with the schema. */
16
+ readonly types?: Types<Input, Output> | undefined;
17
+ }
18
+ /** The result interface of the validate function. */
19
+ type Result<Output> = SuccessResult<Output> | FailureResult;
20
+ /** The result interface if validation succeeds. */
21
+ interface SuccessResult<Output> {
22
+ /** The typed output value. */
23
+ readonly value: Output;
24
+ /** The non-existent issues. */
25
+ readonly issues?: undefined;
26
+ }
27
+ /** The result interface if validation fails. */
28
+ interface FailureResult {
29
+ /** The issues of failed validation. */
30
+ readonly issues: ReadonlyArray<Issue>;
31
+ }
32
+ /** The issue interface of the failure output. */
33
+ interface Issue {
34
+ /** The error message of the issue. */
35
+ readonly message: string;
36
+ /** The path of the issue, if any. */
37
+ readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
38
+ }
39
+ /** The path segment interface of the issue. */
40
+ interface PathSegment {
41
+ /** The key representing a path segment. */
42
+ readonly key: PropertyKey;
43
+ }
44
+ /** The Standard Schema types interface. */
45
+ interface Types<Input = unknown, Output = Input> {
46
+ /** The input type of the schema. */
47
+ readonly input: Input;
48
+ /** The output type of the schema. */
49
+ readonly output: Output;
50
+ }
51
+ /** Infers the input type of a Standard Schema. */
52
+ type InferInput<Schema extends StandardSchemaV1> = NonNullable<Schema['~standard']['types']>['input'];
53
+ /** Infers the output type of a Standard Schema. */
54
+ type InferOutput<Schema extends StandardSchemaV1> = NonNullable<Schema['~standard']['types']>['output'];
55
+ }
@@ -0,0 +1,95 @@
1
+ import type { PermissionEntry, PrincipalAffiliation } from '@fabrika/auth-core';
2
+ import type { ActionOf, AnyContract, DimensionOf, RoleOf } from '../contract/define';
3
+ /** `app` is the connector of another application: its key reaches only the exports that application consumes. */
4
+ export type TestCallerKind = 'human' | 'machine' | 'link' | 'app';
5
+ export interface TestScope<D extends string = string> {
6
+ readonly type: D;
7
+ readonly value: string;
8
+ }
9
+ /** An action, or a role whose actions IAM grants together; without a scope it is global. */
10
+ export type TestGrant<C> = {
11
+ readonly action: ActionOf<C>;
12
+ readonly scope?: TestScope<DimensionOf<C>>;
13
+ } | {
14
+ readonly role: RoleOf<C>;
15
+ readonly scope?: TestScope<DimensionOf<C>>;
16
+ };
17
+ export interface TestCallerOptions {
18
+ /** Defaults to `human`. */
19
+ readonly kind?: TestCallerKind;
20
+ /** How a human signs in: the IAM session (default) or a `px_` key bound to the person. Other kinds use a key. */
21
+ readonly via?: 'session' | 'key';
22
+ readonly id?: string;
23
+ readonly label?: string;
24
+ /** A human's address (`ctx.principal.email`), normalized as IAM stores it. Only a human has one. */
25
+ readonly email?: string;
26
+ /**
27
+ * Defaults to `internal`. An `external` human holds only roles the contract declares `external`, as IAM resolves it
28
+ * (ADR-0051); the kit refuses any other grant for one.
29
+ */
30
+ readonly affiliation?: PrincipalAffiliation;
31
+ /**
32
+ * An operator disabled the principal in IAM: a member directory lists and finds it with `disabled: true`, and the kit's
33
+ * IAM signs no token for its session or key, so its requests are refused as IAM refuses a disabled principal's.
34
+ */
35
+ readonly disabled?: boolean;
36
+ /**
37
+ * When the person last signed in to the application (unix seconds), as IAM records it at the handoff; absent, never.
38
+ * A member directory answers it to a holder of `members.manage` only. Only a human signs in.
39
+ */
40
+ readonly lastSignInAt?: number;
41
+ /** The consuming application's id, for an `app` caller. */
42
+ readonly app?: string;
43
+ /**
44
+ * What a `link` caller's key is bound to: the link mount, and the object first, then each value containing it. A link
45
+ * caller without one holds a key bound to nothing, which no procedure admits.
46
+ */
47
+ readonly link?: TestLink;
48
+ }
49
+ export interface TestLink {
50
+ readonly mount: string;
51
+ readonly chain: readonly TestScope[];
52
+ }
53
+ interface TestCallerCredential {
54
+ /** The principal id, or the share key's credential id for a link. */
55
+ readonly id: string;
56
+ /** `internal` for every caller but an `external` human. */
57
+ readonly affiliation: PrincipalAffiliation;
58
+ /** `true` for a principal an operator disabled; `false` for every other caller, a link holder included. */
59
+ readonly disabled: boolean;
60
+ /** A human's last sign-in to the application (`TestCallerOptions.lastSignInAt`); `null` for one that never did and every other caller. */
61
+ readonly lastSignInAt: number | null;
62
+ readonly perms: readonly PermissionEntry[];
63
+ /** The `px_` key a key caller presents as a bearer; `null` for a session. */
64
+ readonly key: string | null;
65
+ /** The `Cookie` header a signed-in browser sends; `null` for a key caller. */
66
+ readonly cookie: string | null;
67
+ }
68
+ /** A person, a script or another application's connector: a principal with a label. */
69
+ export interface LabelledTestCaller extends TestCallerCredential {
70
+ readonly kind: Exclude<TestCallerKind, 'link'>;
71
+ /** The consuming application of an `app` caller, `null` for every other kind. */
72
+ readonly app: string | null;
73
+ readonly link: null;
74
+ readonly label: string;
75
+ /** A human's normalized address, which its token carries; `null` for a script and a connector. */
76
+ readonly email: string | null;
77
+ }
78
+ /** A person, whose token carries its address: `ctx.principal.email` of its requests. */
79
+ export interface PersonTestCaller extends LabelledTestCaller {
80
+ readonly kind: 'human';
81
+ readonly email: string;
82
+ }
83
+ /** The holder of a share link: an anonymous key, which carries no label. */
84
+ export interface LinkTestCaller extends TestCallerCredential {
85
+ readonly kind: 'link';
86
+ readonly app: null;
87
+ /** The link mount and chain the key is bound to; `null` for a key bound to nothing. */
88
+ readonly link: TestLink | null;
89
+ readonly label: null;
90
+ readonly email: null;
91
+ }
92
+ export type TestCaller = LabelledTestCaller | LinkTestCaller;
93
+ /** The permissions `grants` resolve to: a role becomes each of its actions in the role's scope. */
94
+ export declare function resolveGrants<C extends AnyContract>(contract: C, grants: readonly TestGrant<C>[]): PermissionEntry[];
95
+ export {};
@@ -0,0 +1,11 @@
1
+ import type { RpcFetch } from '../client/transport';
2
+ import type { ChallengeFetch } from '../implement/challenge';
3
+ /** The token Turnstile's always-passing test widget (site key `1x00000000000000000000AA`) hands out every time. */
4
+ export declare const TURNSTILE_TEST_TOKEN = "XXXX.DUMMY.TOKEN.XXXX";
5
+ /** Turnstile's siteverify that passes every token: a fresh answer for the host and the form the check expects. */
6
+ export declare const passingChallenge: ChallengeFetch;
7
+ /**
8
+ * Fetches a challenge from `url` through `fetch` and solves it as the widget does, answering the payload the page puts
9
+ * into `challengeToken`. The kit issues the cheapest challenge, so this takes milliseconds.
10
+ */
11
+ export declare function solveChallengeAt(fetch: RpcFetch, url: string): Promise<string>;
@@ -0,0 +1,97 @@
1
+ import { type EmailDeliveryErrorCode, type EmailDeliveryOutcome } from '@fabrika/email';
2
+ import type { AnyContract, DimensionOf } from '../contract/define';
3
+ import type { ContractImplementation } from '../implement/implement';
4
+ import type { PersonTestCaller, TestCaller } from './caller';
5
+ import { type TestKit, type TestKitOptions } from './kit';
6
+ import type { SentInvitation, TestMembership } from './membership';
7
+ /** Someone the author clicks through the application as: a person the seed made with `dev.testCaller`. */
8
+ export interface DevPersona {
9
+ /** Shown on the picker's card and in the toolbar, unique among the personas: `Petra`. */
10
+ readonly name: string;
11
+ /** One short line of who this is: `Staff of the design workspace`. */
12
+ readonly description?: string;
13
+ readonly caller: PersonTestCaller;
14
+ }
15
+ /** A page a link holder opens: a link the seed issued through the application's own endpoint. */
16
+ export interface DevLink {
17
+ readonly title: string;
18
+ readonly description?: string;
19
+ /** The URL the application answered, a path of its origin: `/s/#px_…`. */
20
+ readonly url: string;
21
+ }
22
+ /** A scope value the seed made, with the name a person knows it by: `{ value: 'w1', label: 'Design team' }`. */
23
+ export interface DevScopeValue {
24
+ readonly value: string;
25
+ readonly label: string;
26
+ }
27
+ /** What a seed answers; every part is optional. */
28
+ export interface DevSeedResult<C extends AnyContract> {
29
+ readonly personas?: readonly DevPersona[];
30
+ readonly links?: readonly DevLink[];
31
+ /** The values of each dimension the seed made, which the picker offers for a membership role. */
32
+ readonly scopes?: {
33
+ readonly [D in DimensionOf<C>]?: readonly DevScopeValue[];
34
+ };
35
+ }
36
+ /** The origin `fabrika app dev` captures every request to: an outside provider's stand-in, named by its path. */
37
+ export declare const DEV_OUTBOX_ORIGIN = "https://outbox.invalid";
38
+ /** A request the application sent to `DEV_OUTBOX_ORIGIN` under `fabrika app dev`: its body, never its headers. */
39
+ export interface DevOutboxMessage {
40
+ readonly id: number;
41
+ readonly method: string;
42
+ /** The path it was sent to: `/mail`. */
43
+ readonly path: string;
44
+ readonly contentType: string | null;
45
+ readonly body: string;
46
+ }
47
+ /** A message a job handed to the kit's mail stand-in, as `fabrika app dev` shows it. */
48
+ export interface DevMail {
49
+ /** Its position among the handoffs, from 1. */
50
+ readonly id: number;
51
+ /** When it was handed off, ISO 8601. */
52
+ readonly at: string;
53
+ readonly to: string;
54
+ readonly subject: string;
55
+ readonly text: string;
56
+ readonly html: string | null;
57
+ readonly replyTo: string | null;
58
+ readonly fromName: string | null;
59
+ /** The idempotency key as the job passed it. */
60
+ readonly key: string;
61
+ /** The key the transport got: `key` behind the application environment's prefix. */
62
+ readonly transportKey: string;
63
+ readonly answer: 'accepted' | EmailDeliveryErrorCode;
64
+ readonly outcome: 'accepted' | EmailDeliveryOutcome;
65
+ /** Whether this handoff put the message in the inbox; a repeat under its key does not. */
66
+ readonly delivered: boolean;
67
+ }
68
+ /** What `fabrika app dev` stands in for outside the application, for the seed to read. */
69
+ export interface DevOutside {
70
+ /** Every request sent to `DEV_OUTBOX_ORIGIN` so far, oldest first. */
71
+ outbox(): readonly DevOutboxMessage[];
72
+ /** Every message a job handed to the mail stand-in so far, oldest first. */
73
+ mails(): readonly DevMail[];
74
+ }
75
+ /**
76
+ * The default export of `dev/seed.ts`: it receives the application's test kit, the API its tests use, writes through the
77
+ * endpoints, and answers who and what the picker offers. `outside` reads what the application sent to the dev outbox
78
+ * and every message a job handed to the mail stand-in, oldest first.
79
+ */
80
+ export type DevSeed<C extends AnyContract> = (dev: TestKit<C>, outside: DevOutside) => Promise<DevSeedResult<C>> | DevSeedResult<C>;
81
+ export interface DevKit<C extends AnyContract> {
82
+ readonly kit: TestKit<C>;
83
+ /** The person `value` is when this kit made it; `undefined` for any other value. */
84
+ personOf(value: unknown): PersonTestCaller | undefined;
85
+ /** The roles a caller of this kit holds now: its grants, with what the application assigned and revoked through IAM. */
86
+ membershipsOf(caller: TestCaller): readonly TestMembership[];
87
+ /** The invitation e-mails the stand-in IAM sent, in the order it first sent them. */
88
+ invitations(): readonly SentInvitation[];
89
+ /** Every message the application's jobs handed to the mail stand-in, oldest first; one refused before the handoff is absent. */
90
+ mails(): readonly DevMail[];
91
+ }
92
+ /**
93
+ * The test kit of `app` with its stand-in IAM, and what `fabrika app dev` shows of it. A challenge passes
94
+ * (`passingChallenge`) unless `options.challengeFetch` answers it, and admits the test widget's token
95
+ * (`TURNSTILE_TEST_TOKEN`) at every call; any other token is single use, as deployed.
96
+ */
97
+ export declare function devKit<C extends AnyContract>(app: ContractImplementation<C>, options?: Omit<TestKitOptions, 'membership'>): DevKit<C>;