/ src / scripts / store.ts
store.ts
 1  /**
 2   * Injected script for discovering Pinia or Vuex stores and their actions/state representations.
 3   *
 4   * This function is serialized via `.toString()` and evaluated inside the page context,
 5   * so the types below only exist at the TS boundary — the runtime shapes are whatever
 6   * Pinia/Vuex put on the Vue app. We use narrow structural types for the fields we touch.
 7   */
 8  
 9  // Minimal structural types describing just the fields we access.
10  type PiniaStore = Record<string, unknown>;
11  interface VuexModule {
12    _rawModule?: { actions?: Record<string, unknown> };
13    state?: Record<string, unknown>;
14  }
15  interface VueApp {
16    __vue_app__?: {
17      config?: {
18        globalProperties?: {
19          $pinia?: { _s?: Map<string, PiniaStore> };
20          $store?: { _modules?: { root?: { _children?: Record<string, VuexModule> } } };
21        };
22      };
23    };
24  }
25  
26  export function discoverStores() {
27    const stores: Array<{ type: string; id: string; actions: string[]; stateKeys: string[] }> = [];
28    try {
29      const app = document.querySelector('#app') as unknown as VueApp | null;
30      if (!app?.__vue_app__) return stores;
31      const gp = app.__vue_app__.config?.globalProperties;
32  
33      // Pinia stores
34      const pinia = gp?.$pinia;
35      if (pinia?._s) {
36        pinia._s.forEach((store, id) => {
37          const actions: string[] = [];
38          const stateKeys: string[] = [];
39          for (const k in store) {
40            try {
41              if (k.startsWith('$') || k.startsWith('_')) continue;
42              if (typeof store[k] === 'function') actions.push(k);
43              else stateKeys.push(k);
44            } catch {}
45          }
46          stores.push({ type: 'pinia', id, actions: actions.slice(0, 20), stateKeys: stateKeys.slice(0, 15) });
47        });
48      }
49  
50      // Vuex store modules
51      const vuex = gp?.$store;
52      if (vuex?._modules?.root?._children) {
53        const children = vuex._modules.root._children;
54        for (const [modName, mod] of Object.entries(children)) {
55          const actions = Object.keys(mod._rawModule?.actions ?? {}).slice(0, 20);
56          const stateKeys = Object.keys(mod.state ?? {}).slice(0, 15);
57          stores.push({ type: 'vuex', id: modName, actions, stateKeys });
58        }
59      }
60    } catch {}
61    return stores;
62  }