@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/export.d.ts
CHANGED
|
@@ -5,16 +5,19 @@ export interface ExportConnection {
|
|
|
5
5
|
readonly url: string;
|
|
6
6
|
/** API key (`TH_API_KEY`) of an account with hosted functions. */
|
|
7
7
|
readonly apiKey: string;
|
|
8
|
-
/** At most this many requests per minute (default: as fast as the server allows). */
|
|
8
|
+
/** At most this many requests per minute (default: as fast as the server allows, 30 a second per account). Optional pacing only. */
|
|
9
9
|
readonly rpm?: number;
|
|
10
|
-
/** Network
|
|
10
|
+
/** Network failures, 502, 503 and 504 tolerated in a row before giving up (default 8); another 5xx is retried once. */
|
|
11
11
|
readonly maxRetries?: number;
|
|
12
|
-
/** Longest wait accepted on a
|
|
12
|
+
/** Longest wait accepted on 429s in a row before stopping (default 5 minutes): a longer one (an older server's daily quota) stops the export. */
|
|
13
13
|
readonly maxWaitMs?: number;
|
|
14
14
|
readonly fetch?: typeof fetch;
|
|
15
15
|
readonly signal?: AbortSignal;
|
|
16
16
|
readonly onProgress?: (progress: ExportProgress) => void;
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Hours that may still change (open, not sealed), files downloaded again,
|
|
19
|
+
* errors saved as the answer of a call.
|
|
20
|
+
*/
|
|
18
21
|
readonly onWarning?: (message: string) => void;
|
|
19
22
|
}
|
|
20
23
|
export interface ExportProgress {
|
|
@@ -52,7 +55,7 @@ export interface ExportSummary {
|
|
|
52
55
|
}
|
|
53
56
|
/** `[since, until)` in parts of at most 30 days, ending on UTC day boundaries. */
|
|
54
57
|
export declare function parts(sinceMs: number, untilMs: number): [number, number][];
|
|
55
|
-
/** Meta of an export answer
|
|
58
|
+
/** Meta of an export answer. */
|
|
56
59
|
interface HourMeta {
|
|
57
60
|
readonly format?: string;
|
|
58
61
|
readonly coverage?: ReadMeta["coverage"];
|
|
@@ -76,12 +79,28 @@ export declare function parseFrame(bytes: Uint8Array): {
|
|
|
76
79
|
export declare function staleFrom(meta: ReadMeta, dir: string): number | null;
|
|
77
80
|
/** Exports one read (cut into parts of at most 30 days). Resumes an interrupted export. */
|
|
78
81
|
export declare function exportHistory(connection: ExportConnection, out: string, read: ExportRead): Promise<ExportSummary>;
|
|
79
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* Saves the totals of a mint for `ctx.data.tokenTotals`, and its info for
|
|
84
|
+
* `ctx.data.tokenInfo`, as hosted programs receive them. Returns the totals,
|
|
85
|
+
* or the error answer saved for them (`{ error: { kind, code, message } }`).
|
|
86
|
+
*/
|
|
80
87
|
export declare function exportTotals(connection: ExportConnection, out: string, mint: string, quote?: string | null): Promise<Json>;
|
|
81
88
|
/**
|
|
82
|
-
* Saves
|
|
89
|
+
* Saves the answer of `ctx.compute(plan)` (a plan or an array of plans), as
|
|
90
|
+
* the service computes it for a function (`POST /v1/functions/compute`, the
|
|
91
|
+
* same engine), so that the same call in a local run finds it. Relative
|
|
92
|
+
* windows (`last`, an `until` left out) are resolved now, at export. An
|
|
93
|
+
* error answer (an invalid plan, a limit) is saved as that error.
|
|
94
|
+
*/
|
|
95
|
+
export declare function exportCompute(connection: ExportConnection, out: string, plan: Json): Promise<{
|
|
96
|
+
rows: number | null;
|
|
97
|
+
}>;
|
|
98
|
+
/**
|
|
99
|
+
* Saves every page of a volume search for `params`, following the
|
|
83
100
|
* cursor exactly as `ctx.data.volumeSearch` does, so that the same call in
|
|
84
|
-
* a local run finds each page.
|
|
101
|
+
* a local run finds each page. A page answered by an error (an invalid
|
|
102
|
+
* parameter...) is saved as that error, and ends the search, as it does in a
|
|
103
|
+
* hosted call.
|
|
85
104
|
*/
|
|
86
105
|
export declare function exportVolumeSearch(connection: ExportConnection, out: string, params: VolumeSearchParams): Promise<number>;
|
|
87
106
|
export {};
|
package/dist/export.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
3
|
import { decompressHour } from "./hourfile.js";
|
|
4
|
-
import { HOUR_MS, READ_FORMAT, sha256, readId, spec as readSpec, totalsName, volumeName, } from "./local-data.js";
|
|
4
|
+
import { HOUR_MS, READ_FORMAT, infoName, sha256, readId, spec as readSpec, totalsName, volumeName, computeName, } from "./local-data.js";
|
|
5
5
|
import { MAX_WINDOW_MS } from "./limits.js";
|
|
6
6
|
const DAY_MS = 86_400_000;
|
|
7
7
|
const PART_MS = 30 * DAY_MS;
|
|
@@ -40,6 +40,8 @@ const writeAtomic = (file, data) => {
|
|
|
40
40
|
writeFileSync(tmp, data);
|
|
41
41
|
renameSync(tmp, file);
|
|
42
42
|
};
|
|
43
|
+
const MAX_BUSY_ATTEMPTS = 12;
|
|
44
|
+
const MAX_SERVER_ERRORS = 1;
|
|
43
45
|
class Http {
|
|
44
46
|
c;
|
|
45
47
|
requests = 0;
|
|
@@ -47,12 +49,15 @@ class Http {
|
|
|
47
49
|
constructor(c) {
|
|
48
50
|
this.c = c;
|
|
49
51
|
}
|
|
50
|
-
async raw(path, query) {
|
|
52
|
+
async raw(path, query, keep, post) {
|
|
51
53
|
const f = this.c.fetch ?? fetch;
|
|
52
54
|
const url = new URL(this.c.url.replace(/\/+$/, "") + path);
|
|
53
55
|
for (const [k, v] of Object.entries(query))
|
|
54
56
|
url.searchParams.set(k, v);
|
|
55
57
|
let failures = 0;
|
|
58
|
+
let serverErrors = 0;
|
|
59
|
+
let busy = 0;
|
|
60
|
+
let waited = 0;
|
|
56
61
|
for (;;) {
|
|
57
62
|
if (this.c.rpm) {
|
|
58
63
|
const gap = 60_000 / this.c.rpm - (Date.now() - this.#last);
|
|
@@ -65,7 +70,13 @@ class Http {
|
|
|
65
70
|
let bytes;
|
|
66
71
|
try {
|
|
67
72
|
response = await f(url, {
|
|
68
|
-
headers: {
|
|
73
|
+
headers: {
|
|
74
|
+
authorization: `Bearer ${this.c.apiKey}`,
|
|
75
|
+
...(post !== undefined
|
|
76
|
+
? { "content-type": "application/json" }
|
|
77
|
+
: {}),
|
|
78
|
+
},
|
|
79
|
+
...(post !== undefined ? { method: "POST", body: post } : {}),
|
|
69
80
|
...(this.c.signal ? { signal: this.c.signal } : {}),
|
|
70
81
|
});
|
|
71
82
|
bytes = new Uint8Array(await response.arrayBuffer());
|
|
@@ -88,6 +99,8 @@ class Http {
|
|
|
88
99
|
catch {
|
|
89
100
|
body = null;
|
|
90
101
|
}
|
|
102
|
+
if (keep?.(response.status, body))
|
|
103
|
+
return { status: response.status, contentType, bytes };
|
|
91
104
|
const err = body?.error;
|
|
92
105
|
const code = typeof err === "string"
|
|
93
106
|
? err
|
|
@@ -105,12 +118,26 @@ class Http {
|
|
|
105
118
|
? header * 1000
|
|
106
119
|
: undefined;
|
|
107
120
|
if (response.status === 429) {
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
121
|
+
const maxWait = this.c.maxWaitMs ?? 300_000;
|
|
122
|
+
const asked = retryMs ?? 60_000;
|
|
123
|
+
if (asked > maxWait)
|
|
124
|
+
throw new ExportError(`${code}: ${message} Retry after ${Math.round(asked / 1000)} s: run the same command again then, the export resumes where it stopped.`, code, 429);
|
|
125
|
+
const wait = Math.max(asked, Math.min(30_000, 1000 * 2 ** busy));
|
|
126
|
+
if (++busy >= MAX_BUSY_ATTEMPTS || waited + wait > maxWait)
|
|
127
|
+
throw new ExportError(`${code}: ${message} The server answered 429 ${busy} times in a row (${Math.round(waited / 1000)} s): run the same command again later, the export resumes where it stopped.`, code, 429);
|
|
128
|
+
waited += wait;
|
|
111
129
|
await sleep(wait, this.c.signal);
|
|
112
130
|
continue;
|
|
113
131
|
}
|
|
132
|
+
if (response.status >= 500 &&
|
|
133
|
+
response.status !== 502 &&
|
|
134
|
+
response.status !== 503 &&
|
|
135
|
+
response.status !== 504) {
|
|
136
|
+
if (++serverErrors > MAX_SERVER_ERRORS)
|
|
137
|
+
throw new ExportError(`${response.status} ${code}: ${message}`, code, response.status);
|
|
138
|
+
await sleep(Math.min(30_000, 500 * 2 ** serverErrors), this.c.signal);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
114
141
|
if (response.status >= 500 && code !== "history_timeout") {
|
|
115
142
|
if (++failures > (this.c.maxRetries ?? 8))
|
|
116
143
|
throw new ExportError(`${response.status} ${code}: ${message}`, code, response.status);
|
|
@@ -120,11 +147,14 @@ class Http {
|
|
|
120
147
|
throw new ExportError(`${response.status} ${code}: ${message}`, code, response.status);
|
|
121
148
|
}
|
|
122
149
|
}
|
|
123
|
-
async get(path, query) {
|
|
124
|
-
const answer = await this.raw(path, query);
|
|
150
|
+
async get(path, query, keep, post) {
|
|
151
|
+
const answer = await this.raw(path, query, keep, post);
|
|
125
152
|
const text = new TextDecoder().decode(answer.bytes);
|
|
126
153
|
try {
|
|
127
|
-
return
|
|
154
|
+
return {
|
|
155
|
+
status: answer.status,
|
|
156
|
+
body: text ? JSON.parse(text) : null,
|
|
157
|
+
};
|
|
128
158
|
}
|
|
129
159
|
catch {
|
|
130
160
|
throw new ExportError(`${path}: the answer is not JSON`, "decode");
|
|
@@ -171,9 +201,9 @@ export function staleFrom(meta, dir) {
|
|
|
171
201
|
break;
|
|
172
202
|
let live = false;
|
|
173
203
|
try {
|
|
204
|
+
const { header } = decompressHour(readFileSync(join(dir, `${hour}.zhb`)));
|
|
174
205
|
live =
|
|
175
|
-
|
|
176
|
-
"live";
|
|
206
|
+
header.source === "live" || (header.market && !header.virtualSides);
|
|
177
207
|
}
|
|
178
208
|
catch {
|
|
179
209
|
live = true;
|
|
@@ -207,7 +237,7 @@ async function exportPart(http, out, s, sinceMs, untilMs, progress, refresh, onP
|
|
|
207
237
|
if (meta.done) {
|
|
208
238
|
const from = staleFrom(meta, dir);
|
|
209
239
|
if (from !== null) {
|
|
210
|
-
onWarning?.(`${id}: hours from ${new Date(from * HOUR_MS).toISOString()} were open or not sealed when exported; downloading them again`);
|
|
240
|
+
onWarning?.(`${id}: hours from ${new Date(from * HOUR_MS).toISOString()} were open or not sealed when exported, or exported by an older version; downloading them again`);
|
|
211
241
|
const hours = meta.hours.filter((h) => h < from);
|
|
212
242
|
meta = {
|
|
213
243
|
...meta,
|
|
@@ -307,12 +337,107 @@ export async function exportHistory(connection, out, read) {
|
|
|
307
337
|
}
|
|
308
338
|
return { reads, requests: http.requests, bytes: progress.bytes };
|
|
309
339
|
}
|
|
340
|
+
const RECORDED = new Set([
|
|
341
|
+
"invalid_window",
|
|
342
|
+
"invalid_parameter",
|
|
343
|
+
"invalid_limit",
|
|
344
|
+
"invalid_amount",
|
|
345
|
+
"invalid_cursor",
|
|
346
|
+
"usd_volume_unavailable",
|
|
347
|
+
"invalid_quote",
|
|
348
|
+
"partial_coverage",
|
|
349
|
+
"query_work_limit",
|
|
350
|
+
]);
|
|
351
|
+
const recordable = (status, body) => {
|
|
352
|
+
const e = body?.error;
|
|
353
|
+
return (status === 400 &&
|
|
354
|
+
typeof e === "object" &&
|
|
355
|
+
e !== null &&
|
|
356
|
+
typeof e.kind === "string" &&
|
|
357
|
+
typeof e.code === "string" &&
|
|
358
|
+
typeof e.message === "string" &&
|
|
359
|
+
RECORDED.has(e.code));
|
|
360
|
+
};
|
|
361
|
+
async function getNew(http, path, query, what) {
|
|
362
|
+
try {
|
|
363
|
+
const { status, body } = await http.get(path, query, recordable);
|
|
364
|
+
if (status !== 400)
|
|
365
|
+
return { ok: body };
|
|
366
|
+
const e = body.error;
|
|
367
|
+
const retry = body.retryAfterMs;
|
|
368
|
+
return {
|
|
369
|
+
code: e.code,
|
|
370
|
+
error: {
|
|
371
|
+
error: { kind: e.kind, code: e.code, message: e.message },
|
|
372
|
+
...(typeof retry === "number" ? { retryAfterMs: retry } : {}),
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
catch (error) {
|
|
377
|
+
if (error instanceof ExportError && error.status === 404)
|
|
378
|
+
throw new ExportError(`This server does not export ${what} yet (it answered 404): it must be upgraded first. Trades (--token, --wallet) export as before.`, "unsupported_server", 404);
|
|
379
|
+
throw error;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
function save(connection, out, [dir, name], answer, content, call) {
|
|
383
|
+
const ok = join(out, dir, name);
|
|
384
|
+
const failed = join(out, "errors", dir, name);
|
|
385
|
+
const [file, other] = "ok" in answer ? [ok, failed] : [failed, ok];
|
|
386
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
387
|
+
writeAtomic(file, JSON.stringify(content));
|
|
388
|
+
rmSync(other, { force: true });
|
|
389
|
+
if (!("ok" in answer))
|
|
390
|
+
connection.onWarning?.(`${call} answered ${answer.code}: saved as its answer; the same call in a local run throws this error, as a hosted call does now`);
|
|
391
|
+
}
|
|
310
392
|
export async function exportTotals(connection, out, mint, quote = null) {
|
|
311
393
|
const http = new Http(connection);
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
394
|
+
const key = encodeURIComponent(mint);
|
|
395
|
+
const body = await getNew(http, `/v1/functions/totals/${key}`, quote ? { quote } : {}, "totals (--totals)");
|
|
396
|
+
const info = await getNew(http, `/v1/functions/info/${key}`, {}, "totals (--totals)");
|
|
397
|
+
const content = (a) => ("ok" in a ? a.ok : a.error);
|
|
398
|
+
const totals = content(body);
|
|
399
|
+
save(connection, out, ["totals", totalsName(mint, quote)], body, totals, "tokenTotals");
|
|
400
|
+
save(connection, out, ["info", infoName(mint)], info, content(info), "tokenInfo");
|
|
401
|
+
return totals;
|
|
402
|
+
}
|
|
403
|
+
export async function exportCompute(connection, out, plan) {
|
|
404
|
+
const http = new Http(connection);
|
|
405
|
+
mkdirSync(join(out, "compute"), { recursive: true });
|
|
406
|
+
let answer;
|
|
407
|
+
try {
|
|
408
|
+
const { status, body } = await http.get("/v1/functions/compute", {}, recordable, JSON.stringify({ plan }));
|
|
409
|
+
if (status === 200)
|
|
410
|
+
answer = { ok: body };
|
|
411
|
+
else {
|
|
412
|
+
const e = body.error;
|
|
413
|
+
const retry = body.retryAfterMs;
|
|
414
|
+
answer = {
|
|
415
|
+
code: e.code,
|
|
416
|
+
error: {
|
|
417
|
+
error: { kind: e.kind, code: e.code, message: e.message },
|
|
418
|
+
...(typeof retry === "number" ? { retryAfterMs: retry } : {}),
|
|
419
|
+
},
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
catch (error) {
|
|
424
|
+
if (error instanceof ExportError && error.status === 404)
|
|
425
|
+
throw new ExportError("This server does not run ctx.compute for local runs yet (it answered 404): it must be upgraded first.", "unsupported_server", 404);
|
|
426
|
+
throw error;
|
|
427
|
+
}
|
|
428
|
+
save(connection, out, ["compute", `${computeName(plan)}.json`], answer, "ok" in answer
|
|
429
|
+
? { plan, result: answer.ok }
|
|
430
|
+
: { plan, ...answer.error }, "compute");
|
|
431
|
+
const ok = "ok" in answer ? answer.ok : null;
|
|
432
|
+
return {
|
|
433
|
+
rows: ok === null
|
|
434
|
+
? null
|
|
435
|
+
: Array.isArray(ok)
|
|
436
|
+
? ok.length
|
|
437
|
+
: typeof ok.length === "number"
|
|
438
|
+
? ok.length
|
|
439
|
+
: null,
|
|
440
|
+
};
|
|
316
441
|
}
|
|
317
442
|
export async function exportVolumeSearch(connection, out, params) {
|
|
318
443
|
const http = new Http(connection);
|
|
@@ -322,8 +447,13 @@ export async function exportVolumeSearch(connection, out, params) {
|
|
|
322
447
|
const flat = Object.entries(p)
|
|
323
448
|
.filter(([, v]) => v !== undefined && v !== null)
|
|
324
449
|
.map(([k, v]) => [k, String(v)]);
|
|
325
|
-
const
|
|
326
|
-
|
|
450
|
+
const answer = await getNew(http, "/v1/functions/volume/search", Object.fromEntries(flat), "volume searches (--volume-search)");
|
|
451
|
+
save(connection, out, ["volume", `${volumeName(flat)}.json`], answer, "ok" in answer
|
|
452
|
+
? { params: flat, page: answer.ok }
|
|
453
|
+
: { params: flat, ...answer.error }, `volumeSearch (page ${pages})`);
|
|
454
|
+
if (!("ok" in answer))
|
|
455
|
+
return pages;
|
|
456
|
+
const page = answer.ok;
|
|
327
457
|
const next = page?.nextCursor;
|
|
328
458
|
if (next === null || next === undefined)
|
|
329
459
|
return pages;
|
package/dist/hourfile.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type HourBatch } from "./sdk.js";
|
|
2
2
|
export declare const HOUR_MAGIC = "ZHB1";
|
|
3
3
|
export type ColumnType = "u8" | "u16" | "u32" | "i32" | "u64" | "f64";
|
|
4
4
|
export interface HourHeader {
|
|
@@ -12,6 +12,8 @@ export interface HourHeader {
|
|
|
12
12
|
/** Exports: timestamp of each operation counted in `skipped` (`null`: unknown). */
|
|
13
13
|
readonly skippedTimes?: readonly (number | null)[];
|
|
14
14
|
readonly market: boolean;
|
|
15
|
+
/** Market flags with one bit per virtual side; absent from older files. */
|
|
16
|
+
readonly virtualSides?: boolean;
|
|
15
17
|
readonly order: boolean;
|
|
16
18
|
/** Absent from files written before transaction ids. */
|
|
17
19
|
readonly tx?: boolean;
|
package/dist/hourfile.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { constants, zstdCompressSync, zstdDecompressSync } from "node:zlib";
|
|
2
|
+
import { MarketFlag } from "./sdk.js";
|
|
2
3
|
export const HOUR_MAGIC = "ZHB1";
|
|
3
4
|
export const nodeRealm = {
|
|
4
5
|
Uint8Array,
|
|
@@ -237,6 +238,12 @@ export function batchOf(raw, realm = nodeRealm) {
|
|
|
237
238
|
const m = new realm.Object();
|
|
238
239
|
for (const [name, type] of MARKET_COLUMNS)
|
|
239
240
|
m[name] = column(`market.${name}`, type);
|
|
241
|
+
const flags = m.flags;
|
|
242
|
+
if (!header.virtualSides)
|
|
243
|
+
for (let i = 0; i < flags.length; i++)
|
|
244
|
+
if ((flags[i] & MarketFlag.virtualReserves) !== 0)
|
|
245
|
+
flags[i] |=
|
|
246
|
+
MarketFlag.virtualTokenReserve | MarketFlag.virtualQuoteReserve;
|
|
240
247
|
batch.market = m;
|
|
241
248
|
}
|
|
242
249
|
if (header.order) {
|
package/dist/limits.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Limits of
|
|
2
|
+
* Limits of hosted functions, mirrored from the server defaults. The server is the
|
|
3
3
|
* authority; these constants let tools and programs plan around them.
|
|
4
4
|
*
|
|
5
5
|
* No dependency: safe to import from guest code.
|
|
@@ -11,7 +11,11 @@ export declare const execLimits: {
|
|
|
11
11
|
* reads excluded: they count in `readMs`.
|
|
12
12
|
*/
|
|
13
13
|
readonly cpuMs: 10000;
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Wall time, waits (`ctx.jobs.wait`, host operations, timers) included.
|
|
16
|
+
* Time spent waiting for history reads (`ctx.data.*`) does not count, up
|
|
17
|
+
* to `readWaitMs`.
|
|
18
|
+
*/
|
|
15
19
|
readonly wallMs: 60000;
|
|
16
20
|
readonly heapBytes: number;
|
|
17
21
|
/** JSON arguments of a direct call or a job. */
|
|
@@ -34,6 +38,16 @@ export declare const execLimits: {
|
|
|
34
38
|
readonly readMs: 30000;
|
|
35
39
|
/** User storage bytes read plus written by one execution (`io_limit`). */
|
|
36
40
|
readonly storageBytes: number;
|
|
41
|
+
/**
|
|
42
|
+
* History read wait not counted in `wallMs`, at most: an execution lasts
|
|
43
|
+
* `wallMs + readWaitMs` (15 minutes) at most.
|
|
44
|
+
*/
|
|
45
|
+
readonly readWaitMs: number;
|
|
46
|
+
/**
|
|
47
|
+
* Of one history read call, at most this much is given back to `wallMs`:
|
|
48
|
+
* a read that hangs still ends at the wall limit.
|
|
49
|
+
*/
|
|
50
|
+
readonly readCallWaitMs: 120000;
|
|
37
51
|
};
|
|
38
52
|
/** User storage (`ctx.storage`), per account. Server defaults; operators may change them. */
|
|
39
53
|
export declare const storageLimits: {
|
package/dist/limits.js
CHANGED
package/dist/local-data.d.ts
CHANGED
|
@@ -69,7 +69,7 @@ export declare class ReadFailed extends Error {
|
|
|
69
69
|
export declare const sha256: (bytes: Uint8Array) => string;
|
|
70
70
|
export declare function windowComplete(c: Coverage, since: number, until: number): boolean;
|
|
71
71
|
export declare function hourComplete(c: Coverage, hour: number, since: number, until: number): boolean;
|
|
72
|
-
/**
|
|
72
|
+
/** What a batch costs against `readBytes`, as the service counts it. */
|
|
73
73
|
export declare function batchBytes(h: HourHeader): number;
|
|
74
74
|
export interface HourRead {
|
|
75
75
|
readonly batch: HourBatch;
|
|
@@ -93,14 +93,31 @@ export declare class LocalData {
|
|
|
93
93
|
find(s: ReadSpec, since: number, until: number): ReadMeta | undefined;
|
|
94
94
|
/**
|
|
95
95
|
* The next batch of a read after `afterHour` (`null`: none left), as
|
|
96
|
-
*
|
|
96
|
+
* the service's reader answers it. Throws {@link ReadFailed}.
|
|
97
97
|
*/
|
|
98
98
|
readHour(s: ReadSpec, since: number, until: number, allowPartial: boolean, afterHour: number | null, realm?: Realm): HourRead | null;
|
|
99
|
-
/**
|
|
99
|
+
/** Totals of a mint saved by the export, or `undefined`. */
|
|
100
100
|
totals(mint: string, quote: string | null): Json | undefined;
|
|
101
|
-
/**
|
|
101
|
+
/** Info of a mint saved by the export (`--totals`), or `undefined`. */
|
|
102
|
+
info(mint: string): Json | undefined;
|
|
103
|
+
/** The error `tokenTotals` answered at export, or `undefined`. */
|
|
104
|
+
totalsFailure(mint: string, quote: string | null): ReadFailure | undefined;
|
|
105
|
+
/** The error `tokenInfo` answered at export, or `undefined`. */
|
|
106
|
+
infoFailure(mint: string): ReadFailure | undefined;
|
|
107
|
+
/** The error a volume search page answered at export, or `undefined`. */
|
|
108
|
+
volumeFailure(params: readonly (readonly [string, string])[]): ReadFailure | undefined;
|
|
109
|
+
/** The answer `ctx.compute(plan)` gave at export (`zyrk export --compute`). */
|
|
110
|
+
computeResult(plan: unknown): Json | undefined;
|
|
111
|
+
/** The error `ctx.compute(plan)` answered at export, or `undefined`. */
|
|
112
|
+
computeFailure(plan: unknown): ReadFailure | undefined;
|
|
113
|
+
/** A volume search page saved by the export for exactly these parameters. */
|
|
102
114
|
volumePage(params: readonly (readonly [string, string])[]): Json | undefined;
|
|
103
115
|
}
|
|
116
|
+
/** JSON with object keys sorted at every level (the name of a plan). */
|
|
117
|
+
export declare function canonical(v: unknown): string;
|
|
118
|
+
/** File name of a saved compute answer. */
|
|
119
|
+
export declare function computeName(plan: unknown): string;
|
|
104
120
|
export declare const totalsName: (mint: string, quote: string | null) => string;
|
|
121
|
+
export declare const infoName: (mint: string) => string;
|
|
105
122
|
/** Parameters as the sandbox passes them (`[key, string value]`), sorted. */
|
|
106
123
|
export declare function volumeName(params: readonly (readonly [string, string])[]): string;
|
package/dist/local-data.js
CHANGED
|
@@ -315,7 +315,8 @@ export class LocalData {
|
|
|
315
315
|
batch: batchOf(raw, realm),
|
|
316
316
|
bytes: batchBytes(raw.header),
|
|
317
317
|
recut: recuts,
|
|
318
|
-
stale: stale(raw.header.source, hour)
|
|
318
|
+
stale: stale(raw.header.source, hour) ||
|
|
319
|
+
(raw.header.market && !raw.header.virtualSides),
|
|
319
320
|
};
|
|
320
321
|
}
|
|
321
322
|
return null;
|
|
@@ -330,15 +331,65 @@ export class LocalData {
|
|
|
330
331
|
throw corrupt(file, e, again);
|
|
331
332
|
}
|
|
332
333
|
}
|
|
334
|
+
#failure(file, again) {
|
|
335
|
+
const saved = this.#json(join(this.dir, "errors", file), again);
|
|
336
|
+
if (saved === undefined)
|
|
337
|
+
return undefined;
|
|
338
|
+
const { code, message } = saved?.error ?? {};
|
|
339
|
+
if (typeof code !== "string" || typeof message !== "string")
|
|
340
|
+
throw corrupt(join(this.dir, "errors", file), new Error("not an error answer"), again);
|
|
341
|
+
const retry = saved.retryAfterMs;
|
|
342
|
+
return {
|
|
343
|
+
code,
|
|
344
|
+
message,
|
|
345
|
+
...(typeof retry === "number" ? { retryAfterMs: retry } : {}),
|
|
346
|
+
};
|
|
347
|
+
}
|
|
333
348
|
totals(mint, quote) {
|
|
334
349
|
return this.#json(join(this.dir, "totals", totalsName(mint, quote)), `zyrk export --totals ${mint}${quote ? ` --quote ${quote}` : ""}`);
|
|
335
350
|
}
|
|
351
|
+
info(mint) {
|
|
352
|
+
return this.#json(join(this.dir, "info", infoName(mint)), `zyrk export --totals ${mint}`);
|
|
353
|
+
}
|
|
354
|
+
totalsFailure(mint, quote) {
|
|
355
|
+
return this.#failure(join("totals", totalsName(mint, quote)), `zyrk export --totals ${mint}${quote ? ` --quote ${quote}` : ""}`);
|
|
356
|
+
}
|
|
357
|
+
infoFailure(mint) {
|
|
358
|
+
return this.#failure(join("info", infoName(mint)), `zyrk export --totals ${mint}`);
|
|
359
|
+
}
|
|
360
|
+
volumeFailure(params) {
|
|
361
|
+
return this.#failure(join("volume", `${volumeName(params)}.json`), "zyrk export --volume-search '<params>'");
|
|
362
|
+
}
|
|
363
|
+
computeResult(plan) {
|
|
364
|
+
const saved = this.#json(join(this.dir, "compute", `${computeName(plan)}.json`), "zyrk export --compute '<plan>'");
|
|
365
|
+
return saved?.result;
|
|
366
|
+
}
|
|
367
|
+
computeFailure(plan) {
|
|
368
|
+
return this.#failure(join("compute", `${computeName(plan)}.json`), "zyrk export --compute '<plan>'");
|
|
369
|
+
}
|
|
336
370
|
volumePage(params) {
|
|
337
371
|
const saved = this.#json(join(this.dir, "volume", `${volumeName(params)}.json`), "zyrk export --volume-search '<params>'");
|
|
338
372
|
return saved?.page;
|
|
339
373
|
}
|
|
340
374
|
}
|
|
375
|
+
export function canonical(v) {
|
|
376
|
+
if (Array.isArray(v))
|
|
377
|
+
return `[${v.map(canonical).join(",")}]`;
|
|
378
|
+
if (v !== null && typeof v === "object")
|
|
379
|
+
return `{${Object.keys(v)
|
|
380
|
+
.sort()
|
|
381
|
+
.map((k) => `${JSON.stringify(k)}:${canonical(v[k])}`)
|
|
382
|
+
.join(",")}}`;
|
|
383
|
+
return JSON.stringify(v) ?? "null";
|
|
384
|
+
}
|
|
385
|
+
export function computeName(plan) {
|
|
386
|
+
return createHash("sha256")
|
|
387
|
+
.update(canonical(plan))
|
|
388
|
+
.digest("hex")
|
|
389
|
+
.slice(0, 32);
|
|
390
|
+
}
|
|
341
391
|
export const totalsName = (mint, quote) => quote ? `${mint}~${quote}.json` : `${mint}.json`;
|
|
392
|
+
export const infoName = (mint) => `${mint}.json`;
|
|
342
393
|
export function volumeName(params) {
|
|
343
394
|
const canonical = JSON.stringify([...params].sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)));
|
|
344
395
|
return createHash("sha256").update(canonical).digest("hex").slice(0, 32);
|
package/dist/local-replay.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { type LocalData, type ReadMeta } from "./local-data.js";
|
|
2
2
|
import { type HourBatch, type Subscription } from "./sdk.js";
|
|
3
|
-
/**
|
|
3
|
+
/** The first 30 s after coverage starts are not trusted (as the service). */
|
|
4
4
|
export declare const LAG_MS = 30000;
|
|
5
5
|
/** Events per delivery, as `delivery_batch` of the service. */
|
|
6
6
|
export declare const DELIVERY_BATCH = 32;
|
|
7
|
-
/** Largest `batchSize` of a replay
|
|
7
|
+
/** Largest `batchSize` of a replay of the service. */
|
|
8
8
|
export declare const REPLAY_MAX_BATCH = 1000;
|
|
9
|
-
/** History items per source page of the service
|
|
9
|
+
/** History items per source page of the service's replays. */
|
|
10
10
|
export declare const SOURCE_PAGE = 500;
|
|
11
11
|
/**
|
|
12
|
-
* Fields of the live operation JSON
|
|
12
|
+
* Fields of the live operation JSON that exported
|
|
13
13
|
* columns do not hold: absent from replayed operations.
|
|
14
14
|
*/
|
|
15
15
|
export declare const MISSING_OPERATION_FIELDS: readonly ["prices (provider strings: read batch.price… instead)", "usdValue", "finality", "provenance", "market (the provider's raw object)"];
|
|
@@ -26,7 +26,7 @@ export interface ReplayProgress {
|
|
|
26
26
|
gaps: number;
|
|
27
27
|
orderFallbacks: number;
|
|
28
28
|
}
|
|
29
|
-
/** Decimal string of a raw amount
|
|
29
|
+
/** Decimal string of a raw amount, as the live JSON writes it. */
|
|
30
30
|
export declare function formatRaw(raw: bigint, decimals: number): string;
|
|
31
31
|
/** Row `i` as the live publication shapes it (without {@link MISSING_OPERATION_FIELDS}). */
|
|
32
32
|
export declare function operationOf(b: HourBatch, i: number): Record<string, unknown>;
|
package/dist/local-replay.js
CHANGED
|
@@ -82,7 +82,8 @@ function poolEventOf(b, i, kind) {
|
|
|
82
82
|
const m = b.market;
|
|
83
83
|
const flags = m.flags[i];
|
|
84
84
|
const reserves = (flags & MarketFlag.reserves) !== 0;
|
|
85
|
-
const
|
|
85
|
+
const vToken = (flags & MarketFlag.virtualTokenReserve) !== 0;
|
|
86
|
+
const vQuote = (flags & MarketFlag.virtualQuoteReserve) !== 0;
|
|
86
87
|
const op = operationOf(b, i);
|
|
87
88
|
return {
|
|
88
89
|
...op,
|
|
@@ -93,10 +94,10 @@ function poolEventOf(b, i, kind) {
|
|
|
93
94
|
market: {
|
|
94
95
|
tokenReserveRaw: reserves ? m.tokenReserve[i].toString() : null,
|
|
95
96
|
quoteReserveRaw: reserves ? m.quoteReserve[i].toString() : null,
|
|
96
|
-
virtualTokenReserveRaw:
|
|
97
|
+
virtualTokenReserveRaw: vToken
|
|
97
98
|
? m.virtualTokenReserve[i].toString()
|
|
98
99
|
: null,
|
|
99
|
-
virtualQuoteReserveRaw:
|
|
100
|
+
virtualQuoteReserveRaw: vQuote
|
|
100
101
|
? m.virtualQuoteReserve[i].toString()
|
|
101
102
|
: null,
|
|
102
103
|
signer: addressAt(b, m.signer[i]),
|
package/dist/local-storage.d.ts
CHANGED
|
@@ -9,9 +9,9 @@ export declare const localStorageLimits: {
|
|
|
9
9
|
readonly maxUploads: 4;
|
|
10
10
|
/** Bytes read plus written by one execution (`io_limit`). */
|
|
11
11
|
readonly ioBytes: number;
|
|
12
|
-
/** Quota cost floor of one object
|
|
12
|
+
/** Quota cost floor of one object. */
|
|
13
13
|
readonly minObjectBytes: 4096;
|
|
14
|
-
/** Objects per area
|
|
14
|
+
/** Objects per area. */
|
|
15
15
|
readonly maxObjects: 100000;
|
|
16
16
|
};
|
|
17
17
|
export interface AreaConfig {
|
package/dist/local.d.ts
CHANGED
|
@@ -199,9 +199,9 @@ export interface KeepStorage {
|
|
|
199
199
|
readonly from?: "short" | "long" | "durable";
|
|
200
200
|
readonly keys?: readonly string[];
|
|
201
201
|
}
|
|
202
|
-
/**
|
|
202
|
+
/** Longest replay of the service (30 min). */
|
|
203
203
|
export declare const REPLAY_MAX_DURATION_MS: number;
|
|
204
|
-
/**
|
|
204
|
+
/** Unread feed of one replay on the service (16 MiB). */
|
|
205
205
|
export declare const REPLAY_FEED_BYTES: number;
|
|
206
206
|
/** What {@link LocalRunner.replay} answers (the service's replay record, locally). */
|
|
207
207
|
export interface LocalReplay {
|