@yaasl/devtools 0.7.0-alpha.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 (36) hide show
  1. package/dist/@types/index.d.ts +2 -0
  2. package/dist/@types/redux-devtools/Action.d.ts +4 -0
  3. package/dist/@types/redux-devtools/ExtensionOptions.d.ts +146 -0
  4. package/dist/@types/redux-devtools/Message.d.ts +32 -0
  5. package/dist/@types/redux-devtools/getReduxConnection.d.ts +25 -0
  6. package/dist/@types/redux-devtools/getReduxExtension.d.ts +28 -0
  7. package/dist/@types/redux-devtools/index.d.ts +4 -0
  8. package/dist/@types/reduxDevtools.d.ts +17 -0
  9. package/dist/@types/utils/cache.d.ts +7 -0
  10. package/dist/@types/utils/subscribeStore.d.ts +4 -0
  11. package/dist/@types/utils/updates.d.ts +5 -0
  12. package/dist/cjs/index.js +5 -0
  13. package/dist/cjs/redux-devtools/Action.js +2 -0
  14. package/dist/cjs/redux-devtools/ExtensionOptions.js +2 -0
  15. package/dist/cjs/redux-devtools/Message.js +2 -0
  16. package/dist/cjs/redux-devtools/getReduxConnection.js +20 -0
  17. package/dist/cjs/redux-devtools/getReduxExtension.js +18 -0
  18. package/dist/cjs/redux-devtools/index.js +20 -0
  19. package/dist/cjs/reduxDevtools.js +53 -0
  20. package/dist/cjs/utils/cache.js +12 -0
  21. package/dist/cjs/utils/subscribeStore.js +65 -0
  22. package/dist/cjs/utils/updates.js +9 -0
  23. package/dist/mjs/index.js +1 -0
  24. package/dist/mjs/redux-devtools/Action.js +1 -0
  25. package/dist/mjs/redux-devtools/ExtensionOptions.js +1 -0
  26. package/dist/mjs/redux-devtools/Message.js +1 -0
  27. package/dist/mjs/redux-devtools/getReduxConnection.js +16 -0
  28. package/dist/mjs/redux-devtools/getReduxExtension.js +14 -0
  29. package/dist/mjs/redux-devtools/index.js +4 -0
  30. package/dist/mjs/reduxDevtools.js +48 -0
  31. package/dist/mjs/utils/cache.js +9 -0
  32. package/dist/mjs/utils/subscribeStore.js +60 -0
  33. package/dist/mjs/utils/updates.js +6 -0
  34. package/package.json +56 -0
  35. package/tsconfig.json +3 -0
  36. package/tsconfig.node.json +9 -0
@@ -0,0 +1,2 @@
1
+ export { reduxDevtools } from "./reduxDevtools";
2
+ export type { ReduxDevtoolsOptions as ApplyDevtoolsOptions } from "./reduxDevtools";
@@ -0,0 +1,4 @@
1
+ export interface Action<T extends string = string> {
2
+ type: T;
3
+ }
4
+ export type ActionCreator<A, P extends unknown[] = unknown[]> = (...args: P) => A;
@@ -0,0 +1,146 @@
1
+ import { Action, ActionCreator } from "./Action";
2
+ export interface ExtensionOptions {
3
+ /**
4
+ * the instance name to be showed on the monitor page. Default value is `document.title`.
5
+ * If not specified and there's no document title, it will consist of `tabId` and `instanceId`.
6
+ */
7
+ name?: string;
8
+ /**
9
+ * action creators functions to be available in the Dispatcher.
10
+ */
11
+ actionCreators?: ActionCreator<unknown>[] | Record<string, ActionCreator<unknown>>;
12
+ /**
13
+ * if more than one action is dispatched in the indicated interval, all new actions will be collected and sent at once.
14
+ * It is the joint between performance and speed. When set to `0`, all actions will be sent instantly.
15
+ * Set it to a higher value when experiencing perf issues (also `maxAge` to a lower value).
16
+ *
17
+ * @default 500 ms.
18
+ */
19
+ latency?: number;
20
+ /**
21
+ * (> 1) - maximum allowed actions to be stored in the history tree. The oldest actions are removed once maxAge is reached. It's critical for performance.
22
+ *
23
+ * @default 50
24
+ */
25
+ maxAge?: number;
26
+ /**
27
+ * function which takes `action` object and id number as arguments, and should return `action` object back.
28
+ */
29
+ actionSanitizer?: <A extends Action>(action: A, id: number) => A;
30
+ /**
31
+ * function which takes `state` object and index as arguments, and should return `state` object back.
32
+ */
33
+ stateSanitizer?: <S>(state: S, index: number) => S;
34
+ /**
35
+ * *string or array of strings as regex* - actions types to be hidden / shown in the monitors (while passed to the reducers).
36
+ * If `actionsAllowlist` specified, `actionsDenylist` is ignored.
37
+ */
38
+ actionsDenylist?: string | string[];
39
+ /**
40
+ * *string or array of strings as regex* - actions types to be hidden / shown in the monitors (while passed to the reducers).
41
+ * If `actionsAllowlist` specified, `actionsDenylist` is ignored.
42
+ */
43
+ actionsAllowlist?: string | string[];
44
+ /**
45
+ * called for every action before sending, takes `state` and `action` object, and returns `true` in case it allows sending the current data to the monitor.
46
+ * Use it as a more advanced version of `actionsDenylist`/`actionsAllowlist` parameters.
47
+ */
48
+ predicate?: <S, A extends Action>(state: S, action: A) => boolean;
49
+ /**
50
+ * if specified as `false`, it will not record the changes till clicking on `Start recording` button.
51
+ * Available only for Redux enhancer, for others use `autoPause`.
52
+ *
53
+ * @default true
54
+ */
55
+ shouldRecordChanges?: boolean;
56
+ /**
57
+ * if specified, whenever clicking on `Pause recording` button and there are actions in the history log, will add this action type.
58
+ * If not specified, will commit when paused. Available only for Redux enhancer.
59
+ *
60
+ * @default "@@PAUSED""
61
+ */
62
+ pauseActionType?: string;
63
+ /**
64
+ * auto pauses when the extension’s window is not opened, and so has zero impact on your app when not in use.
65
+ * Not available for Redux enhancer (as it already does it but storing the data to be sent).
66
+ *
67
+ * @default false
68
+ */
69
+ autoPause?: boolean;
70
+ /**
71
+ * if specified as `true`, it will not allow any non-monitor actions to be dispatched till clicking on `Unlock changes` button.
72
+ * Available only for Redux enhancer.
73
+ *
74
+ * @default false
75
+ */
76
+ shouldStartLocked?: boolean;
77
+ /**
78
+ * if set to `false`, will not recompute the states on hot reloading (or on replacing the reducers). Available only for Redux enhancer.
79
+ *
80
+ * @default true
81
+ */
82
+ shouldHotReload?: boolean;
83
+ /**
84
+ * if specified as `true`, whenever there's an exception in reducers, the monitors will show the error message, and next actions will not be dispatched.
85
+ *
86
+ * @default false
87
+ */
88
+ shouldCatchErrors?: boolean;
89
+ /**
90
+ * If you want to restrict the extension, specify the features you allow.
91
+ * If not specified, all of the features are enabled. When set as an object, only those included as `true` will be allowed.
92
+ * Note that except `true`/`false`, `import` and `export` can be set as `custom` (which is by default for Redux enhancer), meaning that the importing/exporting occurs on the client side.
93
+ * Otherwise, you'll get/set the data right from the monitor part.
94
+ */
95
+ features?: {
96
+ /**
97
+ * start/pause recording of dispatched actions
98
+ */
99
+ pause?: boolean;
100
+ /**
101
+ * lock/unlock dispatching actions and side effects
102
+ */
103
+ lock?: boolean;
104
+ /**
105
+ * persist states on page reloading
106
+ */
107
+ persist?: boolean;
108
+ /**
109
+ * export history of actions in a file
110
+ */
111
+ export?: boolean | "custom";
112
+ /**
113
+ * import history of actions from a file
114
+ */
115
+ import?: boolean | "custom";
116
+ /**
117
+ * jump back and forth (time travelling)
118
+ */
119
+ jump?: boolean;
120
+ /**
121
+ * skip (cancel) actions
122
+ */
123
+ skip?: boolean;
124
+ /**
125
+ * drag and drop actions in the history list
126
+ */
127
+ reorder?: boolean;
128
+ /**
129
+ * dispatch custom actions or action creators
130
+ */
131
+ dispatch?: boolean;
132
+ /**
133
+ * generate tests for the selected actions
134
+ */
135
+ test?: boolean;
136
+ };
137
+ /**
138
+ * Set to true or a stacktrace-returning function to record call stack traces for dispatched actions.
139
+ * Defaults to false.
140
+ */
141
+ trace?: boolean | (<A extends Action>(action: A) => string);
142
+ /**
143
+ * The maximum number of stack trace entries to record per action. Defaults to 10.
144
+ */
145
+ traceLimit?: number;
146
+ }
@@ -0,0 +1,32 @@
1
+ import { Action } from "./Action";
2
+ type Prettify<T> = {
3
+ [K in keyof T]: T[K];
4
+ } & {};
5
+ type GenericMessage<Type extends string, State extends string | undefined, Payload extends {
6
+ type?: string;
7
+ } | undefined> = Action<Type> & {
8
+ state: State;
9
+ payload?: Payload;
10
+ };
11
+ type ImportAction = GenericMessage<"DISPATCH", undefined, {
12
+ type: "IMPORT_STATE";
13
+ nextLiftedState: {
14
+ computedStates: {
15
+ state: Record<string, unknown>;
16
+ }[];
17
+ };
18
+ }>;
19
+ type StateAction = GenericMessage<"DISPATCH", string, {
20
+ type: "JUMP_TO_ACTION" | "ROLLBACK";
21
+ actionId?: number;
22
+ timestamp?: number;
23
+ }>;
24
+ type StatelessAction = GenericMessage<"DISPATCH", undefined, {
25
+ type: "RESET" | "COMMIT";
26
+ timestamp: number;
27
+ }>;
28
+ type StartAction = GenericMessage<"START", undefined, undefined | {
29
+ type: undefined;
30
+ }>;
31
+ export type Message = Prettify<StartAction | StateAction | StatelessAction | ImportAction>;
32
+ export {};
@@ -0,0 +1,25 @@
1
+ import { Action } from "./Action";
2
+ import { Message } from "./Message";
3
+ export interface ConnectionResponse {
4
+ /** Initiate the connection and add it to the extension connections.
5
+ * Should only be executed once in the live time of the connection.
6
+ */
7
+ init: (state: unknown) => void;
8
+ /** Send a new action to the connection to display the state change in the extension.
9
+ * For example when the value of the store changes.
10
+ */
11
+ send: (action: Action, state: unknown) => void;
12
+ /** Add a subscription to the connection.
13
+ * The provided listener will be executed when the user interacts with the extension
14
+ * with actions like time traveling, importing a state or the likes.
15
+ *
16
+ * @param listener function to be executed when an action is submitted
17
+ * @returns function to unsubscribe the applied listener
18
+ */
19
+ subscribe: (listener: (message: Message) => void) => (() => void) | undefined;
20
+ }
21
+ /** Wrapper to create a new or get the existing connection to the redux extension
22
+ * Connections are used to display the stores value and value changes within the extension
23
+ * as well as reacting to extension actions like time traveling.
24
+ **/
25
+ export declare const getReduxConnection: (name: string) => ConnectionResponse | null | undefined;
@@ -0,0 +1,28 @@
1
+ import { ExtensionOptions } from "./ExtensionOptions";
2
+ import { ConnectionResponse } from "./getReduxConnection";
3
+ interface Config extends ExtensionOptions {
4
+ type?: string;
5
+ }
6
+ export interface ReduxDevtoolsExtension {
7
+ /** Create a connection to the extension.
8
+ * This will connect a store (like an atom) to the extension and
9
+ * display it within the extension tab.
10
+ *
11
+ * @param options https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/API/Arguments.md
12
+ * @returns https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/API/Methods.md#connectoptions
13
+ */
14
+ connect: (config: Config) => ConnectionResponse;
15
+ /** Disconnects all existing connections to the redux extension.
16
+ * Only use this when you are sure that no other connection exists
17
+ * or you want to remove all existing connections.
18
+ */
19
+ disconnect?: () => void;
20
+ }
21
+ declare global {
22
+ interface Window {
23
+ __REDUX_DEVTOOLS_EXTENSION__?: ReduxDevtoolsExtension;
24
+ }
25
+ }
26
+ /** Returns the global redux extension object if available */
27
+ export declare const getReduxExtension: () => ReduxDevtoolsExtension | null;
28
+ export {};
@@ -0,0 +1,4 @@
1
+ export * from "./getReduxConnection";
2
+ export * from "./getReduxExtension";
3
+ export * from "./ExtensionOptions";
4
+ export * from "./Message";
@@ -0,0 +1,17 @@
1
+ import { Atom } from "@yaasl/core";
2
+ import { ConnectionResponse } from "./redux-devtools";
3
+ export declare const connectAtom: (connection: ConnectionResponse, atom: Atom<any>) => void;
4
+ export interface ReduxDevtoolsOptions {
5
+ /** Disables the middleware. Useful for production. */
6
+ disable?: boolean;
7
+ }
8
+ /** Middleware to make use of the
9
+ * [redux devtools](https://github.com/reduxjs/redux-devtools)
10
+ * browser extension.
11
+ *
12
+ * @param options.disable Disables the middleware. Useful for production.
13
+ *
14
+ * @returns The middleware to be used on atoms.
15
+ **/
16
+ export declare const reduxDevtools: (...[optionsArg]: [] | [undefined] | [ReduxDevtoolsOptions]) => import("../../core/dist/@types/middleware/middleware").MiddlewareAtomCallback<ReduxDevtoolsOptions | undefined>;
17
+ export declare const disconnectAllConnections: () => void;
@@ -0,0 +1,7 @@
1
+ import { Atom } from "@yaasl/core";
2
+ export declare const cache: {
3
+ getAtomValue: (atom: Atom) => unknown;
4
+ setAtomValue: (atom: Atom, value: unknown) => unknown;
5
+ getStore: () => Record<string, unknown>;
6
+ reset: () => {};
7
+ };
@@ -0,0 +1,4 @@
1
+ import { Atom } from "@yaasl/core";
2
+ import { ConnectionResponse } from "../redux-devtools";
3
+ export declare const subscribeStore: (atom: Atom, connection: ConnectionResponse) => void;
4
+ export declare const resetSubscriptions: () => void;
@@ -0,0 +1,5 @@
1
+ export declare const updates: {
2
+ pause: () => boolean;
3
+ resume: () => boolean;
4
+ isPaused: () => boolean;
5
+ };
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.reduxDevtools = void 0;
4
+ var reduxDevtools_1 = require("./reduxDevtools");
5
+ Object.defineProperty(exports, "reduxDevtools", { enumerable: true, get: function () { return reduxDevtools_1.reduxDevtools; } });
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getReduxConnection = void 0;
4
+ const getReduxExtension_1 = require("./getReduxExtension");
5
+ const connections = {};
6
+ /** Wrapper to create a new or get the existing connection to the redux extension
7
+ * Connections are used to display the stores value and value changes within the extension
8
+ * as well as reacting to extension actions like time traveling.
9
+ **/
10
+ const getReduxConnection = (name) => {
11
+ const existing = connections[name];
12
+ if (existing)
13
+ return existing;
14
+ const extension = (0, getReduxExtension_1.getReduxExtension)();
15
+ if (!extension)
16
+ return null;
17
+ connections[name] = extension.connect({ name });
18
+ return connections[name];
19
+ };
20
+ exports.getReduxConnection = getReduxConnection;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getReduxExtension = void 0;
4
+ const utils_1 = require("@yaasl/utils");
5
+ let didWarn = false;
6
+ /** Returns the global redux extension object if available */
7
+ const getReduxExtension = () => {
8
+ const reduxExtension = window.__REDUX_DEVTOOLS_EXTENSION__;
9
+ if (!reduxExtension) {
10
+ if (!didWarn) {
11
+ didWarn = true;
12
+ utils_1.log.warn("Redux devtools extension was not found");
13
+ }
14
+ return null;
15
+ }
16
+ return reduxExtension;
17
+ };
18
+ exports.getReduxExtension = getReduxExtension;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./getReduxConnection"), exports);
18
+ __exportStar(require("./getReduxExtension"), exports);
19
+ __exportStar(require("./ExtensionOptions"), exports);
20
+ __exportStar(require("./Message"), exports);
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.disconnectAllConnections = exports.reduxDevtools = exports.connectAtom = void 0;
4
+ const core_1 = require("@yaasl/core");
5
+ const redux_devtools_1 = require("./redux-devtools");
6
+ const cache_1 = require("./utils/cache");
7
+ const subscribeStore_1 = require("./utils/subscribeStore");
8
+ const updates_1 = require("./utils/updates");
9
+ const getKey = () => { var _a; return (_a = core_1.CONFIG.name) !== null && _a !== void 0 ? _a : "yaasl"; };
10
+ let isInitPhase = true;
11
+ const connectAtom = (connection, atom) => {
12
+ cache_1.cache.setAtomValue(atom, atom.get());
13
+ if (isInitPhase) {
14
+ connection.init(cache_1.cache.getStore());
15
+ (0, subscribeStore_1.subscribeStore)(atom, connection);
16
+ }
17
+ else {
18
+ connection.send({ type: `SET/${atom.name}` }, cache_1.cache.getStore());
19
+ }
20
+ };
21
+ exports.connectAtom = connectAtom;
22
+ /** Middleware to make use of the
23
+ * [redux devtools](https://github.com/reduxjs/redux-devtools)
24
+ * browser extension.
25
+ *
26
+ * @param options.disable Disables the middleware. Useful for production.
27
+ *
28
+ * @returns The middleware to be used on atoms.
29
+ **/
30
+ exports.reduxDevtools = (0, core_1.middleware)(({ atom, options = {} }) => {
31
+ if (options.disable)
32
+ return {};
33
+ const connection = (0, redux_devtools_1.getReduxConnection)(getKey());
34
+ if (connection == null)
35
+ return {};
36
+ (0, exports.connectAtom)(connection, atom);
37
+ return {
38
+ set: ({ atom, value }) => {
39
+ isInitPhase = false;
40
+ if (updates_1.updates.isPaused())
41
+ return;
42
+ cache_1.cache.setAtomValue(atom, value);
43
+ connection.send({ type: `SET/${atom.name}` }, cache_1.cache.getStore());
44
+ },
45
+ };
46
+ });
47
+ /* For internal testing only */
48
+ const disconnectAllConnections = () => {
49
+ cache_1.cache.reset();
50
+ (0, subscribeStore_1.resetSubscriptions)();
51
+ isInitPhase = true;
52
+ };
53
+ exports.disconnectAllConnections = disconnectAllConnections;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cache = void 0;
4
+ let storeCache = {};
5
+ const setAtomValue = (atom, value) => (storeCache[atom.name] = value);
6
+ const getAtomValue = (atom) => storeCache[atom.name];
7
+ exports.cache = {
8
+ getAtomValue,
9
+ setAtomValue,
10
+ getStore: () => storeCache,
11
+ reset: () => (storeCache = {}),
12
+ };
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resetSubscriptions = exports.subscribeStore = void 0;
4
+ const cache_1 = require("./cache");
5
+ const updates_1 = require("./updates");
6
+ let observedAtoms = new Set();
7
+ const synchronize = (state) => {
8
+ updates_1.updates.pause();
9
+ Array.from(observedAtoms).forEach(atom => {
10
+ const atomName = atom.name;
11
+ if (!(atomName in state))
12
+ return;
13
+ const newValue = state[atomName];
14
+ const cachedValue = cache_1.cache.getAtomValue(atom);
15
+ if (newValue === cachedValue)
16
+ return;
17
+ cache_1.cache.setAtomValue(atom, newValue);
18
+ atom.set(newValue);
19
+ });
20
+ updates_1.updates.resume();
21
+ };
22
+ const getDefaultState = () => Array.from(observedAtoms).reduce((result, atom) => {
23
+ result[atom.name] = atom.defaultValue;
24
+ return result;
25
+ }, {});
26
+ let didInit = false;
27
+ const subscribeStore = (atom, connection) => {
28
+ observedAtoms.add(atom);
29
+ if (didInit)
30
+ return;
31
+ didInit = true;
32
+ connection.subscribe(action => {
33
+ const { payload } = action;
34
+ const nextState = !action.state
35
+ ? null
36
+ : JSON.parse(action.state);
37
+ switch (payload === null || payload === void 0 ? void 0 : payload.type) {
38
+ case "COMMIT":
39
+ connection.init(cache_1.cache.getStore());
40
+ break;
41
+ case "JUMP_TO_ACTION":
42
+ case "ROLLBACK":
43
+ if (!nextState)
44
+ return;
45
+ synchronize(nextState);
46
+ break;
47
+ case "RESET":
48
+ synchronize(getDefaultState());
49
+ connection.send({ type: `RESET_ATOMS` }, cache_1.cache.getStore());
50
+ break;
51
+ case "IMPORT_STATE":
52
+ payload.nextLiftedState.computedStates.forEach(({ state }) => {
53
+ synchronize(state);
54
+ connection.send({ type: `IMPORT_STORE` }, cache_1.cache.getStore());
55
+ });
56
+ break;
57
+ }
58
+ });
59
+ };
60
+ exports.subscribeStore = subscribeStore;
61
+ const resetSubscriptions = () => {
62
+ didInit = false;
63
+ observedAtoms = new Set();
64
+ };
65
+ exports.resetSubscriptions = resetSubscriptions;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.updates = void 0;
4
+ let paused = false;
5
+ exports.updates = {
6
+ pause: () => (paused = true),
7
+ resume: () => (paused = false),
8
+ isPaused: () => paused,
9
+ };
@@ -0,0 +1 @@
1
+ export { reduxDevtools } from "./reduxDevtools";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ import { getReduxExtension } from "./getReduxExtension";
2
+ const connections = {};
3
+ /** Wrapper to create a new or get the existing connection to the redux extension
4
+ * Connections are used to display the stores value and value changes within the extension
5
+ * as well as reacting to extension actions like time traveling.
6
+ **/
7
+ export const getReduxConnection = (name) => {
8
+ const existing = connections[name];
9
+ if (existing)
10
+ return existing;
11
+ const extension = getReduxExtension();
12
+ if (!extension)
13
+ return null;
14
+ connections[name] = extension.connect({ name });
15
+ return connections[name];
16
+ };
@@ -0,0 +1,14 @@
1
+ import { log } from "@yaasl/utils";
2
+ let didWarn = false;
3
+ /** Returns the global redux extension object if available */
4
+ export const getReduxExtension = () => {
5
+ const reduxExtension = window.__REDUX_DEVTOOLS_EXTENSION__;
6
+ if (!reduxExtension) {
7
+ if (!didWarn) {
8
+ didWarn = true;
9
+ log.warn("Redux devtools extension was not found");
10
+ }
11
+ return null;
12
+ }
13
+ return reduxExtension;
14
+ };
@@ -0,0 +1,4 @@
1
+ export * from "./getReduxConnection";
2
+ export * from "./getReduxExtension";
3
+ export * from "./ExtensionOptions";
4
+ export * from "./Message";
@@ -0,0 +1,48 @@
1
+ import { CONFIG, middleware } from "@yaasl/core";
2
+ import { getReduxConnection } from "./redux-devtools";
3
+ import { cache } from "./utils/cache";
4
+ import { resetSubscriptions, subscribeStore } from "./utils/subscribeStore";
5
+ import { updates } from "./utils/updates";
6
+ const getKey = () => CONFIG.name ?? "yaasl";
7
+ let isInitPhase = true;
8
+ export const connectAtom = (connection, atom) => {
9
+ cache.setAtomValue(atom, atom.get());
10
+ if (isInitPhase) {
11
+ connection.init(cache.getStore());
12
+ subscribeStore(atom, connection);
13
+ }
14
+ else {
15
+ connection.send({ type: `SET/${atom.name}` }, cache.getStore());
16
+ }
17
+ };
18
+ /** Middleware to make use of the
19
+ * [redux devtools](https://github.com/reduxjs/redux-devtools)
20
+ * browser extension.
21
+ *
22
+ * @param options.disable Disables the middleware. Useful for production.
23
+ *
24
+ * @returns The middleware to be used on atoms.
25
+ **/
26
+ export const reduxDevtools = middleware(({ atom, options = {} }) => {
27
+ if (options.disable)
28
+ return {};
29
+ const connection = getReduxConnection(getKey());
30
+ if (connection == null)
31
+ return {};
32
+ connectAtom(connection, atom);
33
+ return {
34
+ set: ({ atom, value }) => {
35
+ isInitPhase = false;
36
+ if (updates.isPaused())
37
+ return;
38
+ cache.setAtomValue(atom, value);
39
+ connection.send({ type: `SET/${atom.name}` }, cache.getStore());
40
+ },
41
+ };
42
+ });
43
+ /* For internal testing only */
44
+ export const disconnectAllConnections = () => {
45
+ cache.reset();
46
+ resetSubscriptions();
47
+ isInitPhase = true;
48
+ };
@@ -0,0 +1,9 @@
1
+ let storeCache = {};
2
+ const setAtomValue = (atom, value) => (storeCache[atom.name] = value);
3
+ const getAtomValue = (atom) => storeCache[atom.name];
4
+ export const cache = {
5
+ getAtomValue,
6
+ setAtomValue,
7
+ getStore: () => storeCache,
8
+ reset: () => (storeCache = {}),
9
+ };
@@ -0,0 +1,60 @@
1
+ import { cache } from "./cache";
2
+ import { updates } from "./updates";
3
+ let observedAtoms = new Set();
4
+ const synchronize = (state) => {
5
+ updates.pause();
6
+ Array.from(observedAtoms).forEach(atom => {
7
+ const atomName = atom.name;
8
+ if (!(atomName in state))
9
+ return;
10
+ const newValue = state[atomName];
11
+ const cachedValue = cache.getAtomValue(atom);
12
+ if (newValue === cachedValue)
13
+ return;
14
+ cache.setAtomValue(atom, newValue);
15
+ atom.set(newValue);
16
+ });
17
+ updates.resume();
18
+ };
19
+ const getDefaultState = () => Array.from(observedAtoms).reduce((result, atom) => {
20
+ result[atom.name] = atom.defaultValue;
21
+ return result;
22
+ }, {});
23
+ let didInit = false;
24
+ export const subscribeStore = (atom, connection) => {
25
+ observedAtoms.add(atom);
26
+ if (didInit)
27
+ return;
28
+ didInit = true;
29
+ connection.subscribe(action => {
30
+ const { payload } = action;
31
+ const nextState = !action.state
32
+ ? null
33
+ : JSON.parse(action.state);
34
+ switch (payload?.type) {
35
+ case "COMMIT":
36
+ connection.init(cache.getStore());
37
+ break;
38
+ case "JUMP_TO_ACTION":
39
+ case "ROLLBACK":
40
+ if (!nextState)
41
+ return;
42
+ synchronize(nextState);
43
+ break;
44
+ case "RESET":
45
+ synchronize(getDefaultState());
46
+ connection.send({ type: `RESET_ATOMS` }, cache.getStore());
47
+ break;
48
+ case "IMPORT_STATE":
49
+ payload.nextLiftedState.computedStates.forEach(({ state }) => {
50
+ synchronize(state);
51
+ connection.send({ type: `IMPORT_STORE` }, cache.getStore());
52
+ });
53
+ break;
54
+ }
55
+ });
56
+ };
57
+ export const resetSubscriptions = () => {
58
+ didInit = false;
59
+ observedAtoms = new Set();
60
+ };
@@ -0,0 +1,6 @@
1
+ let paused = false;
2
+ export const updates = {
3
+ pause: () => (paused = true),
4
+ resume: () => (paused = false),
5
+ isPaused: () => paused,
6
+ };
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@yaasl/devtools",
3
+ "version": "0.7.0-alpha.0",
4
+ "description": "yaasl redux-devtools-extension middleware",
5
+ "author": "PrettyCoffee",
6
+ "license": "MIT",
7
+ "homepage": "https://github.com/PrettyCoffee/yaasl#readme",
8
+ "keywords": [
9
+ "devtools"
10
+ ],
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/PrettyCoffee/yaasl.git"
14
+ },
15
+ "bugs": {
16
+ "url": "https://github.com/PrettyCoffee/yaasl/issues"
17
+ },
18
+ "main": "./dist/cjs/index.js",
19
+ "types": "./dist/@types/index.d.ts",
20
+ "exports": {
21
+ "types": "./dist/@types/index.d.ts",
22
+ "import": "./dist/mjs/index.js",
23
+ "require": "./dist/cjs/index.js"
24
+ },
25
+ "scripts": {
26
+ "build": "run-s clean compile",
27
+ "clean": "rimraf ./dist",
28
+ "compile": "run-p compile:mjs compile:cjs compile:types",
29
+ "compile:mjs": "tsc -p ./tsconfig.node.json --outDir ./dist/mjs -m es2020 -t es2020",
30
+ "compile:cjs": "tsc -p ./tsconfig.node.json --outDir ./dist/cjs -m commonjs -t es2015",
31
+ "compile:types": "tsc -p ./tsconfig.node.json --outDir ./dist/@types --declaration --emitDeclarationOnly",
32
+ "test": "jest --testMatch ./**/*.test.*",
33
+ "test:watch": "npm run test -- --watchAll",
34
+ "lint": "eslint ./src",
35
+ "lint:fix": "eslint ./src --fix",
36
+ "validate": "run-s lint test build"
37
+ },
38
+ "dependencies": {
39
+ "@yaasl/core": "0.7.0-alpha.0",
40
+ "@yaasl/utils": "0.7.0-alpha.0"
41
+ },
42
+ "jest": {
43
+ "preset": "ts-jest",
44
+ "testEnvironment": "jsdom"
45
+ },
46
+ "eslintConfig": {
47
+ "extends": [
48
+ "../../.eslintrc"
49
+ ]
50
+ },
51
+ "lint-staged": {
52
+ "*.{ts,tsx}": [
53
+ "npm run lint:fix"
54
+ ]
55
+ }
56
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "include": ["./src"],
4
+ "exclude": ["./**/*.test.ts"],
5
+ "compilerOptions": {
6
+ "declaration": false,
7
+ "noEmit": false,
8
+ }
9
+ }