@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.
- package/README.md +5 -89
- package/dist/skeletor.d.ts +764 -0
- package/dist/skeletor.js +7564 -7743
- package/dist/skeletor.min.js +6 -11
- package/package.json +46 -30
- package/src/{collection.js → collection.ts} +232 -271
- package/src/drivers/{sessionStorage.js → sessionStorage.ts} +81 -93
- package/src/{eventemitter.js → eventemitter.ts} +44 -59
- package/src/{helpers.js → helpers.ts} +56 -69
- package/src/index.ts +84 -0
- package/src/{listening.js → listening.ts} +32 -27
- package/src/{model.js → model.ts} +143 -162
- package/src/{storage.js → storage.ts} +92 -49
- package/src/types.ts +189 -0
- package/src/utils/{events.js → events.ts} +98 -48
- package/dist/skeletor.js.map +0 -1
- package/dist/skeletor.min.js.map +0 -1
- package/src/element.js +0 -238
- package/src/index.js +0 -47
- package/src/types/collection.d.ts +0 -366
- package/src/types/collection.d.ts.map +0 -1
- package/src/types/drivers/sessionStorage.d.ts +0 -37
- package/src/types/drivers/sessionStorage.d.ts.map +0 -1
- package/src/types/element.d.ts +0 -115
- package/src/types/element.d.ts.map +0 -1
- package/src/types/eventemitter.d.ts +0 -93
- package/src/types/eventemitter.d.ts.map +0 -1
- package/src/types/helpers.d.ts +0 -62
- package/src/types/helpers.d.ts.map +0 -1
- package/src/types/index.d.ts +0 -19
- package/src/types/index.d.ts.map +0 -1
- package/src/types/listening.d.ts +0 -40
- package/src/types/listening.d.ts.map +0 -1
- package/src/types/model.d.ts +0 -230
- package/src/types/model.d.ts.map +0 -1
- package/src/types/storage.d.ts +0 -35
- package/src/types/storage.d.ts.map +0 -1
- package/src/types/utils/events.d.ts +0 -28
- package/src/types/utils/events.d.ts.map +0 -1
|
@@ -2,33 +2,71 @@
|
|
|
2
2
|
* IndexedDB, localStorage and sessionStorage adapter
|
|
3
3
|
*/
|
|
4
4
|
import * as memoryDriver from 'localforage-driver-memory';
|
|
5
|
-
import cloneDeep from 'lodash-es/cloneDeep
|
|
6
|
-
import isString from 'lodash-es/isString
|
|
5
|
+
import cloneDeep from 'lodash-es/cloneDeep';
|
|
6
|
+
import isString from 'lodash-es/isString';
|
|
7
7
|
import localForage from 'localforage';
|
|
8
8
|
import mergebounce from 'mergebounce';
|
|
9
|
-
import sessionStorageWrapper from './drivers/sessionStorage
|
|
9
|
+
import sessionStorageWrapper from './drivers/sessionStorage';
|
|
10
10
|
import { extendPrototype as extendPrototypeWithSetItems } from 'localforage-setitems';
|
|
11
11
|
import { extendPrototype as extendPrototypeWithGetItems } from '@converse/localforage-getitems/dist/localforage-getitems.es6';
|
|
12
|
-
import { guid } from './helpers
|
|
12
|
+
import { guid } from './helpers';
|
|
13
|
+
import type { Model } from './model';
|
|
14
|
+
import type { Collection } from './collection';
|
|
15
|
+
import type { SyncOptions, SyncOperation } from './types';
|
|
13
16
|
|
|
14
17
|
const IN_MEMORY = memoryDriver._driver;
|
|
15
18
|
localForage.defineDriver(memoryDriver);
|
|
16
19
|
extendPrototypeWithSetItems(localForage);
|
|
17
20
|
extendPrototypeWithGetItems(localForage);
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
/**
|
|
23
|
+
* @public
|
|
24
|
+
*/
|
|
25
|
+
export interface LocalForageWithExtensions {
|
|
26
|
+
setItem(key: string, value: any): Promise<any>;
|
|
27
|
+
getItem(key: string): Promise<any>;
|
|
28
|
+
removeItem(key: string): Promise<void>;
|
|
29
|
+
clear(): Promise<void>;
|
|
30
|
+
length(): Promise<number>;
|
|
31
|
+
key(keyIndex: number): Promise<string>;
|
|
32
|
+
keys(): Promise<string[]>;
|
|
33
|
+
setItems?(items: Record<string, any>): Promise<void>;
|
|
34
|
+
getItems?(keys: string[]): Promise<Record<string, any>>;
|
|
35
|
+
debouncedSetItems?: {
|
|
36
|
+
(items: Record<string, any>): Promise<void>;
|
|
37
|
+
flush?: () => void;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @public
|
|
43
|
+
*/
|
|
44
|
+
class BrowserStorage {
|
|
45
|
+
storeInitialized: Promise<void>;
|
|
46
|
+
store: LocalForageWithExtensions;
|
|
47
|
+
name: string;
|
|
48
|
+
|
|
49
|
+
static sessionStorageInitialized: Promise<void>;
|
|
50
|
+
static localForage: typeof localForage;
|
|
51
|
+
|
|
52
|
+
constructor(
|
|
53
|
+
id: string,
|
|
54
|
+
type: 'local' | 'session' | 'indexed' | 'in_memory' | LocalForageWithExtensions,
|
|
55
|
+
batchedWrites = false
|
|
56
|
+
) {
|
|
21
57
|
if (type === 'local' && !window.localStorage) {
|
|
22
58
|
throw new Error('Skeletor.storage: Environment does not support localStorage.');
|
|
23
59
|
} else if (type === 'session' && !window.sessionStorage) {
|
|
24
60
|
throw new Error('Skeletor.storage: Environment does not support sessionStorage.');
|
|
25
61
|
}
|
|
26
62
|
if (isString(type)) {
|
|
27
|
-
this.storeInitialized = this.initStore(type, batchedWrites);
|
|
63
|
+
this.storeInitialized = this.initStore(type as 'local' | 'session' | 'indexed' | 'in_memory', batchedWrites);
|
|
28
64
|
} else {
|
|
29
65
|
this.store = type;
|
|
30
66
|
if (batchedWrites) {
|
|
31
|
-
this.store.debouncedSetItems = mergebounce((items) => this.store.setItems(items), 50, {
|
|
67
|
+
this.store.debouncedSetItems = mergebounce((items: Record<string, any>) => this.store.setItems!(items), 50, {
|
|
68
|
+
'promise': true,
|
|
69
|
+
});
|
|
32
70
|
}
|
|
33
71
|
this.storeInitialized = Promise.resolve();
|
|
34
72
|
}
|
|
@@ -36,10 +74,10 @@ class Storage {
|
|
|
36
74
|
}
|
|
37
75
|
|
|
38
76
|
/**
|
|
39
|
-
* @param
|
|
40
|
-
* @param
|
|
77
|
+
* @param type - The storage type: 'local', 'session', 'indexed', or 'in_memory'
|
|
78
|
+
* @param batchedWrites - Whether to enable batched writes
|
|
41
79
|
*/
|
|
42
|
-
async initStore(type, batchedWrites) {
|
|
80
|
+
async initStore(type: 'local' | 'session' | 'indexed' | 'in_memory', batchedWrites: boolean): Promise<void> {
|
|
43
81
|
if (type === 'session') {
|
|
44
82
|
await localForage.setDriver(sessionStorageWrapper._driver);
|
|
45
83
|
} else if (type === 'local') {
|
|
@@ -49,17 +87,21 @@ class Storage {
|
|
|
49
87
|
} else if (type !== 'indexed') {
|
|
50
88
|
throw new Error('Skeletor.storage: No storage type was specified');
|
|
51
89
|
}
|
|
52
|
-
this.store = localForage;
|
|
90
|
+
this.store = localForage as LocalForageWithExtensions;
|
|
53
91
|
if (batchedWrites) {
|
|
54
|
-
this.store.debouncedSetItems = mergebounce((items) => this.store.setItems(items), 50, {
|
|
92
|
+
this.store.debouncedSetItems = mergebounce((items: Record<string, any>) => this.store.setItems!(items), 50, {
|
|
93
|
+
'promise': true,
|
|
94
|
+
});
|
|
55
95
|
}
|
|
56
96
|
}
|
|
57
97
|
|
|
58
|
-
flush() {
|
|
59
|
-
|
|
98
|
+
flush(): void {
|
|
99
|
+
if (this.store.debouncedSetItems && typeof this.store.debouncedSetItems.flush === 'function') {
|
|
100
|
+
this.store.debouncedSetItems.flush();
|
|
101
|
+
}
|
|
60
102
|
}
|
|
61
103
|
|
|
62
|
-
async clear() {
|
|
104
|
+
async clear(): Promise<void> {
|
|
63
105
|
await this.store.removeItem(this.name).catch((e) => console.error(e));
|
|
64
106
|
const re = new RegExp(`^${this.name}-`);
|
|
65
107
|
const keys = await this.store.keys();
|
|
@@ -71,8 +113,8 @@ class Storage {
|
|
|
71
113
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
72
114
|
const that = this;
|
|
73
115
|
|
|
74
|
-
async function localSync(method, model, options) {
|
|
75
|
-
let resp, errorMessage, promise
|
|
116
|
+
async function localSync(method: SyncOperation, model: Model, options: SyncOptions) {
|
|
117
|
+
let resp: any, errorMessage: string | undefined, promise: Promise<any>, new_attributes: any;
|
|
76
118
|
|
|
77
119
|
// We get the collection (and if necessary the model attribute.
|
|
78
120
|
// Waiting for storeInitialized will cause another iteration of
|
|
@@ -121,8 +163,9 @@ class Storage {
|
|
|
121
163
|
resp = await that.destroy(model, collection);
|
|
122
164
|
break;
|
|
123
165
|
}
|
|
124
|
-
} catch (error) {
|
|
125
|
-
|
|
166
|
+
} catch (error: any) {
|
|
167
|
+
const storageSize = await that.getStorageSize();
|
|
168
|
+
if (error.code === 22 && storageSize === 0) {
|
|
126
169
|
errorMessage = 'Private browsing is unsupported';
|
|
127
170
|
} else {
|
|
128
171
|
errorMessage = error.message;
|
|
@@ -150,56 +193,56 @@ class Storage {
|
|
|
150
193
|
return localSync;
|
|
151
194
|
}
|
|
152
195
|
|
|
153
|
-
removeCollectionReference(model, collection) {
|
|
196
|
+
removeCollectionReference(model: Model, collection: Collection | undefined): Promise<any> | undefined {
|
|
154
197
|
if (!collection) {
|
|
155
198
|
return;
|
|
156
199
|
}
|
|
157
|
-
const ids = collection.filter((m) => m.id !== model.id).map((m) => this.getItemName(m.id));
|
|
200
|
+
const ids = collection.filter((m) => m.id !== model.id).map((m) => this.getItemName(m.id!));
|
|
158
201
|
|
|
159
202
|
return this.store.setItem(this.name, ids);
|
|
160
203
|
}
|
|
161
204
|
|
|
162
|
-
addCollectionReference(model, collection) {
|
|
205
|
+
addCollectionReference(model: Model, collection: Collection | undefined): Promise<any> | undefined {
|
|
163
206
|
if (!collection) {
|
|
164
207
|
return;
|
|
165
208
|
}
|
|
166
|
-
const ids = collection.map((m) => this.getItemName(m.id));
|
|
167
|
-
const new_id = this.getItemName(model.id);
|
|
209
|
+
const ids = collection.map((m) => this.getItemName(m.id!));
|
|
210
|
+
const new_id = this.getItemName(model.id!);
|
|
168
211
|
if (!ids.includes(new_id)) {
|
|
169
212
|
ids.push(new_id);
|
|
170
213
|
}
|
|
171
214
|
return this.store.setItem(this.name, ids);
|
|
172
215
|
}
|
|
173
216
|
|
|
174
|
-
getCollectionReferenceData(model) {
|
|
217
|
+
getCollectionReferenceData(model: Model): Record<string, string[]> {
|
|
175
218
|
if (!model.collection) {
|
|
176
219
|
return {};
|
|
177
220
|
}
|
|
178
|
-
const ids = model.collection.map((m) => this.getItemName(m.id));
|
|
179
|
-
const new_id = this.getItemName(model.id);
|
|
221
|
+
const ids = model.collection.map((m) => this.getItemName(m.id!));
|
|
222
|
+
const new_id = this.getItemName(model.id!);
|
|
180
223
|
if (!ids.includes(new_id)) {
|
|
181
224
|
ids.push(new_id);
|
|
182
225
|
}
|
|
183
|
-
const result = {};
|
|
226
|
+
const result: Record<string, string[]> = {};
|
|
184
227
|
result[this.name] = ids;
|
|
185
228
|
return result;
|
|
186
229
|
}
|
|
187
230
|
|
|
188
|
-
async save(model) {
|
|
231
|
+
async save(model: Model): Promise<any> {
|
|
189
232
|
if (this.store.setItems) {
|
|
190
|
-
const items = {};
|
|
191
|
-
items[this.getItemName(model.id)] = model.toJSON();
|
|
233
|
+
const items: Record<string, any> = {};
|
|
234
|
+
items[this.getItemName(model.id!)] = model.toJSON();
|
|
192
235
|
Object.assign(items, this.getCollectionReferenceData(model));
|
|
193
|
-
return this.store.debouncedSetItems ? this.store.debouncedSetItems(items) : this.store.setItems(items);
|
|
236
|
+
return this.store.debouncedSetItems ? this.store.debouncedSetItems(items) : this.store.setItems!(items);
|
|
194
237
|
} else {
|
|
195
|
-
const key = this.getItemName(model.id);
|
|
238
|
+
const key = this.getItemName(model.id!);
|
|
196
239
|
const data = await this.store.setItem(key, model.toJSON());
|
|
197
240
|
await this.addCollectionReference(model, model.collection);
|
|
198
241
|
return data;
|
|
199
242
|
}
|
|
200
243
|
}
|
|
201
244
|
|
|
202
|
-
create(model, options) {
|
|
245
|
+
create(model: Model, options: SyncOptions): Promise<any> {
|
|
203
246
|
/* Add a model, giving it a (hopefully)-unique GUID, if it doesn't already
|
|
204
247
|
* have an id of it's own.
|
|
205
248
|
*/
|
|
@@ -210,41 +253,41 @@ class Storage {
|
|
|
210
253
|
return this.save(model);
|
|
211
254
|
}
|
|
212
255
|
|
|
213
|
-
update(model) {
|
|
256
|
+
update(model: Model): Promise<any> {
|
|
214
257
|
return this.save(model);
|
|
215
258
|
}
|
|
216
259
|
|
|
217
|
-
find(model) {
|
|
218
|
-
return this.store.getItem(this.getItemName(model.id));
|
|
260
|
+
find(model: Model): Promise<any> {
|
|
261
|
+
return this.store.getItem(this.getItemName(model.id!));
|
|
219
262
|
}
|
|
220
263
|
|
|
221
|
-
async findAll() {
|
|
264
|
+
async findAll(): Promise<any[]> {
|
|
222
265
|
/* Return the array of all models currently in storage.
|
|
223
266
|
*/
|
|
224
|
-
const keys = await this.store.getItem(this.name);
|
|
267
|
+
const keys = (await this.store.getItem(this.name)) as string[] | null;
|
|
225
268
|
if (keys?.length) {
|
|
226
|
-
const items = await this.store.getItems(keys);
|
|
269
|
+
const items = await this.store.getItems!(keys);
|
|
227
270
|
return Object.values(items);
|
|
228
271
|
}
|
|
229
272
|
return [];
|
|
230
273
|
}
|
|
231
274
|
|
|
232
|
-
async destroy(model, collection) {
|
|
275
|
+
async destroy(model: Model, collection: Collection | undefined): Promise<Model> {
|
|
233
276
|
await this.flush();
|
|
234
|
-
await this.store.removeItem(this.getItemName(model.id));
|
|
277
|
+
await this.store.removeItem(this.getItemName(model.id!));
|
|
235
278
|
await this.removeCollectionReference(model, collection);
|
|
236
279
|
return model;
|
|
237
280
|
}
|
|
238
281
|
|
|
239
|
-
getStorageSize() {
|
|
240
|
-
return this.store.length;
|
|
282
|
+
async getStorageSize(): Promise<number> {
|
|
283
|
+
return await this.store.length();
|
|
241
284
|
}
|
|
242
285
|
|
|
243
|
-
getItemName(id) {
|
|
286
|
+
getItemName(id: string | number): string {
|
|
244
287
|
return this.name + '-' + id;
|
|
245
288
|
}
|
|
246
289
|
}
|
|
247
290
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
export default
|
|
291
|
+
BrowserStorage.sessionStorageInitialized = localForage.defineDriver(sessionStorageWrapper);
|
|
292
|
+
BrowserStorage.localForage = localForage;
|
|
293
|
+
export default BrowserStorage;
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import {Collection} from './collection';
|
|
2
|
+
import {Model} from './model';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @public
|
|
6
|
+
*/
|
|
7
|
+
export type SyncOperation = 'create' | 'update' | 'patch' | 'delete' | 'read';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @public
|
|
11
|
+
*/
|
|
12
|
+
export type ObjectListenedTo = object & {_listenId?: string};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @public
|
|
16
|
+
*/
|
|
17
|
+
export interface SyncOptions {
|
|
18
|
+
url?: string;
|
|
19
|
+
data?: any;
|
|
20
|
+
attrs?: any;
|
|
21
|
+
success?: (data?: any, options?: SyncOptions) => void;
|
|
22
|
+
error?: (error: any) => void;
|
|
23
|
+
xhr?: any;
|
|
24
|
+
wait?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @public
|
|
29
|
+
*/
|
|
30
|
+
export type ClassConstructor<T = any> = new (...args: any[]) => T;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @public
|
|
34
|
+
*/
|
|
35
|
+
export type EventContext = unknown;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @public
|
|
39
|
+
*/
|
|
40
|
+
export type EventCallback = (...args: any[]) => void;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @public
|
|
44
|
+
*/
|
|
45
|
+
export type EventCallbackMap = Record<string, EventCallback>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @public
|
|
49
|
+
*/
|
|
50
|
+
export type EventCallbacksMap = Record<string, EventCallback[]>;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @public
|
|
54
|
+
*/
|
|
55
|
+
export type EventListenerMap = Record<string, ListeningType>;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @public
|
|
59
|
+
*/
|
|
60
|
+
export interface EventHandlersMap {
|
|
61
|
+
all?: EventHandler[];
|
|
62
|
+
[name: string]: EventHandler[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @public
|
|
67
|
+
*/
|
|
68
|
+
export type ListeningMap = Record<string, ListeningType>;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @public
|
|
72
|
+
*/
|
|
73
|
+
export type ObjectWithId = Record<string, any> & {id: string | number};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @public
|
|
77
|
+
*/
|
|
78
|
+
export interface IEventEmitter {
|
|
79
|
+
_events?: Record<string, any>;
|
|
80
|
+
_listeners?: ListeningMap;
|
|
81
|
+
_listeningTo?: ListeningMap;
|
|
82
|
+
_listenId?: string;
|
|
83
|
+
|
|
84
|
+
on(name: string | EventCallbackMap, callback?: EventCallback | EventContext, context?: EventContext): this;
|
|
85
|
+
off(
|
|
86
|
+
name?: string | EventCallbackMap | null,
|
|
87
|
+
callback?: EventCallback | EventContext | null,
|
|
88
|
+
context?: EventContext
|
|
89
|
+
): this;
|
|
90
|
+
trigger(name: string, ...args: any[]): this;
|
|
91
|
+
stopListening(obj?: any, name?: string | EventCallbackMap, callback?: EventCallback): this;
|
|
92
|
+
once(name: string | EventCallbackMap, callback?: EventCallback | EventContext, context?: EventContext): this;
|
|
93
|
+
listenTo(obj: ObjectListenedTo, name: string | EventCallbackMap, callback?: EventCallback): this;
|
|
94
|
+
listenToOnce(obj: any, name: string | EventCallbackMap, callback?: EventCallback): this;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @public
|
|
99
|
+
*/
|
|
100
|
+
export interface EventHandler {
|
|
101
|
+
callback: EventCallback;
|
|
102
|
+
context: any;
|
|
103
|
+
ctx: any;
|
|
104
|
+
listening?: ListeningType | null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @public
|
|
109
|
+
*/
|
|
110
|
+
export interface ListeningType {
|
|
111
|
+
_events?: EventCallbackMap;
|
|
112
|
+
cleanup(): void;
|
|
113
|
+
count: number;
|
|
114
|
+
id: string;
|
|
115
|
+
interop: boolean;
|
|
116
|
+
listener: IEventEmitter;
|
|
117
|
+
obj: any;
|
|
118
|
+
|
|
119
|
+
start(name: string | EventCallbackMap, callback: EventCallback, context: any, _listening: ListeningType): this;
|
|
120
|
+
stop(name: string | EventCallbackMap, callback: EventCallback): void;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @public
|
|
125
|
+
*/
|
|
126
|
+
export interface EventsApiOptions {
|
|
127
|
+
context?: any;
|
|
128
|
+
ctx?: any;
|
|
129
|
+
listening?: ListeningType;
|
|
130
|
+
listeners?: ListeningMap;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* @public
|
|
135
|
+
*/
|
|
136
|
+
export interface OffApiOptions {
|
|
137
|
+
context?: any;
|
|
138
|
+
listeners?: {[key: string]: ListeningType};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @public
|
|
143
|
+
*/
|
|
144
|
+
export type Comparator<T extends Model = Model> = string | boolean | ((a: T, b: T) => number) | ((a?: T) => string);
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @public
|
|
148
|
+
*/
|
|
149
|
+
export type OfferFunction = (name: string, callback: EventCallback) => void;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @public
|
|
153
|
+
*/
|
|
154
|
+
export type IterateeFunction = (
|
|
155
|
+
eventsOrMap: EventCallbackMap | EventHandlersMap,
|
|
156
|
+
name: string | null,
|
|
157
|
+
callback: EventCallback | EventHandler | null,
|
|
158
|
+
options: EventsApiOptions | OffApiOptions | OfferFunction | any[]
|
|
159
|
+
) => EventCallbackMap | EventHandlersMap | void;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* @public
|
|
163
|
+
*/
|
|
164
|
+
export type ModelAttributes = Record<string | number, any> & {id?: string | number};
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* @public
|
|
168
|
+
*/
|
|
169
|
+
export type Options = Record<string, any>;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* @public
|
|
173
|
+
*/
|
|
174
|
+
export type CollectionOptions<T extends Model = Model> = Options & {
|
|
175
|
+
model?: new (attributes?: Partial<ModelAttributes>, options?: ModelOptions) => T;
|
|
176
|
+
comparator?: Comparator<T>;
|
|
177
|
+
previousModels?: Model[];
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* @public
|
|
182
|
+
*/
|
|
183
|
+
export type ModelOptions = Options & {
|
|
184
|
+
collection?: Collection;
|
|
185
|
+
parse?: boolean;
|
|
186
|
+
unset?: boolean;
|
|
187
|
+
silent?: boolean;
|
|
188
|
+
validate?: boolean;
|
|
189
|
+
};
|
|
@@ -1,5 +1,15 @@
|
|
|
1
|
-
import once from 'lodash-es/once
|
|
2
|
-
import
|
|
1
|
+
import once from 'lodash-es/once';
|
|
2
|
+
import type {
|
|
3
|
+
EventCallback,
|
|
4
|
+
EventHandler,
|
|
5
|
+
EventCallbackMap,
|
|
6
|
+
EventsApiOptions,
|
|
7
|
+
IterateeFunction,
|
|
8
|
+
OffApiOptions,
|
|
9
|
+
OfferFunction,
|
|
10
|
+
EventHandlersMap,
|
|
11
|
+
EventContext,
|
|
12
|
+
} from '../types';
|
|
3
13
|
|
|
4
14
|
// Regular expression used to split event strings.
|
|
5
15
|
const eventSplitter = /\s+/;
|
|
@@ -9,34 +19,55 @@ const eventSplitter = /\s+/;
|
|
|
9
19
|
* space-separated events `"change blur", callback` and jQuery-style event
|
|
10
20
|
* maps `{event: callback}`).
|
|
11
21
|
*/
|
|
12
|
-
export function eventsApi(
|
|
13
|
-
|
|
14
|
-
|
|
22
|
+
export function eventsApi(
|
|
23
|
+
iteratee: IterateeFunction,
|
|
24
|
+
events: EventHandlersMap | EventCallbackMap,
|
|
25
|
+
name: string | EventCallbackMap | null,
|
|
26
|
+
callback: EventCallback | EventHandler | EventContext | null,
|
|
27
|
+
opts: EventsApiOptions | OffApiOptions | any[]
|
|
28
|
+
): EventHandlersMap | EventCallbackMap {
|
|
29
|
+
let i = 0;
|
|
30
|
+
let names: string[];
|
|
31
|
+
|
|
15
32
|
if (name && typeof name === 'object') {
|
|
16
33
|
// Handle event maps.
|
|
17
|
-
if (callback !== undefined && 'context' in opts && opts.context === undefined)
|
|
18
|
-
|
|
34
|
+
if (callback !== undefined && 'context' in opts && opts.context === undefined) {
|
|
35
|
+
opts.context = callback;
|
|
36
|
+
}
|
|
37
|
+
for (names = Object.keys(name); i < names.length; i++) {
|
|
19
38
|
events = eventsApi(iteratee, events, names[i], name[names[i]], opts);
|
|
20
39
|
}
|
|
21
|
-
} else if (name && eventSplitter.test(name)) {
|
|
40
|
+
} else if (name && typeof name === 'string' && eventSplitter.test(name)) {
|
|
22
41
|
// Handle space-separated event names by delegating them individually.
|
|
23
42
|
for (names = name.split(eventSplitter); i < names.length; i++) {
|
|
24
|
-
|
|
43
|
+
const result = iteratee(events, names[i], callback as EventCallback | EventHandler, opts);
|
|
44
|
+
if (result !== undefined) {
|
|
45
|
+
events = result as EventHandlersMap;
|
|
46
|
+
}
|
|
25
47
|
}
|
|
26
48
|
} else {
|
|
27
49
|
// Finally, standard events.
|
|
28
|
-
events = iteratee(events, name, callback, opts)
|
|
50
|
+
events = iteratee(events, name as string | null, callback as EventCallback | EventHandler, opts) as
|
|
51
|
+
| EventHandlersMap
|
|
52
|
+
| EventCallbackMap;
|
|
29
53
|
}
|
|
30
54
|
return events;
|
|
31
55
|
}
|
|
32
56
|
|
|
33
|
-
|
|
34
|
-
|
|
57
|
+
/**
|
|
58
|
+
* A reducer that adds a callback to the `events` object.
|
|
59
|
+
*/
|
|
60
|
+
export function onApi(
|
|
61
|
+
events: EventHandlersMap,
|
|
62
|
+
name: string,
|
|
63
|
+
callback: EventCallback | null,
|
|
64
|
+
options: EventsApiOptions
|
|
65
|
+
): EventHandlersMap {
|
|
35
66
|
if (callback) {
|
|
36
67
|
const handlers = events[name] || (events[name] = []);
|
|
37
|
-
const context = options.context
|
|
38
|
-
|
|
39
|
-
|
|
68
|
+
const context = options.context;
|
|
69
|
+
const ctx = options.ctx;
|
|
70
|
+
const listening = options.listening;
|
|
40
71
|
if (listening) listening.count++;
|
|
41
72
|
|
|
42
73
|
handlers.push({ callback, context, ctx: context || ctx, listening });
|
|
@@ -47,12 +78,13 @@ export function onApi(events, name, callback, options) {
|
|
|
47
78
|
/**
|
|
48
79
|
* An try-catch guarded #on function, to prevent poisoning the global
|
|
49
80
|
* `_listening` variable.
|
|
50
|
-
* @param {any} obj
|
|
51
|
-
* @param {string} name
|
|
52
|
-
* @param {Function} callback
|
|
53
|
-
* @param {any} context
|
|
54
81
|
*/
|
|
55
|
-
export function tryCatchOn(
|
|
82
|
+
export function tryCatchOn(
|
|
83
|
+
obj: any,
|
|
84
|
+
name: string | EventCallbackMap,
|
|
85
|
+
callback: EventCallback | EventContext,
|
|
86
|
+
context: EventContext
|
|
87
|
+
): any {
|
|
56
88
|
try {
|
|
57
89
|
obj.on(name, callback, context);
|
|
58
90
|
} catch (e) {
|
|
@@ -63,52 +95,59 @@ export function tryCatchOn(obj, name, callback, context) {
|
|
|
63
95
|
/**
|
|
64
96
|
* The reducing API that removes a callback from the `events` object.
|
|
65
97
|
*/
|
|
66
|
-
export function offApi(
|
|
98
|
+
export function offApi(
|
|
99
|
+
events: EventHandlersMap,
|
|
100
|
+
name: string | null,
|
|
101
|
+
callback: EventCallback | null,
|
|
102
|
+
options: OffApiOptions
|
|
103
|
+
): EventHandlersMap | void {
|
|
67
104
|
if (!events) return;
|
|
68
105
|
|
|
69
|
-
const context = options.context
|
|
70
|
-
|
|
71
|
-
let i = 0
|
|
72
|
-
|
|
106
|
+
const context = options.context;
|
|
107
|
+
const listeners = options.listeners;
|
|
108
|
+
let i = 0;
|
|
109
|
+
let names: string[];
|
|
73
110
|
|
|
74
111
|
// Delete all event listeners and "drop" events.
|
|
75
112
|
if (!name && !context && !callback) {
|
|
76
|
-
|
|
77
|
-
|
|
113
|
+
if (listeners) {
|
|
114
|
+
for (names = Object.keys(listeners); i < names.length; i++) {
|
|
115
|
+
listeners[names[i]].cleanup();
|
|
116
|
+
}
|
|
78
117
|
}
|
|
79
118
|
return;
|
|
80
119
|
}
|
|
81
120
|
|
|
82
|
-
names = name ? [name] : keys(events);
|
|
121
|
+
names = name ? [name] : Object.keys(events);
|
|
83
122
|
for (; i < names.length; i++) {
|
|
84
|
-
|
|
85
|
-
const handlers = events[
|
|
123
|
+
const currentName = names[i];
|
|
124
|
+
const handlers = events[currentName];
|
|
86
125
|
|
|
87
126
|
// Bail out if there are no events stored.
|
|
88
127
|
if (!handlers) {
|
|
89
|
-
|
|
128
|
+
continue;
|
|
90
129
|
}
|
|
91
130
|
|
|
92
131
|
// Find any remaining events.
|
|
93
|
-
const remaining = [];
|
|
132
|
+
const remaining: EventHandler[] = [];
|
|
94
133
|
for (let j = 0; j < handlers.length; j++) {
|
|
95
134
|
const handler = handlers[j];
|
|
96
135
|
if (
|
|
97
|
-
(callback && callback !== handler.callback && callback !== handler.callback._callback) ||
|
|
136
|
+
(callback && callback !== handler.callback && callback !== (handler.callback as any)._callback) ||
|
|
98
137
|
(context && context !== handler.context)
|
|
99
138
|
) {
|
|
100
139
|
remaining.push(handler);
|
|
101
140
|
} else {
|
|
102
141
|
const listening = handler.listening;
|
|
103
|
-
if (listening) listening.stop(
|
|
142
|
+
if (listening) listening.stop(currentName, callback!);
|
|
104
143
|
}
|
|
105
144
|
}
|
|
106
145
|
|
|
107
146
|
// Replace events if there are any remaining. Otherwise, clean up.
|
|
108
147
|
if (remaining.length) {
|
|
109
|
-
events[
|
|
148
|
+
events[currentName] = remaining;
|
|
110
149
|
} else {
|
|
111
|
-
delete events[
|
|
150
|
+
delete events[currentName];
|
|
112
151
|
}
|
|
113
152
|
}
|
|
114
153
|
|
|
@@ -119,19 +158,30 @@ export function offApi(events, name, callback, options) {
|
|
|
119
158
|
* Reduces the event callbacks into a map of `{event: onceWrapper}`.
|
|
120
159
|
* `offer` unbinds the `onceWrapper` after it has been called.
|
|
121
160
|
*/
|
|
122
|
-
export function onceMap(
|
|
161
|
+
export function onceMap(
|
|
162
|
+
map: EventCallbackMap,
|
|
163
|
+
name: string,
|
|
164
|
+
callback: EventCallback | null,
|
|
165
|
+
offer: OfferFunction
|
|
166
|
+
): { [name: string]: EventCallback } {
|
|
123
167
|
if (callback) {
|
|
124
|
-
const _once =
|
|
168
|
+
const _once = once(function (this: any, ...args: any[]) {
|
|
125
169
|
offer(name, _once);
|
|
126
|
-
callback.apply(this,
|
|
127
|
-
})
|
|
170
|
+
callback.apply(this, args);
|
|
171
|
+
}) as EventCallback & { _callback?: EventCallback };
|
|
172
|
+
map[name] = _once;
|
|
128
173
|
_once._callback = callback;
|
|
129
174
|
}
|
|
130
175
|
return map;
|
|
131
176
|
}
|
|
132
177
|
|
|
133
178
|
/** Handles triggering the appropriate event callbacks. */
|
|
134
|
-
export function triggerApi(
|
|
179
|
+
export function triggerApi(
|
|
180
|
+
objEvents: EventHandlersMap | null,
|
|
181
|
+
name: string,
|
|
182
|
+
_callback: EventCallback | null,
|
|
183
|
+
args: any[]
|
|
184
|
+
): EventHandlersMap | null {
|
|
135
185
|
if (objEvents) {
|
|
136
186
|
const events = objEvents[name];
|
|
137
187
|
let allEvents = objEvents.all;
|
|
@@ -147,13 +197,13 @@ export function triggerApi(objEvents, name, callback, args) {
|
|
|
147
197
|
* triggering events. Tries to keep the usual cases speedy (most internal
|
|
148
198
|
* Backbone events have 3 arguments).
|
|
149
199
|
*/
|
|
150
|
-
function triggerEvents(events, args) {
|
|
151
|
-
let ev
|
|
152
|
-
|
|
153
|
-
const l = events.length
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
200
|
+
function triggerEvents(events: EventHandler[], args: any[]): void {
|
|
201
|
+
let ev: EventHandler;
|
|
202
|
+
let i = -1;
|
|
203
|
+
const l = events.length;
|
|
204
|
+
const a1 = args[0];
|
|
205
|
+
const a2 = args[1];
|
|
206
|
+
const a3 = args[2];
|
|
157
207
|
switch (args.length) {
|
|
158
208
|
case 0:
|
|
159
209
|
while (++i < l) (ev = events[i]).callback.call(ev.ctx);
|