odgn-rights 0.2.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.
@@ -1,3 +1,4 @@
1
+ import type { DatabaseAdapter } from './adapters/types';
1
2
  import { Rights, type RightJSON } from './rights';
2
3
  import { Role } from './role';
3
4
  export type RoleJSON = {
@@ -10,5 +11,13 @@ export declare class RoleRegistry {
10
11
  define(name: string, rights?: Rights): Role;
11
12
  get(name: string): Role | undefined;
12
13
  toJSON(): RoleJSON[];
14
+ /**
15
+ * Load all roles and their relationships from a database adapter
16
+ */
17
+ static loadFrom(adapter: DatabaseAdapter): Promise<RoleRegistry>;
18
+ /**
19
+ * Save all roles and their relationships to a database adapter
20
+ */
21
+ saveTo(adapter: DatabaseAdapter): Promise<void>;
13
22
  static fromJSON(data: RoleJSON[]): RoleRegistry;
14
23
  }
@@ -23,6 +23,18 @@ export class RoleRegistry {
23
23
  toJSON() {
24
24
  return Array.from(this.roles.values()).map(r => r.toJSON());
25
25
  }
26
+ /**
27
+ * Load all roles and their relationships from a database adapter
28
+ */
29
+ static async loadFrom(adapter) {
30
+ return adapter.loadRegistry();
31
+ }
32
+ /**
33
+ * Save all roles and their relationships to a database adapter
34
+ */
35
+ async saveTo(adapter) {
36
+ await adapter.saveRegistry(this);
37
+ }
26
38
  static fromJSON(data) {
27
39
  const registry = new RoleRegistry();
28
40
  // First pass: create all roles
@@ -0,0 +1,77 @@
1
+ import type { DatabaseAdapter } from './adapters/types';
2
+ import { Flags } from './constants';
3
+ import type { ConditionContext } from './right';
4
+ import type { RoleRegistry } from './role-registry';
5
+ import { Subject, type SubjectJSON } from './subject';
6
+ export type SubjectRegistryJSON = {
7
+ [identifier: string]: SubjectJSON;
8
+ };
9
+ /**
10
+ * In-memory registry for managing Subject instances.
11
+ * Provides an API similar to RoleRegistry for consistency.
12
+ */
13
+ export declare class SubjectRegistry {
14
+ private subjects;
15
+ /**
16
+ * Register a subject with an identifier.
17
+ * If a subject with this identifier already exists, it will be replaced.
18
+ */
19
+ register(identifier: string, subject: Subject): void;
20
+ /**
21
+ * Get a subject by its identifier.
22
+ */
23
+ get(identifier: string): Subject | undefined;
24
+ /**
25
+ * Check if a subject with the given identifier exists.
26
+ */
27
+ has(identifier: string): boolean;
28
+ /**
29
+ * Delete a subject by its identifier.
30
+ * @returns true if the subject was deleted, false if not found
31
+ */
32
+ delete(identifier: string): boolean;
33
+ /**
34
+ * Get all registered identifiers.
35
+ */
36
+ identifiers(): string[];
37
+ /**
38
+ * Get the number of registered subjects.
39
+ */
40
+ get size(): number;
41
+ /**
42
+ * Clear all registered subjects.
43
+ */
44
+ clear(): void;
45
+ /**
46
+ * Iterate over all subjects.
47
+ */
48
+ entries(): IterableIterator<[string, Subject]>;
49
+ /**
50
+ * Find all subject identifiers that have access to a specific path with given flags.
51
+ * @param pathPattern The path pattern to check (supports wildcards)
52
+ * @param flags The flags to check for
53
+ * @param context Optional condition context for ABAC-style checks
54
+ * @returns Array of subject identifiers that have access
55
+ */
56
+ findSubjectsWithAccess(pathPattern: string, flags: Flags, context?: ConditionContext): string[];
57
+ /**
58
+ * Serialize the registry to JSON.
59
+ */
60
+ toJSON(): SubjectRegistryJSON;
61
+ /**
62
+ * Create a SubjectRegistry from JSON data.
63
+ * @param data The serialized registry data
64
+ * @param roleRegistry Optional RoleRegistry for resolving role references
65
+ */
66
+ static fromJSON(data: SubjectRegistryJSON, roleRegistry?: RoleRegistry): SubjectRegistry;
67
+ /**
68
+ * Save all subjects to a database adapter.
69
+ */
70
+ saveTo(adapter: DatabaseAdapter): Promise<void>;
71
+ /**
72
+ * Load all subjects from a database adapter.
73
+ * Note: This requires the adapter to support getAllSubjectIdentifiers().
74
+ * For adapters that don't support this, use loadFromIdentifiers() instead.
75
+ */
76
+ static loadFrom(adapter: DatabaseAdapter, identifiers: string[]): Promise<SubjectRegistry>;
77
+ }
@@ -0,0 +1,123 @@
1
+ import { Flags } from './constants';
2
+ import { Subject } from './subject';
3
+ /**
4
+ * In-memory registry for managing Subject instances.
5
+ * Provides an API similar to RoleRegistry for consistency.
6
+ */
7
+ export class SubjectRegistry {
8
+ constructor() {
9
+ this.subjects = new Map();
10
+ }
11
+ /**
12
+ * Register a subject with an identifier.
13
+ * If a subject with this identifier already exists, it will be replaced.
14
+ */
15
+ register(identifier, subject) {
16
+ this.subjects.set(identifier, subject);
17
+ }
18
+ /**
19
+ * Get a subject by its identifier.
20
+ */
21
+ get(identifier) {
22
+ return this.subjects.get(identifier);
23
+ }
24
+ /**
25
+ * Check if a subject with the given identifier exists.
26
+ */
27
+ has(identifier) {
28
+ return this.subjects.has(identifier);
29
+ }
30
+ /**
31
+ * Delete a subject by its identifier.
32
+ * @returns true if the subject was deleted, false if not found
33
+ */
34
+ delete(identifier) {
35
+ return this.subjects.delete(identifier);
36
+ }
37
+ /**
38
+ * Get all registered identifiers.
39
+ */
40
+ identifiers() {
41
+ return Array.from(this.subjects.keys());
42
+ }
43
+ /**
44
+ * Get the number of registered subjects.
45
+ */
46
+ get size() {
47
+ return this.subjects.size;
48
+ }
49
+ /**
50
+ * Clear all registered subjects.
51
+ */
52
+ clear() {
53
+ this.subjects.clear();
54
+ }
55
+ /**
56
+ * Iterate over all subjects.
57
+ */
58
+ entries() {
59
+ return this.subjects.entries();
60
+ }
61
+ /**
62
+ * Find all subject identifiers that have access to a specific path with given flags.
63
+ * @param pathPattern The path pattern to check (supports wildcards)
64
+ * @param flags The flags to check for
65
+ * @param context Optional condition context for ABAC-style checks
66
+ * @returns Array of subject identifiers that have access
67
+ */
68
+ findSubjectsWithAccess(pathPattern, flags, context) {
69
+ const matching = [];
70
+ for (const [identifier, subject] of this.subjects) {
71
+ if (subject.has(pathPattern, flags, context)) {
72
+ matching.push(identifier);
73
+ }
74
+ }
75
+ return matching;
76
+ }
77
+ /**
78
+ * Serialize the registry to JSON.
79
+ */
80
+ toJSON() {
81
+ const result = {};
82
+ for (const [identifier, subject] of this.subjects) {
83
+ result[identifier] = subject.toJSON();
84
+ }
85
+ return result;
86
+ }
87
+ /**
88
+ * Create a SubjectRegistry from JSON data.
89
+ * @param data The serialized registry data
90
+ * @param roleRegistry Optional RoleRegistry for resolving role references
91
+ */
92
+ static fromJSON(data, roleRegistry) {
93
+ const registry = new SubjectRegistry();
94
+ for (const [identifier, subjectData] of Object.entries(data)) {
95
+ const subject = Subject.fromJSON(subjectData, roleRegistry);
96
+ registry.register(identifier, subject);
97
+ }
98
+ return registry;
99
+ }
100
+ /**
101
+ * Save all subjects to a database adapter.
102
+ */
103
+ async saveTo(adapter) {
104
+ for (const [identifier, subject] of this.subjects) {
105
+ await adapter.saveSubject(identifier, subject);
106
+ }
107
+ }
108
+ /**
109
+ * Load all subjects from a database adapter.
110
+ * Note: This requires the adapter to support getAllSubjectIdentifiers().
111
+ * For adapters that don't support this, use loadFromIdentifiers() instead.
112
+ */
113
+ static async loadFrom(adapter, identifiers) {
114
+ const registry = new SubjectRegistry();
115
+ for (const id of identifiers) {
116
+ const subject = await adapter.loadSubject(id);
117
+ if (subject) {
118
+ registry.register(id, subject);
119
+ }
120
+ }
121
+ return registry;
122
+ }
123
+ }
package/dist/subject.d.ts CHANGED
@@ -43,4 +43,8 @@ export declare class Subject {
43
43
  delete(path: string, context?: ConditionContext): boolean;
44
44
  create(path: string, context?: ConditionContext): boolean;
45
45
  execute(path: string, context?: ConditionContext): boolean;
46
+ checkMany(requests: Array<{
47
+ flags: Flags;
48
+ path: string;
49
+ }>, context?: ConditionContext): boolean[];
46
50
  }
package/dist/subject.js CHANGED
@@ -110,4 +110,7 @@ export class Subject {
110
110
  execute(path, context) {
111
111
  return this.has(path, Flags.EXECUTE, context);
112
112
  }
113
+ checkMany(requests, context) {
114
+ return requests.map(req => this.has(req.path, req.flags, context));
115
+ }
113
116
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "odgn-rights",
3
- "version": "0.2.0",
3
+ "version": "0.5.0",
4
4
  "description": "Tiny TypeScript library for expressing and evaluating hierarchical rights with simple glob patterns.",
5
5
  "keywords": [
6
6
  "rights",
@@ -28,6 +28,26 @@
28
28
  "./cli": {
29
29
  "types": "./dist/cli/index.d.ts",
30
30
  "import": "./dist/cli/index.js"
31
+ },
32
+ "./adapters": {
33
+ "types": "./dist/adapters/index.d.ts",
34
+ "import": "./dist/adapters/index.js"
35
+ },
36
+ "./adapters/sqlite": {
37
+ "types": "./dist/adapters/sqlite-adapter.d.ts",
38
+ "import": "./dist/adapters/sqlite-adapter.js"
39
+ },
40
+ "./adapters/postgres": {
41
+ "types": "./dist/adapters/postgres-adapter.d.ts",
42
+ "import": "./dist/adapters/postgres-adapter.js"
43
+ },
44
+ "./adapters/redis": {
45
+ "types": "./dist/adapters/redis-adapter.d.ts",
46
+ "import": "./dist/adapters/redis-adapter.js"
47
+ },
48
+ "./integrations/elysia": {
49
+ "types": "./dist/integrations/elysia.d.ts",
50
+ "import": "./dist/integrations/elysia.js"
31
51
  }
32
52
  },
33
53
  "files": [
@@ -55,15 +75,25 @@
55
75
  "@ianvs/prettier-plugin-sort-imports": "^4.7.0",
56
76
  "@nkzw/eslint-config": "^3.3.0",
57
77
  "@playwright/test": "^1.57.0",
78
+ "@testcontainers/postgresql": "^11.11.0",
79
+ "@testcontainers/valkey": "^11.11.0",
58
80
  "@types/bun": "latest",
59
81
  "@types/react": "^19.2.7",
60
82
  "@types/react-dom": "^19.2.3",
83
+ "elysia": "^1.4.21",
61
84
  "eslint": "^9",
62
- "knip": "^5.76.3",
63
- "prettier": "^3"
85
+ "knip": "^5.80.0",
86
+ "prettier": "^3",
87
+ "testcontainers": "^11.11.0"
64
88
  },
65
89
  "peerDependencies": {
66
- "typescript": "^5"
90
+ "typescript": "^5",
91
+ "elysia": "^1.0.0"
92
+ },
93
+ "peerDependenciesMeta": {
94
+ "elysia": {
95
+ "optional": true
96
+ }
67
97
  },
68
98
  "publishConfig": {
69
99
  "access": "public"
@@ -71,8 +101,13 @@
71
101
  "sideEffects": false,
72
102
  "dependencies": {
73
103
  "commander": "^14.0.2",
74
- "jotai": "^2.16.0",
104
+ "ioredis": "^5.9.0",
105
+ "jotai": "^2.16.1",
75
106
  "react": "^19.2.3",
76
107
  "react-dom": "^19.2.3"
77
- }
108
+ },
109
+ "trustedDependencies": [
110
+ "protobufjs",
111
+ "unrs-resolver"
112
+ ]
78
113
  }
package/dist/utils.d.ts DELETED
@@ -1,2 +0,0 @@
1
- export declare const normalizePath: (p: string) => string;
2
- export declare const lettersFromMask: (mask: number) => string;