@ketrics/sdk-backend 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +609 -1362
- package/dist/context.d.ts +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,1513 +1,760 @@
|
|
|
1
1
|
# @ketrics/sdk-backend
|
|
2
2
|
|
|
3
|
-
TypeScript type definitions for building tenant applications on the Ketrics platform.
|
|
3
|
+
TypeScript type definitions and runtime interfaces for building tenant applications on the Ketrics platform.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 1. Overview
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
npm install @ketrics/sdk-backend
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Overview
|
|
12
|
-
|
|
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:
|
|
7
|
+
### Purpose
|
|
14
8
|
|
|
15
|
-
-
|
|
16
|
-
- **Utilities**: `ketrics.console`, `ketrics.http`
|
|
17
|
-
- **Volume**: `ketrics.Volume.connect()` for S3-backed storage
|
|
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
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
## Quick Start
|
|
27
|
-
|
|
28
|
-
```typescript
|
|
29
|
-
// Import types only if needed for type annotations
|
|
30
|
-
import type { FileContent, IVolume, IDatabaseConnection } from '@ketrics/sdk-backend';
|
|
31
|
-
|
|
32
|
-
export async function handler() {
|
|
33
|
-
// Context is always available
|
|
34
|
-
console.log('Tenant:', ketrics.tenant.name);
|
|
35
|
-
console.log('User:', ketrics.user.email);
|
|
36
|
-
console.log('App:', ketrics.application.code);
|
|
37
|
-
|
|
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();
|
|
9
|
+
The `@ketrics/sdk-backend` package provides TypeScript type definitions for the Ketrics backend SDK. It defines the contract between tenant application code and the Ketrics Data Plane runtime environment. Tenant developers install this package to get full TypeScript intellisense and type safety when writing application code that runs inside Ketrics' sandboxed JavaScript VM.
|
|
46
10
|
|
|
47
|
-
|
|
48
|
-
const apiKey = await ketrics.Secret.get('stripe-api-key');
|
|
11
|
+
### Role in the Architecture
|
|
49
12
|
|
|
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 };
|
|
55
|
-
}
|
|
56
13
|
```
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
14
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
15
|
+
│ Ketrics Platform │
|
|
16
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
17
|
+
│ │
|
|
18
|
+
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
|
|
19
|
+
│ │ Tenant API │ │ Data Plane │ │ ketrics-sdk- │ │
|
|
20
|
+
│ │ API │ │ API │ │ backend │ │
|
|
21
|
+
│ │ │ │ │ │ (this pkg) │ │
|
|
22
|
+
│ │ - Tenant mgmt │ │ - VM Sandbox │◄─────│ │ │
|
|
23
|
+
│ │ - App deploy │ │ - Request exec │ │ Type defs for │ │
|
|
24
|
+
│ │ - User mgmt │ │ - SDK injection │ │ global `ketrics`│ │
|
|
25
|
+
│ └──────────────────┘ └──────────────────┘ └──────────────────┘ │
|
|
26
|
+
│ │ │ ▲ │
|
|
27
|
+
│ │ │ │ │
|
|
28
|
+
│ ▼ ▼ │ │
|
|
29
|
+
│ ┌──────────────────────────────────────────────────┐ │ │
|
|
30
|
+
│ │ Tenant Applications │────────┘ │
|
|
31
|
+
│ │ │ │
|
|
32
|
+
│ │ import type { IVolume } from '@ketrics/sdk-backend' │
|
|
33
|
+
│ │ │ │
|
|
34
|
+
│ │ export async function handler() { │ │
|
|
35
|
+
│ │ const volume = await ketrics.Volume.connect('uploads'); │
|
|
36
|
+
│ │ // Full TypeScript support via this SDK │ │
|
|
37
|
+
│ │ } │ │
|
|
38
|
+
│ └───────────────────────────────────────────────────┘ │
|
|
39
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
70
40
|
```
|
|
71
41
|
|
|
72
|
-
|
|
42
|
+
The SDK sits between the Data Plane runtime (which injects the actual `ketrics` global object) and tenant application code. It provides:
|
|
73
43
|
|
|
74
|
-
|
|
44
|
+
1. **Type definitions** for the global `ketrics` object
|
|
45
|
+
2. **Interface contracts** for all SDK modules (Volume, Database, Secret, Excel, PDF, Job, Messages)
|
|
46
|
+
3. **Error class hierarchies** for type-safe error handling
|
|
47
|
+
4. **Type guards** for runtime error checking
|
|
75
48
|
|
|
76
|
-
|
|
77
|
-
ketrics.application.id // Application UUID
|
|
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)
|
|
81
|
-
ketrics.application.deploymentId // Current deployment ID
|
|
82
|
-
```
|
|
49
|
+
### Key Responsibilities
|
|
83
50
|
|
|
84
|
-
|
|
51
|
+
- Define the shape of the `ketrics` global object available in tenant code
|
|
52
|
+
- Provide TypeScript types for context objects (tenant, application, requestor, runtime)
|
|
53
|
+
- Define interfaces for all platform services (storage, database, secrets, file processing)
|
|
54
|
+
- Export error classes for `instanceof` checks in catch blocks
|
|
55
|
+
- Export type guards (`isVolumeError`, `isDatabaseError`, etc.) for safe error handling
|
|
85
56
|
|
|
86
|
-
|
|
57
|
+
### Boundaries
|
|
87
58
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
ketrics.user.isAdmin // Whether user is tenant admin (boolean)
|
|
93
|
-
```
|
|
59
|
+
This package is **types only** at development time. It contains:
|
|
60
|
+
- TypeScript interfaces and type definitions
|
|
61
|
+
- Abstract error base classes and concrete error implementations
|
|
62
|
+
- Type guard functions
|
|
94
63
|
|
|
95
|
-
|
|
64
|
+
It does **not** contain:
|
|
65
|
+
- Actual SDK implementations (those are in the Data Plane API)
|
|
66
|
+
- Runtime logic (the Data Plane injects the real `ketrics` object)
|
|
67
|
+
- API clients or network code
|
|
96
68
|
|
|
97
|
-
|
|
69
|
+
## 2. Business Logic
|
|
98
70
|
|
|
99
|
-
|
|
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:
|
|
108
|
-
|
|
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
|
-
}
|
|
119
|
-
```
|
|
71
|
+
### Problem Solved
|
|
120
72
|
|
|
121
|
-
|
|
73
|
+
Tenant developers need to write application code that runs inside Ketrics' sandboxed VM. Without this SDK, they would have no TypeScript support, leading to:
|
|
74
|
+
- No autocomplete or intellisense in their IDE
|
|
75
|
+
- No compile-time type checking
|
|
76
|
+
- No documentation for available methods and their signatures
|
|
77
|
+
- Difficulty understanding error types for proper handling
|
|
122
78
|
|
|
123
|
-
|
|
79
|
+
### Core Workflows
|
|
124
80
|
|
|
125
|
-
|
|
81
|
+
#### 1. Context Access
|
|
82
|
+
Tenant code accesses execution context through `ketrics.tenant`, `ketrics.application`, `ketrics.requestor`, and `ketrics.runtime`. The SDK defines the exact shape of these objects.
|
|
126
83
|
|
|
127
84
|
```typescript
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
ketrics.console.debug('Debug info', { state: currentState });
|
|
85
|
+
// TenantContext: id, code, name
|
|
86
|
+
// ApplicationContext: id, code, name, version, deploymentId
|
|
87
|
+
// RequestorContext: type ('USER' | 'SERVICE_ACCOUNT'), userId?, serviceAccountId?, name, email?, applicationPermissions
|
|
88
|
+
// RuntimeContext: nodeVersion, runtime ('ecs-fargate'), region
|
|
133
89
|
```
|
|
134
90
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
- `info()` - Informational messages
|
|
138
|
-
- `warn()` - Warning messages
|
|
139
|
-
- `error()` - Error messages
|
|
140
|
-
- `debug()` - Debug messages (may be filtered in production)
|
|
141
|
-
|
|
142
|
-
---
|
|
91
|
+
#### 2. Resource Access Pattern
|
|
92
|
+
All resources (volumes, databases, secrets) follow a consistent pattern:
|
|
143
93
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
94
|
+
1. **Connect/Get** - Obtain a handle to the resource by code
|
|
95
|
+
2. **Perform Operations** - Use methods on the returned interface
|
|
96
|
+
3. **Handle Errors** - Catch typed errors using `instanceof`
|
|
147
97
|
|
|
148
98
|
```typescript
|
|
149
|
-
// GET request
|
|
150
|
-
const response = await ketrics.http.get<MyData>('https://api.example.com/data');
|
|
151
|
-
console.log(response.data, response.status);
|
|
152
|
-
|
|
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',
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
// DELETE request
|
|
165
|
-
await ketrics.http.delete('https://api.example.com/orders/123');
|
|
166
|
-
|
|
167
|
-
// With configuration options
|
|
168
|
-
const configured = await ketrics.http.get('https://api.example.com/data', {
|
|
169
|
-
headers: {
|
|
170
|
-
'Authorization': 'Bearer token123',
|
|
171
|
-
'X-Custom-Header': 'value',
|
|
172
|
-
},
|
|
173
|
-
timeout: 5000, // 5 seconds
|
|
174
|
-
params: { page: 1, limit: 10 },
|
|
175
|
-
maxRedirects: 3,
|
|
176
|
-
});
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
### Response Structure
|
|
180
|
-
|
|
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
|
|
197
|
-
|
|
198
|
-
```typescript
|
|
199
|
-
import type { IVolume, FileContent, PutResult, ListResult } from '@ketrics/sdk-backend';
|
|
200
|
-
|
|
201
|
-
// Connect to a volume by code
|
|
202
|
-
const volume = await ketrics.Volume.connect('uploads');
|
|
203
|
-
|
|
204
|
-
// Check volume info
|
|
205
|
-
console.log(volume.code); // 'uploads'
|
|
206
|
-
console.log(volume.permissions); // Set { 'ReadObject', 'CreateObject', ... }
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
### Reading Files
|
|
210
|
-
|
|
211
|
-
```typescript
|
|
212
|
-
// Get file content
|
|
213
|
-
const file: FileContent = await volume.get('documents/report.pdf');
|
|
214
|
-
console.log(file.content); // Buffer containing file data
|
|
215
|
-
console.log(file.contentType); // 'application/pdf'
|
|
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');
|
|
223
|
-
|
|
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), {
|
|
234
|
-
contentType: 'application/json',
|
|
235
|
-
});
|
|
236
|
-
console.log(result.key, result.etag, result.size);
|
|
237
|
-
|
|
238
|
-
// Write binary content
|
|
239
|
-
const buffer = Buffer.from('Hello World');
|
|
240
|
-
await volume.put('files/hello.txt', buffer);
|
|
241
|
-
|
|
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
|
-
});
|
|
250
|
-
|
|
251
|
-
// Create only if file doesn't exist
|
|
252
99
|
try {
|
|
253
|
-
await
|
|
100
|
+
const volume = await ketrics.Volume.connect('uploads'); // Step 1
|
|
101
|
+
const file = await volume.get('report.pdf'); // Step 2
|
|
254
102
|
} catch (error) {
|
|
255
|
-
if (error instanceof ketrics.
|
|
256
|
-
|
|
103
|
+
if (error instanceof ketrics.FileNotFoundError) { // Step 3
|
|
104
|
+
// Handle specific error
|
|
257
105
|
}
|
|
258
106
|
}
|
|
259
107
|
```
|
|
260
108
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
109
|
+
#### 3. File Processing
|
|
110
|
+
Excel and PDF modules follow a factory pattern:
|
|
111
|
+
- `ketrics.Excel.read(buffer)` - Parse existing file
|
|
112
|
+
- `ketrics.Excel.create()` - Create new file
|
|
113
|
+
- `ketrics.Pdf.read(buffer)` - Parse existing PDF
|
|
114
|
+
- `ketrics.Pdf.create()` - Create new PDF
|
|
115
|
+
|
|
116
|
+
### Business Rules
|
|
117
|
+
|
|
118
|
+
1. **Permission-Based Access**: All resource access is gated by application grants. The SDK types reflect this with `permissions` properties on connection interfaces.
|
|
119
|
+
|
|
120
|
+
2. **Tenant Isolation**: The SDK's context types (`TenantContext`, `ApplicationContext`) ensure tenant code always knows its execution context.
|
|
121
|
+
|
|
122
|
+
3. **Requestor Types**: The `RequestorContext` distinguishes between human users (`type: 'USER'`) and service accounts (`type: 'SERVICE_ACCOUNT'`) for audit and access control.
|
|
123
|
+
|
|
124
|
+
4. **Error Hierarchy**: Each module has a base error class (e.g., `VolumeError`) with specific subclasses. This allows catching all errors from a module or specific error types.
|
|
125
|
+
|
|
126
|
+
### Input/Output Expectations
|
|
127
|
+
|
|
128
|
+
| Module | Input | Output |
|
|
129
|
+
|--------|-------|--------|
|
|
130
|
+
| Volume.connect | Volume code (string) | IVolume interface |
|
|
131
|
+
| volume.get | File key (string) | FileContent (content, contentType, size, etag) |
|
|
132
|
+
| volume.put | Key, content, options | PutResult (key, etag, size) |
|
|
133
|
+
| DatabaseConnection.connect | Database code (string) | IDatabaseConnection interface |
|
|
134
|
+
| db.query | SQL string, params array | DatabaseQueryResult (rows, rowCount) |
|
|
135
|
+
| Secret.get | Secret code (string) | Decrypted string value |
|
|
136
|
+
| Excel.read | Buffer | IExcelWorkbook |
|
|
137
|
+
| Pdf.create | (none) | IPdfDocument |
|
|
138
|
+
| Job.runInBackground | RunInBackgroundParams | Job ID (string) |
|
|
139
|
+
| Messages.send | SendMessageParams | SendMessageResult |
|
|
140
|
+
|
|
141
|
+
### Edge Cases Handled
|
|
142
|
+
|
|
143
|
+
1. **File Not Found**: `FileNotFoundError` with the key that wasn't found
|
|
144
|
+
2. **Permission Denied**: Separate errors for "no grant" vs "missing specific permission"
|
|
145
|
+
3. **Path Traversal**: `InvalidPathError` for attempts like `../../../etc/passwd`
|
|
146
|
+
4. **Size Limits**: `FileSizeLimitError` includes both actual and max size
|
|
147
|
+
5. **Content Type Restrictions**: `ContentTypeNotAllowedError` includes allowed types
|
|
148
|
+
6. **Cross-App Jobs**: `CrossAppPermissionError` for background jobs targeting other apps
|
|
149
|
+
7. **Transaction Failures**: `DatabaseTransactionError` includes whether rollback occurred
|
|
150
|
+
|
|
151
|
+
## 3. Technical Details
|
|
152
|
+
|
|
153
|
+
### Technology Stack
|
|
154
|
+
|
|
155
|
+
- **Language**: TypeScript 5.x
|
|
156
|
+
- **Target**: ES2020
|
|
157
|
+
- **Module System**: CommonJS
|
|
158
|
+
- **Node Version**: 24.x (required for `@types/node` peer dependency)
|
|
159
|
+
- **Build**: TypeScript compiler (`tsc`)
|
|
160
|
+
- **Output**: JavaScript + Declaration files (`.d.ts`)
|
|
161
|
+
|
|
162
|
+
### File Structure
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
ketrics-sdk-backend/
|
|
166
|
+
├── package.json # NPM package configuration
|
|
167
|
+
├── tsconfig.json # TypeScript compiler options
|
|
168
|
+
├── src/
|
|
169
|
+
│ ├── index.ts # Main entry: exports all types, defines KetricsSdkV1 interface
|
|
170
|
+
│ ├── context.ts # TenantContext, ApplicationContext, RequestorContext, RuntimeContext
|
|
171
|
+
│ ├── console.ts # ConsoleLogger interface (log, error, warn, info, debug)
|
|
172
|
+
│ ├── http.ts # HttpClient, HttpRequestConfig, HttpResponse
|
|
173
|
+
│ ├── volumes.ts # IVolume, FileContent, PutOptions, ListResult, etc.
|
|
174
|
+
│ ├── errors.ts # VolumeError hierarchy (VolumeNotFoundError, FileNotFoundError, etc.)
|
|
175
|
+
│ ├── databases.ts # IDatabaseConnection, DatabaseQueryResult, transaction support
|
|
176
|
+
│ ├── database-errors.ts # DatabaseError hierarchy
|
|
177
|
+
│ ├── secrets.ts # ISecret interface (get, exists)
|
|
178
|
+
│ ├── secret-errors.ts # SecretError hierarchy
|
|
179
|
+
│ ├── excel.ts # IExcelWorkbook, IExcelWorksheet, IExcelRow, IExcelCell
|
|
180
|
+
│ ├── excel-errors.ts # ExcelError, ExcelParseError, ExcelWriteError
|
|
181
|
+
│ ├── pdf.ts # IPdfDocument, IPdfPage, drawing options, embedded resources
|
|
182
|
+
│ ├── pdf-errors.ts # PdfError, PdfParseError, PdfWriteError
|
|
183
|
+
│ ├── job.ts # RunInBackgroundParams, JobStatus, JobListResult
|
|
184
|
+
│ ├── job-errors.ts # JobError hierarchy
|
|
185
|
+
│ ├── messages.ts # SendMessageParams, MessagesManager, bulk/group messaging
|
|
186
|
+
│ └── messages-errors.ts # MessageError hierarchy
|
|
187
|
+
└── dist/ # Compiled output (generated)
|
|
188
|
+
├── index.js # Compiled JavaScript
|
|
189
|
+
├── index.d.ts # Type declarations
|
|
190
|
+
└── ...
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Key Interfaces
|
|
194
|
+
|
|
195
|
+
#### KetricsSdkV1 (Main SDK Interface)
|
|
196
|
+
|
|
197
|
+
The primary interface defining the global `ketrics` object:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
interface KetricsSdkV1 {
|
|
201
|
+
// Context (read-only)
|
|
202
|
+
tenant: TenantContext;
|
|
203
|
+
application: ApplicationContext;
|
|
204
|
+
requestor: RequestorContext;
|
|
205
|
+
runtime: RuntimeContext;
|
|
206
|
+
environment: EnvironmentVariables;
|
|
207
|
+
|
|
208
|
+
// Utilities
|
|
209
|
+
console: ConsoleLogger;
|
|
210
|
+
http: HttpClient;
|
|
211
|
+
|
|
212
|
+
// Resource Factories
|
|
213
|
+
Volume: { connect(code: string): Promise<IVolume> };
|
|
214
|
+
DatabaseConnection: { connect(code: string): Promise<IDatabaseConnection> };
|
|
215
|
+
Secret: { get(code: string): Promise<string>; exists(code: string): Promise<boolean> };
|
|
216
|
+
Excel: { read(buffer: Buffer): Promise<IExcelWorkbook>; create(): IExcelWorkbook };
|
|
217
|
+
Pdf: { read(buffer: Buffer): Promise<IPdfDocument>; create(): Promise<IPdfDocument>; rgb(r, g, b): PdfRgbColor };
|
|
218
|
+
Job: { runInBackground(params): Promise<string>; getStatus(id): Promise<JobStatus>; list(params?): Promise<JobListResult> };
|
|
219
|
+
Messages: MessagesManager;
|
|
220
|
+
|
|
221
|
+
// Error Classes (for instanceof checks)
|
|
222
|
+
VolumeError, VolumeNotFoundError, FileNotFoundError, ...
|
|
223
|
+
DatabaseError, DatabaseNotFoundError, DatabaseQueryError, ...
|
|
224
|
+
SecretError, SecretNotFoundError, ...
|
|
225
|
+
ExcelError, PdfError, JobError, MessageError, ...
|
|
226
|
+
|
|
227
|
+
// Type Guards
|
|
228
|
+
isVolumeError, isVolumeErrorType, isDatabaseError, ...
|
|
280
229
|
}
|
|
281
|
-
|
|
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
|
|
291
|
-
await volume.delete('temp/file.txt');
|
|
292
|
-
|
|
293
|
-
// Delete by prefix (batch delete)
|
|
294
|
-
const result = await volume.deleteByPrefix('temp/');
|
|
295
|
-
console.log(`Deleted ${result.deletedCount} files`);
|
|
296
|
-
console.log('Deleted keys:', result.deletedKeys);
|
|
297
230
|
```
|
|
298
231
|
|
|
299
|
-
|
|
232
|
+
#### IVolume (S3-Backed Storage)
|
|
300
233
|
|
|
301
234
|
```typescript
|
|
302
|
-
|
|
303
|
-
|
|
235
|
+
interface IVolume {
|
|
236
|
+
readonly code: string;
|
|
237
|
+
readonly permissions: ReadonlySet<string>;
|
|
304
238
|
|
|
305
|
-
//
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
});
|
|
239
|
+
// Read
|
|
240
|
+
get(key: string): Promise<FileContent>;
|
|
241
|
+
exists(key: string): Promise<boolean>;
|
|
242
|
+
getMetadata(key: string): Promise<FileMetadata>;
|
|
310
243
|
|
|
311
|
-
//
|
|
312
|
-
|
|
313
|
-
```
|
|
244
|
+
// Write
|
|
245
|
+
put(key: string, content: PutContent, options?: PutOptions): Promise<PutResult>;
|
|
314
246
|
|
|
315
|
-
|
|
247
|
+
// Delete
|
|
248
|
+
delete(key: string): Promise<DeleteResult>;
|
|
249
|
+
deleteByPrefix(prefix: string): Promise<DeleteByPrefixResult>;
|
|
316
250
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
const downloadUrl = await volume.generateDownloadUrl('report.pdf', {
|
|
320
|
-
expiresIn: 3600, // 1 hour
|
|
321
|
-
responseContentDisposition: 'attachment; filename="report.pdf"',
|
|
322
|
-
});
|
|
323
|
-
console.log(downloadUrl.url); // Presigned S3 URL
|
|
324
|
-
console.log(downloadUrl.expiresAt); // Expiration Date
|
|
325
|
-
|
|
326
|
-
// Generate upload URL (for direct client uploads)
|
|
327
|
-
const uploadUrl = await volume.generateUploadUrl('uploads/new-file.pdf', {
|
|
328
|
-
expiresIn: 3600,
|
|
329
|
-
contentType: 'application/pdf',
|
|
330
|
-
maxSize: 10 * 1024 * 1024, // 10MB limit
|
|
331
|
-
});
|
|
332
|
-
// Client can PUT directly to uploadUrl.url
|
|
333
|
-
```
|
|
251
|
+
// List
|
|
252
|
+
list(options?: ListOptions): Promise<ListResult>;
|
|
334
253
|
|
|
335
|
-
|
|
254
|
+
// File Management
|
|
255
|
+
copy(src: string, dest: string, options?: CopyOptions): Promise<CopyResult>;
|
|
256
|
+
move(src: string, dest: string, options?: MoveOptions): Promise<MoveResult>;
|
|
336
257
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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', ... }
|
|
258
|
+
// URL Generation
|
|
259
|
+
generateDownloadUrl(key: string, options?: DownloadUrlOptions): Promise<PresignedUrl>;
|
|
260
|
+
generateUploadUrl(key: string, options?: UploadUrlOptions): Promise<PresignedUrl>;
|
|
261
|
+
}
|
|
352
262
|
```
|
|
353
263
|
|
|
354
|
-
|
|
264
|
+
#### IDatabaseConnection (External Database Access)
|
|
355
265
|
|
|
356
266
|
```typescript
|
|
357
|
-
interface
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
email: string;
|
|
361
|
-
created_at: Date;
|
|
362
|
-
}
|
|
267
|
+
interface IDatabaseConnection {
|
|
268
|
+
readonly code: string;
|
|
269
|
+
readonly permissions: ReadonlySet<string>;
|
|
363
270
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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);
|
|
271
|
+
query<T>(sql: string, params?: unknown[]): Promise<DatabaseQueryResult<T>>;
|
|
272
|
+
execute(sql: string, params?: unknown[]): Promise<DatabaseExecuteResult>;
|
|
273
|
+
transaction<T>(fn: (tx: IDatabaseConnection) => Promise<T>): Promise<T>;
|
|
274
|
+
close(): Promise<void>;
|
|
382
275
|
}
|
|
383
276
|
```
|
|
384
277
|
|
|
385
|
-
###
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
)
|
|
408
|
-
|
|
278
|
+
### Configuration
|
|
279
|
+
|
|
280
|
+
**tsconfig.json settings:**
|
|
281
|
+
- `target: "ES2020"` - Modern JavaScript output
|
|
282
|
+
- `module: "commonjs"` - Node.js compatible modules
|
|
283
|
+
- `declaration: true` - Generate `.d.ts` files
|
|
284
|
+
- `declarationMap: true` - Enable declaration sourcemaps
|
|
285
|
+
- `strict: true` - Full TypeScript strictness
|
|
286
|
+
- `outDir: "./dist"` - Output directory
|
|
287
|
+
|
|
288
|
+
**No environment variables** - This is a pure type definition package.
|
|
289
|
+
|
|
290
|
+
### External Integrations
|
|
291
|
+
|
|
292
|
+
This package has **no runtime dependencies**. It only requires `@types/node` as a peer dependency for Node.js built-in types like `Buffer` and `stream.Readable`.
|
|
293
|
+
|
|
294
|
+
The types it defines correspond to these Data Plane integrations:
|
|
295
|
+
- **AWS S3** (via Volume interface)
|
|
296
|
+
- **PostgreSQL/MySQL/MSSQL** (via DatabaseConnection interface)
|
|
297
|
+
- **AWS KMS** (via Secret interface, for decryption)
|
|
298
|
+
- **ExcelJS** (via Excel interface)
|
|
299
|
+
- **pdf-lib** (via Pdf interface)
|
|
300
|
+
- **AWS CloudWatch Logs** (via ConsoleLogger interface)
|
|
301
|
+
|
|
302
|
+
## 4. Data Flow
|
|
303
|
+
|
|
304
|
+
### Type Definition Flow
|
|
305
|
+
|
|
306
|
+
```
|
|
307
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
308
|
+
│ Development Time │
|
|
309
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
310
|
+
│ │
|
|
311
|
+
│ Tenant Developer's IDE │
|
|
312
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
313
|
+
│ │ │ │
|
|
314
|
+
│ │ import type { IVolume } from '@ketrics/sdk-backend'; │ │
|
|
315
|
+
│ │ ▲ │ │
|
|
316
|
+
│ │ │ │ │
|
|
317
|
+
│ │ // TypeScript uses the │ │ │
|
|
318
|
+
│ │ // SDK types for: │ │ │
|
|
319
|
+
│ │ // - Autocomplete │ │ │
|
|
320
|
+
│ │ // - Type checking │ │ │
|
|
321
|
+
│ │ // - Error detection │ │ │
|
|
322
|
+
│ │ │ │ │
|
|
323
|
+
│ │ const volume = await ketrics.Volume.connect('uploads'); │ │
|
|
324
|
+
│ │ // ▲ │ │
|
|
325
|
+
│ │ // │ TypeScript knows this is IVolume │ │
|
|
326
|
+
│ │ // │ │ │
|
|
327
|
+
│ │ const file = await volume.get('doc.pdf'); │ │
|
|
328
|
+
│ │ // ▲ │ │
|
|
329
|
+
│ │ // │ TypeScript knows this is FileContent │ │
|
|
330
|
+
│ │ │ │
|
|
331
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
332
|
+
│ │
|
|
333
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
334
|
+
|
|
335
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
336
|
+
│ Runtime (Data Plane) │
|
|
337
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
338
|
+
│ │
|
|
339
|
+
│ VM Sandbox │
|
|
340
|
+
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
341
|
+
│ │ │ │
|
|
342
|
+
│ │ global.ketrics = { │ │
|
|
343
|
+
│ │ tenant: { id: '...', code: 'acme', name: 'ACME Corp' }, │ │
|
|
344
|
+
│ │ Volume: { │ │
|
|
345
|
+
│ │ connect: async (code) => { /* actual S3 implementation */ } │ │
|
|
346
|
+
│ │ }, │ │
|
|
347
|
+
│ │ // ... all other SDK modules injected by Data Plane │ │
|
|
348
|
+
│ │ }; │ │
|
|
349
|
+
│ │ │ │
|
|
350
|
+
│ │ // Tenant code executes with real implementations │ │
|
|
351
|
+
│ │ await handler(); │ │
|
|
352
|
+
│ │ │ │
|
|
353
|
+
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
354
|
+
│ │
|
|
355
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Error Flow
|
|
359
|
+
|
|
360
|
+
```
|
|
361
|
+
Operation Fails in Data Plane
|
|
362
|
+
│
|
|
363
|
+
▼
|
|
364
|
+
┌─────────────────────────────────────┐
|
|
365
|
+
│ Data Plane throws typed error │
|
|
366
|
+
│ e.g., new FileNotFoundError(...) │
|
|
367
|
+
└─────────────────────────────────────┘
|
|
368
|
+
│
|
|
369
|
+
▼
|
|
370
|
+
┌─────────────────────────────────────┐
|
|
371
|
+
│ Error propagates to tenant code │
|
|
372
|
+
└─────────────────────────────────────┘
|
|
373
|
+
│
|
|
374
|
+
▼
|
|
375
|
+
┌─────────────────────────────────────┐
|
|
376
|
+
│ Tenant catch block │
|
|
377
|
+
│ │
|
|
378
|
+
│ catch (error) { │
|
|
379
|
+
│ if (error instanceof ketrics. │
|
|
380
|
+
│ FileNotFoundError) { │
|
|
381
|
+
│ // TypeScript knows error.key │
|
|
382
|
+
│ // exists on this type │
|
|
383
|
+
│ } │
|
|
384
|
+
│ } │
|
|
385
|
+
└─────────────────────────────────────┘
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### Module Dependency Graph
|
|
389
|
+
|
|
390
|
+
```
|
|
391
|
+
index.ts (main entry)
|
|
392
|
+
│
|
|
393
|
+
├── context.ts (no dependencies)
|
|
394
|
+
├── console.ts (no dependencies)
|
|
395
|
+
├── http.ts (no dependencies)
|
|
396
|
+
│
|
|
397
|
+
├── volumes.ts
|
|
398
|
+
│ └── uses: stream.Readable from Node.js
|
|
399
|
+
│
|
|
400
|
+
├── errors.ts (volume errors)
|
|
401
|
+
│ └── no dependencies
|
|
402
|
+
│
|
|
403
|
+
├── databases.ts
|
|
404
|
+
│ └── no dependencies
|
|
405
|
+
│
|
|
406
|
+
├── database-errors.ts
|
|
407
|
+
│ └── no dependencies
|
|
408
|
+
│
|
|
409
|
+
├── secrets.ts
|
|
410
|
+
│ └── no dependencies
|
|
411
|
+
│
|
|
412
|
+
├── secret-errors.ts
|
|
413
|
+
│ └── no dependencies
|
|
414
|
+
│
|
|
415
|
+
├── excel.ts
|
|
416
|
+
│ └── no dependencies
|
|
417
|
+
│
|
|
418
|
+
├── excel-errors.ts
|
|
419
|
+
│ └── no dependencies
|
|
420
|
+
│
|
|
421
|
+
├── pdf.ts
|
|
422
|
+
│ └── no dependencies
|
|
423
|
+
│
|
|
424
|
+
├── pdf-errors.ts
|
|
425
|
+
│ └── no dependencies
|
|
426
|
+
│
|
|
427
|
+
├── job.ts
|
|
428
|
+
│ └── no dependencies
|
|
429
|
+
│
|
|
430
|
+
├── job-errors.ts
|
|
431
|
+
│ └── no dependencies
|
|
432
|
+
│
|
|
433
|
+
├── messages.ts
|
|
434
|
+
│ └── no dependencies
|
|
435
|
+
│
|
|
436
|
+
└── messages-errors.ts
|
|
437
|
+
└── no dependencies
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
## 5. Error Handling
|
|
441
|
+
|
|
442
|
+
### Error Hierarchy Design
|
|
443
|
+
|
|
444
|
+
Each SDK module follows a consistent error hierarchy pattern:
|
|
445
|
+
|
|
446
|
+
```
|
|
447
|
+
Error (built-in)
|
|
448
|
+
└── ModuleError (abstract base)
|
|
449
|
+
├── ModuleNotFoundError
|
|
450
|
+
├── ModuleAccessDeniedError
|
|
451
|
+
└── ModuleSpecificError1
|
|
452
|
+
└── ModuleSpecificError2
|
|
453
|
+
└── ...
|
|
409
454
|
```
|
|
410
455
|
|
|
411
|
-
###
|
|
412
|
-
|
|
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]
|
|
420
|
-
);
|
|
421
|
-
|
|
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
|
-
);
|
|
456
|
+
### Volume Errors
|
|
433
457
|
|
|
434
|
-
|
|
435
|
-
|
|
458
|
+
| Error Class | Code | Description |
|
|
459
|
+
|-------------|------|-------------|
|
|
460
|
+
| `VolumeError` | - | Abstract base class |
|
|
461
|
+
| `VolumeNotFoundError` | - | Volume code doesn't exist in tenant |
|
|
462
|
+
| `VolumeAccessDeniedError` | - | Application lacks grant to volume |
|
|
463
|
+
| `VolumePermissionDeniedError` | - | Operation requires missing permission (ReadObject, CreateObject, etc.) |
|
|
464
|
+
| `FileNotFoundError` | - | File key doesn't exist |
|
|
465
|
+
| `FileAlreadyExistsError` | - | File exists when using `ifNotExists: true` |
|
|
466
|
+
| `InvalidPathError` | - | Path contains invalid characters or traversal attempt |
|
|
467
|
+
| `FileSizeLimitError` | - | File exceeds volume's configured limit |
|
|
468
|
+
| `ContentTypeNotAllowedError` | - | MIME type not in volume's allowlist |
|
|
436
469
|
|
|
437
|
-
|
|
438
|
-
```
|
|
470
|
+
### Database Errors
|
|
439
471
|
|
|
440
|
-
|
|
472
|
+
| Error Class | Description |
|
|
473
|
+
|-------------|-------------|
|
|
474
|
+
| `DatabaseError` | Abstract base class |
|
|
475
|
+
| `DatabaseNotFoundError` | Database code doesn't exist |
|
|
476
|
+
| `DatabaseAccessDeniedError` | Application lacks grant |
|
|
477
|
+
| `DatabaseConnectionError` | Cannot establish connection (network, auth, etc.) |
|
|
478
|
+
| `DatabaseQueryError` | SQL execution failed (syntax, constraint violation) |
|
|
479
|
+
| `DatabaseTransactionError` | Transaction failed (includes `rolledBack` property) |
|
|
441
480
|
|
|
442
|
-
|
|
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
|
-
```
|
|
481
|
+
### Secret Errors
|
|
452
482
|
|
|
453
|
-
|
|
483
|
+
| Error Class | Description |
|
|
484
|
+
|-------------|-------------|
|
|
485
|
+
| `SecretError` | Abstract base class |
|
|
486
|
+
| `SecretNotFoundError` | Secret code doesn't exist |
|
|
487
|
+
| `SecretAccessDeniedError` | Application lacks grant |
|
|
488
|
+
| `SecretDecryptionError` | KMS decryption failed (key issue, corrupted data) |
|
|
454
489
|
|
|
455
|
-
|
|
490
|
+
### File Processing Errors
|
|
456
491
|
|
|
457
|
-
|
|
492
|
+
| Error Class | Description |
|
|
493
|
+
|-------------|-------------|
|
|
494
|
+
| `ExcelError` | Abstract base class |
|
|
495
|
+
| `ExcelParseError` | Cannot parse .xlsx file (invalid format, corrupted) |
|
|
496
|
+
| `ExcelWriteError` | Cannot write .xlsx file |
|
|
497
|
+
| `PdfError` | Abstract base class |
|
|
498
|
+
| `PdfParseError` | Cannot parse PDF file |
|
|
499
|
+
| `PdfWriteError` | Cannot write PDF file |
|
|
458
500
|
|
|
459
|
-
###
|
|
501
|
+
### Job Errors
|
|
460
502
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
503
|
+
| Error Class | Description |
|
|
504
|
+
|-------------|-------------|
|
|
505
|
+
| `JobError` | Abstract base class |
|
|
506
|
+
| `JobNotFoundError` | Job ID doesn't exist |
|
|
507
|
+
| `InvalidFunctionError` | Function name is empty or invalid |
|
|
508
|
+
| `CrossAppPermissionError` | Missing permission for cross-app job execution |
|
|
509
|
+
| `JobExecutionError` | Job failed during execution |
|
|
464
510
|
|
|
465
|
-
|
|
466
|
-
const response = await ketrics.http.post('https://api.stripe.com/v1/charges', data, {
|
|
467
|
-
headers: { 'Authorization': `Bearer ${apiKey}` },
|
|
468
|
-
});
|
|
469
|
-
```
|
|
511
|
+
### Message Errors
|
|
470
512
|
|
|
471
|
-
|
|
513
|
+
| Error Class | Description |
|
|
514
|
+
|-------------|-------------|
|
|
515
|
+
| `MessageError` | Base class |
|
|
516
|
+
| `MessageValidationError` | Invalid message parameters |
|
|
517
|
+
| `GroupNotFoundError` | Group code doesn't exist |
|
|
518
|
+
| `TenantGrantPermissionDeniedError` | Missing IAM-data permissions for group messaging |
|
|
472
519
|
|
|
473
|
-
|
|
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
|
-
```
|
|
520
|
+
### Type Guards
|
|
482
521
|
|
|
483
|
-
|
|
522
|
+
Each module provides type guard functions:
|
|
484
523
|
|
|
485
524
|
```typescript
|
|
486
|
-
//
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
const stripeKey = await ketrics.Secret.get('stripe-api-key');
|
|
491
|
-
const sendgridKey = await ketrics.Secret.get('sendgrid-api-key');
|
|
525
|
+
// Check if any error from module
|
|
526
|
+
if (isVolumeError(error)) { /* error is VolumeError */ }
|
|
527
|
+
if (isDatabaseError(error)) { /* error is DatabaseError */ }
|
|
528
|
+
if (isSecretError(error)) { /* error is SecretError */ }
|
|
492
529
|
|
|
493
|
-
//
|
|
494
|
-
|
|
495
|
-
|
|
530
|
+
// Check for specific error type
|
|
531
|
+
if (isVolumeErrorType(error, FileNotFoundError)) {
|
|
532
|
+
console.log(error.key); // TypeScript knows error.key exists
|
|
533
|
+
}
|
|
496
534
|
```
|
|
497
535
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
## Excel Files
|
|
536
|
+
### Error Properties
|
|
501
537
|
|
|
502
|
-
|
|
538
|
+
All error classes include:
|
|
539
|
+
- `name`: Error class name (e.g., `"FileNotFoundError"`)
|
|
540
|
+
- `message`: Human-readable description
|
|
541
|
+
- `timestamp`: When the error occurred
|
|
542
|
+
- `toJSON()`: Serializes error for logging (no stack trace, no internal details)
|
|
503
543
|
|
|
504
|
-
|
|
544
|
+
Module-specific properties:
|
|
545
|
+
- Volume errors: `volumeCode`, `operation`
|
|
546
|
+
- Database errors: `databaseCode`, `operation`
|
|
547
|
+
- Secret errors: `secretCode`, `operation`
|
|
548
|
+
- File errors: `key` (the file path)
|
|
549
|
+
- Permission errors: `requiredPermission`
|
|
505
550
|
|
|
506
|
-
|
|
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');
|
|
551
|
+
### Security Considerations
|
|
512
552
|
|
|
513
|
-
|
|
514
|
-
|
|
553
|
+
Error messages are designed to avoid leaking sensitive information:
|
|
554
|
+
- No connection strings or credentials
|
|
555
|
+
- No internal tenant IDs
|
|
556
|
+
- No AWS account information
|
|
557
|
+
- SQL limited to 200 characters in `DatabaseQueryError`
|
|
515
558
|
|
|
516
|
-
|
|
517
|
-
const sheet = workbook.getWorksheet('Sheet1');
|
|
518
|
-
if (!sheet) {
|
|
519
|
-
throw new Error('Sheet not found');
|
|
520
|
-
}
|
|
559
|
+
## 6. Usage
|
|
521
560
|
|
|
522
|
-
|
|
523
|
-
const firstSheet = workbook.getWorksheet(1);
|
|
561
|
+
### Installation
|
|
524
562
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
for (const ws of workbook.worksheets) {
|
|
528
|
-
console.log(`- ${ws.name}: ${ws.actualRowCount} rows`);
|
|
529
|
-
}
|
|
563
|
+
```bash
|
|
564
|
+
npm install @ketrics/sdk-backend
|
|
530
565
|
```
|
|
531
566
|
|
|
532
|
-
###
|
|
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
|
-
}
|
|
567
|
+
### Development Requirements
|
|
540
568
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
console.log('Headers:', headerRow.values);
|
|
569
|
+
- Node.js 24.x or higher
|
|
570
|
+
- TypeScript 5.x
|
|
544
571
|
|
|
545
|
-
|
|
546
|
-
const cell = sheet.getCell('A1');
|
|
547
|
-
console.log(cell.value, cell.text, cell.address);
|
|
572
|
+
### Build Commands
|
|
548
573
|
|
|
549
|
-
|
|
550
|
-
|
|
574
|
+
```bash
|
|
575
|
+
# Install dependencies
|
|
576
|
+
npm install
|
|
551
577
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
console.log(`Row ${rowNumber}:`, row.values);
|
|
555
|
-
});
|
|
578
|
+
# Build TypeScript to JavaScript + declarations
|
|
579
|
+
npm run build
|
|
556
580
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
console.log(`Row ${rowNumber}:`, row.values);
|
|
560
|
-
}, { includeEmpty: true });
|
|
581
|
+
# Clean build output
|
|
582
|
+
npm run clean
|
|
561
583
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
// allValues[1] = first row, allValues[1][1] = cell A1
|
|
584
|
+
# Build and publish to npm
|
|
585
|
+
npm run prepublishOnly # Runs build automatically
|
|
565
586
|
```
|
|
566
587
|
|
|
567
|
-
###
|
|
588
|
+
### Using in Tenant Applications
|
|
568
589
|
|
|
569
|
-
|
|
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
|
|
590
|
+
The SDK is designed for **type imports only** in tenant code:
|
|
588
591
|
|
|
589
592
|
```typescript
|
|
590
|
-
//
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
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
|
-
```
|
|
593
|
+
// Import types for type annotations
|
|
594
|
+
import type {
|
|
595
|
+
IVolume,
|
|
596
|
+
FileContent,
|
|
597
|
+
IDatabaseConnection,
|
|
598
|
+
DatabaseQueryResult
|
|
599
|
+
} from '@ketrics/sdk-backend';
|
|
634
600
|
|
|
635
|
-
|
|
601
|
+
// The global `ketrics` object is automatically available at runtime
|
|
602
|
+
export async function processOrder(orderId: string) {
|
|
603
|
+
// Access tenant context
|
|
604
|
+
console.log(`Processing for tenant: ${ketrics.tenant.name}`);
|
|
636
605
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
const csvContent = await workbook.toCsv('Sheet1');
|
|
606
|
+
// Log to CloudWatch
|
|
607
|
+
ketrics.console.log('Starting order processing', { orderId });
|
|
640
608
|
|
|
641
|
-
//
|
|
642
|
-
await
|
|
643
|
-
|
|
644
|
-
});
|
|
645
|
-
```
|
|
609
|
+
// Access S3 volume
|
|
610
|
+
const volume: IVolume = await ketrics.Volume.connect('orders');
|
|
611
|
+
const orderFile: FileContent = await volume.get(`${orderId}.json`);
|
|
646
612
|
|
|
647
|
-
|
|
613
|
+
// Query database
|
|
614
|
+
const db = await ketrics.DatabaseConnection.connect('orders-db');
|
|
615
|
+
const result = await db.query<{ status: string }>(
|
|
616
|
+
'UPDATE orders SET status = ? WHERE id = ? RETURNING status',
|
|
617
|
+
['processed', orderId]
|
|
618
|
+
);
|
|
619
|
+
await db.close();
|
|
648
620
|
|
|
649
|
-
|
|
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';
|
|
621
|
+
return { success: true, newStatus: result.rows[0].status };
|
|
658
622
|
}
|
|
659
623
|
```
|
|
660
624
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
## PDF Files
|
|
664
|
-
|
|
665
|
-
Read, create, and modify PDF files using the pdf-lib wrapper:
|
|
666
|
-
|
|
667
|
-
### Reading PDF Files
|
|
625
|
+
### Error Handling Pattern
|
|
668
626
|
|
|
669
627
|
```typescript
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
628
|
+
export async function safeVolumeRead(volumeCode: string, key: string) {
|
|
629
|
+
try {
|
|
630
|
+
const volume = await ketrics.Volume.connect(volumeCode);
|
|
631
|
+
return await volume.get(key);
|
|
632
|
+
} catch (error) {
|
|
633
|
+
// Check specific error types first
|
|
634
|
+
if (error instanceof ketrics.VolumeNotFoundError) {
|
|
635
|
+
return { error: 'VOLUME_NOT_FOUND', volumeCode: error.volumeCode };
|
|
636
|
+
}
|
|
637
|
+
if (error instanceof ketrics.FileNotFoundError) {
|
|
638
|
+
return { error: 'FILE_NOT_FOUND', key: error.key };
|
|
639
|
+
}
|
|
640
|
+
if (error instanceof ketrics.VolumePermissionDeniedError) {
|
|
641
|
+
return { error: 'PERMISSION_DENIED', permission: error.requiredPermission };
|
|
642
|
+
}
|
|
678
643
|
|
|
679
|
-
//
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
644
|
+
// Fallback for any other volume error
|
|
645
|
+
if (ketrics.isVolumeError(error)) {
|
|
646
|
+
return { error: 'VOLUME_ERROR', message: error.message };
|
|
647
|
+
}
|
|
683
648
|
|
|
684
|
-
//
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
console.log(`Page size: ${page.width}x${page.height}`);
|
|
649
|
+
// Re-throw unexpected errors
|
|
650
|
+
throw error;
|
|
651
|
+
}
|
|
688
652
|
}
|
|
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
653
|
```
|
|
779
654
|
|
|
780
|
-
###
|
|
655
|
+
### Complete Example: Report Generation
|
|
781
656
|
|
|
782
657
|
```typescript
|
|
783
|
-
|
|
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
|
-
```
|
|
658
|
+
import type { IExcelWorkbook, IPdfDocument } from '@ketrics/sdk-backend';
|
|
928
659
|
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
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),
|
|
1017
|
-
});
|
|
660
|
+
interface SalesRecord {
|
|
661
|
+
id: number;
|
|
662
|
+
product: string;
|
|
663
|
+
quantity: number;
|
|
664
|
+
total: number;
|
|
1018
665
|
}
|
|
1019
666
|
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
await
|
|
1023
|
-
contentType: 'application/pdf',
|
|
1024
|
-
});
|
|
1025
|
-
```
|
|
1026
|
-
|
|
1027
|
-
### Complete PDF Generation Example
|
|
667
|
+
export async function generateSalesReport(month: string) {
|
|
668
|
+
// Get secrets for external API
|
|
669
|
+
const apiKey = await ketrics.Secret.get('analytics-api-key');
|
|
1028
670
|
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
'SELECT * FROM orders WHERE id = ?',
|
|
1035
|
-
[orderId]
|
|
671
|
+
// Query database
|
|
672
|
+
const db = await ketrics.DatabaseConnection.connect('sales-db');
|
|
673
|
+
const sales = await db.query<SalesRecord>(
|
|
674
|
+
'SELECT * FROM sales WHERE month = ? ORDER BY total DESC',
|
|
675
|
+
[month]
|
|
1036
676
|
);
|
|
1037
|
-
const order = orderResult.rows[0];
|
|
1038
677
|
await db.close();
|
|
1039
678
|
|
|
1040
|
-
// Create
|
|
1041
|
-
const
|
|
1042
|
-
|
|
1043
|
-
doc.setAuthor(ketrics.tenant.name);
|
|
679
|
+
// Create Excel report
|
|
680
|
+
const workbook: IExcelWorkbook = ketrics.Excel.create();
|
|
681
|
+
const sheet = workbook.addWorksheet('Sales Report');
|
|
1044
682
|
|
|
1045
|
-
|
|
1046
|
-
const
|
|
683
|
+
sheet.addRow(['ID', 'Product', 'Quantity', 'Total']);
|
|
684
|
+
for (const sale of sales.rows) {
|
|
685
|
+
sheet.addRow([sale.id, sale.product, sale.quantity, sale.total]);
|
|
686
|
+
}
|
|
1047
687
|
|
|
1048
|
-
|
|
1049
|
-
const boldFont = await doc.embedStandardFont('HelveticaBold');
|
|
1050
|
-
const regularFont = await doc.embedStandardFont('Helvetica');
|
|
688
|
+
const excelBuffer = await workbook.toBuffer();
|
|
1051
689
|
|
|
1052
|
-
//
|
|
1053
|
-
await
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
size: 24,
|
|
1057
|
-
font: boldFont,
|
|
1058
|
-
});
|
|
690
|
+
// Create PDF summary
|
|
691
|
+
const pdf: IPdfDocument = await ketrics.Pdf.create();
|
|
692
|
+
pdf.setTitle(`Sales Report - ${month}`);
|
|
693
|
+
pdf.setAuthor(ketrics.requestor.name);
|
|
1059
694
|
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
y: height - 90,
|
|
1063
|
-
size: 18,
|
|
1064
|
-
font: regularFont,
|
|
1065
|
-
color: ketrics.Pdf.rgb(0.4, 0.4, 0.4),
|
|
1066
|
-
});
|
|
695
|
+
const page = pdf.addPage('A4');
|
|
696
|
+
const font = await pdf.embedStandardFont('HelveticaBold');
|
|
1067
697
|
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
x: 50,
|
|
1071
|
-
y: height - 150,
|
|
1072
|
-
size: 12,
|
|
1073
|
-
font: boldFont,
|
|
698
|
+
await page.drawText(`Sales Report: ${month}`, {
|
|
699
|
+
x: 50, y: 750, size: 24, font
|
|
1074
700
|
});
|
|
1075
701
|
|
|
1076
|
-
await page.drawText(
|
|
1077
|
-
x: 50,
|
|
1078
|
-
y: height - 170,
|
|
1079
|
-
size: 12,
|
|
1080
|
-
font: regularFont,
|
|
702
|
+
await page.drawText(`Total Records: ${sales.rowCount}`, {
|
|
703
|
+
x: 50, y: 700, size: 14
|
|
1081
704
|
});
|
|
1082
705
|
|
|
1083
|
-
|
|
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
|
-
});
|
|
706
|
+
const pdfBuffer = await pdf.toBuffer();
|
|
1092
707
|
|
|
1093
|
-
|
|
1094
|
-
await
|
|
1095
|
-
await
|
|
1096
|
-
await
|
|
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 });
|
|
708
|
+
// Save to volume
|
|
709
|
+
const volume = await ketrics.Volume.connect('reports');
|
|
710
|
+
await volume.put(`${month}/sales.xlsx`, excelBuffer);
|
|
711
|
+
await volume.put(`${month}/summary.pdf`, pdfBuffer);
|
|
1118
712
|
|
|
1119
|
-
//
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
y: 30,
|
|
1123
|
-
size: 8,
|
|
1124
|
-
color: ketrics.Pdf.rgb(0.6, 0.6, 0.6),
|
|
713
|
+
// Generate download URLs
|
|
714
|
+
const excelUrl = await volume.generateDownloadUrl(`${month}/sales.xlsx`, {
|
|
715
|
+
expiresIn: 86400
|
|
1125
716
|
});
|
|
1126
717
|
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
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
|
-
},
|
|
718
|
+
ketrics.console.log('Report generated', {
|
|
719
|
+
month,
|
|
720
|
+
recordCount: sales.rowCount
|
|
1136
721
|
});
|
|
1137
722
|
|
|
1138
723
|
return {
|
|
1139
724
|
success: true,
|
|
1140
|
-
|
|
725
|
+
downloadUrl: excelUrl.url,
|
|
726
|
+
expiresAt: excelUrl.expiresAt
|
|
1141
727
|
};
|
|
1142
728
|
}
|
|
1143
729
|
```
|
|
1144
730
|
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
## Error Handling
|
|
1148
|
-
|
|
1149
|
-
All error classes are available on the `ketrics` object for `instanceof` checks:
|
|
1150
|
-
|
|
1151
|
-
### Volume Errors
|
|
1152
|
-
|
|
1153
|
-
```typescript
|
|
1154
|
-
try {
|
|
1155
|
-
const volume = await ketrics.Volume.connect('uploads');
|
|
1156
|
-
const file = await volume.get('missing.txt');
|
|
1157
|
-
} catch (error) {
|
|
1158
|
-
if (error instanceof ketrics.VolumeNotFoundError) {
|
|
1159
|
-
console.log(`Volume '${error.volumeCode}' not found`);
|
|
1160
|
-
} else if (error instanceof ketrics.VolumeAccessDeniedError) {
|
|
1161
|
-
console.log(`No access to volume '${error.volumeCode}'`);
|
|
1162
|
-
} else if (error instanceof ketrics.VolumePermissionDeniedError) {
|
|
1163
|
-
console.log(`Missing permission: ${error.requiredPermission}`);
|
|
1164
|
-
} else if (error instanceof ketrics.FileNotFoundError) {
|
|
1165
|
-
console.log(`File '${error.key}' not found`);
|
|
1166
|
-
} else if (error instanceof ketrics.FileAlreadyExistsError) {
|
|
1167
|
-
console.log(`File '${error.key}' already exists`);
|
|
1168
|
-
} else if (error instanceof ketrics.FileSizeLimitError) {
|
|
1169
|
-
console.log(`File too large: ${error.size} > ${error.maxSize}`);
|
|
1170
|
-
} else if (error instanceof ketrics.ContentTypeNotAllowedError) {
|
|
1171
|
-
console.log(`Type '${error.contentType}' not allowed. Use: ${error.allowedTypes.join(', ')}`);
|
|
1172
|
-
} else if (error instanceof ketrics.InvalidPathError) {
|
|
1173
|
-
console.log(`Invalid path '${error.path}': ${error.reason}`);
|
|
1174
|
-
} else if (error instanceof ketrics.VolumeError) {
|
|
1175
|
-
// Base class catches all volume errors
|
|
1176
|
-
console.log(`Volume error: ${error.message}`);
|
|
1177
|
-
console.log(error.toJSON());
|
|
1178
|
-
} else {
|
|
1179
|
-
throw error;
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
```
|
|
1183
|
-
|
|
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
|
-
```
|
|
731
|
+
### Testing
|
|
1211
732
|
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
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
|
-
```
|
|
733
|
+
Since this package contains only type definitions, testing focuses on ensuring:
|
|
734
|
+
1. TypeScript compiles without errors
|
|
735
|
+
2. All types are correctly exported
|
|
736
|
+
3. Error class hierarchies work with `instanceof`
|
|
1232
737
|
|
|
1233
|
-
|
|
738
|
+
The actual runtime behavior is tested in the Data Plane API where the SDK is implemented.
|
|
1234
739
|
|
|
1235
|
-
```
|
|
1236
|
-
|
|
1237
|
-
|
|
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
|
|
1280
|
-
|
|
1281
|
-
| Error Class | When Thrown |
|
|
1282
|
-
|-------------|-------------|
|
|
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
|
-
---
|
|
1330
|
-
|
|
1331
|
-
## Type Exports
|
|
1332
|
-
|
|
1333
|
-
All types are exported for use in your application:
|
|
1334
|
-
|
|
1335
|
-
```typescript
|
|
1336
|
-
import type {
|
|
1337
|
-
// Context
|
|
1338
|
-
TenantContext,
|
|
1339
|
-
ApplicationContext,
|
|
1340
|
-
UserContext,
|
|
1341
|
-
EnvironmentContext,
|
|
1342
|
-
|
|
1343
|
-
// Console
|
|
1344
|
-
ConsoleLogger,
|
|
1345
|
-
|
|
1346
|
-
// HTTP
|
|
1347
|
-
HttpRequestConfig,
|
|
1348
|
-
HttpResponse,
|
|
1349
|
-
HttpClient,
|
|
740
|
+
```bash
|
|
741
|
+
# Verify types compile correctly
|
|
742
|
+
npm run build
|
|
1350
743
|
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
PutContent,
|
|
1354
|
-
FileContent,
|
|
1355
|
-
FileMetadata,
|
|
1356
|
-
FileInfo,
|
|
1357
|
-
PutOptions,
|
|
1358
|
-
PutResult,
|
|
1359
|
-
DeleteResult,
|
|
1360
|
-
DeleteByPrefixResult,
|
|
1361
|
-
ListOptions,
|
|
1362
|
-
ListResult,
|
|
1363
|
-
CopyOptions,
|
|
1364
|
-
CopyResult,
|
|
1365
|
-
MoveOptions,
|
|
1366
|
-
MoveResult,
|
|
1367
|
-
DownloadUrlOptions,
|
|
1368
|
-
UploadUrlOptions,
|
|
1369
|
-
PresignedUrl,
|
|
1370
|
-
|
|
1371
|
-
// Databases
|
|
1372
|
-
IDatabaseConnection,
|
|
1373
|
-
DatabaseQueryResult,
|
|
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,
|
|
1405
|
-
|
|
1406
|
-
// Main SDK
|
|
1407
|
-
KetricsSdkV1,
|
|
1408
|
-
} from '@ketrics/sdk-backend';
|
|
744
|
+
# Type checking is performed as part of build
|
|
745
|
+
# No separate test command needed for type-only package
|
|
1409
746
|
```
|
|
1410
747
|
|
|
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');
|
|
748
|
+
### Publishing
|
|
1451
749
|
|
|
1452
|
-
|
|
1453
|
-
|
|
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
|
-
}
|
|
750
|
+
```bash
|
|
751
|
+
# Bump version in package.json
|
|
752
|
+
npm version patch|minor|major
|
|
1459
753
|
|
|
1460
|
-
|
|
1461
|
-
|
|
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
|
-
}
|
|
754
|
+
# Build and publish
|
|
755
|
+
npm publish --access public
|
|
1507
756
|
```
|
|
1508
757
|
|
|
1509
|
-
---
|
|
1510
|
-
|
|
1511
758
|
## License
|
|
1512
759
|
|
|
1513
760
|
MIT
|