@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 +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +136 -32
- package/dist/index.mjs +139 -35
- package/package.json +1 -1
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
|
-
/**
|
|
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
|
-
/**
|
|
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 =
|
|
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
|
-
/**
|
|
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
|
|
28501
|
-
|
|
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
|
|
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
|
|
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() -
|
|
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
|
|
28631
|
-
logger.error(`[parcels] file upload failed for ${message.type}, sending an error instead: ${
|
|
28632
|
-
return { message: asError(message, `Could not send a file: ${
|
|
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 (
|
|
28643
|
-
|
|
28644
|
-
|
|
28645
|
-
const
|
|
28646
|
-
|
|
28647
|
-
|
|
28648
|
-
|
|
28649
|
-
|
|
28650
|
-
|
|
28651
|
-
|
|
28652
|
-
|
|
28653
|
-
|
|
28654
|
-
|
|
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
|
-
|
|
28658
|
-
|
|
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
|
|
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 =
|
|
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
|
-
/**
|
|
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
|
|
28381
|
-
|
|
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
|
|
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
|
|
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() -
|
|
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
|
|
28511
|
-
logger.error(`[parcels] file upload failed for ${message.type}, sending an error instead: ${
|
|
28512
|
-
return { message: asError(message, `Could not send a file: ${
|
|
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 (
|
|
28523
|
-
|
|
28524
|
-
|
|
28525
|
-
const
|
|
28526
|
-
|
|
28527
|
-
|
|
28528
|
-
|
|
28529
|
-
|
|
28530
|
-
|
|
28531
|
-
|
|
28532
|
-
|
|
28533
|
-
|
|
28534
|
-
|
|
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
|
-
|
|
28538
|
-
|
|
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()}_${
|
|
30699
|
+
id: `${reqType.toLowerCase()}_${randomUUID4()}`,
|
|
30596
30700
|
type: reqType,
|
|
30597
30701
|
from: { type: this.type },
|
|
30598
30702
|
to: { type: "runtime" },
|