@danieljvdm/dev-kit 0.11.3 → 0.13.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 (79) hide show
  1. package/README.md +111 -78
  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/build-effect-clis/SKILL.md +61 -0
  17. package/skills/build-effect-clis/agents/openai.yaml +4 -0
  18. package/skills/build-effect-clis/references/command-design.md +93 -0
  19. package/skills/build-effect-clis/references/entrypoints-and-testing.md +59 -0
  20. package/skills/build-effect-clis/references/processes-and-platform.md +73 -0
  21. package/skills/dev-kit/SKILL.md +77 -48
  22. package/skills/effect-architecture-audit/SKILL.md +26 -0
  23. package/skills/effect-architecture-audit/agents/openai.yaml +4 -0
  24. package/skills/effect-architecture-audit/references/service-and-boundary-audit.md +150 -0
  25. package/skills/effect-ts/SKILL.md +21 -256
  26. package/skills/effect-ts/agents/openai.yaml +3 -3
  27. package/skills/testing/SKILL.md +5 -0
  28. package/src/catalog-manager.ts +16 -17
  29. package/src/catalog.ts +71 -16
  30. package/src/effect-source.ts +46 -24
  31. package/src/effect-tsgo.ts +86 -24
  32. package/src/gitignore.ts +5 -5
  33. package/src/index.ts +3 -6
  34. package/src/manifest.ts +0 -34
  35. package/src/node-symbolic-link.ts +2 -2
  36. package/src/oxfmt.js +5 -0
  37. package/src/oxfmt.ts +5 -0
  38. package/src/oxlint.js +5 -0
  39. package/src/oxlint.ts +5 -0
  40. package/src/package-skill-source.ts +51 -59
  41. package/src/path-digest.ts +7 -7
  42. package/src/project-package.ts +8 -7
  43. package/src/project-process-lock.ts +17 -12
  44. package/src/project-state.ts +1 -1
  45. package/src/skill-manager.ts +18 -16
  46. package/src/skill-selector.ts +12 -0
  47. package/src/sync.ts +181 -131
  48. package/src/tool-ignore-patterns.js +9 -0
  49. package/src/tool-ignore-patterns.ts +15 -0
  50. package/src/vendor.ts +67 -61
  51. package/src/vite-plus-dependency.ts +10 -11
  52. package/src/vite-plus-hooks.ts +24 -14
  53. package/src/vite-plus-quality.ts +21 -172
  54. package/src/vite-plus.js +81 -0
  55. package/src/vite-plus.ts +102 -0
  56. package/templates/AGENTS.md +1 -1
  57. package/skills/effect-ts/UPSTREAM.md +0 -28
  58. package/skills/effect-ts/references/atom-cache-lifecycle.md +0 -78
  59. package/skills/effect-ts/references/atom-http-and-invalidation.md +0 -97
  60. package/skills/effect-ts/references/atom-tanstack-start.md +0 -69
  61. package/skills/effect-ts/references/atom-testing.md +0 -67
  62. package/skills/effect-ts/references/audit-services.md +0 -144
  63. package/skills/effect-ts/references/features.md +0 -525
  64. package/skills/effect-ts/references/guide-atom-data-fetching.md +0 -44
  65. package/skills/effect-ts/references/guide-cli.md +0 -107
  66. package/skills/effect-ts/references/guide-datetime.md +0 -72
  67. package/skills/effect-ts/references/guide-effect.md +0 -440
  68. package/skills/effect-ts/references/guide-error-handling.md +0 -565
  69. package/skills/effect-ts/references/guide-http-boundaries.md +0 -55
  70. package/skills/effect-ts/references/guide-layers.md +0 -989
  71. package/skills/effect-ts/references/guide-observability.md +0 -746
  72. package/skills/effect-ts/references/guide-retries.md +0 -434
  73. package/skills/effect-ts/references/guide-schedule.md +0 -343
  74. package/skills/effect-ts/references/guide-schema.md +0 -664
  75. package/skills/effect-ts/references/guide-sql.md +0 -536
  76. package/skills/effect-ts/references/guide-testing.md +0 -532
  77. package/skills/effect-ts/references/guide-type-safety-and-boundaries.md +0 -131
  78. package/skills/effect-ts/references/version-and-source.md +0 -86
  79. package/templates/vite-plus/vite.config.ts +0 -22
@@ -1,532 +0,0 @@
1
- # Testing Guide
2
-
3
- This guide covers `@effect/vitest`, deterministic execution, test Layers,
4
- property tests, protocol round trips, and test-service conventions.
5
-
6
- Key source files:
7
-
8
- - `packages/vitest/src/index.ts`
9
- - `packages/vitest/src/internal/internal.ts`
10
- - `packages/vitest/test/index.test.ts`
11
- - `packages/vitest/typetest/index.tst.ts`
12
-
13
- ## Preferred Rule
14
-
15
- When testing Effect code with Vitest, prefer `@effect/vitest` over manually calling `Effect.runPromise`, `Effect.runSync`, or ad hoc runtime setup inside ordinary Vitest tests.
16
-
17
- Layer provisioning in tests should follow these rules:
18
-
19
- 1. If multiple tests should share the same layered setup, use `layer(...)`.
20
- 2. If a nested group needs extra dependencies, use `it.layer(...)`.
21
- 3. If tests need isolated layer instances per test, use multiple separate `it.layer(...)` calls.
22
- 4. Do not default to local `.pipe(Effect.provide(...))` inside test bodies.
23
-
24
- Use:
25
-
26
- - `it.effect` for Effect-based tests with test services
27
- - `it.live` for Effect-based tests that should use live services
28
- - `layer(...)` and `it.layer(...)` for shared layered test setup
29
- - `it.effect.prop` for Effect-based property tests
30
-
31
- Do not default to `.pipe(Effect.provide(SomeLayer))` inside test bodies when `layer(...)` or `it.layer(...)` expresses the setup more clearly.
32
-
33
- Keep one executable green suite. Do not land `.red.test.ts` suites, red-only
34
- scripts, or wall-clock sleeps. Use `TestClock` for time-driven behavior.
35
-
36
- Use `Effect.exit` when asserting expected failures, then assert the tagged error
37
- and its important payload fields. When a protocol boundary uses Schema, test
38
- its decode and encode round trip rather than bypassing the contract.
39
-
40
- Shared skill guidance should not copy exact package-version claims from one
41
- repository or require agents to reread external URLs unless the target
42
- repository explicitly owns that workflow.
43
-
44
- ## Imports
45
-
46
- Preferred imports for Effect tests:
47
-
48
- ```ts
49
- import { assert, describe, it, layer } from "@effect/vitest";
50
- import { Effect } from "effect";
51
- ```
52
-
53
- `@effect/vitest` re-exports Vitest, so it is the normal entrypoint for test APIs in an Effect codebase.
54
-
55
- ## Core Test Modes
56
-
57
- ## `it.effect`
58
-
59
- Use `it.effect` for most Effect tests.
60
-
61
- It automatically:
62
-
63
- - runs the effect
64
- - scopes it correctly
65
- - provides the default test environment
66
-
67
- The default test environment includes:
68
-
69
- - `TestConsole`
70
- - `TestClock`
71
-
72
- This comes directly from the internal implementation.
73
-
74
- Example:
75
-
76
- ```ts
77
- import { assert, it } from "@effect/vitest";
78
- import { Effect } from "effect";
79
-
80
- it.effect("loads a user", () =>
81
- Effect.gen(function* () {
82
- yield* Effect.void;
83
- assert.isTrue(true);
84
- }),
85
- );
86
- ```
87
-
88
- Use `it.effect` when:
89
-
90
- - the test uses ordinary Effect code
91
- - the test benefits from `TestClock`
92
- - the test benefits from `TestConsole`
93
- - the test should run in a scoped Effect runtime
94
-
95
- ## `it.live`
96
-
97
- Use `it.live` when the test should use live services instead of the default test environment.
98
-
99
- Example:
100
-
101
- ```ts
102
- it.live("uses live services", () =>
103
- Effect.gen(function* () {
104
- yield* Effect.void;
105
- }),
106
- );
107
- ```
108
-
109
- Use `it.live` when:
110
-
111
- - you need the real `Clock`
112
- - the test should interact with live runtime services
113
- - you deliberately do not want the test environment overrides
114
-
115
- Rule of thumb:
116
-
117
- - default: `it.effect`
118
- - opt into `it.live` only when you actually need live behavior
119
-
120
- ## Effect Test Structure
121
-
122
- Preferred pattern:
123
-
124
- ```ts
125
- it.effect("does something", () =>
126
- Effect.gen(function* () {
127
- const value = yield* someEffect;
128
- assert.strictEqual(value, 1);
129
- }),
130
- );
131
- ```
132
-
133
- Prefer `Effect.gen` inside `it.effect` and `it.live` for readability.
134
-
135
- Avoid:
136
-
137
- - `it("...", async () => ...)` for Effect programs
138
- - `Effect.runPromise(...)` inside plain Vitest tests
139
- - manual runtime setup for routine Effect tests
140
-
141
- ## Assertions
142
-
143
- Use `assert` for Effect tests.
144
-
145
- The canonical source also uses `expect` in some tests, but for skill guidance prefer `assert` in Effect-based tests because it keeps tests more uniform and explicit inside Effect programs.
146
-
147
- Examples:
148
-
149
- ```ts
150
- assert.isTrue(value === 1);
151
- assert.strictEqual(a + b, b + a);
152
- assert.include(text, substring);
153
- ```
154
-
155
- ## Test Context
156
-
157
- `it.effect` and `it.live` test functions can receive a Vitest `TestContext`.
158
-
159
- Example:
160
-
161
- ```ts
162
- it.effect("uses context", (ctx) =>
163
- Effect.gen(function* () {
164
- ctx.onTestFailed(() => {
165
- // cleanup or diagnostics
166
- });
167
- }),
168
- );
169
- ```
170
-
171
- Use this when you need:
172
-
173
- - failure hooks
174
- - abort signals
175
- - ordinary Vitest test context integration
176
-
177
- ## `each`, `skip`, `skipIf`, `runIf`, `only`, `fails`
178
-
179
- The Effect testers support the standard test variants:
180
-
181
- - `it.effect.each(...)`
182
- - `it.live.each(...)`
183
- - `it.effect.skip(...)`
184
- - `it.effect.skipIf(...)`
185
- - `it.effect.runIf(...)`
186
- - `it.effect.only(...)`
187
- - `it.live.fails(...)`
188
-
189
- Examples from the canonical tests show these are first-class parts of the API.
190
-
191
- Use them exactly as you would with Vitest, but return `Effect` from the test body.
192
-
193
- ## Property Testing
194
-
195
- There are two different property-test entrypoints and they do not have identical behavior.
196
-
197
- ## Top-level `it.prop`
198
-
199
- Use `it.prop` for non-Effect property tests.
200
-
201
- Example:
202
-
203
- ```ts
204
- import { it } from "@effect/vitest";
205
- import { FastCheck } from "effect/testing";
206
-
207
- const realNumber = FastCheck.float({ noNaN: true, noDefaultInfinity: true });
208
-
209
- it.prop("symmetry", [realNumber, FastCheck.integer()], ([a, b]) => a + b === b + a);
210
- ```
211
-
212
- Important limitation from the internal implementation:
213
-
214
- - top-level `it.prop` does not support `Schema` arbitraries yet
215
- - if you pass a `Schema`, it throws
216
-
217
- So use top-level `it.prop` only with explicit `FastCheck` arbitraries.
218
-
219
- ## `it.effect.prop`
220
-
221
- Use `it.effect.prop` for property tests that return `Effect`.
222
-
223
- This is the more powerful property-testing mode for Effect code.
224
-
225
- Example:
226
-
227
- ```ts
228
- import { assert, it } from "@effect/vitest";
229
- import { Effect } from "effect";
230
- import { FastCheck } from "effect/testing";
231
-
232
- const realNumber = FastCheck.float({ noNaN: true, noDefaultInfinity: true });
233
-
234
- it.effect.prop("symmetry", [realNumber, FastCheck.integer()], ([a, b]) =>
235
- Effect.gen(function* () {
236
- assert.strictEqual(a + b, b + a);
237
- }),
238
- );
239
- ```
240
-
241
- Unlike top-level `it.prop`, `it.effect.prop` does support `Schema` inputs by converting them with `Schema.toArbitrary`.
242
-
243
- So prefer `it.effect.prop` when:
244
-
245
- - the property test needs `Effect`
246
- - you want to use `Schema` values as arbitraries
247
- - the test needs Effect services or scope
248
-
249
- ## `layer(...)`
250
-
251
- Use top-level `layer(...)` to share a `Layer` across a group of tests.
252
-
253
- Hard rule:
254
-
255
- - use `layer(...)` when tests should share one layered setup
256
- - do not use it when each test needs its own isolated layer instance
257
-
258
- This is one of the most important `@effect/vitest` features.
259
-
260
- Example:
261
-
262
- ```ts
263
- import { describe, it, layer } from "@effect/vitest";
264
- import { Context, Effect, Layer } from "effect";
265
-
266
- class Foo extends Context.Service<Foo, string>()("Foo") {
267
- static readonly layer = Layer.succeed(Foo)("foo");
268
- }
269
-
270
- describe("foo", () => {
271
- layer(Foo.layer)((it) => {
272
- it.effect("gets foo", () =>
273
- Effect.gen(function* () {
274
- const foo = yield* Foo;
275
- return foo;
276
- }),
277
- );
278
- });
279
- });
280
- ```
281
-
282
- What it does internally:
283
-
284
- - builds the layer once for the test group
285
- - memoizes the built layer with a `MemoMap`
286
- - keeps the scope open for the group
287
- - closes the scope in `afterAll`
288
-
289
- This makes it the preferred way to share layer setup across related tests.
290
-
291
- This is also the preferred alternative to calling `Effect.provide(...)` inside each individual test body.
292
-
293
- ## Honest Test Layers
294
-
295
- Make every test Layer's name match the behavior it provides:
296
-
297
- - Use `Layer.succeed` for a complete static implementation.
298
- - Add `layerTest` and a test-control service when reusable state, failure
299
- injection, or observation belongs to a real authority seam.
300
- - Use `layerMemory` only when the in-memory implementation faithfully preserves
301
- the service's observable contract.
302
- - Use a real local adapter when persistence, transactions, serialization, or
303
- protocol behavior matters.
304
- - Keep narrow one-off fakes local to their tests.
305
-
306
- Exercise the same service interface as production callers. When a service and
307
- its reusable test-control service share state, back both with the same scoped
308
- object. Keep partial implementations local rather than advertising them as
309
- general test or in-memory Layers.
310
-
311
- ## Anti-Pattern: Local `Effect.provide(...)` In Tests
312
-
313
- If multiple tests use the same layer, do not write tests like this:
314
-
315
- ```ts
316
- it.effect("creates and lists todos", () =>
317
- Effect.gen(function* () {
318
- const service = yield* TodoService;
319
- yield* service.create("write tests");
320
- yield* service.create("ship feature");
321
- }).pipe(Effect.provide(TodoService.inMemoryLayer)),
322
- );
323
- ```
324
-
325
- Why this is the wrong pattern:
326
-
327
- - it repeats provisioning in every test
328
- - it hides the layered test setup inside each test body
329
- - it fights the `@effect/vitest` layer helpers
330
- - it makes shared setup and teardown less explicit
331
- - it bypasses the clearer grouped-layer style that the library is designed for
332
-
333
- Prefer:
334
-
335
- ```ts
336
- describe("TodoService", () => {
337
- layer(TodoService.inMemoryLayer)((it) => {
338
- it.effect("creates and lists todos", () =>
339
- Effect.gen(function* () {
340
- const service = yield* TodoService;
341
- yield* service.create("write tests");
342
- yield* service.create("ship feature");
343
- }),
344
- );
345
-
346
- it.effect("updates completion and deletes todos", () =>
347
- Effect.gen(function* () {
348
- const service = yield* TodoService;
349
- const todo = yield* service.create("close issue");
350
- yield* service.setCompleted(todo.id, true);
351
- yield* service.remove(todo.id);
352
- }),
353
- );
354
- });
355
- });
356
- ```
357
-
358
- Rule:
359
-
360
- - if a test or group of tests depends on a layer, prefer `layer(...)`
361
- - if a nested group needs extra dependencies, prefer `it.layer(...)`
362
- - if tests need isolated layer instances per test, use multiple separate `it.layer(...)` calls
363
- - use local `Effect.provide(...)` in tests only for true one-off edge cases, not as the normal pattern
364
-
365
- This matches the general layer guidance: provisioning belongs at the boundary, and in `@effect/vitest` the test boundary should usually be expressed with `layer(...)` rather than ad hoc local provisioning.
366
-
367
- ## `it.layer(...)`
368
-
369
- Use `it.layer(...)` inside an existing layered test group to add nested layer context.
370
-
371
- Hard rule:
372
-
373
- - use `it.layer(...)` when a specific test or nested test block should get its own isolated layered setup
374
- - if isolation matters, prefer multiple separate `it.layer(...)` calls over one shared `layer(...)` group
375
-
376
- Example:
377
-
378
- ```ts
379
- layer(Foo.layer)((it) => {
380
- it.layer(Bar.layer)("nested", (it) => {
381
- it.effect("gets both", () =>
382
- Effect.gen(function* () {
383
- const foo = yield* Foo;
384
- const bar = yield* Bar;
385
- return [foo, bar];
386
- }),
387
- );
388
- });
389
- });
390
- ```
391
-
392
- Important behavior from the implementation:
393
-
394
- - nested `it.layer(...)` reuses the parent memo map
395
- - nested `it.layer(...)` does not accept `memoMap` or `excludeTestServices`
396
- - nested layering is meant to extend the current layered test environment, not redefine its runtime policy
397
-
398
- Use multiple `it.layer(...)` blocks when each test should get its own isolated layered setup instead of sharing one top-level `layer(...)` group.
399
-
400
- ## `layer` Options
401
-
402
- The top-level `layer(...)` helper accepts:
403
-
404
- - `timeout`
405
- - `memoMap`
406
- - `excludeTestServices`
407
-
408
- ### `excludeTestServices`
409
-
410
- By default, `layer(...)` merges your layer with the test environment (`TestClock` and `TestConsole`).
411
-
412
- Use `excludeTestServices: true` when you want your layer group to run without those test-service overrides.
413
-
414
- This is useful for tests that should keep live runtime behavior.
415
-
416
- ### `memoMap`
417
-
418
- Use `memoMap` only when you have a specific reason to coordinate layer memoization manually across test setups.
419
-
420
- Most tests should let `@effect/vitest` manage it.
421
-
422
- ## Scoped Resources In Tests
423
-
424
- `@effect/vitest` is designed to work correctly with scoped effects and layered resources.
425
-
426
- The canonical tests explicitly verify resource release through `afterAll`.
427
-
428
- Use this normally:
429
-
430
- - define scoped services in layers
431
- - use `layer(...)` to share them
432
- - let the helper own scope setup and teardown
433
-
434
- Avoid manually managing large scopes in test code unless the test specifically needs that control.
435
-
436
- ## `flakyTest`
437
-
438
- Use `flakyTest` for tests that need bounded retrying.
439
-
440
- Repo behavior:
441
-
442
- - wraps the test in `Effect.scoped`
443
- - retries using a schedule
444
- - retries for up to a timeout window
445
- - converts final failure to defect with `Effect.orDie`
446
-
447
- Use this only for truly flaky integration-style conditions, not as a substitute for deterministic tests.
448
-
449
- ## `makeMethods` And `describeWrapped`
450
-
451
- These exist for custom integration and wrapper scenarios.
452
-
453
- ### `makeMethods`
454
-
455
- Use `makeMethods` when you need to extend or wrap a custom Vitest `it` instance while preserving Effect-aware helpers.
456
-
457
- ### `describeWrapped`
458
-
459
- Use `describeWrapped` when you want a `describe` wrapper that hands you the augmented Effect-aware `it` methods directly.
460
-
461
- Most test code can just use imported `describe` and `it` from `@effect/vitest`.
462
-
463
- ## Equality Testers
464
-
465
- `addEqualityTesters()` exists as part of the public API.
466
-
467
- Use it only when you have a concrete need to install equality testers for your Vitest environment.
468
-
469
- It is not needed for ordinary test structure.
470
-
471
- ## Recommended Patterns
472
-
473
- ### Pattern: normal Effect test
474
-
475
- ```ts
476
- it.effect("does work", () =>
477
- Effect.gen(function* () {
478
- const value = yield* Effect.succeed(1);
479
- assert.strictEqual(value, 1);
480
- }),
481
- );
482
- ```
483
-
484
- ### Pattern: shared layer for a test group
485
-
486
- ```ts
487
- layer(AppLayer)("app", (it) => {
488
- it.effect("uses app services", () =>
489
- Effect.gen(function* () {
490
- yield* Effect.void;
491
- }),
492
- );
493
- });
494
- ```
495
-
496
- ### Pattern: property test with Effect
497
-
498
- ```ts
499
- it.effect.prop("law", [FastCheck.integer()], ([n]) =>
500
- Effect.gen(function* () {
501
- assert.strictEqual(n + 0, n);
502
- }),
503
- );
504
- ```
505
-
506
- ### Pattern: use `TestClock`
507
-
508
- ```ts
509
- it.effect("uses TestClock", () =>
510
- Effect.gen(function* () {
511
- const fiber = yield* Effect.forkChild(Effect.sleep("1 second"));
512
- yield* TestClock.adjust("1 second");
513
- yield* Fiber.join(fiber);
514
- }),
515
- );
516
- ```
517
-
518
- ## Anti-Patterns
519
-
520
- - using plain `it(...)` with `Effect.runPromise(...)` for normal Effect tests
521
- - using `it.live` by default when `it.effect` is sufficient
522
- - manually building and tearing down large layer graphs instead of using `layer(...)`
523
- - using top-level `it.prop` with `Schema` inputs
524
- - using `flakyTest` to hide deterministic failures
525
- - duplicating runtime setup instead of sharing a layer
526
-
527
- ## Good Repo Examples To Study
528
-
529
- - `packages/vitest/test/index.test.ts`
530
- - `packages/vitest/src/index.ts`
531
- - `packages/vitest/src/internal/internal.ts`
532
- - `packages/vitest/typetest/index.tst.ts`
@@ -1,131 +0,0 @@
1
- # Effect Type Safety And Boundaries
2
-
3
- Use this when reviewing `unknown`, `any`, assertions, runtime shape checks,
4
- JSON or provider parsing, Promise rejection causes, thrown exceptions, and
5
- small helper functions around typed Effect code.
6
-
7
- ## Contents
8
-
9
- - [Assign Boundary Ownership](#assign-boundary-ownership)
10
- - [Match Typed Values Directly](#match-typed-values-directly)
11
- - [Let Schema Own Runtime Shape](#let-schema-own-runtime-shape)
12
- - [Search Effect Before Adding Local Code](#search-effect-before-adding-local-code)
13
- - [Audit Assertions And Manual Narrowing](#audit-assertions-and-manual-narrowing)
14
- - [Keep Helpers Meaningful](#keep-helpers-meaningful)
15
- - [Preserve Typed Error Channels](#preserve-typed-error-channels)
16
- - [Completion Check](#completion-check)
17
-
18
- ## Assign Boundary Ownership
19
-
20
- Identify who owns each value's type before inspecting it at runtime.
21
-
22
- - Reserve `unknown` for external input, foreign library output, and opaque
23
- causes retained for diagnostics.
24
- - Decode external values with the owning Schema at their earliest application
25
- boundary.
26
- - Pass decoded values and concrete error unions through internal services.
27
- - Expose public service methods as `Effect.Effect<A, DomainErrorUnion, R>`.
28
- - Keep an irreducible foreign cause in a `cause: Schema.Unknown` field inside a
29
- concrete schema-backed error.
30
-
31
- Complete this step when every `unknown` has a named boundary owner.
32
-
33
- ## Match Typed Values Directly
34
-
35
- Read the inferred value and error types before adding a runtime guard.
36
-
37
- - Match discriminated errors with their `_tag`, nested reason tags,
38
- `Effect.catchTag`, or `Effect.catchTags`.
39
- - Access fields directly after the Effect error channel or Schema has already
40
- established their type.
41
- - Use `instanceof` when runtime class identity is part of the owning API's
42
- contract.
43
- - Inline a single-use boolean condition when it states the branch clearly.
44
-
45
- Extract a predicate when it provides reusable narrowing or owns a meaningful
46
- domain policy.
47
-
48
- ## Let Schema Own Runtime Shape
49
-
50
- Choose the Schema adapter that matches the caller's control flow:
51
-
52
- | Intent | Adapter |
53
- | ------------------------- | ----------------------------------- |
54
- | Boolean type guard | `Schema.is(Model)` |
55
- | Optional tolerant decode | `Schema.decodeUnknownOption(Model)` |
56
- | Typed Effect failure | `Schema.decodeUnknownEffect(Model)` |
57
- | JSON string decode | `Schema.fromJsonString(Model)` |
58
- | Any JSON-compatible value | `Schema.Json` |
59
-
60
- Define small schemas for provider responses, SDK payloads, persisted data, and
61
- other structured external values. Decode once in the adapter and return the
62
- schema-derived type.
63
-
64
- ## Search Effect Before Adding Local Code
65
-
66
- Search the project's pinned Effect version and platform packages before
67
- creating a schema, codec, type guard, JSON type, or runtime helper. Check:
68
-
69
- - `Schema` for existing data models, codecs, guards, and transformations.
70
- - `Encoding` for base64, hex, and text encoding.
71
- - `FileSystem`, `Path`, `HttpClient`, `Clock`, `Random`, and other platform
72
- services for runtime capabilities.
73
-
74
- Adopt the built-in value and type together when they model the same concept.
75
- For example, `Schema.Json` supplies both the recursive JSON schema and its
76
- `Schema.Json` type.
77
-
78
- ## Audit Assertions And Manual Narrowing
79
-
80
- Inventory:
81
-
82
- - `as any`, `as unknown as`, branded casts, and non-null assertions.
83
- - Custom type predicates and generic helpers such as `isRecord`.
84
- - `typeof value === "object"`, property probes, `in`, `Array.isArray`, and
85
- `JSON.parse` used to discover structured external data.
86
-
87
- Give each occurrence one disposition:
88
-
89
- 1. Decode the boundary value with its Schema.
90
- 2. Isolate a compiler or framework adapter assertion at the narrowest boundary
91
- and document the contract it bridges.
92
- 3. Remove narrowing already guaranteed by the inferred type.
93
-
94
- ## Keep Helpers Meaningful
95
-
96
- Extract a helper when it owns domain policy, provides reusable type refinement,
97
- removes repeated non-trivial mechanics, or creates a named observability
98
- boundary with `Effect.fn`.
99
-
100
- Inline one-use property checks and tag comparisons. Apply the deletion test:
101
- if removing the helper leaves equally clear typed code, keep the code inline.
102
-
103
- ## Preserve Typed Error Channels
104
-
105
- - Represent recoverable failures with schema-backed tagged errors.
106
- - Map `Effect.tryPromise` rejection causes to the concrete service error at the
107
- Promise boundary.
108
- - Distinguish failures by caller action when recovery differs, such as
109
- permission, download, save, and share errors.
110
- - Preserve the original foreign value as diagnostic context inside the tagged
111
- error.
112
- - Use synchronous throws for framework-required hooks, impossible invariants,
113
- defects, and thunks immediately captured by `Effect.try` or
114
- `Effect.tryPromise`.
115
- - Scope `ThrowStatement` lint restrictions to Effect workflow and service
116
- modules whose expected failures belong in the error channel. Give boundary
117
- adapters, invariant utilities, tests, and captured thunks explicit scopes.
118
-
119
- ## Completion Check
120
-
121
- Before completing a change, account for every modified `unknown`, `any`,
122
- assertion, type predicate, structural probe, JSON parser, Promise catch mapper,
123
- throw, and public Effect error type. Each occurrence should resolve to a typed
124
- internal value, a Schema-owned boundary, or a narrow documented adapter.
125
-
126
- Use these probes during an audit:
127
-
128
- - A typed error tag hidden behind a one-line predicate.
129
- - A provider payload inspected through `isRecord` and property probes.
130
- - A raw Promise rejection broadening a service error channel to `unknown`.
131
- - A custom recursive JSON schema or `JsonValue` type.