@fragno-dev/create 0.0.2

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.
@@ -0,0 +1,220 @@
1
+ # AGENTS.md
2
+
3
+ This file provides guidance for AI agents working with Fragno fragments. It contains architectural
4
+ information, development strategies, and practical approaches for building fragments.
5
+
6
+ ## Overview
7
+
8
+ A **Fragment** is a full-stack, framework-agnostic TypeScript library built with Fragno. Fragments
9
+ provide:
10
+
11
+ - Type-safe server-side API routes
12
+ - Automatic client-side hooks/composables for multiple frameworks (React, Vue, Svelte, Vanilla JS)
13
+ - Automatic code splitting between client and server bundles
14
+ - Built-in state management with reactive stores (TanStack Query-style:
15
+ `const {data, loading, error} = useData()`)
16
+
17
+ **Documentation**: Full documentation is available at https://fragno.dev/docs
18
+
19
+ ## Architecture
20
+
21
+ Fragments follow a core pattern:
22
+
23
+ 1. **Server-side**: Define routes with input/output schemas, handlers, and dependencies
24
+ 2. **Client-side**: Auto-generated type-safe hooks for each route
25
+ 3. **Code splitting**: Server-only code (handlers, dependencies) is stripped from client bundles
26
+
27
+ ## File Structure & Core Concepts
28
+
29
+ ### `src/index.ts` - Main Fragment Definition
30
+
31
+ This is the core file that contains the fragment definition, routes, dependencies, services, and
32
+ client builder.
33
+
34
+ **Key concepts defined in this file**:
35
+
36
+ **Fragment Definition** (`defineFragment`):
37
+
38
+ - Takes a config type parameter that defines what users must provide (API keys, callbacks, etc.)
39
+ - The fragment name is used in the URL path: `/api/<fragment-name>/...`
40
+
41
+ **Dependencies** (`.withDependencies()`):
42
+
43
+ - Server-side only (never included in client bundle)
44
+ - Private to the fragment, not accessible to users
45
+ - Access to config for initialization (e.g., API keys, database connections)
46
+ - Used in route handlers
47
+
48
+ **Services** (`.withServices()`):
49
+
50
+ - Server-side only (never included in client bundle)
51
+ - Public-facing API accessible to users via `fragment.services.methodName()`
52
+ - Access to both config and dependencies
53
+ - Useful for exposing utility methods to users
54
+
55
+ **Route Definition** (`defineRoute` and `defineRoutes`):
56
+
57
+ - `defineRoute`: Simple routes without dependencies
58
+ - `defineRoutes`: Route factory that has access to dependencies and services
59
+ - Route handler context:
60
+ - First parameter (input): `{ input, query, pathParams, request, url }`
61
+ - Second parameter (output): `{ json, jsonStream, empty, error }`
62
+ - `input.valid()` validates and returns parsed data (throws on validation error)
63
+
64
+ **Server-Side Fragment** (`createFragment`):
65
+
66
+ - Users call this function to instantiate the fragment on the server
67
+ - Returns an object with request handlers (`handler(request: Request) => Response`) and `services`
68
+
69
+ **Client-Side Builder** (`createClientBuilder`):
70
+
71
+ - Creates type-safe hooks for each route
72
+ - `createHook(path)`: For GET routes (returns `{ data, loading, error }`)
73
+ - `createMutator(method, path)`: For POST/PUT/PATCH/DELETE routes (returns
74
+ `{ data, loading, error, mutate }`)
75
+ - Advanced: Use `computed`, `atom` from `nanostores` for derived state
76
+
77
+ ### `src/client/*.ts` - Framework-Specific Exports
78
+
79
+ Each framework requires a separate client file that wraps the generic client builder with the
80
+ framework-specific `useFragno` hook. Check the `src/client/` directory for existing framework
81
+ implementations. Use the frameworks page on https://fragno.dev/docs/frameworks to see if all clients
82
+ have their stubs defined. Make sure to include new frameworks in the exports section of
83
+ package.json.
84
+
85
+ ### `package.json` - Package Configuration
86
+
87
+ The package.json defines multiple export paths for different frameworks and environments. Key
88
+ points:
89
+
90
+ - Main export (`.`) is server-side code
91
+ - Framework exports (`./react`, `./vue`, `./svelte`, `./vanilla`) use "browser" condition to load
92
+ client bundle
93
+ - Development mode uses source files for better debugging
94
+ - Production uses built files from `dist/`
95
+ - When adding new framework exports, add corresponding client files in `src/client/`
96
+
97
+ ## Strategies for Building Fragments
98
+
99
+ ### OpenAPI/Swagger Spec → Fragno Routes
100
+
101
+ Parse an OpenAPI specification and convert it to Fragno routes. Map HTTP methods to `defineRoute`,
102
+ convert path parameters (e.g., `/users/{id}` → `/users/:id`), convert JSON schemas to Zod schemas
103
+ for `inputSchema`/`outputSchema`, and generate handlers. Group related routes using `defineRoutes`
104
+ if they share dependencies. Extract error codes from OpenAPI error responses.
105
+
106
+ ### REST API Wrapper
107
+
108
+ Wrap an existing REST API with proper typing and error handling. Add HTTP client (fetch, axios) to
109
+ dependencies with API credentials from config. Create routes that proxy to API endpoints with proper
110
+ error handling and validation. Optionally add caching or rate limiting in services. This approach is
111
+ useful when you want to provide a type-safe interface to an existing API.
112
+
113
+ ### Third-Party SDK Integration
114
+
115
+ Wrap third-party SDKs (Stripe, OpenAI, Twilio, etc.) as fragments. Add SDK to dependencies with API
116
+ keys from config. Create routes that expose SDK functionality, transform SDK responses to match your
117
+ schemas, and handle SDK-specific errors and rate limits. Optionally expose the SDK client directly
118
+ in services for advanced users. Use streaming responses for real-time SDK features like AI chat.
119
+
120
+ ## Development Workflow
121
+
122
+ ### Building
123
+
124
+ Fragments require code splitting between client and server bundles using
125
+ `@fragno-dev/unplugin-fragno`. The plugin can also be imported for different kinds of build tools:
126
+ `/esbuild`, `/rollup`, `/webpack`, `/rspack`, `/farm`.
127
+
128
+ ### Type Checking
129
+
130
+ ```bash
131
+ bun run types:check
132
+ ```
133
+
134
+ ## Common Patterns
135
+
136
+ ### Streaming Responses
137
+
138
+ For real-time data (e.g., AI chat, large datasets), use `jsonStream` with `stream.write()` and
139
+ `stream.sleep()`. The output schema must be an array. Client-side: The `data` array is updated
140
+ reactively as chunks arrive.
141
+
142
+ ### Error Handling
143
+
144
+ - Always define `errorCodes` array in route definition
145
+ - Use structured errors: `error({ message: string, code: string }, statusCode)`
146
+ - `input.valid()` automatically throws validation errors (converted to 400 responses)
147
+ - Check for null/undefined before processing and return appropriate error codes
148
+
149
+ ### Create Callbacks
150
+
151
+ Allow users to react to events by including optional callback functions in your config:
152
+
153
+ ```typescript
154
+ interface FragmentConfig {
155
+ onDataCreated?: (data: Data) => void;
156
+ onError?: (error: Error) => void;
157
+ }
158
+ ```
159
+
160
+ Call them in handlers after operations complete: `config.onDataCreated?.(data);`
161
+
162
+ ## Using Your Fragment in Other Projects
163
+
164
+ Once you've built and published your fragment, users can integrate it into their projects. The
165
+ integration has two parts:
166
+
167
+ ### 1. Server-Side Setup
168
+
169
+ Create a server instance of your fragment (e.g., in `lib/fragment-server.ts`):
170
+
171
+ ```typescript
172
+ import { createFragment } from "your-fragment-name";
173
+
174
+ export const createFragmentInstance = () =>
175
+ createFragment({
176
+ // Fragment-specific configuration here
177
+ apiKey: process.env.API_KEY,
178
+ });
179
+ ```
180
+
181
+ Then mount it as a route handler. For example, in Next.js:
182
+
183
+ ```typescript
184
+ // app/api/your-fragment/[...all]/route.ts
185
+ import { createFragmentInstance } from "@/lib/fragment-server";
186
+
187
+ const fragment = createFragmentInstance();
188
+ export const { GET, POST, PUT, PATCH, DELETE } = fragment.handlersFor("next-js");
189
+ ```
190
+
191
+ ### 2. Client-Side Setup
192
+
193
+ Initialize the client in your app (e.g., React):
194
+
195
+ ```typescript
196
+ // lib/fragment-client.ts
197
+ import { createFragmentClient } from "your-fragment-name/react";
198
+
199
+ export const { useData, useMutateData } = createFragmentClient({
200
+ // Optional Fragno configuration
201
+ baseUrl: "/",
202
+ mountRoute: "/api/your-fragment",
203
+ });
204
+ ```
205
+
206
+ Then use the generated hooks in your components:
207
+
208
+ ```tsx
209
+ import { useData } from "@/lib/fragment-client";
210
+
211
+ function MyComponent() {
212
+ const { data, loading, error } = useData({
213
+ /* input */
214
+ });
215
+
216
+ if (loading) return <div>Loading...</div>;
217
+ if (error) return <div>Error: {error.message}</div>;
218
+ return <div>{JSON.stringify(data)}</div>;
219
+ }
220
+ ```
@@ -0,0 +1,16 @@
1
+ # Fragno Fragment
2
+
3
+ You've created a new [Fragno](https://fragno.dev/) fragment!
4
+
5
+ ## Build
6
+
7
+ ```bash
8
+ npm run types:check
9
+ npm run build
10
+ ```
11
+
12
+ ## Next Steps
13
+
14
+ - Define your routes in `src/index.ts`
15
+ - Add framework-specific clients in `src/client/`
16
+ - See `AGENTS.md` for detailed development patterns
@@ -0,0 +1,65 @@
1
+ {
2
+ "description": "A Fragno fragment",
3
+ "version": "0.0.1",
4
+ "files": ["dist"],
5
+ "keywords": ["fragno", "typescript", "react", "vue", "svelte"],
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/node/index.d.ts",
9
+ "default": "./dist/node/index.js"
10
+ },
11
+ "./react": {
12
+ "development": {
13
+ "browser": "./dist/browser/client/react.js",
14
+ "default": "./src/client/react.ts"
15
+ },
16
+ "types": "./dist/browser/client/react.d.ts",
17
+ "default": "./dist/browser/client/react.js"
18
+ },
19
+ "./vue": {
20
+ "development": {
21
+ "browser": "./dist/browser/client/vue.js",
22
+ "default": "./src/client/vue.ts"
23
+ },
24
+ "types": "./dist/browser/client/vue.d.ts",
25
+ "default": "./dist/browser/client/vue.js"
26
+ },
27
+ "./svelte": {
28
+ "development": {
29
+ "browser": "./dist/browser/client/svelte.js",
30
+ "default": "./src/client/svelte.ts"
31
+ },
32
+ "types": "./dist/browser/client/svelte.d.ts",
33
+ "default": "./dist/browser/client/svelte.js"
34
+ },
35
+ "./vanilla": {
36
+ "development": {
37
+ "browser": "./dist/browser/client/vanilla.js",
38
+ "default": "./src/client/vanilla.ts"
39
+ },
40
+ "types": "./dist/browser/client/vanilla.d.ts",
41
+ "default": "./dist/browser/client/vanilla.js"
42
+ }
43
+ },
44
+ "main": "./dist/node/index.js",
45
+ "module": "./dist/node/index.js",
46
+ "types": "./dist/node/index.d.ts",
47
+ "scripts": {
48
+ "types:check": "tsc --noEmit"
49
+ },
50
+ "type": "module",
51
+ "dependencies": {
52
+ "@fragno-dev/core": "^0.0.6",
53
+ "zod": "^4.0.5"
54
+ },
55
+ "devDependencies": {
56
+ "@types/node": "^20"
57
+ },
58
+ "private": true,
59
+ "peerDependencies": {
60
+ "typescript": "^5",
61
+ "react": ">=18.0.0",
62
+ "svelte": ">=4.0.0",
63
+ "vue": ">=3.0.0"
64
+ }
65
+ }
@@ -0,0 +1,7 @@
1
+ import { useFragno } from "@fragno-dev/core/react";
2
+ import { createExampleFragmentClients } from "..";
3
+ import type { FragnoPublicClientConfig } from "@fragno-dev/core";
4
+
5
+ export function createExampleFragmentClient(config: FragnoPublicClientConfig = {}) {
6
+ return useFragno(createExampleFragmentClients(config));
7
+ }
@@ -0,0 +1,7 @@
1
+ import { useFragno } from "@fragno-dev/core/svelte";
2
+ import { createExampleFragmentClients } from "..";
3
+ import type { FragnoPublicClientConfig } from "@fragno-dev/core";
4
+
5
+ export function createExampleFragmentClient(config: FragnoPublicClientConfig = {}) {
6
+ return useFragno(createExampleFragmentClients(config));
7
+ }
@@ -0,0 +1,7 @@
1
+ import { useFragno } from "@fragno-dev/core/vanilla";
2
+ import { createExampleFragmentClients } from "..";
3
+ import type { FragnoPublicClientConfig } from "@fragno-dev/core";
4
+
5
+ export function createExampleFragmentClient(config: FragnoPublicClientConfig = {}) {
6
+ return useFragno(createExampleFragmentClients(config));
7
+ }
@@ -0,0 +1,7 @@
1
+ import { useFragno } from "@fragno-dev/core/vue";
2
+ import { createExampleFragmentClients } from "..";
3
+ import type { FragnoPublicClientConfig } from "@fragno-dev/core";
4
+
5
+ export function createExampleFragmentClient(config: FragnoPublicClientConfig = {}) {
6
+ return useFragno(createExampleFragmentClients(config));
7
+ }
@@ -0,0 +1,94 @@
1
+ import {
2
+ defineFragment,
3
+ defineRoute,
4
+ defineRoutes,
5
+ createFragment,
6
+ type FragnoPublicClientConfig,
7
+ type FragnoPublicConfig,
8
+ } from "@fragno-dev/core";
9
+ import { createClientBuilder } from "@fragno-dev/core/client";
10
+
11
+ // NOTE: We use zod here for defining schemas, but any StandardSchema library can be used!
12
+ // For a complete list see:
13
+ // https://github.com/standard-schema/standard-schema#what-schema-libraries-implement-the-spec
14
+ import { z } from "zod";
15
+
16
+ export interface ExampleFragmentServerConfig {
17
+ initialData?: string;
18
+ }
19
+
20
+ type ExampleRouteConfig = {
21
+ initialData: string;
22
+ };
23
+
24
+ type ExampleRouteDeps = {
25
+ serverSideData: { value: string };
26
+ };
27
+
28
+ const exampleRoutesFactory = defineRoutes<ExampleRouteConfig, ExampleRouteDeps>().create(
29
+ ({ deps }) => {
30
+ const { serverSideData } = deps;
31
+
32
+ return [
33
+ defineRoute({
34
+ method: "GET",
35
+ path: "/hello",
36
+ outputSchema: z.string(),
37
+ handler: async (_, { json }) => {
38
+ return json(serverSideData.value);
39
+ },
40
+ }),
41
+
42
+ defineRoute({
43
+ method: "POST",
44
+ path: "/hello",
45
+ inputSchema: z.object({ message: z.string() }),
46
+ outputSchema: z.string(),
47
+ errorCodes: [],
48
+ handler: async ({ input }, { json }) => {
49
+ const { message } = await input.valid();
50
+ serverSideData.value = message;
51
+ return json(message);
52
+ },
53
+ }),
54
+ ];
55
+ },
56
+ );
57
+
58
+ const exampleFragmentDefinition = defineFragment<ExampleFragmentServerConfig>("example-fragment")
59
+ .withDependencies((config: ExampleFragmentServerConfig) => {
60
+ return {
61
+ serverSideData: { value: config.initialData ?? "Hello World! This is a server-side data." },
62
+ };
63
+ })
64
+ .withServices((_cfg, deps) => {
65
+ return {
66
+ getData: () => deps.serverSideData.value,
67
+ };
68
+ });
69
+
70
+ export function createExampleFragment(
71
+ serverConfig: ExampleFragmentServerConfig = {},
72
+ fragnoConfig: FragnoPublicConfig = {},
73
+ ) {
74
+ const config: ExampleRouteConfig = {
75
+ initialData: serverConfig.initialData ?? "Hello World! This is a server-side data.",
76
+ };
77
+
78
+ return createFragment(
79
+ exampleFragmentDefinition,
80
+ { ...serverConfig, ...config },
81
+ [exampleRoutesFactory],
82
+ fragnoConfig,
83
+ );
84
+ }
85
+
86
+ export function createExampleFragmentClients(fragnoConfig: FragnoPublicClientConfig) {
87
+ const b = createClientBuilder(exampleFragmentDefinition, fragnoConfig, [exampleRoutesFactory]);
88
+
89
+ return {
90
+ useHello: b.createHook("/hello"),
91
+ useHelloMutator: b.createMutator("POST", "/hello"),
92
+ };
93
+ }
94
+ export type { FragnoRouteConfig } from "@fragno-dev/core/api";
@@ -0,0 +1,34 @@
1
+ {
2
+ "compilerOptions": {
3
+ // Enable latest features
4
+ "target": "ESNext",
5
+ "module": "ESNext",
6
+ "moduleDetection": "force",
7
+ "jsx": "react-jsx",
8
+ "allowJs": false,
9
+ // Bundler mode
10
+ "moduleResolution": "bundler",
11
+ "verbatimModuleSyntax": true,
12
+ "allowImportingTsExtensions": true,
13
+ // Project reference specific settings
14
+ "composite": true,
15
+ "declaration": true,
16
+ "declarationMap": true,
17
+ "sourceMap": true,
18
+ "rewriteRelativeImportExtensions": true,
19
+ "emitDeclarationOnly": false,
20
+ "isolatedDeclarations": false,
21
+ "isolatedModules": true,
22
+ // Best practices
23
+ "strict": true,
24
+ "skipLibCheck": true,
25
+ "noFallthroughCasesInSwitch": true,
26
+ "noImplicitReturns": true,
27
+ // Some stricter flags
28
+ "noUnusedParameters": true,
29
+ "noPropertyAccessFromIndexSignature": true,
30
+ // Misc
31
+ "esModuleInterop": true,
32
+ "noErrorTruncation": true
33
+ }
34
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "./tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": ".",
6
+ "composite": true
7
+ },
8
+ "include": ["src"],
9
+ "exclude": ["node_modules", "dist"]
10
+ }
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ import { build } from "esbuild";
3
+ import unpluginFragno from "@fragno-dev/unplugin-fragno/esbuild";
4
+
5
+ build({
6
+ entryPoints: [
7
+ "./src/index.ts",
8
+ "./src/client/react.ts",
9
+ "./src/client/svelte.ts",
10
+ "./src/client/vanilla.ts",
11
+ "./src/client/vue.ts",
12
+ ],
13
+ outdir: "./dist/browser",
14
+ bundle: true,
15
+ format: "esm",
16
+ platform: "browser",
17
+ target: "es2020",
18
+ splitting: true,
19
+ sourcemap: true,
20
+ plugins: [unpluginFragno({ platform: "browser" })],
21
+ external: ["react", "svelte", "vue", "zod"],
22
+ });
23
+
24
+ build({
25
+ entryPoints: ["./src/index.ts"],
26
+ outdir: "./dist/node",
27
+ bundle: true,
28
+ format: "esm",
29
+ platform: "node",
30
+ target: "node18",
31
+ sourcemap: true,
32
+ plugins: [unpluginFragno({ platform: "node" })],
33
+ external: ["zod"],
34
+ });
@@ -0,0 +1,58 @@
1
+ import typescript from "@rollup/plugin-typescript";
2
+ import unpluginFragno from "@fragno-dev/unplugin-fragno/rollup";
3
+ import resolve from "@rollup/plugin-node-resolve";
4
+
5
+ export default [
6
+ // Browser build
7
+ {
8
+ input: {
9
+ index: "./src/index.ts",
10
+ "client/react": "./src/client/react.ts",
11
+ "client/svelte": "./src/client/svelte.ts",
12
+ "client/vanilla": "./src/client/vanilla.ts",
13
+ "client/vue": "./src/client/vue.ts",
14
+ },
15
+ output: {
16
+ dir: "./dist/browser",
17
+ format: "es",
18
+ sourcemap: true,
19
+ },
20
+ // https://rollupjs.org/tools/#peer-dependencies
21
+ external: ["zod", "react", "svelte", "vue"],
22
+ plugins: [
23
+ resolve({
24
+ moduleDirectories: ["node_modules"],
25
+ browser: true,
26
+ }),
27
+ typescript({
28
+ tsconfig: "./tsconfig.json",
29
+ declaration: true,
30
+ outDir: "./dist/browser",
31
+ declarationDir: "./dist/browser",
32
+ }),
33
+ unpluginFragno({ platform: "browser" }),
34
+ ],
35
+ },
36
+ // Node build
37
+ {
38
+ input: "./src/index.ts",
39
+ output: {
40
+ dir: "./dist/node",
41
+ format: "es",
42
+ sourcemap: true,
43
+ },
44
+ external: ["zod"],
45
+ plugins: [
46
+ resolve({
47
+ moduleDirectories: ["node_modules"],
48
+ }),
49
+ typescript({
50
+ tsconfig: "./tsconfig.json",
51
+ declaration: true,
52
+ outDir: "./dist/node",
53
+ declarationDir: "./dist/node",
54
+ }),
55
+ unpluginFragno({ platform: "node" }),
56
+ ],
57
+ },
58
+ ];
@@ -0,0 +1,93 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import unpluginFragno from "@fragno-dev/unplugin-fragno/rspack";
4
+
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+
7
+ /** @type {import('@rspack/core').Configuration[]} */
8
+ export default [
9
+ // Browser build
10
+ {
11
+ name: "browser",
12
+ mode: "production",
13
+ entry: {
14
+ index: "./src/index.ts",
15
+ "client/react": "./src/client/react.ts",
16
+ "client/svelte": "./src/client/svelte.ts",
17
+ "client/vanilla": "./src/client/vanilla.ts",
18
+ "client/vue": "./src/client/vue.ts",
19
+ },
20
+ output: {
21
+ path: path.resolve(__dirname, "dist/browser"),
22
+ filename: "[name].js",
23
+ library: {
24
+ type: "module",
25
+ },
26
+ },
27
+ experiments: {
28
+ outputModule: true,
29
+ },
30
+ resolve: {
31
+ extensions: [".ts", ".js"],
32
+ },
33
+ module: {
34
+ rules: [
35
+ {
36
+ test: /\.ts$/,
37
+ use: "builtin:swc-loader",
38
+ exclude: /node_modules/,
39
+ options: {
40
+ jsc: {
41
+ parser: {
42
+ syntax: "typescript",
43
+ },
44
+ },
45
+ },
46
+ },
47
+ ],
48
+ },
49
+ plugins: [unpluginFragno({ platform: "browser" })],
50
+ devtool: "source-map",
51
+ externals: ["zod", "react", "vue", "svelte"],
52
+ },
53
+ // Node build
54
+ {
55
+ name: "node",
56
+ mode: "production",
57
+ entry: {
58
+ index: "./src/index.ts",
59
+ },
60
+ output: {
61
+ path: path.resolve(__dirname, "dist/node"),
62
+ filename: "[name].js",
63
+ library: {
64
+ type: "module",
65
+ },
66
+ },
67
+ experiments: {
68
+ outputModule: true,
69
+ },
70
+ resolve: {
71
+ extensions: [".ts", ".js"],
72
+ },
73
+ module: {
74
+ rules: [
75
+ {
76
+ test: /\.ts$/,
77
+ use: "builtin:swc-loader",
78
+ exclude: /node_modules/,
79
+ options: {
80
+ jsc: {
81
+ parser: {
82
+ syntax: "typescript",
83
+ },
84
+ },
85
+ },
86
+ },
87
+ ],
88
+ },
89
+ plugins: [unpluginFragno({ platform: "node" })],
90
+ devtool: "source-map",
91
+ externals: ["zod"],
92
+ },
93
+ ];