@fragno-dev/db 0.1.13 → 0.1.14
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/.turbo/turbo-build.log +48 -41
- package/CHANGELOG.md +6 -0
- package/dist/adapters/adapters.d.ts +13 -1
- package/dist/adapters/adapters.d.ts.map +1 -1
- package/dist/adapters/adapters.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-adapter.d.ts +2 -0
- package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +1 -1
- package/dist/adapters/drizzle/drizzle-adapter.js +6 -1
- package/dist/adapters/drizzle/drizzle-adapter.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-query.js +6 -4
- package/dist/adapters/drizzle/drizzle-query.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts +0 -1
- package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-compiler.js +49 -36
- package/dist/adapters/drizzle/drizzle-uow-compiler.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-decoder.js +1 -1
- package/dist/adapters/drizzle/drizzle-uow-decoder.js.map +1 -1
- package/dist/adapters/drizzle/shared.d.ts +14 -1
- package/dist/adapters/drizzle/shared.d.ts.map +1 -0
- package/dist/adapters/kysely/kysely-adapter.d.ts +2 -0
- package/dist/adapters/kysely/kysely-adapter.d.ts.map +1 -1
- package/dist/adapters/kysely/kysely-adapter.js +7 -2
- package/dist/adapters/kysely/kysely-adapter.js.map +1 -1
- package/dist/adapters/kysely/kysely-query.js +5 -3
- package/dist/adapters/kysely/kysely-query.js.map +1 -1
- package/dist/adapters/kysely/kysely-shared.d.ts +11 -0
- package/dist/adapters/kysely/kysely-shared.d.ts.map +1 -0
- package/dist/adapters/kysely/kysely-uow-compiler.js +38 -9
- package/dist/adapters/kysely/kysely-uow-compiler.js.map +1 -1
- package/dist/bind-services.d.ts +7 -0
- package/dist/bind-services.d.ts.map +1 -0
- package/dist/bind-services.js +14 -0
- package/dist/bind-services.js.map +1 -0
- package/dist/fragment.d.ts +131 -12
- package/dist/fragment.d.ts.map +1 -1
- package/dist/fragment.js +107 -8
- package/dist/fragment.js.map +1 -1
- package/dist/mod.d.ts +4 -2
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +3 -2
- package/dist/mod.js.map +1 -1
- package/dist/query/query.d.ts +2 -2
- package/dist/query/query.d.ts.map +1 -1
- package/dist/query/unit-of-work.d.ts +100 -15
- package/dist/query/unit-of-work.d.ts.map +1 -1
- package/dist/query/unit-of-work.js +214 -7
- package/dist/query/unit-of-work.js.map +1 -1
- package/package.json +3 -3
- package/src/adapters/adapters.ts +14 -0
- package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +6 -1
- package/src/adapters/drizzle/drizzle-adapter-sqlite.test.ts +133 -5
- package/src/adapters/drizzle/drizzle-adapter.ts +16 -1
- package/src/adapters/drizzle/drizzle-query.ts +26 -15
- package/src/adapters/drizzle/drizzle-uow-compiler.test.ts +57 -57
- package/src/adapters/drizzle/drizzle-uow-compiler.ts +79 -39
- package/src/adapters/drizzle/drizzle-uow-decoder.ts +2 -5
- package/src/adapters/kysely/kysely-adapter-pglite.test.ts +2 -2
- package/src/adapters/kysely/kysely-adapter.ts +16 -1
- package/src/adapters/kysely/kysely-query.ts +26 -15
- package/src/adapters/kysely/kysely-uow-compiler.test.ts +43 -43
- package/src/adapters/kysely/kysely-uow-compiler.ts +50 -14
- package/src/adapters/kysely/kysely-uow-joins.test.ts +30 -30
- package/src/bind-services.test.ts +214 -0
- package/src/bind-services.ts +37 -0
- package/src/db-fragment.test.ts +800 -0
- package/src/fragment.ts +557 -28
- package/src/mod.ts +19 -0
- package/src/query/query.ts +2 -2
- package/src/query/unit-of-work-multi-schema.test.ts +64 -0
- package/src/query/unit-of-work-types.test.ts +13 -0
- package/src/query/unit-of-work.test.ts +5 -9
- package/src/query/unit-of-work.ts +511 -62
- package/src/uow-context-integration.test.ts +102 -0
- package/src/uow-context.test.ts +182 -0
- package/src/fragment.test.ts +0 -341
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { defineFragmentWithDatabase } from "./fragment";
|
|
3
|
+
import { createFragment } from "@fragno-dev/core";
|
|
4
|
+
import { schema, column, idColumn } from "./schema/create";
|
|
5
|
+
|
|
6
|
+
describe("UOW Context Integration", () => {
|
|
7
|
+
it("should bind services to use serviceContext", () => {
|
|
8
|
+
// Create a schema
|
|
9
|
+
const testSchema = schema((s) => {
|
|
10
|
+
return s.addTable("user", (t) => {
|
|
11
|
+
return t.addColumn("id", idColumn()).addColumn("name", column("string"));
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// Define fragment with service that uses this.getUnitOfWork()
|
|
16
|
+
const fragmentDef = defineFragmentWithDatabase<{}>("test-fragment")
|
|
17
|
+
.withDatabase(testSchema, "test")
|
|
18
|
+
.providesService(({ defineService }) =>
|
|
19
|
+
defineService({
|
|
20
|
+
createUser: function (name: string) {
|
|
21
|
+
const uow = this.getUnitOfWork(testSchema);
|
|
22
|
+
const userId = uow.create("user", { name });
|
|
23
|
+
return { userId: userId.valueOf(), name };
|
|
24
|
+
},
|
|
25
|
+
}),
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
// Mock database adapter
|
|
29
|
+
const mockSchemaView = {
|
|
30
|
+
create: () => ({ valueOf: () => 1 }),
|
|
31
|
+
};
|
|
32
|
+
const mockUow = {
|
|
33
|
+
forSchema: () => mockSchemaView,
|
|
34
|
+
};
|
|
35
|
+
const mockAdapter = {
|
|
36
|
+
createQueryEngine: () => ({
|
|
37
|
+
createUnitOfWork: () => mockUow,
|
|
38
|
+
}),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Create fragment instance - services should be bound
|
|
42
|
+
const fragment = createFragment(fragmentDef, {}, [], {
|
|
43
|
+
mountRoute: "/api/test",
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
45
|
+
databaseAdapter: mockAdapter as any,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Verify services are defined
|
|
49
|
+
expect(fragment.services).toBeDefined();
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
51
|
+
expect((fragment.services as any).createUser).toBeDefined();
|
|
52
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
53
|
+
expect(typeof (fragment.services as any).createUser).toBe("function");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("should bind providesService services properly", () => {
|
|
57
|
+
const schema1 = schema((s) => {
|
|
58
|
+
return s.addTable("post", (t) => {
|
|
59
|
+
return t.addColumn("id", idColumn()).addColumn("title", column("string"));
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Fragment with providesService
|
|
64
|
+
const fragmentDef = defineFragmentWithDatabase<{}>("fragment1")
|
|
65
|
+
.withDatabase(schema1, "fragment1")
|
|
66
|
+
.providesService(({ defineService }) =>
|
|
67
|
+
defineService({
|
|
68
|
+
createPost: function (title: string) {
|
|
69
|
+
const uow = this.getUnitOfWork(schema1);
|
|
70
|
+
const postId = uow.create("post", { title });
|
|
71
|
+
return { postId: postId.valueOf(), title };
|
|
72
|
+
},
|
|
73
|
+
}),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
// Mock adapter
|
|
77
|
+
const mockSchemaView = {
|
|
78
|
+
create: () => ({ valueOf: () => 2 }),
|
|
79
|
+
};
|
|
80
|
+
const mockUow = {
|
|
81
|
+
forSchema: () => mockSchemaView,
|
|
82
|
+
};
|
|
83
|
+
const mockAdapter = {
|
|
84
|
+
createQueryEngine: () => ({
|
|
85
|
+
createUnitOfWork: () => mockUow,
|
|
86
|
+
}),
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Create fragment
|
|
90
|
+
const fragment = createFragment(fragmentDef, {}, [], {
|
|
91
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
92
|
+
databaseAdapter: mockAdapter as any,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Verify service is properly bound
|
|
96
|
+
expect(fragment.services).toBeDefined();
|
|
97
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
98
|
+
expect((fragment.services as any).createPost).toBeDefined();
|
|
99
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
100
|
+
expect(typeof (fragment.services as any).createPost).toBe("function");
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { uowStorage, serviceContext, withUnitOfWork } from "./fragment";
|
|
3
|
+
import type { IUnitOfWorkBase } from "./query/unit-of-work";
|
|
4
|
+
import { schema, idColumn } from "./schema/create";
|
|
5
|
+
|
|
6
|
+
// Create a simple test schema for tests
|
|
7
|
+
const testSchema = schema((s) => {
|
|
8
|
+
return s.addTable("test", (t) => {
|
|
9
|
+
return t.addColumn("id", idColumn());
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe("UOW Context", () => {
|
|
14
|
+
describe("serviceContext.getUnitOfWork", () => {
|
|
15
|
+
it("should throw error when called outside context", () => {
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
17
|
+
expect(() => serviceContext.getUnitOfWork(testSchema as any)).toThrow(
|
|
18
|
+
"No UnitOfWork in context. Service must be called within a route handler.",
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should return scoped UOW when called inside withUnitOfWork", async () => {
|
|
23
|
+
const mockSchemaView = { test: "schema-view" };
|
|
24
|
+
const mockUow = {
|
|
25
|
+
test: "uow",
|
|
26
|
+
forSchema: () => mockSchemaView,
|
|
27
|
+
} as unknown as IUnitOfWorkBase;
|
|
28
|
+
|
|
29
|
+
await withUnitOfWork(mockUow, () => {
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
|
+
const uow = serviceContext.getUnitOfWork(testSchema as any);
|
|
32
|
+
expect(uow).toBe(mockSchemaView);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should allow nested withUnitOfWork calls", async () => {
|
|
37
|
+
const view1 = { id: 1 };
|
|
38
|
+
const view2 = { id: 2 };
|
|
39
|
+
const uow1 = {
|
|
40
|
+
id: 1,
|
|
41
|
+
forSchema: () => view1,
|
|
42
|
+
} as unknown as IUnitOfWorkBase;
|
|
43
|
+
const uow2 = {
|
|
44
|
+
id: 2,
|
|
45
|
+
forSchema: () => view2,
|
|
46
|
+
} as unknown as IUnitOfWorkBase;
|
|
47
|
+
|
|
48
|
+
await withUnitOfWork(uow1, async () => {
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
|
+
expect(serviceContext.getUnitOfWork(testSchema as any)).toBe(view1);
|
|
51
|
+
|
|
52
|
+
await withUnitOfWork(uow2, () => {
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
54
|
+
expect(serviceContext.getUnitOfWork(testSchema as any)).toBe(view2);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Should be back to uow1 after nested context
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
59
|
+
expect(serviceContext.getUnitOfWork(testSchema as any)).toBe(view1);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should isolate UOW between parallel async calls", async () => {
|
|
64
|
+
const view1 = { id: 1 };
|
|
65
|
+
const view2 = { id: 2 };
|
|
66
|
+
const uow1 = {
|
|
67
|
+
id: 1,
|
|
68
|
+
forSchema: () => view1,
|
|
69
|
+
} as unknown as IUnitOfWorkBase;
|
|
70
|
+
const uow2 = {
|
|
71
|
+
id: 2,
|
|
72
|
+
forSchema: () => view2,
|
|
73
|
+
} as unknown as IUnitOfWorkBase;
|
|
74
|
+
|
|
75
|
+
const promise1 = withUnitOfWork(uow1, async () => {
|
|
76
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
78
|
+
return serviceContext.getUnitOfWork(testSchema as any);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const promise2 = withUnitOfWork(uow2, async () => {
|
|
82
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
83
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
84
|
+
return serviceContext.getUnitOfWork(testSchema as any);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const [result1, result2] = await Promise.all([promise1, promise2]);
|
|
88
|
+
|
|
89
|
+
expect(result1).toBe(view1);
|
|
90
|
+
expect(result2).toBe(view2);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe("uowStorage", () => {
|
|
95
|
+
it("should return undefined when no UOW is stored", () => {
|
|
96
|
+
const stored = uowStorage.getStore();
|
|
97
|
+
expect(stored).toBeUndefined();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("should store and retrieve UOW", async () => {
|
|
101
|
+
const mockUow = { test: "uow" } as unknown as IUnitOfWorkBase;
|
|
102
|
+
|
|
103
|
+
await withUnitOfWork(mockUow, () => {
|
|
104
|
+
const stored = uowStorage.getStore();
|
|
105
|
+
expect(stored).toBe(mockUow);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Should be undefined after exiting context
|
|
109
|
+
const stored = uowStorage.getStore();
|
|
110
|
+
expect(stored).toBeUndefined();
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe("withUnitOfWork", () => {
|
|
115
|
+
it("should execute callback with UOW in context", async () => {
|
|
116
|
+
const mockSchemaView = { test: "schema-view" };
|
|
117
|
+
const mockUow = {
|
|
118
|
+
test: "uow",
|
|
119
|
+
forSchema: () => mockSchemaView,
|
|
120
|
+
} as unknown as IUnitOfWorkBase;
|
|
121
|
+
let called = false;
|
|
122
|
+
|
|
123
|
+
await withUnitOfWork(mockUow, () => {
|
|
124
|
+
called = true;
|
|
125
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
126
|
+
expect(serviceContext.getUnitOfWork(testSchema as any)).toBe(mockSchemaView);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
expect(called).toBe(true);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("should return callback result", async () => {
|
|
133
|
+
const mockUow = { test: "uow" } as unknown as IUnitOfWorkBase;
|
|
134
|
+
const result = await withUnitOfWork(mockUow, () => "test-result");
|
|
135
|
+
|
|
136
|
+
expect(result).toBe("test-result");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("should handle async callbacks", async () => {
|
|
140
|
+
const mockUow = { test: "uow" } as unknown as IUnitOfWorkBase;
|
|
141
|
+
|
|
142
|
+
const result = await withUnitOfWork(mockUow, async () => {
|
|
143
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
144
|
+
return "async-result";
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
expect(result).toBe("async-result");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("should propagate errors from callback", async () => {
|
|
151
|
+
const mockUow = { test: "uow" } as unknown as IUnitOfWorkBase;
|
|
152
|
+
|
|
153
|
+
let error: Error | null = null;
|
|
154
|
+
try {
|
|
155
|
+
await withUnitOfWork(mockUow, () => {
|
|
156
|
+
throw new Error("test error");
|
|
157
|
+
});
|
|
158
|
+
} catch (e) {
|
|
159
|
+
error = e as Error;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
expect(error).toBeInstanceOf(Error);
|
|
163
|
+
expect(error?.message).toBe("test error");
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("should clean up context after error", async () => {
|
|
167
|
+
const mockUow = { test: "uow" } as unknown as IUnitOfWorkBase;
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
await withUnitOfWork(mockUow, () => {
|
|
171
|
+
throw new Error("test error");
|
|
172
|
+
});
|
|
173
|
+
} catch {
|
|
174
|
+
// Expected error
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// UOW should no longer be in context
|
|
178
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
179
|
+
expect(() => serviceContext.getUnitOfWork(testSchema as any)).toThrow();
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
});
|
package/src/fragment.test.ts
DELETED
|
@@ -1,341 +0,0 @@
|
|
|
1
|
-
import { test, expect, describe, expectTypeOf } from "vitest";
|
|
2
|
-
import { defineFragmentWithDatabase, type FragnoPublicConfigWithDatabase } from "./fragment";
|
|
3
|
-
import { createFragment, defineRoute, type FragnoPublicClientConfig } from "@fragno-dev/core";
|
|
4
|
-
import { createClientBuilder } from "@fragno-dev/core/client";
|
|
5
|
-
import { schema, idColumn, column } from "./schema/create";
|
|
6
|
-
import type { AbstractQuery } from "./query/query";
|
|
7
|
-
import type { DatabaseAdapter } from "./mod";
|
|
8
|
-
import { z } from "zod";
|
|
9
|
-
import {
|
|
10
|
-
fragnoDatabaseAdapterNameFakeSymbol,
|
|
11
|
-
fragnoDatabaseAdapterVersionFakeSymbol,
|
|
12
|
-
} from "./adapters/adapters";
|
|
13
|
-
|
|
14
|
-
type Empty = Record<never, never>;
|
|
15
|
-
|
|
16
|
-
const mockDatabaseAdapter: DatabaseAdapter = {
|
|
17
|
-
[fragnoDatabaseAdapterNameFakeSymbol]: "mock",
|
|
18
|
-
[fragnoDatabaseAdapterVersionFakeSymbol]: 0,
|
|
19
|
-
close: () => Promise.resolve(),
|
|
20
|
-
createQueryEngine: () => {
|
|
21
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
|
-
return {} as any;
|
|
23
|
-
},
|
|
24
|
-
getSchemaVersion: () => Promise.resolve("0"),
|
|
25
|
-
createMigrationEngine: () => {
|
|
26
|
-
throw new Error("Not implemented");
|
|
27
|
-
},
|
|
28
|
-
createSchemaGenerator: () => {
|
|
29
|
-
throw new Error("Not implemented");
|
|
30
|
-
},
|
|
31
|
-
isConnectionHealthy: () => Promise.resolve(true),
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
describe("DatabaseFragmentBuilder", () => {
|
|
35
|
-
describe("Type inference", () => {
|
|
36
|
-
test("defineFragmentWithDatabase infers schema type from withDatabase", () => {
|
|
37
|
-
const _testSchema = schema((s) =>
|
|
38
|
-
s.addTable("users", (t) =>
|
|
39
|
-
t.addColumn("id", idColumn()).addColumn("name", column("string")),
|
|
40
|
-
),
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
const _fragment = defineFragmentWithDatabase("test").withDatabase(_testSchema);
|
|
44
|
-
|
|
45
|
-
// Type check that withDatabase returns a builder with the schema
|
|
46
|
-
expectTypeOf(_fragment.definition.name).toEqualTypeOf<string>();
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test("withDatabase correctly transforms schema type", () => {
|
|
50
|
-
const _testSchema1 = schema((s) =>
|
|
51
|
-
s.addTable("users", (t) =>
|
|
52
|
-
t.addColumn("id", idColumn()).addColumn("name", column("string")),
|
|
53
|
-
),
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
const _testSchema2 = schema((s) =>
|
|
57
|
-
s.addTable("posts", (t) =>
|
|
58
|
-
t.addColumn("id", idColumn()).addColumn("title", column("string")),
|
|
59
|
-
),
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
const fragment1 = defineFragmentWithDatabase("test").withDatabase(_testSchema1);
|
|
63
|
-
const fragment2 = fragment1.withDatabase(_testSchema2);
|
|
64
|
-
|
|
65
|
-
// Type check that we can chain withDatabase
|
|
66
|
-
expectTypeOf(fragment1.definition.name).toEqualTypeOf<string>();
|
|
67
|
-
expectTypeOf(fragment2.definition.name).toEqualTypeOf<string>();
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test("withDependencies has access to config, fragnoConfig, and orm", () => {
|
|
71
|
-
const _testSchema = schema((s) =>
|
|
72
|
-
s.addTable("users", (t) =>
|
|
73
|
-
t.addColumn("id", idColumn()).addColumn("name", column("string")),
|
|
74
|
-
),
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
const _fragment = defineFragmentWithDatabase("test")
|
|
78
|
-
.withDatabase(_testSchema)
|
|
79
|
-
.withDependencies(({ fragnoConfig, orm }) => {
|
|
80
|
-
expectTypeOf(fragnoConfig).toEqualTypeOf<{ mountRoute?: string }>();
|
|
81
|
-
expectTypeOf(orm).toEqualTypeOf<AbstractQuery<typeof _testSchema>>();
|
|
82
|
-
|
|
83
|
-
return {
|
|
84
|
-
userService: {
|
|
85
|
-
getUser: async (id: string) => ({ id, name: "Test" }),
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// Type check that the fragment has the expected structure
|
|
91
|
-
expectTypeOf(_fragment.definition.name).toEqualTypeOf<string>();
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test("withServices has access to config, fragnoConfig, deps, and orm", () => {
|
|
95
|
-
const _testSchema = schema((s) =>
|
|
96
|
-
s.addTable("users", (t) =>
|
|
97
|
-
t.addColumn("id", idColumn()).addColumn("name", column("string")),
|
|
98
|
-
),
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
const _fragment = defineFragmentWithDatabase("test")
|
|
102
|
-
.withDatabase(_testSchema)
|
|
103
|
-
.withDependencies(({ orm }) => ({
|
|
104
|
-
userRepo: {
|
|
105
|
-
create: (name: string) => orm.create("users", { name }),
|
|
106
|
-
},
|
|
107
|
-
}))
|
|
108
|
-
.withServices(({ fragnoConfig, deps, orm }) => {
|
|
109
|
-
expectTypeOf(fragnoConfig).toEqualTypeOf<{ mountRoute?: string }>();
|
|
110
|
-
expectTypeOf(deps).toEqualTypeOf<{
|
|
111
|
-
userRepo: {
|
|
112
|
-
create: (name: string) => ReturnType<AbstractQuery<typeof _testSchema>["create"]>;
|
|
113
|
-
};
|
|
114
|
-
}>();
|
|
115
|
-
expectTypeOf(orm).toEqualTypeOf<AbstractQuery<typeof _testSchema>>();
|
|
116
|
-
|
|
117
|
-
return {
|
|
118
|
-
cacheService: {
|
|
119
|
-
get: (_key: string) => "cached",
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
// Type check that the fragment has the expected structure
|
|
125
|
-
expectTypeOf(_fragment.definition.name).toEqualTypeOf<string>();
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
describe("Builder pattern", () => {
|
|
130
|
-
test("Builder methods return new instances", () => {
|
|
131
|
-
const _testSchema1 = schema((s) =>
|
|
132
|
-
s.addTable("users", (t) =>
|
|
133
|
-
t.addColumn("id", idColumn()).addColumn("name", column("string")),
|
|
134
|
-
),
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
const _testSchema2 = schema((s) =>
|
|
138
|
-
s.addTable("posts", (t) =>
|
|
139
|
-
t.addColumn("id", idColumn()).addColumn("title", column("string")),
|
|
140
|
-
),
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
const builder1 = defineFragmentWithDatabase("test");
|
|
144
|
-
const builder2 = builder1.withDatabase(_testSchema1);
|
|
145
|
-
const builder3 = builder2.withDatabase(_testSchema2);
|
|
146
|
-
const builder4 = builder3.withDependencies(() => ({ dep1: "value1" }));
|
|
147
|
-
const builder5 = builder4.withServices(() => ({ service1: "value1" }));
|
|
148
|
-
|
|
149
|
-
expect(builder1).not.toBe(builder2);
|
|
150
|
-
expect(builder2).not.toBe(builder3);
|
|
151
|
-
expect(builder3).not.toBe(builder4);
|
|
152
|
-
expect(builder4).not.toBe(builder5);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
test("Each builder step preserves previous configuration", () => {
|
|
156
|
-
const _testSchema = schema((s) =>
|
|
157
|
-
s.addTable("users", (t) =>
|
|
158
|
-
t.addColumn("id", idColumn()).addColumn("name", column("string")),
|
|
159
|
-
),
|
|
160
|
-
);
|
|
161
|
-
|
|
162
|
-
const fragment = defineFragmentWithDatabase("my-db-lib")
|
|
163
|
-
.withDatabase(_testSchema)
|
|
164
|
-
.withDependencies(({ orm }) => ({
|
|
165
|
-
client: "test client",
|
|
166
|
-
orm,
|
|
167
|
-
}))
|
|
168
|
-
.withServices(({ deps }) => ({
|
|
169
|
-
service: `Service using ${deps.client}`,
|
|
170
|
-
}));
|
|
171
|
-
|
|
172
|
-
expect(fragment.definition.name).toBe("my-db-lib");
|
|
173
|
-
expect(fragment.definition.dependencies).toBeDefined();
|
|
174
|
-
expect(fragment.definition.services).toBeDefined();
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
describe("Fragment instantiation", () => {
|
|
179
|
-
test("createFragment works with database adapter", async () => {
|
|
180
|
-
const testSchema = schema((s) =>
|
|
181
|
-
s.addTable("users", (t) =>
|
|
182
|
-
t.addColumn("id", idColumn()).addColumn("name", column("string")),
|
|
183
|
-
),
|
|
184
|
-
);
|
|
185
|
-
|
|
186
|
-
const fragmentDef = defineFragmentWithDatabase("test-db")
|
|
187
|
-
.withDatabase(testSchema)
|
|
188
|
-
.withDependencies(({ orm }) => ({
|
|
189
|
-
userService: {
|
|
190
|
-
createUser: (name: string) => orm.create("users", { name }),
|
|
191
|
-
},
|
|
192
|
-
}))
|
|
193
|
-
.withServices(() => ({
|
|
194
|
-
logger: { log: (s: string) => console.log(s) },
|
|
195
|
-
}));
|
|
196
|
-
|
|
197
|
-
const options: FragnoPublicConfigWithDatabase = {
|
|
198
|
-
databaseAdapter: mockDatabaseAdapter,
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
const fragment = createFragment(fragmentDef, {}, [], options);
|
|
202
|
-
|
|
203
|
-
expect(fragment.config.name).toBe("test-db");
|
|
204
|
-
expect(fragment.deps).toHaveProperty("userService");
|
|
205
|
-
expect(fragment.services).toHaveProperty("logger");
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
test("throws error when database adapter is missing from dependencies", () => {
|
|
209
|
-
const testSchema = schema((s) =>
|
|
210
|
-
s.addTable("users", (t) =>
|
|
211
|
-
t.addColumn("id", idColumn()).addColumn("name", column("string")),
|
|
212
|
-
),
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
const fragmentDef = defineFragmentWithDatabase("test-db")
|
|
216
|
-
.withDatabase(testSchema)
|
|
217
|
-
.withDependencies(() => ({
|
|
218
|
-
service: "test",
|
|
219
|
-
}));
|
|
220
|
-
|
|
221
|
-
expect(() => {
|
|
222
|
-
// @ts-expect-error - Test case
|
|
223
|
-
createFragment(fragmentDef, {}, [], {});
|
|
224
|
-
}).toThrow(/requires a database adapter/);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
test("throws error when database adapter is missing from services", () => {
|
|
228
|
-
const testSchema = schema((s) =>
|
|
229
|
-
s.addTable("users", (t) =>
|
|
230
|
-
t.addColumn("id", idColumn()).addColumn("name", column("string")),
|
|
231
|
-
),
|
|
232
|
-
);
|
|
233
|
-
|
|
234
|
-
const fragmentDef = defineFragmentWithDatabase("test-db")
|
|
235
|
-
.withDatabase(testSchema)
|
|
236
|
-
.withDependencies(() => ({
|
|
237
|
-
service: "test",
|
|
238
|
-
}))
|
|
239
|
-
.withServices(() => ({
|
|
240
|
-
serviceValue: "test",
|
|
241
|
-
}));
|
|
242
|
-
|
|
243
|
-
const options: FragnoPublicConfigWithDatabase = {
|
|
244
|
-
databaseAdapter: mockDatabaseAdapter,
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
// Services are called after dependencies, so this should work
|
|
248
|
-
const fragment = createFragment(fragmentDef, {}, [], options);
|
|
249
|
-
expect(fragment.services).toHaveProperty("serviceValue");
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
test("throws error when schema is not provided via withDatabase", () => {
|
|
253
|
-
const fragmentDef = defineFragmentWithDatabase<Empty>("test-db").withDependencies(() => ({
|
|
254
|
-
service: "test",
|
|
255
|
-
}));
|
|
256
|
-
|
|
257
|
-
const options: FragnoPublicConfigWithDatabase = {
|
|
258
|
-
databaseAdapter: mockDatabaseAdapter,
|
|
259
|
-
};
|
|
260
|
-
|
|
261
|
-
expect(() => {
|
|
262
|
-
createFragment(fragmentDef, {}, [], options);
|
|
263
|
-
}).toThrow(/requires a schema/);
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
test("orm is accessible in both dependencies and services", async () => {
|
|
267
|
-
const testSchema = schema((s) =>
|
|
268
|
-
s.addTable("users", (t) =>
|
|
269
|
-
t.addColumn("id", idColumn()).addColumn("name", column("string")),
|
|
270
|
-
),
|
|
271
|
-
);
|
|
272
|
-
|
|
273
|
-
let depsOrm: AbstractQuery<typeof testSchema> | undefined;
|
|
274
|
-
let servicesOrm: AbstractQuery<typeof testSchema> | undefined;
|
|
275
|
-
|
|
276
|
-
const fragmentDef = defineFragmentWithDatabase("test-db")
|
|
277
|
-
.withDatabase(testSchema)
|
|
278
|
-
.withDependencies(({ orm }) => {
|
|
279
|
-
depsOrm = orm;
|
|
280
|
-
return { dep: "value" };
|
|
281
|
-
})
|
|
282
|
-
.withServices(({ orm }) => {
|
|
283
|
-
servicesOrm = orm;
|
|
284
|
-
return { service: "value" };
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
const options: FragnoPublicConfigWithDatabase = {
|
|
288
|
-
databaseAdapter: mockDatabaseAdapter,
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
createFragment(fragmentDef, {}, [], options);
|
|
292
|
-
|
|
293
|
-
expect(depsOrm).toBeDefined();
|
|
294
|
-
expect(servicesOrm).toBeDefined();
|
|
295
|
-
});
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
describe("Client builder integration", () => {
|
|
299
|
-
test("createClientBuilder works with database fragment", () => {
|
|
300
|
-
const testSchema = schema((s) =>
|
|
301
|
-
s.addTable("users", (t) =>
|
|
302
|
-
t.addColumn("id", idColumn()).addColumn("name", column("string")),
|
|
303
|
-
),
|
|
304
|
-
);
|
|
305
|
-
|
|
306
|
-
const fragmentDef = defineFragmentWithDatabase("test-db")
|
|
307
|
-
.withDatabase(testSchema)
|
|
308
|
-
.withServices(({ orm }) => ({
|
|
309
|
-
getUserById: (id: string) =>
|
|
310
|
-
orm.findFirst("users", (b) => b.whereIndex("primary", (eb) => eb("id", "=", id))),
|
|
311
|
-
}));
|
|
312
|
-
|
|
313
|
-
const routes = [
|
|
314
|
-
defineRoute({
|
|
315
|
-
method: "GET",
|
|
316
|
-
path: "/users",
|
|
317
|
-
outputSchema: z.array(
|
|
318
|
-
z.object({
|
|
319
|
-
id: z.string(),
|
|
320
|
-
name: z.string(),
|
|
321
|
-
}),
|
|
322
|
-
),
|
|
323
|
-
handler: async (_ctx, { json }) => json([]),
|
|
324
|
-
}),
|
|
325
|
-
] as const;
|
|
326
|
-
|
|
327
|
-
const clientConfig: FragnoPublicClientConfig = {
|
|
328
|
-
baseUrl: "http://localhost:3000",
|
|
329
|
-
};
|
|
330
|
-
|
|
331
|
-
const builder = createClientBuilder(fragmentDef, clientConfig, routes);
|
|
332
|
-
|
|
333
|
-
expect(builder).toBeDefined();
|
|
334
|
-
expectTypeOf(builder.createHook).toBeFunction();
|
|
335
|
-
|
|
336
|
-
const useUsers = builder.createHook("/users");
|
|
337
|
-
expect(useUsers).toHaveProperty("route");
|
|
338
|
-
expect(useUsers.route.path).toBe("/users");
|
|
339
|
-
});
|
|
340
|
-
});
|
|
341
|
-
});
|