@moku-labs/web 2.2.1 → 2.3.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.
@@ -277,7 +277,7 @@ interface PathMatcher {
277
277
  };
278
278
  } | null;
279
279
  }
280
- declare namespace types_d_exports$3 {
280
+ declare namespace types_d_exports$4 {
281
281
  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, ScrollMode, State$2 as State, TransitionMode, TypedRoute, Urls };
282
282
  }
283
283
  /**
@@ -738,7 +738,7 @@ type Config$2 = RouterConfig;
738
738
  type State$2 = RouterState;
739
739
  /** Re-export under the canonical `Api` name for the plugin-types barrel. */
740
740
  type Api$2 = RouterApi;
741
- declare namespace types_d_exports$2 {
741
+ declare namespace types_d_exports$3 {
742
742
  export { Api$1 as Api, ArticleMeta, Config$1 as Config, HeadConfig, HeadDefaults, HeadElement, ResolvedRoute, State$1 as State };
743
743
  }
744
744
  /**
@@ -913,7 +913,7 @@ type Api$1 = {
913
913
  */
914
914
  composeTitle(head: HeadConfig | undefined): string;
915
915
  };
916
- declare namespace types_d_exports$4 {
916
+ declare namespace types_d_exports$5 {
917
917
  export { AnyVNode, ExtractApi, ISLAND_HOOK_NAMES, IslandContext, IslandDef, IslandEventHandler, IslandEvents, IslandHooks, IslandInstance, IslandRender, IslandRouteSlice, IslandSpec, IslandSpecExtras, IslandStateFactory, NavigateOptions, PageData, RenderResult, ResolvedSpaConfig, ScrollMode, SpaApi, SpaConfig, SpaContext, SpaDataReader, SpaEmitFunction, SpaEvents, SpaKernel, SpaKernelDeps, SpaRequire, SpaState, TransitionMode };
918
918
  }
919
919
  /** Per-navigation options for the programmatic navigators (`ctx.navigate` / `app.spa.navigate`). */
@@ -1763,6 +1763,20 @@ interface ChannelOptions<T> {
1763
1763
  * order) on seed, closing the connect→load race. Defaults to `false` (deliver live).
1764
1764
  */
1765
1765
  bufferUntilSeed?: boolean;
1766
+ /**
1767
+ * Guard called on every socket close before the reconnect backoff is scheduled. Return `false`
1768
+ * to suppress reconnection (e.g. stop on a terminal close code like `4401` for 401 Unauthorized
1769
+ * / channel-not-found). The default — when omitted — always reconnects. The guard receives the
1770
+ * `CloseEvent`, so callers can inspect `event.code` / `event.reason` / `event.wasClean`.
1771
+ *
1772
+ * @param event - The `CloseEvent` from the closed socket.
1773
+ * @returns `true` (or `undefined`) to reconnect; `false` to stop permanently.
1774
+ * @example
1775
+ * ```ts
1776
+ * shouldReconnect: (event) => event.code !== 4401
1777
+ * ```
1778
+ */
1779
+ shouldReconnect?: (event: CloseEvent) => boolean;
1766
1780
  }
1767
1781
  /** A live realtime channel (the surface returned by {@link createChannel}). */
1768
1782
  interface Channel<T> {
@@ -1911,7 +1925,7 @@ declare const spaPlugin: import("@moku-labs/core").PluginInstance<"spa", SpaConf
1911
1925
  el: Element;
1912
1926
  };
1913
1927
  }> & Record<never, never>;
1914
- declare namespace types_d_exports$1 {
1928
+ declare namespace types_d_exports$2 {
1915
1929
  export { DataConfig, DataEntry, DataProvider, DataState, DataWriteSummary };
1916
1930
  }
1917
1931
  /**
@@ -2058,7 +2072,43 @@ type DataProvider = {
2058
2072
  * ```
2059
2073
  */
2060
2074
  declare const dataPlugin: import("@moku-labs/core").PluginInstance<"data", DataConfig, DataState, DataProvider, {}> & Record<never, never>;
2061
- declare namespace types_d_exports {
2075
+ //#endregion
2076
+ //#region src/plugins/collection/convention.d.ts
2077
+ /**
2078
+ * Compute the browser fetch URL for a `(collection, shard)` key: the `baseUrl`
2079
+ * prefix joined with the {@link shardSuffix}. Exported so standalone consumers can
2080
+ * derive the same URL without composing the plugin.
2081
+ *
2082
+ * @param baseUrl - The site-root URL prefix (ends with `/`, e.g. `"/"`).
2083
+ * @param collection - The collection name (e.g. `"bank"`).
2084
+ * @param shard - The shard key within the collection (e.g. `"en/animals"`).
2085
+ * @returns The site-root-relative shard URL (e.g. `/bank/en/animals.json`).
2086
+ * @example
2087
+ * ```ts
2088
+ * collectionUrl("/", "bank", "en/animals"); // "/bank/en/animals.json"
2089
+ * ```
2090
+ */
2091
+ declare function collectionUrl(baseUrl: string, collection: string, shard: string): string;
2092
+ //#endregion
2093
+ //#region src/plugins/collection/read.d.ts
2094
+ /**
2095
+ * Fetch + parse a build-authored collection shard over HTTP. Builds the shard URL
2096
+ * via {@link collectionUrl}, fetches it, and throws on a non-OK response so the
2097
+ * caller (`collection.at`) can decide whether to fall back.
2098
+ *
2099
+ * @template T - The expected shape of the parsed shard JSON.
2100
+ * @param baseUrl - The site-root URL prefix (ends with `/`, e.g. `"/"`).
2101
+ * @param collection - The collection name (e.g. `"bank"`).
2102
+ * @param shard - The shard key within the collection (e.g. `"en/animals"`).
2103
+ * @returns The parsed shard JSON, typed as `T`.
2104
+ * @throws {Error} If the fetch is not OK.
2105
+ * @example
2106
+ * ```ts
2107
+ * const animals = await loadCollectionShard<Animal[]>("/", "bank", "en/animals");
2108
+ * ```
2109
+ */
2110
+ declare function loadCollectionShard<T>(baseUrl: string, collection: string, shard: string): Promise<T>;
2111
+ declare namespace types_d_exports$1 {
2062
2112
  export { Api, Article, ArticleCard, ComputedFields, Config, ContentApiContext, ContentEvents, ContentProvider, ContentProviderState, EmbedFacade, EmbedFacadeProps, EmbedOptions, FileSystemContentOptions, Frontmatter, GalleryComponent, GalleryOptions, GalleryProps, GallerySlide, GalleryTransformOptions, LoadAllOptions, MermaidDiagramOptions, State };
2063
2113
  }
2064
2114
  /**
@@ -2557,6 +2607,140 @@ declare const contentPlugin: import("@moku-labs/core").PluginInstance<"content",
2557
2607
  paths: readonly string[];
2558
2608
  };
2559
2609
  }> & Record<never, never>;
2610
+ declare namespace types_d_exports {
2611
+ export { CollectionConfig, CollectionProvider, CollectionShard, CollectionState, CollectionWriteSummary };
2612
+ }
2613
+ /**
2614
+ * @file collection plugin — type definitions (Standard tier).
2615
+ *
2616
+ * The `collection` plugin is the **static-data collection provider** — the
2617
+ * collection-keyed sibling of the page-path-keyed `data` plugin. It owns ONE
2618
+ * thing: the contract `(collection, shard) → persisted JSON file`. It knows
2619
+ * NOTHING about what the data *is* — no domain types appear here. A consumer
2620
+ * decides its own shard shape (`at`'s `T`).
2621
+ *
2622
+ * - **Node (build):** `write(entries)` persists one JSON file per shard, keyed by
2623
+ * `(collection, shard)` via {@link CollectionProvider.fileFor}. The build
2624
+ * supplies the entries (it already authored the shards), so there is no
2625
+ * expansion here.
2626
+ * - **Browser (runtime):** `at(collection, shard)` fetches + caches that file as
2627
+ * `unknown`, which the consumer reads on demand.
2628
+ *
2629
+ * The Node-only file-writing code (`node:fs`) is isolated behind a lazy `import()`
2630
+ * inside `write()`, so composing `collection` in a browser app keeps the bundle
2631
+ * free of `node:*`.
2632
+ */
2633
+ /**
2634
+ * Configuration for {@link collectionPlugin}. All fields have defaults (see
2635
+ * `./config`).
2636
+ *
2637
+ * @example
2638
+ * ```ts
2639
+ * const cfg: CollectionConfig = { baseUrl: "/" };
2640
+ * ```
2641
+ */
2642
+ type CollectionConfig = {
2643
+ /**
2644
+ * READ side (browser): site-root URL prefix the client fetches the per-shard
2645
+ * JSON from. The collection name is the top directory under it, so the fetched
2646
+ * URL is `baseUrl + collection + "/" + shard + ".json"`. Default `"/"`.
2647
+ */
2648
+ baseUrl: string;
2649
+ };
2650
+ /** One build-authored shard to persist — the build produces these. */
2651
+ interface CollectionShard {
2652
+ /** The collection name — the top directory (e.g. `"bank"`). */
2653
+ collection: string;
2654
+ /**
2655
+ * The shard key WITHIN the collection (e.g. `"en/animals"`); may contain `/`,
2656
+ * which is preserved as a nested path. Maps to a file via
2657
+ * {@link CollectionProvider.fileFor}.
2658
+ */
2659
+ shard: string;
2660
+ /** The serializable data for this shard. */
2661
+ data: unknown;
2662
+ }
2663
+ /** Summary returned by {@link CollectionProvider.write} and cached in state. */
2664
+ interface CollectionWriteSummary {
2665
+ /** Number of per-shard JSON files written. */
2666
+ fileCount: number;
2667
+ /** Total bytes written across all files. */
2668
+ bytes: number;
2669
+ /** The written file paths, relative to the build `outDir`. */
2670
+ files: string[];
2671
+ }
2672
+ /**
2673
+ * Internal collection state. `lastWrite` records the most recent `write()` (Node);
2674
+ * `cache` memoizes fetched per-`(collection, shard)` data (browser, lazy). Both
2675
+ * empty until their respective side first runs.
2676
+ */
2677
+ interface CollectionState {
2678
+ /** Result of the last `write()`, or `null` if it has not run yet (Node). */
2679
+ lastWrite: CollectionWriteSummary | null;
2680
+ /** Per-`(collection, shard)` fetched data, cached after the first `at()` (browser). */
2681
+ cache: Map<string, unknown>;
2682
+ }
2683
+ /**
2684
+ * Public API mounted at `app.collection` — the static-data collection provider.
2685
+ * `write()` is the Node persist side; `at()` is the browser read side;
2686
+ * `urlFor`/`fileFor` are the pure URL convention shared by both so the written
2687
+ * file and fetched URL can never drift.
2688
+ *
2689
+ * @example
2690
+ * ```ts
2691
+ * // Node build (build supplies the shards it already authored):
2692
+ * await app.collection.write([{ collection: "bank", shard: "en/animals", data }]);
2693
+ *
2694
+ * // Browser: fetch a shard on demand, read by the consumer:
2695
+ * const raw = await app.collection.at("bank", "en/animals"); // unknown | null (null on failure)
2696
+ * ```
2697
+ */
2698
+ type CollectionProvider = {
2699
+ /**
2700
+ * READ (browser) — fetch (and cache) the persisted data for a `(collection,
2701
+ * shard)` key from `config.baseUrl`. Returns the raw parsed JSON as `unknown`;
2702
+ * returns `null` if the fetch or JSON parse fails.
2703
+ *
2704
+ * @param collection - The collection name (e.g. `"bank"`).
2705
+ * @param shard - The shard key within the collection (e.g. `"en/animals"`).
2706
+ * @returns The shard's raw data, or `null` on failure.
2707
+ */
2708
+ at(collection: string, shard: string): Promise<unknown | null>;
2709
+ /**
2710
+ * WRITE (Node) — persist one JSON file per entry, keyed by `(collection, shard)`
2711
+ * via {@link CollectionProvider.fileFor}. Called by the build after it authors
2712
+ * the shards. Lazily loads its `node:fs` writer, so it never contaminates a
2713
+ * browser bundle.
2714
+ *
2715
+ * @param entries - The per-shard data to persist.
2716
+ * @param options - Optional overrides.
2717
+ * @param options.outDir - Build output directory to write under (default `./dist`).
2718
+ * @returns A summary of the written files.
2719
+ */
2720
+ write(entries: readonly CollectionShard[], options?: {
2721
+ outDir?: string;
2722
+ }): Promise<CollectionWriteSummary>;
2723
+ /**
2724
+ * PURE — the browser fetch URL for a `(collection, shard)` key (e.g.
2725
+ * `("bank", "en/animals")` → `/bank/en/animals.json`). Shared with
2726
+ * {@link CollectionProvider.fileFor}.
2727
+ *
2728
+ * @param collection - The collection name.
2729
+ * @param shard - The shard key within the collection.
2730
+ * @returns The site-root-relative shard URL.
2731
+ */
2732
+ urlFor(collection: string, shard: string): string;
2733
+ /**
2734
+ * PURE — the `outDir`-relative file path for a `(collection, shard)` key (e.g.
2735
+ * `("bank", "en/animals")` → `bank/en/animals.json`). Shared with
2736
+ * {@link CollectionProvider.urlFor}.
2737
+ *
2738
+ * @param collection - The collection name.
2739
+ * @param shard - The shard key within the collection.
2740
+ * @returns The output-relative file path.
2741
+ */
2742
+ fileFor(collection: string, shard: string): string;
2743
+ };
2560
2744
  //#endregion
2561
2745
  //#region src/browser.d.ts
2562
2746
  /**
@@ -2660,4 +2844,4 @@ declare const createApp: <const ExtraPlugins extends readonly import("@moku-labs
2660
2844
  */
2661
2845
  declare const createPlugin: import("@moku-labs/core").BoundCreatePluginFunction<Config$5, Events, import("@moku-labs/core").CoreApisFromTuple<[import("@moku-labs/core").CorePluginInstance<"log", import("@moku-labs/common").LogConfig, import("@moku-labs/common").LogState, import("@moku-labs/common").LogApi>, import("@moku-labs/core").CorePluginInstance<"env", import("@moku-labs/common").EnvConfig, import("@moku-labs/common").EnvState, import("@moku-labs/common").EnvApi>]>>;
2662
2846
  //#endregion
2663
- export { type Channel, type ChannelKeepAlive, type ChannelOptions, type ChannelReconnect, types_d_exports as Content, types_d_exports$1 as Data, type Env, types_d_exports$2 as Head, type Log, types_d_exports$3 as Router, types_d_exports$4 as Spa, browserEnv, buildArticleHead, canonical, contentPlugin, createApp, createChannel, createIsland, createPlugin, createUrls, dataPlugin, defineRoutes, envPlugin, feedLink, hardNavigate, headPlugin, hreflang, i18nPlugin, jsonLd, lazyEmbed, logPlugin, meta, navigate, og, route, routerPlugin, sitePlugin, spaPlugin, twitter };
2847
+ export { type Channel, type ChannelKeepAlive, type ChannelOptions, type ChannelReconnect, types_d_exports as Collection, types_d_exports$1 as Content, types_d_exports$2 as Data, type Env, types_d_exports$3 as Head, type Log, types_d_exports$4 as Router, types_d_exports$5 as Spa, browserEnv, buildArticleHead, canonical, collectionUrl, contentPlugin, createApp, createChannel, createIsland, createPlugin, createUrls, dataPlugin, defineRoutes, envPlugin, feedLink, hardNavigate, headPlugin, hreflang, i18nPlugin, jsonLd, lazyEmbed, loadCollectionShard, logPlugin, meta, navigate, og, route, routerPlugin, sitePlugin, spaPlugin, twitter };
package/dist/browser.mjs CHANGED
@@ -2640,7 +2640,7 @@ function syncHead(_head, doc) {
2640
2640
  }
2641
2641
  //#endregion
2642
2642
  //#region src/plugins/spa/types.ts
2643
- var types_exports$4 = /* @__PURE__ */ __exportAll({ ISLAND_HOOK_NAMES: () => ISLAND_HOOK_NAMES });
2643
+ var types_exports$5 = /* @__PURE__ */ __exportAll({ ISLAND_HOOK_NAMES: () => ISLAND_HOOK_NAMES });
2644
2644
  /** Allowed hook names — single source of truth for fail-fast validation. */
2645
2645
  const ISLAND_HOOK_NAMES = [
2646
2646
  "onCreate",
@@ -4500,10 +4500,12 @@ function createChannel(options) {
4500
4500
  ws.addEventListener("open", () => {
4501
4501
  reconnectAttempts = 0;
4502
4502
  });
4503
- ws.addEventListener("close", () => {
4503
+ ws.addEventListener("close", (event) => {
4504
4504
  if (socket !== ws || desiredId === void 0) return;
4505
4505
  socket = void 0;
4506
- if (reconnectCfg) scheduleReconnect();
4506
+ if (!reconnectCfg) return;
4507
+ if (options.shouldReconnect?.(event) === false) return;
4508
+ scheduleReconnect();
4507
4509
  });
4508
4510
  }
4509
4511
  /**
@@ -4770,6 +4772,112 @@ const spaPlugin = createPlugin$1("spa", {
4770
4772
  onStop: disposeSpa
4771
4773
  });
4772
4774
  //#endregion
4775
+ //#region src/plugins/collection/convention.ts
4776
+ /**
4777
+ * @file collection plugin — the pure URL/file convention (no `node:*`, no DOM).
4778
+ *
4779
+ * ONE function maps a `(collection, shard)` key to its shard suffix, so the browser
4780
+ * fetch URL (`baseUrl + suffix`) and the on-disk file (`outDir + "/" + suffix`) are
4781
+ * derived from the same source and cannot drift. The collection name is the top
4782
+ * directory and the shard becomes a `.json` file beneath it (its INTERNAL slashes
4783
+ * are kept as nested dirs):
4784
+ * ("bank", "en/animals") → "bank/en/animals.json"
4785
+ * ("bank", "ru") → "bank/ru.json"
4786
+ * ("/bank/", "/en/") → "bank/en.json" (outer slashes trimmed)
4787
+ *
4788
+ * Encoding split: the FETCH suffix ({@link shardSuffix}) keeps the key's
4789
+ * percent-encoding (the browser requests the encoded path); the FILE path
4790
+ * ({@link relativeShardFile}) decodes it so the on-disk name is literal.
4791
+ */
4792
+ /**
4793
+ * Strip leading/trailing slashes from `collection` and `shard` (keeping the
4794
+ * shard's INTERNAL slashes as nested path segments), then join them as
4795
+ * `` `${collection}/${shard}.json` `` — the suffix shared by the fetch URL and the
4796
+ * on-disk file.
4797
+ *
4798
+ * @param collection - The collection name (e.g. `"bank"`).
4799
+ * @param shard - The shard key within the collection (e.g. `"en/animals"`).
4800
+ * @returns The shard suffix (e.g. `"bank/en/animals.json"`).
4801
+ * @example
4802
+ * ```ts
4803
+ * shardSuffix("bank", "en/animals"); // "bank/en/animals.json"
4804
+ * shardSuffix("/bank/", "/ru/"); // "bank/ru.json"
4805
+ * ```
4806
+ */
4807
+ function shardSuffix(collection, shard) {
4808
+ return `${trimSlashes(collection)}/${trimSlashes(shard)}.json`;
4809
+ }
4810
+ /**
4811
+ * Strip any leading and trailing slashes from a path segment, leaving its internal
4812
+ * slashes intact.
4813
+ *
4814
+ * @param segment - The raw `collection` or `shard` value.
4815
+ * @returns The segment without outer slashes.
4816
+ * @example
4817
+ * ```ts
4818
+ * trimSlashes("/en/animals/"); // "en/animals"
4819
+ * ```
4820
+ */
4821
+ function trimSlashes(segment) {
4822
+ let trimmed = segment;
4823
+ while (trimmed.startsWith("/")) trimmed = trimmed.slice(1);
4824
+ while (trimmed.endsWith("/")) trimmed = trimmed.slice(0, -1);
4825
+ return trimmed;
4826
+ }
4827
+ /**
4828
+ * Compute the browser fetch URL for a `(collection, shard)` key: the `baseUrl`
4829
+ * prefix joined with the {@link shardSuffix}. Exported so standalone consumers can
4830
+ * derive the same URL without composing the plugin.
4831
+ *
4832
+ * @param baseUrl - The site-root URL prefix (ends with `/`, e.g. `"/"`).
4833
+ * @param collection - The collection name (e.g. `"bank"`).
4834
+ * @param shard - The shard key within the collection (e.g. `"en/animals"`).
4835
+ * @returns The site-root-relative shard URL (e.g. `/bank/en/animals.json`).
4836
+ * @example
4837
+ * ```ts
4838
+ * collectionUrl("/", "bank", "en/animals"); // "/bank/en/animals.json"
4839
+ * ```
4840
+ */
4841
+ function collectionUrl(baseUrl, collection, shard) {
4842
+ return `${baseUrl}${shardSuffix(collection, shard)}`;
4843
+ }
4844
+ //#endregion
4845
+ //#region src/plugins/collection/read.ts
4846
+ /**
4847
+ * @file `loadCollectionShard` — the collection plugin's standalone BROWSER read
4848
+ * primitive. Internal to the `collection` plugin AND exported for standalone
4849
+ * consumers: `collection.at(...)` wraps it, and a room-layer consumer can import it
4850
+ * directly from `@moku-labs/web/browser`.
4851
+ *
4852
+ * Unlike the `data` plugin's isomorphic `loadJson`, this uses the `fetch` global
4853
+ * DIRECTLY (no `node:fs` branch). Consumers — and tests — rely on `fetch` even
4854
+ * under Node test environments, so there is no document sniffing here: a shard is
4855
+ * always fetched over HTTP from the build-authored URL. This keeps the module
4856
+ * fully node-free.
4857
+ */
4858
+ /**
4859
+ * Fetch + parse a build-authored collection shard over HTTP. Builds the shard URL
4860
+ * via {@link collectionUrl}, fetches it, and throws on a non-OK response so the
4861
+ * caller (`collection.at`) can decide whether to fall back.
4862
+ *
4863
+ * @template T - The expected shape of the parsed shard JSON.
4864
+ * @param baseUrl - The site-root URL prefix (ends with `/`, e.g. `"/"`).
4865
+ * @param collection - The collection name (e.g. `"bank"`).
4866
+ * @param shard - The shard key within the collection (e.g. `"en/animals"`).
4867
+ * @returns The parsed shard JSON, typed as `T`.
4868
+ * @throws {Error} If the fetch is not OK.
4869
+ * @example
4870
+ * ```ts
4871
+ * const animals = await loadCollectionShard<Animal[]>("/", "bank", "en/animals");
4872
+ * ```
4873
+ */
4874
+ async function loadCollectionShard(baseUrl, collection, shard) {
4875
+ const url = collectionUrl(baseUrl, collection, shard);
4876
+ const response = await fetch(url);
4877
+ if (!response.ok) throw new Error(`[web] collection: failed to fetch ${url} (${String(response.status)}).`);
4878
+ return response.json();
4879
+ }
4880
+ //#endregion
4773
4881
  //#region src/plugins/content/api.ts
4774
4882
  /** Actionable error when the content plugin is composed without any provider. */
4775
4883
  const NO_PROVIDER = "[web] content: no provider composed.\n Add fileSystemContent(...) to pluginConfigs.content.providers.";
@@ -5323,18 +5431,21 @@ const contentPlugin = createPlugin$1("content", {
5323
5431
  api: contentApi
5324
5432
  });
5325
5433
  //#endregion
5326
- //#region src/plugins/content/types.ts
5434
+ //#region src/plugins/collection/types.ts
5327
5435
  var types_exports = /* @__PURE__ */ __exportAll({});
5328
5436
  //#endregion
5329
- //#region src/plugins/data/types.ts
5437
+ //#region src/plugins/content/types.ts
5330
5438
  var types_exports$1 = /* @__PURE__ */ __exportAll({});
5331
5439
  //#endregion
5332
- //#region src/plugins/head/types.ts
5440
+ //#region src/plugins/data/types.ts
5333
5441
  var types_exports$2 = /* @__PURE__ */ __exportAll({});
5334
5442
  //#endregion
5335
- //#region src/plugins/router/types.ts
5443
+ //#region src/plugins/head/types.ts
5336
5444
  var types_exports$3 = /* @__PURE__ */ __exportAll({});
5337
5445
  //#endregion
5446
+ //#region src/plugins/router/types.ts
5447
+ var types_exports$4 = /* @__PURE__ */ __exportAll({});
5448
+ //#endregion
5338
5449
  //#region src/browser.ts
5339
5450
  /**
5340
5451
  * @file `@moku-labs/web/browser` — the browser-safe entry point.
@@ -5409,4 +5520,4 @@ const createApp = core.createApp;
5409
5520
  */
5410
5521
  const createPlugin = core.createPlugin;
5411
5522
  //#endregion
5412
- export { types_exports as Content, types_exports$1 as Data, types_exports$2 as Head, types_exports$3 as Router, types_exports$4 as Spa, browserEnv, buildArticleHead, canonical, contentPlugin, createApp, createChannel, createIsland, createPlugin, createUrls, dataPlugin, defineRoutes, envPlugin, feedLink, hardNavigate, headPlugin, hreflang, i18nPlugin, jsonLd, lazyEmbed, logPlugin, meta, navigate, og, route, routerPlugin, sitePlugin, spaPlugin, twitter };
5523
+ export { types_exports as Collection, types_exports$1 as Content, types_exports$2 as Data, types_exports$3 as Head, types_exports$4 as Router, types_exports$5 as Spa, browserEnv, buildArticleHead, canonical, collectionUrl, contentPlugin, createApp, createChannel, createIsland, createPlugin, createUrls, dataPlugin, defineRoutes, envPlugin, feedLink, hardNavigate, headPlugin, hreflang, i18nPlugin, jsonLd, lazyEmbed, loadCollectionShard, logPlugin, meta, navigate, og, route, routerPlugin, sitePlugin, spaPlugin, twitter };
@@ -0,0 +1,109 @@
1
+ //#region src/plugins/collection/convention.ts
2
+ /**
3
+ * @file collection plugin — the pure URL/file convention (no `node:*`, no DOM).
4
+ *
5
+ * ONE function maps a `(collection, shard)` key to its shard suffix, so the browser
6
+ * fetch URL (`baseUrl + suffix`) and the on-disk file (`outDir + "/" + suffix`) are
7
+ * derived from the same source and cannot drift. The collection name is the top
8
+ * directory and the shard becomes a `.json` file beneath it (its INTERNAL slashes
9
+ * are kept as nested dirs):
10
+ * ("bank", "en/animals") → "bank/en/animals.json"
11
+ * ("bank", "ru") → "bank/ru.json"
12
+ * ("/bank/", "/en/") → "bank/en.json" (outer slashes trimmed)
13
+ *
14
+ * Encoding split: the FETCH suffix ({@link shardSuffix}) keeps the key's
15
+ * percent-encoding (the browser requests the encoded path); the FILE path
16
+ * ({@link relativeShardFile}) decodes it so the on-disk name is literal.
17
+ */
18
+ /**
19
+ * Strip leading/trailing slashes from `collection` and `shard` (keeping the
20
+ * shard's INTERNAL slashes as nested path segments), then join them as
21
+ * `` `${collection}/${shard}.json` `` — the suffix shared by the fetch URL and the
22
+ * on-disk file.
23
+ *
24
+ * @param collection - The collection name (e.g. `"bank"`).
25
+ * @param shard - The shard key within the collection (e.g. `"en/animals"`).
26
+ * @returns The shard suffix (e.g. `"bank/en/animals.json"`).
27
+ * @example
28
+ * ```ts
29
+ * shardSuffix("bank", "en/animals"); // "bank/en/animals.json"
30
+ * shardSuffix("/bank/", "/ru/"); // "bank/ru.json"
31
+ * ```
32
+ */
33
+ function shardSuffix(collection, shard) {
34
+ return `${trimSlashes(collection)}/${trimSlashes(shard)}.json`;
35
+ }
36
+ /**
37
+ * Strip any leading and trailing slashes from a path segment, leaving its internal
38
+ * slashes intact.
39
+ *
40
+ * @param segment - The raw `collection` or `shard` value.
41
+ * @returns The segment without outer slashes.
42
+ * @example
43
+ * ```ts
44
+ * trimSlashes("/en/animals/"); // "en/animals"
45
+ * ```
46
+ */
47
+ function trimSlashes(segment) {
48
+ let trimmed = segment;
49
+ while (trimmed.startsWith("/")) trimmed = trimmed.slice(1);
50
+ while (trimmed.endsWith("/")) trimmed = trimmed.slice(0, -1);
51
+ return trimmed;
52
+ }
53
+ /**
54
+ * Compute the browser fetch URL for a `(collection, shard)` key: the `baseUrl`
55
+ * prefix joined with the {@link shardSuffix}. Exported so standalone consumers can
56
+ * derive the same URL without composing the plugin.
57
+ *
58
+ * @param baseUrl - The site-root URL prefix (ends with `/`, e.g. `"/"`).
59
+ * @param collection - The collection name (e.g. `"bank"`).
60
+ * @param shard - The shard key within the collection (e.g. `"en/animals"`).
61
+ * @returns The site-root-relative shard URL (e.g. `/bank/en/animals.json`).
62
+ * @example
63
+ * ```ts
64
+ * collectionUrl("/", "bank", "en/animals"); // "/bank/en/animals.json"
65
+ * ```
66
+ */
67
+ function collectionUrl(baseUrl, collection, shard) {
68
+ return `${baseUrl}${shardSuffix(collection, shard)}`;
69
+ }
70
+ /**
71
+ * Decode a shard suffix's percent-escapes so the on-disk file carries the literal
72
+ * name (servers decode the encoded fetch URL before filesystem lookup). Falls back
73
+ * to the raw suffix on malformed escapes.
74
+ *
75
+ * @param suffix - The computed shard suffix (possibly percent-encoded).
76
+ * @returns The decoded suffix, or the raw suffix on malformed escapes.
77
+ * @example
78
+ * ```ts
79
+ * decodeSuffix("bank/en/a%20%26%20b.json"); // "bank/en/a & b.json"
80
+ * ```
81
+ */
82
+ function decodeSuffix(suffix) {
83
+ try {
84
+ return decodeURIComponent(suffix);
85
+ } catch {
86
+ return suffix;
87
+ }
88
+ }
89
+ /**
90
+ * Compute the `outDir`-relative shard file for a `(collection, shard)` key — the
91
+ * DECODED {@link shardSuffix} (servers resolve the decoded request path against
92
+ * literal file names). The collection name IS the top directory, so there is no
93
+ * extra output-dir wrapper. Shared by the Node writer and the pure `fileFor`
94
+ * accessor so the written file and the reported path never drift.
95
+ *
96
+ * @param collection - The collection name (e.g. `"bank"`).
97
+ * @param shard - The shard key within the collection (e.g. `"en/animals"`).
98
+ * @returns The `outDir`-relative file path (e.g. `bank/en/animals.json`).
99
+ * @example
100
+ * ```ts
101
+ * relativeShardFile("bank", "en/animals"); // "bank/en/animals.json"
102
+ * relativeShardFile("bank", "a%20%26%20b"); // "bank/a & b.json"
103
+ * ```
104
+ */
105
+ function relativeShardFile(collection, shard) {
106
+ return decodeSuffix(shardSuffix(collection, shard));
107
+ }
108
+ //#endregion
109
+ export { relativeShardFile as n, collectionUrl as t };
@@ -1,34 +1,3 @@
1
- //#region \0rolldown/runtime.js
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __exportAll = (all, no_symbols) => {
9
- let target = {};
10
- for (var name in all) __defProp(target, name, {
11
- get: all[name],
12
- enumerable: true
13
- });
14
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
15
- return target;
16
- };
17
- var __copyProps = (to, from, except, desc) => {
18
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
19
- key = keys[i];
20
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
21
- get: ((k) => from[k]).bind(null, key),
22
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
23
- });
24
- }
25
- return to;
26
- };
27
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
28
- value: mod,
29
- enumerable: true
30
- }) : target, mod));
31
- //#endregion
32
1
  //#region src/plugins/data/convention.ts
33
2
  /**
34
3
  * @file data plugin — the pure URL/file convention (no `node:*`, no DOM).
@@ -102,18 +71,6 @@ function relativeDataFile(outputDir, path) {
102
71
  return `${outputDir.endsWith("/") ? outputDir.slice(0, -1) : outputDir}/${decodeSuffix(dataSuffix(path))}`;
103
72
  }
104
73
  //#endregion
105
- Object.defineProperty(exports, "__exportAll", {
106
- enumerable: true,
107
- get: function() {
108
- return __exportAll;
109
- }
110
- });
111
- Object.defineProperty(exports, "__toESM", {
112
- enumerable: true,
113
- get: function() {
114
- return __toESM;
115
- }
116
- });
117
74
  Object.defineProperty(exports, "dataSuffix", {
118
75
  enumerable: true,
119
76
  get: function() {