mirror of
https://github.com/morluto/rea
synced 2026-10-27 14:42:18 -04:00
refactor(javascript): group domain semantics and contracts (#907)
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
import { compareCodePoints } from "../canonicalOrdering.js";
|
||||
import type { ApplicationNode } from "./javascriptApplicationGraph.js";
|
||||
import {
|
||||
featureSeedMatchMode,
|
||||
type ApplicationFeatureSeed,
|
||||
} from "./javascriptFeatureTraceSchemas.js";
|
||||
import type { JsonValue } from "../jsonValue.js";
|
||||
|
||||
/** One explicit field that matched a feature seed. */
|
||||
export interface ApplicationFeatureSeedMatch {
|
||||
readonly node_id: string;
|
||||
readonly kind: ApplicationNode["kind"];
|
||||
readonly basis: "node-id" | "label" | "identity" | "property";
|
||||
readonly field: string;
|
||||
}
|
||||
|
||||
interface CandidateField {
|
||||
readonly basis: ApplicationFeatureSeedMatch["basis"];
|
||||
readonly field: string;
|
||||
/** Structural field path, retaining literal keys but omitting array indexes. */
|
||||
readonly selectionPath?: string;
|
||||
readonly value: string;
|
||||
}
|
||||
|
||||
/** Locate every graph node matching a typed literal seed without regex execution. */
|
||||
export const findApplicationFeatureSeeds = (
|
||||
nodes: readonly ApplicationNode[],
|
||||
seed: ApplicationFeatureSeed,
|
||||
): ApplicationFeatureSeedMatch[] =>
|
||||
nodes
|
||||
.flatMap((node) => seedMatch(node, seed))
|
||||
.sort((left, right) =>
|
||||
compareCodePoints(
|
||||
`${left.node_id}\0${left.basis}\0${left.field}`,
|
||||
`${right.node_id}\0${right.basis}\0${right.field}`,
|
||||
),
|
||||
);
|
||||
|
||||
const seedMatch = (
|
||||
node: ApplicationNode,
|
||||
seed: ApplicationFeatureSeed,
|
||||
): ApplicationFeatureSeedMatch[] => {
|
||||
if (seed.kind === "node-id")
|
||||
return node.node_id === seed.value
|
||||
? [
|
||||
{
|
||||
node_id: node.node_id,
|
||||
kind: node.kind,
|
||||
basis: "node-id",
|
||||
field: "node_id",
|
||||
},
|
||||
]
|
||||
: [];
|
||||
if (!kindMatches(node, seed.kind)) return [];
|
||||
const mode = featureSeedMatchMode(seed);
|
||||
const query = normalized(seed.value, seed.case_sensitive);
|
||||
const match = candidateFields(node, seed.kind).find((candidate) => {
|
||||
const value = normalized(candidate.value, seed.case_sensitive);
|
||||
return mode === "exact" ? value === query : value.includes(query);
|
||||
});
|
||||
return match === undefined
|
||||
? []
|
||||
: [
|
||||
{
|
||||
node_id: node.node_id,
|
||||
kind: node.kind,
|
||||
basis: match.basis,
|
||||
field: match.field,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const kindMatches = (
|
||||
node: ApplicationNode,
|
||||
kind: Exclude<ApplicationFeatureSeed["kind"], "node-id">,
|
||||
): boolean => {
|
||||
if (kind === "string") return true;
|
||||
if (kind === "route")
|
||||
return (
|
||||
node.kind === "endpoint" &&
|
||||
node.observations.some(
|
||||
({ properties }) => properties.endpoint_kind === "route",
|
||||
)
|
||||
);
|
||||
if (kind === "api") return node.kind === "context-bridge-api";
|
||||
if (kind === "channel") return node.kind === "ipc-channel";
|
||||
if (kind === "module")
|
||||
return ["javascript-asset", "javascript-module", "source-module"].includes(
|
||||
node.kind,
|
||||
);
|
||||
return node.kind === "native-export";
|
||||
};
|
||||
|
||||
const candidateFields = (
|
||||
node: ApplicationNode,
|
||||
seedKind: Exclude<ApplicationFeatureSeed["kind"], "node-id">,
|
||||
): CandidateField[] => {
|
||||
const fields: CandidateField[] = [
|
||||
...identityFields(node),
|
||||
...node.observations.flatMap((observation, index) => [
|
||||
...(observation.label === null
|
||||
? []
|
||||
: [
|
||||
{
|
||||
basis: "label" as const,
|
||||
field: `observations[${String(index)}].label`,
|
||||
value: observation.label,
|
||||
},
|
||||
]),
|
||||
...propertyFields(
|
||||
observation.properties,
|
||||
`observations[${String(index)}].properties`,
|
||||
),
|
||||
]),
|
||||
];
|
||||
if (seedKind === "string") return fields;
|
||||
const names = relevantFields[seedKind];
|
||||
return fields.filter(({ field, selectionPath = field }) =>
|
||||
names.some((name) => {
|
||||
const path = memberFieldSuffixes.has(name) ? selectionPath : field;
|
||||
return path === "identity" || path.endsWith(name);
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const memberFieldSuffixes = new Set([
|
||||
".members",
|
||||
".methods",
|
||||
".exports",
|
||||
".requested_members",
|
||||
]);
|
||||
|
||||
const relevantFields = {
|
||||
route: [".label", ".value", ".path"],
|
||||
api: [".label", ".api_name", ".api_key", ".key", ".methods", ".members"],
|
||||
channel: [".label", ".channel", ".key"],
|
||||
module: [
|
||||
".label",
|
||||
".module_key",
|
||||
".path",
|
||||
".source",
|
||||
".original_source",
|
||||
".exports",
|
||||
],
|
||||
"native-export": [".label", ".key", ".requested_members", ".members"],
|
||||
} as const;
|
||||
|
||||
const identityFields = (node: ApplicationNode): CandidateField[] => {
|
||||
const identity = node.identity;
|
||||
if (identity.strategy === "content-digest")
|
||||
return [{ basis: "identity", field: "identity", value: identity.sha256 }];
|
||||
if (identity.strategy === "source-map-original")
|
||||
return [
|
||||
{
|
||||
basis: "identity",
|
||||
field: "identity.original_source",
|
||||
value: identity.original_source,
|
||||
},
|
||||
];
|
||||
if (identity.strategy === "canonical-path")
|
||||
return [
|
||||
{ basis: "identity", field: "identity.path", value: identity.path },
|
||||
];
|
||||
if (identity.strategy === "artifact-local-key")
|
||||
return [{ basis: "identity", field: "identity.key", value: identity.key }];
|
||||
if (identity.strategy === "runtime-instance")
|
||||
return [
|
||||
{
|
||||
basis: "identity",
|
||||
field: "identity.runtime_key",
|
||||
value: identity.runtime_key,
|
||||
},
|
||||
];
|
||||
return [];
|
||||
};
|
||||
|
||||
const propertyFields = (
|
||||
value: Readonly<Record<string, JsonValue>>,
|
||||
prefix: string,
|
||||
): CandidateField[] => {
|
||||
const output: CandidateField[] = [];
|
||||
const pending: {
|
||||
readonly current: JsonValue;
|
||||
readonly path: string;
|
||||
readonly selectionPath: string;
|
||||
}[] = [{ current: value, path: prefix, selectionPath: prefix }];
|
||||
while (pending.length > 0) {
|
||||
const next = pending.pop();
|
||||
if (next === undefined) continue;
|
||||
const { current, path, selectionPath } = next;
|
||||
if (typeof current === "string") {
|
||||
output.push({
|
||||
basis: "property",
|
||||
field: path,
|
||||
selectionPath,
|
||||
value: current,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (Array.isArray(current)) {
|
||||
for (let index = current.length - 1; index >= 0; index -= 1) {
|
||||
const item = current[index];
|
||||
if (item !== undefined)
|
||||
pending.push({
|
||||
current: item,
|
||||
path: `${path}[${String(index)}]`,
|
||||
selectionPath,
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (current !== null && typeof current === "object") {
|
||||
for (const key of Object.keys(current).sort(compareCodePoints).reverse())
|
||||
pending.push({
|
||||
current: current[key] ?? null,
|
||||
path: `${path}.${key}`,
|
||||
selectionPath: `${selectionPath}.${key}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
const normalized = (value: string, caseSensitive: boolean): string => {
|
||||
const normalizedValue = value.normalize("NFKC");
|
||||
return caseSensitive
|
||||
? normalizedValue
|
||||
: normalizedValue.toLocaleLowerCase("en-US");
|
||||
};
|
||||
Reference in New Issue
Block a user