@ketrics/sdk-backend 0.3.0 → 0.4.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 +1311 -127
- package/dist/context.d.ts +7 -5
- package/dist/context.d.ts.map +1 -1
- package/dist/index.d.ts +198 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -1
- package/dist/index.js.map +1 -1
- package/dist/job-errors.d.ts +157 -0
- package/dist/job-errors.d.ts.map +1 -0
- package/dist/job-errors.js +212 -0
- package/dist/job-errors.js.map +1 -0
- package/dist/job.d.ts +158 -0
- package/dist/job.d.ts.map +1 -0
- package/dist/job.js +31 -0
- package/dist/job.js.map +1 -0
- package/dist/pdf-errors.d.ts +77 -0
- package/dist/pdf-errors.d.ts.map +1 -0
- package/dist/pdf-errors.js +127 -0
- package/dist/pdf-errors.js.map +1 -0
- package/dist/pdf.d.ts +268 -0
- package/dist/pdf.d.ts.map +1 -0
- package/dist/pdf.js +8 -0
- package/dist/pdf.js.map +1 -0
- package/package.json +2 -2
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
|
-
##
|
|
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
|
|
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
|
-
- **
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Context Properties (Read-only)
|
|
56
61
|
|
|
57
|
-
### Context
|
|
62
|
+
### Tenant Context
|
|
58
63
|
|
|
59
|
-
Access information about the current tenant
|
|
64
|
+
Access information about the current tenant:
|
|
60
65
|
|
|
61
66
|
```typescript
|
|
62
|
-
// Tenant
|
|
63
|
-
ketrics.tenant.
|
|
64
|
-
ketrics.tenant.
|
|
65
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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., "
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
ketrics.
|
|
82
|
-
ketrics.
|
|
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
|
-
|
|
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('
|
|
91
|
-
ketrics.console.
|
|
92
|
-
ketrics.console.warn('
|
|
93
|
-
ketrics.console.
|
|
94
|
-
ketrics.console.debug('Debug
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
108
|
-
|
|
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
|
-
//
|
|
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: {
|
|
114
|
-
|
|
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
|
-
###
|
|
179
|
+
### Response Structure
|
|
120
180
|
|
|
121
|
-
|
|
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
|
-
|
|
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
|
|
201
|
+
// Connect to a volume by code
|
|
128
202
|
const volume = await ketrics.Volume.connect('uploads');
|
|
129
203
|
|
|
130
|
-
// Check
|
|
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
|
-
|
|
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
|
-
//
|
|
143
|
-
const
|
|
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
|
-
//
|
|
150
|
-
const
|
|
238
|
+
// Write binary content
|
|
239
|
+
const buffer = Buffer.from('Hello World');
|
|
240
|
+
await volume.put('files/hello.txt', buffer);
|
|
151
241
|
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
//
|
|
157
|
-
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
//
|
|
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
|
|
174
|
-
console.log(`Deleted ${
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
385
|
+
### Executing Statements
|
|
199
386
|
|
|
200
387
|
```typescript
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
//
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
-
| `
|
|
267
|
-
| `
|
|
268
|
-
| `
|
|
269
|
-
| `
|
|
270
|
-
| `
|
|
271
|
-
| `
|
|
272
|
-
| `
|
|
273
|
-
| `
|
|
274
|
-
| `
|
|
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
|
-
|
|
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
|