@rbacbee-lib/postgres 0.1.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.
package/README.md ADDED
@@ -0,0 +1,10 @@
1
+ # @rbacbee-lib/postgres
2
+
3
+ Postgres adapter for `@rbacbee-lib/core`.
4
+
5
+ ```ts
6
+ import { createPostgresRbacStore } from '@rbacbee-lib/postgres';
7
+
8
+ const store = createPostgresRbacStore({ connectionString: process.env.DATABASE_URL });
9
+ await store.migrate();
10
+ ```
package/dist/index.cjs ADDED
@@ -0,0 +1,264 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ POSTGRES_RBAC_SCHEMA: () => POSTGRES_RBAC_SCHEMA,
24
+ PostgresRbacStore: () => PostgresRbacStore,
25
+ createPostgresRbacStore: () => createPostgresRbacStore
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+
29
+ // src/postgres-rbac-schema.ts
30
+ var POSTGRES_RBAC_SCHEMA = `
31
+ CREATE TABLE IF NOT EXISTS rbac_permissions (
32
+ key text PRIMARY KEY,
33
+ description text NULL,
34
+ created_at timestamptz NOT NULL DEFAULT now(),
35
+ updated_at timestamptz NOT NULL DEFAULT now(),
36
+ CONSTRAINT rbac_permissions_key_length CHECK (length(key) BETWEEN 1 AND 128),
37
+ CONSTRAINT rbac_permissions_key_safe CHECK (key ~ '^[A-Za-z0-9*][A-Za-z0-9:_.*-]{0,127}$')
38
+ );
39
+
40
+ CREATE TABLE IF NOT EXISTS rbac_roles (
41
+ id text PRIMARY KEY,
42
+ name text NOT NULL,
43
+ tenant_id text NULL,
44
+ created_at timestamptz NOT NULL DEFAULT now(),
45
+ updated_at timestamptz NOT NULL DEFAULT now(),
46
+ CONSTRAINT rbac_roles_id_length CHECK (length(id) BETWEEN 1 AND 256),
47
+ CONSTRAINT rbac_roles_name_length CHECK (length(name) BETWEEN 1 AND 256)
48
+ );
49
+
50
+ CREATE TABLE IF NOT EXISTS rbac_role_permissions (
51
+ role_id text NOT NULL REFERENCES rbac_roles(id) ON DELETE CASCADE,
52
+ permission_key text NOT NULL REFERENCES rbac_permissions(key) ON DELETE CASCADE,
53
+ created_at timestamptz NOT NULL DEFAULT now(),
54
+ PRIMARY KEY (role_id, permission_key)
55
+ );
56
+
57
+ CREATE TABLE IF NOT EXISTS rbac_user_roles (
58
+ id bigserial PRIMARY KEY,
59
+ user_id text NOT NULL,
60
+ role_id text NOT NULL REFERENCES rbac_roles(id) ON DELETE CASCADE,
61
+ tenant_id text NULL,
62
+ resource_type text NULL,
63
+ resource_id text NULL,
64
+ expires_at timestamptz NULL,
65
+ created_at timestamptz NOT NULL DEFAULT now(),
66
+ CONSTRAINT rbac_user_roles_user_id_length CHECK (length(user_id) BETWEEN 1 AND 256)
67
+ );
68
+
69
+ CREATE INDEX IF NOT EXISTS idx_rbac_user_roles_user_tenant
70
+ ON rbac_user_roles (user_id, tenant_id);
71
+
72
+ CREATE INDEX IF NOT EXISTS idx_rbac_user_roles_role
73
+ ON rbac_user_roles (role_id);
74
+
75
+ CREATE INDEX IF NOT EXISTS idx_rbac_user_roles_resource
76
+ ON rbac_user_roles (resource_type, resource_id);
77
+
78
+ CREATE INDEX IF NOT EXISTS idx_rbac_role_permissions_permission
79
+ ON rbac_role_permissions (permission_key);
80
+ `;
81
+
82
+ // src/postgres-rbac-store.ts
83
+ var import_pg = require("pg");
84
+ var PostgresRbacStore = class {
85
+ constructor(pool, ownsPool = false) {
86
+ this.pool = pool;
87
+ this.ownsPool = ownsPool;
88
+ }
89
+ pool;
90
+ ownsPool;
91
+ async migrate() {
92
+ await this.pool.query(POSTGRES_RBAC_SCHEMA);
93
+ }
94
+ async close() {
95
+ if (this.ownsPool) {
96
+ await this.pool.end();
97
+ }
98
+ }
99
+ async getAccessProfile(query) {
100
+ const tenantId = query.tenantId ?? null;
101
+ const [rolesResult, permissionsResult] = await Promise.all([
102
+ this.pool.query(
103
+ `
104
+ SELECT r.id, r.name, ur.tenant_id, ur.resource_type, ur.resource_id, ur.expires_at
105
+ FROM rbac_user_roles ur
106
+ INNER JOIN rbac_roles r ON r.id = ur.role_id
107
+ WHERE ur.user_id = $1
108
+ AND ($2::text IS NULL OR ur.tenant_id IS NULL OR ur.tenant_id = $2)
109
+ AND (ur.expires_at IS NULL OR ur.expires_at > now())
110
+ `,
111
+ [query.userId, tenantId]
112
+ ),
113
+ this.pool.query(
114
+ `
115
+ SELECT p.key, ur.role_id, ur.tenant_id, ur.resource_type, ur.resource_id, ur.expires_at
116
+ FROM rbac_user_roles ur
117
+ INNER JOIN rbac_role_permissions rp ON rp.role_id = ur.role_id
118
+ INNER JOIN rbac_permissions p ON p.key = rp.permission_key
119
+ WHERE ur.user_id = $1
120
+ AND ($2::text IS NULL OR ur.tenant_id IS NULL OR ur.tenant_id = $2)
121
+ AND (ur.expires_at IS NULL OR ur.expires_at > now())
122
+ `,
123
+ [query.userId, tenantId]
124
+ )
125
+ ]);
126
+ if (rolesResult.rowCount === 0 && permissionsResult.rowCount === 0) {
127
+ return null;
128
+ }
129
+ const profile = {
130
+ userId: query.userId,
131
+ ...query.tenantId === void 0 ? {} : { tenantId: query.tenantId },
132
+ loadedAt: /* @__PURE__ */ new Date(),
133
+ roles: rolesResult.rows.map(mapRole),
134
+ permissions: permissionsResult.rows.map(mapPermission)
135
+ };
136
+ return profile;
137
+ }
138
+ async upsertPermission(permission) {
139
+ await this.pool.query(
140
+ `
141
+ INSERT INTO rbac_permissions (key, description)
142
+ VALUES ($1, $2)
143
+ ON CONFLICT (key) DO UPDATE
144
+ SET description = EXCLUDED.description,
145
+ updated_at = now()
146
+ `,
147
+ [permission.key, permission.description ?? null]
148
+ );
149
+ }
150
+ async deletePermission(permissionKey) {
151
+ await this.pool.query("DELETE FROM rbac_permissions WHERE key = $1", [permissionKey]);
152
+ }
153
+ async createRole(role) {
154
+ await this.pool.query(
155
+ `
156
+ INSERT INTO rbac_roles (id, name, tenant_id)
157
+ VALUES ($1, $2, $3)
158
+ ON CONFLICT (id) DO UPDATE
159
+ SET name = EXCLUDED.name,
160
+ tenant_id = EXCLUDED.tenant_id,
161
+ updated_at = now()
162
+ `,
163
+ [role.id, role.name, role.tenantId ?? null]
164
+ );
165
+ for (const permission of role.permissions ?? []) {
166
+ await this.upsertPermission({ key: permission });
167
+ await this.grantPermissionToRole(role.id, permission);
168
+ }
169
+ }
170
+ async deleteRole(roleId) {
171
+ await this.pool.query("DELETE FROM rbac_roles WHERE id = $1", [roleId]);
172
+ }
173
+ async grantPermissionToRole(roleId, permissionKey) {
174
+ await this.pool.query(
175
+ `
176
+ INSERT INTO rbac_role_permissions (role_id, permission_key)
177
+ VALUES ($1, $2)
178
+ ON CONFLICT (role_id, permission_key) DO NOTHING
179
+ `,
180
+ [roleId, permissionKey]
181
+ );
182
+ }
183
+ async revokePermissionFromRole(roleId, permissionKey) {
184
+ await this.pool.query(
185
+ "DELETE FROM rbac_role_permissions WHERE role_id = $1 AND permission_key = $2",
186
+ [roleId, permissionKey]
187
+ );
188
+ }
189
+ async assignRole(assignment) {
190
+ await this.pool.query(
191
+ `
192
+ INSERT INTO rbac_user_roles (
193
+ user_id,
194
+ role_id,
195
+ tenant_id,
196
+ resource_type,
197
+ resource_id,
198
+ expires_at
199
+ )
200
+ VALUES ($1, $2, $3, $4, $5, $6)
201
+ `,
202
+ [
203
+ assignment.userId,
204
+ assignment.roleId,
205
+ assignment.tenantId ?? null,
206
+ assignment.resourceType ?? null,
207
+ assignment.resourceId ?? null,
208
+ assignment.expiresAt ?? null
209
+ ]
210
+ );
211
+ }
212
+ async revokeRole(revocation) {
213
+ await this.pool.query(
214
+ `
215
+ DELETE FROM rbac_user_roles
216
+ WHERE user_id = $1
217
+ AND role_id = $2
218
+ AND ($3::text IS NULL OR tenant_id = $3)
219
+ AND ($4::text IS NULL OR resource_type = $4)
220
+ AND ($5::text IS NULL OR resource_id = $5)
221
+ `,
222
+ [
223
+ revocation.userId,
224
+ revocation.roleId,
225
+ revocation.tenantId ?? null,
226
+ revocation.resourceType ?? null,
227
+ revocation.resourceId ?? null
228
+ ]
229
+ );
230
+ }
231
+ };
232
+ function createPostgresRbacStore(options) {
233
+ if (options.pool !== void 0) {
234
+ return new PostgresRbacStore(options.pool);
235
+ }
236
+ return new PostgresRbacStore(new import_pg.Pool(options), true);
237
+ }
238
+ function mapRole(row) {
239
+ return {
240
+ id: row.id,
241
+ name: row.name,
242
+ ...row.tenant_id === null ? {} : { tenantId: row.tenant_id },
243
+ ...row.resource_type === null ? {} : { resourceType: row.resource_type },
244
+ ...row.resource_id === null ? {} : { resourceId: row.resource_id },
245
+ ...row.expires_at === null ? {} : { expiresAt: row.expires_at }
246
+ };
247
+ }
248
+ function mapPermission(row) {
249
+ return {
250
+ key: row.key,
251
+ sourceRoleId: row.role_id,
252
+ ...row.tenant_id === null ? {} : { tenantId: row.tenant_id },
253
+ ...row.resource_type === null ? {} : { resourceType: row.resource_type },
254
+ ...row.resource_id === null ? {} : { resourceId: row.resource_id },
255
+ ...row.expires_at === null ? {} : { expiresAt: row.expires_at }
256
+ };
257
+ }
258
+ // Annotate the CommonJS export names for ESM import in node:
259
+ 0 && (module.exports = {
260
+ POSTGRES_RBAC_SCHEMA,
261
+ PostgresRbacStore,
262
+ createPostgresRbacStore
263
+ });
264
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/postgres-rbac-schema.ts","../src/postgres-rbac-store.ts"],"sourcesContent":["export * from './postgres-rbac-schema';\nexport * from './postgres-rbac-store';\n","export const POSTGRES_RBAC_SCHEMA = `\nCREATE TABLE IF NOT EXISTS rbac_permissions (\n key text PRIMARY KEY,\n description text NULL,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT rbac_permissions_key_length CHECK (length(key) BETWEEN 1 AND 128),\n CONSTRAINT rbac_permissions_key_safe CHECK (key ~ '^[A-Za-z0-9*][A-Za-z0-9:_.*-]{0,127}$')\n);\n\nCREATE TABLE IF NOT EXISTS rbac_roles (\n id text PRIMARY KEY,\n name text NOT NULL,\n tenant_id text NULL,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT rbac_roles_id_length CHECK (length(id) BETWEEN 1 AND 256),\n CONSTRAINT rbac_roles_name_length CHECK (length(name) BETWEEN 1 AND 256)\n);\n\nCREATE TABLE IF NOT EXISTS rbac_role_permissions (\n role_id text NOT NULL REFERENCES rbac_roles(id) ON DELETE CASCADE,\n permission_key text NOT NULL REFERENCES rbac_permissions(key) ON DELETE CASCADE,\n created_at timestamptz NOT NULL DEFAULT now(),\n PRIMARY KEY (role_id, permission_key)\n);\n\nCREATE TABLE IF NOT EXISTS rbac_user_roles (\n id bigserial PRIMARY KEY,\n user_id text NOT NULL,\n role_id text NOT NULL REFERENCES rbac_roles(id) ON DELETE CASCADE,\n tenant_id text NULL,\n resource_type text NULL,\n resource_id text NULL,\n expires_at timestamptz NULL,\n created_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT rbac_user_roles_user_id_length CHECK (length(user_id) BETWEEN 1 AND 256)\n);\n\nCREATE INDEX IF NOT EXISTS idx_rbac_user_roles_user_tenant\n ON rbac_user_roles (user_id, tenant_id);\n\nCREATE INDEX IF NOT EXISTS idx_rbac_user_roles_role\n ON rbac_user_roles (role_id);\n\nCREATE INDEX IF NOT EXISTS idx_rbac_user_roles_resource\n ON rbac_user_roles (resource_type, resource_id);\n\nCREATE INDEX IF NOT EXISTS idx_rbac_role_permissions_permission\n ON rbac_role_permissions (permission_key);\n`;\n","import { Pool, type PoolConfig } from 'pg';\nimport type {\n AccessPermission,\n AccessProfile,\n AccessProfileQuery,\n AccessRepository,\n AccessRole,\n AssignmentRepository,\n PermissionDefinition,\n PermissionRepository,\n RoleAssignment,\n RoleAssignmentRevocation,\n RoleDefinition,\n RoleRepository\n} from '@rbacbee-lib/core';\nimport { POSTGRES_RBAC_SCHEMA } from './postgres-rbac-schema';\n\nexport interface PostgresRbacStoreOptions extends PoolConfig {\n readonly pool?: Pool;\n}\n\ninterface RoleRow {\n readonly id: string;\n readonly name: string;\n readonly tenant_id: string | null;\n readonly resource_type: string | null;\n readonly resource_id: string | null;\n readonly expires_at: Date | null;\n}\n\ninterface PermissionRow {\n readonly key: string;\n readonly role_id: string;\n readonly tenant_id: string | null;\n readonly resource_type: string | null;\n readonly resource_id: string | null;\n readonly expires_at: Date | null;\n}\n\nexport class PostgresRbacStore\n implements AccessRepository, PermissionRepository, RoleRepository, AssignmentRepository\n{\n private readonly ownsPool: boolean;\n\n public constructor(\n private readonly pool: Pool,\n ownsPool = false\n ) {\n this.ownsPool = ownsPool;\n }\n\n public async migrate(): Promise<void> {\n await this.pool.query(POSTGRES_RBAC_SCHEMA);\n }\n\n public async close(): Promise<void> {\n if (this.ownsPool) {\n await this.pool.end();\n }\n }\n\n public async getAccessProfile(query: AccessProfileQuery): Promise<AccessProfile | null> {\n const tenantId = query.tenantId ?? null;\n const [rolesResult, permissionsResult] = await Promise.all([\n this.pool.query<RoleRow>(\n `\n SELECT r.id, r.name, ur.tenant_id, ur.resource_type, ur.resource_id, ur.expires_at\n FROM rbac_user_roles ur\n INNER JOIN rbac_roles r ON r.id = ur.role_id\n WHERE ur.user_id = $1\n AND ($2::text IS NULL OR ur.tenant_id IS NULL OR ur.tenant_id = $2)\n AND (ur.expires_at IS NULL OR ur.expires_at > now())\n `,\n [query.userId, tenantId]\n ),\n this.pool.query<PermissionRow>(\n `\n SELECT p.key, ur.role_id, ur.tenant_id, ur.resource_type, ur.resource_id, ur.expires_at\n FROM rbac_user_roles ur\n INNER JOIN rbac_role_permissions rp ON rp.role_id = ur.role_id\n INNER JOIN rbac_permissions p ON p.key = rp.permission_key\n WHERE ur.user_id = $1\n AND ($2::text IS NULL OR ur.tenant_id IS NULL OR ur.tenant_id = $2)\n AND (ur.expires_at IS NULL OR ur.expires_at > now())\n `,\n [query.userId, tenantId]\n )\n ]);\n\n if (rolesResult.rowCount === 0 && permissionsResult.rowCount === 0) {\n return null;\n }\n\n const profile: AccessProfile = {\n userId: query.userId,\n ...(query.tenantId === undefined ? {} : { tenantId: query.tenantId }),\n loadedAt: new Date(),\n roles: rolesResult.rows.map(mapRole),\n permissions: permissionsResult.rows.map(mapPermission)\n };\n\n return profile;\n }\n\n public async upsertPermission(permission: PermissionDefinition): Promise<void> {\n await this.pool.query(\n `\n INSERT INTO rbac_permissions (key, description)\n VALUES ($1, $2)\n ON CONFLICT (key) DO UPDATE\n SET description = EXCLUDED.description,\n updated_at = now()\n `,\n [permission.key, permission.description ?? null]\n );\n }\n\n public async deletePermission(permissionKey: string): Promise<void> {\n await this.pool.query('DELETE FROM rbac_permissions WHERE key = $1', [permissionKey]);\n }\n\n public async createRole(role: RoleDefinition): Promise<void> {\n await this.pool.query(\n `\n INSERT INTO rbac_roles (id, name, tenant_id)\n VALUES ($1, $2, $3)\n ON CONFLICT (id) DO UPDATE\n SET name = EXCLUDED.name,\n tenant_id = EXCLUDED.tenant_id,\n updated_at = now()\n `,\n [role.id, role.name, role.tenantId ?? null]\n );\n\n for (const permission of role.permissions ?? []) {\n await this.upsertPermission({ key: permission });\n await this.grantPermissionToRole(role.id, permission);\n }\n }\n\n public async deleteRole(roleId: string): Promise<void> {\n await this.pool.query('DELETE FROM rbac_roles WHERE id = $1', [roleId]);\n }\n\n public async grantPermissionToRole(roleId: string, permissionKey: string): Promise<void> {\n await this.pool.query(\n `\n INSERT INTO rbac_role_permissions (role_id, permission_key)\n VALUES ($1, $2)\n ON CONFLICT (role_id, permission_key) DO NOTHING\n `,\n [roleId, permissionKey]\n );\n }\n\n public async revokePermissionFromRole(roleId: string, permissionKey: string): Promise<void> {\n await this.pool.query(\n 'DELETE FROM rbac_role_permissions WHERE role_id = $1 AND permission_key = $2',\n [roleId, permissionKey]\n );\n }\n\n public async assignRole(assignment: RoleAssignment): Promise<void> {\n await this.pool.query(\n `\n INSERT INTO rbac_user_roles (\n user_id,\n role_id,\n tenant_id,\n resource_type,\n resource_id,\n expires_at\n )\n VALUES ($1, $2, $3, $4, $5, $6)\n `,\n [\n assignment.userId,\n assignment.roleId,\n assignment.tenantId ?? null,\n assignment.resourceType ?? null,\n assignment.resourceId ?? null,\n assignment.expiresAt ?? null\n ]\n );\n }\n\n public async revokeRole(revocation: RoleAssignmentRevocation): Promise<void> {\n await this.pool.query(\n `\n DELETE FROM rbac_user_roles\n WHERE user_id = $1\n AND role_id = $2\n AND ($3::text IS NULL OR tenant_id = $3)\n AND ($4::text IS NULL OR resource_type = $4)\n AND ($5::text IS NULL OR resource_id = $5)\n `,\n [\n revocation.userId,\n revocation.roleId,\n revocation.tenantId ?? null,\n revocation.resourceType ?? null,\n revocation.resourceId ?? null\n ]\n );\n }\n}\n\nexport function createPostgresRbacStore(options: PostgresRbacStoreOptions): PostgresRbacStore {\n if (options.pool !== undefined) {\n return new PostgresRbacStore(options.pool);\n }\n\n return new PostgresRbacStore(new Pool(options), true);\n}\n\nfunction mapRole(row: RoleRow): AccessRole {\n return {\n id: row.id,\n name: row.name,\n ...(row.tenant_id === null ? {} : { tenantId: row.tenant_id }),\n ...(row.resource_type === null ? {} : { resourceType: row.resource_type }),\n ...(row.resource_id === null ? {} : { resourceId: row.resource_id }),\n ...(row.expires_at === null ? {} : { expiresAt: row.expires_at })\n };\n}\n\nfunction mapPermission(row: PermissionRow): AccessPermission {\n return {\n key: row.key,\n sourceRoleId: row.role_id,\n ...(row.tenant_id === null ? {} : { tenantId: row.tenant_id }),\n ...(row.resource_type === null ? {} : { resourceType: row.resource_type }),\n ...(row.resource_id === null ? {} : { resourceId: row.resource_id }),\n ...(row.expires_at === null ? {} : { expiresAt: row.expires_at })\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,27 @@
1
+ import { Pool, PoolConfig } from 'pg';
2
+ import { AccessRepository, PermissionRepository, RoleRepository, AssignmentRepository, AccessProfileQuery, AccessProfile, PermissionDefinition, RoleDefinition, RoleAssignment, RoleAssignmentRevocation } from '@rbacbee-lib/core';
3
+
4
+ declare const POSTGRES_RBAC_SCHEMA = "\nCREATE TABLE IF NOT EXISTS rbac_permissions (\n key text PRIMARY KEY,\n description text NULL,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT rbac_permissions_key_length CHECK (length(key) BETWEEN 1 AND 128),\n CONSTRAINT rbac_permissions_key_safe CHECK (key ~ '^[A-Za-z0-9*][A-Za-z0-9:_.*-]{0,127}$')\n);\n\nCREATE TABLE IF NOT EXISTS rbac_roles (\n id text PRIMARY KEY,\n name text NOT NULL,\n tenant_id text NULL,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT rbac_roles_id_length CHECK (length(id) BETWEEN 1 AND 256),\n CONSTRAINT rbac_roles_name_length CHECK (length(name) BETWEEN 1 AND 256)\n);\n\nCREATE TABLE IF NOT EXISTS rbac_role_permissions (\n role_id text NOT NULL REFERENCES rbac_roles(id) ON DELETE CASCADE,\n permission_key text NOT NULL REFERENCES rbac_permissions(key) ON DELETE CASCADE,\n created_at timestamptz NOT NULL DEFAULT now(),\n PRIMARY KEY (role_id, permission_key)\n);\n\nCREATE TABLE IF NOT EXISTS rbac_user_roles (\n id bigserial PRIMARY KEY,\n user_id text NOT NULL,\n role_id text NOT NULL REFERENCES rbac_roles(id) ON DELETE CASCADE,\n tenant_id text NULL,\n resource_type text NULL,\n resource_id text NULL,\n expires_at timestamptz NULL,\n created_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT rbac_user_roles_user_id_length CHECK (length(user_id) BETWEEN 1 AND 256)\n);\n\nCREATE INDEX IF NOT EXISTS idx_rbac_user_roles_user_tenant\n ON rbac_user_roles (user_id, tenant_id);\n\nCREATE INDEX IF NOT EXISTS idx_rbac_user_roles_role\n ON rbac_user_roles (role_id);\n\nCREATE INDEX IF NOT EXISTS idx_rbac_user_roles_resource\n ON rbac_user_roles (resource_type, resource_id);\n\nCREATE INDEX IF NOT EXISTS idx_rbac_role_permissions_permission\n ON rbac_role_permissions (permission_key);\n";
5
+
6
+ interface PostgresRbacStoreOptions extends PoolConfig {
7
+ readonly pool?: Pool;
8
+ }
9
+ declare class PostgresRbacStore implements AccessRepository, PermissionRepository, RoleRepository, AssignmentRepository {
10
+ private readonly pool;
11
+ private readonly ownsPool;
12
+ constructor(pool: Pool, ownsPool?: boolean);
13
+ migrate(): Promise<void>;
14
+ close(): Promise<void>;
15
+ getAccessProfile(query: AccessProfileQuery): Promise<AccessProfile | null>;
16
+ upsertPermission(permission: PermissionDefinition): Promise<void>;
17
+ deletePermission(permissionKey: string): Promise<void>;
18
+ createRole(role: RoleDefinition): Promise<void>;
19
+ deleteRole(roleId: string): Promise<void>;
20
+ grantPermissionToRole(roleId: string, permissionKey: string): Promise<void>;
21
+ revokePermissionFromRole(roleId: string, permissionKey: string): Promise<void>;
22
+ assignRole(assignment: RoleAssignment): Promise<void>;
23
+ revokeRole(revocation: RoleAssignmentRevocation): Promise<void>;
24
+ }
25
+ declare function createPostgresRbacStore(options: PostgresRbacStoreOptions): PostgresRbacStore;
26
+
27
+ export { POSTGRES_RBAC_SCHEMA, PostgresRbacStore, type PostgresRbacStoreOptions, createPostgresRbacStore };
@@ -0,0 +1,27 @@
1
+ import { Pool, PoolConfig } from 'pg';
2
+ import { AccessRepository, PermissionRepository, RoleRepository, AssignmentRepository, AccessProfileQuery, AccessProfile, PermissionDefinition, RoleDefinition, RoleAssignment, RoleAssignmentRevocation } from '@rbacbee-lib/core';
3
+
4
+ declare const POSTGRES_RBAC_SCHEMA = "\nCREATE TABLE IF NOT EXISTS rbac_permissions (\n key text PRIMARY KEY,\n description text NULL,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT rbac_permissions_key_length CHECK (length(key) BETWEEN 1 AND 128),\n CONSTRAINT rbac_permissions_key_safe CHECK (key ~ '^[A-Za-z0-9*][A-Za-z0-9:_.*-]{0,127}$')\n);\n\nCREATE TABLE IF NOT EXISTS rbac_roles (\n id text PRIMARY KEY,\n name text NOT NULL,\n tenant_id text NULL,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT rbac_roles_id_length CHECK (length(id) BETWEEN 1 AND 256),\n CONSTRAINT rbac_roles_name_length CHECK (length(name) BETWEEN 1 AND 256)\n);\n\nCREATE TABLE IF NOT EXISTS rbac_role_permissions (\n role_id text NOT NULL REFERENCES rbac_roles(id) ON DELETE CASCADE,\n permission_key text NOT NULL REFERENCES rbac_permissions(key) ON DELETE CASCADE,\n created_at timestamptz NOT NULL DEFAULT now(),\n PRIMARY KEY (role_id, permission_key)\n);\n\nCREATE TABLE IF NOT EXISTS rbac_user_roles (\n id bigserial PRIMARY KEY,\n user_id text NOT NULL,\n role_id text NOT NULL REFERENCES rbac_roles(id) ON DELETE CASCADE,\n tenant_id text NULL,\n resource_type text NULL,\n resource_id text NULL,\n expires_at timestamptz NULL,\n created_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT rbac_user_roles_user_id_length CHECK (length(user_id) BETWEEN 1 AND 256)\n);\n\nCREATE INDEX IF NOT EXISTS idx_rbac_user_roles_user_tenant\n ON rbac_user_roles (user_id, tenant_id);\n\nCREATE INDEX IF NOT EXISTS idx_rbac_user_roles_role\n ON rbac_user_roles (role_id);\n\nCREATE INDEX IF NOT EXISTS idx_rbac_user_roles_resource\n ON rbac_user_roles (resource_type, resource_id);\n\nCREATE INDEX IF NOT EXISTS idx_rbac_role_permissions_permission\n ON rbac_role_permissions (permission_key);\n";
5
+
6
+ interface PostgresRbacStoreOptions extends PoolConfig {
7
+ readonly pool?: Pool;
8
+ }
9
+ declare class PostgresRbacStore implements AccessRepository, PermissionRepository, RoleRepository, AssignmentRepository {
10
+ private readonly pool;
11
+ private readonly ownsPool;
12
+ constructor(pool: Pool, ownsPool?: boolean);
13
+ migrate(): Promise<void>;
14
+ close(): Promise<void>;
15
+ getAccessProfile(query: AccessProfileQuery): Promise<AccessProfile | null>;
16
+ upsertPermission(permission: PermissionDefinition): Promise<void>;
17
+ deletePermission(permissionKey: string): Promise<void>;
18
+ createRole(role: RoleDefinition): Promise<void>;
19
+ deleteRole(roleId: string): Promise<void>;
20
+ grantPermissionToRole(roleId: string, permissionKey: string): Promise<void>;
21
+ revokePermissionFromRole(roleId: string, permissionKey: string): Promise<void>;
22
+ assignRole(assignment: RoleAssignment): Promise<void>;
23
+ revokeRole(revocation: RoleAssignmentRevocation): Promise<void>;
24
+ }
25
+ declare function createPostgresRbacStore(options: PostgresRbacStoreOptions): PostgresRbacStore;
26
+
27
+ export { POSTGRES_RBAC_SCHEMA, PostgresRbacStore, type PostgresRbacStoreOptions, createPostgresRbacStore };
package/dist/index.js ADDED
@@ -0,0 +1,235 @@
1
+ // src/postgres-rbac-schema.ts
2
+ var POSTGRES_RBAC_SCHEMA = `
3
+ CREATE TABLE IF NOT EXISTS rbac_permissions (
4
+ key text PRIMARY KEY,
5
+ description text NULL,
6
+ created_at timestamptz NOT NULL DEFAULT now(),
7
+ updated_at timestamptz NOT NULL DEFAULT now(),
8
+ CONSTRAINT rbac_permissions_key_length CHECK (length(key) BETWEEN 1 AND 128),
9
+ CONSTRAINT rbac_permissions_key_safe CHECK (key ~ '^[A-Za-z0-9*][A-Za-z0-9:_.*-]{0,127}$')
10
+ );
11
+
12
+ CREATE TABLE IF NOT EXISTS rbac_roles (
13
+ id text PRIMARY KEY,
14
+ name text NOT NULL,
15
+ tenant_id text NULL,
16
+ created_at timestamptz NOT NULL DEFAULT now(),
17
+ updated_at timestamptz NOT NULL DEFAULT now(),
18
+ CONSTRAINT rbac_roles_id_length CHECK (length(id) BETWEEN 1 AND 256),
19
+ CONSTRAINT rbac_roles_name_length CHECK (length(name) BETWEEN 1 AND 256)
20
+ );
21
+
22
+ CREATE TABLE IF NOT EXISTS rbac_role_permissions (
23
+ role_id text NOT NULL REFERENCES rbac_roles(id) ON DELETE CASCADE,
24
+ permission_key text NOT NULL REFERENCES rbac_permissions(key) ON DELETE CASCADE,
25
+ created_at timestamptz NOT NULL DEFAULT now(),
26
+ PRIMARY KEY (role_id, permission_key)
27
+ );
28
+
29
+ CREATE TABLE IF NOT EXISTS rbac_user_roles (
30
+ id bigserial PRIMARY KEY,
31
+ user_id text NOT NULL,
32
+ role_id text NOT NULL REFERENCES rbac_roles(id) ON DELETE CASCADE,
33
+ tenant_id text NULL,
34
+ resource_type text NULL,
35
+ resource_id text NULL,
36
+ expires_at timestamptz NULL,
37
+ created_at timestamptz NOT NULL DEFAULT now(),
38
+ CONSTRAINT rbac_user_roles_user_id_length CHECK (length(user_id) BETWEEN 1 AND 256)
39
+ );
40
+
41
+ CREATE INDEX IF NOT EXISTS idx_rbac_user_roles_user_tenant
42
+ ON rbac_user_roles (user_id, tenant_id);
43
+
44
+ CREATE INDEX IF NOT EXISTS idx_rbac_user_roles_role
45
+ ON rbac_user_roles (role_id);
46
+
47
+ CREATE INDEX IF NOT EXISTS idx_rbac_user_roles_resource
48
+ ON rbac_user_roles (resource_type, resource_id);
49
+
50
+ CREATE INDEX IF NOT EXISTS idx_rbac_role_permissions_permission
51
+ ON rbac_role_permissions (permission_key);
52
+ `;
53
+
54
+ // src/postgres-rbac-store.ts
55
+ import { Pool } from "pg";
56
+ var PostgresRbacStore = class {
57
+ constructor(pool, ownsPool = false) {
58
+ this.pool = pool;
59
+ this.ownsPool = ownsPool;
60
+ }
61
+ pool;
62
+ ownsPool;
63
+ async migrate() {
64
+ await this.pool.query(POSTGRES_RBAC_SCHEMA);
65
+ }
66
+ async close() {
67
+ if (this.ownsPool) {
68
+ await this.pool.end();
69
+ }
70
+ }
71
+ async getAccessProfile(query) {
72
+ const tenantId = query.tenantId ?? null;
73
+ const [rolesResult, permissionsResult] = await Promise.all([
74
+ this.pool.query(
75
+ `
76
+ SELECT r.id, r.name, ur.tenant_id, ur.resource_type, ur.resource_id, ur.expires_at
77
+ FROM rbac_user_roles ur
78
+ INNER JOIN rbac_roles r ON r.id = ur.role_id
79
+ WHERE ur.user_id = $1
80
+ AND ($2::text IS NULL OR ur.tenant_id IS NULL OR ur.tenant_id = $2)
81
+ AND (ur.expires_at IS NULL OR ur.expires_at > now())
82
+ `,
83
+ [query.userId, tenantId]
84
+ ),
85
+ this.pool.query(
86
+ `
87
+ SELECT p.key, ur.role_id, ur.tenant_id, ur.resource_type, ur.resource_id, ur.expires_at
88
+ FROM rbac_user_roles ur
89
+ INNER JOIN rbac_role_permissions rp ON rp.role_id = ur.role_id
90
+ INNER JOIN rbac_permissions p ON p.key = rp.permission_key
91
+ WHERE ur.user_id = $1
92
+ AND ($2::text IS NULL OR ur.tenant_id IS NULL OR ur.tenant_id = $2)
93
+ AND (ur.expires_at IS NULL OR ur.expires_at > now())
94
+ `,
95
+ [query.userId, tenantId]
96
+ )
97
+ ]);
98
+ if (rolesResult.rowCount === 0 && permissionsResult.rowCount === 0) {
99
+ return null;
100
+ }
101
+ const profile = {
102
+ userId: query.userId,
103
+ ...query.tenantId === void 0 ? {} : { tenantId: query.tenantId },
104
+ loadedAt: /* @__PURE__ */ new Date(),
105
+ roles: rolesResult.rows.map(mapRole),
106
+ permissions: permissionsResult.rows.map(mapPermission)
107
+ };
108
+ return profile;
109
+ }
110
+ async upsertPermission(permission) {
111
+ await this.pool.query(
112
+ `
113
+ INSERT INTO rbac_permissions (key, description)
114
+ VALUES ($1, $2)
115
+ ON CONFLICT (key) DO UPDATE
116
+ SET description = EXCLUDED.description,
117
+ updated_at = now()
118
+ `,
119
+ [permission.key, permission.description ?? null]
120
+ );
121
+ }
122
+ async deletePermission(permissionKey) {
123
+ await this.pool.query("DELETE FROM rbac_permissions WHERE key = $1", [permissionKey]);
124
+ }
125
+ async createRole(role) {
126
+ await this.pool.query(
127
+ `
128
+ INSERT INTO rbac_roles (id, name, tenant_id)
129
+ VALUES ($1, $2, $3)
130
+ ON CONFLICT (id) DO UPDATE
131
+ SET name = EXCLUDED.name,
132
+ tenant_id = EXCLUDED.tenant_id,
133
+ updated_at = now()
134
+ `,
135
+ [role.id, role.name, role.tenantId ?? null]
136
+ );
137
+ for (const permission of role.permissions ?? []) {
138
+ await this.upsertPermission({ key: permission });
139
+ await this.grantPermissionToRole(role.id, permission);
140
+ }
141
+ }
142
+ async deleteRole(roleId) {
143
+ await this.pool.query("DELETE FROM rbac_roles WHERE id = $1", [roleId]);
144
+ }
145
+ async grantPermissionToRole(roleId, permissionKey) {
146
+ await this.pool.query(
147
+ `
148
+ INSERT INTO rbac_role_permissions (role_id, permission_key)
149
+ VALUES ($1, $2)
150
+ ON CONFLICT (role_id, permission_key) DO NOTHING
151
+ `,
152
+ [roleId, permissionKey]
153
+ );
154
+ }
155
+ async revokePermissionFromRole(roleId, permissionKey) {
156
+ await this.pool.query(
157
+ "DELETE FROM rbac_role_permissions WHERE role_id = $1 AND permission_key = $2",
158
+ [roleId, permissionKey]
159
+ );
160
+ }
161
+ async assignRole(assignment) {
162
+ await this.pool.query(
163
+ `
164
+ INSERT INTO rbac_user_roles (
165
+ user_id,
166
+ role_id,
167
+ tenant_id,
168
+ resource_type,
169
+ resource_id,
170
+ expires_at
171
+ )
172
+ VALUES ($1, $2, $3, $4, $5, $6)
173
+ `,
174
+ [
175
+ assignment.userId,
176
+ assignment.roleId,
177
+ assignment.tenantId ?? null,
178
+ assignment.resourceType ?? null,
179
+ assignment.resourceId ?? null,
180
+ assignment.expiresAt ?? null
181
+ ]
182
+ );
183
+ }
184
+ async revokeRole(revocation) {
185
+ await this.pool.query(
186
+ `
187
+ DELETE FROM rbac_user_roles
188
+ WHERE user_id = $1
189
+ AND role_id = $2
190
+ AND ($3::text IS NULL OR tenant_id = $3)
191
+ AND ($4::text IS NULL OR resource_type = $4)
192
+ AND ($5::text IS NULL OR resource_id = $5)
193
+ `,
194
+ [
195
+ revocation.userId,
196
+ revocation.roleId,
197
+ revocation.tenantId ?? null,
198
+ revocation.resourceType ?? null,
199
+ revocation.resourceId ?? null
200
+ ]
201
+ );
202
+ }
203
+ };
204
+ function createPostgresRbacStore(options) {
205
+ if (options.pool !== void 0) {
206
+ return new PostgresRbacStore(options.pool);
207
+ }
208
+ return new PostgresRbacStore(new Pool(options), true);
209
+ }
210
+ function mapRole(row) {
211
+ return {
212
+ id: row.id,
213
+ name: row.name,
214
+ ...row.tenant_id === null ? {} : { tenantId: row.tenant_id },
215
+ ...row.resource_type === null ? {} : { resourceType: row.resource_type },
216
+ ...row.resource_id === null ? {} : { resourceId: row.resource_id },
217
+ ...row.expires_at === null ? {} : { expiresAt: row.expires_at }
218
+ };
219
+ }
220
+ function mapPermission(row) {
221
+ return {
222
+ key: row.key,
223
+ sourceRoleId: row.role_id,
224
+ ...row.tenant_id === null ? {} : { tenantId: row.tenant_id },
225
+ ...row.resource_type === null ? {} : { resourceType: row.resource_type },
226
+ ...row.resource_id === null ? {} : { resourceId: row.resource_id },
227
+ ...row.expires_at === null ? {} : { expiresAt: row.expires_at }
228
+ };
229
+ }
230
+ export {
231
+ POSTGRES_RBAC_SCHEMA,
232
+ PostgresRbacStore,
233
+ createPostgresRbacStore
234
+ };
235
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/postgres-rbac-schema.ts","../src/postgres-rbac-store.ts"],"sourcesContent":["export const POSTGRES_RBAC_SCHEMA = `\nCREATE TABLE IF NOT EXISTS rbac_permissions (\n key text PRIMARY KEY,\n description text NULL,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT rbac_permissions_key_length CHECK (length(key) BETWEEN 1 AND 128),\n CONSTRAINT rbac_permissions_key_safe CHECK (key ~ '^[A-Za-z0-9*][A-Za-z0-9:_.*-]{0,127}$')\n);\n\nCREATE TABLE IF NOT EXISTS rbac_roles (\n id text PRIMARY KEY,\n name text NOT NULL,\n tenant_id text NULL,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT rbac_roles_id_length CHECK (length(id) BETWEEN 1 AND 256),\n CONSTRAINT rbac_roles_name_length CHECK (length(name) BETWEEN 1 AND 256)\n);\n\nCREATE TABLE IF NOT EXISTS rbac_role_permissions (\n role_id text NOT NULL REFERENCES rbac_roles(id) ON DELETE CASCADE,\n permission_key text NOT NULL REFERENCES rbac_permissions(key) ON DELETE CASCADE,\n created_at timestamptz NOT NULL DEFAULT now(),\n PRIMARY KEY (role_id, permission_key)\n);\n\nCREATE TABLE IF NOT EXISTS rbac_user_roles (\n id bigserial PRIMARY KEY,\n user_id text NOT NULL,\n role_id text NOT NULL REFERENCES rbac_roles(id) ON DELETE CASCADE,\n tenant_id text NULL,\n resource_type text NULL,\n resource_id text NULL,\n expires_at timestamptz NULL,\n created_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT rbac_user_roles_user_id_length CHECK (length(user_id) BETWEEN 1 AND 256)\n);\n\nCREATE INDEX IF NOT EXISTS idx_rbac_user_roles_user_tenant\n ON rbac_user_roles (user_id, tenant_id);\n\nCREATE INDEX IF NOT EXISTS idx_rbac_user_roles_role\n ON rbac_user_roles (role_id);\n\nCREATE INDEX IF NOT EXISTS idx_rbac_user_roles_resource\n ON rbac_user_roles (resource_type, resource_id);\n\nCREATE INDEX IF NOT EXISTS idx_rbac_role_permissions_permission\n ON rbac_role_permissions (permission_key);\n`;\n","import { Pool, type PoolConfig } from 'pg';\nimport type {\n AccessPermission,\n AccessProfile,\n AccessProfileQuery,\n AccessRepository,\n AccessRole,\n AssignmentRepository,\n PermissionDefinition,\n PermissionRepository,\n RoleAssignment,\n RoleAssignmentRevocation,\n RoleDefinition,\n RoleRepository\n} from '@rbacbee-lib/core';\nimport { POSTGRES_RBAC_SCHEMA } from './postgres-rbac-schema';\n\nexport interface PostgresRbacStoreOptions extends PoolConfig {\n readonly pool?: Pool;\n}\n\ninterface RoleRow {\n readonly id: string;\n readonly name: string;\n readonly tenant_id: string | null;\n readonly resource_type: string | null;\n readonly resource_id: string | null;\n readonly expires_at: Date | null;\n}\n\ninterface PermissionRow {\n readonly key: string;\n readonly role_id: string;\n readonly tenant_id: string | null;\n readonly resource_type: string | null;\n readonly resource_id: string | null;\n readonly expires_at: Date | null;\n}\n\nexport class PostgresRbacStore\n implements AccessRepository, PermissionRepository, RoleRepository, AssignmentRepository\n{\n private readonly ownsPool: boolean;\n\n public constructor(\n private readonly pool: Pool,\n ownsPool = false\n ) {\n this.ownsPool = ownsPool;\n }\n\n public async migrate(): Promise<void> {\n await this.pool.query(POSTGRES_RBAC_SCHEMA);\n }\n\n public async close(): Promise<void> {\n if (this.ownsPool) {\n await this.pool.end();\n }\n }\n\n public async getAccessProfile(query: AccessProfileQuery): Promise<AccessProfile | null> {\n const tenantId = query.tenantId ?? null;\n const [rolesResult, permissionsResult] = await Promise.all([\n this.pool.query<RoleRow>(\n `\n SELECT r.id, r.name, ur.tenant_id, ur.resource_type, ur.resource_id, ur.expires_at\n FROM rbac_user_roles ur\n INNER JOIN rbac_roles r ON r.id = ur.role_id\n WHERE ur.user_id = $1\n AND ($2::text IS NULL OR ur.tenant_id IS NULL OR ur.tenant_id = $2)\n AND (ur.expires_at IS NULL OR ur.expires_at > now())\n `,\n [query.userId, tenantId]\n ),\n this.pool.query<PermissionRow>(\n `\n SELECT p.key, ur.role_id, ur.tenant_id, ur.resource_type, ur.resource_id, ur.expires_at\n FROM rbac_user_roles ur\n INNER JOIN rbac_role_permissions rp ON rp.role_id = ur.role_id\n INNER JOIN rbac_permissions p ON p.key = rp.permission_key\n WHERE ur.user_id = $1\n AND ($2::text IS NULL OR ur.tenant_id IS NULL OR ur.tenant_id = $2)\n AND (ur.expires_at IS NULL OR ur.expires_at > now())\n `,\n [query.userId, tenantId]\n )\n ]);\n\n if (rolesResult.rowCount === 0 && permissionsResult.rowCount === 0) {\n return null;\n }\n\n const profile: AccessProfile = {\n userId: query.userId,\n ...(query.tenantId === undefined ? {} : { tenantId: query.tenantId }),\n loadedAt: new Date(),\n roles: rolesResult.rows.map(mapRole),\n permissions: permissionsResult.rows.map(mapPermission)\n };\n\n return profile;\n }\n\n public async upsertPermission(permission: PermissionDefinition): Promise<void> {\n await this.pool.query(\n `\n INSERT INTO rbac_permissions (key, description)\n VALUES ($1, $2)\n ON CONFLICT (key) DO UPDATE\n SET description = EXCLUDED.description,\n updated_at = now()\n `,\n [permission.key, permission.description ?? null]\n );\n }\n\n public async deletePermission(permissionKey: string): Promise<void> {\n await this.pool.query('DELETE FROM rbac_permissions WHERE key = $1', [permissionKey]);\n }\n\n public async createRole(role: RoleDefinition): Promise<void> {\n await this.pool.query(\n `\n INSERT INTO rbac_roles (id, name, tenant_id)\n VALUES ($1, $2, $3)\n ON CONFLICT (id) DO UPDATE\n SET name = EXCLUDED.name,\n tenant_id = EXCLUDED.tenant_id,\n updated_at = now()\n `,\n [role.id, role.name, role.tenantId ?? null]\n );\n\n for (const permission of role.permissions ?? []) {\n await this.upsertPermission({ key: permission });\n await this.grantPermissionToRole(role.id, permission);\n }\n }\n\n public async deleteRole(roleId: string): Promise<void> {\n await this.pool.query('DELETE FROM rbac_roles WHERE id = $1', [roleId]);\n }\n\n public async grantPermissionToRole(roleId: string, permissionKey: string): Promise<void> {\n await this.pool.query(\n `\n INSERT INTO rbac_role_permissions (role_id, permission_key)\n VALUES ($1, $2)\n ON CONFLICT (role_id, permission_key) DO NOTHING\n `,\n [roleId, permissionKey]\n );\n }\n\n public async revokePermissionFromRole(roleId: string, permissionKey: string): Promise<void> {\n await this.pool.query(\n 'DELETE FROM rbac_role_permissions WHERE role_id = $1 AND permission_key = $2',\n [roleId, permissionKey]\n );\n }\n\n public async assignRole(assignment: RoleAssignment): Promise<void> {\n await this.pool.query(\n `\n INSERT INTO rbac_user_roles (\n user_id,\n role_id,\n tenant_id,\n resource_type,\n resource_id,\n expires_at\n )\n VALUES ($1, $2, $3, $4, $5, $6)\n `,\n [\n assignment.userId,\n assignment.roleId,\n assignment.tenantId ?? null,\n assignment.resourceType ?? null,\n assignment.resourceId ?? null,\n assignment.expiresAt ?? null\n ]\n );\n }\n\n public async revokeRole(revocation: RoleAssignmentRevocation): Promise<void> {\n await this.pool.query(\n `\n DELETE FROM rbac_user_roles\n WHERE user_id = $1\n AND role_id = $2\n AND ($3::text IS NULL OR tenant_id = $3)\n AND ($4::text IS NULL OR resource_type = $4)\n AND ($5::text IS NULL OR resource_id = $5)\n `,\n [\n revocation.userId,\n revocation.roleId,\n revocation.tenantId ?? null,\n revocation.resourceType ?? null,\n revocation.resourceId ?? null\n ]\n );\n }\n}\n\nexport function createPostgresRbacStore(options: PostgresRbacStoreOptions): PostgresRbacStore {\n if (options.pool !== undefined) {\n return new PostgresRbacStore(options.pool);\n }\n\n return new PostgresRbacStore(new Pool(options), true);\n}\n\nfunction mapRole(row: RoleRow): AccessRole {\n return {\n id: row.id,\n name: row.name,\n ...(row.tenant_id === null ? {} : { tenantId: row.tenant_id }),\n ...(row.resource_type === null ? {} : { resourceType: row.resource_type }),\n ...(row.resource_id === null ? {} : { resourceId: row.resource_id }),\n ...(row.expires_at === null ? {} : { expiresAt: row.expires_at })\n };\n}\n\nfunction mapPermission(row: PermissionRow): AccessPermission {\n return {\n key: row.key,\n sourceRoleId: row.role_id,\n ...(row.tenant_id === null ? {} : { tenantId: row.tenant_id }),\n ...(row.resource_type === null ? {} : { resourceType: row.resource_type }),\n ...(row.resource_id === null ? {} : { resourceId: row.resource_id }),\n ...(row.expires_at === null ? {} : { expiresAt: row.expires_at })\n };\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@rbacbee-lib/postgres",
3
+ "version": "0.1.0",
4
+ "description": "Postgres storage adapter for @rbacbee-lib/core.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "main": "./dist/index.cjs",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js",
15
+ "require": "./dist/index.cjs"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "README.md"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsup src/index.ts --format esm,cjs --dts --sourcemap --clean --external pg --external @rbacbee-lib/core",
24
+ "dev": "tsup src/index.ts --format esm,cjs --dts --sourcemap --watch --external pg --external @rbacbee-lib/core",
25
+ "test": "vitest run --passWithNoTests",
26
+ "test:watch": "vitest",
27
+ "lint": "eslint src --max-warnings=0",
28
+ "typecheck": "tsc --noEmit",
29
+ "clean": "rimraf dist coverage"
30
+ },
31
+ "dependencies": {
32
+ "@rbacbee-lib/core": "workspace:^",
33
+ "pg": "^8.13.1"
34
+ },
35
+ "devDependencies": {
36
+ "@types/pg": "^8.11.10"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ }
41
+ }