@johannc-dev/zyrk 0.2.0 → 0.2.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.
- package/README.md +8 -1
- package/dist/cli.d.ts +6 -1
- package/dist/cli.js +44 -8
- package/dist/client.d.ts +17 -4
- package/dist/client.js +515 -18
- package/dist/contracts.d.ts +16 -0
- package/dist/contracts.js +14 -0
- package/dist/errors.d.ts +9 -1
- package/dist/errors.js +6 -1
- package/dist/export.d.ts +27 -8
- package/dist/export.js +149 -19
- package/dist/hourfile.d.ts +3 -1
- package/dist/hourfile.js +7 -0
- package/dist/limits.d.ts +16 -2
- package/dist/limits.js +2 -0
- package/dist/local-data.d.ts +21 -4
- package/dist/local-data.js +52 -1
- package/dist/local-replay.d.ts +5 -5
- package/dist/local-replay.js +4 -3
- package/dist/local-storage.d.ts +2 -2
- package/dist/local.d.ts +2 -2
- package/dist/local.js +166 -35
- package/dist/sandbox-bootstrap.js +231 -9
- package/dist/sdk.d.ts +260 -29
- package/dist/sdk.js +14 -0
- package/dist/stream.d.ts +52 -4
- package/dist/stream.js +111 -13
- package/package.json +3 -3
package/dist/local.js
CHANGED
|
@@ -6,10 +6,35 @@ import { getHeapStatistics } from "node:v8";
|
|
|
6
6
|
import vm from "node:vm";
|
|
7
7
|
import { buildProgram } from "./build.js";
|
|
8
8
|
import { DELIVERY_BATCH, REPLAY_MAX_BATCH, MISSING_OPERATION_FIELDS, intervalEvents, replayEvents, } from "./local-replay.js";
|
|
9
|
-
import { LocalData, ReadFailed, spec as readSpec } from "./local-data.js";
|
|
9
|
+
import { LocalData, canonical, ReadFailed, spec as readSpec, } from "./local-data.js";
|
|
10
10
|
import { LocalStorage, StorageOpError, } from "./local-storage.js";
|
|
11
11
|
import { accountLimits, execLimits, subscriptionLimits } from "./limits.js";
|
|
12
12
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
let batchReadsSaid = false;
|
|
14
|
+
function batchReadsFromEnv() {
|
|
15
|
+
const raw = process.env.FUNRUN_BATCH_READS;
|
|
16
|
+
const v = (raw ?? "").trim();
|
|
17
|
+
const on = v === "on" || v === "1" || v === "true";
|
|
18
|
+
if (raw !== undefined && !batchReadsSaid) {
|
|
19
|
+
batchReadsSaid = true;
|
|
20
|
+
process.stderr.write(`zyrk local runner: FUNRUN_BATCH_READS=${JSON.stringify(raw)}: batched history reads ${on ? "on" : "off"} (hosted functions batch only when the service has the same setting; off there by default)\n`);
|
|
21
|
+
}
|
|
22
|
+
return on;
|
|
23
|
+
}
|
|
24
|
+
const COMPUTE_OUTPUT_BYTES = 64 * 2 ** 20;
|
|
25
|
+
const computeLine = (r, row) => {
|
|
26
|
+
const parts = [...r.keys, ...r.measures].map((n) => {
|
|
27
|
+
const c = r.columns[n];
|
|
28
|
+
const v = c.values[row];
|
|
29
|
+
const value = c.type === "f64"
|
|
30
|
+
? typeof v === "number" && Number.isFinite(v)
|
|
31
|
+
? v
|
|
32
|
+
: null
|
|
33
|
+
: v;
|
|
34
|
+
return `${JSON.stringify(n)}:${JSON.stringify(value)}`;
|
|
35
|
+
});
|
|
36
|
+
return `{${parts.join(",")}}\n`;
|
|
37
|
+
};
|
|
13
38
|
let bootstrapText;
|
|
14
39
|
export function sandboxBootstrap() {
|
|
15
40
|
return (bootstrapText ??= readFileSync(join(here, "sandbox-bootstrap.js"), "utf8"));
|
|
@@ -60,6 +85,13 @@ const WEBHOOK_PAYLOAD_BYTES = 16 * 1024;
|
|
|
60
85
|
const WEBHOOK_OVERHEAD_BYTES = 256;
|
|
61
86
|
const WEBHOOK_QUEUE_LIMIT = 1000;
|
|
62
87
|
const MAX_MESSAGE_BYTES = 2048;
|
|
88
|
+
const openWhenExported = (v) => {
|
|
89
|
+
const c = v?.coverage;
|
|
90
|
+
return (typeof c === "object" &&
|
|
91
|
+
c !== null &&
|
|
92
|
+
(c.unsealed === true ||
|
|
93
|
+
(c.liveUntilMs !== null && c.liveUntilMs !== undefined)));
|
|
94
|
+
};
|
|
63
95
|
const jsonBytes = (v) => Buffer.byteLength(JSON.stringify(v) ?? "null");
|
|
64
96
|
const truncate = (m) => Buffer.byteLength(m) <= MAX_MESSAGE_BYTES
|
|
65
97
|
? m
|
|
@@ -212,6 +244,7 @@ export class LocalRunner {
|
|
|
212
244
|
pending: 0,
|
|
213
245
|
io: { bytes: 0 },
|
|
214
246
|
startedAt: 0,
|
|
247
|
+
readWaitMs: 0,
|
|
215
248
|
done,
|
|
216
249
|
finish,
|
|
217
250
|
};
|
|
@@ -352,7 +385,7 @@ export class LocalRunner {
|
|
|
352
385
|
if (e.usage.outputBytes + size > this.limits.exec.outputBytes * e.scale)
|
|
353
386
|
throw limit("output_limit", `Result, logs and emits went over ${this.limits.exec.outputBytes * e.scale} bytes`);
|
|
354
387
|
e.usage.outputBytes += size;
|
|
355
|
-
if (
|
|
388
|
+
if (this.#wallUsed(e) > this.limits.exec.wallMs)
|
|
356
389
|
throw limit("wall_limit", `The execution ran over ${this.limits.exec.wallMs} ms (measured locally, not preempted)`);
|
|
357
390
|
if (e.cancelled)
|
|
358
391
|
throw new HostError("permission", "cancelled", "Its parent failed: the job was cancelled");
|
|
@@ -428,6 +461,38 @@ export class LocalRunner {
|
|
|
428
461
|
};
|
|
429
462
|
const fromRead = (f) => new HostError(kindOfSdkCode(f.code), f.code, f.message, f.retryAfterMs);
|
|
430
463
|
const guestJson = (v) => realm.JSON.parse(JSON.stringify(v) ?? "null");
|
|
464
|
+
const computeOutputs = new Map();
|
|
465
|
+
let computeNext = 0;
|
|
466
|
+
const computeSummary = (r) => ({
|
|
467
|
+
length: r.length,
|
|
468
|
+
keys: r.keys,
|
|
469
|
+
measures: r.measures,
|
|
470
|
+
coverage: r.coverage,
|
|
471
|
+
complete: r.complete,
|
|
472
|
+
skipped: r.skipped,
|
|
473
|
+
stats: r.stats,
|
|
474
|
+
});
|
|
475
|
+
const computeObject = (r) => {
|
|
476
|
+
const o = guestJson(computeSummary(r));
|
|
477
|
+
const columns = new realm.Object();
|
|
478
|
+
for (const n of [...r.keys, ...r.measures]) {
|
|
479
|
+
const c = r.columns[n];
|
|
480
|
+
columns[n] =
|
|
481
|
+
c.type === "f64"
|
|
482
|
+
? realm.Float64Array.from(c.values.map((v) => v === "NaN"
|
|
483
|
+
? NaN
|
|
484
|
+
: v === "Infinity"
|
|
485
|
+
? Infinity
|
|
486
|
+
: v === "-Infinity"
|
|
487
|
+
? -Infinity
|
|
488
|
+
: Number(v)))
|
|
489
|
+
: c.type === "int"
|
|
490
|
+
? realm.Array.from(c.values.map((v) => (v === null ? null : BigInt(String(v)))))
|
|
491
|
+
: realm.Array.from(c.values);
|
|
492
|
+
}
|
|
493
|
+
o.columns = columns;
|
|
494
|
+
return o;
|
|
495
|
+
};
|
|
431
496
|
const ms = (v) => typeof v === "number" && Number.isFinite(v) && Math.abs(v) < 9e15
|
|
432
497
|
? Math.trunc(v)
|
|
433
498
|
: undefined;
|
|
@@ -454,13 +519,43 @@ export class LocalRunner {
|
|
|
454
519
|
wf.readBytes > this.limits.account.workflowReadBytes)
|
|
455
520
|
throw readLimit();
|
|
456
521
|
};
|
|
522
|
+
const waited = (t0) => {
|
|
523
|
+
e.readWaitMs += Math.min(performance.now() - t0, this.limits.exec.readCallWaitMs);
|
|
524
|
+
};
|
|
457
525
|
const data = () => {
|
|
458
526
|
if (!this.data)
|
|
459
527
|
throw sdkError(program("local_data_missing", "No data directory: export history with zyrk export, then pass --data (or { data })."));
|
|
460
528
|
return this.data;
|
|
461
529
|
};
|
|
530
|
+
const saved = (answer, failure, missing) => {
|
|
531
|
+
let value;
|
|
532
|
+
let failed;
|
|
533
|
+
const t0 = performance.now();
|
|
534
|
+
try {
|
|
535
|
+
value = answer();
|
|
536
|
+
failed = value === undefined ? failure() : undefined;
|
|
537
|
+
}
|
|
538
|
+
catch (error) {
|
|
539
|
+
if (error instanceof ReadFailed)
|
|
540
|
+
throw sdkError(fromRead(error.failure));
|
|
541
|
+
throw error;
|
|
542
|
+
}
|
|
543
|
+
finally {
|
|
544
|
+
waited(t0);
|
|
545
|
+
}
|
|
546
|
+
if (failed)
|
|
547
|
+
throw sdkError(fromRead(failed));
|
|
548
|
+
if (value === undefined)
|
|
549
|
+
throw sdkError(program("local_data_missing", missing));
|
|
550
|
+
if (openWhenExported(value))
|
|
551
|
+
this.#warn("Totals or volume search pages were exported while an hour was still open: the service may now answer otherwise. Run zyrk export again (--totals, --volume-search) to download them again.");
|
|
552
|
+
charge(jsonBytes(value));
|
|
553
|
+
return guestJson(value);
|
|
554
|
+
};
|
|
462
555
|
return {
|
|
463
556
|
maxDepth: this.limits.account.maxDepth,
|
|
557
|
+
batchReads: batchReadsFromEnv(),
|
|
558
|
+
hint: () => undefined,
|
|
464
559
|
fail: (code, message) => sdkError(new HostError(kindOfSdkCode(String(code)), String(code), String(message))),
|
|
465
560
|
op: (text, awaited) => this.#op(e, text, !!awaited, realm, mode, sdkError, guestJson, sample),
|
|
466
561
|
read: (kind, key, since, until, side, allowPartial, withSignatures, after, withMarket, withEvents, withOrder, withTx) => {
|
|
@@ -500,12 +595,13 @@ export class LocalRunner {
|
|
|
500
595
|
}
|
|
501
596
|
finally {
|
|
502
597
|
e.usage.readMs += performance.now() - t0;
|
|
598
|
+
waited(t0);
|
|
503
599
|
sample();
|
|
504
600
|
}
|
|
505
601
|
if (hour === null)
|
|
506
602
|
return null;
|
|
507
603
|
if (hour.stale)
|
|
508
|
-
this.#warn("Some hours were open or not sealed when they were exported: the service may now answer otherwise. Run zyrk export again to download them again.");
|
|
604
|
+
this.#warn("Some hours were open or not sealed when they were exported, or were exported by an older version: the service may now answer otherwise. Run zyrk export again to download them again.");
|
|
509
605
|
if (hour.recut)
|
|
510
606
|
this.#warn("A read used a narrower window than its export: its edge hours were cut locally. Values are the service's; the address indexes and market.flags (chainBreak, chainUnchecked) of a cut hour may differ. Export the exact window for byte-identical batches.");
|
|
511
607
|
charge(hour.bytes);
|
|
@@ -517,47 +613,77 @@ export class LocalRunner {
|
|
|
517
613
|
if (typeof mint !== "string" ||
|
|
518
614
|
(quote !== null && typeof quote !== "string"))
|
|
519
615
|
throw sdkError(program("invalid_argument", "Expected a mint address"));
|
|
520
|
-
|
|
521
|
-
if (value === undefined)
|
|
522
|
-
throw sdkError(program("local_data_missing", `No exported totals for ${mint}: zyrk export --totals ${mint}${quote ? ` --quote ${quote}` : ""}`));
|
|
523
|
-
charge(jsonBytes(value));
|
|
524
|
-
return guestJson(value);
|
|
616
|
+
return saved(() => data().totals(mint, quote), () => data().totalsFailure(mint, quote), `No exported totals for ${mint}: zyrk export --totals ${mint}${quote ? ` --quote ${quote}` : ""}`);
|
|
525
617
|
},
|
|
526
618
|
info: (mint) => {
|
|
527
619
|
readAllowed();
|
|
528
620
|
e.usage.readCalls++;
|
|
529
621
|
if (typeof mint !== "string")
|
|
530
622
|
throw sdkError(program("invalid_argument", "Expected a mint address"));
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
623
|
+
return saved(() => data().info(mint), () => data().infoFailure(mint), `No exported info for ${mint} (exports by older versions saved only its totals): zyrk export --totals ${mint}`);
|
|
624
|
+
},
|
|
625
|
+
computeAsk: () => realm.Promise.resolve(),
|
|
626
|
+
compute: (text, toStorage) => {
|
|
627
|
+
readAllowed();
|
|
628
|
+
e.usage.readCalls++;
|
|
629
|
+
let plan;
|
|
630
|
+
try {
|
|
631
|
+
plan = JSON.parse(String(text));
|
|
632
|
+
}
|
|
633
|
+
catch {
|
|
634
|
+
throw sdkError(program("invalid_plan", "The plan is not JSON"));
|
|
635
|
+
}
|
|
636
|
+
const answer = saved(() => data().computeResult(plan), () => data().computeFailure(plan), `No exported result for this plan: zyrk export --compute '${canonical(plan)}'`);
|
|
637
|
+
if (toStorage === true) {
|
|
638
|
+
if (computeOutputs.size > 0)
|
|
639
|
+
throw sdkError(program("invalid_argument", "One ctx.compute output at a time per execution: await the one being written first"));
|
|
640
|
+
if (Array.isArray(answer))
|
|
641
|
+
throw sdkError(program("invalid_plan", "output writes the result of one plan"));
|
|
642
|
+
const result = answer;
|
|
643
|
+
let bytes = 0;
|
|
644
|
+
for (let row = 0; row < result.length; row++)
|
|
645
|
+
bytes += Buffer.byteLength(computeLine(result, row), "utf8");
|
|
646
|
+
if (bytes > COMPUTE_OUTPUT_BYTES)
|
|
647
|
+
throw sdkError(limit("compute_result_limit", `the result is ${Math.ceil(bytes / 2 ** 20)} MiB of JSON lines (${result.length} rows), more than the ${COMPUTE_OUTPUT_BYTES / 2 ** 20} MiB of one storage object: add a limit, a top or a having, group less finely, or split the range into several calls`));
|
|
648
|
+
const handle = computeNext++;
|
|
649
|
+
computeOutputs.set(handle, {
|
|
650
|
+
result: answer,
|
|
651
|
+
row: 0,
|
|
652
|
+
});
|
|
653
|
+
return guestJson({
|
|
654
|
+
handle,
|
|
655
|
+
summary: computeSummary(answer),
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
return Array.isArray(answer)
|
|
659
|
+
? realm.Array.from(answer.map((a) => computeObject(a)))
|
|
660
|
+
: computeObject(answer);
|
|
661
|
+
},
|
|
662
|
+
computeChunk: (handle) => {
|
|
663
|
+
const o = computeOutputs.get(Number(handle));
|
|
664
|
+
if (!o)
|
|
665
|
+
throw sdkError(program("invalid_argument", "Unknown compute output"));
|
|
666
|
+
const r = o.result;
|
|
667
|
+
if (o.row >= r.length)
|
|
668
|
+
return null;
|
|
669
|
+
let text = "";
|
|
670
|
+
while (o.row < r.length && text.length < 4 * 1024 * 1024 - 64 * 1024) {
|
|
671
|
+
text += computeLine(r, o.row);
|
|
672
|
+
o.row++;
|
|
673
|
+
}
|
|
674
|
+
const bytes = Buffer.from(text, "utf8");
|
|
675
|
+
const out = new realm.Uint8Array(bytes.length);
|
|
676
|
+
out.set(bytes);
|
|
677
|
+
return out;
|
|
678
|
+
},
|
|
679
|
+
computeDrop: (handle) => {
|
|
680
|
+
computeOutputs.delete(Number(handle));
|
|
551
681
|
},
|
|
552
682
|
volume: (text) => {
|
|
553
683
|
readAllowed();
|
|
554
684
|
e.usage.readCalls++;
|
|
555
685
|
const params = JSON.parse(String(text));
|
|
556
|
-
|
|
557
|
-
if (page === undefined)
|
|
558
|
-
throw sdkError(program("local_data_missing", `No exported volume page for these parameters: zyrk export --volume-search '${JSON.stringify(Object.fromEntries(params.filter(([k]) => k !== "cursor")))}'`));
|
|
559
|
-
charge(jsonBytes(page));
|
|
560
|
-
return guestJson(page);
|
|
686
|
+
return saved(() => data().volumePage(params), () => data().volumeFailure(params), `No exported volume page for these parameters: zyrk export --volume-search '${JSON.stringify(Object.fromEntries(params.filter(([k]) => k !== "cursor")))}'`);
|
|
561
687
|
},
|
|
562
688
|
encode: (text) => {
|
|
563
689
|
const bytes = Buffer.from(String(text), "utf8");
|
|
@@ -588,8 +714,13 @@ export class LocalRunner {
|
|
|
588
714
|
},
|
|
589
715
|
};
|
|
590
716
|
}
|
|
717
|
+
#wallUsed(e) {
|
|
718
|
+
return (performance.now() -
|
|
719
|
+
e.startedAt -
|
|
720
|
+
Math.min(e.readWaitMs, this.limits.exec.readWaitMs));
|
|
721
|
+
}
|
|
591
722
|
#checkWall(e) {
|
|
592
|
-
if (
|
|
723
|
+
if (this.#wallUsed(e) > this.limits.exec.wallMs) {
|
|
593
724
|
e.fatal ??= limit("wall_limit", `The execution ran over ${this.limits.exec.wallMs} ms`);
|
|
594
725
|
throw e.fatal;
|
|
595
726
|
}
|
|
@@ -781,7 +912,7 @@ export class LocalRunner {
|
|
|
781
912
|
});
|
|
782
913
|
if (this.#waiting >= account.parallelExecutions - 1)
|
|
783
914
|
throw limit("job_wait_limit", `At most ${account.parallelExecutions - 1} executions wait at once`);
|
|
784
|
-
const left = this.limits.exec.wallMs - (
|
|
915
|
+
const left = this.limits.exec.wallMs - this.#wallUsed(e);
|
|
785
916
|
const timeout = Math.max(0, Math.min(typeof a.timeout_ms === "number" ? a.timeout_ms : left, left));
|
|
786
917
|
this.#waiting++;
|
|
787
918
|
let timer;
|
|
@@ -299,25 +299,25 @@
|
|
|
299
299
|
const summary = (r) => freeze({ key: r.key, size: r.size, contentType: r.contentType, lastModified: r.lastModified, expiresAt: r.expiresAt });
|
|
300
300
|
const storageArea = (area) => {
|
|
301
301
|
const read = async (key, offset, length) => {
|
|
302
|
-
const
|
|
303
|
-
if (
|
|
304
|
-
const end = Math.min(
|
|
305
|
-
const total = Math.max(0, end -
|
|
302
|
+
const first2 = await call({ StorageGet: { area, key, offset, length: length > CHUNK ? CHUNK : length } });
|
|
303
|
+
if (first2 === null) return null;
|
|
304
|
+
const end = Math.min(first2.size, offset + length);
|
|
305
|
+
const total = Math.max(0, end - first2.offset);
|
|
306
306
|
const out = new U8(total);
|
|
307
307
|
let at = 0;
|
|
308
|
-
let r =
|
|
308
|
+
let r = first2;
|
|
309
309
|
for (; ; ) {
|
|
310
310
|
const n = N.b64decodeInto(r.data, out, at);
|
|
311
311
|
if (n < 0) throw fail("unavailable", "Corrupt storage chunk");
|
|
312
312
|
at += n;
|
|
313
313
|
if (at >= total || n === 0) break;
|
|
314
314
|
r = await call({
|
|
315
|
-
StorageGet: { area, key, offset:
|
|
315
|
+
StorageGet: { area, key, offset: first2.offset + at, length: Math.min(CHUNK, total - at) }
|
|
316
316
|
});
|
|
317
|
-
if (r === null || r.etag !==
|
|
317
|
+
if (r === null || r.etag !== first2.etag)
|
|
318
318
|
throw fail("value_changed", "The object changed while it was read: read it again");
|
|
319
319
|
}
|
|
320
|
-
return { first, body: at === total ? out : out.subarray(0, at) };
|
|
320
|
+
return { first: first2, body: at === total ? out : out.subarray(0, at) };
|
|
321
321
|
};
|
|
322
322
|
const get = async (key, range) => {
|
|
323
323
|
keyOf(key);
|
|
@@ -427,6 +427,65 @@
|
|
|
427
427
|
long: storageArea("long"),
|
|
428
428
|
durable: storageArea("durable")
|
|
429
429
|
});
|
|
430
|
+
const computeAreas = ["short", "long", "durable"];
|
|
431
|
+
let outputTurn = PromiseCtor.resolve();
|
|
432
|
+
const compute = async (plan, options) => {
|
|
433
|
+
if (plan === null || typeof plan !== "object")
|
|
434
|
+
throw fail("invalid_plan", "Expected a plan object (or an array of plans)");
|
|
435
|
+
let output;
|
|
436
|
+
if (options !== void 0 && options !== null) {
|
|
437
|
+
if (typeOf(options) !== "object") throw fail("invalid_argument", "Expected options { output }");
|
|
438
|
+
for (const k of keys(options))
|
|
439
|
+
if (k !== "output") throw fail("invalid_argument", "Unknown compute option: " + k);
|
|
440
|
+
output = options.output;
|
|
441
|
+
if (output !== void 0 && (typeOf(output) !== "object" || !computeAreas.includes(output.area) || typeof output.key !== "string"))
|
|
442
|
+
throw fail("invalid_argument", 'output is { area: "short" | "long" | "durable", key }');
|
|
443
|
+
}
|
|
444
|
+
let text;
|
|
445
|
+
try {
|
|
446
|
+
text = stringify(plan);
|
|
447
|
+
} catch {
|
|
448
|
+
throw fail("invalid_plan", "The plan is not JSON (write integers beyond 2^53 as decimal strings)");
|
|
449
|
+
}
|
|
450
|
+
await N.computeAsk(text);
|
|
451
|
+
if (output === void 0) return N.compute(text, false);
|
|
452
|
+
const before = outputTurn;
|
|
453
|
+
let release;
|
|
454
|
+
outputTurn = new PromiseCtor((resolve) => {
|
|
455
|
+
release = resolve;
|
|
456
|
+
});
|
|
457
|
+
try {
|
|
458
|
+
await before;
|
|
459
|
+
return await computeOutput(text, output);
|
|
460
|
+
} finally {
|
|
461
|
+
release();
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
const computeOutput = async (text, output) => {
|
|
465
|
+
const r = N.compute(text, true);
|
|
466
|
+
let w;
|
|
467
|
+
try {
|
|
468
|
+
w = await storage[output.area].writer(output.key, { contentType: "application/x-ndjson" });
|
|
469
|
+
let bytes = 0;
|
|
470
|
+
for (; ; ) {
|
|
471
|
+
const chunk = N.computeChunk(r.handle);
|
|
472
|
+
if (chunk === null) break;
|
|
473
|
+
bytes += chunk.length;
|
|
474
|
+
await w.write(chunk);
|
|
475
|
+
}
|
|
476
|
+
const head2 = await w.close();
|
|
477
|
+
return freeze({
|
|
478
|
+
...r.summary,
|
|
479
|
+
output: freeze({ area: output.area, key: output.key, rows: r.summary.length, bytes, etag: head2.etag })
|
|
480
|
+
});
|
|
481
|
+
} catch (e) {
|
|
482
|
+
if (w) await w.abort().catch(() => {
|
|
483
|
+
});
|
|
484
|
+
throw e;
|
|
485
|
+
} finally {
|
|
486
|
+
N.computeDrop(r.handle);
|
|
487
|
+
}
|
|
488
|
+
};
|
|
430
489
|
const start = (functionName, argsText, executionText) => {
|
|
431
490
|
const p = program();
|
|
432
491
|
let depth = 0;
|
|
@@ -449,7 +508,7 @@
|
|
|
449
508
|
version: execution.version,
|
|
450
509
|
attempt: execution.attempt
|
|
451
510
|
}),
|
|
452
|
-
data: freeze({
|
|
511
|
+
data: batching ? batchedData() : freeze({
|
|
453
512
|
walletTrades: trades("wallet"),
|
|
454
513
|
tokenTrades: trades("token"),
|
|
455
514
|
tokenTotals: async (mint, o) => {
|
|
@@ -487,6 +546,7 @@
|
|
|
487
546
|
continue: (args) => call({ Continue: { args } })
|
|
488
547
|
}),
|
|
489
548
|
storage,
|
|
549
|
+
compute,
|
|
490
550
|
subscriptions: freeze({
|
|
491
551
|
set: (name, subscription) => call({ Subscribe: { name, subscription } }),
|
|
492
552
|
remove: (name) => call({ Unsubscribe: { name } }),
|
|
@@ -515,5 +575,167 @@
|
|
|
515
575
|
return text === void 0 ? "null" : text;
|
|
516
576
|
})();
|
|
517
577
|
};
|
|
578
|
+
const batching = N.batchReads === true;
|
|
579
|
+
const BATCH = 64;
|
|
580
|
+
const MAX_REENTRY = 1e3;
|
|
581
|
+
const reflectApply = Reflect.apply;
|
|
582
|
+
const promiseThen = PromiseCtor.prototype.then;
|
|
583
|
+
const ready = PromiseCtor.resolve();
|
|
584
|
+
Object.defineProperty(ready, "constructor", { value: void 0 });
|
|
585
|
+
let open = false;
|
|
586
|
+
let direct = false;
|
|
587
|
+
let reentry = 0;
|
|
588
|
+
let first = null;
|
|
589
|
+
let last = null;
|
|
590
|
+
let epoch = 0;
|
|
591
|
+
const wait = (run, hint) => new PromiseCtor((resolve, reject) => {
|
|
592
|
+
const q = { run, hint, next: null, resolve, reject };
|
|
593
|
+
if (last === null) first = q;
|
|
594
|
+
else last.next = q;
|
|
595
|
+
last = q;
|
|
596
|
+
});
|
|
597
|
+
const checkpoint = () => {
|
|
598
|
+
let q = first;
|
|
599
|
+
first = null;
|
|
600
|
+
last = null;
|
|
601
|
+
direct = reentry >= MAX_REENTRY;
|
|
602
|
+
while (q !== null) {
|
|
603
|
+
epoch++;
|
|
604
|
+
let n = 0;
|
|
605
|
+
let hinted = 0;
|
|
606
|
+
for (let r = q; r !== null && n < BATCH; r = r.next) {
|
|
607
|
+
n++;
|
|
608
|
+
if (r.hint !== null && r.hint(epoch)) hinted++;
|
|
609
|
+
}
|
|
610
|
+
if (hinted > 0) N.hint();
|
|
611
|
+
for (; n > 0; n--) {
|
|
612
|
+
const c = q;
|
|
613
|
+
q = c.next;
|
|
614
|
+
let value;
|
|
615
|
+
try {
|
|
616
|
+
value = c.run();
|
|
617
|
+
} catch (e) {
|
|
618
|
+
c.reject(e);
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
c.resolve(value);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
direct = false;
|
|
625
|
+
if (first === null) {
|
|
626
|
+
reentry = 0;
|
|
627
|
+
open = false;
|
|
628
|
+
} else {
|
|
629
|
+
reentry++;
|
|
630
|
+
reflectApply(promiseThen, ready, [checkpoint]);
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
const openBatch = () => {
|
|
634
|
+
open = true;
|
|
635
|
+
reflectApply(promiseThen, ready, [checkpoint]);
|
|
636
|
+
};
|
|
637
|
+
const waiting = () => open && !direct;
|
|
638
|
+
const tradesBatched = (kind) => (key, w) => {
|
|
639
|
+
if (typeof key !== "string") throw fail("invalid_argument", "Expected an address string");
|
|
640
|
+
const { since, until, side, allowPartial, withSignatures, withMarket, withEvents, withOrder, withTx } = checkWindow(w);
|
|
641
|
+
return freeze({
|
|
642
|
+
[asyncIterator]() {
|
|
643
|
+
let after = null;
|
|
644
|
+
let done = false;
|
|
645
|
+
let closed = false;
|
|
646
|
+
let mark = 0;
|
|
647
|
+
const end = () => ({ done: true, value: void 0 });
|
|
648
|
+
const read = () => {
|
|
649
|
+
if (done) return end();
|
|
650
|
+
const batch = N.read(kind, key, since, until, side, allowPartial, withSignatures, after, withMarket, withEvents, withOrder, withTx);
|
|
651
|
+
if (batch === null) {
|
|
652
|
+
done = true;
|
|
653
|
+
return end();
|
|
654
|
+
}
|
|
655
|
+
after = batch.hour;
|
|
656
|
+
return { done: false, value: batch };
|
|
657
|
+
};
|
|
658
|
+
const hint = (e) => {
|
|
659
|
+
if (done || mark === e) return false;
|
|
660
|
+
mark = e;
|
|
661
|
+
N.hint(kind, key, since, until, side, allowPartial, withSignatures, after, withMarket, withEvents, withOrder, withTx);
|
|
662
|
+
return true;
|
|
663
|
+
};
|
|
664
|
+
const nextNow = async () => {
|
|
665
|
+
if (done || closed) return end();
|
|
666
|
+
if (!direct) openBatch();
|
|
667
|
+
return read();
|
|
668
|
+
};
|
|
669
|
+
const returnNow = async (value) => {
|
|
670
|
+
closed = true;
|
|
671
|
+
return { done: true, value };
|
|
672
|
+
};
|
|
673
|
+
return {
|
|
674
|
+
next() {
|
|
675
|
+
if (!waiting()) return nextNow();
|
|
676
|
+
if (closed) return wait(end, null);
|
|
677
|
+
return wait(read, hint);
|
|
678
|
+
},
|
|
679
|
+
return(value) {
|
|
680
|
+
if (!waiting()) return returnNow(value);
|
|
681
|
+
closed = true;
|
|
682
|
+
return wait(() => ({ done: true, value }), null);
|
|
683
|
+
},
|
|
684
|
+
[asyncIterator]() {
|
|
685
|
+
return this;
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
};
|
|
691
|
+
const totalsNow = async (mint, o) => {
|
|
692
|
+
if (typeof mint !== "string") throw fail("invalid_argument", "Expected a mint address");
|
|
693
|
+
const rawQuote = o ? o.quote : void 0;
|
|
694
|
+
const quote = rawQuote !== void 0 ? StringCtor(rawQuote) : null;
|
|
695
|
+
return N.totals(mint, quote);
|
|
696
|
+
};
|
|
697
|
+
const infoNow = async (mint) => {
|
|
698
|
+
if (typeof mint !== "string") throw fail("invalid_argument", "Expected a mint address");
|
|
699
|
+
return N.info(mint);
|
|
700
|
+
};
|
|
701
|
+
const batchedData = () => freeze({
|
|
702
|
+
walletTrades: tradesBatched("wallet"),
|
|
703
|
+
tokenTrades: tradesBatched("token"),
|
|
704
|
+
tokenTotals: (mint, o) => {
|
|
705
|
+
if (!waiting()) return totalsNow(mint, o);
|
|
706
|
+
let quote;
|
|
707
|
+
try {
|
|
708
|
+
if (typeof mint !== "string") throw fail("invalid_argument", "Expected a mint address");
|
|
709
|
+
const rawQuote = o ? o.quote : void 0;
|
|
710
|
+
quote = rawQuote !== void 0 ? StringCtor(rawQuote) : null;
|
|
711
|
+
} catch (e) {
|
|
712
|
+
return PromiseCtor.reject(e);
|
|
713
|
+
}
|
|
714
|
+
return wait(() => N.totals(mint, quote), null);
|
|
715
|
+
},
|
|
716
|
+
tokenInfo: (mint) => {
|
|
717
|
+
if (!waiting()) return infoNow(mint);
|
|
718
|
+
if (typeof mint !== "string") return PromiseCtor.reject(fail("invalid_argument", "Expected a mint address"));
|
|
719
|
+
return wait(() => N.info(mint), null);
|
|
720
|
+
},
|
|
721
|
+
volumeSearch: async function* (params) {
|
|
722
|
+
let p = { ...params };
|
|
723
|
+
for (; ; ) {
|
|
724
|
+
let page;
|
|
725
|
+
if (waiting()) {
|
|
726
|
+
const text = flatParams(p);
|
|
727
|
+
page = await wait(() => N.volume(text), null);
|
|
728
|
+
} else page = N.volume(flatParams(p));
|
|
729
|
+
yield page;
|
|
730
|
+
const next = page === null || typeof page !== "object" ? null : page.nextCursor;
|
|
731
|
+
if (next === null || next === void 0) return;
|
|
732
|
+
if (typeof next !== "string" || next === p.cursor)
|
|
733
|
+
throw fail("history_unavailable", "Invalid next cursor");
|
|
734
|
+
const w = page.window;
|
|
735
|
+
if (w && typeof w === "object" && "since" in w) p = { ...p, since: w.since, until: w.until };
|
|
736
|
+
p = { ...p, cursor: next };
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
});
|
|
518
740
|
return freeze({ start, describe });
|
|
519
741
|
});
|