dynamodb-expression-builder 0.1.1 → 0.2.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, 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, AWS CLI, boto3 (Python), Java, Go, .NET, Rust, 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` 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` 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).
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' | 'ddbtoolbox'`.
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).
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` · `emitPartiql` · `emitDdbToolboxProgram` · `emitQueryProgram` · `typedMapToAvMap` (tag-driven marshal) |
79
+ | Emitters | `emitSdkV3` · `emitCli` · `emitBoto3` · `emitJava` · `emitGo` · `emitDotnet` · `emitRust` · `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
@@ -154,6 +154,24 @@ const FILTER_OPERATORS = [
154
154
  "L"
155
155
  ]
156
156
  },
157
+ {
158
+ value: "not_contains",
159
+ label: "Not Contains",
160
+ symbol: "∌",
161
+ wireForm: "NOT_CONTAINS",
162
+ requiresValue: true,
163
+ requiresValue2: false,
164
+ typeOptional: false,
165
+ keyAllowedTypes: [],
166
+ scanAllowedTypes: [
167
+ "S",
168
+ "B",
169
+ "SS",
170
+ "NS",
171
+ "BS",
172
+ "L"
173
+ ]
174
+ },
157
175
  {
158
176
  value: "begins_with",
159
177
  label: "Begins With",
@@ -370,6 +388,9 @@ function buildOne(row, index, prefix, names, typedValues) {
370
388
  case "CONTAINS":
371
389
  typedValues[valueRef] = makeTypedValue(elementType(row.type), row.value);
372
390
  return `contains(${nameRef}, ${valueRef})`;
391
+ case "NOT_CONTAINS":
392
+ typedValues[valueRef] = makeTypedValue(elementType(row.type), row.value);
393
+ return `NOT contains(${nameRef}, ${valueRef})`;
373
394
  case "BEGINS_WITH":
374
395
  typedValues[valueRef] = single$1(row);
375
396
  return `begins_with(${nameRef}, ${valueRef})`;
@@ -967,6 +988,7 @@ function predicate(row) {
967
988
  case "BETWEEN": return `${id} BETWEEN ${literal(makeTypedValue(row.type, row.value))} AND ${literal(makeTypedValue(row.type, row.value2 ?? ""))}`;
968
989
  case "BEGINS_WITH": return `begins_with(${id}, ${literal(single(row))})`;
969
990
  case "CONTAINS": return `contains(${id}, ${literal(makeTypedValue(elementType(row.type), row.value))})`;
991
+ case "NOT_CONTAINS": return `NOT contains(${id}, ${literal(makeTypedValue(elementType(row.type), row.value))})`;
970
992
  case "IN": return `${id} IN (${(row.values ?? (row.value ? [row.value] : [])).map((m) => literal(makeTypedValue(row.type, m))).join(", ")})`;
971
993
  case "EXISTS": return `${id} IS NOT MISSING`;
972
994
  case "NOT_EXISTS": return `${id} IS MISSING`;
@@ -1095,7 +1117,7 @@ const REQUEST_CLASS_BY_OP$1 = {
1095
1117
  Delete: "DeleteItemRequest"
1096
1118
  };
1097
1119
  /** Operation → the `DynamoDbClient` method. */
1098
- const CLIENT_METHOD_BY_OP$2 = {
1120
+ const CLIENT_METHOD_BY_OP$3 = {
1099
1121
  GetItem: "getItem",
1100
1122
  Query: "query",
1101
1123
  Scan: "scan",
@@ -1109,7 +1131,7 @@ function javaRequestClassName(operation) {
1109
1131
  }
1110
1132
  /** The `DynamoDbClient` method name for an operation (`query`, …). */
1111
1133
  function javaClientMethodName(operation) {
1112
- return CLIENT_METHOD_BY_OP$2[operation];
1134
+ return CLIENT_METHOD_BY_OP$3[operation];
1113
1135
  }
1114
1136
  /** Java string literal — `JSON.stringify` escapes are all valid Java escapes. */
1115
1137
  function javaString(value) {
@@ -1188,7 +1210,7 @@ const INPUT_TYPE_BY_OP = {
1188
1210
  Delete: "DeleteItemInput"
1189
1211
  };
1190
1212
  /** Operation → the `dynamodb.Client` method. */
1191
- const CLIENT_METHOD_BY_OP$1 = {
1213
+ const CLIENT_METHOD_BY_OP$2 = {
1192
1214
  GetItem: "GetItem",
1193
1215
  Query: "Query",
1194
1216
  Scan: "Scan",
@@ -1202,7 +1224,7 @@ function goInputTypeName(operation) {
1202
1224
  }
1203
1225
  /** The `dynamodb.Client` method name for an operation. */
1204
1226
  function goClientMethodName(operation) {
1205
- return CLIENT_METHOD_BY_OP$1[operation];
1227
+ return CLIENT_METHOD_BY_OP$2[operation];
1206
1228
  }
1207
1229
  /** Go string literal — `JSON.stringify` escapes are all valid Go escapes. */
1208
1230
  function goString(value) {
@@ -1292,7 +1314,7 @@ const REQUEST_CLASS_BY_OP = {
1292
1314
  Delete: "DeleteItemRequest"
1293
1315
  };
1294
1316
  /** Operation → the async `AmazonDynamoDBClient` method. */
1295
- const CLIENT_METHOD_BY_OP = {
1317
+ const CLIENT_METHOD_BY_OP$1 = {
1296
1318
  GetItem: "GetItemAsync",
1297
1319
  Query: "QueryAsync",
1298
1320
  Scan: "ScanAsync",
@@ -1306,7 +1328,7 @@ function dotnetRequestClassName(operation) {
1306
1328
  }
1307
1329
  /** The async client method name for an operation (`QueryAsync`, …). */
1308
1330
  function dotnetClientMethodName(operation) {
1309
- return CLIENT_METHOD_BY_OP[operation];
1331
+ return CLIENT_METHOD_BY_OP$1[operation];
1310
1332
  }
1311
1333
  /** C# string literal — `JSON.stringify` escapes are all valid C# escapes. */
1312
1334
  function csString(value) {
@@ -1373,6 +1395,102 @@ function emitDotnet(request) {
1373
1395
  return renderCsRequest(request, "");
1374
1396
  }
1375
1397
 
1398
+ //#endregion
1399
+ //#region src/emit/rust.ts
1400
+ /** Operation → the `aws_sdk_dynamodb::Client` fluent method. */
1401
+ const CLIENT_METHOD_BY_OP = {
1402
+ GetItem: "get_item",
1403
+ Query: "query",
1404
+ Scan: "scan",
1405
+ Update: "update_item",
1406
+ Put: "put_item",
1407
+ Delete: "delete_item"
1408
+ };
1409
+ /** The `Client` method name (snake_case) for an operation. */
1410
+ function rustClientMethodName(operation) {
1411
+ return CLIENT_METHOD_BY_OP[operation];
1412
+ }
1413
+ /** Rust string literal — JSON escapes with the three Rust-incompatible ones fixed. */
1414
+ function rustString(value) {
1415
+ return JSON.stringify(value).replace(/\\b/g, "\\u{0008}").replace(/\\f/g, "\\u{000c}").replace(/\\u([0-9a-fA-F]{4})/g, "\\u{$1}");
1416
+ }
1417
+ /** `"…".to_string()` — the owned String the AttributeValue constructors take. */
1418
+ function rustOwned(value) {
1419
+ return `${rustString(value)}.to_string()`;
1420
+ }
1421
+ /** Decode canonical base64 into a `Blob::new(vec![0x…])` (fail-loud on bad input). */
1422
+ function rustBlob(base64) {
1423
+ let raw;
1424
+ try {
1425
+ raw = atob(base64);
1426
+ } catch {
1427
+ throw new Error(`invalid base64 in a binary (B/BS) value: ${base64}`);
1428
+ }
1429
+ return `Blob::new(vec![${Array.from(raw, (c) => `0x${c.charCodeAt(0).toString(16).padStart(2, "0")}`).join(", ")}])`;
1430
+ }
1431
+ /** Render one wire AttributeValue as an `AttributeValue::…` constructor. */
1432
+ function renderRustAv(av) {
1433
+ if ("S" in av) return `AttributeValue::S(${rustOwned(av.S)})`;
1434
+ if ("N" in av) return `AttributeValue::N(${rustOwned(av.N)})`;
1435
+ if ("B" in av) return `AttributeValue::B(${rustBlob(av.B)})`;
1436
+ if ("BOOL" in av) return `AttributeValue::Bool(${av.BOOL})`;
1437
+ if ("SS" in av) return `AttributeValue::Ss(vec![${av.SS.map(rustOwned).join(", ")}])`;
1438
+ if ("NS" in av) return `AttributeValue::Ns(vec![${av.NS.map(rustOwned).join(", ")}])`;
1439
+ if ("BS" in av) return `AttributeValue::Bs(vec![${av.BS.map(rustBlob).join(", ")}])`;
1440
+ return "AttributeValue::Null(true)";
1441
+ }
1442
+ /** One `.method(key, av)` line per entry of a typed map. */
1443
+ function avEntryLines(method, map, indent) {
1444
+ return Object.entries(typedMapToAvMap(map)).map(([name, av]) => `${indent}.${method}(${rustString(name)}, ${renderRustAv(av)})`);
1445
+ }
1446
+ /**
1447
+ * Render the fluent builder chain for a request — every line at `indent`,
1448
+ * starting with `.{op}()` and ending BEFORE `.send()` so callers own the
1449
+ * terminal (the bare emitter awaits inline; the program emitter may hand the
1450
+ * chain to `into_paginator()` instead). Exported for the program emitter.
1451
+ */
1452
+ function renderRustBuilder(request, indent) {
1453
+ const lines = [`${indent}.${CLIENT_METHOD_BY_OP[request.operation]}()`];
1454
+ lines.push(`${indent}.table_name(${rustString(request.tableName)})`);
1455
+ if (request.indexName) lines.push(`${indent}.index_name(${rustString(request.indexName)})`);
1456
+ if (request.key) lines.push(...avEntryLines("key", request.key, indent));
1457
+ if (request.item) lines.push(...avEntryLines("item", request.item, indent));
1458
+ if (request.keyConditionExpression) lines.push(`${indent}.key_condition_expression(${rustString(request.keyConditionExpression)})`);
1459
+ if (request.updateExpression) lines.push(`${indent}.update_expression(${rustString(request.updateExpression)})`);
1460
+ if (request.conditionExpression) lines.push(`${indent}.condition_expression(${rustString(request.conditionExpression)})`);
1461
+ if (request.filterExpression) lines.push(`${indent}.filter_expression(${rustString(request.filterExpression)})`);
1462
+ if (request.projectionExpression) lines.push(`${indent}.projection_expression(${rustString(request.projectionExpression)})`);
1463
+ if (request.names) lines.push(...Object.entries(request.names).map(([alias, name]) => `${indent}.expression_attribute_names(${rustString(alias)}, ${rustString(name)})`));
1464
+ if (request.typedValues) lines.push(...avEntryLines("expression_attribute_values", request.typedValues, indent));
1465
+ if (request.limit !== void 0) lines.push(`${indent}.limit(${request.limit})`);
1466
+ if (request.consistentRead) lines.push(`${indent}.consistent_read(true)`);
1467
+ if (request.scanIndexForward === false) lines.push(`${indent}.scan_index_forward(false)`);
1468
+ if (request.exclusiveStartKey) lines.push(...avEntryLines("exclusive_start_key", request.exclusiveStartKey, indent));
1469
+ return lines;
1470
+ }
1471
+ /** Does this request build any `AttributeValue`? Drives the program emitter's imports. */
1472
+ function rustUsesAttributeValue(request) {
1473
+ return request.key !== void 0 || request.item !== void 0 || request.typedValues !== void 0 || request.exclusiveStartKey !== void 0;
1474
+ }
1475
+ /** Does this request build any binary Blob? Drives the `primitives::Blob` import. */
1476
+ function rustUsesBlob(request) {
1477
+ return [
1478
+ request.key,
1479
+ request.item,
1480
+ request.typedValues,
1481
+ request.exclusiveStartKey
1482
+ ].some((map) => map && Object.values(typedMapToAvMap(map)).some((av) => "B" in av || "BS" in av));
1483
+ }
1484
+ /** Emit the bare fluent call for a canonical request (awaited, `?`-propagated). */
1485
+ function emitRust(request) {
1486
+ return [
1487
+ "let response = client",
1488
+ ...renderRustBuilder(request, " "),
1489
+ " .send()",
1490
+ " .await?;"
1491
+ ].join("\n");
1492
+ }
1493
+
1376
1494
  //#endregion
1377
1495
  //#region src/emit/ddbtoolbox.ts
1378
1496
  /** Key attr DynamoDB type (S/N/B) → the Table `type` string dynamodb-toolbox wants. */
@@ -1438,6 +1556,7 @@ function renderCondition(f) {
1438
1556
  if (f.operator === "exists") return `{ attr: ${attr}, exists: true }`;
1439
1557
  if (f.operator === "not_exists") return `{ attr: ${attr}, exists: false }`;
1440
1558
  if (f.operator === "contains") return `{ attr: ${attr}, contains: ${val()} }`;
1559
+ if (f.operator === "not_contains") return `{ not: { attr: ${attr}, contains: ${val()} } }`;
1441
1560
  if (f.operator === "begins_with") return `{ attr: ${attr}, beginsWith: ${val()} }`;
1442
1561
  if (f.operator === "between") return `{ attr: ${attr}, between: [${renderNativeValue(scalar(f.type, f.value))}, ${renderNativeValue(scalar(f.type, f.value2 ?? ""))}] }`;
1443
1562
  if (f.operator === "in") return `{ attr: ${attr}, in: [${(f.values ?? []).map((v) => renderNativeValue(scalar(f.type, v))).join(", ")}] }`;
@@ -1627,6 +1746,10 @@ function emitQueryProgram(config, format) {
1627
1746
  ok: true,
1628
1747
  code: emitDotnetProgram(request, config.paginate === true)
1629
1748
  };
1749
+ case "rust": return {
1750
+ ok: true,
1751
+ code: emitRustProgram(request, config.paginate === true)
1752
+ };
1630
1753
  case "ddbtoolbox": return {
1631
1754
  ok: true,
1632
1755
  code: emitDdbToolboxProgram(request, config.paginate === true)
@@ -1824,6 +1947,51 @@ function emitGoProgram(request, paginate) {
1824
1947
  "}"
1825
1948
  ].join("\n");
1826
1949
  }
1950
+ function emitRustProgram(request, paginate) {
1951
+ const usesAv = rustUsesAttributeValue(request);
1952
+ const usesBlob = rustUsesBlob(request);
1953
+ const header = [
1954
+ ...[
1955
+ "// Cargo.toml: aws-config, aws-sdk-dynamodb, tokio (features = [\"full\"])",
1956
+ "use aws_sdk_dynamodb::Client;",
1957
+ ...usesAv ? ["use aws_sdk_dynamodb::types::AttributeValue;"] : [],
1958
+ ...usesBlob ? ["use aws_sdk_dynamodb::primitives::Blob;"] : []
1959
+ ],
1960
+ "",
1961
+ "#[tokio::main]",
1962
+ "async fn main() -> Result<(), aws_sdk_dynamodb::Error> {",
1963
+ " let config = aws_config::load_from_env().await;",
1964
+ " let client = Client::new(&config);",
1965
+ ""
1966
+ ];
1967
+ const builder = renderRustBuilder(request, " ");
1968
+ if (!paginate) return [
1969
+ ...header,
1970
+ " let response = client",
1971
+ ...builder,
1972
+ " .send()",
1973
+ " .await?;",
1974
+ " println!(\"{:?}\", response.items());",
1975
+ "",
1976
+ " Ok(())",
1977
+ "}"
1978
+ ].join("\n");
1979
+ return [
1980
+ ...header,
1981
+ " let mut items = client",
1982
+ ...builder,
1983
+ " .into_paginator()",
1984
+ " .items()",
1985
+ " .send();",
1986
+ "",
1987
+ " while let Some(item) = items.next().await {",
1988
+ " println!(\"{:?}\", item?);",
1989
+ " }",
1990
+ "",
1991
+ " Ok(())",
1992
+ "}"
1993
+ ].join("\n");
1994
+ }
1827
1995
  function emitDotnetProgram(request, paginate) {
1828
1996
  const method = dotnetClientMethodName(request.operation);
1829
1997
  const binary = hasBinaryValue(request);
@@ -1891,6 +2059,7 @@ exports.emitGo = emitGo;
1891
2059
  exports.emitJava = emitJava;
1892
2060
  exports.emitPartiql = emitPartiql;
1893
2061
  exports.emitQueryProgram = emitQueryProgram;
2062
+ exports.emitRust = emitRust;
1894
2063
  exports.emitSdkV3 = emitSdkV3;
1895
2064
  exports.getCompatibleComparisonOperators = getCompatibleComparisonOperators;
1896
2065
  exports.getCompatibleFilterOperators = getCompatibleFilterOperators;
@@ -1903,6 +2072,8 @@ exports.javaRequestClassName = javaRequestClassName;
1903
2072
  exports.makeTypedValue = makeTypedValue;
1904
2073
  exports.renderJsValue = renderJsValue;
1905
2074
  exports.renderPyValue = renderPyValue;
2075
+ exports.renderRustAv = renderRustAv;
2076
+ exports.rustClientMethodName = rustClientMethodName;
1906
2077
  exports.sdkV3CommandName = sdkV3CommandName;
1907
2078
  exports.typedMapToAvMap = typedMapToAvMap;
1908
2079
  exports.typedValueToAv = typedValueToAv;
package/dist/index.d.cts CHANGED
@@ -3,7 +3,7 @@
3
3
  * Wire-format (uppercase) operator union. INLINED copy of the app's
4
4
  * `WireFilterOperator` (`src/schemas/dynamodb-schemas.ts`).
5
5
  */
6
- type WireFilterOperator = 'EQ' | 'NE' | 'GT' | 'GE' | 'LT' | 'LE' | 'CONTAINS' | 'BEGINS_WITH' | 'BETWEEN' | 'IN' | 'EXISTS' | 'NOT_EXISTS' | 'SIZE_EQ' | 'SIZE_NE' | 'SIZE_LT' | 'SIZE_LE' | 'SIZE_GT' | 'SIZE_GE' | 'TYPE_EQ' | 'TYPE_NE';
6
+ type WireFilterOperator = 'EQ' | 'NE' | 'GT' | 'GE' | 'LT' | 'LE' | 'CONTAINS' | 'NOT_CONTAINS' | 'BEGINS_WITH' | 'BETWEEN' | 'IN' | 'EXISTS' | 'NOT_EXISTS' | 'SIZE_EQ' | 'SIZE_NE' | 'SIZE_LT' | 'SIZE_LE' | 'SIZE_GT' | 'SIZE_GE' | 'TYPE_EQ' | 'TYPE_NE';
7
7
  /**
8
8
  * Data types the compat map reasons about. `L`/`M` are included even though the
9
9
  * tool's Type selector doesn't expose them — they keep the compat table a
@@ -101,6 +101,16 @@ declare const FILTER_OPERATORS: readonly [{
101
101
  readonly typeOptional: false;
102
102
  readonly keyAllowedTypes: readonly [];
103
103
  readonly scanAllowedTypes: readonly ["S", "B", "SS", "NS", "BS", "L"];
104
+ }, {
105
+ readonly value: "not_contains";
106
+ readonly label: "Not Contains";
107
+ readonly symbol: "∌";
108
+ readonly wireForm: "NOT_CONTAINS";
109
+ readonly requiresValue: true;
110
+ readonly requiresValue2: false;
111
+ readonly typeOptional: false;
112
+ readonly keyAllowedTypes: readonly [];
113
+ readonly scanAllowedTypes: readonly ["S", "B", "SS", "NS", "BS", "L"];
104
114
  }, {
105
115
  readonly value: "begins_with";
106
116
  readonly label: "Begins With";
@@ -600,6 +610,14 @@ declare function dotnetClientMethodName(operation: DdbOperation): string;
600
610
  /** Emit the bare `new <Op>Request { … }` snippet for a canonical request. */
601
611
  declare function emitDotnet(request: CanonicalRequest): string;
602
612
  //#endregion
613
+ //#region src/emit/rust.d.ts
614
+ /** The `Client` method name (snake_case) for an operation. */
615
+ declare function rustClientMethodName(operation: DdbOperation): string;
616
+ /** Render one wire AttributeValue as an `AttributeValue::…` constructor. */
617
+ declare function renderRustAv(av: AttributeValue): string;
618
+ /** Emit the bare fluent call for a canonical request (awaited, `?`-propagated). */
619
+ declare function emitRust(request: CanonicalRequest): string;
620
+ //#endregion
603
621
  //#region src/emit/ddbtoolbox.d.ts
604
622
  /**
605
623
  * Emit the runnable dynamodb-toolbox program for a Query/Scan canonical request.
@@ -618,7 +636,7 @@ interface QueryToolConfig extends BuilderConfig {
618
636
  /** Emit the fetch-all-pages pagination loop (default: single request). */
619
637
  paginate?: boolean;
620
638
  }
621
- type QueryProgramFormat = 'sdk' | 'cli' | 'boto3' | 'partiql' | 'java' | 'go' | 'dotnet' | 'ddbtoolbox';
639
+ type QueryProgramFormat = 'sdk' | 'cli' | 'boto3' | 'partiql' | 'java' | 'go' | 'dotnet' | 'rust' | 'ddbtoolbox';
622
640
  /** A runnable program, or an honest reason the format can't express it. */
623
641
  type ProgramResult = {
624
642
  ok: true;
@@ -636,4 +654,4 @@ type ProgramResult = {
636
654
  */
637
655
  declare function emitQueryProgram(config: QueryToolConfig, format: QueryProgramFormat): ProgramResult;
638
656
  //#endregion
639
- 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, emitSdkV3, getCompatibleComparisonOperators, getCompatibleFilterOperators, goClientMethodName, goInputTypeName, hasBinaryValue, isSetType, javaClientMethodName, javaRequestClassName, makeTypedValue, renderJsValue, renderPyValue, sdkV3CommandName, typedMapToAvMap, typedValueToAv };
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 };
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * Wire-format (uppercase) operator union. INLINED copy of the app's
4
4
  * `WireFilterOperator` (`src/schemas/dynamodb-schemas.ts`).
5
5
  */
6
- type WireFilterOperator = 'EQ' | 'NE' | 'GT' | 'GE' | 'LT' | 'LE' | 'CONTAINS' | 'BEGINS_WITH' | 'BETWEEN' | 'IN' | 'EXISTS' | 'NOT_EXISTS' | 'SIZE_EQ' | 'SIZE_NE' | 'SIZE_LT' | 'SIZE_LE' | 'SIZE_GT' | 'SIZE_GE' | 'TYPE_EQ' | 'TYPE_NE';
6
+ type WireFilterOperator = 'EQ' | 'NE' | 'GT' | 'GE' | 'LT' | 'LE' | 'CONTAINS' | 'NOT_CONTAINS' | 'BEGINS_WITH' | 'BETWEEN' | 'IN' | 'EXISTS' | 'NOT_EXISTS' | 'SIZE_EQ' | 'SIZE_NE' | 'SIZE_LT' | 'SIZE_LE' | 'SIZE_GT' | 'SIZE_GE' | 'TYPE_EQ' | 'TYPE_NE';
7
7
  /**
8
8
  * Data types the compat map reasons about. `L`/`M` are included even though the
9
9
  * tool's Type selector doesn't expose them — they keep the compat table a
@@ -101,6 +101,16 @@ declare const FILTER_OPERATORS: readonly [{
101
101
  readonly typeOptional: false;
102
102
  readonly keyAllowedTypes: readonly [];
103
103
  readonly scanAllowedTypes: readonly ["S", "B", "SS", "NS", "BS", "L"];
104
+ }, {
105
+ readonly value: "not_contains";
106
+ readonly label: "Not Contains";
107
+ readonly symbol: "∌";
108
+ readonly wireForm: "NOT_CONTAINS";
109
+ readonly requiresValue: true;
110
+ readonly requiresValue2: false;
111
+ readonly typeOptional: false;
112
+ readonly keyAllowedTypes: readonly [];
113
+ readonly scanAllowedTypes: readonly ["S", "B", "SS", "NS", "BS", "L"];
104
114
  }, {
105
115
  readonly value: "begins_with";
106
116
  readonly label: "Begins With";
@@ -600,6 +610,14 @@ declare function dotnetClientMethodName(operation: DdbOperation): string;
600
610
  /** Emit the bare `new <Op>Request { … }` snippet for a canonical request. */
601
611
  declare function emitDotnet(request: CanonicalRequest): string;
602
612
  //#endregion
613
+ //#region src/emit/rust.d.ts
614
+ /** The `Client` method name (snake_case) for an operation. */
615
+ declare function rustClientMethodName(operation: DdbOperation): string;
616
+ /** Render one wire AttributeValue as an `AttributeValue::…` constructor. */
617
+ declare function renderRustAv(av: AttributeValue): string;
618
+ /** Emit the bare fluent call for a canonical request (awaited, `?`-propagated). */
619
+ declare function emitRust(request: CanonicalRequest): string;
620
+ //#endregion
603
621
  //#region src/emit/ddbtoolbox.d.ts
604
622
  /**
605
623
  * Emit the runnable dynamodb-toolbox program for a Query/Scan canonical request.
@@ -618,7 +636,7 @@ interface QueryToolConfig extends BuilderConfig {
618
636
  /** Emit the fetch-all-pages pagination loop (default: single request). */
619
637
  paginate?: boolean;
620
638
  }
621
- type QueryProgramFormat = 'sdk' | 'cli' | 'boto3' | 'partiql' | 'java' | 'go' | 'dotnet' | 'ddbtoolbox';
639
+ type QueryProgramFormat = 'sdk' | 'cli' | 'boto3' | 'partiql' | 'java' | 'go' | 'dotnet' | 'rust' | 'ddbtoolbox';
622
640
  /** A runnable program, or an honest reason the format can't express it. */
623
641
  type ProgramResult = {
624
642
  ok: true;
@@ -636,4 +654,4 @@ type ProgramResult = {
636
654
  */
637
655
  declare function emitQueryProgram(config: QueryToolConfig, format: QueryProgramFormat): ProgramResult;
638
656
  //#endregion
639
- 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, emitSdkV3, getCompatibleComparisonOperators, getCompatibleFilterOperators, goClientMethodName, goInputTypeName, hasBinaryValue, isSetType, javaClientMethodName, javaRequestClassName, makeTypedValue, renderJsValue, renderPyValue, sdkV3CommandName, typedMapToAvMap, typedValueToAv };
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 };
package/dist/index.js CHANGED
@@ -153,6 +153,24 @@ const FILTER_OPERATORS = [
153
153
  "L"
154
154
  ]
155
155
  },
156
+ {
157
+ value: "not_contains",
158
+ label: "Not Contains",
159
+ symbol: "∌",
160
+ wireForm: "NOT_CONTAINS",
161
+ requiresValue: true,
162
+ requiresValue2: false,
163
+ typeOptional: false,
164
+ keyAllowedTypes: [],
165
+ scanAllowedTypes: [
166
+ "S",
167
+ "B",
168
+ "SS",
169
+ "NS",
170
+ "BS",
171
+ "L"
172
+ ]
173
+ },
156
174
  {
157
175
  value: "begins_with",
158
176
  label: "Begins With",
@@ -369,6 +387,9 @@ function buildOne(row, index, prefix, names, typedValues) {
369
387
  case "CONTAINS":
370
388
  typedValues[valueRef] = makeTypedValue(elementType(row.type), row.value);
371
389
  return `contains(${nameRef}, ${valueRef})`;
390
+ case "NOT_CONTAINS":
391
+ typedValues[valueRef] = makeTypedValue(elementType(row.type), row.value);
392
+ return `NOT contains(${nameRef}, ${valueRef})`;
372
393
  case "BEGINS_WITH":
373
394
  typedValues[valueRef] = single$1(row);
374
395
  return `begins_with(${nameRef}, ${valueRef})`;
@@ -966,6 +987,7 @@ function predicate(row) {
966
987
  case "BETWEEN": return `${id} BETWEEN ${literal(makeTypedValue(row.type, row.value))} AND ${literal(makeTypedValue(row.type, row.value2 ?? ""))}`;
967
988
  case "BEGINS_WITH": return `begins_with(${id}, ${literal(single(row))})`;
968
989
  case "CONTAINS": return `contains(${id}, ${literal(makeTypedValue(elementType(row.type), row.value))})`;
990
+ case "NOT_CONTAINS": return `NOT contains(${id}, ${literal(makeTypedValue(elementType(row.type), row.value))})`;
969
991
  case "IN": return `${id} IN (${(row.values ?? (row.value ? [row.value] : [])).map((m) => literal(makeTypedValue(row.type, m))).join(", ")})`;
970
992
  case "EXISTS": return `${id} IS NOT MISSING`;
971
993
  case "NOT_EXISTS": return `${id} IS MISSING`;
@@ -1094,7 +1116,7 @@ const REQUEST_CLASS_BY_OP$1 = {
1094
1116
  Delete: "DeleteItemRequest"
1095
1117
  };
1096
1118
  /** Operation → the `DynamoDbClient` method. */
1097
- const CLIENT_METHOD_BY_OP$2 = {
1119
+ const CLIENT_METHOD_BY_OP$3 = {
1098
1120
  GetItem: "getItem",
1099
1121
  Query: "query",
1100
1122
  Scan: "scan",
@@ -1108,7 +1130,7 @@ function javaRequestClassName(operation) {
1108
1130
  }
1109
1131
  /** The `DynamoDbClient` method name for an operation (`query`, …). */
1110
1132
  function javaClientMethodName(operation) {
1111
- return CLIENT_METHOD_BY_OP$2[operation];
1133
+ return CLIENT_METHOD_BY_OP$3[operation];
1112
1134
  }
1113
1135
  /** Java string literal — `JSON.stringify` escapes are all valid Java escapes. */
1114
1136
  function javaString(value) {
@@ -1187,7 +1209,7 @@ const INPUT_TYPE_BY_OP = {
1187
1209
  Delete: "DeleteItemInput"
1188
1210
  };
1189
1211
  /** Operation → the `dynamodb.Client` method. */
1190
- const CLIENT_METHOD_BY_OP$1 = {
1212
+ const CLIENT_METHOD_BY_OP$2 = {
1191
1213
  GetItem: "GetItem",
1192
1214
  Query: "Query",
1193
1215
  Scan: "Scan",
@@ -1201,7 +1223,7 @@ function goInputTypeName(operation) {
1201
1223
  }
1202
1224
  /** The `dynamodb.Client` method name for an operation. */
1203
1225
  function goClientMethodName(operation) {
1204
- return CLIENT_METHOD_BY_OP$1[operation];
1226
+ return CLIENT_METHOD_BY_OP$2[operation];
1205
1227
  }
1206
1228
  /** Go string literal — `JSON.stringify` escapes are all valid Go escapes. */
1207
1229
  function goString(value) {
@@ -1291,7 +1313,7 @@ const REQUEST_CLASS_BY_OP = {
1291
1313
  Delete: "DeleteItemRequest"
1292
1314
  };
1293
1315
  /** Operation → the async `AmazonDynamoDBClient` method. */
1294
- const CLIENT_METHOD_BY_OP = {
1316
+ const CLIENT_METHOD_BY_OP$1 = {
1295
1317
  GetItem: "GetItemAsync",
1296
1318
  Query: "QueryAsync",
1297
1319
  Scan: "ScanAsync",
@@ -1305,7 +1327,7 @@ function dotnetRequestClassName(operation) {
1305
1327
  }
1306
1328
  /** The async client method name for an operation (`QueryAsync`, …). */
1307
1329
  function dotnetClientMethodName(operation) {
1308
- return CLIENT_METHOD_BY_OP[operation];
1330
+ return CLIENT_METHOD_BY_OP$1[operation];
1309
1331
  }
1310
1332
  /** C# string literal — `JSON.stringify` escapes are all valid C# escapes. */
1311
1333
  function csString(value) {
@@ -1372,6 +1394,102 @@ function emitDotnet(request) {
1372
1394
  return renderCsRequest(request, "");
1373
1395
  }
1374
1396
 
1397
+ //#endregion
1398
+ //#region src/emit/rust.ts
1399
+ /** Operation → the `aws_sdk_dynamodb::Client` fluent method. */
1400
+ const CLIENT_METHOD_BY_OP = {
1401
+ GetItem: "get_item",
1402
+ Query: "query",
1403
+ Scan: "scan",
1404
+ Update: "update_item",
1405
+ Put: "put_item",
1406
+ Delete: "delete_item"
1407
+ };
1408
+ /** The `Client` method name (snake_case) for an operation. */
1409
+ function rustClientMethodName(operation) {
1410
+ return CLIENT_METHOD_BY_OP[operation];
1411
+ }
1412
+ /** Rust string literal — JSON escapes with the three Rust-incompatible ones fixed. */
1413
+ function rustString(value) {
1414
+ return JSON.stringify(value).replace(/\\b/g, "\\u{0008}").replace(/\\f/g, "\\u{000c}").replace(/\\u([0-9a-fA-F]{4})/g, "\\u{$1}");
1415
+ }
1416
+ /** `"…".to_string()` — the owned String the AttributeValue constructors take. */
1417
+ function rustOwned(value) {
1418
+ return `${rustString(value)}.to_string()`;
1419
+ }
1420
+ /** Decode canonical base64 into a `Blob::new(vec![0x…])` (fail-loud on bad input). */
1421
+ function rustBlob(base64) {
1422
+ let raw;
1423
+ try {
1424
+ raw = atob(base64);
1425
+ } catch {
1426
+ throw new Error(`invalid base64 in a binary (B/BS) value: ${base64}`);
1427
+ }
1428
+ return `Blob::new(vec![${Array.from(raw, (c) => `0x${c.charCodeAt(0).toString(16).padStart(2, "0")}`).join(", ")}])`;
1429
+ }
1430
+ /** Render one wire AttributeValue as an `AttributeValue::…` constructor. */
1431
+ function renderRustAv(av) {
1432
+ if ("S" in av) return `AttributeValue::S(${rustOwned(av.S)})`;
1433
+ if ("N" in av) return `AttributeValue::N(${rustOwned(av.N)})`;
1434
+ if ("B" in av) return `AttributeValue::B(${rustBlob(av.B)})`;
1435
+ if ("BOOL" in av) return `AttributeValue::Bool(${av.BOOL})`;
1436
+ if ("SS" in av) return `AttributeValue::Ss(vec![${av.SS.map(rustOwned).join(", ")}])`;
1437
+ if ("NS" in av) return `AttributeValue::Ns(vec![${av.NS.map(rustOwned).join(", ")}])`;
1438
+ if ("BS" in av) return `AttributeValue::Bs(vec![${av.BS.map(rustBlob).join(", ")}])`;
1439
+ return "AttributeValue::Null(true)";
1440
+ }
1441
+ /** One `.method(key, av)` line per entry of a typed map. */
1442
+ function avEntryLines(method, map, indent) {
1443
+ return Object.entries(typedMapToAvMap(map)).map(([name, av]) => `${indent}.${method}(${rustString(name)}, ${renderRustAv(av)})`);
1444
+ }
1445
+ /**
1446
+ * Render the fluent builder chain for a request — every line at `indent`,
1447
+ * starting with `.{op}()` and ending BEFORE `.send()` so callers own the
1448
+ * terminal (the bare emitter awaits inline; the program emitter may hand the
1449
+ * chain to `into_paginator()` instead). Exported for the program emitter.
1450
+ */
1451
+ function renderRustBuilder(request, indent) {
1452
+ const lines = [`${indent}.${CLIENT_METHOD_BY_OP[request.operation]}()`];
1453
+ lines.push(`${indent}.table_name(${rustString(request.tableName)})`);
1454
+ if (request.indexName) lines.push(`${indent}.index_name(${rustString(request.indexName)})`);
1455
+ if (request.key) lines.push(...avEntryLines("key", request.key, indent));
1456
+ if (request.item) lines.push(...avEntryLines("item", request.item, indent));
1457
+ if (request.keyConditionExpression) lines.push(`${indent}.key_condition_expression(${rustString(request.keyConditionExpression)})`);
1458
+ if (request.updateExpression) lines.push(`${indent}.update_expression(${rustString(request.updateExpression)})`);
1459
+ if (request.conditionExpression) lines.push(`${indent}.condition_expression(${rustString(request.conditionExpression)})`);
1460
+ if (request.filterExpression) lines.push(`${indent}.filter_expression(${rustString(request.filterExpression)})`);
1461
+ if (request.projectionExpression) lines.push(`${indent}.projection_expression(${rustString(request.projectionExpression)})`);
1462
+ if (request.names) lines.push(...Object.entries(request.names).map(([alias, name]) => `${indent}.expression_attribute_names(${rustString(alias)}, ${rustString(name)})`));
1463
+ if (request.typedValues) lines.push(...avEntryLines("expression_attribute_values", request.typedValues, indent));
1464
+ if (request.limit !== void 0) lines.push(`${indent}.limit(${request.limit})`);
1465
+ if (request.consistentRead) lines.push(`${indent}.consistent_read(true)`);
1466
+ if (request.scanIndexForward === false) lines.push(`${indent}.scan_index_forward(false)`);
1467
+ if (request.exclusiveStartKey) lines.push(...avEntryLines("exclusive_start_key", request.exclusiveStartKey, indent));
1468
+ return lines;
1469
+ }
1470
+ /** Does this request build any `AttributeValue`? Drives the program emitter's imports. */
1471
+ function rustUsesAttributeValue(request) {
1472
+ return request.key !== void 0 || request.item !== void 0 || request.typedValues !== void 0 || request.exclusiveStartKey !== void 0;
1473
+ }
1474
+ /** Does this request build any binary Blob? Drives the `primitives::Blob` import. */
1475
+ function rustUsesBlob(request) {
1476
+ return [
1477
+ request.key,
1478
+ request.item,
1479
+ request.typedValues,
1480
+ request.exclusiveStartKey
1481
+ ].some((map) => map && Object.values(typedMapToAvMap(map)).some((av) => "B" in av || "BS" in av));
1482
+ }
1483
+ /** Emit the bare fluent call for a canonical request (awaited, `?`-propagated). */
1484
+ function emitRust(request) {
1485
+ return [
1486
+ "let response = client",
1487
+ ...renderRustBuilder(request, " "),
1488
+ " .send()",
1489
+ " .await?;"
1490
+ ].join("\n");
1491
+ }
1492
+
1375
1493
  //#endregion
1376
1494
  //#region src/emit/ddbtoolbox.ts
1377
1495
  /** Key attr DynamoDB type (S/N/B) → the Table `type` string dynamodb-toolbox wants. */
@@ -1437,6 +1555,7 @@ function renderCondition(f) {
1437
1555
  if (f.operator === "exists") return `{ attr: ${attr}, exists: true }`;
1438
1556
  if (f.operator === "not_exists") return `{ attr: ${attr}, exists: false }`;
1439
1557
  if (f.operator === "contains") return `{ attr: ${attr}, contains: ${val()} }`;
1558
+ if (f.operator === "not_contains") return `{ not: { attr: ${attr}, contains: ${val()} } }`;
1440
1559
  if (f.operator === "begins_with") return `{ attr: ${attr}, beginsWith: ${val()} }`;
1441
1560
  if (f.operator === "between") return `{ attr: ${attr}, between: [${renderNativeValue(scalar(f.type, f.value))}, ${renderNativeValue(scalar(f.type, f.value2 ?? ""))}] }`;
1442
1561
  if (f.operator === "in") return `{ attr: ${attr}, in: [${(f.values ?? []).map((v) => renderNativeValue(scalar(f.type, v))).join(", ")}] }`;
@@ -1626,6 +1745,10 @@ function emitQueryProgram(config, format) {
1626
1745
  ok: true,
1627
1746
  code: emitDotnetProgram(request, config.paginate === true)
1628
1747
  };
1748
+ case "rust": return {
1749
+ ok: true,
1750
+ code: emitRustProgram(request, config.paginate === true)
1751
+ };
1629
1752
  case "ddbtoolbox": return {
1630
1753
  ok: true,
1631
1754
  code: emitDdbToolboxProgram(request, config.paginate === true)
@@ -1823,6 +1946,51 @@ function emitGoProgram(request, paginate) {
1823
1946
  "}"
1824
1947
  ].join("\n");
1825
1948
  }
1949
+ function emitRustProgram(request, paginate) {
1950
+ const usesAv = rustUsesAttributeValue(request);
1951
+ const usesBlob = rustUsesBlob(request);
1952
+ const header = [
1953
+ ...[
1954
+ "// Cargo.toml: aws-config, aws-sdk-dynamodb, tokio (features = [\"full\"])",
1955
+ "use aws_sdk_dynamodb::Client;",
1956
+ ...usesAv ? ["use aws_sdk_dynamodb::types::AttributeValue;"] : [],
1957
+ ...usesBlob ? ["use aws_sdk_dynamodb::primitives::Blob;"] : []
1958
+ ],
1959
+ "",
1960
+ "#[tokio::main]",
1961
+ "async fn main() -> Result<(), aws_sdk_dynamodb::Error> {",
1962
+ " let config = aws_config::load_from_env().await;",
1963
+ " let client = Client::new(&config);",
1964
+ ""
1965
+ ];
1966
+ const builder = renderRustBuilder(request, " ");
1967
+ if (!paginate) return [
1968
+ ...header,
1969
+ " let response = client",
1970
+ ...builder,
1971
+ " .send()",
1972
+ " .await?;",
1973
+ " println!(\"{:?}\", response.items());",
1974
+ "",
1975
+ " Ok(())",
1976
+ "}"
1977
+ ].join("\n");
1978
+ return [
1979
+ ...header,
1980
+ " let mut items = client",
1981
+ ...builder,
1982
+ " .into_paginator()",
1983
+ " .items()",
1984
+ " .send();",
1985
+ "",
1986
+ " while let Some(item) = items.next().await {",
1987
+ " println!(\"{:?}\", item?);",
1988
+ " }",
1989
+ "",
1990
+ " Ok(())",
1991
+ "}"
1992
+ ].join("\n");
1993
+ }
1826
1994
  function emitDotnetProgram(request, paginate) {
1827
1995
  const method = dotnetClientMethodName(request.operation);
1828
1996
  const binary = hasBinaryValue(request);
@@ -1868,4 +2036,4 @@ function emitDotnetProgram(request, paginate) {
1868
2036
  }
1869
2037
 
1870
2038
  //#endregion
1871
- 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, emitSdkV3, getCompatibleComparisonOperators, getCompatibleFilterOperators, goClientMethodName, goInputTypeName, hasBinaryValue, isSetType, javaClientMethodName, javaRequestClassName, makeTypedValue, renderJsValue, renderPyValue, sdkV3CommandName, typedMapToAvMap, typedValueToAv };
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 };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dynamodb-expression-builder",
3
- "version": "0.1.1",
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, PartiQL and dynamodb-toolbox. Zero dependencies.",
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.",
5
5
  "keywords": [
6
6
  "dynamodb",
7
7
  "expression",
@@ -15,6 +15,7 @@
15
15
  "codegen",
16
16
  "query-builder",
17
17
  "boto3",
18
+ "rust",
18
19
  "aws"
19
20
  ],
20
21
  "license": "MIT",