@superatomai/sdk-node 0.0.94-dsp → 0.0.95-dsp

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/index.d.mts CHANGED
@@ -4836,6 +4836,8 @@ interface ParcelRef {
4836
4836
  interface ParcelFileRef extends ParcelRef {
4837
4837
  name?: string;
4838
4838
  contentType: string;
4839
+ /** The file's bytes as base64, when storage was down and they travel in the message instead. */
4840
+ data?: string;
4839
4841
  }
4840
4842
  interface ParcelClientOptions {
4841
4843
  apiUrl: string;
@@ -4854,6 +4856,8 @@ declare class ParcelClient {
4854
4856
  constructor(opts: ParcelClientOptions);
4855
4857
  private url;
4856
4858
  private call;
4859
+ /** The receipt the bytes would get, without storing them. */
4860
+ refFor(bytes: Uint8Array, ext: string): ParcelRef;
4857
4861
  /** Stores the bytes and returns the receipt for them. */
4858
4862
  upload(bytes: Uint8Array, ext?: string, contentType?: string): Promise<ParcelRef>;
4859
4863
  /** The bytes behind a receipt, checked against its hash. */
@@ -4872,7 +4876,11 @@ declare class ParcelFile {
4872
4876
  readonly size: number;
4873
4877
  readonly contentType: string;
4874
4878
  constructor(ref: ParcelFileRef, client: ParcelClient | null);
4875
- /** The file's bytes, checked against their hash. Throws if they cannot be fetched. */
4879
+ /**
4880
+ * The file's bytes, checked against their hash — from the message itself when
4881
+ * it carried them (storage was down), otherwise from storage. Throws if they
4882
+ * cannot be fetched.
4883
+ */
4876
4884
  download(): Promise<Uint8Array>;
4877
4885
  toJSON(): {
4878
4886
  __parcelFile: ParcelFileRef;
package/dist/index.d.ts CHANGED
@@ -4836,6 +4836,8 @@ interface ParcelRef {
4836
4836
  interface ParcelFileRef extends ParcelRef {
4837
4837
  name?: string;
4838
4838
  contentType: string;
4839
+ /** The file's bytes as base64, when storage was down and they travel in the message instead. */
4840
+ data?: string;
4839
4841
  }
4840
4842
  interface ParcelClientOptions {
4841
4843
  apiUrl: string;
@@ -4854,6 +4856,8 @@ declare class ParcelClient {
4854
4856
  constructor(opts: ParcelClientOptions);
4855
4857
  private url;
4856
4858
  private call;
4859
+ /** The receipt the bytes would get, without storing them. */
4860
+ refFor(bytes: Uint8Array, ext: string): ParcelRef;
4857
4861
  /** Stores the bytes and returns the receipt for them. */
4858
4862
  upload(bytes: Uint8Array, ext?: string, contentType?: string): Promise<ParcelRef>;
4859
4863
  /** The bytes behind a receipt, checked against its hash. */
@@ -4872,7 +4876,11 @@ declare class ParcelFile {
4872
4876
  readonly size: number;
4873
4877
  readonly contentType: string;
4874
4878
  constructor(ref: ParcelFileRef, client: ParcelClient | null);
4875
- /** The file's bytes, checked against their hash. Throws if they cannot be fetched. */
4879
+ /**
4880
+ * The file's bytes, checked against their hash — from the message itself when
4881
+ * it carried them (storage was down), otherwise from storage. Throws if they
4882
+ * cannot be fetched.
4883
+ */
4876
4884
  download(): Promise<Uint8Array>;
4877
4885
  toJSON(): {
4878
4886
  __parcelFile: ParcelFileRef;
package/dist/index.js CHANGED
@@ -28370,12 +28370,19 @@ var import_crypto7 = require("crypto");
28370
28370
  var PARCEL_THRESHOLD_BYTES = 256 * 1024;
28371
28371
  var MAX_INLINE_BYTES = 900 * 1024;
28372
28372
  var MAX_PARCEL_BYTES = 95 * 1024 * 1024;
28373
+ var PART_BYTES = 600 * 1024;
28374
+ var PART_WAIT_MS = 6e4;
28373
28375
  var NEVER_PARCEL = /* @__PURE__ */ new Set(["PING", "PONG", "BUNDLE_CHUNK", "BUNDLE_CSS_CHUNK"]);
28374
28376
  var RETRY_DELAYS_MS = [500, 1500];
28375
28377
  var REQUEST_TIMEOUT_MS = 6e4;
28376
28378
  var MAX_DEPTH = 32;
28377
28379
  var PARCEL_FIELD = "__parcel";
28378
28380
  var FILE_FIELD = "__parcelFile";
28381
+ var PART_FIELD = "__parcelPart";
28382
+ var partOf = (payload) => {
28383
+ const p = payload?.[PART_FIELD];
28384
+ return p && typeof p.id === "string" && typeof p.data === "string" && Number.isInteger(p.total) && Number.isInteger(p.index) && p.total > 0 && p.index >= 0 && p.index < p.total ? p : null;
28385
+ };
28379
28386
  var isRef = (ref) => !!ref && typeof ref === "object" && typeof ref.projectId === "string" && typeof ref.hash === "string" && typeof ref.ext === "string" && typeof ref.size === "number";
28380
28387
  function parcelRefOf(payload) {
28381
28388
  const ref = payload?.[PARCEL_FIELD];
@@ -28430,12 +28437,16 @@ var ParcelClient = class {
28430
28437
  }
28431
28438
  return res;
28432
28439
  }
28440
+ /** The receipt the bytes would get, without storing them. */
28441
+ refFor(bytes, ext) {
28442
+ return { projectId: this.opts.projectId, hash: sha256Hex(bytes), ext, size: bytes.byteLength };
28443
+ }
28433
28444
  /** Stores the bytes and returns the receipt for them. */
28434
28445
  async upload(bytes, ext = "json", contentType = "application/json") {
28435
28446
  if (bytes.byteLength > MAX_PARCEL_BYTES) {
28436
28447
  throw new Error(`Parcel too large (${bytes.byteLength} bytes, limit ${MAX_PARCEL_BYTES})`);
28437
28448
  }
28438
- const ref = { projectId: this.opts.projectId, hash: sha256Hex(bytes), ext, size: bytes.byteLength };
28449
+ const ref = this.refFor(bytes, ext);
28439
28450
  await withRetries(
28440
28451
  "upload",
28441
28452
  () => this.call(this.url(ref), { method: "PUT", headers: { "Content-Type": contentType }, body: bytes })
@@ -28462,8 +28473,17 @@ var ParcelFile = class {
28462
28473
  this.size = ref.size;
28463
28474
  this.contentType = ref.contentType;
28464
28475
  }
28465
- /** The file's bytes, checked against their hash. Throws if they cannot be fetched. */
28476
+ /**
28477
+ * The file's bytes, checked against their hash — from the message itself when
28478
+ * it carried them (storage was down), otherwise from storage. Throws if they
28479
+ * cannot be fetched.
28480
+ */
28466
28481
  async download() {
28482
+ if (typeof this.ref.data === "string") {
28483
+ const bytes = new Uint8Array(Buffer.from(this.ref.data, "base64"));
28484
+ if (sha256Hex(bytes) !== this.ref.hash) throw new Error("File content does not match its hash");
28485
+ return bytes;
28486
+ }
28467
28487
  if (!this.client) throw new Error("Cannot download files: SA_API_URL or the API key is not set");
28468
28488
  return this.client.download(this.ref);
28469
28489
  }
@@ -28492,28 +28512,55 @@ function hasFileRef(value, depth = 0) {
28492
28512
  return false;
28493
28513
  }
28494
28514
  var extOf = (name) => typeof name === "string" && /\.([a-z0-9]{1,10})$/i.exec(name)?.[1]?.toLowerCase() || "bin";
28495
- async function uploadFiles(value, client, parent, depth = 0) {
28515
+ async function uploadFiles(value, client, storage, parent, depth = 0) {
28496
28516
  if (isBinary(value)) {
28497
28517
  const bytes = value instanceof Uint8Array ? value : new Uint8Array(value);
28498
28518
  const name = parent?.name ?? parent?.fileName;
28499
28519
  const contentType = String(parent?.contentType ?? parent?.mimeType ?? "application/octet-stream");
28500
- const ref = await client.upload(bytes, extOf(name), contentType);
28501
- const fileRef = { ...ref, contentType, ...typeof name === "string" ? { name } : {} };
28520
+ const named = typeof name === "string" ? { name } : {};
28521
+ if (!storage.down) {
28522
+ try {
28523
+ const ref = await client.upload(bytes, extOf(name), contentType);
28524
+ const fileRef2 = { ...ref, contentType, ...named };
28525
+ return { [FILE_FIELD]: fileRef2 };
28526
+ } catch (err) {
28527
+ if (bytes.byteLength > MAX_PARCEL_BYTES) throw err;
28528
+ storage.down = true;
28529
+ logger.warn(`[parcels] file upload failed, sending it in the message instead: ${err?.message ?? err}`);
28530
+ }
28531
+ }
28532
+ const fileRef = {
28533
+ ...client.refFor(bytes, extOf(name)),
28534
+ contentType,
28535
+ ...named,
28536
+ data: Buffer.from(bytes).toString("base64")
28537
+ };
28502
28538
  return { [FILE_FIELD]: fileRef };
28503
28539
  }
28504
28540
  if (depth >= MAX_DEPTH) return value;
28505
28541
  if (Array.isArray(value)) {
28506
28542
  const out = [];
28507
- for (const v of value) out.push(await uploadFiles(v, client, void 0, depth + 1));
28543
+ for (const v of value) out.push(await uploadFiles(v, client, storage, void 0, depth + 1));
28508
28544
  return out;
28509
28545
  }
28510
28546
  if (isPlain(value)) {
28511
28547
  const out = {};
28512
- for (const [k, v] of Object.entries(value)) out[k] = await uploadFiles(v, client, value, depth + 1);
28548
+ for (const [k, v] of Object.entries(value)) out[k] = await uploadFiles(v, client, storage, value, depth + 1);
28513
28549
  return out;
28514
28550
  }
28515
28551
  return value;
28516
28552
  }
28553
+ function splitPayload(payload) {
28554
+ const bytes = Buffer.from(JSON.stringify(payload ?? null), "utf8");
28555
+ const total = Math.max(1, Math.ceil(bytes.byteLength / PART_BYTES));
28556
+ const id = (0, import_crypto7.randomUUID)();
28557
+ return Array.from({ length: total }, (_, index) => ({
28558
+ id,
28559
+ index,
28560
+ total,
28561
+ data: bytes.subarray(index * PART_BYTES, (index + 1) * PART_BYTES).toString("base64")
28562
+ }));
28563
+ }
28517
28564
  function toFileHandles(value, client, depth = 0) {
28518
28565
  const ref = fileRefOf(value);
28519
28566
  if (ref) return new ParcelFile(ref, client);
@@ -28593,8 +28640,7 @@ var OutgoingParcels = class {
28593
28640
  if (Buffer.byteLength(wire, "utf8") < this.threshold) return this.now(message, wire);
28594
28641
  }
28595
28642
  this.queueFor(message).run(async () => {
28596
- const out = await this.prepare(message, files);
28597
- this.deliver(out.message, out.wire);
28643
+ for (const out of await this.prepare(message, files)) this.deliver(out.message, out.wire);
28598
28644
  });
28599
28645
  return true;
28600
28646
  }
@@ -28619,44 +28665,60 @@ var OutgoingParcels = class {
28619
28665
  async idle() {
28620
28666
  await Promise.all(Array.from(this.queues.values(), (queue) => queue.idle()));
28621
28667
  }
28668
+ /**
28669
+ * What actually goes on the socket for this message: usually one message (as
28670
+ * is, or as a parcel receipt); several parts when storage is down and the
28671
+ * message is too large for the relay.
28672
+ */
28622
28673
  async prepare(message, files) {
28623
28674
  let current = message;
28675
+ const storage = { down: false };
28624
28676
  if (files) {
28625
- const started2 = Date.now();
28677
+ const started = Date.now();
28626
28678
  try {
28627
- current = { ...message, payload: await uploadFiles(message.payload, this.client) };
28628
- logger.info(`[parcels] ${message.type} ${message.id ?? ""}: file(s) uploaded (${Date.now() - started2}ms)`);
28679
+ current = { ...message, payload: await uploadFiles(message.payload, this.client, storage) };
28680
+ if (!storage.down) logger.info(`[parcels] ${message.type} ${message.id ?? ""}: file(s) uploaded (${Date.now() - started}ms)`);
28629
28681
  } catch (err) {
28630
- const reason = err?.message ?? String(err);
28631
- logger.error(`[parcels] file upload failed for ${message.type}, sending an error instead: ${reason}`);
28632
- return { message: asError(message, `Could not send a file: ${reason}`) };
28682
+ const reason2 = err?.message ?? String(err);
28683
+ logger.error(`[parcels] file upload failed for ${message.type}, sending an error instead: ${reason2}`);
28684
+ return [{ message: asError(message, `Could not send a file: ${reason2}`) }];
28633
28685
  }
28634
28686
  }
28635
28687
  let wire;
28636
28688
  try {
28637
28689
  wire = JSON.stringify(current);
28638
28690
  } catch {
28639
- return { message: current };
28691
+ return [{ message: current }];
28640
28692
  }
28641
28693
  const size = Buffer.byteLength(wire, "utf8");
28642
- if (!this.jsonParcels || size < this.threshold) return { message: current, wire };
28643
- const started = Date.now();
28644
- try {
28645
- const bytes = Buffer.from(JSON.stringify(current.payload ?? null), "utf8");
28646
- const ref = await this.client.upload(bytes);
28647
- logger.info(
28648
- `[parcels] ${message.type} ${message.id ?? ""} \u2192 parcel ${ref.hash.slice(0, 12)} (${ref.size} bytes, ${Date.now() - started}ms)`
28649
- );
28650
- return { message: { ...current, payload: { [PARCEL_FIELD]: ref } } };
28651
- } catch (err) {
28652
- const reason = err?.message ?? String(err);
28653
- if (size <= MAX_INLINE_BYTES) {
28654
- logger.warn(`[parcels] upload failed for ${message.type}, sending inline (${size} bytes): ${reason}`);
28655
- return { message: current, wire };
28694
+ if (size < this.threshold) return [{ message: current, wire }];
28695
+ let reason = "large-JSON parcels are off";
28696
+ if (this.jsonParcels && !storage.down) {
28697
+ const started = Date.now();
28698
+ try {
28699
+ const bytes = Buffer.from(JSON.stringify(current.payload ?? null), "utf8");
28700
+ const ref = await this.client.upload(bytes);
28701
+ logger.info(
28702
+ `[parcels] ${message.type} ${message.id ?? ""} \u2192 parcel ${ref.hash.slice(0, 12)} (${ref.size} bytes, ${Date.now() - started}ms)`
28703
+ );
28704
+ return [{ message: { ...current, payload: { [PARCEL_FIELD]: ref } } }];
28705
+ } catch (err) {
28706
+ reason = err?.message ?? String(err);
28656
28707
  }
28657
- logger.error(`[parcels] upload failed for ${message.type} (${size} bytes), sending an error instead: ${reason}`);
28658
- return { message: asError(message, `The message was too large to deliver (${size} bytes): ${reason}`) };
28708
+ } else if (storage.down) {
28709
+ reason = "storage is down";
28710
+ }
28711
+ if (size <= MAX_INLINE_BYTES) {
28712
+ if (this.jsonParcels) logger.warn(`[parcels] ${message.type} not stored (${reason}), sending inline (${size} bytes)`);
28713
+ return [{ message: current, wire }];
28659
28714
  }
28715
+ if (size > MAX_PARCEL_BYTES) {
28716
+ logger.error(`[parcels] ${message.type} (${size} bytes) is too large to send`);
28717
+ return [{ message: asError(message, `The message was too large to deliver (${size} bytes): ${reason}`) }];
28718
+ }
28719
+ const parts = splitPayload(current.payload);
28720
+ logger.warn(`[parcels] ${message.type} not stored (${reason}), sending in ${parts.length} parts (${size} bytes)`);
28721
+ return parts.map((part) => ({ message: { ...current, payload: { [PART_FIELD]: part } } }));
28660
28722
  }
28661
28723
  };
28662
28724
  var IncomingParcels = class {
@@ -28664,12 +28726,22 @@ var IncomingParcels = class {
28664
28726
  this.client = client;
28665
28727
  this.dispatch = dispatch;
28666
28728
  this.queue = new InOrder();
28729
+ /** Split messages still waiting for parts, by part id. */
28730
+ this.partial = /* @__PURE__ */ new Map();
28731
+ /** Split messages already rebuilt, so a second copy of their parts is ignored. */
28732
+ this.rebuilt = /* @__PURE__ */ new Set();
28667
28733
  }
28668
28734
  /**
28669
28735
  * `raw` is the message's text when it is at hand: a message that never
28670
28736
  * mentions a file placeholder is not searched for one.
28671
28737
  */
28672
28738
  receive(message, raw) {
28739
+ const part = partOf(message?.payload);
28740
+ if (part) {
28741
+ const whole = this.collect(message, part);
28742
+ if (whole) this.receive(whole);
28743
+ return;
28744
+ }
28673
28745
  const ref = parcelRefOf(message?.payload);
28674
28746
  if (!ref) {
28675
28747
  const withFiles = raw === void 0 || raw.includes(FILE_FIELD) ? this.withFiles(message) : message;
@@ -28685,6 +28757,38 @@ var IncomingParcels = class {
28685
28757
  idle() {
28686
28758
  return this.queue.idle();
28687
28759
  }
28760
+ /**
28761
+ * Holds one part; once all of them are here, the original message (envelope
28762
+ * from the last part, payload rebuilt). Null while parts are still missing.
28763
+ */
28764
+ collect(message, part) {
28765
+ if (this.rebuilt.has(part.id)) return null;
28766
+ let entry = this.partial.get(part.id);
28767
+ if (!entry) {
28768
+ const timer = setTimeout(() => {
28769
+ this.partial.delete(part.id);
28770
+ logger.warn(`[parcels] ${message.type}: gave up waiting for the rest of a split message (${part.id})`);
28771
+ }, PART_WAIT_MS).unref();
28772
+ entry = { parts: new Array(part.total), got: 0, timer };
28773
+ this.partial.set(part.id, entry);
28774
+ }
28775
+ if (entry.parts.length !== part.total || entry.parts[part.index] !== void 0) return null;
28776
+ entry.parts[part.index] = part.data;
28777
+ if (++entry.got < part.total) return null;
28778
+ clearTimeout(entry.timer);
28779
+ this.partial.delete(part.id);
28780
+ this.rebuilt.add(part.id);
28781
+ setTimeout(() => this.rebuilt.delete(part.id), PART_WAIT_MS).unref();
28782
+ try {
28783
+ const bytes = Buffer.concat(entry.parts.map((data) => Buffer.from(data, "base64")));
28784
+ logger.info(`[parcels] ${message.type} ${message.id ?? ""} rebuilt from ${part.total} parts (${bytes.byteLength} bytes)`);
28785
+ return { ...message, payload: JSON.parse(bytes.toString("utf8")) };
28786
+ } catch (err) {
28787
+ const reason = err?.message ?? String(err);
28788
+ logger.error(`[parcels] could not rebuild a split ${message.type}: ${reason}`);
28789
+ return asError(message, `Could not rebuild the message: ${reason}`);
28790
+ }
28791
+ }
28688
28792
  withFiles(message) {
28689
28793
  try {
28690
28794
  return hasFileRef(message.payload) ? { ...message, payload: toFileHandles(message.payload, this.client) } : message;
package/dist/index.mjs CHANGED
@@ -6,7 +6,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
6
6
  });
7
7
 
8
8
  // src/index.ts
9
- import { randomUUID as randomUUID3 } from "crypto";
9
+ import { randomUUID as randomUUID4 } from "crypto";
10
10
 
11
11
  // src/websocket.ts
12
12
  import WebSocket from "ws";
@@ -28246,16 +28246,23 @@ var ReportManager = class {
28246
28246
  };
28247
28247
 
28248
28248
  // src/parcels/index.ts
28249
- import { createHash as createHash3 } from "crypto";
28249
+ import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
28250
28250
  var PARCEL_THRESHOLD_BYTES = 256 * 1024;
28251
28251
  var MAX_INLINE_BYTES = 900 * 1024;
28252
28252
  var MAX_PARCEL_BYTES = 95 * 1024 * 1024;
28253
+ var PART_BYTES = 600 * 1024;
28254
+ var PART_WAIT_MS = 6e4;
28253
28255
  var NEVER_PARCEL = /* @__PURE__ */ new Set(["PING", "PONG", "BUNDLE_CHUNK", "BUNDLE_CSS_CHUNK"]);
28254
28256
  var RETRY_DELAYS_MS = [500, 1500];
28255
28257
  var REQUEST_TIMEOUT_MS = 6e4;
28256
28258
  var MAX_DEPTH = 32;
28257
28259
  var PARCEL_FIELD = "__parcel";
28258
28260
  var FILE_FIELD = "__parcelFile";
28261
+ var PART_FIELD = "__parcelPart";
28262
+ var partOf = (payload) => {
28263
+ const p = payload?.[PART_FIELD];
28264
+ return p && typeof p.id === "string" && typeof p.data === "string" && Number.isInteger(p.total) && Number.isInteger(p.index) && p.total > 0 && p.index >= 0 && p.index < p.total ? p : null;
28265
+ };
28259
28266
  var isRef = (ref) => !!ref && typeof ref === "object" && typeof ref.projectId === "string" && typeof ref.hash === "string" && typeof ref.ext === "string" && typeof ref.size === "number";
28260
28267
  function parcelRefOf(payload) {
28261
28268
  const ref = payload?.[PARCEL_FIELD];
@@ -28310,12 +28317,16 @@ var ParcelClient = class {
28310
28317
  }
28311
28318
  return res;
28312
28319
  }
28320
+ /** The receipt the bytes would get, without storing them. */
28321
+ refFor(bytes, ext) {
28322
+ return { projectId: this.opts.projectId, hash: sha256Hex(bytes), ext, size: bytes.byteLength };
28323
+ }
28313
28324
  /** Stores the bytes and returns the receipt for them. */
28314
28325
  async upload(bytes, ext = "json", contentType = "application/json") {
28315
28326
  if (bytes.byteLength > MAX_PARCEL_BYTES) {
28316
28327
  throw new Error(`Parcel too large (${bytes.byteLength} bytes, limit ${MAX_PARCEL_BYTES})`);
28317
28328
  }
28318
- const ref = { projectId: this.opts.projectId, hash: sha256Hex(bytes), ext, size: bytes.byteLength };
28329
+ const ref = this.refFor(bytes, ext);
28319
28330
  await withRetries(
28320
28331
  "upload",
28321
28332
  () => this.call(this.url(ref), { method: "PUT", headers: { "Content-Type": contentType }, body: bytes })
@@ -28342,8 +28353,17 @@ var ParcelFile = class {
28342
28353
  this.size = ref.size;
28343
28354
  this.contentType = ref.contentType;
28344
28355
  }
28345
- /** The file's bytes, checked against their hash. Throws if they cannot be fetched. */
28356
+ /**
28357
+ * The file's bytes, checked against their hash — from the message itself when
28358
+ * it carried them (storage was down), otherwise from storage. Throws if they
28359
+ * cannot be fetched.
28360
+ */
28346
28361
  async download() {
28362
+ if (typeof this.ref.data === "string") {
28363
+ const bytes = new Uint8Array(Buffer.from(this.ref.data, "base64"));
28364
+ if (sha256Hex(bytes) !== this.ref.hash) throw new Error("File content does not match its hash");
28365
+ return bytes;
28366
+ }
28347
28367
  if (!this.client) throw new Error("Cannot download files: SA_API_URL or the API key is not set");
28348
28368
  return this.client.download(this.ref);
28349
28369
  }
@@ -28372,28 +28392,55 @@ function hasFileRef(value, depth = 0) {
28372
28392
  return false;
28373
28393
  }
28374
28394
  var extOf = (name) => typeof name === "string" && /\.([a-z0-9]{1,10})$/i.exec(name)?.[1]?.toLowerCase() || "bin";
28375
- async function uploadFiles(value, client, parent, depth = 0) {
28395
+ async function uploadFiles(value, client, storage, parent, depth = 0) {
28376
28396
  if (isBinary(value)) {
28377
28397
  const bytes = value instanceof Uint8Array ? value : new Uint8Array(value);
28378
28398
  const name = parent?.name ?? parent?.fileName;
28379
28399
  const contentType = String(parent?.contentType ?? parent?.mimeType ?? "application/octet-stream");
28380
- const ref = await client.upload(bytes, extOf(name), contentType);
28381
- const fileRef = { ...ref, contentType, ...typeof name === "string" ? { name } : {} };
28400
+ const named = typeof name === "string" ? { name } : {};
28401
+ if (!storage.down) {
28402
+ try {
28403
+ const ref = await client.upload(bytes, extOf(name), contentType);
28404
+ const fileRef2 = { ...ref, contentType, ...named };
28405
+ return { [FILE_FIELD]: fileRef2 };
28406
+ } catch (err) {
28407
+ if (bytes.byteLength > MAX_PARCEL_BYTES) throw err;
28408
+ storage.down = true;
28409
+ logger.warn(`[parcels] file upload failed, sending it in the message instead: ${err?.message ?? err}`);
28410
+ }
28411
+ }
28412
+ const fileRef = {
28413
+ ...client.refFor(bytes, extOf(name)),
28414
+ contentType,
28415
+ ...named,
28416
+ data: Buffer.from(bytes).toString("base64")
28417
+ };
28382
28418
  return { [FILE_FIELD]: fileRef };
28383
28419
  }
28384
28420
  if (depth >= MAX_DEPTH) return value;
28385
28421
  if (Array.isArray(value)) {
28386
28422
  const out = [];
28387
- for (const v of value) out.push(await uploadFiles(v, client, void 0, depth + 1));
28423
+ for (const v of value) out.push(await uploadFiles(v, client, storage, void 0, depth + 1));
28388
28424
  return out;
28389
28425
  }
28390
28426
  if (isPlain(value)) {
28391
28427
  const out = {};
28392
- for (const [k, v] of Object.entries(value)) out[k] = await uploadFiles(v, client, value, depth + 1);
28428
+ for (const [k, v] of Object.entries(value)) out[k] = await uploadFiles(v, client, storage, value, depth + 1);
28393
28429
  return out;
28394
28430
  }
28395
28431
  return value;
28396
28432
  }
28433
+ function splitPayload(payload) {
28434
+ const bytes = Buffer.from(JSON.stringify(payload ?? null), "utf8");
28435
+ const total = Math.max(1, Math.ceil(bytes.byteLength / PART_BYTES));
28436
+ const id = randomUUID3();
28437
+ return Array.from({ length: total }, (_, index) => ({
28438
+ id,
28439
+ index,
28440
+ total,
28441
+ data: bytes.subarray(index * PART_BYTES, (index + 1) * PART_BYTES).toString("base64")
28442
+ }));
28443
+ }
28397
28444
  function toFileHandles(value, client, depth = 0) {
28398
28445
  const ref = fileRefOf(value);
28399
28446
  if (ref) return new ParcelFile(ref, client);
@@ -28473,8 +28520,7 @@ var OutgoingParcels = class {
28473
28520
  if (Buffer.byteLength(wire, "utf8") < this.threshold) return this.now(message, wire);
28474
28521
  }
28475
28522
  this.queueFor(message).run(async () => {
28476
- const out = await this.prepare(message, files);
28477
- this.deliver(out.message, out.wire);
28523
+ for (const out of await this.prepare(message, files)) this.deliver(out.message, out.wire);
28478
28524
  });
28479
28525
  return true;
28480
28526
  }
@@ -28499,44 +28545,60 @@ var OutgoingParcels = class {
28499
28545
  async idle() {
28500
28546
  await Promise.all(Array.from(this.queues.values(), (queue) => queue.idle()));
28501
28547
  }
28548
+ /**
28549
+ * What actually goes on the socket for this message: usually one message (as
28550
+ * is, or as a parcel receipt); several parts when storage is down and the
28551
+ * message is too large for the relay.
28552
+ */
28502
28553
  async prepare(message, files) {
28503
28554
  let current = message;
28555
+ const storage = { down: false };
28504
28556
  if (files) {
28505
- const started2 = Date.now();
28557
+ const started = Date.now();
28506
28558
  try {
28507
- current = { ...message, payload: await uploadFiles(message.payload, this.client) };
28508
- logger.info(`[parcels] ${message.type} ${message.id ?? ""}: file(s) uploaded (${Date.now() - started2}ms)`);
28559
+ current = { ...message, payload: await uploadFiles(message.payload, this.client, storage) };
28560
+ if (!storage.down) logger.info(`[parcels] ${message.type} ${message.id ?? ""}: file(s) uploaded (${Date.now() - started}ms)`);
28509
28561
  } catch (err) {
28510
- const reason = err?.message ?? String(err);
28511
- logger.error(`[parcels] file upload failed for ${message.type}, sending an error instead: ${reason}`);
28512
- return { message: asError(message, `Could not send a file: ${reason}`) };
28562
+ const reason2 = err?.message ?? String(err);
28563
+ logger.error(`[parcels] file upload failed for ${message.type}, sending an error instead: ${reason2}`);
28564
+ return [{ message: asError(message, `Could not send a file: ${reason2}`) }];
28513
28565
  }
28514
28566
  }
28515
28567
  let wire;
28516
28568
  try {
28517
28569
  wire = JSON.stringify(current);
28518
28570
  } catch {
28519
- return { message: current };
28571
+ return [{ message: current }];
28520
28572
  }
28521
28573
  const size = Buffer.byteLength(wire, "utf8");
28522
- if (!this.jsonParcels || size < this.threshold) return { message: current, wire };
28523
- const started = Date.now();
28524
- try {
28525
- const bytes = Buffer.from(JSON.stringify(current.payload ?? null), "utf8");
28526
- const ref = await this.client.upload(bytes);
28527
- logger.info(
28528
- `[parcels] ${message.type} ${message.id ?? ""} \u2192 parcel ${ref.hash.slice(0, 12)} (${ref.size} bytes, ${Date.now() - started}ms)`
28529
- );
28530
- return { message: { ...current, payload: { [PARCEL_FIELD]: ref } } };
28531
- } catch (err) {
28532
- const reason = err?.message ?? String(err);
28533
- if (size <= MAX_INLINE_BYTES) {
28534
- logger.warn(`[parcels] upload failed for ${message.type}, sending inline (${size} bytes): ${reason}`);
28535
- return { message: current, wire };
28574
+ if (size < this.threshold) return [{ message: current, wire }];
28575
+ let reason = "large-JSON parcels are off";
28576
+ if (this.jsonParcels && !storage.down) {
28577
+ const started = Date.now();
28578
+ try {
28579
+ const bytes = Buffer.from(JSON.stringify(current.payload ?? null), "utf8");
28580
+ const ref = await this.client.upload(bytes);
28581
+ logger.info(
28582
+ `[parcels] ${message.type} ${message.id ?? ""} \u2192 parcel ${ref.hash.slice(0, 12)} (${ref.size} bytes, ${Date.now() - started}ms)`
28583
+ );
28584
+ return [{ message: { ...current, payload: { [PARCEL_FIELD]: ref } } }];
28585
+ } catch (err) {
28586
+ reason = err?.message ?? String(err);
28536
28587
  }
28537
- logger.error(`[parcels] upload failed for ${message.type} (${size} bytes), sending an error instead: ${reason}`);
28538
- return { message: asError(message, `The message was too large to deliver (${size} bytes): ${reason}`) };
28588
+ } else if (storage.down) {
28589
+ reason = "storage is down";
28590
+ }
28591
+ if (size <= MAX_INLINE_BYTES) {
28592
+ if (this.jsonParcels) logger.warn(`[parcels] ${message.type} not stored (${reason}), sending inline (${size} bytes)`);
28593
+ return [{ message: current, wire }];
28539
28594
  }
28595
+ if (size > MAX_PARCEL_BYTES) {
28596
+ logger.error(`[parcels] ${message.type} (${size} bytes) is too large to send`);
28597
+ return [{ message: asError(message, `The message was too large to deliver (${size} bytes): ${reason}`) }];
28598
+ }
28599
+ const parts = splitPayload(current.payload);
28600
+ logger.warn(`[parcels] ${message.type} not stored (${reason}), sending in ${parts.length} parts (${size} bytes)`);
28601
+ return parts.map((part) => ({ message: { ...current, payload: { [PART_FIELD]: part } } }));
28540
28602
  }
28541
28603
  };
28542
28604
  var IncomingParcels = class {
@@ -28544,12 +28606,22 @@ var IncomingParcels = class {
28544
28606
  this.client = client;
28545
28607
  this.dispatch = dispatch;
28546
28608
  this.queue = new InOrder();
28609
+ /** Split messages still waiting for parts, by part id. */
28610
+ this.partial = /* @__PURE__ */ new Map();
28611
+ /** Split messages already rebuilt, so a second copy of their parts is ignored. */
28612
+ this.rebuilt = /* @__PURE__ */ new Set();
28547
28613
  }
28548
28614
  /**
28549
28615
  * `raw` is the message's text when it is at hand: a message that never
28550
28616
  * mentions a file placeholder is not searched for one.
28551
28617
  */
28552
28618
  receive(message, raw) {
28619
+ const part = partOf(message?.payload);
28620
+ if (part) {
28621
+ const whole = this.collect(message, part);
28622
+ if (whole) this.receive(whole);
28623
+ return;
28624
+ }
28553
28625
  const ref = parcelRefOf(message?.payload);
28554
28626
  if (!ref) {
28555
28627
  const withFiles = raw === void 0 || raw.includes(FILE_FIELD) ? this.withFiles(message) : message;
@@ -28565,6 +28637,38 @@ var IncomingParcels = class {
28565
28637
  idle() {
28566
28638
  return this.queue.idle();
28567
28639
  }
28640
+ /**
28641
+ * Holds one part; once all of them are here, the original message (envelope
28642
+ * from the last part, payload rebuilt). Null while parts are still missing.
28643
+ */
28644
+ collect(message, part) {
28645
+ if (this.rebuilt.has(part.id)) return null;
28646
+ let entry = this.partial.get(part.id);
28647
+ if (!entry) {
28648
+ const timer = setTimeout(() => {
28649
+ this.partial.delete(part.id);
28650
+ logger.warn(`[parcels] ${message.type}: gave up waiting for the rest of a split message (${part.id})`);
28651
+ }, PART_WAIT_MS).unref();
28652
+ entry = { parts: new Array(part.total), got: 0, timer };
28653
+ this.partial.set(part.id, entry);
28654
+ }
28655
+ if (entry.parts.length !== part.total || entry.parts[part.index] !== void 0) return null;
28656
+ entry.parts[part.index] = part.data;
28657
+ if (++entry.got < part.total) return null;
28658
+ clearTimeout(entry.timer);
28659
+ this.partial.delete(part.id);
28660
+ this.rebuilt.add(part.id);
28661
+ setTimeout(() => this.rebuilt.delete(part.id), PART_WAIT_MS).unref();
28662
+ try {
28663
+ const bytes = Buffer.concat(entry.parts.map((data) => Buffer.from(data, "base64")));
28664
+ logger.info(`[parcels] ${message.type} ${message.id ?? ""} rebuilt from ${part.total} parts (${bytes.byteLength} bytes)`);
28665
+ return { ...message, payload: JSON.parse(bytes.toString("utf8")) };
28666
+ } catch (err) {
28667
+ const reason = err?.message ?? String(err);
28668
+ logger.error(`[parcels] could not rebuild a split ${message.type}: ${reason}`);
28669
+ return asError(message, `Could not rebuild the message: ${reason}`);
28670
+ }
28671
+ }
28568
28672
  withFiles(message) {
28569
28673
  try {
28570
28674
  return hasFileRef(message.payload) ? { ...message, payload: toFileHandles(message.payload, this.client) } : message;
@@ -30592,7 +30696,7 @@ var SuperatomSDK = class {
30592
30696
  waiters.push(finish);
30593
30697
  this.registryWaiters.set(resType, waiters);
30594
30698
  const sent = this.send({
30595
- id: `${reqType.toLowerCase()}_${randomUUID3()}`,
30699
+ id: `${reqType.toLowerCase()}_${randomUUID4()}`,
30596
30700
  type: reqType,
30597
30701
  from: { type: this.type },
30598
30702
  to: { type: "runtime" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superatomai/sdk-node",
3
- "version": "0.0.94-dsp",
3
+ "version": "0.0.95-dsp",
4
4
  "description": "Node.js TypeScript SDK for Superatom",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",