@mesofact/runtime 0.8.29
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/README.md +19 -0
- package/dist/adapters/r2.d.ts +24 -0
- package/dist/adapters/r2.d.ts.map +1 -0
- package/dist/adapters/r2.js +136 -0
- package/dist/adapters/r2.js.map +1 -0
- package/dist/adapters/sqlite.d.ts +25 -0
- package/dist/adapters/sqlite.d.ts.map +1 -0
- package/dist/adapters/sqlite.js +131 -0
- package/dist/adapters/sqlite.js.map +1 -0
- package/dist/config.d.ts +29 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +139 -0
- package/dist/config.js.map +1 -0
- package/dist/contract.d.ts +40 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +5 -0
- package/dist/contract.js.map +1 -0
- package/dist/errors.d.ts +29 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +40 -0
- package/dist/errors.js.map +1 -0
- package/dist/head.d.ts +32 -0
- package/dist/head.d.ts.map +1 -0
- package/dist/head.js +93 -0
- package/dist/head.js.map +1 -0
- package/dist/health.d.ts +14 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +85 -0
- package/dist/health.js.map +1 -0
- package/dist/hooks.d.ts +28 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +64 -0
- package/dist/hooks.js.map +1 -0
- package/dist/hydration.d.ts +6 -0
- package/dist/hydration.d.ts.map +1 -0
- package/dist/hydration.js +68 -0
- package/dist/hydration.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.d.ts +68 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +9 -0
- package/dist/manifest.js.map +1 -0
- package/dist/routes.d.ts +67 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +130 -0
- package/dist/routes.js.map +1 -0
- package/dist/source.d.ts +36 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +52 -0
- package/dist/source.js.map +1 -0
- package/dist/track-ctx.d.ts +13 -0
- package/dist/track-ctx.d.ts.map +1 -0
- package/dist/track-ctx.js +15 -0
- package/dist/track-ctx.js.map +1 -0
- package/dist/validate.d.ts +20 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +333 -0
- package/dist/validate.js.map +1 -0
- package/package.json +40 -0
- package/src/adapters/r2.ts +163 -0
- package/src/adapters/sqlite.ts +182 -0
- package/src/config.ts +213 -0
- package/src/contract.ts +82 -0
- package/src/errors.ts +52 -0
- package/src/head.ts +130 -0
- package/src/health.ts +99 -0
- package/src/hooks.ts +72 -0
- package/src/hydration.ts +72 -0
- package/src/index.ts +113 -0
- package/src/manifest.ts +104 -0
- package/src/routes.ts +320 -0
- package/src/source.ts +91 -0
- package/src/track-ctx.ts +29 -0
- package/src/validate.ts +388 -0
package/src/health.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// `defineReadyz` — the TSX half of mesofact's `/readyz` probe.
|
|
2
|
+
//
|
|
3
|
+
// mesofact serves `/livez` and `/readyz` itself, in Rust
|
|
4
|
+
// (`crates/mesofact/src/health.rs`), gated on the things only the engine can
|
|
5
|
+
// know: is the SSR isolate up, is there a tree to serve, are we draining. An
|
|
6
|
+
// app opts into contributing its own readiness by declaring the path as an
|
|
7
|
+
// ordinary SSR route — there is no config key and no manifest field:
|
|
8
|
+
//
|
|
9
|
+
// // mesofact.routes.ts
|
|
10
|
+
// { route: "/readyz", mode: "ssr", entrypoint: "src/readyz.ts", cache_policy: { ttl: 0 } }
|
|
11
|
+
//
|
|
12
|
+
// The Rust handler notices the app claimed the path, dispatches to it while
|
|
13
|
+
// answering the probe, and folds the response status into its own listing as a
|
|
14
|
+
// check named `app`. Anything outside 2xx takes the pod out of rotation.
|
|
15
|
+
//
|
|
16
|
+
// # The contract, and its one asymmetry
|
|
17
|
+
//
|
|
18
|
+
// An app verdict is **additive**: it can only make the process less ready. A
|
|
19
|
+
// 200 from here cannot un-drain a terminating process or overrule a dead
|
|
20
|
+
// isolate, because those are exactly the states in which app code is the
|
|
21
|
+
// unreliable narrator. Design your checks accordingly — this is the place to
|
|
22
|
+
// say "my database pool is exhausted", not "ignore the engine, I'm fine".
|
|
23
|
+
//
|
|
24
|
+
// # Vanilla, or this helper
|
|
25
|
+
//
|
|
26
|
+
// The route is a plain Fetch handler, so vanilla works and owes nothing to this
|
|
27
|
+
// module:
|
|
28
|
+
//
|
|
29
|
+
// export default async () =>
|
|
30
|
+
// (await db.ping()) ? new Response("ok") : new Response("db", { status: 503 });
|
|
31
|
+
//
|
|
32
|
+
// `defineReadyz` is the opt-in ergonomic layer: it runs named checks and emits
|
|
33
|
+
// the *same* wire format the Rust side emits — the kube-apiserver `[+]name ok`
|
|
34
|
+
// listing under `?verbose`. Matching formats is the point. An operator running
|
|
35
|
+
// `curl localhost:3000/readyz?verbose` should not be able to tell which
|
|
36
|
+
// language answered, and when the Rust side aggregates, the two listings nest
|
|
37
|
+
// instead of clashing.
|
|
38
|
+
|
|
39
|
+
/** One named readiness condition. `check` should be fast and side-effect free —
|
|
40
|
+
* probes run on the kubelet's schedule, per replica. */
|
|
41
|
+
export type ReadyCheck = {
|
|
42
|
+
/** Operator-facing name in `?verbose` output. A noun (`db`, `cache`). */
|
|
43
|
+
name: string;
|
|
44
|
+
check: () => boolean | Promise<boolean>;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/** Build the Fetch handler for a `mode:"ssr"` `/readyz` route.
|
|
48
|
+
*
|
|
49
|
+
* Every check runs, even after one fails: reporting only the first failure
|
|
50
|
+
* hides a second broken subsystem behind the first. A check that throws counts
|
|
51
|
+
* as failed — an exception is not an assertion of readiness. */
|
|
52
|
+
export function defineReadyz(
|
|
53
|
+
checks: readonly ReadyCheck[],
|
|
54
|
+
): (req: Request) => Promise<Response> {
|
|
55
|
+
return async (req: Request): Promise<Response> => {
|
|
56
|
+
const results = await Promise.all(
|
|
57
|
+
checks.map(async (c) => {
|
|
58
|
+
try {
|
|
59
|
+
return { name: c.name, pass: (await c.check()) === true };
|
|
60
|
+
} catch {
|
|
61
|
+
return { name: c.name, pass: false };
|
|
62
|
+
}
|
|
63
|
+
}),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const ok = results.every((r) => r.pass);
|
|
67
|
+
const status = ok ? 200 : 503;
|
|
68
|
+
const headers = {
|
|
69
|
+
"content-type": "text/plain; charset=utf-8",
|
|
70
|
+
// A cached 200 outlives the condition it described, which is the exact
|
|
71
|
+
// failure the probe exists to catch.
|
|
72
|
+
"cache-control": "no-cache, no-store, must-revalidate",
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
if (!isVerbose(req)) {
|
|
76
|
+
return new Response(ok ? "ok\n" : "readyz check failed\n", {
|
|
77
|
+
status,
|
|
78
|
+
headers,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const listing = results
|
|
83
|
+
.map((r) => (r.pass ? `[+]${r.name} ok\n` : `[-]${r.name} failed\n`))
|
|
84
|
+
.join("");
|
|
85
|
+
const trailer = ok ? "readyz check passed\n" : "readyz check failed\n";
|
|
86
|
+
return new Response(listing + trailer, { status, headers });
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** `?verbose` / `?verbose=1`, matching the Rust `is_verbose`. A malformed URL
|
|
91
|
+
* means the non-verbose body, never a crash — a probe that 500s because it
|
|
92
|
+
* could not parse its own URL is worse than one that answers tersely. */
|
|
93
|
+
function isVerbose(req: Request): boolean {
|
|
94
|
+
try {
|
|
95
|
+
return new URL(req.url).searchParams.has("verbose");
|
|
96
|
+
} catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
package/src/hooks.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Mode 2 hook declaration — W311 §2 "Open Decision 1", answered by R756-F6.
|
|
2
|
+
//
|
|
3
|
+
// # What a hook is
|
|
4
|
+
//
|
|
5
|
+
// mesofact's TSX override surface has two modes (W311 §2). Mode 1 is the
|
|
6
|
+
// general API: `mode:"ssr"` route, `Request -> Response`, the app owns the
|
|
7
|
+
// wire. Mode 2 is the endpoint callback: a small `input -> verdict`, and
|
|
8
|
+
// **Rust** owns the HTTP. R756-F3 shipped Mode 2's wire verb
|
|
9
|
+
// (`__mesofact_ssr.invoke(bundle, hookName, input)`) but hardcoded its single
|
|
10
|
+
// hook name, because `/readyz` — Mode 2's only consumer — could reuse the
|
|
11
|
+
// route-claim opt-in it already had. A hook that is *not* a path (`onRequest`,
|
|
12
|
+
// `onError`, an auth gate) has no route to claim, so it needs a declaration
|
|
13
|
+
// site. This is that site.
|
|
14
|
+
//
|
|
15
|
+
// # Why a top-level `hooks` key, and not a per-route `middleware` field
|
|
16
|
+
//
|
|
17
|
+
// W311 named two candidates and deliberately picked neither. The `hooks` key
|
|
18
|
+
// wins on the doc's own terms:
|
|
19
|
+
//
|
|
20
|
+
// - A per-route `middleware` field is a *chain*, and "a general TS
|
|
21
|
+
// middleware chain" is an explicit W311 non-goal — it has ordering
|
|
22
|
+
// semantics (what runs before what, who may short-circuit, how errors
|
|
23
|
+
// propagate) that Mode 2 does not have and does not want.
|
|
24
|
+
// - Mode 2's defining property is that Rust owns the HTTP. A hook is
|
|
25
|
+
// therefore engine-addressed — the engine decides when to call `readyz`,
|
|
26
|
+
// not a route table — so the declaration belongs beside `routes`, not
|
|
27
|
+
// inside one.
|
|
28
|
+
// - Hook names are a **closed set** for the same reason: only the engine
|
|
29
|
+
// invokes a hook, so a name the engine does not know is dead code and a
|
|
30
|
+
// silent typo. Adding one is three deliberate edits — a name here, an
|
|
31
|
+
// adapter in `crates/mesofact-ssr/js/ssr_harness.js`, and the Rust call
|
|
32
|
+
// site that decides when it runs — and each is a real contract statement.
|
|
33
|
+
//
|
|
34
|
+
// # Adding a hook
|
|
35
|
+
//
|
|
36
|
+
// Add the name to `HOOK_NAMES`, teach `ssr_harness.js` how to call it (the
|
|
37
|
+
// default is plain Mode 2 — `(input) => verdict`, JSON both ways), and write
|
|
38
|
+
// the Rust call site. `hooks` in the manifest carries name → bundled module;
|
|
39
|
+
// `mesofact::ssr::spawn` registers each one on every isolate in the pool and
|
|
40
|
+
// `SsrChild::invoke_hook(name, input)` calls it.
|
|
41
|
+
|
|
42
|
+
/** Every hook name the engine knows how to invoke.
|
|
43
|
+
*
|
|
44
|
+
* `readyz` is the one hook that predates this declaration site, so it has two
|
|
45
|
+
* ways in (see {@link HOOK_ROUTE_CLAIMS}) and its module's contract is a
|
|
46
|
+
* Fetch handler rather than a plain Mode 2 function. Hooks added from here on
|
|
47
|
+
* are `(input) => verdict`. */
|
|
48
|
+
export const HOOK_NAMES = ["readyz"] as const;
|
|
49
|
+
|
|
50
|
+
export type HookName = (typeof HOOK_NAMES)[number];
|
|
51
|
+
|
|
52
|
+
/** The `hooks` block of `defineRoutes` — hook name → entrypoint path, relative
|
|
53
|
+
* to the project root, bundled to `dist/server/hooks/<name>.js`. */
|
|
54
|
+
export type HooksConfig = { readonly [K in HookName]?: string };
|
|
55
|
+
|
|
56
|
+
/** Hooks that may *alternatively* be declared by claiming a route, which is
|
|
57
|
+
* how `/readyz` shipped (R756-F5) before hooks existed.
|
|
58
|
+
*
|
|
59
|
+
* Route-claiming still works and is still the shortest path for an app that
|
|
60
|
+
* wants its readiness handler reachable as an ordinary Fetch handler. The
|
|
61
|
+
* `hooks` declaration is the better one for everything else: the module is
|
|
62
|
+
* not a route, so it never enters `ssr_prefixes`, is never forwarded to the
|
|
63
|
+
* SSR origin by the edge Worker, and is never shadowed by the Rust probe
|
|
64
|
+
* route it would otherwise collide with. Declaring both is rejected — two
|
|
65
|
+
* opt-ins for one verdict is ambiguous, not additive. */
|
|
66
|
+
export const HOOK_ROUTE_CLAIMS: { readonly [K in HookName]?: string } = {
|
|
67
|
+
readyz: "/readyz",
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export function isHookName(name: string): name is HookName {
|
|
71
|
+
return (HOOK_NAMES as readonly string[]).includes(name);
|
|
72
|
+
}
|
package/src/hydration.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Hydration handoff — helpers an SSR route's Fetch handler calls to inline
|
|
2
|
+
// server-resolved data into the response body for the client to read on
|
|
3
|
+
// mount. Disposable when RSC streaming lands; until then this is the
|
|
4
|
+
// Universal cell's contract (W173 § "Hydration handoff").
|
|
5
|
+
//
|
|
6
|
+
// API surface is intentionally minimal — two pure functions. The handler
|
|
7
|
+
// composes them into its own HTML response, which means the consumer keeps
|
|
8
|
+
// full control over the document shell. The trade-off is that the consumer
|
|
9
|
+
// has to know its own build_id + hashed client-script name (read from the
|
|
10
|
+
// manifest at boot, or inject via env). Acceptable for the dogfood window;
|
|
11
|
+
// revisit when more consumers exist.
|
|
12
|
+
//
|
|
13
|
+
// Also published standalone as the `@mesofact/runtime/hydration` subpath
|
|
14
|
+
// export (R513-B11): this file must stay free of imports (or only import
|
|
15
|
+
// other import-free, browser-safe modules) so a client bundle can read
|
|
16
|
+
// `SPA_STATE_SCRIPT_ID` without dragging in the `@mesofact/runtime` barrel,
|
|
17
|
+
// which re-exports server-only code (`track-ctx.ts` → `node:async_hooks`,
|
|
18
|
+
// `config.ts` → `node:fs`) that rolldown can't tree-shake past.
|
|
19
|
+
|
|
20
|
+
// Build-time SPA shell consumes this id; the prerender weaves it in.
|
|
21
|
+
export const SPA_STATE_SCRIPT_ID = "__MESOFACT_STATE__" as const;
|
|
22
|
+
|
|
23
|
+
// Per-request SSR Universal handoff consumes this id; see W173 § "Hydration
|
|
24
|
+
// handoff" — the name is fixed (not configurable) so the client snippet can
|
|
25
|
+
// be copy-pasteable across consumers.
|
|
26
|
+
export const SSR_DATA_SCRIPT_ID = "__mesofact_data__" as const;
|
|
27
|
+
|
|
28
|
+
// JSON-encode `value` and escape the characters that could close the parent
|
|
29
|
+
// `<script>` tag early or be reinterpreted by the HTML parser:
|
|
30
|
+
//
|
|
31
|
+
// `<`, `>`, `&` — `</script>`, `<!--`, `<![CDATA[`, `&` injection
|
|
32
|
+
// U+2028 / U+2029 — JS line separators valid in JSON but break inline
|
|
33
|
+
// scripts when not escaped
|
|
34
|
+
//
|
|
35
|
+
// `JSON.parse` decodes the `\uXXXX` escapes transparently, so the client
|
|
36
|
+
// reads back the original value via `JSON.parse(el.textContent)` with no
|
|
37
|
+
// special handling. Non-negotiable per W173 § "XSS escape rule".
|
|
38
|
+
export function escapeJsonForScriptTag(value: unknown): string {
|
|
39
|
+
return JSON.stringify(value).replace(/[<>&\u2028\u2029]/g, (c) => {
|
|
40
|
+
switch (c) {
|
|
41
|
+
case "<":
|
|
42
|
+
return "\\u003c";
|
|
43
|
+
case ">":
|
|
44
|
+
return "\\u003e";
|
|
45
|
+
case "&":
|
|
46
|
+
return "\\u0026";
|
|
47
|
+
case "\u2028":
|
|
48
|
+
return "\\u2028";
|
|
49
|
+
case "\u2029":
|
|
50
|
+
return "\\u2029";
|
|
51
|
+
default:
|
|
52
|
+
return c;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Serialize `data` into the data-handoff `<script>` tag an SSR route's
|
|
58
|
+
// Fetch handler inlines into its response body. Read back on the client via
|
|
59
|
+
// `JSON.parse(document.getElementById("__mesofact_data__").textContent)`.
|
|
60
|
+
export function hydrationDataTag(data: unknown): string {
|
|
61
|
+
return `<script id="${SSR_DATA_SCRIPT_ID}" type="application/json">${escapeJsonForScriptTag(data)}</script>`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Build the module-script tag that loads the route's hydrate bundle. `src`
|
|
65
|
+
// is escaped against `"`, `<`, and `&` so a manifest-derived URL containing
|
|
66
|
+
// a stray quote can't break the attribute or the tag boundary; belt-and-
|
|
67
|
+
// braces — the manifest paths the build emits don't contain quotes, but
|
|
68
|
+
// consumers may compose paths from request data.
|
|
69
|
+
export function hydrationScriptTag(src: string): string {
|
|
70
|
+
const safeSrc = src.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
71
|
+
return `<script type="module" src="${safeSrc}"></script>`;
|
|
72
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// @mesofact/runtime — render contract types and adapter API.
|
|
2
|
+
// See `.yah/docs/architecture/mesofact.md`.
|
|
3
|
+
|
|
4
|
+
export type {
|
|
5
|
+
Region,
|
|
6
|
+
User,
|
|
7
|
+
Project,
|
|
8
|
+
RenderRequest,
|
|
9
|
+
CachePolicy,
|
|
10
|
+
Hydration,
|
|
11
|
+
RenderResult,
|
|
12
|
+
RenderFn,
|
|
13
|
+
} from "./contract.js";
|
|
14
|
+
|
|
15
|
+
export type {
|
|
16
|
+
ListOpts,
|
|
17
|
+
R2Object,
|
|
18
|
+
Source,
|
|
19
|
+
BlobSource,
|
|
20
|
+
KeyValueSource,
|
|
21
|
+
} from "./source.js";
|
|
22
|
+
export { BaseSource } from "./source.js";
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
SourceError,
|
|
26
|
+
SourceUnavailableError,
|
|
27
|
+
SourceTimeoutError,
|
|
28
|
+
SourceQueryError,
|
|
29
|
+
RowNotFoundError,
|
|
30
|
+
} from "./errors.js";
|
|
31
|
+
|
|
32
|
+
export type {
|
|
33
|
+
RouteMode,
|
|
34
|
+
Placement,
|
|
35
|
+
Requires,
|
|
36
|
+
CachePolicyConfig,
|
|
37
|
+
PrerenderConfig,
|
|
38
|
+
RouteEntry,
|
|
39
|
+
ErrorRoutes,
|
|
40
|
+
RoutesConfig,
|
|
41
|
+
RetryOn,
|
|
42
|
+
RetryPolicy,
|
|
43
|
+
QueuePolicy,
|
|
44
|
+
ResiliencePolicy,
|
|
45
|
+
} from "./routes.js";
|
|
46
|
+
|
|
47
|
+
export { defineRoutes, DEFAULT_RESILIENCE_TIMEOUT_MS } from "./routes.js";
|
|
48
|
+
|
|
49
|
+
export type { HookName, HooksConfig } from "./hooks.js";
|
|
50
|
+
export { HOOK_NAMES, HOOK_ROUTE_CLAIMS, isHookName } from "./hooks.js";
|
|
51
|
+
|
|
52
|
+
export type {
|
|
53
|
+
ManifestVersion,
|
|
54
|
+
ManifestCachePolicy,
|
|
55
|
+
ManifestHydration,
|
|
56
|
+
ManifestPrerender,
|
|
57
|
+
ManifestRoute,
|
|
58
|
+
ManifestStaticAsset,
|
|
59
|
+
ManifestErrorRoutes,
|
|
60
|
+
ManifestHook,
|
|
61
|
+
ManifestHooks,
|
|
62
|
+
Manifest,
|
|
63
|
+
ResolvedPlacement,
|
|
64
|
+
} from "./manifest.js";
|
|
65
|
+
|
|
66
|
+
export { MANIFEST_VERSION } from "./manifest.js";
|
|
67
|
+
|
|
68
|
+
export type {
|
|
69
|
+
SourceScope,
|
|
70
|
+
SourceCatalog,
|
|
71
|
+
ValidationError,
|
|
72
|
+
ValidationErrorKind,
|
|
73
|
+
ValidationResult,
|
|
74
|
+
} from "./validate.js";
|
|
75
|
+
|
|
76
|
+
export { validate } from "./validate.js";
|
|
77
|
+
|
|
78
|
+
export { runInTrackCtx, currentTrackCtx } from "./track-ctx.js";
|
|
79
|
+
export type { TrackCtx } from "./track-ctx.js";
|
|
80
|
+
|
|
81
|
+
export {
|
|
82
|
+
SPA_STATE_SCRIPT_ID,
|
|
83
|
+
SSR_DATA_SCRIPT_ID,
|
|
84
|
+
escapeJsonForScriptTag,
|
|
85
|
+
hydrationDataTag,
|
|
86
|
+
hydrationScriptTag,
|
|
87
|
+
} from "./hydration.js";
|
|
88
|
+
|
|
89
|
+
export type { OpenGraph, TwitterCard, HeadLink, Head } from "./head.js";
|
|
90
|
+
export { renderHead, weaveHead } from "./head.js";
|
|
91
|
+
|
|
92
|
+
export { defineReadyz } from "./health.js";
|
|
93
|
+
export type { ReadyCheck } from "./health.js";
|
|
94
|
+
|
|
95
|
+
export { R2Adapter, r2, registerR2, clearR2Registry } from "./adapters/r2.js";
|
|
96
|
+
export type { R2Config } from "./adapters/r2.js";
|
|
97
|
+
|
|
98
|
+
export { SqliteAdapter, sqlite, registerSqlite, clearSqliteRegistry } from "./adapters/sqlite.js";
|
|
99
|
+
export type { SqliteConfig, SqliteRunner } from "./adapters/sqlite.js";
|
|
100
|
+
|
|
101
|
+
export {
|
|
102
|
+
loadConfig,
|
|
103
|
+
parseConfig,
|
|
104
|
+
registerSourcesFromConfig,
|
|
105
|
+
ConfigError,
|
|
106
|
+
} from "./config.js";
|
|
107
|
+
export type {
|
|
108
|
+
BuildConfig,
|
|
109
|
+
MesofactConfig,
|
|
110
|
+
R2SourceConfig,
|
|
111
|
+
SqliteSourceConfig,
|
|
112
|
+
SourceConfig,
|
|
113
|
+
} from "./config.js";
|
package/src/manifest.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// Manifest types — the single document the build emits and the proxy boots
|
|
2
|
+
// from. Authored shape lives in `routes.ts` (RoutesConfig); the build enriches
|
|
3
|
+
// it with `build_id`, `static_assets`, hydration, and resolved entrypoint
|
|
4
|
+
// paths.
|
|
5
|
+
//
|
|
6
|
+
// Versioned independently of the mesofact binary; major bumps force restart.
|
|
7
|
+
// See `.yah/docs/architecture/mesofact.md` §"Manifest schema".
|
|
8
|
+
|
|
9
|
+
import type { HookName } from "./hooks.js";
|
|
10
|
+
import type { Placement, ResiliencePolicy, RouteMode, Requires } from "./routes.js";
|
|
11
|
+
|
|
12
|
+
// Placement as carried in the manifest — the build resolves `"auto"` to
|
|
13
|
+
// `"host"` or `"edge"` before emission, so consumers never see `"auto"`.
|
|
14
|
+
// See W173 § "Placement: validation rules".
|
|
15
|
+
export type ResolvedPlacement = Exclude<Placement, "auto">;
|
|
16
|
+
|
|
17
|
+
export const MANIFEST_VERSION = "1" as const;
|
|
18
|
+
|
|
19
|
+
export type ManifestVersion = typeof MANIFEST_VERSION;
|
|
20
|
+
|
|
21
|
+
export type ManifestCachePolicy = {
|
|
22
|
+
ttl: number;
|
|
23
|
+
swr?: number;
|
|
24
|
+
negative_ttl?: number;
|
|
25
|
+
vary?: readonly string[];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type ManifestHydration = {
|
|
29
|
+
script: string;
|
|
30
|
+
code_split: readonly string[];
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type ManifestPrerender =
|
|
34
|
+
| { params: ReadonlyArray<Record<string, string>> }
|
|
35
|
+
| { from: string; query: string; param: string }
|
|
36
|
+
| { from_data: string; items_key: string; param: string }
|
|
37
|
+
// Instance-addressed: params are minted after the build (publish time); the
|
|
38
|
+
// build emits the server bundle + manifest entry and prerenders nothing.
|
|
39
|
+
// Serving resolves each instance through the pointer store (W270 §2). Mirrors
|
|
40
|
+
// `Prerender::Deferred` in `crates/mesofact/src/manifest.rs`.
|
|
41
|
+
| { deferred: true };
|
|
42
|
+
|
|
43
|
+
export type ManifestRoute = {
|
|
44
|
+
route: string;
|
|
45
|
+
mode: RouteMode;
|
|
46
|
+
render_entrypoint: string;
|
|
47
|
+
requires?: readonly Requires[];
|
|
48
|
+
source_reads?: readonly string[];
|
|
49
|
+
// Build-time data artifact paths (relative to project root). Present when
|
|
50
|
+
// the route declared `data_inputs`; used by the reconciler to detect which
|
|
51
|
+
// file changes trigger a rebuild of this route.
|
|
52
|
+
data_inputs?: readonly string[];
|
|
53
|
+
cache_policy: ManifestCachePolicy;
|
|
54
|
+
concurrency?: number;
|
|
55
|
+
hydration?: ManifestHydration;
|
|
56
|
+
prerender?: ManifestPrerender;
|
|
57
|
+
// SSR routes only; never "auto" (resolved at build time per W173).
|
|
58
|
+
placement?: ResolvedPlacement;
|
|
59
|
+
// SSR routes only (W181). Carried verbatim from the route declaration so
|
|
60
|
+
// the Worker (prod) and the mesofact-dev proxy (dev) apply the same retry/
|
|
61
|
+
// timeout policy around the origin hop. Absent = one attempt, default
|
|
62
|
+
// timeout, fail with 502 — exactly the pre-W181 behavior.
|
|
63
|
+
resilience?: ResiliencePolicy;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export type ManifestStaticAsset = {
|
|
67
|
+
key: string;
|
|
68
|
+
content_hash: string;
|
|
69
|
+
content_type: string;
|
|
70
|
+
immutable: boolean;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export type ManifestErrorRoutes = {
|
|
74
|
+
"404"?: string;
|
|
75
|
+
"5xx"?: string;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// One declared Mode 2 hook (W311 §2 / R756-F6). An object rather than a bare
|
|
79
|
+
// path string because the per-hook options W311 left open — chiefly "may this
|
|
80
|
+
// hook be async" (Open Decision 3) — land here without a schema break.
|
|
81
|
+
export type ManifestHook = {
|
|
82
|
+
// Bundled module, `dist/server/hooks/<name>.js`. Same shape and resolution
|
|
83
|
+
// rule as a route's `render_entrypoint`.
|
|
84
|
+
entrypoint: string;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export type ManifestHooks = { readonly [K in HookName]?: ManifestHook };
|
|
88
|
+
|
|
89
|
+
export type Manifest = {
|
|
90
|
+
version: ManifestVersion;
|
|
91
|
+
build_id: string;
|
|
92
|
+
routes: readonly ManifestRoute[];
|
|
93
|
+
static_assets: readonly ManifestStaticAsset[];
|
|
94
|
+
error_routes?: ManifestErrorRoutes;
|
|
95
|
+
// Declared Mode 2 hooks, name → bundled module. Absent when the workload
|
|
96
|
+
// declares none. Host-only: the edge Worker never invokes a hook, because
|
|
97
|
+
// Mode 2's whole premise is that the Rust host owns the HTTP around it.
|
|
98
|
+
hooks?: ManifestHooks;
|
|
99
|
+
// Derived from every `mode:"ssr"` route per W173 § "SSR_PREFIXES derivation
|
|
100
|
+
// rule". Used by mesofact-dev (proxy) and the CF Worker to forward matching
|
|
101
|
+
// paths to the SSR runtime. Segment-aware match: `path === p || path.startsWith(p + "/")`.
|
|
102
|
+
// Absent when the workload has no SSR routes.
|
|
103
|
+
ssr_prefixes?: readonly string[];
|
|
104
|
+
};
|