@moku-labs/worker 0.1.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,990 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __exportAll = (all, no_symbols) => {
9
+ let target = {};
10
+ for (var name in all) __defProp(target, name, {
11
+ get: all[name],
12
+ enumerable: true
13
+ });
14
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
15
+ return target;
16
+ };
17
+ var __copyProps = (to, from, except, desc) => {
18
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
19
+ key = keys[i];
20
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
21
+ get: ((k) => from[k]).bind(null, key),
22
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
23
+ });
24
+ }
25
+ return to;
26
+ };
27
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
28
+ value: mod,
29
+ enumerable: true
30
+ }) : target, mod));
31
+ //#endregion
32
+ let _moku_labs_common = require("@moku-labs/common");
33
+ let _moku_labs_core = require("@moku-labs/core");
34
+ /**
35
+ * stage core plugin — deployment-stage / dev-mode detection, flat-injected on
36
+ * every regular plugin's context as `ctx.stage` (spec/02 §6). No state, no
37
+ * events, no depends, no lifecycle hooks.
38
+ *
39
+ * @see README.md
40
+ * @example
41
+ * ```typescript
42
+ * // Inside any regular plugin's api factory:
43
+ * api: (ctx) => ({
44
+ * errorBody: (e: Error) =>
45
+ * ctx.stage.isDev() ? e.stack ?? e.message : "Internal Error",
46
+ * })
47
+ * ```
48
+ */
49
+ const stagePlugin = (0, _moku_labs_core.createCorePlugin)("stage", {
50
+ config: { stage: "production" },
51
+ /**
52
+ * Builds the stage accessor surface from the resolved stage.
53
+ *
54
+ * @param ctx - Core plugin context (spec/02 §6 — `{ config, state }` only;
55
+ * no `global`, `emit`, or `require`). `state` is unused by this plugin.
56
+ * @param ctx.config - The resolved plugin config containing the deployment stage.
57
+ * @returns The `ctx.stage` API: `isDev`, `isProduction`, `current`.
58
+ * @example
59
+ * ```typescript
60
+ * const api = stagePlugin.spec.api({ config: { stage: "development" }, state: {} });
61
+ * api.isDev(); // true
62
+ * ```
63
+ */
64
+ api: ({ config }) => ({
65
+ /**
66
+ * Whether this Worker runs in the development stage.
67
+ *
68
+ * @returns True iff `stage === "development"`.
69
+ * @example
70
+ * ```typescript
71
+ * if (ctx.stage.isDev()) return Response.json({ stack: err.stack });
72
+ * ```
73
+ */
74
+ isDev: () => config.stage === "development",
75
+ /**
76
+ * Whether this Worker runs in the production stage. Note: false in "test".
77
+ *
78
+ * @returns True iff `stage === "production"`.
79
+ * @example
80
+ * ```typescript
81
+ * const cc = ctx.stage.isProduction() ? "public, max-age=31536000" : "no-store";
82
+ * ```
83
+ */
84
+ isProduction: () => config.stage === "production",
85
+ /**
86
+ * The raw deployment stage, as the literal union (not `string`).
87
+ *
88
+ * @returns The resolved stage.
89
+ * @example
90
+ * ```typescript
91
+ * ctx.log.info("startup", { stage: ctx.stage.current() });
92
+ * ```
93
+ */
94
+ current: () => config.stage
95
+ })
96
+ });
97
+ const coreConfig = (0, _moku_labs_core.createCoreConfig)("moku-worker", {
98
+ config: {
99
+ stage: "production",
100
+ name: "moku-worker",
101
+ compatibilityDate: ""
102
+ },
103
+ plugins: [
104
+ _moku_labs_common.logPlugin,
105
+ _moku_labs_common.envPlugin,
106
+ stagePlugin
107
+ ]
108
+ });
109
+ const { createPlugin, createCore } = coreConfig;
110
+ //#endregion
111
+ //#region src/plugins/bindings/index.ts
112
+ /**
113
+ * Checks whether a value read from an env object is nullish (null or undefined).
114
+ * Cloudflare supplies either form when a binding is absent, so both must be caught.
115
+ *
116
+ * @param value - The value read from the env object.
117
+ * @returns True when the value is null or undefined.
118
+ * @example
119
+ * ```typescript
120
+ * isNullish(undefined); // true
121
+ * isNullish(0); // false — falsy but bound
122
+ * ```
123
+ */
124
+ const isNullish = (value) => value === void 0 || value === null;
125
+ /**
126
+ * Resolves binding `name` off a request-supplied env object, narrowed to T.
127
+ * Throws a `[moku-worker]`-prefixed error when the binding is nullish.
128
+ * The env argument is read but never retained.
129
+ *
130
+ * @param env - The Cloudflare request env object passed to fetch/scheduled/queue.
131
+ * @param name - The binding name to resolve.
132
+ * @returns The binding value narrowed to T.
133
+ * @throws {Error} With a `[moku-worker]` prefix when the binding is null or undefined.
134
+ * @example
135
+ * ```typescript
136
+ * const kv = requireBinding<KVNamespace>(env, "MY_KV");
137
+ * ```
138
+ */
139
+ const requireBinding = (env, name) => {
140
+ const value = env[name];
141
+ if (isNullish(value)) throw new Error(`[moku-worker] binding "${name}" is not bound.\n Declare it in wrangler config and pass it in via the request env.`);
142
+ return value;
143
+ };
144
+ /**
145
+ * Returns true when `name` resolves to a non-nullish value on the request env.
146
+ * Never throws. Use for optional-binding branching without forcing an error.
147
+ *
148
+ * @param env - The Cloudflare request env object passed to fetch/scheduled/queue.
149
+ * @param name - The binding name to check.
150
+ * @returns Whether the binding is present and non-nullish.
151
+ * @example
152
+ * ```typescript
153
+ * const ok = hasBinding(env, "DB"); // false if DB is not bound
154
+ * ```
155
+ */
156
+ const hasBinding = (env, name) => !isNullish(env[name]);
157
+ /**
158
+ * Builds the app.bindings API surface. The factory receives a context but does
159
+ * not use it — bindings holds no state (F4) and all resolution is argument-local.
160
+ *
161
+ * @param _ctx - Plugin context (unused; bindings is stateless — F4).
162
+ * @returns BindingsApi with `require` and `has` methods.
163
+ * @example
164
+ * ```typescript
165
+ * const api = createBindingsApi(ctx);
166
+ * const kv = api.require<KVNamespace>(env, "MY_KV");
167
+ * ```
168
+ */
169
+ const createBindingsApi = (_ctx) => ({
170
+ require: requireBinding,
171
+ has: hasBinding
172
+ });
173
+ /**
174
+ * Micro-tier stateless resolver — the binding-family dependency root.
175
+ *
176
+ * Exposes `require<T>(env, name)` and `has(env, name)` off a per-request env
177
+ * object. Regular plugin so downstream binding plugins can declare
178
+ * `depends: [bindingsPlugin]` and reach it via `ctx.require(bindingsPlugin)`.
179
+ *
180
+ * @see README.md
181
+ */
182
+ const bindingsPlugin = createPlugin("bindings", {
183
+ config: { required: [] },
184
+ api: createBindingsApi
185
+ });
186
+ //#endregion
187
+ //#region src/plugins/d1/api.ts
188
+ /**
189
+ * Create the d1 api. Each method resolves the D1Database off the request
190
+ * `env` via the bindings plugin, then forwards to the native D1 call. The
191
+ * binding is never cached, so concurrent requests stay isolated (SB4).
192
+ *
193
+ * The return is intentionally NOT annotated `: Api`. Annotating it would
194
+ * collapse the per-method call-site generic `<T>` on `query`/`first` to
195
+ * `unknown`; instead the implementation forwards `<T>` to `all<T>()` /
196
+ * `first<T>()` and `types.ts#Api` remains the public-surface source of truth.
197
+ *
198
+ * @param {D1Ctx} ctx - Plugin context (own config + require).
199
+ * @returns {object} The d1 public api (query, first, run, batch, prepare, deployManifest).
200
+ * @example
201
+ * ```typescript
202
+ * const api = createD1Api(ctx);
203
+ * const { results } = await api.query<Product>(env, "SELECT * FROM products");
204
+ * ```
205
+ */
206
+ const createD1Api = (ctx) => {
207
+ const db = (env) => ctx.require(bindingsPlugin).require(env, ctx.config.binding);
208
+ return {
209
+ /**
210
+ * Run a statement and return all rows. Forwards the call-site generic to
211
+ * `all<T>()` so the result type is not widened to `unknown`.
212
+ *
213
+ * @param {WorkerEnv} env - Per-request Cloudflare bindings object.
214
+ * @param {string} sql - SQL text with `?` placeholders.
215
+ * @param {unknown[]} params - Bind parameters, in placeholder order.
216
+ * @returns {Promise<D1Result<T>>} All rows (`.results` is `T[]`).
217
+ * @example
218
+ * ```typescript
219
+ * const { results } = await api.query<Product>(env, "SELECT * FROM products WHERE active = ?", 1);
220
+ * ```
221
+ */
222
+ query: (env, sql, ...params) => db(env).prepare(sql).bind(...params).all(),
223
+ /**
224
+ * Run a statement and return the first row, or `null` if there are none.
225
+ * Forwards the call-site generic to `first<T>()`.
226
+ *
227
+ * @param {WorkerEnv} env - Per-request Cloudflare bindings object.
228
+ * @param {string} sql - SQL text with `?` placeholders.
229
+ * @param {unknown[]} params - Bind parameters, in placeholder order.
230
+ * @returns {Promise<T | null>} The first row, or `null` if none matched.
231
+ * @example
232
+ * ```typescript
233
+ * const row = await api.first<Product>(env, "SELECT * FROM products WHERE id = ?", id);
234
+ * ```
235
+ */
236
+ first: (env, sql, ...params) => db(env).prepare(sql).bind(...params).first(),
237
+ /**
238
+ * Run a write/DDL statement (INSERT/UPDATE/DELETE/DDL) and return the
239
+ * D1 result carrying `.meta` (e.g. `rows_written`, `last_row_id`).
240
+ *
241
+ * @param {WorkerEnv} env - Per-request Cloudflare bindings object.
242
+ * @param {string} sql - SQL text with `?` placeholders.
243
+ * @param {unknown[]} params - Bind parameters, in placeholder order.
244
+ * @returns {Promise<D1Result>} Result carrying `.meta`.
245
+ * @example
246
+ * ```typescript
247
+ * const res = await api.run(env, "INSERT INTO products (name) VALUES (?)", name);
248
+ * const id = res.meta.last_row_id;
249
+ * ```
250
+ */
251
+ run: (env, sql, ...params) => db(env).prepare(sql).bind(...params).run(),
252
+ /**
253
+ * Execute caller-built prepared statements atomically in one round-trip,
254
+ * returning one result per statement in order.
255
+ *
256
+ * @param {WorkerEnv} env - Per-request Cloudflare bindings object.
257
+ * @param {D1PreparedStatement[]} stmts - Statements built from prepare(env).
258
+ * @returns {Promise<D1Result[]>} One result per statement, order preserved.
259
+ * @example
260
+ * ```typescript
261
+ * const handle = api.prepare(env);
262
+ * await api.batch(env, [handle.prepare("INSERT INTO a VALUES (1)").bind()]);
263
+ * ```
264
+ */
265
+ batch: (env, stmts) => db(env).batch(stmts),
266
+ /**
267
+ * Resolve the request-scoped D1Database so callers can build prepared
268
+ * statements for batch(). Issues no query itself.
269
+ *
270
+ * @param {WorkerEnv} env - Per-request Cloudflare bindings object.
271
+ * @returns {D1Database} The request-resolved database handle.
272
+ * @example
273
+ * ```typescript
274
+ * const handle = api.prepare(env);
275
+ * const stmt = handle.prepare("SELECT * FROM t").bind();
276
+ * ```
277
+ */
278
+ prepare: (env) => db(env),
279
+ /**
280
+ * Return this plugin's deploy metadata for the deploy plugin to read.
281
+ * Build-time only — takes no `env`. The return is typed `DeployManifest`
282
+ * (from types.ts), which pins `kind` to the literal `"d1"` without an
283
+ * inline `as` assertion.
284
+ *
285
+ * @returns {DeployManifest} Deploy manifest entry `{ kind: "d1", binding, migrations }`.
286
+ * @example
287
+ * ```typescript
288
+ * const m = api.deployManifest();
289
+ * // => { kind: "d1", binding: "DB", migrations: "./migrations" }
290
+ * ```
291
+ */
292
+ deployManifest: () => ({
293
+ kind: "d1",
294
+ binding: ctx.config.binding,
295
+ migrations: ctx.config.migrations
296
+ })
297
+ };
298
+ };
299
+ /**
300
+ * Standard tier — Cloudflare D1 SQL access (thin typed wrappers, not an ORM).
301
+ *
302
+ * Exposes `query`, `first`, `run`, `batch`, `prepare`, and `deployManifest`.
303
+ * Resolves the D1 binding off the per-request `env` via the bindings plugin.
304
+ * No state, no events, no lifecycle hooks (request-scoped, spec/06 §3).
305
+ *
306
+ * @see README.md
307
+ */
308
+ const d1Plugin = createPlugin("d1", {
309
+ depends: [bindingsPlugin],
310
+ config: {
311
+ binding: "DB",
312
+ migrations: ""
313
+ },
314
+ api: (ctx) => createD1Api(ctx)
315
+ });
316
+ //#endregion
317
+ //#region src/plugins/durable-objects/api.ts
318
+ /**
319
+ * Builds the `app.durableObjects` API surface — `get` and `deployManifest`.
320
+ *
321
+ * All namespace resolution uses the per-call `env` argument: `env` is threaded, never
322
+ * stored (SB4 / design §1a). The config bindings map is frozen and read-only. No state
323
+ * is held on the plugin between calls (stateless — `Record<string, never>`).
324
+ *
325
+ * @param ctx - Plugin context with `config.bindings`, `require(bindingsPlugin)`, and core APIs.
326
+ * @returns The durableObjects API: `{ get, deployManifest }`.
327
+ * @example
328
+ * ```typescript
329
+ * const api = createDoApi(ctx);
330
+ * const stub = api.get(env, "counter", "room-42");
331
+ * const manifest = api.deployManifest(); // { kind: "do", bindings: { counter: "COUNTER" } }
332
+ * ```
333
+ */
334
+ const createDoApi = (ctx) => ({
335
+ /**
336
+ * Resolves a `DurableObjectStub` off the per-request env.
337
+ *
338
+ * Maps `logicalName` → `config.bindings[logicalName]` (falling back to `logicalName`
339
+ * itself when unmapped), derives a deterministic id via `namespace.idFromName(idName)`,
340
+ * and returns the addressed stub. Synchronous — returns a stub, not a Promise.
341
+ * Throws (via the bindings resolver) when the binding is not present on `env`.
342
+ *
343
+ * @param env - Per-request Cloudflare bindings object (Worker fetch/queue/scheduled env).
344
+ * @param logicalName - Logical DO name used in code (e.g. `"counter"`).
345
+ * @param idName - Stable id name passed to `idFromName` (e.g. `"room-42"`).
346
+ * @returns The addressed `DurableObjectStub`.
347
+ * @throws {Error} With `[moku-worker]` prefix when the binding is not bound on `env`.
348
+ * @example
349
+ * ```typescript
350
+ * const stub = app.durableObjects.get(env, "counter", "room-42");
351
+ * const res = await stub.fetch("https://do/increment");
352
+ * ```
353
+ */
354
+ get: (env, logicalName, idName) => {
355
+ const binding = ctx.config.bindings[logicalName] ?? logicalName;
356
+ const ns = ctx.require(bindingsPlugin).require(env, binding);
357
+ return ns.get(ns.idFromName(idName));
358
+ },
359
+ /**
360
+ * Returns this plugin's deploy metadata — read by the `deploy` plugin via
361
+ * `ctx.require(durableObjectsPlugin)`. Never reads sibling `pluginConfigs` (F6;
362
+ * spec/08 §5, §7). Pure synchronous read of `ctx.config.bindings`.
363
+ *
364
+ * @returns `{ kind: "do", bindings }` reflecting the frozen plugin config.
365
+ * @example
366
+ * ```typescript
367
+ * const manifest = app.durableObjects.deployManifest();
368
+ * // → { kind: "do", bindings: { counter: "COUNTER" } }
369
+ * ```
370
+ */
371
+ deployManifest: () => ({
372
+ kind: "do",
373
+ bindings: ctx.config.bindings
374
+ })
375
+ });
376
+ //#endregion
377
+ //#region src/plugins/durable-objects/helpers.ts
378
+ /**
379
+ * Returns a base class the consumer extends and exports from `worker.ts`.
380
+ *
381
+ * PURE (spec/03 §1): takes no `ctx`, has no side effects, and may be called before
382
+ * `createApp`. The static `doName` property captures `name` for diagnostics and
383
+ * binding correlation. The constructor stores `(state, env)` as `this.ctx` / `this.env`,
384
+ * satisfying the Cloudflare Durable Object constructor contract. The plugin NEVER
385
+ * generates the final exported class — the consumer owns that class.
386
+ *
387
+ * @param name - Logical DO name; captured as `static doName` for diagnostics.
388
+ * @returns A base class (constructor) the consumer extends.
389
+ * @example
390
+ * ```typescript
391
+ * // src/counter.ts
392
+ * import { defineDurableObject } from "@moku-labs/worker";
393
+ *
394
+ * export class Counter extends defineDurableObject("Counter") {
395
+ * async fetch(): Promise<Response> {
396
+ * const n = ((await this.ctx.storage.get<number>("n")) ?? 0) + 1;
397
+ * await this.ctx.storage.put("n", n);
398
+ * return Response.json({ n });
399
+ * }
400
+ * }
401
+ * ```
402
+ */
403
+ const defineDurableObject = (name) => {
404
+ /**
405
+ * Base implementation of the Cloudflare Durable Object constructor contract.
406
+ * Stores `(ctx, env)` as readonly properties for consumer subclasses to use.
407
+ */
408
+ class DurableObjectBaseImpl {
409
+ /**
410
+ * Cloudflare per-object storage/alarm context (DurableObjectState).
411
+ * Use `this.ctx.storage` to read/write durable storage and `this.ctx.id` to inspect the DO id.
412
+ */
413
+ ctx;
414
+ /**
415
+ * Per-object Cloudflare bindings (per-request WorkerEnv).
416
+ * Mirrors the env passed at construction time; never cached across requests.
417
+ */
418
+ env;
419
+ /**
420
+ * Logical DO name captured from `defineDurableObject(name)`.
421
+ * Used for diagnostics and binding correlation.
422
+ */
423
+ static doName = name;
424
+ /**
425
+ * Constructs the base Durable Object with Cloudflare's required signature.
426
+ *
427
+ * @param ctx - Cloudflare DurableObjectState (storage, id, blockConcurrencyWhile, …).
428
+ * @param env - Per-request Cloudflare bindings object (WorkerEnv).
429
+ * @example
430
+ * ```typescript
431
+ * class Counter extends Base {
432
+ * constructor(ctx: DurableObjectState, env: WorkerEnv) { super(ctx, env); }
433
+ * }
434
+ * ```
435
+ */
436
+ constructor(ctx, env) {
437
+ this.ctx = ctx;
438
+ this.env = env;
439
+ }
440
+ }
441
+ return DurableObjectBaseImpl;
442
+ };
443
+ /**
444
+ * Cloudflare Durable Objects plugin — Standard tier.
445
+ *
446
+ * Exposes `get(env, logicalName, idName)` (synchronous stub accessor, threaded env) and
447
+ * `deployManifest()` (build-time metadata). Depends on `bindingsPlugin` for namespace
448
+ * resolution. The `defineDurableObject` helper is mounted under `helpers` and re-exported
449
+ * at the top level for consumer use.
450
+ *
451
+ * @example
452
+ * ```typescript
453
+ * // Consumer endpoint handler:
454
+ * const stub = app.durableObjects.get(env, "counter", params.room!);
455
+ * const res = await stub.fetch("https://do/increment");
456
+ * // Consumer DO class:
457
+ * export class Counter extends defineDurableObject("Counter") {
458
+ * async fetch(): Promise<Response> { return new Response("ok"); }
459
+ * }
460
+ * ```
461
+ * @see README.md
462
+ */
463
+ const durableObjectsPlugin = createPlugin("durableObjects", {
464
+ depends: [bindingsPlugin],
465
+ config: { bindings: {} },
466
+ api: createDoApi,
467
+ helpers: { defineDurableObject }
468
+ });
469
+ //#endregion
470
+ //#region src/plugins/kv/api.ts
471
+ /**
472
+ * Builds the app.kv.* api. Resolves the KV namespace off the REQUEST-SUPPLIED env
473
+ * on every call — env is threaded, never stored (design §1a / SB4).
474
+ *
475
+ * @param ctx - The kv plugin context (own config + merged events).
476
+ * @returns The app.kv api: get / put / delete / list / deployManifest.
477
+ * @example
478
+ * ```typescript
479
+ * const api = createKvApi(ctx);
480
+ * const value = await api.get(env, "key");
481
+ * ```
482
+ */
483
+ const createKvApi = (ctx) => {
484
+ const ns = (env) => ctx.require(bindingsPlugin).require(env, ctx.config.binding);
485
+ return {
486
+ /**
487
+ * Reads a value by key from the KV namespace. Returns null when absent.
488
+ *
489
+ * @param env - The per-request Cloudflare env (threaded, never stored).
490
+ * @param key - The key to read.
491
+ * @returns The stored value, or null when absent.
492
+ * @example
493
+ * ```typescript
494
+ * const value = await api.get(env, "feature-flags");
495
+ * ```
496
+ */
497
+ get: async (env, key) => ns(env).get(key),
498
+ /**
499
+ * Writes a string value under a key, optionally with KV put options.
500
+ *
501
+ * @param env - The per-request Cloudflare env.
502
+ * @param key - The key to write.
503
+ * @param value - The string value to store.
504
+ * @param opts - Optional expiration / metadata.
505
+ * @returns Resolves once the write is acknowledged.
506
+ * @example
507
+ * ```typescript
508
+ * await api.put(env, "session:1", "data", { expirationTtl: 3600 });
509
+ * ```
510
+ */
511
+ put: async (env, key, value, opts) => ns(env).put(key, value, opts),
512
+ /**
513
+ * Removes a key from the namespace (no-op if absent).
514
+ *
515
+ * @param env - The per-request Cloudflare env.
516
+ * @param key - The key to delete.
517
+ * @returns Resolves once the delete is acknowledged.
518
+ * @example
519
+ * ```typescript
520
+ * await api.delete(env, "session:expired");
521
+ * ```
522
+ */
523
+ delete: async (env, key) => ns(env).delete(key),
524
+ /**
525
+ * Lists keys in the namespace, optionally filtered/paginated via opts.
526
+ *
527
+ * @param env - The per-request Cloudflare env.
528
+ * @param opts - Optional prefix / cursor / limit.
529
+ * @returns The list result from the KV namespace.
530
+ * @example
531
+ * ```typescript
532
+ * const { keys } = await api.list(env, { prefix: "session:" });
533
+ * ```
534
+ */
535
+ list: async (env, opts) => ns(env).list(opts),
536
+ /**
537
+ * Returns this plugin's own deploy metadata, read by the deploy plugin via
538
+ * require (design §6 / F6). Build-time only — takes no env.
539
+ *
540
+ * @returns The kv deploy descriptor with kind literal and binding name.
541
+ * @example
542
+ * ```typescript
543
+ * const manifest = api.deployManifest(); // { kind: "kv", binding: "KV" }
544
+ * ```
545
+ */
546
+ deployManifest: () => ({
547
+ kind: "kv",
548
+ binding: ctx.config.binding
549
+ })
550
+ };
551
+ };
552
+ /**
553
+ * Micro tier — thin env-first wrapper over a Cloudflare KV namespace.
554
+ *
555
+ * Resolves the KV namespace per request via `ctx.require(bindingsPlugin)`;
556
+ * never stores env in state (design §1a / SB4). No lifecycle hooks —
557
+ * request-scoped; nothing to open or close.
558
+ *
559
+ * @see README.md
560
+ */
561
+ const kvPlugin = createPlugin("kv", {
562
+ depends: [bindingsPlugin],
563
+ config: { binding: "KV" },
564
+ api: createKvApi
565
+ });
566
+ //#endregion
567
+ //#region src/plugins/queues/api.ts
568
+ /**
569
+ * @file queues plugin — API factory (send, sendBatch, consume, deployManifest).
570
+ *
571
+ * All binding-resolving methods take the per-request `env` as the first argument
572
+ * and resolve the `Queue` via `ctx.require(bindingsPlugin).require<Queue>(env, name)`.
573
+ * The `env` is never stored (SB4 / design §1a) — resolved fresh on every call.
574
+ */
575
+ /**
576
+ * Builds app.queues.* — read by worker.ts queue() delegation (design §1d; spec/02 §7).
577
+ *
578
+ * Resolves Queue bindings off the request env per call (never stored — SB4).
579
+ * Emits `queue:message` for observability after each consumed message (F8).
580
+ *
581
+ * @param ctx - Plugin context (own config + require + emit).
582
+ * @returns The queues API surface: send, sendBatch, consume, deployManifest.
583
+ * @example
584
+ * ```ts
585
+ * // Worker entry (design §1d)
586
+ * export default {
587
+ * queue: (b, e, c) => app.queues.consume(b, e, c),
588
+ * };
589
+ * ```
590
+ */
591
+ const createQueuesApi = (ctx) => {
592
+ /**
593
+ * Resolves a named Queue binding from the per-request env.
594
+ * Throws a [moku-worker]-prefixed error when the binding is absent.
595
+ *
596
+ * @param env - Per-request Cloudflare bindings.
597
+ * @param name - Queue binding name.
598
+ * @returns The resolved Queue instance.
599
+ * @example
600
+ * ```ts
601
+ * const q = queue(env, "ORDERS");
602
+ * ```
603
+ */
604
+ const queue = (env, name) => ctx.require(bindingsPlugin).require(env, name);
605
+ return {
606
+ /**
607
+ * Enqueue a single message onto the named queue.
608
+ *
609
+ * Resolves the Queue binding fresh from `env` on every call (SB4).
610
+ * Request/response work → api method, never emit (F8).
611
+ *
612
+ * @param env - Per-request Cloudflare bindings object.
613
+ * @param q - Target queue binding name in `env`.
614
+ * @param body - Message body to enqueue.
615
+ * @returns Resolves once the message is enqueued.
616
+ * @throws {Error} With a `[moku-worker]` prefix if the binding is missing.
617
+ * @example
618
+ * ```ts
619
+ * await app.queues.send(env, "ORDERS", { orderId: "123" });
620
+ * ```
621
+ */
622
+ send: async (env, q, body) => {
623
+ await queue(env, q).send(body);
624
+ },
625
+ /**
626
+ * Enqueue many messages in one call; each element becomes one message.
627
+ *
628
+ * Maps each body to `{ body }` before calling `Queue.sendBatch` (design §4.3).
629
+ *
630
+ * @param env - Per-request Cloudflare bindings object.
631
+ * @param q - Target queue binding name in `env`.
632
+ * @param bodies - Array of message bodies; each becomes one message.
633
+ * @returns Resolves once all messages are enqueued.
634
+ * @throws {Error} With a `[moku-worker]` prefix if the binding is missing.
635
+ * @example
636
+ * ```ts
637
+ * await app.queues.sendBatch(env, "ORDERS", orders);
638
+ * ```
639
+ */
640
+ sendBatch: async (env, q, bodies) => {
641
+ await queue(env, q).sendBatch(bodies.map((body) => ({ body })));
642
+ },
643
+ /**
644
+ * Consumer dispatch — the Worker's `queue()` export delegates here.
645
+ *
646
+ * Iterates `batch.messages`, **awaits** `config.onMessage(message, env)` per message
647
+ * (so Cloudflare gets a settled promise and the handler controls ack/retry; F8,
648
+ * spec/07 §3 — never emit for awaited work), then fire-and-forget emits `queue:message`
649
+ * for observability. Returns a promise the Worker **must** await so the isolate is not
650
+ * killed mid-batch.
651
+ *
652
+ * @param batch - The incoming message batch from Cloudflare.
653
+ * @param env - Per-request Cloudflare bindings object.
654
+ * @param _exec - waitUntil / passThroughOnException (reserved for future use).
655
+ * @returns Resolves after all messages in the batch are processed.
656
+ * @throws {Error} Re-throws any error from `config.onMessage` so Cloudflare can retry.
657
+ * @example
658
+ * ```ts
659
+ * // Worker entry
660
+ * queue: (b, e, c) => app.queues.consume(b, e, c),
661
+ * ```
662
+ */
663
+ consume: async (batch, env, _exec) => {
664
+ for (const m of batch.messages) {
665
+ await ctx.config.onMessage(m, env);
666
+ ctx.emit("queue:message", {
667
+ queue: batch.queue,
668
+ messageId: m.id
669
+ });
670
+ }
671
+ },
672
+ /**
673
+ * Returns this plugin's deploy metadata, read by the deploy plugin via
674
+ * `ctx.require(queuesPlugin).deployManifest()` (F6 — never reads sibling config).
675
+ *
676
+ * @returns Deploy manifest entry `{ kind: "queue", producers }`.
677
+ * @example
678
+ * ```ts
679
+ * const manifest = ctx.require(queuesPlugin).deployManifest();
680
+ * // → { kind: "queue", producers: ["orders"] }
681
+ * ```
682
+ */
683
+ deployManifest: () => ({
684
+ kind: "queue",
685
+ producers: ctx.config.producers
686
+ })
687
+ };
688
+ };
689
+ /**
690
+ * Standard tier — Cloudflare Queues producer + consumer dispatch.
691
+ *
692
+ * `events` is declared first and via `register.map<QueueEvents>` so the plugin's own events infer
693
+ * into the factory context; the api wiring is therefore arrow-wrapped (contextually typed).
694
+ *
695
+ * @see README.md
696
+ */
697
+ const queuesPlugin = createPlugin("queues", {
698
+ events: (register) => register.map({ "queue:message": "A queue message was processed" }),
699
+ depends: [bindingsPlugin],
700
+ config: {
701
+ producers: [],
702
+ onMessage: async () => {}
703
+ },
704
+ api: (ctx) => createQueuesApi(ctx)
705
+ });
706
+ //#endregion
707
+ //#region src/plugins/storage/providers/r2.ts
708
+ /**
709
+ * Build a StorageProvider backed by the real R2Bucket resolved off the
710
+ * per-request env via the bindings plugin. The bucket is resolved fresh on
711
+ * EVERY method call — never cached, so concurrent requests stay isolated
712
+ * (worker-api-design SB4; spec/08 §6).
713
+ *
714
+ * Each method is `async` so that synchronous throws from `bindings.require`
715
+ * (e.g. missing binding) are automatically wrapped in rejected Promises —
716
+ * callers can always use `await` / `.catch` instead of `try/catch`.
717
+ *
718
+ * @param bindings - The bindings plugin API (provides `require<T>`).
719
+ * @param env - The per-request Cloudflare bindings object.
720
+ * @param bucket - The R2 bucket binding name (e.g. "ASSETS").
721
+ * @returns {StorageProvider} A provider that delegates to the resolved R2Bucket.
722
+ * @example
723
+ * ```typescript
724
+ * const provider = resolveR2Provider(ctx.require(bindingsPlugin), env, ctx.config.bucket);
725
+ * const body = await provider.get("my-object");
726
+ * ```
727
+ */
728
+ const resolveR2Provider = (bindings, env, bucket) => {
729
+ /**
730
+ * Resolve the R2Bucket for this request's env. Throws on missing binding.
731
+ *
732
+ * @returns {R2Bucket} The resolved R2Bucket binding.
733
+ * @example
734
+ * ```typescript
735
+ * const bucket = b();
736
+ * ```
737
+ */
738
+ const b = () => bindings.require(env, bucket);
739
+ return {
740
+ /**
741
+ * Read an object from the bucket.
742
+ *
743
+ * @param key - The object key.
744
+ * @returns {Promise<R2ObjectBody | null>} The R2ObjectBody, or null if the key is absent.
745
+ * @example
746
+ * ```typescript
747
+ * const body = await provider.get("assets/logo.png");
748
+ * ```
749
+ */
750
+ async get(key) {
751
+ return b().get(key);
752
+ },
753
+ /**
754
+ * Write an object to the bucket.
755
+ *
756
+ * @param key - The object key.
757
+ * @param value - The object contents (any R2-accepted type).
758
+ * @returns {Promise<R2Object>} The R2Object metadata for the written object.
759
+ * @example
760
+ * ```typescript
761
+ * const obj = await provider.put("assets/logo.png", buffer);
762
+ * ```
763
+ */
764
+ async put(key, value) {
765
+ return b().put(key, value);
766
+ },
767
+ /**
768
+ * Remove one or more objects from the bucket. No-op when a key is absent.
769
+ *
770
+ * @param key - A single key or array of keys to remove.
771
+ * @returns {Promise<void>} Resolves once removed.
772
+ * @example
773
+ * ```typescript
774
+ * await provider.delete("assets/old.png");
775
+ * ```
776
+ */
777
+ async delete(key) {
778
+ return b().delete(key);
779
+ },
780
+ /**
781
+ * List objects, optionally filtered by R2ListOptions.
782
+ *
783
+ * @param opts - Optional list options (prefix, limit, cursor, delimiter).
784
+ * @returns {Promise<R2Objects>} The R2Objects list result.
785
+ * @example
786
+ * ```typescript
787
+ * const { objects } = await provider.list({ prefix: "images/" });
788
+ * ```
789
+ */
790
+ async list(opts) {
791
+ return b().list(opts);
792
+ }
793
+ };
794
+ };
795
+ //#endregion
796
+ //#region src/plugins/storage/api.ts
797
+ /**
798
+ * Build the env-first storage API. Each runtime method resolves the bucket
799
+ * provider fresh from the per-request `env` — nothing is stored, so concurrent
800
+ * requests stay isolated (worker-api-design SB4; spec/08 §6,§7).
801
+ *
802
+ * The `deployManifest()` method is build-time only: it reads from `ctx.config`
803
+ * and never touches `env` or R2.
804
+ *
805
+ * @param ctx - Plugin context (config + require for bindings resolution).
806
+ * @returns {StorageApi} The env-first storage API surface.
807
+ * @example
808
+ * ```typescript
809
+ * const api = createStorageApi(ctx);
810
+ * const body = await api.get(env, "my-object");
811
+ * ```
812
+ */
813
+ const createStorageApi = (ctx) => {
814
+ /**
815
+ * Resolve the StorageProvider for the given per-request env. Called on every
816
+ * method invocation — the bucket binding is never cached across calls.
817
+ *
818
+ * @param env - The per-request Cloudflare bindings object.
819
+ * @returns {StorageProvider} A StorageProvider delegating to the resolved R2Bucket.
820
+ * @example
821
+ * ```typescript
822
+ * const p = provider(env);
823
+ * const body = await p.get("key");
824
+ * ```
825
+ */
826
+ const provider = (env) => resolveR2Provider(ctx.require(bindingsPlugin), env, ctx.config.bucket);
827
+ return {
828
+ /**
829
+ * Read an object from the bucket; resolves null when the key is absent.
830
+ *
831
+ * @param env - Per-request Cloudflare bindings.
832
+ * @param key - Object key.
833
+ * @returns {Promise<R2ObjectBody | null>} The R2ObjectBody, or null.
834
+ * @example
835
+ * ```typescript
836
+ * const body = await api.get(env, "assets/logo.png");
837
+ * ```
838
+ */
839
+ get: (env, key) => provider(env).get(key),
840
+ /**
841
+ * Write an object to the bucket.
842
+ *
843
+ * @param env - Per-request Cloudflare bindings.
844
+ * @param key - Object key.
845
+ * @param value - Object contents (ReadableStream | ArrayBuffer | ArrayBufferView | string | Blob | null).
846
+ * @returns {Promise<R2Object>} The R2Object metadata for the written object.
847
+ * @example
848
+ * ```typescript
849
+ * const obj = await api.put(env, "assets/logo.png", buffer);
850
+ * ```
851
+ */
852
+ put: (env, key, value) => provider(env).put(key, value),
853
+ /**
854
+ * Remove an object (or array of keys) from the bucket. No-op when absent.
855
+ *
856
+ * @param env - Per-request Cloudflare bindings.
857
+ * @param key - Object key or array of keys.
858
+ * @returns {Promise<void>} Resolves once removed.
859
+ * @example
860
+ * ```typescript
861
+ * await api.delete(env, "assets/old.png");
862
+ * ```
863
+ */
864
+ delete: (env, key) => provider(env).delete(key),
865
+ /**
866
+ * List objects, optionally filtered by R2ListOptions.
867
+ *
868
+ * @param env - Per-request Cloudflare bindings.
869
+ * @param opts - Optional R2ListOptions (prefix, limit, cursor, delimiter).
870
+ * @returns {Promise<R2Objects>} The R2Objects list result.
871
+ * @example
872
+ * ```typescript
873
+ * const { objects } = await api.list(env, { prefix: "images/" });
874
+ * ```
875
+ */
876
+ list: (env, opts) => provider(env).list(opts),
877
+ /**
878
+ * Return this plugin's deploy metadata. Build-time only — does not touch
879
+ * `env` or R2. The deploy plugin reads this via `ctx.require(storagePlugin).deployManifest()`.
880
+ *
881
+ * @returns {StorageManifest} Deploy manifest entry `{ kind: "r2", bucket, upload }`.
882
+ * @example
883
+ * ```typescript
884
+ * const manifest = api.deployManifest();
885
+ * // { kind: "r2", bucket: "ASSETS", upload: "./public" }
886
+ * ```
887
+ */
888
+ deployManifest: () => ({
889
+ kind: "r2",
890
+ bucket: ctx.config.bucket,
891
+ upload: ctx.config.upload
892
+ })
893
+ };
894
+ };
895
+ /**
896
+ * Complex tier — Cloudflare R2 object storage behind a provider adapter seam.
897
+ *
898
+ * Exposes `get`, `put`, `delete`, `list` (all env-first) and `deployManifest()`
899
+ * (build-time). Depends on `bindingsPlugin` to resolve the `R2Bucket` binding
900
+ * per request. No state, no events, no lifecycle hooks.
901
+ *
902
+ * @see README.md
903
+ */
904
+ const storagePlugin = createPlugin("storage", {
905
+ depends: [bindingsPlugin],
906
+ config: {
907
+ upload: "",
908
+ bucket: "ASSETS"
909
+ },
910
+ api: createStorageApi
911
+ });
912
+ //#endregion
913
+ Object.defineProperty(exports, "__exportAll", {
914
+ enumerable: true,
915
+ get: function() {
916
+ return __exportAll;
917
+ }
918
+ });
919
+ Object.defineProperty(exports, "__toESM", {
920
+ enumerable: true,
921
+ get: function() {
922
+ return __toESM;
923
+ }
924
+ });
925
+ Object.defineProperty(exports, "bindingsPlugin", {
926
+ enumerable: true,
927
+ get: function() {
928
+ return bindingsPlugin;
929
+ }
930
+ });
931
+ Object.defineProperty(exports, "coreConfig", {
932
+ enumerable: true,
933
+ get: function() {
934
+ return coreConfig;
935
+ }
936
+ });
937
+ Object.defineProperty(exports, "createCore", {
938
+ enumerable: true,
939
+ get: function() {
940
+ return createCore;
941
+ }
942
+ });
943
+ Object.defineProperty(exports, "createPlugin", {
944
+ enumerable: true,
945
+ get: function() {
946
+ return createPlugin;
947
+ }
948
+ });
949
+ Object.defineProperty(exports, "d1Plugin", {
950
+ enumerable: true,
951
+ get: function() {
952
+ return d1Plugin;
953
+ }
954
+ });
955
+ Object.defineProperty(exports, "defineDurableObject", {
956
+ enumerable: true,
957
+ get: function() {
958
+ return defineDurableObject;
959
+ }
960
+ });
961
+ Object.defineProperty(exports, "durableObjectsPlugin", {
962
+ enumerable: true,
963
+ get: function() {
964
+ return durableObjectsPlugin;
965
+ }
966
+ });
967
+ Object.defineProperty(exports, "kvPlugin", {
968
+ enumerable: true,
969
+ get: function() {
970
+ return kvPlugin;
971
+ }
972
+ });
973
+ Object.defineProperty(exports, "queuesPlugin", {
974
+ enumerable: true,
975
+ get: function() {
976
+ return queuesPlugin;
977
+ }
978
+ });
979
+ Object.defineProperty(exports, "stagePlugin", {
980
+ enumerable: true,
981
+ get: function() {
982
+ return stagePlugin;
983
+ }
984
+ });
985
+ Object.defineProperty(exports, "storagePlugin", {
986
+ enumerable: true,
987
+ get: function() {
988
+ return storagePlugin;
989
+ }
990
+ });