-
Notifications
You must be signed in to change notification settings - Fork 0
Release notes
Reverse-chronological. Links to the npm version history and this repo's git tags.
Performance fix on the filter compiler plus a substantial documentation refresh. No API-shape changes; drop-in over 3.7.0.
-
adapter.applyFilterauto-promotes sort-key comparison ops toKeyConditionExpression.lt/le/gt/geon an indexed sort key now hit the primary index's key-range instead of landing inFilterExpression— range queries like?gt-createdAt=2020now benefit from DynamoDB's key-range optimisation (scan only the qualifying range) instead of the full-partition scan + post-filter path 3.7.0 used. Single-clause-per-key-component enforced: a second clause on the same pk/sk falls back toFilterExpression(DynamoDB rejects over-specified KCE). Previously onlyeqon pk andeq/beg/btwon sk auto-promoted.
-
llms.txt/llms-full.txtdeep refresh. Full coverage of the 3.7.0 surface — constructor options (structuralKey,indices,typeLabels,typeDiscriminator,typeField,technicalPrefix,filterable,versionField,createdAtField,relationships,descriptorKey), methods (typeOf,buildKey,getListUnder,applyFilter,edit/editListByParams,rename,cloneWithOverwrite, cascade primitives,swapPrefix/overlayFields), filter URL grammar (Option W) with op table,MassOpResultenvelope, provisioning + CLI, marshalling usage, OC viaversionField, scope-freeze viaasOf, all error classes, 3.x milestone history. -
Wiki refresh.
Adapter: Hooksgains a "Built-in steps (composed with your hooks)" section documenting the automaticprepare/revive/prepareKeysteps when declarative options are set, plus a "Canned prepare builders" section forstampCreatedAtISO/stampCreatedAtEpoch.Conceptsrewritten with eleven new declarative-schema sections (Structural key, Type tags, Filter URL grammar, Optimistic concurrency, Scope-freeze, Cascade, Mass-op envelope, Descriptor record, Marshalling pairs, Declarative schema umbrella,technicalPrefixand managed fields). NewKey and field designorientation guide covers pk/sk selection, LSI strategy, GSI patterns (all/keys-only/INCLUDE), and a 13-row technical-fields inventory. Four new recipes: Reservation with auto-release (OC + scope-freeze + resumable sweep), Keys-only GSI with runtime projection (indirect-index pattern with cost tables), Cascade subtree operations (deleteAllUnder/cloneAllUnder[By]/moveAllUnder[By]with partial-failure handling), Querying subtrees withbuildKey(children /{self: true}/{partial: 'Dal'}shapes). NewRecipeslanding page with problem-first navigation. -
src/handler/handler.d.ts— stalekeyFromPathdoc comment corrected (adapter.keyFields[0]→adapter.keyFields[0].nameto match the 3.2.0KeyFieldSpecnormalisation). -
AGENTS.md+ AI rule files refreshed for the 3.2–3.7 surface (declarative schema, sub-modules, built-in steps, error hierarchy, CLI). Rule files re-synced to byte-identical mirrors.
554 Node · 548 Bun · 548 Deno · 45 e2e against DynamoDB Local · ts-test 7/19 · npm audit clean · ts-check / js-check / lint green.
Design round driven by the post-3.6.1 ergonomics review on the car-rental example. Breaking — no compat shims. Consolidates the naming, simplifies the most-used surfaces, and fills several rough edges (typed dispatch on every tier, bulk-load versioning, descriptor hiding). See dev-docs/car-rental-feedback.md for the full decision log.
-
Filter grammar flipped. URL:
?<op>-<field>=<value>(e.g.?eq-kind=car>-dailyPriceCents=5000). Thef-prefix is gone; parser gates on a closed op-prefix set (eq|ne|lt|le|gt|ge|in|btw|beg|ct|ex|nx). Left-anchored split, so dashed field names fall out naturally (?eq-rental-name=Dallas→ fieldrental-name). -
Option renames.
options.fFilter→options.filter(structured clauses). Oldoptions.filter(free-form search string) →options.search. Underlying identifiers renamed too:adapter.applyFFilter→applyFilter;parseFFilter→parseFilter; oldparseFilter→parseSearch;buildFilter→buildSearchin theexpressionssubpath. Filenames follow the exports (parse-filter.*,parse-search.*,search.*). -
Polymorphic filter-clause shape. No more
values: [...]array on every clause; the clause is discriminated byop:- no-value ops (
ex,nx) —{field, op}(novalue) - multi-value ops (
in,btw) —{field, op, value: [...]} - single-value ops —
{field, op, value: <scalar>}
- no-value ops (
-
ensureTablesplit into two methods.planTable(adapter)is read-only and returns the plan;ensureTable(adapter)computes the plan, applies it, writes the descriptor, returns{plan, executed, descriptorWritten?}. No more{yes}/{dryRun}flags. CLI mirrors:dynamodb-toolkit plan-table <module>anddynamodb-toolkit ensure-table <module>. -
adapter.buildKeysimplified. Default is children (begins_with(_sk, 'base|')); nokindargument.{partial: 'abc'}narrows to items whose next tier starts withabc.{self: true}is new — includes the row at the supplied key plus descendants, viabegins_withon base without the trailing separator.kind: 'exact'removed (usegetByKeyfor single-record reads). -
putItems(native) now initializesversionField. Bulk-loaded items get_version: 1on first write, matchingpost/putbehaviour. BatchWriteItem doesn't support per-item conditions, so optimistic-concurrency enforcement still requires the per-item path — useputor{strategy: 'sequential'}if OC is load-bearing. -
Descriptor record hidden from list-op Scans. When
descriptorKeyis declared,getListByParams/deleteListByParams/ etc. inject<pk> <> :descriptorKeyinto the FilterExpression on Scans (Queries already exclude it via pk condition). Opt out with{includeDescriptor: true}for introspection tools.
-
typeField— auto-populate the type discriminator. New Adapter option. Built-in prepare step writesadapter.typeOf(item)to the named field on every full-write insert when the field is absent. Pair withtypeDiscriminatorpointing at the same field —typeOfreads back what the built-in wrote. Enables the Pattern-1 sparse-GSI recipe (indices: {'by-kind': {type: 'gsi', pk: 'kind', ...}}) without a custom prepare hook. -
adapter.getListUnder(partialKey, options)— shorthand forgetListByParams(buildKey(partialKey), options). The common children-only case; composebuildKey+getListByParamsdirectly for{self}/{partial}shapes. -
filterableaccepts{ops, type?}shape. E.g.filterable: {year: {ops: ['eq', 'ge', 'le'], type: 'number'}}. Lets non-keyField fields declare their coercion type explicitly; the old[ops]array shape still works. -
stampCreatedAtISO()/stampCreatedAtEpoch()— cannedpreparehook builders exported from the root. Replace the 4-line boilerplate everyasOf-using adapter was copying. Generic inTItem, so they flow through typed Adapter hooks. -
Shorthand for declaration knobs. String forms for
structuralKey: '_sk',typeDiscriminator: 'kind', and indexpk/sk(pk: 'status'≡pk: {name: 'status', type: 'string'}). Matches thekeyFieldsstring shorthand. Full descriptor form still accepted. -
Additive-presence API principles pinned across the round — see
dev-docs/car-rental-feedback.md§Design principles. "Split methods when complex interactions are unavoidable" is the corollary behind theplanTable/ensureTablesplit.
-
examples/car-rental/— every tier (state, facility, car, boat) now carries real data;putItemsbulk-loads the whole hierarchy; marshalling wired through the adapter'sprepare/revivehooks; new §LSI / §GSI sections. -
examples/car-rental/ts/— typed mirror (adapter.ts,seed-data.ts,run.ts). Runs natively on Node 25 (node examples/car-rental/ts/run.ts; no flag, notsx). Strict discriminated-union record type; surfaces the remaining TS rough edges (record types mustextends Record<string, unknown>; twoas AnyRecordcasts for union-narrowing-through-spread). Everything else flows clean.
551 Node · 545 Bun · 545 Deno · 45 e2e against DynamoDB Local · npm audit clean · ts-check / js-check green.
Bug fixes surfaced by the post-implementation ergonomics review against a realistic hierarchical use case (runnable example at examples/car-rental/). All three fixes restore documented intent; no API-shape changes.
-
adapter.buildKeynow emits the partition-key equality on composite keys. Previously the helper returned only the sort-key condition (begins_with(#sk, :v)or#sk = :v), so every resultingQueryfailed withValidationException: Query condition missed key schema element. Now emits#pk = :pkv AND (…sk condition…)via the primitive's existingpkName/pkValueknobs. Existing buildKey tests updated to assert the corrected shape. -
adapter.patch+versionFieldnow conditions on existence by default. Callingpatch(key, partial)without{expectedVersion}previously emittedattribute_not_exists(<pk>)— the wrong semantic for patch (fails on every existing record). Now emitsattribute_exists(<pk>)unlessexpectedVersionis supplied, matching patch's intended "update-if-exists" contract. The version still ADDs +1 via UpdateExpression regardless. Version-conditioned patches ({expectedVersion}) are unchanged. -
getListByParamsnow honorsoptions.fFilter/options.filter/options.asOf. Callers who hand-built query params (e.g., viabuildKey) and passed them togetListByParams(params, options)found these three options silently dropped — onlygetList(options, example, index)honored them via_buildListParams. Moved option-to-params translation intogetListByParamsso both entry points funnel through one application point.getListremains a thin wrapper.
-
examples/car-rental/— runnable end-to-end walkthrough against DynamoDB Local exercising the full 3.6.0 surface (typed declaration,buildKey,f-filter grammar, mass ops with cursor resume, cascade primitives, optimistic concurrency, scope-freeze,typeOfdispatch for multi-type-same-tier,ensureTable/verifyTable, descriptor round-trip). Serves as integration test + documentation-adjacent template. See the Ergonomics review note.
535 Node · 529 Bun · 529 Deno · npm audit clean. Typed + JS checks pass.
Consolidated publish of the hierarchical-use-case workstream (3.2 → 3.6). Tags 3.2.0, 3.3.0, 3.4.0, 3.5.0 exist locally for history; 3.6.0 is the first npm publish since 3.1.2. No load-bearing breaking changes — renamed bulk-individual helpers ship alongside deprecated aliases. The sections below cover what landed in each tag, newest first.
New subpath dynamodb-toolkit/provisioning and CLI bin/dynamodb-toolkit. Opt-in; IaC-managed deployments ignore it entirely.
-
ensureTable(adapter, {yes?, dryRun?})— ADD-only planner. Returns a plan by default ({tableName, steps, summary[]});{yes: true}executes. Plan steps:create(CreateTable),add-gsi(UpdateTable with a single{Create}),skip-extra-gsi/skip-extra-lsi(reported, never dropped),skip-missing-lsi(surfaced with a note — LSIs can only be declared at CreateTable). Never emits destructive operations. Delegates declaration legality to DynamoDB. -
verifyTable(adapter, {throwOnMismatch?, requireDescriptor?})— structured diff. Compares base KeySchema, AttributeDefinitions, GSI/LSI key schemas + projections. Billing mode / stream config compared only when declared on the adapter. Extras in the live table arewarn(non-blocking); declared-but-missing areerror.{throwOnMismatch: true}throwsTableVerificationFailedcarrying the same diffs. -
Opt-in descriptor record via
options.descriptorKeyon the Adapter (typical value'__adapter__').ensureTablewrites a JSON snapshot under__toolkit_descriptor__on the reserved-key record;verifyTablereads it back and flags drift thatDescribeTablecan't see (marshalling fields, searchable mirrors, filterable allowlist, versionField/createdAtField names). Absent descriptor is neutral by default;{requireDescriptor: true}opts into "this table must have been toolkit-provisioned" strictness. -
CLI —
dynamodb-toolkit ensure-table <adapter-module>/verify-table <adapter-module>. Loads the module via dynamic ESMimportand acceptsdefaultor namedadapterexport. Flags--yes,--strict,--require-descriptor,--json,-h/--help. -
New error:
TableVerificationFailed(root re-export).
Tests: unit against a mock client + e2e against DynamoDB Local (Docker, skipped when unavailable).
Developer-primitive cascade for hierarchical deletes / clones / moves. Relationship gate is explicit — the toolkit does not infer cascade scope from composite keyFields alone.
-
options.relationships: {structural: true}on Adapter construction. Validated (requireskeyFields.length > 1+structuralKeydeclared). Forward-compatible shape for future cross-adapter / via-GSI kinds. -
Five cascade primitives — two intentional styles per op (no options-bag overload):
-
deleteAllUnder(srcKey, options)— leaf-first descendant delete +ifExistsself-delete (absent →skipped). -
cloneAllUnder(srcKey, dstKey, options)— root-first prefix-swap subtree clone.options.mapFncomposes after the swap viamergeMapFn. -
cloneAllUnderBy(srcKey, mapFn, options)— mapFn-driven subtree clone; destinations as mapFn dictates (fan-out across different subtrees). -
moveAllUnder(srcKey, dstKey, options)/moveAllUnderBy(srcKey, mapFn, options)— two-phase idempotent copy+delete (shared_subtreeRenamewithrename).
-
-
Throws
CascadeNotDeclaredwhen cascade primitives are called withoutrelationships.structural. -
Default REST handler unchanged.
DELETE /keystays single-row; developers wire cascade endpoints themselves by callingadapter.deleteAllUnder(key)from their handler. -
Resumability:
maxItems/resumeTokenflow through the descendants phase; self-op runs only when pagination completes (and is skipped onresumeTokenforcloneAllUnder*to prevent re-processing).
Optimistic-concurrency pair (versionField + asOf) and the new /marshalling subpath.
-
options.versionField— opt-in OC. RequirestechnicalPrefix. Universal write conditionattribute_not_exists(<pk>) OR <versionField> = :observed; auto-increment on every write;deleteaccepts{expectedVersion}; masseditListByParamsroutesConditionalCheckFailedto a dedicatedconflicts: [{key, reason: 'VersionConflict', sdkError}]bucket. Preserved acrossreviveso callers round-trip versions naturally. -
options.createdAtField+{asOf}mass-op option — scope-freeze. Mass ops accept{asOf: Date | string | number}; the toolkit AND-merges<createdAtField> <= :asOfintoFilterExpression. Requires the adapter to opt in — throwsCreatedAtFieldNotDeclaredotherwise. No auto-write; the user'spreparehook owns the format. -
dynamodb-toolkit/marshalling— standalone helpers in the SDKmarshall/unmarshallnaming family:-
marshallDateISO/marshallDateEpoch— no genericmarshallDatealias (forces ISO-vs-epoch intent at the call site). -
marshallMap(map, valueTransform?)— optional nested-value transform. -
marshallURL—URLinstance round-trip. -
Marshaller<TRuntime, TStored>type pair with concretedateISO,dateEpoch,urlpairs.
-
-
New errors:
CreatedAtFieldNotDeclared.
Cursor-based resumability for list mass ops, plus the edit primitive and two subtree macros.
-
encodeCursor/decodeCursorindynamodb-toolkit/mass(base64url; NodeBufferfast path +TextEncoder/btoafallback for Bun/Deno). -
MassOpOptions(maxItems,resumeToken,ifNotExists,ifExists) +MassOpResult({processed, skipped, failed, conflicts, cursor?}). -
Resumable list ops —
deleteListByParams/cloneListByParams/moveListByParamsreturnMassOpResult; page-boundary cursor emission;ifNotExists/ifExistson clone switch fromBatchWriteItemto per-itemPutItemwithConditionExpression. -
adapter.edit(key, mapFn, {readFields?})— read → shallow-diff →UpdateCommand. ThrowsKeyFieldChangedon keyField mutation;{allowKeyChange: true}opts into auto-promotion to a move. No-op short-circuit suppresses WCU on empty diffs. -
adapter.editListByParams— resumable per-item edit across a scan. -
adapter.rename(from, to, options)— put-if-not-exists (dst) then delete (src); two idempotent phases. -
adapter.cloneWithOverwrite(from, to, options)— delete (dst) then put (dst); source stays intact. -
Canned mapFn builders —
swapPrefix(src, dst),mergeMapFn(...fns)for composing transforms. -
GIGO cleanup: removed
AmbiguousDestination, removedtypeof mapFn !== 'function'guards, removedifNotExists+ifExistsmutual-exclusion check. TS types are the contract. -
New errors:
KeyFieldChanged.
The largest release of the workstream. Makes hierarchical adapters declarative, ships the read-side key-condition helpers, lands the filter grammar, cleans up misleading "List" names.
-
options.technicalPrefix(opt-in) — namespaces adapter-managed fields. Prepare rejects incoming user items touching the prefix; revive strips prefixed fields before the user's hook sees them. -
options.keyFieldsaccepts full descriptors:{name, type?: 'string' | 'number' | 'binary', width?: number}.widthrequired on{type: 'number'}in composite keys (zero-padding). -
options.structuralKey— required for compositekeyFields. Shorthand'_sk'or{name, separator?}(defaults'|'). -
options.indices— single discriminated map{<name>: {type: 'gsi' | 'lsi', pk?, sk?, projection?, sparse?, indirect?}}. LegacyindirectIndices: {name: 1}entries synthesise into minimal{type: 'gsi', indirect: true, projection: 'keys-only'}. -
options.typeLabels(paired 1:1 withkeyFields) +options.typeDiscriminator— poweradapter.typeOf(item)(type detection by discriminator field → structural depth → raw depth). - Built-in prepare / revive steps — structural-key formation on prepare (contiguous-from-start + zero-padding), stripping on revive. User hooks still compose after built-ins.
-
buildKeyCondition(input, params)primitive indynamodb-toolkit/expressions. Follows thebuild<Target>convention + counter-based placeholder naming. -
adapter.buildKey(values, options, params)— ergonomic Adapter method.valuesis an object keyed bykeyFieldsnames (validated contiguous-from-start).options.kind: 'exact' | 'children' | 'partial';options.partialappends after the separator. Mergesparamsexactly likebuildCondition.
-
f-<field>-<op>=<value>grammar replaces the v2prefix/pair conventions. Single prefixf-, noflt-. -
Op vocabulary:
eq,ne,lt,le,gt,ge,in,btw,beg,ct,ex,nx. SQL-standardge/le(not Django'sgte/lte). -
options.filterable: {<field>: [ops]}— allowlist on the Adapter. Unlisted field →BadFilterField; disallowed op →BadFilterOp. -
Multi-value via first-character delimiter with
,fallback (f-cost-in=,1,2,3orf-cost-in=|1|2|3). -
?prefix=absorbed intof-<sk>-beg=…and removed. One grammar.
Triggered by the design-principle audit that distinguished bulk-individual helpers (plural of single-op; caller supplies the set) from list operations (toolkit produces the list).
-
readListByKeys+readOrderedListByKeys→readByKeys(always ordered, length-preserving —undefinedat missing positions per the D2 fix). -
deleteListByKeys→deleteByKeys. -
writeList→writeItems. - Old names remain as deprecated aliases with a one-time
console.warn; removed in a future minor. - File renames:
read-ordered-list-by-keys.js→read-by-keys.js(callers switch);delete-list-by-keys.js→delete-by-keys.js.
getByKeys and the wire -by-names endpoint no longer silently drop missing items. Result array is length-aligned with the input keys: result[i] === undefined at missing positions. Callers who want a compact array call .filter(Boolean) themselves.
NoIndexForSortField, ConsistentReadOnGSIRejected, BadFilterField, BadFilterOp (root re-exports). ToolkitError base class for instanceof discrimination.
- Thin SDK-helper — consumes/produces SDK types, same semantics; introduces toolkit concepts only when strictly needed.
-
No client-side list manipulation — don't sort/filter/reshape lists post-read. Refuse with a clear error when the DB can't answer natively.
?sort=<field>without a matching index now throwsNoIndexForSortFieldinstead of in-memory sorting. -
User callbacks throw; toolkit does not wrap — every caller-supplied extension point (hooks,
mapFn,sparse.onlyWhenpredicate,exampleFromContext,valueTransform) propagates errors unchanged. Caller's error class / message / stack surfaces intact. -
GIGO: no runtime type-checking of args — TS types are the contract; no
typeof !== 'function'guards on mapFn args, etc.
Rest-core extractions + streaming body reader. Purely additive; no breaking changes. Framework adapters (koa / express / fetch / lambda) adopted these extractions at their 0.2.0 release the same day.
Pulled out of the dynamodb-toolkit/handler + dynamodb-toolkit/rest-core internals so framework adapters can share them:
-
buildListOptions(query)— constructs theListOptionsbag from parsed query state (offset/limit/sort/filter/fields). -
resolveSort(sortField, options)— maps?sort=<field>to the resolved index /descendingflag. -
stripMount(path, mountPath)— trailing-slash-safe mount-point stripping. -
coerceStringQuery(raw)— null-prototype accumulator coercing multi-value query shapes to a flat{k: string | string[]}map. -
validateWriteBody(body)— gates POST// PUT/:key/ PATCH/:keyon object-shaped bodies. Returns400 BadBodyonnull, arrays, or primitives. Previously those bodies were silently spread ({...null, ...key}→ key-only row).
-
readJsonBody(req, {maxBodyBytes, destroy?})— streamingTextDecoderper chunk; peak memory ≈ 1× body size (was 3× with the Buffer-concat path). Partial UTF-8 codepoints across chunk boundaries handled by the streaming decoder. -
matchRouteHEAD → GET auto-promote —HEAD /keynow matches theGET /keyroute (unless a dedicated HEAD handler is registered). -
Body-always-parsed invariant —
PUT /-clone/PUT /-movealways receive the parsed body inexampleFromContext, even when the body is empty. -
Byte-accurate
maxBodyBytes— limit applied to raw bytes, not character count.
Three CodeQL alerts cleared (workflow permissions, two ReDoS candidates).
Security + correctness hardening sweep. Full module-by-module audit — 19 bug fixes + 9 defensive improvements. All changes are either bug fixes restoring the documented / intended contract, or additive opt-in options with sensible defaults — so this is a patch release.
-
Prototype pollution guards (4 vectors closed):
-
paths/getPathno longer reads through the prototype chain.getPath({}, 'toString')now returnsdefaultValueinstead ofFunction.prototype.toString. -
paths/setPath/paths/deletePathrefuse to walk or write through__proto__/constructor/prototypesegments. -
rest-core/parsePatchuses a null-prototype accumulator — a PATCH body containing"__proto__"no longer reassigns the patch's prototype. -
mass/readOrderedListByKeysusesObject.create(null)accumulators — a record with partition-key value'__proto__'no longer pollutesObject.prototypeglobally.
-
-
DoS-on-self vectors closed (3):
-
batchWrite/batchGetcap at 8 retry attempts (was infinite loop on persistent throttling). -
handlerrejects bodies larger than 1 MB with HTTP 413PayloadTooLarge(was unbounded memory accumulation). -
parsePagingcaps?offset=at 100 000 by default (was unbounded —?offset=1e15could DoS via astronomical skip-page calls).
-
-
AWS-aligned retry backoff:
backoff()default per-retry cap is now 20 s (was undocumented 60 s with infinite retries). Matches AWS SDK v3 default and AWS DynamoDB retry guidance ("stop around one minute"). WithMAX_ATTEMPTS = 8, worst-case total wait is ~43 s.
-
Adapter.moveByKeysandmass/moveListnow pairput+deleteper item. Previously, a falsymapFnreturn could drop the put but leave the delete in flight — silent data loss. Now both legs drop together. -
Adapterconstructor rejects non-arraykeyFields.new Adapter({..., keyFields: 'name'})previously silently produced broken queries (attribute alias'n'); now throws. -
applyBatch/applyTransactionthrow on unknownactionvalues (were silently dropped — caused phantom "processed" counts and missing writes). -
buildUpdatethrows on unknownarrayOp.opvalues (typos like'increment'were silently dropped, producing partial UpdateExpressions). -
buildUpdateguardsoptions.deleteandoptions.arrayOpswithArray.isArray(string input no longer iterates characters). -
applyBatchtotalcounts only items actually added to the batch (was inflating the count with dropped items). -
normalizeFields(',,,')now returnsnullinstead of[](restores the "no fields requested → project everything" contract). -
applyPatch(o, null)returnsounchanged instead of crashing onObject.keys(null). -
applyPatchoptions.deletenow gated withArray.isArray. -
cloneParams(null)returns a fresh clone instead of crashing. -
handlerstrips leading//fromreq.urlsoGET //evil.com/pathcannot pivot the URL's origin; sanitizes theHostheader to prevent crashes on malformed headers. -
handler405 error body no longer echoesurl.pathname(minor information disclosure removed).
-
createHandler(adapter, {maxBodyBytes})— HTTP body-size cap. Default 1 MB. -
parseFields(input, {maxItems})— output-length cap. Default 1000. -
parseNames(input, {maxItems})— output-length cap. Default 1000. -
parseFilter(input, {maxLength})— query-string length cap. Default 1024. -
parsePaging(input, {maxOffset})— offset ceiling. Default 100 000. -
defaultPolicy.maxOffsetadded (same default).
| Change | Before | After |
|---|---|---|
backoff() default cap |
undocumented 60 s | 20 s |
batchWrite / batchGet attempts |
infinite retry | 8 (throws after) |
Unknown action in applyBatch / applyTransaction
|
silent drop | throws |
Unknown arrayOp.op in buildUpdate
|
silent drop | throws |
Adapter with non-array keyFields
|
silently accepted (broken) | throws |
| Handler request body > 1 MB | unbounded | 413 PayloadTooLarge
|
?offset= on list routes |
unbounded | capped at 100 000 |
getPath({}, 'toString') |
Function.prototype.toString |
defaultValue |
See the per-page wiki updates (REST core, HTTP handler, Batch and transactions, Mass operations) for the detailed surface.
Additive features. No breaking changes.
-
returnFailedItemoption onpost/put/patch/deleteand theirmake*batch builders. SetsReturnValuesOnConditionCheckFailure: 'ALL_OLD'; the thrownConditionalCheckFailedExceptioncarries the colliding item on itsItemfield (or the matchingCancellationReason.Iteminside a transaction). -
{options: ...}sentinel forapplyTransaction— position-independent descriptor carrying transaction-level knobs:clientRequestToken(idempotency),returnConsumedCapacity,returnItemCollectionMetrics. -
explainTransactionCancellation(err, ...descriptors)in/batch. MapsTransactionCanceledException.CancellationReasonsback to the input descriptors and returns{failures, message} | null. Paired withreturnFailedItemto surface the colliding item in one call. -
Cross-runtime test matrix — Node, Deno, Bun (
npm test,npm run test:deno,npm run test:bun). - CJS smoke test + TypeScript smoke test wired into the suite.
-
LICENSE file shipped (BSD-3-Clause).
llms.txt+llms-full.txtincluded in the npm tarball. - CI lint step re-enabled (
.github/dependabot.ymlin.prettierignore). - Three CodeQL alerts cleared (workflow permissions, two ReDoS candidates).
Typed .d.ts sidecars updated throughout. See Adapter: CRUD methods, Batch and transactions, and Compatibility for the detailed coverage.
Green-field rewrite on AWS SDK v3. Not a drop-in upgrade from v2. v2 consumers stay on dynamodb-toolkit@2.3.0. The v3 line is the actively-developed branch.
Highlights:
-
AWS SDK v3 (
@aws-sdk/client-dynamodb+@aws-sdk/lib-dynamodb) peer deps, replacing v2'saws-sdk. -
ESM-only,
"type": "module", hand-written.d.tssidecars next to every.js. No build step. -
One data format — plain JS via
lib-dynamodbmiddleware. The v2Raw/DbRawpair collapsed into a singleRaw<T>bypass marker. -
Options bags everywhere:
put(item, {force: true})instead ofput(item, true);patch(key, patch, {delete, separator, arrayOps, conditions}). -
Hooks renamed:
prepareListParams→prepareListInput,updateParams→updateInput. Hooks bag (options.hooks) is the canonical extension point. -
Patch wire format:
_delete/_separator(single underscore); configurable viapolicy.metaPrefix. -
REST split:
dynamodb-toolkit/rest-coreis framework-agnostic;dynamodb-toolkit/handleris thenode:httpadapter. Koa / Express / Hono / Fetch / Lambda adapters live in separate packages. -
Transaction auto-upgrade: single-op CRUD automatically upgrades to
TransactWriteItemswhencheckConsistencyreturns extra descriptors. - Indirect-index second-hop for sparse GSIs with key-only projection.
- Dropped:
makeClient,getProfileName,specialTypes,converter,converterOptions— use@aws-sdk/credential-providersdirectly.
See Migration: v2 to v3 and SDK v2 to v3 cheat sheet.
The v2 documentation snapshot lives at the v2.3-docs git tag of this wiki repo. The v2 source code remains on npm as dynamodb-toolkit@2.3.0 and on GitHub at the matching git tag.
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