better-effect 0.13.0 → 0.14.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 (109) hide show
  1. package/README.md +337 -88
  2. package/dist/adapters/iti.d.mts +2 -3
  3. package/dist/adapters/iti.d.mts.map +1 -1
  4. package/dist/adapters/iti.mjs +2 -2
  5. package/dist/bun.d.mts +75 -23
  6. package/dist/bun.d.mts.map +1 -1
  7. package/dist/bun.mjs +170 -33
  8. package/dist/bun.mjs.map +1 -1
  9. package/dist/{context-BphfFUMO.mjs → context-DSGSqcJj.mjs} +12 -3
  10. package/dist/context-DSGSqcJj.mjs.map +1 -0
  11. package/dist/{current-request-DDKNyymv.mjs → current-request-Bc1YHL0w.mjs} +3 -2
  12. package/dist/{current-request-DDKNyymv.mjs.map → current-request-Bc1YHL0w.mjs.map} +1 -1
  13. package/dist/{current-request-CIU7AHhF.d.mts → current-request-Dm-QQ3Xr.d.mts} +4 -4
  14. package/dist/current-request-Dm-QQ3Xr.d.mts.map +1 -0
  15. package/dist/{effect-CPdV7Yv8.mjs → effect-W0IQXY8P.mjs} +21 -2
  16. package/dist/effect-W0IQXY8P.mjs.map +1 -0
  17. package/dist/{errors-BKcsQ0ip.d.mts → errors-CpHMzPiP.d.mts} +6 -2
  18. package/dist/errors-CpHMzPiP.d.mts.map +1 -0
  19. package/dist/hono.d.mts +63 -27
  20. package/dist/hono.d.mts.map +1 -1
  21. package/dist/hono.mjs +111 -45
  22. package/dist/hono.mjs.map +1 -1
  23. package/dist/{index-CCi07kUw.d.mts → index-3Bkl-sxS.d.mts} +7 -2
  24. package/dist/{index-CCi07kUw.d.mts.map → index-3Bkl-sxS.d.mts.map} +1 -1
  25. package/dist/{index-cXgyMa0v.d.mts → index-C2ed9Ilq.d.mts} +32 -7
  26. package/dist/index-C2ed9Ilq.d.mts.map +1 -0
  27. package/dist/{index-BodMy6rO.d.mts → index-ClNndn2-.d.mts} +93 -63
  28. package/dist/index-ClNndn2-.d.mts.map +1 -0
  29. package/dist/{index-DAKn0qHk.d.mts → index-Cy1VsU-u.d.mts} +2 -4
  30. package/dist/index-Cy1VsU-u.d.mts.map +1 -0
  31. package/dist/index.d.mts +6 -8
  32. package/dist/index.mjs +8 -9
  33. package/dist/index.mjs.map +1 -1
  34. package/dist/{internal-identity-CbXoZIfw.mjs → internal-identity-BuHBtP0k.mjs} +2 -2
  35. package/dist/{internal-identity-CbXoZIfw.mjs.map → internal-identity-BuHBtP0k.mjs.map} +1 -1
  36. package/dist/layer-2kjHVv4N.mjs +343 -0
  37. package/dist/layer-2kjHVv4N.mjs.map +1 -0
  38. package/dist/layer-C2OaqTmB.d.mts +987 -0
  39. package/dist/layer-C2OaqTmB.d.mts.map +1 -0
  40. package/dist/next.d.mts +102 -26
  41. package/dist/next.d.mts.map +1 -1
  42. package/dist/next.mjs +157 -40
  43. package/dist/next.mjs.map +1 -1
  44. package/dist/{node-context-DczjAcrX.d.mts → node-context-1YEjrU9g.d.mts} +2 -2
  45. package/dist/{node-context-DczjAcrX.d.mts.map → node-context-1YEjrU9g.d.mts.map} +1 -1
  46. package/dist/{node-context-Ca18oNSg.mjs → node-context-g3bCcY9t.mjs} +2 -2
  47. package/dist/{node-context-Ca18oNSg.mjs.map → node-context-g3bCcY9t.mjs.map} +1 -1
  48. package/dist/node.d.mts +11 -6
  49. package/dist/node.d.mts.map +1 -1
  50. package/dist/node.mjs +156 -10
  51. package/dist/node.mjs.map +1 -1
  52. package/dist/opentelemetry.d.mts +20 -2
  53. package/dist/opentelemetry.d.mts.map +1 -1
  54. package/dist/opentelemetry.mjs +423 -5
  55. package/dist/opentelemetry.mjs.map +1 -1
  56. package/dist/{registration-drnZk0SY.mjs → registration-Bwe2GbVu.mjs} +8 -2
  57. package/dist/registration-Bwe2GbVu.mjs.map +1 -0
  58. package/dist/runtime/explicit.d.mts +2 -2
  59. package/dist/runtime/explicit.mjs +1 -1
  60. package/dist/runtime/node.d.mts +3 -3
  61. package/dist/runtime/node.mjs +2 -2
  62. package/dist/runtime-qpcVwaz_.mjs +1680 -0
  63. package/dist/runtime-qpcVwaz_.mjs.map +1 -0
  64. package/dist/service-CO9VJQYa.mjs +74 -0
  65. package/dist/service-CO9VJQYa.mjs.map +1 -0
  66. package/dist/{signal-nfgFyNCk.mjs → signal-5Rg9bUUe.mjs} +2 -2
  67. package/dist/{signal-nfgFyNCk.mjs.map → signal-5Rg9bUUe.mjs.map} +1 -1
  68. package/dist/{standard-services-vnGIafkS.mjs → standard-services-B-iMM_W7.mjs} +38 -4
  69. package/dist/standard-services-B-iMM_W7.mjs.map +1 -0
  70. package/dist/standard-services.d.mts +4 -4
  71. package/dist/standard-services.mjs +3 -3
  72. package/dist/testing.d.mts +25 -7
  73. package/dist/testing.d.mts.map +1 -1
  74. package/dist/testing.mjs +51 -7
  75. package/dist/testing.mjs.map +1 -1
  76. package/dist/{types-oihI-ESv.d.mts → types-BFTqCCcw.d.mts} +32 -5
  77. package/dist/types-BFTqCCcw.d.mts.map +1 -0
  78. package/dist/web-effect-B9hp9jIe.mjs +380 -0
  79. package/dist/web-effect-B9hp9jIe.mjs.map +1 -0
  80. package/dist/web.d.mts +22 -13
  81. package/dist/web.d.mts.map +1 -1
  82. package/dist/web.mjs +1 -1
  83. package/package.json +10 -16
  84. package/dist/context-BphfFUMO.mjs.map +0 -1
  85. package/dist/current-request-CIU7AHhF.d.mts.map +0 -1
  86. package/dist/disposable-CP-Q2H3l.mjs +0 -57
  87. package/dist/disposable-CP-Q2H3l.mjs.map +0 -1
  88. package/dist/effect-CPdV7Yv8.mjs.map +0 -1
  89. package/dist/errors-BKcsQ0ip.d.mts.map +0 -1
  90. package/dist/index-AiKM71-u.d.mts +0 -376
  91. package/dist/index-AiKM71-u.d.mts.map +0 -1
  92. package/dist/index-BodMy6rO.d.mts.map +0 -1
  93. package/dist/index-DAKn0qHk.d.mts.map +0 -1
  94. package/dist/index-cXgyMa0v.d.mts.map +0 -1
  95. package/dist/layer-D16GKxuX.mjs +0 -269
  96. package/dist/layer-D16GKxuX.mjs.map +0 -1
  97. package/dist/observer-9nhbL7Pr.d.mts +0 -68
  98. package/dist/observer-9nhbL7Pr.d.mts.map +0 -1
  99. package/dist/outcome-DI8LPIQ4.d.mts +0 -387
  100. package/dist/outcome-DI8LPIQ4.d.mts.map +0 -1
  101. package/dist/program-metadata-BxjGkT17.mjs +0 -260
  102. package/dist/program-metadata-BxjGkT17.mjs.map +0 -1
  103. package/dist/registration-drnZk0SY.mjs.map +0 -1
  104. package/dist/runtime-C_55ofnL.mjs +0 -791
  105. package/dist/runtime-C_55ofnL.mjs.map +0 -1
  106. package/dist/standard-services-vnGIafkS.mjs.map +0 -1
  107. package/dist/types-oihI-ESv.d.mts.map +0 -1
  108. package/dist/web-Bq3MfMrj.mjs +0 -147
  109. package/dist/web-Bq3MfMrj.mjs.map +0 -1
@@ -1,4 +1,354 @@
1
1
  import { ROOT_CONTEXT, SpanStatusCode, context, trace } from "@opentelemetry/api";
2
+ //#region src/opentelemetry/lifecycle.ts
3
+ const readMonotonicTime$1 = () => {
4
+ try {
5
+ return performance.now();
6
+ } catch {
7
+ return Date.now();
8
+ }
9
+ };
10
+ const eventName$1 = (phase) => `better-effect.lifecycle.${phase}`;
11
+ const callSpan$2 = (operation) => {
12
+ try {
13
+ Promise.resolve(operation()).catch(() => {});
14
+ } catch {}
15
+ };
16
+ const finishSpan$1 = (span, status, details) => {
17
+ callSpan$2(() => span.setStatus({ code: status === "failure" ? SpanStatusCode.ERROR : SpanStatusCode.OK }));
18
+ if (details !== void 0) {
19
+ const message = details.message;
20
+ if (message !== void 0) callSpan$2(() => span.recordException(message));
21
+ if (Object.keys(details.attributes).length > 0) callSpan$2(() => span.addEvent("better-effect.failure", details.attributes));
22
+ }
23
+ callSpan$2(() => span.end());
24
+ };
25
+ const failureCause = (event) => {
26
+ if (event.error !== void 0) return event.error;
27
+ return event.outcome.status === "failure" ? event.outcome.cause : void 0;
28
+ };
29
+ const eventStatus = (event) => "error" in event && event.error !== void 0 ? "failure" : event.outcome.status;
30
+ /** Internal lifecycle state machine for the OpenTelemetry Runtime observer. */
31
+ var LifecycleTelemetry = class {
32
+ options;
33
+ states = /* @__PURE__ */ new Map();
34
+ disposed = false;
35
+ constructor(options) {
36
+ this.options = options;
37
+ }
38
+ onStart(event) {
39
+ if (this.disposed || this.options.mode === "off" || this.states.has(event.lifecycleId)) return;
40
+ const state = {
41
+ lifecycleId: event.lifecycleId,
42
+ startedAt: readMonotonicTime$1(),
43
+ parentContext: this.options.getParentContext(),
44
+ starting: true,
45
+ carrier: void 0,
46
+ activationSpan: void 0,
47
+ activationEnded: false,
48
+ activationReported: false,
49
+ pendingActivationEnd: void 0
50
+ };
51
+ this.states.set(event.lifecycleId, state);
52
+ const span = this.options.startSpan(this.options.mode === "events" ? "better-effect.lifecycle" : eventName$1("activate"), this.options.makeAttributes(event, "activate", void 0, void 0), state.parentContext);
53
+ if (!this.states.has(event.lifecycleId)) {
54
+ if (span !== void 0) callSpan$2(() => span.end());
55
+ return;
56
+ }
57
+ state.starting = false;
58
+ if (this.options.mode === "events") {
59
+ state.carrier = span;
60
+ if (span !== void 0) callSpan$2(() => span.addEvent(eventName$1("activate"), this.options.makeAttributes(event, "activate", void 0, void 0)));
61
+ } else state.activationSpan = span;
62
+ const pendingEnd = state.pendingActivationEnd;
63
+ if (pendingEnd !== void 0) {
64
+ state.pendingActivationEnd = void 0;
65
+ this.onEnd(pendingEnd);
66
+ }
67
+ }
68
+ onEnd(event) {
69
+ if (this.options.mode === "off") return;
70
+ const state = this.states.get(event.lifecycleId);
71
+ if (state === void 0 || state.activationReported) return;
72
+ const durationMs = Math.max(0, readMonotonicTime$1() - state.startedAt);
73
+ const status = eventStatus(event);
74
+ if (this.options.mode === "events") {
75
+ const span = state.carrier;
76
+ if (span === void 0) {
77
+ if (state.starting) {
78
+ state.pendingActivationEnd = event;
79
+ return;
80
+ }
81
+ state.activationReported = true;
82
+ if (status === "failure") this.states.delete(event.lifecycleId);
83
+ return;
84
+ }
85
+ state.activationReported = true;
86
+ state.activationEnded = true;
87
+ if (status === "failure") this.states.delete(event.lifecycleId);
88
+ callSpan$2(() => span.addEvent(eventName$1("activate"), this.options.makeAttributes(event, "activate", status, durationMs)));
89
+ if (status === "failure" && this.options.recordFailures) {
90
+ const details = this.options.getFailureDetails(failureCause(event));
91
+ if (details !== void 0) finishDetails(span, details);
92
+ }
93
+ if (status === "failure") {
94
+ callSpan$2(() => span.setStatus({ code: SpanStatusCode.ERROR }));
95
+ callSpan$2(() => span.end());
96
+ }
97
+ return;
98
+ }
99
+ const span = state.activationSpan;
100
+ if (span === void 0) {
101
+ if (state.starting) {
102
+ state.pendingActivationEnd = event;
103
+ return;
104
+ }
105
+ state.activationReported = true;
106
+ if (status === "failure") this.states.delete(event.lifecycleId);
107
+ return;
108
+ }
109
+ state.activationReported = true;
110
+ if (status === "failure") this.states.delete(event.lifecycleId);
111
+ state.activationEnded = true;
112
+ finishSpan$1(span, status, status === "failure" && this.options.recordFailures ? this.options.getFailureDetails(failureCause(event)) : void 0);
113
+ }
114
+ onRelease(event) {
115
+ if (this.options.mode === "off") return;
116
+ const state = this.states.get(event.lifecycleId);
117
+ if (state === void 0) return;
118
+ this.states.delete(event.lifecycleId);
119
+ const status = eventStatus(event);
120
+ const attributes = this.options.makeAttributes(event, "release", status, void 0);
121
+ const details = status === "failure" && this.options.recordFailures ? this.options.getFailureDetails(failureCause(event)) : void 0;
122
+ if (this.options.mode === "events") {
123
+ if (state.carrier === void 0) return;
124
+ callSpan$2(() => state.carrier.addEvent(eventName$1("release"), attributes));
125
+ if (details !== void 0) finishDetails(state.carrier, details);
126
+ finishSpan$1(state.carrier, status, void 0);
127
+ return;
128
+ }
129
+ const span = this.options.startSpan(eventName$1("release"), attributes, state.parentContext);
130
+ if (span !== void 0) finishSpan$1(span, status, details);
131
+ }
132
+ dispose() {
133
+ if (this.disposed) return;
134
+ this.disposed = true;
135
+ const pending = [...this.states.values()];
136
+ this.states.clear();
137
+ for (const state of pending) {
138
+ if (state.carrier !== void 0) callSpan$2(() => state.carrier.end());
139
+ if (state.activationSpan !== void 0 && !state.activationEnded) callSpan$2(() => state.activationSpan.end());
140
+ }
141
+ }
142
+ };
143
+ const finishDetails = (span, details) => {
144
+ const message = details.message;
145
+ if (message !== void 0) callSpan$2(() => span.recordException(message));
146
+ if (Object.keys(details.attributes).length > 0) callSpan$2(() => span.addEvent("better-effect.failure", details.attributes));
147
+ };
148
+ //#endregion
149
+ //#region src/opentelemetry/shutdown.ts
150
+ const callSpan$1 = (operation) => {
151
+ try {
152
+ Promise.resolve(operation()).catch(() => {});
153
+ } catch {}
154
+ };
155
+ const readMonotonicTime = () => {
156
+ try {
157
+ return performance.now();
158
+ } catch {
159
+ return Date.now();
160
+ }
161
+ };
162
+ const boundedDuration$1 = (value) => value !== void 0 && Number.isFinite(value) && value >= 0 ? Math.min(value, 2 ** 31 - 1) : void 0;
163
+ const phaseOf = (phase) => {
164
+ switch (phase) {
165
+ case "shutdown-requested": return "requested";
166
+ case "quiesce-start":
167
+ case "quiesce-end": return "quiesce";
168
+ case "drain-start":
169
+ case "drain-end": return "drain";
170
+ case "abort-active": return "abort";
171
+ case "release-start":
172
+ case "release-end": return "release";
173
+ case "shutdown-complete":
174
+ case "shutdown-failure": return "complete";
175
+ }
176
+ };
177
+ const eventName = (phase) => `better-effect.shutdown.${phase}`;
178
+ const isStart = (phase) => phase === "quiesce-start" || phase === "drain-start" || phase === "release-start";
179
+ const isEnd = (phase) => phase === "quiesce-end" || phase === "drain-end" || phase === "release-end";
180
+ const isTerminal = (phase) => phase === "shutdown-complete" || phase === "shutdown-failure";
181
+ const finishSpan = (span, status, details) => {
182
+ callSpan$1(() => span.setStatus({ code: status === "failure" ? SpanStatusCode.ERROR : SpanStatusCode.OK }));
183
+ if (details !== void 0) {
184
+ const message = details.message;
185
+ if (message !== void 0) callSpan$1(() => span.recordException(message));
186
+ if (Object.keys(details.attributes).length > 0) callSpan$1(() => span.addEvent("better-effect.failure", details.attributes));
187
+ }
188
+ callSpan$1(() => span.end());
189
+ };
190
+ /** Internal shutdown state machine for the OpenTelemetry Runtime observer. */
191
+ var ShutdownTelemetry = class {
192
+ options;
193
+ states = /* @__PURE__ */ new Map();
194
+ disposed = false;
195
+ constructor(options) {
196
+ this.options = options;
197
+ }
198
+ onEvent(event) {
199
+ if (this.disposed || this.options.mode === "off") return;
200
+ if (event.phase === "shutdown-requested") {
201
+ this.start(event);
202
+ return;
203
+ }
204
+ const state = this.states.get(event.reason);
205
+ if (state === void 0) return;
206
+ if (state.span === void 0 && state.starting) {
207
+ state.pendingEvents.push(event);
208
+ return;
209
+ }
210
+ if (state.span === void 0 && isTerminal(event.phase)) {
211
+ this.states.delete(event.reason);
212
+ state.pendingEvents.length = 0;
213
+ return;
214
+ }
215
+ if (state.span !== void 0) this.process(state, event);
216
+ }
217
+ start(event) {
218
+ if (this.states.has(event.reason)) return;
219
+ const state = {
220
+ reason: event.reason,
221
+ starting: true,
222
+ span: void 0,
223
+ phaseSpans: /* @__PURE__ */ new Map(),
224
+ phaseStartedAt: /* @__PURE__ */ new Map(),
225
+ pendingPhaseEnds: /* @__PURE__ */ new Map(),
226
+ pendingEvents: []
227
+ };
228
+ this.states.set(event.reason, state);
229
+ const span = this.options.startSpan("better-effect.shutdown", this.options.makeAttributes(event, "requested", void 0), this.options.rootContext);
230
+ if (!this.states.has(event.reason)) {
231
+ if (span !== void 0) callSpan$1(() => span.end());
232
+ return;
233
+ }
234
+ state.starting = false;
235
+ state.span = span;
236
+ if (span !== void 0) this.addEvent(state, event);
237
+ const pendingEvents = state.pendingEvents.splice(0);
238
+ for (const pendingEvent of pendingEvents) this.process(state, pendingEvent);
239
+ }
240
+ process(state, event) {
241
+ const phase = phaseOf(event.phase);
242
+ if (isStart(event.phase)) {
243
+ this.startPhase(state, event, phase);
244
+ return;
245
+ }
246
+ if (isEnd(event.phase)) {
247
+ if (!state.phaseStartedAt.has(phase)) return;
248
+ if (this.options.mode === "spans" && !state.phaseSpans.has(phase)) {
249
+ state.pendingPhaseEnds.set(phase, event);
250
+ return;
251
+ }
252
+ this.finishPhase(state, event, phase);
253
+ return;
254
+ }
255
+ if (event.phase === "abort-active") {
256
+ if (state.phaseStartedAt.has("abort")) return;
257
+ state.phaseStartedAt.set("abort", readMonotonicTime());
258
+ if (this.options.mode === "events") {
259
+ this.addEvent(state, event);
260
+ state.phaseStartedAt.delete("abort");
261
+ } else {
262
+ const span = this.startPhaseSpan(state, event, "abort");
263
+ if (span !== void 0) {
264
+ state.phaseStartedAt.delete("abort");
265
+ finishSpan(span, "success", void 0);
266
+ }
267
+ }
268
+ return;
269
+ }
270
+ if (isTerminal(event.phase)) this.finish(state, event);
271
+ }
272
+ startPhase(state, event, phase) {
273
+ if (state.phaseStartedAt.has(phase)) return;
274
+ state.phaseStartedAt.set(phase, readMonotonicTime());
275
+ if (this.options.mode === "events") {
276
+ this.addEvent(state, event);
277
+ return;
278
+ }
279
+ const span = this.startPhaseSpan(state, event, phase);
280
+ if (!this.states.has(state.reason)) {
281
+ if (span !== void 0) callSpan$1(() => span.end());
282
+ return;
283
+ }
284
+ if (span !== void 0) {
285
+ state.phaseSpans.set(phase, span);
286
+ const pendingEnd = state.pendingPhaseEnds.get(phase);
287
+ if (pendingEnd !== void 0) {
288
+ state.pendingPhaseEnds.delete(phase);
289
+ this.finishPhase(state, pendingEnd, phase);
290
+ }
291
+ }
292
+ }
293
+ startPhaseSpan(state, event, phase) {
294
+ let parent = this.options.rootContext;
295
+ if (state.span !== void 0) try {
296
+ parent = trace.setSpan(this.options.rootContext, state.span);
297
+ } catch {
298
+ parent = this.options.rootContext;
299
+ }
300
+ return this.options.startSpan(eventName(phase), this.options.makeAttributes(event, phase, void 0), parent);
301
+ }
302
+ addEvent(state, event, durationMsOverride) {
303
+ const span = state.span;
304
+ if (span === void 0) return;
305
+ const phase = phaseOf(event.phase);
306
+ const startedAt = state.phaseStartedAt.get(phase);
307
+ const durationMs = durationMsOverride ?? (startedAt === void 0 ? void 0 : Math.max(0, readMonotonicTime() - startedAt));
308
+ callSpan$1(() => span.addEvent(eventName(phase), this.options.makeAttributes(event, phase, durationMs)));
309
+ }
310
+ finishPhase(state, event, phase) {
311
+ const span = state.phaseSpans.get(phase);
312
+ const startedAt = state.phaseStartedAt.get(phase);
313
+ const durationMs = startedAt === void 0 ? event.durationMs : Math.max(0, readMonotonicTime() - startedAt);
314
+ state.phaseSpans.delete(phase);
315
+ state.phaseStartedAt.delete(phase);
316
+ state.pendingPhaseEnds.delete(phase);
317
+ if (this.options.mode === "events") {
318
+ this.addEvent(state, event, durationMs);
319
+ return;
320
+ }
321
+ if (span !== void 0) {
322
+ callSpan$1(() => span.setAttribute("better_effect.duration_ms", boundedDuration$1(durationMs) ?? 0));
323
+ finishSpan(span, "success", void 0);
324
+ }
325
+ }
326
+ finish(state, event) {
327
+ if (this.states.get(state.reason) !== state) return;
328
+ this.states.delete(state.reason);
329
+ const status = event.phase === "shutdown-failure" ? "failure" : "success";
330
+ const details = status === "failure" && this.options.recordFailures ? this.options.getFailureDetails(event.error) : void 0;
331
+ for (const span of state.phaseSpans.values()) callSpan$1(() => span.end());
332
+ state.phaseSpans.clear();
333
+ state.phaseStartedAt.clear();
334
+ state.pendingPhaseEnds.clear();
335
+ state.pendingEvents.length = 0;
336
+ if (state.span === void 0) return;
337
+ this.addEvent(state, event);
338
+ finishSpan(state.span, status, details);
339
+ }
340
+ dispose() {
341
+ if (this.disposed) return;
342
+ this.disposed = true;
343
+ const pending = [...this.states.values()];
344
+ this.states.clear();
345
+ for (const state of pending) {
346
+ for (const span of state.phaseSpans.values()) callSpan$1(() => span.end());
347
+ if (state.span !== void 0) callSpan$1(() => state.span.end());
348
+ }
349
+ }
350
+ };
351
+ //#endregion
2
352
  //#region src/opentelemetry/index.ts
3
353
  const DEFAULT_TRACER_NAME = "better-effect";
4
354
  const DEFAULT_EXECUTION_SPAN_NAME = "better-effect.execution";
@@ -17,11 +367,47 @@ const SERVICE_TAG_ATTRIBUTE = "better_effect.service_tag";
17
367
  const RESOLUTION_PATH_ATTRIBUTE = "better_effect.resolution_path";
18
368
  const RESOLUTION_PATH_TRUNCATED_ATTRIBUTE = "better_effect.resolution_path_truncated";
19
369
  const LIBRARY_ATTRIBUTE = "better_effect.library";
370
+ const LIFECYCLE_NAME_ATTRIBUTE = "better_effect.lifecycle_name";
371
+ const LIFECYCLE_PHASE_ATTRIBUTE = "better_effect.lifecycle_phase";
372
+ const SHUTDOWN_PHASE_ATTRIBUTE = "better_effect.shutdown_phase";
373
+ const SHUTDOWN_REASON_ATTRIBUTE = "better_effect.shutdown_reason";
374
+ const SHUTDOWN_SIGNAL_ATTRIBUTE = "better_effect.shutdown_signal";
375
+ const DURATION_ATTRIBUTE = "better_effect.duration_ms";
20
376
  const FAILURE_EVENT_NAME = "better-effect.failure";
21
377
  const serviceEventStatus = (event) => "error" in event && event.error !== void 0 ? "failure" : event.outcome.status;
22
378
  const executionEventStatus = (event) => event.cleanupFailure !== void 0 ? "failure" : event.outcome.status;
23
379
  const isStartedExecutionSpan = (execution) => execution.span !== void 0;
24
380
  const isActiveExecutionSpan = (execution) => isStartedExecutionSpan(execution) && execution.executionContext !== void 0;
381
+ const isTelemetryMode = (value) => value === "off" || value === "events" || value === "spans";
382
+ const boundedDuration = (value) => value !== void 0 && Number.isFinite(value) && value >= 0 ? Math.min(value, 2 ** 31 - 1) : void 0;
383
+ const addOutcomeAttribute = (attributes, outcome, limit) => {
384
+ if (outcome !== void 0) addAttribute(attributes, OUTCOME_ATTRIBUTE, outcome, limit);
385
+ };
386
+ const addDurationAttribute = (attributes, durationMs, limit) => {
387
+ const duration = boundedDuration(durationMs);
388
+ if (duration !== void 0) addAttribute(attributes, DURATION_ATTRIBUTE, duration, limit);
389
+ };
390
+ const makeLifecycleAttributes = (event, phase, outcome, durationMs, limits) => {
391
+ const attributes = {};
392
+ const name = boundedNonEmptyString(event.name, limits.maxAttributeLength);
393
+ if (name !== void 0) addAttribute(attributes, LIFECYCLE_NAME_ATTRIBUTE, name, limits.maxAttributeCount);
394
+ addAttribute(attributes, LIFECYCLE_PHASE_ATTRIBUTE, phase, limits.maxAttributeCount);
395
+ addOutcomeAttribute(attributes, outcome, limits.maxAttributeCount);
396
+ addDurationAttribute(attributes, durationMs, limits.maxAttributeCount);
397
+ return attributes;
398
+ };
399
+ const makeShutdownAttributes = (event, phase, durationMs, limits) => {
400
+ const attributes = {};
401
+ const reason = event.reason;
402
+ addAttribute(attributes, SHUTDOWN_PHASE_ATTRIBUTE, phase, limits.maxAttributeCount);
403
+ addAttribute(attributes, SHUTDOWN_REASON_ATTRIBUTE, reason.kind, limits.maxAttributeCount);
404
+ if (reason.signal === "SIGINT" || reason.signal === "SIGTERM") addAttribute(attributes, SHUTDOWN_SIGNAL_ATTRIBUTE, reason.signal, limits.maxAttributeCount);
405
+ if (event.activeExecutions !== void 0 && Number.isSafeInteger(event.activeExecutions)) addAttribute(attributes, "better_effect.active_executions", Math.max(0, event.activeExecutions), limits.maxAttributeCount);
406
+ addDurationAttribute(attributes, durationMs ?? event.durationMs, limits.maxAttributeCount);
407
+ if (event.phase === "shutdown-complete") addOutcomeAttribute(attributes, "success", limits.maxAttributeCount);
408
+ else if (event.phase === "shutdown-failure") addOutcomeAttribute(attributes, "failure", limits.maxAttributeCount);
409
+ return attributes;
410
+ };
25
411
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
26
412
  const isSpan = (value) => isRecord(value) && typeof value.end === "function";
27
413
  const isTracer = (value) => isRecord(value) && typeof value.startSpan === "function";
@@ -259,6 +645,8 @@ var OpenTelemetryRuntimeObserver = class OpenTelemetryRuntimeObserver {
259
645
  limits;
260
646
  executionSpans = /* @__PURE__ */ new Map();
261
647
  serviceResolution;
648
+ lifecycleTelemetry;
649
+ shutdownTelemetry;
262
650
  recordFailures;
263
651
  disposed = false;
264
652
  constructor(options) {
@@ -267,11 +655,27 @@ var OpenTelemetryRuntimeObserver = class OpenTelemetryRuntimeObserver {
267
655
  this.limits = normalizeLimits(options);
268
656
  this.serviceResolution = options.serviceResolution ?? "off";
269
657
  this.recordFailures = options.recordFailures === true;
270
- if (![
271
- "off",
272
- "events",
273
- "spans"
274
- ].includes(this.serviceResolution)) throw new TypeError("serviceResolution must be off, events, or spans");
658
+ if (!isTelemetryMode(this.serviceResolution)) throw new TypeError("serviceResolution must be off, events, or spans");
659
+ const lifecycle = options.lifecycle ?? "off";
660
+ if (!isTelemetryMode(lifecycle)) throw new TypeError("lifecycle must be off, events, or spans");
661
+ const shutdown = options.shutdown ?? "off";
662
+ if (!isTelemetryMode(shutdown)) throw new TypeError("shutdown must be off, events, or spans");
663
+ this.lifecycleTelemetry = new LifecycleTelemetry({
664
+ mode: lifecycle,
665
+ recordFailures: this.recordFailures,
666
+ getParentContext: () => readActiveContext() ?? ROOT_CONTEXT,
667
+ startSpan: this.startSpan.bind(this),
668
+ makeAttributes: (event, phase, outcome, durationMs) => makeLifecycleAttributes(event, phase, outcome, durationMs, this.limits),
669
+ getFailureDetails: (cause) => makeFailureDetails(cause, this.options.sanitizeFailure, this.limits)
670
+ });
671
+ this.shutdownTelemetry = new ShutdownTelemetry({
672
+ mode: shutdown,
673
+ recordFailures: this.recordFailures,
674
+ rootContext: ROOT_CONTEXT,
675
+ startSpan: this.startSpan.bind(this),
676
+ makeAttributes: (event, phase, durationMs) => makeShutdownAttributes(event, phase, durationMs, this.limits),
677
+ getFailureDetails: (cause) => makeFailureDetails(cause, this.options.sanitizeFailure, this.limits)
678
+ });
275
679
  }
276
680
  static make(options) {
277
681
  return new OpenTelemetryRuntimeObserver(options);
@@ -291,6 +695,18 @@ var OpenTelemetryRuntimeObserver = class OpenTelemetryRuntimeObserver {
291
695
  onResourceRelease = (event) => {
292
696
  this.safely(() => this.observeService("better-effect.service.release", event));
293
697
  };
698
+ onLifecycleStart = (event) => {
699
+ this.safely(() => this.lifecycleTelemetry.onStart(event));
700
+ };
701
+ onLifecycleEnd = (event) => {
702
+ this.safely(() => this.lifecycleTelemetry.onEnd(event));
703
+ };
704
+ onLifecycleRelease = (event) => {
705
+ this.safely(() => this.lifecycleTelemetry.onRelease(event));
706
+ };
707
+ onShutdown = (event) => {
708
+ this.safely(() => this.shutdownTelemetry.onEvent(event));
709
+ };
294
710
  /** End any spans whose malformed or missing end event left them pending. */
295
711
  dispose() {
296
712
  if (this.disposed) return;
@@ -301,6 +717,8 @@ var OpenTelemetryRuntimeObserver = class OpenTelemetryRuntimeObserver {
301
717
  if (span === void 0) continue;
302
718
  this.endExecutionSpan(execution, span);
303
719
  }
720
+ this.lifecycleTelemetry.dispose();
721
+ this.shutdownTelemetry.dispose();
304
722
  }
305
723
  [Symbol.dispose]() {
306
724
  this.dispose();