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.
Files changed (88) hide show
  1. package/dist/api/__tests__/config.test.js +189 -0
  2. package/dist/api/config.d.ts.map +1 -1
  3. package/dist/api/config.js +7 -2
  4. package/dist/api/sonamu.d.ts.map +1 -1
  5. package/dist/api/sonamu.js +14 -10
  6. package/dist/auth/index.d.ts +1 -0
  7. package/dist/auth/index.d.ts.map +1 -1
  8. package/dist/auth/index.js +2 -1
  9. package/dist/auth/knex-adapter.d.ts +23 -0
  10. package/dist/auth/knex-adapter.d.ts.map +1 -0
  11. package/dist/auth/knex-adapter.js +163 -0
  12. package/dist/auth/plugins/wrappers/admin.d.ts +2 -2
  13. package/dist/bin/__tests__/ts-loader-register.test.js +45 -0
  14. package/dist/bin/cli.js +47 -9
  15. package/dist/bin/ts-loader-register.js +3 -29
  16. package/dist/bin/ts-loader-registration.d.ts +2 -0
  17. package/dist/bin/ts-loader-registration.d.ts.map +1 -0
  18. package/dist/bin/ts-loader-registration.js +42 -0
  19. package/dist/cone/cone-generator.js +3 -3
  20. package/dist/database/puri-subset.test-d.js +9 -1
  21. package/dist/database/puri-subset.types.d.ts +1 -1
  22. package/dist/database/puri-subset.types.d.ts.map +1 -1
  23. package/dist/database/puri-subset.types.js +1 -1
  24. package/dist/testing/fixture-generator.js +5 -5
  25. package/dist/ui/ai-client.js +2 -2
  26. package/dist/ui/api.d.ts.map +1 -1
  27. package/dist/ui/api.js +14 -14
  28. package/dist/ui/cdd-service.d.ts +15 -18
  29. package/dist/ui/cdd-service.d.ts.map +1 -1
  30. package/dist/ui/cdd-service.js +246 -222
  31. package/dist/ui/cdd-types.d.ts +41 -68
  32. package/dist/ui/cdd-types.d.ts.map +1 -1
  33. package/dist/ui/cdd-types.js +2 -2
  34. package/dist/ui-web/assets/index-CKo0Z2Iu.css +1 -0
  35. package/dist/ui-web/assets/{index-CxiydzeC.js → index-DK-2aacv.js} +83 -83
  36. package/dist/ui-web/index.html +2 -2
  37. package/package.json +6 -2
  38. package/src/api/__tests__/config.test.ts +225 -0
  39. package/src/api/config.ts +10 -4
  40. package/src/api/sonamu.ts +16 -13
  41. package/src/auth/index.ts +1 -0
  42. package/src/auth/knex-adapter.ts +208 -0
  43. package/src/bin/__tests__/ts-loader-register.test.ts +62 -0
  44. package/src/bin/cli.ts +52 -9
  45. package/src/bin/ts-loader-register.ts +2 -32
  46. package/src/bin/ts-loader-registration.ts +55 -0
  47. package/src/cone/cone-generator.ts +2 -2
  48. package/src/database/puri-subset.test-d.ts +102 -0
  49. package/src/database/puri-subset.types.ts +1 -1
  50. package/src/skills/commands/sonamu-skills.md +20 -0
  51. package/src/skills/sonamu/SKILL.md +179 -137
  52. package/src/skills/sonamu/ai-agents.md +69 -69
  53. package/src/skills/sonamu/api.md +147 -147
  54. package/src/skills/sonamu/auth-migration.md +220 -220
  55. package/src/skills/sonamu/auth-plugins.md +83 -83
  56. package/src/skills/sonamu/auth.md +106 -106
  57. package/src/skills/sonamu/cdd.md +65 -200
  58. package/src/skills/sonamu/cone.md +138 -138
  59. package/src/skills/sonamu/config.md +191 -191
  60. package/src/skills/sonamu/create-sonamu.md +66 -66
  61. package/src/skills/sonamu/database.md +158 -158
  62. package/src/skills/sonamu/entity-basic.md +292 -293
  63. package/src/skills/sonamu/entity-relations.md +246 -246
  64. package/src/skills/sonamu/entity-validation-checklist.md +124 -124
  65. package/src/skills/sonamu/fixture-cli.md +231 -231
  66. package/src/skills/sonamu/framework-change.md +37 -37
  67. package/src/skills/sonamu/frontend.md +223 -223
  68. package/src/skills/sonamu/i18n.md +82 -82
  69. package/src/skills/sonamu/migration.md +77 -77
  70. package/src/skills/sonamu/model.md +222 -222
  71. package/src/skills/sonamu/naite.md +86 -86
  72. package/src/skills/sonamu/project-init.md +228 -228
  73. package/src/skills/sonamu/puri.md +122 -122
  74. package/src/skills/sonamu/scaffolding.md +154 -154
  75. package/src/skills/sonamu/skill-contribution.md +124 -124
  76. package/src/skills/sonamu/subset.md +46 -46
  77. package/src/skills/sonamu/tasks.md +82 -82
  78. package/src/skills/sonamu/testing-devrunner.md +147 -147
  79. package/src/skills/sonamu/testing.md +673 -673
  80. package/src/skills/sonamu/upsert.md +79 -79
  81. package/src/skills/sonamu/vector.md +67 -67
  82. package/src/testing/fixture-generator.ts +4 -4
  83. package/src/ui/ai-client.ts +1 -1
  84. package/src/ui/api.ts +18 -17
  85. package/src/ui/cdd-service.ts +264 -254
  86. package/src/ui/cdd-types.ts +40 -75
  87. package/dist/ui-web/assets/index-BrQKU3j9.css +0 -1
  88. package/src/skills/sonamu/workflow.md +0 -317
@@ -1,32 +1,32 @@
1
1
  ---
2
2
  name: sonamu-puri
3
- description: Sonamu Puri 타입 안전 쿼리 빌더. SELECT, WHERE, JOIN, 집계함수, FTS, 벡터검색, 트랜잭션. Use when writing database queries in Model.
3
+ description: Sonamu Puri type-safe query builder. SELECT, WHERE, JOIN, aggregate functions, FTS, vector search, transactions. Use when writing database queries in Model.
4
4
  ---
5
5
 
6
- # Puri 쿼리 빌더
6
+ # Puri Query Builder
7
7
 
8
- ## 쿼리 시작
8
+ ## Starting a Query
9
9
 
10
10
  ```typescript
11
- // 읽기
11
+ // Read
12
12
  const users = await this.getPuri("r").table("users").select({ id: "id", name: "username" });
13
13
 
14
- // 쓰기
14
+ // Write
15
15
  await this.getPuri("w").table("users").where("id", 1).update({ is_active: false });
16
16
 
17
- // Alias 사용
17
+ // Using aliases
18
18
  const users = await db.table({ u: "users" }).select({ id: "u.id" });
19
19
  ```
20
20
 
21
21
  ## SELECT
22
22
 
23
- > **CRITICAL: `.select()`는 반드시 객체 형식으로 사용한다.**
23
+ > **CRITICAL: `.select()` must always be used with an object argument.**
24
24
  >
25
- > 문자열 인수를 전달하면 문자열이 character 단위로 spread되어 `select "i" as "0", "d" as "1", ...` 같은 잘못된 SQL이 생성된다.
26
- > `as any` 캐스팅 체이닝할 특히 주의한다.
25
+ > Passing a string argument causes the string to be spread character-by-character, generating incorrect SQL like `select "i" as "0", "d" as "1", ...`.
26
+ > Be especially careful when chaining after an `as any` cast.
27
27
  >
28
28
  > ```typescript
29
- > // WRONG — character spread 버그
29
+ > // WRONG — character spread bug
30
30
  > db.table("files").select("files.entity_id", "files.file_type")
31
31
  >
32
32
  > // CORRECT
@@ -34,13 +34,13 @@ const users = await db.table({ u: "users" }).select({ id: "u.id" });
34
34
  > ```
35
35
 
36
36
  ```typescript
37
- // 기본 select
37
+ // Basic select
38
38
  const users = await db.table("users").select({ id: "id", name: "username" });
39
39
 
40
- // 모든 컬럼
40
+ // All columns
41
41
  const users = await db.table("users").selectAll();
42
42
 
43
- // 중첩 객체 (hydrate 자동 변환)
43
+ // Nested objects (auto-converted during hydration)
44
44
  db.select({
45
45
  id: "users.id",
46
46
  parent: {
@@ -49,13 +49,13 @@ db.select({
49
49
  }
50
50
  });
51
51
 
52
- // 기존 select에 추가
52
+ // Append to existing select
53
53
  db.select({ id: "id" }).appendSelect({ name: "username" });
54
54
  ```
55
55
 
56
- ## Static 함수 (SELECT)
56
+ ## Static Functions (for SELECT)
57
57
 
58
- ### 집계 함수
58
+ ### Aggregate Functions
59
59
 
60
60
  ```typescript
61
61
  // COUNT
@@ -71,7 +71,7 @@ db.select({
71
71
  });
72
72
  ```
73
73
 
74
- ### 문자열 함수
74
+ ### String Functions
75
75
 
76
76
  ```typescript
77
77
  db.select({
@@ -81,12 +81,12 @@ db.select({
81
81
  });
82
82
  ```
83
83
 
84
- ### Raw SQL 표현식
84
+ ### Raw SQL Expressions
85
85
 
86
- 번째 인수 `params`로 바인드 파라미터를 전달할 있다. 값을 SQL 직접 보간하지 말고 params 사용한다.
86
+ Bind parameters can be passed as the second argument `params`. Do not interpolate values directly into SQL; use params instead.
87
87
 
88
88
  ```typescript
89
- // 파라미터 없는 경우
89
+ // Without parameters
90
90
  db.select({
91
91
  custom: Puri.rawString("COALESCE(nickname, username)"),
92
92
  total: Puri.rawNumber("price * quantity"),
@@ -95,26 +95,26 @@ db.select({
95
95
  tags: Puri.rawStringArray("string_to_array(tags, ',')"),
96
96
  });
97
97
 
98
- // params 배열로 바인드 (SQL injection 방지)
98
+ // Bind with params array (prevents SQL injection)
99
99
  db.select({
100
100
  score: Puri.rawNumber(
101
101
  `word_similarity(?, items.title) * 5 + word_similarity(?, items.tags) * 2`,
102
102
  [query, query]
103
103
  ),
104
- label: Puri.rawString(`COALESCE(??, ?)`, ["items.name", "미지정"]),
104
+ label: Puri.rawString(`COALESCE(??, ?)`, ["items.name", "Unspecified"]),
105
105
  });
106
106
  ```
107
107
 
108
108
  ## WHERE
109
109
 
110
110
  ```typescript
111
- // 기본
111
+ // Basic
112
112
  db.where("role", "admin")
113
113
  db.where("age", ">=", 18)
114
114
  db.where("deleted_at", null) // IS NULL
115
115
  db.where("deleted_at", "!=", null) // IS NOT NULL
116
116
 
117
- // 복수 조건 (AND)
117
+ // Multiple conditions (AND)
118
118
  db.where("role", "admin").where("is_active", true)
119
119
 
120
120
  // IN / NOT IN
@@ -128,7 +128,7 @@ db.where("email", "like", `%${keyword}%`)
128
128
  db.whereRaw("EXTRACT(YEAR FROM created_at) = ?", [2024])
129
129
  ```
130
130
 
131
- ### WHERE 그룹핑 (괄호)
131
+ ### WHERE Grouping (Parentheses)
132
132
 
133
133
  ```typescript
134
134
  // (role = 'admin' OR role = 'moderator') AND is_active = true
@@ -136,7 +136,7 @@ db.whereGroup((g) => {
136
136
  g.where("role", "admin").orWhere("role", "moderator");
137
137
  }).where("is_active", true);
138
138
 
139
- // OR 그룹
139
+ // OR group
140
140
  db.where("status", "active")
141
141
  .orWhereGroup((g) => {
142
142
  g.where("role", "admin").where("is_verified", true);
@@ -155,19 +155,19 @@ db.table("employees")
155
155
  db.table("employees")
156
156
  .leftJoin("departments", "employees.department_id", "departments.id")
157
157
 
158
- // Alias 사용
158
+ // Using aliases
159
159
  db.table({ e: "employees" })
160
160
  .join({ u: "users" }, "e.user_id", "u.id")
161
161
  .leftJoin({ d: "departments" }, "e.department_id", "d.id")
162
162
 
163
- // 콜백으로 복잡한 JOIN 조건
163
+ // Complex JOIN conditions with callback
164
164
  db.table("orders")
165
165
  .join("products", (j) => {
166
166
  j.on("orders.product_id", "products.id")
167
167
  .on("orders.store_id", "products.store_id");
168
168
  })
169
169
 
170
- // 서브쿼리 JOIN
170
+ // Subquery JOIN
171
171
  const subquery = db.table("order_items")
172
172
  .select({ order_id: "order_id", total: Puri.sum("amount") })
173
173
  .groupBy("order_id");
@@ -182,7 +182,7 @@ db.table("orders")
182
182
  ```typescript
183
183
  db.orderBy("created_at", "desc")
184
184
  .limit(20)
185
- .offset(40) // 3페이지
185
+ .offset(40) // Page 3
186
186
  ```
187
187
 
188
188
  ## GROUP BY & HAVING
@@ -197,14 +197,14 @@ db.table("orders")
197
197
  .groupBy("user_id")
198
198
  .having("COUNT(*) > 5");
199
199
 
200
- // 컬럼, 연산자, 형태
200
+ // Column, operator, value form
201
201
  db.groupBy("user_id").having("count", ">", 10);
202
202
  ```
203
203
 
204
204
  ## INSERT
205
205
 
206
206
  ```typescript
207
- // 기본 INSERT
207
+ // Basic INSERT
208
208
  await db.table("users").insert({ username: "john", email: "john@test.com" });
209
209
 
210
210
  // RETURNING
@@ -212,12 +212,12 @@ const [{ id }] = await db.table("users")
212
212
  .insert({ username: "john" })
213
213
  .returning("id");
214
214
 
215
- // 복수 컬럼 RETURNING
215
+ // Multiple columns RETURNING
216
216
  const [row] = await db.table("users")
217
217
  .insert({ username: "john" })
218
218
  .returning(["id", "created_at"]);
219
219
 
220
- // 전체 컬럼 RETURNING
220
+ // All columns RETURNING
221
221
  const [user] = await db.table("users")
222
222
  .insert({ username: "john" })
223
223
  .returning("*");
@@ -229,14 +229,14 @@ const [user] = await db.table("users")
229
229
  // DO NOTHING
230
230
  await db.table("users")
231
231
  .insert({ id: 1, username: "john" })
232
- .onConflict("id"); // 또는 .onConflict("id", "nothing")
232
+ .onConflict("id"); // or .onConflict("id", "nothing")
233
233
 
234
- // DO UPDATE - 특정 컬럼만
234
+ // DO UPDATE - specific columns only
235
235
  await db.table("users")
236
236
  .insert({ id: 1, username: "john", email: "new@test.com" })
237
237
  .onConflict("id", { update: ["username", "email"] });
238
238
 
239
- // DO UPDATE - 지정
239
+ // DO UPDATE - with specified values
240
240
  await db.table("users")
241
241
  .insert({ id: 1, username: "john" })
242
242
  .onConflict("id", {
@@ -246,7 +246,7 @@ await db.table("users")
246
246
  }
247
247
  });
248
248
 
249
- // 복합 충돌
249
+ // Composite key conflict
250
250
  await db.table("user_settings")
251
251
  .insert({ user_id: 1, key: "theme", value: "dark" })
252
252
  .onConflict(["user_id", "key"], { update: ["value"] });
@@ -268,13 +268,13 @@ await db.table("users").where("id", 1).decrement("credit", 100);
268
268
  await db.table("users").where("id", 1).delete();
269
269
  ```
270
270
 
271
- ## 결과 메서드
271
+ ## Result Methods
272
272
 
273
- | 메서드 | 반환 | 설명 |
274
- |--------|------|------|
275
- | `await query` | `T[]` | 배열 결과 (Puri Thenable) |
276
- | `first()` | `Promise<T \| undefined>` | 번째 레코드 |
277
- | `pluck("col")` | `Promise<V[]>` | 특정 컬럼만 배열 |
273
+ | Method | Returns | Description |
274
+ |--------|---------|-------------|
275
+ | `await query` | `T[]` | Array result (Puri is Thenable) |
276
+ | `first()` | `Promise<T \| undefined>` | First record |
277
+ | `pluck("col")` | `Promise<V[]>` | Array of a specific column only |
278
278
 
279
279
  ```typescript
280
280
  const users = await db.table("users").select({ id: "id" }); // T[]
@@ -282,31 +282,31 @@ const user = await db.table("users").where("id", 1).first(); // T | und
282
282
  const ids = await db.table("users").where("role", "admin").pluck("id"); // number[]
283
283
  ```
284
284
 
285
- ## 유틸리티
285
+ ## Utilities
286
286
 
287
287
  ```typescript
288
- // 쿼리 문자열 확인
288
+ // Inspect query string
289
289
  const sql = db.table("users").where("id", 1).toQuery();
290
290
 
291
- // 디버그 로그 출력 (콘솔에 쿼리 출력 체이닝 계속)
291
+ // Debug log output (prints query to console, then continues chaining)
292
292
  await db.table("users").where("id", 1).debug().first();
293
293
 
294
- // 쿼리 복제
294
+ // Clone a query
295
295
  const baseQuery = db.table("users").where("is_active", true);
296
296
  const query1 = baseQuery.clone().where("role", "admin");
297
297
  const query2 = baseQuery.clone().where("role", "user");
298
298
 
299
- // 쿼리 부분 초기화
300
- db.clear("select") // SELECT 절 초기화
301
- db.clear("order") // ORDER BY 초기화
302
- db.clear("limit") // LIMIT 초기화
303
- db.clear("offset") // OFFSET 초기화
299
+ // Clear parts of a query
300
+ db.clear("select") // Clear SELECT clause
301
+ db.clear("order") // Clear ORDER BY
302
+ db.clear("limit") // Clear LIMIT
303
+ db.clear("offset") // Clear OFFSET
304
304
 
305
- // 특정 JOIN 제거
305
+ // Remove a specific JOIN
306
306
  db.clearJoin("alias")
307
307
  ```
308
308
 
309
- ## 트랜잭션
309
+ ## Transactions
310
310
 
311
311
  ```typescript
312
312
  await this.getPuri("w").transaction(async (trx) => {
@@ -323,30 +323,30 @@ await this.getPuri("w").transaction(async (trx) => {
323
323
  ### whereTsSearch
324
324
 
325
325
  ```typescript
326
- // 기본 검색 (websearch_to_tsquery, simple config)
327
- db.whereTsSearch("search_vector", "검색어")
326
+ // Basic search (websearch_to_tsquery, simple config)
327
+ db.whereTsSearch("search_vector", "search term")
328
328
 
329
- // config 지정
330
- db.whereTsSearch("search_vector", "검색어", "korean")
329
+ // Specify config
330
+ db.whereTsSearch("search_vector", "search term", "korean")
331
331
 
332
- // 상세 옵션
333
- db.whereTsSearch("search_vector", "검색어", {
332
+ // Detailed options
333
+ db.whereTsSearch("search_vector", "search term", {
334
334
  parser: "plainto_tsquery", // websearch_to_tsquery | plainto_tsquery | phraseto_tsquery
335
335
  config: "korean",
336
336
  })
337
337
  ```
338
338
 
339
- ### tsHighlight (검색어 하이라이팅)
339
+ ### tsHighlight (Search Term Highlighting)
340
340
 
341
341
  ```typescript
342
342
  db.select({
343
343
  title: "title",
344
- highlighted: Puri.tsHighlight("content", "검색어"),
344
+ highlighted: Puri.tsHighlight("content", "search term"),
345
345
  });
346
346
 
347
- // 옵션
347
+ // Options
348
348
  db.select({
349
- highlighted: Puri.tsHighlight("content", "검색어", {
349
+ highlighted: Puri.tsHighlight("content", "search term", {
350
350
  config: "korean",
351
351
  startSel: "<mark>",
352
352
  stopSel: "</mark>",
@@ -357,39 +357,39 @@ db.select({
357
357
  });
358
358
  ```
359
359
 
360
- ### tsRank / tsRankCd (검색 순위)
360
+ ### tsRank / tsRankCd (Search Ranking)
361
361
 
362
- 번째 인수에 컬럼명 문자열 대신 `Puri.toTsVector()`를 전달해야 한다.
362
+ The first argument must be `Puri.toTsVector()` rather than a column name string.
363
363
 
364
364
  ```typescript
365
- // toTsVector() 감싸서 전달 (필수)
365
+ // Must wrap with toTsVector() (required)
366
366
  db.select({
367
- rank: Puri.tsRank(Puri.toTsVector("documents.search_vector"), "검색어"),
367
+ rank: Puri.tsRank(Puri.toTsVector("documents.search_vector"), "search term"),
368
368
  })
369
- .whereTsSearch("documents.search_vector", "검색어")
369
+ .whereTsSearch("documents.search_vector", "search term")
370
370
  .orderBy("rank", "desc");
371
371
 
372
- // config 지정
372
+ // Specify config
373
373
  db.select({
374
- rank: Puri.tsRank(Puri.toTsVector("documents.title", "korean"), "검색어"),
374
+ rank: Puri.tsRank(Puri.toTsVector("documents.title", "korean"), "search term"),
375
375
  });
376
376
 
377
- // 옵션
377
+ // Options
378
378
  db.select({
379
- rank: Puri.tsRank(Puri.toTsVector("documents.title"), "검색어", {
380
- normalization: 1, // 문서 길이 정규화
381
- weights: [0.1, 0.2, 0.4, 1.0], // D, C, B, A 가중치
379
+ rank: Puri.tsRank(Puri.toTsVector("documents.title"), "search term", {
380
+ normalization: 1, // Document length normalization
381
+ weights: [0.1, 0.2, 0.4, 1.0], // D, C, B, A weights
382
382
  }),
383
383
  });
384
384
 
385
385
  // tsRankCd (Cover Density)
386
386
  db.select({
387
- rank: Puri.tsRankCd(Puri.toTsVector("documents.title"), "검색어"),
387
+ rank: Puri.tsRankCd(Puri.toTsVector("documents.title"), "search term"),
388
388
  });
389
389
 
390
- // tsRankCd 옵션
390
+ // tsRankCd options
391
391
  db.select({
392
- rank: Puri.tsRankCd(Puri.toTsVector("documents.title"), "검색어", {
392
+ rank: Puri.tsRankCd(Puri.toTsVector("documents.title"), "search term", {
393
393
  parser: "phraseto_tsquery",
394
394
  normalization: 16,
395
395
  }),
@@ -403,19 +403,19 @@ db.select({
403
403
  ### whereSearch
404
404
 
405
405
  ```typescript
406
- // 단일 컬럼 검색
407
- db.whereSearch("title", "검색어")
406
+ // Single column search
407
+ db.whereSearch("title", "search term")
408
408
 
409
- // 복합 컬럼 검색 (인덱스와 동일한 컬럼 구성 필요)
410
- db.whereSearch(["title", "content"], "검색어")
409
+ // Multi-column search (requires same column composition as index)
410
+ db.whereSearch(["title", "content"], "search term")
411
411
 
412
- // 가중치 옵션
413
- db.whereSearch(["title", "content"], "검색어", {
414
- weights: [10, 1], // title에 10배 가중치
412
+ // Weight options
413
+ db.whereSearch(["title", "content"], "search term", {
414
+ weights: [10, 1], // 10x weight on title
415
415
  })
416
416
  ```
417
417
 
418
- ### score (검색 점수)
418
+ ### score (Search Score)
419
419
 
420
420
  ```typescript
421
421
  db.select({
@@ -423,26 +423,26 @@ db.select({
423
423
  title: "title",
424
424
  score: Puri.score(),
425
425
  })
426
- .whereSearch("title", "검색어")
426
+ .whereSearch("title", "search term")
427
427
  .orderBy("score", "desc");
428
428
  ```
429
429
 
430
- ### highlight (하이라이팅)
430
+ ### highlight (Highlighting)
431
431
 
432
432
  ```typescript
433
- // 단일 컬럼
433
+ // Single column
434
434
  db.select({
435
- highlighted: Puri.highlight("title", "검색어"),
435
+ highlighted: Puri.highlight("title", "search term"),
436
436
  });
437
437
 
438
- // 복합 컬럼 (배열 반환)
438
+ // Multiple columns (returns array)
439
439
  db.select({
440
- highlighted: Puri.highlight(["title", "content"], "검색어"),
440
+ highlighted: Puri.highlight(["title", "content"], "search term"),
441
441
  });
442
442
 
443
- // 검색어 배열
443
+ // Array of search terms
444
444
  db.select({
445
- highlighted: Puri.highlight("title", ["검색어1", "검색어2"]),
445
+ highlighted: Puri.highlight("title", ["term1", "term2"]),
446
446
  });
447
447
  ```
448
448
 
@@ -453,9 +453,9 @@ db.select({
453
453
  ### vectorSimilarity
454
454
 
455
455
  ```typescript
456
- const embedding = await getEmbedding("검색 쿼리");
456
+ const embedding = await getEmbedding("search query");
457
457
 
458
- // 기본 (cosine similarity)
458
+ // Default (cosine similarity)
459
459
  const results = await db.table("documents")
460
460
  .select({ id: "id", title: "title" })
461
461
  .vectorSimilarity("embedding", embedding);
@@ -468,39 +468,39 @@ db.vectorSimilarity("embedding", embedding, { method: "l2" });
468
468
  // Inner product
469
469
  db.vectorSimilarity("embedding", embedding, { method: "inner_product" });
470
470
 
471
- // threshold 필터
471
+ // threshold filter
472
472
  db.vectorSimilarity("embedding", embedding, {
473
473
  method: "cosine",
474
- threshold: 0.7, // similarity >= 0.7
474
+ threshold: 0.7, // only similarity >= 0.7
475
475
  });
476
476
 
477
- // distinctOn (중복 제거)
477
+ // distinctOn (deduplicate)
478
478
  db.vectorSimilarity("embedding", embedding, {
479
- distinctOn: "document_id", // document_id 최고 유사도만
479
+ distinctOn: "document_id", // best similarity per document_id only
480
480
  });
481
481
  ```
482
482
 
483
- **반환값**: `similarity` 컬럼이 자동 추가됨
483
+ **Return value**: `similarity` column is automatically added
484
484
 
485
- | method | similarity 의미 | 정렬 |
486
- |--------|----------------|------|
487
- | cosine | 1 - distance (높을수록 유사) | desc |
488
- | l2 | distance (낮을수록 유사) | asc |
489
- | inner_product | -distance (높을수록 유사) | desc |
485
+ | method | similarity meaning | sort order |
486
+ |--------|-------------------|------------|
487
+ | cosine | 1 - distance (higher = more similar) | desc |
488
+ | l2 | distance (lower = more similar) | asc |
489
+ | inner_product | -distance (higher = more similar) | desc |
490
490
 
491
491
  ---
492
492
 
493
493
  ## pg_trgm Fuzzy Search
494
494
 
495
- `CREATE EXTENSION IF NOT EXISTS pg_trgm` 필요. `searchText` prop으로 생성된 generated column GIN 인덱스를 함께 사용하는 것이 일반적이다.
495
+ Requires `CREATE EXTENSION IF NOT EXISTS pg_trgm`. Typically used together with a generated column created via the `searchText` prop and a GIN index.
496
496
 
497
- ### whereFuzzy — 후보 필터링
497
+ ### whereFuzzy — Candidate Filtering
498
498
 
499
- 연산자별로 SQL 피연산자 순서가 다르다:
499
+ The SQL operand order differs per operator:
500
500
 
501
- | operator | 의미 | SQL |
502
- |----------|------|-----|
503
- | `<%` (기본) | word similarity | `'query' <% column` |
501
+ | operator | meaning | SQL |
502
+ |----------|---------|-----|
503
+ | `<%` (default) | word similarity | `'query' <% column` |
504
504
  | `%` | similarity | `column % 'query'` |
505
505
  | `<<%` | strict word similarity | `'query' <<% column` |
506
506
 
@@ -510,9 +510,9 @@ puri.whereFuzzy("items.search_text", query, { operator: "%" });
510
510
  puri.whereFuzzy("items.search_text", query, { operator: "<<%" });
511
511
  ```
512
512
 
513
- ### 유사도 점수 — Static 메서드
513
+ ### Similarity Scores — Static Methods
514
514
 
515
- `SqlExpression<"number">`를 반환하므로 select에서 점수 컬럼으로 활용한다:
515
+ Returns `SqlExpression<"number">`, so use it as a score column in select:
516
516
 
517
517
  ```typescript
518
518
  // Puri.wordSimilarity(column, query) → word_similarity(?, ??)
@@ -533,13 +533,13 @@ const results = await this.getPuri("r")
533
533
  .orderByRaw("score DESC");
534
534
  ```
535
535
 
536
- ### 언어별 특성
536
+ ### Language-specific Characteristics
537
537
 
538
- | 언어 | 적합성 | 비고 |
539
- |------|--------|------|
540
- | 영어 | 우수 | 단어 단위 분리 + word_similarity |
541
- | 한국어 | 양호 | 1-2글자 검색 성능 저하 |
542
- | 일본어 | 양호 | 1-2글자 검색 성능 저하 |
538
+ | Language | Suitability | Notes |
539
+ |----------|-------------|-------|
540
+ | English | Excellent | Word-level splitting + word_similarity |
541
+ | Korean | Good | Performance degrades for 1-2 character searches |
542
+ | Japanese | Good | Performance degrades for 1-2 character searches |
543
543
 
544
544
  ---
545
545
 
@@ -548,4 +548,4 @@ const results = await this.getPuri("r")
548
548
  - MUST use `getPuri("r")` for read queries, `getPuri("w")` for write queries
549
549
  - MUST include WHERE condition for UPDATE/DELETE operations
550
550
  - MUST use `transaction()` for multiple write operations
551
- - JSON/JSONB 컬럼은 insert/update 자동으로 JSON.stringify 처리됨
551
+ - JSON/JSONB columns are automatically JSON.stringify'd on insert/update