Quick Start
Quick Start
This guide builds a resource cost estimator from scratch. By the end you have a fully working tool with reactive computed results and automatic IndexedDB persistence — in about 30 lines of schema code.
Step 1: Add the module
export default defineNuxtConfig({
modules: [
'./websideproject/modules/protokit',
// Optional: add yjs-sync to enable cross-device sync and server-side backups
// './websideproject/modules/yjs-sync',
],
// protokit options (all optional — defaults shown)
protokit: {
// serverSync: true, // default: enabled (no-ops gracefully if yjs-sync is absent)
// serverSync: false, // disable entirely — no HTTP requests, purely local
// serverSync: { baseUrl: '/api/yjs' }, // custom server base URL
},
})
serverSync defaults to true. If yjs-sync (or a compatible server) is not present, sync calls fail silently — local IndexedDB still works. Set serverSync: false to skip the HTTP calls entirely.Step 2: Define the schema
Create a schema file anywhere in your project:
export const resourceEstimator = definePrototype({
key: 'resource-estimator-v1', // unique — also the IndexedDB key
title: 'Resource Cost Estimator',
fields: {
teamSize: {
type: 'number',
label: 'Team Size',
default: 3,
},
avgMonthlySalary: {
type: 'number',
label: 'Avg. Monthly Salary (€)',
default: 4500,
},
toolingCosts: {
type: 'number',
label: 'Monthly Tooling Budget (€)',
default: 300,
},
projectDurationMonths: {
type: 'range',
label: 'Project Duration (months)',
min: 1,
max: 24,
step: 1,
default: 6,
},
overheadPercent: {
type: 'range',
label: 'Overhead / Benefits (%)',
min: 0,
max: 60,
step: 5,
default: 25,
},
},
derived: {
monthlySalaryCost: {
compute: ({ fields }) => fields.teamSize * fields.avgMonthlySalary,
format: { type: 'money', currency: '€' },
},
monthlyOverhead: {
compute: ({ fields, derived }) =>
derived.monthlySalaryCost * (fields.overheadPercent / 100),
format: { type: 'money', currency: '€' },
},
monthlyTotal: {
compute: ({ fields, derived }) =>
derived.monthlySalaryCost + derived.monthlyOverhead + fields.toolingCosts,
format: { type: 'money', currency: '€' },
},
projectTotal: {
compute: ({ fields, derived }) =>
derived.monthlyTotal * fields.projectDurationMonths,
format: { type: 'money', currency: '€' },
},
},
results: {
badge: ({ derived }) => {
const cost = derived.projectTotal
if (cost < 50_000) return { label: 'Small Project', color: 'primary' }
if (cost < 200_000) return { label: 'Medium Project', color: 'warning' }
return { label: 'Large Project', color: 'error' }
},
stats: ({ derived }) => [
{ label: 'Monthly People Cost', value: derived.monthlySalaryCost, format: 'money' },
{ label: 'Monthly Overhead', value: derived.monthlyOverhead, format: 'money' },
{ label: 'Monthly Total', value: derived.monthlyTotal, format: 'money' },
{ label: 'Project Total', value: derived.projectTotal, format: 'money' },
],
},
visualizations: [
{
type: 'bar-chart',
label: 'Monthly Cost Breakdown',
items: ({ fields, derived }) => [
{ label: 'Salaries', value: derived.monthlySalaryCost },
{ label: 'Overhead', value: derived.monthlyOverhead },
{ label: 'Tooling', value: fields.toolingCosts },
],
format: 'money',
},
],
})
definePrototype is a no-op identity function — it exists only for TypeScript inference. You can omit it and use a plain object if you prefer.Step 3: Render it
<script setup lang="ts">
import { resourceEstimator } from '~/composables/schemas/resourceEstimator'
</script>
<template>
<div class="max-w-2xl mx-auto p-6">
<ClientOnly>
<ProtoTool :schema="resourceEstimator" />
<template #fallback>
<div class="animate-pulse space-y-4">
<div class="h-10 bg-muted rounded" />
<div class="h-10 bg-muted rounded" />
</div>
</template>
</ClientOnly>
</div>
</template>
Step 4: See it work
npm run dev
Visit /estimator. Adjust the sliders. The derived values update instantly. Refresh the page — your values are still there, loaded from IndexedDB. No server call was needed to save them.
What you got for free
| Feature | Source in schema |
|---|---|
| Form with labels and default values | fields |
| Range sliders for duration and overhead | type: 'range' fields |
| Reactive computed totals | derived |
| Project size badge | results.badge |
| Four stat cards below the form | results.stats |
| Bar chart of monthly breakdown | visualizations |
| IndexedDB persistence (survives refresh) | Automatic via Y.js |
| Server sync when online | Automatic when protokit.serverSync is enabled + yjs-sync is installed |
| Corruption recovery modal | Automatic via useProtoDoc |
Multiple independent instances
Provide a custom doc-key to run multiple instances of the same schema with separate storage:
<ClientOnly>
<ProtoTool :schema="resourceEstimator" doc-key="estimator-project-a" />
</ClientOnly>
<ClientOnly>
<ProtoTool :schema="resourceEstimator" doc-key="estimator-project-b" />
</ClientOnly>
Local-only / demo tools
Add disable-sync to any tool that should never push to the server — regardless of the global config. Useful for public demo pages where you don't want to accumulate server snapshots per visitor:
<ClientOnly>
<ProtoTool :schema="resourceEstimator" disable-sync />
</ClientOnly>
The tool still saves to the user's local IndexedDB — inputs survive page refresh. Only the server push is suppressed.
Next steps
- Schema reference → — all field types, collection options, derived formatting
- Offline-first → — how Y.js + IndexedDB work and what survives what
- Composables → — use
usePrototypedirectly for custom rendering
Core Concepts
The mental model behind protokit — schemas, Y.js documents, the produces/consumes data graph, and how everything connects.
Overview
The PrototypeSchema type controls every aspect of a tool — its fields, derived values, collections, results, visualizations, and layout.