@polyester/sdk 0.9.0 → 0.10.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 (129) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/catalogs/decimal.js +6 -4
  3. package/dist/catalogs/decimal.js.map +1 -1
  4. package/dist/catalogs/readers.d.ts.map +1 -1
  5. package/dist/catalogs/readers.js +22 -0
  6. package/dist/catalogs/readers.js.map +1 -1
  7. package/dist/core-client.d.ts.map +1 -1
  8. package/dist/core-client.js +49 -15
  9. package/dist/core-client.js.map +1 -1
  10. package/dist/environment.d.ts.map +1 -1
  11. package/dist/environment.js +3 -2
  12. package/dist/environment.js.map +1 -1
  13. package/dist/realtime/client.d.ts.map +1 -1
  14. package/dist/realtime/client.js +13 -5
  15. package/dist/realtime/client.js.map +1 -1
  16. package/dist/realtime/ready-gated-subscription.js +68 -0
  17. package/dist/realtime/ready-gated-subscription.js.map +1 -0
  18. package/dist/realtime/snapshot-then-stream.js +19 -10
  19. package/dist/realtime/snapshot-then-stream.js.map +1 -1
  20. package/dist/server-client.d.ts +3 -1
  21. package/dist/server-client.d.ts.map +1 -1
  22. package/dist/server-client.js +9 -4
  23. package/dist/server-client.js.map +1 -1
  24. package/dist/services/auth/session.d.ts.map +1 -1
  25. package/dist/services/auth/session.js +4 -1
  26. package/dist/services/auth/session.js.map +1 -1
  27. package/dist/services/auth/token-storage.js +2 -2
  28. package/dist/services/auth/token-storage.js.map +1 -1
  29. package/dist/services/balances/balances.js +5 -8
  30. package/dist/services/balances/balances.js.map +1 -1
  31. package/dist/services/candles/candles.js +9 -12
  32. package/dist/services/candles/candles.js.map +1 -1
  33. package/dist/services/heatmap/heatmap.js +5 -8
  34. package/dist/services/heatmap/heatmap.js.map +1 -1
  35. package/dist/services/lifecycle/lifecycle.schemas.d.ts +42 -42
  36. package/dist/services/lifecycle/lifecycle.schemas.js +1 -1
  37. package/dist/services/market-data/market-data.d.ts.map +1 -1
  38. package/dist/services/market-data/market-data.js +5 -8
  39. package/dist/services/market-data/market-data.js.map +1 -1
  40. package/dist/services/market-data/market-data.schemas.d.ts.map +1 -1
  41. package/dist/services/market-data/market-data.schemas.js +3 -2
  42. package/dist/services/market-data/market-data.schemas.js.map +1 -1
  43. package/dist/services/market-overview/market-overview.d.ts.map +1 -1
  44. package/dist/services/market-overview/market-overview.js +7 -13
  45. package/dist/services/market-overview/market-overview.js.map +1 -1
  46. package/dist/services/orderbook/orderbook.d.ts.map +1 -1
  47. package/dist/services/orderbook/orderbook.js +18 -11
  48. package/dist/services/orderbook/orderbook.js.map +1 -1
  49. package/dist/services/orders/orders-batch.schemas.d.ts +193 -35
  50. package/dist/services/orders/orders-batch.schemas.d.ts.map +1 -1
  51. package/dist/services/orders/orders-batch.schemas.js +27 -21
  52. package/dist/services/orders/orders-batch.schemas.js.map +1 -1
  53. package/dist/services/orders/orders-identifiers.schemas.js +6 -1
  54. package/dist/services/orders/orders-identifiers.schemas.js.map +1 -1
  55. package/dist/services/orders/orders-input.schemas.d.ts +36 -68
  56. package/dist/services/orders/orders-input.schemas.d.ts.map +1 -1
  57. package/dist/services/orders/orders-input.schemas.js +11 -12
  58. package/dist/services/orders/orders-input.schemas.js.map +1 -1
  59. package/dist/services/orders/orders-modify.schemas.d.ts +19 -43
  60. package/dist/services/orders/orders-modify.schemas.d.ts.map +1 -1
  61. package/dist/services/orders/orders-modify.schemas.js +2 -4
  62. package/dist/services/orders/orders-modify.schemas.js.map +1 -1
  63. package/dist/services/orders/orders-output.schemas.d.ts +165 -27
  64. package/dist/services/orders/orders-output.schemas.d.ts.map +1 -1
  65. package/dist/services/orders/orders-output.schemas.js +1 -1
  66. package/dist/services/orders/orders-output.schemas.js.map +1 -1
  67. package/dist/services/orders/orders-risk.schemas.d.ts +13 -32
  68. package/dist/services/orders/orders-risk.schemas.d.ts.map +1 -1
  69. package/dist/services/orders/orders-risk.schemas.js +59 -21
  70. package/dist/services/orders/orders-risk.schemas.js.map +1 -1
  71. package/dist/services/orders/orders.codecs.js +12 -1
  72. package/dist/services/orders/orders.codecs.js.map +1 -1
  73. package/dist/services/orders/orders.d.ts.map +1 -1
  74. package/dist/services/orders/orders.js +6 -9
  75. package/dist/services/orders/orders.js.map +1 -1
  76. package/dist/services/subaccounts/subaccounts.schemas.d.ts +2 -2
  77. package/dist/services/trades/trades.js +5 -8
  78. package/dist/services/trades/trades.js.map +1 -1
  79. package/dist/services/trailing-oneof-inputs.js +3 -3
  80. package/dist/services/trailing-oneof-inputs.js.map +1 -1
  81. package/dist/services/transfers/index.d.ts +2 -2
  82. package/dist/services/transfers/transfers.d.ts +1 -2
  83. package/dist/services/transfers/transfers.d.ts.map +1 -1
  84. package/dist/services/transfers/transfers.js +8 -13
  85. package/dist/services/transfers/transfers.js.map +1 -1
  86. package/dist/services/transfers/transfers.schemas.d.ts +5 -5
  87. package/dist/services/transfers/transfers.schemas.d.ts.map +1 -1
  88. package/dist/services/transfers/transfers.schemas.js +31 -33
  89. package/dist/services/transfers/transfers.schemas.js.map +1 -1
  90. package/dist/services/triggers/trigger-input.schemas.d.ts +22 -9
  91. package/dist/services/triggers/trigger-input.schemas.d.ts.map +1 -1
  92. package/dist/services/triggers/trigger-input.schemas.js +20 -43
  93. package/dist/services/triggers/trigger-input.schemas.js.map +1 -1
  94. package/dist/services/triggers/triggers-output.schemas.d.ts +14 -14
  95. package/dist/services/triggers/triggers-output.schemas.js +5 -5
  96. package/dist/services/triggers/triggers-output.schemas.js.map +1 -1
  97. package/dist/services/triggers/triggers.d.ts.map +1 -1
  98. package/dist/services/triggers/triggers.js +10 -23
  99. package/dist/services/triggers/triggers.js.map +1 -1
  100. package/dist/services/zipper/zipper.d.ts.map +1 -1
  101. package/dist/services/zipper/zipper.js +15 -18
  102. package/dist/services/zipper/zipper.js.map +1 -1
  103. package/dist/shared/connect-error-mapping.d.ts.map +1 -1
  104. package/dist/shared/connect-error-mapping.js +9 -2
  105. package/dist/shared/connect-error-mapping.js.map +1 -1
  106. package/dist/shared/decimal-surface.d.ts.map +1 -1
  107. package/dist/shared/decimal-surface.js +41 -18
  108. package/dist/shared/decimal-surface.js.map +1 -1
  109. package/dist/shared/transports.d.ts.map +1 -1
  110. package/dist/shared/transports.js +33 -11
  111. package/dist/shared/transports.js.map +1 -1
  112. package/dist/utils/base58-id.js +1 -2
  113. package/dist/utils/base58-id.js.map +1 -1
  114. package/dist/utils/errors.d.ts.map +1 -1
  115. package/dist/utils/errors.js +4 -3
  116. package/dist/utils/errors.js.map +1 -1
  117. package/dist/utils/event-emitter.d.ts.map +1 -1
  118. package/dist/utils/event-emitter.js +6 -3
  119. package/dist/utils/event-emitter.js.map +1 -1
  120. package/dist/utils/jwt.d.ts.map +1 -1
  121. package/dist/utils/jwt.js +2 -2
  122. package/dist/utils/jwt.js.map +1 -1
  123. package/dist/utils/numbers.js +20 -3
  124. package/dist/utils/numbers.js.map +1 -1
  125. package/dist/utils/timestamp.js +27 -16
  126. package/dist/utils/timestamp.js.map +1 -1
  127. package/dist/utils/u128.js +6 -18
  128. package/dist/utils/u128.js.map +1 -1
  129. package/package.json +1 -1
@@ -1,8 +1,6 @@
1
1
  import { ConfigurationError } from "../../shared/errors.js";
2
2
  import { parse } from "../../shared/validation.js";
3
3
  import { toConnectCallOptions } from "../../shared/request-options.js";
4
- import { publicationHandlerErrorContext } from "../../shared/subscription-errors.js";
5
- import { createReadyGate } from "../../shared/decimal-surface.js";
6
4
  import { snapshotThenStream } from "../../realtime/snapshot-then-stream.js";
7
5
  import { ZippedAssetSupplyBatchSchema, ZipperService as ZipperService$1 } from "../../gen/chain/zipper/v1/zipper_pb.js";
8
6
  import { DepositWithdrawConfigSchema, createZippedAssetSupplyBatchSchema } from "./zipper.schemas.js";
@@ -38,8 +36,7 @@ var ZipperService = class {
38
36
  if (!realtime || !scales) throw new ConfigurationError("Zipper supply subscriptions require a client-owned realtime connection and catalog scales.");
39
37
  const channel = "public:chain:zipped-asset:supply:proto";
40
38
  const schema = createZippedAssetSupplyBatchSchema(scales);
41
- const onError = (error) => input.onError?.(error);
42
- const gate = createReadyGate(() => scales.ready(), (error) => onError(publicationHandlerErrorContext(channel, error)));
39
+ let catalogReady = false;
43
40
  const parseBatch = (batch) => parse(schema, batch);
44
41
  const parseUpdates = (updates) => parseBatch(create(ZippedAssetSupplyBatchSchema, { updates: [...updates] })).updates;
45
42
  const stream = snapshotThenStream({
@@ -51,25 +48,25 @@ var ZipperService = class {
51
48
  maxAttempts: 3,
52
49
  delayMs: 1e3
53
50
  },
54
- snapshotFailureMode: "live",
55
- fetchSnapshot: () => this.getDepositWithdrawConfig(),
51
+ snapshotFailureMode: () => catalogReady ? "live" : "wait",
52
+ fetchSnapshot: async () => {
53
+ await scales.ready();
54
+ catalogReady = true;
55
+ return this.getDepositWithdrawConfig();
56
+ },
56
57
  readPublication: (batch) => batch.updates,
57
58
  bufferPublicationKey: (update) => update.zippedAssetId,
58
59
  applySnapshot: (snapshot, bufferedUpdates) => {
59
- gate.run(() => {
60
- const latestByZippedAssetId = /* @__PURE__ */ new Map();
61
- for (const asset of snapshot.assets) for (const variant of asset.variants) latestByZippedAssetId.set(variant.zippedAssetId, variant.supply);
62
- for (const update of parseUpdates(bufferedUpdates)) latestByZippedAssetId.set(update.zippedAssetId, update.supply);
63
- input.onEvent({ updates: Array.from(latestByZippedAssetId, ([zippedAssetId, supply]) => ({
64
- zippedAssetId,
65
- supply
66
- })) });
67
- });
60
+ const latestByZippedAssetId = /* @__PURE__ */ new Map();
61
+ for (const asset of snapshot.assets) for (const variant of asset.variants) latestByZippedAssetId.set(variant.zippedAssetId, variant.supply);
62
+ for (const update of parseUpdates(bufferedUpdates)) latestByZippedAssetId.set(update.zippedAssetId, update.supply);
63
+ input.onEvent({ updates: Array.from(latestByZippedAssetId, ([zippedAssetId, supply]) => ({
64
+ zippedAssetId,
65
+ supply
66
+ })) });
68
67
  },
69
68
  applyLivePublications: (updates) => {
70
- gate.run(() => {
71
- input.onEvent({ updates: parseUpdates(updates) });
72
- });
69
+ input.onEvent({ updates: parseUpdates(updates) });
73
70
  },
74
71
  onOpen: input.onOpen,
75
72
  onClose: input.onClose,
@@ -1 +1 @@
1
- {"version":3,"file":"zipper.js","names":["#client","Proto.ZipperService","#realtime","#scales","Proto.ZippedAssetSupplyBatchSchema"],"sources":["../../../src/services/zipper/zipper.ts"],"sourcesContent":["import { create } from \"@bufbuild/protobuf\";\nimport * as Proto from \"../../gen/chain/zipper/v1/zipper_pb.js\";\nimport type { PolyesterRealtime } from \"../../realtime/types.js\";\nimport { createClient, type Client, type Transport } from \"@connectrpc/connect\";\nimport {\n DepositWithdrawConfigSchema,\n createZippedAssetSupplyBatchSchema,\n type DepositWithdrawConfig,\n type ZippedAssetSupplyBatch,\n} from \"./zipper.schemas.js\";\nimport {\n publicationHandlerErrorContext,\n type SdkSubscriptionErrorContext,\n} from \"../../shared/subscription-errors.js\";\nimport { parse } from \"../../shared/validation.js\";\nimport {\n toConnectCallOptions,\n type PolyesterRequestOptions,\n} from \"../../shared/request-options.js\";\nimport type { BaseSubscribeInput } from \"../../shared/types.js\";\nimport { createReadyGate, type SdkScales } from \"../../shared/decimal-surface.js\";\nimport { ConfigurationError } from \"../../shared/errors.js\";\nimport { snapshotThenStream } from \"../../realtime/snapshot-then-stream.js\";\n\nexport interface SubscribeZippedAssetSupplyInput extends BaseSubscribeInput<ZippedAssetSupplyBatch> {}\n\n/**\n * Reads public Zipper deposit/withdraw chain and asset configuration.\n */\nexport class ZipperService {\n #client: Client<typeof Proto.ZipperService>;\n #realtime: PolyesterRealtime | undefined;\n #scales: SdkScales | undefined;\n\n constructor(transport: Transport, realtime?: PolyesterRealtime, scales?: SdkScales) {\n this.#client = createClient(Proto.ZipperService, transport);\n this.#realtime = realtime;\n this.#scales = scales;\n }\n\n /**\n * Returns supported external chains, unified assets, chain-specific asset variants, network fees, min deposit/withdraw amounts, and contract metadata for Zipper-powered deposit and withdraw flows.\n */\n async getDepositWithdrawConfig(\n options?: PolyesterRequestOptions,\n ): Promise<DepositWithdrawConfig> {\n const res = await this.#client.getDepositWithdrawConfig({}, toConnectCallOptions(options));\n return parse(DepositWithdrawConfigSchema, res);\n }\n\n /**\n * Attaches to the public supply stream before capturing a route-supply snapshot,\n * then resnapshots whenever the channel establishes a new subscription epoch.\n */\n subscribeZippedAssetSupply(input: SubscribeZippedAssetSupplyInput): () => void {\n const realtime = this.#realtime;\n const scales = this.#scales;\n if (!realtime || !scales) {\n throw new ConfigurationError(\n \"Zipper supply subscriptions require a client-owned realtime connection and catalog scales.\",\n );\n }\n\n const channel = \"public:chain:zipped-asset:supply:proto\";\n const schema = createZippedAssetSupplyBatchSchema(scales);\n const onError = (error: SdkSubscriptionErrorContext) => input.onError?.(error);\n const gate = createReadyGate(\n () => scales.ready(),\n (error) => onError(publicationHandlerErrorContext(channel, error)),\n );\n\n const parseBatch = (batch: Proto.ZippedAssetSupplyBatch): ZippedAssetSupplyBatch =>\n parse(schema, batch);\n const parseUpdates = (\n updates: readonly Proto.ZippedAssetSupplyUpdate[],\n ): ZippedAssetSupplyBatch[\"updates\"] =>\n parseBatch(create(Proto.ZippedAssetSupplyBatchSchema, { updates: [...updates] }))\n .updates;\n const stream = snapshotThenStream({\n realtime,\n channel,\n schema: Proto.ZippedAssetSupplyBatchSchema,\n snapshotErrorLog: \"Failed to fetch zipped asset supply\",\n snapshotRetry: { maxAttempts: 3, delayMs: 1_000 },\n snapshotFailureMode: \"live\",\n fetchSnapshot: () => this.getDepositWithdrawConfig(),\n readPublication: (batch) => batch.updates,\n bufferPublicationKey: (update) => update.zippedAssetId,\n applySnapshot: (snapshot, bufferedUpdates) => {\n gate.run(() => {\n const latestByZippedAssetId = new Map<number, string>();\n for (const asset of snapshot.assets) {\n for (const variant of asset.variants) {\n latestByZippedAssetId.set(variant.zippedAssetId, variant.supply);\n }\n }\n for (const update of parseUpdates(bufferedUpdates)) {\n latestByZippedAssetId.set(update.zippedAssetId, update.supply);\n }\n input.onEvent({\n updates: Array.from(latestByZippedAssetId, ([zippedAssetId, supply]) => ({\n zippedAssetId,\n supply,\n })),\n });\n });\n },\n applyLivePublications: (updates) => {\n gate.run(() => {\n input.onEvent({\n updates: parseUpdates(updates),\n });\n });\n },\n onOpen: input.onOpen,\n onClose: input.onClose,\n onError: input.onError,\n });\n\n return () => stream.unsubscribe();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AA6BA,IAAa,gBAAb,MAA2B;CACvB;CACA;CACA;CAEA,YAAY,WAAsB,UAA8B,QAAoB;EAChF,KAAKA,UAAU,aAAaC,iBAAqB,SAAS;EAC1D,KAAKC,YAAY;EACjB,KAAKC,UAAU;CACnB;;;;CAKA,MAAM,yBACF,SAC8B;EAC9B,MAAM,MAAM,MAAM,KAAKH,QAAQ,yBAAyB,CAAC,GAAG,qBAAqB,OAAO,CAAC;EACzF,OAAO,MAAM,6BAA6B,GAAG;CACjD;;;;;CAMA,2BAA2B,OAAoD;EAC3E,MAAM,WAAW,KAAKE;EACtB,MAAM,SAAS,KAAKC;EACpB,IAAI,CAAC,YAAY,CAAC,QACd,MAAM,IAAI,mBACN,4FACJ;EAGJ,MAAM,UAAU;EAChB,MAAM,SAAS,mCAAmC,MAAM;EACxD,MAAM,WAAW,UAAuC,MAAM,UAAU,KAAK;EAC7E,MAAM,OAAO,sBACH,OAAO,MAAM,IAClB,UAAU,QAAQ,+BAA+B,SAAS,KAAK,CAAC,CACrE;EAEA,MAAM,cAAc,UAChB,MAAM,QAAQ,KAAK;EACvB,MAAM,gBACF,YAEA,WAAW,OAAOC,8BAAoC,EAAE,SAAS,CAAC,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC,CAC5E;EACT,MAAM,SAAS,mBAAmB;GAC9B;GACA;GACA,QAAQA;GACR,kBAAkB;GAClB,eAAe;IAAE,aAAa;IAAG,SAAS;GAAM;GAChD,qBAAqB;GACrB,qBAAqB,KAAK,yBAAyB;GACnD,kBAAkB,UAAU,MAAM;GAClC,uBAAuB,WAAW,OAAO;GACzC,gBAAgB,UAAU,oBAAoB;IAC1C,KAAK,UAAU;KACX,MAAM,wCAAwB,IAAI,IAAoB;KACtD,KAAK,MAAM,SAAS,SAAS,QACzB,KAAK,MAAM,WAAW,MAAM,UACxB,sBAAsB,IAAI,QAAQ,eAAe,QAAQ,MAAM;KAGvE,KAAK,MAAM,UAAU,aAAa,eAAe,GAC7C,sBAAsB,IAAI,OAAO,eAAe,OAAO,MAAM;KAEjE,MAAM,QAAQ,EACV,SAAS,MAAM,KAAK,wBAAwB,CAAC,eAAe,aAAa;MACrE;MACA;KACJ,EAAE,EACN,CAAC;IACL,CAAC;GACL;GACA,wBAAwB,YAAY;IAChC,KAAK,UAAU;KACX,MAAM,QAAQ,EACV,SAAS,aAAa,OAAO,EACjC,CAAC;IACL,CAAC;GACL;GACA,QAAQ,MAAM;GACd,SAAS,MAAM;GACf,SAAS,MAAM;EACnB,CAAC;EAED,aAAa,OAAO,YAAY;CACpC;AACJ"}
1
+ {"version":3,"file":"zipper.js","names":["#client","Proto.ZipperService","#realtime","#scales","Proto.ZippedAssetSupplyBatchSchema"],"sources":["../../../src/services/zipper/zipper.ts"],"sourcesContent":["import { create } from \"@bufbuild/protobuf\";\nimport * as Proto from \"../../gen/chain/zipper/v1/zipper_pb.js\";\nimport type { PolyesterRealtime } from \"../../realtime/types.js\";\nimport { createClient, type Client, type Transport } from \"@connectrpc/connect\";\nimport {\n DepositWithdrawConfigSchema,\n createZippedAssetSupplyBatchSchema,\n type DepositWithdrawConfig,\n type ZippedAssetSupplyBatch,\n} from \"./zipper.schemas.js\";\nimport { parse } from \"../../shared/validation.js\";\nimport {\n toConnectCallOptions,\n type PolyesterRequestOptions,\n} from \"../../shared/request-options.js\";\nimport type { BaseSubscribeInput } from \"../../shared/types.js\";\nimport type { SdkScales } from \"../../shared/decimal-surface.js\";\nimport { ConfigurationError } from \"../../shared/errors.js\";\nimport { snapshotThenStream } from \"../../realtime/snapshot-then-stream.js\";\n\nexport interface SubscribeZippedAssetSupplyInput extends BaseSubscribeInput<ZippedAssetSupplyBatch> {}\n\n/**\n * Reads public Zipper deposit/withdraw chain and asset configuration.\n */\nexport class ZipperService {\n #client: Client<typeof Proto.ZipperService>;\n #realtime: PolyesterRealtime | undefined;\n #scales: SdkScales | undefined;\n\n constructor(transport: Transport, realtime?: PolyesterRealtime, scales?: SdkScales) {\n this.#client = createClient(Proto.ZipperService, transport);\n this.#realtime = realtime;\n this.#scales = scales;\n }\n\n /**\n * Returns supported external chains, unified assets, chain-specific asset variants, network fees, min deposit/withdraw amounts, and contract metadata for Zipper-powered deposit and withdraw flows.\n */\n async getDepositWithdrawConfig(\n options?: PolyesterRequestOptions,\n ): Promise<DepositWithdrawConfig> {\n const res = await this.#client.getDepositWithdrawConfig({}, toConnectCallOptions(options));\n return parse(DepositWithdrawConfigSchema, res);\n }\n\n /**\n * Attaches to the public supply stream before capturing a route-supply snapshot,\n * then resnapshots whenever the channel establishes a new subscription epoch.\n */\n subscribeZippedAssetSupply(input: SubscribeZippedAssetSupplyInput): () => void {\n const realtime = this.#realtime;\n const scales = this.#scales;\n if (!realtime || !scales) {\n throw new ConfigurationError(\n \"Zipper supply subscriptions require a client-owned realtime connection and catalog scales.\",\n );\n }\n\n const channel = \"public:chain:zipped-asset:supply:proto\";\n const schema = createZippedAssetSupplyBatchSchema(scales);\n let catalogReady = false;\n const parseBatch = (batch: Proto.ZippedAssetSupplyBatch): ZippedAssetSupplyBatch =>\n parse(schema, batch);\n const parseUpdates = (\n updates: readonly Proto.ZippedAssetSupplyUpdate[],\n ): ZippedAssetSupplyBatch[\"updates\"] =>\n parseBatch(create(Proto.ZippedAssetSupplyBatchSchema, { updates: [...updates] }))\n .updates;\n const stream = snapshotThenStream({\n realtime,\n channel,\n schema: Proto.ZippedAssetSupplyBatchSchema,\n snapshotErrorLog: \"Failed to fetch zipped asset supply\",\n snapshotRetry: { maxAttempts: 3, delayMs: 1_000 },\n snapshotFailureMode: () => (catalogReady ? \"live\" : \"wait\"),\n fetchSnapshot: async () => {\n await scales.ready();\n catalogReady = true;\n return this.getDepositWithdrawConfig();\n },\n readPublication: (batch) => batch.updates,\n bufferPublicationKey: (update) => update.zippedAssetId,\n applySnapshot: (snapshot, bufferedUpdates) => {\n const latestByZippedAssetId = new Map<number, string>();\n for (const asset of snapshot.assets) {\n for (const variant of asset.variants) {\n latestByZippedAssetId.set(variant.zippedAssetId, variant.supply);\n }\n }\n for (const update of parseUpdates(bufferedUpdates)) {\n latestByZippedAssetId.set(update.zippedAssetId, update.supply);\n }\n input.onEvent({\n updates: Array.from(latestByZippedAssetId, ([zippedAssetId, supply]) => ({\n zippedAssetId,\n supply,\n })),\n });\n },\n applyLivePublications: (updates) => {\n input.onEvent({\n updates: parseUpdates(updates),\n });\n },\n onOpen: input.onOpen,\n onClose: input.onClose,\n onError: input.onError,\n });\n\n return () => stream.unsubscribe();\n }\n}\n"],"mappings":";;;;;;;;;;;;AAyBA,IAAa,gBAAb,MAA2B;CACvB;CACA;CACA;CAEA,YAAY,WAAsB,UAA8B,QAAoB;EAChF,KAAKA,UAAU,aAAaC,iBAAqB,SAAS;EAC1D,KAAKC,YAAY;EACjB,KAAKC,UAAU;CACnB;;;;CAKA,MAAM,yBACF,SAC8B;EAC9B,MAAM,MAAM,MAAM,KAAKH,QAAQ,yBAAyB,CAAC,GAAG,qBAAqB,OAAO,CAAC;EACzF,OAAO,MAAM,6BAA6B,GAAG;CACjD;;;;;CAMA,2BAA2B,OAAoD;EAC3E,MAAM,WAAW,KAAKE;EACtB,MAAM,SAAS,KAAKC;EACpB,IAAI,CAAC,YAAY,CAAC,QACd,MAAM,IAAI,mBACN,4FACJ;EAGJ,MAAM,UAAU;EAChB,MAAM,SAAS,mCAAmC,MAAM;EACxD,IAAI,eAAe;EACnB,MAAM,cAAc,UAChB,MAAM,QAAQ,KAAK;EACvB,MAAM,gBACF,YAEA,WAAW,OAAOC,8BAAoC,EAAE,SAAS,CAAC,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC,CAC5E;EACT,MAAM,SAAS,mBAAmB;GAC9B;GACA;GACA,QAAQA;GACR,kBAAkB;GAClB,eAAe;IAAE,aAAa;IAAG,SAAS;GAAM;GAChD,2BAA4B,eAAe,SAAS;GACpD,eAAe,YAAY;IACvB,MAAM,OAAO,MAAM;IACnB,eAAe;IACf,OAAO,KAAK,yBAAyB;GACzC;GACA,kBAAkB,UAAU,MAAM;GAClC,uBAAuB,WAAW,OAAO;GACzC,gBAAgB,UAAU,oBAAoB;IAC1C,MAAM,wCAAwB,IAAI,IAAoB;IACtD,KAAK,MAAM,SAAS,SAAS,QACzB,KAAK,MAAM,WAAW,MAAM,UACxB,sBAAsB,IAAI,QAAQ,eAAe,QAAQ,MAAM;IAGvE,KAAK,MAAM,UAAU,aAAa,eAAe,GAC7C,sBAAsB,IAAI,OAAO,eAAe,OAAO,MAAM;IAEjE,MAAM,QAAQ,EACV,SAAS,MAAM,KAAK,wBAAwB,CAAC,eAAe,aAAa;KACrE;KACA;IACJ,EAAE,EACN,CAAC;GACL;GACA,wBAAwB,YAAY;IAChC,MAAM,QAAQ,EACV,SAAS,aAAa,OAAO,EACjC,CAAC;GACL;GACA,QAAQ,MAAM;GACd,SAAS,MAAM;GACf,SAAS,MAAM;EACnB,CAAC;EAED,aAAa,OAAO,YAAY;CACpC;AACJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"connect-error-mapping.d.ts","names":[],"sources":["../../src/shared/connect-error-mapping.ts"],"mappings":";;;;;;;iBAkHgB,6BAA6B,IAAI,eAAe;;;;;;iBAyIhD,iBAAiB;;;;;;iBAyBjB,iCAAiC"}
1
+ {"version":3,"file":"connect-error-mapping.d.ts","names":[],"sources":["../../src/shared/connect-error-mapping.ts"],"mappings":";;;;;;;iBA8HgB,6BAA6B,IAAI,eAAe;;;;;;iBAyIhD,iBAAiB;;;;;;iBAyBjB,iCAAiC"}
@@ -29,6 +29,10 @@ function safeRetryAfterMs(rateLimit) {
29
29
  const value = BigInt(rateLimit.retryAfterMs);
30
30
  return value <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(value) : void 0;
31
31
  }
32
+ const HTTP_WEEKDAY = "(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)";
33
+ const HTTP_WEEKDAY_LONG = "(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)";
34
+ const HTTP_MONTH = "(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)";
35
+ const HTTP_DATE_RE = new RegExp(`^(?:${HTTP_WEEKDAY}, \\d{2} ${HTTP_MONTH} \\d{4} \\d{2}:\\d{2}:\\d{2} GMT|${HTTP_WEEKDAY_LONG}, \\d{2}-${HTTP_MONTH}-\\d{2} \\d{2}:\\d{2}:\\d{2} GMT|${HTTP_WEEKDAY} ${HTTP_MONTH} {1,2}\\d{1,2} \\d{2}:\\d{2}:\\d{2} \\d{4})$`, "u");
32
36
  /**
33
37
  * Returns the error message without the leading "[code] " prefix that
34
38
  * `ConnectError` prepends, e.g. "[invalid_argument] Insufficient funds."
@@ -51,8 +55,11 @@ function detectMfaErrorKind(err) {
51
55
  function parseRetryAfterMs(ce) {
52
56
  const value = ce.metadata.get("retry-after");
53
57
  if (!value) return void 0;
54
- const seconds = Number(value);
55
- if (Number.isFinite(seconds)) return Math.max(0, Math.round(seconds * 1e3));
58
+ if (/^\d+$/u.test(value)) {
59
+ const seconds = BigInt(value);
60
+ return seconds <= BigInt(Math.floor(Number.MAX_SAFE_INTEGER / 1e3)) ? Number(seconds) * 1e3 : void 0;
61
+ }
62
+ if (!HTTP_DATE_RE.test(value)) return void 0;
56
63
  const date = Date.parse(value);
57
64
  if (!Number.isNaN(date)) return Math.max(0, date - Date.now());
58
65
  }
@@ -1 +1 @@
1
- {"version":3,"file":"connect-error-mapping.js","names":["OrderErrorDetailSchema"],"sources":["../../src/shared/connect-error-mapping.ts"],"sourcesContent":["import { Code, ConnectError, type Interceptor, type Transport } from \"@connectrpc/connect\";\nimport * as v from \"valibot\";\nimport { AuthErrorCode, AuthErrorDetailSchema } from \"../gen/auth/v1/auth_pb.js\";\nimport {\n ErrorCode as OrderErrorCode,\n ErrorDetailSchema as OrderErrorDetailSchema,\n} from \"../gen/orders/v1/orders_pb.js\";\nimport {\n AlreadyExistsError,\n AuthenticationError,\n InternalServerError,\n isAbortError,\n MfaEnrollmentRequiredError,\n MfaLastFactorRequiredError,\n MfaVerificationError,\n type MfaVerificationFailureReason,\n normalizeErrorMessage,\n PermissionError,\n PolicyInUseError,\n PolicyLockedError,\n PolicyScopeMismatchError,\n PolyesterError,\n type PolyesterErrorOptions,\n PreconditionFailedError,\n RateLimitError,\n ResourceNotFoundError,\n RevisionConflictError,\n ServiceUnavailableError,\n SessionElevationRequiredError,\n StaleQuoteError,\n StepUpRequiredError,\n TimeoutError,\n TransientError,\n ValidationError,\n} from \"./errors.js\";\nimport { RateLimitDetailSchema, type RateLimitDetail } from \"./rate-limit.schemas.js\";\n\nfunction getAuthErrorDetails(err: unknown) {\n return ConnectError.from(err).findDetails(AuthErrorDetailSchema);\n}\n\nfunction hasAuthErrorCode(err: unknown, code: AuthErrorCode): boolean {\n return getAuthErrorDetails(err).some((detail) => detail.code === code);\n}\n\nfunction hasOrderErrorCode(err: unknown, code: OrderErrorCode): boolean {\n return getOrderErrorDetails(err).some((detail) => detail.code === code);\n}\n\nfunction getOrderErrorDetails(err: unknown) {\n return ConnectError.from(err).findDetails(OrderErrorDetailSchema);\n}\n\nfunction getRateLimitDetail(err: unknown): RateLimitDetail | undefined {\n for (const orderDetail of getOrderErrorDetails(err)) {\n if (!orderDetail.rateLimit) continue;\n const result = v.safeParse(RateLimitDetailSchema, orderDetail.rateLimit);\n if (result.success) return result.output;\n }\n return undefined;\n}\n\nfunction safeRetryAfterMs(rateLimit: RateLimitDetail | undefined): number | undefined {\n if (rateLimit?.retryAfterMs === undefined) return undefined;\n const value = BigInt(rateLimit.retryAfterMs);\n return value <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(value) : undefined;\n}\n\n/**\n * Returns the error message without the leading \"[code] \" prefix that\n * `ConnectError` prepends, e.g. \"[invalid_argument] Insufficient funds.\"\n * becomes \"Insufficient funds.\".\n */\nexport function getNormalizedConnectMessage(err: unknown): string {\n const ce = ConnectError.from(err);\n const raw = ce.message ?? \"\";\n return normalizeErrorMessage(raw);\n}\n\nexport type MfaErrorKind = \"session-elevation\" | \"enrollment\" | \"step-up\";\n\n/**\n * Classifies an error as one of the MFA flows the backend can demand.\n * Classification is based only on stable structured auth error details.\n */\nexport function detectMfaErrorKind(err: unknown): MfaErrorKind | null {\n if (hasAuthErrorCode(err, AuthErrorCode.AUTH_MFA_ELEVATION_REQUIRED)) {\n return \"session-elevation\";\n }\n if (hasAuthErrorCode(err, AuthErrorCode.AUTH_STEP_UP_REQUIRED)) return \"step-up\";\n if (hasAuthErrorCode(err, AuthErrorCode.AUTH_MFA_NOT_ENROLLED)) return \"enrollment\";\n return null;\n}\n\nfunction parseRetryAfterMs(ce: ConnectError): number | undefined {\n const value = ce.metadata.get(\"retry-after\");\n if (!value) return undefined;\n const seconds = Number(value);\n if (Number.isFinite(seconds)) return Math.max(0, Math.round(seconds * 1000));\n const date = Date.parse(value);\n if (!Number.isNaN(date)) return Math.max(0, date - Date.now());\n return undefined;\n}\n\nconst MFA_ERROR_CLASSES = {\n \"session-elevation\": SessionElevationRequiredError,\n enrollment: MfaEnrollmentRequiredError,\n \"step-up\": StepUpRequiredError,\n} as const;\n\n/**\n * Maps a `ConnectError` from the Polyester backend onto the typed\n * {@link PolyesterError} tree. The original error is preserved as `cause`.\n */\nexport function connectErrorToPolyesterError(ce: ConnectError): PolyesterError {\n const message = getNormalizedConnectMessage(ce);\n const options: PolyesterErrorOptions = { cause: ce };\n const withFallback = (fallback: string) => message || fallback;\n\n if (hasAuthErrorCode(ce, AuthErrorCode.AUTH_RESOURCE_NOT_FOUND)) {\n return new ResourceNotFoundError(withFallback(\"Resource not found.\"), options);\n }\n\n if (hasAuthErrorCode(ce, AuthErrorCode.AUTH_REVISION_CONFLICT)) {\n return new RevisionConflictError(\n withFallback(\"Resource changed since it was last read.\"),\n options,\n );\n }\n\n if (hasAuthErrorCode(ce, AuthErrorCode.AUTH_POLICY_IN_USE)) {\n return new PolicyInUseError(withFallback(\"Policy is still in use.\"), options);\n }\n if (hasAuthErrorCode(ce, AuthErrorCode.AUTH_POLICY_LOCKED)) {\n return new PolicyLockedError(withFallback(\"Policy is locked.\"), options);\n }\n if (hasAuthErrorCode(ce, AuthErrorCode.AUTH_POLICY_SCOPE_MISMATCH)) {\n return new PolicyScopeMismatchError(\n withFallback(\"Policy does not belong to the target account scope.\"),\n options,\n );\n }\n if (hasAuthErrorCode(ce, AuthErrorCode.AUTH_MFA_LAST_FACTOR_REQUIRED)) {\n return new MfaLastFactorRequiredError(\n withFallback(\"At least one active MFA factor must remain enrolled.\"),\n options,\n );\n }\n if (hasAuthErrorCode(ce, AuthErrorCode.AUTH_INTERNAL_ERROR)) {\n return new InternalServerError(withFallback(\"Internal server error.\"), options);\n }\n\n const mfaVerificationReason = getMfaVerificationFailureReason(ce);\n if (mfaVerificationReason) {\n return new MfaVerificationError(\n withFallback(\"MFA verification failed.\"),\n mfaVerificationReason,\n options,\n );\n }\n\n const mfaKind = detectMfaErrorKind(ce);\n if (mfaKind) {\n return new MFA_ERROR_CLASSES[mfaKind](\n withFallback(\"Multi-factor authentication required.\"),\n options,\n );\n }\n\n if (hasOrderErrorCode(ce, OrderErrorCode.STALE_QUOTE)) {\n return new StaleQuoteError(withFallback(\"The submitted market quote is stale.\"), options);\n }\n\n const rateLimit = getRateLimitDetail(ce);\n if (rateLimit || hasOrderErrorCode(ce, OrderErrorCode.RATE_LIMIT_EXCEEDED)) {\n return new RateLimitError(withFallback(\"Rate limit exceeded.\"), {\n ...options,\n rateLimit,\n retryAfterMs: safeRetryAfterMs(rateLimit) ?? parseRetryAfterMs(ce),\n });\n }\n\n switch (ce.code) {\n case Code.InvalidArgument:\n case Code.OutOfRange:\n return new ValidationError(withFallback(\"Invalid request.\"), options);\n case Code.NotFound:\n return new ResourceNotFoundError(withFallback(\"Resource not found.\"), options);\n case Code.AlreadyExists:\n return new AlreadyExistsError(withFallback(\"Resource already exists.\"), options);\n case Code.PermissionDenied:\n return new PermissionError(withFallback(\"Permission denied.\"), options);\n case Code.Unauthenticated:\n return new AuthenticationError(withFallback(\"Authentication required.\"), options);\n case Code.FailedPrecondition:\n return new PreconditionFailedError(withFallback(\"Precondition failed.\"), options);\n case Code.ResourceExhausted:\n return new RateLimitError(withFallback(\"Rate limit exceeded.\"), {\n ...options,\n retryAfterMs: parseRetryAfterMs(ce),\n });\n case Code.DeadlineExceeded:\n return new TimeoutError(withFallback(\"Request timed out.\"), options);\n case Code.Unavailable:\n return new ServiceUnavailableError(withFallback(\"Service unavailable.\"), options);\n case Code.Aborted:\n return new TransientError(withFallback(\"Operation aborted by the backend.\"), options);\n default:\n // Gateways sometimes surface outages as Unknown with this stock phrase.\n if (/service temporarily unavailable/i.test(message)) {\n return new ServiceUnavailableError(withFallback(\"Service unavailable.\"), options);\n }\n return new InternalServerError(withFallback(\"Internal server error.\"), options);\n }\n}\n\nfunction getMfaVerificationFailureReason(err: unknown): MfaVerificationFailureReason | null {\n if (hasAuthErrorCode(err, AuthErrorCode.AUTH_MFA_CHALLENGE_INVALID)) {\n return \"challenge-invalid\";\n }\n if (hasAuthErrorCode(err, AuthErrorCode.AUTH_MFA_CHALLENGE_LOCKED)) {\n return \"challenge-locked\";\n }\n if (hasAuthErrorCode(err, AuthErrorCode.AUTH_MFA_OTP_INVALID)) return \"otp-invalid\";\n if (hasAuthErrorCode(err, AuthErrorCode.AUTH_MFA_RECOVERY_INVALID)) {\n return \"recovery-code-invalid\";\n }\n if (\n hasAuthErrorCode(err, AuthErrorCode.AUTH_MFA_PASSKEY_CREDENTIAL_INVALID) ||\n hasAuthErrorCode(err, AuthErrorCode.AUTH_MFA_PASSKEY_VERIFY_FAILED)\n ) {\n return \"passkey-invalid\";\n }\n\n return null;\n}\n\nfunction findPolyesterErrorInCauseChain(err: unknown): PolyesterError | null {\n let current: unknown = err;\n for (let depth = 0; depth < 5 && current instanceof Error; depth++) {\n current = current.cause;\n if (current instanceof PolyesterError) return current;\n }\n return null;\n}\n\n/**\n * Converts any RPC-layer failure into its typed SDK error. Abort errors and\n * caller-cancelled requests pass through unchanged, as do errors that are\n * already typed (e.g. a `NetworkError` from the SDK fetch wrapper).\n */\nexport function toPolyesterError(err: unknown): unknown {\n if (err instanceof PolyesterError) return err;\n if (isAbortError(err)) return err;\n if (err instanceof ConnectError) {\n if (err.code === Code.Canceled) return err;\n const wrapped = findPolyesterErrorInCauseChain(err);\n if (wrapped) return wrapped;\n return connectErrorToPolyesterError(err);\n }\n return err;\n}\n\nasync function* mapStreamErrors<T>(source: AsyncIterable<T>): AsyncIterable<T> {\n try {\n yield* source;\n } catch (err) {\n throw toPolyesterError(err);\n }\n}\n\n/**\n * Interceptor that converts every RPC failure (unary and streaming) into the\n * typed {@link PolyesterError} hierarchy. Installed outermost by\n * `createTransports`, so callers of SDK services always see typed errors.\n */\nexport function createErrorMappingInterceptor(): Interceptor {\n return (next) => async (req) => {\n try {\n const res = await next(req);\n if (res.stream) {\n return { ...res, message: mapStreamErrors(res.message) };\n }\n return res;\n } catch (err) {\n throw toPolyesterError(err);\n }\n };\n}\n\n/**\n * Wraps a Connect transport so errors are translated after Connect's call\n * runner has applied its own error normalization.\n */\nexport function createErrorMappingTransport(transport: Transport): Transport {\n return {\n async unary(method, signal, timeoutMs, header, input, contextValues) {\n try {\n return await transport.unary(\n method,\n signal,\n timeoutMs,\n header,\n input,\n contextValues,\n );\n } catch (error) {\n throw toPolyesterError(error);\n }\n },\n async stream(method, signal, timeoutMs, header, input, contextValues) {\n try {\n const response = await transport.stream(\n method,\n signal,\n timeoutMs,\n header,\n input,\n contextValues,\n );\n return { ...response, message: mapStreamErrors(response.message) };\n } catch (error) {\n throw toPolyesterError(error);\n }\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"connect-error-mapping.js","names":["OrderErrorDetailSchema"],"sources":["../../src/shared/connect-error-mapping.ts"],"sourcesContent":["import { Code, ConnectError, type Interceptor, type Transport } from \"@connectrpc/connect\";\nimport * as v from \"valibot\";\nimport { AuthErrorCode, AuthErrorDetailSchema } from \"../gen/auth/v1/auth_pb.js\";\nimport {\n ErrorCode as OrderErrorCode,\n ErrorDetailSchema as OrderErrorDetailSchema,\n} from \"../gen/orders/v1/orders_pb.js\";\nimport {\n AlreadyExistsError,\n AuthenticationError,\n InternalServerError,\n isAbortError,\n MfaEnrollmentRequiredError,\n MfaLastFactorRequiredError,\n MfaVerificationError,\n type MfaVerificationFailureReason,\n normalizeErrorMessage,\n PermissionError,\n PolicyInUseError,\n PolicyLockedError,\n PolicyScopeMismatchError,\n PolyesterError,\n type PolyesterErrorOptions,\n PreconditionFailedError,\n RateLimitError,\n ResourceNotFoundError,\n RevisionConflictError,\n ServiceUnavailableError,\n SessionElevationRequiredError,\n StaleQuoteError,\n StepUpRequiredError,\n TimeoutError,\n TransientError,\n ValidationError,\n} from \"./errors.js\";\nimport { RateLimitDetailSchema, type RateLimitDetail } from \"./rate-limit.schemas.js\";\n\nfunction getAuthErrorDetails(err: unknown) {\n return ConnectError.from(err).findDetails(AuthErrorDetailSchema);\n}\n\nfunction hasAuthErrorCode(err: unknown, code: AuthErrorCode): boolean {\n return getAuthErrorDetails(err).some((detail) => detail.code === code);\n}\n\nfunction hasOrderErrorCode(err: unknown, code: OrderErrorCode): boolean {\n return getOrderErrorDetails(err).some((detail) => detail.code === code);\n}\n\nfunction getOrderErrorDetails(err: unknown) {\n return ConnectError.from(err).findDetails(OrderErrorDetailSchema);\n}\n\nfunction getRateLimitDetail(err: unknown): RateLimitDetail | undefined {\n for (const orderDetail of getOrderErrorDetails(err)) {\n if (!orderDetail.rateLimit) continue;\n const result = v.safeParse(RateLimitDetailSchema, orderDetail.rateLimit);\n if (result.success) return result.output;\n }\n return undefined;\n}\n\nfunction safeRetryAfterMs(rateLimit: RateLimitDetail | undefined): number | undefined {\n if (rateLimit?.retryAfterMs === undefined) return undefined;\n const value = BigInt(rateLimit.retryAfterMs);\n return value <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(value) : undefined;\n}\n\nconst HTTP_WEEKDAY = \"(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\";\nconst HTTP_WEEKDAY_LONG = \"(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)\";\nconst HTTP_MONTH = \"(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\";\nconst HTTP_DATE_RE = new RegExp(\n `^(?:${HTTP_WEEKDAY}, \\\\d{2} ${HTTP_MONTH} \\\\d{4} \\\\d{2}:\\\\d{2}:\\\\d{2} GMT|${HTTP_WEEKDAY_LONG}, \\\\d{2}-${HTTP_MONTH}-\\\\d{2} \\\\d{2}:\\\\d{2}:\\\\d{2} GMT|${HTTP_WEEKDAY} ${HTTP_MONTH} {1,2}\\\\d{1,2} \\\\d{2}:\\\\d{2}:\\\\d{2} \\\\d{4})$`,\n \"u\",\n);\n\n/**\n * Returns the error message without the leading \"[code] \" prefix that\n * `ConnectError` prepends, e.g. \"[invalid_argument] Insufficient funds.\"\n * becomes \"Insufficient funds.\".\n */\nexport function getNormalizedConnectMessage(err: unknown): string {\n const ce = ConnectError.from(err);\n const raw = ce.message ?? \"\";\n return normalizeErrorMessage(raw);\n}\n\nexport type MfaErrorKind = \"session-elevation\" | \"enrollment\" | \"step-up\";\n\n/**\n * Classifies an error as one of the MFA flows the backend can demand.\n * Classification is based only on stable structured auth error details.\n */\nexport function detectMfaErrorKind(err: unknown): MfaErrorKind | null {\n if (hasAuthErrorCode(err, AuthErrorCode.AUTH_MFA_ELEVATION_REQUIRED)) {\n return \"session-elevation\";\n }\n if (hasAuthErrorCode(err, AuthErrorCode.AUTH_STEP_UP_REQUIRED)) return \"step-up\";\n if (hasAuthErrorCode(err, AuthErrorCode.AUTH_MFA_NOT_ENROLLED)) return \"enrollment\";\n return null;\n}\n\nfunction parseRetryAfterMs(ce: ConnectError): number | undefined {\n const value = ce.metadata.get(\"retry-after\");\n if (!value) return undefined;\n if (/^\\d+$/u.test(value)) {\n const seconds = BigInt(value);\n const maxSafeSeconds = BigInt(Math.floor(Number.MAX_SAFE_INTEGER / 1000));\n return seconds <= maxSafeSeconds ? Number(seconds) * 1000 : undefined;\n }\n if (!HTTP_DATE_RE.test(value)) return undefined;\n const date = Date.parse(value);\n if (!Number.isNaN(date)) return Math.max(0, date - Date.now());\n return undefined;\n}\n\nconst MFA_ERROR_CLASSES = {\n \"session-elevation\": SessionElevationRequiredError,\n enrollment: MfaEnrollmentRequiredError,\n \"step-up\": StepUpRequiredError,\n} as const;\n\n/**\n * Maps a `ConnectError` from the Polyester backend onto the typed\n * {@link PolyesterError} tree. The original error is preserved as `cause`.\n */\nexport function connectErrorToPolyesterError(ce: ConnectError): PolyesterError {\n const message = getNormalizedConnectMessage(ce);\n const options: PolyesterErrorOptions = { cause: ce };\n const withFallback = (fallback: string) => message || fallback;\n\n if (hasAuthErrorCode(ce, AuthErrorCode.AUTH_RESOURCE_NOT_FOUND)) {\n return new ResourceNotFoundError(withFallback(\"Resource not found.\"), options);\n }\n\n if (hasAuthErrorCode(ce, AuthErrorCode.AUTH_REVISION_CONFLICT)) {\n return new RevisionConflictError(\n withFallback(\"Resource changed since it was last read.\"),\n options,\n );\n }\n\n if (hasAuthErrorCode(ce, AuthErrorCode.AUTH_POLICY_IN_USE)) {\n return new PolicyInUseError(withFallback(\"Policy is still in use.\"), options);\n }\n if (hasAuthErrorCode(ce, AuthErrorCode.AUTH_POLICY_LOCKED)) {\n return new PolicyLockedError(withFallback(\"Policy is locked.\"), options);\n }\n if (hasAuthErrorCode(ce, AuthErrorCode.AUTH_POLICY_SCOPE_MISMATCH)) {\n return new PolicyScopeMismatchError(\n withFallback(\"Policy does not belong to the target account scope.\"),\n options,\n );\n }\n if (hasAuthErrorCode(ce, AuthErrorCode.AUTH_MFA_LAST_FACTOR_REQUIRED)) {\n return new MfaLastFactorRequiredError(\n withFallback(\"At least one active MFA factor must remain enrolled.\"),\n options,\n );\n }\n if (hasAuthErrorCode(ce, AuthErrorCode.AUTH_INTERNAL_ERROR)) {\n return new InternalServerError(withFallback(\"Internal server error.\"), options);\n }\n\n const mfaVerificationReason = getMfaVerificationFailureReason(ce);\n if (mfaVerificationReason) {\n return new MfaVerificationError(\n withFallback(\"MFA verification failed.\"),\n mfaVerificationReason,\n options,\n );\n }\n\n const mfaKind = detectMfaErrorKind(ce);\n if (mfaKind) {\n return new MFA_ERROR_CLASSES[mfaKind](\n withFallback(\"Multi-factor authentication required.\"),\n options,\n );\n }\n\n if (hasOrderErrorCode(ce, OrderErrorCode.STALE_QUOTE)) {\n return new StaleQuoteError(withFallback(\"The submitted market quote is stale.\"), options);\n }\n\n const rateLimit = getRateLimitDetail(ce);\n if (rateLimit || hasOrderErrorCode(ce, OrderErrorCode.RATE_LIMIT_EXCEEDED)) {\n return new RateLimitError(withFallback(\"Rate limit exceeded.\"), {\n ...options,\n rateLimit,\n retryAfterMs: safeRetryAfterMs(rateLimit) ?? parseRetryAfterMs(ce),\n });\n }\n\n switch (ce.code) {\n case Code.InvalidArgument:\n case Code.OutOfRange:\n return new ValidationError(withFallback(\"Invalid request.\"), options);\n case Code.NotFound:\n return new ResourceNotFoundError(withFallback(\"Resource not found.\"), options);\n case Code.AlreadyExists:\n return new AlreadyExistsError(withFallback(\"Resource already exists.\"), options);\n case Code.PermissionDenied:\n return new PermissionError(withFallback(\"Permission denied.\"), options);\n case Code.Unauthenticated:\n return new AuthenticationError(withFallback(\"Authentication required.\"), options);\n case Code.FailedPrecondition:\n return new PreconditionFailedError(withFallback(\"Precondition failed.\"), options);\n case Code.ResourceExhausted:\n return new RateLimitError(withFallback(\"Rate limit exceeded.\"), {\n ...options,\n retryAfterMs: parseRetryAfterMs(ce),\n });\n case Code.DeadlineExceeded:\n return new TimeoutError(withFallback(\"Request timed out.\"), options);\n case Code.Unavailable:\n return new ServiceUnavailableError(withFallback(\"Service unavailable.\"), options);\n case Code.Aborted:\n return new TransientError(withFallback(\"Operation aborted by the backend.\"), options);\n default:\n // Gateways sometimes surface outages as Unknown with this stock phrase.\n if (/service temporarily unavailable/i.test(message)) {\n return new ServiceUnavailableError(withFallback(\"Service unavailable.\"), options);\n }\n return new InternalServerError(withFallback(\"Internal server error.\"), options);\n }\n}\n\nfunction getMfaVerificationFailureReason(err: unknown): MfaVerificationFailureReason | null {\n if (hasAuthErrorCode(err, AuthErrorCode.AUTH_MFA_CHALLENGE_INVALID)) {\n return \"challenge-invalid\";\n }\n if (hasAuthErrorCode(err, AuthErrorCode.AUTH_MFA_CHALLENGE_LOCKED)) {\n return \"challenge-locked\";\n }\n if (hasAuthErrorCode(err, AuthErrorCode.AUTH_MFA_OTP_INVALID)) return \"otp-invalid\";\n if (hasAuthErrorCode(err, AuthErrorCode.AUTH_MFA_RECOVERY_INVALID)) {\n return \"recovery-code-invalid\";\n }\n if (\n hasAuthErrorCode(err, AuthErrorCode.AUTH_MFA_PASSKEY_CREDENTIAL_INVALID) ||\n hasAuthErrorCode(err, AuthErrorCode.AUTH_MFA_PASSKEY_VERIFY_FAILED)\n ) {\n return \"passkey-invalid\";\n }\n\n return null;\n}\n\nfunction findPolyesterErrorInCauseChain(err: unknown): PolyesterError | null {\n let current: unknown = err;\n for (let depth = 0; depth < 5 && current instanceof Error; depth++) {\n current = current.cause;\n if (current instanceof PolyesterError) return current;\n }\n return null;\n}\n\n/**\n * Converts any RPC-layer failure into its typed SDK error. Abort errors and\n * caller-cancelled requests pass through unchanged, as do errors that are\n * already typed (e.g. a `NetworkError` from the SDK fetch wrapper).\n */\nexport function toPolyesterError(err: unknown): unknown {\n if (err instanceof PolyesterError) return err;\n if (isAbortError(err)) return err;\n if (err instanceof ConnectError) {\n if (err.code === Code.Canceled) return err;\n const wrapped = findPolyesterErrorInCauseChain(err);\n if (wrapped) return wrapped;\n return connectErrorToPolyesterError(err);\n }\n return err;\n}\n\nasync function* mapStreamErrors<T>(source: AsyncIterable<T>): AsyncIterable<T> {\n try {\n yield* source;\n } catch (err) {\n throw toPolyesterError(err);\n }\n}\n\n/**\n * Interceptor that converts every RPC failure (unary and streaming) into the\n * typed {@link PolyesterError} hierarchy. Installed outermost by\n * `createTransports`, so callers of SDK services always see typed errors.\n */\nexport function createErrorMappingInterceptor(): Interceptor {\n return (next) => async (req) => {\n try {\n const res = await next(req);\n if (res.stream) {\n return { ...res, message: mapStreamErrors(res.message) };\n }\n return res;\n } catch (err) {\n throw toPolyesterError(err);\n }\n };\n}\n\n/**\n * Wraps a Connect transport so errors are translated after Connect's call\n * runner has applied its own error normalization.\n */\nexport function createErrorMappingTransport(transport: Transport): Transport {\n return {\n async unary(method, signal, timeoutMs, header, input, contextValues) {\n try {\n return await transport.unary(\n method,\n signal,\n timeoutMs,\n header,\n input,\n contextValues,\n );\n } catch (error) {\n throw toPolyesterError(error);\n }\n },\n async stream(method, signal, timeoutMs, header, input, contextValues) {\n try {\n const response = await transport.stream(\n method,\n signal,\n timeoutMs,\n header,\n input,\n contextValues,\n );\n return { ...response, message: mapStreamErrors(response.message) };\n } catch (error) {\n throw toPolyesterError(error);\n }\n },\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"decimal-surface.d.ts","names":[],"sources":["../../src/shared/decimal-surface.ts"],"mappings":";;;;;;;UA8BiB;EACb,SAAS;EACT;EACA,QAAQ,MAAM;EACd,YAAY,MAAM;EAClB,aAAa;EACb,kBAAkB"}
1
+ {"version":3,"file":"decimal-surface.d.ts","names":[],"sources":["../../src/shared/decimal-surface.ts"],"mappings":";;;;;;;UAiCiB;EACb,SAAS;EACT;EACA,QAAQ,MAAM;EACd,YAAY,MAAM;EAClB,aAAa;EACb,kBAAkB"}
@@ -1,6 +1,6 @@
1
1
  import "./wire-bounds.js";
2
2
  import { scaledToDecimal, tryDecimalToScaled } from "../catalogs/decimal.js";
3
- import { CatalogConversionError } from "../catalogs/types.js";
3
+ import { CatalogConversionError, CatalogLookupError, CatalogNotReadyError } from "../catalogs/types.js";
4
4
  /**
5
5
  * Catalog-backed scale resolver. Takes a getter so it can be constructed
6
6
  * before the owning client has assigned its catalog.
@@ -13,7 +13,11 @@ function createCatalogSdkScales(getCatalog) {
13
13
  price: () => 6,
14
14
  baseQty: (pair) => requirePair(getCatalog(), pair).baseAsset.quantityScale,
15
15
  quoteAmount: (pair) => requirePair(getCatalog(), pair).quoteAsset.quantityScale,
16
- ledgerAmount: (ledgerAssetId) => getCatalog().ledger.requireAssetByLedgerId(ledgerAssetId).quantityScale,
16
+ ledgerAmount: (ledgerAssetId) => {
17
+ const ledger = getCatalog().ledger;
18
+ if (!ledger.isKnownAssetId(ledgerAssetId)) throw new CatalogLookupError("ledger", "ledgerId", ledgerAssetId);
19
+ return ledger.requireAssetByLedgerId(ledgerAssetId).quantityScale;
20
+ },
17
21
  zippedAssetAmount: (zippedAssetId) => getCatalog().zipper.requireAssetChainByZippedAssetId(zippedAssetId).asset.quantityScale
18
22
  };
19
23
  }
@@ -29,7 +33,7 @@ function conversionFailureMessage(field, value, failure) {
29
33
  }
30
34
  /**
31
35
  * Input direction: strict decimal→scaled conversion. Throws
32
- * CatalogConversionError for non-decimal input or excess precision.
36
+ * CatalogConversionError for non-decimal input or inexact excess precision.
33
37
  */
34
38
  function decimalInputToScaled(field, value, scale) {
35
39
  const result = tryDecimalToScaled(value.trim(), scale);
@@ -61,36 +65,55 @@ function quantityInputToE18(params) {
61
65
  if (assetScale > 18) throw new CatalogConversionError(field, `${field} asset quantityScale ${assetScale} exceeds E18 ledger scale`);
62
66
  return unboundedPositiveDecimalInputToScaled(field, params.quantity, assetScale) * 10n ** BigInt(18 - assetScale);
63
67
  }
68
+ const READY_GATE_MAX_PENDING_DELIVERIES = 1024;
64
69
  /**
65
- * Orders event delivery behind catalog readiness. Events arriving before the
70
+ * Transition event delivery behind catalog readiness. Events arriving before the
66
71
  * catalog can resolve scales are queued and flushed in arrival order once it
67
- * is ready; delivery errors (including post-flush ones) route to `onError`
68
- * instead of escaping into the transport, mirroring the realtime client's
69
- * consumer-handler isolation. If readiness itself fails, queued events are
70
- * dropped after `onError` fires — the stream cannot be decoded without scales.
72
+ * is ready. Delivery errors are isolated from the transport. Readiness failure
73
+ * or queue overflow is terminal because dropping an event would break stream
74
+ * continuity; the owning subscription must visibly terminate when notified.
71
75
  */
72
- function createReadyGate(ready, onError) {
76
+ function createReadyGate(ready, handlers) {
73
77
  let state = "pending";
74
78
  const queue = [];
75
79
  const deliverIsolated = (deliver) => {
76
80
  try {
77
81
  deliver();
78
82
  } catch (error) {
79
- onError?.(error);
83
+ handlers.onDeliveryError?.(error);
80
84
  }
81
85
  };
82
- ready().then(() => {
86
+ const closeWithError = (error) => {
87
+ if (state === "closed") return;
88
+ state = "closed";
89
+ queue.length = 0;
90
+ handlers.onTerminalError(error);
91
+ };
92
+ Promise.resolve().then(ready).then(() => {
93
+ if (state !== "pending") return;
83
94
  state = "open";
84
95
  for (const deliver of queue.splice(0)) deliverIsolated(deliver);
85
96
  }, (error) => {
86
- state = "failed";
87
- queue.length = 0;
88
- onError?.(error);
97
+ if (state !== "pending") return;
98
+ closeWithError(error);
89
99
  });
90
- return { run(deliver) {
91
- if (state === "open") deliverIsolated(deliver);
92
- else if (state === "pending") queue.push(deliver);
93
- } };
100
+ return {
101
+ run(deliver) {
102
+ if (state === "open") deliverIsolated(deliver);
103
+ else if (state === "pending") {
104
+ if (queue.length === READY_GATE_MAX_PENDING_DELIVERIES) {
105
+ closeWithError(new CatalogNotReadyError());
106
+ return;
107
+ }
108
+ queue.push(deliver);
109
+ }
110
+ },
111
+ close() {
112
+ if (state === "closed") return;
113
+ state = "closed";
114
+ queue.length = 0;
115
+ }
116
+ };
94
117
  }
95
118
  //#endregion
96
119
  export { createCatalogSdkScales, createReadyGate, decimalInputToScaled, positiveDecimalInputToScaled, quantityInputToE18, scaledToDecimalOutput };
@@ -1 +1 @@
1
- {"version":3,"file":"decimal-surface.js","names":[],"sources":["../../src/shared/decimal-surface.ts"],"sourcesContent":["/**\n * Internal scaled⇄decimal bridge for the SDK's public decimal-string surface.\n *\n * The wire protocol carries scaled integers (price ticks, per-asset scaled\n * quantities); the public SDK surface carries plain decimal strings. Output\n * schemas convert exactly (no rounding, trailing zeros trimmed); input schemas\n * convert strictly — excess precision is an error, never rounded away. Scaled\n * integers must not escape through any service input or output.\n */\nimport {\n scaledToDecimal,\n tryDecimalToScaled,\n type DecimalToScaledFailure,\n} from \"../catalogs/decimal.js\";\nimport {\n CatalogConversionError,\n type ClientCatalog,\n type PairCatalogKey,\n} from \"../catalogs/types.js\";\nimport { PRICE_SCALE } from \"../catalogs/readers.js\";\nimport { PROTOBUF_INT64_MAX } from \"./wire-bounds.js\";\n\n/** On-chain unified-asset amounts (`amountE18`) are always 18-decimal scaled. */\nexport const E18_SCALE = 18;\n\n/**\n * Resolves wire scales for the SDK's internal decimal conversion. Backed by\n * the client catalog; `ready()` must be awaited before the synchronous lookups\n * are used so the catalog can answer them.\n */\nexport interface SdkScales {\n ready(): Promise<void>;\n price(): number;\n baseQty(pair: PairCatalogKey): number;\n quoteAmount(pair: PairCatalogKey): number;\n ledgerAmount(ledgerAssetId: number): number;\n zippedAssetAmount(zippedAssetId: number): number;\n}\n\n/**\n * Catalog-backed scale resolver. Takes a getter so it can be constructed\n * before the owning client has assigned its catalog.\n */\nexport function createCatalogSdkScales(getCatalog: () => ClientCatalog): SdkScales {\n return {\n ready: async () => {\n await getCatalog().ensureReady();\n },\n price: () => PRICE_SCALE,\n baseQty: (pair) => requirePair(getCatalog(), pair).baseAsset.quantityScale,\n quoteAmount: (pair) => requirePair(getCatalog(), pair).quoteAsset.quantityScale,\n ledgerAmount: (ledgerAssetId) =>\n getCatalog().ledger.requireAssetByLedgerId(ledgerAssetId).quantityScale,\n zippedAssetAmount: (zippedAssetId) =>\n getCatalog().zipper.requireAssetChainByZippedAssetId(zippedAssetId).asset.quantityScale,\n };\n}\n\nfunction requirePair(catalog: ClientCatalog, pair: PairCatalogKey) {\n return typeof pair === \"string\" || (typeof pair === \"object\" && \"symbol\" in pair)\n ? catalog.market.requirePairBySymbol(typeof pair === \"string\" ? pair : pair.symbol)\n : catalog.market.requirePairBySymbolId(typeof pair === \"number\" ? pair : pair.symbolId);\n}\n\n/** Output direction: exact scaled→decimal conversion (`1500000n`@6 → `\"1.5\"`). */\nexport function scaledToDecimalOutput(value: bigint, scale: number): string {\n return scaledToDecimal(value, scale);\n}\n\nfunction conversionFailureMessage(\n field: string,\n value: string,\n failure: DecimalToScaledFailure,\n): string {\n return failure.reason === \"precision\"\n ? `${field} supports at most ${failure.maxDecimals} decimal places: ${value}`\n : `${field} must be a non-negative decimal number: ${value}`;\n}\n\n/**\n * Input direction: strict decimal→scaled conversion. Throws\n * CatalogConversionError for non-decimal input or excess precision.\n */\nexport function decimalInputToScaled(field: string, value: string, scale: number): bigint {\n const result = tryDecimalToScaled(value.trim(), scale);\n if (!result.ok) {\n throw new CatalogConversionError(\n field,\n conversionFailureMessage(field, value, result.failure),\n );\n }\n return result.scaled;\n}\n\nfunction unboundedPositiveDecimalInputToScaled(\n field: string,\n value: string,\n scale: number,\n): bigint {\n const scaled = decimalInputToScaled(field, value, scale);\n if (scaled <= 0n) {\n throw new CatalogConversionError(field, `${field} must be greater than 0: ${value}`);\n }\n return scaled;\n}\n\n/**\n * Converts a positive decimal input to a scaled protobuf `int64` value.\n * Rejects values above the wire-format ceiling before serialization.\n */\nexport function positiveDecimalInputToScaled(field: string, value: string, scale: number): bigint {\n const scaled = unboundedPositiveDecimalInputToScaled(field, value, scale);\n if (scaled > PROTOBUF_INT64_MAX) {\n throw new CatalogConversionError(\n field,\n `${field} exceeds the maximum supported value: ${value}`,\n );\n }\n return scaled;\n}\n\n/**\n * Decimal quantity → PolyesterChain E18 ledger units.\n * Input precision is capped at the asset's quantityScale; the wire value is\n * always upscaled to E18 (trading balances / amount_e18 convention).\n */\nexport function quantityInputToE18(params: {\n scales: SdkScales;\n assetId: number;\n quantity: string;\n field?: string;\n}): bigint {\n const field = params.field ?? \"quantity\";\n const assetScale = params.scales.ledgerAmount(params.assetId);\n if (assetScale > E18_SCALE) {\n throw new CatalogConversionError(\n field,\n `${field} asset quantityScale ${assetScale} exceeds E18 ledger scale`,\n );\n }\n return (\n unboundedPositiveDecimalInputToScaled(field, params.quantity, assetScale) *\n 10n ** BigInt(E18_SCALE - assetScale)\n );\n}\n\nexport interface ReadyGate {\n run(deliver: () => void): void;\n}\n\n/**\n * Orders event delivery behind catalog readiness. Events arriving before the\n * catalog can resolve scales are queued and flushed in arrival order once it\n * is ready; delivery errors (including post-flush ones) route to `onError`\n * instead of escaping into the transport, mirroring the realtime client's\n * consumer-handler isolation. If readiness itself fails, queued events are\n * dropped after `onError` fires — the stream cannot be decoded without scales.\n */\nexport function createReadyGate(\n ready: () => Promise<void>,\n onError?: (error: unknown) => void,\n): ReadyGate {\n let state: \"pending\" | \"open\" | \"failed\" = \"pending\";\n const queue: Array<() => void> = [];\n\n const deliverIsolated = (deliver: () => void) => {\n try {\n deliver();\n } catch (error) {\n onError?.(error);\n }\n };\n\n ready().then(\n () => {\n state = \"open\";\n for (const deliver of queue.splice(0)) deliverIsolated(deliver);\n },\n (error) => {\n state = \"failed\";\n queue.length = 0;\n onError?.(error);\n },\n );\n\n return {\n run(deliver) {\n if (state === \"open\") deliverIsolated(deliver);\n else if (state === \"pending\") queue.push(deliver);\n },\n };\n}\n"],"mappings":";;;;;;;AA2CA,SAAgB,uBAAuB,YAA4C;CAC/E,OAAO;EACH,OAAO,YAAY;GACf,MAAM,WAAW,CAAC,CAAC,YAAY;EACnC;EACA,aAAA;EACA,UAAU,SAAS,YAAY,WAAW,GAAG,IAAI,CAAC,CAAC,UAAU;EAC7D,cAAc,SAAS,YAAY,WAAW,GAAG,IAAI,CAAC,CAAC,WAAW;EAClE,eAAe,kBACX,WAAW,CAAC,CAAC,OAAO,uBAAuB,aAAa,CAAC,CAAC;EAC9D,oBAAoB,kBAChB,WAAW,CAAC,CAAC,OAAO,iCAAiC,aAAa,CAAC,CAAC,MAAM;CAClF;AACJ;AAEA,SAAS,YAAY,SAAwB,MAAsB;CAC/D,OAAO,OAAO,SAAS,YAAa,OAAO,SAAS,YAAY,YAAY,OACtE,QAAQ,OAAO,oBAAoB,OAAO,SAAS,WAAW,OAAO,KAAK,MAAM,IAChF,QAAQ,OAAO,sBAAsB,OAAO,SAAS,WAAW,OAAO,KAAK,QAAQ;AAC9F;;AAGA,SAAgB,sBAAsB,OAAe,OAAuB;CACxE,OAAO,gBAAgB,OAAO,KAAK;AACvC;AAEA,SAAS,yBACL,OACA,OACA,SACM;CACN,OAAO,QAAQ,WAAW,cACpB,GAAG,MAAM,oBAAoB,QAAQ,YAAY,mBAAmB,UACpE,GAAG,MAAM,0CAA0C;AAC7D;;;;;AAMA,SAAgB,qBAAqB,OAAe,OAAe,OAAuB;CACtF,MAAM,SAAS,mBAAmB,MAAM,KAAK,GAAG,KAAK;CACrD,IAAI,CAAC,OAAO,IACR,MAAM,IAAI,uBACN,OACA,yBAAyB,OAAO,OAAO,OAAO,OAAO,CACzD;CAEJ,OAAO,OAAO;AAClB;AAEA,SAAS,sCACL,OACA,OACA,OACM;CACN,MAAM,SAAS,qBAAqB,OAAO,OAAO,KAAK;CACvD,IAAI,UAAU,IACV,MAAM,IAAI,uBAAuB,OAAO,GAAG,MAAM,2BAA2B,OAAO;CAEvF,OAAO;AACX;;;;;AAMA,SAAgB,6BAA6B,OAAe,OAAe,OAAuB;CAC9F,MAAM,SAAS,sCAAsC,OAAO,OAAO,KAAK;CACxE,IAAI,SAAA,sBACA,MAAM,IAAI,uBACN,OACA,GAAG,MAAM,wCAAwC,OACrD;CAEJ,OAAO;AACX;;;;;;AAOA,SAAgB,mBAAmB,QAKxB;CACP,MAAM,QAAQ,OAAO,SAAS;CAC9B,MAAM,aAAa,OAAO,OAAO,aAAa,OAAO,OAAO;CAC5D,IAAI,aAAA,IACA,MAAM,IAAI,uBACN,OACA,GAAG,MAAM,uBAAuB,WAAW,0BAC/C;CAEJ,OACI,sCAAsC,OAAO,OAAO,UAAU,UAAU,IACxE,OAAO,OAAA,KAAmB,UAAU;AAE5C;;;;;;;;;AAcA,SAAgB,gBACZ,OACA,SACS;CACT,IAAI,QAAuC;CAC3C,MAAM,QAA2B,CAAC;CAElC,MAAM,mBAAmB,YAAwB;EAC7C,IAAI;GACA,QAAQ;EACZ,SAAS,OAAO;GACZ,UAAU,KAAK;EACnB;CACJ;CAEA,MAAM,CAAC,CAAC,WACE;EACF,QAAQ;EACR,KAAK,MAAM,WAAW,MAAM,OAAO,CAAC,GAAG,gBAAgB,OAAO;CAClE,IACC,UAAU;EACP,QAAQ;EACR,MAAM,SAAS;EACf,UAAU,KAAK;CACnB,CACJ;CAEA,OAAO,EACH,IAAI,SAAS;EACT,IAAI,UAAU,QAAQ,gBAAgB,OAAO;OACxC,IAAI,UAAU,WAAW,MAAM,KAAK,OAAO;CACpD,EACJ;AACJ"}
1
+ {"version":3,"file":"decimal-surface.js","names":[],"sources":["../../src/shared/decimal-surface.ts"],"sourcesContent":["/**\n * Internal scaled⇄decimal bridge for the SDK's public decimal-string surface.\n *\n * The wire protocol carries scaled integers (price ticks, per-asset scaled\n * quantities); the public SDK surface carries plain decimal strings. Output\n * schemas convert exactly (no rounding, trailing zeros trimmed); input schemas\n * accept zero padding but reject fractional precision that remains above the\n * scale after trailing zeros are removed. Scaled integers must not escape\n * through any service input or output.\n */\nimport {\n scaledToDecimal,\n tryDecimalToScaled,\n type DecimalToScaledFailure,\n} from \"../catalogs/decimal.js\";\nimport {\n CatalogConversionError,\n CatalogLookupError,\n CatalogNotReadyError,\n type ClientCatalog,\n type PairCatalogKey,\n} from \"../catalogs/types.js\";\nimport { PRICE_SCALE } from \"../catalogs/readers.js\";\nimport { PROTOBUF_INT64_MAX } from \"./wire-bounds.js\";\n\n/** On-chain unified-asset amounts (`amountE18`) are always 18-decimal scaled. */\nexport const E18_SCALE = 18;\n\n/**\n * Resolves wire scales for the SDK's internal decimal conversion. Backed by\n * the client catalog; `ready()` must be awaited before the synchronous lookups\n * are used so the catalog can answer them.\n */\nexport interface SdkScales {\n ready(): Promise<void>;\n price(): number;\n baseQty(pair: PairCatalogKey): number;\n quoteAmount(pair: PairCatalogKey): number;\n ledgerAmount(ledgerAssetId: number): number;\n zippedAssetAmount(zippedAssetId: number): number;\n}\n\n/**\n * Catalog-backed scale resolver. Takes a getter so it can be constructed\n * before the owning client has assigned its catalog.\n */\nexport function createCatalogSdkScales(getCatalog: () => ClientCatalog): SdkScales {\n return {\n ready: async () => {\n await getCatalog().ensureReady();\n },\n price: () => PRICE_SCALE,\n baseQty: (pair) => requirePair(getCatalog(), pair).baseAsset.quantityScale,\n quoteAmount: (pair) => requirePair(getCatalog(), pair).quoteAsset.quantityScale,\n ledgerAmount: (ledgerAssetId) => {\n const ledger = getCatalog().ledger;\n if (!ledger.isKnownAssetId(ledgerAssetId)) {\n throw new CatalogLookupError(\"ledger\", \"ledgerId\", ledgerAssetId);\n }\n return ledger.requireAssetByLedgerId(ledgerAssetId).quantityScale;\n },\n zippedAssetAmount: (zippedAssetId) =>\n getCatalog().zipper.requireAssetChainByZippedAssetId(zippedAssetId).asset.quantityScale,\n };\n}\n\nfunction requirePair(catalog: ClientCatalog, pair: PairCatalogKey) {\n return typeof pair === \"string\" || (typeof pair === \"object\" && \"symbol\" in pair)\n ? catalog.market.requirePairBySymbol(typeof pair === \"string\" ? pair : pair.symbol)\n : catalog.market.requirePairBySymbolId(typeof pair === \"number\" ? pair : pair.symbolId);\n}\n\n/** Output direction: exact scaled→decimal conversion (`1500000n`@6 → `\"1.5\"`). */\nexport function scaledToDecimalOutput(value: bigint, scale: number): string {\n return scaledToDecimal(value, scale);\n}\n\nfunction conversionFailureMessage(\n field: string,\n value: string,\n failure: DecimalToScaledFailure,\n): string {\n return failure.reason === \"precision\"\n ? `${field} supports at most ${failure.maxDecimals} decimal places: ${value}`\n : `${field} must be a non-negative decimal number: ${value}`;\n}\n\n/**\n * Input direction: strict decimal→scaled conversion. Throws\n * CatalogConversionError for non-decimal input or inexact excess precision.\n */\nexport function decimalInputToScaled(field: string, value: string, scale: number): bigint {\n const result = tryDecimalToScaled(value.trim(), scale);\n if (!result.ok) {\n throw new CatalogConversionError(\n field,\n conversionFailureMessage(field, value, result.failure),\n );\n }\n return result.scaled;\n}\n\nfunction unboundedPositiveDecimalInputToScaled(\n field: string,\n value: string,\n scale: number,\n): bigint {\n const scaled = decimalInputToScaled(field, value, scale);\n if (scaled <= 0n) {\n throw new CatalogConversionError(field, `${field} must be greater than 0: ${value}`);\n }\n return scaled;\n}\n\n/**\n * Converts a positive decimal input to a scaled protobuf `int64` value.\n * Rejects values above the wire-format ceiling before serialization.\n */\nexport function positiveDecimalInputToScaled(field: string, value: string, scale: number): bigint {\n const scaled = unboundedPositiveDecimalInputToScaled(field, value, scale);\n if (scaled > PROTOBUF_INT64_MAX) {\n throw new CatalogConversionError(\n field,\n `${field} exceeds the maximum supported value: ${value}`,\n );\n }\n return scaled;\n}\n\n/**\n * Decimal quantity → PolyesterChain E18 ledger units.\n * Input precision is capped at the asset's quantityScale; the wire value is\n * always upscaled to E18 (trading balances / amount_e18 convention).\n */\nexport function quantityInputToE18(params: {\n scales: SdkScales;\n assetId: number;\n quantity: string;\n field?: string;\n}): bigint {\n const field = params.field ?? \"quantity\";\n const assetScale = params.scales.ledgerAmount(params.assetId);\n if (assetScale > E18_SCALE) {\n throw new CatalogConversionError(\n field,\n `${field} asset quantityScale ${assetScale} exceeds E18 ledger scale`,\n );\n }\n return (\n unboundedPositiveDecimalInputToScaled(field, params.quantity, assetScale) *\n 10n ** BigInt(E18_SCALE - assetScale)\n );\n}\n\nexport interface ReadyGate {\n /** Queues or delivers one transition while the gate remains active. */\n run(deliver: () => void): void;\n /** Drops pending transitions and permanently prevents future delivery. */\n close(): void;\n}\n\ninterface ReadyGateHandlers {\n onDeliveryError?: (error: unknown) => void;\n onTerminalError: (error: unknown) => void;\n}\n\nconst READY_GATE_MAX_PENDING_DELIVERIES = 1_024;\n\n/**\n * Transition event delivery behind catalog readiness. Events arriving before the\n * catalog can resolve scales are queued and flushed in arrival order once it\n * is ready. Delivery errors are isolated from the transport. Readiness failure\n * or queue overflow is terminal because dropping an event would break stream\n * continuity; the owning subscription must visibly terminate when notified.\n */\nexport function createReadyGate(\n ready: () => Promise<void>,\n handlers: ReadyGateHandlers,\n): ReadyGate {\n let state: \"pending\" | \"open\" | \"closed\" = \"pending\";\n const queue: Array<() => void> = [];\n\n const deliverIsolated = (deliver: () => void) => {\n try {\n deliver();\n } catch (error) {\n handlers.onDeliveryError?.(error);\n }\n };\n\n const closeWithError = (error: unknown) => {\n if (state === \"closed\") return;\n state = \"closed\";\n queue.length = 0;\n handlers.onTerminalError(error);\n };\n\n Promise.resolve()\n .then(ready)\n .then(\n () => {\n if (state !== \"pending\") return;\n state = \"open\";\n for (const deliver of queue.splice(0)) deliverIsolated(deliver);\n },\n (error) => {\n if (state !== \"pending\") return;\n closeWithError(error);\n },\n );\n\n return {\n run(deliver) {\n if (state === \"open\") deliverIsolated(deliver);\n else if (state === \"pending\") {\n if (queue.length === READY_GATE_MAX_PENDING_DELIVERIES) {\n closeWithError(new CatalogNotReadyError());\n return;\n }\n queue.push(deliver);\n }\n },\n close() {\n if (state === \"closed\") return;\n state = \"closed\";\n queue.length = 0;\n },\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"transports.d.ts","names":[],"sources":["../../src/shared/transports.ts"],"mappings":";;;UAwBiB;EACb,SAAS;EACT,WAAW;;;;;UAME;EACb;;EAEA,gCAAgC;;;;;;UAOnB;EACb;EACA,gCAAgC;;EAEhC,oBAAoB,oBAAoB,QAAQ"}
1
+ {"version":3,"file":"transports.d.ts","names":[],"sources":["../../src/shared/transports.ts"],"mappings":";;;UA8BiB;EACb,SAAS;EACT,WAAW;;;;;UAME;EACb;;EAEA,gCAAgC;;;;;;UAOnB;EACb;EACA,gCAAgC;;EAEhC,oBAAoB,oBAAoB,QAAQ"}
@@ -1,4 +1,4 @@
1
- import { ConfigurationError, NetworkError, isAbortError } from "./errors.js";
1
+ import { AuthenticationError, ConfigurationError, NetworkError, PolyesterError, isAbortError } from "./errors.js";
2
2
  import { createErrorMappingTransport } from "./connect-error-mapping.js";
3
3
  import { createConnectTransport } from "@connectrpc/connect-web";
4
4
  import { toBinary, toJsonString } from "@bufbuild/protobuf";
@@ -34,7 +34,7 @@ function createTransports(config) {
34
34
  interceptors,
35
35
  fetch: makeFetch()
36
36
  }));
37
- const authInterceptors = auth ? [createAuthInterceptor(auth, { wireFormat }), ...interceptors] : interceptors;
37
+ const authInterceptors = auth ? auth.kind === "jwt" ? [createAuthInterceptor(auth, { wireFormat }), ...interceptors] : [...interceptors, createAuthInterceptor(auth, { wireFormat })] : interceptors;
38
38
  return {
39
39
  authApi: createErrorMappingTransport(createConnectTransport({
40
40
  baseUrl: apiUrl,
@@ -65,8 +65,16 @@ function canonicalQueryString(params) {
65
65
  * Creates API-key authentication headers for endpoints using the Polyester Ed25519 signing contract.
66
66
  */
67
67
  async function createApiKeyEd25519AuthHeaders(auth, request) {
68
- const [keyId, secretKey] = await Promise.all([auth.getKeyId(), auth.getSecretKey()]);
68
+ let keyId;
69
+ let secretKey;
70
+ try {
71
+ [keyId, secretKey] = await Promise.all([auth.getKeyId(), auth.getSecretKey()]);
72
+ } catch (cause) {
73
+ if (cause instanceof PolyesterError) throw cause;
74
+ throw new ConfigurationError("API key credential provider failed", { cause });
75
+ }
69
76
  if (!keyId || !secretKey) throw new ConfigurationError("Missing API key ID or secret key");
77
+ if (!(secretKey instanceof Uint8Array) || secretKey.byteLength !== 32) throw new ConfigurationError("API key secret key must contain exactly 32 bytes");
70
78
  const urlObj = new URL(request.url);
71
79
  const timestamp = request.timestamp ?? nextTimestamp();
72
80
  const bodyHash = await sha256Hex(request.body ?? /* @__PURE__ */ new Uint8Array(0));
@@ -74,27 +82,41 @@ async function createApiKeyEd25519AuthHeaders(auth, request) {
74
82
  const canonical = `${timestamp}\n${request.method}\n${urlObj.pathname}\n${canonicalQuery}\n${bodyHash}`;
75
83
  const msgBytes = new TextEncoder().encode(canonical);
76
84
  const sig = await signAsync(msgBytes, secretKey);
85
+ const signatureHex = Array.from(sig, (b) => b.toString(16).padStart(2, "0")).join("");
77
86
  return {
78
87
  "X-API-KEY-ID": keyId,
79
88
  "X-API-TIMESTAMP": timestamp,
80
- "X-API-SIGNATURE": Array.from(sig, (b) => b.toString(16).padStart(2, "0")).join("")
89
+ "X-API-SIGNATURE": signatureHex
81
90
  };
82
91
  }
83
92
  /**
93
+ * Resolves a JWT credential and translates provider failures or invalid values
94
+ * into stable SDK errors.
95
+ */
96
+ async function resolveJwtToken(auth) {
97
+ let token;
98
+ try {
99
+ token = await auth.getToken();
100
+ } catch (cause) {
101
+ if (cause instanceof PolyesterError) throw cause;
102
+ throw new AuthenticationError("JWT token provider failed", { cause });
103
+ }
104
+ if (token !== null && typeof token !== "string") throw new ConfigurationError("JWT token provider must return a string or null");
105
+ return token;
106
+ }
107
+ /**
84
108
  * Creates an interceptor that attaches SDK authentication headers.
85
109
  */
86
110
  function createAuthInterceptor(auth, options) {
87
111
  return (next) => async (req) => {
88
112
  const wireFormat = options?.wireFormat ?? "binary";
89
113
  if (auth.kind === "jwt") {
90
- const token = await auth.getToken();
114
+ const token = await resolveJwtToken(auth);
91
115
  if (token) req.header.set("Authorization", `Bearer ${token}`);
92
116
  } else if (auth.kind === "api-key-ed25519") {
93
- let bodyBytes;
94
- if (!req.stream) {
95
- const inputSchema = req.method.input;
96
- bodyBytes = wireFormat === "json" ? new TextEncoder().encode(toJsonString(inputSchema, req.message)) : toBinary(inputSchema, req.message);
97
- } else bodyBytes = /* @__PURE__ */ new Uint8Array(0);
117
+ if (req.stream) throw new ConfigurationError("API key authentication does not support streaming Connect RPCs");
118
+ const inputSchema = req.method.input;
119
+ const bodyBytes = wireFormat === "json" ? new TextEncoder().encode(toJsonString(inputSchema, req.message)) : toBinary(inputSchema, req.message);
98
120
  const headers = await createApiKeyEd25519AuthHeaders(auth, {
99
121
  url: req.url,
100
122
  method: req.requestMethod,
@@ -106,6 +128,6 @@ function createAuthInterceptor(auth, options) {
106
128
  };
107
129
  }
108
130
  //#endregion
109
- export { createApiKeyEd25519AuthHeaders, createAuthInterceptor, createTransports, isAbortError, makeFetch };
131
+ export { createApiKeyEd25519AuthHeaders, createAuthInterceptor, createTransports, isAbortError, makeFetch, resolveJwtToken };
110
132
 
111
133
  //# sourceMappingURL=transports.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"transports.js","names":[],"sources":["../../src/shared/transports.ts"],"sourcesContent":["import { createConnectTransport } from \"@connectrpc/connect-web\";\nimport type { Transport, Interceptor } from \"@connectrpc/connect\";\nimport { toBinary, toJsonString } from \"@bufbuild/protobuf\";\nimport { signAsync } from \"@noble/ed25519\";\nimport { createErrorMappingTransport } from \"./connect-error-mapping.js\";\nimport { ConfigurationError, isAbortError, NetworkError } from \"./errors.js\";\n\nexport { isAbortError };\n\nexport type ConnectWireFormat = \"binary\" | \"json\";\n\nexport const POLYESTER_MOCK_HEADER = \"x-polyester-mock\";\n\nexport interface TransportConfig {\n apiUrl: string;\n interceptors?: Interceptor[];\n auth?: JwtAuthProvider | ApiKeyEd25519AuthProvider;\n /**\n * Connect wire format. Defaults to binary for production performance.\n * Use `json` for the API Playground visualization.\n */\n wireFormat?: ConnectWireFormat;\n}\n\nexport interface Transports {\n authApi: Transport;\n publicApi: Transport;\n}\n\n/**\n * Generic JWT auth for HTTP/Connect endpoints that follow the Bearer token contract.\n */\nexport interface JwtAuthProvider {\n kind: \"jwt\";\n /** Typically wraps the configured browser auth token storage. */\n getToken: () => string | null | Promise<string | null>;\n}\n\n/**\n * Generic Ed25519 API key auth for HTTP/Connect endpoints that follow the\n * X-API-KEY-ID / X-API-TIMESTAMP / X-API-SIGNATURE contract.\n */\nexport interface ApiKeyEd25519AuthProvider {\n kind: \"api-key-ed25519\";\n getKeyId: () => string | null | Promise<string | null>;\n /** Ed25519 secret key bytes (not hex) for signing. */\n getSecretKey: () => Uint8Array | null | Promise<Uint8Array | null>;\n}\n\nexport interface ApiKeyEd25519SigningRequest {\n url: string | URL;\n method: string;\n body?: Uint8Array;\n timestamp?: string;\n}\n\n/**\n * Returns the fetch implementation used by SDK transports.\n */\nexport function makeFetch(): typeof fetch {\n const wrappedFetch = Object.assign(\n async (input: Parameters<typeof fetch>[0], init?: Parameters<typeof fetch>[1]) => {\n // Normalize headers into a mutable Headers object\n const headers = new Headers(init?.headers);\n\n try {\n const res = await fetch(input, {\n ...init,\n headers,\n redirect: \"manual\",\n });\n\n return res;\n } catch (err) {\n if (isAbortError(err)) throw err;\n throw new NetworkError(\"Transport request failed\", { cause: err });\n }\n },\n fetch,\n );\n\n return wrappedFetch;\n}\n\n/**\n * Creates Connect transports for SDK service clients.\n */\nexport function createTransports(config: TransportConfig): Transports {\n const { apiUrl, interceptors = [], auth, wireFormat = \"binary\" } = config;\n const useBinaryFormat = wireFormat === \"binary\";\n const publicApi = createErrorMappingTransport(\n createConnectTransport({\n baseUrl: apiUrl,\n useBinaryFormat,\n interceptors,\n fetch: makeFetch(),\n }),\n );\n\n const authInterceptors = auth\n ? [createAuthInterceptor(auth, { wireFormat }), ...interceptors]\n : interceptors;\n const authApi = createErrorMappingTransport(\n createConnectTransport({\n baseUrl: apiUrl,\n useBinaryFormat,\n interceptors: authInterceptors,\n fetch: makeFetch(),\n }),\n );\n\n return { authApi, publicApi };\n}\n\n/**\n * Creates an interceptor that enables mock transport behavior.\n */\nexport function createMockInterceptor(getEnabled: () => boolean): Interceptor {\n return (next) => async (req) => {\n if (getEnabled()) req.header.set(POLYESTER_MOCK_HEADER, \"true\");\n return next(req);\n };\n}\n\nasync function sha256Hex(bytes: Uint8Array): Promise<string> {\n if (typeof crypto === \"undefined\" || !crypto.subtle) {\n throw new ConfigurationError(\"SHA-256 not available in this environment\");\n }\n const hash = await crypto.subtle.digest(\"SHA-256\", bytes.slice());\n return Array.from(new Uint8Array(hash), (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nlet lastTimestamp = 0;\nfunction nextTimestamp(): string {\n lastTimestamp = Math.max(lastTimestamp + 1, Date.now());\n return String(lastTimestamp);\n}\n\nfunction canonicalQueryString(params: URLSearchParams): string {\n const pairs: string[] = [];\n for (const [k, v] of params.entries()) {\n pairs.push(`${encodeURIComponent(k)}=${encodeURIComponent(v)}`);\n }\n pairs.sort();\n return pairs.join(\"&\");\n}\n\n/**\n * Creates API-key authentication headers for endpoints using the Polyester Ed25519 signing contract.\n */\nexport async function createApiKeyEd25519AuthHeaders(\n auth: ApiKeyEd25519AuthProvider,\n request: ApiKeyEd25519SigningRequest,\n): Promise<Record<string, string>> {\n const [keyId, secretKey] = await Promise.all([auth.getKeyId(), auth.getSecretKey()]);\n if (!keyId || !secretKey) throw new ConfigurationError(\"Missing API key ID or secret key\");\n\n const urlObj = new URL(request.url);\n const timestamp = request.timestamp ?? nextTimestamp();\n const bodyHash = await sha256Hex(request.body ?? new Uint8Array(0));\n const canonicalQuery = canonicalQueryString(urlObj.searchParams);\n const canonical = `${timestamp}\\n${request.method}\\n${urlObj.pathname}\\n${canonicalQuery}\\n${bodyHash}`;\n const msgBytes = new TextEncoder().encode(canonical);\n const sig = await signAsync(msgBytes, secretKey);\n const signatureHex = Array.from(sig, (b: number) => b.toString(16).padStart(2, \"0\")).join(\"\");\n\n return {\n \"X-API-KEY-ID\": keyId,\n \"X-API-TIMESTAMP\": timestamp,\n \"X-API-SIGNATURE\": signatureHex,\n };\n}\n\n/**\n * Creates an interceptor that attaches SDK authentication headers.\n */\nexport function createAuthInterceptor(\n auth: JwtAuthProvider | ApiKeyEd25519AuthProvider,\n options?: { wireFormat?: ConnectWireFormat },\n): Interceptor {\n return (next) => async (req) => {\n const wireFormat = options?.wireFormat ?? \"binary\";\n if (auth.kind === \"jwt\") {\n const token = await auth.getToken();\n if (token) {\n req.header.set(\"Authorization\", `Bearer ${token}`);\n }\n } else if (auth.kind === \"api-key-ed25519\") {\n let bodyBytes: Uint8Array;\n if (!req.stream) {\n // unary request - serialize the message using the method's input schema\n const inputSchema = req.method.input;\n bodyBytes =\n wireFormat === \"json\"\n ? new TextEncoder().encode(toJsonString(inputSchema, req.message))\n : toBinary(inputSchema, req.message);\n } else {\n // stream request - empty body for signing (streams don't have a single body)\n bodyBytes = new Uint8Array(0);\n }\n\n const headers = await createApiKeyEd25519AuthHeaders(auth, {\n url: req.url,\n method: req.requestMethod,\n body: bodyBytes,\n });\n for (const [key, value] of Object.entries(headers)) {\n req.header.set(key, value);\n }\n }\n return next(req);\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"transports.js","names":[],"sources":["../../src/shared/transports.ts"],"sourcesContent":["import { createConnectTransport } from \"@connectrpc/connect-web\";\nimport type { Transport, Interceptor } from \"@connectrpc/connect\";\nimport { toBinary, toJsonString } from \"@bufbuild/protobuf\";\nimport { signAsync } from \"@noble/ed25519\";\nimport { createErrorMappingTransport } from \"./connect-error-mapping.js\";\nimport {\n AuthenticationError,\n ConfigurationError,\n isAbortError,\n NetworkError,\n PolyesterError,\n} from \"./errors.js\";\n\nexport { isAbortError };\n\nexport type ConnectWireFormat = \"binary\" | \"json\";\n\nexport const POLYESTER_MOCK_HEADER = \"x-polyester-mock\";\n\nexport interface TransportConfig {\n apiUrl: string;\n interceptors?: Interceptor[];\n auth?: JwtAuthProvider | ApiKeyEd25519AuthProvider;\n /**\n * Connect wire format. Defaults to binary for production performance.\n * Use `json` for the API Playground visualization.\n */\n wireFormat?: ConnectWireFormat;\n}\n\nexport interface Transports {\n authApi: Transport;\n publicApi: Transport;\n}\n\n/**\n * Generic JWT auth for HTTP/Connect endpoints that follow the Bearer token contract.\n */\nexport interface JwtAuthProvider {\n kind: \"jwt\";\n /** Typically wraps the configured browser auth token storage. */\n getToken: () => string | null | Promise<string | null>;\n}\n\n/**\n * Generic Ed25519 API key auth for HTTP/Connect endpoints that follow the\n * X-API-KEY-ID / X-API-TIMESTAMP / X-API-SIGNATURE contract.\n */\nexport interface ApiKeyEd25519AuthProvider {\n kind: \"api-key-ed25519\";\n getKeyId: () => string | null | Promise<string | null>;\n /** Ed25519 secret key bytes (not hex) for signing. */\n getSecretKey: () => Uint8Array | null | Promise<Uint8Array | null>;\n}\n\nexport interface ApiKeyEd25519SigningRequest {\n url: string | URL;\n method: string;\n body?: Uint8Array;\n timestamp?: string;\n}\n\n/**\n * Returns the fetch implementation used by SDK transports.\n */\nexport function makeFetch(): typeof fetch {\n const wrappedFetch = Object.assign(\n async (input: Parameters<typeof fetch>[0], init?: Parameters<typeof fetch>[1]) => {\n // Normalize headers into a mutable Headers object\n const headers = new Headers(init?.headers);\n\n try {\n const res = await fetch(input, {\n ...init,\n headers,\n redirect: \"manual\",\n });\n\n return res;\n } catch (err) {\n if (isAbortError(err)) throw err;\n throw new NetworkError(\"Transport request failed\", { cause: err });\n }\n },\n fetch,\n );\n\n return wrappedFetch;\n}\n\n/**\n * Creates Connect transports for SDK service clients.\n */\nexport function createTransports(config: TransportConfig): Transports {\n const { apiUrl, interceptors = [], auth, wireFormat = \"binary\" } = config;\n const useBinaryFormat = wireFormat === \"binary\";\n const publicApi = createErrorMappingTransport(\n createConnectTransport({\n baseUrl: apiUrl,\n useBinaryFormat,\n interceptors,\n fetch: makeFetch(),\n }),\n );\n\n const authInterceptors = auth\n ? auth.kind === \"jwt\"\n ? [createAuthInterceptor(auth, { wireFormat }), ...interceptors]\n : [...interceptors, createAuthInterceptor(auth, { wireFormat })]\n : interceptors;\n const authApi = createErrorMappingTransport(\n createConnectTransport({\n baseUrl: apiUrl,\n useBinaryFormat,\n interceptors: authInterceptors,\n fetch: makeFetch(),\n }),\n );\n\n return { authApi, publicApi };\n}\n\n/**\n * Creates an interceptor that enables mock transport behavior.\n */\nexport function createMockInterceptor(getEnabled: () => boolean): Interceptor {\n return (next) => async (req) => {\n if (getEnabled()) req.header.set(POLYESTER_MOCK_HEADER, \"true\");\n return next(req);\n };\n}\n\nasync function sha256Hex(bytes: Uint8Array): Promise<string> {\n if (typeof crypto === \"undefined\" || !crypto.subtle) {\n throw new ConfigurationError(\"SHA-256 not available in this environment\");\n }\n const hash = await crypto.subtle.digest(\"SHA-256\", bytes.slice());\n return Array.from(new Uint8Array(hash), (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nlet lastTimestamp = 0;\nfunction nextTimestamp(): string {\n lastTimestamp = Math.max(lastTimestamp + 1, Date.now());\n return String(lastTimestamp);\n}\n\nfunction canonicalQueryString(params: URLSearchParams): string {\n const pairs: string[] = [];\n for (const [k, v] of params.entries()) {\n pairs.push(`${encodeURIComponent(k)}=${encodeURIComponent(v)}`);\n }\n pairs.sort();\n return pairs.join(\"&\");\n}\n\n/**\n * Creates API-key authentication headers for endpoints using the Polyester Ed25519 signing contract.\n */\nexport async function createApiKeyEd25519AuthHeaders(\n auth: ApiKeyEd25519AuthProvider,\n request: ApiKeyEd25519SigningRequest,\n): Promise<Record<string, string>> {\n let keyId: string | null;\n let secretKey: Uint8Array | null;\n try {\n [keyId, secretKey] = await Promise.all([auth.getKeyId(), auth.getSecretKey()]);\n } catch (cause) {\n if (cause instanceof PolyesterError) throw cause;\n throw new ConfigurationError(\"API key credential provider failed\", { cause });\n }\n if (!keyId || !secretKey) throw new ConfigurationError(\"Missing API key ID or secret key\");\n if (!(secretKey instanceof Uint8Array) || secretKey.byteLength !== 32) {\n throw new ConfigurationError(\"API key secret key must contain exactly 32 bytes\");\n }\n\n const urlObj = new URL(request.url);\n const timestamp = request.timestamp ?? nextTimestamp();\n const bodyHash = await sha256Hex(request.body ?? new Uint8Array(0));\n const canonicalQuery = canonicalQueryString(urlObj.searchParams);\n const canonical = `${timestamp}\\n${request.method}\\n${urlObj.pathname}\\n${canonicalQuery}\\n${bodyHash}`;\n const msgBytes = new TextEncoder().encode(canonical);\n const sig = await signAsync(msgBytes, secretKey);\n const signatureHex = Array.from(sig, (b: number) => b.toString(16).padStart(2, \"0\")).join(\"\");\n\n return {\n \"X-API-KEY-ID\": keyId,\n \"X-API-TIMESTAMP\": timestamp,\n \"X-API-SIGNATURE\": signatureHex,\n };\n}\n\n/**\n * Resolves a JWT credential and translates provider failures or invalid values\n * into stable SDK errors.\n */\nexport async function resolveJwtToken(auth: JwtAuthProvider): Promise<string | null> {\n let token: string | null;\n try {\n token = await auth.getToken();\n } catch (cause) {\n if (cause instanceof PolyesterError) throw cause;\n throw new AuthenticationError(\"JWT token provider failed\", { cause });\n }\n if (token !== null && typeof token !== \"string\") {\n throw new ConfigurationError(\"JWT token provider must return a string or null\");\n }\n return token;\n}\n\n/**\n * Creates an interceptor that attaches SDK authentication headers.\n */\nexport function createAuthInterceptor(\n auth: JwtAuthProvider | ApiKeyEd25519AuthProvider,\n options?: { wireFormat?: ConnectWireFormat },\n): Interceptor {\n return (next) => async (req) => {\n const wireFormat = options?.wireFormat ?? \"binary\";\n if (auth.kind === \"jwt\") {\n const token = await resolveJwtToken(auth);\n if (token) {\n req.header.set(\"Authorization\", `Bearer ${token}`);\n }\n } else if (auth.kind === \"api-key-ed25519\") {\n if (req.stream) {\n throw new ConfigurationError(\n \"API key authentication does not support streaming Connect RPCs\",\n );\n }\n // Unary request: serialize the message using the method's input schema.\n const inputSchema = req.method.input;\n const bodyBytes =\n wireFormat === \"json\"\n ? new TextEncoder().encode(toJsonString(inputSchema, req.message))\n : toBinary(inputSchema, req.message);\n\n const headers = await createApiKeyEd25519AuthHeaders(auth, {\n url: req.url,\n method: req.requestMethod,\n body: bodyBytes,\n });\n for (const [key, value] of Object.entries(headers)) {\n req.header.set(key, value);\n }\n }\n return next(req);\n };\n}\n"],"mappings":"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"}
@@ -21,12 +21,11 @@ function assertU64(x, label) {
21
21
  function idToBigInt(input, label = "id") {
22
22
  if (typeof input === "bigint") return assertU64(input, label);
23
23
  if (typeof input === "number") {
24
- if (!Number.isFinite(input) || input < 0 || !Number.isInteger(input)) throw new ValidationError(`${label}: invalid number`);
24
+ if (!Number.isSafeInteger(input) || input < 0) throw new ValidationError(`${label}: invalid number`);
25
25
  return assertU64(BigInt(input), label);
26
26
  }
27
27
  const raw = input.trim();
28
28
  if (!raw) throw new ValidationError(`${label}: empty`);
29
- if (/^\d+$/.test(raw)) return assertU64(BigInt(raw), label);
30
29
  return idParse(raw);
31
30
  }
32
31
  function idToFixed64Bytes(input) {
@@ -1 +1 @@
1
- {"version":3,"file":"base58-id.js","names":[],"sources":["../../src/utils/base58-id.ts"],"sourcesContent":["import { ValidationError } from \"../shared/errors.js\";\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\" as const;\nconst BASE58_MAP: Record<string, number> = (() => {\n const m: Record<string, number> = {};\n for (let i = 0; i < BASE58_ALPHABET.length; i++) {\n m[BASE58_ALPHABET[i]!] = i;\n }\n return m;\n})();\n\nconst U64_MAX = (1n << 64n) - 1n;\n\nexport type IdInput = bigint | number | string;\n\nfunction assertU64(x: bigint, label: string): bigint {\n if (x < 0n) throw new ValidationError(`${label}: must be >= 0`);\n if (x > U64_MAX) throw new ValidationError(`${label}: must be <= 2^64-1`);\n return x;\n}\n\n/**\n * Convert a base58 encoded ID to a bigint.\n * @param input - The base58 encoded ID.\n * @param label - The label for the input.\n * @returns The bigint ID.\n */\nexport function idToBigInt(input: IdInput, label = \"id\"): bigint {\n if (typeof input === \"bigint\") return assertU64(input, label);\n if (typeof input === \"number\") {\n if (!Number.isFinite(input) || input < 0 || !Number.isInteger(input)) {\n throw new ValidationError(`${label}: invalid number`);\n }\n return assertU64(BigInt(input), label);\n }\n\n const raw = input.trim();\n if (!raw) throw new ValidationError(`${label}: empty`);\n\n // Heuristic: base58 strings are non-numeric and/or contain letters.\n // If it's purely digits, treat as decimal uint64 (useful for debugging/tools).\n if (/^\\d+$/.test(raw)) return assertU64(BigInt(raw), label);\n\n return idParse(raw);\n}\n\nfunction idToFixed64Bytes(input: IdInput): Uint8Array {\n const out = new Uint8Array(8);\n new DataView(out.buffer).setBigUint64(0, idToBigInt(input));\n return out;\n}\n\nfunction bytesToBigintBE(bytes: Uint8Array): bigint {\n return new DataView(bytes.buffer, bytes.byteOffset).getBigUint64(0);\n}\n\nfunction base58Encode(bytes: Uint8Array): string {\n if (bytes.length === 0) return \"\";\n\n let zeros = 0;\n while (zeros < bytes.length && bytes[zeros] === 0) zeros++;\n\n // Convert base256 -> base58 (classic Bitcoin algorithm)\n const digits: number[] = [];\n for (let i = zeros; i < bytes.length; i++) {\n let carry = bytes[i]!;\n for (let j = 0; j < digits.length; j++) {\n const x = digits[j]! * 256 + carry;\n digits[j] = x % 58;\n carry = Math.floor(x / 58);\n }\n while (carry > 0) {\n digits.push(carry % 58);\n carry = Math.floor(carry / 58);\n }\n }\n\n let out = \"1\".repeat(zeros);\n for (let i = digits.length - 1; i >= 0; i--) {\n out += BASE58_ALPHABET[digits[i]!]!;\n }\n return out ?? \"1\";\n}\n\nfunction base58Decode(s: string): Uint8Array {\n const raw = s.trim();\n if (!raw) return new Uint8Array();\n\n let zeros = 0;\n while (zeros < raw.length && raw[zeros] === \"1\") zeros++;\n\n const bytes: number[] = [];\n for (let i = zeros; i < raw.length; i++) {\n const ch = raw[i]!;\n const val = BASE58_MAP[ch];\n if (val === undefined) {\n throw new ValidationError(`idParse: invalid base58 character \"${ch}\"`);\n }\n let carry = val;\n for (let j = 0; j < bytes.length; j++) {\n const x = bytes[j]! * 58 + carry;\n bytes[j] = x & 0xff;\n carry = x >> 8;\n }\n while (carry > 0) {\n bytes.push(carry & 0xff);\n carry >>= 8;\n }\n }\n\n // bytes currently little-endian base256; reverse and add leading zeros\n const out = new Uint8Array(zeros + bytes.length);\n for (let i = 0; i < zeros; i++) out[i] = 0;\n for (let i = 0; i < bytes.length; i++) out[out.length - 1 - i] = bytes[i]!;\n return out;\n}\n\n/**\n * Given a fixed64 ID, format it as a base58 encoded string.\n * @param input - The base58 encoded ID.\n * @returns The base58 encoded ID.\n */\nexport function formatId(input: IdInput): string {\n // Encode fixed64 ID as base58 (big-endian, no left-padding).\n // This avoids confusing leading \"1\"s for small values.\n const fixed = idToFixed64Bytes(input);\n let firstNonZero = 0;\n while (firstNonZero < fixed.length && fixed[firstNonZero] === 0) firstNonZero++;\n const bytes = firstNonZero >= fixed.length ? new Uint8Array([0]) : fixed.slice(firstNonZero);\n return base58Encode(bytes);\n}\n\nfunction idParse(s: string): bigint {\n const raw = s.trim();\n if (!raw) throw new ValidationError(\"idParse: empty\");\n const bytes = base58Decode(raw);\n if (bytes.length > 8) throw new ValidationError(\"idParse: too large\");\n const padded = new Uint8Array(8);\n padded.set(bytes, 8 - bytes.length);\n return assertU64(bytesToBigintBE(padded), \"idParse\");\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"base58-id.js","names":[],"sources":["../../src/utils/base58-id.ts"],"sourcesContent":["import { ValidationError } from \"../shared/errors.js\";\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\" as const;\nconst BASE58_MAP: Record<string, number> = (() => {\n const m: Record<string, number> = {};\n for (let i = 0; i < BASE58_ALPHABET.length; i++) {\n m[BASE58_ALPHABET[i]!] = i;\n }\n return m;\n})();\n\nconst U64_MAX = (1n << 64n) - 1n;\n\nexport type IdInput = bigint | number | string;\n\nfunction assertU64(x: bigint, label: string): bigint {\n if (x < 0n) throw new ValidationError(`${label}: must be >= 0`);\n if (x > U64_MAX) throw new ValidationError(`${label}: must be <= 2^64-1`);\n return x;\n}\n\n/**\n * Convert a base58 encoded ID to a bigint.\n * @param input - The base58 encoded ID.\n * @param label - The label for the input.\n * @returns The bigint ID.\n */\nexport function idToBigInt(input: IdInput, label = \"id\"): bigint {\n if (typeof input === \"bigint\") return assertU64(input, label);\n if (typeof input === \"number\") {\n if (!Number.isSafeInteger(input) || input < 0) {\n throw new ValidationError(`${label}: invalid number`);\n }\n return assertU64(BigInt(input), label);\n }\n\n const raw = input.trim();\n if (!raw) throw new ValidationError(`${label}: empty`);\n\n return idParse(raw);\n}\n\nfunction idToFixed64Bytes(input: IdInput): Uint8Array {\n const out = new Uint8Array(8);\n new DataView(out.buffer).setBigUint64(0, idToBigInt(input));\n return out;\n}\n\nfunction bytesToBigintBE(bytes: Uint8Array): bigint {\n return new DataView(bytes.buffer, bytes.byteOffset).getBigUint64(0);\n}\n\nfunction base58Encode(bytes: Uint8Array): string {\n if (bytes.length === 0) return \"\";\n\n let zeros = 0;\n while (zeros < bytes.length && bytes[zeros] === 0) zeros++;\n\n // Convert base256 -> base58 (classic Bitcoin algorithm)\n const digits: number[] = [];\n for (let i = zeros; i < bytes.length; i++) {\n let carry = bytes[i]!;\n for (let j = 0; j < digits.length; j++) {\n const x = digits[j]! * 256 + carry;\n digits[j] = x % 58;\n carry = Math.floor(x / 58);\n }\n while (carry > 0) {\n digits.push(carry % 58);\n carry = Math.floor(carry / 58);\n }\n }\n\n let out = \"1\".repeat(zeros);\n for (let i = digits.length - 1; i >= 0; i--) {\n out += BASE58_ALPHABET[digits[i]!]!;\n }\n return out ?? \"1\";\n}\n\nfunction base58Decode(s: string): Uint8Array {\n const raw = s.trim();\n if (!raw) return new Uint8Array();\n\n let zeros = 0;\n while (zeros < raw.length && raw[zeros] === \"1\") zeros++;\n\n const bytes: number[] = [];\n for (let i = zeros; i < raw.length; i++) {\n const ch = raw[i]!;\n const val = BASE58_MAP[ch];\n if (val === undefined) {\n throw new ValidationError(`idParse: invalid base58 character \"${ch}\"`);\n }\n let carry = val;\n for (let j = 0; j < bytes.length; j++) {\n const x = bytes[j]! * 58 + carry;\n bytes[j] = x & 0xff;\n carry = x >> 8;\n }\n while (carry > 0) {\n bytes.push(carry & 0xff);\n carry >>= 8;\n }\n }\n\n // bytes currently little-endian base256; reverse and add leading zeros\n const out = new Uint8Array(zeros + bytes.length);\n for (let i = 0; i < zeros; i++) out[i] = 0;\n for (let i = 0; i < bytes.length; i++) out[out.length - 1 - i] = bytes[i]!;\n return out;\n}\n\n/**\n * Given a fixed64 ID, format it as a base58 encoded string.\n * @param input - The base58 encoded ID.\n * @returns The base58 encoded ID.\n */\nexport function formatId(input: IdInput): string {\n // Encode fixed64 ID as base58 (big-endian, no left-padding).\n // This avoids confusing leading \"1\"s for small values.\n const fixed = idToFixed64Bytes(input);\n let firstNonZero = 0;\n while (firstNonZero < fixed.length && fixed[firstNonZero] === 0) firstNonZero++;\n const bytes = firstNonZero >= fixed.length ? new Uint8Array([0]) : fixed.slice(firstNonZero);\n return base58Encode(bytes);\n}\n\nfunction idParse(s: string): bigint {\n const raw = s.trim();\n if (!raw) throw new ValidationError(\"idParse: empty\");\n const bytes = base58Decode(raw);\n if (bytes.length > 8) throw new ValidationError(\"idParse: too large\");\n const padded = new Uint8Array(8);\n padded.set(bytes, 8 - bytes.length);\n return assertU64(bytesToBigintBE(padded), \"idParse\");\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","names":[],"sources":["../../src/utils/errors.ts"],"mappings":";;;;;;iBAoCgB,wBAAwB;;iBAcxB,wBAAwB;;iBAsBxB,mBAAmB;;iBAKnB,oBAAoB;;iBAKpB,2BAA2B;;;;;;iBAS3B,iBAAiB;;;;;;;;;;;iBAiBjB,mBACZ,cACA;iBA0CY,sBAAsB,gBAAgB"}
1
+ {"version":3,"file":"errors.d.ts","names":[],"sources":["../../src/utils/errors.ts"],"mappings":";;;;;;iBAoCgB,wBAAwB;;iBAcxB,wBAAwB;;iBAsBxB,mBAAmB;;iBAKnB,oBAAoB;;iBAKpB,2BAA2B;;;;;;iBAS3B,iBAAiB;;;;;;;;;;;iBAuBjB,mBACZ,cACA;iBA0CY,sBAAsB,gBAAgB"}
@@ -57,9 +57,10 @@ function isPolicyScopeMismatchError(err) {
57
57
  */
58
58
  function isRetryableError(err) {
59
59
  if (isAbortError(err)) return false;
60
- if (err instanceof PolyesterError) return err.retryable;
61
- const ce = ConnectError.from(err);
62
- return ce.code === Code.Unavailable || ce.code === Code.DeadlineExceeded;
60
+ const mapped = toPolyesterError(err);
61
+ if (mapped instanceof PolyesterError) return mapped.retryable;
62
+ const ce = ConnectError.from(mapped);
63
+ return ce.code === Code.Unavailable || ce.code === Code.DeadlineExceeded || ce.code === Code.ResourceExhausted || ce.code === Code.Aborted;
63
64
  }
64
65
  /**
65
66
  * Diagnostic formatter for SDK and ConnectRPC errors.