@danieljvdm/dev-kit 0.5.0 → 0.7.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 +161 -68
- package/dev-kit.example.jsonc +8 -3
- package/package.json +19 -15
- package/schema/dev-kit.schema.json +52 -0
- package/skill-sources.jsonc +8 -12
- package/skill-sources.lock.json +3 -9
- package/skills/dev-kit/SKILL.md +74 -24
- package/skills/effect-atom-data-fetching/SKILL.md +40 -0
- package/skills/effect-atom-data-fetching/agents/openai.yaml +4 -0
- package/skills/effect-atom-data-fetching/references/cache-lifecycle.md +72 -0
- package/skills/effect-atom-data-fetching/references/http-and-invalidation.md +93 -0
- package/skills/effect-atom-data-fetching/references/tanstack-start.md +69 -0
- package/skills/effect-atom-data-fetching/references/testing.md +63 -0
- 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 +72 -34
- 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 +12 -0
- package/src/manifest.ts +51 -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 +190 -75
- package/src/path-digest.ts +37 -10
- package/src/project-package.ts +59 -0
- package/src/project-process-lock.ts +19 -12
- package/src/project-state.ts +29 -2
- 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 +491 -121
- package/src/vendor.ts +112 -42
- package/src/vite-plus-hooks.ts +174 -0
- package/src/vite-plus-quality.ts +49 -0
- package/templates/AGENTS.md +9 -0
- package/templates/vite-plus/github-actions-check.yml +44 -0
- package/templates/vite-plus/vite.config.ts +22 -0
|
@@ -49,9 +49,9 @@ attempt limits, and backoff in the schedule.
|
|
|
49
49
|
Use the operator that matches the control flow:
|
|
50
50
|
|
|
51
51
|
```ts
|
|
52
|
-
const retried = Effect.retry(loadRemote, retryPolicy)
|
|
53
|
-
const repeated = Effect.repeat(refreshCache, refreshPolicy)
|
|
54
|
-
const scheduled = Effect.schedule(runJob, nightly)
|
|
52
|
+
const retried = Effect.retry(loadRemote, retryPolicy);
|
|
53
|
+
const repeated = Effect.repeat(refreshCache, refreshPolicy);
|
|
54
|
+
const scheduled = Effect.schedule(runJob, nightly);
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
- `retry` steps the schedule when the effect fails; the schedule input is the
|
|
@@ -69,7 +69,7 @@ before the schedule is stepped, so `recurs(3)` permits one initial attempt and
|
|
|
69
69
|
up to three retries.
|
|
70
70
|
|
|
71
71
|
```ts
|
|
72
|
-
const retryThreeTimes = Schedule.recurs(3)
|
|
72
|
+
const retryThreeTimes = Schedule.recurs(3);
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
### `Schedule.forever`
|
|
@@ -78,7 +78,7 @@ const retryThreeTimes = Schedule.recurs(3)
|
|
|
78
78
|
combinators unless a tight loop is intentional.
|
|
79
79
|
|
|
80
80
|
```ts
|
|
81
|
-
const unbounded = Schedule.forever
|
|
81
|
+
const unbounded = Schedule.forever;
|
|
82
82
|
```
|
|
83
83
|
|
|
84
84
|
### `Schedule.spaced`
|
|
@@ -86,7 +86,7 @@ const unbounded = Schedule.forever
|
|
|
86
86
|
Use `spaced` when each delay starts after the preceding action completes.
|
|
87
87
|
|
|
88
88
|
```ts
|
|
89
|
-
const pollEverySecond = Schedule.spaced("1 second")
|
|
89
|
+
const pollEverySecond = Schedule.spaced("1 second");
|
|
90
90
|
```
|
|
91
91
|
|
|
92
92
|
### `Schedule.fixed`
|
|
@@ -95,7 +95,7 @@ Use `fixed` for a regular cadence that accounts for the time spent running the
|
|
|
95
95
|
action. This differs from naïve spacing when the action itself is slow.
|
|
96
96
|
|
|
97
97
|
```ts
|
|
98
|
-
const everyMinute = Schedule.fixed("1 minute")
|
|
98
|
+
const everyMinute = Schedule.fixed("1 minute");
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
### `Schedule.windowed`
|
|
@@ -103,7 +103,7 @@ const everyMinute = Schedule.fixed("1 minute")
|
|
|
103
103
|
Use `windowed` to align work to the nearest interval boundary.
|
|
104
104
|
|
|
105
105
|
```ts
|
|
106
|
-
const flushOnTenSecondWindows = Schedule.windowed("10 seconds")
|
|
106
|
+
const flushOnTenSecondWindows = Schedule.windowed("10 seconds");
|
|
107
107
|
```
|
|
108
108
|
|
|
109
109
|
### `Schedule.duration`
|
|
@@ -111,7 +111,7 @@ const flushOnTenSecondWindows = Schedule.windowed("10 seconds")
|
|
|
111
111
|
`duration` recurs once after the given delay, then completes.
|
|
112
112
|
|
|
113
113
|
```ts
|
|
114
|
-
const onceAfterOneSecond = Schedule.duration("1 second")
|
|
114
|
+
const onceAfterOneSecond = Schedule.duration("1 second");
|
|
115
115
|
```
|
|
116
116
|
|
|
117
117
|
### `Schedule.during`
|
|
@@ -119,7 +119,7 @@ const onceAfterOneSecond = Schedule.duration("1 second")
|
|
|
119
119
|
`during` recurs only within an elapsed-time window.
|
|
120
120
|
|
|
121
121
|
```ts
|
|
122
|
-
const forThirtySeconds = Schedule.during("30 seconds")
|
|
122
|
+
const forThirtySeconds = Schedule.during("30 seconds");
|
|
123
123
|
```
|
|
124
124
|
|
|
125
125
|
### `Schedule.cron`
|
|
@@ -129,7 +129,7 @@ with `CronParseError`, so keep that failure visible where the expression is not
|
|
|
129
129
|
a trusted constant.
|
|
130
130
|
|
|
131
131
|
```ts
|
|
132
|
-
const nightly = Schedule.cron("0 0 * * *")
|
|
132
|
+
const nightly = Schedule.cron("0 0 * * *");
|
|
133
133
|
```
|
|
134
134
|
|
|
135
135
|
## Backoff
|
|
@@ -139,7 +139,7 @@ const nightly = Schedule.cron("0 0 * * *")
|
|
|
139
139
|
Use exponential backoff for transient external failures.
|
|
140
140
|
|
|
141
141
|
```ts
|
|
142
|
-
const backoff = Schedule.exponential("100 millis", 2)
|
|
142
|
+
const backoff = Schedule.exponential("100 millis", 2);
|
|
143
143
|
```
|
|
144
144
|
|
|
145
145
|
Bound the policy explicitly:
|
|
@@ -148,7 +148,7 @@ Bound the policy explicitly:
|
|
|
148
148
|
const boundedBackoff = Schedule.exponential("100 millis").pipe(
|
|
149
149
|
Schedule.upTo({ duration: "30 seconds", times: 6 }),
|
|
150
150
|
Schedule.jittered,
|
|
151
|
-
)
|
|
151
|
+
);
|
|
152
152
|
```
|
|
153
153
|
|
|
154
154
|
### `Schedule.fibonacci`
|
|
@@ -156,9 +156,7 @@ const boundedBackoff = Schedule.exponential("100 millis").pipe(
|
|
|
156
156
|
Use Fibonacci backoff when growth should be gentler than exponential.
|
|
157
157
|
|
|
158
158
|
```ts
|
|
159
|
-
const fibonacciBackoff = Schedule.fibonacci("100 millis").pipe(
|
|
160
|
-
Schedule.upTo({ times: 6 }),
|
|
161
|
-
)
|
|
159
|
+
const fibonacciBackoff = Schedule.fibonacci("100 millis").pipe(Schedule.upTo({ times: 6 }));
|
|
162
160
|
```
|
|
163
161
|
|
|
164
162
|
### `Schedule.jittered`
|
|
@@ -167,9 +165,7 @@ Use jitter for distributed clients, workers, or polling loops that might
|
|
|
167
165
|
otherwise synchronize their retries.
|
|
168
166
|
|
|
169
167
|
```ts
|
|
170
|
-
const jittered = Schedule.exponential("200 millis").pipe(
|
|
171
|
-
Schedule.jittered,
|
|
172
|
-
)
|
|
168
|
+
const jittered = Schedule.exponential("200 millis").pipe(Schedule.jittered);
|
|
173
169
|
```
|
|
174
170
|
|
|
175
171
|
## Bounding Policies
|
|
@@ -184,7 +180,7 @@ const bounded = Schedule.spaced("1 second").pipe(
|
|
|
184
180
|
duration: "20 seconds",
|
|
185
181
|
times: 5,
|
|
186
182
|
}),
|
|
187
|
-
)
|
|
183
|
+
);
|
|
188
184
|
```
|
|
189
185
|
|
|
190
186
|
Prefer `upTo` over rebuilding count and elapsed-time checks with mutable state.
|
|
@@ -198,10 +194,8 @@ Use `andThen` when one policy should complete before another begins.
|
|
|
198
194
|
```ts
|
|
199
195
|
const quickThenSlow = Schedule.exponential("100 millis").pipe(
|
|
200
196
|
Schedule.upTo({ times: 3 }),
|
|
201
|
-
Schedule.andThen(
|
|
202
|
-
|
|
203
|
-
),
|
|
204
|
-
)
|
|
197
|
+
Schedule.andThen(Schedule.spaced("5 seconds").pipe(Schedule.upTo({ times: 5 }))),
|
|
198
|
+
);
|
|
205
199
|
```
|
|
206
200
|
|
|
207
201
|
This is the v4 replacement for older examples that used `Schedule.either` to
|
|
@@ -221,10 +215,7 @@ largest delay. Use it to enforce several stop conditions while retaining the
|
|
|
221
215
|
slowest applicable cadence.
|
|
222
216
|
|
|
223
217
|
```ts
|
|
224
|
-
const countedBackoff = Schedule.max([
|
|
225
|
-
Schedule.exponential("100 millis"),
|
|
226
|
-
Schedule.recurs(5),
|
|
227
|
-
])
|
|
218
|
+
const countedBackoff = Schedule.max([Schedule.exponential("100 millis"), Schedule.recurs(5)]);
|
|
228
219
|
```
|
|
229
220
|
|
|
230
221
|
### `Schedule.min`
|
|
@@ -234,10 +225,7 @@ smallest available delay. Use it only when that "any policy may continue"
|
|
|
234
225
|
behavior is intended.
|
|
235
226
|
|
|
236
227
|
```ts
|
|
237
|
-
const fastestAvailable = Schedule.min([
|
|
238
|
-
Schedule.spaced("1 second"),
|
|
239
|
-
Schedule.spaced("5 seconds"),
|
|
240
|
-
])
|
|
228
|
+
const fastestAvailable = Schedule.min([Schedule.spaced("1 second"), Schedule.spaced("5 seconds")]);
|
|
241
229
|
```
|
|
242
230
|
|
|
243
231
|
Do not treat `max` and `min` as ordinary numeric delay combinators without
|
|
@@ -252,9 +240,9 @@ Use `modifyDelay` to replace the computed delay from schedule metadata.
|
|
|
252
240
|
```ts
|
|
253
241
|
const cappedDelay = Schedule.exponential("100 millis").pipe(
|
|
254
242
|
Schedule.modifyDelay(({ duration }) =>
|
|
255
|
-
Effect.succeed(Duration.min(duration, Duration.seconds(5)))
|
|
243
|
+
Effect.succeed(Duration.min(duration, Duration.seconds(5))),
|
|
256
244
|
),
|
|
257
|
-
)
|
|
245
|
+
);
|
|
258
246
|
```
|
|
259
247
|
|
|
260
248
|
### `Schedule.addDelay`
|
|
@@ -266,11 +254,9 @@ For retries, inspect the typed error through `metadata.input`.
|
|
|
266
254
|
const serverAware = Schedule.spaced("1 second").pipe(
|
|
267
255
|
Schedule.setInputType<RateLimitedError>(),
|
|
268
256
|
Schedule.addDelay(({ input: error }) =>
|
|
269
|
-
error._tag === "RateLimited"
|
|
270
|
-
? Effect.succeed(error.retryAfter)
|
|
271
|
-
: Effect.succeed(Duration.zero)
|
|
257
|
+
error._tag === "RateLimited" ? Effect.succeed(error.retryAfter) : Effect.succeed(Duration.zero),
|
|
272
258
|
),
|
|
273
|
-
)
|
|
259
|
+
);
|
|
274
260
|
```
|
|
275
261
|
|
|
276
262
|
### `Schedule.map`
|
|
@@ -299,9 +285,9 @@ const observed = Schedule.exponential("100 millis").pipe(
|
|
|
299
285
|
delay: String(duration),
|
|
300
286
|
errorTag: input._tag,
|
|
301
287
|
}),
|
|
302
|
-
)
|
|
288
|
+
),
|
|
303
289
|
),
|
|
304
|
-
)
|
|
290
|
+
);
|
|
305
291
|
```
|
|
306
292
|
|
|
307
293
|
## Advanced Construction And Inspection
|
|
@@ -57,16 +57,14 @@ Make Schema the source of truth for application data. Export the schema value
|
|
|
57
57
|
and derive its decoded TypeScript type from `.Type` under the same name.
|
|
58
58
|
|
|
59
59
|
```ts
|
|
60
|
-
export const ArtifactId = Schema.NonEmptyString.pipe(
|
|
61
|
-
|
|
62
|
-
)
|
|
63
|
-
export type ArtifactId = typeof ArtifactId.Type
|
|
60
|
+
export const ArtifactId = Schema.NonEmptyString.pipe(Schema.brand("@acme/ArtifactId"));
|
|
61
|
+
export type ArtifactId = typeof ArtifactId.Type;
|
|
64
62
|
|
|
65
63
|
export const GenerateInput = Schema.Struct({
|
|
66
64
|
artifactId: ArtifactId,
|
|
67
|
-
prompt: Schema.String
|
|
68
|
-
})
|
|
69
|
-
export type GenerateInput = typeof GenerateInput.Type
|
|
65
|
+
prompt: Schema.String,
|
|
66
|
+
});
|
|
67
|
+
export type GenerateInput = typeof GenerateInput.Type;
|
|
70
68
|
```
|
|
71
69
|
|
|
72
70
|
Use schema classes for named reusable models when their validated construction,
|
|
@@ -127,14 +125,14 @@ Bad pattern:
|
|
|
127
125
|
const Todo = Schema.Struct({
|
|
128
126
|
id: Schema.Number,
|
|
129
127
|
title: Schema.String,
|
|
130
|
-
completed: Schema.Boolean
|
|
131
|
-
})
|
|
128
|
+
completed: Schema.Boolean,
|
|
129
|
+
});
|
|
132
130
|
|
|
133
131
|
const TodoSql = Schema.Struct({
|
|
134
132
|
id: Schema.Number,
|
|
135
133
|
title: Schema.String,
|
|
136
|
-
completed: Schema.BooleanFromBit
|
|
137
|
-
})
|
|
134
|
+
completed: Schema.BooleanFromBit,
|
|
135
|
+
});
|
|
138
136
|
```
|
|
139
137
|
|
|
140
138
|
This is usually a sign that transformations are not being used properly.
|
|
@@ -165,23 +163,23 @@ When a schema represents a named domain model, reusable payload, or long-lived A
|
|
|
165
163
|
Prefer:
|
|
166
164
|
|
|
167
165
|
```ts
|
|
168
|
-
import { Schema } from "effect"
|
|
166
|
+
import { Schema } from "effect";
|
|
169
167
|
|
|
170
168
|
export class User extends Schema.Class<User>("User")({
|
|
171
169
|
id: Schema.String,
|
|
172
|
-
name: Schema.String
|
|
170
|
+
name: Schema.String,
|
|
173
171
|
}) {}
|
|
174
172
|
```
|
|
175
173
|
|
|
176
174
|
Over:
|
|
177
175
|
|
|
178
176
|
```ts
|
|
179
|
-
import { Schema } from "effect"
|
|
177
|
+
import { Schema } from "effect";
|
|
180
178
|
|
|
181
179
|
export const User = Schema.Struct({
|
|
182
180
|
id: Schema.String,
|
|
183
|
-
name: Schema.String
|
|
184
|
-
})
|
|
181
|
+
name: Schema.String,
|
|
182
|
+
});
|
|
185
183
|
```
|
|
186
184
|
|
|
187
185
|
Why `Class` variants are usually better:
|
|
@@ -248,8 +246,8 @@ Example:
|
|
|
248
246
|
const Todo = Schema.Struct({
|
|
249
247
|
id: Schema.Number,
|
|
250
248
|
title: Schema.String,
|
|
251
|
-
completed: Schema.Boolean
|
|
252
|
-
})
|
|
249
|
+
completed: Schema.Boolean,
|
|
250
|
+
});
|
|
253
251
|
```
|
|
254
252
|
|
|
255
253
|
## `Class`, `TaggedClass`, and `TaggedErrorClass`
|
|
@@ -261,7 +259,7 @@ Use for named reusable schema-backed models.
|
|
|
261
259
|
```ts
|
|
262
260
|
class Product extends Schema.Class<Product>("Product")({
|
|
263
261
|
id: Schema.String,
|
|
264
|
-
price: Schema.Number
|
|
262
|
+
price: Schema.Number,
|
|
265
263
|
}) {}
|
|
266
264
|
```
|
|
267
265
|
|
|
@@ -275,8 +273,8 @@ Prefer:
|
|
|
275
273
|
const todo = Todo.make({
|
|
276
274
|
id: 1,
|
|
277
275
|
title: "write docs",
|
|
278
|
-
completed: false
|
|
279
|
-
})
|
|
276
|
+
completed: false,
|
|
277
|
+
});
|
|
280
278
|
```
|
|
281
279
|
|
|
282
280
|
Over:
|
|
@@ -285,8 +283,8 @@ Over:
|
|
|
285
283
|
const todo = new Todo({
|
|
286
284
|
id: 1,
|
|
287
285
|
title: "write docs",
|
|
288
|
-
completed: false
|
|
289
|
-
})
|
|
286
|
+
completed: false,
|
|
287
|
+
});
|
|
290
288
|
```
|
|
291
289
|
|
|
292
290
|
Why:
|
|
@@ -308,12 +306,12 @@ Use for members of tagged unions.
|
|
|
308
306
|
|
|
309
307
|
```ts
|
|
310
308
|
class Circle extends Schema.TaggedClass<Circle>()("Circle", {
|
|
311
|
-
radius: Schema.Number
|
|
309
|
+
radius: Schema.Number,
|
|
312
310
|
}) {}
|
|
313
311
|
|
|
314
312
|
class Rectangle extends Schema.TaggedClass<Rectangle>()("Rectangle", {
|
|
315
313
|
width: Schema.Number,
|
|
316
|
-
height: Schema.Number
|
|
314
|
+
height: Schema.Number,
|
|
317
315
|
}) {}
|
|
318
316
|
```
|
|
319
317
|
|
|
@@ -323,7 +321,7 @@ Use for schema-backed typed errors.
|
|
|
323
321
|
|
|
324
322
|
```ts
|
|
325
323
|
class NotFound extends Schema.TaggedErrorClass<NotFound>()("NotFound", {
|
|
326
|
-
id: Schema.String
|
|
324
|
+
id: Schema.String,
|
|
327
325
|
}) {}
|
|
328
326
|
```
|
|
329
327
|
|
|
@@ -342,8 +340,8 @@ Prefer:
|
|
|
342
340
|
|
|
343
341
|
```ts
|
|
344
342
|
const Query = Schema.Struct({
|
|
345
|
-
search: Schema.optionalKey(Schema.String)
|
|
346
|
-
})
|
|
343
|
+
search: Schema.optionalKey(Schema.String),
|
|
344
|
+
});
|
|
347
345
|
```
|
|
348
346
|
|
|
349
347
|
Use `optional` when the value itself should be `A | undefined`, not just an omitted field.
|
|
@@ -353,24 +351,21 @@ Use `optional` when the value itself should be `A | undefined`, not just an omit
|
|
|
353
351
|
Use `Schema.Union([...])` for ordinary unions.
|
|
354
352
|
|
|
355
353
|
```ts
|
|
356
|
-
const Id = Schema.Union([
|
|
357
|
-
Schema.String,
|
|
358
|
-
Schema.Number
|
|
359
|
-
])
|
|
354
|
+
const Id = Schema.Union([Schema.String, Schema.Number]);
|
|
360
355
|
```
|
|
361
356
|
|
|
362
357
|
Prefer tagged unions for domain variants.
|
|
363
358
|
|
|
364
359
|
```ts
|
|
365
360
|
class Created extends Schema.TaggedClass<Created>()("Created", {
|
|
366
|
-
id: Schema.String
|
|
361
|
+
id: Schema.String,
|
|
367
362
|
}) {}
|
|
368
363
|
|
|
369
364
|
class Deleted extends Schema.TaggedClass<Deleted>()("Deleted", {
|
|
370
|
-
id: Schema.String
|
|
365
|
+
id: Schema.String,
|
|
371
366
|
}) {}
|
|
372
367
|
|
|
373
|
-
const TodoEvent = Schema.Union([Created, Deleted])
|
|
368
|
+
const TodoEvent = Schema.Union([Created, Deleted]);
|
|
374
369
|
```
|
|
375
370
|
|
|
376
371
|
Why:
|
|
@@ -384,14 +379,14 @@ Use `Schema.suspend` for recursive schemas.
|
|
|
384
379
|
|
|
385
380
|
```ts
|
|
386
381
|
type Tree = {
|
|
387
|
-
readonly name: string
|
|
388
|
-
readonly children: ReadonlyArray<Tree
|
|
389
|
-
}
|
|
382
|
+
readonly name: string;
|
|
383
|
+
readonly children: ReadonlyArray<Tree>;
|
|
384
|
+
};
|
|
390
385
|
|
|
391
386
|
const Tree: Schema.Schema<Tree> = Schema.Struct({
|
|
392
387
|
name: Schema.String,
|
|
393
|
-
children: Schema.Array(Schema.suspend((): Schema.Schema<Tree> => Tree))
|
|
394
|
-
})
|
|
388
|
+
children: Schema.Array(Schema.suspend((): Schema.Schema<Tree> => Tree)),
|
|
389
|
+
});
|
|
395
390
|
```
|
|
396
391
|
|
|
397
392
|
Use it whenever a schema refers to itself, directly or indirectly.
|
|
@@ -416,9 +411,9 @@ Use `decodeTo` when you want one schema to decode into another schema's type.
|
|
|
416
411
|
const TrimmedString = Schema.String.pipe(
|
|
417
412
|
Schema.decodeTo(Schema.String, {
|
|
418
413
|
decode: (value) => value.trim(),
|
|
419
|
-
encode: (value) => value
|
|
420
|
-
})
|
|
421
|
-
)
|
|
414
|
+
encode: (value) => value,
|
|
415
|
+
}),
|
|
416
|
+
);
|
|
422
417
|
```
|
|
423
418
|
|
|
424
419
|
The canonical docs explicitly note that `decodeTo` is curried and should be used with `pipe`.
|
|
@@ -432,19 +427,19 @@ Use `encodeTo` when the reverse direction reads more clearly.
|
|
|
432
427
|
Use `transformOrFail` when the transformation itself is effectful or may fail.
|
|
433
428
|
|
|
434
429
|
```ts
|
|
435
|
-
import * as Effect from "effect/Effect"
|
|
436
|
-
import * as Schema from "effect/Schema"
|
|
437
|
-
import * as SchemaTransformation from "effect/SchemaTransformation"
|
|
430
|
+
import * as Effect from "effect/Effect";
|
|
431
|
+
import * as Schema from "effect/Schema";
|
|
432
|
+
import * as SchemaTransformation from "effect/SchemaTransformation";
|
|
438
433
|
|
|
439
434
|
const VerifiedString = Schema.String.pipe(
|
|
440
435
|
Schema.decodeTo(
|
|
441
436
|
Schema.String,
|
|
442
437
|
SchemaTransformation.transformOrFail({
|
|
443
438
|
decode: (value) => Effect.succeed(value.trim()),
|
|
444
|
-
encode: (value) => Effect.succeed(value)
|
|
445
|
-
})
|
|
446
|
-
)
|
|
447
|
-
)
|
|
439
|
+
encode: (value) => Effect.succeed(value),
|
|
440
|
+
}),
|
|
441
|
+
),
|
|
442
|
+
);
|
|
448
443
|
```
|
|
449
444
|
|
|
450
445
|
Use this when:
|
|
@@ -460,13 +455,13 @@ Very often, the right answer is not a second object schema but a transformed fie
|
|
|
460
455
|
Example shape:
|
|
461
456
|
|
|
462
457
|
```ts
|
|
463
|
-
const Completed = Schema.BooleanFromBit
|
|
458
|
+
const Completed = Schema.BooleanFromBit;
|
|
464
459
|
|
|
465
460
|
const Todo = Schema.Struct({
|
|
466
461
|
id: Schema.Number,
|
|
467
462
|
title: Schema.String,
|
|
468
|
-
completed: Completed
|
|
469
|
-
})
|
|
463
|
+
completed: Completed,
|
|
464
|
+
});
|
|
470
465
|
```
|
|
471
466
|
|
|
472
467
|
In this pattern:
|
|
@@ -519,9 +514,7 @@ Use opaque or branded schemas when a value should stay distinct from its structu
|
|
|
519
514
|
Use `brand` for refined nominal distinctions.
|
|
520
515
|
|
|
521
516
|
```ts
|
|
522
|
-
const UserId = Schema.String.pipe(
|
|
523
|
-
Schema.brand("UserId")
|
|
524
|
-
)
|
|
517
|
+
const UserId = Schema.String.pipe(Schema.brand("UserId"));
|
|
525
518
|
```
|
|
526
519
|
|
|
527
520
|
This is useful for:
|
|
@@ -598,7 +591,7 @@ Preferred rule:
|
|
|
598
591
|
Good pattern:
|
|
599
592
|
|
|
600
593
|
```ts
|
|
601
|
-
const decodeUser = Schema.decodeUnknownEffect(User)
|
|
594
|
+
const decodeUser = Schema.decodeUnknownEffect(User);
|
|
602
595
|
```
|
|
603
596
|
|
|
604
597
|
## Schema Metadata And Derived Tooling
|
|
@@ -79,7 +79,7 @@ Prefer typed SQL queries instead of leaving row shapes implicit.
|
|
|
79
79
|
The repo uses typed query literals like:
|
|
80
80
|
|
|
81
81
|
```ts
|
|
82
|
-
const rows = yield* sql<{ id: number; name: string }>`SELECT * FROM test
|
|
82
|
+
const rows = yield * sql<{ id: number; name: string }>`SELECT * FROM test`;
|
|
83
83
|
```
|
|
84
84
|
|
|
85
85
|
This is the first level of typed SQL usage and is already better than untyped row access.
|
|
@@ -111,8 +111,8 @@ Why:
|
|
|
111
111
|
Avoid this pattern:
|
|
112
112
|
|
|
113
113
|
```ts
|
|
114
|
-
const row = yield* sql`SELECT id, title FROM todos WHERE id = ${id}
|
|
115
|
-
const todo = row[0] as TodoRow
|
|
114
|
+
const row = yield * sql`SELECT id, title FROM todos WHERE id = ${id}`;
|
|
115
|
+
const todo = row[0] as TodoRow;
|
|
116
116
|
```
|
|
117
117
|
|
|
118
118
|
Prefer:
|
|
@@ -126,10 +126,10 @@ Example boundary decode:
|
|
|
126
126
|
const TodoRow = Schema.Struct({
|
|
127
127
|
id: Schema.Number,
|
|
128
128
|
title: Schema.String,
|
|
129
|
-
completed: Schema.Boolean
|
|
130
|
-
})
|
|
129
|
+
completed: Schema.Boolean,
|
|
130
|
+
});
|
|
131
131
|
|
|
132
|
-
const decodeTodoRows = Schema.decodeUnknownEffect(Schema.Array(TodoRow))
|
|
132
|
+
const decodeTodoRows = Schema.decodeUnknownEffect(Schema.Array(TodoRow));
|
|
133
133
|
```
|
|
134
134
|
|
|
135
135
|
Then keep the query and decoding together in one SQL-aware operation.
|
|
@@ -150,11 +150,11 @@ const resolver = SqlResolver.findById({
|
|
|
150
150
|
Id: Schema.Number,
|
|
151
151
|
Result: Schema.Struct({
|
|
152
152
|
id: Schema.Number,
|
|
153
|
-
name: Schema.String
|
|
153
|
+
name: Schema.String,
|
|
154
154
|
}),
|
|
155
155
|
ResultId: (row) => row.id,
|
|
156
|
-
execute: (ids) => sql`SELECT * FROM test WHERE id IN ${sql.in(ids)}
|
|
157
|
-
})
|
|
156
|
+
execute: (ids) => sql`SELECT * FROM test WHERE id IN ${sql.in(ids)}`,
|
|
157
|
+
});
|
|
158
158
|
```
|
|
159
159
|
|
|
160
160
|
This is a preferred pattern when:
|
|
@@ -201,17 +201,17 @@ Prefer a layer that provides `SqlClient`, and let domain services depend on that
|
|
|
201
201
|
Good pattern:
|
|
202
202
|
|
|
203
203
|
```ts
|
|
204
|
-
import * as Context from "effect/Context"
|
|
205
|
-
import * as Effect from "effect/Effect"
|
|
206
|
-
import * as SqlClient from "effect/unstable/sql/SqlClient"
|
|
204
|
+
import * as Context from "effect/Context";
|
|
205
|
+
import * as Effect from "effect/Effect";
|
|
206
|
+
import * as SqlClient from "effect/unstable/sql/SqlClient";
|
|
207
207
|
|
|
208
208
|
class TodoRepo extends Context.Service<TodoRepo>()("TodoRepo", {
|
|
209
209
|
make: Effect.succeed({
|
|
210
|
-
getById: Effect.fn("TodoRepo.getById")(function*(id: number) {
|
|
211
|
-
const sql = yield* SqlClient.SqlClient
|
|
212
|
-
return yield* sql`SELECT id, title, completed FROM todos WHERE id = ${id}
|
|
213
|
-
})
|
|
214
|
-
})
|
|
210
|
+
getById: Effect.fn("TodoRepo.getById")(function* (id: number) {
|
|
211
|
+
const sql = yield* SqlClient.SqlClient;
|
|
212
|
+
return yield* sql`SELECT id, title, completed FROM todos WHERE id = ${id}`;
|
|
213
|
+
}),
|
|
214
|
+
}),
|
|
215
215
|
}) {}
|
|
216
216
|
```
|
|
217
217
|
|
|
@@ -233,16 +233,16 @@ Repo reference:
|
|
|
233
233
|
Prefer:
|
|
234
234
|
|
|
235
235
|
```ts
|
|
236
|
-
const createAndAudit = Effect.fn("TodoRepo.createAndAudit")(function*(title: string) {
|
|
237
|
-
const sql = yield* SqlClient.SqlClient
|
|
236
|
+
const createAndAudit = Effect.fn("TodoRepo.createAndAudit")(function* (title: string) {
|
|
237
|
+
const sql = yield* SqlClient.SqlClient;
|
|
238
238
|
|
|
239
239
|
return yield* sql.withTransaction(
|
|
240
|
-
Effect.gen(function*() {
|
|
241
|
-
yield* sql`INSERT INTO todos ${sql.insert({ title, completed: false })}
|
|
242
|
-
yield* sql`INSERT INTO audit_log ${sql.insert({ event: "todo_created" })}
|
|
243
|
-
})
|
|
244
|
-
)
|
|
245
|
-
})
|
|
240
|
+
Effect.gen(function* () {
|
|
241
|
+
yield* sql`INSERT INTO todos ${sql.insert({ title, completed: false })}`;
|
|
242
|
+
yield* sql`INSERT INTO audit_log ${sql.insert({ event: "todo_created" })}`;
|
|
243
|
+
}),
|
|
244
|
+
);
|
|
245
|
+
});
|
|
246
246
|
```
|
|
247
247
|
|
|
248
248
|
Avoid:
|
|
@@ -265,10 +265,10 @@ Avoid exposing one exported accessor function per SQL service method if it only
|
|
|
265
265
|
Bad:
|
|
266
266
|
|
|
267
267
|
```ts
|
|
268
|
-
export const createTodo = Effect.fn(function*(title: string) {
|
|
269
|
-
const todos = yield* TodoRepo
|
|
270
|
-
return yield* todos.create(title)
|
|
271
|
-
})
|
|
268
|
+
export const createTodo = Effect.fn(function* (title: string) {
|
|
269
|
+
const todos = yield* TodoRepo;
|
|
270
|
+
return yield* todos.create(title);
|
|
271
|
+
});
|
|
272
272
|
```
|
|
273
273
|
|
|
274
274
|
Prefer:
|
|
@@ -352,25 +352,25 @@ The runtime-specific SQL migrator packages expose `fromRecord(...)` to define mi
|
|
|
352
352
|
Example shape:
|
|
353
353
|
|
|
354
354
|
```ts
|
|
355
|
-
import * as SqliteMigrator from "@effect/sql-sqlite-bun/SqliteMigrator"
|
|
356
|
-
import * as Effect from "effect/Effect"
|
|
355
|
+
import * as SqliteMigrator from "@effect/sql-sqlite-bun/SqliteMigrator";
|
|
356
|
+
import * as Effect from "effect/Effect";
|
|
357
357
|
|
|
358
358
|
const migrations = SqliteMigrator.fromRecord({
|
|
359
|
-
"1_create_todos": Effect.gen(function*() {
|
|
359
|
+
"1_create_todos": Effect.gen(function* () {
|
|
360
360
|
yield* sql`
|
|
361
361
|
CREATE TABLE todos (
|
|
362
362
|
id INTEGER PRIMARY KEY NOT NULL,
|
|
363
363
|
title TEXT NOT NULL,
|
|
364
364
|
completed INTEGER NOT NULL DEFAULT 0
|
|
365
365
|
)
|
|
366
|
-
`.withoutTransform
|
|
366
|
+
`.withoutTransform;
|
|
367
367
|
}),
|
|
368
|
-
"2_add_todo_index": Effect.gen(function*() {
|
|
368
|
+
"2_add_todo_index": Effect.gen(function* () {
|
|
369
369
|
yield* sql`
|
|
370
370
|
CREATE INDEX todos_completed_idx ON todos (completed)
|
|
371
|
-
`.withoutTransform
|
|
372
|
-
})
|
|
373
|
-
})
|
|
371
|
+
`.withoutTransform;
|
|
372
|
+
}),
|
|
373
|
+
});
|
|
374
374
|
```
|
|
375
375
|
|
|
376
376
|
This matches the model used by the canonical migrator implementation:
|
|
@@ -386,11 +386,11 @@ Use the runtime-specific `run(...)` helper when you want a startup effect that r
|
|
|
386
386
|
Example shape:
|
|
387
387
|
|
|
388
388
|
```ts
|
|
389
|
-
import * as SqliteMigrator from "@effect/sql-sqlite-bun/SqliteMigrator"
|
|
389
|
+
import * as SqliteMigrator from "@effect/sql-sqlite-bun/SqliteMigrator";
|
|
390
390
|
|
|
391
391
|
const runMigrations = SqliteMigrator.run({
|
|
392
|
-
loader: migrations
|
|
393
|
-
})
|
|
392
|
+
loader: migrations,
|
|
393
|
+
});
|
|
394
394
|
```
|
|
395
395
|
|
|
396
396
|
This is a good fit when:
|
|
@@ -408,11 +408,11 @@ Use the runtime-specific `layer(...)` helper when migrations should run as part
|
|
|
408
408
|
Example shape:
|
|
409
409
|
|
|
410
410
|
```ts
|
|
411
|
-
import * as SqliteMigrator from "@effect/sql-sqlite-bun/SqliteMigrator"
|
|
411
|
+
import * as SqliteMigrator from "@effect/sql-sqlite-bun/SqliteMigrator";
|
|
412
412
|
|
|
413
413
|
const MigrationLayer = SqliteMigrator.layer({
|
|
414
|
-
loader: migrations
|
|
415
|
-
})
|
|
414
|
+
loader: migrations,
|
|
415
|
+
});
|
|
416
416
|
```
|
|
417
417
|
|
|
418
418
|
From the canonical packages, this is implemented as `Layer.effectDiscard(run(options))`.
|
|
@@ -428,16 +428,13 @@ That means:
|
|
|
428
428
|
Preferred shape:
|
|
429
429
|
|
|
430
430
|
```ts
|
|
431
|
-
const SqlLayer = SqliteClient.layer({ filename: "todos.sqlite" })
|
|
431
|
+
const SqlLayer = SqliteClient.layer({ filename: "todos.sqlite" });
|
|
432
432
|
|
|
433
433
|
const MigrationLayer = SqliteMigrator.layer({
|
|
434
|
-
loader: migrations
|
|
435
|
-
})
|
|
434
|
+
loader: migrations,
|
|
435
|
+
});
|
|
436
436
|
|
|
437
|
-
const MigratedSqlLayer = Layer.merge(
|
|
438
|
-
SqlLayer,
|
|
439
|
-
MigrationLayer.pipe(Layer.provide(SqlLayer))
|
|
440
|
-
)
|
|
437
|
+
const MigratedSqlLayer = Layer.merge(SqlLayer, MigrationLayer.pipe(Layer.provide(SqlLayer)));
|
|
441
438
|
```
|
|
442
439
|
|
|
443
440
|
This keeps the structure explicit:
|