@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.
- package/README.md +70 -76
- package/dev-kit.example.jsonc +0 -4
- package/package.json +10 -6
- package/schema/dev-kit.schema.json +1 -46
- package/skills/build-effect-apis/SKILL.md +77 -0
- package/skills/build-effect-apis/agents/openai.yaml +4 -0
- package/skills/build-effect-apis/references/cloudflare-workers.md +71 -0
- package/skills/build-effect-apis/references/effect-atom-client.md +161 -0
- package/skills/build-effect-apis/references/effect-atom-lifecycle.md +78 -0
- package/skills/build-effect-apis/references/effect-atom-testing.md +74 -0
- package/skills/build-effect-apis/references/runtime-assembly.md +56 -0
- package/skills/build-effect-apis/references/server-and-middleware.md +174 -0
- package/skills/build-effect-apis/references/shared-contracts.md +108 -0
- package/skills/build-effect-apis/references/tanstack-start.md +86 -0
- package/skills/build-effect-apis/references/verification.md +50 -0
- package/skills/dev-kit/SKILL.md +58 -46
- package/skills/effect-architecture-audit/SKILL.md +26 -0
- package/skills/effect-architecture-audit/agents/openai.yaml +4 -0
- package/skills/effect-architecture-audit/references/service-and-boundary-audit.md +150 -0
- package/skills/effect-ts/SKILL.md +21 -256
- package/skills/effect-ts/agents/openai.yaml +3 -3
- package/skills/testing/SKILL.md +5 -0
- package/src/catalog-manager.ts +16 -17
- package/src/catalog.ts +71 -16
- package/src/effect-source.ts +46 -24
- package/src/effect-tsgo.ts +49 -24
- package/src/gitignore.ts +5 -5
- package/src/index.ts +0 -6
- package/src/manifest.ts +0 -34
- package/src/node-symbolic-link.ts +2 -2
- package/src/oxfmt.js +5 -0
- package/src/oxfmt.ts +5 -0
- package/src/oxlint.js +5 -0
- package/src/oxlint.ts +5 -0
- package/src/package-skill-source.ts +51 -59
- package/src/path-digest.ts +7 -7
- package/src/project-package.ts +8 -7
- package/src/project-process-lock.ts +17 -12
- package/src/project-state.ts +1 -1
- package/src/skill-manager.ts +16 -14
- package/src/skill-selector.ts +12 -0
- package/src/sync.ts +170 -120
- package/src/tool-ignore-patterns.js +9 -0
- package/src/tool-ignore-patterns.ts +15 -0
- package/src/vendor.ts +67 -61
- package/src/vite-plus-dependency.ts +10 -11
- package/src/vite-plus-hooks.ts +24 -14
- package/src/vite-plus-quality.ts +21 -172
- package/src/vite-plus.js +81 -0
- package/src/vite-plus.ts +102 -0
- package/templates/AGENTS.md +1 -1
- package/skills/effect-ts/UPSTREAM.md +0 -28
- package/skills/effect-ts/references/atom-cache-lifecycle.md +0 -78
- package/skills/effect-ts/references/atom-http-and-invalidation.md +0 -97
- package/skills/effect-ts/references/atom-tanstack-start.md +0 -69
- package/skills/effect-ts/references/atom-testing.md +0 -67
- package/skills/effect-ts/references/audit-services.md +0 -144
- package/skills/effect-ts/references/features.md +0 -525
- package/skills/effect-ts/references/guide-atom-data-fetching.md +0 -44
- package/skills/effect-ts/references/guide-cli.md +0 -107
- package/skills/effect-ts/references/guide-datetime.md +0 -72
- package/skills/effect-ts/references/guide-effect.md +0 -440
- package/skills/effect-ts/references/guide-error-handling.md +0 -565
- package/skills/effect-ts/references/guide-http-boundaries.md +0 -55
- package/skills/effect-ts/references/guide-layers.md +0 -989
- package/skills/effect-ts/references/guide-observability.md +0 -746
- package/skills/effect-ts/references/guide-retries.md +0 -434
- package/skills/effect-ts/references/guide-schedule.md +0 -343
- package/skills/effect-ts/references/guide-schema.md +0 -664
- package/skills/effect-ts/references/guide-sql.md +0 -536
- package/skills/effect-ts/references/guide-testing.md +0 -532
- package/skills/effect-ts/references/guide-type-safety-and-boundaries.md +0 -131
- package/skills/effect-ts/references/version-and-source.md +0 -86
- 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.
|