@qubic.ts/sdk 0.1.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/package.json +38 -0
- package/src/assets.test.ts +114 -0
- package/src/assets.ts +175 -0
- package/src/bob/client.test.ts +109 -0
- package/src/bob/client.ts +393 -0
- package/src/bob/log-stream.test.ts +55 -0
- package/src/bob/log-stream.ts +241 -0
- package/src/browser.ts +43 -0
- package/src/contracts.test.ts +90 -0
- package/src/contracts.ts +140 -0
- package/src/errors.ts +15 -0
- package/src/http.ts +1 -0
- package/src/index.ts +141 -0
- package/src/node.ts +1 -0
- package/src/retry.ts +61 -0
- package/src/rpc/client.test.ts +322 -0
- package/src/rpc/client.ts +688 -0
- package/src/sdk.test.ts +34 -0
- package/src/sdk.ts +113 -0
- package/src/tick.test.ts +69 -0
- package/src/tick.ts +47 -0
- package/src/transactions.queue.test.ts +102 -0
- package/src/transactions.test.ts +149 -0
- package/src/transactions.ts +234 -0
- package/src/transfers.test.ts +59 -0
- package/src/transfers.ts +132 -0
- package/src/tx/confirm.test.ts +149 -0
- package/src/tx/confirm.ts +147 -0
- package/src/tx/tx-queue.test.ts +146 -0
- package/src/tx/tx-queue.ts +214 -0
- package/src/tx/tx.ts +36 -0
- package/src/vault/types.ts +131 -0
- package/src/vault-browser.test.ts +77 -0
- package/src/vault-browser.ts +449 -0
- package/src/vault-cli.test.ts +63 -0
- package/src/vault-cli.ts +123 -0
- package/src/vault.test.ts +97 -0
- package/src/vault.ts +439 -0
package/src/browser.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export type { ContractsHelpers, QueryRawInput, QueryRawResult } from "./contracts.js";
|
|
2
|
+
export type { SdkConfig } from "./sdk.js";
|
|
3
|
+
export { createSdk } from "./sdk.js";
|
|
4
|
+
export type {
|
|
5
|
+
LastProcessedTick,
|
|
6
|
+
LiveBalance,
|
|
7
|
+
QueryTransaction,
|
|
8
|
+
TickInfo,
|
|
9
|
+
TransactionsForIdentityRequest,
|
|
10
|
+
TransactionsForIdentityResponse,
|
|
11
|
+
} from "./rpc/client.js";
|
|
12
|
+
export type { SeedSourceInput, SendTransactionResult } from "./transactions.js";
|
|
13
|
+
export type {
|
|
14
|
+
BuildSignedTransferInput,
|
|
15
|
+
SendAndConfirmInput,
|
|
16
|
+
SendTransferResult,
|
|
17
|
+
} from "./transfers.js";
|
|
18
|
+
export type {
|
|
19
|
+
OpenSeedVaultBrowserInput,
|
|
20
|
+
VaultStore,
|
|
21
|
+
} from "./vault-browser.js";
|
|
22
|
+
export {
|
|
23
|
+
createLocalStorageVaultStore,
|
|
24
|
+
createMemoryVaultStore,
|
|
25
|
+
openSeedVaultBrowser,
|
|
26
|
+
} from "./vault-browser.js";
|
|
27
|
+
export type {
|
|
28
|
+
OpenSeedVaultInput,
|
|
29
|
+
SeedVault,
|
|
30
|
+
VaultEntry,
|
|
31
|
+
VaultEntryEncrypted,
|
|
32
|
+
VaultExport,
|
|
33
|
+
VaultHeader,
|
|
34
|
+
VaultKdfParams,
|
|
35
|
+
VaultSummary,
|
|
36
|
+
} from "./vault/types.js";
|
|
37
|
+
export {
|
|
38
|
+
VaultEntryExistsError,
|
|
39
|
+
VaultEntryNotFoundError,
|
|
40
|
+
VaultError,
|
|
41
|
+
VaultInvalidPassphraseError,
|
|
42
|
+
VaultNotFoundError,
|
|
43
|
+
} from "./vault/types.js";
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { ContractQueryAbortedError, createContractHelpers } from "./contracts.js";
|
|
3
|
+
import type { FetchLike } from "./http.js";
|
|
4
|
+
import { createRpcClient } from "./rpc/client.js";
|
|
5
|
+
|
|
6
|
+
function createQueryFetch(responses: Uint8Array[]) {
|
|
7
|
+
let calls = 0;
|
|
8
|
+
const history = { calls: 0 };
|
|
9
|
+
const fetch: FetchLike = async (...args) => {
|
|
10
|
+
const url = new URL(getUrl(args[0]));
|
|
11
|
+
const method = getMethod(args[0], args[1]);
|
|
12
|
+
if (method === "POST" && url.pathname === "/live/v1/querySmartContract") {
|
|
13
|
+
const response = responses[Math.min(calls, responses.length - 1)] ?? new Uint8Array();
|
|
14
|
+
calls++;
|
|
15
|
+
history.calls = calls;
|
|
16
|
+
return Response.json({
|
|
17
|
+
responseData: Buffer.from(response).toString("base64"),
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return new Response("not found", { status: 404 });
|
|
21
|
+
};
|
|
22
|
+
return { fetch, history };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe("contracts.queryRaw", () => {
|
|
26
|
+
it("retries when response is shorter than expected", async () => {
|
|
27
|
+
const { fetch, history } = createQueryFetch([new Uint8Array(), new Uint8Array([1, 2, 3, 4])]);
|
|
28
|
+
const rpc = createRpcClient({ baseUrl: "https://example.test", fetch });
|
|
29
|
+
const contracts = createContractHelpers({ rpc, defaultRetries: 2, defaultRetryDelayMs: 1 });
|
|
30
|
+
|
|
31
|
+
const res = await contracts.queryRaw({
|
|
32
|
+
contractIndex: 1,
|
|
33
|
+
inputType: 1,
|
|
34
|
+
inputBytes: new Uint8Array(),
|
|
35
|
+
expectedOutputSize: 4,
|
|
36
|
+
retryDelayMs: 1,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
expect(res.responseBytes.length).toBe(4);
|
|
40
|
+
expect(res.attempts).toBe(2);
|
|
41
|
+
expect(history.calls).toBe(2);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("returns immediately when no expected size is provided", async () => {
|
|
45
|
+
const { fetch, history } = createQueryFetch([new Uint8Array([9])]);
|
|
46
|
+
const rpc = createRpcClient({ baseUrl: "https://example.test", fetch });
|
|
47
|
+
const contracts = createContractHelpers({ rpc });
|
|
48
|
+
|
|
49
|
+
const res = await contracts.queryRaw({
|
|
50
|
+
contractIndex: 1,
|
|
51
|
+
inputType: 1,
|
|
52
|
+
inputBytes: new Uint8Array(),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
expect(res.responseBytes.length).toBe(1);
|
|
56
|
+
expect(res.attempts).toBe(1);
|
|
57
|
+
expect(history.calls).toBe(1);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("throws when the input signal is already aborted", async () => {
|
|
61
|
+
const { fetch, history } = createQueryFetch([new Uint8Array([1])]);
|
|
62
|
+
const rpc = createRpcClient({ baseUrl: "https://example.test", fetch });
|
|
63
|
+
const contracts = createContractHelpers({ rpc, defaultRetries: 2, defaultRetryDelayMs: 10 });
|
|
64
|
+
const controller = new AbortController();
|
|
65
|
+
controller.abort();
|
|
66
|
+
|
|
67
|
+
await expect(
|
|
68
|
+
contracts.queryRaw({
|
|
69
|
+
contractIndex: 1,
|
|
70
|
+
inputType: 1,
|
|
71
|
+
inputBytes: new Uint8Array(),
|
|
72
|
+
expectedOutputSize: 4,
|
|
73
|
+
signal: controller.signal,
|
|
74
|
+
}),
|
|
75
|
+
).rejects.toBeInstanceOf(ContractQueryAbortedError);
|
|
76
|
+
expect(history.calls).toBe(0);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
function getUrl(input: Parameters<typeof fetch>[0]): string {
|
|
81
|
+
if (typeof input === "string") return input;
|
|
82
|
+
if (input instanceof URL) return input.toString();
|
|
83
|
+
return input.url;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getMethod(input: Parameters<typeof fetch>[0], init: Parameters<typeof fetch>[1]): string {
|
|
87
|
+
if (init?.method) return init.method;
|
|
88
|
+
if (input instanceof Request) return input.method;
|
|
89
|
+
return "GET";
|
|
90
|
+
}
|
package/src/contracts.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { RpcClient } from "./rpc/client.js";
|
|
2
|
+
|
|
3
|
+
export type ContractsHelpersConfig = Readonly<{
|
|
4
|
+
rpc: RpcClient;
|
|
5
|
+
defaultRetries?: number;
|
|
6
|
+
defaultRetryDelayMs?: number;
|
|
7
|
+
}>;
|
|
8
|
+
|
|
9
|
+
export type QueryRawInput = Readonly<{
|
|
10
|
+
contractIndex: bigint | number;
|
|
11
|
+
inputType: bigint | number;
|
|
12
|
+
inputBytes?: Uint8Array;
|
|
13
|
+
inputBase64?: string;
|
|
14
|
+
/** If provided, retries when response bytes are shorter than this value. */
|
|
15
|
+
expectedOutputSize?: number;
|
|
16
|
+
retries?: number;
|
|
17
|
+
retryDelayMs?: number;
|
|
18
|
+
signal?: AbortSignal;
|
|
19
|
+
}>;
|
|
20
|
+
|
|
21
|
+
export type QueryRawResult = Readonly<{
|
|
22
|
+
responseBytes: Uint8Array;
|
|
23
|
+
responseBase64: string;
|
|
24
|
+
attempts: number;
|
|
25
|
+
}>;
|
|
26
|
+
|
|
27
|
+
export type ContractsHelpers = Readonly<{
|
|
28
|
+
queryRaw(input: QueryRawInput): Promise<QueryRawResult>;
|
|
29
|
+
querySmartContract(input: {
|
|
30
|
+
contractIndex: bigint | number;
|
|
31
|
+
inputType: bigint | number;
|
|
32
|
+
input: Uint8Array | string;
|
|
33
|
+
}): Promise<{ responseBytes: Uint8Array; responseBase64: string }>;
|
|
34
|
+
}>;
|
|
35
|
+
|
|
36
|
+
export class ContractQueryAbortedError extends Error {
|
|
37
|
+
override name = "ContractQueryAbortedError";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function createContractHelpers(config: ContractsHelpersConfig): ContractsHelpers {
|
|
41
|
+
const defaultRetries = config.defaultRetries ?? 0;
|
|
42
|
+
const defaultRetryDelayMs = config.defaultRetryDelayMs ?? 1_000;
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
async querySmartContract(
|
|
46
|
+
input,
|
|
47
|
+
): Promise<{ responseBytes: Uint8Array; responseBase64: string }> {
|
|
48
|
+
return config.rpc.live.querySmartContract(input);
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
async queryRaw(input: QueryRawInput): Promise<QueryRawResult> {
|
|
52
|
+
const retries = input.retries ?? defaultRetries;
|
|
53
|
+
const retryDelayMs = input.retryDelayMs ?? defaultRetryDelayMs;
|
|
54
|
+
const expectedOutputSize = input.expectedOutputSize;
|
|
55
|
+
const inputBytes =
|
|
56
|
+
input.inputBytes ?? (input.inputBase64 ? decodeBase64(input.inputBase64) : undefined);
|
|
57
|
+
if (!inputBytes) {
|
|
58
|
+
throw new TypeError("queryRaw requires inputBytes or inputBase64");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const controller = new AbortController();
|
|
62
|
+
const signals: AbortSignal[] = [controller.signal];
|
|
63
|
+
if (input.signal) signals.push(input.signal);
|
|
64
|
+
const signal = anySignal(signals);
|
|
65
|
+
|
|
66
|
+
let attempts = 0;
|
|
67
|
+
while (true) {
|
|
68
|
+
if (signal.aborted) throw new ContractQueryAbortedError("Contract query aborted");
|
|
69
|
+
attempts++;
|
|
70
|
+
|
|
71
|
+
const res = await config.rpc.live.querySmartContract({
|
|
72
|
+
contractIndex: input.contractIndex,
|
|
73
|
+
inputType: input.inputType,
|
|
74
|
+
input: inputBytes,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const shortResponse =
|
|
78
|
+
typeof expectedOutputSize === "number" &&
|
|
79
|
+
Number.isFinite(expectedOutputSize) &&
|
|
80
|
+
expectedOutputSize > 0 &&
|
|
81
|
+
res.responseBytes.length < expectedOutputSize;
|
|
82
|
+
|
|
83
|
+
if (!shortResponse || attempts > retries) {
|
|
84
|
+
return { ...res, attempts };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
await sleep(retryDelayMs, signal);
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function sleep(ms: number, signal: AbortSignal): Promise<void> {
|
|
94
|
+
if (ms <= 0) return Promise.resolve();
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
96
|
+
const id = setTimeout(() => {
|
|
97
|
+
cleanup();
|
|
98
|
+
resolve();
|
|
99
|
+
}, ms);
|
|
100
|
+
|
|
101
|
+
const onAbort = () => {
|
|
102
|
+
cleanup();
|
|
103
|
+
reject(new ContractQueryAbortedError("Contract query aborted"));
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const cleanup = () => {
|
|
107
|
+
clearTimeout(id);
|
|
108
|
+
signal.removeEventListener("abort", onAbort);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function anySignal(signals: readonly AbortSignal[]): AbortSignal {
|
|
116
|
+
if (signals.length === 0) return new AbortController().signal;
|
|
117
|
+
if (signals.length === 1) {
|
|
118
|
+
const only = signals[0];
|
|
119
|
+
if (!only) return new AbortController().signal;
|
|
120
|
+
return only;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const controller = new AbortController();
|
|
124
|
+
const onAbort = () => controller.abort();
|
|
125
|
+
for (const s of signals) {
|
|
126
|
+
if (s.aborted) return s;
|
|
127
|
+
s.addEventListener("abort", onAbort, { once: true });
|
|
128
|
+
}
|
|
129
|
+
return controller.signal;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function decodeBase64(value: string): Uint8Array {
|
|
133
|
+
if (typeof atob === "function") {
|
|
134
|
+
const bin = atob(value);
|
|
135
|
+
const bytes = new Uint8Array(bin.length);
|
|
136
|
+
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
137
|
+
return bytes;
|
|
138
|
+
}
|
|
139
|
+
throw new Error("Base64 decoding is not available in this runtime");
|
|
140
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type ErrorContext = Readonly<Record<string, unknown>>;
|
|
2
|
+
|
|
3
|
+
export class SdkError extends Error {
|
|
4
|
+
readonly code: string;
|
|
5
|
+
readonly context?: ErrorContext;
|
|
6
|
+
override readonly cause?: unknown;
|
|
7
|
+
|
|
8
|
+
constructor(code: string, message: string, context?: ErrorContext, cause?: unknown) {
|
|
9
|
+
super(message, { cause });
|
|
10
|
+
this.name = "SdkError";
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.context = context;
|
|
13
|
+
this.cause = cause;
|
|
14
|
+
}
|
|
15
|
+
}
|
package/src/http.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
export type { ErrorContext } from "./errors.js";
|
|
2
|
+
export { SdkError } from "./errors.js";
|
|
3
|
+
export type {
|
|
4
|
+
AssetsHelpers,
|
|
5
|
+
AssetsHelpersConfig,
|
|
6
|
+
AssetsQueryInput,
|
|
7
|
+
AssetsRequestFn,
|
|
8
|
+
ListIssuedInput,
|
|
9
|
+
ListOwnedInput,
|
|
10
|
+
ListPossessedInput,
|
|
11
|
+
} from "./assets.js";
|
|
12
|
+
export {
|
|
13
|
+
createAssetsHelpers,
|
|
14
|
+
isIssuanceAsset,
|
|
15
|
+
isOwnershipAsset,
|
|
16
|
+
isPossessionAsset,
|
|
17
|
+
} from "./assets.js";
|
|
18
|
+
export type {
|
|
19
|
+
BobClient,
|
|
20
|
+
BobClientConfig,
|
|
21
|
+
BobQuerySmartContractInput,
|
|
22
|
+
BobQuerySmartContractResult,
|
|
23
|
+
} from "./bob/client.js";
|
|
24
|
+
export { BobError, createBobClient } from "./bob/client.js";
|
|
25
|
+
export type {
|
|
26
|
+
CloseEventLike,
|
|
27
|
+
EventLike,
|
|
28
|
+
LogCursor,
|
|
29
|
+
LogCursorStore,
|
|
30
|
+
LogStream,
|
|
31
|
+
LogStreamConfig,
|
|
32
|
+
LogStreamHandlers,
|
|
33
|
+
LogSubscription,
|
|
34
|
+
MessageEventLike,
|
|
35
|
+
WebSocketLike,
|
|
36
|
+
} from "./bob/log-stream.js";
|
|
37
|
+
export { createLogStream } from "./bob/log-stream.js";
|
|
38
|
+
export type {
|
|
39
|
+
ContractsHelpers,
|
|
40
|
+
ContractsHelpersConfig,
|
|
41
|
+
QueryRawInput,
|
|
42
|
+
QueryRawResult,
|
|
43
|
+
} from "./contracts.js";
|
|
44
|
+
export { ContractQueryAbortedError, createContractHelpers } from "./contracts.js";
|
|
45
|
+
export type { FetchLike } from "./http.js";
|
|
46
|
+
export type { RetryConfig, RetryConfigNormalized } from "./retry.js";
|
|
47
|
+
export { normalizeRetryConfig, withRetry } from "./retry.js";
|
|
48
|
+
export type { SdkConfig } from "./sdk.js";
|
|
49
|
+
export { createSdk } from "./sdk.js";
|
|
50
|
+
export type {
|
|
51
|
+
BroadcastTransactionResult,
|
|
52
|
+
ComputorList,
|
|
53
|
+
Hits,
|
|
54
|
+
LastProcessedTick,
|
|
55
|
+
LiveBalance,
|
|
56
|
+
Pagination,
|
|
57
|
+
ProcessedTickInterval,
|
|
58
|
+
QueryTransaction,
|
|
59
|
+
Range,
|
|
60
|
+
RpcClient,
|
|
61
|
+
RpcClientConfig,
|
|
62
|
+
TickData,
|
|
63
|
+
TickInfo,
|
|
64
|
+
TransactionsForIdentityPagingInput,
|
|
65
|
+
TransactionsForIdentityRequest,
|
|
66
|
+
TransactionsForIdentityResponse,
|
|
67
|
+
} from "./rpc/client.js";
|
|
68
|
+
export { createRpcClient, RpcError } from "./rpc/client.js";
|
|
69
|
+
export type { SuggestedTargetTickInput, TickHelpers, TickHelpersConfig } from "./tick.js";
|
|
70
|
+
export { createTickHelpers } from "./tick.js";
|
|
71
|
+
export type {
|
|
72
|
+
TxConfirmationHelpers,
|
|
73
|
+
TxConfirmationHelpersConfig,
|
|
74
|
+
WaitForConfirmationInput,
|
|
75
|
+
} from "./tx/confirm.js";
|
|
76
|
+
export {
|
|
77
|
+
createTxConfirmationHelpers,
|
|
78
|
+
TxConfirmationAbortedError,
|
|
79
|
+
TxConfirmationTimeoutError,
|
|
80
|
+
TxNotFoundError,
|
|
81
|
+
} from "./tx/confirm.js";
|
|
82
|
+
export type { TxHelpers, TxHelpersConfig } from "./tx/tx.js";
|
|
83
|
+
export { createTxHelpers } from "./tx/tx.js";
|
|
84
|
+
export type {
|
|
85
|
+
EnqueueTxInput,
|
|
86
|
+
TxQueueConfirmFn,
|
|
87
|
+
TxQueueConfig,
|
|
88
|
+
TxQueueItem,
|
|
89
|
+
TxQueueItemStatus,
|
|
90
|
+
TxQueuePolicy,
|
|
91
|
+
} from "./tx/tx-queue.js";
|
|
92
|
+
export { TxQueue, TxQueueError } from "./tx/tx-queue.js";
|
|
93
|
+
export type {
|
|
94
|
+
BuildSignedTransactionInput,
|
|
95
|
+
BuiltTransaction,
|
|
96
|
+
SeedSourceInput,
|
|
97
|
+
SendAndConfirmTransactionInput,
|
|
98
|
+
SendTransactionReceipt,
|
|
99
|
+
SendTransactionResult,
|
|
100
|
+
TransactionHelpers,
|
|
101
|
+
TransactionHelpersConfig,
|
|
102
|
+
} from "./transactions.js";
|
|
103
|
+
export { createTransactionHelpers, QueuedTransactionError } from "./transactions.js";
|
|
104
|
+
export type {
|
|
105
|
+
BuildSignedTransferInput,
|
|
106
|
+
SendAndConfirmInput,
|
|
107
|
+
SendTransferReceipt,
|
|
108
|
+
SendTransferResult,
|
|
109
|
+
SignedTransfer,
|
|
110
|
+
TransferHelpers,
|
|
111
|
+
TransferHelpersConfig,
|
|
112
|
+
} from "./transfers.js";
|
|
113
|
+
export { createTransferHelpers } from "./transfers.js";
|
|
114
|
+
export type {
|
|
115
|
+
OpenSeedVaultInput,
|
|
116
|
+
Pbkdf2Hash,
|
|
117
|
+
Pbkdf2KdfParams,
|
|
118
|
+
ScryptKdfParams,
|
|
119
|
+
SeedVault,
|
|
120
|
+
VaultEntry,
|
|
121
|
+
VaultEntryEncrypted,
|
|
122
|
+
VaultExport,
|
|
123
|
+
VaultHeader,
|
|
124
|
+
VaultKdf,
|
|
125
|
+
VaultKdfParams,
|
|
126
|
+
VaultSummary,
|
|
127
|
+
} from "./vault/types.js";
|
|
128
|
+
export {
|
|
129
|
+
VaultEntryExistsError,
|
|
130
|
+
VaultEntryNotFoundError,
|
|
131
|
+
VaultError,
|
|
132
|
+
VaultInvalidPassphraseError,
|
|
133
|
+
VaultNotFoundError,
|
|
134
|
+
} from "./vault/types.js";
|
|
135
|
+
export { openSeedVault, vaultExists } from "./vault.js";
|
|
136
|
+
export type { OpenSeedVaultBrowserInput, VaultStore } from "./vault-browser.js";
|
|
137
|
+
export {
|
|
138
|
+
createLocalStorageVaultStore,
|
|
139
|
+
createMemoryVaultStore,
|
|
140
|
+
openSeedVaultBrowser,
|
|
141
|
+
} from "./vault-browser.js";
|
package/src/node.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./index.js";
|
package/src/retry.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export type RetryConfig = Readonly<{
|
|
2
|
+
maxRetries?: number;
|
|
3
|
+
baseDelayMs?: number;
|
|
4
|
+
maxDelayMs?: number;
|
|
5
|
+
jitterMs?: number;
|
|
6
|
+
retryOnStatuses?: readonly number[];
|
|
7
|
+
retryOnMethods?: readonly string[];
|
|
8
|
+
}>;
|
|
9
|
+
|
|
10
|
+
export type RetryConfigNormalized = Readonly<{
|
|
11
|
+
maxRetries: number;
|
|
12
|
+
baseDelayMs: number;
|
|
13
|
+
maxDelayMs: number;
|
|
14
|
+
jitterMs: number;
|
|
15
|
+
retryOnStatuses: readonly number[];
|
|
16
|
+
retryOnMethods: readonly string[];
|
|
17
|
+
}>;
|
|
18
|
+
|
|
19
|
+
const DEFAULT_RETRY_STATUSES = [408, 429, 500, 502, 503, 504] as const;
|
|
20
|
+
|
|
21
|
+
export function normalizeRetryConfig(input?: RetryConfig): RetryConfigNormalized {
|
|
22
|
+
return {
|
|
23
|
+
maxRetries: input?.maxRetries ?? 0,
|
|
24
|
+
baseDelayMs: input?.baseDelayMs ?? 250,
|
|
25
|
+
maxDelayMs: input?.maxDelayMs ?? 2_000,
|
|
26
|
+
jitterMs: input?.jitterMs ?? 100,
|
|
27
|
+
retryOnStatuses: input?.retryOnStatuses ?? DEFAULT_RETRY_STATUSES,
|
|
28
|
+
retryOnMethods: input?.retryOnMethods ?? ["GET", "POST"],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function withRetry<T>(
|
|
33
|
+
config: RetryConfigNormalized,
|
|
34
|
+
method: string,
|
|
35
|
+
run: () => Promise<T>,
|
|
36
|
+
shouldRetry: (error: unknown) => boolean,
|
|
37
|
+
): Promise<T> {
|
|
38
|
+
let attempt = 0;
|
|
39
|
+
while (true) {
|
|
40
|
+
try {
|
|
41
|
+
return await run();
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (attempt >= config.maxRetries) throw error;
|
|
44
|
+
if (!config.retryOnMethods.includes(method)) throw error;
|
|
45
|
+
if (!shouldRetry(error)) throw error;
|
|
46
|
+
const delay = computeDelay(config, attempt);
|
|
47
|
+
await sleep(delay);
|
|
48
|
+
attempt += 1;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function computeDelay(config: RetryConfigNormalized, attempt: number): number {
|
|
54
|
+
const base = Math.min(config.baseDelayMs * 2 ** attempt, config.maxDelayMs);
|
|
55
|
+
const jitter = config.jitterMs > 0 ? Math.floor(Math.random() * config.jitterMs) : 0;
|
|
56
|
+
return base + jitter;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function sleep(ms: number): Promise<void> {
|
|
60
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
61
|
+
}
|