@danieljvdm/dev-kit 0.2.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 (47) hide show
  1. package/README.md +290 -0
  2. package/bin/dev-kit.mjs +3 -0
  3. package/dev-kit.example.jsonc +13 -0
  4. package/package.json +69 -0
  5. package/schema/dev-kit.schema.json +128 -0
  6. package/schema/skill-sources.schema.json +83 -0
  7. package/skill-sources.jsonc +55 -0
  8. package/skill-sources.lock.json +136 -0
  9. package/skills/dev-kit/SKILL.md +145 -0
  10. package/skills/dev-kit/agents/openai.yaml +4 -0
  11. package/skills/effect-ts/SKILL.md +242 -0
  12. package/skills/effect-ts/UPSTREAM.md +28 -0
  13. package/skills/effect-ts/agents/openai.yaml +5 -0
  14. package/skills/effect-ts/references/audit-services.md +144 -0
  15. package/skills/effect-ts/references/features.md +525 -0
  16. package/skills/effect-ts/references/guide-cli.md +106 -0
  17. package/skills/effect-ts/references/guide-effect.md +453 -0
  18. package/skills/effect-ts/references/guide-error-handling.md +574 -0
  19. package/skills/effect-ts/references/guide-http-boundaries.md +55 -0
  20. package/skills/effect-ts/references/guide-layers.md +1017 -0
  21. package/skills/effect-ts/references/guide-observability.md +771 -0
  22. package/skills/effect-ts/references/guide-retries.md +446 -0
  23. package/skills/effect-ts/references/guide-schedule.md +357 -0
  24. package/skills/effect-ts/references/guide-schema.md +671 -0
  25. package/skills/effect-ts/references/guide-sql.md +539 -0
  26. package/skills/effect-ts/references/guide-testing.md +534 -0
  27. package/skills/effect-ts/references/guide-type-safety-and-boundaries.md +131 -0
  28. package/skills/effect-ts/references/version-and-source.md +87 -0
  29. package/src/bin/dev-kit.ts +372 -0
  30. package/src/catalog-manager.ts +345 -0
  31. package/src/catalog.ts +246 -0
  32. package/src/cli-ui.ts +110 -0
  33. package/src/effect-source.ts +325 -0
  34. package/src/effect-tsgo.ts +256 -0
  35. package/src/gitignore.ts +212 -0
  36. package/src/index.ts +98 -0
  37. package/src/manifest.ts +133 -0
  38. package/src/node-symbolic-link.ts +31 -0
  39. package/src/path-digest.ts +140 -0
  40. package/src/project-process-lock.ts +76 -0
  41. package/src/project-state.ts +67 -0
  42. package/src/skill-manager.ts +326 -0
  43. package/src/source-manifest.ts +51 -0
  44. package/src/sync.ts +900 -0
  45. package/src/tool-metadata.ts +3 -0
  46. package/src/typescript-package-name.ts +5 -0
  47. package/src/vendor.ts +848 -0
@@ -0,0 +1,771 @@
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(
230
+ Effect.withSpan("fetchProfile")
231
+ )
232
+
233
+ return yield* persistProfile(profile).pipe(
234
+ Effect.withSpan("persistProfile")
235
+ )
236
+ })
237
+ ```
238
+
239
+ Use this when:
240
+
241
+ - you want a nested span inside a larger operation
242
+ - you are instrumenting an existing effect pipeline
243
+ - you need more detailed trace structure than `Effect.fn` alone provides
244
+
245
+ ### `Effect.withSpanScoped`
246
+
247
+ Use `withSpanScoped` when the span should remain open for the lifetime of a scope.
248
+
249
+ This is less common in business logic and more common in long-lived resource or streaming workflows.
250
+
251
+ ### `Effect.withParentSpan`
252
+
253
+ Use `withParentSpan` when integrating with an externally created span or continuing a parent span manually.
254
+
255
+ This is useful in framework or interoperability boundaries.
256
+
257
+ ## Span Enrichment
258
+
259
+ ### `Effect.annotateCurrentSpan`
260
+
261
+ Use `annotateCurrentSpan` to attach important structured fields to the current span.
262
+
263
+ Example:
264
+
265
+ ```ts
266
+ import { Effect } from "effect"
267
+
268
+ const loadUser = Effect.fn("loadUser")(function*(userId: string) {
269
+ yield* Effect.annotateCurrentSpan({ userId })
270
+ return { id: userId, name: "Ada" }
271
+ })
272
+ ```
273
+
274
+ Good span annotations:
275
+
276
+ - stable identifiers
277
+ - domain-relevant keys
278
+ - request or resource identifiers
279
+ - small structured values
280
+
281
+ Avoid:
282
+
283
+ - giant payloads
284
+ - secrets
285
+ - noisy transient data with little diagnostic value
286
+
287
+ ## Logging Patterns
288
+
289
+ ### Use Effect logging inside effects
290
+
291
+ Prefer:
292
+
293
+ - `Effect.log`
294
+ - `Effect.logInfo`
295
+ - `Effect.logDebug`
296
+ - `Effect.logWarning`
297
+ - `Effect.logError`
298
+
299
+ These integrate with the current Effect execution context.
300
+
301
+ Example:
302
+
303
+ ```ts
304
+ const loadUser = Effect.fn("loadUser")(function*(userId: string) {
305
+ yield* Effect.logDebug("loading user", { userId })
306
+ return { id: userId, name: "Ada" }
307
+ })
308
+ ```
309
+
310
+ ### `Effect.withLogSpan`
311
+
312
+ Use `withLogSpan` when you want log messages to carry a local logical span label even when you are not creating a full tracing span.
313
+
314
+ Example:
315
+
316
+ ```ts
317
+ const program = Effect.logInfo("starting sync").pipe(
318
+ Effect.withLogSpan("user-sync")
319
+ )
320
+ ```
321
+
322
+ This is useful for:
323
+
324
+ - log grouping
325
+ - quick local context
326
+ - correlation in plain log output
327
+
328
+ ### Logging Best Practices
329
+
330
+ - log at business boundaries, not every tiny helper
331
+ - prefer structured values over concatenated strings
332
+ - keep logs high-signal
333
+ - avoid duplicate logs at every layer of the stack
334
+ - rely on spans plus a few well-placed logs, not log spam
335
+ - use small structured fields such as identifiers, counts, booleans, and
336
+ operation names
337
+ - attach errors as structured attributes rather than stringifying them into the
338
+ message
339
+ - keep runtime logger Layers and common annotations at runtime boundaries;
340
+ business logic should not create module-level logger instances
341
+
342
+ ### Log Annotations
343
+
344
+ Use `Effect.annotateLogs` for metadata that should appear on every log emitted
345
+ inside an operation. Annotate at the highest meaningful owner—request, route,
346
+ tenant, workflow, or job—rather than repeating the same attributes at each log
347
+ site.
348
+
349
+ ```ts
350
+ 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)
356
+ }).pipe(
357
+ Effect.annotateLogs({
358
+ operation: "checkout",
359
+ requestId: request.id,
360
+ cartId: request.cart.id
361
+ }),
362
+ Effect.withLogSpan("checkout")
363
+ )
364
+ ```
365
+
366
+ Add narrower annotations only when metadata belongs to a nested item, attempt,
367
+ or operation. Use `Effect.annotateLogsScoped` only when an acquired scope should
368
+ carry annotations across multiple effects; ordinary `Effect.annotateLogs` is
369
+ the default for request and operation boundaries.
370
+
371
+ ## Metrics Patterns
372
+
373
+ ### Track effects at meaningful boundaries
374
+
375
+ Use metric tracking on meaningful operations such as:
376
+
377
+ - requests
378
+ - jobs
379
+ - retries
380
+ - external calls
381
+ - queue handlers
382
+
383
+ Repo reference:
384
+
385
+ - `packages/effect/src/Effect.ts`
386
+ - `Effect.track`
387
+
388
+ Example:
389
+
390
+ ```ts
391
+ import { Effect, Metric } from "effect"
392
+
393
+ const requests = Metric.counter("user_load_requests").pipe(
394
+ Metric.withConstantInput(1)
395
+ )
396
+
397
+ const loadUser = Effect.fn("loadUser")(
398
+ function*(userId: string) {
399
+ return { id: userId, name: "Ada" }
400
+ },
401
+ Effect.track(requests)
402
+ )
403
+ ```
404
+
405
+ ### Prefer boundary metrics over micro-metrics
406
+
407
+ Good metrics are usually attached to:
408
+
409
+ - endpoint handlers
410
+ - queue/job handlers
411
+ - repository operations
412
+ - external API boundaries
413
+
414
+ Avoid putting a metric on every tiny internal helper.
415
+
416
+ ## OpenTelemetry Integration
417
+
418
+ For real application observability, compose telemetry at the layer level.
419
+
420
+ The canonical source provides `@effect/opentelemetry` layers such as:
421
+
422
+ - `NodeSdk.layer`
423
+ - `OtelTracer.layer`
424
+ - `OtelLogger.layer`
425
+ - `OtelMetrics.layer`
426
+
427
+ Repo references:
428
+
429
+ - `packages/opentelemetry/src/NodeSdk.ts`
430
+ - `packages/opentelemetry/src/OtelTracer.ts`
431
+
432
+ Preferred composition style:
433
+
434
+ ```ts
435
+ const AppLayer = Layer.mergeAll(
436
+ UserLayer,
437
+ BillingLayer,
438
+ HttpLayer
439
+ ).pipe(
440
+ Layer.provide(Telemetry),
441
+ Layer.provide(NodeSdk)
442
+ )
443
+ ```
444
+
445
+ Why:
446
+
447
+ - business code stays observability-agnostic
448
+ - observability is configured once at the boundary
449
+ - spans, logs, and metrics remain consistent across the app
450
+
451
+ ## OpenTelemetry JS Framework Integration
452
+
453
+ The canonical source includes a real integration layer for the OpenTelemetry JavaScript ecosystem in `@effect/opentelemetry`.
454
+
455
+ This is the preferred integration path when the application needs to participate in the standard OpenTelemetry JS framework, exporters, and SDKs.
456
+
457
+ Relevant modules:
458
+
459
+ - `packages/opentelemetry/src/NodeSdk.ts`
460
+ - `packages/opentelemetry/src/OtelTracer.ts`
461
+ - `packages/opentelemetry/src/OtelMetrics.ts`
462
+ - `packages/opentelemetry/src/OtelLogger.ts`
463
+ - `packages/opentelemetry/src/Resource.ts`
464
+ - `packages/opentelemetry/src/WebSdk.ts`
465
+
466
+ ### Preferred Integration Model
467
+
468
+ Use `@effect/opentelemetry` layers to bridge Effect observability into OpenTelemetry JS.
469
+
470
+ Do not manually wire OpenTelemetry SDK objects inside business code.
471
+
472
+ Prefer:
473
+
474
+ - configuring tracer, metrics, logger, and resource layers once
475
+ - composing them into the application layer graph
476
+ - keeping business code written against Effect tracing, logging, and metrics APIs
477
+
478
+ This means:
479
+
480
+ - application code should keep using `Effect.fn`, `Effect.withSpan`, `Effect.log*`, and Effect metrics
481
+ - OpenTelemetry JS should be introduced at the infrastructure layer, not inside domain operations
482
+
483
+ ### `NodeSdk.layer`
484
+
485
+ `NodeSdk.layer(...)` is the main Node.js integration entrypoint.
486
+
487
+ From the canonical source, it accepts a configuration that can include:
488
+
489
+ - span processors
490
+ - tracer config
491
+ - metric readers
492
+ - temporality preference
493
+ - log record processors
494
+ - logger provider config
495
+ - resource information such as service name and version
496
+ - shutdown timeout
497
+
498
+ It then builds and merges:
499
+
500
+ - resource layer
501
+ - tracer layer
502
+ - metrics layer
503
+ - logger layer
504
+
505
+ This makes it the preferred high-level integration for Node applications.
506
+
507
+ ### Resource Configuration
508
+
509
+ OpenTelemetry JS integration should define resource metadata explicitly.
510
+
511
+ From `NodeSdk.layer`, the supported resource configuration includes:
512
+
513
+ - `serviceName`
514
+ - `serviceVersion`
515
+ - additional attributes
516
+
517
+ This is important because tracer and logger setup depend on the configured resource.
518
+
519
+ Best practice:
520
+
521
+ - always provide a meaningful service name
522
+ - provide service version when available
523
+ - use resource attributes for stable deployment or environment metadata
524
+
525
+ ### Tracing Integration
526
+
527
+ The `OtelTracer` module bridges Effect spans into OpenTelemetry spans.
528
+
529
+ Important integration points from the canonical source:
530
+
531
+ - `OtelTracer.layer`
532
+ - `OtelTracer.layerGlobal`
533
+ - `OtelTracer.layerGlobalProvider`
534
+ - `OtelTracer.currentOtelSpan`
535
+ - `OtelTracer.makeExternalSpan`
536
+
537
+ Use these when:
538
+
539
+ - you need Effect tracing to export through OpenTelemetry JS
540
+ - you need to continue or bridge external trace context
541
+ - you need access to the current OpenTelemetry span object
542
+
543
+ Best practice:
544
+
545
+ - keep creating spans with Effect APIs in application code
546
+ - use the OpenTelemetry tracer layer to export and bridge them
547
+ - use `makeExternalSpan` or parent-span wiring only at integration boundaries
548
+
549
+ ### Metrics Integration
550
+
551
+ The `OtelMetrics` module connects Effect metrics to OpenTelemetry JS metric
552
+ readers.
553
+
554
+ Important details from the canonical implementation:
555
+
556
+ - `OtelMetrics.layer(...)` registers a producer against one or more metric
557
+ readers
558
+ - it supports temporality preferences:
559
+ - `cumulative`
560
+ - `delta`
561
+ - it handles shutdown through scoped layer cleanup
562
+
563
+ Best practice:
564
+
565
+ - choose temporality based on the backend
566
+ - configure metric readers in the telemetry layer
567
+ - keep application code focused on recording Effect metrics, not exporter mechanics
568
+
569
+ ### Logger Integration
570
+
571
+ The `OtelLogger` module connects Effect logging to OpenTelemetry JS logs.
572
+
573
+ Important details from the canonical implementation:
574
+
575
+ - it maps Effect log levels to OpenTelemetry severity numbers
576
+ - it includes fiber ID, span context, log annotations, and log span timing in emitted attributes
577
+ - `OtelLogger.layer({ mergeWithExisting })` can merge with or replace existing
578
+ application loggers
579
+
580
+ Best practice:
581
+
582
+ - prefer merging with existing loggers unless there is a strong reason to replace them
583
+ - use Effect log annotations and log spans so the OpenTelemetry logger receives structured context automatically
584
+
585
+ ### Shutdown And Lifecycle
586
+
587
+ The canonical layers use scoped acquisition and release for tracer providers,
588
+ metric readers, and logger providers.
589
+
590
+ This is the correct lifecycle model.
591
+
592
+ Do not manually call provider shutdown methods from arbitrary business logic.
593
+
594
+ Instead:
595
+
596
+ - let the OpenTelemetry layers own provider lifecycle
597
+ - compose them into the application layer graph
598
+ - let the runtime or outer layer scope manage shutdown
599
+
600
+ ### External Trace Context
601
+
602
+ When integrating with frameworks or inbound protocols that already carry trace context, prefer using the OpenTelemetry integration helpers rather than hand-rolling context propagation.
603
+
604
+ The canonical tracer module provides:
605
+
606
+ - `makeExternalSpan`
607
+ - `currentOtelSpan`
608
+
609
+ Use these only at integration boundaries such as:
610
+
611
+ - HTTP adapters
612
+ - RPC adapters
613
+ - worker or queue adapters
614
+
615
+ Keep business operations oblivious to propagation mechanics.
616
+
617
+ ### Recommended Pattern
618
+
619
+ Preferred architecture:
620
+
621
+ 1. business code uses Effect observability APIs
622
+ 2. infrastructure composes `@effect/opentelemetry` layers
623
+ 3. the final app layer provides telemetry once at the top level
624
+
625
+ Example shape:
626
+
627
+ ```ts
628
+ const TelemetryLayer = NodeSdk.layer(() => ({
629
+ resource: {
630
+ serviceName: "todo-service",
631
+ serviceVersion: "1.0.0"
632
+ },
633
+ spanProcessor: mySpanProcessor,
634
+ metricReader: myMetricReader,
635
+ logRecordProcessor: myLogProcessor
636
+ }))
637
+
638
+ const AppLayer = Layer.mergeAll(
639
+ DomainLayer,
640
+ HttpLayer
641
+ ).pipe(
642
+ Layer.provide(TelemetryLayer)
643
+ )
644
+ ```
645
+
646
+ This keeps:
647
+
648
+ - app code portable
649
+ - OTel JS setup centralized
650
+ - shutdown semantics correct
651
+ - exported spans, logs, and metrics aligned
652
+
653
+ ### Anti-Patterns
654
+
655
+ - constructing OpenTelemetry SDK clients directly inside business services
656
+ - mixing manual exporter setup into domain code
657
+ - bypassing Effect logging and tracing APIs in normal business operations
658
+ - scattering provider shutdown logic across the application
659
+ - configuring telemetry separately in many subsystems instead of one top-level layer
660
+
661
+ ## Anti-Patterns
662
+
663
+ ### Anti-Pattern: business logic built from anonymous `Effect.gen` functions everywhere
664
+
665
+ Bad:
666
+
667
+ ```ts
668
+ const loadUser = (userId: string) =>
669
+ Effect.gen(function*() {
670
+ return { id: userId, name: "Ada" }
671
+ })
672
+ ```
673
+
674
+ Why this is bad:
675
+
676
+ - weaker tracing structure
677
+ - poorer stack traces
678
+ - less consistent naming in debugging output
679
+
680
+ Preferred:
681
+
682
+ ```ts
683
+ const loadUser = Effect.fn("loadUser")(function*(userId: string) {
684
+ return { id: userId, name: "Ada" }
685
+ })
686
+ ```
687
+
688
+ ### Anti-Pattern: using `Effect.fnUntraced` by default
689
+
690
+ This throws away free observability.
691
+
692
+ If you just do not want an explicit span name, use `Effect.fn` without a name instead.
693
+
694
+ Only use `fnUntraced` when you have a specific low-level reason.
695
+
696
+ ### Anti-Pattern: logging without structure or context
697
+
698
+ Bad:
699
+
700
+ - giant interpolated strings
701
+ - duplicate logs at every layer
702
+ - logs with no business identifiers
703
+
704
+ Prefer:
705
+
706
+ - named operations via `Effect.fn`
707
+ - structured logs with IDs and context
708
+ - a few high-value logs at operation boundaries
709
+
710
+ Also avoid:
711
+
712
+ - `console.*` in new Effect code
713
+ - custom logging wrappers around `Effect.log*`
714
+ - broad module-wide annotations when operation-level ownership is clearer
715
+ - dual legacy and Effect logging paths
716
+
717
+ ### Anti-Pattern: hand-instrumenting every helper with spans
718
+
719
+ Do not create explicit spans everywhere just because you can.
720
+
721
+ Preferred order:
722
+
723
+ 1. start with `Effect.fn`
724
+ 2. add `Effect.withSpan` only where extra detail is actually useful
725
+ 3. add metrics at meaningful boundaries
726
+
727
+ ## Recommended Patterns
728
+
729
+ ### Pattern: observable business operation
730
+
731
+ ```ts
732
+ import { Effect } from "effect"
733
+
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
+ })
739
+ ```
740
+
741
+ ### Pattern: orchestration with nested spans
742
+
743
+ ```ts
744
+ const syncUser = Effect.fn("syncUser")(function*(userId: string) {
745
+ const profile = yield* fetchRemoteProfile(userId).pipe(
746
+ Effect.withSpan("fetchRemoteProfile")
747
+ )
748
+
749
+ return yield* persistProfile(profile).pipe(
750
+ Effect.withSpan("persistProfile")
751
+ )
752
+ })
753
+ ```
754
+
755
+ ### Pattern: framework boundary with runtime
756
+
757
+ ```ts
758
+ const runtime = ManagedRuntime.make(AppLayer)
759
+
760
+ const handleRequest = (userId: string) =>
761
+ runtime.runPromise(fetchUser(userId))
762
+ ```
763
+
764
+ ## Good Repo Examples To Study
765
+
766
+ - `packages/effect/src/Effect.ts`
767
+ - `packages/effect/src/Tracer.ts`
768
+ - `packages/effect/src/Logger.ts`
769
+ - `packages/effect/src/Metric.ts`
770
+ - `packages/opentelemetry/src/NodeSdk.ts`
771
+ - `packages/opentelemetry/src/OtelTracer.ts`