@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.
- package/.prettierignore +9 -0
- package/.prettierrc.cjs +13 -0
- package/CONSUMING_APP_GUIDE.md +164 -0
- package/MIGRATION_SUMMARY.md +157 -0
- package/README.md +45 -0
- package/docs/app-store-guide.md +160 -0
- package/docs/property-store.md +192 -0
- package/docs/structured-store-migration.md +129 -0
- package/examples/property-store-migration.ts +164 -0
- package/index.html +29 -0
- package/package.json +53 -0
- package/playground/main.ts +38 -0
- package/src/adapters/dexie.ts +28 -0
- package/src/adapters/structured-store.ts +85 -0
- package/src/adapters/zustand-app.ts +221 -0
- package/src/adapters/zustand-unified.ts +342 -0
- package/src/adapters/zustand.ts +142 -0
- package/src/api/app.ts +270 -0
- package/src/api/favorites.ts +64 -0
- package/src/api/properties.ts +293 -0
- package/src/api/users.ts +66 -0
- package/src/db.ts +185 -0
- package/src/debug.ts +25 -0
- package/src/errors.ts +13 -0
- package/src/index.ts +34 -0
- package/src/schema.ts +48 -0
- package/src/storage.ts +71 -0
- package/src/stores/property.ts +133 -0
- package/src/stores/unified.ts +507 -0
- package/src/units/favorites.ts +19 -0
- package/src/validation.ts +32 -0
- package/test-export.js +6 -0
- package/tests/orm.spec.ts +17 -0
- package/tests/setup.ts +2 -0
- package/tsconfig.build.json +11 -0
- package/tsconfig.json +29 -0
- package/vite.config.ts +16 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Property Store
|
|
2
|
+
|
|
3
|
+
The Property Store provides a complete solution for managing property-related data in your application using IndexedDB as the storage backend.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Native IndexedDB storage** - Direct integration with the ORM's storage layer
|
|
8
|
+
- **Type-safe operations** - Full TypeScript support with Zod validation
|
|
9
|
+
- **Zustand compatibility** - Easy integration with existing Zustand stores
|
|
10
|
+
- **Async by default** - All operations are properly async for better performance
|
|
11
|
+
- **Centralized state management** - All property data managed in one place
|
|
12
|
+
|
|
13
|
+
## Core API
|
|
14
|
+
|
|
15
|
+
### PropertyStore Class
|
|
16
|
+
|
|
17
|
+
The core `PropertyStore` class provides direct access to property data operations:
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { propertyStore } from "@superbright/indexeddb-orm";
|
|
21
|
+
|
|
22
|
+
// Initialize a property
|
|
23
|
+
await propertyStore.initializeProperty("prop-123", "property-slug");
|
|
24
|
+
|
|
25
|
+
// Set current property context
|
|
26
|
+
await propertyStore.setPropertyId("prop-123");
|
|
27
|
+
await propertyStore.setPropertySlug("property-slug");
|
|
28
|
+
|
|
29
|
+
// Manage favorites
|
|
30
|
+
await propertyStore.toggleFavorite("unit-456");
|
|
31
|
+
|
|
32
|
+
// Track viewed units
|
|
33
|
+
await propertyStore.markUnitAsViewed("unit-456", "property-slug");
|
|
34
|
+
|
|
35
|
+
// Store questionnaire results
|
|
36
|
+
await propertyStore.setQuestionnaireResults({ maxRent: 2000, petFriendly: true });
|
|
37
|
+
|
|
38
|
+
// Get unit state
|
|
39
|
+
const unitState = await propertyStore.getUnitState("unit-456");
|
|
40
|
+
console.log(unitState.isFavorite, unitState.viewedDate);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Data Structure
|
|
44
|
+
|
|
45
|
+
Properties are stored with the following structure:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
interface PropertyData {
|
|
49
|
+
id: string;
|
|
50
|
+
slug: string;
|
|
51
|
+
favoritedUnits: string[];
|
|
52
|
+
tourContactedOn: string | null;
|
|
53
|
+
viewedUnits: {
|
|
54
|
+
unitId: string;
|
|
55
|
+
viewedDate: string;
|
|
56
|
+
}[];
|
|
57
|
+
questionnaireResults?: unknown | null;
|
|
58
|
+
tourContactData?: {
|
|
59
|
+
timezone: string;
|
|
60
|
+
favouriteUnits?: string[];
|
|
61
|
+
preferences: Record<string, any>;
|
|
62
|
+
} | null;
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Zustand Integration
|
|
67
|
+
|
|
68
|
+
For apps using Zustand, you can create a store that automatically syncs with the ORM:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { create } from "zustand";
|
|
72
|
+
import { devtools } from "zustand/middleware";
|
|
73
|
+
import {
|
|
74
|
+
createZustandPropertyStore,
|
|
75
|
+
createUseUnitState,
|
|
76
|
+
type ZustandPropertyStoreState
|
|
77
|
+
} from "@superbright/indexeddb-orm";
|
|
78
|
+
|
|
79
|
+
// Create store
|
|
80
|
+
export const usePropertyStore = create<ZustandPropertyStoreState>()(
|
|
81
|
+
devtools(
|
|
82
|
+
createZustandPropertyStore(),
|
|
83
|
+
{ name: "property-store" }
|
|
84
|
+
)
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// Initialize on app startup
|
|
88
|
+
export async function initializeApp() {
|
|
89
|
+
await usePropertyStore.getState()._hydrate();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Create unit state hook
|
|
93
|
+
export const useUnitState = createUseUnitState()(usePropertyStore);
|
|
94
|
+
|
|
95
|
+
// Use in components
|
|
96
|
+
function MyComponent() {
|
|
97
|
+
const { data, propertyId } = usePropertyStore();
|
|
98
|
+
const unitState = useUnitState("unit-123");
|
|
99
|
+
|
|
100
|
+
const handleToggleFavorite = async () => {
|
|
101
|
+
await usePropertyStore.getState().toggleFavorite("unit-123");
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<div>
|
|
106
|
+
<p>Property: {propertyId}</p>
|
|
107
|
+
<p>Is favorite: {unitState.isFavorite}</p>
|
|
108
|
+
<button onClick={handleToggleFavorite}>Toggle Favorite</button>
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Migration from Legacy Store
|
|
115
|
+
|
|
116
|
+
If you're migrating from a previous implementation using `createORMStringStorage` with Zustand persist middleware:
|
|
117
|
+
|
|
118
|
+
### Before (Legacy)
|
|
119
|
+
```typescript
|
|
120
|
+
import { create } from "zustand";
|
|
121
|
+
import { persist, createJSONStorage } from "zustand/middleware";
|
|
122
|
+
import { createORMStringStorage } from "@superbright/indexeddb-orm";
|
|
123
|
+
|
|
124
|
+
const ormStorage = createORMStringStorage({
|
|
125
|
+
fallbackToMemory: true,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
export const usePropertyStore = create(
|
|
129
|
+
persist(
|
|
130
|
+
(set, get) => ({
|
|
131
|
+
// ... store implementation
|
|
132
|
+
}),
|
|
133
|
+
{
|
|
134
|
+
name: "property",
|
|
135
|
+
storage: createJSONStorage(() => ormStorage),
|
|
136
|
+
}
|
|
137
|
+
)
|
|
138
|
+
);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### After (New)
|
|
142
|
+
```typescript
|
|
143
|
+
import { create } from "zustand";
|
|
144
|
+
import { devtools } from "zustand/middleware";
|
|
145
|
+
import { createZustandPropertyStore } from "@superbright/indexeddb-orm";
|
|
146
|
+
|
|
147
|
+
export const usePropertyStore = create()(
|
|
148
|
+
devtools(
|
|
149
|
+
createZustandPropertyStore(),
|
|
150
|
+
{ name: "property-store" }
|
|
151
|
+
)
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
// Don't forget to hydrate on app startup!
|
|
155
|
+
await usePropertyStore.getState()._hydrate();
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Key Changes
|
|
159
|
+
1. **No more persist middleware** - Storage is handled natively by the ORM
|
|
160
|
+
2. **All methods are async** - Better performance and error handling
|
|
161
|
+
3. **Type safety** - Built-in validation with Zod schemas
|
|
162
|
+
4. **Explicit hydration** - Must call `_hydrate()` on app startup
|
|
163
|
+
5. **Centralized data** - All property logic lives in the ORM
|
|
164
|
+
|
|
165
|
+
## API Reference
|
|
166
|
+
|
|
167
|
+
### PropertyStore Methods
|
|
168
|
+
|
|
169
|
+
- `initializeProperty(propertyId, slug)` - Initialize a new property
|
|
170
|
+
- `setPropertyId(id)` - Set current property ID
|
|
171
|
+
- `setPropertySlug(slug)` - Set current property slug
|
|
172
|
+
- `setData(data)` - Set all property data
|
|
173
|
+
- `removeData(key)` - Remove a property by ID
|
|
174
|
+
- `clearData()` - Clear all property data
|
|
175
|
+
- `toggleFavorite(unitId)` - Toggle unit favorite status
|
|
176
|
+
- `markUnitAsViewed(unitId, slug)` - Mark unit as viewed
|
|
177
|
+
- `setTourContactedOn()` - Mark tour as contacted
|
|
178
|
+
- `getTourContactedOn()` - Get tour contact date
|
|
179
|
+
- `setQuestionnaireResults(results)` - Store questionnaire results
|
|
180
|
+
- `setTourContactData(data)` - Store tour contact data
|
|
181
|
+
- `getUnitState(unitId)` - Get unit favorite/viewed state
|
|
182
|
+
- `getPropertyData(propertyId?)` - Get property data
|
|
183
|
+
- `getFullState()` - Get complete store state
|
|
184
|
+
|
|
185
|
+
### Zustand Store Actions
|
|
186
|
+
|
|
187
|
+
When using the Zustand adapter, all PropertyStore methods are available, plus:
|
|
188
|
+
|
|
189
|
+
- `_hydrate()` - Load data from IndexedDB into Zustand state
|
|
190
|
+
- `getUnitState(unitId)` - Synchronous unit state getter for React
|
|
191
|
+
|
|
192
|
+
All data modification methods automatically sync the Zustand state after updating IndexedDB.
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Structured Store Migration Guide
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The `InResiStoreActions` structure has been moved from the consuming app into the ORM library itself. Instead of manually creating action structures in the consuming app, you can now use the new `createStructuredStore` function that provides these actions directly.
|
|
6
|
+
|
|
7
|
+
## Migration
|
|
8
|
+
|
|
9
|
+
### Before (Old Pattern)
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import {
|
|
13
|
+
createZustandPropertyStore,
|
|
14
|
+
type ZustandUnifiedStoreState,
|
|
15
|
+
} from "@superbright/indexeddb-orm";
|
|
16
|
+
|
|
17
|
+
type InResiStoreActions = {
|
|
18
|
+
property: {
|
|
19
|
+
unit: {
|
|
20
|
+
favorites: {
|
|
21
|
+
toggle: (unitId: string) => Promise<void>;
|
|
22
|
+
};
|
|
23
|
+
viewed: {
|
|
24
|
+
mark: (unitId: string, slug: string) => Promise<void>;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
questionnaire: {
|
|
28
|
+
setResults: (results: unknown) => Promise<void>;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const baseStore = create<ZustandUnifiedStoreState>()(
|
|
34
|
+
devtools(createZustandPropertyStore(options))
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const inResiStoreActions: InResiStoreActions = {
|
|
38
|
+
property: {
|
|
39
|
+
unit: {
|
|
40
|
+
favorites: {
|
|
41
|
+
toggle: async (unitId: string) => {
|
|
42
|
+
await baseStore.getState().toggleFavorite(unitId);
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
viewed: {
|
|
46
|
+
mark: async (unitId: string, slug: string) => {
|
|
47
|
+
await baseStore.getState().markUnitAsViewed(unitId, slug);
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
questionnaire: {
|
|
52
|
+
setResults: async (results: unknown) => {
|
|
53
|
+
await baseStore.getState().setQuestionnaireResults(results);
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const useInResiStore = Object.assign(baseStore, inResiStoreActions);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### After (New Pattern)
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import {
|
|
66
|
+
createStructuredStore,
|
|
67
|
+
type StructuredStore,
|
|
68
|
+
} from "@superbright/indexeddb-orm";
|
|
69
|
+
|
|
70
|
+
const baseStore = create<StructuredStore>()(
|
|
71
|
+
devtools(createStructuredStore(options))
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
export const useInResiStore = baseStore;
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Usage
|
|
78
|
+
|
|
79
|
+
The structured actions are now directly available on the store:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
// Toggle favorite
|
|
83
|
+
await useInResiStore.getState().property.unit.favorites.toggle(unitId);
|
|
84
|
+
|
|
85
|
+
// Mark unit as viewed
|
|
86
|
+
await useInResiStore.getState().property.unit.viewed.mark(unitId, slug);
|
|
87
|
+
|
|
88
|
+
// Set questionnaire results
|
|
89
|
+
await useInResiStore.getState().property.questionnaire.setResults(results);
|
|
90
|
+
|
|
91
|
+
// All original store methods are still available
|
|
92
|
+
const currentProperty = await useInResiStore.getState().getCurrentProperty();
|
|
93
|
+
const unitState = useInResiStore.getState().getUnitState(unitId);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Available Structured Actions
|
|
97
|
+
|
|
98
|
+
The `StructuredStore` type includes all the original `ZustandUnifiedStoreState` methods plus the following structured actions:
|
|
99
|
+
|
|
100
|
+
### Property Actions
|
|
101
|
+
- `property.unit.favorites.toggle(unitId: string)` - Toggle unit favorite status
|
|
102
|
+
- `property.unit.viewed.mark(unitId: string, slug: string)` - Mark unit as viewed
|
|
103
|
+
- `property.questionnaire.setResults(results: unknown)` - Set questionnaire results
|
|
104
|
+
- `property.tour.setContactedOn()` - Set tour contacted timestamp
|
|
105
|
+
- `property.tour.getContactedOn()` - Get tour contacted timestamp
|
|
106
|
+
- `property.tour.setContactData(data: TourContactData)` - Set tour contact data
|
|
107
|
+
|
|
108
|
+
### Unit Actions
|
|
109
|
+
- `unit.data.set(unitId: string, data: UnitData)` - Set unit data
|
|
110
|
+
- `unit.data.remove(unitId: string)` - Remove unit data
|
|
111
|
+
- `unit.data.clear()` - Clear all unit data
|
|
112
|
+
- `unit.data.get(unitId: string)` - Get unit data
|
|
113
|
+
- `unit.state.get(unitId: string)` - Get unit state (favorite/viewed status)
|
|
114
|
+
|
|
115
|
+
### Filter Actions
|
|
116
|
+
- `filters.set(filters: Partial<Filters>)` - Set filters
|
|
117
|
+
- `filters.setTemp(filters: Partial<Filters>)` - Set temporary filters
|
|
118
|
+
- `filters.setToDefault()` - Reset filters to default
|
|
119
|
+
- `filters.commitTemp(key, defaultValue)` - Commit temporary filter changes
|
|
120
|
+
- `filters.commitAvailability()` - Commit availability filter changes
|
|
121
|
+
- `filters.submit()` - Submit all filter updates
|
|
122
|
+
|
|
123
|
+
## Benefits
|
|
124
|
+
|
|
125
|
+
1. **Centralized**: Actions are defined once in the ORM library
|
|
126
|
+
2. **Type Safe**: Full TypeScript support with proper types
|
|
127
|
+
3. **Consistent**: Same structure across all consuming applications
|
|
128
|
+
4. **Maintainable**: Changes to action signatures only need to be made in one place
|
|
129
|
+
5. **Extensible**: Easy to add new structured actions to the ORM library
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example: Unified Store Migration Guide
|
|
3
|
+
*
|
|
4
|
+
* This shows how to migrate from separate property and app stores
|
|
5
|
+
* to the new unified ORM store.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// === NEW APPROACH: Using ORM's unified store ===
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
// 1. In your consuming app, create unified Zustand store with ORM backend:
|
|
12
|
+
|
|
13
|
+
import { create } from "zustand";
|
|
14
|
+
import { devtools } from "zustand/middleware";
|
|
15
|
+
import {
|
|
16
|
+
createZustandPropertyStore, // More intuitive name
|
|
17
|
+
createUseUnitState,
|
|
18
|
+
type ZustandUnifiedStoreState,
|
|
19
|
+
type QueryParams
|
|
20
|
+
} from "@superbright/indexeddb-orm";
|
|
21
|
+
|
|
22
|
+
export const useStore = create<ZustandUnifiedStoreState>()(
|
|
23
|
+
devtools(
|
|
24
|
+
createZustandPropertyStore({
|
|
25
|
+
onFilterUpdate: (apiParams: QueryParams) => {
|
|
26
|
+
// Handle filter updates (replaces updateParams call)
|
|
27
|
+
console.log("Filters updated:", apiParams);
|
|
28
|
+
}
|
|
29
|
+
}),
|
|
30
|
+
{ name: "property-store" }
|
|
31
|
+
)
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// 2. Initialize the store on app startup
|
|
35
|
+
export async function initializeStore() {
|
|
36
|
+
await useStore.getState()._initialize();
|
|
37
|
+
await useStore.getState()._hydrate();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 3. Create the unit state hook
|
|
41
|
+
export const useUnitState = createUseUnitState()(useStore);
|
|
42
|
+
|
|
43
|
+
// 4. Export store instance (keep familiar naming)
|
|
44
|
+
export const propertyStore = useStore.getState;
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
// === USAGE EXAMPLES ===
|
|
48
|
+
|
|
49
|
+
import {
|
|
50
|
+
store,
|
|
51
|
+
type TourContactData,
|
|
52
|
+
type Filters as UnifiedFilters
|
|
53
|
+
} from "../src/stores/unified";
|
|
54
|
+
import { createZustandUnifiedStore } from "../src/adapters/zustand-unified";
|
|
55
|
+
|
|
56
|
+
// Direct ORM usage (without Zustand):
|
|
57
|
+
async function directUnifiedStoreUsage() {
|
|
58
|
+
// Initialize the store
|
|
59
|
+
await store.initialize();
|
|
60
|
+
|
|
61
|
+
// === Property operations ===
|
|
62
|
+
// Initialize a property
|
|
63
|
+
await store.initializeProperty("prop-123", "property-slug");
|
|
64
|
+
|
|
65
|
+
// Set current property
|
|
66
|
+
await store.setCurrentProperty("prop-123", "property-slug");
|
|
67
|
+
|
|
68
|
+
// Toggle a favorite
|
|
69
|
+
await store.toggleFavorite("unit-456");
|
|
70
|
+
|
|
71
|
+
// Mark unit as viewed
|
|
72
|
+
await store.markUnitAsViewed("unit-456", "property-slug");
|
|
73
|
+
|
|
74
|
+
// Set tour contact data
|
|
75
|
+
const tourData: TourContactData = {
|
|
76
|
+
timezone: "America/New_York",
|
|
77
|
+
favouriteUnits: ["unit-456"],
|
|
78
|
+
preferences: { bedrooms: 2 }
|
|
79
|
+
};
|
|
80
|
+
await store.setTourContactData(tourData);
|
|
81
|
+
|
|
82
|
+
// Set questionnaire results
|
|
83
|
+
await store.setQuestionnaireResults({ maxRent: 2000, petFriendly: true });
|
|
84
|
+
|
|
85
|
+
// Mark tour contacted
|
|
86
|
+
await store.setTourContactedOn();
|
|
87
|
+
|
|
88
|
+
// === App operations ===
|
|
89
|
+
// Set unit data
|
|
90
|
+
await store.setUnitData("unit-123", { isFavorite: true, viewedDate: "12/25" });
|
|
91
|
+
|
|
92
|
+
// Set filters
|
|
93
|
+
const filters: Partial<UnifiedFilters> = {
|
|
94
|
+
bedrooms: [1, 2],
|
|
95
|
+
cost: 2000,
|
|
96
|
+
highlights: ["pet-friendly", "balcony"]
|
|
97
|
+
};
|
|
98
|
+
await store.setFilters(filters);
|
|
99
|
+
|
|
100
|
+
// Set results mode
|
|
101
|
+
await store.setResultsMode("favorites");
|
|
102
|
+
|
|
103
|
+
// Set sorting
|
|
104
|
+
await store.setSortBy("priceLowToHigh");
|
|
105
|
+
|
|
106
|
+
// Handle questionnaire values
|
|
107
|
+
await store.setResolvedQuestionnaireValues("petPreference", ["dogs", "cats"]);
|
|
108
|
+
|
|
109
|
+
// === Get data ===
|
|
110
|
+
// Get unit state (combines property favorite + app unit data)
|
|
111
|
+
const unitState = await store.getUnitState("unit-456");
|
|
112
|
+
console.log("Is favorite:", unitState.isFavorite);
|
|
113
|
+
console.log("Viewed date:", unitState.viewedDate);
|
|
114
|
+
|
|
115
|
+
// Get results URL
|
|
116
|
+
const resultsUrl = await store.getResultsUrl();
|
|
117
|
+
console.log("Results URL:", resultsUrl);
|
|
118
|
+
|
|
119
|
+
// Get current state
|
|
120
|
+
const state = await store.getFullState();
|
|
121
|
+
console.log("Full state:", state);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Zustand store creation example:
|
|
125
|
+
function createExampleZustandUnifiedStore() {
|
|
126
|
+
return createZustandUnifiedStore({
|
|
127
|
+
onFilterUpdate: (apiParams) => {
|
|
128
|
+
console.log("Filters updated:", apiParams);
|
|
129
|
+
// Call your updateParams function here
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// === MIGRATION NOTES ===
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Key differences from the old approach:
|
|
138
|
+
*
|
|
139
|
+
* 1. Single unified store instead of separate property and app stores
|
|
140
|
+
* 2. No more manual persist middleware configuration
|
|
141
|
+
* 3. No more createORMStringStorage or custom IndexedDB setup
|
|
142
|
+
* 4. All storage is handled natively by the ORM
|
|
143
|
+
* 5. Better TypeScript support with proper schemas
|
|
144
|
+
* 6. Validation built-in via Zod schemas
|
|
145
|
+
* 7. Async operations are explicit
|
|
146
|
+
* 8. Must call _initialize() and _hydrate() on app initialization
|
|
147
|
+
*
|
|
148
|
+
* Breaking changes:
|
|
149
|
+
* - Two stores combined into one unified store
|
|
150
|
+
* - All store methods are now async
|
|
151
|
+
* - Must initialize store with _initialize() and _hydrate()
|
|
152
|
+
* - Property data is now in 'properties' object with currentPropertyId tracking
|
|
153
|
+
* - toggleFavorite no longer takes propertyId (uses currentPropertyId)
|
|
154
|
+
* - Unit data and property data are separate but related
|
|
155
|
+
*
|
|
156
|
+
* Benefits:
|
|
157
|
+
* - Single source of truth for all app state
|
|
158
|
+
* - Centralized data management in ORM
|
|
159
|
+
* - Better error handling and validation
|
|
160
|
+
* - More consistent API across all operations
|
|
161
|
+
* - Easier testing and mocking
|
|
162
|
+
* - Better performance with native IndexedDB operations
|
|
163
|
+
* - Simplified state management and debugging
|
|
164
|
+
*/
|
package/index.html
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>IndexedDB ORM Playground</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root { font-family: system-ui, sans-serif; }
|
|
9
|
+
body { max-width: 800px; margin: 2rem auto; padding: 1rem; }
|
|
10
|
+
button { margin: 0.25rem 0.5rem 0.25rem 0; padding: 0.5rem 0.75rem; }
|
|
11
|
+
pre { background: #111; color: #eee; padding: 1rem; border-radius: 6px; overflow: auto; }
|
|
12
|
+
.row { margin: 0.75rem 0; }
|
|
13
|
+
</style>
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<h1>IndexedDB ORM Playground</h1>
|
|
17
|
+
<p>Use these buttons to write/read data, then open DevTools → <strong>Application</strong> → <strong>IndexedDB</strong> → <code>inresi-orm</code>.</p>
|
|
18
|
+
<div class="row">
|
|
19
|
+
<button id="btn-init">Init DB</button>
|
|
20
|
+
<button id="btn-write">Write sample data</button>
|
|
21
|
+
<button id="btn-dump">Dump to console</button>
|
|
22
|
+
<button id="btn-export">Export JSON</button>
|
|
23
|
+
<button id="btn-reset">Reset DB</button>
|
|
24
|
+
</div>
|
|
25
|
+
<h3>Last dump</h3>
|
|
26
|
+
<pre id="dump">(nothing yet)</pre>
|
|
27
|
+
<script type="module" src="/playground/main.ts"></script>
|
|
28
|
+
</body>
|
|
29
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@superbright/indexeddb-orm",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Vite + TypeScript starter for an IndexedDB ORM (Dexie + Zod) with playground.",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.cjs.js",
|
|
8
|
+
"module": "./dist/index.es.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/index.es.js"
|
|
15
|
+
},
|
|
16
|
+
"require": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.cjs.js"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"scripts": {
|
|
24
|
+
"clean": "rimraf dist",
|
|
25
|
+
"build": "pnpm clean && vite build && tsc -p tsconfig.build.json",
|
|
26
|
+
"prepare": "pnpm build",
|
|
27
|
+
"start": "vite --clearScreen false",
|
|
28
|
+
"dev": "concurrently -k -n PLAY,BUILD \"pnpm:start\" \"pnpm:watch\"",
|
|
29
|
+
"watch": "concurrently -k -n TYPES,BUNDLE \"pnpm:watch:types\" \"pnpm:watch:bundle\"",
|
|
30
|
+
"watch:types": "tsc -w -p tsconfig.build.json",
|
|
31
|
+
"watch:bundle": "vite build --watch",
|
|
32
|
+
"test": "vitest run",
|
|
33
|
+
"test:watch": "vitest",
|
|
34
|
+
"format": "pnpm format:write",
|
|
35
|
+
"format:write": "prettier --write .",
|
|
36
|
+
"format:check": "prettier --check ."
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"dexie": "^4.0.8",
|
|
40
|
+
"zod": "^3.23.8"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^20.12.7",
|
|
44
|
+
"concurrently": "^9.0.0",
|
|
45
|
+
"fake-indexeddb": "^5.0.2",
|
|
46
|
+
"prettier": "^3.6.2",
|
|
47
|
+
"prettier-plugin-packagejson": "^2.5.19",
|
|
48
|
+
"rimraf": "^5.0.5",
|
|
49
|
+
"typescript": "^5.4.5",
|
|
50
|
+
"vite": "^5.4.0",
|
|
51
|
+
"vitest": "^2.0.5"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { getDB, resetDB, debugDump, exportJSON } from "../src/index";
|
|
2
|
+
|
|
3
|
+
const dumpEl = document.getElementById("dump")!;
|
|
4
|
+
|
|
5
|
+
function show(obj: unknown) {
|
|
6
|
+
dumpEl.textContent = JSON.stringify(obj, null, 2);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
document.getElementById("btn-init")!.addEventListener("click", async () => {
|
|
10
|
+
await getDB();
|
|
11
|
+
const data = await debugDump();
|
|
12
|
+
show(data);
|
|
13
|
+
console.log("DB initialized", data);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
document.getElementById("btn-write")!.addEventListener("click", async () => {
|
|
17
|
+
await getDB();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
document.getElementById("btn-dump")!.addEventListener("click", async () => {
|
|
21
|
+
const snapshot = await debugDump();
|
|
22
|
+
console.log("Snapshot:", snapshot);
|
|
23
|
+
show(snapshot);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
document.getElementById("btn-export")!.addEventListener("click", async () => {
|
|
27
|
+
await exportJSON();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
document.getElementById("btn-reset")!.addEventListener("click", async () => {
|
|
31
|
+
await resetDB();
|
|
32
|
+
show({});
|
|
33
|
+
console.warn("DB reset.");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
getDB().then(async () => {
|
|
37
|
+
show(await debugDump());
|
|
38
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import Dexie, { type Table } from "dexie";
|
|
2
|
+
import { SCHEMA_VERSION } from "../schema";
|
|
3
|
+
|
|
4
|
+
export interface KV {
|
|
5
|
+
key: string;
|
|
6
|
+
value: unknown;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface OrmDexieTables {
|
|
10
|
+
users: Table<any, string>;
|
|
11
|
+
kv: Table<KV, string>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class OrmDexie extends Dexie {
|
|
15
|
+
users!: OrmDexieTables["users"];
|
|
16
|
+
kv!: OrmDexieTables["kv"];
|
|
17
|
+
|
|
18
|
+
constructor(name = "inresi-orm") {
|
|
19
|
+
super(name);
|
|
20
|
+
this.version(SCHEMA_VERSION).stores({
|
|
21
|
+
users: "uuid",
|
|
22
|
+
kv: "key",
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
this.users = this.table("users");
|
|
26
|
+
this.kv = this.table("kv");
|
|
27
|
+
}
|
|
28
|
+
}
|