@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/package.json
CHANGED
package/src/ApiClient.js
CHANGED
|
@@ -102,6 +102,8 @@ export default class ApiClient extends EventEmitter {
|
|
|
102
102
|
this._ajv = new Ajv({ strict: false, useDefaults: true, allErrors: true, verbose: true });
|
|
103
103
|
addFormats(this._ajv);
|
|
104
104
|
|
|
105
|
+
this._ajv.addFormat("textarea", true);
|
|
106
|
+
|
|
105
107
|
this._ajv.addKeyword({
|
|
106
108
|
keyword: "startBeforeEnd",
|
|
107
109
|
type: "object",
|
package/src/api/BaseEntity.js
CHANGED
|
@@ -5,7 +5,7 @@ import EJSON from "ejson";
|
|
|
5
5
|
import pkg from "file-type";
|
|
6
6
|
|
|
7
7
|
import { ApiAuthenticationError, ApiError, ApiResponseError, ApiValidationError } from "../error.js";
|
|
8
|
-
import {
|
|
8
|
+
import { isReactive, isSignal, reactive } from "../utils/reactive.js";
|
|
9
9
|
const { fromBuffer } = pkg;
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -89,9 +89,9 @@ export class BaseEntity {
|
|
|
89
89
|
throw new ApiError("deps.EndpointApi doit être une classe ou une instance valide.");
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
this._serverData =
|
|
92
|
+
this._serverData = reactive({});
|
|
93
93
|
|
|
94
|
-
const { draft, proxy } = this._buildDraftAndProxy({
|
|
94
|
+
const { draft, proxy, initial } = this._buildDraftAndProxy({
|
|
95
95
|
data: { ...data, ...this.defaultFields },
|
|
96
96
|
serverData: this._serverData,
|
|
97
97
|
constant: this.constructor.SCHEMA_CONSTANTS,
|
|
@@ -100,7 +100,7 @@ export class BaseEntity {
|
|
|
100
100
|
removeFields: this.removeFields
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
-
this._initialDraftData =
|
|
103
|
+
this._initialDraftData = initial;
|
|
104
104
|
this._draftData = draft;
|
|
105
105
|
this.data = proxy;
|
|
106
106
|
}
|
|
@@ -162,7 +162,7 @@ export class BaseEntity {
|
|
|
162
162
|
* @returns {boolean}
|
|
163
163
|
*/
|
|
164
164
|
hasChanges() {
|
|
165
|
-
return
|
|
165
|
+
return this._serialize(this._toRawDeep(this._draftData)) !== this._serialize(this._initialDraftData);
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
/**
|
|
@@ -186,6 +186,7 @@ export class BaseEntity {
|
|
|
186
186
|
|
|
187
187
|
if (!this.id && typeof this._add === "function") {
|
|
188
188
|
await this._add(payload);
|
|
189
|
+
this._resetInitialDraftData();
|
|
189
190
|
// on refresh le contexte utilisateur si besoin
|
|
190
191
|
if(this.userContext) {
|
|
191
192
|
await this.userContext.refresh();
|
|
@@ -193,6 +194,7 @@ export class BaseEntity {
|
|
|
193
194
|
return await this.refresh();
|
|
194
195
|
} else if (typeof this._update === "function") {
|
|
195
196
|
const hasChanged = await this._update(payload);
|
|
197
|
+
this._resetInitialDraftData();
|
|
196
198
|
if (hasChanged) return await this.refresh();
|
|
197
199
|
}
|
|
198
200
|
|
|
@@ -223,29 +225,94 @@ export class BaseEntity {
|
|
|
223
225
|
* @returns {void}
|
|
224
226
|
* @private
|
|
225
227
|
*/
|
|
226
|
-
_setData(newData) {
|
|
228
|
+
_setData(newData, { forceInitialDraftReset = false } = {}) {
|
|
227
229
|
if (this.userContext && this.userContext !== this) {
|
|
228
230
|
this.apiClient._logger?.info?.(`[${this.__entityTag}] Mise à jour liée à userContext : ${this.userContext.id}`);
|
|
229
231
|
}
|
|
230
|
-
this._serverData = reactive({ ...newData });
|
|
231
232
|
|
|
232
|
-
|
|
233
|
-
|
|
233
|
+
if (isReactive(this._serverData)) {
|
|
234
|
+
Object.assign(this._serverData, newData);
|
|
235
|
+
} else {
|
|
236
|
+
this._serverData = reactive({ ...newData });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const clientDraft = this._draftData ? this._toRawDeep(this._draftData) : {};
|
|
240
|
+
|
|
241
|
+
const mergedData = {
|
|
242
|
+
...newData,
|
|
243
|
+
...this.defaultFields,
|
|
244
|
+
...clientDraft
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const { draft, proxy, initial } = this._buildDraftAndProxy({
|
|
248
|
+
data: mergedData,
|
|
234
249
|
serverData: this._serverData,
|
|
250
|
+
previousDraft: this._draftData,
|
|
235
251
|
constant: this.constructor.SCHEMA_CONSTANTS,
|
|
236
252
|
apiClient: this.apiClient,
|
|
237
253
|
transforms: this.transforms,
|
|
238
254
|
removeFields: this.removeFields
|
|
239
255
|
});
|
|
240
|
-
this._initialDraftData = JSON.parse(JSON.stringify(draft));
|
|
241
|
-
this._draftData = draft;
|
|
242
|
-
this.data = proxy;
|
|
243
256
|
|
|
244
|
-
if (
|
|
245
|
-
this.
|
|
257
|
+
if (forceInitialDraftReset) {
|
|
258
|
+
this._initialDraftData = structuredClone(this._toRawDeep(draft));
|
|
259
|
+
} else if (!this._initialDraftData) {
|
|
260
|
+
this._initialDraftData = initial;
|
|
246
261
|
}
|
|
262
|
+
|
|
263
|
+
if (isReactive(this._draftData)) {
|
|
264
|
+
this._updateDraftPreservingUserChanges(draft);
|
|
265
|
+
} else {
|
|
266
|
+
this._draftData = reactive(draft);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (!this.data) {
|
|
270
|
+
this.data = proxy;
|
|
271
|
+
}
|
|
272
|
+
|
|
247
273
|
}
|
|
248
274
|
|
|
275
|
+
_updateDraftPreservingUserChanges(draft) {
|
|
276
|
+
for (const key of Object.keys(draft)) {
|
|
277
|
+
const current = this._draftData?.[key];
|
|
278
|
+
const initialValue = this._initialDraftData?.[key];
|
|
279
|
+
|
|
280
|
+
const isModified =
|
|
281
|
+
current !== undefined &&
|
|
282
|
+
initialValue !== undefined &&
|
|
283
|
+
this._serialize(this._toRawDeep(current)) !== this._serialize(initialValue);
|
|
284
|
+
|
|
285
|
+
if (!isModified) {
|
|
286
|
+
this._draftData[key] = draft[key];
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
_resetInitialDraftData() {
|
|
292
|
+
const raw = this._toRawDeep(this._draftData);
|
|
293
|
+
this._initialDraftData = structuredClone(raw);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
_toRawDeep(obj) {
|
|
298
|
+
if (isSignal(obj)) {
|
|
299
|
+
return this._toRawDeep(obj.value);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (Array.isArray(obj)) {
|
|
303
|
+
return obj.map(this._toRawDeep);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (typeof obj === "object" && obj !== null) {
|
|
307
|
+
const result = {};
|
|
308
|
+
for (const key of Object.keys(obj)) {
|
|
309
|
+
result[key] = this._toRawDeep(obj[key]);
|
|
310
|
+
}
|
|
311
|
+
return result;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return obj; // valeur primitive
|
|
315
|
+
}
|
|
249
316
|
|
|
250
317
|
/**
|
|
251
318
|
* Champs à ajouter automatiquement à chaque draft (ex: `typeElement`).
|
|
@@ -768,16 +835,24 @@ export class BaseEntity {
|
|
|
768
835
|
_createDraftProxy(apiClient, server = {}, draft = {}, allowedFields = [], transforms = {}, options = {}) {
|
|
769
836
|
return new Proxy({}, {
|
|
770
837
|
get: (_, prop) => {
|
|
771
|
-
|
|
838
|
+
// Ne pas tenter d’accéder à des propriétés système
|
|
839
|
+
if (typeof prop !== "string" && typeof prop !== "symbol") return undefined;
|
|
840
|
+
|
|
841
|
+
// Lecture explicite — déclenche signal si draft est réactif
|
|
842
|
+
if (prop in draft) {
|
|
843
|
+
const value = draft[prop];
|
|
844
|
+
const transformer = transforms[prop];
|
|
845
|
+
return typeof transformer === "function" ? transformer(value) : value;
|
|
846
|
+
}
|
|
772
847
|
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
848
|
+
// Fallback vers serverData — aussi potentiellement réactif
|
|
849
|
+
if (server && typeof server === "object" && prop in server) {
|
|
850
|
+
const value = server[prop];
|
|
851
|
+
const transformer = transforms[prop];
|
|
852
|
+
return typeof transformer === "function" ? transformer(value) : value;
|
|
777
853
|
}
|
|
778
854
|
|
|
779
|
-
|
|
780
|
-
return typeof transformer === "function" ? transformer(val) : val;
|
|
855
|
+
return undefined;
|
|
781
856
|
},
|
|
782
857
|
|
|
783
858
|
set: (_, prop, value) => {
|
|
@@ -793,7 +868,13 @@ export class BaseEntity {
|
|
|
793
868
|
apiClient._logger.warn(message);
|
|
794
869
|
return false;
|
|
795
870
|
}
|
|
796
|
-
draft[prop]
|
|
871
|
+
const current = draft[prop];
|
|
872
|
+
|
|
873
|
+
if (current && typeof current === "object" && current.__isSignal === true) {
|
|
874
|
+
current.value = value; // ✅ met à jour le signal
|
|
875
|
+
} else {
|
|
876
|
+
draft[prop] = value; // fallback classique
|
|
877
|
+
}
|
|
797
878
|
return true;
|
|
798
879
|
},
|
|
799
880
|
|
|
@@ -804,8 +885,16 @@ export class BaseEntity {
|
|
|
804
885
|
},
|
|
805
886
|
|
|
806
887
|
has: (_, prop) => prop in draft || prop in server,
|
|
807
|
-
|
|
808
|
-
|
|
888
|
+
|
|
889
|
+
ownKeys: () => [...new Set([
|
|
890
|
+
...Object.keys(server || {}),
|
|
891
|
+
...Object.keys(draft || {})
|
|
892
|
+
])],
|
|
893
|
+
|
|
894
|
+
getOwnPropertyDescriptor: () => ({
|
|
895
|
+
enumerable: true,
|
|
896
|
+
configurable: true
|
|
897
|
+
})
|
|
809
898
|
});
|
|
810
899
|
}
|
|
811
900
|
|
|
@@ -881,7 +970,7 @@ export class BaseEntity {
|
|
|
881
970
|
* @returns {Object} - Objet contenant le brouillon et le proxy.
|
|
882
971
|
* @private
|
|
883
972
|
*/
|
|
884
|
-
_buildDraftAndProxy({ data = {}, serverData = null, constant, apiClient, transforms = {}, throwOnError = true, removeFields = [] }) {
|
|
973
|
+
_buildDraftAndProxy({ data = {}, serverData = null, previousDraft = null, constant, apiClient, transforms = {}, throwOnError = true, removeFields = [] }) {
|
|
885
974
|
const constants = Array.isArray(constant) ? constant : [constant];
|
|
886
975
|
const combinedSchema = {
|
|
887
976
|
allOf: [],
|
|
@@ -905,7 +994,7 @@ export class BaseEntity {
|
|
|
905
994
|
|
|
906
995
|
combinedSchema.allOf.push(sch);
|
|
907
996
|
}
|
|
908
|
-
|
|
997
|
+
|
|
909
998
|
let allowed = this._extractWritableFields(combinedSchema, data);
|
|
910
999
|
|
|
911
1000
|
if (data.id && allowed.indexOf("id") === -1) {
|
|
@@ -917,15 +1006,32 @@ export class BaseEntity {
|
|
|
917
1006
|
|
|
918
1007
|
allowed = allowed.filter(k => !removeFields.includes(k));
|
|
919
1008
|
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1009
|
+
// Transformation des champs autorisés
|
|
1010
|
+
const rawDraft = Object.fromEntries(
|
|
1011
|
+
allowed.map((key) => {
|
|
1012
|
+
const raw = data[key];
|
|
1013
|
+
const transformed = typeof transforms[key] === "function"
|
|
1014
|
+
? transforms[key](raw, data)
|
|
1015
|
+
: raw;
|
|
1016
|
+
return [key, transformed];
|
|
1017
|
+
// eslint-disable-next-line no-unused-vars
|
|
1018
|
+
}).filter(([_, v]) => v !== undefined)
|
|
1019
|
+
);
|
|
1020
|
+
|
|
1021
|
+
const initial = structuredClone ? structuredClone(rawDraft) : JSON.parse(JSON.stringify(rawDraft));
|
|
925
1022
|
|
|
926
|
-
const
|
|
1023
|
+
const draft = isReactive(previousDraft)
|
|
1024
|
+
? Object.assign(previousDraft, rawDraft)
|
|
1025
|
+
: reactive(rawDraft);
|
|
927
1026
|
|
|
928
|
-
|
|
1027
|
+
// Assure que serverData est réactif si c'est un objet
|
|
1028
|
+
const reactiveServer = isReactive(serverData)
|
|
1029
|
+
? serverData
|
|
1030
|
+
: (serverData && typeof serverData === "object" ? reactive(serverData) : serverData);
|
|
1031
|
+
|
|
1032
|
+
const proxy = this._createDraftProxy(apiClient, reactiveServer, draft, allowed, transforms, { throwOnError });
|
|
1033
|
+
|
|
1034
|
+
return { draft, proxy, initial};
|
|
929
1035
|
}
|
|
930
1036
|
|
|
931
1037
|
/**
|
|
@@ -969,25 +1075,6 @@ export class BaseEntity {
|
|
|
969
1075
|
return Object.keys(changed).length > 0 ? changed : null;
|
|
970
1076
|
}
|
|
971
1077
|
|
|
972
|
-
/**
|
|
973
|
-
* ───────────────────────────────
|
|
974
|
-
* ReactiveMixin
|
|
975
|
-
* ───────────────────────────────
|
|
976
|
-
*/
|
|
977
|
-
|
|
978
|
-
/**
|
|
979
|
-
* Active la synchronisation réactive entre le brouillon et le schéma.
|
|
980
|
-
*
|
|
981
|
-
* @returns {void}
|
|
982
|
-
* @public
|
|
983
|
-
*/
|
|
984
|
-
setupReactiveSync() {
|
|
985
|
-
if (!this._syncReactiveDraft) {
|
|
986
|
-
this._syncReactiveDraft = true;
|
|
987
|
-
}
|
|
988
|
-
autoSyncDraftFromSchema(this);
|
|
989
|
-
}
|
|
990
|
-
|
|
991
1078
|
/**
|
|
992
1079
|
* ───────────────────────────────
|
|
993
1080
|
* MutualEntityMixin
|
|
@@ -1601,7 +1688,7 @@ export class BaseEntity {
|
|
|
1601
1688
|
async get() {
|
|
1602
1689
|
return this.apiClient.safeCall(async () => {
|
|
1603
1690
|
const data = await this._getPublicProfile();
|
|
1604
|
-
this._setData(data);
|
|
1691
|
+
this._setData(data, { forceInitialDraftReset: true });
|
|
1605
1692
|
return data;
|
|
1606
1693
|
});
|
|
1607
1694
|
}
|