@chaim-tools/cdk-lib 0.1.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 +238 -0
- package/lib/binders/base-chaim-binder.d.ts +144 -0
- package/lib/binders/base-chaim-binder.js +532 -0
- package/lib/binders/chaim-dynamodb-binder.d.ts +95 -0
- package/lib/binders/chaim-dynamodb-binder.js +292 -0
- package/lib/config/chaim-endpoints.d.ts +47 -0
- package/lib/config/chaim-endpoints.js +51 -0
- package/lib/index.d.ts +15 -0
- package/lib/index.js +43 -0
- package/lib/lambda-handler/.test-temp/snapshot.json +1 -0
- package/lib/lambda-handler/handler.js +513 -0
- package/lib/lambda-handler/handler.test.ts +365 -0
- package/lib/lambda-handler/package-lock.json +1223 -0
- package/lib/lambda-handler/package.json +14 -0
- package/lib/services/ingestion-service.d.ts +50 -0
- package/lib/services/ingestion-service.js +81 -0
- package/lib/services/os-cache-paths.d.ts +52 -0
- package/lib/services/os-cache-paths.js +123 -0
- package/lib/services/schema-service.d.ts +11 -0
- package/lib/services/schema-service.js +67 -0
- package/lib/services/snapshot-cleanup.d.ts +78 -0
- package/lib/services/snapshot-cleanup.js +220 -0
- package/lib/types/base-binder-props.d.ts +32 -0
- package/lib/types/base-binder-props.js +17 -0
- package/lib/types/credentials.d.ts +57 -0
- package/lib/types/credentials.js +83 -0
- package/lib/types/data-store-metadata.d.ts +67 -0
- package/lib/types/data-store-metadata.js +4 -0
- package/lib/types/failure-mode.d.ts +16 -0
- package/lib/types/failure-mode.js +21 -0
- package/lib/types/ingest-contract.d.ts +110 -0
- package/lib/types/ingest-contract.js +12 -0
- package/lib/types/snapshot-cache-policy.d.ts +52 -0
- package/lib/types/snapshot-cache-policy.js +57 -0
- package/lib/types/snapshot-payload.d.ts +245 -0
- package/lib/types/snapshot-payload.js +3 -0
- package/lib/types/table-binding-config.d.ts +43 -0
- package/lib/types/table-binding-config.js +57 -0
- package/package.json +67 -0
package/README.md
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# @chaim-tools/cdk-lib
|
|
2
|
+
|
|
3
|
+
AWS CDK L2 constructs for binding DynamoDB tables to Chaim schemas.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @chaim-tools/cdk-lib
|
|
9
|
+
# or
|
|
10
|
+
pnpm add @chaim-tools/cdk-lib
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Development
|
|
14
|
+
|
|
15
|
+
### Build
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pnpm install
|
|
19
|
+
pnpm build
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Test
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pnpm test # Run tests
|
|
26
|
+
pnpm test:watch # Watch mode
|
|
27
|
+
pnpm test:coverage # With coverage
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Clean
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pnpm clean
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { ChaimDynamoDBBinder, ChaimCredentials, TableBindingConfig } from '@chaim-tools/cdk-lib';
|
|
40
|
+
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
|
|
41
|
+
|
|
42
|
+
// Create a DynamoDB table
|
|
43
|
+
const usersTable = new dynamodb.Table(this, 'Users', {
|
|
44
|
+
partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING },
|
|
45
|
+
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Create binding configuration
|
|
49
|
+
const config = new TableBindingConfig(
|
|
50
|
+
'my-app',
|
|
51
|
+
ChaimCredentials.fromSecretsManager('chaim/api-credentials')
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// Bind schema to table
|
|
55
|
+
new ChaimDynamoDBBinder(this, 'UsersBinding', {
|
|
56
|
+
schemaPath: './schemas/users.bprint',
|
|
57
|
+
table: usersTable,
|
|
58
|
+
config,
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## API Reference
|
|
63
|
+
|
|
64
|
+
### `ChaimDynamoDBBinder`
|
|
65
|
+
|
|
66
|
+
Construct that binds a DynamoDB table to a Chaim schema.
|
|
67
|
+
|
|
68
|
+
#### Props
|
|
69
|
+
|
|
70
|
+
| Property | Type | Required | Description |
|
|
71
|
+
|----------|------|----------|-------------|
|
|
72
|
+
| `schemaPath` | string | Yes | Path to `.bprint` schema file |
|
|
73
|
+
| `table` | `ITable` | Yes | DynamoDB table to bind |
|
|
74
|
+
| `config` | `TableBindingConfig` | Yes | Binding configuration (appId, credentials, failureMode) |
|
|
75
|
+
|
|
76
|
+
### `TableBindingConfig`
|
|
77
|
+
|
|
78
|
+
Configuration for entity bindings. For single-table design, create one config and share across all entity bindings.
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
// Create config with Secrets Manager (recommended for production)
|
|
82
|
+
const config = new TableBindingConfig(
|
|
83
|
+
'my-app',
|
|
84
|
+
ChaimCredentials.fromSecretsManager('chaim/api-credentials')
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// Or with direct API keys (for development)
|
|
88
|
+
const config = new TableBindingConfig(
|
|
89
|
+
'my-app',
|
|
90
|
+
ChaimCredentials.fromApiKeys(apiKey, apiSecret),
|
|
91
|
+
FailureMode.STRICT // Optional - defaults to BEST_EFFORT
|
|
92
|
+
);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Constructor Parameters:**
|
|
96
|
+
- `appId` (string) - Application ID for the Chaim platform
|
|
97
|
+
- `credentials` (IChaimCredentials) - API credentials
|
|
98
|
+
- `failureMode` (FailureMode) - Optional, defaults to BEST_EFFORT
|
|
99
|
+
|
|
100
|
+
### `ChaimCredentials`
|
|
101
|
+
|
|
102
|
+
Factory class for creating Chaim API credentials.
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// Using AWS Secrets Manager (recommended for production)
|
|
106
|
+
const credentials = ChaimCredentials.fromSecretsManager('chaim/api-credentials');
|
|
107
|
+
|
|
108
|
+
// Using direct API keys (for development/testing)
|
|
109
|
+
const credentials = ChaimCredentials.fromApiKeys(apiKey, apiSecret);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### `FailureMode`
|
|
113
|
+
|
|
114
|
+
| Mode | Behavior |
|
|
115
|
+
|------|----------|
|
|
116
|
+
| `BEST_EFFORT` (default) | Log errors, return SUCCESS to CloudFormation |
|
|
117
|
+
| `STRICT` | Return FAILED to CloudFormation on any ingestion error |
|
|
118
|
+
|
|
119
|
+
## Single-Table Design (Multiple Entities)
|
|
120
|
+
|
|
121
|
+
For single-table design where multiple entity types share one DynamoDB table, create **one** `TableBindingConfig` and share it across all entity bindings:
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
import { ChaimDynamoDBBinder, ChaimCredentials, TableBindingConfig } from '@chaim-tools/cdk-lib';
|
|
125
|
+
|
|
126
|
+
const singleTable = new dynamodb.Table(this, 'SingleTable', {
|
|
127
|
+
partitionKey: { name: 'PK', type: dynamodb.AttributeType.STRING },
|
|
128
|
+
sortKey: { name: 'SK', type: dynamodb.AttributeType.STRING },
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Create config ONCE for the table
|
|
132
|
+
const tableConfig = new TableBindingConfig(
|
|
133
|
+
'my-app',
|
|
134
|
+
ChaimCredentials.fromSecretsManager('chaim/api-credentials')
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// Share config across all entities in the table
|
|
138
|
+
new ChaimDynamoDBBinder(this, 'UserBinding', {
|
|
139
|
+
schemaPath: './schemas/user.bprint',
|
|
140
|
+
table: singleTable,
|
|
141
|
+
config: tableConfig,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
new ChaimDynamoDBBinder(this, 'OrderBinding', {
|
|
145
|
+
schemaPath: './schemas/order.bprint',
|
|
146
|
+
table: singleTable,
|
|
147
|
+
config: tableConfig, // Same config!
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
new ChaimDynamoDBBinder(this, 'ProductBinding', {
|
|
151
|
+
schemaPath: './schemas/product.bprint',
|
|
152
|
+
table: singleTable,
|
|
153
|
+
config: tableConfig, // Same config!
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Why this pattern?**
|
|
158
|
+
|
|
159
|
+
All entities in the same DynamoDB table must belong to the same application (`appId`) with the same credentials. `TableBindingConfig` enforces this by design:
|
|
160
|
+
|
|
161
|
+
- Sharing the same config object makes consistency automatic
|
|
162
|
+
- Validation catches accidental misconfigurations (different appIds)
|
|
163
|
+
- Clear intent in your CDK code
|
|
164
|
+
- DRY - define credentials once
|
|
165
|
+
|
|
166
|
+
**Result:**
|
|
167
|
+
- 3 separate snapshots (one per entity)
|
|
168
|
+
- 3 separate resourceIds: `SingleTable__User`, `SingleTable__Order`, `SingleTable__Product`
|
|
169
|
+
- All with the same `appId` and `credentials`
|
|
170
|
+
- Each entity can be independently created, updated, or deleted
|
|
171
|
+
|
|
172
|
+
## How It Works
|
|
173
|
+
|
|
174
|
+
1. At **synth time**: The construct reads your `.bprint` file, validates it, and writes a snapshot to the CDK asset directory
|
|
175
|
+
2. During **deploy**: CloudFormation invokes the ingestion Lambda in your account
|
|
176
|
+
3. The Lambda:
|
|
177
|
+
- Reads the bundled snapshot from `./snapshot.json`
|
|
178
|
+
- Generates `eventId` (UUID v4), `nonce` (UUID v4), and `contentHash` (SHA-256)
|
|
179
|
+
- Requests presigned URL: `POST /ingest/presign` with HMAC authentication
|
|
180
|
+
- Uploads snapshot: `PUT <presignedUrl>`
|
|
181
|
+
|
|
182
|
+
## Ingestion Flow
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
Create/Update:
|
|
186
|
+
1. POST /ingest/presign with HMAC signature → get presigned S3 URL
|
|
187
|
+
Request includes: appId, eventId, contentHash, timestamp, nonce
|
|
188
|
+
2. PUT snapshot bytes to presigned S3 URL
|
|
189
|
+
|
|
190
|
+
Delete:
|
|
191
|
+
1. Build DELETE snapshot (action: 'DELETE', schema: null)
|
|
192
|
+
2. POST /ingest/presign with HMAC signature → get presigned S3 URL
|
|
193
|
+
3. PUT DELETE snapshot bytes to presigned S3 URL
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Snapshot Payload
|
|
197
|
+
|
|
198
|
+
The snapshot payload includes:
|
|
199
|
+
|
|
200
|
+
**Schema & Identity:**
|
|
201
|
+
- `schemaVersion` - Payload version for backward compatibility (current: 1.0)
|
|
202
|
+
- `.bprint` schema content (entity definitions, field types, constraints)
|
|
203
|
+
- Application ID and resource identifiers
|
|
204
|
+
|
|
205
|
+
**Infrastructure Metadata:**
|
|
206
|
+
- AWS account ID, region, stack information
|
|
207
|
+
- DynamoDB table configuration (keys, indexes, TTL, streams)
|
|
208
|
+
- CloudFormation context
|
|
209
|
+
|
|
210
|
+
**Versioning Strategy:**
|
|
211
|
+
- **Minor bump** (1.0 → 1.1): Additive or optional field changes
|
|
212
|
+
- **Major bump** (1.x → 2.0): Breaking changes (removed/renamed/required fields)
|
|
213
|
+
|
|
214
|
+
## Configuration
|
|
215
|
+
|
|
216
|
+
### Environment Configuration
|
|
217
|
+
|
|
218
|
+
The API defaults to production: `https://api.chaim.co`
|
|
219
|
+
|
|
220
|
+
Override for different environments via CDK context:
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
# Production (default - no context needed)
|
|
224
|
+
cdk deploy
|
|
225
|
+
|
|
226
|
+
# Development
|
|
227
|
+
cdk deploy --context chaimApiBaseUrl=https://api.dev.chaim.co
|
|
228
|
+
|
|
229
|
+
# Beta
|
|
230
|
+
cdk deploy --context chaimApiBaseUrl=https://api.beta.chaim.co
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
You can also set a custom API URL via environment variable in the Lambda:
|
|
234
|
+
- `CHAIM_API_BASE_URL` - Overrides the default at runtime
|
|
235
|
+
|
|
236
|
+
## License
|
|
237
|
+
|
|
238
|
+
Apache-2.0
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
|
|
2
|
+
import { Construct } from 'constructs';
|
|
3
|
+
import { SchemaData } from '@chaim-tools/chaim-bprint-spec';
|
|
4
|
+
import { BaseBinderProps } from '../types/base-binder-props';
|
|
5
|
+
import { DataStoreMetadata } from '../types/data-store-metadata';
|
|
6
|
+
import { TableBindingConfig } from '../types/table-binding-config';
|
|
7
|
+
/**
|
|
8
|
+
* Abstract base class for all Chaim data store binders.
|
|
9
|
+
*
|
|
10
|
+
* Provides shared infrastructure:
|
|
11
|
+
* - Schema loading and validation
|
|
12
|
+
* - Snapshot payload construction
|
|
13
|
+
* - LOCAL snapshot writing during CDK synth (to OS cache)
|
|
14
|
+
* - Lambda-backed custom resource for S3 presigned upload + snapshot-ref
|
|
15
|
+
*
|
|
16
|
+
* Subclasses implement `extractMetadata()` for store-specific metadata extraction
|
|
17
|
+
* and optionally override `getTable()` for DynamoDB-like resources.
|
|
18
|
+
*/
|
|
19
|
+
export declare abstract class BaseChaimBinder extends Construct {
|
|
20
|
+
/** Validated schema data */
|
|
21
|
+
readonly schemaData: SchemaData;
|
|
22
|
+
/** Extracted data store metadata */
|
|
23
|
+
readonly dataStoreMetadata: DataStoreMetadata;
|
|
24
|
+
/** Generated resource ID ({resourceName}__{entityName}[__N]) */
|
|
25
|
+
readonly resourceId: string;
|
|
26
|
+
/** Binding configuration */
|
|
27
|
+
readonly config: TableBindingConfig;
|
|
28
|
+
/** Base props (for internal use) */
|
|
29
|
+
protected readonly baseProps: BaseBinderProps;
|
|
30
|
+
constructor(scope: Construct, id: string, props: BaseBinderProps);
|
|
31
|
+
/**
|
|
32
|
+
* Validate that all bindings to the same table use the same config.
|
|
33
|
+
*
|
|
34
|
+
* This is a safety check - sharing the same TableBindingConfig object
|
|
35
|
+
* already ensures consistency, but this catches cases where users
|
|
36
|
+
* create separate configs with identical values.
|
|
37
|
+
*/
|
|
38
|
+
private validateTableConsistency;
|
|
39
|
+
/**
|
|
40
|
+
* Abstract method - subclasses implement store-specific metadata extraction.
|
|
41
|
+
*/
|
|
42
|
+
protected abstract extractMetadata(): DataStoreMetadata;
|
|
43
|
+
/**
|
|
44
|
+
* Override in subclasses to provide the table construct for stable identity.
|
|
45
|
+
* Default returns undefined (will fall back to construct path).
|
|
46
|
+
*/
|
|
47
|
+
protected getTable(): dynamodb.ITable | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Get the resource name for display and filenames.
|
|
50
|
+
* For DynamoDB, this is the user label (not necessarily the physical table name).
|
|
51
|
+
*
|
|
52
|
+
* Subclasses can override this to provide a more meaningful name
|
|
53
|
+
* (e.g., construct node ID instead of physical resource name which may contain tokens).
|
|
54
|
+
*/
|
|
55
|
+
protected getResourceName(): string;
|
|
56
|
+
/**
|
|
57
|
+
* Get the entity name from schema.
|
|
58
|
+
*/
|
|
59
|
+
private getEntityName;
|
|
60
|
+
/**
|
|
61
|
+
* Read package version from package.json.
|
|
62
|
+
* Used to populate producer metadata in snapshots.
|
|
63
|
+
*/
|
|
64
|
+
private getPackageVersion;
|
|
65
|
+
/**
|
|
66
|
+
* Build a LOCAL snapshot payload for CLI consumption.
|
|
67
|
+
* Does not include eventId or contentHash - those are generated at deploy-time.
|
|
68
|
+
*/
|
|
69
|
+
private buildLocalSnapshot;
|
|
70
|
+
/**
|
|
71
|
+
* Build resource metadata from dataStore metadata.
|
|
72
|
+
*/
|
|
73
|
+
private buildResourceMetadata;
|
|
74
|
+
/**
|
|
75
|
+
* Detect if metadata contains unresolved CDK tokens.
|
|
76
|
+
*/
|
|
77
|
+
private detectUnresolvedTokens;
|
|
78
|
+
/**
|
|
79
|
+
* Generate a UUID v4 for operation tracking.
|
|
80
|
+
*/
|
|
81
|
+
private generateUuid;
|
|
82
|
+
/**
|
|
83
|
+
* Apply snapshot cache policy based on CDK context.
|
|
84
|
+
*
|
|
85
|
+
* Checks the `chaimSnapshotCachePolicy` context value:
|
|
86
|
+
* - NONE (default): No cleanup
|
|
87
|
+
* - PRUNE_STACK: Delete existing stack snapshots before writing new ones
|
|
88
|
+
*
|
|
89
|
+
* This runs once per stack (tracked by static flag) to avoid
|
|
90
|
+
* multiple cleanup attempts when binding multiple entities.
|
|
91
|
+
*/
|
|
92
|
+
private applySnapshotCachePolicy;
|
|
93
|
+
/**
|
|
94
|
+
* Parse snapshot cache policy from context value.
|
|
95
|
+
*/
|
|
96
|
+
private parseSnapshotCachePolicy;
|
|
97
|
+
/**
|
|
98
|
+
* Extract stack name from stableResourceKey.
|
|
99
|
+
* Format: dynamodb:path:StackName/ResourceName
|
|
100
|
+
*/
|
|
101
|
+
private extractStackNameFromResourceKey;
|
|
102
|
+
/**
|
|
103
|
+
* Write LOCAL snapshot to OS cache for chaim-cli consumption.
|
|
104
|
+
* Uses hierarchical path: aws/{accountId}/{region}/{stackName}/{datastoreType}/{resourceId}.json
|
|
105
|
+
*
|
|
106
|
+
* @param snapshot - The snapshot payload to write
|
|
107
|
+
* @returns The path where snapshot was written
|
|
108
|
+
*/
|
|
109
|
+
private writeLocalSnapshotToDisk;
|
|
110
|
+
/**
|
|
111
|
+
* Find the CDK project root by walking up from current module.
|
|
112
|
+
*/
|
|
113
|
+
private findCdkProjectRoot;
|
|
114
|
+
/**
|
|
115
|
+
* Write snapshot and Lambda handler to isolated CDK asset directory for Lambda bundling.
|
|
116
|
+
*
|
|
117
|
+
* Asset directory is per {stackName}/{resourceId} and MUST NOT be shared.
|
|
118
|
+
* The Lambda reads ./snapshot.json from its bundle, NOT from env vars or OS cache.
|
|
119
|
+
*
|
|
120
|
+
* The handler is copied from the canonical handler file (src/lambda-handler/handler.js)
|
|
121
|
+
* rather than being generated inline - this ensures a single source of truth.
|
|
122
|
+
*
|
|
123
|
+
* @returns The asset directory path
|
|
124
|
+
*/
|
|
125
|
+
private writeSnapshotAsset;
|
|
126
|
+
/**
|
|
127
|
+
* Deploy Lambda function and custom resource for ingestion.
|
|
128
|
+
*/
|
|
129
|
+
private deployIngestionResources;
|
|
130
|
+
/**
|
|
131
|
+
* Create Lambda function for ingestion workflow.
|
|
132
|
+
* Lambda reads snapshot from its bundled asset directory.
|
|
133
|
+
*/
|
|
134
|
+
private createIngestionLambda;
|
|
135
|
+
/**
|
|
136
|
+
* Build Lambda environment variables.
|
|
137
|
+
* Note: Snapshot is NOT passed via env - Lambda reads from bundled asset.
|
|
138
|
+
*/
|
|
139
|
+
private buildLambdaEnvironment;
|
|
140
|
+
/**
|
|
141
|
+
* Create CloudFormation custom resource.
|
|
142
|
+
*/
|
|
143
|
+
private createCustomResource;
|
|
144
|
+
}
|