Repository navigation
better-content 0.8.0
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.
createdAtis 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.