@moku-labs/web 2.2.2 → 2.3.1

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`). */
@@ -1925,7 +1925,7 @@ declare const spaPlugin: import("@moku-labs/core").PluginInstance<"spa", SpaConf
1925
1925
  el: Element;
1926
1926
  };
1927
1927
  }> & Record<never, never>;
1928
- declare namespace types_d_exports$1 {
1928
+ declare namespace types_d_exports$2 {
1929
1929
  export { DataConfig, DataEntry, DataProvider, DataState, DataWriteSummary };
1930
1930
  }
1931
1931
  /**
@@ -2072,7 +2072,43 @@ type DataProvider = {
2072
2072
  * ```
2073
2073
  */
2074
2074
  declare const dataPlugin: import("@moku-labs/core").PluginInstance<"data", DataConfig, DataState, DataProvider, {}> & Record<never, never>;
2075
- 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 {
2076
2112
  export { Api, Article, ArticleCard, ComputedFields, Config, ContentApiContext, ContentEvents, ContentProvider, ContentProviderState, EmbedFacade, EmbedFacadeProps, EmbedOptions, FileSystemContentOptions, Frontmatter, GalleryComponent, GalleryOptions, GalleryProps, GallerySlide, GalleryTransformOptions, LoadAllOptions, MermaidDiagramOptions, State };
2077
2113
  }
2078
2114
  /**
@@ -2571,6 +2607,140 @@ declare const contentPlugin: import("@moku-labs/core").PluginInstance<"content",
2571
2607
  paths: readonly string[];
2572
2608
  };
2573
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
+ };
2574
2744
  //#endregion
2575
2745
  //#region src/browser.d.ts
2576
2746
  /**
@@ -2674,4 +2844,4 @@ declare const createApp: <const ExtraPlugins extends readonly import("@moku-labs
2674
2844
  */
2675
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>]>>;
2676
2846
  //#endregion
2677
- 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",
@@ -4772,6 +4772,112 @@ const spaPlugin = createPlugin$1("spa", {
4772
4772
  onStop: disposeSpa
4773
4773
  });
4774
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
4775
4881
  //#region src/plugins/content/api.ts
4776
4882
  /** Actionable error when the content plugin is composed without any provider. */
4777
4883
  const NO_PROVIDER = "[web] content: no provider composed.\n Add fileSystemContent(...) to pluginConfigs.content.providers.";
@@ -5325,18 +5431,21 @@ const contentPlugin = createPlugin$1("content", {
5325
5431
  api: contentApi
5326
5432
  });
5327
5433
  //#endregion
5328
- //#region src/plugins/content/types.ts
5434
+ //#region src/plugins/collection/types.ts
5329
5435
  var types_exports = /* @__PURE__ */ __exportAll({});
5330
5436
  //#endregion
5331
- //#region src/plugins/data/types.ts
5437
+ //#region src/plugins/content/types.ts
5332
5438
  var types_exports$1 = /* @__PURE__ */ __exportAll({});
5333
5439
  //#endregion
5334
- //#region src/plugins/head/types.ts
5440
+ //#region src/plugins/data/types.ts
5335
5441
  var types_exports$2 = /* @__PURE__ */ __exportAll({});
5336
5442
  //#endregion
5337
- //#region src/plugins/router/types.ts
5443
+ //#region src/plugins/head/types.ts
5338
5444
  var types_exports$3 = /* @__PURE__ */ __exportAll({});
5339
5445
  //#endregion
5446
+ //#region src/plugins/router/types.ts
5447
+ var types_exports$4 = /* @__PURE__ */ __exportAll({});
5448
+ //#endregion
5340
5449
  //#region src/browser.ts
5341
5450
  /**
5342
5451
  * @file `@moku-labs/web/browser` — the browser-safe entry point.
@@ -5411,4 +5520,4 @@ const createApp = core.createApp;
5411
5520
  */
5412
5521
  const createPlugin = core.createPlugin;
5413
5522
  //#endregion
5414
- 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() {
@@ -0,0 +1,120 @@
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
+ Object.defineProperty(exports, "collectionUrl", {
110
+ enumerable: true,
111
+ get: function() {
112
+ return collectionUrl;
113
+ }
114
+ });
115
+ Object.defineProperty(exports, "relativeShardFile", {
116
+ enumerable: true,
117
+ get: function() {
118
+ return relativeShardFile;
119
+ }
120
+ });