insite-subscriptions-client 1.0.294

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.
@@ -0,0 +1,300 @@
1
+ import { debounce, removeOne } from "@nesvet/n";
2
+ import EventEmitter from "eventemitter3";
3
+ import { SubscriptionArray } from "../SubscriptionArray";
4
+ import { SubscriptionMap } from "../SubscriptionMap";
5
+ import { SubscriptionObject } from "../SubscriptionObject";
6
+ import {
7
+ getAsInitialUpdatedSymbol,
8
+ getAsUpdatesSymbol,
9
+ getHandleUpdateSymbol,
10
+ renewSymbol,
11
+ setHandleUpdateSymbol,
12
+ subscribeSymbol,
13
+ unsubscribeSymbol
14
+ } from "../symbols";
15
+ import { privates } from "./common";
16
+
17
+
18
+ const SubscriptionValuesByType = {
19
+ object: SubscriptionObject,
20
+ array: SubscriptionArray,
21
+ map: SubscriptionMap
22
+ };
23
+
24
+
25
+ export class SubscriptionGroupItem extends EventEmitter {
26
+ constructor(definition, group) {
27
+ super();
28
+
29
+ this.name = definition.name;
30
+
31
+ this.define(definition, group);
32
+
33
+ }
34
+
35
+ isLoaded = false;
36
+ isInited = false;
37
+
38
+ #loadPromise;
39
+ #initPromise;
40
+ #unloadPromise;
41
+ #loadResolve;
42
+ #initResolve;
43
+ #unloadResolve;
44
+
45
+ define(definition, group = this.group) {
46
+
47
+ const {
48
+ value,
49
+ type = "object",
50
+ publicationName,
51
+ params,
52
+ handle,
53
+ onBeforeInit,
54
+ debounce: debounceLimit = SubscriptionGroupItem.debounceLimit,
55
+ preventBind
56
+ } = definition;
57
+
58
+ if (this.group) {
59
+ if (
60
+ (!value || this.value === value) &&
61
+ this.type === type &&
62
+ this.publicationName === publicationName &&
63
+ JSON.stringify(this.params) === JSON.stringify(params) &&
64
+ this.handle === handle &&
65
+ this.onBeforeInit === onBeforeInit
66
+ )
67
+ return this.#applyOptions({ debounceLimit, preventBind });
68
+
69
+ this.detach();
70
+ delete this.value;
71
+ delete this.publicationName;
72
+ delete this.params;
73
+ }
74
+
75
+ this.type = type;
76
+ this.handle = handle;
77
+ this.onBeforeInit = onBeforeInit;
78
+ if (value)
79
+ this.value = value;
80
+ else {
81
+ this.publicationName = publicationName ?? this.name;
82
+ this.params = params ?? [];
83
+ }
84
+
85
+ this.#applyOptions({ debounceLimit, preventBind });
86
+
87
+ this.#attachTo(group);
88
+
89
+ }
90
+
91
+ #applyOptions({ debounceLimit, preventBind }) {
92
+
93
+ const emitUpdate = updated => {
94
+ this.emit("update", this.value, updated);
95
+ this.group.emit(`update.${this.name}`, this.value, updated);
96
+ this.group.emitUpdate();
97
+
98
+ };
99
+
100
+ if (typeof debounceLimit == "number") {
101
+ let debouncedUpdated;
102
+ let group;
103
+
104
+ const emitUpdateDebounced = debounce(() => {
105
+
106
+ const isDetached = !this.group;
107
+
108
+ if (isDetached)
109
+ this.group = group;
110
+
111
+ emitUpdate(debouncedUpdated);
112
+
113
+ if (isDetached)
114
+ delete this.group;
115
+
116
+ debouncedUpdated = undefined;
117
+
118
+ }, debounceLimit);
119
+
120
+ this.emitUpdate = updated => {
121
+ if (updated !== undefined)
122
+ if (Array.isArray(updated))
123
+ if (debouncedUpdated)
124
+ for (const item of updated)
125
+ debouncedUpdated.push(item);
126
+ else
127
+ debouncedUpdated = [ ...updated ];
128
+ else if (typeof updated == "object")
129
+ if (debouncedUpdated)
130
+ Object.assign(debouncedUpdated, updated);
131
+ else
132
+ debouncedUpdated = { ...updated };
133
+ else
134
+ debouncedUpdated = updated;
135
+
136
+ ({ group } = this);
137
+
138
+ return emitUpdateDebounced();
139
+ };
140
+ } else
141
+ this.emitUpdate = emitUpdate;
142
+
143
+ if (this.preventBind === undefined)
144
+ delete this.preventBind;
145
+ else
146
+ this.preventBind = preventBind;
147
+
148
+ }
149
+
150
+ #attachTo(group) {
151
+ this.group = group;
152
+
153
+ this.group.items.push(this);
154
+ this.group.items[this.name] = this;
155
+
156
+ if (!this.#loadPromise || this.#loadPromise.isResolved)
157
+ this.#loadPromise = new Promise(resolve => (this.#loadResolve = resolve));
158
+
159
+ if (!this.#initPromise || this.#initPromise.isResolved)
160
+ this.#initPromise = new Promise(resolve => (this.#initResolve = resolve));
161
+
162
+ if (!this.#unloadPromise || this.#unloadPromise.isResolved)
163
+ this.#unloadPromise = new Promise(resolve => (this.#unloadResolve = resolve));
164
+
165
+ if (this.value)
166
+ this.value[setHandleUpdateSymbol](this.#handleSubscription);
167
+ else
168
+ this.value = new SubscriptionValuesByType[this.type].WithSubscription(this.publicationName, this.params, this.#handleSubscription, false);
169
+
170
+ this.group.values.push(this.value);
171
+ this.group.values[this.name] = this.value;
172
+
173
+ this.onBeforeInit?.call(this.group, this.value);
174
+
175
+ }
176
+
177
+ detach() {
178
+
179
+ this.value[unsubscribeSymbol]?.();
180
+
181
+ this.isLoaded = false;
182
+ this.#unload();
183
+
184
+ delete this.group.values[this.name];
185
+ removeOne(this.group.values, this.value);
186
+ delete this.group.items[this.name];
187
+ removeOne(this.group.items, this);
188
+
189
+ delete this.group;
190
+
191
+ }
192
+
193
+ #loadUpdated = null;
194
+
195
+ #handleSubscription = (_, updated, updates) => {
196
+ if (updates)
197
+ if (this.group.isLoaded) {
198
+ this.handle?.call(this.group, updated, this.group);
199
+ this.emitUpdate(updated);
200
+ } else {
201
+ this.#loadUpdated = updated;
202
+ this.isLoaded = true;
203
+ privates.groupLoad(this.group);
204
+ }
205
+ else if (this.isLoaded) {
206
+ this.isLoaded = false;
207
+ privates.groupUnload(this.group);
208
+ }
209
+
210
+ if (!this.isInited) {
211
+ this.isInited = true;
212
+ privates.groupInit(this.group);
213
+ }
214
+
215
+ };
216
+
217
+ #load() {
218
+
219
+ if (this.#loadUpdated) {
220
+ this.handle?.call(this.group, this.#loadUpdated, this.group);
221
+ this.#loadUpdated = null;
222
+
223
+ this.emit("load", this.value);
224
+
225
+ this.#loadResolve(this);
226
+ this.#loadPromise.isResolved = true;
227
+
228
+ if (this.#unloadPromise.isResolved)
229
+ this.#unloadPromise = new Promise(resolve => (this.#unloadResolve = resolve));
230
+ }
231
+
232
+ }
233
+
234
+ #init() {
235
+
236
+ this.emit("init", this.value);
237
+
238
+ this.#initResolve(this);
239
+ this.#initPromise.isResolved = true;
240
+
241
+ }
242
+
243
+ #unload() {
244
+
245
+ this.handle?.call(this.group, null, this.group);
246
+
247
+ this.emit("unload", this.value);
248
+
249
+ this.#unloadResolve(this);
250
+ this.#unloadPromise.isResolved = true;
251
+
252
+ if (this.#loadPromise.isResolved)
253
+ this.#loadPromise = new Promise(resolve => (this.#loadResolve = resolve));
254
+
255
+ }
256
+
257
+ loaded() {
258
+ return this.#loadPromise;
259
+ }
260
+
261
+ inited() {
262
+ return this.#initPromise;
263
+ }
264
+
265
+ unloaded() {
266
+ return this.#unloadPromise;
267
+ }
268
+
269
+ subscribe() {
270
+ return (
271
+ this.value[subscribeSymbol] ?
272
+ this.value[subscribeSymbol]() :
273
+ this.value[getHandleUpdateSymbol]()(this.value, this.value[getAsInitialUpdatedSymbol](), this.value[getAsUpdatesSymbol]())
274
+ );
275
+ }
276
+
277
+ unsubscribe() {
278
+ return this.value[unsubscribeSymbol]?.();
279
+ }
280
+
281
+ renew(publicationName, params) {
282
+ return this.value[renewSymbol]?.(publicationName, params);
283
+ }
284
+
285
+ valueOf() {
286
+ return this.value;
287
+ }
288
+
289
+
290
+ static debounceLimit = 4;
291
+
292
+ static {
293
+
294
+ privates.itemLoad = item => item.#load();
295
+ privates.itemInit = item => item.#init();
296
+ privates.itemUnload = item => item.#unload();
297
+
298
+ }
299
+
300
+ }
@@ -0,0 +1 @@
1
+ export const privates = {};
@@ -0,0 +1 @@
1
+ export * from "./SubscriptionGroup";
@@ -0,0 +1,393 @@
1
+ import { removeOne } from "@nesvet/n";
2
+ import { Subscription } from "./Subscription";
3
+ import {
4
+ clearSymbol,
5
+ getAsInitialUpdatedSymbol,
6
+ getAsUpdatesSymbol,
7
+ getHandleUpdateSymbol,
8
+ getSubscriptionSymbol,
9
+ renewSymbol,
10
+ setHandleUpdateSymbol,
11
+ subscribeSymbol,
12
+ unsubscribeSymbol,
13
+ updateSymbol
14
+ } from "./symbols";
15
+
16
+
17
+ function makeSort(sort) {
18
+
19
+ const functionName = `function${performance.now().toString(36).replace(/\./g, "")}`;
20
+
21
+ const tempScript = document.createElement("script");
22
+ tempScript.textContent = `window.${functionName} = function sort(a, b) { return ${`${Object.entries(sort).map(([ key, value ]) => value > 0 ? `a.${key} > b.${key} ? 1 : a.${key} < b.${key} ? -1 :` : `a.${key} < b.${key} ? 1 : a.${key} > b.${key} ? -1 :`).concat("0").join(" ")};`} }`;
23
+ document.head.append(tempScript);
24
+ tempScript.remove();
25
+
26
+ const sortFunction = window[functionName];
27
+
28
+ delete window[functionName];
29
+
30
+ return sortFunction;
31
+ }
32
+
33
+
34
+ export class SubscriptionMap extends Map {
35
+ constructor(updates, onUpdate) {
36
+ super();
37
+
38
+ if (onUpdate)
39
+ this.handleUpdate = onUpdate;
40
+
41
+ if (updates)
42
+ this.update(updates);
43
+
44
+ }
45
+
46
+ #updates = new Map();
47
+
48
+ sortList = null;
49
+
50
+ sortCompareFunction = null;
51
+
52
+ #fieldsToSort = [];
53
+
54
+ sorted = [];
55
+
56
+ sort() {
57
+
58
+ this.sorted.sort(this.sortCompareFunction);
59
+
60
+ }
61
+
62
+ [getHandleUpdateSymbol]() {
63
+ return this.handleUpdate;
64
+ }
65
+
66
+ [setHandleUpdateSymbol](handleUpdate) {
67
+ return (this.handleUpdate = handleUpdate);
68
+ }
69
+
70
+ #Item;
71
+
72
+ set Item(Item) {
73
+ this.#Item = Item;
74
+
75
+ if (this.size)
76
+ for (const [ _id, item ] of this)
77
+ this.set(_id, new Item(this, item));
78
+
79
+ }
80
+
81
+ get Item() {
82
+ return this.#Item;
83
+ }
84
+
85
+ update(updates) {
86
+
87
+ const updated = [];
88
+ updated.added = [];
89
+ updated.deleted = [];
90
+
91
+ if (updates) {
92
+ let shouldSort = false;
93
+
94
+ const Item = this.#Item;
95
+ const prevUpdates = this.#updates;
96
+
97
+ for (const update of updates)
98
+ switch (update?.[0]) {
99
+ case "i"/* initial */: {
100
+ const [ , array, sort ] = update;
101
+
102
+ if (sort) {
103
+ this.sortList = sort;
104
+ this.sortCompareFunction = makeSort(sort);
105
+ this.#fieldsToSort = [ ...new Set(Object.keys(sort).map(key => key.replace(/\..+/, ""))) ];
106
+ shouldSort = true;
107
+ } else {
108
+ this.sortList = null;
109
+ this.sortCompareFunction = null;
110
+ this.#fieldsToSort = [];
111
+ }
112
+
113
+ if (this.size) {
114
+ const updatesMap = new Map();
115
+ for (const itemUpdates of array)
116
+ updatesMap.set(itemUpdates._id, itemUpdates);
117
+
118
+ for (const [ _id, item ] of this) {
119
+ const itemUpdates = updatesMap.get(_id);
120
+ if (itemUpdates) {
121
+ const prevItemUpdates = prevUpdates.get(_id);
122
+
123
+ if (Item)
124
+ item.update(itemUpdates);
125
+ else {
126
+ for (const key in prevItemUpdates)
127
+ delete item[key];
128
+
129
+ Object.assign(item, itemUpdates);
130
+ }
131
+
132
+ prevUpdates.set(_id, itemUpdates);
133
+ updated.push(item);
134
+ updatesMap.delete(_id);
135
+ } else {
136
+ if (Item)
137
+ item.delete();
138
+ else
139
+ this.delete(_id);
140
+ prevUpdates.delete(_id);
141
+ updated.deleted.push(_id);
142
+ }
143
+ }
144
+
145
+ for (const [ _id, itemUpdates ] of updatesMap) {
146
+ const item = Item ? new Item(this, itemUpdates) : { ...itemUpdates };
147
+ this.set(_id, item);
148
+ prevUpdates.set(_id, itemUpdates);
149
+ updated.push(item);
150
+ updated.added.push(item);
151
+ }
152
+
153
+ if (this.sortList)
154
+ this.sorted = array.map(newItem => this.get(newItem._id));
155
+
156
+ } else
157
+ for (const itemUpdates of array) {
158
+ const item = Item ? new Item(this, itemUpdates) : { ...itemUpdates };
159
+ this.set(item._id, item);
160
+ this.sorted.push(item);
161
+ prevUpdates.set(item._id, itemUpdates);
162
+ updated.push(item);
163
+ updated.added.push(item);
164
+ }
165
+
166
+ break;
167
+ }
168
+
169
+ case "c"/* create */:
170
+ case "u"/* update */: {
171
+ let itemUpdates = update[1];
172
+ let item = this.get(itemUpdates._id);
173
+
174
+ if (item) {
175
+ const isAtomic = update[2] === true;
176
+ const updatedFields = isAtomic ? Object.keys(itemUpdates) : update[2];
177
+ const prevItemUpdates = prevUpdates.get(item._id);
178
+
179
+ if (Item)
180
+ item.update(itemUpdates);
181
+ else {
182
+ if (!isAtomic)
183
+ for (const key in prevItemUpdates)
184
+ delete item[key];
185
+
186
+ Object.assign(item, itemUpdates);
187
+ }
188
+
189
+ if (isAtomic)
190
+ itemUpdates = Object.assign(prevItemUpdates, itemUpdates);
191
+
192
+ if (this.sortList && !shouldSort && (!updatedFields || updatedFields.some(fieldName => this.#fieldsToSort.includes(fieldName))))
193
+ shouldSort = true;
194
+ } else {
195
+ item = Item ? new Item(this, itemUpdates) : { ...itemUpdates };
196
+ this.set(item._id, item);
197
+ this.sorted.push(item);
198
+ if (this.sortList)
199
+ shouldSort = true;
200
+ updated.added.push(item);
201
+ }
202
+
203
+ prevUpdates.set(item._id, itemUpdates);
204
+ updated.push(item);
205
+
206
+ break;
207
+ }
208
+
209
+ case "d"/* delete */: {
210
+ const _id = update[1];
211
+ const itemToDelete = this.get(_id);
212
+
213
+ if (itemToDelete) {
214
+ if (Item)
215
+ itemToDelete.delete();
216
+ else
217
+ this.delete(_id);
218
+ prevUpdates.delete(_id);
219
+ removeOne(this.sorted, itemToDelete);
220
+ updated.deleted.push(_id);
221
+ }
222
+
223
+ break;
224
+ }
225
+
226
+ default:
227
+ this.clear();
228
+ }
229
+
230
+ if (shouldSort)
231
+ this.sort();
232
+
233
+ } else
234
+ this.clear();
235
+
236
+ this.handleUpdate?.(this, updated, updates?.[0] && updates);
237
+
238
+ return this;
239
+ }
240
+
241
+ [updateSymbol] = this.update;
242
+
243
+ getAsUpdates() {
244
+ return (
245
+ (this.size || this.sortList) ?
246
+ [ [ "i"/* initial */, [ ...this.#updates.values() ], this.sortList ] ] :
247
+ null
248
+ );
249
+ }
250
+
251
+ [getAsUpdatesSymbol] = this.getAsUpdates;
252
+
253
+ getAsInitialUpdated() {
254
+ return Object.assign([ ...this.sorted ], {
255
+ added: [ ...this.sorted ],
256
+ deleted: []
257
+ });
258
+ }
259
+
260
+ [getAsInitialUpdatedSymbol] = this.getAsInitialUpdated;
261
+
262
+ clear() {
263
+
264
+ super.clear();
265
+ this.#updates.clear();
266
+ this.sorted = [];
267
+ this.sortList = null;
268
+ this.sortCompareFunction = null;
269
+ this.#fieldsToSort = [];
270
+
271
+ }
272
+
273
+ [clearSymbol] = this.clear;
274
+
275
+ valueOf() {
276
+ return this.sorted;
277
+ }
278
+
279
+ get [Symbol.toStringTag]() {
280
+ return "SubscriptionMap";
281
+ }
282
+
283
+
284
+ static updateSymbol = updateSymbol;
285
+ static getHandleUpdateSymbol = getHandleUpdateSymbol;
286
+ static setHandleUpdateSymbol = setHandleUpdateSymbol;
287
+ static getAsUpdatesSymbol = getAsUpdatesSymbol;
288
+ static getAsInitialUpdatedSymbol = getAsInitialUpdatedSymbol;
289
+ static clearSymbol = clearSymbol;
290
+
291
+ }
292
+
293
+
294
+ SubscriptionMap.WithSubscription = class SubscriptionMapWithSubscription extends SubscriptionMap {
295
+ constructor(publicationName, params, onUpdate, immediately = true) {
296
+ super(null, onUpdate);
297
+
298
+ this.publicationName = publicationName;
299
+ this.params = params;
300
+
301
+ if (immediately)
302
+ this.subscribe();
303
+
304
+ }
305
+
306
+ subscription = null;
307
+
308
+ subscribe() {
309
+
310
+ if (!this.subscription)
311
+ this.subscription = new Subscription("map", this.publicationName, this.params, updates => this.update(updates));
312
+
313
+ }
314
+
315
+ get [subscribeSymbol]() {
316
+ return this.subscribe;
317
+ }
318
+
319
+ unsubscribe() {
320
+
321
+ if (this.subscription) {
322
+ this.subscription.cancel();
323
+ this.subscription = null;
324
+ }
325
+
326
+ }
327
+
328
+ get [unsubscribeSymbol]() {
329
+ return this.unsubscribe;
330
+ }
331
+
332
+ renew(publicationName, params) {
333
+
334
+ if (this.subscription) {
335
+ this.unsubscribe();
336
+
337
+ if (publicationName)
338
+ if (Array.isArray(publicationName))
339
+ params = publicationName;
340
+ else
341
+ this.publicationName = publicationName;
342
+
343
+ if (params)
344
+ this.params = params;
345
+
346
+ this.subscribe();
347
+ }
348
+
349
+ }
350
+
351
+ get [renewSymbol]() {
352
+ return this.renew;
353
+ }
354
+
355
+ [getSubscriptionSymbol]() {
356
+ return this.subscription;
357
+ }
358
+
359
+ get [Symbol.toStringTag]() {
360
+ return "SubscriptionMapWithSubscription";
361
+ }
362
+
363
+
364
+ static subscribeSymbol = subscribeSymbol;
365
+ static unsubscribeSymbol = unsubscribeSymbol;
366
+ static renewSymbol = renewSymbol;
367
+ static getSubscriptionSymbol = getSubscriptionSymbol;
368
+
369
+ };
370
+
371
+
372
+ SubscriptionMap.Item = class SubscriptionMapItem {
373
+ constructor(map, updates) {
374
+ this.#map = map;
375
+
376
+ this.update(updates);
377
+
378
+ }
379
+
380
+ #map;
381
+
382
+ update(updates) {
383
+ Object.assign(this, updates);
384
+
385
+ }
386
+
387
+ delete() {
388
+
389
+ this.#map.delete(this._id);
390
+
391
+ }
392
+
393
+ };