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