schema-idb 0.0.5 → 0.0.6

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 CHANGED
@@ -333,13 +333,30 @@ const db = openDB({
333
333
  name: "MyApp",
334
334
  versionStrategy: "auto",
335
335
  // 'error' (default): Throws an error when stores are removed
336
- // 'preserve': Renames removed stores to __storeName_deleted__ as backup.
336
+ // 'preserve': Renames removed stores to __storeName_deleted_v{version}__ as backup.
337
337
  // Preserved stores are isolated from the typed API to avoid future name collisions.
338
338
  removedStoreStrategy: "preserve",
339
339
  stores: [usersStore] as const,
340
340
  });
341
341
  ```
342
342
 
343
+ #### Behavior with explicit versioning
344
+
345
+ When `versionStrategy` is `"explicit"`:
346
+
347
+ - Schema changes are **detected** but **NOT applied** automatically
348
+ - `removedStoreStrategy` is evaluated for preview purposes only
349
+ - A warning is logged if schema changes are detected but version is not bumped
350
+
351
+ ```
352
+ [schema-idb] Schema changes detected but version not bumped:
353
+ - Rename store "oldStore" to "__oldStore_deleted_v2__"
354
+ Current DB version: 1, Provided version: 1
355
+ Bump the version to apply these changes.
356
+ ```
357
+
358
+ **Important:** `removedStoreStrategy` does not perform migrations in explicit mode. It only describes what _would_ happen after a version bump. To apply the changes, increment the `version` number.
359
+
343
360
  To explicitly delete a store (including backups), use a migration:
344
361
 
345
362
  ```ts
@@ -347,7 +364,7 @@ const usersStore = defineStore("users", {
347
364
  // ...
348
365
  }).addMigration("003-delete-old-store", (db) => {
349
366
  db.deleteObjectStore("oldStore");
350
- db.deleteObjectStore("__oldStore_deleted__"); // Remove backup too
367
+ db.deleteObjectStore("__oldStore_deleted_v2__"); // Remove backup too
351
368
  });
352
369
  ```
353
370
 
@@ -1 +1,9 @@
1
- "use strict";var v=Object.defineProperty;var l=(e,r)=>v(e,"name",{value:r,configurable:!0});import{openDatabase as T}from"./utils.js";import{createStoreAccessor as h}from"./storeAccessor.js";import{createStartTransaction as D}from"./transaction.js";import{determineAutoVersion as E,applySafeChanges as A,openDatabaseForSchemaRead as p}from"./schemaDetection.js";import{ensureSchemaHistoryStore as R,getAppliedMigrations as S,recordMigrationApplied as x,initializeSchemaHistory as z}from"./migrationHistory.js";function H(e){return e}l(H,"getKeyPathString");function M(e,r,o){const n=l(async()=>{if(await e.readyPromise,!e.idb)throw new Error("Database initialization failed");return h(e.idb,r,o)},"getAccessor");return new Proxy({},{get(t,i){return i==="query"?c=>c?n().then(d=>d.query(c)):k(e,r,o):async(...c)=>(await n())[i](...c)}})}l(M,"createLazyStoreAccessor");function k(e,r,o){const n=l(async()=>{if(await e.readyPromise,!e.idb)throw new Error("Database initialization failed");return h(e.idb,r,o).query()},"getQueryBuilder"),t=l(i=>new Proxy({},{get(c,d){return d==="findAll"||d==="find"||d==="count"?()=>i().then(s=>s[d]()):(...s)=>t(l(()=>i().then(f=>f[d](...s)),"newGetBuilder"))}}),"createBuilderProxy");return t(n)}l(k,"createLazyQueryBuilder");function j(e,r){const o={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,i]=n,c=Array.isArray(t)?t:[t];return q(e,c,i)}return e.startTransaction(...n)}};for(const n of e.stores)Object.defineProperty(o,n.name,{get(){return M(e,n.name,n.defaults)},enumerable:!0});return o}l(j,"buildSchemaDatabase");function q(e,r,o){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,o).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}l(q,"createLazyTransaction");function B(e){const r=[],o=new Set;for(const n of e)for(const t of n.migrations){if(o.has(t.name))throw new Error(`Duplicate migration name "${t.name}" found across stores`);o.add(t.name),r.push(t)}return r.sort((n,t)=>n.name.localeCompare(t.name))}l(B,"collectMigrations");function P(e,r){const o=new Set(r);return e.filter(n=>!o.has(n.name)).sort((n,t)=>n.name.localeCompare(t.name))}l(P,"filterPendingMigrations");function C(e,r,o,n,t,i,c){if(R(e),o===0){for(const s of n){const a=e.createObjectStore(s.name,{keyPath:s.keyPath});for(const f of s.indexes)a.createIndex(f.name,f.keyPath,{unique:f.unique??!1,multiEntry:f.multiEntry??!1})}z(r)}else c&&c.safe.length>0&&A(e,r,c.safe,n);let d=[...i];for(const s of t)try{const a=s.up(e,r);a instanceof Promise&&a.catch(f=>{console.error(`Migration "${s.name}" failed:`,f),r.abort()}),x(r,s.name,d),d=[...d,s.name].sort()}catch(a){throw console.error(`Migration "${s.name}" failed:`,a),r.abort(),a}}l(C,"handleUpgrade");export function openDB(e){const{name:r,version:o,versionStrategy:n="explicit",removedStoreStrategy:t="error",stores:i,onBlocked:c,onVersionChange:d}=e,s=new Set;for(const m of i){if(s.has(m.name))throw new Error(`Duplicate store name: "${m.name}"`);s.add(m.name)}const a=B(i);let f=l(()=>{},"readyResolve"),y=l(()=>{},"readyReject");const g={idb:null,ready:!1,error:null,readyPromise:new Promise((m,w)=>{f=m,y=w}),readyResolve:f,readyReject:y,stores:i,startTransaction:null},u=j(g,r);return F(g,r,o,n,t,i,a,c,d),u}l(openDB,"openDB");async function F(e,r,o,n,t,i,c,d,s){try{let a=[],f=null,y;if(n==="auto"){const u=await E(r,i,{removedStoreStrategy:t});if(y=u.version,f=u.changes,u.version>1){const w=await p(r);w&&(a=await S(w),w.close())}P(c,a).length>0&&!u.needsUpgrade&&(y=u.version+1)}else{if(o===void 0)throw new Error('Version is required when versionStrategy is "explicit"');y=o;const u=await p(r);u&&(a=await S(u),u.close())}const b=P(c,a),g=await T(r,y,(u,m,w)=>{C(u,m,w,i,b,a,f)},d);s&&(g.onversionchange=s),e.idb=g,e.startTransaction=D(g,i),e.ready=!0,e.readyResolve()}catch(a){e.error=a instanceof Error?a:new Error(String(a)),e.readyReject(e.error)}}l(F,"initializeDatabase");
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 D}from"./storeAccessor.js";import{createStartTransaction as A}from"./transaction.js";import{determineAutoVersion as k,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 D(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 D(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 p={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(p,r);return G(p,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 k(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 b=j(y,_);if(b.hasChanges){const S=[];for(const w of b.dangerous)w.type==="store_delete"&&t==="preserve"?b.safe.push({type:"store_rename",oldName:w.storeName,newName:`__${w.storeName}_deleted_v${f}__`}):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' to backup, or add a migration to explicitly delete it.`;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
+ `)}
4
+
5
+ Add explicit migrations to handle these changes safely.`;throw console.error("[schema-idb]",h),new Error(h)}if(b.dangerous=S,l=b,g<=f){const w=b.safe.map(h=>{switch(h.type){case"store_add":return`- Add 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
+ ${w.join(`
7
+ `)}
8
+ Current DB version: ${f}, Provided version: ${g}
9
+ Bump the version to apply these changes.`)}}}}const P=N(c,i),p=await T(r,g,(m,f,y)=>{V(m,f,y,o,P,i,l)},u);s&&(p.onversionchange=s),e.idb=p,e.startTransaction=A(p,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");
@@ -65,12 +65,6 @@ export declare function applySafeChanges(db: IDBDatabase, tx: IDBTransaction, ch
65
65
  keyPath: string | string[] | undefined;
66
66
  indexes: IndexDefinition[];
67
67
  }[]): void;
68
- export declare function generateSchemaFingerprint(stores: readonly {
69
- name: string;
70
- keyPath: string | string[] | undefined;
71
- indexes: IndexDefinition[];
72
- }[]): string;
73
- export declare function hashFingerprint(fingerprint: string): number;
74
68
  export declare function getCurrentDatabaseVersion(dbName: string): Promise<number>;
75
69
  export declare function openDatabaseForSchemaRead(dbName: string): Promise<IDBDatabase | null>;
76
70
  export interface AutoVersionOptions {
@@ -1,5 +1,5 @@
1
- "use strict";var g=Object.defineProperty;var h=(n,t)=>g(n,"name",{value:t,configurable:!0});function p(n){return n.startsWith("__")}h(p,"isInternalStore");export function readExistingSchema(n){const t=new Map,e=Array.from({length:n.objectStoreNames.length},(a,o)=>n.objectStoreNames.item(o)).filter(a=>!p(a));for(const a of e){const o=n.transaction(a,"readonly"),d=o.objectStore(a),s=new Map,c=Array.from({length:d.indexNames.length},(r,i)=>d.indexNames.item(i));for(const r of c){const i=d.index(r);s.set(r,{keyPath:i.keyPath,unique:i.unique,multiEntry:i.multiEntry})}t.set(a,{name:a,keyPath:d.keyPath,indexes:s}),o.abort()}return t}h(readExistingSchema,"readExistingSchema");export function toDesiredSchema(n){const t=new Map;for(const e of n)t.set(e.name,{name:e.name,keyPath:e.keyPath,indexes:e.indexes});return t}h(toDesiredSchema,"toDesiredSchema");function x(n,t){return n===t||n==null&&t==null?!0:n==null||t==null?!1:Array.isArray(n)&&Array.isArray(t)?n.length!==t.length?!1:n.every((e,a)=>e===t[a]):n===t}h(x,"keyPathEquals");export function detectSchemaChanges(n,t){const e=[],a=[];for(const[o,d]of t){const s=n.get(o);if(!s){e.push({type:"store_add",storeName:o});continue}if(!x(s.keyPath,d.keyPath)){a.push({type:"keypath_change",storeName:o,oldKeyPath:s.keyPath,newKeyPath:d.keyPath});continue}for(const r of d.indexes){const i=s.indexes.get(r.name);if(!i)e.push({type:"index_add",storeName:o,indexName:r.name,index:r});else{const m=!x(i.keyPath,r.keyPath),u=i.unique!==(r.unique??!1),l=i.multiEntry!==(r.multiEntry??!1);(m||u||l)&&(e.push({type:"index_delete",storeName:o,indexName:r.name}),e.push({type:"index_add",storeName:o,indexName:r.name,index:r}))}}const c=new Set(d.indexes.map(r=>r.name));for(const r of s.indexes.keys())c.has(r)||e.push({type:"index_delete",storeName:o,indexName:r})}for(const o of n.keys())t.has(o)||a.push({type:"store_delete",storeName:o});return{safe:e,dangerous:a,hasChanges:e.length>0||a.length>0}}h(detectSchemaChanges,"detectSchemaChanges");export function applySafeChanges(n,t,e,a){const o=e.filter(s=>s.type==="store_rename"),d=new Map;for(const s of o){const c=t.objectStore(s.oldName),r=[],i=Array.from({length:c.indexNames.length},(u,l)=>c.indexNames.item(l));for(const u of i){const l=c.index(u);r.push({name:u,keyPath:l.keyPath,unique:l.unique,multiEntry:l.multiEntry})}d.set(s.oldName,{keyPath:c.keyPath,indexes:r,newName:s.newName});const m=c.getAll();m.onsuccess=()=>{const u=m.result,l=d.get(s.oldName),f=n.createObjectStore(l.newName,{keyPath:l.keyPath});for(const y of l.indexes)f.createIndex(y.name,y.keyPath,{unique:y.unique,multiEntry:y.multiEntry});for(const y of u)f.put(y)},n.deleteObjectStore(s.oldName)}for(const s of e)switch(s.type){case"store_add":{const c=a.find(r=>r.name===s.storeName);if(c){const r=n.createObjectStore(c.name,{keyPath:c.keyPath});for(const i of c.indexes)r.createIndex(i.name,i.keyPath,{unique:i.unique??!1,multiEntry:i.multiEntry??!1})}break}case"index_add":{t.objectStore(s.storeName).createIndex(s.indexName,s.index.keyPath,{unique:s.index.unique??!1,multiEntry:s.index.multiEntry??!1});break}case"index_delete":{t.objectStore(s.storeName).deleteIndex(s.indexName);break}}}h(applySafeChanges,"applySafeChanges");export function generateSchemaFingerprint(n){const t=n.map(e=>({name:e.name,keyPath:e.keyPath,indexes:e.indexes.map(a=>({name:a.name,keyPath:a.keyPath,unique:a.unique??!1,multiEntry:a.multiEntry??!1})).sort((a,o)=>a.name.localeCompare(o.name))})).sort((e,a)=>e.name.localeCompare(a.name));return JSON.stringify(t)}h(generateSchemaFingerprint,"generateSchemaFingerprint");export function hashFingerprint(n){let t=0;for(let e=0;e<n.length;e++){const a=n.charCodeAt(e);t=(t<<5)-t+a,t=t&t}return Math.abs(t)}h(hashFingerprint,"hashFingerprint");export async function getCurrentDatabaseVersion(n){return new Promise(t=>{const e=indexedDB.open(n);e.onsuccess=()=>{const a=e.result,o=a.version;a.close(),t(o)},e.onerror=()=>{t(0)}})}h(getCurrentDatabaseVersion,"getCurrentDatabaseVersion");export async function openDatabaseForSchemaRead(n){return new Promise(t=>{const e=indexedDB.open(n);e.onsuccess=()=>{t(e.result)},e.onerror=()=>{t(null)},e.onupgradeneeded=()=>{e.transaction?.abort()}})}h(openDatabaseForSchemaRead,"openDatabaseForSchemaRead");export async function determineAutoVersion(n,t,e={}){const{removedStoreStrategy:a="error"}=e,o=await openDatabaseForSchemaRead(n);if(!o)return{version:1,changes:null,needsUpgrade:!0};const d=o.version,s=readExistingSchema(o),c=toDesiredSchema(t);o.close();const r=detectSchemaChanges(s,c);if(!r.hasChanges)return{version:d,changes:null,needsUpgrade:!1};const i=[];for(const m of r.dangerous)m.type==="store_delete"&&a==="preserve"?r.safe.push({type:"store_rename",oldName:m.storeName,newName:`__${m.storeName}_deleted__`}):i.push(m);if(r.dangerous=i,r.dangerous.length>0){const m=r.dangerous.map(u=>{switch(u.type){case"store_delete":return`Store "${u.storeName}" would be deleted. Use removedStoreStrategy: 'preserve' to backup, or add a migration to explicitly delete it.`;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"}});throw new Error(`Dangerous schema changes detected:
2
- ${m.join(`
1
+ "use strict";var g=Object.defineProperty;var l=(r,t)=>g(r,"name",{value:t,configurable:!0});function p(r){return r.startsWith("__")}l(p,"isInternalStore");export function readExistingSchema(r){const t=new Map,n=Array.from({length:r.objectStoreNames.length},(a,o)=>r.objectStoreNames.item(o)).filter(a=>!p(a));for(const a of n){const o=r.transaction(a,"readonly"),u=o.objectStore(a),s=new Map,c=Array.from({length:u.indexNames.length},(e,i)=>u.indexNames.item(i));for(const e of c){const i=u.index(e);s.set(e,{keyPath:i.keyPath,unique:i.unique,multiEntry:i.multiEntry})}t.set(a,{name:a,keyPath:u.keyPath,indexes:s}),o.abort()}return t}l(readExistingSchema,"readExistingSchema");export function toDesiredSchema(r){const t=new Map;for(const n of r)t.set(n.name,{name:n.name,keyPath:n.keyPath,indexes:n.indexes});return t}l(toDesiredSchema,"toDesiredSchema");function x(r,t){return r===t||r==null&&t==null?!0:r==null||t==null?!1:Array.isArray(r)&&Array.isArray(t)?r.length!==t.length?!1:r.every((n,a)=>n===t[a]):r===t}l(x,"keyPathEquals");export function detectSchemaChanges(r,t){const n=[],a=[];for(const[o,u]of t){const s=r.get(o);if(!s){n.push({type:"store_add",storeName:o});continue}if(!x(s.keyPath,u.keyPath)){a.push({type:"keypath_change",storeName:o,oldKeyPath:s.keyPath,newKeyPath:u.keyPath});continue}for(const e of u.indexes){const i=s.indexes.get(e.name);if(!i)n.push({type:"index_add",storeName:o,indexName:e.name,index:e});else{const m=!x(i.keyPath,e.keyPath),h=i.unique!==(e.unique??!1),d=i.multiEntry!==(e.multiEntry??!1);(m||h||d)&&(n.push({type:"index_delete",storeName:o,indexName:e.name}),n.push({type:"index_add",storeName:o,indexName:e.name,index:e}))}}const c=new Set(u.indexes.map(e=>e.name));for(const e of s.indexes.keys())c.has(e)||n.push({type:"index_delete",storeName:o,indexName:e})}for(const o of r.keys())t.has(o)||a.push({type:"store_delete",storeName:o});return{safe:n,dangerous:a,hasChanges:n.length>0||a.length>0}}l(detectSchemaChanges,"detectSchemaChanges");export function applySafeChanges(r,t,n,a){const o=n.filter(s=>s.type==="store_rename"),u=new Map;for(const s of o){const c=t.objectStore(s.oldName),e=[],i=Array.from({length:c.indexNames.length},(h,d)=>c.indexNames.item(d));for(const h of i){const d=c.index(h);e.push({name:h,keyPath:d.keyPath,unique:d.unique,multiEntry:d.multiEntry})}u.set(s.oldName,{keyPath:c.keyPath,indexes:e,newName:s.newName});const m=c.getAll();m.onsuccess=()=>{const h=m.result,d=u.get(s.oldName),y=r.createObjectStore(d.newName,{keyPath:d.keyPath});for(const f of d.indexes)y.createIndex(f.name,f.keyPath,{unique:f.unique,multiEntry:f.multiEntry});for(const f of h)y.put(f)},r.deleteObjectStore(s.oldName)}for(const s of n)switch(s.type){case"store_add":{const c=a.find(e=>e.name===s.storeName);if(c){const e=r.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":{t.objectStore(s.storeName).createIndex(s.indexName,s.index.keyPath,{unique:s.index.unique??!1,multiEntry:s.index.multiEntry??!1});break}case"index_delete":{t.objectStore(s.storeName).deleteIndex(s.indexName);break}}}l(applySafeChanges,"applySafeChanges");export async function getCurrentDatabaseVersion(r){return new Promise(t=>{const n=indexedDB.open(r);n.onsuccess=()=>{const a=n.result,o=a.version;a.close(),t(o)},n.onerror=()=>{t(0)}})}l(getCurrentDatabaseVersion,"getCurrentDatabaseVersion");export async function openDatabaseForSchemaRead(r){return new Promise(t=>{const n=indexedDB.open(r);n.onsuccess=()=>{t(n.result)},n.onerror=()=>{t(null)},n.onupgradeneeded=()=>{n.transaction?.abort(),t(null)}})}l(openDatabaseForSchemaRead,"openDatabaseForSchemaRead");export async function determineAutoVersion(r,t,n={}){const{removedStoreStrategy:a="error"}=n,o=await openDatabaseForSchemaRead(r);if(!o)return{version:1,changes:null,needsUpgrade:!0};const u=o.version,s=readExistingSchema(o),c=toDesiredSchema(t);o.close();const e=detectSchemaChanges(s,c);if(!e.hasChanges)return{version:u,changes:null,needsUpgrade:!1};const i=[];for(const m of e.dangerous)m.type==="store_delete"&&a==="preserve"?e.safe.push({type:"store_rename",oldName:m.storeName,newName:`__${m.storeName}_deleted_v${u}__`}):i.push(m);if(e.dangerous=i,e.dangerous.length>0){const h=`Dangerous schema changes detected:
2
+ ${e.dangerous.map(d=>{switch(d.type){case"store_delete":return`Store "${d.storeName}" would be deleted. Use removedStoreStrategy: 'preserve' to backup, or add a migration to explicitly delete it.`;case"keypath_change":return`Store "${d.storeName}" keyPath changed from "${d.oldKeyPath}" to "${d.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.`)}return{version:d+1,changes:r,needsUpgrade:!0}}h(determineAutoVersion,"determineAutoVersion");
5
+ Add explicit migrations to handle these changes safely.`;throw console.error("[schema-idb]",h),new Error(h)}return{version:u+1,changes:e,needsUpgrade:!0}}l(determineAutoVersion,"determineAutoVersion");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "schema-idb",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "Type-safe IndexedDB wrapper with chainable transactions",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -1,2 +0,0 @@
1
- import type { StoreDefinition, DatabaseConfig, DatabaseWithStores } from './types.js';
2
- export declare function openDB<const TStores extends readonly StoreDefinition[]>(config: DatabaseConfig<TStores>): Promise<DatabaseWithStores<TStores>>;
package/dist/createDB.js DELETED
@@ -1 +0,0 @@
1
- "use strict";var w=Object.defineProperty;var f=(n,t)=>w(n,"name",{value:t,configurable:!0});import{openDatabase as y,promisifyTransaction as b}from"./utils.js";import{createReadChain as S}from"./readChain.js";import{createWriteChain as v}from"./writeChain.js";import{createStoreAccessor as x}from"./storeAccessor.js";function D(n){const t=[],s=new Set;for(const r of n)for(const e of r.migrations){if(s.has(e.name))throw new Error(`Duplicate migration name "${e.name}" found across stores`);s.add(e.name),t.push(e)}return t.sort((r,e)=>r.name.localeCompare(e.name))}f(D,"collectMigrations");function E(n,t,s){const r={...n};for(const e of s)r[e.name]=x(t,e.name);return r}f(E,"buildDatabaseWithStores");function P(n,t,s,r,e){if(s===0)for(const a of r){const i=n.createObjectStore(a.name,{keyPath:a.keyPath,autoIncrement:a.autoIncrement});for(const c of a.indexes)i.createIndex(c.name,c.keyPath,{unique:c.unique??!1,multiEntry:c.multiEntry??!1})}for(const a of e)try{const i=a.up(n,t);i instanceof Promise&&i.catch(c=>{console.error(`Migration "${a.name}" failed:`,c),t.abort()})}catch(i){throw console.error(`Migration "${a.name}" failed:`,i),t.abort(),i}}f(P,"handleUpgrade");export async function openDB(n){const{name:t,version:s,versionStrategy:r="explicit",stores:e,onBlocked:a,onVersionChange:i}=n,c=new Set;for(const o of e){if(c.has(o.name))throw new Error(`Duplicate store name: "${o.name}"`);c.add(o.name)}let u;if(r==="auto")u=1;else{if(s===void 0)throw new Error('Version is required when versionStrategy is "explicit"');u=s}const p=D(e),m=await y(t,u,(o,l,d)=>{P(o,l,d,e,p)},a);return i&&(m.onversionchange=i),E({get name(){return m.name},get version(){return m.version},get raw(){return m},close(){m.close()},read(o){return S(m,o)},write(o){return v(m,o)},async transaction(o,l,d){const h=m.transaction(o,l),g=d(h);g instanceof Promise&&await g,await b(h)}},m,e)}f(openDB,"openDB");
@@ -1,5 +0,0 @@
1
- import type { StoreDefinition, StoreKeyPath, ExtractKeyType, StoreOptionsWithKeyPath, StoreOptionsWithoutKeyPath } from './types.js';
2
- export declare function defineStore<T>(): {
3
- <const TName extends string, const KP extends StoreKeyPath<T>>(name: TName, options: StoreOptionsWithKeyPath<T, KP>): StoreDefinition<T, ExtractKeyType<T, KP>, TName>;
4
- <const TName extends string, K extends IDBValidKey = IDBValidKey>(name: TName, options?: StoreOptionsWithoutKeyPath<T, K>): StoreDefinition<T, K, TName>;
5
- };
@@ -1 +0,0 @@
1
- "use strict";var p=Object.defineProperty;var i=(t,e)=>p(t,"name",{value:e,configurable:!0});function g(t){if(!t)return[];const e=[];for(const[r,o]of Object.entries(t))o!==!1&&(o===!0?e.push({name:r,keyPath:r}):e.push({name:r,keyPath:r,unique:o.unique,multiEntry:o.multiEntry}));return e}i(g,"parseIndexConfig");export function defineStore(){function t(e,r={}){const{keyPath:o,autoIncrement:u=!1,indexes:m,migrations:a=[]}=r,f=g(m);if(!e||typeof e!="string")throw new Error("Store name is required and must be a string");const s=new Set;for(const n of a){if(!n.name||typeof n.name!="string")throw new Error(`Invalid migration name in store "${e}": must be a non-empty string`);if(s.has(n.name))throw new Error(`Duplicate migration name "${n.name}" in store "${e}"`);s.add(n.name)}return{name:e,keyPath:o,autoIncrement:u,indexes:f,migrations:[...a].sort((n,c)=>n.name.localeCompare(c.name)),_schema:{},_keyType:{}}}return i(t,"createStore"),t}i(defineStore,"defineStore");
@@ -1,2 +0,0 @@
1
- import type { ReadChain, StoreDefinition } from './types.js';
2
- export declare function createReadChain<TStores extends readonly StoreDefinition[]>(db: IDBDatabase, storeNames: string[]): ReadChain<TStores, []>;
package/dist/readChain.js DELETED
@@ -1 +0,0 @@
1
- "use strict";var l=Object.defineProperty;var i=(s,u)=>l(s,"name",{value:u,configurable:!0});import{promisifyTransaction as p}from"./utils.js";function g(s){return s}i(g,"toReadChain");export function createReadChain(s,u){const r=[],c={get(n,t){return r.push({type:"get",storeName:n,key:t}),c},getAll(n,t){return r.push({type:"getAll",storeName:n,query:t?.query,count:t?.count}),c},getAllByIndex(n,t,o){return r.push({type:"getAllByIndex",storeName:n,indexName:t,query:o}),c},count(n,t){return r.push({type:"count",storeName:n,query:t}),c},async execute(){if(r.length===0)return[];const n=[...new Set(r.map(e=>e.storeName))];for(const e of n)if(!u.includes(e))throw new Error(`Store "${e}" is not in the transaction scope. Available stores: ${u.join(", ")}`);const t=s.transaction(u,"readonly"),o=[];for(const e of r){const a=t.objectStore(e.storeName);switch(e.type){case"get":o.push(a.get(e.key));break;case"getAll":o.push(a.getAll(e.query,e.count));break;case"getAllByIndex":o.push(a.index(e.indexName).getAll(e.query));break;case"count":o.push(a.count(e.query));break}}return await p(t),o.map(e=>e.result)}};return c}i(createReadChain,"createReadChain");
@@ -1,2 +0,0 @@
1
- import type { WriteChain, StoreDefinition } from './types.js';
2
- export declare function createWriteChain<TStores extends readonly StoreDefinition[]>(db: IDBDatabase, storeNames: string[]): WriteChain<TStores>;
@@ -1 +0,0 @@
1
- "use strict";var u=Object.defineProperty;var i=(n,o)=>u(n,"name",{value:o,configurable:!0});import{promisifyTransaction as p}from"./utils.js";function h(n){return n}i(h,"toWriteChain");export function createWriteChain(n,o){const a=[],s={put(t,r,e){return a.push({type:"put",storeName:t,value:r,key:e}),s},add(t,r,e){return a.push({type:"add",storeName:t,value:r,key:e}),s},delete(t,r){return a.push({type:"delete",storeName:t,key:r}),s},clear(t){return a.push({type:"clear",storeName:t}),s},async execute(){if(a.length===0)return;const t=[...new Set(a.map(e=>e.storeName))];for(const e of t)if(!o.includes(e))throw new Error(`Store "${e}" is not in the transaction scope. Available stores: ${o.join(", ")}`);const r=n.transaction(o,"readwrite");for(const e of a){const c=r.objectStore(e.storeName);switch(e.type){case"put":c.put(e.value,e.key);break;case"add":c.add(e.value,e.key);break;case"delete":c.delete(e.key);break;case"clear":c.clear();break}}await p(r)}};return s}i(createWriteChain,"createWriteChain");