@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.
- package/package.json +27 -26
- package/skill/SKILL.md +1 -1
- package/skill/references/app-setup.md +8 -8
- package/src/dev/changes.ts +5 -43
- package/src/dev/contract-digest.ts +17 -0
- package/src/dev/index.ts +1 -1
- package/src/dev/launcher.ts +116 -31
- package/src/dev/wire.ts +11 -1
- package/types/src/agent-skill.d.ts +29 -0
- package/types/src/build.d.ts +11 -0
- package/types/src/bun.d.ts +66 -0
- package/types/src/client/caller.d.ts +19 -0
- package/types/src/client/contract.d.ts +131 -0
- package/types/src/client/error-reporting.d.ts +66 -0
- package/types/src/client/index.d.ts +4 -0
- package/types/src/client/rpc.d.ts +14 -0
- package/types/src/client/share.d.ts +2 -0
- package/types/src/client/transport.d.ts +42 -0
- package/types/src/cloudflare.d.ts +49 -0
- package/types/src/contract/artifact-schema.d.ts +8 -0
- package/types/src/contract/attachment.d.ts +41 -0
- package/types/src/contract/build.d.ts +12 -0
- package/types/src/contract/compile.d.ts +11 -0
- package/types/src/contract/connect.d.ts +51 -0
- package/types/src/contract/context.d.ts +501 -0
- package/types/src/contract/declaration.d.ts +303 -0
- package/types/src/contract/defaults.d.ts +208 -0
- package/types/src/contract/define.d.ts +359 -0
- package/types/src/contract/file.d.ts +44 -0
- package/types/src/contract/group.d.ts +274 -0
- package/types/src/contract/handled.d.ts +18 -0
- package/types/src/contract/index.d.ts +19 -0
- package/types/src/contract/kit.d.ts +210 -0
- package/types/src/contract/platform.d.ts +9 -0
- package/types/src/contract/problems.d.ts +14 -0
- package/types/src/contract/redirect.d.ts +25 -0
- package/types/src/contract/schema-from-shape.d.ts +10 -0
- package/types/src/contract/schema-shape.d.ts +5 -0
- package/types/src/contract/snapshot-source.d.ts +15 -0
- package/types/src/contract/token.d.ts +58 -0
- package/types/src/contract/vocabulary.d.ts +30 -0
- package/types/src/contract/webhook-binding.d.ts +24 -0
- package/types/src/contract/webhook-scheme.d.ts +23 -0
- package/types/src/dev/changes.d.ts +7 -0
- package/types/src/dev/contract-digest.d.ts +1 -0
- package/types/src/dev/index.d.ts +2 -0
- package/types/src/dev/launcher.d.ts +32 -0
- package/types/src/dev/output.d.ts +22 -0
- package/types/src/dev/wire.d.ts +126 -0
- package/types/src/disk-assets.d.ts +3 -0
- package/types/src/errors.d.ts +46 -0
- package/types/src/fetch.d.ts +7 -0
- package/types/src/files/archive.d.ts +26 -0
- package/types/src/files/clamscan.d.ts +32 -0
- package/types/src/files/handle.d.ts +106 -0
- package/types/src/files/kinds.d.ts +105 -0
- package/types/src/files/scan.d.ts +80 -0
- package/types/src/files/sniff.d.ts +7 -0
- package/types/src/files/streams.d.ts +17 -0
- package/types/src/files/treatment.d.ts +18 -0
- package/types/src/from-row.d.ts +20 -0
- package/types/src/honeypot.d.ts +2 -0
- package/types/src/implement/anonymous.d.ts +26 -0
- package/types/src/implement/attachment.d.ts +14 -0
- package/types/src/implement/boot.d.ts +109 -0
- package/types/src/implement/bounded.d.ts +22 -0
- package/types/src/implement/browser-config.d.ts +19 -0
- package/types/src/implement/capability.d.ts +140 -0
- package/types/src/implement/challenge.d.ts +75 -0
- package/types/src/implement/client-address.d.ts +11 -0
- package/types/src/implement/connect.d.ts +67 -0
- package/types/src/implement/crashes.d.ts +31 -0
- package/types/src/implement/dispatch.d.ts +218 -0
- package/types/src/implement/egress.d.ts +14 -0
- package/types/src/implement/environment.d.ts +14 -0
- package/types/src/implement/failures.d.ts +34 -0
- package/types/src/implement/faults.d.ts +44 -0
- package/types/src/implement/headers.d.ts +7 -0
- package/types/src/implement/implement.d.ts +58 -0
- package/types/src/implement/index.d.ts +24 -0
- package/types/src/implement/invocation.d.ts +120 -0
- package/types/src/implement/membership.d.ts +201 -0
- package/types/src/implement/multipart.d.ts +36 -0
- package/types/src/implement/params.d.ts +7 -0
- package/types/src/implement/principal.d.ts +52 -0
- package/types/src/implement/project.d.ts +11 -0
- package/types/src/implement/public.d.ts +61 -0
- package/types/src/implement/serve.d.ts +25 -0
- package/types/src/implement/tokens.d.ts +49 -0
- package/types/src/implement/types.d.ts +243 -0
- package/types/src/implement/urls.d.ts +15 -0
- package/types/src/implement/verifiers.d.ts +19 -0
- package/types/src/implement/webhook.d.ts +55 -0
- package/types/src/implement/workload.d.ts +56 -0
- package/types/src/implement/xlsx.d.ts +6 -0
- package/types/src/index.d.ts +12 -0
- package/types/src/jobs/exception.d.ts +40 -0
- package/types/src/jobs/mail.d.ts +67 -0
- package/types/src/jobs/report.d.ts +56 -0
- package/types/src/jobs/runner.d.ts +145 -0
- package/types/src/jobs/schedule.d.ts +15 -0
- package/types/src/jobs/scrub.d.ts +12 -0
- package/types/src/jobs/types.d.ts +92 -0
- package/types/src/react/caller.d.ts +30 -0
- package/types/src/react/errors.d.ts +40 -0
- package/types/src/react/hooks.d.ts +110 -0
- package/types/src/react/index.d.ts +5 -0
- package/types/src/react/provider.d.ts +58 -0
- package/types/src/react/store.d.ts +68 -0
- package/types/src/react/testing.d.ts +28 -0
- package/types/src/react/types.d.ts +33 -0
- package/types/src/rpc/builder.d.ts +52 -0
- package/types/src/rpc/contract.d.ts +23 -0
- package/types/src/rpc/dispatcher.d.ts +11 -0
- package/types/src/rpc/types.d.ts +62 -0
- package/types/src/service/define.d.ts +42 -0
- package/types/src/service/index.d.ts +3 -0
- package/types/src/service/serve.d.ts +28 -0
- package/types/src/standard-schema.d.ts +55 -0
- package/types/src/testing/caller.d.ts +95 -0
- package/types/src/testing/challenge.d.ts +11 -0
- package/types/src/testing/dev.d.ts +97 -0
- package/types/src/testing/endpoint-access.d.ts +99 -0
- package/types/src/testing/index.d.ts +12 -0
- package/types/src/testing/isolation.d.ts +3 -0
- package/types/src/testing/kit.d.ts +182 -0
- package/types/src/testing/membership.d.ts +132 -0
- package/types/src/testing/policy-isolation.d.ts +113 -0
- package/types/src/testing/preload.d.ts +1 -0
- package/types/src/testing/proxy.d.ts +40 -0
- package/types/src/testing/runner.d.ts +8 -0
- package/types/src/testing/sample-file.d.ts +5 -0
- package/types/src/testing/status.d.ts +4 -0
- package/types/src/undumpable.d.ts +5 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
import type { InputSchema, NotLoose, Schema } from './define';
|
|
3
|
+
declare const SIGNED_TOKEN: unique symbol;
|
|
4
|
+
/** The longest token a field admits: a purpose's claims, a key id, an expiry and a signature, URL-safe. */
|
|
5
|
+
export declare const SIGNED_TOKEN_MAX_LENGTH = 4096;
|
|
6
|
+
/** The most days any purpose may declare: a key stays verifying that long after it stopped signing. */
|
|
7
|
+
export declare const TOKEN_MAX_DAYS = 3650;
|
|
8
|
+
/** One token purpose: the claims a token of it carries and the longest lifetime a site may sign one for. */
|
|
9
|
+
export interface TokenPurpose<C extends Schema = Schema> {
|
|
10
|
+
readonly '~kind': 'token';
|
|
11
|
+
readonly claims: C;
|
|
12
|
+
readonly maxDays: number;
|
|
13
|
+
}
|
|
14
|
+
export type TokenPurposes = {
|
|
15
|
+
readonly [purpose: string]: TokenPurpose;
|
|
16
|
+
};
|
|
17
|
+
export interface TokenOptions {
|
|
18
|
+
/** The longest lifetime, in days, of a token of this purpose. */
|
|
19
|
+
readonly maxDays: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A token purpose: `token(Recipient, { maxDays: 400 })` under `defineApp(vocabulary, tables, { tokens })`. `Claims` is a
|
|
23
|
+
* strict object; `compileContract` and the checker refuse what a token may not carry.
|
|
24
|
+
*/
|
|
25
|
+
export declare function token<C extends InputSchema>(claims: C & NotLoose<C>, options: TokenOptions): TokenPurpose<C>;
|
|
26
|
+
/** What `signedToken()` adds to its schema: the purpose a token of the field must be signed for. */
|
|
27
|
+
export interface SignedTokenMark {
|
|
28
|
+
readonly purpose: string;
|
|
29
|
+
}
|
|
30
|
+
type TokenBrand<P extends string> = `fabrika.token:${P}`;
|
|
31
|
+
/** A field that carries a signed token of purpose `P`: a string on the wire, the purpose's claims in the handler. */
|
|
32
|
+
export type SignedTokenSchema<P extends string> = (PropertyKey extends TokenBrand<P> ? z.ZodString : z.core.$ZodBranded<z.ZodString, TokenBrand<P>>) & {
|
|
33
|
+
readonly [SIGNED_TOKEN]: SignedTokenMark;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* A field of a procedure's input or `http.query` that carries a token of `purpose`, verified as the input is parsed:
|
|
37
|
+
* `z.strictObject({ t: signedToken('unsubscribe') })`. Only a top-level field; the handler receives the claims.
|
|
38
|
+
*/
|
|
39
|
+
export declare function signedToken<const P extends string>(purpose: P): SignedTokenSchema<P>;
|
|
40
|
+
/** The mark of a `signedToken()` schema, or `null` for any other schema. */
|
|
41
|
+
export declare function signedTokenOf(schema: Schema): SignedTokenMark | null;
|
|
42
|
+
/** The purpose a field's parsed value names when the field is a signed token; `never` for any other field. */
|
|
43
|
+
export type TokenPurposeOf<V> = V extends {
|
|
44
|
+
readonly [z.core.$brand]: infer B;
|
|
45
|
+
} ? keyof B extends TokenBrand<infer P> ? P : never : never;
|
|
46
|
+
/** One field as the handler holds it: a signed token as the claims of its purpose, any other field as it is. */
|
|
47
|
+
type ClaimsField<V, T> = [TokenPurposeOf<NonNullable<V>>] extends [never] ? V : (TokenPurposeOf<NonNullable<V>> extends keyof T ? T[TokenPurposeOf<NonNullable<V>>] extends TokenPurpose<infer C> ? z.output<C> : never : never) | (undefined extends V ? undefined : never);
|
|
48
|
+
/** An input as its handler receives it: each signed token field replaced by the verified claims of its purpose. */
|
|
49
|
+
export type WithClaims<In, T> = [keyof In] extends [never] ? In : {
|
|
50
|
+
readonly [F in keyof In]: ClaimsField<In[F], T>;
|
|
51
|
+
};
|
|
52
|
+
/** `ctx.tokens` of a site that declares `signs`: a token of one of those purposes, valid for `days`. */
|
|
53
|
+
export interface TokenSigning<T extends TokenPurposes, S extends string> {
|
|
54
|
+
sign<const P extends S & keyof T & string>(purpose: P, claims: z.input<T[P]['claims']>, options: {
|
|
55
|
+
readonly days: number;
|
|
56
|
+
}): string;
|
|
57
|
+
}
|
|
58
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type AppMembers, type DimensionDef, type Presentation, type RoleDefs, type Vocabulary, type WildcardMessage } from './define';
|
|
2
|
+
/**
|
|
3
|
+
* The vocabulary as `defineVocabulary` returned it, with its literal names, dimension definitions and the actions of its
|
|
4
|
+
* people (`M`, `never` when it declares none).
|
|
5
|
+
*/
|
|
6
|
+
export interface AppVocabulary<A extends string, D extends string, R extends string, Dims, M extends AppMembers<A> = never> extends Vocabulary<A, D, R> {
|
|
7
|
+
readonly dimensions: Dims & {
|
|
8
|
+
readonly [K in D]: DimensionDef<A, D, R>;
|
|
9
|
+
};
|
|
10
|
+
readonly members?: M;
|
|
11
|
+
}
|
|
12
|
+
export type AnyAppVocabulary = AppVocabulary<string, string, string, unknown, AppMembers<string>>;
|
|
13
|
+
/**
|
|
14
|
+
* Declares the dimensions, actions and roles, and how IAM names the application (`locale`, `displayName`). `src/vocabulary.ts` exports it; `defineTables`, `defineApp` and the
|
|
15
|
+
* contract take it, so each name is written once.
|
|
16
|
+
*/
|
|
17
|
+
export declare function defineVocabulary<const A extends string, const D extends string, const R extends string, const Dims extends {
|
|
18
|
+
readonly [K in D]: DimensionDef<NoInfer<A>, NoInfer<D>, NoInfer<R>>;
|
|
19
|
+
}, const M extends AppMembers<NoInfer<A>> = never>(vocabulary: Presentation & {
|
|
20
|
+
readonly app: string;
|
|
21
|
+
readonly dimensions: Dims & {
|
|
22
|
+
readonly [K in D]: DimensionDef<NoInfer<A>, NoInfer<D>, NoInfer<R>>;
|
|
23
|
+
};
|
|
24
|
+
readonly actions: {
|
|
25
|
+
readonly [K in A]: K extends `${string}*${string}` ? WildcardMessage : string;
|
|
26
|
+
};
|
|
27
|
+
readonly roles: RoleDefs<NoInfer<A>, NoInfer<D>, R>;
|
|
28
|
+
readonly members?: M;
|
|
29
|
+
readonly directory?: 'labels';
|
|
30
|
+
}): AppVocabulary<A, D, R, Dims, M>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
import type { AppCapability, Ref } from '../implement/capability';
|
|
3
|
+
/** The output of a procedure that binds: the binding's id, the URL the provider posts to, and the secret, once. */
|
|
4
|
+
export declare const WebhookSecret: z.ZodObject<{
|
|
5
|
+
id: z.ZodUUID;
|
|
6
|
+
url: z.ZodString;
|
|
7
|
+
secret: z.ZodString;
|
|
8
|
+
}, z.core.$strict>;
|
|
9
|
+
export type WebhookSecret = z.output<typeof WebhookSecret>;
|
|
10
|
+
/**
|
|
11
|
+
* The bindings of one webhook. A binding reaches the value the capability names: a `Ref` of the webhook's dimension, or
|
|
12
|
+
* an `AppCapability` for a webhook bound to `'app'`. Both run in the procedure's transaction.
|
|
13
|
+
*/
|
|
14
|
+
export interface WebhookBindings {
|
|
15
|
+
/** A new binding that accepts deliveries for `account`; the framework generates its secret. */
|
|
16
|
+
bind(capability: Ref<string, string> | AppCapability<string>, account: string): Promise<WebhookSecret>;
|
|
17
|
+
/**
|
|
18
|
+
* Replaces the secret of the binding `id` of that value with one the provider issued (Stripe, Slack, Twilio): the
|
|
19
|
+
* provider shows it only after the binding's URL is registered. `false` when the value has no binding of that id.
|
|
20
|
+
*/
|
|
21
|
+
setSecret(capability: Ref<string, string> | AppCapability<string>, id: string, secret: string): Promise<boolean>;
|
|
22
|
+
/** Removes the binding `id` of that value; `false` when it has none of that id. */
|
|
23
|
+
unbind(capability: Ref<string, string> | AppCapability<string>, id: string): Promise<boolean>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type WebhookScheme } from '@fabrika/auth-core';
|
|
2
|
+
import { z } from 'zod/v4';
|
|
3
|
+
export declare const hmacSchemeSchema: z.ZodObject<{
|
|
4
|
+
header: z.ZodString;
|
|
5
|
+
algorithm: z.ZodEnum<{
|
|
6
|
+
sha1: "sha1";
|
|
7
|
+
sha256: "sha256";
|
|
8
|
+
sha512: "sha512";
|
|
9
|
+
}>;
|
|
10
|
+
encoding: z.ZodEnum<{
|
|
11
|
+
base64: "base64";
|
|
12
|
+
hex: "hex";
|
|
13
|
+
}>;
|
|
14
|
+
prefix: z.ZodOptional<z.ZodString>;
|
|
15
|
+
timestamp: z.ZodOptional<z.ZodString>;
|
|
16
|
+
signed: z.ZodEnum<{
|
|
17
|
+
body: "body";
|
|
18
|
+
"timestamp.body": "timestamp.body";
|
|
19
|
+
}>;
|
|
20
|
+
id: z.ZodOptional<z.ZodString>;
|
|
21
|
+
}, z.core.$strict>;
|
|
22
|
+
export declare const webhookSchemeSchema: z.ZodType<WebhookScheme>;
|
|
23
|
+
export declare const WEBHOOK_SCHEME_ALLOWED = "scheme: 'github-sha256' | 'slack' | 'standard-webhooks' | 'stripe' | 'twilio' | { hmac: { header, algorithm: 'sha1' | 'sha256' | 'sha512', encoding: 'hex' | 'base64', prefix?, timestamp?, signed: 'body' | 'timestamp.body', id? } } | { basic: true } | { bearer: true }";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** The application's migrations, as `fabrika db migrate` reads them. */
|
|
2
|
+
export declare const MIGRATIONS_DIRECTORY = "db/migrations/";
|
|
3
|
+
/** Why the change needs a bootstrap run, in one line, or null when the launcher may apply it. */
|
|
4
|
+
export declare function bootstrapReason(paths: readonly string[]): string | null;
|
|
5
|
+
export declare const migrationsChanged: (paths: readonly string[]) => boolean;
|
|
6
|
+
/** A change outside the server code and the database may reach the SPA, so its build runs again. */
|
|
7
|
+
export declare const webSourceChanged: (paths: readonly string[]) => boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type DevLauncherConfig } from '@fabrika/auth-core';
|
|
2
|
+
import { type OutputSink } from './output.js';
|
|
3
|
+
/** The launcher's configuration, which the build generated; a missing or malformed file stops the launch. */
|
|
4
|
+
export declare function readDevLauncherConfig(root: string): Promise<DevLauncherConfig>;
|
|
5
|
+
export interface DevSourceTransport {
|
|
6
|
+
readonly url: string;
|
|
7
|
+
readonly key: string;
|
|
8
|
+
}
|
|
9
|
+
export interface DevLauncherOptions {
|
|
10
|
+
readonly root: string;
|
|
11
|
+
readonly config: DevLauncherConfig;
|
|
12
|
+
/** The launcher's own environment: the application's, with the provisioning credential and the `source` key. */
|
|
13
|
+
readonly environment: Readonly<Record<string, string>>;
|
|
14
|
+
readonly source: DevSourceTransport;
|
|
15
|
+
readonly fetch?: (request: Request) => Promise<Response>;
|
|
16
|
+
readonly output?: OutputSink;
|
|
17
|
+
/** How long a boot tries to reach Control before it serves the files it holds. */
|
|
18
|
+
readonly bootReconcileMs?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface DevLauncher {
|
|
21
|
+
/** Settles once the first boot served, or failed and reported it. */
|
|
22
|
+
readonly ready: Promise<void>;
|
|
23
|
+
/** Stop the long poll and the application. */
|
|
24
|
+
stop(): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
/** Start the launcher: reconcile to the recorded head, serve, then follow Control's long poll. */
|
|
27
|
+
export declare function startDevLauncher(options: DevLauncherOptions): DevLauncher;
|
|
28
|
+
/**
|
|
29
|
+
* The generated dev entrypoint's only call. It reads the build's configuration and the `source` transport, makes
|
|
30
|
+
* itself non-dumpable (it holds the provisioning credential and the key), and runs until a signal stops it.
|
|
31
|
+
*/
|
|
32
|
+
export declare function runDevLauncher(env: Readonly<Record<string, string | undefined>>): Promise<void>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type DevLogLine } from './wire.js';
|
|
2
|
+
export type OutputStream = DevLogLine['stream'];
|
|
3
|
+
export interface OutputSink {
|
|
4
|
+
write(stream: OutputStream, line: string): void;
|
|
5
|
+
}
|
|
6
|
+
/** The launcher process's own stdout and stderr. */
|
|
7
|
+
export declare const processOutput: OutputSink;
|
|
8
|
+
/** The newest `capacity` lines, numbered from 0 since the launcher started, with every known secret value replaced. */
|
|
9
|
+
export declare class OutputRing {
|
|
10
|
+
private readonly capacity;
|
|
11
|
+
private readonly secrets;
|
|
12
|
+
private readonly sink;
|
|
13
|
+
private readonly lines;
|
|
14
|
+
private next;
|
|
15
|
+
constructor(capacity: number, secrets: readonly string[], sink: OutputSink);
|
|
16
|
+
push(stream: OutputStream, text: string): void;
|
|
17
|
+
/** The newest `count` lines, oldest first. */
|
|
18
|
+
tail(count: number): DevLogLine[];
|
|
19
|
+
private redact;
|
|
20
|
+
}
|
|
21
|
+
/** Feed `stream` to `onLine` one line at a time; a last line without a newline is fed when the stream ends. */
|
|
22
|
+
export declare function readLines(stream: ReadableStream<Uint8Array>, onLine: (line: string) => void): Promise<void>;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
/** The long poll under the receiver's URL (`FABRIKA_PLATFORM_SOURCE_URL`). */
|
|
3
|
+
export declare const DEV_SOURCE_WAIT_PATH = "/platform/source/wait";
|
|
4
|
+
/** Where the launcher reports what it serves. */
|
|
5
|
+
export declare const DEV_SOURCE_REPORT_PATH = "/platform/source/report";
|
|
6
|
+
/** How long Control holds a wait before it answers `idle`: below every proxy's idle timeout on the way. */
|
|
7
|
+
export declare const DEV_SOURCE_WAIT_MS = 25000;
|
|
8
|
+
/** The most log lines one report carries, and the longest line. */
|
|
9
|
+
export declare const DEV_REPORT_MAX_LINES = 200;
|
|
10
|
+
export declare const DEV_REPORT_MAX_LINE_CHARACTERS = 2000;
|
|
11
|
+
export declare const DEV_REPORT_MAX_MESSAGE_CHARACTERS = 500;
|
|
12
|
+
/**
|
|
13
|
+
* Repository-relative with forward slashes: no leading slash, no backslash, no empty, `.` or `..` segment, no control
|
|
14
|
+
* character. The launcher writes these paths under its root, so nothing it receives may name a path outside it.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isSourcePath(path: string): boolean;
|
|
17
|
+
export declare const devWaitRequestSchema: z.ZodObject<{
|
|
18
|
+
bootstrap: z.ZodString;
|
|
19
|
+
head: z.ZodString;
|
|
20
|
+
reset: z.ZodNullable<z.ZodNumber>;
|
|
21
|
+
checks: z.ZodNullable<z.ZodNumber>;
|
|
22
|
+
hold: z.ZodBoolean;
|
|
23
|
+
}, z.core.$strict>;
|
|
24
|
+
export type DevWaitRequest = z.infer<typeof devWaitRequestSchema>;
|
|
25
|
+
/** One changed file at the target commit; `content` null deletes it. */
|
|
26
|
+
export declare const devSourceFileSchema: z.ZodObject<{
|
|
27
|
+
path: z.ZodString;
|
|
28
|
+
content: z.ZodNullable<z.ZodString>;
|
|
29
|
+
}, z.core.$strict>;
|
|
30
|
+
export type DevSourceFile = z.infer<typeof devSourceFileSchema>;
|
|
31
|
+
/** The files that bring the container from `from` to `to`. */
|
|
32
|
+
export declare const devSyncSchema: z.ZodObject<{
|
|
33
|
+
from: z.ZodString;
|
|
34
|
+
to: z.ZodString;
|
|
35
|
+
files: z.ZodArray<z.ZodObject<{
|
|
36
|
+
path: z.ZodString;
|
|
37
|
+
content: z.ZodNullable<z.ZodString>;
|
|
38
|
+
}, z.core.$strict>>;
|
|
39
|
+
}, z.core.$strict>;
|
|
40
|
+
export type DevSync = z.infer<typeof devSyncSchema>;
|
|
41
|
+
/**
|
|
42
|
+
* What to do. `sync` is null when the container holds the recorded head (or a head it refused). `reset` and `checks` are
|
|
43
|
+
* the environment's current generations: one past what the container applied asks it to reset its schema, or to run
|
|
44
|
+
* the check chain.
|
|
45
|
+
*/
|
|
46
|
+
export declare const devWaitAnswerSchema: z.ZodObject<{
|
|
47
|
+
sync: z.ZodNullable<z.ZodObject<{
|
|
48
|
+
from: z.ZodString;
|
|
49
|
+
to: z.ZodString;
|
|
50
|
+
files: z.ZodArray<z.ZodObject<{
|
|
51
|
+
path: z.ZodString;
|
|
52
|
+
content: z.ZodNullable<z.ZodString>;
|
|
53
|
+
}, z.core.$strict>>;
|
|
54
|
+
}, z.core.$strict>>;
|
|
55
|
+
reset: z.ZodNumber;
|
|
56
|
+
checks: z.ZodNumber;
|
|
57
|
+
}, z.core.$strict>;
|
|
58
|
+
export type DevWaitAnswer = z.infer<typeof devWaitAnswerSchema>;
|
|
59
|
+
export declare const DEV_STATUSES: readonly ["syncing", "serving", "failed", "needs_bootstrap", "resetting", "checking"];
|
|
60
|
+
export type DevStatus = (typeof DEV_STATUSES)[number];
|
|
61
|
+
export declare const devLogLineSchema: z.ZodObject<{
|
|
62
|
+
n: z.ZodNumber;
|
|
63
|
+
stream: z.ZodEnum<{
|
|
64
|
+
stdout: "stdout";
|
|
65
|
+
stderr: "stderr";
|
|
66
|
+
}>;
|
|
67
|
+
text: z.ZodString;
|
|
68
|
+
}, z.core.$strict>;
|
|
69
|
+
export type DevLogLine = z.infer<typeof devLogLineSchema>;
|
|
70
|
+
export declare const devCheckResultSchema: z.ZodObject<{
|
|
71
|
+
name: z.ZodString;
|
|
72
|
+
ok: z.ZodBoolean;
|
|
73
|
+
exitCode: z.ZodNullable<z.ZodNumber>;
|
|
74
|
+
durationMs: z.ZodNumber;
|
|
75
|
+
output: z.ZodArray<z.ZodString>;
|
|
76
|
+
}, z.core.$strict>;
|
|
77
|
+
export type DevCheckResult = z.infer<typeof devCheckResultSchema>;
|
|
78
|
+
export declare const devReportSchema: z.ZodObject<{
|
|
79
|
+
bootstrap: z.ZodString;
|
|
80
|
+
head: z.ZodString;
|
|
81
|
+
status: z.ZodEnum<{
|
|
82
|
+
failed: "failed";
|
|
83
|
+
syncing: "syncing";
|
|
84
|
+
serving: "serving";
|
|
85
|
+
needs_bootstrap: "needs_bootstrap";
|
|
86
|
+
resetting: "resetting";
|
|
87
|
+
checking: "checking";
|
|
88
|
+
}>;
|
|
89
|
+
target: z.ZodNullable<z.ZodString>;
|
|
90
|
+
message: z.ZodNullable<z.ZodString>;
|
|
91
|
+
log: z.ZodArray<z.ZodObject<{
|
|
92
|
+
n: z.ZodNumber;
|
|
93
|
+
stream: z.ZodEnum<{
|
|
94
|
+
stdout: "stdout";
|
|
95
|
+
stderr: "stderr";
|
|
96
|
+
}>;
|
|
97
|
+
text: z.ZodString;
|
|
98
|
+
}, z.core.$strict>>;
|
|
99
|
+
reset: z.ZodNullable<z.ZodNumber>;
|
|
100
|
+
checks: z.ZodNullable<z.ZodObject<{
|
|
101
|
+
request: z.ZodNumber;
|
|
102
|
+
head: z.ZodString;
|
|
103
|
+
results: z.ZodArray<z.ZodObject<{
|
|
104
|
+
name: z.ZodString;
|
|
105
|
+
ok: z.ZodBoolean;
|
|
106
|
+
exitCode: z.ZodNullable<z.ZodNumber>;
|
|
107
|
+
durationMs: z.ZodNumber;
|
|
108
|
+
output: z.ZodArray<z.ZodString>;
|
|
109
|
+
}, z.core.$strict>>;
|
|
110
|
+
}, z.core.$strict>>;
|
|
111
|
+
}, z.core.$strict>;
|
|
112
|
+
export type DevReport = z.infer<typeof devReportSchema>;
|
|
113
|
+
/** The body of a wait as JSON text. */
|
|
114
|
+
export declare function encodeDevWaitRequest(request: DevWaitRequest): string;
|
|
115
|
+
/** The body of a report as JSON text. */
|
|
116
|
+
export declare function encodeDevReport(report: DevReport): string;
|
|
117
|
+
export type DevParse<T> = {
|
|
118
|
+
readonly ok: true;
|
|
119
|
+
readonly value: T;
|
|
120
|
+
} | {
|
|
121
|
+
readonly ok: false;
|
|
122
|
+
};
|
|
123
|
+
/** The one parser of a wait request; a field this release does not know is refused, never dropped. */
|
|
124
|
+
export declare const parseDevWaitRequest: (json: unknown) => DevParse<DevWaitRequest>;
|
|
125
|
+
export declare const parseDevWaitAnswer: (json: unknown) => DevParse<DevWaitAnswer>;
|
|
126
|
+
export declare const parseDevReport: (json: unknown) => DevParse<DevReport>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
interface ErrorOptions {
|
|
2
|
+
readonly issues?: unknown;
|
|
3
|
+
readonly loginUrl?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare class HttpError extends Error {
|
|
6
|
+
readonly httpStatus: number;
|
|
7
|
+
readonly type: string;
|
|
8
|
+
readonly issues?: unknown;
|
|
9
|
+
readonly loginUrl?: string;
|
|
10
|
+
constructor(httpStatus: number, type: string, message: string, options?: ErrorOptions);
|
|
11
|
+
}
|
|
12
|
+
export declare class BadRequestError extends HttpError {
|
|
13
|
+
constructor(message?: string, options?: ErrorOptions);
|
|
14
|
+
}
|
|
15
|
+
export declare class UnauthorizedError extends HttpError {
|
|
16
|
+
constructor(message?: string, options?: ErrorOptions);
|
|
17
|
+
}
|
|
18
|
+
export declare class ForbiddenError extends HttpError {
|
|
19
|
+
constructor(message?: string, options?: ErrorOptions);
|
|
20
|
+
}
|
|
21
|
+
export declare class NotFoundError extends HttpError {
|
|
22
|
+
constructor(message?: string, options?: ErrorOptions);
|
|
23
|
+
}
|
|
24
|
+
export declare class ConflictError extends HttpError {
|
|
25
|
+
constructor(message?: string, options?: ErrorOptions);
|
|
26
|
+
}
|
|
27
|
+
export interface StructuralError {
|
|
28
|
+
readonly httpStatus: number;
|
|
29
|
+
readonly type: string;
|
|
30
|
+
readonly message: string;
|
|
31
|
+
readonly issues?: unknown;
|
|
32
|
+
readonly loginUrl?: string;
|
|
33
|
+
}
|
|
34
|
+
/** True when a thrown value carries its own HTTP status and type, i.e. it is meant to reach the caller as-is. */
|
|
35
|
+
export declare function isStructuralError(err: unknown): err is StructuralError;
|
|
36
|
+
/**
|
|
37
|
+
* Read the structural HTTP-error fields off ANY thrown value. Defaults:
|
|
38
|
+
* `httpStatus → 500`, `type → 'internal'`, `message → the Error message or 'Internal error'`.
|
|
39
|
+
*/
|
|
40
|
+
export declare function readStructuralError(err: unknown): StructuralError;
|
|
41
|
+
export declare function jsonResponse(body: unknown, status: number): Response;
|
|
42
|
+
/** Map any thrown value to a JSON `Response` per the structural error contract. */
|
|
43
|
+
export declare function toErrorResponse(err: unknown): Response;
|
|
44
|
+
/** The default `onError` handler — a thin wrapper around {@link toErrorResponse}. */
|
|
45
|
+
export declare function defaultOnError(err: unknown, _request?: Request): Response;
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Runtime-neutral background-work capability available while handling a request. */
|
|
2
|
+
export interface RequestExecutionContext {
|
|
3
|
+
waitUntil(promise: Promise<unknown>): void;
|
|
4
|
+
}
|
|
5
|
+
export interface FetchService<Env> {
|
|
6
|
+
fetch(request: Request, env: Env, exec: RequestExecutionContext): Promise<Response>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare const ARCHIVE_LIMITS: {
|
|
2
|
+
/** Entries of one archive, nested ones counted with their parent's. */
|
|
3
|
+
readonly maxEntries: 1000;
|
|
4
|
+
/** Uncompressed bytes of one archive and everything nested in it. */
|
|
5
|
+
readonly maxUnpackedBytes: number;
|
|
6
|
+
/** The outer archive is depth 0; an archive inside it is 1; one inside that is refused. */
|
|
7
|
+
readonly maxDepth: 1;
|
|
8
|
+
};
|
|
9
|
+
/** Why an archive is refused. Each is a reject, never a retry: the bytes will not change. */
|
|
10
|
+
export type ArchiveRefusal = 'encrypted' | 'unsupported_method' | 'zip64' | 'malformed' | 'too_many_entries' | 'too_large_unpacked' | 'nested_too_deep' | 'unsupported_archive'
|
|
11
|
+
/** A ZIP signature inside a file that is no ZIP, or before a ZIP's first entry. */
|
|
12
|
+
| 'embedded_archive'
|
|
13
|
+
/** More than one end record a scanner could pick, or one whose comment does not end the file. */
|
|
14
|
+
| 'ambiguous_directory';
|
|
15
|
+
export type ArchiveCheck = {
|
|
16
|
+
readonly ok: true;
|
|
17
|
+
} | {
|
|
18
|
+
readonly ok: false;
|
|
19
|
+
readonly refusal: ArchiveRefusal;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Whether the scanner can vouch for these bytes. A file of a ZIP-based type (`zipBased`: ZIP and Office Open XML) must
|
|
23
|
+
* be a ZIP from its first byte whose every entry, nested ZIPs included, ClamAV reads in full. Any other file passes
|
|
24
|
+
* only when it holds no archive: no other archive format, and no ZIP signature at any offset.
|
|
25
|
+
*/
|
|
26
|
+
export declare function checkArchive(bytes: Uint8Array, zipBased: boolean): ArchiveCheck;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type FileScanner, type ScanBatchResult } from './scan';
|
|
2
|
+
export declare const CLAMSCAN_LIMITS: {
|
|
3
|
+
/** The scanner refuses signatures older than this; the provider refreshes them at start and daily. */
|
|
4
|
+
readonly maxSignatureAgeDays: 3;
|
|
5
|
+
/** One file's scan time, after which ClamAV alerts instead of passing it. */
|
|
6
|
+
readonly maxScanTimeMs: 120000;
|
|
7
|
+
readonly maxRecursion: 10;
|
|
8
|
+
readonly maxFilesPerContainer: 1000;
|
|
9
|
+
/** What clamscan may print; past it the run is stopped and the batch gets no verdict. */
|
|
10
|
+
readonly maxOutputBytes: number;
|
|
11
|
+
};
|
|
12
|
+
export interface ClamscanOptions {
|
|
13
|
+
/** The `clamscan` binary; by default the one on `PATH`. */
|
|
14
|
+
readonly binary?: string;
|
|
15
|
+
/** The signature directory; by default ClamAV's configured one. */
|
|
16
|
+
readonly database?: string;
|
|
17
|
+
/** Where the batch's directory is made; by default the system's temporary directory. */
|
|
18
|
+
readonly directory?: string;
|
|
19
|
+
readonly timeoutMs?: number;
|
|
20
|
+
/** The largest file ClamAV reads in full; a larger one alerts. By default `FILE_LIMITS.maxScannedBytes`. */
|
|
21
|
+
readonly maxFileBytes?: number;
|
|
22
|
+
}
|
|
23
|
+
/** The arguments of one run; every limit alerts, so a file ClamAV could not read in full is never reported OK. */
|
|
24
|
+
export declare function clamscanArguments(database: string | undefined, size?: number): readonly string[];
|
|
25
|
+
/**
|
|
26
|
+
* The verdicts of one finished run. Exit 0 (nothing found) and 1 (something found) carry per-file lines; any other exit
|
|
27
|
+
* is a failed batch, classified from stderr. A file without exactly one `<path>: OK` or `<path>: <name> FOUND` line,
|
|
28
|
+
* a conflicting second line included, gets no clean verdict.
|
|
29
|
+
*/
|
|
30
|
+
export declare function clamscanVerdicts(paths: ReadonlyMap<string, string>, exitCode: number, stdout: string, stderr: string): ScanBatchResult;
|
|
31
|
+
/** ClamAV's `clamscan` as the scan pass's scanner. Fails closed: see `ScanFailure` for what makes a batch fail. */
|
|
32
|
+
export declare function clamscanScanner(options?: ClamscanOptions): FileScanner;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { Unavailable } from '@fabrika/db/tables';
|
|
2
|
+
import type { FileRef } from '../contract/file';
|
|
3
|
+
import type { Ref } from '../implement/capability';
|
|
4
|
+
/** A held file's state: served when `live`, `409` while `quarantined`, gone when `rejected`. */
|
|
5
|
+
export type HeldFileStatus = 'live' | 'quarantined' | 'rejected';
|
|
6
|
+
/** One file of an owner, as `list` answers it. */
|
|
7
|
+
export interface ListedFile {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
/** The name the uploader gave it, metadata only. */
|
|
10
|
+
readonly name: string | null;
|
|
11
|
+
/** The sniffed media type its bytes were checked against. */
|
|
12
|
+
readonly type: string;
|
|
13
|
+
readonly size: number;
|
|
14
|
+
readonly status: HeldFileStatus;
|
|
15
|
+
readonly createdAt: Date;
|
|
16
|
+
}
|
|
17
|
+
/** A page of an owner's files, oldest first; `next` is the `after` of the following page, `null` after the last. */
|
|
18
|
+
export interface FileListing {
|
|
19
|
+
readonly items: readonly ListedFile[];
|
|
20
|
+
readonly next: string | null;
|
|
21
|
+
}
|
|
22
|
+
export interface FileListPage {
|
|
23
|
+
/** The id of the last file of the previous page. */
|
|
24
|
+
readonly after?: string;
|
|
25
|
+
/** 1–100, 50 by default. */
|
|
26
|
+
readonly limit?: number;
|
|
27
|
+
}
|
|
28
|
+
/** A live file read whole: what an e-mail attaches. */
|
|
29
|
+
export interface OpenedFile {
|
|
30
|
+
readonly id: string;
|
|
31
|
+
readonly name: string | null;
|
|
32
|
+
readonly type: string;
|
|
33
|
+
readonly size: number;
|
|
34
|
+
readonly bytes: Uint8Array;
|
|
35
|
+
}
|
|
36
|
+
export interface OpenOptions {
|
|
37
|
+
/** The most bytes the caller holds in memory; a larger file is refused (413 `too_large`) before it is read. */
|
|
38
|
+
readonly maxBytes: number;
|
|
39
|
+
}
|
|
40
|
+
/** What every site reaches of one collection: its owner's files. */
|
|
41
|
+
export interface FileCollectionReader {
|
|
42
|
+
/**
|
|
43
|
+
* The owner's held files, oldest first; `owner` is a `Ref` of the collection's owner for its `read` action. A
|
|
44
|
+
* handler that must see the uploads holds that action.
|
|
45
|
+
*/
|
|
46
|
+
list(owner: Ref<string, string>, page?: FileListPage): Promise<FileListing>;
|
|
47
|
+
/**
|
|
48
|
+
* The live file `id` of the owner, read whole; `owner` is a `Ref` for the collection's `read` action. A file of
|
|
49
|
+
* another owner, a rejected or a removed one is 404, a quarantined one 409 `quarantined`, one over `maxBytes` 413.
|
|
50
|
+
*/
|
|
51
|
+
open(owner: Ref<string, string>, id: string, options: OpenOptions): Promise<OpenedFile>;
|
|
52
|
+
}
|
|
53
|
+
/** A site that writes also removes: a mutation, and a job inside `ctx.transaction`. */
|
|
54
|
+
export interface FileCollectionWriter extends FileCollectionReader {
|
|
55
|
+
/**
|
|
56
|
+
* Marks every file of the owner removed, in this site's transaction, and answers how many; `owner` is a `Ref` for the
|
|
57
|
+
* collection's `delete` action. The framework's sweep deletes their objects after the commit.
|
|
58
|
+
*/
|
|
59
|
+
removeAll(owner: Ref<string, string>): Promise<number>;
|
|
60
|
+
}
|
|
61
|
+
/** A mutation's handle: it writes, and attaches nothing, since only the job that drains a form's inbox does. */
|
|
62
|
+
export type FileCollectionHandle = FileCollectionWriter & {
|
|
63
|
+
readonly attach: Unavailable<"only a job attaches a form's file, inside ctx.transaction((tx) => …), as tx.files">;
|
|
64
|
+
};
|
|
65
|
+
/** A job's transaction also attaches the files a form received: the job that drains the form's inbox. */
|
|
66
|
+
export interface FileCollectionJobHandle extends FileCollectionWriter {
|
|
67
|
+
/**
|
|
68
|
+
* Attaches the file a form received under `id` (the id its inbox column holds) to `owner`, a `Ref` for the
|
|
69
|
+
* collection's `write` action, in this site's transaction: the one that wrote the owner, located with `tx.ref`. A
|
|
70
|
+
* scanning collection holds it `quarantined` until the scan job's verdict. `false` when no file of this collection
|
|
71
|
+
* waits under the id (none was sent, or its hold of 7 days ended); `true` again for one this owner attached before.
|
|
72
|
+
*/
|
|
73
|
+
attach(owner: Ref<string, string>, id: string): Promise<boolean>;
|
|
74
|
+
}
|
|
75
|
+
/** A site that only reads: a query, and a job outside `ctx.transaction`. */
|
|
76
|
+
export type FileCollectionReadHandle<Why extends string> = FileCollectionReader & {
|
|
77
|
+
readonly removeAll: Unavailable<Why>;
|
|
78
|
+
readonly attach: Unavailable<Why>;
|
|
79
|
+
};
|
|
80
|
+
/** `ctx.files(<collection>)`: the handle of one collection of the contract, by its name in `files`. */
|
|
81
|
+
export type FileCollections<H> = (collection: string) => H;
|
|
82
|
+
/** What `ctx.files.put` writes: bytes in memory, a `Blob`, or a stream a large file comes from. */
|
|
83
|
+
export type StoredFileBody = Uint8Array | ReadableStream<Uint8Array> | Blob;
|
|
84
|
+
/**
|
|
85
|
+
* `ctx.files.put` and `ctx.files.open`: the stored files of a site (`c.files` kinds). A put stages a file this request
|
|
86
|
+
* holds; writing its reference into a file column is what keeps it. `open` reads a live file whose reference this
|
|
87
|
+
* request holds and may read: an input file, a put, or a file column read under its table's read action.
|
|
88
|
+
*/
|
|
89
|
+
export interface StoredFilesHandle {
|
|
90
|
+
/** A file of `kind` with these bytes, which must be of `type`; it is pending until a column references it. */
|
|
91
|
+
put(kind: string, body: StoredFileBody, options: {
|
|
92
|
+
readonly type: string;
|
|
93
|
+
readonly name?: string;
|
|
94
|
+
}): Promise<FileRef>;
|
|
95
|
+
/** The bytes of a live file, read whole, at most `maxBytes` (the kind's by default); 409 until it is live. */
|
|
96
|
+
open(file: FileRef, options?: {
|
|
97
|
+
readonly maxBytes?: number;
|
|
98
|
+
}): Promise<OpenedFile>;
|
|
99
|
+
}
|
|
100
|
+
/** A site that only reads (a query): it opens a file and creates none. */
|
|
101
|
+
export interface StoredFilesReadHandle {
|
|
102
|
+
readonly put: Unavailable<'a query creates no file; put it in a mutation, a job or a webhook'>;
|
|
103
|
+
open(file: FileRef, options?: {
|
|
104
|
+
readonly maxBytes?: number;
|
|
105
|
+
}): Promise<OpenedFile>;
|
|
106
|
+
}
|