@cocreate/elements 1.43.2 → 1.44.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/package.json +10 -11
- package/src/crudHandler.js +282 -0
- package/src/dndHandler.js +154 -0
- package/src/getDataHandler.js +417 -0
- package/src/index.js +11 -1455
- package/src/initHandler.js +295 -0
- package/src/mapRegistry.js +6 -0
- package/src/setDataHandler.js +441 -0
- package/src/subscribeHandler.js +114 -0
- /package/src/{form.js → formHandler.js} +0 -0
- /package/src/{fetchSrc.js → srcHandler.js} +0 -0
- /package/src/{value.js → valueQuery.js} +0 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import {
|
|
2
|
+
dotNotationToObject,
|
|
3
|
+
queryData,
|
|
4
|
+
sortData,
|
|
5
|
+
getAttributes,
|
|
6
|
+
checkValue,
|
|
7
|
+
getValueFromObject
|
|
8
|
+
} from "@cocreate/utils";
|
|
9
|
+
import CRUD from "@cocreate/crud-client";
|
|
10
|
+
import filter from "@cocreate/filter";
|
|
11
|
+
import { elements, forms, keys } from "./mapRegistry.js"
|
|
12
|
+
|
|
13
|
+
export function setDataType(element, data, action) {
|
|
14
|
+
// TODO: if an array name is updated, the attibute array="" needs to be updated.
|
|
15
|
+
|
|
16
|
+
if (!data) return;
|
|
17
|
+
|
|
18
|
+
let form;
|
|
19
|
+
if (element.tagName === "FORM") {
|
|
20
|
+
form = element;
|
|
21
|
+
} else if (element.parentElement){
|
|
22
|
+
form = element.parentElement.closest("form");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!form) {
|
|
26
|
+
if (data.type === "object") {
|
|
27
|
+
if (data.object?.[0]?._id) {
|
|
28
|
+
element.setAttribute("object", data.object[0]._id);
|
|
29
|
+
}
|
|
30
|
+
} else {
|
|
31
|
+
element.setAttribute(data.type, data[data.type].name);
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
let formObject = forms.get(form);
|
|
35
|
+
if (form.getAttribute("object") === "pending")
|
|
36
|
+
form.setAttribute("object", data.object[0]._id);
|
|
37
|
+
|
|
38
|
+
let elements = formObject.types.get(data.type);
|
|
39
|
+
|
|
40
|
+
for (let [el, elData] of elements.entries()) {
|
|
41
|
+
if (data.type === "object") {
|
|
42
|
+
if (!elData.object || elData.object === "pending") {
|
|
43
|
+
elData.object = data.object[0]._id;
|
|
44
|
+
el.setAttribute("object", data.object[0]._id);
|
|
45
|
+
}
|
|
46
|
+
} else if (!elData[data.type]) {
|
|
47
|
+
elData[data.type] = data[data.type].name;
|
|
48
|
+
el.setAttribute(data.type, data[data.type].name);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
setData(Array.from(elements.keys()), data);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (action) {
|
|
55
|
+
action.element.dispatchEvent(
|
|
56
|
+
new CustomEvent("saved", {
|
|
57
|
+
detail: data
|
|
58
|
+
})
|
|
59
|
+
);
|
|
60
|
+
} else {
|
|
61
|
+
document.dispatchEvent(
|
|
62
|
+
new CustomEvent("saved", {
|
|
63
|
+
detail: data
|
|
64
|
+
})
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function get$in(query, mergedValues = []) {
|
|
70
|
+
for (let key of Object.keys(query)) {
|
|
71
|
+
if (key === "$and" || key === "$or" || key === "$nor") {
|
|
72
|
+
for (let i = 0; i < query[key].length; i++) {
|
|
73
|
+
sort$in(query[key][i], mergedValues);
|
|
74
|
+
}
|
|
75
|
+
} else if (
|
|
76
|
+
typeof query[key] === "object" &&
|
|
77
|
+
!Array.isArray(query[key])
|
|
78
|
+
) {
|
|
79
|
+
if (query[key].$in)
|
|
80
|
+
mergedValues = [...mergedValues, ...query[key].$in];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return mergedValues;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function sort$in(data, query) {
|
|
87
|
+
let mergedValues = get$in(query);
|
|
88
|
+
|
|
89
|
+
if (mergedValues.length) {
|
|
90
|
+
const sorted = mergedValues
|
|
91
|
+
.map((name) => data.find((career) => career.name === name))
|
|
92
|
+
.filter((career) => career !== undefined);
|
|
93
|
+
return sorted;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function setData(element, data) {
|
|
98
|
+
if (!element) {
|
|
99
|
+
element = getDataElements(data);
|
|
100
|
+
if (!element.length) return;
|
|
101
|
+
}
|
|
102
|
+
if (!(element instanceof HTMLCollection) && !Array.isArray(element))
|
|
103
|
+
element = [element];
|
|
104
|
+
|
|
105
|
+
let type = data.type;
|
|
106
|
+
if (!type && data.method) {
|
|
107
|
+
type = data.method.split(".")[0];
|
|
108
|
+
} else if (type == "key") type = "object";
|
|
109
|
+
|
|
110
|
+
if (data.$filter && data.$filter.query) {
|
|
111
|
+
let sortedData = sort$in(data[type], data.$filter.query);
|
|
112
|
+
if (sortedData && sortedData.length) data[type] = sortedData;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
for (let el of element) {
|
|
116
|
+
// if rendered in server side skip
|
|
117
|
+
if (el.hasAttribute("rendered")) return el.removeAttribute("rendered");
|
|
118
|
+
if (el.hasAttribute("render-json")) {
|
|
119
|
+
console.log("render-json", "");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let reference = el.getAttribute("reference");
|
|
123
|
+
let action = el.getAttribute("actions");
|
|
124
|
+
if (action && ["database", "array", "object", "key"].includes(action))
|
|
125
|
+
continue;
|
|
126
|
+
|
|
127
|
+
if (data.array.includes("aiConversations")) {
|
|
128
|
+
console.log("testing ai conversations");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const { key, isRead, isUpdate, isListen, isCrdt } = getAttributes(el);
|
|
132
|
+
let elementType = el.getAttribute("type");
|
|
133
|
+
if (elementType !== "file" && (el.getFilter || el.renderValue))
|
|
134
|
+
await filterData(el, data, type, key);
|
|
135
|
+
else {
|
|
136
|
+
if (!data[type] || !data[type].length) {
|
|
137
|
+
if (el.hasAttribute("value-dispatch")) el.setValue("");
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (key && checkValue(key)) {
|
|
142
|
+
let value;
|
|
143
|
+
if (key.includes("$length")) {
|
|
144
|
+
value = CRUD.getValueFromObject(
|
|
145
|
+
data[type][0],
|
|
146
|
+
key.replace(/\.\$length$/, "")
|
|
147
|
+
);
|
|
148
|
+
if (value) value = value.length;
|
|
149
|
+
else value = 0;
|
|
150
|
+
} else {
|
|
151
|
+
let $update = data[type][0].$update;
|
|
152
|
+
if ($update) {
|
|
153
|
+
delete data[type][0].$update;
|
|
154
|
+
data[type][0] = { ...data[type][0], ...$update };
|
|
155
|
+
} else if (key === "$data") {
|
|
156
|
+
value = data;
|
|
157
|
+
} else if (key && key.startsWith("$data")) {
|
|
158
|
+
value = CRUD.getValueFromObject(data, key.slice(1));
|
|
159
|
+
} else if (key === `$${type}`) {
|
|
160
|
+
value = data[type];
|
|
161
|
+
} else if (key && key.startsWith(`$${type}`)) {
|
|
162
|
+
value = CRUD.getValueFromObject(data, key.slice(1));
|
|
163
|
+
} else if (key === "{}") {
|
|
164
|
+
value = data[type][0];
|
|
165
|
+
if (reference === "false") {
|
|
166
|
+
delete data[type][0].$storage;
|
|
167
|
+
delete data[type][0].$database;
|
|
168
|
+
delete data[type][0].$array;
|
|
169
|
+
}
|
|
170
|
+
} else {
|
|
171
|
+
value = CRUD.getValueFromObject(data[type][0], key);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (!data[type].length) continue;
|
|
176
|
+
if (isRead === "false" || isCrdt === "true") continue;
|
|
177
|
+
if (
|
|
178
|
+
isUpdate === "false" ||
|
|
179
|
+
(isListen === "false" && !data.method.endsWith(".read"))
|
|
180
|
+
)
|
|
181
|
+
continue;
|
|
182
|
+
|
|
183
|
+
// TODO: object.update data returned from server will not include $operator
|
|
184
|
+
el.setValue(value);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function filterData(element, data, type, key) {
|
|
191
|
+
let operator = "",
|
|
192
|
+
value;
|
|
193
|
+
|
|
194
|
+
if (key && !key.includes("$length") && key !== "{}") {
|
|
195
|
+
if (!data || !type) return;
|
|
196
|
+
|
|
197
|
+
if (key.startsWith("$")) {
|
|
198
|
+
operator = key.split(".")[0] || "";
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let property = key;
|
|
202
|
+
if (operator) {
|
|
203
|
+
property = property.replace(operator + ".", "");
|
|
204
|
+
if (operator === "$sum") {
|
|
205
|
+
value = 0;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (Array.isArray(data[type])) {
|
|
209
|
+
let filteredItems = [],
|
|
210
|
+
isObject;
|
|
211
|
+
for (let doc of data[type]) {
|
|
212
|
+
if (!doc.$storage) continue;
|
|
213
|
+
// TODO: should have been handled by getDataElements()
|
|
214
|
+
if (type === "object") {
|
|
215
|
+
let _id = element.getAttribute("object");
|
|
216
|
+
if (_id && doc._id !== _id) continue;
|
|
217
|
+
let $update = doc.$update;
|
|
218
|
+
if ($update) {
|
|
219
|
+
delete doc.$update;
|
|
220
|
+
doc = { ...doc, ...$update };
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
let docValue = getValueFromObject(doc, property);
|
|
224
|
+
if (docValue) {
|
|
225
|
+
if (operator === "$sum" && typeof docValue === "number") {
|
|
226
|
+
if (typeof value !== "number") value = 0;
|
|
227
|
+
value += docValue || 0;
|
|
228
|
+
} else if (Array.isArray(docValue)) {
|
|
229
|
+
filteredItems.push(...docValue);
|
|
230
|
+
} else {
|
|
231
|
+
isObject = true;
|
|
232
|
+
filteredItems.push(docValue);
|
|
233
|
+
}
|
|
234
|
+
} else continue;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (isObject && filteredItems.length === 1) {
|
|
238
|
+
filteredItems = filteredItems[0];
|
|
239
|
+
}
|
|
240
|
+
if (!operator) data = filteredItems;
|
|
241
|
+
} else {
|
|
242
|
+
data = { [property]: data[type][property] };
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
let isRendered = element.querySelector("[render-clone]");
|
|
247
|
+
if (operator) {
|
|
248
|
+
element.setValue(value);
|
|
249
|
+
} else if (
|
|
250
|
+
element.renderValue &&
|
|
251
|
+
data.method &&
|
|
252
|
+
data.method.endsWith(".read") &&
|
|
253
|
+
data.$filter &&
|
|
254
|
+
(data.$filter.overwrite || !isRendered)
|
|
255
|
+
) {
|
|
256
|
+
await element.renderValue(data);
|
|
257
|
+
} else if (
|
|
258
|
+
(data.$filter && data.$filter.loadmore) ||
|
|
259
|
+
(data.method && data.method.endsWith(".read") && isRendered)
|
|
260
|
+
) {
|
|
261
|
+
await loadMore(element, data, type);
|
|
262
|
+
} else if (
|
|
263
|
+
element.getFilter &&
|
|
264
|
+
data.method &&
|
|
265
|
+
!data.method.endsWith(".read")
|
|
266
|
+
) {
|
|
267
|
+
await checkFilters(element, data, type);
|
|
268
|
+
} else if (element.renderValue) {
|
|
269
|
+
if (key)
|
|
270
|
+
data = {[key]: data}
|
|
271
|
+
await element.renderValue(data);
|
|
272
|
+
} else if (key === "$length") {
|
|
273
|
+
element.setValue(data[type].length);
|
|
274
|
+
} else if (key && key.includes("$length")) {
|
|
275
|
+
let value = CRUD.getValueFromObject(
|
|
276
|
+
data[type][0],
|
|
277
|
+
key.replace(/\.\$length$/, "")
|
|
278
|
+
);
|
|
279
|
+
element.setValue(value.length);
|
|
280
|
+
} else if (key === "$data") {
|
|
281
|
+
value = data;
|
|
282
|
+
} else if (key && key.startsWith("$data")) {
|
|
283
|
+
value = CRUD.getValueFromObject(data, key.slice(1));
|
|
284
|
+
} else if (key === `$${type}`) {
|
|
285
|
+
value = data[type];
|
|
286
|
+
} else if (key && key.startsWith(`$${type}`)) {
|
|
287
|
+
value = CRUD.getValueFromObject(data, key.slice(1));
|
|
288
|
+
} else if (key === "{}") {
|
|
289
|
+
value = data[type][0];
|
|
290
|
+
} else if (data) element.setValue(data);
|
|
291
|
+
|
|
292
|
+
if (data.$filter) {
|
|
293
|
+
let filterElement = filter.filters.get(element);
|
|
294
|
+
if (filterElement) filterElement.index = data.$filter.index;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (!data[type] || !Array.isArray(data[type]) || !data[type].length) return;
|
|
298
|
+
|
|
299
|
+
const evt = new CustomEvent("fetchedData", { bubbles: true });
|
|
300
|
+
element.dispatchEvent(evt);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function loadMore(element, data, type, sort) {
|
|
304
|
+
let clonedData = { ...data };
|
|
305
|
+
let renderedData = await element.getData();
|
|
306
|
+
if (!renderedData || !renderedData[type]) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
for (let i = 0; i < clonedData[type].length; i++) {
|
|
310
|
+
let index;
|
|
311
|
+
if (type === "object") {
|
|
312
|
+
index = renderedData[type].findIndex(
|
|
313
|
+
(obj) => obj._id === clonedData[type][i]._id
|
|
314
|
+
);
|
|
315
|
+
} else {
|
|
316
|
+
index = renderedData[type].findIndex(
|
|
317
|
+
(obj) => obj.name === clonedData[type][i].name
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (index >= 0) {
|
|
322
|
+
clonedData[type].splice(i, 1);
|
|
323
|
+
i--; // Adjust the index to account for the removed item
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (clonedData[type].length > 0) {
|
|
327
|
+
await element.renderValue(clonedData);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async function checkFilters(element, data, type) {
|
|
332
|
+
let localData;
|
|
333
|
+
if (!element.getData) {
|
|
334
|
+
// TODO: fix: getObject as this is related to render or form, but filter could exist on an input which does not have getObject
|
|
335
|
+
// TODO: generate an object of current element key: value to use with filter. because filter is used object is not defined
|
|
336
|
+
localData = getObject(element);
|
|
337
|
+
if (Array.isArray(localData[type])) localData = localData[type][0];
|
|
338
|
+
} else localData = await element.getData();
|
|
339
|
+
|
|
340
|
+
if (!localData) return;
|
|
341
|
+
|
|
342
|
+
let newData;
|
|
343
|
+
if (type) {
|
|
344
|
+
localData = localData[type];
|
|
345
|
+
newData = data[type];
|
|
346
|
+
} else newData = data;
|
|
347
|
+
|
|
348
|
+
let filter = await element.getFilter();
|
|
349
|
+
if (filter && filter.query) {
|
|
350
|
+
newData = newData.filter(item => queryData(item, filter.query));
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (!newData || !newData.length) return;
|
|
354
|
+
|
|
355
|
+
if (Array.isArray(localData)) {
|
|
356
|
+
if (Array.isArray(newData)) {
|
|
357
|
+
for (let i = 0; i < newData.length; i++) {
|
|
358
|
+
checkIndex(element, data, localData, newData[i], type, filter);
|
|
359
|
+
}
|
|
360
|
+
} else {
|
|
361
|
+
checkIndex(element, data, localData, newData, type, filter);
|
|
362
|
+
}
|
|
363
|
+
} else {
|
|
364
|
+
let primaryKey;
|
|
365
|
+
if (type === "object") {
|
|
366
|
+
primaryKey = "_id";
|
|
367
|
+
} else {
|
|
368
|
+
primaryKey = "name";
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (Array.isArray(newData)) {
|
|
372
|
+
for (let i = 0; i < newData.length; i++) {
|
|
373
|
+
if (
|
|
374
|
+
typeof localData === "string" &&
|
|
375
|
+
localData === newData[i][primaryKey]
|
|
376
|
+
) {
|
|
377
|
+
localData = newData[i];
|
|
378
|
+
} else if (localData[primaryKey] === newData[i][primaryKey]) {
|
|
379
|
+
localData = dotNotationToObject(newData[i], localData);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
} else if (typeof localData === "string" && localData === newData[primaryKey]) {
|
|
383
|
+
localData = newData;
|
|
384
|
+
} else if (localData[primaryKey] === newData[primaryKey]) {
|
|
385
|
+
localData = dotNotationToObject(newData, localData);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (element.renderValue) element.renderValue(data);
|
|
389
|
+
// render({ element, data, key: type });
|
|
390
|
+
else if (data) element.setValue(localData);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function checkIndex(element, data, localData, newData, type, filter) {
|
|
395
|
+
let index;
|
|
396
|
+
if (type === "object") {
|
|
397
|
+
index = localData.findIndex((obj) => obj._id === newData._id);
|
|
398
|
+
} else {
|
|
399
|
+
index = localData.findIndex((obj) => obj.name === newData.name);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (!data.$filter) data.$filter = {};
|
|
403
|
+
|
|
404
|
+
if (data.method.endsWith(".delete")) {
|
|
405
|
+
if (index === -1) return;
|
|
406
|
+
data.$filter.remove = true;
|
|
407
|
+
localData.splice(index, 1);
|
|
408
|
+
} else {
|
|
409
|
+
if (data.method.endsWith(".create")) {
|
|
410
|
+
if (index === -1) {
|
|
411
|
+
data.$filter.create = true;
|
|
412
|
+
if (filter && filter.sort) newData.isNewData = true;
|
|
413
|
+
else index = localData.length;
|
|
414
|
+
localData.push(newData);
|
|
415
|
+
}
|
|
416
|
+
} else {
|
|
417
|
+
if (index === -1) return;
|
|
418
|
+
data.$filter.update = true;
|
|
419
|
+
data.$filter.currentIndex = index;
|
|
420
|
+
localData[index] = dotNotationToObject(newData, localData[index]);
|
|
421
|
+
if (filter && filter.sort) {
|
|
422
|
+
localData[index].isNewData = true;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (filter && filter.sort) {
|
|
427
|
+
localData = sortData(localData, filter.sort);
|
|
428
|
+
index = localData.findIndex((obj) => obj.isNewData);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (index >= 0) {
|
|
433
|
+
if (data.$filter.currentIndex === index)
|
|
434
|
+
delete data.$filter.currentIndex;
|
|
435
|
+
data.$filter.index = index;
|
|
436
|
+
if (element.renderValue) element.renderValue(data);
|
|
437
|
+
else if (data) element.setValue(localData);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export default { setDataType, setData }
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import socket from "@cocreate/socket-client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Canonical scope attributes included in socket server subscription channel keys.
|
|
5
|
+
* Excludes field-level "key" and "$filter" so document and collection channels remain cohesive.
|
|
6
|
+
*/
|
|
7
|
+
const SUBSCRIPTION_ATTRIBUTES = [
|
|
8
|
+
"host",
|
|
9
|
+
"organization_id",
|
|
10
|
+
"storage",
|
|
11
|
+
"database",
|
|
12
|
+
"array",
|
|
13
|
+
"index",
|
|
14
|
+
"object"
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Derives all target subscription channel keys for write operations (create, update, delete).
|
|
19
|
+
* Returns an array containing both the specific document scope key (with ID)
|
|
20
|
+
* and the generic scope key (without ID) to notify both single-item and list subscribers.
|
|
21
|
+
*
|
|
22
|
+
* @param {Object} dataKey - Client dataKey or CRUD payload object
|
|
23
|
+
* @returns {string[]} Array of JSON-stringified subscription key channel strings
|
|
24
|
+
*/
|
|
25
|
+
export function deriveSubscriptionKeys(dataKey) {
|
|
26
|
+
if (!dataKey) return [];
|
|
27
|
+
const rawObj = dataKey.object || dataKey;
|
|
28
|
+
|
|
29
|
+
const baseSubObj = {};
|
|
30
|
+
|
|
31
|
+
for (const attr of SUBSCRIPTION_ATTRIBUTES) {
|
|
32
|
+
const value = rawObj[attr];
|
|
33
|
+
if (value !== undefined && value !== null && value !== "") {
|
|
34
|
+
baseSubObj[attr] = value;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const keysSet = new Set();
|
|
39
|
+
|
|
40
|
+
// 1. Specific Document Key (includes exact object ID if present)
|
|
41
|
+
const sortedSpecific = Object.fromEntries(
|
|
42
|
+
Object.entries(baseSubObj).sort(([a], [b]) => a.localeCompare(b))
|
|
43
|
+
);
|
|
44
|
+
keysSet.add(JSON.stringify(sortedSpecific));
|
|
45
|
+
|
|
46
|
+
// 2. Generic Scope Key (targets list component subscribers watching the collection)
|
|
47
|
+
if (baseSubObj.object) {
|
|
48
|
+
const genericSubObj = { ...baseSubObj };
|
|
49
|
+
genericSubObj.object = []; // Generalize object scope
|
|
50
|
+
|
|
51
|
+
const sortedGeneric = Object.fromEntries(
|
|
52
|
+
Object.entries(genericSubObj).sort(([a], [b]) => a.localeCompare(b))
|
|
53
|
+
);
|
|
54
|
+
keysSet.add(JSON.stringify(sortedGeneric));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return Array.from(keysSet);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Sends a subscription payload to the socket server for a registered dataKey.
|
|
62
|
+
*
|
|
63
|
+
* @param {Object} dataKey - Generated dataKey object { string, object }
|
|
64
|
+
* @param {Object} [data] - Optional metadata or payload overrides
|
|
65
|
+
* @returns {Object|undefined} Payload sent to the socket
|
|
66
|
+
*/
|
|
67
|
+
export function subscribe(dataKey, data = {}) {
|
|
68
|
+
if (!dataKey || !dataKey.string) return;
|
|
69
|
+
|
|
70
|
+
const payload = {
|
|
71
|
+
method: "subscribe",
|
|
72
|
+
key: dataKey.string,
|
|
73
|
+
subscriptionKeys: deriveSubscriptionKeys(dataKey.object || dataKey),
|
|
74
|
+
...dataKey.object,
|
|
75
|
+
...data
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
if (socket && typeof socket.send === "function") {
|
|
79
|
+
socket.send(payload);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return payload;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Sends an unsubscription payload to the socket server when an element scope is unmounted.
|
|
87
|
+
*
|
|
88
|
+
* @param {Object} dataKey - Generated dataKey object { string, object }
|
|
89
|
+
* @param {Object} [data] - Optional metadata or payload overrides
|
|
90
|
+
* @returns {Object|undefined} Payload sent to the socket
|
|
91
|
+
*/
|
|
92
|
+
export function unsubscribe(dataKey, data = {}) {
|
|
93
|
+
if (!dataKey || !dataKey.string) return;
|
|
94
|
+
|
|
95
|
+
const payload = {
|
|
96
|
+
method: "unsubscribe",
|
|
97
|
+
key: dataKey.string,
|
|
98
|
+
subscriptionKeys: deriveSubscriptionKeys(dataKey.object || dataKey),
|
|
99
|
+
...dataKey.object,
|
|
100
|
+
...data
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
if (socket && typeof socket.send === "function") {
|
|
104
|
+
socket.send(payload);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return payload;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export default {
|
|
111
|
+
deriveSubscriptionKeys,
|
|
112
|
+
subscribe,
|
|
113
|
+
unsubscribe
|
|
114
|
+
};
|
|
File without changes
|
|
File without changes
|
|
File without changes
|