@apisr/drizzle-model 0.0.3 → 2.0.0
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/DISCLAIMER.md +5 -0
- package/TODO.md +8 -61
- package/package.json +2 -1
- package/src/core/dialect.ts +81 -0
- package/src/core/index.ts +24 -0
- package/src/core/query/error.ts +15 -0
- package/src/core/query/joins.ts +596 -0
- package/src/core/query/projection.ts +136 -0
- package/src/core/query/where.ts +449 -0
- package/src/core/result.ts +297 -0
- package/src/core/runtime.ts +612 -0
- package/src/core/transform.ts +119 -0
- package/src/model/builder.ts +40 -6
- package/src/model/config.ts +9 -9
- package/src/model/format.ts +20 -8
- package/src/model/methods/exclude.ts +1 -7
- package/src/model/methods/return.ts +11 -11
- package/src/model/methods/select.ts +2 -8
- package/src/model/model.ts +10 -16
- package/src/model/query/error.ts +1 -0
- package/src/model/result.ts +134 -21
- package/src/types.ts +38 -0
- package/tests/base/count.test.ts +47 -0
- package/tests/base/delete.test.ts +90 -0
- package/tests/base/find.test.ts +209 -0
- package/tests/base/insert.test.ts +152 -0
- package/tests/base/safe.test.ts +91 -0
- package/tests/base/update.test.ts +88 -0
- package/tests/base/upsert.test.ts +121 -0
- package/tests/base.ts +21 -0
- package/tests/snippets/x-1.ts +22 -0
- package/src/model/core/joins.ts +0 -364
- package/src/model/core/projection.ts +0 -61
- package/src/model/core/runtime.ts +0 -330
- package/src/model/core/thenable.ts +0 -94
- package/src/model/core/transform.ts +0 -65
- package/src/model/core/where.ts +0 -249
- package/src/model/core/with.ts +0 -28
- package/tests/builder-v2-mysql.type-test.ts +0 -51
- package/tests/builder-v2.type-test.ts +0 -336
- package/tests/builder.test.ts +0 -63
- package/tests/find.test.ts +0 -166
- package/tests/insert.test.ts +0 -247
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { model } from "tests/base";
|
|
3
|
+
import { db } from "tests/db";
|
|
4
|
+
import * as schema from "tests/schema";
|
|
5
|
+
import { esc } from "@/model";
|
|
6
|
+
|
|
7
|
+
const userModel = model("user", {});
|
|
8
|
+
const postsModel = model("userPosts", {});
|
|
9
|
+
|
|
10
|
+
describe("count", () => {
|
|
11
|
+
test("returns total row count", async () => {
|
|
12
|
+
const count = await userModel.count();
|
|
13
|
+
const rows = await db.select().from(schema.user);
|
|
14
|
+
|
|
15
|
+
expect(count).toBeNumber();
|
|
16
|
+
expect(count).toBe((rows as any[]).length);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("respects where clause", async () => {
|
|
20
|
+
const count = await userModel.where({ name: esc("Alex") }).count();
|
|
21
|
+
|
|
22
|
+
expect(count).toBeNumber();
|
|
23
|
+
expect(count).toBeGreaterThanOrEqual(0);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("returns 0 for no matches", async () => {
|
|
27
|
+
const count = await userModel
|
|
28
|
+
.where({ name: esc("__nonexistent_user_name__") })
|
|
29
|
+
.count();
|
|
30
|
+
|
|
31
|
+
expect(count).toBe(0);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("reflects inserted rows", async () => {
|
|
35
|
+
const before = await postsModel.count();
|
|
36
|
+
|
|
37
|
+
await postsModel.insert({
|
|
38
|
+
title: "Count check",
|
|
39
|
+
featured: false,
|
|
40
|
+
userId: 1,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const after = await postsModel.count();
|
|
44
|
+
|
|
45
|
+
expect(after).toBe(before + 1);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { eq } from "drizzle-orm";
|
|
3
|
+
import { model } from "tests/base";
|
|
4
|
+
import { db } from "tests/db";
|
|
5
|
+
import * as schema from "tests/schema";
|
|
6
|
+
import { esc } from "@/model";
|
|
7
|
+
|
|
8
|
+
const postsModel = model("userPosts", {});
|
|
9
|
+
|
|
10
|
+
async function seedPost(title: string) {
|
|
11
|
+
const [row] = (await postsModel
|
|
12
|
+
.insert({ title, featured: false, userId: 1 })
|
|
13
|
+
.return()) as any[];
|
|
14
|
+
return row;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe("delete", () => {
|
|
18
|
+
test("deletes matching rows — return", async () => {
|
|
19
|
+
const seed = await seedPost("Delete seed");
|
|
20
|
+
|
|
21
|
+
const rows = (await postsModel
|
|
22
|
+
.where({ id: esc(seed.id) })
|
|
23
|
+
.delete()
|
|
24
|
+
.return()) as any[];
|
|
25
|
+
|
|
26
|
+
expect(rows).toBeArray();
|
|
27
|
+
expect(rows[0].id).toBe(seed.id);
|
|
28
|
+
|
|
29
|
+
const remaining = await db
|
|
30
|
+
.select()
|
|
31
|
+
.from(schema.userPosts)
|
|
32
|
+
.where(eq(schema.userPosts.id, seed.id));
|
|
33
|
+
|
|
34
|
+
expect(remaining).toHaveLength(0);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("returnFirst — returns the deleted row", async () => {
|
|
38
|
+
const seed = await seedPost("Delete returnFirst");
|
|
39
|
+
|
|
40
|
+
const row = (await postsModel
|
|
41
|
+
.where({ id: esc(seed.id) })
|
|
42
|
+
.delete()
|
|
43
|
+
.returnFirst()) as any;
|
|
44
|
+
|
|
45
|
+
expect(row).toBeDefined();
|
|
46
|
+
expect(row.id).toBe(seed.id);
|
|
47
|
+
expect(row.title).toBe("Delete returnFirst");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("return > omit — removes specified fields", async () => {
|
|
51
|
+
const seed = await seedPost("Delete omit");
|
|
52
|
+
|
|
53
|
+
const rows = (await postsModel
|
|
54
|
+
.where({ id: esc(seed.id) })
|
|
55
|
+
.delete()
|
|
56
|
+
.return()
|
|
57
|
+
.omit({ likes: true, views: true })) as any[];
|
|
58
|
+
|
|
59
|
+
expect(rows[0].likes).toBeUndefined();
|
|
60
|
+
expect(rows[0].views).toBeUndefined();
|
|
61
|
+
expect(rows[0].title).toBe("Delete omit");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("returnFirst > safe — success", async () => {
|
|
65
|
+
const seed = await seedPost("Delete safe");
|
|
66
|
+
|
|
67
|
+
const result = await postsModel
|
|
68
|
+
.where({ id: esc(seed.id) })
|
|
69
|
+
.delete()
|
|
70
|
+
.returnFirst()
|
|
71
|
+
.safe();
|
|
72
|
+
|
|
73
|
+
expect(result.error).toBeUndefined();
|
|
74
|
+
expect(result.data).toBeDefined();
|
|
75
|
+
expect((result.data as any).id).toBe(seed.id);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("row is gone after delete", async () => {
|
|
79
|
+
const seed = await seedPost("Delete verify gone");
|
|
80
|
+
|
|
81
|
+
await postsModel.where({ id: esc(seed.id) }).delete();
|
|
82
|
+
|
|
83
|
+
const remaining = await db
|
|
84
|
+
.select()
|
|
85
|
+
.from(schema.userPosts)
|
|
86
|
+
.where(eq(schema.userPosts.id, seed.id));
|
|
87
|
+
|
|
88
|
+
expect(remaining).toHaveLength(0);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { eq } from "drizzle-orm";
|
|
3
|
+
import { model } from "tests/base";
|
|
4
|
+
import { db } from "tests/db";
|
|
5
|
+
import * as schema from "tests/schema";
|
|
6
|
+
import { esc } from "@/model";
|
|
7
|
+
|
|
8
|
+
const userModel = model("user", {});
|
|
9
|
+
const userModelFormat = model("user", {
|
|
10
|
+
format({ secretField, isVerified, ...rest }) {
|
|
11
|
+
return {
|
|
12
|
+
...rest,
|
|
13
|
+
isVerified: !!isVerified,
|
|
14
|
+
customField: "Hello World" as const,
|
|
15
|
+
};
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("find", () => {
|
|
20
|
+
// -----------------------------------------------------------------------
|
|
21
|
+
// findFirst
|
|
22
|
+
// -----------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
describe("findFirst", () => {
|
|
25
|
+
test("returns a single row", async () => {
|
|
26
|
+
const user = await userModel.findFirst();
|
|
27
|
+
|
|
28
|
+
expect(user).toBeDefined();
|
|
29
|
+
expect(user.id).toBeNumber();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("where — equality", async () => {
|
|
33
|
+
const user = await userModel.where({ name: esc("Alex") }).findFirst();
|
|
34
|
+
|
|
35
|
+
expect(user).toBeDefined();
|
|
36
|
+
expect(user.name).toBe("Alex");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("where — like operator", async () => {
|
|
40
|
+
const user = await userModel.where({ name: { like: "An%" } }).findFirst();
|
|
41
|
+
|
|
42
|
+
expect(user).toBeDefined();
|
|
43
|
+
expect(user.name.startsWith("An")).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("where — or combinator", async () => {
|
|
47
|
+
const user = await userModel
|
|
48
|
+
.where({ name: { or: [esc("Alex"), esc("Anna")] } })
|
|
49
|
+
.findFirst();
|
|
50
|
+
|
|
51
|
+
expect(user).toBeDefined();
|
|
52
|
+
expect(["Alex", "Anna"]).toContain(user.name);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("select — picks only specified fields", async () => {
|
|
56
|
+
const user = await userModel.findFirst().select({
|
|
57
|
+
name: true,
|
|
58
|
+
age: true,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
expect(user.name).toBeDefined();
|
|
62
|
+
expect(user.age).toBeDefined();
|
|
63
|
+
// @ts-expect-error
|
|
64
|
+
expect(user.email).toBeUndefined();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("exclude — removes specified fields", async () => {
|
|
68
|
+
const user = await userModel.findFirst().exclude({
|
|
69
|
+
name: true,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// @ts-expect-error
|
|
73
|
+
expect(user.name).toBeUndefined();
|
|
74
|
+
expect(user.id).toBeDefined();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("format — applies format function", async () => {
|
|
78
|
+
const user = await userModelFormat.findFirst();
|
|
79
|
+
|
|
80
|
+
expect(user.customField).toBe("Hello World");
|
|
81
|
+
// @ts-expect-error
|
|
82
|
+
expect(user.secretField).toBeUndefined();
|
|
83
|
+
expect(typeof user.isVerified).toBe("boolean");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("raw — skips format", async () => {
|
|
87
|
+
const user = await userModelFormat.findFirst().raw();
|
|
88
|
+
|
|
89
|
+
expect(user.secretField).toBeDefined();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("matches raw drizzle query", async () => {
|
|
93
|
+
const user = await userModel.where({ name: esc("Alex") }).findFirst();
|
|
94
|
+
|
|
95
|
+
const [expected] = await db
|
|
96
|
+
.select()
|
|
97
|
+
.from(schema.user)
|
|
98
|
+
.where(eq(schema.user.name, "Alex"))
|
|
99
|
+
.limit(1);
|
|
100
|
+
|
|
101
|
+
expect(user).toEqual(expected as any);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("safe — wraps result in { data, error }", async () => {
|
|
105
|
+
const result = await userModel.findFirst().safe();
|
|
106
|
+
|
|
107
|
+
expect(result.error).toBeUndefined();
|
|
108
|
+
expect(result.data).toBeDefined();
|
|
109
|
+
expect((result.data as any).id).toBeNumber();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// -----------------------------------------------------------------------
|
|
114
|
+
// findMany
|
|
115
|
+
// -----------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
describe("findMany", () => {
|
|
118
|
+
test("returns all rows", async () => {
|
|
119
|
+
const users = await userModel.findMany();
|
|
120
|
+
const expected = await db.select().from(schema.user);
|
|
121
|
+
|
|
122
|
+
expect(users).toBeArray();
|
|
123
|
+
expect(users).toHaveLength((expected as any[]).length);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("where — equality", async () => {
|
|
127
|
+
const users = await userModel.where({ name: esc("Alex") }).findMany();
|
|
128
|
+
|
|
129
|
+
expect(users).toBeArray();
|
|
130
|
+
for (const user of users) {
|
|
131
|
+
expect(user.name).toBe("Alex");
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("where — multiple filters (AND)", async () => {
|
|
136
|
+
const users = await userModel
|
|
137
|
+
.where({
|
|
138
|
+
name: esc("Alex"),
|
|
139
|
+
isVerified: esc(false),
|
|
140
|
+
})
|
|
141
|
+
.findMany();
|
|
142
|
+
|
|
143
|
+
for (const user of users) {
|
|
144
|
+
expect(user.name).toBe("Alex");
|
|
145
|
+
expect(user.isVerified).toBe(false);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("where — gte operator", async () => {
|
|
150
|
+
const users = await userModel.where({ age: { gte: esc(18) } }).findMany();
|
|
151
|
+
|
|
152
|
+
for (const user of users) {
|
|
153
|
+
expect(user.age).toBeGreaterThanOrEqual(18);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("select — picks only specified fields", async () => {
|
|
158
|
+
const users = await userModel.findMany().select({
|
|
159
|
+
name: true,
|
|
160
|
+
age: true,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
for (const user of users) {
|
|
164
|
+
expect(user.name).toBeDefined();
|
|
165
|
+
expect(user.age).toBeDefined();
|
|
166
|
+
// @ts-expect-error
|
|
167
|
+
expect(user.email).toBeUndefined();
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("exclude — removes specified fields", async () => {
|
|
172
|
+
const users = await userModel.findMany().exclude({
|
|
173
|
+
name: true,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
for (const user of users) {
|
|
177
|
+
// @ts-expect-error
|
|
178
|
+
expect(user.name).toBeUndefined();
|
|
179
|
+
expect(user.id).toBeDefined();
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("format — applies format function", async () => {
|
|
184
|
+
const users = await userModelFormat.findMany();
|
|
185
|
+
|
|
186
|
+
for (const user of users) {
|
|
187
|
+
expect(user.customField).toBe("Hello World");
|
|
188
|
+
// @ts-expect-error
|
|
189
|
+
expect(user.secretField).toBeUndefined();
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("raw — skips format", async () => {
|
|
194
|
+
const users = await userModelFormat.findMany().raw();
|
|
195
|
+
|
|
196
|
+
for (const user of users) {
|
|
197
|
+
expect(user.secretField).toBeDefined();
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("safe — wraps result in { data, error }", async () => {
|
|
202
|
+
const result = await userModel.findMany().safe();
|
|
203
|
+
|
|
204
|
+
expect(result.error).toBeUndefined();
|
|
205
|
+
expect(result.data).toBeDefined();
|
|
206
|
+
expect(result.data).toBeArray();
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { eq, inArray } from "drizzle-orm";
|
|
3
|
+
import { model } from "tests/base";
|
|
4
|
+
import { db } from "tests/db";
|
|
5
|
+
import * as schema from "tests/schema";
|
|
6
|
+
|
|
7
|
+
const postsModel = model("userPosts", {});
|
|
8
|
+
const userModel = model("user", {});
|
|
9
|
+
|
|
10
|
+
function uid(): string {
|
|
11
|
+
return `${Date.now()}-${Math.random()}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe("insert", () => {
|
|
15
|
+
test("single row — no return", async () => {
|
|
16
|
+
await postsModel.insert({
|
|
17
|
+
title: "Insert no-return",
|
|
18
|
+
featured: false,
|
|
19
|
+
userId: 1,
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("single row — return", async () => {
|
|
24
|
+
const rows = await postsModel
|
|
25
|
+
.insert({
|
|
26
|
+
title: "Insert return",
|
|
27
|
+
featured: true,
|
|
28
|
+
userId: 1,
|
|
29
|
+
})
|
|
30
|
+
.return();
|
|
31
|
+
|
|
32
|
+
expect(rows).toBeArray();
|
|
33
|
+
expect((rows as any[])[0].title).toBe("Insert return");
|
|
34
|
+
expect((rows as any[])[0].featured).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("single row — returnFirst", async () => {
|
|
38
|
+
const row = await postsModel
|
|
39
|
+
.insert({
|
|
40
|
+
title: "Insert returnFirst",
|
|
41
|
+
featured: false,
|
|
42
|
+
userId: 1,
|
|
43
|
+
})
|
|
44
|
+
.returnFirst();
|
|
45
|
+
|
|
46
|
+
expect(row).toBeDefined();
|
|
47
|
+
expect((row as any).id).toBeNumber();
|
|
48
|
+
expect((row as any).title).toBe("Insert returnFirst");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("single row — matches raw drizzle", async () => {
|
|
52
|
+
const [inserted] = (await postsModel
|
|
53
|
+
.insert({
|
|
54
|
+
title: "Insert drizzle check",
|
|
55
|
+
featured: null,
|
|
56
|
+
userId: 1,
|
|
57
|
+
})
|
|
58
|
+
.return()) as any[];
|
|
59
|
+
|
|
60
|
+
const [expected] = await db
|
|
61
|
+
.select()
|
|
62
|
+
.from(schema.userPosts)
|
|
63
|
+
.where(eq(schema.userPosts.id, inserted.id));
|
|
64
|
+
|
|
65
|
+
expect(inserted).toEqual(expected as any);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("single row with defaults", async () => {
|
|
69
|
+
const row = (await postsModel
|
|
70
|
+
.insert({
|
|
71
|
+
title: "Insert defaults",
|
|
72
|
+
userId: 1,
|
|
73
|
+
})
|
|
74
|
+
.returnFirst()) as any;
|
|
75
|
+
|
|
76
|
+
expect(row.likes).toBe(0);
|
|
77
|
+
expect(row.views).toBe(0);
|
|
78
|
+
expect(row.description).toBeNull();
|
|
79
|
+
expect(row.featured).toBeNull();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("multiple rows — return", async () => {
|
|
83
|
+
const rows = (await postsModel
|
|
84
|
+
.insert([
|
|
85
|
+
{ title: "Bulk 1", featured: false, userId: 1 },
|
|
86
|
+
{ title: "Bulk 2", featured: true, userId: 1 },
|
|
87
|
+
])
|
|
88
|
+
.return()) as any[];
|
|
89
|
+
|
|
90
|
+
const ids = rows.map((r) => r.id);
|
|
91
|
+
const expected = await db
|
|
92
|
+
.select()
|
|
93
|
+
.from(schema.userPosts)
|
|
94
|
+
.where(inArray(schema.userPosts.id, ids));
|
|
95
|
+
|
|
96
|
+
expect(rows).toHaveLength(2);
|
|
97
|
+
expect(rows[0].title).toBe("Bulk 1");
|
|
98
|
+
expect(rows[1].title).toBe("Bulk 2");
|
|
99
|
+
expect(rows.sort((a, b) => a.id - b.id)).toEqual(
|
|
100
|
+
(expected as any[]).sort((a, b) => a.id - b.id)
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("return > omit — removes specified fields", async () => {
|
|
105
|
+
const rows = (await userModel
|
|
106
|
+
.insert({
|
|
107
|
+
email: `${uid()}@test.com`,
|
|
108
|
+
name: "Omit test",
|
|
109
|
+
age: 99,
|
|
110
|
+
secretField: 42,
|
|
111
|
+
})
|
|
112
|
+
.return()
|
|
113
|
+
.omit({ age: true, secretField: true })) as any[];
|
|
114
|
+
|
|
115
|
+
for (const row of rows) {
|
|
116
|
+
expect(row.age).toBeUndefined();
|
|
117
|
+
expect(row.secretField).toBeUndefined();
|
|
118
|
+
expect(row.name).toBe("Omit test");
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("returnFirst > omit — removes specified fields", async () => {
|
|
123
|
+
const row = (await userModel
|
|
124
|
+
.insert({
|
|
125
|
+
email: `${uid()}@test.com`,
|
|
126
|
+
name: "Omit first test",
|
|
127
|
+
age: 55,
|
|
128
|
+
secretField: 7,
|
|
129
|
+
})
|
|
130
|
+
.returnFirst()
|
|
131
|
+
.omit({ age: true, secretField: true })) as any;
|
|
132
|
+
|
|
133
|
+
expect(row.age).toBeUndefined();
|
|
134
|
+
expect(row.secretField).toBeUndefined();
|
|
135
|
+
expect(row.name).toBe("Omit first test");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("returnFirst > safe — success", async () => {
|
|
139
|
+
const result = await userModel
|
|
140
|
+
.insert({
|
|
141
|
+
email: `${uid()}@test.com`,
|
|
142
|
+
name: "Safe test",
|
|
143
|
+
age: 20,
|
|
144
|
+
})
|
|
145
|
+
.returnFirst()
|
|
146
|
+
.safe();
|
|
147
|
+
|
|
148
|
+
expect(result.error).toBeUndefined();
|
|
149
|
+
expect(result.data).toBeDefined();
|
|
150
|
+
expect((result.data as any).name).toBe("Safe test");
|
|
151
|
+
});
|
|
152
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { model } from "tests/base";
|
|
3
|
+
import { esc } from "@/model";
|
|
4
|
+
|
|
5
|
+
const userModel = model("user", {});
|
|
6
|
+
|
|
7
|
+
function uid(): string {
|
|
8
|
+
return `${Date.now()}-${Math.random()}`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe("safe", () => {
|
|
12
|
+
describe("query — safe()", () => {
|
|
13
|
+
test("findFirst — success returns { data, error: undefined }", async () => {
|
|
14
|
+
const result = await userModel.findFirst().safe();
|
|
15
|
+
|
|
16
|
+
expect(result.error).toBeUndefined();
|
|
17
|
+
expect(result.data).toBeDefined();
|
|
18
|
+
expect((result.data as any).id).toBeNumber();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("findMany — success returns { data, error: undefined }", async () => {
|
|
22
|
+
const result = await userModel.findMany().safe();
|
|
23
|
+
|
|
24
|
+
expect(result.error).toBeUndefined();
|
|
25
|
+
expect(result.data).toBeArray();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("findFirst with where — success", async () => {
|
|
29
|
+
const result = await userModel
|
|
30
|
+
.where({ name: esc("Alex") })
|
|
31
|
+
.findFirst()
|
|
32
|
+
.safe();
|
|
33
|
+
|
|
34
|
+
expect(result.error).toBeUndefined();
|
|
35
|
+
expect(result.data).toBeDefined();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe("mutation — safe()", () => {
|
|
40
|
+
test("insert > returnFirst > safe — success", async () => {
|
|
41
|
+
const result = await userModel
|
|
42
|
+
.insert({
|
|
43
|
+
email: `${uid()}@safe.com`,
|
|
44
|
+
name: "Safe insert",
|
|
45
|
+
age: 25,
|
|
46
|
+
})
|
|
47
|
+
.returnFirst()
|
|
48
|
+
.safe();
|
|
49
|
+
|
|
50
|
+
expect(result.error).toBeUndefined();
|
|
51
|
+
expect(result.data).toBeDefined();
|
|
52
|
+
expect((result.data as any).name).toBe("Safe insert");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("insert > return > safe — success", async () => {
|
|
56
|
+
const result = await userModel
|
|
57
|
+
.insert({
|
|
58
|
+
email: `${uid()}@safe.com`,
|
|
59
|
+
name: "Safe array insert",
|
|
60
|
+
age: 30,
|
|
61
|
+
})
|
|
62
|
+
.return()
|
|
63
|
+
.safe();
|
|
64
|
+
|
|
65
|
+
expect(result.error).toBeUndefined();
|
|
66
|
+
expect(result.data).toBeDefined();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("insert > safe — error on duplicate email", async () => {
|
|
70
|
+
const email = `${uid()}@safe-dup.com`;
|
|
71
|
+
|
|
72
|
+
await userModel.insert({
|
|
73
|
+
email,
|
|
74
|
+
name: "First",
|
|
75
|
+
age: 20,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const result = await userModel
|
|
79
|
+
.insert({
|
|
80
|
+
email,
|
|
81
|
+
name: "Duplicate",
|
|
82
|
+
age: 20,
|
|
83
|
+
})
|
|
84
|
+
.returnFirst()
|
|
85
|
+
.safe();
|
|
86
|
+
|
|
87
|
+
expect(result.error).toBeDefined();
|
|
88
|
+
expect(result.data).toBeUndefined();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { eq } from "drizzle-orm";
|
|
3
|
+
import { model } from "tests/base";
|
|
4
|
+
import { db } from "tests/db";
|
|
5
|
+
import * as schema from "tests/schema";
|
|
6
|
+
import { esc } from "@/model";
|
|
7
|
+
|
|
8
|
+
const postsModel = model("userPosts", {});
|
|
9
|
+
|
|
10
|
+
async function seedPost(title: string) {
|
|
11
|
+
const [row] = (await postsModel
|
|
12
|
+
.insert({ title, featured: false, userId: 1 })
|
|
13
|
+
.return()) as any[];
|
|
14
|
+
return row;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe("update", () => {
|
|
18
|
+
test("updates matching rows — return", async () => {
|
|
19
|
+
const seed = await seedPost("Update seed");
|
|
20
|
+
|
|
21
|
+
const rows = (await postsModel
|
|
22
|
+
.where({ id: esc(seed.id) })
|
|
23
|
+
.update({ title: "Updated title", featured: true })
|
|
24
|
+
.return()) as any[];
|
|
25
|
+
|
|
26
|
+
expect(rows).toBeArray();
|
|
27
|
+
expect(rows[0].id).toBe(seed.id);
|
|
28
|
+
expect(rows[0].title).toBe("Updated title");
|
|
29
|
+
expect(rows[0].featured).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("matches raw drizzle after update", async () => {
|
|
33
|
+
const seed = await seedPost("Update drizzle check");
|
|
34
|
+
|
|
35
|
+
await postsModel
|
|
36
|
+
.where({ id: esc(seed.id) })
|
|
37
|
+
.update({ title: "Verified update" })
|
|
38
|
+
.return();
|
|
39
|
+
|
|
40
|
+
const [expected] = await db
|
|
41
|
+
.select()
|
|
42
|
+
.from(schema.userPosts)
|
|
43
|
+
.where(eq(schema.userPosts.id, seed.id));
|
|
44
|
+
|
|
45
|
+
expect((expected as any).title).toBe("Verified update");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("returnFirst — returns single object", async () => {
|
|
49
|
+
const seed = await seedPost("Update returnFirst");
|
|
50
|
+
|
|
51
|
+
const row = (await postsModel
|
|
52
|
+
.where({ id: esc(seed.id) })
|
|
53
|
+
.update({ title: "First updated" })
|
|
54
|
+
.returnFirst()) as any;
|
|
55
|
+
|
|
56
|
+
expect(row).toBeDefined();
|
|
57
|
+
expect(row.id).toBe(seed.id);
|
|
58
|
+
expect(row.title).toBe("First updated");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("return > omit — removes specified fields", async () => {
|
|
62
|
+
const seed = await seedPost("Update omit");
|
|
63
|
+
|
|
64
|
+
const rows = (await postsModel
|
|
65
|
+
.where({ id: esc(seed.id) })
|
|
66
|
+
.update({ title: "Omitted update" })
|
|
67
|
+
.return()
|
|
68
|
+
.omit({ likes: true, views: true })) as any[];
|
|
69
|
+
|
|
70
|
+
expect(rows[0].likes).toBeUndefined();
|
|
71
|
+
expect(rows[0].views).toBeUndefined();
|
|
72
|
+
expect(rows[0].title).toBe("Omitted update");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("returnFirst > safe — success", async () => {
|
|
76
|
+
const seed = await seedPost("Update safe");
|
|
77
|
+
|
|
78
|
+
const result = await postsModel
|
|
79
|
+
.where({ id: esc(seed.id) })
|
|
80
|
+
.update({ title: "Safe updated" })
|
|
81
|
+
.returnFirst()
|
|
82
|
+
.safe();
|
|
83
|
+
|
|
84
|
+
expect(result.error).toBeUndefined();
|
|
85
|
+
expect(result.data).toBeDefined();
|
|
86
|
+
expect((result.data as any).title).toBe("Safe updated");
|
|
87
|
+
});
|
|
88
|
+
});
|