@parity/product-sdk-host 0.12.0 → 0.14.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/dist/chunk-3SWF5CWC.js +49 -0
- package/dist/chunk-3SWF5CWC.js.map +1 -0
- package/dist/index.d.ts +74 -80
- package/dist/index.js +37 -50
- package/dist/index.js.map +1 -1
- package/dist/testing.d.ts +81 -0
- package/dist/testing.js +158 -0
- package/dist/testing.js.map +1 -0
- package/dist/transport-B0cdhwrp.d.ts +31 -0
- package/package.json +11 -4
- package/src/accounts.ts +101 -37
- package/src/chains.ts +7 -7
- package/src/errors.ts +84 -43
- package/src/index.ts +3 -0
- package/src/payments.ts +8 -7
- package/src/result.ts +5 -48
- package/src/testing.ts +397 -0
- package/src/transport.ts +91 -3
package/src/errors.ts
CHANGED
|
@@ -17,38 +17,44 @@
|
|
|
17
17
|
*
|
|
18
18
|
* @module
|
|
19
19
|
*/
|
|
20
|
-
import type {
|
|
20
|
+
import type { SdkError } from "@parity/product-sdk-errors";
|
|
21
|
+
import type { scale } from "@parity/truapi";
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* members with that import so the type is protocol-sourced rather than
|
|
36
|
-
* hand-widened.
|
|
24
|
+
* What a `Domain`-tagged call error carries. Widened from truapi's per-domain
|
|
25
|
+
* `Versioned*Error` types (all `{ tag: "V1", value: <domain error> }` today)
|
|
26
|
+
* so one payload type covers every call.
|
|
27
|
+
*/
|
|
28
|
+
type VersionedDomainError = { tag: string; value?: unknown };
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The error a host call puts on its `Err` channel — truapi's canonical
|
|
32
|
+
* {@link scale.CallErrorValue} envelope. `Denied` / `Unsupported` /
|
|
33
|
+
* `MalformedFrame` / `HostFailure` are transport-level failures; `Domain`
|
|
34
|
+
* wraps the actual per-domain error in a versioned envelope, which
|
|
35
|
+
* {@link formatHostError} digs through when rendering.
|
|
37
36
|
*
|
|
38
|
-
* This is the
|
|
39
|
-
*
|
|
40
|
-
* the error type consumers branch on.
|
|
37
|
+
* This is the payload {@link HostCallFailedError} carries — not the error
|
|
38
|
+
* type consumers branch on.
|
|
41
39
|
*/
|
|
42
|
-
export type HostErrorPayload =
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
40
|
+
export type HostErrorPayload = scale.CallErrorValue<VersionedDomainError>;
|
|
41
|
+
|
|
42
|
+
/** Narrow to a tagged-union member: `{ tag, value? }`. */
|
|
43
|
+
function isTagged(value: unknown): value is { tag: string; value?: unknown } {
|
|
44
|
+
return (
|
|
45
|
+
value != null &&
|
|
46
|
+
typeof value === "object" &&
|
|
47
|
+
typeof (value as { tag?: unknown }).tag === "string"
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Narrow to a reason-carrying payload — truapi's `GenericError` shape. */
|
|
52
|
+
function hasReason(value: unknown): value is { reason: string } {
|
|
53
|
+
return (
|
|
54
|
+
value != null &&
|
|
55
|
+
typeof value === "object" &&
|
|
56
|
+
typeof (value as { reason?: unknown }).reason === "string"
|
|
57
|
+
);
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
/**
|
|
@@ -66,16 +72,20 @@ export function formatHostError(error: unknown): string {
|
|
|
66
72
|
if (error instanceof Error) return error.message;
|
|
67
73
|
if (typeof error === "string") return error;
|
|
68
74
|
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return error.tag
|
|
75
|
+
if (isTagged(error)) {
|
|
76
|
+
// `Domain` carries the real error inside a versioned envelope — unwrap it.
|
|
77
|
+
if (error.tag === "Domain" && isTagged(error.value) && error.value.value !== undefined) {
|
|
78
|
+
return formatHostError(error.value.value);
|
|
79
|
+
}
|
|
80
|
+
// Tagged variant carrying a reason: { tag, value: { reason } }
|
|
81
|
+
if (hasReason(error.value)) {
|
|
82
|
+
return `${error.tag}: ${error.value.reason}`;
|
|
77
83
|
}
|
|
78
|
-
//
|
|
84
|
+
// Unit tagged variant, e.g. { tag: "Denied" } / { tag: "PermissionDenied" }
|
|
85
|
+
return error.tag;
|
|
86
|
+
}
|
|
87
|
+
// GenericError: { reason }
|
|
88
|
+
if (hasReason(error)) {
|
|
79
89
|
return error.reason;
|
|
80
90
|
}
|
|
81
91
|
|
|
@@ -93,9 +103,13 @@ export function formatHostError(error: unknown): string {
|
|
|
93
103
|
|
|
94
104
|
/**
|
|
95
105
|
* Base class for all host errors. Use `instanceof HostError` (or {@link isHostError})
|
|
96
|
-
* to catch any host-related failure.
|
|
106
|
+
* to catch any host-related failure. Implements the cross-package
|
|
107
|
+
* {@link SdkError} marker so `isSdkError(e)` also recognizes it.
|
|
97
108
|
*/
|
|
98
|
-
export class HostError extends Error {
|
|
109
|
+
export class HostError extends Error implements SdkError {
|
|
110
|
+
readonly isSdkError = true as const;
|
|
111
|
+
readonly source = "host";
|
|
112
|
+
|
|
99
113
|
constructor(message: string, options?: ErrorOptions) {
|
|
100
114
|
super(message, options);
|
|
101
115
|
this.name = "HostError";
|
|
@@ -156,7 +170,13 @@ if (import.meta.vitest) {
|
|
|
156
170
|
});
|
|
157
171
|
|
|
158
172
|
test("HostCallFailedError renders payload and preserves it", () => {
|
|
159
|
-
const payload
|
|
173
|
+
const payload: HostErrorPayload = {
|
|
174
|
+
tag: "Domain",
|
|
175
|
+
value: {
|
|
176
|
+
tag: "V1",
|
|
177
|
+
value: { tag: "PermissionDenied", value: { reason: "user said no" } },
|
|
178
|
+
},
|
|
179
|
+
};
|
|
160
180
|
const e = new HostCallFailedError("requestPermission failed", payload);
|
|
161
181
|
expect(e).toBeInstanceOf(HostError);
|
|
162
182
|
expect(e.payload).toBe(payload);
|
|
@@ -164,14 +184,17 @@ if (import.meta.vitest) {
|
|
|
164
184
|
expect(e.message).toBe("requestPermission failed: PermissionDenied: user said no");
|
|
165
185
|
});
|
|
166
186
|
|
|
167
|
-
test("HostCallFailedError renders a GenericError payload", () => {
|
|
168
|
-
const e = new HostCallFailedError("submit failed", {
|
|
187
|
+
test("HostCallFailedError renders a Domain-wrapped GenericError payload", () => {
|
|
188
|
+
const e = new HostCallFailedError("submit failed", {
|
|
189
|
+
tag: "Domain",
|
|
190
|
+
value: { tag: "V1", value: { reason: "timeout" } },
|
|
191
|
+
});
|
|
169
192
|
expect(e.message).toBe("submit failed: timeout");
|
|
170
193
|
});
|
|
171
194
|
|
|
172
195
|
test("isHostError narrows host errors only", () => {
|
|
173
196
|
expect(isHostError(new HostUnavailableError())).toBe(true);
|
|
174
|
-
expect(isHostError(new HostCallFailedError("x", {
|
|
197
|
+
expect(isHostError(new HostCallFailedError("x", { tag: "Denied" }))).toBe(true);
|
|
175
198
|
expect(isHostError(new Error("plain"))).toBe(false);
|
|
176
199
|
expect(isHostError("string")).toBe(false);
|
|
177
200
|
});
|
|
@@ -189,6 +212,24 @@ if (import.meta.vitest) {
|
|
|
189
212
|
expect(formatHostError({ tag: "Full" })).toBe("Full");
|
|
190
213
|
});
|
|
191
214
|
|
|
215
|
+
test("unwraps the CallError Domain envelope to the domain error", () => {
|
|
216
|
+
// { tag: "Domain", value: { tag: "V1", value: <domain error> } }
|
|
217
|
+
expect(
|
|
218
|
+
formatHostError({
|
|
219
|
+
tag: "Domain",
|
|
220
|
+
value: { tag: "V1", value: { tag: "PermissionDenied" } },
|
|
221
|
+
}),
|
|
222
|
+
).toBe("PermissionDenied");
|
|
223
|
+
expect(
|
|
224
|
+
formatHostError({ tag: "Domain", value: { tag: "V1", value: { reason: "boom" } } }),
|
|
225
|
+
).toBe("boom");
|
|
226
|
+
// Transport-level CallError variants render as-is.
|
|
227
|
+
expect(formatHostError({ tag: "Denied" })).toBe("Denied");
|
|
228
|
+
expect(formatHostError({ tag: "HostFailure", value: { reason: "crashed" } })).toBe(
|
|
229
|
+
"HostFailure: crashed",
|
|
230
|
+
);
|
|
231
|
+
});
|
|
232
|
+
|
|
192
233
|
test("falls back for non-host-error input", () => {
|
|
193
234
|
expect(formatHostError(new Error("plain"))).toBe("plain");
|
|
194
235
|
expect(formatHostError("string err")).toBe("string err");
|
package/src/index.ts
CHANGED
|
@@ -56,6 +56,7 @@ export type {
|
|
|
56
56
|
// Result type + typed host errors (the throw→Result boundary)
|
|
57
57
|
export { ok, err } from "./result.js";
|
|
58
58
|
export type { Result } from "./result.js";
|
|
59
|
+
export { type SdkError, isSdkError } from "@parity/product-sdk-errors";
|
|
59
60
|
export {
|
|
60
61
|
HostError,
|
|
61
62
|
HostUnavailableError,
|
|
@@ -72,7 +73,9 @@ export type {
|
|
|
72
73
|
HostAccount,
|
|
73
74
|
ProductAccount,
|
|
74
75
|
ContextualAlias,
|
|
76
|
+
ProductProofContext,
|
|
75
77
|
RingLocation,
|
|
78
|
+
RingVRFProof,
|
|
76
79
|
} from "./accounts.js";
|
|
77
80
|
|
|
78
81
|
// Higher-level permission wrappers
|
package/src/payments.ts
CHANGED
|
@@ -5,16 +5,17 @@
|
|
|
5
5
|
* `truApi.payment.*`.
|
|
6
6
|
*
|
|
7
7
|
* Exposes balance subscription, top-up, payment requests, and payment-status
|
|
8
|
-
* subscription.
|
|
9
|
-
* (RFC-0017)
|
|
10
|
-
* flow
|
|
8
|
+
* subscription. The flow is distinct from the CoinPayment / merchant-payments
|
|
9
|
+
* surface (RFC-0017) — RFC-0006 is the user-initiated balance / top-up /
|
|
10
|
+
* payment-request flow — but both operate on the same CoinPayment purses,
|
|
11
|
+
* hence the shared `CoinPaymentPurseId` (omitted = the main purse).
|
|
11
12
|
*
|
|
12
13
|
* @module
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
import type {
|
|
16
17
|
Balance,
|
|
17
|
-
|
|
18
|
+
CoinPaymentPurseId,
|
|
18
19
|
HexString,
|
|
19
20
|
HostPaymentBalanceSubscribeItem,
|
|
20
21
|
HostPaymentStatusSubscribeItem,
|
|
@@ -37,13 +38,13 @@ import type { HostSubscription } from "./types.js";
|
|
|
37
38
|
export interface PaymentManager {
|
|
38
39
|
subscribeBalance(
|
|
39
40
|
callback: (balance: HostPaymentBalanceSubscribeItem) => void,
|
|
40
|
-
purse?:
|
|
41
|
+
purse?: CoinPaymentPurseId,
|
|
41
42
|
): HostSubscription;
|
|
42
|
-
topUp(amount: Balance, source: PaymentTopUpSource, into?:
|
|
43
|
+
topUp(amount: Balance, source: PaymentTopUpSource, into?: CoinPaymentPurseId): Promise<void>;
|
|
43
44
|
requestPayment(
|
|
44
45
|
amount: Balance,
|
|
45
46
|
destination: HexString,
|
|
46
|
-
from?:
|
|
47
|
+
from?: CoinPaymentPurseId,
|
|
47
48
|
): Promise<{ id: string }>;
|
|
48
49
|
subscribePaymentStatus(
|
|
49
50
|
paymentId: string,
|
package/src/result.ts
CHANGED
|
@@ -1,56 +1,13 @@
|
|
|
1
1
|
// Copyright 2026 Parity Technologies (UK) Ltd.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Re-export of the shared `Result` primitive from `@parity/result`.
|
|
5
5
|
*
|
|
6
6
|
* Host functions return `Promise<Result<T, HostError>>` rather than throwing, so
|
|
7
|
-
* consumers get typed errors on the `err` channel
|
|
8
|
-
* `
|
|
9
|
-
*
|
|
10
|
-
* so the two layers compose with no adapter — host's `Result` flows straight into
|
|
11
|
-
* the signer's pattern matching.
|
|
12
|
-
*
|
|
13
|
-
* NOTE: host owns its own copy because the dependency edge runs `signer → host`,
|
|
14
|
-
* so host cannot import the signer's definition. If a third package ever needs
|
|
15
|
-
* this shape, extract it into a shared `@parity/product-sdk-result` package and
|
|
16
|
-
* have both depend on that instead of duplicating.
|
|
7
|
+
* consumers get typed errors on the `err` channel. The type is now owned by the
|
|
8
|
+
* zero-dependency `@parity/result` leaf so every package shares one
|
|
9
|
+
* definition; this module stays as the host-internal import path.
|
|
17
10
|
*
|
|
18
11
|
* @module
|
|
19
12
|
*/
|
|
20
|
-
|
|
21
|
-
/** A value that is either a success (`ok`) carrying `T`, or a failure (`err`) carrying `E`. */
|
|
22
|
-
export type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };
|
|
23
|
-
|
|
24
|
-
/** Create a successful {@link Result}. */
|
|
25
|
-
export function ok<T>(value: T): Result<T, never> {
|
|
26
|
-
return { ok: true, value };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Create a failed {@link Result}. */
|
|
30
|
-
export function err<E>(error: E): Result<never, E> {
|
|
31
|
-
return { ok: false, error };
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (import.meta.vitest) {
|
|
35
|
-
const { test, expect, describe } = import.meta.vitest;
|
|
36
|
-
|
|
37
|
-
describe("ok", () => {
|
|
38
|
-
test("produces an ok result with value", () => {
|
|
39
|
-
const result = ok(42);
|
|
40
|
-
expect(result.ok).toBe(true);
|
|
41
|
-
expect(result).toEqual({ ok: true, value: 42 });
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test("works with null value", () => {
|
|
45
|
-
expect(ok(null)).toEqual({ ok: true, value: null });
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
describe("err", () => {
|
|
50
|
-
test("produces an error result", () => {
|
|
51
|
-
const result = err("boom");
|
|
52
|
-
expect(result.ok).toBe(false);
|
|
53
|
-
expect(result).toEqual({ ok: false, error: "boom" });
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
}
|
|
13
|
+
export { type Result, ok, err } from "@parity/result";
|