@zamatica/doc-store
v0.1.0
Published
Document persistence with format-by-extension dispatch (JSON5 / YAML). File-backed store at the root; git-backed (isomorphic-git, local-only) at /git with commit + history + diff + restore; NestJS adapter at /nestjs.
Readme
@zamatica/doc-store
Document persistence with format-by-extension dispatch (JSON5 / YAML). File-backed at the root; git-backed (via isomorphic-git, local-only) at /git with commit + history + diff + restore; NestJS adapter at /nestjs.
Subpaths
@zamatica/doc-store—DocStoreinterface +createFileDocStore. Pure filesystem.@zamatica/doc-store/git—createGitDocStorewithcommit / history / diff / restoreon top of a local git repo. Usesisomorphic-gitso it works inside Bun's single-file binary and Electrobun's packaged runtime with nogitsubprocess.@zamatica/doc-store/nestjs—DocStoreModule.forRoot({ store }).
Format dispatch
Format is chosen by the doc-id extension. The caller never passes { format } — extension is the source of truth.
| Extension | Format | Use for |
|---|---|---|
| .json5 | JSON5 | machine-only documents (snapshots, generated state, internal queues) |
| .yaml / .yml | YAML | human-editable documents (workflows, configs the operator may hand-tune) |
Unsupported extensions throw on every call — no silent fallback. Add formatForId cases to the lib if you need more.
Interface
interface DocStore {
read<T>(id: DocId): Promise<T | undefined>;
write<T>(id: DocId, doc: T): Promise<void>;
delete(id: DocId): Promise<void>;
list(prefix?: string): Promise<readonly DocId[]>;
}
interface GitDocStore extends DocStore {
commit(message: string, opts?: { author?: GitAuthor }): Promise<{ sha: string; changed: boolean }>;
history(id: DocId, limit?: number): Promise<readonly DocRev[]>;
diff(id: DocId, from: string, to?: string): Promise<string>; // unified diff
restore(id: DocId, ref: string, opts?: { commit?: boolean }): Promise<void>;
}Use
import { createGitDocStore } from '@zamatica/doc-store/git';
const docs = await createGitDocStore({
repoDir: '/etc/mtz/docs',
defaultAuthor: { name: 'mtz-daemon', email: '[email protected]' },
});
await docs.write('flows/onboarding.yaml', { name: 'foo', steps: ['a', 'b'] });
const { sha, changed } = await docs.commit('add onboarding flow');
// Later — revision history + diff + restore.
const revs = await docs.history('flows/onboarding.yaml');
const diff = await docs.diff('flows/onboarding.yaml', revs[1]!.sha);
await docs.restore('flows/onboarding.yaml', revs[1]!.sha, { commit: true });NestJS wiring
import { DocStoreModule } from '@zamatica/doc-store/nestjs';
const docs = await createGitDocStore({ repoDir, defaultAuthor });
@Module({
imports: [DocStoreModule.forRoot({ store: docs })],
})
export class AppModule {}Consumers @Inject(DOC_STORE) private docs: DocStore (the GitDocStore extension methods are available too — cast or inject as the wider type). The module is global: true.
On disk
- Each doc is a plain file at
<repoDir>/<doc-id>. Extension preserved. - Writes are atomic (sibling
.tmp+ rename) so readers never observe a half-written doc. - The git repo lives at
<repoDir>/.git—list()skips dotfiles so the doc-store enumeration doesn't surface metadata as documents. deleteon a missing doc is a no-op;readreturnsundefined;liston a missing root returns[].
Caveats
- Local-only git in v1. No push/pull, no remote sync, no credential handling. Descendants needing remote backup shell out to
gitthemselves. - Diff is a minimal line-level LCS, not
git diff's full output (no rename detection, no whitespace-mode flags). Fine for "show me what changed in this doc between revisions" UI; insufficient for code-review tooling. - isomorphic-git history per-file is O(commits) because there's no built-in "log per-file" primitive — we walk every commit and compare blobs. Fast enough for the typical document-storage use case (<100 docs, <1000 commits).
Status
v0.x. The interface is stable; the impl is built for correctness-first then ergonomics. Hot-spot optimizations (history caching, diff library swap) are deferred until a real workload demands them.
