@forgebase/database 0.0.1

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 (219) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +470 -0
  3. package/dist/cjs/adapters/base.d.ts +20 -0
  4. package/dist/cjs/adapters/base.d.ts.map +1 -0
  5. package/dist/cjs/adapters/base.js +13 -0
  6. package/dist/cjs/adapters/base.js.map +1 -0
  7. package/dist/cjs/adapters/index.d.ts +6 -0
  8. package/dist/cjs/adapters/index.d.ts.map +1 -0
  9. package/dist/cjs/adapters/index.js +24 -0
  10. package/dist/cjs/adapters/index.js.map +1 -0
  11. package/dist/cjs/adapters/postgres.d.ts +13 -0
  12. package/dist/cjs/adapters/postgres.d.ts.map +1 -0
  13. package/dist/cjs/adapters/postgres.js +51 -0
  14. package/dist/cjs/adapters/postgres.js.map +1 -0
  15. package/dist/cjs/adapters/sqlite.d.ts +13 -0
  16. package/dist/cjs/adapters/sqlite.d.ts.map +1 -0
  17. package/dist/cjs/adapters/sqlite.js +47 -0
  18. package/dist/cjs/adapters/sqlite.js.map +1 -0
  19. package/dist/cjs/adapters/types.d.ts +8 -0
  20. package/dist/cjs/adapters/types.d.ts.map +1 -0
  21. package/dist/cjs/adapters/types.js +3 -0
  22. package/dist/cjs/adapters/types.js.map +1 -0
  23. package/dist/cjs/database.d.ts +73 -0
  24. package/dist/cjs/database.d.ts.map +1 -0
  25. package/dist/cjs/database.js +673 -0
  26. package/dist/cjs/database.js.map +1 -0
  27. package/dist/cjs/errors.d.ts +37 -0
  28. package/dist/cjs/errors.d.ts.map +1 -0
  29. package/dist/cjs/errors.js +64 -0
  30. package/dist/cjs/errors.js.map +1 -0
  31. package/dist/cjs/index.d.ts +16 -0
  32. package/dist/cjs/index.d.ts.map +1 -0
  33. package/dist/cjs/index.js +31 -0
  34. package/dist/cjs/index.js.map +1 -0
  35. package/dist/cjs/kysely-hooks.d.ts +45 -0
  36. package/dist/cjs/kysely-hooks.d.ts.map +1 -0
  37. package/dist/cjs/kysely-hooks.js +93 -0
  38. package/dist/cjs/kysely-hooks.js.map +1 -0
  39. package/dist/cjs/libsql/example.d.ts +2 -0
  40. package/dist/cjs/libsql/example.d.ts.map +1 -0
  41. package/dist/cjs/libsql/example.js +44 -0
  42. package/dist/cjs/libsql/example.js.map +1 -0
  43. package/dist/cjs/libsql/index.d.ts +36 -0
  44. package/dist/cjs/libsql/index.d.ts.map +1 -0
  45. package/dist/cjs/libsql/index.js +155 -0
  46. package/dist/cjs/libsql/index.js.map +1 -0
  47. package/dist/cjs/permissionService.d.ts +20 -0
  48. package/dist/cjs/permissionService.d.ts.map +1 -0
  49. package/dist/cjs/permissionService.js +107 -0
  50. package/dist/cjs/permissionService.js.map +1 -0
  51. package/dist/cjs/rlsFunctionRegistry.d.ts +43 -0
  52. package/dist/cjs/rlsFunctionRegistry.d.ts.map +1 -0
  53. package/dist/cjs/rlsFunctionRegistry.js +63 -0
  54. package/dist/cjs/rlsFunctionRegistry.js.map +1 -0
  55. package/dist/cjs/rlsManager.d.ts +23 -0
  56. package/dist/cjs/rlsManager.d.ts.map +1 -0
  57. package/dist/cjs/rlsManager.js +371 -0
  58. package/dist/cjs/rlsManager.js.map +1 -0
  59. package/dist/cjs/schema.d.ts +15 -0
  60. package/dist/cjs/schema.d.ts.map +1 -0
  61. package/dist/cjs/schema.js +119 -0
  62. package/dist/cjs/schema.js.map +1 -0
  63. package/dist/cjs/sdk/client.d.ts +324 -0
  64. package/dist/cjs/sdk/client.d.ts.map +1 -0
  65. package/dist/cjs/sdk/client.js +554 -0
  66. package/dist/cjs/sdk/client.js.map +1 -0
  67. package/dist/cjs/sdk/examples.d.ts +68 -0
  68. package/dist/cjs/sdk/examples.d.ts.map +1 -0
  69. package/dist/cjs/sdk/examples.js +232 -0
  70. package/dist/cjs/sdk/examples.js.map +1 -0
  71. package/dist/cjs/sdk/server.d.ts +115 -0
  72. package/dist/cjs/sdk/server.d.ts.map +1 -0
  73. package/dist/cjs/sdk/server.js +140 -0
  74. package/dist/cjs/sdk/server.js.map +1 -0
  75. package/dist/cjs/types.d.ts +217 -0
  76. package/dist/cjs/types.d.ts.map +1 -0
  77. package/dist/cjs/types.js +5 -0
  78. package/dist/cjs/types.js.map +1 -0
  79. package/dist/cjs/utils/column-utils.d.ts +8 -0
  80. package/dist/cjs/utils/column-utils.d.ts.map +1 -0
  81. package/dist/cjs/utils/column-utils.js +131 -0
  82. package/dist/cjs/utils/column-utils.js.map +1 -0
  83. package/dist/cjs/utils/db.d.ts +2 -0
  84. package/dist/cjs/utils/db.d.ts.map +1 -0
  85. package/dist/cjs/utils/db.js +6 -0
  86. package/dist/cjs/utils/db.js.map +1 -0
  87. package/dist/cjs/utils/inspector.d.ts +39 -0
  88. package/dist/cjs/utils/inspector.d.ts.map +1 -0
  89. package/dist/cjs/utils/inspector.js +164 -0
  90. package/dist/cjs/utils/inspector.js.map +1 -0
  91. package/dist/cjs/utils/permission-initializer.d.ts +15 -0
  92. package/dist/cjs/utils/permission-initializer.d.ts.map +1 -0
  93. package/dist/cjs/utils/permission-initializer.js +173 -0
  94. package/dist/cjs/utils/permission-initializer.js.map +1 -0
  95. package/dist/cjs/websocket/RealtimeAdapter.d.ts +22 -0
  96. package/dist/cjs/websocket/RealtimeAdapter.d.ts.map +1 -0
  97. package/dist/cjs/websocket/RealtimeAdapter.js +3 -0
  98. package/dist/cjs/websocket/RealtimeAdapter.js.map +1 -0
  99. package/dist/cjs/websocket/SSEManager.d.ts +40 -0
  100. package/dist/cjs/websocket/SSEManager.d.ts.map +1 -0
  101. package/dist/cjs/websocket/SSEManager.js +268 -0
  102. package/dist/cjs/websocket/SSEManager.js.map +1 -0
  103. package/dist/cjs/websocket/WebSocketManager.d.ts +28 -0
  104. package/dist/cjs/websocket/WebSocketManager.d.ts.map +1 -0
  105. package/dist/cjs/websocket/WebSocketManager.js +156 -0
  106. package/dist/cjs/websocket/WebSocketManager.js.map +1 -0
  107. package/dist/cjs/websocket/index.d.ts +4 -0
  108. package/dist/cjs/websocket/index.d.ts.map +1 -0
  109. package/dist/cjs/websocket/index.js +20 -0
  110. package/dist/cjs/websocket/index.js.map +1 -0
  111. package/dist/esm/adapters/base.d.ts +20 -0
  112. package/dist/esm/adapters/base.d.ts.map +1 -0
  113. package/dist/esm/adapters/base.js +10 -0
  114. package/dist/esm/adapters/base.js.map +1 -0
  115. package/dist/esm/adapters/index.d.ts +6 -0
  116. package/dist/esm/adapters/index.d.ts.map +1 -0
  117. package/dist/esm/adapters/index.js +19 -0
  118. package/dist/esm/adapters/index.js.map +1 -0
  119. package/dist/esm/adapters/postgres.d.ts +13 -0
  120. package/dist/esm/adapters/postgres.d.ts.map +1 -0
  121. package/dist/esm/adapters/postgres.js +47 -0
  122. package/dist/esm/adapters/postgres.js.map +1 -0
  123. package/dist/esm/adapters/sqlite.d.ts +13 -0
  124. package/dist/esm/adapters/sqlite.d.ts.map +1 -0
  125. package/dist/esm/adapters/sqlite.js +43 -0
  126. package/dist/esm/adapters/sqlite.js.map +1 -0
  127. package/dist/esm/adapters/types.d.ts +8 -0
  128. package/dist/esm/adapters/types.d.ts.map +1 -0
  129. package/dist/esm/adapters/types.js +2 -0
  130. package/dist/esm/adapters/types.js.map +1 -0
  131. package/dist/esm/database.d.ts +73 -0
  132. package/dist/esm/database.d.ts.map +1 -0
  133. package/dist/esm/database.js +668 -0
  134. package/dist/esm/database.js.map +1 -0
  135. package/dist/esm/errors.d.ts +37 -0
  136. package/dist/esm/errors.d.ts.map +1 -0
  137. package/dist/esm/errors.js +55 -0
  138. package/dist/esm/errors.js.map +1 -0
  139. package/dist/esm/index.d.ts +16 -0
  140. package/dist/esm/index.d.ts.map +1 -0
  141. package/dist/esm/index.js +15 -0
  142. package/dist/esm/index.js.map +1 -0
  143. package/dist/esm/kysely-hooks.d.ts +45 -0
  144. package/dist/esm/kysely-hooks.d.ts.map +1 -0
  145. package/dist/esm/kysely-hooks.js +86 -0
  146. package/dist/esm/kysely-hooks.js.map +1 -0
  147. package/dist/esm/libsql/example.d.ts +2 -0
  148. package/dist/esm/libsql/example.d.ts.map +1 -0
  149. package/dist/esm/libsql/example.js +42 -0
  150. package/dist/esm/libsql/example.js.map +1 -0
  151. package/dist/esm/libsql/index.d.ts +36 -0
  152. package/dist/esm/libsql/index.d.ts.map +1 -0
  153. package/dist/esm/libsql/index.js +116 -0
  154. package/dist/esm/libsql/index.js.map +1 -0
  155. package/dist/esm/permissionService.d.ts +20 -0
  156. package/dist/esm/permissionService.d.ts.map +1 -0
  157. package/dist/esm/permissionService.js +103 -0
  158. package/dist/esm/permissionService.js.map +1 -0
  159. package/dist/esm/rlsFunctionRegistry.d.ts +43 -0
  160. package/dist/esm/rlsFunctionRegistry.d.ts.map +1 -0
  161. package/dist/esm/rlsFunctionRegistry.js +60 -0
  162. package/dist/esm/rlsFunctionRegistry.js.map +1 -0
  163. package/dist/esm/rlsManager.d.ts +23 -0
  164. package/dist/esm/rlsManager.d.ts.map +1 -0
  165. package/dist/esm/rlsManager.js +366 -0
  166. package/dist/esm/rlsManager.js.map +1 -0
  167. package/dist/esm/schema.d.ts +15 -0
  168. package/dist/esm/schema.d.ts.map +1 -0
  169. package/dist/esm/schema.js +113 -0
  170. package/dist/esm/schema.js.map +1 -0
  171. package/dist/esm/sdk/client.d.ts +324 -0
  172. package/dist/esm/sdk/client.d.ts.map +1 -0
  173. package/dist/esm/sdk/client.js +550 -0
  174. package/dist/esm/sdk/client.js.map +1 -0
  175. package/dist/esm/sdk/examples.d.ts +68 -0
  176. package/dist/esm/sdk/examples.d.ts.map +1 -0
  177. package/dist/esm/sdk/examples.js +229 -0
  178. package/dist/esm/sdk/examples.js.map +1 -0
  179. package/dist/esm/sdk/server.d.ts +115 -0
  180. package/dist/esm/sdk/server.d.ts.map +1 -0
  181. package/dist/esm/sdk/server.js +136 -0
  182. package/dist/esm/sdk/server.js.map +1 -0
  183. package/dist/esm/types.d.ts +217 -0
  184. package/dist/esm/types.d.ts.map +1 -0
  185. package/dist/esm/types.js +2 -0
  186. package/dist/esm/types.js.map +1 -0
  187. package/dist/esm/utils/column-utils.d.ts +8 -0
  188. package/dist/esm/utils/column-utils.d.ts.map +1 -0
  189. package/dist/esm/utils/column-utils.js +127 -0
  190. package/dist/esm/utils/column-utils.js.map +1 -0
  191. package/dist/esm/utils/db.d.ts +2 -0
  192. package/dist/esm/utils/db.d.ts.map +1 -0
  193. package/dist/esm/utils/db.js +3 -0
  194. package/dist/esm/utils/db.js.map +1 -0
  195. package/dist/esm/utils/inspector.d.ts +39 -0
  196. package/dist/esm/utils/inspector.d.ts.map +1 -0
  197. package/dist/esm/utils/inspector.js +160 -0
  198. package/dist/esm/utils/inspector.js.map +1 -0
  199. package/dist/esm/utils/permission-initializer.d.ts +15 -0
  200. package/dist/esm/utils/permission-initializer.d.ts.map +1 -0
  201. package/dist/esm/utils/permission-initializer.js +137 -0
  202. package/dist/esm/utils/permission-initializer.js.map +1 -0
  203. package/dist/esm/websocket/RealtimeAdapter.d.ts +22 -0
  204. package/dist/esm/websocket/RealtimeAdapter.d.ts.map +1 -0
  205. package/dist/esm/websocket/RealtimeAdapter.js +2 -0
  206. package/dist/esm/websocket/RealtimeAdapter.js.map +1 -0
  207. package/dist/esm/websocket/SSEManager.d.ts +40 -0
  208. package/dist/esm/websocket/SSEManager.d.ts.map +1 -0
  209. package/dist/esm/websocket/SSEManager.js +231 -0
  210. package/dist/esm/websocket/SSEManager.js.map +1 -0
  211. package/dist/esm/websocket/WebSocketManager.d.ts +28 -0
  212. package/dist/esm/websocket/WebSocketManager.d.ts.map +1 -0
  213. package/dist/esm/websocket/WebSocketManager.js +152 -0
  214. package/dist/esm/websocket/WebSocketManager.js.map +1 -0
  215. package/dist/esm/websocket/index.d.ts +4 -0
  216. package/dist/esm/websocket/index.d.ts.map +1 -0
  217. package/dist/esm/websocket/index.js +4 -0
  218. package/dist/esm/websocket/index.js.map +1 -0
  219. package/package.json +67 -0
@@ -0,0 +1,668 @@
1
+ import { Kysely } from 'kysely';
2
+ import { PermissionService } from './permissionService';
3
+ import { enforcePermissions, evaluateFieldCheckForRow } from './rlsManager';
4
+ import { DBInspector } from './utils/inspector';
5
+ import { KyselyHooks } from './kysely-hooks';
6
+ import { AuthenticationRequiredError, ExcludedTableError, PermissionDeniedError, } from './errors';
7
+ import { FG_PERMISSION_TABLE, } from './types';
8
+ import { createColumn } from './utils/column-utils';
9
+ import { addForeignKey, dropForeignKey, modifySchema, truncateTable, } from './schema';
10
+ import { KyselyQueryHandler } from './sdk/server';
11
+ import { WebSocketManager } from './websocket/WebSocketManager';
12
+ import { SSEManager } from './websocket/SSEManager';
13
+ import { initializePermissions } from './utils/permission-initializer';
14
+ import { LibsqlDialect } from './libsql';
15
+ export class ForgeDatabase {
16
+ constructor(config = {}) {
17
+ this.config = config;
18
+ this.defaultPermissions = {
19
+ operations: {
20
+ SELECT: [
21
+ {
22
+ allow: 'private',
23
+ },
24
+ ],
25
+ INSERT: [
26
+ {
27
+ allow: 'private',
28
+ },
29
+ ],
30
+ UPDATE: [
31
+ {
32
+ allow: 'private',
33
+ },
34
+ ],
35
+ DELETE: [
36
+ {
37
+ allow: 'private',
38
+ },
39
+ ],
40
+ },
41
+ };
42
+ this.excludedTables = [FG_PERMISSION_TABLE];
43
+ /**
44
+ * Get the endpoints for the database
45
+ * @returns Endpoints for the database
46
+ */
47
+ this.endpoints = {
48
+ /**
49
+ * Work with the database schema
50
+ * @returns Schema endpoints
51
+ */
52
+ schema: {
53
+ get: async (trx) => {
54
+ return await this.dbInspector.getDatabaseSchema(this.excludedTables);
55
+ },
56
+ create: async (payload, trx) => {
57
+ const { tableName, columns } = payload;
58
+ if (!tableName) {
59
+ throw new Error('Invalid request body');
60
+ }
61
+ // Efficiently check if table exists
62
+ const hasTable = await this.dbInspector.hasTable(tableName);
63
+ if (hasTable) {
64
+ console.log('Table already exists');
65
+ throw new Error('Table already exists');
66
+ }
67
+ // Use transaction if provided, otherwise use the db instance
68
+ const builder = trx || this.hooks.getDbInstance();
69
+ // Start creating the table
70
+ // Start creating the table
71
+ let tableBuilder = builder.schema.createTable(tableName);
72
+ // Add columns using strict createColumn util
73
+ columns.forEach((col) => {
74
+ tableBuilder = createColumn(tableBuilder, col, this.hooks.getDbInstance());
75
+ });
76
+ await tableBuilder.execute();
77
+ await this.permissionService.setPermissionsForTable(tableName, this.defaultPermissions, builder);
78
+ return {
79
+ message: 'Table created successfully',
80
+ tablename: tableName,
81
+ action: 'create',
82
+ };
83
+ },
84
+ delete: async (tableName, trx) => {
85
+ if (this.excludedTables.includes(tableName)) {
86
+ throw new ExcludedTableError(tableName);
87
+ }
88
+ // Use transaction if provided, otherwise use the db instance
89
+ const builder = trx || this.hooks.getDbInstance();
90
+ await builder.schema.dropTable(tableName).ifExists().execute();
91
+ await this.permissionService.deletePermissionsForTable(tableName, builder);
92
+ return {
93
+ message: 'Table deleted successfully',
94
+ tablename: tableName,
95
+ action: 'delete',
96
+ };
97
+ },
98
+ modify: async (payload, trx) => {
99
+ if (this.excludedTables.includes(payload.tableName)) {
100
+ throw new ExcludedTableError(payload.tableName);
101
+ }
102
+ const builder = this.hooks.getDbInstance();
103
+ return await modifySchema(builder, payload, trx);
104
+ },
105
+ addForeingKey: async (payload, trx) => {
106
+ if (this.excludedTables.includes(payload.tableName)) {
107
+ throw new ExcludedTableError(payload.tableName);
108
+ }
109
+ if (this.excludedTables.includes(payload.foreignTableName)) {
110
+ throw new ExcludedTableError(payload.foreignTableName);
111
+ }
112
+ return await addForeignKey(payload, this.hooks.getDbInstance(), trx);
113
+ },
114
+ dropForeignKey: async (payload, trx) => {
115
+ if (this.excludedTables.includes(payload.tableName)) {
116
+ throw new ExcludedTableError(payload.tableName);
117
+ }
118
+ return await dropForeignKey(payload, this.hooks.getDbInstance(), trx);
119
+ },
120
+ truncateTable: async (tableName, trx) => {
121
+ if (this.excludedTables.includes(tableName)) {
122
+ throw new ExcludedTableError(tableName);
123
+ }
124
+ return await truncateTable(tableName, this.hooks.getDbInstance(), trx);
125
+ },
126
+ getTableSchema: async (tableName, trx) => {
127
+ if (this.excludedTables.includes(tableName)) {
128
+ throw new ExcludedTableError(tableName);
129
+ }
130
+ const tableInfo = await this.dbInspector.getTableInfo(tableName);
131
+ return {
132
+ name: tableName,
133
+ info: tableInfo,
134
+ };
135
+ },
136
+ getTables: async (trx) => {
137
+ let tables = await this.dbInspector.getTables();
138
+ tables = tables.filter((t) => !this.excludedTables.includes(t));
139
+ return tables;
140
+ },
141
+ getTablePermissions: async (tableName, trx) => {
142
+ if (this.excludedTables.includes(tableName)) {
143
+ throw new ExcludedTableError(tableName);
144
+ }
145
+ return await this.permissionService.getPermissionsForTable(tableName, trx);
146
+ },
147
+ getTableSchemaWithPermissions: async (tableName, trx) => {
148
+ if (this.excludedTables.includes(tableName)) {
149
+ throw new ExcludedTableError(tableName);
150
+ }
151
+ const tableInfo = await this.dbInspector.getTableInfo(tableName);
152
+ const permissions = await this.permissionService.getPermissionsForTable(tableName, trx);
153
+ return {
154
+ name: tableName,
155
+ info: tableInfo,
156
+ permissions,
157
+ };
158
+ },
159
+ },
160
+ /**
161
+ * Work with the database data
162
+ * @returns Data endpoints
163
+ */
164
+ data: {
165
+ query: async (tableName, params, user, isSystem = false, trx) => {
166
+ if (this.excludedTables.includes(tableName)) {
167
+ throw new ExcludedTableError(tableName);
168
+ }
169
+ // const queryParams = this.parseQueryParams(params);
170
+ const queryParams = params; // KyselyQueryHandler handles params object directly
171
+ if (!this.config.enforceRls || isSystem) {
172
+ return this.hooks.query(tableName, (db) => this.queryHandler
173
+ .buildQuery(queryParams, db.selectFrom(tableName))
174
+ .execute(), queryParams, trx);
175
+ }
176
+ if (!user && !isSystem && this.config.enforceRls) {
177
+ throw new AuthenticationRequiredError('Authentication required to query records');
178
+ }
179
+ const { status: initialStatus, hasFieldCheck: initialHasFieldCheck, hasCustomFunction: initialHasCustomFunction, fieldCheckRules: extractedFieldCheckRules, } = await enforcePermissions(tableName, 'SELECT', user, this.permissionService, undefined, this.hooks.getDbInstance());
180
+ if (!initialStatus &&
181
+ !initialHasFieldCheck &&
182
+ !initialHasCustomFunction) {
183
+ throw new PermissionDeniedError(`User does not have permission to query table "${tableName}"`);
184
+ }
185
+ const records = (await this.hooks.query(tableName, (db) => this.queryHandler
186
+ .buildQuery(queryParams, db.selectFrom(tableName))
187
+ .execute(), queryParams, trx));
188
+ if (initialStatus) {
189
+ // Simple rule matched — return all records
190
+ return records;
191
+ }
192
+ // Fast path: filter rows using pre-extracted fieldCheck rules
193
+ if (extractedFieldCheckRules?.length) {
194
+ const filtered = [];
195
+ for (const record of records) {
196
+ const allowed = await evaluateFieldCheckForRow(extractedFieldCheckRules, user, record, this.hooks.getDbInstance());
197
+ if (allowed) {
198
+ filtered.push(record);
199
+ }
200
+ }
201
+ if (filtered.length === 0) {
202
+ throw new PermissionDeniedError(`User does not have permission to query table "${tableName}"`);
203
+ }
204
+ return filtered;
205
+ }
206
+ // Fallback for customFunction rules
207
+ const { status, row } = await enforcePermissions(tableName, 'SELECT', user, this.permissionService, records, this.hooks.getDbInstance());
208
+ if (!status) {
209
+ throw new PermissionDeniedError(`User does not have permission to query table "${tableName}"`);
210
+ }
211
+ return row;
212
+ },
213
+ create: async (params, user, isSystem = false, trx) => {
214
+ if (this.excludedTables.includes(params.tableName)) {
215
+ throw new ExcludedTableError(params.tableName);
216
+ }
217
+ const { data, tableName } = params;
218
+ // console.log('data-db', data, tableName);
219
+ // Handle both single record and array of records
220
+ const isArray = Array.isArray(data);
221
+ const records = isArray ? data : [data];
222
+ // Validate records
223
+ if (!records.length ||
224
+ !records.every((record) => typeof record === 'object' && Object.keys(record).length > 0)) {
225
+ console.log('Invalid request body', records);
226
+ throw new Error('Invalid request body');
227
+ }
228
+ if (!this.config.enforceRls || isSystem) {
229
+ return this.hooks.mutate(tableName, 'create', async (db) => db.insertInto(tableName).values(records).returningAll().execute(), records, undefined, trx);
230
+ }
231
+ if (!user && !isSystem && this.config.enforceRls) {
232
+ throw new AuthenticationRequiredError('Authentication required to create records');
233
+ }
234
+ const { status: initialStatus, hasFieldCheck: initialHasFieldCheck, hasCustomFunction: initialHasCustomFunction, } = await enforcePermissions(tableName, 'INSERT', user, this.permissionService, undefined, this.hooks.getDbInstance());
235
+ if (!initialStatus &&
236
+ !initialHasFieldCheck &&
237
+ !initialHasCustomFunction) {
238
+ throw new PermissionDeniedError(`User does not have permission to create record in table "${tableName}"`);
239
+ }
240
+ if (initialStatus) {
241
+ // If the user has permission to create, proceed with the creation
242
+ return this.hooks.mutate(tableName, 'create', async (db) => db.insertInto(tableName).values(records).returningAll().execute(), records, undefined, trx);
243
+ }
244
+ const { status, row } = await enforcePermissions(tableName, 'INSERT', user, this.permissionService, records, this.hooks.getDbInstance());
245
+ if (!status) {
246
+ throw new Error(`User does not have permission to create record in table "${tableName}"`);
247
+ }
248
+ if (!row || (Array.isArray(row) && row.length === 0)) {
249
+ throw new PermissionDeniedError(`User does not have permission to create this record in table "${tableName}"`);
250
+ }
251
+ const result = this.hooks.mutate(tableName, 'create', async (db) => db.insertInto(tableName).values(row).returningAll().execute(), row, undefined, trx);
252
+ return result;
253
+ },
254
+ update: async (params, user, isSystem = false, trx) => {
255
+ if (this.excludedTables.includes(params.tableName)) {
256
+ throw new ExcludedTableError(params.tableName);
257
+ }
258
+ const { id, tableName, data } = params;
259
+ if (!this.config.enforceRls || isSystem) {
260
+ const result = this.hooks.mutate(tableName, 'update', async (db) => db
261
+ .updateTable(tableName)
262
+ .set(data)
263
+ .where('id', '=', id)
264
+ .returningAll()
265
+ .execute(), { id, ...data }, undefined, trx);
266
+ return result;
267
+ }
268
+ if (!user && !isSystem && this.config.enforceRls) {
269
+ throw new AuthenticationRequiredError('Authentication required to update records');
270
+ }
271
+ const { status: initialStatus, hasFieldCheck: initialHasFieldCheck, hasCustomFunction: initialHasCustomFunction, fieldCheckRules: extractedFieldCheckRules, } = await enforcePermissions(tableName, 'UPDATE', user, this.permissionService, undefined, this.hooks.getDbInstance());
272
+ if (!initialStatus &&
273
+ !initialHasFieldCheck &&
274
+ !initialHasCustomFunction) {
275
+ throw new Error(`User does not have permission to update record with id ${id}`);
276
+ }
277
+ if (initialStatus) {
278
+ // Simple rule matched — proceed directly
279
+ const result = this.hooks.mutate(tableName, 'update', async (db) => db
280
+ .updateTable(tableName)
281
+ .set(data)
282
+ .where('id', '=', id)
283
+ .returningAll()
284
+ .execute(), { id, ...data }, undefined, trx);
285
+ return result;
286
+ }
287
+ // fieldCheck/customFunction path — fetch the row then evaluate
288
+ const record = await this.hooks.query(tableName, (db) => {
289
+ return db
290
+ .selectFrom(tableName)
291
+ .where('id', '=', id)
292
+ .selectAll()
293
+ .execute();
294
+ }, { id }, trx);
295
+ // Fast path: use pre-extracted rules instead of full enforcePermissions
296
+ if (extractedFieldCheckRules?.length) {
297
+ const allowed = await evaluateFieldCheckForRow(extractedFieldCheckRules, user, record[0], this.hooks.getDbInstance());
298
+ if (!allowed) {
299
+ throw new PermissionDeniedError(`User does not have permission to update record with id ${id}`);
300
+ }
301
+ }
302
+ else {
303
+ // Fallback for customFunction rules
304
+ const { status } = await enforcePermissions(tableName, 'UPDATE', user, this.permissionService, record[0], this.hooks.getDbInstance());
305
+ if (!status) {
306
+ throw new PermissionDeniedError(`User does not have permission to update record with id ${id}`);
307
+ }
308
+ }
309
+ const result = this.hooks.mutate(tableName, 'update', async (db) => db
310
+ .updateTable(tableName)
311
+ .set(data)
312
+ .where('id', '=', id)
313
+ .returningAll()
314
+ .execute(), { id, ...data }, undefined, trx);
315
+ return result;
316
+ },
317
+ advanceUpdate: async (params, user, isSystem = false, trx) => {
318
+ if (this.excludedTables.includes(params.tableName)) {
319
+ throw new ExcludedTableError(params.tableName);
320
+ }
321
+ const { query, tableName, data } = params;
322
+ // const queryParams = this.parseQueryParams(query);
323
+ const queryParams = query;
324
+ if (!this.config.enforceRls || isSystem) {
325
+ const result = this.hooks.mutate(tableName, 'update', async (db) => this.queryHandler
326
+ .buildQuery(queryParams, db.updateTable(tableName))
327
+ .set(data)
328
+ .returningAll()
329
+ .execute(), { ...data }, undefined, trx);
330
+ return result;
331
+ }
332
+ if (!user && !isSystem && this.config.enforceRls) {
333
+ throw new AuthenticationRequiredError('Authentication required to update records');
334
+ }
335
+ const { status: initialStatus, hasFieldCheck: initialHasFieldCheck, hasCustomFunction: initialHasCustomFunction, fieldCheckRules: extractedFieldCheckRules, } = await enforcePermissions(tableName, 'UPDATE', user, this.permissionService, undefined, this.hooks.getDbInstance());
336
+ if (!initialStatus &&
337
+ !initialHasFieldCheck &&
338
+ !initialHasCustomFunction) {
339
+ throw new Error(`User does not have permission to update this records`);
340
+ }
341
+ if (initialStatus) {
342
+ // Simple rule matched — proceed directly
343
+ const result = this.hooks.mutate(tableName, 'update', async (db) => this.queryHandler
344
+ .buildQuery(queryParams, db.updateTable(tableName))
345
+ .set(data)
346
+ .returningAll()
347
+ .execute(), { ...data }, queryParams, trx);
348
+ return result;
349
+ }
350
+ // fieldCheck/customFunction path — fetch matching rows then evaluate
351
+ const records = (await this.hooks.query(tableName, (db) => this.queryHandler
352
+ .buildQuery(queryParams, db.selectFrom(tableName))
353
+ .selectAll()
354
+ .execute(), queryParams, trx));
355
+ // Fast path: use pre-extracted rules instead of full enforcePermissions
356
+ if (extractedFieldCheckRules?.length) {
357
+ for (const record of records) {
358
+ const allowed = await evaluateFieldCheckForRow(extractedFieldCheckRules, user, record, this.hooks.getDbInstance());
359
+ if (!allowed) {
360
+ throw new PermissionDeniedError(`User does not have permission to update this records`);
361
+ }
362
+ }
363
+ }
364
+ else {
365
+ // Fallback for customFunction rules
366
+ const { status } = await enforcePermissions(tableName, 'UPDATE', user, this.permissionService, records, this.hooks.getDbInstance());
367
+ if (!status) {
368
+ throw new PermissionDeniedError(`User does not have permission to update this records`);
369
+ }
370
+ }
371
+ const result = this.hooks.mutate(tableName, 'update', async (db) => this.queryHandler
372
+ .buildQuery(queryParams, db.updateTable(tableName))
373
+ .set(data)
374
+ .returningAll()
375
+ .execute(), { ...data }, queryParams, trx);
376
+ return result;
377
+ },
378
+ delete: async (params, user, isSystem = false, trx) => {
379
+ if (this.excludedTables.includes(params.tableName)) {
380
+ throw new ExcludedTableError(params.tableName);
381
+ }
382
+ const { id, tableName } = params;
383
+ if (!this.config.enforceRls || isSystem) {
384
+ return this.hooks.mutate(tableName, 'delete', async (db) => db
385
+ .deleteFrom(tableName)
386
+ .where('id', '=', id)
387
+ .returning('id')
388
+ .execute(), { id }, undefined, trx);
389
+ }
390
+ if (!user && !isSystem && this.config.enforceRls) {
391
+ throw new AuthenticationRequiredError('Authentication required to delete records');
392
+ }
393
+ const { status: initialStatus, hasFieldCheck: initialHasFieldCheck, hasCustomFunction: initialHasCustomFunction, fieldCheckRules: extractedFieldCheckRules, } = await enforcePermissions(tableName, 'DELETE', user, this.permissionService, undefined, this.hooks.getDbInstance());
394
+ if (!initialStatus &&
395
+ !initialHasFieldCheck &&
396
+ !initialHasCustomFunction) {
397
+ throw new PermissionDeniedError(`User does not have permission to delete record with id ${id}`);
398
+ }
399
+ if (initialStatus) {
400
+ // If the user has permission to delete, proceed with the deletion
401
+ return this.hooks.mutate(tableName, 'delete', async (db) => db.deleteFrom(tableName).where('id', '=', id).execute(), { id }, undefined, trx);
402
+ }
403
+ // fieldCheck/customFunction path — fetch the record then evaluate
404
+ const record = (await this.hooks.query(tableName, (db) => {
405
+ return db
406
+ .selectFrom(tableName)
407
+ .where('id', '=', id)
408
+ .selectAll()
409
+ .execute();
410
+ }, { id }, trx));
411
+ // Fast path: use pre-extracted rules instead of full enforcePermissions
412
+ if (extractedFieldCheckRules?.length) {
413
+ const allowed = await evaluateFieldCheckForRow(extractedFieldCheckRules, user, record[0], this.hooks.getDbInstance());
414
+ if (!allowed) {
415
+ throw new PermissionDeniedError(`User does not have permission to delete record with id ${id}`);
416
+ }
417
+ }
418
+ else {
419
+ // Fallback for customFunction rules
420
+ const { status } = await enforcePermissions(tableName, 'DELETE', user, this.permissionService, record, this.hooks.getDbInstance());
421
+ if (!status) {
422
+ throw new PermissionDeniedError(`User does not have permission to delete record with id ${id}`);
423
+ }
424
+ }
425
+ return this.hooks.mutate(tableName, 'delete', async (db) => db.deleteFrom(tableName).where('id', '=', id).execute(), { id }, undefined, trx);
426
+ },
427
+ advanceDelete: async (params, user, isSystem = false, trx) => {
428
+ if (this.excludedTables.includes(params.tableName)) {
429
+ throw new ExcludedTableError(params.tableName);
430
+ }
431
+ const { query, tableName } = params;
432
+ // const queryParams = this.parseQueryParams(query);
433
+ const queryParams = query;
434
+ if (!this.config.enforceRls || isSystem) {
435
+ return this.hooks.mutate(tableName, 'delete', async (db) => this.queryHandler
436
+ .buildQuery(queryParams, db.deleteFrom(tableName))
437
+ .returningAll()
438
+ .execute(), undefined, queryParams, trx);
439
+ }
440
+ if (!user && !isSystem && this.config.enforceRls) {
441
+ throw new AuthenticationRequiredError('Authentication required to delete records');
442
+ }
443
+ const { status: initialStatus, hasFieldCheck: initialHasFieldCheck, hasCustomFunction: initialHasCustomFunction, fieldCheckRules: extractedFieldCheckRules, } = await enforcePermissions(tableName, 'DELETE', user, this.permissionService, undefined, this.hooks.getDbInstance());
444
+ if (!initialStatus &&
445
+ !initialHasFieldCheck &&
446
+ !initialHasCustomFunction) {
447
+ throw new PermissionDeniedError(`User does not have permission to delete this records`);
448
+ }
449
+ if (initialStatus) {
450
+ // Simple rule matched — proceed directly
451
+ return this.hooks.mutate(tableName, 'delete', async (db) => this.queryHandler
452
+ .buildQuery(queryParams, db.deleteFrom(tableName))
453
+ .returningAll()
454
+ .execute(), undefined, queryParams, trx);
455
+ }
456
+ // fieldCheck/customFunction path — fetch matching rows then evaluate
457
+ const records = (await this.hooks.query(tableName, async (db) => this.queryHandler
458
+ .buildQuery(queryParams, db.selectFrom(tableName))
459
+ .execute(), queryParams, trx));
460
+ // Fast path: use pre-extracted rules instead of full enforcePermissions
461
+ if (extractedFieldCheckRules?.length) {
462
+ for (const record of records) {
463
+ const allowed = await evaluateFieldCheckForRow(extractedFieldCheckRules, user, record, this.hooks.getDbInstance());
464
+ if (!allowed) {
465
+ throw new PermissionDeniedError(`User does not have permission to delete this records`);
466
+ }
467
+ }
468
+ }
469
+ else {
470
+ // Fallback for customFunction rules
471
+ const { status } = await enforcePermissions(tableName, 'DELETE', user, this.permissionService, records, this.hooks.getDbInstance());
472
+ if (!status) {
473
+ throw new PermissionDeniedError(`User does not have permission to delete this records`);
474
+ }
475
+ }
476
+ return this.hooks.mutate(tableName, 'delete', async (db) => this.queryHandler
477
+ .buildQuery(queryParams, db.deleteFrom(tableName))
478
+ .returningAll()
479
+ .execute(), undefined, queryParams, trx);
480
+ },
481
+ },
482
+ /**
483
+ * Work with the database permissions
484
+ * @returns Permissions endpoints
485
+ */
486
+ permissions: {
487
+ get: async (params, trx) => {
488
+ if (this.excludedTables.includes(params.tableName)) {
489
+ throw new ExcludedTableError(params.tableName);
490
+ }
491
+ // If no transaction is provided, create one internally
492
+ if (!trx) {
493
+ return this.transaction(async (newTrx) => {
494
+ return await this.endpoints.permissions.get(params, newTrx);
495
+ });
496
+ }
497
+ const { tableName } = params;
498
+ return this.permissionService.getPermissionsForTable(tableName, trx);
499
+ },
500
+ set: async (params, trx) => {
501
+ if (this.excludedTables.includes(params.tableName)) {
502
+ throw new ExcludedTableError(params.tableName);
503
+ }
504
+ // If no transaction is provided, create one internally and manage it
505
+ if (!trx) {
506
+ return this.transaction(async (newTrx) => {
507
+ return await this.endpoints.permissions.set(params, newTrx);
508
+ });
509
+ }
510
+ const { tableName, permissions } = params;
511
+ if (!permissions) {
512
+ throw new Error('Permissions object is required');
513
+ }
514
+ return this.permissionService.setPermissionsForTable(tableName, permissions, trx);
515
+ },
516
+ },
517
+ };
518
+ if (!config.db && !config.libsql) {
519
+ throw new Error('Either database instance (db) or libsql config is required');
520
+ }
521
+ if (!config.db && config.libsql) {
522
+ this.config.db = new Kysely({
523
+ dialect: new LibsqlDialect(config.libsql),
524
+ });
525
+ }
526
+ if (config.excludedTables) {
527
+ this.excludedTables = [...this.excludedTables, ...config.excludedTables];
528
+ }
529
+ this.permissionService =
530
+ config.permissionsService || new PermissionService(config.db);
531
+ this.dbInspector = new DBInspector(config.db);
532
+ // Initialize realtime adapter if realtime is enabled
533
+ if (config.realtime) {
534
+ const adapterType = config.realtimeAdapter || 'sse';
535
+ const port = config.websocketPort || 9001;
536
+ if (adapterType === 'websocket') {
537
+ this.realtimeAdapter = new WebSocketManager(port, this.permissionService);
538
+ }
539
+ else if (adapterType === 'sse') {
540
+ this.realtimeAdapter = new SSEManager(port, this.permissionService);
541
+ }
542
+ }
543
+ this.hooks =
544
+ config.hooks || new KyselyHooks(config.db, this.realtimeAdapter);
545
+ this.queryHandler = new KyselyQueryHandler(this.hooks.getDbInstance());
546
+ // Set default permissions for all tables
547
+ if (config.defaultPermissions) {
548
+ this.defaultPermissions = config.defaultPermissions;
549
+ }
550
+ // Initialize permissions for all tables if enabled
551
+ if (config.initializePermissions) {
552
+ this.initializeTablePermissions();
553
+ }
554
+ }
555
+ getDbInspector() {
556
+ return this.dbInspector;
557
+ }
558
+ async ready() {
559
+ await this.permissionService.ready();
560
+ }
561
+ getExcludedTables() {
562
+ return this.excludedTables;
563
+ }
564
+ getDefaultPermissions() {
565
+ return this.defaultPermissions;
566
+ }
567
+ /**
568
+ * Initialize permissions for all tables in the database
569
+ * This is a non-blocking operation that runs in the background
570
+ */
571
+ initializeTablePermissions() {
572
+ const { permissionReportPath, onPermissionInitComplete } = this.config;
573
+ // Use the public method to avoid code duplication
574
+ this.initializePermissions(permissionReportPath, onPermissionInitComplete);
575
+ }
576
+ /**
577
+ * Add a table to the excluded tables list
578
+ * @param tables List of tables to exclude
579
+ */
580
+ addExcludedTables(tables) {
581
+ this.excludedTables = [...this.excludedTables, ...tables];
582
+ }
583
+ /**
584
+ * Remove a table from the excluded tables list
585
+ * @param tables List of tables to include
586
+ */
587
+ removeExcludedTables(tables) {
588
+ this.excludedTables = this.excludedTables.filter((table) => !tables.includes(table));
589
+ }
590
+ /**
591
+ * Manually trigger permission initialization for all tables
592
+ * @param reportPath Optional path to save the report file
593
+ * @param onComplete Optional callback function to call when initialization is complete
594
+ */
595
+ initializePermissions(reportPath, onComplete) {
596
+ // Use provided parameters or fall back to config values
597
+ const finalReportPath = reportPath || this.config.permissionReportPath;
598
+ const finalCallback = onComplete || this.config.onPermissionInitComplete;
599
+ // Start the initialization process
600
+ initializePermissions(this.hooks.getDbInstance(), this.permissionService, this.dbInspector, this.excludedTables, this.defaultPermissions, finalReportPath, finalCallback);
601
+ console.log('Permission initialization started in the background');
602
+ }
603
+ /**
604
+ * Get the database schema
605
+ * @returns Database schema
606
+ */
607
+ getEndpoints() {
608
+ return this.endpoints;
609
+ }
610
+ /**
611
+ * Get the knex instance
612
+ * @returns Knex instance
613
+ */
614
+ getDbInstance() {
615
+ return this.hooks.getDbInstance();
616
+ }
617
+ getHooksDb() {
618
+ return this.hooks;
619
+ }
620
+ getPermissionService() {
621
+ return this.permissionService;
622
+ }
623
+ /**
624
+ * Execute a function within a transaction
625
+ * @param callback Function to execute within the transaction
626
+ * @returns Result of the callback function
627
+ */
628
+ async transaction(callback) {
629
+ try {
630
+ return await this.hooks.getDbInstance().transaction().execute(callback);
631
+ }
632
+ catch (error) {
633
+ console.error('Transaction error:', error);
634
+ throw error;
635
+ }
636
+ }
637
+ parseQueryParams(params) {
638
+ const queryParams = {};
639
+ Object.entries(params).forEach(([key, value]) => {
640
+ if (typeof value === 'string') {
641
+ try {
642
+ queryParams[key] = JSON.parse(value);
643
+ }
644
+ catch {
645
+ if (key === 'limit' || key === 'offset') {
646
+ queryParams[key] = parseInt(value, 10) || 10;
647
+ }
648
+ else {
649
+ queryParams[key] = value;
650
+ }
651
+ }
652
+ }
653
+ else {
654
+ queryParams[key] = value;
655
+ }
656
+ });
657
+ return queryParams;
658
+ }
659
+ }
660
+ /**
661
+ * Create a new instance of the ForgeDatabase class
662
+ * @param config Configuration object for the ForgeDatabase instance
663
+ * @returns A new instance of the ForgeDatabase class
664
+ */
665
+ export const createForgeDatabase = (config) => {
666
+ return new ForgeDatabase(config);
667
+ };
668
+ //# sourceMappingURL=database.js.map