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 +4 -4
- package/dist/index.cjs +177 -6
- package/dist/index.d.cts +21 -3
- package/dist/index.d.ts +21 -3
- package/dist/index.js +175 -7
- package/package.json +3 -2
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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.
|
|
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",
|