@ar.io/sdk 3.21.1-alpha.1 → 3.22.0-alpha.2

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.
@@ -448,11 +448,7 @@ const utils_js_1 = require("./utils.js");
448
448
  (0, utils_js_1.makeCommand)({
449
449
  name: 'get-ants-for-address',
450
450
  description: 'Get the list of ANTs owned by an address according to the ANT registry',
451
- options: [
452
- options_js_1.optionMap.address,
453
- options_js_1.optionMap.antRegistryProcessId,
454
- options_js_1.optionMap.hyperbeamUrl,
455
- ],
451
+ options: [options_js_1.optionMap.address, options_js_1.optionMap.antRegistryProcessId],
456
452
  action: readCommands_js_1.listAntsForAddress,
457
453
  });
458
454
  // # ANTS
@@ -140,6 +140,10 @@ exports.optionMap = {
140
140
  description: 'The allowed delegates for the gateway. By default this is empty, meaning all are allowed delegate stake unless delegating is explicitly disallowed by the gateway',
141
141
  type: 'array',
142
142
  },
143
+ services: {
144
+ alias: '--services <services>',
145
+ description: 'JSON string of gateway services configuration (e.g., \'{"bundlers":[{"fqdn":"bundler.example.com","port":443,"protocol":"https","path":"/bundler"}]}\')',
146
+ },
143
147
  skipConfirmation: {
144
148
  alias: '--skip-confirmation',
145
149
  description: 'Skip confirmation prompts',
@@ -335,6 +339,7 @@ exports.globalOptions = [
335
339
  exports.optionMap.debug,
336
340
  exports.optionMap.arioProcessId,
337
341
  exports.optionMap.cuUrl,
342
+ exports.optionMap.hyperbeamUrl,
338
343
  ];
339
344
  exports.writeActionOptions = [exports.optionMap.skipConfirmation, exports.optionMap.tags];
340
345
  exports.arnsPurchaseOptions = [
@@ -406,6 +411,7 @@ exports.updateGatewaySettingsOptions = [
406
411
  exports.optionMap.fqdn,
407
412
  exports.optionMap.port,
408
413
  exports.optionMap.protocol,
414
+ exports.optionMap.services,
409
415
  ];
410
416
  exports.joinNetworkOptions = [
411
417
  ...exports.updateGatewaySettingsOptions,
@@ -27,6 +27,7 @@ exports.paginationParamsFromOptions = paginationParamsFromOptions;
27
27
  exports.epochInputFromOptions = epochInputFromOptions;
28
28
  exports.requiredInitiatorFromOptions = requiredInitiatorFromOptions;
29
29
  exports.customTagsFromOptions = customTagsFromOptions;
30
+ exports.servicesFromOptions = servicesFromOptions;
30
31
  exports.gatewaySettingsFromOptions = gatewaySettingsFromOptions;
31
32
  exports.requiredTargetAndQuantityFromOptions = requiredTargetAndQuantityFromOptions;
32
33
  exports.redelegateParamsFromOptions = redelegateParamsFromOptions;
@@ -180,6 +181,7 @@ function aoProcessFromOptions(options) {
180
181
  function readARIOFromOptions(options) {
181
182
  setLoggerIfDebug(options);
182
183
  return index_js_1.ARIO.init({
184
+ hyperbeamUrl: options.hyperbeamUrl,
183
185
  process: aoProcessFromOptions({
184
186
  cuUrl: 'https://cu.ardrive.io', // default to ardrive cu for ARIO process
185
187
  ...options,
@@ -226,6 +228,7 @@ function writeARIOFromOptions(options) {
226
228
  process: aoProcessFromOptions(options),
227
229
  signer,
228
230
  paymentUrl: options.paymentUrl,
231
+ hyperbeamUrl: options.hyperbeamUrl,
229
232
  }),
230
233
  signerAddress,
231
234
  };
@@ -333,7 +336,44 @@ function customTagsFromOptions(options) {
333
336
  tags,
334
337
  };
335
338
  }
336
- function gatewaySettingsFromOptions({ allowDelegatedStaking, autoStake, delegateRewardShareRatio, fqdn, label, minDelegatedStake, note, observerAddress, port, properties, allowedDelegates, }) {
339
+ function servicesFromOptions(services) {
340
+ if (services === undefined || services === null || services === '') {
341
+ return undefined;
342
+ }
343
+ try {
344
+ const parsed = JSON.parse(services);
345
+ // Validate structure
346
+ if (!parsed.bundlers || !Array.isArray(parsed.bundlers)) {
347
+ throw new Error('Services must have a "bundlers" array');
348
+ }
349
+ if (parsed.bundlers.length > 20) {
350
+ throw new Error('Maximum 20 bundlers allowed');
351
+ }
352
+ // Validate each bundler
353
+ for (const bundler of parsed.bundlers) {
354
+ if (!bundler.fqdn || typeof bundler.fqdn !== 'string') {
355
+ throw new Error('Each bundler must have a valid "fqdn" string');
356
+ }
357
+ if (typeof bundler.port !== 'number' ||
358
+ bundler.port < 0 ||
359
+ bundler.port > 65535) {
360
+ throw new Error('Each bundler must have a valid "port" (0-65535)');
361
+ }
362
+ if (bundler.protocol !== 'https') {
363
+ throw new Error('Each bundler protocol must be "https"');
364
+ }
365
+ if (!bundler.path || typeof bundler.path !== 'string') {
366
+ throw new Error('Each bundler must have a valid "path" string');
367
+ }
368
+ }
369
+ return parsed;
370
+ }
371
+ catch (error) {
372
+ throw new Error(`Invalid services JSON: ${error instanceof Error ? error.message : String(error)}`);
373
+ }
374
+ }
375
+ function gatewaySettingsFromOptions(options) {
376
+ const { allowDelegatedStaking, autoStake, delegateRewardShareRatio, fqdn, label, minDelegatedStake, note, observerAddress, port, properties, allowedDelegates, services, } = options;
337
377
  return {
338
378
  observerAddress,
339
379
  allowDelegatedStaking,
@@ -348,6 +388,7 @@ function gatewaySettingsFromOptions({ allowDelegatedStaking, autoStake, delegate
348
388
  note,
349
389
  port: port !== undefined ? +port : undefined,
350
390
  properties,
391
+ services: servicesFromOptions(services),
351
392
  };
352
393
  }
353
394
  function requiredTargetAndQuantityFromOptions(options) {
@@ -0,0 +1,173 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HB = void 0;
4
+ const logger_js_1 = require("../logger.js");
5
+ class HB {
6
+ url;
7
+ processId;
8
+ isHyperBeamCompatible;
9
+ checkHyperBeamPromise;
10
+ logger;
11
+ hbTimeoutMs;
12
+ constructor(config) {
13
+ this.url = config.url;
14
+ this.processId = config.processId;
15
+ this.logger = config.logger ?? logger_js_1.Logger.default;
16
+ this.hbTimeoutMs = config.hbTimeoutMs ?? 5000;
17
+ this.isHyperBeamCompatible = undefined;
18
+ this.checkHyperBeamPromise = this.checkHyperBeamCompatibility();
19
+ }
20
+ /**
21
+ * fetches the meta data for the process
22
+ *
23
+ * @returns The meta data for the process
24
+ *
25
+ * @example
26
+ * const hyperbeam = new Hyperbeam({ url: 'https://hyperbeam.ario.permaweb.services', processId: 'qNvAoz0TgcH7DMg8BCVn8jF32QH5L6T29VjHxhHqqGE' });
27
+ * const meta = await hyperbeam.meta();
28
+ * console.log(meta);
29
+ */
30
+ async meta() {
31
+ const url = new URL(`${this.url}/${this.processId}~process@1.0/meta`);
32
+ return this.fetchHyperbeamPath({
33
+ path: url.toString(),
34
+ });
35
+ }
36
+ /**
37
+ * calls the process device /now function, which evaluates the current process state pulling new messages
38
+ * to get the latest state
39
+ *
40
+ * @param path - The path to the hb state
41
+ * @param json - Whether to return the result as JSON, defaults to true
42
+ * @returns The result of the compute operation
43
+ *
44
+ * @example
45
+ * const hyperbeam = new Hyperbeam({ url: 'https://hyperbeam.ario.permaweb.services', processId: 'qNvAoz0TgcH7DMg8BCVn8jF32QH5L6T29VjHxhHqqGE' });
46
+ * const result = await hyperbeam.now({ path: 'balances/QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ' });
47
+ * console.log(result);
48
+ */
49
+ async now({ path, json = false, }) {
50
+ return this.fetchHyperbeamPath({
51
+ path: `${this.url}/${this.processId}~process@1.0/now/${path}`,
52
+ json,
53
+ });
54
+ }
55
+ /**
56
+ * calls the process device /compute function, which uses the currently evaluated state in the node
57
+ *
58
+ * @param path - The path to the compute resource
59
+ * @param json - Whether to return the result as JSON, defaults to true
60
+ * @returns The result of the compute operation
61
+ *
62
+ * @example
63
+ * const hyperbeam = new Hyperbeam({ url: 'https://hyperbeam.ario.permaweb.services', processId: 'qNvAoz0TgcH7DMg8BCVn8jF32QH5L6T29VjHxhHqqGE' });
64
+ * const result = await hyperbeam.compute({ path: 'balances/QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ' });
65
+ * console.log(result);
66
+ */
67
+ async compute({ path, json = false, }) {
68
+ return this.fetchHyperbeamPath({
69
+ path: `${this.url}/${this.processId}~process@1.0/compute/${path}`,
70
+ json,
71
+ });
72
+ }
73
+ /**
74
+ * Checks if the process is HyperBeam compatible and caches the result.
75
+ *
76
+ * @returns {Promise<boolean>} True if the process is HyperBeam compatible, false otherwise.
77
+ */
78
+ async checkHyperBeamCompatibility({ minSlot, } = {}) {
79
+ // refetch if min slot is provided
80
+ if (minSlot !== undefined) {
81
+ this.isHyperBeamCompatible = undefined;
82
+ this.checkHyperBeamPromise = undefined;
83
+ }
84
+ if (this.checkHyperBeamPromise !== undefined) {
85
+ return this.checkHyperBeamPromise;
86
+ }
87
+ if (this.isHyperBeamCompatible !== undefined) {
88
+ return Promise.resolve(this.isHyperBeamCompatible);
89
+ }
90
+ const result = fetch(
91
+ // use /now to force a refresh of the cache state, then compute when calling it for keys
92
+ `${this.url.toString()}/${this.processId}~process@1.0/now`, {
93
+ method: 'HEAD',
94
+ signal: AbortSignal.timeout(this.hbTimeoutMs),
95
+ })
96
+ .then(async (res) => {
97
+ if (res.ok) {
98
+ if (minSlot !== undefined) {
99
+ const slotRes = await this.compute({
100
+ path: 'at-slot',
101
+ json: false,
102
+ });
103
+ const slot = Number(slotRes);
104
+ if (slot < minSlot) {
105
+ return false;
106
+ }
107
+ }
108
+ this.isHyperBeamCompatible = true;
109
+ return true;
110
+ }
111
+ this.isHyperBeamCompatible = false;
112
+ return false;
113
+ })
114
+ .catch((error) => {
115
+ this.logger.debug('Failed to check HyperBeam compatibility', {
116
+ cause: error,
117
+ });
118
+ this.isHyperBeamCompatible = false;
119
+ return false;
120
+ });
121
+ this.checkHyperBeamPromise = result;
122
+ return result;
123
+ }
124
+ async fetchHyperbeamPath({ path, json = true, }) {
125
+ try {
126
+ const url = new URL(path);
127
+ if (json) {
128
+ this.logger.debug('Fetching path as JSON', { path });
129
+ /**
130
+ * This is the (current) way to access data as json
131
+ * the old way is /~json@1.0/serialize path
132
+ */
133
+ url.searchParams.set('require-codec', 'application/json');
134
+ url.searchParams.set('accept-bundle', 'true');
135
+ const res = await fetch(url);
136
+ if (!res.ok) {
137
+ throw new Error(`Failed to fetch path as JSON: ${res.statusText}`);
138
+ }
139
+ const jsonResult = await res
140
+ .json()
141
+ .then((json) => json)
142
+ .catch((error) => {
143
+ this.logger.error('Failed to parse JSON', {
144
+ cause: error,
145
+ });
146
+ throw new Error(`Received response but failed to parse JSON: ${error.message}`);
147
+ });
148
+ if (typeof jsonResult !== 'object' ||
149
+ jsonResult === null ||
150
+ !('body' in jsonResult)) {
151
+ throw new Error('Response body missing in JSON response');
152
+ }
153
+ return jsonResult.body;
154
+ }
155
+ else {
156
+ this.logger.debug('Fetching path as text', { path });
157
+ const res = await fetch(url);
158
+ if (!res.ok) {
159
+ throw new Error(`Failed to fetch path: ${res.statusText}`);
160
+ }
161
+ const body = await res.text();
162
+ return body;
163
+ }
164
+ }
165
+ catch (error) {
166
+ this.logger.error('Failed to fetch path as JSON', {
167
+ cause: error,
168
+ });
169
+ throw error;
170
+ }
171
+ }
172
+ }
173
+ exports.HB = HB;
@@ -27,6 +27,7 @@ const arweave_js_2 = require("./arweave.js");
27
27
  const ao_process_js_1 = require("./contracts/ao-process.js");
28
28
  const error_js_1 = require("./error.js");
29
29
  const faucet_js_1 = require("./faucet.js");
30
+ const hb_js_1 = require("./hyperbeam/hb.js");
30
31
  const logger_js_1 = require("./logger.js");
31
32
  const turbo_js_1 = require("./turbo.js");
32
33
  class ARIO {
@@ -104,9 +105,9 @@ class ARIOReadable {
104
105
  hyperbeamUrl;
105
106
  paymentProvider; // TODO: this could be an array/map of payment providers
106
107
  logger = logger_js_1.Logger.default;
108
+ hb;
107
109
  constructor(config) {
108
110
  this.arweave = config?.arweave ?? arweave_js_2.defaultArweave;
109
- this.hyperbeamUrl = config?.hyperbeamUrl;
110
111
  if (config === undefined || Object.keys(config).length === 0) {
111
112
  this.process = new ao_process_js_1.AOProcess({
112
113
  processId: constants_js_1.ARIO_MAINNET_PROCESS_ID,
@@ -123,6 +124,19 @@ class ARIOReadable {
123
124
  else {
124
125
  throw new error_js_1.InvalidContractConfigurationError();
125
126
  }
127
+ // only use hyperbeam if the client has provided a hyperbeamUrl
128
+ // this will avoid overwhelming the HyperBeam node with requests
129
+ // as we shift using HyperBEAM for all ANT operations
130
+ if (config?.hyperbeamUrl !== undefined) {
131
+ this.hyperbeamUrl = config.hyperbeamUrl;
132
+ this.hb = new hb_js_1.HB({
133
+ url: this.hyperbeamUrl,
134
+ processId: this.process.processId,
135
+ });
136
+ this.logger.debug(`Using HyperBEAM node for process ${this.process.processId}`, {
137
+ hyperbeamUrl: this.hyperbeamUrl,
138
+ });
139
+ }
126
140
  this.paymentProvider = turbo_js_1.TurboArNSPaymentFactory.init({
127
141
  paymentUrl: config?.paymentUrl,
128
142
  });
@@ -222,6 +236,24 @@ class ARIOReadable {
222
236
  });
223
237
  }
224
238
  async getBalance({ address }) {
239
+ if (this.hb && (await this.hb.checkHyperBeamCompatibility())) {
240
+ this.logger.debug('Getting balance from HyperBEAM', { address });
241
+ const res = await this.hb
242
+ .compute({
243
+ path: `balances/${address}`,
244
+ })
245
+ .then((res) => Number(res))
246
+ .catch((error) => {
247
+ this.logger.error('Failed to get balance from HyperBEAM', {
248
+ cause: error,
249
+ });
250
+ return null;
251
+ });
252
+ if (res !== null)
253
+ return res;
254
+ // else fall through to CU read
255
+ this.logger.info('Failed to get balance from HyperBEAM, failing over to to CU read', { address });
256
+ }
225
257
  return this.process.read({
226
258
  tags: [
227
259
  { name: 'Action', value: 'Balance' },
@@ -821,7 +853,7 @@ class ARIOWriteable extends ARIOReadable {
821
853
  signer: this.signer,
822
854
  });
823
855
  }
824
- async joinNetwork({ operatorStake, allowDelegatedStaking, allowedDelegates, delegateRewardShareRatio, fqdn, label, minDelegatedStake, note, port, properties, protocol, autoStake, observerAddress, }, options) {
856
+ async joinNetwork({ operatorStake, allowDelegatedStaking, allowedDelegates, delegateRewardShareRatio, fqdn, label, minDelegatedStake, note, port, properties, protocol, autoStake, observerAddress, services, }, options) {
825
857
  const { tags = [] } = options || {};
826
858
  const allTags = [
827
859
  ...tags,
@@ -878,6 +910,10 @@ class ARIOWriteable extends ARIOReadable {
878
910
  name: 'Observer-Address',
879
911
  value: observerAddress,
880
912
  },
913
+ {
914
+ name: 'Services',
915
+ value: services ? JSON.stringify(services) : undefined,
916
+ },
881
917
  ];
882
918
  return this.process.send({
883
919
  signer: this.signer,
@@ -891,7 +927,7 @@ class ARIOWriteable extends ARIOReadable {
891
927
  tags: [...tags, { name: 'Action', value: 'Leave-Network' }],
892
928
  });
893
929
  }
894
- async updateGatewaySettings({ allowDelegatedStaking, allowedDelegates, delegateRewardShareRatio, fqdn, label, minDelegatedStake, note, port, properties, protocol, autoStake, observerAddress, }, options) {
930
+ async updateGatewaySettings({ allowDelegatedStaking, allowedDelegates, delegateRewardShareRatio, fqdn, label, minDelegatedStake, note, port, properties, protocol, autoStake, observerAddress, services, }, options) {
895
931
  const { tags = [] } = options || {};
896
932
  const allTags = [
897
933
  ...tags,
@@ -920,6 +956,10 @@ class ARIOWriteable extends ARIOReadable {
920
956
  value: minDelegatedStake?.valueOf().toString(),
921
957
  },
922
958
  { name: 'Auto-Stake', value: autoStake?.toString() },
959
+ {
960
+ name: 'Services',
961
+ value: services ? JSON.stringify(services) : undefined,
962
+ },
923
963
  ];
924
964
  return this.process.send({
925
965
  signer: this.signer,
@@ -17,4 +17,4 @@
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.version = void 0;
19
19
  // AUTOMATICALLY GENERATED FILE - DO NOT TOUCH
20
- exports.version = '3.21.1-alpha.1';
20
+ exports.version = '3.22.0-alpha.2';
@@ -446,11 +446,7 @@ makeCommand({
446
446
  makeCommand({
447
447
  name: 'get-ants-for-address',
448
448
  description: 'Get the list of ANTs owned by an address according to the ANT registry',
449
- options: [
450
- optionMap.address,
451
- optionMap.antRegistryProcessId,
452
- optionMap.hyperbeamUrl,
453
- ],
449
+ options: [optionMap.address, optionMap.antRegistryProcessId],
454
450
  action: listAntsForAddress,
455
451
  });
456
452
  // # ANTS
@@ -137,6 +137,10 @@ export const optionMap = {
137
137
  description: 'The allowed delegates for the gateway. By default this is empty, meaning all are allowed delegate stake unless delegating is explicitly disallowed by the gateway',
138
138
  type: 'array',
139
139
  },
140
+ services: {
141
+ alias: '--services <services>',
142
+ description: 'JSON string of gateway services configuration (e.g., \'{"bundlers":[{"fqdn":"bundler.example.com","port":443,"protocol":"https","path":"/bundler"}]}\')',
143
+ },
140
144
  skipConfirmation: {
141
145
  alias: '--skip-confirmation',
142
146
  description: 'Skip confirmation prompts',
@@ -332,6 +336,7 @@ export const globalOptions = [
332
336
  optionMap.debug,
333
337
  optionMap.arioProcessId,
334
338
  optionMap.cuUrl,
339
+ optionMap.hyperbeamUrl,
335
340
  ];
336
341
  export const writeActionOptions = [optionMap.skipConfirmation, optionMap.tags];
337
342
  export const arnsPurchaseOptions = [
@@ -403,6 +408,7 @@ export const updateGatewaySettingsOptions = [
403
408
  optionMap.fqdn,
404
409
  optionMap.port,
405
410
  optionMap.protocol,
411
+ optionMap.services,
406
412
  ];
407
413
  export const joinNetworkOptions = [
408
414
  ...updateGatewaySettingsOptions,
@@ -127,6 +127,7 @@ function aoProcessFromOptions(options) {
127
127
  export function readARIOFromOptions(options) {
128
128
  setLoggerIfDebug(options);
129
129
  return ARIO.init({
130
+ hyperbeamUrl: options.hyperbeamUrl,
130
131
  process: aoProcessFromOptions({
131
132
  cuUrl: 'https://cu.ardrive.io', // default to ardrive cu for ARIO process
132
133
  ...options,
@@ -173,6 +174,7 @@ export function writeARIOFromOptions(options) {
173
174
  process: aoProcessFromOptions(options),
174
175
  signer,
175
176
  paymentUrl: options.paymentUrl,
177
+ hyperbeamUrl: options.hyperbeamUrl,
176
178
  }),
177
179
  signerAddress,
178
180
  };
@@ -280,7 +282,44 @@ export function customTagsFromOptions(options) {
280
282
  tags,
281
283
  };
282
284
  }
283
- export function gatewaySettingsFromOptions({ allowDelegatedStaking, autoStake, delegateRewardShareRatio, fqdn, label, minDelegatedStake, note, observerAddress, port, properties, allowedDelegates, }) {
285
+ export function servicesFromOptions(services) {
286
+ if (services === undefined || services === null || services === '') {
287
+ return undefined;
288
+ }
289
+ try {
290
+ const parsed = JSON.parse(services);
291
+ // Validate structure
292
+ if (!parsed.bundlers || !Array.isArray(parsed.bundlers)) {
293
+ throw new Error('Services must have a "bundlers" array');
294
+ }
295
+ if (parsed.bundlers.length > 20) {
296
+ throw new Error('Maximum 20 bundlers allowed');
297
+ }
298
+ // Validate each bundler
299
+ for (const bundler of parsed.bundlers) {
300
+ if (!bundler.fqdn || typeof bundler.fqdn !== 'string') {
301
+ throw new Error('Each bundler must have a valid "fqdn" string');
302
+ }
303
+ if (typeof bundler.port !== 'number' ||
304
+ bundler.port < 0 ||
305
+ bundler.port > 65535) {
306
+ throw new Error('Each bundler must have a valid "port" (0-65535)');
307
+ }
308
+ if (bundler.protocol !== 'https') {
309
+ throw new Error('Each bundler protocol must be "https"');
310
+ }
311
+ if (!bundler.path || typeof bundler.path !== 'string') {
312
+ throw new Error('Each bundler must have a valid "path" string');
313
+ }
314
+ }
315
+ return parsed;
316
+ }
317
+ catch (error) {
318
+ throw new Error(`Invalid services JSON: ${error instanceof Error ? error.message : String(error)}`);
319
+ }
320
+ }
321
+ export function gatewaySettingsFromOptions(options) {
322
+ const { allowDelegatedStaking, autoStake, delegateRewardShareRatio, fqdn, label, minDelegatedStake, note, observerAddress, port, properties, allowedDelegates, services, } = options;
284
323
  return {
285
324
  observerAddress,
286
325
  allowDelegatedStaking,
@@ -295,6 +334,7 @@ export function gatewaySettingsFromOptions({ allowDelegatedStaking, autoStake, d
295
334
  note,
296
335
  port: port !== undefined ? +port : undefined,
297
336
  properties,
337
+ services: servicesFromOptions(services),
298
338
  };
299
339
  }
300
340
  export function requiredTargetAndQuantityFromOptions(options) {
@@ -0,0 +1,169 @@
1
+ import { Logger } from '../logger.js';
2
+ export class HB {
3
+ url;
4
+ processId;
5
+ isHyperBeamCompatible;
6
+ checkHyperBeamPromise;
7
+ logger;
8
+ hbTimeoutMs;
9
+ constructor(config) {
10
+ this.url = config.url;
11
+ this.processId = config.processId;
12
+ this.logger = config.logger ?? Logger.default;
13
+ this.hbTimeoutMs = config.hbTimeoutMs ?? 5000;
14
+ this.isHyperBeamCompatible = undefined;
15
+ this.checkHyperBeamPromise = this.checkHyperBeamCompatibility();
16
+ }
17
+ /**
18
+ * fetches the meta data for the process
19
+ *
20
+ * @returns The meta data for the process
21
+ *
22
+ * @example
23
+ * const hyperbeam = new Hyperbeam({ url: 'https://hyperbeam.ario.permaweb.services', processId: 'qNvAoz0TgcH7DMg8BCVn8jF32QH5L6T29VjHxhHqqGE' });
24
+ * const meta = await hyperbeam.meta();
25
+ * console.log(meta);
26
+ */
27
+ async meta() {
28
+ const url = new URL(`${this.url}/${this.processId}~process@1.0/meta`);
29
+ return this.fetchHyperbeamPath({
30
+ path: url.toString(),
31
+ });
32
+ }
33
+ /**
34
+ * calls the process device /now function, which evaluates the current process state pulling new messages
35
+ * to get the latest state
36
+ *
37
+ * @param path - The path to the hb state
38
+ * @param json - Whether to return the result as JSON, defaults to true
39
+ * @returns The result of the compute operation
40
+ *
41
+ * @example
42
+ * const hyperbeam = new Hyperbeam({ url: 'https://hyperbeam.ario.permaweb.services', processId: 'qNvAoz0TgcH7DMg8BCVn8jF32QH5L6T29VjHxhHqqGE' });
43
+ * const result = await hyperbeam.now({ path: 'balances/QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ' });
44
+ * console.log(result);
45
+ */
46
+ async now({ path, json = false, }) {
47
+ return this.fetchHyperbeamPath({
48
+ path: `${this.url}/${this.processId}~process@1.0/now/${path}`,
49
+ json,
50
+ });
51
+ }
52
+ /**
53
+ * calls the process device /compute function, which uses the currently evaluated state in the node
54
+ *
55
+ * @param path - The path to the compute resource
56
+ * @param json - Whether to return the result as JSON, defaults to true
57
+ * @returns The result of the compute operation
58
+ *
59
+ * @example
60
+ * const hyperbeam = new Hyperbeam({ url: 'https://hyperbeam.ario.permaweb.services', processId: 'qNvAoz0TgcH7DMg8BCVn8jF32QH5L6T29VjHxhHqqGE' });
61
+ * const result = await hyperbeam.compute({ path: 'balances/QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ' });
62
+ * console.log(result);
63
+ */
64
+ async compute({ path, json = false, }) {
65
+ return this.fetchHyperbeamPath({
66
+ path: `${this.url}/${this.processId}~process@1.0/compute/${path}`,
67
+ json,
68
+ });
69
+ }
70
+ /**
71
+ * Checks if the process is HyperBeam compatible and caches the result.
72
+ *
73
+ * @returns {Promise<boolean>} True if the process is HyperBeam compatible, false otherwise.
74
+ */
75
+ async checkHyperBeamCompatibility({ minSlot, } = {}) {
76
+ // refetch if min slot is provided
77
+ if (minSlot !== undefined) {
78
+ this.isHyperBeamCompatible = undefined;
79
+ this.checkHyperBeamPromise = undefined;
80
+ }
81
+ if (this.checkHyperBeamPromise !== undefined) {
82
+ return this.checkHyperBeamPromise;
83
+ }
84
+ if (this.isHyperBeamCompatible !== undefined) {
85
+ return Promise.resolve(this.isHyperBeamCompatible);
86
+ }
87
+ const result = fetch(
88
+ // use /now to force a refresh of the cache state, then compute when calling it for keys
89
+ `${this.url.toString()}/${this.processId}~process@1.0/now`, {
90
+ method: 'HEAD',
91
+ signal: AbortSignal.timeout(this.hbTimeoutMs),
92
+ })
93
+ .then(async (res) => {
94
+ if (res.ok) {
95
+ if (minSlot !== undefined) {
96
+ const slotRes = await this.compute({
97
+ path: 'at-slot',
98
+ json: false,
99
+ });
100
+ const slot = Number(slotRes);
101
+ if (slot < minSlot) {
102
+ return false;
103
+ }
104
+ }
105
+ this.isHyperBeamCompatible = true;
106
+ return true;
107
+ }
108
+ this.isHyperBeamCompatible = false;
109
+ return false;
110
+ })
111
+ .catch((error) => {
112
+ this.logger.debug('Failed to check HyperBeam compatibility', {
113
+ cause: error,
114
+ });
115
+ this.isHyperBeamCompatible = false;
116
+ return false;
117
+ });
118
+ this.checkHyperBeamPromise = result;
119
+ return result;
120
+ }
121
+ async fetchHyperbeamPath({ path, json = true, }) {
122
+ try {
123
+ const url = new URL(path);
124
+ if (json) {
125
+ this.logger.debug('Fetching path as JSON', { path });
126
+ /**
127
+ * This is the (current) way to access data as json
128
+ * the old way is /~json@1.0/serialize path
129
+ */
130
+ url.searchParams.set('require-codec', 'application/json');
131
+ url.searchParams.set('accept-bundle', 'true');
132
+ const res = await fetch(url);
133
+ if (!res.ok) {
134
+ throw new Error(`Failed to fetch path as JSON: ${res.statusText}`);
135
+ }
136
+ const jsonResult = await res
137
+ .json()
138
+ .then((json) => json)
139
+ .catch((error) => {
140
+ this.logger.error('Failed to parse JSON', {
141
+ cause: error,
142
+ });
143
+ throw new Error(`Received response but failed to parse JSON: ${error.message}`);
144
+ });
145
+ if (typeof jsonResult !== 'object' ||
146
+ jsonResult === null ||
147
+ !('body' in jsonResult)) {
148
+ throw new Error('Response body missing in JSON response');
149
+ }
150
+ return jsonResult.body;
151
+ }
152
+ else {
153
+ this.logger.debug('Fetching path as text', { path });
154
+ const res = await fetch(url);
155
+ if (!res.ok) {
156
+ throw new Error(`Failed to fetch path: ${res.statusText}`);
157
+ }
158
+ const body = await res.text();
159
+ return body;
160
+ }
161
+ }
162
+ catch (error) {
163
+ this.logger.error('Failed to fetch path as JSON', {
164
+ cause: error,
165
+ });
166
+ throw error;
167
+ }
168
+ }
169
+ }