@casekit/orm2 0.0.0-20250331181319 → 0.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.
Files changed (94) hide show
  1. package/build/builders/buildCount.d.ts +2 -1
  2. package/build/builders/buildCount.js +5 -8
  3. package/build/builders/buildCount.test.js +5 -5
  4. package/build/builders/buildCreate.d.ts +2 -1
  5. package/build/builders/buildCreate.js +6 -3
  6. package/build/builders/buildCreate.test.js +4 -4
  7. package/build/builders/buildDelete.d.ts +2 -1
  8. package/build/builders/buildDelete.js +2 -2
  9. package/build/builders/buildDelete.test.js +8 -8
  10. package/build/builders/buildFind.d.ts +2 -1
  11. package/build/builders/buildFind.js +5 -8
  12. package/build/builders/buildFind.test.js +4 -4
  13. package/build/builders/buildUpdate.d.ts +2 -1
  14. package/build/builders/buildUpdate.js +6 -3
  15. package/build/builders/buildUpdate.test.js +8 -8
  16. package/build/builders/buildWhere.d.ts +2 -1
  17. package/build/builders/buildWhere.js +30 -8
  18. package/build/builders/buildWhere.test.js +39 -24
  19. package/build/connection.d.ts +2 -2
  20. package/build/index.d.ts +1 -1
  21. package/build/index.js +1 -1
  22. package/build/orm.count.d.ts +2 -10
  23. package/build/orm.count.js +2 -11
  24. package/build/orm.createMany.d.ts +2 -1
  25. package/build/orm.createMany.js +2 -2
  26. package/build/orm.createOne.d.ts +2 -1
  27. package/build/orm.createOne.js +2 -2
  28. package/build/orm.d.ts +2 -2
  29. package/build/orm.deleteMany.d.ts +2 -1
  30. package/build/orm.deleteMany.js +2 -2
  31. package/build/orm.deleteOne.d.ts +2 -1
  32. package/build/orm.deleteOne.js +2 -2
  33. package/build/orm.findMany.d.ts +2 -1
  34. package/build/orm.findMany.js +3 -3
  35. package/build/orm.findOne.d.ts +2 -1
  36. package/build/orm.findOne.js +11 -19
  37. package/build/orm.js +13 -13
  38. package/build/orm.restrict.d.ts +2 -1
  39. package/build/orm.restrict.js +2 -2
  40. package/build/orm.updateMany.d.ts +2 -1
  41. package/build/orm.updateMany.js +3 -3
  42. package/build/orm.updateOne.d.ts +2 -1
  43. package/build/orm.updateOne.js +2 -2
  44. package/build/tests/orm.findMany.includeManyToOne.test.js +21 -5
  45. package/build/tests/orm.findOne.includeManyToMany.test.js +270 -0
  46. package/build/tests/orm.findOne.includeManyToOne.test.js +280 -0
  47. package/build/tests/orm.findOne.includeOneToMany.test.js +454 -0
  48. package/build/tests/orm.findOne.select.test.js +205 -0
  49. package/build/tests/orm.findOne.where.test.js +471 -0
  50. package/build/tests/orm.middleware.findOne.test.d.ts +1 -0
  51. package/build/tests/orm.middleware.set.test.d.ts +1 -0
  52. package/build/tests/orm.middleware.set.test.js +100 -0
  53. package/build/tests/orm.middleware.updateMany.test.d.ts +1 -0
  54. package/build/tests/orm.middleware.updateOne.test.d.ts +1 -0
  55. package/build/tests/orm.middleware.values.test.d.ts +1 -0
  56. package/build/tests/orm.middleware.values.test.js +126 -0
  57. package/build/tests/orm.middleware.where.test.d.ts +1 -0
  58. package/build/tests/orm.middleware.where.test.js +1054 -0
  59. package/build/tests/util/db.d.ts +52 -70
  60. package/build/types/CreateOneResult.d.ts +6 -2
  61. package/build/types/CreateOneResult.test-d.js +3 -0
  62. package/build/types/Middleware.d.ts +14 -0
  63. package/build/types/Middleware.js +37 -1
  64. package/build/types/WhereClause.d.ts +8 -8
  65. package/build/util/applySetMiddleware.d.ts +5 -0
  66. package/build/util/applySetMiddleware.js +7 -0
  67. package/build/util/applyValuesMiddleware.d.ts +5 -0
  68. package/build/util/applyValuesMiddleware.js +7 -0
  69. package/build/util/applyWhereMiddleware.d.ts +5 -0
  70. package/build/util/applyWhereMiddleware.js +7 -0
  71. package/build/util/applyWhereMiddleware.test.d.ts +1 -0
  72. package/build/util/applyWhereMiddleware.test.js +88 -0
  73. package/build/util/resultSchema.d.ts +6 -6
  74. package/package.json +23 -24
  75. /package/build/tests/{orm.count.middleware.test.d.ts → orm.findOne.includeManyToMany.test.d.ts} +0 -0
  76. /package/build/tests/{orm.createMany.middleware.test.d.ts → orm.findOne.includeManyToOne.test.d.ts} +0 -0
  77. /package/build/tests/{orm.createOne.middleware.test.d.ts → orm.findOne.includeOneToMany.test.d.ts} +0 -0
  78. /package/build/tests/{orm.deleteMany.middleware.test.d.ts → orm.findOne.select.test.d.ts} +0 -0
  79. /package/build/tests/{orm.deleteOne.middleware.test.d.ts → orm.findOne.where.test.d.ts} +0 -0
  80. /package/build/tests/{orm.findMany.middleware.test.d.ts → orm.middleware.count.test.d.ts} +0 -0
  81. /package/build/tests/{orm.count.middleware.test.js → orm.middleware.count.test.js} +0 -0
  82. /package/build/tests/{orm.findOne.middleware.test.d.ts → orm.middleware.createMany.test.d.ts} +0 -0
  83. /package/build/tests/{orm.createMany.middleware.test.js → orm.middleware.createMany.test.js} +0 -0
  84. /package/build/tests/{orm.updateMany.middleware.test.d.ts → orm.middleware.createOne.test.d.ts} +0 -0
  85. /package/build/tests/{orm.createOne.middleware.test.js → orm.middleware.createOne.test.js} +0 -0
  86. /package/build/tests/{orm.updateOne.middleware.test.d.ts → orm.middleware.deleteMany.test.d.ts} +0 -0
  87. /package/build/tests/{orm.deleteMany.middleware.test.js → orm.middleware.deleteMany.test.js} +0 -0
  88. /package/build/{types/BaseFindParams.d.ts → tests/orm.middleware.deleteOne.test.d.ts} +0 -0
  89. /package/build/tests/{orm.deleteOne.middleware.test.js → orm.middleware.deleteOne.test.js} +0 -0
  90. /package/build/{types/BaseFindParams.js → tests/orm.middleware.findMany.test.d.ts} +0 -0
  91. /package/build/tests/{orm.findMany.middleware.test.js → orm.middleware.findMany.test.js} +0 -0
  92. /package/build/tests/{orm.findOne.middleware.test.js → orm.middleware.findOne.test.js} +0 -0
  93. /package/build/tests/{orm.updateMany.middleware.test.js → orm.middleware.updateMany.test.js} +0 -0
  94. /package/build/tests/{orm.updateOne.middleware.test.js → orm.middleware.updateOne.test.js} +0 -0
@@ -0,0 +1,1054 @@
1
+ import { afterAll, beforeAll, beforeEach, describe, expect, test, } from "vitest";
2
+ import { config } from "@casekit/orm2-fixtures";
3
+ import { $in, $like, $not } from "../operators.js";
4
+ import { orm } from "../orm.js";
5
+ import { mockLogger } from "./util/logger.js";
6
+ const softdelete = () => ({
7
+ where: (config, modelName, where) => {
8
+ if ("deletedAt" in (config.models[modelName]?.fields ?? {})) {
9
+ return { deletedAt: null, ...where };
10
+ }
11
+ else {
12
+ return where;
13
+ }
14
+ },
15
+ });
16
+ describe("where middleware", () => {
17
+ const logger = mockLogger();
18
+ let db;
19
+ beforeEach(() => {
20
+ logger.clear();
21
+ });
22
+ beforeAll(async () => {
23
+ db = orm({ ...config, logger }).middleware([softdelete()]);
24
+ await db.connect();
25
+ });
26
+ afterAll(async () => {
27
+ await db.close();
28
+ });
29
+ describe("findOne and findMany", () => {
30
+ test("applies to top level where clauses", async () => {
31
+ await db.transact(async (db) => {
32
+ await db.createMany("user", {
33
+ values: [
34
+ {
35
+ id: 1,
36
+ name: "Test User",
37
+ email: "test@example.com",
38
+ role: "user",
39
+ },
40
+ {
41
+ id: 2,
42
+ name: "Test User 2",
43
+ email: "test2@example.com",
44
+ role: "user",
45
+ },
46
+ ],
47
+ returning: ["id"],
48
+ });
49
+ const usersBeforeDelete = await db.findMany("user", {
50
+ select: ["id", "name", "email", "deletedAt"],
51
+ orderBy: ["id"],
52
+ });
53
+ expect(usersBeforeDelete).toEqual([
54
+ {
55
+ id: 1,
56
+ name: "Test User",
57
+ email: "test@example.com",
58
+ deletedAt: null,
59
+ },
60
+ {
61
+ id: 2,
62
+ name: "Test User 2",
63
+ email: "test2@example.com",
64
+ deletedAt: null,
65
+ },
66
+ ]);
67
+ await db.updateOne("user", {
68
+ where: { id: 1 },
69
+ set: { deletedAt: new Date() },
70
+ });
71
+ const usersAfterDelete = await db.findMany("user", {
72
+ select: ["id", "name", "email", "deletedAt"],
73
+ orderBy: ["id"],
74
+ });
75
+ expect(usersAfterDelete).toEqual([
76
+ {
77
+ id: 2,
78
+ name: "Test User 2",
79
+ email: "test2@example.com",
80
+ deletedAt: null,
81
+ },
82
+ ]);
83
+ }, { rollback: true });
84
+ });
85
+ test("middleware is still applied after orm.restrict", async () => {
86
+ const restrictedDb = db.restrict(["user", "post"]);
87
+ await restrictedDb.transact(async (db) => {
88
+ await db.createMany("user", {
89
+ values: [
90
+ {
91
+ id: 3,
92
+ name: "Test User 3",
93
+ email: "test3@example.com",
94
+ role: "user",
95
+ deletedAt: null,
96
+ },
97
+ {
98
+ id: 4,
99
+ name: "Test User 4",
100
+ email: "test4@example.com",
101
+ role: "user",
102
+ deletedAt: new Date(),
103
+ },
104
+ ],
105
+ returning: ["id"],
106
+ });
107
+ const users = await db.findMany("user", {
108
+ select: ["id", "name", "email", "deletedAt"],
109
+ orderBy: ["id"],
110
+ });
111
+ expect(users).toEqual([
112
+ {
113
+ id: 3,
114
+ name: "Test User 3",
115
+ email: "test3@example.com",
116
+ deletedAt: null,
117
+ },
118
+ ]);
119
+ const count = await db.count("user", {});
120
+ expect(count).toBe(1);
121
+ }, { rollback: true });
122
+ });
123
+ test("applies to N:1 relations without where clause", async () => {
124
+ await db.transact(async (db) => {
125
+ const activeUser = await db.createOne("user", {
126
+ values: {
127
+ id: 5,
128
+ name: "Active User",
129
+ email: "active@example.com",
130
+ role: "user",
131
+ },
132
+ returning: ["id"],
133
+ });
134
+ const deletedUser = await db.createOne("user", {
135
+ values: {
136
+ id: 6,
137
+ name: "Deleted User",
138
+ email: "deleted@example.com",
139
+ role: "user",
140
+ deletedAt: new Date(),
141
+ },
142
+ returning: ["id"],
143
+ });
144
+ await db.createOne("post", {
145
+ values: {
146
+ id: 1,
147
+ title: "Post by Active User",
148
+ content: "Content",
149
+ authorId: activeUser.id,
150
+ },
151
+ });
152
+ await db.createOne("post", {
153
+ values: {
154
+ id: 2,
155
+ title: "Post by Deleted User",
156
+ content: "Content",
157
+ authorId: deletedUser.id,
158
+ },
159
+ });
160
+ // Without where clause on relation - should still filter deleted author
161
+ const posts = await db.findMany("post", {
162
+ select: ["id", "title"],
163
+ include: {
164
+ author: {
165
+ select: ["id", "name"],
166
+ },
167
+ },
168
+ orderBy: ["id"],
169
+ });
170
+ expect(posts).toHaveLength(1);
171
+ expect(posts).toEqual([
172
+ {
173
+ id: 1,
174
+ title: "Post by Active User",
175
+ author: {
176
+ id: 5,
177
+ name: "Active User",
178
+ },
179
+ },
180
+ ]);
181
+ }, { rollback: true });
182
+ });
183
+ test("applies to N:1 relations with where clause", async () => {
184
+ await db.transact(async (db) => {
185
+ await db.createMany("user", {
186
+ values: [
187
+ {
188
+ id: 7,
189
+ name: "Admin User",
190
+ email: "admin@example.com",
191
+ role: "admin",
192
+ },
193
+ {
194
+ id: 8,
195
+ name: "Regular User",
196
+ email: "regular@example.com",
197
+ role: "user",
198
+ },
199
+ {
200
+ id: 9,
201
+ name: "Deleted Admin",
202
+ email: "deleted-admin@example.com",
203
+ role: "admin",
204
+ deletedAt: new Date(),
205
+ },
206
+ ],
207
+ });
208
+ await db.createMany("post", {
209
+ values: [
210
+ {
211
+ id: 3,
212
+ title: "Post by Admin",
213
+ content: "Content",
214
+ authorId: 7,
215
+ deletedAt: null,
216
+ },
217
+ {
218
+ id: 4,
219
+ title: "Post by Regular User",
220
+ content: "Content",
221
+ authorId: 8,
222
+ deletedAt: null,
223
+ },
224
+ {
225
+ id: 5,
226
+ title: "Post by Deleted Admin",
227
+ content: "Content",
228
+ authorId: 9,
229
+ deletedAt: null,
230
+ },
231
+ ],
232
+ });
233
+ // With where clause on relation - should combine with middleware
234
+ const posts = await db.findMany("post", {
235
+ select: ["id", "title"],
236
+ include: {
237
+ author: {
238
+ select: ["id", "name", "role"],
239
+ where: { role: "admin" },
240
+ },
241
+ },
242
+ orderBy: ["id"],
243
+ });
244
+ expect(posts).toHaveLength(1);
245
+ expect(posts).toEqual([
246
+ {
247
+ author: {
248
+ id: 7,
249
+ name: "Admin User",
250
+ role: "admin",
251
+ },
252
+ id: 3,
253
+ title: "Post by Admin",
254
+ },
255
+ ]);
256
+ }, { rollback: true });
257
+ });
258
+ test("applies to 1:N relations without where clause", async () => {
259
+ await db.transact(async (db) => {
260
+ const user = await db.createOne("user", {
261
+ values: {
262
+ id: 10,
263
+ name: "Post Author",
264
+ email: "author@example.com",
265
+ role: "user",
266
+ },
267
+ returning: ["id"],
268
+ });
269
+ await db.createMany("post", {
270
+ values: [
271
+ {
272
+ id: 6,
273
+ title: "Active Post 1",
274
+ content: "Content",
275
+ authorId: user.id,
276
+ },
277
+ {
278
+ id: 7,
279
+ title: "Deleted Post",
280
+ content: "Content",
281
+ authorId: user.id,
282
+ deletedAt: new Date(),
283
+ },
284
+ {
285
+ id: 8,
286
+ title: "Active Post 2",
287
+ content: "Content",
288
+ authorId: user.id,
289
+ },
290
+ ],
291
+ });
292
+ const result = await db.findOne("user", {
293
+ select: ["id", "name"],
294
+ include: {
295
+ posts: {
296
+ select: ["id", "title", "deletedAt"],
297
+ orderBy: ["id"],
298
+ },
299
+ },
300
+ where: { id: user.id },
301
+ });
302
+ expect(result.posts).toHaveLength(2);
303
+ expect(result.posts[0].title).toBe("Active Post 1");
304
+ expect(result.posts[1].title).toBe("Active Post 2");
305
+ }, { rollback: true });
306
+ });
307
+ test("applies to 1:N relations with where clause", async () => {
308
+ await db.transact(async (db) => {
309
+ const user = await db.createOne("user", {
310
+ values: {
311
+ id: 11,
312
+ name: "Post Author 2",
313
+ email: "author2@example.com",
314
+ role: "user",
315
+ },
316
+ returning: ["id"],
317
+ });
318
+ await db.createMany("post", {
319
+ values: [
320
+ {
321
+ id: 9,
322
+ title: "Short Post",
323
+ content: "Short",
324
+ authorId: user.id,
325
+ },
326
+ {
327
+ id: 10,
328
+ title: "Long Post",
329
+ content: "This is a long post with lots of content",
330
+ authorId: user.id,
331
+ },
332
+ {
333
+ id: 11,
334
+ title: "Deleted Long Post",
335
+ content: "This was a long post but now deleted",
336
+ authorId: user.id,
337
+ deletedAt: new Date(),
338
+ },
339
+ ],
340
+ });
341
+ const result = await db.findOne("user", {
342
+ select: ["id", "name"],
343
+ include: {
344
+ posts: {
345
+ select: ["id", "title", "content"],
346
+ where: { content: { [$like]: "%long%" } },
347
+ orderBy: ["id"],
348
+ },
349
+ },
350
+ where: { id: user.id },
351
+ });
352
+ expect(result.posts).toHaveLength(1);
353
+ expect(result.posts[0].title).toBe("Long Post");
354
+ }, { rollback: true });
355
+ });
356
+ test("applies to N:N relations", async () => {
357
+ await db.transact(async (db) => {
358
+ const [activeUser, deletedUser, anotherUser] = await db.createMany("user", {
359
+ values: [
360
+ {
361
+ id: 12,
362
+ name: "Active User with Friends",
363
+ email: "active-friends@example.com",
364
+ role: "user",
365
+ },
366
+ {
367
+ id: 13,
368
+ name: "Deleted Friend",
369
+ email: "deleted-friend@example.com",
370
+ role: "user",
371
+ deletedAt: new Date(),
372
+ },
373
+ {
374
+ id: 14,
375
+ name: "Another Active Friend",
376
+ email: "another-friend@example.com",
377
+ role: "user",
378
+ },
379
+ ],
380
+ returning: ["id"],
381
+ });
382
+ await db.createMany("friendship", {
383
+ values: [
384
+ {
385
+ userId: activeUser.id,
386
+ friendId: deletedUser.id,
387
+ },
388
+ {
389
+ userId: activeUser.id,
390
+ friendId: anotherUser.id,
391
+ },
392
+ ],
393
+ });
394
+ const result = await db.findOne("user", {
395
+ select: ["id", "name"],
396
+ include: {
397
+ friends: {
398
+ select: ["id", "name"],
399
+ orderBy: ["id"],
400
+ },
401
+ },
402
+ where: { id: activeUser.id },
403
+ });
404
+ expect(result.friends).toHaveLength(1);
405
+ expect(result.friends[0].name).toBe("Another Active Friend");
406
+ }, { rollback: true });
407
+ });
408
+ test("applies to multiple levels of nested relations", async () => {
409
+ await db.transact(async (db) => {
410
+ const [activeUser, deletedUser, anotherActiveUser] = await db.createMany("user", {
411
+ values: [
412
+ {
413
+ id: 15,
414
+ name: "Active User Multi Level",
415
+ email: "active-multi@example.com",
416
+ role: "user",
417
+ },
418
+ {
419
+ id: 16,
420
+ name: "Deleted User Multi Level",
421
+ email: "deleted-multi@example.com",
422
+ role: "user",
423
+ deletedAt: new Date(),
424
+ },
425
+ {
426
+ id: 17,
427
+ name: "Another Active User",
428
+ email: "another-active@example.com",
429
+ role: "user",
430
+ },
431
+ ],
432
+ returning: ["id"],
433
+ });
434
+ const [activePost, deletedPost] = await db.createMany("post", {
435
+ values: [
436
+ {
437
+ id: 12,
438
+ title: "Active Post Multi",
439
+ content: "Content",
440
+ authorId: activeUser.id,
441
+ },
442
+ {
443
+ id: 13,
444
+ title: "Deleted Post Multi",
445
+ content: "Content",
446
+ authorId: activeUser.id,
447
+ deletedAt: new Date(),
448
+ },
449
+ ],
450
+ returning: ["id"],
451
+ });
452
+ await db.createMany("like", {
453
+ values: [
454
+ {
455
+ id: 1,
456
+ postId: activePost.id,
457
+ userId: anotherActiveUser.id,
458
+ },
459
+ {
460
+ id: 2,
461
+ postId: activePost.id,
462
+ userId: deletedUser.id,
463
+ },
464
+ {
465
+ id: 3,
466
+ postId: deletedPost.id,
467
+ userId: anotherActiveUser.id,
468
+ },
469
+ ],
470
+ });
471
+ const result = await db.findOne("user", {
472
+ select: ["id", "name"],
473
+ include: {
474
+ posts: {
475
+ select: ["id", "title"],
476
+ include: {
477
+ likes: {
478
+ select: ["id"],
479
+ include: {
480
+ user: {
481
+ select: ["id", "name"],
482
+ },
483
+ },
484
+ orderBy: ["id"],
485
+ },
486
+ },
487
+ orderBy: ["id"],
488
+ },
489
+ },
490
+ where: { id: activeUser.id },
491
+ });
492
+ // Should only have 1 active post
493
+ expect(result.posts).toHaveLength(1);
494
+ expect(result.posts[0].title).toBe("Active Post Multi");
495
+ // Should only have 1 like (like by deleted user filtered)
496
+ expect(result.posts[0].likes).toHaveLength(1);
497
+ expect(result.posts[0].likes[0].user.name).toBe("Another Active User");
498
+ }, { rollback: true });
499
+ });
500
+ });
501
+ describe("updateOne", () => {
502
+ test("applies middleware to prevent updating soft-deleted records", async () => {
503
+ await db.transact(async (db) => {
504
+ // Create two users, one active and one soft-deleted
505
+ await db.createMany("user", {
506
+ values: [
507
+ {
508
+ id: 100,
509
+ name: "Active User",
510
+ email: "active@example.com",
511
+ role: "user",
512
+ deletedAt: null,
513
+ },
514
+ {
515
+ id: 101,
516
+ name: "Deleted User",
517
+ email: "deleted@example.com",
518
+ role: "user",
519
+ deletedAt: new Date(),
520
+ },
521
+ ],
522
+ });
523
+ // Try to update the deleted user - should throw because middleware prevents finding it
524
+ await expect(db.updateOne("user", {
525
+ where: { id: 101 },
526
+ set: { name: "Should Not Update" },
527
+ returning: ["id", "name"],
528
+ })).rejects.toThrow("Update one failed to update a row");
529
+ // Verify the deleted user wasn't updated by explicitly querying soft-deleted records
530
+ const deletedUser = await db.findMany("user", {
531
+ where: { id: 101, deletedAt: { [$not]: null } },
532
+ select: ["name", "deletedAt"],
533
+ });
534
+ // Should find the soft-deleted user with original name
535
+ expect(deletedUser).toHaveLength(1);
536
+ expect(deletedUser[0].name).toBe("Deleted User");
537
+ expect(deletedUser[0].deletedAt).toBeTruthy();
538
+ // Update the active user - should work
539
+ const activeResult = await db.updateOne("user", {
540
+ where: { id: 100 },
541
+ set: { name: "Updated Active User" },
542
+ returning: ["id", "name"],
543
+ });
544
+ expect(activeResult).toEqual({
545
+ id: 100,
546
+ name: "Updated Active User",
547
+ });
548
+ }, { rollback: true });
549
+ });
550
+ test("combines explicit where conditions with middleware", async () => {
551
+ await db.transact(async (db) => {
552
+ await db.createMany("user", {
553
+ values: [
554
+ {
555
+ id: 102,
556
+ name: "Admin User",
557
+ email: "admin@example.com",
558
+ role: "admin",
559
+ deletedAt: null,
560
+ },
561
+ {
562
+ id: 103,
563
+ name: "Regular User",
564
+ email: "regular@example.com",
565
+ role: "user",
566
+ deletedAt: null,
567
+ },
568
+ {
569
+ id: 104,
570
+ name: "Deleted Admin",
571
+ email: "deleted-admin@example.com",
572
+ role: "admin",
573
+ deletedAt: new Date(),
574
+ },
575
+ ],
576
+ });
577
+ // Update only admin users - should skip deleted admin
578
+ const result = await db.updateOne("user", {
579
+ where: { role: "admin" },
580
+ set: { name: "Updated Admin" },
581
+ returning: ["id", "name"],
582
+ });
583
+ expect(result).toEqual({
584
+ id: 102,
585
+ name: "Updated Admin",
586
+ });
587
+ // Verify deleted admin wasn't updated by explicitly querying soft-deleted records
588
+ const deletedAdmins = await db.findMany("user", {
589
+ where: { id: 104, deletedAt: { [$not]: null } },
590
+ select: ["name", "deletedAt"],
591
+ });
592
+ // Should find the soft-deleted admin with original name
593
+ expect(deletedAdmins).toHaveLength(1);
594
+ expect(deletedAdmins[0].name).toBe("Deleted Admin");
595
+ expect(deletedAdmins[0].deletedAt).toBeTruthy();
596
+ }, { rollback: true });
597
+ });
598
+ });
599
+ describe("updateMany", () => {
600
+ test("applies middleware to prevent updating soft-deleted records", async () => {
601
+ await db.transact(async (db) => {
602
+ await db.createMany("user", {
603
+ values: [
604
+ {
605
+ id: 105,
606
+ name: "Active User 1",
607
+ email: "active1@example.com",
608
+ role: "user",
609
+ deletedAt: null,
610
+ },
611
+ {
612
+ id: 106,
613
+ name: "Active User 2",
614
+ email: "active2@example.com",
615
+ role: "user",
616
+ deletedAt: null,
617
+ },
618
+ {
619
+ id: 107,
620
+ name: "Deleted User",
621
+ email: "deleted@example.com",
622
+ role: "user",
623
+ deletedAt: new Date(),
624
+ },
625
+ ],
626
+ });
627
+ // Update all users - should only update active ones
628
+ const count = await db.updateMany("user", {
629
+ where: { id: { [$in]: [105, 106, 107] } },
630
+ set: { name: "Bulk Updated" },
631
+ });
632
+ expect(count).toBe(2);
633
+ // Verify only active users are visible and were updated
634
+ const visibleUsers = await db.findMany("user", {
635
+ where: { id: { [$in]: [105, 106, 107] } },
636
+ select: ["id", "name"],
637
+ orderBy: ["id"],
638
+ });
639
+ // Should only see the 2 active users that were updated (deleted user not visible due to middleware)
640
+ expect(visibleUsers).toHaveLength(2);
641
+ expect(visibleUsers).toEqual([
642
+ { id: 105, name: "Bulk Updated" },
643
+ { id: 106, name: "Bulk Updated" },
644
+ ]);
645
+ // Verify the deleted user wasn't updated by explicitly querying soft-deleted records
646
+ const deletedUser = await db.findMany("user", {
647
+ where: { id: 107, deletedAt: { [$not]: null } },
648
+ select: ["name", "deletedAt"],
649
+ });
650
+ // Should find the soft-deleted user with original name
651
+ expect(deletedUser).toHaveLength(1);
652
+ expect(deletedUser[0].name).toBe("Deleted User");
653
+ expect(deletedUser[0].deletedAt).toBeTruthy();
654
+ }, { rollback: true });
655
+ });
656
+ test("combines explicit where conditions with middleware", async () => {
657
+ await db.transact(async (db) => {
658
+ await db.createMany("user", {
659
+ values: [
660
+ {
661
+ id: 108,
662
+ name: "Admin 1",
663
+ email: "admin1@example.com",
664
+ role: "admin",
665
+ deletedAt: null,
666
+ },
667
+ {
668
+ id: 109,
669
+ name: "Admin 2",
670
+ email: "admin2@example.com",
671
+ role: "admin",
672
+ deletedAt: null,
673
+ },
674
+ {
675
+ id: 110,
676
+ name: "User 1",
677
+ email: "user1@example.com",
678
+ role: "user",
679
+ deletedAt: null,
680
+ },
681
+ {
682
+ id: 111,
683
+ name: "Deleted Admin",
684
+ email: "deleted-admin@example.com",
685
+ role: "admin",
686
+ deletedAt: new Date(),
687
+ },
688
+ ],
689
+ });
690
+ // Update only admins - should skip deleted admin
691
+ const count = await db.updateMany("user", {
692
+ where: { role: "admin" },
693
+ set: { name: "Updated Admin" },
694
+ });
695
+ expect(count).toBe(2);
696
+ // Verify only active users are visible and correct ones were updated
697
+ const visibleUsers = await db.findMany("user", {
698
+ where: { id: { [$in]: [108, 109, 110, 111] } },
699
+ select: ["id", "name", "role"],
700
+ orderBy: ["id"],
701
+ });
702
+ // Should only see the 3 active users (deleted admin not visible due to middleware)
703
+ expect(visibleUsers).toHaveLength(3);
704
+ expect(visibleUsers).toEqual([
705
+ { id: 108, name: "Updated Admin", role: "admin" },
706
+ { id: 109, name: "Updated Admin", role: "admin" },
707
+ { id: 110, name: "User 1", role: "user" },
708
+ ]);
709
+ // Verify the deleted admin wasn't updated by explicitly querying soft-deleted records
710
+ const deletedAdmin = await db.findMany("user", {
711
+ where: { id: 111, deletedAt: { [$not]: null } },
712
+ select: ["name", "deletedAt"],
713
+ });
714
+ // Should find the soft-deleted admin with original name
715
+ expect(deletedAdmin).toHaveLength(1);
716
+ expect(deletedAdmin[0].name).toBe("Deleted Admin");
717
+ expect(deletedAdmin[0].deletedAt).toBeTruthy();
718
+ }, { rollback: true });
719
+ });
720
+ });
721
+ describe("deleteOne", () => {
722
+ test("applies middleware to prevent deleting already soft-deleted records", async () => {
723
+ await db.transact(async (db) => {
724
+ await db.createMany("user", {
725
+ values: [
726
+ {
727
+ id: 112,
728
+ name: "Active User",
729
+ email: "active@example.com",
730
+ role: "user",
731
+ deletedAt: null,
732
+ },
733
+ {
734
+ id: 113,
735
+ name: "Soft Deleted User",
736
+ email: "deleted@example.com",
737
+ role: "user",
738
+ deletedAt: new Date(),
739
+ },
740
+ ],
741
+ });
742
+ // Try to delete the already soft-deleted user - should throw because middleware prevents finding it
743
+ await expect(db.deleteOne("user", {
744
+ where: { id: 113 },
745
+ returning: ["id"],
746
+ })).rejects.toThrow("Delete one failed to delete a row");
747
+ // Verify the soft-deleted user still exists and wasn't hard-deleted
748
+ const deletedUsers = await db.findMany("user", {
749
+ where: { id: 113, deletedAt: { [$not]: null } },
750
+ select: ["id", "name", "deletedAt"],
751
+ });
752
+ // Should find the soft-deleted user unchanged
753
+ expect(deletedUsers).toHaveLength(1);
754
+ expect(deletedUsers[0].name).toBe("Soft Deleted User");
755
+ expect(deletedUsers[0].deletedAt).toBeTruthy();
756
+ // Delete the active user - should work
757
+ const activeResult = await db.deleteOne("user", {
758
+ where: { id: 112 },
759
+ returning: ["id"],
760
+ });
761
+ expect(activeResult).toEqual({ id: 112 });
762
+ // Verify the active user was deleted - should not be found
763
+ const activeUsers = await db.findMany("user", {
764
+ where: { id: 112 },
765
+ select: ["id"],
766
+ });
767
+ expect(activeUsers).toHaveLength(0);
768
+ }, { rollback: true });
769
+ });
770
+ test("combines explicit where conditions with middleware", async () => {
771
+ await db.transact(async (db) => {
772
+ await db.createMany("user", {
773
+ values: [
774
+ {
775
+ id: 114,
776
+ name: "Admin User",
777
+ email: "admin@example.com",
778
+ role: "admin",
779
+ deletedAt: null,
780
+ },
781
+ {
782
+ id: 115,
783
+ name: "Regular User",
784
+ email: "regular@example.com",
785
+ role: "user",
786
+ deletedAt: null,
787
+ },
788
+ {
789
+ id: 116,
790
+ name: "Deleted Admin",
791
+ email: "deleted-admin@example.com",
792
+ role: "admin",
793
+ deletedAt: new Date(),
794
+ },
795
+ ],
796
+ });
797
+ // Delete admin users - should skip soft-deleted admin
798
+ const result = await db.deleteOne("user", {
799
+ where: { role: "admin" },
800
+ returning: ["id", "name"],
801
+ });
802
+ expect(result).toEqual({
803
+ id: 114,
804
+ name: "Admin User",
805
+ });
806
+ // Verify only the regular user remains visible (soft-deleted admin not visible due to middleware)
807
+ const remainingUsers = await db.findMany("user", {
808
+ where: { id: { [$in]: [114, 115, 116] } },
809
+ select: ["id", "role"],
810
+ orderBy: ["id"],
811
+ });
812
+ // Should only see the regular user (admin was deleted, soft-deleted admin not visible)
813
+ expect(remainingUsers).toHaveLength(1);
814
+ expect(remainingUsers).toEqual([{ id: 115, role: "user" }]);
815
+ // Verify the soft-deleted admin still exists and wasn't hard-deleted
816
+ const deletedAdmin = await db.findMany("user", {
817
+ where: { id: 116, deletedAt: { [$not]: null } },
818
+ select: ["id", "name", "deletedAt"],
819
+ });
820
+ // Should find the soft-deleted admin unchanged
821
+ expect(deletedAdmin).toHaveLength(1);
822
+ expect(deletedAdmin[0].name).toBe("Deleted Admin");
823
+ expect(deletedAdmin[0].deletedAt).toBeTruthy();
824
+ }, { rollback: true });
825
+ });
826
+ });
827
+ describe("deleteMany", () => {
828
+ test("applies middleware to prevent deleting soft-deleted records", async () => {
829
+ await db.transact(async (db) => {
830
+ await db.createMany("user", {
831
+ values: [
832
+ {
833
+ id: 117,
834
+ name: "Active User 1",
835
+ email: "active1@example.com",
836
+ role: "user",
837
+ deletedAt: null,
838
+ },
839
+ {
840
+ id: 118,
841
+ name: "Active User 2",
842
+ email: "active2@example.com",
843
+ role: "user",
844
+ deletedAt: null,
845
+ },
846
+ {
847
+ id: 119,
848
+ name: "Deleted User",
849
+ email: "deleted@example.com",
850
+ role: "user",
851
+ deletedAt: new Date(),
852
+ },
853
+ ],
854
+ });
855
+ // Delete all users - should only delete active ones
856
+ const count = await db.deleteMany("user", {
857
+ where: { id: { [$in]: [117, 118, 119] } },
858
+ });
859
+ expect(count).toBe(2);
860
+ // Verify no users are visible (active ones deleted, soft-deleted one not visible due to middleware)
861
+ const remainingUsers = await db.findMany("user", {
862
+ where: { id: { [$in]: [117, 118, 119] } },
863
+ select: ["id", "deletedAt"],
864
+ orderBy: ["id"],
865
+ });
866
+ // Should see no users (active ones were deleted, soft-deleted one filtered out by middleware)
867
+ expect(remainingUsers).toHaveLength(0);
868
+ // Verify the soft-deleted user still exists and wasn't hard-deleted
869
+ const deletedUser = await db.findMany("user", {
870
+ where: { id: 119, deletedAt: { [$not]: null } },
871
+ select: ["id", "name", "deletedAt"],
872
+ });
873
+ // Should find the soft-deleted user unchanged
874
+ expect(deletedUser).toHaveLength(1);
875
+ expect(deletedUser[0].name).toBe("Deleted User");
876
+ expect(deletedUser[0].deletedAt).toBeTruthy();
877
+ }, { rollback: true });
878
+ });
879
+ test("combines explicit where conditions with middleware", async () => {
880
+ await db.transact(async (db) => {
881
+ await db.createMany("user", {
882
+ values: [
883
+ {
884
+ id: 120,
885
+ name: "Admin 1",
886
+ email: "admin1@example.com",
887
+ role: "admin",
888
+ deletedAt: null,
889
+ },
890
+ {
891
+ id: 121,
892
+ name: "Admin 2",
893
+ email: "admin2@example.com",
894
+ role: "admin",
895
+ deletedAt: null,
896
+ },
897
+ {
898
+ id: 122,
899
+ name: "User 1",
900
+ email: "user1@example.com",
901
+ role: "user",
902
+ deletedAt: null,
903
+ },
904
+ {
905
+ id: 123,
906
+ name: "Deleted Admin",
907
+ email: "deleted-admin@example.com",
908
+ role: "admin",
909
+ deletedAt: new Date(),
910
+ },
911
+ ],
912
+ });
913
+ // Delete only admins - should skip soft-deleted admin
914
+ const count = await db.deleteMany("user", {
915
+ where: { role: "admin" },
916
+ });
917
+ expect(count).toBe(2);
918
+ // Verify only the regular user remains visible (admins deleted, soft-deleted admin not visible due to middleware)
919
+ const remainingUsers = await db.findMany("user", {
920
+ where: { id: { [$in]: [120, 121, 122, 123] } },
921
+ select: ["id", "role"],
922
+ orderBy: ["id"],
923
+ });
924
+ // Should only see the regular user (admins were deleted, soft-deleted admin filtered out by middleware)
925
+ expect(remainingUsers).toHaveLength(1);
926
+ expect(remainingUsers).toEqual([{ id: 122, role: "user" }]);
927
+ // Verify the soft-deleted admin still exists and wasn't hard-deleted
928
+ const deletedAdmin = await db.findMany("user", {
929
+ where: { id: 123, deletedAt: { [$not]: null } },
930
+ select: ["id", "name", "deletedAt"],
931
+ });
932
+ // Should find the soft-deleted admin unchanged
933
+ expect(deletedAdmin).toHaveLength(1);
934
+ expect(deletedAdmin[0].name).toBe("Deleted Admin");
935
+ expect(deletedAdmin[0].deletedAt).toBeTruthy();
936
+ }, { rollback: true });
937
+ });
938
+ });
939
+ describe("count", () => {
940
+ test("applies middleware to exclude soft-deleted records from count", async () => {
941
+ await db.transact(async (db) => {
942
+ await db.createMany("user", {
943
+ values: [
944
+ {
945
+ id: 124,
946
+ name: "Active User 1",
947
+ email: "active1@example.com",
948
+ role: "user",
949
+ deletedAt: null,
950
+ },
951
+ {
952
+ id: 125,
953
+ name: "Active User 2",
954
+ email: "active2@example.com",
955
+ role: "user",
956
+ deletedAt: null,
957
+ },
958
+ {
959
+ id: 126,
960
+ name: "Deleted User",
961
+ email: "deleted@example.com",
962
+ role: "user",
963
+ deletedAt: new Date(),
964
+ },
965
+ ],
966
+ });
967
+ // Count all users - should only count active ones
968
+ const count = await db.count("user", {});
969
+ expect(count).toBe(2);
970
+ // The test should pass as middleware correctly excludes soft-deleted records
971
+ // Count is working correctly by only counting active users
972
+ }, { rollback: true });
973
+ });
974
+ test("combines explicit where conditions with middleware", async () => {
975
+ await db.transact(async (db) => {
976
+ await db.createMany("user", {
977
+ values: [
978
+ {
979
+ id: 127,
980
+ name: "Admin 1",
981
+ email: "admin1@example.com",
982
+ role: "admin",
983
+ deletedAt: null,
984
+ },
985
+ {
986
+ id: 128,
987
+ name: "Admin 2",
988
+ email: "admin2@example.com",
989
+ role: "admin",
990
+ deletedAt: null,
991
+ },
992
+ {
993
+ id: 129,
994
+ name: "User 1",
995
+ email: "user1@example.com",
996
+ role: "user",
997
+ deletedAt: null,
998
+ },
999
+ {
1000
+ id: 130,
1001
+ name: "Deleted Admin",
1002
+ email: "deleted-admin@example.com",
1003
+ role: "admin",
1004
+ deletedAt: new Date(),
1005
+ },
1006
+ ],
1007
+ });
1008
+ // Count only admins - should exclude soft-deleted admin
1009
+ const count = await db.count("user", {
1010
+ where: { role: "admin" },
1011
+ });
1012
+ expect(count).toBe(2);
1013
+ // Count all users
1014
+ const totalCount = await db.count("user", {});
1015
+ expect(totalCount).toBe(3);
1016
+ }, { rollback: true });
1017
+ });
1018
+ test("applies middleware even with empty where clause", async () => {
1019
+ await db.transact(async (db) => {
1020
+ await db.createMany("user", {
1021
+ values: [
1022
+ {
1023
+ id: 131,
1024
+ name: "Active User",
1025
+ email: "active@example.com",
1026
+ role: "user",
1027
+ deletedAt: null,
1028
+ },
1029
+ {
1030
+ id: 132,
1031
+ name: "Deleted User",
1032
+ email: "deleted@example.com",
1033
+ role: "user",
1034
+ deletedAt: new Date(),
1035
+ },
1036
+ ],
1037
+ });
1038
+ // Count with undefined where
1039
+ const count1 = await db.count("user", {
1040
+ where: undefined,
1041
+ });
1042
+ expect(count1).toBe(1);
1043
+ // Count with empty object where
1044
+ const count2 = await db.count("user", {
1045
+ where: {},
1046
+ });
1047
+ expect(count2).toBe(1);
1048
+ // Count with no options
1049
+ const count3 = await db.count("user", {});
1050
+ expect(count3).toBe(1);
1051
+ }, { rollback: true });
1052
+ });
1053
+ });
1054
+ });