dynamodb-expression-builder 0.2.0 → 0.3.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,6 +1,6 @@
1
1
  # dynamodb-expression-builder
2
2
 
3
- DynamoDB expression builder and code generator. Build update, condition, filter and key condition expressions with automatic `ExpressionAttributeNames` / `ExpressionAttributeValues` aliasing — then emit the whole request as runnable code for the JavaScript SDK v3, AWS CLI, boto3 (Python), Java, Go, .NET, Rust, PartiQL or [dynamodb-toolbox](https://github.com/dynamodb-toolbox/dynamodb-toolbox). Zero dependencies.
3
+ DynamoDB expression builder and code generator. Build update, condition, filter and key condition expressions with automatic `ExpressionAttributeNames` / `ExpressionAttributeValues` aliasing — then emit the whole request as runnable code for the JavaScript SDK v3 (low-level or DocumentClient), AWS CLI, boto3 (Python), Java, Go, .NET, Rust, Kotlin, PHP, Ruby, PartiQL or [dynamodb-toolbox](https://github.com/dynamodb-toolbox/dynamodb-toolbox). Zero dependencies.
4
4
 
5
5
  Hand-writing DynamoDB expressions means juggling three coupled structures — the expression string, the `#name` aliases (mandatory whenever an attribute name is one of DynamoDB's 573 reserved words), and the typed `:value` placeholders — and keeping them consistent across every operation. The AWS SDKs for [Go](https://docs.aws.amazon.com/sdk-for-go/) and [Java](https://docs.aws.amazon.com/sdk-for-java/) ship expression builders for this; the JavaScript SDK v3 [does not](https://github.com/aws/aws-sdk-js-v3/issues/3165). This package is that builder, plus something the official ones don't do in any language: code generation, so one structured request becomes a paste-ready command in whichever SDK your team actually runs.
6
6
 
@@ -45,7 +45,7 @@ emitSdkV3(request);
45
45
  // })
46
46
  ```
47
47
 
48
- The same `request` feeds every emitter — `emitCli(request)` gives the `aws dynamodb query \ …` command, `emitBoto3(request)` the Python, `emitJava` / `emitGo` / `emitDotnet` / `emitRust` the typed AttributeValue constructors for those SDKs, and `emitPartiql(request)` the equivalent `SELECT` statement (or an honest `{ok: false, reason}` where PartiQL can't express the request).
48
+ The same `request` feeds every emitter — `emitCli(request)` gives the `aws dynamodb query \ …` command, `emitBoto3(request)` the Python, `emitJava` / `emitGo` / `emitDotnet` / `emitRust` / `emitKotlin` / `emitPhp` the typed AttributeValue constructors for those SDKs, `emitDocClient` / `emitRuby` the native-value shapes their SDKs marshal themselves, and `emitPartiql(request)` the equivalent `SELECT` statement (or an honest `{ok: false, reason}` where PartiQL can't express the request).
49
49
 
50
50
  Update expressions compile from a list of actions:
51
51
 
@@ -66,7 +66,7 @@ buildUpdateExpression([
66
66
 
67
67
  SET idioms are first-class: `assign`, `if_not_exists`, atomic counters (`add`/`subtract`), `list_append`/`list_prepend`, plus `REMOVE` (including list elements by index) and `ADD`/`DELETE` for numbers and sets.
68
68
 
69
- And `emitQueryProgram(config, format)` wraps a Query/Scan request into a complete runnable program — client setup, the request, and a `LastEvaluatedKey` pagination loop — with `format` one of `'sdk' | 'cli' | 'boto3' | 'partiql' | 'java' | 'go' | 'dotnet' | 'rust' | 'ddbtoolbox'` (the Rust program paginates with the SDK's `into_paginator()` stream).
69
+ And `emitQueryProgram(config, format)` wraps a Query/Scan request into a complete runnable program — client setup, the request, and a `LastEvaluatedKey` pagination loop — with `format` one of `'sdk' | 'docclient' | 'cli' | 'boto3' | 'partiql' | 'java' | 'go' | 'dotnet' | 'rust' | 'kotlin' | 'php' | 'ruby' | 'ddbtoolbox'` each paginated program uses its SDK's own idiom (`paginateQuery`, `into_paginator()`, `queryPaginated` flows, `getPaginator`, pageable responses).
70
70
 
71
71
  ## API
72
72
 
@@ -76,7 +76,7 @@ Three layers, each usable on its own:
76
76
  | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
77
77
  | Model | `TypedValue`, `makeTypedValue`, `FilterRow`, `KeyAttr`, `RangeKeyCondition`, `UpdateAction`, the `FILTER_OPERATORS` registry + per-type compatibility helpers |
78
78
  | Builders | `buildRequest(config)` → one `CanonicalRequest` for any of GetItem/Query/Scan/Update/Put/Delete · `buildFilterExpressions` · `buildKeyConditionExpression` · `buildUpdateExpression` |
79
- | Emitters | `emitSdkV3` · `emitCli` · `emitBoto3` · `emitJava` · `emitGo` · `emitDotnet` · `emitRust` · `emitPartiql` · `emitDdbToolboxProgram` · `emitQueryProgram` · `typedMapToAvMap` (tag-driven marshal) |
79
+ | Emitters | `emitSdkV3` · `emitDocClient` · `emitCli` · `emitBoto3` · `emitJava` · `emitGo` · `emitDotnet` · `emitRust` · `emitKotlin` · `emitPhp` · `emitRuby` · `emitPartiql` · `emitDdbToolboxProgram` · `emitQueryProgram` · `typedMapToAvMap` (tag-driven marshal) |
80
80
 
81
81
  Placeholder namespaces never collide: keys use `#hashKey`/`#rangeKey`, filters `#filter{i}`, conditions `#cond{i}`, updates `#upd{i}` — one request can carry a key condition, a filter, a write condition and an update expression simultaneously.
82
82
 
package/dist/index.cjs CHANGED
@@ -747,7 +747,7 @@ function hasBinaryValue(request) {
747
747
  //#endregion
748
748
  //#region src/emit/sdk-v3.ts
749
749
  /** Operation → the `@aws-sdk/client-dynamodb` command class name. */
750
- const COMMAND_BY_OP = {
750
+ const COMMAND_BY_OP$1 = {
751
751
  GetItem: "GetItemCommand",
752
752
  Query: "QueryCommand",
753
753
  Scan: "ScanCommand",
@@ -805,11 +805,11 @@ function jsLiteral(value, indent) {
805
805
  }
806
806
  /** Emit the `new <Op>Command({...})` source snippet for a canonical request. */
807
807
  function emitSdkV3(request) {
808
- return `new ${COMMAND_BY_OP[request.operation]}(${jsLiteral(buildSdkV3Params(request), 0)})`;
808
+ return `new ${COMMAND_BY_OP$1[request.operation]}(${jsLiteral(buildSdkV3Params(request), 0)})`;
809
809
  }
810
810
  /** The `@aws-sdk/client-dynamodb` command class name for an operation. */
811
811
  function sdkV3CommandName(operation) {
812
- return COMMAND_BY_OP[operation];
812
+ return COMMAND_BY_OP$1[operation];
813
813
  }
814
814
  /**
815
815
  * Render an arbitrary params value as the same pretty-printed JS literal
@@ -1117,7 +1117,7 @@ const REQUEST_CLASS_BY_OP$1 = {
1117
1117
  Delete: "DeleteItemRequest"
1118
1118
  };
1119
1119
  /** Operation → the `DynamoDbClient` method. */
1120
- const CLIENT_METHOD_BY_OP$3 = {
1120
+ const CLIENT_METHOD_BY_OP$6 = {
1121
1121
  GetItem: "getItem",
1122
1122
  Query: "query",
1123
1123
  Scan: "scan",
@@ -1131,7 +1131,7 @@ function javaRequestClassName(operation) {
1131
1131
  }
1132
1132
  /** The `DynamoDbClient` method name for an operation (`query`, …). */
1133
1133
  function javaClientMethodName(operation) {
1134
- return CLIENT_METHOD_BY_OP$3[operation];
1134
+ return CLIENT_METHOD_BY_OP$6[operation];
1135
1135
  }
1136
1136
  /** Java string literal — `JSON.stringify` escapes are all valid Java escapes. */
1137
1137
  function javaString(value) {
@@ -1210,7 +1210,7 @@ const INPUT_TYPE_BY_OP = {
1210
1210
  Delete: "DeleteItemInput"
1211
1211
  };
1212
1212
  /** Operation → the `dynamodb.Client` method. */
1213
- const CLIENT_METHOD_BY_OP$2 = {
1213
+ const CLIENT_METHOD_BY_OP$5 = {
1214
1214
  GetItem: "GetItem",
1215
1215
  Query: "Query",
1216
1216
  Scan: "Scan",
@@ -1224,7 +1224,7 @@ function goInputTypeName(operation) {
1224
1224
  }
1225
1225
  /** The `dynamodb.Client` method name for an operation. */
1226
1226
  function goClientMethodName(operation) {
1227
- return CLIENT_METHOD_BY_OP$2[operation];
1227
+ return CLIENT_METHOD_BY_OP$5[operation];
1228
1228
  }
1229
1229
  /** Go string literal — `JSON.stringify` escapes are all valid Go escapes. */
1230
1230
  function goString(value) {
@@ -1314,7 +1314,7 @@ const REQUEST_CLASS_BY_OP = {
1314
1314
  Delete: "DeleteItemRequest"
1315
1315
  };
1316
1316
  /** Operation → the async `AmazonDynamoDBClient` method. */
1317
- const CLIENT_METHOD_BY_OP$1 = {
1317
+ const CLIENT_METHOD_BY_OP$4 = {
1318
1318
  GetItem: "GetItemAsync",
1319
1319
  Query: "QueryAsync",
1320
1320
  Scan: "ScanAsync",
@@ -1328,7 +1328,7 @@ function dotnetRequestClassName(operation) {
1328
1328
  }
1329
1329
  /** The async client method name for an operation (`QueryAsync`, …). */
1330
1330
  function dotnetClientMethodName(operation) {
1331
- return CLIENT_METHOD_BY_OP$1[operation];
1331
+ return CLIENT_METHOD_BY_OP$4[operation];
1332
1332
  }
1333
1333
  /** C# string literal — `JSON.stringify` escapes are all valid C# escapes. */
1334
1334
  function csString(value) {
@@ -1398,7 +1398,7 @@ function emitDotnet(request) {
1398
1398
  //#endregion
1399
1399
  //#region src/emit/rust.ts
1400
1400
  /** Operation → the `aws_sdk_dynamodb::Client` fluent method. */
1401
- const CLIENT_METHOD_BY_OP = {
1401
+ const CLIENT_METHOD_BY_OP$3 = {
1402
1402
  GetItem: "get_item",
1403
1403
  Query: "query",
1404
1404
  Scan: "scan",
@@ -1408,7 +1408,7 @@ const CLIENT_METHOD_BY_OP = {
1408
1408
  };
1409
1409
  /** The `Client` method name (snake_case) for an operation. */
1410
1410
  function rustClientMethodName(operation) {
1411
- return CLIENT_METHOD_BY_OP[operation];
1411
+ return CLIENT_METHOD_BY_OP$3[operation];
1412
1412
  }
1413
1413
  /** Rust string literal — JSON escapes with the three Rust-incompatible ones fixed. */
1414
1414
  function rustString(value) {
@@ -1450,7 +1450,7 @@ function avEntryLines(method, map, indent) {
1450
1450
  * chain to `into_paginator()` instead). Exported for the program emitter.
1451
1451
  */
1452
1452
  function renderRustBuilder(request, indent) {
1453
- const lines = [`${indent}.${CLIENT_METHOD_BY_OP[request.operation]}()`];
1453
+ const lines = [`${indent}.${CLIENT_METHOD_BY_OP$3[request.operation]}()`];
1454
1454
  lines.push(`${indent}.table_name(${rustString(request.tableName)})`);
1455
1455
  if (request.indexName) lines.push(`${indent}.index_name(${rustString(request.indexName)})`);
1456
1456
  if (request.key) lines.push(...avEntryLines("key", request.key, indent));
@@ -1702,6 +1702,292 @@ function emitDdbToolboxProgram(request, paginate) {
1702
1702
  ].filter((l) => l !== null).join("\n");
1703
1703
  }
1704
1704
 
1705
+ //#endregion
1706
+ //#region src/emit/docclient.ts
1707
+ /** Operation → the `@aws-sdk/lib-dynamodb` command class. */
1708
+ const COMMAND_BY_OP = {
1709
+ GetItem: "GetCommand",
1710
+ Query: "QueryCommand",
1711
+ Scan: "ScanCommand",
1712
+ Update: "UpdateCommand",
1713
+ Put: "PutCommand",
1714
+ Delete: "DeleteCommand"
1715
+ };
1716
+ /** The lib-dynamodb command class name for an operation. */
1717
+ function docClientCommandName(operation) {
1718
+ return COMMAND_BY_OP[operation];
1719
+ }
1720
+ /** Render a typed map as a native-value object literal, one entry per line. */
1721
+ function renderNativeMap(map, indent) {
1722
+ const inner = `${indent} `;
1723
+ return `{\n${Object.entries(map).map(([name, tv]) => `${inner}${JSON.stringify(name)}: ${renderNativeValue(tv)},`).join("\n")}\n${indent}}`;
1724
+ }
1725
+ /** Render the plain string map (`ExpressionAttributeNames`). */
1726
+ function renderNameMap$3(map, indent) {
1727
+ const inner = `${indent} `;
1728
+ return `{\n${Object.entries(map).map(([alias, name]) => `${inner}${JSON.stringify(alias)}: ${JSON.stringify(name)},`).join("\n")}\n${indent}}`;
1729
+ }
1730
+ /**
1731
+ * Render the command's params object literal, one field per line at
1732
+ * `indent + 2`. Exported for the program emitter.
1733
+ */
1734
+ function renderDocClientParams(request, indent) {
1735
+ const pad = `${indent} `;
1736
+ const fields = [`TableName: ${JSON.stringify(request.tableName)},`];
1737
+ if (request.indexName) fields.push(`IndexName: ${JSON.stringify(request.indexName)},`);
1738
+ if (request.key) fields.push(`Key: ${renderNativeMap(request.key, pad)},`);
1739
+ if (request.item) fields.push(`Item: ${renderNativeMap(request.item, pad)},`);
1740
+ if (request.keyConditionExpression) fields.push(`KeyConditionExpression: ${JSON.stringify(request.keyConditionExpression)},`);
1741
+ if (request.updateExpression) fields.push(`UpdateExpression: ${JSON.stringify(request.updateExpression)},`);
1742
+ if (request.conditionExpression) fields.push(`ConditionExpression: ${JSON.stringify(request.conditionExpression)},`);
1743
+ if (request.filterExpression) fields.push(`FilterExpression: ${JSON.stringify(request.filterExpression)},`);
1744
+ if (request.projectionExpression) fields.push(`ProjectionExpression: ${JSON.stringify(request.projectionExpression)},`);
1745
+ if (request.names) fields.push(`ExpressionAttributeNames: ${renderNameMap$3(request.names, pad)},`);
1746
+ if (request.typedValues) fields.push(`ExpressionAttributeValues: ${renderNativeMap(request.typedValues, pad)},`);
1747
+ if (request.limit !== void 0) fields.push(`Limit: ${request.limit},`);
1748
+ if (request.consistentRead) fields.push("ConsistentRead: true,");
1749
+ if (request.scanIndexForward === false) fields.push("ScanIndexForward: false,");
1750
+ if (request.exclusiveStartKey) fields.push(`ExclusiveStartKey: ${renderNativeMap(request.exclusiveStartKey, pad)},`);
1751
+ return [
1752
+ "{",
1753
+ ...fields.map((f) => `${pad}${f}`),
1754
+ `${indent}}`
1755
+ ].join("\n");
1756
+ }
1757
+ /** Emit the bare `new XCommand({…})` snippet with native values. */
1758
+ function emitDocClient(request) {
1759
+ return `new ${COMMAND_BY_OP[request.operation]}(${renderDocClientParams(request, "")})`;
1760
+ }
1761
+
1762
+ //#endregion
1763
+ //#region src/emit/kotlin.ts
1764
+ /** Operation → the `DynamoDbClient` suspend function. */
1765
+ const CLIENT_METHOD_BY_OP$2 = {
1766
+ GetItem: "getItem",
1767
+ Query: "query",
1768
+ Scan: "scan",
1769
+ Update: "updateItem",
1770
+ Put: "putItem",
1771
+ Delete: "deleteItem"
1772
+ };
1773
+ /** The client method name (camelCase) for an operation. */
1774
+ function kotlinClientMethodName(operation) {
1775
+ return CLIENT_METHOD_BY_OP$2[operation];
1776
+ }
1777
+ /** Kotlin string literal — JSON escapes are valid Kotlin escapes, plus `$` must
1778
+ * be escaped (string templates). */
1779
+ function ktString(value) {
1780
+ return JSON.stringify(value).replace(/\$/g, "\\$");
1781
+ }
1782
+ /** Decode canonical base64 into a `byteArrayOf(0x…)` literal (fail-loud). */
1783
+ function ktBytes(base64) {
1784
+ let raw;
1785
+ try {
1786
+ raw = atob(base64);
1787
+ } catch {
1788
+ throw new Error(`invalid base64 in a binary (B/BS) value: ${base64}`);
1789
+ }
1790
+ return `byteArrayOf(${Array.from(raw, (c) => `0x${c.charCodeAt(0).toString(16).padStart(2, "0")}`).join(", ")})`;
1791
+ }
1792
+ /** Render one wire AttributeValue as an `AttributeValue.…` constructor. */
1793
+ function renderKotlinAv(av) {
1794
+ if ("S" in av) return `AttributeValue.S(${ktString(av.S)})`;
1795
+ if ("N" in av) return `AttributeValue.N(${ktString(av.N)})`;
1796
+ if ("B" in av) return `AttributeValue.B(${ktBytes(av.B)})`;
1797
+ if ("BOOL" in av) return `AttributeValue.Bool(${av.BOOL})`;
1798
+ if ("SS" in av) return `AttributeValue.Ss(listOf(${av.SS.map(ktString).join(", ")}))`;
1799
+ if ("NS" in av) return `AttributeValue.Ns(listOf(${av.NS.map(ktString).join(", ")}))`;
1800
+ if ("BS" in av) return `AttributeValue.Bs(listOf(${av.BS.map(ktBytes).join(", ")}))`;
1801
+ return "AttributeValue.Null(true)";
1802
+ }
1803
+ /** Render a typed map as `mapOf("k" to AttributeValue.…)`, one entry per line. */
1804
+ function renderAvMap$1(map, indent) {
1805
+ const inner = `${indent} `;
1806
+ return `mapOf(\n${Object.entries(typedMapToAvMap(map)).map(([name, av]) => `${inner}${ktString(name)} to ${renderKotlinAv(av)},`).join("\n")}\n${indent})`;
1807
+ }
1808
+ /** Render the plain string map (`expressionAttributeNames`). */
1809
+ function renderNameMap$2(map, indent) {
1810
+ const inner = `${indent} `;
1811
+ return `mapOf(\n${Object.entries(map).map(([alias, name]) => `${inner}${ktString(alias)} to ${ktString(name)},`).join("\n")}\n${indent})`;
1812
+ }
1813
+ /**
1814
+ * Render the builder-lambda body — one property assignment per line at
1815
+ * `indent`. Exported for the program emitter, which wraps the same body in
1816
+ * either the plain suspend call or the `Paginated` flow variant.
1817
+ */
1818
+ function renderKotlinBuilder(request, indent) {
1819
+ const lines = [`${indent}tableName = ${ktString(request.tableName)}`];
1820
+ if (request.indexName) lines.push(`${indent}indexName = ${ktString(request.indexName)}`);
1821
+ if (request.key) lines.push(`${indent}key = ${renderAvMap$1(request.key, indent)}`);
1822
+ if (request.item) lines.push(`${indent}item = ${renderAvMap$1(request.item, indent)}`);
1823
+ if (request.keyConditionExpression) lines.push(`${indent}keyConditionExpression = ${ktString(request.keyConditionExpression)}`);
1824
+ if (request.updateExpression) lines.push(`${indent}updateExpression = ${ktString(request.updateExpression)}`);
1825
+ if (request.conditionExpression) lines.push(`${indent}conditionExpression = ${ktString(request.conditionExpression)}`);
1826
+ if (request.filterExpression) lines.push(`${indent}filterExpression = ${ktString(request.filterExpression)}`);
1827
+ if (request.projectionExpression) lines.push(`${indent}projectionExpression = ${ktString(request.projectionExpression)}`);
1828
+ if (request.names) lines.push(`${indent}expressionAttributeNames = ${renderNameMap$2(request.names, indent)}`);
1829
+ if (request.typedValues) lines.push(`${indent}expressionAttributeValues = ${renderAvMap$1(request.typedValues, indent)}`);
1830
+ if (request.limit !== void 0) lines.push(`${indent}limit = ${request.limit}`);
1831
+ if (request.consistentRead) lines.push(`${indent}consistentRead = true`);
1832
+ if (request.scanIndexForward === false) lines.push(`${indent}scanIndexForward = false`);
1833
+ if (request.exclusiveStartKey) lines.push(`${indent}exclusiveStartKey = ${renderAvMap$1(request.exclusiveStartKey, indent)}`);
1834
+ return lines;
1835
+ }
1836
+ /** Emit the bare `val response = client.…{ … }` call for a canonical request. */
1837
+ function emitKotlin(request) {
1838
+ return [
1839
+ `val response = client.${CLIENT_METHOD_BY_OP$2[request.operation]} {`,
1840
+ ...renderKotlinBuilder(request, " "),
1841
+ "}"
1842
+ ].join("\n");
1843
+ }
1844
+
1845
+ //#endregion
1846
+ //#region src/emit/php.ts
1847
+ /** Operation → the `DynamoDbClient` method. */
1848
+ const CLIENT_METHOD_BY_OP$1 = {
1849
+ GetItem: "getItem",
1850
+ Query: "query",
1851
+ Scan: "scan",
1852
+ Update: "updateItem",
1853
+ Put: "putItem",
1854
+ Delete: "deleteItem"
1855
+ };
1856
+ /** The client method name (camelCase) for an operation. */
1857
+ function phpClientMethodName(operation) {
1858
+ return CLIENT_METHOD_BY_OP$1[operation];
1859
+ }
1860
+ /** PHP single-quoted string — only `\` and `'` need escaping. */
1861
+ function phpString(value) {
1862
+ return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
1863
+ }
1864
+ /** Render one wire AttributeValue as a nested `['TAG' => …]` literal. */
1865
+ function renderPhpAv(av) {
1866
+ if ("S" in av) return `['S' => ${phpString(av.S)}]`;
1867
+ if ("N" in av) return `['N' => ${phpString(av.N)}]`;
1868
+ if ("B" in av) return `['B' => base64_decode(${phpString(av.B)})]`;
1869
+ if ("BOOL" in av) return `['BOOL' => ${av.BOOL}]`;
1870
+ if ("SS" in av) return `['SS' => [${av.SS.map(phpString).join(", ")}]]`;
1871
+ if ("NS" in av) return `['NS' => [${av.NS.map(phpString).join(", ")}]]`;
1872
+ if ("BS" in av) return `['BS' => [${av.BS.map((b) => `base64_decode(${phpString(b)})`).join(", ")}]]`;
1873
+ return "['NULL' => true]";
1874
+ }
1875
+ /** Render a typed map as a PHP array of AV literals, one entry per line. */
1876
+ function renderAvMap(map, indent) {
1877
+ const inner = `${indent} `;
1878
+ return `[\n${Object.entries(typedMapToAvMap(map)).map(([name, av]) => `${inner}${phpString(name)} => ${renderPhpAv(av)},`).join("\n")}\n${indent}]`;
1879
+ }
1880
+ /** Render the plain string map (`ExpressionAttributeNames`). */
1881
+ function renderNameMap$1(map, indent) {
1882
+ const inner = `${indent} `;
1883
+ return `[\n${Object.entries(map).map(([alias, name]) => `${inner}${phpString(alias)} => ${phpString(name)},`).join("\n")}\n${indent}]`;
1884
+ }
1885
+ /**
1886
+ * Render the request array literal, one field per line at `indent + 4`.
1887
+ * Exported for the program emitter.
1888
+ */
1889
+ function renderPhpParams(request, indent) {
1890
+ const pad = `${indent} `;
1891
+ const fields = [`'TableName' => ${phpString(request.tableName)},`];
1892
+ if (request.indexName) fields.push(`'IndexName' => ${phpString(request.indexName)},`);
1893
+ if (request.key) fields.push(`'Key' => ${renderAvMap(request.key, pad)},`);
1894
+ if (request.item) fields.push(`'Item' => ${renderAvMap(request.item, pad)},`);
1895
+ if (request.keyConditionExpression) fields.push(`'KeyConditionExpression' => ${phpString(request.keyConditionExpression)},`);
1896
+ if (request.updateExpression) fields.push(`'UpdateExpression' => ${phpString(request.updateExpression)},`);
1897
+ if (request.conditionExpression) fields.push(`'ConditionExpression' => ${phpString(request.conditionExpression)},`);
1898
+ if (request.filterExpression) fields.push(`'FilterExpression' => ${phpString(request.filterExpression)},`);
1899
+ if (request.projectionExpression) fields.push(`'ProjectionExpression' => ${phpString(request.projectionExpression)},`);
1900
+ if (request.names) fields.push(`'ExpressionAttributeNames' => ${renderNameMap$1(request.names, pad)},`);
1901
+ if (request.typedValues) fields.push(`'ExpressionAttributeValues' => ${renderAvMap(request.typedValues, pad)},`);
1902
+ if (request.limit !== void 0) fields.push(`'Limit' => ${request.limit},`);
1903
+ if (request.consistentRead) fields.push("'ConsistentRead' => true,");
1904
+ if (request.scanIndexForward === false) fields.push("'ScanIndexForward' => false,");
1905
+ if (request.exclusiveStartKey) fields.push(`'ExclusiveStartKey' => ${renderAvMap(request.exclusiveStartKey, pad)},`);
1906
+ return [
1907
+ "[",
1908
+ ...fields.map((f) => `${pad}${f}`),
1909
+ `${indent}]`
1910
+ ].join("\n");
1911
+ }
1912
+ /** Emit the bare `$response = $client->…([…]);` call for a canonical request. */
1913
+ function emitPhp(request) {
1914
+ return `$response = $client->${CLIENT_METHOD_BY_OP$1[request.operation]}(${renderPhpParams(request, "")});`;
1915
+ }
1916
+
1917
+ //#endregion
1918
+ //#region src/emit/ruby.ts
1919
+ /** Operation → the `Aws::DynamoDB::Client` method (snake_case). */
1920
+ const CLIENT_METHOD_BY_OP = {
1921
+ GetItem: "get_item",
1922
+ Query: "query",
1923
+ Scan: "scan",
1924
+ Update: "update_item",
1925
+ Put: "put_item",
1926
+ Delete: "delete_item"
1927
+ };
1928
+ /** The client method name (snake_case) for an operation. */
1929
+ function rubyClientMethodName(operation) {
1930
+ return CLIENT_METHOD_BY_OP[operation];
1931
+ }
1932
+ /** Ruby single-quoted string — only `\` and `'` need escaping. */
1933
+ function rubyString(value) {
1934
+ return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
1935
+ }
1936
+ /** Render one typed value as the native Ruby literal the SDK marshals. */
1937
+ function renderRubyValue(tv) {
1938
+ switch (tv.type) {
1939
+ case "S": return rubyString(tv.value);
1940
+ case "N": return tv.value;
1941
+ case "B": return `Base64.decode64(${rubyString(tv.value)})`;
1942
+ case "BOOL": return tv.value === "true" ? "true" : "false";
1943
+ case "NULL": return "nil";
1944
+ case "SS": return `Set.new([${(tv.values ?? []).map(rubyString).join(", ")}])`;
1945
+ case "NS": return `Set.new([${(tv.values ?? []).join(", ")}])`;
1946
+ case "BS": return `Set.new([${(tv.values ?? []).map((b) => `Base64.decode64(${rubyString(b)})`).join(", ")}])`;
1947
+ }
1948
+ }
1949
+ /** Render a typed map as a Ruby hash of native values, one entry per line. */
1950
+ function renderValueMap(map, indent) {
1951
+ const inner = `${indent} `;
1952
+ return `{\n${Object.entries(map).map(([name, tv]) => `${inner}${rubyString(name)} => ${renderRubyValue(tv)},`).join("\n")}\n${indent}}`;
1953
+ }
1954
+ /** Render the plain string map (`expression_attribute_names`). */
1955
+ function renderNameMap(map, indent) {
1956
+ const inner = `${indent} `;
1957
+ return `{\n${Object.entries(map).map(([alias, name]) => `${inner}${rubyString(alias)} => ${rubyString(name)},`).join("\n")}\n${indent}}`;
1958
+ }
1959
+ /**
1960
+ * Render the request keyword-argument hash, one field per line at `indent + 2`.
1961
+ * Exported for the program emitter.
1962
+ */
1963
+ function renderRubyParams(request, indent) {
1964
+ const pad = `${indent} `;
1965
+ const fields = [`table_name: ${rubyString(request.tableName)},`];
1966
+ if (request.indexName) fields.push(`index_name: ${rubyString(request.indexName)},`);
1967
+ if (request.key) fields.push(`key: ${renderValueMap(request.key, pad)},`);
1968
+ if (request.item) fields.push(`item: ${renderValueMap(request.item, pad)},`);
1969
+ if (request.keyConditionExpression) fields.push(`key_condition_expression: ${rubyString(request.keyConditionExpression)},`);
1970
+ if (request.updateExpression) fields.push(`update_expression: ${rubyString(request.updateExpression)},`);
1971
+ if (request.conditionExpression) fields.push(`condition_expression: ${rubyString(request.conditionExpression)},`);
1972
+ if (request.filterExpression) fields.push(`filter_expression: ${rubyString(request.filterExpression)},`);
1973
+ if (request.projectionExpression) fields.push(`projection_expression: ${rubyString(request.projectionExpression)},`);
1974
+ if (request.names) fields.push(`expression_attribute_names: ${renderNameMap(request.names, pad)},`);
1975
+ if (request.typedValues) fields.push(`expression_attribute_values: ${renderValueMap(request.typedValues, pad)},`);
1976
+ if (request.limit !== void 0) fields.push(`limit: ${request.limit},`);
1977
+ if (request.consistentRead) fields.push("consistent_read: true,");
1978
+ if (request.scanIndexForward === false) fields.push("scan_index_forward: false,");
1979
+ if (request.exclusiveStartKey) fields.push(`exclusive_start_key: ${renderValueMap(request.exclusiveStartKey, pad)},`);
1980
+ return [
1981
+ "{",
1982
+ ...fields.map((f) => `${pad}${f}`),
1983
+ `${indent}}`
1984
+ ].join("\n");
1985
+ }
1986
+ /** Emit the bare `response = client.…(…)` call for a canonical request. */
1987
+ function emitRuby(request) {
1988
+ return `response = client.${CLIENT_METHOD_BY_OP[request.operation]}(${renderRubyParams(request, "")})`;
1989
+ }
1990
+
1705
1991
  //#endregion
1706
1992
  //#region src/program.ts
1707
1993
  /**
@@ -1750,6 +2036,22 @@ function emitQueryProgram(config, format) {
1750
2036
  ok: true,
1751
2037
  code: emitRustProgram(request, config.paginate === true)
1752
2038
  };
2039
+ case "docclient": return {
2040
+ ok: true,
2041
+ code: emitDocClientProgram(request, config.paginate === true)
2042
+ };
2043
+ case "kotlin": return {
2044
+ ok: true,
2045
+ code: emitKotlinProgram(request, config.paginate === true)
2046
+ };
2047
+ case "php": return {
2048
+ ok: true,
2049
+ code: emitPhpProgram(request, config.paginate === true)
2050
+ };
2051
+ case "ruby": return {
2052
+ ok: true,
2053
+ code: emitRubyProgram(request, config.paginate === true)
2054
+ };
1753
2055
  case "ddbtoolbox": return {
1754
2056
  ok: true,
1755
2057
  code: emitDdbToolboxProgram(request, config.paginate === true)
@@ -1992,6 +2294,128 @@ function emitRustProgram(request, paginate) {
1992
2294
  "}"
1993
2295
  ].join("\n");
1994
2296
  }
2297
+ function emitDocClientProgram(request, paginate) {
2298
+ const command = docClientCommandName(request.operation);
2299
+ renderDocClientParams(request, "");
2300
+ if (!paginate) return [
2301
+ "import { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";",
2302
+ `import { DynamoDBDocumentClient, ${command} } from "@aws-sdk/lib-dynamodb";`,
2303
+ "",
2304
+ "const client = DynamoDBDocumentClient.from(new DynamoDBClient({}));",
2305
+ "",
2306
+ "const response = await client.send(",
2307
+ ` new ${command}(${renderDocClientParams(request, " ")})`,
2308
+ ");",
2309
+ "",
2310
+ "console.log(response.Items);"
2311
+ ].join("\n");
2312
+ const paginator = request.operation === "Scan" ? "paginateScan" : "paginateQuery";
2313
+ return [
2314
+ "import { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";",
2315
+ `import { DynamoDBDocumentClient, ${paginator} } from "@aws-sdk/lib-dynamodb";`,
2316
+ "",
2317
+ "const client = DynamoDBDocumentClient.from(new DynamoDBClient({}));",
2318
+ "",
2319
+ `const paginator = ${paginator}(`,
2320
+ " { client },",
2321
+ ` ${renderDocClientParams(request, " ")}`,
2322
+ ");",
2323
+ "",
2324
+ "for await (const page of paginator) {",
2325
+ " console.log(page.Items);",
2326
+ "}"
2327
+ ].join("\n");
2328
+ }
2329
+ function emitKotlinProgram(request, paginate) {
2330
+ const method = kotlinClientMethodName(request.operation);
2331
+ const header = [
2332
+ "import aws.sdk.kotlin.services.dynamodb.DynamoDbClient",
2333
+ "import aws.sdk.kotlin.services.dynamodb.model.AttributeValue",
2334
+ ...paginate ? [
2335
+ `import aws.sdk.kotlin.services.dynamodb.paginators.${method}Paginated`,
2336
+ "import aws.sdk.kotlin.services.dynamodb.paginators.items",
2337
+ "import kotlinx.coroutines.flow.collect"
2338
+ ] : [],
2339
+ "",
2340
+ "suspend fun main() {",
2341
+ " DynamoDbClient.fromEnvironment().use { client ->"
2342
+ ];
2343
+ const body = renderKotlinBuilder(request, " ");
2344
+ if (!paginate) return [
2345
+ ...header,
2346
+ ` val response = client.${method} {`,
2347
+ ...body,
2348
+ " }",
2349
+ " println(response.items)",
2350
+ " }",
2351
+ "}"
2352
+ ].join("\n");
2353
+ return [
2354
+ ...header,
2355
+ ` client.${method}Paginated {`,
2356
+ ...body,
2357
+ " }",
2358
+ " .items()",
2359
+ " .collect { item -> println(item) }",
2360
+ " }",
2361
+ "}"
2362
+ ].join("\n");
2363
+ }
2364
+ function emitPhpProgram(request, paginate) {
2365
+ const method = phpClientMethodName(request.operation);
2366
+ const header = [
2367
+ "<?php",
2368
+ "",
2369
+ "require 'vendor/autoload.php';",
2370
+ "",
2371
+ "use Aws\\DynamoDb\\DynamoDbClient;",
2372
+ "",
2373
+ "$client = new DynamoDbClient(['region' => 'us-east-1', 'version' => 'latest']);",
2374
+ ""
2375
+ ];
2376
+ if (!paginate) return [
2377
+ ...header,
2378
+ `$response = $client->${method}(${renderPhpParams(request, "")});`,
2379
+ "",
2380
+ "print_r($response['Items']);"
2381
+ ].join("\n");
2382
+ const operation = request.operation === "Scan" ? "Scan" : "Query";
2383
+ return [
2384
+ ...header,
2385
+ `$pages = $client->getPaginator('${operation}', ${renderPhpParams(request, "")});`,
2386
+ "",
2387
+ "foreach ($pages as $page) {",
2388
+ " print_r($page['Items']);",
2389
+ "}"
2390
+ ].join("\n");
2391
+ }
2392
+ function emitRubyProgram(request, paginate) {
2393
+ const method = rubyClientMethodName(request.operation);
2394
+ const usesSets = /Set\.new\(/.test(renderRubyParams(request, ""));
2395
+ const usesBase64 = /Base64\.decode64\(/.test(renderRubyParams(request, ""));
2396
+ const header = [
2397
+ "require 'aws-sdk-dynamodb'",
2398
+ ...usesSets ? ["require 'set'"] : [],
2399
+ ...usesBase64 ? ["require 'base64'"] : [],
2400
+ "",
2401
+ "client = Aws::DynamoDB::Client.new",
2402
+ ""
2403
+ ];
2404
+ if (!paginate) return [
2405
+ ...header,
2406
+ `response = client.${method}(${renderRubyParams(request, "")})`,
2407
+ "",
2408
+ "puts response.items"
2409
+ ].join("\n");
2410
+ return [
2411
+ ...header,
2412
+ `response = client.${method}(${renderRubyParams(request, "")})`,
2413
+ "",
2414
+ "response.each do |page|",
2415
+ " puts page.items",
2416
+ "end"
2417
+ ].join("\n");
2418
+ }
1995
2419
  function emitDotnetProgram(request, paginate) {
1996
2420
  const method = dotnetClientMethodName(request.operation);
1997
2421
  const binary = hasBinaryValue(request);
@@ -2048,17 +2472,22 @@ exports.buildKeyMap = buildKeyMap;
2048
2472
  exports.buildRequest = buildRequest;
2049
2473
  exports.buildSdkV3Params = buildSdkV3Params;
2050
2474
  exports.buildUpdateExpression = buildUpdateExpression;
2475
+ exports.docClientCommandName = docClientCommandName;
2051
2476
  exports.dotnetClientMethodName = dotnetClientMethodName;
2052
2477
  exports.dotnetRequestClassName = dotnetRequestClassName;
2053
2478
  exports.elementType = elementType;
2054
2479
  exports.emitBoto3 = emitBoto3;
2055
2480
  exports.emitCli = emitCli;
2056
2481
  exports.emitDdbToolboxProgram = emitDdbToolboxProgram;
2482
+ exports.emitDocClient = emitDocClient;
2057
2483
  exports.emitDotnet = emitDotnet;
2058
2484
  exports.emitGo = emitGo;
2059
2485
  exports.emitJava = emitJava;
2486
+ exports.emitKotlin = emitKotlin;
2060
2487
  exports.emitPartiql = emitPartiql;
2488
+ exports.emitPhp = emitPhp;
2061
2489
  exports.emitQueryProgram = emitQueryProgram;
2490
+ exports.emitRuby = emitRuby;
2062
2491
  exports.emitRust = emitRust;
2063
2492
  exports.emitSdkV3 = emitSdkV3;
2064
2493
  exports.getCompatibleComparisonOperators = getCompatibleComparisonOperators;
@@ -2069,10 +2498,16 @@ exports.hasBinaryValue = hasBinaryValue;
2069
2498
  exports.isSetType = isSetType;
2070
2499
  exports.javaClientMethodName = javaClientMethodName;
2071
2500
  exports.javaRequestClassName = javaRequestClassName;
2501
+ exports.kotlinClientMethodName = kotlinClientMethodName;
2072
2502
  exports.makeTypedValue = makeTypedValue;
2503
+ exports.phpClientMethodName = phpClientMethodName;
2073
2504
  exports.renderJsValue = renderJsValue;
2505
+ exports.renderKotlinAv = renderKotlinAv;
2506
+ exports.renderPhpAv = renderPhpAv;
2074
2507
  exports.renderPyValue = renderPyValue;
2508
+ exports.renderRubyValue = renderRubyValue;
2075
2509
  exports.renderRustAv = renderRustAv;
2510
+ exports.rubyClientMethodName = rubyClientMethodName;
2076
2511
  exports.rustClientMethodName = rustClientMethodName;
2077
2512
  exports.sdkV3CommandName = sdkV3CommandName;
2078
2513
  exports.typedMapToAvMap = typedMapToAvMap;
package/dist/index.d.cts CHANGED
@@ -618,6 +618,36 @@ declare function renderRustAv(av: AttributeValue): string;
618
618
  /** Emit the bare fluent call for a canonical request (awaited, `?`-propagated). */
619
619
  declare function emitRust(request: CanonicalRequest): string;
620
620
  //#endregion
621
+ //#region src/emit/docclient.d.ts
622
+ /** The lib-dynamodb command class name for an operation. */
623
+ declare function docClientCommandName(operation: DdbOperation): string;
624
+ /** Emit the bare `new XCommand({…})` snippet with native values. */
625
+ declare function emitDocClient(request: CanonicalRequest): string;
626
+ //#endregion
627
+ //#region src/emit/kotlin.d.ts
628
+ /** The client method name (camelCase) for an operation. */
629
+ declare function kotlinClientMethodName(operation: DdbOperation): string;
630
+ /** Render one wire AttributeValue as an `AttributeValue.…` constructor. */
631
+ declare function renderKotlinAv(av: AttributeValue): string;
632
+ /** Emit the bare `val response = client.…{ … }` call for a canonical request. */
633
+ declare function emitKotlin(request: CanonicalRequest): string;
634
+ //#endregion
635
+ //#region src/emit/php.d.ts
636
+ /** The client method name (camelCase) for an operation. */
637
+ declare function phpClientMethodName(operation: DdbOperation): string;
638
+ /** Render one wire AttributeValue as a nested `['TAG' => …]` literal. */
639
+ declare function renderPhpAv(av: AttributeValue): string;
640
+ /** Emit the bare `$response = $client->…([…]);` call for a canonical request. */
641
+ declare function emitPhp(request: CanonicalRequest): string;
642
+ //#endregion
643
+ //#region src/emit/ruby.d.ts
644
+ /** The client method name (snake_case) for an operation. */
645
+ declare function rubyClientMethodName(operation: DdbOperation): string;
646
+ /** Render one typed value as the native Ruby literal the SDK marshals. */
647
+ declare function renderRubyValue(tv: TypedValue): string;
648
+ /** Emit the bare `response = client.…(…)` call for a canonical request. */
649
+ declare function emitRuby(request: CanonicalRequest): string;
650
+ //#endregion
621
651
  //#region src/emit/ddbtoolbox.d.ts
622
652
  /**
623
653
  * Emit the runnable dynamodb-toolbox program for a Query/Scan canonical request.
@@ -636,7 +666,7 @@ interface QueryToolConfig extends BuilderConfig {
636
666
  /** Emit the fetch-all-pages pagination loop (default: single request). */
637
667
  paginate?: boolean;
638
668
  }
639
- type QueryProgramFormat = 'sdk' | 'cli' | 'boto3' | 'partiql' | 'java' | 'go' | 'dotnet' | 'rust' | 'ddbtoolbox';
669
+ type QueryProgramFormat = 'sdk' | 'docclient' | 'cli' | 'boto3' | 'partiql' | 'java' | 'go' | 'dotnet' | 'rust' | 'kotlin' | 'php' | 'ruby' | 'ddbtoolbox';
640
670
  /** A runnable program, or an honest reason the format can't express it. */
641
671
  type ProgramResult = {
642
672
  ok: true;
@@ -654,4 +684,4 @@ type ProgramResult = {
654
684
  */
655
685
  declare function emitQueryProgram(config: QueryToolConfig, format: QueryProgramFormat): ProgramResult;
656
686
  //#endregion
657
- export { type AttributeValue, type BuilderConfig, type CanonicalRequest, type DdbOperation, type DdbScalarType, FILTER_OPERATORS, type FilterDataType, type FilterOperatorOption, type FilterRow, type ItemAttr, KEY_OPERATORS, type KeyAttr, type KeyConditionResult, OPERATOR_BY_VALUE, type OperatorDef, type OperatorValue, type PartiqlResult, type PredicateExpression, type PredicatePrefix, type ProgramResult, type QueryProgramFormat, type QueryToolConfig, type RangeKeyCondition, SET_TYPES, type SetOperation, type SetType, type TypedValue, type UpdateAction, type UpdateActionKind, type UpdateExpressionResult, type WireFilterOperator, boto3MethodName, buildFilterExpressions, buildKeyConditionExpression, buildKeyMap, buildRequest, buildSdkV3Params, buildUpdateExpression, dotnetClientMethodName, dotnetRequestClassName, elementType, emitBoto3, emitCli, emitDdbToolboxProgram, emitDotnet, emitGo, emitJava, emitPartiql, emitQueryProgram, emitRust, emitSdkV3, getCompatibleComparisonOperators, getCompatibleFilterOperators, goClientMethodName, goInputTypeName, hasBinaryValue, isSetType, javaClientMethodName, javaRequestClassName, makeTypedValue, renderJsValue, renderPyValue, renderRustAv, rustClientMethodName, sdkV3CommandName, typedMapToAvMap, typedValueToAv };
687
+ export { type AttributeValue, type BuilderConfig, type CanonicalRequest, type DdbOperation, type DdbScalarType, FILTER_OPERATORS, type FilterDataType, type FilterOperatorOption, type FilterRow, type ItemAttr, KEY_OPERATORS, type KeyAttr, type KeyConditionResult, OPERATOR_BY_VALUE, type OperatorDef, type OperatorValue, type PartiqlResult, type PredicateExpression, type PredicatePrefix, type ProgramResult, type QueryProgramFormat, type QueryToolConfig, type RangeKeyCondition, SET_TYPES, type SetOperation, type SetType, type TypedValue, type UpdateAction, type UpdateActionKind, type UpdateExpressionResult, type WireFilterOperator, boto3MethodName, buildFilterExpressions, buildKeyConditionExpression, buildKeyMap, buildRequest, buildSdkV3Params, buildUpdateExpression, docClientCommandName, dotnetClientMethodName, dotnetRequestClassName, elementType, emitBoto3, emitCli, emitDdbToolboxProgram, emitDocClient, emitDotnet, emitGo, emitJava, emitKotlin, emitPartiql, emitPhp, emitQueryProgram, emitRuby, emitRust, emitSdkV3, getCompatibleComparisonOperators, getCompatibleFilterOperators, goClientMethodName, goInputTypeName, hasBinaryValue, isSetType, javaClientMethodName, javaRequestClassName, kotlinClientMethodName, makeTypedValue, phpClientMethodName, renderJsValue, renderKotlinAv, renderPhpAv, renderPyValue, renderRubyValue, renderRustAv, rubyClientMethodName, rustClientMethodName, sdkV3CommandName, typedMapToAvMap, typedValueToAv };
package/dist/index.d.ts CHANGED
@@ -618,6 +618,36 @@ declare function renderRustAv(av: AttributeValue): string;
618
618
  /** Emit the bare fluent call for a canonical request (awaited, `?`-propagated). */
619
619
  declare function emitRust(request: CanonicalRequest): string;
620
620
  //#endregion
621
+ //#region src/emit/docclient.d.ts
622
+ /** The lib-dynamodb command class name for an operation. */
623
+ declare function docClientCommandName(operation: DdbOperation): string;
624
+ /** Emit the bare `new XCommand({…})` snippet with native values. */
625
+ declare function emitDocClient(request: CanonicalRequest): string;
626
+ //#endregion
627
+ //#region src/emit/kotlin.d.ts
628
+ /** The client method name (camelCase) for an operation. */
629
+ declare function kotlinClientMethodName(operation: DdbOperation): string;
630
+ /** Render one wire AttributeValue as an `AttributeValue.…` constructor. */
631
+ declare function renderKotlinAv(av: AttributeValue): string;
632
+ /** Emit the bare `val response = client.…{ … }` call for a canonical request. */
633
+ declare function emitKotlin(request: CanonicalRequest): string;
634
+ //#endregion
635
+ //#region src/emit/php.d.ts
636
+ /** The client method name (camelCase) for an operation. */
637
+ declare function phpClientMethodName(operation: DdbOperation): string;
638
+ /** Render one wire AttributeValue as a nested `['TAG' => …]` literal. */
639
+ declare function renderPhpAv(av: AttributeValue): string;
640
+ /** Emit the bare `$response = $client->…([…]);` call for a canonical request. */
641
+ declare function emitPhp(request: CanonicalRequest): string;
642
+ //#endregion
643
+ //#region src/emit/ruby.d.ts
644
+ /** The client method name (snake_case) for an operation. */
645
+ declare function rubyClientMethodName(operation: DdbOperation): string;
646
+ /** Render one typed value as the native Ruby literal the SDK marshals. */
647
+ declare function renderRubyValue(tv: TypedValue): string;
648
+ /** Emit the bare `response = client.…(…)` call for a canonical request. */
649
+ declare function emitRuby(request: CanonicalRequest): string;
650
+ //#endregion
621
651
  //#region src/emit/ddbtoolbox.d.ts
622
652
  /**
623
653
  * Emit the runnable dynamodb-toolbox program for a Query/Scan canonical request.
@@ -636,7 +666,7 @@ interface QueryToolConfig extends BuilderConfig {
636
666
  /** Emit the fetch-all-pages pagination loop (default: single request). */
637
667
  paginate?: boolean;
638
668
  }
639
- type QueryProgramFormat = 'sdk' | 'cli' | 'boto3' | 'partiql' | 'java' | 'go' | 'dotnet' | 'rust' | 'ddbtoolbox';
669
+ type QueryProgramFormat = 'sdk' | 'docclient' | 'cli' | 'boto3' | 'partiql' | 'java' | 'go' | 'dotnet' | 'rust' | 'kotlin' | 'php' | 'ruby' | 'ddbtoolbox';
640
670
  /** A runnable program, or an honest reason the format can't express it. */
641
671
  type ProgramResult = {
642
672
  ok: true;
@@ -654,4 +684,4 @@ type ProgramResult = {
654
684
  */
655
685
  declare function emitQueryProgram(config: QueryToolConfig, format: QueryProgramFormat): ProgramResult;
656
686
  //#endregion
657
- export { type AttributeValue, type BuilderConfig, type CanonicalRequest, type DdbOperation, type DdbScalarType, FILTER_OPERATORS, type FilterDataType, type FilterOperatorOption, type FilterRow, type ItemAttr, KEY_OPERATORS, type KeyAttr, type KeyConditionResult, OPERATOR_BY_VALUE, type OperatorDef, type OperatorValue, type PartiqlResult, type PredicateExpression, type PredicatePrefix, type ProgramResult, type QueryProgramFormat, type QueryToolConfig, type RangeKeyCondition, SET_TYPES, type SetOperation, type SetType, type TypedValue, type UpdateAction, type UpdateActionKind, type UpdateExpressionResult, type WireFilterOperator, boto3MethodName, buildFilterExpressions, buildKeyConditionExpression, buildKeyMap, buildRequest, buildSdkV3Params, buildUpdateExpression, dotnetClientMethodName, dotnetRequestClassName, elementType, emitBoto3, emitCli, emitDdbToolboxProgram, emitDotnet, emitGo, emitJava, emitPartiql, emitQueryProgram, emitRust, emitSdkV3, getCompatibleComparisonOperators, getCompatibleFilterOperators, goClientMethodName, goInputTypeName, hasBinaryValue, isSetType, javaClientMethodName, javaRequestClassName, makeTypedValue, renderJsValue, renderPyValue, renderRustAv, rustClientMethodName, sdkV3CommandName, typedMapToAvMap, typedValueToAv };
687
+ export { type AttributeValue, type BuilderConfig, type CanonicalRequest, type DdbOperation, type DdbScalarType, FILTER_OPERATORS, type FilterDataType, type FilterOperatorOption, type FilterRow, type ItemAttr, KEY_OPERATORS, type KeyAttr, type KeyConditionResult, OPERATOR_BY_VALUE, type OperatorDef, type OperatorValue, type PartiqlResult, type PredicateExpression, type PredicatePrefix, type ProgramResult, type QueryProgramFormat, type QueryToolConfig, type RangeKeyCondition, SET_TYPES, type SetOperation, type SetType, type TypedValue, type UpdateAction, type UpdateActionKind, type UpdateExpressionResult, type WireFilterOperator, boto3MethodName, buildFilterExpressions, buildKeyConditionExpression, buildKeyMap, buildRequest, buildSdkV3Params, buildUpdateExpression, docClientCommandName, dotnetClientMethodName, dotnetRequestClassName, elementType, emitBoto3, emitCli, emitDdbToolboxProgram, emitDocClient, emitDotnet, emitGo, emitJava, emitKotlin, emitPartiql, emitPhp, emitQueryProgram, emitRuby, emitRust, emitSdkV3, getCompatibleComparisonOperators, getCompatibleFilterOperators, goClientMethodName, goInputTypeName, hasBinaryValue, isSetType, javaClientMethodName, javaRequestClassName, kotlinClientMethodName, makeTypedValue, phpClientMethodName, renderJsValue, renderKotlinAv, renderPhpAv, renderPyValue, renderRubyValue, renderRustAv, rubyClientMethodName, rustClientMethodName, sdkV3CommandName, typedMapToAvMap, typedValueToAv };
package/dist/index.js CHANGED
@@ -746,7 +746,7 @@ function hasBinaryValue(request) {
746
746
  //#endregion
747
747
  //#region src/emit/sdk-v3.ts
748
748
  /** Operation → the `@aws-sdk/client-dynamodb` command class name. */
749
- const COMMAND_BY_OP = {
749
+ const COMMAND_BY_OP$1 = {
750
750
  GetItem: "GetItemCommand",
751
751
  Query: "QueryCommand",
752
752
  Scan: "ScanCommand",
@@ -804,11 +804,11 @@ function jsLiteral(value, indent) {
804
804
  }
805
805
  /** Emit the `new <Op>Command({...})` source snippet for a canonical request. */
806
806
  function emitSdkV3(request) {
807
- return `new ${COMMAND_BY_OP[request.operation]}(${jsLiteral(buildSdkV3Params(request), 0)})`;
807
+ return `new ${COMMAND_BY_OP$1[request.operation]}(${jsLiteral(buildSdkV3Params(request), 0)})`;
808
808
  }
809
809
  /** The `@aws-sdk/client-dynamodb` command class name for an operation. */
810
810
  function sdkV3CommandName(operation) {
811
- return COMMAND_BY_OP[operation];
811
+ return COMMAND_BY_OP$1[operation];
812
812
  }
813
813
  /**
814
814
  * Render an arbitrary params value as the same pretty-printed JS literal
@@ -1116,7 +1116,7 @@ const REQUEST_CLASS_BY_OP$1 = {
1116
1116
  Delete: "DeleteItemRequest"
1117
1117
  };
1118
1118
  /** Operation → the `DynamoDbClient` method. */
1119
- const CLIENT_METHOD_BY_OP$3 = {
1119
+ const CLIENT_METHOD_BY_OP$6 = {
1120
1120
  GetItem: "getItem",
1121
1121
  Query: "query",
1122
1122
  Scan: "scan",
@@ -1130,7 +1130,7 @@ function javaRequestClassName(operation) {
1130
1130
  }
1131
1131
  /** The `DynamoDbClient` method name for an operation (`query`, …). */
1132
1132
  function javaClientMethodName(operation) {
1133
- return CLIENT_METHOD_BY_OP$3[operation];
1133
+ return CLIENT_METHOD_BY_OP$6[operation];
1134
1134
  }
1135
1135
  /** Java string literal — `JSON.stringify` escapes are all valid Java escapes. */
1136
1136
  function javaString(value) {
@@ -1209,7 +1209,7 @@ const INPUT_TYPE_BY_OP = {
1209
1209
  Delete: "DeleteItemInput"
1210
1210
  };
1211
1211
  /** Operation → the `dynamodb.Client` method. */
1212
- const CLIENT_METHOD_BY_OP$2 = {
1212
+ const CLIENT_METHOD_BY_OP$5 = {
1213
1213
  GetItem: "GetItem",
1214
1214
  Query: "Query",
1215
1215
  Scan: "Scan",
@@ -1223,7 +1223,7 @@ function goInputTypeName(operation) {
1223
1223
  }
1224
1224
  /** The `dynamodb.Client` method name for an operation. */
1225
1225
  function goClientMethodName(operation) {
1226
- return CLIENT_METHOD_BY_OP$2[operation];
1226
+ return CLIENT_METHOD_BY_OP$5[operation];
1227
1227
  }
1228
1228
  /** Go string literal — `JSON.stringify` escapes are all valid Go escapes. */
1229
1229
  function goString(value) {
@@ -1313,7 +1313,7 @@ const REQUEST_CLASS_BY_OP = {
1313
1313
  Delete: "DeleteItemRequest"
1314
1314
  };
1315
1315
  /** Operation → the async `AmazonDynamoDBClient` method. */
1316
- const CLIENT_METHOD_BY_OP$1 = {
1316
+ const CLIENT_METHOD_BY_OP$4 = {
1317
1317
  GetItem: "GetItemAsync",
1318
1318
  Query: "QueryAsync",
1319
1319
  Scan: "ScanAsync",
@@ -1327,7 +1327,7 @@ function dotnetRequestClassName(operation) {
1327
1327
  }
1328
1328
  /** The async client method name for an operation (`QueryAsync`, …). */
1329
1329
  function dotnetClientMethodName(operation) {
1330
- return CLIENT_METHOD_BY_OP$1[operation];
1330
+ return CLIENT_METHOD_BY_OP$4[operation];
1331
1331
  }
1332
1332
  /** C# string literal — `JSON.stringify` escapes are all valid C# escapes. */
1333
1333
  function csString(value) {
@@ -1397,7 +1397,7 @@ function emitDotnet(request) {
1397
1397
  //#endregion
1398
1398
  //#region src/emit/rust.ts
1399
1399
  /** Operation → the `aws_sdk_dynamodb::Client` fluent method. */
1400
- const CLIENT_METHOD_BY_OP = {
1400
+ const CLIENT_METHOD_BY_OP$3 = {
1401
1401
  GetItem: "get_item",
1402
1402
  Query: "query",
1403
1403
  Scan: "scan",
@@ -1407,7 +1407,7 @@ const CLIENT_METHOD_BY_OP = {
1407
1407
  };
1408
1408
  /** The `Client` method name (snake_case) for an operation. */
1409
1409
  function rustClientMethodName(operation) {
1410
- return CLIENT_METHOD_BY_OP[operation];
1410
+ return CLIENT_METHOD_BY_OP$3[operation];
1411
1411
  }
1412
1412
  /** Rust string literal — JSON escapes with the three Rust-incompatible ones fixed. */
1413
1413
  function rustString(value) {
@@ -1449,7 +1449,7 @@ function avEntryLines(method, map, indent) {
1449
1449
  * chain to `into_paginator()` instead). Exported for the program emitter.
1450
1450
  */
1451
1451
  function renderRustBuilder(request, indent) {
1452
- const lines = [`${indent}.${CLIENT_METHOD_BY_OP[request.operation]}()`];
1452
+ const lines = [`${indent}.${CLIENT_METHOD_BY_OP$3[request.operation]}()`];
1453
1453
  lines.push(`${indent}.table_name(${rustString(request.tableName)})`);
1454
1454
  if (request.indexName) lines.push(`${indent}.index_name(${rustString(request.indexName)})`);
1455
1455
  if (request.key) lines.push(...avEntryLines("key", request.key, indent));
@@ -1701,6 +1701,292 @@ function emitDdbToolboxProgram(request, paginate) {
1701
1701
  ].filter((l) => l !== null).join("\n");
1702
1702
  }
1703
1703
 
1704
+ //#endregion
1705
+ //#region src/emit/docclient.ts
1706
+ /** Operation → the `@aws-sdk/lib-dynamodb` command class. */
1707
+ const COMMAND_BY_OP = {
1708
+ GetItem: "GetCommand",
1709
+ Query: "QueryCommand",
1710
+ Scan: "ScanCommand",
1711
+ Update: "UpdateCommand",
1712
+ Put: "PutCommand",
1713
+ Delete: "DeleteCommand"
1714
+ };
1715
+ /** The lib-dynamodb command class name for an operation. */
1716
+ function docClientCommandName(operation) {
1717
+ return COMMAND_BY_OP[operation];
1718
+ }
1719
+ /** Render a typed map as a native-value object literal, one entry per line. */
1720
+ function renderNativeMap(map, indent) {
1721
+ const inner = `${indent} `;
1722
+ return `{\n${Object.entries(map).map(([name, tv]) => `${inner}${JSON.stringify(name)}: ${renderNativeValue(tv)},`).join("\n")}\n${indent}}`;
1723
+ }
1724
+ /** Render the plain string map (`ExpressionAttributeNames`). */
1725
+ function renderNameMap$3(map, indent) {
1726
+ const inner = `${indent} `;
1727
+ return `{\n${Object.entries(map).map(([alias, name]) => `${inner}${JSON.stringify(alias)}: ${JSON.stringify(name)},`).join("\n")}\n${indent}}`;
1728
+ }
1729
+ /**
1730
+ * Render the command's params object literal, one field per line at
1731
+ * `indent + 2`. Exported for the program emitter.
1732
+ */
1733
+ function renderDocClientParams(request, indent) {
1734
+ const pad = `${indent} `;
1735
+ const fields = [`TableName: ${JSON.stringify(request.tableName)},`];
1736
+ if (request.indexName) fields.push(`IndexName: ${JSON.stringify(request.indexName)},`);
1737
+ if (request.key) fields.push(`Key: ${renderNativeMap(request.key, pad)},`);
1738
+ if (request.item) fields.push(`Item: ${renderNativeMap(request.item, pad)},`);
1739
+ if (request.keyConditionExpression) fields.push(`KeyConditionExpression: ${JSON.stringify(request.keyConditionExpression)},`);
1740
+ if (request.updateExpression) fields.push(`UpdateExpression: ${JSON.stringify(request.updateExpression)},`);
1741
+ if (request.conditionExpression) fields.push(`ConditionExpression: ${JSON.stringify(request.conditionExpression)},`);
1742
+ if (request.filterExpression) fields.push(`FilterExpression: ${JSON.stringify(request.filterExpression)},`);
1743
+ if (request.projectionExpression) fields.push(`ProjectionExpression: ${JSON.stringify(request.projectionExpression)},`);
1744
+ if (request.names) fields.push(`ExpressionAttributeNames: ${renderNameMap$3(request.names, pad)},`);
1745
+ if (request.typedValues) fields.push(`ExpressionAttributeValues: ${renderNativeMap(request.typedValues, pad)},`);
1746
+ if (request.limit !== void 0) fields.push(`Limit: ${request.limit},`);
1747
+ if (request.consistentRead) fields.push("ConsistentRead: true,");
1748
+ if (request.scanIndexForward === false) fields.push("ScanIndexForward: false,");
1749
+ if (request.exclusiveStartKey) fields.push(`ExclusiveStartKey: ${renderNativeMap(request.exclusiveStartKey, pad)},`);
1750
+ return [
1751
+ "{",
1752
+ ...fields.map((f) => `${pad}${f}`),
1753
+ `${indent}}`
1754
+ ].join("\n");
1755
+ }
1756
+ /** Emit the bare `new XCommand({…})` snippet with native values. */
1757
+ function emitDocClient(request) {
1758
+ return `new ${COMMAND_BY_OP[request.operation]}(${renderDocClientParams(request, "")})`;
1759
+ }
1760
+
1761
+ //#endregion
1762
+ //#region src/emit/kotlin.ts
1763
+ /** Operation → the `DynamoDbClient` suspend function. */
1764
+ const CLIENT_METHOD_BY_OP$2 = {
1765
+ GetItem: "getItem",
1766
+ Query: "query",
1767
+ Scan: "scan",
1768
+ Update: "updateItem",
1769
+ Put: "putItem",
1770
+ Delete: "deleteItem"
1771
+ };
1772
+ /** The client method name (camelCase) for an operation. */
1773
+ function kotlinClientMethodName(operation) {
1774
+ return CLIENT_METHOD_BY_OP$2[operation];
1775
+ }
1776
+ /** Kotlin string literal — JSON escapes are valid Kotlin escapes, plus `$` must
1777
+ * be escaped (string templates). */
1778
+ function ktString(value) {
1779
+ return JSON.stringify(value).replace(/\$/g, "\\$");
1780
+ }
1781
+ /** Decode canonical base64 into a `byteArrayOf(0x…)` literal (fail-loud). */
1782
+ function ktBytes(base64) {
1783
+ let raw;
1784
+ try {
1785
+ raw = atob(base64);
1786
+ } catch {
1787
+ throw new Error(`invalid base64 in a binary (B/BS) value: ${base64}`);
1788
+ }
1789
+ return `byteArrayOf(${Array.from(raw, (c) => `0x${c.charCodeAt(0).toString(16).padStart(2, "0")}`).join(", ")})`;
1790
+ }
1791
+ /** Render one wire AttributeValue as an `AttributeValue.…` constructor. */
1792
+ function renderKotlinAv(av) {
1793
+ if ("S" in av) return `AttributeValue.S(${ktString(av.S)})`;
1794
+ if ("N" in av) return `AttributeValue.N(${ktString(av.N)})`;
1795
+ if ("B" in av) return `AttributeValue.B(${ktBytes(av.B)})`;
1796
+ if ("BOOL" in av) return `AttributeValue.Bool(${av.BOOL})`;
1797
+ if ("SS" in av) return `AttributeValue.Ss(listOf(${av.SS.map(ktString).join(", ")}))`;
1798
+ if ("NS" in av) return `AttributeValue.Ns(listOf(${av.NS.map(ktString).join(", ")}))`;
1799
+ if ("BS" in av) return `AttributeValue.Bs(listOf(${av.BS.map(ktBytes).join(", ")}))`;
1800
+ return "AttributeValue.Null(true)";
1801
+ }
1802
+ /** Render a typed map as `mapOf("k" to AttributeValue.…)`, one entry per line. */
1803
+ function renderAvMap$1(map, indent) {
1804
+ const inner = `${indent} `;
1805
+ return `mapOf(\n${Object.entries(typedMapToAvMap(map)).map(([name, av]) => `${inner}${ktString(name)} to ${renderKotlinAv(av)},`).join("\n")}\n${indent})`;
1806
+ }
1807
+ /** Render the plain string map (`expressionAttributeNames`). */
1808
+ function renderNameMap$2(map, indent) {
1809
+ const inner = `${indent} `;
1810
+ return `mapOf(\n${Object.entries(map).map(([alias, name]) => `${inner}${ktString(alias)} to ${ktString(name)},`).join("\n")}\n${indent})`;
1811
+ }
1812
+ /**
1813
+ * Render the builder-lambda body — one property assignment per line at
1814
+ * `indent`. Exported for the program emitter, which wraps the same body in
1815
+ * either the plain suspend call or the `Paginated` flow variant.
1816
+ */
1817
+ function renderKotlinBuilder(request, indent) {
1818
+ const lines = [`${indent}tableName = ${ktString(request.tableName)}`];
1819
+ if (request.indexName) lines.push(`${indent}indexName = ${ktString(request.indexName)}`);
1820
+ if (request.key) lines.push(`${indent}key = ${renderAvMap$1(request.key, indent)}`);
1821
+ if (request.item) lines.push(`${indent}item = ${renderAvMap$1(request.item, indent)}`);
1822
+ if (request.keyConditionExpression) lines.push(`${indent}keyConditionExpression = ${ktString(request.keyConditionExpression)}`);
1823
+ if (request.updateExpression) lines.push(`${indent}updateExpression = ${ktString(request.updateExpression)}`);
1824
+ if (request.conditionExpression) lines.push(`${indent}conditionExpression = ${ktString(request.conditionExpression)}`);
1825
+ if (request.filterExpression) lines.push(`${indent}filterExpression = ${ktString(request.filterExpression)}`);
1826
+ if (request.projectionExpression) lines.push(`${indent}projectionExpression = ${ktString(request.projectionExpression)}`);
1827
+ if (request.names) lines.push(`${indent}expressionAttributeNames = ${renderNameMap$2(request.names, indent)}`);
1828
+ if (request.typedValues) lines.push(`${indent}expressionAttributeValues = ${renderAvMap$1(request.typedValues, indent)}`);
1829
+ if (request.limit !== void 0) lines.push(`${indent}limit = ${request.limit}`);
1830
+ if (request.consistentRead) lines.push(`${indent}consistentRead = true`);
1831
+ if (request.scanIndexForward === false) lines.push(`${indent}scanIndexForward = false`);
1832
+ if (request.exclusiveStartKey) lines.push(`${indent}exclusiveStartKey = ${renderAvMap$1(request.exclusiveStartKey, indent)}`);
1833
+ return lines;
1834
+ }
1835
+ /** Emit the bare `val response = client.…{ … }` call for a canonical request. */
1836
+ function emitKotlin(request) {
1837
+ return [
1838
+ `val response = client.${CLIENT_METHOD_BY_OP$2[request.operation]} {`,
1839
+ ...renderKotlinBuilder(request, " "),
1840
+ "}"
1841
+ ].join("\n");
1842
+ }
1843
+
1844
+ //#endregion
1845
+ //#region src/emit/php.ts
1846
+ /** Operation → the `DynamoDbClient` method. */
1847
+ const CLIENT_METHOD_BY_OP$1 = {
1848
+ GetItem: "getItem",
1849
+ Query: "query",
1850
+ Scan: "scan",
1851
+ Update: "updateItem",
1852
+ Put: "putItem",
1853
+ Delete: "deleteItem"
1854
+ };
1855
+ /** The client method name (camelCase) for an operation. */
1856
+ function phpClientMethodName(operation) {
1857
+ return CLIENT_METHOD_BY_OP$1[operation];
1858
+ }
1859
+ /** PHP single-quoted string — only `\` and `'` need escaping. */
1860
+ function phpString(value) {
1861
+ return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
1862
+ }
1863
+ /** Render one wire AttributeValue as a nested `['TAG' => …]` literal. */
1864
+ function renderPhpAv(av) {
1865
+ if ("S" in av) return `['S' => ${phpString(av.S)}]`;
1866
+ if ("N" in av) return `['N' => ${phpString(av.N)}]`;
1867
+ if ("B" in av) return `['B' => base64_decode(${phpString(av.B)})]`;
1868
+ if ("BOOL" in av) return `['BOOL' => ${av.BOOL}]`;
1869
+ if ("SS" in av) return `['SS' => [${av.SS.map(phpString).join(", ")}]]`;
1870
+ if ("NS" in av) return `['NS' => [${av.NS.map(phpString).join(", ")}]]`;
1871
+ if ("BS" in av) return `['BS' => [${av.BS.map((b) => `base64_decode(${phpString(b)})`).join(", ")}]]`;
1872
+ return "['NULL' => true]";
1873
+ }
1874
+ /** Render a typed map as a PHP array of AV literals, one entry per line. */
1875
+ function renderAvMap(map, indent) {
1876
+ const inner = `${indent} `;
1877
+ return `[\n${Object.entries(typedMapToAvMap(map)).map(([name, av]) => `${inner}${phpString(name)} => ${renderPhpAv(av)},`).join("\n")}\n${indent}]`;
1878
+ }
1879
+ /** Render the plain string map (`ExpressionAttributeNames`). */
1880
+ function renderNameMap$1(map, indent) {
1881
+ const inner = `${indent} `;
1882
+ return `[\n${Object.entries(map).map(([alias, name]) => `${inner}${phpString(alias)} => ${phpString(name)},`).join("\n")}\n${indent}]`;
1883
+ }
1884
+ /**
1885
+ * Render the request array literal, one field per line at `indent + 4`.
1886
+ * Exported for the program emitter.
1887
+ */
1888
+ function renderPhpParams(request, indent) {
1889
+ const pad = `${indent} `;
1890
+ const fields = [`'TableName' => ${phpString(request.tableName)},`];
1891
+ if (request.indexName) fields.push(`'IndexName' => ${phpString(request.indexName)},`);
1892
+ if (request.key) fields.push(`'Key' => ${renderAvMap(request.key, pad)},`);
1893
+ if (request.item) fields.push(`'Item' => ${renderAvMap(request.item, pad)},`);
1894
+ if (request.keyConditionExpression) fields.push(`'KeyConditionExpression' => ${phpString(request.keyConditionExpression)},`);
1895
+ if (request.updateExpression) fields.push(`'UpdateExpression' => ${phpString(request.updateExpression)},`);
1896
+ if (request.conditionExpression) fields.push(`'ConditionExpression' => ${phpString(request.conditionExpression)},`);
1897
+ if (request.filterExpression) fields.push(`'FilterExpression' => ${phpString(request.filterExpression)},`);
1898
+ if (request.projectionExpression) fields.push(`'ProjectionExpression' => ${phpString(request.projectionExpression)},`);
1899
+ if (request.names) fields.push(`'ExpressionAttributeNames' => ${renderNameMap$1(request.names, pad)},`);
1900
+ if (request.typedValues) fields.push(`'ExpressionAttributeValues' => ${renderAvMap(request.typedValues, pad)},`);
1901
+ if (request.limit !== void 0) fields.push(`'Limit' => ${request.limit},`);
1902
+ if (request.consistentRead) fields.push("'ConsistentRead' => true,");
1903
+ if (request.scanIndexForward === false) fields.push("'ScanIndexForward' => false,");
1904
+ if (request.exclusiveStartKey) fields.push(`'ExclusiveStartKey' => ${renderAvMap(request.exclusiveStartKey, pad)},`);
1905
+ return [
1906
+ "[",
1907
+ ...fields.map((f) => `${pad}${f}`),
1908
+ `${indent}]`
1909
+ ].join("\n");
1910
+ }
1911
+ /** Emit the bare `$response = $client->…([…]);` call for a canonical request. */
1912
+ function emitPhp(request) {
1913
+ return `$response = $client->${CLIENT_METHOD_BY_OP$1[request.operation]}(${renderPhpParams(request, "")});`;
1914
+ }
1915
+
1916
+ //#endregion
1917
+ //#region src/emit/ruby.ts
1918
+ /** Operation → the `Aws::DynamoDB::Client` method (snake_case). */
1919
+ const CLIENT_METHOD_BY_OP = {
1920
+ GetItem: "get_item",
1921
+ Query: "query",
1922
+ Scan: "scan",
1923
+ Update: "update_item",
1924
+ Put: "put_item",
1925
+ Delete: "delete_item"
1926
+ };
1927
+ /** The client method name (snake_case) for an operation. */
1928
+ function rubyClientMethodName(operation) {
1929
+ return CLIENT_METHOD_BY_OP[operation];
1930
+ }
1931
+ /** Ruby single-quoted string — only `\` and `'` need escaping. */
1932
+ function rubyString(value) {
1933
+ return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
1934
+ }
1935
+ /** Render one typed value as the native Ruby literal the SDK marshals. */
1936
+ function renderRubyValue(tv) {
1937
+ switch (tv.type) {
1938
+ case "S": return rubyString(tv.value);
1939
+ case "N": return tv.value;
1940
+ case "B": return `Base64.decode64(${rubyString(tv.value)})`;
1941
+ case "BOOL": return tv.value === "true" ? "true" : "false";
1942
+ case "NULL": return "nil";
1943
+ case "SS": return `Set.new([${(tv.values ?? []).map(rubyString).join(", ")}])`;
1944
+ case "NS": return `Set.new([${(tv.values ?? []).join(", ")}])`;
1945
+ case "BS": return `Set.new([${(tv.values ?? []).map((b) => `Base64.decode64(${rubyString(b)})`).join(", ")}])`;
1946
+ }
1947
+ }
1948
+ /** Render a typed map as a Ruby hash of native values, one entry per line. */
1949
+ function renderValueMap(map, indent) {
1950
+ const inner = `${indent} `;
1951
+ return `{\n${Object.entries(map).map(([name, tv]) => `${inner}${rubyString(name)} => ${renderRubyValue(tv)},`).join("\n")}\n${indent}}`;
1952
+ }
1953
+ /** Render the plain string map (`expression_attribute_names`). */
1954
+ function renderNameMap(map, indent) {
1955
+ const inner = `${indent} `;
1956
+ return `{\n${Object.entries(map).map(([alias, name]) => `${inner}${rubyString(alias)} => ${rubyString(name)},`).join("\n")}\n${indent}}`;
1957
+ }
1958
+ /**
1959
+ * Render the request keyword-argument hash, one field per line at `indent + 2`.
1960
+ * Exported for the program emitter.
1961
+ */
1962
+ function renderRubyParams(request, indent) {
1963
+ const pad = `${indent} `;
1964
+ const fields = [`table_name: ${rubyString(request.tableName)},`];
1965
+ if (request.indexName) fields.push(`index_name: ${rubyString(request.indexName)},`);
1966
+ if (request.key) fields.push(`key: ${renderValueMap(request.key, pad)},`);
1967
+ if (request.item) fields.push(`item: ${renderValueMap(request.item, pad)},`);
1968
+ if (request.keyConditionExpression) fields.push(`key_condition_expression: ${rubyString(request.keyConditionExpression)},`);
1969
+ if (request.updateExpression) fields.push(`update_expression: ${rubyString(request.updateExpression)},`);
1970
+ if (request.conditionExpression) fields.push(`condition_expression: ${rubyString(request.conditionExpression)},`);
1971
+ if (request.filterExpression) fields.push(`filter_expression: ${rubyString(request.filterExpression)},`);
1972
+ if (request.projectionExpression) fields.push(`projection_expression: ${rubyString(request.projectionExpression)},`);
1973
+ if (request.names) fields.push(`expression_attribute_names: ${renderNameMap(request.names, pad)},`);
1974
+ if (request.typedValues) fields.push(`expression_attribute_values: ${renderValueMap(request.typedValues, pad)},`);
1975
+ if (request.limit !== void 0) fields.push(`limit: ${request.limit},`);
1976
+ if (request.consistentRead) fields.push("consistent_read: true,");
1977
+ if (request.scanIndexForward === false) fields.push("scan_index_forward: false,");
1978
+ if (request.exclusiveStartKey) fields.push(`exclusive_start_key: ${renderValueMap(request.exclusiveStartKey, pad)},`);
1979
+ return [
1980
+ "{",
1981
+ ...fields.map((f) => `${pad}${f}`),
1982
+ `${indent}}`
1983
+ ].join("\n");
1984
+ }
1985
+ /** Emit the bare `response = client.…(…)` call for a canonical request. */
1986
+ function emitRuby(request) {
1987
+ return `response = client.${CLIENT_METHOD_BY_OP[request.operation]}(${renderRubyParams(request, "")})`;
1988
+ }
1989
+
1704
1990
  //#endregion
1705
1991
  //#region src/program.ts
1706
1992
  /**
@@ -1749,6 +2035,22 @@ function emitQueryProgram(config, format) {
1749
2035
  ok: true,
1750
2036
  code: emitRustProgram(request, config.paginate === true)
1751
2037
  };
2038
+ case "docclient": return {
2039
+ ok: true,
2040
+ code: emitDocClientProgram(request, config.paginate === true)
2041
+ };
2042
+ case "kotlin": return {
2043
+ ok: true,
2044
+ code: emitKotlinProgram(request, config.paginate === true)
2045
+ };
2046
+ case "php": return {
2047
+ ok: true,
2048
+ code: emitPhpProgram(request, config.paginate === true)
2049
+ };
2050
+ case "ruby": return {
2051
+ ok: true,
2052
+ code: emitRubyProgram(request, config.paginate === true)
2053
+ };
1752
2054
  case "ddbtoolbox": return {
1753
2055
  ok: true,
1754
2056
  code: emitDdbToolboxProgram(request, config.paginate === true)
@@ -1991,6 +2293,128 @@ function emitRustProgram(request, paginate) {
1991
2293
  "}"
1992
2294
  ].join("\n");
1993
2295
  }
2296
+ function emitDocClientProgram(request, paginate) {
2297
+ const command = docClientCommandName(request.operation);
2298
+ renderDocClientParams(request, "");
2299
+ if (!paginate) return [
2300
+ "import { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";",
2301
+ `import { DynamoDBDocumentClient, ${command} } from "@aws-sdk/lib-dynamodb";`,
2302
+ "",
2303
+ "const client = DynamoDBDocumentClient.from(new DynamoDBClient({}));",
2304
+ "",
2305
+ "const response = await client.send(",
2306
+ ` new ${command}(${renderDocClientParams(request, " ")})`,
2307
+ ");",
2308
+ "",
2309
+ "console.log(response.Items);"
2310
+ ].join("\n");
2311
+ const paginator = request.operation === "Scan" ? "paginateScan" : "paginateQuery";
2312
+ return [
2313
+ "import { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";",
2314
+ `import { DynamoDBDocumentClient, ${paginator} } from "@aws-sdk/lib-dynamodb";`,
2315
+ "",
2316
+ "const client = DynamoDBDocumentClient.from(new DynamoDBClient({}));",
2317
+ "",
2318
+ `const paginator = ${paginator}(`,
2319
+ " { client },",
2320
+ ` ${renderDocClientParams(request, " ")}`,
2321
+ ");",
2322
+ "",
2323
+ "for await (const page of paginator) {",
2324
+ " console.log(page.Items);",
2325
+ "}"
2326
+ ].join("\n");
2327
+ }
2328
+ function emitKotlinProgram(request, paginate) {
2329
+ const method = kotlinClientMethodName(request.operation);
2330
+ const header = [
2331
+ "import aws.sdk.kotlin.services.dynamodb.DynamoDbClient",
2332
+ "import aws.sdk.kotlin.services.dynamodb.model.AttributeValue",
2333
+ ...paginate ? [
2334
+ `import aws.sdk.kotlin.services.dynamodb.paginators.${method}Paginated`,
2335
+ "import aws.sdk.kotlin.services.dynamodb.paginators.items",
2336
+ "import kotlinx.coroutines.flow.collect"
2337
+ ] : [],
2338
+ "",
2339
+ "suspend fun main() {",
2340
+ " DynamoDbClient.fromEnvironment().use { client ->"
2341
+ ];
2342
+ const body = renderKotlinBuilder(request, " ");
2343
+ if (!paginate) return [
2344
+ ...header,
2345
+ ` val response = client.${method} {`,
2346
+ ...body,
2347
+ " }",
2348
+ " println(response.items)",
2349
+ " }",
2350
+ "}"
2351
+ ].join("\n");
2352
+ return [
2353
+ ...header,
2354
+ ` client.${method}Paginated {`,
2355
+ ...body,
2356
+ " }",
2357
+ " .items()",
2358
+ " .collect { item -> println(item) }",
2359
+ " }",
2360
+ "}"
2361
+ ].join("\n");
2362
+ }
2363
+ function emitPhpProgram(request, paginate) {
2364
+ const method = phpClientMethodName(request.operation);
2365
+ const header = [
2366
+ "<?php",
2367
+ "",
2368
+ "require 'vendor/autoload.php';",
2369
+ "",
2370
+ "use Aws\\DynamoDb\\DynamoDbClient;",
2371
+ "",
2372
+ "$client = new DynamoDbClient(['region' => 'us-east-1', 'version' => 'latest']);",
2373
+ ""
2374
+ ];
2375
+ if (!paginate) return [
2376
+ ...header,
2377
+ `$response = $client->${method}(${renderPhpParams(request, "")});`,
2378
+ "",
2379
+ "print_r($response['Items']);"
2380
+ ].join("\n");
2381
+ const operation = request.operation === "Scan" ? "Scan" : "Query";
2382
+ return [
2383
+ ...header,
2384
+ `$pages = $client->getPaginator('${operation}', ${renderPhpParams(request, "")});`,
2385
+ "",
2386
+ "foreach ($pages as $page) {",
2387
+ " print_r($page['Items']);",
2388
+ "}"
2389
+ ].join("\n");
2390
+ }
2391
+ function emitRubyProgram(request, paginate) {
2392
+ const method = rubyClientMethodName(request.operation);
2393
+ const usesSets = /Set\.new\(/.test(renderRubyParams(request, ""));
2394
+ const usesBase64 = /Base64\.decode64\(/.test(renderRubyParams(request, ""));
2395
+ const header = [
2396
+ "require 'aws-sdk-dynamodb'",
2397
+ ...usesSets ? ["require 'set'"] : [],
2398
+ ...usesBase64 ? ["require 'base64'"] : [],
2399
+ "",
2400
+ "client = Aws::DynamoDB::Client.new",
2401
+ ""
2402
+ ];
2403
+ if (!paginate) return [
2404
+ ...header,
2405
+ `response = client.${method}(${renderRubyParams(request, "")})`,
2406
+ "",
2407
+ "puts response.items"
2408
+ ].join("\n");
2409
+ return [
2410
+ ...header,
2411
+ `response = client.${method}(${renderRubyParams(request, "")})`,
2412
+ "",
2413
+ "response.each do |page|",
2414
+ " puts page.items",
2415
+ "end"
2416
+ ].join("\n");
2417
+ }
1994
2418
  function emitDotnetProgram(request, paginate) {
1995
2419
  const method = dotnetClientMethodName(request.operation);
1996
2420
  const binary = hasBinaryValue(request);
@@ -2036,4 +2460,4 @@ function emitDotnetProgram(request, paginate) {
2036
2460
  }
2037
2461
 
2038
2462
  //#endregion
2039
- export { FILTER_OPERATORS, KEY_OPERATORS, OPERATOR_BY_VALUE, SET_TYPES, boto3MethodName, buildFilterExpressions, buildKeyConditionExpression, buildKeyMap, buildRequest, buildSdkV3Params, buildUpdateExpression, dotnetClientMethodName, dotnetRequestClassName, elementType, emitBoto3, emitCli, emitDdbToolboxProgram, emitDotnet, emitGo, emitJava, emitPartiql, emitQueryProgram, emitRust, emitSdkV3, getCompatibleComparisonOperators, getCompatibleFilterOperators, goClientMethodName, goInputTypeName, hasBinaryValue, isSetType, javaClientMethodName, javaRequestClassName, makeTypedValue, renderJsValue, renderPyValue, renderRustAv, rustClientMethodName, sdkV3CommandName, typedMapToAvMap, typedValueToAv };
2463
+ export { FILTER_OPERATORS, KEY_OPERATORS, OPERATOR_BY_VALUE, SET_TYPES, boto3MethodName, buildFilterExpressions, buildKeyConditionExpression, buildKeyMap, buildRequest, buildSdkV3Params, buildUpdateExpression, docClientCommandName, dotnetClientMethodName, dotnetRequestClassName, elementType, emitBoto3, emitCli, emitDdbToolboxProgram, emitDocClient, emitDotnet, emitGo, emitJava, emitKotlin, emitPartiql, emitPhp, emitQueryProgram, emitRuby, emitRust, emitSdkV3, getCompatibleComparisonOperators, getCompatibleFilterOperators, goClientMethodName, goInputTypeName, hasBinaryValue, isSetType, javaClientMethodName, javaRequestClassName, kotlinClientMethodName, makeTypedValue, phpClientMethodName, renderJsValue, renderKotlinAv, renderPhpAv, renderPyValue, renderRubyValue, renderRustAv, rubyClientMethodName, rustClientMethodName, sdkV3CommandName, typedMapToAvMap, typedValueToAv };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dynamodb-expression-builder",
3
- "version": "0.2.0",
4
- "description": "DynamoDB expression builder and code generator: build update, condition, filter and key condition expressions with automatic ExpressionAttributeNames/Values aliasing, then emit runnable code for the JavaScript SDK v3, AWS CLI, boto3, Java, Go, .NET, Rust, PartiQL and dynamodb-toolbox. Zero dependencies.",
3
+ "version": "0.3.0",
4
+ "description": "DynamoDB expression builder and code generator: build update, condition, filter and key condition expressions with automatic ExpressionAttributeNames/Values aliasing, then emit runnable code for the JavaScript SDK v3 and DocumentClient, AWS CLI, boto3, Java, Go, .NET, Rust, Kotlin, PHP, Ruby, PartiQL and dynamodb-toolbox. Zero dependencies.",
5
5
  "keywords": [
6
6
  "dynamodb",
7
7
  "expression",
@@ -16,6 +16,10 @@
16
16
  "query-builder",
17
17
  "boto3",
18
18
  "rust",
19
+ "kotlin",
20
+ "php",
21
+ "ruby",
22
+ "documentclient",
19
23
  "aws"
20
24
  ],
21
25
  "license": "MIT",