@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.
@@ -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
- subscribers.forEach(fn => fn());
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
- function isSignal(obj) {
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
- }