@fragno-dev/corpus 0.0.5 → 0.0.7
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/README.md +4 -2
- package/dist/subjects/client-state-management.md +3 -7
- package/dist/subjects/database-querying.md +13 -11
- package/dist/subjects/defining-routes.md +23 -66
- package/dist/subjects/drizzle-adapter.md +0 -51
- package/dist/subjects/fragment-instantiation.md +19 -16
- package/dist/subjects/fragment-services.md +63 -52
- package/dist/subjects/kysely-adapter.md +32 -34
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -53,8 +53,10 @@ Optional ID syntax (`:schema`) helps identify code blocks for agent references.
|
|
|
53
53
|
|
|
54
54
|
Test-only initialization code (not shown to users, only used in generated tests):
|
|
55
55
|
|
|
56
|
-
\`\`\`typescript @fragno-test-init const {
|
|
57
|
-
|
|
56
|
+
\`\`\`typescript @fragno-test-init const { fragments } = await buildDatabaseFragmentsTest()
|
|
57
|
+
.withTestAdapter({ type: "kysely-sqlite" }) .withFragment("test",
|
|
58
|
+
instantiate(testFragmentDef).withRoutes([]), { definition: testFragmentDef }) .build(); const db =
|
|
59
|
+
fragments.test.db; \`\`\`
|
|
58
60
|
|
|
59
61
|
### @fragno-test
|
|
60
62
|
|
|
@@ -5,12 +5,8 @@ that integrate with React, Vue, Svelte, and vanilla JavaScript. The `ClientBuild
|
|
|
5
5
|
hooks and mutators for your routes.
|
|
6
6
|
|
|
7
7
|
```typescript @fragno-imports
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
defineRoute,
|
|
11
|
-
defineRoutes,
|
|
12
|
-
type FragnoPublicClientConfig,
|
|
13
|
-
} from "@fragno-dev/core";
|
|
8
|
+
import { defineFragment, defineRoute, defineRoutes } from "@fragno-dev/core";
|
|
9
|
+
import type { FragnoPublicClientConfig } from "@fragno-dev/core/client";
|
|
14
10
|
import { createClientBuilder } from "@fragno-dev/core/client";
|
|
15
11
|
import { computed } from "nanostores";
|
|
16
12
|
import { z } from "zod";
|
|
@@ -23,7 +19,7 @@ interface TodoConfig {
|
|
|
23
19
|
|
|
24
20
|
const todoFragment = defineFragment<TodoConfig>("todos");
|
|
25
21
|
|
|
26
|
-
const routes = defineRoutes
|
|
22
|
+
const routes = defineRoutes(todoFragment).create(({ defineRoute }) => [
|
|
27
23
|
defineRoute({
|
|
28
24
|
method: "GET",
|
|
29
25
|
path: "/todos",
|
|
@@ -4,10 +4,11 @@ Fragno provides a unified database query API that works across different ORMs. T
|
|
|
4
4
|
CRUD operations and querying with conditions.
|
|
5
5
|
|
|
6
6
|
```typescript @fragno-imports
|
|
7
|
-
import {
|
|
7
|
+
import { defineFragment, instantiate } from "@fragno-dev/core";
|
|
8
|
+
import { withDatabase } from "@fragno-dev/db";
|
|
8
9
|
import { schema, idColumn, column } from "@fragno-dev/db/schema";
|
|
9
|
-
import type {
|
|
10
|
-
import {
|
|
10
|
+
import type { SimpleQueryInterface } from "@fragno-dev/db/query";
|
|
11
|
+
import { buildDatabaseFragmentsTest } from "@fragno-dev/test";
|
|
11
12
|
```
|
|
12
13
|
|
|
13
14
|
```typescript @fragno-prelude:schema
|
|
@@ -38,16 +39,17 @@ type UserSchema = typeof userSchema;
|
|
|
38
39
|
|
|
39
40
|
```typescript @fragno-test-init
|
|
40
41
|
// Create a test fragment with database
|
|
41
|
-
const testFragmentDef =
|
|
42
|
+
const testFragmentDef = defineFragment<{}>("test-fragment")
|
|
43
|
+
.extend(withDatabase(userSchema))
|
|
44
|
+
.providesBaseService(() => ({}))
|
|
45
|
+
.build();
|
|
42
46
|
|
|
43
|
-
const {
|
|
44
|
-
{
|
|
45
|
-
{
|
|
46
|
-
|
|
47
|
-
},
|
|
48
|
-
);
|
|
47
|
+
const { fragments, test } = await buildDatabaseFragmentsTest()
|
|
48
|
+
.withTestAdapter({ type: "kysely-sqlite" })
|
|
49
|
+
.withFragment("test", instantiate(testFragmentDef).withConfig({}).withRoutes([]))
|
|
50
|
+
.build();
|
|
49
51
|
|
|
50
|
-
const db =
|
|
52
|
+
const db = fragments.test.db;
|
|
51
53
|
```
|
|
52
54
|
|
|
53
55
|
## Create
|
|
@@ -4,8 +4,13 @@ Routes are the core of a Fragno fragment, defining HTTP endpoints that handle re
|
|
|
4
4
|
responses. This guide covers the essential patterns for defining routes.
|
|
5
5
|
|
|
6
6
|
```typescript @fragno-imports
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import {
|
|
8
|
+
defineFragment,
|
|
9
|
+
defineRoute,
|
|
10
|
+
defineRoutes,
|
|
11
|
+
instantiate,
|
|
12
|
+
type FragnoPublicConfig,
|
|
13
|
+
} from "@fragno-dev/core";
|
|
9
14
|
import { z } from "zod";
|
|
10
15
|
```
|
|
11
16
|
|
|
@@ -151,15 +156,14 @@ Routes can access dependencies defined in `withDependencies` through route facto
|
|
|
151
156
|
|
|
152
157
|
```typescript @fragno-test:using-dependencies
|
|
153
158
|
interface AppConfig {
|
|
154
|
-
apiKey
|
|
159
|
+
apiKey?: string;
|
|
155
160
|
}
|
|
156
161
|
|
|
157
|
-
|
|
158
|
-
config:
|
|
159
|
-
|
|
160
|
-
}
|
|
162
|
+
const definition = defineFragment<AppConfig>("test")
|
|
163
|
+
.withDependencies(({ config }) => ({ timestamp: Date.now() }))
|
|
164
|
+
.build();
|
|
161
165
|
|
|
162
|
-
export const routesWithDeps = defineRoutes<
|
|
166
|
+
export const routesWithDeps = defineRoutes<typeof definition>().create(({ config, deps }) => {
|
|
163
167
|
return [
|
|
164
168
|
defineRoute({
|
|
165
169
|
method: "GET",
|
|
@@ -170,7 +174,7 @@ export const routesWithDeps = defineRoutes<AppConfig, AppDeps>().create(({ deps
|
|
|
170
174
|
}),
|
|
171
175
|
handler: async (_, { json }) => {
|
|
172
176
|
return json({
|
|
173
|
-
hasApiKey: !!
|
|
177
|
+
hasApiKey: !!config.apiKey,
|
|
174
178
|
timestamp: deps.timestamp,
|
|
175
179
|
});
|
|
176
180
|
},
|
|
@@ -186,12 +190,17 @@ Dependencies are passed to the route factory function and can be used in route h
|
|
|
186
190
|
Services defined in `providesService` can be used in routes for business logic.
|
|
187
191
|
|
|
188
192
|
```typescript @fragno-test:using-services
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
+
const definition = defineFragment("test")
|
|
194
|
+
.withDependencies(({ config }) => ({ timestamp: Date.now() }))
|
|
195
|
+
.providesBaseService(({ defineService }) =>
|
|
196
|
+
defineService({
|
|
197
|
+
getData: () => "Hello, World!",
|
|
198
|
+
processData: async (input: string) => input,
|
|
199
|
+
}),
|
|
200
|
+
)
|
|
201
|
+
.build();
|
|
193
202
|
|
|
194
|
-
export const routesWithServices = defineRoutes<
|
|
203
|
+
export const routesWithServices = defineRoutes<typeof definition>().create(({ services }) => {
|
|
195
204
|
return [
|
|
196
205
|
defineRoute({
|
|
197
206
|
method: "GET",
|
|
@@ -218,55 +227,3 @@ export const routesWithServices = defineRoutes<{}, {}, DataService>().create(({
|
|
|
218
227
|
```
|
|
219
228
|
|
|
220
229
|
Services provide reusable business logic that can be shared across multiple routes.
|
|
221
|
-
|
|
222
|
-
## Complete Fragment Example
|
|
223
|
-
|
|
224
|
-
A complete example showing how routes integrate with fragment definition.
|
|
225
|
-
|
|
226
|
-
```typescript @fragno-test:complete-fragment
|
|
227
|
-
interface MyFragmentConfig {
|
|
228
|
-
apiKey: string;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
interface MyFragmentDeps {
|
|
232
|
-
config: MyFragmentConfig;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
interface MyFragmentServices {
|
|
236
|
-
getStatus: () => string;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const myFragmentDefinition = defineFragment<MyFragmentConfig>("my-fragment")
|
|
240
|
-
.withDependencies(({ config }) => {
|
|
241
|
-
return {
|
|
242
|
-
config,
|
|
243
|
-
};
|
|
244
|
-
})
|
|
245
|
-
.providesService(({ deps, defineService }) => {
|
|
246
|
-
return defineService({
|
|
247
|
-
getStatus: () => `API Key: ${deps.config.apiKey.substring(0, 3)}...`,
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
const myRoutes = defineRoutes<MyFragmentConfig, MyFragmentDeps, MyFragmentServices>().create(
|
|
252
|
-
({ services }) => {
|
|
253
|
-
return [
|
|
254
|
-
defineRoute({
|
|
255
|
-
method: "GET",
|
|
256
|
-
path: "/status",
|
|
257
|
-
outputSchema: z.string(),
|
|
258
|
-
handler: async (_, { json }) => {
|
|
259
|
-
return json(services.getStatus());
|
|
260
|
-
},
|
|
261
|
-
}),
|
|
262
|
-
];
|
|
263
|
-
},
|
|
264
|
-
);
|
|
265
|
-
|
|
266
|
-
export function createMyFragment(config: MyFragmentConfig, options: FragnoPublicConfig = {}) {
|
|
267
|
-
return createFragment(myFragmentDefinition, config, [myRoutes], options);
|
|
268
|
-
}
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
This example shows the complete flow: fragment definition with dependencies and services, route
|
|
272
|
-
factory using those services, and the fragment creation function.
|
|
@@ -7,54 +7,3 @@ import { DrizzleAdapter } from "@fragno-dev/db/adapters/drizzle";
|
|
|
7
7
|
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
8
8
|
import type { PgliteDatabase } from "drizzle-orm/pglite";
|
|
9
9
|
```
|
|
10
|
-
|
|
11
|
-
## Basic Setup
|
|
12
|
-
|
|
13
|
-
Create a DrizzleAdapter with your Drizzle database instance and provider.
|
|
14
|
-
|
|
15
|
-
```typescript @fragno-test:basic-setup types-only
|
|
16
|
-
interface MyDatabase extends Record<string, unknown> {
|
|
17
|
-
users: {
|
|
18
|
-
id: string;
|
|
19
|
-
email: string;
|
|
20
|
-
name: string;
|
|
21
|
-
};
|
|
22
|
-
posts: {
|
|
23
|
-
id: string;
|
|
24
|
-
title: string;
|
|
25
|
-
content: string;
|
|
26
|
-
authorId: string;
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
declare const db: NodePgDatabase<MyDatabase>;
|
|
31
|
-
|
|
32
|
-
export const adapter = new DrizzleAdapter({
|
|
33
|
-
db,
|
|
34
|
-
provider: "postgresql",
|
|
35
|
-
});
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
The adapter requires your Drizzle instance and the database provider (`"postgresql"`, `"mysql"`, or
|
|
39
|
-
`"sqlite"`).
|
|
40
|
-
|
|
41
|
-
## Factory Function
|
|
42
|
-
|
|
43
|
-
For async or sync database initialization, pass a factory function instead of a direct instance.
|
|
44
|
-
|
|
45
|
-
```typescript @fragno-test:factory-function types-only
|
|
46
|
-
import type { PgliteDatabase } from "drizzle-orm/pglite";
|
|
47
|
-
|
|
48
|
-
async function createDatabase(): Promise<PgliteDatabase> {
|
|
49
|
-
// Async initialization logic
|
|
50
|
-
const db = {} as PgliteDatabase;
|
|
51
|
-
return db;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export const adapter = new DrizzleAdapter({
|
|
55
|
-
db: createDatabase,
|
|
56
|
-
provider: "postgresql",
|
|
57
|
-
});
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
Factory functions can also be synchronous for lazy initialization scenarios.
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
# Fragment Instantiation
|
|
2
2
|
|
|
3
|
-
Fragno provides the `
|
|
4
|
-
|
|
3
|
+
Fragno provides the `instantiate` function, which uses a builder pattern to help you and your user
|
|
4
|
+
create Fragments.
|
|
5
5
|
|
|
6
6
|
```typescript @fragno-imports
|
|
7
7
|
import {
|
|
8
8
|
defineFragment,
|
|
9
|
-
createFragment,
|
|
10
|
-
instantiateFragment,
|
|
11
9
|
defineRoute,
|
|
10
|
+
instantiate,
|
|
12
11
|
type FragnoPublicConfig,
|
|
13
12
|
} from "@fragno-dev/core";
|
|
14
13
|
import { z } from "zod";
|
|
@@ -22,12 +21,12 @@ interface AppConfig {
|
|
|
22
21
|
apiKey: string;
|
|
23
22
|
}
|
|
24
23
|
|
|
25
|
-
const
|
|
24
|
+
const fragmentDefinition = defineFragment<AppConfig>("api-fragment").build();
|
|
26
25
|
|
|
27
26
|
// User-facing fragment creator function. This is the main integration point for users of your fragment.
|
|
28
27
|
export function createMyFragment(config: AppConfig, options: FragnoPublicConfig = {}) {
|
|
29
28
|
return (
|
|
30
|
-
|
|
29
|
+
instantiate(fragmentDefinition)
|
|
31
30
|
.withConfig(config)
|
|
32
31
|
/** Options are passed to Fragno internally */
|
|
33
32
|
.withOptions(options)
|
|
@@ -38,7 +37,7 @@ export function createMyFragment(config: AppConfig, options: FragnoPublicConfig
|
|
|
38
37
|
// What your user will call to instantiate the fragment:
|
|
39
38
|
const instance = createMyFragment({ apiKey: "my-secret-key" }, { mountRoute: "/api/v1" });
|
|
40
39
|
|
|
41
|
-
expect(instance.
|
|
40
|
+
expect(instance.name).toBe("api-fragment");
|
|
42
41
|
expect(instance.mountRoute).toBe("/api/v1");
|
|
43
42
|
```
|
|
44
43
|
|
|
@@ -51,7 +50,7 @@ Also see the `defining-routes` subject.
|
|
|
51
50
|
|
|
52
51
|
```typescript @fragno-test:builder-with-routes
|
|
53
52
|
// should add routes using withRoutes
|
|
54
|
-
const
|
|
53
|
+
const fragmentDefinition = defineFragment("routes-fragment").build();
|
|
55
54
|
|
|
56
55
|
const route1 = defineRoute({
|
|
57
56
|
method: "GET",
|
|
@@ -67,11 +66,14 @@ const route2 = defineRoute({
|
|
|
67
66
|
handler: async (_, { json }) => json("Goodbye"),
|
|
68
67
|
});
|
|
69
68
|
|
|
70
|
-
const instance =
|
|
69
|
+
const instance = instantiate(fragmentDefinition)
|
|
70
|
+
.withRoutes([route1, route2])
|
|
71
|
+
.withOptions({})
|
|
72
|
+
.build();
|
|
71
73
|
|
|
72
|
-
expect(instance.
|
|
73
|
-
expect(instance.
|
|
74
|
-
expect(instance.
|
|
74
|
+
expect(instance.routes).toHaveLength(2);
|
|
75
|
+
expect(instance.routes[0].path).toBe("/hello");
|
|
76
|
+
expect(instance.routes[1].path).toBe("/goodbye");
|
|
75
77
|
```
|
|
76
78
|
|
|
77
79
|
Routes can be added as an array using `withRoutes`.
|
|
@@ -94,19 +96,20 @@ const fragment = defineFragment<AppConfig>("service-fragment")
|
|
|
94
96
|
.withDependencies(({ config }) => ({
|
|
95
97
|
client: { key: config.apiKey },
|
|
96
98
|
}))
|
|
97
|
-
.usesService<"logger", ILogger>("logger")
|
|
99
|
+
.usesService<"logger", ILogger>("logger")
|
|
100
|
+
.build();
|
|
98
101
|
|
|
99
102
|
const loggerImpl: ILogger = {
|
|
100
103
|
log: (msg) => console.log(msg),
|
|
101
104
|
};
|
|
102
105
|
|
|
103
|
-
const instance =
|
|
106
|
+
const instance = instantiate(fragment)
|
|
104
107
|
.withConfig({ apiKey: "my-key" })
|
|
105
108
|
.withServices({ logger: loggerImpl })
|
|
109
|
+
.withOptions({})
|
|
106
110
|
.build();
|
|
107
111
|
|
|
108
|
-
expect(instance.deps.client.key).toBe("my-key");
|
|
109
|
-
expect(instance.services.logger).toBeDefined();
|
|
112
|
+
expect(instance.$internal.deps.client.key).toBe("my-key");
|
|
110
113
|
```
|
|
111
114
|
|
|
112
115
|
Use `withDependencies` to create dependencies from config, and `withServices` to provide required
|
|
@@ -7,8 +7,7 @@ Note that if the goal is to make your user provide certain functionality, it usu
|
|
|
7
7
|
sense to add a required property to the fragment's config.
|
|
8
8
|
|
|
9
9
|
```typescript @fragno-imports
|
|
10
|
-
import { defineFragment,
|
|
11
|
-
import type { FragnoPublicConfig } from "@fragno-dev/core";
|
|
10
|
+
import { defineFragment, instantiate, type FragnoPublicConfig } from "@fragno-dev/core";
|
|
12
11
|
```
|
|
13
12
|
|
|
14
13
|
## Providing Services
|
|
@@ -17,23 +16,27 @@ Fragments provide services using `providesService()`. Services can be passed as
|
|
|
17
16
|
via a factory function that receives context (`config`, `deps`, `fragnoConfig`, `defineService`).
|
|
18
17
|
|
|
19
18
|
```typescript @fragno-test:provide-direct-object
|
|
20
|
-
// Object syntax is the simplest way to provide services.
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
19
|
+
// Object syntax is the simplest way to provide base services.
|
|
20
|
+
const fragmentDefinition = defineFragment("email-fragment")
|
|
21
|
+
.providesBaseService(() => ({
|
|
22
|
+
sendEmail: async (to: string, subject: string) => {
|
|
23
|
+
// Email sending logic here
|
|
24
|
+
},
|
|
25
|
+
}))
|
|
26
|
+
.build();
|
|
26
27
|
```
|
|
27
28
|
|
|
28
29
|
```typescript @fragno-test:provide-factory-function
|
|
29
30
|
// The factory function receives the fragment's `config`, and `deps` (dependencies).
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
31
|
+
const fragmentDefinition = defineFragment("api-fragment")
|
|
32
|
+
.providesBaseService(({ config }) => ({
|
|
33
|
+
makeRequest: async (endpoint: string) => {
|
|
34
|
+
return { data: "response" };
|
|
35
|
+
},
|
|
36
|
+
}))
|
|
37
|
+
.build();
|
|
35
38
|
|
|
36
|
-
const instance =
|
|
39
|
+
const instance = instantiate(fragmentDefinition).withOptions({}).build();
|
|
37
40
|
expect(instance.services.makeRequest).toBeInstanceOf(Function);
|
|
38
41
|
```
|
|
39
42
|
|
|
@@ -45,16 +48,14 @@ required services.
|
|
|
45
48
|
|
|
46
49
|
```typescript @fragno-test:provide-named-services
|
|
47
50
|
// should provide named services
|
|
48
|
-
const
|
|
49
|
-
"logger",
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}),
|
|
55
|
-
);
|
|
51
|
+
const fragmentDefinition = defineFragment("logger-fragment")
|
|
52
|
+
.providesService("logger", () => ({
|
|
53
|
+
log: (message: string) => console.log(message),
|
|
54
|
+
error: (message: string) => console.error(message),
|
|
55
|
+
}))
|
|
56
|
+
.build();
|
|
56
57
|
|
|
57
|
-
const instance =
|
|
58
|
+
const instance = instantiate(fragmentDefinition).withOptions({}).build();
|
|
58
59
|
expect(instance.services.logger.log).toBeDefined();
|
|
59
60
|
expect(instance.services.logger.error).toBeDefined();
|
|
60
61
|
```
|
|
@@ -66,7 +67,7 @@ for database fragments where methods need access to the current unit of work.
|
|
|
66
67
|
|
|
67
68
|
```typescript @fragno-test:chaining-services
|
|
68
69
|
// should chain multiple service definitions
|
|
69
|
-
const
|
|
70
|
+
const fragmentDefinition = defineFragment("multi-service-fragment")
|
|
70
71
|
.providesService("logger", ({ defineService }) =>
|
|
71
72
|
defineService({
|
|
72
73
|
log: (msg: string) => console.log(msg),
|
|
@@ -76,13 +77,14 @@ const fragment = defineFragment<{}>("multi-service-fragment")
|
|
|
76
77
|
defineService({
|
|
77
78
|
validate: (input: string) => input.length > 0,
|
|
78
79
|
}),
|
|
79
|
-
)
|
|
80
|
+
)
|
|
81
|
+
.build();
|
|
80
82
|
```
|
|
81
83
|
|
|
82
84
|
## Using Services
|
|
83
85
|
|
|
84
|
-
Fragments specify required services using `usesService`. Services can be marked as optional
|
|
85
|
-
`
|
|
86
|
+
Fragments specify required services using `usesService`. Services can be marked as optional using
|
|
87
|
+
`usesOptionalService`, making them `undefined` if not provided.
|
|
86
88
|
|
|
87
89
|
```typescript @fragno-test:declaring-required-service
|
|
88
90
|
// should require a service from the user
|
|
@@ -90,9 +92,13 @@ interface IEmailService {
|
|
|
90
92
|
sendEmail(to: string, subject: string, body: string): Promise<void>;
|
|
91
93
|
}
|
|
92
94
|
|
|
93
|
-
const
|
|
94
|
-
"email",
|
|
95
|
-
)
|
|
95
|
+
const fragmentDefinition = defineFragment("notification-fragment")
|
|
96
|
+
.usesService<"email", IEmailService>("email")
|
|
97
|
+
.providesBaseService(({ serviceDeps }) => ({
|
|
98
|
+
sendNotification: (to: string, subject: string, body: string) =>
|
|
99
|
+
serviceDeps.email.sendEmail(to, subject, body),
|
|
100
|
+
}))
|
|
101
|
+
.build();
|
|
96
102
|
|
|
97
103
|
const emailImpl: IEmailService = {
|
|
98
104
|
sendEmail: async (to, subject, body) => {
|
|
@@ -100,13 +106,12 @@ const emailImpl: IEmailService = {
|
|
|
100
106
|
},
|
|
101
107
|
};
|
|
102
108
|
|
|
103
|
-
const instance =
|
|
104
|
-
.withConfig({})
|
|
109
|
+
const instance = instantiate(fragmentDefinition)
|
|
105
110
|
.withServices({ email: emailImpl })
|
|
111
|
+
.withOptions({})
|
|
106
112
|
.build();
|
|
107
113
|
|
|
108
|
-
expect(instance.services.
|
|
109
|
-
expect(instance.services.email.sendEmail).toBeDefined();
|
|
114
|
+
expect(instance.services.sendNotification).toBeDefined();
|
|
110
115
|
```
|
|
111
116
|
|
|
112
117
|
### Optional Services
|
|
@@ -117,21 +122,27 @@ interface ILogger {
|
|
|
117
122
|
log(message: string): void;
|
|
118
123
|
}
|
|
119
124
|
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
})
|
|
125
|
+
const fragmentDefinition = defineFragment("app-fragment")
|
|
126
|
+
.usesOptionalService<"logger", ILogger>("logger")
|
|
127
|
+
.providesBaseService(({ serviceDeps }) => ({
|
|
128
|
+
maybeLog: (message: string) => {
|
|
129
|
+
if (serviceDeps.logger) {
|
|
130
|
+
serviceDeps.logger.log(message);
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
}))
|
|
134
|
+
.build();
|
|
123
135
|
|
|
124
136
|
// Can instantiate without providing the service
|
|
125
|
-
const instance =
|
|
137
|
+
const instance = instantiate(fragmentDefinition).withOptions({}).build();
|
|
126
138
|
|
|
127
139
|
expect(instance).toBeDefined();
|
|
128
|
-
|
|
129
|
-
expect(instance.services.logger).toBeUndefined();
|
|
140
|
+
expect(instance.services.maybeLog).toBeDefined();
|
|
130
141
|
```
|
|
131
142
|
|
|
132
143
|
### Using Services in Provided Services
|
|
133
144
|
|
|
134
|
-
Used services are available via `
|
|
145
|
+
Used services are available via `serviceDeps` in the factory function context, enabling composition.
|
|
135
146
|
|
|
136
147
|
```typescript @fragno-test:using-in-provided
|
|
137
148
|
// should use external services in provided services
|
|
@@ -139,25 +150,25 @@ interface IEmailService {
|
|
|
139
150
|
sendEmail(to: string, subject: string, body: string): Promise<void>;
|
|
140
151
|
}
|
|
141
152
|
|
|
142
|
-
const
|
|
153
|
+
const fragmentDefinition = defineFragment("welcome-fragment")
|
|
143
154
|
.usesService<"email", IEmailService>("email")
|
|
144
|
-
.
|
|
155
|
+
.providesBaseService(({ serviceDeps }) => ({
|
|
145
156
|
sendWelcomeEmail: async (to: string) => {
|
|
146
|
-
await
|
|
157
|
+
await serviceDeps.email.sendEmail(to, "Welcome!", "Welcome to our service!");
|
|
147
158
|
},
|
|
148
|
-
}))
|
|
159
|
+
}))
|
|
160
|
+
.build();
|
|
149
161
|
|
|
150
162
|
const emailImpl: IEmailService = {
|
|
151
163
|
sendEmail: async () => {},
|
|
152
164
|
};
|
|
153
165
|
|
|
154
|
-
const instance =
|
|
155
|
-
.withConfig({})
|
|
166
|
+
const instance = instantiate(fragmentDefinition)
|
|
156
167
|
.withServices({ email: emailImpl })
|
|
168
|
+
.withOptions({})
|
|
157
169
|
.build();
|
|
158
170
|
|
|
159
171
|
expect(instance.services.sendWelcomeEmail).toBeDefined();
|
|
160
|
-
expect(instance.services.email).toBeDefined();
|
|
161
172
|
```
|
|
162
173
|
|
|
163
174
|
### Missing Required Services
|
|
@@ -168,11 +179,11 @@ interface IStorageService {
|
|
|
168
179
|
save(key: string, value: string): Promise<void>;
|
|
169
180
|
}
|
|
170
181
|
|
|
171
|
-
const
|
|
172
|
-
"storage",
|
|
173
|
-
);
|
|
182
|
+
const fragmentDefinition = defineFragment("storage-fragment")
|
|
183
|
+
.usesService<"storage", IStorageService>("storage")
|
|
184
|
+
.build();
|
|
174
185
|
|
|
175
186
|
expect(() => {
|
|
176
|
-
|
|
187
|
+
instantiate(fragmentDefinition).withOptions({}).build();
|
|
177
188
|
}).toThrow("Fragment 'storage-fragment' requires service 'storage' but it was not provided");
|
|
178
189
|
```
|
|
@@ -4,56 +4,54 @@ The KyselyAdapter connects Fragno's database API to your Kysely database instanc
|
|
|
4
4
|
|
|
5
5
|
```typescript @fragno-imports
|
|
6
6
|
import { KyselyAdapter } from "@fragno-dev/db/adapters/kysely";
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import {
|
|
8
|
+
PGLiteDriverConfig,
|
|
9
|
+
SQLocalDriverConfig,
|
|
10
|
+
BetterSQLite3DriverConfig,
|
|
11
|
+
} from "@fragno-dev/db/drivers";
|
|
12
|
+
import { SqliteDialect, PostgresDialect, MysqlDialect } from "@fragno-dev/db/dialects";
|
|
13
|
+
import type { Dialect } from "@fragno-dev/db/sql-driver";
|
|
14
|
+
import SQLite from "better-sqlite3";
|
|
15
|
+
import { KyselyPGlite } from "kysely-pglite";
|
|
9
16
|
```
|
|
10
17
|
|
|
11
18
|
## Basic Setup
|
|
12
19
|
|
|
13
|
-
Create a KyselyAdapter with your Kysely
|
|
20
|
+
Create a KyselyAdapter with your Kysely dialect and driver configuration.
|
|
14
21
|
|
|
15
22
|
```typescript @fragno-test:basic-setup types-only
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
id: string;
|
|
19
|
-
email: string;
|
|
20
|
-
name: string;
|
|
21
|
-
};
|
|
22
|
-
posts: {
|
|
23
|
-
id: string;
|
|
24
|
-
title: string;
|
|
25
|
-
content: string;
|
|
26
|
-
authorId: string;
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
declare const dialect: Dialect;
|
|
31
|
-
|
|
32
|
-
export const db = new Kysely<MyDatabase>({
|
|
33
|
-
dialect,
|
|
23
|
+
const dialect = new SqliteDialect({
|
|
24
|
+
database: new SQLite(":memory:"),
|
|
34
25
|
});
|
|
35
26
|
|
|
36
27
|
export const adapter = new KyselyAdapter({
|
|
37
|
-
|
|
38
|
-
|
|
28
|
+
dialect,
|
|
29
|
+
driverConfig: new BetterSQLite3DriverConfig(),
|
|
39
30
|
});
|
|
40
31
|
```
|
|
41
32
|
|
|
42
|
-
The adapter requires
|
|
43
|
-
`"sqlite"`).
|
|
33
|
+
The adapter requires:
|
|
44
34
|
|
|
45
|
-
|
|
35
|
+
- `dialect`: A Kysely dialect instance for your database
|
|
36
|
+
- `driverConfig`: A driver configuration matching your database type (`PGLiteDriverConfig`,
|
|
37
|
+
`SQLocalDriverConfig`, or `BetterSQLite3DriverConfig`)
|
|
46
38
|
|
|
47
|
-
|
|
39
|
+
## First Party Kysely Dialects
|
|
48
40
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
41
|
+
Fragno re-exports the "first party" Kysely dialects for SQLite, PostgreSQL, and MySQL. This means
|
|
42
|
+
you can use these dialects without installing them yourself.
|
|
43
|
+
|
|
44
|
+
```typescript @fragno-test:first-party-kysely-dialects types-only
|
|
45
|
+
import { SqliteDialect, PostgresDialect, MysqlDialect } from "@fragno-dev/db/dialects";
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## PGLite Example
|
|
49
|
+
|
|
50
|
+
```typescript @fragno-test:postgresql-example types-only
|
|
51
|
+
const { dialect } = await KyselyPGlite.create();
|
|
54
52
|
|
|
55
53
|
export const adapter = new KyselyAdapter({
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
dialect,
|
|
55
|
+
driverConfig: new PGLiteDriverConfig(),
|
|
58
56
|
});
|
|
59
57
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fragno-dev/corpus",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -16,14 +16,17 @@
|
|
|
16
16
|
"dist"
|
|
17
17
|
],
|
|
18
18
|
"devDependencies": {
|
|
19
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
19
20
|
"@types/marked-terminal": "^6.1.1",
|
|
20
21
|
"@types/node": "^22",
|
|
22
|
+
"better-sqlite3": "^12.5.0",
|
|
21
23
|
"drizzle-orm": "^0.44.7",
|
|
22
24
|
"kysely": "^0.28.0",
|
|
25
|
+
"kysely-pglite": "^0.6.1",
|
|
23
26
|
"zod": "^4.0.5",
|
|
24
|
-
"@fragno-dev/core": "0.1.
|
|
25
|
-
"@fragno-dev/db": "0.
|
|
26
|
-
"@fragno-dev/test": "0.
|
|
27
|
+
"@fragno-dev/core": "0.1.11",
|
|
28
|
+
"@fragno-dev/db": "0.2.0",
|
|
29
|
+
"@fragno-dev/test": "1.0.0",
|
|
27
30
|
"@fragno-private/typescript-config": "0.0.1",
|
|
28
31
|
"@fragno-private/vitest-config": "0.0.0"
|
|
29
32
|
},
|