@pyreon/permissions 0.11.5 → 0.11.7

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.
@@ -1,344 +1,344 @@
1
- import { computed, effect } from "@pyreon/reactivity"
2
- import { describe, expect, it } from "vitest"
3
- import { createPermissions } from "../index"
1
+ import { computed, effect } from '@pyreon/reactivity'
2
+ import { describe, expect, it } from 'vitest'
3
+ import { createPermissions } from '../index'
4
4
 
5
- describe("createPermissions — can.all()", () => {
6
- it("returns true when all permissions are granted", () => {
5
+ describe('createPermissions — can.all()', () => {
6
+ it('returns true when all permissions are granted', () => {
7
7
  const can = createPermissions({
8
- "posts.read": true,
9
- "posts.create": true,
10
- "posts.update": true,
8
+ 'posts.read': true,
9
+ 'posts.create': true,
10
+ 'posts.update': true,
11
11
  })
12
- expect(can.all("posts.read", "posts.create", "posts.update")).toBe(true)
12
+ expect(can.all('posts.read', 'posts.create', 'posts.update')).toBe(true)
13
13
  })
14
14
 
15
- it("returns false when any permission is denied", () => {
15
+ it('returns false when any permission is denied', () => {
16
16
  const can = createPermissions({
17
- "posts.read": true,
18
- "posts.create": true,
19
- "posts.delete": false,
17
+ 'posts.read': true,
18
+ 'posts.create': true,
19
+ 'posts.delete': false,
20
20
  })
21
- expect(can.all("posts.read", "posts.create", "posts.delete")).toBe(false)
21
+ expect(can.all('posts.read', 'posts.create', 'posts.delete')).toBe(false)
22
22
  })
23
23
 
24
- it("returns false when any permission is undefined", () => {
25
- const can = createPermissions({ "posts.read": true })
26
- expect(can.all("posts.read", "posts.create")).toBe(false) // posts.create undefined
24
+ it('returns false when any permission is undefined', () => {
25
+ const can = createPermissions({ 'posts.read': true })
26
+ expect(can.all('posts.read', 'posts.create')).toBe(false) // posts.create undefined
27
27
  })
28
28
 
29
- it("returns true for empty args (vacuous truth)", () => {
29
+ it('returns true for empty args (vacuous truth)', () => {
30
30
  const can = createPermissions()
31
31
  expect(can.all()).toBe(true)
32
32
  })
33
33
 
34
- it("works with wildcards", () => {
35
- const can = createPermissions({ "posts.*": true })
36
- expect(can.all("posts.read", "posts.create")).toBe(true)
37
- expect(can.all("posts.read", "users.manage")).toBe(false)
34
+ it('works with wildcards', () => {
35
+ const can = createPermissions({ 'posts.*': true })
36
+ expect(can.all('posts.read', 'posts.create')).toBe(true)
37
+ expect(can.all('posts.read', 'users.manage')).toBe(false)
38
38
  })
39
39
 
40
- it("is reactive in effects", () => {
40
+ it('is reactive in effects', () => {
41
41
  const can = createPermissions({
42
- "posts.read": true,
43
- "posts.create": true,
42
+ 'posts.read': true,
43
+ 'posts.create': true,
44
44
  })
45
45
  const results: boolean[] = []
46
46
 
47
47
  effect(() => {
48
- results.push(can.all("posts.read", "posts.create"))
48
+ results.push(can.all('posts.read', 'posts.create'))
49
49
  })
50
50
 
51
- can.patch({ "posts.create": false })
51
+ can.patch({ 'posts.create': false })
52
52
  expect(results).toEqual([true, false])
53
53
  })
54
54
 
55
- it("is reactive in computed", () => {
55
+ it('is reactive in computed', () => {
56
56
  const can = createPermissions({
57
- "posts.read": true,
58
- "posts.create": true,
57
+ 'posts.read': true,
58
+ 'posts.create': true,
59
59
  })
60
- const allGranted = computed(() => can.all("posts.read", "posts.create"))
60
+ const allGranted = computed(() => can.all('posts.read', 'posts.create'))
61
61
  expect(allGranted()).toBe(true)
62
62
 
63
- can.patch({ "posts.create": false })
63
+ can.patch({ 'posts.create': false })
64
64
  expect(allGranted()).toBe(false)
65
65
  })
66
66
  })
67
67
 
68
- describe("createPermissions — can.any()", () => {
69
- it("returns true when at least one permission is granted", () => {
68
+ describe('createPermissions — can.any()', () => {
69
+ it('returns true when at least one permission is granted', () => {
70
70
  const can = createPermissions({
71
- "posts.read": false,
72
- "posts.create": true,
71
+ 'posts.read': false,
72
+ 'posts.create': true,
73
73
  })
74
- expect(can.any("posts.read", "posts.create")).toBe(true)
74
+ expect(can.any('posts.read', 'posts.create')).toBe(true)
75
75
  })
76
76
 
77
- it("returns false when no permissions are granted", () => {
77
+ it('returns false when no permissions are granted', () => {
78
78
  const can = createPermissions({
79
- "posts.read": false,
80
- "posts.create": false,
79
+ 'posts.read': false,
80
+ 'posts.create': false,
81
81
  })
82
- expect(can.any("posts.read", "posts.create")).toBe(false)
82
+ expect(can.any('posts.read', 'posts.create')).toBe(false)
83
83
  })
84
84
 
85
- it("returns true when all are granted", () => {
85
+ it('returns true when all are granted', () => {
86
86
  const can = createPermissions({
87
- "posts.read": true,
88
- "posts.create": true,
87
+ 'posts.read': true,
88
+ 'posts.create': true,
89
89
  })
90
- expect(can.any("posts.read", "posts.create")).toBe(true)
90
+ expect(can.any('posts.read', 'posts.create')).toBe(true)
91
91
  })
92
92
 
93
- it("returns false for empty args", () => {
94
- const can = createPermissions({ "posts.read": true })
93
+ it('returns false for empty args', () => {
94
+ const can = createPermissions({ 'posts.read': true })
95
95
  expect(can.any()).toBe(false)
96
96
  })
97
97
 
98
- it("works with undefined permissions", () => {
99
- const can = createPermissions({ "posts.read": true })
100
- expect(can.any("posts.read", "nonexistent")).toBe(true)
101
- expect(can.any("nonexistent", "also-nonexistent")).toBe(false)
98
+ it('works with undefined permissions', () => {
99
+ const can = createPermissions({ 'posts.read': true })
100
+ expect(can.any('posts.read', 'nonexistent')).toBe(true)
101
+ expect(can.any('nonexistent', 'also-nonexistent')).toBe(false)
102
102
  })
103
103
 
104
- it("works with wildcards", () => {
105
- const can = createPermissions({ "posts.*": true })
106
- expect(can.any("posts.read", "users.manage")).toBe(true)
107
- expect(can.any("users.read", "users.manage")).toBe(false)
104
+ it('works with wildcards', () => {
105
+ const can = createPermissions({ 'posts.*': true })
106
+ expect(can.any('posts.read', 'users.manage')).toBe(true)
107
+ expect(can.any('users.read', 'users.manage')).toBe(false)
108
108
  })
109
109
 
110
- it("is reactive in effects", () => {
110
+ it('is reactive in effects', () => {
111
111
  const can = createPermissions({
112
- "posts.read": false,
113
- "posts.create": true,
112
+ 'posts.read': false,
113
+ 'posts.create': true,
114
114
  })
115
115
  const results: boolean[] = []
116
116
 
117
117
  effect(() => {
118
- results.push(can.any("posts.read", "posts.create"))
118
+ results.push(can.any('posts.read', 'posts.create'))
119
119
  })
120
120
 
121
- can.patch({ "posts.create": false })
121
+ can.patch({ 'posts.create': false })
122
122
  expect(results).toEqual([true, false])
123
123
  })
124
124
  })
125
125
 
126
- describe("createPermissions — can.not()", () => {
127
- it("returns inverse of can()", () => {
126
+ describe('createPermissions — can.not()', () => {
127
+ it('returns inverse of can()', () => {
128
128
  const can = createPermissions({
129
- "posts.read": true,
130
- "posts.delete": false,
129
+ 'posts.read': true,
130
+ 'posts.delete': false,
131
131
  })
132
- expect(can.not("posts.read")).toBe(false)
133
- expect(can.not("posts.delete")).toBe(true)
132
+ expect(can.not('posts.read')).toBe(false)
133
+ expect(can.not('posts.delete')).toBe(true)
134
134
  })
135
135
 
136
- it("returns true for undefined permissions", () => {
136
+ it('returns true for undefined permissions', () => {
137
137
  const can = createPermissions()
138
- expect(can.not("anything")).toBe(true)
138
+ expect(can.not('anything')).toBe(true)
139
139
  })
140
140
 
141
- it("works with context for predicate permissions", () => {
141
+ it('works with context for predicate permissions', () => {
142
142
  const can = createPermissions({
143
- "posts.update": (post: any) => post?.authorId === "me",
143
+ 'posts.update': (post: any) => post?.authorId === 'me',
144
144
  })
145
- expect(can.not("posts.update", { authorId: "me" })).toBe(false)
146
- expect(can.not("posts.update", { authorId: "other" })).toBe(true)
145
+ expect(can.not('posts.update', { authorId: 'me' })).toBe(false)
146
+ expect(can.not('posts.update', { authorId: 'other' })).toBe(true)
147
147
  })
148
148
 
149
- it("is reactive", () => {
150
- const can = createPermissions({ "posts.read": true })
149
+ it('is reactive', () => {
150
+ const can = createPermissions({ 'posts.read': true })
151
151
  const results: boolean[] = []
152
152
 
153
153
  effect(() => {
154
- results.push(can.not("posts.read"))
154
+ results.push(can.not('posts.read'))
155
155
  })
156
156
 
157
- can.set({ "posts.read": false })
157
+ can.set({ 'posts.read': false })
158
158
  expect(results).toEqual([false, true])
159
159
  })
160
160
  })
161
161
 
162
- describe("createPermissions — can.set()", () => {
163
- it("replaces all permissions entirely", () => {
162
+ describe('createPermissions — can.set()', () => {
163
+ it('replaces all permissions entirely', () => {
164
164
  const can = createPermissions({
165
- "posts.read": true,
166
- "users.manage": true,
165
+ 'posts.read': true,
166
+ 'users.manage': true,
167
167
  })
168
168
 
169
- can.set({ "posts.read": false })
170
- expect(can("posts.read")).toBe(false)
171
- expect(can("users.manage")).toBe(false) // was not in new set
169
+ can.set({ 'posts.read': false })
170
+ expect(can('posts.read')).toBe(false)
171
+ expect(can('users.manage')).toBe(false) // was not in new set
172
172
  })
173
173
 
174
- it("triggers reactive updates", () => {
175
- const can = createPermissions({ "posts.read": true })
174
+ it('triggers reactive updates', () => {
175
+ const can = createPermissions({ 'posts.read': true })
176
176
  const results: boolean[] = []
177
177
 
178
178
  effect(() => {
179
- results.push(can("posts.read"))
179
+ results.push(can('posts.read'))
180
180
  })
181
181
 
182
- can.set({ "posts.read": false })
183
- can.set({ "posts.read": true })
182
+ can.set({ 'posts.read': false })
183
+ can.set({ 'posts.read': true })
184
184
  expect(results).toEqual([true, false, true])
185
185
  })
186
186
 
187
- it("set with empty object removes all permissions", () => {
188
- const can = createPermissions({ "posts.read": true, "users.manage": true })
187
+ it('set with empty object removes all permissions', () => {
188
+ const can = createPermissions({ 'posts.read': true, 'users.manage': true })
189
189
  can.set({})
190
- expect(can("posts.read")).toBe(false)
191
- expect(can("users.manage")).toBe(false)
190
+ expect(can('posts.read')).toBe(false)
191
+ expect(can('users.manage')).toBe(false)
192
192
  })
193
193
 
194
- it("set with predicates", () => {
194
+ it('set with predicates', () => {
195
195
  const can = createPermissions({})
196
196
  can.set({
197
- "posts.update": (post: any) => post?.authorId === "me",
197
+ 'posts.update': (post: any) => post?.authorId === 'me',
198
198
  })
199
- expect(can("posts.update", { authorId: "me" })).toBe(true)
200
- expect(can("posts.update", { authorId: "other" })).toBe(false)
199
+ expect(can('posts.update', { authorId: 'me' })).toBe(true)
200
+ expect(can('posts.update', { authorId: 'other' })).toBe(false)
201
201
  })
202
202
  })
203
203
 
204
- describe("createPermissions — can.patch()", () => {
205
- it("merges with existing permissions", () => {
204
+ describe('createPermissions — can.patch()', () => {
205
+ it('merges with existing permissions', () => {
206
206
  const can = createPermissions({
207
- "posts.read": true,
208
- "users.manage": false,
207
+ 'posts.read': true,
208
+ 'users.manage': false,
209
209
  })
210
210
 
211
- can.patch({ "users.manage": true, "billing.view": true })
212
- expect(can("posts.read")).toBe(true) // unchanged
213
- expect(can("users.manage")).toBe(true) // updated
214
- expect(can("billing.view")).toBe(true) // added
211
+ can.patch({ 'users.manage': true, 'billing.view': true })
212
+ expect(can('posts.read')).toBe(true) // unchanged
213
+ expect(can('users.manage')).toBe(true) // updated
214
+ expect(can('billing.view')).toBe(true) // added
215
215
  })
216
216
 
217
- it("overwrites existing keys", () => {
218
- const can = createPermissions({ "posts.read": true })
219
- can.patch({ "posts.read": false })
220
- expect(can("posts.read")).toBe(false)
217
+ it('overwrites existing keys', () => {
218
+ const can = createPermissions({ 'posts.read': true })
219
+ can.patch({ 'posts.read': false })
220
+ expect(can('posts.read')).toBe(false)
221
221
  })
222
222
 
223
- it("adds new keys without removing existing", () => {
223
+ it('adds new keys without removing existing', () => {
224
224
  const can = createPermissions({ a: true })
225
225
  can.patch({ b: true })
226
- expect(can("a")).toBe(true)
227
- expect(can("b")).toBe(true)
226
+ expect(can('a')).toBe(true)
227
+ expect(can('b')).toBe(true)
228
228
  })
229
229
 
230
- it("triggers reactive updates", () => {
231
- const can = createPermissions({ "posts.read": false })
230
+ it('triggers reactive updates', () => {
231
+ const can = createPermissions({ 'posts.read': false })
232
232
  const results: boolean[] = []
233
233
 
234
234
  effect(() => {
235
- results.push(can("posts.read"))
235
+ results.push(can('posts.read'))
236
236
  })
237
237
 
238
- can.patch({ "posts.read": true })
238
+ can.patch({ 'posts.read': true })
239
239
  expect(results).toEqual([false, true])
240
240
  })
241
241
 
242
- it("patch with predicates", () => {
243
- const can = createPermissions({ "posts.read": true })
242
+ it('patch with predicates', () => {
243
+ const can = createPermissions({ 'posts.read': true })
244
244
  can.patch({
245
- "posts.update": (post: any) => post?.draft === true,
245
+ 'posts.update': (post: any) => post?.draft === true,
246
246
  })
247
- expect(can("posts.read")).toBe(true)
248
- expect(can("posts.update", { draft: true })).toBe(true)
249
- expect(can("posts.update", { draft: false })).toBe(false)
247
+ expect(can('posts.read')).toBe(true)
248
+ expect(can('posts.update', { draft: true })).toBe(true)
249
+ expect(can('posts.update', { draft: false })).toBe(false)
250
250
  })
251
251
  })
252
252
 
253
- describe("createPermissions — can.granted()", () => {
254
- it("returns keys with true values", () => {
253
+ describe('createPermissions — can.granted()', () => {
254
+ it('returns keys with true values', () => {
255
255
  const can = createPermissions({
256
- "posts.read": true,
257
- "posts.delete": false,
258
- "users.manage": true,
256
+ 'posts.read': true,
257
+ 'posts.delete': false,
258
+ 'users.manage': true,
259
259
  })
260
260
  const granted = can.granted()
261
- expect(granted).toContain("posts.read")
262
- expect(granted).toContain("users.manage")
263
- expect(granted).not.toContain("posts.delete")
261
+ expect(granted).toContain('posts.read')
262
+ expect(granted).toContain('users.manage')
263
+ expect(granted).not.toContain('posts.delete')
264
264
  })
265
265
 
266
- it("includes predicate keys (capabilities exist)", () => {
266
+ it('includes predicate keys (capabilities exist)', () => {
267
267
  const can = createPermissions({
268
- "posts.update": (post: any) => post?.authorId === "me",
268
+ 'posts.update': (post: any) => post?.authorId === 'me',
269
269
  })
270
- expect(can.granted()).toContain("posts.update")
270
+ expect(can.granted()).toContain('posts.update')
271
271
  })
272
272
 
273
- it("returns empty array for no permissions", () => {
273
+ it('returns empty array for no permissions', () => {
274
274
  const can = createPermissions()
275
275
  expect(can.granted()).toEqual([])
276
276
  })
277
277
 
278
- it("returns empty array when all denied", () => {
278
+ it('returns empty array when all denied', () => {
279
279
  const can = createPermissions({ a: false, b: false })
280
280
  expect(can.granted()).toEqual([])
281
281
  })
282
282
 
283
- it("is reactive — updates on set()", () => {
284
- const can = createPermissions({ "posts.read": true })
283
+ it('is reactive — updates on set()', () => {
284
+ const can = createPermissions({ 'posts.read': true })
285
285
  const results: string[][] = []
286
286
 
287
287
  effect(() => {
288
288
  results.push([...can.granted()])
289
289
  })
290
290
 
291
- can.set({ "posts.read": true, "users.manage": true })
291
+ can.set({ 'posts.read': true, 'users.manage': true })
292
292
  expect(results).toHaveLength(2)
293
- expect(results[1]).toEqual(expect.arrayContaining(["posts.read", "users.manage"]))
293
+ expect(results[1]).toEqual(expect.arrayContaining(['posts.read', 'users.manage']))
294
294
  })
295
295
 
296
- it("is reactive — updates on patch()", () => {
297
- const can = createPermissions({ "posts.read": true })
296
+ it('is reactive — updates on patch()', () => {
297
+ const can = createPermissions({ 'posts.read': true })
298
298
  const results: string[][] = []
299
299
 
300
300
  effect(() => {
301
301
  results.push([...can.granted()])
302
302
  })
303
303
 
304
- can.patch({ "users.manage": true })
305
- expect(results).toEqual([["posts.read"], ["posts.read", "users.manage"]])
304
+ can.patch({ 'users.manage': true })
305
+ expect(results).toEqual([['posts.read'], ['posts.read', 'users.manage']])
306
306
  })
307
307
  })
308
308
 
309
- describe("createPermissions — can.entries()", () => {
310
- it("returns all entries as [key, value] pairs", () => {
309
+ describe('createPermissions — can.entries()', () => {
310
+ it('returns all entries as [key, value] pairs', () => {
311
311
  const can = createPermissions({
312
- "posts.read": true,
313
- "posts.delete": false,
312
+ 'posts.read': true,
313
+ 'posts.delete': false,
314
314
  })
315
315
  const entries = can.entries()
316
316
  expect(entries).toHaveLength(2)
317
317
  expect(entries).toEqual(
318
318
  expect.arrayContaining([
319
- ["posts.read", true],
320
- ["posts.delete", false],
319
+ ['posts.read', true],
320
+ ['posts.delete', false],
321
321
  ]),
322
322
  )
323
323
  })
324
324
 
325
- it("returns empty array for no permissions", () => {
325
+ it('returns empty array for no permissions', () => {
326
326
  const can = createPermissions()
327
327
  expect(can.entries()).toEqual([])
328
328
  })
329
329
 
330
- it("includes predicate entries", () => {
331
- const pred = (post: any) => post?.authorId === "me"
330
+ it('includes predicate entries', () => {
331
+ const pred = (post: any) => post?.authorId === 'me'
332
332
  const can = createPermissions({
333
- "posts.update": pred,
333
+ 'posts.update': pred,
334
334
  })
335
335
  const entries = can.entries()
336
336
  expect(entries).toHaveLength(1)
337
- expect(entries[0]?.[0]).toBe("posts.update")
337
+ expect(entries[0]?.[0]).toBe('posts.update')
338
338
  expect(entries[0]?.[1]).toBe(pred)
339
339
  })
340
340
 
341
- it("is reactive — updates on mutations", () => {
341
+ it('is reactive — updates on mutations', () => {
342
342
  const can = createPermissions({ a: true })
343
343
  const counts: number[] = []
344
344
 
@@ -351,12 +351,12 @@ describe("createPermissions — can.entries()", () => {
351
351
  expect(counts).toEqual([1, 2, 3])
352
352
  })
353
353
 
354
- it("reflects set() replacement", () => {
354
+ it('reflects set() replacement', () => {
355
355
  const can = createPermissions({ a: true, b: true, c: true })
356
356
  expect(can.entries()).toHaveLength(3)
357
357
 
358
358
  can.set({ x: true })
359
359
  expect(can.entries()).toHaveLength(1)
360
- expect(can.entries()[0]?.[0]).toBe("x")
360
+ expect(can.entries()[0]?.[0]).toBe('x')
361
361
  })
362
362
  })
@@ -1,50 +1,50 @@
1
- import { describe, expect, it } from "vitest"
2
- import { createPermissions, usePermissions } from "../index"
1
+ import { describe, expect, it } from 'vitest'
2
+ import { createPermissions, usePermissions } from '../index'
3
3
 
4
- describe("usePermissions", () => {
5
- it("throws when called outside PermissionsProvider", () => {
4
+ describe('usePermissions', () => {
5
+ it('throws when called outside PermissionsProvider', () => {
6
6
  expect(() => usePermissions()).toThrow(
7
- "[@pyreon/permissions] usePermissions() must be used within <PermissionsProvider>.",
7
+ '[@pyreon/permissions] usePermissions() must be used within <PermissionsProvider>.',
8
8
  )
9
9
  })
10
10
  })
11
11
 
12
- describe("createPermissions used directly (no context)", () => {
13
- it("works standalone without any provider", () => {
14
- const can = createPermissions({ "posts.read": true })
15
- expect(can("posts.read")).toBe(true)
12
+ describe('createPermissions used directly (no context)', () => {
13
+ it('works standalone without any provider', () => {
14
+ const can = createPermissions({ 'posts.read': true })
15
+ expect(can('posts.read')).toBe(true)
16
16
  })
17
17
 
18
- it("multiple independent instances do not interfere", () => {
19
- const canA = createPermissions({ "posts.read": true })
20
- const canB = createPermissions({ "posts.read": false })
18
+ it('multiple independent instances do not interfere', () => {
19
+ const canA = createPermissions({ 'posts.read': true })
20
+ const canB = createPermissions({ 'posts.read': false })
21
21
 
22
- expect(canA("posts.read")).toBe(true)
23
- expect(canB("posts.read")).toBe(false)
22
+ expect(canA('posts.read')).toBe(true)
23
+ expect(canB('posts.read')).toBe(false)
24
24
 
25
- canA.set({ "posts.read": false })
26
- expect(canA("posts.read")).toBe(false)
27
- expect(canB("posts.read")).toBe(false) // unchanged, already false
25
+ canA.set({ 'posts.read': false })
26
+ expect(canA('posts.read')).toBe(false)
27
+ expect(canB('posts.read')).toBe(false) // unchanged, already false
28
28
  })
29
29
 
30
- it("set() on one instance does not affect another", () => {
30
+ it('set() on one instance does not affect another', () => {
31
31
  const canA = createPermissions({ a: true, b: true })
32
32
  const canB = createPermissions({ a: true, b: true })
33
33
 
34
34
  canA.set({ a: false })
35
- expect(canA("a")).toBe(false)
36
- expect(canA("b")).toBe(false) // cleared by set
35
+ expect(canA('a')).toBe(false)
36
+ expect(canA('b')).toBe(false) // cleared by set
37
37
 
38
- expect(canB("a")).toBe(true) // unaffected
39
- expect(canB("b")).toBe(true)
38
+ expect(canB('a')).toBe(true) // unaffected
39
+ expect(canB('b')).toBe(true)
40
40
  })
41
41
 
42
- it("patch() on one instance does not affect another", () => {
42
+ it('patch() on one instance does not affect another', () => {
43
43
  const canA = createPermissions({ shared: true })
44
44
  const canB = createPermissions({ shared: true })
45
45
 
46
46
  canA.patch({ shared: false })
47
- expect(canA("shared")).toBe(false)
48
- expect(canB("shared")).toBe(true) // unaffected
47
+ expect(canA('shared')).toBe(false)
48
+ expect(canB('shared')).toBe(true) // unaffected
49
49
  })
50
50
  })