brass-runtime 1.15.0 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +409 -137
- package/dist/agent/cli/main.cjs +40 -35
- package/dist/agent/cli/main.js +9 -4
- package/dist/agent/cli/main.mjs +9 -4
- package/dist/agent/index.cjs +8 -4
- package/dist/agent/index.d.ts +1 -1
- package/dist/agent/index.js +7 -3
- package/dist/agent/index.mjs +7 -3
- package/dist/{chunk-PPUXIH5R.js → chunk-2WC63LJK.mjs} +11 -7
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/chunk-45F7OKGT.cjs +104 -0
- package/dist/chunk-5YOQOXEQ.cjs +2491 -0
- package/dist/{chunk-STVLQ3XD.cjs → chunk-7HUOJA4W.cjs} +78 -74
- package/dist/{chunk-BMH5AV44.js → chunk-7LVI2GIN.js} +251 -370
- package/dist/chunk-7TL2LHQJ.js +2491 -0
- package/dist/chunk-7V4KY4RL.mjs +104 -0
- package/dist/chunk-7XOPAB5Q.js +2143 -0
- package/dist/chunk-CCKHV5BT.mjs +193 -0
- package/dist/{chunk-AR22SXML.js → chunk-CY33PGEX.mjs} +488 -421
- package/dist/chunk-DJQ7OMMB.cjs +144 -0
- package/dist/chunk-F5EUMJL7.mjs +2143 -0
- package/dist/chunk-FM4W4QPL.js +193 -0
- package/dist/{chunk-TO7IKXYT.js → chunk-G3XGCZDQ.js} +1 -1
- package/dist/{chunk-BDF4AMWX.mjs → chunk-G6IQOE4P.mjs} +251 -370
- package/dist/chunk-GOV47PPB.mjs +552 -0
- package/dist/chunk-H55LI6WY.js +93 -0
- package/dist/chunk-IJT6RRQ5.cjs +93 -0
- package/dist/{chunk-ELOOF35R.mjs → chunk-J3H54ZRV.mjs} +1 -1
- package/dist/chunk-JF4XXPZ5.cjs +552 -0
- package/dist/chunk-JNFRRJYH.cjs +2143 -0
- package/dist/chunk-JX3LZQJH.cjs +354 -0
- package/dist/chunk-K2T3DV26.mjs +93 -0
- package/dist/chunk-KCPT2D6G.js +552 -0
- package/dist/chunk-MWXMNYJS.cjs +1110 -0
- package/dist/{chunk-VEZNF5GZ.cjs → chunk-N6VHMOWB.cjs} +130 -126
- package/dist/{chunk-3QMOKAS5.js → chunk-NC5SDRYE.js} +9 -5
- package/dist/chunk-NOYZIMUJ.mjs +144 -0
- package/dist/{chunk-R3R2FVLG.cjs → chunk-NYL4D7SK.cjs} +5 -5
- package/dist/chunk-OBGZSXTJ.cjs +10 -0
- package/dist/{chunk-4NHES7VK.mjs → chunk-OOGJ73B6.js} +11 -7
- package/dist/chunk-PNVFW245.js +144 -0
- package/dist/chunk-PRWCB3QL.mjs +2491 -0
- package/dist/{chunk-JFPU5GQI.mjs → chunk-QY5FKYEQ.js} +488 -421
- package/dist/chunk-ROJC3NBJ.js +104 -0
- package/dist/chunk-SPUEME2B.cjs +343 -0
- package/dist/chunk-TDVMADDN.js +343 -0
- package/dist/chunk-TVN5I4U6.cjs +193 -0
- package/dist/chunk-U5KWK3PX.mjs +343 -0
- package/dist/chunk-VFIUZG7J.mjs +354 -0
- package/dist/{chunk-TGIFUAK4.cjs → chunk-WQ5QNU5R.cjs} +459 -578
- package/dist/chunk-XDZOO4L5.js +354 -0
- package/dist/chunk-Y6FXYEAI.mjs +10 -0
- package/dist/{chunk-K6M7MDZ4.mjs → chunk-ZGLD4TVZ.mjs} +9 -5
- package/dist/client-CtFmoDvM.d.ts +645 -0
- package/dist/core/index.cjs +72 -4
- package/dist/core/index.d.ts +92 -198
- package/dist/core/index.js +106 -38
- package/dist/core/index.mjs +106 -38
- package/dist/{effect-CMOQKX8y.d.ts → effect-CGNl5Rqp.d.ts} +107 -1
- package/dist/effectRunner-3ZHAD3LE.cjs +8 -0
- package/dist/effectRunner-A4CHJXJI.js +8 -0
- package/dist/effectRunner-OPUF6QRN.mjs +8 -0
- package/dist/http/index.cjs +2189 -1271
- package/dist/http/index.d.ts +830 -270
- package/dist/http/index.js +2008 -1090
- package/dist/http/index.mjs +2008 -1090
- package/dist/http/testing.cjs +159 -0
- package/dist/http/testing.d.ts +42 -0
- package/dist/http/testing.js +159 -0
- package/dist/http/testing.mjs +159 -0
- package/dist/index.cjs +246 -178
- package/dist/index.d.ts +9 -35
- package/dist/index.js +120 -52
- package/dist/index.mjs +120 -52
- package/dist/observability/index.cjs +677 -0
- package/dist/observability/index.d.ts +79 -0
- package/dist/observability/index.js +677 -0
- package/dist/observability/index.mjs +677 -0
- package/dist/schedule-Fque9Abz.d.ts +70 -0
- package/dist/schema/index.cjs +25 -0
- package/dist/schema/index.d.ts +177 -0
- package/dist/schema/index.js +25 -0
- package/dist/schema/index.mjs +25 -0
- package/dist/server-C8hDXA74.d.ts +674 -0
- package/dist/{stream-FQm9h4Mg.d.ts → stream-dvSs0QS5.d.ts} +1 -1
- package/dist/tracer-B5tRH9H7.d.ts +230 -0
- package/dist/tracing-Dt9S_6V8.d.ts +148 -0
- package/package.json +27 -1
- package/dist/chunk-BDYEENHT.js +0 -224
- package/dist/chunk-MS34J5LY.cjs +0 -224
- package/dist/chunk-UMAZLXAB.mjs +0 -224
- package/dist/chunk-XPZNXSVN.cjs +0 -1043
- package/dist/tracing-DNT9jEbr.d.ts +0 -106
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { A as Async, R as RuntimeHooks, a as RingBufferOptions, b as RuntimeEvent, c as RuntimeEmitContext, d as RuntimeEventRecord, e as RuntimeSpanLink, T as TraceContext, B as Baggage } from './effect-CGNl5Rqp.js';
|
|
2
|
+
|
|
3
|
+
type CircuitBreakerState = "closed" | "open" | "half-open";
|
|
4
|
+
type CircuitBreakerError = {
|
|
5
|
+
readonly _tag: "CircuitBreakerOpen";
|
|
6
|
+
readonly openSince: number;
|
|
7
|
+
readonly failures: number;
|
|
8
|
+
};
|
|
9
|
+
type CircuitBreakerConfig = {
|
|
10
|
+
/** Number of consecutive failures before opening the circuit. Default: 5 */
|
|
11
|
+
readonly failureThreshold?: number;
|
|
12
|
+
/** Time in ms to wait before transitioning from OPEN to HALF_OPEN. Default: 30000 */
|
|
13
|
+
readonly resetTimeoutMs?: number;
|
|
14
|
+
/** Number of successes in HALF_OPEN needed to close the circuit. Default: 1 */
|
|
15
|
+
readonly successThreshold?: number;
|
|
16
|
+
/** Custom predicate: should this error count as a failure? Default: all errors count. */
|
|
17
|
+
readonly isFailure?: (error: unknown) => boolean;
|
|
18
|
+
/** Called on state transitions (for observability). */
|
|
19
|
+
readonly onStateChange?: (from: CircuitBreakerState, to: CircuitBreakerState) => void;
|
|
20
|
+
};
|
|
21
|
+
type CircuitBreakerStats = {
|
|
22
|
+
readonly state: CircuitBreakerState;
|
|
23
|
+
readonly failures: number;
|
|
24
|
+
readonly successes: number;
|
|
25
|
+
readonly totalRequests: number;
|
|
26
|
+
readonly totalFailures: number;
|
|
27
|
+
readonly totalSuccesses: number;
|
|
28
|
+
readonly totalRejected: number;
|
|
29
|
+
readonly lastFailureTime: number | null;
|
|
30
|
+
readonly lastSuccessTime: number | null;
|
|
31
|
+
};
|
|
32
|
+
type CircuitBreaker = {
|
|
33
|
+
/** Current state of the circuit breaker. */
|
|
34
|
+
readonly state: () => CircuitBreakerState;
|
|
35
|
+
/** Run an effect through the circuit breaker. */
|
|
36
|
+
readonly protect: <R, E, A>(effect: Async<R, E, A>) => Async<R, E | CircuitBreakerError, A>;
|
|
37
|
+
/** Get current stats. */
|
|
38
|
+
readonly stats: () => CircuitBreakerStats;
|
|
39
|
+
/** Manually reset to closed state. */
|
|
40
|
+
readonly reset: () => void;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Creates a circuit breaker.
|
|
44
|
+
*
|
|
45
|
+
* ```ts
|
|
46
|
+
* const breaker = makeCircuitBreaker({ failureThreshold: 3, resetTimeoutMs: 10000 });
|
|
47
|
+
*
|
|
48
|
+
* // Protect an effect:
|
|
49
|
+
* const result = await run(breaker.protect(callExternalService()));
|
|
50
|
+
* // Throws CircuitBreakerOpen if circuit is open
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
declare function makeCircuitBreaker(config?: CircuitBreakerConfig): CircuitBreaker;
|
|
54
|
+
|
|
55
|
+
type MetricType = "counter" | "gauge" | "histogram";
|
|
56
|
+
type MetricValue = {
|
|
57
|
+
readonly name: string;
|
|
58
|
+
readonly type: MetricType;
|
|
59
|
+
readonly value: number;
|
|
60
|
+
readonly labels: Record<string, string>;
|
|
61
|
+
readonly timestamp: number;
|
|
62
|
+
};
|
|
63
|
+
type HistogramBuckets = {
|
|
64
|
+
readonly boundaries: readonly number[];
|
|
65
|
+
counts: number[];
|
|
66
|
+
sum: number;
|
|
67
|
+
count: number;
|
|
68
|
+
min: number;
|
|
69
|
+
max: number;
|
|
70
|
+
exemplars?: Array<MetricExemplar | undefined>;
|
|
71
|
+
};
|
|
72
|
+
type MetricExemplar = {
|
|
73
|
+
readonly value: number;
|
|
74
|
+
readonly timestamp: number;
|
|
75
|
+
readonly traceId?: string;
|
|
76
|
+
readonly spanId?: string;
|
|
77
|
+
readonly labels?: Record<string, string>;
|
|
78
|
+
};
|
|
79
|
+
type MetricsRegistry = {
|
|
80
|
+
/** Create or get a counter. */
|
|
81
|
+
readonly counter: (name: string, labels?: Record<string, string>) => Counter;
|
|
82
|
+
/** Create or get a gauge. */
|
|
83
|
+
readonly gauge: (name: string, labels?: Record<string, string>) => Gauge;
|
|
84
|
+
/** Create or get a histogram. */
|
|
85
|
+
readonly histogram: (name: string, boundaries?: number[], labels?: Record<string, string>) => Histogram;
|
|
86
|
+
/** Get all current metric values. */
|
|
87
|
+
readonly snapshot: () => MetricSnapshot;
|
|
88
|
+
/** Reset all metrics. */
|
|
89
|
+
readonly reset: () => void;
|
|
90
|
+
};
|
|
91
|
+
type Counter = {
|
|
92
|
+
readonly increment: (n?: number) => void;
|
|
93
|
+
readonly value: () => number;
|
|
94
|
+
};
|
|
95
|
+
type Gauge = {
|
|
96
|
+
readonly set: (value: number) => void;
|
|
97
|
+
readonly increment: (n?: number) => void;
|
|
98
|
+
readonly decrement: (n?: number) => void;
|
|
99
|
+
readonly value: () => number;
|
|
100
|
+
};
|
|
101
|
+
type Histogram = {
|
|
102
|
+
readonly observe: (value: number, exemplar?: Omit<MetricExemplar, "value"> | MetricExemplar) => void;
|
|
103
|
+
readonly buckets: () => HistogramBuckets;
|
|
104
|
+
readonly percentile: (p: number) => number;
|
|
105
|
+
};
|
|
106
|
+
type MetricSnapshot = {
|
|
107
|
+
readonly counters: Array<{
|
|
108
|
+
name: string;
|
|
109
|
+
labels: Record<string, string>;
|
|
110
|
+
value: number;
|
|
111
|
+
}>;
|
|
112
|
+
readonly gauges: Array<{
|
|
113
|
+
name: string;
|
|
114
|
+
labels: Record<string, string>;
|
|
115
|
+
value: number;
|
|
116
|
+
}>;
|
|
117
|
+
readonly histograms: Array<{
|
|
118
|
+
name: string;
|
|
119
|
+
labels: Record<string, string>;
|
|
120
|
+
buckets: HistogramBuckets;
|
|
121
|
+
}>;
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Creates a metrics registry.
|
|
125
|
+
*
|
|
126
|
+
* ```ts
|
|
127
|
+
* const metrics = makeMetrics();
|
|
128
|
+
*
|
|
129
|
+
* const requestCount = metrics.counter("http_requests_total", { method: "GET" });
|
|
130
|
+
* requestCount.increment();
|
|
131
|
+
*
|
|
132
|
+
* const latency = metrics.histogram("http_request_duration_ms");
|
|
133
|
+
* latency.observe(42.5);
|
|
134
|
+
*
|
|
135
|
+
* const activeConns = metrics.gauge("active_connections");
|
|
136
|
+
* activeConns.set(10);
|
|
137
|
+
*
|
|
138
|
+
* console.log(metrics.snapshot());
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
declare function makeMetrics(): MetricsRegistry;
|
|
142
|
+
|
|
143
|
+
type EventHandler = (ev: RuntimeEventRecord) => void;
|
|
144
|
+
declare function runtimeHooksToEventHandler(hooks: RuntimeHooks): EventHandler;
|
|
145
|
+
type EventBusOptions = RingBufferOptions;
|
|
146
|
+
declare class EventBus implements RuntimeHooks {
|
|
147
|
+
private readonly options;
|
|
148
|
+
private seq;
|
|
149
|
+
private subs;
|
|
150
|
+
private flushScheduled;
|
|
151
|
+
private readonly boundFlush;
|
|
152
|
+
constructor(options?: EventBusOptions);
|
|
153
|
+
emit(ev: RuntimeEvent, ctx: RuntimeEmitContext): void;
|
|
154
|
+
subscribe(handler: EventHandler, perSubscriberCapacity?: number): () => void;
|
|
155
|
+
subscribeHooks(hooks: RuntimeHooks, perSubscriberCapacity?: number): () => void;
|
|
156
|
+
flush(budget?: number): void;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
type RuntimeSpanEvent = {
|
|
160
|
+
wallTs: number;
|
|
161
|
+
name: string;
|
|
162
|
+
attrs?: unknown;
|
|
163
|
+
};
|
|
164
|
+
type RuntimeSpan = {
|
|
165
|
+
traceId: string;
|
|
166
|
+
spanId: string;
|
|
167
|
+
parentSpanId?: string;
|
|
168
|
+
traceState?: string;
|
|
169
|
+
name: string;
|
|
170
|
+
startWallTs: number;
|
|
171
|
+
endWallTs?: number;
|
|
172
|
+
attrs: Record<string, unknown>;
|
|
173
|
+
links: RuntimeSpanLink[];
|
|
174
|
+
events: RuntimeSpanEvent[];
|
|
175
|
+
};
|
|
176
|
+
type InMemoryTracerOptions = {
|
|
177
|
+
readonly sanitizeAttributes?: (attrs: Record<string, unknown>) => Record<string, unknown>;
|
|
178
|
+
readonly sanitizeError?: (error: unknown) => unknown;
|
|
179
|
+
readonly maxFinishedSpans?: number;
|
|
180
|
+
readonly maxSpanAgeMs?: number;
|
|
181
|
+
readonly clock?: () => number;
|
|
182
|
+
};
|
|
183
|
+
type InMemoryTracerStats = {
|
|
184
|
+
readonly storedSpans: number;
|
|
185
|
+
readonly finishedSpans: number;
|
|
186
|
+
readonly prunedFinishedSpans: number;
|
|
187
|
+
};
|
|
188
|
+
declare class InMemoryTracer implements RuntimeHooks {
|
|
189
|
+
private readonly options;
|
|
190
|
+
spans: Map<string, RuntimeSpan>;
|
|
191
|
+
private prunedFinishedSpans;
|
|
192
|
+
constructor(options?: InMemoryTracerOptions);
|
|
193
|
+
emit(ev: RuntimeEvent, ctx: RuntimeEmitContext): void;
|
|
194
|
+
exportFinished(): RuntimeSpan[];
|
|
195
|
+
pruneFinished(spanIds?: Iterable<string>): number;
|
|
196
|
+
stats(): InMemoryTracerStats;
|
|
197
|
+
private attrs;
|
|
198
|
+
private error;
|
|
199
|
+
private now;
|
|
200
|
+
private pruneExpiredFinished;
|
|
201
|
+
private pruneFinishedOverLimit;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
type TraceSamplingInput = {
|
|
205
|
+
readonly traceId: string;
|
|
206
|
+
readonly spanName?: string;
|
|
207
|
+
readonly parentSampled?: boolean;
|
|
208
|
+
readonly attributes?: Record<string, unknown>;
|
|
209
|
+
};
|
|
210
|
+
type TraceSampler = ((input: TraceSamplingInput) => boolean) | {
|
|
211
|
+
shouldSample(input: TraceSamplingInput): boolean;
|
|
212
|
+
};
|
|
213
|
+
interface Tracer {
|
|
214
|
+
newTraceId(): string;
|
|
215
|
+
newSpanId(): string;
|
|
216
|
+
}
|
|
217
|
+
declare const defaultTracer: Tracer;
|
|
218
|
+
type BrassEnv = {
|
|
219
|
+
brass?: {
|
|
220
|
+
tracer?: Tracer;
|
|
221
|
+
traceSeed?: TraceContext;
|
|
222
|
+
baggage?: Baggage;
|
|
223
|
+
sampler?: TraceSampler;
|
|
224
|
+
respectRemoteSampled?: boolean;
|
|
225
|
+
forceSampleOnError?: boolean;
|
|
226
|
+
childName?: (parentName?: string) => string | undefined;
|
|
227
|
+
};
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
export { type BrassEnv as B, type CircuitBreakerStats as C, EventBus as E, type Gauge as G, type Histogram as H, InMemoryTracer as I, type MetricsRegistry as M, type RuntimeSpan as R, type TraceSampler as T, type MetricExemplar as a, type MetricSnapshot as b, type TraceSamplingInput as c, type Tracer as d, type InMemoryTracerOptions as e, type CircuitBreaker as f, type CircuitBreakerConfig as g, type CircuitBreakerError as h, type CircuitBreakerState as i, type Counter as j, type EventBusOptions as k, type EventHandler as l, type HistogramBuckets as m, type InMemoryTracerStats as n, type MetricType as o, type MetricValue as p, type RuntimeSpanEvent as q, defaultTracer as r, makeCircuitBreaker as s, makeMetrics as t, runtimeHooksToEventHandler as u };
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { A as Async, E as Exit } from './effect-CGNl5Rqp.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Acquires a resource, uses it, and guarantees release regardless of outcome.
|
|
5
|
+
*
|
|
6
|
+
* - `acquire` runs uninterruptibly (once started, it completes)
|
|
7
|
+
* - `use` runs with the acquired resource
|
|
8
|
+
* - `release` runs after `use` completes (success, failure, or interruption)
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* const result = bracket(
|
|
12
|
+
* openConnection(), // acquire
|
|
13
|
+
* (conn) => queryDatabase(conn), // use
|
|
14
|
+
* (conn, exit) => conn.close() // release (always runs)
|
|
15
|
+
* );
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
declare function bracket<R, E, A, B>(acquire: Async<R, E, A>, use: (resource: A) => Async<R, E, B>, release: (resource: A, exit: Exit<E, B>) => Async<R, any, void>): Async<R, E, B>;
|
|
19
|
+
/**
|
|
20
|
+
* Runs `effect` and then runs `finalizer` regardless of the outcome.
|
|
21
|
+
* The finalizer receives the exit value for inspection.
|
|
22
|
+
*
|
|
23
|
+
* ```ts
|
|
24
|
+
* const result = ensuring(
|
|
25
|
+
* doWork(),
|
|
26
|
+
* (exit) => logCompletion(exit)
|
|
27
|
+
* );
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
declare function ensuring<R, E, A>(effect: Async<R, E, A>, finalizer: (exit: Exit<E, A>) => Async<R, any, void>): Async<R, E, A>;
|
|
31
|
+
declare function resource<R, E, A>(acquire: Async<R, E, A>, release: (resource: A, exit: Exit<any, any>) => Async<R, any, void>): Resource<R, E, A>;
|
|
32
|
+
declare const makeResource: typeof resource;
|
|
33
|
+
declare function resourceSucceed<A>(value: A): Resource<unknown, never, A>;
|
|
34
|
+
declare function resourceFromManaged<R, E, A>(m: Managed<R, E, A>): Resource<R, E, A>;
|
|
35
|
+
declare function useResource<R, E, A, R2, E2, B>(res: Resource<R, E, A>, body: (resource: A) => Async<R2, E2, B>): Async<R & R2, E | E2, B>;
|
|
36
|
+
declare function resourceAll<R, E, Resources extends readonly any[]>(resources: {
|
|
37
|
+
[K in keyof Resources]: Resource<R, E, Resources[K]>;
|
|
38
|
+
}): Resource<R, E, Resources>;
|
|
39
|
+
/**
|
|
40
|
+
* A Resource describes a scoped value. It is acquired for each `use`, released
|
|
41
|
+
* in LIFO order, and can be composed with `map`, `flatMap`, and `zip`.
|
|
42
|
+
*/
|
|
43
|
+
type Resource<R, E, A> = {
|
|
44
|
+
readonly _tag: "Resource";
|
|
45
|
+
readonly use: <R2, E2, B>(body: (resource: A) => Async<R2, E2, B>) => Async<R & R2, E | E2, B>;
|
|
46
|
+
readonly map: <B>(f: (resource: A) => B) => Resource<R, E, B>;
|
|
47
|
+
readonly flatMap: <R2, E2, B>(f: (resource: A) => Resource<R2, E2, B>) => Resource<R & R2, E | E2, B>;
|
|
48
|
+
readonly zip: <R2, E2, B>(that: Resource<R2, E2, B>) => Resource<R & R2, E | E2, [A, B]>;
|
|
49
|
+
};
|
|
50
|
+
declare const Resource: Readonly<{
|
|
51
|
+
make: typeof resource;
|
|
52
|
+
succeed: typeof resourceSucceed;
|
|
53
|
+
fromManaged: typeof resourceFromManaged;
|
|
54
|
+
all: typeof resourceAll;
|
|
55
|
+
use: typeof useResource;
|
|
56
|
+
}>;
|
|
57
|
+
/**
|
|
58
|
+
* A Managed resource describes how to acquire and release a resource.
|
|
59
|
+
* It can be used multiple times — each `use` call acquires a fresh instance.
|
|
60
|
+
*
|
|
61
|
+
* ```ts
|
|
62
|
+
* const dbPool = managed(
|
|
63
|
+
* createPool({ max: 10 }),
|
|
64
|
+
* (pool) => pool.close()
|
|
65
|
+
* );
|
|
66
|
+
*
|
|
67
|
+
* // Use it:
|
|
68
|
+
* const result = useManaged(dbPool, (pool) => pool.query("SELECT 1"));
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
type Managed<R, E, A> = {
|
|
72
|
+
readonly _tag: "Managed";
|
|
73
|
+
readonly acquire: Async<R, E, A>;
|
|
74
|
+
readonly release: (resource: A, exit: Exit<any, any>) => Async<R, any, void>;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Creates a Managed resource descriptor.
|
|
78
|
+
*/
|
|
79
|
+
declare function managed<R, E, A>(acquire: Async<R, E, A>, release: (resource: A, exit?: Exit<any, any>) => Async<R, any, void>): Managed<R, E, A>;
|
|
80
|
+
/**
|
|
81
|
+
* Uses a Managed resource: acquires, runs the body, and releases.
|
|
82
|
+
*/
|
|
83
|
+
declare function useManaged<R, E, A, B>(m: Managed<R, E, A>, body: (resource: A) => Async<R, E, B>): Async<R, E, B>;
|
|
84
|
+
/**
|
|
85
|
+
* Combines multiple Managed resources. All are acquired in order,
|
|
86
|
+
* and released in reverse order (LIFO).
|
|
87
|
+
*
|
|
88
|
+
* ```ts
|
|
89
|
+
* const resources = managedAll([dbPool, cacheConn, fileHandle]);
|
|
90
|
+
* const result = useManaged(resources, ([db, cache, file]) => ...);
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
declare function managedAll<R, E, Resources extends readonly any[]>(manageds: {
|
|
94
|
+
[K in keyof Resources]: Managed<R, E, Resources[K]>;
|
|
95
|
+
}): Managed<R, E, Resources>;
|
|
96
|
+
|
|
97
|
+
type SpanContext = {
|
|
98
|
+
readonly traceId: string;
|
|
99
|
+
readonly spanId: string;
|
|
100
|
+
readonly parentSpanId?: string;
|
|
101
|
+
};
|
|
102
|
+
type SpanStatus = "ok" | "error" | "unset";
|
|
103
|
+
type Span = {
|
|
104
|
+
readonly name: string;
|
|
105
|
+
readonly context: SpanContext;
|
|
106
|
+
readonly startTime: number;
|
|
107
|
+
endTime?: number;
|
|
108
|
+
status: SpanStatus;
|
|
109
|
+
readonly attributes: Record<string, string | number | boolean>;
|
|
110
|
+
readonly events: SpanEvent[];
|
|
111
|
+
};
|
|
112
|
+
type SpanEvent = {
|
|
113
|
+
readonly name: string;
|
|
114
|
+
readonly time: number;
|
|
115
|
+
readonly attributes?: Record<string, string | number | boolean>;
|
|
116
|
+
};
|
|
117
|
+
type TracerConfig = {
|
|
118
|
+
/** Service name for span metadata. */
|
|
119
|
+
readonly serviceName: string;
|
|
120
|
+
/** Called when a span ends (for export). */
|
|
121
|
+
readonly onSpanEnd?: (span: Span) => void;
|
|
122
|
+
/** Sampling rate (0.0 to 1.0). Default: 1.0 (sample everything). */
|
|
123
|
+
readonly sampleRate?: number;
|
|
124
|
+
};
|
|
125
|
+
type Tracer = {
|
|
126
|
+
/** Wrap an effect in a span. */
|
|
127
|
+
readonly span: <R, E, A>(name: string, effect: Async<R, E, A>, attributes?: Record<string, string | number | boolean>) => Async<R, E, A>;
|
|
128
|
+
/** Get all completed spans (for testing). */
|
|
129
|
+
readonly spans: () => readonly Span[];
|
|
130
|
+
/** Clear collected spans. */
|
|
131
|
+
readonly clear: () => void;
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* Creates a tracer that wraps effects in spans.
|
|
135
|
+
*
|
|
136
|
+
* ```ts
|
|
137
|
+
* const tracer = makeTracer({ serviceName: "my-app" });
|
|
138
|
+
*
|
|
139
|
+
* const result = await run(
|
|
140
|
+
* tracer.span("fetchUser", fetchUser(id), { userId: id })
|
|
141
|
+
* );
|
|
142
|
+
*
|
|
143
|
+
* console.log(tracer.spans()); // all completed spans
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
declare function makeTracer(config: TracerConfig): Tracer;
|
|
147
|
+
|
|
148
|
+
export { type Managed as M, Resource as R, type Span as S, type Tracer as T, type SpanContext as a, type SpanEvent as b, type SpanStatus as c, type TracerConfig as d, bracket as e, ensuring as f, makeTracer as g, managed as h, managedAll as i, resourceAll as j, resourceFromManaged as k, resourceSucceed as l, makeResource as m, useResource as n, resource as r, useManaged as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brass-runtime",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.0",
|
|
4
4
|
"description": "Effect runtime utilities for TypeScript",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Augusto Vivaldelli",
|
|
@@ -18,11 +18,26 @@
|
|
|
18
18
|
"import": "./dist/http/index.mjs",
|
|
19
19
|
"require": "./dist/http/index.cjs"
|
|
20
20
|
},
|
|
21
|
+
"./http/testing": {
|
|
22
|
+
"types": "./dist/http/testing.d.ts",
|
|
23
|
+
"import": "./dist/http/testing.mjs",
|
|
24
|
+
"require": "./dist/http/testing.cjs"
|
|
25
|
+
},
|
|
26
|
+
"./schema": {
|
|
27
|
+
"types": "./dist/schema/index.d.ts",
|
|
28
|
+
"import": "./dist/schema/index.mjs",
|
|
29
|
+
"require": "./dist/schema/index.cjs"
|
|
30
|
+
},
|
|
21
31
|
"./core": {
|
|
22
32
|
"types": "./dist/core/index.d.ts",
|
|
23
33
|
"import": "./dist/core/index.mjs",
|
|
24
34
|
"require": "./dist/core/index.cjs"
|
|
25
35
|
},
|
|
36
|
+
"./observability": {
|
|
37
|
+
"types": "./dist/observability/index.d.ts",
|
|
38
|
+
"import": "./dist/observability/index.mjs",
|
|
39
|
+
"require": "./dist/observability/index.cjs"
|
|
40
|
+
},
|
|
26
41
|
"./agent": {
|
|
27
42
|
"types": "./dist/agent/index.d.ts",
|
|
28
43
|
"import": "./dist/agent/index.mjs",
|
|
@@ -91,6 +106,17 @@
|
|
|
91
106
|
"agent:vscode:reinstall:global": "npm run agent:vscode:uninstall:global && npm run agent:vscode:install:global",
|
|
92
107
|
"benchmark": "tsx src/benchmarks/runner.ts",
|
|
93
108
|
"benchmark:json": "tsx src/benchmarks/runner.ts --json",
|
|
109
|
+
"benchmark:http": "tsx src/benchmarks/runner.ts http-concurrent",
|
|
110
|
+
"benchmark:http:soak": "BRASS_HTTP_BENCH_MODE=soak tsx src/benchmarks/runner.ts http-concurrent",
|
|
111
|
+
"benchmark:http:budget": "node scripts/check-http-benchmark-budget.mjs",
|
|
112
|
+
"benchmark:adaptive": "tsx src/benchmarks/runner.ts adaptive-limiter-soak",
|
|
113
|
+
"benchmark:adaptive:soak": "BRASS_ADAPTIVE_BENCH_MODE=soak tsx src/benchmarks/runner.ts adaptive-limiter-soak",
|
|
114
|
+
"benchmark:observability": "tsx src/benchmarks/runner.ts observability-overhead",
|
|
115
|
+
"benchmark:observability:budget": "node scripts/check-observability-benchmark-budget.mjs",
|
|
116
|
+
"example:observability:express": "tsx src/examples/observabilityExpress.ts",
|
|
117
|
+
"example:observability:fastify": "tsx src/examples/observabilityFastify.ts",
|
|
118
|
+
"example:observability:nest": "tsx src/examples/observabilityNest.ts",
|
|
119
|
+
"smoke:observability:collector": "node scripts/observability-collector-smoke.mjs",
|
|
94
120
|
"context": "node scripts/context-pack.mjs",
|
|
95
121
|
"test:coverage:html": "COVERAGE_GATE=off vitest run --coverage --reporter=default",
|
|
96
122
|
"build:wasm": "cd crates/brass-runtime-wasm-engine && wasm-pack build --target nodejs --out-dir ../../wasm/pkg",
|
package/dist/chunk-BDYEENHT.js
DELETED
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
asyncEffect,
|
|
3
|
-
asyncFail,
|
|
4
|
-
asyncFlatMap,
|
|
5
|
-
asyncFold,
|
|
6
|
-
asyncSucceed,
|
|
7
|
-
unsafeGetCurrentRuntime
|
|
8
|
-
} from "./chunk-BMH5AV44.js";
|
|
9
|
-
|
|
10
|
-
// src/core/runtime/combinators.ts
|
|
11
|
-
function sleep(ms) {
|
|
12
|
-
return asyncEffect((_env, cb) => {
|
|
13
|
-
const delay = Math.max(0, Math.floor(ms));
|
|
14
|
-
const id = setTimeout(() => cb({ _tag: "Success", value: void 0 }), delay);
|
|
15
|
-
return () => clearTimeout(id);
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
function timeout(effect, ms) {
|
|
19
|
-
return asyncEffect((env, cb) => {
|
|
20
|
-
let done = false;
|
|
21
|
-
let timerId;
|
|
22
|
-
let effectRunning = true;
|
|
23
|
-
timerId = setTimeout(() => {
|
|
24
|
-
if (done) return;
|
|
25
|
-
done = true;
|
|
26
|
-
effectRunning = false;
|
|
27
|
-
cb({
|
|
28
|
-
_tag: "Failure",
|
|
29
|
-
cause: { _tag: "Fail", error: { _tag: "TimeoutError", ms } }
|
|
30
|
-
});
|
|
31
|
-
}, Math.max(0, Math.floor(ms)));
|
|
32
|
-
const runtime = unsafeGetCurrentRuntime();
|
|
33
|
-
if (runtime) {
|
|
34
|
-
const fiber = runtime.fork(effect);
|
|
35
|
-
fiber.join((exit) => {
|
|
36
|
-
if (done) return;
|
|
37
|
-
done = true;
|
|
38
|
-
clearTimeout(timerId);
|
|
39
|
-
cb(exit);
|
|
40
|
-
});
|
|
41
|
-
return () => {
|
|
42
|
-
if (done) return;
|
|
43
|
-
done = true;
|
|
44
|
-
clearTimeout(timerId);
|
|
45
|
-
fiber.interrupt();
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
return () => {
|
|
49
|
-
if (done) return;
|
|
50
|
-
done = true;
|
|
51
|
-
clearTimeout(timerId);
|
|
52
|
-
};
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
function retry(effect, policy) {
|
|
56
|
-
const shouldRetry = policy.shouldRetry ?? (() => true);
|
|
57
|
-
const jitter = policy.jitter ?? "full";
|
|
58
|
-
const maxElapsedMs = policy.maxElapsedMs;
|
|
59
|
-
const computeDelay = (attempt) => {
|
|
60
|
-
const exp = policy.baseDelayMs * Math.pow(2, attempt);
|
|
61
|
-
const capped = Math.min(exp, policy.maxDelayMs);
|
|
62
|
-
if (jitter === "none") return capped;
|
|
63
|
-
return Math.floor(Math.random() * capped);
|
|
64
|
-
};
|
|
65
|
-
const loop = (attempt, startedAt) => asyncFold(
|
|
66
|
-
effect,
|
|
67
|
-
(error) => {
|
|
68
|
-
if (attempt >= policy.maxRetries) return asyncFail(error);
|
|
69
|
-
if (!shouldRetry(error, attempt)) return asyncFail(error);
|
|
70
|
-
if (maxElapsedMs !== void 0) {
|
|
71
|
-
const elapsed = performance.now() - startedAt;
|
|
72
|
-
if (elapsed >= maxElapsedMs) return asyncFail(error);
|
|
73
|
-
}
|
|
74
|
-
const delay = computeDelay(attempt);
|
|
75
|
-
return asyncFlatMap(sleep(delay), () => loop(attempt + 1, startedAt));
|
|
76
|
-
},
|
|
77
|
-
(value) => asyncSucceed(value)
|
|
78
|
-
);
|
|
79
|
-
return asyncFlatMap(
|
|
80
|
-
{ _tag: "Sync", thunk: () => performance.now() },
|
|
81
|
-
(startedAt) => loop(0, startedAt)
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
function retryN(effect, n) {
|
|
85
|
-
return retry(effect, {
|
|
86
|
-
maxRetries: n,
|
|
87
|
-
baseDelayMs: 0,
|
|
88
|
-
maxDelayMs: 0,
|
|
89
|
-
jitter: "none"
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
function retryWithBackoff(effect, opts = {}) {
|
|
93
|
-
return retry(effect, {
|
|
94
|
-
maxRetries: opts.maxRetries ?? 3,
|
|
95
|
-
baseDelayMs: opts.baseDelayMs ?? 100,
|
|
96
|
-
maxDelayMs: opts.maxDelayMs ?? 1e4,
|
|
97
|
-
maxElapsedMs: opts.maxElapsedMs,
|
|
98
|
-
shouldRetry: opts.shouldRetry,
|
|
99
|
-
jitter: "full"
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// src/core/runtime/circuitBreaker.ts
|
|
104
|
-
function makeCircuitBreaker(config = {}) {
|
|
105
|
-
const failureThreshold = config.failureThreshold ?? 5;
|
|
106
|
-
const resetTimeoutMs = config.resetTimeoutMs ?? 3e4;
|
|
107
|
-
const successThreshold = config.successThreshold ?? 1;
|
|
108
|
-
const isFailure = config.isFailure ?? (() => true);
|
|
109
|
-
const onStateChange = config.onStateChange;
|
|
110
|
-
let currentState = "closed";
|
|
111
|
-
let consecutiveFailures = 0;
|
|
112
|
-
let consecutiveSuccesses = 0;
|
|
113
|
-
let openedAt = 0;
|
|
114
|
-
let totalRequests = 0;
|
|
115
|
-
let totalFailures = 0;
|
|
116
|
-
let totalSuccesses = 0;
|
|
117
|
-
let totalRejected = 0;
|
|
118
|
-
let lastFailureTime = null;
|
|
119
|
-
let lastSuccessTime = null;
|
|
120
|
-
const transition = (to) => {
|
|
121
|
-
if (currentState === to) return;
|
|
122
|
-
const from = currentState;
|
|
123
|
-
currentState = to;
|
|
124
|
-
onStateChange?.(from, to);
|
|
125
|
-
};
|
|
126
|
-
const onSuccess = () => {
|
|
127
|
-
totalSuccesses++;
|
|
128
|
-
lastSuccessTime = Date.now();
|
|
129
|
-
consecutiveFailures = 0;
|
|
130
|
-
if (currentState === "half-open") {
|
|
131
|
-
consecutiveSuccesses++;
|
|
132
|
-
if (consecutiveSuccesses >= successThreshold) {
|
|
133
|
-
consecutiveSuccesses = 0;
|
|
134
|
-
transition("closed");
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
const onFailure = (error) => {
|
|
139
|
-
if (!isFailure(error)) {
|
|
140
|
-
onSuccess();
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
totalFailures++;
|
|
144
|
-
lastFailureTime = Date.now();
|
|
145
|
-
consecutiveSuccesses = 0;
|
|
146
|
-
consecutiveFailures++;
|
|
147
|
-
if (currentState === "half-open") {
|
|
148
|
-
openedAt = Date.now();
|
|
149
|
-
transition("open");
|
|
150
|
-
} else if (currentState === "closed" && consecutiveFailures >= failureThreshold) {
|
|
151
|
-
openedAt = Date.now();
|
|
152
|
-
transition("open");
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
const shouldAllow = () => {
|
|
156
|
-
switch (currentState) {
|
|
157
|
-
case "closed":
|
|
158
|
-
return true;
|
|
159
|
-
case "open": {
|
|
160
|
-
const elapsed = Date.now() - openedAt;
|
|
161
|
-
if (elapsed >= resetTimeoutMs) {
|
|
162
|
-
transition("half-open");
|
|
163
|
-
return true;
|
|
164
|
-
}
|
|
165
|
-
return false;
|
|
166
|
-
}
|
|
167
|
-
case "half-open":
|
|
168
|
-
return true;
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
const protect = (effect) => {
|
|
172
|
-
totalRequests++;
|
|
173
|
-
if (!shouldAllow()) {
|
|
174
|
-
totalRejected++;
|
|
175
|
-
return asyncFail({
|
|
176
|
-
_tag: "CircuitBreakerOpen",
|
|
177
|
-
openSince: openedAt,
|
|
178
|
-
failures: consecutiveFailures
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
return asyncFold(
|
|
182
|
-
effect,
|
|
183
|
-
(error) => {
|
|
184
|
-
onFailure(error);
|
|
185
|
-
return asyncFail(error);
|
|
186
|
-
},
|
|
187
|
-
(value) => {
|
|
188
|
-
onSuccess();
|
|
189
|
-
return asyncSucceed(value);
|
|
190
|
-
}
|
|
191
|
-
);
|
|
192
|
-
};
|
|
193
|
-
const stats = () => ({
|
|
194
|
-
state: currentState,
|
|
195
|
-
failures: consecutiveFailures,
|
|
196
|
-
successes: consecutiveSuccesses,
|
|
197
|
-
totalRequests,
|
|
198
|
-
totalFailures,
|
|
199
|
-
totalSuccesses,
|
|
200
|
-
totalRejected,
|
|
201
|
-
lastFailureTime,
|
|
202
|
-
lastSuccessTime
|
|
203
|
-
});
|
|
204
|
-
const reset = () => {
|
|
205
|
-
consecutiveFailures = 0;
|
|
206
|
-
consecutiveSuccesses = 0;
|
|
207
|
-
transition("closed");
|
|
208
|
-
};
|
|
209
|
-
return {
|
|
210
|
-
state: () => currentState,
|
|
211
|
-
protect,
|
|
212
|
-
stats,
|
|
213
|
-
reset
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
export {
|
|
218
|
-
sleep,
|
|
219
|
-
timeout,
|
|
220
|
-
retry,
|
|
221
|
-
retryN,
|
|
222
|
-
retryWithBackoff,
|
|
223
|
-
makeCircuitBreaker
|
|
224
|
-
};
|