schema-idb 0.0.6 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -6
- package/dist/createSchemaDB.d.ts +1 -1
- package/dist/createSchemaDB.js +4 -4
- package/dist/schemaDetection.d.ts +1 -1
- package/dist/schemaDetection.js +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -332,14 +332,18 @@ When a store is removed from the schema, you can choose how to handle it:
|
|
|
332
332
|
const db = openDB({
|
|
333
333
|
name: "MyApp",
|
|
334
334
|
versionStrategy: "auto",
|
|
335
|
-
|
|
336
|
-
// 'preserve': Renames removed stores to __storeName_deleted_v{version}__ as backup.
|
|
337
|
-
// Preserved stores are isolated from the typed API to avoid future name collisions.
|
|
338
|
-
removedStoreStrategy: "preserve",
|
|
335
|
+
removedStoreStrategy: "preserve", // See options below
|
|
339
336
|
stores: [usersStore] as const,
|
|
340
337
|
});
|
|
341
338
|
```
|
|
342
339
|
|
|
340
|
+
| Strategy | Behavior |
|
|
341
|
+
| -------- | -------- |
|
|
342
|
+
| `'error'` | Throws an error (default) |
|
|
343
|
+
| `'preserve'` | Renames to `__storeName_deleted_v{version}__` as backup |
|
|
344
|
+
| `'drop'` | Deletes the store and its data |
|
|
345
|
+
| `'ignore'` | Keeps the store as-is (no changes) |
|
|
346
|
+
|
|
343
347
|
#### Behavior with explicit versioning
|
|
344
348
|
|
|
345
349
|
When `versionStrategy` is `"explicit"`:
|
|
@@ -398,7 +402,7 @@ function openDB<T extends readonly SchemaStoreDefinition[]>(options: {
|
|
|
398
402
|
stores: T;
|
|
399
403
|
versionStrategy?: "auto" | "explicit";
|
|
400
404
|
version?: number;
|
|
401
|
-
removedStoreStrategy?: "error" | "preserve";
|
|
405
|
+
removedStoreStrategy?: "error" | "preserve" | "drop" | "ignore";
|
|
402
406
|
}): SchemaDatabase<T>;
|
|
403
407
|
```
|
|
404
408
|
|
|
@@ -408,7 +412,7 @@ function openDB<T extends readonly SchemaStoreDefinition[]>(options: {
|
|
|
408
412
|
| `stores` | `readonly SchemaStoreDefinition[]` | Store definitions created with `defineStore` |
|
|
409
413
|
| `versionStrategy` | `"auto" \| "explicit"` | `"auto"` detects schema changes automatically. Default: `"explicit"` (recommended for production control) |
|
|
410
414
|
| `version` | `number` | Required when `versionStrategy` is `"explicit"` |
|
|
411
|
-
| `removedStoreStrategy` | `"error" \| "preserve"` | How to handle removed stores. Default: `"error"` |
|
|
415
|
+
| `removedStoreStrategy` | `"error" \| "preserve" \| "drop" \| "ignore"` | How to handle removed stores. Default: `"error"` |
|
|
412
416
|
|
|
413
417
|
### SchemaDatabase
|
|
414
418
|
|
package/dist/createSchemaDB.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export interface SchemaDBConfig<TStores extends readonly AnySchemaStore[]> {
|
|
|
21
21
|
name: string;
|
|
22
22
|
version?: number;
|
|
23
23
|
versionStrategy?: 'explicit' | 'auto';
|
|
24
|
-
removedStoreStrategy?: 'error' | 'preserve';
|
|
24
|
+
removedStoreStrategy?: 'error' | 'preserve' | 'drop' | 'ignore';
|
|
25
25
|
stores: TStores;
|
|
26
26
|
onBlocked?: () => void;
|
|
27
27
|
onVersionChange?: () => void;
|
package/dist/createSchemaDB.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
"use strict";var E=Object.defineProperty;var d=(e,r)=>E(e,"name",{value:r,configurable:!0});import{openDatabase as T}from"./utils.js";import{createStoreAccessor as
|
|
2
|
-
${S.map(v=>{switch(v.type){case"store_delete":return`Store "${v.storeName}" would be deleted. Use removedStoreStrategy: 'preserve'
|
|
1
|
+
"use strict";var E=Object.defineProperty;var d=(e,r)=>E(e,"name",{value:r,configurable:!0});import{openDatabase as T}from"./utils.js";import{createStoreAccessor as P}from"./storeAccessor.js";import{createStartTransaction as k}from"./transaction.js";import{determineAutoVersion as A,applySafeChanges as R,openDatabaseForSchemaRead as $,readExistingSchema as M,toDesiredSchema as z,detectSchemaChanges as j}from"./schemaDetection.js";import{ensureSchemaHistoryStore as B,getAppliedMigrations as x,recordMigrationApplied as C,initializeSchemaHistory as q}from"./migrationHistory.js";function Y(e){return e}d(Y,"getKeyPathString");function U(e,r,a){const n=d(async()=>{if(await e.readyPromise,!e.idb)throw new Error("Database initialization failed");return P(e.idb,r,a)},"getAccessor");return new Proxy({},{get(t,o){return o==="query"?c=>c?n().then(u=>u.query(c)):F(e,r,a):async(...c)=>(await n())[o](...c)}})}d(U,"createLazyStoreAccessor");function F(e,r,a){const n=d(async()=>{if(await e.readyPromise,!e.idb)throw new Error("Database initialization failed");return P(e.idb,r,a).query()},"getQueryBuilder"),t=d(o=>new Proxy({},{get(c,u){return u==="findAll"||u==="find"||u==="count"?()=>o().then(s=>s[u]()):(...s)=>t(d(()=>o().then(l=>l[u](...s)),"newGetBuilder"))}}),"createBuilderProxy");return t(n)}d(F,"createLazyQueryBuilder");function K(e,r){const a={get name(){return e.idb?.name??r},get version(){return e.idb?.version??0},get raw(){if(!e.idb)throw new Error("Database not ready. Call waitForReady() first or check ready property.");return e.idb},get ready(){return e.ready},waitForReady(){return e.readyPromise},close(){e.idb?.close()},startTransaction(...n){if(!e.startTransaction){const[t,o]=n,c=Array.isArray(t)?t:[t];return L(e,c,o)}return e.startTransaction(...n)}};for(const n of e.stores)Object.defineProperty(a,n.name,{get(){return U(e,n.name,n.defaults)},enumerable:!0});return a}d(K,"buildSchemaDatabase");function L(e,r,a){const n={get raw(){throw new Error("Transaction raw is not available before ready state. Use await db.waitForReady() before starting transactions.")},async commit(){if(await e.readyPromise,!e.startTransaction)throw new Error("Database initialization failed");return e.startTransaction(r,a).commit()},abort(){}};for(const t of r)e.stores.find(c=>c.name===t)&&Object.defineProperty(n,t,{get(){throw new Error("Transaction operations before ready state are not yet supported. Use await db.waitForReady() before starting transactions.")},enumerable:!0});return n}d(L,"createLazyTransaction");function O(e){const r=[],a=new Set;for(const n of e)for(const t of n.migrations){if(a.has(t.name))throw new Error(`Duplicate migration name "${t.name}" found across stores`);a.add(t.name),r.push(t)}return r.sort((n,t)=>n.name.localeCompare(t.name))}d(O,"collectMigrations");function N(e,r){const a=new Set(r);return e.filter(n=>!a.has(n.name)).sort((n,t)=>n.name.localeCompare(t.name))}d(N,"filterPendingMigrations");function V(e,r,a,n,t,o,c){if(B(e),a===0){for(const s of n){const i=e.createObjectStore(s.name,{keyPath:s.keyPath});for(const l of s.indexes)i.createIndex(l.name,l.keyPath,{unique:l.unique??!1,multiEntry:l.multiEntry??!1})}q(r)}else c&&c.safe.length>0&&R(e,r,c.safe,n);let u=[...o];for(const s of t)try{const i=s.up(e,r);i instanceof Promise&&i.catch(l=>{console.error(`Migration "${s.name}" failed:`,l),r.abort()}),C(r,s.name,u),u=[...u,s.name].sort()}catch(i){throw console.error(`Migration "${s.name}" failed:`,i),r.abort(),i}}d(V,"handleUpgrade");export function openDB(e){const{name:r,version:a,versionStrategy:n="explicit",removedStoreStrategy:t="error",stores:o,onBlocked:c,onVersionChange:u}=e,s=new Set;for(const f of o){if(s.has(f.name))throw new Error(`Duplicate store name: "${f.name}"`);s.add(f.name)}const i=O(o);let l=d(()=>{},"readyResolve"),g=d(()=>{},"readyReject");const b={idb:null,ready:!1,error:null,readyPromise:new Promise((f,y)=>{l=f,g=y}),readyResolve:l,readyReject:g,stores:o,startTransaction:null},m=K(b,r);return G(b,r,a,n,t,o,i,c,u),m}d(openDB,"openDB");async function G(e,r,a,n,t,o,c,u,s){try{let i=[],l=null,g;if(n==="auto"){const m=await A(r,o,{removedStoreStrategy:t});if(g=m.version,l=m.changes,m.version>1){const y=await $(r);y&&(i=await x(y),y.close())}N(c,i).length>0&&!m.needsUpgrade&&(g=m.version+1)}else{if(a===void 0)throw new Error('Version is required when versionStrategy is "explicit"');g=a;const m=await $(r);if(m){const f=m.version;i=await x(m);const y=M(m),_=z(o);m.close();const p=j(y,_);if(p.hasChanges){const S=[];for(const w of p.dangerous)if(w.type==="store_delete")switch(t){case"preserve":p.safe.push({type:"store_rename",oldName:w.storeName,newName:`__${w.storeName}_deleted_v${f}__`});break;case"drop":p.safe.push(w);break;case"ignore":break;default:S.push(w);break}else S.push(w);if(S.length>0){const h=`Dangerous schema changes detected:
|
|
2
|
+
${S.map(v=>{switch(v.type){case"store_delete":return`Store "${v.storeName}" would be deleted. Use removedStoreStrategy: 'preserve', 'drop', or 'ignore' to handle this.`;case"keypath_change":return`Store "${v.storeName}" keyPath changed from "${v.oldKeyPath}" to "${v.newKeyPath}". This requires recreating the store with a manual migration.`;default:return"Unknown dangerous change"}}).join(`
|
|
3
3
|
`)}
|
|
4
4
|
|
|
5
|
-
Add explicit migrations to handle these changes safely.`;throw console.error("[schema-idb]",h),new Error(h)}if(
|
|
5
|
+
Add explicit migrations to handle these changes safely.`;throw console.error("[schema-idb]",h),new Error(h)}if(p.dangerous=S,l=p,g<=f){const w=p.safe.map(h=>{switch(h.type){case"store_add":return`- Add store "${h.storeName}"`;case"store_delete":return`- Delete store "${h.storeName}"`;case"store_rename":return`- Rename store "${h.oldName}" to "${h.newName}"`;case"index_add":return`- Add index "${h.indexName}" on "${h.storeName}"`;case"index_delete":return`- Delete index "${h.indexName}" from "${h.storeName}"`;default:return"- Schema change"}});console.warn(`[schema-idb] Schema changes detected but version not bumped:
|
|
6
6
|
${w.join(`
|
|
7
7
|
`)}
|
|
8
8
|
Current DB version: ${f}, Provided version: ${g}
|
|
9
|
-
Bump the version to apply these changes.`)}}}}const
|
|
9
|
+
Bump the version to apply these changes.`)}}}}const D=N(c,i),b=await T(r,g,(m,f,y)=>{V(m,f,y,o,D,i,l)},u);s&&(b.onversionchange=s),e.idb=b,e.startTransaction=k(b,o),e.ready=!0,e.readyResolve()}catch(i){e.error=i instanceof Error?i:new Error(String(i)),e.readyReject(e.error)}}d(G,"initializeDatabase");
|
|
@@ -68,7 +68,7 @@ export declare function applySafeChanges(db: IDBDatabase, tx: IDBTransaction, ch
|
|
|
68
68
|
export declare function getCurrentDatabaseVersion(dbName: string): Promise<number>;
|
|
69
69
|
export declare function openDatabaseForSchemaRead(dbName: string): Promise<IDBDatabase | null>;
|
|
70
70
|
export interface AutoVersionOptions {
|
|
71
|
-
removedStoreStrategy?: 'error' | 'preserve';
|
|
71
|
+
removedStoreStrategy?: 'error' | 'preserve' | 'drop' | 'ignore';
|
|
72
72
|
}
|
|
73
73
|
export declare function determineAutoVersion(dbName: string, stores: readonly {
|
|
74
74
|
name: string;
|
package/dist/schemaDetection.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
${e.dangerous.map(
|
|
1
|
+
"use strict";var x=Object.defineProperty;var l=(t,n)=>x(t,"name",{value:n,configurable:!0});function p(t){return t.startsWith("__")}l(p,"isInternalStore");export function readExistingSchema(t){const n=new Map,r=Array.from({length:t.objectStoreNames.length},(a,o)=>t.objectStoreNames.item(o)).filter(a=>!p(a));for(const a of r){const o=t.transaction(a,"readonly"),d=o.objectStore(a),s=new Map,c=Array.from({length:d.indexNames.length},(e,i)=>d.indexNames.item(i));for(const e of c){const i=d.index(e);s.set(e,{keyPath:i.keyPath,unique:i.unique,multiEntry:i.multiEntry})}n.set(a,{name:a,keyPath:d.keyPath,indexes:s}),o.abort()}return n}l(readExistingSchema,"readExistingSchema");export function toDesiredSchema(t){const n=new Map;for(const r of t)n.set(r.name,{name:r.name,keyPath:r.keyPath,indexes:r.indexes});return n}l(toDesiredSchema,"toDesiredSchema");function g(t,n){return t===n||t==null&&n==null?!0:t==null||n==null?!1:Array.isArray(t)&&Array.isArray(n)?t.length!==n.length?!1:t.every((r,a)=>r===n[a]):t===n}l(g,"keyPathEquals");export function detectSchemaChanges(t,n){const r=[],a=[];for(const[o,d]of n){const s=t.get(o);if(!s){r.push({type:"store_add",storeName:o});continue}if(!g(s.keyPath,d.keyPath)){a.push({type:"keypath_change",storeName:o,oldKeyPath:s.keyPath,newKeyPath:d.keyPath});continue}for(const e of d.indexes){const i=s.indexes.get(e.name);if(!i)r.push({type:"index_add",storeName:o,indexName:e.name,index:e});else{const h=!g(i.keyPath,e.keyPath),m=i.unique!==(e.unique??!1),u=i.multiEntry!==(e.multiEntry??!1);(h||m||u)&&(r.push({type:"index_delete",storeName:o,indexName:e.name}),r.push({type:"index_add",storeName:o,indexName:e.name,index:e}))}}const c=new Set(d.indexes.map(e=>e.name));for(const e of s.indexes.keys())c.has(e)||r.push({type:"index_delete",storeName:o,indexName:e})}for(const o of t.keys())n.has(o)||a.push({type:"store_delete",storeName:o});return{safe:r,dangerous:a,hasChanges:r.length>0||a.length>0}}l(detectSchemaChanges,"detectSchemaChanges");export function applySafeChanges(t,n,r,a){const o=r.filter(s=>s.type==="store_rename"),d=new Map;for(const s of o){const c=n.objectStore(s.oldName),e=[],i=Array.from({length:c.indexNames.length},(m,u)=>c.indexNames.item(u));for(const m of i){const u=c.index(m);e.push({name:m,keyPath:u.keyPath,unique:u.unique,multiEntry:u.multiEntry})}d.set(s.oldName,{keyPath:c.keyPath,indexes:e,newName:s.newName});const h=c.getAll();h.onsuccess=()=>{const m=h.result,u=d.get(s.oldName),y=t.createObjectStore(u.newName,{keyPath:u.keyPath});for(const f of u.indexes)y.createIndex(f.name,f.keyPath,{unique:f.unique,multiEntry:f.multiEntry});for(const f of m)y.put(f)},t.deleteObjectStore(s.oldName)}for(const s of r)switch(s.type){case"store_add":{const c=a.find(e=>e.name===s.storeName);if(c){const e=t.createObjectStore(c.name,{keyPath:c.keyPath});for(const i of c.indexes)e.createIndex(i.name,i.keyPath,{unique:i.unique??!1,multiEntry:i.multiEntry??!1})}break}case"index_add":{n.objectStore(s.storeName).createIndex(s.indexName,s.index.keyPath,{unique:s.index.unique??!1,multiEntry:s.index.multiEntry??!1});break}case"index_delete":{n.objectStore(s.storeName).deleteIndex(s.indexName);break}case"store_delete":{t.deleteObjectStore(s.storeName);break}}}l(applySafeChanges,"applySafeChanges");export async function getCurrentDatabaseVersion(t){return new Promise(n=>{const r=indexedDB.open(t);r.onsuccess=()=>{const a=r.result,o=a.version;a.close(),n(o)},r.onerror=()=>{n(0)}})}l(getCurrentDatabaseVersion,"getCurrentDatabaseVersion");export async function openDatabaseForSchemaRead(t){return new Promise(n=>{const r=indexedDB.open(t);r.onsuccess=()=>{n(r.result)},r.onerror=()=>{n(null)},r.onupgradeneeded=()=>{r.transaction?.abort(),n(null)}})}l(openDatabaseForSchemaRead,"openDatabaseForSchemaRead");export async function determineAutoVersion(t,n,r={}){const{removedStoreStrategy:a="error"}=r,o=await openDatabaseForSchemaRead(t);if(!o)return{version:1,changes:null,needsUpgrade:!0};const d=o.version,s=readExistingSchema(o),c=toDesiredSchema(n);o.close();const e=detectSchemaChanges(s,c);if(!e.hasChanges)return{version:d,changes:null,needsUpgrade:!1};const i=[];for(const h of e.dangerous)if(h.type==="store_delete")switch(a){case"preserve":e.safe.push({type:"store_rename",oldName:h.storeName,newName:`__${h.storeName}_deleted_v${d}__`});break;case"drop":e.safe.push(h);break;case"ignore":break;default:i.push(h);break}else i.push(h);if(e.dangerous=i,e.dangerous.length>0){const m=`Dangerous schema changes detected:
|
|
2
|
+
${e.dangerous.map(u=>{switch(u.type){case"store_delete":return`Store "${u.storeName}" would be deleted. Use removedStoreStrategy: 'preserve', 'drop', or 'ignore' to handle this.`;case"keypath_change":return`Store "${u.storeName}" keyPath changed from "${u.oldKeyPath}" to "${u.newKeyPath}". This requires recreating the store with a manual migration.`;default:return"Unknown dangerous change"}}).join(`
|
|
3
3
|
`)}
|
|
4
4
|
|
|
5
|
-
Add explicit migrations to handle these changes safely.`;throw console.error("[schema-idb]",
|
|
5
|
+
Add explicit migrations to handle these changes safely.`;throw console.error("[schema-idb]",m),new Error(m)}return e.safe.length===0?{version:d,changes:null,needsUpgrade:!1}:{version:d+1,changes:e,needsUpgrade:!0}}l(determineAutoVersion,"determineAutoVersion");
|