state-invariant 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +147 -0
- package/dist/backend.cjs +90 -0
- package/dist/backend.cjs.map +1 -0
- package/dist/backend.d.ts +16 -0
- package/dist/backend.js +18 -0
- package/dist/backend.js.map +1 -0
- package/dist/chunk-DRSBKG3Y.js +54 -0
- package/dist/chunk-DRSBKG3Y.js.map +1 -0
- package/dist/core.d.ts +54 -0
- package/dist/generic.cjs +108 -0
- package/dist/generic.cjs.map +1 -0
- package/dist/generic.d.ts +29 -0
- package/dist/generic.js +35 -0
- package/dist/generic.js.map +1 -0
- package/dist/index.cjs +81 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/redux.cjs +91 -0
- package/dist/redux.cjs.map +1 -0
- package/dist/redux.d.ts +24 -0
- package/dist/redux.js +19 -0
- package/dist/redux.js.map +1 -0
- package/dist/zustand.cjs +94 -0
- package/dist/zustand.cjs.map +1 -0
- package/dist/zustand.d.ts +22 -0
- package/dist/zustand.js +22 -0
- package/dist/zustand.js.map +1 -0
- package/package.json +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# state-invariant
|
|
2
|
+
|
|
3
|
+
Catch illegal **state transitions** at runtime, in any state management library.
|
|
4
|
+
|
|
5
|
+
Most validation libraries (Zod, Yup, io-ts) check the *shape* of one object at one
|
|
6
|
+
moment in time. `state-invariant` checks something different: given the state
|
|
7
|
+
**before**, the state **after**, and the **action** that caused the change — was
|
|
8
|
+
that transition actually legal?
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
prev state + action → next state
|
|
12
|
+
↑ ↑
|
|
13
|
+
└──── is this legal? ──────┘
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install state-invariant
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Core usage (works anywhere)
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { createInvariantChecker } from 'state-invariant';
|
|
26
|
+
|
|
27
|
+
const check = createInvariantChecker([
|
|
28
|
+
{
|
|
29
|
+
name: 'balance-never-negative',
|
|
30
|
+
check: (prev, next) => next.balance >= 0,
|
|
31
|
+
message: (prev, next, action) =>
|
|
32
|
+
`action ${action.type} would make balance negative: ${next.balance}`,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'logout-clears-user',
|
|
36
|
+
when: (action) => action.type === 'LOGOUT',
|
|
37
|
+
check: (prev, next) => next.user === null,
|
|
38
|
+
},
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
check(prevState, nextState, action); // throws InvariantViolationError if a rule fails
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## With Redux
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { configureStore } from '@reduxjs/toolkit';
|
|
48
|
+
import { createInvariantMiddleware } from 'state-invariant/redux';
|
|
49
|
+
|
|
50
|
+
const middleware = createInvariantMiddleware([
|
|
51
|
+
{ name: 'balance-never-negative', check: (p, n) => n.balance >= 0 },
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
const store = configureStore({
|
|
55
|
+
reducer,
|
|
56
|
+
middleware: (getDefault) => getDefault().concat(middleware),
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## With Zustand
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { create } from 'zustand';
|
|
64
|
+
import { invariant } from 'state-invariant/zustand';
|
|
65
|
+
|
|
66
|
+
const useStore = create(
|
|
67
|
+
invariant([{ name: 'count-never-negative', check: (p, n) => n.count >= 0 }])(
|
|
68
|
+
(set) => ({ count: 0, decrement: () => set((s) => ({ count: s.count - 1 })) })
|
|
69
|
+
)
|
|
70
|
+
);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## With anything else (plain reducer)
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { wrapReducer } from 'state-invariant/generic';
|
|
77
|
+
|
|
78
|
+
const checkedReducer = wrapReducer(myReducer, [
|
|
79
|
+
{ name: 'total-conserved', check: (p, n) => p.total === n.total },
|
|
80
|
+
]);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Or bracket any mutation manually, for stores with no middleware hooks at all:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { wrapManual } from 'state-invariant/generic';
|
|
87
|
+
|
|
88
|
+
const checker = wrapManual(myVanillaStore, rules);
|
|
89
|
+
checker.before();
|
|
90
|
+
myVanillaStore.doSomething();
|
|
91
|
+
checker.after('doSomething');
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## With APIs & Backends (Database Mutations)
|
|
95
|
+
|
|
96
|
+
This pattern is incredibly powerful for backend APIs and distributed systems where you want to guarantee that a database mutation doesn't violate core business rules.
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { createInvariantTransaction } from 'state-invariant/backend';
|
|
100
|
+
|
|
101
|
+
const safeTransfer = createInvariantTransaction([
|
|
102
|
+
{
|
|
103
|
+
name: 'balance-never-negative',
|
|
104
|
+
check: (prev, next) => next.balance >= 0 || 'Insufficient funds'
|
|
105
|
+
}
|
|
106
|
+
]);
|
|
107
|
+
|
|
108
|
+
// Inside your Express/Next.js route handler
|
|
109
|
+
app.post('/transfer', async (req, res) => {
|
|
110
|
+
try {
|
|
111
|
+
const result = await safeTransfer(req.body, {
|
|
112
|
+
read: () => db.accounts.findById(req.body.accountId),
|
|
113
|
+
compute: (prev) => ({
|
|
114
|
+
...prev,
|
|
115
|
+
balance: prev.balance - req.body.amount
|
|
116
|
+
}),
|
|
117
|
+
write: (next) => db.accounts.save(next)
|
|
118
|
+
});
|
|
119
|
+
res.json(result);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
res.status(400).json({ error: error.message });
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
The invariant check runs *after* `compute` but *before* `write`. If the invariant fails, it throws, meaning your database is never corrupted.
|
|
126
|
+
|
|
127
|
+
## Error handling modes
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
createInvariantChecker(rules, { mode: 'throw' }); // default: throw immediately
|
|
131
|
+
createInvariantChecker(rules, { mode: 'warn' }); // console.warn, keep running
|
|
132
|
+
createInvariantChecker(rules, { mode: 'collect' }); // gather violations silently
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
In `collect` mode, read them back with `check.getViolations()`.
|
|
136
|
+
|
|
137
|
+
## Why not just Zod?
|
|
138
|
+
|
|
139
|
+
Zod validates one object's shape. It can't tell you whether `next.balance`
|
|
140
|
+
is a *legal consequence* of `prev.balance` and the action that ran. That
|
|
141
|
+
requires seeing both states plus the action together — which is what this
|
|
142
|
+
package is for. Use Zod for shape, `state-invariant` for transitions. They
|
|
143
|
+
compose fine together.
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
MIT
|
package/dist/backend.cjs
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/backend.ts
|
|
21
|
+
var backend_exports = {};
|
|
22
|
+
__export(backend_exports, {
|
|
23
|
+
createInvariantTransaction: () => createInvariantTransaction
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(backend_exports);
|
|
26
|
+
|
|
27
|
+
// src/core.ts
|
|
28
|
+
var InvariantViolationError = class extends Error {
|
|
29
|
+
constructor(message, details) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.name = "InvariantViolationError";
|
|
32
|
+
this.ruleName = details.ruleName;
|
|
33
|
+
this.prev = details.prev;
|
|
34
|
+
this.next = details.next;
|
|
35
|
+
this.action = details.action;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
function createInvariantChecker(rules, options = {}) {
|
|
39
|
+
var _a;
|
|
40
|
+
const mode = (_a = options.mode) != null ? _a : "throw";
|
|
41
|
+
const violations = [];
|
|
42
|
+
function check(prev, next, action) {
|
|
43
|
+
for (const rule of rules) {
|
|
44
|
+
if (rule.when && !rule.when(action)) continue;
|
|
45
|
+
let ok;
|
|
46
|
+
try {
|
|
47
|
+
ok = rule.check(prev, next, action);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
ok = false;
|
|
50
|
+
}
|
|
51
|
+
if (ok === false || typeof ok === "string") {
|
|
52
|
+
const message = typeof ok === "string" ? ok : rule.message ? rule.message(prev, next, action) : `Invariant "${rule.name}" violated`;
|
|
53
|
+
const error = new InvariantViolationError(message, {
|
|
54
|
+
ruleName: rule.name,
|
|
55
|
+
prev,
|
|
56
|
+
next,
|
|
57
|
+
action
|
|
58
|
+
});
|
|
59
|
+
if (mode === "throw") {
|
|
60
|
+
throw error;
|
|
61
|
+
} else if (mode === "warn") {
|
|
62
|
+
console.warn(`[state-invariant] ${message}`, { prev, next, action });
|
|
63
|
+
} else {
|
|
64
|
+
violations.push(error);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
check.getViolations = () => violations.slice();
|
|
70
|
+
check.clearViolations = () => {
|
|
71
|
+
violations.length = 0;
|
|
72
|
+
};
|
|
73
|
+
return check;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/backend.ts
|
|
77
|
+
function createInvariantTransaction(rules, options = {}) {
|
|
78
|
+
const check = createInvariantChecker(rules, options);
|
|
79
|
+
return async (action, handlers) => {
|
|
80
|
+
const prev = await handlers.read();
|
|
81
|
+
const next = await handlers.compute(prev);
|
|
82
|
+
check(prev, next, action);
|
|
83
|
+
return await handlers.write(next);
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
87
|
+
0 && (module.exports = {
|
|
88
|
+
createInvariantTransaction
|
|
89
|
+
});
|
|
90
|
+
//# sourceMappingURL=backend.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/backend.ts","../src/core.ts"],"sourcesContent":["import { createInvariantChecker, InvariantRule, CheckerOptions } from './core';\n\n/**\n * Orchestrates a backend data transaction (read, compute, write).\n * Ensures that the transition from `prev` to `next` state is validated\n * against the provided invariant rules BEFORE the `write` handler is executed.\n * \n * Perfect for API routes, database operations, and event processors.\n */\nexport function createInvariantTransaction<State = any, Action = any, Result = any>(\n rules: InvariantRule<State, Action>[],\n options: CheckerOptions = {}\n) {\n const check = createInvariantChecker<State, Action>(rules, options);\n\n return async (\n action: Action,\n handlers: {\n /** Fetch the current state from your database/source */\n read: () => Promise<State> | State;\n /** Compute the proposed new state based on the current state */\n compute: (prev: State) => Promise<State> | State;\n /** Save the new state. This will NOT be called if the invariant check fails. */\n write: (next: State) => Promise<Result> | Result;\n }\n ): Promise<Result> => {\n const prev = await handlers.read();\n const next = await handlers.compute(prev);\n \n // Check if the transition is legal before saving\n check(prev, next, action);\n \n return await handlers.write(next);\n };\n}\n","/**\n * state-invariant/core\n *\n * Framework-agnostic engine. Doesn't know about Redux, Zustand, or anyone else.\n * You give it rules; it tells you whether a state TRANSITION (prev -> next, caused\n * by some action) was legal. This is different from Zod/Yup: those check the shape\n * of ONE object. This checks the relationship between two states and the action\n * that connected them.\n */\n\nexport interface InvariantRule<State = any, Action = any> {\n /** Human-readable name, shown in error messages and stack traces. */\n name: string;\n /**\n * Optional filter. If provided, the rule only runs when this returns true.\n * Lets you scope a rule to one action type, e.g. action => action.type === 'WITHDRAW'\n */\n when?: (action: Action) => boolean;\n /**\n * The actual check. Return true if the transition is valid, false if it violates\n * the invariant. Alternatively, return a string to indicate a violation and use\n * that string as the error message.\n */\n check: (prev: State, next: State, action: Action) => boolean | string;\n /**\n * Optional custom error message builder. Falls back to a generic message.\n * Ignored if `check` returns a string.\n */\n message?: (prev: State, next: State, action: Action) => string;\n}\n\nexport class InvariantViolationError extends Error {\n ruleName: string;\n prev: unknown;\n next: unknown;\n action: unknown;\n\n constructor(\n message: string,\n details: { ruleName: string; prev: unknown; next: unknown; action: unknown }\n ) {\n super(message);\n this.name = 'InvariantViolationError';\n this.ruleName = details.ruleName;\n this.prev = details.prev;\n this.next = details.next;\n this.action = details.action;\n }\n}\n\nexport interface CheckerOptions {\n /**\n * 'throw' (default) - throw InvariantViolationError immediately, stopping execution.\n * 'warn' - console.warn and continue. Useful in production if you don't want to crash.\n * 'collect' - don't throw or warn, just gather violations; call .getViolations() yourself.\n */\n mode?: 'throw' | 'warn' | 'collect';\n}\n\nexport function createInvariantChecker<State = any, Action = any>(\n rules: InvariantRule<State, Action>[],\n options: CheckerOptions = {}\n) {\n const mode = options.mode ?? 'throw';\n const violations: InvariantViolationError[] = [];\n\n function check(prev: State, next: State, action: Action): void {\n for (const rule of rules) {\n if (rule.when && !rule.when(action)) continue;\n\n let ok: boolean | string;\n try {\n ok = rule.check(prev, next, action);\n } catch (err) {\n // A rule that throws internally (e.g. undefined property access) is itself\n // a bug in the rule, but we surface it as a violation so it's not silently\n // swallowed by the store.\n ok = false;\n }\n\n if (ok === false || typeof ok === 'string') {\n const message =\n typeof ok === 'string'\n ? ok\n : rule.message\n ? rule.message(prev, next, action)\n : `Invariant \"${rule.name}\" violated`;\n\n const error = new InvariantViolationError(message, {\n ruleName: rule.name,\n prev,\n next,\n action,\n });\n\n if (mode === 'throw') {\n throw error;\n } else if (mode === 'warn') {\n // eslint-disable-next-line no-console\n console.warn(`[state-invariant] ${message}`, { prev, next, action });\n } else {\n violations.push(error);\n }\n }\n }\n }\n\n check.getViolations = () => violations.slice();\n check.clearViolations = () => {\n violations.length = 0;\n };\n\n return check;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC+BO,IAAM,0BAAN,cAAsC,MAAM;AAAA,EAMjD,YACE,SACA,SACA;AACA,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,WAAW,QAAQ;AACxB,SAAK,OAAO,QAAQ;AACpB,SAAK,OAAO,QAAQ;AACpB,SAAK,SAAS,QAAQ;AAAA,EACxB;AACF;AAWO,SAAS,uBACd,OACA,UAA0B,CAAC,GAC3B;AA9DF;AA+DE,QAAM,QAAO,aAAQ,SAAR,YAAgB;AAC7B,QAAM,aAAwC,CAAC;AAE/C,WAAS,MAAM,MAAa,MAAa,QAAsB;AAC7D,eAAW,QAAQ,OAAO;AACxB,UAAI,KAAK,QAAQ,CAAC,KAAK,KAAK,MAAM,EAAG;AAErC,UAAI;AACJ,UAAI;AACF,aAAK,KAAK,MAAM,MAAM,MAAM,MAAM;AAAA,MACpC,SAAS,KAAK;AAIZ,aAAK;AAAA,MACP;AAEA,UAAI,OAAO,SAAS,OAAO,OAAO,UAAU;AAC1C,cAAM,UACJ,OAAO,OAAO,WACV,KACA,KAAK,UACL,KAAK,QAAQ,MAAM,MAAM,MAAM,IAC/B,cAAc,KAAK,IAAI;AAE7B,cAAM,QAAQ,IAAI,wBAAwB,SAAS;AAAA,UACjD,UAAU,KAAK;AAAA,UACf;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAED,YAAI,SAAS,SAAS;AACpB,gBAAM;AAAA,QACR,WAAW,SAAS,QAAQ;AAE1B,kBAAQ,KAAK,qBAAqB,OAAO,IAAI,EAAE,MAAM,MAAM,OAAO,CAAC;AAAA,QACrE,OAAO;AACL,qBAAW,KAAK,KAAK;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,gBAAgB,MAAM,WAAW,MAAM;AAC7C,QAAM,kBAAkB,MAAM;AAC5B,eAAW,SAAS;AAAA,EACtB;AAEA,SAAO;AACT;;;ADxGO,SAAS,2BACd,OACA,UAA0B,CAAC,GAC3B;AACA,QAAM,QAAQ,uBAAsC,OAAO,OAAO;AAElE,SAAO,OACL,QACA,aAQoB;AACpB,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,UAAM,OAAO,MAAM,SAAS,QAAQ,IAAI;AAGxC,UAAM,MAAM,MAAM,MAAM;AAExB,WAAO,MAAM,SAAS,MAAM,IAAI;AAAA,EAClC;AACF;","names":[]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { InvariantRule, CheckerOptions } from './core';
|
|
2
|
+
/**
|
|
3
|
+
* Orchestrates a backend data transaction (read, compute, write).
|
|
4
|
+
* Ensures that the transition from `prev` to `next` state is validated
|
|
5
|
+
* against the provided invariant rules BEFORE the `write` handler is executed.
|
|
6
|
+
*
|
|
7
|
+
* Perfect for API routes, database operations, and event processors.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createInvariantTransaction<State = any, Action = any, Result = any>(rules: InvariantRule<State, Action>[], options?: CheckerOptions): (action: Action, handlers: {
|
|
10
|
+
/** Fetch the current state from your database/source */
|
|
11
|
+
read: () => Promise<State> | State;
|
|
12
|
+
/** Compute the proposed new state based on the current state */
|
|
13
|
+
compute: (prev: State) => Promise<State> | State;
|
|
14
|
+
/** Save the new state. This will NOT be called if the invariant check fails. */
|
|
15
|
+
write: (next: State) => Promise<Result> | Result;
|
|
16
|
+
}) => Promise<Result>;
|
package/dist/backend.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createInvariantChecker
|
|
3
|
+
} from "./chunk-DRSBKG3Y.js";
|
|
4
|
+
|
|
5
|
+
// src/backend.ts
|
|
6
|
+
function createInvariantTransaction(rules, options = {}) {
|
|
7
|
+
const check = createInvariantChecker(rules, options);
|
|
8
|
+
return async (action, handlers) => {
|
|
9
|
+
const prev = await handlers.read();
|
|
10
|
+
const next = await handlers.compute(prev);
|
|
11
|
+
check(prev, next, action);
|
|
12
|
+
return await handlers.write(next);
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
createInvariantTransaction
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/backend.ts"],"sourcesContent":["import { createInvariantChecker, InvariantRule, CheckerOptions } from './core';\n\n/**\n * Orchestrates a backend data transaction (read, compute, write).\n * Ensures that the transition from `prev` to `next` state is validated\n * against the provided invariant rules BEFORE the `write` handler is executed.\n * \n * Perfect for API routes, database operations, and event processors.\n */\nexport function createInvariantTransaction<State = any, Action = any, Result = any>(\n rules: InvariantRule<State, Action>[],\n options: CheckerOptions = {}\n) {\n const check = createInvariantChecker<State, Action>(rules, options);\n\n return async (\n action: Action,\n handlers: {\n /** Fetch the current state from your database/source */\n read: () => Promise<State> | State;\n /** Compute the proposed new state based on the current state */\n compute: (prev: State) => Promise<State> | State;\n /** Save the new state. This will NOT be called if the invariant check fails. */\n write: (next: State) => Promise<Result> | Result;\n }\n ): Promise<Result> => {\n const prev = await handlers.read();\n const next = await handlers.compute(prev);\n \n // Check if the transition is legal before saving\n check(prev, next, action);\n \n return await handlers.write(next);\n };\n}\n"],"mappings":";;;;;AASO,SAAS,2BACd,OACA,UAA0B,CAAC,GAC3B;AACA,QAAM,QAAQ,uBAAsC,OAAO,OAAO;AAElE,SAAO,OACL,QACA,aAQoB;AACpB,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,UAAM,OAAO,MAAM,SAAS,QAAQ,IAAI;AAGxC,UAAM,MAAM,MAAM,MAAM;AAExB,WAAO,MAAM,SAAS,MAAM,IAAI;AAAA,EAClC;AACF;","names":[]}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// src/core.ts
|
|
2
|
+
var InvariantViolationError = class extends Error {
|
|
3
|
+
constructor(message, details) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "InvariantViolationError";
|
|
6
|
+
this.ruleName = details.ruleName;
|
|
7
|
+
this.prev = details.prev;
|
|
8
|
+
this.next = details.next;
|
|
9
|
+
this.action = details.action;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
function createInvariantChecker(rules, options = {}) {
|
|
13
|
+
var _a;
|
|
14
|
+
const mode = (_a = options.mode) != null ? _a : "throw";
|
|
15
|
+
const violations = [];
|
|
16
|
+
function check(prev, next, action) {
|
|
17
|
+
for (const rule of rules) {
|
|
18
|
+
if (rule.when && !rule.when(action)) continue;
|
|
19
|
+
let ok;
|
|
20
|
+
try {
|
|
21
|
+
ok = rule.check(prev, next, action);
|
|
22
|
+
} catch (err) {
|
|
23
|
+
ok = false;
|
|
24
|
+
}
|
|
25
|
+
if (ok === false || typeof ok === "string") {
|
|
26
|
+
const message = typeof ok === "string" ? ok : rule.message ? rule.message(prev, next, action) : `Invariant "${rule.name}" violated`;
|
|
27
|
+
const error = new InvariantViolationError(message, {
|
|
28
|
+
ruleName: rule.name,
|
|
29
|
+
prev,
|
|
30
|
+
next,
|
|
31
|
+
action
|
|
32
|
+
});
|
|
33
|
+
if (mode === "throw") {
|
|
34
|
+
throw error;
|
|
35
|
+
} else if (mode === "warn") {
|
|
36
|
+
console.warn(`[state-invariant] ${message}`, { prev, next, action });
|
|
37
|
+
} else {
|
|
38
|
+
violations.push(error);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
check.getViolations = () => violations.slice();
|
|
44
|
+
check.clearViolations = () => {
|
|
45
|
+
violations.length = 0;
|
|
46
|
+
};
|
|
47
|
+
return check;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export {
|
|
51
|
+
InvariantViolationError,
|
|
52
|
+
createInvariantChecker
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=chunk-DRSBKG3Y.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core.ts"],"sourcesContent":["/**\n * state-invariant/core\n *\n * Framework-agnostic engine. Doesn't know about Redux, Zustand, or anyone else.\n * You give it rules; it tells you whether a state TRANSITION (prev -> next, caused\n * by some action) was legal. This is different from Zod/Yup: those check the shape\n * of ONE object. This checks the relationship between two states and the action\n * that connected them.\n */\n\nexport interface InvariantRule<State = any, Action = any> {\n /** Human-readable name, shown in error messages and stack traces. */\n name: string;\n /**\n * Optional filter. If provided, the rule only runs when this returns true.\n * Lets you scope a rule to one action type, e.g. action => action.type === 'WITHDRAW'\n */\n when?: (action: Action) => boolean;\n /**\n * The actual check. Return true if the transition is valid, false if it violates\n * the invariant. Alternatively, return a string to indicate a violation and use\n * that string as the error message.\n */\n check: (prev: State, next: State, action: Action) => boolean | string;\n /**\n * Optional custom error message builder. Falls back to a generic message.\n * Ignored if `check` returns a string.\n */\n message?: (prev: State, next: State, action: Action) => string;\n}\n\nexport class InvariantViolationError extends Error {\n ruleName: string;\n prev: unknown;\n next: unknown;\n action: unknown;\n\n constructor(\n message: string,\n details: { ruleName: string; prev: unknown; next: unknown; action: unknown }\n ) {\n super(message);\n this.name = 'InvariantViolationError';\n this.ruleName = details.ruleName;\n this.prev = details.prev;\n this.next = details.next;\n this.action = details.action;\n }\n}\n\nexport interface CheckerOptions {\n /**\n * 'throw' (default) - throw InvariantViolationError immediately, stopping execution.\n * 'warn' - console.warn and continue. Useful in production if you don't want to crash.\n * 'collect' - don't throw or warn, just gather violations; call .getViolations() yourself.\n */\n mode?: 'throw' | 'warn' | 'collect';\n}\n\nexport function createInvariantChecker<State = any, Action = any>(\n rules: InvariantRule<State, Action>[],\n options: CheckerOptions = {}\n) {\n const mode = options.mode ?? 'throw';\n const violations: InvariantViolationError[] = [];\n\n function check(prev: State, next: State, action: Action): void {\n for (const rule of rules) {\n if (rule.when && !rule.when(action)) continue;\n\n let ok: boolean | string;\n try {\n ok = rule.check(prev, next, action);\n } catch (err) {\n // A rule that throws internally (e.g. undefined property access) is itself\n // a bug in the rule, but we surface it as a violation so it's not silently\n // swallowed by the store.\n ok = false;\n }\n\n if (ok === false || typeof ok === 'string') {\n const message =\n typeof ok === 'string'\n ? ok\n : rule.message\n ? rule.message(prev, next, action)\n : `Invariant \"${rule.name}\" violated`;\n\n const error = new InvariantViolationError(message, {\n ruleName: rule.name,\n prev,\n next,\n action,\n });\n\n if (mode === 'throw') {\n throw error;\n } else if (mode === 'warn') {\n // eslint-disable-next-line no-console\n console.warn(`[state-invariant] ${message}`, { prev, next, action });\n } else {\n violations.push(error);\n }\n }\n }\n }\n\n check.getViolations = () => violations.slice();\n check.clearViolations = () => {\n violations.length = 0;\n };\n\n return check;\n}\n"],"mappings":";AA+BO,IAAM,0BAAN,cAAsC,MAAM;AAAA,EAMjD,YACE,SACA,SACA;AACA,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,WAAW,QAAQ;AACxB,SAAK,OAAO,QAAQ;AACpB,SAAK,OAAO,QAAQ;AACpB,SAAK,SAAS,QAAQ;AAAA,EACxB;AACF;AAWO,SAAS,uBACd,OACA,UAA0B,CAAC,GAC3B;AA9DF;AA+DE,QAAM,QAAO,aAAQ,SAAR,YAAgB;AAC7B,QAAM,aAAwC,CAAC;AAE/C,WAAS,MAAM,MAAa,MAAa,QAAsB;AAC7D,eAAW,QAAQ,OAAO;AACxB,UAAI,KAAK,QAAQ,CAAC,KAAK,KAAK,MAAM,EAAG;AAErC,UAAI;AACJ,UAAI;AACF,aAAK,KAAK,MAAM,MAAM,MAAM,MAAM;AAAA,MACpC,SAAS,KAAK;AAIZ,aAAK;AAAA,MACP;AAEA,UAAI,OAAO,SAAS,OAAO,OAAO,UAAU;AAC1C,cAAM,UACJ,OAAO,OAAO,WACV,KACA,KAAK,UACL,KAAK,QAAQ,MAAM,MAAM,MAAM,IAC/B,cAAc,KAAK,IAAI;AAE7B,cAAM,QAAQ,IAAI,wBAAwB,SAAS;AAAA,UACjD,UAAU,KAAK;AAAA,UACf;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAED,YAAI,SAAS,SAAS;AACpB,gBAAM;AAAA,QACR,WAAW,SAAS,QAAQ;AAE1B,kBAAQ,KAAK,qBAAqB,OAAO,IAAI,EAAE,MAAM,MAAM,OAAO,CAAC;AAAA,QACrE,OAAO;AACL,qBAAW,KAAK,KAAK;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,gBAAgB,MAAM,WAAW,MAAM;AAC7C,QAAM,kBAAkB,MAAM;AAC5B,eAAW,SAAS;AAAA,EACtB;AAEA,SAAO;AACT;","names":[]}
|
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* state-invariant/core
|
|
3
|
+
*
|
|
4
|
+
* Framework-agnostic engine. Doesn't know about Redux, Zustand, or anyone else.
|
|
5
|
+
* You give it rules; it tells you whether a state TRANSITION (prev -> next, caused
|
|
6
|
+
* by some action) was legal. This is different from Zod/Yup: those check the shape
|
|
7
|
+
* of ONE object. This checks the relationship between two states and the action
|
|
8
|
+
* that connected them.
|
|
9
|
+
*/
|
|
10
|
+
export interface InvariantRule<State = any, Action = any> {
|
|
11
|
+
/** Human-readable name, shown in error messages and stack traces. */
|
|
12
|
+
name: string;
|
|
13
|
+
/**
|
|
14
|
+
* Optional filter. If provided, the rule only runs when this returns true.
|
|
15
|
+
* Lets you scope a rule to one action type, e.g. action => action.type === 'WITHDRAW'
|
|
16
|
+
*/
|
|
17
|
+
when?: (action: Action) => boolean;
|
|
18
|
+
/**
|
|
19
|
+
* The actual check. Return true if the transition is valid, false if it violates
|
|
20
|
+
* the invariant. Alternatively, return a string to indicate a violation and use
|
|
21
|
+
* that string as the error message.
|
|
22
|
+
*/
|
|
23
|
+
check: (prev: State, next: State, action: Action) => boolean | string;
|
|
24
|
+
/**
|
|
25
|
+
* Optional custom error message builder. Falls back to a generic message.
|
|
26
|
+
* Ignored if `check` returns a string.
|
|
27
|
+
*/
|
|
28
|
+
message?: (prev: State, next: State, action: Action) => string;
|
|
29
|
+
}
|
|
30
|
+
export declare class InvariantViolationError extends Error {
|
|
31
|
+
ruleName: string;
|
|
32
|
+
prev: unknown;
|
|
33
|
+
next: unknown;
|
|
34
|
+
action: unknown;
|
|
35
|
+
constructor(message: string, details: {
|
|
36
|
+
ruleName: string;
|
|
37
|
+
prev: unknown;
|
|
38
|
+
next: unknown;
|
|
39
|
+
action: unknown;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
export interface CheckerOptions {
|
|
43
|
+
/**
|
|
44
|
+
* 'throw' (default) - throw InvariantViolationError immediately, stopping execution.
|
|
45
|
+
* 'warn' - console.warn and continue. Useful in production if you don't want to crash.
|
|
46
|
+
* 'collect' - don't throw or warn, just gather violations; call .getViolations() yourself.
|
|
47
|
+
*/
|
|
48
|
+
mode?: 'throw' | 'warn' | 'collect';
|
|
49
|
+
}
|
|
50
|
+
export declare function createInvariantChecker<State = any, Action = any>(rules: InvariantRule<State, Action>[], options?: CheckerOptions): {
|
|
51
|
+
(prev: State, next: State, action: Action): void;
|
|
52
|
+
getViolations(): InvariantViolationError[];
|
|
53
|
+
clearViolations(): void;
|
|
54
|
+
};
|
package/dist/generic.cjs
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/generic.ts
|
|
21
|
+
var generic_exports = {};
|
|
22
|
+
__export(generic_exports, {
|
|
23
|
+
wrapManual: () => wrapManual,
|
|
24
|
+
wrapReducer: () => wrapReducer
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(generic_exports);
|
|
27
|
+
|
|
28
|
+
// src/core.ts
|
|
29
|
+
var InvariantViolationError = class extends Error {
|
|
30
|
+
constructor(message, details) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.name = "InvariantViolationError";
|
|
33
|
+
this.ruleName = details.ruleName;
|
|
34
|
+
this.prev = details.prev;
|
|
35
|
+
this.next = details.next;
|
|
36
|
+
this.action = details.action;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
function createInvariantChecker(rules, options = {}) {
|
|
40
|
+
var _a;
|
|
41
|
+
const mode = (_a = options.mode) != null ? _a : "throw";
|
|
42
|
+
const violations = [];
|
|
43
|
+
function check(prev, next, action) {
|
|
44
|
+
for (const rule of rules) {
|
|
45
|
+
if (rule.when && !rule.when(action)) continue;
|
|
46
|
+
let ok;
|
|
47
|
+
try {
|
|
48
|
+
ok = rule.check(prev, next, action);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
ok = false;
|
|
51
|
+
}
|
|
52
|
+
if (ok === false || typeof ok === "string") {
|
|
53
|
+
const message = typeof ok === "string" ? ok : rule.message ? rule.message(prev, next, action) : `Invariant "${rule.name}" violated`;
|
|
54
|
+
const error = new InvariantViolationError(message, {
|
|
55
|
+
ruleName: rule.name,
|
|
56
|
+
prev,
|
|
57
|
+
next,
|
|
58
|
+
action
|
|
59
|
+
});
|
|
60
|
+
if (mode === "throw") {
|
|
61
|
+
throw error;
|
|
62
|
+
} else if (mode === "warn") {
|
|
63
|
+
console.warn(`[state-invariant] ${message}`, { prev, next, action });
|
|
64
|
+
} else {
|
|
65
|
+
violations.push(error);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
check.getViolations = () => violations.slice();
|
|
71
|
+
check.clearViolations = () => {
|
|
72
|
+
violations.length = 0;
|
|
73
|
+
};
|
|
74
|
+
return check;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/generic.ts
|
|
78
|
+
function wrapReducer(reducer, rules, options = {}) {
|
|
79
|
+
const check = createInvariantChecker(rules, options);
|
|
80
|
+
return (state, action) => {
|
|
81
|
+
const nextState = reducer(state, action);
|
|
82
|
+
check(state, nextState, action);
|
|
83
|
+
return nextState;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function wrapManual(store, rules, options = {}) {
|
|
87
|
+
const check = createInvariantChecker(rules, options);
|
|
88
|
+
let snapshot;
|
|
89
|
+
return {
|
|
90
|
+
before() {
|
|
91
|
+
snapshot = store.getState();
|
|
92
|
+
},
|
|
93
|
+
after(action) {
|
|
94
|
+
if (snapshot === void 0) {
|
|
95
|
+
throw new Error("wrapManual: after() called without before()");
|
|
96
|
+
}
|
|
97
|
+
const next = store.getState();
|
|
98
|
+
check(snapshot, next, action);
|
|
99
|
+
snapshot = void 0;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
104
|
+
0 && (module.exports = {
|
|
105
|
+
wrapManual,
|
|
106
|
+
wrapReducer
|
|
107
|
+
});
|
|
108
|
+
//# sourceMappingURL=generic.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/generic.ts","../src/core.ts"],"sourcesContent":["import { createInvariantChecker, InvariantRule, CheckerOptions } from './core';\n\n/**\n * Works with ANY reducer-shaped function: (state, action) => newState.\n * This is the \"plugs into anything\" escape hatch — if a library doesn't have\n * a dedicated adapter, and it exposes a plain reducer somewhere, wrap it with this.\n *\n * Usage:\n * const checkedReducer = wrapReducer(myReducer, [\n * { name: 'total-conserved', check: (p, n) => p.total === n.total }\n * ]);\n */\nexport function wrapReducer<State = any, Action = any>(\n reducer: (state: State, action: Action) => State,\n rules: InvariantRule<State, Action>[],\n options: CheckerOptions = {}\n) {\n const check = createInvariantChecker<State, Action>(rules, options);\n\n return (state: State, action: Action): State => {\n const nextState = reducer(state, action);\n check(state, nextState, action);\n return nextState;\n };\n}\n\n/**\n * Works with any object that has getState()/setState()-style access, by letting\n * you manually bracket a mutation. Useful for libraries with no middleware hooks\n * at all (vanilla stores, custom pub-sub state, etc).\n *\n * Usage:\n * const checker = wrapManual(myStore, rules);\n * checker.before();\n * myStore.doSomething();\n * checker.after(actionLabel);\n */\nexport function wrapManual<State = any, Action = any>(\n store: { getState: () => State },\n rules: InvariantRule<State, Action>[],\n options: CheckerOptions = {}\n) {\n const check = createInvariantChecker<State, Action>(rules, options);\n let snapshot: State | undefined;\n\n return {\n before() {\n snapshot = store.getState();\n },\n after(action: Action) {\n if (snapshot === undefined) {\n throw new Error('wrapManual: after() called without before()');\n }\n const next = store.getState();\n check(snapshot, next, action);\n snapshot = undefined;\n },\n };\n}\n","/**\n * state-invariant/core\n *\n * Framework-agnostic engine. Doesn't know about Redux, Zustand, or anyone else.\n * You give it rules; it tells you whether a state TRANSITION (prev -> next, caused\n * by some action) was legal. This is different from Zod/Yup: those check the shape\n * of ONE object. This checks the relationship between two states and the action\n * that connected them.\n */\n\nexport interface InvariantRule<State = any, Action = any> {\n /** Human-readable name, shown in error messages and stack traces. */\n name: string;\n /**\n * Optional filter. If provided, the rule only runs when this returns true.\n * Lets you scope a rule to one action type, e.g. action => action.type === 'WITHDRAW'\n */\n when?: (action: Action) => boolean;\n /**\n * The actual check. Return true if the transition is valid, false if it violates\n * the invariant. Alternatively, return a string to indicate a violation and use\n * that string as the error message.\n */\n check: (prev: State, next: State, action: Action) => boolean | string;\n /**\n * Optional custom error message builder. Falls back to a generic message.\n * Ignored if `check` returns a string.\n */\n message?: (prev: State, next: State, action: Action) => string;\n}\n\nexport class InvariantViolationError extends Error {\n ruleName: string;\n prev: unknown;\n next: unknown;\n action: unknown;\n\n constructor(\n message: string,\n details: { ruleName: string; prev: unknown; next: unknown; action: unknown }\n ) {\n super(message);\n this.name = 'InvariantViolationError';\n this.ruleName = details.ruleName;\n this.prev = details.prev;\n this.next = details.next;\n this.action = details.action;\n }\n}\n\nexport interface CheckerOptions {\n /**\n * 'throw' (default) - throw InvariantViolationError immediately, stopping execution.\n * 'warn' - console.warn and continue. Useful in production if you don't want to crash.\n * 'collect' - don't throw or warn, just gather violations; call .getViolations() yourself.\n */\n mode?: 'throw' | 'warn' | 'collect';\n}\n\nexport function createInvariantChecker<State = any, Action = any>(\n rules: InvariantRule<State, Action>[],\n options: CheckerOptions = {}\n) {\n const mode = options.mode ?? 'throw';\n const violations: InvariantViolationError[] = [];\n\n function check(prev: State, next: State, action: Action): void {\n for (const rule of rules) {\n if (rule.when && !rule.when(action)) continue;\n\n let ok: boolean | string;\n try {\n ok = rule.check(prev, next, action);\n } catch (err) {\n // A rule that throws internally (e.g. undefined property access) is itself\n // a bug in the rule, but we surface it as a violation so it's not silently\n // swallowed by the store.\n ok = false;\n }\n\n if (ok === false || typeof ok === 'string') {\n const message =\n typeof ok === 'string'\n ? ok\n : rule.message\n ? rule.message(prev, next, action)\n : `Invariant \"${rule.name}\" violated`;\n\n const error = new InvariantViolationError(message, {\n ruleName: rule.name,\n prev,\n next,\n action,\n });\n\n if (mode === 'throw') {\n throw error;\n } else if (mode === 'warn') {\n // eslint-disable-next-line no-console\n console.warn(`[state-invariant] ${message}`, { prev, next, action });\n } else {\n violations.push(error);\n }\n }\n }\n }\n\n check.getViolations = () => violations.slice();\n check.clearViolations = () => {\n violations.length = 0;\n };\n\n return check;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC+BO,IAAM,0BAAN,cAAsC,MAAM;AAAA,EAMjD,YACE,SACA,SACA;AACA,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,WAAW,QAAQ;AACxB,SAAK,OAAO,QAAQ;AACpB,SAAK,OAAO,QAAQ;AACpB,SAAK,SAAS,QAAQ;AAAA,EACxB;AACF;AAWO,SAAS,uBACd,OACA,UAA0B,CAAC,GAC3B;AA9DF;AA+DE,QAAM,QAAO,aAAQ,SAAR,YAAgB;AAC7B,QAAM,aAAwC,CAAC;AAE/C,WAAS,MAAM,MAAa,MAAa,QAAsB;AAC7D,eAAW,QAAQ,OAAO;AACxB,UAAI,KAAK,QAAQ,CAAC,KAAK,KAAK,MAAM,EAAG;AAErC,UAAI;AACJ,UAAI;AACF,aAAK,KAAK,MAAM,MAAM,MAAM,MAAM;AAAA,MACpC,SAAS,KAAK;AAIZ,aAAK;AAAA,MACP;AAEA,UAAI,OAAO,SAAS,OAAO,OAAO,UAAU;AAC1C,cAAM,UACJ,OAAO,OAAO,WACV,KACA,KAAK,UACL,KAAK,QAAQ,MAAM,MAAM,MAAM,IAC/B,cAAc,KAAK,IAAI;AAE7B,cAAM,QAAQ,IAAI,wBAAwB,SAAS;AAAA,UACjD,UAAU,KAAK;AAAA,UACf;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAED,YAAI,SAAS,SAAS;AACpB,gBAAM;AAAA,QACR,WAAW,SAAS,QAAQ;AAE1B,kBAAQ,KAAK,qBAAqB,OAAO,IAAI,EAAE,MAAM,MAAM,OAAO,CAAC;AAAA,QACrE,OAAO;AACL,qBAAW,KAAK,KAAK;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,gBAAgB,MAAM,WAAW,MAAM;AAC7C,QAAM,kBAAkB,MAAM;AAC5B,eAAW,SAAS;AAAA,EACtB;AAEA,SAAO;AACT;;;ADrGO,SAAS,YACd,SACA,OACA,UAA0B,CAAC,GAC3B;AACA,QAAM,QAAQ,uBAAsC,OAAO,OAAO;AAElE,SAAO,CAAC,OAAc,WAA0B;AAC9C,UAAM,YAAY,QAAQ,OAAO,MAAM;AACvC,UAAM,OAAO,WAAW,MAAM;AAC9B,WAAO;AAAA,EACT;AACF;AAaO,SAAS,WACd,OACA,OACA,UAA0B,CAAC,GAC3B;AACA,QAAM,QAAQ,uBAAsC,OAAO,OAAO;AAClE,MAAI;AAEJ,SAAO;AAAA,IACL,SAAS;AACP,iBAAW,MAAM,SAAS;AAAA,IAC5B;AAAA,IACA,MAAM,QAAgB;AACpB,UAAI,aAAa,QAAW;AAC1B,cAAM,IAAI,MAAM,6CAA6C;AAAA,MAC/D;AACA,YAAM,OAAO,MAAM,SAAS;AAC5B,YAAM,UAAU,MAAM,MAAM;AAC5B,iBAAW;AAAA,IACb;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { InvariantRule, CheckerOptions } from './core';
|
|
2
|
+
/**
|
|
3
|
+
* Works with ANY reducer-shaped function: (state, action) => newState.
|
|
4
|
+
* This is the "plugs into anything" escape hatch — if a library doesn't have
|
|
5
|
+
* a dedicated adapter, and it exposes a plain reducer somewhere, wrap it with this.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* const checkedReducer = wrapReducer(myReducer, [
|
|
9
|
+
* { name: 'total-conserved', check: (p, n) => p.total === n.total }
|
|
10
|
+
* ]);
|
|
11
|
+
*/
|
|
12
|
+
export declare function wrapReducer<State = any, Action = any>(reducer: (state: State, action: Action) => State, rules: InvariantRule<State, Action>[], options?: CheckerOptions): (state: State, action: Action) => State;
|
|
13
|
+
/**
|
|
14
|
+
* Works with any object that has getState()/setState()-style access, by letting
|
|
15
|
+
* you manually bracket a mutation. Useful for libraries with no middleware hooks
|
|
16
|
+
* at all (vanilla stores, custom pub-sub state, etc).
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
* const checker = wrapManual(myStore, rules);
|
|
20
|
+
* checker.before();
|
|
21
|
+
* myStore.doSomething();
|
|
22
|
+
* checker.after(actionLabel);
|
|
23
|
+
*/
|
|
24
|
+
export declare function wrapManual<State = any, Action = any>(store: {
|
|
25
|
+
getState: () => State;
|
|
26
|
+
}, rules: InvariantRule<State, Action>[], options?: CheckerOptions): {
|
|
27
|
+
before(): void;
|
|
28
|
+
after(action: Action): void;
|
|
29
|
+
};
|
package/dist/generic.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createInvariantChecker
|
|
3
|
+
} from "./chunk-DRSBKG3Y.js";
|
|
4
|
+
|
|
5
|
+
// src/generic.ts
|
|
6
|
+
function wrapReducer(reducer, rules, options = {}) {
|
|
7
|
+
const check = createInvariantChecker(rules, options);
|
|
8
|
+
return (state, action) => {
|
|
9
|
+
const nextState = reducer(state, action);
|
|
10
|
+
check(state, nextState, action);
|
|
11
|
+
return nextState;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function wrapManual(store, rules, options = {}) {
|
|
15
|
+
const check = createInvariantChecker(rules, options);
|
|
16
|
+
let snapshot;
|
|
17
|
+
return {
|
|
18
|
+
before() {
|
|
19
|
+
snapshot = store.getState();
|
|
20
|
+
},
|
|
21
|
+
after(action) {
|
|
22
|
+
if (snapshot === void 0) {
|
|
23
|
+
throw new Error("wrapManual: after() called without before()");
|
|
24
|
+
}
|
|
25
|
+
const next = store.getState();
|
|
26
|
+
check(snapshot, next, action);
|
|
27
|
+
snapshot = void 0;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
wrapManual,
|
|
33
|
+
wrapReducer
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=generic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/generic.ts"],"sourcesContent":["import { createInvariantChecker, InvariantRule, CheckerOptions } from './core';\n\n/**\n * Works with ANY reducer-shaped function: (state, action) => newState.\n * This is the \"plugs into anything\" escape hatch — if a library doesn't have\n * a dedicated adapter, and it exposes a plain reducer somewhere, wrap it with this.\n *\n * Usage:\n * const checkedReducer = wrapReducer(myReducer, [\n * { name: 'total-conserved', check: (p, n) => p.total === n.total }\n * ]);\n */\nexport function wrapReducer<State = any, Action = any>(\n reducer: (state: State, action: Action) => State,\n rules: InvariantRule<State, Action>[],\n options: CheckerOptions = {}\n) {\n const check = createInvariantChecker<State, Action>(rules, options);\n\n return (state: State, action: Action): State => {\n const nextState = reducer(state, action);\n check(state, nextState, action);\n return nextState;\n };\n}\n\n/**\n * Works with any object that has getState()/setState()-style access, by letting\n * you manually bracket a mutation. Useful for libraries with no middleware hooks\n * at all (vanilla stores, custom pub-sub state, etc).\n *\n * Usage:\n * const checker = wrapManual(myStore, rules);\n * checker.before();\n * myStore.doSomething();\n * checker.after(actionLabel);\n */\nexport function wrapManual<State = any, Action = any>(\n store: { getState: () => State },\n rules: InvariantRule<State, Action>[],\n options: CheckerOptions = {}\n) {\n const check = createInvariantChecker<State, Action>(rules, options);\n let snapshot: State | undefined;\n\n return {\n before() {\n snapshot = store.getState();\n },\n after(action: Action) {\n if (snapshot === undefined) {\n throw new Error('wrapManual: after() called without before()');\n }\n const next = store.getState();\n check(snapshot, next, action);\n snapshot = undefined;\n },\n };\n}\n"],"mappings":";;;;;AAYO,SAAS,YACd,SACA,OACA,UAA0B,CAAC,GAC3B;AACA,QAAM,QAAQ,uBAAsC,OAAO,OAAO;AAElE,SAAO,CAAC,OAAc,WAA0B;AAC9C,UAAM,YAAY,QAAQ,OAAO,MAAM;AACvC,UAAM,OAAO,WAAW,MAAM;AAC9B,WAAO;AAAA,EACT;AACF;AAaO,SAAS,WACd,OACA,OACA,UAA0B,CAAC,GAC3B;AACA,QAAM,QAAQ,uBAAsC,OAAO,OAAO;AAClE,MAAI;AAEJ,SAAO;AAAA,IACL,SAAS;AACP,iBAAW,MAAM,SAAS;AAAA,IAC5B;AAAA,IACA,MAAM,QAAgB;AACpB,UAAI,aAAa,QAAW;AAC1B,cAAM,IAAI,MAAM,6CAA6C;AAAA,MAC/D;AACA,YAAM,OAAO,MAAM,SAAS;AAC5B,YAAM,UAAU,MAAM,MAAM;AAC5B,iBAAW;AAAA,IACb;AAAA,EACF;AACF;","names":[]}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
InvariantViolationError: () => InvariantViolationError,
|
|
24
|
+
createInvariantChecker: () => createInvariantChecker
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(src_exports);
|
|
27
|
+
|
|
28
|
+
// src/core.ts
|
|
29
|
+
var InvariantViolationError = class extends Error {
|
|
30
|
+
constructor(message, details) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.name = "InvariantViolationError";
|
|
33
|
+
this.ruleName = details.ruleName;
|
|
34
|
+
this.prev = details.prev;
|
|
35
|
+
this.next = details.next;
|
|
36
|
+
this.action = details.action;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
function createInvariantChecker(rules, options = {}) {
|
|
40
|
+
var _a;
|
|
41
|
+
const mode = (_a = options.mode) != null ? _a : "throw";
|
|
42
|
+
const violations = [];
|
|
43
|
+
function check(prev, next, action) {
|
|
44
|
+
for (const rule of rules) {
|
|
45
|
+
if (rule.when && !rule.when(action)) continue;
|
|
46
|
+
let ok;
|
|
47
|
+
try {
|
|
48
|
+
ok = rule.check(prev, next, action);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
ok = false;
|
|
51
|
+
}
|
|
52
|
+
if (ok === false || typeof ok === "string") {
|
|
53
|
+
const message = typeof ok === "string" ? ok : rule.message ? rule.message(prev, next, action) : `Invariant "${rule.name}" violated`;
|
|
54
|
+
const error = new InvariantViolationError(message, {
|
|
55
|
+
ruleName: rule.name,
|
|
56
|
+
prev,
|
|
57
|
+
next,
|
|
58
|
+
action
|
|
59
|
+
});
|
|
60
|
+
if (mode === "throw") {
|
|
61
|
+
throw error;
|
|
62
|
+
} else if (mode === "warn") {
|
|
63
|
+
console.warn(`[state-invariant] ${message}`, { prev, next, action });
|
|
64
|
+
} else {
|
|
65
|
+
violations.push(error);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
check.getViolations = () => violations.slice();
|
|
71
|
+
check.clearViolations = () => {
|
|
72
|
+
violations.length = 0;
|
|
73
|
+
};
|
|
74
|
+
return check;
|
|
75
|
+
}
|
|
76
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
77
|
+
0 && (module.exports = {
|
|
78
|
+
InvariantViolationError,
|
|
79
|
+
createInvariantChecker
|
|
80
|
+
});
|
|
81
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/core.ts"],"sourcesContent":["export * from './core';\n","/**\n * state-invariant/core\n *\n * Framework-agnostic engine. Doesn't know about Redux, Zustand, or anyone else.\n * You give it rules; it tells you whether a state TRANSITION (prev -> next, caused\n * by some action) was legal. This is different from Zod/Yup: those check the shape\n * of ONE object. This checks the relationship between two states and the action\n * that connected them.\n */\n\nexport interface InvariantRule<State = any, Action = any> {\n /** Human-readable name, shown in error messages and stack traces. */\n name: string;\n /**\n * Optional filter. If provided, the rule only runs when this returns true.\n * Lets you scope a rule to one action type, e.g. action => action.type === 'WITHDRAW'\n */\n when?: (action: Action) => boolean;\n /**\n * The actual check. Return true if the transition is valid, false if it violates\n * the invariant. Alternatively, return a string to indicate a violation and use\n * that string as the error message.\n */\n check: (prev: State, next: State, action: Action) => boolean | string;\n /**\n * Optional custom error message builder. Falls back to a generic message.\n * Ignored if `check` returns a string.\n */\n message?: (prev: State, next: State, action: Action) => string;\n}\n\nexport class InvariantViolationError extends Error {\n ruleName: string;\n prev: unknown;\n next: unknown;\n action: unknown;\n\n constructor(\n message: string,\n details: { ruleName: string; prev: unknown; next: unknown; action: unknown }\n ) {\n super(message);\n this.name = 'InvariantViolationError';\n this.ruleName = details.ruleName;\n this.prev = details.prev;\n this.next = details.next;\n this.action = details.action;\n }\n}\n\nexport interface CheckerOptions {\n /**\n * 'throw' (default) - throw InvariantViolationError immediately, stopping execution.\n * 'warn' - console.warn and continue. Useful in production if you don't want to crash.\n * 'collect' - don't throw or warn, just gather violations; call .getViolations() yourself.\n */\n mode?: 'throw' | 'warn' | 'collect';\n}\n\nexport function createInvariantChecker<State = any, Action = any>(\n rules: InvariantRule<State, Action>[],\n options: CheckerOptions = {}\n) {\n const mode = options.mode ?? 'throw';\n const violations: InvariantViolationError[] = [];\n\n function check(prev: State, next: State, action: Action): void {\n for (const rule of rules) {\n if (rule.when && !rule.when(action)) continue;\n\n let ok: boolean | string;\n try {\n ok = rule.check(prev, next, action);\n } catch (err) {\n // A rule that throws internally (e.g. undefined property access) is itself\n // a bug in the rule, but we surface it as a violation so it's not silently\n // swallowed by the store.\n ok = false;\n }\n\n if (ok === false || typeof ok === 'string') {\n const message =\n typeof ok === 'string'\n ? ok\n : rule.message\n ? rule.message(prev, next, action)\n : `Invariant \"${rule.name}\" violated`;\n\n const error = new InvariantViolationError(message, {\n ruleName: rule.name,\n prev,\n next,\n action,\n });\n\n if (mode === 'throw') {\n throw error;\n } else if (mode === 'warn') {\n // eslint-disable-next-line no-console\n console.warn(`[state-invariant] ${message}`, { prev, next, action });\n } else {\n violations.push(error);\n }\n }\n }\n }\n\n check.getViolations = () => violations.slice();\n check.clearViolations = () => {\n violations.length = 0;\n };\n\n return check;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC+BO,IAAM,0BAAN,cAAsC,MAAM;AAAA,EAMjD,YACE,SACA,SACA;AACA,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,WAAW,QAAQ;AACxB,SAAK,OAAO,QAAQ;AACpB,SAAK,OAAO,QAAQ;AACpB,SAAK,SAAS,QAAQ;AAAA,EACxB;AACF;AAWO,SAAS,uBACd,OACA,UAA0B,CAAC,GAC3B;AA9DF;AA+DE,QAAM,QAAO,aAAQ,SAAR,YAAgB;AAC7B,QAAM,aAAwC,CAAC;AAE/C,WAAS,MAAM,MAAa,MAAa,QAAsB;AAC7D,eAAW,QAAQ,OAAO;AACxB,UAAI,KAAK,QAAQ,CAAC,KAAK,KAAK,MAAM,EAAG;AAErC,UAAI;AACJ,UAAI;AACF,aAAK,KAAK,MAAM,MAAM,MAAM,MAAM;AAAA,MACpC,SAAS,KAAK;AAIZ,aAAK;AAAA,MACP;AAEA,UAAI,OAAO,SAAS,OAAO,OAAO,UAAU;AAC1C,cAAM,UACJ,OAAO,OAAO,WACV,KACA,KAAK,UACL,KAAK,QAAQ,MAAM,MAAM,MAAM,IAC/B,cAAc,KAAK,IAAI;AAE7B,cAAM,QAAQ,IAAI,wBAAwB,SAAS;AAAA,UACjD,UAAU,KAAK;AAAA,UACf;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAED,YAAI,SAAS,SAAS;AACpB,gBAAM;AAAA,QACR,WAAW,SAAS,QAAQ;AAE1B,kBAAQ,KAAK,qBAAqB,OAAO,IAAI,EAAE,MAAM,MAAM,OAAO,CAAC;AAAA,QACrE,OAAO;AACL,qBAAW,KAAK,KAAK;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,gBAAgB,MAAM,WAAW,MAAM;AAC7C,QAAM,kBAAkB,MAAM;AAC5B,eAAW,SAAS;AAAA,EACtB;AAEA,SAAO;AACT;","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './core';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/redux.cjs
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/redux.ts
|
|
21
|
+
var redux_exports = {};
|
|
22
|
+
__export(redux_exports, {
|
|
23
|
+
createInvariantMiddleware: () => createInvariantMiddleware
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(redux_exports);
|
|
26
|
+
|
|
27
|
+
// src/core.ts
|
|
28
|
+
var InvariantViolationError = class extends Error {
|
|
29
|
+
constructor(message, details) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.name = "InvariantViolationError";
|
|
32
|
+
this.ruleName = details.ruleName;
|
|
33
|
+
this.prev = details.prev;
|
|
34
|
+
this.next = details.next;
|
|
35
|
+
this.action = details.action;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
function createInvariantChecker(rules, options = {}) {
|
|
39
|
+
var _a;
|
|
40
|
+
const mode = (_a = options.mode) != null ? _a : "throw";
|
|
41
|
+
const violations = [];
|
|
42
|
+
function check(prev, next, action) {
|
|
43
|
+
for (const rule of rules) {
|
|
44
|
+
if (rule.when && !rule.when(action)) continue;
|
|
45
|
+
let ok;
|
|
46
|
+
try {
|
|
47
|
+
ok = rule.check(prev, next, action);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
ok = false;
|
|
50
|
+
}
|
|
51
|
+
if (ok === false || typeof ok === "string") {
|
|
52
|
+
const message = typeof ok === "string" ? ok : rule.message ? rule.message(prev, next, action) : `Invariant "${rule.name}" violated`;
|
|
53
|
+
const error = new InvariantViolationError(message, {
|
|
54
|
+
ruleName: rule.name,
|
|
55
|
+
prev,
|
|
56
|
+
next,
|
|
57
|
+
action
|
|
58
|
+
});
|
|
59
|
+
if (mode === "throw") {
|
|
60
|
+
throw error;
|
|
61
|
+
} else if (mode === "warn") {
|
|
62
|
+
console.warn(`[state-invariant] ${message}`, { prev, next, action });
|
|
63
|
+
} else {
|
|
64
|
+
violations.push(error);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
check.getViolations = () => violations.slice();
|
|
70
|
+
check.clearViolations = () => {
|
|
71
|
+
violations.length = 0;
|
|
72
|
+
};
|
|
73
|
+
return check;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/redux.ts
|
|
77
|
+
function createInvariantMiddleware(rules, options = {}) {
|
|
78
|
+
const check = createInvariantChecker(rules, options);
|
|
79
|
+
return (storeApi) => (next) => (action) => {
|
|
80
|
+
const prev = storeApi.getState();
|
|
81
|
+
const result = next(action);
|
|
82
|
+
const nextState = storeApi.getState();
|
|
83
|
+
check(prev, nextState, action);
|
|
84
|
+
return result;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
88
|
+
0 && (module.exports = {
|
|
89
|
+
createInvariantMiddleware
|
|
90
|
+
});
|
|
91
|
+
//# sourceMappingURL=redux.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/redux.ts","../src/core.ts"],"sourcesContent":["import { createInvariantChecker, InvariantRule, CheckerOptions } from './core';\n\n/**\n * Redux middleware. Runs your invariant rules after every dispatch, comparing\n * state before and after.\n *\n * Usage:\n * import { createInvariantMiddleware } from 'state-invariant/redux';\n *\n * const middleware = createInvariantMiddleware([\n * {\n * name: 'balance-never-negative',\n * check: (prev, next) => next.balance >= 0,\n * message: (prev, next) => `balance went negative: ${next.balance}`,\n * },\n * ]);\n *\n * const store = configureStore({\n * reducer,\n * middleware: (getDefault) => getDefault().concat(middleware),\n * });\n */\nexport function createInvariantMiddleware<State = any, Action = any>(\n rules: InvariantRule<State, Action>[],\n options: CheckerOptions = {}\n) {\n const check = createInvariantChecker<State, Action>(rules, options);\n\n return (storeApi: { getState: () => State }) =>\n (next: (action: Action) => unknown) =>\n (action: Action) => {\n const prev = storeApi.getState();\n const result = next(action);\n const nextState = storeApi.getState();\n check(prev, nextState, action);\n return result;\n };\n}\n","/**\n * state-invariant/core\n *\n * Framework-agnostic engine. Doesn't know about Redux, Zustand, or anyone else.\n * You give it rules; it tells you whether a state TRANSITION (prev -> next, caused\n * by some action) was legal. This is different from Zod/Yup: those check the shape\n * of ONE object. This checks the relationship between two states and the action\n * that connected them.\n */\n\nexport interface InvariantRule<State = any, Action = any> {\n /** Human-readable name, shown in error messages and stack traces. */\n name: string;\n /**\n * Optional filter. If provided, the rule only runs when this returns true.\n * Lets you scope a rule to one action type, e.g. action => action.type === 'WITHDRAW'\n */\n when?: (action: Action) => boolean;\n /**\n * The actual check. Return true if the transition is valid, false if it violates\n * the invariant. Alternatively, return a string to indicate a violation and use\n * that string as the error message.\n */\n check: (prev: State, next: State, action: Action) => boolean | string;\n /**\n * Optional custom error message builder. Falls back to a generic message.\n * Ignored if `check` returns a string.\n */\n message?: (prev: State, next: State, action: Action) => string;\n}\n\nexport class InvariantViolationError extends Error {\n ruleName: string;\n prev: unknown;\n next: unknown;\n action: unknown;\n\n constructor(\n message: string,\n details: { ruleName: string; prev: unknown; next: unknown; action: unknown }\n ) {\n super(message);\n this.name = 'InvariantViolationError';\n this.ruleName = details.ruleName;\n this.prev = details.prev;\n this.next = details.next;\n this.action = details.action;\n }\n}\n\nexport interface CheckerOptions {\n /**\n * 'throw' (default) - throw InvariantViolationError immediately, stopping execution.\n * 'warn' - console.warn and continue. Useful in production if you don't want to crash.\n * 'collect' - don't throw or warn, just gather violations; call .getViolations() yourself.\n */\n mode?: 'throw' | 'warn' | 'collect';\n}\n\nexport function createInvariantChecker<State = any, Action = any>(\n rules: InvariantRule<State, Action>[],\n options: CheckerOptions = {}\n) {\n const mode = options.mode ?? 'throw';\n const violations: InvariantViolationError[] = [];\n\n function check(prev: State, next: State, action: Action): void {\n for (const rule of rules) {\n if (rule.when && !rule.when(action)) continue;\n\n let ok: boolean | string;\n try {\n ok = rule.check(prev, next, action);\n } catch (err) {\n // A rule that throws internally (e.g. undefined property access) is itself\n // a bug in the rule, but we surface it as a violation so it's not silently\n // swallowed by the store.\n ok = false;\n }\n\n if (ok === false || typeof ok === 'string') {\n const message =\n typeof ok === 'string'\n ? ok\n : rule.message\n ? rule.message(prev, next, action)\n : `Invariant \"${rule.name}\" violated`;\n\n const error = new InvariantViolationError(message, {\n ruleName: rule.name,\n prev,\n next,\n action,\n });\n\n if (mode === 'throw') {\n throw error;\n } else if (mode === 'warn') {\n // eslint-disable-next-line no-console\n console.warn(`[state-invariant] ${message}`, { prev, next, action });\n } else {\n violations.push(error);\n }\n }\n }\n }\n\n check.getViolations = () => violations.slice();\n check.clearViolations = () => {\n violations.length = 0;\n };\n\n return check;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC+BO,IAAM,0BAAN,cAAsC,MAAM;AAAA,EAMjD,YACE,SACA,SACA;AACA,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,WAAW,QAAQ;AACxB,SAAK,OAAO,QAAQ;AACpB,SAAK,OAAO,QAAQ;AACpB,SAAK,SAAS,QAAQ;AAAA,EACxB;AACF;AAWO,SAAS,uBACd,OACA,UAA0B,CAAC,GAC3B;AA9DF;AA+DE,QAAM,QAAO,aAAQ,SAAR,YAAgB;AAC7B,QAAM,aAAwC,CAAC;AAE/C,WAAS,MAAM,MAAa,MAAa,QAAsB;AAC7D,eAAW,QAAQ,OAAO;AACxB,UAAI,KAAK,QAAQ,CAAC,KAAK,KAAK,MAAM,EAAG;AAErC,UAAI;AACJ,UAAI;AACF,aAAK,KAAK,MAAM,MAAM,MAAM,MAAM;AAAA,MACpC,SAAS,KAAK;AAIZ,aAAK;AAAA,MACP;AAEA,UAAI,OAAO,SAAS,OAAO,OAAO,UAAU;AAC1C,cAAM,UACJ,OAAO,OAAO,WACV,KACA,KAAK,UACL,KAAK,QAAQ,MAAM,MAAM,MAAM,IAC/B,cAAc,KAAK,IAAI;AAE7B,cAAM,QAAQ,IAAI,wBAAwB,SAAS;AAAA,UACjD,UAAU,KAAK;AAAA,UACf;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAED,YAAI,SAAS,SAAS;AACpB,gBAAM;AAAA,QACR,WAAW,SAAS,QAAQ;AAE1B,kBAAQ,KAAK,qBAAqB,OAAO,IAAI,EAAE,MAAM,MAAM,OAAO,CAAC;AAAA,QACrE,OAAO;AACL,qBAAW,KAAK,KAAK;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,gBAAgB,MAAM,WAAW,MAAM;AAC7C,QAAM,kBAAkB,MAAM;AAC5B,eAAW,SAAS;AAAA,EACtB;AAEA,SAAO;AACT;;;AD3FO,SAAS,0BACd,OACA,UAA0B,CAAC,GAC3B;AACA,QAAM,QAAQ,uBAAsC,OAAO,OAAO;AAElE,SAAO,CAAC,aACN,CAAC,SACD,CAAC,WAAmB;AAClB,UAAM,OAAO,SAAS,SAAS;AAC/B,UAAM,SAAS,KAAK,MAAM;AAC1B,UAAM,YAAY,SAAS,SAAS;AACpC,UAAM,MAAM,WAAW,MAAM;AAC7B,WAAO;AAAA,EACT;AACJ;","names":[]}
|
package/dist/redux.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { InvariantRule, CheckerOptions } from './core';
|
|
2
|
+
/**
|
|
3
|
+
* Redux middleware. Runs your invariant rules after every dispatch, comparing
|
|
4
|
+
* state before and after.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* import { createInvariantMiddleware } from 'state-invariant/redux';
|
|
8
|
+
*
|
|
9
|
+
* const middleware = createInvariantMiddleware([
|
|
10
|
+
* {
|
|
11
|
+
* name: 'balance-never-negative',
|
|
12
|
+
* check: (prev, next) => next.balance >= 0,
|
|
13
|
+
* message: (prev, next) => `balance went negative: ${next.balance}`,
|
|
14
|
+
* },
|
|
15
|
+
* ]);
|
|
16
|
+
*
|
|
17
|
+
* const store = configureStore({
|
|
18
|
+
* reducer,
|
|
19
|
+
* middleware: (getDefault) => getDefault().concat(middleware),
|
|
20
|
+
* });
|
|
21
|
+
*/
|
|
22
|
+
export declare function createInvariantMiddleware<State = any, Action = any>(rules: InvariantRule<State, Action>[], options?: CheckerOptions): (storeApi: {
|
|
23
|
+
getState: () => State;
|
|
24
|
+
}) => (next: (action: Action) => unknown) => (action: Action) => unknown;
|
package/dist/redux.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createInvariantChecker
|
|
3
|
+
} from "./chunk-DRSBKG3Y.js";
|
|
4
|
+
|
|
5
|
+
// src/redux.ts
|
|
6
|
+
function createInvariantMiddleware(rules, options = {}) {
|
|
7
|
+
const check = createInvariantChecker(rules, options);
|
|
8
|
+
return (storeApi) => (next) => (action) => {
|
|
9
|
+
const prev = storeApi.getState();
|
|
10
|
+
const result = next(action);
|
|
11
|
+
const nextState = storeApi.getState();
|
|
12
|
+
check(prev, nextState, action);
|
|
13
|
+
return result;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export {
|
|
17
|
+
createInvariantMiddleware
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=redux.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/redux.ts"],"sourcesContent":["import { createInvariantChecker, InvariantRule, CheckerOptions } from './core';\n\n/**\n * Redux middleware. Runs your invariant rules after every dispatch, comparing\n * state before and after.\n *\n * Usage:\n * import { createInvariantMiddleware } from 'state-invariant/redux';\n *\n * const middleware = createInvariantMiddleware([\n * {\n * name: 'balance-never-negative',\n * check: (prev, next) => next.balance >= 0,\n * message: (prev, next) => `balance went negative: ${next.balance}`,\n * },\n * ]);\n *\n * const store = configureStore({\n * reducer,\n * middleware: (getDefault) => getDefault().concat(middleware),\n * });\n */\nexport function createInvariantMiddleware<State = any, Action = any>(\n rules: InvariantRule<State, Action>[],\n options: CheckerOptions = {}\n) {\n const check = createInvariantChecker<State, Action>(rules, options);\n\n return (storeApi: { getState: () => State }) =>\n (next: (action: Action) => unknown) =>\n (action: Action) => {\n const prev = storeApi.getState();\n const result = next(action);\n const nextState = storeApi.getState();\n check(prev, nextState, action);\n return result;\n };\n}\n"],"mappings":";;;;;AAsBO,SAAS,0BACd,OACA,UAA0B,CAAC,GAC3B;AACA,QAAM,QAAQ,uBAAsC,OAAO,OAAO;AAElE,SAAO,CAAC,aACN,CAAC,SACD,CAAC,WAAmB;AAClB,UAAM,OAAO,SAAS,SAAS;AAC/B,UAAM,SAAS,KAAK,MAAM;AAC1B,UAAM,YAAY,SAAS,SAAS;AACpC,UAAM,MAAM,WAAW,MAAM;AAC7B,WAAO;AAAA,EACT;AACJ;","names":[]}
|
package/dist/zustand.cjs
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/zustand.ts
|
|
21
|
+
var zustand_exports = {};
|
|
22
|
+
__export(zustand_exports, {
|
|
23
|
+
invariant: () => invariant
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(zustand_exports);
|
|
26
|
+
|
|
27
|
+
// src/core.ts
|
|
28
|
+
var InvariantViolationError = class extends Error {
|
|
29
|
+
constructor(message, details) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.name = "InvariantViolationError";
|
|
32
|
+
this.ruleName = details.ruleName;
|
|
33
|
+
this.prev = details.prev;
|
|
34
|
+
this.next = details.next;
|
|
35
|
+
this.action = details.action;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
function createInvariantChecker(rules, options = {}) {
|
|
39
|
+
var _a;
|
|
40
|
+
const mode = (_a = options.mode) != null ? _a : "throw";
|
|
41
|
+
const violations = [];
|
|
42
|
+
function check(prev, next, action) {
|
|
43
|
+
for (const rule of rules) {
|
|
44
|
+
if (rule.when && !rule.when(action)) continue;
|
|
45
|
+
let ok;
|
|
46
|
+
try {
|
|
47
|
+
ok = rule.check(prev, next, action);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
ok = false;
|
|
50
|
+
}
|
|
51
|
+
if (ok === false || typeof ok === "string") {
|
|
52
|
+
const message = typeof ok === "string" ? ok : rule.message ? rule.message(prev, next, action) : `Invariant "${rule.name}" violated`;
|
|
53
|
+
const error = new InvariantViolationError(message, {
|
|
54
|
+
ruleName: rule.name,
|
|
55
|
+
prev,
|
|
56
|
+
next,
|
|
57
|
+
action
|
|
58
|
+
});
|
|
59
|
+
if (mode === "throw") {
|
|
60
|
+
throw error;
|
|
61
|
+
} else if (mode === "warn") {
|
|
62
|
+
console.warn(`[state-invariant] ${message}`, { prev, next, action });
|
|
63
|
+
} else {
|
|
64
|
+
violations.push(error);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
check.getViolations = () => violations.slice();
|
|
70
|
+
check.clearViolations = () => {
|
|
71
|
+
violations.length = 0;
|
|
72
|
+
};
|
|
73
|
+
return check;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/zustand.ts
|
|
77
|
+
function invariant(rules, options = {}) {
|
|
78
|
+
const check = createInvariantChecker(rules, options);
|
|
79
|
+
return (config) => (set, get, api) => {
|
|
80
|
+
const wrappedSet = (...args) => {
|
|
81
|
+
const prev = get();
|
|
82
|
+
set(...args);
|
|
83
|
+
const next = get();
|
|
84
|
+
const action = args.length > 2 ? args[2] : void 0;
|
|
85
|
+
check(prev, next, action);
|
|
86
|
+
};
|
|
87
|
+
return config(wrappedSet, get, api);
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
91
|
+
0 && (module.exports = {
|
|
92
|
+
invariant
|
|
93
|
+
});
|
|
94
|
+
//# sourceMappingURL=zustand.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/zustand.ts","../src/core.ts"],"sourcesContent":["import { createInvariantChecker, InvariantRule, CheckerOptions } from './core';\n\n/**\n * Zustand middleware. Wraps `set` so every state update is checked before\n * and after.\n *\n * Note: plain Zustand doesn't have a first-class \"action\" object like Redux does,\n * so `action` will be undefined unless you're using Zustand's `redux` middleware\n * on top (in which case compose this OUTSIDE that one, closer to `create`).\n *\n * Usage:\n * import { create } from 'zustand';\n * import { invariant } from 'state-invariant/zustand';\n *\n * const useStore = create(\n * invariant([\n * { name: 'count-never-negative', check: (p, n) => n.count >= 0 }\n * ])(\n * (set) => ({ count: 0, decrement: () => set((s) => ({ count: s.count - 1 })) })\n * )\n * );\n */\nexport function invariant<State = any, Action = any>(\n rules: InvariantRule<State, Action>[],\n options: CheckerOptions = {}\n) {\n const check = createInvariantChecker<State, Action>(rules, options);\n\n return (config: (set: any, get: any, api: any) => State) =>\n (set: any, get: any, api: any) => {\n const wrappedSet = (...args: any[]) => {\n const prev = get();\n set(...args);\n const next = get();\n // Zustand `set` signature: set(partial, replace, actionType)\n const action = (args.length > 2 ? args[2] : undefined) as Action;\n check(prev, next, action);\n };\n return config(wrappedSet, get, api);\n };\n}\n","/**\n * state-invariant/core\n *\n * Framework-agnostic engine. Doesn't know about Redux, Zustand, or anyone else.\n * You give it rules; it tells you whether a state TRANSITION (prev -> next, caused\n * by some action) was legal. This is different from Zod/Yup: those check the shape\n * of ONE object. This checks the relationship between two states and the action\n * that connected them.\n */\n\nexport interface InvariantRule<State = any, Action = any> {\n /** Human-readable name, shown in error messages and stack traces. */\n name: string;\n /**\n * Optional filter. If provided, the rule only runs when this returns true.\n * Lets you scope a rule to one action type, e.g. action => action.type === 'WITHDRAW'\n */\n when?: (action: Action) => boolean;\n /**\n * The actual check. Return true if the transition is valid, false if it violates\n * the invariant. Alternatively, return a string to indicate a violation and use\n * that string as the error message.\n */\n check: (prev: State, next: State, action: Action) => boolean | string;\n /**\n * Optional custom error message builder. Falls back to a generic message.\n * Ignored if `check` returns a string.\n */\n message?: (prev: State, next: State, action: Action) => string;\n}\n\nexport class InvariantViolationError extends Error {\n ruleName: string;\n prev: unknown;\n next: unknown;\n action: unknown;\n\n constructor(\n message: string,\n details: { ruleName: string; prev: unknown; next: unknown; action: unknown }\n ) {\n super(message);\n this.name = 'InvariantViolationError';\n this.ruleName = details.ruleName;\n this.prev = details.prev;\n this.next = details.next;\n this.action = details.action;\n }\n}\n\nexport interface CheckerOptions {\n /**\n * 'throw' (default) - throw InvariantViolationError immediately, stopping execution.\n * 'warn' - console.warn and continue. Useful in production if you don't want to crash.\n * 'collect' - don't throw or warn, just gather violations; call .getViolations() yourself.\n */\n mode?: 'throw' | 'warn' | 'collect';\n}\n\nexport function createInvariantChecker<State = any, Action = any>(\n rules: InvariantRule<State, Action>[],\n options: CheckerOptions = {}\n) {\n const mode = options.mode ?? 'throw';\n const violations: InvariantViolationError[] = [];\n\n function check(prev: State, next: State, action: Action): void {\n for (const rule of rules) {\n if (rule.when && !rule.when(action)) continue;\n\n let ok: boolean | string;\n try {\n ok = rule.check(prev, next, action);\n } catch (err) {\n // A rule that throws internally (e.g. undefined property access) is itself\n // a bug in the rule, but we surface it as a violation so it's not silently\n // swallowed by the store.\n ok = false;\n }\n\n if (ok === false || typeof ok === 'string') {\n const message =\n typeof ok === 'string'\n ? ok\n : rule.message\n ? rule.message(prev, next, action)\n : `Invariant \"${rule.name}\" violated`;\n\n const error = new InvariantViolationError(message, {\n ruleName: rule.name,\n prev,\n next,\n action,\n });\n\n if (mode === 'throw') {\n throw error;\n } else if (mode === 'warn') {\n // eslint-disable-next-line no-console\n console.warn(`[state-invariant] ${message}`, { prev, next, action });\n } else {\n violations.push(error);\n }\n }\n }\n }\n\n check.getViolations = () => violations.slice();\n check.clearViolations = () => {\n violations.length = 0;\n };\n\n return check;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC+BO,IAAM,0BAAN,cAAsC,MAAM;AAAA,EAMjD,YACE,SACA,SACA;AACA,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,WAAW,QAAQ;AACxB,SAAK,OAAO,QAAQ;AACpB,SAAK,OAAO,QAAQ;AACpB,SAAK,SAAS,QAAQ;AAAA,EACxB;AACF;AAWO,SAAS,uBACd,OACA,UAA0B,CAAC,GAC3B;AA9DF;AA+DE,QAAM,QAAO,aAAQ,SAAR,YAAgB;AAC7B,QAAM,aAAwC,CAAC;AAE/C,WAAS,MAAM,MAAa,MAAa,QAAsB;AAC7D,eAAW,QAAQ,OAAO;AACxB,UAAI,KAAK,QAAQ,CAAC,KAAK,KAAK,MAAM,EAAG;AAErC,UAAI;AACJ,UAAI;AACF,aAAK,KAAK,MAAM,MAAM,MAAM,MAAM;AAAA,MACpC,SAAS,KAAK;AAIZ,aAAK;AAAA,MACP;AAEA,UAAI,OAAO,SAAS,OAAO,OAAO,UAAU;AAC1C,cAAM,UACJ,OAAO,OAAO,WACV,KACA,KAAK,UACL,KAAK,QAAQ,MAAM,MAAM,MAAM,IAC/B,cAAc,KAAK,IAAI;AAE7B,cAAM,QAAQ,IAAI,wBAAwB,SAAS;AAAA,UACjD,UAAU,KAAK;AAAA,UACf;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAED,YAAI,SAAS,SAAS;AACpB,gBAAM;AAAA,QACR,WAAW,SAAS,QAAQ;AAE1B,kBAAQ,KAAK,qBAAqB,OAAO,IAAI,EAAE,MAAM,MAAM,OAAO,CAAC;AAAA,QACrE,OAAO;AACL,qBAAW,KAAK,KAAK;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,gBAAgB,MAAM,WAAW,MAAM;AAC7C,QAAM,kBAAkB,MAAM;AAC5B,eAAW,SAAS;AAAA,EACtB;AAEA,SAAO;AACT;;;AD3FO,SAAS,UACd,OACA,UAA0B,CAAC,GAC3B;AACA,QAAM,QAAQ,uBAAsC,OAAO,OAAO;AAElE,SAAO,CAAC,WACN,CAAC,KAAU,KAAU,QAAa;AAChC,UAAM,aAAa,IAAI,SAAgB;AACrC,YAAM,OAAO,IAAI;AACjB,UAAI,GAAG,IAAI;AACX,YAAM,OAAO,IAAI;AAEjB,YAAM,SAAU,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI;AAC5C,YAAM,MAAM,MAAM,MAAM;AAAA,IAC1B;AACA,WAAO,OAAO,YAAY,KAAK,GAAG;AAAA,EACpC;AACJ;","names":[]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { InvariantRule, CheckerOptions } from './core';
|
|
2
|
+
/**
|
|
3
|
+
* Zustand middleware. Wraps `set` so every state update is checked before
|
|
4
|
+
* and after.
|
|
5
|
+
*
|
|
6
|
+
* Note: plain Zustand doesn't have a first-class "action" object like Redux does,
|
|
7
|
+
* so `action` will be undefined unless you're using Zustand's `redux` middleware
|
|
8
|
+
* on top (in which case compose this OUTSIDE that one, closer to `create`).
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* import { create } from 'zustand';
|
|
12
|
+
* import { invariant } from 'state-invariant/zustand';
|
|
13
|
+
*
|
|
14
|
+
* const useStore = create(
|
|
15
|
+
* invariant([
|
|
16
|
+
* { name: 'count-never-negative', check: (p, n) => n.count >= 0 }
|
|
17
|
+
* ])(
|
|
18
|
+
* (set) => ({ count: 0, decrement: () => set((s) => ({ count: s.count - 1 })) })
|
|
19
|
+
* )
|
|
20
|
+
* );
|
|
21
|
+
*/
|
|
22
|
+
export declare function invariant<State = any, Action = any>(rules: InvariantRule<State, Action>[], options?: CheckerOptions): (config: (set: any, get: any, api: any) => State) => (set: any, get: any, api: any) => State;
|
package/dist/zustand.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createInvariantChecker
|
|
3
|
+
} from "./chunk-DRSBKG3Y.js";
|
|
4
|
+
|
|
5
|
+
// src/zustand.ts
|
|
6
|
+
function invariant(rules, options = {}) {
|
|
7
|
+
const check = createInvariantChecker(rules, options);
|
|
8
|
+
return (config) => (set, get, api) => {
|
|
9
|
+
const wrappedSet = (...args) => {
|
|
10
|
+
const prev = get();
|
|
11
|
+
set(...args);
|
|
12
|
+
const next = get();
|
|
13
|
+
const action = args.length > 2 ? args[2] : void 0;
|
|
14
|
+
check(prev, next, action);
|
|
15
|
+
};
|
|
16
|
+
return config(wrappedSet, get, api);
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export {
|
|
20
|
+
invariant
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=zustand.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/zustand.ts"],"sourcesContent":["import { createInvariantChecker, InvariantRule, CheckerOptions } from './core';\n\n/**\n * Zustand middleware. Wraps `set` so every state update is checked before\n * and after.\n *\n * Note: plain Zustand doesn't have a first-class \"action\" object like Redux does,\n * so `action` will be undefined unless you're using Zustand's `redux` middleware\n * on top (in which case compose this OUTSIDE that one, closer to `create`).\n *\n * Usage:\n * import { create } from 'zustand';\n * import { invariant } from 'state-invariant/zustand';\n *\n * const useStore = create(\n * invariant([\n * { name: 'count-never-negative', check: (p, n) => n.count >= 0 }\n * ])(\n * (set) => ({ count: 0, decrement: () => set((s) => ({ count: s.count - 1 })) })\n * )\n * );\n */\nexport function invariant<State = any, Action = any>(\n rules: InvariantRule<State, Action>[],\n options: CheckerOptions = {}\n) {\n const check = createInvariantChecker<State, Action>(rules, options);\n\n return (config: (set: any, get: any, api: any) => State) =>\n (set: any, get: any, api: any) => {\n const wrappedSet = (...args: any[]) => {\n const prev = get();\n set(...args);\n const next = get();\n // Zustand `set` signature: set(partial, replace, actionType)\n const action = (args.length > 2 ? args[2] : undefined) as Action;\n check(prev, next, action);\n };\n return config(wrappedSet, get, api);\n };\n}\n"],"mappings":";;;;;AAsBO,SAAS,UACd,OACA,UAA0B,CAAC,GAC3B;AACA,QAAM,QAAQ,uBAAsC,OAAO,OAAO;AAElE,SAAO,CAAC,WACN,CAAC,KAAU,KAAU,QAAa;AAChC,UAAM,aAAa,IAAI,SAAgB;AACrC,YAAM,OAAO,IAAI;AACjB,UAAI,GAAG,IAAI;AACX,YAAM,OAAO,IAAI;AAEjB,YAAM,SAAU,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI;AAC5C,YAAM,MAAM,MAAM,MAAM;AAAA,IAC1B;AACA,WAAO,OAAO,YAAY,KAAK,GAAG;AAAA,EACpC;AACJ;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "state-invariant",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Cross-verify state transitions against declared invariants, in any state management library (Redux, Zustand, or plain reducers). Not shape validation like Zod - transition validation: given prev state, next state, and the action that connected them, was it legal?",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./redux": {
|
|
16
|
+
"types": "./dist/redux.d.ts",
|
|
17
|
+
"import": "./dist/redux.js",
|
|
18
|
+
"require": "./dist/redux.cjs"
|
|
19
|
+
},
|
|
20
|
+
"./zustand": {
|
|
21
|
+
"types": "./dist/zustand.d.ts",
|
|
22
|
+
"import": "./dist/zustand.js",
|
|
23
|
+
"require": "./dist/zustand.cjs"
|
|
24
|
+
},
|
|
25
|
+
"./generic": {
|
|
26
|
+
"types": "./dist/generic.d.ts",
|
|
27
|
+
"import": "./dist/generic.js",
|
|
28
|
+
"require": "./dist/generic.cjs"
|
|
29
|
+
},
|
|
30
|
+
"./backend": {
|
|
31
|
+
"types": "./dist/backend.d.ts",
|
|
32
|
+
"import": "./dist/backend.js",
|
|
33
|
+
"require": "./dist/backend.cjs"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsup && tsc",
|
|
41
|
+
"prepublishOnly": "npm run build"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"state management",
|
|
45
|
+
"redux",
|
|
46
|
+
"zustand",
|
|
47
|
+
"validation",
|
|
48
|
+
"invariant",
|
|
49
|
+
"runtime checking"
|
|
50
|
+
],
|
|
51
|
+
"author": "Amal Vijayakumar",
|
|
52
|
+
"license": "MIT",
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"typescript": "^5.9.3",
|
|
55
|
+
"tsup": "^8.5.0"
|
|
56
|
+
}
|
|
57
|
+
}
|