@superbright/indexeddb-orm 0.1.1

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.
@@ -0,0 +1,9 @@
1
+ dist
2
+ node_modules
3
+ coverage
4
+ *.log
5
+ .vite
6
+ .vscode
7
+ .DS_Store
8
+ *.zip
9
+ inresi-orm-export.json
@@ -0,0 +1,13 @@
1
+ /** @type {import("prettier").Config} */
2
+ export default {
3
+ printWidth: 100,
4
+ tabWidth: 2,
5
+ useTabs: false,
6
+ semi: true,
7
+ singleQuote: true,
8
+ trailingComma: "all",
9
+ bracketSpacing: true,
10
+ arrowParens: "always",
11
+ endOfLine: "lf",
12
+ plugins: ["prettier-plugin-packagejson"],
13
+ };
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Consuming App Integration Guide
3
+ *
4
+ * Replace your existing stores with a single unified ORM-backed store
5
+ */
6
+
7
+ // 1. Remove these old imports:
8
+ // import { persist, createJSONStorage } from "zustand/middleware";
9
+ // import { get, set, del } from "idb-keyval";
10
+ // import { createORMStringStorage } from "@superbright/indexeddb-orm";
11
+
12
+ // 2. Add these new imports for the UNIFIED STORE:
13
+ import { create } from "zustand";
14
+ import { devtools } from "zustand/middleware";
15
+ import {
16
+ createZustandPropertyStore, // More intuitive name (alias for createZustandUnifiedStore)
17
+ createUseUnitState,
18
+ type ZustandUnifiedStoreState,
19
+ type Filters,
20
+ type QueryParams,
21
+ type UnitData,
22
+ type TourContactData
23
+ } from "@superbright/indexeddb-orm";
24
+
25
+ // 3. Replace BOTH your property store AND app store with one unified store:
26
+
27
+ // This replaces both usePropertyStore AND useStore
28
+ export const useStore = create<ZustandUnifiedStoreState>()(
29
+ devtools(
30
+ createZustandPropertyStore({
31
+ // Optional: callback for when filters are updated
32
+ onFilterUpdate: (apiParams: QueryParams) => {
33
+ // Replace your updateParams call here
34
+ // updateParams(apiParams);
35
+ console.log("Filters updated:", apiParams);
36
+ }
37
+ }),
38
+ { name: "property-store" }
39
+ )
40
+ );
41
+
42
+ // 4. Create the unit state hook (same as before):
43
+ export const useUnitState = createUseUnitState()(useStore);
44
+
45
+ // 5. Export store instance (replaces both propertyStore.getState and old useStore.getState):
46
+ export const propertyStore = useStore.getState;
47
+
48
+ // 6. Initialize the unified store in your app startup (e.g., main.tsx or App.tsx):
49
+ /*
50
+ import { useStore } from './stores/propertyStore'; // Keep the same file name
51
+
52
+ async function initializeApp() {
53
+ // Initialize and hydrate the unified store with data from IndexedDB
54
+ await useStore.getState()._initialize();
55
+ await useStore.getState()._hydrate();
56
+
57
+ // ... rest of your app initialization
58
+ }
59
+
60
+ // Call this on app startup
61
+ initializeApp();
62
+ */
63
+
64
+ // 7. Usage in components remains largely the same:
65
+ /*
66
+ function MyComponent() {
67
+ const { data, propertyId } = usePropertyStore();
68
+ const unitState = useUnitState("unit-123");
69
+
70
+ // NOTE: These are now async operations
71
+ const handleToggleFavorite = async () => {
72
+ await usePropertyStore.getState().toggleFavorite("unit-123");
73
+ };
74
+
75
+ const handleMarkViewed = async () => {
76
+ await usePropertyStore.getState().markUnitAsViewed("unit-123", "property-slug");
77
+ };
78
+
79
+ return (
80
+ <div>
81
+ <p>Property: {propertyId}</p>
82
+ <p>Is favorite: {unitState.isFavorite}</p>
83
+ <p>Viewed: {unitState.viewedDate}</p>
84
+ <button onClick={handleToggleFavorite}>Toggle Favorite</button>
85
+ <button onClick={handleMarkViewed}>Mark as Viewed</button>
86
+ </div>
87
+ );
88
+ }
89
+ */
90
+
91
+ // 7. Usage examples - the API combines both stores:
92
+ /*
93
+ function PropertyComponent() {
94
+ const {
95
+ // Property data (was usePropertyStore)
96
+ properties,
97
+ currentPropertyId,
98
+ currentPropertySlug,
99
+
100
+ // App data (was useStore)
101
+ units,
102
+ filters,
103
+ resultsMode,
104
+ sortBy
105
+ } = useStore();
106
+
107
+ const unitState = useUnitState("unit-123");
108
+
109
+ // Property operations
110
+ const handleToggleFavorite = async () => {
111
+ await useStore.getState().toggleFavorite("unit-123");
112
+ };
113
+
114
+ const handleMarkViewed = async () => {
115
+ await useStore.getState().markUnitAsViewed("unit-123", "property-slug");
116
+ };
117
+
118
+ // App operations
119
+ const handleFilterChange = async () => {
120
+ await useStore.getState().setFilters({ bedrooms: [1, 2] });
121
+ };
122
+
123
+ const handleSetUnitData = async () => {
124
+ await useStore.getState().setUnitData("unit-123", { isFavorite: true });
125
+ };
126
+
127
+ return (
128
+ <div>
129
+ <p>Property: {currentPropertyId}</p>
130
+ <p>Unit is favorite: {unitState.isFavorite}</p>
131
+ <p>Results mode: {resultsMode}</p>
132
+ <button onClick={handleToggleFavorite}>Toggle Favorite</button>
133
+ <button onClick={handleFilterChange}>Set Filters</button>
134
+ </div>
135
+ );
136
+ }
137
+ */
138
+
139
+ // 8. Key breaking changes to be aware of:
140
+ /*
141
+ BREAKING CHANGES:
142
+ - Two stores combined into one unified store
143
+ - All store methods are now async (return Promise<void>)
144
+ - Must call _initialize() and _hydrate() on app startup
145
+ - No more persist middleware configuration needed
146
+ - No more custom IndexedDB storage setup
147
+ - Property data is now in 'properties' object, current property tracked separately
148
+ - toggleFavorite() no longer takes propertyId parameter (uses currentPropertyId)
149
+ - loadPersistedFilters() is now redundant (always loads from IndexedDB)
150
+ - availabilityOptions array should be handled in UI components
151
+ - updateParams callback is now handled in store configuration
152
+
153
+ BENEFITS:
154
+ - Single source of truth for all app state
155
+ - Better type safety with Zod validation
156
+ - Centralized storage logic in ORM
157
+ - Better error handling and validation
158
+ - No more manual storage configuration
159
+ - More consistent API across all operations
160
+ - Better performance with native IndexedDB operations
161
+ - Automatic state synchronization
162
+ - Built-in callbacks for external integrations
163
+ - Easier state management and debugging
164
+ */
@@ -0,0 +1,157 @@
1
+ # Complete Store Migration Summary
2
+
3
+ This document provides a complete overview of migrating both the Property Store and App Store from the consuming application into a single unified IndexedDB ORM store.
4
+
5
+ ## What Was Extracted
6
+
7
+ ### Unified Store (Combines Both Previous Stores)
8
+ - **Original**: Two separate Zustand stores (property + app) with different storage mechanisms
9
+ - **New**: Single unified ORM-backed store with Zustand adapter
10
+ - **Key Features**:
11
+ - **Property data**: Multiple properties, current property tracking, favorites, viewed units, tour contacts, questionnaire results
12
+ - **App data**: Unit data, filters, temp filters, API filters, results mode, sorting, questionnaire values
13
+
14
+ ## Architecture Changes
15
+
16
+ ### Before (Consuming App)
17
+ ```
18
+ ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
19
+ │ Property Store │ │ Persist │ │ ORM String │
20
+ │ (Zustand) │───▶│ Middleware │───▶│ Storage │
21
+ └─────────────────┘ └──────────────────┘ └─────────────────┘
22
+
23
+ ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
24
+ │ App Store │ │ Persist │ │ idb-keyval │
25
+ │ (Zustand) │───▶│ Middleware │───▶│ Custom Storage │
26
+ └─────────────────┘ └──────────────────┘ └─────────────────┘
27
+ ```
28
+
29
+ ### After (Unified ORM-Centric)
30
+ ```
31
+ ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
32
+ │ Zustand │ │ Unified ORM │ │ IndexedDB │
33
+ │ Store │───▶│ Store Adapter │───▶│ (Native) │
34
+ │ (UI Layer) │ │ │ │ │
35
+ └─────────────────┘ └──────────────────┘ └─────────────────┘
36
+
37
+
38
+ ┌──────────────────┐
39
+ │ Direct ORM API │
40
+ │ (Server/Logic) │
41
+ └──────────────────┘
42
+ ```
43
+
44
+ ## New Unified ORM API
45
+
46
+ ### UnifiedStore (Direct ORM Usage)
47
+ ```typescript
48
+ import { store } from "@superbright/indexeddb-orm";
49
+
50
+ // Property operations
51
+ await store.initializeProperty("prop-123", "property-slug");
52
+ await store.setCurrentProperty("prop-123", "property-slug");
53
+ await store.toggleFavorite("unit-456");
54
+ await store.markUnitAsViewed("unit-456", "property-slug");
55
+
56
+ // App operations
57
+ await store.setUnitData("unit-123", { isFavorite: true });
58
+ await store.setFilters({ bedrooms: [1, 2], cost: 2000 });
59
+ await store.setResultsMode("favorites");
60
+
61
+ // Get data
62
+ const unitState = await store.getUnitState("unit-456");
63
+ const fullState = await store.getFullState();
64
+ ```
65
+
66
+ ## Zustand Integration
67
+
68
+ ### Unified Store (Replaces Both Previous Stores)
69
+ ```typescript
70
+ import { create } from "zustand";
71
+ import { devtools } from "zustand/middleware";
72
+ import { createZustandUnifiedStore } from "@superbright/indexeddb-orm";
73
+
74
+ // This replaces BOTH usePropertyStore AND useStore
75
+ export const useStore = create()(
76
+ devtools(
77
+ createZustandUnifiedStore({
78
+ onFilterUpdate: (apiParams) => {
79
+ // Handle filter updates (e.g., call updateParams)
80
+ updateParams(apiParams);
81
+ }
82
+ }),
83
+ { name: "property-store" } // Keep familiar naming
84
+ )
85
+ );
86
+
87
+ // Usage combines both property and app data
88
+ const {
89
+ // Property data
90
+ properties, currentPropertyId, currentPropertySlug,
91
+ // App data
92
+ units, filters, resultsMode, sortBy
93
+ } = useStore();
94
+ ```
95
+
96
+ ## Migration Checklist
97
+
98
+ ### ✅ Completed in ORM
99
+ - [x] UnifiedStore class combining both property and app functionality
100
+ - [x] Zustand adapter for unified store
101
+ - [x] Type definitions with Zod validation
102
+ - [x] Schema exports and integration
103
+ - [x] Legacy compatibility methods for smooth migration
104
+ - [x] Documentation and migration guides
105
+ - [x] Example usage code
106
+ - [x] Single storage mechanism for all data
107
+
108
+ ### 🔄 Required in Consuming App
109
+ - [ ] Remove old persist middleware imports
110
+ - [ ] Remove custom storage setup (idb-keyval, createORMStringStorage)
111
+ - [ ] Replace BOTH stores with single unified store
112
+ - [ ] Update imports to use unified store types
113
+ - [ ] Add store initialization calls in app startup
114
+ - [ ] Update component handlers to be async
115
+ - [ ] Handle updateParams callback in store configuration
116
+ - [ ] Remove availabilityOptions from store state (handle in UI)
117
+ - [ ] Update component state selectors (properties vs data)
118
+ - [ ] Test all existing functionality
119
+
120
+ ## Benefits
121
+
122
+ 1. **Single Source of Truth**: All app state in one unified store
123
+ 2. **Centralized Logic**: All storage logic lives in the ORM
124
+ 3. **Type Safety**: Full TypeScript + Zod validation
125
+ 4. **Better Performance**: Direct IndexedDB operations
126
+ 5. **Simplified Architecture**: One store instead of two
127
+ 6. **Testability**: Easier to mock and test centralized logic
128
+ 7. **Flexibility**: Can use direct ORM API or Zustand adapters
129
+ 8. **Error Handling**: Built-in validation and error handling
130
+ 9. **Consistent API**: Same patterns for all operations
131
+ 10. **Easier Debugging**: Single store to inspect and debug
132
+
133
+ ## Breaking Changes Summary
134
+
135
+ ### All Stores
136
+ - All methods are now async
137
+ - Must call initialization methods on app startup
138
+ - No more persist middleware configuration
139
+
140
+ ### Property Store Specific
141
+ - `toggleFavorite()` no longer takes propertyId parameter
142
+ - Questionnaire results can be any type (not just IFilters)
143
+
144
+ ### App Store Specific
145
+ - `loadPersistedFilters()` is redundant (always loads from IndexedDB)
146
+ - `availabilityOptions` should be handled in UI components
147
+ - `updateParams` callback moved to store configuration
148
+
149
+ ## Next Steps
150
+
151
+ 1. **Update Consuming App**: Follow the migration guides to update both stores
152
+ 2. **Test Thoroughly**: Ensure all existing functionality works
153
+ 3. **Remove Dead Code**: Clean up old storage-related code
154
+ 4. **Add Error Handling**: Leverage the improved error handling in the ORM
155
+ 5. **Consider Direct ORM Usage**: For server-side or complex logic, consider using the ORM APIs directly
156
+
157
+ The migration provides a solid foundation for scalable, type-safe data management while maintaining compatibility with existing Zustand-based UI patterns.
package/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # indexeddb-orm-starter
2
+
3
+ Minimal **Vite + TypeScript** starter for an IndexedDB ORM using **Dexie** + **Zod**, with a built-in browser playground.
4
+
5
+ ## Features
6
+ - Library build via Vite (ES + CJS) with type declarations (tsc)
7
+ - Dexie-powered tables: `users`, `properties`, `units`, and a `kv` store
8
+ - Helpers for **favourites** and **preferences**
9
+ - Schema version manifest + reset on incompatible version
10
+ - Vitest + fake-indexeddb tests
11
+ - Browser playground to inspect data
12
+
13
+ ## Dev / Playground
14
+ ```bash
15
+ pnpm i
16
+ pnpm dev
17
+ # Visit http://localhost:5173 (or printed URL)
18
+ # Use the buttons to write/dump/export, then open DevTools → Application → IndexedDB → inresi-orm
19
+ ```
20
+
21
+ ## Build & Test
22
+ ```bash
23
+ pnpm build
24
+ pnpm test
25
+ ```
26
+
27
+ ## Programmatic usage
28
+ ```ts
29
+ import {
30
+ getDB,
31
+ getFavouritedUnits, setFavouritedUnits,
32
+ getPreferences, setPreferences,
33
+ upsertUser, getUser,
34
+ debugDump, exportJSON
35
+ } from "indexeddb-orm-starter";
36
+
37
+ await getDB();
38
+ await setFavouritedUnits(["u1","u2"]);
39
+ console.log(await getFavouritedUnits());
40
+ await exportJSON();
41
+ ```
42
+
43
+ ## Notes
44
+ - Increment `SCHEMA_VERSION` in `src/schema.ts` to force a reset (basic strategy).
45
+ - Add entities/repositories in `src/api/*` following the same pattern.
@@ -0,0 +1,160 @@
1
+ /**
2
+ * App Store Integration Guide
3
+ *
4
+ * Replace your existing app store (useStore) with this new ORM-backed implementation
5
+ */
6
+
7
+ // 1. Remove these old imports:
8
+ // import { persist, createJSONStorage } from "zustand/middleware";
9
+ // import { get, set, del } from "idb-keyval";
10
+ // import { createORMStringStorage } from "@superbright/indexeddb-orm";
11
+
12
+ // 2. Add these new imports:
13
+ import { create } from "zustand";
14
+ import { devtools } from "zustand/middleware";
15
+ import {
16
+ createZustandAppStore,
17
+ type ZustandAppStoreState,
18
+ type Filters,
19
+ type QueryParams,
20
+ type UnitData
21
+ } from "@superbright/indexeddb-orm";
22
+
23
+ // 3. Replace your store creation:
24
+ export const useStore = create<ZustandAppStoreState>()(
25
+ devtools(
26
+ createZustandAppStore({
27
+ // Optional: callback for when filters are updated
28
+ onFilterUpdate: (apiParams: QueryParams) => {
29
+ // This replaces your updateParams call
30
+ // Import and call your updateParams function here
31
+ // updateParams(apiParams);
32
+ console.log("Filters updated:", apiParams);
33
+ }
34
+ }),
35
+ { name: "app-store" }
36
+ )
37
+ );
38
+
39
+ // 4. Initialize the store in your app startup (e.g., main.tsx or App.tsx):
40
+ /*
41
+ import { useStore } from './stores/appStore';
42
+
43
+ async function initializeApp() {
44
+ // Initialize and hydrate the store with data from IndexedDB
45
+ await useStore.getState()._initialize();
46
+ await useStore.getState()._hydrate();
47
+
48
+ // ... rest of your app initialization
49
+ }
50
+
51
+ // Call this on app startup
52
+ initializeApp();
53
+ */
54
+
55
+ // 5. Usage in components remains largely the same, but methods are now async:
56
+ /*
57
+ function FiltersComponent() {
58
+ const {
59
+ filters,
60
+ tempFilters,
61
+ resultsMode,
62
+ sortBy,
63
+ filtersLoaded
64
+ } = useStore();
65
+
66
+ // NOTE: These are now async operations
67
+ const handleFilterChange = async (key: keyof Filters, value: any) => {
68
+ await useStore.getState().handleTempFilterChange(key, value);
69
+ };
70
+
71
+ const handleCommitFilter = async () => {
72
+ await useStore.getState().submitFilterUpdate();
73
+ };
74
+
75
+ const handleResultsModeChange = async (mode: "all" | "bestFit" | "closestMatch" | "favorites") => {
76
+ await useStore.getState().setResultsMode(mode);
77
+ };
78
+
79
+ const handleSortChange = async (sortBy: "relevance" | "newest" | "priceLowToHigh" | "priceHighToLow") => {
80
+ await useStore.getState().setSortBy(sortBy);
81
+ };
82
+
83
+ return (
84
+ <div>
85
+ <p>Filters loaded: {filtersLoaded}</p>
86
+ <p>Current bedrooms: {filters.bedrooms?.join(", ")}</p>
87
+ <p>Results mode: {resultsMode}</p>
88
+ <p>Sort by: {sortBy}</p>
89
+ <button onClick={() => handleFilterChange("bedrooms", [1, 2])}>
90
+ Set 1-2 bedrooms
91
+ </button>
92
+ <button onClick={handleCommitFilter}>Apply Filters</button>
93
+ </div>
94
+ );
95
+ }
96
+
97
+ function UnitComponent({ unitId }: { unitId: string }) {
98
+ const { data } = useStore();
99
+ const unitData = data[unitId];
100
+
101
+ const handleToggleFavorite = async () => {
102
+ const currentData = data[unitId] || {};
103
+ await useStore.getState().setData(unitId, {
104
+ ...currentData,
105
+ isFavorite: !currentData.isFavorite
106
+ });
107
+ };
108
+
109
+ const handleMarkViewed = async () => {
110
+ const today = new Date();
111
+ const formattedDate = `${String(today.getMonth() + 1).padStart(2, "0")}/${String(today.getDate()).padStart(2, "0")}`;
112
+
113
+ const currentData = data[unitId] || {};
114
+ await useStore.getState().setData(unitId, {
115
+ ...currentData,
116
+ viewedDate: formattedDate
117
+ });
118
+ };
119
+
120
+ return (
121
+ <div>
122
+ <p>Unit: {unitId}</p>
123
+ <p>Is favorite: {unitData?.isFavorite ? "Yes" : "No"}</p>
124
+ <p>Viewed: {unitData?.viewedDate || "Never"}</p>
125
+ <button onClick={handleToggleFavorite}>Toggle Favorite</button>
126
+ <button onClick={handleMarkViewed}>Mark as Viewed</button>
127
+ </div>
128
+ );
129
+ }
130
+ */
131
+
132
+ // 6. Key breaking changes to be aware of:
133
+ /*
134
+ BREAKING CHANGES:
135
+ - All store methods are now async (return Promise<void>)
136
+ - Must call _initialize() and _hydrate() on app startup
137
+ - No more persist middleware configuration needed
138
+ - No more custom IndexedDB storage setup
139
+ - loadPersistedFilters() is now redundant (always loads from IndexedDB)
140
+ - availabilityOptions array is not stored (should be handled in UI)
141
+
142
+ BENEFITS:
143
+ - Better type safety with Zod validation
144
+ - Centralized storage logic in ORM
145
+ - Better error handling and validation
146
+ - No more manual storage configuration
147
+ - More consistent API across different data types
148
+ - Better performance with native IndexedDB operations
149
+ - Automatic state synchronization
150
+ - Built-in filter update callbacks
151
+
152
+ MIGRATION NOTES:
153
+ 1. The store structure remains the same, but all operations are async
154
+ 2. Remove custom IndexedDB setup (idb-keyval, custom storage)
155
+ 3. Replace persist middleware with ORM storage
156
+ 4. Add initialization calls in app startup
157
+ 5. Update component handlers to be async
158
+ 6. Handle updateParams callback in store configuration
159
+ 7. Remove availabilityOptions from store (handle in UI components)
160
+ */