@mandujs/core 0.13.0 → 0.13.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.ko.md +4 -4
- package/README.md +653 -653
- package/package.json +1 -1
- package/src/bundler/build.ts +91 -91
- package/src/bundler/css.ts +302 -302
- package/src/client/Link.tsx +227 -227
- package/src/client/globals.ts +44 -44
- package/src/client/hooks.ts +267 -267
- package/src/client/index.ts +5 -5
- package/src/client/island.ts +8 -8
- package/src/client/router.ts +435 -435
- package/src/client/runtime.ts +23 -23
- package/src/client/serialize.ts +404 -404
- package/src/client/window-state.ts +101 -101
- package/src/config/mandu.ts +9 -0
- package/src/config/validate.ts +12 -0
- package/src/config/watcher.ts +311 -311
- package/src/constants.ts +40 -40
- package/src/content/content-layer.ts +314 -314
- package/src/content/content.test.ts +433 -433
- package/src/content/data-store.ts +245 -245
- package/src/content/digest.ts +133 -133
- package/src/content/index.ts +164 -164
- package/src/content/loader-context.ts +172 -172
- package/src/content/loaders/api.ts +216 -216
- package/src/content/loaders/file.ts +169 -169
- package/src/content/loaders/glob.ts +252 -252
- package/src/content/loaders/index.ts +34 -34
- package/src/content/loaders/types.ts +137 -137
- package/src/content/meta-store.ts +209 -209
- package/src/content/types.ts +282 -282
- package/src/content/watcher.ts +135 -135
- package/src/contract/client-safe.test.ts +42 -42
- package/src/contract/client-safe.ts +114 -114
- package/src/contract/client.ts +16 -16
- package/src/contract/define.ts +459 -459
- package/src/contract/handler.ts +10 -10
- package/src/contract/normalize.test.ts +276 -276
- package/src/contract/normalize.ts +404 -404
- package/src/contract/registry.test.ts +206 -206
- package/src/contract/registry.ts +568 -568
- package/src/contract/schema.ts +48 -48
- package/src/contract/types.ts +58 -58
- package/src/contract/validator.ts +32 -32
- package/src/devtools/ai/context-builder.ts +375 -375
- package/src/devtools/ai/index.ts +25 -25
- package/src/devtools/ai/mcp-connector.ts +465 -465
- package/src/devtools/client/catchers/error-catcher.ts +327 -327
- package/src/devtools/client/catchers/index.ts +18 -18
- package/src/devtools/client/catchers/network-proxy.ts +363 -363
- package/src/devtools/client/components/index.ts +39 -39
- package/src/devtools/client/components/kitchen-root.tsx +362 -362
- package/src/devtools/client/components/mandu-character.tsx +241 -241
- package/src/devtools/client/components/overlay.tsx +368 -368
- package/src/devtools/client/components/panel/errors-panel.tsx +259 -259
- package/src/devtools/client/components/panel/guard-panel.tsx +244 -244
- package/src/devtools/client/components/panel/index.ts +32 -32
- package/src/devtools/client/components/panel/islands-panel.tsx +304 -304
- package/src/devtools/client/components/panel/network-panel.tsx +292 -292
- package/src/devtools/client/components/panel/panel-container.tsx +259 -259
- package/src/devtools/client/filters/context-filters.ts +282 -282
- package/src/devtools/client/filters/index.ts +16 -16
- package/src/devtools/client/index.ts +63 -63
- package/src/devtools/client/persistence.ts +335 -335
- package/src/devtools/client/state-manager.ts +478 -478
- package/src/devtools/design-tokens.ts +263 -263
- package/src/devtools/hook/create-hook.ts +207 -207
- package/src/devtools/hook/index.ts +13 -13
- package/src/devtools/index.ts +439 -439
- package/src/devtools/init.ts +266 -266
- package/src/devtools/protocol.ts +237 -237
- package/src/devtools/server/index.ts +17 -17
- package/src/devtools/server/source-context.ts +444 -444
- package/src/devtools/types.ts +319 -319
- package/src/devtools/worker/index.ts +25 -25
- package/src/devtools/worker/redaction-worker.ts +222 -222
- package/src/devtools/worker/worker-manager.ts +409 -409
- package/src/error/domains.ts +265 -265
- package/src/error/result.ts +46 -46
- package/src/error/types.ts +6 -6
- package/src/errors/extractor.ts +409 -409
- package/src/errors/index.ts +19 -19
- package/src/filling/auth.ts +308 -308
- package/src/filling/context.ts +24 -1
- package/src/filling/deps.ts +238 -238
- package/src/filling/index.ts +2 -0
- package/src/filling/sse.test.ts +168 -0
- package/src/filling/sse.ts +162 -0
- package/src/generator/index.ts +3 -3
- package/src/guard/analyzer.ts +360 -360
- package/src/guard/ast-analyzer.ts +806 -806
- package/src/guard/contract-guard.ts +9 -9
- package/src/guard/file-type.test.ts +24 -24
- package/src/guard/presets/atomic.ts +70 -70
- package/src/guard/presets/clean.ts +77 -77
- package/src/guard/presets/fsd.ts +79 -79
- package/src/guard/presets/hexagonal.ts +68 -68
- package/src/guard/presets/index.ts +291 -291
- package/src/guard/reporter.ts +445 -445
- package/src/guard/rules.ts +12 -12
- package/src/guard/statistics.ts +578 -578
- package/src/guard/suggestions.ts +358 -358
- package/src/guard/types.ts +348 -348
- package/src/guard/validator.ts +834 -834
- package/src/guard/watcher.ts +404 -404
- package/src/index.ts +6 -1
- package/src/intent/index.ts +310 -310
- package/src/island/index.ts +304 -304
- package/src/logging/index.ts +22 -22
- package/src/logging/transports.ts +365 -365
- package/src/plugins/index.ts +38 -38
- package/src/plugins/registry.ts +377 -377
- package/src/plugins/types.ts +363 -363
- package/src/report/index.ts +1 -1
- package/src/router/fs-patterns.ts +387 -387
- package/src/router/fs-scanner.ts +497 -497
- package/src/runtime/boundary.tsx +232 -232
- package/src/runtime/compose.ts +222 -222
- package/src/runtime/escape.ts +44 -0
- package/src/runtime/lifecycle.ts +381 -381
- package/src/runtime/logger.test.ts +345 -345
- package/src/runtime/logger.ts +677 -677
- package/src/runtime/router.test.ts +476 -476
- package/src/runtime/router.ts +105 -105
- package/src/runtime/security.ts +155 -155
- package/src/runtime/server.ts +257 -0
- package/src/runtime/session-key.ts +328 -328
- package/src/runtime/ssr.ts +16 -21
- package/src/runtime/streaming-ssr.ts +24 -33
- package/src/runtime/trace.ts +144 -144
- package/src/seo/index.ts +214 -214
- package/src/seo/integration/ssr.ts +307 -307
- package/src/seo/render/basic.ts +427 -427
- package/src/seo/render/index.ts +143 -143
- package/src/seo/render/jsonld.ts +539 -539
- package/src/seo/render/opengraph.ts +191 -191
- package/src/seo/render/robots.ts +116 -116
- package/src/seo/render/sitemap.ts +137 -137
- package/src/seo/render/twitter.ts +126 -126
- package/src/seo/resolve/index.ts +353 -353
- package/src/seo/resolve/opengraph.ts +143 -143
- package/src/seo/resolve/robots.ts +73 -73
- package/src/seo/resolve/title.ts +94 -94
- package/src/seo/resolve/twitter.ts +73 -73
- package/src/seo/resolve/url.ts +97 -97
- package/src/seo/routes/index.ts +290 -290
- package/src/seo/types.ts +575 -575
- package/src/slot/validator.ts +39 -39
- package/src/spec/index.ts +3 -3
- package/src/spec/load.ts +76 -76
- package/src/spec/lock.ts +56 -56
- package/src/utils/bun.ts +8 -8
- package/src/utils/lru-cache.ts +75 -75
- package/src/utils/safe-io.ts +188 -188
- package/src/utils/string-safe.ts +298 -298
package/README.md
CHANGED
|
@@ -1,653 +1,653 @@
|
|
|
1
|
-
<p align="center">
|
|
2
|
-
<img src="https://raw.githubusercontent.com/konamgil/mandu/main/mandu_only_simbol.png" alt="Mandu" width="200" />
|
|
3
|
-
</p>
|
|
4
|
-
|
|
5
|
-
<h1 align="center">@mandujs/core</h1>
|
|
6
|
-
|
|
7
|
-
<p align="center">
|
|
8
|
-
<strong>Mandu Framework Core</strong><br/>
|
|
9
|
-
Runtime, FS Routes, Guard, Bundler, Contract, Filling
|
|
10
|
-
</p>
|
|
11
|
-
|
|
12
|
-
<p align="center">
|
|
13
|
-
English | <a href="./README.ko.md"><strong>한국어</strong></a>
|
|
14
|
-
</p>
|
|
15
|
-
|
|
16
|
-
## Installation
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
bun add @mandujs/core
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
> Typically used through `@mandujs/cli`. Direct usage is for advanced use cases.
|
|
23
|
-
|
|
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
|
|
72
|
-
|
|
73
|
-
```
|
|
74
|
-
@mandujs/core
|
|
75
|
-
├── router/ # FS Routes - file-system based routing
|
|
76
|
-
├── guard/ # Mandu Guard - architecture enforcement
|
|
77
|
-
│ ├── healing # Self-Healing Guard with auto-fix
|
|
78
|
-
│ ├── decision-memory # ADR storage (RFC-001)
|
|
79
|
-
│ ├── semantic-slots # Constraint validation (RFC-001)
|
|
80
|
-
│ └── negotiation # AI-Framework dialog (RFC-001)
|
|
81
|
-
├── runtime/ # Server, SSR, streaming
|
|
82
|
-
├── filling/ # Handler chain API (Mandu.filling())
|
|
83
|
-
├── contract/ # Type-safe API contracts
|
|
84
|
-
├── content/ # Content Layer - build-time content loading 🆕
|
|
85
|
-
│ └── loaders # file(), glob(), api() loaders
|
|
86
|
-
├── bundler/ # Client bundling, HMR
|
|
87
|
-
├── client/ # Island hydration, client router
|
|
88
|
-
├── brain/ # Doctor, Watcher, Architecture analyzer
|
|
89
|
-
├── change/ # Transaction & history
|
|
90
|
-
└── spec/ # Manifest schema & validation
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
---
|
|
94
|
-
|
|
95
|
-
## FS Routes
|
|
96
|
-
|
|
97
|
-
File-system based routing system.
|
|
98
|
-
|
|
99
|
-
```typescript
|
|
100
|
-
import { scanRoutes, generateManifest, watchFSRoutes } from "@mandujs/core/router";
|
|
101
|
-
|
|
102
|
-
// Scan routes from app/ directory
|
|
103
|
-
const result = await scanRoutes("/path/to/project");
|
|
104
|
-
console.log(result.routes);
|
|
105
|
-
|
|
106
|
-
// Generate manifest
|
|
107
|
-
const { manifest } = await generateManifest("/path/to/project", {
|
|
108
|
-
outputPath: ".mandu/manifest.json"
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
// Watch for changes
|
|
112
|
-
const watcher = await watchFSRoutes("/path/to/project", {
|
|
113
|
-
onChange: (result) => console.log("Routes updated!", result.routes.length)
|
|
114
|
-
});
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### Route Patterns
|
|
118
|
-
|
|
119
|
-
```typescript
|
|
120
|
-
import { pathToPattern, parseSegments } from "@mandujs/core/router";
|
|
121
|
-
|
|
122
|
-
// Convert path to URL pattern
|
|
123
|
-
pathToPattern("users/[id]/posts"); // → "/users/:id/posts"
|
|
124
|
-
pathToPattern("docs/[...slug]"); // → "/docs/:slug*"
|
|
125
|
-
pathToPattern("(auth)/login"); // → "/login" (group ignored)
|
|
126
|
-
|
|
127
|
-
// Parse segments
|
|
128
|
-
parseSegments("[id]"); // → [{ type: "dynamic", name: "id" }]
|
|
129
|
-
parseSegments("[...slug]"); // → [{ type: "catch-all", name: "slug" }]
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
---
|
|
133
|
-
|
|
134
|
-
## Mandu Guard
|
|
135
|
-
|
|
136
|
-
Real-time architecture enforcement with preset support.
|
|
137
|
-
|
|
138
|
-
```typescript
|
|
139
|
-
import {
|
|
140
|
-
createGuardWatcher,
|
|
141
|
-
checkDirectory,
|
|
142
|
-
getPreset,
|
|
143
|
-
listPresets
|
|
144
|
-
} from "@mandujs/core/guard";
|
|
145
|
-
|
|
146
|
-
// One-time check
|
|
147
|
-
const report = await checkDirectory(
|
|
148
|
-
{ preset: "mandu" },
|
|
149
|
-
process.cwd()
|
|
150
|
-
);
|
|
151
|
-
console.log(`Violations: ${report.totalViolations}`);
|
|
152
|
-
|
|
153
|
-
// Real-time watching
|
|
154
|
-
const watcher = createGuardWatcher({
|
|
155
|
-
config: { preset: "mandu", srcDir: "src" },
|
|
156
|
-
rootDir: process.cwd(),
|
|
157
|
-
onViolation: (v) => console.log(`${v.filePath}: ${v.ruleDescription}`),
|
|
158
|
-
});
|
|
159
|
-
watcher.start();
|
|
160
|
-
|
|
161
|
-
// List available presets
|
|
162
|
-
listPresets().forEach(p => console.log(p.name, p.description));
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
### Presets
|
|
166
|
-
|
|
167
|
-
| Preset | Layers | Use Case |
|
|
168
|
-
|--------|--------|----------|
|
|
169
|
-
| `mandu` | client/*, shared/(contracts, types, utils/*, schema, env), server/* | Fullstack (default) |
|
|
170
|
-
| `fsd` | app, pages, widgets, features, entities, shared | Frontend |
|
|
171
|
-
| `clean` | api, application, domain, infra, shared | Backend |
|
|
172
|
-
| `hexagonal` | adapters, ports, application, domain | DDD |
|
|
173
|
-
| `atomic` | pages, templates, organisms, molecules, atoms | UI |
|
|
174
|
-
|
|
175
|
-
### AST-based Analysis
|
|
176
|
-
|
|
177
|
-
```typescript
|
|
178
|
-
import { extractImportsAST, analyzeModuleAST } from "@mandujs/core/guard";
|
|
179
|
-
|
|
180
|
-
// Extract imports with AST (more accurate than regex)
|
|
181
|
-
const imports = extractImportsAST(code);
|
|
182
|
-
// → [{ path: "./utils", type: "static", line: 1, namedImports: ["foo"] }]
|
|
183
|
-
|
|
184
|
-
// Full module analysis
|
|
185
|
-
const analysis = analyzeModuleAST(code, "src/features/user/api.ts");
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
### Statistics & Trends
|
|
189
|
-
|
|
190
|
-
```typescript
|
|
191
|
-
import {
|
|
192
|
-
createScanRecord,
|
|
193
|
-
addScanRecord,
|
|
194
|
-
analyzeTrend,
|
|
195
|
-
generateGuardMarkdownReport
|
|
196
|
-
} from "@mandujs/core/guard";
|
|
197
|
-
|
|
198
|
-
// Save scan for trend analysis
|
|
199
|
-
const record = createScanRecord(report, "mandu");
|
|
200
|
-
await addScanRecord(rootDir, record);
|
|
201
|
-
|
|
202
|
-
// Analyze improvement trend
|
|
203
|
-
const trend = analyzeTrend(records, 7); // 7 days
|
|
204
|
-
console.log(trend.trend); // "improving" | "stable" | "degrading"
|
|
205
|
-
|
|
206
|
-
// Generate reports
|
|
207
|
-
const markdown = generateGuardMarkdownReport(report, trend);
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
### Self-Healing Guard (RFC-001) 🆕
|
|
211
|
-
|
|
212
|
-
```typescript
|
|
213
|
-
import { checkWithHealing, healAll, explainRule } from "@mandujs/core/guard";
|
|
214
|
-
|
|
215
|
-
// Detect violations with fix suggestions
|
|
216
|
-
const result = await checkWithHealing({ preset: "mandu" }, process.cwd());
|
|
217
|
-
|
|
218
|
-
// Auto-fix all fixable violations
|
|
219
|
-
if (result.items.length > 0) {
|
|
220
|
-
const healResult = await healAll(result);
|
|
221
|
-
console.log(`Fixed: ${healResult.fixed}, Failed: ${healResult.failed}`);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Explain any rule
|
|
225
|
-
const explanation = explainRule("layer-dependency");
|
|
226
|
-
console.log(explanation.description, explanation.examples);
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
### Decision Memory (RFC-001) 🆕
|
|
230
|
-
|
|
231
|
-
```typescript
|
|
232
|
-
import {
|
|
233
|
-
searchDecisions,
|
|
234
|
-
saveDecision,
|
|
235
|
-
checkConsistency,
|
|
236
|
-
getCompactArchitecture
|
|
237
|
-
} from "@mandujs/core/guard";
|
|
238
|
-
|
|
239
|
-
// Search past decisions
|
|
240
|
-
const results = await searchDecisions(rootDir, ["auth", "jwt"]);
|
|
241
|
-
|
|
242
|
-
// Save new decision (ADR)
|
|
243
|
-
await saveDecision(rootDir, {
|
|
244
|
-
id: "ADR-002",
|
|
245
|
-
title: "Use PostgreSQL",
|
|
246
|
-
status: "accepted",
|
|
247
|
-
context: "Need relational database",
|
|
248
|
-
decision: "Use PostgreSQL with Drizzle ORM",
|
|
249
|
-
consequences: ["Need to manage migrations"],
|
|
250
|
-
tags: ["database", "orm"]
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
// Check implementation consistency
|
|
254
|
-
const consistency = await checkConsistency(rootDir);
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
### Architecture Negotiation (RFC-001) 🆕
|
|
258
|
-
|
|
259
|
-
```typescript
|
|
260
|
-
import { negotiate, generateScaffold } from "@mandujs/core/guard";
|
|
261
|
-
|
|
262
|
-
// AI negotiates with framework before implementation
|
|
263
|
-
const plan = await negotiate({
|
|
264
|
-
intent: "Add user authentication",
|
|
265
|
-
requirements: ["JWT based", "Refresh tokens"],
|
|
266
|
-
constraints: ["Use existing User model"]
|
|
267
|
-
}, projectRoot);
|
|
268
|
-
|
|
269
|
-
if (plan.approved) {
|
|
270
|
-
// Generate scaffold files
|
|
271
|
-
await generateScaffold(plan.structure, projectRoot);
|
|
272
|
-
}
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
---
|
|
276
|
-
|
|
277
|
-
## Filling API
|
|
278
|
-
|
|
279
|
-
Handler chain for business logic.
|
|
280
|
-
|
|
281
|
-
```typescript
|
|
282
|
-
import { Mandu } from "@mandujs/core";
|
|
283
|
-
|
|
284
|
-
export default Mandu.filling()
|
|
285
|
-
// Lifecycle hooks
|
|
286
|
-
.onRequest((ctx) => {
|
|
287
|
-
ctx.set("requestId", crypto.randomUUID());
|
|
288
|
-
})
|
|
289
|
-
|
|
290
|
-
// Guard (return Response to block)
|
|
291
|
-
.guard((ctx) => {
|
|
292
|
-
if (!ctx.get("user")) return ctx.unauthorized("Login required");
|
|
293
|
-
})
|
|
294
|
-
|
|
295
|
-
// Handlers
|
|
296
|
-
.get(async (ctx) => {
|
|
297
|
-
return ctx.ok({ users: await fetchUsers() });
|
|
298
|
-
})
|
|
299
|
-
|
|
300
|
-
.post(async (ctx) => {
|
|
301
|
-
const body = await ctx.body();
|
|
302
|
-
return ctx.created({ user: await createUser(body) });
|
|
303
|
-
})
|
|
304
|
-
|
|
305
|
-
// After response
|
|
306
|
-
.afterResponse((ctx) => {
|
|
307
|
-
console.log("Request completed:", ctx.get("requestId"));
|
|
308
|
-
});
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
### Middleware (Compose-style)
|
|
312
|
-
|
|
313
|
-
```typescript
|
|
314
|
-
export default Mandu.filling()
|
|
315
|
-
.middleware(async (ctx, next) => {
|
|
316
|
-
console.log("before");
|
|
317
|
-
await next();
|
|
318
|
-
console.log("after");
|
|
319
|
-
})
|
|
320
|
-
.get((ctx) => ctx.ok({ ok: true }));
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
### Context API
|
|
324
|
-
|
|
325
|
-
| Method | Description |
|
|
326
|
-
|--------|-------------|
|
|
327
|
-
| `ctx.ok(data)` | 200 OK |
|
|
328
|
-
| `ctx.created(data)` | 201 Created |
|
|
329
|
-
| `ctx.noContent()` | 204 No Content |
|
|
330
|
-
| `ctx.error(message)` | 400 Bad Request |
|
|
331
|
-
| `ctx.unauthorized(message)` | 401 Unauthorized |
|
|
332
|
-
| `ctx.forbidden(message)` | 403 Forbidden |
|
|
333
|
-
| `ctx.notFound(message)` | 404 Not Found |
|
|
334
|
-
| `ctx.fail(message)` | 500 Internal Server Error |
|
|
335
|
-
| `ctx.body<T>()` | Parse request body |
|
|
336
|
-
| `ctx.params` | Route parameters |
|
|
337
|
-
| `ctx.query` | Query parameters |
|
|
338
|
-
| `ctx.set(key, value)` | Store in context |
|
|
339
|
-
| `ctx.get<T>(key)` | Retrieve from context |
|
|
340
|
-
|
|
341
|
-
---
|
|
342
|
-
|
|
343
|
-
## Contract API
|
|
344
|
-
|
|
345
|
-
Type-safe API contracts with Zod.
|
|
346
|
-
|
|
347
|
-
```typescript
|
|
348
|
-
import { Mandu } from "@mandujs/core";
|
|
349
|
-
import { z } from "zod";
|
|
350
|
-
|
|
351
|
-
// Define contract
|
|
352
|
-
const userContract = Mandu.contract({
|
|
353
|
-
request: {
|
|
354
|
-
GET: { query: z.object({ id: z.string() }) },
|
|
355
|
-
POST: { body: z.object({ name: z.string() }) }
|
|
356
|
-
},
|
|
357
|
-
response: {
|
|
358
|
-
200: z.object({ data: z.any() }),
|
|
359
|
-
400: z.object({ error: z.string() })
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
// Create typed handlers
|
|
364
|
-
const handlers = Mandu.handler(userContract, {
|
|
365
|
-
GET: (ctx) => ({ data: fetchUser(ctx.query.id) }),
|
|
366
|
-
POST: (ctx) => ({ data: createUser(ctx.body) })
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
// Type-safe client
|
|
370
|
-
const client = Mandu.client(userContract, { baseUrl: "/api/users" });
|
|
371
|
-
const result = await client.GET({ query: { id: "123" } });
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
### Client-safe Contract
|
|
375
|
-
|
|
376
|
-
Limit schemas exposed to client-side usage (forms/UI validation):
|
|
377
|
-
|
|
378
|
-
```typescript
|
|
379
|
-
const clientContract = Mandu.clientContract(userContract, {
|
|
380
|
-
request: {
|
|
381
|
-
POST: { body: true },
|
|
382
|
-
},
|
|
383
|
-
response: [201],
|
|
384
|
-
includeErrors: true,
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
const client = Mandu.client(clientContract, { baseUrl: "/api/users" });
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
---
|
|
391
|
-
|
|
392
|
-
## Runtime
|
|
393
|
-
|
|
394
|
-
Server and SSR.
|
|
395
|
-
|
|
396
|
-
```typescript
|
|
397
|
-
import { startServer, registerApiHandler, registerPageLoader } from "@mandujs/core";
|
|
398
|
-
|
|
399
|
-
// Register handlers
|
|
400
|
-
registerApiHandler("getUsers", async (req) => ({ users: [] }));
|
|
401
|
-
registerPageLoader("home", () => import("./pages/Home"));
|
|
402
|
-
|
|
403
|
-
// Start server
|
|
404
|
-
const server = startServer(manifest, { port: 3000 });
|
|
405
|
-
```
|
|
406
|
-
|
|
407
|
-
### Streaming SSR
|
|
408
|
-
|
|
409
|
-
```typescript
|
|
410
|
-
import { renderToStream } from "@mandujs/core";
|
|
411
|
-
|
|
412
|
-
const stream = await renderToStream(<App />, {
|
|
413
|
-
bootstrapScripts: ["/client.js"],
|
|
414
|
-
onError: (err) => console.error(err)
|
|
415
|
-
});
|
|
416
|
-
```
|
|
417
|
-
|
|
418
|
-
---
|
|
419
|
-
|
|
420
|
-
## Client (Islands & Router)
|
|
421
|
-
|
|
422
|
-
### Island Hydration
|
|
423
|
-
|
|
424
|
-
```typescript
|
|
425
|
-
import { createIsland, partial } from "@mandujs/core/client";
|
|
426
|
-
|
|
427
|
-
// Define island
|
|
428
|
-
const CounterIsland = createIsland({
|
|
429
|
-
name: "counter",
|
|
430
|
-
component: Counter,
|
|
431
|
-
priority: "visible"
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
// Partial (smaller than island)
|
|
435
|
-
const ButtonPartial = partial("submit-btn", SubmitButton);
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
### Client Router
|
|
439
|
-
|
|
440
|
-
```typescript
|
|
441
|
-
import { useRouter, useParams, Link, NavLink } from "@mandujs/core/client";
|
|
442
|
-
|
|
443
|
-
function Navigation() {
|
|
444
|
-
const router = useRouter();
|
|
445
|
-
const params = useParams();
|
|
446
|
-
|
|
447
|
-
return (
|
|
448
|
-
<nav>
|
|
449
|
-
<NavLink href="/users" activeClass="active">Users</NavLink>
|
|
450
|
-
<button onClick={() => router.push("/settings")}>Settings</button>
|
|
451
|
-
</nav>
|
|
452
|
-
);
|
|
453
|
-
}
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
---
|
|
457
|
-
|
|
458
|
-
## Content Layer 🆕
|
|
459
|
-
|
|
460
|
-
Astro-inspired build-time content loading system.
|
|
461
|
-
|
|
462
|
-
```typescript
|
|
463
|
-
// content.config.ts
|
|
464
|
-
import { defineContentConfig, glob, file, api } from "@mandujs/core/content";
|
|
465
|
-
import { z } from "zod";
|
|
466
|
-
|
|
467
|
-
const postSchema = z.object({
|
|
468
|
-
title: z.string(),
|
|
469
|
-
date: z.coerce.date(),
|
|
470
|
-
tags: z.array(z.string()).default([]),
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
export default defineContentConfig({
|
|
474
|
-
collections: {
|
|
475
|
-
// Markdown files with frontmatter
|
|
476
|
-
posts: {
|
|
477
|
-
loader: glob({ pattern: "content/posts/**/*.md" }),
|
|
478
|
-
schema: postSchema,
|
|
479
|
-
},
|
|
480
|
-
// Single JSON/YAML file
|
|
481
|
-
settings: {
|
|
482
|
-
loader: file({ path: "data/settings.json" }),
|
|
483
|
-
},
|
|
484
|
-
// External API
|
|
485
|
-
products: {
|
|
486
|
-
loader: api({
|
|
487
|
-
url: "https://api.example.com/products",
|
|
488
|
-
headers: () => ({ Authorization: `Bearer ${process.env.API_KEY}` }),
|
|
489
|
-
cacheTTL: 3600,
|
|
490
|
-
}),
|
|
491
|
-
},
|
|
492
|
-
},
|
|
493
|
-
});
|
|
494
|
-
```
|
|
495
|
-
|
|
496
|
-
### Querying Content
|
|
497
|
-
|
|
498
|
-
```typescript
|
|
499
|
-
import { getCollection, getEntry } from "@mandujs/core/content";
|
|
500
|
-
|
|
501
|
-
// Get all entries
|
|
502
|
-
const posts = await getCollection("posts");
|
|
503
|
-
posts.forEach(post => {
|
|
504
|
-
console.log(post.id, post.data.title);
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
// Get single entry
|
|
508
|
-
const post = await getEntry("posts", "hello-world");
|
|
509
|
-
console.log(post?.data.title, post?.body);
|
|
510
|
-
```
|
|
511
|
-
|
|
512
|
-
### Built-in Loaders
|
|
513
|
-
|
|
514
|
-
| Loader | Description | Example |
|
|
515
|
-
|--------|-------------|---------|
|
|
516
|
-
| `file()` | Single file (JSON, YAML, TOML) | `file({ path: "data/config.json" })` |
|
|
517
|
-
| `glob()` | Pattern matching (Markdown, JSON) | `glob({ pattern: "content/**/*.md" })` |
|
|
518
|
-
| `api()` | HTTP API with caching | `api({ url: "https://...", cacheTTL: 3600 })` |
|
|
519
|
-
|
|
520
|
-
### Features
|
|
521
|
-
|
|
522
|
-
- **Digest-based caching**: Only re-parse changed files
|
|
523
|
-
- **Zod validation**: Type-safe content with schema validation
|
|
524
|
-
- **Frontmatter support**: YAML frontmatter in Markdown files
|
|
525
|
-
- **Dev mode watching**: Auto-reload on content changes
|
|
526
|
-
- **Incremental updates**: Efficient builds with change detection
|
|
527
|
-
|
|
528
|
-
---
|
|
529
|
-
|
|
530
|
-
## Brain (AI Assistant)
|
|
531
|
-
|
|
532
|
-
Doctor and architecture analyzer.
|
|
533
|
-
|
|
534
|
-
```typescript
|
|
535
|
-
import {
|
|
536
|
-
initializeBrain,
|
|
537
|
-
getBrain,
|
|
538
|
-
analyzeViolations,
|
|
539
|
-
initializeArchitectureAnalyzer
|
|
540
|
-
} from "@mandujs/core";
|
|
541
|
-
|
|
542
|
-
// Initialize
|
|
543
|
-
await initializeBrain();
|
|
544
|
-
const brain = getBrain();
|
|
545
|
-
|
|
546
|
-
// Analyze violations with suggestions
|
|
547
|
-
const analysis = await analyzeViolations(violations, { useLLM: true });
|
|
548
|
-
console.log(analysis.patches); // Suggested fixes
|
|
549
|
-
|
|
550
|
-
// Architecture analyzer
|
|
551
|
-
const analyzer = initializeArchitectureAnalyzer(rootDir);
|
|
552
|
-
const locationResult = await analyzer.checkLocation({ path: "src/features/user.ts" });
|
|
553
|
-
const importResult = await analyzer.checkImports({
|
|
554
|
-
sourceFile: "src/features/user.ts",
|
|
555
|
-
imports: ["../entities/product"]
|
|
556
|
-
});
|
|
557
|
-
```
|
|
558
|
-
|
|
559
|
-
---
|
|
560
|
-
|
|
561
|
-
## Bundler
|
|
562
|
-
|
|
563
|
-
Client bundling with HMR.
|
|
564
|
-
|
|
565
|
-
```typescript
|
|
566
|
-
import { buildClientBundle, createDevBundler } from "@mandujs/core/bundler";
|
|
567
|
-
|
|
568
|
-
// Production build
|
|
569
|
-
const result = await buildClientBundle(manifest, {
|
|
570
|
-
outDir: ".mandu/client",
|
|
571
|
-
minify: true,
|
|
572
|
-
sourcemap: true
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
// Development with HMR
|
|
576
|
-
const devBundler = await createDevBundler(manifest, {
|
|
577
|
-
rootDir: process.cwd(),
|
|
578
|
-
isDev: true
|
|
579
|
-
});
|
|
580
|
-
```
|
|
581
|
-
|
|
582
|
-
---
|
|
583
|
-
|
|
584
|
-
## Transaction API
|
|
585
|
-
|
|
586
|
-
Atomic changes with rollback.
|
|
587
|
-
|
|
588
|
-
```typescript
|
|
589
|
-
import { beginChange, commitChange, rollbackChange } from "@mandujs/core";
|
|
590
|
-
|
|
591
|
-
// Start transaction
|
|
592
|
-
const { changeId, snapshotId } = await beginChange(rootDir, "Add user API");
|
|
593
|
-
|
|
594
|
-
// Make changes...
|
|
595
|
-
|
|
596
|
-
// Commit or rollback
|
|
597
|
-
await commitChange(rootDir);
|
|
598
|
-
// or
|
|
599
|
-
await rollbackChange(rootDir);
|
|
600
|
-
```
|
|
601
|
-
|
|
602
|
-
---
|
|
603
|
-
|
|
604
|
-
## Types
|
|
605
|
-
|
|
606
|
-
```typescript
|
|
607
|
-
import type {
|
|
608
|
-
// Spec
|
|
609
|
-
RoutesManifest,
|
|
610
|
-
RouteSpec,
|
|
611
|
-
|
|
612
|
-
// Guard
|
|
613
|
-
GuardPreset,
|
|
614
|
-
GuardConfig,
|
|
615
|
-
Violation,
|
|
616
|
-
ViolationReport,
|
|
617
|
-
|
|
618
|
-
// Router
|
|
619
|
-
ScanResult,
|
|
620
|
-
FSRouteConfig,
|
|
621
|
-
|
|
622
|
-
// Contract
|
|
623
|
-
ContractDefinition,
|
|
624
|
-
ContractHandlers,
|
|
625
|
-
|
|
626
|
-
// Filling
|
|
627
|
-
ManduContext,
|
|
628
|
-
|
|
629
|
-
// Content Layer
|
|
630
|
-
DataEntry,
|
|
631
|
-
ContentConfig,
|
|
632
|
-
CollectionConfig,
|
|
633
|
-
Loader,
|
|
634
|
-
LoaderContext,
|
|
635
|
-
} from "@mandujs/core";
|
|
636
|
-
```
|
|
637
|
-
|
|
638
|
-
---
|
|
639
|
-
|
|
640
|
-
## Requirements
|
|
641
|
-
|
|
642
|
-
- Bun >= 1.0.0
|
|
643
|
-
- React >= 19.0.0
|
|
644
|
-
- Zod >= 3.0.0
|
|
645
|
-
|
|
646
|
-
## Related Packages
|
|
647
|
-
|
|
648
|
-
- [@mandujs/cli](https://www.npmjs.com/package/@mandujs/cli) - CLI tool
|
|
649
|
-
- [@mandujs/mcp](https://www.npmjs.com/package/@mandujs/mcp) - MCP server for AI agents
|
|
650
|
-
|
|
651
|
-
## License
|
|
652
|
-
|
|
653
|
-
MIT
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/konamgil/mandu/main/mandu_only_simbol.png" alt="Mandu" width="200" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">@mandujs/core</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>Mandu Framework Core</strong><br/>
|
|
9
|
+
Runtime, FS Routes, Guard, Bundler, Contract, Filling
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
English | <a href="./README.ko.md"><strong>한국어</strong></a>
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
bun add @mandujs/core
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
> Typically used through `@mandujs/cli`. Direct usage is for advanced use cases.
|
|
23
|
+
|
|
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
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
@mandujs/core
|
|
75
|
+
├── router/ # FS Routes - file-system based routing
|
|
76
|
+
├── guard/ # Mandu Guard - architecture enforcement
|
|
77
|
+
│ ├── healing # Self-Healing Guard with auto-fix
|
|
78
|
+
│ ├── decision-memory # ADR storage (RFC-001)
|
|
79
|
+
│ ├── semantic-slots # Constraint validation (RFC-001)
|
|
80
|
+
│ └── negotiation # AI-Framework dialog (RFC-001)
|
|
81
|
+
├── runtime/ # Server, SSR, streaming
|
|
82
|
+
├── filling/ # Handler chain API (Mandu.filling())
|
|
83
|
+
├── contract/ # Type-safe API contracts
|
|
84
|
+
├── content/ # Content Layer - build-time content loading 🆕
|
|
85
|
+
│ └── loaders # file(), glob(), api() loaders
|
|
86
|
+
├── bundler/ # Client bundling, HMR
|
|
87
|
+
├── client/ # Island hydration, client router
|
|
88
|
+
├── brain/ # Doctor, Watcher, Architecture analyzer
|
|
89
|
+
├── change/ # Transaction & history
|
|
90
|
+
└── spec/ # Manifest schema & validation
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## FS Routes
|
|
96
|
+
|
|
97
|
+
File-system based routing system.
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import { scanRoutes, generateManifest, watchFSRoutes } from "@mandujs/core/router";
|
|
101
|
+
|
|
102
|
+
// Scan routes from app/ directory
|
|
103
|
+
const result = await scanRoutes("/path/to/project");
|
|
104
|
+
console.log(result.routes);
|
|
105
|
+
|
|
106
|
+
// Generate manifest
|
|
107
|
+
const { manifest } = await generateManifest("/path/to/project", {
|
|
108
|
+
outputPath: ".mandu/manifest.json"
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Watch for changes
|
|
112
|
+
const watcher = await watchFSRoutes("/path/to/project", {
|
|
113
|
+
onChange: (result) => console.log("Routes updated!", result.routes.length)
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Route Patterns
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
import { pathToPattern, parseSegments } from "@mandujs/core/router";
|
|
121
|
+
|
|
122
|
+
// Convert path to URL pattern
|
|
123
|
+
pathToPattern("users/[id]/posts"); // → "/users/:id/posts"
|
|
124
|
+
pathToPattern("docs/[...slug]"); // → "/docs/:slug*"
|
|
125
|
+
pathToPattern("(auth)/login"); // → "/login" (group ignored)
|
|
126
|
+
|
|
127
|
+
// Parse segments
|
|
128
|
+
parseSegments("[id]"); // → [{ type: "dynamic", name: "id" }]
|
|
129
|
+
parseSegments("[...slug]"); // → [{ type: "catch-all", name: "slug" }]
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Mandu Guard
|
|
135
|
+
|
|
136
|
+
Real-time architecture enforcement with preset support.
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
import {
|
|
140
|
+
createGuardWatcher,
|
|
141
|
+
checkDirectory,
|
|
142
|
+
getPreset,
|
|
143
|
+
listPresets
|
|
144
|
+
} from "@mandujs/core/guard";
|
|
145
|
+
|
|
146
|
+
// One-time check
|
|
147
|
+
const report = await checkDirectory(
|
|
148
|
+
{ preset: "mandu" },
|
|
149
|
+
process.cwd()
|
|
150
|
+
);
|
|
151
|
+
console.log(`Violations: ${report.totalViolations}`);
|
|
152
|
+
|
|
153
|
+
// Real-time watching
|
|
154
|
+
const watcher = createGuardWatcher({
|
|
155
|
+
config: { preset: "mandu", srcDir: "src" },
|
|
156
|
+
rootDir: process.cwd(),
|
|
157
|
+
onViolation: (v) => console.log(`${v.filePath}: ${v.ruleDescription}`),
|
|
158
|
+
});
|
|
159
|
+
watcher.start();
|
|
160
|
+
|
|
161
|
+
// List available presets
|
|
162
|
+
listPresets().forEach(p => console.log(p.name, p.description));
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Presets
|
|
166
|
+
|
|
167
|
+
| Preset | Layers | Use Case |
|
|
168
|
+
|--------|--------|----------|
|
|
169
|
+
| `mandu` | client/*, shared/(contracts, types, utils/*, schema, env), server/* | Fullstack (default) |
|
|
170
|
+
| `fsd` | app, pages, widgets, features, entities, shared | Frontend |
|
|
171
|
+
| `clean` | api, application, domain, infra, shared | Backend |
|
|
172
|
+
| `hexagonal` | adapters, ports, application, domain | DDD |
|
|
173
|
+
| `atomic` | pages, templates, organisms, molecules, atoms | UI |
|
|
174
|
+
|
|
175
|
+
### AST-based Analysis
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
import { extractImportsAST, analyzeModuleAST } from "@mandujs/core/guard";
|
|
179
|
+
|
|
180
|
+
// Extract imports with AST (more accurate than regex)
|
|
181
|
+
const imports = extractImportsAST(code);
|
|
182
|
+
// → [{ path: "./utils", type: "static", line: 1, namedImports: ["foo"] }]
|
|
183
|
+
|
|
184
|
+
// Full module analysis
|
|
185
|
+
const analysis = analyzeModuleAST(code, "src/features/user/api.ts");
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Statistics & Trends
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
import {
|
|
192
|
+
createScanRecord,
|
|
193
|
+
addScanRecord,
|
|
194
|
+
analyzeTrend,
|
|
195
|
+
generateGuardMarkdownReport
|
|
196
|
+
} from "@mandujs/core/guard";
|
|
197
|
+
|
|
198
|
+
// Save scan for trend analysis
|
|
199
|
+
const record = createScanRecord(report, "mandu");
|
|
200
|
+
await addScanRecord(rootDir, record);
|
|
201
|
+
|
|
202
|
+
// Analyze improvement trend
|
|
203
|
+
const trend = analyzeTrend(records, 7); // 7 days
|
|
204
|
+
console.log(trend.trend); // "improving" | "stable" | "degrading"
|
|
205
|
+
|
|
206
|
+
// Generate reports
|
|
207
|
+
const markdown = generateGuardMarkdownReport(report, trend);
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Self-Healing Guard (RFC-001) 🆕
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
import { checkWithHealing, healAll, explainRule } from "@mandujs/core/guard";
|
|
214
|
+
|
|
215
|
+
// Detect violations with fix suggestions
|
|
216
|
+
const result = await checkWithHealing({ preset: "mandu" }, process.cwd());
|
|
217
|
+
|
|
218
|
+
// Auto-fix all fixable violations
|
|
219
|
+
if (result.items.length > 0) {
|
|
220
|
+
const healResult = await healAll(result);
|
|
221
|
+
console.log(`Fixed: ${healResult.fixed}, Failed: ${healResult.failed}`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Explain any rule
|
|
225
|
+
const explanation = explainRule("layer-dependency");
|
|
226
|
+
console.log(explanation.description, explanation.examples);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Decision Memory (RFC-001) 🆕
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
import {
|
|
233
|
+
searchDecisions,
|
|
234
|
+
saveDecision,
|
|
235
|
+
checkConsistency,
|
|
236
|
+
getCompactArchitecture
|
|
237
|
+
} from "@mandujs/core/guard";
|
|
238
|
+
|
|
239
|
+
// Search past decisions
|
|
240
|
+
const results = await searchDecisions(rootDir, ["auth", "jwt"]);
|
|
241
|
+
|
|
242
|
+
// Save new decision (ADR)
|
|
243
|
+
await saveDecision(rootDir, {
|
|
244
|
+
id: "ADR-002",
|
|
245
|
+
title: "Use PostgreSQL",
|
|
246
|
+
status: "accepted",
|
|
247
|
+
context: "Need relational database",
|
|
248
|
+
decision: "Use PostgreSQL with Drizzle ORM",
|
|
249
|
+
consequences: ["Need to manage migrations"],
|
|
250
|
+
tags: ["database", "orm"]
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// Check implementation consistency
|
|
254
|
+
const consistency = await checkConsistency(rootDir);
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Architecture Negotiation (RFC-001) 🆕
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
import { negotiate, generateScaffold } from "@mandujs/core/guard";
|
|
261
|
+
|
|
262
|
+
// AI negotiates with framework before implementation
|
|
263
|
+
const plan = await negotiate({
|
|
264
|
+
intent: "Add user authentication",
|
|
265
|
+
requirements: ["JWT based", "Refresh tokens"],
|
|
266
|
+
constraints: ["Use existing User model"]
|
|
267
|
+
}, projectRoot);
|
|
268
|
+
|
|
269
|
+
if (plan.approved) {
|
|
270
|
+
// Generate scaffold files
|
|
271
|
+
await generateScaffold(plan.structure, projectRoot);
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Filling API
|
|
278
|
+
|
|
279
|
+
Handler chain for business logic.
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
import { Mandu } from "@mandujs/core";
|
|
283
|
+
|
|
284
|
+
export default Mandu.filling()
|
|
285
|
+
// Lifecycle hooks
|
|
286
|
+
.onRequest((ctx) => {
|
|
287
|
+
ctx.set("requestId", crypto.randomUUID());
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
// Guard (return Response to block)
|
|
291
|
+
.guard((ctx) => {
|
|
292
|
+
if (!ctx.get("user")) return ctx.unauthorized("Login required");
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
// Handlers
|
|
296
|
+
.get(async (ctx) => {
|
|
297
|
+
return ctx.ok({ users: await fetchUsers() });
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
.post(async (ctx) => {
|
|
301
|
+
const body = await ctx.body();
|
|
302
|
+
return ctx.created({ user: await createUser(body) });
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
// After response
|
|
306
|
+
.afterResponse((ctx) => {
|
|
307
|
+
console.log("Request completed:", ctx.get("requestId"));
|
|
308
|
+
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Middleware (Compose-style)
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
export default Mandu.filling()
|
|
315
|
+
.middleware(async (ctx, next) => {
|
|
316
|
+
console.log("before");
|
|
317
|
+
await next();
|
|
318
|
+
console.log("after");
|
|
319
|
+
})
|
|
320
|
+
.get((ctx) => ctx.ok({ ok: true }));
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Context API
|
|
324
|
+
|
|
325
|
+
| Method | Description |
|
|
326
|
+
|--------|-------------|
|
|
327
|
+
| `ctx.ok(data)` | 200 OK |
|
|
328
|
+
| `ctx.created(data)` | 201 Created |
|
|
329
|
+
| `ctx.noContent()` | 204 No Content |
|
|
330
|
+
| `ctx.error(message)` | 400 Bad Request |
|
|
331
|
+
| `ctx.unauthorized(message)` | 401 Unauthorized |
|
|
332
|
+
| `ctx.forbidden(message)` | 403 Forbidden |
|
|
333
|
+
| `ctx.notFound(message)` | 404 Not Found |
|
|
334
|
+
| `ctx.fail(message)` | 500 Internal Server Error |
|
|
335
|
+
| `ctx.body<T>()` | Parse request body |
|
|
336
|
+
| `ctx.params` | Route parameters |
|
|
337
|
+
| `ctx.query` | Query parameters |
|
|
338
|
+
| `ctx.set(key, value)` | Store in context |
|
|
339
|
+
| `ctx.get<T>(key)` | Retrieve from context |
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## Contract API
|
|
344
|
+
|
|
345
|
+
Type-safe API contracts with Zod.
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
import { Mandu } from "@mandujs/core";
|
|
349
|
+
import { z } from "zod";
|
|
350
|
+
|
|
351
|
+
// Define contract
|
|
352
|
+
const userContract = Mandu.contract({
|
|
353
|
+
request: {
|
|
354
|
+
GET: { query: z.object({ id: z.string() }) },
|
|
355
|
+
POST: { body: z.object({ name: z.string() }) }
|
|
356
|
+
},
|
|
357
|
+
response: {
|
|
358
|
+
200: z.object({ data: z.any() }),
|
|
359
|
+
400: z.object({ error: z.string() })
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// Create typed handlers
|
|
364
|
+
const handlers = Mandu.handler(userContract, {
|
|
365
|
+
GET: (ctx) => ({ data: fetchUser(ctx.query.id) }),
|
|
366
|
+
POST: (ctx) => ({ data: createUser(ctx.body) })
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// Type-safe client
|
|
370
|
+
const client = Mandu.client(userContract, { baseUrl: "/api/users" });
|
|
371
|
+
const result = await client.GET({ query: { id: "123" } });
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Client-safe Contract
|
|
375
|
+
|
|
376
|
+
Limit schemas exposed to client-side usage (forms/UI validation):
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
const clientContract = Mandu.clientContract(userContract, {
|
|
380
|
+
request: {
|
|
381
|
+
POST: { body: true },
|
|
382
|
+
},
|
|
383
|
+
response: [201],
|
|
384
|
+
includeErrors: true,
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
const client = Mandu.client(clientContract, { baseUrl: "/api/users" });
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## Runtime
|
|
393
|
+
|
|
394
|
+
Server and SSR.
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
import { startServer, registerApiHandler, registerPageLoader } from "@mandujs/core";
|
|
398
|
+
|
|
399
|
+
// Register handlers
|
|
400
|
+
registerApiHandler("getUsers", async (req) => ({ users: [] }));
|
|
401
|
+
registerPageLoader("home", () => import("./pages/Home"));
|
|
402
|
+
|
|
403
|
+
// Start server
|
|
404
|
+
const server = startServer(manifest, { port: 3000 });
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Streaming SSR
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
import { renderToStream } from "@mandujs/core";
|
|
411
|
+
|
|
412
|
+
const stream = await renderToStream(<App />, {
|
|
413
|
+
bootstrapScripts: ["/client.js"],
|
|
414
|
+
onError: (err) => console.error(err)
|
|
415
|
+
});
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
## Client (Islands & Router)
|
|
421
|
+
|
|
422
|
+
### Island Hydration
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
import { createIsland, partial } from "@mandujs/core/client";
|
|
426
|
+
|
|
427
|
+
// Define island
|
|
428
|
+
const CounterIsland = createIsland({
|
|
429
|
+
name: "counter",
|
|
430
|
+
component: Counter,
|
|
431
|
+
priority: "visible"
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
// Partial (smaller than island)
|
|
435
|
+
const ButtonPartial = partial("submit-btn", SubmitButton);
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### Client Router
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
import { useRouter, useParams, Link, NavLink } from "@mandujs/core/client";
|
|
442
|
+
|
|
443
|
+
function Navigation() {
|
|
444
|
+
const router = useRouter();
|
|
445
|
+
const params = useParams();
|
|
446
|
+
|
|
447
|
+
return (
|
|
448
|
+
<nav>
|
|
449
|
+
<NavLink href="/users" activeClass="active">Users</NavLink>
|
|
450
|
+
<button onClick={() => router.push("/settings")}>Settings</button>
|
|
451
|
+
</nav>
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
## Content Layer 🆕
|
|
459
|
+
|
|
460
|
+
Astro-inspired build-time content loading system.
|
|
461
|
+
|
|
462
|
+
```typescript
|
|
463
|
+
// content.config.ts
|
|
464
|
+
import { defineContentConfig, glob, file, api } from "@mandujs/core/content";
|
|
465
|
+
import { z } from "zod";
|
|
466
|
+
|
|
467
|
+
const postSchema = z.object({
|
|
468
|
+
title: z.string(),
|
|
469
|
+
date: z.coerce.date(),
|
|
470
|
+
tags: z.array(z.string()).default([]),
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
export default defineContentConfig({
|
|
474
|
+
collections: {
|
|
475
|
+
// Markdown files with frontmatter
|
|
476
|
+
posts: {
|
|
477
|
+
loader: glob({ pattern: "content/posts/**/*.md" }),
|
|
478
|
+
schema: postSchema,
|
|
479
|
+
},
|
|
480
|
+
// Single JSON/YAML file
|
|
481
|
+
settings: {
|
|
482
|
+
loader: file({ path: "data/settings.json" }),
|
|
483
|
+
},
|
|
484
|
+
// External API
|
|
485
|
+
products: {
|
|
486
|
+
loader: api({
|
|
487
|
+
url: "https://api.example.com/products",
|
|
488
|
+
headers: () => ({ Authorization: `Bearer ${process.env.API_KEY}` }),
|
|
489
|
+
cacheTTL: 3600,
|
|
490
|
+
}),
|
|
491
|
+
},
|
|
492
|
+
},
|
|
493
|
+
});
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### Querying Content
|
|
497
|
+
|
|
498
|
+
```typescript
|
|
499
|
+
import { getCollection, getEntry } from "@mandujs/core/content";
|
|
500
|
+
|
|
501
|
+
// Get all entries
|
|
502
|
+
const posts = await getCollection("posts");
|
|
503
|
+
posts.forEach(post => {
|
|
504
|
+
console.log(post.id, post.data.title);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
// Get single entry
|
|
508
|
+
const post = await getEntry("posts", "hello-world");
|
|
509
|
+
console.log(post?.data.title, post?.body);
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
### Built-in Loaders
|
|
513
|
+
|
|
514
|
+
| Loader | Description | Example |
|
|
515
|
+
|--------|-------------|---------|
|
|
516
|
+
| `file()` | Single file (JSON, YAML, TOML) | `file({ path: "data/config.json" })` |
|
|
517
|
+
| `glob()` | Pattern matching (Markdown, JSON) | `glob({ pattern: "content/**/*.md" })` |
|
|
518
|
+
| `api()` | HTTP API with caching | `api({ url: "https://...", cacheTTL: 3600 })` |
|
|
519
|
+
|
|
520
|
+
### Features
|
|
521
|
+
|
|
522
|
+
- **Digest-based caching**: Only re-parse changed files
|
|
523
|
+
- **Zod validation**: Type-safe content with schema validation
|
|
524
|
+
- **Frontmatter support**: YAML frontmatter in Markdown files
|
|
525
|
+
- **Dev mode watching**: Auto-reload on content changes
|
|
526
|
+
- **Incremental updates**: Efficient builds with change detection
|
|
527
|
+
|
|
528
|
+
---
|
|
529
|
+
|
|
530
|
+
## Brain (AI Assistant)
|
|
531
|
+
|
|
532
|
+
Doctor and architecture analyzer.
|
|
533
|
+
|
|
534
|
+
```typescript
|
|
535
|
+
import {
|
|
536
|
+
initializeBrain,
|
|
537
|
+
getBrain,
|
|
538
|
+
analyzeViolations,
|
|
539
|
+
initializeArchitectureAnalyzer
|
|
540
|
+
} from "@mandujs/core";
|
|
541
|
+
|
|
542
|
+
// Initialize
|
|
543
|
+
await initializeBrain();
|
|
544
|
+
const brain = getBrain();
|
|
545
|
+
|
|
546
|
+
// Analyze violations with suggestions
|
|
547
|
+
const analysis = await analyzeViolations(violations, { useLLM: true });
|
|
548
|
+
console.log(analysis.patches); // Suggested fixes
|
|
549
|
+
|
|
550
|
+
// Architecture analyzer
|
|
551
|
+
const analyzer = initializeArchitectureAnalyzer(rootDir);
|
|
552
|
+
const locationResult = await analyzer.checkLocation({ path: "src/features/user.ts" });
|
|
553
|
+
const importResult = await analyzer.checkImports({
|
|
554
|
+
sourceFile: "src/features/user.ts",
|
|
555
|
+
imports: ["../entities/product"]
|
|
556
|
+
});
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
---
|
|
560
|
+
|
|
561
|
+
## Bundler
|
|
562
|
+
|
|
563
|
+
Client bundling with HMR.
|
|
564
|
+
|
|
565
|
+
```typescript
|
|
566
|
+
import { buildClientBundle, createDevBundler } from "@mandujs/core/bundler";
|
|
567
|
+
|
|
568
|
+
// Production build
|
|
569
|
+
const result = await buildClientBundle(manifest, {
|
|
570
|
+
outDir: ".mandu/client",
|
|
571
|
+
minify: true,
|
|
572
|
+
sourcemap: true
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
// Development with HMR
|
|
576
|
+
const devBundler = await createDevBundler(manifest, {
|
|
577
|
+
rootDir: process.cwd(),
|
|
578
|
+
isDev: true
|
|
579
|
+
});
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
---
|
|
583
|
+
|
|
584
|
+
## Transaction API
|
|
585
|
+
|
|
586
|
+
Atomic changes with rollback.
|
|
587
|
+
|
|
588
|
+
```typescript
|
|
589
|
+
import { beginChange, commitChange, rollbackChange } from "@mandujs/core";
|
|
590
|
+
|
|
591
|
+
// Start transaction
|
|
592
|
+
const { changeId, snapshotId } = await beginChange(rootDir, "Add user API");
|
|
593
|
+
|
|
594
|
+
// Make changes...
|
|
595
|
+
|
|
596
|
+
// Commit or rollback
|
|
597
|
+
await commitChange(rootDir);
|
|
598
|
+
// or
|
|
599
|
+
await rollbackChange(rootDir);
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
---
|
|
603
|
+
|
|
604
|
+
## Types
|
|
605
|
+
|
|
606
|
+
```typescript
|
|
607
|
+
import type {
|
|
608
|
+
// Spec
|
|
609
|
+
RoutesManifest,
|
|
610
|
+
RouteSpec,
|
|
611
|
+
|
|
612
|
+
// Guard
|
|
613
|
+
GuardPreset,
|
|
614
|
+
GuardConfig,
|
|
615
|
+
Violation,
|
|
616
|
+
ViolationReport,
|
|
617
|
+
|
|
618
|
+
// Router
|
|
619
|
+
ScanResult,
|
|
620
|
+
FSRouteConfig,
|
|
621
|
+
|
|
622
|
+
// Contract
|
|
623
|
+
ContractDefinition,
|
|
624
|
+
ContractHandlers,
|
|
625
|
+
|
|
626
|
+
// Filling
|
|
627
|
+
ManduContext,
|
|
628
|
+
|
|
629
|
+
// Content Layer
|
|
630
|
+
DataEntry,
|
|
631
|
+
ContentConfig,
|
|
632
|
+
CollectionConfig,
|
|
633
|
+
Loader,
|
|
634
|
+
LoaderContext,
|
|
635
|
+
} from "@mandujs/core";
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
---
|
|
639
|
+
|
|
640
|
+
## Requirements
|
|
641
|
+
|
|
642
|
+
- Bun >= 1.0.0
|
|
643
|
+
- React >= 19.0.0
|
|
644
|
+
- Zod >= 3.0.0
|
|
645
|
+
|
|
646
|
+
## Related Packages
|
|
647
|
+
|
|
648
|
+
- [@mandujs/cli](https://www.npmjs.com/package/@mandujs/cli) - CLI tool
|
|
649
|
+
- [@mandujs/mcp](https://www.npmjs.com/package/@mandujs/mcp) - MCP server for AI agents
|
|
650
|
+
|
|
651
|
+
## License
|
|
652
|
+
|
|
653
|
+
MIT
|