@pylonsync/functions 0.3.291 → 0.3.293

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Function definition constructors.
3
+ *
4
+ * These are the primary API for defining server-side functions.
5
+ *
6
+ * Each constructor (query / mutation / action) has two overloads:
7
+ *
8
+ * 1. `auth` omitted, or `auth: "user" | "admin"` → the framework
9
+ * enforces a real signed-in user, so the handler's
10
+ * `ctx.auth.userId` is narrowed from `string | null` to
11
+ * `string`. The redundant `if (!ctx.auth.userId) throw …`
12
+ * check disappears from app code.
13
+ *
14
+ * 2. `auth: "public" | "guest"` → the function is reachable by
15
+ * anonymous callers, so `ctx.auth.userId` stays `string | null`
16
+ * and the handler must check it manually if relevant.
17
+ *
18
+ * The framework gate happens BEFORE the handler runs (in Rust,
19
+ * inside the router). A handler can't accidentally leak data by
20
+ * forgetting an auth check — when `auth: "user"` is in effect,
21
+ * an anonymous request never reaches the handler at all.
22
+ */
23
+ import type { FnDefinition, QueryCtx, MutationCtx, ActionCtx, Validator } from "./types";
24
+ interface CommonDef {
25
+ args?: Record<string, Validator>;
26
+ /**
27
+ * When true, the function is callable only via `ctx.runQuery()` /
28
+ * `ctx.runMutation()` / `ctx.runAction()` from another function
29
+ * — never via the public `/api/fn/<name>` HTTP endpoint. The
30
+ * router refuses external calls with `404 FN_NOT_FOUND` so
31
+ * probing can't even confirm the name exists.
32
+ *
33
+ * Use for helper functions that are safe inside trusted
34
+ * wrappers but unsafe if any caller could invoke them directly
35
+ * (e.g. they trust args without re-checking caller authority).
36
+ *
37
+ * Internal functions inherit the wrapping handler's auth — the
38
+ * `auth` field has no effect when `internal: true`. The router
39
+ * isn't reachable anyway, and the caller has already passed its
40
+ * own gate.
41
+ */
42
+ internal?: boolean;
43
+ /**
44
+ * Max wall-clock SECONDS this function may run before the runtime
45
+ * recycles its worker as wedged. Defaults to `PYLON_FN_CALL_TIMEOUT`
46
+ * (30s). Raise it for legitimately long-running work — heavy renders,
47
+ * big batch jobs, slow external calls — so the call isn't force-killed
48
+ * mid-flight. This also lifts the runtime's wedge backstop for the
49
+ * worker while such a call is in flight, so a busy-but-progressing
50
+ * worker (e.g. one doing synchronous canvas/image work that blocks the
51
+ * event loop) isn't respawned out from under the work.
52
+ *
53
+ * Keep it as small as the work honestly needs: a genuinely stuck call
54
+ * still ties up its worker until this deadline. Prefer offloading very
55
+ * heavy CPU work to a dedicated service over setting a huge timeout.
56
+ */
57
+ timeout?: number;
58
+ }
59
+ interface QueryDefRequired<TArgs, TReturn> extends CommonDef {
60
+ /** Defaults to `"user"`. See [`AuthMode`] for the full surface. */
61
+ auth?: "user" | "admin";
62
+ handler: (ctx: QueryCtx<"required">, args: TArgs) => Promise<TReturn>;
63
+ }
64
+ interface QueryDefOptional<TArgs, TReturn> extends CommonDef {
65
+ auth: "public" | "guest";
66
+ handler: (ctx: QueryCtx<"optional">, args: TArgs) => Promise<TReturn>;
67
+ }
68
+ interface MutationDefRequired<TArgs, TReturn> extends CommonDef {
69
+ auth?: "user" | "admin";
70
+ handler: (ctx: MutationCtx<"required">, args: TArgs) => Promise<TReturn>;
71
+ }
72
+ interface MutationDefOptional<TArgs, TReturn> extends CommonDef {
73
+ auth: "public" | "guest";
74
+ handler: (ctx: MutationCtx<"optional">, args: TArgs) => Promise<TReturn>;
75
+ }
76
+ interface ActionDefRequired<TArgs, TReturn> extends CommonDef {
77
+ auth?: "user" | "admin";
78
+ handler: (ctx: ActionCtx<"required">, args: TArgs) => Promise<TReturn>;
79
+ }
80
+ interface ActionDefOptional<TArgs, TReturn> extends CommonDef {
81
+ auth: "public" | "guest";
82
+ handler: (ctx: ActionCtx<"optional">, args: TArgs) => Promise<TReturn>;
83
+ }
84
+ /**
85
+ * Define a read-only query function.
86
+ *
87
+ * Queries use the read pool — they never block writes and can run
88
+ * concurrently. They cannot modify data.
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * export default query({
93
+ * // auth: "user" is the default — ctx.auth.userId is `string`,
94
+ * // not `string | null`, inside the handler.
95
+ * args: { auctionId: v.string() },
96
+ * async handler(ctx, args) {
97
+ * return ctx.db.query("Lot", {
98
+ * auctionId: args.auctionId,
99
+ * authorId: ctx.auth.userId,
100
+ * });
101
+ * },
102
+ * });
103
+ * ```
104
+ *
105
+ * @example
106
+ * ```typescript
107
+ * // Explicitly public — landing-page count, never auth-shaped.
108
+ * export default query({
109
+ * auth: "public",
110
+ * async handler(ctx) {
111
+ * return ctx.db.query("PublicPost", { published: true });
112
+ * },
113
+ * });
114
+ * ```
115
+ */
116
+ export declare function query<TArgs = Record<string, unknown>, TReturn = unknown>(def: QueryDefRequired<TArgs, TReturn>): FnDefinition<TArgs, TReturn>;
117
+ export declare function query<TArgs = Record<string, unknown>, TReturn = unknown>(def: QueryDefOptional<TArgs, TReturn>): FnDefinition<TArgs, TReturn>;
118
+ /**
119
+ * Define a transactional mutation function.
120
+ *
121
+ * The entire handler IS the transaction. If it returns, all writes commit
122
+ * atomically. If it throws, all writes roll back — including scheduled
123
+ * functions.
124
+ *
125
+ * Mutations can stream data to the client via `ctx.stream.write()`.
126
+ * Stream chunks are sent immediately; DB writes commit at the end.
127
+ *
128
+ * @example
129
+ * ```typescript
130
+ * export default mutation({
131
+ * args: { lotId: v.string(), amount: v.number() },
132
+ * async handler(ctx, args) {
133
+ * // ctx.auth.userId is `string` (not nullable) — the runtime
134
+ * // already enforced `auth: "user"` (default) before reaching here.
135
+ * const lot = await ctx.db.get("Lot", args.lotId);
136
+ * if (!lot) throw ctx.error("NOT_FOUND", "Lot not found");
137
+ * await ctx.db.insert("Bid", {
138
+ * lotId: args.lotId,
139
+ * amount: args.amount,
140
+ * bidderId: ctx.auth.userId,
141
+ * });
142
+ * return { accepted: true };
143
+ * },
144
+ * });
145
+ * ```
146
+ */
147
+ export declare function mutation<TArgs = Record<string, unknown>, TReturn = unknown>(def: MutationDefRequired<TArgs, TReturn>): FnDefinition<TArgs, TReturn>;
148
+ export declare function mutation<TArgs = Record<string, unknown>, TReturn = unknown>(def: MutationDefOptional<TArgs, TReturn>): FnDefinition<TArgs, TReturn>;
149
+ /**
150
+ * Define an action function (external I/O allowed).
151
+ *
152
+ * Actions can call external APIs (fetch, email, Stripe, etc.) but cannot
153
+ * access the database directly. Use `ctx.runQuery()` and `ctx.runMutation()`
154
+ * for DB access — each runs in its own transaction.
155
+ *
156
+ * Actions are NOT automatically retried because they may have side effects.
157
+ *
158
+ * **Important:** policies don't gate actions — `auth: "user"` (the
159
+ * default) is the only thing protecting an action from anonymous calls.
160
+ * An action that charges Stripe, hits a private API, or reads a
161
+ * secret will respond happily to anonymous POSTs unless this gate
162
+ * fires. Pick `auth: "public"` explicitly only when the endpoint is
163
+ * meant to be open (a webhook receiver, a public form submit, …).
164
+ *
165
+ * @example
166
+ * ```typescript
167
+ * export default action({
168
+ * args: { lotId: v.string() },
169
+ * async handler(ctx, args) {
170
+ * const lot = await ctx.runQuery("lotDetails", { lotId: args.lotId });
171
+ * await fetch("https://api.sendgrid.com/...", { ... });
172
+ * await ctx.runMutation("markNotified", { lotId: args.lotId });
173
+ * },
174
+ * });
175
+ * ```
176
+ *
177
+ * @example
178
+ * ```typescript
179
+ * // GitHub webhook receiver — public because GitHub doesn't sign
180
+ * // in, the handler verifies the signature itself, then optionally
181
+ * // elevates.
182
+ * export default action({
183
+ * auth: "public",
184
+ * async handler(ctx) {
185
+ * const ok = verifyGithubSignature(secret, ctx.request!.rawBody, sig);
186
+ * if (!ok) throw ctx.error("INVALID_SIGNATURE", "bad sig");
187
+ * await ctx.auth.elevate({ admin: true, reason: "github webhook hmac" });
188
+ * // …
189
+ * },
190
+ * });
191
+ * ```
192
+ */
193
+ export declare function action<TArgs = Record<string, unknown>, TReturn = unknown>(def: ActionDefRequired<TArgs, TReturn>): FnDefinition<TArgs, TReturn>;
194
+ export declare function action<TArgs = Record<string, unknown>, TReturn = unknown>(def: ActionDefOptional<TArgs, TReturn>): FnDefinition<TArgs, TReturn>;
195
+ export {};
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @pylonsync/functions — TypeScript function definitions for pylon.
3
+ *
4
+ * This is the developer-facing API. App developers import from here
5
+ * to define queries, mutations, and actions.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { mutation, v } from "@pylonsync/functions";
10
+ *
11
+ * export default mutation({
12
+ * args: { lotId: v.string(), amount: v.number() },
13
+ * async handler(ctx, args) {
14
+ * const lot = await ctx.db.get("Lot", args.lotId);
15
+ * // ...
16
+ * },
17
+ * });
18
+ * ```
19
+ */
20
+ export { query, mutation, action } from "./define";
21
+ export { v } from "./validators";
22
+ export { resetDb, installTestIsolation } from "./testing";
23
+ export { slugifyName, availableSlug } from "./slugify";
24
+ export type { SsrResponse, SsrCookieOptions, SsrMetadata, Sitemap, SitemapEntry, Robots, RobotsRule, } from "./ssr-runtime";
25
+ export type { QueryCtx, MutationCtx, ActionCtx, DbReader, DbWriter, Stream, Scheduler, AuthInfo, AuthMode, AuthRequirement, FnDefinition, RequireMember, RequireMemberOptions, MemberRow, } from "./types";
@@ -0,0 +1,8 @@
1
+ import type { RequireMember } from "./types";
2
+ /**
3
+ * Build `ctx.requireMember(orgId, opts)` bound to a membership `read`. Errors
4
+ * carry `.code` (UNAUTHENTICATED / MISSING_ORG / FORBIDDEN) the same way
5
+ * `ctx.error` does — self-contained so it also works on the query ctx, which
6
+ * has no `ctx.error`.
7
+ */
8
+ export declare function makeRequireMember(userId: string | null | undefined, read: (entity: string, filter: Record<string, unknown>) => Promise<any[]>): RequireMember;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Function runtime — the Bun process that loads and executes TypeScript functions.
3
+ *
4
+ * Protocol: NDJSON over stdin/stdout.
5
+ *
6
+ * Usage:
7
+ * bun run packages/functions/src/runtime.ts ./functions
8
+ *
9
+ * Design:
10
+ * - A single reader consumes lines from stdin and dispatches by message type.
11
+ * - Incoming `call` messages launch a handler.
12
+ * - Incoming `result` messages resolve a pending RPC keyed by call_id.
13
+ * - Each call's handler has at most ONE outstanding RPC at a time (it awaits
14
+ * each ctx.db / ctx.scheduler / ctx.runMutation call), so the map never
15
+ * needs to queue multiple RPCs per call_id.
16
+ */
17
+ import type { DbReader, DbWriter } from "./types";
18
+ export declare function buildDbReader(callId: string): DbReader;
19
+ export declare function buildDbWriter(callId: string): DbWriter;
@@ -0,0 +1,49 @@
1
+ import type { DbReader, DbWriter } from "./types";
2
+ /**
3
+ * Pure: turn a human name into a URL-safe base slug. Doesn't check
4
+ * uniqueness — combine with `availableSlug` for that.
5
+ *
6
+ * Rules:
7
+ * - lowercase
8
+ * - non-alphanumeric runs become single dashes
9
+ * - trim leading/trailing dashes
10
+ * - max 40 chars (so a numeric suffix can land within typical
11
+ * 50-char schema constraints)
12
+ * - empty result (e.g. emoji-only name) → `fallback`
13
+ *
14
+ * @param name The user-typed display name to slugify.
15
+ * @param fallback Replacement when the slug strips to empty. Default: "x".
16
+ */
17
+ export declare function slugifyName(name: string, fallback?: string): string;
18
+ /**
19
+ * Find the first slug that's available in the named entity's `field`
20
+ * column. Tries `base`, then `base-2`, `base-3`, …, then a 6-char
21
+ * random suffix as a final fallback.
22
+ *
23
+ * Reserved slugs (operational subdomain names, brand-protected words)
24
+ * can be passed via `reserved`; matches are skipped.
25
+ *
26
+ * Caller still wraps the eventual insert in try/catch — TOCTOU is
27
+ * always possible with this kind of "check then write" probe, and
28
+ * the framework's UNIQUE index is the actual source of truth. The
29
+ * helper just minimises the number of avoidable retries.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * const slug = await availableSlug(ctx.db, {
34
+ * entity: "Workspace",
35
+ * field: "slug",
36
+ * name: args.name,
37
+ * reserved: RESERVED_SUBDOMAINS,
38
+ * });
39
+ * await ctx.db.insert("Workspace", { slug, ... });
40
+ * ```
41
+ */
42
+ export declare function availableSlug(db: DbReader | DbWriter, options: {
43
+ entity: string;
44
+ field?: string;
45
+ name: string;
46
+ reserved?: ReadonlySet<string> | readonly string[];
47
+ /** Max numeric suffix to try before falling back to random. Default: 50. */
48
+ maxNumericTries?: number;
49
+ }): Promise<string>;
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Resolve the nearest boundary module (`<dir>/not-found` or `<dir>/error`)
3
+ * for a route by walking its component path up to the app root, returning the
4
+ * first manifest route key that exists. Nearest ancestor wins — the same model
5
+ * the server's `findBoundary` uses, but driven off the client build manifest's
6
+ * route keys so the client runtime needs no extra server round-trip.
7
+ *
8
+ * `component` is a cwd-relative path with "/" separators and no extension
9
+ * (e.g. "web/app/dashboard/orgs/[slug]/page"); `routeKeys` is
10
+ * `Object.keys(manifest.routes)`.
11
+ */
12
+ export declare function nearestBoundaryComponent(component: string, fileName: "not-found" | "error", routeKeys: Iterable<string>): string | null;
13
+ /** Runtime internals the boundary needs, injected so the module stays
14
+ * browser-safe AND unit-testable. */
15
+ export interface BoundaryDeps {
16
+ /** Resolve the client build manifest (`{ routes: {...} }`). */
17
+ loadManifest: () => Promise<{
18
+ routes?: Record<string, unknown>;
19
+ } | null>;
20
+ /** Dynamically load a route entry → its Page + Layout chain. */
21
+ loadRouteEntry: (component: string) => Promise<{
22
+ Page: any;
23
+ Layouts: any[];
24
+ }>;
25
+ /** Client-side navigation — used by an error boundary's reset(). */
26
+ navigate: (href: string, opts?: {
27
+ replace?: boolean;
28
+ }) => void;
29
+ /** Wrap a Page in its Layout chain with props (the runtime's buildTree). */
30
+ buildTree: (Page: any, Layouts: any[], props: any) => any;
31
+ /** The props the active page was rendered with (auth, serverData, …). */
32
+ getPageProps: () => any;
33
+ /** Current same-origin href, for an error boundary's reset() re-navigation. */
34
+ getResetHref: () => string;
35
+ }
36
+ /**
37
+ * Build the client error / not-found boundary against the runtime's internals.
38
+ * Returns `withBoundary(tree, component, navEpoch)` — the transparent root
39
+ * wrapper the runtime puts around every page — plus `PylonBoundary` for tests.
40
+ *
41
+ * Behavior: a `notFound()` (digest "PYLON_NOT_FOUND") or any error thrown
42
+ * during a descendant's render is caught; the nearest not-found.tsx / error.tsx
43
+ * for the active route is resolved from the manifest, loaded, and rendered in
44
+ * its own layout chain WITH the page's props (so an auth-guarding layout sees
45
+ * the real auth and doesn't redirect away). Resets on navigation via a changing
46
+ * `navEpoch` prop — not a key — so layouts keep their state across nav.
47
+ */
48
+ export declare function createPylonBoundary(deps: BoundaryDeps): {
49
+ withBoundary: (tree: any, component: string, navEpoch: number) => import("react").CElement<any, {
50
+ componentDidUpdate(prev: any): void;
51
+ componentDidCatch(err: any): void;
52
+ render(): any;
53
+ context: unknown;
54
+ setState<K extends "err">(state: {
55
+ err: any;
56
+ } | ((prevState: Readonly<{
57
+ err: any;
58
+ }>, props: Readonly<any>) => {
59
+ err: any;
60
+ } | Pick<{
61
+ err: any;
62
+ }, K> | null) | Pick<{
63
+ err: any;
64
+ }, K> | null, callback?: (() => void) | undefined): void;
65
+ forceUpdate(callback?: (() => void) | undefined): void;
66
+ readonly props: Readonly<any>;
67
+ state: Readonly<{
68
+ err: any;
69
+ }>;
70
+ componentDidMount?(): void;
71
+ shouldComponentUpdate?(nextProps: Readonly<any>, nextState: Readonly<{
72
+ err: any;
73
+ }>, nextContext: any): boolean;
74
+ componentWillUnmount?(): void;
75
+ getSnapshotBeforeUpdate?(prevProps: Readonly<any>, prevState: Readonly<{
76
+ err: any;
77
+ }>): any;
78
+ componentWillMount?(): void;
79
+ UNSAFE_componentWillMount?(): void;
80
+ componentWillReceiveProps?(nextProps: Readonly<any>, nextContext: any): void;
81
+ UNSAFE_componentWillReceiveProps?(nextProps: Readonly<any>, nextContext: any): void;
82
+ componentWillUpdate?(nextProps: Readonly<any>, nextState: Readonly<{
83
+ err: any;
84
+ }>, nextContext: any): void;
85
+ UNSAFE_componentWillUpdate?(nextProps: Readonly<any>, nextState: Readonly<{
86
+ err: any;
87
+ }>, nextContext: any): void;
88
+ }>;
89
+ PylonBoundary: {
90
+ new (p: any): {
91
+ componentDidUpdate(prev: any): void;
92
+ componentDidCatch(err: any): void;
93
+ render(): any;
94
+ context: unknown;
95
+ setState<K extends "err">(state: {
96
+ err: any;
97
+ } | ((prevState: Readonly<{
98
+ err: any;
99
+ }>, props: Readonly<any>) => {
100
+ err: any;
101
+ } | Pick<{
102
+ err: any;
103
+ }, K> | null) | Pick<{
104
+ err: any;
105
+ }, K> | null, callback?: (() => void) | undefined): void;
106
+ forceUpdate(callback?: (() => void) | undefined): void;
107
+ readonly props: Readonly<any>;
108
+ state: Readonly<{
109
+ err: any;
110
+ }>;
111
+ componentDidMount?(): void;
112
+ shouldComponentUpdate?(nextProps: Readonly<any>, nextState: Readonly<{
113
+ err: any;
114
+ }>, nextContext: any): boolean;
115
+ componentWillUnmount?(): void;
116
+ getSnapshotBeforeUpdate?(prevProps: Readonly<any>, prevState: Readonly<{
117
+ err: any;
118
+ }>): any;
119
+ componentWillMount?(): void;
120
+ UNSAFE_componentWillMount?(): void;
121
+ componentWillReceiveProps?(nextProps: Readonly<any>, nextContext: any): void;
122
+ UNSAFE_componentWillReceiveProps?(nextProps: Readonly<any>, nextContext: any): void;
123
+ componentWillUpdate?(nextProps: Readonly<any>, nextState: Readonly<{
124
+ err: any;
125
+ }>, nextContext: any): void;
126
+ UNSAFE_componentWillUpdate?(nextProps: Readonly<any>, nextState: Readonly<{
127
+ err: any;
128
+ }>, nextContext: any): void;
129
+ };
130
+ getDerivedStateFromError(err: any): {
131
+ err: any;
132
+ };
133
+ contextType?: import("react").Context<any> | undefined;
134
+ propTypes?: any;
135
+ };
136
+ };
@@ -0,0 +1,79 @@
1
+ import { type ManifestFonts } from "./ssr-fonts";
2
+ type Send = (msg: Record<string, unknown>) => void;
3
+ interface BundleClientMessage {
4
+ type: "bundle_client";
5
+ call_id: string;
6
+ /**
7
+ * Project-relative directory holding the route tree
8
+ * (`<app_dir>/**​/page.tsx`). Defaults to `"app"` when the host
9
+ * doesn't send it (older hosts, or the default single-`app/`
10
+ * layout). The full-stack app that namespaces its frontend under a
11
+ * subdir — e.g. `web/app` via `discoverAppRoutes({appDir:"web/app"})`
12
+ * — sends the same dir here so the client bundler and the SSR
13
+ * manifest agree on where the routes live.
14
+ */
15
+ app_dir?: string;
16
+ }
17
+ /**
18
+ * Manifest schema. One entry per route, indexed by the same
19
+ * project-relative component path the SSR side passes through.
20
+ * `file` is the entry chunk, `imports` is the transitive set of
21
+ * shared chunks the browser needs to load BEFORE the entry runs
22
+ * — that's the modulepreload list.
23
+ *
24
+ * Paths in the manifest are relative to `.pylon/client-build/` so
25
+ * the Rust host can serve them under `/_pylon/build/<path>` with
26
+ * no rewriting.
27
+ */
28
+ export interface PylonBundleManifest {
29
+ /** Build identity — bumps every successful build. */
30
+ build_id: string;
31
+ /** Output root, relative to cwd (always `.pylon/client-build`). */
32
+ outdir: string;
33
+ /** Public URL prefix the Rust host serves chunks under. */
34
+ public_prefix: string;
35
+ /** routeComponentPath → file + imports for that route. */
36
+ routes: Record<string, {
37
+ /** Per-route entry file, relative to outdir. */
38
+ file: string;
39
+ /** Transitive shared chunks to modulepreload, relative to outdir. */
40
+ imports: string[];
41
+ /** CSS chunks (Phase 1.5f). */
42
+ css: string[];
43
+ }>;
44
+ /** Self-hosted fonts (next/font parity): structured `@font-face`s + the
45
+ * `:root` CSS variables + the woff2 files to preload. Global (route-
46
+ * independent); rendered into every SSR `<head>` against `public_prefix`.
47
+ * Absent when the app declares no `font({...})`. */
48
+ fonts?: ManifestFonts;
49
+ }
50
+ /** Result of an in-process build — same shape the protocol returns. */
51
+ export interface BuildOutput {
52
+ manifestPath: string;
53
+ outdir: string;
54
+ }
55
+ /**
56
+ * Run the bundler in-process and return the manifest path + outdir.
57
+ * Used from `handleBundleClient` (protocol RPC path from Rust) AND
58
+ * from `getManifest` (in-process SSR path).
59
+ */
60
+ export declare function buildClientBundle(appDirRel?: string): Promise<BuildOutput>;
61
+ /**
62
+ * Return the bundle manifest. If a fresh manifest exists on disk,
63
+ * use it (caching parse output across requests). Otherwise build
64
+ * the bundle in-process (deduped by `buildClientBundle`) and read.
65
+ *
66
+ * Called from `ssr-runtime.ts` per-request, so the disk-stat fast
67
+ * path matters. Bun's `fs.statSync` is a ~5µs syscall; cheap enough
68
+ * that we don't gate it on a flag.
69
+ */
70
+ export declare function getManifest(): Promise<PylonBundleManifest>;
71
+ /**
72
+ * Protocol entry. Builds + responds. Rust calls this via the
73
+ * `bundle_client` RPC; on success the response carries both
74
+ * the manifest path (so Rust can load it if it wants, today it
75
+ * doesn't) and the outdir (so `/_pylon/build/<rel>` serves the
76
+ * right tree).
77
+ */
78
+ export declare function handleBundleClient(msg: BundleClientMessage, send: Send): Promise<void>;
79
+ export {};
@@ -0,0 +1,94 @@
1
+ /** One `@font-face` rule. A real face carries `src` (woff2 files); a generated
2
+ * fallback face carries `local` + the size-adjust/override metrics instead. */
3
+ export interface FontFace {
4
+ /** `font-family` — the real family, or `"<Family> Fallback"` for the
5
+ * size-adjusted fallback face. */
6
+ family: string;
7
+ /** outdir-relative woff2 filenames (real face). Empty for a fallback face. */
8
+ src: string[];
9
+ /** `local("...")` source for a size-adjusted fallback face, e.g. `"Arial"`. */
10
+ local?: string;
11
+ weight?: string;
12
+ style?: string;
13
+ display?: string;
14
+ unicodeRange?: string;
15
+ /** `size-adjust` percentage (fallback face only), e.g. `"107.40%"`. */
16
+ sizeAdjust?: string;
17
+ /** `ascent-override` percentage (fallback face only). */
18
+ ascentOverride?: string;
19
+ /** `descent-override` percentage (fallback face only). */
20
+ descentOverride?: string;
21
+ /** `line-gap-override` percentage (fallback face only). */
22
+ lineGapOverride?: string;
23
+ }
24
+ /** The bundle manifest's `fonts` block: structured faces + CSS variables +
25
+ * the woff2 files to `<link rel=preload>`. Rendered into every SSR `<head>`. */
26
+ export interface ManifestFonts {
27
+ faces: FontFace[];
28
+ /** CSS custom property → font-family stack, e.g.
29
+ * `{"--font-sans": '"Geist", "Geist Fallback", sans-serif'}`. */
30
+ variables: Record<string, string>;
31
+ /** outdir-relative woff2 filenames to preload. */
32
+ preload: string[];
33
+ }
34
+ interface SfntMetrics {
35
+ unitsPerEm: number;
36
+ ascent: number;
37
+ descent: number;
38
+ lineGap: number;
39
+ xAvgCharWidth: number;
40
+ }
41
+ /** A `ManifestFont` as it appears in `pylon.manifest.json` (snake_case wire
42
+ * shape — see the SDK `font()` helper). */
43
+ interface ManifestFontInput {
44
+ family: string;
45
+ variable: string;
46
+ weights?: string[];
47
+ styles?: string[];
48
+ subsets?: string[];
49
+ display?: string;
50
+ preload?: boolean;
51
+ fallback?: string[];
52
+ adjust_font_fallback?: boolean;
53
+ }
54
+ /** Decode the line-box metrics from a font buffer (woff2 or raw sfnt). Returns
55
+ * null when the format/tables can't be read — callers degrade gracefully. */
56
+ export declare function decodeFontMetrics(buf: Uint8Array): SfntMetrics | null;
57
+ /** Compute the size-adjusted fallback `@font-face` for a family. The web font's
58
+ * metrics, divided by the size-adjust factor, become the overrides — so the
59
+ * local fallback occupies the same line box + average advance as the web font
60
+ * will, eliminating the swap-in shift. */
61
+ export declare function computeFallbackFace(metrics: SfntMetrics, family: string, category: "sans-serif" | "serif"): FontFace;
62
+ /** Render the structured faces + `:root` variables into a CSS string. woff2 URLs
63
+ * are resolved against `prefix` (the live `public_prefix`) so they match
64
+ * wherever the assets are served (same-origin or CDN). */
65
+ export declare function renderFontFaceCss(fonts: ManifestFonts, prefix: string): string;
66
+ interface FontSpec {
67
+ family: string;
68
+ weights: string[];
69
+ styles: string[];
70
+ subsets: string[];
71
+ display: string;
72
+ }
73
+ interface FaceBlock {
74
+ subset: string;
75
+ style: string;
76
+ weight: string;
77
+ unicodeRange: string;
78
+ url: string;
79
+ }
80
+ /** Build the Google Fonts CSS2 API URL for a spec. */
81
+ export declare function buildGoogleFontsUrl(spec: FontSpec): string;
82
+ /** Parse a Google Fonts CSS2 response into per-subset face blocks, keeping only
83
+ * the requested subsets. Each `@font-face` is preceded by a subset comment in
84
+ * the API output. */
85
+ export declare function parseGoogleFontsCss(css: string, subsets: string[]): FaceBlock[];
86
+ /** Build all declared fonts: fetch + self-host + metrics + structured faces.
87
+ * Network/parse failures for a single family degrade to a variable-only entry
88
+ * (so `var(--font-x)` still resolves to the fallback stack) rather than killing
89
+ * the build. Returns null when nothing was produced. */
90
+ export declare function buildFonts(fs: any, path: any, cwd: string, outdir: string, fonts: ManifestFontInput[]): Promise<ManifestFonts | null>;
91
+ /** Read the `fonts` array from the app's `pylon.manifest.json` (written next to
92
+ * app.ts). Returns [] when absent/unreadable. */
93
+ export declare function readManifestFonts(fs: any, path: any, cwd: string): ManifestFontInput[];
94
+ export {};
@@ -0,0 +1,33 @@
1
+ /** Matches HandleFormMessage in crates/functions/src/protocol.rs. */
2
+ export interface HandleFormMessage {
3
+ type: "handle_form";
4
+ call_id: string;
5
+ component: string;
6
+ route_path: string;
7
+ method: string;
8
+ url: string;
9
+ params: Record<string, string>;
10
+ search_params: Record<string, string>;
11
+ form: Record<string, string | string[]>;
12
+ headers: Record<string, string>;
13
+ cookies: Record<string, string>;
14
+ auth: {
15
+ user_id: string | null;
16
+ is_admin: boolean;
17
+ tenant_id: string | null;
18
+ roles: string[];
19
+ };
20
+ }
21
+ type Send = (msg: Record<string, unknown>) => void;
22
+ /** Parsed form fields. Mirrors URLSearchParams get/getAll/has semantics. */
23
+ export interface FormFields {
24
+ /** First value for `name`, or null. */
25
+ get(name: string): string | null;
26
+ /** All values for `name` (empty array if none). */
27
+ getAll(name: string): string[];
28
+ has(name: string): boolean;
29
+ /** Raw map: name → value | values. */
30
+ readonly fields: Record<string, string | string[]>;
31
+ }
32
+ export declare function handleForm(msg: HandleFormMessage, send: Send): Promise<void>;
33
+ export {};