@fragno-dev/test 2.0.0 → 2.0.2

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 (59) hide show
  1. package/.turbo/turbo-build.log +41 -31
  2. package/CHANGELOG.md +69 -0
  3. package/dist/adapters.d.ts +4 -7
  4. package/dist/adapters.d.ts.map +1 -1
  5. package/dist/adapters.js +18 -302
  6. package/dist/adapters.js.map +1 -1
  7. package/dist/db-test.d.ts +120 -18
  8. package/dist/db-test.d.ts.map +1 -1
  9. package/dist/db-test.js +203 -55
  10. package/dist/db-test.js.map +1 -1
  11. package/dist/durable-hooks.d.ts +6 -2
  12. package/dist/durable-hooks.d.ts.map +1 -1
  13. package/dist/durable-hooks.js +10 -5
  14. package/dist/durable-hooks.js.map +1 -1
  15. package/dist/index.d.ts +3 -3
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +1 -1
  18. package/dist/index.js.map +1 -1
  19. package/dist/model-checker-actors.d.ts.map +1 -1
  20. package/dist/model-checker-actors.js +1 -1
  21. package/dist/model-checker-actors.js.map +1 -1
  22. package/dist/model-checker-adapter.d.ts +1 -1
  23. package/dist/model-checker-adapter.d.ts.map +1 -1
  24. package/dist/model-checker-adapter.js.map +1 -1
  25. package/dist/model-checker.d.ts.map +1 -1
  26. package/dist/model-checker.js.map +1 -1
  27. package/dist/test-adapters/drizzle-pglite.js +116 -0
  28. package/dist/test-adapters/drizzle-pglite.js.map +1 -0
  29. package/dist/test-adapters/in-memory.js +39 -0
  30. package/dist/test-adapters/in-memory.js.map +1 -0
  31. package/dist/test-adapters/kysely-pglite.js +105 -0
  32. package/dist/test-adapters/kysely-pglite.js.map +1 -0
  33. package/dist/test-adapters/kysely-sqlite.js +87 -0
  34. package/dist/test-adapters/kysely-sqlite.js.map +1 -0
  35. package/dist/test-adapters/model-checker.js +41 -0
  36. package/dist/test-adapters/model-checker.js.map +1 -0
  37. package/dist/tsconfig.tsbuildinfo +1 -0
  38. package/package.json +32 -33
  39. package/src/adapter-conformance.test.ts +3 -1
  40. package/src/adapters.ts +24 -455
  41. package/src/db-roundtrip-guard.test.ts +206 -0
  42. package/src/db-test.test.ts +131 -77
  43. package/src/db-test.ts +530 -96
  44. package/src/durable-hooks.test.ts +58 -0
  45. package/src/durable-hooks.ts +23 -8
  46. package/src/index.test.ts +188 -104
  47. package/src/index.ts +6 -2
  48. package/src/model-checker-actors.test.ts +5 -2
  49. package/src/model-checker-actors.ts +2 -1
  50. package/src/model-checker-adapter.ts +3 -2
  51. package/src/model-checker.test.ts +4 -1
  52. package/src/model-checker.ts +4 -3
  53. package/src/test-adapters/drizzle-pglite.ts +162 -0
  54. package/src/test-adapters/in-memory.ts +56 -0
  55. package/src/test-adapters/kysely-pglite.ts +151 -0
  56. package/src/test-adapters/kysely-sqlite.ts +119 -0
  57. package/src/test-adapters/model-checker.ts +58 -0
  58. package/tsconfig.json +1 -1
  59. package/vitest.config.ts +1 -0
@@ -0,0 +1,58 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+
3
+ import { createDurableHooksProcessor } from "@fragno-dev/db/dispatchers/node";
4
+
5
+ import { drainDurableHooks } from "./durable-hooks";
6
+
7
+ const drainMock = vi.fn<() => Promise<void>>();
8
+ const wakeMock = vi.fn<() => Promise<void>>();
9
+
10
+ vi.mock("@fragno-dev/db/dispatchers/node", () => ({
11
+ createDurableHooksProcessor: vi.fn(() => ({
12
+ drain: drainMock,
13
+ wake: wakeMock,
14
+ })),
15
+ }));
16
+
17
+ describe("drainDurableHooks", () => {
18
+ beforeEach(() => {
19
+ drainMock.mockReset();
20
+ wakeMock.mockReset();
21
+ vi.mocked(createDurableHooksProcessor).mockClear();
22
+ });
23
+
24
+ it("no-ops when durable hooks are not configured", async () => {
25
+ await expect(
26
+ drainDurableHooks({
27
+ $internal: {},
28
+ } as never),
29
+ ).resolves.toBeUndefined();
30
+
31
+ expect(createDurableHooksProcessor).not.toHaveBeenCalled();
32
+ expect(drainMock).not.toHaveBeenCalled();
33
+ expect(wakeMock).not.toHaveBeenCalled();
34
+ });
35
+
36
+ it("drains until idle by default", async () => {
37
+ await drainDurableHooks({
38
+ $internal: { durableHooksToken: {} },
39
+ } as never);
40
+
41
+ expect(createDurableHooksProcessor).toHaveBeenCalledTimes(1);
42
+ expect(wakeMock).not.toHaveBeenCalled();
43
+ expect(drainMock).toHaveBeenCalledTimes(1);
44
+ });
45
+
46
+ it("supports single-pass draining", async () => {
47
+ await drainDurableHooks(
48
+ {
49
+ $internal: { durableHooksToken: {} },
50
+ } as never,
51
+ { mode: "singlePass" },
52
+ );
53
+
54
+ expect(createDurableHooksProcessor).toHaveBeenCalledTimes(1);
55
+ expect(wakeMock).toHaveBeenCalledTimes(1);
56
+ expect(drainMock).not.toHaveBeenCalled();
57
+ });
58
+ });
@@ -1,13 +1,28 @@
1
- import {
2
- createDurableHooksProcessor,
3
- type AnyFragnoInstantiatedDatabaseFragment,
4
- } from "@fragno-dev/db";
1
+ import { createDurableHooksProcessor } from "@fragno-dev/db/dispatchers/node";
2
+
5
3
  import type { AnyFragnoInstantiatedFragment } from "@fragno-dev/core";
4
+ import { type AnyFragnoInstantiatedDatabaseFragment } from "@fragno-dev/db";
5
+
6
+ export type DrainDurableHooksMode = "untilIdle" | "singlePass";
7
+
8
+ export type DrainDurableHooksOptions = {
9
+ mode?: DrainDurableHooksMode;
10
+ };
6
11
 
7
- export async function drainDurableHooks(fragment: AnyFragnoInstantiatedFragment): Promise<void> {
8
- const processor = createDurableHooksProcessor(fragment as AnyFragnoInstantiatedDatabaseFragment);
9
- if (!processor) {
12
+ export async function drainDurableHooks(
13
+ fragment: AnyFragnoInstantiatedFragment,
14
+ options: DrainDurableHooksOptions = {},
15
+ ): Promise<void> {
16
+ const internal = fragment.$internal as { durableHooksToken?: object } | undefined;
17
+ if (!internal?.durableHooksToken) {
18
+ return;
19
+ }
20
+ const dispatcher = createDurableHooksProcessor([
21
+ fragment as AnyFragnoInstantiatedDatabaseFragment,
22
+ ]);
23
+ if (options.mode === "singlePass") {
24
+ await dispatcher.wake();
10
25
  return;
11
26
  }
12
- await processor.drain();
27
+ await dispatcher.drain();
13
28
  }
package/src/index.test.ts CHANGED
@@ -1,10 +1,14 @@
1
1
  import { describe, expect, expectTypeOf, it } from "vitest";
2
+
3
+ import type { ExtractFragmentServices } from "@fragno-dev/core/route";
2
4
  import { column, idColumn, schema } from "@fragno-dev/db/schema";
3
- import { Cursor, withDatabase } from "@fragno-dev/db";
5
+
4
6
  import { defineFragment } from "@fragno-dev/core";
5
7
  import { instantiate } from "@fragno-dev/core";
8
+ import { Cursor, withDatabase } from "@fragno-dev/db";
9
+
6
10
  import { buildDatabaseFragmentsTest } from "./db-test";
7
- import type { ExtractFragmentServices } from "@fragno-dev/core/route";
11
+ import { drainDurableHooks } from "./durable-hooks";
8
12
 
9
13
  // Test schema with multiple versions
10
14
  const testSchema = schema("test", (s) => {
@@ -25,32 +29,41 @@ const testSchema = schema("test", (s) => {
25
29
  // Test fragment definition
26
30
  const testFragmentDef = defineFragment<{}>("test-fragment")
27
31
  .extend(withDatabase(testSchema))
28
- .providesBaseService(({ deps }) => {
29
- return {
30
- createUser: async (data: { name: string; email: string; age?: number | null }) => {
31
- const id = await deps.db.create("users", data);
32
- return { ...data, id: id.valueOf() };
32
+ .providesBaseService(({ defineService }) =>
33
+ defineService({
34
+ createUser: function (data: { name: string; email: string; age?: number | null }) {
35
+ return this.serviceTx(testSchema)
36
+ .mutate(({ uow }) => uow.create("users", data))
37
+ .transform(({ mutateResult }) => ({ ...data, id: mutateResult.valueOf() }))
38
+ .build();
33
39
  },
34
- getUsers: async () => {
35
- const users = await deps.db.find("users", (b) =>
36
- b.whereIndex("idx_users_all", (eb) => eb("id", "!=", "")),
37
- );
38
- return users.map((u) => ({ ...u, id: u.id.valueOf() }));
40
+ getUsers: function () {
41
+ return this.serviceTx(testSchema)
42
+ .retrieve((uow) =>
43
+ uow.find("users", (b) => b.whereIndex("idx_users_all", (eb) => eb("id", "!=", ""))),
44
+ )
45
+ .transformRetrieve(([users]) => users.map((u) => ({ ...u, id: u.id.valueOf() })))
46
+ .build();
39
47
  },
40
- getUsersWithCursor: async (cursor?: Cursor | string) => {
41
- return deps.db.findWithCursor("users", (b) => {
42
- let builder = b
43
- .whereIndex("idx_users_name")
44
- .orderByIndex("idx_users_name", "asc")
45
- .pageSize(2);
46
- if (cursor) {
47
- builder = builder.after(cursor);
48
- }
49
- return builder;
50
- });
48
+ getUsersWithCursor: function (cursor?: Cursor | string) {
49
+ return this.serviceTx(testSchema)
50
+ .retrieve((uow) =>
51
+ uow.findWithCursor("users", (b) => {
52
+ let builder = b
53
+ .whereIndex("idx_users_name")
54
+ .orderByIndex("idx_users_name", "asc")
55
+ .pageSize(2);
56
+ if (cursor) {
57
+ builder = builder.after(cursor);
58
+ }
59
+ return builder;
60
+ }),
61
+ )
62
+ .transformRetrieve(([result]) => result)
63
+ .build();
51
64
  },
52
- };
53
- })
65
+ }),
66
+ )
54
67
  .build();
55
68
 
56
69
  describe("buildDatabaseFragmentsTest", () => {
@@ -63,11 +76,13 @@ describe("buildDatabaseFragmentsTest", () => {
63
76
  const fragment = fragments.test;
64
77
 
65
78
  // Should be able to create and query users
66
- const user = await fragment.services.createUser({
67
- name: "Test User",
68
- email: "test@example.com",
69
- age: 25,
70
- });
79
+ const user = await fragment.fragment.callServices(() =>
80
+ fragment.services.createUser({
81
+ name: "Test User",
82
+ email: "test@example.com",
83
+ age: 25,
84
+ }),
85
+ );
71
86
 
72
87
  expect(user).toMatchObject({
73
88
  id: expect.any(String),
@@ -76,13 +91,24 @@ describe("buildDatabaseFragmentsTest", () => {
76
91
  age: 25,
77
92
  });
78
93
 
79
- const users = await fragment.services.getUsers();
94
+ const users = await fragment.fragment.callServices(() => fragment.services.getUsers());
80
95
  expect(users).toHaveLength(1);
81
96
  expect(users[0]).toMatchObject(user);
82
97
 
83
98
  await test.cleanup();
84
99
  });
85
100
 
101
+ it("should no-op drainDurableHooks when durable hooks are not configured", async () => {
102
+ const { fragments, test } = await buildDatabaseFragmentsTest()
103
+ .withTestAdapter({ type: "kysely-sqlite" })
104
+ .withFragment("test", instantiate(testFragmentDef).withConfig({}).withRoutes([]))
105
+ .build();
106
+
107
+ await expect(drainDurableHooks(fragments.test.fragment)).resolves.toBeUndefined();
108
+
109
+ await test.cleanup();
110
+ });
111
+
86
112
  it("should throw error for non-database fragment", async () => {
87
113
  const nonDbFragmentDef = defineFragment<{}>("non-db-fragment")
88
114
  .providesBaseService(() => ({}))
@@ -102,11 +128,13 @@ describe("buildDatabaseFragmentsTest", () => {
102
128
  .withFragment("test", instantiate(testFragmentDef).withConfig({}).withRoutes([]))
103
129
  .build();
104
130
 
105
- const user = await fragments.test.services.createUser({
106
- name: "Memory User",
107
- email: "memory@example.com",
108
- age: 31,
109
- });
131
+ const user = await fragments.test.fragment.callServices(() =>
132
+ fragments.test.services.createUser({
133
+ name: "Memory User",
134
+ email: "memory@example.com",
135
+ age: 31,
136
+ }),
137
+ );
110
138
 
111
139
  expect(user).toMatchObject({
112
140
  id: expect.any(String),
@@ -115,7 +143,9 @@ describe("buildDatabaseFragmentsTest", () => {
115
143
  age: 31,
116
144
  });
117
145
 
118
- const users = await fragments.test.services.getUsers();
146
+ const users = await fragments.test.fragment.callServices(() =>
147
+ fragments.test.services.getUsers(),
148
+ );
119
149
  expect(users).toHaveLength(1);
120
150
  expect(users[0]).toMatchObject(user);
121
151
 
@@ -139,20 +169,26 @@ describe("buildDatabaseFragmentsTest", () => {
139
169
  ];
140
170
 
141
171
  for (const user of users) {
142
- await fragment.services.createUser(user);
172
+ await fragment.fragment.callServices(() => fragment.services.createUser(user));
143
173
  }
144
174
 
145
- const firstPage = await fragment.services.getUsersWithCursor();
175
+ const firstPage = await fragment.fragment.callServices(() =>
176
+ fragment.services.getUsersWithCursor(),
177
+ );
146
178
  expect(firstPage.items.map((item) => item.name)).toEqual(["Alice", "Brett"]);
147
179
  expect(firstPage.hasNextPage).toBe(true);
148
180
  expect(firstPage.cursor).toBeDefined();
149
181
 
150
- const secondPage = await fragment.services.getUsersWithCursor(firstPage.cursor);
182
+ const secondPage = await fragment.fragment.callServices(() =>
183
+ fragment.services.getUsersWithCursor(firstPage.cursor),
184
+ );
151
185
  expect(secondPage.items.map((item) => item.name)).toEqual(["Cora", "Dylan"]);
152
186
  expect(secondPage.hasNextPage).toBe(true);
153
187
  expect(secondPage.cursor).toBeDefined();
154
188
 
155
- const thirdPage = await fragment.services.getUsersWithCursor(secondPage.cursor);
189
+ const thirdPage = await fragment.fragment.callServices(() =>
190
+ fragment.services.getUsersWithCursor(secondPage.cursor),
191
+ );
156
192
  expect(thirdPage.items.map((item) => item.name)).toEqual(["Emma"]);
157
193
  expect(thirdPage.hasNextPage).toBe(false);
158
194
  expect(thirdPage.cursor).toBeUndefined();
@@ -169,28 +205,30 @@ describe("buildDatabaseFragmentsTest", () => {
169
205
  const fragment = fragments.test;
170
206
 
171
207
  // Create some users
172
- await fragment.services.createUser({
173
- name: "User 1",
174
- email: "user1@example.com",
175
- age: 25,
176
- });
208
+ await fragment.fragment.callServices(() =>
209
+ fragment.services.createUser({
210
+ name: "User 1",
211
+ email: "user1@example.com",
212
+ age: 25,
213
+ }),
214
+ );
177
215
 
178
216
  // Verify users exist
179
- let users = await fragment.services.getUsers();
217
+ let users = await fragment.fragment.callServices(() => fragment.services.getUsers());
180
218
  expect(users).toHaveLength(1);
181
219
 
182
220
  // Reset the database
183
221
  await test.resetDatabase();
184
222
 
185
223
  // Verify database is empty
186
- users = await fragment.services.getUsers();
224
+ users = await fragment.fragment.callServices(() => fragment.services.getUsers());
187
225
  expect(users).toHaveLength(0);
188
226
 
189
227
  // Cleanup
190
228
  await test.cleanup();
191
229
  });
192
230
 
193
- it("should expose db property for direct ORM queries", async () => {
231
+ it("should allow handlerTx direct ORM queries", async () => {
194
232
  const { fragments, test } = await buildDatabaseFragmentsTest()
195
233
  .withTestAdapter({ type: "kysely-sqlite" })
196
234
  .withFragment("test", instantiate(testFragmentDef).withConfig({}).withRoutes([]))
@@ -198,20 +236,34 @@ describe("buildDatabaseFragmentsTest", () => {
198
236
 
199
237
  const fragment = fragments.test;
200
238
 
201
- // Test creating a record directly using test.db
202
- const userId = await fragment.db.create("users", {
203
- name: "Direct DB User",
204
- email: "direct@example.com",
205
- age: 28,
239
+ // Test creating a record directly using handlerTx
240
+ const userId = await fragment.fragment.inContext(async function () {
241
+ return await this.handlerTx()
242
+ .mutate(({ forSchema }) =>
243
+ forSchema(testSchema).create("users", {
244
+ name: "Direct DB User",
245
+ email: "direct@example.com",
246
+ age: 28,
247
+ }),
248
+ )
249
+ .transform(({ mutateResult }) => mutateResult)
250
+ .execute();
206
251
  });
207
252
 
208
253
  expect(userId).toBeDefined();
209
254
  expect(typeof userId.valueOf()).toBe("string");
210
255
 
211
- // Test finding records using test.db
212
- const users = await fragment.db.find("users", (b) =>
213
- b.whereIndex("idx_users_all", (eb) => eb("id", "=", userId)),
214
- );
256
+ // Test finding records using handlerTx
257
+ const users = await fragment.fragment.inContext(async function () {
258
+ return await this.handlerTx()
259
+ .retrieve(({ forSchema }) =>
260
+ forSchema(testSchema).find("users", (b) =>
261
+ b.whereIndex("idx_users_all", (eb) => eb("id", "=", userId)),
262
+ ),
263
+ )
264
+ .transformRetrieve(([result]) => result)
265
+ .execute();
266
+ });
215
267
 
216
268
  expect(users).toHaveLength(1);
217
269
  expect(users[0]).toMatchObject({
@@ -246,20 +298,32 @@ describe("buildDatabaseFragmentsTest", () => {
246
298
 
247
299
  const authFragmentDef = defineFragment<{}>("auth-test")
248
300
  .extend(withDatabase(authSchema))
249
- .providesBaseService(({ deps }) => {
250
- return {
251
- createUser: async (email: string, passwordHash: string) => {
252
- const id = await deps.db.create("user", { email, passwordHash });
253
- return { id: id.valueOf(), email, passwordHash };
301
+ .providesBaseService(({ defineService }) =>
302
+ defineService({
303
+ createUser: function (email: string, passwordHash: string) {
304
+ return this.serviceTx(authSchema)
305
+ .mutate(({ uow }) => uow.create("user", { email, passwordHash }))
306
+ .transform(({ mutateResult }) => ({
307
+ id: mutateResult.valueOf(),
308
+ email,
309
+ passwordHash,
310
+ }))
311
+ .build();
254
312
  },
255
- createSession: async (userId: string) => {
313
+ createSession: function (userId: string) {
256
314
  const expiresAt = new Date();
257
315
  expiresAt.setDate(expiresAt.getDate() + 30);
258
- const id = await deps.db.create("session", { userId, expiresAt });
259
- return { id: id.valueOf(), userId, expiresAt };
316
+ return this.serviceTx(authSchema)
317
+ .mutate(({ uow }) => uow.create("session", { userId, expiresAt }))
318
+ .transform(({ mutateResult }) => ({
319
+ id: mutateResult.valueOf(),
320
+ userId,
321
+ expiresAt,
322
+ }))
323
+ .build();
260
324
  },
261
- };
262
- })
325
+ }),
326
+ )
263
327
  .build();
264
328
 
265
329
  const { fragments, test } = await buildDatabaseFragmentsTest()
@@ -270,7 +334,9 @@ describe("buildDatabaseFragmentsTest", () => {
270
334
  const fragment = fragments.auth;
271
335
 
272
336
  // Create a user
273
- const user = await fragment.services.createUser("test@test.com", "hashed-password");
337
+ const user = await fragment.fragment.callServices(() =>
338
+ fragment.services.createUser("test@test.com", "hashed-password"),
339
+ );
274
340
  expect(user).toMatchObject({
275
341
  id: expect.any(String),
276
342
  email: "test@test.com",
@@ -278,7 +344,9 @@ describe("buildDatabaseFragmentsTest", () => {
278
344
  });
279
345
 
280
346
  // Create a session for the user
281
- const session = await fragment.services.createSession(user.id);
347
+ const session = await fragment.fragment.callServices(() =>
348
+ fragment.services.createSession(user.id),
349
+ );
282
350
  expect(session).toMatchObject({
283
351
  id: expect.any(String),
284
352
  userId: user.id,
@@ -313,38 +381,46 @@ describe("multi-fragment tests", () => {
313
381
 
314
382
  const userFragmentDef = defineFragment<{}>("user-fragment")
315
383
  .extend(withDatabase(userSchema))
316
- .providesBaseService(({ deps }) => {
317
- return {
318
- createUser: async (data: { name: string; email: string }) => {
319
- const id = await deps.db.create("user", data);
320
- return { ...data, id: id.valueOf() };
384
+ .providesBaseService(({ defineService }) =>
385
+ defineService({
386
+ createUser: function (data: { name: string; email: string }) {
387
+ return this.serviceTx(userSchema)
388
+ .mutate(({ uow }) => uow.create("user", data))
389
+ .transform(({ mutateResult }) => ({ ...data, id: mutateResult.valueOf() }))
390
+ .build();
321
391
  },
322
- getUsers: async () => {
323
- const users = await deps.db.find("user", (b) =>
324
- b.whereIndex("idx_user_all", (eb) => eb("id", "!=", "")),
325
- );
326
- return users.map((u) => ({ ...u, id: u.id.valueOf() }));
392
+ getUsers: function () {
393
+ return this.serviceTx(userSchema)
394
+ .retrieve((uow) =>
395
+ uow.find("user", (b) => b.whereIndex("idx_user_all", (eb) => eb("id", "!=", ""))),
396
+ )
397
+ .transformRetrieve(([users]) => users.map((u) => ({ ...u, id: u.id.valueOf() })))
398
+ .build();
327
399
  },
328
- };
329
- })
400
+ }),
401
+ )
330
402
  .build();
331
403
 
332
404
  const postFragmentDef = defineFragment<{}>("post-fragment")
333
405
  .extend(withDatabase(postSchema))
334
- .providesBaseService(({ deps }) => {
335
- return {
336
- createPost: async (data: { title: string; userId: string }) => {
337
- const id = await deps.db.create("post", data);
338
- return { ...data, id: id.valueOf() };
406
+ .providesBaseService(({ defineService }) =>
407
+ defineService({
408
+ createPost: function (data: { title: string; userId: string }) {
409
+ return this.serviceTx(postSchema)
410
+ .mutate(({ uow }) => uow.create("post", data))
411
+ .transform(({ mutateResult }) => ({ ...data, id: mutateResult.valueOf() }))
412
+ .build();
339
413
  },
340
- getPosts: async () => {
341
- const posts = await deps.db.find("post", (b) =>
342
- b.whereIndex("idx_post_all", (eb) => eb("id", "!=", "")),
343
- );
344
- return posts.map((p) => ({ ...p, id: p.id.valueOf() }));
414
+ getPosts: function () {
415
+ return this.serviceTx(postSchema)
416
+ .retrieve((uow) =>
417
+ uow.find("post", (b) => b.whereIndex("idx_post_all", (eb) => eb("id", "!=", ""))),
418
+ )
419
+ .transformRetrieve(([posts]) => posts.map((p) => ({ ...p, id: p.id.valueOf() })))
420
+ .build();
345
421
  },
346
- };
347
- })
422
+ }),
423
+ )
348
424
  .build();
349
425
 
350
426
  const adapters = [
@@ -363,10 +439,12 @@ describe("multi-fragment tests", () => {
363
439
  .build();
364
440
 
365
441
  // Create a user
366
- const user = await fragments.user.services.createUser({
367
- name: "John Doe",
368
- email: "john@example.com",
369
- });
442
+ const user = await fragments.user.fragment.callServices(() =>
443
+ fragments.user.services.createUser({
444
+ name: "John Doe",
445
+ email: "john@example.com",
446
+ }),
447
+ );
370
448
 
371
449
  expect(user).toMatchObject({
372
450
  id: expect.any(String),
@@ -375,10 +453,12 @@ describe("multi-fragment tests", () => {
375
453
  });
376
454
 
377
455
  // Create a post with the user's ID
378
- const post = await fragments.post.services.createPost({
379
- title: "My First Post",
380
- userId: user.id,
381
- });
456
+ const post = await fragments.post.fragment.callServices(() =>
457
+ fragments.post.services.createPost({
458
+ title: "My First Post",
459
+ userId: user.id,
460
+ }),
461
+ );
382
462
 
383
463
  expect(post).toMatchObject({
384
464
  id: expect.any(String),
@@ -387,10 +467,14 @@ describe("multi-fragment tests", () => {
387
467
  });
388
468
 
389
469
  // Verify data exists
390
- const users = await fragments.user.services.getUsers();
470
+ const users = await fragments.user.fragment.callServices(() =>
471
+ fragments.user.services.getUsers(),
472
+ );
391
473
  expect(users).toHaveLength(1);
392
474
 
393
- const posts = await fragments.post.services.getPosts();
475
+ const posts = await fragments.post.fragment.callServices(() =>
476
+ fragments.post.services.getPosts(),
477
+ );
394
478
  expect(posts).toHaveLength(1);
395
479
  expect(posts[0]!.userId).toBe(user.id);
396
480
 
package/src/index.ts CHANGED
@@ -1,4 +1,8 @@
1
+ import type { SimpleQueryInterface } from "@fragno-dev/db/query";
1
2
  import type { AnySchema } from "@fragno-dev/db/schema";
3
+
4
+ import type { DatabaseAdapter } from "@fragno-dev/db";
5
+
2
6
  import type {
3
7
  SupportedAdapter,
4
8
  AdapterContext,
@@ -7,8 +11,6 @@ import type {
7
11
  DrizzlePgliteAdapter,
8
12
  InMemoryAdapterConfig,
9
13
  } from "./adapters";
10
- import type { DatabaseAdapter } from "@fragno-dev/db";
11
- import type { SimpleQueryInterface } from "@fragno-dev/db/query";
12
14
 
13
15
  // Re-export utilities from @fragno-dev/core/test
14
16
  export {
@@ -29,7 +31,9 @@ export type {
29
31
 
30
32
  // Re-export new builder-based database test utilities
31
33
  export { buildDatabaseFragmentsTest, DatabaseFragmentsTestBuilder } from "./db-test";
34
+ export type { AnyFragmentResult } from "./db-test";
32
35
  export { drainDurableHooks } from "./durable-hooks";
36
+ export type { DrainDurableHooksMode, DrainDurableHooksOptions } from "./durable-hooks";
33
37
  export {
34
38
  runModelChecker,
35
39
  defaultStateHasher,
@@ -1,8 +1,11 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { InMemoryAdapter } from "@fragno-dev/db";
2
+
3
3
  import { column, idColumn, schema } from "@fragno-dev/db/schema";
4
- import { ModelCheckerAdapter } from "./model-checker-adapter";
4
+
5
+ import { InMemoryAdapter } from "@fragno-dev/db";
6
+
5
7
  import { runModelCheckerWithActors } from "./model-checker-actors";
8
+ import { ModelCheckerAdapter } from "./model-checker-adapter";
6
9
 
7
10
  const testSchema = schema("test", (s) =>
8
11
  s.addTable("items", (t) => t.addColumn("id", idColumn()).addColumn("name", column("string"))),
@@ -1,6 +1,7 @@
1
- import type { AnySchema } from "@fragno-dev/db/schema";
2
1
  import type { SimpleQueryInterface } from "@fragno-dev/db/query";
2
+ import type { AnySchema } from "@fragno-dev/db/schema";
3
3
  import type { UOWInstrumentationContext } from "@fragno-dev/db/unit-of-work";
4
+
4
5
  import {
5
6
  defaultStateHasher,
6
7
  type ModelCheckerBounds,
@@ -1,3 +1,4 @@
1
+ import type { RequestContextStorage } from "@fragno-dev/core/internal/request-context-storage";
1
2
  import type {
2
3
  DatabaseAdapter,
3
4
  DatabaseAdapterMetadata,
@@ -7,15 +8,15 @@ import {
7
8
  fragnoDatabaseAdapterNameFakeSymbol,
8
9
  fragnoDatabaseAdapterVersionFakeSymbol,
9
10
  } from "@fragno-dev/db/adapters";
10
- import type { AnySchema } from "@fragno-dev/db/schema";
11
11
  import type { SimpleQueryInterface } from "@fragno-dev/db/query";
12
- import type { RequestContextStorage } from "@fragno-dev/core/internal/request-context-storage";
12
+ import type { AnySchema } from "@fragno-dev/db/schema";
13
13
  import type {
14
14
  UOWInstrumentation,
15
15
  UOWInstrumentationContext,
16
16
  UOWInstrumentationInjection,
17
17
  UOWInstrumentationFinalizer,
18
18
  } from "@fragno-dev/db/unit-of-work";
19
+
19
20
  import type { ModelCheckerPhase } from "./model-checker";
20
21
 
21
22
  type SchedulerHook = (ctx: UOWInstrumentationContext, phase: ModelCheckerPhase) => Promise<void>;
@@ -1,7 +1,10 @@
1
1
  import { describe, expect, it, vi } from "vitest";
2
- import { InMemoryAdapter } from "@fragno-dev/db";
2
+
3
3
  import { column, idColumn, schema, type FragnoId } from "@fragno-dev/db/schema";
4
4
  import type { UnitOfWorkConfig } from "@fragno-dev/db/unit-of-work";
5
+
6
+ import { InMemoryAdapter } from "@fragno-dev/db";
7
+
5
8
  import {
6
9
  createRawUowTransaction,
7
10
  defaultStateHasher,
@@ -1,13 +1,14 @@
1
- import type { FragnoRuntime } from "@fragno-dev/core";
2
1
  import {
3
2
  runWithTraceRecorder,
4
3
  type FragnoCoreTraceEvent,
5
4
  } from "@fragno-dev/core/internal/trace-context";
6
- import { FragnoId, FragnoReference, type AnySchema } from "@fragno-dev/db/schema";
7
5
  import type { SimpleQueryInterface } from "@fragno-dev/db/query";
8
- import type { TypedUnitOfWork } from "@fragno-dev/db";
6
+ import { FragnoId, FragnoReference, type AnySchema } from "@fragno-dev/db/schema";
9
7
  import type { MutationOperation } from "@fragno-dev/db/unit-of-work";
10
8
 
9
+ import type { FragnoRuntime } from "@fragno-dev/core";
10
+ import type { TypedUnitOfWork } from "@fragno-dev/db";
11
+
11
12
  export type ModelCheckerMode = "exhaustive" | "bounded-exhaustive" | "random" | "infinite";
12
13
  export type ModelCheckerPhase = "retrieve" | "mutate";
13
14