@mysten/sui 2.28.0 → 2.29.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/bcs/bcs.d.mts +6 -6
  3. package/dist/bcs/index.d.mts +36 -36
  4. package/dist/cryptography/signature.d.mts +6 -6
  5. package/dist/grpc/client.d.mts +1 -1
  6. package/dist/grpc/client.d.mts.map +1 -1
  7. package/dist/grpc/client.mjs +3 -5
  8. package/dist/grpc/client.mjs.map +1 -1
  9. package/dist/grpc/core.d.mts.map +1 -1
  10. package/dist/grpc/core.mjs +10 -10
  11. package/dist/grpc/core.mjs.map +1 -1
  12. package/dist/grpc/index.d.mts +4 -3
  13. package/dist/grpc/index.mjs +4 -2
  14. package/dist/grpc/proto/sui/forking/v1alpha/forking_service.client.d.mts +4 -4
  15. package/dist/grpc/proto/sui/rpc/v2/move_package_service.client.d.mts +4 -4
  16. package/dist/grpc/proto/sui/rpc/v2/name_service.client.d.mts +4 -4
  17. package/dist/grpc/proto/sui/rpc/v2/signature_verification_service.client.d.mts +4 -4
  18. package/dist/grpc/proto/sui/rpc/v2/subscription_service.client.d.mts +4 -4
  19. package/dist/grpc/proto/sui/rpc/v2/transaction_execution_service.client.d.mts +4 -4
  20. package/dist/grpc/transport.d.mts +18 -0
  21. package/dist/grpc/transport.d.mts.map +1 -0
  22. package/dist/grpc/transport.mjs +97 -0
  23. package/dist/grpc/transport.mjs.map +1 -0
  24. package/dist/transactions/Transaction.d.mts +9 -9
  25. package/dist/transactions/data/internal.d.mts +127 -127
  26. package/dist/transactions/data/v1.d.mts +239 -239
  27. package/dist/transactions/data/v1.d.mts.map +1 -1
  28. package/dist/transactions/data/v2.d.mts +16 -16
  29. package/dist/transactions/data/v2.d.mts.map +1 -1
  30. package/dist/version.mjs +1 -1
  31. package/dist/version.mjs.map +1 -1
  32. package/dist/zklogin/bcs.d.mts +14 -14
  33. package/docs/clients/grpc.md +31 -7
  34. package/package.json +1 -1
  35. package/src/grpc/client.ts +12 -4
  36. package/src/grpc/core.ts +18 -14
  37. package/src/grpc/index.ts +7 -3
  38. package/src/grpc/transport.ts +160 -0
  39. package/src/version.ts +1 -1
@@ -1,12 +1,12 @@
1
1
  import { EnumInputShape } from "@mysten/bcs";
2
- import * as valibot1337 from "valibot";
2
+ import * as valibot0 from "valibot";
3
3
  import { GenericSchema, InferOutput } from "valibot";
4
4
 
5
5
  //#region src/transactions/data/v2.d.ts
6
- declare const SerializedTransactionDataV2Schema: valibot1337.ObjectSchema<{
7
- readonly version: valibot1337.LiteralSchema<2, undefined>;
8
- readonly sender: valibot1337.NullishSchema<valibot1337.SchemaWithPipe<readonly [valibot1337.StringSchema<undefined>, valibot1337.TransformAction<string, string>, valibot1337.CheckAction<string, undefined>]>, undefined>;
9
- readonly expiration: valibot1337.NullishSchema<GenericSchema<EnumInputShape<{
6
+ declare const SerializedTransactionDataV2Schema: valibot0.ObjectSchema<{
7
+ readonly version: valibot0.LiteralSchema<2, undefined>;
8
+ readonly sender: valibot0.NullishSchema<valibot0.SchemaWithPipe<readonly [valibot0.StringSchema<undefined>, valibot0.TransformAction<string, string>, valibot0.CheckAction<string, undefined>]>, undefined>;
9
+ readonly expiration: valibot0.NullishSchema<GenericSchema<EnumInputShape<{
10
10
  None: true;
11
11
  Epoch: string | number;
12
12
  ValidDuring: {
@@ -30,17 +30,17 @@ declare const SerializedTransactionDataV2Schema: valibot1337.ObjectSchema<{
30
30
  nonce: number;
31
31
  };
32
32
  }>>, undefined>;
33
- readonly gasData: valibot1337.ObjectSchema<{
34
- readonly budget: valibot1337.NullableSchema<valibot1337.SchemaWithPipe<readonly [valibot1337.UnionSchema<[valibot1337.StringSchema<undefined>, valibot1337.SchemaWithPipe<readonly [valibot1337.NumberSchema<undefined>, valibot1337.IntegerAction<number, undefined>]>], undefined>, valibot1337.CheckAction<string | number, "Invalid u64">]>, undefined>;
35
- readonly price: valibot1337.NullableSchema<valibot1337.SchemaWithPipe<readonly [valibot1337.UnionSchema<[valibot1337.StringSchema<undefined>, valibot1337.SchemaWithPipe<readonly [valibot1337.NumberSchema<undefined>, valibot1337.IntegerAction<number, undefined>]>], undefined>, valibot1337.CheckAction<string | number, "Invalid u64">]>, undefined>;
36
- readonly owner: valibot1337.NullableSchema<valibot1337.SchemaWithPipe<readonly [valibot1337.StringSchema<undefined>, valibot1337.TransformAction<string, string>, valibot1337.CheckAction<string, undefined>]>, undefined>;
37
- readonly payment: valibot1337.NullableSchema<valibot1337.ArraySchema<valibot1337.ObjectSchema<{
38
- readonly objectId: valibot1337.SchemaWithPipe<readonly [valibot1337.StringSchema<undefined>, valibot1337.TransformAction<string, string>, valibot1337.CheckAction<string, undefined>]>;
39
- readonly version: valibot1337.SchemaWithPipe<readonly [valibot1337.UnionSchema<[valibot1337.StringSchema<undefined>, valibot1337.SchemaWithPipe<readonly [valibot1337.NumberSchema<undefined>, valibot1337.IntegerAction<number, undefined>]>], undefined>, valibot1337.CheckAction<string | number, "Invalid u64">]>;
40
- readonly digest: valibot1337.StringSchema<undefined>;
33
+ readonly gasData: valibot0.ObjectSchema<{
34
+ readonly budget: valibot0.NullableSchema<valibot0.SchemaWithPipe<readonly [valibot0.UnionSchema<[valibot0.StringSchema<undefined>, valibot0.SchemaWithPipe<readonly [valibot0.NumberSchema<undefined>, valibot0.IntegerAction<number, undefined>]>], undefined>, valibot0.CheckAction<string | number, "Invalid u64">]>, undefined>;
35
+ readonly price: valibot0.NullableSchema<valibot0.SchemaWithPipe<readonly [valibot0.UnionSchema<[valibot0.StringSchema<undefined>, valibot0.SchemaWithPipe<readonly [valibot0.NumberSchema<undefined>, valibot0.IntegerAction<number, undefined>]>], undefined>, valibot0.CheckAction<string | number, "Invalid u64">]>, undefined>;
36
+ readonly owner: valibot0.NullableSchema<valibot0.SchemaWithPipe<readonly [valibot0.StringSchema<undefined>, valibot0.TransformAction<string, string>, valibot0.CheckAction<string, undefined>]>, undefined>;
37
+ readonly payment: valibot0.NullableSchema<valibot0.ArraySchema<valibot0.ObjectSchema<{
38
+ readonly objectId: valibot0.SchemaWithPipe<readonly [valibot0.StringSchema<undefined>, valibot0.TransformAction<string, string>, valibot0.CheckAction<string, undefined>]>;
39
+ readonly version: valibot0.SchemaWithPipe<readonly [valibot0.UnionSchema<[valibot0.StringSchema<undefined>, valibot0.SchemaWithPipe<readonly [valibot0.NumberSchema<undefined>, valibot0.IntegerAction<number, undefined>]>], undefined>, valibot0.CheckAction<string | number, "Invalid u64">]>;
40
+ readonly digest: valibot0.StringSchema<undefined>;
41
41
  }, undefined>, undefined>, undefined>;
42
42
  }, undefined>;
43
- readonly inputs: valibot1337.ArraySchema<GenericSchema<EnumInputShape<{
43
+ readonly inputs: valibot0.ArraySchema<GenericSchema<EnumInputShape<{
44
44
  Object: EnumInputShape<{
45
45
  ImmOrOwnedObject: {
46
46
  objectId: string;
@@ -85,7 +85,7 @@ declare const SerializedTransactionDataV2Schema: valibot1337.ObjectSchema<{
85
85
  };
86
86
  };
87
87
  }>>, undefined>;
88
- readonly commands: valibot1337.ArraySchema<GenericSchema<EnumInputShape<{
88
+ readonly commands: valibot0.ArraySchema<GenericSchema<EnumInputShape<{
89
89
  MoveCall: {
90
90
  package: string;
91
91
  module: string;
@@ -184,7 +184,7 @@ declare const SerializedTransactionDataV2Schema: valibot1337.ObjectSchema<{
184
184
  };
185
185
  };
186
186
  }>>, undefined>;
187
- readonly digest: valibot1337.OptionalSchema<valibot1337.NullableSchema<valibot1337.StringSchema<undefined>, undefined>, undefined>;
187
+ readonly digest: valibot0.OptionalSchema<valibot0.NullableSchema<valibot0.StringSchema<undefined>, undefined>, undefined>;
188
188
  }, undefined>;
189
189
  type SerializedTransactionDataV2 = InferOutput<typeof SerializedTransactionDataV2Schema>;
190
190
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"v2.d.mts","names":[],"sources":["../../../src/transactions/data/v2.ts"],"mappings":";;;;;cAqJa,iCAAA,cAAiC,YAAA;EAAA,kBAQ5C,WAAA,CAAA,aAAA;EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAEU,2BAAA,GAA8B,WAAA,QAAmB,iCAAA"}
1
+ {"version":3,"file":"v2.d.mts","names":[],"sources":["../../../src/transactions/data/v2.ts"],"mappings":";;;;;cAqJa,iCAAA,WAAiC,YAAA;EAAA,kBAQ5C,QAAA,CAAA,aAAA;EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAEU,2BAAA,GAA8B,WAAA,QAAmB,iCAAA"}
package/dist/version.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region src/version.ts
2
- const PACKAGE_VERSION = "2.28.0";
2
+ const PACKAGE_VERSION = "2.29.0";
3
3
 
4
4
  //#endregion
5
5
  export { PACKAGE_VERSION };
@@ -1 +1 @@
1
- {"version":3,"file":"version.mjs","names":[],"sources":["../src/version.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\n// This file is generated by genversion.mjs. Do not edit it directly.\n\nexport const PACKAGE_VERSION = '2.28.0';\n"],"mappings":";AAKA,MAAa,kBAAkB"}
1
+ {"version":3,"file":"version.mjs","names":[],"sources":["../src/version.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\n// This file is generated by genversion.mjs. Do not edit it directly.\n\nexport const PACKAGE_VERSION = '2.29.0';\n"],"mappings":";AAKA,MAAa,kBAAkB"}
@@ -1,31 +1,31 @@
1
- import * as _mysten_bcs1245 from "@mysten/bcs";
1
+ import * as _mysten_bcs1378 from "@mysten/bcs";
2
2
  import { InferBcsInput } from "@mysten/bcs";
3
3
 
4
4
  //#region src/zklogin/bcs.d.ts
5
- declare const zkLoginSignature: _mysten_bcs1245.BcsStruct<{
6
- inputs: _mysten_bcs1245.BcsStruct<{
7
- proofPoints: _mysten_bcs1245.BcsStruct<{
8
- a: _mysten_bcs1245.BcsType<string[], Iterable<string> & {
5
+ declare const zkLoginSignature: _mysten_bcs1378.BcsStruct<{
6
+ inputs: _mysten_bcs1378.BcsStruct<{
7
+ proofPoints: _mysten_bcs1378.BcsStruct<{
8
+ a: _mysten_bcs1378.BcsType<string[], Iterable<string> & {
9
9
  length: number;
10
10
  }, string>;
11
- b: _mysten_bcs1245.BcsType<string[][], Iterable<Iterable<string> & {
11
+ b: _mysten_bcs1378.BcsType<string[][], Iterable<Iterable<string> & {
12
12
  length: number;
13
13
  }> & {
14
14
  length: number;
15
15
  }, string>;
16
- c: _mysten_bcs1245.BcsType<string[], Iterable<string> & {
16
+ c: _mysten_bcs1378.BcsType<string[], Iterable<string> & {
17
17
  length: number;
18
18
  }, string>;
19
19
  }, string>;
20
- issBase64Details: _mysten_bcs1245.BcsStruct<{
21
- value: _mysten_bcs1245.BcsType<string, string, "string">;
22
- indexMod4: _mysten_bcs1245.BcsType<number, number, "u8">;
20
+ issBase64Details: _mysten_bcs1378.BcsStruct<{
21
+ value: _mysten_bcs1378.BcsType<string, string, "string">;
22
+ indexMod4: _mysten_bcs1378.BcsType<number, number, "u8">;
23
23
  }, string>;
24
- headerBase64: _mysten_bcs1245.BcsType<string, string, "string">;
25
- addressSeed: _mysten_bcs1245.BcsType<string, string, "string">;
24
+ headerBase64: _mysten_bcs1378.BcsType<string, string, "string">;
25
+ addressSeed: _mysten_bcs1378.BcsType<string, string, "string">;
26
26
  }, string>;
27
- maxEpoch: _mysten_bcs1245.BcsType<string, string | number | bigint, "u64">;
28
- userSignature: _mysten_bcs1245.BcsType<Uint8Array<ArrayBufferLike>, Iterable<number>, "vector<u8>">;
27
+ maxEpoch: _mysten_bcs1378.BcsType<string, string | number | bigint, "u64">;
28
+ userSignature: _mysten_bcs1378.BcsType<Uint8Array<ArrayBufferLike>, Iterable<number>, "vector<u8>">;
29
29
  }, string>;
30
30
  type ZkLoginSignature = InferBcsInput<typeof zkLoginSignature>;
31
31
  type ZkLoginSignatureInputs = ZkLoginSignature['inputs'];
@@ -55,16 +55,39 @@ console.log(transaction.digest, result.protoJson);
55
55
 
56
56
  ## Transport options
57
57
 
58
- By default, `SuiGrpcClient` uses `GrpcWebFetchTransport` from
59
- [protobuf-ts](https://github.com/timostamm/protobuf-ts), which works in browsers and Node.js through
60
- the Fetch API. The `GrpcWebFetchTransport` class, `GrpcWebOptions` type, and `RpcTransport` type are
61
- re-exported from `@mysten/sui/grpc`, so you can configure a transport without adding
62
- `@protobuf-ts/*` as a direct dependency.
58
+ By default, `SuiGrpcClient` uses `GrpcWebFetchTransport`, which works in browsers and Node.js
59
+ through the Fetch API. `@mysten/sui/grpc` exports it alongside the `GrpcWebOptions` and
60
+ `RpcTransport` types, and the `RpcError` class and `GrpcStatusCode` enum that gRPC failures use, so
61
+ you can configure a transport and handle its errors without adding `@protobuf-ts/*` as a direct
62
+ dependency.
63
+
64
+ That class extends the transport of the same name from
65
+ [protobuf-ts](https://github.com/timostamm/protobuf-ts) and fixes two defects in it:
66
+
67
+ - It decodes status messages, rather than delivering them as `Object%20not%20found:%200x1`.
68
+ - It codes a call that an abort ended from the reason the abort carried: `DEADLINE_EXCEEDED` for an
69
+ `AbortSignal.timeout`, the status on a reason carrying one, and `CANCELLED` otherwise. Upstream
70
+ reports all but a standard `AbortError` cancellation as `INTERNAL`.
71
+
72
+ Build custom transports from this class. `SuiGrpcClient` uses a transport you pass it as given.
63
73
 
64
74
  ### gRPC-web transport (default)
65
75
 
66
- The default transport uses the gRPC-web protocol over HTTP/1.1 or HTTP/2. Pass
67
- `GrpcWebFetchTransport` options to customize it:
76
+ The default transport uses the gRPC-web protocol over HTTP/1.1 or HTTP/2. `SuiGrpcClient` passes the
77
+ `GrpcWebOptions` it is given to the transport it builds, so most configuration needs no transport of
78
+ your own. The one exception is `abort`: Core API calls each carry their own signal, so a client-wide
79
+ one is not forwarded.
80
+
81
+ ```typescript
82
+
83
+ const client = new SuiGrpcClient({
84
+ network: 'testnet',
85
+ baseUrl: 'https://your-custom-grpc-endpoint.com',
86
+ format: 'binary', // default is 'text' (base64-encoded)
87
+ });
88
+ ```
89
+
90
+ Build the transport yourself to share one between clients, or to subclass it:
68
91
 
69
92
  ```typescript
70
93
 
@@ -228,6 +251,7 @@ const result = parseGrpcTransactionResponse(response.transaction!, {
228
251
  | `parseGrpcTransactionResponse` | Raw `ExecutedTransaction` to the unified result shape |
229
252
  | `parseGrpcSimulateTransactionResponse` | Raw simulation response to the unified result shape |
230
253
  | `GrpcWebFetchTransport`, `GrpcWebOptions`, `RpcTransport` | Configuring a [transport](#transport-options) |
254
+ | `RpcError`, `GrpcStatusCode` | Narrowing and coding gRPC failures |
231
255
  | `SuiGrpcClientOptions`, `GrpcTransactionInclude`, … | Typing your own wrappers around the client |
232
256
  | `isSuiGrpcClient` | Type guard for narrowing an unknown client |
233
257
 
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "author": "Mysten Labs <build@mystenlabs.com>",
4
4
  "description": "Sui TypeScript API",
5
5
  "homepage": "https://sdk.mystenlabs.com",
6
- "version": "2.28.0",
6
+ "version": "2.29.0",
7
7
  "license": "Apache-2.0",
8
8
  "sideEffects": false,
9
9
  "files": [
@@ -2,7 +2,6 @@
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
4
  import type { GrpcWebOptions } from '@protobuf-ts/grpcweb-transport';
5
- import { GrpcWebFetchTransport } from '@protobuf-ts/grpcweb-transport';
6
5
  import { TransactionExecutionServiceClient } from './proto/sui/rpc/v2/transaction_execution_service.client.js';
7
6
  import { LedgerServiceClient } from './proto/sui/rpc/v2/ledger_service.client.js';
8
7
  import { MovePackageServiceClient } from './proto/sui/rpc/v2/move_package_service.client.js';
@@ -19,6 +18,7 @@ import { fromBase64, toBase64 } from '@mysten/utils';
19
18
  import { NameServiceClient } from './proto/sui/rpc/v2/name_service.client.js';
20
19
  import { ForkingServiceClient } from './proto/sui/forking/v1alpha/forking_service.client.js';
21
20
  import type { TransactionPlugin } from '../transactions/index.js';
21
+ import { GrpcWebFetchTransport } from './transport.js';
22
22
 
23
23
  interface SuiGrpcTransportOptions extends GrpcWebOptions {
24
24
  transport?: never;
@@ -157,9 +157,17 @@ export class SuiGrpcClient extends BaseClient implements SuiClientTypes.Transpor
157
157
 
158
158
  constructor(options: SuiGrpcClientOptions) {
159
159
  super({ network: options.network });
160
- const transport =
161
- options.transport ??
162
- new GrpcWebFetchTransport({ baseUrl: options.baseUrl, fetchInit: options.fetchInit });
160
+ const {
161
+ network: _network,
162
+ mvr: _mvr,
163
+ // Not forwarded: every Core API call passes its own `signal`, which would overwrite it.
164
+ abort: _abort,
165
+ transport: providedTransport,
166
+ ...transportOptions
167
+ } = options as SuiGrpcClientOptions & SuiGrpcTransportOptions & { transport?: RpcTransport };
168
+
169
+ // A caller-supplied transport is used as given. See ./transport.ts for the default.
170
+ const transport = providedTransport ?? new GrpcWebFetchTransport(transportOptions);
163
171
  this.transactionExecutionService = new TransactionExecutionServiceClient(transport);
164
172
  this.ledgerService = new LedgerServiceClient(transport);
165
173
  this.stateService = new StateServiceClient(transport);
package/src/grpc/core.ts CHANGED
@@ -69,24 +69,28 @@ import {
69
69
  validateTransactionQuery,
70
70
  } from '../client/query-filters.js';
71
71
  import { toGrpcEventFilter, toGrpcTransactionFilter } from './filters.js';
72
+ import { hasDecodedStatusMessage } from './transport.js';
72
73
 
73
74
  export interface GrpcCoreClientOptions extends CoreClientOptions {
74
75
  client: SuiGrpcClient;
75
76
  }
76
77
 
77
78
  function isNameServiceResolutionMiss(error: unknown): boolean {
78
- if (!(error instanceof RpcError)) return false;
79
- if (error.code === GrpcStatusCode[GrpcStatusCode.NOT_FOUND]) return true;
80
- if (error.code !== GrpcStatusCode[GrpcStatusCode.RESOURCE_EXHAUSTED]) return false;
81
-
82
- try {
83
- // The gRPC service currently reports expired names as RESOURCE_EXHAUSTED without a
84
- // structured reason. grpc-web URI-encodes status messages, so decode before matching while
85
- // preserving unrelated RESOURCE_EXHAUSTED failures such as capacity limits.
86
- return decodeURIComponent(error.message) === 'name has expired';
87
- } catch {
88
- return false;
89
- }
79
+ // Read by shape, so an error from another copy of runtime-rpc is still recognised.
80
+ if (typeof error !== 'object' || error === null) return false;
81
+
82
+ const { code, message } = error as { code?: unknown; message?: unknown };
83
+ if (typeof code !== 'string' || typeof message !== 'string') return false;
84
+
85
+ if (code === GrpcStatusCode[GrpcStatusCode.NOT_FOUND]) return true;
86
+ if (code !== GrpcStatusCode[GrpcStatusCode.RESOURCE_EXHAUSTED]) return false;
87
+
88
+ // The service reports an expired name as RESOURCE_EXHAUSTED with no structured reason, so match
89
+ // the status text and leave other RESOURCE_EXHAUSTED failures alone. A transport this package did
90
+ // not build leaves `grpc-message` encoded, hence the second form.
91
+ if (message === 'name has expired') return true;
92
+
93
+ return !hasDecodedStatusMessage(error) && message === 'name%20has%20expired';
90
94
  }
91
95
 
92
96
  export class GrpcCoreClient extends CoreClient {
@@ -987,9 +991,9 @@ export class GrpcCoreClient extends CoreClient {
987
991
  });
988
992
  response = result.response;
989
993
  } catch (error) {
990
- // https://github.com/timostamm/protobuf-ts/pull/739
994
+ // The transport owns the status text. See ./transport.ts.
991
995
  if (error instanceof Error && error.message) {
992
- throw new SimulationError(decodeURIComponent(error.message), { cause: error });
996
+ throw new SimulationError(error.message, { cause: error });
993
997
  }
994
998
  throw error;
995
999
  }
package/src/grpc/index.ts CHANGED
@@ -23,10 +23,14 @@ export type {
23
23
  } from './client.js';
24
24
  export type { GrpcCoreClientOptions } from './core.js';
25
25
 
26
- // Re-export transports and types so users can configure custom transports
27
- // without adding @protobuf-ts/* as direct dependencies.
28
- export { GrpcWebFetchTransport } from '@protobuf-ts/grpcweb-transport';
26
+ // Subclasses `@protobuf-ts/grpcweb-transport`'s transport to fix its error handling.
27
+ export { GrpcWebFetchTransport } from './transport.js';
28
+
29
+ // Re-exported so users can configure a transport, and narrow and code the errors it produces,
30
+ // without adding @protobuf-ts/* as a dependency.
31
+ export { GrpcStatusCode } from '@protobuf-ts/grpcweb-transport';
29
32
  export type { GrpcWebOptions } from '@protobuf-ts/grpcweb-transport';
33
+ export { RpcError } from '@protobuf-ts/runtime-rpc';
30
34
  export type { RpcTransport } from '@protobuf-ts/runtime-rpc';
31
35
 
32
36
  // Export all gRPC proto types as a namespace
@@ -0,0 +1,160 @@
1
+ // Copyright (c) Mysten Labs, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { GrpcStatusCode } from '@protobuf-ts/grpcweb-transport';
5
+ import { GrpcWebFetchTransport as UpstreamGrpcWebFetchTransport } from '@protobuf-ts/grpcweb-transport';
6
+ import type {
7
+ MethodInfo,
8
+ RpcOptions,
9
+ ServerStreamingCall,
10
+ UnaryCall,
11
+ } from '@protobuf-ts/runtime-rpc';
12
+ import { RpcError } from '@protobuf-ts/runtime-rpc';
13
+
14
+ // A failed call rejects four promises with the same error, so it is only decoded once. Registered
15
+ // globally so another installed copy of this package sees the same marker.
16
+ const MESSAGE_DECODED = Symbol.for('@mysten/sui/grpc/decoded-status-message');
17
+
18
+ // `grpc-message` is percent-encoded on the wire and the upstream transport does not decode it:
19
+ // https://github.com/timostamm/protobuf-ts/pull/739
20
+ function decodeGrpcStatusMessage(message: string): string {
21
+ if (!message.includes('%')) return message;
22
+
23
+ try {
24
+ return decodeURIComponent(message);
25
+ } catch {
26
+ return message;
27
+ }
28
+ }
29
+
30
+ function markStatusMessageRead(error: RpcError): boolean {
31
+ if (MESSAGE_DECODED in error) return false;
32
+ Object.defineProperty(error, MESSAGE_DECODED, { value: true, configurable: true });
33
+
34
+ return true;
35
+ }
36
+
37
+ function decodeStatusMessageOnce(error: RpcError): void {
38
+ if (markStatusMessageRead(error)) error.message = decodeGrpcStatusMessage(error.message);
39
+ }
40
+
41
+ /** Whether this package's transport decoded the error's status text. */
42
+ export function hasDecodedStatusMessage(error: object): boolean {
43
+ return MESSAGE_DECODED in error;
44
+ }
45
+
46
+ // An abort reason is arbitrary, and reading a property on it runs code the caller wrote. A read
47
+ // that throws counts as absent, so one bad accessor does not decide the status.
48
+ function readProperty(value: unknown, key: 'code' | 'name' | 'message'): unknown {
49
+ if (typeof value !== 'object' || value === null) return undefined;
50
+
51
+ try {
52
+ return (value as Record<string, unknown>)[key];
53
+ } catch {
54
+ return undefined;
55
+ }
56
+ }
57
+
58
+ // The reason is read by shape, since another copy of runtime-rpc or another realm fails
59
+ // `instanceof`. `AbortSignal.timeout` aborts with a `TimeoutError`.
60
+ function abortStatus(reason: unknown): string {
61
+ const code = readProperty(reason, 'code');
62
+
63
+ // A status name maps to a number; `in` would also match the enum's reverse mapping.
64
+ if (
65
+ typeof code === 'string' &&
66
+ typeof GrpcStatusCode[code as keyof typeof GrpcStatusCode] === 'number'
67
+ ) {
68
+ return code;
69
+ }
70
+
71
+ return readProperty(reason, 'name') === 'TimeoutError'
72
+ ? GrpcStatusCode[GrpcStatusCode.DEADLINE_EXCEEDED]
73
+ : GrpcStatusCode[GrpcStatusCode.CANCELLED];
74
+ }
75
+
76
+ // The transport reuses the abort reason's message, so matching it says the text is local. A fetch
77
+ // that substitutes its own error (node-fetch) gives text of its own, which holds nothing to decode.
78
+ // The reason is never coerced: a symbol or a throwing hook would throw here.
79
+ function isAbortReasonText(message: string, reason: unknown): boolean {
80
+ if (typeof reason === 'string') return message === reason;
81
+
82
+ const reasonMessage = readProperty(reason, 'message');
83
+
84
+ return typeof reasonMessage === 'string' && message === reasonMessage;
85
+ }
86
+
87
+ // Rewritten in place, so every promise a call rejects surfaces the same error. Only the two codes
88
+ // upstream uses for an abort are re-coded; a failure that raced the abort is relabelled with it.
89
+ function normalizeGrpcError(error: unknown, signal: AbortSignal | undefined): void {
90
+ try {
91
+ normalize(error, signal);
92
+ } catch {
93
+ // Never an unhandled rejection from a discarded handler: the call reports what it was going
94
+ // to report.
95
+ }
96
+ }
97
+
98
+ function normalize(error: unknown, signal: AbortSignal | undefined): void {
99
+ if (!(error instanceof RpcError)) return;
100
+
101
+ if (
102
+ signal?.aborted &&
103
+ (error.code === GrpcStatusCode[GrpcStatusCode.INTERNAL] ||
104
+ error.code === GrpcStatusCode[GrpcStatusCode.CANCELLED])
105
+ ) {
106
+ error.code = abortStatus(signal.reason);
107
+
108
+ // Text the abort reason supplied is local and holds nothing to decode; anything else came off
109
+ // the wire, even though the abort is what the caller sees. Settled here for the other promises
110
+ // the call rejects, which no longer match the codes above.
111
+ if (isAbortReasonText(error.message, signal.reason)) markStatusMessageRead(error);
112
+ else decodeStatusMessageOnce(error);
113
+
114
+ return;
115
+ }
116
+
117
+ decodeStatusMessageOnce(error);
118
+ }
119
+
120
+ function normalizeRejections(promises: Promise<unknown>[], signal: AbortSignal | undefined) {
121
+ for (const promise of promises) {
122
+ // Registered before the call is returned, so it runs before the consumer's own await.
123
+ promise.then(undefined, (error: unknown) => normalizeGrpcError(error, signal));
124
+ }
125
+ }
126
+
127
+ /**
128
+ * `GrpcWebFetchTransport` from `@protobuf-ts/grpcweb-transport`, subclassed to decode status
129
+ * messages and to code an aborted call from its reason rather than as `INTERNAL`.
130
+ *
131
+ * `SuiGrpcClient` builds one by default. A transport imported from `@protobuf-ts/grpcweb-transport`
132
+ * keeps that package's behaviour.
133
+ */
134
+ export class GrpcWebFetchTransport extends UpstreamGrpcWebFetchTransport {
135
+ override unary<I extends object, O extends object>(
136
+ method: MethodInfo<I, O>,
137
+ input: I,
138
+ options: RpcOptions,
139
+ ): UnaryCall<I, O> {
140
+ const call = super.unary(method, input, options);
141
+
142
+ normalizeRejections([call.headers, call.response, call.status, call.trailers], options.abort);
143
+
144
+ return call;
145
+ }
146
+
147
+ override serverStreaming<I extends object, O extends object>(
148
+ method: MethodInfo<I, O>,
149
+ input: I,
150
+ options: RpcOptions,
151
+ ): ServerStreamingCall<I, O> {
152
+ const call = super.serverStreaming(method, input, options);
153
+
154
+ // A finite stream is often read through `responses` without awaiting `status`.
155
+ call.responses.onError((error) => normalizeGrpcError(error, options.abort));
156
+ normalizeRejections([call.headers, call.status, call.trailers], options.abort);
157
+
158
+ return call;
159
+ }
160
+ }
package/src/version.ts CHANGED
@@ -3,4 +3,4 @@
3
3
 
4
4
  // This file is generated by genversion.mjs. Do not edit it directly.
5
5
 
6
- export const PACKAGE_VERSION = '2.28.0';
6
+ export const PACKAGE_VERSION = '2.29.0';