coaction 1.1.0 → 1.2.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.
Files changed (3) hide show
  1. package/dist/index.js +107 -23
  2. package/dist/index.mjs +107 -23
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -80,23 +80,54 @@ var createAsyncClientStore = (createStore, asyncStoreClientOption) => {
80
80
  throw new Error("transport is required");
81
81
  }
82
82
  asyncClientStore.transport = transport;
83
+ let syncingPromise = null;
83
84
  const fullSync = async () => {
84
- const latest = await transport.emit("fullSync");
85
- asyncClientStore.apply(JSON.parse(latest.state));
86
- internal.sequence = latest.sequence;
85
+ if (!syncingPromise) {
86
+ syncingPromise = (async () => {
87
+ const latest = await transport.emit("fullSync");
88
+ if (typeof latest.sequence !== "number" || typeof latest.state !== "string") {
89
+ throw new Error("Invalid fullSync payload");
90
+ }
91
+ if (latest.sequence < internal.sequence) {
92
+ return;
93
+ }
94
+ asyncClientStore.apply(JSON.parse(latest.state));
95
+ internal.sequence = latest.sequence;
96
+ })().finally(() => {
97
+ syncingPromise = null;
98
+ });
99
+ }
100
+ return syncingPromise;
87
101
  };
88
102
  if (typeof transport.onConnect !== "function") {
89
103
  throw new Error("transport.onConnect is required");
90
104
  }
91
- transport.onConnect?.(async () => {
92
- await fullSync();
105
+ transport.onConnect?.(() => {
106
+ void fullSync().catch((error) => {
107
+ if (process.env.NODE_ENV === "development") {
108
+ console.error(error);
109
+ }
110
+ });
93
111
  });
94
112
  transport.listen("update", async (options) => {
95
- if (typeof options.sequence === "number" && options.sequence === internal.sequence + 1) {
96
- internal.sequence = options.sequence;
97
- asyncClientStore.apply(void 0, options.patches);
98
- } else {
99
- await fullSync();
113
+ try {
114
+ if (typeof options.sequence !== "number") {
115
+ await fullSync();
116
+ return;
117
+ }
118
+ if (options.sequence <= internal.sequence) {
119
+ return;
120
+ }
121
+ if (options.sequence === internal.sequence + 1) {
122
+ internal.sequence = options.sequence;
123
+ asyncClientStore.apply(void 0, options.patches);
124
+ } else {
125
+ await fullSync();
126
+ }
127
+ } catch (error) {
128
+ if (process.env.NODE_ENV === "development") {
129
+ console.error(error);
130
+ }
100
131
  }
101
132
  });
102
133
  return wrapStore(asyncClientStore, () => asyncClientStore.getState());
@@ -131,6 +162,7 @@ var isObject = (value) => typeof value === "object" && value !== null;
131
162
  var isStateFactory = (value) => typeof value === "function";
132
163
  var hasGetState = (value) => (typeof value === "object" || typeof value === "function") && value !== null && typeof value.getState === "function";
133
164
  var hasBindState = (value) => isObject(value) && !!value[bindSymbol];
165
+ var formatInvalidStateMessage = (type, stateOrFn, key) => `Invalid state ${type} encountered in makeState: ${key ? `for key ${key}, ` : ""}${typeof stateOrFn}`;
134
166
  var getInitialState = (store, createState, internal) => {
135
167
  const makeState = (stateOrFn, key) => {
136
168
  let state;
@@ -140,9 +172,7 @@ var getInitialState = (store, createState, internal) => {
140
172
  state = stateOrFn;
141
173
  } else {
142
174
  if (process.env.NODE_ENV !== "production") {
143
- throw new Error(
144
- `Invalid state value encountered in makeState: ${key ? `for key ${key}, ` : ""}${typeof stateOrFn}`
145
- );
175
+ throw new Error(formatInvalidStateMessage("value", stateOrFn, key));
146
176
  }
147
177
  return {};
148
178
  }
@@ -169,6 +199,12 @@ var getInitialState = (store, createState, internal) => {
169
199
  delete state[bindSymbol];
170
200
  return rawState;
171
201
  }
202
+ if (!isObject(state)) {
203
+ if (process.env.NODE_ENV !== "production") {
204
+ throw new Error(formatInvalidStateMessage("result", state, key));
205
+ }
206
+ return {};
207
+ }
172
208
  return state;
173
209
  };
174
210
  return store.isSliceStore ? Object.entries(createState).reduce(
@@ -204,9 +240,14 @@ var areShallowEqualWithArray = (prev, next) => {
204
240
  var mergeObject = (target, source, isSlice) => {
205
241
  if (isSlice) {
206
242
  if (typeof source === "object" && source !== null) {
207
- for (const key in source) {
208
- if (typeof source[key] === "object" && source[key] !== null) {
209
- Object.assign(target[key], source[key]);
243
+ for (const key of Object.keys(source)) {
244
+ const sourceValue = source[key];
245
+ if (typeof sourceValue !== "object" || sourceValue === null) {
246
+ continue;
247
+ }
248
+ const targetValue = target[key];
249
+ if (typeof targetValue === "object" && targetValue !== null) {
250
+ Object.assign(targetValue, sourceValue);
210
251
  }
211
252
  }
212
253
  }
@@ -260,6 +301,20 @@ var createSelectorWithArray = createSelectorCreatorWithArray();
260
301
 
261
302
  // src/getRawState.ts
262
303
  var clientExecuteSyncTimeoutMs = 1500;
304
+ var transportErrorMarker = "__coactionTransportError__";
305
+ var isTransportErrorEnvelope = (value) => {
306
+ if (typeof value !== "object" || value === null) {
307
+ return false;
308
+ }
309
+ return value[transportErrorMarker] === true && typeof value.message === "string";
310
+ };
311
+ var isLegacyTransportErrorEnvelope = (value) => {
312
+ if (typeof value !== "object" || value === null) {
313
+ return false;
314
+ }
315
+ const candidate = value;
316
+ return typeof candidate.$$Error === "string" && candidate.$$Error.length > 0 && Object.keys(candidate).length === 1;
317
+ };
263
318
  var getRawState = (store, internal, initialState, options) => {
264
319
  const rawState = {};
265
320
  const handle = (_rawState, _initialState, sliceKey) => {
@@ -338,7 +393,9 @@ var getRawState = (store, internal, initialState, options) => {
338
393
  };
339
394
  }
340
395
  const keys = sliceKey ? [sliceKey, key] : [key];
341
- return store.transport.emit("execute", keys, args).then(async ([result, sequence]) => {
396
+ return store.transport.emit("execute", keys, args).then(async (response) => {
397
+ const result = Array.isArray(response) ? response[0] : response;
398
+ const sequence = Array.isArray(response) ? typeof response[1] === "number" ? response[1] : internal.sequence : internal.sequence;
342
399
  if (internal.sequence < sequence) {
343
400
  if (process.env.NODE_ENV === "development") {
344
401
  console.warn(
@@ -378,16 +435,31 @@ var getRawState = (store, internal, initialState, options) => {
378
435
  timeoutId = setTimeout(() => {
379
436
  void store.transport.emit("fullSync").then((latest) => {
380
437
  const next = latest;
438
+ if (typeof next.state !== "string" || typeof next.sequence !== "number") {
439
+ throw new Error("Invalid fullSync payload");
440
+ }
381
441
  store.apply(JSON.parse(next.state));
382
442
  internal.sequence = next.sequence;
383
- finishResolve();
443
+ if (internal.sequence >= sequence) {
444
+ finishResolve();
445
+ return;
446
+ }
447
+ finishReject(
448
+ new Error(
449
+ `Stale fullSync sequence: expected >= ${sequence}, got ${internal.sequence}`
450
+ )
451
+ );
384
452
  }).catch((error) => {
385
453
  finishReject(error);
386
454
  });
387
455
  }, clientExecuteSyncTimeoutMs);
388
456
  });
389
457
  }
390
- if (result?.$$Error) {
458
+ if (isTransportErrorEnvelope(result)) {
459
+ done?.(result);
460
+ throw new Error(result.message);
461
+ }
462
+ if (isLegacyTransportErrorEnvelope(result)) {
391
463
  done?.(result);
392
464
  throw new Error(result.$$Error);
393
465
  }
@@ -556,7 +628,11 @@ var handleState = (store, internal, options) => {
556
628
  }
557
629
  }
558
630
  }
559
- return [internal.rootState, patches, inversePatches];
631
+ return [
632
+ internal.rootState,
633
+ finalPatches.patches,
634
+ finalPatches.inversePatches
635
+ ];
560
636
  }) => {
561
637
  if (store.share === "client") {
562
638
  throw new Error(
@@ -665,6 +741,7 @@ var getErrorMessage = (error) => {
665
741
  }
666
742
  return String(error);
667
743
  };
744
+ var transportErrorMarker2 = "__coactionTransportError__";
668
745
  var handleMainTransport = (store, internal, storeTransport, workerType, checkEnablePatches) => {
669
746
  const transport = storeTransport ?? (workerType === "SharedWorkerInternal" || workerType === "WebWorkerInternal" ? (0, import_data_transport2.createTransport)(workerType, {
670
747
  prefix: store.name
@@ -690,12 +767,19 @@ var handleMainTransport = (store, internal, storeTransport, workerType, checkEna
690
767
  if (typeof base !== "function") {
691
768
  throw new Error("The function is not found");
692
769
  }
693
- return [base(...args), internal.sequence];
770
+ const result = await base(...args);
771
+ return [result, internal.sequence];
694
772
  } catch (error) {
695
773
  if (process.env.NODE_ENV === "development") {
696
774
  console.error(error);
697
775
  }
698
- return [{ $$Error: getErrorMessage(error) }, internal.sequence];
776
+ return [
777
+ {
778
+ [transportErrorMarker2]: true,
779
+ message: getErrorMessage(error)
780
+ },
781
+ internal.sequence
782
+ ];
699
783
  }
700
784
  });
701
785
  transport.listen("fullSync", async () => {
@@ -727,7 +811,7 @@ var create = (createState, options = {}) => {
727
811
  listeners: /* @__PURE__ */ new Set()
728
812
  };
729
813
  const name = options.name ?? defaultName;
730
- const shouldTrackName = process.env.NODE_ENV === "development" && share2 === "main";
814
+ const shouldTrackName = share2 === "main" && process.env.NODE_ENV !== "test";
731
815
  const releaseStoreName = () => {
732
816
  if (shouldTrackName) {
733
817
  namespaceMap.delete(name);
package/dist/index.mjs CHANGED
@@ -52,23 +52,54 @@ var createAsyncClientStore = (createStore, asyncStoreClientOption) => {
52
52
  throw new Error("transport is required");
53
53
  }
54
54
  asyncClientStore.transport = transport;
55
+ let syncingPromise = null;
55
56
  const fullSync = async () => {
56
- const latest = await transport.emit("fullSync");
57
- asyncClientStore.apply(JSON.parse(latest.state));
58
- internal.sequence = latest.sequence;
57
+ if (!syncingPromise) {
58
+ syncingPromise = (async () => {
59
+ const latest = await transport.emit("fullSync");
60
+ if (typeof latest.sequence !== "number" || typeof latest.state !== "string") {
61
+ throw new Error("Invalid fullSync payload");
62
+ }
63
+ if (latest.sequence < internal.sequence) {
64
+ return;
65
+ }
66
+ asyncClientStore.apply(JSON.parse(latest.state));
67
+ internal.sequence = latest.sequence;
68
+ })().finally(() => {
69
+ syncingPromise = null;
70
+ });
71
+ }
72
+ return syncingPromise;
59
73
  };
60
74
  if (typeof transport.onConnect !== "function") {
61
75
  throw new Error("transport.onConnect is required");
62
76
  }
63
- transport.onConnect?.(async () => {
64
- await fullSync();
77
+ transport.onConnect?.(() => {
78
+ void fullSync().catch((error) => {
79
+ if (process.env.NODE_ENV === "development") {
80
+ console.error(error);
81
+ }
82
+ });
65
83
  });
66
84
  transport.listen("update", async (options) => {
67
- if (typeof options.sequence === "number" && options.sequence === internal.sequence + 1) {
68
- internal.sequence = options.sequence;
69
- asyncClientStore.apply(void 0, options.patches);
70
- } else {
71
- await fullSync();
85
+ try {
86
+ if (typeof options.sequence !== "number") {
87
+ await fullSync();
88
+ return;
89
+ }
90
+ if (options.sequence <= internal.sequence) {
91
+ return;
92
+ }
93
+ if (options.sequence === internal.sequence + 1) {
94
+ internal.sequence = options.sequence;
95
+ asyncClientStore.apply(void 0, options.patches);
96
+ } else {
97
+ await fullSync();
98
+ }
99
+ } catch (error) {
100
+ if (process.env.NODE_ENV === "development") {
101
+ console.error(error);
102
+ }
72
103
  }
73
104
  });
74
105
  return wrapStore(asyncClientStore, () => asyncClientStore.getState());
@@ -103,6 +134,7 @@ var isObject = (value) => typeof value === "object" && value !== null;
103
134
  var isStateFactory = (value) => typeof value === "function";
104
135
  var hasGetState = (value) => (typeof value === "object" || typeof value === "function") && value !== null && typeof value.getState === "function";
105
136
  var hasBindState = (value) => isObject(value) && !!value[bindSymbol];
137
+ var formatInvalidStateMessage = (type, stateOrFn, key) => `Invalid state ${type} encountered in makeState: ${key ? `for key ${key}, ` : ""}${typeof stateOrFn}`;
106
138
  var getInitialState = (store, createState, internal) => {
107
139
  const makeState = (stateOrFn, key) => {
108
140
  let state;
@@ -112,9 +144,7 @@ var getInitialState = (store, createState, internal) => {
112
144
  state = stateOrFn;
113
145
  } else {
114
146
  if (process.env.NODE_ENV !== "production") {
115
- throw new Error(
116
- `Invalid state value encountered in makeState: ${key ? `for key ${key}, ` : ""}${typeof stateOrFn}`
117
- );
147
+ throw new Error(formatInvalidStateMessage("value", stateOrFn, key));
118
148
  }
119
149
  return {};
120
150
  }
@@ -141,6 +171,12 @@ var getInitialState = (store, createState, internal) => {
141
171
  delete state[bindSymbol];
142
172
  return rawState;
143
173
  }
174
+ if (!isObject(state)) {
175
+ if (process.env.NODE_ENV !== "production") {
176
+ throw new Error(formatInvalidStateMessage("result", state, key));
177
+ }
178
+ return {};
179
+ }
144
180
  return state;
145
181
  };
146
182
  return store.isSliceStore ? Object.entries(createState).reduce(
@@ -179,9 +215,14 @@ var areShallowEqualWithArray = (prev, next) => {
179
215
  var mergeObject = (target, source, isSlice) => {
180
216
  if (isSlice) {
181
217
  if (typeof source === "object" && source !== null) {
182
- for (const key in source) {
183
- if (typeof source[key] === "object" && source[key] !== null) {
184
- Object.assign(target[key], source[key]);
218
+ for (const key of Object.keys(source)) {
219
+ const sourceValue = source[key];
220
+ if (typeof sourceValue !== "object" || sourceValue === null) {
221
+ continue;
222
+ }
223
+ const targetValue = target[key];
224
+ if (typeof targetValue === "object" && targetValue !== null) {
225
+ Object.assign(targetValue, sourceValue);
185
226
  }
186
227
  }
187
228
  }
@@ -235,6 +276,20 @@ var createSelectorWithArray = createSelectorCreatorWithArray();
235
276
 
236
277
  // src/getRawState.ts
237
278
  var clientExecuteSyncTimeoutMs = 1500;
279
+ var transportErrorMarker = "__coactionTransportError__";
280
+ var isTransportErrorEnvelope = (value) => {
281
+ if (typeof value !== "object" || value === null) {
282
+ return false;
283
+ }
284
+ return value[transportErrorMarker] === true && typeof value.message === "string";
285
+ };
286
+ var isLegacyTransportErrorEnvelope = (value) => {
287
+ if (typeof value !== "object" || value === null) {
288
+ return false;
289
+ }
290
+ const candidate = value;
291
+ return typeof candidate.$$Error === "string" && candidate.$$Error.length > 0 && Object.keys(candidate).length === 1;
292
+ };
238
293
  var getRawState = (store, internal, initialState, options) => {
239
294
  const rawState = {};
240
295
  const handle = (_rawState, _initialState, sliceKey) => {
@@ -313,7 +368,9 @@ var getRawState = (store, internal, initialState, options) => {
313
368
  };
314
369
  }
315
370
  const keys = sliceKey ? [sliceKey, key] : [key];
316
- return store.transport.emit("execute", keys, args).then(async ([result, sequence]) => {
371
+ return store.transport.emit("execute", keys, args).then(async (response) => {
372
+ const result = Array.isArray(response) ? response[0] : response;
373
+ const sequence = Array.isArray(response) ? typeof response[1] === "number" ? response[1] : internal.sequence : internal.sequence;
317
374
  if (internal.sequence < sequence) {
318
375
  if (process.env.NODE_ENV === "development") {
319
376
  console.warn(
@@ -353,16 +410,31 @@ var getRawState = (store, internal, initialState, options) => {
353
410
  timeoutId = setTimeout(() => {
354
411
  void store.transport.emit("fullSync").then((latest) => {
355
412
  const next = latest;
413
+ if (typeof next.state !== "string" || typeof next.sequence !== "number") {
414
+ throw new Error("Invalid fullSync payload");
415
+ }
356
416
  store.apply(JSON.parse(next.state));
357
417
  internal.sequence = next.sequence;
358
- finishResolve();
418
+ if (internal.sequence >= sequence) {
419
+ finishResolve();
420
+ return;
421
+ }
422
+ finishReject(
423
+ new Error(
424
+ `Stale fullSync sequence: expected >= ${sequence}, got ${internal.sequence}`
425
+ )
426
+ );
359
427
  }).catch((error) => {
360
428
  finishReject(error);
361
429
  });
362
430
  }, clientExecuteSyncTimeoutMs);
363
431
  });
364
432
  }
365
- if (result?.$$Error) {
433
+ if (isTransportErrorEnvelope(result)) {
434
+ done?.(result);
435
+ throw new Error(result.message);
436
+ }
437
+ if (isLegacyTransportErrorEnvelope(result)) {
366
438
  done?.(result);
367
439
  throw new Error(result.$$Error);
368
440
  }
@@ -534,7 +606,11 @@ var handleState = (store, internal, options) => {
534
606
  }
535
607
  }
536
608
  }
537
- return [internal.rootState, patches, inversePatches];
609
+ return [
610
+ internal.rootState,
611
+ finalPatches.patches,
612
+ finalPatches.inversePatches
613
+ ];
538
614
  }) => {
539
615
  if (store.share === "client") {
540
616
  throw new Error(
@@ -643,6 +719,7 @@ var getErrorMessage = (error) => {
643
719
  }
644
720
  return String(error);
645
721
  };
722
+ var transportErrorMarker2 = "__coactionTransportError__";
646
723
  var handleMainTransport = (store, internal, storeTransport, workerType, checkEnablePatches) => {
647
724
  const transport = storeTransport ?? (workerType === "SharedWorkerInternal" || workerType === "WebWorkerInternal" ? createTransport2(workerType, {
648
725
  prefix: store.name
@@ -668,12 +745,19 @@ var handleMainTransport = (store, internal, storeTransport, workerType, checkEna
668
745
  if (typeof base !== "function") {
669
746
  throw new Error("The function is not found");
670
747
  }
671
- return [base(...args), internal.sequence];
748
+ const result = await base(...args);
749
+ return [result, internal.sequence];
672
750
  } catch (error) {
673
751
  if (process.env.NODE_ENV === "development") {
674
752
  console.error(error);
675
753
  }
676
- return [{ $$Error: getErrorMessage(error) }, internal.sequence];
754
+ return [
755
+ {
756
+ [transportErrorMarker2]: true,
757
+ message: getErrorMessage(error)
758
+ },
759
+ internal.sequence
760
+ ];
677
761
  }
678
762
  });
679
763
  transport.listen("fullSync", async () => {
@@ -705,7 +789,7 @@ var create = (createState, options = {}) => {
705
789
  listeners: /* @__PURE__ */ new Set()
706
790
  };
707
791
  const name = options.name ?? defaultName;
708
- const shouldTrackName = process.env.NODE_ENV === "development" && share2 === "main";
792
+ const shouldTrackName = share2 === "main" && process.env.NODE_ENV !== "test";
709
793
  const releaseStoreName = () => {
710
794
  if (shouldTrackName) {
711
795
  namespaceMap.delete(name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coaction",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "A sleek JavaScript library designed for high-performance and multithreading web apps.",
5
5
  "keywords": [
6
6
  "coaction"