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.
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Default table prefix for all adapter tables
3
+ */
4
+ export const DEFAULT_TABLE_PREFIX = 'tbl_';
5
+ /**
6
+ * Generate table names with the given prefix
7
+ */
8
+ export const createTableNames = (prefix = DEFAULT_TABLE_PREFIX) => ({
9
+ rights: `${prefix}rights`,
10
+ roleInheritance: `${prefix}role_inheritance`,
11
+ roleRights: `${prefix}role_rights`,
12
+ roles: `${prefix}roles`,
13
+ subjectRights: `${prefix}subject_rights`,
14
+ subjectRoles: `${prefix}subject_roles`,
15
+ subjects: `${prefix}subjects`
16
+ });
17
+ /**
18
+ * Generate SQLite schema with the given table names
19
+ */
20
+ export const generateSQLiteSchema = (tables) => `
21
+ -- Rights table
22
+ CREATE TABLE IF NOT EXISTS ${tables.rights} (
23
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
24
+ path TEXT NOT NULL,
25
+ allow_mask INTEGER NOT NULL DEFAULT 0,
26
+ deny_mask INTEGER NOT NULL DEFAULT 0,
27
+ priority INTEGER NOT NULL DEFAULT 0,
28
+ description TEXT,
29
+ tags TEXT,
30
+ valid_from TEXT,
31
+ valid_until TEXT,
32
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
33
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
34
+ UNIQUE(path, allow_mask, deny_mask, priority, valid_from, valid_until)
35
+ );
36
+
37
+ -- Roles table
38
+ CREATE TABLE IF NOT EXISTS ${tables.roles} (
39
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
40
+ name TEXT NOT NULL UNIQUE,
41
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
42
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
43
+ );
44
+
45
+ -- Role-Rights junction table
46
+ CREATE TABLE IF NOT EXISTS ${tables.roleRights} (
47
+ role_id INTEGER NOT NULL,
48
+ right_id INTEGER NOT NULL,
49
+ PRIMARY KEY (role_id, right_id),
50
+ FOREIGN KEY (role_id) REFERENCES ${tables.roles}(id) ON DELETE CASCADE,
51
+ FOREIGN KEY (right_id) REFERENCES ${tables.rights}(id) ON DELETE CASCADE
52
+ );
53
+
54
+ -- Role inheritance table
55
+ CREATE TABLE IF NOT EXISTS ${tables.roleInheritance} (
56
+ child_role_id INTEGER NOT NULL,
57
+ parent_role_id INTEGER NOT NULL,
58
+ PRIMARY KEY (child_role_id, parent_role_id),
59
+ FOREIGN KEY (child_role_id) REFERENCES ${tables.roles}(id) ON DELETE CASCADE,
60
+ FOREIGN KEY (parent_role_id) REFERENCES ${tables.roles}(id) ON DELETE CASCADE
61
+ );
62
+
63
+ -- Subjects table
64
+ CREATE TABLE IF NOT EXISTS ${tables.subjects} (
65
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
66
+ identifier TEXT NOT NULL UNIQUE,
67
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
68
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
69
+ );
70
+
71
+ -- Subject-Roles junction table
72
+ CREATE TABLE IF NOT EXISTS ${tables.subjectRoles} (
73
+ subject_id INTEGER NOT NULL,
74
+ role_id INTEGER NOT NULL,
75
+ PRIMARY KEY (subject_id, role_id),
76
+ FOREIGN KEY (subject_id) REFERENCES ${tables.subjects}(id) ON DELETE CASCADE,
77
+ FOREIGN KEY (role_id) REFERENCES ${tables.roles}(id) ON DELETE CASCADE
78
+ );
79
+
80
+ -- Subject-Rights junction table (direct rights)
81
+ CREATE TABLE IF NOT EXISTS ${tables.subjectRights} (
82
+ subject_id INTEGER NOT NULL,
83
+ right_id INTEGER NOT NULL,
84
+ PRIMARY KEY (subject_id, right_id),
85
+ FOREIGN KEY (subject_id) REFERENCES ${tables.subjects}(id) ON DELETE CASCADE,
86
+ FOREIGN KEY (right_id) REFERENCES ${tables.rights}(id) ON DELETE CASCADE
87
+ );
88
+
89
+ -- Indexes for common queries
90
+ CREATE INDEX IF NOT EXISTS idx_${tables.rights}_path ON ${tables.rights}(path);
91
+ CREATE INDEX IF NOT EXISTS idx_${tables.rights}_valid_dates ON ${tables.rights}(valid_from, valid_until);
92
+ CREATE INDEX IF NOT EXISTS idx_${tables.roles}_name ON ${tables.roles}(name);
93
+ `;
94
+ /**
95
+ * Generate PostgreSQL schema with the given table names
96
+ */
97
+ export const generatePostgresSchema = (tables) => `
98
+ -- Rights table
99
+ CREATE TABLE IF NOT EXISTS ${tables.rights} (
100
+ id SERIAL PRIMARY KEY,
101
+ path TEXT NOT NULL,
102
+ allow_mask INTEGER NOT NULL DEFAULT 0,
103
+ deny_mask INTEGER NOT NULL DEFAULT 0,
104
+ priority INTEGER NOT NULL DEFAULT 0,
105
+ description TEXT,
106
+ tags TEXT,
107
+ valid_from TIMESTAMPTZ,
108
+ valid_until TIMESTAMPTZ,
109
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
110
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
111
+ UNIQUE(path, allow_mask, deny_mask, priority, valid_from, valid_until)
112
+ );
113
+
114
+ -- Roles table
115
+ CREATE TABLE IF NOT EXISTS ${tables.roles} (
116
+ id SERIAL PRIMARY KEY,
117
+ name TEXT NOT NULL UNIQUE,
118
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
119
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
120
+ );
121
+
122
+ -- Role-Rights junction table
123
+ CREATE TABLE IF NOT EXISTS ${tables.roleRights} (
124
+ role_id INTEGER NOT NULL REFERENCES ${tables.roles}(id) ON DELETE CASCADE,
125
+ right_id INTEGER NOT NULL REFERENCES ${tables.rights}(id) ON DELETE CASCADE,
126
+ PRIMARY KEY (role_id, right_id)
127
+ );
128
+
129
+ -- Role inheritance table
130
+ CREATE TABLE IF NOT EXISTS ${tables.roleInheritance} (
131
+ child_role_id INTEGER NOT NULL REFERENCES ${tables.roles}(id) ON DELETE CASCADE,
132
+ parent_role_id INTEGER NOT NULL REFERENCES ${tables.roles}(id) ON DELETE CASCADE,
133
+ PRIMARY KEY (child_role_id, parent_role_id)
134
+ );
135
+
136
+ -- Subjects table
137
+ CREATE TABLE IF NOT EXISTS ${tables.subjects} (
138
+ id SERIAL PRIMARY KEY,
139
+ identifier TEXT NOT NULL UNIQUE,
140
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
141
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
142
+ );
143
+
144
+ -- Subject-Roles junction table
145
+ CREATE TABLE IF NOT EXISTS ${tables.subjectRoles} (
146
+ subject_id INTEGER NOT NULL REFERENCES ${tables.subjects}(id) ON DELETE CASCADE,
147
+ role_id INTEGER NOT NULL REFERENCES ${tables.roles}(id) ON DELETE CASCADE,
148
+ PRIMARY KEY (subject_id, role_id)
149
+ );
150
+
151
+ -- Subject-Rights junction table (direct rights)
152
+ CREATE TABLE IF NOT EXISTS ${tables.subjectRights} (
153
+ subject_id INTEGER NOT NULL REFERENCES ${tables.subjects}(id) ON DELETE CASCADE,
154
+ right_id INTEGER NOT NULL REFERENCES ${tables.rights}(id) ON DELETE CASCADE,
155
+ PRIMARY KEY (subject_id, right_id)
156
+ );
157
+
158
+ -- Indexes for common queries
159
+ CREATE INDEX IF NOT EXISTS idx_${tables.rights}_path ON ${tables.rights}(path);
160
+ CREATE INDEX IF NOT EXISTS idx_${tables.rights}_valid_dates ON ${tables.rights}(valid_from, valid_until);
161
+ CREATE INDEX IF NOT EXISTS idx_${tables.roles}_name ON ${tables.roles}(name);
162
+ `;
163
+ /**
164
+ * Generate SQLite statements to drop all tables (useful for testing)
165
+ */
166
+ export const generateSQLiteDropSchema = (tables) => `
167
+ DROP TABLE IF EXISTS ${tables.subjectRights};
168
+ DROP TABLE IF EXISTS ${tables.subjectRoles};
169
+ DROP TABLE IF EXISTS ${tables.subjects};
170
+ DROP TABLE IF EXISTS ${tables.roleInheritance};
171
+ DROP TABLE IF EXISTS ${tables.roleRights};
172
+ DROP TABLE IF EXISTS ${tables.roles};
173
+ DROP TABLE IF EXISTS ${tables.rights};
174
+ `;
175
+ /**
176
+ * Generate PostgreSQL statements to drop all tables (useful for testing)
177
+ */
178
+ export const generatePostgresDropSchema = (tables) => `
179
+ DROP TABLE IF EXISTS ${tables.subjectRights} CASCADE;
180
+ DROP TABLE IF EXISTS ${tables.subjectRoles} CASCADE;
181
+ DROP TABLE IF EXISTS ${tables.subjects} CASCADE;
182
+ DROP TABLE IF EXISTS ${tables.roleInheritance} CASCADE;
183
+ DROP TABLE IF EXISTS ${tables.roleRights} CASCADE;
184
+ DROP TABLE IF EXISTS ${tables.roles} CASCADE;
185
+ DROP TABLE IF EXISTS ${tables.rights} CASCADE;
186
+ `;
@@ -0,0 +1,78 @@
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 { BaseAdapter } from './base-adapter';
8
+ import type { BaseAdapterOptions, DatabaseAdapter } from './types';
9
+ export type SQLiteAdapterOptions = BaseAdapterOptions & {
10
+ create?: boolean;
11
+ enableWAL?: boolean;
12
+ filename?: string;
13
+ readonly?: boolean;
14
+ strict?: boolean;
15
+ };
16
+ export declare class SQLiteAdapter extends BaseAdapter {
17
+ private db;
18
+ private readonly options;
19
+ private roleIdCache;
20
+ private transactionDepth;
21
+ private stmtInsertRight;
22
+ private stmtSelectRightById;
23
+ private stmtSelectAllRights;
24
+ private stmtDeleteRight;
25
+ private stmtInsertRole;
26
+ private stmtSelectRoleByName;
27
+ private stmtSelectAllRoles;
28
+ private stmtSelectRoleById;
29
+ private stmtDeleteRole;
30
+ private stmtInsertRoleRight;
31
+ private stmtDeleteRoleRights;
32
+ private stmtSelectRoleRights;
33
+ private stmtInsertRoleInheritance;
34
+ private stmtDeleteRoleInheritance;
35
+ private stmtDeleteChildInheritance;
36
+ private stmtSelectRoleInheritance;
37
+ private stmtInsertSubject;
38
+ private stmtSelectSubjectByIdentifier;
39
+ private stmtUpdateSubject;
40
+ private stmtDeleteSubject;
41
+ private stmtInsertSubjectRole;
42
+ private stmtDeleteSubjectRoles;
43
+ private stmtSelectSubjectRoles;
44
+ private stmtInsertSubjectRight;
45
+ private stmtDeleteSubjectRights;
46
+ private stmtSelectSubjectRights;
47
+ constructor(options?: SQLiteAdapterOptions);
48
+ connect(): Promise<void>;
49
+ prepareStatementsAfterMigration(): void;
50
+ disconnect(): Promise<void>;
51
+ migrate(): Promise<void>;
52
+ enableWAL(): Promise<void>;
53
+ saveRight(right: Right): Promise<number>;
54
+ saveRights(rights: Rights): Promise<number[]>;
55
+ loadRight(id: number): Promise<Right | null>;
56
+ loadRights(): Promise<Rights>;
57
+ loadRightsByPath(pathPattern: string): Promise<Rights>;
58
+ deleteRight(id: number): Promise<boolean>;
59
+ saveRole(role: Role): Promise<number>;
60
+ loadRole(name: string): Promise<Role | null>;
61
+ loadRoles(): Promise<Role[]>;
62
+ deleteRole(name: string): Promise<boolean>;
63
+ saveRegistry(registry: RoleRegistry): Promise<void>;
64
+ loadRegistry(): Promise<RoleRegistry>;
65
+ saveSubject(identifier: string, subject: Subject): Promise<number>;
66
+ loadSubject(identifier: string): Promise<Subject | null>;
67
+ deleteSubject(identifier: string): Promise<boolean>;
68
+ protected getAllSubjectIdentifiers(): Promise<string[]>;
69
+ /**
70
+ * Optimized findSubjectsWithAccess using batch loading with JOINs.
71
+ * Reduces N+1 queries to a constant number of queries regardless of subject count.
72
+ */
73
+ findSubjectsWithAccess(pathPattern: string, flags: Flags): Promise<string[]>;
74
+ clear(): Promise<void>;
75
+ transaction<T>(fn: (adapter: DatabaseAdapter) => Promise<T>): Promise<T>;
76
+ private prepareStatements;
77
+ private finalizeStatements;
78
+ }