feat(extraction): index Vuex/Pinia store actions, mutations, and getters

A Vue store's callable surface — Vuex `actions`/`mutations`/`getters` and Pinia
store actions — lived only as object-literal properties, so the symbols an agent
looks for (`login`, `getSessionList`, `getAuthMenuList`) were never nodes:
`codegraph search`/`codegraph_node` returned "not found" and the agent had to
read the store by hand. This extracts them as function nodes (with their real
bodies + callees), the foundation under any later dispatch-bridge synthesis.

A corpus probe (vue-element-admin, vue2-elm, Geeker-Admin, MallChatWeb) showed
Vue store dispatch is NOT one clean string-keyed shape but ~5; extraction here
covers the three dominant definition forms:
  - Vuex MODULE: non-exported `const actions/mutations = {…}` collections
    (gated by a ≥2-signal looksLikeVueStoreFile + the object-of-functions shape,
    so a Redux file's stray `const actions` is a 0-node no-op).
  - Pinia OPTIONS: `defineStore({ actions: {…}, getters: {…} })` — methods of
    the actions/mutations/getters properties of a store-factory config.
  - Pinia SETUP: `defineStore('id', () => { const foo = …; return {…} })` — the
    body-local function consts (findPiniaSetupFn + extractPiniaSetupBody; the
    generic body walk doesn't reach nested function scopes). Distinguished from
    an inline action map via objectHasInlineFunctions so zustand/SvelteKit
    extraction is unchanged.

Validated findable on element-admin (50 fns), Geeker (21), MallChat (68);
0-node no-op on a non-Vue control (uwave-web, unchanged at 4496 nodes). Deferred
(documented in the backlog): vue2-elm's `export default {…}` split-file +
computed-key `commit(CONST)` form (n=1), and the dispatch BRIDGE synthesis
(Vuex string-key + Pinia useStore().action()). Suite green (1610); new
__tests__/vue-store-extraction.test.ts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Colby McHenry
2026-06-20 20:08:34 -05:00
parent e9f7422223
commit cc9c2f7420
4 changed files with 304 additions and 6 deletions
+1
View File
@@ -11,6 +11,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
### New Features
- Vue store actions, mutations, and getters are now indexed as symbols you can find and read. Whether your store is **Vuex** (`mutations` / `actions` objects in a module) or **Pinia** — both the options form (`defineStore({ actions: { … } })`) and the setup form (`defineStore('id', () => { … })`, where actions are local functions) — each action, mutation, and getter is now a real node. So `codegraph search` finds `login` or `getSessionList`, and `codegraph_explore` / `codegraph_node` show its body and what it calls, instead of "not found" because the function only existed as an object-literal property. (Connecting a component's `dispatch('user/login')` / `store.action()` call through to the action is a separate follow-up.)
- `codegraph_explore` now connects React data-fetching flows built on **RTK Query** (Redux Toolkit's `createApi`). An endpoint defined inside `createApi({ endpoints })` and the `useGetXQuery` / `useUpdateYMutation` hook it generates were both invisible to analysis — so "what does this component fetch?" or "where does `useGetThingQuery` get its data?" dead-ended, because the hook, the endpoint, and the component had nothing linking them. CodeGraph now indexes each endpoint and each generated hook as real symbols and wires the path `component → useGetXQuery → getX → queryFn`, so the flow resolves in one explore call instead of reading the API slice by hand. Both the arrow (`endpoints: build => ({ … })`) and method (`endpoints(builder) { return { … } }`) styles are recognized, along with the `useLazyGetXQuery` variant; hand-written hooks of a similar name are left untouched.
- `codegraph_explore` now surfaces the right code in large multi-layer projects. When you ask a backend-flow question in a repo that pairs an API server with a big frontend that mirrors the same domain words — say an `app/` admin UI sitting over an `api/` server — the server-side file that genuinely matches several of your query's terms is no longer pushed out of the results by the larger, more interconnected frontend layer. A file corroborated by two or more distinct query terms is now kept in the answer even when a denser unrelated layer would otherwise crowd it out, so "how does X read items / handle the request" returns the service or handler that does the work instead of a wall of frontend views. Single-layer projects are unaffected; set `CODEGRAPH_RANK_NO_MULTITERM=1` to revert to the previous ranking.
+138
View File
@@ -0,0 +1,138 @@
/**
* Vue store action/mutation/getter extraction (the foundation for finding and
* reading store logic — `codegraph_node login` / `getSessionList`).
*
* Vuex/Pinia define a store's callable surface as object-literal members nested
* under `actions`/`mutations`/`getters`, or as body-local consts in a Pinia setup
* store — none of which were extracted, so the symbols an agent looks for didn't
* exist as nodes. This covers the three dominant forms:
* - Vuex module: non-exported `const actions = {…}` / `const mutations = {…}`.
* - Pinia options: `defineStore({ actions: {…}, getters: {…} })`.
* - Pinia setup: `defineStore('id', () => { const foo = …; return { foo } })`.
* And the precision gate: a non-exported `const actions = {…}` in a file that
* isn't a Vue store contributes nothing.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as os from 'node:os';
import { CodeGraph } from '../src';
describe('vue store extraction', () => {
let dir: string;
beforeEach(() => { dir = fs.mkdtempSync(path.join(os.tmpdir(), 'vue-store-')); });
afterEach(() => { fs.rmSync(dir, { recursive: true, force: true }); });
it('extracts Vuex module + Pinia options + Pinia setup store members as function nodes', async () => {
// Vuex MODULE form: non-exported `const mutations`/`const actions` collections,
// wired via a default export (element-admin style). Method shorthand + arrow pairs.
fs.writeFileSync(
path.join(dir, 'userModule.js'),
`import { persistToken } from './auth-utils';
const state = { token: '' };
const mutations = {
SET_TOKEN: (state, token) => { state.token = token; },
};
const actions = {
login({ commit }, info) {
persistToken(info.token);
},
async logout({ commit }) {
commit('SET_TOKEN', '');
},
};
export default { namespaced: true, state, mutations, actions };
`
);
fs.writeFileSync(
path.join(dir, 'auth-utils.js'),
`export function persistToken(token) { return token; }
`
);
// Pinia OPTIONS form: actions + getters as object properties of a defineStore config.
fs.writeFileSync(
path.join(dir, 'authStore.ts'),
`import { defineStore } from 'pinia';
export const useAuthStore = defineStore({
id: 'auth',
state: () => ({ name: '' }),
getters: {
upperName: state => state.name.toUpperCase(),
},
actions: {
async fetchMenu() { return loadMenu(); },
setName(n: string) { this.name = n; },
},
});
`
);
// Pinia SETUP form: actions are body-local consts exposed via the return block.
fs.writeFileSync(
path.join(dir, 'chatStore.ts'),
`import { defineStore } from 'pinia';
export const useChatStore = defineStore('chat', () => {
const list = reactive([]);
const getList = async () => { return fetchList(); };
function pushItem(x) { list.push(x); }
return { list, getList, pushItem };
});
`
);
const cg = await CodeGraph.init(dir, { silent: true });
await cg.indexAll();
const db = (cg as any).db.db;
const fn = (name: string) =>
db.prepare(`SELECT count(*) c FROM nodes WHERE name = ? AND kind = 'function'`).get(name).c;
// Vuex module: actions + mutations extracted.
expect(fn('login')).toBeGreaterThan(0);
expect(fn('logout')).toBeGreaterThan(0);
expect(fn('SET_TOKEN')).toBeGreaterThan(0);
// Pinia options: actions + getter extracted.
expect(fn('fetchMenu')).toBeGreaterThan(0);
expect(fn('setName')).toBeGreaterThan(0);
expect(fn('upperName')).toBeGreaterThan(0);
// Pinia setup: body-local actions extracted (and reachable via their bodies).
expect(fn('getList')).toBeGreaterThan(0);
expect(fn('pushItem')).toBeGreaterThan(0);
// The extracted action spans its real body — `login`'s `persistToken(...)`
// call attributes to it (extraction, not the deferred dispatch synthesis).
const loginCalls = db
.prepare(
`SELECT t.name FROM edges e JOIN nodes s ON s.id = e.source JOIN nodes t ON t.id = e.target
WHERE s.name = 'login' AND e.kind = 'calls'`
)
.all()
.map((r: any) => r.name);
expect(loginCalls).toContain('persistToken');
cg.close?.();
});
it('does not extract a non-exported `const actions = {…}` outside a Vue store file', async () => {
// A plain module that happens to hold a non-exported `const actions` object of
// functions, but lacks any second Vue-store signal — the gate must not fire.
fs.writeFileSync(
path.join(dir, 'commands.js'),
`const actions = {
doThing() { return 1; },
doOther() { return 2; },
};
export function run(key) { return actions[key](); }
`
);
const cg = await CodeGraph.init(dir, { silent: true });
await cg.indexAll();
const db = (cg as any).db.db;
expect(db.prepare(`SELECT count(*) c FROM nodes WHERE name = 'doThing'`).get().c).toBe(0);
expect(db.prepare(`SELECT count(*) c FROM nodes WHERE name = 'doOther'`).get().c).toBe(0);
// The real exported function is still extracted normally.
expect(db.prepare(`SELECT count(*) c FROM nodes WHERE name = 'run' AND kind='function'`).get().c).toBeGreaterThan(0);
cg.close?.();
});
});
+1 -1
View File
@@ -56,7 +56,7 @@ Status legend (matches the playbook): ✅ done+validated · 🟡 shipped but und
|---|---|---|---|---|
| **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) | ✅ **SHIPPED (2026-06-20)**`synthesizedBy:'rtk-query'`. **X:** extraction mints a function node per endpoint (named by its key, spanning the `queryFn`/`query` handler so its calls attribute; both `endpoints: b => ({…})` arrow and `endpoints(b){ return {…} }` method forms; a factory-handler endpoint `queryFn: makeFn(url)` falls back to a bare node spanning the builder call) **and** per generated-hook binding from `export const {…} = api` (carrying the sentinel signature `= RTK Query generated hook`). **S:** `rtkQueryEdges` bridges hook→same-file endpoint by the naming convention (strip `use` + optional `Lazy` + `Query`/`Mutation`, lc head). Component→hook is normal import/call resolution; hook→endpoint surfaces in explore as `dynamic: rtk query`. Validated **100% precision** (hooks == synth edges, **0 cross-file**) on **basetool** (small, 54 edges, both forms + factory fallback), **minusx-metabase** (small, 11), **shapeshift** (large, 13); **0** on the uwave-web control (no `createApi` → a complete no-op, 0 nodes/edges added). Sentinel gate correctly ignores hand-written look-alikes (shapeshift's `useFoxyQuery` is a real custom hook, never bridged). **Deferred:** cross-module `injectEndpoints` where the hook destructuring's RHS isn't the same bare api const (synth requires same-file endpoint). |
| **Vuex / Pinia** | Vue | `store.dispatch('ns/action')` / `commit('mutation')` → action/mutation by string key (namespaced) | S (string-keyed, like `event-emitter`) | ⬜ |
| **Vuex / Pinia** | Vue | `store.dispatch('ns/action')` / `commit('mutation')` → action/mutation by string key (namespaced); Pinia `useStore().action()` instance call | **X (extract collections) ✅ + S (dispatch bridge) ⬜** | 🟡 **EXTRACTION FOUNDATION SHIPPED (2026-06-20)** — store actions/mutations/getters are now nodes (`codegraph_node login`/`getSessionList` works). Corpus probe found this is **NOT one clean string-keyed shape** — it's ~5: **(1)** Vuex MODULE non-exported `const actions/mutations = {…}` (element-admin), **(2)** Vuex split-file `export default {…}` + computed-key `commit(CONST)` + `mapActions` (vue2-elm), **(3)** Pinia OPTIONS `defineStore({actions:{…}})` (Geeker), **(4)** Pinia SETUP `defineStore('id',()=>{const f=…;return{f}})` body-locals (MallChat), **(5)** Pinia `useStore().action()` instance dispatch. Extraction covers **1, 3, 4** (`extractObjectLiteralFunctions` on `actions`/`mutations`/`getters` collections + a `findPiniaSetupFn`/`extractPiniaSetupBody` for setup locals; `looksLikeVueStoreFile` ≥2-signal gate + the shape gate make it a **0-node no-op on a Redux control** despite the word "actions"). Validated findable on element-admin (50 fns), Geeker (21), MallChat (68); vue2-elm form-2 + computed-key **deferred** (n=1, needs export-default dispatch + const-string resolution). **Next — the dispatch BRIDGE synth (⬜), 2 separate members:** **(a)** Vuex string-key `dispatch('ns/action')`/`commit('M')` → action/mutation node (an `event-emitter`-style string-key clone, + namespace `module/`) — **n=1 in hand (element-admin)**, needs a 2nd string-literal Vuex repo; **(b)** Pinia `useStore().action()` → action (per-file `const s=useXStore()` var-binding → store-file method) — **n=2 in hand (Geeker+MallChat)**. Corpus: `/tmp/cg-vuex-eval/{vue-element-admin,vue2-elm,Geeker-Admin,MallChatWeb}`. |
| **NgRx effects** | Angular | `createEffect(() => actions.pipe(ofType(LoginAction), …))` → effect handler; `Store.dispatch(new LoginAction())` → effect by action type/class | S (type/class-keyed) | ⬜ |
### Tier B — backend command/event/message buses (each needs its own canonical flow + ≥2 repos)
+164 -5
View File
@@ -44,6 +44,16 @@ export { generateNodeId } from './tree-sitter-helpers';
*/
const RTK_HOOK_NAME_RE = /^use[A-Z][A-Za-z0-9]*(?:Query|Mutation)$/;
/** Vue store collections whose object-literal members are the symbols an agent
* looks for. Extracted as function nodes so `actions`/`mutations`/`getters` are
* findable + readable (the foundation under any later dispatch-bridge synth). */
const VUE_STORE_COLLECTION_NAMES = new Set(['actions', 'mutations', 'getters']);
/** Store-definition callees whose config object carries those collections. */
const VUE_STORE_FACTORY_CALLEES = new Set(['defineStore', 'createStore']);
/** Distinct signals that a file is a Vuex/Pinia store (≥2 ⇒ treat a bare
* `const actions = {…}` as a store collection — see looksLikeVueStoreFile). */
const VUE_STORE_FILE_SIGNAL = /\bdefineStore\b|\bcreateStore\b|\bVuex\b|\bmutations\b|\bactions\b|\bgetters\b|\bnamespaced\b/g;
/**
* Extract the name from a node based on language
*/
@@ -325,6 +335,8 @@ export class TreeSitterExtractor {
// (see flushFnRefCandidates).
private fnRefSpec: FnRefSpec | undefined;
private fnRefCandidates: Array<FnRefCandidate & { fromNodeId: string }> = [];
// Memoized "is this a Vue store file" verdict (per-extractor = per-file).
private vueStoreFile: boolean | null = null;
constructor(filePath: string, source: string, language?: Language) {
this.filePath = filePath;
@@ -2103,6 +2115,118 @@ export class TreeSitterExtractor {
}
}
/** Cheap per-file heuristic: the file carries ≥2 distinct Vue-store signals
* (defineStore/createStore/Vuex, or the actions/mutations/getters/namespaced
* vocabulary). Gates the non-exported `const actions = {…}` Vuex-module form so
* a stray `const actions` in unrelated code is never mistaken for a store. */
private looksLikeVueStoreFile(): boolean {
if (this.vueStoreFile !== null) return this.vueStoreFile;
const seen = new Set<string>();
VUE_STORE_FILE_SIGNAL.lastIndex = 0;
let m: RegExpExecArray | null;
while ((m = VUE_STORE_FILE_SIGNAL.exec(this.source))) {
seen.add(m[0]);
if (seen.size >= 2) break;
}
this.vueStoreFile = seen.size >= 2;
return this.vueStoreFile;
}
/** True if an object literal has ≥1 inline function member (`key: () => …` /
* `method(){}`) — distinguishes an inline action map (zustand/SvelteKit form
* actions) from a Pinia SETUP store's all-shorthand `return { foo, bar }`
* (whose functions are body-local consts, walked normally instead). */
private objectHasInlineFunctions(obj: SyntaxNode): boolean {
for (let i = 0; i < obj.namedChildCount; i++) {
const member = obj.namedChild(i);
if (member?.type === 'method_definition') return true;
if (member?.type === 'pair') {
const v = getChildByField(member, 'value');
if (v?.type === 'arrow_function' || v?.type === 'function_expression') return true;
}
}
return false;
}
/** Vue store action/mutation/getter collections defined INLINE in a store call:
* `defineStore({ actions: {…}, getters: {…} })` (Pinia options form),
* `defineStore('id', { actions: {…} })`, `createStore({ mutations: {…} })`,
* `new Vuex.Store({ actions: {…} })`. Returns the object literals under those
* keys so their methods become nodes. Gated on the store-factory callee. */
private findVueStoreCollectionObjects(callNode: SyntaxNode): SyntaxNode[] {
const callee = getChildByField(callNode, 'function') ?? getChildByField(callNode, 'constructor');
if (!callee) return [];
const calleeName =
callee.type === 'identifier'
? getNodeText(callee, this.source)
: callee.type === 'member_expression'
? getNodeText(getChildByField(callee, 'property') ?? callee, this.source)
: '';
if (!VUE_STORE_FACTORY_CALLEES.has(calleeName) && calleeName !== 'Store') return [];
const args = getChildByField(callNode, 'arguments');
if (!args) return [];
const objects: SyntaxNode[] = [];
for (let i = 0; i < args.namedChildCount; i++) {
const arg = args.namedChild(i);
if (arg?.type !== 'object' && arg?.type !== 'object_expression') continue;
for (let j = 0; j < arg.namedChildCount; j++) {
const member = arg.namedChild(j);
if (member?.type !== 'pair') continue;
const key = getChildByField(member, 'key');
if (!key || !VUE_STORE_COLLECTION_NAMES.has(getNodeText(key, this.source))) continue;
const value = getChildByField(member, 'value');
if (value && (value.type === 'object' || value.type === 'object_expression')) {
objects.push(value);
}
}
}
return objects;
}
/** The SETUP function of a Pinia setup store (`defineStore('id', () => {…})`)
* — an arrow/function arg with a block body. Returns null for the options form
* (`defineStore({…})`) and for any non-defineStore call. The setup body's local
* function consts are the store's actions; the generic body walk doesn't reach
* them (nested functions are separate scopes), so they're extracted explicitly. */
private findPiniaSetupFn(callNode: SyntaxNode): SyntaxNode | null {
const callee = getChildByField(callNode, 'function');
if (!callee || callee.type !== 'identifier' || getNodeText(callee, this.source) !== 'defineStore') return null;
const args = getChildByField(callNode, 'arguments');
if (!args) return null;
for (let i = 0; i < args.namedChildCount; i++) {
const arg = args.namedChild(i);
if (arg?.type !== 'arrow_function' && arg?.type !== 'function_expression') continue;
const body = getChildByField(arg, 'body');
if (body?.type === 'statement_block') return arg; // block body ⇒ setup form
}
return null;
}
/** Extract a Pinia setup store's actions: the body-local `const foo = () => …`
* / `function foo(){}` declarations, named by the binding. (State refs and other
* consts are left to the normal value-extraction; only the functions matter as
* the store's callable surface.) */
private extractPiniaSetupBody(setupFn: SyntaxNode): void {
const body = getChildByField(setupFn, 'body');
if (!body || body.type !== 'statement_block') return;
for (let i = 0; i < body.namedChildCount; i++) {
const stmt = body.namedChild(i);
if (!stmt) continue;
if (stmt.type === 'function_declaration') {
this.extractFunction(stmt);
} else if (this.extractor!.variableTypes.includes(stmt.type)) {
for (let j = 0; j < stmt.namedChildCount; j++) {
const decl = stmt.namedChild(j);
if (decl?.type !== 'variable_declarator') continue;
const v = getChildByField(decl, 'value');
if (v?.type === 'arrow_function' || v?.type === 'function_expression') {
this.extractFunction(v); // name resolved from the parent declarator
}
}
}
}
}
/**
* Extract a variable declaration (const, let, var, etc.)
*
@@ -2190,7 +2314,13 @@ export class TreeSitterExtractor {
: valueNode?.type === 'call_expression'
? this.findInitializerReturnedObject(valueNode)
: null;
const extractObjectMethods = isExported && !!objectOfFns;
// Only treat as an inline object-of-functions when the object actually
// HAS inline functions. A Pinia SETUP store `defineStore('id', () => {
// const foo = …; return { foo } })` returns an ALL-SHORTHAND object
// whose functions are body-local consts — it must fall through to a
// normal body walk (extracting those consts), not be skipped here.
const hasInlineFns = !!objectOfFns && this.objectHasInlineFunctions(objectOfFns);
const extractObjectMethods = isExported && !!objectOfFns && hasInlineFns;
// RTK Query: `createApi`/`injectEndpoints` define endpoints as
// object-literal properties whose values are `build.query/mutation(...)`
@@ -2202,16 +2332,39 @@ export class TreeSitterExtractor {
const rtkEndpoints =
valueNode?.type === 'call_expression' ? this.findRtkEndpointsObject(valueNode) : null;
// Pinia SETUP store: `defineStore('id', () => { const foo = …; return {…} })`.
// Its actions are body-local consts the generic walk can't reach.
const piniaSetup =
valueNode?.type === 'call_expression' ? this.findPiniaSetupFn(valueNode) : null;
// Vue store collections — make `actions`/`mutations`/`getters` findable
// function nodes (the foundation under any later dispatch-bridge synth).
// Two positions: INLINE in a store call (`defineStore({ actions: {…} })`
// / `createStore` / `new Vuex.Store`), and the non-exported Vuex-MODULE
// form (`const actions = {…}` at a store file's top level, wired via a
// `export default { actions }`). The Pinia SETUP form is handled by the
// body walk above (its actions are local consts).
const storeCollections: SyntaxNode[] = [];
if (valueNode?.type === 'call_expression' || valueNode?.type === 'new_expression') {
storeCollections.push(...this.findVueStoreCollectionObjects(valueNode));
}
if (objectOfFns && !extractObjectMethods &&
VUE_STORE_COLLECTION_NAMES.has(name) && this.looksLikeVueStoreFile()) {
storeCollections.push(objectOfFns);
}
// Visit the initializer body for calls — EXCEPT object literals (their
// function-valued properties are extracted below) and the store-factory
// / createApi call whose returned object we extract method-by-method
// below (walking the whole call would re-visit those method arrows and
// mis-attribute their inner calls to the file/module scope).
// / createApi / store-collection call whose nested objects we extract
// method-by-method below (walking the whole call would re-visit those
// method arrows and mis-attribute their inner calls to the file scope).
if (valueNode &&
valueNode.type !== 'object' &&
valueNode.type !== 'object_expression' &&
!(extractObjectMethods && valueNode.type === 'call_expression') &&
!rtkEndpoints) {
!rtkEndpoints &&
!piniaSetup &&
storeCollections.length === 0) {
this.visitFunctionBody(valueNode, '');
}
@@ -2221,6 +2374,12 @@ export class TreeSitterExtractor {
if (rtkEndpoints) {
this.extractRtkEndpoints(rtkEndpoints);
}
if (piniaSetup) {
this.extractPiniaSetupBody(piniaSetup);
}
for (const coll of storeCollections) {
this.extractObjectLiteralFunctions(coll);
}
}
}
}