Skip to main content

Interface: ArtifactStore

Defined in: packages/core/src/artifact.types.ts:188

The storage contract behind publishing: implement it over Postgres, S3, or anything else, and every part of Nubbin that publishes, rolls back or checks routes works against it. Two kinds of state sit behind it — artifacts keyed by content hash, written once and never changed, and one pointer per route, which is the only thing that moves.

Callers order the two: write the artifact, then publish the route at its hash. An implementation holds up its half by making absence a value rather than a failure, by taking a repeated write as a no-op and a repeated publish as an ordinary one so a retried publish succeeds, and by writing each pointer whole — two publishes racing for one route must leave one of them intact, never a blend. @nubbin/store-fs is the reference implementation, and runArtifactStoreContract from @nubbin/store-fs/testing is the suite every implementation is expected to pass — call it with a factory for your store and the guarantees above are executed rather than read. It needs vitest, which the package declares as an optional peer.

Example

import { parseMatchKind } from "@nubbin/core";
import type { ArtifactStore } from "@nubbin/core";

const store: ArtifactStore = {
read: async (hash) => (await db.artifact(hash)) ?? null,
write: async (artifact) => { await db.putArtifactIfAbsent(artifact.hash, artifact); },
manifest: async () => ({ routes: await db.pointers(), generatedAt: new Date().toISOString() }),
pointer: async (route) => (await db.pointer(route)) ?? null,
publish: async (route, hash) => { await db.movePointer(route, hash, parseMatchKind(route)); },
unpublish: async (route) => { await db.deletePointer(route); },
};

Methods

history()?

optional history(route): Promise<PointerMove[]>

Defined in: packages/core/src/artifact.types.ts:291

Every move publish made at this route, oldest first, surviving unpublish. Optional because a write-only blob store is still a valid adapter — a caller degrades with a message rather than requiring it.

Parameters

route

string

The route whose moves are read.

Returns

Promise<PointerMove[]>

One entry per publish, oldest first, and an empty array for a route that has never been published. A store that keeps no history omits the method rather than returning [], so a caller can tell "never published" from "not recorded".

Example

const moves = (await store.history?.("/pricing")) ?? [];
moves.at(-1)?.hash; // what it points at now

manifest()

manifest(): Promise<Manifest>

Defined in: packages/core/src/artifact.types.ts:226

Lists every published route.

Returns

Promise<Manifest>

A snapshot of every pointer the store holds, with the time it was taken. An empty routes is a store with nothing published, not a failure.

Example

const { routes } = await store.manifest();

pointer()

pointer(route): Promise<RoutePointer | null>

Defined in: packages/core/src/artifact.types.ts:240

Reads the pointer for one route.

Parameters

route

string

The route as it was published, matched exactly — /pricing does not find /pricing/, and a param or prefix pointer is found by its pattern, not by a path it would match.

Returns

Promise<RoutePointer | null>

The pointer, or null when nothing is published at that route.

Example

const pointer = await store.pointer("/guides/[city]");

publish()

publish(route, hash): Promise<void>

Defined in: packages/core/src/artifact.types.ts:262

Points a route at an artifact already in the store — the publish, and the rollback. The implementation derives matchKind with parseMatchKind and stamps updatedAt itself.

Parameters

route

string

The route to serve. It is validated on the way through parseMatchKind.

hash

string

The artifact to serve there. It has to be written first.

Returns

Promise<void>

Nothing. Publishing the same route and hash twice succeeds and leaves the route where it already pointed, which is what makes a retry safe — it is not a no-op. The pointer is rewritten and a store keeping history records a second move, since what is deduplicated is the artifact rather than the act of publishing.

Throws

NubbinError with code NubbinIssueCode.InvalidRoute when the route is not addressable, from parseMatchKind. An implementation also rejects a hash it holds no artifact for, so no pointer can name one that was never written — @nubbin/store-fs refuses that with NubbinIssueCode.ArtifactNotStored.

Example

await store.write(artifact);
await store.publish("/pricing", artifact.hash);

read()

read(hash): Promise<Artifact | null>

Defined in: packages/core/src/artifact.types.ts:200

Reads one artifact by its content hash.

Parameters

hash

string

The hash of an artifact already written.

Returns

Promise<Artifact | null>

The artifact as written, or null when the store holds nothing at that hash. Absence is a value here — an unknown hash never throws.

Example

const artifact = await store.read("4a162726");

unpublish()

unpublish(route): Promise<void>

Defined in: packages/core/src/artifact.types.ts:275

Takes a route offline by removing its pointer. The artifact stays, so republishing it is another publish at the same hash.

Parameters

route

string

The route to stop serving.

Returns

Promise<void>

Nothing. Unpublishing a route that has no pointer is a no-op.

Example

await store.unpublish("/pricing");

write()

write(artifact): Promise<void>

Defined in: packages/core/src/artifact.types.ts:214

Stores one artifact under its own hash. Write before publishing the route at it.

Parameters

artifact

Artifact

A compiled artifact. artifact.hash is the key; nothing else is read.

Returns

Promise<void>

Nothing. Writing a hash the store already holds is a no-op, so a publish retried after a timeout succeeds on its second attempt.

Throws

Whatever the underlying storage raises when the write itself fails.

Example

await store.write(compile(version, catalog, registry, "/pricing").artifact);