coaction 0.1.5 → 0.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.
@@ -1,679 +0,0 @@
1
- 'use strict';
2
-
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
- var mutative = require('mutative');
6
- var dataTransport = require('data-transport');
7
-
8
- const getGlobal = () => {
9
- let _global;
10
- if (typeof window !== 'undefined') {
11
- _global = window;
12
- } else if (typeof global !== 'undefined') {
13
- _global = global;
14
- } else if (typeof self !== 'undefined') {
15
- _global = self;
16
- } else {
17
- _global = {};
18
- }
19
- return _global;
20
- };
21
- const _global = getGlobal();
22
-
23
- const WorkerType = _global.SharedWorkerGlobalScope ? 'SharedWorkerInternal' : globalThis.WorkerGlobalScope ? 'WebWorkerInternal' : null;
24
- const bindSymbol = Symbol('bind');
25
- const defaultName = 'default';
26
-
27
- const createAsyncClientStore = (createStore, asyncStoreClientOption) => {
28
- const {
29
- store: asyncClientStore
30
- } = createStore({
31
- share: 'client'
32
- });
33
- // the transport is in the worker or shared worker, and the client is in the main thread.
34
- // This store can't be directly executed by any of the store's methods
35
- // its methods are proxied to the worker or share worker for execution.
36
- // and the executed patch is sent to the store to be applied to synchronize the state.
37
- const transport = asyncStoreClientOption.worker ? dataTransport.createTransport(asyncStoreClientOption.worker instanceof SharedWorker ? 'SharedWorkerClient' : 'WebWorkerClient', {
38
- worker: asyncStoreClientOption.worker,
39
- prefix: asyncClientStore.name
40
- }) : asyncStoreClientOption.clientTransport;
41
- if (!transport) {
42
- throw new Error('transport is required');
43
- }
44
- asyncClientStore.transport = transport;
45
- let sequence;
46
- const fullSync = async () => {
47
- const latest = await transport.emit('fullSync');
48
- asyncClientStore.apply(JSON.parse(latest.state));
49
- sequence = latest.sequence;
50
- };
51
- if (typeof transport.onConnect !== 'function') {
52
- throw new Error('transport.onConnect is required');
53
- }
54
- transport.onConnect?.(async () => {
55
- await fullSync();
56
- });
57
- transport.listen('update', async options => {
58
- if (typeof options.sequence === 'number' && options.sequence === sequence + 1) {
59
- sequence = options.sequence;
60
- asyncClientStore.apply(undefined, options.patches);
61
- } else {
62
- await fullSync();
63
- }
64
- });
65
- const {
66
- name,
67
- ..._store
68
- } = asyncClientStore;
69
- return Object.assign({
70
- [name]: () => asyncClientStore.getState()
71
- }[name], _store);
72
- };
73
- const emit = (store, internal, patches) => {
74
- if (store.transport && patches?.length) {
75
- internal.sequence += 1;
76
- // it is not necessary to respond to the update event
77
- store.transport.emit({
78
- name: 'update',
79
- respond: false
80
- }, {
81
- patches: patches,
82
- sequence: internal.sequence
83
- });
84
- }
85
- };
86
- const handleDraft = (store, internal) => {
87
- internal.rootState = internal.backupState;
88
- const [, patches, inversePatches] = internal.finalizeDraft();
89
- const finalPatches = store.patch ? store.patch({
90
- patches,
91
- inversePatches
92
- }) : {
93
- patches,
94
- inversePatches
95
- };
96
- if (finalPatches.patches.length) {
97
- store.apply(internal.rootState, finalPatches.patches);
98
- // 3rd party model will send update notifications on its own after `store.apply` in mutableInstance mode
99
- emit(store, internal, finalPatches.patches);
100
- }
101
- };
102
-
103
- const getInitialState = (store, createState, internal) => {
104
- const makeState = fn => {
105
- // make sure createState is a function
106
- if (process.env.NODE_ENV !== 'production' && typeof fn !== 'function') {
107
- throw new Error('createState should be a function');
108
- }
109
- let state = fn(store.setState, store.getState, store);
110
- // support 3rd party library store like zustand, redux
111
- if (state.getState) {
112
- state = state.getState();
113
- // support 3rd party library store like pinia
114
- } else if (typeof state === 'function') {
115
- state = state();
116
- }
117
- // support bind store like mobx
118
- if (state[bindSymbol]) {
119
- const rawState = state[bindSymbol].bind(state);
120
- state[bindSymbol].handleStore(store, rawState, state, internal);
121
- delete state[bindSymbol];
122
- return rawState;
123
- }
124
- return state;
125
- };
126
- return store.isSliceStore ? Object.entries(createState).reduce((stateTree, [key, value]) => Object.assign(stateTree, {
127
- [key]: makeState(value)
128
- }), {}) : makeState(createState);
129
- };
130
-
131
- const isEqual = (x, y) => {
132
- if (x === y) {
133
- return x !== 0 || y !== 0 || 1 / x === 1 / y;
134
- }
135
- return x !== x && y !== y;
136
- };
137
- const areShallowEqualWithArray = (prev, next) => {
138
- if (prev === null || next === null || prev.length !== next.length) {
139
- return false;
140
- }
141
- const {
142
- length
143
- } = prev;
144
- for (let i = 0; i < length; i += 1) {
145
- if (!isEqual(prev[i], next[i])) {
146
- return false;
147
- }
148
- }
149
- return true;
150
- };
151
- const mergeObject = (target, source, isSlice) => {
152
- if (isSlice) {
153
- if (typeof source === 'object' && source !== null) {
154
- for (const key in source) {
155
- if (typeof source[key] === 'object' && source[key] !== null) {
156
- Object.assign(target[key], source[key]);
157
- }
158
- }
159
- }
160
- } else {
161
- Object.assign(target, source);
162
- }
163
- };
164
- const uuid = () => {
165
- let timestamp = new Date().getTime();
166
- const uuidTemplate = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
167
- const uuid = uuidTemplate.replace(/[xy]/g, char => {
168
- let randomNum = (timestamp + Math.random() * 16) % 16 | 0;
169
- timestamp = Math.floor(timestamp / 16);
170
- return (char === 'x' ? randomNum : randomNum & 0x3 | 0x8).toString(16);
171
- });
172
- return uuid;
173
- };
174
-
175
- class Computed {
176
- constructor(deps, fn) {
177
- this.deps = deps;
178
- this.fn = fn;
179
- }
180
- }
181
- const defaultMemoize = func => {
182
- const lastArgs = new WeakMap();
183
- const lastResult = new WeakMap();
184
- return function () {
185
- if (!areShallowEqualWithArray(lastArgs.get(this) ?? [], arguments)) {
186
- lastResult.set(this, func.apply(this, arguments));
187
- }
188
- lastArgs.set(this, arguments);
189
- return lastResult.get(this);
190
- };
191
- };
192
- const createSelectorCreatorWithArray = (memoize = defaultMemoize) => {
193
- return (dependenciesFunc, resultFunc) => {
194
- const memoizedResultFunc = memoize(function () {
195
- return resultFunc.apply(this, arguments);
196
- });
197
- return function () {
198
- return memoizedResultFunc.apply(this, dependenciesFunc.apply(null, [this]));
199
- };
200
- };
201
- };
202
- const createSelectorWithArray = createSelectorCreatorWithArray();
203
-
204
- const getRawState = (store, internal, initialState, options) => {
205
- const rawState = {};
206
- const handle = (_rawState, _initialState, sliceKey) => {
207
- internal.mutableInstance = internal.toMutableRaw?.(_initialState);
208
- const descriptors = Object.getOwnPropertyDescriptors(_initialState);
209
- Object.entries(descriptors).forEach(([key, descriptor]) => {
210
- if (Object.prototype.hasOwnProperty.call(descriptor, 'value')) {
211
- if (typeof descriptor.value !== 'function') {
212
- const isComputed = descriptor.value instanceof Computed;
213
- if (internal.mutableInstance) {
214
- Object.defineProperty(_rawState, key, {
215
- get: () => internal.mutableInstance[key],
216
- set: value => {
217
- internal.mutableInstance[key] = value;
218
- },
219
- enumerable: true
220
- });
221
- } else if (!isComputed) {
222
- _rawState[key] = descriptor.value;
223
- }
224
- if (isComputed) {
225
- if (internal.mutableInstance) {
226
- throw new Error('Computed is not supported with mutable instance');
227
- }
228
- // manually handle computed property
229
- const {
230
- deps,
231
- fn
232
- } = descriptor.value;
233
- const depsCallbackSelector = createSelectorWithArray(() => [internal.rootState], () => {
234
- return deps(internal.module);
235
- });
236
- const selector = createSelectorWithArray(that => depsCallbackSelector.call(that), fn);
237
- descriptor.get = function () {
238
- return selector.call(this);
239
- };
240
- } else {
241
- if (sliceKey) {
242
- descriptor.get = () => internal.rootState[sliceKey][key];
243
- descriptor.set = value => {
244
- internal.rootState[sliceKey][key] = value;
245
- };
246
- } else {
247
- descriptor.get = () => internal.rootState[key];
248
- descriptor.set = value => {
249
- internal.rootState[key] = value;
250
- };
251
- }
252
- }
253
- // handle state property
254
- delete descriptor.value;
255
- delete descriptor.writable;
256
- } else if (store.share === 'client') {
257
- descriptor.value = (...args) => {
258
- let actionId;
259
- let done;
260
- if (store.trace) {
261
- actionId = uuid();
262
- store.trace({
263
- method: key,
264
- parameters: args,
265
- id: actionId,
266
- sliceKey
267
- });
268
- done = result => {
269
- store.trace({
270
- method: key,
271
- id: actionId,
272
- result,
273
- sliceKey
274
- });
275
- };
276
- }
277
- const keys = sliceKey ? [sliceKey, key] : [key];
278
- // emit the action to worker or main thread execute
279
- return store.transport.emit('execute', keys, args).then(result => {
280
- if (result?.$$Error) {
281
- done?.(result);
282
- throw new Error(result.$$Error);
283
- }
284
- done?.(result);
285
- return result;
286
- });
287
- };
288
- } else {
289
- const fn = descriptor.value;
290
- descriptor.value = (...args) => {
291
- let actionId;
292
- let done;
293
- if (store.trace) {
294
- actionId = uuid();
295
- store.trace({
296
- method: key,
297
- parameters: args,
298
- id: actionId,
299
- sliceKey
300
- });
301
- done = result => {
302
- store.trace({
303
- method: key,
304
- id: actionId,
305
- result,
306
- sliceKey
307
- });
308
- };
309
- }
310
- const enablePatches = store.transport ?? options.enablePatches;
311
- if (internal.mutableInstance && !internal.isBatching && enablePatches) {
312
- let result;
313
- const handleResult = isDrafted => {
314
- handleDraft(store, internal);
315
- if (isDrafted) {
316
- internal.backupState = internal.rootState;
317
- const [draft, finalize] = mutative.create(internal.rootState, {
318
- enablePatches: true
319
- });
320
- internal.finalizeDraft = finalize;
321
- internal.rootState = draft;
322
- }
323
- };
324
- const isDrafted = mutative.isDraft(internal.rootState);
325
- if (isDrafted) {
326
- handleResult();
327
- }
328
- internal.backupState = internal.rootState;
329
- const [draft, finalize] = mutative.create(internal.rootState, {
330
- enablePatches: true
331
- });
332
- internal.finalizeDraft = finalize;
333
- internal.rootState = draft;
334
- try {
335
- result = fn.apply(sliceKey ? store.getState()[sliceKey] : store.getState(), args);
336
- } finally {
337
- if (result instanceof Promise) {
338
- // if (process.env.NODE_ENV === 'development') {
339
- // console.warn(
340
- // 'It will be combined with the next state in the async function.'
341
- // );
342
- // }
343
- return result.finally(() => {
344
- const result = handleResult(isDrafted);
345
- done?.(result);
346
- return result;
347
- });
348
- }
349
- handleResult(isDrafted);
350
- }
351
- done?.(result);
352
- return result;
353
- }
354
- if (internal.mutableInstance && internal.actMutable) {
355
- const result = internal.actMutable(() => {
356
- return fn.apply(sliceKey ? store.getState()[sliceKey] : store.getState(), args);
357
- });
358
- done?.(result);
359
- return result;
360
- }
361
- const result = fn.apply(sliceKey ? store.getState()[sliceKey] : store.getState(), args);
362
- done?.(result);
363
- return result;
364
- };
365
- }
366
- }
367
- });
368
- // it should be a immutable state
369
- const slice = Object.defineProperties({}, descriptors);
370
- return slice;
371
- };
372
- if (store.isSliceStore) {
373
- internal.module = {};
374
- Object.entries(initialState).forEach(([key, value]) => {
375
- rawState[key] = {};
376
- internal.module[key] = handle(rawState[key], value, key);
377
- });
378
- } else {
379
- internal.module = handle(rawState, initialState);
380
- }
381
- return rawState;
382
- };
383
-
384
- const handleState = (store, internal, options) => {
385
- const setState = (next, updater = next => {
386
- const merge = (_next = next) => {
387
- mergeObject(internal.rootState, _next, store.isSliceStore);
388
- };
389
- const fn = typeof next === 'function' ? () => {
390
- const returnValue = next(internal.module);
391
- if (returnValue instanceof Promise) {
392
- throw new Error('setState with async function is not supported');
393
- }
394
- if (typeof returnValue === 'object' && returnValue !== null) {
395
- merge(returnValue);
396
- }
397
- } : merge;
398
- const enablePatches = store.transport ?? options.enablePatches;
399
- if (!enablePatches) {
400
- if (internal.mutableInstance) {
401
- if (internal.actMutable) {
402
- internal.actMutable(() => {
403
- fn.apply(null);
404
- });
405
- return [];
406
- }
407
- fn.apply(null);
408
- return [];
409
- }
410
- // best performance by default for immutable state
411
- // TODO: supporting nested set functions?
412
- try {
413
- internal.backupState = internal.rootState;
414
- internal.rootState = mutative.create(internal.rootState, draft => {
415
- internal.rootState = draft;
416
- return fn.apply(null);
417
- });
418
- } catch (error) {
419
- internal.rootState = internal.backupState;
420
- throw error;
421
- }
422
- if (internal.updateImmutable) {
423
- internal.updateImmutable(internal.rootState);
424
- } else {
425
- internal.listeners.forEach(listener => listener());
426
- }
427
- return [];
428
- }
429
- internal.backupState = internal.rootState;
430
- let patches;
431
- let inversePatches;
432
- try {
433
- const result = mutative.create(internal.rootState, draft => {
434
- internal.rootState = draft;
435
- return fn.apply(null);
436
- }, {
437
- enablePatches: true
438
- });
439
- patches = result[1];
440
- inversePatches = result[2];
441
- } finally {
442
- internal.rootState = internal.backupState;
443
- }
444
- const finalPatches = store.patch ? store.patch({
445
- patches,
446
- inversePatches
447
- }) : {
448
- patches,
449
- inversePatches
450
- };
451
- if (finalPatches.patches.length) {
452
- store.apply(internal.rootState, finalPatches.patches);
453
- if (!internal.mutableInstance) {
454
- if (internal.updateImmutable) {
455
- internal.updateImmutable(internal.rootState);
456
- } else {
457
- internal.listeners.forEach(listener => listener());
458
- }
459
- }
460
- }
461
- return [internal.rootState, patches, inversePatches];
462
- }) => {
463
- if (store.share === 'client') {
464
- throw new Error(`setState() cannot be called in the client store. To update the state, please trigger a store method with setState() instead.`);
465
- }
466
- if (internal.isBatching) {
467
- throw new Error('setState cannot be called within the updater');
468
- }
469
- internal.isBatching = true;
470
- let result;
471
- try {
472
- const isDrafted = internal.mutableInstance && mutative.isDraft(internal.rootState);
473
- if (isDrafted) {
474
- handleDraft(store, internal);
475
- }
476
- result = updater(next);
477
- if (isDrafted) {
478
- internal.backupState = internal.rootState;
479
- const [draft, finalize] = mutative.create(internal.rootState, {
480
- enablePatches: true
481
- });
482
- internal.finalizeDraft = finalize;
483
- internal.rootState = draft;
484
- }
485
- } finally {
486
- internal.isBatching = false;
487
- }
488
- emit(store, internal, result?.[1]);
489
- return result;
490
- };
491
- const getState = (deps, selector) => deps && selector ? new Computed(deps, selector) : internal.module;
492
- return {
493
- setState,
494
- getState
495
- };
496
- };
497
-
498
- const applyMiddlewares = (store, middlewares) => {
499
- return middlewares.reduce((store, middleware, index) => {
500
- if (process.env.NODE_ENV === 'development') {
501
- if (typeof middleware !== 'function') {
502
- throw new Error(`middlewares[${index}] should be a function`);
503
- }
504
- }
505
- return middleware(store);
506
- }, store);
507
- };
508
-
509
- /**
510
- * wrapStore is a function to wrap the store and return function to get the state with store.
511
- */
512
- const wrapStore = (store, getState = () => store.getState()) => {
513
- const {
514
- name,
515
- ..._store
516
- } = store;
517
- return Object.assign({
518
- [name]: (...args) => getState(...args)
519
- }[name], _store);
520
- };
521
-
522
- const handleMainTransport = (store, internal, storeTransport, workerType, checkEnablePatches) => {
523
- // store transport for server port
524
- // the store transport is responsible for transmitting the sync state to the client transport.
525
- const transport = storeTransport ?? (workerType === 'SharedWorkerInternal' || workerType === 'WebWorkerInternal' ? dataTransport.createTransport(workerType, {
526
- prefix: store.name
527
- }) : undefined);
528
- if (!transport) return;
529
- if (typeof transport.onConnect !== 'function') {
530
- throw new Error('transport.onConnect is required');
531
- }
532
- if (checkEnablePatches) {
533
- throw new Error(`enablePatches: true is required for the transport`);
534
- }
535
- transport.listen('execute', async (keys, args) => {
536
- let base = store.getState();
537
- let obj = base;
538
- for (const key of keys) {
539
- base = base[key];
540
- if (typeof base === 'function') {
541
- base = base.bind(obj);
542
- }
543
- obj = base;
544
- }
545
- if (process.env.NODE_ENV === 'development' && typeof base !== 'function') {
546
- throw new Error('The function is not found');
547
- }
548
- try {
549
- return base(...args);
550
- } catch (error) {
551
- console.error(error);
552
- return {
553
- $$Error: error.message
554
- };
555
- }
556
- });
557
- transport.listen('fullSync', async () => {
558
- return {
559
- state: JSON.stringify(internal.rootState),
560
- sequence: internal.sequence
561
- };
562
- });
563
- store.transport = transport;
564
- };
565
-
566
- const namespaceMap = new Map();
567
-
568
- /**
569
- * Create a simple store or a shared store. The shared store can be used in a worker or another thread.
570
- */
571
- const create = (createState, options = {}) => {
572
- const checkEnablePatches = Object.hasOwnProperty.call(options, 'enablePatches') && !options.enablePatches;
573
- const workerType = options.workerType ?? WorkerType;
574
- if (process.env.NODE_ENV === 'development' && options.transport && options.clientTransport) {
575
- throw new Error(`transport and clientTransport cannot be used together, please use one of them.`);
576
- }
577
- const storeTransport = options.transport;
578
- const share = workerType === 'WebWorkerInternal' || workerType === 'SharedWorkerInternal' || storeTransport ? 'main' : undefined;
579
- const createStore = ({
580
- share
581
- }) => {
582
- const store = {};
583
- const internal = {
584
- sequence: 0,
585
- isBatching: false,
586
- listeners: new Set()
587
- };
588
- const name = options.name ?? defaultName;
589
- // check if the store name is unique in main share mode
590
- if (process.env.NODE_ENV === 'development' && share === 'main') {
591
- if (namespaceMap.get(name)) {
592
- throw new Error(`Store name '${name}' is not unique.`);
593
- }
594
- namespaceMap.set(name, true);
595
- }
596
- const {
597
- setState,
598
- getState
599
- } = handleState(store, internal, options);
600
- const subscribe = listener => {
601
- internal.listeners.add(listener);
602
- return () => internal.listeners.delete(listener);
603
- };
604
- const destroy = () => {
605
- internal.listeners.clear();
606
- store.transport?.dispose();
607
- };
608
- const apply = (state = internal.rootState, patches) => {
609
- internal.rootState = patches ? mutative.apply(state, patches) : state;
610
- if (internal.updateImmutable) {
611
- internal.updateImmutable(internal.rootState);
612
- } else {
613
- internal.listeners.forEach(listener => listener());
614
- }
615
- };
616
- const getPureState = () => internal.rootState;
617
- const isSliceStore = typeof createState === 'object';
618
- Object.assign(store, {
619
- name,
620
- share: share ?? false,
621
- setState,
622
- getState,
623
- subscribe,
624
- destroy,
625
- apply,
626
- isSliceStore,
627
- getPureState
628
- });
629
- applyMiddlewares(store, options.middlewares ?? []);
630
- const initialState = getInitialState(store, createState, internal);
631
- store.getInitialState = () => initialState;
632
- internal.rootState = getRawState(store, internal, initialState, options);
633
- return {
634
- store,
635
- internal
636
- };
637
- };
638
- if (options.clientTransport || options.worker || options.workerType === 'WebWorkerClient' || options.workerType === 'SharedWorkerClient') {
639
- if (checkEnablePatches) {
640
- throw new Error(`enablePatches: true is required for the async store`);
641
- }
642
- const store = createAsyncClientStore(createStore, options);
643
- return wrapStore(store);
644
- }
645
- const {
646
- store,
647
- internal
648
- } = createStore({
649
- share
650
- });
651
- handleMainTransport(store, internal, storeTransport, workerType, checkEnablePatches);
652
- return wrapStore(store);
653
- };
654
-
655
- /**
656
- * createBinder is a function to create a binder for the 3rd party store.
657
- */
658
- function createBinder({
659
- handleState,
660
- handleStore
661
- }) {
662
- return state => {
663
- const {
664
- copyState,
665
- key,
666
- bind
667
- } = handleState(state);
668
- const value = key ? copyState[key] : copyState;
669
- value[bindSymbol] = {
670
- handleStore,
671
- bind
672
- };
673
- return copyState;
674
- };
675
- }
676
-
677
- exports.create = create;
678
- exports.createBinder = createBinder;
679
- exports.wrapStore = wrapStore;
@@ -1,5 +0,0 @@
1
- export {
2
- create,
3
- createBinder,
4
- wrapStore
5
- } from "./coaction.cjs.js";