@axorum/client 0.0.0-stage → 1.0.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/LICENSE +202 -0
- package/README.md +530 -2
- package/dist/agent-uri.d.ts +30 -0
- package/dist/agent-uri.d.ts.map +1 -0
- package/dist/agent-uri.js +218 -0
- package/dist/agent-uri.js.map +1 -0
- package/dist/authorize.d.ts +671 -0
- package/dist/authorize.d.ts.map +1 -0
- package/dist/authorize.js +947 -0
- package/dist/authorize.js.map +1 -0
- package/dist/capabilities.d.ts +30 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +40 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/chart.d.ts +74 -0
- package/dist/chart.d.ts.map +1 -0
- package/dist/chart.js +107 -0
- package/dist/chart.js.map +1 -0
- package/dist/client.d.ts +644 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +1302 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +646 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +928 -0
- package/dist/errors.js.map +1 -0
- package/dist/failover.d.ts +197 -0
- package/dist/failover.d.ts.map +1 -0
- package/dist/failover.js +289 -0
- package/dist/failover.js.map +1 -0
- package/dist/freshness.d.ts +65 -0
- package/dist/freshness.d.ts.map +1 -0
- package/dist/freshness.js +148 -0
- package/dist/freshness.js.map +1 -0
- package/dist/generated/agent-plane.d.ts +1863 -0
- package/dist/generated/agent-plane.d.ts.map +1 -0
- package/dist/generated/agent-plane.js +19 -0
- package/dist/generated/agent-plane.js.map +1 -0
- package/dist/idempotency.d.ts +160 -0
- package/dist/idempotency.d.ts.map +1 -0
- package/dist/idempotency.js +208 -0
- package/dist/idempotency.js.map +1 -0
- package/dist/index.d.ts +89 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +87 -0
- package/dist/index.js.map +1 -0
- package/dist/intent.d.ts +163 -0
- package/dist/intent.d.ts.map +1 -0
- package/dist/intent.js +82 -0
- package/dist/intent.js.map +1 -0
- package/dist/json.d.ts +49 -0
- package/dist/json.d.ts.map +1 -0
- package/dist/json.js +259 -0
- package/dist/json.js.map +1 -0
- package/dist/key.d.ts +64 -0
- package/dist/key.d.ts.map +1 -0
- package/dist/key.js +71 -0
- package/dist/key.js.map +1 -0
- package/dist/payment.d.ts +172 -0
- package/dist/payment.d.ts.map +1 -0
- package/dist/payment.js +178 -0
- package/dist/payment.js.map +1 -0
- package/dist/refusal.d.ts +121 -0
- package/dist/refusal.d.ts.map +1 -0
- package/dist/refusal.js +176 -0
- package/dist/refusal.js.map +1 -0
- package/dist/retry.d.ts +215 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +297 -0
- package/dist/retry.js.map +1 -0
- package/dist/typeid.d.ts +64 -0
- package/dist/typeid.d.ts.map +1 -0
- package/dist/typeid.js +119 -0
- package/dist/typeid.js.map +1 -0
- package/dist/types.d.ts +245 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +14 -0
- package/dist/types.js.map +1 -0
- package/package.json +58 -3
package/dist/retry.js
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The transport retry policy: a mirror of `acton-service-client` 0.2's, which the Rust
|
|
3
|
+
* SDK uses, so both SDKs retry alike.
|
|
4
|
+
*
|
|
5
|
+
* Retries are **off** for a client's own calls unless it is built with a
|
|
6
|
+
* {@link RetryPolicy}. When one is, a request is sent again after a connection failure
|
|
7
|
+
* or a timeout, and after a `429`, `502` or `504` from something in front of the
|
|
8
|
+
* service (or a `423` that says when to come back), honouring `Retry-After`. A status
|
|
9
|
+
* the service documents as an answer (every `4xx` it sends, `421 not_primary`, `503
|
|
10
|
+
* commit_unavailable`) is not retried there: it reaches the caller after one send.
|
|
11
|
+
*
|
|
12
|
+
* `authorize` always retries, under its own policy ({@link AUTHORIZE_RETRY_POLICY}) and
|
|
13
|
+
* one deadline for the whole call ({@link AUTHORIZE_DEADLINE_MS}), and re-sends a `421`
|
|
14
|
+
* and a `503` too ({@link AUTHORIZE_RESENT_STATUSES}).
|
|
15
|
+
*
|
|
16
|
+
* Every function here is pure. `spec/fixtures/retry-v1.json` pins each one to the rows
|
|
17
|
+
* the Rust SDK pins `acton-service-client` itself to.
|
|
18
|
+
*
|
|
19
|
+
* @packageDocumentation
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Three sends, 100 ms doubling, capped at 5 s, no jitter, no deadline:
|
|
23
|
+
* `acton-service-client`'s default.
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* import { AxorumClient, DEFAULT_RETRY_POLICY } from "@axorum/client";
|
|
27
|
+
*
|
|
28
|
+
* const axorum = new AxorumClient({ baseUrl, retry: DEFAULT_RETRY_POLICY });
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export const DEFAULT_RETRY_POLICY = Object.freeze({
|
|
32
|
+
maxAttempts: 3,
|
|
33
|
+
baseDelayMs: 100,
|
|
34
|
+
maxDelayMs: 5_000,
|
|
35
|
+
});
|
|
36
|
+
/**
|
|
37
|
+
* The whole budget of one `authorize` call, in milliseconds: the window and pin reads,
|
|
38
|
+
* every submission, every attempt and every pause. The most `authorizeDeadlineMs`
|
|
39
|
+
* accepts, since the attestation minimum is sized for it.
|
|
40
|
+
*/
|
|
41
|
+
export const AUTHORIZE_DEADLINE_MS = 15_000;
|
|
42
|
+
/**
|
|
43
|
+
* The response header on `GET /api/openapi.json` in which a clustered service advertises
|
|
44
|
+
* its commit wait (`[cluster] submit_timeout_ms`), in milliseconds: how long it holds a
|
|
45
|
+
* submission before answering `503 commit_unavailable`. A standalone service has no
|
|
46
|
+
* commit wait and sends none.
|
|
47
|
+
*/
|
|
48
|
+
export const SUBMIT_TIMEOUT_HEADER = "axorum-submit-timeout-ms";
|
|
49
|
+
/**
|
|
50
|
+
* How much longer than the advertised commit wait an attempt waits, in milliseconds, so
|
|
51
|
+
* the service's own `503 commit_unavailable` arrives before the client gives up.
|
|
52
|
+
*/
|
|
53
|
+
export const COMMIT_WAIT_MARGIN_MS = 1_000;
|
|
54
|
+
/**
|
|
55
|
+
* The most one attempt waits, in milliseconds, when the service advertises no commit
|
|
56
|
+
* wait: a standalone service, one older than the header, or an advertisement that could
|
|
57
|
+
* not be read. Also the bound on each of `authorize`'s reads.
|
|
58
|
+
*/
|
|
59
|
+
export const SUBMIT_ATTEMPT_FALLBACK_MS = 6_000;
|
|
60
|
+
/**
|
|
61
|
+
* The most one submission attempt waits against a service whose advertised commit wait
|
|
62
|
+
* is `commitWaitMs`: that wait plus {@link COMMIT_WAIT_MARGIN_MS}, or
|
|
63
|
+
* {@link SUBMIT_ATTEMPT_FALLBACK_MS} when none is advertised. Pure.
|
|
64
|
+
*
|
|
65
|
+
* Under `authorize`'s deadline the attempt is further clamped to what is left of it. When
|
|
66
|
+
* that clamp cuts an attempt below the commit wait plus the margin, the service may still
|
|
67
|
+
* be deciding when the client stops waiting, so an attempt that ends that way is honestly
|
|
68
|
+
* `indeterminate`: re-drive the same payment to learn its answer.
|
|
69
|
+
*
|
|
70
|
+
* ```ts
|
|
71
|
+
* submitAttemptTimeout(undefined); // 6000
|
|
72
|
+
* submitAttemptTimeout(9_000); // 10000
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export function submitAttemptTimeout(commitWaitMs) {
|
|
76
|
+
return commitWaitMs === undefined ? SUBMIT_ATTEMPT_FALLBACK_MS : commitWaitMs + COMMIT_WAIT_MARGIN_MS;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The timeout a plain submission needs in place of the SDK's default `defaultMs` to
|
|
80
|
+
* outlast an advertised `commitWaitMs`: {@link submitAttemptTimeout} of it, when that is
|
|
81
|
+
* longer. Never shorter, and `undefined` when no wait is advertised. Pure.
|
|
82
|
+
*/
|
|
83
|
+
export function liftedSubmitTimeout(defaultMs, commitWaitMs) {
|
|
84
|
+
if (commitWaitMs === undefined) {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
const needed = submitAttemptTimeout(commitWaitMs);
|
|
88
|
+
return needed > defaultMs ? needed : undefined;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The commit wait, in milliseconds, a {@link SUBMIT_TIMEOUT_HEADER} value states, or
|
|
92
|
+
* `undefined` when it states none: only 1 to 9 ASCII digits, not all zero, state a wait.
|
|
93
|
+
* Anything else (a sign, a space, an exponent, a tenth digit) is treated as no
|
|
94
|
+
* advertisement rather than guessed at, so this client reads the header as the Rust
|
|
95
|
+
* client does (`spec/fixtures/attempt-timeout-v1.json` pins both). Pure.
|
|
96
|
+
*/
|
|
97
|
+
export function advertisedCommitWait(value) {
|
|
98
|
+
if (value === null || value === undefined || !/^[0-9]{1,9}$/.test(value)) {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
const millis = Number(value);
|
|
102
|
+
return millis === 0 ? undefined : millis;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The policy every `authorize` request is sent under: as many attempts as the deadline
|
|
106
|
+
* allows, full jitter floored at 100 ms, capped at 1.6 s. The deadline is not in it: it is
|
|
107
|
+
* an instant, set once per call and shared by all of its requests.
|
|
108
|
+
*/
|
|
109
|
+
export const AUTHORIZE_RETRY_POLICY = Object.freeze({
|
|
110
|
+
maxAttempts: 2 ** 32 - 1,
|
|
111
|
+
baseDelayMs: 100,
|
|
112
|
+
maxDelayMs: 1_600,
|
|
113
|
+
jitter: "full",
|
|
114
|
+
});
|
|
115
|
+
/**
|
|
116
|
+
* The status an `authorize` submission is re-sent on beyond the defaults: `421
|
|
117
|
+
* not_primary`, safe to re-send under the same transaction id. A `503` is not here: the
|
|
118
|
+
* transport re-sends on the status alone and reads no body, and only its body tells a
|
|
119
|
+
* `503 idempotency_unseeded`, which wrote nothing, from a `503 commit_unavailable`,
|
|
120
|
+
* which may have committed. So the call sends a `503` again itself, its body read.
|
|
121
|
+
*/
|
|
122
|
+
export const AUTHORIZE_RESENT_STATUSES = Object.freeze([421]);
|
|
123
|
+
/** The largest double strictly below 1: the top of the clamped draw range. */
|
|
124
|
+
const DRAW_MAX = 1 - Number.EPSILON / 2;
|
|
125
|
+
/**
|
|
126
|
+
* The exponential ceiling for the pause after send number `attempt` (1-based):
|
|
127
|
+
* `baseDelayMs × 2^(attempt − 1)`, capped at `maxDelayMs`. An attempt below 1 is
|
|
128
|
+
* treated as 1; a huge one saturates to the cap. With no jitter it is the pause. Pure.
|
|
129
|
+
*/
|
|
130
|
+
export function backoffDelay(policy, attempt) {
|
|
131
|
+
const exponent = Math.min(Math.max(attempt - 1, 0), 63);
|
|
132
|
+
return Math.min(policy.baseDelayMs * 2 ** exponent, policy.maxDelayMs);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* The pause after send number `attempt` (1-based), or `undefined` when it would reach the
|
|
136
|
+
* deadline. Pure.
|
|
137
|
+
*
|
|
138
|
+
* - `draw` is a uniform sample, clamped to `[0, 1)`; a `NaN` counts as 0. Ignored
|
|
139
|
+
* without jitter.
|
|
140
|
+
* - `remainingMs` is the time left before the deadline, or `undefined` for none.
|
|
141
|
+
*
|
|
142
|
+
* With `ceiling = backoffDelay(policy, attempt)` and `floor = min(baseDelayMs, ceiling)`:
|
|
143
|
+
* no jitter pauses `ceiling`; full jitter pauses `floor + ⌊(ceiling − floor) × draw⌋`.
|
|
144
|
+
* Either is refused when `remainingMs` is set and the pause is at least it.
|
|
145
|
+
*/
|
|
146
|
+
export function pause(policy, attempt, draw, remainingMs) {
|
|
147
|
+
const ceiling = backoffDelay(policy, attempt);
|
|
148
|
+
let delay = ceiling;
|
|
149
|
+
if (policy.jitter === "full") {
|
|
150
|
+
const floor = Math.min(policy.baseDelayMs, ceiling);
|
|
151
|
+
delay = floor + spread(ceiling - floor, draw);
|
|
152
|
+
}
|
|
153
|
+
return fitsBefore(delay, remainingMs);
|
|
154
|
+
}
|
|
155
|
+
/** Whether another send is allowed after send number `attempt` (1-based). Pure. */
|
|
156
|
+
export function shouldRetry(policy, attempt) {
|
|
157
|
+
return attempt < Math.max(policy.maxAttempts, 1);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* The pause before the send after `attempt`, or `undefined` to stop: when `maxAttempts`
|
|
161
|
+
* is spent, or when the pause would reach the deadline. A server's `Retry-After`
|
|
162
|
+
* replaces the computed pause (unjittered, not capped by `maxDelayMs`) but is still
|
|
163
|
+
* refused if it would reach the deadline. Pure.
|
|
164
|
+
*/
|
|
165
|
+
export function nextPause(policy, attempt, draw, retryAfterMs, remainingMs) {
|
|
166
|
+
if (!shouldRetry(policy, attempt)) {
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
return retryAfterMs === undefined
|
|
170
|
+
? pause(policy, attempt, draw, remainingMs)
|
|
171
|
+
: fitsBefore(retryAfterMs, remainingMs);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* The timeout for one send, or `undefined` to leave the client's own. Pure.
|
|
175
|
+
*
|
|
176
|
+
* In order: the request's own, the client-wide attempt timeout, then (under a deadline
|
|
177
|
+
* only) the client's timeout, then the time remaining; under a deadline, whichever
|
|
178
|
+
* applies is clamped to what remains.
|
|
179
|
+
*/
|
|
180
|
+
export function attemptTimeout(requestMs, clientAttemptMs, clientMs, remainingMs) {
|
|
181
|
+
const configured = requestMs ?? clientAttemptMs;
|
|
182
|
+
if (remainingMs === undefined) {
|
|
183
|
+
return configured;
|
|
184
|
+
}
|
|
185
|
+
const own = configured ?? clientMs;
|
|
186
|
+
return own === undefined ? remainingMs : Math.min(own, remainingMs);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Whether a failing `status` asks for another send. A status listed in `retryOn` always
|
|
190
|
+
* does, checked first; otherwise an accepted (documented) status never does, and any
|
|
191
|
+
* other does when {@link isRetriableStatus} says so. Pure.
|
|
192
|
+
*/
|
|
193
|
+
export function wantsRetry(status, hasRetryAfter, accepted, retryOn) {
|
|
194
|
+
return retryOn.includes(status) || (!accepted && isRetriableStatus(status, hasRetryAfter));
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Whether a failing status is worth another send: `429`, `502`, `503`, `504`, and `423`
|
|
198
|
+
* only when the server said when to come back. Pure.
|
|
199
|
+
*/
|
|
200
|
+
export function isRetriableStatus(status, hasRetryAfter) {
|
|
201
|
+
switch (status) {
|
|
202
|
+
case 429:
|
|
203
|
+
case 502:
|
|
204
|
+
case 503:
|
|
205
|
+
case 504:
|
|
206
|
+
return true;
|
|
207
|
+
case 423:
|
|
208
|
+
return hasRetryAfter;
|
|
209
|
+
default:
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/** A reason's label, as the Rust SDK's `RetryReason::label` spells it: `"503"`, `"timeout"`. Pure. */
|
|
214
|
+
export function retryLabel(reason) {
|
|
215
|
+
return reason.kind === "status" ? String(reason.status) : reason.kind;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* The pause, if it ends strictly before the deadline `remainingMs` away, or `undefined`
|
|
219
|
+
* when it would reach it: `acton-service-client`'s rule, the boundary included. With no
|
|
220
|
+
* deadline every pause fits. Pure.
|
|
221
|
+
*/
|
|
222
|
+
export function fitsBefore(pauseMs, remainingMs) {
|
|
223
|
+
return remainingMs !== undefined && pauseMs >= remainingMs ? undefined : pauseMs;
|
|
224
|
+
}
|
|
225
|
+
/** The largest `Retry-After` the Rust SDK reads: an unsigned 64-bit second count. */
|
|
226
|
+
const MAX_RETRY_AFTER_SECONDS = 2n ** 64n - 1n;
|
|
227
|
+
/**
|
|
228
|
+
* A `Retry-After` header's delay in milliseconds, or `undefined`. Only the
|
|
229
|
+
* delta-seconds form is read, as Rust parses a `u64`: an optional `+`, then digits. An
|
|
230
|
+
* HTTP date, a fraction, a minus sign or an overflow is ignored. Pure.
|
|
231
|
+
*/
|
|
232
|
+
export function parseRetryAfter(header) {
|
|
233
|
+
if (header === null) {
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
const trimmed = header.trim();
|
|
237
|
+
if (!/^\+?\d+$/.test(trimmed)) {
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
|
240
|
+
const seconds = BigInt(trimmed.replace(/^\+/, ""));
|
|
241
|
+
return seconds > MAX_RETRY_AFTER_SECONDS ? undefined : Number(seconds) * 1_000;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Rejects a policy whose counts and delays are not non-negative whole numbers, whose
|
|
245
|
+
* deadline is not a positive whole number, or whose jitter is not one this SDK knows.
|
|
246
|
+
*
|
|
247
|
+
* @throws {RangeError} Naming the bad field.
|
|
248
|
+
*/
|
|
249
|
+
export function checkRetryPolicy(policy) {
|
|
250
|
+
for (const [name, value] of [
|
|
251
|
+
["maxAttempts", policy.maxAttempts],
|
|
252
|
+
["baseDelayMs", policy.baseDelayMs],
|
|
253
|
+
["maxDelayMs", policy.maxDelayMs],
|
|
254
|
+
]) {
|
|
255
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
256
|
+
throw new RangeError(`${name} must be a non-negative whole number, not ${value}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (policy.deadlineMs !== undefined &&
|
|
260
|
+
(!Number.isSafeInteger(policy.deadlineMs) || policy.deadlineMs <= 0)) {
|
|
261
|
+
throw new RangeError(`deadlineMs must be a positive whole number, not ${policy.deadlineMs}`);
|
|
262
|
+
}
|
|
263
|
+
if (policy.jitter !== undefined && policy.jitter !== "none" && policy.jitter !== "full") {
|
|
264
|
+
throw new RangeError(`jitter must be "none" or "full", not ${String(policy.jitter)}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/** Error codes Node's `fetch` (undici) and its resolver give a connection that never opened. */
|
|
268
|
+
const CONNECT_FAILURE_CODES = new Set([
|
|
269
|
+
"ECONNREFUSED",
|
|
270
|
+
"ENOTFOUND",
|
|
271
|
+
"EAI_AGAIN",
|
|
272
|
+
"EHOSTUNREACH",
|
|
273
|
+
"ENETUNREACH",
|
|
274
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
275
|
+
]);
|
|
276
|
+
/**
|
|
277
|
+
* Whether a `fetch` rejection is a connection that never opened: the counterpart of
|
|
278
|
+
* reqwest's `is_connect`, which is what `acton-service-client` retries (with timeouts).
|
|
279
|
+
* A connection that broke after the request went out is not one. Pure.
|
|
280
|
+
*/
|
|
281
|
+
export function isConnectFailure(error) {
|
|
282
|
+
let current = error;
|
|
283
|
+
for (let depth = 0; depth < 4 && current instanceof Error; depth += 1) {
|
|
284
|
+
const code = current.code;
|
|
285
|
+
if (typeof code === "string" && CONNECT_FAILURE_CODES.has(code)) {
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
current = current.cause;
|
|
289
|
+
}
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
/** `⌊span × draw⌋` with `draw` clamped to `[0, 1)` (NaN as 0), never above `span`. */
|
|
293
|
+
function spread(span, draw) {
|
|
294
|
+
const clamped = Number.isNaN(draw) ? 0 : Math.min(Math.max(draw, 0), DRAW_MAX);
|
|
295
|
+
return Math.min(Math.floor(span * clamped), span);
|
|
296
|
+
}
|
|
297
|
+
//# sourceMappingURL=retry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"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"}
|
package/dist/typeid.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeID encoding — how Axorum spells its identifiers on the wire.
|
|
3
|
+
*
|
|
4
|
+
* Every Axorum id is a {@link https://github.com/jetify-com/typeid | TypeID}: a fixed
|
|
5
|
+
* prefix, an underscore, and the 128 bits of a UUID in Crockford base32 —
|
|
6
|
+
* `txn_7w5b4trnc7b2ja027jyyb64395`. The client needs this for exactly one reason:
|
|
7
|
+
* it mints the transaction id, which is the substrate's idempotency key, and it has
|
|
8
|
+
* to spell it the way the ledger reads it.
|
|
9
|
+
*
|
|
10
|
+
* @packageDocumentation
|
|
11
|
+
*/
|
|
12
|
+
/** The TypeID prefix for a transaction id. */
|
|
13
|
+
export declare const TRANSACTION_PREFIX = "txn";
|
|
14
|
+
/**
|
|
15
|
+
* Encodes the 16 bytes of a UUID as a 26-character Crockford base32 TypeID suffix.
|
|
16
|
+
*
|
|
17
|
+
* A pure function of its input: the same UUID always spells the same suffix, which
|
|
18
|
+
* is what makes an id reconstructible rather than merely unique.
|
|
19
|
+
*
|
|
20
|
+
* @param uuid - A canonical UUID string, e.g. `01890a5d-ac96-774b-bcce-b302099a8057`.
|
|
21
|
+
* @returns The 26-character suffix, e.g. `01h455vb4pex5vsknk084sn02q`.
|
|
22
|
+
* @throws {TypeError} If `uuid` is not 32 hex digits with the usual dashes.
|
|
23
|
+
*/
|
|
24
|
+
export declare function encodeSuffix(uuid: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* The 128 bits a 26-character TypeID suffix encodes, or `undefined` when it is not one.
|
|
27
|
+
* The inverse of {@link encodeSuffix}. Pure.
|
|
28
|
+
*
|
|
29
|
+
* ```ts
|
|
30
|
+
* decodeSuffix(encodeSuffix("01890a5d-ac96-774b-bcce-b302099a8057")); // 0x01890a5d…n
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare function decodeSuffix(suffix: string): bigint | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* Mints a fresh transaction id: a UUID v7 minted at `nowMs`, spelled as a `txn_…`
|
|
36
|
+
* TypeID. The id carries its own mint time ({@link v7MintedAtMs}), which every envelope
|
|
37
|
+
* pinned from it sends as `minted_at_ms`.
|
|
38
|
+
*
|
|
39
|
+
* **Mint it once.** The transaction id is the substrate's idempotency key — every
|
|
40
|
+
* retry of a submission must carry the *same* id, so that an attempt which in fact
|
|
41
|
+
* landed replays its stored outcome instead of posting a second time. This is why
|
|
42
|
+
* {@link AxorumClient.submitPinned} mints the id before its first attempt and never
|
|
43
|
+
* again, and why you should hold on to the id you minted rather than mint another.
|
|
44
|
+
*
|
|
45
|
+
* @param nowMs - The minting time; `Date.now()` when omitted.
|
|
46
|
+
* @returns A new transaction id, e.g. `txn_01m3250v3ve008000000000000`.
|
|
47
|
+
*/
|
|
48
|
+
export declare function newTransactionId(nowMs?: number): string;
|
|
49
|
+
/**
|
|
50
|
+
* RFC 9562 UUID version 7 from a millisecond timestamp and 16 random bytes (of which
|
|
51
|
+
* the first six are replaced by the timestamp). Pure.
|
|
52
|
+
*/
|
|
53
|
+
export declare function uuidV7(unixMs: number, random: Uint8Array): string;
|
|
54
|
+
/**
|
|
55
|
+
* The mint time a UUID v7 transaction id carries: its 48-bit Unix millisecond
|
|
56
|
+
* timestamp. `undefined` for any other version or a malformed id. Pure; the same rule
|
|
57
|
+
* as the Rust SDK's.
|
|
58
|
+
*
|
|
59
|
+
* ```ts
|
|
60
|
+
* v7MintedAtMs(`txn_${encodeSuffix(uuidV7(1_790_000_000_000, new Uint8Array(16)))}`); // 1790000000000
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export declare function v7MintedAtMs(transaction: string): number | undefined;
|
|
64
|
+
//# sourceMappingURL=typeid.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typeid.d.ts","sourceRoot":"","sources":["../src/typeid.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH,8CAA8C;AAC9C,eAAO,MAAM,kBAAkB,QAAQ,CAAC;AAExC;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAgBjD;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAa/D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,GAAE,MAAmB,GAAG,MAAM,CAEnE;AAED;;;GAGG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,MAAM,CAWjE;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAUpE"}
|
package/dist/typeid.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeID encoding — how Axorum spells its identifiers on the wire.
|
|
3
|
+
*
|
|
4
|
+
* Every Axorum id is a {@link https://github.com/jetify-com/typeid | TypeID}: a fixed
|
|
5
|
+
* prefix, an underscore, and the 128 bits of a UUID in Crockford base32 —
|
|
6
|
+
* `txn_7w5b4trnc7b2ja027jyyb64395`. The client needs this for exactly one reason:
|
|
7
|
+
* it mints the transaction id, which is the substrate's idempotency key, and it has
|
|
8
|
+
* to spell it the way the ledger reads it.
|
|
9
|
+
*
|
|
10
|
+
* @packageDocumentation
|
|
11
|
+
*/
|
|
12
|
+
/** Crockford's base32 alphabet, lowercase: no `i`, `l`, `o`, or `u`. */
|
|
13
|
+
const ALPHABET = "0123456789abcdefghjkmnpqrstvwxyz";
|
|
14
|
+
/** The number of base32 characters 128 bits encodes to (26 × 5 = 130 ≥ 128). */
|
|
15
|
+
const SUFFIX_LENGTH = 26;
|
|
16
|
+
/** The TypeID prefix for a transaction id. */
|
|
17
|
+
export const TRANSACTION_PREFIX = "txn";
|
|
18
|
+
/**
|
|
19
|
+
* Encodes the 16 bytes of a UUID as a 26-character Crockford base32 TypeID suffix.
|
|
20
|
+
*
|
|
21
|
+
* A pure function of its input: the same UUID always spells the same suffix, which
|
|
22
|
+
* is what makes an id reconstructible rather than merely unique.
|
|
23
|
+
*
|
|
24
|
+
* @param uuid - A canonical UUID string, e.g. `01890a5d-ac96-774b-bcce-b302099a8057`.
|
|
25
|
+
* @returns The 26-character suffix, e.g. `01h455vb4pex5vsknk084sn02q`.
|
|
26
|
+
* @throws {TypeError} If `uuid` is not 32 hex digits with the usual dashes.
|
|
27
|
+
*/
|
|
28
|
+
export function encodeSuffix(uuid) {
|
|
29
|
+
const hex = uuid.replaceAll("-", "").toLowerCase();
|
|
30
|
+
if (hex.length !== 32 || !/^[0-9a-f]{32}$/.test(hex)) {
|
|
31
|
+
throw new TypeError(`'${uuid}' is not a UUID`);
|
|
32
|
+
}
|
|
33
|
+
// 128 bits as one integer, then 26 five-bit digits, most significant first. The
|
|
34
|
+
// two bits of headroom land in the leading digit, which is why a TypeID suffix
|
|
35
|
+
// always starts in `0`–`7`.
|
|
36
|
+
let value = BigInt(`0x${hex}`);
|
|
37
|
+
const digits = new Array(SUFFIX_LENGTH);
|
|
38
|
+
for (let index = SUFFIX_LENGTH - 1; index >= 0; index -= 1) {
|
|
39
|
+
digits[index] = ALPHABET[Number(value & 31n)];
|
|
40
|
+
value >>= 5n;
|
|
41
|
+
}
|
|
42
|
+
return digits.join("");
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The 128 bits a 26-character TypeID suffix encodes, or `undefined` when it is not one.
|
|
46
|
+
* The inverse of {@link encodeSuffix}. Pure.
|
|
47
|
+
*
|
|
48
|
+
* ```ts
|
|
49
|
+
* decodeSuffix(encodeSuffix("01890a5d-ac96-774b-bcce-b302099a8057")); // 0x01890a5d…n
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export function decodeSuffix(suffix) {
|
|
53
|
+
if (suffix.length !== SUFFIX_LENGTH || suffix[0] === undefined || suffix[0] > "7") {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
let value = 0n;
|
|
57
|
+
for (const digit of suffix) {
|
|
58
|
+
const index = ALPHABET.indexOf(digit);
|
|
59
|
+
if (index < 0) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
value = (value << 5n) | BigInt(index);
|
|
63
|
+
}
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Mints a fresh transaction id: a UUID v7 minted at `nowMs`, spelled as a `txn_…`
|
|
68
|
+
* TypeID. The id carries its own mint time ({@link v7MintedAtMs}), which every envelope
|
|
69
|
+
* pinned from it sends as `minted_at_ms`.
|
|
70
|
+
*
|
|
71
|
+
* **Mint it once.** The transaction id is the substrate's idempotency key — every
|
|
72
|
+
* retry of a submission must carry the *same* id, so that an attempt which in fact
|
|
73
|
+
* landed replays its stored outcome instead of posting a second time. This is why
|
|
74
|
+
* {@link AxorumClient.submitPinned} mints the id before its first attempt and never
|
|
75
|
+
* again, and why you should hold on to the id you minted rather than mint another.
|
|
76
|
+
*
|
|
77
|
+
* @param nowMs - The minting time; `Date.now()` when omitted.
|
|
78
|
+
* @returns A new transaction id, e.g. `txn_01m3250v3ve008000000000000`.
|
|
79
|
+
*/
|
|
80
|
+
export function newTransactionId(nowMs = Date.now()) {
|
|
81
|
+
return `${TRANSACTION_PREFIX}_${encodeSuffix(uuidV7(nowMs, crypto.getRandomValues(new Uint8Array(16))))}`;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* RFC 9562 UUID version 7 from a millisecond timestamp and 16 random bytes (of which
|
|
85
|
+
* the first six are replaced by the timestamp). Pure.
|
|
86
|
+
*/
|
|
87
|
+
export function uuidV7(unixMs, random) {
|
|
88
|
+
const bytes = Uint8Array.from(random.subarray(0, 16));
|
|
89
|
+
let time = BigInt(unixMs);
|
|
90
|
+
for (let index = 5; index >= 0; index -= 1) {
|
|
91
|
+
bytes[index] = Number(time & 0xffn);
|
|
92
|
+
time >>= 8n;
|
|
93
|
+
}
|
|
94
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x70;
|
|
95
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
96
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
97
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The mint time a UUID v7 transaction id carries: its 48-bit Unix millisecond
|
|
101
|
+
* timestamp. `undefined` for any other version or a malformed id. Pure; the same rule
|
|
102
|
+
* as the Rust SDK's.
|
|
103
|
+
*
|
|
104
|
+
* ```ts
|
|
105
|
+
* v7MintedAtMs(`txn_${encodeSuffix(uuidV7(1_790_000_000_000, new Uint8Array(16)))}`); // 1790000000000
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
export function v7MintedAtMs(transaction) {
|
|
109
|
+
const prefix = `${TRANSACTION_PREFIX}_`;
|
|
110
|
+
if (!transaction.startsWith(prefix)) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
const bits = decodeSuffix(transaction.slice(prefix.length));
|
|
114
|
+
if (bits === undefined || ((bits >> 76n) & 0xfn) !== 7n) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
return Number(bits >> 80n);
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=typeid.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typeid.js","sourceRoot":"","sources":["../src/typeid.ts"],"names":[],"mappings":"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"}
|