Skip to content

better-content 0.8.0

Choose a tag to compare

@Invalid8 Invalid8 released this 06 Sep 15:39
· 24 commits to main since this release

seedItemMap, the write mirror of loadItemMap

import { seedItemMap } from "better-content/server";

await seedItemMap(data, {
  portfolio: sections,                          // byId, the default
  projects: { items: projects, mode: "replace" },
});
  • "byId" replaces the named records and leaves everything else in the collection alone.
  • "replace" makes the seeded array the collection: records in the backend but absent from the seed are deleted.

The mode is per collection because a real seed needs both in one run — singletons written by id, lists replaced wholesale. A bare Item[] is accepted, so an ItemMap is already a valid argument and seedItemMap(target, await loadItemMap(source, …)) copies an environment.

Why it exists

Not for the loop, which is a dozen lines. For the things a hand-rolled seed has to know:

Each record is written as delete then createWithId, because neither verb expresses a portable replace on its own. createWithId rejects an existing id, and upsert merges rather than replaces and does not stamp createdAt — which on Firestore leaves the record invisible to a default read that orders by that field. A seed written the obvious way, as a loop of upsert calls, looks fine on Postgres and silently writes unreadable data on Firestore.

Stated rather than left to be discovered

  • Two round trips per record. Seeding is an offline one-shot.
  • createdAt is reset for a record that already existed. Correct for a replace.
  • Not atomic. The seam has no batch or transaction. Writes run sequentially in a deterministic order and the first failure throws, naming the collection, the id and how many writes landed — and saying explicitly when the record was deleted before the write failed, since it is then gone rather than merely unwritten.

There is deliberately no "merge" mode (a migration concern, and the one shape carrying the createdAt trap), and ordering is untouched with no order field invented.

Fixed

id and collection are treated as a record's address rather than its fields, so neither is written into the document. This matters across backends: the Postgres adapter adds a collection field to every row it reads and the Firestore adapter does not, so copying a Postgres-sourced ItemMap into Firestore would otherwise have stored collection as a real field in every document. The divergence between the two adapters' read shapes is not changed here.

Docs: server API

Requires create-better-content@0.7.0 for newly generated apps to pick this up.