UNPKG

5.91 kBPlain TextView Raw
1import {
2 IRepoPlugin,
3 IKeyValue,
4 PluginType,
5 IModel,
6 Repo,
7 ICoordinator,
8 ICoordinatorOptions,
9 PluginEventType,
10 IFinderPlugin,
11 getMetadata,
12 IModelMapper,
13 ModelPersistenceEventType,
14 TKeyValue,
15 Log
16} from 'typestore'
17
18import {IndexedDBPlugin} from "./IndexedDBPlugin";
19import Dexie from "dexie";
20import {IndexedDBFinderKey} from "./IndexedDBConstants";
21import {IIndexedDBFinderOptions} from './IndexedDBDecorations'
22
23const log = Log.create(__filename)
24
25/**
26 * Super simple plain jain key for now
27 * what you send to the constructor comes out the
28 * other end
29 *
30 * just like poop!
31 */
32export class IndexedDBKeyValue implements IKeyValue {
33
34 public args:any[]
35
36 indexedDBKey = true
37
38 constructor(...args:any[]) {
39 this.args = args
40 }
41}
42
43export class IndexedDBRepoPlugin<M extends IModel> implements IRepoPlugin<M>, IFinderPlugin {
44
45 type = PluginType.Repo | PluginType.Finder
46 supportedModels:any[]
47
48 private coordinator
49 private keys:string[]
50
51 /**
52 * Construct a new repo/store
53 * manager for a given repo/model
54 *
55 * @param store
56 * @param repo
57 */
58 constructor(private store:IndexedDBPlugin, public repo:Repo<M>) {
59 this.supportedModels = [repo.modelClazz]
60 this.keys = repo.modelType.options.attrs
61 .filter(attr => attr.primaryKey || attr.secondaryKey)
62 .map(attr => attr.name)
63
64
65 repo.attach(this)
66 }
67
68 /**
69 * Create a finder method with descriptor
70 * and signature
71 *
72 * @param repo
73 * @param finderKey
74 * @returns {any}
75 */
76 decorateFinder(repo:Repo<any>, finderKey:string) {
77 const finderOpts = getMetadata(
78 IndexedDBFinderKey,
79 this.repo,
80 finderKey
81 ) as IIndexedDBFinderOptions
82
83 if (!finderOpts)
84 return null
85
86 const {fn, filter} = finderOpts
87 if (!fn && !filter)
88 throw new Error('finder or fn properties MUST be provided on an indexeddb finder descriptor')
89
90 return async(...args) => {
91
92 let results = await ((fn) ? fn(this, ...args) : this.table
93 .filter(record => filter(record, ...args))
94 .toArray())
95
96
97 const mapper = this.mapper
98
99 const mappedResults = results.map(record => mapper.fromObject(record))
100 return finderOpts.singleResult ? mappedResults[0] : mappedResults
101 }
102 }
103
104 /**
105 * Handle a plugin event
106 *
107 * @param eventType
108 * @param args
109 * @returns {boolean}
110 */
111 handle(eventType:PluginEventType, ...args):boolean|any {
112 return false;
113 }
114
115 /**
116 * Model mapper
117 *
118 * @returns {IModelMapper<M>}
119 */
120 get mapper():IModelMapper<M> {
121 return this.repo.getMapper(this.repo.modelClazz)
122 }
123
124 /**
125 * Get dexie table
126 *
127 * @returns {Dexie.Table<any, any>}
128 */
129 get table():Dexie.Table<any,any> {
130 return this.store.table(this.repo.modelType)
131 }
132
133 /**
134 * Get db ref
135 *
136 * @returns {Dexie}
137 */
138 get db() {
139 return this.store.db
140 }
141
142
143 async init(coordinator:ICoordinator, opts:ICoordinatorOptions):Promise<ICoordinator> {
144 return (this.coordinator = coordinator)
145 }
146
147 async start():Promise<ICoordinator> {
148 return this.coordinator
149 }
150
151 async stop():Promise<ICoordinator> {
152 return this.coordinator
153 }
154
155 key(...args):IndexedDBKeyValue {
156 return new IndexedDBKeyValue(...args);
157 }
158
159 keyFromObject(o:any):IndexedDBKeyValue {
160 return new IndexedDBKeyValue(...this.keys.map(key => o[key]))
161 }
162
163 dbKeyFromKey(key:IndexedDBKeyValue) {
164 return key.args[0]
165 }
166
167 async get(key:IndexedDBKeyValue):Promise<M> {
168 key = key.indexedDBKey ? key : this.key(key as any)
169
170
171 const dbObjects = await this.table
172 .filter(record => {
173
174 const recordKey = this.keyFromObject(record)
175 return Array.isEqual(key.args, recordKey.args)
176 })
177 .toArray()
178
179 if (dbObjects.length === 0)
180 return null
181 else if (dbObjects.length > 1)
182 throw new Error(`More than one database object returned for key: ${JSON.stringify(key.args)}`)
183
184 return this.repo.getMapper(this.repo.modelClazz).fromObject(dbObjects[0])
185
186
187 }
188
189 async save(model:M):Promise<M> {
190 const mapper = this.mapper
191 const json = mapper.toObject(model)
192
193 try {
194 await this.table.put(json)
195 this.repo.triggerPersistenceEvent(ModelPersistenceEventType.Save, model)
196 } catch (err) {
197 log.error('Failed to persist model',err)
198 log.error('Failed persisted json',json,model)
199
200 throw err
201 }
202 return model
203 }
204
205 /**
206 * Remove implementation
207 *
208 * @param key
209 * @returns {Promise<void>}
210 */
211 async remove(key:IndexedDBKeyValue):Promise<any> {
212 key = key.indexedDBKey ? key : this.key(key as any)
213
214 const model = (this.repo.supportPersistenceEvents()) ?
215 await this.get(key) : null
216
217 const result = await this.table.delete(key.args[0])
218
219 if (model)
220 this.repo.triggerPersistenceEvent(ModelPersistenceEventType.Remove,model)
221
222 return Promise.resolve(result);
223 }
224
225 count():Promise<number> {
226 return Promise.resolve(this.table.count());
227 }
228
229 /**
230 * Bulk get
231 *
232 * @param keys
233 * @returns {any}
234 */
235 async bulkGet(...keys:IndexedDBKeyValue[]):Promise<M[]> {
236 keys = keys.map(key => (key.indexedDBKey) ? key : this.key(key as any))
237
238 const promises = keys.map(key => this.get(key))
239 return await Promise.all(promises)
240 }
241
242 /**
243 * Bulk save/put
244 *
245 * @param models
246 * @returns {M[]}
247 */
248 async bulkSave(...models:M[]):Promise<M[]> {
249 const mapper = this.repo.getMapper(this.repo.modelClazz)
250 const jsons = models.map(model => mapper.toObject(model))
251
252 await (this.table as any).bulkPut(jsons)
253 this.repo.triggerPersistenceEvent(ModelPersistenceEventType.Save,...models)
254
255 return models
256 }
257
258 /**
259 * Bulk remove
260 *
261 * @param keys
262 * @returns {IndexedDBKeyValue[]}
263 */
264 async bulkRemove(...keys:IndexedDBKeyValue[]):Promise<any[]> {
265 keys = keys.map(key => (key.indexedDBKey) ? key : this.key(key as any))
266
267 const models = (this.repo.supportPersistenceEvents()) ?
268 await this.bulkGet(...keys) : null
269
270 const dbKeys = keys.map(key => this.dbKeyFromKey(key))
271
272 await (this.table as any).bulkDelete(dbKeys)
273
274 if (models)
275 this.repo.triggerPersistenceEvent(ModelPersistenceEventType.Remove,...models)
276
277 return keys
278 }
279}
\No newline at end of file