@pinia/colada-devtools 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist/index.js +368 -252
  2. package/dist/index.js.map +1 -1
  3. package/dist-panel/{_queryId_-hQFnZzX2.js → _queryId_-dahx0N-a.js} +16 -16
  4. package/dist-panel/{_queryId_-hQFnZzX2.js.map → _queryId_-dahx0N-a.js.map} +1 -1
  5. package/dist-panel/{index-BVvWMadg.js → index-BkrEntNA.js} +3 -3
  6. package/dist-panel/{index-BVvWMadg.js.map → index-BkrEntNA.js.map} +1 -1
  7. package/dist-panel/{index-B2dJ1q6y.js → index-BoUh47CR.js} +2 -2
  8. package/dist-panel/{index-B2dJ1q6y.js.map → index-BoUh47CR.js.map} +1 -1
  9. package/dist-panel/{index-DfIXa7Jb.js → index-Covr4drg.js} +693 -713
  10. package/dist-panel/{index-DfIXa7Jb.js.map → index-Covr4drg.js.map} +1 -1
  11. package/dist-panel/index.js +1 -1
  12. package/dist-panel/{loader-B7hpsxwv.js → loader-CuAz6irK.js} +2 -2
  13. package/dist-panel/loader-CuAz6irK.js.map +1 -0
  14. package/dist-panel/{mouse-pointer-click-C8Q9Aulw.js → mouse-pointer-click-BRg0nGpd.js} +2 -2
  15. package/dist-panel/mouse-pointer-click-BRg0nGpd.js.map +1 -0
  16. package/dist-panel/{mutations-DqlFNONQ.js → mutations-ComK9mEg.js} +2 -2
  17. package/dist-panel/{mutations-DqlFNONQ.js.map → mutations-ComK9mEg.js.map} +1 -1
  18. package/dist-panel/{queries-DPJvb6qB.js → queries-C8H8bbDX.js} +85 -88
  19. package/dist-panel/{queries-DPJvb6qB.js.map → queries-C8H8bbDX.js.map} +1 -1
  20. package/dist-panel/{settings-DiWRvbPR.js → settings-DoijmHf7.js} +2 -2
  21. package/dist-panel/{settings-DiWRvbPR.js.map → settings-DoijmHf7.js.map} +1 -1
  22. package/dist-shared/index.d.ts +0 -15
  23. package/dist-shared/index.js +51 -152
  24. package/dist-shared/index.js.map +1 -1
  25. package/package.json +2 -2
  26. package/dist-panel/loader-B7hpsxwv.js.map +0 -1
  27. package/dist-panel/mouse-pointer-click-C8Q9Aulw.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/shared/plugins/fetch-count.ts","../src/shared/query-serialized.ts","../src/shared/use-event-listener.ts","../src/shared/rpc/index.ts"],"sourcesContent":["/**\n * Pinia Colada plugin that counts how many times a query has been fetched, has resolved, rejected, etc.\n *\n */\nimport type { DataState, QueryCache, UseQueryEntry } from '@pinia/colada'\nimport { toValue } from 'vue'\n\nconst now = () => performance.timeOrigin + performance.now()\n\nexport function addDevtoolsInfo(queryCache: QueryCache): void {\n if (installationMap.has(queryCache)) {\n return\n }\n installationMap.set(queryCache, true)\n\n queryCache.$onAction(({ name, args, after, onError }) => {\n if (name === 'create') {\n after((entry) => {\n entry[DEVTOOLS_INFO_KEY] = {\n count: {\n total: 0,\n succeed: 0,\n errored: 0,\n cancelled: 0,\n },\n updatedAt: now(),\n inactiveAt: 0,\n simulate: null,\n history: [],\n }\n })\n } else if (name === 'fetch') {\n const [entry] = args\n entry[DEVTOOLS_INFO_KEY].count.total++\n entry[DEVTOOLS_INFO_KEY].updatedAt = now()\n const createdAt = now()\n const historyEntry: UseQueryEntryHistoryEntry = {\n id: (entry[DEVTOOLS_INFO_KEY].history.at(0)?.id ?? -1) + 1,\n key: entry.key,\n state: entry.state.value,\n updatedAt: createdAt,\n createdAt,\n fetchTime: null,\n }\n\n const isEnabled = toValue(entry.options?.enabled) ?? true\n if (isEnabled) {\n historyEntry.fetchTime = {\n start: createdAt,\n end: null,\n }\n }\n\n entry[DEVTOOLS_INFO_KEY].history.unshift(historyEntry)\n // limit history to 10 entries\n entry[DEVTOOLS_INFO_KEY].history = entry[DEVTOOLS_INFO_KEY].history.slice(0, 10)\n\n after(() => {\n if (isEnabled) {\n historyEntry.fetchTime!.end = now()\n entry[DEVTOOLS_INFO_KEY].count.succeed++\n // set by the setEntryState\n // entry[DEVTOOLS_INFO_KEY].updatedAt = now()\n historyEntry.state = entry.state.value\n historyEntry.updatedAt = now()\n }\n })\n onError(() => {\n if (isEnabled) {\n historyEntry.fetchTime!.end = now()\n entry[DEVTOOLS_INFO_KEY].count.errored++\n // set by the setEntryState\n // entry[DEVTOOLS_INFO_KEY].updatedAt = now()\n historyEntry.state = entry.state.value\n historyEntry.updatedAt = now()\n }\n })\n } else if (name === 'cancel') {\n const [entry] = args\n if (entry.pending) {\n entry[DEVTOOLS_INFO_KEY].count.cancelled++\n }\n } else if (name === 'setEntryState') {\n const [entry] = args\n let lastHistoryEntry = entry[DEVTOOLS_INFO_KEY].history[0]\n after(() => {\n entry[DEVTOOLS_INFO_KEY].updatedAt = now()\n lastHistoryEntry ??= entry[DEVTOOLS_INFO_KEY].history[0]\n if (lastHistoryEntry) {\n lastHistoryEntry.state = entry.state.value\n lastHistoryEntry.updatedAt = now()\n }\n })\n } else if (name === 'untrack') {\n const [entry] = args\n after(() => {\n if (!entry.active) {\n entry[DEVTOOLS_INFO_KEY].inactiveAt = now()\n }\n })\n } else if (name === 'setQueryData') {\n // setQueryData can also trigger gc\n const [key] = args\n after(() => {\n const entry = queryCache.getEntries({ key, exact: true })[0]\n if (entry && !entry.active) {\n entry[DEVTOOLS_INFO_KEY].inactiveAt = now()\n }\n })\n }\n })\n}\n\nexport const DEVTOOLS_INFO_KEY = Symbol('fetch-count-pinia-colada-plugin')\n\nexport interface UseQueryEntryHistoryEntry extends Pick<UseQueryEntry, 'key'> {\n id: number\n\n state: DataState<unknown, unknown, unknown>\n\n /**\n * When was the last time the entry was updated.\n */\n updatedAt: number\n\n /**\n * When was the entry created.\n */\n createdAt: number\n\n /**\n * The time it took to fetch the entry.\n */\n fetchTime: {\n start: number\n end: number | null\n } | null\n}\n\nexport interface UseQueryDevtoolsInfo {\n count: {\n succeed: number\n errored: number\n cancelled: number\n total: number\n }\n\n updatedAt: number\n\n /**\n * When was this entry last inactive. 0 if it has never been inactive.\n */\n inactiveAt: number\n\n simulate: 'error' | 'loading' | null\n\n /**\n * Only the last 10 entries are kept.\n */\n history: UseQueryEntryHistoryEntry[]\n}\n\ndeclare module '@pinia/colada' {\n // eslint-disable-next-line unused-imports/no-unused-vars\n interface UseQueryEntry<TData, TError, TDataInitial> {\n /**\n * Returns whether the query is currently delaying its `asyncStatus` from becoming `'loading'`. Requires the {@link PiniaColadaDelay} plugin.\n */\n [DEVTOOLS_INFO_KEY]: UseQueryDevtoolsInfo\n }\n}\n\nconst installationMap = new WeakMap<object, boolean>()\n","import type { RefetchOnControl, UseQueryEntry, UseQueryOptionsWithDefaults } from '@pinia/colada'\nimport { toValue } from 'vue'\nimport { DEVTOOLS_INFO_KEY } from './plugins/fetch-count'\n\nexport interface UseQueryEntryPayload {\n id: string\n\n key: UseQueryEntry['key']\n state: UseQueryEntry['state']['value']\n asyncStatus: UseQueryEntry['asyncStatus']['value']\n\n active: UseQueryEntry['active']\n stale: UseQueryEntry['stale']\n when: UseQueryEntry['when']\n options: UseQueryEntryPayloadOptions | null\n deps: UseQueryEntryPayloadDep[]\n gcTimeout: number | null\n\n devtools: UseQueryEntry[typeof DEVTOOLS_INFO_KEY]\n}\n\nexport interface UseQueryEntryPayloadDepComponent {\n type: 'component'\n uid: number\n name: string | undefined\n}\n\nexport interface UseQueryEntryPayloadDepEffect {\n type: 'effect'\n active: boolean\n detached: boolean\n}\n\nexport type UseQueryEntryPayloadDep =\n | UseQueryEntryPayloadDepComponent\n | UseQueryEntryPayloadDepEffect\n\nexport interface UseQueryEntryPayloadOptions\n extends Pick<UseQueryOptionsWithDefaults, 'gcTime' | 'staleTime'> {\n // manually overriden to extract only plain values\n enabled: boolean\n refetchOnMount: RefetchOnControl\n refetchOnReconnect: RefetchOnControl\n refetchOnWindowFocus: RefetchOnControl\n}\n\nexport function createQueryEntryPayload(entry: UseQueryEntry): UseQueryEntryPayload {\n return {\n id: entry.key.join('\\0'),\n key: entry.key,\n state: entry.state.value,\n asyncStatus: entry.asyncStatus.value,\n\n active: entry.active,\n stale: entry.stale,\n when: entry.when,\n options: entry.options && {\n staleTime: entry.options.staleTime,\n gcTime: entry.options.gcTime,\n refetchOnMount: toValue(entry.options.refetchOnMount),\n refetchOnReconnect: toValue(entry.options.refetchOnReconnect),\n refetchOnWindowFocus: toValue(entry.options.refetchOnWindowFocus),\n enabled: toValue(entry.options.enabled),\n },\n deps: Array.from(entry.deps).map((dep) =>\n 'uid' in dep\n ? {\n type: 'component',\n uid: dep.uid,\n name: dep.type.displayName ?? dep.type.name,\n }\n : {\n type: 'effect',\n active: dep.active,\n detached: dep.detached,\n },\n ),\n gcTimeout: typeof entry.gcTimeout === 'number' ? (entry.gcTimeout as number) : null,\n\n devtools: entry[DEVTOOLS_INFO_KEY],\n }\n}\n\nexport function miniJsonParse(value: unknown): string {\n const isValidIdentifier = (key: string): boolean => /^[A-Z_$][\\w$]*$/i.test(key)\n\n const serialize = (val: unknown): string => {\n if (val === null) return 'null'\n if (typeof val === 'number') return val.toString()\n if (typeof val === 'string') return JSON.stringify(val)\n if (typeof val === 'boolean') return val ? 'true' : 'false'\n\n if (Array.isArray(val)) {\n return `[${val.map(serialize).join(',')}]`\n }\n\n if (typeof val === 'object') {\n const obj = val as Record<string, unknown>\n const entries = Object.keys(obj).map((key) => {\n const k = isValidIdentifier(key) ? key : JSON.stringify(key)\n const v = serialize(obj[key])\n return `${k}:${v}`\n })\n return `{${entries.join(',')}}`\n }\n\n return 'undefined' // or throw if you prefer to exclude unsupported values\n }\n\n return serialize(value)\n}\n","import { getCurrentScope, onScopeDispose } from 'vue'\n\n/**\n * Adds an event listener to Window that is automatically removed on scope dispose.\n */\nexport function useEventListener<E extends keyof WindowEventMap>(\n target: Window,\n event: E,\n listener: (this: Window, ev: WindowEventMap[E]) => any,\n options?: boolean | AddEventListenerOptions,\n): void\n\n/**\n * Adds an event listener to Document that is automatically removed on scope dispose.\n */\nexport function useEventListener<E extends keyof DocumentEventMap>(\n target: Document,\n event: E,\n listener: (this: Document, ev: DocumentEventMap[E]) => any,\n options?: boolean | AddEventListenerOptions,\n): void\n\nexport function useEventListener(\n target: Document | Window | EventTarget,\n event: string,\n listener: (this: EventTarget, ev: Event) => any,\n options?: boolean | AddEventListenerOptions,\n) {\n target.addEventListener(event, listener, options)\n if (getCurrentScope()) {\n onScopeDispose(() => {\n target.removeEventListener(event, listener)\n })\n }\n}\n","import type { DataState, EntryKey, UseQueryEntryFilter } from '@pinia/colada'\nimport type { UseQueryEntryPayload } from '../query-serialized'\nimport { toRaw } from 'vue'\n\nexport class DuplexChannel<\n const Emits extends Record<EmitsKeys, any[]>,\n const Listens extends Record<ListensKeys, [...any[]]>,\n // TODO: this seems to be enough. Test it\n // const Emits extends Record<keyof Emits, [...any[]]>,\n // const Listens extends Record<keyof Listens, [...any[]]>,\n // NOTE: we need these two to avoid requiring the interface to have an index signature for string\n EmitsKeys extends keyof Emits = keyof Emits,\n ListensKeys extends keyof Listens = keyof Listens,\n> {\n protected listenersByEvent = new Map<keyof Listens, Set<(...args: any[]) => void>>()\n protected eventsController: null | AbortController = null\n\n constructor(protected port: MessagePort) {\n this.setPort(port)\n }\n\n setPort(port: MessagePort) {\n // remove the previous event listeners\n this.eventsController?.abort()\n const { signal } = (this.eventsController = new AbortController())\n this.port = port\n port.addEventListener('message', this.onMessage.bind(this), { signal })\n port.addEventListener('messageerror', this.onMessageError.bind(this), { signal })\n // needed when we call `addEventListener` instead of using `onmessage`\n port.start()\n }\n\n private onMessage(event: MessageEvent) {\n if (!event.data || typeof event.data !== 'object' || typeof event.data.id !== 'string') {\n console.error(`${this.constructor.name}: invalid message`, event.data)\n return\n }\n const listeners = this.listenersByEvent.get(event.data.id)\n if (!listeners) return\n\n for (const listener of listeners.values()) {\n listener(...(event.data.data as Listens[keyof Listens]))\n }\n }\n\n private onMessageError(event: MessageEvent) {\n console.error(`${this.constructor.name}: message error`, event)\n }\n\n stop() {\n this.listenersByEvent.clear()\n this.eventsController?.abort()\n }\n\n emit<K extends keyof Emits>(event: K, ...args: NoInfer<Emits[K]>): void {\n const clonedData = args.map((arg) => toRawDeep(arg))\n this.port.postMessage({ id: event, data: clonedData })\n }\n\n on<K extends keyof Listens>(event: K, callback: (...args: Listens[K]) => void): () => void {\n let listeners = this.listenersByEvent.get(event)\n if (!listeners) {\n this.listenersByEvent.set(event, (listeners = new Set()))\n }\n listeners.add(callback)\n\n return () => {\n this.listenersByEvent.get(event)?.delete(callback)\n }\n }\n\n off<K extends keyof Listens>(event: K): void {\n this.listenersByEvent.delete(event)\n }\n}\n\nexport interface AppEmits {\n 'queries:all': [entries: UseQueryEntryPayload[]]\n 'queries:update': [entry: UseQueryEntryPayload]\n 'queries:delete': [entry: UseQueryEntryPayload]\n 'mutations:all': [entries: unknown[]]\n\n // for testing\n 'ping': []\n 'pong': []\n}\n\nexport interface DevtoolsEmits {\n 'queries:clear': [] | [filters: UseQueryEntryFilter]\n 'queries:refetch': [entryKey: EntryKey]\n 'queries:invalidate': [entryKey: EntryKey]\n 'queries:reset': [entryKey: EntryKey]\n\n 'queries:simulate:error': [entryKey: EntryKey]\n 'queries:simulate:error:stop': [entryKey: EntryKey]\n 'queries:simulate:loading': [entryKey: EntryKey]\n 'queries:simulate:loading:stop': [entryKey: EntryKey]\n\n 'queries:set:state': [entryKey: EntryKey, state: DataState<unknown, unknown, unknown>]\n\n // for testing\n 'ping': []\n 'pong': []\n}\n\nexport function _testTypes() {\n // the app\n const client = new DuplexChannel<AppEmits, DevtoolsEmits>({} as any)\n // the devtools\n const server = new DuplexChannel<DevtoolsEmits, AppEmits>({} as any)\n\n client.emit('queries:all', [])\n // client.emit('queries:all', [{ id: '', active: false, asyncStatus: 'idle', }])\n client.on('queries:clear', () => {})\n client.on('queries:clear', (filters = {}) => {\n console.log(filters.key)\n // ...\n })\n\n server.emit('queries:clear')\n server.emit('queries:clear', { key: [''] })\n}\n\nfunction toRawDeep<T>(val: T): T\nfunction toRawDeep(val: unknown): unknown {\n if (Array.isArray(val)) {\n return val.map((item) => toRawDeep(item))\n }\n // TODO: custom classes?\n if (val && typeof val === 'object' && !isError(val)) {\n return Object.fromEntries(Object.entries(val).map(([key, value]) => [key, toRawDeep(value)]))\n }\n return toRaw(val)\n}\n\nfunction isError(err: unknown): err is Error {\n return 'isError' in Error && typeof Error.isError === 'function'\n ? Error.isError(err)\n : err instanceof Error\n}\n"],"names":["now","addDevtoolsInfo","queryCache","installationMap","name","args","after","onError","entry","DEVTOOLS_INFO_KEY","createdAt","historyEntry","_a","isEnabled","toValue","_b","lastHistoryEntry","key","createQueryEntryPayload","dep","miniJsonParse","value","isValidIdentifier","serialize","val","obj","k","v","useEventListener","target","event","listener","options","getCurrentScope","onScopeDispose","DuplexChannel","port","__publicField","signal","listeners","clonedData","arg","toRawDeep","callback","_testTypes","client","server","filters","item","isError","toRaw","err"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/shared/query-serialized.ts","../src/shared/rpc/index.ts","../src/shared/plugins/devtools-info.ts"],"sourcesContent":["import type { RefetchOnControl, UseQueryEntry, UseQueryOptionsWithDefaults } from '@pinia/colada'\nimport type { DEVTOOLS_INFO_KEY } from './plugins/devtools-info'\n\nexport interface UseQueryEntryPayload {\n id: string\n\n key: UseQueryEntry['key']\n state: UseQueryEntry['state']['value']\n asyncStatus: UseQueryEntry['asyncStatus']['value']\n\n active: UseQueryEntry['active']\n stale: UseQueryEntry['stale']\n when: UseQueryEntry['when']\n options: UseQueryEntryPayloadOptions | null\n deps: UseQueryEntryPayloadDep[]\n gcTimeout: number | null\n\n devtools: UseQueryEntry[typeof DEVTOOLS_INFO_KEY]\n}\n\nexport interface UseQueryEntryPayloadDepComponent {\n type: 'component'\n uid: number\n name: string | undefined\n}\n\nexport interface UseQueryEntryPayloadDepEffect {\n type: 'effect'\n active: boolean\n detached: boolean\n}\n\nexport type UseQueryEntryPayloadDep =\n | UseQueryEntryPayloadDepComponent\n | UseQueryEntryPayloadDepEffect\n\nexport interface UseQueryEntryPayloadOptions\n extends Pick<UseQueryOptionsWithDefaults, 'gcTime' | 'staleTime'> {\n // manually overriden to extract only plain values\n enabled: boolean\n refetchOnMount: RefetchOnControl\n refetchOnReconnect: RefetchOnControl\n refetchOnWindowFocus: RefetchOnControl\n}\n\nexport function miniJsonParse(value: unknown): string {\n const isValidIdentifier = (key: string): boolean => /^[A-Z_$][\\w$]*$/i.test(key)\n\n const serialize = (val: unknown): string => {\n if (val === null) return 'null'\n if (typeof val === 'number') return val.toString()\n if (typeof val === 'string') return JSON.stringify(val)\n if (typeof val === 'boolean') return val ? 'true' : 'false'\n\n if (Array.isArray(val)) {\n return `[${val.map(serialize).join(',')}]`\n }\n\n if (typeof val === 'object') {\n const obj = val as Record<string, unknown>\n const entries = Object.keys(obj).map((key) => {\n const k = isValidIdentifier(key) ? key : JSON.stringify(key)\n const v = serialize(obj[key])\n return `${k}:${v}`\n })\n return `{${entries.join(',')}}`\n }\n\n return 'undefined' // or throw if you prefer to exclude unsupported values\n }\n\n return serialize(value)\n}\n","import type { DataState, EntryKey, UseQueryEntryFilter } from '@pinia/colada'\nimport type { UseQueryEntryPayload } from '../query-serialized'\nimport { toRaw } from 'vue'\n\nexport class DuplexChannel<\n const Emits extends Record<EmitsKeys, any[]>,\n const Listens extends Record<ListensKeys, [...any[]]>,\n // TODO: this seems to be enough. Test it\n // const Emits extends Record<keyof Emits, [...any[]]>,\n // const Listens extends Record<keyof Listens, [...any[]]>,\n // NOTE: we need these two to avoid requiring the interface to have an index signature for string\n EmitsKeys extends keyof Emits = keyof Emits,\n ListensKeys extends keyof Listens = keyof Listens,\n> {\n protected listenersByEvent = new Map<keyof Listens, Set<(...args: any[]) => void>>()\n protected eventsController: null | AbortController = null\n\n constructor(protected port: MessagePort) {\n this.setPort(port)\n }\n\n setPort(port: MessagePort) {\n // remove the previous event listeners\n this.eventsController?.abort()\n const { signal } = (this.eventsController = new AbortController())\n this.port = port\n port.addEventListener('message', this.onMessage.bind(this), { signal })\n port.addEventListener('messageerror', this.onMessageError.bind(this), { signal })\n // needed when we call `addEventListener` instead of using `onmessage`\n port.start()\n }\n\n private onMessage(event: MessageEvent) {\n if (!event.data || typeof event.data !== 'object' || typeof event.data.id !== 'string') {\n console.error(`${this.constructor.name}: invalid message`, event.data)\n return\n }\n const listeners = this.listenersByEvent.get(event.data.id)\n if (!listeners) return\n\n for (const listener of listeners.values()) {\n listener(...(event.data.data as Listens[keyof Listens]))\n }\n }\n\n private onMessageError(event: MessageEvent) {\n console.error(`${this.constructor.name}: message error`, event)\n }\n\n stop() {\n this.listenersByEvent.clear()\n this.eventsController?.abort()\n }\n\n emit<K extends keyof Emits>(event: K, ...args: NoInfer<Emits[K]>): void {\n const clonedData = args.map((arg) => toRawDeep(arg))\n this.port.postMessage({ id: event, data: clonedData })\n }\n\n on<K extends keyof Listens>(event: K, callback: (...args: Listens[K]) => void): () => void {\n let listeners = this.listenersByEvent.get(event)\n if (!listeners) {\n this.listenersByEvent.set(event, (listeners = new Set()))\n }\n listeners.add(callback)\n\n return () => {\n this.listenersByEvent.get(event)?.delete(callback)\n }\n }\n\n off<K extends keyof Listens>(event: K): void {\n this.listenersByEvent.delete(event)\n }\n}\n\nexport interface AppEmits {\n 'queries:all': [entries: UseQueryEntryPayload[]]\n 'queries:update': [entry: UseQueryEntryPayload]\n 'queries:delete': [entry: UseQueryEntryPayload]\n 'mutations:all': [entries: unknown[]]\n\n // for testing\n 'ping': []\n 'pong': []\n}\n\nexport interface DevtoolsEmits {\n 'queries:clear': [] | [filters: UseQueryEntryFilter]\n 'queries:refetch': [entryKey: EntryKey]\n 'queries:invalidate': [entryKey: EntryKey]\n 'queries:reset': [entryKey: EntryKey]\n\n 'queries:simulate:error': [entryKey: EntryKey]\n 'queries:simulate:error:stop': [entryKey: EntryKey]\n 'queries:simulate:loading': [entryKey: EntryKey]\n 'queries:simulate:loading:stop': [entryKey: EntryKey]\n\n 'queries:set:state': [entryKey: EntryKey, state: DataState<unknown, unknown, unknown>]\n\n // for testing\n 'ping': []\n 'pong': []\n}\n\nexport function _testTypes() {\n // the app\n const client = new DuplexChannel<AppEmits, DevtoolsEmits>({} as any)\n // the devtools\n const server = new DuplexChannel<DevtoolsEmits, AppEmits>({} as any)\n\n client.emit('queries:all', [])\n // client.emit('queries:all', [{ id: '', active: false, asyncStatus: 'idle', }])\n client.on('queries:clear', () => {})\n client.on('queries:clear', (filters = {}) => {\n console.log(filters.key)\n // ...\n })\n\n server.emit('queries:clear')\n server.emit('queries:clear', { key: [''] })\n}\n\nfunction toRawDeep<T>(val: T): T\nfunction toRawDeep(val: unknown): unknown {\n if (Array.isArray(val)) {\n return val.map((item) => toRawDeep(item))\n }\n // TODO: custom classes?\n if (val && typeof val === 'object' && !isError(val)) {\n return Object.fromEntries(Object.entries(val).map(([key, value]) => [key, toRawDeep(value)]))\n }\n return toRaw(val)\n}\n\nfunction isError(err: unknown): err is Error {\n return 'isError' in Error && typeof Error.isError === 'function'\n ? Error.isError(err)\n : err instanceof Error\n}\n","/**\n * Pinia Colada plugin that counts how many times a query has been fetched, has resolved, rejected, etc.\n *\n */\nimport type { DataState, UseQueryEntry } from '@pinia/colada'\n\nexport const DEVTOOLS_INFO_KEY = Symbol('fetch-count-pinia-colada-plugin')\n\nexport interface UseQueryEntryHistoryEntry extends Pick<UseQueryEntry, 'key'> {\n id: number\n\n state: DataState<unknown, unknown, unknown>\n\n /**\n * When was the last time the entry was updated.\n */\n updatedAt: number\n\n /**\n * When was the entry created.\n */\n createdAt: number\n\n /**\n * The time it took to fetch the entry.\n */\n fetchTime: {\n start: number\n end: number | null\n } | null\n}\n\nexport interface UseQueryDevtoolsInfo {\n count: {\n succeed: number\n errored: number\n cancelled: number\n total: number\n }\n\n updatedAt: number\n\n /**\n * When was this entry last inactive. 0 if it has never been inactive.\n */\n inactiveAt: number\n\n simulate: 'error' | 'loading' | null\n\n /**\n * Only the last 10 entries are kept.\n */\n history: UseQueryEntryHistoryEntry[]\n}\n\ndeclare module '@pinia/colada' {\n // eslint-disable-next-line unused-imports/no-unused-vars\n interface UseQueryEntry<TData, TError, TDataInitial> {\n /**\n * Returns whether the query is currently delaying its `asyncStatus` from becoming `'loading'`. Requires the {@link PiniaColadaDelay} plugin.\n */\n [DEVTOOLS_INFO_KEY]: UseQueryDevtoolsInfo\n }\n}\n"],"names":["miniJsonParse","value","isValidIdentifier","key","serialize","val","obj","k","v","DuplexChannel","port","__publicField","_a","signal","event","listeners","listener","args","clonedData","arg","toRawDeep","callback","_testTypes","client","server","filters","item","isError","toRaw","err","DEVTOOLS_INFO_KEY"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pinia/colada-devtools",
3
3
  "type": "module",
4
- "version": "0.0.1",
4
+ "version": "0.0.3",
5
5
  "description": "Devtools for Pinia Colada",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -59,7 +59,7 @@
59
59
  "unplugin-vue-router": "^0.12.0",
60
60
  "vite-plugin-dts": "^4.5.3",
61
61
  "vue-router": "^4.5.1",
62
- "@pinia/colada": "0.16.0"
62
+ "@pinia/colada": "0.16.1"
63
63
  },
64
64
  "scripts": {
65
65
  "dev:app": "vite",
@@ -1 +0,0 @@
1
- {"version":3,"file":"loader-B7hpsxwv.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"mouse-pointer-click-C8Q9Aulw.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;"}