├── .eslintignore
├── usage
├── .babelrc
├── index.html
├── api
│ └── session.ts
├── pages
│ └── home.ts
├── vite.config.js
├── features
│ └── session.ts
├── tsconfig.json
└── index.ts
├── .babelrc
├── .commitlintrc.json
├── .huskyrc
├── .prettierrc
├── .github
├── pr-labeler.yml
├── workflows
│ ├── release-drafter.yml
│ ├── test.yml
│ └── publish.yml
├── FUNDING.yml
└── release-drafter.yml
├── .editorconfig
├── .eslintrc.json
├── .all-contributorsrc
├── tsconfig.json
├── src
├── lib.ts
├── index.ts
├── logger.ts
└── basic.test.ts
├── babel.config.js
├── package.json
├── .gitignore
├── scripts
└── build.ts
├── __snapshots__
├── babel-plugin.test.js.snap
└── macro.test.js.snap
└── README.md
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist
2 |
--------------------------------------------------------------------------------
/usage/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["effector/babel-plugin"]
3 | }
4 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [["effector/babel-plugin", { "addLoc": true, "addNames": true }]]
3 | }
4 |
--------------------------------------------------------------------------------
/.commitlintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "@commitlint/config-conventional"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/.huskyrc:
--------------------------------------------------------------------------------
1 | {
2 | "hooks": {
3 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "semi": true,
4 | "singleQuote": true,
5 | "trailingComma": "all",
6 | "arrowParens": "always",
7 | "tabWidth": 2,
8 | "useTabs": false
9 | }
10 |
--------------------------------------------------------------------------------
/.github/pr-labeler.yml:
--------------------------------------------------------------------------------
1 | feature: ['feature/*', 'feat/*', 'add-', 'create-']
2 | test: ['test/*', 'tests/*']
3 | fix: ['fix/*', 'hotfix/*', 'bugfix/*', 'fix-']
4 | chore: chore/*
5 | refactor: ['refactor/*', 'refactoring/*']
6 |
--------------------------------------------------------------------------------
/usage/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Effector Inspector Usage App
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/usage/api/session.ts:
--------------------------------------------------------------------------------
1 | import { createDomain } from 'effector';
2 |
3 | const api = createDomain();
4 |
5 | export const sessionFetch = api.createEffect({
6 | handler: (params) => ({ id: 100000 + params }),
7 | });
8 |
--------------------------------------------------------------------------------
/usage/pages/home.ts:
--------------------------------------------------------------------------------
1 | import { createEvent, forward } from 'effector';
2 | import { sessionFetch } from '../api/session';
3 |
4 | export const pageMounted = createEvent();
5 |
6 | forward({
7 | from: pageMounted,
8 | to: sessionFetch,
9 | });
10 |
--------------------------------------------------------------------------------
/usage/vite.config.js:
--------------------------------------------------------------------------------
1 | import { babel } from '@rollup/plugin-babel';
2 | import { defineConfig } from 'vite';
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [babel({ extensions: ['.ts'], babelHelpers: 'bundled' })],
7 | });
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_size = 2
7 | indent_style = space
8 | insert_final_newline = true
9 | max_line_length = 80
10 | trim_trailing_whitespace = true
11 |
12 | [*.md]
13 | max_line_length = 0
14 | trim_trailing_whitespace = false
--------------------------------------------------------------------------------
/.github/workflows/release-drafter.yml:
--------------------------------------------------------------------------------
1 | name: Release Drafter
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | workflow_dispatch:
8 |
9 | jobs:
10 | update_release_draft:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: release-drafter/release-drafter@v5
14 | env:
15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
16 |
--------------------------------------------------------------------------------
/usage/features/session.ts:
--------------------------------------------------------------------------------
1 | import { createStore } from 'effector';
2 | import { sessionFetch } from '../api/session';
3 |
4 | export const $session = createStore(null);
5 |
6 | $session.on(sessionFetch.doneData, (_, result) => result);
7 |
8 | setTimeout(() => {
9 | const $asyncLoadedStore = createStore('some string');
10 |
11 | $asyncLoadedStore.on(sessionFetch.doneData, (result) => `${Math.random()}`);
12 | }, 1500);
13 |
--------------------------------------------------------------------------------
/usage/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": false,
4 | "allowSyntheticDefaultImports": true,
5 | "esModuleInterop": true,
6 | "forceConsistentCasingInFileNames": true,
7 | "isolatedModules": false,
8 | "jsx": "react",
9 | "lib": ["dom", "dom.iterable", "esnext"],
10 | "module": "esnext",
11 | "moduleResolution": "node",
12 | "resolveJsonModule": true,
13 | "skipLibCheck": true,
14 | "strict": true,
15 | "baseUrl": ".",
16 | "rootDir": "."
17 | },
18 | "include": ["./src"]
19 | }
20 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: sergeysova
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: ['https://www.buymeacoffee.com/sergeysova', 'https://vk.com/sovadev']
13 |
--------------------------------------------------------------------------------
/usage/index.ts:
--------------------------------------------------------------------------------
1 | import { fork, scopeBind } from 'effector';
2 | import { attachLogger } from '../src';
3 |
4 | import { $session } from './features/session';
5 | import { pageMounted } from './pages/home';
6 |
7 | const scope = fork({
8 | values: [[$session, { id: 'lol' }]],
9 | });
10 |
11 | attachLogger({
12 | scope,
13 | });
14 |
15 | attachLogger({
16 | name: 'my-cool-app',
17 | });
18 |
19 | setTimeout(pageMounted, 500, Math.floor(Math.random() * 3000));
20 | setTimeout(pageMounted, 1000, Math.floor(Math.random() * 3000));
21 | setTimeout(pageMounted, 1500, Math.floor(Math.random() * 3000));
22 | setTimeout(pageMounted, 2000, Math.floor(Math.random() * 3000));
23 | setTimeout(scopeBind(pageMounted, { scope }), 2500, Math.floor(Math.random() * 3000));
24 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push]
4 |
5 | jobs:
6 | test-package:
7 | name: Test on Node v${{ matrix.node }} and ${{ matrix.os }}
8 |
9 | runs-on: ${{ matrix.os }}
10 | strategy:
11 | matrix:
12 | node: ['16.x', '18.x', '20.x']
13 | os: [ubuntu-latest, macOS-latest]
14 |
15 | steps:
16 | - name: 🛎️ Checkout
17 | uses: actions/checkout@v3
18 |
19 | - name: 📦 Setup pnpm
20 | uses: pnpm/action-setup@v2
21 |
22 | - name: 🐧 Use Node.js ${{ matrix.node }}
23 | uses: actions/setup-node@v3
24 | with:
25 | node-version: ${{ matrix.node }}
26 | cache: 'pnpm'
27 |
28 | - name: 🔍 Install dependencies
29 | run: pnpm install
30 |
31 | - name: 🔧 Build package
32 | run: pnpm build
33 |
34 | - name: 🧪 Test
35 | run: pnpm test
36 | env:
37 | CI: true
38 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "es2017": true,
6 | "node": true
7 | },
8 | "extends": ["eslint:recommended"],
9 | "rules": {
10 | "import/extensions": "off",
11 | "@typescript-eslint/explicit-function-return-type": "off",
12 | "no-unused-vars": "warn"
13 | },
14 | "parser": "@babel/eslint-parser",
15 | "overrides": [
16 | {
17 | "files": ["*.ts", "*.tsx"],
18 | "extends": [
19 | "plugin:@typescript-eslint/eslint-recommended",
20 | "plugin:@typescript-eslint/recommended",
21 | "plugin:@typescript-eslint/recommended-requiring-type-checking"
22 | ],
23 | "parser": "@typescript-eslint/parser",
24 | "parserOptions": {
25 | "project": "tsconfig.json",
26 | "createDefaultProgram": true,
27 | "tsconfigRootDir": "./"
28 | },
29 | "plugins": ["@typescript-eslint"]
30 | }
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/.github/release-drafter.yml:
--------------------------------------------------------------------------------
1 | categories:
2 | - title: '⚠️ Breaking changes'
3 | label: 'BREAKING CHANGES'
4 |
5 | - title: '🚀 Features'
6 | labels:
7 | - 'feature'
8 | - 'enhancement'
9 |
10 | - title: '🐛 Bug Fixes'
11 | labels:
12 | - 'fix'
13 | - 'bugfix'
14 | - 'bug'
15 |
16 | - title: '🧰 Maintenance'
17 | labels:
18 | - 'chore'
19 | - 'dependencies'
20 |
21 | - title: '📚 Documentation'
22 | label: 'documentation'
23 |
24 | - title: '🧪 Tests'
25 | label: 'tests'
26 |
27 | - title: '🏎 Optimizations'
28 | label: 'optimizations'
29 |
30 | version-resolver:
31 | major:
32 | labels:
33 | - 'BREAKING CHANGES'
34 | minor:
35 | labels:
36 | - 'feature'
37 | - 'enhancement'
38 | patch:
39 | labels:
40 | - 'fix'
41 | default: patch
42 |
43 | name-template: 'v$RESOLVED_VERSION'
44 | tag-template: 'v$RESOLVED_VERSION'
45 |
46 | change-template: '- $TITLE #$NUMBER (@$AUTHOR)'
47 | template: |
48 | $CHANGES
49 |
--------------------------------------------------------------------------------
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "files": [
3 | "README.md"
4 | ],
5 | "imageSize": 100,
6 | "commit": false,
7 | "contributors": [
8 | {
9 | "login": "Laiff",
10 | "name": "Andrey Antropov",
11 | "avatar_url": "https://avatars0.githubusercontent.com/u/575885?v=4",
12 | "profile": "https://github.com/Laiff",
13 | "contributions": [
14 | "code"
15 | ]
16 | },
17 | {
18 | "login": "sergeysova",
19 | "name": "Sergey Sova",
20 | "avatar_url": "https://avatars0.githubusercontent.com/u/5620073?v=4",
21 | "profile": "https://sova.dev",
22 | "contributions": [
23 | "code"
24 | ]
25 | },
26 | {
27 | "login": "Sozonov",
28 | "name": "Sozonov",
29 | "avatar_url": "https://avatars2.githubusercontent.com/u/1931637?v=4",
30 | "profile": "https://github.com/Sozonov",
31 | "contributions": [
32 | "code"
33 | ]
34 | }
35 | ],
36 | "contributorsPerLine": 7,
37 | "projectName": "effector-logger",
38 | "projectOwner": "sergeysova",
39 | "repoType": "github",
40 | "repoHost": "https://github.com",
41 | "skipCi": true
42 | }
43 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": false,
4 | "allowSyntheticDefaultImports": true,
5 | "esModuleInterop": true,
6 | "forceConsistentCasingInFileNames": true,
7 | "isolatedModules": false,
8 | "jsx": "react",
9 | "lib": ["dom", "dom.iterable", "esnext"],
10 | "module": "esnext",
11 | "moduleResolution": "node",
12 | "resolveJsonModule": true,
13 | "skipLibCheck": true,
14 | "strict": true,
15 | "target": "es6",
16 | "declaration": true,
17 | "emitDeclarationOnly": true,
18 | "declarationDir": "./dist",
19 | "baseUrl": "./",
20 | "rootDir": "./src",
21 | "outDir": "./dist",
22 | "types": ["node", "jest"]
23 | },
24 | "exclude": ["./dist"],
25 | "include": ["./src/index.ts"],
26 | "ts-node": {
27 | // It is faster to skip typechecking.
28 | // Remove if you want ts-node to do typechecking.
29 | "transpileOnly": true,
30 |
31 | "compilerOptions": {
32 | // compilerOptions specified here will override those declared below,
33 | // but *only* in ts-node. Useful if you want ts-node and tsc to use
34 | // different options with a single tsconfig.json.
35 | "module": "CommonJS"
36 | }
37 | },
38 | }
39 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish CI
2 |
3 | on:
4 | release:
5 | types: [published]
6 |
7 | jobs:
8 | publish-to-npm:
9 | runs-on: ubuntu-22.04
10 | steps:
11 | - name: 🛎️ Checkout
12 | uses: actions/checkout@v3
13 |
14 | - name: 📦 Setup pnpm
15 | uses: pnpm/action-setup@v2
16 |
17 | - name: 🐧 Use Node.js v20.x
18 | uses: actions/setup-node@v3
19 | with:
20 | node-version: v20.x
21 | cache: 'pnpm'
22 |
23 | - name: 🔍 Install dependencies
24 | run: pnpm install
25 |
26 | - name: 🔧 Build package
27 | run: pnpm build
28 |
29 | - name: 📜 Copy README
30 | run: cp ./README.md ./dist/
31 |
32 | - name: 🧪 Test
33 | run: pnpm test
34 | env:
35 | CI: true
36 |
37 | - name: 🔦 Extract version
38 | id: version
39 | uses: olegtarasov/get-tag@v2.1
40 | with:
41 | tagRegex: 'v(.*)'
42 |
43 | - name: 🥤 Set version from release
44 | uses: reedyuk/npm-version@1.1.1
45 | with:
46 | version: ${{ steps.version.outputs.tag }}
47 | git-tag-version: false
48 | package: 'dist/'
49 |
50 | - name: 📝 Create NPM config
51 | working-directory: './dist/'
52 | run: npm config set //registry.npmjs.org/:_authToken $NPM_TOKEN
53 | env:
54 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
55 |
56 | - name: 🚀 Publish package
57 | working-directory: './dist/'
58 | run: npm publish
59 |
--------------------------------------------------------------------------------
/src/lib.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/camelcase */
2 | /* eslint-disable @typescript-eslint/explicit-function-return-type */
3 | /* eslint-disable @typescript-eslint/no-explicit-any */
4 | /* eslint-disable @typescript-eslint/no-use-before-define */
5 | import type { Message, Declaration } from 'effector/inspect';
6 | import type { Unit, Node, Scope } from 'effector';
7 |
8 | export function getName(m: Message | Declaration, loggerName: string) {
9 | const finalName = m.name || locToString(m.loc) || `unknown_${m.id}`;
10 |
11 | if (!loggerName) return finalName;
12 |
13 | return `(${loggerName}) ${finalName}`;
14 | }
15 | export function locToString(loc: any) {
16 | if (!loc) return null;
17 | return `${loc.file}:${loc.line}:${loc.column}`;
18 | }
19 | export function getNode(unit: Unit) {
20 | return (unit as any).graphite as Node;
21 | }
22 |
23 | export function getStateFromDeclaration(d: Declaration, scope?: Scope) {
24 | if (scope) {
25 | return HACK_readStateFromScope(d, scope);
26 | }
27 |
28 | return d.meta.defaultState;
29 | }
30 |
31 | function HACK_readStateFromScope(d: Declaration, scope: Scope) {
32 | const stateRefId = d.meta.rootStateRefId as any;
33 |
34 | const scopeRef = (scope as any).reg[stateRefId];
35 |
36 | if (scopeRef) {
37 | return scopeRef.current;
38 | }
39 |
40 | if (stateRefId in (scope as any).values.idMap) {
41 | return { value: (scope as any).values.idMap[stateRefId] };
42 | }
43 |
44 | const sid = d.meta.sid as any;
45 |
46 | if (sid && sid in (scope as any).values.sidMap) {
47 | return (scope as any).values.sidMap[sid];
48 | }
49 |
50 | return d.meta.defaultState;
51 | }
52 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | const { resolve: resolvePath } = require('path');
2 |
3 | module.exports = (api) => {
4 | api && api.cache && api.cache.never && api.cache.never();
5 | // const env = api.cache(() => process.env.NODE_ENV)
6 | return generateConfig(meta, babelConfig);
7 | };
8 |
9 | const meta = {
10 | isEsm: true,
11 | };
12 |
13 | function generateConfig(meta, config = babelConfig) {
14 | const result = {};
15 | for (const key in config) {
16 | const value = config[key];
17 | result[key] = typeof value === 'function' ? value(meta) : value;
18 | }
19 | return result;
20 | }
21 |
22 | module.exports.generateConfig = generateConfig;
23 |
24 | const aliases = {};
25 |
26 | const babelConfig = {
27 | plugins(meta) {
28 | const alias = parseAliases(meta, aliases);
29 | const result = [
30 | ['effector/babel-plugin', { addLoc: true }],
31 | [
32 | 'babel-plugin-module-resolver',
33 | {
34 | alias,
35 | loglevel: 'silent',
36 | },
37 | ],
38 | ];
39 |
40 | return result;
41 | },
42 | };
43 |
44 | function parseAliases(meta, obj) {
45 | const result = {};
46 | for (const key in obj) {
47 | const value = obj[key];
48 | if (typeof value === 'function') {
49 | const name = value(meta);
50 | if (name === undefined || name === null) continue;
51 | result[key] = name;
52 | } else if (typeof value === 'object' && value !== null) {
53 | const name = applyPaths(value);
54 | if (name === undefined || name === null) continue;
55 | result[key] = name;
56 | } else {
57 | const name = value;
58 | if (name === undefined || name === null) continue;
59 | result[key] = name;
60 | }
61 | }
62 | return result;
63 |
64 | function applyPaths(paths) {
65 | if (meta.isEsm) return paths.esm;
66 | return paths.default;
67 | }
68 | }
69 |
70 | module.exports.getAliases = (metadata = meta) => parseAliases(metadata, aliases);
71 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "effector-logger",
3 | "version": "0.0.0-real-version-will-be-set-on-ci",
4 | "main": "./index.js",
5 | "module": "./index.mjs",
6 | "typings": "./index.d.ts",
7 | "license": "MIT",
8 | "repository": "github:effector/logger",
9 | "publishConfig": {
10 | "access": "public"
11 | },
12 | "scripts": {
13 | "start": "vite ./usage",
14 | "build": "rm -rf ./dist && ts-node ./scripts/build.ts",
15 | "lint": "eslint --ext .ts,.tsx src",
16 | "test": "vitest run",
17 | "commit": "git-cz",
18 | "prepublish": "pnpm build"
19 | },
20 | "packageManager": "pnpm@8.11.0",
21 | "peerDependencies": {
22 | "effector": "^22.8.8 || ^23.0.0"
23 | },
24 | "devDependencies": {
25 | "@babel/core": "^7.13.10",
26 | "@babel/eslint-parser": "^7.13.10",
27 | "@commitlint/cli": "8.2.0",
28 | "@commitlint/config-conventional": "8.2.0",
29 | "@rollup/plugin-babel": "^5.3.1",
30 | "@rollup/plugin-commonjs": "^20.0.0",
31 | "@rollup/plugin-node-resolve": "^13.0.4",
32 | "@rollup/plugin-typescript": "^8.2.5",
33 | "@types/babel__core": "^7.1.19",
34 | "@types/detect-node": "^2.0.0",
35 | "@types/jest": "^29.4.0",
36 | "@types/node": "^18.14.2",
37 | "@types/set-value": "^2.0.0",
38 | "@typescript-eslint/eslint-plugin": "^2.16.0",
39 | "@typescript-eslint/parser": "^2.16.0",
40 | "babel-plugin-module-resolver": "^4.1.0",
41 | "babel-plugin-tester": "^10.0.0",
42 | "commitizen": "4.0.3",
43 | "cz-conventional-changelog": "3.0.2",
44 | "effector": "^23.0.0",
45 | "eslint": "^6.6.0",
46 | "eslint-plugin-prettier": "^3.1.2",
47 | "husky": "3.1.0",
48 | "jest": "^29.3.1",
49 | "prettier": "^2.1.2",
50 | "rollup": "^2.79.1",
51 | "rollup-plugin-terser": "^7.0.2",
52 | "ts-node": "^10.4.0",
53 | "typescript": "^4.9.5",
54 | "vite": "^3.1.8",
55 | "vitest": "^0.31.0"
56 | },
57 | "dependencies": {
58 | "detect-node": "2.0.4",
59 | "just-debounce-it": "^1.1.0"
60 | },
61 | "config": {
62 | "commitizen": {
63 | "path": "cz-conventional-changelog"
64 | }
65 | },
66 | "files": [
67 | "index.js",
68 | "index.js.map",
69 | "index.mjs",
70 | "index.mjs.map",
71 | "index.d.ts"
72 | ],
73 | "exports": {
74 | ".": {
75 | "types": "./index.d.ts",
76 | "import": "./index.mjs",
77 | "require": "./index.js",
78 | "default": "./index.mjs"
79 | },
80 | "./index.mjs": "./index.mjs"
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### Git ###
2 | # Created by git for backups. To disable backups in Git:
3 | # $ git config --global mergetool.keepBackup false
4 | *.orig
5 |
6 | # Created by git when using merge tools for conflicts
7 | *.BACKUP.*
8 | *.BASE.*
9 | *.LOCAL.*
10 | *.REMOTE.*
11 | *_BACKUP_*.txt
12 | *_BASE_*.txt
13 | *_LOCAL_*.txt
14 | *_REMOTE_*.txt
15 |
16 | ### macOS ###
17 | # General
18 | .DS_Store
19 | .AppleDouble
20 | .LSOverride
21 |
22 | # Icon must end with two \r
23 | Icon
24 |
25 | # Thumbnails
26 | ._*
27 |
28 | # Files that might appear in the root of a volume
29 | .DocumentRevisions-V100
30 | .fseventsd
31 | .Spotlight-V100
32 | .TemporaryItems
33 | .Trashes
34 | .VolumeIcon.icns
35 | .com.apple.timemachine.donotpresent
36 |
37 | # Directories potentially created on remote AFP share
38 | .AppleDB
39 | .AppleDesktop
40 | Network Trash Folder
41 | Temporary Items
42 | .apdisk
43 |
44 | ### Node ###
45 | # Logs
46 | logs
47 | *.log
48 | npm-debug.log*
49 | yarn-debug.log*
50 | yarn-error.log*
51 | lerna-debug.log*
52 |
53 | # Diagnostic reports (https://nodejs.org/api/report.html)
54 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
55 |
56 | # Runtime data
57 | pids
58 | *.pid
59 | *.seed
60 | *.pid.lock
61 |
62 | # Directory for instrumented libs generated by jscoverage/JSCover
63 | lib-cov
64 |
65 | # Coverage directory used by tools like istanbul
66 | coverage
67 | *.lcov
68 |
69 | # nyc test coverage
70 | .nyc_output
71 |
72 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
73 | .grunt
74 |
75 | # Bower dependency directory (https://bower.io/)
76 | bower_components
77 |
78 | # node-waf configuration
79 | .lock-wscript
80 |
81 | # Compiled binary addons (https://nodejs.org/api/addons.html)
82 | build/Release
83 |
84 | # Dependency directories
85 | node_modules/
86 | jspm_packages/
87 |
88 | # TypeScript v1 declaration files
89 | typings/
90 |
91 | # TypeScript cache
92 | *.tsbuildinfo
93 |
94 | # Optional npm cache directory
95 | .npm
96 |
97 | # Optional eslint cache
98 | .eslintcache
99 |
100 | # Optional REPL history
101 | .node_repl_history
102 |
103 | # Output of 'npm pack'
104 | *.tgz
105 |
106 | # Yarn Integrity file
107 | .yarn-integrity
108 |
109 | # dotenv environment variables file
110 | .env
111 | .env.test
112 |
113 | # parcel-bundler cache (https://parceljs.org/)
114 | .cache
115 |
116 | # next.js build output
117 | .next
118 |
119 | # nuxt.js build output
120 | .nuxt
121 |
122 | # react / gatsby
123 | public/
124 |
125 | # vuepress build output
126 | .vuepress/dist
127 |
128 | # Serverless directories
129 | .serverless/
130 |
131 | # FuseBox cache
132 | .fusebox/
133 |
134 | # DynamoDB Local files
135 | .dynamodb/
136 |
137 | ### react ###
138 | .DS_*
139 | **/*.backup.*
140 | **/*.back.*
141 |
142 | node_modules
143 | bower_componets
144 |
145 | *.sublime*
146 |
147 | psd
148 | thumb
149 | sketch
150 |
151 | ### VisualStudioCode ###
152 | .vscode/*
153 | !.vscode/settings.json
154 | !.vscode/tasks.json
155 | !.vscode/launch.json
156 | !.vscode/extensions.json
157 |
158 | ### VisualStudioCode Patch ###
159 | # Ignore all local history of files
160 | .history
161 |
162 | storybook-static
163 | dist
164 | dist-test
165 | ### Jetbrains
166 |
167 | /.idea
168 |
169 | # End of https://www.gitignore.io/api/git,node,react,macos,visualstudiocode
170 |
171 | # Built package artifacts
172 | attach.js
173 | attach.js.map
174 | attach.mjs
175 | attach.mjs.map
176 | index.js
177 | index.js.map
178 | index.mjs
179 | index.mjs.map
180 | inspector.js
181 | inspector.js.map
182 | inspector.mjs
183 | inspector.mjs.map
184 |
--------------------------------------------------------------------------------
/scripts/build.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/explicit-function-return-type */
2 | /* eslint-disable @typescript-eslint/no-use-before-define */
3 | import fs from 'node:fs';
4 | import { promisify } from 'util';
5 | import { rollup, InputOptions, OutputOptions } from 'rollup';
6 | import { resolve } from 'path';
7 | import pluginResolve from '@rollup/plugin-node-resolve';
8 | import { babel } from '@rollup/plugin-babel';
9 | import commonjs from '@rollup/plugin-commonjs';
10 | import Package from '../package.json';
11 | import typescript from '@rollup/plugin-typescript';
12 | import { generateConfig } from '../babel.config';
13 |
14 | const DIR = 'dist/';
15 | const LIB_NAME = process.env.LIB_NAME ?? Package.name;
16 |
17 | async function build(): Promise {
18 | const configs = getAllConfigs();
19 |
20 | for (const config of configs) {
21 | await buildEntry(config);
22 | }
23 | const packageJson = JSON.parse(JSON.stringify(Package));
24 | packageJson.name = LIB_NAME;
25 | delete packageJson['scripts'];
26 | delete packageJson['config'];
27 | delete packageJson.devDependencies;
28 |
29 | await saveFile(`${DIR}package.json`, JSON.stringify(packageJson));
30 | }
31 |
32 | async function buildEntry(config: ReturnType) {
33 | console.log('building: ', `src/${config.name}.ts`, '->', config.format);
34 | const bundle = await rollup(config.input);
35 | console.log('saving: ', config.output.file);
36 | await bundle.write(config.output);
37 | console.log('');
38 | }
39 |
40 | const entrypoints = ['index'] as const;
41 | const formats = ['cjs', 'esm'] as const;
42 |
43 | function getAllConfigs() {
44 | const configs: ReturnType[] = [];
45 | entrypoints.forEach((name) => {
46 | formats.forEach((format) => {
47 | configs.push(
48 | getConfig({
49 | name,
50 | format,
51 | base: DIR,
52 | }),
53 | );
54 | });
55 | });
56 |
57 | return configs;
58 | }
59 |
60 | function getConfig(config: { name: string; format: 'esm' | 'cjs'; base: string }) {
61 | const { name, format, base } = config;
62 | const input = getInput(name, format);
63 | const output = getOutput(name, format, base);
64 |
65 | return {
66 | input,
67 | output,
68 | name,
69 | format,
70 | };
71 | }
72 |
73 | const extensions = ['.tsx', '.ts', '.js', '.json'];
74 | const external = [
75 | 'forest/forest.mjs',
76 | 'effector/effector.mjs',
77 | 'effector-inspector/index.mjs',
78 | ].concat(Object.keys(Package.peerDependencies), Object.keys(Package.dependencies));
79 |
80 | function getInput(name: string, format: 'esm' | 'cjs'): InputOptions {
81 | const input = resolve(__dirname, `../src/${name}.ts`);
82 | const plugins = [
83 | commonjs(),
84 | pluginResolve({ extensions }),
85 | typescript({
86 | tsconfig: './tsconfig.json',
87 | }),
88 | babel({
89 | babelHelpers: 'bundled',
90 | extensions,
91 | skipPreflightCheck: true,
92 | babelrc: false,
93 | ...generateConfig({
94 | isEsm: format === 'esm',
95 | }),
96 | }),
97 | ];
98 | return {
99 | input,
100 | plugins,
101 | external,
102 | };
103 | }
104 |
105 | function getOutput(name: string, format: 'esm' | 'cjs', base = ''): OutputOptions {
106 | const file = `${base}${name}.${format === 'esm' ? 'mjs' : 'js'}`;
107 | return {
108 | file,
109 | sourcemap: true,
110 | format,
111 | };
112 | }
113 |
114 | const saveFile = promisify(fs.writeFile);
115 |
116 | build()
117 | .then(() => {
118 | process.exit(0);
119 | })
120 | .catch((error) => {
121 | // eslint-disable-next-line no-console
122 | console.error(error);
123 | process.exit(-1);
124 | });
125 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/explicit-function-return-type */
2 | /* eslint-disable @typescript-eslint/no-explicit-any */
3 | /* eslint-disable @typescript-eslint/no-use-before-define */
4 | import { inspect, Message, inspectGraph } from 'effector/inspect';
5 | import type { Scope, Unit } from 'effector';
6 | import { is } from 'effector';
7 |
8 | import {
9 | storeUpdated,
10 | eventCalled,
11 | effectCalled,
12 | effectDone,
13 | effectFail,
14 | createDeclarationsLogger,
15 | } from './logger';
16 | import { getNode, getName, locToString } from './lib';
17 |
18 | const ignored = new Set();
19 | const forceLog = new Set();
20 | const fxRunning = new Map();
21 |
22 | export function attachLogger(config: { scope?: Scope; name?: string } = {}): () => void {
23 | const name = config.name || (config.scope ? `scope: ${getNode(config.scope).id}` : '');
24 |
25 | const logDeclarations = createDeclarationsLogger({
26 | name,
27 | scope: config.scope,
28 | });
29 |
30 | const uninspect = inspect({
31 | scope: config.scope || undefined,
32 | fn: (m) => {
33 | if (
34 | /**
35 | * Log only non-derived units by default
36 | */
37 | (isLoggable(m) && !ignored.has(m.id)) ||
38 | /**
39 | * Log any units if they are forced to be logged
40 | */
41 | forceLog.has(m.id)
42 | ) {
43 | log(m, name);
44 | }
45 | },
46 | });
47 |
48 | logDeclarations();
49 |
50 | const uninspectGraph = inspectGraph({
51 | fn: () => {
52 | logDeclarations();
53 | },
54 | });
55 |
56 | return () => {
57 | uninspect();
58 | uninspectGraph();
59 | };
60 | }
61 |
62 | export function configure(
63 | unitOrUnits: Unit | Unit[],
64 | config: { log: 'disabled' | 'enabled' },
65 | ): void {
66 | const units = Array.isArray(unitOrUnits) ? unitOrUnits : [unitOrUnits];
67 |
68 | if (config.log === 'disabled') {
69 | units.forEach((unit) => {
70 | ignored.add(getNode(unit).id);
71 |
72 | if (is.effect(unit)) {
73 | ignored.add(getNode(unit.finally).id);
74 | }
75 | });
76 | }
77 | if (config.log === 'enabled') {
78 | units.forEach((unit) => {
79 | forceLog.add(getNode(unit).id);
80 |
81 | if (is.effect(unit)) {
82 | forceLog.add(getNode(unit.finally).id);
83 | }
84 | });
85 | }
86 | }
87 |
88 | // utils
89 | function isLoggable(m: Message) {
90 | return isEffectFinally(m) || (!m.meta.derived && m.type === 'update');
91 | }
92 | function isEffectFinally(m: Message) {
93 | return m.kind === 'event' && m.meta.named === 'finally';
94 | }
95 | function log(m: Message, name: string) {
96 | if (m.kind === 'effect') {
97 | const fxName = getName(m, name);
98 |
99 | effectCalled(fxName, locToString(m.loc) || '', m.value);
100 | fxRunning.set((m.stack as any).fxID, fxName);
101 |
102 | return;
103 | }
104 | if (isEffectFinally(m)) {
105 | const fxName = fxRunning.get((m.stack as any).fxID);
106 | if (fxName) {
107 | fxRunning.delete((m.stack as any).fxID);
108 |
109 | const finallyValue = m.value as {
110 | status: 'fail' | 'done';
111 | params: any;
112 | result: any;
113 | error: any;
114 | };
115 |
116 | if (finallyValue.status === 'fail') {
117 | effectFail(fxName, locToString(m.loc) || '', finallyValue.params, finallyValue.error);
118 | }
119 | if (finallyValue.status === 'done') {
120 | effectDone(fxName, locToString(m.loc) || '', finallyValue.params, finallyValue.result);
121 | }
122 | }
123 |
124 | return;
125 | }
126 | if (m.kind === 'store') {
127 | storeUpdated(getName(m, name), locToString(m.loc) || '', m.value);
128 |
129 | return;
130 | }
131 | if (m.kind === 'event') {
132 | eventCalled(getName(m, name), locToString(m.loc) || '', m.value);
133 |
134 | return;
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/__snapshots__/babel-plugin.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`effector-logger/babel-plugin adds inspector: adds inspector 1`] = `
4 |
5 | import { createStore, createEvent, createEffect, attach, restore } from 'effector';
6 | const fx = createEffect(() => 1);
7 | const $data = createStore(0);
8 | const was = createEvent();
9 | const anotherFx = attach({
10 | effect: fx,
11 | source: $data,
12 | mapParams: (a) => a,
13 | })
14 | const $has = restore(was, "");
15 |
16 | ↓ ↓ ↓ ↓ ↓ ↓
17 |
18 | var _effectorFileName = '/babel-plugin.test.js';
19 | import 'effector-logger/inspector';
20 | import { createStore, createEvent, createEffect, attach, restore } from 'effector-logger';
21 | const fx = createEffect(() => 1, {
22 | loc: {
23 | file: _effectorFileName,
24 | line: 2,
25 | column: 11,
26 | },
27 | name: 'fx',
28 | sid: '-vy4jz4',
29 | });
30 | const $data = createStore(0, {
31 | loc: {
32 | file: _effectorFileName,
33 | line: 3,
34 | column: 14,
35 | },
36 | name: '$data',
37 | sid: 'ais0rc',
38 | });
39 | const was = createEvent({
40 | loc: {
41 | file: _effectorFileName,
42 | line: 4,
43 | column: 12,
44 | },
45 | name: 'was',
46 | sid: '-jc3cc',
47 | });
48 | const anotherFx = attach({
49 | ɔ: {
50 | effect: fx,
51 | source: $data,
52 | mapParams: (a) => a,
53 | },
54 | config: {
55 | loc: {
56 | file: _effectorFileName,
57 | line: 5,
58 | column: 18,
59 | },
60 | name: 'anotherFx',
61 | sid: '-o67sjr',
62 | },
63 | });
64 | const $has = restore(was, '', {
65 | loc: {
66 | file: _effectorFileName,
67 | line: 10,
68 | column: 13,
69 | },
70 | name: '$has',
71 | sid: '3zimrf',
72 | });
73 |
74 |
75 | `;
76 |
77 | exports[`effector-logger/babel-plugin adds loc and names: adds loc and names 1`] = `
78 |
79 | import { createStore, createEvent, createEffect, attach, restore } from 'effector';
80 | const fx = createEffect(() => 1);
81 | const $data = createStore(0);
82 | const was = createEvent();
83 | const anotherFx = attach({
84 | effect: fx,
85 | source: $data,
86 | mapParams: (a) => a,
87 | })
88 | const $has = restore(was, "");
89 |
90 | ↓ ↓ ↓ ↓ ↓ ↓
91 |
92 | var _effectorFileName = '/babel-plugin.test.js';
93 | import { createStore, createEvent, createEffect, attach, restore } from 'effector-logger';
94 | const fx = createEffect(() => 1, {
95 | loc: {
96 | file: _effectorFileName,
97 | line: 2,
98 | column: 11,
99 | },
100 | name: 'fx',
101 | sid: '-vy4jz4',
102 | });
103 | const $data = createStore(0, {
104 | loc: {
105 | file: _effectorFileName,
106 | line: 3,
107 | column: 14,
108 | },
109 | name: '$data',
110 | sid: 'ais0rc',
111 | });
112 | const was = createEvent({
113 | loc: {
114 | file: _effectorFileName,
115 | line: 4,
116 | column: 12,
117 | },
118 | name: 'was',
119 | sid: '-jc3cc',
120 | });
121 | const anotherFx = attach({
122 | ɔ: {
123 | effect: fx,
124 | source: $data,
125 | mapParams: (a) => a,
126 | },
127 | config: {
128 | loc: {
129 | file: _effectorFileName,
130 | line: 5,
131 | column: 18,
132 | },
133 | name: 'anotherFx',
134 | sid: '-o67sjr',
135 | },
136 | });
137 | const $has = restore(was, '', {
138 | loc: {
139 | file: _effectorFileName,
140 | line: 10,
141 | column: 13,
142 | },
143 | name: '$has',
144 | sid: '3zimrf',
145 | });
146 |
147 |
148 | `;
149 |
150 | exports[`effector-logger/babel-plugin replaces imports from effector: replaces imports from effector 1`] = `
151 |
152 | import { createStore, createEvent, createEffect, attach, restore } from 'effector';
153 | const fx = createEffect(() => 1);
154 | const $data = createStore(0);
155 | const was = createEvent();
156 | const anotherFx = attach({
157 | effect: fx,
158 | source: $data,
159 | mapParams: (a) => a,
160 | })
161 | const $has = restore(was, "");
162 |
163 | ↓ ↓ ↓ ↓ ↓ ↓
164 |
165 | var _effectorFileName = '/babel-plugin.test.js';
166 | import { createStore, createEvent, createEffect, attach, restore } from 'effector-logger';
167 | const fx = createEffect(() => 1, {
168 | loc: {
169 | file: _effectorFileName,
170 | line: 2,
171 | column: 11,
172 | },
173 | name: 'fx',
174 | sid: '-vy4jz4',
175 | });
176 | const $data = createStore(0, {
177 | loc: {
178 | file: _effectorFileName,
179 | line: 3,
180 | column: 14,
181 | },
182 | name: '$data',
183 | sid: 'ais0rc',
184 | });
185 | const was = createEvent({
186 | loc: {
187 | file: _effectorFileName,
188 | line: 4,
189 | column: 12,
190 | },
191 | name: 'was',
192 | sid: '-jc3cc',
193 | });
194 | const anotherFx = attach({
195 | ɔ: {
196 | effect: fx,
197 | source: $data,
198 | mapParams: (a) => a,
199 | },
200 | config: {
201 | loc: {
202 | file: _effectorFileName,
203 | line: 5,
204 | column: 18,
205 | },
206 | name: 'anotherFx',
207 | sid: '-o67sjr',
208 | },
209 | });
210 | const $has = restore(was, '', {
211 | loc: {
212 | file: _effectorFileName,
213 | line: 10,
214 | column: 13,
215 | },
216 | name: '$has',
217 | sid: '3zimrf',
218 | });
219 |
220 |
221 | `;
222 |
--------------------------------------------------------------------------------
/__snapshots__/macro.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`effector-logger/macro add sid and loc: add sid and loc 1`] = `
4 |
5 | import { createStore, createEvent, createEffect, attach, restore } from './macro';
6 | const fx = createEffect(() => 1);
7 | const $data = createStore(0);
8 | const was = createEvent();
9 | const anotherFx = attach({
10 | effect: fx,
11 | source: $data,
12 | mapParams: (a) => a,
13 | })
14 | const $has = restore(was, "");
15 |
16 | ↓ ↓ ↓ ↓ ↓ ↓
17 |
18 | var _effectorFileName = '/macro.test.js';
19 | import { restore as _restore } from 'effector-logger';
20 | import { attach as _attach } from 'effector-logger';
21 | import { createEffect as _createEffect } from 'effector-logger';
22 | import { createEvent as _createEvent } from 'effector-logger';
23 | import { createStore as _createStore } from 'effector-logger';
24 |
25 | const fx = _createEffect(() => 1, {
26 | loc: {
27 | file: _effectorFileName,
28 | line: 2,
29 | column: 11,
30 | },
31 | name: 'fx',
32 | sid: '-u6p3sc',
33 | });
34 |
35 | const $data = _createStore(0, {
36 | loc: {
37 | file: _effectorFileName,
38 | line: 3,
39 | column: 14,
40 | },
41 | name: '$data',
42 | sid: '8tv02c',
43 | });
44 |
45 | const was = _createEvent({
46 | loc: {
47 | file: _effectorFileName,
48 | line: 4,
49 | column: 12,
50 | },
51 | name: 'was',
52 | sid: '-3z5gxy',
53 | });
54 |
55 | const anotherFx = _attach({
56 | ɔ: {
57 | effect: fx,
58 | source: $data,
59 | mapParams: (a) => a,
60 | },
61 | config: {
62 | loc: {
63 | file: _effectorFileName,
64 | line: 5,
65 | column: 18,
66 | },
67 | name: 'anotherFx',
68 | sid: '-4ce2k9',
69 | },
70 | });
71 |
72 | const $has = _restore(was, '', {
73 | loc: {
74 | file: _effectorFileName,
75 | line: 10,
76 | column: 13,
77 | },
78 | name: '$has',
79 | sid: 'css95b',
80 | });
81 |
82 |
83 | `;
84 |
85 | exports[`effector-logger/macro add sid, loc, and inspector: add sid, loc, and inspector 1`] = `
86 |
87 | import { createStore, createEvent, createEffect, attach, restore } from './macro';
88 | const fx = createEffect(() => 1);
89 | const $data = createStore(0);
90 | const was = createEvent();
91 | const anotherFx = attach({
92 | effect: fx,
93 | source: $data,
94 | mapParams: (a) => a,
95 | })
96 | const $has = restore(was, "");
97 |
98 | ↓ ↓ ↓ ↓ ↓ ↓
99 |
100 | var _effectorFileName = '/macro.test.js';
101 | import 'effector-logger/inspector';
102 | import { restore as _restore } from 'effector-logger';
103 | import { attach as _attach } from 'effector-logger';
104 | import { createEffect as _createEffect } from 'effector-logger';
105 | import { createEvent as _createEvent } from 'effector-logger';
106 | import { createStore as _createStore } from 'effector-logger';
107 |
108 | const fx = _createEffect(() => 1, {
109 | loc: {
110 | file: _effectorFileName,
111 | line: 2,
112 | column: 11,
113 | },
114 | name: 'fx',
115 | sid: '-u6p3sc',
116 | });
117 |
118 | const $data = _createStore(0, {
119 | loc: {
120 | file: _effectorFileName,
121 | line: 3,
122 | column: 14,
123 | },
124 | name: '$data',
125 | sid: '8tv02c',
126 | });
127 |
128 | const was = _createEvent({
129 | loc: {
130 | file: _effectorFileName,
131 | line: 4,
132 | column: 12,
133 | },
134 | name: 'was',
135 | sid: '-3z5gxy',
136 | });
137 |
138 | const anotherFx = _attach({
139 | ɔ: {
140 | effect: fx,
141 | source: $data,
142 | mapParams: (a) => a,
143 | },
144 | config: {
145 | loc: {
146 | file: _effectorFileName,
147 | line: 5,
148 | column: 18,
149 | },
150 | name: 'anotherFx',
151 | sid: '-4ce2k9',
152 | },
153 | });
154 |
155 | const $has = _restore(was, '', {
156 | loc: {
157 | file: _effectorFileName,
158 | line: 10,
159 | column: 13,
160 | },
161 | name: '$has',
162 | sid: 'css95b',
163 | });
164 |
165 |
166 | `;
167 |
168 | exports[`effector-logger/macro add sid: add sid 1`] = `
169 |
170 | import { createStore, createEvent, createEffect, attach, restore } from './macro';
171 | const fx = createEffect(() => 1);
172 | const $data = createStore(0);
173 | const was = createEvent();
174 | const anotherFx = attach({
175 | effect: fx,
176 | source: $data,
177 | mapParams: (a) => a,
178 | })
179 | const $has = restore(was, "");
180 |
181 | ↓ ↓ ↓ ↓ ↓ ↓
182 |
183 | import { restore as _restore } from 'effector-logger';
184 | import { attach as _attach } from 'effector-logger';
185 | import { createEffect as _createEffect } from 'effector-logger';
186 | import { createEvent as _createEvent } from 'effector-logger';
187 | import { createStore as _createStore } from 'effector-logger';
188 |
189 | const fx = _createEffect(() => 1, {
190 | name: 'fx',
191 | sid: '-u6p3sc',
192 | });
193 |
194 | const $data = _createStore(0, {
195 | name: '$data',
196 | sid: '8tv02c',
197 | });
198 |
199 | const was = _createEvent({
200 | name: 'was',
201 | sid: '-3z5gxy',
202 | });
203 |
204 | const anotherFx = _attach({
205 | ɔ: {
206 | effect: fx,
207 | source: $data,
208 | mapParams: (a) => a,
209 | },
210 | config: {
211 | name: 'anotherFx',
212 | sid: '-4ce2k9',
213 | },
214 | });
215 |
216 | const $has = _restore(was, '', {
217 | name: '$has',
218 | sid: 'css95b',
219 | });
220 |
221 |
222 | `;
223 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Effector Logger
2 |
3 | Pretty logger for stores, events, effects and domains.
4 |
5 |
6 |
7 | [](#contributors-)
8 |
9 |
10 |
11 | |  |
12 | | ---------------------------------------------------------------- |
13 | |  |
14 |
15 | ## Installation
16 |
17 | ```bash
18 | npm add effector
19 | npm add --dev effector-logger
20 | ```
21 |
22 | or yarn
23 |
24 | ```bash
25 | yarn add effector
26 | yarn add -D effector-logger
27 | ```
28 |
29 | > Note: **effector-logger** requires `effector` to be installed
30 |
31 | ## Usage
32 |
33 | ### Prepare metadata
34 |
35 | To make logs more useful we need additional metadata (like names, locations in the code, etc), which is provided by one of the `effector` plugins.
36 |
37 | #### Babel-plugin
38 |
39 | Babel-plugin is built-in in the `effector` package.
40 |
41 | Just add it to your babel configuration.
42 |
43 | ```json
44 | {
45 | "plugins": ["effector/babel-plugin"]
46 | }
47 | ```
48 |
49 | It is also useful to enable `loc` generation for dev environment, to see for exact locations of units in the code.
50 |
51 | ```json
52 | {
53 | "plugins": [["effector/babel-plugin", { "addLoc": true }]]
54 | }
55 | ```
56 |
57 | [Read the docs](https://effector.dev/docs/api/effector/babel-plugin/#usage)
58 |
59 | #### SWC Plugin
60 |
61 | [Read effector SWC plugin documentation](https://github.com/effector/swc-plugin)
62 |
63 | ### Start logging
64 |
65 | Just call `attachLogger` in your entrypoint module.
66 |
67 | NOTE: To "see" the `createStore`, `createEvent`, etc calls `effector-logger` needs to be imported at the very top of your entrypoint module. This way initial states of stores will be properly logged at the moment of `attachLogger` call.
68 |
69 | Update logs are not affected by import order.
70 |
71 | ```ts
72 | // src/index.tsx
73 | import { attachLogger } from 'effector-logger';
74 |
75 | import React from 'react';
76 | import { createRoot } from 'react-dom/client';
77 | import { App } from './app';
78 | import { appStarted } from './shared/app-events';
79 |
80 | attachLogger();
81 |
82 | appStarted();
83 |
84 | createRoot(document.querySelector('#app')).render();
85 | ```
86 |
87 | After that you will see the logs in your console.
88 |
89 | ### With `Scope`
90 |
91 | If your app uses scope (e.g. you have Server-Side-Rendering) - you will need to pass it to the logger to work.
92 |
93 | ```ts
94 | attachLogger({ scope });
95 | ```
96 |
97 | Updates related to provided scope will be prefixed with scope id.
98 |
99 | ### Name
100 |
101 | There optional `name` prefix to the logs.
102 | It can be useful if there are few instances of your app, which are using different scopes or if you just want prefix that is better than boring scope id.
103 |
104 | ```ts
105 | attachLogger({
106 | scope,
107 | name: `my-cool-app-${appId}`, // all logs will be prefixed with this string
108 | });
109 | ```
110 |
111 | ### Stop logs
112 |
113 | To stop logs just call unsubscribe function.
114 |
115 | ```ts
116 | const unlogger = attachLogger();
117 |
118 | unlogger();
119 | ```
120 |
121 | ### Hide any unit from log
122 |
123 | Sometimes it is required to hide some events or stores from log.
124 | It is simple to implement: just call `configure` on your unit.
125 |
126 | ```ts
127 | import { createEvent } from 'effector';
128 | import { configure } from 'effector-logger';
129 | import { $data, loadDataFx } from './model';
130 |
131 | const pageMounted = createEvent();
132 |
133 | configure(pageMounted, { log: 'disabled' });
134 |
135 | // You can pass multiple units as array
136 | configure([$data, loadDataFx], { log: 'disabled' });
137 | ```
138 |
139 | ### Force any unit to be logged
140 |
141 | By default only non-derived units are logged. If you want to force some unit to be logged, use configure `enabled`
142 |
143 | ```ts
144 | import { createEvent } from 'effector';
145 | import { configure } from 'effector-logger';
146 | import { $data, loadDataFx } from './model';
147 |
148 | const pageMounted = createEvent();
149 |
150 | const mappedMounted = pageMounter.map((x) => x);
151 |
152 | configure(mappedMounted, { log: 'enabled' });
153 |
154 | // You can pass multiple units as array
155 | configure([$data, loadDataFx], { log: 'enabled' });
156 | ```
157 |
158 | ## Redux DevTools support
159 |
160 | Redux DevTools is moved to a different package.
161 |
162 | See the [`@effector/redux-devtools-adapter`](https://github.com/effector/redux-devtools-adapter)
163 |
164 | ## Contributors ✨
165 |
166 | Thanks go to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
167 |
168 |
169 |
170 |
171 |
178 |
179 |
180 |
181 |
182 |
183 |
184 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
185 |
186 | ## Release process
187 |
188 | 1. Check out the [draft release](https://github.com/effector/logger/releases).
189 | 1. All PRs should have correct labels and useful titles. You can [review available labels here](https://github.com/effector/logger/blob/master/.github/release-drafter.yml).
190 | 1. Update labels for PRs and titles, next [manually run the release drafter action](https://github.com/effector/logger/actions/workflows/release-drafter.yml) to regenerate the draft release.
191 | 1. Review the new version and press "Publish"
192 | 1. If required check "Create discussion for this release"
193 |
--------------------------------------------------------------------------------
/src/logger.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | import debounce from 'just-debounce-it';
3 | import isNode from 'detect-node';
4 | import { inspectGraph, Declaration } from 'effector/inspect';
5 | import { Scope } from 'effector';
6 |
7 | import { getName, locToString, getStateFromDeclaration } from './lib';
8 |
9 | const SEPARATOR = isNode ? ' ' : '';
10 |
11 | const storeListToInit: Array = [];
12 | const eventListToInit: Array = [];
13 | const effectListToInit: Array = [];
14 |
15 | inspectGraph({
16 | fn: (d) => {
17 | if (!d.derived) {
18 | if (d.kind === 'store' && !d.meta.named) {
19 | storeListToInit.push(d);
20 | }
21 | if (d.kind === 'event' && !d.meta.named) {
22 | eventListToInit.push(d);
23 | }
24 | if (d.kind === 'effect' && !d.meta.named) {
25 | effectListToInit.push(d);
26 | }
27 | }
28 | },
29 | });
30 |
31 | const styles = {
32 | block: 'padding-left: 4px; padding-right: 4px; font-weight: normal;',
33 | chunk: 'padding-left: 4px; padding-right: 4px; font-weight: normal;',
34 | effector:
35 | 'line-height:1.5; color: #000; font-family: "Apple Emoji Font"; font-weight: normal !important;',
36 | new: 'background-color: #29b6f6; color: #000',
37 | store: 'background-color: #7e57c2; color: #fff',
38 | event: 'background-color: #9ccc65; color: #000',
39 | effect: 'background-color: #26a69a; color: #000',
40 | emoji: '',
41 | file: 'color: #9e9e9e; padding-left: 20px;',
42 | reset: 'color: currentColor; background-color: transparent;',
43 | };
44 |
45 | const effectorLabel: Block[] = [
46 | ['☄️', '%s', styles.effector],
47 | ['effector', '%s', 'font-family: Menlo, monospace;'],
48 | ];
49 |
50 | const reset = (index: number, count: number, style: string): string =>
51 | index === count - 1 ? styles.reset : style;
52 |
53 | type Block = [string, string, string];
54 | type Chunk = [any, string, string];
55 |
56 | function log(
57 | blocks: Block[],
58 | chunks: Chunk[],
59 | group: 'collapsed' | 'open' | void = undefined,
60 | ): void {
61 | const str: string[] = [];
62 | const params: any[] = [];
63 |
64 | blocks.unshift(...effectorLabel);
65 |
66 | blocks.forEach(([value, view, style], index) => {
67 | str.push(`%c${view}%c`);
68 | params.push(
69 | `${styles.block} ${style}`,
70 | value,
71 | reset(index, blocks.length, `${styles.block} ${style}`),
72 | );
73 | });
74 |
75 | chunks.forEach(([value, view, style]) => {
76 | str.push(`%c${view}`);
77 | params.push(`${styles.chunk} ${style}`, value);
78 | });
79 |
80 | const args = [str.join(SEPARATOR), ...params];
81 |
82 | if (group === 'open') {
83 | console.group(...args);
84 | } else if (group === 'collapsed') {
85 | console.groupCollapsed(...args);
86 | } else {
87 | console.log(...args);
88 | }
89 | }
90 |
91 | const blockNew: Block = ['new', '%s', styles.new];
92 |
93 | const stripDomain = (name: string) => name.split('/').pop() || name;
94 |
95 | const createBlockStore: (name: string) => Block = (name) => [stripDomain(name), '%s', styles.store];
96 | const createBlockEvent: (name: string) => Block = (name) => [stripDomain(name), '%s', styles.event];
97 | const createBlockEffect: (name: string) => Block = (name) => [
98 | stripDomain(name),
99 | '%s',
100 | styles.effect,
101 | ];
102 |
103 | export const createDeclarationsLogger = (config: { name: string; scope?: Scope }) =>
104 | debounce(() => {
105 | const stores = storeListToInit.splice(0, storeListToInit.length);
106 | const events = eventListToInit.splice(0, eventListToInit.length);
107 | const effects = effectListToInit.splice(0, effectListToInit.length);
108 |
109 | if (stores.length + events.length + effects.length > 0) {
110 | log(
111 | [blockNew],
112 | [
113 | ['Initialized', '%s', ''],
114 | [`events(${events.length})`, '%s', ''],
115 | [`effects(${effects.length})`, '%s', ''],
116 | [`stores(${stores.length})`, '%s', ''],
117 | ],
118 | 'collapsed',
119 | );
120 |
121 | if (stores.length) {
122 | stores.forEach((store) => {
123 | const name = getName(store, config.name);
124 | const fileName = locToString(store.loc);
125 |
126 | log(
127 | [blockNew, createBlockStore(name)],
128 | [
129 | ['-> ', '%s', ''],
130 | [getStateFromDeclaration(store, config.scope), '%o', ''],
131 | [fileName, '%s', styles.file],
132 | [name, '%s', ''],
133 | ],
134 | );
135 | });
136 | }
137 | if (events.length > 0) {
138 | events.forEach((event) => {
139 | const name = getName(event, config.name);
140 | const fileName = locToString(event.loc);
141 |
142 | log(
143 | [blockNew, createBlockEvent(name)],
144 | [
145 | [fileName, '%s', styles.file],
146 | [name, '%s', ''],
147 | ],
148 | );
149 | });
150 | }
151 | if (effects.length > 0) {
152 | effects.forEach((effect) => {
153 | const name = getName(effect, config.name);
154 | const fileName = locToString(effect.loc);
155 |
156 | log(
157 | [blockNew, createBlockEffect(name)],
158 | [
159 | [fileName, '%s', styles.file],
160 | [name, '%s', ''],
161 | ],
162 | );
163 | });
164 | }
165 | console.groupEnd();
166 | }
167 | }, 5);
168 |
169 | export function storeUpdated(name: string, fileName: string, value: any): void {
170 | log(
171 | [createBlockStore(name)],
172 | [
173 | ['-> ', '%s', ''],
174 | [value, '%o', ''],
175 | [fileName, '%s', styles.file],
176 | [name, '%s', styles.file],
177 | ],
178 | );
179 | }
180 |
181 | export function eventCalled(name: string, fileName: string, payload: any): void {
182 | log(
183 | [createBlockEvent(name)],
184 | [
185 | [payload, '%o', 'padding-left: 4px;'],
186 | [fileName, '%s', styles.file],
187 | [name, '%s', styles.file],
188 | ],
189 | );
190 | }
191 |
192 | export function effectCalled(name: string, fileName: string, parameters: any): void {
193 | log(
194 | [createBlockEffect(name)],
195 | [
196 | [parameters, '%o', 'padding-left: 4px;'],
197 | [fileName, '%s', styles.file],
198 | [name, '%s', styles.file],
199 | ],
200 | );
201 | }
202 |
203 | export function effectDone(name: string, fileName: string, parameters: any, result: any): void {
204 | log(
205 | [createBlockEffect(name)],
206 | [
207 | ['done ✅', '%s', styles.emoji],
208 | [parameters, '(%o)', 'padding-left: 4px;'],
209 | ['-> ', '%s', ''],
210 | [result, '%o', 'padding: 0;'],
211 | [fileName, '%s', styles.file],
212 | [name, '%s', styles.file],
213 | ],
214 | );
215 | }
216 |
217 | export function effectFail(name: string, fileName: string, parameters: any, error: any): void {
218 | const instanceofError = error instanceof Error;
219 |
220 | log(
221 | [createBlockEffect(name)],
222 | [
223 | ['fail ❌', '%s', styles.emoji],
224 | [parameters, '(%o)', 'padding-left: 4px;'],
225 | ['-> ', '%s', ''],
226 | instanceofError ? [String(error), '%s', ''] : [error, '%o', 'padding: 0;'],
227 | [fileName, '%s', styles.file],
228 | [name, '%s', styles.file],
229 | ],
230 | instanceofError ? 'collapsed' : undefined,
231 | );
232 |
233 | if (instanceofError) {
234 | log(
235 | [],
236 | [
237 | [' ', '%s', ''],
238 | [error, '%o', 'padding-left: 20px;'],
239 | ],
240 | );
241 | }
242 |
243 | console.groupEnd();
244 | }
245 |
--------------------------------------------------------------------------------
/src/basic.test.ts:
--------------------------------------------------------------------------------
1 | import { test, expect, describe, vi } from 'vitest';
2 |
3 | import { createStore, createEvent, createEffect, sample, fork, allSettled } from 'effector';
4 |
5 | import { attachLogger, configure } from '../dist';
6 |
7 | describe('logger tests', () => {
8 | test('logger works', () => {
9 | const $abc = createStore(0);
10 | const inc = createEvent();
11 | $abc.on(inc, (state) => state + 1);
12 | const myFx = createEffect(() => 42);
13 | sample({
14 | clock: inc,
15 | target: myFx,
16 | });
17 |
18 | const logged = vi.fn();
19 |
20 | vi.stubGlobal('console', {
21 | log: logged,
22 | warn: logged,
23 | error: logged,
24 | info: logged,
25 | group: logged,
26 | groupEnd: logged,
27 | groupCollapsed: logged,
28 | });
29 |
30 | const unlogger = attachLogger();
31 |
32 | inc();
33 |
34 | const logs = logged.mock.calls;
35 | expect(logs).toMatchInlineSnapshot(`
36 | [
37 | [
38 | "%c%s%c %c%s%c %c%s%c %c%o %c%s %c%s",
39 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
40 | "☄️",
41 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
42 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
43 | "effector",
44 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
45 | "padding-left: 4px; padding-right: 4px; font-weight: normal; background-color: #9ccc65; color: #000",
46 | "4",
47 | "color: currentColor; background-color: transparent;",
48 | "padding-left: 4px; padding-right: 4px; font-weight: normal; padding-left: 4px;",
49 | undefined,
50 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
51 | "",
52 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
53 | "4",
54 | ],
55 | [
56 | "%c%s%c %c%s%c %c%s%c %c%s %c%o %c%s %c%s",
57 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
58 | "☄️",
59 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
60 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
61 | "effector",
62 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
63 | "padding-left: 4px; padding-right: 4px; font-weight: normal; background-color: #7e57c2; color: #fff",
64 | "2",
65 | "color: currentColor; background-color: transparent;",
66 | "padding-left: 4px; padding-right: 4px; font-weight: normal; ",
67 | "-> ",
68 | "padding-left: 4px; padding-right: 4px; font-weight: normal; ",
69 | 1,
70 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
71 | "",
72 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
73 | "2",
74 | ],
75 | [
76 | "%c%s%c %c%s%c %c%s%c %c%o %c%s %c%s",
77 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
78 | "☄️",
79 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
80 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
81 | "effector",
82 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
83 | "padding-left: 4px; padding-right: 4px; font-weight: normal; background-color: #26a69a; color: #000",
84 | "5",
85 | "color: currentColor; background-color: transparent;",
86 | "padding-left: 4px; padding-right: 4px; font-weight: normal; padding-left: 4px;",
87 | undefined,
88 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
89 | "",
90 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
91 | "5",
92 | ],
93 | [
94 | "%c%s%c %c%s%c %c%s%c %c%s %c%o %c%s %c%s",
95 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
96 | "☄️",
97 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
98 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
99 | "effector",
100 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
101 | "padding-left: 4px; padding-right: 4px; font-weight: normal; background-color: #7e57c2; color: #fff",
102 | "5.inFlight",
103 | "color: currentColor; background-color: transparent;",
104 | "padding-left: 4px; padding-right: 4px; font-weight: normal; ",
105 | "-> ",
106 | "padding-left: 4px; padding-right: 4px; font-weight: normal; ",
107 | 1,
108 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
109 | "",
110 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
111 | "5.inFlight",
112 | ],
113 | [
114 | "%c%s%c %c%s%c %c%s%c %c%s %c(%o) %c%s %c%o %c%s %c%s",
115 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
116 | "☄️",
117 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
118 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
119 | "effector",
120 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
121 | "padding-left: 4px; padding-right: 4px; font-weight: normal; background-color: #26a69a; color: #000",
122 | "5",
123 | "color: currentColor; background-color: transparent;",
124 | "padding-left: 4px; padding-right: 4px; font-weight: normal; ",
125 | "done ✅",
126 | "padding-left: 4px; padding-right: 4px; font-weight: normal; padding-left: 4px;",
127 | undefined,
128 | "padding-left: 4px; padding-right: 4px; font-weight: normal; ",
129 | "-> ",
130 | "padding-left: 4px; padding-right: 4px; font-weight: normal; padding: 0;",
131 | 42,
132 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
133 | "",
134 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
135 | "5",
136 | ],
137 | [
138 | "%c%s%c %c%s%c %c%s%c %c%s %c%o %c%s %c%s",
139 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
140 | "☄️",
141 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
142 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
143 | "effector",
144 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
145 | "padding-left: 4px; padding-right: 4px; font-weight: normal; background-color: #7e57c2; color: #fff",
146 | "5.inFlight",
147 | "color: currentColor; background-color: transparent;",
148 | "padding-left: 4px; padding-right: 4px; font-weight: normal; ",
149 | "-> ",
150 | "padding-left: 4px; padding-right: 4px; font-weight: normal; ",
151 | 0,
152 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
153 | "",
154 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
155 | "5.inFlight",
156 | ],
157 | ]
158 | `);
159 | unlogger();
160 |
161 | inc();
162 |
163 | const nextLogs = logged.mock.calls;
164 |
165 | expect(logs).toStrictEqual(nextLogs);
166 | });
167 |
168 | test('logger works (scope)', () => {
169 | const $abc = createStore(0);
170 | const inc = createEvent();
171 | $abc.on(inc, (state) => state + 1);
172 | const myFx = createEffect(() => 42);
173 | sample({
174 | clock: inc,
175 | target: myFx,
176 | });
177 |
178 | const logged = vi.fn();
179 |
180 | vi.stubGlobal('console', {
181 | log: logged,
182 | warn: logged,
183 | error: logged,
184 | info: logged,
185 | group: logged,
186 | groupEnd: logged,
187 | groupCollapsed: logged,
188 | });
189 |
190 | const scope = fork();
191 |
192 | const unlogger = attachLogger({
193 | scope,
194 | });
195 |
196 | allSettled(inc, { scope });
197 |
198 | const logs = logged.mock.calls;
199 | expect(logs).toMatchInlineSnapshot(`
200 | [
201 | [
202 | "%c%s%c %c%s%c %c%s%c %c%o %c%s %c%s",
203 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
204 | "☄️",
205 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
206 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
207 | "effector",
208 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
209 | "padding-left: 4px; padding-right: 4px; font-weight: normal; background-color: #9ccc65; color: #000",
210 | "(scope: 65) 21",
211 | "color: currentColor; background-color: transparent;",
212 | "padding-left: 4px; padding-right: 4px; font-weight: normal; padding-left: 4px;",
213 | undefined,
214 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
215 | "",
216 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
217 | "(scope: 65) 21",
218 | ],
219 | [
220 | "%c%s%c %c%s%c %c%s%c %c%s %c%o %c%s %c%s",
221 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
222 | "☄️",
223 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
224 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
225 | "effector",
226 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
227 | "padding-left: 4px; padding-right: 4px; font-weight: normal; background-color: #7e57c2; color: #fff",
228 | "(scope: 65) 19",
229 | "color: currentColor; background-color: transparent;",
230 | "padding-left: 4px; padding-right: 4px; font-weight: normal; ",
231 | "-> ",
232 | "padding-left: 4px; padding-right: 4px; font-weight: normal; ",
233 | 1,
234 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
235 | "",
236 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
237 | "(scope: 65) 19",
238 | ],
239 | [
240 | "%c%s%c %c%s%c %c%s%c %c%o %c%s %c%s",
241 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
242 | "☄️",
243 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
244 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
245 | "effector",
246 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
247 | "padding-left: 4px; padding-right: 4px; font-weight: normal; background-color: #26a69a; color: #000",
248 | "(scope: 65) 22",
249 | "color: currentColor; background-color: transparent;",
250 | "padding-left: 4px; padding-right: 4px; font-weight: normal; padding-left: 4px;",
251 | undefined,
252 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
253 | "",
254 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
255 | "(scope: 65) 22",
256 | ],
257 | [
258 | "%c%s%c %c%s%c %c%s%c %c%s %c%o %c%s %c%s",
259 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
260 | "☄️",
261 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
262 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
263 | "effector",
264 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
265 | "padding-left: 4px; padding-right: 4px; font-weight: normal; background-color: #7e57c2; color: #fff",
266 | "(scope: 65) 22.inFlight",
267 | "color: currentColor; background-color: transparent;",
268 | "padding-left: 4px; padding-right: 4px; font-weight: normal; ",
269 | "-> ",
270 | "padding-left: 4px; padding-right: 4px; font-weight: normal; ",
271 | 1,
272 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
273 | "",
274 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
275 | "(scope: 65) 22.inFlight",
276 | ],
277 | [
278 | "%c%s%c %c%s%c %c%s%c %c%s %c(%o) %c%s %c%o %c%s %c%s",
279 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
280 | "☄️",
281 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
282 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
283 | "effector",
284 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
285 | "padding-left: 4px; padding-right: 4px; font-weight: normal; background-color: #26a69a; color: #000",
286 | "(scope: 65) 22",
287 | "color: currentColor; background-color: transparent;",
288 | "padding-left: 4px; padding-right: 4px; font-weight: normal; ",
289 | "done ✅",
290 | "padding-left: 4px; padding-right: 4px; font-weight: normal; padding-left: 4px;",
291 | undefined,
292 | "padding-left: 4px; padding-right: 4px; font-weight: normal; ",
293 | "-> ",
294 | "padding-left: 4px; padding-right: 4px; font-weight: normal; padding: 0;",
295 | 42,
296 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
297 | "",
298 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
299 | "(scope: 65) 22",
300 | ],
301 | [
302 | "%c%s%c %c%s%c %c%s%c %c%s %c%o %c%s %c%s",
303 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
304 | "☄️",
305 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
306 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
307 | "effector",
308 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
309 | "padding-left: 4px; padding-right: 4px; font-weight: normal; background-color: #7e57c2; color: #fff",
310 | "(scope: 65) 22.inFlight",
311 | "color: currentColor; background-color: transparent;",
312 | "padding-left: 4px; padding-right: 4px; font-weight: normal; ",
313 | "-> ",
314 | "padding-left: 4px; padding-right: 4px; font-weight: normal; ",
315 | 0,
316 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
317 | "",
318 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
319 | "(scope: 65) 22.inFlight",
320 | ],
321 | ]
322 | `);
323 | unlogger();
324 |
325 | allSettled(inc, { scope });
326 |
327 | const nextLogs = logged.mock.calls;
328 |
329 | expect(logs).toStrictEqual(nextLogs);
330 | });
331 |
332 | describe('configure', () => {
333 | test('configure can supress logs', () => {
334 | const $abc = createStore(0);
335 | const inc = createEvent();
336 | $abc.on(inc, (state) => state + 1);
337 | const myFx = createEffect(() => 42);
338 | sample({
339 | clock: inc,
340 | target: myFx,
341 | });
342 |
343 | const logged = vi.fn();
344 |
345 | vi.stubGlobal('console', {
346 | log: logged,
347 | warn: logged,
348 | error: logged,
349 | info: logged,
350 | group: logged,
351 | groupEnd: logged,
352 | groupCollapsed: logged,
353 | });
354 |
355 | configure($abc, { log: 'disabled' });
356 | configure([inc, myFx], { log: 'disabled' });
357 |
358 | const unlogger = attachLogger();
359 |
360 | inc();
361 |
362 | expect(logged.mock.calls.length).toBe(2); // initial logs of logger installation
363 | expect(logged.mock.calls).toMatchInlineSnapshot(`
364 | [
365 | [
366 | "%c%s%c %c%s%c %c%s%c %c%s %c%o %c%s %c%s",
367 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
368 | "☄️",
369 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
370 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
371 | "effector",
372 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
373 | "padding-left: 4px; padding-right: 4px; font-weight: normal; background-color: #7e57c2; color: #fff",
374 | "39.inFlight",
375 | "color: currentColor; background-color: transparent;",
376 | "padding-left: 4px; padding-right: 4px; font-weight: normal; ",
377 | "-> ",
378 | "padding-left: 4px; padding-right: 4px; font-weight: normal; ",
379 | 1,
380 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
381 | "",
382 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
383 | "39.inFlight",
384 | ],
385 | [
386 | "%c%s%c %c%s%c %c%s%c %c%s %c%o %c%s %c%s",
387 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
388 | "☄️",
389 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
390 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
391 | "effector",
392 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
393 | "padding-left: 4px; padding-right: 4px; font-weight: normal; background-color: #7e57c2; color: #fff",
394 | "39.inFlight",
395 | "color: currentColor; background-color: transparent;",
396 | "padding-left: 4px; padding-right: 4px; font-weight: normal; ",
397 | "-> ",
398 | "padding-left: 4px; padding-right: 4px; font-weight: normal; ",
399 | 0,
400 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
401 | "",
402 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
403 | "39.inFlight",
404 | ],
405 | ]
406 | `);
407 |
408 | unlogger();
409 | });
410 |
411 | test('configure can force logs', () => {
412 | const $abc = createStore(0);
413 | const inc = createEvent();
414 | $abc.on(inc, (state) => state + 1);
415 | const myFx = createEffect(() => 42);
416 | sample({
417 | clock: inc,
418 | target: myFx,
419 | });
420 |
421 | const derivedInc = inc.map((x) => 'LOGGED!!!!');
422 |
423 | const logged = vi.fn();
424 |
425 | vi.stubGlobal('console', {
426 | log: logged,
427 | warn: logged,
428 | error: logged,
429 | info: logged,
430 | group: logged,
431 | groupEnd: logged,
432 | groupCollapsed: logged,
433 | });
434 |
435 | configure($abc, { log: 'disabled' });
436 | configure([inc, myFx], { log: 'disabled' });
437 |
438 | // force logs of derived unit
439 | configure(derivedInc, { log: 'enabled' });
440 |
441 | const unlogger = attachLogger();
442 |
443 | inc();
444 |
445 | expect(logged.mock.calls.length).toBe(3); // initial logs of logger installation + single update of derivedInc
446 | expect(logged.mock.calls).toMatchInlineSnapshot(`
447 | [
448 | [
449 | "%c%s%c %c%s%c %c%s%c %c%o %c%s %c%s",
450 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
451 | "☄️",
452 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
453 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
454 | "effector",
455 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
456 | "padding-left: 4px; padding-right: 4px; font-weight: normal; background-color: #9ccc65; color: #000",
457 | "55 → *",
458 | "color: currentColor; background-color: transparent;",
459 | "padding-left: 4px; padding-right: 4px; font-weight: normal; padding-left: 4px;",
460 | "LOGGED!!!!",
461 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
462 | "",
463 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
464 | "55 → *",
465 | ],
466 | [
467 | "%c%s%c %c%s%c %c%s%c %c%s %c%o %c%s %c%s",
468 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
469 | "☄️",
470 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
471 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
472 | "effector",
473 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
474 | "padding-left: 4px; padding-right: 4px; font-weight: normal; background-color: #7e57c2; color: #fff",
475 | "56.inFlight",
476 | "color: currentColor; background-color: transparent;",
477 | "padding-left: 4px; padding-right: 4px; font-weight: normal; ",
478 | "-> ",
479 | "padding-left: 4px; padding-right: 4px; font-weight: normal; ",
480 | 1,
481 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
482 | "",
483 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
484 | "56.inFlight",
485 | ],
486 | [
487 | "%c%s%c %c%s%c %c%s%c %c%s %c%o %c%s %c%s",
488 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
489 | "☄️",
490 | "padding-left: 4px; padding-right: 4px; font-weight: normal; line-height:1.5; color: #000; font-family: \\"Apple Emoji Font\\"; font-weight: normal !important;",
491 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
492 | "effector",
493 | "padding-left: 4px; padding-right: 4px; font-weight: normal; font-family: Menlo, monospace;",
494 | "padding-left: 4px; padding-right: 4px; font-weight: normal; background-color: #7e57c2; color: #fff",
495 | "56.inFlight",
496 | "color: currentColor; background-color: transparent;",
497 | "padding-left: 4px; padding-right: 4px; font-weight: normal; ",
498 | "-> ",
499 | "padding-left: 4px; padding-right: 4px; font-weight: normal; ",
500 | 0,
501 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
502 | "",
503 | "padding-left: 4px; padding-right: 4px; font-weight: normal; color: #9e9e9e; padding-left: 20px;",
504 | "56.inFlight",
505 | ],
506 | ]
507 | `);
508 |
509 | unlogger();
510 | });
511 | });
512 | });
513 |
--------------------------------------------------------------------------------