@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.
@@ -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
+ });
package/dist/index.cjs CHANGED
@@ -1,5 +1,37 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_convention = require("./convention-BpDfzX7e.cjs");
2
+ //#region \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __exportAll = (all, no_symbols) => {
10
+ let target = {};
11
+ for (var name in all) __defProp(target, name, {
12
+ get: all[name],
13
+ enumerable: true
14
+ });
15
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
16
+ return target;
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
20
+ key = keys[i];
21
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
22
+ get: ((k) => from[k]).bind(null, key),
23
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
24
+ });
25
+ }
26
+ return to;
27
+ };
28
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
29
+ value: mod,
30
+ enumerable: true
31
+ }) : target, mod));
32
+ //#endregion
33
+ const require_convention = require("./convention-CLIDd9k-.cjs");
34
+ const require_convention$1 = require("./convention-CsJw7CML.cjs");
3
35
  let _moku_labs_common = require("@moku-labs/common");
4
36
  let _moku_labs_core = require("@moku-labs/core");
5
37
  let node_fs = require("node:fs");
@@ -7,8 +39,8 @@ let node_crypto = require("node:crypto");
7
39
  let node_fs_promises = require("node:fs/promises");
8
40
  let node_os = require("node:os");
9
41
  let node_path = require("node:path");
10
- let node_path$1 = require_convention.__toESM(node_path, 1);
11
- node_path = require_convention.__toESM(node_path);
42
+ let node_path$1 = __toESM(node_path, 1);
43
+ node_path = __toESM(node_path);
12
44
  let feed = require("feed");
13
45
  let preact = require("preact");
14
46
  let preact_render_to_string = require("preact-render-to-string");
@@ -17,31 +49,31 @@ let node_readline = require("node:readline");
17
49
  let node_url = require("node:url");
18
50
  let _moku_labs_common_cli = require("@moku-labs/common/cli");
19
51
  let gray_matter = require("gray-matter");
20
- gray_matter = require_convention.__toESM(gray_matter, 1);
52
+ gray_matter = __toESM(gray_matter, 1);
21
53
  let _shikijs_rehype = require("@shikijs/rehype");
22
- _shikijs_rehype = require_convention.__toESM(_shikijs_rehype, 1);
54
+ _shikijs_rehype = __toESM(_shikijs_rehype, 1);
23
55
  let rehype_sanitize = require("rehype-sanitize");
24
- rehype_sanitize = require_convention.__toESM(rehype_sanitize, 1);
56
+ rehype_sanitize = __toESM(rehype_sanitize, 1);
25
57
  let rehype_stringify = require("rehype-stringify");
26
- rehype_stringify = require_convention.__toESM(rehype_stringify, 1);
58
+ rehype_stringify = __toESM(rehype_stringify, 1);
27
59
  let unified = require("unified");
28
60
  let rehype_raw = require("rehype-raw");
29
- rehype_raw = require_convention.__toESM(rehype_raw, 1);
61
+ rehype_raw = __toESM(rehype_raw, 1);
30
62
  let remark_directive = require("remark-directive");
31
- remark_directive = require_convention.__toESM(remark_directive, 1);
63
+ remark_directive = __toESM(remark_directive, 1);
32
64
  let remark_frontmatter = require("remark-frontmatter");
33
- remark_frontmatter = require_convention.__toESM(remark_frontmatter, 1);
65
+ remark_frontmatter = __toESM(remark_frontmatter, 1);
34
66
  let remark_gfm = require("remark-gfm");
35
- remark_gfm = require_convention.__toESM(remark_gfm, 1);
67
+ remark_gfm = __toESM(remark_gfm, 1);
36
68
  let remark_parse = require("remark-parse");
37
- remark_parse = require_convention.__toESM(remark_parse, 1);
69
+ remark_parse = __toESM(remark_parse, 1);
38
70
  let remark_rehype = require("remark-rehype");
39
- remark_rehype = require_convention.__toESM(remark_rehype, 1);
71
+ remark_rehype = __toESM(remark_rehype, 1);
40
72
  let unist_util_visit = require("unist-util-visit");
41
73
  let preact_jsx_runtime = require("preact/jsx-runtime");
42
74
  let hast_util_sanitize = require("hast-util-sanitize");
43
75
  let reading_time = require("reading-time");
44
- reading_time = require_convention.__toESM(reading_time, 1);
76
+ reading_time = __toESM(reading_time, 1);
45
77
  let node_process = require("node:process");
46
78
  //#region src/config.ts
47
79
  /**
@@ -4460,7 +4492,7 @@ function dataApi(ctx) {
4460
4492
  * ```
4461
4493
  */
4462
4494
  async write(entries, options) {
4463
- const { writeData } = await Promise.resolve().then(() => require("./writer-JdhX1Wld.cjs"));
4495
+ const { writeData } = await Promise.resolve().then(() => require("./writer-BEM-Pbgb.cjs"));
4464
4496
  return writeData(ctx, entries, options);
4465
4497
  },
4466
4498
  /**
@@ -4544,7 +4576,7 @@ function createDataState(_ctx) {
4544
4576
  * isInvalidBaseUrl("_data"); // true
4545
4577
  * ```
4546
4578
  */
4547
- function isInvalidBaseUrl(baseUrl) {
4579
+ function isInvalidBaseUrl$1(baseUrl) {
4548
4580
  return typeof baseUrl !== "string" || !baseUrl.startsWith("/");
4549
4581
  }
4550
4582
  /**
@@ -4559,7 +4591,7 @@ function isInvalidBaseUrl(baseUrl) {
4559
4591
  * ```
4560
4592
  */
4561
4593
  function validateDataConfig(config) {
4562
- if (isInvalidBaseUrl(config.baseUrl)) throw new Error(`[web] data.baseUrl: must be a site-root-relative URL path starting with "/" (e.g. "/_data/").`);
4594
+ if (isInvalidBaseUrl$1(config.baseUrl)) throw new Error(`[web] data.baseUrl: must be a site-root-relative URL path starting with "/" (e.g. "/_data/").`);
4563
4595
  }
4564
4596
  //#endregion
4565
4597
  //#region src/plugins/data/index.ts
@@ -9181,6 +9213,252 @@ const cliPlugin = createPlugin$1("cli", {
9181
9213
  api: createApi$1
9182
9214
  });
9183
9215
  //#endregion
9216
+ //#region src/plugins/collection/read.ts
9217
+ /**
9218
+ * @file `loadCollectionShard` — the collection plugin's standalone BROWSER read
9219
+ * primitive. Internal to the `collection` plugin AND exported for standalone
9220
+ * consumers: `collection.at(...)` wraps it, and a room-layer consumer can import it
9221
+ * directly from `@moku-labs/web/browser`.
9222
+ *
9223
+ * Unlike the `data` plugin's isomorphic `loadJson`, this uses the `fetch` global
9224
+ * DIRECTLY (no `node:fs` branch). Consumers — and tests — rely on `fetch` even
9225
+ * under Node test environments, so there is no document sniffing here: a shard is
9226
+ * always fetched over HTTP from the build-authored URL. This keeps the module
9227
+ * fully node-free.
9228
+ */
9229
+ /**
9230
+ * Fetch + parse a build-authored collection shard over HTTP. Builds the shard URL
9231
+ * via {@link collectionUrl}, fetches it, and throws on a non-OK response so the
9232
+ * caller (`collection.at`) can decide whether to fall back.
9233
+ *
9234
+ * @template T - The expected shape of the parsed shard JSON.
9235
+ * @param baseUrl - The site-root URL prefix (ends with `/`, e.g. `"/"`).
9236
+ * @param collection - The collection name (e.g. `"bank"`).
9237
+ * @param shard - The shard key within the collection (e.g. `"en/animals"`).
9238
+ * @returns The parsed shard JSON, typed as `T`.
9239
+ * @throws {Error} If the fetch is not OK.
9240
+ * @example
9241
+ * ```ts
9242
+ * const animals = await loadCollectionShard<Animal[]>("/", "bank", "en/animals");
9243
+ * ```
9244
+ */
9245
+ async function loadCollectionShard(baseUrl, collection, shard) {
9246
+ const url = require_convention$1.collectionUrl(baseUrl, collection, shard);
9247
+ const response = await fetch(url);
9248
+ if (!response.ok) throw new Error(`[web] collection: failed to fetch ${url} (${String(response.status)}).`);
9249
+ return response.json();
9250
+ }
9251
+ //#endregion
9252
+ //#region src/plugins/collection/api.ts
9253
+ /**
9254
+ * @file collection plugin — API factory (the collection provider surface).
9255
+ *
9256
+ * Node-free by construction: this module statically imports only types + the pure
9257
+ * convention + the node-free `read` primitive. The Node write side (`write()`)
9258
+ * reaches its `node:fs` writer through a lazy `await import("./writer")` at call
9259
+ * time, so a browser bundle that composes `collection` for the read side never
9260
+ * pulls `node:*`. The read side (`at()`) uses only the `fetch`-based
9261
+ * `loadCollectionShard`.
9262
+ */
9263
+ /**
9264
+ * Builds the collection provider — the static-data bridge. `write()` is the Node
9265
+ * persist side; `at()` is the browser read side; `urlFor`/`fileFor` are the pure
9266
+ * convention. No `onStart`/`onStop` (holds no long-lived resource).
9267
+ *
9268
+ * @param ctx - The collection plugin context.
9269
+ * @returns The {@link CollectionProvider} mounted at `app.collection`.
9270
+ * @example
9271
+ * ```ts
9272
+ * const api = collectionApi(ctx);
9273
+ * await api.write([{ collection: "bank", shard: "en/animals", data }]); // Node build
9274
+ * await api.at("bank", "en/animals"); // browser
9275
+ * ```
9276
+ */
9277
+ function collectionApi(ctx) {
9278
+ return {
9279
+ /**
9280
+ * READ (browser) — fetch (and cache) the persisted data for a `(collection,
9281
+ * shard)` key. Returns the raw JSON as `unknown`; returns `null` if the fetch
9282
+ * or JSON parse fails (so the consumer can fall back). Wraps the throwing
9283
+ * {@link loadCollectionShard} reader into a soft null-returning app read.
9284
+ *
9285
+ * @param collection - The collection name (e.g. `"bank"`).
9286
+ * @param shard - The shard key within the collection (e.g. `"en/animals"`).
9287
+ * @returns The shard's raw data, or `null` on failure.
9288
+ * @example
9289
+ * ```ts
9290
+ * const raw = await api.at("bank", "en/animals");
9291
+ * ```
9292
+ */
9293
+ async at(collection, shard) {
9294
+ const key = `${collection}/${shard}`;
9295
+ if (ctx.state.cache.has(key)) return ctx.state.cache.get(key);
9296
+ try {
9297
+ const data = await loadCollectionShard(ctx.config.baseUrl, collection, shard);
9298
+ ctx.state.cache.set(key, data);
9299
+ return data;
9300
+ } catch {
9301
+ return null;
9302
+ }
9303
+ },
9304
+ /**
9305
+ * WRITE (Node) — persist one JSON file per entry, keyed by `(collection,
9306
+ * shard)`. Called by the build after it authors the shards. Lazily loads its
9307
+ * `node:fs` writer (keeping a browser bundle node-free).
9308
+ *
9309
+ * @param entries - The per-shard data to persist.
9310
+ * @param options - Optional `{ outDir }` override (defaults to `./dist`).
9311
+ * @param options.outDir - Build output directory the write happens under.
9312
+ * @returns A summary of the written files.
9313
+ * @example
9314
+ * ```ts
9315
+ * await api.write([{ collection: "bank", shard: "ru", data }], { outDir: "dist" });
9316
+ * ```
9317
+ */
9318
+ async write(entries, options) {
9319
+ const { writeCollection } = await Promise.resolve().then(() => require("./writer-BBtgz9wt.cjs"));
9320
+ return writeCollection(ctx, entries, options);
9321
+ },
9322
+ /**
9323
+ * PURE — the browser fetch URL for a `(collection, shard)` key.
9324
+ *
9325
+ * @param collection - The collection name.
9326
+ * @param shard - The shard key within the collection.
9327
+ * @returns The site-root-relative shard URL.
9328
+ * @example
9329
+ * ```ts
9330
+ * api.urlFor("bank", "en/animals"); // "/bank/en/animals.json"
9331
+ * ```
9332
+ */
9333
+ urlFor(collection, shard) {
9334
+ return require_convention$1.collectionUrl(ctx.config.baseUrl, collection, shard);
9335
+ },
9336
+ /**
9337
+ * PURE — the `outDir`-relative file path for a `(collection, shard)` key.
9338
+ *
9339
+ * @param collection - The collection name.
9340
+ * @param shard - The shard key within the collection.
9341
+ * @returns The output-relative file path.
9342
+ * @example
9343
+ * ```ts
9344
+ * api.fileFor("bank", "en/animals"); // "bank/en/animals.json"
9345
+ * ```
9346
+ */
9347
+ fileFor(collection, shard) {
9348
+ return require_convention$1.relativeShardFile(collection, shard);
9349
+ }
9350
+ };
9351
+ }
9352
+ //#endregion
9353
+ //#region src/plugins/collection/config.ts
9354
+ /**
9355
+ * Typed default collection config (R6: no inline `as`). `baseUrl` is the READ-side
9356
+ * site-root URL prefix the browser fetches shards from; the collection name is the
9357
+ * top directory under it. The default `"/"` serves shards straight from the site
9358
+ * root (e.g. `/bank/en/animals.json`).
9359
+ *
9360
+ * @example
9361
+ * ```ts
9362
+ * createPlugin("collection", { config: defaultCollectionConfig });
9363
+ * ```
9364
+ */
9365
+ const defaultCollectionConfig = { baseUrl: "/" };
9366
+ //#endregion
9367
+ //#region src/plugins/collection/state.ts
9368
+ /**
9369
+ * Creates initial collection state: a null `lastWrite` slot (populated by the Node
9370
+ * `write()` side) and an empty `cache` (populated lazily by the browser
9371
+ * `at(collection, shard)` side on first fetch).
9372
+ *
9373
+ * @param _ctx - Minimal context with global and config.
9374
+ * @param _ctx.global - Global framework configuration.
9375
+ * @param _ctx.config - Resolved plugin configuration.
9376
+ * @returns Fresh collection state with no recorded write and an empty per-shard cache.
9377
+ * @example
9378
+ * ```ts
9379
+ * const state = createCollectionState({ global: {}, config });
9380
+ * ```
9381
+ */
9382
+ function createCollectionState(_ctx) {
9383
+ return {
9384
+ lastWrite: null,
9385
+ cache: /* @__PURE__ */ new Map()
9386
+ };
9387
+ }
9388
+ //#endregion
9389
+ //#region src/plugins/collection/validate.ts
9390
+ /**
9391
+ * Reports whether a `baseUrl` value is invalid: it must be a string that ends with
9392
+ * a `"/"` so the collection name appends cleanly (`baseUrl + collection + "/" + …`).
9393
+ *
9394
+ * @param baseUrl - The candidate `baseUrl` value to check.
9395
+ * @returns `true` when `baseUrl` is not a string or does not end with "/".
9396
+ * @example
9397
+ * ```ts
9398
+ * isInvalidBaseUrl("/"); // false
9399
+ * isInvalidBaseUrl("/cdn"); // true
9400
+ * ```
9401
+ */
9402
+ function isInvalidBaseUrl(baseUrl) {
9403
+ return typeof baseUrl !== "string" || !baseUrl.endsWith("/");
9404
+ }
9405
+ /**
9406
+ * Validates the resolved collection config: the browser `baseUrl` must be a string
9407
+ * ending with `"/"` so the collection name appends cleanly.
9408
+ *
9409
+ * @param config - The resolved plugin configuration.
9410
+ * @throws {Error} If `baseUrl` is not a string ending with "/".
9411
+ * @example
9412
+ * ```ts
9413
+ * validateCollectionConfig({ baseUrl: "/" });
9414
+ * ```
9415
+ */
9416
+ function validateCollectionConfig(config) {
9417
+ if (isInvalidBaseUrl(config.baseUrl)) throw new Error(`[web] collection.baseUrl: must be a URL prefix ending with "/" (e.g. "/" or "/cdn/").`);
9418
+ }
9419
+ //#endregion
9420
+ //#region src/plugins/collection/index.ts
9421
+ /**
9422
+ * @file collection — Standard tier plugin (wiring-only). The static-data
9423
+ * COLLECTION provider — the collection-keyed sibling of the page-path-keyed `data`
9424
+ * plugin.
9425
+ *
9426
+ * Owns ONE contract — `(collection, shard) → persisted JSON file` — and nothing
9427
+ * about what the data is: `write(entries)` persists per-shard JSON on Node (the
9428
+ * build supplies the shards it already authored); `at(collection, shard)` fetches +
9429
+ * caches it in the browser as `unknown`, read on demand by the consumer. NOT a
9430
+ * framework default — the consumer composes it where needed (Node build AND/OR
9431
+ * browser app).
9432
+ *
9433
+ * **No hard `depends`** — fully browser-composable; the `node:fs` writer is behind
9434
+ * a lazy `import()` inside `write()`. Build ordering is a call-site contract: the
9435
+ * build writes shards during/after its pages phase, via `app.collection.write`.
9436
+ * No `onStart`/`onStop`.
9437
+ * @see README.md
9438
+ */
9439
+ /**
9440
+ * Collection plugin — the static-data collection provider. Mounts
9441
+ * `write(entries)` (Node persist), `at(collection, shard)` (browser read), and the
9442
+ * pure `urlFor`/`fileFor` convention at `app.collection`.
9443
+ *
9444
+ * @example
9445
+ * ```ts
9446
+ * // Node build: write build-authored shards during/after the build's pages phase.
9447
+ * const app = createApp({ plugins: [collectionPlugin, buildPlugin] });
9448
+ * await app.build.run();
9449
+ * await app.collection.write([{ collection: "bank", shard: "en/animals", data }]);
9450
+ *
9451
+ * // Browser app: compose `collectionPlugin` too; fetch a shard on demand.
9452
+ * const raw = await app.collection.at("bank", "en/animals"); // unknown | null
9453
+ * ```
9454
+ */
9455
+ const collectionPlugin = createPlugin$1("collection", {
9456
+ config: defaultCollectionConfig,
9457
+ createState: createCollectionState,
9458
+ onInit: (ctx) => validateCollectionConfig(ctx.config),
9459
+ api: collectionApi
9460
+ });
9461
+ //#endregion
9184
9462
  //#region src/plugins/spa/api.ts
9185
9463
  /**
9186
9464
  * Creates the spa plugin API surface (registration / control). All methods
@@ -9346,7 +9624,7 @@ function syncHead(_head, doc) {
9346
9624
  }
9347
9625
  //#endregion
9348
9626
  //#region src/plugins/spa/types.ts
9349
- var types_exports$7 = /* @__PURE__ */ require_convention.__exportAll({ ISLAND_HOOK_NAMES: () => ISLAND_HOOK_NAMES });
9627
+ var types_exports$8 = /* @__PURE__ */ __exportAll({ ISLAND_HOOK_NAMES: () => ISLAND_HOOK_NAMES });
9350
9628
  /** Allowed hook names — single source of truth for fail-fast validation. */
9351
9629
  const ISLAND_HOOK_NAMES = [
9352
9630
  "onCreate",
@@ -11206,10 +11484,12 @@ function createChannel(options) {
11206
11484
  ws.addEventListener("open", () => {
11207
11485
  reconnectAttempts = 0;
11208
11486
  });
11209
- ws.addEventListener("close", () => {
11487
+ ws.addEventListener("close", (event) => {
11210
11488
  if (socket !== ws || desiredId === void 0) return;
11211
11489
  socket = void 0;
11212
- if (reconnectCfg) scheduleReconnect();
11490
+ if (!reconnectCfg) return;
11491
+ if (options.shouldReconnect?.(event) === false) return;
11492
+ scheduleReconnect();
11213
11493
  });
11214
11494
  }
11215
11495
  /**
@@ -11477,25 +11757,28 @@ const spaPlugin = createPlugin$1("spa", {
11477
11757
  });
11478
11758
  //#endregion
11479
11759
  //#region src/plugins/build/types.ts
11480
- var types_exports = /* @__PURE__ */ require_convention.__exportAll({});
11760
+ var types_exports = /* @__PURE__ */ __exportAll({});
11481
11761
  //#endregion
11482
11762
  //#region src/plugins/cli/types.ts
11483
- var types_exports$1 = /* @__PURE__ */ require_convention.__exportAll({});
11763
+ var types_exports$1 = /* @__PURE__ */ __exportAll({});
11764
+ //#endregion
11765
+ //#region src/plugins/collection/types.ts
11766
+ var types_exports$2 = /* @__PURE__ */ __exportAll({});
11484
11767
  //#endregion
11485
11768
  //#region src/plugins/content/types.ts
11486
- var types_exports$2 = /* @__PURE__ */ require_convention.__exportAll({});
11769
+ var types_exports$3 = /* @__PURE__ */ __exportAll({});
11487
11770
  //#endregion
11488
11771
  //#region src/plugins/data/types.ts
11489
- var types_exports$3 = /* @__PURE__ */ require_convention.__exportAll({});
11772
+ var types_exports$4 = /* @__PURE__ */ __exportAll({});
11490
11773
  //#endregion
11491
11774
  //#region src/plugins/deploy/types.ts
11492
- var types_exports$4 = /* @__PURE__ */ require_convention.__exportAll({});
11775
+ var types_exports$5 = /* @__PURE__ */ __exportAll({});
11493
11776
  //#endregion
11494
11777
  //#region src/plugins/head/types.ts
11495
- var types_exports$5 = /* @__PURE__ */ require_convention.__exportAll({});
11778
+ var types_exports$6 = /* @__PURE__ */ __exportAll({});
11496
11779
  //#endregion
11497
11780
  //#region src/plugins/router/types.ts
11498
- var types_exports$6 = /* @__PURE__ */ require_convention.__exportAll({});
11781
+ var types_exports$7 = /* @__PURE__ */ __exportAll({});
11499
11782
  //#endregion
11500
11783
  //#region node_modules/unist-util-stringify-position/lib/index.js
11501
11784
  /**
@@ -13707,44 +13990,51 @@ Object.defineProperty(exports, "Cli", {
13707
13990
  return types_exports$1;
13708
13991
  }
13709
13992
  });
13710
- Object.defineProperty(exports, "Content", {
13993
+ Object.defineProperty(exports, "Collection", {
13711
13994
  enumerable: true,
13712
13995
  get: function() {
13713
13996
  return types_exports$2;
13714
13997
  }
13715
13998
  });
13716
- Object.defineProperty(exports, "Data", {
13999
+ Object.defineProperty(exports, "Content", {
13717
14000
  enumerable: true,
13718
14001
  get: function() {
13719
14002
  return types_exports$3;
13720
14003
  }
13721
14004
  });
13722
- Object.defineProperty(exports, "Deploy", {
14005
+ Object.defineProperty(exports, "Data", {
13723
14006
  enumerable: true,
13724
14007
  get: function() {
13725
14008
  return types_exports$4;
13726
14009
  }
13727
14010
  });
14011
+ Object.defineProperty(exports, "Deploy", {
14012
+ enumerable: true,
14013
+ get: function() {
14014
+ return types_exports$5;
14015
+ }
14016
+ });
13728
14017
  exports.EmbedFacadeButton = EmbedFacadeButton;
13729
14018
  exports.GalleryTrack = GalleryTrack;
13730
14019
  Object.defineProperty(exports, "Head", {
13731
14020
  enumerable: true,
13732
14021
  get: function() {
13733
- return types_exports$5;
14022
+ return types_exports$6;
13734
14023
  }
13735
14024
  });
13736
14025
  Object.defineProperty(exports, "Router", {
13737
14026
  enumerable: true,
13738
14027
  get: function() {
13739
- return types_exports$6;
14028
+ return types_exports$7;
13740
14029
  }
13741
14030
  });
13742
14031
  Object.defineProperty(exports, "Spa", {
13743
14032
  enumerable: true,
13744
14033
  get: function() {
13745
- return types_exports$7;
14034
+ return types_exports$8;
13746
14035
  }
13747
14036
  });
14037
+ exports.__toESM = __toESM;
13748
14038
  Object.defineProperty(exports, "browserEnv", {
13749
14039
  enumerable: true,
13750
14040
  get: function() {
@@ -13761,6 +14051,8 @@ Object.defineProperty(exports, "cloudflareBindings", {
13761
14051
  return _moku_labs_common.cloudflareBindings;
13762
14052
  }
13763
14053
  });
14054
+ exports.collectionPlugin = collectionPlugin;
14055
+ exports.collectionUrl = require_convention$1.collectionUrl;
13764
14056
  exports.contentPlugin = contentPlugin;
13765
14057
  exports.createApp = createApp;
13766
14058
  exports.createChannel = createChannel;
@@ -13790,6 +14082,7 @@ exports.hreflang = hreflang;
13790
14082
  exports.i18nPlugin = i18nPlugin;
13791
14083
  exports.jsonLd = jsonLd;
13792
14084
  exports.lazyEmbed = lazyEmbed;
14085
+ exports.loadCollectionShard = loadCollectionShard;
13793
14086
  Object.defineProperty(exports, "logPlugin", {
13794
14087
  enumerable: true,
13795
14088
  get: function() {