sonamu 0.8.25 → 0.8.26
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/dist/auth/knex-adapter.d.ts.map +1 -1
- package/dist/auth/knex-adapter.js +7 -3
- package/dist/cone/cone-generator.js +3 -3
- package/dist/database/puri-subset.test-d.js +9 -1
- package/dist/database/puri-subset.types.d.ts +1 -1
- package/dist/database/puri-subset.types.d.ts.map +1 -1
- package/dist/database/puri-subset.types.js +1 -1
- package/dist/testing/fixture-generator.js +5 -5
- package/dist/ui/ai-client.js +2 -2
- package/dist/ui/api.d.ts.map +1 -1
- package/dist/ui/api.js +14 -14
- package/dist/ui/cdd-service.d.ts +15 -18
- package/dist/ui/cdd-service.d.ts.map +1 -1
- package/dist/ui/cdd-service.js +246 -222
- package/dist/ui/cdd-types.d.ts +41 -68
- package/dist/ui/cdd-types.d.ts.map +1 -1
- package/dist/ui/cdd-types.js +2 -2
- package/dist/ui-web/assets/index-CKo0Z2Iu.css +1 -0
- package/dist/ui-web/assets/{index-CxiydzeC.js → index-DK-2aacv.js} +83 -83
- package/dist/ui-web/index.html +2 -2
- package/package.json +7 -3
- package/src/auth/knex-adapter.ts +3 -2
- package/src/cone/cone-generator.ts +2 -2
- package/src/database/puri-subset.test-d.ts +102 -0
- package/src/database/puri-subset.types.ts +1 -1
- package/src/skills/sonamu/SKILL.md +43 -0
- package/src/testing/fixture-generator.ts +4 -4
- package/src/ui/ai-client.ts +1 -1
- package/src/ui/api.ts +18 -17
- package/src/ui/cdd-service.ts +264 -254
- package/src/ui/cdd-types.ts +40 -75
- package/dist/ui-web/assets/index-BrQKU3j9.css +0 -1
package/dist/ui-web/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/sonamu-ui/setting.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>{{projectName}}: Sonamu UI</title>
|
|
8
|
-
<script type="module" crossorigin src="/sonamu-ui/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/sonamu-ui/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/sonamu-ui/assets/index-DK-2aacv.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/sonamu-ui/assets/index-CKo0Z2Iu.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sonamu",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.26",
|
|
4
4
|
"description": "Sonamu — TypeScript Fullstack API Framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -55,6 +55,10 @@
|
|
|
55
55
|
"import": "./dist/filter/index.js",
|
|
56
56
|
"types": "./dist/filter/index.d.ts"
|
|
57
57
|
},
|
|
58
|
+
"./auth/plugins": {
|
|
59
|
+
"import": "./dist/auth/plugins/index.js",
|
|
60
|
+
"types": "./dist/auth/plugins/index.d.ts"
|
|
61
|
+
},
|
|
58
62
|
"./cdd-types": {
|
|
59
63
|
"import": "./dist/ui/cdd-types.js",
|
|
60
64
|
"types": "./dist/ui/cdd-types.d.ts"
|
|
@@ -124,9 +128,9 @@
|
|
|
124
128
|
"vite": "7.3.0",
|
|
125
129
|
"vitest": "^4.0.10",
|
|
126
130
|
"@sonamu-kit/hmr-hook": "^0.4.1",
|
|
127
|
-
"@sonamu-kit/tasks": "^0.2.0",
|
|
128
131
|
"@sonamu-kit/hmr-runner": "^0.1.1",
|
|
129
|
-
"@sonamu-kit/ts-loader": "^2.1.3"
|
|
132
|
+
"@sonamu-kit/ts-loader": "^2.1.3",
|
|
133
|
+
"@sonamu-kit/tasks": "^0.2.0"
|
|
130
134
|
},
|
|
131
135
|
"devDependencies": {
|
|
132
136
|
"@biomejs/biome": "^2.3.13",
|
package/src/auth/knex-adapter.ts
CHANGED
|
@@ -26,7 +26,7 @@ export const sonamuKnexAdapter = () => {
|
|
|
26
26
|
const createCustomAdapter = (
|
|
27
27
|
getDb: () => Knex | Knex.Transaction,
|
|
28
28
|
): AdapterFactoryCustomizeAdapterCreator => {
|
|
29
|
-
return () => ({
|
|
29
|
+
return ({ getFieldName }) => ({
|
|
30
30
|
create: async ({ model, data }) => {
|
|
31
31
|
const [row] = await getDb()(model).insert(data).returning("*");
|
|
32
32
|
return row;
|
|
@@ -45,7 +45,8 @@ export const sonamuKnexAdapter = () => {
|
|
|
45
45
|
query = applyWhere(query, where);
|
|
46
46
|
}
|
|
47
47
|
if (sortBy) {
|
|
48
|
-
|
|
48
|
+
const dbField = getFieldName({ model, field: sortBy.field });
|
|
49
|
+
query = query.orderBy(dbField, sortBy.direction);
|
|
49
50
|
}
|
|
50
51
|
if (limit) {
|
|
51
52
|
query = query.limit(limit);
|
|
@@ -63,7 +63,7 @@ function getApiKey(): string {
|
|
|
63
63
|
try {
|
|
64
64
|
// Sonamu가 초기화되어 있는 경우
|
|
65
65
|
const { Sonamu } = require("../api");
|
|
66
|
-
apiKey = Sonamu.
|
|
66
|
+
apiKey = Sonamu.secrets?.anthropic_api_key;
|
|
67
67
|
} catch {
|
|
68
68
|
// Sonamu가 초기화되지 않은 경우 (테스트 등)
|
|
69
69
|
apiKey = undefined;
|
|
@@ -294,7 +294,7 @@ async function callAnthropicAPI(
|
|
|
294
294
|
});
|
|
295
295
|
|
|
296
296
|
const { text, usage } = await generateText({
|
|
297
|
-
model: anthropic("claude-sonnet-4-
|
|
297
|
+
model: anthropic("claude-sonnet-4-6"),
|
|
298
298
|
prompt,
|
|
299
299
|
});
|
|
300
300
|
|
|
@@ -160,6 +160,51 @@ describe("LoadersResult", () => {
|
|
|
160
160
|
}>();
|
|
161
161
|
});
|
|
162
162
|
|
|
163
|
+
it("중첩 로더의 as 경로도 hydrate한다", () => {
|
|
164
|
+
type IngredientLoaderQb = (
|
|
165
|
+
qbWrapper: PuriWrapper<DatabaseSchemaExtend>,
|
|
166
|
+
fromIds: number[] | string[],
|
|
167
|
+
) => MockPuri<{ id: number; name: string; refId: number }>;
|
|
168
|
+
|
|
169
|
+
type PrescriptionItemLoaderQb = (
|
|
170
|
+
qbWrapper: PuriWrapper<DatabaseSchemaExtend>,
|
|
171
|
+
fromIds: number[] | string[],
|
|
172
|
+
) => MockPuri<{
|
|
173
|
+
id: number;
|
|
174
|
+
test__id: number;
|
|
175
|
+
test__name: string;
|
|
176
|
+
refId: number;
|
|
177
|
+
}>;
|
|
178
|
+
|
|
179
|
+
type Loaders = [
|
|
180
|
+
{
|
|
181
|
+
as: "items";
|
|
182
|
+
refId: "id";
|
|
183
|
+
qb: PrescriptionItemLoaderQb;
|
|
184
|
+
loaders: [
|
|
185
|
+
{
|
|
186
|
+
as: "test__items";
|
|
187
|
+
refId: "test__id";
|
|
188
|
+
qb: IngredientLoaderQb;
|
|
189
|
+
},
|
|
190
|
+
];
|
|
191
|
+
},
|
|
192
|
+
];
|
|
193
|
+
type Result = LoadersResult<Loaders>;
|
|
194
|
+
|
|
195
|
+
const result = {} as Result;
|
|
196
|
+
expectTypeOf(result).toEqualTypeOf<{
|
|
197
|
+
items: {
|
|
198
|
+
id: number;
|
|
199
|
+
test: {
|
|
200
|
+
id: number;
|
|
201
|
+
name: string;
|
|
202
|
+
items: { id: number; name: string }[];
|
|
203
|
+
};
|
|
204
|
+
}[];
|
|
205
|
+
}>();
|
|
206
|
+
});
|
|
207
|
+
|
|
163
208
|
it("여러 로더를 처리한다", () => {
|
|
164
209
|
type ProjectLoader = {
|
|
165
210
|
as: "projects";
|
|
@@ -351,6 +396,63 @@ describe("InferAllSubsets", () => {
|
|
|
351
396
|
};
|
|
352
397
|
}>();
|
|
353
398
|
});
|
|
399
|
+
|
|
400
|
+
it("중첩 로더 경로 alias를 포함한 Subset 결과를 추론한다", () => {
|
|
401
|
+
type SubsetFnA = (qbWrapper: PuriWrapper<DatabaseSchemaExtend>) => MockPuri<{
|
|
402
|
+
id: number;
|
|
403
|
+
root__id: number;
|
|
404
|
+
}>;
|
|
405
|
+
type SubsetQueries = {
|
|
406
|
+
A: SubsetFnA;
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
type RootItemLoader = {
|
|
410
|
+
as: "root__items";
|
|
411
|
+
refId: "root__id";
|
|
412
|
+
qb: (
|
|
413
|
+
qbWrapper: PuriWrapper<DatabaseSchemaExtend>,
|
|
414
|
+
fromIds: number[] | string[],
|
|
415
|
+
) => MockPuri<{
|
|
416
|
+
id: number;
|
|
417
|
+
test__id: number;
|
|
418
|
+
test__name: string;
|
|
419
|
+
refId: number;
|
|
420
|
+
}>;
|
|
421
|
+
loaders: [
|
|
422
|
+
{
|
|
423
|
+
as: "test__inner_items";
|
|
424
|
+
refId: "test__id";
|
|
425
|
+
qb: (
|
|
426
|
+
qbWrapper: PuriWrapper<DatabaseSchemaExtend>,
|
|
427
|
+
fromIds: number[] | string[],
|
|
428
|
+
) => MockPuri<{ id: number; name: string; refId: number }>;
|
|
429
|
+
},
|
|
430
|
+
];
|
|
431
|
+
};
|
|
432
|
+
type LoaderQueries = {
|
|
433
|
+
A: [RootItemLoader];
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
type Result = InferAllSubsets<SubsetQueries, LoaderQueries>;
|
|
437
|
+
|
|
438
|
+
const result = {} as Result;
|
|
439
|
+
expectTypeOf(result).toEqualTypeOf<{
|
|
440
|
+
A: {
|
|
441
|
+
id: number;
|
|
442
|
+
root: {
|
|
443
|
+
id: number;
|
|
444
|
+
items: {
|
|
445
|
+
id: number;
|
|
446
|
+
test: {
|
|
447
|
+
id: number;
|
|
448
|
+
name: string;
|
|
449
|
+
inner_items: { id: number; name: string }[];
|
|
450
|
+
};
|
|
451
|
+
}[];
|
|
452
|
+
};
|
|
453
|
+
};
|
|
454
|
+
}>();
|
|
455
|
+
});
|
|
354
456
|
});
|
|
355
457
|
|
|
356
458
|
describe("서브셋이 여러 개인 경우", () => {
|
|
@@ -134,7 +134,7 @@ export type LoadersResult<TLoaders extends GenericPuriLoader[]> = Expand<{
|
|
|
134
134
|
* 기본 결과와 Loader 결과 병합
|
|
135
135
|
*/
|
|
136
136
|
type WithLoaders<TBase, TLoaders> = TLoaders extends GenericPuriLoader[]
|
|
137
|
-
? Expand<TBase & LoadersResult<TLoaders
|
|
137
|
+
? Expand<Hydrate<TBase & LoadersResult<TLoaders>>>
|
|
138
138
|
: TBase;
|
|
139
139
|
|
|
140
140
|
// ============================================================================
|
|
@@ -187,6 +187,49 @@ Documents are persisted as files, so project context is preserved even when the
|
|
|
187
187
|
| **Troubleshooting → skill contribution** | **skill-contribution** |
|
|
188
188
|
| **Framework bug / constraint response** | **framework-change** |
|
|
189
189
|
|
|
190
|
+
## Skill Selection by CDD Claim Type
|
|
191
|
+
|
|
192
|
+
**For Planners:** Use this table to populate `required_skills` in each Claim blueprint.
|
|
193
|
+
|
|
194
|
+
### `surface` — Entity / Schema / Shared Type Work
|
|
195
|
+
|
|
196
|
+
| Case | Required Skills |
|
|
197
|
+
|------|----------------|
|
|
198
|
+
| Add new entity | `entity-basic`, `entity-relations`, `migration`, `cone`, `fixture-cli`, `i18n` |
|
|
199
|
+
| Define subset | `entity-basic`, `subset` |
|
|
200
|
+
| Add auth entity | `auth`, `auth-migration` |
|
|
201
|
+
| Add auth plugin | `auth-plugins` |
|
|
202
|
+
| Add batch save structure | `upsert` |
|
|
203
|
+
| Add vector search structure | `vector`, `entity-basic` |
|
|
204
|
+
| Resolve scaffolding errors | `scaffolding` |
|
|
205
|
+
|
|
206
|
+
### `test` — Test Writing
|
|
207
|
+
|
|
208
|
+
| Case | Required Skills |
|
|
209
|
+
|------|----------------|
|
|
210
|
+
| Business logic tests | `testing`, `testing-devrunner` |
|
|
211
|
+
| Tests requiring fixtures | `testing`, `fixture-cli` |
|
|
212
|
+
| Debugging / tracing | `naite` |
|
|
213
|
+
|
|
214
|
+
### `implement` — Business Logic Implementation
|
|
215
|
+
|
|
216
|
+
| Case | Required Skills |
|
|
217
|
+
|------|----------------|
|
|
218
|
+
| Model CRUD implementation | `model`, `puri` |
|
|
219
|
+
| API endpoint implementation | `api`, `model` |
|
|
220
|
+
| File upload implementation | `api`, `framework-change` |
|
|
221
|
+
| SQL query writing | `puri` |
|
|
222
|
+
| Internationalization | `i18n` |
|
|
223
|
+
| Auth guard / session handling | `auth` |
|
|
224
|
+
| Auth plugin implementation | `auth-plugins` |
|
|
225
|
+
| Batch save implementation | `upsert`, `model` |
|
|
226
|
+
| AI Agent implementation | `ai-agents` |
|
|
227
|
+
| Background job / scheduling | `tasks` |
|
|
228
|
+
| Vector search implementation | `vector`, `puri` |
|
|
229
|
+
| Frontend integration | `frontend`, `scaffolding` |
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
190
233
|
## Command Execution Path
|
|
191
234
|
|
|
192
235
|
All `pnpm` commands are run from the **`packages/api`** directory.
|
|
@@ -57,7 +57,7 @@ export class FixtureGenerator {
|
|
|
57
57
|
locale: options?.locale || "ko",
|
|
58
58
|
useLLM: options?.useLLM || false,
|
|
59
59
|
enableLLMCache: options?.enableLLMCache !== false,
|
|
60
|
-
llmModel: options?.llmModel || "claude-sonnet-4-
|
|
60
|
+
llmModel: options?.llmModel || "claude-sonnet-4-6",
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
63
|
|
|
@@ -825,7 +825,7 @@ export class FixtureGenerator {
|
|
|
825
825
|
const { generateText } = await import("ai");
|
|
826
826
|
|
|
827
827
|
const rowResponse = await generateText({
|
|
828
|
-
model: createAnthropic({ apiKey })(this.options.llmModel || "claude-sonnet-4-
|
|
828
|
+
model: createAnthropic({ apiKey })(this.options.llmModel || "claude-sonnet-4-6"),
|
|
829
829
|
prompt: this.buildRowLLMPrompt(llmProps, entity),
|
|
830
830
|
});
|
|
831
831
|
if (!rowResponse || typeof rowResponse.text !== "string") {
|
|
@@ -869,7 +869,7 @@ export class FixtureGenerator {
|
|
|
869
869
|
const { generateText } = await import("ai");
|
|
870
870
|
|
|
871
871
|
const singleResponse = await generateText({
|
|
872
|
-
model: createAnthropic({ apiKey })(this.options.llmModel || "claude-sonnet-4-
|
|
872
|
+
model: createAnthropic({ apiKey })(this.options.llmModel || "claude-sonnet-4-6"),
|
|
873
873
|
prompt: this.buildLLMPrompt(fixtureHint, prop, entity),
|
|
874
874
|
});
|
|
875
875
|
if (!singleResponse || typeof singleResponse.text !== "string") {
|
|
@@ -1208,7 +1208,7 @@ Rules:
|
|
|
1208
1208
|
|
|
1209
1209
|
try {
|
|
1210
1210
|
const { Sonamu } = require("../api");
|
|
1211
|
-
apiKey = Sonamu.
|
|
1211
|
+
apiKey = Sonamu.secrets?.anthropic_api_key;
|
|
1212
1212
|
} catch {
|
|
1213
1213
|
// Sonamu가 초기화되지 않은 경우 (테스트 환경 등)
|
|
1214
1214
|
}
|
package/src/ui/ai-client.ts
CHANGED
|
@@ -34,7 +34,7 @@ class AIClient {
|
|
|
34
34
|
const { anthropic } = await import("@ai-sdk/anthropic");
|
|
35
35
|
const aiModule = await import("ai");
|
|
36
36
|
this.aiSdk = { ...aiModule, anthropic };
|
|
37
|
-
this.model = anthropic("claude-sonnet-4-
|
|
37
|
+
this.model = anthropic("claude-sonnet-4-6");
|
|
38
38
|
} catch (error) {
|
|
39
39
|
console.warn(
|
|
40
40
|
"AI SDK packages not installed. Install @ai-sdk/anthropic and ai to use AI features.",
|
package/src/ui/api.ts
CHANGED
|
@@ -37,15 +37,16 @@ import {
|
|
|
37
37
|
} from "../types/types";
|
|
38
38
|
import { nonNullable } from "../utils/utils";
|
|
39
39
|
import { setAiApi } from "./ai-api";
|
|
40
|
+
import type { CddAddRuleRequest } from "./cdd-service";
|
|
40
41
|
import {
|
|
42
|
+
addRule,
|
|
41
43
|
editContent,
|
|
42
|
-
|
|
44
|
+
getAcList,
|
|
43
45
|
getCddTree,
|
|
44
|
-
|
|
45
|
-
listSchemas,
|
|
46
|
+
listRules,
|
|
46
47
|
openSourceFile,
|
|
47
48
|
readContent,
|
|
48
|
-
|
|
49
|
+
readRule,
|
|
49
50
|
} from "./cdd-service";
|
|
50
51
|
|
|
51
52
|
export async function sonamuUIApiPlugin(fastify: FastifyInstance) {
|
|
@@ -1404,10 +1405,6 @@ export async function sonamuUIApiPlugin(fastify: FastifyInstance) {
|
|
|
1404
1405
|
});
|
|
1405
1406
|
|
|
1406
1407
|
// CDD API
|
|
1407
|
-
server.get("/api/cdd/dashboard", async () => {
|
|
1408
|
-
return getDashboard();
|
|
1409
|
-
});
|
|
1410
|
-
|
|
1411
1408
|
server.get("/api/cdd/tree", async () => {
|
|
1412
1409
|
return getCddTree();
|
|
1413
1410
|
});
|
|
@@ -1428,19 +1425,23 @@ export async function sonamuUIApiPlugin(fastify: FastifyInstance) {
|
|
|
1428
1425
|
return { success: true };
|
|
1429
1426
|
});
|
|
1430
1427
|
|
|
1431
|
-
// CDD
|
|
1432
|
-
server.get("/api/cdd/
|
|
1433
|
-
return
|
|
1428
|
+
// CDD Rules API
|
|
1429
|
+
server.get("/api/cdd/rules", async () => {
|
|
1430
|
+
return listRules();
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1433
|
+
server.post<{ Body: { ruleKey: string } }>("/api/cdd/readRule", async (request) => {
|
|
1434
|
+
const { ruleKey } = request.body;
|
|
1435
|
+
return readRule(ruleKey);
|
|
1434
1436
|
});
|
|
1435
1437
|
|
|
1436
|
-
server.post<{ Body:
|
|
1437
|
-
|
|
1438
|
-
return readSchema(schemaKey);
|
|
1438
|
+
server.post<{ Body: CddAddRuleRequest }>("/api/cdd/addRule", async (request) => {
|
|
1439
|
+
return addRule(request.body);
|
|
1439
1440
|
});
|
|
1440
1441
|
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
return
|
|
1442
|
+
// CDD AC API
|
|
1443
|
+
server.get("/api/cdd/ac", async () => {
|
|
1444
|
+
return getAcList();
|
|
1444
1445
|
});
|
|
1445
1446
|
|
|
1446
1447
|
// ui-web 빌드 파일 서빙
|