@mandujs/core 0.53.0 → 0.53.2
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/package.json +38 -2
- package/src/a11y/run-audit.ts +6 -6
- package/src/brain/adapters/oauth-flow.ts +1 -1
- package/src/brain/architecture/analyzer.ts +0 -2
- package/src/brain/credentials.ts +2 -2
- package/src/bundler/__tests__/build-runner.ts +13 -1
- package/src/bundler/__tests__/extended-watch.test.ts +3 -2
- package/src/bundler/__tests__/fast-refresh.test.ts +10 -12
- package/src/bundler/build.test.ts +30 -33
- package/src/bundler/build.ts +56 -27
- package/src/bundler/dev.ts +44 -39
- package/src/bundler/prerender.ts +63 -0
- package/src/bundler/safe-build.test.ts +2 -1
- package/src/bundler/types.ts +13 -6
- package/src/change/history.ts +1 -1
- package/src/change/snapshot.ts +18 -9
- package/src/client/Link.tsx +9 -9
- package/src/client/globals.ts +3 -2
- package/src/client/hooks.ts +0 -4
- package/src/client/hydrate.ts +1 -1
- package/src/client/router.ts +12 -12
- package/src/client/rpc.ts +1 -1
- package/src/client/use-fetch.ts +6 -6
- package/src/client/use-sse.ts +5 -5
- package/src/config/mcp-status.ts +0 -1
- package/src/config/validate.ts +4 -4
- package/src/config/watcher.ts +1 -1
- package/src/content/collection.ts +8 -12
- package/src/content/content-layer.ts +5 -5
- package/src/content/data-store.ts +2 -2
- package/src/content/loader-context.ts +0 -1
- package/src/content/loaders/file.ts +1 -1
- package/src/content/loaders/glob.ts +4 -4
- package/src/content/loaders/types.ts +1 -1
- package/src/content/meta-store.ts +1 -1
- package/src/content/types.ts +1 -1
- package/src/contract/define.ts +11 -12
- package/src/contract/index.ts +3 -3
- package/src/contract/protection.ts +2 -2
- package/src/contract/types.ts +1 -1
- package/src/contract/validator.ts +0 -1
- package/src/db/__tests__/db.test.ts +4 -7
- package/src/db/index.ts +138 -51
- package/src/db/migrations/index.ts +3 -0
- package/src/db/migrations/lock.ts +78 -23
- package/src/db/migrations/runner.ts +118 -101
- package/src/deploy/cache.ts +1 -0
- package/src/design/extract.ts +2 -1
- package/src/design/patch.ts +0 -1
- package/src/devtools/ai/mcp-connector.ts +24 -24
- package/src/devtools/client/components/kitchen-root.tsx +1 -1
- package/src/devtools/client/components/panel/errors-panel.tsx +1 -1
- package/src/devtools/client/state-manager.ts +1 -1
- package/src/devtools/init.ts +2 -3
- package/src/devtools/server/source-context.ts +1 -1
- package/src/devtools/worker/redaction-worker.ts +8 -4
- package/src/devtools/worker/worker-manager.ts +4 -3
- package/src/error/classifier.ts +1 -1
- package/src/error/domains.ts +1 -1
- package/src/filling/sse.ts +5 -5
- package/src/generator/generate.ts +2 -2
- package/src/guard/analyzer.ts +1 -1
- package/src/guard/ast-analyzer.ts +7 -7
- package/src/guard/auto-correct.ts +2 -2
- package/src/guard/config-guard.ts +0 -2
- package/src/guard/contract-guard.ts +1 -1
- package/src/guard/decision-memory.ts +2 -2
- package/src/guard/design-inline-class.ts +2 -1
- package/src/guard/graph.ts +1 -1
- package/src/guard/healing.ts +36 -41
- package/src/guard/negotiation.ts +3 -3
- package/src/guard/reporter.ts +1 -4
- package/src/guard/semantic-slots.ts +2 -2
- package/src/guard/suggestions.ts +2 -2
- package/src/guard/watcher.ts +5 -4
- package/src/index.ts +5 -5
- package/src/intent/index.ts +1 -1
- package/src/kitchen/api/agent-devtools-api.ts +544 -0
- package/src/kitchen/api/contract-api.ts +1 -1
- package/src/kitchen/api/file-api.ts +3 -3
- package/src/kitchen/kitchen-handler.ts +63 -38
- package/src/kitchen/kitchen-ui.ts +346 -62
- package/src/lockfile/validate.ts +0 -1
- package/src/openapi/generator.ts +2 -1
- package/src/openapi/openapi.test.ts +43 -14
- package/src/perf/user-marks.ts +1 -1
- package/src/plugins/registry.ts +1 -2
- package/src/plugins/types.ts +1 -1
- package/src/resource/__tests__/generator.test.ts +32 -15
- package/src/resource/ddl/__tests__/emit.test.ts +24 -0
- package/src/resource/ddl/diff.ts +0 -1
- package/src/resource/ddl/emit.ts +12 -1
- package/src/resource/generator-repo.ts +40 -20
- package/src/resource/generator.ts +1 -1
- package/src/resource/generators/contract.ts +1 -1
- package/src/resource/schema.ts +2 -1
- package/src/router/fs-routes.ts +6 -6
- package/src/router/fs-scanner.ts +1 -1
- package/src/runtime/env.ts +1 -1
- package/src/runtime/image-handler.ts +2 -1
- package/src/runtime/middleware.ts +2 -1
- package/src/runtime/server.ts +67 -60
- package/src/runtime/ssr.ts +3 -3
- package/src/runtime/streaming-ssr.ts +25 -27
- package/src/scheduler/validate.ts +1 -1
- package/src/seo/integration/ssr.ts +2 -3
- package/src/seo/render/opengraph.ts +1 -1
- package/src/seo/resolve/index.ts +0 -4
- package/src/slot/corrector.ts +1 -1
- package/src/slot/validator.ts +4 -4
- package/src/watcher/reporter.ts +1 -1
- package/src/watcher/watcher.ts +19 -19
|
@@ -69,7 +69,7 @@ import type {
|
|
|
69
69
|
PendingMigration,
|
|
70
70
|
SqlProvider,
|
|
71
71
|
} from "../../resource/ddl/types";
|
|
72
|
-
import type
|
|
72
|
+
import { withPinnedDbHandle, type Db } from "../index";
|
|
73
73
|
import {
|
|
74
74
|
DEFAULT_HISTORY_TABLE,
|
|
75
75
|
SAFE_HISTORY_TABLE_RE,
|
|
@@ -128,6 +128,24 @@ export class MigrationTimeoutError extends Error {
|
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
function assertNoTamperedHistory(
|
|
132
|
+
history: HistoryRow[],
|
|
133
|
+
diskByVersion: Map<string, PendingMigration>,
|
|
134
|
+
): void {
|
|
135
|
+
for (const row of history) {
|
|
136
|
+
if (row.success !== 1) continue;
|
|
137
|
+
const disk = diskByVersion.get(row.version);
|
|
138
|
+
if (!disk) continue; // orphan on the history side — surfaced via status(), not apply()
|
|
139
|
+
if (disk.checksum !== row.checksum) {
|
|
140
|
+
throw new MigrationTamperedError(
|
|
141
|
+
disk.filename,
|
|
142
|
+
row.checksum,
|
|
143
|
+
disk.checksum,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
131
149
|
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
132
150
|
|
|
133
151
|
/** Options for {@link createMigrationRunner}. */
|
|
@@ -246,34 +264,18 @@ export function createMigrationRunner(
|
|
|
246
264
|
async function apply(
|
|
247
265
|
opts: { dryRun?: boolean } = {},
|
|
248
266
|
): Promise<AppliedMigration[]> {
|
|
249
|
-
await ensureReady();
|
|
250
|
-
|
|
251
|
-
// Tamper check BEFORE acquiring the lock so the fast-fail path
|
|
252
|
-
// doesn't hold the advisory lock longer than necessary.
|
|
253
|
-
const history = await readAllHistory(db, historyTable);
|
|
254
267
|
const diskFiles = await readMigrationsFromDisk(migrationsDir);
|
|
255
268
|
const diskByVersion = new Map(diskFiles.map((f) => [f.version, f]));
|
|
256
|
-
for (const row of history) {
|
|
257
|
-
if (row.success !== 1) continue;
|
|
258
|
-
const disk = diskByVersion.get(row.version);
|
|
259
|
-
if (!disk) continue; // orphan on the history side — surfaced via status(), not apply()
|
|
260
|
-
if (disk.checksum !== row.checksum) {
|
|
261
|
-
throw new MigrationTamperedError(
|
|
262
|
-
disk.filename,
|
|
263
|
-
row.checksum,
|
|
264
|
-
disk.checksum,
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
269
|
|
|
269
|
-
const appliedVersions = new Set(
|
|
270
|
-
history.filter((h) => h.success === 1).map((h) => h.version),
|
|
271
|
-
);
|
|
272
|
-
const pending = diskFiles.filter((f) => !appliedVersions.has(f.version));
|
|
273
|
-
if (pending.length === 0) return [];
|
|
274
|
-
|
|
275
|
-
// Dry-run: report what we WOULD apply, no IO, no history.
|
|
276
270
|
if (opts.dryRun === true) {
|
|
271
|
+
await ensureReady();
|
|
272
|
+
const history = await readAllHistory(db, historyTable);
|
|
273
|
+
assertNoTamperedHistory(history, diskByVersion);
|
|
274
|
+
|
|
275
|
+
const appliedVersions = new Set(
|
|
276
|
+
history.filter((h) => h.success === 1).map((h) => h.version),
|
|
277
|
+
);
|
|
278
|
+
const pending = diskFiles.filter((f) => !appliedVersions.has(f.version));
|
|
277
279
|
return pending.map<AppliedMigration>((p) => ({
|
|
278
280
|
version: p.version,
|
|
279
281
|
filename: p.filename,
|
|
@@ -290,100 +292,115 @@ export function createMigrationRunner(
|
|
|
290
292
|
|
|
291
293
|
const applied: AppliedMigration[] = [];
|
|
292
294
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
295
|
+
const runLockedApply = async (): Promise<AppliedMigration[]> => {
|
|
296
|
+
heldLock = await acquireMigrationLock(db, lockStrategy);
|
|
297
|
+
try {
|
|
298
|
+
await ensureReady();
|
|
299
|
+
const lockedHistory = await readAllHistory(db, historyTable);
|
|
300
|
+
assertNoTamperedHistory(lockedHistory, diskByVersion);
|
|
301
|
+
const lockedAppliedVersions = new Set(
|
|
302
|
+
lockedHistory.filter((h) => h.success === 1).map((h) => h.version),
|
|
303
|
+
);
|
|
304
|
+
const lockedPending = diskFiles.filter((f) => !lockedAppliedVersions.has(f.version));
|
|
305
|
+
|
|
306
|
+
for (const migration of lockedPending) {
|
|
307
|
+
const start = Date.now();
|
|
308
|
+
|
|
309
|
+
const statements = splitStatements(migration.sql);
|
|
310
|
+
if (statements.length === 0) {
|
|
311
|
+
// Empty migration — still record a history row so we don't
|
|
312
|
+
// re-run it. execution_ms = 0 reflects reality.
|
|
313
|
+
await insertHistory(db, historyTable, {
|
|
314
|
+
version: migration.version,
|
|
315
|
+
filename: migration.filename,
|
|
316
|
+
checksum: migration.checksum,
|
|
317
|
+
applied_at: new Date(),
|
|
318
|
+
execution_ms: 0,
|
|
319
|
+
success: 1,
|
|
320
|
+
installed_by: installedBy,
|
|
321
|
+
});
|
|
322
|
+
applied.push({
|
|
323
|
+
version: migration.version,
|
|
324
|
+
filename: migration.filename,
|
|
325
|
+
checksum: migration.checksum,
|
|
326
|
+
appliedAt: new Date(),
|
|
327
|
+
executionMs: 0,
|
|
328
|
+
success: true,
|
|
329
|
+
});
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
try {
|
|
334
|
+
await db.transaction(async (tx) => {
|
|
335
|
+
for (const stmt of statements) {
|
|
336
|
+
await execRaw(tx, stmt);
|
|
337
|
+
const elapsed = Date.now() - start;
|
|
338
|
+
if (elapsed > applyTimeoutMs) {
|
|
339
|
+
throw new MigrationTimeoutError(
|
|
340
|
+
migration.filename,
|
|
341
|
+
elapsed,
|
|
342
|
+
applyTimeoutMs,
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
} catch (err) {
|
|
348
|
+
if (err instanceof MigrationTimeoutError) throw err;
|
|
349
|
+
// Wrap with migration context so downstream callers know
|
|
350
|
+
// which file blew up. Preserve the original stack where
|
|
351
|
+
// possible via `cause`.
|
|
352
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
353
|
+
const wrapped = new Error(
|
|
354
|
+
`[@mandujs/core/db/migrations] Failed to apply ${migration.filename}: ${msg}`,
|
|
355
|
+
);
|
|
356
|
+
// Preserve the original as a `cause` chain for diagnostics.
|
|
357
|
+
(wrapped as { cause?: unknown }).cause = err;
|
|
358
|
+
throw wrapped;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const executionMs = Date.now() - start;
|
|
362
|
+
const appliedAt = new Date();
|
|
363
|
+
|
|
364
|
+
// History row is written AFTER the SQL transaction commits.
|
|
365
|
+
// If this INSERT itself fails, the migration has run but we
|
|
366
|
+
// have no record — the user will see it as pending again.
|
|
367
|
+
// Mitigation: the insert is a single tiny statement; in
|
|
368
|
+
// practice it either succeeds or the whole connection is
|
|
369
|
+
// dead (in which case subsequent apply() calls will also fail
|
|
370
|
+
// and the user will debug from the DB side).
|
|
302
371
|
await insertHistory(db, historyTable, {
|
|
303
372
|
version: migration.version,
|
|
304
373
|
filename: migration.filename,
|
|
305
374
|
checksum: migration.checksum,
|
|
306
|
-
applied_at:
|
|
307
|
-
execution_ms:
|
|
375
|
+
applied_at: appliedAt,
|
|
376
|
+
execution_ms: executionMs,
|
|
308
377
|
success: 1,
|
|
309
378
|
installed_by: installedBy,
|
|
310
379
|
});
|
|
380
|
+
|
|
311
381
|
applied.push({
|
|
312
382
|
version: migration.version,
|
|
313
383
|
filename: migration.filename,
|
|
314
384
|
checksum: migration.checksum,
|
|
315
|
-
appliedAt
|
|
316
|
-
executionMs
|
|
385
|
+
appliedAt,
|
|
386
|
+
executionMs,
|
|
317
387
|
success: true,
|
|
318
388
|
});
|
|
319
|
-
continue;
|
|
320
389
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
await
|
|
324
|
-
|
|
325
|
-
await execRaw(tx, stmt);
|
|
326
|
-
const elapsed = Date.now() - start;
|
|
327
|
-
if (elapsed > applyTimeoutMs) {
|
|
328
|
-
throw new MigrationTimeoutError(
|
|
329
|
-
migration.filename,
|
|
330
|
-
elapsed,
|
|
331
|
-
applyTimeoutMs,
|
|
332
|
-
);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
});
|
|
336
|
-
} catch (err) {
|
|
337
|
-
if (err instanceof MigrationTimeoutError) throw err;
|
|
338
|
-
// Wrap with migration context so downstream callers know
|
|
339
|
-
// which file blew up. Preserve the original stack where
|
|
340
|
-
// possible via `cause`.
|
|
341
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
342
|
-
const wrapped = new Error(
|
|
343
|
-
`[@mandujs/core/db/migrations] Failed to apply ${migration.filename}: ${msg}`,
|
|
344
|
-
);
|
|
345
|
-
// Preserve the original as a `cause` chain for diagnostics.
|
|
346
|
-
(wrapped as { cause?: unknown }).cause = err;
|
|
347
|
-
throw wrapped;
|
|
390
|
+
} finally {
|
|
391
|
+
if (heldLock) {
|
|
392
|
+
await heldLock.release();
|
|
393
|
+
heldLock = null;
|
|
348
394
|
}
|
|
395
|
+
}
|
|
349
396
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
// History row is written AFTER the SQL transaction commits.
|
|
354
|
-
// If this INSERT itself fails, the migration has run but we
|
|
355
|
-
// have no record — the user will see it as pending again.
|
|
356
|
-
// Mitigation: the insert is a single tiny statement; in
|
|
357
|
-
// practice it either succeeds or the whole connection is
|
|
358
|
-
// dead (in which case subsequent apply() calls will also fail
|
|
359
|
-
// and the user will debug from the DB side).
|
|
360
|
-
await insertHistory(db, historyTable, {
|
|
361
|
-
version: migration.version,
|
|
362
|
-
filename: migration.filename,
|
|
363
|
-
checksum: migration.checksum,
|
|
364
|
-
applied_at: appliedAt,
|
|
365
|
-
execution_ms: executionMs,
|
|
366
|
-
success: 1,
|
|
367
|
-
installed_by: installedBy,
|
|
368
|
-
});
|
|
397
|
+
return applied;
|
|
398
|
+
};
|
|
369
399
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
filename: migration.filename,
|
|
373
|
-
checksum: migration.checksum,
|
|
374
|
-
appliedAt,
|
|
375
|
-
executionMs,
|
|
376
|
-
success: true,
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
} finally {
|
|
380
|
-
if (heldLock) {
|
|
381
|
-
await heldLock.release();
|
|
382
|
-
heldLock = null;
|
|
383
|
-
}
|
|
400
|
+
if (lockStrategy === "mysql_get_lock") {
|
|
401
|
+
return await withPinnedDbHandle(db, runLockedApply);
|
|
384
402
|
}
|
|
385
|
-
|
|
386
|
-
return applied;
|
|
403
|
+
return await runLockedApply();
|
|
387
404
|
}
|
|
388
405
|
|
|
389
406
|
async function status(): Promise<MigrationStatus> {
|
package/src/deploy/cache.ts
CHANGED
package/src/design/extract.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type * as __ManduNodeFsTypes0 from "node:fs";
|
|
1
2
|
/**
|
|
2
3
|
* DESIGN.md token extractor — scan project source for tokens that
|
|
3
4
|
* should be promoted into DESIGN.md (Issue #245 M4 §3.5 internal loop).
|
|
@@ -155,7 +156,7 @@ const SOURCE_RX = /\.(?:tsx?|jsx?|mdx?|css)$/;
|
|
|
155
156
|
async function collectFiles(root: string): Promise<string[]> {
|
|
156
157
|
const out: string[] = [];
|
|
157
158
|
async function walk(dir: string): Promise<void> {
|
|
158
|
-
let entries:
|
|
159
|
+
let entries: __ManduNodeFsTypes0.Dirent[];
|
|
159
160
|
try {
|
|
160
161
|
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
161
162
|
} catch {
|
package/src/design/patch.ts
CHANGED
|
@@ -165,26 +165,26 @@ export class MCPConnector {
|
|
|
165
165
|
try {
|
|
166
166
|
this.ws = new WebSocket(url);
|
|
167
167
|
|
|
168
|
-
this.ws.
|
|
169
|
-
clearTimeout(timeout);
|
|
170
|
-
this.setStatus('connected');
|
|
171
|
-
this.reconnectAttempts = 0;
|
|
172
|
-
resolve();
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
this.ws.
|
|
176
|
-
this.handleDisconnect();
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
this.ws.
|
|
180
|
-
clearTimeout(timeout);
|
|
181
|
-
this.handleConnectionError(new Error('WebSocket error'));
|
|
182
|
-
reject(new Error('WebSocket connection failed'));
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
this.ws.
|
|
186
|
-
this.handleMessage(event.data);
|
|
187
|
-
};
|
|
168
|
+
this.ws.addEventListener('open', () => {
|
|
169
|
+
clearTimeout(timeout);
|
|
170
|
+
this.setStatus('connected');
|
|
171
|
+
this.reconnectAttempts = 0;
|
|
172
|
+
resolve();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
this.ws.addEventListener('close', () => {
|
|
176
|
+
this.handleDisconnect();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
this.ws.addEventListener('error', () => {
|
|
180
|
+
clearTimeout(timeout);
|
|
181
|
+
this.handleConnectionError(new Error('WebSocket error'));
|
|
182
|
+
reject(new Error('WebSocket connection failed'));
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
this.ws.addEventListener('message', (event) => {
|
|
186
|
+
this.handleMessage(event.data);
|
|
187
|
+
});
|
|
188
188
|
} catch (error) {
|
|
189
189
|
clearTimeout(timeout);
|
|
190
190
|
this.handleConnectionError(
|
|
@@ -210,10 +210,10 @@ export class MCPConnector {
|
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
// 대기 중인 요청 모두 reject
|
|
213
|
-
for (const [
|
|
214
|
-
clearTimeout(pending.timeout);
|
|
215
|
-
pending.reject(new Error('Connection closed'));
|
|
216
|
-
}
|
|
213
|
+
for (const [, pending] of this.pendingRequests) {
|
|
214
|
+
clearTimeout(pending.timeout);
|
|
215
|
+
pending.reject(new Error('Connection closed'));
|
|
216
|
+
}
|
|
217
217
|
this.pendingRequests.clear();
|
|
218
218
|
|
|
219
219
|
this.setStatus('disconnected');
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import React, { useEffect, useState, useCallback, useMemo } from 'react';
|
|
9
9
|
import { createRoot, type Root } from 'react-dom/client';
|
|
10
|
-
import type { NormalizedError, DevToolsConfig
|
|
10
|
+
import type { NormalizedError, DevToolsConfig } from '../../types';
|
|
11
11
|
import { generateCSSVariables, testIds, zIndex } from '../../design-tokens';
|
|
12
12
|
import { getStateManager, type KitchenState } from '../state-manager';
|
|
13
13
|
import { getOrCreateHook } from '../../hook';
|
|
@@ -159,7 +159,7 @@ export interface ErrorsPanelProps {
|
|
|
159
159
|
export function ErrorsPanel({
|
|
160
160
|
errors,
|
|
161
161
|
onErrorClick,
|
|
162
|
-
onErrorIgnore,
|
|
162
|
+
onErrorIgnore: _onErrorIgnore,
|
|
163
163
|
onClearAll,
|
|
164
164
|
}: ErrorsPanelProps): React.ReactElement {
|
|
165
165
|
const formatTime = useCallback((timestamp: number): string => {
|
package/src/devtools/init.ts
CHANGED
|
@@ -17,10 +17,9 @@ import {
|
|
|
17
17
|
initializePersistence,
|
|
18
18
|
destroyPersistence,
|
|
19
19
|
getStateManager,
|
|
20
|
-
getPersistenceManager,
|
|
21
20
|
} from './client';
|
|
22
21
|
import { initializeWorkerManager, destroyWorkerManager } from './worker';
|
|
23
|
-
import { getContextBuilder, resetContextBuilder,
|
|
22
|
+
import { getContextBuilder, resetContextBuilder, destroyMCPConnector } from './ai';
|
|
24
23
|
|
|
25
24
|
// ============================================================================
|
|
26
25
|
// Types
|
|
@@ -134,7 +133,7 @@ export function initManduKitchen(config: DevToolsConfig = {}): KitchenInstance {
|
|
|
134
133
|
});
|
|
135
134
|
|
|
136
135
|
// 6. v1.1: AI Context Builder 초기화
|
|
137
|
-
const
|
|
136
|
+
const _contextBuilder = getContextBuilder({ config });
|
|
138
137
|
|
|
139
138
|
// 7. UI 마운트
|
|
140
139
|
mountKitchen(config);
|
|
@@ -424,7 +424,7 @@ export function createViteMiddleware(projectRoot: string) {
|
|
|
424
424
|
res.setHeader('Cache-Control', 'no-store');
|
|
425
425
|
res.statusCode = result.success ? 200 : 400;
|
|
426
426
|
res.end(JSON.stringify(result));
|
|
427
|
-
} catch
|
|
427
|
+
} catch {
|
|
428
428
|
res.statusCode = 500;
|
|
429
429
|
res.end(JSON.stringify({
|
|
430
430
|
success: false,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Prevents main thread blocking during large payload processing
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type { RedactPattern
|
|
9
|
+
import type { RedactPattern } from '../types';
|
|
10
10
|
|
|
11
11
|
// ============================================================================
|
|
12
12
|
// Worker Message Types
|
|
@@ -206,14 +206,18 @@ function handleMessage(request: WorkerRequest): WorkerResponse {
|
|
|
206
206
|
// Worker 컨텍스트에서만 실행
|
|
207
207
|
interface WorkerSelf {
|
|
208
208
|
postMessage: (message: WorkerResponse) => void;
|
|
209
|
-
|
|
209
|
+
addEventListener: (
|
|
210
|
+
type: 'message',
|
|
211
|
+
listener: (event: MessageEvent<WorkerRequest>) => void
|
|
212
|
+
) => void;
|
|
210
213
|
}
|
|
211
214
|
const workerSelf = typeof self !== 'undefined' ? (self as unknown as WorkerSelf) : undefined;
|
|
212
215
|
if (workerSelf && typeof workerSelf.postMessage === 'function') {
|
|
213
|
-
workerSelf.
|
|
216
|
+
workerSelf.addEventListener('message', (event: MessageEvent<WorkerRequest>) => {
|
|
214
217
|
const response = handleMessage(event.data);
|
|
218
|
+
// oxlint-disable-next-line require-post-message-target-origin -- Worker.postMessage has no targetOrigin parameter.
|
|
215
219
|
workerSelf.postMessage(response);
|
|
216
|
-
};
|
|
220
|
+
});
|
|
217
221
|
}
|
|
218
222
|
|
|
219
223
|
// ============================================================================
|
|
@@ -88,8 +88,8 @@ export class WorkerManager {
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
// 메시지 핸들러 설정
|
|
91
|
-
this.worker.
|
|
92
|
-
this.worker.
|
|
91
|
+
this.worker.addEventListener('message', this.handleWorkerMessage.bind(this));
|
|
92
|
+
this.worker.addEventListener('error', this.handleWorkerError.bind(this));
|
|
93
93
|
|
|
94
94
|
// Ping 테스트
|
|
95
95
|
const pingResult = await this.sendRequest({ type: 'ping', data: {} });
|
|
@@ -117,7 +117,7 @@ export class WorkerManager {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
// 대기 중인 요청 모두 reject
|
|
120
|
-
for (const [
|
|
120
|
+
for (const [, pending] of this.pendingRequests) {
|
|
121
121
|
clearTimeout(pending.timeout);
|
|
122
122
|
pending.reject(new Error('Worker terminated'));
|
|
123
123
|
}
|
|
@@ -323,6 +323,7 @@ export class WorkerManager {
|
|
|
323
323
|
|
|
324
324
|
this.pendingRequests.set(id, { resolve, reject, timeout });
|
|
325
325
|
|
|
326
|
+
// oxlint-disable-next-line require-post-message-target-origin -- Worker.postMessage has no targetOrigin parameter.
|
|
326
327
|
this.worker!.postMessage({ id, ...request });
|
|
327
328
|
});
|
|
328
329
|
}
|
package/src/error/classifier.ts
CHANGED
|
@@ -54,7 +54,7 @@ export class ErrorClassifier {
|
|
|
54
54
|
/**
|
|
55
55
|
* ValidationError 처리
|
|
56
56
|
*/
|
|
57
|
-
private createValidationError(
|
|
57
|
+
private createValidationError(_error: { errors: unknown[] }): ManduError {
|
|
58
58
|
const slotFile = this.findSlotFile();
|
|
59
59
|
|
|
60
60
|
return {
|
package/src/error/domains.ts
CHANGED
package/src/filling/sse.ts
CHANGED
|
@@ -45,11 +45,11 @@ export class SSEConnection {
|
|
|
45
45
|
controller.enqueue(this.encoder.encode(chunk));
|
|
46
46
|
}
|
|
47
47
|
this.pending = [];
|
|
48
|
-
},
|
|
49
|
-
cancel: () => {
|
|
50
|
-
this.close();
|
|
51
|
-
},
|
|
52
|
-
});
|
|
48
|
+
},
|
|
49
|
+
cancel: () => {
|
|
50
|
+
void this.close();
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
53
|
|
|
54
54
|
const headers = new Headers(DEFAULT_SSE_HEADERS);
|
|
55
55
|
if (options.headers) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RoutesManifest
|
|
1
|
+
import type { RoutesManifest } from "../spec/schema";
|
|
2
2
|
import { generateApiHandler, generatePageComponent, generateSlotLogic } from "./templates";
|
|
3
3
|
import { generateContractTypeGlue, generateContractTemplate, generateContractTypesIndex } from "./contract-glue";
|
|
4
4
|
import { createHash } from "crypto";
|
|
@@ -102,7 +102,7 @@ export interface GeneratedMap {
|
|
|
102
102
|
async function ensureDir(dirPath: string): Promise<void> {
|
|
103
103
|
try {
|
|
104
104
|
await fs.mkdir(dirPath, { recursive: true });
|
|
105
|
-
} catch
|
|
105
|
+
} catch {
|
|
106
106
|
// ignore if exists
|
|
107
107
|
}
|
|
108
108
|
}
|
package/src/guard/analyzer.ts
CHANGED
|
@@ -63,7 +63,7 @@ const DEFAULT_IMPORT_PATTERN = /^import\s+(\w+)/;
|
|
|
63
63
|
*/
|
|
64
64
|
export function extractImports(content: string): ImportInfo[] {
|
|
65
65
|
const imports: ImportInfo[] = [];
|
|
66
|
-
const
|
|
66
|
+
const _lines = content.split("\n");
|
|
67
67
|
|
|
68
68
|
// Static imports
|
|
69
69
|
let match: RegExpExecArray | null;
|
|
@@ -382,14 +382,14 @@ export function extractImportsAST(content: string): ImportInfo[] {
|
|
|
382
382
|
|
|
383
383
|
let namedImports: string[] | undefined;
|
|
384
384
|
let defaultImport: string | undefined;
|
|
385
|
-
let
|
|
386
|
-
let
|
|
385
|
+
let _namespaceImport: string | undefined;
|
|
386
|
+
let _isTypeOnly = false;
|
|
387
387
|
|
|
388
388
|
// Check for type-only import
|
|
389
389
|
if (is("keyword", "type")) {
|
|
390
390
|
consume("keyword", "type");
|
|
391
391
|
skip();
|
|
392
|
-
|
|
392
|
+
_isTypeOnly = true;
|
|
393
393
|
}
|
|
394
394
|
|
|
395
395
|
// Side-effect import: import '...'
|
|
@@ -427,7 +427,7 @@ export function extractImportsAST(content: string): ImportInfo[] {
|
|
|
427
427
|
consume("keyword", "as");
|
|
428
428
|
skip();
|
|
429
429
|
if (is("identifier")) {
|
|
430
|
-
|
|
430
|
+
_namespaceImport = consume("identifier")!.value;
|
|
431
431
|
skip();
|
|
432
432
|
}
|
|
433
433
|
}
|
|
@@ -777,7 +777,7 @@ export interface ModuleAnalysis {
|
|
|
777
777
|
/** Public API 여부 (index 파일) */
|
|
778
778
|
isPublicAPI: boolean;
|
|
779
779
|
/** 순수 타입 모듈 여부 */
|
|
780
|
-
|
|
780
|
+
_isTypeOnly: boolean;
|
|
781
781
|
}
|
|
782
782
|
|
|
783
783
|
/**
|
|
@@ -795,12 +795,12 @@ export function analyzeModuleAST(content: string, filePath: string): ModuleAnaly
|
|
|
795
795
|
// 타입만 있는 모듈인지 확인
|
|
796
796
|
const hasRuntimeExport = exports.some((e) => e.type !== "type");
|
|
797
797
|
const hasRuntimeImport = imports.some((i) => !i.statement.includes("import type"));
|
|
798
|
-
const
|
|
798
|
+
const _isTypeOnly = !hasRuntimeExport && !hasRuntimeImport && exports.length > 0;
|
|
799
799
|
|
|
800
800
|
return {
|
|
801
801
|
imports,
|
|
802
802
|
exports,
|
|
803
803
|
isPublicAPI,
|
|
804
|
-
|
|
804
|
+
_isTypeOnly,
|
|
805
805
|
};
|
|
806
806
|
}
|
|
@@ -4,7 +4,7 @@ import { GUARD_RULES } from "./rules";
|
|
|
4
4
|
import { runGuardCheck } from "./check";
|
|
5
5
|
import { generateRoutes } from "../generator/generate";
|
|
6
6
|
import { beginChange, commitChange, rollbackChange } from "../change";
|
|
7
|
-
|
|
7
|
+
|
|
8
8
|
|
|
9
9
|
export interface AutoCorrectStep {
|
|
10
10
|
ruleId: string;
|
|
@@ -51,7 +51,7 @@ export async function runAutoCorrect(
|
|
|
51
51
|
message: `Auto-correct: ${violations.length} violations`,
|
|
52
52
|
autoGenerated: true,
|
|
53
53
|
});
|
|
54
|
-
} catch
|
|
54
|
+
} catch {
|
|
55
55
|
// 이미 활성 트랜잭션이 있는 경우 그대로 진행 (사용자가 수동으로 시작한 경우)
|
|
56
56
|
// 다른 오류는 트랜잭션 없이 진행
|
|
57
57
|
}
|
|
@@ -79,7 +79,7 @@ export interface ContractViolation extends GuardViolation {
|
|
|
79
79
|
*/
|
|
80
80
|
export async function checkMissingContract(
|
|
81
81
|
manifest: RoutesManifest,
|
|
82
|
-
|
|
82
|
+
_rootDir: string
|
|
83
83
|
): Promise<ContractViolation[]> {
|
|
84
84
|
const violations: ContractViolation[] = [];
|
|
85
85
|
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
* ```
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
-
import { join,
|
|
29
|
-
import { mkdir, readdir, readFile, writeFile
|
|
28
|
+
import { join, extname } from "path";
|
|
29
|
+
import { mkdir, readdir, readFile, writeFile } from "fs/promises";
|
|
30
30
|
|
|
31
31
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
32
32
|
// Types
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type * as __ManduNodeFsTypes0 from "node:fs";
|
|
1
2
|
/**
|
|
2
3
|
* Guard rule — `DESIGN_INLINE_CLASS` (Issue #245).
|
|
3
4
|
*
|
|
@@ -92,7 +93,7 @@ async function pathExists(p: string): Promise<boolean> {
|
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
async function* walk(rootDir: string): AsyncIterable<string> {
|
|
95
|
-
let entries:
|
|
96
|
+
let entries: __ManduNodeFsTypes0.Dirent[];
|
|
96
97
|
try {
|
|
97
98
|
entries = await fs.readdir(rootDir, { withFileTypes: true });
|
|
98
99
|
} catch {
|