CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-pinia

Intuitive, type safe and flexible Store for Vue

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

store-usage.mddocs/

Store Usage

Utilities for working with store instances including extracting reactive references, hydration control, and hot module replacement support.

Capabilities

Store to Refs

Extracts store properties as reactive refs while maintaining reactivity. This is essential when destructuring stores in Vue components to preserve reactivity.

/**
 * Creates reactive refs from store properties while maintaining reactivity
 * @param store - Store instance to extract refs from
 * @returns Object with reactive refs for all non-function store properties
 */
function storeToRefs<SS extends StoreGeneric>(store: SS): StoreToRefs<SS>;

type StoreToRefs<SS extends StoreGeneric> = {
  [K in keyof SS as SS[K] extends (...args: any[]) => any ? never : K]: SS[K] extends Ref
    ? SS[K]
    : Ref<SS[K]>;
};

Usage Examples:

import { defineStore, storeToRefs } from "pinia";
import { computed } from "vue";

const useCounterStore = defineStore("counter", () => {
  const count = ref(0);
  const doubleCount = computed(() => count.value * 2);
  const increment = () => count.value++;
  
  return { count, doubleCount, increment };
});

// In a Vue component
export default {
  setup() {
    const counterStore = useCounterStore();
    
    // ❌ This breaks reactivity
    const { count, doubleCount } = counterStore;
    
    // ✅ This preserves reactivity
    const { count, doubleCount } = storeToRefs(counterStore);
    
    // Actions can be destructured directly (they don't need refs)
    const { increment } = counterStore;
    
    return {
      count,      // Ref<number>
      doubleCount, // ComputedRef<number>
      increment,   // Function
    };
  }
};

// In a Composition API component
<script setup>
import { storeToRefs } from "pinia";
import { useCounterStore } from "./stores/counter";

const counterStore = useCounterStore();
const { count, doubleCount } = storeToRefs(counterStore);
const { increment } = counterStore;

// count and doubleCount are reactive refs
// increment is the original function
</script>

Skip Hydration

Marks an object to skip the hydration process during Server-Side Rendering. Useful for stateful objects that aren't really state.

/**
 * Tells Pinia to skip the hydration process for a given object
 * @param obj - Target object to skip hydration
 * @returns The same object with hydration skip marker
 */
function skipHydrate<T = any>(obj: T): T;

Usage Examples:

import { defineStore, skipHydrate } from "pinia";
import { useRouter } from "vue-router";

const useAppStore = defineStore("app", () => {
  const count = ref(0);
  const router = useRouter();
  
  // Skip hydration for router instance (not state)
  const routerInstance = skipHydrate(router);
  
  // Skip hydration for non-serializable objects
  const webSocket = skipHydrate(new WebSocket("ws://localhost:8080"));
  
  return {
    count,           // Will be hydrated
    routerInstance,  // Will skip hydration
    webSocket,       // Will skip hydration
  };
});

// Useful for plugins, external libraries, or browser-only objects
const useServiceStore = defineStore("services", () => {
  const apiClient = skipHydrate(new ApiClient());
  const analytics = skipHydrate(window.gtag);
  
  return { apiClient, analytics };
});

Should Hydrate

Checks whether a value should be hydrated during the SSR hydration process.

/**
 * Returns whether a value should be hydrated
 * @param obj - Target variable to check
 * @returns true if obj should be hydrated, false otherwise
 */
function shouldHydrate(obj: any): boolean;

Usage Examples:

import { shouldHydrate, skipHydrate } from "pinia";

const normalObject = { name: "John", age: 30 };
const skippedObject = skipHydrate({ socket: new WebSocket("ws://localhost") });

console.log(shouldHydrate(normalObject));  // true
console.log(shouldHydrate(skippedObject)); // false

// Custom hydration logic
function customHydration(state: any, initialState: any) {
  for (const key in initialState) {
    if (shouldHydrate(initialState[key])) {
      state[key] = initialState[key];
    }
  }
}

Hot Module Replacement

Enables hot module replacement for stores during development, allowing store updates without losing state.

/**
 * Creates a function to accept Hot Module Replacement for a store
 * @param store - Store definition to enable HMR for
 * @param hot - Hot module object from build tool
 * @returns Function that accepts new store definitions
 */
function acceptHMRUpdate(
  store: StoreDefinition,
  hot: any
): (newStore: StoreDefinition) => any;

Usage Examples:

import { defineStore, acceptHMRUpdate } from "pinia";

const useCounterStore = defineStore("counter", {
  state: () => ({ count: 0 }),
  getters: {
    doubleCount: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++;
    },
  },
});

// Enable HMR for this store (development only)
if (import.meta.hot) {
  import.meta.hot.accept(acceptHMRUpdate(useCounterStore, import.meta.hot));
}

// For Vite
if (import.meta.hot) {
  import.meta.hot.accept(acceptHMRUpdate(useCounterStore, import.meta.hot));
}

// For Webpack
if (module.hot) {
  module.hot.accept(acceptHMRUpdate(useCounterStore, module.hot));
}

Store Instance Interface

When you call a store definition function, you get a store instance with built-in properties and methods:

interface Store<Id extends string = string, S extends StateTree = {}, G = {}, A = {}> {
  /** Unique store identifier */
  $id: Id;
  
  /** Direct access to store state */
  $state: UnwrapRef<S>;
  
  /** Patch state with partial updates */
  $patch(partialState: _DeepPartial<UnwrapRef<S>>): void;
  $patch<F extends (state: UnwrapRef<S>) => any>(
    stateMutator: ReturnType<F> extends Promise<any> ? never : F
  ): void;
  
  /** Reset state to initial values */
  $reset(): void;
  
  /** Subscribe to state changes */
  $subscribe(
    callback: SubscriptionCallback<S>,
    options?: { detached?: boolean } & WatchOptions
  ): () => void;
  
  /** Subscribe to action calls */
  $onAction(
    callback: StoreOnActionListener<Id, S, G, A>,
    detached?: boolean
  ): () => void;
  
  /** Dispose of the store and clean up subscriptions */
  $dispose(): void;
}

type SubscriptionCallback<S> = (
  mutation: SubscriptionCallbackMutation<S>,
  state: UnwrapRef<S>
) => void;

type StoreOnActionListener<Id extends string, S, G, A> = (
  context: StoreOnActionListenerContext<Id, S, G, A>
) => void;

interface StoreOnActionListenerContext<Id extends string, S, G, A> {
  name: string;
  store: Store<Id, S, G, A>;
  args: any[];
  after: (callback: () => void) => void;
  onError: (callback: (error: any) => void) => void;
}

Usage Examples:

const store = useCounterStore();

// Access store properties
console.log(store.$id);    // "counter"
console.log(store.$state); // { count: 0 }

// Patch state
store.$patch({ count: 10 });
store.$patch((state) => {
  state.count += 5;
});

// Reset state
store.$reset();

// Subscribe to state changes
const unsubscribe = store.$subscribe((mutation, state) => {
  console.log(`${mutation.type}: ${JSON.stringify(state)}`);
});

// Subscribe to actions
const unsubscribeActions = store.$onAction(({ name, args, after, onError }) => {
  console.log(`Action ${name} called with:`, args);
  
  after(() => {
    console.log(`Action ${name} completed`);
  });
  
  onError((error) => {
    console.error(`Action ${name} failed:`, error);
  });
});

// Clean up
unsubscribe();
unsubscribeActions();
store.$dispose();

Types

type StoreGeneric = Store<string, StateTree, Record<string, any>, Record<string, any>>;

type _DeepPartial<T> = { [K in keyof T]?: _DeepPartial<T[K]> };

type SubscriptionCallbackMutation<S> = 
  | SubscriptionCallbackMutationDirect
  | SubscriptionCallbackMutationPatchObject<S>
  | SubscriptionCallbackMutationPatchFunction;

interface SubscriptionCallbackMutationDirect {
  type: MutationType.direct;
  storeId: string;
  events?: DebuggerEvent[] | DebuggerEvent;
}

interface SubscriptionCallbackMutationPatchObject<S> {
  type: MutationType.patchObject;  
  storeId: string;
  payload: _DeepPartial<S>;
  events?: DebuggerEvent[] | DebuggerEvent;
}

interface SubscriptionCallbackMutationPatchFunction {
  type: MutationType.patchFunction;
  storeId: string;
  events?: DebuggerEvent[] | DebuggerEvent;
}

Install with Tessl CLI

npx tessl i tessl/npm-pinia

docs

index.md

options-api.md

pinia-instance.md

store-definition.md

store-usage.md

tile.json