@converse/skeletor 0.0.8 → 0.0.9

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.
Files changed (42) hide show
  1. package/README.md +38 -25
  2. package/dist/skeletor.js +9232 -5096
  3. package/dist/skeletor.js.map +1 -1
  4. package/dist/skeletor.min.js +74 -3
  5. package/dist/skeletor.min.js.map +1 -1
  6. package/package.json +21 -11
  7. package/src/collection.js +540 -395
  8. package/src/drivers/sessionStorage.js +222 -219
  9. package/src/element.js +132 -82
  10. package/src/eventemitter.js +187 -0
  11. package/src/helpers.js +143 -118
  12. package/src/{main.js → index.js} +21 -17
  13. package/src/listening.js +81 -0
  14. package/src/model.js +340 -238
  15. package/src/storage.js +222 -230
  16. package/src/types/collection.d.ts +366 -0
  17. package/src/types/collection.d.ts.map +1 -0
  18. package/src/types/drivers/sessionStorage.d.ts +37 -0
  19. package/src/types/drivers/sessionStorage.d.ts.map +1 -0
  20. package/src/types/element.d.ts +115 -0
  21. package/src/types/element.d.ts.map +1 -0
  22. package/src/types/eventemitter.d.ts +93 -0
  23. package/src/types/eventemitter.d.ts.map +1 -0
  24. package/src/types/helpers.d.ts +62 -0
  25. package/src/types/helpers.d.ts.map +1 -0
  26. package/src/types/index.d.ts +19 -0
  27. package/src/types/index.d.ts.map +1 -0
  28. package/src/types/listening.d.ts +40 -0
  29. package/src/types/listening.d.ts.map +1 -0
  30. package/src/types/model.d.ts +230 -0
  31. package/src/types/model.d.ts.map +1 -0
  32. package/src/types/storage.d.ts +35 -0
  33. package/src/types/storage.d.ts.map +1 -0
  34. package/src/types/utils/events.d.ts +28 -0
  35. package/src/types/utils/events.d.ts.map +1 -0
  36. package/src/utils/events.js +174 -0
  37. package/src/drivers/sessionStorage.js~ +0 -307
  38. package/src/events.js +0 -328
  39. package/src/history.js +0 -301
  40. package/src/overview.js +0 -191
  41. package/src/router.js +0 -119
  42. package/src/view.js +0 -267
package/src/storage.js CHANGED
@@ -4,253 +4,245 @@
4
4
  import * as memoryDriver from 'localforage-driver-memory';
5
5
  import cloneDeep from 'lodash-es/cloneDeep.js';
6
6
  import isString from 'lodash-es/isString.js';
7
- import localForage from "localforage/src/localforage";
7
+ import localForage from 'localforage';
8
8
  import mergebounce from 'mergebounce';
9
- import sessionStorageWrapper from "./drivers/sessionStorage.js";
9
+ import sessionStorageWrapper from './drivers/sessionStorage.js';
10
10
  import { extendPrototype as extendPrototypeWithSetItems } from 'localforage-setitems';
11
- import { extendPrototype as extendPrototypeWithGetItems } from '@converse/localforage-getitems';
11
+ import { extendPrototype as extendPrototypeWithGetItems } from '@converse/localforage-getitems/dist/localforage-getitems.es6';
12
12
  import { guid } from './helpers.js';
13
13
 
14
- const IN_MEMORY = memoryDriver._driver
14
+ const IN_MEMORY = memoryDriver._driver;
15
15
  localForage.defineDriver(memoryDriver);
16
16
  extendPrototypeWithSetItems(localForage);
17
17
  extendPrototypeWithGetItems(localForage);
18
18
 
19
19
  class Storage {
20
-
21
- constructor (id, type, batchedWrites=false) {
22
- if (type === 'local' && !window.localStorage ) {
23
- throw new Error("Skeletor.storage: Environment does not support localStorage.");
24
- } else if (type === 'session' && !window.sessionStorage ) {
25
- throw new Error("Skeletor.storage: Environment does not support sessionStorage.");
26
- }
27
- if (isString(type)) {
28
- this.storeInitialized = this.initStore(type, batchedWrites);
29
- } else {
30
- this.store = type;
31
- if (batchedWrites) {
32
- this.store.debouncedSetItems = mergebounce(
33
- items => this.store.setItems(items),
34
- 50,
35
- {'promise': true}
36
- );
37
- }
38
- this.storeInitialized = Promise.resolve();
39
- }
40
- this.name = id;
41
- }
42
-
43
- async initStore (type, batchedWrites) {
44
- if (type === 'session') {
45
- localForage.setDriver(sessionStorageWrapper._driver);
46
- } else if (type === 'local') {
47
- await localForage.config({'driver': localForage.LOCALSTORAGE});
48
- } else if (type === 'in_memory') {
49
- localForage.config({'driver': IN_MEMORY});
50
- } else if (type !== 'indexed') {
51
- throw new Error("Skeletor.storage: No storage type was specified");
52
- }
53
- this.store = localForage;
54
- if (batchedWrites) {
55
- this.store.debouncedSetItems = mergebounce(
56
- items => this.store.setItems(items),
57
- 50,
58
- {'promise': true}
59
- );
60
- }
61
- }
62
-
63
- flush () {
64
- return this.store.debouncedSetItems?.flush();
65
- }
66
-
67
- async clear () {
68
- await this.store.removeItem(this.name).catch(e => console.error(e));
69
- const re = new RegExp(`^${this.name}-`);
70
- const keys = await this.store.keys();
71
- const removed_keys = keys.filter(k => re.test(k));
72
- await Promise.all(removed_keys.map(k => this.store.removeItem(k).catch(e => console.error(e))));
73
- }
74
-
75
- sync () {
76
- const that = this;
77
-
78
- async function localSync (method, model, options) {
79
- let resp, errorMessage, promise, new_attributes;
80
-
81
- // We get the collection (and if necessary the model attribute.
82
- // Waiting for storeInitialized will cause another iteration of
83
- // the event loop, after which the collection reference will
84
- // be removed from the model.
85
- const collection = model.collection;
86
- if (['patch', 'update'].includes(method)) {
87
- new_attributes = cloneDeep(model.attributes);
20
+ constructor(id, type, batchedWrites = false) {
21
+ if (type === 'local' && !window.localStorage) {
22
+ throw new Error('Skeletor.storage: Environment does not support localStorage.');
23
+ } else if (type === 'session' && !window.sessionStorage) {
24
+ throw new Error('Skeletor.storage: Environment does not support sessionStorage.');
25
+ }
26
+ if (isString(type)) {
27
+ this.storeInitialized = this.initStore(type, batchedWrites);
28
+ } else {
29
+ this.store = type;
30
+ if (batchedWrites) {
31
+ this.store.debouncedSetItems = mergebounce((items) => this.store.setItems(items), 50, { 'promise': true });
32
+ }
33
+ this.storeInitialized = Promise.resolve();
34
+ }
35
+ this.name = id;
36
+ }
37
+
38
+ /**
39
+ * @param {'local'|'session'|'indexed'|'in_memory'} type
40
+ * @param {boolean} batchedWrites
41
+ */
42
+ async initStore(type, batchedWrites) {
43
+ if (type === 'session') {
44
+ await localForage.setDriver(sessionStorageWrapper._driver);
45
+ } else if (type === 'local') {
46
+ await localForage.config({ 'driver': localForage.LOCALSTORAGE });
47
+ } else if (type === 'in_memory') {
48
+ await localForage.config({ 'driver': IN_MEMORY });
49
+ } else if (type !== 'indexed') {
50
+ throw new Error('Skeletor.storage: No storage type was specified');
51
+ }
52
+ this.store = localForage;
53
+ if (batchedWrites) {
54
+ this.store.debouncedSetItems = mergebounce((items) => this.store.setItems(items), 50, { 'promise': true });
55
+ }
56
+ }
57
+
58
+ flush() {
59
+ return this.store.debouncedSetItems?.flush();
60
+ }
61
+
62
+ async clear() {
63
+ await this.store.removeItem(this.name).catch((e) => console.error(e));
64
+ const re = new RegExp(`^${this.name}-`);
65
+ const keys = await this.store.keys();
66
+ const removed_keys = keys.filter((k) => re.test(k));
67
+ await Promise.all(removed_keys.map((k) => this.store.removeItem(k).catch((e) => console.error(e))));
68
+ }
69
+
70
+ sync() {
71
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
72
+ const that = this;
73
+
74
+ async function localSync(method, model, options) {
75
+ let resp, errorMessage, promise, new_attributes;
76
+
77
+ // We get the collection (and if necessary the model attribute.
78
+ // Waiting for storeInitialized will cause another iteration of
79
+ // the event loop, after which the collection reference will
80
+ // be removed from the model.
81
+ const collection = model.collection;
82
+ if (['patch', 'update'].includes(method)) {
83
+ new_attributes = cloneDeep(model.attributes);
84
+ }
85
+ await that.storeInitialized;
86
+ try {
87
+ const original_attributes = model.attributes;
88
+ switch (method) {
89
+ case 'read':
90
+ if (model.id !== undefined) {
91
+ resp = await that.find(model);
92
+ } else {
93
+ resp = await that.findAll();
88
94
  }
89
- await that.storeInitialized;
90
- try {
91
- const original_attributes = model.attributes;
92
- switch (method) {
93
- case "read":
94
- if (model.id !== undefined) {
95
- resp = await that.find(model);
96
- } else {
97
- resp = await that.findAll();
98
- }
99
- break;
100
- case "create":
101
- resp = await that.create(model, options);
102
- break;
103
- case 'patch':
104
- case "update":
105
- if (options.wait) {
106
- // When `wait` is set to true, Skeletor waits until
107
- // confirmation of storage before setting the values on
108
- // the model.
109
- // However, the new attributes needs to be sent, so it
110
- // sets them manually on the model and then removes
111
- // them after calling `sync`.
112
- // Because our `sync` method is asynchronous and we
113
- // wait for `storeInitialized`, the attributes are
114
- // already restored once we get here, so we need to do
115
- // the attributes dance again.
116
- model.attributes = new_attributes;
117
- }
118
- promise = that.update(model, options);
119
- if (options.wait) {
120
- model.attributes = original_attributes;
121
- }
122
- resp = await promise;
123
- break;
124
- case "delete":
125
- resp = await that.destroy(model, collection);
126
- break;
127
- }
128
- } catch (error) {
129
- if (error.code === 22 && that.getStorageSize() === 0) {
130
- errorMessage = "Private browsing is unsupported";
131
- } else {
132
- errorMessage = error.message;
133
- }
95
+ break;
96
+ case 'create':
97
+ resp = await that.create(model, options);
98
+ break;
99
+ case 'patch':
100
+ case 'update':
101
+ if (options.wait) {
102
+ // When `wait` is set to true, Skeletor waits until
103
+ // confirmation of storage before setting the values on
104
+ // the model.
105
+ // However, the new attributes needs to be sent, so it
106
+ // sets them manually on the model and then removes
107
+ // them after calling `sync`.
108
+ // Because our `sync` method is asynchronous and we
109
+ // wait for `storeInitialized`, the attributes are
110
+ // already restored once we get here, so we need to do
111
+ // the attributes dance again.
112
+ model.attributes = new_attributes;
134
113
  }
135
-
136
- if (resp) {
137
- if (options && options.success) {
138
- // When storing, we don't pass back the response (which is
139
- // the set attributes returned from localforage because
140
- // Skeletor sets them again on the model and due to the async
141
- // nature of localforage it can cause stale attributes to be
142
- // set on a model after it's been updated in the meantime.
143
- const data = (method === "read") ? resp : null;
144
- options.success(data, options);
145
- }
146
- } else {
147
- errorMessage = errorMessage ? errorMessage : "Record Not Found";
148
- if (options && options.error) {
149
- options.error(errorMessage);
150
- }
114
+ promise = that.update(model);
115
+ if (options.wait) {
116
+ model.attributes = original_attributes;
151
117
  }
118
+ resp = await promise;
119
+ break;
120
+ case 'delete':
121
+ resp = await that.destroy(model, collection);
122
+ break;
152
123
  }
153
- localSync.__name__ = 'localSync';
154
- return localSync;
155
- }
156
-
157
- removeCollectionReference (model, collection) {
158
- if (!collection) {
159
- return;
160
- }
161
- const ids = collection
162
- .filter(m => (m.id !== model.id))
163
- .map(m => this.getItemName(m.id));
164
-
165
- return this.store.setItem(this.name, ids);
166
- }
167
-
168
- addCollectionReference (model, collection) {
169
- if (!collection) {
170
- return;
171
- }
172
- const ids = collection.map(m => this.getItemName(m.id));
173
- const new_id = this.getItemName(model.id);
174
- if (!ids.includes(new_id)) {
175
- ids.push(new_id);
176
- }
177
- return this.store.setItem(this.name, ids);
178
- }
179
-
180
- getCollectionReferenceData (model) {
181
- if (!model.collection) {
182
- return {};
183
- }
184
- const ids = model.collection.map(m => this.getItemName(m.id));
185
- const new_id = this.getItemName(model.id);
186
- if (!ids.includes(new_id)) {
187
- ids.push(new_id);
188
- }
189
- const result = {};
190
- result[this.name] = ids;
191
- return result;
192
- }
193
-
194
- async save (model) {
195
- if (this.store.setItems) {
196
- const items = {}
197
- items[this.getItemName(model.id)] = model.toJSON();
198
- Object.assign(items, this.getCollectionReferenceData(model));
199
- return (this.store.debouncedSetItems) ?
200
- this.store.debouncedSetItems(items) :
201
- this.store.setItems(items);
124
+ } catch (error) {
125
+ if (error.code === 22 && that.getStorageSize() === 0) {
126
+ errorMessage = 'Private browsing is unsupported';
202
127
  } else {
203
- const key = this.getItemName(model.id);
204
- const data = await this.store.setItem(key, model.toJSON());
205
- await this.addCollectionReference(model, model.collection);
206
- return data;
128
+ errorMessage = error.message;
207
129
  }
208
- }
209
-
210
- create (model, options) {
211
- /* Add a model, giving it a (hopefully)-unique GUID, if it doesn't already
212
- * have an id of it's own.
213
- */
214
- if (!model.id) {
215
- model.id = guid();
216
- model.set(model.idAttribute, model.id, options);
130
+ }
131
+
132
+ if (resp) {
133
+ if (options && options.success) {
134
+ // When storing, we don't pass back the response (which is
135
+ // the set attributes returned from localforage because
136
+ // Skeletor sets them again on the model and due to the async
137
+ // nature of localforage it can cause stale attributes to be
138
+ // set on a model after it's been updated in the meantime.
139
+ const data = method === 'read' ? resp : null;
140
+ options.success(data, options);
217
141
  }
218
- return this.save(model);
219
- }
220
-
221
- update (model) {
222
- return this.save(model);
223
- }
224
-
225
- find (model) {
226
- return this.store.getItem(this.getItemName(model.id));
227
- }
228
-
229
- async findAll () {
230
- /* Return the array of all models currently in storage.
231
- */
232
- const keys = await this.store.getItem(this.name);
233
- if (keys?.length) {
234
- const items = await this.store.getItems(keys);
235
- return Object.values(items);
142
+ } else {
143
+ errorMessage = errorMessage ? errorMessage : 'Record Not Found';
144
+ if (options && options.error) {
145
+ options.error(errorMessage);
236
146
  }
237
- return [];
238
- }
239
-
240
- async destroy (model, collection) {
241
- await this.flush();
242
- await this.store.removeItem(this.getItemName(model.id));
243
- await this.removeCollectionReference(model, collection);
244
- return model;
245
- }
246
-
247
- getStorageSize () {
248
- return this.store.length;
249
- }
250
-
251
- getItemName (id) {
252
- return this.name+"-"+id;
253
- }
147
+ }
148
+ }
149
+ localSync.__name__ = 'localSync';
150
+ return localSync;
151
+ }
152
+
153
+ removeCollectionReference(model, collection) {
154
+ if (!collection) {
155
+ return;
156
+ }
157
+ const ids = collection.filter((m) => m.id !== model.id).map((m) => this.getItemName(m.id));
158
+
159
+ return this.store.setItem(this.name, ids);
160
+ }
161
+
162
+ addCollectionReference(model, collection) {
163
+ if (!collection) {
164
+ return;
165
+ }
166
+ const ids = collection.map((m) => this.getItemName(m.id));
167
+ const new_id = this.getItemName(model.id);
168
+ if (!ids.includes(new_id)) {
169
+ ids.push(new_id);
170
+ }
171
+ return this.store.setItem(this.name, ids);
172
+ }
173
+
174
+ getCollectionReferenceData(model) {
175
+ if (!model.collection) {
176
+ return {};
177
+ }
178
+ const ids = model.collection.map((m) => this.getItemName(m.id));
179
+ const new_id = this.getItemName(model.id);
180
+ if (!ids.includes(new_id)) {
181
+ ids.push(new_id);
182
+ }
183
+ const result = {};
184
+ result[this.name] = ids;
185
+ return result;
186
+ }
187
+
188
+ async save(model) {
189
+ if (this.store.setItems) {
190
+ const items = {};
191
+ items[this.getItemName(model.id)] = model.toJSON();
192
+ Object.assign(items, this.getCollectionReferenceData(model));
193
+ return this.store.debouncedSetItems ? this.store.debouncedSetItems(items) : this.store.setItems(items);
194
+ } else {
195
+ const key = this.getItemName(model.id);
196
+ const data = await this.store.setItem(key, model.toJSON());
197
+ await this.addCollectionReference(model, model.collection);
198
+ return data;
199
+ }
200
+ }
201
+
202
+ create(model, options) {
203
+ /* Add a model, giving it a (hopefully)-unique GUID, if it doesn't already
204
+ * have an id of it's own.
205
+ */
206
+ if (!model.id) {
207
+ model.id = guid();
208
+ model.set(model.idAttribute, model.id, options);
209
+ }
210
+ return this.save(model);
211
+ }
212
+
213
+ update(model) {
214
+ return this.save(model);
215
+ }
216
+
217
+ find(model) {
218
+ return this.store.getItem(this.getItemName(model.id));
219
+ }
220
+
221
+ async findAll() {
222
+ /* Return the array of all models currently in storage.
223
+ */
224
+ const keys = await this.store.getItem(this.name);
225
+ if (keys?.length) {
226
+ const items = await this.store.getItems(keys);
227
+ return Object.values(items);
228
+ }
229
+ return [];
230
+ }
231
+
232
+ async destroy(model, collection) {
233
+ await this.flush();
234
+ await this.store.removeItem(this.getItemName(model.id));
235
+ await this.removeCollectionReference(model, collection);
236
+ return model;
237
+ }
238
+
239
+ getStorageSize() {
240
+ return this.store.length;
241
+ }
242
+
243
+ getItemName(id) {
244
+ return this.name + '-' + id;
245
+ }
254
246
  }
255
247
 
256
248
  Storage.sessionStorageInitialized = localForage.defineDriver(sessionStorageWrapper);