@moku-labs/web 0.6.0 → 1.0.0
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 +227 -125
- package/dist/browser.d.mts +524 -111
- package/dist/browser.mjs +1565 -798
- package/dist/{convention-X3zLTlJ8.mjs → convention-CepUwWmT.mjs} +18 -1
- package/dist/{convention-Dr8jxG70.cjs → convention-krwh7Y6Q.cjs} +23 -0
- package/dist/index.cjs +2906 -1908
- package/dist/index.d.cts +365 -176
- package/dist/index.d.mts +366 -177
- package/dist/index.mjs +2903 -1907
- package/dist/{writer-DAF0pM25.cjs → writer-DV5hWB2i.cjs} +25 -27
- package/dist/{writer-BcWqa_7I.mjs → writer-Dc_lx22j.mjs} +25 -27
- package/package.json +1 -1
package/dist/browser.d.mts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { EmitFn } from "@moku-labs/core";
|
|
2
2
|
import { ComponentChildren, VNode } from "preact";
|
|
3
|
+
import { BundledTheme, ThemeRegistrationAny } from "shiki";
|
|
4
|
+
import { Pluggable, Processor } from "unified";
|
|
3
5
|
|
|
4
6
|
//#region src/plugins/log/types.d.ts
|
|
5
|
-
declare namespace types_d_exports$
|
|
7
|
+
declare namespace types_d_exports$4 {
|
|
6
8
|
export { ExpectChain, LogApi, LogConfig, LogEntry, LogLevel, LogSink, LogState };
|
|
7
9
|
}
|
|
8
10
|
/**
|
|
@@ -142,7 +144,7 @@ type LogApi = {
|
|
|
142
144
|
addSink(sink: LogSink): void; /** Clear all recorded entries while keeping registered sinks. */
|
|
143
145
|
reset(): void;
|
|
144
146
|
};
|
|
145
|
-
declare namespace types_d_exports$
|
|
147
|
+
declare namespace types_d_exports$2 {
|
|
146
148
|
export { EnvApi, EnvConfig, EnvProvider, EnvState, EnvVarSpec };
|
|
147
149
|
}
|
|
148
150
|
/**
|
|
@@ -153,8 +155,9 @@ declare namespace types_d_exports$1 {
|
|
|
153
155
|
*
|
|
154
156
|
* Providers are walked in array order during resolution; the first provider to
|
|
155
157
|
* return a non-`undefined` (and non-empty-string) value for a key wins. `load()`
|
|
156
|
-
* is called once per resolution at `onInit` time
|
|
157
|
-
*
|
|
158
|
+
* is called exactly once per resolution at `onInit` time, after which both env
|
|
159
|
+
* maps are frozen. A provider like {@link cloudflareBindings} reads `globalThis`
|
|
160
|
+
* at that single `onInit` call (not per request).
|
|
158
161
|
*
|
|
159
162
|
* @example
|
|
160
163
|
* ```ts
|
|
@@ -225,7 +228,8 @@ type EnvConfig = {
|
|
|
225
228
|
/**
|
|
226
229
|
* Ordered list of value sources. The first provider yielding a non-`undefined`
|
|
227
230
|
* (and non-empty-string) value for a key wins. The plugin's own spec default is
|
|
228
|
-
* `[]`; the
|
|
231
|
+
* `[]`; the consumer supplies the providers per target (`[dotenv(), processEnv()]`
|
|
232
|
+
* on Node) — only the `/browser` entry pre-wires `browserEnv()` out of the box.
|
|
229
233
|
*/
|
|
230
234
|
providers: EnvProvider[];
|
|
231
235
|
/**
|
|
@@ -334,12 +338,25 @@ declare function browserEnv(options?: {
|
|
|
334
338
|
declare const envPlugin: import("@moku-labs/core").CorePluginInstance<"env", EnvConfig, EnvState, EnvApi>;
|
|
335
339
|
//#endregion
|
|
336
340
|
//#region src/config.d.ts
|
|
341
|
+
/**
|
|
342
|
+
* Deployment stage. Drives content draft visibility — drafts are suppressed
|
|
343
|
+
* only in `"production"`; `"development"` and `"test"` both surface them.
|
|
344
|
+
*/
|
|
345
|
+
type Stage = "production" | "development" | "test";
|
|
337
346
|
/**
|
|
338
347
|
* Global framework configuration. Minimal by design — per-plugin config is
|
|
339
348
|
* resolved via `pluginConfigs`, not merged here.
|
|
340
349
|
*/
|
|
341
|
-
type Config$
|
|
342
|
-
/**
|
|
350
|
+
type Config$5 = {
|
|
351
|
+
/** Deployment stage. Drives content draft visibility (drafts hidden only in `"production"`). */stage: Stage;
|
|
352
|
+
/**
|
|
353
|
+
* Render mode — the single SSG/DATA/SPA switch, read by the router (`ctx.global`)
|
|
354
|
+
* and consumed by `build`/`spa` via `router.mode()`.
|
|
355
|
+
* - `"ssg"` static generation only (no client router emitted).
|
|
356
|
+
* - `"spa"` client-side routing only.
|
|
357
|
+
* - `"hybrid"` static HTML + client navigation overlay (default).
|
|
358
|
+
*/
|
|
359
|
+
mode: "ssg" | "spa" | "hybrid";
|
|
343
360
|
};
|
|
344
361
|
/**
|
|
345
362
|
* Framework event contract. Empty base — each plugin declares its own events
|
|
@@ -373,7 +390,7 @@ type Events = {};
|
|
|
373
390
|
* });
|
|
374
391
|
* ```
|
|
375
392
|
*/
|
|
376
|
-
type Config$
|
|
393
|
+
type Config$4 = {
|
|
377
394
|
/** Human-readable site name. Used in feeds, og:site_name, and titles. MUST be non-empty. */name: string; /** Absolute base URL of the site, e.g. "https://blog.dev". MUST be a valid absolute URL (http/https). */
|
|
378
395
|
url: string; /** Default author/byline for the site. Used in feeds and article author meta. */
|
|
379
396
|
author: string; /** Short site description. Used in feeds, the default meta description, and og:description fallbacks. */
|
|
@@ -383,7 +400,7 @@ type Config$3 = {
|
|
|
383
400
|
* Public API of the site plugin — read-only accessors over frozen global
|
|
384
401
|
* site metadata, plus canonical URL construction.
|
|
385
402
|
*/
|
|
386
|
-
type Api$
|
|
403
|
+
type Api$4 = {
|
|
387
404
|
/**
|
|
388
405
|
* Returns the configured site name.
|
|
389
406
|
*
|
|
@@ -444,7 +461,7 @@ type Api$3 = {
|
|
|
444
461
|
* @file i18n plugin — public type definitions (Config + Api).
|
|
445
462
|
*/
|
|
446
463
|
/**
|
|
447
|
-
* i18n plugin configuration.
|
|
464
|
+
* i18n plugin configuration.
|
|
448
465
|
*
|
|
449
466
|
* `locales` and `defaultLocale` are required and validated in `onInit`. The
|
|
450
467
|
* optional maps default to empty objects so every lookup method is total —
|
|
@@ -461,7 +478,7 @@ type Api$3 = {
|
|
|
461
478
|
* }
|
|
462
479
|
* ```
|
|
463
480
|
*/
|
|
464
|
-
type Config$
|
|
481
|
+
type Config$3 = {
|
|
465
482
|
readonly locales: readonly string[];
|
|
466
483
|
readonly defaultLocale: string;
|
|
467
484
|
readonly localeNames?: Record<string, string>;
|
|
@@ -472,7 +489,7 @@ type Config$2 = {
|
|
|
472
489
|
* Public API of the i18n plugin. Injected as `app.i18n` and reachable from
|
|
473
490
|
* other plugins via `ctx.require(i18nPlugin)`.
|
|
474
491
|
*/
|
|
475
|
-
type Api$
|
|
492
|
+
type Api$3 = {
|
|
476
493
|
/**
|
|
477
494
|
* Returns the configured supported locales in declared order.
|
|
478
495
|
*
|
|
@@ -540,8 +557,8 @@ type Api$2 = {
|
|
|
540
557
|
*/
|
|
541
558
|
t(locale: string, key: string): string;
|
|
542
559
|
};
|
|
543
|
-
declare namespace types_d_exports$
|
|
544
|
-
export { Api$
|
|
560
|
+
declare namespace types_d_exports$5 {
|
|
561
|
+
export { Api$2 as Api, ClientRoute, CompileInput, CompiledRoute, Config$2 as Config, ExtractApi$1 as ExtractApi, ExtractRouteParams, ExtractSegmentParameter, GenerateContext, HeadConfig$1 as HeadConfig, LayoutContext, LoadContext, MatcherTable, Prettify, RouteBuilder, RouteContext, RouteDefinition, RouteHandlers, RouteMap, RouteRequire, RouteState, RouterApi, RouterConfig, RouterState, State$2 as State, TypedRoute, Urls };
|
|
545
562
|
}
|
|
546
563
|
/**
|
|
547
564
|
* Param contribution of a single path segment. `{name:?}` / `:name?` → optional;
|
|
@@ -580,6 +597,84 @@ interface RouteContext<S extends RouteState> {
|
|
|
580
597
|
readonly data: S["data"];
|
|
581
598
|
/** Active locale for this render. */
|
|
582
599
|
readonly locale: string;
|
|
600
|
+
/**
|
|
601
|
+
* Build a link to a named route by pattern substitution — the framework delivers
|
|
602
|
+
* this on the context (same output as `app.router.toUrl`), so render/head build
|
|
603
|
+
* links with no `app`/`createUrls` reference. Works identically at build and on
|
|
604
|
+
* client navigation.
|
|
605
|
+
*/
|
|
606
|
+
readonly url: (name: string, params?: Record<string, string>) => string;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Structural extraction of a plugin instance's public API from its `_phantom`
|
|
610
|
+
* carrier (mirrors the kernel's `ExtractApi` / spec/09 §3). Lets the loader/generator
|
|
611
|
+
* `require` resolve a plugin instance to its typed public API.
|
|
612
|
+
*
|
|
613
|
+
* @example
|
|
614
|
+
* type ContentApi = ExtractApi<typeof contentPlugin>;
|
|
615
|
+
*/
|
|
616
|
+
type ExtractApi$1<PluginCandidate> = PluginCandidate extends {
|
|
617
|
+
readonly _phantom: {
|
|
618
|
+
readonly api: infer PluginApi;
|
|
619
|
+
};
|
|
620
|
+
} ? PluginApi : never;
|
|
621
|
+
/**
|
|
622
|
+
* Generic, instance-only `require` handed to a route's `.load()` / `.generate()` —
|
|
623
|
+
* the SAME shape as the kernel's `RequireFunction` (spec/08 §7) and the build's
|
|
624
|
+
* `PhaseRequire`, so the build forwards its own `ctx.require` straight through.
|
|
625
|
+
* Resolves a plugin INSTANCE to its public API; the consumer supplies the instance
|
|
626
|
+
* (e.g. `ctx.require(contentPlugin)`), so the router never names a sibling plugin.
|
|
627
|
+
*
|
|
628
|
+
* @example
|
|
629
|
+
* const content = ctx.require(contentPlugin); // ContentApi
|
|
630
|
+
*/
|
|
631
|
+
type RouteRequire = <PluginCandidate extends {
|
|
632
|
+
readonly name: string;
|
|
633
|
+
readonly spec: unknown;
|
|
634
|
+
readonly _phantom: {
|
|
635
|
+
readonly config: unknown;
|
|
636
|
+
readonly state: unknown;
|
|
637
|
+
readonly api: unknown;
|
|
638
|
+
readonly events: Record<string, unknown>;
|
|
639
|
+
};
|
|
640
|
+
}>(plugin: PluginCandidate) => ExtractApi$1<PluginCandidate>;
|
|
641
|
+
/**
|
|
642
|
+
* Build-time context handed to a route's `.load()`. Carries the resolved path
|
|
643
|
+
* `params` and active `locale`, plus the spec's `require`/`has` so a loader pulls
|
|
644
|
+
* sibling plugin APIs the canonical way — `ctx.require(contentPlugin)` — with no
|
|
645
|
+
* module global and no router→content coupling. Loaders run ONLY at build time
|
|
646
|
+
* (never on the client), inside the build plugin's context, so `require`/`has` are
|
|
647
|
+
* always live here.
|
|
648
|
+
*
|
|
649
|
+
* @example
|
|
650
|
+
* route("/{slug}/").load((ctx) => ctx.require(contentPlugin).load(ctx.params.slug, ctx.locale));
|
|
651
|
+
*/
|
|
652
|
+
interface LoadContext<S extends RouteState> {
|
|
653
|
+
/** Resolved path params for this page instance. */
|
|
654
|
+
readonly params: S["params"];
|
|
655
|
+
/** Active locale this page instance is built for. */
|
|
656
|
+
readonly locale: string;
|
|
657
|
+
/** Resolve a sibling plugin instance to its public API (spec/08 §7). */
|
|
658
|
+
readonly require: RouteRequire;
|
|
659
|
+
/** Whether a plugin is registered (by name) — branch on OPTIONAL plugins. */
|
|
660
|
+
readonly has: (name: string) => boolean;
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Build-time context handed to a route's `.generate()` — the static-param producer.
|
|
664
|
+
* Carries the active `locale` plus `require`/`has` (no `params` yet — `.generate()`
|
|
665
|
+
* PRODUCES the param sets). Same build-only guarantee as {@link LoadContext}.
|
|
666
|
+
*
|
|
667
|
+
* @example
|
|
668
|
+
* route("/{slug}/").generate(async (ctx) =>
|
|
669
|
+
* [...(await ctx.require(contentPlugin).loadAll()).get(ctx.locale) ?? []].map((a) => ({ slug: a.computed.slug })));
|
|
670
|
+
*/
|
|
671
|
+
interface GenerateContext {
|
|
672
|
+
/** Active locale to enumerate param sets for. */
|
|
673
|
+
readonly locale: string;
|
|
674
|
+
/** Resolve a sibling plugin instance to its public API (spec/08 §7). */
|
|
675
|
+
readonly require: RouteRequire;
|
|
676
|
+
/** Whether a plugin is registered (by name). */
|
|
677
|
+
readonly has: (name: string) => boolean;
|
|
583
678
|
}
|
|
584
679
|
/**
|
|
585
680
|
* Context handed to a route's `.layout()` wrapper: the render-time
|
|
@@ -615,7 +710,7 @@ interface RouteBuilder<S extends RouteState> extends RouteDefinition {
|
|
|
615
710
|
* Attach a data loader; widens the data generic (and ONLY the data generic) so
|
|
616
711
|
* `.render()`/`.head()` see its return. Path params are preserved unchanged.
|
|
617
712
|
*/
|
|
618
|
-
load<D>(loader: (
|
|
713
|
+
load<D>(loader: (ctx: LoadContext<S>) => D | Promise<D>): RouteBuilder<{
|
|
619
714
|
readonly params: S["params"];
|
|
620
715
|
readonly data: Awaited<D>;
|
|
621
716
|
}>;
|
|
@@ -629,19 +724,10 @@ interface RouteBuilder<S extends RouteState> extends RouteDefinition {
|
|
|
629
724
|
layout(component: (ctx: LayoutContext<S>, children: ComponentChildren) => VNode): RouteBuilder<S>;
|
|
630
725
|
/** Attach the page render handler. */
|
|
631
726
|
render(handler: (ctx: RouteContext<S>) => VNode): RouteBuilder<S>;
|
|
632
|
-
/**
|
|
633
|
-
* Attach the client-side validation gate: parse the raw `unknown` fetched from
|
|
634
|
-
* the persisted data file back into this route's data type `S["data"]`. Runs at
|
|
635
|
-
* the trust boundary before `render` on the client (and MUST return `S["data"]`,
|
|
636
|
-
* so a mismatched schema is a compile error). Throw inside it to reject malformed
|
|
637
|
-
* data — `spa` then falls back to HTML-over-fetch. Use a hand guard or any
|
|
638
|
-
* Standard-Schema validator (zod/valibot/arktype).
|
|
639
|
-
*/
|
|
640
|
-
parse(handler: (raw: unknown) => S["data"]): RouteBuilder<S>;
|
|
641
727
|
/** Attach the head/SEO handler. */
|
|
642
728
|
head(handler: (ctx: RouteContext<S>) => HeadConfig$1): RouteBuilder<S>;
|
|
643
|
-
/** Attach a static-generation param producer. */
|
|
644
|
-
generate(handler: (
|
|
729
|
+
/** Attach a static-generation param producer (receives a {@link GenerateContext}). */
|
|
730
|
+
generate(handler: (ctx: GenerateContext) => S["params"][] | Promise<S["params"][]>): RouteBuilder<S>;
|
|
645
731
|
/**
|
|
646
732
|
* Attach an arbitrary metadata bag. The bag MUST be JSON-serializable: it is
|
|
647
733
|
* projected verbatim into `clientManifest()` and shipped to the browser.
|
|
@@ -654,18 +740,16 @@ interface RouteBuilder<S extends RouteState> extends RouteDefinition {
|
|
|
654
740
|
}
|
|
655
741
|
/** Build-only handler bag captured by a `RouteBuilder` (consumed by `build` via `manifest()`). */
|
|
656
742
|
interface RouteHandlers {
|
|
657
|
-
/** Data loader. */
|
|
658
|
-
readonly load?: (
|
|
743
|
+
/** Data loader (receives a {@link LoadContext}: params + locale + require/has). */
|
|
744
|
+
readonly load?: (ctx: LoadContext<RouteState>) => unknown;
|
|
659
745
|
/** Layout wrapper (ctx-aware): frames the page in persistent chrome. SSG-only. */
|
|
660
746
|
readonly layout?: (ctx: LayoutContext<RouteState>, children: ComponentChildren) => VNode;
|
|
661
747
|
/** Page renderer. */
|
|
662
748
|
readonly render?: (ctx: RouteContext<RouteState>) => VNode;
|
|
663
|
-
/** Client-side validation gate: `unknown` (fetched JSON) → the route's data type, or throw. */
|
|
664
|
-
readonly parse?: (raw: unknown) => unknown;
|
|
665
749
|
/** Head/SEO producer. */
|
|
666
750
|
readonly head?: (ctx: RouteContext<RouteState>) => HeadConfig$1;
|
|
667
|
-
/** Static-generation param producer. */
|
|
668
|
-
readonly generate?: (
|
|
751
|
+
/** Static-generation param producer (receives a {@link GenerateContext}). */
|
|
752
|
+
readonly generate?: (ctx: GenerateContext) => unknown[] | Promise<unknown[]>;
|
|
669
753
|
/** JSON serializer. */
|
|
670
754
|
readonly toJson?: (ctx: RouteContext<RouteState>) => unknown;
|
|
671
755
|
/** Output file-path producer. */
|
|
@@ -689,28 +773,45 @@ interface RouteDefinition {
|
|
|
689
773
|
* base (erased) `RouteDefinition`; this is the documented generic-erasure boundary.
|
|
690
774
|
*/
|
|
691
775
|
type RouteMap = Record<string, RouteDefinition>;
|
|
776
|
+
/**
|
|
777
|
+
* A pure, app-free URL builder over a route map (the return type of `createUrls`).
|
|
778
|
+
* `toUrl` builds a route's path by name + params via pattern substitution — it needs
|
|
779
|
+
* NO running app, router instance, base URL, or i18n: just the route map the consumer
|
|
780
|
+
* already holds at module scope. Works identically at build, on client navigation,
|
|
781
|
+
* and inside hydrated islands. Reuses the SAME `buildUrl` as the runtime `RouterApi`,
|
|
782
|
+
* so the helper and the API can never diverge.
|
|
783
|
+
*
|
|
784
|
+
* @example
|
|
785
|
+
* const url = createUrls(routes);
|
|
786
|
+
* url.toUrl("article", { lang: "en", slug: "hello" }); // "/en/hello/"
|
|
787
|
+
*/
|
|
788
|
+
interface Urls<T extends RouteMap> {
|
|
789
|
+
/**
|
|
790
|
+
* Build a route's URL path from its name and params. The name is typed to the
|
|
791
|
+
* route map's keys — only declared routes are accepted.
|
|
792
|
+
*
|
|
793
|
+
* @param name - Route name key from the map (e.g. `"home"`, `"article"`).
|
|
794
|
+
* @param params - Path params to substitute into the pattern. Defaults to `{}`.
|
|
795
|
+
* @returns The resolved relative URL path.
|
|
796
|
+
* @throws {Error} If `name` is not present in the route map.
|
|
797
|
+
* @example
|
|
798
|
+
* url.toUrl("home", { lang: "en" }); // "/en/"
|
|
799
|
+
*/
|
|
800
|
+
toUrl<K extends keyof T & string>(name: K, params?: Record<string, string>): string;
|
|
801
|
+
}
|
|
692
802
|
/**
|
|
693
803
|
* Configuration for the router plugin.
|
|
694
804
|
*
|
|
695
805
|
* @remarks
|
|
696
|
-
* `routes` is
|
|
697
|
-
*
|
|
698
|
-
*
|
|
806
|
+
* `routes` is the declarative route map — registered the normal config way via
|
|
807
|
+
* `createApp({ pluginConfigs: { router: { routes } } })` and compiled into the matcher
|
|
808
|
+
* table in the router's `onInit`. An `import * as routes` namespace is a valid value. It is
|
|
809
|
+
* the SOLE registration path: omitting it leaves the matcher table empty, so every read
|
|
810
|
+
* (`match`/`toUrl`/`entries`/…) throws. The render `mode` is NOT here — it is a GLOBAL
|
|
811
|
+
* framework option (`createApp({ config: { mode } })`), read by the router via `ctx.global`.
|
|
699
812
|
*/
|
|
700
813
|
type RouterConfig = {
|
|
701
|
-
/**
|
|
702
|
-
* Named route definitions. Element type erases to the base `RouteDefinition`
|
|
703
|
-
* at this config boundary; per-route call-site types are preserved only through
|
|
704
|
-
* `defineRoutes()` + `route()` at the consumer and re-exposed via `manifest()`.
|
|
705
|
-
*/
|
|
706
|
-
routes: RouteMap;
|
|
707
|
-
/**
|
|
708
|
-
* Render mode for URL/file resolution. Defaults to `"hybrid"`.
|
|
709
|
-
* - `"ssg"` static generation only (no client router emitted).
|
|
710
|
-
* - `"spa"` client-side routing only.
|
|
711
|
-
* - `"hybrid"` static HTML + client navigation overlay.
|
|
712
|
-
*/
|
|
713
|
-
mode?: "ssg" | "spa" | "hybrid";
|
|
814
|
+
/** Declarative route map (route name → `route(...)`); compiled at init. An `import * as` namespace works. */readonly routes?: RouteMap;
|
|
714
815
|
};
|
|
715
816
|
/** A resolved route exposing URL utilities with typed params (port of legacy TypedRoute). */
|
|
716
817
|
interface TypedRoute<TParams = Record<string, string>> {
|
|
@@ -759,15 +860,14 @@ interface MatcherTable {
|
|
|
759
860
|
readonly byName: ReadonlyMap<string, CompiledRoute>;
|
|
760
861
|
}
|
|
761
862
|
/**
|
|
762
|
-
* Router plugin state
|
|
763
|
-
*
|
|
764
|
-
*
|
|
863
|
+
* Router plugin state — a mutable holder whose `table` is `null` until the router's
|
|
864
|
+
* `onInit` compiles `config.routes`. The render `mode` is NOT stored here; it is read
|
|
865
|
+
* from the global framework config via the API context. Keeps all mutable state in
|
|
866
|
+
* `ctx.state` (no singletons).
|
|
765
867
|
*/
|
|
766
868
|
interface RouterState {
|
|
767
|
-
/** Compiled matcher table; `null` until `onInit`
|
|
869
|
+
/** Compiled matcher table; `null` until `onInit` compiles `config.routes`. */
|
|
768
870
|
table: MatcherTable | null;
|
|
769
|
-
/** Resolved render mode (single source of truth; set in `onInit`). Defaults `"hybrid"`. */
|
|
770
|
-
mode: "ssg" | "spa" | "hybrid";
|
|
771
871
|
}
|
|
772
872
|
/** Plain-data input to `compileRoutes` — resolved DATA only, never the plugin ctx. */
|
|
773
873
|
interface CompileInput {
|
|
@@ -800,7 +900,7 @@ interface ClientRoute {
|
|
|
800
900
|
/** Route metadata bag from `.meta()`. MUST be JSON-serializable. */
|
|
801
901
|
readonly meta: Record<string, unknown>;
|
|
802
902
|
}
|
|
803
|
-
/** Public API exposed via `ctx.require(routerPlugin)`. */
|
|
903
|
+
/** Public API exposed via `ctx.require(routerPlugin)` and `app.router`. */
|
|
804
904
|
type RouterApi = {
|
|
805
905
|
/**
|
|
806
906
|
* Match a pathname against the compiled route table (specificity-sorted).
|
|
@@ -839,7 +939,7 @@ type RouterApi = {
|
|
|
839
939
|
*
|
|
840
940
|
* @returns Read-only array of the typed route definitions, in declaration order.
|
|
841
941
|
* @example
|
|
842
|
-
* for (const def of ctx.require(routerPlugin).manifest())
|
|
942
|
+
* for (const def of ctx.require(routerPlugin).manifest()) def._handlers.render?.(routeContext);
|
|
843
943
|
*/
|
|
844
944
|
manifest(): readonly RouteDefinition[];
|
|
845
945
|
/**
|
|
@@ -865,13 +965,13 @@ type RouterApi = {
|
|
|
865
965
|
mode(): "ssg" | "spa" | "hybrid";
|
|
866
966
|
};
|
|
867
967
|
/** Re-export under the canonical `Config` name for the plugin-types barrel. */
|
|
868
|
-
type Config$
|
|
968
|
+
type Config$2 = RouterConfig;
|
|
869
969
|
/** Re-export under the canonical `State` name for the plugin-types barrel. */
|
|
870
|
-
type State$
|
|
970
|
+
type State$2 = RouterState;
|
|
871
971
|
/** Re-export under the canonical `Api` name for the plugin-types barrel. */
|
|
872
|
-
type Api$
|
|
873
|
-
declare namespace types_d_exports$
|
|
874
|
-
export { Api, ArticleMeta, Config, HeadConfig, HeadDefaults, HeadElement, ResolvedRoute, State };
|
|
972
|
+
type Api$2 = RouterApi;
|
|
973
|
+
declare namespace types_d_exports$3 {
|
|
974
|
+
export { Api$1 as Api, ArticleMeta, Config$1 as Config, HeadConfig, HeadDefaults, HeadElement, ResolvedRoute, State$1 as State };
|
|
875
975
|
}
|
|
876
976
|
/**
|
|
877
977
|
* @file head plugin — type definitions skeleton
|
|
@@ -887,7 +987,7 @@ declare namespace types_d_exports$2 {
|
|
|
887
987
|
* const config: Config = { titleTemplate: "%s — Moku" };
|
|
888
988
|
* ```
|
|
889
989
|
*/
|
|
890
|
-
type Config = {
|
|
990
|
+
type Config$1 = {
|
|
891
991
|
/** Title template applied to per-route titles. `%s` is replaced by the route title. */titleTemplate?: string; /** Default Open Graph image URL used when a route does not supply one. */
|
|
892
992
|
defaultOgImage?: string; /** Default Twitter card type emitted when og/twitter content is present. */
|
|
893
993
|
twitterCard?: "summary" | "summary_large_image"; /** Default Twitter site handle (e.g. `"@moku_labs"`) emitted as `twitter:site`. */
|
|
@@ -905,7 +1005,7 @@ type Config = {
|
|
|
905
1005
|
* const state: State = { defaults: null };
|
|
906
1006
|
* ```
|
|
907
1007
|
*/
|
|
908
|
-
type State = {
|
|
1008
|
+
type State$1 = {
|
|
909
1009
|
/** Normalized head defaults, assigned once in `onInit` (initially `null`). */defaults: HeadDefaults | null;
|
|
910
1010
|
};
|
|
911
1011
|
/**
|
|
@@ -999,7 +1099,7 @@ type ResolvedRoute = {
|
|
|
999
1099
|
* const html: string = api.render(route, data);
|
|
1000
1100
|
* ```
|
|
1001
1101
|
*/
|
|
1002
|
-
type Api = {
|
|
1102
|
+
type Api$1 = {
|
|
1003
1103
|
/**
|
|
1004
1104
|
* Compose the final `<head>` inner HTML for a route. Pulled synchronously by `build`.
|
|
1005
1105
|
*
|
|
@@ -1013,7 +1113,7 @@ type Api = {
|
|
|
1013
1113
|
*/
|
|
1014
1114
|
render(route: ResolvedRoute, data: unknown): string;
|
|
1015
1115
|
};
|
|
1016
|
-
declare namespace types_d_exports$
|
|
1116
|
+
declare namespace types_d_exports$6 {
|
|
1017
1117
|
export { COMPONENT_HOOK_NAMES, ComponentContext, ComponentDef, ComponentHooks, ComponentInstance, ExtractApi, PageData, ResolvedSpaConfig, SpaApi, SpaConfig, SpaContext, SpaDataReader, SpaEmitFunction, SpaEvents, SpaKernel, SpaKernelDeps, SpaRequire, SpaState };
|
|
1018
1118
|
}
|
|
1019
1119
|
/** Payload map for the events `spa` emits, used to type the kernel's `emit` closure. */
|
|
@@ -1214,11 +1314,11 @@ interface SpaKernelDeps {
|
|
|
1214
1314
|
/** Router plugin API — used for client-side route matching (`match`) + the resolved `mode`. */
|
|
1215
1315
|
router: RouterApi;
|
|
1216
1316
|
/** Head plugin API — its pure compose is reused for client head-sync. */
|
|
1217
|
-
head: Api;
|
|
1317
|
+
head: Api$1;
|
|
1218
1318
|
/**
|
|
1219
1319
|
* The OPTIONAL `data` reader. Present only when the `data` plugin is composed.
|
|
1220
1320
|
* When present (and `router.mode() !== "ssg"`), navigation first tries the client
|
|
1221
|
-
* DATA path (match → `dataAt(path)` → `route.
|
|
1321
|
+
* DATA path (match → `dataAt(path)` → `route.render`); otherwise
|
|
1222
1322
|
* it always uses HTML-over-fetch.
|
|
1223
1323
|
*/
|
|
1224
1324
|
dataAt?: SpaDataReader;
|
|
@@ -1341,7 +1441,7 @@ type SpaApi = {
|
|
|
1341
1441
|
* });
|
|
1342
1442
|
* ```
|
|
1343
1443
|
*/
|
|
1344
|
-
declare const sitePlugin: import("@moku-labs/core").PluginInstance<"site", Config$
|
|
1444
|
+
declare const sitePlugin: import("@moku-labs/core").PluginInstance<"site", Config$4, Record<string, never>, Api$4, {}> & Record<never, never>;
|
|
1345
1445
|
//#endregion
|
|
1346
1446
|
//#region src/plugins/i18n/index.d.ts
|
|
1347
1447
|
/**
|
|
@@ -1363,7 +1463,7 @@ declare const sitePlugin: import("@moku-labs/core").PluginInstance<"site", Confi
|
|
|
1363
1463
|
* });
|
|
1364
1464
|
* ```
|
|
1365
1465
|
*/
|
|
1366
|
-
declare const i18nPlugin: import("@moku-labs/core").PluginInstance<"i18n", Config$
|
|
1466
|
+
declare const i18nPlugin: import("@moku-labs/core").PluginInstance<"i18n", Config$3, Record<string, never>, Api$3, {}> & Record<never, never>;
|
|
1367
1467
|
//#endregion
|
|
1368
1468
|
//#region src/plugins/router/builders/route-builder.d.ts
|
|
1369
1469
|
/**
|
|
@@ -1378,7 +1478,7 @@ declare const i18nPlugin: import("@moku-labs/core").PluginInstance<"i18n", Confi
|
|
|
1378
1478
|
* @example
|
|
1379
1479
|
* ```ts
|
|
1380
1480
|
* route("/{lang:?}/{slug}/")
|
|
1381
|
-
* .load((
|
|
1481
|
+
* .load((ctx) => loadArticle(ctx.params.slug))
|
|
1382
1482
|
* .render((ctx) => <Article a={ctx.data} />)
|
|
1383
1483
|
* .head((ctx) => ({ title: ctx.data.title }));
|
|
1384
1484
|
* ```
|
|
@@ -1396,31 +1496,46 @@ declare function route<P extends string>(pattern: P): RouteBuilder<RouteState<P>
|
|
|
1396
1496
|
* ```
|
|
1397
1497
|
*/
|
|
1398
1498
|
declare function defineRoutes<T extends RouteMap>(routes: T): T;
|
|
1499
|
+
/**
|
|
1500
|
+
* Build a pure, app-free URL builder from a route map. `toUrl(name, params)` resolves
|
|
1501
|
+
* a route's path by pattern substitution using the SAME `buildUrl` as the runtime
|
|
1502
|
+
* `RouterApi.toUrl`, so the helper and the API can never diverge. It needs no running
|
|
1503
|
+
* app, router instance, base URL, or i18n — just the route map the consumer already
|
|
1504
|
+
* holds at module scope. So components, layouts, and hydrated islands import it
|
|
1505
|
+
* directly: no `app.router` reference, no manual "bind", no module global, no
|
|
1506
|
+
* "not bound" guard, and no createApp ↔ routes cycle.
|
|
1507
|
+
*
|
|
1508
|
+
* @param routes - The route map (typically the value returned by {@link defineRoutes}).
|
|
1509
|
+
* @returns A {@link Urls} builder whose `toUrl` accepts only this map's route names.
|
|
1510
|
+
* @example
|
|
1511
|
+
* ```ts
|
|
1512
|
+
* const url = createUrls(routes);
|
|
1513
|
+
* url.toUrl("article", { lang: "en", slug: "hello" }); // "/en/hello/"
|
|
1514
|
+
* ```
|
|
1515
|
+
*/
|
|
1516
|
+
declare function createUrls<T extends RouteMap>(routes: T): Urls<T>;
|
|
1399
1517
|
//#endregion
|
|
1400
1518
|
//#region src/plugins/router/index.d.ts
|
|
1401
1519
|
/**
|
|
1402
1520
|
* Router plugin — typed, named route definitions with locale-aware URL generation
|
|
1403
|
-
* and matching. Author routes with {@link route}
|
|
1404
|
-
*
|
|
1521
|
+
* and matching. Author routes with {@link route}, then register them the normal config
|
|
1522
|
+
* way via `pluginConfigs.router.routes` (compiled at init). Depends on site (base URL)
|
|
1523
|
+
* and i18n (locales).
|
|
1405
1524
|
*
|
|
1406
|
-
* @example
|
|
1525
|
+
* @example Register routes via config, then start/build
|
|
1407
1526
|
* ```ts
|
|
1527
|
+
* import * as routes from "./routes";
|
|
1408
1528
|
* const app = createApp({
|
|
1409
|
-
*
|
|
1410
|
-
*
|
|
1411
|
-
* routes: defineRoutes({
|
|
1412
|
-
* home: route("/"),
|
|
1413
|
-
* article: route("/blog/{slug}/")
|
|
1414
|
-
* }),
|
|
1415
|
-
* mode: "hybrid" // "ssg" | "spa" | "hybrid" (default)
|
|
1416
|
-
* }
|
|
1417
|
-
* }
|
|
1529
|
+
* config: { mode: "hybrid" }, // render mode is GLOBAL config
|
|
1530
|
+
* pluginConfigs: { router: { routes } } // declarative route map (a namespace works)
|
|
1418
1531
|
* });
|
|
1532
|
+
* await app.build.run(); // or: await app.start(); — routes compiled at init
|
|
1419
1533
|
* ```
|
|
1420
1534
|
*/
|
|
1421
1535
|
declare const routerPlugin: import("@moku-labs/core").PluginInstance<"router", RouterConfig, RouterState, RouterApi, {}> & {
|
|
1422
1536
|
route: typeof route;
|
|
1423
1537
|
defineRoutes: typeof defineRoutes;
|
|
1538
|
+
createUrls: typeof createUrls;
|
|
1424
1539
|
};
|
|
1425
1540
|
//#endregion
|
|
1426
1541
|
//#region src/plugins/head/primitives.d.ts
|
|
@@ -1495,6 +1610,8 @@ declare function feedLink(title: string, url: string, type?: string): HeadElemen
|
|
|
1495
1610
|
* modified times, author, section, tags, plus a JSON-LD `Article` block and canonical.
|
|
1496
1611
|
*
|
|
1497
1612
|
* @param articleMeta - Article metadata (title, description, author, dates, tags, image…).
|
|
1613
|
+
* `image`, when present, is pushed to `og:image` verbatim and must therefore be
|
|
1614
|
+
* an absolute URL (this helper does not resolve relative paths against the site).
|
|
1498
1615
|
* @param canonicalUrl - The article's canonical absolute URL.
|
|
1499
1616
|
* @returns An ordered array of serializable head elements.
|
|
1500
1617
|
* @example buildArticleHead({ title: "Hi", author: "A", published: "2026-01-01" }, "https://x/p")
|
|
@@ -1521,7 +1638,7 @@ declare function buildArticleHead(articleMeta: ArticleMeta, canonicalUrl: string
|
|
|
1521
1638
|
* });
|
|
1522
1639
|
* ```
|
|
1523
1640
|
*/
|
|
1524
|
-
declare const headPlugin: import("@moku-labs/core").PluginInstance<"head", Config, State, Api, {}> & {
|
|
1641
|
+
declare const headPlugin: import("@moku-labs/core").PluginInstance<"head", Config$1, State$1, Api$1, {}> & {
|
|
1525
1642
|
meta: typeof meta;
|
|
1526
1643
|
og: typeof og;
|
|
1527
1644
|
twitter: typeof twitter;
|
|
@@ -1588,7 +1705,7 @@ declare const spaPlugin: import("@moku-labs/core").PluginInstance<"spa", SpaConf
|
|
|
1588
1705
|
el: Element;
|
|
1589
1706
|
};
|
|
1590
1707
|
}> & Record<never, never>;
|
|
1591
|
-
declare namespace types_d_exports {
|
|
1708
|
+
declare namespace types_d_exports$1 {
|
|
1592
1709
|
export { DataConfig, DataEntry, DataProvider, DataState, DataWriteSummary };
|
|
1593
1710
|
}
|
|
1594
1711
|
/**
|
|
@@ -1597,13 +1714,13 @@ declare namespace types_d_exports {
|
|
|
1597
1714
|
* The `data` plugin is the **agnostic data provider** for the SSG→DATA→SPA pattern.
|
|
1598
1715
|
* It owns ONE thing: the contract `page path → persisted JSON file`. It knows
|
|
1599
1716
|
* NOTHING about what the data *is* — no domain types appear here. A route decides
|
|
1600
|
-
* its own data shape (`load`'s return)
|
|
1717
|
+
* its own data shape (`load`'s return).
|
|
1601
1718
|
*
|
|
1602
1719
|
* - **Node (build):** `write(entries)` persists one JSON file per page, keyed by
|
|
1603
1720
|
* the page's URL via {@link DataProvider.fileFor}. `build` supplies the entries
|
|
1604
1721
|
* (it already expanded the routes), so there is no duplicate expansion here.
|
|
1605
|
-
* - **Browser (runtime):** `at(path)` fetches + caches that file as `unknown
|
|
1606
|
-
* route
|
|
1722
|
+
* - **Browser (runtime):** `at(path)` fetches + caches that file as `unknown`, which
|
|
1723
|
+
* the route uses directly as `ctx.data` in `render`.
|
|
1607
1724
|
*
|
|
1608
1725
|
* The Node-only file-writing code (`node:fs`) is isolated behind a lazy `import()`
|
|
1609
1726
|
* inside `write()`, so composing `data` in a browser app keeps the bundle free of
|
|
@@ -1625,8 +1742,8 @@ type DataConfig = {
|
|
|
1625
1742
|
outputDir: string;
|
|
1626
1743
|
/**
|
|
1627
1744
|
* READ side (browser): site-root-relative URL the client fetches the per-page
|
|
1628
|
-
* JSON from.
|
|
1629
|
-
* path); keep consistent (`"/" + trim(outputDir) + "/"`). Default `"/_data/"`.
|
|
1745
|
+
* JSON from. The URL-space mirror of {@link DataConfig.outputDir} (a filesystem
|
|
1746
|
+
* path); keep them consistent (`"/" + trim(outputDir) + "/"`). Default `"/_data/"`.
|
|
1630
1747
|
*/
|
|
1631
1748
|
baseUrl: string;
|
|
1632
1749
|
};
|
|
@@ -1668,15 +1785,15 @@ interface DataState {
|
|
|
1668
1785
|
* // Node build (build supplies the entries it already expanded):
|
|
1669
1786
|
* await app.data.write([{ path: "/en/hello/", data: article }]);
|
|
1670
1787
|
*
|
|
1671
|
-
* // Browser (inside spa nav): fetch the page's data,
|
|
1672
|
-
* const raw = await app.data.at("/en/hello/"); // unknown | null
|
|
1788
|
+
* // Browser (inside spa nav): fetch the page's data, used directly as ctx.data:
|
|
1789
|
+
* const raw = await app.data.at("/en/hello/"); // unknown | null (null on failure)
|
|
1673
1790
|
* ```
|
|
1674
1791
|
*/
|
|
1675
1792
|
type DataProvider = {
|
|
1676
1793
|
/**
|
|
1677
1794
|
* READ (browser) — fetch (and cache) the persisted data for a page path from
|
|
1678
|
-
* `config.baseUrl`. Returns the raw parsed JSON as `unknown
|
|
1679
|
-
* `
|
|
1795
|
+
* `config.baseUrl`. Returns the raw parsed JSON as `unknown`, used directly as
|
|
1796
|
+
* the route's `ctx.data`; returns `null` if the fetch or JSON parse fails.
|
|
1680
1797
|
*
|
|
1681
1798
|
* @param path - The page URL path (e.g. `/en/hello/`).
|
|
1682
1799
|
* @returns The page's raw data, or `null` on failure.
|
|
@@ -1722,18 +1839,316 @@ type DataProvider = {
|
|
|
1722
1839
|
* @example
|
|
1723
1840
|
* ```ts
|
|
1724
1841
|
* // Node build: `build` calls app.data.write(...) during its pages phase when
|
|
1725
|
-
* // router.mode !== "ssg".
|
|
1842
|
+
* // router.mode() !== "ssg". Compose the plugin + set the global render mode:
|
|
1843
|
+
* import * as routes from "./routes";
|
|
1726
1844
|
* const app = createApp({
|
|
1727
1845
|
* plugins: [dataPlugin, contentPlugin, buildPlugin],
|
|
1728
|
-
*
|
|
1846
|
+
* config: { mode: "hybrid" },
|
|
1847
|
+
* pluginConfigs: { content: { providers: [fileSystemContent({ contentDir: "./content" })] }, router: { routes } }
|
|
1729
1848
|
* });
|
|
1730
|
-
* await app.
|
|
1731
|
-
* await app.build.run(); // writes HTML + per-page data sidecars
|
|
1849
|
+
* await app.build.run(); // writes HTML + per-page data sidecars (routes compiled at init)
|
|
1732
1850
|
*
|
|
1733
1851
|
* // Browser app: compose `dataPlugin` too; spa fetches via app.data.at(path) on nav.
|
|
1734
1852
|
* ```
|
|
1735
1853
|
*/
|
|
1736
1854
|
declare const dataPlugin: import("@moku-labs/core").PluginInstance<"data", DataConfig, DataState, DataProvider, {}> & Record<never, never>;
|
|
1855
|
+
declare namespace types_d_exports {
|
|
1856
|
+
export { Api, Article, ArticleCard, ComputedFields, Config, ContentApiContext, ContentEvents, ContentProvider, ContentProviderState, FileSystemContentOptions, Frontmatter, State };
|
|
1857
|
+
}
|
|
1858
|
+
/**
|
|
1859
|
+
* YAML frontmatter parsed from each article file.
|
|
1860
|
+
*
|
|
1861
|
+
* @example
|
|
1862
|
+
* ```ts
|
|
1863
|
+
* { title: "Hello", date: "2026-01-15", description: "Intro", tags: [], language: "en" }
|
|
1864
|
+
* ```
|
|
1865
|
+
*/
|
|
1866
|
+
type Frontmatter = {
|
|
1867
|
+
/** Article title. Required. */title: string; /** ISO 8601 date string, e.g. "2026-01-15". Required. */
|
|
1868
|
+
date: string; /** Short summary used in cards, feeds, and meta description. Required. */
|
|
1869
|
+
description: string; /** Topic tags. Required (may be empty array). */
|
|
1870
|
+
tags: string[]; /** Source language code of this file. Required. */
|
|
1871
|
+
language: string; /** Draft flag. Excluded from output in production mode. Defaults to false. */
|
|
1872
|
+
draft?: boolean; /** Author name. Falls back to the provider's defaultAuthor when omitted. */
|
|
1873
|
+
author?: string;
|
|
1874
|
+
};
|
|
1875
|
+
/**
|
|
1876
|
+
* Fields computed by the pipeline (not authored in frontmatter).
|
|
1877
|
+
*
|
|
1878
|
+
* @example
|
|
1879
|
+
* ```ts
|
|
1880
|
+
* { slug: "hello", readingTime: 1, contentId: "hello", status: "published", wordCount: 42 }
|
|
1881
|
+
* ```
|
|
1882
|
+
*/
|
|
1883
|
+
type ComputedFields = {
|
|
1884
|
+
/** Article directory name. */slug: string; /** Reading time in minutes (ceiling, minimum 1). */
|
|
1885
|
+
readingTime: number; /** Stable content identifier (slug by default). */
|
|
1886
|
+
contentId: string; /** Derived publication status. */
|
|
1887
|
+
status: "published" | "draft"; /** Word count from the source body. */
|
|
1888
|
+
wordCount: number;
|
|
1889
|
+
};
|
|
1890
|
+
/**
|
|
1891
|
+
* A fully processed, render-ready article.
|
|
1892
|
+
*
|
|
1893
|
+
* @example
|
|
1894
|
+
* ```ts
|
|
1895
|
+
* { frontmatter, computed, html: "<p>…</p>", locale: "en", isFallback: false, url: "/en/hello/" }
|
|
1896
|
+
* ```
|
|
1897
|
+
*/
|
|
1898
|
+
type Article = {
|
|
1899
|
+
/** Parsed frontmatter. */frontmatter: Frontmatter; /** Pipeline-computed metadata. */
|
|
1900
|
+
computed: ComputedFields; /** Sanitized rendered HTML body. */
|
|
1901
|
+
html: string; /** Locale this Article instance represents (the requested locale, even on fallback). */
|
|
1902
|
+
locale: string; /** True when the default-locale file was used because the requested locale was missing. */
|
|
1903
|
+
isFallback: boolean; /** Canonical URL for this article in this locale. */
|
|
1904
|
+
url: string;
|
|
1905
|
+
};
|
|
1906
|
+
/**
|
|
1907
|
+
* Lightweight projection of Article for cards/lists.
|
|
1908
|
+
*
|
|
1909
|
+
* @example
|
|
1910
|
+
* ```ts
|
|
1911
|
+
* { contentId: "hello", status: "published", title: "Hello", date: "2026-01-15", description: "Intro", tags: [], readingTime: 1, url: "/en/hello/" }
|
|
1912
|
+
* ```
|
|
1913
|
+
*/
|
|
1914
|
+
type ArticleCard = {
|
|
1915
|
+
/** Stable content identifier. */contentId: string; /** Derived publication status. */
|
|
1916
|
+
status: "published" | "draft"; /** Article title. */
|
|
1917
|
+
title: string; /** ISO 8601 date string. */
|
|
1918
|
+
date: string; /** Short summary. */
|
|
1919
|
+
description: string; /** Topic tags. */
|
|
1920
|
+
tags: string[]; /** Reading time in minutes. */
|
|
1921
|
+
readingTime: number; /** Canonical URL for this article in this locale. */
|
|
1922
|
+
url: string;
|
|
1923
|
+
};
|
|
1924
|
+
/**
|
|
1925
|
+
* A pluggable content SOURCE. The shell calls these to read articles; whether content
|
|
1926
|
+
* is read from the filesystem (Node) or some other source is chosen by which provider
|
|
1927
|
+
* you compose — exactly like `env` providers (`dotenv`/`processEnv` vs `browserEnv`).
|
|
1928
|
+
* The shell adds locale fallback, draft filtering, sorting, caching, and events on top.
|
|
1929
|
+
*
|
|
1930
|
+
* @example
|
|
1931
|
+
* ```ts
|
|
1932
|
+
* const provider = fileSystemContent({ contentDir: "./content" });
|
|
1933
|
+
* ```
|
|
1934
|
+
*/
|
|
1935
|
+
interface ContentProvider {
|
|
1936
|
+
/** Human-readable provider name, used in diagnostics. */
|
|
1937
|
+
readonly name: string;
|
|
1938
|
+
/** Source directory surfaced via `api.contentDir()` (filesystem providers; "" otherwise). */
|
|
1939
|
+
readonly contentDir: string;
|
|
1940
|
+
/**
|
|
1941
|
+
* Discover the article slugs this provider can supply.
|
|
1942
|
+
*
|
|
1943
|
+
* @returns The provider's slug list.
|
|
1944
|
+
*/
|
|
1945
|
+
slugs(): Promise<readonly string[]>;
|
|
1946
|
+
/**
|
|
1947
|
+
* Read + render ONE article for a file-locale; `null` if this provider has no such file.
|
|
1948
|
+
*
|
|
1949
|
+
* @param slug - Article directory name.
|
|
1950
|
+
* @param fileLocale - Locale whose source file is read.
|
|
1951
|
+
* @param outLocale - Locale the resulting Article represents (the requested locale).
|
|
1952
|
+
* @param isFallback - Whether this resolution used the default-locale fallback.
|
|
1953
|
+
* @returns The constructed Article, or `null` when absent.
|
|
1954
|
+
*/
|
|
1955
|
+
readArticle(slug: string, fileLocale: string, outLocale: string, isFallback: boolean): Promise<Article | null>;
|
|
1956
|
+
/**
|
|
1957
|
+
* Render a standalone Markdown string to HTML through the provider's pipeline.
|
|
1958
|
+
*
|
|
1959
|
+
* @param markdown - Raw Markdown source.
|
|
1960
|
+
* @returns The rendered HTML.
|
|
1961
|
+
*/
|
|
1962
|
+
render(markdown: string): Promise<string>;
|
|
1963
|
+
/**
|
|
1964
|
+
* Optional dev hook: drop cached discovery so stale paths are re-read next time.
|
|
1965
|
+
*
|
|
1966
|
+
* @param paths - Stale file paths.
|
|
1967
|
+
*/
|
|
1968
|
+
invalidate?(paths: readonly string[]): void;
|
|
1969
|
+
}
|
|
1970
|
+
/**
|
|
1971
|
+
* Options for the node filesystem provider {@link ContentProvider} `fileSystemContent`.
|
|
1972
|
+
* These are the markdown-pipeline + source concerns that used to live on the content
|
|
1973
|
+
* plugin config; they now belong to the provider you compose.
|
|
1974
|
+
*
|
|
1975
|
+
* @example
|
|
1976
|
+
* ```ts
|
|
1977
|
+
* fileSystemContent({ contentDir: "./content", shikiTheme: "github-dark", defaultAuthor: "Ada" });
|
|
1978
|
+
* ```
|
|
1979
|
+
*/
|
|
1980
|
+
type FileSystemContentOptions = {
|
|
1981
|
+
/** Absolute or project-relative path to the content directory. */contentDir: string;
|
|
1982
|
+
/**
|
|
1983
|
+
* SECURITY GATE. When false (the default), rehype-sanitize runs as the final
|
|
1984
|
+
* pipeline step. Set true ONLY for fully author-controlled Markdown.
|
|
1985
|
+
*/
|
|
1986
|
+
trustedContent?: boolean; /** Additional remark plugins, concatenated AFTER framework defaults. Defaults to []. */
|
|
1987
|
+
extraRemarkPlugins?: readonly Pluggable[]; /** Additional rehype plugins, concatenated after custom transforms, before Shiki + sanitize. Defaults to []. */
|
|
1988
|
+
extraRehypePlugins?: readonly Pluggable[];
|
|
1989
|
+
/**
|
|
1990
|
+
* Shiki theme for syntax highlighting: a bundled theme NAME (default "github-dark")
|
|
1991
|
+
* or a custom `ThemeRegistration` object. Passed straight through to `@shikijs/rehype`.
|
|
1992
|
+
*/
|
|
1993
|
+
shikiTheme?: BundledTheme | ThemeRegistrationAny; /** Author applied to articles whose frontmatter omits author. Defaults to undefined. */
|
|
1994
|
+
defaultAuthor?: string;
|
|
1995
|
+
};
|
|
1996
|
+
/**
|
|
1997
|
+
* Internal mutable state of the filesystem provider: the lazy unified processor and
|
|
1998
|
+
* the discovery caches. Owned by the provider closure, never by the plugin shell.
|
|
1999
|
+
*
|
|
2000
|
+
* @example
|
|
2001
|
+
* ```ts
|
|
2002
|
+
* { processor: null, slugs: null, dirtyPaths: new Set() }
|
|
2003
|
+
* ```
|
|
2004
|
+
*/
|
|
2005
|
+
type ContentProviderState = {
|
|
2006
|
+
/** Lazily-created unified processor singleton. null until first render()/readArticle(). */processor: Processor | null; /** Discovered, sorted slug list cached after first disk scan. null until first discovery. */
|
|
2007
|
+
slugs: string[] | null; /** Paths marked stale by invalidate(); next discovery re-reads only these. Starts empty. */
|
|
2008
|
+
dirtyPaths: Set<string>;
|
|
2009
|
+
};
|
|
2010
|
+
/**
|
|
2011
|
+
* Configuration for the content plugin (shell).
|
|
2012
|
+
*
|
|
2013
|
+
* @example
|
|
2014
|
+
* ```ts
|
|
2015
|
+
* { providers: [fileSystemContent({ contentDir: "./content" })] }
|
|
2016
|
+
* ```
|
|
2017
|
+
*/
|
|
2018
|
+
type Config = {
|
|
2019
|
+
/**
|
|
2020
|
+
* Ordered content sources. Compose at least one (e.g. `fileSystemContent(...)` on
|
|
2021
|
+
* Node). The first provider that supplies an article for a slug+locale wins;
|
|
2022
|
+
* `slugs()` are unioned. The plugin's own spec default is `[]` (a build must supply one).
|
|
2023
|
+
*/
|
|
2024
|
+
providers: ContentProvider[];
|
|
2025
|
+
};
|
|
2026
|
+
/**
|
|
2027
|
+
* Internal mutable state for the content plugin shell: the locale-keyed article cache.
|
|
2028
|
+
*
|
|
2029
|
+
* @example
|
|
2030
|
+
* ```ts
|
|
2031
|
+
* { articles: new Map() }
|
|
2032
|
+
* ```
|
|
2033
|
+
*/
|
|
2034
|
+
type State = {
|
|
2035
|
+
/** Article cache keyed locale -> (slug -> Article). Starts empty. */articles: Map<string, Map<string, Article>>;
|
|
2036
|
+
};
|
|
2037
|
+
/**
|
|
2038
|
+
* Notification-only events emitted by the content plugin.
|
|
2039
|
+
*
|
|
2040
|
+
* @example
|
|
2041
|
+
* ```ts
|
|
2042
|
+
* emit("content:ready", { locales: ["en"], articleCount: 3 });
|
|
2043
|
+
* ```
|
|
2044
|
+
*/
|
|
2045
|
+
type ContentEvents = {
|
|
2046
|
+
/** All articles loaded across locales. */"content:ready": {
|
|
2047
|
+
locales: readonly string[];
|
|
2048
|
+
articleCount: number;
|
|
2049
|
+
}; /** Article paths marked stale for dev rebuild. */
|
|
2050
|
+
"content:invalidated": {
|
|
2051
|
+
paths: readonly string[];
|
|
2052
|
+
};
|
|
2053
|
+
};
|
|
2054
|
+
/**
|
|
2055
|
+
* Kernel-free domain context handed to createContentApi by the wiring harness.
|
|
2056
|
+
* Carries the shell state (article cache), global flag, emit, the i18n-derived
|
|
2057
|
+
* locale helpers, and the resolved content {@link ContentProvider} — so api.ts stays
|
|
2058
|
+
* free of createPlugin/ctx AND of any node/pipeline import.
|
|
2059
|
+
*
|
|
2060
|
+
* @example
|
|
2061
|
+
* ```ts
|
|
2062
|
+
* const apiContext: ContentApiContext = { state, global, emit, locales, defaultLocale, provider };
|
|
2063
|
+
* ```
|
|
2064
|
+
*/
|
|
2065
|
+
type ContentApiContext = {
|
|
2066
|
+
/** Mutable shell state (article cache). */state: State; /** Global framework configuration (deployment stage). */
|
|
2067
|
+
global: {
|
|
2068
|
+
stage: Stage;
|
|
2069
|
+
}; /** Emit a registered content event. */
|
|
2070
|
+
emit: <K extends keyof ContentEvents>(event: K, payload: ContentEvents[K]) => void; /** Active locale codes from i18n. */
|
|
2071
|
+
locales: () => readonly string[]; /** Default locale code from i18n (fallback source). */
|
|
2072
|
+
defaultLocale: () => string; /** The resolved content source (merged from `config.providers`). */
|
|
2073
|
+
provider: ContentProvider;
|
|
2074
|
+
};
|
|
2075
|
+
/**
|
|
2076
|
+
* Public API for the content plugin.
|
|
2077
|
+
*
|
|
2078
|
+
* @example
|
|
2079
|
+
* ```ts
|
|
2080
|
+
* const map = await app.content.loadAll();
|
|
2081
|
+
* ```
|
|
2082
|
+
*/
|
|
2083
|
+
type Api = {
|
|
2084
|
+
/**
|
|
2085
|
+
* Load every article across every active locale, returning a locale-keyed
|
|
2086
|
+
* map of date-descending Article arrays. Emits content:ready.
|
|
2087
|
+
*/
|
|
2088
|
+
loadAll(): Promise<Map<string, Article[]>>;
|
|
2089
|
+
/**
|
|
2090
|
+
* Resolve and render a single article for one locale, with locale fallback.
|
|
2091
|
+
*
|
|
2092
|
+
* @param slug - Article directory name.
|
|
2093
|
+
* @param locale - Requested locale code.
|
|
2094
|
+
*/
|
|
2095
|
+
load(slug: string, locale: string): Promise<Article>;
|
|
2096
|
+
/**
|
|
2097
|
+
* Render a raw Markdown string to HTML through the full pipeline.
|
|
2098
|
+
*
|
|
2099
|
+
* @param md - Raw Markdown source.
|
|
2100
|
+
*/
|
|
2101
|
+
renderMarkdown(md: string): Promise<string>;
|
|
2102
|
+
/**
|
|
2103
|
+
* Mark file paths stale for incremental dev rebuilds. Emits content:invalidated.
|
|
2104
|
+
*
|
|
2105
|
+
* @param paths - File paths to invalidate.
|
|
2106
|
+
*/
|
|
2107
|
+
invalidate(paths: readonly string[]): void;
|
|
2108
|
+
/**
|
|
2109
|
+
* Project a full Article to a lightweight ArticleCard for list/grid rendering.
|
|
2110
|
+
*
|
|
2111
|
+
* @param article - The source article.
|
|
2112
|
+
*/
|
|
2113
|
+
articleToCard(article: Article): ArticleCard;
|
|
2114
|
+
/**
|
|
2115
|
+
* The configured content source directory (e.g. `"./content"`), from the first
|
|
2116
|
+
* provider. Lets the build copy each article's co-located assets
|
|
2117
|
+
* (`<contentDir>/<slug>/images/`) into the output.
|
|
2118
|
+
*/
|
|
2119
|
+
contentDir(): string;
|
|
2120
|
+
};
|
|
2121
|
+
//#endregion
|
|
2122
|
+
//#region src/plugins/content/index.d.ts
|
|
2123
|
+
/**
|
|
2124
|
+
* Content plugin (shell) — provider-driven locale-keyed Article model. Orchestration
|
|
2125
|
+
* (locale fallback, draft filtering, sort, caching, events) lives here; source I/O +
|
|
2126
|
+
* the Markdown pipeline live in a {@link ContentProvider} you compose (like `env`
|
|
2127
|
+
* providers). The shell imports zero node code, so `contentPlugin` is browser-safe.
|
|
2128
|
+
* Depends on i18n; emits `content:ready` and `content:invalidated`.
|
|
2129
|
+
*
|
|
2130
|
+
* @example Compose the node filesystem provider with a content dir + Shiki theme
|
|
2131
|
+
* ```ts
|
|
2132
|
+
* import { contentPlugin, fileSystemContent } from "@moku-labs/web";
|
|
2133
|
+
* const app = createApp({
|
|
2134
|
+
* plugins: [contentPlugin],
|
|
2135
|
+
* pluginConfigs: {
|
|
2136
|
+
* content: {
|
|
2137
|
+
* providers: [fileSystemContent({ contentDir: "./content", shikiTheme: "github-dark", defaultAuthor: "Ada" })]
|
|
2138
|
+
* }
|
|
2139
|
+
* }
|
|
2140
|
+
* });
|
|
2141
|
+
* ```
|
|
2142
|
+
*/
|
|
2143
|
+
declare const contentPlugin: import("@moku-labs/core").PluginInstance<"content", Config, State, Api, {
|
|
2144
|
+
"content:ready": {
|
|
2145
|
+
locales: readonly string[];
|
|
2146
|
+
articleCount: number;
|
|
2147
|
+
};
|
|
2148
|
+
"content:invalidated": {
|
|
2149
|
+
paths: readonly string[];
|
|
2150
|
+
};
|
|
2151
|
+
}> & Record<never, never>;
|
|
1737
2152
|
//#endregion
|
|
1738
2153
|
//#region src/plugins/log/index.d.ts
|
|
1739
2154
|
/**
|
|
@@ -1758,27 +2173,24 @@ declare const logPlugin: import("@moku-labs/core").CorePluginInstance<"log", Log
|
|
|
1758
2173
|
*
|
|
1759
2174
|
* @param options - Optional configuration:
|
|
1760
2175
|
* - `pluginConfigs` — per-plugin overrides, keyed by plugin name.
|
|
1761
|
-
* - `config` — global framework config (e.g. `{ mode: "
|
|
2176
|
+
* - `config` — global framework config (e.g. `{ mode: "spa" }`).
|
|
1762
2177
|
* - `plugins` — extra plugins (e.g. `dataPlugin` or your own) merged into the app and its type.
|
|
1763
2178
|
* - `onReady` / `onError` / `onStart` / `onStop` — lifecycle callbacks.
|
|
1764
2179
|
* @returns The initialized app: `start()`, `stop()`, every plugin's API, and `log`.
|
|
1765
2180
|
* @example
|
|
1766
2181
|
* ```ts
|
|
1767
2182
|
* // Client SPA — env works with no wiring (browserEnv is the default provider):
|
|
1768
|
-
*
|
|
1769
|
-
*
|
|
1770
|
-
*
|
|
1771
|
-
* router: { mode: "spa", routes: defineRoutes({ home: route("/"), post: route("/blog/{slug}/") }) }
|
|
1772
|
-
* }
|
|
1773
|
-
* });
|
|
1774
|
-
* await app.start();
|
|
2183
|
+
* import * as routes from "./routes";
|
|
2184
|
+
* const app = createApp({ config: { mode: "spa" }, pluginConfigs: { router: { routes } } });
|
|
2185
|
+
* await app.start(); // routes compiled at init from config
|
|
1775
2186
|
* app.env.get("PUBLIC_API_URL"); // resolved from import.meta.env
|
|
1776
2187
|
* ```
|
|
1777
2188
|
*/
|
|
1778
|
-
declare const createApp: <const ExtraPlugins extends readonly import("@moku-labs/core").AnyPluginInstance[] = readonly []>(options?: import("@moku-labs/core").CreateAppOptions<Config$
|
|
2189
|
+
declare const createApp: <const ExtraPlugins extends readonly import("@moku-labs/core").AnyPluginInstance[] = readonly []>(options?: import("@moku-labs/core").CreateAppOptions<Config$5, Events, (import("@moku-labs/core").PluginInstance<"site", Config$4, Record<string, never>, Api$4, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"i18n", Config$3, Record<string, never>, Api$3, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"router", RouterConfig, RouterState, RouterApi, {}> & {
|
|
1779
2190
|
route: typeof route;
|
|
1780
2191
|
defineRoutes: typeof defineRoutes;
|
|
1781
|
-
|
|
2192
|
+
createUrls: typeof createUrls;
|
|
2193
|
+
}) | (import("@moku-labs/core").PluginInstance<"head", Config$1, State$1, Api$1, {}> & {
|
|
1782
2194
|
meta: typeof meta;
|
|
1783
2195
|
og: typeof og;
|
|
1784
2196
|
twitter: typeof twitter;
|
|
@@ -1803,10 +2215,11 @@ declare const createApp: <const ExtraPlugins extends readonly import("@moku-labs
|
|
|
1803
2215
|
name: string;
|
|
1804
2216
|
el: Element;
|
|
1805
2217
|
};
|
|
1806
|
-
}> & Record<never, never>) | ExtraPlugins[number], [...ExtraPlugins], import("@moku-labs/core").CoreApisFromTuple<[import("@moku-labs/core").CorePluginInstance<"log", LogConfig, LogState, LogApi>, import("@moku-labs/core").CorePluginInstance<"env", EnvConfig, EnvState, EnvApi>]>> | undefined) => import("@moku-labs/core").App<Config$
|
|
2218
|
+
}> & Record<never, never>) | ExtraPlugins[number], [...ExtraPlugins], import("@moku-labs/core").CoreApisFromTuple<[import("@moku-labs/core").CorePluginInstance<"log", LogConfig, LogState, LogApi>, import("@moku-labs/core").CorePluginInstance<"env", EnvConfig, EnvState, EnvApi>]>> | undefined) => import("@moku-labs/core").App<Config$5, Events, (import("@moku-labs/core").PluginInstance<"site", Config$4, Record<string, never>, Api$4, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"i18n", Config$3, Record<string, never>, Api$3, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"router", RouterConfig, RouterState, RouterApi, {}> & {
|
|
1807
2219
|
route: typeof route;
|
|
1808
2220
|
defineRoutes: typeof defineRoutes;
|
|
1809
|
-
|
|
2221
|
+
createUrls: typeof createUrls;
|
|
2222
|
+
}) | (import("@moku-labs/core").PluginInstance<"head", Config$1, State$1, Api$1, {}> & {
|
|
1810
2223
|
meta: typeof meta;
|
|
1811
2224
|
og: typeof og;
|
|
1812
2225
|
twitter: typeof twitter;
|
|
@@ -1847,6 +2260,6 @@ declare const createApp: <const ExtraPlugins extends readonly import("@moku-labs
|
|
|
1847
2260
|
* const app = createApp({ plugins: [analytics] });
|
|
1848
2261
|
* ```
|
|
1849
2262
|
*/
|
|
1850
|
-
declare const createPlugin: import("@moku-labs/core").BoundCreatePluginFunction<Config$
|
|
2263
|
+
declare const createPlugin: import("@moku-labs/core").BoundCreatePluginFunction<Config$5, Events, import("@moku-labs/core").CoreApisFromTuple<[import("@moku-labs/core").CorePluginInstance<"log", LogConfig, LogState, LogApi>, import("@moku-labs/core").CorePluginInstance<"env", EnvConfig, EnvState, EnvApi>]>>;
|
|
1851
2264
|
//#endregion
|
|
1852
|
-
export { types_d_exports as
|
|
2265
|
+
export { types_d_exports as Content, types_d_exports$1 as Data, types_d_exports$2 as Env, types_d_exports$3 as Head, types_d_exports$4 as Log, types_d_exports$5 as Router, types_d_exports$6 as Spa, browserEnv, buildArticleHead, canonical, contentPlugin, createApp, createComponent, createPlugin, createUrls, dataPlugin, defineRoutes, envPlugin, feedLink, headPlugin, hreflang, i18nPlugin, jsonLd, logPlugin, meta, og, route, routerPlugin, sitePlugin, spaPlugin, twitter };
|