@ckb-ccc/core 2.0.0-next.0 → 2.0.0-next.1
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 +133 -1
- package/README.md +3 -3
- package/dist/advanced.d.mts +2 -2
- package/dist/advanced.mjs +1 -1
- package/{dist.commonjs/advancedBarrel-DNdhwiU0.d.ts → dist/advancedBarrel-DfFMFhdl.d.mts} +2790 -2237
- package/dist/advancedBarrel-DfFMFhdl.d.mts.map +1 -0
- package/dist/advancedBarrel.d.mts +2 -2
- package/dist/advancedBarrel.mjs +1 -1
- package/dist/advancedBarrel.mjs.map +1 -1
- package/dist/barrel-ONYEMVg5.mjs +7 -0
- package/dist/barrel-ONYEMVg5.mjs.map +1 -0
- package/dist/barrel.d.mts +2 -2
- package/dist/barrel.mjs +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/transformers-CduZ1Js5.mjs +2 -0
- package/dist/transformers-CduZ1Js5.mjs.map +1 -0
- package/dist.commonjs/advanced.d.ts +2 -2
- package/dist.commonjs/advanced.js +1 -1
- package/{dist/advancedBarrel-BHwnMJqR.d.mts → dist.commonjs/advancedBarrel-Dl0mM9JO.d.ts} +2791 -2236
- package/dist.commonjs/advancedBarrel-Dl0mM9JO.d.ts.map +1 -0
- package/dist.commonjs/advancedBarrel.d.ts +2 -2
- package/dist.commonjs/advancedBarrel.js +1 -1
- package/dist.commonjs/advancedBarrel.js.map +1 -1
- package/dist.commonjs/barrel-HfgYMxP_.js +23 -0
- package/dist.commonjs/barrel-HfgYMxP_.js.map +1 -0
- package/dist.commonjs/barrel.d.ts +2 -2
- package/dist.commonjs/barrel.js +1 -1
- package/dist.commonjs/index.d.ts +2 -2
- package/dist.commonjs/index.js +1 -1
- package/dist.commonjs/{rolldown-runtime-DWNI8pZO.js → rolldown-runtime-Chgba0Kb.js} +1 -1
- package/dist.commonjs/transformers-BUlLwiyt.js +2 -0
- package/dist.commonjs/transformers-BUlLwiyt.js.map +1 -0
- package/package.json +8 -7
- package/src/advancedBarrel.ts +1 -0
- package/src/bytes/advanced.ts +1 -6
- package/src/bytes/index.ts +37 -10
- package/src/ckb/transaction.ts +7 -5
- package/src/client/client.ts +83 -21
- package/src/client/clientPublicMainnet.advanced.ts +9 -0
- package/src/client/clientPublicMainnet.ts +75 -36
- package/src/client/clientPublicTestnet.advanced.ts +9 -0
- package/src/client/clientPublicTestnet.ts +75 -36
- package/src/client/jsonRpc/client.ts +77 -30
- package/src/client/jsonRpc/transformers.ts +4 -4
- package/src/jsonRpc/index.ts +1 -0
- package/src/jsonRpc/requestor.ts +106 -29
- package/src/jsonRpc/transports/advanced.ts +0 -4
- package/src/jsonRpc/transports/factory.ts +12 -5
- package/src/jsonRpc/transports/fallback.ts +14 -6
- package/src/jsonRpc/transports/http.ts +19 -12
- package/src/jsonRpc/transports/index.ts +4 -0
- package/src/jsonRpc/transports/transport.ts +42 -6
- package/src/jsonRpc/transports/webSocket.ts +145 -40
- package/src/keystore/index.ts +3 -3
- package/src/molecule/codec.ts +20 -17
- package/src/signer/advanced.ts +1 -0
- package/src/signer/btc/psbt.ts +4 -2
- package/src/signer/btc/verify.ts +2 -2
- package/src/signer/ckb/secp256k1Signing.ts +3 -3
- package/src/signer/doge/signerDoge.ts +1 -1
- package/src/signer/doge/signerDogePrivateKey.ts +1 -1
- package/src/signer/index.ts +1 -0
- package/src/signer/jsonRpc/handler.ts +478 -0
- package/src/signer/jsonRpc/index.ts +2 -0
- package/src/signer/jsonRpc/network.ts +15 -0
- package/src/signer/jsonRpc/signer.ts +378 -0
- package/src/signer/jsonRpc/transformers.ts +71 -0
- package/src/signer/signer/index.ts +5 -13
- package/src/utils/abortSignal.ts +56 -0
- package/src/utils/constructor.ts +11 -0
- package/src/utils/index.ts +172 -5
- package/src/utils/owner/aggregated.ts +56 -0
- package/src/utils/owner/index.ts +4 -0
- package/src/utils/owner/owner.ts +141 -0
- package/src/utils/owner/refCount.ts +78 -0
- package/src/utils/owner/unique.ts +30 -0
- package/src/utils/proxy.ts +165 -0
- package/tsdown.config.mts +1 -0
- package/dist/address.advanced-LxntWbev.mjs +0 -2
- package/dist/address.advanced-LxntWbev.mjs.map +0 -1
- package/dist/advancedBarrel-BHwnMJqR.d.mts.map +0 -1
- package/dist/barrel-C-BaV9Pe.mjs +0 -7
- package/dist/barrel-C-BaV9Pe.mjs.map +0 -1
- package/dist.commonjs/address.advanced-BqcYOpZn.js +0 -2
- package/dist.commonjs/address.advanced-BqcYOpZn.js.map +0 -1
- package/dist.commonjs/advancedBarrel-DNdhwiU0.d.ts.map +0 -1
- package/dist.commonjs/barrel-BBOmj4RJ.js +0 -21
- package/dist.commonjs/barrel-BBOmj4RJ.js.map +0 -1
package/src/utils/index.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { Zero } from "../fixedPoint/index.js";
|
|
2
2
|
import { NumLike, numFrom, numToHex, type Num } from "../num/index.js";
|
|
3
3
|
|
|
4
|
+
export * from "./abortSignal.js";
|
|
5
|
+
export * from "./constructor.js";
|
|
6
|
+
export * from "./owner/index.js";
|
|
7
|
+
export * from "./proxy.js";
|
|
8
|
+
|
|
4
9
|
/**
|
|
5
10
|
* A type safe way to apply a transformer on a value if it's not empty.
|
|
6
11
|
* @public
|
|
@@ -251,15 +256,177 @@ export async function reduceAsync<T, V>(
|
|
|
251
256
|
return acc;
|
|
252
257
|
}
|
|
253
258
|
|
|
254
|
-
|
|
255
|
-
|
|
259
|
+
/**
|
|
260
|
+
* Waits for the given duration, rejecting with the abort reason if cancelled.
|
|
261
|
+
* @public
|
|
262
|
+
*/
|
|
263
|
+
export function sleep(ms: NumLike, signal?: AbortSignal): Promise<void> {
|
|
264
|
+
return new Promise((resolve, reject) => {
|
|
265
|
+
signal?.throwIfAborted();
|
|
266
|
+
|
|
267
|
+
const onAbort = () => {
|
|
268
|
+
clearTimeout(timeout);
|
|
269
|
+
reject(signal?.reason);
|
|
270
|
+
};
|
|
271
|
+
const timeout = setTimeout(
|
|
272
|
+
() => {
|
|
273
|
+
signal?.removeEventListener("abort", onAbort);
|
|
274
|
+
resolve();
|
|
275
|
+
},
|
|
276
|
+
Number(numFrom(ms)),
|
|
277
|
+
);
|
|
278
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
279
|
+
});
|
|
256
280
|
}
|
|
257
281
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
282
|
+
export type RetryOptions = {
|
|
283
|
+
/** Repeats indefinitely with this delay after the delay iterable is exhausted. */
|
|
284
|
+
repeat?: NumLike;
|
|
285
|
+
/** Cancels waiting between attempts. */
|
|
286
|
+
signal?: AbortSignal;
|
|
261
287
|
};
|
|
262
288
|
|
|
289
|
+
/** A result returned by a retry operation. @public */
|
|
290
|
+
export type RetryResult<T> =
|
|
291
|
+
| { status: "resolved"; value: T }
|
|
292
|
+
| { status: "rejected"; error: unknown }
|
|
293
|
+
| { status: "retry"; value?: never };
|
|
294
|
+
|
|
295
|
+
/** Values available to each retry attempt. @public */
|
|
296
|
+
export type RetryOperationOptions<T> = {
|
|
297
|
+
/** Zero-based attempt index. */
|
|
298
|
+
index: number;
|
|
299
|
+
resolve: (value: T) => RetryResult<T>;
|
|
300
|
+
reject: (error: unknown) => RetryResult<T>;
|
|
301
|
+
next: () => RetryResult<never>;
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
function retryResolve<T>(value: T): RetryResult<T> {
|
|
305
|
+
return { status: "resolved", value };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function retryReject(error: unknown): RetryResult<never> {
|
|
309
|
+
return { status: "rejected", error };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function retryAgain(): RetryResult<never> {
|
|
313
|
+
return { status: "retry" };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Retries an asynchronous operation when it throws. The iterable supplies one
|
|
318
|
+
* delay per retry and stops retrying when it is exhausted unless `repeat` is
|
|
319
|
+
* configured. Returning `resolve(value)` or `reject(error)` settles
|
|
320
|
+
* immediately. Returning `next()` consumes the next delay without throwing
|
|
321
|
+
* an error.
|
|
322
|
+
* @public
|
|
323
|
+
*/
|
|
324
|
+
export async function retry<T>(
|
|
325
|
+
delays: Iterable<NumLike>,
|
|
326
|
+
operation: (
|
|
327
|
+
options: RetryOperationOptions<T>,
|
|
328
|
+
) => PromiseLike<RetryResult<T>> | RetryResult<T>,
|
|
329
|
+
options?: RetryOptions,
|
|
330
|
+
): Promise<T> {
|
|
331
|
+
const iterator = delays[Symbol.iterator]();
|
|
332
|
+
let index = 0;
|
|
333
|
+
|
|
334
|
+
while (true) {
|
|
335
|
+
options?.signal?.throwIfAborted();
|
|
336
|
+
|
|
337
|
+
let result: RetryResult<T>;
|
|
338
|
+
try {
|
|
339
|
+
result = await operation({
|
|
340
|
+
index,
|
|
341
|
+
resolve: retryResolve,
|
|
342
|
+
reject: retryReject,
|
|
343
|
+
next: retryAgain,
|
|
344
|
+
});
|
|
345
|
+
if (result.status === "retry") {
|
|
346
|
+
throw new Error("Retry limit exhausted");
|
|
347
|
+
}
|
|
348
|
+
} catch (cause) {
|
|
349
|
+
const next = iterator.next();
|
|
350
|
+
const delay = next.done ? options?.repeat : next.value;
|
|
351
|
+
if (delay === undefined) {
|
|
352
|
+
throw cause;
|
|
353
|
+
}
|
|
354
|
+
await sleep(delay, options?.signal);
|
|
355
|
+
index += 1;
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (result.status === "rejected") {
|
|
360
|
+
throw result.error;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return result.value;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Waits until the current browser page is visible and the browser reports it
|
|
369
|
+
* is online. This does not guarantee that a remote endpoint is reachable.
|
|
370
|
+
* Resolves immediately outside a browser and rejects with the abort reason
|
|
371
|
+
* when the optional signal is aborted.
|
|
372
|
+
* @public
|
|
373
|
+
*/
|
|
374
|
+
export async function waitForAvailability(signal?: AbortSignal): Promise<void> {
|
|
375
|
+
signal?.throwIfAborted();
|
|
376
|
+
|
|
377
|
+
if (
|
|
378
|
+
typeof window === "undefined" ||
|
|
379
|
+
typeof document === "undefined" ||
|
|
380
|
+
typeof navigator === "undefined"
|
|
381
|
+
) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const isAvailable = () =>
|
|
386
|
+
document.visibilityState === "visible" && navigator.onLine;
|
|
387
|
+
if (isAvailable()) {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
await new Promise<void>((resolve, reject) => {
|
|
392
|
+
let settled = false;
|
|
393
|
+
|
|
394
|
+
function cleanup() {
|
|
395
|
+
document.removeEventListener("visibilitychange", onAvailabilityChange);
|
|
396
|
+
window.removeEventListener("online", onAvailabilityChange);
|
|
397
|
+
signal?.removeEventListener("abort", onAbort);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function onAvailabilityChange() {
|
|
401
|
+
if (settled || !isAvailable()) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
settled = true;
|
|
406
|
+
cleanup();
|
|
407
|
+
resolve();
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function onAbort() {
|
|
411
|
+
if (settled) return;
|
|
412
|
+
|
|
413
|
+
settled = true;
|
|
414
|
+
cleanup();
|
|
415
|
+
reject(signal?.reason);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
document.addEventListener("visibilitychange", onAvailabilityChange);
|
|
419
|
+
window.addEventListener("online", onAvailabilityChange);
|
|
420
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
421
|
+
|
|
422
|
+
if (signal?.aborted) {
|
|
423
|
+
onAbort();
|
|
424
|
+
} else {
|
|
425
|
+
onAvailabilityChange();
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
|
|
263
430
|
/**
|
|
264
431
|
* @public
|
|
265
432
|
*/
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Owner, OwnerMoved } from "./owner.js";
|
|
2
|
+
|
|
3
|
+
// TypeScript has no existential type for heterogeneous Owner values.
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5
|
+
type Owners = readonly Owner<any>[];
|
|
6
|
+
|
|
7
|
+
type OwnerValues<T extends Owners> = {
|
|
8
|
+
[K in keyof T]: T[K] extends Owner<infer U> ? U : never;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* An Owner that aggregates multiple ownership claims.
|
|
13
|
+
*
|
|
14
|
+
* Disposing it attempts to release every claim and aggregates any failures.
|
|
15
|
+
*
|
|
16
|
+
* @public
|
|
17
|
+
*/
|
|
18
|
+
export class OwnerAggregated<T extends readonly unknown[]> extends Owner<T> {
|
|
19
|
+
private constructor(
|
|
20
|
+
protected readonly value_: T,
|
|
21
|
+
private readonly owners: readonly OwnerMoved<unknown>[],
|
|
22
|
+
) {
|
|
23
|
+
super();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Moves multiple ownership claims into one Owner. */
|
|
27
|
+
static from<const TOwners extends Owners>(
|
|
28
|
+
owners: TOwners,
|
|
29
|
+
): OwnerAggregated<OwnerValues<TOwners>> {
|
|
30
|
+
if (new Set(owners).size !== owners.length) {
|
|
31
|
+
throw new Error("Cannot aggregate the same Owner more than once");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Validate every source before moving any of them.
|
|
35
|
+
const values = owners.map(
|
|
36
|
+
(owner): unknown => owner.value,
|
|
37
|
+
) as OwnerValues<TOwners>;
|
|
38
|
+
const moved = owners.map((owner) => this.moveFrom(owner));
|
|
39
|
+
return new OwnerAggregated(values, moved);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
protected async release(): Promise<void> {
|
|
43
|
+
const results = await Promise.allSettled(
|
|
44
|
+
this.owners.map((owner) => owner.dispose()),
|
|
45
|
+
);
|
|
46
|
+
const errors: unknown[] = [];
|
|
47
|
+
for (const result of results) {
|
|
48
|
+
if (result.status === "rejected") {
|
|
49
|
+
errors.push(result.reason as unknown);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (errors.length !== 0) {
|
|
53
|
+
throw new AggregateError(errors, "Failed to dispose aggregated Owners");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An ownership claim moved out of an {@link Owner}.
|
|
3
|
+
*
|
|
4
|
+
* @public
|
|
5
|
+
*/
|
|
6
|
+
export class OwnerMoved<T> {
|
|
7
|
+
private disposing?: Promise<void>;
|
|
8
|
+
|
|
9
|
+
constructor(
|
|
10
|
+
/** The owned value. */
|
|
11
|
+
readonly value: T,
|
|
12
|
+
private readonly disposer: (value: T) => PromiseLike<void> | void,
|
|
13
|
+
) {}
|
|
14
|
+
|
|
15
|
+
/** Disposes this ownership claim. */
|
|
16
|
+
dispose(): Promise<void> {
|
|
17
|
+
this.disposing ??= Promise.resolve().then(() => this.disposer(this.value));
|
|
18
|
+
return this.disposing;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const ownerId = Symbol.for("@ckb-ccc/core.Owner");
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Owns the lifecycle of a value.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* class ConnectionOwner extends Owner<Connection> {
|
|
30
|
+
* constructor(protected readonly value_: Connection) {
|
|
31
|
+
* super();
|
|
32
|
+
* }
|
|
33
|
+
*
|
|
34
|
+
* protected release(connection: Connection): Promise<void> {
|
|
35
|
+
* return connection.close();
|
|
36
|
+
* }
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @public
|
|
41
|
+
*/
|
|
42
|
+
export abstract class Owner<T> {
|
|
43
|
+
protected abstract readonly value_: T;
|
|
44
|
+
|
|
45
|
+
private readonly [ownerId] = true;
|
|
46
|
+
private disposing?: Promise<void>;
|
|
47
|
+
|
|
48
|
+
/** Whether this ownership claim has not been moved or disposed. */
|
|
49
|
+
get isValid(): boolean {
|
|
50
|
+
return this.disposing === undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Returns whether a value implements CCC Owner semantics. */
|
|
54
|
+
static is(value: unknown): value is Owner<unknown> {
|
|
55
|
+
return (
|
|
56
|
+
typeof value === "object" &&
|
|
57
|
+
value !== null &&
|
|
58
|
+
(value as { [ownerId]?: unknown })[ownerId] === true
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The owned value.
|
|
64
|
+
*
|
|
65
|
+
* @throws If this ownership has been moved or disposed.
|
|
66
|
+
*/
|
|
67
|
+
get value(): T {
|
|
68
|
+
if (this.disposing) {
|
|
69
|
+
throw new Error("Cannot access a moved or disposed Owner");
|
|
70
|
+
}
|
|
71
|
+
return this.value_;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Releases this ownership claim.
|
|
76
|
+
*
|
|
77
|
+
* Implementations decide whether this disposes the value immediately or,
|
|
78
|
+
* for example, only decrements a reference count.
|
|
79
|
+
*/
|
|
80
|
+
protected abstract release(value: T): PromiseLike<void> | void;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Disposes this ownership claim. Repeated calls are safe.
|
|
84
|
+
*/
|
|
85
|
+
dispose(): Promise<void> {
|
|
86
|
+
this.disposing ??= Promise.resolve().then(() => this.release(this.value_));
|
|
87
|
+
return this.disposing;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Maps the owned value while transferring this ownership claim.
|
|
92
|
+
*
|
|
93
|
+
* The mapper runs before the claim is moved, so a mapper failure leaves this
|
|
94
|
+
* Owner active. After a successful mapping, this Owner is invalidated and the
|
|
95
|
+
* returned Owner retains its release semantics.
|
|
96
|
+
*/
|
|
97
|
+
map<U>(mapper: (value: T) => U): Owner<U> {
|
|
98
|
+
const value = mapper(this.value);
|
|
99
|
+
return new OwnerMapped(value, this.move());
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Moves the ownership claim out of this owner without disposing it.
|
|
104
|
+
*
|
|
105
|
+
* Access is invalidated synchronously and the moved claim retains this
|
|
106
|
+
* Owner's release semantics.
|
|
107
|
+
*
|
|
108
|
+
* @throws If this owner no longer has an ownership claim.
|
|
109
|
+
*/
|
|
110
|
+
protected move(): OwnerMoved<T> {
|
|
111
|
+
if (this.disposing) {
|
|
112
|
+
throw new Error("Owner has already been moved or disposed");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
this.disposing = Promise.resolve();
|
|
116
|
+
|
|
117
|
+
return new OwnerMoved(this.value_, (value) => this.release(value));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Moves an ownership claim between Owner implementations.
|
|
122
|
+
*
|
|
123
|
+
* @throws If the source owner has already been moved or disposed.
|
|
124
|
+
*/
|
|
125
|
+
protected static moveFrom<T>(owner: Owner<T>): OwnerMoved<T> {
|
|
126
|
+
return owner.move();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
class OwnerMapped<TValue, TSource> extends Owner<TValue> {
|
|
131
|
+
constructor(
|
|
132
|
+
protected readonly value_: TValue,
|
|
133
|
+
private readonly source: OwnerMoved<TSource>,
|
|
134
|
+
) {
|
|
135
|
+
super();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
protected release() {
|
|
139
|
+
return this.source.dispose();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Owner, OwnerMoved } from "./owner.js";
|
|
2
|
+
|
|
3
|
+
type OwnerRefCountState<T> = {
|
|
4
|
+
owner: OwnerMoved<T>;
|
|
5
|
+
referenceCount: number;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A reference-counted Owner for a shared value.
|
|
10
|
+
*
|
|
11
|
+
* The source ownership is disposed after the final OwnerRefCount is disposed.
|
|
12
|
+
* Each clone owns one reference and can be disposed independently.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* const controller = new AbortController();
|
|
17
|
+
* const first = OwnerRefCount.new(
|
|
18
|
+
* controller,
|
|
19
|
+
* (controller) => controller.abort(),
|
|
20
|
+
* );
|
|
21
|
+
* const second = first.clone();
|
|
22
|
+
*
|
|
23
|
+
* await first.dispose();
|
|
24
|
+
* console.log(controller.signal.aborted); // false
|
|
25
|
+
*
|
|
26
|
+
* await second.dispose();
|
|
27
|
+
* console.log(controller.signal.aborted); // true
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @public
|
|
31
|
+
*/
|
|
32
|
+
export class OwnerRefCount<T> extends Owner<T> {
|
|
33
|
+
private constructor(private readonly state: OwnerRefCountState<T>) {
|
|
34
|
+
super();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
protected get value_(): T {
|
|
38
|
+
return this.state.owner.value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Creates the first reference-counted Owner for a value. */
|
|
42
|
+
static new<T>(
|
|
43
|
+
value: T,
|
|
44
|
+
disposer: (value: T) => PromiseLike<void> | void,
|
|
45
|
+
): OwnerRefCount<T> {
|
|
46
|
+
return new OwnerRefCount({
|
|
47
|
+
owner: new OwnerMoved(value, disposer),
|
|
48
|
+
referenceCount: 1,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Moves an ownership claim into a new reference-counted Owner.
|
|
54
|
+
*
|
|
55
|
+
* @throws If the source Owner has already been moved or disposed.
|
|
56
|
+
*/
|
|
57
|
+
static from<T>(owner: Owner<T>): OwnerRefCount<T> {
|
|
58
|
+
return new OwnerRefCount({
|
|
59
|
+
owner: this.moveFrom(owner),
|
|
60
|
+
referenceCount: 1,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Creates another Owner for the same shared value. */
|
|
65
|
+
clone(): OwnerRefCount<T> {
|
|
66
|
+
// Assert that this Owner is still active before adding a reference.
|
|
67
|
+
void this.value;
|
|
68
|
+
this.state.referenceCount += 1;
|
|
69
|
+
return new OwnerRefCount(this.state);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
protected release(): Promise<void> | void {
|
|
73
|
+
this.state.referenceCount -= 1;
|
|
74
|
+
if (this.state.referenceCount === 0) {
|
|
75
|
+
return this.state.owner.dispose();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Owner } from "./owner.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* An Owner with a single, transferable ownership claim.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* const owner = new OwnerUnique(
|
|
9
|
+
* new AbortController(),
|
|
10
|
+
* (controller) => controller.abort(),
|
|
11
|
+
* );
|
|
12
|
+
*
|
|
13
|
+
* const signal = owner.value.signal;
|
|
14
|
+
* await owner.dispose();
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* @public
|
|
18
|
+
*/
|
|
19
|
+
export class OwnerUnique<T> extends Owner<T> {
|
|
20
|
+
constructor(
|
|
21
|
+
protected readonly value_: T,
|
|
22
|
+
private readonly disposer: (value: T) => PromiseLike<void> | void,
|
|
23
|
+
) {
|
|
24
|
+
super();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
protected release(value: T) {
|
|
28
|
+
return this.disposer(value);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import type { AbstractConstructor, Constructor } from "./constructor.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Utilities for creating composition wrappers.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* class Wrapper extends Proxy.Base(Client) {
|
|
9
|
+
* static readonly [Proxy.localKeys] = ["feeRate"];
|
|
10
|
+
* public feeRate = 1000;
|
|
11
|
+
*
|
|
12
|
+
* request(): void {
|
|
13
|
+
* this[Proxy.inner].request();
|
|
14
|
+
* }
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* const wrapper = new Wrapper(client);
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @public
|
|
21
|
+
*/
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
23
|
+
export namespace Proxy {
|
|
24
|
+
/** The property key for accessing the immediately wrapped object. */
|
|
25
|
+
export const inner = "@ckb-ccc/core.Proxy.inner";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The static metadata key for instance fields that belong to the wrapper.
|
|
29
|
+
* Every wrapper instance field must be listed here. Prototype members are
|
|
30
|
+
* detected automatically.
|
|
31
|
+
*/
|
|
32
|
+
export const localKeys: unique symbol = Symbol.for(
|
|
33
|
+
"@ckb-ccc/core.Proxy.localKeys",
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Creates a wrapper base that delegates non-local members to an inner instance.
|
|
38
|
+
* Its constructor accepts the inner instance and does not invoke `BaseClass`.
|
|
39
|
+
*
|
|
40
|
+
* @remarks
|
|
41
|
+
* Wrapping an abstract class may require suppressing TS2655 because its abstract
|
|
42
|
+
* members are implemented by runtime delegation.
|
|
43
|
+
*
|
|
44
|
+
* @param BaseClass - The class represented by the wrapper.
|
|
45
|
+
*/
|
|
46
|
+
export function Base<BaseClass extends AbstractConstructor<object>>(
|
|
47
|
+
BaseClass: BaseClass,
|
|
48
|
+
): Omit<BaseClass, "prototype"> &
|
|
49
|
+
Constructor<
|
|
50
|
+
InstanceType<BaseClass> & {
|
|
51
|
+
readonly [inner]: InstanceType<BaseClass>;
|
|
52
|
+
},
|
|
53
|
+
[InstanceType<BaseClass>]
|
|
54
|
+
> {
|
|
55
|
+
type T = InstanceType<BaseClass>;
|
|
56
|
+
|
|
57
|
+
class ProxyClass {
|
|
58
|
+
public readonly [inner]: T;
|
|
59
|
+
|
|
60
|
+
constructor(innerValue: T) {
|
|
61
|
+
this[inner] = innerValue;
|
|
62
|
+
|
|
63
|
+
const allLocalKeys = new Set<PropertyKey>([inner]);
|
|
64
|
+
const forwardedFunctions = new Map<
|
|
65
|
+
PropertyKey,
|
|
66
|
+
{ value: unknown; wrapper: (...args: unknown[]) => unknown }
|
|
67
|
+
>();
|
|
68
|
+
|
|
69
|
+
// Members introduced between the concrete subclass and ProxyClass belong
|
|
70
|
+
// to the wrapper. Stop before ProxyClass so members inherited from Base
|
|
71
|
+
// continue to be forwarded to inner.
|
|
72
|
+
for (
|
|
73
|
+
let prototype: object | null = new.target.prototype;
|
|
74
|
+
prototype !== null && prototype !== ProxyClass.prototype;
|
|
75
|
+
prototype = Reflect.getPrototypeOf(prototype)
|
|
76
|
+
) {
|
|
77
|
+
// Every member defined directly on this prototype belongs to the wrapper.
|
|
78
|
+
for (const property of Reflect.ownKeys(prototype)) {
|
|
79
|
+
allLocalKeys.add(property);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Resolve the class that owns this prototype and merge the local keys
|
|
83
|
+
// declared in its static metadata.
|
|
84
|
+
const constructor = Object.getOwnPropertyDescriptor(
|
|
85
|
+
prototype,
|
|
86
|
+
"constructor",
|
|
87
|
+
)?.value as unknown;
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const metadataKeys = Object.getOwnPropertyDescriptor(
|
|
91
|
+
constructor,
|
|
92
|
+
localKeys,
|
|
93
|
+
)?.value as readonly PropertyKey[] | undefined;
|
|
94
|
+
for (const property of metadataKeys ?? []) {
|
|
95
|
+
allLocalKeys.add(property);
|
|
96
|
+
}
|
|
97
|
+
} catch {}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const isLocalProperty = (
|
|
101
|
+
target: object,
|
|
102
|
+
property: PropertyKey,
|
|
103
|
+
): boolean =>
|
|
104
|
+
allLocalKeys.has(property) ||
|
|
105
|
+
Reflect.getOwnPropertyDescriptor(target, property) !== undefined;
|
|
106
|
+
|
|
107
|
+
return new globalThis.Proxy(this, {
|
|
108
|
+
deleteProperty: (target, property) => {
|
|
109
|
+
return Reflect.deleteProperty(
|
|
110
|
+
isLocalProperty(target, property) ? target : innerValue,
|
|
111
|
+
property,
|
|
112
|
+
);
|
|
113
|
+
},
|
|
114
|
+
get: (target, property, receiver) => {
|
|
115
|
+
const isLocal = isLocalProperty(target, property);
|
|
116
|
+
const value = Reflect.get(
|
|
117
|
+
isLocal ? target : innerValue,
|
|
118
|
+
property,
|
|
119
|
+
isLocal ? receiver : innerValue,
|
|
120
|
+
) as unknown;
|
|
121
|
+
if (isLocal || typeof value !== "function") {
|
|
122
|
+
return value;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let cached = forwardedFunctions.get(property);
|
|
126
|
+
if (cached?.value !== value) {
|
|
127
|
+
cached = {
|
|
128
|
+
value,
|
|
129
|
+
wrapper: (...args: unknown[]): unknown =>
|
|
130
|
+
Reflect.apply(value, innerValue, args) as unknown,
|
|
131
|
+
};
|
|
132
|
+
forwardedFunctions.set(property, cached);
|
|
133
|
+
}
|
|
134
|
+
return cached.wrapper;
|
|
135
|
+
},
|
|
136
|
+
has: (target, property) => {
|
|
137
|
+
return Reflect.has(
|
|
138
|
+
isLocalProperty(target, property) ? target : innerValue,
|
|
139
|
+
property,
|
|
140
|
+
);
|
|
141
|
+
},
|
|
142
|
+
set: (target, property, value, receiver) => {
|
|
143
|
+
const isLocal = isLocalProperty(target, property);
|
|
144
|
+
return Reflect.set(
|
|
145
|
+
isLocal ? target : innerValue,
|
|
146
|
+
property,
|
|
147
|
+
value,
|
|
148
|
+
isLocal ? receiver : innerValue,
|
|
149
|
+
);
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Preserve the runtime instance and static inheritance relationships without
|
|
156
|
+
// constructing a second BaseClass instance as the proxy target.
|
|
157
|
+
const BasePrototype = (BaseClass as unknown as { prototype: object })
|
|
158
|
+
.prototype;
|
|
159
|
+
Object.setPrototypeOf(ProxyClass.prototype, BasePrototype);
|
|
160
|
+
Object.setPrototypeOf(ProxyClass, BaseClass);
|
|
161
|
+
|
|
162
|
+
return ProxyClass as unknown as Omit<BaseClass, "prototype"> &
|
|
163
|
+
Constructor<T & { readonly [inner]: T }, [T]>;
|
|
164
|
+
}
|
|
165
|
+
}
|