@converse/skeletor 0.0.9 → 1.0.0

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 (39) hide show
  1. package/README.md +5 -89
  2. package/dist/skeletor.d.ts +764 -0
  3. package/dist/skeletor.js +7564 -7743
  4. package/dist/skeletor.min.js +6 -11
  5. package/package.json +46 -30
  6. package/src/{collection.js → collection.ts} +232 -271
  7. package/src/drivers/{sessionStorage.js → sessionStorage.ts} +81 -93
  8. package/src/{eventemitter.js → eventemitter.ts} +44 -59
  9. package/src/{helpers.js → helpers.ts} +56 -69
  10. package/src/index.ts +84 -0
  11. package/src/{listening.js → listening.ts} +32 -27
  12. package/src/{model.js → model.ts} +143 -162
  13. package/src/{storage.js → storage.ts} +92 -49
  14. package/src/types.ts +189 -0
  15. package/src/utils/{events.js → events.ts} +98 -48
  16. package/dist/skeletor.js.map +0 -1
  17. package/dist/skeletor.min.js.map +0 -1
  18. package/src/element.js +0 -238
  19. package/src/index.js +0 -47
  20. package/src/types/collection.d.ts +0 -366
  21. package/src/types/collection.d.ts.map +0 -1
  22. package/src/types/drivers/sessionStorage.d.ts +0 -37
  23. package/src/types/drivers/sessionStorage.d.ts.map +0 -1
  24. package/src/types/element.d.ts +0 -115
  25. package/src/types/element.d.ts.map +0 -1
  26. package/src/types/eventemitter.d.ts +0 -93
  27. package/src/types/eventemitter.d.ts.map +0 -1
  28. package/src/types/helpers.d.ts +0 -62
  29. package/src/types/helpers.d.ts.map +0 -1
  30. package/src/types/index.d.ts +0 -19
  31. package/src/types/index.d.ts.map +0 -1
  32. package/src/types/listening.d.ts +0 -40
  33. package/src/types/listening.d.ts.map +0 -1
  34. package/src/types/model.d.ts +0 -230
  35. package/src/types/model.d.ts.map +0 -1
  36. package/src/types/storage.d.ts +0 -35
  37. package/src/types/storage.d.ts.map +0 -1
  38. package/src/types/utils/events.d.ts +0 -28
  39. package/src/types/utils/events.d.ts.map +0 -1
@@ -1,6 +1,6 @@
1
1
  // Copyright 2014 Mozilla
2
2
  // Copyright 2015 Thodoris Greasidis
3
- // Copyright 2018 JC Brand
3
+ // Copyright 2018-2025 JC Brand
4
4
  //
5
5
  // Licensed under the Apache License, Version 2.0 (the "License");
6
6
  // you may not use this file except in compliance with the License.
@@ -22,15 +22,27 @@ import serializer from 'localforage/src/utils/serializer';
22
22
  const serialize = serializer['serialize'];
23
23
  const deserialize = serializer['deserialize'];
24
24
 
25
- function isSessionStorageValid() {
26
- // If the app is running inside a Google Chrome packaged webapp, or some
27
- // other context where sessionStorage isn't available, we don't use
28
- // sessionStorage. This feature detection is preferred over the old
29
- // `if (window.chrome && window.chrome.runtime)` code.
30
- // See: https://github.com/mozilla/localForage/issues/68
25
+ interface DbInfo {
26
+ keyPrefix: string;
27
+ serializer: {
28
+ serialize: typeof serialize;
29
+ deserialize: typeof deserialize;
30
+ };
31
+ [key: string]: any;
32
+ }
33
+
34
+ interface LocalForageOptions {
35
+ name: string;
36
+ storeName: string;
37
+ [key: string]: any;
38
+ }
39
+
40
+ interface IteratorCallback<T, U> {
41
+ (value: T, key: string, iterationNumber: number): U;
42
+ }
43
+
44
+ function isSessionStorageValid(): boolean {
31
45
  try {
32
- // If sessionStorage isn't available, we get outta here!
33
- // This should be inside a try catch
34
46
  if (sessionStorage && 'setItem' in sessionStorage) {
35
47
  return true;
36
48
  }
@@ -40,7 +52,7 @@ function isSessionStorageValid() {
40
52
  return false;
41
53
  }
42
54
 
43
- function _getKeyPrefix(options, defaultConfig) {
55
+ function _getKeyPrefix(options: LocalForageOptions, defaultConfig: LocalForageOptions): string {
44
56
  let keyPrefix = options.name + '/';
45
57
 
46
58
  if (options.storeName !== defaultConfig.storeName) {
@@ -49,18 +61,18 @@ function _getKeyPrefix(options, defaultConfig) {
49
61
  return keyPrefix;
50
62
  }
51
63
 
52
- const dbInfo = {
53
- 'serializer': {
54
- 'serialize': serialize,
55
- 'deserialize': deserialize,
64
+ const dbInfo: DbInfo = {
65
+ keyPrefix: '',
66
+ serializer: {
67
+ serialize: serialize,
68
+ deserialize: deserialize,
56
69
  },
57
70
  };
58
71
 
59
- function _initStorage(options) {
72
+ function _initStorage(this: any, options: LocalForageOptions): void {
60
73
  dbInfo.keyPrefix = _getKeyPrefix(options, this._defaultConfig);
61
74
  if (options) {
62
75
  for (const i in options) {
63
- // eslint-disable-line guard-for-in
64
76
  dbInfo[i] = options[i];
65
77
  }
66
78
  }
@@ -68,14 +80,13 @@ function _initStorage(options) {
68
80
 
69
81
  // Remove all keys from the datastore, effectively destroying all data in
70
82
  // the app's key/value store!
71
- function clear(callback) {
83
+ function clear(callback?: (err: any) => void): Promise<void> {
72
84
  const promise = this.ready().then(function () {
73
85
  const keyPrefix = dbInfo.keyPrefix;
74
86
 
75
87
  for (let i = sessionStorage.length - 1; i >= 0; i--) {
76
88
  const key = sessionStorage.key(i);
77
-
78
- if (key.indexOf(keyPrefix) === 0) {
89
+ if (key && key.indexOf(keyPrefix) === 0) {
79
90
  sessionStorage.removeItem(key);
80
91
  }
81
92
  }
@@ -85,66 +96,56 @@ function clear(callback) {
85
96
  return promise;
86
97
  }
87
98
 
88
- // Retrieve an item from the store. Unlike the original async_storage
89
- // library in Gaia, we don't modify return values at all. If a key's value
90
- // is `undefined`, we pass that value to the callback function.
91
- function getItem(key, callback) {
99
+ function getItem<T>(key: string, callback?: (err: any, value: T | null) => void): Promise<T | null> {
92
100
  key = normalizeKey(key);
93
101
 
94
- const promise = this.ready().then(function () {
95
- let result = sessionStorage.getItem(dbInfo.keyPrefix + key);
102
+ const promise = this.ready().then(function (): T | null {
103
+ const result = sessionStorage.getItem(dbInfo.keyPrefix + key);
96
104
  // If a result was found, parse it from the serialized
97
105
  // string into a JS object. If result isn't truthy, the key
98
106
  // is likely undefined and we'll pass it straight to the
99
107
  // callback.
100
108
  if (result) {
101
- result = dbInfo.serializer.deserialize(result);
109
+ return dbInfo.serializer.deserialize(result) as T;
102
110
  }
103
- return result;
111
+ return null;
104
112
  });
113
+
105
114
  executeCallback(promise, callback);
106
115
  return promise;
107
116
  }
108
117
 
109
- // Iterate over all items in the store.
110
- function iterate(iterator, callback) {
118
+ function iterate<T, U>(
119
+ iterator: IteratorCallback<T, U>,
120
+ callback?: (err: any, result: U | undefined) => void
121
+ ): Promise<U | undefined> {
111
122
  const self = this;
112
123
 
113
- const promise = self.ready().then(function () {
124
+ const promise = self.ready().then(function (): U | undefined {
114
125
  const keyPrefix = dbInfo.keyPrefix;
115
126
  const keyPrefixLength = keyPrefix.length;
116
127
  const length = sessionStorage.length;
117
-
118
- // We use a dedicated iterator instead of the `i` variable below
119
- // so other keys we fetch in sessionStorage aren't counted in
120
- // the `iterationNumber` argument passed to the `iterate()`
121
- // callback.
122
- //
123
- // See: github.com/mozilla/localForage/pull/435#discussion_r38061530
124
128
  let iterationNumber = 1;
125
129
 
126
130
  for (let i = 0; i < length; i++) {
127
131
  const key = sessionStorage.key(i);
128
- if (key.indexOf(keyPrefix) !== 0) {
132
+ if (!key || key.indexOf(keyPrefix) !== 0) {
129
133
  continue;
130
134
  }
131
- let value = sessionStorage.getItem(key);
132
135
 
133
136
  // If a result was found, parse it from the serialized
134
137
  // string into a JS object. If result isn't truthy, the
135
138
  // key is likely undefined and we'll pass it straight
136
139
  // to the iterator.
137
- if (value) {
138
- value = dbInfo.serializer.deserialize(value);
139
- }
140
+ const item = sessionStorage.getItem(key);
141
+ const value = item ? dbInfo.serializer.deserialize(item) : item;
140
142
 
141
- value = iterator(value, key.substring(keyPrefixLength), iterationNumber++);
142
-
143
- if (value !== void 0) {
144
- // eslint-disable-line no-void
145
- return value;
143
+ const result = iterator(value as T, key.substring(keyPrefixLength), iterationNumber++);
144
+ if (result !== void 0) {
145
+ return result;
146
146
  }
147
147
  }
148
+ return;
148
149
  });
149
150
 
150
151
  executeCallback(promise, callback);
@@ -152,10 +153,10 @@ function iterate(iterator, callback) {
152
153
  }
153
154
 
154
155
  // Same as sessionStorage's key() method, except takes a callback.
155
- function key(n, callback) {
156
+ function key(n: number, callback?: (err: any, key: string | null) => void): Promise<string | null> {
156
157
  const self = this;
157
- const promise = self.ready().then(function () {
158
- let result;
158
+ const promise = self.ready().then(function (): string | null {
159
+ let result: string | null;
159
160
  try {
160
161
  result = sessionStorage.key(n);
161
162
  } catch (error) {
@@ -174,15 +175,15 @@ function key(n, callback) {
174
175
  return promise;
175
176
  }
176
177
 
177
- function keys(callback) {
178
+ function keys(callback?: (err: any, keys: string[]) => void): Promise<string[]> {
178
179
  const self = this;
179
- const promise = self.ready().then(function () {
180
+ const promise = self.ready().then(function (): string[] {
180
181
  const length = sessionStorage.length;
181
- const keys = [];
182
+ const keys: string[] = [];
182
183
 
183
184
  for (let i = 0; i < length; i++) {
184
185
  const itemKey = sessionStorage.key(i);
185
- if (itemKey.indexOf(dbInfo.keyPrefix) === 0) {
186
+ if (itemKey && itemKey.indexOf(dbInfo.keyPrefix) === 0) {
186
187
  keys.push(itemKey.substring(dbInfo.keyPrefix.length));
187
188
  }
188
189
  }
@@ -194,9 +195,9 @@ function keys(callback) {
194
195
  }
195
196
 
196
197
  // Supply the number of keys in the datastore to the callback function.
197
- function length(callback) {
198
+ function length(callback?: (err: any, numberOfKeys: number) => void): Promise<number> {
198
199
  const self = this;
199
- const promise = self.keys().then(function (keys) {
200
+ const promise = self.keys().then(function (keys: string[]) {
200
201
  return keys.length;
201
202
  });
202
203
 
@@ -204,8 +205,7 @@ function length(callback) {
204
205
  return promise;
205
206
  }
206
207
 
207
- // Remove an item from the store, nice and simple.
208
- function removeItem(key, callback) {
208
+ function removeItem(key: string, callback?: (err: any) => void): Promise<void> {
209
209
  key = normalizeKey(key);
210
210
  const promise = this.ready().then(function () {
211
211
  sessionStorage.removeItem(dbInfo.keyPrefix + key);
@@ -214,50 +214,38 @@ function removeItem(key, callback) {
214
214
  return promise;
215
215
  }
216
216
 
217
- /**
218
- * Set a key's value and run an optional callback once the value is set.
219
- * Unlike Gaia's implementation, the callback function is passed the value,
220
- * in case you want to operate on that value only after you're sure it
221
- * saved, or something like that.
222
- * @template T
223
- * @param {string} key - The key under which the value is stored.
224
- * @param {T} value - The value to be stored, can be of any type.
225
- * @param {(err: any, value: T) => void} [callback] - Optional callback function to be called after the value is set.
226
- * @returns {Promise<T>}
227
- */
228
- async function setItem(key, value, callback) {
217
+ async function setItem<T>(key: string, value: T, callback?: (err: any, value: T) => void): Promise<T> {
229
218
  key = normalizeKey(key);
230
219
  await this.ready();
231
220
 
232
- // Convert undefined values to null.
233
- // https://github.com/mozilla/localForage/pull/42
234
- value = value ?? null;
235
-
236
- // Save the original value to pass to the callback.
221
+ value = value ?? (null as unknown as T);
237
222
  const originalValue = value;
238
223
 
239
- dbInfo.serializer.serialize(value, (value, error) => {
240
- if (error) {
241
- throw error;
242
- } else {
224
+ return new Promise<T>((resolve, reject) => {
225
+ dbInfo.serializer.serialize(value, (serializedValue: string, error: any) => {
226
+ if (error) {
227
+ reject(error);
228
+ executeCallback(Promise.reject(error), callback);
229
+ return;
230
+ }
231
+
243
232
  try {
244
- sessionStorage.setItem(dbInfo.keyPrefix + key, value);
233
+ sessionStorage.setItem(dbInfo.keyPrefix + key, serializedValue);
234
+ resolve(originalValue);
245
235
  executeCallback(Promise.resolve(originalValue), callback);
246
- } catch (e) {
236
+ } catch (e: any) {
247
237
  if (e.name === 'QuotaExceededError' || e.name === 'NS_ERROR_DOM_QUOTA_REACHED') {
248
- console.error('Your sesionStorage capacity is used up.');
249
- throw e;
238
+ console.error('Your sessionStorage capacity is used up.');
250
239
  }
251
- throw e;
240
+ reject(e);
241
+ executeCallback(Promise.reject(e), callback);
252
242
  }
253
- }
243
+ });
254
244
  });
255
-
256
- return value;
257
245
  }
258
246
 
259
- function dropInstance(options, callback) {
260
- callback = getCallback.apply(this, arguments);
247
+ function dropInstance(options: any, callback?: (err: any) => void): Promise<void> {
248
+ callback = getCallback.apply(this, arguments as any);
261
249
 
262
250
  options = (typeof options !== 'function' && options) || {};
263
251
  if (!options.name) {
@@ -267,20 +255,20 @@ function dropInstance(options, callback) {
267
255
  }
268
256
 
269
257
  const self = this;
270
- let promise;
258
+ let promise: Promise<void>;
271
259
  if (!options.name) {
272
260
  promise = Promise.reject(new Error('Invalid arguments'));
273
261
  } else {
274
- promise = new Promise(function (resolve) {
262
+ promise = new Promise<string>(function (resolve) {
275
263
  if (!options.storeName) {
276
264
  resolve(`${options.name}/`);
277
265
  } else {
278
266
  resolve(_getKeyPrefix(options, self._defaultConfig));
279
267
  }
280
- }).then(function (keyPrefix) {
268
+ }).then(function (keyPrefix: string) {
281
269
  for (let i = sessionStorage.length - 1; i >= 0; i--) {
282
270
  const key = sessionStorage.key(i);
283
- if (key.indexOf(keyPrefix) === 0) {
271
+ if (key && key.indexOf(keyPrefix) === 0) {
284
272
  sessionStorage.removeItem(key);
285
273
  }
286
274
  }
@@ -2,48 +2,44 @@
2
2
  * @copyright 2010-2019 Jeremy Ashkenas and DocumentCloud
3
3
  * @copyright 2023 JC Brand
4
4
  */
5
- import isEmpty from 'lodash-es/isEmpty.js';
6
- import keys from 'lodash-es/keys.js';
7
- import uniqueId from 'lodash-es/uniqueId.js';
8
- import Listening from './listening.js';
9
- import { eventsApi, onApi, offApi, onceMap, tryCatchOn, triggerApi } from './utils/events.js';
5
+ import isEmpty from 'lodash-es/isEmpty';
6
+ import keys from 'lodash-es/keys';
7
+ import uniqueId from 'lodash-es/uniqueId';
8
+ import Listening from './listening';
9
+ import { eventsApi, onApi, offApi, onceMap, tryCatchOn, triggerApi } from './utils/events';
10
+ import {
11
+ ClassConstructor,
12
+ EventCallback,
13
+ EventCallbackMap,
14
+ EventContext,
15
+ EventHandlersMap,
16
+ EventListenerMap,
17
+ ObjectListenedTo,
18
+ } from './types';
10
19
 
11
20
  // A private global variable to share between listeners and listenees.
12
- let _listening;
21
+ let _listening: Listening | undefined;
13
22
 
14
23
  /**
15
- * @function
16
- * @template {new(...args: any[]) => {}} ClassConstructor
17
- * @param {ClassConstructor} Base
24
+ * @public
18
25
  */
19
- export function EventEmitter(Base) {
20
- return class EventEmitter extends Base {
21
- /**
22
- * @typedef {import('./model.js').Model} Model
23
- * @typedef {import('./collection.js').Collection} Collection
24
- * @typedef {Record.<string, any>} Options
25
- *
26
- * @callback EventCallback
27
- * @param {any} event
28
- * @param {Model} model
29
- * @param {Collection} collection
30
- * @param {Options} [options]
31
- */
26
+ export function EventEmitter<T extends ClassConstructor>(Base: T) {
27
+ return class EventEmitter extends Base implements EventEmitter {
28
+ _events?: EventHandlersMap;
29
+ _listeners?: EventListenerMap;
30
+ _listeningTo?: EventListenerMap;
31
+ _listenId?: string;
32
32
 
33
33
  /**
34
34
  * Bind an event to a `callback` function. Passing `"all"` will bind
35
35
  * the callback to all events fired.
36
- * @param {string} name
37
- * @param {EventCallback} callback
38
- * @param {any} context
39
- * @return {EventEmitter}
40
36
  */
41
- on(name, callback, context) {
37
+ on(name: string | EventCallbackMap, callback?: EventCallback | EventContext, context?: EventContext): this {
42
38
  this._events = eventsApi(onApi, this._events || {}, name, callback, {
43
39
  context: context,
44
40
  ctx: this,
45
41
  listening: _listening,
46
- });
42
+ }) as EventHandlersMap;
47
43
 
48
44
  if (_listening) {
49
45
  const listeners = this._listeners || (this._listeners = {});
@@ -60,13 +56,10 @@ export function EventEmitter(Base) {
60
56
  * Inversion-of-control versions of `on`. Tell *this* object to listen to
61
57
  * an event in another object... keeping track of what it's listening to
62
58
  * for easier unbinding later.
63
- * @param {any} obj
64
- * @param {string} name
65
- * @param {EventCallback} [callback]
66
- * @return {EventEmitter}
67
59
  */
68
- listenTo(obj, name, callback) {
60
+ listenTo(obj: ObjectListenedTo, name: string | EventCallbackMap, callback?: EventCallback): this {
69
61
  if (!obj) return this;
62
+
70
63
  const id = obj._listenId || (obj._listenId = uniqueId('l'));
71
64
  const listeningTo = this._listeningTo || (this._listeningTo = {});
72
65
  let listening = (_listening = listeningTo[id]);
@@ -94,17 +87,17 @@ export function EventEmitter(Base) {
94
87
  * callbacks with that function. If `callback` is null, removes all
95
88
  * callbacks for the event. If `name` is null, removes all bound
96
89
  * callbacks for all events.
97
- * @param {string} name
98
- * @param {EventCallback} callback
99
- * @param {any} [context]
100
- * @return {EventEmitter}
101
90
  */
102
- off(name, callback, context) {
91
+ off(
92
+ name?: string | EventCallbackMap | null,
93
+ callback?: EventCallback | EventContext | null,
94
+ context?: EventContext
95
+ ): this {
103
96
  if (!this._events) return this;
104
97
  this._events = eventsApi(offApi, this._events, name, callback, {
105
98
  context: context,
106
99
  listeners: this._listeners,
107
- });
100
+ }) as EventHandlersMap;
108
101
 
109
102
  return this;
110
103
  }
@@ -112,12 +105,8 @@ export function EventEmitter(Base) {
112
105
  /**
113
106
  * Tell this object to stop listening to either specific events ... or
114
107
  * to every object it's currently listening to.
115
- * @param {any} [obj]
116
- * @param {string} [name]
117
- * @param {EventCallback} [callback]
118
- * @return {EventEmitter}
119
108
  */
120
- stopListening(obj, name, callback) {
109
+ stopListening(obj?: any, name?: string | EventCallbackMap, callback?: EventCallback): this {
121
110
  const listeningTo = this._listeningTo;
122
111
  if (!listeningTo) return this;
123
112
 
@@ -142,29 +131,21 @@ export function EventEmitter(Base) {
142
131
  * the callback is invoked, its listener will be removed. If multiple events
143
132
  * are passed in using the space-separated syntax, the handler will fire
144
133
  * once for each event, not once for a combination of all events.
145
- * @param {string} name
146
- * @param {EventCallback} callback
147
- * @param {any} context
148
- * @return {EventEmitter}
149
134
  */
150
- once(name, callback, context) {
135
+ once(name: string | EventCallbackMap, callback?: EventCallback | EventContext, context?: EventContext): this {
151
136
  // Map the event into a `{event: once}` object.
152
137
  const events = eventsApi(onceMap, {}, name, callback, this.off.bind(this));
153
138
  if (typeof name === 'string' && (context === null || context === undefined)) callback = undefined;
154
- return this.on(events, callback, context);
139
+ return this.on(events as EventCallbackMap, callback, context);
155
140
  }
156
141
 
157
142
  /**
158
143
  * Inversion-of-control versions of `once`.
159
- * @param {any} obj
160
- * @param {string} name
161
- * @param {EventCallback} [callback]
162
- * @return {EventEmitter}
163
144
  */
164
- listenToOnce(obj, name, callback) {
145
+ listenToOnce(obj: any, name: string | EventCallbackMap, callback?: EventCallback): this {
165
146
  // Map the event into a `{event: once}` object.
166
147
  const events = eventsApi(onceMap, {}, name, callback, this.stopListening.bind(this, obj));
167
- return this.listenTo(obj, events);
148
+ return this.listenTo(obj, events as string | EventCallbackMap);
168
149
  }
169
150
 
170
151
  /**
@@ -172,10 +153,8 @@ export function EventEmitter(Base) {
172
153
  * passed the same arguments as `trigger` is, apart from the event name
173
154
  * (unless you're listening on `"all"`, which will cause your callback to
174
155
  * receive the true name of the event as the first argument).
175
- * @param {string} name
176
- * @return {EventEmitter}
177
156
  */
178
- trigger(name, ...args) {
157
+ trigger(name: string, ...args: any[]): this {
179
158
  if (!this._events) return this;
180
159
 
181
160
  eventsApi(triggerApi, this._events, name, undefined, args);
@@ -184,4 +163,10 @@ export function EventEmitter(Base) {
184
163
  };
185
164
  }
186
165
 
166
+ /**
167
+ * @public
168
+ */
169
+ const EventEmitterObject = EventEmitter(Object);
170
+ export { EventEmitterObject };
171
+
187
172
  export default EventEmitter;