CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-vuex

State management pattern and library for Vue.js applications that serves as a centralized store with predictable state mutations

Pending
Overview
Eval results
Files

module-system.mddocs/

Module System

Modular organization system for complex applications with namespacing, dynamic registration, and hierarchical state management.

Capabilities

Module Definition

Define store modules with their own state, mutations, actions, and getters.

/**
 * Module definition interface
 */
interface Module<S, R> {
  /** Enable/disable namespacing for this module */
  namespaced?: boolean;
  /** Module state object or function returning state */
  state?: S | (() => S);
  /** Module getters for derived state */
  getters?: GetterTree<S, R>;
  /** Module actions for async operations */
  actions?: ActionTree<S, R>;
  /** Module mutations for state changes */
  mutations?: MutationTree<S>;
  /** Sub-modules for nested organization */
  modules?: ModuleTree<R>;
}

interface GetterTree<S, R> {
  [key: string]: Getter<S, R>;
}

interface ActionTree<S, R> {
  [key: string]: ActionHandler<S, R>;
}

interface MutationTree<S> {
  [key: string]: Mutation<S>;
}

interface ModuleTree<R> {
  [key: string]: Module<any, R>;
}

type Getter<S, R> = (state: S, getters: any, rootState: R, rootGetters: any) => any;
type ActionHandler<S, R> = (context: ActionContext<S, R>, payload?: any) => any;
type Mutation<S> = (state: S, payload?: any) => any;

Usage Examples:

// User module
const userModule = {
  namespaced: true,
  
  state: () => ({
    profile: null,
    preferences: {},
    isAuthenticated: false
  }),
  
  mutations: {
    setProfile(state, profile) {
      state.profile = profile;
    },
    setAuthenticated(state, status) {
      state.isAuthenticated = status;
    }
  },
  
  actions: {
    async login({ commit, dispatch }, credentials) {
      const user = await api.login(credentials);
      commit('setProfile', user);
      commit('setAuthenticated', true);
      
      // Call root action
      dispatch('initializeApp', null, { root: true });
    }
  },
  
  getters: {
    displayName: state => state.profile?.name || 'Guest',
    isLoggedIn: state => state.isAuthenticated && state.profile
  }
};

// Cart module with sub-modules
const cartModule = {
  namespaced: true,
  
  state: () => ({
    items: [],
    discounts: []
  }),
  
  modules: {
    shipping: shippingModule,
    payment: paymentModule
  },
  
  mutations: {
    addItem(state, item) {
      const existing = state.items.find(i => i.id === item.id);
      if (existing) {
        existing.quantity += item.quantity;
      } else {
        state.items.push(item);
      }
    }
  },
  
  getters: {
    total: state => state.items.reduce((sum, item) => 
      sum + (item.price * item.quantity), 0),
    itemCount: state => state.items.reduce((sum, item) => 
      sum + item.quantity, 0)
  }
};

Dynamic Module Registration

Register modules dynamically at runtime.

/**
 * Register a module dynamically
 * @param path - Module path (string or array)
 * @param module - Module definition
 * @param options - Registration options
 */
registerModule<T>(path: string | string[], module: Module<T, S>, options?: ModuleOptions): void;

interface ModuleOptions {
  /** Preserve existing state when registering */
  preserveState?: boolean;
}

Usage Examples:

import { createStore } from 'vuex';

const store = createStore({
  state: { count: 0 }
});

// Register single module
store.registerModule('user', userModule);

// Access: store.state.user.profile
// Commit: store.commit('user/setProfile', profile)

// Register nested module
store.registerModule(['cart', 'shipping'], shippingModule);

// Access: store.state.cart.shipping.address
// Commit: store.commit('cart/shipping/setAddress', address)

// Register with preserved state
store.registerModule('settings', settingsModule, {
  preserveState: true // Don't replace existing settings state
});

// Register multiple modules
const modules = {
  user: userModule,
  cart: cartModule,
  notifications: notificationModule
};

Object.keys(modules).forEach(name => {
  store.registerModule(name, modules[name]);
});

Dynamic Module Unregistration

Unregister modules dynamically to clean up resources.

/**
 * Unregister a module dynamically
 * @param path - Module path (string or array)
 */
unregisterModule(path: string | string[]): void;

Usage Examples:

// Unregister single module
store.unregisterModule('user');

// Unregister nested module
store.unregisterModule(['cart', 'shipping']);

// Conditional unregistration
if (store.hasModule('temporaryData')) {
  store.unregisterModule('temporaryData');
}

// Clean up route-specific modules
router.beforeEach((to, from, next) => {
  // Unregister previous route's modules
  if (from.meta.storeModules) {
    from.meta.storeModules.forEach(moduleName => {
      if (store.hasModule(moduleName)) {
        store.unregisterModule(moduleName);
      }
    });
  }
  
  // Register new route's modules
  if (to.meta.storeModules) {
    to.meta.storeModules.forEach(({ name, module }) => {
      store.registerModule(name, module);
    });
  }
  
  next();
});

Module Existence Check

Check if a module is registered.

/**
 * Check if a module exists
 * @param path - Module path (string or array)
 * @returns True if module exists
 */
hasModule(path: string | string[]): boolean;

Usage Examples:

// Check single module
if (store.hasModule('user')) {
  console.log('User module is registered');
}

// Check nested module
if (store.hasModule(['cart', 'payment'])) {
  store.dispatch('cart/payment/processPayment');
}

// Conditional registration
if (!store.hasModule('analytics')) {
  store.registerModule('analytics', analyticsModule);
}

// Guard against missing modules
const safeDispatch = (action, payload) => {
  const [module] = action.split('/');
  if (store.hasModule(module)) {
    return store.dispatch(action, payload);
  } else {
    console.warn(`Module ${module} not found for action ${action}`);
    return Promise.resolve();
  }
};

Namespaced Module Access

Access namespaced module state, getters, mutations, and actions.

Usage Examples:

const store = createStore({
  modules: {
    user: {
      namespaced: true,
      state: { profile: null },
      mutations: { setProfile(state, profile) { state.profile = profile; } },
      actions: { 
        async fetchProfile({ commit }, userId) {
          const profile = await api.getProfile(userId);
          commit('setProfile', profile);
        }
      },
      getters: { 
        displayName: state => state.profile?.name || 'Guest' 
      }
    },
    
    cart: {
      namespaced: true,
      state: { items: [] },
      modules: {
        shipping: {
          namespaced: true,
          state: { address: null },
          mutations: { setAddress(state, address) { state.address = address; } }
        }
      }
    }
  }
});

// Access namespaced state
const userProfile = store.state.user.profile;
const cartItems = store.state.cart.items;
const shippingAddress = store.state.cart.shipping.address;

// Access namespaced getters
const displayName = store.getters['user/displayName'];

// Commit namespaced mutations
store.commit('user/setProfile', profile);
store.commit('cart/shipping/setAddress', address);

// Dispatch namespaced actions
await store.dispatch('user/fetchProfile', userId);

// Using helpers with namespaces
import { mapState, mapActions } from 'vuex';

export default {
  computed: {
    ...mapState('user', ['profile']),
    ...mapState('cart', {
      cartItems: 'items'
    })
  },
  methods: {
    ...mapActions('user', ['fetchProfile']),
    ...mapActions('cart', ['addItem'])
  }
};

Root Access from Modules

Access root state and dispatch root actions from within modules.

Usage Examples:

const userModule = {
  namespaced: true,
  
  state: () => ({ profile: null }),
  
  actions: {
    async login({ commit, dispatch, rootState, rootGetters }, credentials) {
      // Access root state
      const appVersion = rootState.appVersion;
      
      // Access root getters
      const isOnline = rootGetters.isOnline;
      
      if (!isOnline) {
        throw new Error('Cannot login while offline');
      }
      
      const user = await api.login(credentials, { appVersion });
      commit('setProfile', user);
      
      // Dispatch root action
      await dispatch('analytics/trackEvent', {
        event: 'user_login',
        userId: user.id
      }, { root: true });
      
      // Commit root mutation
      commit('setLastLoginTime', Date.now(), { root: true });
    }
  },
  
  getters: {
    // Access root state in getters
    canAccessPremium: (state, getters, rootState, rootGetters) => {
      return state.profile?.tier === 'premium' || rootGetters.hasGlobalAccess;
    }
  }
};

Module Hot Reloading

Support for hot module replacement during development.

Usage Examples:

import { createStore } from 'vuex';
import userModule from './modules/user';
import cartModule from './modules/cart';

const store = createStore({
  modules: {
    user: userModule,
    cart: cartModule
  }
});

// Hot module replacement
if (module.hot) {
  // Accept updates for modules
  module.hot.accept(['./modules/user'], () => {
    const newUserModule = require('./modules/user').default;
    store.hotUpdate({
      modules: {
        user: newUserModule
      }
    });
  });
  
  // Accept updates for multiple modules
  module.hot.accept([
    './modules/user',
    './modules/cart'
  ], () => {
    const newUserModule = require('./modules/user').default;
    const newCartModule = require('./modules/cart').default;
    
    store.hotUpdate({
      modules: {
        user: newUserModule,
        cart: newCartModule
      }
    });
  });
}

export default store;

Module Composition Patterns

Advanced patterns for organizing and composing modules.

Usage Examples:

// Base module factory
const createCrudModule = (resource) => ({
  namespaced: true,
  
  state: () => ({
    items: [],
    loading: false,
    error: null
  }),
  
  mutations: {
    setItems(state, items) { state.items = items; },
    setLoading(state, loading) { state.loading = loading; },
    setError(state, error) { state.error = error; }
  },
  
  actions: {
    async fetchAll({ commit }) {
      commit('setLoading', true);
      try {
        const items = await api.getAll(resource);
        commit('setItems', items);
      } catch (error) {
        commit('setError', error);
      } finally {
        commit('setLoading', false);
      }
    }
  },
  
  getters: {
    byId: state => id => state.items.find(item => item.id === id),
    count: state => state.items.length
  }
});

// Create specific modules
const usersModule = {
  ...createCrudModule('users'),
  actions: {
    ...createCrudModule('users').actions,
    async login({ dispatch }, credentials) {
      // User-specific action
      const user = await api.login(credentials);
      dispatch('fetchAll'); // Inherited action
      return user;
    }
  }
};

const postsModule = createCrudModule('posts');

// Plugin-based module composition
const withCaching = (module, cacheKey) => ({
  ...module,
  actions: {
    ...module.actions,
    async fetchAll({ commit, state }) {
      const cached = localStorage.getItem(cacheKey);
      if (cached) {
        commit('setItems', JSON.parse(cached));
      } else {
        await module.actions.fetchAll({ commit, state });
        localStorage.setItem(cacheKey, JSON.stringify(state.items));
      }
    }
  }
});

const cachedUsersModule = withCaching(usersModule, 'cached_users');

Install with Tessl CLI

npx tessl i tessl/npm-vuex

docs

component-helpers.md

composition-api.md

development-tools.md

index.md

module-system.md

store-management.md

tile.json