Skip to content

Commit d620b41

Browse files
feat(stdlib): add jsonField extern for minimal JSON field extraction (#211)
Adds `pub extern fn jsonField(json, key) -> String` to stdlib/Vscode.affine + runtime impl in packages/affine-vscode/mod.js. The wasm guest has no JSON parser and only stringConcat/stringEndsWith/ stringIsEmpty, so it cannot decode the payloads returned by thenableResultJson / httpPostJson. jsonField reads one top-level field (scalar coerced to string; object/array re-serialised), mirroring thenableResultJson's synchronous reg(string) shape with "" as the absent/parse-fail sentinel. Deliberately minimal — enough for result objects like { tier, tier_code, score } and the { __error } reject shape; nested/array access is out of scope by design. Surfaced as a real gap by the first #205 consumer (rsr-certifier extension rewire, PR-5d-B); unblocks every future #205 consumer. Full dune gate green (257/257), zero regression. Refs #199 #205 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 0a4692a commit d620b41

2 files changed

Lines changed: 37 additions & 0 deletions

File tree

packages/affine-vscode/mod.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,27 @@ module.exports = function makeVscodeBindings(vscode, lcModule, hostShim) {
384384
: Promise.reject(new Error("fetch unavailable"));
385385
return reg(doFetch.catch((err) => ({ __error: String(err) })));
386386
},
387+
388+
// `jsonField(json, key)` — minimal one-level JSON field read for
389+
// guests with no JSON parser. Mirrors thenableResultJson's
390+
// synchronous reg(string) shape; "" on parse failure / non-object /
391+
// missing key (the guest treats "" as absent). Scalars are coerced
392+
// to their string form; objects/arrays are re-serialised so the
393+
// guest can at least detect presence / pass them on.
394+
jsonField: (jsonPtr, keyPtr) => {
395+
const raw = readString(jsonPtr);
396+
const key = readString(keyPtr);
397+
try {
398+
const obj = JSON.parse(raw);
399+
if (obj === null || typeof obj !== "object") return reg("");
400+
if (!(key in obj)) return reg("");
401+
const v = obj[key];
402+
if (v === null || v === undefined) return reg("");
403+
return reg(typeof v === "object" ? JSON.stringify(v) : String(v));
404+
} catch (_e) {
405+
return reg("");
406+
}
407+
},
387408
};
388409

389410
const VscodeLanguageClient = {

stdlib/Vscode.affine

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,3 +336,19 @@ pub extern fn thenableResultJson(t: Thenable) -> String;
336336
/// Resolves with the JSON-decoded response body. Use thenableThen /
337337
/// thenableResultJson to read the settled value from a wasm guest.
338338
pub extern fn httpPostJson(url: String, body_json: String) -> Thenable / Async;
339+
340+
// ── Minimal JSON field extraction ────────────────────────────────────
341+
//
342+
// The guest has no JSON parser and only stringConcat/stringEndsWith/
343+
// stringIsEmpty for strings, so it cannot decode the payloads returned
344+
// by thenableResultJson / httpPostJson. This synchronous String->String
345+
// extern reads one top-level field, mirroring the thenableResultJson
346+
// shape (no Async; "" sentinel). It deliberately stays minimal (one
347+
// top-level key, scalar coerced to its string form) — enough for result
348+
// objects like { tier, tier_code, score } and the { __error } reject
349+
// shape; nested/array access is out of scope by design.
350+
351+
/// The top-level `key` of JSON object `json`, coerced to its string
352+
/// form (numbers/bools stringified). Empty string if `json` is not an
353+
/// object, does not parse, or has no such key.
354+
pub extern fn jsonField(json: String, key: String) -> String;

0 commit comments

Comments
 (0)