@danieljvdm/dev-kit 0.11.3 → 0.12.0

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.
Files changed (74) hide show
  1. package/README.md +70 -76
  2. package/dev-kit.example.jsonc +0 -4
  3. package/package.json +10 -6
  4. package/schema/dev-kit.schema.json +1 -46
  5. package/skills/build-effect-apis/SKILL.md +77 -0
  6. package/skills/build-effect-apis/agents/openai.yaml +4 -0
  7. package/skills/build-effect-apis/references/cloudflare-workers.md +71 -0
  8. package/skills/build-effect-apis/references/effect-atom-client.md +161 -0
  9. package/skills/build-effect-apis/references/effect-atom-lifecycle.md +78 -0
  10. package/skills/build-effect-apis/references/effect-atom-testing.md +74 -0
  11. package/skills/build-effect-apis/references/runtime-assembly.md +56 -0
  12. package/skills/build-effect-apis/references/server-and-middleware.md +174 -0
  13. package/skills/build-effect-apis/references/shared-contracts.md +108 -0
  14. package/skills/build-effect-apis/references/tanstack-start.md +86 -0
  15. package/skills/build-effect-apis/references/verification.md +50 -0
  16. package/skills/dev-kit/SKILL.md +58 -46
  17. package/skills/effect-architecture-audit/SKILL.md +26 -0
  18. package/skills/effect-architecture-audit/agents/openai.yaml +4 -0
  19. package/skills/effect-architecture-audit/references/service-and-boundary-audit.md +150 -0
  20. package/skills/effect-ts/SKILL.md +21 -256
  21. package/skills/effect-ts/agents/openai.yaml +3 -3
  22. package/skills/testing/SKILL.md +5 -0
  23. package/src/catalog-manager.ts +16 -17
  24. package/src/catalog.ts +71 -16
  25. package/src/effect-source.ts +46 -24
  26. package/src/effect-tsgo.ts +49 -24
  27. package/src/gitignore.ts +5 -5
  28. package/src/index.ts +0 -6
  29. package/src/manifest.ts +0 -34
  30. package/src/node-symbolic-link.ts +2 -2
  31. package/src/oxfmt.js +5 -0
  32. package/src/oxfmt.ts +5 -0
  33. package/src/oxlint.js +5 -0
  34. package/src/oxlint.ts +5 -0
  35. package/src/package-skill-source.ts +51 -59
  36. package/src/path-digest.ts +7 -7
  37. package/src/project-package.ts +8 -7
  38. package/src/project-process-lock.ts +17 -12
  39. package/src/project-state.ts +1 -1
  40. package/src/skill-manager.ts +16 -14
  41. package/src/skill-selector.ts +12 -0
  42. package/src/sync.ts +170 -120
  43. package/src/tool-ignore-patterns.js +9 -0
  44. package/src/tool-ignore-patterns.ts +15 -0
  45. package/src/vendor.ts +67 -61
  46. package/src/vite-plus-dependency.ts +10 -11
  47. package/src/vite-plus-hooks.ts +24 -14
  48. package/src/vite-plus-quality.ts +21 -172
  49. package/src/vite-plus.js +81 -0
  50. package/src/vite-plus.ts +102 -0
  51. package/templates/AGENTS.md +1 -1
  52. package/skills/effect-ts/UPSTREAM.md +0 -28
  53. package/skills/effect-ts/references/atom-cache-lifecycle.md +0 -78
  54. package/skills/effect-ts/references/atom-http-and-invalidation.md +0 -97
  55. package/skills/effect-ts/references/atom-tanstack-start.md +0 -69
  56. package/skills/effect-ts/references/atom-testing.md +0 -67
  57. package/skills/effect-ts/references/audit-services.md +0 -144
  58. package/skills/effect-ts/references/features.md +0 -525
  59. package/skills/effect-ts/references/guide-atom-data-fetching.md +0 -44
  60. package/skills/effect-ts/references/guide-cli.md +0 -107
  61. package/skills/effect-ts/references/guide-datetime.md +0 -72
  62. package/skills/effect-ts/references/guide-effect.md +0 -440
  63. package/skills/effect-ts/references/guide-error-handling.md +0 -565
  64. package/skills/effect-ts/references/guide-http-boundaries.md +0 -55
  65. package/skills/effect-ts/references/guide-layers.md +0 -989
  66. package/skills/effect-ts/references/guide-observability.md +0 -746
  67. package/skills/effect-ts/references/guide-retries.md +0 -434
  68. package/skills/effect-ts/references/guide-schedule.md +0 -343
  69. package/skills/effect-ts/references/guide-schema.md +0 -664
  70. package/skills/effect-ts/references/guide-sql.md +0 -536
  71. package/skills/effect-ts/references/guide-testing.md +0 -532
  72. package/skills/effect-ts/references/guide-type-safety-and-boundaries.md +0 -131
  73. package/skills/effect-ts/references/version-and-source.md +0 -86
  74. package/templates/vite-plus/vite.config.ts +0 -22
package/README.md CHANGED
@@ -198,9 +198,9 @@ tool versions. A project-local process lock also prevents concurrent applies.
198
198
  ```
199
199
 
200
200
  - `dev-kit` installs guidance for operating the toolkit itself.
201
- - `effect` expands to the `effect-ts` umbrella, whose lazy references include
202
- Effect Atom HTTP data fetching and Effect DateTime guidance. The former
203
- `effect-atom-data-fetching` selector remains an alias for compatibility.
201
+ - `effect` expands to the package-guidance `effect-ts` bootstrap, the
202
+ opinionated `effect-architecture-audit`, and `build-effect-apis` for shared
203
+ HTTP contracts, Effect Atom clients, TanStack Start, and Cloudflare Workers.
204
204
  - Prefer individual external skills such as `workers-best-practices` and
205
205
  `wrangler`, selected after scanning the project for relevant technologies.
206
206
  - `serve-sim` selects the approved Evan Bacon simulator skill directly.
@@ -284,9 +284,35 @@ worktree converges its own copy during install while the project-owned
284
284
 
285
285
  ## Vite+ quality setup
286
286
 
287
- Vite+/Effect repositories opt into the canonical Vite config and GitHub Actions
287
+ The repository always owns `vite.config.ts`. Compose Dev Kit's quality defaults
288
+ from that project-owned config, then opt into the hardened GitHub Actions
288
289
  workflow independently:
289
290
 
291
+ ```ts
292
+ import { createRecommendedVitePlusConfig } from "@danieljvdm/dev-kit/vite-plus";
293
+ import { defineConfig } from "vite-plus";
294
+
295
+ const recommended = createRecommendedVitePlusConfig({
296
+ ignorePatterns: ["apps/api/worker-configuration.d.ts", "apps/web/src/routeTree.gen.ts"],
297
+ });
298
+
299
+ export default defineConfig({
300
+ ...recommended,
301
+ // Project-owned Vite, test, build, and framework options stay local.
302
+ server: { port: 5173 },
303
+ });
304
+ ```
305
+
306
+ Spread the returned top-level config before local options. When overriding a
307
+ `fmt`, `lint`, `run`, or `staged` block, spread that returned block as well so
308
+ its defaults remain composed.
309
+
310
+ The factory configures `vp staged`, matching Oxlint/Oxfmt ignores for Dev Kit's
311
+ tool-owned paths, and separate `vp run check` and pure `vp run typecheck` tasks.
312
+ Project and framework-generated paths belong in `ignorePatterns` as shown;
313
+ custom harness target paths belong there too. Dev Kit does not grow a global
314
+ framework ignore list.
315
+
290
316
  ```jsonc
291
317
  {
292
318
  "include": ["dev-kit", "effect"],
@@ -295,7 +321,6 @@ workflow independently:
295
321
  "vitePlus": {
296
322
  "hooks": { "enabled": true },
297
323
  "quality": {
298
- "config": { "enabled": true },
299
324
  "workflow": { "enabled": true },
300
325
  },
301
326
  },
@@ -303,47 +328,28 @@ workflow independently:
303
328
  }
304
329
  ```
305
330
 
306
- `quality.config.enabled` owns `vite.config.ts`; `quality.workflow.enabled` owns
307
- `.github/workflows/check.yml`. Selecting one does not impose the other resource's
308
- config, scripts, or TypeScript topology. Both require direct
331
+ `quality.workflow.enabled` owns `.github/workflows/check.yml` but never reads,
332
+ rewrites, adopts, or removes `vite.config.ts`. Workflow setup requires direct
309
333
  `@danieljvdm/dev-kit`, `vite-plus`, `effect`, `@effect/tsgo`, and native
310
334
  TypeScript dependencies with `setup.effectTsgo.enabled`. The installed Vite+
311
335
  must satisfy Dev Kit's peer range.
312
336
 
313
- The managed Vite config composes the shared Oxfmt and Oxlint presets, configures
314
- `vp staged`, and defines separate `vp run check` and pure `vp run typecheck`
315
- tasks. It rejects conflicting root scripts. The default `single-project`
316
- strategy requires a root `tsconfig.json` without project references. Workspaces
317
- can instead select Vite Task orchestration explicitly:
337
+ Workspaces select bounded, dependency-ordered typechecking in their config:
318
338
 
319
- ```jsonc
320
- {
321
- "setup": {
322
- "effectTsgo": { "enabled": true },
323
- "vitePlus": {
324
- "quality": {
325
- "config": {
326
- "enabled": true,
327
- "typecheck": {
328
- "strategy": "workspace",
329
- "concurrency": 4,
330
- "packages": ["apps/web", "packages/core"],
331
- },
332
- },
333
- },
339
+ ```ts
340
+ export default defineConfig(
341
+ createRecommendedVitePlusConfig({
342
+ typecheck: {
343
+ strategy: "workspace",
344
+ concurrency: 4,
345
+ packages: ["apps/web", "packages/core"],
334
346
  },
335
- },
336
- }
347
+ }),
348
+ );
337
349
  ```
338
350
 
339
- Workspace mode requires `package.json#workspaces` and an explicit list of
340
- package directories with pure `typecheck` scripts. The generated task uses
341
- dependency order, caching, `--fail-if-no-match`, and bounded concurrency.
342
- Project-reference builds remain custom because their `.tsbuildinfo` inputs and
343
- outputs are repository-specific.
344
-
345
- Repositories with a custom Vite config can still adopt only the hardened CI
346
- workflow and declare repository-specific preparation and typecheck commands:
351
+ Each listed package must expose a pure `typecheck` script. Repositories can
352
+ also declare workflow-specific preparation and typecheck commands:
347
353
 
348
354
  ```jsonc
349
355
  {
@@ -370,16 +376,12 @@ workflow and declare repository-specific preparation and typecheck commands:
370
376
  }
371
377
  ```
372
378
 
373
- Workflow-only setup does not validate or take ownership of the Vite config,
374
- scripts, or TypeScript topology. It performs one frozen, script-suppressed
375
- install, runs `dev-kit apply --locked`, and only then runs custom preparation,
376
- formatting, linting, tests, and typechecking. Its default typecheck command is
377
- `vp run typecheck`; `workflow.typecheck` replaces it. Vite+ maps install flags
378
- to the detected package manager. The template installs the consumer's declared
379
- Bun version, follows the maintained `setup-bun@v2` tag, and names the current
380
- `setup-vp` release because that action's `v1` tag is frozen. Existing
381
- workflows remain user-owned until their rendered content matches exactly—Dev
382
- Kit never merges YAML. See the primary
379
+ The workflow performs one frozen, script-suppressed install, runs
380
+ `dev-kit apply --locked`, and only then runs preparation, formatting, linting,
381
+ tests, and typechecking. Its default typecheck command is `vp run typecheck`;
382
+ `workflow.typecheck` replaces it. Existing workflows remain user-owned until
383
+ their rendered content matches exactly—Dev Kit never merges YAML. See the
384
+ primary
383
385
  [`setup-vp` versioning guidance](https://github.com/voidzero-dev/setup-vp#versioning),
384
386
  [Vite+ install guide](https://viteplus.dev/guide/install), and
385
387
  [Vite Task run guide](https://viteplus.dev/guide/run) when maintaining the
@@ -427,7 +429,7 @@ Pin the compatible packages in the consuming project:
427
429
  {
428
430
  "devDependencies": {
429
431
  "@danieljvdm/dev-kit": "^0.2.0",
430
- "@effect/tsgo": "0.24.3",
432
+ "@effect/tsgo": "0.33.0",
431
433
  "typescript": "7.0.2",
432
434
  },
433
435
  }
@@ -477,8 +479,16 @@ bun x dev-kit add @tanstack/ai#ai-core
477
479
  That writes `@tanstack/ai#ai-core` to `dev-kit.jsonc` and, unless
478
480
  `--no-apply` is passed, installs it through the normal ownership-safe sync
479
481
  path. The qualifier prevents ambiguity when two dependencies publish the same
480
- skill name. Two selected skills that would both write the same destination are
481
- rejected before any output is changed.
482
+ skill name, and the installed output carries it too: the copied directory is
483
+ named by flattening the package name (drop `@`, turn every other
484
+ non-alphanumeric run into one dash) and appending the skill name, so
485
+ `@tanstack/ai#ai-core` installs as `tanstack-ai-ai-core`. Agent harnesses
486
+ identify a project skill by its directory name, so the copied `SKILL.md`
487
+ frontmatter `name:` is rewritten to that same install name; all other content
488
+ is copied verbatim. Symlink-mode targets link straight into `node_modules`, so
489
+ only the link itself carries the qualified name while the linked frontmatter
490
+ keeps the upstream bare name. Two selected skills that would both write the
491
+ same destination are rejected before any output is changed.
482
492
 
483
493
  The initial compatibility boundary is intentionally small and deterministic:
484
494
 
@@ -496,9 +506,9 @@ and frontmatter-name invariants expected by Agent Skills targets. Dev Kit does
496
506
  not rewrite nested names or ask Intent to manage agent configuration.
497
507
 
498
508
  The project `dev-kit.lock.json` records the selected package name, installed
499
- version, skill name, and content digest. `apply --locked` therefore rejects
500
- package-version or skill-content drift. Dev Kit never downloads a missing
501
- package or substitutes a registry version.
509
+ version, original bare skill name, and the `node_modules` content digest.
510
+ `apply --locked` therefore rejects package-version or skill-content drift.
511
+ Dev Kit never downloads a missing package or substitutes a registry version.
502
512
 
503
513
  See TanStack's
504
514
  [Agent Skills documentation](https://tanstack.com/ai/latest/docs/getting-started/agent-skills)
@@ -554,34 +564,18 @@ approved Git content.
554
564
 
555
565
  ## Oxlint and Oxfmt configurations
556
566
 
557
- Dev Kit exports one typed Oxlint ruleset and one typed Oxfmt configuration for
558
- both standalone Oxc projects and Vite+ projects. A Vite+ project composes them
559
- in `vite.config.ts`:
567
+ Dev Kit exports typed Oxlint/Oxfmt presets for standalone Oxc projects. The
568
+ Vite+ factory composes both presets and keeps their tool-path ignores aligned:
560
569
 
561
570
  ```ts
562
- import { recommendedOxlintConfig } from "@danieljvdm/dev-kit/oxlint";
563
- import { recommendedOxfmtConfig } from "@danieljvdm/dev-kit/oxfmt";
571
+ import { createRecommendedVitePlusConfig } from "@danieljvdm/dev-kit/vite-plus";
564
572
  import { defineConfig } from "vite-plus";
565
573
 
566
- export default defineConfig({
567
- fmt: {
568
- ...recommendedOxfmtConfig,
569
- },
570
- lint: {
571
- extends: [recommendedOxlintConfig],
572
- rules: {
573
- // Add repository-specific rules here.
574
- },
575
- },
576
- });
574
+ export default defineConfig(createRecommendedVitePlusConfig());
577
575
  ```
578
576
 
579
- Use `lint.extends` rather than a shallow object spread so Vite+ composes the
580
- nested plugin and rule configuration correctly. Oxfmt has no `extends`, so
581
- spread its configuration before project-local formatter options. The shared
582
- lint preset enables `typeAware` for semantic lint rules but leaves `typeCheck`
583
- disabled. Managed quality config uses this composition; custom Vite configs
584
- compose the exports manually. Effect TypeScript-Go projects run:
577
+ The shared lint preset enables `typeAware` for semantic lint rules but leaves
578
+ `typeCheck` disabled. Effect TypeScript-Go projects run:
585
579
 
586
580
  ```sh
587
581
  vp fmt --check
@@ -9,10 +9,6 @@
9
9
  "vitePlus": {
10
10
  "hooks": { "enabled": true },
11
11
  "quality": {
12
- "config": {
13
- "enabled": true,
14
- "typecheck": { "strategy": "single-project" },
15
- },
16
12
  "workflow": { "enabled": true },
17
13
  },
18
14
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danieljvdm/dev-kit",
3
- "version": "0.11.3",
3
+ "version": "0.12.0",
4
4
  "private": false,
5
5
  "description": "Declarative project development toolkit with portable agent skills.",
6
6
  "license": "MIT",
@@ -38,6 +38,10 @@
38
38
  "types": "./src/oxfmt.ts",
39
39
  "default": "./src/oxfmt.js"
40
40
  },
41
+ "./vite-plus": {
42
+ "types": "./src/vite-plus.ts",
43
+ "default": "./src/vite-plus.js"
44
+ },
41
45
  "./oxlint-plugin-effect": {
42
46
  "types": "./src/oxlint-plugin-effect.d.ts",
43
47
  "default": "./src/oxlint-plugin-effect.js"
@@ -65,17 +69,17 @@
65
69
  "catalog:check": "./bin/dev-kit.mjs catalog verify"
66
70
  },
67
71
  "dependencies": {
68
- "@effect/platform-bun": "4.0.0-beta.102",
72
+ "@effect/platform-bun": "4.0.0-beta.105",
69
73
  "@stylistic/eslint-plugin": "5.10.0",
70
- "effect": "4.0.0-beta.102",
74
+ "effect": "4.0.0-beta.105",
71
75
  "jsonc-parser": "3.3.1",
72
76
  "semver": "7.8.5"
73
77
  },
74
78
  "devDependencies": {
75
79
  "@changesets/cli": "3.0.0-next.10",
76
- "@effect/platform-node": "4.0.0-beta.102",
77
- "@effect/tsgo": "0.24.3",
78
- "@effect/vitest": "4.0.0-beta.102",
80
+ "@effect/platform-node": "4.0.0-beta.105",
81
+ "@effect/tsgo": "0.33.0",
82
+ "@effect/vitest": "4.0.0-beta.105",
79
83
  "@types/node": "25.9.1",
80
84
  "@types/semver": "7.8.0",
81
85
  "oxfmt": "0.60.0",
@@ -161,32 +161,15 @@
161
161
  }
162
162
  },
163
163
  "vitePlusQualitySetup": {
164
- "description": "Opt into independently managed Vite+ quality resources for supported Effect TypeScript-Go repositories.",
164
+ "description": "Manage the Vite+ quality workflow while the repository owns and extends vite.config.ts.",
165
165
  "type": "object",
166
166
  "additionalProperties": false,
167
167
  "properties": {
168
- "config": {
169
- "$ref": "#/$defs/vitePlusQualityConfigSetup"
170
- },
171
168
  "workflow": {
172
169
  "$ref": "#/$defs/vitePlusQualityWorkflowSetup"
173
170
  }
174
171
  }
175
172
  },
176
- "vitePlusQualityConfigSetup": {
177
- "description": "Manage the canonical root Vite config without requiring Dev Kit to own CI.",
178
- "type": "object",
179
- "additionalProperties": false,
180
- "properties": {
181
- "enabled": {
182
- "type": "boolean",
183
- "default": false
184
- },
185
- "typecheck": {
186
- "$ref": "#/$defs/vitePlusQualityTypecheckSetup"
187
- }
188
- }
189
- },
190
173
  "vitePlusQualityWorkflowSetup": {
191
174
  "description": "Manage the canonical GitHub Actions check workflow without requiring Dev Kit to own the Vite config.",
192
175
  "type": "object",
@@ -235,34 +218,6 @@
235
218
  }
236
219
  }
237
220
  },
238
- "vitePlusQualityTypecheckSetup": {
239
- "description": "Select the managed typecheck topology. Single-project is the default; workspace mode runs explicitly scoped package scripts through Vite Task.",
240
- "type": "object",
241
- "additionalProperties": false,
242
- "properties": {
243
- "strategy": {
244
- "enum": ["single-project", "workspace"],
245
- "default": "single-project"
246
- },
247
- "concurrency": {
248
- "description": "Maximum concurrent workspace typecheck tasks.",
249
- "type": "integer",
250
- "minimum": 1,
251
- "maximum": 32,
252
- "default": 4
253
- },
254
- "packages": {
255
- "description": "Project-relative workspace package directories whose typecheck scripts should run.",
256
- "type": "array",
257
- "items": {
258
- "type": "string",
259
- "minLength": 1
260
- },
261
- "uniqueItems": true,
262
- "default": []
263
- }
264
- }
265
- },
266
221
  "target": {
267
222
  "type": "object",
268
223
  "additionalProperties": false,
@@ -0,0 +1,77 @@
1
+ ---
2
+ name: build-effect-apis
3
+ description: Build and consume contract-first Effect HTTP APIs. Use when defining shared HttpApiEndpoint/HttpApiGroup contracts, implementing HttpApiBuilder or HttpApiServer handlers and middleware, deriving HttpApiClient or Effect Atom clients, or integrating those APIs with TanStack Start and Cloudflare Workers/effect-cf.
4
+ ---
5
+
6
+ # Build Effect APIs
7
+
8
+ Treat the shared `HttpApi` value as the **contract spine**: schemas, server,
9
+ OpenAPI, and clients all derive from it. Keep transport contracts isomorphic;
10
+ keep runtime behavior in handlers, services, layers, and client state modules.
11
+
12
+ Effect HTTP and reactivity APIs are version-sensitive. Read the target
13
+ repository's manifests and lockfile, inspect its existing imports, and confirm
14
+ exact signatures from the installed package declarations before editing. In
15
+ current Effect v4 betas the server module is `HttpApiBuilder`; a request that
16
+ mentions `HttpApiServer` may refer to the same server-building responsibility
17
+ from another version.
18
+
19
+ ## Build the contract spine
20
+
21
+ 1. Inventory the existing API root, groups, endpoints, schemas, transport
22
+ errors, middleware, handler layers, client construction, and runtime edge.
23
+ Finish when every existing contract consumer and provider is identified.
24
+ 2. Read [shared-contracts.md](references/shared-contracts.md), then change the
25
+ shared domain package first: schemas and errors, one endpoint contract,
26
+ group composition, root API composition, and public exports. Finish when
27
+ server and client can import the same API value without runtime-specific
28
+ dependencies entering the domain package.
29
+ 3. Read [server-and-middleware.md](references/server-and-middleware.md), then
30
+ implement middleware and every changed group handler. Keep handlers as
31
+ decoded boundary adapters into application services and assemble all
32
+ requirements at the runtime edge. Finish when each endpoint identifier has
33
+ exactly one handler and every declared middleware has a provided layer.
34
+ 4. Choose the consumer branch:
35
+ - For React server state, read
36
+ [effect-atom-client.md](references/effect-atom-client.md) and
37
+ [effect-atom-lifecycle.md](references/effect-atom-lifecycle.md), then
38
+ derive one `AtomHttpApi.Service`, stable query atoms or families, mutation
39
+ atoms, and one reactivity-key vocabulary.
40
+ - For non-React Effect code, use the direct `HttpApiClient` branch in
41
+ [effect-atom-client.md](references/effect-atom-client.md).
42
+
43
+ Finish when consumers call the shared contract rather than redefining wire
44
+ types or using ad hoc `fetch` for declared endpoints.
45
+
46
+ 5. Read [verification.md](references/verification.md). Run the repository's
47
+ format, lint, typecheck, and test commands. Finish when changed schemas
48
+ round-trip, middleware failures use declared error channels, server and
49
+ client agree on every request shape, and changed atom lifecycles have
50
+ deterministic coverage.
51
+
52
+ ## Optional branches
53
+
54
+ - Read [runtime-assembly.md](references/runtime-assembly.md) when wiring a
55
+ conventional Node/Bun server, generated API docs, process entrypoint, or
56
+ serverless web handler.
57
+ - Read [effect-atom-testing.md](references/effect-atom-testing.md) when changing
58
+ Atom cache retention, SWR, polling, invalidation, cancellation, aggregation,
59
+ provider placement, SSR, or hydration behavior.
60
+ - Read [tanstack-start.md](references/tanstack-start.md) when the client is
61
+ TanStack Start, SSR, hydration, `ClientOnly`, loaders, server functions, or a
62
+ proxied separate API.
63
+ - Read [cloudflare-workers.md](references/cloudflare-workers.md) when the server
64
+ runs on Cloudflare Workers or uses `effect-cf`, bindings, Durable Objects,
65
+ Queues, WebSockets, streaming, or raw byte routes.
66
+
67
+ ## Boundary rules
68
+
69
+ - Let schemas own wire validation, encoding, status metadata, and branded IDs.
70
+ - Let endpoint files own route inputs, success, and expected transport errors.
71
+ - Let middleware own cross-cutting request behavior and request-scoped services.
72
+ - Let handlers own transport-to-application mapping and boundary invariants.
73
+ - Let application services own orchestration, persistence, retries, and
74
+ transactions.
75
+ - Let client data modules own API services, query identity, cache policy,
76
+ invalidation keys, and mutation atoms; let UI action owners own navigation,
77
+ toasts, optimistic presentation, and form reset.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Build Effect APIs"
3
+ short_description: "Build and consume typed Effect HTTP APIs"
4
+ default_prompt: "Use $build-effect-apis to build a shared Effect HTTP API and consume it from the application client."
@@ -0,0 +1,71 @@
1
+ # Cloudflare Workers and effect-cf
2
+
3
+ Use `effect-cf` to turn Worker bindings and entrypoints into Effect services and
4
+ layers. Confirm its compatibility with the repository's exact Effect v4 beta.
5
+
6
+ ## Runtime edge
7
+
8
+ Assemble application services, `HttpApiBuilder` routes, the Effect HTTP
9
+ platform/router requirements, and binding layers once, then hand the final
10
+ layer and fetch effect to `Worker.make` or another effect-cf definition.
11
+
12
+ ```ts
13
+ import { Effect, Layer } from "effect";
14
+ import { HttpRouter } from "effect/unstable/http";
15
+ import { Worker } from "effect-cf";
16
+
17
+ const WorkerLive = Layer.mergeAll(ApplicationServicesLive, ApiRoutesLive);
18
+
19
+ const fetch = Effect.gen(function* () {
20
+ const request = yield* Worker.NativeRequest;
21
+ const router = yield* HttpRouter.HttpRouter;
22
+ return yield* router.asHttpEffect();
23
+ });
24
+
25
+ export default Worker.make(WorkerLive, { fetch });
26
+ ```
27
+
28
+ The concrete Effect HTTP platform prerequisites differ by version and runtime.
29
+ Let the `HttpApiBuilder.layer` requirements drive the layer graph and verify it
30
+ with typecheck plus a Worker startup/dry-run command.
31
+
32
+ ## Typed bindings
33
+
34
+ Represent D1, R2, KV, Queues, Durable Object namespaces, and configuration as
35
+ services/layers at the Worker boundary. Application services depend on those
36
+ tags, not on a raw `env` object. Keep Wrangler binding names synchronized with
37
+ the layers and generated Worker types.
38
+
39
+ `effect-cf` typed Durable Object and Queue definitions can serve both as the
40
+ contract and as the producer/namespace service. Use deterministic Durable
41
+ Object names for domain ownership, keep RPC errors typed, and design Queue
42
+ consumers for at-least-once delivery.
43
+
44
+ ## Raw route escape hatches
45
+
46
+ JSON request/response routes fit `HttpApi`. WebSocket upgrades, streaming
47
+ responses, passthrough `Response` values, and large or direct byte transfers may
48
+ need a raw adapter at the Worker edge.
49
+
50
+ Dispatch raw routes before `router.asHttpEffect()`, decode every path/query/
51
+ header value with shared schemas, then fall through to the generated API router.
52
+ Mirror all relevant security, tenancy, tracing, and rate-limit behavior because
53
+ `HttpApi` middleware does not run for routes that bypass it.
54
+
55
+ For uploads, verify declared size, maximum size, media type, capability, and
56
+ checksum before storage. For WebSockets, validate the upgrade request and
57
+ identity before forwarding it to a Durable Object or session service.
58
+
59
+ Apply runtime-wide CORS around both raw and `HttpApi` routes. Include every
60
+ custom request header and method, and test preflight behavior. Treat OpenAPI as
61
+ documentation only for declared `HttpApi` endpoints; document raw routes
62
+ separately when clients need them.
63
+
64
+ ## Operational checks
65
+
66
+ - Generate Worker binding types after Wrangler binding changes.
67
+ - Run local migrations before local server tests.
68
+ - Run the repository's startup or deployment dry-run check after layer changes.
69
+ - Keep remote resource creation, migrations, and deployment as explicit
70
+ user-authorized operations.
71
+ - Test Queue idempotency, Durable Object naming, and raw-route middleware parity.
@@ -0,0 +1,161 @@
1
+ # Effect API clients
2
+
3
+ Derive clients from the shared `HttpApi`; keep request types, response types,
4
+ and expected errors owned by the contract.
5
+
6
+ - [Inventory the client boundary](#inventory-the-client-boundary)
7
+ - [Build one Atom API service](#build-one-atom-api-service)
8
+ - [Define queries](#define-queries)
9
+ - [Define mutations and invalidation](#define-mutations-and-invalidation)
10
+ - [Use a direct client outside React](#use-a-direct-client-outside-react)
11
+
12
+ ## Inventory the client boundary
13
+
14
+ Before changing Atom code, locate every `RegistryProvider`, runtime factory,
15
+ `AtomHttpApi.Service`, query atom, mutation atom, reactivity-key constructor,
16
+ and route aggregate. Confirm the installed `effect` and `@effect/atom-react`
17
+ versions before copying signatures.
18
+
19
+ ## Build one Atom API service
20
+
21
+ Create one module-scoped runtime factory and one `AtomHttpApi.Service`. Share a
22
+ memo map when multiple client services must reuse layers.
23
+
24
+ ```ts
25
+ import { Effect, Layer } from "effect";
26
+ import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http";
27
+ import { HttpApiMiddleware } from "effect/unstable/httpapi";
28
+ import { Atom, AtomHttpApi } from "effect/unstable/reactivity";
29
+ import { ApplicationApi, Authenticate } from "@app/domain";
30
+
31
+ export const AuthenticateClient = HttpApiMiddleware.layerClient(
32
+ Authenticate,
33
+ Effect.fn("AuthenticateClient")(function* ({ next, request }) {
34
+ const token = yield* readAccessToken;
35
+ return yield* next(HttpClientRequest.bearerToken(request, token));
36
+ }),
37
+ );
38
+
39
+ const ApiHttpClient = Layer.mergeAll(FetchHttpClient.layer, AuthenticateClient);
40
+
41
+ export const appRuntime = Atom.context({
42
+ memoMap: Layer.makeMemoMapUnsafe(),
43
+ });
44
+
45
+ export const ApiClient = AtomHttpApi.Service()("ApiClient", {
46
+ api: ApplicationApi,
47
+ httpClient: ApiHttpClient,
48
+ runtime: appRuntime,
49
+ baseUrl: "/api",
50
+ transformClient: (client) =>
51
+ HttpClient.mapRequest(client, (request) =>
52
+ HttpClientRequest.setHeader(request, "x-request-id", requestId()),
53
+ ),
54
+ });
55
+ ```
56
+
57
+ Define the runtime, service, key constructors, queries, and mutations outside
58
+ React renders. Keep browser-only identity access behind the client boundary.
59
+ When contract middleware sets `requiredForClient`, satisfy it with
60
+ `HttpApiMiddleware.layerClient` in the `httpClient` layer. Reserve
61
+ `transformClient` for transport-wide behavior such as correlation headers,
62
+ base URL changes, tracing, or retries; do not bypass declared API security with
63
+ an unrelated raw header transform.
64
+
65
+ ## Define queries
66
+
67
+ ```ts
68
+ export const projectKeys = {
69
+ collection: ["projects"] as const,
70
+ project: (id: ProjectId) => [`project:${id}`] as const,
71
+ };
72
+
73
+ export const projectAtom = Atom.family((projectId: ProjectId) =>
74
+ ApiClient.query("projects", "getProject", {
75
+ params: { projectId },
76
+ timeToLive: "5 minutes",
77
+ reactivityKeys: projectKeys.project(projectId),
78
+ }).pipe(Atom.swr({ staleTime: "30 seconds", revalidateOnMount: true })),
79
+ );
80
+ ```
81
+
82
+ `query(group, endpoint, request)` returns an `Atom<AsyncResult<...>>`. The
83
+ service memoizes encoded request keys internally; a public `Atom.family` still
84
+ expresses domain ownership and cache policy through a stable scalar or Effect
85
+ `Hash`/`Equal` key.
86
+
87
+ Query options have distinct jobs:
88
+
89
+ - `timeToLive` controls idle registry retention;
90
+ - `reactivityKeys` registers invalidation subscriptions;
91
+ - `serializationKey` enables decoded-only hydration serialization and is not
92
+ the runtime cache key;
93
+ - `responseMode` changes the response and error shape.
94
+
95
+ Keep secrets out of serialization keys, URL state, hydration payloads, and
96
+ client-visible layers.
97
+
98
+ ## Define mutations and invalidation
99
+
100
+ ```ts
101
+ export const updateProjectMutation = ApiClient.mutation("projects", "updateProject");
102
+
103
+ const updateProject = useAtomSet(updateProjectMutation, { mode: "promise" });
104
+
105
+ await updateProject({
106
+ params: { projectId },
107
+ payload: patch,
108
+ reactivityKeys: [...projectKeys.collection, ...projectKeys.project(projectId)],
109
+ });
110
+ ```
111
+
112
+ Successful mutations invalidate matching keys; failed mutations do not.
113
+ Centralize reactivity-key constructors because mismatched strings fail silently.
114
+ When one mutation affects several exact keys, flatten their arrays; an array
115
+ containing key arrays registers those nested arrays as different keys.
116
+
117
+ Exact array keys invalidate only exact matches. Record-form keys are
118
+ hierarchical in current Effect v4 implementations: a property registers both
119
+ its broad name and each `property:id` combination. Confirm this against the
120
+ installed version before relying on it.
121
+
122
+ Choose invalidation breadth from the server write:
123
+
124
+ - invalidate an entity key when only its detail changed;
125
+ - invalidate the collection when membership, ordering, totals, or filters can
126
+ change;
127
+ - invalidate every affected namespace when a write crosses aggregates.
128
+
129
+ Combine invalidation with manual refresh only when two requests are intended.
130
+ Keep navigation, toasts, form reset, and presentation-level optimistic state
131
+ with the initiating UI.
132
+
133
+ ## Use a direct client outside React
134
+
135
+ ```ts
136
+ import { Effect } from "effect";
137
+ import { FetchHttpClient, HttpClientRequest } from "effect/unstable/http";
138
+ import { HttpApiClient, HttpApiMiddleware } from "effect/unstable/httpapi";
139
+ import { ApplicationApi, Authenticate } from "@app/domain";
140
+ import { AccessToken, AccessTokenLive } from "./AccessToken";
141
+
142
+ const AuthenticateClient = HttpApiMiddleware.layerClient(
143
+ Authenticate,
144
+ Effect.fn("AuthenticateClient")(function* ({ next, request }) {
145
+ const token = yield* AccessToken;
146
+ return yield* next(HttpClientRequest.bearerToken(request, token.value));
147
+ }),
148
+ );
149
+
150
+ const program = Effect.gen(function* () {
151
+ const client = yield* HttpApiClient.make(ApplicationApi, {
152
+ baseUrl: "https://api.example.com",
153
+ });
154
+ return yield* client.projects.getProject({ params: { projectId } });
155
+ }).pipe(Effect.provide([FetchHttpClient.layer, AuthenticateClient, AccessTokenLive]));
156
+ ```
157
+
158
+ Use the direct client in services, scripts, tests, and non-React applications.
159
+ Provide every required contract middleware at the client composition root.
160
+ Transform the underlying `HttpClient` once for non-contract headers, tracing,
161
+ retry, or base behavior rather than repeating it at every call site.