@mandujs/core 0.9.2 → 0.9.4

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.
@@ -293,7 +293,11 @@ export class ManduFilling<TLoaderData = unknown> {
293
293
  }
294
294
  }
295
295
 
296
- export const Mandu = {
296
+ /**
297
+ * Mandu Filling factory functions
298
+ * Note: These are also available via the main `Mandu` namespace
299
+ */
300
+ export const ManduFillingFactory = {
297
301
  filling<TLoaderData = unknown>(): ManduFilling<TLoaderData> {
298
302
  return new ManduFilling<TLoaderData>();
299
303
  },
@@ -4,7 +4,7 @@
4
4
 
5
5
  export { ManduContext, ValidationError, CookieManager } from "./context";
6
6
  export type { CookieOptions } from "./context";
7
- export { ManduFilling, Mandu, LoaderTimeoutError } from "./filling";
7
+ export { ManduFilling, ManduFillingFactory, LoaderTimeoutError } from "./filling";
8
8
  export type { Handler, Guard, HttpMethod, Loader, LoaderOptions } from "./filling";
9
9
 
10
10
  // Auth Guards
@@ -1,3 +1,3 @@
1
- export * from "./generate";
2
- export * from "./templates";
3
- export * from "./contract-glue";
1
+ export * from "./generate";
2
+ export * from "./templates";
3
+ export * from "./contract-glue";
package/src/index.ts CHANGED
@@ -12,3 +12,78 @@ export * from "./contract";
12
12
  export * from "./openapi";
13
13
  export * from "./brain";
14
14
  export * from "./watcher";
15
+
16
+ // Consolidated Mandu namespace
17
+ import { ManduFilling, ManduContext, ManduFillingFactory } from "./filling";
18
+ import { createContract, defineHandler, defineRoute, createClient, contractFetch } from "./contract";
19
+ import type { ContractDefinition, ContractInstance, ContractSchema } from "./contract";
20
+ import type { ContractHandlers, ClientOptions } from "./contract";
21
+
22
+ /**
23
+ * Mandu - Unified Namespace
24
+ *
25
+ * 통합된 Mandu API 인터페이스
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * import { Mandu } from "@mandujs/core";
30
+ * import { z } from "zod";
31
+ *
32
+ * // Filling (Handler) API
33
+ * export default Mandu.filling()
34
+ * .get(async (ctx) => ctx.json({ message: "Hello" }));
35
+ *
36
+ * // Contract API
37
+ * const contract = Mandu.contract({
38
+ * request: { GET: { query: z.object({ id: z.string() }) } },
39
+ * response: { 200: z.object({ data: z.string() }) },
40
+ * });
41
+ *
42
+ * // Handler API (with type inference)
43
+ * const handlers = Mandu.handler(contract, {
44
+ * GET: (ctx) => ({ data: ctx.query.id }),
45
+ * });
46
+ *
47
+ * // Client API (type-safe fetch)
48
+ * const client = Mandu.client(contract, { baseUrl: "/api" });
49
+ * const result = await client.GET({ query: { id: "123" } });
50
+ * ```
51
+ */
52
+ export const Mandu = {
53
+ // === Filling (Handler) API ===
54
+ /**
55
+ * Create a new filling (handler chain)
56
+ */
57
+ filling: ManduFillingFactory.filling,
58
+
59
+ /**
60
+ * Create a ManduContext from a Request
61
+ */
62
+ context: ManduFillingFactory.context,
63
+
64
+ // === Contract API ===
65
+ /**
66
+ * Define a typed API contract
67
+ */
68
+ contract: createContract,
69
+
70
+ /**
71
+ * Create typed handlers for a contract
72
+ */
73
+ handler: defineHandler,
74
+
75
+ /**
76
+ * Define a complete route (contract + handler)
77
+ */
78
+ route: defineRoute,
79
+
80
+ /**
81
+ * Create a type-safe API client
82
+ */
83
+ client: createClient,
84
+
85
+ /**
86
+ * Make a type-safe fetch call
87
+ */
88
+ fetch: contractFetch,
89
+ } as const;
@@ -1 +1 @@
1
- export * from "./build";
1
+ export * from "./build";
@@ -1,222 +1,222 @@
1
- /**
2
- * Mandu Middleware Compose 🔗
3
- * Hono 스타일 미들웨어 조합 패턴
4
- *
5
- * @see https://github.com/honojs/hono/blob/main/src/compose.ts
6
- */
7
-
8
- import type { ManduContext } from "../filling/context";
9
-
10
- /**
11
- * Next 함수 타입
12
- */
13
- export type Next = () => Promise<void>;
14
-
15
- /**
16
- * 미들웨어 함수 타입
17
- * - Response 반환: 체인 중단 (Guard 역할)
18
- * - void 반환: 다음 미들웨어 실행
19
- */
20
- export type Middleware = (
21
- ctx: ManduContext,
22
- next: Next
23
- ) => Response | void | Promise<Response | void>;
24
-
25
- /**
26
- * 에러 핸들러 타입
27
- */
28
- export type ErrorHandler = (
29
- error: Error,
30
- ctx: ManduContext
31
- ) => Response | Promise<Response>;
32
-
33
- /**
34
- * NotFound 핸들러 타입
35
- */
36
- export type NotFoundHandler = (ctx: ManduContext) => Response | Promise<Response>;
37
-
38
- /**
39
- * 미들웨어 엔트리 (메타데이터 포함)
40
- */
41
- export interface MiddlewareEntry {
42
- fn: Middleware;
43
- name?: string;
44
- isAsync?: boolean;
45
- }
46
-
47
- /**
48
- * Compose 옵션
49
- */
50
- export interface ComposeOptions {
51
- onError?: ErrorHandler;
52
- onNotFound?: NotFoundHandler;
53
- }
54
-
55
- /**
56
- * 미들웨어 함수들을 하나의 실행 함수로 조합
57
- *
58
- * @example
59
- * ```typescript
60
- * const middleware = [
61
- * { fn: async (ctx, next) => { console.log('before'); await next(); console.log('after'); } },
62
- * { fn: async (ctx, next) => { return ctx.ok({ data: 'hello' }); } },
63
- * ];
64
- *
65
- * const handler = compose(middleware, {
66
- * onError: (err, ctx) => ctx.json({ error: err.message }, 500),
67
- * onNotFound: (ctx) => ctx.notFound(),
68
- * });
69
- *
70
- * const response = await handler(context);
71
- * ```
72
- */
73
- export function compose(
74
- middleware: MiddlewareEntry[],
75
- options: ComposeOptions = {}
76
- ): (ctx: ManduContext) => Promise<Response> {
77
- const { onError, onNotFound } = options;
78
-
79
- return async (ctx: ManduContext): Promise<Response> => {
80
- let index = -1;
81
- let finalResponse: Response | undefined;
82
-
83
- /**
84
- * 미들웨어 순차 실행
85
- * @param i 현재 인덱스
86
- */
87
- async function dispatch(i: number): Promise<void> {
88
- // next() 이중 호출 방지
89
- if (i <= index) {
90
- throw new Error("next() called multiple times");
91
- }
92
- index = i;
93
-
94
- const entry = middleware[i];
95
-
96
- if (!entry) {
97
- // 모든 미들웨어 통과 후 핸들러 없음
98
- if (!finalResponse && onNotFound) {
99
- finalResponse = await onNotFound(ctx);
100
- }
101
- return;
102
- }
103
-
104
- try {
105
- const result = await entry.fn(ctx, () => dispatch(i + 1));
106
-
107
- // Response 반환 시 체인 중단
108
- if (result instanceof Response) {
109
- finalResponse = result;
110
- return;
111
- }
112
- } catch (err) {
113
- if (err instanceof Error && onError) {
114
- finalResponse = await onError(err, ctx);
115
- return;
116
- }
117
- throw err;
118
- }
119
- }
120
-
121
- await dispatch(0);
122
-
123
- // 응답이 없으면 404
124
- if (!finalResponse) {
125
- if (onNotFound) {
126
- finalResponse = await onNotFound(ctx);
127
- } else {
128
- finalResponse = new Response("Not Found", { status: 404 });
129
- }
130
- }
131
-
132
- return finalResponse;
133
- };
134
- }
135
-
136
- /**
137
- * 미들웨어 배열 생성 헬퍼
138
- *
139
- * @example
140
- * ```typescript
141
- * const mw = createMiddleware([
142
- * authGuard,
143
- * rateLimitGuard,
144
- * mainHandler,
145
- * ]);
146
- * ```
147
- */
148
- export function createMiddleware(
149
- fns: Middleware[]
150
- ): MiddlewareEntry[] {
151
- return fns.map((fn, i) => ({
152
- fn,
153
- name: fn.name || `middleware_${i}`,
154
- isAsync: fn.constructor.name === "AsyncFunction",
155
- }));
156
- }
157
-
158
- /**
159
- * 미들웨어 체인 빌더
160
- *
161
- * @example
162
- * ```typescript
163
- * const chain = new MiddlewareChain()
164
- * .use(authGuard)
165
- * .use(rateLimitGuard)
166
- * .use(mainHandler)
167
- * .onError((err, ctx) => ctx.json({ error: err.message }, 500))
168
- * .build();
169
- *
170
- * const response = await chain(ctx);
171
- * ```
172
- */
173
- export class MiddlewareChain {
174
- private middleware: MiddlewareEntry[] = [];
175
- private errorHandler?: ErrorHandler;
176
- private notFoundHandler?: NotFoundHandler;
177
-
178
- /**
179
- * 미들웨어 추가
180
- */
181
- use(fn: Middleware, name?: string): this {
182
- this.middleware.push({
183
- fn,
184
- name: name || fn.name || `middleware_${this.middleware.length}`,
185
- isAsync: fn.constructor.name === "AsyncFunction",
186
- });
187
- return this;
188
- }
189
-
190
- /**
191
- * 에러 핸들러 설정
192
- */
193
- onError(handler: ErrorHandler): this {
194
- this.errorHandler = handler;
195
- return this;
196
- }
197
-
198
- /**
199
- * NotFound 핸들러 설정
200
- */
201
- onNotFound(handler: NotFoundHandler): this {
202
- this.notFoundHandler = handler;
203
- return this;
204
- }
205
-
206
- /**
207
- * 미들웨어 체인 빌드
208
- */
209
- build(): (ctx: ManduContext) => Promise<Response> {
210
- return compose(this.middleware, {
211
- onError: this.errorHandler,
212
- onNotFound: this.notFoundHandler,
213
- });
214
- }
215
-
216
- /**
217
- * 미들웨어 목록 조회
218
- */
219
- getMiddleware(): MiddlewareEntry[] {
220
- return [...this.middleware];
221
- }
222
- }
1
+ /**
2
+ * Mandu Middleware Compose 🔗
3
+ * Hono 스타일 미들웨어 조합 패턴
4
+ *
5
+ * @see https://github.com/honojs/hono/blob/main/src/compose.ts
6
+ */
7
+
8
+ import type { ManduContext } from "../filling/context";
9
+
10
+ /**
11
+ * Next 함수 타입
12
+ */
13
+ export type Next = () => Promise<void>;
14
+
15
+ /**
16
+ * 미들웨어 함수 타입
17
+ * - Response 반환: 체인 중단 (Guard 역할)
18
+ * - void 반환: 다음 미들웨어 실행
19
+ */
20
+ export type Middleware = (
21
+ ctx: ManduContext,
22
+ next: Next
23
+ ) => Response | void | Promise<Response | void>;
24
+
25
+ /**
26
+ * 에러 핸들러 타입
27
+ */
28
+ export type ErrorHandler = (
29
+ error: Error,
30
+ ctx: ManduContext
31
+ ) => Response | Promise<Response>;
32
+
33
+ /**
34
+ * NotFound 핸들러 타입
35
+ */
36
+ export type NotFoundHandler = (ctx: ManduContext) => Response | Promise<Response>;
37
+
38
+ /**
39
+ * 미들웨어 엔트리 (메타데이터 포함)
40
+ */
41
+ export interface MiddlewareEntry {
42
+ fn: Middleware;
43
+ name?: string;
44
+ isAsync?: boolean;
45
+ }
46
+
47
+ /**
48
+ * Compose 옵션
49
+ */
50
+ export interface ComposeOptions {
51
+ onError?: ErrorHandler;
52
+ onNotFound?: NotFoundHandler;
53
+ }
54
+
55
+ /**
56
+ * 미들웨어 함수들을 하나의 실행 함수로 조합
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * const middleware = [
61
+ * { fn: async (ctx, next) => { console.log('before'); await next(); console.log('after'); } },
62
+ * { fn: async (ctx, next) => { return ctx.ok({ data: 'hello' }); } },
63
+ * ];
64
+ *
65
+ * const handler = compose(middleware, {
66
+ * onError: (err, ctx) => ctx.json({ error: err.message }, 500),
67
+ * onNotFound: (ctx) => ctx.notFound(),
68
+ * });
69
+ *
70
+ * const response = await handler(context);
71
+ * ```
72
+ */
73
+ export function compose(
74
+ middleware: MiddlewareEntry[],
75
+ options: ComposeOptions = {}
76
+ ): (ctx: ManduContext) => Promise<Response> {
77
+ const { onError, onNotFound } = options;
78
+
79
+ return async (ctx: ManduContext): Promise<Response> => {
80
+ let index = -1;
81
+ let finalResponse: Response | undefined;
82
+
83
+ /**
84
+ * 미들웨어 순차 실행
85
+ * @param i 현재 인덱스
86
+ */
87
+ async function dispatch(i: number): Promise<void> {
88
+ // next() 이중 호출 방지
89
+ if (i <= index) {
90
+ throw new Error("next() called multiple times");
91
+ }
92
+ index = i;
93
+
94
+ const entry = middleware[i];
95
+
96
+ if (!entry) {
97
+ // 모든 미들웨어 통과 후 핸들러 없음
98
+ if (!finalResponse && onNotFound) {
99
+ finalResponse = await onNotFound(ctx);
100
+ }
101
+ return;
102
+ }
103
+
104
+ try {
105
+ const result = await entry.fn(ctx, () => dispatch(i + 1));
106
+
107
+ // Response 반환 시 체인 중단
108
+ if (result instanceof Response) {
109
+ finalResponse = result;
110
+ return;
111
+ }
112
+ } catch (err) {
113
+ if (err instanceof Error && onError) {
114
+ finalResponse = await onError(err, ctx);
115
+ return;
116
+ }
117
+ throw err;
118
+ }
119
+ }
120
+
121
+ await dispatch(0);
122
+
123
+ // 응답이 없으면 404
124
+ if (!finalResponse) {
125
+ if (onNotFound) {
126
+ finalResponse = await onNotFound(ctx);
127
+ } else {
128
+ finalResponse = new Response("Not Found", { status: 404 });
129
+ }
130
+ }
131
+
132
+ return finalResponse;
133
+ };
134
+ }
135
+
136
+ /**
137
+ * 미들웨어 배열 생성 헬퍼
138
+ *
139
+ * @example
140
+ * ```typescript
141
+ * const mw = createMiddleware([
142
+ * authGuard,
143
+ * rateLimitGuard,
144
+ * mainHandler,
145
+ * ]);
146
+ * ```
147
+ */
148
+ export function createMiddleware(
149
+ fns: Middleware[]
150
+ ): MiddlewareEntry[] {
151
+ return fns.map((fn, i) => ({
152
+ fn,
153
+ name: fn.name || `middleware_${i}`,
154
+ isAsync: fn.constructor.name === "AsyncFunction",
155
+ }));
156
+ }
157
+
158
+ /**
159
+ * 미들웨어 체인 빌더
160
+ *
161
+ * @example
162
+ * ```typescript
163
+ * const chain = new MiddlewareChain()
164
+ * .use(authGuard)
165
+ * .use(rateLimitGuard)
166
+ * .use(mainHandler)
167
+ * .onError((err, ctx) => ctx.json({ error: err.message }, 500))
168
+ * .build();
169
+ *
170
+ * const response = await chain(ctx);
171
+ * ```
172
+ */
173
+ export class MiddlewareChain {
174
+ private middleware: MiddlewareEntry[] = [];
175
+ private errorHandler?: ErrorHandler;
176
+ private notFoundHandler?: NotFoundHandler;
177
+
178
+ /**
179
+ * 미들웨어 추가
180
+ */
181
+ use(fn: Middleware, name?: string): this {
182
+ this.middleware.push({
183
+ fn,
184
+ name: name || fn.name || `middleware_${this.middleware.length}`,
185
+ isAsync: fn.constructor.name === "AsyncFunction",
186
+ });
187
+ return this;
188
+ }
189
+
190
+ /**
191
+ * 에러 핸들러 설정
192
+ */
193
+ onError(handler: ErrorHandler): this {
194
+ this.errorHandler = handler;
195
+ return this;
196
+ }
197
+
198
+ /**
199
+ * NotFound 핸들러 설정
200
+ */
201
+ onNotFound(handler: NotFoundHandler): this {
202
+ this.notFoundHandler = handler;
203
+ return this;
204
+ }
205
+
206
+ /**
207
+ * 미들웨어 체인 빌드
208
+ */
209
+ build(): (ctx: ManduContext) => Promise<Response> {
210
+ return compose(this.middleware, {
211
+ onError: this.errorHandler,
212
+ onNotFound: this.notFoundHandler,
213
+ });
214
+ }
215
+
216
+ /**
217
+ * 미들웨어 목록 조회
218
+ */
219
+ getMiddleware(): MiddlewareEntry[] {
220
+ return [...this.middleware];
221
+ }
222
+ }
@@ -3,6 +3,6 @@ export * from "./router";
3
3
  export * from "./server";
4
4
  export * from "./cors";
5
5
  export * from "./env";
6
- export * from "./compose";
7
- export * from "./lifecycle";
8
- export * from "./trace";
6
+ export * from "./compose";
7
+ export * from "./lifecycle";
8
+ export * from "./trace";