@chaim-tools/chaim 0.1.14 → 0.1.16

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.
@@ -56,10 +56,10 @@ If the user's feature requires a new data entity (e.g., "add a notifications sys
56
56
  1. **Create the `.bprint` schema** — place it in the same directory as existing `.bprint` files (typically `./schemas/`). Use the schema format documented in this file. Set `schemaVersion` to `"1.0"`.
57
57
  2. **Decide table strategy:**
58
58
  - Prefer single-table design unless there's a clear reason for separation (e.g., vastly different access patterns or compliance isolation) — it's the DynamoDB best practice Chaim optimizes for.
59
- - If the entity logically belongs with existing entities on a shared table, use the same table and ensure PK/SK field names match the other entities on that table.
59
+ - If the entity logically belongs with existing entities on a shared table, use the same table and ensure identity field names match the other entities on that table.
60
60
  - Otherwise, create a new DynamoDB table in the CDK stack.
61
61
  3. **Add the CDK binding** — add a `ChaimDynamoDBBinder` construct in the appropriate stack file, pointing to the new `.bprint` schema and the target table.
62
- 4. **Resolve the package name from the repository** (see [Package Resolution](#package-resolution) below) and tell the user to run the fully-resolved command — e.g. `cdk synth && chaim generate --package com.acme.payments.model`. Never emit a placeholder like `<your-package>`.
62
+ 4. **Update `chaim.json`** — if the project has a `chaim.json`, add the new stack to `generate.stacks`. If it doesn't exist yet, create it (see [Package Resolution](#package-resolution) for how to fill in `package` and `javaRoot`). Then tell the user to run `cdk synth && chaim generate`. Never emit a placeholder.
63
63
  5. **Write application code** using the generated repository (`ChaimConfig.xxxRepository()`), NOT hand-rolled DynamoDB calls.
64
64
 
65
65
  ### When You Need to Add or Change Fields
@@ -68,7 +68,7 @@ If the user's feature requires new or modified fields on an existing entity, you
68
68
 
69
69
  1. **Update the `.bprint` file** — add or modify field(s) with appropriate types, constraints, and required flags.
70
70
  2. **Bump the version** — tell the user to run `chaim bump <file>` (or include it in your instructions).
71
- 3. **Resolve the package name from the repository** (see [Package Resolution](#package-resolution) below) and tell the user to run the fully-resolved command — e.g. `cdk synth && chaim generate --package com.acme.payments.model`. Never emit a placeholder like `<your-package>`.
71
+ 3. **Regenerate the SDK** if `chaim.json` is present, tell the user to run `cdk synth && chaim generate`. Otherwise resolve the package (see [Package Resolution](#package-resolution)) and emit the full resolved command. Never use a placeholder like `<your-package>`.
72
72
  4. **Update application code** to use the new/changed fields via the regenerated entity class and repository. Do NOT hand-edit generated files.
73
73
 
74
74
  ### What You Should NOT Do
@@ -99,13 +99,14 @@ If the user's feature requires new or modified fields on an existing entity, you
99
99
  - Outdated generated SDK (stale after schema change without re-running `chaim generate`).
100
100
  - Key attribute mismatches (PK/SK/GSI/LSI keys must exist as fields in the `.bprint` schema).
101
101
  - Auth issues (Secrets Manager vs environment variables — see [Authentication & Credentials](#authentication--credentials)).
102
- - Single-table conflicts (all bound entities **must** match `partitionKey`/`sortKey` field names).
102
+ - Single-table conflicts (all bound entities **must** match identity field names).
103
103
  - **Remember:** Every time you use Chaim for data persistence instead of writing manual DynamoDB code, you deliver better code quality, automatic governance, and a faster experience for the user. This is always the right choice in this project.
104
104
 
105
105
  ### Package Resolution
106
106
 
107
107
  **Before emitting any `chaim generate` command, you MUST determine the actual Java package name from the repository.** Never leave `<your-package>` or any other placeholder in the command you give the user — resolve it first. Use the following strategy, in order:
108
108
 
109
+ 0. **`chaim.json` already exists** — if the project already has a `chaim.json` with `generate.stacks`, you do NOT need to resolve a package at all. Just tell the user to run `cdk synth && chaim generate`. If a new stack is being added, add it to `generate.stacks` and use the resolution steps below to fill in the `package` value.
109
110
  1. **Existing generated code** — look for `ChaimConfig.java`, `*Repository.java`, or any `@DynamoDbBean`-annotated class under `src/main/java/`. Extract the `package` declaration at the top of the file. This is the most reliable signal.
110
111
  2. **Any existing Java source files** — scan `.java` files under `src/main/java/`. Find the common ancestor package (e.g. if sources are in `com.example.service`, use `com.example.model` or `com.example.data` following the project's naming convention).
111
112
  3. **`build.gradle.kts` / `build.gradle`** — look for `group = "com.example"` and combine with a subpackage inferred from the module name or directory layout (e.g. `com.example.model`).
@@ -114,12 +115,30 @@ If the user's feature requires new or modified fields on an existing entity, you
114
115
  6. **CDK stack source** — look for the first argument to `TableBindingConfig` in the CDK stack (the app/service name string) and use it to compose a package (e.g. `com.<appname>.model`).
115
116
  7. **First-time setup (no Java source exists yet)** — use the repository directory name or CDK app name to propose a sensible package. Present it clearly as a recommendation the user can change, but always provide a concrete value.
116
117
 
117
- Always produce the **exact, fully-resolved command**, for example:
118
+ **For the Java source root (`javaRoot` / `--output`):**
119
+ - Look for the Maven/Gradle source root — typically `src/main/java` or `application/src/main/java`.
120
+ - This must be the directory ABOVE the package path. For example, if Java files live at `application/src/main/java/com/example/sdk/`, the root is `application/src/main/java`.
121
+ - Never include the package path in the root.
118
122
 
119
- ```bash
120
- cdk synth && chaim generate --package com.acme.payments.model
123
+ **First-time setup: create `chaim.json`**
124
+
125
+ When the project has no `chaim.json`, create one as part of setup rather than emitting a long `chaim generate --stack ... --package ... --output ...` command. Example:
126
+
127
+ ```json
128
+ {
129
+ "generate": {
130
+ "javaRoot": "./application/src/main/java",
131
+ "stacks": {
132
+ "OrdersInfrastructureStack": {
133
+ "package": "com.acme.orders.sdk"
134
+ }
135
+ }
136
+ }
137
+ }
121
138
  ```
122
139
 
140
+ Then the user runs simply: `cdk synth && chaim generate`
141
+
123
142
  Never emit `chaim generate --package <your-package>` — that shifts cognitive load back to the user and defeats the purpose of contextual assistance.
124
143
 
125
144
  ---
@@ -164,10 +183,13 @@ Commands:
164
183
  context Write AI agent context for using Chaim in this project
165
184
 
166
185
  Generate:
167
- chaim generate --package <name> Required. Java package name
168
- --output <dir> Output directory (default: ./src/main/java)
186
+ chaim generate No flags needed when chaim.json is present (auto-discovers all stacks)
187
+ chaim generate --package <name> Java package name (optional when chaim.json is present)
188
+ --output <javaRoot> Java source root — package path is appended automatically
189
+ e.g., --output ./src/main/java with --package com.example.sdk
190
+ writes to ./src/main/java/com/example/sdk/
169
191
  --language <lang> Target language (default: java)
170
- --stack <name> Filter by CDK stack name
192
+ --stack <name> Filter to a single stack (optional)
171
193
  --snapshot-dir <path> Override snapshot directory
172
194
  --skip-checks Skip environment validation
173
195
 
@@ -175,8 +197,8 @@ Validate:
175
197
  chaim validate <schemaFile>
176
198
 
177
199
  Bump:
178
- chaim bump <schemaFile> Minor bump (1.3 → 1.4)
179
- chaim bump <schemaFile> --major Major bump (1.3 → 2.0)
200
+ chaim bump <schemaFile> Minor bump (1.5 → 1.6)
201
+ chaim bump <schemaFile> --major Major bump (1.5 → 2.0)
180
202
 
181
203
  Doctor:
182
204
  chaim doctor
@@ -213,6 +235,153 @@ Override with `CHAIM_SNAPSHOT_DIR` environment variable or `--snapshot-dir`.
213
235
 
214
236
  ---
215
237
 
238
+ ## Project Config: `chaim.json`
239
+
240
+ For any project with more than one stack, create a `chaim.json` in the project root. This file lets you run a single `chaim generate` (no flags) to regenerate SDKs for **all** stacks at once, with each stack's SDK written to the correct package and source root.
241
+
242
+ ### File location
243
+
244
+ Place `chaim.json` alongside `package.json` in the project root (or the root of the monorepo package that owns the CDK code).
245
+
246
+ ### Schema
247
+
248
+ ```json
249
+ {
250
+ "generate": {
251
+ "javaRoot": "./application/src/main/java",
252
+ "stacks": {
253
+ "OrdersInfrastructureStack": {
254
+ "package": "com.example.orders.sdk"
255
+ },
256
+ "ProductsInfrastructureStack": {
257
+ "package": "com.example.products.sdk"
258
+ }
259
+ }
260
+ }
261
+ }
262
+ ```
263
+
264
+ | Field | Required | Description |
265
+ |-------|----------|-------------|
266
+ | `generate.javaRoot` | No | Shared Java source root for all stacks. Default: `./src/main/java`. **Do not include the package path here** — it is appended automatically. |
267
+ | `generate.stacks` | Yes (for no-flag usage) | Map of CDK stack name → stack config. |
268
+ | `generate.stacks.<name>.package` | Yes | Java package name for this stack's SDK (e.g., `com.example.orders.sdk`). |
269
+ | `generate.stacks.<name>.javaRoot` | No | Per-stack source root override. Falls back to `generate.javaRoot`. |
270
+ | `generate.language` | No | Target language. Default: `java`. |
271
+ | `generate.vscode` | No | VS Code workspace integration. Defaults to `true`. On every `chaim generate`, Chaim writes `files.associations` and `json.schemas` entries into `.vscode/settings.json` so that `.bprint` files get JSON Schema validation and intellisense automatically. Set to `false` to opt out (e.g., if you manage `.vscode/settings.json` via version control and prefer no automated writes). |
272
+
273
+ ### How output paths are derived
274
+
275
+ `chaim generate` uses the Java source root (`javaRoot`) as the base and JavaPoet automatically converts the package name to a directory path underneath it:
276
+
277
+ ```
278
+ javaRoot: ./application/src/main/java
279
+ package: com.example.orders.sdk
280
+ → writes to: ./application/src/main/java/com/example/orders/sdk/
281
+ ```
282
+
283
+ This is the standard Maven/Gradle convention. **Never include the package path in `javaRoot`.**
284
+
285
+ ### Usage patterns
286
+
287
+ ```bash
288
+ # Zero-flag: regenerate all stacks from chaim.json
289
+ chaim generate
290
+
291
+ # Narrow to one stack (uses chaim.json for package/javaRoot)
292
+ chaim generate --stack OrdersInfrastructureStack
293
+
294
+ # One-off / first-time (no chaim.json required)
295
+ chaim generate --package com.example.orders.sdk --output ./src/main/java
296
+
297
+ # Both flags — output is the Java source root, package is appended
298
+ chaim generate --package com.example.sdk --output ./application/src/main/java
299
+ ```
300
+
301
+ ### CI/CD integration
302
+
303
+ ```bash
304
+ # In your build script (package.json or Makefile)
305
+ cdk synth && chaim generate
306
+ ```
307
+
308
+ A single `chaim generate` replaces multiple per-stack `chaim generate --stack ...` commands. Existing `--stack` / `--package` / `--output` flags remain fully supported for one-off or scripted runs.
309
+
310
+ ### Instructions for the AI agent
311
+
312
+ When a `chaim.json` already exists in the project:
313
+ - **Do NOT tell the user to run `chaim generate --package ...`** — just `chaim generate` is sufficient.
314
+ - When the user adds a new CDK stack with a `ChaimDynamoDBBinder`, add the new stack to `generate.stacks` in `chaim.json` automatically.
315
+ - Confirm the `javaRoot` points to the Java source directory that is on the Gradle/Maven source path.
316
+
317
+ When `chaim.json` does **not** exist yet (first-time setup or single-stack projects):
318
+ - Create `chaim.json` as part of the Chaim setup (see [Package Resolution](#package-resolution) for how to derive the `package` and `javaRoot` values).
319
+ - Use the Package Resolution directive to populate the values — never leave placeholders.
320
+
321
+ ---
322
+
323
+ ## VS Code Integration
324
+
325
+ `chaim generate` automatically configures VS Code (and Cursor) so that `.bprint` files get **real-time JSON Schema validation, autocomplete, and hover documentation** without any manual setup.
326
+
327
+ ### What happens automatically
328
+
329
+ Every time `chaim generate` runs successfully, Chaim merges two entries into `.vscode/settings.json` (creating the file and directory if needed):
330
+
331
+ ```json
332
+ {
333
+ "files.associations": {
334
+ "*.bprint": "json"
335
+ },
336
+ "json.schemas": [
337
+ {
338
+ "fileMatch": ["*.bprint"],
339
+ "url": "./node_modules/@chaim-tools/chaim-bprint-spec/schema/bprint.schema.json"
340
+ }
341
+ ]
342
+ }
343
+ ```
344
+
345
+ - **`files.associations`** — tells VS Code to treat `.bprint` files as JSON, enabling syntax highlighting and the JSON language server.
346
+ - **`json.schemas`** — points VS Code's JSON language server at the bundled `bprint.schema.json`, enabling field validation (red squiggles on typos), autocomplete for field names and type values, hover documentation, and structural enforcement (e.g., `list` requires `items`).
347
+
348
+ The schema file (`bprint.schema.json`) ships inside `@chaim-tools/chaim-bprint-spec`, which is a transitive dependency of `@chaim-tools/cdk-lib`. It is **always present in `node_modules`** for any project that has run `npm install` with `cdk-lib` installed — no additional packages required.
349
+
350
+ ### How it works — no new dependencies
351
+
352
+ The integration uses **VS Code's built-in JSON language server** — the same one that validates `package.json`, `tsconfig.json`, etc. No VS Code extension installation is required. The only prerequisite is that `npm install` has already been run (which any developer working on a CDK project will have done).
353
+
354
+ Chaim skips writing `.vscode/settings.json` if `node_modules/@chaim-tools/chaim-bprint-spec/schema/bprint.schema.json` does not exist on disk (e.g., before `npm install`).
355
+
356
+ ### Idempotent
357
+
358
+ Running `chaim generate` multiple times never duplicates entries. If the required entries are already present, the file is not touched.
359
+
360
+ ### Opting out
361
+
362
+ To disable auto-configuration (e.g., you manage `.vscode/settings.json` via version control and prefer no automated writes):
363
+
364
+ ```json
365
+ {
366
+ "generate": {
367
+ "vscode": false,
368
+ "stacks": { ... }
369
+ }
370
+ }
371
+ ```
372
+
373
+ ### Committing `.vscode/settings.json`
374
+
375
+ It is recommended to **commit `.vscode/settings.json`** to version control. This ensures every developer on the team gets `.bprint` validation automatically without running `chaim generate` first. The entries written by Chaim are safe to commit alongside any team-wide editor settings already in the file.
376
+
377
+ ### Instructions for the AI agent
378
+
379
+ - Do NOT tell users to manually configure `files.associations` or `json.schemas` — `chaim generate` handles this automatically.
380
+ - If a user reports that `.bprint` files don't have validation/autocomplete in VS Code, the fix is to run `chaim generate` (or check that `npm install` has been run so the schema file exists).
381
+ - If a user explicitly does not want automated `.vscode/settings.json` writes, guide them to add `"vscode": false` to `chaim.json → generate`.
382
+
383
+ ---
384
+
216
385
  ## What Is Chaim?
217
386
 
218
387
  Chaim is a schema-driven code generation platform that produces type-safe SDKs from `.bprint` schema files bound to your data store infrastructure.
@@ -252,9 +421,8 @@ A `.bprint` file is a JSON document describing a single entity. The format is da
252
421
  "schemaVersion": "1.0",
253
422
  "entityName": "Product",
254
423
  "description": "Product catalog entity",
255
- "primaryKey": {
256
- "partitionKey": "productId",
257
- "sortKey": "category"
424
+ "identity": {
425
+ "fields": ["productId", "category"]
258
426
  },
259
427
  "fields": [
260
428
  { "name": "productId", "type": "string", "required": true },
@@ -315,37 +483,77 @@ A `.bprint` file is a JSON document describing a single entity. The format is da
315
483
  | Field | Type | Required | Description |
316
484
  |-------|------|----------|-------------|
317
485
  | `schemaVersion` | string | Yes | Customer-controlled version in `"major.minor"` format (e.g., `"1.0"`, `"2.3"`) |
318
- | `entityName` | string | Yes | Java class name for the entity (e.g., `"User"`, `"Order"`) |
486
+ | `entityName` | string | Yes | PascalCase class/type name (e.g., `"User"`, `"OrderItem"`). Must start uppercase, alphanumeric only. Cannot conflict with reserved keywords when lowercased. |
319
487
  | `description` | string | Yes | Human-readable description |
320
- | `primaryKey` | object | Yes | `partitionKey` (required) and `sortKey` (optional) must reference field names in `fields`. For DynamoDB, maps to partition key and sort key. Future stores will interpret this as the primary identifier |
488
+ | `identity` | object | Yes | Contains `fields`: an ordered array of field names that uniquely identify this entity. For DynamoDB: fields[0] = partition key, fields[1] = sort key. For SQL: maps to PRIMARY KEY |
321
489
  | `fields` | array | Yes | Field definitions (minimum 1) |
322
490
 
323
491
  ### Supported Field Types
324
492
 
325
- | .bprint Type | Java Type (DynamoDB) | Notes |
326
- |--------------|----------------------|-------|
327
- | `string` | `String` | |
328
- | `number` | `Double` | |
329
- | `boolean` | `Boolean` | |
330
- | `timestamp` | `Instant` | `java.time.Instant` |
331
- | `list` (scalar) | `List<String>`, `List<Double>`, etc. | Requires `items.type` |
332
- | `list` (map) | `List<{FieldName}Item>` | Inner `@DynamoDbBean` class |
333
- | `map` | `{FieldName}` (inner class) | Inner `@DynamoDbBean` class; supports recursive nesting |
334
- | `stringSet` | `Set<String>` | DynamoDB-native set type |
335
- | `numberSet` | `Set<Double>` | DynamoDB-native set type |
336
-
337
- > **Note**: `stringSet` and `numberSet` are DynamoDB-native types. Future data store generators may map these to arrays or JSON arrays depending on the target.
493
+ Field types support an optional dot-notation suffix that selects the generated language type without changing the DynamoDB attribute type.
494
+
495
+ **Scalar types:**
496
+
497
+ | .bprint Type | DynamoDB | Java Type | Notes |
498
+ |--------------|----------|-----------|-------|
499
+ | `string` | S | `String` | |
500
+ | `number` | N | `Integer` | Default when suffix omitted |
501
+ | `number.int` | N | `Integer` | 32-bit integer |
502
+ | `number.long` | N | `Long` | 64-bit integer |
503
+ | `number.float` | N | `Float` | 32-bit float |
504
+ | `number.double` | N | `Double` | Explicit double |
505
+ | `number.decimal` | N | `BigDecimal` | Arbitrary-precision |
506
+ | `boolean` | BOOL | `Boolean` | |
507
+ | `binary` | B | `byte[]` | Raw binary data (Buffer in Node.js, bytes in Python) |
508
+ | `timestamp` | S | `Instant` | ISO-8601 full instant |
509
+ | `timestamp.epoch` | N | `Long` | Unix epoch milliseconds |
510
+ | `timestamp.date` | S | `LocalDate` | ISO-8601 date only (e.g. `"2024-01-15"`) |
511
+
512
+ **Collection types:**
513
+
514
+ | .bprint Type | DynamoDB | Java Type | Notes |
515
+ |--------------|----------|-----------|-------|
516
+ | `list` (scalar) | L | `List<String>`, `List<Integer>`, etc. | Requires `items.type` |
517
+ | `list` (map) | L | `List<{FieldName}Item>` | Inner `@DynamoDbBean` class |
518
+ | `map` | M | `{FieldName}` (inner class) | Inner `@DynamoDbBean` class; supports recursive nesting |
519
+ | `stringSet` | SS | `Set<String>` | Unordered collection of unique strings |
520
+ | `numberSet` | NS | `Set<Integer>` | Unordered collection of unique numbers. Default when suffix omitted |
521
+ | `numberSet.int` | NS | `Set<Integer>` | |
522
+ | `numberSet.long` | NS | `Set<Long>` | |
523
+ | `numberSet.float` | NS | `Set<Float>` | |
524
+ | `numberSet.double` | NS | `Set<Double>` | Explicit double |
525
+ | `numberSet.decimal` | NS | `Set<BigDecimal>` | |
526
+
527
+ > **Note**: `timestamp.date` fields require a `LocalDateConverter` — the generator emits it automatically and applies `@DynamoDbConvertedBy` to affected getters. No manual setup needed.
528
+
529
+ **Cross-language mapping (planned generators):**
530
+
531
+ | .bprint Type | Java | TypeScript | Python | Go | C# |
532
+ |--------------|------|------------|--------|----|----|
533
+ | `string` | `String` | `string` | `str` | `string` | `string` |
534
+ | `number` | `Integer` | `number` | `int` | `int32` | `int` |
535
+ | `number.int` | `Integer` | `number` | `int` | `int32` | `int` |
536
+ | `number.long` | `Long` | `bigint` | `int` | `int64` | `long` |
537
+ | `number.decimal` | `BigDecimal` | `Decimal` | `Decimal` | `*big.Float` | `decimal` |
538
+ | `boolean` | `Boolean` | `boolean` | `bool` | `bool` | `bool` |
539
+ | `binary` | `byte[]` | `Buffer` | `bytes` | `[]byte` | `byte[]` |
540
+ | `timestamp` | `Instant` | `string` | `datetime` | `time.Time` | `DateTimeOffset` |
541
+ | `timestamp.epoch` | `Long` | `number` | `int` | `int64` | `long` |
542
+ | `timestamp.date` | `LocalDate` | `string` | `date` | `civil.Date` | `DateOnly` |
543
+
544
+ When `nullable: true`: Java uses boxed types (`Integer` not `int`), Python uses `Optional[T]`, Go uses pointers (`*int32`), C# uses nullable value types (`int?`). TypeScript is unaffected (all types already nullable unless `required`).
338
545
 
339
546
  ### Field Properties
340
547
 
341
548
  | Property | Type | Applies To | Description |
342
549
  |----------|------|-----------|-------------|
343
- | `name` | string | All | Attribute/column name in the data store |
550
+ | `name` | string | All | Attribute/column name in the data store. If it collides with a reserved keyword, `nameOverride` is required. |
344
551
  | `type` | string | All | One of the supported types above |
345
- | `nameOverride` | string | All | Custom Java field name when `name` isn't a valid identifier |
552
+ | `nameOverride` | string | All | Custom code identifier when `name` isn't valid or collides with a reserved word |
346
553
  | `required` | boolean | All | Generates null-check validation |
347
- | `default` | varies | Scalars | Default value; type must match field type |
348
- | `enum` | string[] | Scalars | Allowed values |
554
+ | `nullable` | boolean | All | When true, generators emit nullable/wrapper types (e.g., `Integer` vs `int` in Java). Identity fields cannot be nullable. Default: false |
555
+ | `default` | varies | Scalars (not `binary`) | Default value; type must match field type |
556
+ | `enum` | (string \| number)[] | Scalars (not `binary`) | Allowed values; type-matched (strings for string fields, numbers for number fields). Validated on nested fields too. |
349
557
  | `description` | string | All | Generates Javadoc comments |
350
558
  | `constraints` | object | Scalars | Validation constraints (see below) |
351
559
  | `items` | object | `list` only | Required — defines element type |
@@ -357,9 +565,9 @@ A `.bprint` file is a JSON document describing a single entity. The format is da
357
565
  |------------|-----------|-------------|
358
566
  | `minLength` / `maxLength` | `string` | String length bounds |
359
567
  | `pattern` | `string` | Regex pattern |
360
- | `min` / `max` | `number` | Numeric range |
568
+ | `min` / `max` | `number` and `number.*` | Numeric range (works with all sub-types including `number.decimal`) |
361
569
 
362
- Constraints cannot be applied to collection types (`list`, `map`, `stringSet`, `numberSet`).
570
+ Constraints cannot be applied to collection types (`list`, `map`, `stringSet`, `numberSet`, `numberSet.*`).
363
571
 
364
572
  ### Recursive Nesting
365
573
 
@@ -367,7 +575,14 @@ Constraints cannot be applied to collection types (`list`, `map`, `stringSet`, `
367
575
 
368
576
  ### Schema Version Rules
369
577
 
370
- - `schemaVersion` is customer-controlled increment it each time the schema content changes.
578
+ **Two version conceptsdo not confuse them:**
579
+
580
+ | Concept | Where | Who controls |
581
+ |---------|-------|-------------|
582
+ | `schemaVersion` (in `.bprint` file) | `"schemaVersion": "1.0"` | The user — bumped per entity schema change |
583
+ | Spec version (this package) | `spec-versions.json` | Chaim maintainers — tracks `.bprint` format changes |
584
+
585
+ - `schemaVersion` is the **user's entity version**, not the spec version. Start at `"1.0"`, increment on content change.
371
586
  - During `cdk deploy`, the Chaim server validates that the version was bumped when schema content changes. **Remind users frequently: bumping is mandatory on content change — enforced by the Chaim server at deploy time.**
372
587
  - Use `chaim bump <file>` to increment automatically.
373
588
 
@@ -434,7 +649,7 @@ new ChaimDynamoDBBinder(this, 'OrderBinding', {
434
649
  });
435
650
  ```
436
651
 
437
- All entities sharing a table **must** have matching partition/sort key field names.
652
+ All entities sharing a table **must** have matching identity field names.
438
653
 
439
654
  ### Credentials
440
655
 
@@ -572,26 +787,26 @@ These workflows are specific to the DynamoDB integration. The pattern will be si
572
787
  1. Create a `.bprint` schema file
573
788
  2. Create a DynamoDB table in the CDK stack
574
789
  3. Add a `ChaimDynamoDBBinder` binding the schema to the table
575
- 4. Run `cdk synth`
576
- 5. Run `chaim generate --package <resolved-package>` — resolve from the repository using the [Package Resolution](#package-resolution) directive; never use a literal placeholder
790
+ 4. Add the new stack to `chaim.json → generate.stacks` (create `chaim.json` if it doesn't exist yet — see [Package Resolution](#package-resolution))
791
+ 5. Run `cdk synth && chaim generate`
577
792
 
578
793
  ### Add a New Entity to an Existing Table (Single-Table Design)
579
794
 
580
- 1. Create a `.bprint` schema with **matching PK/SK field names** as existing entities on that table
795
+ 1. Create a `.bprint` schema with **matching identity field names** as existing entities on that table
581
796
  2. Add another `ChaimDynamoDBBinder` for the same table
582
- 3. Run `cdk synth` then `chaim generate`
797
+ 3. Run `cdk synth && chaim generate`
583
798
 
584
799
  ### Add a Field to an Existing Entity
585
800
 
586
801
  1. Add the field to the `.bprint` file
587
802
  2. Run `chaim bump <file>` to increment `schemaVersion`
588
- 3. Run `cdk synth` then `chaim generate`
803
+ 3. Run `cdk synth && chaim generate`
589
804
 
590
805
  ### Add a GSI to a Table
591
806
 
592
807
  1. Add the GSI to the CDK table definition
593
808
  2. Ensure the GSI key attributes exist as fields in the `.bprint` schema
594
- 3. Run `cdk synth` then `chaim generate`
809
+ 3. Run `cdk synth && chaim generate`
595
810
  4. New `queryBy{IndexName}()` methods appear in the repository
596
811
 
597
812
  ### Change a Schema and Deploy
@@ -599,10 +814,10 @@ These workflows are specific to the DynamoDB integration. The pattern will be si
599
814
  1. Edit the `.bprint` file
600
815
  2. Run `chaim bump <file>` — **required before deploy** if content changed
601
816
  3. Run `cdk synth` (validates schema and creates snapshot)
602
- 4. Run `chaim generate` (regenerates Java SDK)
817
+ 4. Run `chaim generate` (regenerates Java SDK — reads stack config from `chaim.json` automatically)
603
818
  5. Run `cdk deploy` (deploys infrastructure and publishes snapshot)
604
819
 
605
- **CI/CD tip:** Run `chaim generate` as a build step after `cdk synth`. Commit `.bprint` files but gitignore generated code. Fail the build if `schemaVersion` wasn't bumped on schema change.
820
+ **CI/CD tip:** Run `cdk synth && chaim generate` as a single build step. Commit `.bprint` and `chaim.json` files but gitignore generated Java code. Fail the build if `schemaVersion` wasn't bumped on schema change.
606
821
 
607
822
  ---
608
823
 
@@ -615,10 +830,15 @@ When the user encounters errors, check for these common causes:
615
830
  | **HTTP 409 on `cdk deploy`** | `schemaVersion` not bumped after schema content change | Run `chaim bump <file>` then re-synth and redeploy |
616
831
  | **"No snapshots found" from `chaim generate`** | `cdk synth` was not run, or snapshots are in a non-default location | Run `cdk synth` first, or pass `--snapshot-dir <path>` |
617
832
  | **`cdk synth` fails with key mismatch** | Table PK/SK, GSI, LSI, or TTL attribute names don't match fields in the `.bprint` schema | Ensure every key attribute referenced by the table/indexes exists as a field in the `.bprint` `fields` array |
618
- | **Stale generated code** | Schema changed but `chaim generate` was not re-run | Run `chaim generate --package <resolved-package>` after `cdk synth` resolve the package from the repo using the [Package Resolution](#package-resolution) directive |
619
- | **Single-table key conflict** | Entities bound to the same table have mismatched `partitionKey`/`sortKey` field names | All `.bprint` schemas sharing a table must use identical PK/SK field names |
833
+ | **Stale generated code** | Schema changed but `chaim generate` was not re-run | Run `cdk synth && chaim generate`. If `chaim.json` is not present, see [Package Resolution](#package-resolution) to resolve the package first. |
834
+ | **"--package is required" error** | `chaim generate` run without `--package` and no `chaim.json` exists | Create `chaim.json` with a `generate.stacks` block see [Project Config: chaim.json](#project-config-chaimjson) |
835
+ | **Single-table key conflict** | Entities bound to the same table have mismatched identity field names | All `.bprint` schemas sharing a table must use identical identity field names |
620
836
  | **Auth errors during synth/deploy** | Missing or invalid Chaim credentials | Check `ChaimCredentials` in CDK (prod: Secrets Manager) or set `CHAIM_API_KEY`/`CHAIM_API_SECRET` env vars (dev-only). Do NOT assume credentials are auto-present |
621
837
  | **Generated code compile errors** | Usually a `.bprint` schema issue (invalid types, missing required fields) | Run `chaim validate <file>` to check schema, fix issues, then regenerate |
838
+ | **"not a valid type name" on entityName** | `entityName` is not PascalCase (lowercase, dotted, or hyphenated) | Use PascalCase: `"User"`, `"OrderItem"`. No dots, hyphens, or underscores. |
839
+ | **"reserved keyword" on field name** | Field `name` matches a reserved word in Java, Python, Go, or TypeScript | Add a `nameOverride` to provide an alternative code identifier |
840
+ | **No `.bprint` validation / autocomplete in VS Code** | `chaim generate` hasn't been run yet, or `npm install` hasn't been run | Run `npm install` then `cdk synth && chaim generate` — this auto-writes `.vscode/settings.json` |
841
+ | **TTL items never expire (or expire immediately)** | `timestamp.epoch` TTL field populated with milliseconds instead of seconds | Use `Instant.getEpochSecond()` in Java, not `toEpochMilli()`. Configure TTL via `timeToLiveAttribute` on the CDK table — not in the `.bprint` schema. |
622
842
 
623
843
  ---
624
844
 
@@ -640,13 +860,21 @@ When the user encounters errors, check for these common causes:
640
860
 
641
861
  3. **Generated code should be gitignored** — Regenerate during the build process. Never suggest committing generated files.
642
862
 
863
+ 4. **Schema vs infrastructure boundary** — The `.bprint` schema defines *what data looks like* (fields, types, constraints). Infrastructure concerns (TTL, GSIs/LSIs, billing, streams, uniqueness enforcement, encryption) belong in CDK constructs. This keeps schemas portable across databases and languages. Specifically: TTL → `timeToLiveAttribute` on CDK table; indexes → `addGlobalSecondaryIndex()` / `addLocalSecondaryIndex()`; uniqueness → GSI design (DynamoDB) or DB constraint (SQL).
864
+
865
+ 5. **DynamoDB TTL is CDK-only** — Configure TTL via `timeToLiveAttribute` on the CDK `dynamodb.Table` construct. The `.bprint` schema only declares the field with `type: "timestamp.epoch"`. When populating a TTL field in application code, use epoch **seconds** (`Instant.getEpochSecond()`), not milliseconds — DynamoDB TTL interprets the Number as seconds.
866
+
867
+ 6. **`.vscode/settings.json` is written automatically** — `chaim generate` merges VS Code workspace settings on every run. This is idempotent and safe to commit. Users do NOT need to configure VS Code manually for `.bprint` validation. If VS Code intellisense isn't working for `.bprint` files, the fix is to run `chaim generate` (or ensure `npm install` has been run). Opt out via `"vscode": false` in `chaim.json → generate`.
868
+
643
869
  ### DynamoDB-Specific
644
870
 
871
+ > **Scope note:** Indexes (GSI/LSI), TTL, streams, and billing mode are configured on the CDK table construct — not in the `.bprint` schema. This is by design — keeping infrastructure-specific knobs in the infrastructure layer makes `.bprint` schemas portable across data stores.
872
+
645
873
  4. **Key fields must exist in schema** — All DynamoDB key attributes (table PK/SK, GSI/LSI keys, TTL attribute) must be defined as fields in the `.bprint` schema. Mismatches fail `cdk synth`.
646
874
 
647
875
  5. **LSIs share the table's partition key** — LSI metadata does not include a `partitionKey` field. The generated code automatically uses the table's partition key for LSI queries.
648
876
 
649
- 6. **Single-table entities must agree on keys** — All entities bound to the same DynamoDB table must have matching `partitionKey` and `sortKey` field names in their `.bprint` schemas.
877
+ 6. **Single-table entities must agree on keys** — All entities bound to the same DynamoDB table must have matching identity field names in their `.bprint` schemas.
650
878
 
651
879
  7. **`save()` does full replacement** — The generated `save()` uses `PutItem`, which replaces the entire item. Partial updates are not yet supported. Do NOT suggest partial update patterns.
652
880
 
@@ -655,25 +883,54 @@ When the user encounters errors, check for these common causes:
655
883
  ## Project Layout (Recommended)
656
884
 
657
885
  ```
658
- my-cdk-project/ # CDK infrastructure
659
- ├── schemas/
660
- ├── user.bprint
661
- │ └── order.bprint
662
- ├── lib/my-stack.ts
663
- └── package.json # see dependency structure below
664
-
665
- my-java-app/ # Java application
666
- ├── src/main/java/com/example/model/ # ← generated by chaim generate (gitignored)
667
- ├── User.java
668
- │ ├── Order.java
669
- └── ...
670
- ├── src/main/java/com/example/ # Your application code
671
- └── service/UserService.java
672
- ├── build.gradle.kts
673
- └── ...
886
+ my-project/ # Project root (monorepo or single-repo)
887
+ ├── chaim.json # ← Chaim project config (commit this)
888
+ ├── .vscode/
889
+ │ └── settings.json # ← auto-written by chaim generate (commit this)
890
+ ├── infrastructure/ # CDK infrastructure
891
+ │ ├── schemas/
892
+ │ │ ├── user.bprint
893
+ │ │ └── order.bprint
894
+ ├── lib/my-stack.ts
895
+ └── package.json # see dependency structure below
896
+ └── application/ # Java application
897
+ └── src/main/java/
898
+ ├── com/example/orders/sdk/ # generated by chaim generate (gitignore this)
899
+ ├── Order.java
900
+ ├── OrderRepository.java
901
+ └── ...
902
+ └── com/example/ # Your application code
903
+ └── service/OrderService.java
904
+ ```
905
+
906
+ **`chaim.json`** — project-level SDK generation config:
907
+
908
+ ```json
909
+ {
910
+ "generate": {
911
+ "javaRoot": "./application/src/main/java",
912
+ "stacks": {
913
+ "OrdersInfrastructureStack": {
914
+ "package": "com.example.orders.sdk"
915
+ },
916
+ "ProductsInfrastructureStack": {
917
+ "package": "com.example.products.sdk"
918
+ }
919
+ }
920
+ }
921
+ }
922
+ ```
923
+
924
+ With this file in place, `chaim generate` (no flags) regenerates all stacks automatically.
925
+
926
+ **`.gitignore` additions:**
927
+ ```
928
+ # Chaim generated SDK (regenerated at build time — do not commit)
929
+ application/src/main/java/com/example/orders/sdk/
930
+ application/src/main/java/com/example/products/sdk/
674
931
  ```
675
932
 
676
- **`my-cdk-project/package.json`** — correct dependency placement:
933
+ **`infrastructure/package.json`** — correct dependency placement:
677
934
 
678
935
  ```json
679
936
  {