Skip to content

Commit b265ab4

Browse files
fatal10110claude
andauthored
feat(setresp): notify host on redis.setresp() via onSetResp hook (#24)
The WASM encoder owns the RESP-mode flip (g_resp_version drives reply encoding and cannot be host-set). To let the host mirror the protocol when choosing reply shapes for redisCall/redisPcall, l_redis_setresp now calls a new host_redis_setresp(version) import after flipping. The engine exposes this as an optional RedisHost.onSetResp(version) hook; absent, it is a no-op so existing hosts are unaffected. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 837bdcd commit b265ab4

6 files changed

Lines changed: 46 additions & 0 deletions

File tree

docs/host-interface.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export type RedisHost = {
1717
redisCall: RedisCallHandler;
1818
redisPcall: RedisCallHandler;
1919
log: RedisLogHandler;
20+
onSetResp?: (version: 2 | 3) => void;
2021
};
2122
```
2223

@@ -38,6 +39,14 @@ sets, big numbers, and verbatim strings. Push replies are not representable.
3839
- `level` is the numeric Redis log level.
3940
- `message` is a binary-safe `Buffer`.
4041

42+
### onSetResp
43+
- Optional. Invoked when the script calls `redis.setresp(n)` with the new
44+
version (`2` or `3`).
45+
- The WASM encoder flips its own RESP mode regardless; this hook only lets the
46+
host mirror the protocol when choosing reply shapes for
47+
`redisCall`/`redisPcall`.
48+
- Fires after validation, so it only ever receives `2` or `3`.
49+
4150
## ReplyValue
4251

4352
```ts

src/engine.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,7 @@ type MutableHandlers = {
452452
call: (...args: number[]) => bigint | void;
453453
pcall: (...args: number[]) => bigint | void;
454454
props: (...args: number[]) => bigint | void;
455+
setresp: (version: number) => void;
455456
};
456457

457458
/**
@@ -607,6 +608,10 @@ export class LuaWasmModule {
607608
host.log(level, msg);
608609
};
609610

611+
this.handlers.setresp = (version: number): void => {
612+
host.onSetResp?.call(host, version as 2 | 3);
613+
};
614+
610615
this.handlers.sha1hex = (...args: number[]): bigint | void => {
611616
const abiArgs = parseAbiArgs(args);
612617
const data = readBytes(exports.HEAPU8, abiArgs.ptr, abiArgs.len);
@@ -698,6 +703,7 @@ export async function load(options: LoadOptions = {}): Promise<LuaWasmModule> {
698703
call: () => BigInt(0),
699704
pcall: () => BigInt(0),
700705
props: () => BigInt(0),
706+
setresp: () => {},
701707
};
702708

703709
// Create wrapper imports that delegate to mutable handlers
@@ -709,6 +715,7 @@ export async function load(options: LoadOptions = {}): Promise<LuaWasmModule> {
709715
host_redis_call: (...args: number[]) => handlers.call(...args),
710716
host_redis_pcall: (...args: number[]) => handlers.pcall(...args),
711717
host_redis_props: (...args: number[]) => handlers.props(...args),
718+
host_redis_setresp: (version: number) => handlers.setresp(version),
712719
};
713720

714721
const { exports } = await loadModule(options, hostImports);

src/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,13 @@ export type RedisHost = {
182182

183183
/** Handler for redis.log() messages. */
184184
log: RedisLogHandler;
185+
186+
/**
187+
* Optional: notified when the script calls `redis.setresp(n)`. The WASM
188+
* encoder still flips its own RESP mode; this hook lets the host match the
189+
* reply shapes it returns from `redisCall`/`redisPcall` to the new protocol.
190+
*/
191+
onSetResp?: (version: 2 | 3) => void;
185192
};
186193

187194
/**

test/engine.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,27 @@ test("redis.setresp: rejects unsupported protocol versions", async () => {
460460
assert.equal(result.meta?.line, 1);
461461
});
462462

463+
test("redis.setresp: notifies the host of the new protocol version", async () => {
464+
await resolveWasmPath();
465+
const module = await load();
466+
const seen: number[] = [];
467+
const engine = module.create(createTestHost({
468+
onSetResp: (version) => seen.push(version),
469+
}));
470+
471+
engine.eval("redis.setresp(3); redis.setresp(2)");
472+
assert.deepEqual(seen, [3, 2]);
473+
});
474+
475+
test("redis.setresp: missing onSetResp host hook is a no-op", async () => {
476+
await resolveWasmPath();
477+
const module = await load();
478+
const engine = module.create(createTestHost());
479+
480+
// No onSetResp provided; eval must not throw.
481+
assert.equal(engine.eval("redis.setresp(3); return true"), true);
482+
});
483+
463484
test("redis.setresp: decodes RESP3 host replies for redis.call", async () => {
464485
await resolveWasmPath();
465486
const module = await load();

wasm/include/abi.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ typedef struct PtrLen {
5252
PtrLen host_redis_call(uint32_t ptr, uint32_t len);
5353
PtrLen host_redis_pcall(uint32_t ptr, uint32_t len);
5454
void host_redis_log(uint32_t level, uint32_t ptr, uint32_t len);
55+
void host_redis_setresp(uint32_t version);
5556
PtrLen host_sha1hex(uint32_t ptr, uint32_t len);
5657
PtrLen host_redis_props(void);
5758

wasm/src/redis_api.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,7 @@ static int l_redis_setresp(lua_State *L) {
401401
return luaL_error(L, "ERR RESP version must be 2 or 3.");
402402
}
403403
g_resp_version = next;
404+
host_redis_setresp(next); /* notify host so it can match reply shapes */
404405
return 0;
405406
}
406407

0 commit comments

Comments
 (0)