brustjs 0.1.49-alpha → 0.1.51-alpha
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 +39 -15
- package/runtime/cache-sync.ts +291 -0
- package/runtime/cache.ts +4 -0
- package/runtime/cli/build.ts +7 -0
- package/runtime/cli/dev.ts +7 -0
- package/runtime/cli/native-routes-emit.ts +147 -1
- package/runtime/cli/ssg.ts +94 -23
- package/runtime/config.ts +42 -0
- package/runtime/index.d.ts +63 -0
- package/runtime/index.js +57 -52
- package/runtime/index.ts +114 -9
- package/runtime/islands/page-cache.ts +32 -2
- package/runtime/native/runtime.ts +220 -7
- package/runtime/render/fragment.ts +87 -0
- package/runtime/routes.ts +482 -95
- package/runtime/templates.ts +47 -0
- package/runtime/treaty.ts +24 -1
- package/types/action-error.d.ts +18 -0
- package/types/cache-sync.d.ts +42 -0
- package/types/cache.d.ts +20 -0
- package/types/cli/help.d.ts +28 -0
- package/types/cli/jinja-staleness.d.ts +14 -0
- package/types/cli/native-routes-emit.d.ts +217 -0
- package/types/cli/new.d.ts +30 -0
- package/types/cli/templates.d.ts +39 -0
- package/types/client/index.d.ts +14 -0
- package/types/config.d.ts +42 -0
- package/types/cookies.d.ts +25 -0
- package/types/create.d.ts +1 -0
- package/types/css/build.d.ts +11 -0
- package/types/css/component-build.d.ts +17 -0
- package/types/css/component-loader.d.ts +8 -0
- package/types/css/manifest.d.ts +21 -0
- package/types/css/process-modules.d.ts +31 -0
- package/types/css/route-deps.d.ts +20 -0
- package/types/css/scan-imports.d.ts +13 -0
- package/types/css.d.ts +16 -0
- package/types/define-actions.d.ts +133 -0
- package/types/dev/client.d.ts +8 -0
- package/types/dev/coordinator.d.ts +33 -0
- package/types/dev/inject.d.ts +6 -0
- package/types/dev/jinja-reload.d.ts +7 -0
- package/types/dev/tui.d.ts +35 -0
- package/types/dev/watcher.d.ts +34 -0
- package/types/dev/worker-registry.d.ts +17 -0
- package/types/dev/ws-channel.d.ts +39 -0
- package/types/generator.d.ts +23 -0
- package/types/index.d.ts +222 -0
- package/types/islands/brust-page.d.ts +74 -0
- package/types/islands/build.d.ts +49 -0
- package/types/islands/chunk-id.d.ts +10 -0
- package/types/islands/importmap.d.ts +2 -0
- package/types/islands/island.d.ts +65 -0
- package/types/islands/isr-jsx.d.ts +31 -0
- package/types/islands/native-render.d.ts +89 -0
- package/types/loader-cache.d.ts +18 -0
- package/types/mcp/extractor.d.ts +14 -0
- package/types/mcp/manifest.d.ts +23 -0
- package/types/mcp/schema.d.ts +19 -0
- package/types/mcp/server.d.ts +15 -0
- package/types/md/emit.d.ts +72 -0
- package/types/md/render.d.ts +80 -0
- package/types/md/routes.d.ts +119 -0
- package/types/md/scan.d.ts +34 -0
- package/types/md/slug.d.ts +1 -0
- package/types/native/build.d.ts +30 -0
- package/types/native/index.d.ts +2 -0
- package/types/native/runtime.d.ts +52 -0
- package/types/navigation/active-nav.d.ts +2 -0
- package/types/navigation/index.d.ts +5 -0
- package/types/navigation/navigate.d.ts +14 -0
- package/types/navigation/react.d.ts +15 -0
- package/types/navigation/store.d.ts +44 -0
- package/types/render/fragment.d.ts +20 -0
- package/types/render/inject-action-prefix.d.ts +9 -0
- package/types/render/inject-css-link.d.ts +8 -0
- package/types/render/inject-dev-client.d.ts +6 -0
- package/types/render/inject-generator.d.ts +7 -0
- package/types/render/inject-store.d.ts +9 -0
- package/types/render/stream.d.ts +45 -0
- package/types/request-context.d.ts +16 -0
- package/types/routes.d.ts +506 -0
- package/types/sse/handler.d.ts +22 -0
- package/types/standard-schema.d.ts +31 -0
- package/types/store/define-store.d.ts +31 -0
- package/types/store/index.d.ts +5 -0
- package/types/store/react.d.ts +2 -0
- package/types/store/serialize.d.ts +5 -0
- package/types/store/server-context.d.ts +4 -0
- package/types/store/signal.d.ts +18 -0
- package/types/templates.d.ts +18 -0
- package/types/treaty.d.ts +70 -0
- package/types/ws/handler.d.ts +26 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type MdRouteSource } from './routes.ts';
|
|
2
|
+
/** The slice of a FlatRoute the md emit step reads. The chain holds the route
|
|
3
|
+
* NODES, so the md leaf's `__mdSource` (runtime/md/routes.ts) survives into it.
|
|
4
|
+
* `fullPath` is only read by the manifest derivation (emitMdArtifacts).
|
|
5
|
+
* `Component` admits plain `{ name }` carriers AND real component values
|
|
6
|
+
* (function/class — both expose `.name` via the Function prototype), so a
|
|
7
|
+
* `FlatRoute[]` passes without casts. */
|
|
8
|
+
export interface FlatRouteLike {
|
|
9
|
+
fullPath?: string;
|
|
10
|
+
nativeTemplate?: string;
|
|
11
|
+
chain?: Array<{
|
|
12
|
+
Component?: {
|
|
13
|
+
name?: string;
|
|
14
|
+
} | ((...args: never[]) => unknown) | (abstract new (...args: never[]) => unknown);
|
|
15
|
+
__mdSource?: MdRouteSource;
|
|
16
|
+
}>;
|
|
17
|
+
}
|
|
18
|
+
export interface MdEmitOpts {
|
|
19
|
+
/** User's routes entry file (absolute path) — scanned for the default-import
|
|
20
|
+
* idents that resolve embedded component tags, and for app-wide directives. */
|
|
21
|
+
entryFile: string;
|
|
22
|
+
/** Flat routes; only chains whose LEAF carries `__mdSource` are emitted. */
|
|
23
|
+
flatRoutes: FlatRouteLike[];
|
|
24
|
+
/** Jinja output dir (same dir `emitNativeTemplates` writes to). */
|
|
25
|
+
outDir: string;
|
|
26
|
+
/** Bake the /_brust/dev WS client tag (parity with the native emit's
|
|
27
|
+
* BRUST_DEV injection — md pages render Rust-side and never pass through the
|
|
28
|
+
* React renderer's dev-client injection). */
|
|
29
|
+
withDevClient?: boolean;
|
|
30
|
+
/** What to do when a route's md file no longer exists on disk (deleted after
|
|
31
|
+
* the route table was built). emitMdTemplates serves BOTH `brust build` and
|
|
32
|
+
* the dev re-emit, and the two must diverge here:
|
|
33
|
+
* - `'throw'` (build): a missing file is a HARD error — silently skipping
|
|
34
|
+
* ships a dist with the route registered but its template absent.
|
|
35
|
+
* - `'skip-warn'` (default; dev boot/re-emit): skip the route and warn
|
|
36
|
+
* once that a restart is required — the dev route table is frozen at
|
|
37
|
+
* boot, so a crash here would take down the whole hot-reload loop. */
|
|
38
|
+
onMissing?: 'throw' | 'skip-warn';
|
|
39
|
+
}
|
|
40
|
+
/** Test helper. */
|
|
41
|
+
export declare function _resetMdRoutesChangedWarnForTests(): void;
|
|
42
|
+
/** Emit one `.jinja` (+ `.islands.json` sidecar) per md route in `flatRoutes`.
|
|
43
|
+
* Returns the islands referenced from md content (`name → absolute source
|
|
44
|
+
* path`) so the build step can thread them into island-chunk discovery as
|
|
45
|
+
* `extraIslands` (task 2.8). Behaviors are NOT in the map — their chunks are
|
|
46
|
+
* built by `scanDirectiveComponents`, which already walks the routes-entry
|
|
47
|
+
* import graph the registry keeps alive. */
|
|
48
|
+
export declare function emitMdTemplates(opts: MdEmitOpts): Promise<{
|
|
49
|
+
mdIslands: Map<string, string>;
|
|
50
|
+
}>;
|
|
51
|
+
export interface MdArtifactsOpts extends MdEmitOpts {
|
|
52
|
+
/** Dirs to write `md-manifest.json` into when the app has md routes (e.g.
|
|
53
|
+
* `<distDir>` and `<cwd>/.brust` — both "next to" their jinja dirs, where
|
|
54
|
+
* `loadPrebuiltMdManifest` / the staleness check look). Skipped entirely
|
|
55
|
+
* when there are no md routes (zero output difference for md-free apps). */
|
|
56
|
+
manifestDirs?: string[];
|
|
57
|
+
}
|
|
58
|
+
/** Task 2.8 build-integration seam: emit the md `.jinja` templates AND the
|
|
59
|
+
* frozen `md-manifest.json` (derived from the same flat route table — single
|
|
60
|
+
* source of truth) in one call. All build sites (`brust build`, `brust dev`
|
|
61
|
+
* boot/re-emit, runtime boot, dev HMR island rebuild) go through this so the
|
|
62
|
+
* template, manifest, and returned `mdIslands` chunk inputs can never diverge.
|
|
63
|
+
* Strict no-op for apps without md routes. */
|
|
64
|
+
export declare function emitMdArtifacts(opts: MdArtifactsOpts): Promise<{
|
|
65
|
+
mdIslands: Map<string, string>;
|
|
66
|
+
}>;
|
|
67
|
+
/** Replace the slot element's inner content with the md HTML. The slot attr
|
|
68
|
+
* itself stays (hydration-neutral, useful for tests). Exactly one slot must
|
|
69
|
+
* exist and it must be empty — both are emit-pipeline invariants, so a
|
|
70
|
+
* violation is a hard error, not a soft skip. `name` is a generated template
|
|
71
|
+
* name (`[A-Za-z0-9_]` only), so interpolating it into the regex is safe. */
|
|
72
|
+
export declare function spliceMdSlot(template: string, name: string, mdHtml: string): string;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export type MdHydrate = 'load' | 'idle' | 'visible' | 'interaction';
|
|
2
|
+
/** One embedded-component use found in a markdown page (islands only). */
|
|
3
|
+
export interface MdIslandUse {
|
|
4
|
+
name: string;
|
|
5
|
+
/** 0-based per page; the emit step offsets past the wrapper's own islands. */
|
|
6
|
+
instanceLocal: number;
|
|
7
|
+
props: Record<string, unknown>;
|
|
8
|
+
hydrate: MdHydrate;
|
|
9
|
+
csr: boolean;
|
|
10
|
+
/** 1-based line number within the md body (post-frontmatter). */
|
|
11
|
+
line: number;
|
|
12
|
+
}
|
|
13
|
+
/** One behavior-component (x-data) use found in a markdown page. */
|
|
14
|
+
export interface MdBehaviorUse {
|
|
15
|
+
name: string;
|
|
16
|
+
directive: string;
|
|
17
|
+
/** 1-based line number within the md body (post-frontmatter). */
|
|
18
|
+
line: number;
|
|
19
|
+
/** Literal tag props (string/number only — validated at extract time). The
|
|
20
|
+
* emit step inline-substitutes them into the component's compiled body. */
|
|
21
|
+
props: Record<string, unknown>;
|
|
22
|
+
/** The EXACT placeholder host markup injected into the rendered HTML. The
|
|
23
|
+
* emit step (which owns compileJsx) substitutes the fully inlined component
|
|
24
|
+
* body over this exact string. It carries the per-render nonce, so user
|
|
25
|
+
* prose can never collide with it. */
|
|
26
|
+
marker: string;
|
|
27
|
+
}
|
|
28
|
+
export type MdComponentResolution = {
|
|
29
|
+
kind: 'island';
|
|
30
|
+
id: string;
|
|
31
|
+
} | {
|
|
32
|
+
kind: 'behavior';
|
|
33
|
+
directive: string;
|
|
34
|
+
};
|
|
35
|
+
export interface RenderMdPageOptions {
|
|
36
|
+
/** Markdown source, frontmatter already stripped. */
|
|
37
|
+
body: string;
|
|
38
|
+
absPath: string;
|
|
39
|
+
/** `null` → unknown name → renderMdPage throws with `file:line`. */
|
|
40
|
+
resolve: (name: string, line: number) => MdComponentResolution | null;
|
|
41
|
+
}
|
|
42
|
+
interface ShikiLike {
|
|
43
|
+
codeToHtml(code: string, options: {
|
|
44
|
+
lang: string;
|
|
45
|
+
themes: {
|
|
46
|
+
light: string;
|
|
47
|
+
dark: string;
|
|
48
|
+
};
|
|
49
|
+
}): Promise<string>;
|
|
50
|
+
}
|
|
51
|
+
/** Test seam: replaces the dynamic `import('shiki')` and resets all cached state. */
|
|
52
|
+
export declare function __setShikiImporterForTests(importer: (() => Promise<ShikiLike>) | null): void;
|
|
53
|
+
/**
|
|
54
|
+
* Highlights a code fence via shiki (lazy-imported once, cached) with dual
|
|
55
|
+
* CSS-variables themes. shiki absent → escape-only `<pre><code>` fallback and
|
|
56
|
+
* ONE warning per build. Unknown language → silent escape-only fallback.
|
|
57
|
+
*/
|
|
58
|
+
export declare function highlightCode(code: string, lang: string): Promise<string>;
|
|
59
|
+
/**
|
|
60
|
+
* Replaces every minijinja delimiter in md-origin HTML with a string-literal
|
|
61
|
+
* expression that renders back to the original text. Single pass — the
|
|
62
|
+
* replacements themselves are never re-matched. Component-host markup is
|
|
63
|
+
* injected AFTER this pass so its jinja stays live.
|
|
64
|
+
*/
|
|
65
|
+
export declare function neutralizeBraces(html: string): string;
|
|
66
|
+
/**
|
|
67
|
+
* Renders one markdown page body to jinja-safe HTML.
|
|
68
|
+
*
|
|
69
|
+
* Order is load-bearing (locked by tests):
|
|
70
|
+
* 1. extract component-tag lines to opaque placeholders (marked never sees them)
|
|
71
|
+
* 2. render markdown (GFM, heading ids, shiki fences)
|
|
72
|
+
* 3. neutralize jinja braces over the rendered HTML
|
|
73
|
+
* 4. substitute placeholders with host markup — its jinja stays live
|
|
74
|
+
*/
|
|
75
|
+
export declare function renderMdPage(opts: RenderMdPageOptions): Promise<{
|
|
76
|
+
html: string;
|
|
77
|
+
islands: MdIslandUse[];
|
|
78
|
+
behaviors: MdBehaviorUse[];
|
|
79
|
+
}>;
|
|
80
|
+
export {};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { ComponentType } from 'react';
|
|
2
|
+
import type { Route } from '../routes.ts';
|
|
3
|
+
import { type MdFile } from './scan.ts';
|
|
4
|
+
/**
|
|
5
|
+
* Content-addressed jinja template name for an md page:
|
|
6
|
+
* `Md_<sanitized relPath>_<8hex(sha256 relPath)>`.
|
|
7
|
+
*
|
|
8
|
+
* Sanitizes `[^A-Za-z0-9_]` to `_`; the hash (same scheme as
|
|
9
|
+
* `islandChunkBasename` in runtime/islands/chunk-id.ts) keeps two relPaths
|
|
10
|
+
* that sanitize identically (e.g. `a-b.md` vs `a_b.md`) from colliding.
|
|
11
|
+
*/
|
|
12
|
+
export declare function mdTemplateName(relPath: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Maps a content-relative md path to its URL under `prefix`.
|
|
15
|
+
* `index.md` maps to the prefix itself; `guide/index.md` maps to
|
|
16
|
+
* `<prefix>/guide`; `query/where.md` maps to `<prefix>/query/where`.
|
|
17
|
+
* Trailing slashes on `prefix` are normalized away (`/docs/` == `/docs`).
|
|
18
|
+
*/
|
|
19
|
+
export declare function mdUrlPath(relPath: string, prefix: string): string;
|
|
20
|
+
/** Build-time source info attached to every md leaf route. Plain field on the
|
|
21
|
+
* Route node, so it survives `flattenRoutes` into `FlatRoute.chain` (the chain
|
|
22
|
+
* holds the node objects); the emit step filters chains whose leaf has it. */
|
|
23
|
+
export interface MdRouteSource {
|
|
24
|
+
absPath: string;
|
|
25
|
+
relPath: string;
|
|
26
|
+
contentDir: string;
|
|
27
|
+
frontmatter: MdFile['frontmatter'];
|
|
28
|
+
components: Record<string, ComponentType<any>>;
|
|
29
|
+
layoutName?: string;
|
|
30
|
+
}
|
|
31
|
+
/** An md leaf route — an ordinary native Route plus the `__mdSource` marker. */
|
|
32
|
+
export type MdRoute = Route & {
|
|
33
|
+
__mdSource: MdRouteSource;
|
|
34
|
+
};
|
|
35
|
+
export interface MdRoutesOptions {
|
|
36
|
+
/** URL prefix the pages mount under. Default `'/'`. */
|
|
37
|
+
prefix?: string;
|
|
38
|
+
/** Optional layout component — when set, mdRoutes returns ONE parent route
|
|
39
|
+
* `{ path: prefix, Component: layout, children: [...mdLeaves] }`. */
|
|
40
|
+
layout?: ComponentType<any>;
|
|
41
|
+
/** Component-tag registry for `<Name />` tags inside the md body. */
|
|
42
|
+
components?: Record<string, ComponentType<any>>;
|
|
43
|
+
/** Loader for the LAYOUT parent route — runs before each md leaf renders and
|
|
44
|
+
* its data merges into the leaf's context top-down (so it feeds the layout
|
|
45
|
+
* chrome: sidebar, pager, …). Only applies when `layout` is set. Return keys
|
|
46
|
+
* that DON'T collide with the leaf's `__md` head metadata, which must win.
|
|
47
|
+
* Without this, the layout loader had to be attached by mutating the returned
|
|
48
|
+
* route node (`tree.loader = …`) — undocumented surface (was FRAMEWORK-GAPS G2). */
|
|
49
|
+
loader?: Route['loader'];
|
|
50
|
+
}
|
|
51
|
+
/** One frozen-manifest entry (everything route construction needs per page).
|
|
52
|
+
* `contentDir` is only present when the entry belongs to a DIFFERENT content
|
|
53
|
+
* dir than the manifest's top-level `contentDir` (apps mounting two or more
|
|
54
|
+
* `mdRoutes()` dirs share ONE manifest file per dist) — additive, so version-1
|
|
55
|
+
* manifests without it stay readable. */
|
|
56
|
+
export interface MdManifestEntry {
|
|
57
|
+
relPath: string;
|
|
58
|
+
templateName: string;
|
|
59
|
+
urlPath: string;
|
|
60
|
+
frontmatter: MdFile['frontmatter'];
|
|
61
|
+
contentDir?: string;
|
|
62
|
+
}
|
|
63
|
+
export interface MdManifest {
|
|
64
|
+
version: 1;
|
|
65
|
+
contentDir: string;
|
|
66
|
+
entries: MdManifestEntry[];
|
|
67
|
+
}
|
|
68
|
+
export declare const MD_MANIFEST_FILENAME = "md-manifest.json";
|
|
69
|
+
/** Write the frozen md manifest as `<dir>/md-manifest.json` (creates `dir`).
|
|
70
|
+
* Returns the absolute file path written. */
|
|
71
|
+
export declare function writeMdManifest(dir: string, entries: MdManifestEntry[], contentDir: string): string;
|
|
72
|
+
/** Read + schema-check a frozen md manifest. Throws on a missing file, bad
|
|
73
|
+
* JSON, or an unsupported version (fail loudly — the manifest is build output). */
|
|
74
|
+
export declare function readMdManifest(file: string): MdManifest;
|
|
75
|
+
/** The slice of a FlatRoute the manifest derivation reads (structural — avoids
|
|
76
|
+
* a circular import with runtime/md/emit.ts's FlatRouteLike). `Component` is
|
|
77
|
+
* declared (as unknown — never read here) so the all-optional shape shares a
|
|
78
|
+
* property with Route and FlatRoute[] passes TS weak-type detection. */
|
|
79
|
+
export interface MdManifestFlatRouteLike {
|
|
80
|
+
fullPath?: string;
|
|
81
|
+
nativeTemplate?: string;
|
|
82
|
+
chain?: Array<{
|
|
83
|
+
Component?: unknown;
|
|
84
|
+
__mdSource?: MdRouteSource;
|
|
85
|
+
}>;
|
|
86
|
+
}
|
|
87
|
+
/** Derive the frozen md manifest from the FLAT ROUTE TABLE (single source of
|
|
88
|
+
* truth — never re-scan the fs at manifest-write time). Returns `null` when the
|
|
89
|
+
* app has no md routes, so callers can skip ALL md output (the `brust build`
|
|
90
|
+
* byte-identical invariant for md-free apps). With multiple `mdRoutes()` dirs,
|
|
91
|
+
* the first dir seen is the manifest's top-level `contentDir`; entries from
|
|
92
|
+
* other dirs carry a per-entry `contentDir`. */
|
|
93
|
+
export declare function mdManifestFromFlatRoutes(flatRoutes: MdManifestFlatRouteLike[]): {
|
|
94
|
+
contentDir: string;
|
|
95
|
+
entries: MdManifestEntry[];
|
|
96
|
+
} | null;
|
|
97
|
+
/** Turn a directory of `.md` files into native Route entries. Each file gets a
|
|
98
|
+
* synthetic named component (name = its jinja template name, satisfying
|
|
99
|
+
* `validateRoute`'s native checks) and a loader exposing the frontmatter as
|
|
100
|
+
* `{ __md: { title, description } }`. With `layout`, returns ONE parent route
|
|
101
|
+
* at `prefix` whose children carry prefix-relative paths; without, the leaves
|
|
102
|
+
* carry the full prefixed path. */
|
|
103
|
+
export declare function mdRoutes(contentDir: string, opts?: MdRoutesOptions): Route[];
|
|
104
|
+
export interface MdNavItem {
|
|
105
|
+
title: string;
|
|
106
|
+
path: string;
|
|
107
|
+
order?: number;
|
|
108
|
+
}
|
|
109
|
+
export interface MdNavGroup {
|
|
110
|
+
group: string | null;
|
|
111
|
+
items: MdNavItem[];
|
|
112
|
+
}
|
|
113
|
+
/** Navigation model for a content dir: items grouped by `frontmatter.nav.group`
|
|
114
|
+
* (ungrouped pages land in a `group: null` top-level bucket), sorted by
|
|
115
|
+
* `nav.order` (missing order sorts last) then title. Paths use the prefix the
|
|
116
|
+
* dir was mounted under by `mdRoutes` (frozen-manifest urlPaths in a prebuilt
|
|
117
|
+
* run; `'/'` if mdRoutes was never called for the dir). Group order follows
|
|
118
|
+
* the first appearance of each group in the sorted item sequence. */
|
|
119
|
+
export declare function mdNav(contentDir: string): MdNavGroup[];
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface MdFile {
|
|
2
|
+
/** Posix-separated path relative to the content dir, e.g. 'query/where.md'. */
|
|
3
|
+
relPath: string;
|
|
4
|
+
absPath: string;
|
|
5
|
+
frontmatter: {
|
|
6
|
+
title?: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
nav?: {
|
|
9
|
+
group?: string;
|
|
10
|
+
order?: number;
|
|
11
|
+
};
|
|
12
|
+
[k: string]: unknown;
|
|
13
|
+
};
|
|
14
|
+
/** Markdown source after the frontmatter block is stripped. */
|
|
15
|
+
body: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Recursively scans `contentDir` for `.md` files, sorted by `relPath`.
|
|
19
|
+
*
|
|
20
|
+
* Frontmatter is a leading `---` … `---` block parsed as a hand-rolled YAML
|
|
21
|
+
* subset (NO yaml dependency):
|
|
22
|
+
* - `key: value` lines; keys match `[A-Za-z0-9_-]+`
|
|
23
|
+
* - values: double-quoted strings (JSON escapes), single-quoted strings (no
|
|
24
|
+
* escapes), bare strings, numbers, booleans
|
|
25
|
+
* - one-level nested maps via the INLINE-BRACES form only, e.g.
|
|
26
|
+
* `nav: { group: "Getting Started", order: 1 }` — indented child keys are
|
|
27
|
+
* NOT supported and throw
|
|
28
|
+
* - blank lines inside the block are ignored
|
|
29
|
+
*
|
|
30
|
+
* Files without a frontmatter block get `frontmatter: {}` and the whole file
|
|
31
|
+
* as `body`. Malformed frontmatter throws with `<absPath>:<line>`. CRLF line
|
|
32
|
+
* endings are tolerated.
|
|
33
|
+
*/
|
|
34
|
+
export declare function scanMdDir(contentDir: string): MdFile[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function slugifyHeading(text: string): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** The single island-vs-behavior classifier: a component source is a native
|
|
2
|
+
* behavior component iff it has `export const behavior`. Shared by
|
|
3
|
+
* `scanDirectiveComponents` and the md emit step (runtime/md/emit.ts) so the
|
|
4
|
+
* two paths can never diverge. */
|
|
5
|
+
export declare function isBehaviorSource(src: string): boolean;
|
|
6
|
+
/** Deterministic, app-unique directive name = camelCase(basename) + "_" + 8 hex of
|
|
7
|
+
* sha256(cwd-relative path). The SINGLE name contract: chunk filename, runtime
|
|
8
|
+
* registry key, and the compiler-emitted `x-data` all derive from this. */
|
|
9
|
+
export declare function directiveName(absPath: string, projectRoot: string): string;
|
|
10
|
+
/** BFS the local import graph from the routes entry; return directiveName →
|
|
11
|
+
* absolute sourcePath for every file that has `export const behavior`. Throws on
|
|
12
|
+
* two distinct files deriving the same directive name (path-hashed, collision-resistant). */
|
|
13
|
+
export declare function scanDirectiveComponents(routesEntryFile: string): Map<string, string>;
|
|
14
|
+
export interface BuildDirectivesResult {
|
|
15
|
+
outDir: string;
|
|
16
|
+
count: number;
|
|
17
|
+
/** All emitted filenames (basenames): `_directives.js` (the shared runtime) plus
|
|
18
|
+
* one `<name>.directive.js` per component. Callers mirror these into .brust/islands. */
|
|
19
|
+
files: string[];
|
|
20
|
+
}
|
|
21
|
+
/** Build the shared directive runtime (`_directives.js`, loaded on every native page)
|
|
22
|
+
* PLUS one `<name>.directive.js` chunk per component (loaded ON DEMAND by the runtime
|
|
23
|
+
* when an `x-data="<name>"` appears). Splitting behaviors into per-component chunks
|
|
24
|
+
* keeps a page from downloading the JS of components it never renders; the runtime
|
|
25
|
+
* dynamically `import()`s a chunk the first time its name is seen (initial or post
|
|
26
|
+
* SPA-nav). Each chunk self-registers via the global `register` handle (so it needs
|
|
27
|
+
* no runtime import). React must not leak into any chunk — asserted per file. */
|
|
28
|
+
export declare function buildDirectives(components: Map<string, string>, options: {
|
|
29
|
+
outDir: string;
|
|
30
|
+
}): Promise<BuildDirectivesResult>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export type Instance = Record<string, unknown>;
|
|
2
|
+
/** What a `behavior` receives. Beyond `el`/`props`, two lifecycle helpers whose
|
|
3
|
+
* teardown auto-joins the component's disposer set (run on unmount / SPA-nav swap):
|
|
4
|
+
* - `effect(fn)` — a reactive effect (React `useEffect` semantics: `fn` may return
|
|
5
|
+
* a cleanup that runs before each re-run and on unmount). Use it
|
|
6
|
+
* for side-effects on signal change (sync localStorage, the DOM
|
|
7
|
+
* outside the component, timers). Returns the disposer too.
|
|
8
|
+
* - `onCleanup(fn)` — register a one-shot teardown for unmount (e.g. removeEventListener). */
|
|
9
|
+
export interface BehaviorCtx {
|
|
10
|
+
el: HTMLElement;
|
|
11
|
+
props: unknown;
|
|
12
|
+
effect: (fn: () => void | (() => void)) => () => void;
|
|
13
|
+
onCleanup: (fn: () => void) => void;
|
|
14
|
+
}
|
|
15
|
+
export type Behavior = (ctx: BehaviorCtx) => Instance;
|
|
16
|
+
/** Register a component behavior under `name`. Called by `<name>.directive.js` chunks
|
|
17
|
+
* via the global handle below (they do NOT import this module — keeps each chunk to
|
|
18
|
+
* just its behavior, with the runtime shared as the single `_directives.js` copy). */
|
|
19
|
+
export declare function register(name: string, behavior: Behavior): void;
|
|
20
|
+
/** Scan `root` (default: document) for [x-data], mount each, and (once) attach a
|
|
21
|
+
* MutationObserver for dynamic mount/dispose. Idempotent. NOTE: `root` scopes the
|
|
22
|
+
* INITIAL scan only; the observer always watches the global `document.body` (one
|
|
23
|
+
* observer per document handles every later mount/dispose, incl. SPA-nav swaps),
|
|
24
|
+
* plus one observer per discovered OPEN shadow root (a body observer cannot see
|
|
25
|
+
* mutations inside a shadow tree). */
|
|
26
|
+
export declare function start(root?: ParentNode): void;
|
|
27
|
+
export interface ForExpr {
|
|
28
|
+
itemName: string;
|
|
29
|
+
indexName?: string;
|
|
30
|
+
listPath: string;
|
|
31
|
+
keyPaths?: string[];
|
|
32
|
+
}
|
|
33
|
+
/** Parse an x-for expression. Grammar:
|
|
34
|
+
* (item[, index]) in listPath [by keyPath, keyPath, ...]
|
|
35
|
+
* Returns null on malformed input (caller warns + skips). */
|
|
36
|
+
export declare function parseFor(raw: string): ForExpr | null;
|
|
37
|
+
/** Write `value` into the signal at `path`, unwrapping intermediate signal/computed
|
|
38
|
+
* hops like `read()` (resolveRaw does NOT unwrap intermediates and cannot be the base
|
|
39
|
+
* for multi-hop paths). The LEAF is never called: `isSignal(leaf)` → `.set(value)`,
|
|
40
|
+
* else warn once and skip. */
|
|
41
|
+
export declare function writePath(scope: Instance, path: string, value: unknown): void;
|
|
42
|
+
/** Apply a bound value to a DOM attr/property. class → className; boolean props →
|
|
43
|
+
* property (when present) + attribute presence; value → property; else attribute. */
|
|
44
|
+
export declare function setBound(el: HTMLElement, attr: string, value: unknown): void;
|
|
45
|
+
/** Walk a dotted member path against `scope`, unwrapping a signal/computed at EVERY
|
|
46
|
+
* hop (so an intermediate item-signal is tracked by `effect`); at the LEAF also call
|
|
47
|
+
* a plain function to obtain its value (this read is what `effect` tracks). */
|
|
48
|
+
export declare function read(scope: Instance, path: string): unknown;
|
|
49
|
+
/** Resolve a dotted path WITHOUT calling the leaf (for x-on handlers). */
|
|
50
|
+
export declare function resolveRaw(scope: Instance, path: string): unknown;
|
|
51
|
+
/** Resolve `path` on `scope` and, if a function, call it with the event. */
|
|
52
|
+
export declare function callMethod(scope: Instance, path: string, event: Event): void;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { NavPhase, NavState, NavStore } from './store.ts';
|
|
2
|
+
export { nav, getNavState, subscribe, onBeforeNavigate, onNavigate, onNavigateError, } from './store.ts';
|
|
3
|
+
export { installActiveNav } from './active-nav.ts';
|
|
4
|
+
export { navigate, buildSearch } from './navigate.ts';
|
|
5
|
+
export type { NavigateOptions, QueryInit, QueryValue } from './navigate.ts';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type QueryValue = string | number | boolean;
|
|
2
|
+
export type QueryInit = Record<string, QueryValue | null | undefined | ReadonlyArray<QueryValue>>;
|
|
3
|
+
export interface NavigateOptions {
|
|
4
|
+
query?: QueryInit;
|
|
5
|
+
replace?: boolean;
|
|
6
|
+
}
|
|
7
|
+
/** Serialize a query object to a `?...` string (or '' when empty). Pure, DOM-free. */
|
|
8
|
+
export declare function buildSearch(query: QueryInit): string;
|
|
9
|
+
/** Programmatic SPA navigation. Resolves `path` against the current location,
|
|
10
|
+
* merges `options.query` over any query already in `path` (each key replaces all
|
|
11
|
+
* base occurrences; null/undefined deletes), then delegates to the registered
|
|
12
|
+
* navigator (full SPA swap + nav-state lifecycle). No navigator registered (no
|
|
13
|
+
* islands bootstrap) → full-document load so the call still navigates. */
|
|
14
|
+
export declare function navigate(path: string, options?: NavigateOptions): Promise<void>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type NavState } from './store.ts';
|
|
2
|
+
/** Watch the navigation state from a React island.
|
|
3
|
+
*
|
|
4
|
+
* ```tsx
|
|
5
|
+
* import { useNav } from 'brustjs/client'
|
|
6
|
+
* function Crumb() {
|
|
7
|
+
* const { path, phase } = useNav()
|
|
8
|
+
* return <span>{phase === 'loading' ? 'Loading…' : path}</span>
|
|
9
|
+
* }
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* The third arg (server snapshot) is the same getter — on the server the store
|
|
13
|
+
* holds its idle defaults (no `location`), so SSR renders the idle state and the
|
|
14
|
+
* client hydrates the real one on mount. */
|
|
15
|
+
export declare function useNav(): NavState;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type Signal } from '../store/signal.ts';
|
|
2
|
+
export type NavPhase = 'idle' | 'loading' | 'success' | 'error';
|
|
3
|
+
export interface NavState {
|
|
4
|
+
path: string;
|
|
5
|
+
search: string;
|
|
6
|
+
phase: NavPhase;
|
|
7
|
+
error: Error | null;
|
|
8
|
+
from: string | null;
|
|
9
|
+
to: string | null;
|
|
10
|
+
}
|
|
11
|
+
export interface NavStore {
|
|
12
|
+
path: Signal<string>;
|
|
13
|
+
search: Signal<string>;
|
|
14
|
+
phase: Signal<NavPhase>;
|
|
15
|
+
error: Signal<Error | null>;
|
|
16
|
+
from: Signal<string | null>;
|
|
17
|
+
to: Signal<string | null>;
|
|
18
|
+
}
|
|
19
|
+
type BeforeCb = (e: {
|
|
20
|
+
from: string;
|
|
21
|
+
to: string;
|
|
22
|
+
}) => void;
|
|
23
|
+
type NavCb = (e: NavState) => void;
|
|
24
|
+
type ErrorCb = (e: {
|
|
25
|
+
to: string;
|
|
26
|
+
error: Error;
|
|
27
|
+
}) => void;
|
|
28
|
+
export declare const nav: NavStore;
|
|
29
|
+
export declare function getNavState(): NavState;
|
|
30
|
+
export declare function subscribe(cb: NavCb): () => void;
|
|
31
|
+
export declare function onBeforeNavigate(cb: BeforeCb): () => void;
|
|
32
|
+
export declare function onNavigate(cb: NavCb): () => void;
|
|
33
|
+
export declare function onNavigateError(cb: ErrorCb): () => void;
|
|
34
|
+
export declare function __navInit(path: string, search: string): void;
|
|
35
|
+
export declare function __navStart(toPath: string, _toSearch: string): void;
|
|
36
|
+
export declare function __navCommit(toPath: string, toSearch: string): void;
|
|
37
|
+
export declare function __navError(toPath: string, error: unknown): void;
|
|
38
|
+
/** Register the SPA navigator implementation (bootstrap's swap). Last-write-wins;
|
|
39
|
+
* re-registration with the same closure (HMR / multiple chunks) is idempotent. */
|
|
40
|
+
export declare function registerNavigator(fn: (url: URL, replace: boolean) => Promise<void>): void;
|
|
41
|
+
/** @internal — the registered navigator, or null when no islands bootstrap loaded. */
|
|
42
|
+
export declare function _getNavigator(): ((url: URL, replace: boolean) => Promise<void>) | null;
|
|
43
|
+
export declare function __resetNavForTest(): void;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ComponentType, type ReactNode } from 'react';
|
|
2
|
+
/** Render a React element to a single HTML string, awaiting all Suspense
|
|
3
|
+
* boundaries via onAllReady. Used by navigationBranch — renderToString
|
|
4
|
+
* would only capture the shell + fallbacks, while renderBranchStreaming
|
|
5
|
+
* is for the streaming render path. */
|
|
6
|
+
export declare function renderToAwaitedString(element: ReactNode): Promise<string>;
|
|
7
|
+
export interface RenderFragmentOpts {
|
|
8
|
+
/** Request cookies visible to cookies()/session helpers inside the tree. */
|
|
9
|
+
cookies?: Record<string, string>;
|
|
10
|
+
}
|
|
11
|
+
/** Render a component to an HTML string with framework contexts scoped:
|
|
12
|
+
* request scope (cookies) ∘ request cache (cachedFetch/dedupe) ∘ store
|
|
13
|
+
* context (fresh per call — no cross-call leakage; concurrency-safe via
|
|
14
|
+
* AsyncLocalStorage). Suspense supported — the promise resolves when the
|
|
15
|
+
* whole tree is ready, never with fallbacks. Static HTML only: no island
|
|
16
|
+
* hydration markers/scripts (an `<Island>` inside the fragment SSRs its
|
|
17
|
+
* component but ships no hydration), no head/CSS collection, no streaming.
|
|
18
|
+
* Native/jinja fragments are served by `templates.render` instead.
|
|
19
|
+
* Errors reject with the component's error — no error-boundary semantics. */
|
|
20
|
+
export declare function renderFragment<P extends object>(Component: ComponentType<P>, props: P, opts?: RenderFragmentOpts): Promise<string>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** @internal — used by tests to reset the warn-once flag. */
|
|
2
|
+
export declare function _resetWarnedForTests(): void;
|
|
3
|
+
/** Splice `snippet` (a full `<script>…</script>`) into `body` immediately
|
|
4
|
+
* before the first `</head>` (case-insensitive on the four ASCII letters
|
|
5
|
+
* only). Returns the original body untouched if `snippet` is null/empty or if
|
|
6
|
+
* `</head>` is absent. */
|
|
7
|
+
export declare function injectActionPrefix(body: Uint8Array, snippet: string | null): Uint8Array;
|
|
8
|
+
export declare function configureActionPrefixSnippet(s: string | null): void;
|
|
9
|
+
export declare function getActionPrefixSnippet(): string | null;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** @internal — used by the unit test suite to reset the warn-once flag. */
|
|
2
|
+
export declare function _resetWarnedForTests(): void;
|
|
3
|
+
/** Splice `<link rel="stylesheet" href="...">` tags into `body` immediately
|
|
4
|
+
* before the first occurrence of `</head>` (case-insensitive). Returns the
|
|
5
|
+
* original body untouched if `hrefs` is empty or if `</head>` is absent
|
|
6
|
+
* (warns once in the latter case). Renderer calls this on the first chunk
|
|
7
|
+
* only — see spec S"SSR <link> injection". */
|
|
8
|
+
export declare function injectCssLink(body: Uint8Array, hrefs: readonly string[]): Uint8Array;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** @internal — used by tests to reset the warn-once flag. */
|
|
2
|
+
export declare function _resetWarnedForTests(): void;
|
|
3
|
+
/** Splice `snippet` into `body` immediately before the first `</head>`
|
|
4
|
+
* (case-insensitive on the four ASCII letters only). Returns the original body
|
|
5
|
+
* untouched if `snippet` is null/empty or if `</head>` is absent. */
|
|
6
|
+
export declare function injectDevClient(body: Uint8Array, snippet: string | null): Uint8Array;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Seed from generator.json at boot (main + worker). null → no injection. */
|
|
2
|
+
export declare function configureGeneratorMeta(meta: string | null): void;
|
|
3
|
+
export declare function getGeneratorMeta(): string | null;
|
|
4
|
+
/** Splice `metaTag` immediately before the first `</head>` (case-insensitive).
|
|
5
|
+
* No </head> in the chunk, empty tag, or an existing generator meta → body
|
|
6
|
+
* returned untouched. Byte-level (no decode) — safe with multibyte content. */
|
|
7
|
+
export declare function injectGeneratorMeta(body: Uint8Array, metaTag: string | null): Uint8Array;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** @internal — used by tests to reset the warn-once flag. */
|
|
2
|
+
export declare function _resetWarnedForTests(): void;
|
|
3
|
+
/** Build the combined `<script type="application/json">` blob for every touched
|
|
4
|
+
* store. Returns '' when the snapshot is null/empty. */
|
|
5
|
+
export declare function buildStoreScripts(snap: Record<string, Record<string, unknown>> | null): string;
|
|
6
|
+
/** Splice the store snapshot `<script>`(s) into `body` immediately before the
|
|
7
|
+
* first `</head>`. Returns the original body untouched if the snapshot is
|
|
8
|
+
* null/empty or if `</head>` is absent. */
|
|
9
|
+
export declare function injectBrustStore(body: Uint8Array, snap: Record<string, Record<string, unknown>> | null): Uint8Array;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type ReactNode, type ComponentType } from 'react';
|
|
2
|
+
export interface RenderBranchStreamingArgs {
|
|
3
|
+
element: ReactNode;
|
|
4
|
+
/** The render's SAB sub-view (whole buffer at slots=1). All chunk encodes
|
|
5
|
+
* write at offset 0 of this view → automatically the slot's base. */
|
|
6
|
+
view: Uint8Array;
|
|
7
|
+
/** The render slot index, passed to every napi.renderChunk* call so Rust
|
|
8
|
+
* reads/writes the matching SAB sub-region. Default 0 (single-slot path). */
|
|
9
|
+
slot?: number;
|
|
10
|
+
workerId: bigint;
|
|
11
|
+
napi: {
|
|
12
|
+
renderChunk: (workerId: bigint, slot: number, len: number, sabBytes: Uint8Array) => Promise<void>;
|
|
13
|
+
renderChunkFinal: (workerId: bigint, slot: number, len: number, sabBytes: Uint8Array) => Promise<void>;
|
|
14
|
+
};
|
|
15
|
+
errorBoundary: ComponentType<{
|
|
16
|
+
error: Error;
|
|
17
|
+
}>;
|
|
18
|
+
/** Status for the successful (non-error) render. Default 200. Used by
|
|
19
|
+
* middleware-wrapped routes that want to set a non-200 status before
|
|
20
|
+
* the component runs (e.g. 201 from a server action redirect). */
|
|
21
|
+
status?: number;
|
|
22
|
+
/** Extra response headers injected by middleware (e.g. `x-render-ms`).
|
|
23
|
+
* Merged into the meta envelope's `headers` map. */
|
|
24
|
+
headers?: Record<string, string>;
|
|
25
|
+
/** The matched route's fullPath (e.g. '/' or '/blog/{slug}'). Used to
|
|
26
|
+
* combine global CSS hrefs with per-route CSS hrefs before injection. */
|
|
27
|
+
routePath?: string;
|
|
28
|
+
/** Per-request store snapshot collected after loaders run. Injected as a
|
|
29
|
+
* `<script data-brust-store="…">` blob before `</head>` (buffering) or into
|
|
30
|
+
* the streaming first-chunk prepend. Null/undefined → no injection. */
|
|
31
|
+
storeSnapshot?: Record<string, Record<string, unknown>> | null;
|
|
32
|
+
/** Inject the islands importmap + bootstrap even when no <Island> rendered.
|
|
33
|
+
* SSG fallback shells need the bootstrap (client takeover runtime) on pages
|
|
34
|
+
* that may have zero real islands. */
|
|
35
|
+
forceIslands?: boolean;
|
|
36
|
+
}
|
|
37
|
+
/** JSON.stringify the per-chunk meta. Defaults match the renderToString
|
|
38
|
+
* path so single-chunk responses keep their existing wire shape. */
|
|
39
|
+
export declare function makeMeta(opts: {
|
|
40
|
+
status: number;
|
|
41
|
+
streaming: boolean;
|
|
42
|
+
contentType?: string;
|
|
43
|
+
headers?: Record<string, string>;
|
|
44
|
+
}): string;
|
|
45
|
+
export declare function renderBranchStreaming(args: RenderBranchStreamingArgs): Promise<void>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface RequestScope {
|
|
2
|
+
ctx: Map<string, unknown>;
|
|
3
|
+
reqCookies: Record<string, string>;
|
|
4
|
+
setCookies: string[];
|
|
5
|
+
}
|
|
6
|
+
/** Open a per-request scope. `reqCookies` is the parsed incoming Cookie header
|
|
7
|
+
* (BrustRequest.cookies). Staged Set-Cookie strings accumulate in `setCookies`
|
|
8
|
+
* and are flushed by routes.ts onto the response headers. */
|
|
9
|
+
export declare function runInRequestScope<T>(reqCookies: Record<string, string>, fn: () => T): T;
|
|
10
|
+
/** Per-request scratch Map for apps building request-scoped state on top of the
|
|
11
|
+
* module-global store. Throws when called outside a request scope. */
|
|
12
|
+
export declare function getRequestContext(): Map<string, unknown>;
|
|
13
|
+
/** Internal — the active scope (or undefined). Used by cookies.ts and the
|
|
14
|
+
* routes.ts flush. Not re-exported from the package entry. */
|
|
15
|
+
export declare function __scope(): RequestScope | undefined;
|
|
16
|
+
export {};
|