mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-04 12:07:09 +00:00
feat(web-runtime): named() and cross-extractor read guard
This commit is contained in:
1 parent
159bcb6dce
commit
fe86e91aff
1 file changed
+44
-8
@@ -20,18 +20,34 @@ import { installRuntime } from "./runtime-entry.ts";
|
|||||||
import type { BuiltinVerb, Candidate, Host } from "./action-tree.ts";
|
import type { BuiltinVerb, Candidate, Host } from "./action-tree.ts";
|
||||||
|
|
||||||
interface Handle {
|
interface Handle {
|
||||||
current: unknown;
|
readonly current: unknown;
|
||||||
previous: unknown;
|
readonly previous: unknown;
|
||||||
|
named(name: string): Handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExtractorEntry {
|
interface ExtractorEntry {
|
||||||
getter: (state: unknown) => unknown;
|
getter: (state: unknown) => unknown;
|
||||||
handle: Handle;
|
handle: Handle;
|
||||||
name: string;
|
name: string;
|
||||||
|
currentValue: unknown;
|
||||||
|
previousValue: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
const extractors: ExtractorEntry[] = [];
|
const extractors: ExtractorEntry[] = [];
|
||||||
|
|
||||||
|
// extracting is true only while an extractor getter is running. The current/
|
||||||
|
// previous accessors consult it so a getter that reaches into another
|
||||||
|
// extractor's handle throws instead of reading a stale cross-extractor value.
|
||||||
|
let extracting = false;
|
||||||
|
|
||||||
|
function checkNotExtracting(slot: "current" | "previous"): void {
|
||||||
|
if (extracting) {
|
||||||
|
throw new Error(
|
||||||
|
`reading .${slot} of an extractor inside another extractor is not allowed; extractor getters may read only from the state argument`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SANDERLING_SEED is the host-computed 64-bit seed, injected as a decimal
|
// SANDERLING_SEED is the host-computed 64-bit seed, injected as a decimal
|
||||||
// string via the bundle define. We parse it into a BigInt without ever going
|
// string via the bundle define. We parse it into a BigInt without ever going
|
||||||
// through a JS Number (which loses precision above 2^53), matching the goja
|
// through a JS Number (which loses precision above 2^53), matching the goja
|
||||||
@@ -315,13 +331,30 @@ function buildState(): unknown {
|
|||||||
|
|
||||||
const runtime = {
|
const runtime = {
|
||||||
extract<T>(getter: (state: unknown) => T, name?: string): Handle {
|
extract<T>(getter: (state: unknown) => T, name?: string): Handle {
|
||||||
const handle: Handle = { current: undefined, previous: undefined };
|
|
||||||
const resolvedName = name && name.length > 0 ? name : `extractor_${extractors.length}`;
|
const resolvedName = name && name.length > 0 ? name : `extractor_${extractors.length}`;
|
||||||
extractors.push({
|
const entry: ExtractorEntry = {
|
||||||
getter: getter as (s: unknown) => unknown,
|
getter: getter as (s: unknown) => unknown,
|
||||||
handle,
|
handle: undefined as unknown as Handle,
|
||||||
name: resolvedName,
|
name: resolvedName,
|
||||||
});
|
currentValue: undefined,
|
||||||
|
previousValue: undefined,
|
||||||
|
};
|
||||||
|
const handle: Handle = {
|
||||||
|
get current() {
|
||||||
|
checkNotExtracting("current");
|
||||||
|
return entry.currentValue;
|
||||||
|
},
|
||||||
|
get previous() {
|
||||||
|
checkNotExtracting("previous");
|
||||||
|
return entry.previousValue;
|
||||||
|
},
|
||||||
|
named(name: string): Handle {
|
||||||
|
entry.name = name;
|
||||||
|
return handle;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
entry.handle = handle;
|
||||||
|
extractors.push(entry);
|
||||||
return handle;
|
return handle;
|
||||||
},
|
},
|
||||||
always: noopFormula,
|
always: noopFormula,
|
||||||
@@ -350,14 +383,17 @@ function evaluateExtractors(): Record<number, unknown> {
|
|||||||
for (let i = 0; i < extractors.length; i++) {
|
for (let i = 0; i < extractors.length; i++) {
|
||||||
const entry = extractors[i];
|
const entry = extractors[i];
|
||||||
if (!entry) continue;
|
if (!entry) continue;
|
||||||
entry.handle.previous = entry.handle.current;
|
entry.previousValue = entry.currentValue;
|
||||||
let value: unknown;
|
let value: unknown;
|
||||||
|
extracting = true;
|
||||||
try {
|
try {
|
||||||
value = entry.getter(state);
|
value = entry.getter(state);
|
||||||
} catch {
|
} catch {
|
||||||
value = undefined;
|
value = undefined;
|
||||||
|
} finally {
|
||||||
|
extracting = false;
|
||||||
}
|
}
|
||||||
entry.handle.current = value;
|
entry.currentValue = value;
|
||||||
result[i] = sanitize(value);
|
result[i] = sanitize(value);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
Reference in new issue
Block a user