@ketrics/sdk-backend 0.7.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,760 +1,720 @@
1
- # @ketrics/sdk-backend
2
-
3
- TypeScript type definitions and runtime interfaces for building tenant applications on the Ketrics platform.
4
-
5
- ## 1. Overview
6
-
7
- ### Purpose
8
-
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.
10
-
11
- ### Role in the Architecture
1
+ # Ketrics SDK for Backend (@ketrics/sdk-backend)
2
+
3
+ ## Overview
4
+
5
+ The Ketrics SDK for Backend is a TypeScript type definition library that provides the complete interface for building tenant applications on the Ketrics platform. This SDK defines the `ketrics` global object—a comprehensive runtime API that tenant code uses to access platform features including storage, databases, secrets management, document generation, background jobs, and messaging.
6
+
7
+ **Purpose**: This package serves as the type contract between tenant application code and the Ketrics runtime execution environment. It is published as an npm package and consumed by tenant developers building applications that run in isolated VM sandboxes.
8
+
9
+ **Architecture Context**:
10
+ - Tenant applications run in ECS Fargate containers with Node.js 24+
11
+ - The global `ketrics` object is injected at runtime by the Ketrics platform
12
+ - This SDK provides TypeScript definitions so tenant code has IDE support and type safety
13
+ - The SDK is a pure type definition library (no implementation)
14
+
15
+ **Key Responsibilities**:
16
+ 1. Define all TypeScript interfaces and types for the `ketrics` global object
17
+ 2. Provide comprehensive error classes for all SDK operations
18
+ 3. Establish the contract between tenant code and the Ketrics runtime
19
+ 4. Enable type-safe access to platform capabilities (storage, databases, secrets, etc.)
20
+
21
+ ## Business Logic
22
+
23
+ ### What Problem Does This Solve?
24
+
25
+ Tenant applications need standardized, type-safe access to platform infrastructure without direct access to underlying services. This SDK enables developers to:
26
+
27
+ - Read/write files to S3-backed volumes with permission controls
28
+ - Query external databases with transaction support
29
+ - Retrieve encrypted secrets via KMS
30
+ - Generate PDF and Excel documents programmatically
31
+ - Execute long-running tasks asynchronously
32
+ - Send messages to users with multiple channels (inbox, push notifications)
33
+ - Make external API calls with a standardized HTTP client
34
+ - Access tenant and application metadata
35
+ - Log to CloudWatch with proper context
36
+
37
+ ### Core Workflows and Processes
38
+
39
+ **1. Storage Access (Volumes)**
40
+ - Connect to an S3-backed volume by code
41
+ - Get files (with content, metadata, etag)
42
+ - Put files (with metadata and conditional create)
43
+ - Delete files (single or by prefix)
44
+ - List files with pagination and filtering
45
+ - Copy/move files within volumes
46
+ - Generate presigned URLs for direct browser access
47
+
48
+ **2. Database Operations**
49
+ - Connect to external databases by code
50
+ - Execute SELECT queries with typed results
51
+ - Execute INSERT/UPDATE/DELETE with affected row counts
52
+ - Run queries within transactions (auto-commit on success, auto-rollback on error)
53
+ - Connection pooling and lifecycle management
54
+
55
+ **3. Secrets Management**
56
+ - Retrieve encrypted secrets by code
57
+ - Check if a secret exists before accessing
58
+ - Secrets are decrypted using tenant-specific KMS keys
59
+ - Subject to application access grants
60
+
61
+ **4. Document Generation**
62
+ - **Excel**: Read/parse existing Excel files, create new workbooks, add worksheets, format cells, write files
63
+ - **PDF**: Read/parse existing PDFs, create new documents, draw text/shapes/images, embed fonts, write files
64
+
65
+ **5. Background Jobs**
66
+ - Queue async function execution with payloads
67
+ - Support cross-app jobs with permission validation
68
+ - Track job status (pending, running, completed, failed)
69
+ - Idempotency keys to prevent duplicate execution
70
+ - Configurable timeouts (default 5min, max 15min)
71
+
72
+ **6. User Messaging**
73
+ - Send messages to individual users
74
+ - Bulk send to multiple users
75
+ - Send to group members (requires IAM-data permissions)
76
+ - Support for multiple channels: inbox (always) + push notifications (optional)
77
+ - Priority levels: LOW, MEDIUM, HIGH
78
+ - Custom action URLs for deep linking
79
+
80
+ ### Business Rules Implemented
81
+
82
+ **Access Control**:
83
+ - Volume access requires an access grant to the specific volume
84
+ - Database access requires an access grant with specific permissions
85
+ - Secrets access requires an access grant with decrypt permission
86
+ - Messages to groups require IAM-data tenant permissions (via application role)
87
+ - Cross-app jobs require application grants with background job permission
88
+
89
+ **Data Boundaries**:
90
+ - Requestor context distinguishes between users (type: "USER") and service accounts (type: "SERVICE_ACCOUNT")
91
+ - Requestor's application permissions determine what operations are allowed
92
+ - Each tenant's secrets are encrypted with tenant-specific KMS keys
93
+
94
+ **Input/Output Expectations**:
95
+ - File operations support streams, buffers, and strings
96
+ - Database queries return generic typed result objects
97
+ - Job operations return UUID job identifiers
98
+ - List operations support pagination with cursors
99
+ - All timestamps are ISO 8601 strings
100
+
101
+ **Edge Cases Handled**:
102
+ - File already exists with `ifNotExists` option
103
+ - File size limits enforced on put operations
104
+ - Content type restrictions on volumes
105
+ - Invalid path characters and path traversal detection
106
+ - Connection timeouts with retry semantics
107
+ - Null/undefined secret values
108
+ - Transaction rollback on error
109
+ - Job timeout after max execution time
110
+ - Bulk messaging with partial failures
111
+
112
+ ## Technical Details
113
+
114
+ ### Technology Stack and Dependencies
115
+
116
+ **Runtime Environment**:
117
+ - Node.js 24.0.0+ (specified in package.json engines)
118
+ - TypeScript 5.0.0+ for compilation
119
+ - ES2020 target with CommonJS module format
120
+
121
+ **Package Dependencies**:
122
+ - `@types/node@^24.10.2` - Node.js type definitions for stream types (Readable)
123
+ - `typescript@^5.0.0` - TypeScript compiler (dev dependency)
124
+
125
+ **Key Type System Features**:
126
+ - Strict mode enabled for type safety
127
+ - Declaration maps for source map debugging
128
+ - Module resolution set to "node" for standard npm resolution
129
+ - Force consistent casing for file imports
130
+
131
+ ### File Structure with Purpose
12
132
 
13
133
  ```
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
- └─────────────────────────────────────────────────────────────────────────────┘
40
- ```
41
-
42
- The SDK sits between the Data Plane runtime (which injects the actual `ketrics` global object) and tenant application code. It provides:
43
-
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
48
-
49
- ### Key Responsibilities
50
-
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
56
-
57
- ### Boundaries
58
-
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
63
-
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
68
-
69
- ## 2. Business Logic
70
-
71
- ### Problem Solved
72
-
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
78
-
79
- ### Core Workflows
80
-
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.
83
-
84
- ```typescript
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
134
+ src/
135
+ ├── index.ts # Main entry point, exports all public types and interfaces
136
+ ├── context.ts # Runtime context types (tenant, app, requestor, environment)
137
+ ├── console.ts # Console logger interface
138
+ ├── http.ts # HTTP client interface for external API calls
139
+ ├── databases.ts # Database connection interfaces and types
140
+ ├── database-errors.ts # Database error class hierarchy
141
+ ├── volumes.ts # Volume storage interfaces and types
142
+ ├── errors.ts # Volume error class hierarchy
143
+ ├── secrets.ts # Secret retrieval interfaces
144
+ ├── secret-errors.ts # Secret error classes
145
+ ├── excel.ts # Excel workbook interfaces and types
146
+ ├── excel-errors.ts # Excel error classes
147
+ ├── pdf.ts # PDF document interfaces and types
148
+ ├── pdf-errors.ts # PDF error classes
149
+ ├── job.ts # Background job types and interfaces
150
+ ├── job-errors.ts # Job execution error classes
151
+ ├── messages.ts # Message sending interfaces and types
152
+ ├── messages-errors.ts # Message error classes
153
+ └── dist/ # Compiled JavaScript and .d.ts files (generated)
89
154
  ```
90
155
 
91
- #### 2. Resource Access Pattern
92
- All resources (volumes, databases, secrets) follow a consistent pattern:
156
+ ### Key Functions/Classes and Their Purposes
157
+
158
+ **Context Interfaces** (`context.ts`):
159
+ - `TenantContext`: Tenant ID, code, and name
160
+ - `ApplicationContext`: Application ID, code, name, version, deployment ID
161
+ - `RequestorContext`: Identifies if request is from USER or SERVICE_ACCOUNT with permissions
162
+ - `RuntimeContext`: Node.js version, AWS region, runtime platform
163
+ - `EnvironmentVariables`: Tenant application environment variables
164
+
165
+ **SDK Manager Classes** (defined in index.ts as static interfaces):
166
+
167
+ 1. **Volume** - S3-backed file storage
168
+ - `connect(volumeCode: string): Promise<IVolume>`
169
+ - Methods: get, put, delete, deleteByPrefix, list, copy, move, downloadUrl, uploadUrl
170
+
171
+ 2. **DatabaseConnection** - External database access
172
+ - `connect(databaseCode: string): Promise<IDatabaseConnection>`
173
+ - Methods: query<T>(), execute(), transaction(), close()
174
+
175
+ 3. **Secret** - Encrypted secret retrieval
176
+ - `get(secretCode: string): Promise<string>`
177
+ - `exists(secretCode: string): Promise<boolean>`
178
+
179
+ 4. **Excel** - Excel file operations
180
+ - `read(buffer: Buffer): Promise<IExcelWorkbook>`
181
+ - `create(): IExcelWorkbook`
182
+ - Workbook methods: getWorksheet, addWorksheet, writeFile
183
+
184
+ 5. **Pdf** - PDF document operations
185
+ - `read(buffer: Buffer): Promise<IPdfDocument>`
186
+ - `create(): Promise<IPdfDocument>`
187
+ - `rgb(r, g, b): PdfRgbColor`
188
+ - Document methods: page manipulation, drawing, embedding
189
+
190
+ 6. **Job** - Background job execution
191
+ - `runInBackground(params: RunInBackgroundParams): Promise<string>` - Returns job ID
192
+ - `getStatus(jobId: string): Promise<JobStatus>`
193
+ - `list(params?: JobListParams): Promise<JobListResult>`
194
+
195
+ 7. **Messages** - User messaging
196
+ - `send(params: SendMessageParams): Promise<SendMessageResult>`
197
+ - `sendBulk(params: SendBulkMessageParams): Promise<SendBulkMessageResult>`
198
+ - `sendToGroup(params: SendGroupMessageParams): Promise<SendBulkMessageResult>`
199
+
200
+ **Error Class Hierarchy**:
201
+ Each feature domain (Volume, Database, Secret, Excel, PDF, Job, Messages) defines:
202
+ - Abstract base error class (extends Error)
203
+ - Specific error subclasses for different failure scenarios
204
+ - Type guards: `is[Feature]Error()`, `is[Feature]ErrorType()`
205
+
206
+ Example Volume errors:
207
+ - `VolumeError` (abstract base)
208
+ - `VolumeNotFoundError`, `VolumeAccessDeniedError`, `VolumePermissionDeniedError`
209
+ - `FileNotFoundError`, `FileAlreadyExistsError`, `InvalidPathError`
210
+ - `FileSizeLimitError`, `ContentTypeNotAllowedError`
211
+
212
+ ### Configuration Options and Environment Variables
213
+
214
+ **Tenant Environment Variables** (via `ketrics.environment`):
215
+ - Application-defined key-value pairs passed at deployment time
216
+ - All keys are uppercase with letters, numbers, underscores only
217
+ - Accessed at runtime for configuration (API keys, feature flags, etc.)
218
+
219
+ **Volume Configuration**:
220
+ - Volume codes define which S3 bucket is accessed
221
+ - Permissions defined per volume: ReadObject, CreateObject, UpdateObject, DeleteObject, ListObjects
222
+ - Content-type restrictions enforced on put operations
223
+ - File size limits enforced per volume
224
+
225
+ **Database Configuration**:
226
+ - Database codes identify external database servers (PostgreSQL, MySQL, etc.)
227
+ - Permissions per database: read, write, admin
228
+ - Connection pooling managed by runtime
229
+ - Query timeouts configurable
230
+
231
+ **Excel Configuration**:
232
+ - Tab colors, default row height, default column width configurable per worksheet
233
+ - Column definitions with header, key, width
234
+
235
+ **PDF Configuration**:
236
+ - Standard page sizes: A4, Letter, Legal, Tabloid, A3, A5
237
+ - Standard fonts: Courier variants, Helvetica variants, TimesRoman variants, Symbol, ZapfDingbats
238
+ - RGB color values (0-1 range)
239
+ - Drawing options: size, opacity, rotation, line height, max width
240
+
241
+ **Job Configuration**:
242
+ - Timeout range: 1ms to 900,000ms (15 minutes), default 300,000ms (5 minutes)
243
+ - Idempotency keys prevent duplicate job creation
244
+ - Cross-app execution requires application grants
245
+
246
+ **Message Configuration**:
247
+ - Subject max length: 200 chars
248
+ - Body max length: 10,000 chars (markdown supported)
249
+ - Priority levels: LOW, MEDIUM, HIGH
250
+ - Channels: inbox (always enabled) + push notifications (optional per message)
93
251
 
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`
252
+ ### External Integrations
97
253
 
254
+ **AWS S3** (Volumes):
255
+ - S3 buckets mapped to volume codes
256
+ - Presigned URLs for direct browser download/upload
257
+ - Versioning support via version IDs
258
+ - ETag support for content hashing
259
+
260
+ **External Databases**:
261
+ - PostgreSQL, MySQL, and other JDBC-compatible databases
262
+ - Connection strings and credentials never exposed to tenant code
263
+ - Parameterized queries to prevent SQL injection
264
+ - Transaction support with automatic rollback
265
+
266
+ **AWS KMS** (Secrets):
267
+ - Tenant-specific KMS keys for secret encryption
268
+ - Secrets decrypted at request time
269
+ - Decryption errors surface as SecretDecryptionError
270
+
271
+ **CloudWatch** (Logging):
272
+ - Console logs forwarded to CloudWatch with tenant/app/requestor context
273
+ - Proper log grouping and filtering capabilities
274
+
275
+ **Application Job Queue**:
276
+ - Background job execution in same or different applications
277
+ - Status tracking in centralized job store
278
+ - Timeout enforcement and error capture
279
+
280
+ **User Messaging Platform**:
281
+ - Inbox storage for messages
282
+ - Push notification delivery
283
+ - Group membership resolution via IAM service
284
+
285
+ ## Data Flow
286
+
287
+ ### How Data Enters the Component
288
+
289
+ 1. **Context Injection**: At runtime, the Ketrics platform injects a global `ketrics` object with:
290
+ - Tenant metadata (ID, code, name)
291
+ - Application metadata (ID, code, version, deploymentId)
292
+ - Requestor information (type, ID, permissions)
293
+ - Runtime environment (Node.js version, AWS region)
294
+ - Environment variables (key-value pairs)
295
+
296
+ 2. **Function Parameters**: Tenant code receives:
297
+ - HTTP request payloads (JSON, form data, etc.)
298
+ - File uploads to volumes
299
+ - Database query parameters
300
+ - Job payloads
301
+ - Message content from users
302
+
303
+ 3. **External Services**: Data retrieved from:
304
+ - S3 via volume.get()
305
+ - Database queries via db.query()
306
+ - Secrets via Secret.get()
307
+ - Uploaded Excel/PDF files via Excel.read(), Pdf.read()
308
+
309
+ ### Transformations Applied
310
+
311
+ **Volume Operations**:
312
+ - Raw S3 objects → FileContent (buffer + metadata)
313
+ - List operations → Paginated FileInfo arrays with cursors
314
+ - Put operations → PutResult with etag and optional versionId
315
+ - Copy/move → CopyResult/MoveResult with operation status
316
+
317
+ **Database Operations**:
318
+ - Raw SQL results → DatabaseQueryResult<T> with typed rows
319
+ - Execute operations → DatabaseExecuteResult with affectedRows and insertId
320
+ - Transactions → Implicit begin/commit/rollback with error handling
321
+
322
+ **Document Operations**:
323
+ - Raw file buffers → IExcelWorkbook/IPdfDocument with method interfaces
324
+ - Workbook operations → Cells, rows, worksheets with formatting
325
+ - PDF drawing → Rendered text, shapes, images on pages
326
+
327
+ **Job Operations**:
328
+ - RunInBackgroundParams → Job ID string
329
+ - Job polling → JobStatus with timestamps and error details
330
+ - Job listing → Paginated JobStatus arrays with cursor
331
+
332
+ **Message Operations**:
333
+ - SendMessageParams → SendMessageResult with messageId and status
334
+ - Bulk operations → Aggregated counts (sent, failed) + per-user results
335
+ - Group operations → Member resolution + bulk message delivery
336
+
337
+ ### How Data Exits or Is Persisted
338
+
339
+ **Persistent Storage**:
340
+ - Volume.put() → Data stored in S3 (encrypted at rest)
341
+ - Database execute() → Data persisted in external database
342
+ - Excel workbook → Buffer written to volume or returned to caller
343
+ - PDF document → Buffer written to volume or returned to caller
344
+ - Message → Stored in user inbox, push notification sent
345
+
346
+ **Return Values**:
347
+ - Query results → Returned synchronously to caller
348
+ - File content → Streamed or buffered to caller
349
+ - Generated documents → Returned as buffers
350
+ - Job IDs → Returned immediately, execution async
351
+ - Operation results → Status objects with metadata
352
+
353
+ **External API Calls**:
354
+ - ketrics.http → External API responses via HttpResponse<T>
355
+ - Logging → CloudWatch via ketrics.console
356
+ - Notifications → Push delivery platform
357
+ - Messages → Inbox storage + notification delivery
358
+
359
+ ## Error Handling
360
+
361
+ ### Error Scenarios Considered
362
+
363
+ Each feature domain handles specific error scenarios:
364
+
365
+ **Volume Errors**:
366
+ - `VolumeNotFoundError`: Volume code doesn't exist in tenant's namespace
367
+ - `VolumeAccessDeniedError`: Application lacks access grant
368
+ - `VolumePermissionDeniedError`: Specific operation (read/write/delete/list) not permitted
369
+ - `FileNotFoundError`: Requested file doesn't exist
370
+ - `FileAlreadyExistsError`: File exists when `ifNotExists: true`
371
+ - `InvalidPathError`: Path contains invalid characters or traversal attempts (../)
372
+ - `FileSizeLimitError`: File exceeds volume's size limit
373
+ - `ContentTypeNotAllowedError`: MIME type not allowed for volume
374
+
375
+ **Database Errors**:
376
+ - `DatabaseNotFoundError`: Database code doesn't exist
377
+ - `DatabaseAccessDeniedError`: No access grant to database
378
+ - `DatabaseConnectionError`: Cannot establish connection (network, credentials, server down)
379
+ - `DatabaseQueryError`: SQL syntax error, constraint violation, etc.
380
+ - `DatabaseTransactionError`: Transaction commit/rollback failed
381
+
382
+ **Secret Errors**:
383
+ - `SecretNotFoundError`: Secret code doesn't exist
384
+ - `SecretAccessDeniedError`: Application lacks access grant
385
+ - `SecretDecryptionError`: KMS decryption failed (corrupted value, key revoked)
386
+
387
+ **Excel Errors**:
388
+ - `ExcelParseError`: Provided buffer is not valid Excel format
389
+ - `ExcelWriteError`: File write operation failed
390
+
391
+ **PDF Errors**:
392
+ - `PdfParseError`: Provided buffer is not valid PDF format
393
+ - `PdfWriteError`: File write operation failed
394
+
395
+ **Job Errors**:
396
+ - `JobNotFoundError`: Job ID doesn't exist
397
+ - `InvalidFunctionError`: Target function name invalid or missing
398
+ - `CrossAppPermissionError`: Application lacks permission for cross-app job
399
+ - `EltJobExecutionError`: Job failed during execution (timeout, runtime error)
400
+
401
+ **Message Errors**:
402
+ - `MessageValidationError`: Subject/body too long, invalid parameters
403
+ - `GroupNotFoundError`: Group code doesn't exist
404
+ - `TenantGrantPermissionDeniedError`: Application lacks IAM-data permissions for group access
405
+
406
+ ### Retry Logic or Fallback Mechanisms
407
+
408
+ **Built-in Retry Behaviors**:
409
+ - Database connection pooling automatically handles connection reuse
410
+ - Connection timeouts trigger error rather than unlimited retries (tenant responsibility to retry)
411
+ - Failed bulk messages don't prevent other messages in batch (partial success tracked)
412
+
413
+ **Tenant-Implemented Retry Patterns**:
98
414
  ```typescript
99
- try {
100
- const volume = await ketrics.Volume.connect('uploads'); // Step 1
101
- const file = await volume.get('report.pdf'); // Step 2
102
- } catch (error) {
103
- if (error instanceof ketrics.FileNotFoundError) { // Step 3
104
- // Handle specific error
415
+ // Retry with exponential backoff
416
+ async function retryWithBackoff(fn, maxAttempts = 3) {
417
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
418
+ try {
419
+ return await fn();
420
+ } catch (error) {
421
+ if (attempt === maxAttempts) throw error;
422
+ const delay = Math.pow(2, attempt) * 1000;
423
+ await new Promise(resolve => setTimeout(resolve, delay));
424
+ }
105
425
  }
106
426
  }
107
- ```
108
-
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, ...
229
- }
230
- ```
231
-
232
- #### IVolume (S3-Backed Storage)
233
-
234
- ```typescript
235
- interface IVolume {
236
- readonly code: string;
237
- readonly permissions: ReadonlySet<string>;
238
-
239
- // Read
240
- get(key: string): Promise<FileContent>;
241
- exists(key: string): Promise<boolean>;
242
- getMetadata(key: string): Promise<FileMetadata>;
243
-
244
- // Write
245
- put(key: string, content: PutContent, options?: PutOptions): Promise<PutResult>;
246
-
247
- // Delete
248
- delete(key: string): Promise<DeleteResult>;
249
- deleteByPrefix(prefix: string): Promise<DeleteByPrefixResult>;
250
-
251
- // List
252
- list(options?: ListOptions): Promise<ListResult>;
253
-
254
- // File Management
255
- copy(src: string, dest: string, options?: CopyOptions): Promise<CopyResult>;
256
- move(src: string, dest: string, options?: MoveOptions): Promise<MoveResult>;
257
-
258
- // URL Generation
259
- generateDownloadUrl(key: string, options?: DownloadUrlOptions): Promise<PresignedUrl>;
260
- generateUploadUrl(key: string, options?: UploadUrlOptions): Promise<PresignedUrl>;
261
- }
262
- ```
263
-
264
- #### IDatabaseConnection (External Database Access)
265
-
266
- ```typescript
267
- interface IDatabaseConnection {
268
- readonly code: string;
269
- readonly permissions: ReadonlySet<string>;
270
-
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>;
275
- }
276
- ```
277
-
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
427
 
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
428
+ // Idempotency keys prevent duplicate job execution
429
+ const jobId = await ketrics.Job.runInBackground({
430
+ function: 'processPayment',
431
+ payload: { orderId: '123' },
432
+ options: { idempotencyKey: 'order-123-payment' }
433
+ });
438
434
  ```
439
435
 
440
- ## 5. Error Handling
436
+ ### Logging Approach
441
437
 
442
- ### Error Hierarchy Design
438
+ **Console Logging** (ketrics.console):
439
+ - Methods: log(), error(), warn(), info(), debug()
440
+ - Forwarded to CloudWatch with context:
441
+ - Tenant ID and code
442
+ - Application ID and code
443
+ - Requestor information (user ID or service account ID)
444
+ - Timestamp
445
+ - Log level
443
446
 
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
- └── ...
454
- ```
455
-
456
- ### Volume Errors
457
-
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 |
469
-
470
- ### Database Errors
471
-
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) |
480
-
481
- ### Secret Errors
482
-
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) |
489
-
490
- ### File Processing Errors
491
-
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 |
500
-
501
- ### Job Errors
502
-
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 |
510
-
511
- ### Message Errors
512
-
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 |
519
-
520
- ### Type Guards
521
-
522
- Each module provides type guard functions:
447
+ **Error Logging**:
448
+ - Error classes implement toJSON() for serialization
449
+ - Error properties: name, message, code (operation), timestamp, resource code
450
+ - Security note: Error messages never expose credentials, connection strings, or internal IDs
523
451
 
452
+ **Example Logging Pattern**:
524
453
  ```typescript
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 */ }
529
-
530
- // Check for specific error type
531
- if (isVolumeErrorType(error, FileNotFoundError)) {
532
- console.log(error.key); // TypeScript knows error.key exists
454
+ try {
455
+ const db = await ketrics.DatabaseConnection.connect('main-db');
456
+ ketrics.console.log('Connected to database');
457
+ const result = await db.query('SELECT * FROM users');
458
+ ketrics.console.log(`Retrieved ${result.rowCount} users`);
459
+ } catch (error) {
460
+ if (error instanceof ketrics.DatabaseQueryError) {
461
+ ketrics.console.error('Database query failed', error.toJSON());
462
+ } else {
463
+ ketrics.console.error('Unexpected error', { message: error.message });
464
+ }
533
465
  }
534
466
  ```
535
467
 
536
- ### Error Properties
537
-
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)
543
-
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`
550
-
551
- ### Security Considerations
552
-
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`
468
+ ## Usage
558
469
 
559
- ## 6. Usage
470
+ ### How to Run/Deploy This Component
560
471
 
561
- ### Installation
472
+ This is a type definition library. It does not run standalone but is consumed by tenant applications.
562
473
 
474
+ **Installation in Tenant Application**:
563
475
  ```bash
564
476
  npm install @ketrics/sdk-backend
565
477
  ```
566
478
 
567
- ### Development Requirements
568
-
569
- - Node.js 24.x or higher
570
- - TypeScript 5.x
571
-
572
- ### Build Commands
479
+ **TypeScript Configuration** (tsconfig.json):
480
+ ```json
481
+ {
482
+ "compilerOptions": {
483
+ "strict": true,
484
+ "lib": ["ES2020"],
485
+ "types": ["@ketrics/sdk-backend"]
486
+ }
487
+ }
488
+ ```
573
489
 
490
+ **Building the SDK Package**:
574
491
  ```bash
575
492
  # Install dependencies
576
493
  npm install
577
494
 
578
- # Build TypeScript to JavaScript + declarations
495
+ # Compile TypeScript to JavaScript
579
496
  npm run build
580
497
 
581
- # Clean build output
582
- npm run clean
498
+ # Output: dist/ folder with .js, .d.ts, .map files
499
+ ```
583
500
 
584
- # Build and publish to npm
585
- npm run prepublishOnly # Runs build automatically
501
+ **Publishing**:
502
+ ```bash
503
+ npm publish
586
504
  ```
587
505
 
588
- ### Using in Tenant Applications
506
+ This publishes to npm registry at https://www.npmjs.com/package/@ketrics/sdk-backend
589
507
 
590
- The SDK is designed for **type imports only** in tenant code:
508
+ ### Example Invocations
591
509
 
510
+ **Complete Tenant Application Example**:
592
511
  ```typescript
593
- // Import types for type annotations
594
- import type {
595
- IVolume,
596
- FileContent,
597
- IDatabaseConnection,
598
- DatabaseQueryResult
599
- } from '@ketrics/sdk-backend';
600
-
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}`);
605
-
606
- // Log to CloudWatch
607
- ketrics.console.log('Starting order processing', { orderId });
608
-
609
- // Access S3 volume
610
- const volume: IVolume = await ketrics.Volume.connect('orders');
611
- const orderFile: FileContent = await volume.get(`${orderId}.json`);
612
-
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();
620
-
621
- return { success: true, newStatus: result.rows[0].status };
622
- }
623
- ```
512
+ // handler.ts - Tenant application code
624
513
 
625
- ### Error Handling Pattern
626
-
627
- ```typescript
628
- export async function safeVolumeRead(volumeCode: string, key: string) {
514
+ export async function processOrder(payload: { orderId: string }) {
629
515
  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 };
516
+ // Access context
517
+ ketrics.console.log(`Processing for tenant: ${ketrics.tenant.name}`);
518
+ ketrics.console.log(`Requestor: ${ketrics.requestor.name} (${ketrics.requestor.type})`);
519
+
520
+ // Get secret
521
+ const apiKey = await ketrics.Secret.get('stripe-api-key');
522
+
523
+ // Query database
524
+ const db = await ketrics.DatabaseConnection.connect('orders-db');
525
+ try {
526
+ const result = await db.query<Order>(
527
+ 'SELECT * FROM orders WHERE id = ?',
528
+ [payload.orderId]
529
+ );
530
+ if (result.rowCount === 0) {
531
+ return { error: 'Order not found' };
532
+ }
533
+ const order = result.rows[0];
534
+
535
+ // Make external API call
536
+ const response = await ketrics.http.post<ShipmentResponse>(
537
+ 'https://shipping-api.example.com/create-shipment',
538
+ { orderId: order.id, address: order.shippingAddress },
539
+ { headers: { 'Authorization': `Bearer ${apiKey}` } }
540
+ );
541
+
542
+ // Store file in volume
543
+ const volume = await ketrics.Volume.connect('orders');
544
+ const result = await volume.put(
545
+ `shipments/${order.id}/label.pdf`,
546
+ response.data.labelPdf,
547
+ { contentType: 'application/pdf' }
548
+ );
549
+
550
+ // Update database in transaction
551
+ await db.transaction(async (tx) => {
552
+ await tx.execute(
553
+ 'UPDATE orders SET status = ?, shipment_id = ? WHERE id = ?',
554
+ ['shipped', response.data.shipmentId, order.id]
555
+ );
556
+ });
557
+
558
+ // Send message to user
559
+ await ketrics.Messages.send({
560
+ userId: order.userId,
561
+ type: 'ORDER_SHIPPED',
562
+ subject: 'Your order has shipped!',
563
+ body: `Track your package at: ${response.data.trackingUrl}`,
564
+ priority: 'HIGH',
565
+ actionUrl: response.data.trackingUrl
566
+ });
567
+
568
+ // Queue async job
569
+ const jobId = await ketrics.Job.runInBackground({
570
+ function: 'sendShipmentNotifications',
571
+ payload: { orderId: order.id },
572
+ options: { timeout: 60000 }
573
+ });
574
+
575
+ return {
576
+ success: true,
577
+ shipmentId: response.data.shipmentId,
578
+ jobId: jobId
579
+ };
580
+ } finally {
581
+ await db.close();
642
582
  }
583
+ } catch (error) {
584
+ ketrics.console.error('Order processing failed', { error: error.message });
585
+ throw error;
586
+ }
587
+ }
643
588
 
644
- // Fallback for any other volume error
645
- if (ketrics.isVolumeError(error)) {
646
- return { error: 'VOLUME_ERROR', message: error.message };
589
+ export async function generateMonthlyReport() {
590
+ try {
591
+ // Create Excel workbook
592
+ const workbook = ketrics.Excel.create();
593
+ const sheet = workbook.addWorksheet('Sales Report');
594
+
595
+ // Query all transactions
596
+ const db = await ketrics.DatabaseConnection.connect('analytics-db');
597
+ try {
598
+ const result = await db.query<Transaction>(
599
+ 'SELECT * FROM transactions WHERE month = MONTH(NOW())'
600
+ );
601
+
602
+ // Populate worksheet
603
+ sheet.addRow(['Date', 'Amount', 'Category']);
604
+ for (const tx of result.rows) {
605
+ sheet.addRow([new Date(tx.date), tx.amount, tx.category]);
606
+ }
607
+
608
+ // Write to volume
609
+ const buffer = await workbook.writeFile();
610
+ const volume = await ketrics.Volume.connect('reports');
611
+ await volume.put(`monthly/${new Date().toISOString().split('T')[0]}.xlsx`, buffer);
612
+
613
+ // Send to group (requires IAM-data permissions)
614
+ await ketrics.Messages.sendToGroup({
615
+ groupCode: 'finance-team',
616
+ type: 'REPORT_READY',
617
+ subject: 'Monthly Sales Report',
618
+ body: 'Your monthly sales report is ready',
619
+ priority: 'MEDIUM'
620
+ });
621
+
622
+ return { success: true };
623
+ } finally {
624
+ await db.close();
625
+ }
626
+ } catch (error) {
627
+ if (error instanceof ketrics.GroupNotFoundError) {
628
+ ketrics.console.error(`Group not found: ${error.message}`);
629
+ } else if (error instanceof ketrics.ExcelWriteError) {
630
+ ketrics.console.error('Failed to write Excel file');
631
+ } else {
632
+ throw error;
647
633
  }
648
-
649
- // Re-throw unexpected errors
650
- throw error;
651
634
  }
652
635
  }
653
636
  ```
654
637
 
655
- ### Complete Example: Report Generation
656
-
638
+ **Error Handling Patterns**:
657
639
  ```typescript
658
- import type { IExcelWorkbook, IPdfDocument } from '@ketrics/sdk-backend';
659
-
660
- interface SalesRecord {
661
- id: number;
662
- product: string;
663
- quantity: number;
664
- total: number;
665
- }
666
-
667
- export async function generateSalesReport(month: string) {
668
- // Get secrets for external API
669
- const apiKey = await ketrics.Secret.get('analytics-api-key');
670
-
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]
676
- );
677
- await db.close();
678
-
679
- // Create Excel report
680
- const workbook: IExcelWorkbook = ketrics.Excel.create();
681
- const sheet = workbook.addWorksheet('Sales Report');
682
-
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]);
640
+ // Type guards for error handling
641
+ try {
642
+ const db = await ketrics.DatabaseConnection.connect('main-db');
643
+ } catch (error) {
644
+ if (ketrics.isDatabaseError(error)) {
645
+ // Specific database error
646
+ console.log(`Database error: ${error.message}`);
686
647
  }
687
648
 
688
- const excelBuffer = await workbook.toBuffer();
689
-
690
- // Create PDF summary
691
- const pdf: IPdfDocument = await ketrics.Pdf.create();
692
- pdf.setTitle(`Sales Report - ${month}`);
693
- pdf.setAuthor(ketrics.requestor.name);
694
-
695
- const page = pdf.addPage('A4');
696
- const font = await pdf.embedStandardFont('HelveticaBold');
697
-
698
- await page.drawText(`Sales Report: ${month}`, {
699
- x: 50, y: 750, size: 24, font
700
- });
701
-
702
- await page.drawText(`Total Records: ${sales.rowCount}`, {
703
- x: 50, y: 700, size: 14
704
- });
705
-
706
- const pdfBuffer = await pdf.toBuffer();
707
-
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);
712
-
713
- // Generate download URLs
714
- const excelUrl = await volume.generateDownloadUrl(`${month}/sales.xlsx`, {
715
- expiresIn: 86400
716
- });
717
-
718
- ketrics.console.log('Report generated', {
719
- month,
720
- recordCount: sales.rowCount
721
- });
649
+ if (ketrics.isDatabaseErrorType(error, 'DatabaseNotFoundError')) {
650
+ // Exact error type check
651
+ console.log(`Database not found: ${error.databaseCode}`);
652
+ }
722
653
 
723
- return {
724
- success: true,
725
- downloadUrl: excelUrl.url,
726
- expiresAt: excelUrl.expiresAt
727
- };
654
+ if (error instanceof ketrics.DatabaseAccessDeniedError) {
655
+ // Direct instanceof check
656
+ console.log('Application lacks database access grant');
657
+ }
728
658
  }
729
659
  ```
730
660
 
731
- ### Testing
732
-
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`
737
-
738
- The actual runtime behavior is tested in the Data Plane API where the SDK is implemented.
739
-
740
- ```bash
741
- # Verify types compile correctly
742
- npm run build
743
-
744
- # Type checking is performed as part of build
745
- # No separate test command needed for type-only package
746
- ```
747
-
748
- ### Publishing
661
+ ### Testing Approach
749
662
 
663
+ **Unit Testing Tenant Code**:
750
664
  ```bash
751
- # Bump version in package.json
752
- npm version patch|minor|major
665
+ # Tenant applications test their handlers without running in Ketrics platform
666
+ npm install --save-dev jest @types/jest ts-jest
753
667
 
754
- # Build and publish
755
- npm publish --access public
668
+ # Mock the global ketrics object for tests
669
+ jest.mock('@ketrics/sdk-backend', () => ({
670
+ // Provide mock implementations
671
+ }));
756
672
  ```
757
673
 
758
- ## License
759
-
760
- MIT
674
+ **Integration Testing** (in Ketrics platform):
675
+ - Deploy application to staging environment
676
+ - Ketrics platform verifies type compatibility at build time
677
+ - Runtime validates against actual service permissions
678
+ - Test tenant isolation and error scenarios
679
+
680
+ **Type Safety**:
681
+ - TypeScript compilation enforces correct API usage at build time
682
+ - No runtime type checking (SDK is pure types)
683
+ - Type definitions include JSDoc comments with usage examples
684
+
685
+ ## Architecture Patterns
686
+
687
+ ### API Design Patterns
688
+
689
+ **Static Factory Pattern**:
690
+ - Volume.connect(), DatabaseConnection.connect(), Secret.get()
691
+ - No constructor calls, explicit factory methods
692
+ - Enables lazy connection pooling and resource management
693
+
694
+ **Error Type Guards**:
695
+ - `isVolumeError(error)` - Checks if error is any VolumeError
696
+ - `isVolumeErrorType(error, 'VolumeNotFoundError')` - Checks specific type
697
+ - Enables type-safe error narrowing without instanceof
698
+
699
+ **Transaction Scope**:
700
+ - `db.transaction(async (tx) => { ... })`
701
+ - Callback receives transaction connection
702
+ - Automatic commit on success, rollback on error
703
+
704
+ **Pagination with Cursors**:
705
+ - List operations return cursor for next page
706
+ - Enables efficient large result set handling
707
+ - Stateless pagination without offset/limit complexity
708
+
709
+ ### File Export Strategy
710
+
711
+ The index.ts file re-exports all types from feature modules:
712
+ 1. Interfaces and types for tenant usage
713
+ 2. Error classes for instanceof checking
714
+ 3. Type guards for runtime error discrimination
715
+ 4. Global KetricsSdkV1 interface defining the complete ketrics object
716
+
717
+ This central export point ensures:
718
+ - Single source of truth for SDK surface
719
+ - Complete type definitions for IDE autocomplete
720
+ - Proper TypeScript declaration file generation