-
Notifications
You must be signed in to change notification settings - Fork 0
Adapter: CRUD methods
All methods are async. Single-write paths can auto-upgrade to TransactWriteItems — see Adapter: Transaction auto-upgrade.
See also (AWS JS SDK v3):
GetCommand·PutCommand·UpdateCommand·DeleteCommand·QueryCommand/ScanCommand. Vocabulary: Concepts.
getByKey(
key: TKey | Raw<TKey>,
fields?: string | string[] | null,
options?: {consistent?: boolean; reviveItems?: boolean; ignoreIndirection?: boolean; params?: object}
): Promise<TItem | undefined>;Returns undefined on miss. With {reviveItems: false} returns a Raw<TItem> (see Adapter: Raw marker). With an indirect index in params.IndexName, automatically does a second-hop BatchGet against the base table — see Adapter: Indirect indices.
const item = await adapter.getByKey({name: 'Hoth'}, ['name', 'climate']);getByKeys(keys: (TKey | Raw<TKey>)[], fields?, options?): Promise<TItem[]>;Uses BatchGetItem with UnprocessedKeys retry. Missing keys are silently dropped; order is not preserved.
getList(options?: ListOptions, example?: Partial<TItem>, index?: string): Promise<PaginatedResult<TItem>>;Delegates list-input construction to the prepareListInput(example, index) hook, then forwards to getListByParams. The default hook returns {} (you'll typically override it to set IndexName/KeyConditionExpression for a sortable GSI — see KeyConditionExpression and Adapter: Hooks).
getListByParams(params: object, options?: ListOptions): Promise<PaginatedResult<TItem>>;params is the prepared input (KeyConditionExpression, IndexName, etc.). options drives offset/limit/fields/filter/search/asOf/needTotal/reviveItems/ignoreIndirection/includeDescriptor.
interface ListOptions {
offset?: number; // offset paging only — ignored by getPage*
limit?: number;
cursor?: string; // getPage* only — opaque token from the prior page
descending?: boolean;
sort?: string; // signed field → auto-picked index
consistent?: boolean;
fields?: string | string[];
search?: string; // free-form text search over `searchable` mirrors
filter?: Array< // structured clauses (Option D shape)
| {field: string; op: 'eq'|'ne'|'lt'|'le'|'gt'|'ge'|'beg'|'ct'; value: string}
| {field: string; op: 'in'|'btw'; value: string[]}
| {field: string; op: 'ex'|'nx'}
>;
asOf?: Date | string | number; // scope-freeze upper bound (requires createdAtField)
includeDescriptor?: boolean; // show the reserved descriptor row (otherwise hidden)
caseSensitive?: boolean; // search match
needTotal?: boolean; // default true
reviveItems?: boolean;
ignoreIndirection?: boolean;
retry?: RetryOptions; // indirect-hop BatchGetItem retry; defaults to adapter.retry
}Returns {data: TItem[], offset, limit, total?}. With {needTotal: false}, total is omitted (no count round-trip).
getListUnder(partialKey: Partial<TItem>, options?: ListOptions): Promise<PaginatedResult<TItem>>;Shorthand for getListByParams(buildKey(partialKey), options) — the common "list descendants under a key" pattern. Equivalent to the children-default of buildKey. For {self: true} / {partial} shapes, compose buildKey + getListByParams directly.
const txVehicles = await adapter.getListUnder({state: 'TX'}, {limit: 50});getPage(options?: ListOptions, example?: Partial<TItem>, index?: string): Promise<CursorPage<TItem>>;
getPageByParams(params: object, options?: ListOptions): Promise<CursorPage<TItem>>;Cursor-paged siblings of getList / getListByParams — DynamoDB's native LastEvaluatedKey paging. Constant cost per page regardless of depth (offset paging walks COUNT pages toward the offset) and never computes a total. Returns {data, limit, cursor?}; pass the opaque cursor back via options.cursor for the next page. With a filter, a page may come back short (even empty) and still carry a cursor — keep paging while cursor is present. Shares the whole list pipeline (filter / search / asOf / descriptor hiding / indirect second hop / revive).
let page = await adapter.getPage({limit: 50});
while (page.cursor) {
page = await adapter.getPage({limit: 50, cursor: page.cursor});
}buildKey(
values: Partial<TItem>,
options?: {self?: boolean; partial?: string; indexName?: string},
params?: object
): object;Composes the KeyConditionExpression / ExpressionAttributeNames / ExpressionAttributeValues for a Query against the base table using the adapter's declared keyFields + structuralKey. Always list-oriented — defaults to descendants.
-
Default —
begins_with(structuralKey, "TX|Dallas|")(children only; the trailing separator excludes the parent row). -
{partial: 'Bui'}—begins_with(structuralKey, "TX|Dallas|Bui"); narrows to items whose next-tier value starts withBui. -
{self: true}—begins_with(structuralKey, "TX|Dallas")(no trailing separator); includes the row at the supplied key plus descendants. Requires that sibling values at the last supplied tier not be prefixes of each other (true by construction for zero-padded numeric keyFields; caller's responsibility for string values). -
{partial}takes precedence over{self}when both are set.
Single-field keyFields (no structural key) emits pk equality; {self} / {partial} throw — there's no sort key to narrow against. Use getByKey for single-record reads instead.
post(item: TItem | Raw<TItem>, options?: {returnFailedItem?: boolean}): Promise<unknown>;Adds an attribute_not_exists(<partition_key>) condition. Throws ConditionalCheckFailedException if the key already exists.
put(item: TItem | Raw<TItem>, options?: {force?: boolean; conditions?: ConditionClause[]; params?: object; returnFailedItem?: boolean}): Promise<unknown>;Default adds an attribute_exists condition (write fails if missing). {force: true} skips the existence check (create-or-replace). Extra options.conditions are composed on top via buildCondition.
patch(
key: TKey | Raw<TKey>,
patch: Partial<TItem> | Raw<Partial<TItem>>,
options?: {delete?: string[]; separator?: string; arrayOps?: ArrayOp[]; conditions?: ConditionClause[]; params?: object; returnFailedItem?: boolean}
): Promise<unknown>;Builds an UpdateExpression. Key fields are stripped from patch automatically. options.delete lists paths to REMOVE. options.arrayOps adds atomic array operations — see Expressions: Update builder.
await adapter.patch(
{name: 'Hoth'},
{gravity: '1.5g'},
{delete: ['old_field'], arrayOps: [{op: 'append', path: 'tags', values: ['frozen']}]}
);delete(key: TKey | Raw<TKey>, options?: {conditions?: ConditionClause[]; params?: object; returnFailedItem?: boolean}): Promise<unknown>;DynamoDB's Delete is idempotent — it succeeds whether or not the item exists. Supply options.conditions to gate the delete on a predicate; the toolkit composes them into ConditionExpression via buildCondition.
Guarded delete example — refuse to delete unless a matching state flag is still set:
await adapter.delete(
{name: 'Hoth'},
{
conditions: [
{path: 'status', op: '=', value: 'archived'}
]
}
);
// Throws ConditionalCheckFailedException if `status !== 'archived'`options.params is merged in unchanged (e.g., set ReturnValues: 'ALL_OLD' to get the deleted item back as .Attributes).
post, put, patch, and delete all accept {returnFailedItem: true}. When set, the toolkit adds ReturnValuesOnConditionCheckFailure: 'ALL_OLD' to the request — so when the existence check (or any options.conditions) fails, the thrown ConditionalCheckFailedException carries the current state of the item on its Item field. Useful for "tell me what I collided with" messages.
try {
await adapter.post({name: 'Hoth', climate: 'frozen'}, {returnFailedItem: true});
} catch (err) {
if (err.name === 'ConditionalCheckFailedException') {
console.error('Collided with existing item:', err.Item);
// → {name: 'Hoth', climate: 'temperate', diameter: 7200, ...}
}
}The same flag also works inside transaction descriptors (makePost / makePut / makePatch / makeDelete) — when the transaction aborts, the failing CancellationReason on the thrown TransactionCanceledException carries the item.
options.params escape hatch: setting ReturnValuesOnConditionCheckFailure: 'ALL_OLD' by hand through options.params is equivalent; the typed option just saves the import and is easier to discover.
clone(key, mapFn?: (item: TItem) => TItem, options?: {force?: boolean; params?: object}): Promise<TItem | undefined>;
move(key, mapFn?, options?): Promise<TItem | undefined>;clone reads the source, applies mapFn, writes the result (post if force is unset, put({force: true}) otherwise). move does the same plus deletes the original — bundled into a single TransactWriteItems call for atomicity.
mapFn is optional; the default is identity — the item is cloned/moved as-is. You almost always want to pass one, because cloning a key-addressable item back to the same key with post (the default) throws ConditionalCheckFailedException. The common patterns are:
- Rewrite the key (e.g.
item => ({...item, name: item.name + '-copy'})). - Pass
{force: true}if you actually want to overwrite the destination.
Returns the cloned/moved item (post-mapFn), or undefined when the source is missing.
// Rename + duplicate:
const copy = await adapter.clone(
{name: 'Tatooine'},
item => ({...item, name: 'Tatooine-copy'})
);
// Move with key rewrite — atomic put + delete:
await adapter.move(
{name: 'Tatooine'},
item => ({...item, name: 'Jakku'})
);
// Overwrite destination (use with care — `force` bypasses the existence check):
await adapter.clone({name: 'Hoth'}, item => ({...item, name: 'Hoth-mirror'}), {force: true});edit(
key: TKey | Raw<TKey>,
mapFn: (item: TItem) => TItem | undefined,
options?: {readFields?: string[]; allowKeyChange?: boolean; expectedVersion?: number; params?: object}
): Promise<TItem | undefined>;Reads the item, runs mapFn, diffs the result against the original, and dispatches an UpdateCommand with the computed SET / REMOVE ops. Returns the revived post-edit item. mapFn returning undefined short-circuits with no write. If mapFn returns an unchanged shape (identity / no diff), the write is skipped entirely — no WCU charged.
-
Key-field mutations — by default throw
KeyFieldChanged. Pass{allowKeyChange: true}to auto-promote the edit into amove(atomic put-to-new-key + delete-from-old-key). -
versionFieldinteraction — the OC condition is auto-applied using the version observed on the read; stale state rejects with a CCF. -
readFields— optional projection for the initial read (saves RCU when the mapFn only touches a few fields).
Good for "update only the fields that actually changed" flows where computing the diff beats rewriting the whole item.
editListByParams(
params: object,
mapFn: (item: TItem) => TItem | undefined,
options?: MassOptions
): Promise<MassOpResult>;Resumable batch edit across every item matching params (Query or Scan). Same per-item semantics as edit. Honors options.asOf / options.filter / options.maxItems / options.resumeToken for scope-freeze and incremental runs. See Adapter: Mass methods for the mass-op envelope.
typeOf(item: Partial<TItem> | undefined | null): string | number | undefined;Classifies an item. Priority:
-
typeDiscriminator.namevalue when present on the item — returned as-is (coerced to string). -
typeLabels[depth - 1]wheredepthis the count of contiguous-from-start definedkeyFieldson the item, whentypeLabelsis declared. - Raw
depthnumber when notypeLabelsis declared. -
undefinedwhen the item carries no recognised type-signalling fields.
With typeField also declared, the built-in prepare step stamps this label on write — so typeOf reads back what it wrote. Combined pattern: typeField: 'kind', typeDiscriminator: 'kind'.
applyFilter(params: object, clauses: FilterClause[]): object;Compiles parsed ?<op>-<field>=<value> clauses (from the REST layer's parseFilter) into FilterExpression / KeyConditionExpression on params. Validates against the adapter's filterable allowlist; coerces values to the declared type; auto-promotes index-compatible clauses into KeyConditionExpression when the target (base table or params.IndexName) has matching pk/sk.
Throws BadFilterField / BadFilterOp when a clause names an unlisted field or uses an unallowed op. Typically called internally by getListByParams (via options.filter); call it directly only when building custom list paths outside the adapter's standard list flow.
Start here
- Getting started
- Concepts
- Key and field design
- Compatibility
- Migration: v2 to v3
- SDK v2 to v3 cheat sheet
Guides
- Hierarchical data walkthrough
- Key expression patterns
- Multi-type tables
- Pagination
- Mass operation semantics
- URL schema design
Adapter
- Adapter
- Constructor options
- CRUD methods
- Mass methods
- Batch builders
- Hooks
- Raw marker
- Indirect indices
- Transaction auto-upgrade
Expression builders
Batch / transactions / mass / paths
REST surface
Framework adapters
Recipes
- Recipes index
- List records of a tier
- Per-tier sparse GSI markers
- Tier within a partition
- Reservation with auto-release
- Keys-only GSI, runtime projection
- Cascade subtree operations
- Querying subtrees with buildKey
- Filter URL grammar
- Text search
- Provisioning workflow
- Resumable mass operations
History