@danieljvdm/dev-kit 0.6.0 → 0.7.1
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 +123 -56
- package/dev-kit.example.jsonc +7 -3
- package/package.json +19 -16
- package/schema/dev-kit.schema.json +38 -0
- package/skill-sources.jsonc +8 -12
- package/skill-sources.lock.json +3 -9
- package/skills/dev-kit/SKILL.md +52 -17
- package/skills/effect-ts/agents/openai.yaml +0 -1
- package/skills/effect-ts/references/audit-services.md +11 -11
- package/skills/effect-ts/references/guide-effect.md +56 -69
- package/skills/effect-ts/references/guide-error-handling.md +64 -73
- package/skills/effect-ts/references/guide-layers.md +187 -215
- package/skills/effect-ts/references/guide-observability.md +91 -116
- package/skills/effect-ts/references/guide-retries.md +32 -44
- package/skills/effect-ts/references/guide-schedule.md +26 -40
- package/skills/effect-ts/references/guide-schema.md +50 -57
- package/skills/effect-ts/references/guide-sql.md +47 -50
- package/skills/effect-ts/references/guide-testing.md +96 -98
- package/skills/effect-ts/references/guide-type-safety-and-boundaries.md +7 -7
- package/skills/effect-ts/references/version-and-source.md +0 -1
- package/src/bin/dev-kit.ts +61 -28
- package/src/catalog-manager.ts +86 -34
- package/src/catalog.ts +71 -33
- package/src/cli-ui.ts +20 -16
- package/src/effect-source.ts +49 -19
- package/src/effect-tsgo.ts +66 -35
- package/src/gitignore.ts +19 -6
- package/src/index.ts +6 -0
- package/src/manifest.ts +38 -3
- package/src/node-symbolic-link.ts +3 -0
- package/src/oxlint-plugin-effect.js +3 -0
- package/src/oxlint-plugin-style.d.ts +8 -0
- package/src/oxlint-plugin-style.js +8 -0
- package/src/oxlint.js +14 -0
- package/src/oxlint.ts +14 -0
- package/src/package-skill-source.ts +189 -52
- package/src/path-digest.ts +47 -13
- package/src/project-package.ts +44 -19
- package/src/project-process-lock.ts +19 -12
- package/src/project-state.ts +11 -0
- package/src/skill-manager.ts +134 -55
- package/src/skill-selector.ts +8 -2
- package/src/source-manifest.ts +2 -6
- package/src/sync.ts +417 -107
- package/src/vendor.ts +112 -42
- package/src/vite-plus-hooks.ts +174 -0
- package/src/vite-plus-quality.ts +49 -0
- package/templates/vite-plus/github-actions-check.yml +44 -0
- package/templates/vite-plus/vite.config.ts +22 -0
|
@@ -45,22 +45,22 @@ Use `Effect.fn` as the default constructor for business-logic functions that ret
|
|
|
45
45
|
Prefer this:
|
|
46
46
|
|
|
47
47
|
```ts
|
|
48
|
-
import { Effect } from "effect"
|
|
48
|
+
import { Effect } from "effect";
|
|
49
49
|
|
|
50
|
-
const loadUser = Effect.fn("loadUser")(function*(userId: string) {
|
|
51
|
-
return { id: userId, name: "Ada" }
|
|
52
|
-
})
|
|
50
|
+
const loadUser = Effect.fn("loadUser")(function* (userId: string) {
|
|
51
|
+
return { id: userId, name: "Ada" };
|
|
52
|
+
});
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
Over this:
|
|
56
56
|
|
|
57
57
|
```ts
|
|
58
|
-
import { Effect } from "effect"
|
|
58
|
+
import { Effect } from "effect";
|
|
59
59
|
|
|
60
60
|
const loadUser = (userId: string) =>
|
|
61
|
-
Effect.gen(function*() {
|
|
62
|
-
return { id: userId, name: "Ada" }
|
|
63
|
-
})
|
|
61
|
+
Effect.gen(function* () {
|
|
62
|
+
return { id: userId, name: "Ada" };
|
|
63
|
+
});
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
The second version works, but it throws away useful observability structure that `Effect.fn` gives you automatically.
|
|
@@ -72,17 +72,17 @@ For business-logic definitions, prefer `Effect.fn` over writing raw `Effect.gen`
|
|
|
72
72
|
Prefer this:
|
|
73
73
|
|
|
74
74
|
```ts
|
|
75
|
-
const refreshCache = Effect.fn("refreshCache")(function*() {
|
|
76
|
-
yield* Effect.logInfo("refreshing cache")
|
|
77
|
-
})
|
|
75
|
+
const refreshCache = Effect.fn("refreshCache")(function* () {
|
|
76
|
+
yield* Effect.logInfo("refreshing cache");
|
|
77
|
+
});
|
|
78
78
|
```
|
|
79
79
|
|
|
80
80
|
Over this:
|
|
81
81
|
|
|
82
82
|
```ts
|
|
83
|
-
const refreshCache = Effect.gen(function*() {
|
|
84
|
-
yield* Effect.logInfo("refreshing cache")
|
|
85
|
-
})
|
|
83
|
+
const refreshCache = Effect.gen(function* () {
|
|
84
|
+
yield* Effect.logInfo("refreshing cache");
|
|
85
|
+
});
|
|
86
86
|
```
|
|
87
87
|
|
|
88
88
|
Why:
|
|
@@ -118,11 +118,11 @@ It is the preferred default because it adds:
|
|
|
118
118
|
Example:
|
|
119
119
|
|
|
120
120
|
```ts
|
|
121
|
-
import { Effect } from "effect"
|
|
121
|
+
import { Effect } from "effect";
|
|
122
122
|
|
|
123
|
-
const createUser = Effect.fn("createUser")(function*(name: string) {
|
|
124
|
-
return { id: "u_123", name }
|
|
125
|
-
})
|
|
123
|
+
const createUser = Effect.fn("createUser")(function* (name: string) {
|
|
124
|
+
return { id: "u_123", name };
|
|
125
|
+
});
|
|
126
126
|
```
|
|
127
127
|
|
|
128
128
|
Use this for:
|
|
@@ -145,17 +145,17 @@ If you do not want an explicit named span, prefer `Effect.fn` without a span nam
|
|
|
145
145
|
Prefer this:
|
|
146
146
|
|
|
147
147
|
```ts
|
|
148
|
-
const normalizeUser = Effect.fn(function*(input: string) {
|
|
149
|
-
return input.trim().toLowerCase()
|
|
150
|
-
})
|
|
148
|
+
const normalizeUser = Effect.fn(function* (input: string) {
|
|
149
|
+
return input.trim().toLowerCase();
|
|
150
|
+
});
|
|
151
151
|
```
|
|
152
152
|
|
|
153
153
|
Over this:
|
|
154
154
|
|
|
155
155
|
```ts
|
|
156
|
-
const normalizeUser = Effect.fnUntraced(function*(input: string) {
|
|
157
|
-
return input.trim().toLowerCase()
|
|
158
|
-
})
|
|
156
|
+
const normalizeUser = Effect.fnUntraced(function* (input: string) {
|
|
157
|
+
return input.trim().toLowerCase();
|
|
158
|
+
});
|
|
159
159
|
```
|
|
160
160
|
|
|
161
161
|
Typical cases:
|
|
@@ -177,18 +177,18 @@ Preferred rule:
|
|
|
177
177
|
Good:
|
|
178
178
|
|
|
179
179
|
```ts
|
|
180
|
-
const parseCommand = Effect.fn("parseCommand")(function*(input: string) {
|
|
181
|
-
return input.trim()
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
const loadUser = Effect.fn("loadUser")(function*(userId: string) {
|
|
185
|
-
return { id: userId, name: "Ada" }
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
const sendWelcomeEmail = Effect.fn("sendWelcomeEmail")(function*(userId: string) {
|
|
189
|
-
const user = yield* loadUser(userId)
|
|
190
|
-
return user.email
|
|
191
|
-
})
|
|
180
|
+
const parseCommand = Effect.fn("parseCommand")(function* (input: string) {
|
|
181
|
+
return input.trim();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const loadUser = Effect.fn("loadUser")(function* (userId: string) {
|
|
185
|
+
return { id: userId, name: "Ada" };
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const sendWelcomeEmail = Effect.fn("sendWelcomeEmail")(function* (userId: string) {
|
|
189
|
+
const user = yield* loadUser(userId);
|
|
190
|
+
return user.email;
|
|
191
|
+
});
|
|
192
192
|
```
|
|
193
193
|
|
|
194
194
|
Why this is good:
|
|
@@ -223,17 +223,13 @@ Use `withSpan` when you need an explicit span around an effect that is not alrea
|
|
|
223
223
|
Example:
|
|
224
224
|
|
|
225
225
|
```ts
|
|
226
|
-
import { Effect } from "effect"
|
|
226
|
+
import { Effect } from "effect";
|
|
227
227
|
|
|
228
|
-
const syncUser = Effect.fn("syncUser")(function*(userId: string) {
|
|
229
|
-
const profile = yield* fetchProfile(userId).pipe(
|
|
230
|
-
Effect.withSpan("fetchProfile")
|
|
231
|
-
)
|
|
228
|
+
const syncUser = Effect.fn("syncUser")(function* (userId: string) {
|
|
229
|
+
const profile = yield* fetchProfile(userId).pipe(Effect.withSpan("fetchProfile"));
|
|
232
230
|
|
|
233
|
-
return yield* persistProfile(profile).pipe(
|
|
234
|
-
|
|
235
|
-
)
|
|
236
|
-
})
|
|
231
|
+
return yield* persistProfile(profile).pipe(Effect.withSpan("persistProfile"));
|
|
232
|
+
});
|
|
237
233
|
```
|
|
238
234
|
|
|
239
235
|
Use this when:
|
|
@@ -263,12 +259,12 @@ Use `annotateCurrentSpan` to attach important structured fields to the current s
|
|
|
263
259
|
Example:
|
|
264
260
|
|
|
265
261
|
```ts
|
|
266
|
-
import { Effect } from "effect"
|
|
262
|
+
import { Effect } from "effect";
|
|
267
263
|
|
|
268
|
-
const loadUser = Effect.fn("loadUser")(function*(userId: string) {
|
|
269
|
-
yield* Effect.annotateCurrentSpan({ userId })
|
|
270
|
-
return { id: userId, name: "Ada" }
|
|
271
|
-
})
|
|
264
|
+
const loadUser = Effect.fn("loadUser")(function* (userId: string) {
|
|
265
|
+
yield* Effect.annotateCurrentSpan({ userId });
|
|
266
|
+
return { id: userId, name: "Ada" };
|
|
267
|
+
});
|
|
272
268
|
```
|
|
273
269
|
|
|
274
270
|
Good span annotations:
|
|
@@ -301,10 +297,10 @@ These integrate with the current Effect execution context.
|
|
|
301
297
|
Example:
|
|
302
298
|
|
|
303
299
|
```ts
|
|
304
|
-
const loadUser = Effect.fn("loadUser")(function*(userId: string) {
|
|
305
|
-
yield* Effect.logDebug("loading user", { userId })
|
|
306
|
-
return { id: userId, name: "Ada" }
|
|
307
|
-
})
|
|
300
|
+
const loadUser = Effect.fn("loadUser")(function* (userId: string) {
|
|
301
|
+
yield* Effect.logDebug("loading user", { userId });
|
|
302
|
+
return { id: userId, name: "Ada" };
|
|
303
|
+
});
|
|
308
304
|
```
|
|
309
305
|
|
|
310
306
|
### `Effect.withLogSpan`
|
|
@@ -314,9 +310,7 @@ Use `withLogSpan` when you want log messages to carry a local logical span label
|
|
|
314
310
|
Example:
|
|
315
311
|
|
|
316
312
|
```ts
|
|
317
|
-
const program = Effect.logInfo("starting sync").pipe(
|
|
318
|
-
Effect.withLogSpan("user-sync")
|
|
319
|
-
)
|
|
313
|
+
const program = Effect.logInfo("starting sync").pipe(Effect.withLogSpan("user-sync"));
|
|
320
314
|
```
|
|
321
315
|
|
|
322
316
|
This is useful for:
|
|
@@ -348,19 +342,19 @@ site.
|
|
|
348
342
|
|
|
349
343
|
```ts
|
|
350
344
|
const runCheckout = (request: CheckoutRequest) =>
|
|
351
|
-
Effect.gen(function*() {
|
|
352
|
-
yield* Effect.logInfo("loading checkout state")
|
|
353
|
-
yield* validateCart(request.cart)
|
|
354
|
-
yield* Effect.logInfo("submitting payment")
|
|
355
|
-
yield* submitPayment(request.payment)
|
|
345
|
+
Effect.gen(function* () {
|
|
346
|
+
yield* Effect.logInfo("loading checkout state");
|
|
347
|
+
yield* validateCart(request.cart);
|
|
348
|
+
yield* Effect.logInfo("submitting payment");
|
|
349
|
+
yield* submitPayment(request.payment);
|
|
356
350
|
}).pipe(
|
|
357
351
|
Effect.annotateLogs({
|
|
358
352
|
operation: "checkout",
|
|
359
353
|
requestId: request.id,
|
|
360
|
-
cartId: request.cart.id
|
|
354
|
+
cartId: request.cart.id,
|
|
361
355
|
}),
|
|
362
|
-
Effect.withLogSpan("checkout")
|
|
363
|
-
)
|
|
356
|
+
Effect.withLogSpan("checkout"),
|
|
357
|
+
);
|
|
364
358
|
```
|
|
365
359
|
|
|
366
360
|
Add narrower annotations only when metadata belongs to a nested item, attempt,
|
|
@@ -388,18 +382,13 @@ Repo reference:
|
|
|
388
382
|
Example:
|
|
389
383
|
|
|
390
384
|
```ts
|
|
391
|
-
import { Effect, Metric } from "effect"
|
|
385
|
+
import { Effect, Metric } from "effect";
|
|
392
386
|
|
|
393
|
-
const requests = Metric.counter("user_load_requests").pipe(
|
|
394
|
-
Metric.withConstantInput(1)
|
|
395
|
-
)
|
|
387
|
+
const requests = Metric.counter("user_load_requests").pipe(Metric.withConstantInput(1));
|
|
396
388
|
|
|
397
|
-
const loadUser = Effect.fn("loadUser")(
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
},
|
|
401
|
-
Effect.track(requests)
|
|
402
|
-
)
|
|
389
|
+
const loadUser = Effect.fn("loadUser")(function* (userId: string) {
|
|
390
|
+
return { id: userId, name: "Ada" };
|
|
391
|
+
}, Effect.track(requests));
|
|
403
392
|
```
|
|
404
393
|
|
|
405
394
|
### Prefer boundary metrics over micro-metrics
|
|
@@ -432,14 +421,10 @@ Repo references:
|
|
|
432
421
|
Preferred composition style:
|
|
433
422
|
|
|
434
423
|
```ts
|
|
435
|
-
const AppLayer = Layer.mergeAll(
|
|
436
|
-
UserLayer,
|
|
437
|
-
BillingLayer,
|
|
438
|
-
HttpLayer
|
|
439
|
-
).pipe(
|
|
424
|
+
const AppLayer = Layer.mergeAll(UserLayer, BillingLayer, HttpLayer).pipe(
|
|
440
425
|
Layer.provide(Telemetry),
|
|
441
|
-
Layer.provide(NodeSdk)
|
|
442
|
-
)
|
|
426
|
+
Layer.provide(NodeSdk),
|
|
427
|
+
);
|
|
443
428
|
```
|
|
444
429
|
|
|
445
430
|
Why:
|
|
@@ -628,19 +613,14 @@ Example shape:
|
|
|
628
613
|
const TelemetryLayer = NodeSdk.layer(() => ({
|
|
629
614
|
resource: {
|
|
630
615
|
serviceName: "todo-service",
|
|
631
|
-
serviceVersion: "1.0.0"
|
|
616
|
+
serviceVersion: "1.0.0",
|
|
632
617
|
},
|
|
633
618
|
spanProcessor: mySpanProcessor,
|
|
634
619
|
metricReader: myMetricReader,
|
|
635
|
-
logRecordProcessor: myLogProcessor
|
|
636
|
-
}))
|
|
637
|
-
|
|
638
|
-
const AppLayer = Layer.mergeAll(
|
|
639
|
-
DomainLayer,
|
|
640
|
-
HttpLayer
|
|
641
|
-
).pipe(
|
|
642
|
-
Layer.provide(TelemetryLayer)
|
|
643
|
-
)
|
|
620
|
+
logRecordProcessor: myLogProcessor,
|
|
621
|
+
}));
|
|
622
|
+
|
|
623
|
+
const AppLayer = Layer.mergeAll(DomainLayer, HttpLayer).pipe(Layer.provide(TelemetryLayer));
|
|
644
624
|
```
|
|
645
625
|
|
|
646
626
|
This keeps:
|
|
@@ -666,9 +646,9 @@ Bad:
|
|
|
666
646
|
|
|
667
647
|
```ts
|
|
668
648
|
const loadUser = (userId: string) =>
|
|
669
|
-
Effect.gen(function*() {
|
|
670
|
-
return { id: userId, name: "Ada" }
|
|
671
|
-
})
|
|
649
|
+
Effect.gen(function* () {
|
|
650
|
+
return { id: userId, name: "Ada" };
|
|
651
|
+
});
|
|
672
652
|
```
|
|
673
653
|
|
|
674
654
|
Why this is bad:
|
|
@@ -680,9 +660,9 @@ Why this is bad:
|
|
|
680
660
|
Preferred:
|
|
681
661
|
|
|
682
662
|
```ts
|
|
683
|
-
const loadUser = Effect.fn("loadUser")(function*(userId: string) {
|
|
684
|
-
return { id: userId, name: "Ada" }
|
|
685
|
-
})
|
|
663
|
+
const loadUser = Effect.fn("loadUser")(function* (userId: string) {
|
|
664
|
+
return { id: userId, name: "Ada" };
|
|
665
|
+
});
|
|
686
666
|
```
|
|
687
667
|
|
|
688
668
|
### Anti-Pattern: using `Effect.fnUntraced` by default
|
|
@@ -729,36 +709,31 @@ Preferred order:
|
|
|
729
709
|
### Pattern: observable business operation
|
|
730
710
|
|
|
731
711
|
```ts
|
|
732
|
-
import { Effect } from "effect"
|
|
712
|
+
import { Effect } from "effect";
|
|
733
713
|
|
|
734
|
-
const fetchUser = Effect.fn("fetchUser")(function*(userId: string) {
|
|
735
|
-
yield* Effect.annotateCurrentSpan({ userId })
|
|
736
|
-
yield* Effect.logDebug("fetching user", { userId })
|
|
737
|
-
return { id: userId, name: "Ada" }
|
|
738
|
-
})
|
|
714
|
+
const fetchUser = Effect.fn("fetchUser")(function* (userId: string) {
|
|
715
|
+
yield* Effect.annotateCurrentSpan({ userId });
|
|
716
|
+
yield* Effect.logDebug("fetching user", { userId });
|
|
717
|
+
return { id: userId, name: "Ada" };
|
|
718
|
+
});
|
|
739
719
|
```
|
|
740
720
|
|
|
741
721
|
### Pattern: orchestration with nested spans
|
|
742
722
|
|
|
743
723
|
```ts
|
|
744
|
-
const syncUser = Effect.fn("syncUser")(function*(userId: string) {
|
|
745
|
-
const profile = yield* fetchRemoteProfile(userId).pipe(
|
|
746
|
-
|
|
747
|
-
)
|
|
748
|
-
|
|
749
|
-
return yield* persistProfile(profile).pipe(
|
|
750
|
-
Effect.withSpan("persistProfile")
|
|
751
|
-
)
|
|
752
|
-
})
|
|
724
|
+
const syncUser = Effect.fn("syncUser")(function* (userId: string) {
|
|
725
|
+
const profile = yield* fetchRemoteProfile(userId).pipe(Effect.withSpan("fetchRemoteProfile"));
|
|
726
|
+
|
|
727
|
+
return yield* persistProfile(profile).pipe(Effect.withSpan("persistProfile"));
|
|
728
|
+
});
|
|
753
729
|
```
|
|
754
730
|
|
|
755
731
|
### Pattern: framework boundary with runtime
|
|
756
732
|
|
|
757
733
|
```ts
|
|
758
|
-
const runtime = ManagedRuntime.make(AppLayer)
|
|
734
|
+
const runtime = ManagedRuntime.make(AppLayer);
|
|
759
735
|
|
|
760
|
-
const handleRequest = (userId: string) =>
|
|
761
|
-
runtime.runPromise(fetchUser(userId))
|
|
736
|
+
const handleRequest = (userId: string) => runtime.runPromise(fetchUser(userId));
|
|
762
737
|
```
|
|
763
738
|
|
|
764
739
|
## Good Repo Examples To Study
|
|
@@ -70,9 +70,7 @@ This is explicitly covered in the module tests.
|
|
|
70
70
|
Use this for the simplest bounded retry case.
|
|
71
71
|
|
|
72
72
|
```ts
|
|
73
|
-
const retried = effect.pipe(
|
|
74
|
-
Effect.retry({ times: 3 })
|
|
75
|
-
)
|
|
73
|
+
const retried = effect.pipe(Effect.retry({ times: 3 }));
|
|
76
74
|
```
|
|
77
75
|
|
|
78
76
|
Use this when:
|
|
@@ -93,9 +91,7 @@ The module tests show:
|
|
|
93
91
|
Example:
|
|
94
92
|
|
|
95
93
|
```ts
|
|
96
|
-
const retried = effect.pipe(
|
|
97
|
-
Effect.retry({ until: (error) => error._tag === "Done" })
|
|
98
|
-
)
|
|
94
|
+
const retried = effect.pipe(Effect.retry({ until: (error) => error._tag === "Done" }));
|
|
99
95
|
```
|
|
100
96
|
|
|
101
97
|
### `{ while: predicate }`
|
|
@@ -107,9 +103,7 @@ The tests also show pure and effectful `while` variants.
|
|
|
107
103
|
Example:
|
|
108
104
|
|
|
109
105
|
```ts
|
|
110
|
-
const retried = effect.pipe(
|
|
111
|
-
Effect.retry({ while: (error) => error._tag === "Retryable" })
|
|
112
|
-
)
|
|
106
|
+
const retried = effect.pipe(Effect.retry({ while: (error) => error._tag === "Retryable" }));
|
|
113
107
|
```
|
|
114
108
|
|
|
115
109
|
## Retry With Schedule
|
|
@@ -117,9 +111,7 @@ const retried = effect.pipe(
|
|
|
117
111
|
Use a `Schedule` whenever timing matters.
|
|
118
112
|
|
|
119
113
|
```ts
|
|
120
|
-
const retried = effect.pipe(
|
|
121
|
-
Effect.retry(Schedule.recurs(3))
|
|
122
|
-
)
|
|
114
|
+
const retried = effect.pipe(Effect.retry(Schedule.recurs(3)));
|
|
123
115
|
```
|
|
124
116
|
|
|
125
117
|
Or with the richer object form:
|
|
@@ -128,9 +120,9 @@ Or with the richer object form:
|
|
|
128
120
|
const retried = effect.pipe(
|
|
129
121
|
Effect.retry({
|
|
130
122
|
schedule: Schedule.recurs(3),
|
|
131
|
-
while: (error) => error._tag === "Retryable"
|
|
132
|
-
})
|
|
133
|
-
)
|
|
123
|
+
while: (error) => error._tag === "Retryable",
|
|
124
|
+
}),
|
|
125
|
+
);
|
|
134
126
|
```
|
|
135
127
|
|
|
136
128
|
This is a very important repo pattern because it lets you combine:
|
|
@@ -178,13 +170,13 @@ The canonical source repeatedly uses these patterns:
|
|
|
178
170
|
### Fixed retry count
|
|
179
171
|
|
|
180
172
|
```ts
|
|
181
|
-
Schedule.recurs(3)
|
|
173
|
+
Schedule.recurs(3);
|
|
182
174
|
```
|
|
183
175
|
|
|
184
176
|
### Exponential backoff
|
|
185
177
|
|
|
186
178
|
```ts
|
|
187
|
-
Schedule.exponential("500 millis", 1.5)
|
|
179
|
+
Schedule.exponential("500 millis", 1.5);
|
|
188
180
|
```
|
|
189
181
|
|
|
190
182
|
### Exponential plus steady fallback spacing
|
|
@@ -193,7 +185,7 @@ Schedule.exponential("500 millis", 1.5)
|
|
|
193
185
|
Schedule.exponential("500 millis", 1.5).pipe(
|
|
194
186
|
Schedule.upTo({ times: 4 }),
|
|
195
187
|
Schedule.andThen(Schedule.spaced("5 seconds")),
|
|
196
|
-
)
|
|
188
|
+
);
|
|
197
189
|
```
|
|
198
190
|
|
|
199
191
|
This appears in production modules such as RPC and workflow code.
|
|
@@ -203,10 +195,8 @@ This appears in production modules such as RPC and workflow code.
|
|
|
203
195
|
```ts
|
|
204
196
|
Schedule.forever.pipe(
|
|
205
197
|
Schedule.setInputType<RetryableError>(),
|
|
206
|
-
Schedule.addDelay(({ input: error }) =>
|
|
207
|
-
|
|
208
|
-
),
|
|
209
|
-
)
|
|
198
|
+
Schedule.addDelay(({ input: error }) => Effect.succeed(error.retryAfter ?? "1 second")),
|
|
199
|
+
);
|
|
210
200
|
```
|
|
211
201
|
|
|
212
202
|
The OTLP exporter uses this shape to derive delays from actual HTTP failure details such as rate limits.
|
|
@@ -227,11 +217,11 @@ effect.pipe(
|
|
|
227
217
|
Effect.retry(policy),
|
|
228
218
|
Effect.catch((cause) => {
|
|
229
219
|
if (!Cause.hasInterrupts(cause)) {
|
|
230
|
-
return Effect.failCause(cause)
|
|
220
|
+
return Effect.failCause(cause);
|
|
231
221
|
}
|
|
232
|
-
return Effect.die("interrupted and retries exhausted")
|
|
233
|
-
})
|
|
234
|
-
)
|
|
222
|
+
return Effect.die("interrupted and retries exhausted");
|
|
223
|
+
}),
|
|
224
|
+
);
|
|
235
225
|
```
|
|
236
226
|
|
|
237
227
|
Use this when:
|
|
@@ -298,17 +288,17 @@ const Plan = ExecutionPlan.make(
|
|
|
298
288
|
{
|
|
299
289
|
provide: FastLayer,
|
|
300
290
|
attempts: 2,
|
|
301
|
-
schedule: Schedule.spaced("3 seconds")
|
|
291
|
+
schedule: Schedule.spaced("3 seconds"),
|
|
302
292
|
},
|
|
303
293
|
{
|
|
304
294
|
provide: SafeLayer,
|
|
305
295
|
attempts: 3,
|
|
306
|
-
schedule: Schedule.spaced("1 second")
|
|
296
|
+
schedule: Schedule.spaced("1 second"),
|
|
307
297
|
},
|
|
308
298
|
{
|
|
309
|
-
provide: FinalFallbackLayer
|
|
310
|
-
}
|
|
311
|
-
)
|
|
299
|
+
provide: FinalFallbackLayer,
|
|
300
|
+
},
|
|
301
|
+
);
|
|
312
302
|
```
|
|
313
303
|
|
|
314
304
|
### Step Semantics
|
|
@@ -358,7 +348,7 @@ one whose requirements are satisfied from the current context.
|
|
|
358
348
|
Use this when the plan should be frozen with the current environment before being applied later.
|
|
359
349
|
|
|
360
350
|
```ts
|
|
361
|
-
const capturedPlan = yield* Plan.captureRequirements
|
|
351
|
+
const capturedPlan = yield * Plan.captureRequirements;
|
|
362
352
|
```
|
|
363
353
|
|
|
364
354
|
## `ExecutionPlan.merge`
|
|
@@ -385,9 +375,7 @@ Use `ExecutionPlan` when:
|
|
|
385
375
|
### Pattern: simple bounded retry
|
|
386
376
|
|
|
387
377
|
```ts
|
|
388
|
-
const retried = effect.pipe(
|
|
389
|
-
Effect.retry({ times: 3 })
|
|
390
|
-
)
|
|
378
|
+
const retried = effect.pipe(Effect.retry({ times: 3 }));
|
|
391
379
|
```
|
|
392
380
|
|
|
393
381
|
### Pattern: retryable-error backoff
|
|
@@ -396,14 +384,14 @@ const retried = effect.pipe(
|
|
|
396
384
|
const retryPolicy = Schedule.exponential("500 millis", 1.5).pipe(
|
|
397
385
|
Schedule.upTo({ times: 4 }),
|
|
398
386
|
Schedule.andThen(Schedule.spaced("5 seconds")),
|
|
399
|
-
)
|
|
387
|
+
);
|
|
400
388
|
|
|
401
389
|
const retried = effect.pipe(
|
|
402
390
|
Effect.retry({
|
|
403
391
|
schedule: retryPolicy,
|
|
404
|
-
while: (error) => error._tag === "Retryable"
|
|
405
|
-
})
|
|
406
|
-
)
|
|
392
|
+
while: (error) => error._tag === "Retryable",
|
|
393
|
+
}),
|
|
394
|
+
);
|
|
407
395
|
```
|
|
408
396
|
|
|
409
397
|
### Pattern: fallback across providers
|
|
@@ -413,17 +401,17 @@ const Plan = ExecutionPlan.make(
|
|
|
413
401
|
{
|
|
414
402
|
provide: PrimaryLayer,
|
|
415
403
|
attempts: 2,
|
|
416
|
-
schedule: Schedule.spaced("1 second")
|
|
404
|
+
schedule: Schedule.spaced("1 second"),
|
|
417
405
|
},
|
|
418
406
|
{
|
|
419
407
|
provide: SecondaryLayer,
|
|
420
408
|
attempts: 3,
|
|
421
|
-
schedule: Schedule.exponential(500, 1.5)
|
|
409
|
+
schedule: Schedule.exponential(500, 1.5),
|
|
422
410
|
},
|
|
423
411
|
{
|
|
424
|
-
provide: FinalFallbackLayer
|
|
425
|
-
}
|
|
426
|
-
)
|
|
412
|
+
provide: FinalFallbackLayer,
|
|
413
|
+
},
|
|
414
|
+
);
|
|
427
415
|
```
|
|
428
416
|
|
|
429
417
|
## Anti-Patterns
|