coaction 1.0.1 → 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 +156 -41
  2. package/dist/index.mjs +156 -41
  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
  }
@@ -259,6 +300,21 @@ var createSelectorCreatorWithArray = (memoize = defaultMemoize) => {
259
300
  var createSelectorWithArray = createSelectorCreatorWithArray();
260
301
 
261
302
  // src/getRawState.ts
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
+ };
262
318
  var getRawState = (store, internal, initialState, options) => {
263
319
  const rawState = {};
264
320
  const handle = (_rawState, _initialState, sliceKey) => {
@@ -337,31 +393,78 @@ var getRawState = (store, internal, initialState, options) => {
337
393
  };
338
394
  }
339
395
  const keys = sliceKey ? [sliceKey, key] : [key];
340
- return store.transport.emit("execute", keys, args).then(([result, sequence]) => {
341
- if (internal.sequence >= sequence) {
342
- if (result?.$$Error) {
343
- done?.(result);
344
- throw new Error(result.$$Error);
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;
399
+ if (internal.sequence < sequence) {
400
+ if (process.env.NODE_ENV === "development") {
401
+ console.warn(
402
+ `The sequence of the action is not consistent.`,
403
+ sequence,
404
+ internal.sequence
405
+ );
345
406
  }
407
+ await new Promise((resolve, reject) => {
408
+ let settled = false;
409
+ let unsubscribe = () => {
410
+ };
411
+ let timeoutId;
412
+ const cleanup = () => {
413
+ unsubscribe();
414
+ if (typeof timeoutId !== "undefined") {
415
+ clearTimeout(timeoutId);
416
+ }
417
+ };
418
+ const finishResolve = () => {
419
+ if (settled) return;
420
+ settled = true;
421
+ cleanup();
422
+ resolve();
423
+ };
424
+ const finishReject = (error) => {
425
+ if (settled) return;
426
+ settled = true;
427
+ cleanup();
428
+ reject(error);
429
+ };
430
+ unsubscribe = store.subscribe(() => {
431
+ if (internal.sequence >= sequence) {
432
+ finishResolve();
433
+ }
434
+ });
435
+ timeoutId = setTimeout(() => {
436
+ void store.transport.emit("fullSync").then((latest) => {
437
+ const next = latest;
438
+ if (typeof next.state !== "string" || typeof next.sequence !== "number") {
439
+ throw new Error("Invalid fullSync payload");
440
+ }
441
+ store.apply(JSON.parse(next.state));
442
+ internal.sequence = next.sequence;
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
+ );
452
+ }).catch((error) => {
453
+ finishReject(error);
454
+ });
455
+ }, clientExecuteSyncTimeoutMs);
456
+ });
457
+ }
458
+ if (isTransportErrorEnvelope(result)) {
346
459
  done?.(result);
347
- return result;
460
+ throw new Error(result.message);
348
461
  }
349
- if (process.env.NODE_ENV === "development") {
350
- console.warn(
351
- `The sequence of the action is not consistent.`,
352
- sequence,
353
- internal.sequence
354
- );
462
+ if (isLegacyTransportErrorEnvelope(result)) {
463
+ done?.(result);
464
+ throw new Error(result.$$Error);
355
465
  }
356
- return new Promise((resolve) => {
357
- const unsubscribe = store.subscribe(() => {
358
- if (internal.sequence >= sequence) {
359
- unsubscribe();
360
- done?.(result);
361
- resolve(result);
362
- }
363
- });
364
- });
466
+ done?.(result);
467
+ return result;
365
468
  });
366
469
  };
367
470
  } else {
@@ -525,7 +628,11 @@ var handleState = (store, internal, options) => {
525
628
  }
526
629
  }
527
630
  }
528
- return [internal.rootState, patches, inversePatches];
631
+ return [
632
+ internal.rootState,
633
+ finalPatches.patches,
634
+ finalPatches.inversePatches
635
+ ];
529
636
  }) => {
530
637
  if (store.share === "client") {
531
638
  throw new Error(
@@ -634,6 +741,7 @@ var getErrorMessage = (error) => {
634
741
  }
635
742
  return String(error);
636
743
  };
744
+ var transportErrorMarker2 = "__coactionTransportError__";
637
745
  var handleMainTransport = (store, internal, storeTransport, workerType, checkEnablePatches) => {
638
746
  const transport = storeTransport ?? (workerType === "SharedWorkerInternal" || workerType === "WebWorkerInternal" ? (0, import_data_transport2.createTransport)(workerType, {
639
747
  prefix: store.name
@@ -659,12 +767,19 @@ var handleMainTransport = (store, internal, storeTransport, workerType, checkEna
659
767
  if (typeof base !== "function") {
660
768
  throw new Error("The function is not found");
661
769
  }
662
- return [base(...args), internal.sequence];
770
+ const result = await base(...args);
771
+ return [result, internal.sequence];
663
772
  } catch (error) {
664
773
  if (process.env.NODE_ENV === "development") {
665
774
  console.error(error);
666
775
  }
667
- return [{ $$Error: getErrorMessage(error) }, internal.sequence];
776
+ return [
777
+ {
778
+ [transportErrorMarker2]: true,
779
+ message: getErrorMessage(error)
780
+ },
781
+ internal.sequence
782
+ ];
668
783
  }
669
784
  });
670
785
  transport.listen("fullSync", async () => {
@@ -696,7 +811,7 @@ var create = (createState, options = {}) => {
696
811
  listeners: /* @__PURE__ */ new Set()
697
812
  };
698
813
  const name = options.name ?? defaultName;
699
- const shouldTrackName = process.env.NODE_ENV === "development" && share2 === "main";
814
+ const shouldTrackName = share2 === "main" && process.env.NODE_ENV !== "test";
700
815
  const releaseStoreName = () => {
701
816
  if (shouldTrackName) {
702
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
  }
@@ -234,6 +275,21 @@ var createSelectorCreatorWithArray = (memoize = defaultMemoize) => {
234
275
  var createSelectorWithArray = createSelectorCreatorWithArray();
235
276
 
236
277
  // src/getRawState.ts
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
+ };
237
293
  var getRawState = (store, internal, initialState, options) => {
238
294
  const rawState = {};
239
295
  const handle = (_rawState, _initialState, sliceKey) => {
@@ -312,31 +368,78 @@ var getRawState = (store, internal, initialState, options) => {
312
368
  };
313
369
  }
314
370
  const keys = sliceKey ? [sliceKey, key] : [key];
315
- return store.transport.emit("execute", keys, args).then(([result, sequence]) => {
316
- if (internal.sequence >= sequence) {
317
- if (result?.$$Error) {
318
- done?.(result);
319
- throw new Error(result.$$Error);
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;
374
+ if (internal.sequence < sequence) {
375
+ if (process.env.NODE_ENV === "development") {
376
+ console.warn(
377
+ `The sequence of the action is not consistent.`,
378
+ sequence,
379
+ internal.sequence
380
+ );
320
381
  }
382
+ await new Promise((resolve, reject) => {
383
+ let settled = false;
384
+ let unsubscribe = () => {
385
+ };
386
+ let timeoutId;
387
+ const cleanup = () => {
388
+ unsubscribe();
389
+ if (typeof timeoutId !== "undefined") {
390
+ clearTimeout(timeoutId);
391
+ }
392
+ };
393
+ const finishResolve = () => {
394
+ if (settled) return;
395
+ settled = true;
396
+ cleanup();
397
+ resolve();
398
+ };
399
+ const finishReject = (error) => {
400
+ if (settled) return;
401
+ settled = true;
402
+ cleanup();
403
+ reject(error);
404
+ };
405
+ unsubscribe = store.subscribe(() => {
406
+ if (internal.sequence >= sequence) {
407
+ finishResolve();
408
+ }
409
+ });
410
+ timeoutId = setTimeout(() => {
411
+ void store.transport.emit("fullSync").then((latest) => {
412
+ const next = latest;
413
+ if (typeof next.state !== "string" || typeof next.sequence !== "number") {
414
+ throw new Error("Invalid fullSync payload");
415
+ }
416
+ store.apply(JSON.parse(next.state));
417
+ internal.sequence = next.sequence;
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
+ );
427
+ }).catch((error) => {
428
+ finishReject(error);
429
+ });
430
+ }, clientExecuteSyncTimeoutMs);
431
+ });
432
+ }
433
+ if (isTransportErrorEnvelope(result)) {
321
434
  done?.(result);
322
- return result;
435
+ throw new Error(result.message);
323
436
  }
324
- if (process.env.NODE_ENV === "development") {
325
- console.warn(
326
- `The sequence of the action is not consistent.`,
327
- sequence,
328
- internal.sequence
329
- );
437
+ if (isLegacyTransportErrorEnvelope(result)) {
438
+ done?.(result);
439
+ throw new Error(result.$$Error);
330
440
  }
331
- return new Promise((resolve) => {
332
- const unsubscribe = store.subscribe(() => {
333
- if (internal.sequence >= sequence) {
334
- unsubscribe();
335
- done?.(result);
336
- resolve(result);
337
- }
338
- });
339
- });
441
+ done?.(result);
442
+ return result;
340
443
  });
341
444
  };
342
445
  } else {
@@ -503,7 +606,11 @@ var handleState = (store, internal, options) => {
503
606
  }
504
607
  }
505
608
  }
506
- return [internal.rootState, patches, inversePatches];
609
+ return [
610
+ internal.rootState,
611
+ finalPatches.patches,
612
+ finalPatches.inversePatches
613
+ ];
507
614
  }) => {
508
615
  if (store.share === "client") {
509
616
  throw new Error(
@@ -612,6 +719,7 @@ var getErrorMessage = (error) => {
612
719
  }
613
720
  return String(error);
614
721
  };
722
+ var transportErrorMarker2 = "__coactionTransportError__";
615
723
  var handleMainTransport = (store, internal, storeTransport, workerType, checkEnablePatches) => {
616
724
  const transport = storeTransport ?? (workerType === "SharedWorkerInternal" || workerType === "WebWorkerInternal" ? createTransport2(workerType, {
617
725
  prefix: store.name
@@ -637,12 +745,19 @@ var handleMainTransport = (store, internal, storeTransport, workerType, checkEna
637
745
  if (typeof base !== "function") {
638
746
  throw new Error("The function is not found");
639
747
  }
640
- return [base(...args), internal.sequence];
748
+ const result = await base(...args);
749
+ return [result, internal.sequence];
641
750
  } catch (error) {
642
751
  if (process.env.NODE_ENV === "development") {
643
752
  console.error(error);
644
753
  }
645
- return [{ $$Error: getErrorMessage(error) }, internal.sequence];
754
+ return [
755
+ {
756
+ [transportErrorMarker2]: true,
757
+ message: getErrorMessage(error)
758
+ },
759
+ internal.sequence
760
+ ];
646
761
  }
647
762
  });
648
763
  transport.listen("fullSync", async () => {
@@ -674,7 +789,7 @@ var create = (createState, options = {}) => {
674
789
  listeners: /* @__PURE__ */ new Set()
675
790
  };
676
791
  const name = options.name ?? defaultName;
677
- const shouldTrackName = process.env.NODE_ENV === "development" && share2 === "main";
792
+ const shouldTrackName = share2 === "main" && process.env.NODE_ENV !== "test";
678
793
  const releaseStoreName = () => {
679
794
  if (shouldTrackName) {
680
795
  namespaceMap.delete(name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coaction",
3
- "version": "1.0.1",
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"