@beam-network/sdk 0.4.4 → 0.5.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.
package/dist/client.d.ts CHANGED
@@ -1,11 +1,6 @@
1
1
  import type { AttachSignedUrlsResponse, BeamClientOptions, DistributeResponse, MultipartGroupManifest, PlanningHttpSource, PreparedDestination, PreparedHttpSource, ProviderTransferCreateInput, RawTransferCreateInput, SignedChunkRoute, SignedUrlFlow, TransferCancelResponse, TransferCreateResponse, TransferPlanResponse, TransferPrepareResponse, TransferStatusInfo, TransferTerminalSignalWaiter } from "./models.js";
2
2
  import { BEAM_DEFAULT_NATS_URL } from "./nats-control.js";
3
3
  export { BEAM_DEFAULT_NATS_URL };
4
- export declare class BeamApiError extends Error {
5
- readonly status: number;
6
- readonly body: string;
7
- constructor(message: string, status: number, body: string);
8
- }
9
4
  export declare class BeamClient {
10
5
  readonly apiKey: string;
11
6
  readonly natsUrl: string;
@@ -13,6 +8,8 @@ export declare class BeamClient {
13
8
  private readonly control;
14
9
  private readonly routeSigningConcurrency;
15
10
  private readonly routeSigningConcurrencyOverridden;
11
+ /** Last credit state Beam reported. Null until the first request or connect. */
12
+ get creditStatus(): import("./models.js").CreditStatus | null;
16
13
  constructor(options?: BeamClientOptions);
17
14
  close(): Promise<void>;
18
15
  openTransferTerminalWaiter(transferId: string): Promise<TransferTerminalSignalWaiter>;
package/dist/client.js CHANGED
@@ -2,16 +2,9 @@ import { abortMultipartUpload, createMultipartUpload, expiresAtIso, prepareProvi
2
2
  import { BeamTransferControl, BEAM_DEFAULT_NATS_URL, compactSignedRoutes } from "./nats-control.js";
3
3
  import { multipartPartNumber } from "./multipart-limits.js";
4
4
  export { BEAM_DEFAULT_NATS_URL };
5
- export class BeamApiError extends Error {
6
- status;
7
- body;
8
- constructor(message, status, body) {
9
- super(message);
10
- this.status = status;
11
- this.body = body;
12
- this.name = "BeamApiError";
13
- }
14
- }
5
+ // The error classes live in ./errors.js and are re-exported by ./index.js.
6
+ // Re-exporting them here too would make `export *` in index.ts ambiguous, which
7
+ // silently drops the names from the package's public types.
15
8
  export class BeamClient {
16
9
  apiKey;
17
10
  natsUrl;
@@ -19,6 +12,10 @@ export class BeamClient {
19
12
  control;
20
13
  routeSigningConcurrency;
21
14
  routeSigningConcurrencyOverridden;
15
+ /** Last credit state Beam reported. Null until the first request or connect. */
16
+ get creditStatus() {
17
+ return this.control.creditStatus;
18
+ }
22
19
  constructor(options = {}) {
23
20
  if (!options.apiKey?.trim()) {
24
21
  throw new Error("apiKey is required.");
@@ -35,7 +32,8 @@ export class BeamClient {
35
32
  subjectPrefix: options.transferClientSubjectPrefix,
36
33
  transferRuntimeShardCount: options.transferRuntimeShardCount,
37
34
  requestTimeoutMs: options.requestTimeoutMs,
38
- maxPayloadBytes: options.maxPayloadBytes
35
+ maxPayloadBytes: options.maxPayloadBytes,
36
+ onCreditWarning: options.onCreditWarning
39
37
  });
40
38
  this.fetchImpl = options.fetch ?? globalThis.fetch;
41
39
  if (!this.fetchImpl) {
@@ -0,0 +1,17 @@
1
+ import type { CreditStatus } from "./models.js";
2
+ export declare class BeamApiError extends Error {
3
+ readonly status: number;
4
+ readonly body: string;
5
+ constructor(message: string, status: number, body: string);
6
+ }
7
+ /**
8
+ * Raised when Beam refuses a request because the organization is out of credits.
9
+ *
10
+ * Always terminal: 402 is outside the retryable status set, so the SDK fails fast
11
+ * rather than retrying a request that cannot succeed until the balance is topped up.
12
+ */
13
+ export declare class BeamInsufficientCreditsError extends BeamApiError {
14
+ readonly code: string;
15
+ readonly credits: CreditStatus | null;
16
+ constructor(message: string, code: string, credits: CreditStatus | null, body: string);
17
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,26 @@
1
+ export class BeamApiError extends Error {
2
+ status;
3
+ body;
4
+ constructor(message, status, body) {
5
+ super(message);
6
+ this.status = status;
7
+ this.body = body;
8
+ this.name = "BeamApiError";
9
+ }
10
+ }
11
+ /**
12
+ * Raised when Beam refuses a request because the organization is out of credits.
13
+ *
14
+ * Always terminal: 402 is outside the retryable status set, so the SDK fails fast
15
+ * rather than retrying a request that cannot succeed until the balance is topped up.
16
+ */
17
+ export class BeamInsufficientCreditsError extends BeamApiError {
18
+ code;
19
+ credits;
20
+ constructor(message, code, credits, body) {
21
+ super(message, 402, body);
22
+ this.code = code;
23
+ this.credits = credits;
24
+ this.name = "BeamInsufficientCreditsError";
25
+ }
26
+ }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from "./client.js";
2
+ export * from "./errors.js";
2
3
  export * from "./models.js";
3
4
  export * from "./provider-signing.js";
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from "./client.js";
2
+ export * from "./errors.js";
2
3
  export * from "./models.js";
3
4
  export * from "./provider-signing.js";
package/dist/models.d.ts CHANGED
@@ -1,4 +1,27 @@
1
1
  export type SignedUrlFlow = "signed_url_v1" | "signed_url_v2";
2
+ export type CreditVerdict = "unlimited" | "ok" | "low" | "exhausted" | "unknown";
3
+ /**
4
+ * Credit state for the API key that made the request.
5
+ *
6
+ * The verdict is computed server-side and is the single source of truth: never
7
+ * derive one client-side from `remaining` and `low_threshold`, or the SDK will
8
+ * warn at a different point than Beam actually blocks.
9
+ */
10
+ export interface CreditStatus {
11
+ verdict: CreditVerdict;
12
+ /** Credits left in the organization pool. Null when unlimited or unknown. */
13
+ remaining: number | null;
14
+ /** Balance below which Beam warns. Display only. */
15
+ low_threshold: number | null;
16
+ billing_key_id: string | null;
17
+ billing_org_id: string | null;
18
+ }
19
+ export interface CreditWarning {
20
+ code: string;
21
+ message: string;
22
+ detail?: unknown;
23
+ credits?: CreditStatus;
24
+ }
2
25
  export interface BeamClientOptions {
3
26
  apiKey?: string;
4
27
  natsUrl?: string;
@@ -10,6 +33,12 @@ export interface BeamClientOptions {
10
33
  maxPayloadBytes?: number;
11
34
  routeSigningConcurrency?: number;
12
35
  fetch?: typeof fetch;
36
+ /**
37
+ * Called when Beam reports a credit problem, including once at connect time
38
+ * from the auth token. Without a handler the SDK logs one line per verdict
39
+ * change rather than per request.
40
+ */
41
+ onCreditWarning?: (warning: CreditWarning) => void;
13
42
  }
14
43
  export interface SourceConfig {
15
44
  type: string;
@@ -72,6 +101,8 @@ export interface RawTransferCreateInput {
72
101
  idempotencyKey?: string;
73
102
  }
74
103
  export interface TransferCreateResponse {
104
+ /** Credit state after this request. */
105
+ credits?: CreditStatus;
75
106
  success: boolean;
76
107
  transfer_id: string;
77
108
  transfer_key?: string;
@@ -85,6 +116,8 @@ export interface TransferCreateResponse {
85
116
  message?: string;
86
117
  }
87
118
  export interface TransferCancelResponse {
119
+ /** Credit state after this request. */
120
+ credits?: CreditStatus;
88
121
  success: boolean;
89
122
  message?: string;
90
123
  }
@@ -115,6 +148,8 @@ export interface DestinationStatusInfo {
115
148
  location?: string;
116
149
  }
117
150
  export interface TransferStatusInfo {
151
+ /** Credit state after this request. */
152
+ credits?: CreditStatus;
118
153
  transfer_id: string;
119
154
  name?: string;
120
155
  status: string;
@@ -350,6 +385,8 @@ export interface MultipartGroupManifest {
350
385
  urls_expires_at: string;
351
386
  }
352
387
  export interface TransferPlanResponse {
388
+ /** Credit state after this request. */
389
+ credits?: CreditStatus;
353
390
  success: boolean;
354
391
  chunk_size?: number;
355
392
  total_size?: number;
@@ -363,6 +400,8 @@ export interface TransferPlanResponse {
363
400
  message?: string;
364
401
  }
365
402
  export interface TransferPrepareResponse {
403
+ /** Credit state after this request. */
404
+ credits?: CreditStatus;
366
405
  success: boolean;
367
406
  transfer_id: string;
368
407
  transfer_key?: string;
@@ -1,4 +1,4 @@
1
- import type { MultipartGroupManifest, SignedChunkRoute, TransferTerminalSignalWaiter } from "./models.js";
1
+ import type { CreditStatus, CreditWarning, MultipartGroupManifest, SignedChunkRoute, TransferTerminalSignalWaiter } from "./models.js";
2
2
  export declare const TRANSFER_CLIENT_CONTROL_SCHEMA_VERSION = "transfer-client-control/v4";
3
3
  export declare const BEAM_DEFAULT_NATS_URL = "tls://nats.b1m.ai:4222";
4
4
  export declare const BEAM_DEFAULT_NATS_WS_URL = "wss://nats.b1m.ai:443";
@@ -51,6 +51,7 @@ export interface TransferControlOptions {
51
51
  transferRuntimeShardCount?: number;
52
52
  requestTimeoutMs?: number;
53
53
  maxPayloadBytes?: number;
54
+ onCreditWarning?: (warning: CreditWarning) => void;
54
55
  }
55
56
  export interface LifecycleRequestOptions {
56
57
  transferId?: string;
@@ -72,6 +73,18 @@ export declare class BeamTransferControl {
72
73
  private authTokenExpiresAt;
73
74
  private authResolvePromise;
74
75
  private readonly terminalWaitCancellations;
76
+ private lastCredits;
77
+ private reportedVerdict;
78
+ private readonly onCreditWarning;
79
+ /** Last credit state Beam reported, for display without issuing a request. */
80
+ get creditStatus(): CreditStatus | null;
81
+ /**
82
+ * Records the credit state from a reply or auth token and surfaces a warning.
83
+ *
84
+ * Fires on verdict transitions, not per request, so a long-running client does
85
+ * not emit the same warning on every call.
86
+ */
87
+ private observeCredits;
75
88
  constructor(options: TransferControlOptions);
76
89
  close(): Promise<void>;
77
90
  request<T>(messageType: TransferClientMessageType, payload: Record<string, unknown>, options?: LifecycleRequestOptions): Promise<T>;
@@ -1,5 +1,6 @@
1
1
  import { encode, decode } from "@msgpack/msgpack";
2
2
  import { connect } from "nats";
3
+ import { BeamApiError, BeamInsufficientCreditsError } from "./errors.js";
3
4
  export const TRANSFER_CLIENT_CONTROL_SCHEMA_VERSION = "transfer-client-control/v4";
4
5
  export const BEAM_DEFAULT_NATS_URL = "tls://nats.b1m.ai:4222";
5
6
  export const BEAM_DEFAULT_NATS_WS_URL = "wss://nats.b1m.ai:443";
@@ -41,6 +42,42 @@ export class BeamTransferControl {
41
42
  authTokenExpiresAt = 0;
42
43
  authResolvePromise = null;
43
44
  terminalWaitCancellations = new Set();
45
+ lastCredits = null;
46
+ reportedVerdict = null;
47
+ onCreditWarning;
48
+ /** Last credit state Beam reported, for display without issuing a request. */
49
+ get creditStatus() {
50
+ return this.lastCredits;
51
+ }
52
+ /**
53
+ * Records the credit state from a reply or auth token and surfaces a warning.
54
+ *
55
+ * Fires on verdict transitions, not per request, so a long-running client does
56
+ * not emit the same warning on every call.
57
+ */
58
+ observeCredits(credits, warnings) {
59
+ if (!credits)
60
+ return this.lastCredits;
61
+ this.lastCredits = credits;
62
+ if (credits.verdict === this.reportedVerdict)
63
+ return credits;
64
+ this.reportedVerdict = credits.verdict;
65
+ if (credits.verdict !== "low" && credits.verdict !== "exhausted")
66
+ return credits;
67
+ const warning = warnings?.[0] ?? {
68
+ code: credits.verdict === "exhausted" ? "insufficient_credits" : "credits_low",
69
+ message: credits.remaining === null
70
+ ? "Beam credit balance is low."
71
+ : `Beam credit balance is low: ${credits.remaining} credits remaining.`,
72
+ credits
73
+ };
74
+ if (this.onCreditWarning) {
75
+ this.onCreditWarning({ ...warning, credits });
76
+ return credits;
77
+ }
78
+ console.warn(`[beam] ${warning.message}`);
79
+ return credits;
80
+ }
44
81
  constructor(options) {
45
82
  this.apiKey = options.apiKey;
46
83
  this.keyPrefix = options.apiKey.slice(0, 12);
@@ -50,6 +87,7 @@ export class BeamTransferControl {
50
87
  this.requestTimeoutMs = options.requestTimeoutMs ?? 30_000;
51
88
  this.maxPayloadBytes = options.maxPayloadBytes ?? BEAM_DEFAULT_MAX_PAYLOAD_BYTES;
52
89
  this.natsUrl = options.natsUrl ?? options.natsWsUrl ?? BEAM_DEFAULT_NATS_URL;
90
+ this.onCreditWarning = options.onCreditWarning;
53
91
  if (/^https?:\/\//i.test(this.natsUrl) || /^wss?:\/\//i.test(this.natsUrl) && !options.natsWsUrl) {
54
92
  throw new Error("Beam SDK lifecycle endpoint must be nats://, tls://, or configured as natsWsUrl.");
55
93
  }
@@ -104,10 +142,19 @@ export class BeamTransferControl {
104
142
  const nc = await this.connection();
105
143
  const response = await nc.request(subject, bytes, { timeout: this.requestTimeoutMs });
106
144
  const decoded = decode(response.data);
107
- if (decoded.ok)
108
- return (decoded.payload ?? {});
145
+ const credits = this.observeCredits(decoded.credits, decoded.warnings);
146
+ if (decoded.ok) {
147
+ const payload = (decoded.payload ?? {});
148
+ // Credits ride on the envelope, not the payload, so they reach every
149
+ // response type without a per-message-type model change.
150
+ return (credits ? { ...payload, credits } : payload);
151
+ }
109
152
  const body = JSON.stringify(decoded.error ?? {});
110
- const error = new Error(`Beam lifecycle request failed with ${decoded.status}: ${body}`);
153
+ const code = decoded.error?.code ?? "lifecycle_error";
154
+ const message = decoded.error?.message ?? body;
155
+ const error = decoded.status === 402
156
+ ? new BeamInsufficientCreditsError(message, code, credits, body)
157
+ : new BeamApiError(`Beam lifecycle request failed with ${decoded.status}: ${body}`, decoded.status, body);
111
158
  if (!isRetryableLifecycleStatus(decoded.status) || attempt + 1 >= LIFECYCLE_REQUEST_MAX_ATTEMPTS) {
112
159
  throw error;
113
160
  }
@@ -311,6 +358,9 @@ export class BeamTransferControl {
311
358
  const claims = decodeJwtPayload(parsed.token);
312
359
  this.authToken = parsed.token;
313
360
  this.authTokenExpiresAt = claims.exp;
361
+ // Pre-flight: the token carries the verdict, so a low balance surfaces
362
+ // before the first lifecycle request is ever sent.
363
+ this.observeCredits(claims.credits);
314
364
  return parsed.token;
315
365
  }
316
366
  catch (error) {
package/package.json CHANGED
@@ -1,38 +1,38 @@
1
1
  {
2
- "name": "@beam-network/sdk",
3
- "version": "0.4.4",
4
- "description": "TypeScript SDK for BEAM transfer creation and management.",
5
- "type": "module",
6
- "license": "MIT",
7
- "sideEffects": false,
8
- "main": "./dist/index.js",
9
- "types": "./dist/index.d.ts",
10
- "exports": {
11
- ".": {
12
- "types": "./dist/index.d.ts",
13
- "import": "./dist/index.js"
14
- }
15
- },
16
- "files": [
17
- "dist",
18
- "README.md"
19
- ],
20
- "scripts": {
21
- "build": "tsc -p tsconfig.json",
22
- "test": "tsc -p tsconfig.json --noEmit && npm run test:functional",
23
- "test:functional": "npm run build && node --test test/client.functional.test.mjs",
24
- "lint": "tsc -p tsconfig.json --noEmit"
25
- },
26
- "publishConfig": {
27
- "access": "public"
28
- },
29
- "dependencies": {
30
- "@aws-sdk/client-s3": "^3.806.0",
31
- "@aws-sdk/s3-request-presigner": "^3.806.0",
32
- "@msgpack/msgpack": "^3.0.0",
33
- "nats": "^2.29.3"
34
- },
35
- "devDependencies": {
36
- "typescript": "^5.5.0"
2
+ "name": "@beam-network/sdk",
3
+ "version": "0.5.0",
4
+ "description": "TypeScript SDK for BEAM transfer creation and management.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "sideEffects": false,
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
37
14
  }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsc -p tsconfig.json",
22
+ "test": "tsc -p tsconfig.json --noEmit && npm run test:functional",
23
+ "test:functional": "npm run build && node --test test/client.functional.test.mjs",
24
+ "lint": "tsc -p tsconfig.json --noEmit"
25
+ },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "dependencies": {
30
+ "@aws-sdk/client-s3": "^3.806.0",
31
+ "@aws-sdk/s3-request-presigner": "^3.806.0",
32
+ "@msgpack/msgpack": "^3.0.0",
33
+ "nats": "^2.29.3"
34
+ },
35
+ "devDependencies": {
36
+ "typescript": "^5.5.0"
37
+ }
38
38
  }