@bluewombat/persist-fs 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # persist-fs
2
+
3
+ Persistence Port for WorkLedger: one JSON file per FeatureStandard key, atomic
4
+ rename on save. The ledger never imports this package. Glue does.
5
+
6
+ ```ts
7
+ import { openWorkLedger } from "@bluewombat/work-ledger";
8
+ import { openFilesystemPersist } from "@bluewombat/persist-fs";
9
+
10
+ const persist = await openFilesystemPersist({ root: "/absolute/path/to/ledger" });
11
+ const ledger = openWorkLedger({ persist });
12
+ ```
13
+
14
+ This is what Host uses when a config says nothing (`"persist"` unset): it calls
15
+ `openPersist({ ledgerRoot })`, one JSON file per key directly under the root.
16
+
17
+ `root` must be absolute. The adapter creates it if missing.
18
+
19
+ ## Stack
20
+
21
+ Repository stack — TypeScript, Node 24.20, Biome, `node:test`. See [docs/DEVELOPMENT.md](../../../docs/DEVELOPMENT.md).
22
+
23
+ ## Commands
24
+
25
+ | Task | Command |
26
+ | --------- | ---------------- |
27
+ | Lint | `npm run lint` |
28
+ | Format | `npm run format` |
29
+ | Typecheck | `npm run tsc` |
30
+ | Test | `npm test` |
31
+ | Build | `npm run build` |
@@ -0,0 +1,4 @@
1
+ export type { FilesystemPersistOptions } from "./open-filesystem-persist.js";
2
+ export { openFilesystemPersist } from "./open-filesystem-persist.js";
3
+ export { openPersist } from "./open-persist.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAC7E,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { openFilesystemPersist } from "./open-filesystem-persist.js";
2
+ export { openPersist } from "./open-persist.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { PersistencePort } from "@bluewombat/work-ledger";
2
+ export type FilesystemPersistOptions = {
3
+ root: string;
4
+ };
5
+ /**
6
+ * Filesystem Persistence Port. One JSON file per key. Atomic rename on save.
7
+ * Does not know FeatureStandard transitions.
8
+ */
9
+ export declare function openFilesystemPersist(options: FilesystemPersistOptions): Promise<PersistencePort>;
10
+ //# sourceMappingURL=open-filesystem-persist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open-filesystem-persist.d.ts","sourceRoot":"","sources":["../src/open-filesystem-persist.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAGV,eAAe,EAEhB,MAAM,yBAAyB,CAAC;AAEjC,MAAM,MAAM,wBAAwB,GAAG;IACrC,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAgCF;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,eAAe,CAAC,CA+D1B"}
@@ -0,0 +1,95 @@
1
+ import { mkdir, readdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
2
+ import { isAbsolute, join } from "node:path";
3
+ function fileNameFor(key) {
4
+ return `${encodeURIComponent(key)}.json`;
5
+ }
6
+ function isAggregate(value) {
7
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
8
+ return false;
9
+ }
10
+ const record = value;
11
+ return (typeof record.state === "string" &&
12
+ record.intention !== null &&
13
+ typeof record.intention === "object");
14
+ }
15
+ function toSummary(aggregate) {
16
+ const summary = {
17
+ key: aggregate.intention.key,
18
+ project: aggregate.intention.project,
19
+ state: aggregate.state,
20
+ priority: aggregate.intention.priority,
21
+ received_at: aggregate.received_at,
22
+ };
23
+ if (aggregate.bail !== undefined) {
24
+ summary.bail_expires_at = aggregate.bail.expires_at;
25
+ }
26
+ return summary;
27
+ }
28
+ /**
29
+ * Filesystem Persistence Port. One JSON file per key. Atomic rename on save.
30
+ * Does not know FeatureStandard transitions.
31
+ */
32
+ export async function openFilesystemPersist(options) {
33
+ if (!isAbsolute(options.root)) {
34
+ throw new Error(`Filesystem persist root must be an absolute directory, got "${options.root}".`);
35
+ }
36
+ await mkdir(options.root, { recursive: true });
37
+ const root = options.root;
38
+ return {
39
+ async load(key) {
40
+ const path = join(root, fileNameFor(key));
41
+ try {
42
+ const raw = await readFile(path, "utf8");
43
+ const parsed = JSON.parse(raw);
44
+ if (!isAggregate(parsed)) {
45
+ return undefined;
46
+ }
47
+ return parsed;
48
+ }
49
+ catch (error) {
50
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
51
+ return undefined;
52
+ }
53
+ throw error;
54
+ }
55
+ },
56
+ async save(key, aggregate) {
57
+ const target = join(root, fileNameFor(key));
58
+ const temporary = join(root, `.${fileNameFor(key)}.tmp`);
59
+ try {
60
+ await writeFile(temporary, `${JSON.stringify(aggregate)}\n`, "utf8");
61
+ await rename(temporary, target);
62
+ return { ok: true };
63
+ }
64
+ catch (error) {
65
+ try {
66
+ await unlink(temporary);
67
+ }
68
+ catch {
69
+ // Best-effort cleanup of a leftover temporary file.
70
+ }
71
+ const detail = error instanceof Error ? error.message : String(error);
72
+ return { ok: false, detail };
73
+ }
74
+ },
75
+ async listSummaries() {
76
+ const names = await readdir(root);
77
+ const summaries = [];
78
+ for (const name of names) {
79
+ if (!name.endsWith(".json") || name.startsWith(".")) {
80
+ continue;
81
+ }
82
+ try {
83
+ const raw = await readFile(join(root, name), "utf8");
84
+ const parsed = JSON.parse(raw);
85
+ if (isAggregate(parsed)) {
86
+ summaries.push(toSummary(parsed));
87
+ }
88
+ }
89
+ catch { }
90
+ }
91
+ return summaries;
92
+ },
93
+ };
94
+ }
95
+ //# sourceMappingURL=open-filesystem-persist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open-filesystem-persist.js","sourceRoot":"","sources":["../src/open-filesystem-persist.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { PersistencePort } from "@bluewombat/work-ledger";
2
+ /**
3
+ * The face Host loads by name (`"persist": "@bluewombat/persist-fs"`): the
4
+ * ledger directory in, a Port out. One JSON file per key, directly under it.
5
+ */
6
+ export declare function openPersist(context: {
7
+ ledgerRoot: string;
8
+ }): Promise<PersistencePort>;
9
+ //# sourceMappingURL=open-persist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open-persist.d.ts","sourceRoot":"","sources":["../src/open-persist.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAG/D;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,eAAe,CAAC,CAErF"}
@@ -0,0 +1,9 @@
1
+ import { openFilesystemPersist } from "./open-filesystem-persist.js";
2
+ /**
3
+ * The face Host loads by name (`"persist": "@bluewombat/persist-fs"`): the
4
+ * ledger directory in, a Port out. One JSON file per key, directly under it.
5
+ */
6
+ export function openPersist(context) {
7
+ return openFilesystemPersist({ root: context.ledgerRoot });
8
+ }
9
+ //# sourceMappingURL=open-persist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open-persist.js","sourceRoot":"","sources":["../src/open-persist.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAErE;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,OAA+B;IACzD,OAAO,qBAAqB,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;AAC7D,CAAC"}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@bluewombat/persist-fs",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "WorkLedger persistence: one JSON file per key on the filesystem.",
6
+ "keywords": [
7
+ "mason",
8
+ "bluewombat",
9
+ "persistence"
10
+ ],
11
+ "license": "MIT",
12
+ "homepage": "https://github.com/tilap/bluewombat/tree/main/packages/plugins/persist-fs#readme",
13
+ "bugs": "https://github.com/tilap/bluewombat/issues",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/tilap/bluewombat.git",
17
+ "directory": "packages/plugins/persist-fs"
18
+ },
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js"
23
+ }
24
+ },
25
+ "types": "./dist/index.d.ts",
26
+ "files": [
27
+ "dist",
28
+ "README.md"
29
+ ],
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "engines": {
34
+ "node": ">=24.20.0"
35
+ },
36
+ "scripts": {
37
+ "lint": "biome check .",
38
+ "format": "biome check --write .",
39
+ "tsc": "tsc --noEmit",
40
+ "test": "tsx --test $(find src -name '*.test.ts')",
41
+ "build": "tsc -p tsconfig.build.json"
42
+ },
43
+ "dependencies": {
44
+ "@bluewombat/work-ledger": "^0.1.0"
45
+ }
46
+ }