@parity/truapi 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/LICENSE +21 -0
- package/README.md +102 -0
- package/dist/client.d.ts +20 -0
- package/dist/client.js +320 -0
- package/dist/generated/client.d.ts +243 -0
- package/dist/generated/client.js +1081 -0
- package/dist/generated/index.d.ts +2 -0
- package/dist/generated/index.js +2 -0
- package/dist/generated/types.d.ts +2666 -0
- package/dist/generated/types.js +877 -0
- package/dist/generated/wire-table.d.ts +246 -0
- package/dist/generated/wire-table.js +249 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +4 -0
- package/dist/playground/codegen/services.d.ts +2 -0
- package/dist/playground/codegen/services.js +456 -0
- package/dist/playground/services-types.d.ts +12 -0
- package/dist/playground/services-types.js +1 -0
- package/dist/scale.d.ts +77 -0
- package/dist/scale.js +121 -0
- package/dist/transport.d.ts +237 -0
- package/dist/transport.js +238 -0
- package/package.json +57 -0
package/dist/scale.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/** SCALE codec primitives used by the generated client.
|
|
2
|
+
*
|
|
3
|
+
* Thin wrapper over `scale-ts`: re-exports its primitives and combinators,
|
|
4
|
+
* plus the Polkadot-flavour helpers it does not ship (hex-encoded bytes,
|
|
5
|
+
* lazy recursive codecs, and `V<N>`-indexed tagged unions).
|
|
6
|
+
*/
|
|
7
|
+
import { Bytes, Enum, createCodec, createDecoder, enhanceCodec, u8, } from "scale-ts";
|
|
8
|
+
export { Enum, Option, Result, Struct, Tuple, Vector, _void, bool, i8, i16, i32, i64, i128, str, u8, u16, u32, u64, u128, } from "scale-ts";
|
|
9
|
+
/** Assert that a string is a valid hex string (`0x...`). */
|
|
10
|
+
export function toHexString(value) {
|
|
11
|
+
if (!value.startsWith("0x")) {
|
|
12
|
+
throw new Error(`Expected hex string starting with 0x, got: ${value.slice(0, 20)}`);
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
/** Encode a byte array as a lower-case hex string with a `0x` prefix. */
|
|
17
|
+
export function bytesToHex(bytes) {
|
|
18
|
+
let hex = "0x";
|
|
19
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
20
|
+
hex += bytes[i].toString(16).padStart(2, "0");
|
|
21
|
+
}
|
|
22
|
+
return hex;
|
|
23
|
+
}
|
|
24
|
+
/** Decode a hex string into a byte array. Tolerates a missing `0x` prefix. */
|
|
25
|
+
export function hexToBytes(hex) {
|
|
26
|
+
const start = hex.startsWith("0x") ? 2 : 0;
|
|
27
|
+
const length = (hex.length - start) >> 1;
|
|
28
|
+
const bytes = new Uint8Array(length);
|
|
29
|
+
for (let i = 0; i < length; i++) {
|
|
30
|
+
bytes[i] = parseInt(hex.substring(start + i * 2, start + i * 2 + 2), 16);
|
|
31
|
+
}
|
|
32
|
+
return bytes;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* SCALE codec for hex-encoded byte strings.
|
|
36
|
+
*
|
|
37
|
+
* Encode accepts a `0x`-prefixed hex string and emits SCALE bytes; decode
|
|
38
|
+
* returns the bytes as a hex string. Pass `length` for fixed-size byte arrays
|
|
39
|
+
* (`[u8; N]`); omit it for variable-length byte vectors (`Vec<u8>`).
|
|
40
|
+
*/
|
|
41
|
+
export function Hex(length) {
|
|
42
|
+
return enhanceCodec(Bytes(length), hexToBytes, bytesToHex);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Same wire format as `scale-ts`'s `Enum`, but exposes `value` as optional in
|
|
46
|
+
* the public TS type when the variant codec is `Codec<undefined>`. Lets unit
|
|
47
|
+
* variants of mixed enums round-trip as `{ tag: "X" }` (no `value` key).
|
|
48
|
+
*/
|
|
49
|
+
export function TaggedUnion(inner) {
|
|
50
|
+
return Enum(inner);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Enum without payloads — maps string labels to SCALE discriminant bytes.
|
|
54
|
+
*
|
|
55
|
+
* `scale-ts` models `Enum({ Foo: _void, Bar: _void })` as tagged objects. For
|
|
56
|
+
* user-facing TrUAPI enums with only unit variants, we keep the public TS shape
|
|
57
|
+
* as a plain string union instead.
|
|
58
|
+
*/
|
|
59
|
+
export function Status(...variants) {
|
|
60
|
+
return enhanceCodec(u8, (value) => {
|
|
61
|
+
const index = variants.indexOf(value);
|
|
62
|
+
if (index === -1) {
|
|
63
|
+
throw new Error(`Unknown status value: ${String(value)}`);
|
|
64
|
+
}
|
|
65
|
+
return index;
|
|
66
|
+
}, (index) => {
|
|
67
|
+
const value = variants[index];
|
|
68
|
+
if (value === undefined) {
|
|
69
|
+
throw new Error(`Unknown status index: ${index}`);
|
|
70
|
+
}
|
|
71
|
+
return value;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Defers codec construction until first use so recursive generated codecs can
|
|
76
|
+
* reference each other safely.
|
|
77
|
+
*/
|
|
78
|
+
export function lazy(factory) {
|
|
79
|
+
let resolved;
|
|
80
|
+
const get = () => (resolved ??= factory());
|
|
81
|
+
return createCodec((value) => get().enc(value), (input) => get().dec(input));
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Builds a tagged union codec with explicit SCALE discriminants.
|
|
85
|
+
*
|
|
86
|
+
* `scale-ts` assigns enum indexes by object key order. TrUAPI versioned enums pin
|
|
87
|
+
* `V<N>` to index `N - 1`, including V2-only enums, so generated codecs use this
|
|
88
|
+
* helper for versioned wire wrappers.
|
|
89
|
+
*/
|
|
90
|
+
export function indexedTaggedUnion(variants) {
|
|
91
|
+
const byIndex = new Map();
|
|
92
|
+
for (const [tag, [index, codec]] of Object.entries(variants)) {
|
|
93
|
+
if (!Number.isInteger(index) || index < 0 || index > 255) {
|
|
94
|
+
throw new Error(`Invalid enum discriminant for ${tag}: ${index}`);
|
|
95
|
+
}
|
|
96
|
+
if (byIndex.has(index)) {
|
|
97
|
+
throw new Error(`Duplicate enum discriminant: ${index}`);
|
|
98
|
+
}
|
|
99
|
+
byIndex.set(index, [tag, codec]);
|
|
100
|
+
}
|
|
101
|
+
return createCodec((value) => {
|
|
102
|
+
const variant = variants[value.tag];
|
|
103
|
+
if (!variant) {
|
|
104
|
+
throw new Error(`Unknown enum variant: ${value.tag}`);
|
|
105
|
+
}
|
|
106
|
+
const [index, codec] = variant;
|
|
107
|
+
const payload = codec.enc(value.value);
|
|
108
|
+
const out = new Uint8Array(payload.length + 1);
|
|
109
|
+
out[0] = index;
|
|
110
|
+
out.set(payload, 1);
|
|
111
|
+
return out;
|
|
112
|
+
}, createDecoder((input) => {
|
|
113
|
+
const index = u8.dec(input);
|
|
114
|
+
const variant = byIndex.get(index);
|
|
115
|
+
if (!variant) {
|
|
116
|
+
throw new Error(`Unknown enum discriminant: ${index}`);
|
|
117
|
+
}
|
|
118
|
+
const [tag, codec] = variant;
|
|
119
|
+
return { tag, value: codec.dec(input) };
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { type Result } from "neverthrow";
|
|
2
|
+
/**
|
|
3
|
+
* Handle returned by TrUAPI subscription APIs.
|
|
4
|
+
**/
|
|
5
|
+
export interface Subscription {
|
|
6
|
+
/**
|
|
7
|
+
* Stop the subscription. Calling this more than once has no additional effect.
|
|
8
|
+
**/
|
|
9
|
+
unsubscribe: () => void;
|
|
10
|
+
/**
|
|
11
|
+
* Transport-assigned request id for the subscription start frame.
|
|
12
|
+
*
|
|
13
|
+
* Methods that accept a `followSubscriptionId` use this value to scope
|
|
14
|
+
* follow-up requests to a specific active subscription.
|
|
15
|
+
**/
|
|
16
|
+
subscriptionId: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Terminal error delivered through `Observer.error` for every non-normal
|
|
20
|
+
* subscription end. When the peer interrupted the stream with a typed payload,
|
|
21
|
+
* `reason` carries the decoded `Reason`; otherwise `reason` is `undefined` and
|
|
22
|
+
* the underlying transport/decode error is preserved on `cause`.
|
|
23
|
+
*
|
|
24
|
+
* Discriminate with `error.reason !== undefined` (or `'reason' in error`).
|
|
25
|
+
**/
|
|
26
|
+
export declare class SubscriptionError<Reason = never> extends Error {
|
|
27
|
+
/**
|
|
28
|
+
* Typed payload supplied by the peer when it interrupted the subscription.
|
|
29
|
+
* `undefined` when the stream ended for any other reason (transport close,
|
|
30
|
+
* decode failure, malformed interrupt payload).
|
|
31
|
+
**/
|
|
32
|
+
readonly reason?: Reason;
|
|
33
|
+
constructor(message: string, options?: {
|
|
34
|
+
reason?: Reason;
|
|
35
|
+
cause?: unknown;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Minimal Observable-compatible observer shape used by generated subscription
|
|
40
|
+
* APIs without depending on RxJS.
|
|
41
|
+
*
|
|
42
|
+
* `Reason` is the typed interrupt payload for the originating subscription.
|
|
43
|
+
* Methods without a typed interrupt resolve `Reason` to `never`, leaving
|
|
44
|
+
* `error.reason` typed as `undefined`.
|
|
45
|
+
**/
|
|
46
|
+
export interface Observer<Item, Reason = never> {
|
|
47
|
+
/**
|
|
48
|
+
* Called with each successfully decoded subscription item.
|
|
49
|
+
**/
|
|
50
|
+
next(value: Item): void;
|
|
51
|
+
/**
|
|
52
|
+
* Called once when the stream terminates with an error. Inspect
|
|
53
|
+
* `error.reason` to distinguish a typed peer interrupt from a transport or
|
|
54
|
+
* decode failure (`error.cause` carries the underlying failure in the
|
|
55
|
+
* latter case).
|
|
56
|
+
**/
|
|
57
|
+
error(error: SubscriptionError<Reason>): void;
|
|
58
|
+
/**
|
|
59
|
+
* Called once when the peer normally completes the stream.
|
|
60
|
+
**/
|
|
61
|
+
complete(): void;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Minimal Observable-compatible object returned by generated subscription APIs.
|
|
65
|
+
**/
|
|
66
|
+
export interface ObservableLike<Item, Reason = never> {
|
|
67
|
+
/**
|
|
68
|
+
* Start the stream and receive `next`, `error`, and `complete` callbacks.
|
|
69
|
+
**/
|
|
70
|
+
subscribe(observer?: Partial<Observer<Item, Reason>>): Subscription;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Numeric frame ids for a one-shot request method.
|
|
74
|
+
**/
|
|
75
|
+
export interface RequestFrameIds {
|
|
76
|
+
/**
|
|
77
|
+
* Wire discriminant for the outbound request frame.
|
|
78
|
+
**/
|
|
79
|
+
request: number;
|
|
80
|
+
/**
|
|
81
|
+
* Wire discriminant for the inbound response frame.
|
|
82
|
+
**/
|
|
83
|
+
response: number;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Numeric frame ids for a subscription method.
|
|
87
|
+
**/
|
|
88
|
+
export interface SubscriptionFrameIds {
|
|
89
|
+
/**
|
|
90
|
+
* Wire discriminant for the outbound start frame.
|
|
91
|
+
**/
|
|
92
|
+
start: number;
|
|
93
|
+
/**
|
|
94
|
+
* Wire discriminant for the outbound stop frame.
|
|
95
|
+
**/
|
|
96
|
+
stop: number;
|
|
97
|
+
/**
|
|
98
|
+
* Wire discriminant for the inbound interrupt frame.
|
|
99
|
+
**/
|
|
100
|
+
interrupt: number;
|
|
101
|
+
/**
|
|
102
|
+
* Wire discriminant for the inbound receive frame.
|
|
103
|
+
**/
|
|
104
|
+
receive: number;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Options accepted by `TrUApiTransport.request`.
|
|
108
|
+
**/
|
|
109
|
+
export interface RequestParams<Response> {
|
|
110
|
+
/**
|
|
111
|
+
* Wire discriminants for this request method.
|
|
112
|
+
**/
|
|
113
|
+
ids: RequestFrameIds;
|
|
114
|
+
/**
|
|
115
|
+
* SCALE-encoded request payload bytes.
|
|
116
|
+
**/
|
|
117
|
+
payload: Uint8Array;
|
|
118
|
+
/**
|
|
119
|
+
* Decode SCALE response payload bytes into the generated client return type.
|
|
120
|
+
**/
|
|
121
|
+
decodeResponse: (payload: Uint8Array) => Response;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Options accepted by `TrUApiTransport.subscribeRaw`.
|
|
125
|
+
**/
|
|
126
|
+
export interface SubscribeRawParams {
|
|
127
|
+
/**
|
|
128
|
+
* Wire discriminants for this subscription method.
|
|
129
|
+
**/
|
|
130
|
+
ids: SubscriptionFrameIds;
|
|
131
|
+
/**
|
|
132
|
+
* SCALE-encoded subscription start payload bytes.
|
|
133
|
+
**/
|
|
134
|
+
payload: Uint8Array;
|
|
135
|
+
/**
|
|
136
|
+
* Called with raw SCALE receive payload bytes.
|
|
137
|
+
**/
|
|
138
|
+
onReceive: (payload: Uint8Array) => void;
|
|
139
|
+
/**
|
|
140
|
+
* Called with raw SCALE interrupt payload bytes when the peer interrupts the subscription.
|
|
141
|
+
**/
|
|
142
|
+
onInterrupt?: (payload: Uint8Array) => void;
|
|
143
|
+
/**
|
|
144
|
+
* Called when the underlying provider closes while the subscription is active.
|
|
145
|
+
**/
|
|
146
|
+
onClose?: (error: Error) => void;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Byte-level transport used by generated client stubs.
|
|
150
|
+
**/
|
|
151
|
+
export interface TrUApiTransport {
|
|
152
|
+
/**
|
|
153
|
+
* Highest TrUAPI protocol version supported by this generated client.
|
|
154
|
+
**/
|
|
155
|
+
readonly truapiVersion: number;
|
|
156
|
+
/**
|
|
157
|
+
* SCALE codec version negotiated through the handshake.
|
|
158
|
+
**/
|
|
159
|
+
readonly codecVersion: number;
|
|
160
|
+
/**
|
|
161
|
+
* Send a one-shot request and resolve with the decoded response payload.
|
|
162
|
+
**/
|
|
163
|
+
request<Response>(params: RequestParams<Response>): Promise<Response>;
|
|
164
|
+
/**
|
|
165
|
+
* Start a subscription and return a handle that can stop it.
|
|
166
|
+
**/
|
|
167
|
+
subscribeRaw(params: SubscribeRawParams): Subscription;
|
|
168
|
+
/**
|
|
169
|
+
* Tear down the transport and release the listeners it registered on the
|
|
170
|
+
* underlying `Provider`. Pending requests reject and live subscriptions
|
|
171
|
+
* receive `onClose`. Idempotent.
|
|
172
|
+
*
|
|
173
|
+
* The provider itself is left alone; the caller decides whether to also
|
|
174
|
+
* call `provider.dispose()` (long-lived hosts that swap providers will
|
|
175
|
+
* typically dispose the transport but keep the provider).
|
|
176
|
+
**/
|
|
177
|
+
dispose(): void;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Tagged payload inside a TrUAPI wire frame.
|
|
181
|
+
**/
|
|
182
|
+
export interface Payload {
|
|
183
|
+
/**
|
|
184
|
+
* Wire-table numeric discriminant.
|
|
185
|
+
**/
|
|
186
|
+
id: number;
|
|
187
|
+
/**
|
|
188
|
+
* SCALE-encoded payload body.
|
|
189
|
+
**/
|
|
190
|
+
value: Uint8Array;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Top-level TrUAPI wire message.
|
|
194
|
+
**/
|
|
195
|
+
export interface ProtocolMessage {
|
|
196
|
+
/**
|
|
197
|
+
* Request id used to correlate request/response and subscription frames.
|
|
198
|
+
**/
|
|
199
|
+
requestId: string;
|
|
200
|
+
/**
|
|
201
|
+
* Tagged SCALE payload carried by this frame.
|
|
202
|
+
**/
|
|
203
|
+
payload: Payload;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Raw message pipe abstraction used by the transport.
|
|
207
|
+
**/
|
|
208
|
+
export interface Provider {
|
|
209
|
+
/**
|
|
210
|
+
* Send a complete SCALE-encoded wire frame to the peer.
|
|
211
|
+
**/
|
|
212
|
+
postMessage(message: Uint8Array): void;
|
|
213
|
+
/**
|
|
214
|
+
* Register a callback for inbound SCALE-encoded wire frames.
|
|
215
|
+
**/
|
|
216
|
+
subscribe(callback: (message: Uint8Array) => void): () => void;
|
|
217
|
+
/**
|
|
218
|
+
* Register a callback for provider-level close or failure events.
|
|
219
|
+
**/
|
|
220
|
+
subscribeClose?(callback: (error: Error) => void): () => void;
|
|
221
|
+
/**
|
|
222
|
+
* Release provider resources and close the underlying pipe.
|
|
223
|
+
**/
|
|
224
|
+
dispose(): void;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Encode a `ProtocolMessage` into a SCALE wire frame.
|
|
228
|
+
**/
|
|
229
|
+
export declare function encodeWireMessage(message: ProtocolMessage): Result<Uint8Array, Error>;
|
|
230
|
+
/**
|
|
231
|
+
* Decode a SCALE wire frame into a `ProtocolMessage`.
|
|
232
|
+
**/
|
|
233
|
+
export declare function decodeWireMessage(message: Uint8Array): Result<ProtocolMessage, Error>;
|
|
234
|
+
/**
|
|
235
|
+
* Create a provider from a web or Electron `MessagePort`.
|
|
236
|
+
**/
|
|
237
|
+
export declare function createMessagePortProvider(port: MessagePort | Promise<MessagePort>): Provider;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { err, ok } from "neverthrow";
|
|
2
|
+
import { str, u8 } from "./scale.js";
|
|
3
|
+
/**
|
|
4
|
+
* Terminal error delivered through `Observer.error` for every non-normal
|
|
5
|
+
* subscription end. When the peer interrupted the stream with a typed payload,
|
|
6
|
+
* `reason` carries the decoded `Reason`; otherwise `reason` is `undefined` and
|
|
7
|
+
* the underlying transport/decode error is preserved on `cause`.
|
|
8
|
+
*
|
|
9
|
+
* Discriminate with `error.reason !== undefined` (or `'reason' in error`).
|
|
10
|
+
**/
|
|
11
|
+
export class SubscriptionError extends Error {
|
|
12
|
+
/**
|
|
13
|
+
* Typed payload supplied by the peer when it interrupted the subscription.
|
|
14
|
+
* `undefined` when the stream ended for any other reason (transport close,
|
|
15
|
+
* decode failure, malformed interrupt payload).
|
|
16
|
+
**/
|
|
17
|
+
reason;
|
|
18
|
+
constructor(message, options) {
|
|
19
|
+
super(message, options?.cause !== undefined ? { cause: options.cause } : undefined);
|
|
20
|
+
this.name = "SubscriptionError";
|
|
21
|
+
if (options?.reason !== undefined)
|
|
22
|
+
this.reason = options.reason;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Concatenate byte arrays without mutating the source arrays.
|
|
27
|
+
**/
|
|
28
|
+
function concatBytes(parts) {
|
|
29
|
+
let total = 0;
|
|
30
|
+
for (const p of parts)
|
|
31
|
+
total += p.length;
|
|
32
|
+
const out = new Uint8Array(total);
|
|
33
|
+
let off = 0;
|
|
34
|
+
for (const p of parts) {
|
|
35
|
+
out.set(p, off);
|
|
36
|
+
off += p.length;
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Encode a `ProtocolMessage` into a SCALE wire frame.
|
|
42
|
+
**/
|
|
43
|
+
export function encodeWireMessage(message) {
|
|
44
|
+
const id = message.payload.id;
|
|
45
|
+
if (!Number.isInteger(id) || id < 0 || id > 255) {
|
|
46
|
+
return err(new Error(`Invalid wire discriminant: ${id}`));
|
|
47
|
+
}
|
|
48
|
+
return ok(concatBytes([
|
|
49
|
+
str.enc(message.requestId),
|
|
50
|
+
u8.enc(id),
|
|
51
|
+
message.payload.value,
|
|
52
|
+
]));
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Decode a SCALE wire frame into a `ProtocolMessage`.
|
|
56
|
+
**/
|
|
57
|
+
export function decodeWireMessage(message) {
|
|
58
|
+
if (message.length < 1) {
|
|
59
|
+
return err(new Error("Wire frame too short: empty buffer"));
|
|
60
|
+
}
|
|
61
|
+
let cursor = message;
|
|
62
|
+
const requestIdEndResult = scanStrEnd(cursor);
|
|
63
|
+
if (requestIdEndResult.isErr()) {
|
|
64
|
+
return err(requestIdEndResult.error);
|
|
65
|
+
}
|
|
66
|
+
const requestIdEnd = requestIdEndResult.value;
|
|
67
|
+
const requestId = str.dec(cursor.subarray(0, requestIdEnd));
|
|
68
|
+
cursor = cursor.subarray(requestIdEnd);
|
|
69
|
+
if (cursor.length < 1) {
|
|
70
|
+
return err(new Error("Wire frame too short: missing discriminant byte"));
|
|
71
|
+
}
|
|
72
|
+
const id = cursor[0];
|
|
73
|
+
const value = cursor.subarray(1);
|
|
74
|
+
// Hand the value bytes back as a fresh slice so callers may safely retain
|
|
75
|
+
// it even if the source buffer is reused by the transport.
|
|
76
|
+
const valueCopy = new Uint8Array(value.length);
|
|
77
|
+
valueCopy.set(value);
|
|
78
|
+
return ok({ requestId, payload: { id, value: valueCopy } });
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Return the byte offset just past the leading SCALE-encoded string.
|
|
82
|
+
**/
|
|
83
|
+
function scanStrEnd(bytes) {
|
|
84
|
+
if (bytes.length < 1) {
|
|
85
|
+
return err(new Error("compact-len: empty buffer"));
|
|
86
|
+
}
|
|
87
|
+
const first = bytes[0];
|
|
88
|
+
const mode = first & 0b11;
|
|
89
|
+
let lengthLen;
|
|
90
|
+
let strLen;
|
|
91
|
+
if (mode === 0) {
|
|
92
|
+
lengthLen = 1;
|
|
93
|
+
strLen = first >> 2;
|
|
94
|
+
}
|
|
95
|
+
else if (mode === 1) {
|
|
96
|
+
if (bytes.length < 2) {
|
|
97
|
+
return err(new Error("compact-len: truncated mode-1 prefix"));
|
|
98
|
+
}
|
|
99
|
+
lengthLen = 2;
|
|
100
|
+
strLen = ((first >> 2) | (bytes[1] << 6)) & 0x3fff;
|
|
101
|
+
}
|
|
102
|
+
else if (mode === 2) {
|
|
103
|
+
if (bytes.length < 4) {
|
|
104
|
+
return err(new Error("compact-len: truncated mode-2 prefix"));
|
|
105
|
+
}
|
|
106
|
+
lengthLen = 4;
|
|
107
|
+
strLen =
|
|
108
|
+
((first >> 2) | (bytes[1] << 6) | (bytes[2] << 14) | (bytes[3] << 22)) >>>
|
|
109
|
+
0;
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
// big-int mode: not used for requestId in our protocol
|
|
113
|
+
return err(new Error("compact big-int mode not supported in wire envelope"));
|
|
114
|
+
}
|
|
115
|
+
const total = lengthLen + strLen;
|
|
116
|
+
if (total > bytes.length) {
|
|
117
|
+
return err(new Error("compact-len: declared length exceeds buffer"));
|
|
118
|
+
}
|
|
119
|
+
return ok(total);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Create a provider from a web or Electron `MessagePort`.
|
|
123
|
+
**/
|
|
124
|
+
export function createMessagePortProvider(port) {
|
|
125
|
+
let resolvedPort = null;
|
|
126
|
+
let closedError = null;
|
|
127
|
+
const pending = [];
|
|
128
|
+
const listeners = [];
|
|
129
|
+
const closeListeners = [];
|
|
130
|
+
/**
|
|
131
|
+
* Notify close listeners once and drop queued outbound messages.
|
|
132
|
+
**/
|
|
133
|
+
function notifyClose(error) {
|
|
134
|
+
const nextError = error instanceof Error ? error : new Error(String(error));
|
|
135
|
+
if (closedError) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
closedError = nextError;
|
|
139
|
+
pending.length = 0;
|
|
140
|
+
for (const listener of [...closeListeners]) {
|
|
141
|
+
listener(nextError);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
void Promise.resolve(port)
|
|
145
|
+
.then((p) => {
|
|
146
|
+
if (closedError) {
|
|
147
|
+
try {
|
|
148
|
+
p.close();
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// ignore duplicate close during shutdown
|
|
152
|
+
}
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
resolvedPort = p;
|
|
156
|
+
p.onmessage = (event) => {
|
|
157
|
+
const data = event.data;
|
|
158
|
+
if (!(data instanceof Uint8Array))
|
|
159
|
+
return;
|
|
160
|
+
for (const listener of [...listeners])
|
|
161
|
+
listener(data);
|
|
162
|
+
};
|
|
163
|
+
if ("onmessageerror" in p) {
|
|
164
|
+
p.onmessageerror = () => {
|
|
165
|
+
notifyClose(new Error("message port closed unexpectedly"));
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
p.start();
|
|
169
|
+
for (const msg of pending)
|
|
170
|
+
p.postMessage(msg);
|
|
171
|
+
pending.length = 0;
|
|
172
|
+
})
|
|
173
|
+
.catch((error) => {
|
|
174
|
+
notifyClose(error);
|
|
175
|
+
});
|
|
176
|
+
return {
|
|
177
|
+
/**
|
|
178
|
+
* Send bytes through the resolved port or queue them until it resolves.
|
|
179
|
+
**/
|
|
180
|
+
postMessage(message) {
|
|
181
|
+
if (closedError) {
|
|
182
|
+
throw closedError;
|
|
183
|
+
}
|
|
184
|
+
if (resolvedPort) {
|
|
185
|
+
try {
|
|
186
|
+
resolvedPort.postMessage(message);
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
notifyClose(error);
|
|
190
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
pending.push(message);
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
/**
|
|
198
|
+
* Register an inbound message listener.
|
|
199
|
+
**/
|
|
200
|
+
subscribe(callback) {
|
|
201
|
+
listeners.push(callback);
|
|
202
|
+
return () => {
|
|
203
|
+
const idx = listeners.indexOf(callback);
|
|
204
|
+
if (idx >= 0)
|
|
205
|
+
listeners.splice(idx, 1);
|
|
206
|
+
};
|
|
207
|
+
},
|
|
208
|
+
/**
|
|
209
|
+
* Register a close listener.
|
|
210
|
+
**/
|
|
211
|
+
subscribeClose(callback) {
|
|
212
|
+
if (closedError) {
|
|
213
|
+
callback(closedError);
|
|
214
|
+
return () => { };
|
|
215
|
+
}
|
|
216
|
+
closeListeners.push(callback);
|
|
217
|
+
return () => {
|
|
218
|
+
const idx = closeListeners.indexOf(callback);
|
|
219
|
+
if (idx >= 0)
|
|
220
|
+
closeListeners.splice(idx, 1);
|
|
221
|
+
};
|
|
222
|
+
},
|
|
223
|
+
/**
|
|
224
|
+
* Dispose the provider and close the port if it has resolved.
|
|
225
|
+
**/
|
|
226
|
+
dispose() {
|
|
227
|
+
notifyClose(new Error("message port provider disposed"));
|
|
228
|
+
try {
|
|
229
|
+
resolvedPort?.close();
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
// ignore duplicate close during shutdown
|
|
233
|
+
}
|
|
234
|
+
listeners.length = 0;
|
|
235
|
+
closeListeners.length = 0;
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@parity/truapi",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TrUAPI TypeScript transport, SCALE codecs, and generated API client",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Parity Technologies <admin@parity.io>",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/paritytech/truapi.git",
|
|
10
|
+
"directory": "js/packages/truapi"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/paritytech/truapi#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/paritytech/truapi/issues"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"main": "dist/index.js",
|
|
23
|
+
"types": "dist/index.d.ts",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"import": "./dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./playground/services": {
|
|
30
|
+
"types": "./dist/playground/codegen/services.d.ts",
|
|
31
|
+
"import": "./dist/playground/codegen/services.js"
|
|
32
|
+
},
|
|
33
|
+
"./playground/services-types": {
|
|
34
|
+
"types": "./dist/playground/services-types.d.ts",
|
|
35
|
+
"import": "./dist/playground/services-types.js"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"ensure-generated": "./scripts/ensure-generated.sh",
|
|
40
|
+
"build": "tsc",
|
|
41
|
+
"prebuild": "npm run ensure-generated",
|
|
42
|
+
"prepare": "npm run build",
|
|
43
|
+
"codegen": "cargo run -p truapi-codegen -- --input ../../../target/doc/truapi.json --output src/generated --playground-output src/playground --client-examples-output test/generated/examples",
|
|
44
|
+
"typecheck": "npm run build && npm run typecheck:examples",
|
|
45
|
+
"pretypecheck:examples": "npm run ensure-generated",
|
|
46
|
+
"typecheck:examples": "tsc -p tsconfig.examples.json",
|
|
47
|
+
"pretest": "npm run ensure-generated",
|
|
48
|
+
"test": "for f in test/*.test.mjs; do echo \"=== $f ===\" && bun \"$f\" || exit 1; done"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"typescript": "^5.7"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"neverthrow": "^8.2.0",
|
|
55
|
+
"scale-ts": "^1.6.1"
|
|
56
|
+
}
|
|
57
|
+
}
|