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