Skip to content

Commit 655ddb2

Browse files
committed
wip(feathers-localforage): Migrate feathers-localforage to this monorepo #7
1 parent 3074663 commit 655ddb2

6 files changed

Lines changed: 388 additions & 385 deletions

File tree

packages/feathers-localforage/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
"dist"
1515
],
1616
"standard": {
17+
"globals": [
18+
"self"
19+
],
1720
"ignore": [
1821
"dist/**"
1922
]
Lines changed: 1 addition & 384 deletions
Original file line numberDiff line numberDiff line change
@@ -1,384 +1 @@
1-
/* global self */
2-
import { sorter, select, getLimit, AdapterBase } from '@feathersjs/adapter-commons'
3-
import { _ } from '@feathersjs/commons'
4-
import errors from '@feathersjs/errors'
5-
import LocalForage from 'localforage'
6-
import sift from 'sift'
7-
import makeDebug from 'debug'
8-
import { stringsToDates } from './strings-to-dates.js'
9-
10-
export { default as LocalForage } from 'localforage'
11-
12-
const debug = makeDebug('@feathersjs-offline:feathers-localforage')
13-
const usedKeys = []
14-
15-
const _select = (data, params, ...args) => {
16-
const base = select(params, ...args)
17-
18-
return base(JSON.parse(JSON.stringify(data)))
19-
}
20-
21-
const validDrivers = {
22-
INDEXEDDB: LocalForage.INDEXEDDB,
23-
WEBSQL: LocalForage.WEBSQL,
24-
LOCALSTORAGE: LocalForage.LOCALSTORAGE
25-
}
26-
27-
// Create the adapter
28-
class Adapter extends AdapterBase {
29-
constructor (options = {}) {
30-
super(_.extend({
31-
id: 'id',
32-
matcher: sift,
33-
sorter
34-
}, options))
35-
36-
this.store = options.store || {}
37-
this._dates = options.dates || false
38-
39-
this.sanitizeParameters(options)
40-
41-
debug(`Constructor started:
42-
\t_storageType = ${JSON.stringify(this._storageType)}
43-
\t_version = ${JSON.stringify(this._version)}
44-
\t_name = ${JSON.stringify(this._name)}
45-
\t_storageKey = ${JSON.stringify(this._storageKey)}
46-
\t_storageSize = ${JSON.stringify(this._storageSize)}
47-
\t_reuseKeys = ${JSON.stringify(this._reuseKeys)}\n`)
48-
49-
this._storage = LocalForage.createInstance({
50-
driver: this._storageType,
51-
name: this._name,
52-
size: this._storageSize,
53-
version: this._version,
54-
storeName: this._storageKey,
55-
description: 'Created by @feathersjs-offline/localforage'
56-
})
57-
58-
this.checkStoreName()
59-
60-
// Make a handy suffix primarily for debugging owndata/ownnet
61-
const self = this
62-
this._debugSuffix = self._name.includes('_local')
63-
? ' LOCAL'
64-
: (self._name.includes('_queue') ? ' QUEUE' : '')
65-
66-
this.ready()
67-
}
68-
69-
sanitizeParameters (options) {
70-
this._name = options.name || 'feathersjs-offline'
71-
this._storageKey = options.storeName || options.name || 'feathers'
72-
73-
let storage = this.options.storage || 'LOCALSTORAGE'
74-
storage = Array.isArray(storage) ? storage : [storage]
75-
const ok = storage.reduce((value, s) => value && (s.toUpperCase() in validDrivers), true)
76-
if (!ok) { throw new errors.NotAcceptable(`Unknown storage type specified '${this.options.storage}\nPlease use one (or more) of 'websql', 'indexeddb', or 'localstorage'.`) }
77-
78-
this._storageType = storage.map(s => validDrivers[s.toUpperCase()])
79-
80-
this._version = options.version || 1.0
81-
82-
// Default DB size is _JUST UNDER_ 5MB, as it's the highest size we can use without a prompt.
83-
this._storageSize = options.storageSize || 4980736
84-
this._reuseKeys = options.reuseKeys || false
85-
86-
this._id = options.startId || 0
87-
}
88-
89-
checkStoreName () {
90-
if (usedKeys.indexOf(this._storageKey) === -1) {
91-
usedKeys.push(this._storageKey)
92-
} else {
93-
if (!this._reuseKeys) { // Allow reuse if options.reuseKeys set to true
94-
throw new errors.Forbidden(`The storage name '${this._storageKey}' is already in use by another instance.`)
95-
}
96-
}
97-
}
98-
99-
async ready () {
100-
const self = this
101-
// Now pre-load data (if any)
102-
const keys = Object.keys(this.store)
103-
await Promise.all([
104-
keys.forEach(key => {
105-
let id = self.store[key][self.id]
106-
id = self.setMax(id)
107-
return self.getModel().setItem(String(id), self.store[key])
108-
}
109-
)])
110-
}
111-
112-
getModel () {
113-
return this._storage
114-
}
115-
116-
async getEntries (params = {}) {
117-
debug(`getEntries(${JSON.stringify(params)})` + this._debugSuffix)
118-
119-
return this._find({
120-
...params,
121-
paginate: false
122-
})
123-
.then(select(params, this.id))
124-
.then(stringsToDates(this._dates))
125-
}
126-
127-
getQuery (params) {
128-
const options = this.getOptions(params)
129-
const { $skip, $sort, $limit, $select, ...query } = params.query || {}
130-
131-
return {
132-
query,
133-
filters: { $skip, $sort, $limit: getLimit($limit, options.paginate), $select }
134-
}
135-
}
136-
137-
setMax (id) {
138-
if (Number.isInteger(id)) {
139-
this._id = Math.max(Number.parseInt(id), this._id)
140-
}
141-
return id
142-
}
143-
144-
async _find (params = {}) {
145-
debug(`_find(${JSON.stringify(params)})` + this._debugSuffix)
146-
const self = this
147-
const { paginate } = this.getOptions(params)
148-
const { query, filters } = self.getQuery(params)
149-
let keys = await self.getModel().keys()
150-
151-
// An async equivalent of Array.filter()
152-
const asyncFilter = async (arr, predicate) => {
153-
const results = await Promise.all(arr.map(predicate))
154-
155-
return arr.filter((_v, index) => results[index])
156-
}
157-
158-
// Determine relevant keys
159-
keys = await asyncFilter(keys, async key => {
160-
const item = await self.getModel().getItem(key)
161-
const match = self.options.matcher(query)(item)
162-
return match
163-
})
164-
165-
// Now retrieve all values
166-
let values = await Promise.all(keys.map(key => self.getModel().getItem(key)))
167-
const total = values.length
168-
169-
// Now we sort (if requested)
170-
if (filters.$sort !== undefined) {
171-
values.sort(this.options.sorter(filters.$sort))
172-
}
173-
174-
// Skip requested items
175-
if (filters.$skip !== undefined) {
176-
values = values.slice(filters.$skip)
177-
}
178-
179-
// Limit result to specified (or default) length
180-
if (filters.$limit !== undefined) {
181-
values = values.slice(0, filters.$limit)
182-
}
183-
184-
// If wanted we convert all ISO string dates to Date objects
185-
values = stringsToDates(this._dates)(values)
186-
187-
const result = {
188-
total,
189-
limit: filters.$limit,
190-
skip: filters.$skip || 0,
191-
data: values.map(value => _select(value, params, this.id))
192-
}
193-
194-
if (!(paginate && paginate.default)) {
195-
debug(`_find res = ${JSON.stringify(result.data)}`)
196-
return result.data
197-
}
198-
199-
debug(`_find res = ${JSON.stringify(result)}`)
200-
return result
201-
}
202-
203-
async _get (id, params = {}) {
204-
debug(`_get(${id}, ${JSON.stringify(params)})` + this._debugSuffix)
205-
const self = this
206-
const { query } = this.getQuery(params)
207-
208-
return this.getModel().getItem(String(id), null)
209-
.catch(err => { throw new errors.NotFound(`No record found for ${this.id} '${id}', err=${err.name} ${err.message}` + this._debugSuffix) })
210-
.then(item => {
211-
if (item === null) throw new errors.NotFound(`No match for ${this.id} = '${id}', query=${JSON.stringify(query)}` + this._debugSuffix)
212-
213-
const match = self.options.matcher(query)(item)
214-
if (match) {
215-
return item
216-
} else {
217-
throw new errors.NotFound(`No match for item = ${JSON.stringify(item)}, query=${JSON.stringify(query)}` + this._debugSuffix)
218-
}
219-
})
220-
.then(select(params, this.id))
221-
.then(stringsToDates(this._dates))
222-
}
223-
224-
async _findOrGet (id, params = {}) {
225-
debug(`_findOrGet(${id}, ${JSON.stringify(params)})` + this._debugSuffix)
226-
if (id === null) {
227-
return this._find(_.extend({}, params, {
228-
paginate: false
229-
}))
230-
}
231-
232-
return this._get(id, params)
233-
}
234-
235-
async _create (raw, params = {}) {
236-
if (Array.isArray(raw) && !this.allowsMulti('create', params)) {
237-
throw new errors.MethodNotAllowed('Can not create multiple entries')
238-
}
239-
debug(`_create(${JSON.stringify(raw)}, ${JSON.stringify(params)})` + this._debugSuffix)
240-
241-
const addId = item => {
242-
const thisId = item[this.id]
243-
244-
item[this.id] = thisId !== undefined ? this.setMax(thisId) : ++this._id
245-
246-
return item
247-
}
248-
// Duplicate with generated IDs so that we can track which item had previously one or not
249-
const data = Array.isArray(raw) ? raw.map(item => addId(Object.assign({}, item))) : addId(Object.assign({}, raw))
250-
// We default to automatically add ID to items without but this can be skipped
251-
const addItemId = (!Object.prototype.hasOwnProperty.call(params, 'addId') || params.addId)
252-
const doOne = (item, indexOrData) => {
253-
// Check if initial data had an ID or not
254-
const originalItem = (typeof indexOrData === 'object' ? indexOrData : raw[indexOrData])
255-
const hadId = (originalItem[this.id] !== undefined)
256-
return this.getModel().setItem(String(item[this.id]), addItemId || hadId ? item : _.omit(item, [this.id]), null)
257-
.then(() => addItemId || hadId ? item : _.omit(item, [this.id]))
258-
.then(select(params, this.id))
259-
.then(stringsToDates(this._dates))
260-
.then(item => {
261-
return item
262-
})
263-
.catch(err => {
264-
throw new errors.GeneralError(`_create doOne: ERROR: err=${err.name}, ${err.message}`)
265-
})
266-
}
267-
268-
return Array.isArray(data) ? Promise.all(data.map(doOne)) : doOne(data, raw)
269-
}
270-
271-
async _patch (id, data, params = {}) {
272-
if (id === null && !this.allowsMulti('patch', params)) {
273-
throw new errors.MethodNotAllowed('Can not patch multiple entries')
274-
}
275-
debug(`_patch(${id}, ${JSON.stringify(data)}, ${JSON.stringify(params)})` + this._debugSuffix)
276-
const self = this
277-
const items = await this._findOrGet(id, params)
278-
279-
if (params.upsert) {
280-
if (Array.isArray(items) && (items.length === 0)) {
281-
return self._create(data)
282-
} else if (!items) {
283-
return self._create(data)
284-
}
285-
}
286-
287-
const patchEntry = async entry => {
288-
const currentId = entry[this.id]
289-
290-
const item = _.extend(entry, _.omit(data, this.id))
291-
await self.getModel().setItem(String(currentId), item, null)
292-
293-
return stringsToDates(this._dates)(_select(item, params, this.id))
294-
}
295-
296-
if (Array.isArray(items)) {
297-
return Promise.all(items.map(patchEntry))
298-
} else {
299-
return patchEntry(items)
300-
}
301-
}
302-
303-
async _update (id, data, params = {}) {
304-
if (id === null || Array.isArray(data)) {
305-
throw new errors.BadRequest("You can not replace multiple instances. Did you mean 'patch'?")
306-
}
307-
debug(`_update(${id}, ${JSON.stringify(data)}, ${JSON.stringify(params)})` + this._debugSuffix)
308-
const item = await this._findOrGet(id, params)
309-
310-
if (params.upsert) {
311-
if (Array.isArray(item) && (item.length === 0)) {
312-
return self._create(data)
313-
} else if (!item) {
314-
return self._create(data)
315-
}
316-
}
317-
318-
id = item[this.id]
319-
320-
const entry = _.omit(data, this.id)
321-
entry[this.id] = id
322-
323-
return this.getModel().setItem(String(id), entry, null)
324-
.then(() => entry)
325-
.then(select(params, this.id))
326-
.then(stringsToDates(this._dates))
327-
}
328-
329-
async __removeItem (item) {
330-
await this.getModel(null).removeItem(String(item[this.id]), null)
331-
332-
return item
333-
}
334-
335-
async _remove (id, params = {}) {
336-
if (id === null && !this.allowsMulti('remove', params)) {
337-
throw new errors.MethodNotAllowed('Can not remove multiple entries')
338-
}
339-
debug(`_remove(${id}, ${JSON.stringify(params)})` + this._debugSuffix)
340-
const items = await this._findOrGet(id, params)
341-
if (Array.isArray(items)) {
342-
return Promise.all(items.map(item => this.__removeItem(item), null))
343-
.then(select(params, this.id))
344-
} else {
345-
return this.__removeItem(items)
346-
.then(select(params, this.id))
347-
}
348-
}
349-
}
350-
351-
// Create the service.
352-
export class Service extends Adapter {
353-
constructor (options = {}) {
354-
super(options)
355-
}
356-
357-
// Perform requests to adapter
358-
async find (params) {
359-
return this._find(params)
360-
}
361-
362-
async get (id, params) {
363-
return this._get(id, params)
364-
}
365-
366-
async create (data, params) {
367-
return this._create(data, params)
368-
}
369-
370-
async update (id, data, params) {
371-
return this._update(id, data, params)
372-
}
373-
374-
async patch (id, data, params) {
375-
return this._patch(id, data, params)
376-
}
377-
378-
async remove (id, params) {
379-
return this._remove(id, params)
380-
}
381-
}
382-
export function init (options) {
383-
return new Service(options)
384-
}
1+
export * from './service.js'

0 commit comments

Comments
 (0)