@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 ForgeBase
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,470 @@
1
+ # ForgeBase Database
2
+
3
+ A flexible, powerful database abstraction layer for ForgeBase, providing database operations, schema management, row-level security (RLS), and real-time capabilities.
4
+
5
+ ## Features
6
+
7
+ - 🔄 **Multiple Database Support**: Works with SQLite, PostgreSQL, MySQL, MSSQL, and any Knex-compatible database
8
+ - 🔒 **Row-Level Security (RLS)**: Fine-grained access control at the row level
9
+ - 🔐 **Permission Management**: Role-based access control for tables and operations
10
+ - 📊 **Schema Management**: Create, modify, and delete tables and columns dynamically
11
+ - 🔍 **Query Builder**: Powerful query building with filtering, sorting, and pagination
12
+ - ⚡ **Real-time Updates**: Optional real-time database changes via WebSockets
13
+ - 🧩 **Type Safety**: Full TypeScript support with type definitions
14
+ - 🔌 **Adapter System**: Extensible adapter system for different database engines
15
+ - 🪝 **Event Hooks**: Before/After hooks for queries and mutations
16
+ - 🔎 **Database Inspection**: Retrieve complete database schema and structure
17
+ - 🔄 **Integration Options**: Use with API package, frontend SDK, REST API, or custom frameworks
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install @forgebase/database
23
+ # or
24
+ yarn add @forgebase/database
25
+ # or
26
+ pnpm add @forgebase/database
27
+ ```
28
+
29
+ ## Basic Usage
30
+
31
+ ### Initialize the Database
32
+
33
+ ```typescript
34
+ import { ForgeDatabase } from '@forgebase/database';
35
+ import knex from 'knex';
36
+
37
+ // Create a Knex instance
38
+ const knexInstance = knex({
39
+ client: 'sqlite3',
40
+ connection: {
41
+ filename: './mydb.sqlite',
42
+ },
43
+ useNullAsDefault: true,
44
+ });
45
+
46
+ // Initialize ForgeDatabase
47
+ const db = new ForgeDatabase({
48
+ db: knexInstance,
49
+ enforceRls: true, // Enable row-level security
50
+ realtime: true, // Enable real-time updates
51
+ });
52
+ ```
53
+
54
+ ### Schema Operations
55
+
56
+ ```typescript
57
+ // Create a new table
58
+ await db.endpoints.schema.create({
59
+ tableName: 'users',
60
+ columns: [
61
+ { name: 'id', type: 'increments', primary: true },
62
+ { name: 'username', type: 'string', unique: true, nullable: false },
63
+ { name: 'email', type: 'string', unique: true, nullable: false },
64
+ { name: 'password', type: 'string', nullable: false },
65
+ { name: 'role', type: 'string', defaultValue: 'user' },
66
+ { name: 'created_at', type: 'timestamp', defaultToNow: true },
67
+ ],
68
+ });
69
+
70
+ // Get database schema
71
+ const schema = await db.endpoints.schema.get();
72
+ console.log(schema);
73
+
74
+ // Modify a table
75
+ await db.endpoints.schema.modify({
76
+ tableName: 'users',
77
+ addColumns: [{ name: 'last_login', type: 'timestamp', nullable: true }],
78
+ dropColumns: ['unused_column'],
79
+ modifyColumns: [{ name: 'role', type: 'string', defaultValue: 'member' }],
80
+ });
81
+ ```
82
+
83
+ ### Data Operations
84
+
85
+ ```typescript
86
+ // Query data
87
+ const users = await db.endpoints.data.query(
88
+ 'users',
89
+ {
90
+ select: ['id', 'username', 'email', 'role'],
91
+ where: { role: 'admin' },
92
+ orderBy: [{ column: 'created_at', direction: 'desc' }],
93
+ limit: 10,
94
+ offset: 0,
95
+ },
96
+ { id: 1, role: 'admin' }, // User context for RLS
97
+ );
98
+
99
+ // Create data
100
+ const newUser = await db.endpoints.data.create(
101
+ {
102
+ tableName: 'users',
103
+ data: {
104
+ username: 'johndoe',
105
+ email: 'john@example.com',
106
+ password: 'hashedpassword',
107
+ role: 'user',
108
+ },
109
+ },
110
+ { id: 1, role: 'admin' }, // User context for RLS
111
+ );
112
+
113
+ // Update data
114
+ await db.endpoints.data.update(
115
+ {
116
+ tableName: 'users',
117
+ id: 1,
118
+ data: {
119
+ role: 'moderator',
120
+ },
121
+ },
122
+ { id: 1, role: 'admin' }, // User context for RLS
123
+ );
124
+
125
+ // Delete data
126
+ await db.endpoints.data.delete(
127
+ {
128
+ tableName: 'users',
129
+ id: 1,
130
+ },
131
+ { id: 1, role: 'admin' }, // User context for RLS
132
+ );
133
+ ```
134
+
135
+ ### Permissions Management
136
+
137
+ ```typescript
138
+ // Get permissions for a table
139
+ const permissions = await db.getPermissionService().getPermissionsForTable('users');
140
+
141
+ // Set permissions for a table
142
+ await db.setPermissions('users', {
143
+ operations: {
144
+ SELECT: [
145
+ // Allow authenticated users to see their own data
146
+ {
147
+ allow: 'auth',
148
+ fieldCheck: {
149
+ field: 'id',
150
+ operator: '===',
151
+ valueType: 'userContext',
152
+ value: 'userId',
153
+ },
154
+ },
155
+ // Allow admins and moderators to see all data
156
+ {
157
+ allow: 'role',
158
+ roles: ['admin', 'moderator'],
159
+ },
160
+ // Allow users with specific labels
161
+ {
162
+ allow: 'labels',
163
+ labels: ['user_manager'],
164
+ },
165
+ // Allow users in specific teams
166
+ {
167
+ allow: 'teams',
168
+ teams: ['support_team'],
169
+ },
170
+ ],
171
+ INSERT: [
172
+ // Only admins can create users
173
+ {
174
+ allow: 'role',
175
+ roles: ['admin'],
176
+ },
177
+ ],
178
+ UPDATE: [
179
+ // Users can update their own data
180
+ {
181
+ allow: 'auth',
182
+ fieldCheck: {
183
+ field: 'id',
184
+ operator: '===',
185
+ valueType: 'userContext',
186
+ value: 'userId',
187
+ },
188
+ },
189
+ // Admins and moderators can update any user
190
+ {
191
+ allow: 'role',
192
+ roles: ['admin', 'moderator'],
193
+ },
194
+ ],
195
+ DELETE: [
196
+ // Only admins can delete users
197
+ {
198
+ allow: 'role',
199
+ roles: ['admin'],
200
+ },
201
+ // Custom SQL condition for complex rules
202
+ {
203
+ allow: 'customSql',
204
+ customSql: `
205
+ SELECT 1 WHERE
206
+ EXISTS (SELECT 1 FROM user_managers WHERE manager_id = :userId AND user_id = users.id)
207
+ `,
208
+ },
209
+ ],
210
+ },
211
+ });
212
+ ```
213
+
214
+ ### Transactions
215
+
216
+ ForgeBase Database supports transactions to ensure data consistency and atomicity. All database operations support transactions, allowing you to perform multiple operations as a single unit of work.
217
+
218
+ There are two ways to use transactions:
219
+
220
+ #### Explicit Transactions
221
+
222
+ Pass a transaction object to each method:
223
+
224
+ ```typescript
225
+ // Execute operations in a transaction
226
+ await db.transaction(async (trx) => {
227
+ // Create a user
228
+ const user = await db.endpoints.data.create(
229
+ {
230
+ tableName: 'users',
231
+ data: { username: 'jane', email: 'jane@example.com', password: 'hashedpw' },
232
+ },
233
+ { id: 1, role: 'admin' },
234
+ false, // Not a system operation
235
+ trx, // Pass the transaction
236
+ );
237
+
238
+ // Create a profile for the user
239
+ await db.endpoints.data.create(
240
+ {
241
+ tableName: 'profiles',
242
+ data: { user_id: user.id, bio: 'New user' },
243
+ },
244
+ { id: 1, role: 'admin' },
245
+ false,
246
+ trx,
247
+ );
248
+ });
249
+ ```
250
+
251
+ #### Implicit Transactions
252
+
253
+ Many methods automatically create a transaction if one isn't provided:
254
+
255
+ ```typescript
256
+ // This will automatically create a transaction internally
257
+ const user = await db.endpoints.data.create(
258
+ {
259
+ tableName: 'users',
260
+ data: { username: 'john', email: 'john@example.com', password: 'hashedpw' },
261
+ },
262
+ { id: 1, role: 'admin' },
263
+ );
264
+ ```
265
+
266
+ ## Security Best Practices
267
+
268
+ ### Row-Level Security (RLS)
269
+
270
+ ForgeBase Database provides powerful row-level security capabilities that allow you to define fine-grained access control rules at the row level. There are several ways to implement RLS:
271
+
272
+ #### Basic RLS with Field Checks
273
+
274
+ ```typescript
275
+ // Enable RLS
276
+ const db = new ForgeDatabase({
277
+ enforceRls: true,
278
+ // ...
279
+ });
280
+
281
+ // Set row-level policies
282
+ await db.setPermissions('documents', {
283
+ operations: {
284
+ SELECT: [
285
+ {
286
+ allow: 'auth',
287
+ fieldCheck: {
288
+ field: 'owner_id',
289
+ operator: '===',
290
+ valueType: 'userContext',
291
+ value: 'userId',
292
+ },
293
+ },
294
+ ],
295
+ },
296
+ });
297
+ ```
298
+
299
+ #### Advanced RLS with Custom SQL
300
+
301
+ For complex permission rules that require database queries:
302
+
303
+ ```typescript
304
+ // Example: Limit free users to 5 CVs, but allow pro users unlimited CVs
305
+ await db.setPermissions('cvs', {
306
+ operations: {
307
+ INSERT: [
308
+ {
309
+ allow: 'customSql',
310
+ customSql: `
311
+ SELECT 1 WHERE
312
+ -- Check if user is on pro plan
313
+ EXISTS (SELECT 1 FROM subscriptions WHERE user_id = :userId AND plan_type = 'pro')
314
+ -- OR check if user is on free plan but has fewer than 5 CVs
315
+ OR (
316
+ NOT EXISTS (SELECT 1 FROM subscriptions WHERE user_id = :userId AND plan_type = 'pro')
317
+ AND (SELECT COUNT(*) FROM cvs WHERE user_id = :userId) < 5
318
+ )
319
+ `,
320
+ },
321
+ ],
322
+ },
323
+ });
324
+ ```
325
+
326
+ #### Advanced RLS with Custom Functions
327
+
328
+ For the most flexible permission rules, you can register custom JavaScript functions:
329
+
330
+ ```typescript
331
+ // Register a custom RLS function
332
+ import { rlsFunctionRegistry } from '@forgebase/database';
333
+
334
+ // Register a function that checks subscription limits
335
+ rlsFunctionRegistry.register('checkSubscriptionLimits', async (userContext, row, knex) => {
336
+ if (!knex) return false;
337
+
338
+ // Check if user is on pro plan
339
+ const proSub = await knex('subscriptions').where({ user_id: userContext.userId, plan_type: 'pro' }).first();
340
+
341
+ if (proSub) return true; // Pro users can create unlimited resources
342
+
343
+ // For free users, check resource count
344
+ const count = await knex('cvs').where({ user_id: userContext.userId }).count('id as count').first();
345
+
346
+ return count && count.count < 5; // Allow if less than 5 resources
347
+ });
348
+
349
+ // Use the registered function in permissions
350
+ await db.setPermissions('cvs', {
351
+ operations: {
352
+ INSERT: [
353
+ {
354
+ allow: 'customFunction',
355
+ customFunction: 'checkSubscriptionLimits',
356
+ },
357
+ ],
358
+ },
359
+ });
360
+ ```
361
+
362
+ ### Automatic Permission Initialization
363
+
364
+ ForgeBase Database can automatically initialize permissions for all tables in your database. This feature is useful when you want to ensure that all tables have at least basic permissions set.
365
+
366
+ #### Configuration
367
+
368
+ You can enable automatic permission initialization when creating the ForgeDatabase instance:
369
+
370
+ ```typescript
371
+ const db = new ForgeDatabase({
372
+ db: knexInstance,
373
+ // Enable automatic permission initialization
374
+ initializePermissions: true,
375
+ // Optional: Specify where to save the initialization report
376
+ permissionReportPath: './permission-report.md',
377
+ // Optional: Callback function when initialization completes
378
+ onPermissionInitComplete: (report) => {
379
+ console.log(`Initialized permissions for ${report.tablesInitialized} tables`);
380
+ },
381
+ });
382
+ ```
383
+
384
+ #### Manual Initialization
385
+
386
+ You can also manually trigger permission initialization at any time:
387
+
388
+ ```typescript
389
+ // Initialize permissions with default options from config
390
+ db.initializePermissions();
391
+
392
+ // Or specify custom options
393
+ db.initializePermissions('./custom-report-path.md', (report) => {
394
+ console.log('Permission initialization completed!');
395
+ console.log(`Tables initialized: ${report.initializedTables.join(', ')}`);
396
+ });
397
+ ```
398
+
399
+ ### Real-time Updates
400
+
401
+ When enabled, ForgeBase Database can provide real-time updates via WebSockets:
402
+
403
+ ```typescript
404
+ // Enable real-time updates when initializing
405
+ const db = new ForgeDatabase({
406
+ db: knexInstance,
407
+ realtime: true,
408
+ websocketPort: 8080, // Optional, defaults to 8080
409
+ });
410
+
411
+ // The WebSocket server will automatically broadcast changes to connected clients
412
+ ```
413
+
414
+ ## Frontend Integration
415
+
416
+ ForgeBase Database can be easily integrated with frontend applications using the `@forgebase/sdk` package:
417
+
418
+ ```typescript
419
+ // Initialize the SDK with your API URL
420
+ import { DatabaseSDK } from '@forgebase/sdk/client';
421
+
422
+ const db = new DatabaseSDK({
423
+ baseUrl: 'http://localhost:3000/api',
424
+ axiosConfig: {
425
+ withCredentials: true, // Important for auth cookies
426
+ },
427
+ });
428
+
429
+ // Query data with a fluent API
430
+ const users = await db.table('users').select('id', 'name', 'email').where('status', 'active').orderBy('name', 'asc').query();
431
+
432
+ // Create a new record
433
+ const newUser = await db.table('users').create({
434
+ name: 'John Doe',
435
+ email: 'john@example.com',
436
+ role: 'user',
437
+ });
438
+
439
+ // Update a record
440
+ await db.table('users').update(123, {
441
+ name: 'John Smith',
442
+ });
443
+
444
+ // Delete a record
445
+ await db.table('users').delete(123);
446
+
447
+ // Real-time updates
448
+ const unsubscribe = db.table('users').subscribe((event) => {
449
+ if (event.type === 'create') {
450
+ console.log('New user created:', event.record);
451
+ } else if (event.type === 'update') {
452
+ console.log('User updated:', event.record);
453
+ } else if (event.type === 'delete') {
454
+ console.log('User deleted:', event.id);
455
+ }
456
+ });
457
+
458
+ // Later, unsubscribe when no longer needed
459
+ unsubscribe();
460
+ ```
461
+
462
+ For more details on integration options, see the [Complete Integration](/database/complete-integration) guide.
463
+
464
+ ## API Reference
465
+
466
+ For detailed API documentation, please refer to the [ForgeBase Documentation](https://docs.forgebase.dev/database).
467
+
468
+ ## License
469
+
470
+ [MIT](https://github.com/The-ForgeBase/forgebase-ts/blob/main/LICENSE)
@@ -0,0 +1,20 @@
1
+ import { WindowFunction, OrderByClause } from '../sdk/server';
2
+ export interface DatabaseAdapter {
3
+ buildWindowFunction(wf: WindowFunction): string;
4
+ buildOrderByClause(clauses: OrderByClause[]): {
5
+ column: string;
6
+ order: 'asc' | 'desc';
7
+ null?: 'first' | 'last';
8
+ }[];
9
+ supportsFeature(feature: DatabaseFeature): boolean;
10
+ sanitizeIdentifier(identifier: string): string;
11
+ }
12
+ export declare enum DatabaseFeature {
13
+ WindowFunctions = "windowFunctions",
14
+ CTEs = "ctes",
15
+ RecursiveCTEs = "recursiveCTEs",
16
+ NullsOrdering = "nullsOrdering",
17
+ JsonOperations = "jsonOperations",
18
+ ArrayOperations = "arrayOperations"
19
+ }
20
+ //# sourceMappingURL=base.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../src/adapters/base.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9D,MAAM,WAAW,eAAe;IAC9B,mBAAmB,CAAC,EAAE,EAAE,cAAc,GAAG,MAAM,CAAC;IAChD,kBAAkB,CAChB,OAAO,EAAE,aAAa,EAAE,GACvB;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;KAAE,EAAE,CAAC;IACxE,eAAe,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC;IACnD,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC;CAChD;AAED,oBAAY,eAAe;IACzB,eAAe,oBAAoB;IACnC,IAAI,SAAS;IACb,aAAa,kBAAkB;IAC/B,aAAa,kBAAkB;IAC/B,cAAc,mBAAmB;IACjC,eAAe,oBAAoB;CACpC"}
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DatabaseFeature = void 0;
4
+ var DatabaseFeature;
5
+ (function (DatabaseFeature) {
6
+ DatabaseFeature["WindowFunctions"] = "windowFunctions";
7
+ DatabaseFeature["CTEs"] = "ctes";
8
+ DatabaseFeature["RecursiveCTEs"] = "recursiveCTEs";
9
+ DatabaseFeature["NullsOrdering"] = "nullsOrdering";
10
+ DatabaseFeature["JsonOperations"] = "jsonOperations";
11
+ DatabaseFeature["ArrayOperations"] = "arrayOperations";
12
+ })(DatabaseFeature || (exports.DatabaseFeature = DatabaseFeature = {}));
13
+ //# sourceMappingURL=base.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.js","sourceRoot":"","sources":["../../../src/adapters/base.ts"],"names":[],"mappings":";;;AAWA,IAAY,eAOX;AAPD,WAAY,eAAe;IACzB,sDAAmC,CAAA;IACnC,gCAAa,CAAA;IACb,kDAA+B,CAAA;IAC/B,kDAA+B,CAAA;IAC/B,oDAAiC,CAAA;IACjC,sDAAmC,CAAA;AACrC,CAAC,EAPW,eAAe,+BAAf,eAAe,QAO1B"}
@@ -0,0 +1,6 @@
1
+ import type { Kysely } from 'kysely';
2
+ import type { DatabaseAdapter } from './base';
3
+ export declare function getAdapter(db: Kysely<any>): DatabaseAdapter;
4
+ export { DatabaseFeature } from './base';
5
+ export type { DatabaseAdapter } from './base';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/adapters/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGrC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAE9C,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,eAAe,CAgB3D;AAED,OAAO,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AACzC,YAAY,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC"}
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DatabaseFeature = void 0;
4
+ exports.getAdapter = getAdapter;
5
+ const sqlite_1 = require("./sqlite");
6
+ const postgres_1 = require("./postgres");
7
+ function getAdapter(db) {
8
+ // Try to determine the adapter from the executor or provided config
9
+ // Kysely structure: db.getExecutor().adapter
10
+ // This is a heuristic based on Kysely internal class names or we can rely on passed config if we had it.
11
+ // Assuming standard Kysely adapters.
12
+ const adapterName = db.getExecutor().adapter.constructor.name;
13
+ if (adapterName.includes('Postgres')) {
14
+ return new postgres_1.PostgresAdapter();
15
+ }
16
+ else if (adapterName.includes('Sqlite') || adapterName.includes('Libsql')) {
17
+ return new sqlite_1.SQLiteAdapter();
18
+ }
19
+ // Default fallback or more checks
20
+ return new sqlite_1.SQLiteAdapter(); // Safest default for now? Or better throw?
21
+ }
22
+ var base_1 = require("./base");
23
+ Object.defineProperty(exports, "DatabaseFeature", { enumerable: true, get: function () { return base_1.DatabaseFeature; } });
24
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/adapters/index.ts"],"names":[],"mappings":";;;AAKA,gCAgBC;AApBD,qCAAyC;AACzC,yCAA6C;AAG7C,SAAgB,UAAU,CAAC,EAAe;IACxC,oEAAoE;IACpE,6CAA6C;IAE7C,yGAAyG;IACzG,qCAAqC;IACrC,MAAM,WAAW,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC;IAE9D,IAAI,WAAW,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QACrC,OAAO,IAAI,0BAAe,EAAE,CAAC;IAC/B,CAAC;SAAM,IAAI,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5E,OAAO,IAAI,sBAAa,EAAE,CAAC;IAC7B,CAAC;IAED,kCAAkC;IAClC,OAAO,IAAI,sBAAa,EAAE,CAAC,CAAC,2CAA2C;AACzE,CAAC;AAED,+BAAyC;AAAhC,uGAAA,eAAe,OAAA"}
@@ -0,0 +1,13 @@
1
+ import { type DatabaseAdapter, DatabaseFeature } from './base';
2
+ import type { WindowFunction, OrderByClause } from '../sdk/server';
3
+ export declare class PostgresAdapter implements DatabaseAdapter {
4
+ buildWindowFunction(wf: WindowFunction): string;
5
+ buildOrderByClause(clauses: OrderByClause[]): {
6
+ column: string;
7
+ order: 'asc' | 'desc';
8
+ null?: 'first' | 'last';
9
+ }[];
10
+ supportsFeature(feature: DatabaseFeature): boolean;
11
+ sanitizeIdentifier(identifier: string): string;
12
+ }
13
+ //# sourceMappingURL=postgres.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../../src/adapters/postgres.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,eAAe,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnE,qBAAa,eAAgB,YAAW,eAAe;IACrD,mBAAmB,CAAC,EAAE,EAAE,cAAc,GAAG,MAAM;IAwB/C,kBAAkB,CAChB,OAAO,EAAE,aAAa,EAAE,GACvB;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;KAAE,EAAE;IASvE,eAAe,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO;IAYlD,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;CAI/C"}
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PostgresAdapter = void 0;
4
+ const base_1 = require("./base");
5
+ class PostgresAdapter {
6
+ buildWindowFunction(wf) {
7
+ // PostgreSQL full window function support
8
+ const fnCall = wf.type === 'row_number'
9
+ ? 'ROW_NUMBER()'
10
+ : `${wf.type}(${wf.field || '*'})`;
11
+ let overClause = 'OVER (';
12
+ if (wf.partitionBy?.length) {
13
+ overClause += `PARTITION BY ${wf.partitionBy.join(',')}`;
14
+ }
15
+ if (wf.orderBy?.length) {
16
+ overClause += ` ORDER BY ${wf.orderBy
17
+ .map((ob) => `${ob.field} ${ob.direction || 'ASC'}`)
18
+ .join(',')}`;
19
+ }
20
+ if (wf.frameClause) {
21
+ overClause += ` ${wf.frameClause}`;
22
+ }
23
+ overClause += ')';
24
+ return `${fnCall} ${overClause} AS ${wf.alias}`;
25
+ }
26
+ buildOrderByClause(clauses) {
27
+ // PostgreSQL supports NULLS FIRST/LAST natively
28
+ return clauses.map(({ field, direction, nulls }) => ({
29
+ column: field,
30
+ order: direction || 'asc',
31
+ nulls: nulls,
32
+ }));
33
+ }
34
+ supportsFeature(feature) {
35
+ const supported = {
36
+ [base_1.DatabaseFeature.WindowFunctions]: true,
37
+ [base_1.DatabaseFeature.CTEs]: true,
38
+ [base_1.DatabaseFeature.RecursiveCTEs]: true,
39
+ [base_1.DatabaseFeature.NullsOrdering]: true,
40
+ [base_1.DatabaseFeature.JsonOperations]: true,
41
+ [base_1.DatabaseFeature.ArrayOperations]: true,
42
+ };
43
+ return supported[feature] || false;
44
+ }
45
+ sanitizeIdentifier(identifier) {
46
+ // PostgreSQL identifier sanitization
47
+ return `"${identifier.replace(/"/g, '""')}"`;
48
+ }
49
+ }
50
+ exports.PostgresAdapter = PostgresAdapter;
51
+ //# sourceMappingURL=postgres.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres.js","sourceRoot":"","sources":["../../../src/adapters/postgres.ts"],"names":[],"mappings":";;;AAAA,iCAA+D;AAG/D,MAAa,eAAe;IAC1B,mBAAmB,CAAC,EAAkB;QACpC,0CAA0C;QAC1C,MAAM,MAAM,GACV,EAAE,CAAC,IAAI,KAAK,YAAY;YACtB,CAAC,CAAC,cAAc;YAChB,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,KAAK,IAAI,GAAG,GAAG,CAAC;QAEvC,IAAI,UAAU,GAAG,QAAQ,CAAC;QAC1B,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC;YAC3B,UAAU,IAAI,gBAAgB,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3D,CAAC;QACD,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;YACvB,UAAU,IAAI,aAAa,EAAE,CAAC,OAAO;iBAClC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,SAAS,IAAI,KAAK,EAAE,CAAC;iBACnD,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACjB,CAAC;QACD,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YACnB,UAAU,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACrC,CAAC;QACD,UAAU,IAAI,GAAG,CAAC;QAElB,OAAO,GAAG,MAAM,IAAI,UAAU,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC;IAClD,CAAC;IAED,kBAAkB,CAChB,OAAwB;QAExB,gDAAgD;QAChD,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;YACnD,MAAM,EAAE,KAAK;YACb,KAAK,EAAE,SAAS,IAAI,KAAK;YACzB,KAAK,EAAE,KAAK;SACb,CAAC,CAAC,CAAC;IACN,CAAC;IAED,eAAe,CAAC,OAAwB;QACtC,MAAM,SAAS,GAAG;YAChB,CAAC,sBAAe,CAAC,eAAe,CAAC,EAAE,IAAI;YACvC,CAAC,sBAAe,CAAC,IAAI,CAAC,EAAE,IAAI;YAC5B,CAAC,sBAAe,CAAC,aAAa,CAAC,EAAE,IAAI;YACrC,CAAC,sBAAe,CAAC,aAAa,CAAC,EAAE,IAAI;YACrC,CAAC,sBAAe,CAAC,cAAc,CAAC,EAAE,IAAI;YACtC,CAAC,sBAAe,CAAC,eAAe,CAAC,EAAE,IAAI;SACxC,CAAC;QACF,OAAO,SAAS,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC;IACrC,CAAC;IAED,kBAAkB,CAAC,UAAkB;QACnC,qCAAqC;QACrC,OAAO,IAAI,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC;IAC/C,CAAC;CACF;AApDD,0CAoDC"}
@@ -0,0 +1,13 @@
1
+ import { type DatabaseAdapter, DatabaseFeature } from './base';
2
+ import { WindowFunction, OrderByClause } from '../sdk/server';
3
+ export declare class SQLiteAdapter implements DatabaseAdapter {
4
+ buildWindowFunction(wf: WindowFunction): string;
5
+ buildOrderByClause(clauses: OrderByClause[]): {
6
+ column: string;
7
+ order: 'asc' | 'desc';
8
+ null?: 'first' | 'last';
9
+ }[];
10
+ supportsFeature(feature: DatabaseFeature): boolean;
11
+ sanitizeIdentifier(identifier: string): string;
12
+ }
13
+ //# sourceMappingURL=sqlite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../../src/adapters/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,eAAe,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9D,qBAAa,aAAc,YAAW,eAAe;IACnD,mBAAmB,CAAC,EAAE,EAAE,cAAc,GAAG,MAAM;IAqB/C,kBAAkB,CAChB,OAAO,EAAE,aAAa,EAAE,GACvB;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;KAAE,EAAE;IAQvE,eAAe,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO;IAYlD,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;CAI/C"}