@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.
Files changed (89) hide show
  1. package/CHANGELOG.md +133 -1
  2. package/README.md +3 -3
  3. package/dist/advanced.d.mts +2 -2
  4. package/dist/advanced.mjs +1 -1
  5. package/{dist.commonjs/advancedBarrel-DNdhwiU0.d.ts → dist/advancedBarrel-DfFMFhdl.d.mts} +2790 -2237
  6. package/dist/advancedBarrel-DfFMFhdl.d.mts.map +1 -0
  7. package/dist/advancedBarrel.d.mts +2 -2
  8. package/dist/advancedBarrel.mjs +1 -1
  9. package/dist/advancedBarrel.mjs.map +1 -1
  10. package/dist/barrel-ONYEMVg5.mjs +7 -0
  11. package/dist/barrel-ONYEMVg5.mjs.map +1 -0
  12. package/dist/barrel.d.mts +2 -2
  13. package/dist/barrel.mjs +1 -1
  14. package/dist/index.d.mts +2 -2
  15. package/dist/index.mjs +1 -1
  16. package/dist/transformers-CduZ1Js5.mjs +2 -0
  17. package/dist/transformers-CduZ1Js5.mjs.map +1 -0
  18. package/dist.commonjs/advanced.d.ts +2 -2
  19. package/dist.commonjs/advanced.js +1 -1
  20. package/{dist/advancedBarrel-BHwnMJqR.d.mts → dist.commonjs/advancedBarrel-Dl0mM9JO.d.ts} +2791 -2236
  21. package/dist.commonjs/advancedBarrel-Dl0mM9JO.d.ts.map +1 -0
  22. package/dist.commonjs/advancedBarrel.d.ts +2 -2
  23. package/dist.commonjs/advancedBarrel.js +1 -1
  24. package/dist.commonjs/advancedBarrel.js.map +1 -1
  25. package/dist.commonjs/barrel-HfgYMxP_.js +23 -0
  26. package/dist.commonjs/barrel-HfgYMxP_.js.map +1 -0
  27. package/dist.commonjs/barrel.d.ts +2 -2
  28. package/dist.commonjs/barrel.js +1 -1
  29. package/dist.commonjs/index.d.ts +2 -2
  30. package/dist.commonjs/index.js +1 -1
  31. package/dist.commonjs/{rolldown-runtime-DWNI8pZO.js → rolldown-runtime-Chgba0Kb.js} +1 -1
  32. package/dist.commonjs/transformers-BUlLwiyt.js +2 -0
  33. package/dist.commonjs/transformers-BUlLwiyt.js.map +1 -0
  34. package/package.json +8 -7
  35. package/src/advancedBarrel.ts +1 -0
  36. package/src/bytes/advanced.ts +1 -6
  37. package/src/bytes/index.ts +37 -10
  38. package/src/ckb/transaction.ts +7 -5
  39. package/src/client/client.ts +83 -21
  40. package/src/client/clientPublicMainnet.advanced.ts +9 -0
  41. package/src/client/clientPublicMainnet.ts +75 -36
  42. package/src/client/clientPublicTestnet.advanced.ts +9 -0
  43. package/src/client/clientPublicTestnet.ts +75 -36
  44. package/src/client/jsonRpc/client.ts +77 -30
  45. package/src/client/jsonRpc/transformers.ts +4 -4
  46. package/src/jsonRpc/index.ts +1 -0
  47. package/src/jsonRpc/requestor.ts +106 -29
  48. package/src/jsonRpc/transports/advanced.ts +0 -4
  49. package/src/jsonRpc/transports/factory.ts +12 -5
  50. package/src/jsonRpc/transports/fallback.ts +14 -6
  51. package/src/jsonRpc/transports/http.ts +19 -12
  52. package/src/jsonRpc/transports/index.ts +4 -0
  53. package/src/jsonRpc/transports/transport.ts +42 -6
  54. package/src/jsonRpc/transports/webSocket.ts +145 -40
  55. package/src/keystore/index.ts +3 -3
  56. package/src/molecule/codec.ts +20 -17
  57. package/src/signer/advanced.ts +1 -0
  58. package/src/signer/btc/psbt.ts +4 -2
  59. package/src/signer/btc/verify.ts +2 -2
  60. package/src/signer/ckb/secp256k1Signing.ts +3 -3
  61. package/src/signer/doge/signerDoge.ts +1 -1
  62. package/src/signer/doge/signerDogePrivateKey.ts +1 -1
  63. package/src/signer/index.ts +1 -0
  64. package/src/signer/jsonRpc/handler.ts +478 -0
  65. package/src/signer/jsonRpc/index.ts +2 -0
  66. package/src/signer/jsonRpc/network.ts +15 -0
  67. package/src/signer/jsonRpc/signer.ts +378 -0
  68. package/src/signer/jsonRpc/transformers.ts +71 -0
  69. package/src/signer/signer/index.ts +5 -13
  70. package/src/utils/abortSignal.ts +56 -0
  71. package/src/utils/constructor.ts +11 -0
  72. package/src/utils/index.ts +172 -5
  73. package/src/utils/owner/aggregated.ts +56 -0
  74. package/src/utils/owner/index.ts +4 -0
  75. package/src/utils/owner/owner.ts +141 -0
  76. package/src/utils/owner/refCount.ts +78 -0
  77. package/src/utils/owner/unique.ts +30 -0
  78. package/src/utils/proxy.ts +165 -0
  79. package/tsdown.config.mts +1 -0
  80. package/dist/address.advanced-LxntWbev.mjs +0 -2
  81. package/dist/address.advanced-LxntWbev.mjs.map +0 -1
  82. package/dist/advancedBarrel-BHwnMJqR.d.mts.map +0 -1
  83. package/dist/barrel-C-BaV9Pe.mjs +0 -7
  84. package/dist/barrel-C-BaV9Pe.mjs.map +0 -1
  85. package/dist.commonjs/address.advanced-BqcYOpZn.js +0 -2
  86. package/dist.commonjs/address.advanced-BqcYOpZn.js.map +0 -1
  87. package/dist.commonjs/advancedBarrel-DNdhwiU0.d.ts.map +0 -1
  88. package/dist.commonjs/barrel-BBOmj4RJ.js +0 -21
  89. package/dist.commonjs/barrel-BBOmj4RJ.js.map +0 -1
@@ -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
- export function sleep(ms: NumLike) {
255
- return new Promise((resolve) => setTimeout(resolve, Number(numFrom(ms))));
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
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
259
- export type Constructor<T, Args extends any[] = any[]> = {
260
- new (...args: Args): T;
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,4 @@
1
+ export * from "./aggregated.js";
2
+ export * from "./owner.js";
3
+ export * from "./refCount.js";
4
+ export * from "./unique.js";
@@ -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
+ }
package/tsdown.config.mts CHANGED
@@ -25,6 +25,7 @@ const bundleDeps = [
25
25
  "bs58check",
26
26
  "bs58", // By bs58check
27
27
  "base-x", // By bs58 - bs58check
28
+ "uint8array-extras",
28
29
  ] as string[];
29
30
 
30
31
  export default defineConfig(