sonamu 0.9.11 → 0.9.13

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.
@@ -0,0 +1,35 @@
1
+ import { knex } from "knex";
2
+ import { afterAll, describe, expect, it } from "vitest";
3
+
4
+ import { Puri } from "../puri";
5
+
6
+ type TestSchema = {
7
+ users: {
8
+ id: number;
9
+ name: string;
10
+ };
11
+ };
12
+
13
+ const db = knex({ client: "pg" });
14
+
15
+ function usersQuery(): Puri<TestSchema, { users: TestSchema["users"] }, TestSchema["users"]> {
16
+ return new Puri(db, "users");
17
+ }
18
+
19
+ afterAll(async () => {
20
+ await db.destroy();
21
+ });
22
+
23
+ describe("Puri locking methods", () => {
24
+ it("adds FOR UPDATE to select queries", () => {
25
+ const query = usersQuery().where("id", 1).forUpdate().first().toQuery();
26
+
27
+ expect(query).toBe('select * from "users" where "id" = 1 limit 1 for update');
28
+ });
29
+
30
+ it("adds FOR SHARE to select queries", () => {
31
+ const query = usersQuery().where("id", 1).forShare().first().toQuery();
32
+
33
+ expect(query).toBe('select * from "users" where "id" = 1 limit 1 for share');
34
+ });
35
+ });
@@ -852,6 +852,16 @@ export class Puri<TSchema, TTables extends Record<string, any>, TResult> {
852
852
  return this;
853
853
  }
854
854
 
855
+ forUpdate(): this {
856
+ this.knexQuery.forUpdate();
857
+ return this;
858
+ }
859
+
860
+ forShare(): this {
861
+ this.knexQuery.forShare();
862
+ return this;
863
+ }
864
+
855
865
  /**
856
866
  * 벡터 유사도 검색 설정
857
867
  *
@@ -1,5 +1,6 @@
1
1
  import { describe, expectTypeOf, it } from "vitest";
2
2
 
3
+ import { type Puri } from "./puri";
3
4
  import {
4
5
  type AvailableColumns,
5
6
  type ExtractColumnType,
@@ -456,3 +457,23 @@ describe("AvailableColumns", () => {
456
457
  expectTypeOf(valid2).toExtend<Result>();
457
458
  });
458
459
  });
460
+
461
+ describe("Puri locking methods", () => {
462
+ it("forUpdate 체이닝 후 first 결과 타입을 유지한다", () => {
463
+ type Query = Puri<MockSchema, { users: MockSchema["users"] }, MockSchema["users"]>;
464
+ const query = {} as Query;
465
+
466
+ const result = query.where("id", 1).forUpdate().first();
467
+
468
+ expectTypeOf(result).resolves.toEqualTypeOf<MockSchema["users"]>();
469
+ });
470
+
471
+ it("forShare 체이닝 후 first 결과 타입을 유지한다", () => {
472
+ type Query = Puri<MockSchema, { users: MockSchema["users"] }, MockSchema["users"]>;
473
+ const query = {} as Query;
474
+
475
+ const result = query.where("id", 1).forShare().first();
476
+
477
+ expectTypeOf(result).resolves.toEqualTypeOf<MockSchema["users"]>();
478
+ });
479
+ });
@@ -36,7 +36,7 @@ During fixture generation, the LLM references `cone.note` to produce realistic,
36
36
  | `note` | string | **Highest priority.** Business meaning of the field, concrete examples, value ranges, format constraints. Input the LLM reads to generate data |
37
37
  | `fixtureGenerator` | string | **Fallback.** faker.js expression. Fallback when no API key is available |
38
38
  | `fixtureDefault` | any | Fixed default value |
39
- | `fixtureStrategy` | string | `"sequence"` — used when id is auto-assigned by a DB sequence |
39
+ | `fixtureStrategy` | string | `"sequence"` — used when a DB sequence auto-assigns the id. Never use on string PK. |
40
40
  | `dataSource` | object | Strategy for fetching reference data for relation props |
41
41
 
42
42
  ### Subset cone
@@ -186,31 +186,17 @@ Do not set `fixtureGenerator` on correlated fields. The LLM generates them toget
186
186
  }
187
187
  ```
188
188
 
189
- ### DB sequence PK
189
+ ### String PK
190
190
 
191
- ```json
192
- {
193
- "name": "id",
194
- "type": "string",
195
- "cone": {
196
- "fixtureStrategy": "sequence",
197
- "note": "Sequential number automatically assigned by the DB sequence (stored as string)"
198
- }
199
- }
200
- ```
201
-
202
- ### better-auth entity PK (Account / Session / Verification)
203
-
204
- The id of entities managed by better-auth is a UUID generated via `crypto.randomUUID()`.
205
- Using `fixtureStrategy: "sequence"` causes a `MAX(id::bigint)` error during fixture sync, so always use `fixtureGenerator: "faker.string.uuid()"`.
191
+ Never set `fixtureStrategy: "sequence"` or `fixtureGenerator` on an id prop with `type: "string"`.
192
+ fixture-generator automatically generates `alphanumeric(32)`.
206
193
 
207
194
  ```json
208
195
  {
209
196
  "name": "id",
210
197
  "type": "string",
211
198
  "cone": {
212
- "note": "UUID-format identifier generated by better-auth via crypto.randomUUID()",
213
- "fixtureGenerator": "faker.string.uuid()"
199
+ "note": "Random alphanumeric string ID (32 chars, [a-zA-Z0-9]). Generated by better-auth via generateId()"
214
200
  }
215
201
  }
216
202
  ```
@@ -184,6 +184,8 @@ export class DevVitestManager {
184
184
  }
185
185
  }
186
186
 
187
+ this.eventListeners.clear();
188
+
187
189
  if (this.vitest) {
188
190
  await this.vitest.close();
189
191
  this.vitest = null;
@@ -92,12 +92,20 @@ export class FixtureGenerator {
92
92
 
93
93
  // id prop 처리
94
94
  if (prop.name === "id") {
95
- if ("cone" in prop && prop.cone?.fixtureStrategy === "sequence") {
96
- // DB sequence가 자동 할당하므로 스킵 (User 등)
95
+ // DB sequence가 자동 할당: integer/bigInteger이거나, string이지만 dbDefault에 nextval이 있는 경우
96
+ const hasDbSequence =
97
+ prop.type === "integer" ||
98
+ prop.type === "bigInteger" ||
99
+ (prop.type === "string" &&
100
+ "dbDefault" in prop &&
101
+ typeof prop.dbDefault === "string" &&
102
+ prop.dbDefault.includes("nextval"));
103
+ if (hasDbSequence) {
104
+ // generateBatch에서 처리하므로 여기선 스킵
97
105
  continue;
98
106
  }
99
107
  if (prop.type === "string") {
100
- // DB DEFAULT 없는 string PK: alphanumeric 32자 생성 (better-auth 스타일)
108
+ // string PK: alphanumeric 32자 생성 (better-auth 스타일)
101
109
  const { faker: _faker } = await import("@faker-js/faker");
102
110
  fixture[prop.name] = _faker.string.alphanumeric(32);
103
111
  continue;
@@ -107,7 +115,6 @@ export class FixtureGenerator {
107
115
  fixture[prop.name] = _faker.string.uuid();
108
116
  continue;
109
117
  }
110
- // integer/bigInteger PK: generateBatch에서 tempId를 넣으므로 여기선 스킵
111
118
  continue;
112
119
  }
113
120
 
@@ -802,7 +809,7 @@ export class FixtureGenerator {
802
809
  const llmProps = entity.props.filter((p) => {
803
810
  if (isRelationProp(p)) return false;
804
811
  if (p.cone?.fixtureGenerator) return false;
805
- if (p.name === "id" && p.cone?.fixtureStrategy === "sequence") return false;
812
+ if (p.name === "id") return false;
806
813
  return !!p.cone?.note;
807
814
  });
808
815
 
@@ -1520,14 +1527,18 @@ Rules:
1520
1527
  const entity = this.entityManager.get(entityName);
1521
1528
 
1522
1529
  // integer/bigInteger PK는 임시 ID 생성 (DB 시퀀스가 실제 ID 할당)
1523
- // string PK generate()에서 이미 생성된 id 값을 그대로 사용
1530
+ // string PK with dbDefault nextval도 동일하게 처리
1531
+ // 그 외 string/uuid PK는 generate()에서 이미 생성된 id 값을 그대로 사용
1524
1532
  // parentId 엔티티는 부모의 실제 id를 그대로 사용 (시퀀스 미사용)
1525
1533
  const idProp = entity.props.find((p) => p.name === "id");
1526
1534
  const usesSequence =
1527
1535
  !explicitId &&
1528
1536
  (idProp?.type === "integer" ||
1529
1537
  idProp?.type === "bigInteger" ||
1530
- idProp?.cone?.fixtureStrategy === "sequence");
1538
+ (idProp?.type === "string" &&
1539
+ "dbDefault" in (idProp ?? {}) &&
1540
+ typeof (idProp as { dbDefault?: unknown })?.dbDefault === "string" &&
1541
+ ((idProp as { dbDefault?: string })?.dbDefault ?? "").includes("nextval")));
1531
1542
 
1532
1543
  const dataForRecord = usesSequence
1533
1544
  ? { ...data, id: Math.floor(Math.random() * 1000000) }
@@ -1600,7 +1611,10 @@ Rules:
1600
1611
  const usesSequence =
1601
1612
  companionIdProp?.type === "integer" ||
1602
1613
  companionIdProp?.type === "bigInteger" ||
1603
- companionIdProp?.cone?.fixtureStrategy === "sequence";
1614
+ (companionIdProp?.type === "string" &&
1615
+ "dbDefault" in (companionIdProp ?? {}) &&
1616
+ typeof (companionIdProp as { dbDefault?: unknown })?.dbDefault === "string" &&
1617
+ ((companionIdProp as { dbDefault?: string })?.dbDefault ?? "").includes("nextval"));
1604
1618
  const companionCount = companion.count ?? 1;
1605
1619
 
1606
1620
  // 각 parent result에 대해 companion fixture 생성
@@ -329,7 +329,7 @@ export type SonamuFileBase = {
329
329
  size: number;
330
330
  };
331
331
 
332
- export type SonamuFile = SonamuFileBase & SonamuFileExtend;
332
+ export interface SonamuFile extends SonamuFileBase, SonamuFileExtend {}
333
333
 
334
334
  /**
335
335
  * UploadParams