sonamu 0.8.24 → 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/api/__tests__/config.test.js +189 -0
- package/dist/api/config.d.ts.map +1 -1
- package/dist/api/config.js +7 -2
- package/dist/api/sonamu.d.ts.map +1 -1
- package/dist/api/sonamu.js +14 -10
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +2 -1
- package/dist/auth/knex-adapter.d.ts +23 -0
- package/dist/auth/knex-adapter.d.ts.map +1 -0
- package/dist/auth/knex-adapter.js +163 -0
- package/dist/auth/plugins/wrappers/admin.d.ts +2 -2
- package/dist/bin/__tests__/ts-loader-register.test.js +45 -0
- package/dist/bin/cli.js +47 -9
- package/dist/bin/ts-loader-register.js +3 -29
- package/dist/bin/ts-loader-registration.d.ts +2 -0
- package/dist/bin/ts-loader-registration.d.ts.map +1 -0
- package/dist/bin/ts-loader-registration.js +42 -0
- 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 +6 -2
- package/src/api/__tests__/config.test.ts +225 -0
- package/src/api/config.ts +10 -4
- package/src/api/sonamu.ts +16 -13
- package/src/auth/index.ts +1 -0
- package/src/auth/knex-adapter.ts +208 -0
- package/src/bin/__tests__/ts-loader-register.test.ts +62 -0
- package/src/bin/cli.ts +52 -9
- package/src/bin/ts-loader-register.ts +2 -32
- package/src/bin/ts-loader-registration.ts +55 -0
- 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/commands/sonamu-skills.md +20 -0
- package/src/skills/sonamu/SKILL.md +179 -137
- package/src/skills/sonamu/ai-agents.md +69 -69
- package/src/skills/sonamu/api.md +147 -147
- package/src/skills/sonamu/auth-migration.md +220 -220
- package/src/skills/sonamu/auth-plugins.md +83 -83
- package/src/skills/sonamu/auth.md +106 -106
- package/src/skills/sonamu/cdd.md +65 -200
- package/src/skills/sonamu/cone.md +138 -138
- package/src/skills/sonamu/config.md +191 -191
- package/src/skills/sonamu/create-sonamu.md +66 -66
- package/src/skills/sonamu/database.md +158 -158
- package/src/skills/sonamu/entity-basic.md +292 -293
- package/src/skills/sonamu/entity-relations.md +246 -246
- package/src/skills/sonamu/entity-validation-checklist.md +124 -124
- package/src/skills/sonamu/fixture-cli.md +231 -231
- package/src/skills/sonamu/framework-change.md +37 -37
- package/src/skills/sonamu/frontend.md +223 -223
- package/src/skills/sonamu/i18n.md +82 -82
- package/src/skills/sonamu/migration.md +77 -77
- package/src/skills/sonamu/model.md +222 -222
- package/src/skills/sonamu/naite.md +86 -86
- package/src/skills/sonamu/project-init.md +228 -228
- package/src/skills/sonamu/puri.md +122 -122
- package/src/skills/sonamu/scaffolding.md +154 -154
- package/src/skills/sonamu/skill-contribution.md +124 -124
- package/src/skills/sonamu/subset.md +46 -46
- package/src/skills/sonamu/tasks.md +82 -82
- package/src/skills/sonamu/testing-devrunner.md +147 -147
- package/src/skills/sonamu/testing.md +673 -673
- package/src/skills/sonamu/upsert.md +79 -79
- package/src/skills/sonamu/vector.md +67 -67
- 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/src/skills/sonamu/workflow.md +0 -317
|
@@ -1,94 +1,94 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sonamu-upsert
|
|
3
|
-
description:
|
|
3
|
+
description: Saving complex relational data with Sonamu UpsertBuilder. ubRegister, ubUpsert, insertOnly, updateBatch patterns, FK ordering, cleanOrphans. Use when saving related data with foreign key dependencies.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# UpsertBuilder
|
|
7
7
|
|
|
8
|
-
## UBRef
|
|
8
|
+
## UBRef Type
|
|
9
9
|
|
|
10
|
-
`ubRegister()
|
|
10
|
+
The reference object returned by `ubRegister()`:
|
|
11
11
|
|
|
12
12
|
```typescript
|
|
13
13
|
type UBRef = {
|
|
14
|
-
uuid: string; //
|
|
15
|
-
of: string; //
|
|
16
|
-
use?: string; //
|
|
14
|
+
uuid: string; // unique identifier
|
|
15
|
+
of: string; // table name
|
|
16
|
+
use?: string; // field to reference (default: "id")
|
|
17
17
|
};
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
##
|
|
20
|
+
## Basic Pattern
|
|
21
21
|
|
|
22
22
|
```typescript
|
|
23
23
|
const wdb = this.getPuri("w");
|
|
24
24
|
|
|
25
|
-
//
|
|
25
|
+
// Register data (returns UBRef)
|
|
26
26
|
const userRef = wdb.ubRegister("users", { email: "john@test.com", username: "john" });
|
|
27
27
|
|
|
28
|
-
//
|
|
28
|
+
// Use UBRef in related data
|
|
29
29
|
wdb.ubRegister("employees", { user_id: userRef, department_id: deptId });
|
|
30
30
|
|
|
31
|
-
//
|
|
31
|
+
// Save in order inside a transaction
|
|
32
32
|
return wdb.transaction(async (trx) => {
|
|
33
|
-
await trx.ubUpsert("users"); //
|
|
34
|
-
return trx.ubUpsert("employees"); //
|
|
33
|
+
await trx.ubUpsert("users"); // Save first (referenced by FK)
|
|
34
|
+
return trx.ubUpsert("employees"); // Save after (uses FK)
|
|
35
35
|
});
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
## CRITICAL:
|
|
38
|
+
## CRITICAL: All Required Fields Must Be Included
|
|
39
39
|
|
|
40
|
-
**ubUpsert
|
|
40
|
+
**ubUpsert uses PostgreSQL's `ON CONFLICT ... DO UPDATE` query.**
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
Even when updating, **all required fields (fields with NOT NULL constraints)** must be included.
|
|
43
43
|
|
|
44
44
|
```typescript
|
|
45
|
-
// BAD -
|
|
45
|
+
// BAD - missing required field
|
|
46
46
|
wdb.ubRegister("posts", {
|
|
47
47
|
id: 1,
|
|
48
48
|
title: "Updated Title",
|
|
49
|
-
// content
|
|
49
|
+
// content required field missing! → ON CONFLICT UPDATE tries to set NULL → DB error
|
|
50
50
|
});
|
|
51
51
|
// Error: null value in column "content" violates not-null constraint
|
|
52
52
|
|
|
53
|
-
// GOOD -
|
|
53
|
+
// GOOD - all required fields included
|
|
54
54
|
wdb.ubRegister("posts", {
|
|
55
55
|
id: 1,
|
|
56
56
|
title: "Updated Title",
|
|
57
|
-
content: "Updated Content", //
|
|
58
|
-
author_id: 1, // FK
|
|
57
|
+
content: "Updated Content", // required field included!
|
|
58
|
+
author_id: 1, // FK also included if required!
|
|
59
59
|
});
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
1. entity.json
|
|
64
|
-
2. `nullable: true
|
|
65
|
-
3. `id`, `created_at`, `dbDefault`
|
|
62
|
+
**How to identify required fields**:
|
|
63
|
+
1. Check props in entity.json
|
|
64
|
+
2. Fields without `nullable: true` = required fields
|
|
65
|
+
3. `id`, `created_at`, fields with `dbDefault` can be omitted
|
|
66
66
|
|
|
67
67
|
```json
|
|
68
|
-
// entity.json
|
|
68
|
+
// entity.json example
|
|
69
69
|
{
|
|
70
70
|
"props": [
|
|
71
|
-
{ "name": "id", "type": "integer" }, //
|
|
72
|
-
{ "name": "title", "type": "string" }, //
|
|
73
|
-
{ "name": "content", "type": "string" }, //
|
|
74
|
-
{ "name": "category", "type": "string", "nullable": true }, //
|
|
75
|
-
{ "name": "created_at", "type": "date", "dbDefault": "CURRENT_TIMESTAMP" } //
|
|
71
|
+
{ "name": "id", "type": "integer" }, // can be omitted
|
|
72
|
+
{ "name": "title", "type": "string" }, // required! (no nullable)
|
|
73
|
+
{ "name": "content", "type": "string" }, // required! (no nullable)
|
|
74
|
+
{ "name": "category", "type": "string", "nullable": true }, // optional
|
|
75
|
+
{ "name": "created_at", "type": "date", "dbDefault": "CURRENT_TIMESTAMP" } // can be omitted
|
|
76
76
|
]
|
|
77
77
|
}
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
##
|
|
80
|
+
## Save Order (Important!)
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
Save the table referenced by FK first:
|
|
83
83
|
|
|
84
84
|
```typescript
|
|
85
|
-
await trx.ubUpsert("companies"); // 1.
|
|
86
|
-
await trx.ubUpsert("departments"); // 2. company_id
|
|
87
|
-
await trx.ubUpsert("users"); // 3.
|
|
88
|
-
await trx.ubUpsert("employees"); // 4. user_id, department_id
|
|
85
|
+
await trx.ubUpsert("companies"); // 1. No dependencies
|
|
86
|
+
await trx.ubUpsert("departments"); // 2. Needs company_id
|
|
87
|
+
await trx.ubUpsert("users"); // 3. No dependencies
|
|
88
|
+
await trx.ubUpsert("employees"); // 4. Needs user_id, department_id
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
## Model save
|
|
91
|
+
## Model save Pattern
|
|
92
92
|
|
|
93
93
|
```typescript
|
|
94
94
|
@api({ httpMethod: "POST" })
|
|
@@ -102,25 +102,25 @@ async save(spa: UserSaveParams[]): Promise<number[]> {
|
|
|
102
102
|
}
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
-
##
|
|
105
|
+
## Saving Related Data
|
|
106
106
|
|
|
107
107
|
```typescript
|
|
108
108
|
await this.getPuri("w").transaction(async (trx) => {
|
|
109
|
-
// User
|
|
109
|
+
// Register User
|
|
110
110
|
const userRef = trx.ubRegister("users", {
|
|
111
111
|
email: data.email,
|
|
112
112
|
username: data.username,
|
|
113
113
|
password: bcrypt.hashSync(data.password, 10),
|
|
114
114
|
});
|
|
115
115
|
|
|
116
|
-
// Employee
|
|
116
|
+
// Register Employee (using userRef)
|
|
117
117
|
trx.ubRegister("employees", {
|
|
118
118
|
user_id: userRef,
|
|
119
119
|
department_id: data.departmentId,
|
|
120
120
|
salary: data.salary,
|
|
121
121
|
});
|
|
122
122
|
|
|
123
|
-
//
|
|
123
|
+
// Save in order
|
|
124
124
|
await trx.ubUpsert("users");
|
|
125
125
|
const [employeeId] = await trx.ubUpsert("employees");
|
|
126
126
|
return employeeId;
|
|
@@ -130,16 +130,16 @@ await this.getPuri("w").transaction(async (trx) => {
|
|
|
130
130
|
## Upsert (Insert or Update)
|
|
131
131
|
|
|
132
132
|
```typescript
|
|
133
|
-
//
|
|
133
|
+
// INSERT when no id
|
|
134
134
|
wdb.ubRegister("users", { email: "new@test.com", username: "new" });
|
|
135
135
|
|
|
136
|
-
// id
|
|
136
|
+
// UPDATE when id is present
|
|
137
137
|
wdb.ubRegister("users", { id: 1, email: "updated@test.com" });
|
|
138
138
|
```
|
|
139
139
|
|
|
140
|
-
|
|
140
|
+
**Conflict handling**: If the Entity has a unique index, automatically pre-fetches to populate the existing record's id, then performs UPDATE
|
|
141
141
|
|
|
142
|
-
## ManyToMany
|
|
142
|
+
## ManyToMany Relationships
|
|
143
143
|
|
|
144
144
|
```typescript
|
|
145
145
|
await wdb.transaction(async (trx) => {
|
|
@@ -157,52 +157,52 @@ await wdb.transaction(async (trx) => {
|
|
|
157
157
|
});
|
|
158
158
|
```
|
|
159
159
|
|
|
160
|
-
##
|
|
160
|
+
## Self-Reference
|
|
161
161
|
|
|
162
|
-
|
|
162
|
+
In hierarchical structures (e.g. categories, org charts), self-referential relationships are automatically processed level by level:
|
|
163
163
|
|
|
164
164
|
```typescript
|
|
165
165
|
await wdb.transaction(async (trx) => {
|
|
166
|
-
//
|
|
166
|
+
// Root category
|
|
167
167
|
const rootRef = trx.ubRegister("categories", { name: "Root", parent_id: null });
|
|
168
168
|
|
|
169
|
-
//
|
|
169
|
+
// Child category (references rootRef)
|
|
170
170
|
const childRef = trx.ubRegister("categories", { name: "Child", parent_id: rootRef });
|
|
171
171
|
|
|
172
|
-
//
|
|
172
|
+
// Grandchild category (references childRef)
|
|
173
173
|
trx.ubRegister("categories", { name: "Grandchild", parent_id: childRef });
|
|
174
174
|
|
|
175
|
-
//
|
|
175
|
+
// Internally processed level by level (Root → Child → Grandchild)
|
|
176
176
|
await trx.ubUpsert("categories");
|
|
177
177
|
});
|
|
178
178
|
```
|
|
179
179
|
|
|
180
|
-
## insertOnly (INSERT
|
|
180
|
+
## insertOnly (INSERT Only)
|
|
181
181
|
|
|
182
|
-
|
|
182
|
+
Perform INSERT without UPDATE:
|
|
183
183
|
|
|
184
184
|
```typescript
|
|
185
185
|
await trx.insertOnly("logs", { chunkSize: 1000 });
|
|
186
186
|
```
|
|
187
187
|
|
|
188
|
-
## updateBatch (
|
|
188
|
+
## updateBatch (Batch Update)
|
|
189
189
|
|
|
190
|
-
|
|
190
|
+
Bulk UPDATE operations:
|
|
191
191
|
|
|
192
192
|
```typescript
|
|
193
|
-
//
|
|
193
|
+
// Register multiple records
|
|
194
194
|
wdb.ubRegister("users", { id: 1, status: "active" });
|
|
195
195
|
wdb.ubRegister("users", { id: 2, status: "active" });
|
|
196
196
|
wdb.ubRegister("users", { id: 3, status: "inactive" });
|
|
197
197
|
|
|
198
198
|
await wdb.transaction(async (trx) => {
|
|
199
199
|
await trx.updateBatch("users", {
|
|
200
|
-
chunkSize: 500, //
|
|
201
|
-
where: "id", // WHERE
|
|
200
|
+
chunkSize: 500, // batch size (default: 500)
|
|
201
|
+
where: "id", // WHERE condition column (default: "id")
|
|
202
202
|
});
|
|
203
203
|
});
|
|
204
204
|
|
|
205
|
-
//
|
|
205
|
+
// Composite key for WHERE condition
|
|
206
206
|
await trx.updateBatch("user_settings", {
|
|
207
207
|
where: ["user_id", "setting_key"],
|
|
208
208
|
});
|
|
@@ -210,19 +210,19 @@ await trx.updateBatch("user_settings", {
|
|
|
210
210
|
|
|
211
211
|
## UpsertOptions
|
|
212
212
|
|
|
213
|
-
`ubUpsert()
|
|
213
|
+
Options for `ubUpsert()`:
|
|
214
214
|
|
|
215
215
|
```typescript
|
|
216
216
|
type UpsertOptions = {
|
|
217
|
-
chunkSize?: number; //
|
|
218
|
-
cleanOrphans?: string | string[]; //
|
|
219
|
-
inherit?: string[]; //
|
|
217
|
+
chunkSize?: number; // batch size
|
|
218
|
+
cleanOrphans?: string | string[]; // FK column(s) to use as basis for deleting orphan records
|
|
219
|
+
inherit?: string[]; // columns to preserve existing values on UPDATE
|
|
220
220
|
};
|
|
221
221
|
```
|
|
222
222
|
|
|
223
223
|
### chunkSize
|
|
224
224
|
|
|
225
|
-
|
|
225
|
+
Specify batch size for large data processing:
|
|
226
226
|
|
|
227
227
|
```typescript
|
|
228
228
|
await trx.ubUpsert("logs", { chunkSize: 1000 });
|
|
@@ -230,15 +230,15 @@ await trx.ubUpsert("logs", { chunkSize: 1000 });
|
|
|
230
230
|
|
|
231
231
|
### cleanOrphans
|
|
232
232
|
|
|
233
|
-
|
|
233
|
+
Automatically delete orphan records based on FK:
|
|
234
234
|
|
|
235
235
|
```typescript
|
|
236
|
-
//
|
|
236
|
+
// Single FK
|
|
237
237
|
await trx.ubUpsert("order_items", {
|
|
238
|
-
cleanOrphans: "order_id", // order_id
|
|
238
|
+
cleanOrphans: "order_id", // delete records with the same order_id that were not upserted this time
|
|
239
239
|
});
|
|
240
240
|
|
|
241
|
-
//
|
|
241
|
+
// Composite FK
|
|
242
242
|
await trx.ubUpsert("project_members", {
|
|
243
243
|
cleanOrphans: ["project_id", "team_id"],
|
|
244
244
|
});
|
|
@@ -246,31 +246,31 @@ await trx.ubUpsert("project_members", {
|
|
|
246
246
|
|
|
247
247
|
### inherit
|
|
248
248
|
|
|
249
|
-
|
|
249
|
+
Preserve existing values for specific columns on UPDATE:
|
|
250
250
|
|
|
251
251
|
```typescript
|
|
252
252
|
await trx.ubUpsert("users", {
|
|
253
|
-
inherit: ["created_at", "password"], //
|
|
253
|
+
inherit: ["created_at", "password"], // these columns are excluded from UPDATE
|
|
254
254
|
});
|
|
255
255
|
```
|
|
256
256
|
|
|
257
|
-
## ubUpsertOrInsert (
|
|
257
|
+
## ubUpsertOrInsert (Conditional Mode)
|
|
258
258
|
|
|
259
|
-
upsert
|
|
259
|
+
Select upsert or insert mode at runtime.
|
|
260
260
|
|
|
261
261
|
```typescript
|
|
262
|
-
await trx.ubUpsertOrInsert("logs", "insert"); // INSERT
|
|
263
|
-
await trx.ubUpsertOrInsert("users", "upsert"); // UPSERT (
|
|
262
|
+
await trx.ubUpsertOrInsert("logs", "insert"); // INSERT only
|
|
263
|
+
await trx.ubUpsertOrInsert("users", "upsert"); // UPSERT (default)
|
|
264
264
|
await trx.ubUpsertOrInsert("users", "upsert", { cleanOrphans: "team_id" });
|
|
265
265
|
```
|
|
266
266
|
|
|
267
|
-
|
|
|
267
|
+
| Parameter | Type | Description |
|
|
268
268
|
|----------|------|------|
|
|
269
|
-
| `tableName` | string |
|
|
270
|
-
| `mode` | `"upsert"` \| `"insert"` |
|
|
271
|
-
| `options` | `UpsertOptions` | chunkSize, cleanOrphans, inherit (ubUpsert
|
|
269
|
+
| `tableName` | string | table name |
|
|
270
|
+
| `mode` | `"upsert"` \| `"insert"` | operation mode |
|
|
271
|
+
| `options` | `UpsertOptions` | chunkSize, cleanOrphans, inherit (same as ubUpsert) |
|
|
272
272
|
|
|
273
|
-
`mode: "insert"
|
|
273
|
+
When `mode: "insert"`, unlike `insertOnly`, `UpsertOptions` (cleanOrphans, inherit) can be used.
|
|
274
274
|
|
|
275
275
|
## Rules
|
|
276
276
|
|
|
@@ -1,43 +1,43 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sonamu-vector
|
|
3
|
-
description: pgvector
|
|
3
|
+
description: pgvector-based vector search. Embedding (Voyage AI/OpenAI), Chunking, hybrid search (Vector+FTS) support. Use when implementing vector search, semantic search, or text embedding features.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# Vector Search Guide
|
|
7
7
|
|
|
8
|
-
Sonamu
|
|
8
|
+
Sonamu supports pgvector-based vector search. It integrates both Voyage AI and OpenAI embedding providers, and also supports hybrid search (Vector + Full-Text Search).
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
**Source code:** `modules/sonamu/src/vector/`
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
-
##
|
|
14
|
+
## Structure
|
|
15
15
|
|
|
16
|
-
|
|
|
16
|
+
| File | Role |
|
|
17
17
|
|------|------|
|
|
18
|
-
| `types.ts` |
|
|
19
|
-
| `config.ts` |
|
|
20
|
-
| `embedding.ts` | Embedding
|
|
21
|
-
| `chunking.ts` |
|
|
18
|
+
| `types.ts` | Full type definitions (EmbeddingProvider, VectorSearchResult, VectorConfig, etc.) |
|
|
19
|
+
| `config.ts` | Default configuration values + `createVectorConfig()` helper |
|
|
20
|
+
| `embedding.ts` | Embedding client (Voyage AI and OpenAI integration) |
|
|
21
|
+
| `chunking.ts` | Text chunking (splitting long documents) |
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
-
##
|
|
25
|
+
## Embedding Providers
|
|
26
26
|
|
|
27
|
-
|
|
|
27
|
+
| Provider | Model | Dimensions | maxTokens | batchSize | Package |
|
|
28
28
|
|-----------|------|------|-----------|-----------|--------|
|
|
29
29
|
| `voyage` | `voyage-3` | 1024 | 32000 | 128 | `voyageai` |
|
|
30
30
|
| `openai` | `text-embedding-3-small` | 1536 | 8191 | 100 | `@ai-sdk/openai` |
|
|
31
31
|
|
|
32
|
-
### API
|
|
32
|
+
### API Key Configuration
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
|
-
#
|
|
35
|
+
# Environment variables
|
|
36
36
|
export VOYAGE_API_KEY=pa-...
|
|
37
37
|
export OPENAI_API_KEY=sk-...
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
Or in `sonamu.config.ts`:
|
|
41
41
|
```typescript
|
|
42
42
|
export default defineConfig({
|
|
43
43
|
secret: {
|
|
@@ -47,80 +47,80 @@ export default defineConfig({
|
|
|
47
47
|
});
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
Key priority: `Sonamu.secrets.voyage_api_key` → `process.env.VOYAGE_API_KEY`
|
|
51
51
|
|
|
52
52
|
---
|
|
53
53
|
|
|
54
|
-
## Embedding
|
|
54
|
+
## Embedding Usage
|
|
55
55
|
|
|
56
56
|
```typescript
|
|
57
57
|
import { Embedding } from "sonamu/vector";
|
|
58
58
|
|
|
59
|
-
//
|
|
60
|
-
const result = await Embedding.embedOne("
|
|
59
|
+
// Single text
|
|
60
|
+
const result = await Embedding.embedOne("text to search", "voyage", "query");
|
|
61
61
|
// result: { embedding: number[], model: "voyage-3", tokenCount: 15 }
|
|
62
62
|
|
|
63
|
-
//
|
|
63
|
+
// Multiple texts (auto-splits when exceeding batchSize)
|
|
64
64
|
const results = await Embedding.embed(
|
|
65
|
-
["
|
|
65
|
+
["text1", "text2", ...],
|
|
66
66
|
"voyage",
|
|
67
67
|
"document", // inputType: "document" | "query"
|
|
68
|
-
(processed, total) => console.log(`${processed}/${total}`), //
|
|
68
|
+
(processed, total) => console.log(`${processed}/${total}`), // progress callback
|
|
69
69
|
);
|
|
70
70
|
|
|
71
|
-
//
|
|
71
|
+
// Check number of dimensions
|
|
72
72
|
Embedding.getDimensions("voyage"); // 1024
|
|
73
73
|
Embedding.getDimensions("openai"); // 1536
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
-
### Voyage AI inputType (
|
|
76
|
+
### Voyage AI inputType (Asymmetric Embedding)
|
|
77
77
|
|
|
78
|
-
| inputType |
|
|
78
|
+
| inputType | Use case |
|
|
79
79
|
|-----------|------|
|
|
80
|
-
| `"document"` |
|
|
81
|
-
| `"query"` |
|
|
80
|
+
| `"document"` | When embedding documents to store in DB |
|
|
81
|
+
| `"query"` | When embedding search queries |
|
|
82
82
|
|
|
83
|
-
**CRITICAL:
|
|
83
|
+
**CRITICAL: Use `"document"` when storing and `"query"` when searching for asymmetric embedding to work correctly.**
|
|
84
84
|
|
|
85
85
|
---
|
|
86
86
|
|
|
87
|
-
## Chunking
|
|
87
|
+
## Chunking Usage
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
Splits long documents into appropriately-sized pieces.
|
|
90
90
|
|
|
91
91
|
```typescript
|
|
92
92
|
import { Chunking } from "sonamu/vector";
|
|
93
93
|
|
|
94
94
|
const chunker = new Chunking({
|
|
95
|
-
chunkSize: 500, //
|
|
96
|
-
chunkOverlap: 50, //
|
|
97
|
-
minChunkSize: 50, //
|
|
95
|
+
chunkSize: 500, // Maximum chunk size (character count)
|
|
96
|
+
chunkOverlap: 50, // Overlap between chunks
|
|
97
|
+
minChunkSize: 50, // Minimum chunk size
|
|
98
98
|
});
|
|
99
99
|
|
|
100
|
-
//
|
|
101
|
-
chunker.needsChunking("
|
|
100
|
+
// Check if chunking is needed
|
|
101
|
+
chunker.needsChunking("short text"); // false
|
|
102
102
|
|
|
103
|
-
//
|
|
103
|
+
// Split into chunks
|
|
104
104
|
const chunks = chunker.chunk(longText);
|
|
105
105
|
// chunks: [{ index: 0, text: "...", startOffset: 0, endOffset: 500 }, ...]
|
|
106
106
|
|
|
107
|
-
//
|
|
107
|
+
// Estimate number of chunks
|
|
108
108
|
chunker.estimateChunkCount(longText); // 5
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
-
###
|
|
111
|
+
### Chunking Default Settings
|
|
112
112
|
|
|
113
|
-
|
|
|
113
|
+
| Option | Default | Description |
|
|
114
114
|
|------|--------|------|
|
|
115
|
-
| `chunkSize` | 500 |
|
|
116
|
-
| `chunkOverlap` | 50 |
|
|
117
|
-
| `minChunkSize` | 50 |
|
|
118
|
-
| `skipThreshold` | 200 |
|
|
119
|
-
| `separators` | `["\n\n", "\n", "。", ". ", ...]` |
|
|
115
|
+
| `chunkSize` | 500 | Maximum chunk size (character count) |
|
|
116
|
+
| `chunkOverlap` | 50 | Overlap between chunks |
|
|
117
|
+
| `minChunkSize` | 50 | Minimum chunk size |
|
|
118
|
+
| `skipThreshold` | 200 | Passes through without chunking if at or below this size |
|
|
119
|
+
| `separators` | `["\n\n", "\n", "。", ". ", ...]` | Split delimiters (in priority order) |
|
|
120
120
|
|
|
121
121
|
---
|
|
122
122
|
|
|
123
|
-
##
|
|
123
|
+
## Search Configuration
|
|
124
124
|
|
|
125
125
|
```typescript
|
|
126
126
|
import { createVectorConfig } from "sonamu/vector";
|
|
@@ -128,20 +128,20 @@ import { createVectorConfig } from "sonamu/vector";
|
|
|
128
128
|
const config = createVectorConfig({
|
|
129
129
|
search: {
|
|
130
130
|
defaultLimit: 10,
|
|
131
|
-
similarityThreshold: 0.5, //
|
|
132
|
-
vectorWeight: 0.7, //
|
|
133
|
-
ftsWeight: 0.3, //
|
|
131
|
+
similarityThreshold: 0.5, // Results below this value are excluded
|
|
132
|
+
vectorWeight: 0.7, // Vector weight in hybrid search
|
|
133
|
+
ftsWeight: 0.3, // FTS weight in hybrid search
|
|
134
134
|
},
|
|
135
135
|
pgvector: {
|
|
136
|
-
iterativeScan: true, // pgvector iterative scan
|
|
137
|
-
efSearch: 100, // HNSW
|
|
136
|
+
iterativeScan: true, // Use pgvector iterative scan
|
|
137
|
+
efSearch: 100, // HNSW index search accuracy
|
|
138
138
|
},
|
|
139
139
|
});
|
|
140
140
|
```
|
|
141
141
|
|
|
142
142
|
---
|
|
143
143
|
|
|
144
|
-
##
|
|
144
|
+
## Type Definitions
|
|
145
145
|
|
|
146
146
|
### VectorSearchResult
|
|
147
147
|
|
|
@@ -166,10 +166,10 @@ interface HybridSearchResult<T> extends VectorSearchResult<T> {
|
|
|
166
166
|
|
|
167
167
|
```typescript
|
|
168
168
|
interface VectorSearchOptions {
|
|
169
|
-
embeddingColumn?: string; //
|
|
169
|
+
embeddingColumn?: string; // Embedding column name (default: "embedding")
|
|
170
170
|
limit?: number;
|
|
171
|
-
threshold?: number; //
|
|
172
|
-
where?: string; // SQL WHERE
|
|
171
|
+
threshold?: number; // Similarity threshold
|
|
172
|
+
where?: string; // SQL WHERE condition
|
|
173
173
|
}
|
|
174
174
|
```
|
|
175
175
|
|
|
@@ -177,36 +177,36 @@ interface VectorSearchOptions {
|
|
|
177
177
|
|
|
178
178
|
```typescript
|
|
179
179
|
interface HybridSearchOptions extends VectorSearchOptions {
|
|
180
|
-
vectorWeight?: number; //
|
|
181
|
-
ftsWeight?: number; // FTS
|
|
182
|
-
ftsColumn?: string; //
|
|
180
|
+
vectorWeight?: number; // Vector search weight
|
|
181
|
+
ftsWeight?: number; // FTS weight
|
|
182
|
+
ftsColumn?: string; // Target column name for FTS
|
|
183
183
|
}
|
|
184
184
|
```
|
|
185
185
|
|
|
186
186
|
---
|
|
187
187
|
|
|
188
|
-
## pgvector DB
|
|
188
|
+
## pgvector DB Setup
|
|
189
189
|
|
|
190
|
-
###
|
|
190
|
+
### Install Extension
|
|
191
191
|
|
|
192
192
|
```sql
|
|
193
193
|
CREATE EXTENSION IF NOT EXISTS vector;
|
|
194
194
|
```
|
|
195
195
|
|
|
196
|
-
###
|
|
196
|
+
### Add Embedding Column
|
|
197
197
|
|
|
198
198
|
```sql
|
|
199
|
-
-- Voyage AI (1024
|
|
199
|
+
-- Voyage AI (1024 dimensions)
|
|
200
200
|
ALTER TABLE documents ADD COLUMN embedding vector(1024);
|
|
201
201
|
|
|
202
|
-
-- OpenAI (1536
|
|
202
|
+
-- OpenAI (1536 dimensions)
|
|
203
203
|
ALTER TABLE documents ADD COLUMN embedding vector(1536);
|
|
204
204
|
```
|
|
205
205
|
|
|
206
|
-
### HNSW
|
|
206
|
+
### HNSW Index
|
|
207
207
|
|
|
208
208
|
```sql
|
|
209
|
-
--
|
|
209
|
+
-- Cosine similarity-based index
|
|
210
210
|
CREATE INDEX ON documents
|
|
211
211
|
USING hnsw (embedding vector_cosine_ops)
|
|
212
212
|
WITH (m = 16, ef_construction = 64);
|
|
@@ -214,8 +214,8 @@ WITH (m = 16, ef_construction = 64);
|
|
|
214
214
|
|
|
215
215
|
---
|
|
216
216
|
|
|
217
|
-
##
|
|
217
|
+
## References
|
|
218
218
|
|
|
219
|
-
-
|
|
220
|
-
- **pgvector
|
|
219
|
+
- **Source code**: `modules/sonamu/src/vector/`
|
|
220
|
+
- **pgvector official**: https://github.com/pgvector/pgvector
|
|
221
221
|
- **Voyage AI**: https://docs.voyageai.com/
|
|
@@ -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.",
|