@fragno-dev/create 0.0.4 → 0.1.1
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 +5 -5
- package/CHANGELOG.md +12 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -3
- package/dist/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/index.ts +13 -2
- package/src/integration.test.ts +17 -8
- package/src/package-json.ts +28 -1
- package/src/utils.ts +6 -2
- package/templates/fragment/package.template.json +1 -15
- package/templates/fragment/src/index.ts +2 -2
- package/templates/optional/agent/AGENTS.md +120 -1
- package/templates/optional/database/index.ts +141 -0
- package/templates/optional/database/schema.ts +15 -0
- package/.turbo/turbo-test.log +0 -66
- package/.turbo/turbo-types$colon$check.log +0 -1
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { copy, merge } from "./utils.ts";
|
|
5
|
-
import { buildToolPkg } from "./package-json.ts";
|
|
5
|
+
import { basePkg, buildToolPkg, databasePkg } from "./package-json.ts";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
|
|
8
8
|
const templateTypesSchema = z.literal("fragment");
|
|
@@ -28,16 +28,22 @@ export const createOptionsSchema = z.object({
|
|
|
28
28
|
name: z.string(),
|
|
29
29
|
template: templateTypesSchema,
|
|
30
30
|
agentDocs: agentDocsSchema,
|
|
31
|
+
withDatabase: z.boolean(),
|
|
31
32
|
});
|
|
32
33
|
|
|
33
34
|
type CreateOptions = z.infer<typeof createOptionsSchema>;
|
|
34
35
|
|
|
35
36
|
export function create(options: CreateOptions) {
|
|
36
|
-
let pkgOverride: Record<string, unknown> = { name: options.name };
|
|
37
|
+
let pkgOverride: Record<string, unknown> = merge(basePkg, { name: options.name });
|
|
37
38
|
|
|
38
39
|
// Build tool pkg overrides
|
|
39
40
|
pkgOverride = merge(pkgOverride, buildToolPkg[options.buildTool]);
|
|
40
41
|
|
|
42
|
+
// Database pkg overrides
|
|
43
|
+
if (options.withDatabase) {
|
|
44
|
+
pkgOverride = merge(pkgOverride, databasePkg);
|
|
45
|
+
}
|
|
46
|
+
|
|
41
47
|
if (options.template == "fragment") {
|
|
42
48
|
writeFragmentTemplate(options.path, pkgOverride);
|
|
43
49
|
} else {
|
|
@@ -77,6 +83,11 @@ export function create(options: CreateOptions) {
|
|
|
77
83
|
case "none":
|
|
78
84
|
break;
|
|
79
85
|
}
|
|
86
|
+
|
|
87
|
+
if (options.withDatabase) {
|
|
88
|
+
writeOptionalTemplate(options.path, "database/index.ts", "src/index.ts");
|
|
89
|
+
writeOptionalTemplate(options.path, "database/schema.ts", "src/schema.ts");
|
|
90
|
+
}
|
|
80
91
|
}
|
|
81
92
|
|
|
82
93
|
function getTemplateDir(): string {
|
package/src/integration.test.ts
CHANGED
|
@@ -19,9 +19,10 @@ async function createTempDir(name: string): Promise<string> {
|
|
|
19
19
|
return dir;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
type BuildTool = "tsdown" | "esbuild" | "vite" | "rollup" | "webpack" | "rspack";
|
|
23
|
+
|
|
24
|
+
function createFragmentTestSuite(buildTool: BuildTool, withDatabase: boolean) {
|
|
25
|
+
return () => {
|
|
25
26
|
let tempDir: string;
|
|
26
27
|
const testConfig = {
|
|
27
28
|
name: "@myorg/test",
|
|
@@ -31,16 +32,17 @@ describe.concurrent.each(["tsdown", "esbuild", "vite", "rollup", "webpack", "rsp
|
|
|
31
32
|
};
|
|
32
33
|
|
|
33
34
|
beforeAll(async () => {
|
|
34
|
-
|
|
35
|
+
const suffix = withDatabase ? "db" : "no-db";
|
|
36
|
+
tempDir = await createTempDir(`fragment-test-${buildTool}-${suffix}`);
|
|
35
37
|
console.log("temp", tempDir);
|
|
36
|
-
create({ ...testConfig, path: tempDir });
|
|
38
|
+
create({ ...testConfig, path: tempDir, withDatabase });
|
|
37
39
|
});
|
|
38
40
|
|
|
39
41
|
afterAll(async () => {
|
|
40
42
|
await fs.rm(tempDir, { recursive: true });
|
|
41
43
|
});
|
|
42
44
|
|
|
43
|
-
describe.sequential(
|
|
45
|
+
describe.sequential(buildTool, () => {
|
|
44
46
|
test("package.json correctly templated", async () => {
|
|
45
47
|
const pkg = path.join(tempDir, "package.json");
|
|
46
48
|
const pkgContent = await fs.readFile(pkg, "utf8");
|
|
@@ -74,7 +76,7 @@ describe.concurrent.each(["tsdown", "esbuild", "vite", "rollup", "webpack", "rsp
|
|
|
74
76
|
but somehow when running through vitest the module resolution mechanism changes causing
|
|
75
77
|
the build to fail.
|
|
76
78
|
*/
|
|
77
|
-
test.skipIf(buildTool
|
|
79
|
+
test.skipIf(buildTool === "rollup")("builds", { timeout: 50000 }, async () => {
|
|
78
80
|
const result = await execAsync("bun run build", {
|
|
79
81
|
cwd: tempDir,
|
|
80
82
|
encoding: "utf8",
|
|
@@ -99,5 +101,12 @@ describe.concurrent.each(["tsdown", "esbuild", "vite", "rollup", "webpack", "rsp
|
|
|
99
101
|
}
|
|
100
102
|
});
|
|
101
103
|
});
|
|
102
|
-
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
describe.concurrent.each(["tsdown", "esbuild", "vite", "rollup", "webpack", "rspack"] as const)(
|
|
108
|
+
"fragment with %s (with database)",
|
|
109
|
+
(buildTool) => createFragmentTestSuite(buildTool, true)(),
|
|
103
110
|
);
|
|
111
|
+
|
|
112
|
+
describe("fragment with tsdown (without database)", createFragmentTestSuite("tsdown", false));
|
package/src/package-json.ts
CHANGED
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
import type { BuildTools } from "./index";
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const fragnoCoreVersion = "^0.1.2";
|
|
4
|
+
const fragnoDbVersion = "^0.1.2";
|
|
5
|
+
const unpluginFragnoVersion = "^0.0.2";
|
|
6
|
+
const fragnoCliVersion = "^0.1.3";
|
|
7
|
+
|
|
8
|
+
export const basePkg: Record<string, unknown> = {
|
|
9
|
+
dependencies: {
|
|
10
|
+
"@fragno-dev/core": fragnoCoreVersion,
|
|
11
|
+
zod: "^4.0.5",
|
|
12
|
+
},
|
|
13
|
+
devDependencies: {
|
|
14
|
+
"@fragno-dev/cli": fragnoCliVersion,
|
|
15
|
+
"@types/node": "^22",
|
|
16
|
+
},
|
|
17
|
+
peerDependencies: {
|
|
18
|
+
typescript: "^5",
|
|
19
|
+
react: ">=18.0.0",
|
|
20
|
+
svelte: ">=4.0.0",
|
|
21
|
+
"solid-js": ">=1.0.0",
|
|
22
|
+
vue: ">=3.0.0",
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const databasePkg: Record<string, unknown> = {
|
|
27
|
+
dependencies: {
|
|
28
|
+
"@fragno-dev/db": fragnoDbVersion,
|
|
29
|
+
},
|
|
30
|
+
};
|
|
4
31
|
|
|
5
32
|
export const buildToolPkg: Record<BuildTools, Record<string, unknown>> = {
|
|
6
33
|
none: {},
|
package/src/utils.ts
CHANGED
|
@@ -5,7 +5,9 @@ export function mkdirp(dir: string): void {
|
|
|
5
5
|
try {
|
|
6
6
|
fs.mkdirSync(dir, { recursive: true });
|
|
7
7
|
} catch (e: unknown) {
|
|
8
|
-
if (e instanceof Error && "code" in e && e.code === "EEXIST")
|
|
8
|
+
if (e instanceof Error && "code" in e && e.code === "EEXIST") {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
9
11
|
throw e;
|
|
10
12
|
}
|
|
11
13
|
}
|
|
@@ -54,7 +56,9 @@ export function copy(
|
|
|
54
56
|
to: string,
|
|
55
57
|
rename: (basename: string) => string = identity,
|
|
56
58
|
): void {
|
|
57
|
-
if (!fs.existsSync(from))
|
|
59
|
+
if (!fs.existsSync(from)) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
58
62
|
|
|
59
63
|
const stats = fs.statSync(from);
|
|
60
64
|
|
|
@@ -56,19 +56,5 @@
|
|
|
56
56
|
"types:check": "tsc --noEmit"
|
|
57
57
|
},
|
|
58
58
|
"type": "module",
|
|
59
|
-
"
|
|
60
|
-
"@fragno-dev/core": "^0.0.7",
|
|
61
|
-
"zod": "^4.0.5"
|
|
62
|
-
},
|
|
63
|
-
"devDependencies": {
|
|
64
|
-
"@types/node": "^20"
|
|
65
|
-
},
|
|
66
|
-
"private": true,
|
|
67
|
-
"peerDependencies": {
|
|
68
|
-
"typescript": "^5",
|
|
69
|
-
"react": ">=18.0.0",
|
|
70
|
-
"svelte": ">=4.0.0",
|
|
71
|
-
"solid-js": ">=1.0.0",
|
|
72
|
-
"vue": ">=3.0.0"
|
|
73
|
-
}
|
|
59
|
+
"private": true
|
|
74
60
|
}
|
|
@@ -56,12 +56,12 @@ const exampleRoutesFactory = defineRoutes<ExampleConfig, ExampleDeps, ExampleSer
|
|
|
56
56
|
);
|
|
57
57
|
|
|
58
58
|
const exampleFragmentDefinition = defineFragment<ExampleConfig>("example-fragment")
|
|
59
|
-
.withDependencies((config
|
|
59
|
+
.withDependencies(({ config }) => {
|
|
60
60
|
return {
|
|
61
61
|
serverSideData: { value: config.initialData ?? "Hello World! This is a server-side data." },
|
|
62
62
|
};
|
|
63
63
|
})
|
|
64
|
-
.withServices((
|
|
64
|
+
.withServices(({ deps }) => {
|
|
65
65
|
return {
|
|
66
66
|
getData: () => deps.serverSideData.value,
|
|
67
67
|
};
|
|
@@ -14,7 +14,9 @@ provide:
|
|
|
14
14
|
- Built-in state management with reactive stores (TanStack Query-style:
|
|
15
15
|
`const {data, loading, error} = useData()`)
|
|
16
16
|
|
|
17
|
-
**Documentation**:
|
|
17
|
+
**Documentation**: https://fragno.dev/docs
|
|
18
|
+
|
|
19
|
+
All docs are also available with a `.md` extension: https://fragno.dev/docs.md
|
|
18
20
|
|
|
19
21
|
## Architecture
|
|
20
22
|
|
|
@@ -24,6 +26,22 @@ Fragments follow a core pattern:
|
|
|
24
26
|
2. **Client-side**: Auto-generated type-safe hooks for each route
|
|
25
27
|
3. **Code splitting**: Server-only code (handlers, dependencies) is stripped from client bundles
|
|
26
28
|
|
|
29
|
+
### Fragment Configuration
|
|
30
|
+
|
|
31
|
+
Database-enabled Fragments require `FragnoPublicConfigWithDatabase`:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
export function createMyFragment(
|
|
35
|
+
config: MyFragmentConfig = {},
|
|
36
|
+
options: FragnoPublicConfigWithDatabase, // Enforces databaseAdapter requirement
|
|
37
|
+
) {
|
|
38
|
+
return createFragment(fragmentDef, config, [], options);
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
For complete database documentation, see:
|
|
43
|
+
https://fragno.dev/docs/for-library-authors/database-integration/overview.md
|
|
44
|
+
|
|
27
45
|
## File Structure & Core Concepts
|
|
28
46
|
|
|
29
47
|
### `src/index.ts` - Main Fragment Definition
|
|
@@ -94,6 +112,107 @@ points:
|
|
|
94
112
|
- Production uses built files from `dist/`
|
|
95
113
|
- When adding new framework exports, add corresponding client files in `src/client/`
|
|
96
114
|
|
|
115
|
+
## Database Integration (Optional)
|
|
116
|
+
|
|
117
|
+
Some Fragments require persistent storage. Fragno provides an optional database layer via
|
|
118
|
+
`@fragno-dev/db` that integrates with your users' existing databases.
|
|
119
|
+
|
|
120
|
+
### When to Use Database Integration
|
|
121
|
+
|
|
122
|
+
Use `defineFragmentWithDatabase()` when your Fragment needs to:
|
|
123
|
+
|
|
124
|
+
- Store persistent data (comments, likes, user preferences, etc.)
|
|
125
|
+
- Query structured data efficiently
|
|
126
|
+
- Maintain data integrity with indexes and constraints
|
|
127
|
+
- Provide users with full control over their data
|
|
128
|
+
|
|
129
|
+
### Schema Definition
|
|
130
|
+
|
|
131
|
+
Database schemas are defined in a separate `schema.ts` file using the Fragno schema builder:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import { column, idColumn, schema } from "@fragno-dev/db/schema";
|
|
135
|
+
|
|
136
|
+
export const noteSchema = schema((s) => {
|
|
137
|
+
return s.addTable("note", (t) => {
|
|
138
|
+
return t
|
|
139
|
+
.addColumn("id", idColumn()) // Auto-generated ID
|
|
140
|
+
.addColumn("content", column("string"))
|
|
141
|
+
.addColumn("userId", column("string"))
|
|
142
|
+
.addColumn("createdAt", column("timestamp").defaultTo$("now"))
|
|
143
|
+
.createIndex("idx_note_user", ["userId"]); // Index for efficient queries
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Key concepts**:
|
|
149
|
+
|
|
150
|
+
- **Append-only**: Schemas use an append-only log approach. Never modify existing operations -
|
|
151
|
+
always add new ones
|
|
152
|
+
- **Versioning**: Each schema operation increments the version number
|
|
153
|
+
- **Indexes**: Create indexes on columns you'll frequently query (e.g., foreign keys, user IDs)
|
|
154
|
+
- **Defaults**: `.defaultTo(value)` or `.defaultTo((b) => b.now())` for DB defaults;
|
|
155
|
+
`.defaultTo$((b) => b.cuid())` for runtime defaults
|
|
156
|
+
|
|
157
|
+
### Using the ORM
|
|
158
|
+
|
|
159
|
+
The ORM is available in both `withDependencies()` and `withServices()` via the `orm` parameter:
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
const fragmentDef = defineFragmentWithDatabase<Config>("my-fragment")
|
|
163
|
+
.withDatabase(mySchema)
|
|
164
|
+
.withServices(({ orm }) => {
|
|
165
|
+
return {
|
|
166
|
+
createNote: async (note) => {
|
|
167
|
+
const id = await orm.create("note", note);
|
|
168
|
+
return { id: id.toJSON(), ...note };
|
|
169
|
+
},
|
|
170
|
+
getNotesByUser: (userId: string) => {
|
|
171
|
+
// Use whereIndex for efficient indexed queries
|
|
172
|
+
return orm.find("note", (b) =>
|
|
173
|
+
b.whereIndex("idx_note_user", (eb) => eb("userId", "=", userId)),
|
|
174
|
+
);
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**ORM methods**:
|
|
181
|
+
|
|
182
|
+
- `orm.create(table, data)` - Insert a row, returns FragnoId
|
|
183
|
+
- `orm.find(table, builder)` - Query rows with filtering
|
|
184
|
+
- `orm.findOne(table, builder)` - Query a single row
|
|
185
|
+
- `orm.update(table, id, data)` - Update a row by ID
|
|
186
|
+
- `orm.delete(table, id)` - Delete a row by ID
|
|
187
|
+
- `.whereIndex(indexName, condition)` - Use indexes for efficient queries
|
|
188
|
+
|
|
189
|
+
### Transactions (Unit of Work)
|
|
190
|
+
|
|
191
|
+
Two-phase pattern for atomic operations (optimistic concurrency control):
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
// Phase 1: Retrieve with version tracking
|
|
195
|
+
const uow = orm
|
|
196
|
+
.createUnitOfWork()
|
|
197
|
+
.find("users", (b) => b.whereIndex("primary", (eb) => eb("id", "=", userId)))
|
|
198
|
+
.find("accounts", (b) => b.whereIndex("idx_user", (eb) => eb("userId", "=", userId)));
|
|
199
|
+
const [users, accounts] = await uow.executeRetrieve();
|
|
200
|
+
|
|
201
|
+
// Phase 2: Mutate atomically
|
|
202
|
+
uow.update("users", users[0].id, (b) => b.set({ lastLogin: new Date() }).check());
|
|
203
|
+
uow.update("accounts", accounts[0].id, (b) =>
|
|
204
|
+
b.set({ balance: accounts[0].balance + 100 }).check(),
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
const { success } = await uow.executeMutations();
|
|
208
|
+
if (!success) {
|
|
209
|
+
/* Version conflict - retry */
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Notes**: `.check()` enables optimistic concurrency control; requires `FragnoId` objects (not
|
|
214
|
+
string IDs); use `uow.getCreatedIds()` for new record IDs
|
|
215
|
+
|
|
97
216
|
## Strategies for Building Fragments
|
|
98
217
|
|
|
99
218
|
### OpenAPI/Swagger Spec → Fragno Routes
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineRoute,
|
|
3
|
+
defineRoutes,
|
|
4
|
+
createFragment,
|
|
5
|
+
type FragnoPublicClientConfig,
|
|
6
|
+
} from "@fragno-dev/core";
|
|
7
|
+
import { createClientBuilder } from "@fragno-dev/core/client";
|
|
8
|
+
import {
|
|
9
|
+
defineFragmentWithDatabase,
|
|
10
|
+
type FragnoPublicConfigWithDatabase,
|
|
11
|
+
} from "@fragno-dev/db/fragment";
|
|
12
|
+
import type { AbstractQuery, TableToInsertValues } from "@fragno-dev/db/query";
|
|
13
|
+
import { noteSchema } from "./schema";
|
|
14
|
+
|
|
15
|
+
// NOTE: We use zod here for defining schemas, but any StandardSchema library can be used!
|
|
16
|
+
// For a complete list see:
|
|
17
|
+
// https://github.com/standard-schema/standard-schema#what-schema-libraries-implement-the-spec
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
|
|
20
|
+
export interface ExampleConfig {
|
|
21
|
+
// Add any server-side configuration here if needed
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type ExampleServices = {
|
|
25
|
+
createNote: (note: TableToInsertValues<typeof noteSchema.tables.note>) => Promise<{
|
|
26
|
+
id: string;
|
|
27
|
+
content: string;
|
|
28
|
+
userId: string;
|
|
29
|
+
createdAt: Date;
|
|
30
|
+
}>;
|
|
31
|
+
getNotes: () => Promise<
|
|
32
|
+
Array<{
|
|
33
|
+
id: string;
|
|
34
|
+
content: string;
|
|
35
|
+
userId: string;
|
|
36
|
+
createdAt: Date;
|
|
37
|
+
}>
|
|
38
|
+
>;
|
|
39
|
+
getNotesByUser: (userId: string) => Promise<
|
|
40
|
+
Array<{
|
|
41
|
+
id: string;
|
|
42
|
+
content: string;
|
|
43
|
+
userId: string;
|
|
44
|
+
createdAt: Date;
|
|
45
|
+
}>
|
|
46
|
+
>;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type ExampleDeps = {
|
|
50
|
+
orm: AbstractQuery<typeof noteSchema>;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const exampleRoutesFactory = defineRoutes<ExampleConfig, ExampleDeps, ExampleServices>().create(
|
|
54
|
+
({ services }) => {
|
|
55
|
+
return [
|
|
56
|
+
defineRoute({
|
|
57
|
+
method: "GET",
|
|
58
|
+
path: "/notes",
|
|
59
|
+
queryParameters: ["userId"],
|
|
60
|
+
outputSchema: z.array(
|
|
61
|
+
z.object({
|
|
62
|
+
id: z.string(),
|
|
63
|
+
content: z.string(),
|
|
64
|
+
userId: z.string(),
|
|
65
|
+
createdAt: z.date(),
|
|
66
|
+
}),
|
|
67
|
+
),
|
|
68
|
+
handler: async ({ query }, { json }) => {
|
|
69
|
+
const userId = query.get("userId");
|
|
70
|
+
|
|
71
|
+
if (userId) {
|
|
72
|
+
const notes = await services.getNotesByUser(userId);
|
|
73
|
+
return json(notes);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const notes = await services.getNotes();
|
|
77
|
+
return json(notes);
|
|
78
|
+
},
|
|
79
|
+
}),
|
|
80
|
+
|
|
81
|
+
defineRoute({
|
|
82
|
+
method: "POST",
|
|
83
|
+
path: "/notes",
|
|
84
|
+
inputSchema: z.object({ content: z.string(), userId: z.string() }),
|
|
85
|
+
outputSchema: z.object({
|
|
86
|
+
id: z.string(),
|
|
87
|
+
content: z.string(),
|
|
88
|
+
userId: z.string(),
|
|
89
|
+
createdAt: z.date(),
|
|
90
|
+
}),
|
|
91
|
+
errorCodes: [],
|
|
92
|
+
handler: async ({ input }, { json }) => {
|
|
93
|
+
const { content, userId } = await input.valid();
|
|
94
|
+
|
|
95
|
+
const note = await services.createNote({ content, userId });
|
|
96
|
+
return json(note);
|
|
97
|
+
},
|
|
98
|
+
}),
|
|
99
|
+
];
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const exampleFragmentDefinition = defineFragmentWithDatabase<ExampleConfig>("example-fragment")
|
|
104
|
+
.withDatabase(noteSchema)
|
|
105
|
+
.withServices(({ orm }) => {
|
|
106
|
+
return {
|
|
107
|
+
createNote: async (note: TableToInsertValues<typeof noteSchema.tables.note>) => {
|
|
108
|
+
const id = await orm.create("note", note);
|
|
109
|
+
return {
|
|
110
|
+
...note,
|
|
111
|
+
id: id.toJSON(),
|
|
112
|
+
createdAt: note.createdAt ?? new Date(),
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
getNotes: () => {
|
|
116
|
+
return orm.find("note", (b) => b);
|
|
117
|
+
},
|
|
118
|
+
getNotesByUser: (userId: string) => {
|
|
119
|
+
return orm.find("note", (b) =>
|
|
120
|
+
b.whereIndex("idx_note_user", (eb) => eb("userId", "=", userId)),
|
|
121
|
+
);
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
export function createExampleFragment(
|
|
127
|
+
config: ExampleConfig = {},
|
|
128
|
+
fragnoConfig: FragnoPublicConfigWithDatabase,
|
|
129
|
+
) {
|
|
130
|
+
return createFragment(exampleFragmentDefinition, config, [exampleRoutesFactory], fragnoConfig);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function createExampleFragmentClients(fragnoConfig: FragnoPublicClientConfig) {
|
|
134
|
+
const b = createClientBuilder(exampleFragmentDefinition, fragnoConfig, [exampleRoutesFactory]);
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
useNotes: b.createHook("/notes"),
|
|
138
|
+
useCreateNote: b.createMutator("POST", "/notes"),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
export type { FragnoRouteConfig } from "@fragno-dev/core/api";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { column, idColumn, schema } from "@fragno-dev/db/schema";
|
|
2
|
+
|
|
3
|
+
export const noteSchema = schema((s) => {
|
|
4
|
+
return s.addTable("note", (t) => {
|
|
5
|
+
return t
|
|
6
|
+
.addColumn("id", idColumn())
|
|
7
|
+
.addColumn("content", column("string"))
|
|
8
|
+
.addColumn("userId", column("string"))
|
|
9
|
+
.addColumn(
|
|
10
|
+
"createdAt",
|
|
11
|
+
column("timestamp").defaultTo((b) => b.now()),
|
|
12
|
+
)
|
|
13
|
+
.createIndex("idx_note_user", ["userId"]);
|
|
14
|
+
});
|
|
15
|
+
});
|
package/.turbo/turbo-test.log
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
$ vitest run
|
|
2
|
-
|
|
3
|
-
[1m[46m RUN [49m[22m [36mv3.2.4 [39m[90m/home/runner/work/fragno/fragno/packages/create[39m
|
|
4
|
-
[2mCoverage enabled with [22m[33mistanbul[39m
|
|
5
|
-
|
|
6
|
-
[32m✓[39m src/utils.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 4[2mms[22m[39m
|
|
7
|
-
[90mstdout[2m | src/integration.test.ts[2m > [22m[2mfragment with rspack
|
|
8
|
-
[22m[39mtemp /tmp/fragment-test-tsdown-1760972394592
|
|
9
|
-
|
|
10
|
-
[90mstdout[2m | src/integration.test.ts[2m > [22m[2mfragment with tsdown[2m > [22m[2mpackage.json correctly templated
|
|
11
|
-
[22m[39mtemp /tmp/fragment-test-esbuild-1760972394596
|
|
12
|
-
|
|
13
|
-
[90mstdout[2m | src/integration.test.ts[2m > [22m[2mfragment with esbuild[2m > [22m[2mpackage.json correctly templated
|
|
14
|
-
[22m[39mtemp /tmp/fragment-test-vite-1760972394596
|
|
15
|
-
|
|
16
|
-
[90mstdout[2m | src/integration.test.ts[2m > [22m[2mfragment with vite[2m > [22m[2mpackage.json correctly templated
|
|
17
|
-
[22m[39mtemp /tmp/fragment-test-rollup-1760972394596
|
|
18
|
-
|
|
19
|
-
[90mstdout[2m | src/integration.test.ts[2m > [22m[2mfragment with rollup[2m > [22m[2mpackage.json correctly templated
|
|
20
|
-
[22m[39mtemp /tmp/fragment-test-webpack-1760972394597
|
|
21
|
-
|
|
22
|
-
[90mstdout[2m | src/integration.test.ts[2m > [22m[2mfragment with webpack[2m > [22m[2mpackage.json correctly templated
|
|
23
|
-
[22m[39mtemp /tmp/fragment-test-rspack-1760972394597
|
|
24
|
-
|
|
25
|
-
[90mstdout[2m | src/integration.test.ts[2m > [22m[2mfragment with webpack[2m > [22m[2mcompiles
|
|
26
|
-
[22m[39m
|
|
27
|
-
|
|
28
|
-
[90mstdout[2m | src/integration.test.ts[2m > [22m[2mfragment with rspack[2m > [22m[2mcompiles
|
|
29
|
-
[22m[39m
|
|
30
|
-
|
|
31
|
-
[90mstdout[2m | src/integration.test.ts[2m > [22m[2mfragment with rollup
|
|
32
|
-
[22m[39m
|
|
33
|
-
|
|
34
|
-
[90mstdout[2m | src/integration.test.ts
|
|
35
|
-
[22m[39m
|
|
36
|
-
|
|
37
|
-
[90mstdout[2m | src/integration.test.ts
|
|
38
|
-
[22m[39m
|
|
39
|
-
|
|
40
|
-
[90mstdout[2m | src/integration.test.ts[2m > [22m[2mfragment with webpack[2m > [22m[2mbuilds
|
|
41
|
-
[22m[39m
|
|
42
|
-
|
|
43
|
-
[32m✓[39m src/integration.test.ts [2m([22m[2m30 tests[22m[2m | [22m[33m1 skipped[39m[2m)[22m[33m 24967[2mms[22m[39m
|
|
44
|
-
[33m[2m✓[22m[39m fragment with tsdown[2m > [22minstalls [33m 2596[2mms[22m[39m
|
|
45
|
-
[33m[2m✓[22m[39m fragment with tsdown[2m > [22mcompiles [33m 7152[2mms[22m[39m
|
|
46
|
-
[33m[2m✓[22m[39m fragment with tsdown[2m > [22mbuilds [33m 9413[2mms[22m[39m
|
|
47
|
-
[33m[2m✓[22m[39m fragment with esbuild[2m > [22minstalls [33m 2515[2mms[22m[39m
|
|
48
|
-
[33m[2m✓[22m[39m fragment with esbuild[2m > [22mcompiles [33m 6488[2mms[22m[39m
|
|
49
|
-
[33m[2m✓[22m[39m fragment with esbuild[2m > [22mbuilds [33m 1739[2mms[22m[39m
|
|
50
|
-
[33m[2m✓[22m[39m fragment with vite[2m > [22minstalls [33m 2517[2mms[22m[39m
|
|
51
|
-
[33m[2m✓[22m[39m fragment with vite[2m > [22mcompiles [33m 6562[2mms[22m[39m
|
|
52
|
-
[33m[2m✓[22m[39m fragment with vite[2m > [22mbuilds [33m 6624[2mms[22m[39m
|
|
53
|
-
[33m[2m✓[22m[39m fragment with rollup[2m > [22minstalls [33m 2570[2mms[22m[39m
|
|
54
|
-
[33m[2m✓[22m[39m fragment with rollup[2m > [22mcompiles [33m 6173[2mms[22m[39m
|
|
55
|
-
[33m[2m✓[22m[39m fragment with webpack[2m > [22minstalls [33m 2607[2mms[22m[39m
|
|
56
|
-
[33m[2m✓[22m[39m fragment with webpack[2m > [22mcompiles [33m 6274[2mms[22m[39m
|
|
57
|
-
[33m[2m✓[22m[39m fragment with webpack[2m > [22mbuilds [33m 13005[2mms[22m[39m
|
|
58
|
-
[33m[2m✓[22m[39m fragment with rspack[2m > [22minstalls [33m 3026[2mms[22m[39m
|
|
59
|
-
[33m[2m✓[22m[39m fragment with rspack[2m > [22mcompiles [33m 5303[2mms[22m[39m
|
|
60
|
-
[33m[2m✓[22m[39m fragment with rspack[2m > [22mbuilds [33m 3715[2mms[22m[39m
|
|
61
|
-
|
|
62
|
-
[2m Test Files [22m [1m[32m2 passed[39m[22m[90m (2)[39m
|
|
63
|
-
[2m Tests [22m [1m[32m30 passed[39m[22m[2m | [22m[33m1 skipped[39m[90m (31)[39m
|
|
64
|
-
[2m Start at [22m 14:59:53
|
|
65
|
-
[2m Duration [22m 25.62s[2m (transform 325ms, setup 0ms, collect 498ms, tests 24.97s, environment 0ms, prepare 179ms)[22m
|
|
66
|
-
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
$ tsc --noEmit
|