@fragno-dev/db 0.1.13 → 0.1.15
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 +179 -132
- package/CHANGELOG.md +30 -0
- package/dist/adapters/adapters.d.ts +27 -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 +5 -1
- package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +1 -1
- package/dist/adapters/drizzle/drizzle-adapter.js +15 -3
- package/dist/adapters/drizzle/drizzle-adapter.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-query.js +7 -5
- 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 +76 -44
- package/dist/adapters/drizzle/drizzle-uow-compiler.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-decoder.js +23 -16
- package/dist/adapters/drizzle/drizzle-uow-decoder.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-executor.js +18 -7
- package/dist/adapters/drizzle/drizzle-uow-executor.js.map +1 -1
- package/dist/adapters/drizzle/generate.d.ts +4 -1
- package/dist/adapters/drizzle/generate.d.ts.map +1 -1
- package/dist/adapters/drizzle/generate.js +11 -18
- package/dist/adapters/drizzle/generate.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 +5 -1
- package/dist/adapters/kysely/kysely-adapter.d.ts.map +1 -1
- package/dist/adapters/kysely/kysely-adapter.js +14 -3
- package/dist/adapters/kysely/kysely-adapter.js.map +1 -1
- package/dist/adapters/kysely/kysely-query-builder.js +1 -1
- package/dist/adapters/kysely/kysely-query-compiler.js +3 -2
- package/dist/adapters/kysely/kysely-query-compiler.js.map +1 -1
- package/dist/adapters/kysely/kysely-query.d.ts +1 -0
- package/dist/adapters/kysely/kysely-query.d.ts.map +1 -1
- package/dist/adapters/kysely/kysely-query.js +28 -19
- package/dist/adapters/kysely/kysely-query.js.map +1 -1
- package/dist/adapters/kysely/kysely-shared.d.ts +14 -0
- package/dist/adapters/kysely/kysely-shared.d.ts.map +1 -0
- package/dist/adapters/kysely/kysely-shared.js +16 -1
- package/dist/adapters/kysely/kysely-shared.js.map +1 -1
- package/dist/adapters/kysely/kysely-uow-compiler.js +68 -16
- package/dist/adapters/kysely/kysely-uow-compiler.js.map +1 -1
- package/dist/adapters/kysely/kysely-uow-executor.js +8 -4
- package/dist/adapters/kysely/kysely-uow-executor.js.map +1 -1
- package/dist/adapters/kysely/migration/execute-base.js +1 -1
- package/dist/adapters/kysely/migration/execute-base.js.map +1 -1
- package/dist/db-fragment-definition-builder.d.ts +152 -0
- package/dist/db-fragment-definition-builder.d.ts.map +1 -0
- package/dist/db-fragment-definition-builder.js +137 -0
- package/dist/db-fragment-definition-builder.js.map +1 -0
- package/dist/fragments/internal-fragment.d.ts +19 -0
- package/dist/fragments/internal-fragment.d.ts.map +1 -0
- package/dist/fragments/internal-fragment.js +39 -0
- package/dist/fragments/internal-fragment.js.map +1 -0
- package/dist/migration-engine/generation-engine.d.ts.map +1 -1
- package/dist/migration-engine/generation-engine.js +35 -15
- package/dist/migration-engine/generation-engine.js.map +1 -1
- package/dist/mod.d.ts +8 -18
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +7 -34
- package/dist/mod.js.map +1 -1
- package/dist/node_modules/.pnpm/rou3@0.7.8/node_modules/rou3/dist/index.js +165 -0
- package/dist/node_modules/.pnpm/rou3@0.7.8/node_modules/rou3/dist/index.js.map +1 -0
- package/dist/packages/fragno/dist/api/bind-services.js +20 -0
- package/dist/packages/fragno/dist/api/bind-services.js.map +1 -0
- package/dist/packages/fragno/dist/api/error.js +48 -0
- package/dist/packages/fragno/dist/api/error.js.map +1 -0
- package/dist/packages/fragno/dist/api/fragment-definition-builder.js +320 -0
- package/dist/packages/fragno/dist/api/fragment-definition-builder.js.map +1 -0
- package/dist/packages/fragno/dist/api/fragment-instantiator.js +487 -0
- package/dist/packages/fragno/dist/api/fragment-instantiator.js.map +1 -0
- package/dist/packages/fragno/dist/api/fragno-response.js +73 -0
- package/dist/packages/fragno/dist/api/fragno-response.js.map +1 -0
- package/dist/packages/fragno/dist/api/internal/response-stream.js +81 -0
- package/dist/packages/fragno/dist/api/internal/response-stream.js.map +1 -0
- package/dist/packages/fragno/dist/api/internal/route.js +10 -0
- package/dist/packages/fragno/dist/api/internal/route.js.map +1 -0
- package/dist/packages/fragno/dist/api/mutable-request-state.js +97 -0
- package/dist/packages/fragno/dist/api/mutable-request-state.js.map +1 -0
- package/dist/packages/fragno/dist/api/request-context-storage.js +43 -0
- package/dist/packages/fragno/dist/api/request-context-storage.js.map +1 -0
- package/dist/packages/fragno/dist/api/request-input-context.js +118 -0
- package/dist/packages/fragno/dist/api/request-input-context.js.map +1 -0
- package/dist/packages/fragno/dist/api/request-middleware.js +83 -0
- package/dist/packages/fragno/dist/api/request-middleware.js.map +1 -0
- package/dist/packages/fragno/dist/api/request-output-context.js +119 -0
- package/dist/packages/fragno/dist/api/request-output-context.js.map +1 -0
- package/dist/packages/fragno/dist/api/route.js +17 -0
- package/dist/packages/fragno/dist/api/route.js.map +1 -0
- package/dist/packages/fragno/dist/internal/symbols.js +10 -0
- package/dist/packages/fragno/dist/internal/symbols.js.map +1 -0
- package/dist/query/cursor.d.ts +10 -2
- package/dist/query/cursor.d.ts.map +1 -1
- package/dist/query/cursor.js +11 -4
- package/dist/query/cursor.js.map +1 -1
- package/dist/query/execute-unit-of-work.d.ts +123 -0
- package/dist/query/execute-unit-of-work.d.ts.map +1 -0
- package/dist/query/execute-unit-of-work.js +184 -0
- package/dist/query/execute-unit-of-work.js.map +1 -0
- package/dist/query/query.d.ts +3 -3
- package/dist/query/query.d.ts.map +1 -1
- package/dist/query/result-transform.js +4 -2
- package/dist/query/result-transform.js.map +1 -1
- package/dist/query/retry-policy.d.ts +88 -0
- package/dist/query/retry-policy.d.ts.map +1 -0
- package/dist/query/retry-policy.js +61 -0
- package/dist/query/retry-policy.js.map +1 -0
- package/dist/query/unit-of-work.d.ts +171 -32
- package/dist/query/unit-of-work.d.ts.map +1 -1
- package/dist/query/unit-of-work.js +530 -133
- package/dist/query/unit-of-work.js.map +1 -1
- package/dist/schema/serialize.js +12 -7
- package/dist/schema/serialize.js.map +1 -1
- package/dist/with-database.d.ts +28 -0
- package/dist/with-database.d.ts.map +1 -0
- package/dist/with-database.js +34 -0
- package/dist/with-database.js.map +1 -0
- package/package.json +10 -3
- package/src/adapters/adapters.ts +30 -0
- package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +86 -17
- package/src/adapters/drizzle/drizzle-adapter-sqlite.test.ts +291 -7
- package/src/adapters/drizzle/drizzle-adapter.test.ts +3 -51
- package/src/adapters/drizzle/drizzle-adapter.ts +35 -7
- package/src/adapters/drizzle/drizzle-query.ts +25 -15
- package/src/adapters/drizzle/drizzle-uow-compiler-mysql.test.ts +1442 -0
- package/src/adapters/drizzle/drizzle-uow-compiler-sqlite.test.ts +1414 -0
- package/src/adapters/drizzle/drizzle-uow-compiler.test.ts +78 -61
- package/src/adapters/drizzle/drizzle-uow-compiler.ts +123 -42
- package/src/adapters/drizzle/drizzle-uow-decoder.ts +34 -27
- package/src/adapters/drizzle/drizzle-uow-executor.ts +41 -8
- package/src/adapters/drizzle/generate.test.ts +102 -269
- package/src/adapters/drizzle/generate.ts +12 -30
- package/src/adapters/drizzle/test-utils.ts +36 -5
- package/src/adapters/kysely/kysely-adapter-pglite.test.ts +66 -22
- package/src/adapters/kysely/kysely-adapter-sqlite.test.ts +156 -0
- package/src/adapters/kysely/kysely-adapter.ts +25 -2
- package/src/adapters/kysely/kysely-query-compiler.ts +3 -8
- package/src/adapters/kysely/kysely-query.ts +57 -37
- package/src/adapters/kysely/kysely-shared.ts +34 -0
- package/src/adapters/kysely/kysely-uow-compiler.test.ts +62 -74
- package/src/adapters/kysely/kysely-uow-compiler.ts +92 -24
- package/src/adapters/kysely/kysely-uow-executor.ts +26 -7
- package/src/adapters/kysely/kysely-uow-joins.test.ts +33 -50
- package/src/adapters/kysely/migration/execute-base.ts +1 -1
- package/src/db-fragment-definition-builder.test.ts +887 -0
- package/src/db-fragment-definition-builder.ts +506 -0
- package/src/db-fragment-instantiator.test.ts +467 -0
- package/src/db-fragment-integration.test.ts +408 -0
- package/src/fragments/internal-fragment.test.ts +160 -0
- package/src/fragments/internal-fragment.ts +85 -0
- package/src/migration-engine/generation-engine.test.ts +58 -15
- package/src/migration-engine/generation-engine.ts +78 -25
- package/src/mod.ts +35 -43
- package/src/query/cursor.test.ts +119 -0
- package/src/query/cursor.ts +17 -4
- package/src/query/execute-unit-of-work.test.ts +1310 -0
- package/src/query/execute-unit-of-work.ts +463 -0
- package/src/query/query.ts +4 -4
- package/src/query/result-transform.test.ts +129 -0
- package/src/query/result-transform.ts +4 -1
- package/src/query/retry-policy.test.ts +217 -0
- package/src/query/retry-policy.ts +141 -0
- package/src/query/unit-of-work-coordinator.test.ts +833 -0
- package/src/query/unit-of-work-types.test.ts +15 -2
- package/src/query/unit-of-work.test.ts +878 -200
- package/src/query/unit-of-work.ts +963 -321
- package/src/schema/serialize.ts +22 -11
- package/src/with-database.ts +140 -0
- package/tsdown.config.ts +1 -0
- package/dist/fragment.d.ts +0 -54
- package/dist/fragment.d.ts.map +0 -1
- package/dist/fragment.js +0 -92
- package/dist/fragment.js.map +0 -1
- package/dist/shared/settings-schema.js +0 -36
- package/dist/shared/settings-schema.js.map +0 -1
- package/src/fragment.test.ts +0 -341
- package/src/fragment.ts +0 -198
- package/src/shared/settings-schema.ts +0 -61
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
|
-
});
|
package/src/fragment.ts
DELETED
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import type { AnySchema } from "./schema/create";
|
|
2
|
-
import type { AbstractQuery } from "./query/query";
|
|
3
|
-
import type { DatabaseAdapter } from "./adapters/adapters";
|
|
4
|
-
import type { FragnoPublicConfig, FragmentDefinition } from "@fragno-dev/core";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Extended FragnoPublicConfig that includes a database adapter.
|
|
8
|
-
* Use this type when creating fragments with database support.
|
|
9
|
-
*/
|
|
10
|
-
export type FragnoPublicConfigWithDatabase = FragnoPublicConfig & {
|
|
11
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12
|
-
databaseAdapter: DatabaseAdapter<any>;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Additional context provided to database fragments containing the database adapter and ORM instance.
|
|
17
|
-
*/
|
|
18
|
-
export type DatabaseFragmentContext<TSchema extends AnySchema> = {
|
|
19
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
|
-
databaseAdapter: DatabaseAdapter<any>;
|
|
21
|
-
orm: AbstractQuery<TSchema>;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export class DatabaseFragmentBuilder<
|
|
25
|
-
const TSchema extends AnySchema,
|
|
26
|
-
const TConfig,
|
|
27
|
-
const TDeps = {},
|
|
28
|
-
const TServices extends Record<string, unknown> = {},
|
|
29
|
-
> {
|
|
30
|
-
#name: string;
|
|
31
|
-
#schema?: TSchema;
|
|
32
|
-
#namespace?: string;
|
|
33
|
-
#dependencies?: (
|
|
34
|
-
context: {
|
|
35
|
-
config: TConfig;
|
|
36
|
-
fragnoConfig: FragnoPublicConfig;
|
|
37
|
-
} & DatabaseFragmentContext<TSchema>,
|
|
38
|
-
) => TDeps;
|
|
39
|
-
#services?: (
|
|
40
|
-
context: {
|
|
41
|
-
config: TConfig;
|
|
42
|
-
fragnoConfig: FragnoPublicConfig;
|
|
43
|
-
deps: TDeps;
|
|
44
|
-
} & DatabaseFragmentContext<TSchema>,
|
|
45
|
-
) => TServices;
|
|
46
|
-
|
|
47
|
-
constructor(options: {
|
|
48
|
-
name: string;
|
|
49
|
-
schema?: TSchema;
|
|
50
|
-
namespace?: string;
|
|
51
|
-
dependencies?: (
|
|
52
|
-
context: {
|
|
53
|
-
config: TConfig;
|
|
54
|
-
fragnoConfig: FragnoPublicConfig;
|
|
55
|
-
} & DatabaseFragmentContext<TSchema>,
|
|
56
|
-
) => TDeps;
|
|
57
|
-
services?: (
|
|
58
|
-
context: {
|
|
59
|
-
config: TConfig;
|
|
60
|
-
fragnoConfig: FragnoPublicConfig;
|
|
61
|
-
deps: TDeps;
|
|
62
|
-
} & DatabaseFragmentContext<TSchema>,
|
|
63
|
-
) => TServices;
|
|
64
|
-
}) {
|
|
65
|
-
this.#name = options.name;
|
|
66
|
-
this.#schema = options.schema;
|
|
67
|
-
this.#namespace = options.namespace;
|
|
68
|
-
this.#dependencies = options.dependencies;
|
|
69
|
-
this.#services = options.services;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
get $requiredOptions(): FragnoPublicConfigWithDatabase {
|
|
73
|
-
throw new Error("Type only method. Do not call.");
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
get definition(): FragmentDefinition<TConfig, TDeps, TServices> {
|
|
77
|
-
const schema = this.#schema;
|
|
78
|
-
const namespace = this.#namespace ?? "";
|
|
79
|
-
const name = this.#name;
|
|
80
|
-
const dependencies = this.#dependencies;
|
|
81
|
-
const services = this.#services;
|
|
82
|
-
|
|
83
|
-
return {
|
|
84
|
-
name,
|
|
85
|
-
dependencies: (config: TConfig, options: FragnoPublicConfig) => {
|
|
86
|
-
const dbContext = this.#createDatabaseContext(options, schema, namespace, name);
|
|
87
|
-
return dependencies?.({ config, fragnoConfig: options, ...dbContext }) ?? ({} as TDeps);
|
|
88
|
-
},
|
|
89
|
-
services: (config: TConfig, options: FragnoPublicConfig, deps: TDeps) => {
|
|
90
|
-
const dbContext = this.#createDatabaseContext(options, schema, namespace, name);
|
|
91
|
-
return (
|
|
92
|
-
services?.({ config, fragnoConfig: options, deps, ...dbContext }) ?? ({} as TServices)
|
|
93
|
-
);
|
|
94
|
-
},
|
|
95
|
-
additionalContext: {
|
|
96
|
-
databaseSchema: schema,
|
|
97
|
-
databaseNamespace: namespace,
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
#createDatabaseContext(
|
|
103
|
-
options: FragnoPublicConfig,
|
|
104
|
-
schema: TSchema | undefined,
|
|
105
|
-
namespace: string,
|
|
106
|
-
name: string,
|
|
107
|
-
): DatabaseFragmentContext<TSchema> {
|
|
108
|
-
// Safe cast: FragnoPublicConfig is extended with databaseAdapter by the user
|
|
109
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
110
|
-
const databaseAdapter = (options as any).databaseAdapter as DatabaseAdapter<any> | undefined;
|
|
111
|
-
|
|
112
|
-
if (!databaseAdapter) {
|
|
113
|
-
throw new Error(`Fragment '${name}' requires a database adapter in options.databaseAdapter`);
|
|
114
|
-
}
|
|
115
|
-
if (!schema) {
|
|
116
|
-
throw new Error(`Fragment '${name}' requires a schema. Use withDatabase() to provide one.`);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Safe cast: we create a query engine for TSchema and know it will be AbstractQuery<TSchema>
|
|
120
|
-
const orm = databaseAdapter.createQueryEngine(
|
|
121
|
-
schema,
|
|
122
|
-
namespace,
|
|
123
|
-
) as unknown as AbstractQuery<TSchema>;
|
|
124
|
-
|
|
125
|
-
return { databaseAdapter, orm };
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
withDatabase<TNewSchema extends AnySchema>(
|
|
129
|
-
schema: TNewSchema,
|
|
130
|
-
namespace?: string,
|
|
131
|
-
): DatabaseFragmentBuilder<TNewSchema, TConfig, TDeps, TServices> {
|
|
132
|
-
return new DatabaseFragmentBuilder<TNewSchema, TConfig, TDeps, TServices>({
|
|
133
|
-
name: this.#name,
|
|
134
|
-
schema,
|
|
135
|
-
namespace: namespace ?? this.#name + "-db",
|
|
136
|
-
dependencies: this.#dependencies as
|
|
137
|
-
| ((
|
|
138
|
-
context: {
|
|
139
|
-
config: TConfig;
|
|
140
|
-
fragnoConfig: FragnoPublicConfig;
|
|
141
|
-
} & DatabaseFragmentContext<TNewSchema>,
|
|
142
|
-
) => TDeps)
|
|
143
|
-
| undefined,
|
|
144
|
-
services: this.#services as
|
|
145
|
-
| ((
|
|
146
|
-
context: {
|
|
147
|
-
config: TConfig;
|
|
148
|
-
fragnoConfig: FragnoPublicConfig;
|
|
149
|
-
deps: TDeps;
|
|
150
|
-
} & DatabaseFragmentContext<TNewSchema>,
|
|
151
|
-
) => TServices)
|
|
152
|
-
| undefined,
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
withDependencies<TNewDeps>(
|
|
157
|
-
fn: (
|
|
158
|
-
context: {
|
|
159
|
-
config: TConfig;
|
|
160
|
-
fragnoConfig: FragnoPublicConfig;
|
|
161
|
-
} & DatabaseFragmentContext<TSchema>,
|
|
162
|
-
) => TNewDeps,
|
|
163
|
-
): DatabaseFragmentBuilder<TSchema, TConfig, TNewDeps, {}> {
|
|
164
|
-
return new DatabaseFragmentBuilder<TSchema, TConfig, TNewDeps, {}>({
|
|
165
|
-
name: this.#name,
|
|
166
|
-
schema: this.#schema,
|
|
167
|
-
namespace: this.#namespace,
|
|
168
|
-
dependencies: fn,
|
|
169
|
-
services: undefined,
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
withServices<TNewServices extends Record<string, unknown>>(
|
|
174
|
-
fn: (
|
|
175
|
-
context: {
|
|
176
|
-
config: TConfig;
|
|
177
|
-
fragnoConfig: FragnoPublicConfig;
|
|
178
|
-
deps: TDeps;
|
|
179
|
-
} & DatabaseFragmentContext<TSchema>,
|
|
180
|
-
) => TNewServices,
|
|
181
|
-
): DatabaseFragmentBuilder<TSchema, TConfig, TDeps, TNewServices> {
|
|
182
|
-
return new DatabaseFragmentBuilder<TSchema, TConfig, TDeps, TNewServices>({
|
|
183
|
-
name: this.#name,
|
|
184
|
-
schema: this.#schema,
|
|
185
|
-
namespace: this.#namespace,
|
|
186
|
-
dependencies: this.#dependencies,
|
|
187
|
-
services: fn,
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
export function defineFragmentWithDatabase<TConfig = {}>(
|
|
193
|
-
name: string,
|
|
194
|
-
): DatabaseFragmentBuilder<never, TConfig, {}, {}> {
|
|
195
|
-
return new DatabaseFragmentBuilder<never, TConfig, {}, {}>({
|
|
196
|
-
name,
|
|
197
|
-
});
|
|
198
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import type { AbstractQuery } from "../query/query";
|
|
2
|
-
import { schema, idColumn, column, type FragnoId } from "../schema/create";
|
|
3
|
-
|
|
4
|
-
export const SETTINGS_TABLE_NAME = "fragno_db_settings" as const;
|
|
5
|
-
export const SETTINGS_NAMESPACE = "fragno-db-settings" as const;
|
|
6
|
-
|
|
7
|
-
export const settingsSchema = schema((s) => {
|
|
8
|
-
return s.addTable(SETTINGS_TABLE_NAME, (t) => {
|
|
9
|
-
return t
|
|
10
|
-
.addColumn("id", idColumn())
|
|
11
|
-
.addColumn("key", column("string"))
|
|
12
|
-
.addColumn("value", column("string"))
|
|
13
|
-
.createIndex("unique_key", ["key"], { unique: true });
|
|
14
|
-
});
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
export function createSettingsManager(
|
|
18
|
-
// oxlint-disable-next-line no-explicit-any
|
|
19
|
-
queryEngine: AbstractQuery<typeof settingsSchema, any>,
|
|
20
|
-
namespace: string,
|
|
21
|
-
) {
|
|
22
|
-
return {
|
|
23
|
-
async get(key: string): Promise<{ id: FragnoId; key: string; value: string } | undefined> {
|
|
24
|
-
const uow = queryEngine
|
|
25
|
-
.createUnitOfWork()
|
|
26
|
-
.find(SETTINGS_TABLE_NAME, (b) =>
|
|
27
|
-
b.whereIndex("unique_key", (eb) => eb("key", "=", `${namespace}.${key}`)),
|
|
28
|
-
);
|
|
29
|
-
const [[result]] = await uow.executeRetrieve();
|
|
30
|
-
return result; // Safe: result can be undefined if key doesn't exist
|
|
31
|
-
},
|
|
32
|
-
|
|
33
|
-
async set(key: string, value: string) {
|
|
34
|
-
const uow = queryEngine
|
|
35
|
-
.createUnitOfWork("createSettingsManager#set")
|
|
36
|
-
.find(SETTINGS_TABLE_NAME, (b) =>
|
|
37
|
-
b.whereIndex("unique_key", (eb) => eb("key", "=", `${namespace}.${key}`)),
|
|
38
|
-
);
|
|
39
|
-
const [[existing]] = await uow.executeRetrieve();
|
|
40
|
-
|
|
41
|
-
if (existing) {
|
|
42
|
-
uow.update(SETTINGS_TABLE_NAME, existing.id, (b) => b.set({ value }).check());
|
|
43
|
-
} else {
|
|
44
|
-
uow.create(SETTINGS_TABLE_NAME, {
|
|
45
|
-
key: `${namespace}.${key}`,
|
|
46
|
-
value,
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const { success } = await uow.executeMutations();
|
|
51
|
-
|
|
52
|
-
if (!success) {
|
|
53
|
-
throw new Error("Failed to set schema version");
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
|
|
57
|
-
async delete(id: FragnoId) {
|
|
58
|
-
await queryEngine.delete(SETTINGS_TABLE_NAME, id);
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
}
|