@voltro/shared 0.86.0 → 0.88.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/CHANGELOG.md +5484 -0
- package/README.md +2 -0
- package/THIRD-PARTY-NOTICES.md +1 -1
- package/dist/circuitBreaker.js +71 -0
- package/dist/errors.js +1 -2
- package/dist/ids.js +1 -2
- package/dist/index.d.ts +72 -5
- package/dist/index.js +3 -1
- package/dist/schemas.js +1 -2
- package/dist/traceId.js +15 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -24,3 +24,5 @@ deliberately short so there is nothing here to drift out of date.
|
|
|
24
24
|
|
|
25
25
|
Proprietary. © 2026 Voltro UG. Your use is governed by the
|
|
26
26
|
[Terms of Service](https://voltro.cloud/legal/terms). See the bundled `LICENSE`.
|
|
27
|
+
|
|
28
|
+
`newTraceId()` creates a nonzero 128-bit trace ID as 32 lowercase hexadecimal characters, using the same implementation re-exported by `@voltro/runtime`. It is browser-safe and diagnostic; it is not a security token.
|
package/THIRD-PARTY-NOTICES.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Third-Party Notices — @voltro/shared
|
|
2
2
|
|
|
3
|
-
This package uses the
|
|
3
|
+
This package uses the third-party software listed below. Each is the
|
|
4
4
|
property of its respective copyright holders and is used under the terms of
|
|
5
5
|
its license. This file is provided for attribution; it grants no rights in
|
|
6
6
|
@voltro/shared itself, which is proprietary (see LICENSE).
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Cause as e, Clock as t, Duration as n, Effect as r, Ref as i, Schema as a } from "effect";
|
|
2
|
+
// src/circuitBreaker.ts
|
|
3
|
+
var o = class extends a.TaggedError()("CircuitOpen", {
|
|
4
|
+
name: a.String,
|
|
5
|
+
retryAfterMs: a.Number
|
|
6
|
+
}) {}, s = {
|
|
7
|
+
tag: "closed",
|
|
8
|
+
failureCount: 0,
|
|
9
|
+
halfOpenSuccesses: 0,
|
|
10
|
+
openedAt: 0
|
|
11
|
+
}, c = { make: (a) => r.gen(function* () {
|
|
12
|
+
let c = a.name ?? "circuit", l = n.toMillis(n.fromInputUnsafe(a.resetTimeout)), u = a.halfOpenSuccessThreshold ?? 1, d = a.isFailure ?? (() => !0), f = yield* i.make(s), p = r.gen(function* () {
|
|
13
|
+
let e = yield* i.get(f);
|
|
14
|
+
if (e.tag === "half-open") {
|
|
15
|
+
let t = e.halfOpenSuccesses + 1;
|
|
16
|
+
yield* i.set(f, t >= u ? s : {
|
|
17
|
+
...e,
|
|
18
|
+
halfOpenSuccesses: t
|
|
19
|
+
});
|
|
20
|
+
} else e.failureCount !== 0 && (yield* i.set(f, s));
|
|
21
|
+
}), m = r.gen(function* () {
|
|
22
|
+
let e = yield* t.currentTimeMillis, n = yield* i.get(f);
|
|
23
|
+
if (n.tag === "half-open" || n.tag === "open") {
|
|
24
|
+
yield* i.set(f, {
|
|
25
|
+
tag: "open",
|
|
26
|
+
failureCount: a.failureThreshold,
|
|
27
|
+
halfOpenSuccesses: 0,
|
|
28
|
+
openedAt: e
|
|
29
|
+
});
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
let r = n.failureCount + 1;
|
|
33
|
+
yield* i.set(f, r >= a.failureThreshold ? {
|
|
34
|
+
tag: "open",
|
|
35
|
+
failureCount: r,
|
|
36
|
+
halfOpenSuccesses: 0,
|
|
37
|
+
openedAt: e
|
|
38
|
+
} : {
|
|
39
|
+
...n,
|
|
40
|
+
failureCount: r
|
|
41
|
+
});
|
|
42
|
+
}), h = r.gen(function* () {
|
|
43
|
+
let e = yield* i.get(f);
|
|
44
|
+
if (e.tag !== "open") return null;
|
|
45
|
+
let n = (yield* t.currentTimeMillis) - e.openedAt;
|
|
46
|
+
return n >= l ? (yield* i.set(f, {
|
|
47
|
+
tag: "half-open",
|
|
48
|
+
failureCount: 0,
|
|
49
|
+
halfOpenSuccesses: 0,
|
|
50
|
+
openedAt: e.openedAt
|
|
51
|
+
}), null) : l - n;
|
|
52
|
+
}), g = (t) => r.gen(function* () {
|
|
53
|
+
let n = yield* h;
|
|
54
|
+
return n === null ? yield* t.pipe(r.tap(() => p), r.tapCause((t) => {
|
|
55
|
+
if (e.hasInterruptsOnly(t)) return r.void;
|
|
56
|
+
let n = e.findErrorOption(t), i = n._tag === "Some" ? n.value : void 0;
|
|
57
|
+
return d(i) ? m : r.void;
|
|
58
|
+
})) : yield* r.fail(new o({
|
|
59
|
+
name: c,
|
|
60
|
+
retryAfterMs: n
|
|
61
|
+
}));
|
|
62
|
+
}), _ = ((e) => g(e));
|
|
63
|
+
return Object.defineProperty(_, "state", {
|
|
64
|
+
value: i.get(f).pipe(r.map((e) => e.tag)),
|
|
65
|
+
enumerable: !0
|
|
66
|
+
}), Object.defineProperty(_, "reset", {
|
|
67
|
+
value: i.set(f, s),
|
|
68
|
+
enumerable: !0
|
|
69
|
+
}), _;
|
|
70
|
+
}) };
|
|
71
|
+
export { c as CircuitBreaker, o as CircuitOpen };
|
package/dist/errors.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Data as e } from "effect";
|
|
2
|
-
|
|
2
|
+
// src/errors.ts
|
|
3
3
|
var t = "@voltro/error", n = class extends e.TaggedError(`${t}/FrameworkError`) {}, r = class extends e.TaggedError(`${t}/I18nError`) {}, i = class extends e.TaggedError(`${t}/Unauthorized`) {}, a = class extends e.TaggedError(`${t}/Forbidden`) {}, o = class extends e.TaggedError(`${t}/NotFound`) {}, s = class extends e.TaggedError(`${t}/Conflict`) {}, c = class extends e.TaggedError(`${t}/BadRequest`) {}, l = class extends e.TaggedError(`${t}/RateLimitExceeded`) {}, u = class extends e.TaggedError(`${t}/DatabaseUnavailable`) {}, d = class extends e.TaggedError(`${t}/ServiceUnavailable`) {}, f = class extends e.TaggedError(`${t}/TenantContextMissing`) {}, p = class extends e.TaggedError(`${t}/CrossTenantAccessDenied`) {};
|
|
4
|
-
//#endregion
|
|
5
4
|
export { c as BadRequest, s as Conflict, p as CrossTenantAccessDenied, u as DatabaseUnavailable, a as Forbidden, n as FrameworkError, r as I18nError, o as NotFound, l as RateLimitExceeded, d as ServiceUnavailable, f as TenantContextMissing, i as Unauthorized };
|
package/dist/ids.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Schema as e } from "effect";
|
|
2
|
-
|
|
2
|
+
// src/ids.ts
|
|
3
3
|
var t = (t) => e.NonEmptyString.pipe(e.brand(t)), n = t("SubscriptionId"), r = t("CallId"), i = t("TenantId"), a = t("UserId"), o = t("ApiKeyId"), s = t("WorkflowExecutionId"), c = t("ConnectionId"), l = t("RouteId"), u = t("TraceId");
|
|
4
|
-
//#endregion
|
|
5
4
|
export { o as ApiKeyId, r as CallId, c as ConnectionId, l as RouteId, n as SubscriptionId, i as TenantId, u as TraceId, a as UserId, s as WorkflowExecutionId };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { Cause } from 'effect';
|
|
2
|
+
import { Duration } from 'effect';
|
|
3
|
+
import { Effect } from 'effect';
|
|
1
4
|
import { Schema } from 'effect';
|
|
2
5
|
import { VoidIfEmpty } from 'effect/Types';
|
|
3
6
|
import { YieldableError } from 'effect/Cause';
|
|
@@ -19,6 +22,51 @@ export declare const CallId: Schema.brand<Schema.NonEmptyString, "CallId">;
|
|
|
19
22
|
|
|
20
23
|
export declare type CallId = typeof CallId.Type;
|
|
21
24
|
|
|
25
|
+
export declare interface CircuitBreaker {
|
|
26
|
+
/** Run an effect through the breaker. OPEN ⇒ fails fast with CircuitOpen. */
|
|
27
|
+
<A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E | CircuitOpen, R>;
|
|
28
|
+
/** Current state — reads the Ref. */
|
|
29
|
+
readonly state: Effect.Effect<CircuitState>;
|
|
30
|
+
/** Force the breaker back to closed (clears counters). */
|
|
31
|
+
readonly reset: Effect.Effect<void>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export declare const CircuitBreaker: {
|
|
35
|
+
/** Construct a breaker. Effect-returning because it allocates a Ref. */
|
|
36
|
+
readonly make: (options: CircuitBreakerOptions) => Effect.Effect<CircuitBreaker>;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export declare interface CircuitBreakerOptions {
|
|
40
|
+
/** Consecutive failures before the breaker opens. */
|
|
41
|
+
readonly failureThreshold: number;
|
|
42
|
+
/** Wait before transitioning open → half-open (Duration or string). */
|
|
43
|
+
readonly resetTimeout: Duration.Input;
|
|
44
|
+
/** Successes required in half-open before closing again. Default 1. */
|
|
45
|
+
readonly halfOpenSuccessThreshold?: number;
|
|
46
|
+
/** Predicate: does this error count toward the threshold? Default: all
|
|
47
|
+
* typed failures count. Interruptions NEVER count (they're not a
|
|
48
|
+
* downstream failure — the caller went away). */
|
|
49
|
+
readonly isFailure?: (error: unknown) => boolean;
|
|
50
|
+
/** Optional name for inspect / metrics + dashboard surfacing. */
|
|
51
|
+
readonly name?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Tagged error the breaker fails with while OPEN. Declarable on a
|
|
56
|
+
* mutation / workflow `error:` schema so it marshals typed to the client.
|
|
57
|
+
*/
|
|
58
|
+
export declare class CircuitOpen extends CircuitOpen_base {
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
declare const CircuitOpen_base: Schema.Class<CircuitOpen, Schema.TaggedStruct<"CircuitOpen", {
|
|
62
|
+
/** Breaker name (the `name` option, or `'circuit'` when unnamed). */
|
|
63
|
+
readonly name: Schema.String;
|
|
64
|
+
/** Best-effort ms until the breaker next admits a probe call. */
|
|
65
|
+
readonly retryAfterMs: Schema.Number;
|
|
66
|
+
}>, Cause.YieldableError>;
|
|
67
|
+
|
|
68
|
+
export declare type CircuitState = 'closed' | 'open' | 'half-open';
|
|
69
|
+
|
|
22
70
|
export declare class Conflict extends Conflict_base<{
|
|
23
71
|
readonly resource: string;
|
|
24
72
|
readonly reason: string;
|
|
@@ -88,18 +136,37 @@ declare const I18nError_base: new <A extends Record<string, any> = {}>(args: Voi
|
|
|
88
136
|
readonly _tag: "@voltro/error/I18nError";
|
|
89
137
|
} & Readonly<A>;
|
|
90
138
|
|
|
91
|
-
export declare
|
|
139
|
+
export declare type JsonValue = JsonValueData;
|
|
92
140
|
|
|
93
|
-
declare
|
|
94
|
-
readonly [key: string]: JsonValue_2;
|
|
95
|
-
};
|
|
141
|
+
export declare const JsonValue: Schema.Codec<JsonValueData>;
|
|
96
142
|
|
|
97
|
-
declare
|
|
143
|
+
declare type JsonValueData = null | boolean | number | string | ReadonlyArray<JsonValueData> | {
|
|
144
|
+
readonly [key: string]: JsonValueData;
|
|
145
|
+
};
|
|
98
146
|
|
|
99
147
|
export declare const LocaleTag: Schema.brand<Schema.String, "LocaleTag">;
|
|
100
148
|
|
|
101
149
|
export declare type LocaleTag = typeof LocaleTag.Type;
|
|
102
150
|
|
|
151
|
+
/**
|
|
152
|
+
* A fresh trace id for work that has NO incoming span to continue — a webhook
|
|
153
|
+
* delivery, a schedule firing, a devtools invoke, a REST or gRPC entry point.
|
|
154
|
+
*
|
|
155
|
+
* 128 random bits as 32 lowercase hex, the shape `TRACEPARENT_RE` above accepts
|
|
156
|
+
* and the shape a collector, a log pipeline or `voltro traces` can join on.
|
|
157
|
+
*
|
|
158
|
+
* The reason this exists as ONE function: every entry point that minted its own
|
|
159
|
+
* had drifted somewhere different, and three of the shapes were not ids at all.
|
|
160
|
+
* `incoming:<webhookId>`, `reaction:<workflow>` and the literal
|
|
161
|
+
* `'webhooks:trigger'` are CONSTANTS — they were the caller's `source` label
|
|
162
|
+
* copied into the id field, so every delivery a process ever handled shared one
|
|
163
|
+
* trace and the waterfall merged unrelated work. `public-`/`grpc-`/`rest-` plus
|
|
164
|
+
* eight base36 characters carried ~41 bits, which collides long before a busy
|
|
165
|
+
* day is over. The entry-point kind is not lost by fixing this: it lives in the
|
|
166
|
+
* `source` field beside the id, which is where it was already being written.
|
|
167
|
+
*/
|
|
168
|
+
export declare const newTraceId: () => string;
|
|
169
|
+
|
|
103
170
|
export declare class NotFound extends NotFound_base<{
|
|
104
171
|
readonly resource: string;
|
|
105
172
|
readonly id?: string;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { BadRequest as e, Conflict as t, CrossTenantAccessDenied as n, DatabaseUnavailable as r, Forbidden as i, FrameworkError as a, I18nError as o, NotFound as s, RateLimitExceeded as c, ServiceUnavailable as l, TenantContextMissing as u, Unauthorized as d } from "./errors.js";
|
|
2
2
|
import { ApiKeyId as f, CallId as p, ConnectionId as m, RouteId as h, SubscriptionId as g, TenantId as _, TraceId as v, UserId as y, WorkflowExecutionId as b } from "./ids.js";
|
|
3
3
|
import { EmailAddress as x, JsonValue as S, LocaleTag as C, TimestampISO as w, TimezoneName as T } from "./schemas.js";
|
|
4
|
-
|
|
4
|
+
import { CircuitBreaker as E, CircuitOpen as D } from "./circuitBreaker.js";
|
|
5
|
+
import { newTraceId as O } from "./traceId.js";
|
|
6
|
+
export { f as ApiKeyId, e as BadRequest, p as CallId, E as CircuitBreaker, D as CircuitOpen, t as Conflict, m as ConnectionId, n as CrossTenantAccessDenied, r as DatabaseUnavailable, x as EmailAddress, i as Forbidden, a as FrameworkError, o as I18nError, S as JsonValue, C as LocaleTag, s as NotFound, c as RateLimitExceeded, h as RouteId, l as ServiceUnavailable, g as SubscriptionId, u as TenantContextMissing, _ as TenantId, w as TimestampISO, T as TimezoneName, v as TraceId, d as Unauthorized, y as UserId, b as WorkflowExecutionId, O as newTraceId };
|
package/dist/schemas.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Schema as e } from "effect";
|
|
2
|
-
|
|
2
|
+
// src/schemas.ts
|
|
3
3
|
var t = e.DateFromString, n = e.suspend(() => e.Union([
|
|
4
4
|
e.Null,
|
|
5
5
|
e.Boolean,
|
|
@@ -8,5 +8,4 @@ var t = e.DateFromString, n = e.suspend(() => e.Union([
|
|
|
8
8
|
e.Array(n),
|
|
9
9
|
e.Record(e.String, n)
|
|
10
10
|
])), r = e.String.pipe(e.check(e.isPattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/, { message: "invalid email address" })), e.brand("EmailAddress")), i = e.String.pipe(e.check(e.isPattern(/^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$/)), e.brand("LocaleTag")), a = e.String.pipe(e.check(e.isPattern(/^[A-Za-z]+(\/[A-Za-z_-]+)*$/)), e.brand("TimezoneName"));
|
|
11
|
-
//#endregion
|
|
12
11
|
export { r as EmailAddress, n as JsonValue, i as LocaleTag, t as TimestampISO, a as TimezoneName };
|
package/dist/traceId.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/traceId.ts
|
|
2
|
+
var e = () => {
|
|
3
|
+
let e = globalThis.crypto, t = /* @__PURE__ */ new Uint8Array(16);
|
|
4
|
+
if (e?.getRandomValues !== void 0) e.getRandomValues(t);
|
|
5
|
+
else {
|
|
6
|
+
let e = `${Date.now().toString(16)}${Math.random().toString(16).slice(2)}`;
|
|
7
|
+
for (let n = 0; n < t.length; n += 1) {
|
|
8
|
+
let r = e.slice(n * 2 % e.length, n * 2 % e.length + 2);
|
|
9
|
+
t[n] = (parseInt(r, 16) || n + 1) ^ (Math.random() * 256 | 0);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
let n = Array.from(t, (e) => e.toString(16).padStart(2, "0")).join("");
|
|
13
|
+
return n === "0".repeat(32) ? `${"0".repeat(31)}1` : n;
|
|
14
|
+
};
|
|
15
|
+
export { e as newTraceId };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/shared",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.88.0",
|
|
4
4
|
"description": "Internal shared utilities used across the @voltro/* packages.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"voltro",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"node": ">=24.0.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"effect": "4.0
|
|
36
|
+
"effect": "4.1.0"
|
|
37
37
|
},
|
|
38
38
|
"publishConfig": {
|
|
39
39
|
"access": "public"
|