@communecter/cocolight-api-client 1.0.41 → 1.0.43
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/dist/cocolight-api-client.browser.js +2 -2
- package/dist/cocolight-api-client.cjs +1 -1
- package/dist/cocolight-api-client.mjs.js +1 -1
- package/dist/cocolight-api-client.vite.mjs.js +1 -1
- package/dist/cocolight-api-client.vite.mjs.js.map +1 -1
- package/package.json +1 -1
- package/src/ApiClient.js +2 -0
- package/src/api/BaseEntity.js +140 -53
- package/src/api/EndpointApi.types.d.ts +1 -1
- package/src/endpoints.module.js +1 -1
- package/src/utils/reactive.js +33 -96
package/src/utils/reactive.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// reactive.js - Système maison combinant Signal + Proxy pour réactivité profonde et fine
|
|
2
2
|
|
|
3
|
-
import { ApiError } from "../error.js";
|
|
4
|
-
|
|
5
3
|
const effectStack = [];
|
|
6
4
|
const computedCache = new WeakMap();
|
|
7
5
|
const signalRegistry = new WeakMap();
|
|
6
|
+
let batchDepth = 0;
|
|
7
|
+
const pendingEffects = new Set();
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Crée une fonction réactive qui se relance automatiquement
|
|
@@ -25,6 +25,26 @@ export function effect(fn) {
|
|
|
25
25
|
return wrapper;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Exécute une fonction dans un lot, en retardant l'exécution
|
|
30
|
+
* des effets jusqu'à ce que le lot soit terminé.
|
|
31
|
+
* @param {Function} fn
|
|
32
|
+
* @returns {void}
|
|
33
|
+
*/
|
|
34
|
+
export function batch(fn) {
|
|
35
|
+
batchDepth++;
|
|
36
|
+
try {
|
|
37
|
+
fn();
|
|
38
|
+
} finally {
|
|
39
|
+
batchDepth--;
|
|
40
|
+
if (batchDepth === 0) {
|
|
41
|
+
const effects = Array.from(pendingEffects);
|
|
42
|
+
pendingEffects.clear();
|
|
43
|
+
for (const effect of effects) effect();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
28
48
|
/**
|
|
29
49
|
* Crée un signal observable pour une valeur primitive ou complexe.
|
|
30
50
|
* @param {*} initialValue
|
|
@@ -43,7 +63,11 @@ function createSignal(initialValue) {
|
|
|
43
63
|
set value(newValue) {
|
|
44
64
|
if (value !== newValue) {
|
|
45
65
|
value = newValue;
|
|
46
|
-
|
|
66
|
+
if (batchDepth > 0) {
|
|
67
|
+
subscribers.forEach((fn) => pendingEffects.add(fn));
|
|
68
|
+
} else {
|
|
69
|
+
subscribers.forEach((fn) => fn());
|
|
70
|
+
}
|
|
47
71
|
}
|
|
48
72
|
},
|
|
49
73
|
subscribe(fn) {
|
|
@@ -56,7 +80,12 @@ function createSignal(initialValue) {
|
|
|
56
80
|
return signal;
|
|
57
81
|
}
|
|
58
82
|
|
|
59
|
-
|
|
83
|
+
/**
|
|
84
|
+
* vérifie si un objet est un signal.
|
|
85
|
+
* @param {*} obj
|
|
86
|
+
* @returns {boolean}
|
|
87
|
+
*/
|
|
88
|
+
export function isSignal(obj) {
|
|
60
89
|
return obj && obj.__isSignal === true;
|
|
61
90
|
}
|
|
62
91
|
|
|
@@ -199,95 +228,3 @@ export function subscribeTo(obj, key, callback) {
|
|
|
199
228
|
const signal = signals.get(key);
|
|
200
229
|
return signal?.subscribe?.(() => callback(signal.value)) ?? (() => {});
|
|
201
230
|
}
|
|
202
|
-
|
|
203
|
-
// --- Helpers internes ---
|
|
204
|
-
function _getPathValue(obj, path) {
|
|
205
|
-
return path.split(".").reduce((o, k) => (o ? o[k] : undefined), obj);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function _setPathValue(obj, path, value) {
|
|
209
|
-
const keys = path.split(".");
|
|
210
|
-
const lastKey = keys.pop();
|
|
211
|
-
const parent = keys.reduce((o, k) => {
|
|
212
|
-
if (o[k] == null) o[k] = {};
|
|
213
|
-
return o[k];
|
|
214
|
-
}, obj);
|
|
215
|
-
parent[lastKey] = value;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function _deletePathValue(obj, path) {
|
|
219
|
-
const keys = path.split(".");
|
|
220
|
-
const lastKey = keys.pop();
|
|
221
|
-
const parent = keys.reduce((o, k) => o?.[k], obj);
|
|
222
|
-
if (parent && lastKey in parent) delete parent[lastKey];
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function _applyTransformIfExists(path, val, entity) {
|
|
226
|
-
const topKey = path.split(".")[0];
|
|
227
|
-
const transform = entity.transforms?.[topKey];
|
|
228
|
-
return typeof transform === "function" ? transform(val, entity.serverData) : val;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Synchronise certaines clés du draft avec serverData tant qu'elles ne sont pas modifiées.
|
|
233
|
-
* @param {object} entityInstance
|
|
234
|
-
* @param {string[]} keys
|
|
235
|
-
* @param {{ cleanIfEqual?: boolean }} [options]
|
|
236
|
-
*/
|
|
237
|
-
export function syncDraftOnServerChange(entityInstance, keys = [], { cleanIfEqual = true } = {}) {
|
|
238
|
-
for (const path of keys) {
|
|
239
|
-
effect(() => {
|
|
240
|
-
const rawServerVal = _getPathValue(entityInstance.serverData, path);
|
|
241
|
-
const transformedServerVal = _applyTransformIfExists(path, rawServerVal, entityInstance);
|
|
242
|
-
|
|
243
|
-
const draftVal = _getPathValue(entityInstance.draftData, path);
|
|
244
|
-
const initialVal = _getPathValue(entityInstance.initialDraftData, path);
|
|
245
|
-
|
|
246
|
-
const isModified = JSON.stringify(draftVal) !== JSON.stringify(initialVal);
|
|
247
|
-
|
|
248
|
-
if (!isModified) {
|
|
249
|
-
if (cleanIfEqual && JSON.stringify(draftVal) === JSON.stringify(transformedServerVal)) {
|
|
250
|
-
_deletePathValue(entityInstance.draftData, path);
|
|
251
|
-
} else {
|
|
252
|
-
_setPathValue(entityInstance.draftData, path, transformedServerVal);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Applique `syncDraftOnServerChange()` pour tous les champs autorisés de l'entité.
|
|
261
|
-
* @param {object} entityInstance
|
|
262
|
-
*/
|
|
263
|
-
export function autoSyncDraftFromSchema(entityInstance) {
|
|
264
|
-
const constants = Array.isArray(entityInstance.constructor.SCHEMA_CONSTANTS)
|
|
265
|
-
? entityInstance.constructor.SCHEMA_CONSTANTS
|
|
266
|
-
: [entityInstance.constructor.SCHEMA_CONSTANTS];
|
|
267
|
-
|
|
268
|
-
const combinedSchema = { allOf: [], $defs: {} };
|
|
269
|
-
|
|
270
|
-
for (const key of constants) {
|
|
271
|
-
const sch = entityInstance.apiClient.getRequestSchema(key);
|
|
272
|
-
if (!sch) throw new ApiError(`Unable to find schema for ${key}.`);
|
|
273
|
-
if (sch.$defs) {
|
|
274
|
-
for (const [defKey, defVal] of Object.entries(sch.$defs)) {
|
|
275
|
-
if (!combinedSchema.$defs[defKey]) {
|
|
276
|
-
combinedSchema.$defs[defKey] = defVal;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
combinedSchema.allOf.push(sch);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const data = {
|
|
284
|
-
...entityInstance.serverData,
|
|
285
|
-
...entityInstance.defaultFields
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
let allowed = entityInstance._extractWritableFields?.(combinedSchema, data) || [];
|
|
289
|
-
allowed = allowed.filter(k => !entityInstance.removeFields.includes(k));
|
|
290
|
-
allowed = allowed.filter(k => !["id"].includes(k));
|
|
291
|
-
|
|
292
|
-
syncDraftOnServerChange(entityInstance, allowed);
|
|
293
|
-
}
|