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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Eugene Nesvetaev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # inSite subscriptions client side
2
+
3
+ ## Installation
4
+
5
+ ```sh
6
+ $ npm install insite-subscriptions-client
7
+ ```
8
+
9
+ Or
10
+
11
+ ```sh
12
+ $ yarn add insite-subscriptions-client
13
+ ```
@@ -0,0 +1,106 @@
1
+ //#if _SSR
2
+ // import { publications } from "insite-subscriptions-server";
3
+
4
+
5
+ // export const initialsMap = new Map();
6
+
7
+ // export const subscriptionContext = {};
8
+
9
+ // export async function Subscription(type, publicationName, args = [], handler) {
10
+ // const value = await publications.get(publicationName)?.fetch(subscriptionContext, ...args);
11
+
12
+ // subscriptionContext.initials.push(JSON.stringify(value));
13
+
14
+ // handler(value);
15
+
16
+ // }
17
+
18
+
19
+ //#else
20
+ let i, subscriptions, ws;
21
+
22
+ export class Subscription {
23
+ constructor(type, publicationName, args = [], handler, immediately = true) {
24
+
25
+ if (!ws)
26
+ throw new Error("Subscription is not binded to WS. Do Subscription.bindTo(ws) first");
27
+
28
+ this.i = i++;
29
+ this.type = type;
30
+ this.publicationName = publicationName;
31
+ this.args = args;
32
+ this.handler = handler;
33
+ this.immediately = immediately && !window.__initials;
34
+
35
+ subscriptions.set(this.i, this);
36
+
37
+ ws.on("open", this.start);
38
+
39
+ if (ws.isOpen)
40
+ this.start();
41
+
42
+ if (window.__initials) {
43
+ this.isActive = true;
44
+ this.handler(window.__initials.splice(0, 1)[0]);
45
+ } else
46
+ this.isActive = false;
47
+
48
+ }
49
+
50
+ start = () => {
51
+
52
+ ws.sendMessage("s-s"/* subscription subscribe */, this.type, this.publicationName, this.i, this.args, this.immediately);
53
+
54
+ this.immediately = true;
55
+
56
+ };
57
+
58
+ cancel() {
59
+
60
+ subscriptions.delete(this.i);
61
+
62
+ ws.off("open", this.start);
63
+
64
+ ws.sendMessage("s-u"/* subscription unsubscribe */, this.i);
65
+
66
+ }
67
+
68
+
69
+ static bindTo(_ws) {
70
+ ws = _ws;
71
+
72
+ i = 0;
73
+
74
+ subscriptions =
75
+ Subscription.subscriptions =
76
+ new Map();
77
+
78
+ ws.on("message:s-c"/* subscription changed */, (_i, data) => {
79
+ const subscription = subscriptions.get(_i);
80
+ if (subscription) {
81
+ subscription.isActive = true;
82
+ subscription.handler(data);
83
+ }
84
+
85
+ });
86
+
87
+ ws.on("close", () => {
88
+
89
+ for (const subscription of subscriptions.values())
90
+ subscription.isActive = false;
91
+
92
+ });
93
+
94
+ ws.on("server-change", () => {
95
+
96
+ for (const subscription of subscriptions.values())
97
+ subscription.handler(null);
98
+
99
+ });
100
+
101
+ }
102
+
103
+ }
104
+
105
+
106
+ //#endif
@@ -0,0 +1,241 @@
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
+ const ascSort = (a, b) => a > b ? 1 : a < b ? -1 : 0;
18
+ const descSort = (a, b) => a > b ? -1 : a < b ? 1 : 0;
19
+
20
+
21
+ export class SubscriptionArray extends Array {
22
+ constructor(updates, onUpdate) {
23
+ super();
24
+
25
+ this.handleUpdate = onUpdate;
26
+
27
+ if (Array.isArray(updates))
28
+ this.update(updates);
29
+
30
+ }
31
+
32
+ sortDirection = 0;
33
+
34
+ sortCompareFunction = null;
35
+
36
+ [getHandleUpdateSymbol]() {
37
+ return this.handleUpdate;
38
+ }
39
+
40
+ [setHandleUpdateSymbol](handleUpdate) {
41
+ return (this.handleUpdate = handleUpdate);
42
+ }
43
+
44
+ update(updates) {
45
+
46
+ const updated = [];
47
+ updated.deleted = [];
48
+ updated.added = [];
49
+
50
+ if (updates) {
51
+ let shouldSort = false;
52
+
53
+ for (const update of updates)
54
+ switch (update?.[0]) {
55
+ case "i"/* initial */: {
56
+ const [ , array, sort ] = update;
57
+
58
+ if (sort) {
59
+ this.sortDirection = sort;
60
+ this.sortCompareFunction = sort > 0 ? ascSort : descSort;
61
+ shouldSort = true;
62
+ } else {
63
+ this.sortDirection = 0;
64
+ this.sortCompareFunction = null;
65
+ }
66
+
67
+ this.length = 0;
68
+
69
+ for (const newItem of array) {
70
+ this.push(newItem);
71
+ updated.push(newItem);
72
+ updated.added.push(newItem);
73
+ }
74
+
75
+ break;
76
+ }
77
+
78
+ case "a"/* add */: {
79
+ const newItem = update[1];
80
+
81
+ if (!this.includes(newItem)) {
82
+ this.push(newItem);
83
+ if (this.sortDirection)
84
+ shouldSort = true;
85
+ updated.push(newItem);
86
+ updated.added.push(newItem);
87
+ }
88
+
89
+ break;
90
+ }
91
+
92
+ case "d"/* delete */: {
93
+ const itemToDelete = update[1];
94
+
95
+ if (removeOne(this, itemToDelete))
96
+ updated.deleted.push(itemToDelete);
97
+
98
+ break;
99
+ }
100
+
101
+ default:
102
+ this.clear();
103
+ }
104
+
105
+ if (shouldSort)
106
+ this.sort(this.sortCompareFunction);
107
+
108
+ } else
109
+ this.clear();
110
+
111
+ this.handleUpdate?.(this, updated, updates?.[0] && updates);
112
+
113
+ return this;
114
+ }
115
+
116
+ [updateSymbol] = this.update;
117
+
118
+ getAsUpdates() {
119
+ return (
120
+ (this.length || this.sortDirection) ?
121
+ [ [ "i"/* initial */, [ ...this ], this.sortDirection ] ] :
122
+ null
123
+ );
124
+ }
125
+
126
+ [getAsUpdatesSymbol] = this.getAsUpdates;
127
+
128
+ getAsInitialUpdated() {
129
+ return Object.assign([ ...this.sorted ], {
130
+ added: [ ...this.sorted ],
131
+ deleted: []
132
+ });
133
+ }
134
+
135
+ [getAsInitialUpdatedSymbol] = this.getAsInitialUpdated;
136
+
137
+ clear() {
138
+
139
+ this.length = 0;
140
+ this.sortDirection = 0;
141
+ this.sortCompareFunction = null;
142
+
143
+ }
144
+
145
+ [clearSymbol] = this.clear;
146
+
147
+ valueOf() {
148
+ return this;
149
+ }
150
+
151
+ get [Symbol.toStringTag]() {
152
+ return "SubscriptionArray";
153
+ }
154
+
155
+
156
+ static updateSymbol = updateSymbol;
157
+ static getHandleUpdateSymbol = getHandleUpdateSymbol;
158
+ static setHandleUpdateSymbol = setHandleUpdateSymbol;
159
+ static getAsUpdatesSymbol = getAsUpdatesSymbol;
160
+ static getAsInitialUpdatedSymbol = getAsInitialUpdatedSymbol;
161
+ static clearSymbol = clearSymbol;
162
+
163
+ }
164
+
165
+
166
+ SubscriptionArray.WithSubscription = class SubscriptionArrayWithSubscription extends SubscriptionArray {
167
+ constructor(publicationName, params, onUpdate, immediately = true) {
168
+ super(null, onUpdate);
169
+
170
+ this.publicationName = publicationName;
171
+ this.params = params;
172
+
173
+ if (immediately)
174
+ this.subscribe();
175
+
176
+ }
177
+
178
+ subscription = null;
179
+
180
+ subscribe() {
181
+
182
+ if (!this.subscription)
183
+ this.subscription = new Subscription("array", this.publicationName, this.params, updates => this.update(updates));
184
+
185
+ }
186
+
187
+ get [subscribeSymbol]() {
188
+ return this.subscribe;
189
+ }
190
+
191
+ unsubscribe() {
192
+
193
+ if (this.subscription) {
194
+ this.subscription.cancel();
195
+ this.subscription = null;
196
+ }
197
+
198
+ }
199
+
200
+ get [unsubscribeSymbol]() {
201
+ return this.unsubscribe;
202
+ }
203
+
204
+ renew(publicationName, params) {
205
+
206
+ if (this.subscription) {
207
+ this.unsubscribe();
208
+
209
+ if (publicationName)
210
+ if (Array.isArray(publicationName))
211
+ params = publicationName;
212
+ else
213
+ this.publicationName = publicationName;
214
+
215
+ if (params)
216
+ this.params = params;
217
+
218
+ this.subscribe();
219
+ }
220
+
221
+ }
222
+
223
+ get [renewSymbol]() {
224
+ return this.renew;
225
+ }
226
+
227
+ [getSubscriptionSymbol]() {
228
+ return this.subscription;
229
+ }
230
+
231
+ get [Symbol.toStringTag]() {
232
+ return "SubscriptionArrayWithSubscription";
233
+ }
234
+
235
+
236
+ static subscribeSymbol = subscribeSymbol;
237
+ static unsubscribeSymbol = unsubscribeSymbol;
238
+ static renewSymbol = renewSymbol;
239
+ static getSubscriptionSymbol = getSubscriptionSymbol;
240
+
241
+ };
@@ -0,0 +1,292 @@
1
+ import { debounce } from "@nesvet/n";
2
+ import EventEmitter from "eventemitter3";
3
+ import { privates } from "./common";
4
+ import { SubscriptionGroupItem } from "./SubscriptionGroupItem";
5
+
6
+
7
+ function parseDefinition(definition) {
8
+ if (Array.isArray(definition)) {
9
+ const args = definition.slice();
10
+
11
+ const parsed = {};
12
+
13
+ parsed.name = args.shift();
14
+ if (typeof args[0] == "object" && !Array.isArray(args[0])) {
15
+ parsed.value = args.shift();
16
+ parsed.type = parsed.value.toString().match(/Subscription(Object|Array|Map)/)[1];
17
+ if (parsed.type)
18
+ parsed.type = parsed.type.toLowerCase();
19
+ else
20
+ throw new Error("Unknown value type");
21
+ } else {
22
+ if (typeof args[0] == "string")
23
+ parsed.type = args.shift();
24
+ if (typeof args[0] == "string")
25
+ parsed.publicationName = args.shift();
26
+ if (Array.isArray(args[0]))
27
+ parsed.params = args.shift();
28
+ }
29
+ if (typeof args[0] == "function")
30
+ parsed.handle = args.shift();
31
+ if (typeof args[0] == "function")
32
+ parsed.onBeforeInit = args.shift();
33
+ if (typeof args[0] == "number")
34
+ parsed.debounceLimit = args.shift();
35
+ if (typeof args[0] == "boolean")
36
+ parsed.preventBind = args.shift();
37
+
38
+ return parsed;
39
+ }
40
+
41
+ return definition;
42
+ }
43
+
44
+ export class SubscriptionGroup extends EventEmitter {
45
+ constructor(definitions, options = {}) {
46
+ super();
47
+
48
+ const {
49
+ target,
50
+ debounce: debounceLimit = SubscriptionGroup.debounceLimit,
51
+ immediately
52
+ } = options;
53
+
54
+ this.#applyOptions({ target, debounceLimit });
55
+
56
+ this.#initPromise = new Promise(resolve => (this.#initResolve = resolve));
57
+
58
+ this.attach(definitions, false, immediately);
59
+
60
+ }
61
+
62
+ items = [];
63
+ values = [];
64
+
65
+ isLoaded = false;
66
+ isInited = false;
67
+
68
+ #loadPromise;
69
+ #initPromise;
70
+ #unloadPromise;
71
+ #loadResolve;
72
+ #initResolve;
73
+ #unloadResolve;
74
+
75
+ #debounceLimit = null;
76
+
77
+ #applyOptions({ target, debounceLimit }) {
78
+
79
+ if (this.target !== target) {
80
+ if (this.target)
81
+ for (const item of this.items)
82
+ if (item.isLoaded && !item.preventBind)
83
+ delete this.target[item.name];
84
+
85
+ this.target = target;
86
+
87
+ if (this.target)
88
+ for (const item of this.items)
89
+ if (item.isLoaded && !item.preventBind)
90
+ this.target[item.name] = item.value;
91
+ }
92
+
93
+ if (this.#debounceLimit !== debounceLimit) {
94
+ this.#debounceLimit = debounceLimit;
95
+ if (typeof this.#debounceLimit == "number") {
96
+ const emitUpdateDebounced = debounce(() => this.emit("update", this.values), this.#debounceLimit);
97
+ this.emitUpdate = () => emitUpdateDebounced();
98
+ } else
99
+ this.emitUpdate = () => this.emit("update", this.values);
100
+ }
101
+
102
+ }
103
+
104
+ attach(definitions, shouldReload = this.isLoaded, immediately = true) {
105
+ if (shouldReload)
106
+ this.isLoaded = false;
107
+
108
+ if (!this.#loadPromise || this.#loadPromise.isResolved)
109
+ this.#loadPromise = new Promise(resolve => (this.#loadResolve = resolve));
110
+
111
+ if (!this.#unloadPromise || this.#unloadPromise.isResolved)
112
+ this.#unloadPromise = new Promise(resolve => (this.#unloadResolve = resolve));
113
+
114
+ return Promise.all(definitions.map(definition =>
115
+ new SubscriptionGroupItem(parseDefinition(definition), this)
116
+ ).map(item => {
117
+
118
+ if (immediately)
119
+ item.subscribe();
120
+
121
+ return item.loaded();
122
+ }));
123
+ }
124
+
125
+ detach(names, shouldUpdate = true) {
126
+
127
+ for (const name of names)
128
+ this.items[name]?.detach();
129
+
130
+ if (shouldUpdate) {
131
+ this.#unload();
132
+ this.#load();
133
+ }
134
+
135
+ }
136
+
137
+ redefine(definitions) {
138
+
139
+ const itemsToDetach = new Map(this.items.map(item => [ item.name, item ]));
140
+ const definitionsToRedefine = [];
141
+ const definitionsToAttach = [];
142
+
143
+ for (let definition of definitions) {
144
+ definition = parseDefinition(definition);
145
+ if (itemsToDetach.delete(definition.name))
146
+ definitionsToRedefine.push(definition);
147
+ else
148
+ definitionsToAttach.push(definition);
149
+ }
150
+
151
+ this.isLoaded = false;
152
+
153
+ this.detach([ ...itemsToDetach.keys() ], false);
154
+
155
+ return Promise.all([
156
+ ...definitionsToRedefine.map(definition => {
157
+ const item = this.items[definition.name];
158
+ item.define(definition);
159
+
160
+ item.subscribe();
161
+
162
+ return item.loaded();
163
+ }),
164
+ ...definitionsToAttach.length ? [ this.attach(definitionsToAttach, false) ] : []
165
+ ]);
166
+ }
167
+
168
+ subscribe() {
169
+ return this.items.map(item => item.subscribe());
170
+ }
171
+
172
+ unsubscribe() {
173
+ return this.items.map(item => item.unsubscribe());
174
+ }
175
+
176
+ #load() {
177
+
178
+ if (!this.isLoaded && this.items.every(item => item.isLoaded)) {
179
+ if (this.target)
180
+ for (const item of this.items)
181
+ if (!item.preventBind)
182
+ this.target[item.name] = item.value;
183
+
184
+ this.isLoaded = true;
185
+
186
+ for (const item of this.items)
187
+ privates.itemLoad(item);
188
+
189
+ this.emit("load", this.values);
190
+
191
+ if (this.isInited)
192
+ if (this.items.length)
193
+ for (const item of this.items)
194
+ item.emitUpdate();
195
+ else
196
+ this.emitUpdate();
197
+
198
+
199
+ this.#loadResolve(this);
200
+ this.#loadPromise.isResolved = true;
201
+
202
+ if (this.#unloadPromise.isResolved)
203
+ this.#unloadPromise = new Promise(resolve => (this.#unloadResolve = resolve));
204
+ }
205
+
206
+ }
207
+
208
+ #init() {
209
+
210
+ if (!this.isInited && this.items.every(item => item.isInited)) {
211
+ this.isInited = true;
212
+
213
+ if (this.isLoaded)
214
+ if (this.items.length)
215
+ for (const item of this.items)
216
+ item.emitUpdate();
217
+ else
218
+ this.emitUpdate();
219
+
220
+
221
+ for (const item of this.items)
222
+ privates.itemInit(item);
223
+
224
+ this.emit("init", this.values);
225
+
226
+ this.#initResolve(this);
227
+ }
228
+
229
+ }
230
+
231
+ #unload() {
232
+
233
+ if (this.isLoaded && this.items.every(item => !item.isLoaded)) {
234
+ if (this.target)
235
+ for (const item of this.items)
236
+ if (!item.preventBind)
237
+ delete this.target[item.name];
238
+
239
+ this.isLoaded = false;
240
+
241
+ for (const item of this.items)
242
+ privates.itemUnload(item);
243
+
244
+ this.emit("unload", this.values);
245
+
246
+ if (this.items.length)
247
+ for (const item of this.items)
248
+ item.emitUpdate();
249
+ else
250
+ this.emitUpdate();
251
+
252
+ this.#unloadResolve(this);
253
+ this.#unloadPromise.isResolved = true;
254
+
255
+ if (this.#loadPromise.isResolved)
256
+ this.#loadPromise = new Promise(resolve => (this.#loadResolve = resolve));
257
+ }
258
+
259
+ }
260
+
261
+ loaded() {
262
+ return this.#loadPromise;
263
+ }
264
+
265
+ inited() {
266
+ return this.#initPromise;
267
+ }
268
+
269
+ unloaded() {
270
+ return this.#unloadPromise;
271
+ }
272
+
273
+ valueOf() {
274
+ return this.values;
275
+ }
276
+
277
+ [Symbol.iterator]() {
278
+ return this.values[Symbol.iterator]();
279
+ }
280
+
281
+
282
+ static debounceLimit = 8;
283
+
284
+ static {
285
+
286
+ privates.groupLoad = group => group.#load();
287
+ privates.groupInit = group => group.#init();
288
+ privates.groupUnload = group => group.#unload();
289
+
290
+ }
291
+
292
+ }