@moku-labs/worker 0.1.4 → 0.2.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/README.md +1 -1
- package/dist/config-Bj3GUJT_.d.cts +63 -0
- package/dist/config-Bj3GUJT_.d.mts +63 -0
- package/dist/index.cjs +24 -13
- package/dist/index.d.cts +84 -37
- package/dist/index.d.mts +84 -37
- package/dist/index.mjs +24 -13
- package/package.json +1 -1
- package/dist/config-AjH57AmD.d.cts +0 -36
- package/dist/config-AjH57AmD.d.mts +0 -36
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ export const app = createApp({
|
|
|
33
33
|
server: {
|
|
34
34
|
endpoints: [
|
|
35
35
|
endpoint("/health").get(() => new Response("ok", { status: 200 })),
|
|
36
|
-
endpoint("/api/data/{lang
|
|
36
|
+
endpoint("/api/data/{lang:?}").get(({ params }) =>
|
|
37
37
|
Response.json({ lang: params.lang ?? "en" })
|
|
38
38
|
),
|
|
39
39
|
endpoint("/users/{userId}").get(
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { PluginCtx } from "@moku-labs/core";
|
|
2
|
+
|
|
3
|
+
//#region src/config.d.ts
|
|
4
|
+
/** Per-request Cloudflare bindings object (env). Framework-level shared type. */
|
|
5
|
+
type WorkerEnv = Record<string, unknown>;
|
|
6
|
+
/** Global framework config — flat, with complete defaults. */
|
|
7
|
+
type WorkerConfig = {
|
|
8
|
+
stage: "production" | "development" | "test";
|
|
9
|
+
name: string;
|
|
10
|
+
compatibilityDate: string;
|
|
11
|
+
};
|
|
12
|
+
/** Global framework events — declared once, visible to every plugin. */
|
|
13
|
+
type WorkerEvents = {
|
|
14
|
+
"request:start": {
|
|
15
|
+
method: string;
|
|
16
|
+
path: string;
|
|
17
|
+
requestId: string;
|
|
18
|
+
};
|
|
19
|
+
"request:end": {
|
|
20
|
+
method: string;
|
|
21
|
+
path: string;
|
|
22
|
+
status: number;
|
|
23
|
+
ms: number;
|
|
24
|
+
};
|
|
25
|
+
"deploy:phase": {
|
|
26
|
+
phase: string;
|
|
27
|
+
detail?: string;
|
|
28
|
+
};
|
|
29
|
+
"deploy:complete": {
|
|
30
|
+
url: string;
|
|
31
|
+
};
|
|
32
|
+
"provision:resource": {
|
|
33
|
+
kind: "kv" | "r2" | "d1" | "queue" | "do";
|
|
34
|
+
name: string;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Worker-bound plugin context for Layer-3 consumer plugins. Aliases the core
|
|
39
|
+
* {@link PluginCtx} with the global {@link WorkerEvents} pre-merged into the event
|
|
40
|
+
* map, so a consumer plugin types its own `config`/`state`/`emit` by passing only
|
|
41
|
+
* its OWN event map — never hand-merging `WorkerEvents`, and never importing from
|
|
42
|
+
* `@moku-labs/core` (a Layer-1 boundary the spec validator flags for consumers).
|
|
43
|
+
*
|
|
44
|
+
* A plugin that resolves sibling plugins also needs a `require` field; intersect the
|
|
45
|
+
* public `Server.RequireFn` for it, exactly as this framework's own plugins do. When
|
|
46
|
+
* you need the unaliased shape (e.g. a different global event map), use the raw
|
|
47
|
+
* re-exported {@link PluginCtx} instead.
|
|
48
|
+
*
|
|
49
|
+
* @template Config - This plugin's own flat configuration object.
|
|
50
|
+
* @template State - This plugin's mutable state (use `Record<string, never>` when stateless).
|
|
51
|
+
* @template Events - This plugin's own event map, merged on top of {@link WorkerEvents}; defaults to none.
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* import type { Server, WorkerPluginCtx } from "@moku-labs/worker";
|
|
55
|
+
* type MyEvents = { "my:done": { id: string } };
|
|
56
|
+
* export type MyCtx = WorkerPluginCtx<MyConfig, Record<string, never>, MyEvents> & {
|
|
57
|
+
* require: Server.RequireFn;
|
|
58
|
+
* };
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
type WorkerPluginCtx<Config, State, Events extends Record<string, unknown> = Record<never, never>> = PluginCtx<Config, State, WorkerEvents & Events>;
|
|
62
|
+
//#endregion
|
|
63
|
+
export { WorkerPluginCtx as i, WorkerEnv as n, WorkerEvents as r, WorkerConfig as t };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { PluginCtx } from "@moku-labs/core";
|
|
2
|
+
|
|
3
|
+
//#region src/config.d.ts
|
|
4
|
+
/** Per-request Cloudflare bindings object (env). Framework-level shared type. */
|
|
5
|
+
type WorkerEnv = Record<string, unknown>;
|
|
6
|
+
/** Global framework config — flat, with complete defaults. */
|
|
7
|
+
type WorkerConfig = {
|
|
8
|
+
stage: "production" | "development" | "test";
|
|
9
|
+
name: string;
|
|
10
|
+
compatibilityDate: string;
|
|
11
|
+
};
|
|
12
|
+
/** Global framework events — declared once, visible to every plugin. */
|
|
13
|
+
type WorkerEvents = {
|
|
14
|
+
"request:start": {
|
|
15
|
+
method: string;
|
|
16
|
+
path: string;
|
|
17
|
+
requestId: string;
|
|
18
|
+
};
|
|
19
|
+
"request:end": {
|
|
20
|
+
method: string;
|
|
21
|
+
path: string;
|
|
22
|
+
status: number;
|
|
23
|
+
ms: number;
|
|
24
|
+
};
|
|
25
|
+
"deploy:phase": {
|
|
26
|
+
phase: string;
|
|
27
|
+
detail?: string;
|
|
28
|
+
};
|
|
29
|
+
"deploy:complete": {
|
|
30
|
+
url: string;
|
|
31
|
+
};
|
|
32
|
+
"provision:resource": {
|
|
33
|
+
kind: "kv" | "r2" | "d1" | "queue" | "do";
|
|
34
|
+
name: string;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Worker-bound plugin context for Layer-3 consumer plugins. Aliases the core
|
|
39
|
+
* {@link PluginCtx} with the global {@link WorkerEvents} pre-merged into the event
|
|
40
|
+
* map, so a consumer plugin types its own `config`/`state`/`emit` by passing only
|
|
41
|
+
* its OWN event map — never hand-merging `WorkerEvents`, and never importing from
|
|
42
|
+
* `@moku-labs/core` (a Layer-1 boundary the spec validator flags for consumers).
|
|
43
|
+
*
|
|
44
|
+
* A plugin that resolves sibling plugins also needs a `require` field; intersect the
|
|
45
|
+
* public `Server.RequireFn` for it, exactly as this framework's own plugins do. When
|
|
46
|
+
* you need the unaliased shape (e.g. a different global event map), use the raw
|
|
47
|
+
* re-exported {@link PluginCtx} instead.
|
|
48
|
+
*
|
|
49
|
+
* @template Config - This plugin's own flat configuration object.
|
|
50
|
+
* @template State - This plugin's mutable state (use `Record<string, never>` when stateless).
|
|
51
|
+
* @template Events - This plugin's own event map, merged on top of {@link WorkerEvents}; defaults to none.
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* import type { Server, WorkerPluginCtx } from "@moku-labs/worker";
|
|
55
|
+
* type MyEvents = { "my:done": { id: string } };
|
|
56
|
+
* export type MyCtx = WorkerPluginCtx<MyConfig, Record<string, never>, MyEvents> & {
|
|
57
|
+
* require: Server.RequireFn;
|
|
58
|
+
* };
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
type WorkerPluginCtx<Config, State, Events extends Record<string, unknown> = Record<never, never>> = PluginCtx<Config, State, WorkerEvents & Events>;
|
|
62
|
+
//#endregion
|
|
63
|
+
export { WorkerPluginCtx as i, WorkerEnv as n, WorkerEvents as r, WorkerConfig as t };
|
package/dist/index.cjs
CHANGED
|
@@ -121,17 +121,21 @@ const makeEndpoint = (path, method, handler) => ({
|
|
|
121
121
|
handler
|
|
122
122
|
});
|
|
123
123
|
/**
|
|
124
|
-
* Build a typed `Endpoint`. `{name}` → required param; `{name
|
|
124
|
+
* Build a typed `Endpoint`. `{name}` → required param (`string`); `{name:?}` →
|
|
125
|
+
* optional param (`string | undefined`). The path template flows into each
|
|
126
|
+
* handler's `ctx.params` ({@link PathParams}), so a required `{id}` is typed
|
|
127
|
+
* `string` — no `?? ""` fallback needed.
|
|
125
128
|
*
|
|
126
129
|
* PURE factory (spec/03 §1): no ctx, no lifecycle, no side effects; safe to run
|
|
127
130
|
* before `createApp`. Each verb method (`get`, `post`, …, `all`) returns the
|
|
128
131
|
* truthful Endpoint value — `method: "ALL"` is never used as a `"get"` sentinel.
|
|
129
132
|
*
|
|
130
|
-
* @
|
|
133
|
+
* @template Path - The path template literal, inferred from `path`.
|
|
134
|
+
* @param path - Endpoint path, optionally with `{name}` / `{name:?}` params.
|
|
131
135
|
* @returns A builder whose verb methods each return a typed `Endpoint`.
|
|
132
136
|
* @example
|
|
133
137
|
* ```typescript
|
|
134
|
-
* endpoint("/api/data/{lang
|
|
138
|
+
* endpoint("/api/data/{lang:?}").get(({ params }) =>
|
|
135
139
|
* Response.json({ lang: params.lang ?? "en" })
|
|
136
140
|
* );
|
|
137
141
|
* ```
|
|
@@ -232,27 +236,28 @@ const endpoint = (path) => ({
|
|
|
232
236
|
const LITERAL_WEIGHT = 2;
|
|
233
237
|
/** Specificity weight for a required param segment `{name}`. */
|
|
234
238
|
const REQUIRED_PARAM_WEIGHT = 1;
|
|
235
|
-
/** Specificity weight for an optional param segment `{name
|
|
239
|
+
/** Specificity weight for an optional param segment `{name:?}`. */
|
|
236
240
|
const OPTIONAL_PARAM_WEIGHT = 0;
|
|
237
241
|
/**
|
|
238
242
|
* Parse one path segment string into a typed `PathSegment`.
|
|
239
243
|
*
|
|
240
|
-
* `{name}` → required param; `{name
|
|
244
|
+
* `{name}` → required param; `{name:?}` → optional param; anything else → literal.
|
|
245
|
+
* The `:?` optional suffix matches the `@moku-labs/web` router pattern.
|
|
241
246
|
*
|
|
242
247
|
* @param raw - A single path segment token (no leading slash).
|
|
243
248
|
* @returns The parsed `PathSegment`.
|
|
244
249
|
* @example
|
|
245
250
|
* ```typescript
|
|
246
|
-
* parseSegment("{id}")
|
|
247
|
-
* parseSegment("{id
|
|
248
|
-
* parseSegment("api")
|
|
251
|
+
* parseSegment("{id}") // → { value: "id", param: true, optional: false }
|
|
252
|
+
* parseSegment("{id:?}") // → { value: "id", param: true, optional: true }
|
|
253
|
+
* parseSegment("api") // → { value: "api", param: false, optional: false }
|
|
249
254
|
* ```
|
|
250
255
|
*/
|
|
251
256
|
const parseSegment = (raw) => {
|
|
252
257
|
if (raw.startsWith("{") && raw.endsWith("}")) {
|
|
253
258
|
const inner = raw.slice(1, -1);
|
|
254
|
-
if (inner.endsWith("
|
|
255
|
-
value: inner.slice(0, -
|
|
259
|
+
if (inner.endsWith(":?")) return {
|
|
260
|
+
value: inner.slice(0, -2),
|
|
256
261
|
param: true,
|
|
257
262
|
optional: true
|
|
258
263
|
};
|
|
@@ -347,7 +352,7 @@ const tryMatchEndpoint = (compiled, method, tokens) => {
|
|
|
347
352
|
* @example
|
|
348
353
|
* ```typescript
|
|
349
354
|
* const a = compileEndpoint(endpoint("/api/{id}").get(handler)); // specificity 3
|
|
350
|
-
* const b = compileEndpoint(endpoint("/api/{id
|
|
355
|
+
* const b = compileEndpoint(endpoint("/api/{id:?}").get(handler)); // specificity 2
|
|
351
356
|
* [b, a].sort(bySpecificityDesc); // → [a, b] — higher specificity first
|
|
352
357
|
* ```
|
|
353
358
|
*/
|
|
@@ -390,10 +395,12 @@ const findBestMatch = (table, method, tokens) => {
|
|
|
390
395
|
*
|
|
391
396
|
* Called by `onInit` — the one-time per-isolate setup. Sorts `state.table` by
|
|
392
397
|
* specificity (descending), validates that no endpoint path contains duplicate
|
|
393
|
-
* `{param}` names
|
|
398
|
+
* `{param}` names or the retired `{name?}` optional syntax, and sets
|
|
399
|
+
* `state.compiled = true` to guard re-entry.
|
|
394
400
|
*
|
|
395
401
|
* @param state - The mutable server state whose `table` should be compiled.
|
|
396
|
-
* @throws {Error} With `[moku-worker]` prefix when a path has duplicate param
|
|
402
|
+
* @throws {Error} With `[moku-worker]` prefix when a path has duplicate param
|
|
403
|
+
* names, or uses the old `{name?}` optional syntax (now `{name:?}`).
|
|
397
404
|
* @example
|
|
398
405
|
* ```typescript
|
|
399
406
|
* // Called inside serverPlugin.onInit:
|
|
@@ -407,6 +414,10 @@ const compileServerState = (state) => {
|
|
|
407
414
|
const seen = /* @__PURE__ */ new Set();
|
|
408
415
|
for (const segment of compiled.segments) {
|
|
409
416
|
if (!segment.param) continue;
|
|
417
|
+
if (segment.value.endsWith("?")) {
|
|
418
|
+
const name = segment.value.slice(0, -1);
|
|
419
|
+
throw new Error(`[moku-worker] endpoint path "${compiled.endpoint.path}" uses the old optional-param syntax "{${segment.value}}".\n Optional params now use the colon form (matching @moku-labs/web): write "{${name}:?}" instead of "{${name}?}".`);
|
|
420
|
+
}
|
|
410
421
|
if (seen.has(segment.value)) throw new Error(`[moku-worker] endpoint path "${compiled.endpoint.path}" has duplicate param "{${segment.value}}".\n Each {param} name in a path must be unique.`);
|
|
411
422
|
seen.add(segment.value);
|
|
412
423
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as WorkerEnv, r as WorkerEvents, t as WorkerConfig } from "./config-
|
|
2
|
-
import { PluginCtx, PluginInstance } from "@moku-labs/core";
|
|
1
|
+
import { i as WorkerPluginCtx, n as WorkerEnv, r as WorkerEvents, t as WorkerConfig } from "./config-Bj3GUJT_.cjs";
|
|
2
|
+
import { PluginCtx, PluginCtx as PluginCtx$1, PluginInstance } from "@moku-labs/core";
|
|
3
3
|
import { envPlugin, logPlugin } from "@moku-labs/common";
|
|
4
4
|
|
|
5
5
|
//#region \0rolldown/runtime.js
|
|
@@ -50,7 +50,7 @@ type BindingsApi = {
|
|
|
50
50
|
* api-factory context. State slot is Record<string, never> — bindings holds NO
|
|
51
51
|
* state (F4). Type-argument order is PluginCtx<Config, State, Events>.
|
|
52
52
|
*/
|
|
53
|
-
type Context = PluginCtx<Config$4, Record<string, never>, WorkerEvents>;
|
|
53
|
+
type Context = PluginCtx$1<Config$4, Record<string, never>, WorkerEvents>;
|
|
54
54
|
//#endregion
|
|
55
55
|
//#region src/plugins/bindings/index.d.ts
|
|
56
56
|
/**
|
|
@@ -64,19 +64,19 @@ type Context = PluginCtx<Config$4, Record<string, never>, WorkerEvents>;
|
|
|
64
64
|
*/
|
|
65
65
|
declare const bindingsPlugin: import("@moku-labs/core").PluginInstance<"bindings", Config$4, Record<string, never>, BindingsApi, {}> & Record<never, never>;
|
|
66
66
|
declare namespace types_d_exports$4 {
|
|
67
|
-
export { Api$3 as Api, CompiledEndpoint, Endpoint, EndpointHandler, MatchResult, Method, PathSegment, RequestContext, RequireFn, ServerConfig, ServerCtx, ServerEvents, ServerState };
|
|
67
|
+
export { Api$3 as Api, CompiledEndpoint, Endpoint, EndpointHandler, MatchResult, Method, PathParams, PathSegment, RequestContext, RequireFn, ServerConfig, ServerCtx, ServerEvents, ServerState };
|
|
68
68
|
}
|
|
69
69
|
/** HTTP method an endpoint matches; "ALL" matches any verb. */
|
|
70
70
|
type Method = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS" | "ALL";
|
|
71
|
-
/** One parsed path segment: a literal, a required `{name}`, or an optional `{name
|
|
71
|
+
/** One parsed path segment: a literal, a required `{name}`, or an optional `{name:?}`. */
|
|
72
72
|
type PathSegment = {
|
|
73
|
-
/** The literal text, or the param name when a param. */readonly value: string; /** Whether this segment is a `{name}` / `{name
|
|
74
|
-
readonly param: boolean; /** Whether the param is optional (`{name
|
|
73
|
+
/** The literal text, or the param name when a param. */readonly value: string; /** Whether this segment is a `{name}` / `{name:?}` parameter. */
|
|
74
|
+
readonly param: boolean; /** Whether the param is optional (`{name:?}`). */
|
|
75
75
|
readonly optional: boolean;
|
|
76
76
|
};
|
|
77
77
|
/** A declarative endpoint produced by the pure endpoint() builder. */
|
|
78
78
|
type Endpoint = {
|
|
79
|
-
/** Endpoint path, optionally with `{name}` / `{name
|
|
79
|
+
/** Endpoint path, optionally with `{name}` / `{name:?}` params. */readonly path: string; /** HTTP method or "ALL". */
|
|
80
80
|
readonly method: Method; /** The handler invoked on a match. */
|
|
81
81
|
readonly handler: EndpointHandler;
|
|
82
82
|
};
|
|
@@ -125,14 +125,55 @@ type ApiOf<P> = P extends {
|
|
|
125
125
|
} ? A : never;
|
|
126
126
|
/** Cross-plugin reach used inside handlers: require(plugin) returns that plugin's API. Mirrors ctx.require. */
|
|
127
127
|
type RequireFn = <P extends AnyPlugin>(plugin: P) => ApiOf<P>;
|
|
128
|
-
/**
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Prettify an intersection into a single flat object type for readable hovers.
|
|
130
|
+
* Homomorphic mapped type — preserves each property's `?` optionality modifier.
|
|
131
|
+
*/
|
|
132
|
+
type Prettify<T> = { [K in keyof T]: T[K] } & {};
|
|
133
|
+
/**
|
|
134
|
+
* Required param names in a path template, as a string union (`never` if none).
|
|
135
|
+
* Walks each `{...}` segment: collects a bare `{name}`, skips an optional `{name:?}`.
|
|
136
|
+
*/
|
|
137
|
+
type RequiredParamNames<Path extends string> = Path extends `${string}{${infer Rest}` ? Rest extends `${infer Body}}${infer Tail}` ? Body extends `${string}:?` ? RequiredParamNames<Tail> : Body | RequiredParamNames<Tail> : never : never;
|
|
138
|
+
/**
|
|
139
|
+
* Optional param names in a path template with the `:?` suffix stripped, as a
|
|
140
|
+
* string union (`never` if none). Collects `{name:?}`, skips a bare `{name}`.
|
|
141
|
+
*/
|
|
142
|
+
type OptionalParamNames<Path extends string> = Path extends `${string}{${infer Rest}` ? Rest extends `${infer Body}}${infer Tail}` ? Body extends `${infer Name}:?` ? Name | OptionalParamNames<Tail> : OptionalParamNames<Tail> : never : never;
|
|
143
|
+
/**
|
|
144
|
+
* Map a path template to its typed `params` object: a required `{name}` becomes
|
|
145
|
+
* `name: string`; an optional `{name:?}` becomes `name?: string`. A non-literal
|
|
146
|
+
* `string` path (e.g. one assembled at runtime) widens to the permissive
|
|
147
|
+
* `Record<string, string | undefined>`.
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```typescript
|
|
151
|
+
* type P = PathParams<"/boards/{id}/data/{lang:?}">;
|
|
152
|
+
* // { id: string; lang?: string }
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
type PathParams<Path extends string> = string extends Path ? Record<string, string | undefined> : Prettify<{ [K in RequiredParamNames<Path>]: string } & { [K in OptionalParamNames<Path>]?: string }>;
|
|
156
|
+
/**
|
|
157
|
+
* A request handler: receives the per-request context, returns a Response.
|
|
158
|
+
*
|
|
159
|
+
* @template Params - Path-params shape, inferred by the `endpoint()` builder
|
|
160
|
+
* from the path template ({@link PathParams}) — a required `{name}` is
|
|
161
|
+
* `string`, an optional `{name:?}` is `string | undefined`. Defaults to the
|
|
162
|
+
* permissive `Record<string, string | undefined>` for hand-written handler types.
|
|
163
|
+
*/
|
|
164
|
+
type EndpointHandler<Params = Record<string, string | undefined>> = (ctx: RequestContext<Params>) => Response | Promise<Response>;
|
|
165
|
+
/**
|
|
166
|
+
* Fresh per-request object threaded to each EndpointHandler.
|
|
167
|
+
*
|
|
168
|
+
* @template Params - Path-params shape for `params`, inferred from the path
|
|
169
|
+
* template by the `endpoint()` builder ({@link PathParams}). Defaults to the
|
|
170
|
+
* permissive `Record<string, string | undefined>`.
|
|
171
|
+
*/
|
|
172
|
+
type RequestContext<Params = Record<string, string | undefined>> = {
|
|
132
173
|
/** The incoming request. */readonly request: Request; /** Per-request Cloudflare bindings — threaded on the stack, NEVER stored in state. */
|
|
133
174
|
readonly env: WorkerEnv; /** waitUntil / passThroughOnException. */
|
|
134
|
-
readonly exec: ExecutionContext; /** Path params extracted from the matched endpoint. */
|
|
135
|
-
readonly params:
|
|
175
|
+
readonly exec: ExecutionContext; /** Path params extracted from the matched endpoint, typed from the path template. */
|
|
176
|
+
readonly params: Params; /** Parsed request URL. */
|
|
136
177
|
readonly url: URL; /** Cross-plugin reach for handlers (e.g. require(bindingsPlugin)). */
|
|
137
178
|
readonly require: RequireFn; /** Presence check for an optional plugin. */
|
|
138
179
|
readonly has: (name: string) => boolean;
|
|
@@ -145,7 +186,7 @@ type ServerEvents = {
|
|
|
145
186
|
};
|
|
146
187
|
};
|
|
147
188
|
/** Full server plugin context (own config + state + merged events + cross-plugin reach). */
|
|
148
|
-
type ServerCtx = PluginCtx<ServerConfig, ServerState, WorkerEvents & ServerEvents> & {
|
|
189
|
+
type ServerCtx = PluginCtx$1<ServerConfig, ServerState, WorkerEvents & ServerEvents> & {
|
|
149
190
|
/** Cross-plugin require threaded into each RequestContext. */require: RequireFn; /** Presence check for an optional plugin. */
|
|
150
191
|
has: (name: string) => boolean;
|
|
151
192
|
};
|
|
@@ -175,12 +216,14 @@ type Api$3 = {
|
|
|
175
216
|
/**
|
|
176
217
|
* Fluent builder whose verb methods each return a typed `Endpoint`.
|
|
177
218
|
*
|
|
219
|
+
* @template Path - The path template literal, used to infer each handler's
|
|
220
|
+
* typed `ctx.params` ({@link PathParams}).
|
|
178
221
|
* @example
|
|
179
222
|
* ```typescript
|
|
180
|
-
* const e = endpoint("/api/{id}").get(
|
|
223
|
+
* const e = endpoint("/api/{id}").get(({ params }) => Response.json({ id: params.id }));
|
|
181
224
|
* ```
|
|
182
225
|
*/
|
|
183
|
-
type EndpointBuilder = {
|
|
226
|
+
type EndpointBuilder<Path extends string> = {
|
|
184
227
|
/**
|
|
185
228
|
* Build a GET endpoint bound to this path.
|
|
186
229
|
*
|
|
@@ -191,7 +234,7 @@ type EndpointBuilder = {
|
|
|
191
234
|
* endpoint("/health").get(() => new Response("ok"));
|
|
192
235
|
* ```
|
|
193
236
|
*/
|
|
194
|
-
get(handler: EndpointHandler): Endpoint;
|
|
237
|
+
get(handler: EndpointHandler<PathParams<Path>>): Endpoint;
|
|
195
238
|
/**
|
|
196
239
|
* Build a POST endpoint bound to this path.
|
|
197
240
|
*
|
|
@@ -202,7 +245,7 @@ type EndpointBuilder = {
|
|
|
202
245
|
* endpoint("/users").post(({ request }) => Response.json({ created: true }, { status: 201 }));
|
|
203
246
|
* ```
|
|
204
247
|
*/
|
|
205
|
-
post(handler: EndpointHandler): Endpoint;
|
|
248
|
+
post(handler: EndpointHandler<PathParams<Path>>): Endpoint;
|
|
206
249
|
/**
|
|
207
250
|
* Build a PUT endpoint bound to this path.
|
|
208
251
|
*
|
|
@@ -213,7 +256,7 @@ type EndpointBuilder = {
|
|
|
213
256
|
* endpoint("/users/{id}").put(({ params }) => Response.json({ updated: params.id }));
|
|
214
257
|
* ```
|
|
215
258
|
*/
|
|
216
|
-
put(handler: EndpointHandler): Endpoint;
|
|
259
|
+
put(handler: EndpointHandler<PathParams<Path>>): Endpoint;
|
|
217
260
|
/**
|
|
218
261
|
* Build a PATCH endpoint bound to this path.
|
|
219
262
|
*
|
|
@@ -224,7 +267,7 @@ type EndpointBuilder = {
|
|
|
224
267
|
* endpoint("/users/{id}").patch(({ params }) => Response.json({ patched: params.id }));
|
|
225
268
|
* ```
|
|
226
269
|
*/
|
|
227
|
-
patch(handler: EndpointHandler): Endpoint;
|
|
270
|
+
patch(handler: EndpointHandler<PathParams<Path>>): Endpoint;
|
|
228
271
|
/**
|
|
229
272
|
* Build a DELETE endpoint bound to this path.
|
|
230
273
|
*
|
|
@@ -235,7 +278,7 @@ type EndpointBuilder = {
|
|
|
235
278
|
* endpoint("/users/{id}").delete(() => new Response(null, { status: 204 }));
|
|
236
279
|
* ```
|
|
237
280
|
*/
|
|
238
|
-
delete(handler: EndpointHandler): Endpoint;
|
|
281
|
+
delete(handler: EndpointHandler<PathParams<Path>>): Endpoint;
|
|
239
282
|
/**
|
|
240
283
|
* Build a HEAD endpoint bound to this path.
|
|
241
284
|
*
|
|
@@ -246,7 +289,7 @@ type EndpointBuilder = {
|
|
|
246
289
|
* endpoint("/health").head(() => new Response(null, { status: 200 }));
|
|
247
290
|
* ```
|
|
248
291
|
*/
|
|
249
|
-
head(handler: EndpointHandler): Endpoint;
|
|
292
|
+
head(handler: EndpointHandler<PathParams<Path>>): Endpoint;
|
|
250
293
|
/**
|
|
251
294
|
* Build an OPTIONS endpoint bound to this path.
|
|
252
295
|
*
|
|
@@ -257,7 +300,7 @@ type EndpointBuilder = {
|
|
|
257
300
|
* endpoint("/api").options(() => new Response(null, { headers: { Allow: "GET, POST" } }));
|
|
258
301
|
* ```
|
|
259
302
|
*/
|
|
260
|
-
options(handler: EndpointHandler): Endpoint;
|
|
303
|
+
options(handler: EndpointHandler<PathParams<Path>>): Endpoint;
|
|
261
304
|
/**
|
|
262
305
|
* Build an ALL-method endpoint bound to this path (`method: "ALL"` — matches any verb).
|
|
263
306
|
*
|
|
@@ -268,25 +311,29 @@ type EndpointBuilder = {
|
|
|
268
311
|
* endpoint("0 * * * *").all(async () => new Response("cron done"));
|
|
269
312
|
* ```
|
|
270
313
|
*/
|
|
271
|
-
all(handler: EndpointHandler): Endpoint;
|
|
314
|
+
all(handler: EndpointHandler<PathParams<Path>>): Endpoint;
|
|
272
315
|
};
|
|
273
316
|
/**
|
|
274
|
-
* Build a typed `Endpoint`. `{name}` → required param; `{name
|
|
317
|
+
* Build a typed `Endpoint`. `{name}` → required param (`string`); `{name:?}` →
|
|
318
|
+
* optional param (`string | undefined`). The path template flows into each
|
|
319
|
+
* handler's `ctx.params` ({@link PathParams}), so a required `{id}` is typed
|
|
320
|
+
* `string` — no `?? ""` fallback needed.
|
|
275
321
|
*
|
|
276
322
|
* PURE factory (spec/03 §1): no ctx, no lifecycle, no side effects; safe to run
|
|
277
323
|
* before `createApp`. Each verb method (`get`, `post`, …, `all`) returns the
|
|
278
324
|
* truthful Endpoint value — `method: "ALL"` is never used as a `"get"` sentinel.
|
|
279
325
|
*
|
|
280
|
-
* @
|
|
326
|
+
* @template Path - The path template literal, inferred from `path`.
|
|
327
|
+
* @param path - Endpoint path, optionally with `{name}` / `{name:?}` params.
|
|
281
328
|
* @returns A builder whose verb methods each return a typed `Endpoint`.
|
|
282
329
|
* @example
|
|
283
330
|
* ```typescript
|
|
284
|
-
* endpoint("/api/data/{lang
|
|
331
|
+
* endpoint("/api/data/{lang:?}").get(({ params }) =>
|
|
285
332
|
* Response.json({ lang: params.lang ?? "en" })
|
|
286
333
|
* );
|
|
287
334
|
* ```
|
|
288
335
|
*/
|
|
289
|
-
declare const endpoint: (path:
|
|
336
|
+
declare const endpoint: <Path extends string>(path: Path) => EndpointBuilder<Path>;
|
|
290
337
|
declare namespace types_d_exports$1 {
|
|
291
338
|
export { Api$2 as Api, Config$3 as Config, D1Ctx, DeployManifest$2 as DeployManifest };
|
|
292
339
|
}
|
|
@@ -370,7 +417,7 @@ type Api$2 = {
|
|
|
370
417
|
* Internal context type — own config first, no state, no d1-local events.
|
|
371
418
|
* Intersected with a narrow `require` typed to the one dependency d1 resolves.
|
|
372
419
|
*/
|
|
373
|
-
type D1Ctx = PluginCtx<Config$3, Record<string, never>, WorkerEvents> & {
|
|
420
|
+
type D1Ctx = PluginCtx$1<Config$3, Record<string, never>, WorkerEvents> & {
|
|
374
421
|
/**
|
|
375
422
|
* Resolve a dependency plugin's api. d1 only ever resolves `bindingsPlugin`.
|
|
376
423
|
*
|
|
@@ -446,7 +493,7 @@ type Api$1 = {
|
|
|
446
493
|
* Internal context type — own config first, no state, no DO events.
|
|
447
494
|
* Intersected with a narrow `require` typed to the one dependency durableObjects resolves.
|
|
448
495
|
*/
|
|
449
|
-
type Ctx$1 = PluginCtx<Config$2, Record<string, never>, WorkerEvents> & {
|
|
496
|
+
type Ctx$1 = PluginCtx$1<Config$2, Record<string, never>, WorkerEvents> & {
|
|
450
497
|
/**
|
|
451
498
|
* Resolve a dependency plugin's api. durableObjects only ever resolves `bindingsPlugin`.
|
|
452
499
|
*
|
|
@@ -685,7 +732,7 @@ type Api = {
|
|
|
685
732
|
* (core's "advanced composition" note), typed to the one dependency queues resolves —
|
|
686
733
|
* `require(bindingsPlugin)` → `BindingsApi`. Core does not export `RequireFunction`.
|
|
687
734
|
*/
|
|
688
|
-
type Ctx = PluginCtx<Config$1, Record<string, never>, WorkerEvents & QueueEvents> & {
|
|
735
|
+
type Ctx = PluginCtx$1<Config$1, Record<string, never>, WorkerEvents & QueueEvents> & {
|
|
689
736
|
/**
|
|
690
737
|
* Resolve a dependency plugin's api. queues only ever resolves `bindingsPlugin`.
|
|
691
738
|
*
|
|
@@ -735,7 +782,7 @@ declare const serverPlugin: import("@moku-labs/core").PluginInstance<"server", S
|
|
|
735
782
|
method: string;
|
|
736
783
|
};
|
|
737
784
|
}> & {
|
|
738
|
-
endpoint: (path:
|
|
785
|
+
endpoint: <Path extends string>(path: Path) => EndpointBuilder<Path>;
|
|
739
786
|
};
|
|
740
787
|
//#endregion
|
|
741
788
|
//#region src/plugins/storage/providers/types.d.ts
|
|
@@ -831,7 +878,7 @@ type StorageApi = {
|
|
|
831
878
|
* resolves — mirrors the kv/api.ts pattern (PluginCtx has no `require` by
|
|
832
879
|
* default; core does not export a generic RequireFunction).
|
|
833
880
|
*/
|
|
834
|
-
type StorageCtx = PluginCtx<StorageConfig, Record<string, never>, WorkerEvents> & {
|
|
881
|
+
type StorageCtx = PluginCtx$1<StorageConfig, Record<string, never>, WorkerEvents> & {
|
|
835
882
|
/**
|
|
836
883
|
* Resolve a dependency plugin's api. storage only ever resolves bindingsPlugin.
|
|
837
884
|
*
|
|
@@ -953,7 +1000,7 @@ declare const createApp: <const ExtraPlugins extends readonly import("@moku-labs
|
|
|
953
1000
|
method: string;
|
|
954
1001
|
};
|
|
955
1002
|
}> & {
|
|
956
|
-
endpoint: (path:
|
|
1003
|
+
endpoint: <Path extends string>(path: Path) => EndpointBuilder<Path>;
|
|
957
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", {
|
|
958
1005
|
stage: "production" | "development" | "test";
|
|
959
1006
|
}, Record<string, never>, {
|
|
@@ -966,7 +1013,7 @@ declare const createApp: <const ExtraPlugins extends readonly import("@moku-labs
|
|
|
966
1013
|
method: string;
|
|
967
1014
|
};
|
|
968
1015
|
}> & {
|
|
969
|
-
endpoint: (path:
|
|
1016
|
+
endpoint: <Path extends string>(path: Path) => EndpointBuilder<Path>;
|
|
970
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", {
|
|
971
1018
|
stage: "production" | "development" | "test";
|
|
972
1019
|
}, Record<string, never>, {
|
|
@@ -981,4 +1028,4 @@ declare const createApp: <const ExtraPlugins extends readonly import("@moku-labs
|
|
|
981
1028
|
current: () => "production" | "development" | "test";
|
|
982
1029
|
}>]>>;
|
|
983
1030
|
//#endregion
|
|
984
|
-
export { type types_d_exports as Bindings, type types_d_exports$1 as D1, type types_d_exports$2 as DurableObjects, 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, bindingsPlugin, createApp, createPlugin, d1Plugin, defineDurableObject, durableObjectsPlugin, endpoint, envPlugin, kvPlugin, logPlugin, queuesPlugin, serverPlugin, stagePlugin, storagePlugin };
|
|
1031
|
+
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
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { n as WorkerEnv, r as WorkerEvents, t as WorkerConfig } from "./config-
|
|
1
|
+
import { i as WorkerPluginCtx, n as WorkerEnv, r as WorkerEvents, t as WorkerConfig } from "./config-Bj3GUJT_.mjs";
|
|
2
2
|
import { envPlugin, logPlugin } from "@moku-labs/common";
|
|
3
|
-
import { PluginCtx, PluginInstance } from "@moku-labs/core";
|
|
3
|
+
import { PluginCtx, PluginCtx as PluginCtx$1, PluginInstance } from "@moku-labs/core";
|
|
4
4
|
|
|
5
5
|
//#region \0rolldown/runtime.js
|
|
6
6
|
declare namespace types_d_exports {
|
|
@@ -50,7 +50,7 @@ type BindingsApi = {
|
|
|
50
50
|
* api-factory context. State slot is Record<string, never> — bindings holds NO
|
|
51
51
|
* state (F4). Type-argument order is PluginCtx<Config, State, Events>.
|
|
52
52
|
*/
|
|
53
|
-
type Context = PluginCtx<Config$4, Record<string, never>, WorkerEvents>;
|
|
53
|
+
type Context = PluginCtx$1<Config$4, Record<string, never>, WorkerEvents>;
|
|
54
54
|
//#endregion
|
|
55
55
|
//#region src/plugins/bindings/index.d.ts
|
|
56
56
|
/**
|
|
@@ -64,19 +64,19 @@ type Context = PluginCtx<Config$4, Record<string, never>, WorkerEvents>;
|
|
|
64
64
|
*/
|
|
65
65
|
declare const bindingsPlugin: import("@moku-labs/core").PluginInstance<"bindings", Config$4, Record<string, never>, BindingsApi, {}> & Record<never, never>;
|
|
66
66
|
declare namespace types_d_exports$4 {
|
|
67
|
-
export { Api$3 as Api, CompiledEndpoint, Endpoint, EndpointHandler, MatchResult, Method, PathSegment, RequestContext, RequireFn, ServerConfig, ServerCtx, ServerEvents, ServerState };
|
|
67
|
+
export { Api$3 as Api, CompiledEndpoint, Endpoint, EndpointHandler, MatchResult, Method, PathParams, PathSegment, RequestContext, RequireFn, ServerConfig, ServerCtx, ServerEvents, ServerState };
|
|
68
68
|
}
|
|
69
69
|
/** HTTP method an endpoint matches; "ALL" matches any verb. */
|
|
70
70
|
type Method = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS" | "ALL";
|
|
71
|
-
/** One parsed path segment: a literal, a required `{name}`, or an optional `{name
|
|
71
|
+
/** One parsed path segment: a literal, a required `{name}`, or an optional `{name:?}`. */
|
|
72
72
|
type PathSegment = {
|
|
73
|
-
/** The literal text, or the param name when a param. */readonly value: string; /** Whether this segment is a `{name}` / `{name
|
|
74
|
-
readonly param: boolean; /** Whether the param is optional (`{name
|
|
73
|
+
/** The literal text, or the param name when a param. */readonly value: string; /** Whether this segment is a `{name}` / `{name:?}` parameter. */
|
|
74
|
+
readonly param: boolean; /** Whether the param is optional (`{name:?}`). */
|
|
75
75
|
readonly optional: boolean;
|
|
76
76
|
};
|
|
77
77
|
/** A declarative endpoint produced by the pure endpoint() builder. */
|
|
78
78
|
type Endpoint = {
|
|
79
|
-
/** Endpoint path, optionally with `{name}` / `{name
|
|
79
|
+
/** Endpoint path, optionally with `{name}` / `{name:?}` params. */readonly path: string; /** HTTP method or "ALL". */
|
|
80
80
|
readonly method: Method; /** The handler invoked on a match. */
|
|
81
81
|
readonly handler: EndpointHandler;
|
|
82
82
|
};
|
|
@@ -125,14 +125,55 @@ type ApiOf<P> = P extends {
|
|
|
125
125
|
} ? A : never;
|
|
126
126
|
/** Cross-plugin reach used inside handlers: require(plugin) returns that plugin's API. Mirrors ctx.require. */
|
|
127
127
|
type RequireFn = <P extends AnyPlugin>(plugin: P) => ApiOf<P>;
|
|
128
|
-
/**
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Prettify an intersection into a single flat object type for readable hovers.
|
|
130
|
+
* Homomorphic mapped type — preserves each property's `?` optionality modifier.
|
|
131
|
+
*/
|
|
132
|
+
type Prettify<T> = { [K in keyof T]: T[K] } & {};
|
|
133
|
+
/**
|
|
134
|
+
* Required param names in a path template, as a string union (`never` if none).
|
|
135
|
+
* Walks each `{...}` segment: collects a bare `{name}`, skips an optional `{name:?}`.
|
|
136
|
+
*/
|
|
137
|
+
type RequiredParamNames<Path extends string> = Path extends `${string}{${infer Rest}` ? Rest extends `${infer Body}}${infer Tail}` ? Body extends `${string}:?` ? RequiredParamNames<Tail> : Body | RequiredParamNames<Tail> : never : never;
|
|
138
|
+
/**
|
|
139
|
+
* Optional param names in a path template with the `:?` suffix stripped, as a
|
|
140
|
+
* string union (`never` if none). Collects `{name:?}`, skips a bare `{name}`.
|
|
141
|
+
*/
|
|
142
|
+
type OptionalParamNames<Path extends string> = Path extends `${string}{${infer Rest}` ? Rest extends `${infer Body}}${infer Tail}` ? Body extends `${infer Name}:?` ? Name | OptionalParamNames<Tail> : OptionalParamNames<Tail> : never : never;
|
|
143
|
+
/**
|
|
144
|
+
* Map a path template to its typed `params` object: a required `{name}` becomes
|
|
145
|
+
* `name: string`; an optional `{name:?}` becomes `name?: string`. A non-literal
|
|
146
|
+
* `string` path (e.g. one assembled at runtime) widens to the permissive
|
|
147
|
+
* `Record<string, string | undefined>`.
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```typescript
|
|
151
|
+
* type P = PathParams<"/boards/{id}/data/{lang:?}">;
|
|
152
|
+
* // { id: string; lang?: string }
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
type PathParams<Path extends string> = string extends Path ? Record<string, string | undefined> : Prettify<{ [K in RequiredParamNames<Path>]: string } & { [K in OptionalParamNames<Path>]?: string }>;
|
|
156
|
+
/**
|
|
157
|
+
* A request handler: receives the per-request context, returns a Response.
|
|
158
|
+
*
|
|
159
|
+
* @template Params - Path-params shape, inferred by the `endpoint()` builder
|
|
160
|
+
* from the path template ({@link PathParams}) — a required `{name}` is
|
|
161
|
+
* `string`, an optional `{name:?}` is `string | undefined`. Defaults to the
|
|
162
|
+
* permissive `Record<string, string | undefined>` for hand-written handler types.
|
|
163
|
+
*/
|
|
164
|
+
type EndpointHandler<Params = Record<string, string | undefined>> = (ctx: RequestContext<Params>) => Response | Promise<Response>;
|
|
165
|
+
/**
|
|
166
|
+
* Fresh per-request object threaded to each EndpointHandler.
|
|
167
|
+
*
|
|
168
|
+
* @template Params - Path-params shape for `params`, inferred from the path
|
|
169
|
+
* template by the `endpoint()` builder ({@link PathParams}). Defaults to the
|
|
170
|
+
* permissive `Record<string, string | undefined>`.
|
|
171
|
+
*/
|
|
172
|
+
type RequestContext<Params = Record<string, string | undefined>> = {
|
|
132
173
|
/** The incoming request. */readonly request: Request; /** Per-request Cloudflare bindings — threaded on the stack, NEVER stored in state. */
|
|
133
174
|
readonly env: WorkerEnv; /** waitUntil / passThroughOnException. */
|
|
134
|
-
readonly exec: ExecutionContext; /** Path params extracted from the matched endpoint. */
|
|
135
|
-
readonly params:
|
|
175
|
+
readonly exec: ExecutionContext; /** Path params extracted from the matched endpoint, typed from the path template. */
|
|
176
|
+
readonly params: Params; /** Parsed request URL. */
|
|
136
177
|
readonly url: URL; /** Cross-plugin reach for handlers (e.g. require(bindingsPlugin)). */
|
|
137
178
|
readonly require: RequireFn; /** Presence check for an optional plugin. */
|
|
138
179
|
readonly has: (name: string) => boolean;
|
|
@@ -145,7 +186,7 @@ type ServerEvents = {
|
|
|
145
186
|
};
|
|
146
187
|
};
|
|
147
188
|
/** Full server plugin context (own config + state + merged events + cross-plugin reach). */
|
|
148
|
-
type ServerCtx = PluginCtx<ServerConfig, ServerState, WorkerEvents & ServerEvents> & {
|
|
189
|
+
type ServerCtx = PluginCtx$1<ServerConfig, ServerState, WorkerEvents & ServerEvents> & {
|
|
149
190
|
/** Cross-plugin require threaded into each RequestContext. */require: RequireFn; /** Presence check for an optional plugin. */
|
|
150
191
|
has: (name: string) => boolean;
|
|
151
192
|
};
|
|
@@ -175,12 +216,14 @@ type Api$3 = {
|
|
|
175
216
|
/**
|
|
176
217
|
* Fluent builder whose verb methods each return a typed `Endpoint`.
|
|
177
218
|
*
|
|
219
|
+
* @template Path - The path template literal, used to infer each handler's
|
|
220
|
+
* typed `ctx.params` ({@link PathParams}).
|
|
178
221
|
* @example
|
|
179
222
|
* ```typescript
|
|
180
|
-
* const e = endpoint("/api/{id}").get(
|
|
223
|
+
* const e = endpoint("/api/{id}").get(({ params }) => Response.json({ id: params.id }));
|
|
181
224
|
* ```
|
|
182
225
|
*/
|
|
183
|
-
type EndpointBuilder = {
|
|
226
|
+
type EndpointBuilder<Path extends string> = {
|
|
184
227
|
/**
|
|
185
228
|
* Build a GET endpoint bound to this path.
|
|
186
229
|
*
|
|
@@ -191,7 +234,7 @@ type EndpointBuilder = {
|
|
|
191
234
|
* endpoint("/health").get(() => new Response("ok"));
|
|
192
235
|
* ```
|
|
193
236
|
*/
|
|
194
|
-
get(handler: EndpointHandler): Endpoint;
|
|
237
|
+
get(handler: EndpointHandler<PathParams<Path>>): Endpoint;
|
|
195
238
|
/**
|
|
196
239
|
* Build a POST endpoint bound to this path.
|
|
197
240
|
*
|
|
@@ -202,7 +245,7 @@ type EndpointBuilder = {
|
|
|
202
245
|
* endpoint("/users").post(({ request }) => Response.json({ created: true }, { status: 201 }));
|
|
203
246
|
* ```
|
|
204
247
|
*/
|
|
205
|
-
post(handler: EndpointHandler): Endpoint;
|
|
248
|
+
post(handler: EndpointHandler<PathParams<Path>>): Endpoint;
|
|
206
249
|
/**
|
|
207
250
|
* Build a PUT endpoint bound to this path.
|
|
208
251
|
*
|
|
@@ -213,7 +256,7 @@ type EndpointBuilder = {
|
|
|
213
256
|
* endpoint("/users/{id}").put(({ params }) => Response.json({ updated: params.id }));
|
|
214
257
|
* ```
|
|
215
258
|
*/
|
|
216
|
-
put(handler: EndpointHandler): Endpoint;
|
|
259
|
+
put(handler: EndpointHandler<PathParams<Path>>): Endpoint;
|
|
217
260
|
/**
|
|
218
261
|
* Build a PATCH endpoint bound to this path.
|
|
219
262
|
*
|
|
@@ -224,7 +267,7 @@ type EndpointBuilder = {
|
|
|
224
267
|
* endpoint("/users/{id}").patch(({ params }) => Response.json({ patched: params.id }));
|
|
225
268
|
* ```
|
|
226
269
|
*/
|
|
227
|
-
patch(handler: EndpointHandler): Endpoint;
|
|
270
|
+
patch(handler: EndpointHandler<PathParams<Path>>): Endpoint;
|
|
228
271
|
/**
|
|
229
272
|
* Build a DELETE endpoint bound to this path.
|
|
230
273
|
*
|
|
@@ -235,7 +278,7 @@ type EndpointBuilder = {
|
|
|
235
278
|
* endpoint("/users/{id}").delete(() => new Response(null, { status: 204 }));
|
|
236
279
|
* ```
|
|
237
280
|
*/
|
|
238
|
-
delete(handler: EndpointHandler): Endpoint;
|
|
281
|
+
delete(handler: EndpointHandler<PathParams<Path>>): Endpoint;
|
|
239
282
|
/**
|
|
240
283
|
* Build a HEAD endpoint bound to this path.
|
|
241
284
|
*
|
|
@@ -246,7 +289,7 @@ type EndpointBuilder = {
|
|
|
246
289
|
* endpoint("/health").head(() => new Response(null, { status: 200 }));
|
|
247
290
|
* ```
|
|
248
291
|
*/
|
|
249
|
-
head(handler: EndpointHandler): Endpoint;
|
|
292
|
+
head(handler: EndpointHandler<PathParams<Path>>): Endpoint;
|
|
250
293
|
/**
|
|
251
294
|
* Build an OPTIONS endpoint bound to this path.
|
|
252
295
|
*
|
|
@@ -257,7 +300,7 @@ type EndpointBuilder = {
|
|
|
257
300
|
* endpoint("/api").options(() => new Response(null, { headers: { Allow: "GET, POST" } }));
|
|
258
301
|
* ```
|
|
259
302
|
*/
|
|
260
|
-
options(handler: EndpointHandler): Endpoint;
|
|
303
|
+
options(handler: EndpointHandler<PathParams<Path>>): Endpoint;
|
|
261
304
|
/**
|
|
262
305
|
* Build an ALL-method endpoint bound to this path (`method: "ALL"` — matches any verb).
|
|
263
306
|
*
|
|
@@ -268,25 +311,29 @@ type EndpointBuilder = {
|
|
|
268
311
|
* endpoint("0 * * * *").all(async () => new Response("cron done"));
|
|
269
312
|
* ```
|
|
270
313
|
*/
|
|
271
|
-
all(handler: EndpointHandler): Endpoint;
|
|
314
|
+
all(handler: EndpointHandler<PathParams<Path>>): Endpoint;
|
|
272
315
|
};
|
|
273
316
|
/**
|
|
274
|
-
* Build a typed `Endpoint`. `{name}` → required param; `{name
|
|
317
|
+
* Build a typed `Endpoint`. `{name}` → required param (`string`); `{name:?}` →
|
|
318
|
+
* optional param (`string | undefined`). The path template flows into each
|
|
319
|
+
* handler's `ctx.params` ({@link PathParams}), so a required `{id}` is typed
|
|
320
|
+
* `string` — no `?? ""` fallback needed.
|
|
275
321
|
*
|
|
276
322
|
* PURE factory (spec/03 §1): no ctx, no lifecycle, no side effects; safe to run
|
|
277
323
|
* before `createApp`. Each verb method (`get`, `post`, …, `all`) returns the
|
|
278
324
|
* truthful Endpoint value — `method: "ALL"` is never used as a `"get"` sentinel.
|
|
279
325
|
*
|
|
280
|
-
* @
|
|
326
|
+
* @template Path - The path template literal, inferred from `path`.
|
|
327
|
+
* @param path - Endpoint path, optionally with `{name}` / `{name:?}` params.
|
|
281
328
|
* @returns A builder whose verb methods each return a typed `Endpoint`.
|
|
282
329
|
* @example
|
|
283
330
|
* ```typescript
|
|
284
|
-
* endpoint("/api/data/{lang
|
|
331
|
+
* endpoint("/api/data/{lang:?}").get(({ params }) =>
|
|
285
332
|
* Response.json({ lang: params.lang ?? "en" })
|
|
286
333
|
* );
|
|
287
334
|
* ```
|
|
288
335
|
*/
|
|
289
|
-
declare const endpoint: (path:
|
|
336
|
+
declare const endpoint: <Path extends string>(path: Path) => EndpointBuilder<Path>;
|
|
290
337
|
declare namespace types_d_exports$1 {
|
|
291
338
|
export { Api$2 as Api, Config$3 as Config, D1Ctx, DeployManifest$2 as DeployManifest };
|
|
292
339
|
}
|
|
@@ -370,7 +417,7 @@ type Api$2 = {
|
|
|
370
417
|
* Internal context type — own config first, no state, no d1-local events.
|
|
371
418
|
* Intersected with a narrow `require` typed to the one dependency d1 resolves.
|
|
372
419
|
*/
|
|
373
|
-
type D1Ctx = PluginCtx<Config$3, Record<string, never>, WorkerEvents> & {
|
|
420
|
+
type D1Ctx = PluginCtx$1<Config$3, Record<string, never>, WorkerEvents> & {
|
|
374
421
|
/**
|
|
375
422
|
* Resolve a dependency plugin's api. d1 only ever resolves `bindingsPlugin`.
|
|
376
423
|
*
|
|
@@ -446,7 +493,7 @@ type Api$1 = {
|
|
|
446
493
|
* Internal context type — own config first, no state, no DO events.
|
|
447
494
|
* Intersected with a narrow `require` typed to the one dependency durableObjects resolves.
|
|
448
495
|
*/
|
|
449
|
-
type Ctx$1 = PluginCtx<Config$2, Record<string, never>, WorkerEvents> & {
|
|
496
|
+
type Ctx$1 = PluginCtx$1<Config$2, Record<string, never>, WorkerEvents> & {
|
|
450
497
|
/**
|
|
451
498
|
* Resolve a dependency plugin's api. durableObjects only ever resolves `bindingsPlugin`.
|
|
452
499
|
*
|
|
@@ -685,7 +732,7 @@ type Api = {
|
|
|
685
732
|
* (core's "advanced composition" note), typed to the one dependency queues resolves —
|
|
686
733
|
* `require(bindingsPlugin)` → `BindingsApi`. Core does not export `RequireFunction`.
|
|
687
734
|
*/
|
|
688
|
-
type Ctx = PluginCtx<Config$1, Record<string, never>, WorkerEvents & QueueEvents> & {
|
|
735
|
+
type Ctx = PluginCtx$1<Config$1, Record<string, never>, WorkerEvents & QueueEvents> & {
|
|
689
736
|
/**
|
|
690
737
|
* Resolve a dependency plugin's api. queues only ever resolves `bindingsPlugin`.
|
|
691
738
|
*
|
|
@@ -735,7 +782,7 @@ declare const serverPlugin: import("@moku-labs/core").PluginInstance<"server", S
|
|
|
735
782
|
method: string;
|
|
736
783
|
};
|
|
737
784
|
}> & {
|
|
738
|
-
endpoint: (path:
|
|
785
|
+
endpoint: <Path extends string>(path: Path) => EndpointBuilder<Path>;
|
|
739
786
|
};
|
|
740
787
|
//#endregion
|
|
741
788
|
//#region src/plugins/storage/providers/types.d.ts
|
|
@@ -831,7 +878,7 @@ type StorageApi = {
|
|
|
831
878
|
* resolves — mirrors the kv/api.ts pattern (PluginCtx has no `require` by
|
|
832
879
|
* default; core does not export a generic RequireFunction).
|
|
833
880
|
*/
|
|
834
|
-
type StorageCtx = PluginCtx<StorageConfig, Record<string, never>, WorkerEvents> & {
|
|
881
|
+
type StorageCtx = PluginCtx$1<StorageConfig, Record<string, never>, WorkerEvents> & {
|
|
835
882
|
/**
|
|
836
883
|
* Resolve a dependency plugin's api. storage only ever resolves bindingsPlugin.
|
|
837
884
|
*
|
|
@@ -953,7 +1000,7 @@ declare const createApp: <const ExtraPlugins extends readonly import("@moku-labs
|
|
|
953
1000
|
method: string;
|
|
954
1001
|
};
|
|
955
1002
|
}> & {
|
|
956
|
-
endpoint: (path:
|
|
1003
|
+
endpoint: <Path extends string>(path: Path) => EndpointBuilder<Path>;
|
|
957
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", {
|
|
958
1005
|
stage: "production" | "development" | "test";
|
|
959
1006
|
}, Record<string, never>, {
|
|
@@ -966,7 +1013,7 @@ declare const createApp: <const ExtraPlugins extends readonly import("@moku-labs
|
|
|
966
1013
|
method: string;
|
|
967
1014
|
};
|
|
968
1015
|
}> & {
|
|
969
|
-
endpoint: (path:
|
|
1016
|
+
endpoint: <Path extends string>(path: Path) => EndpointBuilder<Path>;
|
|
970
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", {
|
|
971
1018
|
stage: "production" | "development" | "test";
|
|
972
1019
|
}, Record<string, never>, {
|
|
@@ -981,4 +1028,4 @@ declare const createApp: <const ExtraPlugins extends readonly import("@moku-labs
|
|
|
981
1028
|
current: () => "production" | "development" | "test";
|
|
982
1029
|
}>]>>;
|
|
983
1030
|
//#endregion
|
|
984
|
-
export { type types_d_exports as Bindings, type types_d_exports$1 as D1, type types_d_exports$2 as DurableObjects, 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, bindingsPlugin, createApp, createPlugin, d1Plugin, defineDurableObject, durableObjectsPlugin, endpoint, envPlugin, kvPlugin, logPlugin, queuesPlugin, serverPlugin, stagePlugin, storagePlugin };
|
|
1031
|
+
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
|
@@ -120,17 +120,21 @@ const makeEndpoint = (path, method, handler) => ({
|
|
|
120
120
|
handler
|
|
121
121
|
});
|
|
122
122
|
/**
|
|
123
|
-
* Build a typed `Endpoint`. `{name}` → required param; `{name
|
|
123
|
+
* Build a typed `Endpoint`. `{name}` → required param (`string`); `{name:?}` →
|
|
124
|
+
* optional param (`string | undefined`). The path template flows into each
|
|
125
|
+
* handler's `ctx.params` ({@link PathParams}), so a required `{id}` is typed
|
|
126
|
+
* `string` — no `?? ""` fallback needed.
|
|
124
127
|
*
|
|
125
128
|
* PURE factory (spec/03 §1): no ctx, no lifecycle, no side effects; safe to run
|
|
126
129
|
* before `createApp`. Each verb method (`get`, `post`, …, `all`) returns the
|
|
127
130
|
* truthful Endpoint value — `method: "ALL"` is never used as a `"get"` sentinel.
|
|
128
131
|
*
|
|
129
|
-
* @
|
|
132
|
+
* @template Path - The path template literal, inferred from `path`.
|
|
133
|
+
* @param path - Endpoint path, optionally with `{name}` / `{name:?}` params.
|
|
130
134
|
* @returns A builder whose verb methods each return a typed `Endpoint`.
|
|
131
135
|
* @example
|
|
132
136
|
* ```typescript
|
|
133
|
-
* endpoint("/api/data/{lang
|
|
137
|
+
* endpoint("/api/data/{lang:?}").get(({ params }) =>
|
|
134
138
|
* Response.json({ lang: params.lang ?? "en" })
|
|
135
139
|
* );
|
|
136
140
|
* ```
|
|
@@ -231,27 +235,28 @@ const endpoint = (path) => ({
|
|
|
231
235
|
const LITERAL_WEIGHT = 2;
|
|
232
236
|
/** Specificity weight for a required param segment `{name}`. */
|
|
233
237
|
const REQUIRED_PARAM_WEIGHT = 1;
|
|
234
|
-
/** Specificity weight for an optional param segment `{name
|
|
238
|
+
/** Specificity weight for an optional param segment `{name:?}`. */
|
|
235
239
|
const OPTIONAL_PARAM_WEIGHT = 0;
|
|
236
240
|
/**
|
|
237
241
|
* Parse one path segment string into a typed `PathSegment`.
|
|
238
242
|
*
|
|
239
|
-
* `{name}` → required param; `{name
|
|
243
|
+
* `{name}` → required param; `{name:?}` → optional param; anything else → literal.
|
|
244
|
+
* The `:?` optional suffix matches the `@moku-labs/web` router pattern.
|
|
240
245
|
*
|
|
241
246
|
* @param raw - A single path segment token (no leading slash).
|
|
242
247
|
* @returns The parsed `PathSegment`.
|
|
243
248
|
* @example
|
|
244
249
|
* ```typescript
|
|
245
|
-
* parseSegment("{id}")
|
|
246
|
-
* parseSegment("{id
|
|
247
|
-
* parseSegment("api")
|
|
250
|
+
* parseSegment("{id}") // → { value: "id", param: true, optional: false }
|
|
251
|
+
* parseSegment("{id:?}") // → { value: "id", param: true, optional: true }
|
|
252
|
+
* parseSegment("api") // → { value: "api", param: false, optional: false }
|
|
248
253
|
* ```
|
|
249
254
|
*/
|
|
250
255
|
const parseSegment = (raw) => {
|
|
251
256
|
if (raw.startsWith("{") && raw.endsWith("}")) {
|
|
252
257
|
const inner = raw.slice(1, -1);
|
|
253
|
-
if (inner.endsWith("
|
|
254
|
-
value: inner.slice(0, -
|
|
258
|
+
if (inner.endsWith(":?")) return {
|
|
259
|
+
value: inner.slice(0, -2),
|
|
255
260
|
param: true,
|
|
256
261
|
optional: true
|
|
257
262
|
};
|
|
@@ -346,7 +351,7 @@ const tryMatchEndpoint = (compiled, method, tokens) => {
|
|
|
346
351
|
* @example
|
|
347
352
|
* ```typescript
|
|
348
353
|
* const a = compileEndpoint(endpoint("/api/{id}").get(handler)); // specificity 3
|
|
349
|
-
* const b = compileEndpoint(endpoint("/api/{id
|
|
354
|
+
* const b = compileEndpoint(endpoint("/api/{id:?}").get(handler)); // specificity 2
|
|
350
355
|
* [b, a].sort(bySpecificityDesc); // → [a, b] — higher specificity first
|
|
351
356
|
* ```
|
|
352
357
|
*/
|
|
@@ -389,10 +394,12 @@ const findBestMatch = (table, method, tokens) => {
|
|
|
389
394
|
*
|
|
390
395
|
* Called by `onInit` — the one-time per-isolate setup. Sorts `state.table` by
|
|
391
396
|
* specificity (descending), validates that no endpoint path contains duplicate
|
|
392
|
-
* `{param}` names
|
|
397
|
+
* `{param}` names or the retired `{name?}` optional syntax, and sets
|
|
398
|
+
* `state.compiled = true` to guard re-entry.
|
|
393
399
|
*
|
|
394
400
|
* @param state - The mutable server state whose `table` should be compiled.
|
|
395
|
-
* @throws {Error} With `[moku-worker]` prefix when a path has duplicate param
|
|
401
|
+
* @throws {Error} With `[moku-worker]` prefix when a path has duplicate param
|
|
402
|
+
* names, or uses the old `{name?}` optional syntax (now `{name:?}`).
|
|
396
403
|
* @example
|
|
397
404
|
* ```typescript
|
|
398
405
|
* // Called inside serverPlugin.onInit:
|
|
@@ -406,6 +413,10 @@ const compileServerState = (state) => {
|
|
|
406
413
|
const seen = /* @__PURE__ */ new Set();
|
|
407
414
|
for (const segment of compiled.segments) {
|
|
408
415
|
if (!segment.param) continue;
|
|
416
|
+
if (segment.value.endsWith("?")) {
|
|
417
|
+
const name = segment.value.slice(0, -1);
|
|
418
|
+
throw new Error(`[moku-worker] endpoint path "${compiled.endpoint.path}" uses the old optional-param syntax "{${segment.value}}".\n Optional params now use the colon form (matching @moku-labs/web): write "{${name}:?}" instead of "{${name}?}".`);
|
|
419
|
+
}
|
|
409
420
|
if (seen.has(segment.value)) throw new Error(`[moku-worker] endpoint path "${compiled.endpoint.path}" has duplicate param "{${segment.value}}".\n Each {param} name in a path must be unique.`);
|
|
410
421
|
seen.add(segment.value);
|
|
411
422
|
}
|
package/package.json
CHANGED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
//#region src/config.d.ts
|
|
2
|
-
/** Per-request Cloudflare bindings object (env). Framework-level shared type. */
|
|
3
|
-
type WorkerEnv = Record<string, unknown>;
|
|
4
|
-
/** Global framework config — flat, with complete defaults. */
|
|
5
|
-
type WorkerConfig = {
|
|
6
|
-
stage: "production" | "development" | "test";
|
|
7
|
-
name: string;
|
|
8
|
-
compatibilityDate: string;
|
|
9
|
-
};
|
|
10
|
-
/** Global framework events — declared once, visible to every plugin. */
|
|
11
|
-
type WorkerEvents = {
|
|
12
|
-
"request:start": {
|
|
13
|
-
method: string;
|
|
14
|
-
path: string;
|
|
15
|
-
requestId: string;
|
|
16
|
-
};
|
|
17
|
-
"request:end": {
|
|
18
|
-
method: string;
|
|
19
|
-
path: string;
|
|
20
|
-
status: number;
|
|
21
|
-
ms: number;
|
|
22
|
-
};
|
|
23
|
-
"deploy:phase": {
|
|
24
|
-
phase: string;
|
|
25
|
-
detail?: string;
|
|
26
|
-
};
|
|
27
|
-
"deploy:complete": {
|
|
28
|
-
url: string;
|
|
29
|
-
};
|
|
30
|
-
"provision:resource": {
|
|
31
|
-
kind: "kv" | "r2" | "d1" | "queue" | "do";
|
|
32
|
-
name: string;
|
|
33
|
-
};
|
|
34
|
-
};
|
|
35
|
-
//#endregion
|
|
36
|
-
export { WorkerEnv as n, WorkerEvents as r, WorkerConfig as t };
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
//#region src/config.d.ts
|
|
2
|
-
/** Per-request Cloudflare bindings object (env). Framework-level shared type. */
|
|
3
|
-
type WorkerEnv = Record<string, unknown>;
|
|
4
|
-
/** Global framework config — flat, with complete defaults. */
|
|
5
|
-
type WorkerConfig = {
|
|
6
|
-
stage: "production" | "development" | "test";
|
|
7
|
-
name: string;
|
|
8
|
-
compatibilityDate: string;
|
|
9
|
-
};
|
|
10
|
-
/** Global framework events — declared once, visible to every plugin. */
|
|
11
|
-
type WorkerEvents = {
|
|
12
|
-
"request:start": {
|
|
13
|
-
method: string;
|
|
14
|
-
path: string;
|
|
15
|
-
requestId: string;
|
|
16
|
-
};
|
|
17
|
-
"request:end": {
|
|
18
|
-
method: string;
|
|
19
|
-
path: string;
|
|
20
|
-
status: number;
|
|
21
|
-
ms: number;
|
|
22
|
-
};
|
|
23
|
-
"deploy:phase": {
|
|
24
|
-
phase: string;
|
|
25
|
-
detail?: string;
|
|
26
|
-
};
|
|
27
|
-
"deploy:complete": {
|
|
28
|
-
url: string;
|
|
29
|
-
};
|
|
30
|
-
"provision:resource": {
|
|
31
|
-
kind: "kv" | "r2" | "d1" | "queue" | "do";
|
|
32
|
-
name: string;
|
|
33
|
-
};
|
|
34
|
-
};
|
|
35
|
-
//#endregion
|
|
36
|
-
export { WorkerEnv as n, WorkerEvents as r, WorkerConfig as t };
|