@fragno-dev/create 0.1.0 → 0.1.3
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 +9 -6
- package/CHANGELOG.md +19 -0
- package/LICENSE.md +16 -0
- package/dist/index.js +19 -16
- package/dist/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +19 -14
- package/src/integration.test.ts +9 -6
- package/src/package-json.ts +18 -15
- package/templates/fragment/src/index.ts +1 -1
- package/templates/optional/agent/AGENTS.md +124 -79
- package/templates/optional/builder/esbuild.config.js +3 -3
- package/templates/optional/builder/rollup.config.js +2 -2
- package/templates/optional/builder/rspack.config.js +3 -2
- package/templates/optional/builder/vite.config.ts +2 -1
- package/templates/optional/builder/webpack.config.js +3 -2
- package/templates/optional/database/index.ts +6 -10
- package/templates/optional/database/schema.ts +4 -1
package/package.json
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fragno-dev/create",
|
|
3
3
|
"description": "A library for creating Fragno fragments",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.3",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
7
|
-
"bun": "./src/index.ts",
|
|
8
7
|
"development": "./src/index.ts",
|
|
9
8
|
"types": "./dist/index.d.ts",
|
|
10
9
|
"default": "./dist/index.js"
|
|
@@ -13,26 +12,32 @@
|
|
|
13
12
|
"main": "./dist/index.js",
|
|
14
13
|
"module": "./dist/index.js",
|
|
15
14
|
"types": "./dist/index.d.ts",
|
|
16
|
-
"scripts": {
|
|
17
|
-
"test": "vitest run",
|
|
18
|
-
"build": "tsdown",
|
|
19
|
-
"build:watch": "tsdown --watch",
|
|
20
|
-
"types:check": "tsc --noEmit"
|
|
21
|
-
},
|
|
22
15
|
"type": "module",
|
|
23
16
|
"dependencies": {
|
|
24
17
|
"zod": "^4.1.12"
|
|
25
18
|
},
|
|
26
19
|
"devDependencies": {
|
|
27
|
-
"@
|
|
28
|
-
"@fragno-private/vitest-config": "0.0.0",
|
|
29
|
-
"@types/node": "^20",
|
|
30
|
-
"@types/bun": "latest",
|
|
20
|
+
"@types/node": "^22",
|
|
31
21
|
"@vitest/coverage-istanbul": "^3.2.4",
|
|
32
|
-
"vitest": "^3.2.4"
|
|
22
|
+
"vitest": "^3.2.4",
|
|
23
|
+
"@fragno-private/typescript-config": "0.0.1",
|
|
24
|
+
"@fragno-private/vitest-config": "0.0.0"
|
|
33
25
|
},
|
|
34
26
|
"private": false,
|
|
35
27
|
"peerDependencies": {
|
|
36
28
|
"typescript": "^5"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/rejot-dev/fragno.git",
|
|
33
|
+
"directory": "packages/create"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://fragno.dev",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"scripts": {
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"build": "tsdown",
|
|
40
|
+
"build:watch": "tsdown --watch",
|
|
41
|
+
"types:check": "tsc --noEmit"
|
|
37
42
|
}
|
|
38
|
-
}
|
|
43
|
+
}
|
package/src/integration.test.ts
CHANGED
|
@@ -54,8 +54,8 @@ function createFragmentTestSuite(buildTool: BuildTool, withDatabase: boolean) {
|
|
|
54
54
|
await expect(fs.access(agentFile)).resolves.toBeUndefined();
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
-
test("installs", { timeout:
|
|
58
|
-
const { stdout } = await execAsync("
|
|
57
|
+
test("installs", { timeout: 30000 }, async () => {
|
|
58
|
+
const { stdout } = await execAsync("pnpm install", {
|
|
59
59
|
cwd: tempDir,
|
|
60
60
|
encoding: "utf8",
|
|
61
61
|
});
|
|
@@ -63,7 +63,7 @@ function createFragmentTestSuite(buildTool: BuildTool, withDatabase: boolean) {
|
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
test("compiles", { timeout: 30000 }, async () => {
|
|
66
|
-
const { stdout } = await execAsync("
|
|
66
|
+
const { stdout } = await execAsync("pnpm run types:check", {
|
|
67
67
|
cwd: tempDir,
|
|
68
68
|
encoding: "utf8",
|
|
69
69
|
});
|
|
@@ -72,12 +72,12 @@ function createFragmentTestSuite(buildTool: BuildTool, withDatabase: boolean) {
|
|
|
72
72
|
});
|
|
73
73
|
/*
|
|
74
74
|
FIXME: Skipping this test for rollup:
|
|
75
|
-
When running rollup directly through
|
|
75
|
+
When running rollup directly through pnpm run build or npm run build the build succeeds,
|
|
76
76
|
but somehow when running through vitest the module resolution mechanism changes causing
|
|
77
77
|
the build to fail.
|
|
78
78
|
*/
|
|
79
|
-
test.skipIf(buildTool === "rollup")("builds", { timeout:
|
|
80
|
-
const result = await execAsync("
|
|
79
|
+
test.skipIf(buildTool === "rollup")("builds", { timeout: 50000 }, async () => {
|
|
80
|
+
const result = await execAsync("pnpm run build", {
|
|
81
81
|
cwd: tempDir,
|
|
82
82
|
encoding: "utf8",
|
|
83
83
|
});
|
|
@@ -95,6 +95,9 @@ function createFragmentTestSuite(buildTool: BuildTool, withDatabase: boolean) {
|
|
|
95
95
|
// However, the peerDependencies of @fragno-dev/core must not be included
|
|
96
96
|
expect(reactBundleContent).toMatch(/from\s*['"]react['"]/);
|
|
97
97
|
|
|
98
|
+
// db should also not be included in the frontend
|
|
99
|
+
expect(reactBundleContent).not.toMatch(/import\s+.*?\s+from\s+['"]@fragno-dev\/db/);
|
|
100
|
+
|
|
98
101
|
// Vite builds only the browser bundle
|
|
99
102
|
if (buildTool !== "vite") {
|
|
100
103
|
await expect(fs.access(path.join(tempDir, "dist", "node"))).resolves.toBeUndefined();
|
package/src/package-json.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import type { BuildTools } from "./index";
|
|
2
2
|
|
|
3
|
-
const fragnoCoreVersion = "^0.1.
|
|
4
|
-
const fragnoDbVersion = "^0.1.
|
|
5
|
-
const unpluginFragnoVersion = "^0.0.
|
|
6
|
-
const fragnoCliVersion = "^0.1.
|
|
3
|
+
const fragnoCoreVersion = "^0.1.7";
|
|
4
|
+
const fragnoDbVersion = "^0.1.13";
|
|
5
|
+
const unpluginFragnoVersion = "^0.0.3";
|
|
6
|
+
const fragnoCliVersion = "^0.1.16";
|
|
7
7
|
|
|
8
8
|
export const basePkg: Record<string, unknown> = {
|
|
9
9
|
dependencies: {
|
|
10
10
|
"@fragno-dev/core": fragnoCoreVersion,
|
|
11
|
-
zod: "^4.
|
|
11
|
+
zod: "^4.1.12",
|
|
12
12
|
},
|
|
13
13
|
devDependencies: {
|
|
14
|
-
"@fragno-dev/cli": fragnoCliVersion,
|
|
15
14
|
"@types/node": "^22",
|
|
15
|
+
"@fragno-dev/cli": fragnoCliVersion,
|
|
16
16
|
},
|
|
17
17
|
peerDependencies: {
|
|
18
|
-
typescript: "
|
|
18
|
+
typescript: ">=5",
|
|
19
19
|
react: ">=18.0.0",
|
|
20
20
|
svelte: ">=4.0.0",
|
|
21
21
|
"solid-js": ">=1.0.0",
|
|
@@ -24,7 +24,10 @@ export const basePkg: Record<string, unknown> = {
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
export const databasePkg: Record<string, unknown> = {
|
|
27
|
-
|
|
27
|
+
devDependencies: {
|
|
28
|
+
"@fragno-dev/db": fragnoDbVersion,
|
|
29
|
+
},
|
|
30
|
+
peerDependencies: {
|
|
28
31
|
"@fragno-dev/db": fragnoDbVersion,
|
|
29
32
|
},
|
|
30
33
|
};
|
|
@@ -34,7 +37,7 @@ export const buildToolPkg: Record<BuildTools, Record<string, unknown>> = {
|
|
|
34
37
|
tsdown: {
|
|
35
38
|
devDependencies: {
|
|
36
39
|
"@fragno-dev/unplugin-fragno": unpluginFragnoVersion,
|
|
37
|
-
tsdown: "^0.
|
|
40
|
+
tsdown: "^0.12.0",
|
|
38
41
|
},
|
|
39
42
|
scripts: {
|
|
40
43
|
build: "tsdown",
|
|
@@ -43,7 +46,7 @@ export const buildToolPkg: Record<BuildTools, Record<string, unknown>> = {
|
|
|
43
46
|
esbuild: {
|
|
44
47
|
devDependencies: {
|
|
45
48
|
"@fragno-dev/unplugin-fragno": unpluginFragnoVersion,
|
|
46
|
-
esbuild: "^0.25.
|
|
49
|
+
esbuild: "^0.25.12",
|
|
47
50
|
},
|
|
48
51
|
scripts: {
|
|
49
52
|
build: "./esbuild.config.js",
|
|
@@ -52,7 +55,7 @@ export const buildToolPkg: Record<BuildTools, Record<string, unknown>> = {
|
|
|
52
55
|
vite: {
|
|
53
56
|
devDependencies: {
|
|
54
57
|
"@fragno-dev/unplugin-fragno": unpluginFragnoVersion,
|
|
55
|
-
vite: "^6.
|
|
58
|
+
vite: "^6.3.5",
|
|
56
59
|
},
|
|
57
60
|
scripts: {
|
|
58
61
|
build: "vite build",
|
|
@@ -64,7 +67,7 @@ export const buildToolPkg: Record<BuildTools, Record<string, unknown>> = {
|
|
|
64
67
|
"@rollup/plugin-node-resolve": "^16.0.2",
|
|
65
68
|
"@rollup/plugin-typescript": "^12.1.4",
|
|
66
69
|
tslib: "^2.8.1",
|
|
67
|
-
rollup: "^4.
|
|
70
|
+
rollup: "^4.41.0",
|
|
68
71
|
},
|
|
69
72
|
scripts: {
|
|
70
73
|
build: "rollup -c",
|
|
@@ -73,7 +76,7 @@ export const buildToolPkg: Record<BuildTools, Record<string, unknown>> = {
|
|
|
73
76
|
webpack: {
|
|
74
77
|
devDependencies: {
|
|
75
78
|
"@fragno-dev/unplugin-fragno": unpluginFragnoVersion,
|
|
76
|
-
webpack: "^5.
|
|
79
|
+
webpack: "^5.99.9",
|
|
77
80
|
"webpack-cli": "^6.0.1",
|
|
78
81
|
"ts-loader": "^9.5.1",
|
|
79
82
|
},
|
|
@@ -84,8 +87,8 @@ export const buildToolPkg: Record<BuildTools, Record<string, unknown>> = {
|
|
|
84
87
|
rspack: {
|
|
85
88
|
devDependencies: {
|
|
86
89
|
"@fragno-dev/unplugin-fragno": unpluginFragnoVersion,
|
|
87
|
-
"@rspack/core": "^1.
|
|
88
|
-
"@rspack/cli": "^1.
|
|
90
|
+
"@rspack/core": "^1.6.1",
|
|
91
|
+
"@rspack/cli": "^1.6.1",
|
|
89
92
|
},
|
|
90
93
|
scripts: {
|
|
91
94
|
build: "rspack build",
|
|
@@ -61,7 +61,7 @@ const exampleFragmentDefinition = defineFragment<ExampleConfig>("example-fragmen
|
|
|
61
61
|
serverSideData: { value: config.initialData ?? "Hello World! This is a server-side data." },
|
|
62
62
|
};
|
|
63
63
|
})
|
|
64
|
-
.
|
|
64
|
+
.providesService(({ 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,79 +26,6 @@ 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
|
|
|
27
|
-
## Database Integration (Optional)
|
|
28
|
-
|
|
29
|
-
Some Fragments require persistent storage. Fragno provides an optional database layer via
|
|
30
|
-
`@fragno-dev/db` that integrates with your users' existing databases.
|
|
31
|
-
|
|
32
|
-
### When to Use Database Integration
|
|
33
|
-
|
|
34
|
-
Use `defineFragmentWithDatabase()` when your Fragment needs to:
|
|
35
|
-
|
|
36
|
-
- Store persistent data (comments, likes, user preferences, etc.)
|
|
37
|
-
- Query structured data efficiently
|
|
38
|
-
- Maintain data integrity with indexes and constraints
|
|
39
|
-
- Provide users with full control over their data
|
|
40
|
-
|
|
41
|
-
### Schema Definition
|
|
42
|
-
|
|
43
|
-
Database schemas are defined in a separate `schema.ts` file using the Fragno schema builder:
|
|
44
|
-
|
|
45
|
-
```typescript
|
|
46
|
-
import { column, idColumn, schema } from "@fragno-dev/db/schema";
|
|
47
|
-
|
|
48
|
-
export const noteSchema = schema((s) => {
|
|
49
|
-
return s.addTable("note", (t) => {
|
|
50
|
-
return t
|
|
51
|
-
.addColumn("id", idColumn()) // Auto-generated ID
|
|
52
|
-
.addColumn("content", column("string"))
|
|
53
|
-
.addColumn("userId", column("string"))
|
|
54
|
-
.addColumn("createdAt", column("timestamp").defaultTo$("now"))
|
|
55
|
-
.createIndex("idx_note_user", ["userId"]); // Index for efficient queries
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
**Key concepts**:
|
|
61
|
-
|
|
62
|
-
- **Append-only**: Schemas use an append-only log approach. Never modify existing operations -
|
|
63
|
-
always add new ones
|
|
64
|
-
- **Versioning**: Each schema operation increments the version number
|
|
65
|
-
- **Indexes**: Create indexes on columns you'll frequently query (e.g., foreign keys, user IDs)
|
|
66
|
-
- **Defaults**: Use `.defaultTo(value)` or `.defaultTo$("now")` for timestamps
|
|
67
|
-
|
|
68
|
-
### Using the ORM
|
|
69
|
-
|
|
70
|
-
The ORM is available in both `withDependencies()` and `withServices()` via the `orm` parameter:
|
|
71
|
-
|
|
72
|
-
```typescript
|
|
73
|
-
const fragmentDef = defineFragmentWithDatabase<Config>("my-fragment")
|
|
74
|
-
.withDatabase(mySchema)
|
|
75
|
-
.withServices(({ orm }) => {
|
|
76
|
-
return {
|
|
77
|
-
createNote: async (note) => {
|
|
78
|
-
const id = await orm.create("note", note);
|
|
79
|
-
return { id: id.toJSON(), ...note };
|
|
80
|
-
},
|
|
81
|
-
getNotesByUser: (userId: string) => {
|
|
82
|
-
// Use whereIndex for efficient indexed queries
|
|
83
|
-
return orm.find("note", (b) =>
|
|
84
|
-
b.whereIndex("idx_note_user", (eb) => eb("userId", "=", userId)),
|
|
85
|
-
);
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
});
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
**ORM methods**:
|
|
92
|
-
|
|
93
|
-
- `orm.create(table, data)` - Insert a row, returns FragnoId
|
|
94
|
-
- `orm.find(table, builder)` - Query rows with filtering
|
|
95
|
-
- `orm.findOne(table, builder)` - Query a single row
|
|
96
|
-
- `orm.update(table, id, data)` - Update a row by ID
|
|
97
|
-
- `orm.delete(table, id)` - Delete a row by ID
|
|
98
|
-
- `.whereIndex(indexName, condition)` - Use indexes for efficient queries
|
|
99
|
-
|
|
100
29
|
### Fragment Configuration
|
|
101
30
|
|
|
102
31
|
Database-enabled Fragments require `FragnoPublicConfigWithDatabase`:
|
|
@@ -111,7 +40,7 @@ export function createMyFragment(
|
|
|
111
40
|
```
|
|
112
41
|
|
|
113
42
|
For complete database documentation, see:
|
|
114
|
-
https://fragno.dev/docs/for-library-authors/database-integration/overview.md
|
|
43
|
+
https://fragno.dev/docs/fragno/for-library-authors/database-integration/overview.md
|
|
115
44
|
|
|
116
45
|
## File Structure & Core Concepts
|
|
117
46
|
|
|
@@ -167,9 +96,9 @@ client builder.
|
|
|
167
96
|
|
|
168
97
|
Each framework requires a separate client file that wraps the generic client builder with the
|
|
169
98
|
framework-specific `useFragno` hook. Check the `src/client/` directory for existing framework
|
|
170
|
-
implementations. Use the frameworks page on https://fragno.dev/docs/frameworks to
|
|
171
|
-
have their stubs defined. Make sure to include new frameworks in the exports
|
|
172
|
-
package.json.
|
|
99
|
+
implementations. Use the frameworks page on https://fragno.dev/docs/fragno/reference/frameworks to
|
|
100
|
+
see if all clients have their stubs defined. Make sure to include new frameworks in the exports
|
|
101
|
+
section of package.json.
|
|
173
102
|
|
|
174
103
|
### `package.json` - Package Configuration
|
|
175
104
|
|
|
@@ -183,6 +112,122 @@ points:
|
|
|
183
112
|
- Production uses built files from `dist/`
|
|
184
113
|
- When adding new framework exports, add corresponding client files in `src/client/`
|
|
185
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, referenceColumn, schema } from "@fragno-dev/db/schema";
|
|
135
|
+
|
|
136
|
+
export const noteSchema = schema((s) => {
|
|
137
|
+
return s
|
|
138
|
+
.addTable("users", (t) => {
|
|
139
|
+
return t.addColumn("id", idColumn()).addColumn("name", column("string"));
|
|
140
|
+
})
|
|
141
|
+
.addTable("note", (t) => {
|
|
142
|
+
return t
|
|
143
|
+
.addColumn("id", idColumn()) // Auto-generated ID
|
|
144
|
+
.addColumn("content", column("string"))
|
|
145
|
+
.addColumn("userId", referenceColumn())
|
|
146
|
+
.addColumn("createdAt", column("timestamp").defaultTo$("now"))
|
|
147
|
+
.createIndex("idx_note_user", ["userId"]);
|
|
148
|
+
})
|
|
149
|
+
.addReference("author", {
|
|
150
|
+
from: {
|
|
151
|
+
table: "note",
|
|
152
|
+
column: "userId",
|
|
153
|
+
},
|
|
154
|
+
to: {
|
|
155
|
+
table: "user",
|
|
156
|
+
column: "id",
|
|
157
|
+
},
|
|
158
|
+
type: "one",
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Key concepts**:
|
|
164
|
+
|
|
165
|
+
- **Append-only**: Schemas use an append-only log approach. Never modify existing operations -
|
|
166
|
+
always add new ones
|
|
167
|
+
- **Versioning**: Each schema operation increments the version number
|
|
168
|
+
- **Indexes**: Create indexes on columns you'll frequently query (e.g., foreign keys, user IDs)
|
|
169
|
+
- **Defaults**: `.defaultTo(value)` or `.defaultTo((b) => b.now())` for DB defaults;
|
|
170
|
+
`.defaultTo$((b) => b.cuid())` for runtime defaults
|
|
171
|
+
|
|
172
|
+
### Using the ORM
|
|
173
|
+
|
|
174
|
+
The ORM is available in both `withDependencies()` and `withServices()` via the `orm` parameter:
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
const fragmentDef = defineFragmentWithDatabase<Config>("my-fragment")
|
|
178
|
+
.withDatabase(mySchema)
|
|
179
|
+
.withServices(({ orm }) => {
|
|
180
|
+
return {
|
|
181
|
+
createNote: async (note) => {
|
|
182
|
+
const id = await orm.create("note", note);
|
|
183
|
+
return { id: id.toJSON(), ...note };
|
|
184
|
+
},
|
|
185
|
+
getNotesByUser: (userId: string) => {
|
|
186
|
+
// Use whereIndex for efficient indexed queries
|
|
187
|
+
return orm.find("note", (b) =>
|
|
188
|
+
b.whereIndex("idx_note_user", (eb) => eb("userId", "=", userId)),
|
|
189
|
+
);
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**ORM methods**:
|
|
196
|
+
|
|
197
|
+
- `orm.create(table, data)` - Insert a row, returns FragnoId
|
|
198
|
+
- `orm.find(table, builder)` - Query rows with filtering
|
|
199
|
+
- `orm.findOne(table, builder)` - Query a single row
|
|
200
|
+
- `orm.update(table, id, data)` - Update a row by ID
|
|
201
|
+
- `orm.delete(table, id)` - Delete a row by ID
|
|
202
|
+
- `.whereIndex(indexName, condition)` - Use indexes for efficient queries
|
|
203
|
+
|
|
204
|
+
### Transactions (Unit of Work)
|
|
205
|
+
|
|
206
|
+
Two-phase pattern for atomic operations (optimistic concurrency control):
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
// Phase 1: Retrieve with version tracking
|
|
210
|
+
const uow = orm
|
|
211
|
+
.createUnitOfWork()
|
|
212
|
+
.find("users", (b) => b.whereIndex("primary", (eb) => eb("id", "=", userId)))
|
|
213
|
+
.find("accounts", (b) => b.whereIndex("idx_user", (eb) => eb("userId", "=", userId)));
|
|
214
|
+
const [users, accounts] = await uow.executeRetrieve();
|
|
215
|
+
|
|
216
|
+
// Phase 2: Mutate atomically
|
|
217
|
+
uow.update("users", users[0].id, (b) => b.set({ lastLogin: new Date() }).check());
|
|
218
|
+
uow.update("accounts", accounts[0].id, (b) =>
|
|
219
|
+
b.set({ balance: accounts[0].balance + 100 }).check(),
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
const { success } = await uow.executeMutations();
|
|
223
|
+
if (!success) {
|
|
224
|
+
/* Version conflict - retry */
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Notes**: `.check()` enables optimistic concurrency control; requires `FragnoId` objects (not
|
|
229
|
+
string IDs); use `uow.getCreatedIds()` for new record IDs
|
|
230
|
+
|
|
186
231
|
## Strategies for Building Fragments
|
|
187
232
|
|
|
188
233
|
### OpenAPI/Swagger Spec → Fragno Routes
|
|
@@ -217,7 +262,7 @@ Fragments require code splitting between client and server bundles using
|
|
|
217
262
|
### Type Checking
|
|
218
263
|
|
|
219
264
|
```bash
|
|
220
|
-
|
|
265
|
+
pnpm run types:check
|
|
221
266
|
```
|
|
222
267
|
|
|
223
268
|
## Common Patterns
|
|
@@ -9,6 +9,7 @@ build({
|
|
|
9
9
|
"./src/client/svelte.ts",
|
|
10
10
|
"./src/client/vanilla.ts",
|
|
11
11
|
"./src/client/vue.ts",
|
|
12
|
+
"./src/client/solid.ts",
|
|
12
13
|
],
|
|
13
14
|
outdir: "./dist/browser",
|
|
14
15
|
bundle: true,
|
|
@@ -18,7 +19,7 @@ build({
|
|
|
18
19
|
splitting: true,
|
|
19
20
|
sourcemap: true,
|
|
20
21
|
plugins: [unpluginFragno({ platform: "browser" })],
|
|
21
|
-
external: ["react", "svelte", "vue", "
|
|
22
|
+
external: ["react", "svelte", "vue", "solid-js", "@fragno-dev/db"],
|
|
22
23
|
});
|
|
23
24
|
|
|
24
25
|
build({
|
|
@@ -27,8 +28,7 @@ build({
|
|
|
27
28
|
bundle: true,
|
|
28
29
|
format: "esm",
|
|
29
30
|
platform: "node",
|
|
30
|
-
target: "
|
|
31
|
+
target: "node22",
|
|
31
32
|
sourcemap: true,
|
|
32
33
|
plugins: [unpluginFragno({ platform: "node" })],
|
|
33
|
-
external: ["zod"],
|
|
34
34
|
});
|
|
@@ -11,6 +11,7 @@ export default [
|
|
|
11
11
|
"client/svelte": "./src/client/svelte.ts",
|
|
12
12
|
"client/vanilla": "./src/client/vanilla.ts",
|
|
13
13
|
"client/vue": "./src/client/vue.ts",
|
|
14
|
+
"client/solid": "./src/client/solid.ts",
|
|
14
15
|
},
|
|
15
16
|
output: {
|
|
16
17
|
dir: "./dist/browser",
|
|
@@ -18,7 +19,7 @@ export default [
|
|
|
18
19
|
sourcemap: true,
|
|
19
20
|
},
|
|
20
21
|
// https://rollupjs.org/tools/#peer-dependencies
|
|
21
|
-
external: ["zod", "react", "svelte", "vue"],
|
|
22
|
+
external: ["zod", "react", "svelte", "vue", "solid-js", /^@fragno-dev\/db/],
|
|
22
23
|
plugins: [
|
|
23
24
|
resolve({
|
|
24
25
|
moduleDirectories: ["node_modules"],
|
|
@@ -41,7 +42,6 @@ export default [
|
|
|
41
42
|
format: "es",
|
|
42
43
|
sourcemap: true,
|
|
43
44
|
},
|
|
44
|
-
external: ["zod"],
|
|
45
45
|
plugins: [
|
|
46
46
|
resolve({
|
|
47
47
|
moduleDirectories: ["node_modules"],
|
|
@@ -16,6 +16,7 @@ export default [
|
|
|
16
16
|
"client/svelte": "./src/client/svelte.ts",
|
|
17
17
|
"client/vanilla": "./src/client/vanilla.ts",
|
|
18
18
|
"client/vue": "./src/client/vue.ts",
|
|
19
|
+
"client/solid": "./src/client/solid.ts",
|
|
19
20
|
},
|
|
20
21
|
output: {
|
|
21
22
|
path: path.resolve(__dirname, "dist/browser"),
|
|
@@ -48,7 +49,7 @@ export default [
|
|
|
48
49
|
},
|
|
49
50
|
plugins: [unpluginFragno({ platform: "browser" })],
|
|
50
51
|
devtool: "source-map",
|
|
51
|
-
externals: ["zod", "react", "vue", "svelte"],
|
|
52
|
+
externals: ["zod", "react", "vue", "svelte", "solid-js", /^@fragno-dev\/db/],
|
|
52
53
|
},
|
|
53
54
|
// Node build
|
|
54
55
|
{
|
|
@@ -88,6 +89,6 @@ export default [
|
|
|
88
89
|
},
|
|
89
90
|
plugins: [unpluginFragno({ platform: "node" })],
|
|
90
91
|
devtool: "source-map",
|
|
91
|
-
externals: ["zod"],
|
|
92
|
+
externals: ["zod", /^@fragno-dev\/core/, /^@fragno-dev\/db/],
|
|
92
93
|
},
|
|
93
94
|
];
|
|
@@ -12,11 +12,12 @@ export default defineConfig({
|
|
|
12
12
|
"client/svelte": "./src/client/svelte.ts",
|
|
13
13
|
"client/vanilla": "./src/client/vanilla.ts",
|
|
14
14
|
"client/vue": "./src/client/vue.ts",
|
|
15
|
+
"client/solid": "./src/client/solid.ts",
|
|
15
16
|
},
|
|
16
17
|
formats: ["es"],
|
|
17
18
|
},
|
|
18
19
|
rollupOptions: {
|
|
19
|
-
external: ["react", "vue", "svelte", "zod"],
|
|
20
|
+
external: ["react", "vue", "svelte", "solid-js", "zod", /^@fragno-dev\/db/],
|
|
20
21
|
},
|
|
21
22
|
outDir: "./dist/browser",
|
|
22
23
|
sourcemap: true,
|
|
@@ -16,6 +16,7 @@ export default [
|
|
|
16
16
|
"client/svelte": "./src/client/svelte.ts",
|
|
17
17
|
"client/vanilla": "./src/client/vanilla.ts",
|
|
18
18
|
"client/vue": "./src/client/vue.ts",
|
|
19
|
+
"client/solid": "./src/client/solid.ts",
|
|
19
20
|
},
|
|
20
21
|
output: {
|
|
21
22
|
path: path.resolve(__dirname, "dist/browser"),
|
|
@@ -41,7 +42,7 @@ export default [
|
|
|
41
42
|
},
|
|
42
43
|
plugins: [unpluginFragno({ platform: "browser" })],
|
|
43
44
|
devtool: "source-map",
|
|
44
|
-
externals: ["react", "vue", "svelte", "zod"],
|
|
45
|
+
externals: ["react", "vue", "svelte", "solid-js", "zod", /^@fragno-dev\/db/],
|
|
45
46
|
},
|
|
46
47
|
// Node build
|
|
47
48
|
{
|
|
@@ -74,6 +75,6 @@ export default [
|
|
|
74
75
|
},
|
|
75
76
|
plugins: [unpluginFragno({ platform: "node" })],
|
|
76
77
|
devtool: "source-map",
|
|
77
|
-
externals: ["zod"],
|
|
78
|
+
externals: ["zod", /^@fragno-dev\/core/, /^@fragno-dev\/db/],
|
|
78
79
|
},
|
|
79
80
|
];
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
defineFragmentWithDatabase,
|
|
10
10
|
type FragnoPublicConfigWithDatabase,
|
|
11
11
|
} from "@fragno-dev/db/fragment";
|
|
12
|
-
import type {
|
|
12
|
+
import type { TableToInsertValues } from "@fragno-dev/db/query";
|
|
13
13
|
import { noteSchema } from "./schema";
|
|
14
14
|
|
|
15
15
|
// NOTE: We use zod here for defining schemas, but any StandardSchema library can be used!
|
|
@@ -46,11 +46,7 @@ type ExampleServices = {
|
|
|
46
46
|
>;
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
orm: AbstractQuery<typeof noteSchema>;
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const exampleRoutesFactory = defineRoutes<ExampleConfig, ExampleDeps, ExampleServices>().create(
|
|
49
|
+
const exampleRoutesFactory = defineRoutes<ExampleConfig, {}, ExampleServices>().create(
|
|
54
50
|
({ services }) => {
|
|
55
51
|
return [
|
|
56
52
|
defineRoute({
|
|
@@ -102,10 +98,10 @@ const exampleRoutesFactory = defineRoutes<ExampleConfig, ExampleDeps, ExampleSer
|
|
|
102
98
|
|
|
103
99
|
const exampleFragmentDefinition = defineFragmentWithDatabase<ExampleConfig>("example-fragment")
|
|
104
100
|
.withDatabase(noteSchema)
|
|
105
|
-
.
|
|
101
|
+
.providesService(({ db }) => {
|
|
106
102
|
return {
|
|
107
103
|
createNote: async (note: TableToInsertValues<typeof noteSchema.tables.note>) => {
|
|
108
|
-
const id = await
|
|
104
|
+
const id = await db.create("note", note);
|
|
109
105
|
return {
|
|
110
106
|
...note,
|
|
111
107
|
id: id.toJSON(),
|
|
@@ -113,10 +109,10 @@ const exampleFragmentDefinition = defineFragmentWithDatabase<ExampleConfig>("exa
|
|
|
113
109
|
};
|
|
114
110
|
},
|
|
115
111
|
getNotes: () => {
|
|
116
|
-
return
|
|
112
|
+
return db.find("note", (b) => b);
|
|
117
113
|
},
|
|
118
114
|
getNotesByUser: (userId: string) => {
|
|
119
|
-
return
|
|
115
|
+
return db.find("note", (b) =>
|
|
120
116
|
b.whereIndex("idx_note_user", (eb) => eb("userId", "=", userId)),
|
|
121
117
|
);
|
|
122
118
|
},
|
|
@@ -6,7 +6,10 @@ export const noteSchema = schema((s) => {
|
|
|
6
6
|
.addColumn("id", idColumn())
|
|
7
7
|
.addColumn("content", column("string"))
|
|
8
8
|
.addColumn("userId", column("string"))
|
|
9
|
-
.addColumn(
|
|
9
|
+
.addColumn(
|
|
10
|
+
"createdAt",
|
|
11
|
+
column("timestamp").defaultTo((b) => b.now()),
|
|
12
|
+
)
|
|
10
13
|
.createIndex("idx_note_user", ["userId"]);
|
|
11
14
|
});
|
|
12
15
|
});
|