@jzhuo3/dynamodb-lib 0.1.1 → 0.1.2

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
@@ -102,6 +102,31 @@ The constructor accepts independent configurations, rather than using one proces
102
102
 
103
103
  **Update Expression builders** compose the changes passed to `update` as a command array: `Assign`, `AssignIfNotExists`, `Increment`, `Decrement`, `ListAppend`, `ListPrepend`, `Remove`, `SetAdd`, `SetDelete`.
104
104
 
105
+ ### Inspecting expressions in unit tests
106
+
107
+ Call `expression.describe()` for a detached, JSON-serializable snapshot containing `expression`, `expressionAttributeNames`, `expressionAttributeValues`, and (for updates) `updateExpressionGroup`. Expressions without values return an empty values object.
108
+
109
+ ```ts
110
+ const description = SetAdd('tags', new Set(['a', 'b'])).describe();
111
+ // description.expressionAttributeValues:
112
+ // { ':val0': { $type: 'Set', values: ['a', 'b'] } }
113
+ const json = JSON.stringify(description);
114
+ ```
115
+
116
+ Strings, booleans, null, finite numbers, arrays and plain objects keep their JSON shape. Nested native values use these explicit representations:
117
+
118
+ | Native value | Description |
119
+ | --- | --- |
120
+ | `bigint` | `{ $type: 'BigInt', value: '9007199254740993' }` |
121
+ | SDK `NumberValue` | `{ $type: 'NumberValue', value: '123.456' }` |
122
+ | `Set` | `{ $type: 'Set', values: [...] }` |
123
+ | `Buffer`, `ArrayBuffer`, typed array or `DataView` | `{ $type: 'Binary', encoding: 'base64', value: 'AQI=' }` |
124
+ | `Map` | `{ $type: 'Map', entries: [[key, value], ...] }` |
125
+ | `undefined` | `{ $type: 'Undefined' }` |
126
+ | `NaN`, positive/negative infinity | `{ $type: 'Number', value: 'NaN' }` (or `'Infinity'` / `'-Infinity'`) |
127
+
128
+ Sets and Maps retain insertion order. Binary views serialize only their visible bytes. Circular references, functions, symbols, enumerable symbol keys, and unsupported class instances (including `Date` and `Blob`) throw `TypeError`. Serialization does not validate whether a value is accepted by DynamoDB. This format is for inspection and assertions, not deserialization or sending to DynamoDB; plain objects containing `$type` remain plain objects. The live expression and its values are unchanged.
129
+
105
130
  ## Detailed documentation
106
131
 
107
132
  See the [documentation index](doc/README.md) for [every service method](doc/service.md), [configuration](doc/configuration.md), [Condition Expression and Update Expression builders](doc/expressions.md), [transaction methods](doc/transactions.md), and [examples](doc/examples.md).
@@ -135,6 +135,20 @@ export declare function ListPrepend(attribute: string, value: Array<any>, upsert
135
135
  export declare function Remove(attribute: string): DynamoDBExpression;
136
136
  export declare function SetAdd(attribute: string, value: Set<any>): DynamoDBExpression;
137
137
  export declare function SetDelete(attribute: string, value: Set<any>): DynamoDBExpression;
138
+ /** JSON-compatible value used by expression descriptions. */
139
+ export type DynamoDBExpressionSerializedValue = null | boolean | number | string | DynamoDBExpressionSerializedValue[] | {
140
+ [key: string]: DynamoDBExpressionSerializedValue;
141
+ };
142
+ export type DynamoDBExpressionDescription = {
143
+ /** DynamoDB expression string, e.g. `#attr0 = :val0`. */
144
+ expression: string;
145
+ /** Placeholder -> attribute name (e.g. `#attr0` -> `VersionStamp`). */
146
+ expressionAttributeNames: Record<string, string>;
147
+ /** Placeholder -> JSON-compatible value; native non-JSON values use explicit `$type` tags. */
148
+ expressionAttributeValues: Record<string, DynamoDBExpressionSerializedValue>;
149
+ /** Which update clause the expression belongs to (SET/REMOVE/ADD/DELETE). */
150
+ updateExpressionGroup?: 'SET' | 'REMOVE' | 'ADD' | 'DELETE';
151
+ };
138
152
  export declare class DynamoDBExpression {
139
153
  expressionAttributeNameMap: Map<string, string>;
140
154
  expressionAttributeValueMap: Map<string, any> | null;
@@ -143,6 +157,14 @@ export declare class DynamoDBExpression {
143
157
  constructor(expressionAttributeNameMap?: Map<string, string>, // e.g. #attr0 -> attribute
144
158
  expressionAttributeValueMap?: Map<string, any> | null, // e.g. :val0 -> value
145
159
  expression?: string, updateExpressionGroup?: "SET" | "REMOVE" | "ADD" | "DELETE" | undefined);
160
+ /**
161
+ * Detached, JSON-serializable description of the expression. Intended for logging,
162
+ * debugging, and unit tests that need to assert on a built expression
163
+ * without reaching into the internal Maps.
164
+ * Sets, bigint, binary, Maps, NumberValue, undefined and non-finite numbers
165
+ * use explicit `$type` tags. Circular and unsupported values throw TypeError.
166
+ */
167
+ describe(): DynamoDBExpressionDescription;
146
168
  }
147
169
  export declare enum DynamoDBTransactionMode {
148
170
  READ = "READ",
package/dist/cjs/index.js CHANGED
@@ -356,6 +356,55 @@ function SetDelete(attribute, value) {
356
356
  const expression = `${path} :val0`;
357
357
  return new DynamoDBExpression(expressionAttributeNameMap, expressionAttributeValueMap, expression, 'DELETE');
358
358
  }
359
+ function serializeExpressionValue(value, ancestors = new Set()) {
360
+ if (value === null || typeof value === 'string' || typeof value === 'boolean')
361
+ return value;
362
+ if (typeof value === 'number') {
363
+ return Number.isFinite(value) ? value : { $type: 'Number', value: String(value) };
364
+ }
365
+ if (typeof value === 'bigint')
366
+ return { $type: 'BigInt', value: value.toString() };
367
+ if (value === undefined)
368
+ return { $type: 'Undefined' };
369
+ if (typeof value !== 'object') {
370
+ throw new TypeError(`Cannot describe expression value of type ${typeof value}`);
371
+ }
372
+ if (ancestors.has(value))
373
+ throw new TypeError('Cannot describe circular expression value');
374
+ ancestors.add(value);
375
+ try {
376
+ if (value instanceof lib_dynamodb_1.NumberValue)
377
+ return { $type: 'NumberValue', value: value.toString() };
378
+ if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
379
+ const bytes = value instanceof ArrayBuffer
380
+ ? Buffer.from(value)
381
+ : Buffer.from(value.buffer, value.byteOffset, value.byteLength);
382
+ return { $type: 'Binary', encoding: 'base64', value: bytes.toString('base64') };
383
+ }
384
+ if (value instanceof Set) {
385
+ return { $type: 'Set', values: Array.from(value, item => serializeExpressionValue(item, ancestors)) };
386
+ }
387
+ if (Array.isArray(value))
388
+ return Array.from(value, item => serializeExpressionValue(item, ancestors));
389
+ if (value instanceof Map) {
390
+ return { $type: 'Map', entries: Array.from(value, ([key, item]) => [
391
+ serializeExpressionValue(key, ancestors), serializeExpressionValue(item, ancestors),
392
+ ]) };
393
+ }
394
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
395
+ throw new TypeError('Cannot describe unsupported expression value object');
396
+ }
397
+ if (Object.getOwnPropertySymbols(value).some(key => Object.prototype.propertyIsEnumerable.call(value, key))) {
398
+ throw new TypeError('Cannot describe expression value with symbol keys');
399
+ }
400
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
401
+ key, serializeExpressionValue(item, ancestors),
402
+ ]));
403
+ }
404
+ finally {
405
+ ancestors.delete(value);
406
+ }
407
+ }
359
408
  class DynamoDBExpression {
360
409
  expressionAttributeNameMap;
361
410
  expressionAttributeValueMap;
@@ -369,6 +418,27 @@ class DynamoDBExpression {
369
418
  this.expression = expression;
370
419
  this.updateExpressionGroup = updateExpressionGroup;
371
420
  }
421
+ /**
422
+ * Detached, JSON-serializable description of the expression. Intended for logging,
423
+ * debugging, and unit tests that need to assert on a built expression
424
+ * without reaching into the internal Maps.
425
+ * Sets, bigint, binary, Maps, NumberValue, undefined and non-finite numbers
426
+ * use explicit `$type` tags. Circular and unsupported values throw TypeError.
427
+ */
428
+ describe() {
429
+ return {
430
+ expression: this.expression,
431
+ expressionAttributeNames: Object.fromEntries(this.expressionAttributeNameMap),
432
+ expressionAttributeValues: this.expressionAttributeValueMap
433
+ ? Object.fromEntries(Array.from(this.expressionAttributeValueMap, ([key, value]) => [
434
+ key, serializeExpressionValue(value),
435
+ ]))
436
+ : {},
437
+ ...(this.updateExpressionGroup !== undefined
438
+ ? { updateExpressionGroup: this.updateExpressionGroup }
439
+ : {}),
440
+ };
441
+ }
372
442
  }
373
443
  exports.DynamoDBExpression = DynamoDBExpression;
374
444
  var DynamoDBTransactionMode;