eslint-plugin-effector-units-order
v0.0.3
Published
ESLint plugin for enforcing consistent ordering of Effector units
Maintainers
Readme
eslint-plugin-effector-units-order
An ESLint plugin that enforces a consistent order for Effector units (domains, events, effects, stores, and computed values) in your codebase. Keep your Effector code organized and maintainable with automatic code reordering.

✨ Features
- 🔧 Auto-fix — Automatically reorders your code to match the configured order
- 📦 Grouping — Organize units into logical groups for better code structure
- 📏 Empty lines — Configurable spacing between groups (0-3 lines)
- 🏭 Custom factories — Support for custom unit factories and factory methods
- 🌐 Domain support — Works with
domain.createEvent(),domain.createEffect(), and other domain methods - 🧮 Computed stores — Supports
combine()andor()from patronum - ⚙️ Flexible configuration — Configure via rule options or ESLint shared settings
- 📋 ESLint 9+ — Supports both flat config and legacy
.eslintrcformat
📦 Installation
# npm
npm install -D eslint-plugin-effector-units-order
# pnpm
pnpm add -D eslint-plugin-effector-units-order
# yarn
yarn add -D eslint-plugin-effector-units-order🚀 Quick Start
ESLint Flat Config (ESLint 9+)
// eslint.config.js
import effectorOrder from "eslint-plugin-effector-units-order";
export default [
// Use recommended config
effectorOrder.configs.recommended,
];Legacy Config (.eslintrc)
{
"extends": ["plugin:effector-order/recommended-legacy"]
}📖 Usage
Recommended Configuration
The recommended configuration enforces the following order:
domain—createDomain()events—createEvent(),domain.createEvent()effects—createEffect(),domain.createEffect()stores—createStore(),domain.createStore()computed—combine(),or()
With 1 empty line between groups for better readability.
Custom Configuration
// eslint.config.js
import effectorOrder from "eslint-plugin-effector-units-order";
export default [
{
plugins: {
"effector-order": effectorOrder,
},
rules: {
"effector-order/keep-units-order": [
"error",
{
order: ["domain", "events", "effects", "stores", "computed"],
emptyLinesBetweenGroups: 1,
},
],
},
},
];⚙️ Options
| Option | Type | Default | Description |
| ------------------------- | ---------- | ------------------------------------------------------- | ----------------------------------------------------------- |
| order | string[] | ['domain', 'events', 'effects', 'stores', 'computed'] | Defines the order of unit groups |
| emptyLinesBetweenGroups | number | 1 | Number of empty lines between groups (0-3) |
| groups | object | See below | Custom group definitions with factories and derived methods |
| customFactories | object | {} | Maps custom factory functions to specific groups |
| packages | string[] | ['effector', 'patronum'] | Packages to detect imports from |
Group Options:
Each group in groups can have:
factories: string[]— Factory functions that create units of this type (e.g.,['createStore'])derivedMethods?: string[]— Methods and properties that create derived units (e.g.,['stores.map', 'effects.pending'])
Default Groups
{
domain: {
factories: ['createDomain']
},
events: {
factories: ['createEvent'],
derivedMethods: [
'events.prepend',
'events.map',
'events.filter',
'events.filterMap',
'stores.updates',
'stores.reinit',
'effects.done',
'effects.doneData',
'effects.fail',
'effects.failData',
'effects.finally'
]
},
effects: {
factories: ['createEffect'],
derivedMethods: [
'effects.map',
'effects.prepend',
'effects.filterMap'
]
},
stores: {
factories: ['createStore']
},
computed: {
factories: ['or', 'combine', 'merge', 'attach', 'forward'],
derivedMethods: [
'stores.map',
'effects.pending',
'effects.inFlight'
]
}
}📋 Examples
Basic Usage
❌ Incorrect Order
import { createStore, createEvent, createDomain } from "effector";
export const $user = createStore(null); // ❌ Store before event
export const userClicked = createEvent(); // ❌ Event before domain
export const userDomain = createDomain();✅ Correct Order
import { createStore, createEvent, createDomain } from "effector";
export const userDomain = createDomain();
export const userClicked = createEvent();
export const $user = createStore(null);Domain Methods
The plugin also recognizes units created via domain methods:
import { createDomain } from "effector";
export const userDomain = createDomain();
export const userClicked = userDomain.createEvent();
export const fetchUserFx = userDomain.createEffect();
export const $user = userDomain.createStore(null);Computed Stores
Computed stores using combine(), merge(), attach(), forward(), and or() from patronum are properly recognized:
import { createStore, combine, merge, attach, forward } from "effector";
import { or } from "patronum";
export const $isLoading = createStore(false);
export const $error = createStore(null);
export const $hasIssues = or($isLoading, $error);
export const $state = combine({
isLoading: $isLoading,
error: $error,
});Derived Units
The plugin automatically recognizes derived stores and events created from existing units:
Derived Stores
import { createStore, createEffect } from "effector";
export const $products = createStore([]);
export const fetchProductsFx = createEffect();
export const $enrichedProducts = $products.map((products) =>
products.map((p) => ({ ...p, priceWithTax: p.price * 1.2 }))
);
export const $loading = fetchProductsFx.pending;
export const $isLoading = $loading.map((loading) => loading);Derived Events
import { createEvent, createEffect, createStore } from "effector";
export const userLogin = createEvent();
export const fetchUserFx = createEffect();
export const $user = createStore(null);
export const loginWithEmail = userLogin.map((email) => ({ email })); /
export const fetchSucceeded = fetchUserFx.done;
export const fetchFailed = fetchUserFx.fail;
export const userUpdated = $user.updates;
export const resetUser = $user.reinit;❌ Incorrect Order (Auto-fixed)
import { createStore, createEffect } from "effector";
export const $loading = fetchProductsFx.pending;
export const fetchProductsFx = createEffect();
export const $products = createStore([]);
export const $enriched = $products.map((p) => p);✅ Correct Order (After auto-fix)
import { createStore, createEffect } from "effector";
export const fetchProductsFx = createEffect();
export const $products = createStore([]);
export const $loading = fetchProductsFx.pending;
export const $enriched = $products.map((p) => p);Custom Factories
You can map custom factory functions to specific groups:
// eslint.config.js
export default [
{
plugins: { "effector-order": effectorOrder },
rules: {
"effector-order/keep-units-order": [
"error",
{
order: ["domain", "events", "effects", "stores", "computed"],
customFactories: {
tableStateFactory: "stores",
permissionSelectionFactory: "stores",
},
},
],
},
},
];Custom Groups
Define your own groups with custom factory mappings:
// eslint.config.js
export default [
{
plugins: { "effector-order": effectorOrder },
rules: {
"effector-order/keep-units-order": [
"error",
{
order: ["domain", "actions", "data", "computed"],
groups: {
actions: { factories: ["createEvent", "createEffect"] },
data: { factories: ["createStore"] },
},
},
],
},
},
];Custom Derived Methods
You can configure which methods and properties create derived units:
// eslint.config.js
export default [
{
plugins: { "effector-order": effectorOrder },
rules: {
"effector-order/keep-units-order": [
"error",
{
order: ["domain", "events", "effects", "stores", "computed"],
groups: {
events: {
factories: ["createEvent"],
// Configure which methods create derived events
derivedMethods: [
"events.map",
"events.prepend",
"effects.done",
"stores.updates",
],
},
effects: {
factories: ["createEffect"],
derivedMethods: ["effects.map", "effects.prepend"],
},
computed: {
factories: ["combine", "merge", "attach", "forward"],
// Configure which methods create derived stores
derivedMethods: [
"stores.map",
"effects.pending",
"effects.inFlight",
],
},
},
},
],
},
},
];Format for derivedMethods:
"unitType.methodName"— for methods like$store.map(),event.prepend()"unitType.property"— for properties likeeffect.pending,store.updates
Where unitType is the type of the parent unit (stores, events, effects, computed), and the result will be categorized into the group that contains this derivedMethod in its configuration.
No Empty Lines Between Groups
You can disable empty lines between groups by setting emptyLinesBetweenGroups to 0:
// eslint.config.js
export default [
{
plugins: { "effector-order": effectorOrder },
rules: {
"effector-order/keep-units-order": [
"error",
{
order: ["domain", "events", "effects", "stores"],
emptyLinesBetweenGroups: 0,
},
],
},
},
];Result:
export const userDomain = createDomain();
export const userClicked = createEvent();
export const fetchUserFx = createEffect();
export const $user = createStore(null);🔧 Shared Settings
You can also configure the plugin via ESLint shared settings. This is useful when you want to share the same configuration across multiple rules or files:
// eslint.config.js
export default [
{
plugins: { "effector-order": effectorOrder },
settings: {
"effector-units-order": {
order: ["domain", "events", "effects", "stores", "computed"],
emptyLinesBetweenGroups: 1,
},
},
rules: {
"effector-order/keep-units-order": "error",
},
},
];Note: Rule options take precedence over shared settings. If you specify options directly in the rule configuration, they will override the shared settings.
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
📄 License
MIT © Nikita
