@johannc-dev/zyrk 0.0.0-stage → 0.2.1

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.
@@ -0,0 +1,345 @@
1
+ import { constants, zstdCompressSync, zstdDecompressSync } from "node:zlib";
2
+ import { MarketFlag } from "./sdk.js";
3
+ export const HOUR_MAGIC = "ZHB1";
4
+ export const nodeRealm = {
5
+ Uint8Array,
6
+ Uint16Array,
7
+ Uint32Array,
8
+ Int32Array,
9
+ BigUint64Array,
10
+ Float64Array,
11
+ Array,
12
+ Object,
13
+ };
14
+ export const COLUMNS = [
15
+ ["line", "u32"],
16
+ ["op", "u16"],
17
+ ["slot", "f64"],
18
+ ["timeMs", "f64"],
19
+ ["receivedMs", "f64"],
20
+ ["side", "u8"],
21
+ ["protocol", "u8"],
22
+ ["mint", "u32"],
23
+ ["pool", "i32"],
24
+ ["trader", "i32"],
25
+ ["quoteMint", "u32"],
26
+ ["tokenRaw", "u64"],
27
+ ["tokenDecimals", "u8"],
28
+ ["quoteRaw", "u64"],
29
+ ["quoteDecimals", "u8"],
30
+ ["tokenAmount", "f64"],
31
+ ["quoteAmount", "f64"],
32
+ ["price", "f64"],
33
+ ["priceSol", "f64"],
34
+ ["priceUsd", "f64"],
35
+ ["quotePriceUsd", "f64"],
36
+ ["usdValue", "f64"],
37
+ ];
38
+ export const MARKET_COLUMNS = [
39
+ ["tokenReserve", "u64"],
40
+ ["quoteReserve", "u64"],
41
+ ["virtualTokenReserve", "u64"],
42
+ ["virtualQuoteReserve", "u64"],
43
+ ["poolFeeBps", "f64"],
44
+ ["routeFeeBps", "f64"],
45
+ ["holderRewardsBps", "f64"],
46
+ ["creatorFeeRecipient", "i32"],
47
+ ["signer", "i32"],
48
+ ["priorityFeeRaw", "u64"],
49
+ ["flags", "u16"],
50
+ ["event", "u8"],
51
+ ];
52
+ export const ORDER_COLUMNS = [
53
+ ["txIndex", "u32"],
54
+ ["eventIndex", "u16"],
55
+ ["blockTimeMs", "f64"],
56
+ ["flags", "u8"],
57
+ ];
58
+ export const TX_COLUMNS = [
59
+ ["id", "u32"],
60
+ ["index", "u16"],
61
+ ["flags", "u8"],
62
+ ];
63
+ const SIZE = {
64
+ u8: 1,
65
+ u16: 2,
66
+ u32: 4,
67
+ i32: 4,
68
+ u64: 8,
69
+ f64: 8,
70
+ };
71
+ function ctor(realm, type) {
72
+ switch (type) {
73
+ case "u8":
74
+ return realm.Uint8Array;
75
+ case "u16":
76
+ return realm.Uint16Array;
77
+ case "u32":
78
+ return realm.Uint32Array;
79
+ case "i32":
80
+ return realm.Int32Array;
81
+ case "u64":
82
+ return realm.BigUint64Array;
83
+ case "f64":
84
+ return realm.Float64Array;
85
+ }
86
+ }
87
+ export class HourFileError extends Error {
88
+ name = "HourFileError";
89
+ }
90
+ export function decompressHour(file) {
91
+ let bytes;
92
+ try {
93
+ bytes = new Uint8Array(zstdDecompressSync(file));
94
+ }
95
+ catch (e) {
96
+ throw new HourFileError(`Not a valid zstd frame: ${e.message}`);
97
+ }
98
+ return parseHour(bytes);
99
+ }
100
+ const isInt = (v, min = 0) => typeof v === "number" && Number.isSafeInteger(v) && v >= min;
101
+ export function parseHour(bytes) {
102
+ if (bytes.length < 8)
103
+ throw new HourFileError(`Truncated exported hour (${bytes.length} bytes)`);
104
+ const magic = String.fromCharCode(...bytes.subarray(0, 4));
105
+ if (magic !== HOUR_MAGIC)
106
+ throw new HourFileError(`Not an exported hour (magic ${magic})`);
107
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
108
+ const n = view.getUint32(4, true);
109
+ if (8 + n > bytes.length)
110
+ throw new HourFileError("Truncated header of an exported hour");
111
+ let header;
112
+ try {
113
+ header = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes.subarray(8, 8 + n)));
114
+ }
115
+ catch (e) {
116
+ throw new HourFileError(`Unreadable header: ${e.message}`);
117
+ }
118
+ const h = header;
119
+ const fail = (what) => {
120
+ throw new HourFileError(`Invalid header: ${what}`);
121
+ };
122
+ if (!isInt(h.hour))
123
+ fail("hour");
124
+ if (h.source !== "sealed" && h.source !== "live")
125
+ fail("source");
126
+ if (typeof h.complete !== "boolean")
127
+ fail("complete");
128
+ if (!isInt(h.skipped))
129
+ fail("skipped");
130
+ if (!isInt(h.length))
131
+ fail("length");
132
+ if (typeof h.market !== "boolean" || typeof h.order !== "boolean")
133
+ fail("market/order");
134
+ if (h.tx !== undefined && typeof h.tx !== "boolean")
135
+ fail("tx");
136
+ if (!Array.isArray(h.addresses) ||
137
+ !h.addresses.every((a) => typeof a === "string"))
138
+ fail("addresses");
139
+ if (h.signatures !== undefined &&
140
+ (!Array.isArray(h.signatures) ||
141
+ h.signatures.length !== header.length ||
142
+ !h.signatures.every((x) => x === null || typeof x === "string")))
143
+ fail("signatures");
144
+ if (h.skippedTimes !== undefined &&
145
+ (!Array.isArray(h.skippedTimes) ||
146
+ h.skippedTimes.length !== header.skipped ||
147
+ !h.skippedTimes.every((x) => x === null || isInt(x))))
148
+ fail("skippedTimes");
149
+ if (!Array.isArray(h.columns))
150
+ fail("columns");
151
+ const expected = [
152
+ ...COLUMNS,
153
+ ...(header.market
154
+ ? MARKET_COLUMNS.map(([k, t]) => [`market.${k}`, t])
155
+ : []),
156
+ ...(header.order
157
+ ? ORDER_COLUMNS.map(([k, t]) => [`order.${k}`, t])
158
+ : []),
159
+ ...(header.tx ? TX_COLUMNS.map(([k, t]) => [`tx.${k}`, t]) : []),
160
+ ];
161
+ const at = new Map();
162
+ for (const c of header.columns) {
163
+ if (!Array.isArray(c) ||
164
+ typeof c[0] !== "string" ||
165
+ !(c[1] in SIZE) ||
166
+ !isInt(c[2]))
167
+ fail("column entry");
168
+ at.set(c[0], c);
169
+ }
170
+ for (const [name, type] of expected) {
171
+ const c = at.get(name);
172
+ if (!c || c[1] !== type)
173
+ fail(`column ${name}`);
174
+ const [, , offset] = c;
175
+ if (offset % 8 !== 0 ||
176
+ offset < 8 + n ||
177
+ offset + header.length * SIZE[type] > bytes.length)
178
+ throw new HourFileError(`Column ${name} out of the file`);
179
+ }
180
+ const raw = { header, bytes };
181
+ checkIndexes(raw);
182
+ return raw;
183
+ }
184
+ function checkIndexes(raw) {
185
+ const { header, bytes } = raw;
186
+ const n = header.addresses.length;
187
+ const at = new Map(header.columns.map((c) => [c[0], c]));
188
+ const check = (name, signed) => {
189
+ const c = at.get(name);
190
+ if (!c)
191
+ return;
192
+ const view = new DataView(bytes.buffer, bytes.byteOffset + c[2], header.length * 4);
193
+ for (let i = 0; i < header.length; i++) {
194
+ const v = signed
195
+ ? view.getInt32(i * 4, true)
196
+ : view.getUint32(i * 4, true);
197
+ if (v >= n || v < (signed ? -1 : 0))
198
+ throw new HourFileError(`${name}[${i}] = ${v}: not an index of addresses (${n})`);
199
+ }
200
+ };
201
+ check("mint", false);
202
+ check("quoteMint", false);
203
+ check("pool", true);
204
+ check("trader", true);
205
+ check("market.creatorFeeRecipient", true);
206
+ check("market.signer", true);
207
+ }
208
+ export function batchOf(raw, realm = nodeRealm) {
209
+ const { header, bytes } = raw;
210
+ const at = new Map(header.columns.map((c) => [c[0], c]));
211
+ const column = (name, type) => {
212
+ const c = at.get(name);
213
+ if (!c || c[1] !== type)
214
+ throw new HourFileError(`Missing column ${name}: ${type}`);
215
+ const n = header.length * SIZE[type];
216
+ const copy = new realm.Uint8Array(n);
217
+ copy.set(bytes.subarray(c[2], c[2] + n));
218
+ return new (ctor(realm, type))(copy.buffer, 0, header.length);
219
+ };
220
+ const strings = (list) => {
221
+ const out = new realm.Array(list.length);
222
+ for (let i = 0; i < list.length; i++)
223
+ out[i] = list[i] ?? null;
224
+ return out;
225
+ };
226
+ const batch = new realm.Object();
227
+ batch.hour = header.hour;
228
+ batch.source = header.source;
229
+ batch.complete = header.complete;
230
+ batch.skipped = header.skipped;
231
+ batch.length = header.length;
232
+ batch.addresses = strings(header.addresses);
233
+ for (const [name, type] of COLUMNS)
234
+ batch[name] = column(name, type);
235
+ if (header.signatures)
236
+ batch.signatures = strings(header.signatures);
237
+ if (header.market) {
238
+ const m = new realm.Object();
239
+ for (const [name, type] of MARKET_COLUMNS)
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;
247
+ batch.market = m;
248
+ }
249
+ if (header.order) {
250
+ const o = new realm.Object();
251
+ for (const [name, type] of ORDER_COLUMNS)
252
+ o[name] = column(`order.${name}`, type);
253
+ batch.order = o;
254
+ }
255
+ if (header.tx) {
256
+ const t = new realm.Object();
257
+ for (const [name, type] of TX_COLUMNS)
258
+ t[name] = column(`tx.${name}`, type);
259
+ batch.tx = t;
260
+ }
261
+ return batch;
262
+ }
263
+ export function encodeHour(batch) {
264
+ const parts = [];
265
+ const add = (name, type, values) => parts.push({
266
+ name,
267
+ type,
268
+ data: new Uint8Array(values.buffer, values.byteOffset, batch.length * SIZE[type]),
269
+ });
270
+ const b = batch;
271
+ for (const [name, type] of COLUMNS)
272
+ add(name, type, b[name]);
273
+ const m = batch.market;
274
+ if (m)
275
+ for (const [name, type] of MARKET_COLUMNS)
276
+ add(`market.${name}`, type, m[name]);
277
+ const o = batch.order;
278
+ if (o)
279
+ for (const [name, type] of ORDER_COLUMNS)
280
+ add(`order.${name}`, type, o[name]);
281
+ const t = batch.tx;
282
+ if (t)
283
+ for (const [name, type] of TX_COLUMNS)
284
+ add(`tx.${name}`, type, t[name]);
285
+ const header = (base) => {
286
+ let offset = base;
287
+ const columns = parts.map((p) => {
288
+ offset = Math.ceil(offset / 8) * 8;
289
+ const c = [p.name, p.type, offset];
290
+ offset += p.data.length;
291
+ return c;
292
+ });
293
+ return JSON.stringify({
294
+ hour: batch.hour,
295
+ source: batch.source,
296
+ complete: batch.complete,
297
+ skipped: batch.skipped,
298
+ length: batch.length,
299
+ addresses: batch.addresses,
300
+ market: !!batch.market,
301
+ order: !!batch.order,
302
+ ...(batch.tx ? { tx: true } : {}),
303
+ columns,
304
+ ...(batch.signatures ? { signatures: batch.signatures } : {}),
305
+ ...(batch.skippedTimes
306
+ ? { skippedTimes: batch.skippedTimes }
307
+ : {}),
308
+ });
309
+ };
310
+ let base = 0;
311
+ let text = header(base);
312
+ for (;;) {
313
+ const start = Math.ceil((8 + Buffer.byteLength(text)) / 8) * 8;
314
+ if (start === base)
315
+ break;
316
+ base = start;
317
+ text = header(base);
318
+ }
319
+ const head = new TextEncoder().encode(text);
320
+ let size = base;
321
+ for (const p of parts)
322
+ size = Math.ceil(size / 8) * 8 + p.data.length;
323
+ const out = new Uint8Array(size);
324
+ out.set(new TextEncoder().encode(HOUR_MAGIC), 0);
325
+ new DataView(out.buffer).setUint32(4, head.length, true);
326
+ out.set(head, 8);
327
+ let offset = base;
328
+ for (const p of parts) {
329
+ offset = Math.ceil(offset / 8) * 8;
330
+ out.set(p.data, offset);
331
+ offset += p.data.length;
332
+ }
333
+ return out;
334
+ }
335
+ export function compress(raw) {
336
+ return new Uint8Array(zstdCompressSync(raw, {
337
+ params: {
338
+ [constants.ZSTD_c_compressionLevel]: 3,
339
+ [constants.ZSTD_c_checksumFlag]: 1,
340
+ },
341
+ }));
342
+ }
343
+ export function compressHour(batch) {
344
+ return compress(encodeHour(batch));
345
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Limits of hosted functions, mirrored from the server defaults. The server is the
3
+ * authority; these constants let tools and programs plan around them.
4
+ *
5
+ * No dependency: safe to import from guest code.
6
+ */
7
+ /** Per execution, enforced by the sandbox. */
8
+ export declare const execLimits: {
9
+ /**
10
+ * CPU time of the isolate thread (not instructions, not wall time), history
11
+ * reads excluded: they count in `readMs`.
12
+ */
13
+ readonly cpuMs: 10000;
14
+ /** Wall time, waits (`ctx.jobs.wait`, host operations) included. */
15
+ readonly wallMs: 60000;
16
+ readonly heapBytes: number;
17
+ /** JSON arguments of a direct call or a job. */
18
+ readonly inputBytes: number;
19
+ /**
20
+ * JSON arguments of an event delivery (live or replay): 32 real trade events
21
+ * are ~64 KB (~2 KB each). A replay `batchSize` multiplies it.
22
+ */
23
+ readonly eventInputBytes: number;
24
+ /** Result + logs + emits of one execution. */
25
+ readonly outputBytes: number;
26
+ /** History bytes served to one execution (columns, totals, volume pages). */
27
+ readonly readBytes: number;
28
+ /** Calls to `ctx.state`, `ctx.jobs`, `ctx.subscriptions`, `ctx.log`, `ctx.emit`. */
29
+ readonly hostOps: 10000;
30
+ /**
31
+ * CPU time spent by the service serving history reads (`ctx.data.*`:
32
+ * decompression, filtering) for one execution. Over it: `read_time_limit`.
33
+ */
34
+ readonly readMs: 30000;
35
+ /** User storage bytes read plus written by one execution (`io_limit`). */
36
+ readonly storageBytes: number;
37
+ };
38
+ /** User storage (`ctx.storage`), per account. Server defaults; operators may change them. */
39
+ export declare const storageLimits: {
40
+ readonly shortQuotaBytes: number;
41
+ readonly shortTtlMs: number;
42
+ readonly longQuotaBytes: number;
43
+ readonly longTtlMs: number;
44
+ /** Never expires. */
45
+ readonly durableQuotaBytes: number;
46
+ /** Largest object (`too_large`). Reading one whole keeps it in memory (`heapBytes`). */
47
+ readonly maxObjectBytes: number;
48
+ /** Bytes per host operation: larger values take several. */
49
+ readonly chunkBytes: number;
50
+ readonly maxKeyBytes: 512;
51
+ readonly maxMetadataEntries: 16;
52
+ readonly maxMetadataBytes: 4096;
53
+ /** Items per `list` page. */
54
+ readonly maxList: 1000;
55
+ /** `writer()`s open at once in one execution. */
56
+ readonly maxUploads: 4;
57
+ };
58
+ /** Per account and per workflow, enforced by the orchestrator. */
59
+ export declare const accountLimits: {
60
+ /** Executions of one account running at once (job grids). */
61
+ readonly parallelExecutions: 8;
62
+ readonly queuedExecutions: 1000;
63
+ /** Jobs started by one execution. */
64
+ readonly maxChildren: 32;
65
+ readonly maxDepth: 16;
66
+ readonly stateBytesPerProgram: number;
67
+ readonly stateValueBytes: number;
68
+ readonly maxPrograms: 32;
69
+ readonly storedCodeBytes: number;
70
+ readonly bundleBytes: number;
71
+ readonly maxSubscriptions: 64;
72
+ readonly maxPriceSubscriptions: 8;
73
+ readonly workflowCpuMs: 60000;
74
+ readonly workflowReadBytes: number;
75
+ /** History read time (`execLimits.readMs`) of a workflow, children included. */
76
+ readonly workflowReadMs: 180000;
77
+ readonly workflowAttempts: 256;
78
+ /** Results are kept this long, or until acknowledged. */
79
+ readonly resultTtlMs: number;
80
+ readonly retryAttempts: 3;
81
+ readonly retryBackoffMs: 250;
82
+ };
83
+ /** Bundle size accepted by `zyrk build` and by the server. */
84
+ export declare const MAX_BUNDLE_BYTES: number;
85
+ /** Longest history window of one read (`[sinceMs, untilMs)`), as the HTTP API. */
86
+ export declare const MAX_WINDOW_MS: number;
87
+ /** Subscription delivery: events per batch and replay cap. */
88
+ export declare const subscriptionLimits: {
89
+ readonly eventsPerDelivery: 32;
90
+ readonly maxReplayEvents: 1000;
91
+ };
package/dist/limits.js ADDED
@@ -0,0 +1,54 @@
1
+ const KIB = 1024;
2
+ const MIB = 1024 * KIB;
3
+ export const execLimits = {
4
+ cpuMs: 10_000,
5
+ wallMs: 60_000,
6
+ heapBytes: 256 * MIB,
7
+ inputBytes: 32 * KIB,
8
+ eventInputBytes: 128 * KIB,
9
+ outputBytes: 64 * KIB,
10
+ readBytes: 256 * MIB,
11
+ hostOps: 10_000,
12
+ readMs: 30_000,
13
+ storageBytes: 1024 * MIB,
14
+ };
15
+ export const storageLimits = {
16
+ shortQuotaBytes: 1024 * MIB,
17
+ shortTtlMs: 24 * 3_600_000,
18
+ longQuotaBytes: 500 * MIB,
19
+ longTtlMs: 7 * 24 * 3_600_000,
20
+ durableQuotaBytes: 50 * MIB,
21
+ maxObjectBytes: 64 * MIB,
22
+ chunkBytes: 4 * MIB,
23
+ maxKeyBytes: 512,
24
+ maxMetadataEntries: 16,
25
+ maxMetadataBytes: 4096,
26
+ maxList: 1000,
27
+ maxUploads: 4,
28
+ };
29
+ export const accountLimits = {
30
+ parallelExecutions: 8,
31
+ queuedExecutions: 1000,
32
+ maxChildren: 32,
33
+ maxDepth: 16,
34
+ stateBytesPerProgram: 32 * MIB,
35
+ stateValueBytes: MIB,
36
+ maxPrograms: 32,
37
+ storedCodeBytes: 32 * MIB,
38
+ bundleBytes: MIB,
39
+ maxSubscriptions: 64,
40
+ maxPriceSubscriptions: 8,
41
+ workflowCpuMs: 60_000,
42
+ workflowReadBytes: 1024 * MIB,
43
+ workflowReadMs: 180_000,
44
+ workflowAttempts: 256,
45
+ resultTtlMs: 15 * 60_000,
46
+ retryAttempts: 3,
47
+ retryBackoffMs: 250,
48
+ };
49
+ export const MAX_BUNDLE_BYTES = accountLimits.bundleBytes;
50
+ export const MAX_WINDOW_MS = 31 * 24 * 3_600_000;
51
+ export const subscriptionLimits = {
52
+ eventsPerDelivery: 32,
53
+ maxReplayEvents: 1000,
54
+ };
@@ -0,0 +1,115 @@
1
+ import { type HourHeader, type Realm } from "./hourfile.js";
2
+ import type { HourBatch, Json } from "./sdk.js";
3
+ export declare const HOUR_MS = 3600000;
4
+ export declare const READ_FORMAT = "zyrk-read/1";
5
+ /** What one exported read selects: the options that shape a batch. */
6
+ export interface ReadSpec {
7
+ readonly scope: "token" | "wallet";
8
+ readonly key: string;
9
+ readonly side: "any" | "buy" | "sell";
10
+ readonly withSignatures: boolean;
11
+ /** `withMarket || withEvents`, as the sandbox does. */
12
+ readonly withMarket: boolean;
13
+ readonly withEvents: boolean;
14
+ readonly withOrder: boolean;
15
+ /** Absent from exports written before transaction ids (false). */
16
+ readonly withTx?: boolean;
17
+ }
18
+ /** `coverage` of the export window, as the server computed it. */
19
+ export interface Coverage {
20
+ readonly complete?: boolean;
21
+ readonly preparing?: boolean;
22
+ readonly retentionHorizonMs?: number | null;
23
+ readonly sealedUntilMs?: number | null;
24
+ readonly liveUntilMs?: number | null;
25
+ readonly gaps?: readonly {
26
+ readonly fromMs?: number | null;
27
+ readonly toMs?: number | null;
28
+ }[];
29
+ readonly [field: string]: unknown;
30
+ }
31
+ export interface ReadMeta extends ReadSpec {
32
+ readonly format: typeof READ_FORMAT;
33
+ readonly sinceMs: number;
34
+ readonly untilMs: number;
35
+ readonly coverage: Coverage | null;
36
+ /** Last hour received (`after_hour` of the next request); `null` before the first. */
37
+ readonly afterHour: number | null;
38
+ readonly done: boolean;
39
+ /** Hours with a file, in order. */
40
+ readonly hours: readonly number[];
41
+ /** SHA-256 (hex) of each hour file, checked before it is read. */
42
+ readonly hashes?: Readonly<Record<string, string>>;
43
+ readonly updatedAt: string;
44
+ }
45
+ export declare function spec(input: {
46
+ scope: "token" | "wallet";
47
+ key: string;
48
+ side?: "any" | "buy" | "sell" | undefined;
49
+ withSignatures?: boolean | undefined;
50
+ withMarket?: boolean | undefined;
51
+ withEvents?: boolean | undefined;
52
+ withOrder?: boolean | undefined;
53
+ withTx?: boolean | undefined;
54
+ }): ReadSpec;
55
+ /** Directory name of an exported read. */
56
+ export declare function readId(s: ReadSpec, sinceMs: number, untilMs: number): string;
57
+ /** The `zyrk export` command that would produce a read. */
58
+ export declare function exportCommand(s: ReadSpec, sinceMs: number, untilMs: number, refresh?: boolean): string;
59
+ /** An error a read throws in the program: `code` as in production. */
60
+ export interface ReadFailure {
61
+ readonly code: string;
62
+ readonly message: string;
63
+ readonly retryAfterMs?: number;
64
+ }
65
+ export declare class ReadFailed extends Error {
66
+ readonly failure: ReadFailure;
67
+ constructor(failure: ReadFailure);
68
+ }
69
+ export declare const sha256: (bytes: Uint8Array) => string;
70
+ export declare function windowComplete(c: Coverage, since: number, until: number): boolean;
71
+ export declare function hourComplete(c: Coverage, hour: number, since: number, until: number): boolean;
72
+ /** What a batch costs against `readBytes`, as the service counts it. */
73
+ export declare function batchBytes(h: HourHeader): number;
74
+ export interface HourRead {
75
+ readonly batch: HourBatch;
76
+ readonly bytes: number;
77
+ /** The hour was re-cut to a narrower window than the export's. */
78
+ readonly recut: boolean;
79
+ /**
80
+ * The hour may have changed since the export: it was open (`live`) or not
81
+ * sealed yet. `zyrk export` run again downloads such hours again.
82
+ */
83
+ readonly stale: boolean;
84
+ }
85
+ export declare class LocalData {
86
+ #private;
87
+ readonly dir: string;
88
+ constructor(dir: string);
89
+ /** Every exported read (re-scanned after {@link refresh}). */
90
+ reads(): readonly ReadMeta[];
91
+ refresh(): void;
92
+ /** The narrowest export with these options whose window holds `[since, until)`. */
93
+ find(s: ReadSpec, since: number, until: number): ReadMeta | undefined;
94
+ /**
95
+ * The next batch of a read after `afterHour` (`null`: none left), as
96
+ * the service's reader answers it. Throws {@link ReadFailed}.
97
+ */
98
+ readHour(s: ReadSpec, since: number, until: number, allowPartial: boolean, afterHour: number | null, realm?: Realm): HourRead | null;
99
+ /** Totals of a mint saved by the export, or `undefined`. */
100
+ totals(mint: string, quote: string | null): Json | undefined;
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
+ /** A volume search page saved by the export for exactly these parameters. */
110
+ volumePage(params: readonly (readonly [string, string])[]): Json | undefined;
111
+ }
112
+ export declare const totalsName: (mint: string, quote: string | null) => string;
113
+ export declare const infoName: (mint: string) => string;
114
+ /** Parameters as the sandbox passes them (`[key, string value]`), sorted. */
115
+ export declare function volumeName(params: readonly (readonly [string, string])[]): string;