← Public packages
@kentcdodds/package-storage-migrations
Ordered idempotent packageStorage schema migrations + isolate-memoized runner. Caller always passes storage.
AGENTS.md
121 lines · 3.3 KB · Markdown@kentcdodds/package-storage-migrations — agent playbook
Human Intent lives in README.md. This file is the agent runbook.
Positive defaults
- Import the runner; pass the caller's storage:
import { runPackageStorageMigrations, createMigrationRunner, } from 'kody:@kentcdodds/package-storage-migrations' import { packageStorage } from 'kody:runtime' const ensureSchema = createMigrationRunner({ storage: packageStorage(), versionKey: 'my-app:schema-version', migrations: [/* version >= 1, unique, ascending when sorted */], }) await ensureSchema() - Prefer
createMigrationRunnerat module scope for Worker handlers so schema work runs at most once per isolate boot. - Never call
packageStorage()inside product migration helpers that live in this package — stamp would hit this package's bucket. Only./smokemay use this package's storage for self-test. - Version integers must be unique integers
>= 1. Duplicate versions throw.
Imports
| Specifier | Use |
|---|---|
kody:@kentcdodds/package-storage-migrations | runPackageStorageMigrations, createMigrationRunner, types |
kody:@kentcdodds/package-storage-migrations/smoke | Post-publish roundtrip against this package's bucket |
Smoke tests
After publish (./smoke)
import smoke from 'kody:@kentcdodds/package-storage-migrations/smoke'
export default async function main() {
return await smoke()
}Expect { ok: true, secondApplied: 0, sharedEnsure: true, … }.
Caller-style execute (copy-paste)
import {
runPackageStorageMigrations,
createMigrationRunner,
} from 'kody:@kentcdodds/package-storage-migrations'
export default async function main() {
const map = new Map()
const storage = {
get: async (k) => map.get(k),
set: async (k, v) => void map.set(k, v),
delete: async (k) => void map.delete(k),
}
await storage.set('notes-v1', [
{ id: '1', text: 'hi', createdAt: '2026-01-01T00:00:00.000Z' },
])
const migrations = [
{
version: 1,
name: 'notes-to-document',
async up(s) {
const legacy = await s.get('notes-v1')
if (Array.isArray(legacy)) {
await s.set('notes', { items: legacy })
await s.delete('notes-v1')
}
},
},
]
const first = await runPackageStorageMigrations({
storage,
versionKey: 't:v',
migrations,
})
const second = await runPackageStorageMigrations({
storage,
versionKey: 't:v',
migrations,
})
const ensure = createMigrationRunner({
storage,
versionKey: 't:v',
migrations,
})
const a = await ensure()
const b = await ensure()
return {
first,
second,
idempotent: second.applied.length === 0,
sharedEnsure: a === b,
}
}Edge cases
- Missing / non-numeric version key → treat as
0(all migrations pending). - String numeric versions in storage are accepted and floored.
- Failed
upleaves version unset for that step; runner promise is cleared so the nextensure()can retry. - Visibility stays private unless Kent asks to publish to /community.
Do not
- Rewrite
@kentcdodds/package-app-kitto depend on this until Patch confirms realtime tipbd4c9a4(or newer) is published. - Touch realtime files from this package lane.
- Call
packageStorage()on the factory / public API path (smoke only).