@volter/twin-upstash 0.1.0 → 0.1.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.
- package/README.md +2 -1
- package/dist/qstash/src/doors.js +1 -1
- package/dist/qstash/src/doors.ts +2 -2
- package/dist/qstash/src/fetch.js +2 -2
- package/dist/qstash/src/fetch.ts +2 -2
- package/dist/qstash/src/semantics/delivery.d.ts +1 -1
- package/dist/qstash/src/semantics/delivery.js +13 -8
- package/dist/qstash/src/semantics/delivery.ts +13 -8
- package/dist/src/upstash-lua.d.ts +8 -140
- package/dist/src/upstash-lua.js +13 -1227
- package/dist/src/upstash-store.d.ts +15 -87
- package/dist/src/upstash-store.js +49 -1579
- package/dist/src/upstash-twin.js +2 -2
- package/package.json +4 -4
- package/qstash/src/doors.ts +2 -2
- package/qstash/src/fetch.ts +2 -2
- package/qstash/src/semantics/delivery.ts +13 -8
- package/src/manifest.ts +3 -2
- package/src/upstash-lua.ts +31 -1119
- package/src/upstash-store.ts +73 -1346
- package/src/upstash-twin.ts +2 -2
package/README.md
CHANGED
|
@@ -30,7 +30,8 @@ silent no-op.
|
|
|
30
30
|
|
|
31
31
|
## `EVAL` runs a Lua interpreter
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
The kernel's Redis library (`@volter/world-core/redis`, `lua.ts`; this pack's `upstash-lua.ts` names its command
|
|
34
|
+
API `redis`) is a tokenizer, parser and evaluator for the Lua subset real Redis scripts use.
|
|
34
35
|
It was a deliberate choice over the cheaper alternative — recognising known script SHA1s and
|
|
35
36
|
dispatching to hand-written TypeScript — because recognition is a fake-success generator: it passes
|
|
36
37
|
for the exact bytes it memorised and silently mis-answers anything one character different, and the
|
package/dist/qstash/src/doors.js
CHANGED
|
@@ -17,7 +17,7 @@ export async function door(ctx, request, report) {
|
|
|
17
17
|
return Response.json({ error: 'to is required' }, { status: 400 });
|
|
18
18
|
const deliveries = ctx.rowsRaw(DELIVERY).filter((d) => d.to === to)
|
|
19
19
|
.sort((a, b) => String(a.at).localeCompare(String(b.at)) || Number(a.n) - Number(b.n))
|
|
20
|
-
.map((d) => ({ messageId: d.message_id, retried: d.retried, at: d.at, method: d.method, headers: d.headers, body: d.body, status: d.status }));
|
|
20
|
+
.map((d) => ({ messageId: d.message_id, retried: d.retried, at: d.at, ...(d.sent_at !== undefined ? { sentAt: d.sent_at } : {}), method: d.method, headers: d.headers, body: d.body, status: d.status }));
|
|
21
21
|
return Response.json({ to, deliveries });
|
|
22
22
|
}
|
|
23
23
|
if (request.method === 'POST' && path === '/_twin/destinations') {
|
package/dist/qstash/src/doors.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// `POST /_twin/recipients/:domain`).
|
|
6
6
|
//
|
|
7
7
|
// - GET /_twin/deliveries?to=<url> → {to, deliveries}: every request QStash made to that URL, oldest first, each as the
|
|
8
|
-
// endpoint received it (its method, headers and body) and the status it answered.
|
|
8
|
+
// endpoint received it (its method, headers and body), when it fell due (`at`) and was sent (`sentAt`), and the status it answered.
|
|
9
9
|
// - POST /_twin/destinations {url, status, body?, headers?, when?} → 204: the World's word on how the endpoints under a URL
|
|
10
10
|
// answer, and, with `when` (header names and values), how they answer a delivery carrying those headers (a workflow's
|
|
11
11
|
// route answers its failure call, `Upstash-Workflow-Is-Failure: true`, apart from its steps). The statement that names
|
|
@@ -36,7 +36,7 @@ export async function door(ctx: SemanticsContext, request: Request, report: Catc
|
|
|
36
36
|
if (!to) return Response.json({ error: 'to is required' }, { status: 400 });
|
|
37
37
|
const deliveries = ctx.rowsRaw(DELIVERY).filter((d) => d.to === to)
|
|
38
38
|
.sort((a, b) => String(a.at).localeCompare(String(b.at)) || Number(a.n) - Number(b.n))
|
|
39
|
-
.map((d) => ({ messageId: d.message_id, retried: d.retried, at: d.at, method: d.method, headers: d.headers, body: d.body, status: d.status }));
|
|
39
|
+
.map((d) => ({ messageId: d.message_id, retried: d.retried, at: d.at, ...(d.sent_at !== undefined ? { sentAt: d.sent_at } : {}), method: d.method, headers: d.headers, body: d.body, status: d.status }));
|
|
40
40
|
return Response.json({ to, deliveries });
|
|
41
41
|
}
|
|
42
42
|
if (request.method === 'POST' && path === '/_twin/destinations') {
|
package/dist/qstash/src/fetch.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// World's doors and the lane's gate in front, and the catch-up before all of them. The upstash pack's fetch
|
|
3
3
|
// (../../src/upstash-server.ts) sends it every request for a QStash host, every `/v2/` path (an application reaches QStash
|
|
4
4
|
// at its QSTASH_URL, the pack's own address) and the lane's doors.
|
|
5
|
-
import { bindSemantics, coreFor, createDerivedFetch, crossCutting, semanticsContext } from '@volter/world-core';
|
|
5
|
+
import { bindSemantics, coreFor, worldNow, createDerivedFetch, crossCutting, semanticsContext } from '@volter/world-core';
|
|
6
6
|
import surface from './generated/surface.gen.json' with { type: 'json' };
|
|
7
7
|
import { manifest, spanning } from "./manifest.js";
|
|
8
8
|
import { door, DOOR } from "./doors.js";
|
|
@@ -33,7 +33,7 @@ export function createQstashLaneFetch(options = {}) {
|
|
|
33
33
|
if (!options.readOnly && !catchingUp.has(key)) {
|
|
34
34
|
catchingUp.add(key);
|
|
35
35
|
try {
|
|
36
|
-
report = await catchUp(ctx);
|
|
36
|
+
report = await catchUp(ctx, () => Date.parse((options.clock ?? worldNow)()));
|
|
37
37
|
}
|
|
38
38
|
finally {
|
|
39
39
|
catchingUp.delete(key);
|
package/dist/qstash/src/fetch.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// World's doors and the lane's gate in front, and the catch-up before all of them. The upstash pack's fetch
|
|
3
3
|
// (../../src/upstash-server.ts) sends it every request for a QStash host, every `/v2/` path (an application reaches QStash
|
|
4
4
|
// at its QSTASH_URL, the pack's own address) and the lane's doors.
|
|
5
|
-
import { bindSemantics, coreFor, createDerivedFetch, crossCutting, semanticsContext, type DerivedFetch, type DerivedSurface } from '@volter/world-core';
|
|
5
|
+
import { bindSemantics, coreFor, worldNow, createDerivedFetch, crossCutting, semanticsContext, type DerivedFetch, type DerivedSurface } from '@volter/world-core';
|
|
6
6
|
import surface from './generated/surface.gen.json' with { type: 'json' };
|
|
7
7
|
import { manifest, spanning } from './manifest.ts';
|
|
8
8
|
import { door, DOOR } from './doors.ts';
|
|
@@ -36,7 +36,7 @@ export function createQstashLaneFetch(options: QstashLaneOptions = {}): DerivedF
|
|
|
36
36
|
let report: CatchUpReport = { delivered: [], retried: [], failed: [] };
|
|
37
37
|
if (!options.readOnly && !catchingUp.has(key)) {
|
|
38
38
|
catchingUp.add(key);
|
|
39
|
-
try { report = await catchUp(ctx); } finally { catchingUp.delete(key); }
|
|
39
|
+
try { report = await catchUp(ctx, () => Date.parse((options.clock ?? worldNow)())); } finally { catchingUp.delete(key); }
|
|
40
40
|
}
|
|
41
41
|
const own = await door(ctx, request, report);
|
|
42
42
|
if (own) return own;
|
|
@@ -14,4 +14,4 @@ export type CatchUpReport = {
|
|
|
14
14
|
failed: Row[];
|
|
15
15
|
};
|
|
16
16
|
/** Run everything due by the World's instant, in order, each at its own moment. */
|
|
17
|
-
export declare function catchUp(ctx: SemanticsContext): Promise<CatchUpReport>;
|
|
17
|
+
export declare function catchUp(ctx: SemanticsContext, live?: () => number): Promise<CatchUpReport>;
|
|
@@ -119,7 +119,7 @@ function eligible(ctx, m, all) {
|
|
|
119
119
|
}
|
|
120
120
|
return true;
|
|
121
121
|
}
|
|
122
|
-
function nextDue(ctx, until) {
|
|
122
|
+
function nextDue(ctx, until, live) {
|
|
123
123
|
const due = [];
|
|
124
124
|
for (const s of ctx.rowsRaw('Schedule')) {
|
|
125
125
|
if (typeof s.nextScheduleTime === 'number' && s.nextScheduleTime <= until)
|
|
@@ -138,7 +138,7 @@ function nextDue(ctx, until) {
|
|
|
138
138
|
if (m.state === 'CANCEL_REQUESTED')
|
|
139
139
|
due.push({ when, order: 2 + Number(m._ordinal) / 1e9, act: (at) => cancelled(at, m) });
|
|
140
140
|
else if (eligible(ctx, m, all))
|
|
141
|
-
due.push({ when, order: 3 + Number(m._ordinal) / 1e9, act: (at, report) => attempt(at, m, report) });
|
|
141
|
+
due.push({ when, order: 3 + Number(m._ordinal) / 1e9, act: (at, report) => attempt(at, m, report, live) });
|
|
142
142
|
}
|
|
143
143
|
for (const resource of ['DLQMessage', 'WorkflowDLQMessage']) {
|
|
144
144
|
for (const e of ctx.rowsRaw(resource)) {
|
|
@@ -150,14 +150,14 @@ function nextDue(ctx, until) {
|
|
|
150
150
|
return due.sort((a, b) => a.when - b.when || a.order - b.order)[0];
|
|
151
151
|
}
|
|
152
152
|
/** Run everything due by the World's instant, in order, each at its own moment. */
|
|
153
|
-
export async function catchUp(ctx) {
|
|
153
|
+
export async function catchUp(ctx, live = () => nowMs(ctx)) {
|
|
154
154
|
const until = nowMs(ctx);
|
|
155
155
|
const report = { delivered: [], retried: [], failed: [] };
|
|
156
156
|
// a message held behind another (the one before it in its queue or its run) goes once that one has left, so the World
|
|
157
157
|
// clock never runs back: each move happens at its own due time or at the last move's, whichever is later
|
|
158
158
|
let last = 0;
|
|
159
159
|
for (let n = 0; n < 5000; n += 1) {
|
|
160
|
-
const next = nextDue(ctx, until);
|
|
160
|
+
const next = nextDue(ctx, until, live);
|
|
161
161
|
if (!next)
|
|
162
162
|
break;
|
|
163
163
|
last = Math.max(last, next.when);
|
|
@@ -198,9 +198,14 @@ async function move(ctx, m, to, actor, fields) {
|
|
|
198
198
|
await ctx.write('Message', String(m.id), { ...fields, state: to }, `message.${to.toLowerCase()}`);
|
|
199
199
|
m.state = to;
|
|
200
200
|
}
|
|
201
|
-
/** One delivery attempt of a message, at the moment it is due.
|
|
202
|
-
|
|
201
|
+
/** One delivery attempt of a message, at the moment it is due. Its signature is issued when it is sent — the World
|
|
202
|
+
* clock as the post leaves (`live`), never before the due time: QStash signs a request when it sends it, and a
|
|
203
|
+
* delivery the World makes late (its clock moved past the due time) reaches an application already living at that
|
|
204
|
+
* instant, which refuses a token that expired while it waited (dub walk 4: every workflow step after a clock shift
|
|
205
|
+
* answered "signature verification failed"). A long catch-up on a running clock signs each post at its own instant. */
|
|
206
|
+
async function attempt(ctx, m, report, live) {
|
|
203
207
|
const when = nowMs(ctx);
|
|
208
|
+
const sentAt = Math.max(when, live());
|
|
204
209
|
const tried = Number(m._attempts ?? 0);
|
|
205
210
|
await move(ctx, m, 'ACTIVE', 'time', { _attempts: tried });
|
|
206
211
|
const published = (m._published ?? {});
|
|
@@ -214,11 +219,11 @@ async function attempt(ctx, m, report) {
|
|
|
214
219
|
const key = accountOf(ctx, String(m._owner ?? 'world')).current;
|
|
215
220
|
const headers = {
|
|
216
221
|
...base, 'upstash-message-id': String(m.messageId), 'upstash-retried': String(tried),
|
|
217
|
-
'upstash-signature': sign(body, url, Math.floor(
|
|
222
|
+
'upstash-signature': sign(body, url, Math.floor(sentAt / 1000), `jwt_${String(m.messageId)}_${tried}`, key),
|
|
218
223
|
};
|
|
219
224
|
const method = String(m.method ?? 'POST');
|
|
220
225
|
const outcome = stated(ctx, url, headers) ?? await post(url, method, headers, body);
|
|
221
|
-
await ctx.record(DELIVERY, { to: url, message_id: m.messageId, retried: tried, at: ctx.occurredAt, n: ctx.rowsRaw(DELIVERY).length, method, headers, body, status: outcome.status }, `delivery:${String(m.messageId)}:${tried}`);
|
|
226
|
+
await ctx.record(DELIVERY, { to: url, message_id: m.messageId, retried: tried, at: ctx.occurredAt, sent_at: iso(sentAt), n: ctx.rowsRaw(DELIVERY).length, method, headers, body, status: outcome.status }, `delivery:${String(m.messageId)}:${tried}`);
|
|
222
227
|
const n = tried + 1;
|
|
223
228
|
const response = { _response_status: outcome.status, _response_body: outcome.body, _response_headers: outcome.headers };
|
|
224
229
|
if (outcome.status >= 200 && outcome.status < 300) {
|
|
@@ -125,7 +125,7 @@ export type CatchUpReport = { delivered: Row[]; retried: Row[]; failed: Row[] };
|
|
|
125
125
|
|
|
126
126
|
type Due = { when: number; order: number; act: (at: SemanticsContext, report: CatchUpReport) => Promise<void> };
|
|
127
127
|
|
|
128
|
-
function nextDue(ctx: SemanticsContext, until: number): Due | undefined {
|
|
128
|
+
function nextDue(ctx: SemanticsContext, until: number, live: () => number): Due | undefined {
|
|
129
129
|
const due: Due[] = [];
|
|
130
130
|
for (const s of ctx.rowsRaw('Schedule')) {
|
|
131
131
|
if (typeof s.nextScheduleTime === 'number' && s.nextScheduleTime <= until) due.push({ when: s.nextScheduleTime, order: 0, act: (at) => fire(at, s) });
|
|
@@ -139,7 +139,7 @@ function nextDue(ctx: SemanticsContext, until: number): Due | undefined {
|
|
|
139
139
|
const when = Number(m._due);
|
|
140
140
|
if (when > until) continue;
|
|
141
141
|
if (m.state === 'CANCEL_REQUESTED') due.push({ when, order: 2 + Number(m._ordinal) / 1e9, act: (at) => cancelled(at, m) });
|
|
142
|
-
else if (eligible(ctx, m, all)) due.push({ when, order: 3 + Number(m._ordinal) / 1e9, act: (at, report) => attempt(at, m, report) });
|
|
142
|
+
else if (eligible(ctx, m, all)) due.push({ when, order: 3 + Number(m._ordinal) / 1e9, act: (at, report) => attempt(at, m, report, live) });
|
|
143
143
|
}
|
|
144
144
|
for (const resource of ['DLQMessage', 'WorkflowDLQMessage']) {
|
|
145
145
|
for (const e of ctx.rowsRaw(resource)) {
|
|
@@ -151,14 +151,14 @@ function nextDue(ctx: SemanticsContext, until: number): Due | undefined {
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
/** Run everything due by the World's instant, in order, each at its own moment. */
|
|
154
|
-
export async function catchUp(ctx: SemanticsContext): Promise<CatchUpReport> {
|
|
154
|
+
export async function catchUp(ctx: SemanticsContext, live: () => number = () => nowMs(ctx)): Promise<CatchUpReport> {
|
|
155
155
|
const until = nowMs(ctx);
|
|
156
156
|
const report: CatchUpReport = { delivered: [], retried: [], failed: [] };
|
|
157
157
|
// a message held behind another (the one before it in its queue or its run) goes once that one has left, so the World
|
|
158
158
|
// clock never runs back: each move happens at its own due time or at the last move's, whichever is later
|
|
159
159
|
let last = 0;
|
|
160
160
|
for (let n = 0; n < 5000; n += 1) {
|
|
161
|
-
const next = nextDue(ctx, until);
|
|
161
|
+
const next = nextDue(ctx, until, live);
|
|
162
162
|
if (!next) break;
|
|
163
163
|
last = Math.max(last, next.when);
|
|
164
164
|
await next.act(await ctx.at(iso(last)), report);
|
|
@@ -197,9 +197,14 @@ async function move(ctx: SemanticsContext, m: Row, to: string, actor: 'time' | '
|
|
|
197
197
|
m.state = to;
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
/** One delivery attempt of a message, at the moment it is due.
|
|
201
|
-
|
|
200
|
+
/** One delivery attempt of a message, at the moment it is due. Its signature is issued when it is sent — the World
|
|
201
|
+
* clock as the post leaves (`live`), never before the due time: QStash signs a request when it sends it, and a
|
|
202
|
+
* delivery the World makes late (its clock moved past the due time) reaches an application already living at that
|
|
203
|
+
* instant, which refuses a token that expired while it waited (dub walk 4: every workflow step after a clock shift
|
|
204
|
+
* answered "signature verification failed"). A long catch-up on a running clock signs each post at its own instant. */
|
|
205
|
+
async function attempt(ctx: SemanticsContext, m: Row, report: CatchUpReport, live: () => number): Promise<void> {
|
|
202
206
|
const when = nowMs(ctx);
|
|
207
|
+
const sentAt = Math.max(when, live());
|
|
203
208
|
const tried = Number(m._attempts ?? 0);
|
|
204
209
|
await move(ctx, m, 'ACTIVE', 'time', { _attempts: tried });
|
|
205
210
|
const published = (m._published ?? {}) as Record<string, string>;
|
|
@@ -213,11 +218,11 @@ async function attempt(ctx: SemanticsContext, m: Row, report: CatchUpReport): Pr
|
|
|
213
218
|
const key = accountOf(ctx, String(m._owner ?? 'world')).current;
|
|
214
219
|
const headers: Record<string, string> = {
|
|
215
220
|
...base, 'upstash-message-id': String(m.messageId), 'upstash-retried': String(tried),
|
|
216
|
-
'upstash-signature': sign(body, url, Math.floor(
|
|
221
|
+
'upstash-signature': sign(body, url, Math.floor(sentAt / 1000), `jwt_${String(m.messageId)}_${tried}`, key),
|
|
217
222
|
};
|
|
218
223
|
const method = String(m.method ?? 'POST');
|
|
219
224
|
const outcome = stated(ctx, url, headers) ?? await post(url, method, headers, body);
|
|
220
|
-
await ctx.record(DELIVERY, { to: url, message_id: m.messageId, retried: tried, at: ctx.occurredAt, n: ctx.rowsRaw(DELIVERY).length, method, headers, body, status: outcome.status }, `delivery:${String(m.messageId)}:${tried}`);
|
|
225
|
+
await ctx.record(DELIVERY, { to: url, message_id: m.messageId, retried: tried, at: ctx.occurredAt, sent_at: iso(sentAt), n: ctx.rowsRaw(DELIVERY).length, method, headers, body, status: outcome.status }, `delivery:${String(m.messageId)}:${tried}`);
|
|
221
226
|
const n = tried + 1;
|
|
222
227
|
const response = { _response_status: outcome.status, _response_body: outcome.body, _response_headers: outcome.headers };
|
|
223
228
|
if (outcome.status >= 200 && outcome.status < 300) {
|
|
@@ -1,140 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
export declare
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
set(k: LuaValue, v: LuaValue): void;
|
|
10
|
-
/** A non-nil value at a non-nil key. */
|
|
11
|
-
private store;
|
|
12
|
-
/** `#t` — the border of the array part: the largest n where t[1..n] are all non-nil. */
|
|
13
|
-
get length(): number;
|
|
14
|
-
static fromArray(values: LuaValue[]): LuaTable;
|
|
15
|
-
toArray(): LuaValue[];
|
|
16
|
-
}
|
|
17
|
-
export type LuaFunction = (args: LuaValue[]) => LuaValue[];
|
|
18
|
-
/** `null` stands for Lua `nil` (JS `undefined` is never used as a value in this interpreter). */
|
|
19
|
-
export type LuaValue = null | boolean | number | string | LuaTable | LuaFunction;
|
|
20
|
-
type TokKind = 'name' | 'number' | 'string' | 'op' | 'keyword' | 'eof';
|
|
21
|
-
type Token = {
|
|
22
|
-
kind: TokKind;
|
|
23
|
-
value: string;
|
|
24
|
-
line: number;
|
|
25
|
-
};
|
|
26
|
-
export declare function tokenize(src: string): Token[];
|
|
27
|
-
type Expr = {
|
|
28
|
-
t: 'nil';
|
|
29
|
-
} | {
|
|
30
|
-
t: 'true';
|
|
31
|
-
} | {
|
|
32
|
-
t: 'false';
|
|
33
|
-
} | {
|
|
34
|
-
t: 'num';
|
|
35
|
-
v: number;
|
|
36
|
-
} | {
|
|
37
|
-
t: 'str';
|
|
38
|
-
v: string;
|
|
39
|
-
} | {
|
|
40
|
-
t: 'name';
|
|
41
|
-
v: string;
|
|
42
|
-
} | {
|
|
43
|
-
t: 'index';
|
|
44
|
-
obj: Expr;
|
|
45
|
-
key: Expr;
|
|
46
|
-
} | {
|
|
47
|
-
t: 'call';
|
|
48
|
-
fn: Expr;
|
|
49
|
-
args: Expr[];
|
|
50
|
-
line?: number;
|
|
51
|
-
} | {
|
|
52
|
-
t: 'bin';
|
|
53
|
-
op: string;
|
|
54
|
-
l: Expr;
|
|
55
|
-
r: Expr;
|
|
56
|
-
} | {
|
|
57
|
-
t: 'un';
|
|
58
|
-
op: string;
|
|
59
|
-
e: Expr;
|
|
60
|
-
} | {
|
|
61
|
-
t: 'table';
|
|
62
|
-
array: Expr[];
|
|
63
|
-
pairs: Array<{
|
|
64
|
-
k: Expr;
|
|
65
|
-
v: Expr;
|
|
66
|
-
}>;
|
|
67
|
-
} | {
|
|
68
|
-
t: 'func';
|
|
69
|
-
params: string[];
|
|
70
|
-
body: Stat[];
|
|
71
|
-
};
|
|
72
|
-
type Stat = {
|
|
73
|
-
t: 'local';
|
|
74
|
-
names: string[];
|
|
75
|
-
exprs: Expr[];
|
|
76
|
-
} | {
|
|
77
|
-
t: 'assign';
|
|
78
|
-
targets: Expr[];
|
|
79
|
-
exprs: Expr[];
|
|
80
|
-
} | {
|
|
81
|
-
t: 'callstat';
|
|
82
|
-
call: Expr;
|
|
83
|
-
} | {
|
|
84
|
-
t: 'if';
|
|
85
|
-
branches: Array<{
|
|
86
|
-
cond: Expr;
|
|
87
|
-
body: Stat[];
|
|
88
|
-
}>;
|
|
89
|
-
orelse: Stat[] | null;
|
|
90
|
-
} | {
|
|
91
|
-
t: 'while';
|
|
92
|
-
cond: Expr;
|
|
93
|
-
body: Stat[];
|
|
94
|
-
} | {
|
|
95
|
-
t: 'repeat';
|
|
96
|
-
body: Stat[];
|
|
97
|
-
cond: Expr;
|
|
98
|
-
} | {
|
|
99
|
-
t: 'fornum';
|
|
100
|
-
name: string;
|
|
101
|
-
from: Expr;
|
|
102
|
-
to: Expr;
|
|
103
|
-
step: Expr | null;
|
|
104
|
-
body: Stat[];
|
|
105
|
-
} | {
|
|
106
|
-
t: 'forin';
|
|
107
|
-
names: string[];
|
|
108
|
-
exprs: Expr[];
|
|
109
|
-
body: Stat[];
|
|
110
|
-
} | {
|
|
111
|
-
t: 'do';
|
|
112
|
-
body: Stat[];
|
|
113
|
-
} | {
|
|
114
|
-
t: 'return';
|
|
115
|
-
exprs: Expr[];
|
|
116
|
-
} | {
|
|
117
|
-
t: 'break';
|
|
118
|
-
};
|
|
119
|
-
/** Parse Lua source into a chunk. Exported so a test can prove a bad script fails at PARSE time. */
|
|
120
|
-
export declare function parseLua(src: string): Stat[];
|
|
121
|
-
export declare function luaTruthy(v: LuaValue): boolean;
|
|
122
|
-
export declare function luaTypeName(v: LuaValue): string;
|
|
123
|
-
/** Lua 5.1's `%.14g` number formatting — `3` prints as "3", not "3.0". */
|
|
124
|
-
export declare function numberToLuaString(n: number): string;
|
|
125
|
-
/** `tonumber`, exactly: a number passes through, a numeric string converts, anything else is nil. */
|
|
126
|
-
export declare function luaToNumber(v: LuaValue, base?: number): LuaValue;
|
|
127
|
-
/** What the host must supply: a synchronous `redis.call`, plus KEYS/ARGV. */
|
|
128
|
-
export type LuaHost = {
|
|
129
|
-
/** Run one Redis command, called from the script's line `line`. MUST throw to abort the script the way `redis.call` does. */
|
|
130
|
-
call: (args: string[], line: number) => LuaValue;
|
|
131
|
-
keys: string[];
|
|
132
|
-
argv: string[];
|
|
133
|
-
};
|
|
134
|
-
/** Lua `==`: same type required (no string/number coercion); tables compare by identity. */
|
|
135
|
-
export declare function luaEquals(l: LuaValue, r: LuaValue): boolean;
|
|
136
|
-
/** Parse + run a script. Returns the Lua value the chunk returned (or `nil`). */
|
|
137
|
-
export declare function runLua(script: string, host: LuaHost): LuaValue;
|
|
138
|
-
/** The SHA1 real Redis keys the script cache by — the same hex digest `SCRIPT LOAD` returns. */
|
|
139
|
-
export declare function luaScriptSha1(script: string): string;
|
|
140
|
-
export {};
|
|
1
|
+
import { type LuaHost, type LuaValue } from '@volter/world-core/redis';
|
|
2
|
+
export { LuaError, LuaTable, luaEquals, luaScriptSha1, luaToNumber, luaTruthy, luaTypeName, numberToLuaString, parseLua, tokenize, } from '@volter/world-core/redis';
|
|
3
|
+
export type { LuaFunction, LuaHost, LuaValue } from '@volter/world-core/redis';
|
|
4
|
+
type Globals = Parameters<NonNullable<LuaHost['extend']>>[0];
|
|
5
|
+
/** The command API under the names Redis's scripts call it by. */
|
|
6
|
+
export declare function nameCommandApi(g: Globals): void;
|
|
7
|
+
/** Parse + run a script with the command API named: the kernel's `runLua`. */
|
|
8
|
+
export declare function runLua(script: string, host: Omit<LuaHost, 'numberArg'> & Partial<Pick<LuaHost, 'numberArg'>>): LuaValue;
|