@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.
- package/dist/browser.d.mts +176 -6
- package/dist/browser.mjs +115 -6
- package/dist/convention-Bs8SrQS9.mjs +109 -0
- package/dist/{convention-BpDfzX7e.cjs → convention-CLIDd9k-.cjs} +0 -43
- package/dist/convention-CsJw7CML.cjs +120 -0
- package/dist/index.cjs +390 -37
- package/dist/index.d.cts +195 -6
- package/dist/index.d.mts +195 -6
- package/dist/index.mjs +328 -16
- package/dist/writer-BBtgz9wt.cjs +90 -0
- package/dist/{writer-JdhX1Wld.cjs → writer-BEM-Pbgb.cjs} +4 -3
- package/dist/writer-C9BfpPio.mjs +87 -0
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-D7D4PA-g.mjs";
|
|
2
2
|
import { n as relativeDataFile, t as dataSuffix } from "./convention-Dp650o3y.mjs";
|
|
3
|
+
import { n as relativeShardFile, t as collectionUrl } from "./convention-Bs8SrQS9.mjs";
|
|
3
4
|
import { browserEnv, cloudflareBindings, dotenv, envPlugin, envPlugin as envPlugin$1, logPlugin, logPlugin as logPlugin$1, processEnv } from "@moku-labs/common";
|
|
4
5
|
import { createCoreConfig } from "@moku-labs/core";
|
|
5
6
|
import { appendFileSync, existsSync, readFileSync, readdirSync, realpathSync, statSync, watch } from "node:fs";
|
|
6
7
|
import { createHash, randomUUID } from "node:crypto";
|
|
7
|
-
import { cp, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
8
|
+
import { copyFile, cp, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
8
9
|
import { homedir, networkInterfaces, tmpdir } from "node:os";
|
|
9
10
|
import minpath, { default as path } from "node:path";
|
|
10
11
|
import { Feed } from "feed";
|
|
@@ -2558,9 +2559,70 @@ function substituteAssetPlaceholders(ctx, html) {
|
|
|
2558
2559
|
return html.replaceAll(ASSETS_PLACEHOLDER, buildAssetTags(ctx)).replaceAll(CSS_ASSETS_PLACEHOLDER, buildAssetTags(ctx, "css")).replaceAll(JS_ASSETS_PLACEHOLDER, buildAssetTags(ctx, "js"));
|
|
2559
2560
|
}
|
|
2560
2561
|
/**
|
|
2561
|
-
*
|
|
2562
|
-
*
|
|
2563
|
-
*
|
|
2562
|
+
* Test whether `destination` is already an up-to-date copy of the `src` file: same
|
|
2563
|
+
* size and at least as new (mtime). A missing destination — or any stat failure —
|
|
2564
|
+
* reports stale: when in doubt, copy.
|
|
2565
|
+
*
|
|
2566
|
+
* @param src - The source file path.
|
|
2567
|
+
* @param destination - The candidate destination file path.
|
|
2568
|
+
* @returns `true` when the destination can be skipped.
|
|
2569
|
+
* @example
|
|
2570
|
+
* ```ts
|
|
2571
|
+
* if (await isFreshCopy(src, destination)) return;
|
|
2572
|
+
* ```
|
|
2573
|
+
*/
|
|
2574
|
+
async function isFreshCopy(src, destination) {
|
|
2575
|
+
try {
|
|
2576
|
+
const [srcStat, destinationStat] = await Promise.all([stat(src), stat(destination)]);
|
|
2577
|
+
return destinationStat.size === srcStat.size && destinationStat.mtimeMs >= srcStat.mtimeMs;
|
|
2578
|
+
} catch {
|
|
2579
|
+
return false;
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
/**
|
|
2583
|
+
* Recursively copy `from` into `to`, skipping every file whose destination is already
|
|
2584
|
+
* fresh. Skipping matters in dev rebuilds (which keep the previous outDir): re-copying
|
|
2585
|
+
* unchanged assets makes asset-watching dev servers (wrangler) reload on src-only
|
|
2586
|
+
* edits, and on macOS/APFS under Bun a large-file re-copy goes through clonefile(2),
|
|
2587
|
+
* whose FSEvents "ItemCloned" echo on the SOURCE path re-triggers `public/**` watchers
|
|
2588
|
+
* — a self-sustaining rebuild loop. Serial on purpose: public dirs can hold thousands
|
|
2589
|
+
* of assets and the skip path is stat-only, while an unbounded parallel copy risks fd
|
|
2590
|
+
* exhaustion.
|
|
2591
|
+
*
|
|
2592
|
+
* @param from - The source directory.
|
|
2593
|
+
* @param to - The destination directory (created when missing).
|
|
2594
|
+
* @returns The number of entries actually copied.
|
|
2595
|
+
* @example
|
|
2596
|
+
* ```ts
|
|
2597
|
+
* const copied = await copyDirIncremental("public", "dist");
|
|
2598
|
+
* ```
|
|
2599
|
+
*/
|
|
2600
|
+
async function copyDirIncremental(from, to) {
|
|
2601
|
+
await mkdir(to, { recursive: true });
|
|
2602
|
+
const entries = await readdir(from, { withFileTypes: true });
|
|
2603
|
+
let copied = 0;
|
|
2604
|
+
for (const entry of entries) {
|
|
2605
|
+
const src = path.join(from, entry.name);
|
|
2606
|
+
const destination = path.join(to, entry.name);
|
|
2607
|
+
if (entry.isDirectory()) copied += await copyDirIncremental(src, destination);
|
|
2608
|
+
else if (entry.isFile()) {
|
|
2609
|
+
if (await isFreshCopy(src, destination)) continue;
|
|
2610
|
+
await copyFile(src, destination);
|
|
2611
|
+
copied += 1;
|
|
2612
|
+
} else {
|
|
2613
|
+
await cp(src, destination, { recursive: true });
|
|
2614
|
+
copied += 1;
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
return copied;
|
|
2618
|
+
}
|
|
2619
|
+
/**
|
|
2620
|
+
* Copies the configured `publicDir` (default `"public"`) into `outDir`, preserving the
|
|
2621
|
+
* nested directory structure. The copy is incremental: a file whose destination already
|
|
2622
|
+
* exists with the same size and an mtime at least as new as the source is skipped. A
|
|
2623
|
+
* full build cleans outDir first, so every file misses the compare and the output is
|
|
2624
|
+
* identical to a verbatim copy; a dev rebuild (`skipClean`) rewrites only what changed.
|
|
2625
|
+
* Skips silently (returns `null`) when the source directory does not exist.
|
|
2564
2626
|
*
|
|
2565
2627
|
* @param ctx - Plugin context (provides `config`, `log`).
|
|
2566
2628
|
* @returns The copy result, or `null` when the public directory is absent.
|
|
@@ -2578,14 +2640,15 @@ async function copyPublic(ctx) {
|
|
|
2578
2640
|
});
|
|
2579
2641
|
return null;
|
|
2580
2642
|
}
|
|
2581
|
-
await
|
|
2643
|
+
const copied = await copyDirIncremental(from, ctx.config.outDir);
|
|
2582
2644
|
ctx.log.debug("build:public", {
|
|
2583
2645
|
from,
|
|
2584
|
-
dest: ctx.config.outDir
|
|
2646
|
+
dest: ctx.config.outDir,
|
|
2647
|
+
copied
|
|
2585
2648
|
});
|
|
2586
2649
|
return {
|
|
2587
2650
|
from: path.normalize(from),
|
|
2588
|
-
copied
|
|
2651
|
+
copied
|
|
2589
2652
|
};
|
|
2590
2653
|
}
|
|
2591
2654
|
//#endregion
|
|
@@ -4531,7 +4594,7 @@ function createDataState(_ctx) {
|
|
|
4531
4594
|
* isInvalidBaseUrl("_data"); // true
|
|
4532
4595
|
* ```
|
|
4533
4596
|
*/
|
|
4534
|
-
function isInvalidBaseUrl(baseUrl) {
|
|
4597
|
+
function isInvalidBaseUrl$1(baseUrl) {
|
|
4535
4598
|
return typeof baseUrl !== "string" || !baseUrl.startsWith("/");
|
|
4536
4599
|
}
|
|
4537
4600
|
/**
|
|
@@ -4546,7 +4609,7 @@ function isInvalidBaseUrl(baseUrl) {
|
|
|
4546
4609
|
* ```
|
|
4547
4610
|
*/
|
|
4548
4611
|
function validateDataConfig(config) {
|
|
4549
|
-
if (isInvalidBaseUrl(config.baseUrl)) throw new Error(`[web] data.baseUrl: must be a site-root-relative URL path starting with "/" (e.g. "/_data/").`);
|
|
4612
|
+
if (isInvalidBaseUrl$1(config.baseUrl)) throw new Error(`[web] data.baseUrl: must be a site-root-relative URL path starting with "/" (e.g. "/_data/").`);
|
|
4550
4613
|
}
|
|
4551
4614
|
//#endregion
|
|
4552
4615
|
//#region src/plugins/data/index.ts
|
|
@@ -9168,6 +9231,252 @@ const cliPlugin = createPlugin$1("cli", {
|
|
|
9168
9231
|
api: createApi$1
|
|
9169
9232
|
});
|
|
9170
9233
|
//#endregion
|
|
9234
|
+
//#region src/plugins/collection/read.ts
|
|
9235
|
+
/**
|
|
9236
|
+
* @file `loadCollectionShard` — the collection plugin's standalone BROWSER read
|
|
9237
|
+
* primitive. Internal to the `collection` plugin AND exported for standalone
|
|
9238
|
+
* consumers: `collection.at(...)` wraps it, and a room-layer consumer can import it
|
|
9239
|
+
* directly from `@moku-labs/web/browser`.
|
|
9240
|
+
*
|
|
9241
|
+
* Unlike the `data` plugin's isomorphic `loadJson`, this uses the `fetch` global
|
|
9242
|
+
* DIRECTLY (no `node:fs` branch). Consumers — and tests — rely on `fetch` even
|
|
9243
|
+
* under Node test environments, so there is no document sniffing here: a shard is
|
|
9244
|
+
* always fetched over HTTP from the build-authored URL. This keeps the module
|
|
9245
|
+
* fully node-free.
|
|
9246
|
+
*/
|
|
9247
|
+
/**
|
|
9248
|
+
* Fetch + parse a build-authored collection shard over HTTP. Builds the shard URL
|
|
9249
|
+
* via {@link collectionUrl}, fetches it, and throws on a non-OK response so the
|
|
9250
|
+
* caller (`collection.at`) can decide whether to fall back.
|
|
9251
|
+
*
|
|
9252
|
+
* @template T - The expected shape of the parsed shard JSON.
|
|
9253
|
+
* @param baseUrl - The site-root URL prefix (ends with `/`, e.g. `"/"`).
|
|
9254
|
+
* @param collection - The collection name (e.g. `"bank"`).
|
|
9255
|
+
* @param shard - The shard key within the collection (e.g. `"en/animals"`).
|
|
9256
|
+
* @returns The parsed shard JSON, typed as `T`.
|
|
9257
|
+
* @throws {Error} If the fetch is not OK.
|
|
9258
|
+
* @example
|
|
9259
|
+
* ```ts
|
|
9260
|
+
* const animals = await loadCollectionShard<Animal[]>("/", "bank", "en/animals");
|
|
9261
|
+
* ```
|
|
9262
|
+
*/
|
|
9263
|
+
async function loadCollectionShard(baseUrl, collection, shard) {
|
|
9264
|
+
const url = collectionUrl(baseUrl, collection, shard);
|
|
9265
|
+
const response = await fetch(url);
|
|
9266
|
+
if (!response.ok) throw new Error(`[web] collection: failed to fetch ${url} (${String(response.status)}).`);
|
|
9267
|
+
return response.json();
|
|
9268
|
+
}
|
|
9269
|
+
//#endregion
|
|
9270
|
+
//#region src/plugins/collection/api.ts
|
|
9271
|
+
/**
|
|
9272
|
+
* @file collection plugin — API factory (the collection provider surface).
|
|
9273
|
+
*
|
|
9274
|
+
* Node-free by construction: this module statically imports only types + the pure
|
|
9275
|
+
* convention + the node-free `read` primitive. The Node write side (`write()`)
|
|
9276
|
+
* reaches its `node:fs` writer through a lazy `await import("./writer")` at call
|
|
9277
|
+
* time, so a browser bundle that composes `collection` for the read side never
|
|
9278
|
+
* pulls `node:*`. The read side (`at()`) uses only the `fetch`-based
|
|
9279
|
+
* `loadCollectionShard`.
|
|
9280
|
+
*/
|
|
9281
|
+
/**
|
|
9282
|
+
* Builds the collection provider — the static-data bridge. `write()` is the Node
|
|
9283
|
+
* persist side; `at()` is the browser read side; `urlFor`/`fileFor` are the pure
|
|
9284
|
+
* convention. No `onStart`/`onStop` (holds no long-lived resource).
|
|
9285
|
+
*
|
|
9286
|
+
* @param ctx - The collection plugin context.
|
|
9287
|
+
* @returns The {@link CollectionProvider} mounted at `app.collection`.
|
|
9288
|
+
* @example
|
|
9289
|
+
* ```ts
|
|
9290
|
+
* const api = collectionApi(ctx);
|
|
9291
|
+
* await api.write([{ collection: "bank", shard: "en/animals", data }]); // Node build
|
|
9292
|
+
* await api.at("bank", "en/animals"); // browser
|
|
9293
|
+
* ```
|
|
9294
|
+
*/
|
|
9295
|
+
function collectionApi(ctx) {
|
|
9296
|
+
return {
|
|
9297
|
+
/**
|
|
9298
|
+
* READ (browser) — fetch (and cache) the persisted data for a `(collection,
|
|
9299
|
+
* shard)` key. Returns the raw JSON as `unknown`; returns `null` if the fetch
|
|
9300
|
+
* or JSON parse fails (so the consumer can fall back). Wraps the throwing
|
|
9301
|
+
* {@link loadCollectionShard} reader into a soft null-returning app read.
|
|
9302
|
+
*
|
|
9303
|
+
* @param collection - The collection name (e.g. `"bank"`).
|
|
9304
|
+
* @param shard - The shard key within the collection (e.g. `"en/animals"`).
|
|
9305
|
+
* @returns The shard's raw data, or `null` on failure.
|
|
9306
|
+
* @example
|
|
9307
|
+
* ```ts
|
|
9308
|
+
* const raw = await api.at("bank", "en/animals");
|
|
9309
|
+
* ```
|
|
9310
|
+
*/
|
|
9311
|
+
async at(collection, shard) {
|
|
9312
|
+
const key = `${collection}/${shard}`;
|
|
9313
|
+
if (ctx.state.cache.has(key)) return ctx.state.cache.get(key);
|
|
9314
|
+
try {
|
|
9315
|
+
const data = await loadCollectionShard(ctx.config.baseUrl, collection, shard);
|
|
9316
|
+
ctx.state.cache.set(key, data);
|
|
9317
|
+
return data;
|
|
9318
|
+
} catch {
|
|
9319
|
+
return null;
|
|
9320
|
+
}
|
|
9321
|
+
},
|
|
9322
|
+
/**
|
|
9323
|
+
* WRITE (Node) — persist one JSON file per entry, keyed by `(collection,
|
|
9324
|
+
* shard)`. Called by the build after it authors the shards. Lazily loads its
|
|
9325
|
+
* `node:fs` writer (keeping a browser bundle node-free).
|
|
9326
|
+
*
|
|
9327
|
+
* @param entries - The per-shard data to persist.
|
|
9328
|
+
* @param options - Optional `{ outDir }` override (defaults to `./dist`).
|
|
9329
|
+
* @param options.outDir - Build output directory the write happens under.
|
|
9330
|
+
* @returns A summary of the written files.
|
|
9331
|
+
* @example
|
|
9332
|
+
* ```ts
|
|
9333
|
+
* await api.write([{ collection: "bank", shard: "ru", data }], { outDir: "dist" });
|
|
9334
|
+
* ```
|
|
9335
|
+
*/
|
|
9336
|
+
async write(entries, options) {
|
|
9337
|
+
const { writeCollection } = await import("./writer-C9BfpPio.mjs");
|
|
9338
|
+
return writeCollection(ctx, entries, options);
|
|
9339
|
+
},
|
|
9340
|
+
/**
|
|
9341
|
+
* PURE — the browser fetch URL for a `(collection, shard)` key.
|
|
9342
|
+
*
|
|
9343
|
+
* @param collection - The collection name.
|
|
9344
|
+
* @param shard - The shard key within the collection.
|
|
9345
|
+
* @returns The site-root-relative shard URL.
|
|
9346
|
+
* @example
|
|
9347
|
+
* ```ts
|
|
9348
|
+
* api.urlFor("bank", "en/animals"); // "/bank/en/animals.json"
|
|
9349
|
+
* ```
|
|
9350
|
+
*/
|
|
9351
|
+
urlFor(collection, shard) {
|
|
9352
|
+
return collectionUrl(ctx.config.baseUrl, collection, shard);
|
|
9353
|
+
},
|
|
9354
|
+
/**
|
|
9355
|
+
* PURE — the `outDir`-relative file path for a `(collection, shard)` key.
|
|
9356
|
+
*
|
|
9357
|
+
* @param collection - The collection name.
|
|
9358
|
+
* @param shard - The shard key within the collection.
|
|
9359
|
+
* @returns The output-relative file path.
|
|
9360
|
+
* @example
|
|
9361
|
+
* ```ts
|
|
9362
|
+
* api.fileFor("bank", "en/animals"); // "bank/en/animals.json"
|
|
9363
|
+
* ```
|
|
9364
|
+
*/
|
|
9365
|
+
fileFor(collection, shard) {
|
|
9366
|
+
return relativeShardFile(collection, shard);
|
|
9367
|
+
}
|
|
9368
|
+
};
|
|
9369
|
+
}
|
|
9370
|
+
//#endregion
|
|
9371
|
+
//#region src/plugins/collection/config.ts
|
|
9372
|
+
/**
|
|
9373
|
+
* Typed default collection config (R6: no inline `as`). `baseUrl` is the READ-side
|
|
9374
|
+
* site-root URL prefix the browser fetches shards from; the collection name is the
|
|
9375
|
+
* top directory under it. The default `"/"` serves shards straight from the site
|
|
9376
|
+
* root (e.g. `/bank/en/animals.json`).
|
|
9377
|
+
*
|
|
9378
|
+
* @example
|
|
9379
|
+
* ```ts
|
|
9380
|
+
* createPlugin("collection", { config: defaultCollectionConfig });
|
|
9381
|
+
* ```
|
|
9382
|
+
*/
|
|
9383
|
+
const defaultCollectionConfig = { baseUrl: "/" };
|
|
9384
|
+
//#endregion
|
|
9385
|
+
//#region src/plugins/collection/state.ts
|
|
9386
|
+
/**
|
|
9387
|
+
* Creates initial collection state: a null `lastWrite` slot (populated by the Node
|
|
9388
|
+
* `write()` side) and an empty `cache` (populated lazily by the browser
|
|
9389
|
+
* `at(collection, shard)` side on first fetch).
|
|
9390
|
+
*
|
|
9391
|
+
* @param _ctx - Minimal context with global and config.
|
|
9392
|
+
* @param _ctx.global - Global framework configuration.
|
|
9393
|
+
* @param _ctx.config - Resolved plugin configuration.
|
|
9394
|
+
* @returns Fresh collection state with no recorded write and an empty per-shard cache.
|
|
9395
|
+
* @example
|
|
9396
|
+
* ```ts
|
|
9397
|
+
* const state = createCollectionState({ global: {}, config });
|
|
9398
|
+
* ```
|
|
9399
|
+
*/
|
|
9400
|
+
function createCollectionState(_ctx) {
|
|
9401
|
+
return {
|
|
9402
|
+
lastWrite: null,
|
|
9403
|
+
cache: /* @__PURE__ */ new Map()
|
|
9404
|
+
};
|
|
9405
|
+
}
|
|
9406
|
+
//#endregion
|
|
9407
|
+
//#region src/plugins/collection/validate.ts
|
|
9408
|
+
/**
|
|
9409
|
+
* Reports whether a `baseUrl` value is invalid: it must be a string that ends with
|
|
9410
|
+
* a `"/"` so the collection name appends cleanly (`baseUrl + collection + "/" + …`).
|
|
9411
|
+
*
|
|
9412
|
+
* @param baseUrl - The candidate `baseUrl` value to check.
|
|
9413
|
+
* @returns `true` when `baseUrl` is not a string or does not end with "/".
|
|
9414
|
+
* @example
|
|
9415
|
+
* ```ts
|
|
9416
|
+
* isInvalidBaseUrl("/"); // false
|
|
9417
|
+
* isInvalidBaseUrl("/cdn"); // true
|
|
9418
|
+
* ```
|
|
9419
|
+
*/
|
|
9420
|
+
function isInvalidBaseUrl(baseUrl) {
|
|
9421
|
+
return typeof baseUrl !== "string" || !baseUrl.endsWith("/");
|
|
9422
|
+
}
|
|
9423
|
+
/**
|
|
9424
|
+
* Validates the resolved collection config: the browser `baseUrl` must be a string
|
|
9425
|
+
* ending with `"/"` so the collection name appends cleanly.
|
|
9426
|
+
*
|
|
9427
|
+
* @param config - The resolved plugin configuration.
|
|
9428
|
+
* @throws {Error} If `baseUrl` is not a string ending with "/".
|
|
9429
|
+
* @example
|
|
9430
|
+
* ```ts
|
|
9431
|
+
* validateCollectionConfig({ baseUrl: "/" });
|
|
9432
|
+
* ```
|
|
9433
|
+
*/
|
|
9434
|
+
function validateCollectionConfig(config) {
|
|
9435
|
+
if (isInvalidBaseUrl(config.baseUrl)) throw new Error(`[web] collection.baseUrl: must be a URL prefix ending with "/" (e.g. "/" or "/cdn/").`);
|
|
9436
|
+
}
|
|
9437
|
+
//#endregion
|
|
9438
|
+
//#region src/plugins/collection/index.ts
|
|
9439
|
+
/**
|
|
9440
|
+
* @file collection — Standard tier plugin (wiring-only). The static-data
|
|
9441
|
+
* COLLECTION provider — the collection-keyed sibling of the page-path-keyed `data`
|
|
9442
|
+
* plugin.
|
|
9443
|
+
*
|
|
9444
|
+
* Owns ONE contract — `(collection, shard) → persisted JSON file` — and nothing
|
|
9445
|
+
* about what the data is: `write(entries)` persists per-shard JSON on Node (the
|
|
9446
|
+
* build supplies the shards it already authored); `at(collection, shard)` fetches +
|
|
9447
|
+
* caches it in the browser as `unknown`, read on demand by the consumer. NOT a
|
|
9448
|
+
* framework default — the consumer composes it where needed (Node build AND/OR
|
|
9449
|
+
* browser app).
|
|
9450
|
+
*
|
|
9451
|
+
* **No hard `depends`** — fully browser-composable; the `node:fs` writer is behind
|
|
9452
|
+
* a lazy `import()` inside `write()`. Build ordering is a call-site contract: the
|
|
9453
|
+
* build writes shards during/after its pages phase, via `app.collection.write`.
|
|
9454
|
+
* No `onStart`/`onStop`.
|
|
9455
|
+
* @see README.md
|
|
9456
|
+
*/
|
|
9457
|
+
/**
|
|
9458
|
+
* Collection plugin — the static-data collection provider. Mounts
|
|
9459
|
+
* `write(entries)` (Node persist), `at(collection, shard)` (browser read), and the
|
|
9460
|
+
* pure `urlFor`/`fileFor` convention at `app.collection`.
|
|
9461
|
+
*
|
|
9462
|
+
* @example
|
|
9463
|
+
* ```ts
|
|
9464
|
+
* // Node build: write build-authored shards during/after the build's pages phase.
|
|
9465
|
+
* const app = createApp({ plugins: [collectionPlugin, buildPlugin] });
|
|
9466
|
+
* await app.build.run();
|
|
9467
|
+
* await app.collection.write([{ collection: "bank", shard: "en/animals", data }]);
|
|
9468
|
+
*
|
|
9469
|
+
* // Browser app: compose `collectionPlugin` too; fetch a shard on demand.
|
|
9470
|
+
* const raw = await app.collection.at("bank", "en/animals"); // unknown | null
|
|
9471
|
+
* ```
|
|
9472
|
+
*/
|
|
9473
|
+
const collectionPlugin = createPlugin$1("collection", {
|
|
9474
|
+
config: defaultCollectionConfig,
|
|
9475
|
+
createState: createCollectionState,
|
|
9476
|
+
onInit: (ctx) => validateCollectionConfig(ctx.config),
|
|
9477
|
+
api: collectionApi
|
|
9478
|
+
});
|
|
9479
|
+
//#endregion
|
|
9171
9480
|
//#region src/plugins/spa/api.ts
|
|
9172
9481
|
/**
|
|
9173
9482
|
* Creates the spa plugin API surface (registration / control). All methods
|
|
@@ -9333,7 +9642,7 @@ function syncHead(_head, doc) {
|
|
|
9333
9642
|
}
|
|
9334
9643
|
//#endregion
|
|
9335
9644
|
//#region src/plugins/spa/types.ts
|
|
9336
|
-
var types_exports$
|
|
9645
|
+
var types_exports$8 = /* @__PURE__ */ __exportAll({ ISLAND_HOOK_NAMES: () => ISLAND_HOOK_NAMES });
|
|
9337
9646
|
/** Allowed hook names — single source of truth for fail-fast validation. */
|
|
9338
9647
|
const ISLAND_HOOK_NAMES = [
|
|
9339
9648
|
"onCreate",
|
|
@@ -11471,21 +11780,24 @@ var types_exports = /* @__PURE__ */ __exportAll({});
|
|
|
11471
11780
|
//#region src/plugins/cli/types.ts
|
|
11472
11781
|
var types_exports$1 = /* @__PURE__ */ __exportAll({});
|
|
11473
11782
|
//#endregion
|
|
11474
|
-
//#region src/plugins/
|
|
11783
|
+
//#region src/plugins/collection/types.ts
|
|
11475
11784
|
var types_exports$2 = /* @__PURE__ */ __exportAll({});
|
|
11476
11785
|
//#endregion
|
|
11477
|
-
//#region src/plugins/
|
|
11786
|
+
//#region src/plugins/content/types.ts
|
|
11478
11787
|
var types_exports$3 = /* @__PURE__ */ __exportAll({});
|
|
11479
11788
|
//#endregion
|
|
11480
|
-
//#region src/plugins/
|
|
11789
|
+
//#region src/plugins/data/types.ts
|
|
11481
11790
|
var types_exports$4 = /* @__PURE__ */ __exportAll({});
|
|
11482
11791
|
//#endregion
|
|
11483
|
-
//#region src/plugins/
|
|
11792
|
+
//#region src/plugins/deploy/types.ts
|
|
11484
11793
|
var types_exports$5 = /* @__PURE__ */ __exportAll({});
|
|
11485
11794
|
//#endregion
|
|
11486
|
-
//#region src/plugins/
|
|
11795
|
+
//#region src/plugins/head/types.ts
|
|
11487
11796
|
var types_exports$6 = /* @__PURE__ */ __exportAll({});
|
|
11488
11797
|
//#endregion
|
|
11798
|
+
//#region src/plugins/router/types.ts
|
|
11799
|
+
var types_exports$7 = /* @__PURE__ */ __exportAll({});
|
|
11800
|
+
//#endregion
|
|
11489
11801
|
//#region node_modules/unist-util-stringify-position/lib/index.js
|
|
11490
11802
|
/**
|
|
11491
11803
|
* @typedef {import('unist').Node} Node
|
|
@@ -13684,4 +13996,4 @@ const createApp = core.createApp;
|
|
|
13684
13996
|
*/
|
|
13685
13997
|
const createPlugin = core.createPlugin;
|
|
13686
13998
|
//#endregion
|
|
13687
|
-
export { types_exports as Build, types_exports$1 as Cli, types_exports$2 as
|
|
13999
|
+
export { types_exports as Build, types_exports$1 as Cli, types_exports$2 as Collection, types_exports$3 as Content, types_exports$4 as Data, types_exports$5 as Deploy, EmbedFacadeButton, GalleryTrack, types_exports$6 as Head, types_exports$7 as Router, types_exports$8 as Spa, browserEnv, buildArticleHead, buildPlugin, canonical, cliPlugin, cloudflareBindings, collectionPlugin, collectionUrl, contentPlugin, createApp, createChannel, createIsland, createPlugin, createUrls, dataPlugin, defineRoutes, deployPlugin, dotenv, envPlugin, feedLink, fileSystemContent, hardNavigate, headPlugin, hreflang, i18nPlugin, jsonLd, lazyEmbed, loadCollectionShard, logPlugin, meta, navigate, og, processEnv, route, routerPlugin, sitePlugin, spaPlugin, twitter };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const require_convention = require("./convention-CsJw7CML.cjs");
|
|
2
|
+
const require_index = require("./index.cjs");
|
|
3
|
+
let node_fs_promises = require("node:fs/promises");
|
|
4
|
+
let node_path = require("node:path");
|
|
5
|
+
node_path = require_index.__toESM(node_path, 1);
|
|
6
|
+
let p_limit = require("p-limit");
|
|
7
|
+
p_limit = require_index.__toESM(p_limit, 1);
|
|
8
|
+
//#region src/plugins/collection/writer.ts
|
|
9
|
+
/**
|
|
10
|
+
* @file collection plugin — Node write side of the provider (`write()`).
|
|
11
|
+
*
|
|
12
|
+
* This is the ONLY module in the `collection` plugin that touches `node:*`. It is
|
|
13
|
+
* reached exclusively through a lazy `await import("./writer")` inside `api.ts`'s
|
|
14
|
+
* `write()`, so a browser bundle that composes `collection` for the read side never
|
|
15
|
+
* statically pulls `node:fs` (see `__tests__/unit/isolation.test.ts`).
|
|
16
|
+
*
|
|
17
|
+
* It is domain-agnostic: it persists whatever `data` each {@link CollectionShard}
|
|
18
|
+
* carries (the build authored these), one JSON file per shard, at the path
|
|
19
|
+
* {@link relativeShardFile} derives from `(collection, shard)`. No domain knowledge,
|
|
20
|
+
* no expansion (build already authored the shards).
|
|
21
|
+
*/
|
|
22
|
+
/** Default build output root, matching build's default `outDir` ("./dist", build/api.ts). */
|
|
23
|
+
const DEFAULT_OUT_DIR = "./dist";
|
|
24
|
+
/** Concurrency bound for per-shard JSON writes. */
|
|
25
|
+
const WRITE_CONCURRENCY = 8;
|
|
26
|
+
/**
|
|
27
|
+
* Persist one shard's data as JSON under `<outDir>/<relativeShardFile>` and return
|
|
28
|
+
* the written `{ relative, bytes }`.
|
|
29
|
+
*
|
|
30
|
+
* @param entry - The shard entry to persist.
|
|
31
|
+
* @param outDir - The build output root.
|
|
32
|
+
* @returns The written file's `outDir`-relative path and byte length.
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* await writeEntry({ collection: "bank", shard: "en/animals", data }, "./dist");
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
async function writeEntry(entry, outDir) {
|
|
39
|
+
const relative = require_convention.relativeShardFile(entry.collection, entry.shard);
|
|
40
|
+
const filePath = node_path.default.join(outDir, relative);
|
|
41
|
+
await (0, node_fs_promises.mkdir)(node_path.default.dirname(filePath), { recursive: true });
|
|
42
|
+
const body = JSON.stringify(entry.data);
|
|
43
|
+
await (0, node_fs_promises.writeFile)(filePath, body, "utf8");
|
|
44
|
+
return {
|
|
45
|
+
relative,
|
|
46
|
+
bytes: Buffer.byteLength(body, "utf8")
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Roll up the per-file write records into the public {@link CollectionWriteSummary}:
|
|
51
|
+
* file count, total byte length, and the `outDir`-relative paths in write order.
|
|
52
|
+
*
|
|
53
|
+
* @param written - The per-file `{ relative, bytes }` records from {@link writeEntry}.
|
|
54
|
+
* @returns The aggregated write summary.
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* compileSummary([{ relative: "bank/en/animals.json", bytes: 12 }]);
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
function compileSummary(written) {
|
|
61
|
+
return {
|
|
62
|
+
fileCount: written.length,
|
|
63
|
+
bytes: written.reduce((total, file) => total + file.bytes, 0),
|
|
64
|
+
files: written.map((file) => file.relative)
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The Node write side of the provider. Persists one JSON file per shard (bounded
|
|
69
|
+
* by `p-limit`) — domain-agnostic, no expansion (`build` already authored the
|
|
70
|
+
* shards). Records the summary in `ctx.state.lastWrite`.
|
|
71
|
+
*
|
|
72
|
+
* @param ctx - The collection plugin context (state, config).
|
|
73
|
+
* @param entries - The per-shard data to persist.
|
|
74
|
+
* @param options - Optional overrides.
|
|
75
|
+
* @param options.outDir - Build output directory to write under (default `./dist`).
|
|
76
|
+
* @returns A summary of the written files.
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* const summary = await writeCollection(ctx, [{ collection: "bank", shard: "ru", data }]);
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
async function writeCollection(ctx, entries, options) {
|
|
83
|
+
const outDir = options?.outDir ?? DEFAULT_OUT_DIR;
|
|
84
|
+
const limit = (0, p_limit.default)(WRITE_CONCURRENCY);
|
|
85
|
+
const summary = compileSummary(await Promise.all(entries.map((entry) => limit(() => writeEntry(entry, outDir)))));
|
|
86
|
+
ctx.state.lastWrite = summary;
|
|
87
|
+
return summary;
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
90
|
+
exports.writeCollection = writeCollection;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
const require_convention = require("./convention-
|
|
1
|
+
const require_convention = require("./convention-CLIDd9k-.cjs");
|
|
2
|
+
const require_index = require("./index.cjs");
|
|
2
3
|
let node_fs_promises = require("node:fs/promises");
|
|
3
4
|
let node_path = require("node:path");
|
|
4
|
-
node_path =
|
|
5
|
+
node_path = require_index.__toESM(node_path, 1);
|
|
5
6
|
let p_limit = require("p-limit");
|
|
6
|
-
p_limit =
|
|
7
|
+
p_limit = require_index.__toESM(p_limit, 1);
|
|
7
8
|
//#region src/plugins/data/writer.ts
|
|
8
9
|
/**
|
|
9
10
|
* @file data plugin — Node write side of the agnostic provider (`write()`).
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { n as relativeShardFile } from "./convention-Bs8SrQS9.mjs";
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import pLimit from "p-limit";
|
|
5
|
+
//#region src/plugins/collection/writer.ts
|
|
6
|
+
/**
|
|
7
|
+
* @file collection plugin — Node write side of the provider (`write()`).
|
|
8
|
+
*
|
|
9
|
+
* This is the ONLY module in the `collection` plugin that touches `node:*`. It is
|
|
10
|
+
* reached exclusively through a lazy `await import("./writer")` inside `api.ts`'s
|
|
11
|
+
* `write()`, so a browser bundle that composes `collection` for the read side never
|
|
12
|
+
* statically pulls `node:fs` (see `__tests__/unit/isolation.test.ts`).
|
|
13
|
+
*
|
|
14
|
+
* It is domain-agnostic: it persists whatever `data` each {@link CollectionShard}
|
|
15
|
+
* carries (the build authored these), one JSON file per shard, at the path
|
|
16
|
+
* {@link relativeShardFile} derives from `(collection, shard)`. No domain knowledge,
|
|
17
|
+
* no expansion (build already authored the shards).
|
|
18
|
+
*/
|
|
19
|
+
/** Default build output root, matching build's default `outDir` ("./dist", build/api.ts). */
|
|
20
|
+
const DEFAULT_OUT_DIR = "./dist";
|
|
21
|
+
/** Concurrency bound for per-shard JSON writes. */
|
|
22
|
+
const WRITE_CONCURRENCY = 8;
|
|
23
|
+
/**
|
|
24
|
+
* Persist one shard's data as JSON under `<outDir>/<relativeShardFile>` and return
|
|
25
|
+
* the written `{ relative, bytes }`.
|
|
26
|
+
*
|
|
27
|
+
* @param entry - The shard entry to persist.
|
|
28
|
+
* @param outDir - The build output root.
|
|
29
|
+
* @returns The written file's `outDir`-relative path and byte length.
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* await writeEntry({ collection: "bank", shard: "en/animals", data }, "./dist");
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
async function writeEntry(entry, outDir) {
|
|
36
|
+
const relative = relativeShardFile(entry.collection, entry.shard);
|
|
37
|
+
const filePath = path.join(outDir, relative);
|
|
38
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
39
|
+
const body = JSON.stringify(entry.data);
|
|
40
|
+
await writeFile(filePath, body, "utf8");
|
|
41
|
+
return {
|
|
42
|
+
relative,
|
|
43
|
+
bytes: Buffer.byteLength(body, "utf8")
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Roll up the per-file write records into the public {@link CollectionWriteSummary}:
|
|
48
|
+
* file count, total byte length, and the `outDir`-relative paths in write order.
|
|
49
|
+
*
|
|
50
|
+
* @param written - The per-file `{ relative, bytes }` records from {@link writeEntry}.
|
|
51
|
+
* @returns The aggregated write summary.
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* compileSummary([{ relative: "bank/en/animals.json", bytes: 12 }]);
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
function compileSummary(written) {
|
|
58
|
+
return {
|
|
59
|
+
fileCount: written.length,
|
|
60
|
+
bytes: written.reduce((total, file) => total + file.bytes, 0),
|
|
61
|
+
files: written.map((file) => file.relative)
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The Node write side of the provider. Persists one JSON file per shard (bounded
|
|
66
|
+
* by `p-limit`) — domain-agnostic, no expansion (`build` already authored the
|
|
67
|
+
* shards). Records the summary in `ctx.state.lastWrite`.
|
|
68
|
+
*
|
|
69
|
+
* @param ctx - The collection plugin context (state, config).
|
|
70
|
+
* @param entries - The per-shard data to persist.
|
|
71
|
+
* @param options - Optional overrides.
|
|
72
|
+
* @param options.outDir - Build output directory to write under (default `./dist`).
|
|
73
|
+
* @returns A summary of the written files.
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* const summary = await writeCollection(ctx, [{ collection: "bank", shard: "ru", data }]);
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
async function writeCollection(ctx, entries, options) {
|
|
80
|
+
const outDir = options?.outDir ?? DEFAULT_OUT_DIR;
|
|
81
|
+
const limit = pLimit(WRITE_CONCURRENCY);
|
|
82
|
+
const summary = compileSummary(await Promise.all(entries.map((entry) => limit(() => writeEntry(entry, outDir)))));
|
|
83
|
+
ctx.state.lastWrite = summary;
|
|
84
|
+
return summary;
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
export { writeCollection };
|
package/package.json
CHANGED