react-ability-kit 0.1.3 → 0.1.5
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 +117 -111
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
# React Ability
|
|
2
2
|
|
|
3
|
-
A small, typed permission layer for React
|
|
3
|
+
**A small, strongly-typed permission layer for React**
|
|
4
|
+
Keep authorization logic **out of your components** and **in one place**.
|
|
4
5
|
|
|
5
6
|
---
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
+
## Why this exists
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
Most React apps don’t plan to become permission nightmares — they just grow into one.
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
This package introduces a **policy-first** approach to permissions, so your UI stays clean and your rules stay auditable.
|
|
12
13
|
|
|
13
14
|
---
|
|
14
15
|
|
|
15
|
-
##
|
|
16
|
+
## Core idea (one sentence)
|
|
17
|
+
|
|
18
|
+
> Define permission rules once, then query them everywhere — instead of scattering fragile `if` checks across your UI.
|
|
19
|
+
|
|
20
|
+
Everything else is just implementation details.
|
|
21
|
+
|
|
22
|
+
---
|
|
16
23
|
|
|
17
|
-
|
|
24
|
+
## The real problem (what goes wrong in real apps)
|
|
18
25
|
|
|
19
|
-
### ❌ Without a
|
|
26
|
+
### ❌ Without a permission layer
|
|
20
27
|
|
|
21
28
|
```tsx
|
|
22
29
|
// Button.tsx
|
|
@@ -41,55 +48,48 @@ if (user && user.role !== "guest") {
|
|
|
41
48
|
}
|
|
42
49
|
```
|
|
43
50
|
|
|
51
|
+
### Problems this creates
|
|
44
52
|
|
|
45
|
-
|
|
46
|
-
❌
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
- ❌ **Logic duplication** – same rules rewritten in different places
|
|
54
|
+
- ❌ **Rules drift** – one condition changes, others don’t
|
|
55
|
+
- ❌ **Impossible to audit** – “Who can edit invoices?” → grep the whole repo
|
|
56
|
+
- ❌ **UI inconsistencies**
|
|
57
|
+
- Button visible but API rejects
|
|
58
|
+
- Button hidden but API allows
|
|
59
|
+
- ❌ **No type safety**
|
|
51
60
|
|
|
52
|
-
|
|
53
|
-
|
|
61
|
+
```ts
|
|
62
|
+
"inovice:update" // typo = silent bug
|
|
63
|
+
```
|
|
54
64
|
|
|
55
|
-
❌
|
|
56
|
-
Button visible but API rejects
|
|
65
|
+
- ❌ **Hard to evolve roles** – adding a new role breaks logic everywhere
|
|
57
66
|
|
|
58
|
-
|
|
67
|
+
---
|
|
59
68
|
|
|
60
|
-
|
|
61
|
-
ts
|
|
62
|
-
Copier le code
|
|
63
|
-
"inovice:update" // typo = silent bug
|
|
64
|
-
❌ Hard to change roles
|
|
65
|
-
Adding a new role breaks logic everywhere.
|
|
69
|
+
## The missing abstraction: policy-first permissions
|
|
66
70
|
|
|
67
|
-
What this package introduces (the missing abstraction)
|
|
68
|
-
Key idea: policy-first permissions
|
|
69
71
|
Instead of asking:
|
|
70
72
|
|
|
71
|
-
“Can the user do this?”
|
|
72
|
-
|
|
73
|
-
everywhere in the UI…
|
|
73
|
+
> “Can the user do this?”
|
|
74
|
+
> everywhere in the UI…
|
|
74
75
|
|
|
75
76
|
You define rules once, then query them everywhere.
|
|
76
77
|
|
|
77
|
-
Mental model
|
|
78
|
-
Think of your app like this:
|
|
78
|
+
### Mental model
|
|
79
79
|
|
|
80
|
-
```
|
|
80
|
+
```text
|
|
81
81
|
User + Context → Ability → UI decisions
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
```bash
|
|
84
|
+
```text
|
|
87
85
|
User ──► Policy ──► Ability ──► UI / Components
|
|
88
86
|
```
|
|
89
87
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## What this package actually solves
|
|
91
|
+
|
|
92
|
+
### 1️⃣ Single source of truth for permissions
|
|
93
93
|
|
|
94
94
|
```ts
|
|
95
95
|
// policy.ts
|
|
@@ -97,13 +97,16 @@ allow("update", "Invoice", invoice => invoice.ownerId === user.id);
|
|
|
97
97
|
deny("delete", "Invoice");
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
Result
|
|
100
|
+
**Result**
|
|
101
101
|
|
|
102
|
-
All
|
|
102
|
+
- All rules live in one place
|
|
103
|
+
- Easy to review, change, and reason about
|
|
104
|
+
- No more scattered conditions
|
|
105
|
+
|
|
106
|
+
---
|
|
103
107
|
|
|
104
|
-
|
|
108
|
+
### 2️⃣ Business rules become readable policies
|
|
105
109
|
|
|
106
|
-
2️⃣ Turns business rules into readable policies
|
|
107
110
|
❌ Before
|
|
108
111
|
|
|
109
112
|
```ts
|
|
@@ -115,7 +118,7 @@ if (
|
|
|
115
118
|
)
|
|
116
119
|
```
|
|
117
120
|
|
|
118
|
-
✅
|
|
121
|
+
✅ After
|
|
119
122
|
|
|
120
123
|
```ts
|
|
121
124
|
allow(
|
|
@@ -125,12 +128,15 @@ allow(
|
|
|
125
128
|
);
|
|
126
129
|
```
|
|
127
130
|
|
|
128
|
-
This is domain language
|
|
131
|
+
This is **domain language**, not UI logic.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
### 3️⃣ Removes permission logic from components
|
|
129
136
|
|
|
130
|
-
3️⃣ Removes permission logic from components
|
|
131
137
|
❌ Before
|
|
132
138
|
|
|
133
|
-
```
|
|
139
|
+
```tsx
|
|
134
140
|
{user?.role === "admin" && <DeleteButton />}
|
|
135
141
|
```
|
|
136
142
|
|
|
@@ -142,10 +148,11 @@ This is domain language, not UI logic.
|
|
|
142
148
|
</Can>
|
|
143
149
|
```
|
|
144
150
|
|
|
145
|
-
|
|
151
|
+
Your components focus on **rendering**, not authorization.
|
|
152
|
+
|
|
153
|
+
---
|
|
146
154
|
|
|
147
|
-
4️⃣
|
|
148
|
-
This is huge.
|
|
155
|
+
### 4️⃣ Type-safe permissions (TypeScript win)
|
|
149
156
|
|
|
150
157
|
❌ Without typing
|
|
151
158
|
|
|
@@ -160,55 +167,62 @@ can("updtae", "Invioce");
|
|
|
160
167
|
// ❌ TypeScript error immediately
|
|
161
168
|
```
|
|
162
169
|
|
|
163
|
-
This
|
|
170
|
+
This removes an entire class of bugs.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
### 5️⃣ Ownership rules become first-class
|
|
164
175
|
|
|
165
|
-
|
|
166
|
-
Ownership checks are usually scattered:
|
|
176
|
+
❌ Scattered ownership checks
|
|
167
177
|
|
|
168
178
|
```ts
|
|
169
179
|
if (invoice.ownerId === user.id)
|
|
170
180
|
```
|
|
171
181
|
|
|
172
|
-
|
|
182
|
+
✅ Centralized ownership rule
|
|
173
183
|
|
|
174
184
|
```ts
|
|
175
185
|
allow("update", "Invoice", invoice => invoice.ownerId === user.id);
|
|
176
186
|
```
|
|
177
187
|
|
|
178
|
-
Ownership logic
|
|
188
|
+
Ownership logic is now:
|
|
179
189
|
|
|
180
|
-
consistent
|
|
190
|
+
- consistent
|
|
191
|
+
- reusable
|
|
192
|
+
- testable
|
|
181
193
|
|
|
182
|
-
|
|
194
|
+
---
|
|
183
195
|
|
|
184
|
-
|
|
196
|
+
### 6️⃣ Predictable SSR & hydration
|
|
185
197
|
|
|
186
|
-
6️⃣ Makes SSR and hydration predictable
|
|
187
198
|
Without a system:
|
|
188
199
|
|
|
189
|
-
UI flickers
|
|
190
|
-
|
|
191
|
-
|
|
200
|
+
- UI flickers
|
|
201
|
+
- Buttons appear/disappear after hydration
|
|
202
|
+
- Server/client logic diverges
|
|
192
203
|
|
|
193
|
-
|
|
204
|
+
With **React Ability**:
|
|
194
205
|
|
|
195
|
-
|
|
206
|
+
- Ability is built once from user data
|
|
207
|
+
- Server and client agree on permissions
|
|
208
|
+
- Stable, predictable rendering
|
|
196
209
|
|
|
197
|
-
|
|
210
|
+
---
|
|
198
211
|
|
|
199
|
-
|
|
212
|
+
## What `<Can />` actually does
|
|
200
213
|
|
|
201
|
-
What the <Can /> component really is
|
|
202
214
|
It’s not magic.
|
|
203
215
|
|
|
204
216
|
It simply means:
|
|
205
217
|
|
|
206
|
-
|
|
218
|
+
> Render children **only if the permission passes**.
|
|
207
219
|
|
|
208
220
|
Instead of:
|
|
221
|
+
|
|
209
222
|
```tsx
|
|
210
223
|
if (!canEdit) return null;
|
|
211
224
|
```
|
|
225
|
+
|
|
212
226
|
You write:
|
|
213
227
|
|
|
214
228
|
```tsx
|
|
@@ -219,68 +233,60 @@ You write:
|
|
|
219
233
|
|
|
220
234
|
That’s it.
|
|
221
235
|
|
|
222
|
-
|
|
223
|
-
This is important.
|
|
224
|
-
|
|
225
|
-
❌ Not an auth system
|
|
226
|
-
❌ Not a backend security layer
|
|
227
|
-
❌ Not a role manager UI
|
|
228
|
-
❌ Not a permission database
|
|
229
|
-
|
|
230
|
-
This package:
|
|
231
|
-
|
|
232
|
-
does not replace backend checks
|
|
233
|
-
|
|
234
|
-
does not handle authentication
|
|
236
|
+
---
|
|
235
237
|
|
|
236
|
-
|
|
238
|
+
## What this package is NOT
|
|
237
239
|
|
|
238
|
-
|
|
240
|
+
❌ Not an authentication system
|
|
241
|
+
❌ Not a backend security layer
|
|
242
|
+
❌ Not a role management UI
|
|
243
|
+
❌ Not a permission database
|
|
239
244
|
|
|
240
|
-
|
|
245
|
+
This package **does not**:
|
|
241
246
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
✅ B2B products
|
|
246
|
-
✅ Apps with ownership rules
|
|
247
|
-
✅ Teams larger than 1 developer
|
|
247
|
+
- replace backend checks
|
|
248
|
+
- handle authentication
|
|
249
|
+
- store users or roles
|
|
248
250
|
|
|
249
|
-
|
|
250
|
-
❌ Landing pages
|
|
251
|
-
❌ Simple blogs
|
|
252
|
-
❌ Apps with only admin / non-admin logic
|
|
251
|
+
It answers one question only:
|
|
253
252
|
|
|
254
|
-
|
|
255
|
-
Most developers:
|
|
253
|
+
> **“Given a user and a resource, is this action allowed?”**
|
|
256
254
|
|
|
257
|
-
|
|
255
|
+
---
|
|
258
256
|
|
|
259
|
-
|
|
257
|
+
## When this package makes sense
|
|
260
258
|
|
|
261
|
-
|
|
259
|
+
- ✅ SaaS dashboards
|
|
260
|
+
- ✅ Multi-role applications
|
|
261
|
+
- ✅ B2B products
|
|
262
|
+
- ✅ Ownership-based rules
|
|
263
|
+
- ✅ Teams larger than one developer
|
|
262
264
|
|
|
263
|
-
|
|
265
|
+
---
|
|
264
266
|
|
|
265
|
-
|
|
267
|
+
## When it’s overkill
|
|
266
268
|
|
|
267
|
-
|
|
269
|
+
- ❌ Landing pages
|
|
270
|
+
- ❌ Simple blogs
|
|
271
|
+
- ❌ Apps with only admin / non-admin logic
|
|
268
272
|
|
|
269
|
-
|
|
273
|
+
---
|
|
270
274
|
|
|
271
|
-
|
|
275
|
+
## Final summary
|
|
272
276
|
|
|
273
|
-
|
|
274
|
-
This package solves one problem:
|
|
277
|
+
**React Ability solves one problem:**
|
|
275
278
|
|
|
276
|
-
|
|
279
|
+
> How do I express and use permissions in React without scattering fragile conditional logic everywhere?
|
|
277
280
|
|
|
278
|
-
It
|
|
281
|
+
It does this by:
|
|
279
282
|
|
|
280
|
-
centralizing permission rules
|
|
283
|
+
- centralizing permission rules
|
|
284
|
+
- typing actions and resources
|
|
285
|
+
- exposing a clean `can()` API
|
|
286
|
+
- providing `<Can />` for UI rendering
|
|
281
287
|
|
|
282
|
-
|
|
288
|
+
---
|
|
283
289
|
|
|
284
|
-
|
|
290
|
+
## Credits
|
|
285
291
|
|
|
286
|
-
|
|
292
|
+
Created by **Mohamed Ali Sraieb**
|