@ketrics/sdk-backend 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,238 +1,1160 @@
1
- # @ketrics/sdk
1
+ # @ketrics/sdk-backend
2
2
 
3
3
  TypeScript type definitions for building tenant applications on the Ketrics platform.
4
4
 
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- npm install @ketrics/sdk
8
+ npm install @ketrics/sdk-backend
9
9
  ```
10
10
 
11
- ## Usage
11
+ ## Overview
12
12
 
13
13
  The SDK provides TypeScript types for the global `ketrics` object that is automatically injected into your tenant application code at runtime. Everything is accessible via the `ketrics` object:
14
14
 
15
- - **Context**: `ketrics.tenant`, `ketrics.application`, `ketrics.user`, `ketrics.env`
16
- - **Utilities**: `ketrics.console`, `ketrics.http`, `ketrics.databases`
15
+ - **Context**: `ketrics.tenant`, `ketrics.application`, `ketrics.user`, `ketrics.env`, `ketrics.environment`
16
+ - **Utilities**: `ketrics.console`, `ketrics.http`
17
17
  - **Volume**: `ketrics.Volume.connect()` for S3-backed storage
18
- - **Error classes**: `ketrics.VolumeNotFoundError`, etc. for `instanceof` checks
18
+ - **Database**: `ketrics.DatabaseConnection.connect()` for external database access
19
+ - **Secrets**: `ketrics.Secret.get()` for encrypted secrets
20
+ - **Excel**: `ketrics.Excel.read()` and `ketrics.Excel.create()` for Excel files
21
+ - **PDF**: `ketrics.Pdf.read()` and `ketrics.Pdf.create()` for PDF files
22
+ - **Error classes**: `ketrics.VolumeNotFoundError`, `ketrics.DatabaseError`, etc. for `instanceof` checks
19
23
 
20
- ### Basic Example
24
+ ---
25
+
26
+ ## Quick Start
21
27
 
22
28
  ```typescript
23
29
  // Import types only if needed for type annotations
24
- import type { FileContent, IVolume } from '@ketrics/sdk';
30
+ import type { FileContent, IVolume, IDatabaseConnection } from '@ketrics/sdk-backend';
25
31
 
26
32
  export async function handler() {
27
- // The global `ketrics` object is automatically typed
33
+ // Context is always available
28
34
  console.log('Tenant:', ketrics.tenant.name);
29
35
  console.log('User:', ketrics.user.email);
30
36
  console.log('App:', ketrics.application.code);
31
37
 
32
- // Use ketrics.Volume.connect() for S3 storage
33
- try {
34
- const volume = await ketrics.Volume.connect('uploads');
35
-
36
- // Get a file
37
- const file = await volume.get('documents/report.pdf');
38
- console.log('Content-Type:', file.contentType);
39
- console.log('Size:', file.contentLength);
40
-
41
- return { success: true, size: file.contentLength };
42
- } catch (error) {
43
- // Error classes are on the ketrics object
44
- if (error instanceof ketrics.VolumeNotFoundError) {
45
- return { error: `Volume '${error.volumeCode}' not found` };
46
- }
47
- if (error instanceof ketrics.FileNotFoundError) {
48
- return { error: `File '${error.key}' not found` };
49
- }
50
- throw error;
51
- }
38
+ // Access S3 volumes
39
+ const volume = await ketrics.Volume.connect('uploads');
40
+ const file = await volume.get('report.pdf');
41
+
42
+ // Access external databases
43
+ const db = await ketrics.DatabaseConnection.connect('main-db');
44
+ const result = await db.query('SELECT * FROM users');
45
+ await db.close();
46
+
47
+ // Access encrypted secrets
48
+ const apiKey = await ketrics.Secret.get('stripe-api-key');
49
+
50
+ // Read/write Excel files
51
+ const workbook = await ketrics.Excel.read(file.content);
52
+ const sheet = workbook.getWorksheet('Sheet1');
53
+
54
+ return { success: true };
52
55
  }
53
56
  ```
54
57
 
55
- ## Available APIs
58
+ ---
59
+
60
+ ## Context Properties (Read-only)
56
61
 
57
- ### Context (Read-only)
62
+ ### Tenant Context
58
63
 
59
- Access information about the current tenant, application, user, and environment:
64
+ Access information about the current tenant:
60
65
 
61
66
  ```typescript
62
- // Tenant context
63
- ketrics.tenant.id // Tenant UUID
64
- ketrics.tenant.code // e.g., "acme-corp"
65
- ketrics.tenant.name // e.g., "ACME Corporation"
67
+ ketrics.tenant.id // Tenant UUID (e.g., "550e8400-e29b-41d4-a716-446655440000")
68
+ ketrics.tenant.code // Tenant code/slug (e.g., "acme-corp")
69
+ ketrics.tenant.name // Tenant display name (e.g., "ACME Corporation")
70
+ ```
71
+
72
+ ### Application Context
73
+
74
+ Access information about the current application:
66
75
 
67
- // Application context
76
+ ```typescript
68
77
  ketrics.application.id // Application UUID
69
- ketrics.application.code // e.g., "inventory"
70
- ketrics.application.name // e.g., "Inventory Manager"
78
+ ketrics.application.code // Application code (e.g., "inventory")
79
+ ketrics.application.name // Application display name (e.g., "Inventory Manager")
80
+ ketrics.application.version // Application version (optional)
71
81
  ketrics.application.deploymentId // Current deployment ID
82
+ ```
83
+
84
+ ### User Context
72
85
 
73
- // User context
86
+ Access information about the user making the request:
87
+
88
+ ```typescript
74
89
  ketrics.user.id // User UUID
75
- ketrics.user.email // e.g., "user@example.com"
76
- ketrics.user.name // e.g., "John Doe"
77
- ketrics.user.isAdmin // boolean
90
+ ketrics.user.email // User email (e.g., "john@example.com")
91
+ ketrics.user.name // User full name (e.g., "John Doe")
92
+ ketrics.user.isAdmin // Whether user is tenant admin (boolean)
93
+ ```
94
+
95
+ ### Environment Context
96
+
97
+ Access runtime environment information:
98
+
99
+ ```typescript
100
+ ketrics.env.nodeVersion // Node.js version (e.g., "v18.17.0")
101
+ ketrics.env.runtime // Runtime platform ("ecs-fargate")
102
+ ketrics.env.region // AWS region (e.g., "us-east-1")
103
+ ```
104
+
105
+ ### Environment Variables
106
+
107
+ Access application-level environment variables configured in the Ketrics portal:
78
108
 
79
- // Environment context
80
- ketrics.env.nodeVersion // e.g., "18.x"
81
- ketrics.env.runtime // "ecs-fargate"
82
- ketrics.env.region // e.g., "us-east-1"
109
+ ```typescript
110
+ // Access custom environment variables set for your application
111
+ const apiEndpoint = ketrics.environment['API_ENDPOINT'];
112
+ const debugMode = ketrics.environment['DEBUG_MODE'];
113
+ const maxRetries = ketrics.environment['MAX_RETRIES'];
114
+
115
+ // Example: Configure behavior based on env vars
116
+ if (ketrics.environment['FEATURE_FLAG_NEW_UI'] === 'true') {
117
+ // Use new UI logic
118
+ }
83
119
  ```
84
120
 
85
- ### Console Logging
121
+ ---
122
+
123
+ ## Console Logging
86
124
 
87
- Logs are forwarded to CloudWatch with proper context:
125
+ Logs are automatically forwarded to CloudWatch with proper context (tenant, application, user, request ID):
88
126
 
89
127
  ```typescript
90
- ketrics.console.log('Info message');
91
- ketrics.console.error('Error message');
92
- ketrics.console.warn('Warning message');
93
- ketrics.console.info('Info message');
94
- ketrics.console.debug('Debug message');
128
+ ketrics.console.log('Processing order...');
129
+ ketrics.console.info('Order received', { orderId: 123 });
130
+ ketrics.console.warn('Rate limit approaching');
131
+ ketrics.console.error('Failed to process order', { error: err.message });
132
+ ketrics.console.debug('Debug info', { state: currentState });
95
133
  ```
96
134
 
97
- ### HTTP Client
135
+ All log levels are supported:
136
+ - `log()` - General logging
137
+ - `info()` - Informational messages
138
+ - `warn()` - Warning messages
139
+ - `error()` - Error messages
140
+ - `debug()` - Debug messages (may be filtered in production)
98
141
 
99
- Make external API requests:
142
+ ---
143
+
144
+ ## HTTP Client
145
+
146
+ Make external API requests with built-in timeout and user agent:
100
147
 
101
148
  ```typescript
102
149
  // GET request
103
150
  const response = await ketrics.http.get<MyData>('https://api.example.com/data');
104
151
  console.log(response.data, response.status);
105
152
 
106
- // POST request
107
- const result = await ketrics.http.post('https://api.example.com/submit', {
108
- name: 'Test',
153
+ // POST request with body
154
+ const result = await ketrics.http.post('https://api.example.com/orders', {
155
+ product: 'Widget',
156
+ quantity: 5,
157
+ });
158
+
159
+ // PUT request
160
+ await ketrics.http.put('https://api.example.com/orders/123', {
161
+ status: 'shipped',
109
162
  });
110
163
 
111
- // With options
164
+ // DELETE request
165
+ await ketrics.http.delete('https://api.example.com/orders/123');
166
+
167
+ // With configuration options
112
168
  const configured = await ketrics.http.get('https://api.example.com/data', {
113
- headers: { 'Authorization': 'Bearer token' },
114
- timeout: 5000,
169
+ headers: {
170
+ 'Authorization': 'Bearer token123',
171
+ 'X-Custom-Header': 'value',
172
+ },
173
+ timeout: 5000, // 5 seconds
115
174
  params: { page: 1, limit: 10 },
175
+ maxRedirects: 3,
116
176
  });
117
177
  ```
118
178
 
119
- ### Volume Storage
179
+ ### Response Structure
120
180
 
121
- S3-backed file storage with granular permissions. Access via `ketrics.Volume`:
181
+ ```typescript
182
+ interface HttpResponse<T> {
183
+ data: T; // Response body
184
+ status: number; // HTTP status code (e.g., 200)
185
+ statusText: string; // Status text (e.g., "OK")
186
+ headers: Record<string, string>; // Response headers
187
+ }
188
+ ```
189
+
190
+ ---
191
+
192
+ ## Volume Storage (S3-backed)
193
+
194
+ Access S3-backed file storage with granular permissions:
195
+
196
+ ### Connecting to a Volume
122
197
 
123
198
  ```typescript
124
- // Import types only if needed for type annotations
125
- import type { FileContent, PutResult, ListResult, IVolume } from '@ketrics/sdk';
199
+ import type { IVolume, FileContent, PutResult, ListResult } from '@ketrics/sdk-backend';
126
200
 
127
- // Connect to a volume via ketrics.Volume
201
+ // Connect to a volume by code
128
202
  const volume = await ketrics.Volume.connect('uploads');
129
203
 
130
- // Check permissions
204
+ // Check volume info
131
205
  console.log(volume.code); // 'uploads'
132
206
  console.log(volume.permissions); // Set { 'ReadObject', 'CreateObject', ... }
207
+ ```
208
+
209
+ ### Reading Files
133
210
 
134
- // Read a file
211
+ ```typescript
212
+ // Get file content
135
213
  const file: FileContent = await volume.get('documents/report.pdf');
136
- console.log(file.content); // Buffer
214
+ console.log(file.content); // Buffer containing file data
137
215
  console.log(file.contentType); // 'application/pdf'
138
- console.log(file.contentLength); // 12345
139
- console.log(file.lastModified); // Date
140
- console.log(file.etag); // 'abc123'
216
+ console.log(file.contentLength); // 12345 (bytes)
217
+ console.log(file.lastModified); // Date object
218
+ console.log(file.etag); // '"abc123..."'
219
+ console.log(file.metadata); // { author: 'john@example.com' }
220
+
221
+ // Check if file exists (without downloading)
222
+ const exists = await volume.exists('config.json');
141
223
 
142
- // Write a file
143
- const result: PutResult = await volume.put('output/data.json', JSON.stringify(data), {
224
+ // Get file metadata only (no content download)
225
+ const meta = await volume.getMetadata('large-file.zip');
226
+ console.log(meta.size, meta.contentType, meta.lastModified);
227
+ ```
228
+
229
+ ### Writing Files
230
+
231
+ ```typescript
232
+ // Write text/JSON content
233
+ const result = await volume.put('output/data.json', JSON.stringify(data), {
144
234
  contentType: 'application/json',
145
- metadata: { author: ketrics.user.email },
146
235
  });
147
236
  console.log(result.key, result.etag, result.size);
148
237
 
149
- // Check if file exists
150
- const exists = await volume.exists('config.json');
238
+ // Write binary content
239
+ const buffer = Buffer.from('Hello World');
240
+ await volume.put('files/hello.txt', buffer);
151
241
 
152
- // Get file metadata (without downloading content)
153
- const meta = await volume.getMetadata('large-file.zip');
154
- console.log(meta.size, meta.contentType);
242
+ // Write with metadata
243
+ await volume.put('documents/report.pdf', pdfBuffer, {
244
+ contentType: 'application/pdf',
245
+ metadata: {
246
+ author: ketrics.user.email,
247
+ version: '1.0',
248
+ },
249
+ });
155
250
 
156
- // List files
157
- const list: ListResult = await volume.list({ prefix: 'documents/', maxResults: 100 });
251
+ // Create only if file doesn't exist
252
+ try {
253
+ await volume.put('config.json', defaultConfig, { ifNotExists: true });
254
+ } catch (error) {
255
+ if (error instanceof ketrics.FileAlreadyExistsError) {
256
+ console.log('Config already exists, skipping');
257
+ }
258
+ }
259
+ ```
260
+
261
+ ### Listing Files
262
+
263
+ ```typescript
264
+ // List all files
265
+ const list = await volume.list();
158
266
  for (const file of list.files) {
159
267
  console.log(file.key, file.size, file.lastModified);
160
268
  }
161
- if (list.isTruncated) {
162
- // Fetch next page
163
- const nextPage = await volume.list({
164
- prefix: 'documents/',
165
- continuationToken: list.continuationToken,
269
+
270
+ // List with prefix filter
271
+ const docs = await volume.list({ prefix: 'documents/' });
272
+
273
+ // Paginated listing
274
+ const page1 = await volume.list({ maxResults: 100 });
275
+ if (page1.isTruncated) {
276
+ const page2 = await volume.list({
277
+ maxResults: 100,
278
+ continuationToken: page1.continuationToken,
166
279
  });
167
280
  }
168
281
 
169
- // Delete a file
282
+ // Hierarchical listing with delimiter
283
+ const folders = await volume.list({ delimiter: '/' });
284
+ console.log(folders.folders); // ['documents/', 'images/', 'uploads/']
285
+ ```
286
+
287
+ ### Deleting Files
288
+
289
+ ```typescript
290
+ // Delete single file
170
291
  await volume.delete('temp/file.txt');
171
292
 
172
293
  // Delete by prefix (batch delete)
173
- const deleteResult = await volume.deleteByPrefix('temp/');
174
- console.log(`Deleted ${deleteResult.deletedCount} files`);
294
+ const result = await volume.deleteByPrefix('temp/');
295
+ console.log(`Deleted ${result.deletedCount} files`);
296
+ console.log('Deleted keys:', result.deletedKeys);
297
+ ```
298
+
299
+ ### Copying and Moving Files
175
300
 
301
+ ```typescript
176
302
  // Copy a file
177
303
  await volume.copy('source.pdf', 'backup/source.pdf');
178
304
 
179
- // Move a file
305
+ // Copy with metadata replacement
306
+ await volume.copy('source.pdf', 'archive/source.pdf', {
307
+ metadataDirective: 'REPLACE',
308
+ metadata: { archived: 'true', archivedBy: ketrics.user.email },
309
+ });
310
+
311
+ // Move a file (copy + delete)
180
312
  await volume.move('temp/upload.pdf', 'documents/final.pdf');
313
+ ```
314
+
315
+ ### Generating Presigned URLs
181
316
 
182
- // Generate presigned URLs
317
+ ```typescript
318
+ // Generate download URL (for sharing)
183
319
  const downloadUrl = await volume.generateDownloadUrl('report.pdf', {
184
320
  expiresIn: 3600, // 1 hour
185
321
  responseContentDisposition: 'attachment; filename="report.pdf"',
186
322
  });
187
- console.log(downloadUrl.url, downloadUrl.expiresAt);
323
+ console.log(downloadUrl.url); // Presigned S3 URL
324
+ console.log(downloadUrl.expiresAt); // Expiration Date
188
325
 
326
+ // Generate upload URL (for direct client uploads)
189
327
  const uploadUrl = await volume.generateUploadUrl('uploads/new-file.pdf', {
190
328
  expiresIn: 3600,
191
329
  contentType: 'application/pdf',
330
+ maxSize: 10 * 1024 * 1024, // 10MB limit
192
331
  });
193
332
  // Client can PUT directly to uploadUrl.url
194
333
  ```
195
334
 
196
- ### Database Connections
335
+ ---
336
+
337
+ ## Database Connections
338
+
339
+ Access external databases (PostgreSQL, MySQL, MSSQL) with connection pooling:
340
+
341
+ ### Connecting to a Database
342
+
343
+ ```typescript
344
+ import type { IDatabaseConnection, DatabaseQueryResult } from '@ketrics/sdk-backend';
345
+
346
+ // Connect to a database by code
347
+ const db = await ketrics.DatabaseConnection.connect('main-db');
348
+
349
+ // Check connection info
350
+ console.log(db.code); // 'main-db'
351
+ console.log(db.permissions); // Set { 'query', 'execute', ... }
352
+ ```
353
+
354
+ ### Querying Data
355
+
356
+ ```typescript
357
+ interface User {
358
+ id: number;
359
+ name: string;
360
+ email: string;
361
+ created_at: Date;
362
+ }
363
+
364
+ // Simple query
365
+ const result = await db.query<User>('SELECT * FROM users');
366
+ console.log(result.rows); // [{ id: 1, name: 'John', ... }, ...]
367
+ console.log(result.rowCount); // 10
368
+
369
+ // Query with parameters (prevents SQL injection)
370
+ const users = await db.query<User>(
371
+ 'SELECT * FROM users WHERE age > ? AND status = ?',
372
+ [18, 'active']
373
+ );
374
+
375
+ // Query single record
376
+ const user = await db.query<User>(
377
+ 'SELECT * FROM users WHERE id = ?',
378
+ [userId]
379
+ );
380
+ if (user.rows.length > 0) {
381
+ console.log('Found user:', user.rows[0].name);
382
+ }
383
+ ```
197
384
 
198
- Access external databases:
385
+ ### Executing Statements
199
386
 
200
387
  ```typescript
201
- const db = await ketrics.databases.connect('main-db');
202
- if (db) {
203
- // SELECT query
204
- const result = await db.query<User>('SELECT * FROM users WHERE id = ?', [userId]);
205
- console.log(result.rows, result.rowCount);
388
+ // INSERT
389
+ const insertResult = await db.execute(
390
+ 'INSERT INTO users (name, email) VALUES (?, ?)',
391
+ ['John Doe', 'john@example.com']
392
+ );
393
+ console.log(insertResult.affectedRows); // 1
394
+ console.log(insertResult.insertId); // 123 (auto-increment ID)
395
+
396
+ // UPDATE
397
+ const updateResult = await db.execute(
398
+ 'UPDATE users SET status = ? WHERE id = ?',
399
+ ['inactive', userId]
400
+ );
401
+ console.log(updateResult.affectedRows); // Number of rows updated
402
+
403
+ // DELETE
404
+ const deleteResult = await db.execute(
405
+ 'DELETE FROM users WHERE status = ?',
406
+ ['deleted']
407
+ );
408
+ console.log(deleteResult.affectedRows); // Number of rows deleted
409
+ ```
410
+
411
+ ### Transactions
206
412
 
207
- // INSERT/UPDATE/DELETE
208
- const execResult = await db.execute(
209
- 'INSERT INTO logs (message) VALUES (?)',
210
- ['User logged in']
413
+ ```typescript
414
+ // Automatic commit on success, rollback on error
415
+ const result = await db.transaction(async (tx) => {
416
+ // Debit from account
417
+ await tx.execute(
418
+ 'UPDATE accounts SET balance = balance - ? WHERE id = ?',
419
+ [100, fromAccountId]
211
420
  );
212
- console.log(execResult.affectedRows, execResult.insertId);
213
421
 
214
- // Transaction
215
- const transactionResult = await db.transaction(async (tx) => {
216
- await tx.execute('UPDATE accounts SET balance = balance - ? WHERE id = ?', [100, fromId]);
217
- await tx.execute('UPDATE accounts SET balance = balance + ? WHERE id = ?', [100, toId]);
218
- return { success: true };
422
+ // Credit to account
423
+ await tx.execute(
424
+ 'UPDATE accounts SET balance = balance + ? WHERE id = ?',
425
+ [100, toAccountId]
426
+ );
427
+
428
+ // Record transfer
429
+ const transfer = await tx.execute(
430
+ 'INSERT INTO transfers (from_id, to_id, amount) VALUES (?, ?, ?)',
431
+ [fromAccountId, toAccountId, 100]
432
+ );
433
+
434
+ return { transferId: transfer.insertId };
435
+ });
436
+
437
+ console.log('Transfer completed:', result.transferId);
438
+ ```
439
+
440
+ ### Closing Connections
441
+
442
+ ```typescript
443
+ // Always close when done to return connection to pool
444
+ const db = await ketrics.DatabaseConnection.connect('main-db');
445
+ try {
446
+ const result = await db.query('SELECT * FROM users');
447
+ return result.rows;
448
+ } finally {
449
+ await db.close();
450
+ }
451
+ ```
452
+
453
+ ---
454
+
455
+ ## Secrets
456
+
457
+ Access encrypted secrets stored with tenant-specific KMS encryption:
458
+
459
+ ### Getting Secrets
460
+
461
+ ```typescript
462
+ // Get a secret value
463
+ const apiKey = await ketrics.Secret.get('stripe-api-key');
464
+
465
+ // Use the secret (value is decrypted automatically)
466
+ const response = await ketrics.http.post('https://api.stripe.com/v1/charges', data, {
467
+ headers: { 'Authorization': `Bearer ${apiKey}` },
468
+ });
469
+ ```
470
+
471
+ ### Checking Secret Existence
472
+
473
+ ```typescript
474
+ // Check if a secret exists before using it
475
+ if (await ketrics.Secret.exists('optional-webhook-secret')) {
476
+ const secret = await ketrics.Secret.get('optional-webhook-secret');
477
+ // Use the optional secret
478
+ } else {
479
+ // Use default behavior
480
+ }
481
+ ```
482
+
483
+ ### Common Patterns
484
+
485
+ ```typescript
486
+ // Database password from secret
487
+ const dbPassword = await ketrics.Secret.get('db-password');
488
+
489
+ // API keys
490
+ const stripeKey = await ketrics.Secret.get('stripe-api-key');
491
+ const sendgridKey = await ketrics.Secret.get('sendgrid-api-key');
492
+
493
+ // OAuth credentials
494
+ const clientId = await ketrics.Secret.get('oauth-client-id');
495
+ const clientSecret = await ketrics.Secret.get('oauth-client-secret');
496
+ ```
497
+
498
+ ---
499
+
500
+ ## Excel Files
501
+
502
+ Read and write Excel files (.xlsx) using a simple API:
503
+
504
+ ### Reading Excel Files
505
+
506
+ ```typescript
507
+ import type { IExcelWorkbook, IExcelWorksheet, IExcelRow } from '@ketrics/sdk-backend';
508
+
509
+ // Read from volume
510
+ const volume = await ketrics.Volume.connect('uploads');
511
+ const file = await volume.get('data/report.xlsx');
512
+
513
+ // Parse Excel file
514
+ const workbook = await ketrics.Excel.read(file.content);
515
+
516
+ // Get worksheet by name
517
+ const sheet = workbook.getWorksheet('Sheet1');
518
+ if (!sheet) {
519
+ throw new Error('Sheet not found');
520
+ }
521
+
522
+ // Get worksheet by index (1-indexed)
523
+ const firstSheet = workbook.getWorksheet(1);
524
+
525
+ // List all worksheets
526
+ console.log(`Workbook has ${workbook.worksheetCount} sheets`);
527
+ for (const ws of workbook.worksheets) {
528
+ console.log(`- ${ws.name}: ${ws.actualRowCount} rows`);
529
+ }
530
+ ```
531
+
532
+ ### Reading Rows and Cells
533
+
534
+ ```typescript
535
+ // Get all rows with values
536
+ const rows = sheet.getRows();
537
+ for (const row of rows) {
538
+ console.log('Row', row.number, ':', row.values);
539
+ }
540
+
541
+ // Get a specific row (1-indexed)
542
+ const headerRow = sheet.getRow(1);
543
+ console.log('Headers:', headerRow.values);
544
+
545
+ // Get a specific cell
546
+ const cell = sheet.getCell('A1');
547
+ console.log(cell.value, cell.text, cell.address);
548
+
549
+ // Get cell by coordinates
550
+ const cellB2 = sheet.getCell(2, 2);
551
+
552
+ // Iterate over rows
553
+ sheet.eachRow((row, rowNumber) => {
554
+ console.log(`Row ${rowNumber}:`, row.values);
555
+ });
556
+
557
+ // Include empty rows
558
+ sheet.eachRow((row, rowNumber) => {
559
+ console.log(`Row ${rowNumber}:`, row.values);
560
+ }, { includeEmpty: true });
561
+
562
+ // Get all values as 2D array
563
+ const allValues = sheet.getSheetValues();
564
+ // allValues[1] = first row, allValues[1][1] = cell A1
565
+ ```
566
+
567
+ ### Working with Cells
568
+
569
+ ```typescript
570
+ // Get cell properties
571
+ const cell = sheet.getCell('B5');
572
+ console.log(cell.value); // Cell value (number, string, Date, etc.)
573
+ console.log(cell.text); // Text representation
574
+ console.log(cell.address); // 'B5'
575
+ console.log(cell.row); // 5
576
+ console.log(cell.col); // 2
577
+ console.log(cell.formula); // Formula if present (e.g., '=SUM(A1:A10)')
578
+ console.log(cell.type); // Cell type
579
+
580
+ // Iterate over cells in a row
581
+ const row = sheet.getRow(1);
582
+ row.eachCell((cell, colNumber) => {
583
+ console.log(`Column ${colNumber}:`, cell.value);
584
+ });
585
+ ```
586
+
587
+ ### Creating Excel Files
588
+
589
+ ```typescript
590
+ // Create new workbook
591
+ const workbook = ketrics.Excel.create();
592
+
593
+ // Add worksheet
594
+ const sheet = workbook.addWorksheet('Sales Report', {
595
+ tabColor: 'FF0000', // Red tab
596
+ defaultRowHeight: 20,
597
+ defaultColWidth: 15,
598
+ });
599
+
600
+ // Define columns (optional)
601
+ sheet.columns = [
602
+ { header: 'ID', key: 'id', width: 10 },
603
+ { header: 'Product', key: 'product', width: 30 },
604
+ { header: 'Price', key: 'price', width: 15 },
605
+ { header: 'Quantity', key: 'qty', width: 10 },
606
+ ];
607
+
608
+ // Add rows
609
+ sheet.addRow(['1', 'Widget A', 9.99, 100]);
610
+ sheet.addRow(['2', 'Widget B', 19.99, 50]);
611
+ sheet.addRow(['3', 'Widget C', 29.99, 25]);
612
+
613
+ // Add multiple rows at once
614
+ sheet.addRows([
615
+ ['4', 'Widget D', 39.99, 10],
616
+ ['5', 'Widget E', 49.99, 5],
617
+ ]);
618
+
619
+ // Insert row at position
620
+ sheet.insertRow(2, ['INSERTED', 'Row', 0, 0]);
621
+
622
+ // Merge cells
623
+ sheet.mergeCells('A1', 'D1'); // Merge A1:D1
624
+ sheet.mergeCells(5, 1, 5, 4); // Merge row 5, columns 1-4
625
+
626
+ // Write to buffer
627
+ const buffer = await workbook.toBuffer();
628
+
629
+ // Save to volume
630
+ await volume.put('output/report.xlsx', buffer, {
631
+ contentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
632
+ });
633
+ ```
634
+
635
+ ### Converting to CSV
636
+
637
+ ```typescript
638
+ // Convert worksheet to CSV
639
+ const csvContent = await workbook.toCsv('Sheet1');
640
+
641
+ // Save as CSV
642
+ await volume.put('output/data.csv', csvContent, {
643
+ contentType: 'text/csv',
644
+ });
645
+ ```
646
+
647
+ ### Managing Worksheets
648
+
649
+ ```typescript
650
+ // Remove worksheet
651
+ workbook.removeWorksheet('TempSheet');
652
+ workbook.removeWorksheet(2); // By index
653
+
654
+ // Rename worksheet
655
+ const sheet = workbook.getWorksheet('OldName');
656
+ if (sheet) {
657
+ sheet.name = 'NewName';
658
+ }
659
+ ```
660
+
661
+ ---
662
+
663
+ ## PDF Files
664
+
665
+ Read, create, and modify PDF files using the pdf-lib wrapper:
666
+
667
+ ### Reading PDF Files
668
+
669
+ ```typescript
670
+ import type { IPdfDocument, IPdfPage } from '@ketrics/sdk-backend';
671
+
672
+ // Read from volume
673
+ const volume = await ketrics.Volume.connect('uploads');
674
+ const file = await volume.get('documents/report.pdf');
675
+
676
+ // Parse PDF file
677
+ const doc = await ketrics.Pdf.read(file.content);
678
+
679
+ // Get document info
680
+ console.log('Page count:', doc.getPageCount());
681
+ console.log('Title:', doc.getTitle());
682
+ console.log('Author:', doc.getAuthor());
683
+
684
+ // Get all pages
685
+ const pages = doc.getPages();
686
+ for (const page of pages) {
687
+ console.log(`Page size: ${page.width}x${page.height}`);
688
+ }
689
+
690
+ // Get specific page (0-indexed)
691
+ const firstPage = doc.getPage(0);
692
+ console.log('First page size:', firstPage.getSize());
693
+ ```
694
+
695
+ ### Creating PDF Files
696
+
697
+ ```typescript
698
+ // Create new empty document
699
+ const doc = await ketrics.Pdf.create();
700
+
701
+ // Set document metadata
702
+ doc.setTitle('Sales Report Q4 2024');
703
+ doc.setAuthor(ketrics.user.name);
704
+ doc.setSubject('Quarterly sales data');
705
+ doc.setKeywords(['sales', 'report', 'Q4', '2024']);
706
+ doc.setCreator('Ketrics Application');
707
+
708
+ // Add pages with different sizes
709
+ const pageA4 = doc.addPage('A4'); // Standard A4
710
+ const pageLetter = doc.addPage('Letter'); // US Letter
711
+ const pageCustom = doc.addPage([600, 400]); // Custom size in points
712
+
713
+ // Insert page at specific position (0-indexed)
714
+ const insertedPage = doc.insertPage(1, 'A4');
715
+
716
+ // Remove a page
717
+ doc.removePage(2);
718
+
719
+ // Write to buffer
720
+ const buffer = await doc.toBuffer();
721
+
722
+ // Save to volume
723
+ await volume.put('output/report.pdf', buffer, {
724
+ contentType: 'application/pdf',
725
+ });
726
+ ```
727
+
728
+ ### Drawing Text
729
+
730
+ ```typescript
731
+ const doc = await ketrics.Pdf.create();
732
+ const page = doc.addPage('A4');
733
+
734
+ // Simple text (default: Helvetica, 12pt, black)
735
+ await page.drawText('Hello, World!', { x: 50, y: 700 });
736
+
737
+ // Styled text
738
+ await page.drawText('Large Red Title', {
739
+ x: 50,
740
+ y: 750,
741
+ size: 32,
742
+ color: ketrics.Pdf.rgb(1, 0, 0), // Red (RGB values 0-1)
743
+ });
744
+
745
+ // Text with custom font
746
+ const boldFont = await doc.embedStandardFont('HelveticaBold');
747
+ await page.drawText('Bold Text', {
748
+ x: 50,
749
+ y: 650,
750
+ size: 16,
751
+ font: boldFont,
752
+ });
753
+
754
+ // Rotated text
755
+ await page.drawText('Rotated', {
756
+ x: 200,
757
+ y: 500,
758
+ size: 14,
759
+ rotate: 45, // degrees
760
+ });
761
+
762
+ // Text with opacity
763
+ await page.drawText('Semi-transparent', {
764
+ x: 50,
765
+ y: 600,
766
+ size: 14,
767
+ opacity: 0.5,
768
+ });
769
+
770
+ // Multiline text with wrapping
771
+ await page.drawText('This is a long paragraph that will wrap to multiple lines when it exceeds the maximum width.', {
772
+ x: 50,
773
+ y: 550,
774
+ size: 12,
775
+ maxWidth: 200,
776
+ lineHeight: 16,
777
+ });
778
+ ```
779
+
780
+ ### Drawing Shapes
781
+
782
+ ```typescript
783
+ const doc = await ketrics.Pdf.create();
784
+ const page = doc.addPage('A4');
785
+
786
+ // Rectangle (filled)
787
+ page.drawRectangle({
788
+ x: 50,
789
+ y: 600,
790
+ width: 200,
791
+ height: 100,
792
+ color: ketrics.Pdf.rgb(0.2, 0.4, 0.8), // Blue fill
793
+ });
794
+
795
+ // Rectangle (outlined)
796
+ page.drawRectangle({
797
+ x: 50,
798
+ y: 480,
799
+ width: 200,
800
+ height: 100,
801
+ borderColor: ketrics.Pdf.rgb(0, 0, 0),
802
+ borderWidth: 2,
803
+ });
804
+
805
+ // Rectangle (filled with border)
806
+ page.drawRectangle({
807
+ x: 50,
808
+ y: 360,
809
+ width: 200,
810
+ height: 100,
811
+ color: ketrics.Pdf.rgb(0.9, 0.9, 0.9), // Light gray fill
812
+ borderColor: ketrics.Pdf.rgb(0, 0, 0),
813
+ borderWidth: 1,
814
+ opacity: 0.8,
815
+ });
816
+
817
+ // Line
818
+ page.drawLine({
819
+ start: { x: 50, y: 340 },
820
+ end: { x: 250, y: 340 },
821
+ thickness: 2,
822
+ color: ketrics.Pdf.rgb(0, 0, 0),
823
+ });
824
+
825
+ // Circle
826
+ page.drawCircle({
827
+ x: 150,
828
+ y: 250,
829
+ radius: 50,
830
+ color: ketrics.Pdf.rgb(0.8, 0.2, 0.2), // Red fill
831
+ borderColor: ketrics.Pdf.rgb(0, 0, 0),
832
+ borderWidth: 2,
833
+ });
834
+ ```
835
+
836
+ ### Embedding Images
837
+
838
+ ```typescript
839
+ const doc = await ketrics.Pdf.create();
840
+ const page = doc.addPage('A4');
841
+
842
+ // Read image from volume
843
+ const volume = await ketrics.Volume.connect('uploads');
844
+ const logoFile = await volume.get('images/logo.png');
845
+ const photoFile = await volume.get('images/photo.jpg');
846
+
847
+ // Embed PNG image
848
+ const pngImage = await doc.embedPng(logoFile.content);
849
+ console.log('PNG dimensions:', pngImage.width, 'x', pngImage.height);
850
+
851
+ // Embed JPG image
852
+ const jpgImage = await doc.embedJpg(photoFile.content);
853
+
854
+ // Draw image at original size
855
+ page.drawImage(pngImage, { x: 50, y: 700 });
856
+
857
+ // Draw image at specific size
858
+ page.drawImage(jpgImage, {
859
+ x: 50,
860
+ y: 500,
861
+ width: 200,
862
+ height: 150,
863
+ });
864
+
865
+ // Draw image with opacity
866
+ page.drawImage(pngImage, {
867
+ x: 300,
868
+ y: 700,
869
+ opacity: 0.5,
870
+ });
871
+
872
+ // Scale image to fit within bounds
873
+ const scaled = pngImage.scaleToFit(150, 100);
874
+ page.drawImage(pngImage, {
875
+ x: 50,
876
+ y: 350,
877
+ width: scaled.width,
878
+ height: scaled.height,
879
+ });
880
+
881
+ // Scale image by factor
882
+ const factor = pngImage.scale(0.5);
883
+ page.drawImage(pngImage, {
884
+ x: 250,
885
+ y: 350,
886
+ width: factor.width,
887
+ height: factor.height,
888
+ });
889
+ ```
890
+
891
+ ### Working with Fonts
892
+
893
+ ```typescript
894
+ const doc = await ketrics.Pdf.create();
895
+ const page = doc.addPage('A4');
896
+
897
+ // Embed standard fonts (14 built-in fonts available)
898
+ const helvetica = await doc.embedStandardFont('Helvetica');
899
+ const helveticaBold = await doc.embedStandardFont('HelveticaBold');
900
+ const timesRoman = await doc.embedStandardFont('TimesRoman');
901
+ const courier = await doc.embedStandardFont('Courier');
902
+
903
+ // Use different fonts
904
+ await page.drawText('Helvetica Regular', { x: 50, y: 750, font: helvetica, size: 14 });
905
+ await page.drawText('Helvetica Bold', { x: 50, y: 720, font: helveticaBold, size: 14 });
906
+ await page.drawText('Times Roman', { x: 50, y: 690, font: timesRoman, size: 14 });
907
+ await page.drawText('Courier', { x: 50, y: 660, font: courier, size: 14 });
908
+
909
+ // Calculate text width for positioning
910
+ const text = 'Right-aligned text';
911
+ const textWidth = helvetica.widthOfTextAtSize(text, 14);
912
+ await page.drawText(text, {
913
+ x: page.width - 50 - textWidth, // Right-align with 50pt margin
914
+ y: 600,
915
+ font: helvetica,
916
+ size: 14,
917
+ });
918
+
919
+ // Get font height
920
+ const lineHeight = helvetica.heightAtSize(14);
921
+ console.log('Line height:', lineHeight);
922
+
923
+ // Embed custom font from file
924
+ const customFontFile = await volume.get('fonts/OpenSans-Regular.ttf');
925
+ const customFont = await doc.embedFont(customFontFile.content);
926
+ await page.drawText('Custom Font', { x: 50, y: 550, font: customFont, size: 14 });
927
+ ```
928
+
929
+ ### Available Standard Fonts
930
+
931
+ | Font Name | Description |
932
+ |-----------|-------------|
933
+ | `Helvetica` | Sans-serif regular |
934
+ | `HelveticaBold` | Sans-serif bold |
935
+ | `HelveticaOblique` | Sans-serif italic |
936
+ | `HelveticaBoldOblique` | Sans-serif bold italic |
937
+ | `TimesRoman` | Serif regular |
938
+ | `TimesRomanBold` | Serif bold |
939
+ | `TimesRomanItalic` | Serif italic |
940
+ | `TimesRomanBoldItalic` | Serif bold italic |
941
+ | `Courier` | Monospace regular |
942
+ | `CourierBold` | Monospace bold |
943
+ | `CourierOblique` | Monospace italic |
944
+ | `CourierBoldOblique` | Monospace bold italic |
945
+ | `Symbol` | Symbol characters |
946
+ | `ZapfDingbats` | Decorative symbols |
947
+
948
+ ### Page Sizes
949
+
950
+ | Size | Dimensions (points) |
951
+ |------|---------------------|
952
+ | `A4` | 595 x 842 |
953
+ | `A3` | 842 x 1191 |
954
+ | `A5` | 420 x 595 |
955
+ | `Letter` | 612 x 792 |
956
+ | `Legal` | 612 x 1008 |
957
+ | `Tabloid` | 792 x 1224 |
958
+
959
+ ### Merging PDF Documents
960
+
961
+ ```typescript
962
+ // Read source documents
963
+ const volume = await ketrics.Volume.connect('uploads');
964
+ const file1 = await volume.get('doc1.pdf');
965
+ const file2 = await volume.get('doc2.pdf');
966
+
967
+ const doc1 = await ketrics.Pdf.read(file1.content);
968
+ const doc2 = await ketrics.Pdf.read(file2.content);
969
+
970
+ // Create merged document
971
+ const mergedDoc = await ketrics.Pdf.create();
972
+ mergedDoc.setTitle('Merged Document');
973
+
974
+ // Copy all pages from doc1
975
+ const doc1Pages = await mergedDoc.copyPages(doc1,
976
+ Array.from({ length: doc1.getPageCount() }, (_, i) => i)
977
+ );
978
+
979
+ // Copy specific pages from doc2 (pages 0 and 2)
980
+ const doc2Pages = await mergedDoc.copyPages(doc2, [0, 2]);
981
+
982
+ // Save merged document
983
+ const buffer = await mergedDoc.toBuffer();
984
+ await volume.put('merged.pdf', buffer, {
985
+ contentType: 'application/pdf',
986
+ });
987
+ ```
988
+
989
+ ### Modifying Existing PDFs
990
+
991
+ ```typescript
992
+ // Read existing PDF
993
+ const volume = await ketrics.Volume.connect('uploads');
994
+ const file = await volume.get('template.pdf');
995
+ const doc = await ketrics.Pdf.read(file.content);
996
+
997
+ // Get first page and add watermark
998
+ const page = doc.getPage(0);
999
+ await page.drawText('CONFIDENTIAL', {
1000
+ x: page.width / 2 - 100,
1001
+ y: page.height / 2,
1002
+ size: 48,
1003
+ color: ketrics.Pdf.rgb(0.9, 0.1, 0.1),
1004
+ opacity: 0.3,
1005
+ rotate: 45,
1006
+ });
1007
+
1008
+ // Add footer to all pages
1009
+ const pages = doc.getPages();
1010
+ for (let i = 0; i < pages.length; i++) {
1011
+ const p = pages[i];
1012
+ await p.drawText(`Page ${i + 1} of ${pages.length}`, {
1013
+ x: p.width / 2 - 30,
1014
+ y: 30,
1015
+ size: 10,
1016
+ color: ketrics.Pdf.rgb(0.5, 0.5, 0.5),
219
1017
  });
1018
+ }
220
1019
 
221
- // Always close when done
1020
+ // Save modified document
1021
+ const buffer = await doc.toBuffer();
1022
+ await volume.put('modified.pdf', buffer, {
1023
+ contentType: 'application/pdf',
1024
+ });
1025
+ ```
1026
+
1027
+ ### Complete PDF Generation Example
1028
+
1029
+ ```typescript
1030
+ export async function generateInvoice(orderId: string) {
1031
+ // Get order data from database
1032
+ const db = await ketrics.DatabaseConnection.connect('orders-db');
1033
+ const orderResult = await db.query(
1034
+ 'SELECT * FROM orders WHERE id = ?',
1035
+ [orderId]
1036
+ );
1037
+ const order = orderResult.rows[0];
222
1038
  await db.close();
1039
+
1040
+ // Create PDF
1041
+ const doc = await ketrics.Pdf.create();
1042
+ doc.setTitle(`Invoice #${order.invoice_number}`);
1043
+ doc.setAuthor(ketrics.tenant.name);
1044
+
1045
+ const page = doc.addPage('A4');
1046
+ const { width, height } = page.getSize();
1047
+
1048
+ // Embed fonts
1049
+ const boldFont = await doc.embedStandardFont('HelveticaBold');
1050
+ const regularFont = await doc.embedStandardFont('Helvetica');
1051
+
1052
+ // Header
1053
+ await page.drawText(ketrics.tenant.name, {
1054
+ x: 50,
1055
+ y: height - 50,
1056
+ size: 24,
1057
+ font: boldFont,
1058
+ });
1059
+
1060
+ await page.drawText(`Invoice #${order.invoice_number}`, {
1061
+ x: 50,
1062
+ y: height - 90,
1063
+ size: 18,
1064
+ font: regularFont,
1065
+ color: ketrics.Pdf.rgb(0.4, 0.4, 0.4),
1066
+ });
1067
+
1068
+ // Customer info
1069
+ await page.drawText('Bill To:', {
1070
+ x: 50,
1071
+ y: height - 150,
1072
+ size: 12,
1073
+ font: boldFont,
1074
+ });
1075
+
1076
+ await page.drawText(order.customer_name, {
1077
+ x: 50,
1078
+ y: height - 170,
1079
+ size: 12,
1080
+ font: regularFont,
1081
+ });
1082
+
1083
+ // Line items header
1084
+ const tableTop = height - 250;
1085
+ page.drawRectangle({
1086
+ x: 50,
1087
+ y: tableTop - 5,
1088
+ width: width - 100,
1089
+ height: 25,
1090
+ color: ketrics.Pdf.rgb(0.9, 0.9, 0.9),
1091
+ });
1092
+
1093
+ await page.drawText('Item', { x: 55, y: tableTop, size: 10, font: boldFont });
1094
+ await page.drawText('Qty', { x: 300, y: tableTop, size: 10, font: boldFont });
1095
+ await page.drawText('Price', { x: 380, y: tableTop, size: 10, font: boldFont });
1096
+ await page.drawText('Total', { x: 460, y: tableTop, size: 10, font: boldFont });
1097
+
1098
+ // Line items (simplified example)
1099
+ let yPos = tableTop - 30;
1100
+ for (const item of order.items) {
1101
+ await page.drawText(item.name, { x: 55, y: yPos, size: 10, font: regularFont });
1102
+ await page.drawText(String(item.quantity), { x: 300, y: yPos, size: 10, font: regularFont });
1103
+ await page.drawText(`$${item.price.toFixed(2)}`, { x: 380, y: yPos, size: 10, font: regularFont });
1104
+ await page.drawText(`$${(item.quantity * item.price).toFixed(2)}`, { x: 460, y: yPos, size: 10, font: regularFont });
1105
+ yPos -= 20;
1106
+ }
1107
+
1108
+ // Total line
1109
+ page.drawLine({
1110
+ start: { x: 380, y: yPos + 5 },
1111
+ end: { x: width - 50, y: yPos + 5 },
1112
+ thickness: 1,
1113
+ color: ketrics.Pdf.rgb(0, 0, 0),
1114
+ });
1115
+
1116
+ await page.drawText('Total:', { x: 380, y: yPos - 15, size: 12, font: boldFont });
1117
+ await page.drawText(`$${order.total.toFixed(2)}`, { x: 460, y: yPos - 15, size: 12, font: boldFont });
1118
+
1119
+ // Footer
1120
+ await page.drawText(`Generated on ${new Date().toLocaleDateString()}`, {
1121
+ x: 50,
1122
+ y: 30,
1123
+ size: 8,
1124
+ color: ketrics.Pdf.rgb(0.6, 0.6, 0.6),
1125
+ });
1126
+
1127
+ // Save to volume
1128
+ const volume = await ketrics.Volume.connect('invoices');
1129
+ const buffer = await doc.toBuffer();
1130
+ await volume.put(`${order.invoice_number}.pdf`, buffer, {
1131
+ contentType: 'application/pdf',
1132
+ metadata: {
1133
+ orderId: order.id,
1134
+ generatedBy: ketrics.user.email,
1135
+ },
1136
+ });
1137
+
1138
+ return {
1139
+ success: true,
1140
+ invoiceNumber: order.invoice_number,
1141
+ };
223
1142
  }
224
1143
  ```
225
1144
 
1145
+ ---
1146
+
226
1147
  ## Error Handling
227
1148
 
228
- Error classes are available on the `ketrics` object for `instanceof` checks:
1149
+ All error classes are available on the `ketrics` object for `instanceof` checks:
1150
+
1151
+ ### Volume Errors
229
1152
 
230
1153
  ```typescript
231
1154
  try {
232
1155
  const volume = await ketrics.Volume.connect('uploads');
233
1156
  const file = await volume.get('missing.txt');
234
1157
  } catch (error) {
235
- // Error classes are on the ketrics object
236
1158
  if (error instanceof ketrics.VolumeNotFoundError) {
237
1159
  console.log(`Volume '${error.volumeCode}' not found`);
238
1160
  } else if (error instanceof ketrics.VolumeAccessDeniedError) {
@@ -259,19 +1181,152 @@ try {
259
1181
  }
260
1182
  ```
261
1183
 
262
- ### Available Error Classes (on `ketrics` object)
1184
+ ### Database Errors
1185
+
1186
+ ```typescript
1187
+ try {
1188
+ const db = await ketrics.DatabaseConnection.connect('main-db');
1189
+ await db.query('SELECT * FROM users');
1190
+ } catch (error) {
1191
+ if (error instanceof ketrics.DatabaseNotFoundError) {
1192
+ console.log(`Database '${error.databaseCode}' not found`);
1193
+ } else if (error instanceof ketrics.DatabaseAccessDeniedError) {
1194
+ console.log(`No access to database '${error.databaseCode}'`);
1195
+ } else if (error instanceof ketrics.DatabaseConnectionError) {
1196
+ console.log(`Connection failed: ${error.reason}`);
1197
+ } else if (error instanceof ketrics.DatabaseQueryError) {
1198
+ console.log(`Query failed: ${error.reason}`);
1199
+ if (error.sql) console.log(`SQL: ${error.sql}`);
1200
+ } else if (error instanceof ketrics.DatabaseTransactionError) {
1201
+ console.log(`Transaction failed: ${error.reason}`);
1202
+ console.log(`Rolled back: ${error.rolledBack}`);
1203
+ } else if (error instanceof ketrics.DatabaseError) {
1204
+ // Base class catches all database errors
1205
+ console.log(`Database error: ${error.message}`);
1206
+ } else {
1207
+ throw error;
1208
+ }
1209
+ }
1210
+ ```
1211
+
1212
+ ### Secret Errors
1213
+
1214
+ ```typescript
1215
+ try {
1216
+ const secret = await ketrics.Secret.get('api-key');
1217
+ } catch (error) {
1218
+ if (error instanceof ketrics.SecretNotFoundError) {
1219
+ console.log(`Secret '${error.secretCode}' not found`);
1220
+ } else if (error instanceof ketrics.SecretAccessDeniedError) {
1221
+ console.log(`No access to secret '${error.secretCode}'`);
1222
+ } else if (error instanceof ketrics.SecretDecryptionError) {
1223
+ console.log(`Decryption failed: ${error.reason}`);
1224
+ } else if (error instanceof ketrics.SecretError) {
1225
+ // Base class catches all secret errors
1226
+ console.log(`Secret error: ${error.message}`);
1227
+ } else {
1228
+ throw error;
1229
+ }
1230
+ }
1231
+ ```
1232
+
1233
+ ### Excel Errors
1234
+
1235
+ ```typescript
1236
+ try {
1237
+ const workbook = await ketrics.Excel.read(buffer);
1238
+ const outputBuffer = await workbook.toBuffer();
1239
+ } catch (error) {
1240
+ if (error instanceof ketrics.ExcelParseError) {
1241
+ console.log(`Failed to parse Excel: ${error.reason}`);
1242
+ } else if (error instanceof ketrics.ExcelWriteError) {
1243
+ console.log(`Failed to write Excel: ${error.reason}`);
1244
+ } else if (error instanceof ketrics.ExcelError) {
1245
+ // Base class catches all Excel errors
1246
+ console.log(`Excel error: ${error.message}`);
1247
+ } else {
1248
+ throw error;
1249
+ }
1250
+ }
1251
+ ```
1252
+
1253
+ ### PDF Errors
1254
+
1255
+ ```typescript
1256
+ try {
1257
+ const doc = await ketrics.Pdf.read(buffer);
1258
+ const page = doc.addPage('A4');
1259
+ await page.drawText('Hello');
1260
+ const outputBuffer = await doc.toBuffer();
1261
+ } catch (error) {
1262
+ if (error instanceof ketrics.PdfParseError) {
1263
+ console.log(`Failed to parse PDF: ${error.reason}`);
1264
+ } else if (error instanceof ketrics.PdfWriteError) {
1265
+ console.log(`Failed to write PDF: ${error.reason}`);
1266
+ } else if (error instanceof ketrics.PdfError) {
1267
+ // Base class catches all PDF errors
1268
+ console.log(`PDF error: ${error.message}`);
1269
+ } else {
1270
+ throw error;
1271
+ }
1272
+ }
1273
+ ```
1274
+
1275
+ ---
1276
+
1277
+ ## Error Class Reference
1278
+
1279
+ ### Volume Errors
263
1280
 
264
1281
  | Error Class | When Thrown |
265
1282
  |-------------|-------------|
266
- | `ketrics.VolumeError` | Base class for all volume errors |
267
- | `ketrics.VolumeNotFoundError` | Volume doesn't exist |
268
- | `ketrics.VolumeAccessDeniedError` | Application has no access grant |
269
- | `ketrics.VolumePermissionDeniedError` | Missing required permission |
270
- | `ketrics.FileNotFoundError` | File doesn't exist |
271
- | `ketrics.FileAlreadyExistsError` | File exists (with `ifNotExists` option) |
272
- | `ketrics.InvalidPathError` | Invalid file path |
273
- | `ketrics.FileSizeLimitError` | File exceeds size limit |
274
- | `ketrics.ContentTypeNotAllowedError` | Content type not allowed |
1283
+ | `VolumeError` | Base class for all volume errors |
1284
+ | `VolumeNotFoundError` | Volume doesn't exist |
1285
+ | `VolumeAccessDeniedError` | Application has no access grant |
1286
+ | `VolumePermissionDeniedError` | Missing required permission (Read, Create, Update, Delete, List) |
1287
+ | `FileNotFoundError` | File doesn't exist |
1288
+ | `FileAlreadyExistsError` | File exists (with `ifNotExists` option) |
1289
+ | `InvalidPathError` | Invalid file path (e.g., path traversal attempt) |
1290
+ | `FileSizeLimitError` | File exceeds volume size limit |
1291
+ | `ContentTypeNotAllowedError` | Content type not allowed by volume |
1292
+
1293
+ ### Database Errors
1294
+
1295
+ | Error Class | When Thrown |
1296
+ |-------------|-------------|
1297
+ | `DatabaseError` | Base class for all database errors |
1298
+ | `DatabaseNotFoundError` | Database doesn't exist |
1299
+ | `DatabaseAccessDeniedError` | Application has no access grant |
1300
+ | `DatabaseConnectionError` | Connection cannot be established |
1301
+ | `DatabaseQueryError` | SQL query fails |
1302
+ | `DatabaseTransactionError` | Transaction fails (automatically rolled back) |
1303
+
1304
+ ### Secret Errors
1305
+
1306
+ | Error Class | When Thrown |
1307
+ |-------------|-------------|
1308
+ | `SecretError` | Base class for all secret errors |
1309
+ | `SecretNotFoundError` | Secret doesn't exist |
1310
+ | `SecretAccessDeniedError` | Application has no access grant |
1311
+ | `SecretDecryptionError` | KMS decryption fails |
1312
+
1313
+ ### Excel Errors
1314
+
1315
+ | Error Class | When Thrown |
1316
+ |-------------|-------------|
1317
+ | `ExcelError` | Base class for all Excel errors |
1318
+ | `ExcelParseError` | File cannot be parsed (invalid/corrupted) |
1319
+ | `ExcelWriteError` | File cannot be written |
1320
+
1321
+ ### PDF Errors
1322
+
1323
+ | Error Class | When Thrown |
1324
+ |-------------|-------------|
1325
+ | `PdfError` | Base class for all PDF errors |
1326
+ | `PdfParseError` | PDF file cannot be parsed (invalid/corrupted) |
1327
+ | `PdfWriteError` | PDF file cannot be written |
1328
+
1329
+ ---
275
1330
 
276
1331
  ## Type Exports
277
1332
 
@@ -314,16 +1369,145 @@ import type {
314
1369
  PresignedUrl,
315
1370
 
316
1371
  // Databases
317
- DatabaseManager,
318
- DatabaseConnection,
1372
+ IDatabaseConnection,
319
1373
  DatabaseQueryResult,
320
1374
  DatabaseExecuteResult,
1375
+ DatabaseManager,
1376
+
1377
+ // Secrets
1378
+ ISecret,
1379
+
1380
+ // Excel
1381
+ IExcelWorkbook,
1382
+ IExcelWorksheet,
1383
+ IExcelRow,
1384
+ IExcelCell,
1385
+ ExcelCellValue,
1386
+ ExcelRowValues,
1387
+ ExcelColumnDefinition,
1388
+ AddWorksheetOptions,
1389
+ ExcelManager,
1390
+
1391
+ // PDF
1392
+ IPdfDocument,
1393
+ IPdfPage,
1394
+ PdfPageSize,
1395
+ PdfStandardFont,
1396
+ PdfRgbColor,
1397
+ PdfDrawTextOptions,
1398
+ PdfDrawRectOptions,
1399
+ PdfDrawLineOptions,
1400
+ PdfDrawCircleOptions,
1401
+ PdfDrawImageOptions,
1402
+ PdfEmbeddedImage,
1403
+ PdfEmbeddedFont,
1404
+ PdfManager,
321
1405
 
322
1406
  // Main SDK
323
1407
  KetricsSdkV1,
324
- } from '@ketrics/sdk';
1408
+ } from '@ketrics/sdk-backend';
1409
+ ```
1410
+
1411
+ ---
1412
+
1413
+ ## Complete Example
1414
+
1415
+ Here's a complete example showing multiple SDK features working together:
1416
+
1417
+ ```typescript
1418
+ import type { IVolume, IDatabaseConnection, FileContent } from '@ketrics/sdk-backend';
1419
+
1420
+ interface Order {
1421
+ id: number;
1422
+ customer_name: string;
1423
+ total: number;
1424
+ status: string;
1425
+ }
1426
+
1427
+ export async function generateReport() {
1428
+ ketrics.console.log('Starting report generation', {
1429
+ tenant: ketrics.tenant.code,
1430
+ user: ketrics.user.email,
1431
+ });
1432
+
1433
+ // Get API key from secrets
1434
+ const emailApiKey = await ketrics.Secret.get('sendgrid-api-key');
1435
+
1436
+ // Connect to database
1437
+ const db = await ketrics.DatabaseConnection.connect('orders-db');
1438
+
1439
+ try {
1440
+ // Query orders from database
1441
+ const orders = await db.query<Order>(
1442
+ 'SELECT * FROM orders WHERE status = ? AND created_at > ?',
1443
+ ['completed', '2024-01-01']
1444
+ );
1445
+
1446
+ ketrics.console.info(`Found ${orders.rowCount} orders`);
1447
+
1448
+ // Create Excel report
1449
+ const workbook = ketrics.Excel.create();
1450
+ const sheet = workbook.addWorksheet('Orders Report');
1451
+
1452
+ // Add header row
1453
+ sheet.addRow(['Order ID', 'Customer', 'Total', 'Status']);
1454
+
1455
+ // Add data rows
1456
+ for (const order of orders.rows) {
1457
+ sheet.addRow([order.id, order.customer_name, order.total, order.status]);
1458
+ }
1459
+
1460
+ // Calculate total
1461
+ const total = orders.rows.reduce((sum, o) => sum + o.total, 0);
1462
+ sheet.addRow(['', 'TOTAL', total, '']);
1463
+
1464
+ // Generate buffer
1465
+ const buffer = await workbook.toBuffer();
1466
+
1467
+ // Save to volume
1468
+ const volume = await ketrics.Volume.connect('reports');
1469
+ const fileName = `orders-${new Date().toISOString().split('T')[0]}.xlsx`;
1470
+
1471
+ await volume.put(`monthly/${fileName}`, buffer, {
1472
+ contentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
1473
+ metadata: {
1474
+ generatedBy: ketrics.user.email,
1475
+ orderCount: String(orders.rowCount),
1476
+ },
1477
+ });
1478
+
1479
+ // Generate download URL
1480
+ const downloadUrl = await volume.generateDownloadUrl(`monthly/${fileName}`, {
1481
+ expiresIn: 86400, // 24 hours
1482
+ });
1483
+
1484
+ // Send email notification via external API
1485
+ await ketrics.http.post('https://api.sendgrid.com/v3/mail/send', {
1486
+ to: ketrics.user.email,
1487
+ subject: 'Your report is ready',
1488
+ body: `Download your report: ${downloadUrl.url}`,
1489
+ }, {
1490
+ headers: { 'Authorization': `Bearer ${emailApiKey}` },
1491
+ });
1492
+
1493
+ ketrics.console.log('Report generated successfully', { fileName });
1494
+
1495
+ return {
1496
+ success: true,
1497
+ fileName,
1498
+ downloadUrl: downloadUrl.url,
1499
+ orderCount: orders.rowCount,
1500
+ total,
1501
+ };
1502
+
1503
+ } finally {
1504
+ await db.close();
1505
+ }
1506
+ }
325
1507
  ```
326
1508
 
1509
+ ---
1510
+
327
1511
  ## License
328
1512
 
329
1513
  MIT