@diister/quick-permission 0.9.0-beta.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.
Files changed (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +747 -0
  3. package/aggregation.ts +114 -0
  4. package/core/filtering.ts +70 -0
  5. package/core/matching.ts +82 -0
  6. package/core/merging.ts +143 -0
  7. package/dist/aggregation.d.ts +62 -0
  8. package/dist/aggregation.d.ts.map +1 -0
  9. package/dist/aggregation.js +97 -0
  10. package/dist/aggregation.js.map +1 -0
  11. package/dist/core/filtering.d.ts +35 -0
  12. package/dist/core/filtering.d.ts.map +1 -0
  13. package/dist/core/filtering.js +62 -0
  14. package/dist/core/filtering.js.map +1 -0
  15. package/dist/core/matching.d.ts +31 -0
  16. package/dist/core/matching.d.ts.map +1 -0
  17. package/dist/core/matching.js +75 -0
  18. package/dist/core/matching.js.map +1 -0
  19. package/dist/core/merging.d.ts +29 -0
  20. package/dist/core/merging.d.ts.map +1 -0
  21. package/dist/core/merging.js +124 -0
  22. package/dist/core/merging.js.map +1 -0
  23. package/dist/indirect-aggregation.d.ts +41 -0
  24. package/dist/indirect-aggregation.d.ts.map +1 -0
  25. package/dist/indirect-aggregation.js +185 -0
  26. package/dist/indirect-aggregation.js.map +1 -0
  27. package/dist/indirect-resource.d.ts +126 -0
  28. package/dist/indirect-resource.d.ts.map +1 -0
  29. package/dist/indirect-resource.js +109 -0
  30. package/dist/indirect-resource.js.map +1 -0
  31. package/dist/mod.d.ts +25 -0
  32. package/dist/mod.d.ts.map +1 -0
  33. package/dist/mod.js +25 -0
  34. package/dist/mod.js.map +1 -0
  35. package/dist/mongo-query.d.ts +38 -0
  36. package/dist/mongo-query.d.ts.map +1 -0
  37. package/dist/mongo-query.js +88 -0
  38. package/dist/mongo-query.js.map +1 -0
  39. package/dist/permission.d.ts +57 -0
  40. package/dist/permission.d.ts.map +1 -0
  41. package/dist/permission.js +60 -0
  42. package/dist/permission.js.map +1 -0
  43. package/dist/resource.d.ts +48 -0
  44. package/dist/resource.d.ts.map +1 -0
  45. package/dist/resource.js +298 -0
  46. package/dist/resource.js.map +1 -0
  47. package/dist/rules.d.ts +106 -0
  48. package/dist/rules.d.ts.map +1 -0
  49. package/dist/rules.js +183 -0
  50. package/dist/rules.js.map +1 -0
  51. package/dist/sift/core.d.ts +104 -0
  52. package/dist/sift/core.d.ts.map +1 -0
  53. package/dist/sift/core.js +248 -0
  54. package/dist/sift/core.js.map +1 -0
  55. package/dist/sift/index.d.ts +10 -0
  56. package/dist/sift/index.d.ts.map +1 -0
  57. package/dist/sift/index.js +18 -0
  58. package/dist/sift/index.js.map +1 -0
  59. package/dist/sift/operations.d.ts +87 -0
  60. package/dist/sift/operations.d.ts.map +1 -0
  61. package/dist/sift/operations.js +257 -0
  62. package/dist/sift/operations.js.map +1 -0
  63. package/dist/sift/utils.d.ts +12 -0
  64. package/dist/sift/utils.d.ts.map +1 -0
  65. package/dist/sift/utils.js +80 -0
  66. package/dist/sift/utils.js.map +1 -0
  67. package/dist/system.d.ts +113 -0
  68. package/dist/system.d.ts.map +1 -0
  69. package/dist/system.js +712 -0
  70. package/dist/system.js.map +1 -0
  71. package/dist/target.d.ts +18 -0
  72. package/dist/target.d.ts.map +1 -0
  73. package/dist/target.js +41 -0
  74. package/dist/target.js.map +1 -0
  75. package/dist/types.d.ts +345 -0
  76. package/dist/types.d.ts.map +1 -0
  77. package/dist/types.js +10 -0
  78. package/dist/types.js.map +1 -0
  79. package/indirect-aggregation.ts +216 -0
  80. package/indirect-resource.ts +205 -0
  81. package/mod.ts +81 -0
  82. package/mongo-query.ts +94 -0
  83. package/package.json +58 -0
  84. package/permission.ts +88 -0
  85. package/resource.ts +352 -0
  86. package/rules.ts +241 -0
  87. package/sift/MIT-LICENSE.txt +20 -0
  88. package/sift/core.ts +551 -0
  89. package/sift/index.ts +62 -0
  90. package/sift/operations.ts +449 -0
  91. package/sift/utils.ts +96 -0
  92. package/system.ts +974 -0
  93. package/target.ts +84 -0
  94. package/types.ts +408 -0
package/README.md ADDED
@@ -0,0 +1,747 @@
1
+ # Quick Permission - Library 2
2
+
3
+ A TypeScript permission management library that is **type-safe**, **flexible**, and **composable** with support for hierarchical permissions, field-level filtering, and metadata accumulation.
4
+
5
+ ## 🎯 Key Features
6
+
7
+ ### ✨ **Extreme Type Safety**
8
+ ```typescript
9
+ // ✅ TypeScript enforces the correct argument type
10
+ await permSystem.can(user, "article.read", articleId); // Accepts ArticleId
11
+ await permSystem.can(user, "article.create"); // Does not accept argument
12
+
13
+ // ✅ Output type is inferred automatically
14
+ const result = await permSystem.can(user, "article.read", "article:1");
15
+ result.output?.filter // Type: Record<string, boolean> | undefined
16
+ result.output?.data // Type: any
17
+ ```
18
+
19
+ ### 🔥 **Field-Level Permissions with Accumulation**
20
+ ```typescript
21
+ // Multiple permissions can contribute
22
+ // Owner permission : { filter: { _id, title, owner, salary } }
23
+ // Public permission : { filter: { _id, title, body } }
24
+ // Contributor permission : { filter: { views } }
25
+
26
+ const result = await permSystem.can(ownerUser, "article.read", "article:1");
27
+ // result.output.data = { _id, title, body, owner, salary, views }
28
+ // ↑ Union of all filters!
29
+ ```
30
+
31
+ ### 🎪 **Recursive Intermediate Permissions**
32
+ ```typescript
33
+ "article.manage": intermediate((ctx) => [
34
+ { ...ctx, key: "article.read" },
35
+ { ...ctx, key: "article.update" },
36
+ { ...ctx, key: "article.comment.manage", target: [ctx.target, "*"] }
37
+ ])
38
+
39
+ // Recursive resolution with configurable max depth
40
+ ```
41
+
42
+ ### 🧩 **Providers + Rules Architecture**
43
+ ```typescript
44
+ const permSystem = createPermissionSystem({
45
+ schemas: permissionsSchemas,
46
+ sources: [
47
+ directProvider(staticPerms),
48
+ ownerProvider(["article.read"], "article:*"),
49
+ customDatabaseProvider(),
50
+ ],
51
+ rules: [TimeRule(), IpRule(), WithRule()],
52
+ });
53
+ ```
54
+
55
+ ---
56
+
57
+ ## 📚 Usage Guide
58
+
59
+ ### Installation
60
+
61
+ ```typescript
62
+ import {
63
+ createPermissionSystem,
64
+ permission,
65
+ intermediate,
66
+ FilterRule,
67
+ directProvider,
68
+ ownerProvider,
69
+ } from "./library_2/mod.ts";
70
+ ```
71
+
72
+ ### 1. Define Permissions
73
+
74
+ ```typescript
75
+ import { permission, intermediate, FilterRule } from "./library_2/mod.ts";
76
+
77
+ // Resource fetchers
78
+ async function getArticle(id: string) {
79
+ return db.articles.findById(id);
80
+ }
81
+
82
+ async function getComment(ids: [string, string]) {
83
+ const [articleId, commentId] = ids;
84
+ return db.comments.findById(commentId, { articleId });
85
+ }
86
+
87
+ // Permission schemas
88
+ const permissionsSchemas = {
89
+ // Simple permission
90
+ "article.create": permission(),
91
+
92
+ // Permission with context + field filtering
93
+ "article.read": permission(getArticle, [FilterRule()] as const),
94
+
95
+ // Permission with context but without rules
96
+ "article.update": permission(getArticle),
97
+
98
+ // Intermediate permission (expand to children)
99
+ "article.manage": intermediate(
100
+ (ctx) => [
101
+ { subject: ctx.subject, key: "article.read", target: ctx.target },
102
+ { subject: ctx.subject, key: "article.update", target: ctx.target },
103
+ { subject: ctx.subject, key: "article.delete", target: ctx.target },
104
+ ],
105
+ getArticle
106
+ ),
107
+
108
+ // Permission with nested context
109
+ "article.comment.delete": permission(getComment),
110
+ };
111
+ ```
112
+
113
+ ### 2. Create Providers
114
+
115
+ **Providers** are sources of permissions. They return the permissions for a subject.
116
+
117
+ ```typescript
118
+ import { directProvider, ownerProvider } from "./library_2/mod.ts";
119
+ import type { PermissionProvider } from "./library_2/mod.ts";
120
+
121
+ // Provider 1: Static permissions
122
+ const staticPerms = directProvider([
123
+ {
124
+ subject: adminUser,
125
+ key: "article.create",
126
+ },
127
+ {
128
+ subject: moderatorUser,
129
+ key: "article.update",
130
+ target: "article:*",
131
+ },
132
+ ]);
133
+
134
+ // Provider 2: Ownership-based permissions
135
+ const ownerPerms = ownerProvider(
136
+ ["article.read", "article.update", "article.delete"],
137
+ "article:*",
138
+ {
139
+ filter: { _id: true, title: true, body: true, owner: true }
140
+ }
141
+ );
142
+
143
+ // Provider 3: Custom provider (e.g., from database)
144
+ function databaseProvider(): PermissionProvider {
145
+ return {
146
+ provide: async (subject, _key, _target) => {
147
+ const userPerms = await db.permissions.find({ userId: subject.id });
148
+ return userPerms.map(p => ({
149
+ subject,
150
+ key: p.permissionKey,
151
+ target: p.target,
152
+ filter: p.filter,
153
+ }));
154
+ }
155
+ };
156
+ }
157
+
158
+ // Provider 4: Conditional provider
159
+ function publicArticleProvider(): PermissionProvider {
160
+ return {
161
+ provide: (subject, _key, _target) => {
162
+ return Promise.resolve([
163
+ {
164
+ key: "article.read",
165
+ subject,
166
+ target: "*",
167
+ with: { public: true }, // Condition: article must be public
168
+ filter: { _id: true, title: true, body: true },
169
+ },
170
+ ]);
171
+ }
172
+ };
173
+ }
174
+ ```
175
+
176
+ ### 3. Create the Permission System
177
+
178
+ ```typescript
179
+ import { createPermissionSystem, TimeRule, IpRule, WithRule } from "./library_2/mod.ts";
180
+
181
+ const permSystem = createPermissionSystem({
182
+ schemas: permissionsSchemas,
183
+
184
+ sources: [
185
+ staticPerms,
186
+ ownerPerms,
187
+ publicArticleProvider(),
188
+ databaseProvider(),
189
+ ],
190
+
191
+ // Global validation rules (applied to ALL permissions)
192
+ rules: [
193
+ TimeRule(), // Validate time constraints (startDate, endDate)
194
+ IpRule(), // Validate IP restrictions
195
+ WithRule(), // Validate resource constraints
196
+ ] as const,
197
+
198
+ // Max depth for recursive intermediate resolution
199
+ maxIntermediateDepth: 10,
200
+ });
201
+ ```
202
+
203
+ ### 4. Check Permissions
204
+
205
+ ```typescript
206
+ const user = { id: "user:123" };
207
+
208
+ // Permission without context
209
+ const canCreate = await permSystem.can(user, "article.create");
210
+ if (canCreate.ok) {
211
+ // User can create articles
212
+ }
213
+
214
+ // Permission with context (target)
215
+ const canRead = await permSystem.can(user, "article.read", "article:1");
216
+ if (canRead.ok) {
217
+ // User can read article:1
218
+ console.log("Allowed fields:", canRead.output?.filter);
219
+ console.log("Filtered data:", canRead.output?.data);
220
+ }
221
+
222
+ // Create a checker with shared context + cache
223
+ const checker = permSystem.context({
224
+ subject: user,
225
+ checkDate: new Date("2024-01-01"),
226
+ ips: ["192.168.1.1"],
227
+ });
228
+
229
+ // All calls to checker.can() share the same cache!
230
+ const r1 = await checker.can("article.read", "article:1"); // FETCH
231
+ const r2 = await checker.can("article.update", "article:1"); // CACHE HIT
232
+ const r3 = await checker.can("article.delete", "article:1"); // CACHE HIT
233
+ ```
234
+
235
+ ---
236
+
237
+ ## 🧪 Complete Examples
238
+
239
+ ### Example 1: Blog with Field Filtering
240
+
241
+ ```typescript
242
+ import { createPermissionSystem, permission, FilterRule, ownerProvider } from "./library_2/mod.ts";
243
+
244
+ type Article = {
245
+ _id: string;
246
+ title: string;
247
+ body: string;
248
+ owner: string;
249
+ salary?: number;
250
+ secret?: string;
251
+ };
252
+
253
+ async function getArticle(id: string): Promise<Article | undefined> {
254
+ return db.articles.findById(id);
255
+ }
256
+
257
+ const schemas = {
258
+ "article.read": permission(getArticle, [FilterRule()] as const),
259
+ };
260
+
261
+ const permSystem = createPermissionSystem({
262
+ schemas,
263
+ sources: [
264
+ // Public users see basic fields
265
+ publicProvider({
266
+ filter: { _id: true, title: true, body: true }
267
+ }),
268
+
269
+ // Owners see more fields
270
+ ownerProvider(["article.read"], "article:*", {
271
+ filter: { _id: true, title: true, body: true, owner: true, salary: true }
272
+ }),
273
+
274
+ // Admins see everything
275
+ adminProvider({
276
+ filter: { _id: true, title: true, body: true, owner: true, salary: true, secret: true }
277
+ }),
278
+ ],
279
+ });
280
+
281
+ // Usage
282
+ const result = await permSystem.can(publicUser, "article.read", "article:1");
283
+ // result.output.data = { _id, title, body } ← filtered automatically
284
+
285
+ const result2 = await permSystem.can(ownerUser, "article.read", "article:1");
286
+ // result2.output.data = { _id, title, body, owner, salary } ← more fields
287
+ ```
288
+
289
+ ### Example 2: Hierarchical Permissions
290
+
291
+ ```typescript
292
+ const schemas = {
293
+ "article.read": permission(getArticle),
294
+ "article.update": permission(getArticle),
295
+ "article.delete": permission(getArticle),
296
+
297
+ // Intermediate permission
298
+ "article.manage": intermediate(
299
+ (ctx) => [
300
+ { ...ctx, key: "article.read" },
301
+ { ...ctx, key: "article.update" },
302
+ { ...ctx, key: "article.delete" },
303
+ ],
304
+ getArticle
305
+ ),
306
+ };
307
+
308
+ const permSystem = createPermissionSystem({
309
+ schemas,
310
+ sources: [
311
+ directProvider([
312
+ { subject: admin, key: "article.manage", target: "*" }
313
+ ])
314
+ ],
315
+ });
316
+
317
+ // Admin has article.manage, which expands to read + update + delete
318
+ const canRead = await permSystem.can(admin, "article.read", "article:1");
319
+ // ✅ true (via article.manage)
320
+
321
+ const canUpdate = await permSystem.can(admin, "article.update", "article:1");
322
+ // ✅ true (via article.manage)
323
+ ```
324
+
325
+ ### Example 3: Conditional Permissions
326
+
327
+ ```typescript
328
+ import { WithRule } from "./library_2/mod.ts";
329
+
330
+ const schemas = {
331
+ "article.read": permission(getArticle),
332
+ };
333
+
334
+ const permSystem = createPermissionSystem({
335
+ schemas,
336
+ sources: [
337
+ // Only on public articles
338
+ {
339
+ provide: (subject) => Promise.resolve([{
340
+ subject,
341
+ key: "article.read",
342
+ target: "*",
343
+ with: { public: true }, // ← Condition
344
+ }])
345
+ },
346
+
347
+ // Only if user is author
348
+ {
349
+ provide: (subject) => Promise.resolve([{
350
+ subject,
351
+ key: "article.read",
352
+ target: "*",
353
+ with: { author: subject.id }, // ← Condition
354
+ }])
355
+ },
356
+ ],
357
+ rules: [WithRule()] as const,
358
+ });
359
+
360
+ // Only works if article.public === true OR article.author === user.id
361
+ ```
362
+
363
+ ### Example 4: Resource Caching with `context()`
364
+
365
+ ```typescript
366
+ import { createPermissionSystem, permission, FilterRule } from "./library_2/mod.ts";
367
+
368
+ const schemas = {
369
+ "article.read": permission(getArticle, [FilterRule()] as const),
370
+ "article.update": permission(getArticle),
371
+ "article.delete": permission(getArticle),
372
+ };
373
+
374
+ const permSystem = createPermissionSystem({
375
+ schemas,
376
+ sources: [ownerProvider(["article.read", "article.update", "article.delete"], "article:*")],
377
+ });
378
+
379
+ // WITHOUT context(): each permission triggers a fetch
380
+ await permSystem.can(user, "article.read", "article:1"); // FETCH #1
381
+ await permSystem.can(user, "article.update", "article:1"); // FETCH #2
382
+ await permSystem.can(user, "article.delete", "article:1"); // FETCH #3
383
+
384
+ // WITH context(): cache is shared between all can()
385
+ const checker = permSystem.context({ subject: user });
386
+ await checker.can("article.read", "article:1"); // FETCH #1
387
+ await checker.can("article.update", "article:1"); // CACHE HIT ✨
388
+ await checker.can("article.delete", "article:1"); // CACHE HIT ✨
389
+
390
+ // Cache also works in nested functions
391
+ async function checkAllPermissions(checker, articleId: string) {
392
+ const canRead = await checker.can("article.read", articleId); // CACHE HIT
393
+ const canUpdate = await checker.can("article.update", articleId); // CACHE HIT
394
+ return { canRead: canRead.ok, canUpdate: canUpdate.ok };
395
+ }
396
+
397
+ const perms = await checkAllPermissions(checker, "article:1");
398
+ // ↑ No new fetch! Everything is cached
399
+ ```
400
+
401
+ **Advantages of `context()`:**
402
+ - ✅ Cache shared between all `can()` of the same checker
403
+ - ✅ Custom context (dates, IPs, metadata) in all calls
404
+ - ✅ Elegant API: `checker.can(key, target)` instead of `can(subject, key, target)`
405
+ - ✅ No need for callbacks/closures (unlike AsyncContext)
406
+
407
+ ---
408
+
409
+ ## 🏗️ Architecture
410
+
411
+ ### Validation Flow
412
+
413
+ ```
414
+ 1. permSystem.can(subject, key, target) or checker.can(key, target)
415
+ ↓
416
+ 2. Call all providers → [permissions]
417
+ ↓
418
+ 3. Resolve intermediates (recursive, max depth: 10) → [resolved permissions]
419
+ ↓
420
+ 4. Filter by key + target matching → [matched permissions]
421
+ ↓
422
+ 5. Apply global rules (Time, IP, With) → [valid permissions]
423
+ ↓
424
+ 6. Fetch resource (with cache if context()) ✨
425
+ ↓
426
+ 7. Apply output rules (FilterRule) → outputs
427
+ ↓
428
+ 8. Merge outputs (union strategy) → final output
429
+ ↓
430
+ 9. Return { ok: true, output }
431
+ ```
432
+
433
+ **Note on caching**: Without `context()`, each `can()` creates its own local cache. With `context()`, all `can()` of the same checker share the same cache Map.
434
+
435
+ ### Components
436
+
437
+ | Component | Role | Example |
438
+ |-----------|------|---------|
439
+ | **Permission** | Defines a permission with optional context | `permission(getArticle)` |
440
+ | **Intermediate** | Permission that resolves to other permissions | `intermediate((ctx) => [...])` |
441
+ | **Provider** | Source of permissions for a subject | `ownerProvider(...)` |
442
+ | **Global Rule** | Global validation (applied to all permissions) | `TimeRule()`, `IpRule()` |
443
+ | **Output Rule** | Generates metadata/output | `FilterRule()` |
444
+
445
+ ---
446
+
447
+ ## 🎨 Advanced Patterns
448
+
449
+ ### Pattern 1: Multi-Tenant with Organizations
450
+
451
+ ```typescript
452
+ const schemas = {
453
+ "org.member.read": permission(getOrgMember, [FilterRule()] as const),
454
+ };
455
+
456
+ function orgMemberProvider(): PermissionProvider {
457
+ return {
458
+ provide: async (subject, _key, _target) => {
459
+ // Get user's organizations
460
+ const orgs = await db.orgMembers.find({ userId: subject.id });
461
+
462
+ return orgs.map(org => ({
463
+ subject,
464
+ key: "org.member.read",
465
+ target: `org:${org.orgId}:member:*`,
466
+ filter: org.role === 'admin'
467
+ ? { _id: true, name: true, email: true, salary: true }
468
+ : { _id: true, name: true, email: true },
469
+ }));
470
+ }
471
+ };
472
+ }
473
+ ```
474
+
475
+ ### Pattern 2: Rate Limiting
476
+
477
+ ```typescript
478
+ function RateLimitRule(): OutputRule<
479
+ { rateLimit?: { max: number, window: string } }
480
+ > {
481
+ return {
482
+ name: "rateLimit",
483
+ output: ({ state, currentOutput }) => {
484
+ const current = currentOutput?.rateLimit?.max || 0;
485
+ const incoming = state.rateLimit?.max || 0;
486
+
487
+ return {
488
+ rateLimit: {
489
+ max: Math.max(current, incoming), // Most permissive
490
+ window: state.rateLimit?.window || "1h"
491
+ }
492
+ };
493
+ }
494
+ };
495
+ }
496
+
497
+ // Usage
498
+ const schemas = {
499
+ "api.call": permission(undefined, [RateLimitRule()] as const),
500
+ };
501
+
502
+ const result = await permSystem.can(user, "api.call");
503
+ // result.output.rateLimit = { max: 1000, window: "1h" }
504
+ ```
505
+
506
+ ### Pattern 3: Temporary Permissions
507
+
508
+ ```typescript
509
+ import { TimeRule } from "./library_2/mod.ts";
510
+
511
+ const permSystem = createPermissionSystem({
512
+ schemas,
513
+ sources: [
514
+ {
515
+ provide: (subject) => Promise.resolve([{
516
+ subject,
517
+ key: "article.read",
518
+ target: "article:1",
519
+ startDate: new Date("2024-01-01"),
520
+ endDate: new Date("2024-12-31"),
521
+ }])
522
+ }
523
+ ],
524
+ rules: [TimeRule()] as const,
525
+ });
526
+
527
+ // Only works between 2024-01-01 and 2024-12-31
528
+ ```
529
+
530
+ ---
531
+
532
+ ## 🚀 Performance
533
+
534
+ ### Built-in Optimizations
535
+
536
+ 1. **Resource Caching**: With `context()`, cache is shared between all `can()` calls
537
+ 2. **Resource Fetching**: Resource is fetched **only once** per permission check
538
+ 3. **Short-Circuit**: Global rules stop validation as soon as one fails
539
+ 4. **Lazy Evaluation**: Intermediates are resolved only if necessary
540
+
541
+ ### Tips for Best Performance
542
+
543
+ ```typescript
544
+ // ✅ GOOD: Put most restrictive rules first
545
+ rules: [
546
+ IpRule(), // Fast, filters many
547
+ WithRule(), // May fetch resource
548
+ TimeRule(), // Fast
549
+ ]
550
+
551
+ // ❌ BAD: Everyone has access, providers are useless
552
+ sources: [
553
+ alwaysAllowProvider(), // ← Useless if you have this
554
+ ownerProvider(...), // ← Never used
555
+ ]
556
+
557
+ // ✅ GOOD: Specific provider
558
+ sources: [
559
+ roleBasedProvider(), // Return quickly if wrong role
560
+ ownerProvider(...),
561
+ ]
562
+ ```
563
+
564
+ ---
565
+
566
+ ## 🔧 API Reference
567
+
568
+ ### Core Functions
569
+
570
+ #### `createPermissionSystem<PS, TRules>(config)`
571
+
572
+ Creates a permission system.
573
+
574
+ ```typescript
575
+ const permSystem = createPermissionSystem({
576
+ schemas: PermissionSchemas,
577
+ sources: PermissionProvider[],
578
+ rules?: readonly PermissionRule[],
579
+ maxIntermediateDepth?: number,
580
+ });
581
+ ```
582
+
583
+ #### `permission<C, TRules>(fetchTarget?, rules?)`
584
+
585
+ Creates a permission.
586
+
587
+ ```typescript
588
+ // No context, no rules
589
+ permission()
590
+
591
+ // With context
592
+ permission<ArticleId>(getArticle)
593
+
594
+ // With context + rules
595
+ permission(getArticle, [FilterRule()] as const)
596
+ ```
597
+
598
+ #### `intermediate<C, TRules>(provide, fetchTarget?, rules?)`
599
+
600
+ Creates an intermediate permission.
601
+
602
+ ```typescript
603
+ intermediate(
604
+ (ctx) => [
605
+ { ...ctx, key: "article.read" },
606
+ { ...ctx, key: "article.update" },
607
+ ],
608
+ getArticle
609
+ )
610
+ ```
611
+
612
+ ### Permission System Methods
613
+
614
+ #### `permSystem.can<K>(subject, key, ...target?)`
615
+
616
+ Checks a permission for a given subject.
617
+
618
+ ```typescript
619
+ // Without target
620
+ await permSystem.can(user, "article.create")
621
+
622
+ // With target
623
+ await permSystem.can(user, "article.read", "article:1")
624
+
625
+ // Return
626
+ type PermissionResult<TOutput> = {
627
+ ok: boolean;
628
+ output?: TOutput; // Type inferred from rules
629
+ }
630
+ ```
631
+
632
+ #### `permSystem.context(ctx)`
633
+
634
+ Creates a checker with shared context and cache.
635
+
636
+ ```typescript
637
+ const checker = permSystem.context({
638
+ subject: user, // Required
639
+ checkDate: new Date(), // Optional (for TimeRule)
640
+ ips: ["192.168.1.1"], // Optional (for IpRule)
641
+ // ... other custom properties
642
+ });
643
+
644
+ // All can() of the checker share the same cache
645
+ await checker.can("article.read", "article:1"); // FETCH
646
+ await checker.can("article.update", "article:1"); // CACHE HIT
647
+ ```
648
+
649
+ **Return**: `{ can<K>(key, ...target?) => Promise<PermissionResult<...>> }`
650
+
651
+ ### Built-in Providers
652
+
653
+ #### `directProvider(permissions)`
654
+
655
+ Provider with static permissions.
656
+
657
+ ```typescript
658
+ directProvider([
659
+ { subject: user1, key: "article.create" },
660
+ { subject: user2, key: "article.read", target: "article:*" },
661
+ ])
662
+ ```
663
+
664
+ #### `ownerProvider(keys, targetPattern, metadata?)`
665
+
666
+ Ownership-based provider.
667
+
668
+ ```typescript
669
+ ownerProvider(
670
+ ["article.read", "article.update"],
671
+ "article:*",
672
+ { filter: { _id: true, title: true } }
673
+ )
674
+ ```
675
+
676
+ ### Built-in Rules
677
+
678
+ #### Global Rules
679
+
680
+ - `TimeRule()`: Validates time constraints (`startDate`, `endDate`)
681
+ - `IpRule()`: Validates IP restrictions (`allowedIps`)
682
+ - `WithRule()`: Validates resource constraints (`with`)
683
+
684
+ #### Output Rules
685
+
686
+ - `FilterRule()`: Applies field-level filtering
687
+
688
+ ### Utilities
689
+
690
+ #### `matchPath(requested, pattern)`
691
+
692
+ Matches a path with wildcards.
693
+
694
+ ```typescript
695
+ matchPath("article:123", "article:*") // true
696
+ matchPath(["article:1", "comment:2"], ["article:*", "*"]) // true
697
+ ```
698
+
699
+ #### `applyFilter(obj, filterSpec)`
700
+
701
+ Applies a filter to an object.
702
+
703
+ ```typescript
704
+ applyFilter(
705
+ { _id: 1, name: "John", password: "secret" },
706
+ { _id: true, name: true }
707
+ )
708
+ // → { _id: 1, name: "John" }
709
+ ```
710
+
711
+ #### `mergeFilters(filter1, filter2)`
712
+
713
+ Merges two filters (union).
714
+
715
+ ```typescript
716
+ mergeFilters(
717
+ { _id: true, name: true },
718
+ { name: true, email: true }
719
+ )
720
+ // → { _id: true, name: true, email: true }
721
+ ```
722
+
723
+ ---
724
+
725
+ ## 🆚 Comparison with Other Libraries
726
+
727
+ | Feature | CASL | Casbin | **Library_2** |
728
+ |---------|------|--------|---------------|
729
+ | Type Safety | ⭐⭐⭐ | ⭐ | ⭐⭐⭐⭐⭐ |
730
+ | Field-level permissions | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ |
731
+ | Auto-filtering | ❌ | ❌ | ✅ |
732
+ | Multi-permission accumulation | ❌ | ❌ | ✅ |
733
+ | Output metadata | ⭐⭐ | ⭐ | ⭐⭐⭐⭐⭐ |
734
+ | Hierarchical permissions | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
735
+ | Flexible providers | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
736
+
737
+ ---
738
+
739
+ ## 📝 License
740
+
741
+ MIT
742
+
743
+ ---
744
+
745
+ ## 🤝 Contributing
746
+
747
+ Contributions welcome! See examples in `/playground` for usage patterns.