---
title: "Dynoxide 1.4.0: clean shutdowns and JSON Lines import - Martin Hicks"
description: "Dynoxide 1.4.0 adds JSON Lines import, a clean way to stop an embedded server, and closer DynamoDB behaviour for updates and scans. What changed and what to check before upgrading."
canonical: https://martinhicks.dev/articles/dynoxide-140-release-notes
last-updated: 2026-10-21
---

![The Dynoxide hexagonal logo centred on a near-black background](https://martinhicks.dev/images/articles/dynoxide-0120-release-notes.png)21st October 2026

# Dynoxide 1.4.0: clean shutdowns and JSON Lines import

[Dynoxide](https://dynoxide.dev) 1.4.0 is a smaller release than [1.3.0](https://martinhicks.dev/articles/dynoxide-130-release-notes). There's a community contribution, a new way to stop an embedded server, and another round of fixes that bring updates and scans closer to DynamoDB.

## JSON Lines import, thanks to Sam Chung

[Sam Chung (@samchungy)](https://github.com/samchungy) was testing import, noticed that the `.json` files it expects are really JSON Lines, and renamed them to `.jsonl` to get editor support, then [added support for them](https://github.com/nubo-db/dynoxide/pull/250).

`dynoxide import` now reads `.jsonl` and `.jsonl.gz` as well as `.json` and `.json.gz`. The files can sit in a DynamoDB export layout (`<dir>/<TableName>/data/`) or in a flat directory.

## Stopping an embedded server

If you run Dynoxide's HTTP server inside your own Rust code, the only way to stop it used to be aborting its task. That left connection tasks holding the database, so a file-backed database's files stayed open, and Windows won't delete an open file.

`dynoxide-rs` 3.1.0 adds `server::serve_with_shutdown`, which takes a future as its stop signal. Once that completes, the server stops taking new connections, closes idle keep-alive ones, waits for running requests and returns:

```rust
let (stop, stopped) = tokio::sync::oneshot::channel::<()>();
let server = tokio::spawn(dynoxide::server::serve_with_shutdown(listener, db, async {
    let _ = stopped.await;
}));

// ... run your tests against the server ...

let _ = stop.send(());
server.await??;
```

The last connection can let go of the database a moment after the call returns, so delete files with a short retry. The doc comment covers that and a couple of other caveats. `dynoxide start` runs on this now too, with Ctrl-C and SIGTERM working as before.

## Closer to DynamoDB

Most of the rest is parity work on `UpdateItem`, PartiQL `UPDATE`, `Scan` and `Query`. The changes most likely to show up in a test:

-   **An update path needs its parent.** PartiQL `SET zz.y = 1` used to create `zz`. It's now refused, as DynamoDB refuses it.
-   **Every clause reads the item as it was.** `REMOVE l[0], l[1]` removes both elements, and `SET n = 7, s = n + 1` gives `s` the old `n` plus one.
-   **A missing attribute isn't zero.** Arithmetic and `list_append` on a missing or wrong-typed operand now return DynamoDB's error.
-   **Scan validation matches the newer DynamoDB.** The [validation rollout I caught in June](https://martinhicks.dev/articles/dynamodb-changed-its-validation-errors) has reached 20 of the 33 AWS regions, and Dynoxide's Scan now follows its error order and wording ([#254](https://github.com/nubo-db/dynoxide/issues/254)).

There were also two parallel Scan bugs that returned the wrong items:

-   **`TotalSegments` above 4,096** returned almost every item from the segments past the last hash bucket. Each item now comes back once.
-   **A scan that deletes each page as it goes** could get an empty page with no `LastEvaluatedKey` once its start key's item had been deleted. It now sees its whole segment.

The release notes also list `rustls` and `rustls-webpki` updates for published advisories. Those only reach Dynoxide through test dependencies, so nothing shipped is affected.

## Before upgrading

-   **The Rust API validates like the HTTP one.** `Database::scan` and `Database::query` now refuse a `Limit` of 0, and `scan` refuses an out-of-range `Select`, `ReturnConsumedCapacity` or `Segment`. An empty `UpdateExpression` comes back as `DynoxideError::EnvelopedValidation` rather than `ValidationException`, with the same message.
-   **`dynoxide mcp` refuses `--max-items 0`** at startup, and its scan and query tools refuse a limit of 0.
-   **The crate is 3.1.0.** The product is 1.4.0, and `dynoxide-rs = "3.1"` picks up the new function.

The [1.4.0 release notes](https://github.com/nubo-db/dynoxide/releases/tag/v1.4.0) have the full list. For an npm project, `npm install --save-dev dynoxide@1.4.0` pins this release.

## Further reading

-    [Dynoxide 1.3.0: security fixes and fussier pagination
    
    Dynoxide 1.3.0 fixes two security issues, matches more of DynamoDB's query quirks, and corrects vector capacity accounting. What caught my attention, and what to check before upgrading.](https://martinhicks.dev/articles/dynoxide-130-release-notes)
-    [I watched DynamoDB change under my conformance suite
    
    My DynamoDB conformance suite went red and the test that failed was real AWS. It had reworded its validation errors, and the change hasn't reached every region yet.](https://martinhicks.dev/articles/dynamodb-changed-its-validation-errors)
-    [Building a DynamoDB conformance suite
    
    How I built a 526-test DynamoDB conformance suite, and what it found in DynamoDB Local, LocalStack, Dynalite, and Dynoxide.](https://martinhicks.dev/articles/dynoxide-conformance-suite)

---

Source: https://martinhicks.dev/articles/dynoxide-140-release-notes
