Skip to Content
Jumentix DocsPackages@jumentix/canaCana with Vue 3 and Pinia

Cana with Vue 3 and Pinia

Build the categorized task system with Vue 3 and Pinia. Cana owns durable browser storage, while Pinia owns the reactive state that components render.

1. Start from zero

bun create vite cana-vue-pinia --template vue-ts cd cana-vue-pinia bun add @jumentix/cana @jumentix/cana-vue pinia

Register Pinia in main.ts, create src/cana.ts, then define task stores under src/stores.

2. Simple implementation

The simple store action init() opens Cana, loads the current tables, and returns the unsubscribe function from client.subscribe.

Components call store actions:

  • tasks.addTask(title, categoryId)
  • tasks.toggleTask(task)

The store patches its arrays from CanaChangeEvent, so every component using the store re-renders from committed storage state.

Vue 3 + Pinia
simple

Vue 3 + Pinia: store patched from Cana

Pinia actions write to Cana and patch state from committed events.

Download app
{
  "name": "cana-vue-pinia-tasks",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc -b && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "@jumentix/cana": "^0.1.0",
    "@jumentix/cana-vue": "^0.1.0",
    "@vitejs/plugin-vue": "^6.0.5",
    "pinia": "^4.0.3",
    "typescript": "^6.0.3",
    "vite": "^7.2.7",
    "vue": "^3.5.41",
    "vue-tsc": "^3.1.8"
  }
}

3. Advanced implementation

The advanced store keeps lastCursor in localStorage, subscribes with sinceCursor, and reloads all tables when the replay cursor is no longer available. It also writes category and first task in one Cana transaction.

Use onUnmounted(() => stop()) in components or teardown logic so old views do not keep applying events after navigation.

Vue 3 + Pinia
advanced

Vue 3 + Pinia: replay-safe transaction flow

The Pinia store records the last Cana cursor and reloads when replay is unavailable.

Download app
{
  "name": "cana-vue-pinia-tasks",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc -b && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "@jumentix/cana": "^0.1.0",
    "@jumentix/cana-vue": "^0.1.0",
    "@vitejs/plugin-vue": "^6.0.5",
    "pinia": "^4.0.3",
    "typescript": "^6.0.3",
    "vite": "^7.2.7",
    "vue": "^3.5.41",
    "vue-tsc": "^3.1.8"
  }
}

4. Final app shape

src/
  cana.ts
  main.ts
  App.vue
  stores/
    tasks.ts
    advancedTasks.ts

Pinia getters should hold derived views such as tasks by category, not duplicated copies of Cana data.

Download the complete Vite app used by the advanced example: cana-vue-pinia.zip.

5. Checklist

  • createPinia() is installed before components use stores.
  • init() opens Cana, loads tables, then subscribes.
  • onUnmounted unsubscribes listeners.
  • Store actions write to Cana and state patches from committed events.
  • Advanced flows reload from tables when replay is unavailable.

Next

Compare the same model in React Context and React Redux.