@x12i/static-memorix-explorer-api 1.0.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.
@@ -0,0 +1,188 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { CONTENT_TYPES } from "../types.js";
4
+ import { MOCKS_DIR, METADATA_DIR, DATA_DIR } from "../config.js";
5
+ import { validateRecordArray } from "../engine/identity.js";
6
+ import { isDir, listJsonFiles, readJson, writeJson, basenameNoExt, } from "./fs.js";
7
+ import { DISK_FLUSH_DEBOUNCE_MS } from "../config.js";
8
+ const DIRTY = Symbol("dirty");
9
+ export class InMemoryStore {
10
+ collections = new Map(); // key: `${objectType}/${contentType}`
11
+ metadata = new Map();
12
+ flushTimers = new Map();
13
+ loaded = false;
14
+ /**
15
+ * Load everything from disk into memory. Idempotent.
16
+ */
17
+ async load() {
18
+ if (this.loaded)
19
+ return;
20
+ this.loadMetadataCatalog();
21
+ this.loadDataCollections();
22
+ this.loaded = true;
23
+ }
24
+ loadMetadataCatalog() {
25
+ const sections = {
26
+ "object-types": "object-types",
27
+ lists: "lists",
28
+ "write-descriptors": "write-descriptors",
29
+ narratives: "narratives",
30
+ };
31
+ for (const [subdir] of Object.entries(sections)) {
32
+ const dir = path.join(METADATA_DIR, subdir);
33
+ for (const file of listJsonFiles(dir)) {
34
+ const key = `${subdir}/${basenameNoExt(file)}`;
35
+ this.metadata.set(key, { path: file, data: readJson(file, {}) });
36
+ }
37
+ }
38
+ const agentsFile = path.join(METADATA_DIR, "agents.json");
39
+ if (fs.existsSync(agentsFile)) {
40
+ this.metadata.set("agents", {
41
+ path: agentsFile,
42
+ data: readJson(agentsFile, { agents: [] }),
43
+ });
44
+ }
45
+ }
46
+ loadDataCollections() {
47
+ if (!isDir(DATA_DIR))
48
+ return;
49
+ for (const objectType of fs.readdirSync(DATA_DIR)) {
50
+ const otDir = path.join(DATA_DIR, objectType);
51
+ if (!isDir(otDir))
52
+ continue;
53
+ for (const ct of CONTENT_TYPES) {
54
+ const file = path.join(otDir, `${ct}.json`);
55
+ const key = `${objectType}/${ct}`;
56
+ const data = readJson(file, []);
57
+ // Startup validation: ensure records carry exactly one identity key
58
+ // (MemorixRecordArraySchema mock). Memory collections use memoryId.
59
+ const result = validateRecordArray(objectType, ct, data);
60
+ if (!result.ok) {
61
+ for (const err of result.errors) {
62
+ console.warn(`[mock-memorix-explorer-api] schema warning: ${err}`);
63
+ }
64
+ }
65
+ this.collections.set(key, {
66
+ path: file,
67
+ data,
68
+ });
69
+ }
70
+ // system overrides
71
+ const invFile = path.join(DATA_DIR, "system", "inventory.json");
72
+ if (objectType === "system" && fs.existsSync(invFile)) {
73
+ this.collections.set("system/inventory", {
74
+ path: invFile,
75
+ data: readJson(invFile, []),
76
+ });
77
+ }
78
+ }
79
+ }
80
+ // ---- Metadata access ----
81
+ getMeta(key) {
82
+ return this.metadata.get(key)?.data;
83
+ }
84
+ setMeta(key, data, persist = true) {
85
+ const existing = this.metadata.get(key);
86
+ if (existing) {
87
+ existing.data = data;
88
+ existing[DIRTY] = true;
89
+ }
90
+ else {
91
+ this.metadata.set(key, {
92
+ path: path.join(METADATA_DIR, `${key}.json`),
93
+ data,
94
+ [DIRTY]: true,
95
+ });
96
+ }
97
+ if (persist)
98
+ this.scheduleMetaFlush(key);
99
+ }
100
+ getMetaKeys() {
101
+ return [...this.metadata.keys()];
102
+ }
103
+ // ---- Collection access ----
104
+ getCollection(objectType, ct) {
105
+ const key = `${objectType}/${ct}`;
106
+ const c = this.collections.get(key);
107
+ if (!c) {
108
+ const newC = {
109
+ path: path.join(DATA_DIR, objectType, `${ct}.json`),
110
+ data: [],
111
+ };
112
+ this.collections.set(key, newC);
113
+ return newC.data;
114
+ }
115
+ return c.data;
116
+ }
117
+ setCollection(objectType, ct, data) {
118
+ const key = `${objectType}/${ct}`;
119
+ const c = this.collections.get(key);
120
+ if (c) {
121
+ c.data = data;
122
+ c[DIRTY] = true;
123
+ }
124
+ else {
125
+ this.collections.set(key, {
126
+ path: path.join(DATA_DIR, objectType, `${ct}.json`),
127
+ data,
128
+ [DIRTY]: true,
129
+ });
130
+ }
131
+ this.scheduleCollectionFlush(key);
132
+ }
133
+ getCollectionKeys() {
134
+ return [...this.collections.keys()];
135
+ }
136
+ // ---- Flush machinery ----
137
+ scheduleCollectionFlush(key) {
138
+ const existing = this.flushTimers.get(key);
139
+ if (existing)
140
+ clearTimeout(existing);
141
+ const t = setTimeout(() => {
142
+ this.flushCollectionNow(key);
143
+ this.flushTimers.delete(key);
144
+ }, DISK_FLUSH_DEBOUNCE_MS);
145
+ this.flushTimers.set(key, t);
146
+ }
147
+ scheduleMetaFlush(key) {
148
+ const fkey = `meta:${key}`;
149
+ const existing = this.flushTimers.get(fkey);
150
+ if (existing)
151
+ clearTimeout(existing);
152
+ const t = setTimeout(() => {
153
+ this.flushMetaNow(key);
154
+ this.flushTimers.delete(fkey);
155
+ }, DISK_FLUSH_DEBOUNCE_MS);
156
+ this.flushTimers.set(fkey, t);
157
+ }
158
+ flushCollectionNow(key) {
159
+ const c = this.collections.get(key);
160
+ if (c && c[DIRTY]) {
161
+ writeJson(c.path, c.data);
162
+ c[DIRTY] = false;
163
+ }
164
+ }
165
+ flushMetaNow(key) {
166
+ const m = this.metadata.get(key);
167
+ if (m && m[DIRTY]) {
168
+ writeJson(m.path, m.data);
169
+ m[DIRTY] = false;
170
+ }
171
+ }
172
+ /**
173
+ * Flush every dirty file immediately and clear timers.
174
+ */
175
+ async flushAll() {
176
+ for (const t of this.flushTimers.values())
177
+ clearTimeout(t);
178
+ this.flushTimers.clear();
179
+ for (const key of this.collections.keys())
180
+ this.flushCollectionNow(key);
181
+ for (const key of this.metadata.keys())
182
+ this.flushMetaNow(key);
183
+ }
184
+ get mocksDir() {
185
+ return MOCKS_DIR;
186
+ }
187
+ }
188
+ export const store = new InMemoryStore();
@@ -0,0 +1,39 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ export function ensureDir(dir) {
4
+ fs.mkdirSync(dir, { recursive: true });
5
+ }
6
+ export function isDir(p) {
7
+ try {
8
+ return fs.statSync(p).isDirectory();
9
+ }
10
+ catch {
11
+ return false;
12
+ }
13
+ }
14
+ export function readJson(p, fallback) {
15
+ try {
16
+ const raw = fs.readFileSync(p, "utf8");
17
+ if (!raw.trim())
18
+ return fallback;
19
+ return JSON.parse(raw);
20
+ }
21
+ catch {
22
+ return fallback;
23
+ }
24
+ }
25
+ export function writeJson(p, data) {
26
+ ensureDir(path.dirname(p));
27
+ fs.writeFileSync(p, JSON.stringify(data, null, 2) + "\n", "utf8");
28
+ }
29
+ export function listJsonFiles(dir) {
30
+ if (!isDir(dir))
31
+ return [];
32
+ return fs
33
+ .readdirSync(dir)
34
+ .filter((f) => f.endsWith(".json"))
35
+ .map((f) => path.join(dir, f));
36
+ }
37
+ export function basenameNoExt(p) {
38
+ return path.basename(p, ".json");
39
+ }
package/dist/types.js ADDED
@@ -0,0 +1,37 @@
1
+ export const CONTENT_TYPES = [
2
+ "snapshots",
3
+ "analysis",
4
+ "decisions",
5
+ "events",
6
+ "memory",
7
+ ];
8
+ /**
9
+ * The fourth tier of data alongside entities, events, and knowledge.
10
+ * `memory` collections are keyed by `memoryId`.
11
+ */
12
+ export const MEMORY_CONTENT_TYPE = "memory";
13
+ /** Primary identity keys accepted across fetch/write engines. */
14
+ export const IDENTITY_KEYS = [
15
+ "recordId",
16
+ "entityId",
17
+ "eventId",
18
+ "knowledgeId",
19
+ "memoryId",
20
+ ];
21
+ /** Map a target type to its canonical id field. */
22
+ export const TARGET_TO_ID_FIELD = {
23
+ entity: "recordId",
24
+ event: "eventId",
25
+ knowledge: "knowledgeId",
26
+ memory: "memoryId",
27
+ };
28
+ export const INCLUDE_FLAGS = [
29
+ "data",
30
+ "analytics",
31
+ "enrichment",
32
+ "insights",
33
+ "system",
34
+ "associated",
35
+ "discovery",
36
+ "analysis",
37
+ ];
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@x12i/static-memorix-explorer-api",
3
+ "version": "1.0.0",
4
+ "description": "Static mock server providing full API parity for the Memorix Explorer API.",
5
+ "type": "module",
6
+ "main": "dist/server.js",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/server.js"
10
+ },
11
+ "./config": {
12
+ "import": "./dist/config.js"
13
+ }
14
+ },
15
+ "bin": {
16
+ "mock-memorix-explorer-api": "dist/server.js"
17
+ },
18
+ "scripts": {
19
+ "build": "tsc -p tsconfig.json",
20
+ "start": "node dist/server.js",
21
+ "dev": "node --experimental-strip-types server.ts",
22
+ "typecheck": "tsc -p tsconfig.json --noEmit",
23
+ "prepack": "npm run build",
24
+ "postpack": "true"
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "LICENSE",
29
+ "README.md"
30
+ ],
31
+ "dependencies": {
32
+ "fastify": "^4.28.1",
33
+ "mingo": "^6.4.12"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^20.14.0",
37
+ "typescript": "^5.5.0"
38
+ },
39
+ "license": "MIT",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/x12i/static-memorix-explorer-api.git"
43
+ },
44
+ "homepage": "https://github.com/x12i/static-memorix-explorer-api#readme",
45
+ "bugs": {
46
+ "url": "https://github.com/x12i/static-memorix-explorer-api/issues"
47
+ },
48
+ "keywords": [
49
+ "mock",
50
+ "api",
51
+ "memorix",
52
+ "explorer",
53
+ "fastify",
54
+ "test-server",
55
+ "development-tools",
56
+ "static"
57
+ ],
58
+ "engines": {
59
+ "node": ">=18.0.0"
60
+ }
61
+ }