effect-dynamodb 1.5.0 → 1.7.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/dist/DynamoModel.d.ts +60 -16
- package/dist/DynamoModel.d.ts.map +1 -1
- package/dist/DynamoModel.js +40 -3
- package/dist/DynamoModel.js.map +1 -1
- package/dist/Entity.d.ts.map +1 -1
- package/dist/Entity.js +240 -54
- package/dist/Entity.js.map +1 -1
- package/dist/Errors.d.ts +99 -0
- package/dist/Errors.d.ts.map +1 -1
- package/dist/Errors.js +77 -0
- package/dist/Errors.js.map +1 -1
- package/dist/KeyComposer.d.ts +75 -39
- package/dist/KeyComposer.d.ts.map +1 -1
- package/dist/KeyComposer.js +204 -40
- package/dist/KeyComposer.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/EntitySchemas.d.ts.map +1 -1
- package/dist/internal/EntitySchemas.js +9 -0
- package/dist/internal/EntitySchemas.js.map +1 -1
- package/dist/internal/EntityTypes.d.ts +14 -2
- package/dist/internal/EntityTypes.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/Entity.js
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
* carries derived schemas for 7 type extractors (Model, Record, Input, Update,
|
|
7
7
|
* Key, Item, Marshalled).
|
|
8
8
|
*/
|
|
9
|
-
import { DateTime, Duration, Effect, Option, Schema, Stream } from "effect";
|
|
9
|
+
import { DateTime, Duration, Effect, Option, Schema, SchemaAST, Stream } from "effect";
|
|
10
10
|
import { DynamoClient } from "./DynamoClient.js";
|
|
11
11
|
import { getIdentifierField, getSparseFields, isConfiguredModel, isHidden, isRecordAst, isRecordSchema, isRef, isRefField, } from "./DynamoModel.js";
|
|
12
12
|
import * as DynamoSchema from "./DynamoSchema.js";
|
|
13
|
-
import { CascadePartialFailure, ConditionalCheckFailed, ItemNotFound, isAwsConditionalCheckFailed, isAwsTransactionCancelled, OptimisticLockError, RefNotFound, StaleAppend, TransactionOverflow, UniqueConstraintViolation, ValidationError, } from "./Errors.js";
|
|
13
|
+
import { CascadePartialFailure, CompositeKeyHoleError, ConditionalCheckFailed, ItemNotFound, isAwsConditionalCheckFailed, isAwsTransactionCancelled, makeCompositeNullableError, OptimisticLockError, RefNotFound, StaleAppend, TransactionOverflow, UniqueConstraintViolation, ValidationError, } from "./Errors.js";
|
|
14
14
|
import { makeBoundAppend, makeBoundDelete, makeBoundPut, makeBoundUpdate, } from "./internal/BoundCrud.js";
|
|
15
15
|
import { BoundQueryImpl } from "./internal/BoundQuery.js";
|
|
16
16
|
import { compileExpr, createConditionOps, isExpr, parseShorthand, parseSimpleShorthand, } from "./internal/Expr.js";
|
|
@@ -206,6 +206,108 @@ const makeImpl = (config) => {
|
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
208
|
// ---------------------------------------------------------------------------
|
|
209
|
+
// EDD-9025: composite attribute schemas must not include `null`.
|
|
210
|
+
//
|
|
211
|
+
// Composites participate in string composition (`acc#X#alert#Y`); `null` is
|
|
212
|
+
// not a meaningful slot value. Allowing `Schema.NullOr` / `NullishOr` /
|
|
213
|
+
// `Schema.Union([..., Schema.Null])` on a composite would let
|
|
214
|
+
// `set({ composite: null })` typecheck (after the v1.6 update-payload
|
|
215
|
+
// widening was reverted), then either blow up at runtime or silently
|
|
216
|
+
// produce a key with the literal string `"null"` as a slot.
|
|
217
|
+
//
|
|
218
|
+
// Sparse pattern is still expressible via `Schema.optional(...)` (T |
|
|
219
|
+
// undefined), which produces no Null AST node. See DESIGN.md §7.
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
// Resolve the AST for a domain composite name. For ref-derived `${name}Id`
|
|
222
|
+
// composites, the schema is the referenced entity's identifier schema (not
|
|
223
|
+
// the model field). For all other composites, it's the model field's
|
|
224
|
+
// schema. Returns undefined when the composite isn't declared on the model
|
|
225
|
+
// (already caught by EDD-9002 above, so this is defensive).
|
|
226
|
+
const resolveCompositeSchemaForNullCheck = (compositeAttr) => {
|
|
227
|
+
if (compositeAttr in modelFields) {
|
|
228
|
+
return { schema: modelFields[compositeAttr], source: `model field` };
|
|
229
|
+
}
|
|
230
|
+
// Ref-derived ${name}Id composite — look up the identifier schema on the
|
|
231
|
+
// referenced entity's model. We can't use the resolvedRefs array here
|
|
232
|
+
// (it's built later); recompute inline against config.refs.
|
|
233
|
+
if (config.refs) {
|
|
234
|
+
for (const [refFieldName, refValue] of Object.entries(config.refs)) {
|
|
235
|
+
if (`${refFieldName}Id` !== compositeAttr)
|
|
236
|
+
continue;
|
|
237
|
+
const refEntity = refValue.entity;
|
|
238
|
+
const idField = getIdentifierField(refEntity.model);
|
|
239
|
+
if (!idField)
|
|
240
|
+
return undefined;
|
|
241
|
+
return { schema: idField.schema, source: `ref "${refFieldName}" identifier` };
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return undefined;
|
|
245
|
+
};
|
|
246
|
+
// Recursively walk an AST and return a path-string if `null` is reachable
|
|
247
|
+
// in the type union (anywhere — direct, nested in Union, behind Suspend).
|
|
248
|
+
// `Schema.optional(X)` produces `T | undefined` (no Null) and is allowed.
|
|
249
|
+
// `Schema.NullOr(X)` produces `T | null` and is rejected.
|
|
250
|
+
const findNullInAst = (ast, path, seen = new Set()) => {
|
|
251
|
+
if (seen.has(ast))
|
|
252
|
+
return undefined;
|
|
253
|
+
seen.add(ast);
|
|
254
|
+
if (SchemaAST.isNull(ast))
|
|
255
|
+
return `${path}.Null`;
|
|
256
|
+
if (SchemaAST.isUnion(ast)) {
|
|
257
|
+
for (let i = 0; i < ast.types.length; i++) {
|
|
258
|
+
const found = findNullInAst(ast.types[i], `${path}.Union[${i}]`, seen);
|
|
259
|
+
if (found)
|
|
260
|
+
return found;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// Other AST shapes (Declaration, String, Number, Suspend, ...) cannot
|
|
264
|
+
// introduce `null` themselves. Suspend wrappers are handled
|
|
265
|
+
// conservatively — we don't recurse into thunks because the EDD-9025
|
|
266
|
+
// check is about static type unions, and recursive schemas wrapping
|
|
267
|
+
// composite-eligible scalars are pathological for keys anyway.
|
|
268
|
+
return undefined;
|
|
269
|
+
};
|
|
270
|
+
const checkCompositeForNull = (surface, compositeAttr) => {
|
|
271
|
+
const resolved = resolveCompositeSchemaForNullCheck(compositeAttr);
|
|
272
|
+
if (!resolved)
|
|
273
|
+
return; // EDD-9002 already caught the unknown-attribute case
|
|
274
|
+
const found = findNullInAst(resolved.schema.ast, resolved.source);
|
|
275
|
+
if (found) {
|
|
276
|
+
throw makeCompositeNullableError({
|
|
277
|
+
entityType: config.entityType,
|
|
278
|
+
surface,
|
|
279
|
+
compositeAttribute: compositeAttr,
|
|
280
|
+
schemaPath: found,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
// primaryKey composites
|
|
285
|
+
for (const attr of [
|
|
286
|
+
...config.indexes.primary.pk.composite,
|
|
287
|
+
...config.indexes.primary.sk.composite,
|
|
288
|
+
]) {
|
|
289
|
+
checkCompositeForNull("primaryKey", attr);
|
|
290
|
+
}
|
|
291
|
+
// GSI composites (each index)
|
|
292
|
+
for (const [indexName, indexDef] of Object.entries(config.indexes)) {
|
|
293
|
+
if (indexName === "primary")
|
|
294
|
+
continue;
|
|
295
|
+
for (const attr of [...indexDef.pk.composite, ...indexDef.sk.composite]) {
|
|
296
|
+
checkCompositeForNull(`index "${indexName}"`, attr);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
// Unique constraint composites — `unique` is keyed by constraint name,
|
|
300
|
+
// value is a single field name OR an array of field names OR a config
|
|
301
|
+
// object. resolveUniqueFields normalises all three to a list of field names.
|
|
302
|
+
if (config.unique) {
|
|
303
|
+
for (const [constraintName, constraintDef] of Object.entries(config.unique)) {
|
|
304
|
+
const fields = resolveUniqueFields(constraintDef);
|
|
305
|
+
for (const f of fields) {
|
|
306
|
+
checkCompositeForNull(`unique:${constraintName}`, f);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// ---------------------------------------------------------------------------
|
|
209
311
|
// Validate timeSeries config (EDD-9010..9016)
|
|
210
312
|
// ---------------------------------------------------------------------------
|
|
211
313
|
if (config.timeSeries !== undefined && config.timeSeries !== null) {
|
|
@@ -1173,13 +1275,20 @@ const makeImpl = (config) => {
|
|
|
1173
1275
|
// rather than whole-field replace — concurrent writers to disjoint
|
|
1174
1276
|
// buckets must coexist (the version CAS protects against same-bucket
|
|
1175
1277
|
// races). Non-sparse fields use the existing replace semantics.
|
|
1278
|
+
//
|
|
1279
|
+
// null and undefined collapse: both mean "absent" under v3's
|
|
1280
|
+
// two-way classification (DESIGN.md §7). For the in-memory item
|
|
1281
|
+
// we DELETE the attribute (mirrors a DynamoDB REMOVE), so
|
|
1282
|
+
// subsequent decode sees the field as absent rather than as a
|
|
1283
|
+
// typed null that the model schema may not permit. The composer
|
|
1284
|
+
// below sees the attr as absent in `newItem` and applies the
|
|
1285
|
+
// structural rule.
|
|
1176
1286
|
for (const [attr, val] of Object.entries(hydratedUpdates)) {
|
|
1177
|
-
if (val === undefined)
|
|
1287
|
+
if (val === null || val === undefined) {
|
|
1288
|
+
delete newItem[attr];
|
|
1178
1289
|
continue;
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
val !== null &&
|
|
1182
|
-
typeof val === "object") {
|
|
1290
|
+
}
|
|
1291
|
+
if (hasSparseFields && attr in sparseFields && typeof val === "object") {
|
|
1183
1292
|
const existing = newItem[attr] ?? {};
|
|
1184
1293
|
newItem[attr] = { ...existing, ...val };
|
|
1185
1294
|
}
|
|
@@ -1259,8 +1368,55 @@ const makeImpl = (config) => {
|
|
|
1259
1368
|
// `newItem` is already in domain names (the merge built it from
|
|
1260
1369
|
// `currentDomainItem` + domain-keyed updates) — no rename needed
|
|
1261
1370
|
// here. Recompose all keys with the updated attributes.
|
|
1262
|
-
|
|
1263
|
-
|
|
1371
|
+
//
|
|
1372
|
+
// Primary key always recomposes from `newItem` (Put-style).
|
|
1373
|
+
const primaryKeyMap = composePrimaryKey(newItem);
|
|
1374
|
+
Object.assign(newItem, primaryKeyMap);
|
|
1375
|
+
// GSI keys: route through the policy-aware composer so the v3
|
|
1376
|
+
// structural rule + `Entity.remove` cascade match the standard
|
|
1377
|
+
// update path. `newItem` carries stored values for any composite
|
|
1378
|
+
// the user did not touch — passing it as `keyRecord` lets the
|
|
1379
|
+
// composer treat omissions as no-ops (the half stays untouched
|
|
1380
|
+
// because the stored value is present), while still firing the
|
|
1381
|
+
// cascade and the structural truncation rule. See DESIGN.md §7.
|
|
1382
|
+
const retainRemovedSet = uState.remove ? new Set(uState.remove) : undefined;
|
|
1383
|
+
try {
|
|
1384
|
+
const gsiUpdate = KeyComposer.composeGsiKeysForUpdatePolicyAware(schema, entityType, entityVersion, allIndexes, hydratedUpdates, newItem, { removedSet: retainRemovedSet });
|
|
1385
|
+
for (const [field, value] of Object.entries(gsiUpdate.sets)) {
|
|
1386
|
+
newItem[field] = value;
|
|
1387
|
+
}
|
|
1388
|
+
for (const field of gsiUpdate.removes) {
|
|
1389
|
+
delete newItem[field];
|
|
1390
|
+
}
|
|
1391
|
+
// Untouched GSIs (no policy + no payload + no cascade): retain
|
|
1392
|
+
// path semantics are Put-style — recompose from `newItem` and
|
|
1393
|
+
// drop both keys when any composite is missing. Matches the
|
|
1394
|
+
// existing `composeAllKeys` behavior for those GSIs.
|
|
1395
|
+
const addressed = new Set([
|
|
1396
|
+
...Object.keys(gsiUpdate.sets),
|
|
1397
|
+
...gsiUpdate.removes,
|
|
1398
|
+
]);
|
|
1399
|
+
for (const [indexName, indexDef] of Object.entries(allIndexes)) {
|
|
1400
|
+
if (indexName === "primary")
|
|
1401
|
+
continue;
|
|
1402
|
+
if (addressed.has(indexDef.pk.field) || addressed.has(indexDef.sk.field))
|
|
1403
|
+
continue;
|
|
1404
|
+
const keys = KeyComposer.tryComposeIndexKeys(schema, entityType, entityVersion, indexDef, newItem);
|
|
1405
|
+
if (keys) {
|
|
1406
|
+
Object.assign(newItem, keys);
|
|
1407
|
+
}
|
|
1408
|
+
else {
|
|
1409
|
+
delete newItem[indexDef.pk.field];
|
|
1410
|
+
delete newItem[indexDef.sk.field];
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
catch (e) {
|
|
1415
|
+
if (e instanceof CompositeKeyHoleError) {
|
|
1416
|
+
return yield* e;
|
|
1417
|
+
}
|
|
1418
|
+
throw e;
|
|
1419
|
+
}
|
|
1264
1420
|
const sentinelRotations = [];
|
|
1265
1421
|
if (touchesUniqueFields) {
|
|
1266
1422
|
const currentRawRecord = currentRaw;
|
|
@@ -1443,18 +1599,32 @@ const makeImpl = (config) => {
|
|
|
1443
1599
|
const updateSystemColliders = new Set();
|
|
1444
1600
|
if (systemFields.updatedAtCollision && systemFields.updatedAt)
|
|
1445
1601
|
updateSystemColliders.add(systemFields.updatedAt);
|
|
1446
|
-
// Add user-provided updates
|
|
1602
|
+
// Add user-provided updates. Buffered REMOVE clauses for null
|
|
1603
|
+
// payload entries land in `removeClauses` below — under v3,
|
|
1604
|
+
// `set({ attr: null | undefined })` REMOVEs the attribute from the
|
|
1605
|
+
// item but does NOT separately cascade-drop GSI keys. The structural
|
|
1606
|
+
// composer treats the cleared attribute as absent and recomposes
|
|
1607
|
+
// (or truncates / drops / no-ops) per the half's policy.
|
|
1608
|
+
// EDD-9025 guarantees no composite is nullable, so `set` of `null`
|
|
1609
|
+
// is only reachable for non-composite, model-declared-nullable
|
|
1610
|
+
// attributes. See DESIGN.md §7.
|
|
1611
|
+
const nullClears = [];
|
|
1447
1612
|
for (const [attr, val] of Object.entries(encodedUpdatesMap)) {
|
|
1448
|
-
if (val === undefined)
|
|
1449
|
-
continue;
|
|
1450
1613
|
if (updateSystemColliders.has(attr))
|
|
1451
1614
|
continue;
|
|
1615
|
+
if (val === null || val === undefined) {
|
|
1616
|
+
// Sparse fields: clearing a sparse field with null is a no-op
|
|
1617
|
+
// here — the `.removeEntries` API is the explicit per-key
|
|
1618
|
+
// remove. Skip to avoid REMOVE'ing the whole prefix erroneously.
|
|
1619
|
+
if (hasSparseFields && attr in sparseFields)
|
|
1620
|
+
continue;
|
|
1621
|
+
nullClears.push(attr);
|
|
1622
|
+
continue;
|
|
1623
|
+
}
|
|
1452
1624
|
// Sparse fields: emit one SET per bucket. Whole-bucket replace
|
|
1453
1625
|
// semantics — concurrent writers to disjoint buckets are safe.
|
|
1454
|
-
// `null` is NOT REMOVE — that's footgunny; explicit removal goes
|
|
1455
|
-
// through `.removeEntries(field, keys)`.
|
|
1456
1626
|
if (hasSparseFields && attr in sparseFields) {
|
|
1457
|
-
if (
|
|
1627
|
+
if (typeof val !== "object")
|
|
1458
1628
|
continue;
|
|
1459
1629
|
const sparse = sparseFields[attr];
|
|
1460
1630
|
for (const [k, v] of Object.entries(val)) {
|
|
@@ -1530,6 +1700,17 @@ const makeImpl = (config) => {
|
|
|
1530
1700
|
}
|
|
1531
1701
|
}
|
|
1532
1702
|
// REMOVE
|
|
1703
|
+
// Two channels feed REMOVE clauses:
|
|
1704
|
+
// 1. `Entity.remove([attr])` — explicit per-attribute drop. Cascades
|
|
1705
|
+
// to drop the GSI keys for any GSI containing the attr as a
|
|
1706
|
+
// composite (one of v3's two drop triggers — see DESIGN.md §7).
|
|
1707
|
+
// Tracked in `removedSet` so the composer can apply the cascade
|
|
1708
|
+
// rule.
|
|
1709
|
+
// 2. Null payload entries (`set({ attr: null })`). These REMOVE the
|
|
1710
|
+
// attribute from the item only. They do NOT cascade GSI drops —
|
|
1711
|
+
// under v3 two-way classification, the structural composer just
|
|
1712
|
+
// treats the attribute as absent. (EDD-9025 guarantees these
|
|
1713
|
+
// are never composites.)
|
|
1533
1714
|
const removedSet = uState.remove ? new Set(uState.remove) : undefined;
|
|
1534
1715
|
if (uState.remove) {
|
|
1535
1716
|
for (const attr of uState.remove) {
|
|
@@ -1538,11 +1719,31 @@ const makeImpl = (config) => {
|
|
|
1538
1719
|
removeClauses.push(nameKey);
|
|
1539
1720
|
}
|
|
1540
1721
|
}
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1722
|
+
for (const attr of nullClears) {
|
|
1723
|
+
const nameKey = `#r${removeClauses.length}`;
|
|
1724
|
+
names[nameKey] = resolveDbName(attr);
|
|
1725
|
+
removeClauses.push(nameKey);
|
|
1726
|
+
}
|
|
1727
|
+
// Policy-aware GSI key composition (v3 — per-half structural rule).
|
|
1728
|
+
// One call covers SETs (full recompose, truncated leading prefix,
|
|
1729
|
+
// or no-op), sparse whole-half-empty REMOVEs, and cascade REMOVEs
|
|
1730
|
+
// when an Entity.remove() targets a GSI composite. See DESIGN.md
|
|
1731
|
+
// §7 Policy-Aware GSI Composition.
|
|
1732
|
+
//
|
|
1733
|
+
// The composer throws CompositeKeyHoleError (EDD-9024) when the
|
|
1734
|
+
// merged payload describes a hole pattern AND the relevant half's
|
|
1735
|
+
// policy is `'preserve'`. Under `'sparse'`, holes silently truncate.
|
|
1736
|
+
// Catch the error synchronously and surface as a typed Effect failure.
|
|
1737
|
+
let gsiUpdate;
|
|
1738
|
+
try {
|
|
1739
|
+
gsiUpdate = KeyComposer.composeGsiKeysForUpdatePolicyAware(schema, entityType, entityVersion, allIndexes, hydratedUpdates, decodedKey, { removedSet });
|
|
1740
|
+
}
|
|
1741
|
+
catch (e) {
|
|
1742
|
+
if (e instanceof CompositeKeyHoleError) {
|
|
1743
|
+
return yield* e;
|
|
1744
|
+
}
|
|
1745
|
+
throw e;
|
|
1746
|
+
}
|
|
1546
1747
|
for (const [field, value] of Object.entries(gsiUpdate.sets)) {
|
|
1547
1748
|
const nameKey = `#u${counter}`;
|
|
1548
1749
|
const valKey = `:u${counter}`;
|
|
@@ -2267,48 +2468,33 @@ const makeImpl = (config) => {
|
|
|
2267
2468
|
setClauses.push(`${nameKey} = ${valKey}`);
|
|
2268
2469
|
counter++;
|
|
2269
2470
|
}
|
|
2270
|
-
// GSI key recomposition
|
|
2271
|
-
//
|
|
2272
|
-
//
|
|
2273
|
-
//
|
|
2274
|
-
//
|
|
2471
|
+
// GSI key recomposition. v3 unifies append with update — both call
|
|
2472
|
+
// the same composer. PK composites are excluded from the update
|
|
2473
|
+
// payload — they're already in the key and never change during an
|
|
2474
|
+
// append. The composer treats composites outside `appendInput` as
|
|
2475
|
+
// absent under the structural rule (per-half policy decides whether
|
|
2476
|
+
// a half empties to drop or no-op). See DESIGN.md §7 — "append uses
|
|
2477
|
+
// the same composer."
|
|
2275
2478
|
const nonPkAppendFields = {};
|
|
2276
2479
|
for (const [attr, val] of Object.entries(encoded)) {
|
|
2277
2480
|
if (!pkCompositeSet.has(attr)) {
|
|
2278
2481
|
nonPkAppendFields[attr] = val;
|
|
2279
2482
|
}
|
|
2280
2483
|
}
|
|
2281
|
-
//
|
|
2282
|
-
//
|
|
2283
|
-
//
|
|
2284
|
-
//
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
if (!indexDef.indexPolicy) {
|
|
2293
|
-
policyAwareIndexes[indexName] = indexDef;
|
|
2294
|
-
continue;
|
|
2484
|
+
// The composer can throw CompositeKeyHoleError (EDD-9024) when a hole
|
|
2485
|
+
// pattern is encountered AND the relevant half's policy is `'preserve'`.
|
|
2486
|
+
// Under `'sparse'`, holes silently truncate. Surface the error as a
|
|
2487
|
+
// typed Effect failure.
|
|
2488
|
+
let gsiUpdate;
|
|
2489
|
+
try {
|
|
2490
|
+
gsiUpdate = KeyComposer.composeGsiKeysForUpdatePolicyAware(schema, entityType, entityVersion, allIndexes, nonPkAppendFields, encoded);
|
|
2491
|
+
}
|
|
2492
|
+
catch (e) {
|
|
2493
|
+
if (e instanceof CompositeKeyHoleError) {
|
|
2494
|
+
return yield* e;
|
|
2295
2495
|
}
|
|
2296
|
-
|
|
2297
|
-
policyAwareIndexes[indexName] = {
|
|
2298
|
-
...indexDef,
|
|
2299
|
-
indexPolicy: (item) => {
|
|
2300
|
-
const raw = originalPolicy(item);
|
|
2301
|
-
const filtered = {};
|
|
2302
|
-
for (const [attr, policy] of Object.entries(raw)) {
|
|
2303
|
-
if (appendInputFieldSet.has(attr) && policy !== undefined) {
|
|
2304
|
-
filtered[attr] = policy;
|
|
2305
|
-
}
|
|
2306
|
-
}
|
|
2307
|
-
return filtered;
|
|
2308
|
-
},
|
|
2309
|
-
};
|
|
2496
|
+
throw e;
|
|
2310
2497
|
}
|
|
2311
|
-
const gsiUpdate = KeyComposer.composeGsiKeysForUpdatePolicyAware(schema, entityType, entityVersion, policyAwareIndexes, nonPkAppendFields, encoded);
|
|
2312
2498
|
for (const [field, value] of Object.entries(gsiUpdate.sets)) {
|
|
2313
2499
|
const nameKey = `#a${counter}`;
|
|
2314
2500
|
const valKey = `:a${counter}`;
|