@danieljvdm/dev-kit 0.11.3 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +70 -76
  2. package/dev-kit.example.jsonc +0 -4
  3. package/package.json +10 -6
  4. package/schema/dev-kit.schema.json +1 -46
  5. package/skills/build-effect-apis/SKILL.md +77 -0
  6. package/skills/build-effect-apis/agents/openai.yaml +4 -0
  7. package/skills/build-effect-apis/references/cloudflare-workers.md +71 -0
  8. package/skills/build-effect-apis/references/effect-atom-client.md +161 -0
  9. package/skills/build-effect-apis/references/effect-atom-lifecycle.md +78 -0
  10. package/skills/build-effect-apis/references/effect-atom-testing.md +74 -0
  11. package/skills/build-effect-apis/references/runtime-assembly.md +56 -0
  12. package/skills/build-effect-apis/references/server-and-middleware.md +174 -0
  13. package/skills/build-effect-apis/references/shared-contracts.md +108 -0
  14. package/skills/build-effect-apis/references/tanstack-start.md +86 -0
  15. package/skills/build-effect-apis/references/verification.md +50 -0
  16. package/skills/dev-kit/SKILL.md +58 -46
  17. package/skills/effect-architecture-audit/SKILL.md +26 -0
  18. package/skills/effect-architecture-audit/agents/openai.yaml +4 -0
  19. package/skills/effect-architecture-audit/references/service-and-boundary-audit.md +150 -0
  20. package/skills/effect-ts/SKILL.md +21 -256
  21. package/skills/effect-ts/agents/openai.yaml +3 -3
  22. package/skills/testing/SKILL.md +5 -0
  23. package/src/catalog-manager.ts +16 -17
  24. package/src/catalog.ts +71 -16
  25. package/src/effect-source.ts +46 -24
  26. package/src/effect-tsgo.ts +49 -24
  27. package/src/gitignore.ts +5 -5
  28. package/src/index.ts +0 -6
  29. package/src/manifest.ts +0 -34
  30. package/src/node-symbolic-link.ts +2 -2
  31. package/src/oxfmt.js +5 -0
  32. package/src/oxfmt.ts +5 -0
  33. package/src/oxlint.js +5 -0
  34. package/src/oxlint.ts +5 -0
  35. package/src/package-skill-source.ts +51 -59
  36. package/src/path-digest.ts +7 -7
  37. package/src/project-package.ts +8 -7
  38. package/src/project-process-lock.ts +17 -12
  39. package/src/project-state.ts +1 -1
  40. package/src/skill-manager.ts +16 -14
  41. package/src/skill-selector.ts +12 -0
  42. package/src/sync.ts +170 -120
  43. package/src/tool-ignore-patterns.js +9 -0
  44. package/src/tool-ignore-patterns.ts +15 -0
  45. package/src/vendor.ts +67 -61
  46. package/src/vite-plus-dependency.ts +10 -11
  47. package/src/vite-plus-hooks.ts +24 -14
  48. package/src/vite-plus-quality.ts +21 -172
  49. package/src/vite-plus.js +81 -0
  50. package/src/vite-plus.ts +102 -0
  51. package/templates/AGENTS.md +1 -1
  52. package/skills/effect-ts/UPSTREAM.md +0 -28
  53. package/skills/effect-ts/references/atom-cache-lifecycle.md +0 -78
  54. package/skills/effect-ts/references/atom-http-and-invalidation.md +0 -97
  55. package/skills/effect-ts/references/atom-tanstack-start.md +0 -69
  56. package/skills/effect-ts/references/atom-testing.md +0 -67
  57. package/skills/effect-ts/references/audit-services.md +0 -144
  58. package/skills/effect-ts/references/features.md +0 -525
  59. package/skills/effect-ts/references/guide-atom-data-fetching.md +0 -44
  60. package/skills/effect-ts/references/guide-cli.md +0 -107
  61. package/skills/effect-ts/references/guide-datetime.md +0 -72
  62. package/skills/effect-ts/references/guide-effect.md +0 -440
  63. package/skills/effect-ts/references/guide-error-handling.md +0 -565
  64. package/skills/effect-ts/references/guide-http-boundaries.md +0 -55
  65. package/skills/effect-ts/references/guide-layers.md +0 -989
  66. package/skills/effect-ts/references/guide-observability.md +0 -746
  67. package/skills/effect-ts/references/guide-retries.md +0 -434
  68. package/skills/effect-ts/references/guide-schedule.md +0 -343
  69. package/skills/effect-ts/references/guide-schema.md +0 -664
  70. package/skills/effect-ts/references/guide-sql.md +0 -536
  71. package/skills/effect-ts/references/guide-testing.md +0 -532
  72. package/skills/effect-ts/references/guide-type-safety-and-boundaries.md +0 -131
  73. package/skills/effect-ts/references/version-and-source.md +0 -86
  74. package/templates/vite-plus/vite.config.ts +0 -22
@@ -1,746 +0,0 @@
1
- # Observability Guide
2
-
3
- This guide covers named operations, spans, structured logging, metrics, and
4
- telemetry wiring.
5
-
6
- Key source files:
7
-
8
- - `packages/effect/src/Effect.ts`
9
- - `packages/effect/src/Tracer.ts`
10
- - `packages/effect/src/Logger.ts`
11
- - `packages/effect/src/Metric.ts`
12
- - `packages/opentelemetry/src/NodeSdk.ts`
13
- - `packages/opentelemetry/src/OtelTracer.ts`
14
-
15
- ## Mental Model
16
-
17
- Observable Effect code should make business operations visible by default.
18
-
19
- That means:
20
-
21
- - business logic should show up clearly in stack traces
22
- - important operations should produce spans
23
- - logs should inherit execution context
24
- - metrics should be attached at meaningful boundaries
25
-
26
- The most important best practice is:
27
-
28
- - prefer `Effect.fn(...)` whenever possible for business logic
29
-
30
- Why:
31
-
32
- - it adds stack frames
33
- - it creates spans automatically
34
- - it gives you better tracing and debugging for free
35
- - it keeps business logic observable without extra boilerplate
36
-
37
- Repo reference:
38
-
39
- - `packages/effect/src/Effect.ts`
40
-
41
- ## Preferred Rule
42
-
43
- Use `Effect.fn` as the default constructor for business-logic functions that return `Effect`.
44
-
45
- Prefer this:
46
-
47
- ```ts
48
- import { Effect } from "effect";
49
-
50
- const loadUser = Effect.fn("loadUser")(function* (userId: string) {
51
- return { id: userId, name: "Ada" };
52
- });
53
- ```
54
-
55
- Over this:
56
-
57
- ```ts
58
- import { Effect } from "effect";
59
-
60
- const loadUser = (userId: string) =>
61
- Effect.gen(function* () {
62
- return { id: userId, name: "Ada" };
63
- });
64
- ```
65
-
66
- The second version works, but it throws away useful observability structure that `Effect.fn` gives you automatically.
67
-
68
- ## Prefer `Effect.fn` Over Raw `Effect.gen`
69
-
70
- For business-logic definitions, prefer `Effect.fn` over writing raw `Effect.gen` directly, even when the operation takes no arguments.
71
-
72
- Prefer this:
73
-
74
- ```ts
75
- const refreshCache = Effect.fn("refreshCache")(function* () {
76
- yield* Effect.logInfo("refreshing cache");
77
- });
78
- ```
79
-
80
- Over this:
81
-
82
- ```ts
83
- const refreshCache = Effect.gen(function* () {
84
- yield* Effect.logInfo("refreshing cache");
85
- });
86
- ```
87
-
88
- Why:
89
-
90
- - `Effect.fn` gives the operation a clear observable identity
91
- - stack traces are better
92
- - tracing is more consistent
93
- - the codebase gets a uniform shape for business operations
94
-
95
- Use raw `Effect.gen` when necessary, for example:
96
-
97
- - inline effect blocks inside another `Effect.fn`
98
- - small one-off composition at call sites
99
- - top-level assembly code where you are not defining a reusable business operation
100
-
101
- Rule of thumb:
102
-
103
- - reusable business operation: `Effect.fn`
104
- - inline composition block: `Effect.gen`
105
-
106
- ## `Effect.fn` vs `Effect.fnUntraced`
107
-
108
- ### `Effect.fn`
109
-
110
- Use `Effect.fn` for almost all business logic.
111
-
112
- It is the preferred default because it adds:
113
-
114
- - stack frames
115
- - tracing spans
116
- - optional post-processing of the produced effect
117
-
118
- Example:
119
-
120
- ```ts
121
- import { Effect } from "effect";
122
-
123
- const createUser = Effect.fn("createUser")(function* (name: string) {
124
- return { id: "u_123", name };
125
- });
126
- ```
127
-
128
- Use this for:
129
-
130
- - domain operations
131
- - application services
132
- - handlers
133
- - workflows
134
- - orchestrations
135
- - repository calls
136
-
137
- ### `Effect.fnUntraced`
138
-
139
- Use `Effect.fnUntraced` only for edge cases.
140
-
141
- The canonical Effect source itself uses `fnUntraced` in a number of low-level internals and integration helpers. That does not make it the default recommendation for downstream application or business code.
142
-
143
- If you do not want an explicit named span, prefer `Effect.fn` without a span name so you still keep stack traces and the normal traced-function behavior.
144
-
145
- Prefer this:
146
-
147
- ```ts
148
- const normalizeUser = Effect.fn(function* (input: string) {
149
- return input.trim().toLowerCase();
150
- });
151
- ```
152
-
153
- Over this:
154
-
155
- ```ts
156
- const normalizeUser = Effect.fnUntraced(function* (input: string) {
157
- return input.trim().toLowerCase();
158
- });
159
- ```
160
-
161
- Typical cases:
162
-
163
- - extremely hot low-level internal helpers
164
- - very small internal combinators
165
- - tight loops where you have measured overhead and need to reduce it
166
-
167
- Preferred rule:
168
-
169
- - `Effect.fn` by default
170
- - `Effect.fn` without a span name when you want to avoid an explicit named span
171
- - `Effect.fnUntraced` only with a concrete measured reason
172
-
173
- ## Business Logic Patterns
174
-
175
- ### Pattern: one named `Effect.fn` per meaningful operation
176
-
177
- Good:
178
-
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
- });
192
- ```
193
-
194
- Why this is good:
195
-
196
- - each operation has a clear span name
197
- - traces reflect business concepts
198
- - stack traces reflect the actual workflow
199
-
200
- ### Pattern: use meaningful names
201
-
202
- Span names created through `Effect.fn` should represent business operations, not generic implementation detail.
203
-
204
- Prefer:
205
-
206
- - `loadUser`
207
- - `chargeInvoice`
208
- - `syncGithubInstallation`
209
-
210
- Avoid:
211
-
212
- - `helper`
213
- - `run`
214
- - `process`
215
- - `step1`
216
-
217
- ## Explicit Spans
218
-
219
- ### `Effect.withSpan`
220
-
221
- Use `withSpan` when you need an explicit span around an effect that is not already naturally represented by a named `Effect.fn`, or when you want a nested sub-operation span.
222
-
223
- Example:
224
-
225
- ```ts
226
- import { Effect } from "effect";
227
-
228
- const syncUser = Effect.fn("syncUser")(function* (userId: string) {
229
- const profile = yield* fetchProfile(userId).pipe(Effect.withSpan("fetchProfile"));
230
-
231
- return yield* persistProfile(profile).pipe(Effect.withSpan("persistProfile"));
232
- });
233
- ```
234
-
235
- Use this when:
236
-
237
- - you want a nested span inside a larger operation
238
- - you are instrumenting an existing effect pipeline
239
- - you need more detailed trace structure than `Effect.fn` alone provides
240
-
241
- ### `Effect.withSpanScoped`
242
-
243
- Use `withSpanScoped` when the span should remain open for the lifetime of a scope.
244
-
245
- This is less common in business logic and more common in long-lived resource or streaming workflows.
246
-
247
- ### `Effect.withParentSpan`
248
-
249
- Use `withParentSpan` when integrating with an externally created span or continuing a parent span manually.
250
-
251
- This is useful in framework or interoperability boundaries.
252
-
253
- ## Span Enrichment
254
-
255
- ### `Effect.annotateCurrentSpan`
256
-
257
- Use `annotateCurrentSpan` to attach important structured fields to the current span.
258
-
259
- Example:
260
-
261
- ```ts
262
- import { Effect } from "effect";
263
-
264
- const loadUser = Effect.fn("loadUser")(function* (userId: string) {
265
- yield* Effect.annotateCurrentSpan({ userId });
266
- return { id: userId, name: "Ada" };
267
- });
268
- ```
269
-
270
- Good span annotations:
271
-
272
- - stable identifiers
273
- - domain-relevant keys
274
- - request or resource identifiers
275
- - small structured values
276
-
277
- Avoid:
278
-
279
- - giant payloads
280
- - secrets
281
- - noisy transient data with little diagnostic value
282
-
283
- ## Logging Patterns
284
-
285
- ### Use Effect logging inside effects
286
-
287
- Prefer:
288
-
289
- - `Effect.log`
290
- - `Effect.logInfo`
291
- - `Effect.logDebug`
292
- - `Effect.logWarning`
293
- - `Effect.logError`
294
-
295
- These integrate with the current Effect execution context.
296
-
297
- Example:
298
-
299
- ```ts
300
- const loadUser = Effect.fn("loadUser")(function* (userId: string) {
301
- yield* Effect.logDebug("loading user", { userId });
302
- return { id: userId, name: "Ada" };
303
- });
304
- ```
305
-
306
- ### `Effect.withLogSpan`
307
-
308
- Use `withLogSpan` when you want log messages to carry a local logical span label even when you are not creating a full tracing span.
309
-
310
- Example:
311
-
312
- ```ts
313
- const program = Effect.logInfo("starting sync").pipe(Effect.withLogSpan("user-sync"));
314
- ```
315
-
316
- This is useful for:
317
-
318
- - log grouping
319
- - quick local context
320
- - correlation in plain log output
321
-
322
- ### Logging Best Practices
323
-
324
- - log at business boundaries, not every tiny helper
325
- - prefer structured values over concatenated strings
326
- - keep logs high-signal
327
- - avoid duplicate logs at every layer of the stack
328
- - rely on spans plus a few well-placed logs, not log spam
329
- - use small structured fields such as identifiers, counts, booleans, and
330
- operation names
331
- - attach errors as structured attributes rather than stringifying them into the
332
- message
333
- - keep runtime logger Layers and common annotations at runtime boundaries;
334
- business logic should not create module-level logger instances
335
-
336
- ### Log Annotations
337
-
338
- Use `Effect.annotateLogs` for metadata that should appear on every log emitted
339
- inside an operation. Annotate at the highest meaningful owner—request, route,
340
- tenant, workflow, or job—rather than repeating the same attributes at each log
341
- site.
342
-
343
- ```ts
344
- const runCheckout = (request: CheckoutRequest) =>
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);
350
- }).pipe(
351
- Effect.annotateLogs({
352
- operation: "checkout",
353
- requestId: request.id,
354
- cartId: request.cart.id,
355
- }),
356
- Effect.withLogSpan("checkout"),
357
- );
358
- ```
359
-
360
- Add narrower annotations only when metadata belongs to a nested item, attempt,
361
- or operation. Use `Effect.annotateLogsScoped` only when an acquired scope should
362
- carry annotations across multiple effects; ordinary `Effect.annotateLogs` is
363
- the default for request and operation boundaries.
364
-
365
- ## Metrics Patterns
366
-
367
- ### Track effects at meaningful boundaries
368
-
369
- Use metric tracking on meaningful operations such as:
370
-
371
- - requests
372
- - jobs
373
- - retries
374
- - external calls
375
- - queue handlers
376
-
377
- Repo reference:
378
-
379
- - `packages/effect/src/Effect.ts`
380
- - `Effect.track`
381
-
382
- Example:
383
-
384
- ```ts
385
- import { Effect, Metric } from "effect";
386
-
387
- const requests = Metric.counter("user_load_requests").pipe(Metric.withConstantInput(1));
388
-
389
- const loadUser = Effect.fn("loadUser")(function* (userId: string) {
390
- return { id: userId, name: "Ada" };
391
- }, Effect.track(requests));
392
- ```
393
-
394
- ### Prefer boundary metrics over micro-metrics
395
-
396
- Good metrics are usually attached to:
397
-
398
- - endpoint handlers
399
- - queue/job handlers
400
- - repository operations
401
- - external API boundaries
402
-
403
- Avoid putting a metric on every tiny internal helper.
404
-
405
- ## OpenTelemetry Integration
406
-
407
- For real application observability, compose telemetry at the layer level.
408
-
409
- The canonical source provides `@effect/opentelemetry` layers such as:
410
-
411
- - `NodeSdk.layer`
412
- - `OtelTracer.layer`
413
- - `OtelLogger.layer`
414
- - `OtelMetrics.layer`
415
-
416
- Repo references:
417
-
418
- - `packages/opentelemetry/src/NodeSdk.ts`
419
- - `packages/opentelemetry/src/OtelTracer.ts`
420
-
421
- Preferred composition style:
422
-
423
- ```ts
424
- const AppLayer = Layer.mergeAll(UserLayer, BillingLayer, HttpLayer).pipe(
425
- Layer.provide(Telemetry),
426
- Layer.provide(NodeSdk),
427
- );
428
- ```
429
-
430
- Why:
431
-
432
- - business code stays observability-agnostic
433
- - observability is configured once at the boundary
434
- - spans, logs, and metrics remain consistent across the app
435
-
436
- ## OpenTelemetry JS Framework Integration
437
-
438
- The canonical source includes a real integration layer for the OpenTelemetry JavaScript ecosystem in `@effect/opentelemetry`.
439
-
440
- This is the preferred integration path when the application needs to participate in the standard OpenTelemetry JS framework, exporters, and SDKs.
441
-
442
- Relevant modules:
443
-
444
- - `packages/opentelemetry/src/NodeSdk.ts`
445
- - `packages/opentelemetry/src/OtelTracer.ts`
446
- - `packages/opentelemetry/src/OtelMetrics.ts`
447
- - `packages/opentelemetry/src/OtelLogger.ts`
448
- - `packages/opentelemetry/src/Resource.ts`
449
- - `packages/opentelemetry/src/WebSdk.ts`
450
-
451
- ### Preferred Integration Model
452
-
453
- Use `@effect/opentelemetry` layers to bridge Effect observability into OpenTelemetry JS.
454
-
455
- Do not manually wire OpenTelemetry SDK objects inside business code.
456
-
457
- Prefer:
458
-
459
- - configuring tracer, metrics, logger, and resource layers once
460
- - composing them into the application layer graph
461
- - keeping business code written against Effect tracing, logging, and metrics APIs
462
-
463
- This means:
464
-
465
- - application code should keep using `Effect.fn`, `Effect.withSpan`, `Effect.log*`, and Effect metrics
466
- - OpenTelemetry JS should be introduced at the infrastructure layer, not inside domain operations
467
-
468
- ### `NodeSdk.layer`
469
-
470
- `NodeSdk.layer(...)` is the main Node.js integration entrypoint.
471
-
472
- From the canonical source, it accepts a configuration that can include:
473
-
474
- - span processors
475
- - tracer config
476
- - metric readers
477
- - temporality preference
478
- - log record processors
479
- - logger provider config
480
- - resource information such as service name and version
481
- - shutdown timeout
482
-
483
- It then builds and merges:
484
-
485
- - resource layer
486
- - tracer layer
487
- - metrics layer
488
- - logger layer
489
-
490
- This makes it the preferred high-level integration for Node applications.
491
-
492
- ### Resource Configuration
493
-
494
- OpenTelemetry JS integration should define resource metadata explicitly.
495
-
496
- From `NodeSdk.layer`, the supported resource configuration includes:
497
-
498
- - `serviceName`
499
- - `serviceVersion`
500
- - additional attributes
501
-
502
- This is important because tracer and logger setup depend on the configured resource.
503
-
504
- Best practice:
505
-
506
- - always provide a meaningful service name
507
- - provide service version when available
508
- - use resource attributes for stable deployment or environment metadata
509
-
510
- ### Tracing Integration
511
-
512
- The `OtelTracer` module bridges Effect spans into OpenTelemetry spans.
513
-
514
- Important integration points from the canonical source:
515
-
516
- - `OtelTracer.layer`
517
- - `OtelTracer.layerGlobal`
518
- - `OtelTracer.layerGlobalProvider`
519
- - `OtelTracer.currentOtelSpan`
520
- - `OtelTracer.makeExternalSpan`
521
-
522
- Use these when:
523
-
524
- - you need Effect tracing to export through OpenTelemetry JS
525
- - you need to continue or bridge external trace context
526
- - you need access to the current OpenTelemetry span object
527
-
528
- Best practice:
529
-
530
- - keep creating spans with Effect APIs in application code
531
- - use the OpenTelemetry tracer layer to export and bridge them
532
- - use `makeExternalSpan` or parent-span wiring only at integration boundaries
533
-
534
- ### Metrics Integration
535
-
536
- The `OtelMetrics` module connects Effect metrics to OpenTelemetry JS metric
537
- readers.
538
-
539
- Important details from the canonical implementation:
540
-
541
- - `OtelMetrics.layer(...)` registers a producer against one or more metric
542
- readers
543
- - it supports temporality preferences:
544
- - `cumulative`
545
- - `delta`
546
- - it handles shutdown through scoped layer cleanup
547
-
548
- Best practice:
549
-
550
- - choose temporality based on the backend
551
- - configure metric readers in the telemetry layer
552
- - keep application code focused on recording Effect metrics, not exporter mechanics
553
-
554
- ### Logger Integration
555
-
556
- The `OtelLogger` module connects Effect logging to OpenTelemetry JS logs.
557
-
558
- Important details from the canonical implementation:
559
-
560
- - it maps Effect log levels to OpenTelemetry severity numbers
561
- - it includes fiber ID, span context, log annotations, and log span timing in emitted attributes
562
- - `OtelLogger.layer({ mergeWithExisting })` can merge with or replace existing
563
- application loggers
564
-
565
- Best practice:
566
-
567
- - prefer merging with existing loggers unless there is a strong reason to replace them
568
- - use Effect log annotations and log spans so the OpenTelemetry logger receives structured context automatically
569
-
570
- ### Shutdown And Lifecycle
571
-
572
- The canonical layers use scoped acquisition and release for tracer providers,
573
- metric readers, and logger providers.
574
-
575
- This is the correct lifecycle model.
576
-
577
- Do not manually call provider shutdown methods from arbitrary business logic.
578
-
579
- Instead:
580
-
581
- - let the OpenTelemetry layers own provider lifecycle
582
- - compose them into the application layer graph
583
- - let the runtime or outer layer scope manage shutdown
584
-
585
- ### External Trace Context
586
-
587
- When integrating with frameworks or inbound protocols that already carry trace context, prefer using the OpenTelemetry integration helpers rather than hand-rolling context propagation.
588
-
589
- The canonical tracer module provides:
590
-
591
- - `makeExternalSpan`
592
- - `currentOtelSpan`
593
-
594
- Use these only at integration boundaries such as:
595
-
596
- - HTTP adapters
597
- - RPC adapters
598
- - worker or queue adapters
599
-
600
- Keep business operations oblivious to propagation mechanics.
601
-
602
- ### Recommended Pattern
603
-
604
- Preferred architecture:
605
-
606
- 1. business code uses Effect observability APIs
607
- 2. infrastructure composes `@effect/opentelemetry` layers
608
- 3. the final app layer provides telemetry once at the top level
609
-
610
- Example shape:
611
-
612
- ```ts
613
- const TelemetryLayer = NodeSdk.layer(() => ({
614
- resource: {
615
- serviceName: "todo-service",
616
- serviceVersion: "1.0.0",
617
- },
618
- spanProcessor: mySpanProcessor,
619
- metricReader: myMetricReader,
620
- logRecordProcessor: myLogProcessor,
621
- }));
622
-
623
- const AppLayer = Layer.mergeAll(DomainLayer, HttpLayer).pipe(Layer.provide(TelemetryLayer));
624
- ```
625
-
626
- This keeps:
627
-
628
- - app code portable
629
- - OTel JS setup centralized
630
- - shutdown semantics correct
631
- - exported spans, logs, and metrics aligned
632
-
633
- ### Anti-Patterns
634
-
635
- - constructing OpenTelemetry SDK clients directly inside business services
636
- - mixing manual exporter setup into domain code
637
- - bypassing Effect logging and tracing APIs in normal business operations
638
- - scattering provider shutdown logic across the application
639
- - configuring telemetry separately in many subsystems instead of one top-level layer
640
-
641
- ## Anti-Patterns
642
-
643
- ### Anti-Pattern: business logic built from anonymous `Effect.gen` functions everywhere
644
-
645
- Bad:
646
-
647
- ```ts
648
- const loadUser = (userId: string) =>
649
- Effect.gen(function* () {
650
- return { id: userId, name: "Ada" };
651
- });
652
- ```
653
-
654
- Why this is bad:
655
-
656
- - weaker tracing structure
657
- - poorer stack traces
658
- - less consistent naming in debugging output
659
-
660
- Preferred:
661
-
662
- ```ts
663
- const loadUser = Effect.fn("loadUser")(function* (userId: string) {
664
- return { id: userId, name: "Ada" };
665
- });
666
- ```
667
-
668
- ### Anti-Pattern: using `Effect.fnUntraced` by default
669
-
670
- This throws away free observability.
671
-
672
- If you just do not want an explicit span name, use `Effect.fn` without a name instead.
673
-
674
- Only use `fnUntraced` when you have a specific low-level reason.
675
-
676
- ### Anti-Pattern: logging without structure or context
677
-
678
- Bad:
679
-
680
- - giant interpolated strings
681
- - duplicate logs at every layer
682
- - logs with no business identifiers
683
-
684
- Prefer:
685
-
686
- - named operations via `Effect.fn`
687
- - structured logs with IDs and context
688
- - a few high-value logs at operation boundaries
689
-
690
- Also avoid:
691
-
692
- - `console.*` in new Effect code
693
- - custom logging wrappers around `Effect.log*`
694
- - broad module-wide annotations when operation-level ownership is clearer
695
- - dual legacy and Effect logging paths
696
-
697
- ### Anti-Pattern: hand-instrumenting every helper with spans
698
-
699
- Do not create explicit spans everywhere just because you can.
700
-
701
- Preferred order:
702
-
703
- 1. start with `Effect.fn`
704
- 2. add `Effect.withSpan` only where extra detail is actually useful
705
- 3. add metrics at meaningful boundaries
706
-
707
- ## Recommended Patterns
708
-
709
- ### Pattern: observable business operation
710
-
711
- ```ts
712
- import { Effect } from "effect";
713
-
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
- });
719
- ```
720
-
721
- ### Pattern: orchestration with nested spans
722
-
723
- ```ts
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
- });
729
- ```
730
-
731
- ### Pattern: framework boundary with runtime
732
-
733
- ```ts
734
- const runtime = ManagedRuntime.make(AppLayer);
735
-
736
- const handleRequest = (userId: string) => runtime.runPromise(fetchUser(userId));
737
- ```
738
-
739
- ## Good Repo Examples To Study
740
-
741
- - `packages/effect/src/Effect.ts`
742
- - `packages/effect/src/Tracer.ts`
743
- - `packages/effect/src/Logger.ts`
744
- - `packages/effect/src/Metric.ts`
745
- - `packages/opentelemetry/src/NodeSdk.ts`
746
- - `packages/opentelemetry/src/OtelTracer.ts`