@mappamind_/store 0.1.0-rc.1
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/LICENSE +21 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/paths.d.ts +10 -0
- package/dist/paths.js +61 -0
- package/dist/store.d.ts +10 -0
- package/dist/store.js +40 -0
- package/package.json +36 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mappamind contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/paths.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RepoFiles, WorkspaceModel } from "@mappamind_/baseline";
|
|
2
|
+
export declare function stateRoot(env?: NodeJS.ProcessEnv): string;
|
|
3
|
+
export declare function workspaceIdFor(repoRoots: readonly string[]): string;
|
|
4
|
+
export declare function workspaceDir(workspaceId: string, env?: NodeJS.ProcessEnv): string;
|
|
5
|
+
export declare function baselinePath(workspaceId: string, env?: NodeJS.ProcessEnv): string;
|
|
6
|
+
export declare function factsHashFor(repos: readonly RepoFiles[]): string;
|
|
7
|
+
export declare function workspaceIdentity(repos: readonly RepoFiles[], _model?: WorkspaceModel): {
|
|
8
|
+
readonly workspaceId: string;
|
|
9
|
+
readonly factsHash: string;
|
|
10
|
+
};
|
package/dist/paths.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Identity and location for the workspace store (D12).
|
|
2
|
+
//
|
|
3
|
+
// The low-level store only knows a state root + workspace id. Product entrypoints
|
|
4
|
+
// set MAPPAMIND_STATE_DIR to <workspace>/.mappamind/state by default, so the
|
|
5
|
+
// durable baseline/cache/snapshot live beside the repo/workspace the user ran on.
|
|
6
|
+
// The XDG fallback remains for direct package use and custom installs.
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
import { homedir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
function shortHash(input) {
|
|
11
|
+
return createHash("sha256").update(input).digest("hex").slice(0, 16);
|
|
12
|
+
}
|
|
13
|
+
// The root that holds all workspaces. Order of precedence: explicit env override,
|
|
14
|
+
// XDG_STATE_HOME, then the XDG default.
|
|
15
|
+
export function stateRoot(env = process.env) {
|
|
16
|
+
if (env["MAPPAMIND_STATE_DIR"]) {
|
|
17
|
+
return env["MAPPAMIND_STATE_DIR"];
|
|
18
|
+
}
|
|
19
|
+
const xdg = env["XDG_STATE_HOME"];
|
|
20
|
+
return xdg ? join(xdg, "mappamind") : join(homedir(), ".local", "state", "mappamind");
|
|
21
|
+
}
|
|
22
|
+
// Stable id for a workspace = hash of its repo roots, order-independent (sorted), so
|
|
23
|
+
// the same set of repos always maps to the same store regardless of listing order.
|
|
24
|
+
export function workspaceIdFor(repoRoots) {
|
|
25
|
+
const canonical = [...repoRoots].sort().join("\n");
|
|
26
|
+
return `ws_${shortHash(canonical)}`;
|
|
27
|
+
}
|
|
28
|
+
export function workspaceDir(workspaceId, env = process.env) {
|
|
29
|
+
return join(stateRoot(env), "workspaces", workspaceId);
|
|
30
|
+
}
|
|
31
|
+
export function baselinePath(workspaceId, env = process.env) {
|
|
32
|
+
return join(workspaceDir(workspaceId, env), "baseline.json");
|
|
33
|
+
}
|
|
34
|
+
// The structural fingerprint the baseline was derived from. If the code's structure
|
|
35
|
+
// changes, this changes, and the stored baseline is known to be stale (D12). We hash
|
|
36
|
+
// a canonical projection of the facts — not formatting, not order — so only real
|
|
37
|
+
// structural change moves it.
|
|
38
|
+
export function factsHashFor(repos) {
|
|
39
|
+
const canonicalRepos = [...repos]
|
|
40
|
+
.sort((a, b) => a.repo.localeCompare(b.repo))
|
|
41
|
+
.map((repo) => ({
|
|
42
|
+
repo: repo.repo,
|
|
43
|
+
files: [...repo.files]
|
|
44
|
+
.sort((a, b) => a.path.localeCompare(b.path))
|
|
45
|
+
.map((file) => ({
|
|
46
|
+
path: file.path,
|
|
47
|
+
symbols: file.symbols.map((symbol) => `${symbol.kind}:${symbol.name}`).sort(),
|
|
48
|
+
exports: file.exports.map((exported) => exported.name).sort(),
|
|
49
|
+
imports: file.imports.map((imported) => imported.module).sort(),
|
|
50
|
+
calls: file.calls.map((call) => `${call.callee}(${call.args.join(",")})`).sort()
|
|
51
|
+
}))
|
|
52
|
+
}));
|
|
53
|
+
return shortHash(JSON.stringify(canonicalRepos));
|
|
54
|
+
}
|
|
55
|
+
// Convenience: the id + fingerprint for a workspace, ready to stamp a baseline.
|
|
56
|
+
export function workspaceIdentity(repos, _model) {
|
|
57
|
+
return {
|
|
58
|
+
workspaceId: workspaceIdFor(repos.map((repo) => repo.repo)),
|
|
59
|
+
factsHash: factsHashFor(repos)
|
|
60
|
+
};
|
|
61
|
+
}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Baseline, RepoFiles } from "@mappamind_/baseline";
|
|
2
|
+
export declare function writeBaseline(workspaceId: string, baseline: Baseline, env?: NodeJS.ProcessEnv): Promise<string>;
|
|
3
|
+
export declare function readBaseline(workspaceId: string, env?: NodeJS.ProcessEnv): Promise<Baseline | null>;
|
|
4
|
+
export declare function isStale(baseline: Baseline, currentFactsHash: string): boolean;
|
|
5
|
+
export type BaselineStatus = {
|
|
6
|
+
readonly baseline: Baseline | null;
|
|
7
|
+
readonly stale: boolean;
|
|
8
|
+
readonly currentFactsHash: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function loadBaselineStatus(workspaceId: string, repos: readonly RepoFiles[], env?: NodeJS.ProcessEnv): Promise<BaselineStatus>;
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Read/write the persisted baseline, and detect staleness (D12).
|
|
2
|
+
//
|
|
3
|
+
// Writes are atomic (temp file + rename) so a crash mid-write never leaves a
|
|
4
|
+
// half-baseline on disk. Reads tolerate a missing or unreadable file by returning
|
|
5
|
+
// null — an absent baseline is a normal first-run state, not an error. Staleness is
|
|
6
|
+
// a pure comparison of the stored fingerprint against the current one.
|
|
7
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
import { baselinePath, factsHashFor } from "./paths.js";
|
|
10
|
+
export async function writeBaseline(workspaceId, baseline, env = process.env) {
|
|
11
|
+
const target = baselinePath(workspaceId, env);
|
|
12
|
+
await mkdir(dirname(target), { recursive: true });
|
|
13
|
+
const tmp = join(dirname(target), `baseline.json.tmp-${process.pid}`);
|
|
14
|
+
await writeFile(tmp, `${JSON.stringify(baseline, null, 2)}\n`, "utf8");
|
|
15
|
+
await rename(tmp, target);
|
|
16
|
+
return target;
|
|
17
|
+
}
|
|
18
|
+
export async function readBaseline(workspaceId, env = process.env) {
|
|
19
|
+
try {
|
|
20
|
+
const text = await readFile(baselinePath(workspaceId, env), "utf8");
|
|
21
|
+
return JSON.parse(text);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// The baseline is stale if the code it was derived from has structurally changed.
|
|
28
|
+
export function isStale(baseline, currentFactsHash) {
|
|
29
|
+
return baseline.derivedFrom.factsHash !== currentFactsHash;
|
|
30
|
+
}
|
|
31
|
+
// Load the stored baseline and tell the caller whether it still matches the code.
|
|
32
|
+
export async function loadBaselineStatus(workspaceId, repos, env = process.env) {
|
|
33
|
+
const currentFactsHash = factsHashFor(repos);
|
|
34
|
+
const baseline = await readBaseline(workspaceId, env);
|
|
35
|
+
return {
|
|
36
|
+
baseline,
|
|
37
|
+
stale: baseline !== null && isStale(baseline, currentFactsHash),
|
|
38
|
+
currentFactsHash
|
|
39
|
+
};
|
|
40
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mappamind_/store",
|
|
3
|
+
"version": "0.1.0-rc.1",
|
|
4
|
+
"description": "Structured JSON/JSONL persistence for Mappamind.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist/**/*.js",
|
|
13
|
+
"dist/**/*.d.ts",
|
|
14
|
+
"!dist/**/*.test.js",
|
|
15
|
+
"!dist/**/*.test.d.ts"
|
|
16
|
+
],
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=20"
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/mappamind/mappamind.git",
|
|
26
|
+
"directory": "packages/store"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc -b",
|
|
30
|
+
"test": "node --test dist/**/*.test.js"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@mappamind_/baseline": "0.1.0-rc.1",
|
|
34
|
+
"@mappamind_/extractors": "0.1.0-rc.1"
|
|
35
|
+
}
|
|
36
|
+
}
|