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.
- package/dist/auth/better-auth-entities.d.ts.map +1 -1
- package/dist/auth/better-auth-entities.js +9 -12
- package/dist/cone/cone-generator.js +5 -4
- package/dist/database/puri.d.ts +2 -0
- package/dist/database/puri.d.ts.map +1 -1
- package/dist/database/puri.js +9 -1
- package/dist/testing/dev-vitest-manager.d.ts.map +1 -1
- package/dist/testing/dev-vitest-manager.js +2 -1
- package/dist/testing/fixture-generator.d.ts.map +1 -1
- package/dist/testing/fixture-generator.js +6 -5
- package/dist/types/types.d.ts +2 -1
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/types.js +1 -1
- package/package.json +4 -4
- package/src/auth/better-auth-entities.ts +0 -1
- package/src/cone/cone-generator.ts +4 -3
- package/src/database/__tests__/puri.test.ts +35 -0
- package/src/database/puri.ts +10 -0
- package/src/database/puri.types.test-d.ts +21 -0
- package/src/skills/sonamu/cone.md +5 -19
- package/src/testing/dev-vitest-manager.ts +2 -0
- package/src/testing/fixture-generator.ts +22 -8
- package/src/types/types.ts +1 -1
|
@@ -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
|
+
});
|
package/src/database/puri.ts
CHANGED
|
@@ -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
|
|
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
|
-
###
|
|
189
|
+
### String PK
|
|
190
190
|
|
|
191
|
-
|
|
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": "
|
|
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
|
```
|
|
@@ -92,12 +92,20 @@ export class FixtureGenerator {
|
|
|
92
92
|
|
|
93
93
|
// id prop 처리
|
|
94
94
|
if (prop.name === "id") {
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
//
|
|
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"
|
|
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
|
|
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?.
|
|
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?.
|
|
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 생성
|
package/src/types/types.ts
CHANGED