mirror of
https://github.com/morluto/rea.git
synced 2026-10-30 11:25:39 +00:00
feat(javascript): expose bounded semantic tracing
This commit is contained in:
1 parent
8678b57b5e
commit
508a9ccde9
56 files changed
+2455
-307
No files matched your search
@@ -0,0 +1,137 @@
|
||||
import { createHash } from "node:crypto";
|
||||
|
||||
import canonicalize from "canonicalize";
|
||||
|
||||
import { compareCodePoints } from "./javascriptApplicationGraph.js";
|
||||
import type {
|
||||
JavaScriptSemanticGraph,
|
||||
JavaScriptSemanticGraphNode,
|
||||
} from "./javascriptSemanticGraph.js";
|
||||
import type { JavaScriptSemanticQueryInput } from "./javascriptSemanticQuerySchemas.js";
|
||||
|
||||
/** Caller cursor that cannot identify a page of the current semantic query. */
|
||||
export class JavaScriptSemanticQueryCursorError extends TypeError {}
|
||||
|
||||
/** Canonical JSON used by semantic query commitments and ordering. */
|
||||
export const canonicalJavaScriptSemanticQueryJson = (
|
||||
value: unknown,
|
||||
): string => {
|
||||
const encoded = canonicalize(value);
|
||||
if (encoded === undefined)
|
||||
throw new TypeError(
|
||||
"JavaScript semantic query could not canonicalize data",
|
||||
);
|
||||
return encoded;
|
||||
};
|
||||
|
||||
const digest = (value: unknown): string =>
|
||||
createHash("sha256")
|
||||
.update(canonicalJavaScriptSemanticQueryJson(value))
|
||||
.digest("hex");
|
||||
|
||||
const uniqueSorted = (values: readonly string[]): string[] =>
|
||||
[...new Set(values)].sort(compareCodePoints);
|
||||
|
||||
/** Derive the stable identity for one graph-bound query without its cursor. */
|
||||
export const javaScriptSemanticQueryIdentifier = (
|
||||
graph: JavaScriptSemanticGraph,
|
||||
input: JavaScriptSemanticQueryInput,
|
||||
): string => {
|
||||
const { cursor: _cursor, ...semantic } = input;
|
||||
return `jsrq_${digest({
|
||||
source_graph_id: graph.graph_id,
|
||||
...semantic,
|
||||
allowed_relations:
|
||||
semantic.allowed_relations === undefined
|
||||
? undefined
|
||||
: uniqueSorted(semantic.allowed_relations),
|
||||
expected:
|
||||
semantic.expected === null
|
||||
? null
|
||||
: {
|
||||
...semantic.expected,
|
||||
classes: uniqueSorted(semantic.expected.classes),
|
||||
},
|
||||
})}`;
|
||||
};
|
||||
|
||||
/** Commit one deterministic relation-page offset to a query identity. */
|
||||
export const createJavaScriptSemanticQueryCursor = (
|
||||
queryId: string,
|
||||
offset: number,
|
||||
): string => `jsrqc_${String(offset)}_${digest({ query_id: queryId, offset })}`;
|
||||
|
||||
/** Parse a query-bound cursor and reject stale, malformed, or foreign values. */
|
||||
export const parseJavaScriptSemanticQueryCursor = (
|
||||
cursor: string | null,
|
||||
queryId: string,
|
||||
): number => {
|
||||
if (cursor === null) return 0;
|
||||
const match = /^jsrqc_([0-9]+)_([a-f0-9]{64})$/u.exec(cursor);
|
||||
const offsetText = match?.[1];
|
||||
const commitment = match?.[2];
|
||||
if (offsetText === undefined || commitment === undefined)
|
||||
throw new JavaScriptSemanticQueryCursorError(
|
||||
"Semantic query cursor is malformed",
|
||||
);
|
||||
const offset = Number(offsetText);
|
||||
if (
|
||||
!Number.isSafeInteger(offset) ||
|
||||
offset < 0 ||
|
||||
commitment !== digest({ query_id: queryId, offset })
|
||||
)
|
||||
throw new JavaScriptSemanticQueryCursorError(
|
||||
"Semantic query cursor does not match this query",
|
||||
);
|
||||
return offset;
|
||||
};
|
||||
|
||||
/** Resolve deterministic seed candidates without applying traversal limits. */
|
||||
export const resolveJavaScriptSemanticQuerySeeds = (
|
||||
graph: JavaScriptSemanticGraph,
|
||||
input: JavaScriptSemanticQueryInput,
|
||||
): string[] => {
|
||||
const fingerprintFunctions = new Set(
|
||||
input.seed.kind === "function"
|
||||
? graph.fingerprints
|
||||
.filter(
|
||||
({ fingerprint_sha256 }) =>
|
||||
input.seed.kind === "function" &&
|
||||
fingerprint_sha256 === input.seed.fingerprint_sha256,
|
||||
)
|
||||
.map(({ function_node_id }) => function_node_id)
|
||||
: [],
|
||||
);
|
||||
return graph.nodes
|
||||
.filter((node) => seedMatches(node, input, fingerprintFunctions))
|
||||
.map(({ node_id }) => node_id)
|
||||
.sort(compareCodePoints);
|
||||
};
|
||||
|
||||
const seedMatches = (
|
||||
node: JavaScriptSemanticGraphNode,
|
||||
input: JavaScriptSemanticQueryInput,
|
||||
fingerprintFunctions: ReadonlySet<string>,
|
||||
): boolean => {
|
||||
const seed = input.seed;
|
||||
if (seed.kind === "semantic-node") return node.node_id === seed.node_id;
|
||||
if (seed.kind === "application-node")
|
||||
return node.application_node_ids.includes(seed.node_id);
|
||||
if (seed.kind === "function") return fingerprintFunctions.has(node.node_id);
|
||||
if (seed.kind === "literal")
|
||||
return (
|
||||
node.kind === "literal" &&
|
||||
canonicalJavaScriptSemanticQueryJson(node.properties.value) ===
|
||||
canonicalJavaScriptSemanticQueryJson(seed.value)
|
||||
);
|
||||
if (seed.kind === "property")
|
||||
return node.kind === "property-slot" && node.properties.name === seed.name;
|
||||
if (seed.kind === "endpoint")
|
||||
return node.kind === "request" && node.properties.endpoint === seed.value;
|
||||
if (seed.kind === "event")
|
||||
return (
|
||||
["event", "listener"].includes(node.kind) &&
|
||||
node.properties.event_name === seed.name
|
||||
);
|
||||
return node.kind === "boundary" && node.properties.field === seed.field;
|
||||
};
|
||||
Reference in new issue
Block a user