@gl-life/gl-life-database 1.0.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/API.md +1486 -0
- package/LICENSE +190 -0
- package/README.md +480 -0
- package/dist/cache/index.d.ts +4 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/invalidation.d.ts +156 -0
- package/dist/cache/invalidation.d.ts.map +1 -0
- package/dist/cache/kv-cache.d.ts +79 -0
- package/dist/cache/kv-cache.d.ts.map +1 -0
- package/dist/cache/memory-cache.d.ts +68 -0
- package/dist/cache/memory-cache.d.ts.map +1 -0
- package/dist/cloudforge/d1-adapter.d.ts +67 -0
- package/dist/cloudforge/d1-adapter.d.ts.map +1 -0
- package/dist/cloudforge/do-storage.d.ts +51 -0
- package/dist/cloudforge/do-storage.d.ts.map +1 -0
- package/dist/cloudforge/index.d.ts +4 -0
- package/dist/cloudforge/index.d.ts.map +1 -0
- package/dist/cloudforge/r2-backup.d.ts +38 -0
- package/dist/cloudforge/r2-backup.d.ts.map +1 -0
- package/dist/connection/index.d.ts +2 -0
- package/dist/connection/index.d.ts.map +1 -0
- package/dist/connection/manager.d.ts +54 -0
- package/dist/connection/manager.d.ts.map +1 -0
- package/dist/index.cjs +4762 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4701 -0
- package/dist/index.js.map +1 -0
- package/dist/migration/index.d.ts +4 -0
- package/dist/migration/index.d.ts.map +1 -0
- package/dist/migration/loader.d.ts +88 -0
- package/dist/migration/loader.d.ts.map +1 -0
- package/dist/migration/runner.d.ts +91 -0
- package/dist/migration/runner.d.ts.map +1 -0
- package/dist/migration/seeder.d.ts +95 -0
- package/dist/migration/seeder.d.ts.map +1 -0
- package/dist/query/builder.d.ts +47 -0
- package/dist/query/builder.d.ts.map +1 -0
- package/dist/query/index.d.ts +3 -0
- package/dist/query/index.d.ts.map +1 -0
- package/dist/query/raw.d.ts +92 -0
- package/dist/query/raw.d.ts.map +1 -0
- package/dist/tenant/context.d.ts +52 -0
- package/dist/tenant/context.d.ts.map +1 -0
- package/dist/tenant/index.d.ts +4 -0
- package/dist/tenant/index.d.ts.map +1 -0
- package/dist/tenant/query-wrapper.d.ts +96 -0
- package/dist/tenant/query-wrapper.d.ts.map +1 -0
- package/dist/tenant/schema-manager.d.ts +185 -0
- package/dist/tenant/schema-manager.d.ts.map +1 -0
- package/dist/transaction/index.d.ts +2 -0
- package/dist/transaction/index.d.ts.map +1 -0
- package/dist/transaction/transaction.d.ts +51 -0
- package/dist/transaction/transaction.d.ts.map +1 -0
- package/dist/types/cache.d.ts +214 -0
- package/dist/types/cache.d.ts.map +1 -0
- package/dist/types/cloudforge.d.ts +753 -0
- package/dist/types/cloudforge.d.ts.map +1 -0
- package/dist/types/connection.d.ts +91 -0
- package/dist/types/connection.d.ts.map +1 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/migration.d.ts +225 -0
- package/dist/types/migration.d.ts.map +1 -0
- package/dist/types/plugin.d.ts +432 -0
- package/dist/types/plugin.d.ts.map +1 -0
- package/dist/types/query-builder.d.ts +217 -0
- package/dist/types/query-builder.d.ts.map +1 -0
- package/dist/types/seed.d.ts +187 -0
- package/dist/types/seed.d.ts.map +1 -0
- package/dist/types/tenant.d.ts +140 -0
- package/dist/types/tenant.d.ts.map +1 -0
- package/dist/types/transaction.d.ts +144 -0
- package/dist/types/transaction.d.ts.map +1 -0
- package/package.json +78 -0
package/API.md
ADDED
|
@@ -0,0 +1,1486 @@
|
|
|
1
|
+
# @gl-life/gl-life-database API Reference
|
|
2
|
+
|
|
3
|
+
**Version**: 1.0.0
|
|
4
|
+
**Package**: `@gl-life/gl-life-database`
|
|
5
|
+
**License**: Apache-2.0
|
|
6
|
+
**Homepage**: https://gl.life
|
|
7
|
+
|
|
8
|
+
## Table of Contents
|
|
9
|
+
|
|
10
|
+
- [Core Types](#core-types)
|
|
11
|
+
- [Connection Management](#connection-management)
|
|
12
|
+
- [Query Builder](#query-builder)
|
|
13
|
+
- [Transactions](#transactions)
|
|
14
|
+
- [Multi-Tenancy](#multi-tenancy)
|
|
15
|
+
- [Caching](#caching)
|
|
16
|
+
- [Migrations](#migrations)
|
|
17
|
+
- [CloudForge Adapters](#cloudforge-adapters)
|
|
18
|
+
- [Error Handling](#error-handling)
|
|
19
|
+
- [Usage Patterns](#usage-patterns)
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Core Types
|
|
24
|
+
|
|
25
|
+
### Result<T, E>
|
|
26
|
+
|
|
27
|
+
Functional error handling type from `@gl-life/gl-life-core`.
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
type Result<T, E> = {
|
|
31
|
+
isOk(): boolean;
|
|
32
|
+
isErr(): boolean;
|
|
33
|
+
unwrap(): T;
|
|
34
|
+
unwrapErr(): E;
|
|
35
|
+
unwrapOr(defaultValue: T): T;
|
|
36
|
+
map<U>(fn: (value: T) => U): Result<U, E>;
|
|
37
|
+
mapErr<F>(fn: (error: E) => F): Result<T, F>;
|
|
38
|
+
};
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Usage**:
|
|
42
|
+
```typescript
|
|
43
|
+
const result: Result<User[], DatabaseError> = await queryUsers();
|
|
44
|
+
if (result.isOk()) {
|
|
45
|
+
const users = result.unwrap();
|
|
46
|
+
} else {
|
|
47
|
+
const error = result.unwrapErr();
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Option<T>
|
|
52
|
+
|
|
53
|
+
Optional value type from `@gl-life/gl-life-core`.
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
type Option<T> = {
|
|
57
|
+
isSome(): boolean;
|
|
58
|
+
isNone(): boolean;
|
|
59
|
+
unwrap(): T;
|
|
60
|
+
unwrapOr(defaultValue: T): T;
|
|
61
|
+
map<U>(fn: (value: T) => U): Option<U>;
|
|
62
|
+
};
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Connection Management
|
|
68
|
+
|
|
69
|
+
### ConnectionManager
|
|
70
|
+
|
|
71
|
+
Manages database connections with pooling support.
|
|
72
|
+
|
|
73
|
+
#### Constructor
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
class ConnectionManager {
|
|
77
|
+
constructor(logger: Logger);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Parameters**:
|
|
82
|
+
- `logger: Logger` - Logger instance from `@gl-life/gl-life-core`
|
|
83
|
+
|
|
84
|
+
#### Methods
|
|
85
|
+
|
|
86
|
+
##### connect()
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
connect(config: DatabaseConfig): Promise<Result<DatabaseConnection, ConnectionError>>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Parameters**:
|
|
93
|
+
```typescript
|
|
94
|
+
interface DatabaseConfig {
|
|
95
|
+
type: 'sqlite' | 'postgres' | 'mysql' | 'd1';
|
|
96
|
+
filename?: string; // SQLite only
|
|
97
|
+
host?: string; // Network databases
|
|
98
|
+
port?: number; // Network databases
|
|
99
|
+
database?: string; // Network databases
|
|
100
|
+
username?: string; // Network databases
|
|
101
|
+
password?: string; // Network databases
|
|
102
|
+
poolSize?: number; // Connection pool size (default: 10)
|
|
103
|
+
timeout?: number; // Connection timeout in ms (default: 30000)
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Returns**: `Result<DatabaseConnection, ConnectionError>`
|
|
108
|
+
|
|
109
|
+
**Example**:
|
|
110
|
+
```typescript
|
|
111
|
+
const manager = new ConnectionManager(logger);
|
|
112
|
+
const result = await manager.connect({
|
|
113
|
+
type: 'sqlite',
|
|
114
|
+
filename: './database.db'
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
if (result.isOk()) {
|
|
118
|
+
const connection = result.unwrap();
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
##### disconnect()
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
disconnect(): Promise<Result<void, ConnectionError>>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Closes all connections in the pool.
|
|
129
|
+
|
|
130
|
+
##### getConnection()
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
getConnection(): Result<DatabaseConnection, ConnectionError>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Returns an available connection from the pool.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Query Builder
|
|
141
|
+
|
|
142
|
+
### TypeSafeQueryBuilder
|
|
143
|
+
|
|
144
|
+
Fluent API for building type-safe SQL queries.
|
|
145
|
+
|
|
146
|
+
#### Constructor
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
class TypeSafeQueryBuilder<T = any> {
|
|
150
|
+
constructor(
|
|
151
|
+
tableName: string,
|
|
152
|
+
metaDataService: MetaDataService | null,
|
|
153
|
+
logger: Logger
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Parameters**:
|
|
159
|
+
- `tableName: string` - Target table name
|
|
160
|
+
- `metaDataService: MetaDataService | null` - Optional metadata service for schema validation
|
|
161
|
+
- `logger: Logger` - Logger instance
|
|
162
|
+
|
|
163
|
+
#### Methods
|
|
164
|
+
|
|
165
|
+
##### select()
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
select(columns: string[]): TypeSafeQueryBuilder<T>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Specify columns to SELECT.
|
|
172
|
+
|
|
173
|
+
**Example**:
|
|
174
|
+
```typescript
|
|
175
|
+
builder.select(['id', 'name', 'email'])
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
##### where()
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
where(
|
|
182
|
+
field: string,
|
|
183
|
+
operator: '=' | '!=' | '>' | '<' | '>=' | '<=' | 'LIKE' | 'IN',
|
|
184
|
+
value: any
|
|
185
|
+
): TypeSafeQueryBuilder<T>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Add WHERE condition.
|
|
189
|
+
|
|
190
|
+
**Example**:
|
|
191
|
+
```typescript
|
|
192
|
+
builder.where('status', '=', 'active')
|
|
193
|
+
.where('age', '>', 18)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
##### orWhere()
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
orWhere(
|
|
200
|
+
field: string,
|
|
201
|
+
operator: '=' | '!=' | '>' | '<' | '>=' | '<=' | 'LIKE' | 'IN',
|
|
202
|
+
value: any
|
|
203
|
+
): TypeSafeQueryBuilder<T>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Add OR WHERE condition.
|
|
207
|
+
|
|
208
|
+
##### whereIn()
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
whereIn(field: string, values: any[]): TypeSafeQueryBuilder<T>
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Add WHERE IN condition.
|
|
215
|
+
|
|
216
|
+
**Example**:
|
|
217
|
+
```typescript
|
|
218
|
+
builder.whereIn('status', ['active', 'pending', 'verified'])
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
##### join()
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
join(
|
|
225
|
+
table: string,
|
|
226
|
+
leftColumn: string,
|
|
227
|
+
operator: '=' | '!=' | '>' | '<',
|
|
228
|
+
rightColumn: string,
|
|
229
|
+
joinType?: 'INNER' | 'LEFT' | 'RIGHT' | 'FULL'
|
|
230
|
+
): TypeSafeQueryBuilder<T>
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Add JOIN clause.
|
|
234
|
+
|
|
235
|
+
**Example**:
|
|
236
|
+
```typescript
|
|
237
|
+
builder.join('posts', 'posts.user_id', '=', 'users.id')
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
##### orderBy()
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
orderBy(column: string, direction: 'ASC' | 'DESC'): TypeSafeQueryBuilder<T>
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Add ORDER BY clause.
|
|
247
|
+
|
|
248
|
+
##### groupBy()
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
groupBy(column: string): TypeSafeQueryBuilder<T>
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Add GROUP BY clause.
|
|
255
|
+
|
|
256
|
+
##### having()
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
having(condition: string): TypeSafeQueryBuilder<T>
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Add HAVING clause.
|
|
263
|
+
|
|
264
|
+
**Example**:
|
|
265
|
+
```typescript
|
|
266
|
+
builder.groupBy('customer_id')
|
|
267
|
+
.having('COUNT(*) > 5')
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
##### limit()
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
limit(count: number): TypeSafeQueryBuilder<T>
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Add LIMIT clause.
|
|
277
|
+
|
|
278
|
+
##### offset()
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
offset(count: number): TypeSafeQueryBuilder<T>
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Add OFFSET clause.
|
|
285
|
+
|
|
286
|
+
##### toSQL()
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
toSQL(): string
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Generate SQL string with parameterized placeholders.
|
|
293
|
+
|
|
294
|
+
**Returns**: SQL string with `?` placeholders
|
|
295
|
+
|
|
296
|
+
**Example**:
|
|
297
|
+
```typescript
|
|
298
|
+
const sql = builder
|
|
299
|
+
.select(['id', 'name'])
|
|
300
|
+
.where('status', '=', 'active')
|
|
301
|
+
.toSQL();
|
|
302
|
+
// Returns: "SELECT id, name FROM users WHERE status = ?"
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Transactions
|
|
308
|
+
|
|
309
|
+
### TransactionManager
|
|
310
|
+
|
|
311
|
+
Handles database transactions with isolation levels.
|
|
312
|
+
|
|
313
|
+
#### Constructor
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
class TransactionManager {
|
|
317
|
+
constructor(
|
|
318
|
+
connection: DatabaseConnection,
|
|
319
|
+
config: TransactionConfig,
|
|
320
|
+
logger: Logger
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**Parameters**:
|
|
326
|
+
```typescript
|
|
327
|
+
interface TransactionConfig {
|
|
328
|
+
isolationLevel: 'READ_UNCOMMITTED' | 'READ_COMMITTED' | 'REPEATABLE_READ' | 'SERIALIZABLE';
|
|
329
|
+
timeout: number; // Transaction timeout in ms
|
|
330
|
+
retryAttempts: number; // Number of retry attempts on conflict
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
#### Methods
|
|
335
|
+
|
|
336
|
+
##### execute()
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
execute<T>(
|
|
340
|
+
callback: (tx: DatabaseTransaction) => Promise<T>
|
|
341
|
+
): Promise<Result<T, TransactionError>>
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Execute operations within a transaction.
|
|
345
|
+
|
|
346
|
+
**Example**:
|
|
347
|
+
```typescript
|
|
348
|
+
const result = await txManager.execute(async (tx) => {
|
|
349
|
+
await tx.execute('INSERT INTO accounts (balance) VALUES (?)', [1000]);
|
|
350
|
+
await tx.execute('UPDATE accounts SET balance = balance - ? WHERE id = ?', [100, 1]);
|
|
351
|
+
await tx.execute('UPDATE accounts SET balance = balance + ? WHERE id = ?', [100, 2]);
|
|
352
|
+
return { success: true };
|
|
353
|
+
});
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### DatabaseTransaction
|
|
357
|
+
|
|
358
|
+
Represents an active transaction.
|
|
359
|
+
|
|
360
|
+
#### Methods
|
|
361
|
+
|
|
362
|
+
##### execute()
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
execute(sql: string, params?: any[]): Promise<Result<any, DatabaseError>>
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Execute SQL within the transaction.
|
|
369
|
+
|
|
370
|
+
##### commit()
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
commit(): Promise<Result<void, TransactionError>>
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Commit the transaction.
|
|
377
|
+
|
|
378
|
+
##### rollback()
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
rollback(): Promise<Result<void, TransactionError>>
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
Rollback the transaction.
|
|
385
|
+
|
|
386
|
+
##### getState()
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
getState(): 'PENDING' | 'ACTIVE' | 'COMMITTED' | 'ROLLED_BACK'
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
Get current transaction state.
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## Multi-Tenancy
|
|
397
|
+
|
|
398
|
+
### TenantContext
|
|
399
|
+
|
|
400
|
+
Manages tenant context for multi-tenant applications.
|
|
401
|
+
|
|
402
|
+
#### Constructor
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
class TenantContext {
|
|
406
|
+
constructor(logger: Logger);
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
#### Methods
|
|
411
|
+
|
|
412
|
+
##### setTenant()
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
setTenant(context: TenantContextData): void
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
Set the current tenant context.
|
|
419
|
+
|
|
420
|
+
**Parameters**:
|
|
421
|
+
```typescript
|
|
422
|
+
interface TenantContextData {
|
|
423
|
+
tenantId: string;
|
|
424
|
+
isAdmin: boolean;
|
|
425
|
+
metadata?: Record<string, any>;
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
**Example**:
|
|
430
|
+
```typescript
|
|
431
|
+
tenantContext.setTenant({
|
|
432
|
+
tenantId: 'tenant-123',
|
|
433
|
+
isAdmin: false
|
|
434
|
+
});
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
##### getTenant()
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
getTenant(): Option<TenantContextData>
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
Get the current tenant context.
|
|
444
|
+
|
|
445
|
+
##### clearTenant()
|
|
446
|
+
|
|
447
|
+
```typescript
|
|
448
|
+
clearTenant(): void
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Clear the tenant context.
|
|
452
|
+
|
|
453
|
+
##### withTenant()
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
withTenant<T>(
|
|
457
|
+
context: TenantContextData,
|
|
458
|
+
callback: () => Promise<T>
|
|
459
|
+
): Promise<T>
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
Execute callback with temporary tenant context.
|
|
463
|
+
|
|
464
|
+
**Example**:
|
|
465
|
+
```typescript
|
|
466
|
+
const result = await tenantContext.withTenant(
|
|
467
|
+
{ tenantId: 'tenant-456', isAdmin: false },
|
|
468
|
+
async () => {
|
|
469
|
+
return await queryUsers();
|
|
470
|
+
}
|
|
471
|
+
);
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### QueryWrapper
|
|
475
|
+
|
|
476
|
+
Wraps queries with automatic tenant filtering.
|
|
477
|
+
|
|
478
|
+
#### Constructor
|
|
479
|
+
|
|
480
|
+
```typescript
|
|
481
|
+
class QueryWrapper {
|
|
482
|
+
constructor(
|
|
483
|
+
tenantContext: TenantContext,
|
|
484
|
+
logger: Logger
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
#### Methods
|
|
490
|
+
|
|
491
|
+
##### query()
|
|
492
|
+
|
|
493
|
+
```typescript
|
|
494
|
+
query<T>(
|
|
495
|
+
table: string,
|
|
496
|
+
options: QueryOptions,
|
|
497
|
+
config?: QueryConfig
|
|
498
|
+
): Promise<Result<T[], DatabaseError>>
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
Execute query with automatic tenant filtering.
|
|
502
|
+
|
|
503
|
+
**Parameters**:
|
|
504
|
+
```typescript
|
|
505
|
+
interface QueryOptions {
|
|
506
|
+
select?: string[];
|
|
507
|
+
where?: Record<string, any>;
|
|
508
|
+
orderBy?: { column: string; direction: 'ASC' | 'DESC' };
|
|
509
|
+
limit?: number;
|
|
510
|
+
offset?: number;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
interface QueryConfig {
|
|
514
|
+
bypassTenantFilter?: boolean; // Admin only
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
**Example**:
|
|
519
|
+
```typescript
|
|
520
|
+
const result = await wrapper.query('products', {
|
|
521
|
+
where: { status: 'active' },
|
|
522
|
+
orderBy: { column: 'created_at', direction: 'DESC' },
|
|
523
|
+
limit: 10
|
|
524
|
+
});
|
|
525
|
+
// Automatically adds: WHERE tenant_id = ? AND status = ?
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
---
|
|
529
|
+
|
|
530
|
+
## Caching
|
|
531
|
+
|
|
532
|
+
### MemoryCache
|
|
533
|
+
|
|
534
|
+
In-memory LRU cache for query results.
|
|
535
|
+
|
|
536
|
+
#### Constructor
|
|
537
|
+
|
|
538
|
+
```typescript
|
|
539
|
+
class MemoryCache {
|
|
540
|
+
constructor(config: CacheConfig, logger: Logger);
|
|
541
|
+
}
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
**Parameters**:
|
|
545
|
+
```typescript
|
|
546
|
+
interface CacheConfig {
|
|
547
|
+
enabled: boolean;
|
|
548
|
+
backend: 'MEMORY' | 'KV' | 'HYBRID';
|
|
549
|
+
maxItems?: number; // Max cache entries (default: 1000)
|
|
550
|
+
defaultTTL?: number; // Default TTL in seconds (default: 300)
|
|
551
|
+
evictionPolicy?: 'LRU' | 'LFU' | 'FIFO';
|
|
552
|
+
invalidationStrategy?: 'TIME_BASED' | 'EVENT_BASED' | 'HYBRID';
|
|
553
|
+
keyPrefix?: string; // Key prefix for namespacing
|
|
554
|
+
}
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
#### Methods
|
|
558
|
+
|
|
559
|
+
##### get()
|
|
560
|
+
|
|
561
|
+
```typescript
|
|
562
|
+
get<T>(key: string): Promise<Option<T>>
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
Get value from cache.
|
|
566
|
+
|
|
567
|
+
**Example**:
|
|
568
|
+
```typescript
|
|
569
|
+
const result = await cache.get<User>('user:123');
|
|
570
|
+
if (result.isSome()) {
|
|
571
|
+
const user = result.unwrap();
|
|
572
|
+
}
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
##### set()
|
|
576
|
+
|
|
577
|
+
```typescript
|
|
578
|
+
set<T>(key: string, value: T, ttl?: number): Promise<Result<void, CacheError>>
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
Set value in cache.
|
|
582
|
+
|
|
583
|
+
**Example**:
|
|
584
|
+
```typescript
|
|
585
|
+
await cache.set('user:123', { id: 123, name: 'Alice' }, 60);
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
##### delete()
|
|
589
|
+
|
|
590
|
+
```typescript
|
|
591
|
+
delete(key: string): Promise<Result<void, CacheError>>
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
Delete key from cache.
|
|
595
|
+
|
|
596
|
+
##### has()
|
|
597
|
+
|
|
598
|
+
```typescript
|
|
599
|
+
has(key: string): Promise<boolean>
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
Check if key exists in cache.
|
|
603
|
+
|
|
604
|
+
##### clear()
|
|
605
|
+
|
|
606
|
+
```typescript
|
|
607
|
+
clear(): Promise<Result<void, CacheError>>
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
Clear all cache entries.
|
|
611
|
+
|
|
612
|
+
##### invalidatePattern()
|
|
613
|
+
|
|
614
|
+
```typescript
|
|
615
|
+
invalidatePattern(pattern: string): Promise<Result<number, CacheError>>
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
Invalidate all keys matching pattern (supports wildcards).
|
|
619
|
+
|
|
620
|
+
**Example**:
|
|
621
|
+
```typescript
|
|
622
|
+
await cache.invalidatePattern('user:*');
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
##### getStats()
|
|
626
|
+
|
|
627
|
+
```typescript
|
|
628
|
+
getStats(): CacheStats
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
Get cache statistics.
|
|
632
|
+
|
|
633
|
+
**Returns**:
|
|
634
|
+
```typescript
|
|
635
|
+
interface CacheStats {
|
|
636
|
+
hits: number;
|
|
637
|
+
misses: number;
|
|
638
|
+
hitRate: number;
|
|
639
|
+
size: number;
|
|
640
|
+
evictions: number;
|
|
641
|
+
}
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
### KVCache
|
|
645
|
+
|
|
646
|
+
Cloudflare Workers KV-backed cache.
|
|
647
|
+
|
|
648
|
+
#### Constructor
|
|
649
|
+
|
|
650
|
+
```typescript
|
|
651
|
+
class KVCache {
|
|
652
|
+
constructor(
|
|
653
|
+
kv: KVNamespace,
|
|
654
|
+
config: CacheConfig,
|
|
655
|
+
logger: Logger
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
Methods are identical to `MemoryCache`.
|
|
661
|
+
|
|
662
|
+
---
|
|
663
|
+
|
|
664
|
+
## Migrations
|
|
665
|
+
|
|
666
|
+
### MigrationPlanner
|
|
667
|
+
|
|
668
|
+
Plans and validates database migrations.
|
|
669
|
+
|
|
670
|
+
#### Constructor
|
|
671
|
+
|
|
672
|
+
```typescript
|
|
673
|
+
class MigrationPlanner {
|
|
674
|
+
constructor(config: MigrationConfig, logger: Logger);
|
|
675
|
+
}
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
**Parameters**:
|
|
679
|
+
```typescript
|
|
680
|
+
interface MigrationConfig {
|
|
681
|
+
enabled: boolean;
|
|
682
|
+
migrationTable: string; // Table to track migrations (default: 'migrations')
|
|
683
|
+
autoRun: boolean; // Auto-run migrations on startup (default: false)
|
|
684
|
+
migrationPath?: string; // Path to migration files
|
|
685
|
+
}
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
#### Methods
|
|
689
|
+
|
|
690
|
+
##### loadMigrations()
|
|
691
|
+
|
|
692
|
+
```typescript
|
|
693
|
+
loadMigrations(
|
|
694
|
+
migrations: Migration[]
|
|
695
|
+
): Promise<Result<void, MigrationError>>
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
Load migrations from array.
|
|
699
|
+
|
|
700
|
+
**Parameters**:
|
|
701
|
+
```typescript
|
|
702
|
+
interface Migration {
|
|
703
|
+
version: string;
|
|
704
|
+
name: string;
|
|
705
|
+
up: string; // SQL to apply migration
|
|
706
|
+
down: string; // SQL to rollback migration
|
|
707
|
+
dependencies?: string[];
|
|
708
|
+
}
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
##### loadFromDirectory()
|
|
712
|
+
|
|
713
|
+
```typescript
|
|
714
|
+
loadFromDirectory(
|
|
715
|
+
path: string
|
|
716
|
+
): Promise<Result<Migration[], MigrationError>>
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
Load migrations from directory.
|
|
720
|
+
|
|
721
|
+
##### planMigration()
|
|
722
|
+
|
|
723
|
+
```typescript
|
|
724
|
+
planMigration(
|
|
725
|
+
targetVersion: string
|
|
726
|
+
): Result<MigrationPlan, MigrationError>
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
Plan migration to target version.
|
|
730
|
+
|
|
731
|
+
**Returns**:
|
|
732
|
+
```typescript
|
|
733
|
+
interface MigrationPlan {
|
|
734
|
+
migrations: Migration[];
|
|
735
|
+
totalSteps: number;
|
|
736
|
+
estimatedDuration: number;
|
|
737
|
+
warnings: string[];
|
|
738
|
+
}
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
##### checkStatus()
|
|
742
|
+
|
|
743
|
+
```typescript
|
|
744
|
+
checkStatus(): Promise<Result<MigrationStatus, MigrationError>>
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
Check migration status.
|
|
748
|
+
|
|
749
|
+
**Returns**:
|
|
750
|
+
```typescript
|
|
751
|
+
interface MigrationStatus {
|
|
752
|
+
currentVersion: string;
|
|
753
|
+
appliedMigrations: string[];
|
|
754
|
+
pendingMigrations: string[];
|
|
755
|
+
lastMigrationDate: Date;
|
|
756
|
+
}
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
### MigrationExecutor
|
|
760
|
+
|
|
761
|
+
Executes migration plans.
|
|
762
|
+
|
|
763
|
+
#### Constructor
|
|
764
|
+
|
|
765
|
+
```typescript
|
|
766
|
+
class MigrationExecutor {
|
|
767
|
+
constructor(
|
|
768
|
+
connection: DatabaseConnection,
|
|
769
|
+
logger: Logger
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
#### Methods
|
|
775
|
+
|
|
776
|
+
##### executeMigration()
|
|
777
|
+
|
|
778
|
+
```typescript
|
|
779
|
+
executeMigration(
|
|
780
|
+
plan: MigrationPlan
|
|
781
|
+
): Promise<Result<MigrationResult, MigrationError>>
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
Execute migration plan.
|
|
785
|
+
|
|
786
|
+
**Returns**:
|
|
787
|
+
```typescript
|
|
788
|
+
interface MigrationResult {
|
|
789
|
+
appliedMigrations: string[];
|
|
790
|
+
duration: number;
|
|
791
|
+
success: boolean;
|
|
792
|
+
}
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
##### rollback()
|
|
796
|
+
|
|
797
|
+
```typescript
|
|
798
|
+
rollback(steps?: number): Promise<Result<void, MigrationError>>
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
Rollback migrations.
|
|
802
|
+
|
|
803
|
+
**Parameters**:
|
|
804
|
+
- `steps?: number` - Number of steps to rollback (default: 1)
|
|
805
|
+
|
|
806
|
+
---
|
|
807
|
+
|
|
808
|
+
## CloudForge Adapters
|
|
809
|
+
|
|
810
|
+
### D1Adapter
|
|
811
|
+
|
|
812
|
+
Cloudflare D1 database adapter.
|
|
813
|
+
|
|
814
|
+
#### Constructor
|
|
815
|
+
|
|
816
|
+
```typescript
|
|
817
|
+
class D1Adapter {
|
|
818
|
+
constructor(d1: D1Database, logger: Logger);
|
|
819
|
+
}
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
**Parameters**:
|
|
823
|
+
- `d1: D1Database` - Cloudflare D1 database binding
|
|
824
|
+
- `logger: Logger` - Logger instance
|
|
825
|
+
|
|
826
|
+
#### Methods
|
|
827
|
+
|
|
828
|
+
##### execute()
|
|
829
|
+
|
|
830
|
+
```typescript
|
|
831
|
+
execute(
|
|
832
|
+
sql: string,
|
|
833
|
+
params?: any[]
|
|
834
|
+
): Promise<Result<D1Result, DatabaseError>>
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
Execute SQL query.
|
|
838
|
+
|
|
839
|
+
**Example**:
|
|
840
|
+
```typescript
|
|
841
|
+
// In Cloudflare Worker
|
|
842
|
+
export default {
|
|
843
|
+
async fetch(request: Request, env: Env): Promise<Response> {
|
|
844
|
+
const adapter = new D1Adapter(env.DB, logger);
|
|
845
|
+
const result = await adapter.execute(
|
|
846
|
+
'SELECT * FROM users WHERE id = ?',
|
|
847
|
+
[123]
|
|
848
|
+
);
|
|
849
|
+
return new Response(JSON.stringify(result));
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
```
|
|
853
|
+
|
|
854
|
+
##### batch()
|
|
855
|
+
|
|
856
|
+
```typescript
|
|
857
|
+
batch(
|
|
858
|
+
statements: { sql: string; params?: any[] }[]
|
|
859
|
+
): Promise<Result<D1Result[], DatabaseError>>
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
Execute batch of statements.
|
|
863
|
+
|
|
864
|
+
### DurableObjectStorage
|
|
865
|
+
|
|
866
|
+
Durable Objects storage adapter.
|
|
867
|
+
|
|
868
|
+
#### Constructor
|
|
869
|
+
|
|
870
|
+
```typescript
|
|
871
|
+
class DurableObjectStorage {
|
|
872
|
+
constructor(
|
|
873
|
+
storage: DurableObjectStorage,
|
|
874
|
+
logger: Logger
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
#### Methods
|
|
880
|
+
|
|
881
|
+
##### get()
|
|
882
|
+
|
|
883
|
+
```typescript
|
|
884
|
+
get<T>(key: string): Promise<Option<T>>
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
Get value from Durable Object storage.
|
|
888
|
+
|
|
889
|
+
##### put()
|
|
890
|
+
|
|
891
|
+
```typescript
|
|
892
|
+
put<T>(key: string, value: T): Promise<Result<void, StorageError>>
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
Store value in Durable Object storage.
|
|
896
|
+
|
|
897
|
+
##### delete()
|
|
898
|
+
|
|
899
|
+
```typescript
|
|
900
|
+
delete(key: string): Promise<Result<void, StorageError>>
|
|
901
|
+
```
|
|
902
|
+
|
|
903
|
+
Delete key from storage.
|
|
904
|
+
|
|
905
|
+
##### list()
|
|
906
|
+
|
|
907
|
+
```typescript
|
|
908
|
+
list(options?: ListOptions): Promise<Result<Map<string, any>, StorageError>>
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
List keys in storage.
|
|
912
|
+
|
|
913
|
+
**Parameters**:
|
|
914
|
+
```typescript
|
|
915
|
+
interface ListOptions {
|
|
916
|
+
start?: string;
|
|
917
|
+
end?: string;
|
|
918
|
+
prefix?: string;
|
|
919
|
+
limit?: number;
|
|
920
|
+
reverse?: boolean;
|
|
921
|
+
}
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
---
|
|
925
|
+
|
|
926
|
+
## Error Handling
|
|
927
|
+
|
|
928
|
+
All methods return `Result<T, E>` types for functional error handling.
|
|
929
|
+
|
|
930
|
+
### Error Types
|
|
931
|
+
|
|
932
|
+
#### DatabaseError
|
|
933
|
+
|
|
934
|
+
```typescript
|
|
935
|
+
class DatabaseError extends Error {
|
|
936
|
+
code: string;
|
|
937
|
+
query?: string;
|
|
938
|
+
params?: any[];
|
|
939
|
+
constructor(message: string, code: string, query?: string, params?: any[]);
|
|
940
|
+
}
|
|
941
|
+
```
|
|
942
|
+
|
|
943
|
+
**Error Codes**:
|
|
944
|
+
- `DB_CONNECTION_FAILED` - Connection failure
|
|
945
|
+
- `DB_QUERY_FAILED` - Query execution failed
|
|
946
|
+
- `DB_CONSTRAINT_VIOLATION` - Constraint violation
|
|
947
|
+
- `DB_TIMEOUT` - Query timeout
|
|
948
|
+
- `DB_PERMISSION_DENIED` - Permission denied
|
|
949
|
+
|
|
950
|
+
#### TransactionError
|
|
951
|
+
|
|
952
|
+
```typescript
|
|
953
|
+
class TransactionError extends Error {
|
|
954
|
+
code: string;
|
|
955
|
+
transactionId?: string;
|
|
956
|
+
constructor(message: string, code: string, transactionId?: string);
|
|
957
|
+
}
|
|
958
|
+
```
|
|
959
|
+
|
|
960
|
+
**Error Codes**:
|
|
961
|
+
- `TX_ALREADY_STARTED` - Transaction already active
|
|
962
|
+
- `TX_NOT_ACTIVE` - Transaction not active
|
|
963
|
+
- `TX_COMMIT_FAILED` - Commit failed
|
|
964
|
+
- `TX_ROLLBACK_FAILED` - Rollback failed
|
|
965
|
+
- `TX_TIMEOUT` - Transaction timeout
|
|
966
|
+
|
|
967
|
+
#### CacheError
|
|
968
|
+
|
|
969
|
+
```typescript
|
|
970
|
+
class CacheError extends Error {
|
|
971
|
+
code: string;
|
|
972
|
+
key?: string;
|
|
973
|
+
constructor(message: string, code: string, key?: string);
|
|
974
|
+
}
|
|
975
|
+
```
|
|
976
|
+
|
|
977
|
+
**Error Codes**:
|
|
978
|
+
- `CACHE_GET_FAILED` - Get operation failed
|
|
979
|
+
- `CACHE_SET_FAILED` - Set operation failed
|
|
980
|
+
- `CACHE_DELETE_FAILED` - Delete operation failed
|
|
981
|
+
- `CACHE_FULL` - Cache is full
|
|
982
|
+
|
|
983
|
+
#### MigrationError
|
|
984
|
+
|
|
985
|
+
```typescript
|
|
986
|
+
class MigrationError extends Error {
|
|
987
|
+
code: string;
|
|
988
|
+
migration?: string;
|
|
989
|
+
constructor(message: string, code: string, migration?: string);
|
|
990
|
+
}
|
|
991
|
+
```
|
|
992
|
+
|
|
993
|
+
**Error Codes**:
|
|
994
|
+
- `MIGRATION_LOAD_FAILED` - Failed to load migrations
|
|
995
|
+
- `MIGRATION_EXECUTE_FAILED` - Migration execution failed
|
|
996
|
+
- `MIGRATION_ROLLBACK_FAILED` - Rollback failed
|
|
997
|
+
- `MIGRATION_VERSION_CONFLICT` - Version conflict
|
|
998
|
+
|
|
999
|
+
---
|
|
1000
|
+
|
|
1001
|
+
## Usage Patterns
|
|
1002
|
+
|
|
1003
|
+
### Pattern 1: Basic Query with Error Handling
|
|
1004
|
+
|
|
1005
|
+
```typescript
|
|
1006
|
+
import {
|
|
1007
|
+
ConnectionManager,
|
|
1008
|
+
TypeSafeQueryBuilder,
|
|
1009
|
+
Logger,
|
|
1010
|
+
Result,
|
|
1011
|
+
DatabaseError
|
|
1012
|
+
} from '@gl-life/gl-life-database';
|
|
1013
|
+
|
|
1014
|
+
const logger = new Logger({ level: 'info' });
|
|
1015
|
+
const manager = new ConnectionManager(logger);
|
|
1016
|
+
|
|
1017
|
+
const connResult = await manager.connect({
|
|
1018
|
+
type: 'sqlite',
|
|
1019
|
+
filename: './app.db'
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
if (connResult.isErr()) {
|
|
1023
|
+
console.error('Connection failed:', connResult.unwrapErr());
|
|
1024
|
+
process.exit(1);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
const connection = connResult.unwrap();
|
|
1028
|
+
const builder = new TypeSafeQueryBuilder('users', null, logger);
|
|
1029
|
+
const sql = builder.select(['*']).where('active', '=', true).toSQL();
|
|
1030
|
+
|
|
1031
|
+
const queryResult = await connection.execute(sql, [true]);
|
|
1032
|
+
if (queryResult.isOk()) {
|
|
1033
|
+
const users = queryResult.unwrap();
|
|
1034
|
+
console.log('Users:', users);
|
|
1035
|
+
}
|
|
1036
|
+
```
|
|
1037
|
+
|
|
1038
|
+
### Pattern 2: Multi-Tenant Query
|
|
1039
|
+
|
|
1040
|
+
```typescript
|
|
1041
|
+
import {
|
|
1042
|
+
TenantContext,
|
|
1043
|
+
QueryWrapper,
|
|
1044
|
+
Logger
|
|
1045
|
+
} from '@gl-life/gl-life-database';
|
|
1046
|
+
|
|
1047
|
+
const logger = new Logger({ level: 'info' });
|
|
1048
|
+
const tenantContext = new TenantContext(logger);
|
|
1049
|
+
const wrapper = new QueryWrapper(tenantContext, logger);
|
|
1050
|
+
|
|
1051
|
+
// Set tenant from request
|
|
1052
|
+
const tenantId = request.headers.get('X-Tenant-ID');
|
|
1053
|
+
tenantContext.setTenant({
|
|
1054
|
+
tenantId,
|
|
1055
|
+
isAdmin: false
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
// Query automatically includes tenant filter
|
|
1059
|
+
const result = await wrapper.query('products', {
|
|
1060
|
+
where: { status: 'active' },
|
|
1061
|
+
limit: 20
|
|
1062
|
+
});
|
|
1063
|
+
// SQL: WHERE tenant_id = ? AND status = ? LIMIT 20
|
|
1064
|
+
```
|
|
1065
|
+
|
|
1066
|
+
### Pattern 3: Transaction with Retry
|
|
1067
|
+
|
|
1068
|
+
```typescript
|
|
1069
|
+
import {
|
|
1070
|
+
TransactionManager,
|
|
1071
|
+
Logger
|
|
1072
|
+
} from '@gl-life/gl-life-database';
|
|
1073
|
+
|
|
1074
|
+
const config = {
|
|
1075
|
+
isolationLevel: 'READ_COMMITTED' as const,
|
|
1076
|
+
timeout: 30000,
|
|
1077
|
+
retryAttempts: 3
|
|
1078
|
+
};
|
|
1079
|
+
|
|
1080
|
+
const txManager = new TransactionManager(connection, config, logger);
|
|
1081
|
+
|
|
1082
|
+
const result = await txManager.execute(async (tx) => {
|
|
1083
|
+
// Transfer money between accounts
|
|
1084
|
+
const debit = await tx.execute(
|
|
1085
|
+
'UPDATE accounts SET balance = balance - ? WHERE id = ?',
|
|
1086
|
+
[amount, fromAccountId]
|
|
1087
|
+
);
|
|
1088
|
+
|
|
1089
|
+
if (debit.isErr()) throw debit.unwrapErr();
|
|
1090
|
+
|
|
1091
|
+
const credit = await tx.execute(
|
|
1092
|
+
'UPDATE accounts SET balance = balance + ? WHERE id = ?',
|
|
1093
|
+
[amount, toAccountId]
|
|
1094
|
+
);
|
|
1095
|
+
|
|
1096
|
+
if (credit.isErr()) throw credit.unwrapErr();
|
|
1097
|
+
|
|
1098
|
+
return { transferred: amount };
|
|
1099
|
+
});
|
|
1100
|
+
```
|
|
1101
|
+
|
|
1102
|
+
### Pattern 4: Cached Query
|
|
1103
|
+
|
|
1104
|
+
```typescript
|
|
1105
|
+
import {
|
|
1106
|
+
MemoryCache,
|
|
1107
|
+
Logger,
|
|
1108
|
+
CacheConfig
|
|
1109
|
+
} from '@gl-life/gl-life-database';
|
|
1110
|
+
|
|
1111
|
+
const cacheConfig: CacheConfig = {
|
|
1112
|
+
enabled: true,
|
|
1113
|
+
backend: 'MEMORY',
|
|
1114
|
+
maxItems: 1000,
|
|
1115
|
+
defaultTTL: 300,
|
|
1116
|
+
evictionPolicy: 'LRU',
|
|
1117
|
+
invalidationStrategy: 'TIME_BASED',
|
|
1118
|
+
keyPrefix: 'app:'
|
|
1119
|
+
};
|
|
1120
|
+
|
|
1121
|
+
const cache = new MemoryCache(cacheConfig, logger);
|
|
1122
|
+
|
|
1123
|
+
async function getUser(id: number) {
|
|
1124
|
+
const cacheKey = `user:${id}`;
|
|
1125
|
+
|
|
1126
|
+
// Try cache first
|
|
1127
|
+
const cached = await cache.get<User>(cacheKey);
|
|
1128
|
+
if (cached.isSome()) {
|
|
1129
|
+
return cached.unwrap();
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// Query database
|
|
1133
|
+
const result = await queryUser(id);
|
|
1134
|
+
if (result.isOk()) {
|
|
1135
|
+
const user = result.unwrap();
|
|
1136
|
+
await cache.set(cacheKey, user, 60);
|
|
1137
|
+
return user;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
throw result.unwrapErr();
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
// Invalidate on update
|
|
1144
|
+
async function updateUser(id: number, data: Partial<User>) {
|
|
1145
|
+
const result = await executeUpdate(id, data);
|
|
1146
|
+
if (result.isOk()) {
|
|
1147
|
+
await cache.delete(`user:${id}`);
|
|
1148
|
+
}
|
|
1149
|
+
return result;
|
|
1150
|
+
}
|
|
1151
|
+
```
|
|
1152
|
+
|
|
1153
|
+
### Pattern 5: Database Migration
|
|
1154
|
+
|
|
1155
|
+
```typescript
|
|
1156
|
+
import {
|
|
1157
|
+
MigrationPlanner,
|
|
1158
|
+
MigrationExecutor,
|
|
1159
|
+
Logger,
|
|
1160
|
+
MigrationConfig
|
|
1161
|
+
} from '@gl-life/gl-life-database';
|
|
1162
|
+
|
|
1163
|
+
const config: MigrationConfig = {
|
|
1164
|
+
enabled: true,
|
|
1165
|
+
migrationTable: 'migrations',
|
|
1166
|
+
autoRun: false
|
|
1167
|
+
};
|
|
1168
|
+
|
|
1169
|
+
const logger = new Logger({ level: 'info' });
|
|
1170
|
+
const planner = new MigrationPlanner(config, logger);
|
|
1171
|
+
const executor = new MigrationExecutor(connection, logger);
|
|
1172
|
+
|
|
1173
|
+
// Load migrations
|
|
1174
|
+
const migrations = [
|
|
1175
|
+
{
|
|
1176
|
+
version: '001',
|
|
1177
|
+
name: 'create_users',
|
|
1178
|
+
up: 'CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)',
|
|
1179
|
+
down: 'DROP TABLE users'
|
|
1180
|
+
},
|
|
1181
|
+
{
|
|
1182
|
+
version: '002',
|
|
1183
|
+
name: 'add_users_email',
|
|
1184
|
+
up: 'ALTER TABLE users ADD COLUMN email TEXT',
|
|
1185
|
+
down: 'ALTER TABLE users DROP COLUMN email',
|
|
1186
|
+
dependencies: ['001']
|
|
1187
|
+
}
|
|
1188
|
+
];
|
|
1189
|
+
|
|
1190
|
+
await planner.loadMigrations(migrations);
|
|
1191
|
+
|
|
1192
|
+
// Check status
|
|
1193
|
+
const status = await planner.checkStatus();
|
|
1194
|
+
if (status.isOk()) {
|
|
1195
|
+
console.log('Current version:', status.unwrap().currentVersion);
|
|
1196
|
+
console.log('Pending:', status.unwrap().pendingMigrations);
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
// Plan and execute
|
|
1200
|
+
const plan = planner.planMigration('002');
|
|
1201
|
+
if (plan.isOk()) {
|
|
1202
|
+
const result = await executor.executeMigration(plan.unwrap());
|
|
1203
|
+
console.log('Migration result:', result);
|
|
1204
|
+
}
|
|
1205
|
+
```
|
|
1206
|
+
|
|
1207
|
+
### Pattern 6: Cloudflare D1 Worker
|
|
1208
|
+
|
|
1209
|
+
```typescript
|
|
1210
|
+
import {
|
|
1211
|
+
D1Adapter,
|
|
1212
|
+
Logger,
|
|
1213
|
+
MemoryCache,
|
|
1214
|
+
CacheConfig
|
|
1215
|
+
} from '@gl-life/gl-life-database';
|
|
1216
|
+
|
|
1217
|
+
export default {
|
|
1218
|
+
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
|
1219
|
+
const logger = new Logger({ level: 'info' });
|
|
1220
|
+
const db = new D1Adapter(env.DB, logger);
|
|
1221
|
+
|
|
1222
|
+
// Set up cache
|
|
1223
|
+
const cacheConfig: CacheConfig = {
|
|
1224
|
+
enabled: true,
|
|
1225
|
+
backend: 'MEMORY',
|
|
1226
|
+
maxItems: 100,
|
|
1227
|
+
defaultTTL: 60,
|
|
1228
|
+
evictionPolicy: 'LRU',
|
|
1229
|
+
invalidationStrategy: 'TIME_BASED'
|
|
1230
|
+
};
|
|
1231
|
+
const cache = new MemoryCache(cacheConfig, logger);
|
|
1232
|
+
|
|
1233
|
+
// Parse request
|
|
1234
|
+
const url = new URL(request.url);
|
|
1235
|
+
const userId = url.searchParams.get('id');
|
|
1236
|
+
|
|
1237
|
+
if (!userId) {
|
|
1238
|
+
return new Response('Missing user ID', { status: 400 });
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
// Try cache
|
|
1242
|
+
const cacheKey = `user:${userId}`;
|
|
1243
|
+
const cached = await cache.get(cacheKey);
|
|
1244
|
+
|
|
1245
|
+
if (cached.isSome()) {
|
|
1246
|
+
return new Response(JSON.stringify({
|
|
1247
|
+
data: cached.unwrap(),
|
|
1248
|
+
cached: true
|
|
1249
|
+
}), {
|
|
1250
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// Query D1
|
|
1255
|
+
const result = await db.execute(
|
|
1256
|
+
'SELECT * FROM users WHERE id = ?',
|
|
1257
|
+
[userId]
|
|
1258
|
+
);
|
|
1259
|
+
|
|
1260
|
+
if (result.isErr()) {
|
|
1261
|
+
return new Response(
|
|
1262
|
+
JSON.stringify({ error: result.unwrapErr().message }),
|
|
1263
|
+
{ status: 500 }
|
|
1264
|
+
);
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
const data = result.unwrap();
|
|
1268
|
+
await cache.set(cacheKey, data, 60);
|
|
1269
|
+
|
|
1270
|
+
return new Response(JSON.stringify({
|
|
1271
|
+
data,
|
|
1272
|
+
cached: false
|
|
1273
|
+
}), {
|
|
1274
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
};
|
|
1278
|
+
```
|
|
1279
|
+
|
|
1280
|
+
---
|
|
1281
|
+
|
|
1282
|
+
## Type Definitions Summary
|
|
1283
|
+
|
|
1284
|
+
### Connection Types
|
|
1285
|
+
|
|
1286
|
+
```typescript
|
|
1287
|
+
type DatabaseType = 'sqlite' | 'postgres' | 'mysql' | 'd1';
|
|
1288
|
+
|
|
1289
|
+
interface DatabaseConfig {
|
|
1290
|
+
type: DatabaseType;
|
|
1291
|
+
filename?: string;
|
|
1292
|
+
host?: string;
|
|
1293
|
+
port?: number;
|
|
1294
|
+
database?: string;
|
|
1295
|
+
username?: string;
|
|
1296
|
+
password?: string;
|
|
1297
|
+
poolSize?: number;
|
|
1298
|
+
timeout?: number;
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
interface DatabaseConnection {
|
|
1302
|
+
execute(sql: string, params?: any[]): Promise<Result<any, DatabaseError>>;
|
|
1303
|
+
close(): Promise<Result<void, ConnectionError>>;
|
|
1304
|
+
}
|
|
1305
|
+
```
|
|
1306
|
+
|
|
1307
|
+
### Query Types
|
|
1308
|
+
|
|
1309
|
+
```typescript
|
|
1310
|
+
type Operator = '=' | '!=' | '>' | '<' | '>=' | '<=' | 'LIKE' | 'IN';
|
|
1311
|
+
type JoinType = 'INNER' | 'LEFT' | 'RIGHT' | 'FULL';
|
|
1312
|
+
type SortDirection = 'ASC' | 'DESC';
|
|
1313
|
+
|
|
1314
|
+
interface WhereCondition {
|
|
1315
|
+
field: string;
|
|
1316
|
+
operator: Operator;
|
|
1317
|
+
value: any;
|
|
1318
|
+
connector: 'AND' | 'OR';
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
interface JoinClause {
|
|
1322
|
+
table: string;
|
|
1323
|
+
leftColumn: string;
|
|
1324
|
+
operator: string;
|
|
1325
|
+
rightColumn: string;
|
|
1326
|
+
joinType: JoinType;
|
|
1327
|
+
}
|
|
1328
|
+
```
|
|
1329
|
+
|
|
1330
|
+
### Transaction Types
|
|
1331
|
+
|
|
1332
|
+
```typescript
|
|
1333
|
+
type IsolationLevel =
|
|
1334
|
+
| 'READ_UNCOMMITTED'
|
|
1335
|
+
| 'READ_COMMITTED'
|
|
1336
|
+
| 'REPEATABLE_READ'
|
|
1337
|
+
| 'SERIALIZABLE';
|
|
1338
|
+
|
|
1339
|
+
type TransactionState =
|
|
1340
|
+
| 'PENDING'
|
|
1341
|
+
| 'ACTIVE'
|
|
1342
|
+
| 'COMMITTED'
|
|
1343
|
+
| 'ROLLED_BACK';
|
|
1344
|
+
|
|
1345
|
+
interface TransactionConfig {
|
|
1346
|
+
isolationLevel: IsolationLevel;
|
|
1347
|
+
timeout: number;
|
|
1348
|
+
retryAttempts: number;
|
|
1349
|
+
}
|
|
1350
|
+
```
|
|
1351
|
+
|
|
1352
|
+
### Cache Types
|
|
1353
|
+
|
|
1354
|
+
```typescript
|
|
1355
|
+
type CacheBackend = 'MEMORY' | 'KV' | 'HYBRID';
|
|
1356
|
+
type EvictionPolicy = 'LRU' | 'LFU' | 'FIFO';
|
|
1357
|
+
type InvalidationStrategy = 'TIME_BASED' | 'EVENT_BASED' | 'HYBRID';
|
|
1358
|
+
|
|
1359
|
+
interface CacheConfig {
|
|
1360
|
+
enabled: boolean;
|
|
1361
|
+
backend: CacheBackend;
|
|
1362
|
+
maxItems?: number;
|
|
1363
|
+
defaultTTL?: number;
|
|
1364
|
+
evictionPolicy?: EvictionPolicy;
|
|
1365
|
+
invalidationStrategy?: InvalidationStrategy;
|
|
1366
|
+
keyPrefix?: string;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
interface CacheStats {
|
|
1370
|
+
hits: number;
|
|
1371
|
+
misses: number;
|
|
1372
|
+
hitRate: number;
|
|
1373
|
+
size: number;
|
|
1374
|
+
evictions: number;
|
|
1375
|
+
}
|
|
1376
|
+
```
|
|
1377
|
+
|
|
1378
|
+
### Migration Types
|
|
1379
|
+
|
|
1380
|
+
```typescript
|
|
1381
|
+
interface Migration {
|
|
1382
|
+
version: string;
|
|
1383
|
+
name: string;
|
|
1384
|
+
up: string;
|
|
1385
|
+
down: string;
|
|
1386
|
+
dependencies?: string[];
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
interface MigrationConfig {
|
|
1390
|
+
enabled: boolean;
|
|
1391
|
+
migrationTable: string;
|
|
1392
|
+
autoRun: boolean;
|
|
1393
|
+
migrationPath?: string;
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
interface MigrationStatus {
|
|
1397
|
+
currentVersion: string;
|
|
1398
|
+
appliedMigrations: string[];
|
|
1399
|
+
pendingMigrations: string[];
|
|
1400
|
+
lastMigrationDate: Date;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
interface MigrationPlan {
|
|
1404
|
+
migrations: Migration[];
|
|
1405
|
+
totalSteps: number;
|
|
1406
|
+
estimatedDuration: number;
|
|
1407
|
+
warnings: string[];
|
|
1408
|
+
}
|
|
1409
|
+
```
|
|
1410
|
+
|
|
1411
|
+
### Tenant Types
|
|
1412
|
+
|
|
1413
|
+
```typescript
|
|
1414
|
+
interface TenantContextData {
|
|
1415
|
+
tenantId: string;
|
|
1416
|
+
isAdmin: boolean;
|
|
1417
|
+
metadata?: Record<string, any>;
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
interface QueryOptions {
|
|
1421
|
+
select?: string[];
|
|
1422
|
+
where?: Record<string, any>;
|
|
1423
|
+
orderBy?: { column: string; direction: 'ASC' | 'DESC' };
|
|
1424
|
+
limit?: number;
|
|
1425
|
+
offset?: number;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
interface QueryConfig {
|
|
1429
|
+
bypassTenantFilter?: boolean;
|
|
1430
|
+
}
|
|
1431
|
+
```
|
|
1432
|
+
|
|
1433
|
+
---
|
|
1434
|
+
|
|
1435
|
+
## Performance Considerations
|
|
1436
|
+
|
|
1437
|
+
### Query Builder
|
|
1438
|
+
|
|
1439
|
+
- Query building overhead: **<1ms** (P95)
|
|
1440
|
+
- Supports parameterized queries to prevent SQL injection
|
|
1441
|
+
- Minimal memory footprint per query (~0.001 MB)
|
|
1442
|
+
|
|
1443
|
+
### Cache
|
|
1444
|
+
|
|
1445
|
+
- GET operation: **<1ms** (P95)
|
|
1446
|
+
- SET operation: **<1ms** (P95)
|
|
1447
|
+
- Pattern invalidation (100 keys): **<10ms** (P95)
|
|
1448
|
+
- LRU eviction prevents unbounded memory growth
|
|
1449
|
+
|
|
1450
|
+
### Transactions
|
|
1451
|
+
|
|
1452
|
+
- Automatic retry on conflict
|
|
1453
|
+
- Configurable isolation levels
|
|
1454
|
+
- Timeout protection
|
|
1455
|
+
|
|
1456
|
+
---
|
|
1457
|
+
|
|
1458
|
+
## Security Features
|
|
1459
|
+
|
|
1460
|
+
- ✅ **SQL Injection Protection** - All queries use parameterized statements
|
|
1461
|
+
- ✅ **Tenant Isolation** - Automatic tenant_id filtering prevents data leaks
|
|
1462
|
+
- ✅ **Type Safety** - TypeScript compile-time + Zod runtime validation
|
|
1463
|
+
- ✅ **Audit Logging** - All operations logged for security auditing
|
|
1464
|
+
|
|
1465
|
+
See [SECURITY.md](./SECURITY.md) for security audit results.
|
|
1466
|
+
|
|
1467
|
+
---
|
|
1468
|
+
|
|
1469
|
+
## Version Compatibility
|
|
1470
|
+
|
|
1471
|
+
- **TypeScript**: 5.0+
|
|
1472
|
+
- **Node.js**: 18+
|
|
1473
|
+
- **Cloudflare Workers**: Compatible
|
|
1474
|
+
- **Deno**: Compatible (use npm: imports)
|
|
1475
|
+
|
|
1476
|
+
---
|
|
1477
|
+
|
|
1478
|
+
## Support
|
|
1479
|
+
|
|
1480
|
+
For questions or issues:
|
|
1481
|
+
- Email: [packages@gl.life](mailto:packages@gl.life)
|
|
1482
|
+
- Website: [gl.life](https://gl.life)
|
|
1483
|
+
|
|
1484
|
+
---
|
|
1485
|
+
|
|
1486
|
+
**Part of the GoodLife Sargam ecosystem**
|