@moku-labs/worker 0.2.1 → 0.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/cli.cjs CHANGED
@@ -22,6 +22,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
22
  }) : target, mod));
23
23
  //#endregion
24
24
  const require_storage = require("./storage-CgXl-dUA.cjs");
25
+ let _moku_labs_common_cli = require("@moku-labs/common/cli");
25
26
  let node_child_process = require("node:child_process");
26
27
  let node_fs_promises = require("node:fs/promises");
27
28
  let node_path = require("node:path");
@@ -691,58 +692,59 @@ const createCliApi = (ctx) => ({
691
692
  //#endregion
692
693
  //#region src/plugins/cli/handlers.ts
693
694
  /**
694
- * Builds the hook handlers that turn global deploy events into a live progress TUI
695
- * via ctx.log. Pure observers print and return; never mutate state, never block
696
- * the deploy pipeline (fire-and-forget, spec/07 §3,§4).
695
+ * Builds the hook handlers that turn global deploy events into a live progress TUI.
696
+ * Each logs a clean, prefix-free message via `ctx.log`; the branded log sink (installed
697
+ * by the cli plugin's onInit from `@moku-labs/common/cli`) adds the `›` marker, brand
698
+ * color, and stderr routing. Pure observers — print and return; never mutate state,
699
+ * never block the deploy pipeline (fire-and-forget, spec/07 §3,§4).
697
700
  *
698
701
  * @param ctx - CLI plugin context with injected log core API.
699
702
  * @returns Hook map for the three global deploy events.
700
703
  * @example
701
704
  * ```ts
702
705
  * const hooks = createCliHooks(ctx);
703
- * hooks["deploy:phase"]({ phase: "detect" }); // logs "> detect"
704
- * hooks["provision:resource"]({ kind: "kv", name: "KV" }); // logs " + kv KV"
705
- * hooks["deploy:complete"]({ url: "https://x.workers.dev" }); // logs "done -> https://x.workers.dev"
706
+ * hooks["deploy:phase"]({ phase: "detect" }); // logs "detect" → renders " › detect"
707
+ * hooks["provision:resource"]({ kind: "kv", name: "KV" }); // logs "kv KV" → " kv KV"
708
+ * hooks["deploy:complete"]({ url: "https://x.workers.dev" }); // "deployed https://x.workers.dev"
706
709
  * ```
707
710
  */
708
711
  const createCliHooks = (ctx) => ({
709
712
  /**
710
- * Print one line per pipeline phase: "> phase" or "> phase - detail".
713
+ * Log one clean line per pipeline phase: "phase" or "phase · detail".
711
714
  *
712
715
  * @param p - The deploy:phase event payload.
713
716
  * @example
714
717
  * ```ts
715
- * handler({ phase: "detect" }); // "> detect"
716
- * handler({ phase: "upload", detail: "3 files" }); // "> upload - 3 files"
718
+ * handler({ phase: "detect" }); // "detect"
719
+ * handler({ phase: "upload", detail: "3 files" }); // "upload · 3 files"
717
720
  * ```
718
721
  */
719
722
  "deploy:phase"(p) {
720
- const detail = p.detail ? ` - ${p.detail}` : "";
721
- ctx.log.info(`> ${p.phase}${detail}`);
723
+ ctx.log.info(p.detail ? `${p.phase} · ${p.detail}` : p.phase);
722
724
  },
723
725
  /**
724
- * Print one indented line per provisioned resource: " + kind name".
726
+ * Log one clean line per provisioned resource: "kind name".
725
727
  *
726
728
  * @param p - The provision:resource event payload.
727
729
  * @example
728
730
  * ```ts
729
- * handler({ kind: "kv", name: "KV" }); // " + kv KV"
731
+ * handler({ kind: "kv", name: "KV" }); // "kv KV"
730
732
  * ```
731
733
  */
732
734
  "provision:resource"(p) {
733
- ctx.log.info(` + ${p.kind} ${p.name}`);
735
+ ctx.log.info(`${p.kind} ${p.name}`);
734
736
  },
735
737
  /**
736
- * Print the terminal success line with the deployed URL.
738
+ * Log the terminal success line with the deployed URL.
737
739
  *
738
740
  * @param p - The deploy:complete event payload.
739
741
  * @example
740
742
  * ```ts
741
- * handler({ url: "https://my-worker.workers.dev" }); // "done -> https://my-worker.workers.dev"
743
+ * handler({ url: "https://my-worker.workers.dev" }); // "deployed https://my-worker.workers.dev"
742
744
  * ```
743
745
  */
744
746
  "deploy:complete"(p) {
745
- ctx.log.info(`done -> ${p.url}`);
747
+ ctx.log.info(`deployed ${p.url}`);
746
748
  }
747
749
  });
748
750
  /**
@@ -760,6 +762,10 @@ const createCliHooks = (ctx) => ({
760
762
  const cliPlugin = require_storage.createPlugin("cli", {
761
763
  depends: [deployPlugin],
762
764
  config: { port: 8787 },
765
+ onInit: (ctx) => {
766
+ ctx.log.clearSinks();
767
+ ctx.log.addSink((0, _moku_labs_common_cli.brandedSink)("info"));
768
+ },
763
769
  api: (ctx) => createCliApi(ctx),
764
770
  hooks: (ctx) => createCliHooks(ctx)
765
771
  });
package/dist/cli.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import { i as durableObjectsPlugin, n as queuesPlugin, o as d1Plugin, r as kvPlugin, t as storagePlugin, u as createPlugin } from "./storage-COo-F38H.mjs";
2
+ import { brandedSink } from "@moku-labs/common/cli";
2
3
  import { spawn } from "node:child_process";
3
4
  import { readdir, stat, writeFile } from "node:fs/promises";
4
5
  import path from "node:path";
@@ -667,58 +668,59 @@ const createCliApi = (ctx) => ({
667
668
  //#endregion
668
669
  //#region src/plugins/cli/handlers.ts
669
670
  /**
670
- * Builds the hook handlers that turn global deploy events into a live progress TUI
671
- * via ctx.log. Pure observers print and return; never mutate state, never block
672
- * the deploy pipeline (fire-and-forget, spec/07 §3,§4).
671
+ * Builds the hook handlers that turn global deploy events into a live progress TUI.
672
+ * Each logs a clean, prefix-free message via `ctx.log`; the branded log sink (installed
673
+ * by the cli plugin's onInit from `@moku-labs/common/cli`) adds the `›` marker, brand
674
+ * color, and stderr routing. Pure observers — print and return; never mutate state,
675
+ * never block the deploy pipeline (fire-and-forget, spec/07 §3,§4).
673
676
  *
674
677
  * @param ctx - CLI plugin context with injected log core API.
675
678
  * @returns Hook map for the three global deploy events.
676
679
  * @example
677
680
  * ```ts
678
681
  * const hooks = createCliHooks(ctx);
679
- * hooks["deploy:phase"]({ phase: "detect" }); // logs "> detect"
680
- * hooks["provision:resource"]({ kind: "kv", name: "KV" }); // logs " + kv KV"
681
- * hooks["deploy:complete"]({ url: "https://x.workers.dev" }); // logs "done -> https://x.workers.dev"
682
+ * hooks["deploy:phase"]({ phase: "detect" }); // logs "detect" → renders " › detect"
683
+ * hooks["provision:resource"]({ kind: "kv", name: "KV" }); // logs "kv KV" → " kv KV"
684
+ * hooks["deploy:complete"]({ url: "https://x.workers.dev" }); // "deployed https://x.workers.dev"
682
685
  * ```
683
686
  */
684
687
  const createCliHooks = (ctx) => ({
685
688
  /**
686
- * Print one line per pipeline phase: "> phase" or "> phase - detail".
689
+ * Log one clean line per pipeline phase: "phase" or "phase · detail".
687
690
  *
688
691
  * @param p - The deploy:phase event payload.
689
692
  * @example
690
693
  * ```ts
691
- * handler({ phase: "detect" }); // "> detect"
692
- * handler({ phase: "upload", detail: "3 files" }); // "> upload - 3 files"
694
+ * handler({ phase: "detect" }); // "detect"
695
+ * handler({ phase: "upload", detail: "3 files" }); // "upload · 3 files"
693
696
  * ```
694
697
  */
695
698
  "deploy:phase"(p) {
696
- const detail = p.detail ? ` - ${p.detail}` : "";
697
- ctx.log.info(`> ${p.phase}${detail}`);
699
+ ctx.log.info(p.detail ? `${p.phase} · ${p.detail}` : p.phase);
698
700
  },
699
701
  /**
700
- * Print one indented line per provisioned resource: " + kind name".
702
+ * Log one clean line per provisioned resource: "kind name".
701
703
  *
702
704
  * @param p - The provision:resource event payload.
703
705
  * @example
704
706
  * ```ts
705
- * handler({ kind: "kv", name: "KV" }); // " + kv KV"
707
+ * handler({ kind: "kv", name: "KV" }); // "kv KV"
706
708
  * ```
707
709
  */
708
710
  "provision:resource"(p) {
709
- ctx.log.info(` + ${p.kind} ${p.name}`);
711
+ ctx.log.info(`${p.kind} ${p.name}`);
710
712
  },
711
713
  /**
712
- * Print the terminal success line with the deployed URL.
714
+ * Log the terminal success line with the deployed URL.
713
715
  *
714
716
  * @param p - The deploy:complete event payload.
715
717
  * @example
716
718
  * ```ts
717
- * handler({ url: "https://my-worker.workers.dev" }); // "done -> https://my-worker.workers.dev"
719
+ * handler({ url: "https://my-worker.workers.dev" }); // "deployed https://my-worker.workers.dev"
718
720
  * ```
719
721
  */
720
722
  "deploy:complete"(p) {
721
- ctx.log.info(`done -> ${p.url}`);
723
+ ctx.log.info(`deployed ${p.url}`);
722
724
  }
723
725
  });
724
726
  /**
@@ -736,6 +738,10 @@ const createCliHooks = (ctx) => ({
736
738
  const cliPlugin = createPlugin("cli", {
737
739
  depends: [deployPlugin],
738
740
  config: { port: 8787 },
741
+ onInit: (ctx) => {
742
+ ctx.log.clearSinks();
743
+ ctx.log.addSink(brandedSink("info"));
744
+ },
739
745
  api: (ctx) => createCliApi(ctx),
740
746
  hooks: (ctx) => createCliHooks(ctx)
741
747
  });
package/dist/index.cjs CHANGED
@@ -482,7 +482,90 @@ const serverPlugin = require_storage.createPlugin("server", {
482
482
  },
483
483
  helpers: { endpoint }
484
484
  });
485
- const { createApp, createPlugin } = require_storage.createCore(require_storage.coreConfig, { plugins: [require_storage.bindingsPlugin, serverPlugin] });
485
+ //#endregion
486
+ //#region src/index.ts
487
+ /**
488
+ * @file `@moku-labs/worker` — server-side Cloudflare Workers app + deploy framework on `@moku-labs/core`.
489
+ *
490
+ * The package root exports the bound {@link createApp} factory (the Layer-3 entry
491
+ * point), {@link createPlugin} for consumer plugins, every runtime plugin instance,
492
+ * the `server`/`durable-objects` helpers, and the framework types. Node-only tooling
493
+ * (`deploy`, `cli`) ships from the separate `@moku-labs/worker/cli` entry, never here.
494
+ *
495
+ * `createApp(options?)` boots a fully-typed, synchronous, per-isolate app. The
496
+ * framework defaults `[logPlugin, envPlugin, stagePlugin, bindingsPlugin, serverPlugin]`
497
+ * are applied first, then the `options` below are shallow-merged on top:
498
+ *
499
+ * - `config` — `Partial<WorkerConfig>`; defaults `{ stage: "production", name: "moku-worker", compatibilityDate: "" }`.
500
+ * `config.stage` is the single stage source: the framework mirrors it into the `stage` core
501
+ * plugin so `ctx.stage.*` / `app.stage.*` stay in lockstep with `ctx.global.stage` (see {@link createApp}).
502
+ * - `pluginConfigs` — per-plugin config overrides keyed by plugin name (e.g. `server.endpoints`, `bindings.required`); default `{}`.
503
+ * - `plugins` — extra `PluginInstance[]` appended to the defaults; default `[]`. Do NOT re-list a default plugin.
504
+ * - `onReady` — optional `(app) => void`, runs after every plugin's `onInit`.
505
+ * - `onError` — optional `(error) => void` boot/lifecycle error handler.
506
+ * - `onStart` / `onStop` — optional `() => void | Promise<void>` runtime lifecycle hooks (`app.start()` / `app.stop()`).
507
+ *
508
+ * Re-listing a default plugin name in `plugins` throws
509
+ * `TypeError: [moku-worker] Duplicate plugin name: "<name>"` — `bindings` and `server`
510
+ * are already wired, so consumers list only the resource plugins they add (`kv`, `d1`, …).
511
+ *
512
+ * Minimal HTTP Worker (shape taken from the server integration test):
513
+ *
514
+ * ```typescript
515
+ * import { createApp, endpoint } from "@moku-labs/worker";
516
+ *
517
+ * export const app = createApp({
518
+ * config: { name: "my-worker", compatibilityDate: "2024-09-23" },
519
+ * pluginConfigs: {
520
+ * server: { endpoints: [endpoint("/health").get(() => new Response("ok"))] }
521
+ * }
522
+ * });
523
+ *
524
+ * // worker.ts — the default export is hand-assembled; no plugin produces it.
525
+ * export default {
526
+ * fetch: (request: Request, env: Record<string, unknown>, ctx: ExecutionContext) =>
527
+ * app.server.handle(request, env, ctx)
528
+ * } satisfies ExportedHandler;
529
+ * ```
530
+ */
531
+ const framework = require_storage.createCore(require_storage.coreConfig, { plugins: [require_storage.bindingsPlugin, serverPlugin] });
532
+ const { createPlugin } = framework;
533
+ /** The core-bound app factory; wrapped by {@link createApp} to bridge `config.stage`. */
534
+ const boundCreateApp = framework.createApp;
535
+ /**
536
+ * Boots a fully-typed, synchronous, per-isolate Worker app — the Layer-3 entry point.
537
+ *
538
+ * Wraps the core-bound factory to BRIDGE the single consumer-facing `config.stage`
539
+ * into the `stage` core plugin's own config, so the typed accessors
540
+ * (`ctx.stage.isDev()` / `app.stage.current()` / …) can never diverge from the
541
+ * global `ctx.global.stage`. Global config and core-plugin config resolve on two
542
+ * SEPARATE cascades (spec/05 §1b), and a core plugin cannot read global config (its
543
+ * context is `{ config, state }` only — spec/02 §6). `createApp` is the only layer
544
+ * that sees the consumer's chosen stage, so it mirrors `config.stage` into the stage
545
+ * plugin's level-4 `pluginConfigs` override (`WorkerConfig.stage → pluginConfigs.stage.stage`).
546
+ * When `config.stage` is omitted, the global config and the stage plugin both fall back
547
+ * to their identical `"production"` default. See the module JSDoc above for the full
548
+ * options/defaults table.
549
+ *
550
+ * @param options - The createApp options (`config`, `pluginConfigs`, `plugins`, and lifecycle callbacks).
551
+ * @returns The initialized app — every plugin's `onInit` has already run.
552
+ * @example
553
+ * ```typescript
554
+ * const app = createApp({ config: { stage: "development", name: "my-worker" } });
555
+ * app.stage.isDev(); // true — bridged from config.stage
556
+ * ```
557
+ */
558
+ const createApp = (options) => {
559
+ const explicitStage = options?.config?.stage;
560
+ if (explicitStage === void 0) return boundCreateApp(options);
561
+ return boundCreateApp({
562
+ ...options,
563
+ pluginConfigs: {
564
+ ...options?.pluginConfigs,
565
+ stage: { stage: explicitStage }
566
+ }
567
+ });
568
+ };
486
569
  //#endregion
487
570
  exports.bindingsPlugin = require_storage.bindingsPlugin;
488
571
  exports.createApp = createApp;
package/dist/index.d.cts CHANGED
@@ -994,38 +994,64 @@ declare const stagePlugin: import("@moku-labs/core").CorePluginInstance<"stage",
994
994
  }>;
995
995
  //#endregion
996
996
  //#region src/index.d.ts
997
- declare const createApp: <const ExtraPlugins extends readonly import("@moku-labs/core").AnyPluginInstance[] = readonly []>(options?: import("@moku-labs/core").CreateAppOptions<WorkerConfig, WorkerEvents, (import("@moku-labs/core").PluginInstance<"bindings", Config$4, Record<string, never>, BindingsApi, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"server", ServerConfig, ServerState, Api$3, {
998
- "server:matched": {
999
- path: string;
1000
- method: string;
1001
- };
1002
- }> & {
1003
- endpoint: <Path extends string>(path: Path) => EndpointBuilder<Path>;
1004
- }) | ExtraPlugins[number], [...ExtraPlugins], 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>, import("@moku-labs/core").CorePluginInstance<"stage", {
1005
- stage: "production" | "development" | "test";
1006
- }, Record<string, never>, {
1007
- isDev: () => boolean;
1008
- isProduction: () => boolean;
1009
- current: () => "production" | "development" | "test";
1010
- }>]>> | undefined) => import("@moku-labs/core").App<WorkerConfig, WorkerEvents, (import("@moku-labs/core").PluginInstance<"bindings", Config$4, Record<string, never>, BindingsApi, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"server", ServerConfig, ServerState, Api$3, {
1011
- "server:matched": {
1012
- path: string;
1013
- method: string;
1014
- };
1015
- }> & {
1016
- endpoint: <Path extends string>(path: Path) => EndpointBuilder<Path>;
1017
- }) | ExtraPlugins[number], 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>, import("@moku-labs/core").CorePluginInstance<"stage", {
1018
- stage: "production" | "development" | "test";
1019
- }, Record<string, never>, {
1020
- isDev: () => boolean;
1021
- isProduction: () => boolean;
1022
- current: () => "production" | "development" | "test";
1023
- }>]>>, createPlugin: import("@moku-labs/core").BoundCreatePluginFunction<WorkerConfig, WorkerEvents, 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>, import("@moku-labs/core").CorePluginInstance<"stage", {
1024
- stage: "production" | "development" | "test";
1025
- }, Record<string, never>, {
1026
- isDev: () => boolean;
1027
- isProduction: () => boolean;
1028
- current: () => "production" | "development" | "test";
1029
- }>]>>;
997
+ declare const createPlugin: import("@moku-labs/core").BoundCreatePluginFunction<WorkerConfig, WorkerEvents, 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>, import("@moku-labs/core").CorePluginInstance<"stage", {
998
+ stage: "production" | "development" | "test";
999
+ }, Record<string, never>, {
1000
+ isDev: () => boolean;
1001
+ isProduction: () => boolean;
1002
+ current: () => "production" | "development" | "test";
1003
+ }>]>>;
1004
+ /** The core-bound app factory; wrapped by {@link createApp} to bridge `config.stage`. */
1005
+ declare const boundCreateApp: <const ExtraPlugins extends readonly import("@moku-labs/core").AnyPluginInstance[] = readonly []>(options?: import("@moku-labs/core").CreateAppOptions<WorkerConfig, WorkerEvents, (import("@moku-labs/core").PluginInstance<"bindings", Config$4, Record<string, never>, BindingsApi, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"server", ServerConfig, ServerState, Api$3, {
1006
+ "server:matched": {
1007
+ path: string;
1008
+ method: string;
1009
+ };
1010
+ }> & {
1011
+ endpoint: <Path extends string>(path: Path) => EndpointBuilder<Path>;
1012
+ }) | ExtraPlugins[number], [...ExtraPlugins], 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>, import("@moku-labs/core").CorePluginInstance<"stage", {
1013
+ stage: "production" | "development" | "test";
1014
+ }, Record<string, never>, {
1015
+ isDev: () => boolean;
1016
+ isProduction: () => boolean;
1017
+ current: () => "production" | "development" | "test";
1018
+ }>]>> | undefined) => import("@moku-labs/core").App<WorkerConfig, WorkerEvents, (import("@moku-labs/core").PluginInstance<"bindings", Config$4, Record<string, never>, BindingsApi, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"server", ServerConfig, ServerState, Api$3, {
1019
+ "server:matched": {
1020
+ path: string;
1021
+ method: string;
1022
+ };
1023
+ }> & {
1024
+ endpoint: <Path extends string>(path: Path) => EndpointBuilder<Path>;
1025
+ }) | ExtraPlugins[number], 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>, import("@moku-labs/core").CorePluginInstance<"stage", {
1026
+ stage: "production" | "development" | "test";
1027
+ }, Record<string, never>, {
1028
+ isDev: () => boolean;
1029
+ isProduction: () => boolean;
1030
+ current: () => "production" | "development" | "test";
1031
+ }>]>>;
1032
+ /**
1033
+ * Boots a fully-typed, synchronous, per-isolate Worker app — the Layer-3 entry point.
1034
+ *
1035
+ * Wraps the core-bound factory to BRIDGE the single consumer-facing `config.stage`
1036
+ * into the `stage` core plugin's own config, so the typed accessors
1037
+ * (`ctx.stage.isDev()` / `app.stage.current()` / …) can never diverge from the
1038
+ * global `ctx.global.stage`. Global config and core-plugin config resolve on two
1039
+ * SEPARATE cascades (spec/05 §1b), and a core plugin cannot read global config (its
1040
+ * context is `{ config, state }` only — spec/02 §6). `createApp` is the only layer
1041
+ * that sees the consumer's chosen stage, so it mirrors `config.stage` into the stage
1042
+ * plugin's level-4 `pluginConfigs` override (`WorkerConfig.stage → pluginConfigs.stage.stage`).
1043
+ * When `config.stage` is omitted, the global config and the stage plugin both fall back
1044
+ * to their identical `"production"` default. See the module JSDoc above for the full
1045
+ * options/defaults table.
1046
+ *
1047
+ * @param options - The createApp options (`config`, `pluginConfigs`, `plugins`, and lifecycle callbacks).
1048
+ * @returns The initialized app — every plugin's `onInit` has already run.
1049
+ * @example
1050
+ * ```typescript
1051
+ * const app = createApp({ config: { stage: "development", name: "my-worker" } });
1052
+ * app.stage.isDev(); // true — bridged from config.stage
1053
+ * ```
1054
+ */
1055
+ declare const createApp: typeof boundCreateApp;
1030
1056
  //#endregion
1031
1057
  export { type types_d_exports as Bindings, type types_d_exports$1 as D1, type types_d_exports$2 as DurableObjects, type PluginCtx, type types_d_exports$3 as Queues, type types_d_exports$4 as Server, type StageApi, type types_d_exports$5 as Storage, type WorkerConfig, type WorkerEnv, type WorkerEvents, type WorkerPluginCtx, bindingsPlugin, createApp, createPlugin, d1Plugin, defineDurableObject, durableObjectsPlugin, endpoint, envPlugin, kvPlugin, logPlugin, queuesPlugin, serverPlugin, stagePlugin, storagePlugin };
package/dist/index.d.mts CHANGED
@@ -994,38 +994,64 @@ declare const stagePlugin: import("@moku-labs/core").CorePluginInstance<"stage",
994
994
  }>;
995
995
  //#endregion
996
996
  //#region src/index.d.ts
997
- declare const createApp: <const ExtraPlugins extends readonly import("@moku-labs/core").AnyPluginInstance[] = readonly []>(options?: import("@moku-labs/core").CreateAppOptions<WorkerConfig, WorkerEvents, (import("@moku-labs/core").PluginInstance<"bindings", Config$4, Record<string, never>, BindingsApi, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"server", ServerConfig, ServerState, Api$3, {
998
- "server:matched": {
999
- path: string;
1000
- method: string;
1001
- };
1002
- }> & {
1003
- endpoint: <Path extends string>(path: Path) => EndpointBuilder<Path>;
1004
- }) | ExtraPlugins[number], [...ExtraPlugins], 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>, import("@moku-labs/core").CorePluginInstance<"stage", {
1005
- stage: "production" | "development" | "test";
1006
- }, Record<string, never>, {
1007
- isDev: () => boolean;
1008
- isProduction: () => boolean;
1009
- current: () => "production" | "development" | "test";
1010
- }>]>> | undefined) => import("@moku-labs/core").App<WorkerConfig, WorkerEvents, (import("@moku-labs/core").PluginInstance<"bindings", Config$4, Record<string, never>, BindingsApi, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"server", ServerConfig, ServerState, Api$3, {
1011
- "server:matched": {
1012
- path: string;
1013
- method: string;
1014
- };
1015
- }> & {
1016
- endpoint: <Path extends string>(path: Path) => EndpointBuilder<Path>;
1017
- }) | ExtraPlugins[number], 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>, import("@moku-labs/core").CorePluginInstance<"stage", {
1018
- stage: "production" | "development" | "test";
1019
- }, Record<string, never>, {
1020
- isDev: () => boolean;
1021
- isProduction: () => boolean;
1022
- current: () => "production" | "development" | "test";
1023
- }>]>>, createPlugin: import("@moku-labs/core").BoundCreatePluginFunction<WorkerConfig, WorkerEvents, 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>, import("@moku-labs/core").CorePluginInstance<"stage", {
1024
- stage: "production" | "development" | "test";
1025
- }, Record<string, never>, {
1026
- isDev: () => boolean;
1027
- isProduction: () => boolean;
1028
- current: () => "production" | "development" | "test";
1029
- }>]>>;
997
+ declare const createPlugin: import("@moku-labs/core").BoundCreatePluginFunction<WorkerConfig, WorkerEvents, 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>, import("@moku-labs/core").CorePluginInstance<"stage", {
998
+ stage: "production" | "development" | "test";
999
+ }, Record<string, never>, {
1000
+ isDev: () => boolean;
1001
+ isProduction: () => boolean;
1002
+ current: () => "production" | "development" | "test";
1003
+ }>]>>;
1004
+ /** The core-bound app factory; wrapped by {@link createApp} to bridge `config.stage`. */
1005
+ declare const boundCreateApp: <const ExtraPlugins extends readonly import("@moku-labs/core").AnyPluginInstance[] = readonly []>(options?: import("@moku-labs/core").CreateAppOptions<WorkerConfig, WorkerEvents, (import("@moku-labs/core").PluginInstance<"bindings", Config$4, Record<string, never>, BindingsApi, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"server", ServerConfig, ServerState, Api$3, {
1006
+ "server:matched": {
1007
+ path: string;
1008
+ method: string;
1009
+ };
1010
+ }> & {
1011
+ endpoint: <Path extends string>(path: Path) => EndpointBuilder<Path>;
1012
+ }) | ExtraPlugins[number], [...ExtraPlugins], 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>, import("@moku-labs/core").CorePluginInstance<"stage", {
1013
+ stage: "production" | "development" | "test";
1014
+ }, Record<string, never>, {
1015
+ isDev: () => boolean;
1016
+ isProduction: () => boolean;
1017
+ current: () => "production" | "development" | "test";
1018
+ }>]>> | undefined) => import("@moku-labs/core").App<WorkerConfig, WorkerEvents, (import("@moku-labs/core").PluginInstance<"bindings", Config$4, Record<string, never>, BindingsApi, {}> & Record<never, never>) | (import("@moku-labs/core").PluginInstance<"server", ServerConfig, ServerState, Api$3, {
1019
+ "server:matched": {
1020
+ path: string;
1021
+ method: string;
1022
+ };
1023
+ }> & {
1024
+ endpoint: <Path extends string>(path: Path) => EndpointBuilder<Path>;
1025
+ }) | ExtraPlugins[number], 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>, import("@moku-labs/core").CorePluginInstance<"stage", {
1026
+ stage: "production" | "development" | "test";
1027
+ }, Record<string, never>, {
1028
+ isDev: () => boolean;
1029
+ isProduction: () => boolean;
1030
+ current: () => "production" | "development" | "test";
1031
+ }>]>>;
1032
+ /**
1033
+ * Boots a fully-typed, synchronous, per-isolate Worker app — the Layer-3 entry point.
1034
+ *
1035
+ * Wraps the core-bound factory to BRIDGE the single consumer-facing `config.stage`
1036
+ * into the `stage` core plugin's own config, so the typed accessors
1037
+ * (`ctx.stage.isDev()` / `app.stage.current()` / …) can never diverge from the
1038
+ * global `ctx.global.stage`. Global config and core-plugin config resolve on two
1039
+ * SEPARATE cascades (spec/05 §1b), and a core plugin cannot read global config (its
1040
+ * context is `{ config, state }` only — spec/02 §6). `createApp` is the only layer
1041
+ * that sees the consumer's chosen stage, so it mirrors `config.stage` into the stage
1042
+ * plugin's level-4 `pluginConfigs` override (`WorkerConfig.stage → pluginConfigs.stage.stage`).
1043
+ * When `config.stage` is omitted, the global config and the stage plugin both fall back
1044
+ * to their identical `"production"` default. See the module JSDoc above for the full
1045
+ * options/defaults table.
1046
+ *
1047
+ * @param options - The createApp options (`config`, `pluginConfigs`, `plugins`, and lifecycle callbacks).
1048
+ * @returns The initialized app — every plugin's `onInit` has already run.
1049
+ * @example
1050
+ * ```typescript
1051
+ * const app = createApp({ config: { stage: "development", name: "my-worker" } });
1052
+ * app.stage.isDev(); // true — bridged from config.stage
1053
+ * ```
1054
+ */
1055
+ declare const createApp: typeof boundCreateApp;
1030
1056
  //#endregion
1031
1057
  export { type types_d_exports as Bindings, type types_d_exports$1 as D1, type types_d_exports$2 as DurableObjects, type PluginCtx, type types_d_exports$3 as Queues, type types_d_exports$4 as Server, type StageApi, type types_d_exports$5 as Storage, type WorkerConfig, type WorkerEnv, type WorkerEvents, type WorkerPluginCtx, bindingsPlugin, createApp, createPlugin, d1Plugin, defineDurableObject, durableObjectsPlugin, endpoint, envPlugin, kvPlugin, logPlugin, queuesPlugin, serverPlugin, stagePlugin, storagePlugin };
package/dist/index.mjs CHANGED
@@ -481,6 +481,89 @@ const serverPlugin = createPlugin$1("server", {
481
481
  },
482
482
  helpers: { endpoint }
483
483
  });
484
- const { createApp, createPlugin } = createCore(coreConfig, { plugins: [bindingsPlugin, serverPlugin] });
484
+ //#endregion
485
+ //#region src/index.ts
486
+ /**
487
+ * @file `@moku-labs/worker` — server-side Cloudflare Workers app + deploy framework on `@moku-labs/core`.
488
+ *
489
+ * The package root exports the bound {@link createApp} factory (the Layer-3 entry
490
+ * point), {@link createPlugin} for consumer plugins, every runtime plugin instance,
491
+ * the `server`/`durable-objects` helpers, and the framework types. Node-only tooling
492
+ * (`deploy`, `cli`) ships from the separate `@moku-labs/worker/cli` entry, never here.
493
+ *
494
+ * `createApp(options?)` boots a fully-typed, synchronous, per-isolate app. The
495
+ * framework defaults `[logPlugin, envPlugin, stagePlugin, bindingsPlugin, serverPlugin]`
496
+ * are applied first, then the `options` below are shallow-merged on top:
497
+ *
498
+ * - `config` — `Partial<WorkerConfig>`; defaults `{ stage: "production", name: "moku-worker", compatibilityDate: "" }`.
499
+ * `config.stage` is the single stage source: the framework mirrors it into the `stage` core
500
+ * plugin so `ctx.stage.*` / `app.stage.*` stay in lockstep with `ctx.global.stage` (see {@link createApp}).
501
+ * - `pluginConfigs` — per-plugin config overrides keyed by plugin name (e.g. `server.endpoints`, `bindings.required`); default `{}`.
502
+ * - `plugins` — extra `PluginInstance[]` appended to the defaults; default `[]`. Do NOT re-list a default plugin.
503
+ * - `onReady` — optional `(app) => void`, runs after every plugin's `onInit`.
504
+ * - `onError` — optional `(error) => void` boot/lifecycle error handler.
505
+ * - `onStart` / `onStop` — optional `() => void | Promise<void>` runtime lifecycle hooks (`app.start()` / `app.stop()`).
506
+ *
507
+ * Re-listing a default plugin name in `plugins` throws
508
+ * `TypeError: [moku-worker] Duplicate plugin name: "<name>"` — `bindings` and `server`
509
+ * are already wired, so consumers list only the resource plugins they add (`kv`, `d1`, …).
510
+ *
511
+ * Minimal HTTP Worker (shape taken from the server integration test):
512
+ *
513
+ * ```typescript
514
+ * import { createApp, endpoint } from "@moku-labs/worker";
515
+ *
516
+ * export const app = createApp({
517
+ * config: { name: "my-worker", compatibilityDate: "2024-09-23" },
518
+ * pluginConfigs: {
519
+ * server: { endpoints: [endpoint("/health").get(() => new Response("ok"))] }
520
+ * }
521
+ * });
522
+ *
523
+ * // worker.ts — the default export is hand-assembled; no plugin produces it.
524
+ * export default {
525
+ * fetch: (request: Request, env: Record<string, unknown>, ctx: ExecutionContext) =>
526
+ * app.server.handle(request, env, ctx)
527
+ * } satisfies ExportedHandler;
528
+ * ```
529
+ */
530
+ const framework = createCore(coreConfig, { plugins: [bindingsPlugin, serverPlugin] });
531
+ const { createPlugin } = framework;
532
+ /** The core-bound app factory; wrapped by {@link createApp} to bridge `config.stage`. */
533
+ const boundCreateApp = framework.createApp;
534
+ /**
535
+ * Boots a fully-typed, synchronous, per-isolate Worker app — the Layer-3 entry point.
536
+ *
537
+ * Wraps the core-bound factory to BRIDGE the single consumer-facing `config.stage`
538
+ * into the `stage` core plugin's own config, so the typed accessors
539
+ * (`ctx.stage.isDev()` / `app.stage.current()` / …) can never diverge from the
540
+ * global `ctx.global.stage`. Global config and core-plugin config resolve on two
541
+ * SEPARATE cascades (spec/05 §1b), and a core plugin cannot read global config (its
542
+ * context is `{ config, state }` only — spec/02 §6). `createApp` is the only layer
543
+ * that sees the consumer's chosen stage, so it mirrors `config.stage` into the stage
544
+ * plugin's level-4 `pluginConfigs` override (`WorkerConfig.stage → pluginConfigs.stage.stage`).
545
+ * When `config.stage` is omitted, the global config and the stage plugin both fall back
546
+ * to their identical `"production"` default. See the module JSDoc above for the full
547
+ * options/defaults table.
548
+ *
549
+ * @param options - The createApp options (`config`, `pluginConfigs`, `plugins`, and lifecycle callbacks).
550
+ * @returns The initialized app — every plugin's `onInit` has already run.
551
+ * @example
552
+ * ```typescript
553
+ * const app = createApp({ config: { stage: "development", name: "my-worker" } });
554
+ * app.stage.isDev(); // true — bridged from config.stage
555
+ * ```
556
+ */
557
+ const createApp = (options) => {
558
+ const explicitStage = options?.config?.stage;
559
+ if (explicitStage === void 0) return boundCreateApp(options);
560
+ return boundCreateApp({
561
+ ...options,
562
+ pluginConfigs: {
563
+ ...options?.pluginConfigs,
564
+ stage: { stage: explicitStage }
565
+ }
566
+ });
567
+ };
485
568
  //#endregion
486
569
  export { bindingsPlugin, createApp, createPlugin, d1Plugin, defineDurableObject, durableObjectsPlugin, endpoint, envPlugin, kvPlugin, logPlugin, queuesPlugin, serverPlugin, stagePlugin, storagePlugin };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moku-labs/worker",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "Cloudflare Worker framework for Moku — Durable Objects, Queues, R2, D1, and KV plugins that compose with Moku Web.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -62,7 +62,7 @@
62
62
  "test:coverage": "vitest run --project unit --project integration --coverage"
63
63
  },
64
64
  "dependencies": {
65
- "@moku-labs/common": "0.1.1",
65
+ "@moku-labs/common": "0.2.0",
66
66
  "@moku-labs/core": "0.1.4"
67
67
  },
68
68
  "devDependencies": {