@capxul/observability 4.20.0-beta.23 → 4.20.0-beta.26

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.
@@ -1,8 +1,190 @@
1
- import { _ as CLI_COMMAND_NAMES, cn as ORGANIZATION_SETUP_REASONS, dn as FAILURE_MODES, fn as isCredentialField, g as CLI_CAPABILITIES, ln as ORGANIZATION_SETUP_STATUSES, mn as redactUrlSecrets, on as isCapxulOperation, pn as redactSecrets, un as CAPXUL_ERROR_CODES, v as CLI_INVOCATION_KINDS } from "./src-CCuVZt-H.mjs";
1
+ import { I as CLI_CAPABILITIES, L as CLI_COMMAND_NAMES, R as CLI_INVOCATION_KINDS, dn as redactSecrets, fn as redactUrlSecrets, hn as FAILURE_MODES, ln as ORGANIZATION_SETUP_REASONS, mn as CAPXUL_ERROR_CODES, pn as isCredentialField, sn as isCapxulOperation, un as ORGANIZATION_SETUP_STATUSES } from "./src-CX-Shigb.mjs";
2
2
  import { Cause, Clock, Context, Deferred, Duration, Effect, Exit, FiberSet, Layer, LogLevel, Logger, Metric, Option, References, Schedule, Stream, Tracer } from "effect";
3
- import { FetchHttpClient, Headers, HttpBody, HttpClient, HttpClientError, HttpClientResponse } from "effect/unstable/http";
4
- import { OtlpExporter, OtlpLogger, OtlpMetrics, OtlpSerialization, OtlpTracer } from "effect/unstable/observability";
5
- //#region src/engineering-delivery.ts
3
+ import { FetchHttpClient, Headers, HttpBody, HttpClient, HttpClientError, HttpClientResponse } from "effect/http";
4
+ import { OtlpExporter, OtlpLogger, OtlpMetrics, OtlpSerialization, OtlpTracer } from "effect/observability";
5
+ //#region ../protocol/src/primitives/record.ts
6
+ /** A non-null object that is not an array: a value whose fields can be read by name. */
7
+ function isRecord(value) {
8
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9
+ }
10
+ /** The field `key` of `value` when `value` is a record, else undefined. */
11
+ function readField(value, key) {
12
+ return isRecord(value) ? value[key] : void 0;
13
+ }
14
+ //#endregion
15
+ //#region src/delivery/admission.ts
16
+ function newDeliveryState(budget, ownsMetrics) {
17
+ return {
18
+ budget,
19
+ ownsMetrics,
20
+ closed: Deferred.makeUnsafe(),
21
+ isClosed: false,
22
+ metricsRunning: false,
23
+ logs: [],
24
+ spans: /* @__PURE__ */ new Map(),
25
+ terminalBodies: /* @__PURE__ */ new WeakSet(),
26
+ bodyLeases: /* @__PURE__ */ new WeakMap(),
27
+ pendingBodies: /* @__PURE__ */ new Set()
28
+ };
29
+ }
30
+ /** Hold the records a body carries until its request finishes or the body is discarded. */
31
+ function holdBody(state, body, releases) {
32
+ if (releases.length === 0) return;
33
+ const lease = {
34
+ started: false,
35
+ release: () => {
36
+ if (!state.pendingBodies.delete(lease)) return;
37
+ state.bodyLeases.delete(body);
38
+ for (const release of releases) release();
39
+ }
40
+ };
41
+ state.bodyLeases.set(body, lease);
42
+ state.pendingBodies.add(lease);
43
+ }
44
+ const discardBody = (state, body) => state.bodyLeases.get(body)?.release();
45
+ const drainLogs = (state) => {
46
+ for (const release of state.logs.splice(0)) release();
47
+ };
48
+ function closeDelivery(state) {
49
+ state.isClosed = true;
50
+ Deferred.doneUnsafe(state.closed, Effect.void);
51
+ for (const lease of state.pendingBodies) if (!lease.started) lease.release();
52
+ drainLogs(state);
53
+ for (const release of state.spans.values()) release();
54
+ state.spans.clear();
55
+ if (state.ownsMetrics) state.budget.metricRegistry.clear();
56
+ }
57
+ const text = (value) => typeof value === "string" ? value.slice(0, 128) : "capxul.engineering";
58
+ const attributes = (input, limit = 32) => {
59
+ const out = {};
60
+ let count = 0;
61
+ let bytes = 0;
62
+ try {
63
+ for (const key in input) {
64
+ if (count++ >= limit) break;
65
+ if (key.length > 64) continue;
66
+ const value = Object.getOwnPropertyDescriptor(input, key)?.value;
67
+ const scalar = typeof value === "string" ? value.slice(0, 128) : typeof value === "boolean" || typeof value === "number" && Number.isFinite(value) ? value : void 0;
68
+ if (scalar === void 0) continue;
69
+ bytes += new TextEncoder().encode(JSON.stringify([key, scalar])).length;
70
+ if (bytes > (limit >= 24 ? 2e3 : 300)) break;
71
+ out[key] = scalar;
72
+ }
73
+ } catch {}
74
+ return out;
75
+ };
76
+ const terminalName = (name) => name === "cli.command.completed" || name === "cli.wallet.step" || CLI_COMMAND_NAMES.some((command) => name === `cli.command.${command}`);
77
+ //#endregion
78
+ //#region src/delivery/records.ts
79
+ function admittedLogger(state, delegate) {
80
+ return Logger.make((options) => {
81
+ if (state.isClosed) return;
82
+ let release;
83
+ try {
84
+ const message = text(Array.isArray(options.message) ? Object.getOwnPropertyDescriptor(options.message, "0")?.value : options.message);
85
+ release = state.budget.admitRecord(terminalName(message));
86
+ if (release === void 0) return;
87
+ const context = Context.add(options.fiber.context, References.CurrentLogAnnotations, attributes(options.fiber.getRef(References.CurrentLogAnnotations)));
88
+ const fiber = Object.create(options.fiber);
89
+ Object.defineProperty(fiber, "getRef", { value: (ref) => Context.get(context, ref) });
90
+ state.logs.push(release);
91
+ delegate.log({
92
+ ...options,
93
+ message,
94
+ cause: Cause.empty,
95
+ fiber
96
+ });
97
+ } catch {
98
+ if (release !== void 0) {
99
+ const index = state.logs.indexOf(release);
100
+ if (index !== -1) state.logs.splice(index, 1);
101
+ release();
102
+ }
103
+ state.budget.drop();
104
+ }
105
+ });
106
+ }
107
+ function admittedTracer(state, delegate) {
108
+ return Tracer.make({
109
+ span(options) {
110
+ const name = text(options.name);
111
+ const release = state.isClosed ? void 0 : state.budget.admitRecord(terminalName(name));
112
+ const spanOptions = {
113
+ ...options,
114
+ name,
115
+ annotations: Context.empty(),
116
+ links: []
117
+ };
118
+ const span = release === void 0 ? new Tracer.NativeSpan({
119
+ ...spanOptions,
120
+ sampled: false
121
+ }) : delegate.span(spanOptions);
122
+ if (release !== void 0) state.spans.set(span.spanId, release);
123
+ const wrapped = boundedSpan(state, span);
124
+ wrapped.addLinks(options.links ?? []);
125
+ return wrapped;
126
+ },
127
+ ...delegate.context === void 0 ? {} : { context: delegate.context.bind(delegate) }
128
+ });
129
+ }
130
+ /** The span as a producer sees it: at most 32 fields and 2000 attribute bytes, 2 events, 2 links. */
131
+ function boundedSpan(state, span) {
132
+ const { budget } = state;
133
+ const wrapped = Object.create(span);
134
+ let fields = 0, events = 0, links = 0, attributeBytes = 0;
135
+ let ended = false;
136
+ Object.defineProperties(wrapped, {
137
+ attribute: { value: (key, value) => {
138
+ if (fields >= 32 && !span.attributes.has(key)) {
139
+ budget.drop();
140
+ return;
141
+ }
142
+ const safe = attributes({ [key]: value }, 1);
143
+ if (!Object.hasOwn(safe, key)) return;
144
+ const previous = span.attributes.has(key) ? new TextEncoder().encode(JSON.stringify([key, span.attributes.get(key)])).length : 0;
145
+ const next = new TextEncoder().encode(JSON.stringify([key, safe[key]])).length;
146
+ if (attributeBytes - previous + next > 2e3) {
147
+ budget.drop();
148
+ return;
149
+ }
150
+ attributeBytes += next - previous;
151
+ if (!span.attributes.has(key)) fields += 1;
152
+ span.attribute(key, safe[key]);
153
+ } },
154
+ event: { value: (eventName, time, attrs) => {
155
+ if (events++ >= 2) {
156
+ budget.drop();
157
+ return;
158
+ }
159
+ span.event(text(eventName), time, attributes(attrs ?? {}, 4));
160
+ } },
161
+ addLinks: { value: (items) => {
162
+ for (const item of items.slice(0, Math.max(0, 2 - links))) {
163
+ if (!/^[a-f0-9]{32}$/u.test(item.span.traceId) || !/^[a-f0-9]{16}$/u.test(item.span.spanId)) {
164
+ budget.drop();
165
+ continue;
166
+ }
167
+ span.addLinks([{
168
+ span: Tracer.externalSpan({
169
+ traceId: item.span.traceId,
170
+ spanId: item.span.spanId,
171
+ sampled: item.span.sampled
172
+ }),
173
+ attributes: attributes(item.attributes, 4)
174
+ }]);
175
+ links += 1;
176
+ }
177
+ } },
178
+ end: { value: (time, exit) => {
179
+ if (ended) return;
180
+ ended = true;
181
+ span.end(time, Exit.isFailure(exit) ? Exit.fail("CLI operation failed") : Exit.void);
182
+ } }
183
+ });
184
+ return wrapped;
185
+ }
186
+ //#endregion
187
+ //#region src/delivery/budget.ts
6
188
  /** Fixed admission limits for one CLI invocation and its SDK client. */
7
189
  const CLI_ENGINEERING_LIMITS = Object.freeze({
8
190
  records: 80,
@@ -52,9 +234,8 @@ var CliMetricRegistry = class extends Map {
52
234
  return this;
53
235
  }
54
236
  const labels = Object.entries(entry.attributes ?? {});
55
- const state = entry.hooks.get(Context.empty());
56
- const buckets = typeof state === "object" && state !== null && "buckets" in state ? state.buckets : void 0;
57
- if (!["Counter", "Histogram"].includes(entry.type) || (entry.description?.length ?? 0) > 128 || labels.length > 6 || labels.some(([name, value]) => !metricValues[name]?.includes(String(value))) || entry.type === "Histogram" && (!Array.isArray(buckets) || buckets.length > 32)) {
237
+ const buckets = readField(entry.hooks.get(Context.empty()), "buckets");
238
+ if (!["Counter", "Histogram"].includes(entry.type) || (entry.description?.length ?? 0) > 128 || labels.length > 6 || labels.some(([name, value]) => !metricValues[name]?.includes(value)) || entry.type === "Histogram" && (!Array.isArray(buckets) || buckets.length > 32)) {
58
239
  this.dropped();
59
240
  return this;
60
241
  }
@@ -118,266 +299,123 @@ function makeCliEngineeringBudget() {
118
299
  }
119
300
  });
120
301
  }
121
- const text = (value) => typeof value === "string" ? value.slice(0, 128) : "capxul.engineering";
122
- const attributes = (input, limit = 32) => {
123
- const out = {};
124
- let count = 0;
125
- let bytes = 0;
126
- try {
127
- for (const key in input) {
128
- if (count++ >= limit) break;
129
- if (key.length > 64) continue;
130
- const value = Object.getOwnPropertyDescriptor(input, key)?.value;
131
- const scalar = typeof value === "string" ? value.slice(0, 128) : typeof value === "boolean" || typeof value === "number" && Number.isFinite(value) ? value : void 0;
132
- if (scalar === void 0) continue;
133
- bytes += new TextEncoder().encode(JSON.stringify([key, scalar])).length;
134
- if (bytes > (limit >= 24 ? 2e3 : 300)) break;
135
- out[key] = scalar;
136
- }
137
- } catch {}
138
- return out;
139
- };
140
- const terminalName = (name) => name === "cli.command.completed" || name === "cli.wallet.step" || CLI_COMMAND_NAMES.some((command) => name === `cli.command.${command}`);
141
- /** Native exporters retain their serialization and scheduling; this owns admission only. */
142
- function cliEngineeringDelivery(budget, ownsMetrics) {
143
- const closed = Deferred.makeUnsafe();
144
- let isClosed = false;
145
- let metricsRunning = false;
146
- const logs = [];
147
- const spans = /* @__PURE__ */ new Map();
148
- const terminalBodies = /* @__PURE__ */ new WeakSet();
149
- const bodyLeases = /* @__PURE__ */ new WeakMap();
150
- const pendingBodies = /* @__PURE__ */ new Set();
151
- const holdBody = (body, releases) => {
152
- if (releases.length === 0) return;
153
- const lease = {
154
- started: false,
155
- release: () => {
156
- if (!pendingBodies.delete(lease)) return;
157
- bodyLeases.delete(body);
302
+ //#endregion
303
+ //#region src/delivery/requests.ts
304
+ function admittedSerialization(state, delegate) {
305
+ return {
306
+ logs(data) {
307
+ const releases = state.logs.splice(0);
308
+ try {
309
+ const body = delegate.logs(data);
310
+ if (data.resourceLogs.some((r) => r.scopeLogs.some((s) => s.logRecords?.some((l) => terminalName(l.body?.stringValue ?? ""))))) state.terminalBodies.add(body);
311
+ holdBody(state, body, releases);
312
+ return body;
313
+ } catch (error) {
158
314
  for (const release of releases) release();
315
+ throw error;
159
316
  }
160
- };
161
- bodyLeases.set(body, lease);
162
- pendingBodies.add(lease);
163
- };
164
- const discardBody = (body) => bodyLeases.get(body)?.release();
165
- const drainLogs = () => {
166
- for (const release of logs.splice(0)) release();
167
- };
168
- const close = () => {
169
- isClosed = true;
170
- Deferred.doneUnsafe(closed, Effect.void);
171
- for (const lease of pendingBodies) if (!lease.started) lease.release();
172
- drainLogs();
173
- for (const release of spans.values()) release();
174
- spans.clear();
175
- if (ownsMetrics) budget.metricRegistry.clear();
317
+ },
318
+ traces(data) {
319
+ const releases = [];
320
+ for (const resource of data.resourceSpans) for (const scope of resource.scopeSpans) for (const span of scope.spans) {
321
+ const release = state.spans.get(span.spanId);
322
+ if (release !== void 0) releases.push(release);
323
+ state.spans.delete(span.spanId);
324
+ }
325
+ try {
326
+ const body = delegate.traces(data);
327
+ if (data.resourceSpans.some((r) => r.scopeSpans.some((s) => s.spans.some((span) => terminalName(span.name))))) state.terminalBodies.add(body);
328
+ holdBody(state, body, releases);
329
+ return body;
330
+ } catch (error) {
331
+ for (const release of releases) release();
332
+ throw error;
333
+ }
334
+ },
335
+ metrics(data) {
336
+ const body = delegate.metrics(data);
337
+ state.terminalBodies.add(body);
338
+ return body;
339
+ }
176
340
  };
341
+ }
342
+ /** A 429's Retry-After, bounded to two seconds; any other failure retries at once. */
343
+ const retryAfter = Schedule.spaced("100 millis").pipe(Schedule.addDelay(({ input }) => Effect.map(Clock.currentTimeMillis, (now) => {
344
+ if (!HttpClientError.isHttpClientError(input) || input.reason._tag !== "StatusCodeError" || input.reason.response.status !== 429) return Duration.zero;
345
+ const header = input.reason.response.headers["retry-after"];
346
+ const numeric = Number(header);
347
+ const delay = header === void 0 ? 2e3 : Number.isFinite(numeric) ? numeric * 1e3 : Date.parse(header) - now;
348
+ return Duration.millis(Number.isFinite(delay) ? Math.min(2e3, Math.max(0, delay)) : 2e3);
349
+ })));
350
+ function admittedTransport(state, client, enabled = () => true) {
351
+ const { budget } = state;
352
+ const retrying = HttpClient.transform(client, (response, request) => Effect.suspend(() => {
353
+ if (enabled()) return response;
354
+ budget.drop();
355
+ return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null)));
356
+ })).pipe(HttpClient.transformResponse((response) => Effect.tap(response, (result) => {
357
+ let received = 0;
358
+ return Stream.runForEach(result.stream, (chunk) => Effect.sync(() => {
359
+ received += chunk.byteLength;
360
+ if (received > 65536) throw new Error("Engineering response limit exceeded");
361
+ })).pipe(Effect.catchReason("HttpClientError", "EmptyBodyError", () => Effect.void));
362
+ }))).pipe(HttpClient.filterStatusOk, HttpClient.retryTransient({
363
+ times: 1,
364
+ schedule: retryAfter
365
+ }));
366
+ return HttpClient.transform(retrying, (response, request) => Effect.suspend(() => {
367
+ if (state.isClosed) {
368
+ discardBody(state, request.body);
369
+ return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null)));
370
+ }
371
+ const droppedResponse = () => HttpClientResponse.fromWeb(request, new Response(null, { status: 200 }));
372
+ if (request.body._tag === "Empty") {
373
+ discardBody(state, request.body);
374
+ return Effect.succeed(droppedResponse());
375
+ }
376
+ const bytes = request.body.contentLength;
377
+ const release = budget.admitRequest(bytes ?? Number.POSITIVE_INFINITY, state.terminalBodies.has(request.body));
378
+ if (release === void 0) {
379
+ discardBody(state, request.body);
380
+ return Effect.succeed(droppedResponse());
381
+ }
382
+ const lease = state.bodyLeases.get(request.body);
383
+ if (lease !== void 0) lease.started = true;
384
+ return response.pipe(Effect.asVoid, Effect.timeoutOption(CLI_ENGINEERING_LIMITS.requestTimeoutMs), Effect.raceFirst(Deferred.await(state.closed).pipe(Effect.andThen(Effect.interrupt))), Effect.tap((result) => Effect.sync(() => {
385
+ if (Option.isNone(result)) budget.drop();
386
+ })), Effect.catchCause(() => Effect.sync(() => {
387
+ budget.drop();
388
+ })), Effect.as(droppedResponse()), Effect.ensuring(Effect.sync(() => {
389
+ release();
390
+ discardBody(state, request.body);
391
+ })));
392
+ }));
393
+ }
394
+ //#endregion
395
+ //#region src/engineering-delivery.ts
396
+ /** Native exporters retain their serialization and scheduling; this owns admission only. */
397
+ function cliEngineeringDelivery(budget, ownsMetrics) {
398
+ const state = newDeliveryState(budget, ownsMetrics);
177
399
  return {
178
- close,
179
- discardBody,
400
+ close: () => closeDelivery(state),
401
+ discardBody: (body) => discardBody(state, body),
180
402
  metrics(run) {
181
403
  return Effect.suspend(() => {
182
- if (isClosed || metricsRunning) return Effect.void;
183
- metricsRunning = true;
404
+ if (state.isClosed || state.metricsRunning) return Effect.void;
405
+ state.metricsRunning = true;
184
406
  return run.pipe(Effect.ensuring(Effect.sync(() => {
185
- metricsRunning = false;
407
+ state.metricsRunning = false;
186
408
  })));
187
409
  });
188
410
  },
189
- logger(delegate) {
190
- return Logger.make((options) => {
191
- if (isClosed) return;
192
- let release;
193
- try {
194
- const message = text(Array.isArray(options.message) ? Object.getOwnPropertyDescriptor(options.message, "0")?.value : options.message);
195
- release = budget.admitRecord(terminalName(message));
196
- if (release === void 0) return;
197
- const context = Context.add(options.fiber.context, References.CurrentLogAnnotations, attributes(options.fiber.getRef(References.CurrentLogAnnotations)));
198
- const fiber = Object.create(options.fiber);
199
- Object.defineProperty(fiber, "getRef", { value: (ref) => Context.get(context, ref) });
200
- logs.push(release);
201
- delegate.log({
202
- ...options,
203
- message,
204
- cause: Cause.empty,
205
- fiber
206
- });
207
- } catch {
208
- if (release !== void 0) {
209
- const index = logs.indexOf(release);
210
- if (index !== -1) logs.splice(index, 1);
211
- release();
212
- }
213
- budget.drop();
214
- }
215
- });
216
- },
217
- tracer(delegate) {
218
- return Tracer.make({
219
- span(options) {
220
- const name = text(options.name);
221
- const release = isClosed ? void 0 : budget.admitRecord(terminalName(name));
222
- const spanOptions = {
223
- ...options,
224
- name,
225
- annotations: Context.empty(),
226
- links: []
227
- };
228
- const span = release === void 0 ? new Tracer.NativeSpan({
229
- ...spanOptions,
230
- sampled: false
231
- }) : delegate.span(spanOptions);
232
- if (release !== void 0) spans.set(span.spanId, release);
233
- const wrapped = Object.create(span);
234
- let fields = 0, events = 0, links = 0, attributeBytes = 0;
235
- let ended = false;
236
- Object.defineProperties(wrapped, {
237
- attribute: { value: (key, value) => {
238
- if (fields >= 32 && !span.attributes.has(key)) {
239
- budget.drop();
240
- return;
241
- }
242
- const safe = attributes({ [key]: value }, 1);
243
- if (!Object.hasOwn(safe, key)) return;
244
- const previous = span.attributes.has(key) ? new TextEncoder().encode(JSON.stringify([key, span.attributes.get(key)])).length : 0;
245
- const next = new TextEncoder().encode(JSON.stringify([key, safe[key]])).length;
246
- if (attributeBytes - previous + next > 2e3) {
247
- budget.drop();
248
- return;
249
- }
250
- attributeBytes += next - previous;
251
- if (!span.attributes.has(key)) fields += 1;
252
- span.attribute(key, safe[key]);
253
- } },
254
- event: { value: (eventName, time, attrs) => {
255
- if (events++ >= 2) {
256
- budget.drop();
257
- return;
258
- }
259
- span.event(text(eventName), time, attributes(attrs ?? {}, 4));
260
- } },
261
- addLinks: { value: (items) => {
262
- for (const item of items.slice(0, Math.max(0, 2 - links))) {
263
- if (!/^[a-f0-9]{32}$/u.test(item.span.traceId) || !/^[a-f0-9]{16}$/u.test(item.span.spanId)) {
264
- budget.drop();
265
- continue;
266
- }
267
- span.addLinks([{
268
- span: Tracer.externalSpan({
269
- traceId: item.span.traceId,
270
- spanId: item.span.spanId,
271
- sampled: item.span.sampled
272
- }),
273
- attributes: attributes(item.attributes, 4)
274
- }]);
275
- links += 1;
276
- }
277
- } },
278
- end: { value: (time, exit) => {
279
- if (ended) return;
280
- ended = true;
281
- span.end(time, Exit.isFailure(exit) ? Exit.fail("CLI operation failed") : Exit.void);
282
- } }
283
- });
284
- wrapped.addLinks(options.links ?? []);
285
- return wrapped;
286
- },
287
- ...delegate.context === void 0 ? {} : { context: delegate.context.bind(delegate) }
288
- });
289
- },
290
- serialization(delegate) {
291
- return {
292
- logs(data) {
293
- const releases = logs.splice(0);
294
- try {
295
- const body = delegate.logs(data);
296
- if (data.resourceLogs.some((r) => r.scopeLogs.some((s) => s.logRecords?.some((l) => terminalName(l.body?.stringValue ?? ""))))) terminalBodies.add(body);
297
- holdBody(body, releases);
298
- return body;
299
- } catch (error) {
300
- for (const release of releases) release();
301
- throw error;
302
- }
303
- },
304
- traces(data) {
305
- const releases = [];
306
- for (const resource of data.resourceSpans) for (const scope of resource.scopeSpans) for (const span of scope.spans) {
307
- const release = spans.get(span.spanId);
308
- if (release !== void 0) releases.push(release);
309
- spans.delete(span.spanId);
310
- }
311
- try {
312
- const body = delegate.traces(data);
313
- if (data.resourceSpans.some((r) => r.scopeSpans.some((s) => s.spans.some((span) => terminalName(span.name))))) terminalBodies.add(body);
314
- holdBody(body, releases);
315
- return body;
316
- } catch (error) {
317
- for (const release of releases) release();
318
- throw error;
319
- }
320
- },
321
- metrics(data) {
322
- const body = delegate.metrics(data);
323
- terminalBodies.add(body);
324
- return body;
325
- }
326
- };
327
- },
328
- transport(client, enabled = () => true) {
329
- const retrying = HttpClient.transform(client, (response, request) => Effect.suspend(() => {
330
- if (enabled()) return response;
331
- budget.drop();
332
- return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null)));
333
- })).pipe(HttpClient.transformResponse((response) => Effect.tap(response, (result) => {
334
- let received = 0;
335
- return Stream.runForEach(result.stream, (chunk) => Effect.sync(() => {
336
- received += chunk.byteLength;
337
- if (received > 65536) throw new Error("Engineering response limit exceeded");
338
- })).pipe(Effect.catchTag("HttpClientError", (error) => error.reason._tag === "EmptyBodyError" ? Effect.void : Effect.fail(error)));
339
- }))).pipe(HttpClient.filterStatusOk, HttpClient.retryTransient({
340
- times: 1,
341
- schedule: Schedule.spaced("100 millis").pipe(Schedule.addDelay(({ input }) => Effect.map(Clock.currentTimeMillis, (now) => {
342
- if (!HttpClientError.isHttpClientError(input) || input.reason._tag !== "StatusCodeError" || input.reason.response.status !== 429) return Duration.zero;
343
- const header = input.reason.response.headers["retry-after"];
344
- const numeric = Number(header);
345
- const delay = header === void 0 ? 2e3 : Number.isFinite(numeric) ? numeric * 1e3 : Date.parse(header) - now;
346
- return Duration.millis(Number.isFinite(delay) ? Math.min(2e3, Math.max(0, delay)) : 2e3);
347
- })))
348
- }));
349
- return HttpClient.transform(retrying, (response, request) => Effect.suspend(() => {
350
- if (isClosed) {
351
- discardBody(request.body);
352
- return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null)));
353
- }
354
- const droppedResponse = () => HttpClientResponse.fromWeb(request, new Response(null, { status: 200 }));
355
- if (request.body._tag === "Empty") {
356
- discardBody(request.body);
357
- return Effect.succeed(droppedResponse());
358
- }
359
- const bytes = request.body.contentLength;
360
- const release = budget.admitRequest(bytes ?? Number.POSITIVE_INFINITY, terminalBodies.has(request.body));
361
- if (release === void 0) {
362
- discardBody(request.body);
363
- return Effect.succeed(droppedResponse());
364
- }
365
- const lease = bodyLeases.get(request.body);
366
- if (lease !== void 0) lease.started = true;
367
- return response.pipe(Effect.asVoid, Effect.timeoutOption(CLI_ENGINEERING_LIMITS.requestTimeoutMs), Effect.raceFirst(Deferred.await(closed).pipe(Effect.andThen(Effect.interrupt))), Effect.tap((result) => Effect.sync(() => {
368
- if (Option.isNone(result)) budget.drop();
369
- })), Effect.catchCause(() => Effect.sync(() => {
370
- budget.drop();
371
- })), Effect.as(droppedResponse()), Effect.ensuring(Effect.sync(() => {
372
- release();
373
- discardBody(request.body);
374
- })));
375
- }));
376
- }
411
+ logger: (delegate) => admittedLogger(state, delegate),
412
+ tracer: (delegate) => admittedTracer(state, delegate),
413
+ serialization: (delegate) => admittedSerialization(state, delegate),
414
+ transport: (client, enabled) => admittedTransport(state, client, enabled)
377
415
  };
378
416
  }
379
417
  //#endregion
380
- //#region src/engineering.ts
418
+ //#region src/engineering/config.ts
381
419
  const ENGINEERING_CAPXUL_ENVS = [
382
420
  "development",
383
421
  "staging",
@@ -385,7 +423,10 @@ const ENGINEERING_CAPXUL_ENVS = [
385
423
  "local"
386
424
  ];
387
425
  const traceHeaderFilter = (_name) => true;
388
- const credentialHeaderPattern = Object.assign(/./u, { test: (name) => isCredentialField(name, "header") });
426
+ const credentialHeaderPattern = Object.assign(/./u, { exec: (name) => isCredentialField(name, "header") ? Object.assign([name], {
427
+ index: 0,
428
+ input: name
429
+ }) : null });
389
430
  const postHogOtlpEndpoints = (host) => {
390
431
  const base = host.replace(/\/+$/, "");
391
432
  return {
@@ -424,6 +465,8 @@ const validateEngineeringTelemetryConfig = (config) => {
424
465
  headers: Object.freeze({ authorization: headerEntries[0][1] })
425
466
  };
426
467
  };
468
+ //#endregion
469
+ //#region src/engineering/tracer.ts
427
470
  const failureText = (failure, key, limit, source) => {
428
471
  if (failure === null || typeof failure !== "object") return void 0;
429
472
  try {
@@ -457,7 +500,7 @@ const browserExportClient = (client) => HttpClient.transform(client, (response,
457
500
  if (request.body._tag === "Stream" || request.body._tag === "FormData" || bytes === void 0 || !Number.isSafeInteger(bytes) || bytes < 0 || unfinishedBrowserExportBytes + bytes > BROWSER_KEEPALIVE_BYTES) return;
458
501
  unfinishedBrowserExportBytes += bytes;
459
502
  return bytes;
460
- }), (bytes) => response.pipe(Effect.provideService(FetchHttpClient.RequestInit, { keepalive: bytes !== void 0 }), Effect.tap((result) => bytes === void 0 ? Effect.void : Stream.runDrain(result.stream).pipe(Effect.catch((error) => error.reason._tag === "EmptyBodyError" ? Effect.void : Effect.fail(error))))), (bytes) => Effect.sync(() => {
503
+ }), (bytes) => response.pipe(Effect.provideService(FetchHttpClient.RequestInit, { keepalive: bytes !== void 0 }), Effect.tap((result) => bytes === void 0 ? Effect.void : Stream.runDrain(result.stream).pipe(Effect.catchIf((error) => error.reason._tag === "EmptyBodyError", () => Effect.void)))), (bytes) => Effect.sync(() => {
461
504
  if (bytes !== void 0) unfinishedBrowserExportBytes -= bytes;
462
505
  })));
463
506
  const spanUpstream = (span) => {
@@ -477,11 +520,15 @@ function redactHttpPath(path, fullUrl, source) {
477
520
  const prefix = path.startsWith("/") ? origin : "";
478
521
  return redactUrlSecrets(`${prefix}${path}`, source).slice(prefix.length, prefix.length + 2048);
479
522
  }
523
+ /** A span whose prototype is `span`: it answers every Span member until an own property overrides it. */
524
+ function inheritSpan(span) {
525
+ return Object.create(span);
526
+ }
480
527
  /** Preserve domain exits while preventing the OTLP serializer from seeing raw causes. */
481
528
  const makeLeakSafeEngineeringTracer = (delegate, enabled = () => true) => Tracer.make({
482
529
  span(options) {
483
530
  const span = enabled() ? delegate.span(options) : new Tracer.NativeSpan(options);
484
- const wrapped = Object.create(span);
531
+ const wrapped = inheritSpan(span);
485
532
  Object.defineProperty(wrapped, "attribute", { value: (name, value) => {
486
533
  const source = spanUpstream(span);
487
534
  const header = /^http\.(?:request|response)\.header\.(.+)$/u.exec(name)?.[1];
@@ -521,6 +568,8 @@ const makeLeakSafeEngineeringTracer = (delegate, enabled = () => true) => Tracer
521
568
  },
522
569
  ...delegate.context === void 0 ? {} : { context: delegate.context.bind(delegate) }
523
570
  });
571
+ //#endregion
572
+ //#region src/engineering/minimal.ts
524
573
  const MINIMAL_LOG_NAMES = /* @__PURE__ */ new Set([
525
574
  "capxul_sdk_operation",
526
575
  "identity.transition",
@@ -670,6 +719,8 @@ function minimalAttributes(attributes, metric = false) {
670
719
  }] : [];
671
720
  });
672
721
  }
722
+ //#endregion
723
+ //#region src/engineering/metric-points.ts
673
724
  const finiteProtocolNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
674
725
  const protocolCount = (value, maximum = Number.MAX_SAFE_INTEGER) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && value <= maximum ? value : void 0;
675
726
  const protocolFlags = (value) => protocolCount(Object.getOwnPropertyDescriptor(value, "flags")?.value, 4294967295);
@@ -719,6 +770,8 @@ function minimalHistogramPoints(points) {
719
770
  }];
720
771
  });
721
772
  }
773
+ //#endregion
774
+ //#region src/engineering/serialization.ts
722
775
  function minimalSerialization(config) {
723
776
  const service = config.serviceName ?? "capxul-sdk";
724
777
  const resource = { attributes: Object.entries({
@@ -809,7 +862,7 @@ function minimalSerialization(config) {
809
862
  const gauge = metric.gauge === void 0 ? void 0 : { dataPoints: minimalNumberPoints(metric.gauge.dataPoints) };
810
863
  const sum = metric.sum === void 0 ? void 0 : {
811
864
  aggregationTemporality: protocolCount(metric.sum.aggregationTemporality, 2) ?? 0,
812
- isMonotonic: metric.sum.isMonotonic === true,
865
+ isMonotonic: metric.sum.isMonotonic,
813
866
  dataPoints: minimalNumberPoints(metric.sum.dataPoints)
814
867
  };
815
868
  const histogram = metric.histogram === void 0 ? void 0 : {
@@ -839,6 +892,8 @@ function minimalSerialization(config) {
839
892
  }
840
893
  };
841
894
  }
895
+ //#endregion
896
+ //#region src/engineering/logs.ts
842
897
  /** Attempt to drain all engineering signals; completion does not prove ingestion. */
843
898
  const flushEngineeringTelemetry = Effect.flatMap(OtlpExporter.Flusher, (flusher) => flusher.flush);
844
899
  var EngineeringLog = class extends Context.Service()("capxul/EngineeringLog") {};
@@ -847,6 +902,8 @@ const logEngineeringInfo = (message, attributes) => {
847
902
  const record = Effect.logInfo(message).pipe(Effect.annotateLogs(attributes));
848
903
  return Effect.flatMap(LogLevel.isEnabled("Info"), (visible) => visible ? record : Effect.flatMap(Effect.serviceOption(EngineeringLog), (logger) => logger._tag === "None" ? Effect.void : record.pipe(Effect.provideService(Logger.CurrentLoggers, /* @__PURE__ */ new Set([logger.value])), Effect.provideService(References.MinimumLogLevel, "All"))));
849
904
  };
905
+ //#endregion
906
+ //#region src/engineering.ts
850
907
  /** Shared browser/server OTLP layer. OtlpLogger merges with incumbent loggers once. */
851
908
  const makeEngineeringTelemetryLayer = (config) => {
852
909
  const validated = validateEngineeringTelemetryConfig(config);