@mandujs/core 0.9.31 → 0.9.38

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 CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  <p align="center">
8
8
  <strong>Mandu Framework Core</strong><br/>
9
- Spec, Generator, Guard, Runtime, Filling
9
+ Runtime, FS Routes, Guard, Bundler, Contract, Filling
10
10
  </p>
11
11
 
12
12
  <p align="center">
@@ -21,170 +21,456 @@ bun add @mandujs/core
21
21
 
22
22
  > Typically used through `@mandujs/cli`. Direct usage is for advanced use cases.
23
23
 
24
- ## Module Structure
24
+ ## Quick Start
25
+
26
+ ### Basic API Handler (Filling)
27
+
28
+ ```typescript
29
+ import { Mandu } from "@mandujs/core";
30
+
31
+ export default Mandu.filling()
32
+ .guard((ctx) => {
33
+ if (!ctx.get("user")) return ctx.unauthorized("Login required");
34
+ })
35
+ .get(async (ctx) => {
36
+ const users = await db.users.findMany();
37
+ return ctx.ok({ data: users });
38
+ })
39
+ .post(async (ctx) => {
40
+ const body = await ctx.body<{ name: string }>();
41
+ const user = await db.users.create({ data: body });
42
+ return ctx.created({ data: user });
43
+ });
44
+ ```
45
+
46
+ ### Type-Safe Contract
47
+
48
+ ```typescript
49
+ import { Mandu } from "@mandujs/core";
50
+ import { z } from "zod";
51
+
52
+ const userContract = Mandu.contract({
53
+ request: {
54
+ GET: { query: z.object({ id: z.string() }) },
55
+ POST: { body: z.object({ name: z.string() }) }
56
+ },
57
+ response: {
58
+ 200: z.object({ data: z.any() }),
59
+ 400: z.object({ error: z.string() })
60
+ }
61
+ });
62
+
63
+ const handlers = Mandu.handler(userContract, {
64
+ GET: (ctx) => ({ data: fetchUser(ctx.query.id) }),
65
+ POST: (ctx) => ({ data: createUser(ctx.body) })
66
+ });
67
+ ```
68
+
69
+ ---
70
+
71
+ ## Module Overview
25
72
 
26
73
  ```
27
74
  @mandujs/core
28
- ├── spec/ # Spec schema and loading
29
- ├── generator/ # Code generation
30
- ├── guard/ # Architecture checking and auto-correction
31
- ├── runtime/ # Server and router
32
- └── report/ # Guard report generation
75
+ ├── router/ # FS Routes - file-system based routing
76
+ ├── guard/ # Mandu Guard - architecture enforcement
77
+ ├── runtime/ # Server, SSR, streaming
78
+ ├── filling/ # Handler chain API (Mandu.filling())
79
+ ├── contract/ # Type-safe API contracts
80
+ ├── bundler/ # Client bundling, HMR
81
+ ├── client/ # Island hydration, client router
82
+ ├── brain/ # Doctor, Watcher, Architecture analyzer
83
+ ├── change/ # Transaction & history
84
+ └── spec/ # Manifest schema & validation
33
85
  ```
34
86
 
35
- ## Spec Module
87
+ ---
88
+
89
+ ## FS Routes
36
90
 
37
- Route manifest schema definition and loading.
91
+ File-system based routing system.
38
92
 
39
93
  ```typescript
40
- import { loadManifest, RoutesManifest, RouteSpec } from "@mandujs/core";
94
+ import { scanRoutes, generateManifest, watchFSRoutes } from "@mandujs/core/router";
41
95
 
42
- // Load and validate manifest
43
- const result = await loadManifest("spec/routes.manifest.json");
96
+ // Scan routes from app/ directory
97
+ const result = await scanRoutes("/path/to/project");
98
+ console.log(result.routes);
44
99
 
45
- if (result.success && result.data) {
46
- const manifest: RoutesManifest = result.data;
47
- manifest.routes.forEach((route: RouteSpec) => {
48
- console.log(route.id, route.pattern, route.kind);
49
- });
50
- }
100
+ // Generate manifest
101
+ const { manifest } = await generateManifest("/path/to/project", {
102
+ outputPath: ".mandu/manifest.json"
103
+ });
104
+
105
+ // Watch for changes
106
+ const watcher = await watchFSRoutes("/path/to/project", {
107
+ onChange: (result) => console.log("Routes updated!", result.routes.length)
108
+ });
51
109
  ```
52
110
 
53
- ### Lock File
111
+ ### Route Patterns
54
112
 
55
113
  ```typescript
56
- import { writeLock, readLock } from "@mandujs/core";
114
+ import { pathToPattern, parseSegments } from "@mandujs/core/router";
115
+
116
+ // Convert path to URL pattern
117
+ pathToPattern("users/[id]/posts"); // → "/users/:id/posts"
118
+ pathToPattern("docs/[...slug]"); // → "/docs/:slug*"
119
+ pathToPattern("(auth)/login"); // → "/login" (group ignored)
120
+
121
+ // Parse segments
122
+ parseSegments("[id]"); // → [{ type: "dynamic", name: "id" }]
123
+ parseSegments("[...slug]"); // → [{ type: "catch-all", name: "slug" }]
124
+ ```
125
+
126
+ ---
57
127
 
58
- // Write lock file
59
- const lock = await writeLock("spec/spec.lock.json", manifest);
60
- console.log(lock.routesHash);
128
+ ## Mandu Guard
61
129
 
62
- // Read lock file
63
- const existing = await readLock("spec/spec.lock.json");
130
+ Real-time architecture enforcement with preset support.
131
+
132
+ ```typescript
133
+ import {
134
+ createGuardWatcher,
135
+ checkDirectory,
136
+ getPreset,
137
+ listPresets
138
+ } from "@mandujs/core/guard";
139
+
140
+ // One-time check
141
+ const report = await checkDirectory(
142
+ { preset: "mandu" },
143
+ process.cwd()
144
+ );
145
+ console.log(`Violations: ${report.totalViolations}`);
146
+
147
+ // Real-time watching
148
+ const watcher = createGuardWatcher({
149
+ config: { preset: "mandu", srcDir: "src" },
150
+ rootDir: process.cwd(),
151
+ onViolation: (v) => console.log(`${v.filePath}: ${v.ruleDescription}`),
152
+ });
153
+ watcher.start();
154
+
155
+ // List available presets
156
+ listPresets().forEach(p => console.log(p.name, p.description));
64
157
  ```
65
158
 
66
- ## Generator Module
159
+ ### Presets
160
+
161
+ | Preset | Layers | Use Case |
162
+ |--------|--------|----------|
163
+ | `mandu` | app, pages, widgets, features, entities, api, application, domain, infra, core, shared | Fullstack (default) |
164
+ | `fsd` | app, pages, widgets, features, entities, shared | Frontend |
165
+ | `clean` | api, application, domain, infra, shared | Backend |
166
+ | `hexagonal` | adapters, ports, application, domain | DDD |
167
+ | `atomic` | pages, templates, organisms, molecules, atoms | UI |
67
168
 
68
- Spec-based code generation.
169
+ ### AST-based Analysis
69
170
 
70
171
  ```typescript
71
- import { generateRoutes, GenerateResult } from "@mandujs/core";
172
+ import { extractImportsAST, analyzeModuleAST } from "@mandujs/core/guard";
72
173
 
73
- const result: GenerateResult = await generateRoutes(manifest, "./");
174
+ // Extract imports with AST (more accurate than regex)
175
+ const imports = extractImportsAST(code);
176
+ // → [{ path: "./utils", type: "static", line: 1, namedImports: ["foo"] }]
74
177
 
75
- console.log("Created:", result.created);
76
- console.log("Skipped:", result.skipped); // Existing slot files
178
+ // Full module analysis
179
+ const analysis = analyzeModuleAST(code, "src/features/user/api.ts");
77
180
  ```
78
181
 
79
- ### Template Functions
182
+ ### Statistics & Trends
80
183
 
81
184
  ```typescript
82
185
  import {
83
- generateApiHandler,
84
- generateApiHandlerWithSlot,
85
- generateSlotLogic,
86
- generatePageComponent
87
- } from "@mandujs/core";
186
+ createScanRecord,
187
+ addScanRecord,
188
+ analyzeTrend,
189
+ generateGuardMarkdownReport
190
+ } from "@mandujs/core/guard";
191
+
192
+ // Save scan for trend analysis
193
+ const record = createScanRecord(report, "mandu");
194
+ await addScanRecord(rootDir, record);
195
+
196
+ // Analyze improvement trend
197
+ const trend = analyzeTrend(records, 7); // 7 days
198
+ console.log(trend.trend); // "improving" | "stable" | "degrading"
199
+
200
+ // Generate reports
201
+ const markdown = generateGuardMarkdownReport(report, trend);
202
+ ```
203
+
204
+ ---
88
205
 
89
- // Generate API handler
90
- const code = generateApiHandler(route);
206
+ ## Filling API
207
+
208
+ Handler chain for business logic.
209
+
210
+ ```typescript
211
+ import { Mandu } from "@mandujs/core";
212
+
213
+ export default Mandu.filling()
214
+ // Lifecycle hooks
215
+ .onRequest((ctx) => {
216
+ ctx.set("requestId", crypto.randomUUID());
217
+ })
218
+
219
+ // Guard (return Response to block)
220
+ .guard((ctx) => {
221
+ if (!ctx.get("user")) return ctx.unauthorized("Login required");
222
+ })
223
+
224
+ // Handlers
225
+ .get(async (ctx) => {
226
+ return ctx.ok({ users: await fetchUsers() });
227
+ })
228
+
229
+ .post(async (ctx) => {
230
+ const body = await ctx.body();
231
+ return ctx.created({ user: await createUser(body) });
232
+ })
233
+
234
+ // After response
235
+ .afterResponse((ctx) => {
236
+ console.log("Request completed:", ctx.get("requestId"));
237
+ });
238
+ ```
91
239
 
92
- // API handler with slot
93
- const codeWithSlot = generateApiHandlerWithSlot(route);
240
+ ### Middleware (Compose-style)
94
241
 
95
- // Slot logic file
96
- const slotCode = generateSlotLogic(route);
242
+ ```typescript
243
+ export default Mandu.filling()
244
+ .middleware(async (ctx, next) => {
245
+ console.log("before");
246
+ await next();
247
+ console.log("after");
248
+ })
249
+ .get((ctx) => ctx.ok({ ok: true }));
97
250
  ```
98
251
 
99
- ## Guard Module
252
+ ### Context API
253
+
254
+ | Method | Description |
255
+ |--------|-------------|
256
+ | `ctx.ok(data)` | 200 OK |
257
+ | `ctx.created(data)` | 201 Created |
258
+ | `ctx.noContent()` | 204 No Content |
259
+ | `ctx.error(message)` | 400 Bad Request |
260
+ | `ctx.unauthorized(message)` | 401 Unauthorized |
261
+ | `ctx.forbidden(message)` | 403 Forbidden |
262
+ | `ctx.notFound(message)` | 404 Not Found |
263
+ | `ctx.fail(message)` | 500 Internal Server Error |
264
+ | `ctx.body<T>()` | Parse request body |
265
+ | `ctx.params` | Route parameters |
266
+ | `ctx.query` | Query parameters |
267
+ | `ctx.set(key, value)` | Store in context |
268
+ | `ctx.get<T>(key)` | Retrieve from context |
269
+
270
+ ---
271
+
272
+ ## Contract API
100
273
 
101
- Architecture rule checking and auto-correction.
274
+ Type-safe API contracts with Zod.
102
275
 
103
276
  ```typescript
104
- import {
105
- runGuardCheck,
106
- runAutoCorrect,
107
- GuardResult,
108
- GuardViolation
109
- } from "@mandujs/core";
277
+ import { Mandu } from "@mandujs/core";
278
+ import { z } from "zod";
279
+
280
+ // Define contract
281
+ const userContract = Mandu.contract({
282
+ request: {
283
+ GET: { query: z.object({ id: z.string() }) },
284
+ POST: { body: z.object({ name: z.string() }) }
285
+ },
286
+ response: {
287
+ 200: z.object({ data: z.any() }),
288
+ 400: z.object({ error: z.string() })
289
+ }
290
+ });
110
291
 
111
- // Run check
112
- const result: GuardResult = await runGuardCheck(manifest, "./");
292
+ // Create typed handlers
293
+ const handlers = Mandu.handler(userContract, {
294
+ GET: (ctx) => ({ data: fetchUser(ctx.query.id) }),
295
+ POST: (ctx) => ({ data: createUser(ctx.body) })
296
+ });
113
297
 
114
- if (!result.passed) {
115
- result.violations.forEach((v: GuardViolation) => {
116
- console.log(`${v.rule}: ${v.message}`);
117
- });
298
+ // Type-safe client
299
+ const client = Mandu.client(userContract, { baseUrl: "/api/users" });
300
+ const result = await client.GET({ query: { id: "123" } });
301
+ ```
118
302
 
119
- // Run auto-correction
120
- const corrected = await runAutoCorrect(result.violations, manifest, "./");
121
- console.log("Fixed:", corrected.steps);
122
- console.log("Remaining violations:", corrected.remainingViolations);
123
- }
303
+ ---
304
+
305
+ ## Runtime
306
+
307
+ Server and SSR.
308
+
309
+ ```typescript
310
+ import { startServer, registerApiHandler, registerPageLoader } from "@mandujs/core";
311
+
312
+ // Register handlers
313
+ registerApiHandler("getUsers", async (req) => ({ users: [] }));
314
+ registerPageLoader("home", () => import("./pages/Home"));
315
+
316
+ // Start server
317
+ const server = startServer(manifest, { port: 3000 });
318
+ ```
319
+
320
+ ### Streaming SSR
321
+
322
+ ```typescript
323
+ import { renderToStream } from "@mandujs/core";
324
+
325
+ const stream = await renderToStream(<App />, {
326
+ bootstrapScripts: ["/client.js"],
327
+ onError: (err) => console.error(err)
328
+ });
329
+ ```
330
+
331
+ ---
332
+
333
+ ## Client (Islands & Router)
334
+
335
+ ### Island Hydration
336
+
337
+ ```typescript
338
+ import { createIsland, partial } from "@mandujs/core/client";
339
+
340
+ // Define island
341
+ const CounterIsland = createIsland({
342
+ name: "counter",
343
+ component: Counter,
344
+ priority: "visible"
345
+ });
346
+
347
+ // Partial (smaller than island)
348
+ const ButtonPartial = partial("submit-btn", SubmitButton);
124
349
  ```
125
350
 
126
- ### Guard Rules
351
+ ### Client Router
127
352
 
128
- | Rule ID | Description | Auto-correctable |
129
- |---------|-------------|------------------|
130
- | `SPEC_HASH_MISMATCH` | Spec and lock hash mismatch | ✅ |
131
- | `GENERATED_MANUAL_EDIT` | Manual edit to generated file | ✅ |
132
- | `HANDLER_NOT_FOUND` | Handler file not found | ❌ |
133
- | `COMPONENT_NOT_FOUND` | Component file not found | ❌ |
134
- | `SLOT_NOT_FOUND` | Slot file not found | ✅ |
353
+ ```typescript
354
+ import { useRouter, useParams, Link, NavLink } from "@mandujs/core/client";
355
+
356
+ function Navigation() {
357
+ const router = useRouter();
358
+ const params = useParams();
359
+
360
+ return (
361
+ <nav>
362
+ <NavLink href="/users" activeClass="active">Users</NavLink>
363
+ <button onClick={() => router.push("/settings")}>Settings</button>
364
+ </nav>
365
+ );
366
+ }
367
+ ```
135
368
 
136
- ## Runtime Module
369
+ ---
137
370
 
138
- Server startup and routing.
371
+ ## Brain (AI Assistant)
372
+
373
+ Doctor and architecture analyzer.
139
374
 
140
375
  ```typescript
141
376
  import {
142
- startServer,
143
- registerApiHandler,
144
- registerPageLoader
377
+ initializeBrain,
378
+ getBrain,
379
+ analyzeViolations,
380
+ initializeArchitectureAnalyzer
145
381
  } from "@mandujs/core";
146
382
 
147
- // Register API handler
148
- registerApiHandler("getUsers", async (req) => {
149
- return { users: [] };
383
+ // Initialize
384
+ await initializeBrain();
385
+ const brain = getBrain();
386
+
387
+ // Analyze violations with suggestions
388
+ const analysis = await analyzeViolations(violations, { useLLM: true });
389
+ console.log(analysis.patches); // Suggested fixes
390
+
391
+ // Architecture analyzer
392
+ const analyzer = initializeArchitectureAnalyzer(rootDir);
393
+ const locationResult = await analyzer.checkLocation({ path: "src/features/user.ts" });
394
+ const importResult = await analyzer.checkImports({
395
+ sourceFile: "src/features/user.ts",
396
+ imports: ["../entities/product"]
150
397
  });
398
+ ```
151
399
 
152
- // Register page loader
153
- registerPageLoader("homePage", () => import("./pages/Home"));
400
+ ---
154
401
 
155
- // Start server
156
- const server = startServer(manifest, { port: 3000 });
402
+ ## Bundler
403
+
404
+ Client bundling with HMR.
405
+
406
+ ```typescript
407
+ import { buildClientBundle, createDevBundler } from "@mandujs/core/bundler";
157
408
 
158
- // Stop
159
- server.stop();
409
+ // Production build
410
+ const result = await buildClientBundle(manifest, {
411
+ outDir: ".mandu/client",
412
+ minify: true,
413
+ sourcemap: true
414
+ });
415
+
416
+ // Development with HMR
417
+ const devBundler = await createDevBundler(manifest, {
418
+ rootDir: process.cwd(),
419
+ isDev: true
420
+ });
160
421
  ```
161
422
 
162
- ## Report Module
423
+ ---
163
424
 
164
- Guard result report generation.
425
+ ## Transaction API
426
+
427
+ Atomic changes with rollback.
165
428
 
166
429
  ```typescript
167
- import { buildGuardReport } from "@mandujs/core";
430
+ import { beginChange, commitChange, rollbackChange } from "@mandujs/core";
431
+
432
+ // Start transaction
433
+ const { changeId, snapshotId } = await beginChange(rootDir, "Add user API");
434
+
435
+ // Make changes...
168
436
 
169
- const report = buildGuardReport(guardResult, lockPath);
170
- console.log(report); // Formatted text report
437
+ // Commit or rollback
438
+ await commitChange(rootDir);
439
+ // or
440
+ await rollbackChange(rootDir);
171
441
  ```
172
442
 
443
+ ---
444
+
173
445
  ## Types
174
446
 
175
447
  ```typescript
176
448
  import type {
449
+ // Spec
177
450
  RoutesManifest,
178
451
  RouteSpec,
179
- RouteKind,
180
- SpecLock,
181
- GuardResult,
182
- GuardViolation,
183
- GenerateResult,
184
- AutoCorrectResult,
452
+
453
+ // Guard
454
+ GuardPreset,
455
+ GuardConfig,
456
+ Violation,
457
+ ViolationReport,
458
+
459
+ // Router
460
+ ScanResult,
461
+ FSRouteConfig,
462
+
463
+ // Contract
464
+ ContractDefinition,
465
+ ContractHandlers,
466
+
467
+ // Filling
468
+ ManduContext,
185
469
  } from "@mandujs/core";
186
470
  ```
187
471
 
472
+ ---
473
+
188
474
  ## Requirements
189
475
 
190
476
  - Bun >= 1.0.0
@@ -194,6 +480,7 @@ import type {
194
480
  ## Related Packages
195
481
 
196
482
  - [@mandujs/cli](https://www.npmjs.com/package/@mandujs/cli) - CLI tool
483
+ - [@mandujs/mcp](https://www.npmjs.com/package/@mandujs/mcp) - MCP server for AI agents
197
484
 
198
485
  ## License
199
486
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mandujs/core",
3
- "version": "0.9.31",
3
+ "version": "0.9.38",
4
4
  "description": "Mandu Framework Core - Spec, Generator, Guard, Runtime",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -49,6 +49,8 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "chokidar": "^5.0.0",
52
+ "glob": "^13.0.0",
53
+ "minimatch": "^10.1.1",
52
54
  "ollama": "^0.6.3"
53
55
  }
54
56
  }
@@ -173,9 +173,9 @@ export class ArchitectureAnalyzer {
173
173
  /**
174
174
  * 파일 위치 검증
175
175
  */
176
- async checkLocation(request: CheckLocationRequest): Promise<CheckLocationResult> {
177
- const violations: ArchitectureViolation[] = [];
178
- const normalizedPath = request.path.replace(/\\/g, "/");
176
+ async checkLocation(request: CheckLocationRequest): Promise<CheckLocationResult> {
177
+ const violations: ArchitectureViolation[] = [];
178
+ const normalizedPath = this.toRelativePath(request.path);
179
179
 
180
180
  // 1. readonly 폴더 검사
181
181
  for (const [key, rule] of Object.entries(this.config.folders || {})) {
@@ -288,14 +288,14 @@ export class ArchitectureAnalyzer {
288
288
  /**
289
289
  * Import 검증
290
290
  */
291
- async checkImports(request: CheckImportRequest): Promise<CheckImportResult> {
291
+ async checkImports(request: CheckImportRequest): Promise<CheckImportResult> {
292
292
  const violations: Array<{
293
293
  import: string;
294
294
  reason: string;
295
295
  suggestion?: string;
296
296
  }> = [];
297
297
 
298
- const normalizedSource = request.sourceFile.replace(/\\/g, "/");
298
+ const normalizedSource = this.toRelativePath(request.sourceFile);
299
299
 
300
300
  for (const importPath of request.imports) {
301
301
  for (const rule of this.config.imports || []) {
@@ -368,7 +368,7 @@ export class ArchitectureAnalyzer {
368
368
  /**
369
369
  * 코드에서 import 문 추출
370
370
  */
371
- private extractImports(content: string): string[] {
371
+ private extractImports(content: string): string[] {
372
372
  const imports: string[] = [];
373
373
 
374
374
  // ES6 import
@@ -385,7 +385,15 @@ export class ArchitectureAnalyzer {
385
385
  }
386
386
 
387
387
  return imports;
388
- }
388
+ }
389
+
390
+ private toRelativePath(filePath: string): string {
391
+ const normalized = filePath.replace(/\\/g, "/");
392
+ if (path.isAbsolute(normalized)) {
393
+ return path.relative(this.rootDir, normalized).replace(/\\/g, "/");
394
+ }
395
+ return normalized;
396
+ }
389
397
 
390
398
  /**
391
399
  * 폴더 스캔
@@ -34,7 +34,7 @@ export {
34
34
  formatPatch,
35
35
  printDoctorReport,
36
36
  generateJsonReport,
37
- generateMarkdownReport,
37
+ generateDoctorMarkdownReport,
38
38
  formatDoctorReport,
39
39
  type ReportFormat,
40
40
  } from "./reporter";
@@ -222,9 +222,9 @@ export function generateJsonReport(analysis: DoctorAnalysis): string {
222
222
  }
223
223
 
224
224
  /**
225
- * Generate a Markdown report
225
+ * Generate a Markdown report (Doctor Analysis)
226
226
  */
227
- export function generateMarkdownReport(analysis: DoctorAnalysis): string {
227
+ export function generateDoctorMarkdownReport(analysis: DoctorAnalysis): string {
228
228
  const lines: string[] = [];
229
229
 
230
230
  lines.push("# 🩺 Mandu Doctor Report");
@@ -328,7 +328,7 @@ export function formatDoctorReport(
328
328
  return generateJsonReport(analysis);
329
329
 
330
330
  case "markdown":
331
- return generateMarkdownReport(analysis);
331
+ return generateDoctorMarkdownReport(analysis);
332
332
 
333
333
  default:
334
334
  printDoctorReport(analysis);