feat(resolution): synthesize object-literal registry dispatch edges

Adds `objectRegistryEdges` — a dynamic-dispatch synthesizer for the command/handler
registry pattern: an object literal maps string keys → handler classes/functions, then
dispatches by a RUNTIME key static parsing can't follow:

    this.commands = { [Cmd.ADD]: AddObjectCommand, ... }    // registration
    new this.commands[command](args).execute()              // dynamic dispatch

It links each dispatching function → each registered handler's callable entry (a class's
execute/run/handle method — preferring the method chained at the dispatch site — or the
function value), like the gin-middleware-chain fan-out. Same-file registry+dispatch only.

Validated precise on 3 real repos (the discipline that caught redux-thunk's n=1 overfit):
EtherealEngine's CommandManager (64 edges, class registry → .execute), Prebid.js (7:
builder/consent/message dispatch, function registry), warp-drive (1). Zero false positives
after several precision gates found during validation:
- skip minified/generated bundles (avg line length > 200) — draco/three.min were a
  false-positive minefield of `h[x](...)` calls + `{a:b}` literals;
- DEPTH-AWARE entry parsing (top-level `key: Identifier` only) so method-shorthand bodies
  and nested objects don't leak their inner `k: v` pairs as bogus handlers;
- callable-only targets (drop data `constant`s — a `{x: URL}` entry resolving to the global);
- dynamic-dispatch gate (a statically-accessed look-alike object yields nothing).
Handles constructor and field-initializer registry forms (this. normalized). Surfaces in
codegraph_explore via the existing Dynamic-dispatch-links section.

Deferred (recall, documented in dispatch-synthesizer-backlog.md): assign-then-call dispatch,
augmentation registration (reg[k]=H), and the cross-file barrel-namespace variant
(trezor getMethod) — the hard tier.

Full suite green (1606); new __tests__/object-registry-synthesizer.test.ts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Colby McHenry
2026-06-20 15:17:31 -05:00
co-authored by Claude Opus 4.8
parent 270e50655a
commit 7f970296cf
3 changed files with 240 additions and 2 deletions
+1 -1
View File
@@ -54,7 +54,7 @@ Status legend (matches the playbook): ✅ done+validated · 🟡 shipped but und
| Shape | Ecosystem | The static anchor that bridges it | Mechanism | Status |
|---|---|---|---|---|
| **Name→class registry / command bus** | any (TS, .NET, Java, Go…) | a registry `{key: Class}` (object literal *or* module-namespace export) + a call naming `key`; bridge `key → Class.run/handle` by literal-key match, else camel↔Pascal convention | S (fan-out + name-match) | 🔬 **the most generalizable one.** trezor `getMethod` (`methods[method]→new MethodConstructor`), n8n node registry, VS Code commands, webpack loaders. **Hard sub-case:** trezor resolves via *dynamic import of a computed path* + runtime-string index + **case transform** (`'signTransaction'``SignTransaction`)a fan-out (dispatcher→all registered classes) gated by name-match, like `gin-middleware-chain`. |
| **Name→class registry / command bus** | any (TS/JS first) | object-literal registry `{key: Handler}` + computed-key dispatch `(new) reg[var](…)` | S (fan-out, `object-registry`) | ✅ **SHIPPED v1 (2026-06-20)**`objectRegistryEdges`. Links each dispatcher fn → each registered handler's callable entry (a class's `execute`/run/handle method — preferring the method chained at the dispatch — or the function value). Precise on **xrengine** (CommandManager, 64 edges, class registry → `.execute`), **Prebid.js** (7: builder/consent/message dispatch, fn registry), **warp-drive** (1). **0 false positives** after: minified-file skip (avg line >200), **depth-aware** entry parse (top-level `key: Ident` only — method-shorthand/nested-object bodies don't leak), callable-only targets (no data `constant`), dynamic-dispatch gate. Handles constructor + field-initializer (`this.` normalized) forms. **Deferred (recall, documented):** assign-then-call (`const h=reg[k]; h()` — warp-drive's main `COMMANDS`), augmentation (`reg[k]=H`Prebid single-entry), method-shorthand entry recall, and the **cross-file barrel-namespace** variant (trezor `getMethod`: `import * as M; M[method]→new` + computed dynamic import + camel↔Pascal — the hard tier, still 🔬). |
| **RTK Query** | TS / Redux Toolkit | `createApi({ endpoints: b => ({ getX: b.query(...) }) })` → generated `useGetXQuery` hook → component; endpoint name ↔ hook name (`getX``useGetXQuery`) is convention | X (extract endpoints) + S (endpoint→hook) | 🔬 **found on shapeshift** (14 `createApi` files, currently invisible). The modern RTK default — likely higher traffic than hand thunks now. |
| **Vuex / Pinia** | Vue | `store.dispatch('ns/action')` / `commit('mutation')` → action/mutation by string key (namespaced) | S (string-keyed, like `event-emitter`) | ⬜ |
| **NgRx effects** | Angular | `createEffect(() => actions.pipe(ofType(LoginAction), …))` → effect handler; `Store.dispatch(new LoginAction())` → effect by action type/class | S (type/class-keyed) | ⬜ |