@superbright/indexeddb-orm 0.1.1 → 0.1.3
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 +370 -19
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.mjs +1154 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +6 -9
- package/.prettierignore +0 -9
- package/.prettierrc.cjs +0 -13
- package/CONSUMING_APP_GUIDE.md +0 -164
- package/MIGRATION_SUMMARY.md +0 -157
- package/docs/app-store-guide.md +0 -160
- package/docs/property-store.md +0 -192
- package/docs/structured-store-migration.md +0 -129
- package/examples/property-store-migration.ts +0 -164
- package/index.html +0 -29
- package/playground/main.ts +0 -38
- package/src/adapters/dexie.ts +0 -28
- package/src/adapters/structured-store.ts +0 -85
- package/src/adapters/zustand-app.ts +0 -221
- package/src/adapters/zustand-unified.ts +0 -342
- package/src/adapters/zustand.ts +0 -142
- package/src/api/app.ts +0 -270
- package/src/api/favorites.ts +0 -64
- package/src/api/properties.ts +0 -293
- package/src/api/users.ts +0 -66
- package/src/db.ts +0 -185
- package/src/debug.ts +0 -25
- package/src/errors.ts +0 -13
- package/src/index.ts +0 -34
- package/src/schema.ts +0 -48
- package/src/storage.ts +0 -71
- package/src/stores/property.ts +0 -133
- package/src/stores/unified.ts +0 -507
- package/src/units/favorites.ts +0 -19
- package/src/validation.ts +0 -32
- package/test-export.js +0 -6
- package/tests/orm.spec.ts +0 -17
- package/tests/setup.ts +0 -2
- package/tsconfig.build.json +0 -11
- package/tsconfig.json +0 -29
- package/vite.config.ts +0 -16
- package/vitest.config.ts +0 -9
package/src/adapters/zustand.ts
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import { propertyStore, type PropertyData, type TourContactData } from "../api/properties";
|
|
2
|
-
|
|
3
|
-
// Zustand-compatible store creation helper
|
|
4
|
-
export interface ZustandPropertyStoreState {
|
|
5
|
-
data: Record<string, PropertyData>;
|
|
6
|
-
propertySlug: string | null;
|
|
7
|
-
propertyId: string | null;
|
|
8
|
-
hasPreviouslySearched: string[];
|
|
9
|
-
|
|
10
|
-
// Async actions
|
|
11
|
-
setData: (value: Record<string, PropertyData>) => Promise<void>;
|
|
12
|
-
setPropertySlug: (slug: string) => Promise<void>;
|
|
13
|
-
setPropertyId: (id: string) => Promise<void>;
|
|
14
|
-
removeData: (key: string) => Promise<void>;
|
|
15
|
-
clearData: () => Promise<void>;
|
|
16
|
-
setHasPreviouslySearched: (slug: string) => Promise<void>;
|
|
17
|
-
toggleFavorite: (unitId: string) => Promise<void>;
|
|
18
|
-
markUnitAsViewed: (unitId: string, slug: string) => Promise<void>;
|
|
19
|
-
setTourContactedOn: () => Promise<void>;
|
|
20
|
-
getTourContactedOn: () => Promise<string | null>;
|
|
21
|
-
setQuestionnaireResults: (results: unknown) => Promise<void>;
|
|
22
|
-
setTourContactData: (data: TourContactData) => Promise<void>;
|
|
23
|
-
initializeProperty: (propertyId: string, slug: string) => Promise<void>;
|
|
24
|
-
|
|
25
|
-
// Sync getters
|
|
26
|
-
getUnitState: (unitId: string) => {
|
|
27
|
-
isFavorite: boolean;
|
|
28
|
-
viewedDate: string;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
// Internal hydration
|
|
32
|
-
_hydrate: () => Promise<void>;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function createZustandPropertyStore() {
|
|
36
|
-
return (set: any, get: any): ZustandPropertyStoreState => {
|
|
37
|
-
// Helper to update local state after ORM operations
|
|
38
|
-
const syncState = async () => {
|
|
39
|
-
const ormState = await propertyStore.getFullState();
|
|
40
|
-
set({
|
|
41
|
-
data: ormState.data,
|
|
42
|
-
propertySlug: ormState.propertySlug,
|
|
43
|
-
propertyId: ormState.propertyId,
|
|
44
|
-
hasPreviouslySearched: ormState.hasPreviouslySearched,
|
|
45
|
-
});
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
// Initial state
|
|
50
|
-
data: {},
|
|
51
|
-
propertySlug: null,
|
|
52
|
-
propertyId: null,
|
|
53
|
-
hasPreviouslySearched: [],
|
|
54
|
-
|
|
55
|
-
// Actions that modify state
|
|
56
|
-
async setData(value) {
|
|
57
|
-
await propertyStore.setData(value);
|
|
58
|
-
set({ data: value });
|
|
59
|
-
},
|
|
60
|
-
|
|
61
|
-
async setPropertySlug(slug) {
|
|
62
|
-
await propertyStore.setPropertySlug(slug);
|
|
63
|
-
set({ propertySlug: slug });
|
|
64
|
-
},
|
|
65
|
-
|
|
66
|
-
async setPropertyId(id) {
|
|
67
|
-
await propertyStore.setPropertyId(id);
|
|
68
|
-
set({ propertyId: id });
|
|
69
|
-
},
|
|
70
|
-
|
|
71
|
-
async removeData(key) {
|
|
72
|
-
await propertyStore.removeData(key);
|
|
73
|
-
await syncState();
|
|
74
|
-
},
|
|
75
|
-
|
|
76
|
-
async clearData() {
|
|
77
|
-
await propertyStore.clearData();
|
|
78
|
-
set({ data: {} });
|
|
79
|
-
},
|
|
80
|
-
|
|
81
|
-
async setHasPreviouslySearched(slug) {
|
|
82
|
-
await propertyStore.setHasPreviouslySearched(slug);
|
|
83
|
-
await syncState();
|
|
84
|
-
},
|
|
85
|
-
|
|
86
|
-
async toggleFavorite(unitId) {
|
|
87
|
-
await propertyStore.toggleFavorite(unitId);
|
|
88
|
-
await syncState();
|
|
89
|
-
},
|
|
90
|
-
|
|
91
|
-
async markUnitAsViewed(unitId, slug) {
|
|
92
|
-
await propertyStore.markUnitAsViewed(unitId, slug);
|
|
93
|
-
await syncState();
|
|
94
|
-
},
|
|
95
|
-
|
|
96
|
-
async setTourContactedOn() {
|
|
97
|
-
await propertyStore.setTourContactedOn();
|
|
98
|
-
await syncState();
|
|
99
|
-
},
|
|
100
|
-
|
|
101
|
-
getTourContactedOn: propertyStore.getTourContactedOn.bind(propertyStore),
|
|
102
|
-
|
|
103
|
-
async setQuestionnaireResults(results) {
|
|
104
|
-
await propertyStore.setQuestionnaireResults(results);
|
|
105
|
-
await syncState();
|
|
106
|
-
},
|
|
107
|
-
|
|
108
|
-
async setTourContactData(data) {
|
|
109
|
-
await propertyStore.setTourContactData(data);
|
|
110
|
-
await syncState();
|
|
111
|
-
},
|
|
112
|
-
|
|
113
|
-
async initializeProperty(propertyId, slug) {
|
|
114
|
-
await propertyStore.initializeProperty(propertyId, slug);
|
|
115
|
-
await syncState();
|
|
116
|
-
},
|
|
117
|
-
|
|
118
|
-
// Sync getter for unit state
|
|
119
|
-
getUnitState(unitId: string) {
|
|
120
|
-
const state = get();
|
|
121
|
-
const property = state.propertyId ? state.data[state.propertyId] : null;
|
|
122
|
-
|
|
123
|
-
return {
|
|
124
|
-
isFavorite: property?.favoritedUnits.includes(unitId) ?? false,
|
|
125
|
-
viewedDate:
|
|
126
|
-
property?.viewedUnits.find((u: any) => u.unitId === unitId)?.viewedDate ?? "",
|
|
127
|
-
};
|
|
128
|
-
},
|
|
129
|
-
|
|
130
|
-
// Internal hydration method
|
|
131
|
-
async _hydrate() {
|
|
132
|
-
await syncState();
|
|
133
|
-
},
|
|
134
|
-
};
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Helper hook factory for React apps
|
|
139
|
-
export function createUseUnitState() {
|
|
140
|
-
return (useStore: any) => (unitId: string) =>
|
|
141
|
-
useStore((state: ZustandPropertyStoreState) => state.getUnitState(unitId));
|
|
142
|
-
}
|
package/src/api/app.ts
DELETED
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { kvGet, kvSet } from "../storage";
|
|
3
|
-
|
|
4
|
-
// App-related schemas
|
|
5
|
-
export const UnitDataSchema = z.object({
|
|
6
|
-
isFavorite: z.boolean().optional(),
|
|
7
|
-
viewedDate: z.string().optional(),
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
export const FiltersSchema = z.object({
|
|
11
|
-
availability: z.union([z.string(), z.array(z.string())]).nullable().optional(),
|
|
12
|
-
bedrooms: z.array(z.number()).nullable().optional(),
|
|
13
|
-
cost: z.number().nullable().optional(),
|
|
14
|
-
highlights: z.array(z.string()).optional(),
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
export const QueryParamsSchema = z.object({
|
|
18
|
-
limit: z.number().default(10),
|
|
19
|
-
page: z.number().default(1),
|
|
20
|
-
availability: z.array(z.string()).optional(),
|
|
21
|
-
bedrooms: z.array(z.number()).optional(),
|
|
22
|
-
cost: z.number().nullable().optional(),
|
|
23
|
-
highlights: z.array(z.string()).optional(),
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
export const AppStoreDataSchema = z.object({
|
|
27
|
-
data: z.record(UnitDataSchema),
|
|
28
|
-
filters: FiltersSchema,
|
|
29
|
-
tempFilters: FiltersSchema,
|
|
30
|
-
apiFilters: QueryParamsSchema,
|
|
31
|
-
resultsMode: z.enum(["all", "bestFit", "closestMatch", "favorites"]),
|
|
32
|
-
propertySlug: z.string().nullable(),
|
|
33
|
-
resolvedQuestionnaireValues: z.record(z.array(z.string())),
|
|
34
|
-
sortBy: z.enum(["relevance", "newest", "priceLowToHigh", "priceHighToLow"]),
|
|
35
|
-
filtersLoaded: z.boolean(),
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
export type UnitData = z.infer<typeof UnitDataSchema>;
|
|
39
|
-
export type Filters = z.infer<typeof FiltersSchema>;
|
|
40
|
-
export type QueryParams = z.infer<typeof QueryParamsSchema>;
|
|
41
|
-
export type AppStoreData = z.infer<typeof AppStoreDataSchema>;
|
|
42
|
-
export type ResultsMode = "all" | "bestFit" | "closestMatch" | "favorites";
|
|
43
|
-
export type SortBy = "relevance" | "newest" | "priceLowToHigh" | "priceHighToLow";
|
|
44
|
-
|
|
45
|
-
// Default values
|
|
46
|
-
const defaultFilters: Filters = {
|
|
47
|
-
availability: undefined,
|
|
48
|
-
bedrooms: undefined,
|
|
49
|
-
cost: undefined,
|
|
50
|
-
highlights: undefined,
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const defaultAppStoreData: AppStoreData = {
|
|
54
|
-
data: {},
|
|
55
|
-
filters: defaultFilters,
|
|
56
|
-
tempFilters: defaultFilters,
|
|
57
|
-
apiFilters: {
|
|
58
|
-
limit: 10,
|
|
59
|
-
page: 1,
|
|
60
|
-
},
|
|
61
|
-
resultsMode: "all",
|
|
62
|
-
propertySlug: null,
|
|
63
|
-
resolvedQuestionnaireValues: {},
|
|
64
|
-
sortBy: "relevance",
|
|
65
|
-
filtersLoaded: false,
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
// Core app store API
|
|
69
|
-
export class AppStore {
|
|
70
|
-
private async getState(): Promise<AppStoreData> {
|
|
71
|
-
const state = await kvGet<AppStoreData>("app");
|
|
72
|
-
return state ?? defaultAppStoreData;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
private async setState(updater: (state: AppStoreData) => AppStoreData): Promise<void> {
|
|
76
|
-
const currentState = await this.getState();
|
|
77
|
-
const newState = updater(currentState);
|
|
78
|
-
await kvSet("app", newState);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Unit data operations
|
|
82
|
-
async setData(key: string, value: UnitData): Promise<void> {
|
|
83
|
-
await this.setState(state => ({
|
|
84
|
-
...state,
|
|
85
|
-
data: { ...state.data, [key]: value }
|
|
86
|
-
}));
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async removeData(key: string): Promise<void> {
|
|
90
|
-
await this.setState(state => {
|
|
91
|
-
const { [key]: removed, ...rest } = state.data;
|
|
92
|
-
return { ...state, data: rest };
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async clearData(): Promise<void> {
|
|
97
|
-
await this.setState(state => ({ ...state, data: {} }));
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Filter operations
|
|
101
|
-
async setFilters(filters: Partial<Filters>): Promise<void> {
|
|
102
|
-
await this.setState(state => ({
|
|
103
|
-
...state,
|
|
104
|
-
filters: { ...state.filters, ...filters }
|
|
105
|
-
}));
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
async setTempFilters(filters: Partial<Filters>): Promise<void> {
|
|
109
|
-
await this.setState(state => ({
|
|
110
|
-
...state,
|
|
111
|
-
tempFilters: { ...state.tempFilters, ...filters }
|
|
112
|
-
}));
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
async setFiltersToDefault(): Promise<void> {
|
|
116
|
-
await this.setState(state => ({ ...state, filters: defaultFilters }));
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
async setApiFilters(filters: Partial<QueryParams>): Promise<void> {
|
|
120
|
-
await this.setState(state => ({
|
|
121
|
-
...state,
|
|
122
|
-
apiFilters: { ...state.apiFilters, ...filters }
|
|
123
|
-
}));
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Results and sorting
|
|
127
|
-
async setResultsMode(mode: ResultsMode): Promise<void> {
|
|
128
|
-
await this.setState(state => ({ ...state, resultsMode: mode }));
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
async setSortBy(sortBy: SortBy): Promise<void> {
|
|
132
|
-
await this.setState(state => ({ ...state, sortBy }));
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Property operations
|
|
136
|
-
async setPropertySlug(slug: string): Promise<void> {
|
|
137
|
-
await this.setState(state => ({ ...state, propertySlug: slug }));
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
async getResultsUrl(): Promise<string | null> {
|
|
141
|
-
const state = await this.getState();
|
|
142
|
-
return state.propertySlug ? `/${state.propertySlug}/results` : null;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Questionnaire values
|
|
146
|
-
async setResolvedQuestionnaireValues(name: string, values: string[]): Promise<void> {
|
|
147
|
-
await this.setState(state => ({
|
|
148
|
-
...state,
|
|
149
|
-
resolvedQuestionnaireValues: {
|
|
150
|
-
...state.resolvedQuestionnaireValues,
|
|
151
|
-
[name]: values
|
|
152
|
-
}
|
|
153
|
-
}));
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Complex filter operations
|
|
157
|
-
async handleTempFilterChange<K extends keyof Filters>(
|
|
158
|
-
key: K,
|
|
159
|
-
value: Filters[K]
|
|
160
|
-
): Promise<void> {
|
|
161
|
-
await this.setState(state => ({
|
|
162
|
-
...state,
|
|
163
|
-
tempFilters: { ...state.tempFilters, [key]: value }
|
|
164
|
-
}));
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
async commitTempFilterChange<K extends keyof Filters>(
|
|
168
|
-
key: K,
|
|
169
|
-
defaultValue: Filters[K]
|
|
170
|
-
): Promise<void> {
|
|
171
|
-
const state = await this.getState();
|
|
172
|
-
const value = state.tempFilters[key] ?? defaultValue;
|
|
173
|
-
|
|
174
|
-
await this.handleTempFilterChange(key, value);
|
|
175
|
-
await this.submitFilterUpdate();
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
async handleFilterCommitIndexDB(newFilters: Partial<Filters>): Promise<void> {
|
|
179
|
-
await this.setState(state => {
|
|
180
|
-
const apiParams: QueryParams = {
|
|
181
|
-
...state.apiFilters,
|
|
182
|
-
availability: (newFilters.availability as string[]) || [],
|
|
183
|
-
bedrooms: newFilters.bedrooms || [],
|
|
184
|
-
cost: newFilters.cost || null,
|
|
185
|
-
highlights: newFilters.highlights || [],
|
|
186
|
-
};
|
|
187
|
-
return {
|
|
188
|
-
...state,
|
|
189
|
-
filters: { ...state.filters, ...newFilters },
|
|
190
|
-
apiFilters: apiParams,
|
|
191
|
-
};
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
async commitAvailabilityChange(): Promise<void> {
|
|
196
|
-
await this.submitFilterUpdate();
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
async submitFilterUpdate(): Promise<void> {
|
|
200
|
-
await this.setState(state => {
|
|
201
|
-
try {
|
|
202
|
-
const apiParams: QueryParams = {
|
|
203
|
-
...state.apiFilters,
|
|
204
|
-
availability: (state.filters.availability as string[]) || [],
|
|
205
|
-
bedrooms: state.filters.bedrooms || [],
|
|
206
|
-
cost: state.filters.cost || null,
|
|
207
|
-
highlights: state.filters.highlights || [],
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
// Note: updateParams function call would need to be handled in consuming app
|
|
211
|
-
// You might want to emit an event or use a callback for this
|
|
212
|
-
|
|
213
|
-
return {
|
|
214
|
-
...state,
|
|
215
|
-
apiFilters: apiParams,
|
|
216
|
-
};
|
|
217
|
-
} catch (error) {
|
|
218
|
-
console.error("Error submitting filter update:", error);
|
|
219
|
-
return state;
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Persistence operations
|
|
225
|
-
async loadPersistedFilters(): Promise<void> {
|
|
226
|
-
// This method is now redundant since we're always loading from IndexedDB
|
|
227
|
-
// But we'll keep it for compatibility and mark filters as loaded
|
|
228
|
-
await this.setState(state => ({ ...state, filtersLoaded: true }));
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Utility methods
|
|
232
|
-
async getUnitData(unitId: string): Promise<UnitData | null> {
|
|
233
|
-
const state = await this.getState();
|
|
234
|
-
return state.data[unitId] ?? null;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
async getFilters(): Promise<Filters> {
|
|
238
|
-
const state = await this.getState();
|
|
239
|
-
return state.filters;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
async getTempFilters(): Promise<Filters> {
|
|
243
|
-
const state = await this.getState();
|
|
244
|
-
return state.tempFilters;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
async getApiFilters(): Promise<QueryParams> {
|
|
248
|
-
const state = await this.getState();
|
|
249
|
-
return state.apiFilters;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
async getFullState(): Promise<AppStoreData> {
|
|
253
|
-
return this.getState();
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Initialize with default values if needed
|
|
257
|
-
async initialize(): Promise<void> {
|
|
258
|
-
const state = await this.getState();
|
|
259
|
-
if (Object.keys(state.data).length === 0 && !state.filtersLoaded) {
|
|
260
|
-
await this.setState(state => ({
|
|
261
|
-
...defaultAppStoreData,
|
|
262
|
-
...state,
|
|
263
|
-
filtersLoaded: true
|
|
264
|
-
}));
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Export singleton instance
|
|
270
|
-
export const appStore = new AppStore();
|
package/src/api/favorites.ts
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
// src/api/favourites.ts
|
|
2
|
-
import { getDB } from "../db";
|
|
3
|
-
import { ensureUser } from "./users";
|
|
4
|
-
import { FavoritesSchema } from "../schema";
|
|
5
|
-
|
|
6
|
-
const keyFor = (userId: string, propertyId: string) =>
|
|
7
|
-
`favorites:${userId}:${propertyId}`;
|
|
8
|
-
|
|
9
|
-
export async function getFavoritedUnitsForProperty(
|
|
10
|
-
propertyId: string | number,
|
|
11
|
-
): Promise<string[]> {
|
|
12
|
-
const db = await getDB();
|
|
13
|
-
const { uuid } = await ensureUser();
|
|
14
|
-
const key = keyFor(uuid, String(propertyId));
|
|
15
|
-
const row = await db.kv.get(key);
|
|
16
|
-
const parsed = row ? FavoritesSchema.safeParse(row.value) : null;
|
|
17
|
-
return parsed?.success ? parsed.data.unitIds : [];
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export async function setFavoriteUnit(
|
|
21
|
-
propertyId: string | number,
|
|
22
|
-
unitId: string,
|
|
23
|
-
on: boolean,
|
|
24
|
-
): Promise<string[]> {
|
|
25
|
-
const db = await getDB();
|
|
26
|
-
const { uuid } = await ensureUser();
|
|
27
|
-
const key = keyFor(uuid, String(propertyId));
|
|
28
|
-
|
|
29
|
-
return db.transaction("rw", db.kv, async () => {
|
|
30
|
-
const row = await db.kv.get(key);
|
|
31
|
-
const current =
|
|
32
|
-
row && FavoritesSchema.safeParse(row.value).success
|
|
33
|
-
? (row!.value as any)
|
|
34
|
-
: { unitIds: [] as string[], updatedAt: new Date().toISOString() };
|
|
35
|
-
|
|
36
|
-
const nextSet = new Set<string>(current.unitIds);
|
|
37
|
-
on ? nextSet.add(unitId) : nextSet.delete(unitId);
|
|
38
|
-
|
|
39
|
-
const next = {
|
|
40
|
-
unitIds: [...nextSet],
|
|
41
|
-
updatedAt: new Date().toISOString(),
|
|
42
|
-
};
|
|
43
|
-
const validated = FavoritesSchema.parse(next);
|
|
44
|
-
await db.kv.put({ key, value: validated });
|
|
45
|
-
return validated.unitIds;
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export async function toggleFavoriteUnit(
|
|
50
|
-
propertyId: string | number,
|
|
51
|
-
unitId: string,
|
|
52
|
-
): Promise<string[]> {
|
|
53
|
-
const current = await getFavoritedUnitsForProperty(propertyId);
|
|
54
|
-
const on = !current.includes(unitId);
|
|
55
|
-
return setFavoriteUnit(propertyId, unitId, on);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export async function isUnitFavorited(
|
|
59
|
-
propertyId: string | number,
|
|
60
|
-
unitId: string,
|
|
61
|
-
): Promise<boolean> {
|
|
62
|
-
const list = await getFavoritedUnitsForProperty(propertyId);
|
|
63
|
-
return list.includes(unitId);
|
|
64
|
-
}
|