You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Breaking (Rust API): the public partiql::parser::Statement enum gained a returning field on its Update and Delete variants, and both variants are now #[non_exhaustive]; the public actions::batch_execute_statement::BatchStatementResponse struct gained a table_name field and is now #[non_exhaustive] too. Library consumers that construct or exhaustively match these types must add ... This is a source-breaking change for the crate's public API, so the next release is a minor bump (0.12.0). The DynamoDB wire API and the CLI/server/MCP surfaces are unaffected.
Added
PartiQL now honours the RETURNING clause on ExecuteStatement, where dynoxide previously parsed the statement but silently dropped the clause. DELETE ... RETURNING ALL OLD * returns the deleted item in Items (a present but empty Items array on a missing target, matching DynamoDB rather than the classic DeleteItem path), and UPDATE ... RETURNING <ALL|MODIFIED> <OLD|NEW> * returns the matching projection of the item; the MODIFIED variants return only the changed paths (a nested SET a.b returns just the changed leaf, not the whole a attribute), exclude the primary key, and return an empty Items array when nothing was projected. BatchExecuteStatement honours a member's RETURNING clause; ExecuteTransaction rejects one with a top-level ValidationException. The RETURNING variants DynamoDB does not allow on DELETE (MODIFIED OLD *, ALL NEW *, MODIFIED NEW *) are rejected with its exact validation message instead of being ignored (#137).
A test-only HTTP server for the wasm engine, npm run wasm:serve. It exists so the conformance suite can reach the browser build over a socket. It is not a way to run dynoxide and is deliberately not distributed: not in the release binary, not on npm. To run dynoxide, use the native build. It drives the shipping dist/ bundle in a headless Chromium it installs itself and serves DynamoDB JSON-1.0 on port 8003, with one browser page's worth of concurrency and no TLS. Each start is a fresh in-memory database. The engine gained a dispatchHttp worker op so the wire envelope is decided there instead of in the transport, and an operation the preview does not implement returns HTTP 501. See docs/wasm.md.
dynoxide serve and dynoxide (no-subcommand) now accept a --schema flag, taking the same DynamoDB DescribeTable JSON format as import --schema. On startup, dynoxide creates each table defined in the file and skips any that already exist. This lets you pre-populate an empty database (in-memory or persistent) with the correct table structure without running an import first.
Fixed
OnDemandThroughput now follows real DynamoDB's semantics on every surface, captured against eu-west-2: CreateTable and UpdateTable both reject it when the effective billing mode is PROVISIONED (each operation with its own captured wording, naming the first present member, read checked first), members must be at least 1 (-1 is valid only on UpdateTable, where it removes that ceiling), a partial UpdateTable object merges member-wise over the stored ceilings instead of replacing them, the UpdateTable response echoes the merge with -1 kept verbatim while DescribeTable reports the post-removal state, and switching billing mode to PROVISIONED clears the stored ceilings. The billing gate fires before the range check on both operations, and an OnDemandThroughput object with no members is treated as absent: real DynamoDB accepts one at creation and returns InternalFailure for one on UpdateTable, which dynoxide deliberately replaces with its deterministic no-change validation error rather than emulating a 500. dynoxide previously stored whatever it was given, on any billing mode, and replaced wholesale on update (#159).
The MCP describe_table default view now includes billing_mode, table_class and the capacity settings for the mode the table is in (provisioned_throughput or on_demand_throughput), where it previously showed none of the table configuration fields and only the raw: true view carried them.
The MCP create_table and update_table tools now accept on_demand_throughput, so the on-demand ceilings that HTTP and wasm could already set and round-trip are reachable over MCP too (#157).
The MCP update_table tool now accepts billing_mode, provisioned_throughput and table_class, so a table can be switched between PROVISIONED and PAY_PER_REQUEST or moved to STANDARD_INFREQUENT_ACCESS over MCP, as it already could over HTTP and wasm. The handler previously hardcoded all three to none, so the engine saw an empty update (#156).
The MCP create_table tool now accepts billing_mode and provisioned_throughput; it previously had neither, so every table created over MCP was PROVISIONED with default throughput. An invalid billing mode is now rejected with DynamoDB's enum validation message on every surface, not just over HTTP (#154).
import --schema and serve --schema no longer drop BillingMode and TableClass from a DescribeTable response. DescribeTable wraps both in summary objects (BillingModeSummary, TableClassSummary) that the rebuilt CreateTableRequest never read, so an on-demand table came back PROVISIONED and STANDARD_INFREQUENT_ACCESS came back STANDARD. The schema path now unwraps both summaries and, when the billing mode came from the summary, drops the zeroed ProvisionedThroughput blocks DescribeTable reports for an on-demand table and its GSIs, which CreateTable would otherwise reject as zero capacity units. A table's own DescribeTable output round-trips without degrading or failing, a provisioned table's capacity values survive intact, and a schema already in CreateTable shape passes through untouched, so an inconsistent one still fails validation exactly as it would on the CreateTable API (#140).
PutItem and UpdateItem validation errors now carry DynamoDB's 1 validation error detected: envelope on exactly the request-validation families real DynamoDB envelopes: empty and duplicate sets, {"NULL": false} in any position (item body, key or expression attribute values), expression syntax and oversize errors, redundant parentheses, the distinct-operand rule for contains, expression parameter misuse (ExpressionAttributeValues without an expression, mixing Expected with ConditionExpression), and invalid ReturnValues. Data-plane, structural and limit families stay bare, matching DynamoDB: key and index-key type mismatches, cannot-update-key, invalid document paths, references to missing attributes, empty-string key values, empty or multi-typed AttributeValue objects, and oversized items. dynoxide previously enveloped only its constraint-collection path and left the rest bare. The split is classified per family at the raising site, never by matching message text, so an attribute value that echoes a bare-family phrase cannot shed its envelope, and the message is identical on every surface: HTTP, wasm, MCP and the in-process Rust API, whose errors gain the prefix for these families. Deserialisation failures on the wasm and MCP surfaces now classify the same way the HTTP server does, instead of leaking an internal marker inside a mis-typed SerializationException. Read operations are unchanged and their expression errors stay bare. Confirmed against real DynamoDB in eu-west-2.
PartiQL UPDATE now performs real list-index writes. SET tags[0] = :v updates the list element (appending when the index is at or beyond the end) and REMOVE tags[0] deletes it and shifts the rest, where dynoxide previously treated tags[0] as a literal map key so both the stored item and a RETURNING MODIFIED projection over it diverged from DynamoDB. A RETURNING MODIFIED projection over list-index paths now packs the changed elements into a dense list in ascending index order (SET a[0], a[2] yields {a: [v0, v2]}), matching DynamoDB.
BatchExecuteStatement now echoes TableName on each successful member response, and a member that fails to parse now carries the short-form ValidationError code (matching a per-statement execution error) instead of the long-form ValidationException. Both match DynamoDB.
PartiQL UPDATE on a non-existent key now fails with ConditionalCheckFailedException (The conditional request failed) and creates nothing, where dynoxide upserted the item. UPDATE is not an upsert: the target must already exist, matching DynamoDB.
PartiQL parse errors now use DynamoDB's message wording: Statement wasn't well formed, can't be processed: <detail> (previously ... got error: ...), and a statement that does not begin with a DML keyword reports Expected data manipulation. Applies to ExecuteStatement, BatchExecuteStatement, and ExecuteTransaction.
A { "NULL": false } attribute value is now rejected with the ValidationException real DynamoDB returns (One or more parameter values were invalid: Null attribute value types must have the value of true), where dynoxide normalised it to { "NULL": true } and accepted it. The NULL member must be exactly true; this specific input flipped behaviour across AWS regions and has since settled on rejection everywhere. The fix covers the item body and the DeleteItem raw expression-value path, where the rejection had surfaced as a mis-typed SerializationException leaking an internal prefix rather than the plain ValidationException. Confirmed against real DynamoDB in eu-west-2 (#145).
UpdateTable adding a global secondary index now validates the index's key attributes against the request's own AttributeDefinitions, where dynoxide resolved them from the merged stored set and so accepted a new index keyed on an existing table attribute the request did not re-declare. DynamoDB requires a new index's key attributes to appear in the request itself and rejects the omission with One or more parameter values were invalid: Some index key attributes are not defined in AttributeDefinitions. An unused definition supplied in the delta is still dropped, and a redeclared attribute still keeps its stored type. Confirmed against real DynamoDB in eu-west-2 (#144).
An expression parameter over 4096 bytes is now rejected with DynamoDB's Invalid <Type>Expression: Expression size has exceeded the maximum allowed size message, where dynoxide parsed it regardless of length. The length is measured on the raw string as sent, before name and value substitution. The guard covers every expression surface, each carrying the surface-specific Invalid <Type>Expression: prefix: UpdateExpression, ConditionExpression, FilterExpression (Query and Scan), ProjectionExpression, and KeyConditionExpression; on the key-condition surface the size check runs before parsing, so an oversized key condition is rejected for size even when otherwise malformed. Confirmed against real DynamoDB in eu-west-2 (#146).