Schema and keys
Cana schema is plain data. IndexedDB applies schema changes only when the database version increases, so versioning is part of your application contract.
Versioned schema
import { createClient, validateSchema, type CanaSchema } from '@jumentix/cana';
const schemaV1: CanaSchema = {
version: 1,
stores: [
{
name: 'categories',
keyPath: 'id',
indexes: [{ name: 'byName', keyPath: 'name', unique: true }]
}
]
};
const schemaV2: CanaSchema = {
version: 2,
stores: [
{
name: 'categories',
keyPath: 'id',
indexes: [{ name: 'byName', keyPath: 'name', unique: true }]
},
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
};
const problems = validateSchema(schemaV2);
if (problems.length > 0) {
throw new Error(problems.join('\n'));
}
const firstClient = createClient({ name: 'tasks-schema-demo', schema: schemaV1 });
await firstClient.open();
await firstClient.table('categories').put({ id: 'work', name: 'Work' });
await firstClient.close();
const upgradedClient = createClient({ name: 'tasks-schema-demo', schema: schemaV2 });
await upgradedClient.open();
await upgradedClient.table('tasks').put({
id: 'task-1',
title: 'Created after schema upgrade',
categoryId: 'work',
completed: false,
priority: 'medium',
createdAt: Date.now(),
updatedAt: Date.now()
});Upgrade rules
| Change | Cana behavior |
|---|---|
| Add store | Applied during the next higher version. |
| Add index | Applied during the next higher version. |
| Remove a store from the schema object | The old store remains. Cana avoids accidental data loss. |
| Change an existing index definition | The old index remains. Use a new index name to rebuild intentionally. |
| Open an older version over newer data | Refused with UpgradeFailed. |
Key strategies
const inboundSchema: CanaSchema = {
version: 1,
stores: [{ name: 'tasks', keyPath: 'id' }]
};
const generatedInboundSchema: CanaSchema = {
version: 1,
stores: [{ name: 'tasks', keyPath: 'id', autoIncrement: true }]
};
const outboundSchema: CanaSchema = {
version: 1,
stores: [{ name: 'cache' }]
};Use inbound keys for application records such as categories and tasks.
Use outbound keys for cache entries where the key is metadata outside the
stored object.
await client.table('tasks').put({
id: 'task-1',
title: 'Stable inbound key',
categoryId: 'work',
completed: false,
priority: 'medium',
createdAt: 1,
updatedAt: 1
});
await client.table('cache').put(
{ body: '<html>cached response</html>', savedAt: Date.now() },
'https://example.com/tasks'
);Run it here
Schema upgrade
Start with Category, then raise the version and add the Task table.
### Schema upgrade
```ts
const v1 = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [{ name: 'categories', keyPath: 'id' }]
}
});
await v1.open();
await v1.table('categories').add({ id: 'work', name: 'Work' });
await v1.close();
const v2 = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await v2.open();
await v2.table('tasks').add({
id: 'task-1',
title: 'Created after upgrade',
categoryId: 'work',
completed: false,
priority: 'medium',
createdAt: Date.now(),
updatedAt: Date.now()
});
return {
categories: await v2.table('categories').query(),
tasks: await v2.table('tasks').query()
};
```const v1 = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [{ name: 'categories', keyPath: 'id' }]
}
});
await v1.open();
await v1.table('categories').add({ id: 'work', name: 'Work' });
await v1.close();
const v2 = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await v2.open();
await v2.table('tasks').add({
id: 'task-1',
title: 'Created after upgrade',
categoryId: 'work',
completed: false,
priority: 'medium',
createdAt: Date.now(),
updatedAt: Date.now()
});
return {
categories: await v2.table('categories').query(),
tasks: await v2.table('tasks').query()
};Keys
Use stable ids in Category and Task records.
### Keys
```ts
const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await client.open();
await client.table('categories').add({
id: 'docs',
name: 'Docs',
color: '#0f766e',
createdAt: 1,
updatedAt: 1
});
await client.table('tasks').add({
id: 'docs-1',
title: 'Document stable keys',
categoryId: 'docs',
completed: false,
priority: 'medium',
createdAt: 2,
updatedAt: 2
});
return {
categoryKey: 'docs',
taskKey: 'docs-1',
task: await client.table('tasks').get('docs-1')
};
```const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await client.open();
await client.table('categories').add({
id: 'docs',
name: 'Docs',
color: '#0f766e',
createdAt: 1,
updatedAt: 1
});
await client.table('tasks').add({
id: 'docs-1',
title: 'Document stable keys',
categoryId: 'docs',
completed: false,
priority: 'medium',
createdAt: 2,
updatedAt: 2
});
return {
categoryKey: 'docs',
taskKey: 'docs-1',
task: await client.table('tasks').get('docs-1')
};Next
Continue to reading, writing and bulk operations.