odgn-rights 0.1.0 → 0.5.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.
Files changed (53) hide show
  1. package/README.md +489 -0
  2. package/dist/adapters/base-adapter.d.ts +83 -0
  3. package/dist/adapters/base-adapter.js +142 -0
  4. package/dist/adapters/factories.d.ts +31 -0
  5. package/dist/adapters/factories.js +48 -0
  6. package/dist/adapters/index.d.ts +11 -0
  7. package/dist/adapters/index.js +12 -0
  8. package/dist/adapters/postgres-adapter.d.ts +51 -0
  9. package/dist/adapters/postgres-adapter.js +469 -0
  10. package/dist/adapters/redis-adapter.d.ts +84 -0
  11. package/dist/adapters/redis-adapter.js +673 -0
  12. package/dist/adapters/schema.d.ts +25 -0
  13. package/dist/adapters/schema.js +186 -0
  14. package/dist/adapters/sqlite-adapter.d.ts +78 -0
  15. package/dist/adapters/sqlite-adapter.js +655 -0
  16. package/dist/adapters/types.d.ts +174 -0
  17. package/dist/adapters/types.js +1 -0
  18. package/dist/cli/commands/check.d.ts +2 -0
  19. package/dist/cli/commands/check.js +38 -0
  20. package/dist/cli/commands/explain.d.ts +2 -0
  21. package/dist/cli/commands/explain.js +93 -0
  22. package/dist/cli/commands/validate.d.ts +2 -0
  23. package/dist/cli/commands/validate.js +177 -0
  24. package/dist/cli/helpers/config-loader.d.ts +3 -0
  25. package/dist/cli/helpers/config-loader.js +13 -0
  26. package/dist/cli/helpers/flag-parser.d.ts +3 -0
  27. package/dist/cli/helpers/flag-parser.js +40 -0
  28. package/dist/cli/helpers/output.d.ts +10 -0
  29. package/dist/cli/helpers/output.js +29 -0
  30. package/dist/cli/index.d.ts +2 -0
  31. package/dist/cli/index.js +15 -0
  32. package/dist/cli/types.d.ts +10 -0
  33. package/dist/cli/types.js +1 -0
  34. package/dist/helpers.d.ts +16 -0
  35. package/dist/{utils.js → helpers.js} +22 -0
  36. package/dist/index.d.ts +3 -1
  37. package/dist/index.js +3 -1
  38. package/dist/integrations/elysia.d.ts +235 -0
  39. package/dist/integrations/elysia.js +375 -0
  40. package/dist/right.d.ts +25 -1
  41. package/dist/right.js +183 -19
  42. package/dist/rights.d.ts +31 -0
  43. package/dist/rights.js +162 -31
  44. package/dist/role-registry.d.ts +9 -0
  45. package/dist/role-registry.js +15 -0
  46. package/dist/role.d.ts +3 -1
  47. package/dist/role.js +11 -0
  48. package/dist/subject-registry.d.ts +77 -0
  49. package/dist/subject-registry.js +123 -0
  50. package/dist/subject.d.ts +21 -2
  51. package/dist/subject.js +51 -8
  52. package/package.json +63 -7
  53. package/dist/utils.d.ts +0 -2
package/README.md CHANGED
@@ -98,6 +98,100 @@ rights.write('/posts/1', { userId: 'abc', ownerId: 'xyz' }); // false
98
98
  - `**` matches across segments (`/*/device/**`)
99
99
  - `?` matches a single character (no slash)
100
100
 
101
+ ## Rule Priority
102
+
103
+ By default, rules are matched by **specificity** — the most specific matching rule wins. However, you can override this with explicit **priority** values.
104
+
105
+ ### How Priority Works
106
+
107
+ 1. **Higher priority wins** regardless of specificity
108
+ 2. **Equal priorities** fall back to specificity comparison
109
+ 3. **Default priority is 0** when not specified
110
+ 4. **Negative priorities** can deprioritize rules below the default
111
+
112
+ ```ts
113
+ const rights = new Rights();
114
+
115
+ // Specific path, default priority (0)
116
+ rights.add(
117
+ new Right('/posts/123', {
118
+ allow: [Flags.READ],
119
+ deny: [Flags.WRITE]
120
+ })
121
+ );
122
+
123
+ // Wildcard path, but high priority (100) — this rule wins!
124
+ rights.add(
125
+ new Right('/posts/*', {
126
+ allow: [Flags.READ, Flags.WRITE],
127
+ priority: 100
128
+ })
129
+ );
130
+
131
+ rights.write('/posts/123'); // true — high-priority wildcard rule wins
132
+ ```
133
+
134
+ ### Priority in Serialization
135
+
136
+ Priority is included in both text and JSON serialization formats.
137
+
138
+ **Text format** uses `^` after the path:
139
+
140
+ ```ts
141
+ const right = new Right('/posts/*', {
142
+ allow: [Flags.WRITE],
143
+ priority: 100
144
+ });
145
+ right.toString(); // "+w:/posts/*^100"
146
+
147
+ // With tags and time ranges
148
+ // Format: [flags]:[path]^[priority]#[tags]@[validFrom]/[validUntil]
149
+ Right.parse('+rw:/admin/*^50#secure');
150
+ ```
151
+
152
+ **JSON format** includes an optional `priority` field:
153
+
154
+ ```json
155
+ [
156
+ { "path": "/posts/*", "allow": "rw", "priority": 100 },
157
+ { "path": "/posts/123", "allow": "r", "deny": "w" }
158
+ ]
159
+ ```
160
+
161
+ Priority is omitted from serialization when it equals 0 (the default).
162
+
163
+ ### Use Cases
164
+
165
+ - **Emergency overrides**: Grant temporary high-priority access that bypasses normal rules
166
+ - **Default deny rules**: Use negative priority for fallback deny rules
167
+ - **Policy layers**: Implement organizational policies at different priority levels
168
+
169
+ ```ts
170
+ // Low-priority default: deny all writes
171
+ rights.add(
172
+ new Right('/**', {
173
+ deny: [Flags.WRITE],
174
+ priority: -100
175
+ })
176
+ );
177
+
178
+ // Normal priority: department-level permissions
179
+ rights.add(
180
+ new Right('/dept/engineering/**', {
181
+ allow: [Flags.READ, Flags.WRITE]
182
+ })
183
+ );
184
+
185
+ // High priority: emergency maintenance access
186
+ rights.add(
187
+ new Right('/system/**', {
188
+ allow: [Flags.ALL],
189
+ priority: 1000,
190
+ tags: ['emergency']
191
+ })
192
+ );
193
+ ```
194
+
101
195
  ## JSON Round‑Trip
102
196
 
103
197
  ```ts
@@ -105,3 +199,398 @@ const json = rights.toJSON();
105
199
  // [ { path: '/', allow: 'r' }, { path: '/*/device/**', allow: 'c' }, ... ]
106
200
  const loaded = Rights.fromJSON(json);
107
201
  ```
202
+
203
+ ## Batch Permission Checks
204
+
205
+ Efficiently check multiple permissions at once with `checkMany()`.
206
+
207
+ ### Basic Usage
208
+
209
+ ```ts
210
+ const rights = new Rights();
211
+ rights.allow('/users/*', Flags.READ);
212
+ rights.allow('/posts/*', Flags.WRITE);
213
+ rights.deny('/admin', Flags.ALL);
214
+
215
+ const results = rights.checkMany([
216
+ { path: '/users/1', flags: Flags.READ },
217
+ { path: '/posts/1', flags: Flags.WRITE },
218
+ { path: '/admin', flags: Flags.ALL }
219
+ ]);
220
+ // Returns: [true, true, false]
221
+ ```
222
+
223
+ ### With Context
224
+
225
+ The same context is shared across all checks:
226
+
227
+ ```ts
228
+ rights.add(
229
+ new Right('/posts/*', {
230
+ allow: [Flags.WRITE],
231
+ condition: ctx => ctx.userId === ctx.ownerId
232
+ })
233
+ );
234
+
235
+ const results = rights.checkMany(
236
+ [
237
+ { path: '/posts/1', flags: Flags.WRITE },
238
+ { path: '/posts/2', flags: Flags.WRITE },
239
+ { path: '/posts/3', flags: Flags.WRITE }
240
+ ],
241
+ { userId: 'user1', ownerId: 'user1' }
242
+ );
243
+ // Returns: [true, true, true]
244
+ ```
245
+
246
+ ### With Subjects
247
+
248
+ Works with subjects that have multiple roles:
249
+
250
+ ```ts
251
+ const viewer = new Role('viewer', new Rights().allow('/docs', Flags.READ));
252
+ const writer = new Role('writer', new Rights().allow('/docs', Flags.WRITE));
253
+
254
+ const subject = new Subject().memberOf(viewer).memberOf(writer);
255
+
256
+ const results = subject.checkMany([
257
+ { path: '/docs', flags: Flags.READ },
258
+ { path: '/docs', flags: Flags.WRITE },
259
+ { path: '/docs', flags: Flags.DELETE }
260
+ ]);
261
+ // Returns: [true, true, false]
262
+ ```
263
+
264
+ ### Use Cases
265
+
266
+ - **Bulk authorization**: Check multiple resource permissions in a single call
267
+ - **Feature flags**: Enable/disable multiple features based on permissions
268
+ - **API responses**: Include permission information for multiple resources
269
+ - **UI rendering**: Determine visibility of multiple UI elements efficiently
270
+
271
+ ## CLI Tool
272
+
273
+ The CLI tool helps test and debug permission configurations from the command line.
274
+
275
+ ### Installation
276
+
277
+ ```bash
278
+ # Install globally
279
+ npm install -g @odgn/rights
280
+
281
+ # Or use with npx
282
+ npx @odgn/rights --help
283
+
284
+ # Or run directly with bun
285
+ bun run src/cli/index.ts --help
286
+ ```
287
+
288
+ ### Commands
289
+
290
+ #### check
291
+
292
+ Test if a permission is allowed:
293
+
294
+ ```bash
295
+ # Basic usage
296
+ odgn-rights check -c config.json -p /users/123 -f READ
297
+
298
+ # With combined flags
299
+ odgn-rights check -c config.json -p /users/123 -f RW
300
+
301
+ # With comma-separated flags
302
+ odgn-rights check -c config.json -p /users/123 -f READ,WRITE
303
+
304
+ # Quiet mode for scripting (outputs 'true' or 'false')
305
+ odgn-rights check -c config.json -p /users/123 -f READ --quiet
306
+
307
+ # With context for conditional rights
308
+ odgn-rights check -c config.json -p /posts/1 -f WRITE --context '{"userId":"abc","ownerId":"abc"}'
309
+
310
+ # Override time for time-based rights
311
+ odgn-rights check -c config.json -p /scheduled -f READ --time 2025-06-15T12:00:00Z
312
+ ```
313
+
314
+ Exit codes: `0` = allowed, `1` = denied, `2` = error
315
+
316
+ #### explain
317
+
318
+ Understand why a permission is allowed or denied:
319
+
320
+ ```bash
321
+ # Basic usage
322
+ odgn-rights explain -c config.json -p /users/123 -f WRITE
323
+
324
+ # JSON output
325
+ odgn-rights explain -c config.json -p /users/123 -f READ --json
326
+ ```
327
+
328
+ The explain command shows:
329
+
330
+ - Decision breakdown per flag
331
+ - Matching rules sorted by specificity
332
+ - Suggestions for granting denied permissions
333
+
334
+ #### validate
335
+
336
+ Validate a configuration file:
337
+
338
+ ```bash
339
+ # Validate JSON config
340
+ odgn-rights validate config.json
341
+
342
+ # Validate string format config
343
+ odgn-rights validate config.txt
344
+
345
+ # Strict mode (warns on broad patterns like /**)
346
+ odgn-rights validate --strict config.json
347
+
348
+ # JSON output
349
+ odgn-rights validate --json config.json
350
+ ```
351
+
352
+ Exit codes: `0` = valid, `1` = validation errors, `2` = file error
353
+
354
+ ### Configuration Formats
355
+
356
+ The CLI supports two configuration formats:
357
+
358
+ **JSON format** (`config.json`):
359
+
360
+ ```json
361
+ [
362
+ { "path": "/", "allow": "r" },
363
+ { "path": "/users/*", "allow": "rw" },
364
+ { "path": "/admin/**", "allow": "*", "tags": ["admin"] },
365
+ { "path": "/scheduled", "allow": "r", "validFrom": "2025-01-01T00:00:00Z" }
366
+ ]
367
+ ```
368
+
369
+ **String format** (`config.txt`):
370
+
371
+ ```
372
+ # Comments start with #
373
+ +r:/
374
+ +rw:/users/*
375
+ +*:/admin/**
376
+ -d+rw:/public
377
+ ```
378
+
379
+ ### Flag Reference
380
+
381
+ | Flag | Letter | Description |
382
+ | ------- | ------ | ------------------ |
383
+ | READ | R | Read permission |
384
+ | WRITE | W | Write permission |
385
+ | CREATE | C | Create permission |
386
+ | DELETE | D | Delete permission |
387
+ | EXECUTE | X | Execute permission |
388
+ | ALL | \* | All permissions |
389
+
390
+ Flags can be combined: `RW`, `READ,WRITE`, `RWCDX`
391
+
392
+ ## Database Adapters
393
+
394
+ Database adapters enable persistent storage of rights configurations in SQLite or PostgreSQL databases. This is useful for applications that need to load permissions from a database, share configurations across services, or audit permission changes.
395
+
396
+ ### Installation
397
+
398
+ The adapters use Bun's built-in database drivers (`bun:sqlite` and `bun` SQL), so no additional dependencies are required.
399
+
400
+ ```ts
401
+ import { PostgresAdapter, SQLiteAdapter } from 'odgn-rights/adapters';
402
+ ```
403
+
404
+ ### Table Prefix
405
+
406
+ All adapters support a configurable table prefix. The default prefix is `tbl_`.
407
+
408
+ ```ts
409
+ // Default prefix creates tables: tbl_rights, tbl_roles, etc.
410
+ const adapter = new SQLiteAdapter({ filename: './permissions.db' });
411
+
412
+ // Custom prefix creates tables: auth_rights, auth_roles, etc.
413
+ const adapter = new SQLiteAdapter({
414
+ filename: './permissions.db',
415
+ tablePrefix: 'auth_'
416
+ });
417
+
418
+ // No prefix creates tables: rights, roles, etc.
419
+ const adapter = new SQLiteAdapter({
420
+ filename: './permissions.db',
421
+ tablePrefix: ''
422
+ });
423
+ ```
424
+
425
+ ### SQLite Adapter
426
+
427
+ SQLite is ideal for single-process applications, embedded systems, or local development.
428
+
429
+ ```ts
430
+ import { Flags, Right, Rights } from 'odgn-rights';
431
+ import { SQLiteAdapter } from 'odgn-rights/adapters';
432
+
433
+ // Create adapter and connect
434
+ const adapter = new SQLiteAdapter({
435
+ filename: './permissions.db', // Use ':memory:' for in-memory
436
+ enableWAL: true // Enable WAL mode for better concurrency
437
+ });
438
+
439
+ await adapter.connect();
440
+ await adapter.migrate();
441
+
442
+ // Save rights
443
+ const rights = new Rights();
444
+ rights.allow('/users/*', Flags.READ);
445
+ rights.allow('/admin/**', Flags.ALL);
446
+ await adapter.saveRights(rights);
447
+
448
+ // Load rights
449
+ const loaded = await adapter.loadRights();
450
+ loaded.has('/users/123', Flags.READ); // true
451
+
452
+ // Save and load roles
453
+ const { Role, RoleRegistry } = await import('odgn-rights');
454
+ const registry = new RoleRegistry();
455
+ const admin = registry.define('admin');
456
+ admin.rights.allow('/**', Flags.ALL);
457
+ await registry.saveTo(adapter);
458
+
459
+ // Load registry from database
460
+ const loadedRegistry = await RoleRegistry.loadFrom(adapter);
461
+
462
+ await adapter.disconnect();
463
+ ```
464
+
465
+ ### PostgreSQL Adapter
466
+
467
+ PostgreSQL is ideal for multi-process applications, microservices, or when you need shared access to permissions.
468
+
469
+ ```ts
470
+ import { Flags, RoleRegistry, Subject } from 'odgn-rights';
471
+ import { PostgresAdapter } from 'odgn-rights/adapters';
472
+
473
+ const adapter = new PostgresAdapter({
474
+ url: 'postgres://user:pass@localhost:5432/mydb',
475
+ // Or use individual options:
476
+ // hostname: 'localhost',
477
+ // port: 5432,
478
+ // database: 'mydb',
479
+ // username: 'user',
480
+ // password: 'pass',
481
+ tablePrefix: 'perms_' // Optional custom prefix
482
+ });
483
+
484
+ await adapter.connect();
485
+ await adapter.migrate();
486
+
487
+ // Load registry and make changes
488
+ const registry = await adapter.loadRegistry();
489
+ const editor = registry.define('editor');
490
+ editor.rights.allow('/content/**', Flags.READ, Flags.WRITE);
491
+
492
+ // Save back
493
+ await adapter.saveRegistry(registry);
494
+
495
+ // Save subjects with roles
496
+ const user = new Subject();
497
+ user.memberOf(editor);
498
+ await adapter.saveSubject('user-123', user);
499
+
500
+ await adapter.disconnect();
501
+ ```
502
+
503
+ ### Factory Functions
504
+
505
+ Convenience functions for common patterns:
506
+
507
+ ```ts
508
+ import {
509
+ createPostgresRegistry,
510
+ createPostgresRights,
511
+ createSQLiteRegistry,
512
+ createSQLiteRights
513
+ } from 'odgn-rights/adapters';
514
+
515
+ // Create SQLite adapter with rights
516
+ const { adapter, rights } = await createSQLiteRights({
517
+ filename: './permissions.db'
518
+ });
519
+ rights.allow('/public/**', Flags.READ);
520
+ await adapter.saveRights(rights);
521
+ await adapter.disconnect();
522
+
523
+ // Create SQLite adapter with registry
524
+ const { adapter: regAdapter, registry } = await createSQLiteRegistry({
525
+ filename: ':memory:'
526
+ });
527
+ const viewer = registry.define('viewer');
528
+ viewer.rights.allow('/read/*', Flags.READ);
529
+ await registry.saveTo(regAdapter);
530
+ await regAdapter.disconnect();
531
+ ```
532
+
533
+ ### Transactions
534
+
535
+ Both adapters support transactions for atomic operations:
536
+
537
+ ```ts
538
+ await adapter.transaction(async () => {
539
+ await adapter.saveRight(new Right('/a', { allow: [Flags.READ] }));
540
+ await adapter.saveRight(new Right('/b', { allow: [Flags.WRITE] }));
541
+ // If an error is thrown, all changes are rolled back
542
+ });
543
+ ```
544
+
545
+ ### Adapter Interface
546
+
547
+ All adapters implement the `DatabaseAdapter` interface:
548
+
549
+ | Method | Description |
550
+ | ---------------------------------- | --------------------------------- |
551
+ | `connect()` | Connect to the database |
552
+ | `disconnect()` | Disconnect from the database |
553
+ | `migrate()` | Create or update schema |
554
+ | `saveRight(right)` | Save a single right |
555
+ | `saveRights(rights)` | Save multiple rights |
556
+ | `loadRight(id)` | Load a right by ID |
557
+ | `loadRights()` | Load all rights |
558
+ | `loadRightsByPath(pattern)` | Load rights matching a pattern |
559
+ | `deleteRight(id)` | Delete a right |
560
+ | `saveRole(role)` | Save a role with its rights |
561
+ | `loadRole(name)` | Load a role by name |
562
+ | `loadRoles()` | Load all roles |
563
+ | `deleteRole(name)` | Delete a role |
564
+ | `saveRegistry(registry)` | Save entire RoleRegistry |
565
+ | `loadRegistry()` | Load RoleRegistry with all roles |
566
+ | `saveSubject(identifier, subject)` | Save a subject |
567
+ | `loadSubject(identifier)` | Load a subject |
568
+ | `deleteSubject(identifier)` | Delete a subject |
569
+ | `clear()` | Clear all data (for testing) |
570
+ | `transaction(fn)` | Execute operations in transaction |
571
+
572
+ ### Database Schema
573
+
574
+ The adapters create the following tables (with the configured prefix):
575
+
576
+ | Table | Purpose |
577
+ | -------------------------- | ------------------------------------ |
578
+ | `{prefix}rights` | Individual rights with paths & flags |
579
+ | `{prefix}roles` | Role definitions |
580
+ | `{prefix}role_rights` | Role-to-rights mapping |
581
+ | `{prefix}role_inheritance` | Role inheritance relationships |
582
+ | `{prefix}subjects` | Subject records |
583
+ | `{prefix}subject_roles` | Subject-to-roles mapping |
584
+ | `{prefix}subject_rights` | Direct subject rights |
585
+
586
+ ### Persistence Metadata
587
+
588
+ Rights saved to the database receive a `dbId` property:
589
+
590
+ ```ts
591
+ const right = new Right('/test', { allow: [Flags.READ] });
592
+ console.log(right.dbId); // undefined
593
+
594
+ await adapter.saveRight(right);
595
+ console.log(right.dbId); // 1 (database ID)
596
+ ```
@@ -0,0 +1,83 @@
1
+ import { Flags } from '../constants';
2
+ import { Right } from '../right';
3
+ import { Rights } from '../rights';
4
+ import { Role } from '../role';
5
+ import { RoleRegistry } from '../role-registry';
6
+ import { Subject } from '../subject';
7
+ import type { BaseAdapterOptions, DatabaseAdapter, RightsRow, TableNames } from './types';
8
+ /**
9
+ * Abstract base class for database adapters.
10
+ * Provides common utility methods for serialization/deserialization
11
+ * and table name management.
12
+ */
13
+ export declare abstract class BaseAdapter implements DatabaseAdapter {
14
+ protected readonly tablePrefix: string;
15
+ protected readonly _tables: TableNames;
16
+ constructor(options?: BaseAdapterOptions);
17
+ /**
18
+ * Get the table names with the configured prefix
19
+ */
20
+ protected get tables(): TableNames;
21
+ /**
22
+ * Serialize tags array to JSON string
23
+ */
24
+ protected serializeTags: (tags: string[]) => string | null;
25
+ /**
26
+ * Deserialize JSON string to tags array
27
+ */
28
+ protected deserializeTags: (json: string | null) => string[];
29
+ /**
30
+ * Convert a Right instance to a database row (partial, without id and timestamps)
31
+ */
32
+ protected rightToRow: (right: Right) => Omit<RightsRow, "id" | "created_at" | "updated_at">;
33
+ /**
34
+ * Convert a database row to a Right instance
35
+ */
36
+ protected rowToRight: (row: RightsRow) => Right;
37
+ /**
38
+ * Convert a bitmask to an array of Flag values
39
+ */
40
+ protected maskToFlags: (mask: number) => Flags[];
41
+ /**
42
+ * Convert an array of Flag values to a bitmask
43
+ */
44
+ protected flagsToMask: (flags: Flags[]) => number;
45
+ /**
46
+ * Parse an ISO 8601 timestamp string to a Date, or undefined if null/invalid
47
+ */
48
+ protected parseTimestamp: (value: string | null) => Date | undefined;
49
+ abstract connect(): Promise<void>;
50
+ abstract disconnect(): Promise<void>;
51
+ abstract migrate(): Promise<void>;
52
+ abstract saveRight(right: Right): Promise<number>;
53
+ abstract saveRights(rights: Rights): Promise<number[]>;
54
+ abstract loadRight(id: number): Promise<Right | null>;
55
+ abstract loadRights(): Promise<Rights>;
56
+ abstract loadRightsByPath(pathPattern: string): Promise<Rights>;
57
+ abstract deleteRight(id: number): Promise<boolean>;
58
+ abstract saveRole(role: Role): Promise<number>;
59
+ abstract loadRole(name: string): Promise<Role | null>;
60
+ abstract loadRoles(): Promise<Role[]>;
61
+ abstract deleteRole(name: string): Promise<boolean>;
62
+ abstract saveRegistry(registry: RoleRegistry): Promise<void>;
63
+ abstract loadRegistry(): Promise<RoleRegistry>;
64
+ abstract saveSubject(identifier: string, subject: Subject): Promise<number>;
65
+ abstract loadSubject(identifier: string): Promise<Subject | null>;
66
+ abstract deleteSubject(identifier: string): Promise<boolean>;
67
+ /**
68
+ * Get all subject identifiers from the database.
69
+ * Used by findSubjectsWithAccess and can be overridden for optimization.
70
+ */
71
+ protected abstract getAllSubjectIdentifiers(): Promise<string[]>;
72
+ /**
73
+ * Find all subject identifiers that have access to a specific path with given flags.
74
+ * Default implementation uses getAllSubjectIdentifiers + loadSubject.
75
+ * Subclasses can override with optimized batch loading implementations.
76
+ * @param pathPattern The path pattern to check (supports wildcards)
77
+ * @param flags The flags to check for
78
+ * @returns Array of subject identifiers that have access
79
+ */
80
+ findSubjectsWithAccess(pathPattern: string, flags: Flags): Promise<string[]>;
81
+ abstract clear(): Promise<void>;
82
+ abstract transaction<T>(fn: (adapter: DatabaseAdapter) => Promise<T>): Promise<T>;
83
+ }