@x12i/static-memorix 2.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.
Files changed (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +431 -0
  3. package/dist/cli.d.ts +17 -0
  4. package/dist/cli.js +278 -0
  5. package/dist/config.d.ts +39 -0
  6. package/dist/config.js +109 -0
  7. package/dist/engine/aliases.d.ts +32 -0
  8. package/dist/engine/aliases.js +74 -0
  9. package/dist/engine/associations.d.ts +26 -0
  10. package/dist/engine/associations.js +60 -0
  11. package/dist/engine/identity.d.ts +22 -0
  12. package/dist/engine/identity.js +50 -0
  13. package/dist/engine/inventory.d.ts +29 -0
  14. package/dist/engine/inventory.js +133 -0
  15. package/dist/engine/lists.d.ts +18 -0
  16. package/dist/engine/lists.js +62 -0
  17. package/dist/engine/narratives.d.ts +16 -0
  18. package/dist/engine/narratives.js +67 -0
  19. package/dist/engine/objectTypes.d.ts +11 -0
  20. package/dist/engine/objectTypes.js +71 -0
  21. package/dist/engine/query.d.ts +38 -0
  22. package/dist/engine/query.js +191 -0
  23. package/dist/engine/records.d.ts +23 -0
  24. package/dist/engine/records.js +152 -0
  25. package/dist/engine/snapshots.d.ts +17 -0
  26. package/dist/engine/snapshots.js +110 -0
  27. package/dist/engine/write.d.ts +30 -0
  28. package/dist/engine/write.js +163 -0
  29. package/dist/routes/helpers.d.ts +4 -0
  30. package/dist/routes/helpers.js +29 -0
  31. package/dist/routes/index.d.ts +2 -0
  32. package/dist/routes/index.js +402 -0
  33. package/dist/server.d.ts +15 -0
  34. package/dist/server.js +130 -0
  35. package/dist/storage/InMemoryStore.d.ts +31 -0
  36. package/dist/storage/InMemoryStore.js +242 -0
  37. package/dist/storage/fs.d.ts +6 -0
  38. package/dist/storage/fs.js +54 -0
  39. package/dist/types.d.ts +88 -0
  40. package/dist/types.js +37 -0
  41. package/guides/demo-app.md +527 -0
  42. package/guides/managing-json-files.md +384 -0
  43. package/guides/running-the-service.md +269 -0
  44. package/guides/using-the-api.md +265 -0
  45. package/mocks/data/admin/snapshots.json +52 -0
  46. package/mocks/data/assets/analysis.json +14 -0
  47. package/mocks/data/assets/decisions.json +14 -0
  48. package/mocks/data/assets/events.json +4 -0
  49. package/mocks/data/assets/snapshots.json +49 -0
  50. package/mocks/data/findings/analysis.json +4 -0
  51. package/mocks/data/findings/decisions.json +4 -0
  52. package/mocks/data/findings/events.json +3 -0
  53. package/mocks/data/findings/snapshots.json +21 -0
  54. package/mocks/data/marketing/snapshots.json +75 -0
  55. package/mocks/data/memory/memory.json +31 -0
  56. package/mocks/data/product/snapshots.json +84 -0
  57. package/mocks/data/system/inventory.json +8 -0
  58. package/mocks/data/users/snapshots.json +5 -0
  59. package/mocks/demo.html +828 -0
  60. package/mocks/metadata/agents.json +7 -0
  61. package/mocks/metadata/lists/assets-default.json +22 -0
  62. package/mocks/metadata/lists/backlog.json +10 -0
  63. package/mocks/metadata/lists/findings-default.json +9 -0
  64. package/mocks/metadata/lists/my-plate.json +10 -0
  65. package/mocks/metadata/lists/this-week.json +10 -0
  66. package/mocks/metadata/narratives/admin.json +7 -0
  67. package/mocks/metadata/narratives/assets.json +17 -0
  68. package/mocks/metadata/narratives/findings.json +7 -0
  69. package/mocks/metadata/narratives/marketing.json +17 -0
  70. package/mocks/metadata/narratives/product.json +12 -0
  71. package/mocks/metadata/object-types/admin.json +6 -0
  72. package/mocks/metadata/object-types/assets.json +9 -0
  73. package/mocks/metadata/object-types/findings.json +8 -0
  74. package/mocks/metadata/object-types/marketing.json +6 -0
  75. package/mocks/metadata/object-types/memory.json +6 -0
  76. package/mocks/metadata/object-types/product.json +6 -0
  77. package/mocks/metadata/object-types/users.json +13 -0
  78. package/mocks/metadata/write-descriptors/admin-task-write.json +20 -0
  79. package/mocks/metadata/write-descriptors/assets-analysis-write.json +14 -0
  80. package/mocks/metadata/write-descriptors/marketing-task-write.json +20 -0
  81. package/mocks/metadata/write-descriptors/memory-write.json +14 -0
  82. package/mocks/metadata/write-descriptors/product-task-write.json +20 -0
  83. package/package.json +67 -0
@@ -0,0 +1,242 @@
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, buildDataFilePrefix, buildMetadataFilePrefix, prefixedJsonFilename, validateRouteId, } 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
+ function routedMetadataPath(key) {
10
+ const parts = key.split("/");
11
+ if (parts.length === 1 && parts[0] === "agents") {
12
+ return path.join(METADATA_DIR, prefixedJsonFilename(buildMetadataFilePrefix(), "agents"));
13
+ }
14
+ if (parts.length !== 2) {
15
+ throw Object.assign(new Error(`Unsafe metadata key: ${key}`), { statusCode: 400 });
16
+ }
17
+ const [section, name] = parts;
18
+ if (!["object-types", "lists", "write-descriptors", "narratives"].includes(section)) {
19
+ throw Object.assign(new Error(`Unsupported metadata section: ${section}`), {
20
+ statusCode: 400,
21
+ });
22
+ }
23
+ validateRouteId(name, "metadata fixture name");
24
+ return path.join(METADATA_DIR, section, prefixedJsonFilename(buildMetadataFilePrefix(), name));
25
+ }
26
+ export class InMemoryStore {
27
+ collections = new Map(); // key: `${objectType}/${contentType}`
28
+ metadata = new Map();
29
+ flushTimers = new Map();
30
+ loaded = false;
31
+ /**
32
+ * Load everything from disk into memory. Idempotent.
33
+ */
34
+ async load() {
35
+ if (this.loaded)
36
+ return;
37
+ this.loadMetadataCatalog();
38
+ this.loadDataCollections();
39
+ this.loaded = true;
40
+ }
41
+ loadMetadataCatalog() {
42
+ const sections = {
43
+ "object-types": "object-types",
44
+ lists: "lists",
45
+ "write-descriptors": "write-descriptors",
46
+ narratives: "narratives",
47
+ };
48
+ const prefix = buildMetadataFilePrefix();
49
+ for (const [subdir] of Object.entries(sections)) {
50
+ const dir = path.join(METADATA_DIR, subdir);
51
+ const files = listJsonFiles(dir);
52
+ const logicalNames = new Set();
53
+ for (const file of files) {
54
+ const name = basenameNoExt(file);
55
+ if (!name.includes("--"))
56
+ logicalNames.add(name);
57
+ if (name.startsWith(`${prefix}--`))
58
+ logicalNames.add(name.slice(prefix.length + 2));
59
+ }
60
+ for (const name of logicalNames) {
61
+ const baseFile = path.join(dir, `${name}.json`);
62
+ const routedFile = path.join(dir, prefixedJsonFilename(prefix, name));
63
+ const source = fs.existsSync(routedFile) ? routedFile : baseFile;
64
+ const key = `${subdir}/${name}`;
65
+ this.metadata.set(key, { path: routedFile, data: readJson(source, {}) });
66
+ }
67
+ }
68
+ const baseAgentsFile = path.join(METADATA_DIR, "agents.json");
69
+ const routedAgentsFile = path.join(METADATA_DIR, prefixedJsonFilename(prefix, "agents"));
70
+ const agentsSource = fs.existsSync(routedAgentsFile) ? routedAgentsFile : baseAgentsFile;
71
+ if (fs.existsSync(agentsSource)) {
72
+ this.metadata.set("agents", {
73
+ path: routedAgentsFile,
74
+ data: readJson(agentsSource, { agents: [] }),
75
+ });
76
+ }
77
+ }
78
+ loadDataCollections() {
79
+ if (!isDir(DATA_DIR))
80
+ return;
81
+ for (const objectType of fs.readdirSync(DATA_DIR)) {
82
+ const otDir = path.join(DATA_DIR, objectType);
83
+ if (!isDir(otDir))
84
+ continue;
85
+ for (const ct of CONTENT_TYPES) {
86
+ const baseFile = path.join(otDir, `${ct}.json`);
87
+ const file = path.join(otDir, prefixedJsonFilename(buildDataFilePrefix(ct), ct));
88
+ const key = `${objectType}/${ct}`;
89
+ const source = fs.existsSync(file) ? file : baseFile;
90
+ const data = readJson(source, []);
91
+ // Startup validation: ensure records carry exactly one identity key
92
+ // (MemorixRecordArraySchema mock). Memory collections use memoryId.
93
+ const result = validateRecordArray(objectType, ct, data);
94
+ if (!result.ok) {
95
+ for (const err of result.errors) {
96
+ console.warn(`[static-memorix] schema warning: ${err}`);
97
+ }
98
+ }
99
+ this.collections.set(key, {
100
+ path: file,
101
+ data,
102
+ });
103
+ }
104
+ // system overrides
105
+ const baseInvFile = path.join(DATA_DIR, "system", "inventory.json");
106
+ const invFile = path.join(DATA_DIR, "system", prefixedJsonFilename(buildDataFilePrefix("snapshots"), "inventory"));
107
+ const invSource = fs.existsSync(invFile) ? invFile : baseInvFile;
108
+ if (objectType === "system" && fs.existsSync(invSource)) {
109
+ this.collections.set("system/inventory", {
110
+ path: invFile,
111
+ data: readJson(invSource, []),
112
+ });
113
+ }
114
+ }
115
+ }
116
+ // ---- Metadata access ----
117
+ getMeta(key) {
118
+ return this.metadata.get(key)?.data;
119
+ }
120
+ setMeta(key, data, persist = true) {
121
+ const existing = this.metadata.get(key);
122
+ if (existing) {
123
+ existing.data = data;
124
+ existing[DIRTY] = true;
125
+ }
126
+ else {
127
+ this.metadata.set(key, {
128
+ path: routedMetadataPath(key),
129
+ data,
130
+ [DIRTY]: true,
131
+ });
132
+ }
133
+ if (persist)
134
+ this.scheduleMetaFlush(key);
135
+ }
136
+ /** Persist a routed tombstone so an unprefixed seed does not reappear. */
137
+ deleteMeta(key) {
138
+ const existing = this.metadata.get(key);
139
+ if (existing) {
140
+ existing.data = null;
141
+ existing[DIRTY] = true;
142
+ }
143
+ else {
144
+ this.metadata.set(key, {
145
+ path: routedMetadataPath(key),
146
+ data: null,
147
+ [DIRTY]: true,
148
+ });
149
+ }
150
+ this.scheduleMetaFlush(key);
151
+ }
152
+ getMetaKeys() {
153
+ return [...this.metadata.keys()];
154
+ }
155
+ // ---- Collection access ----
156
+ getCollection(objectType, ct) {
157
+ validateRouteId(objectType, "object type");
158
+ const key = `${objectType}/${ct}`;
159
+ const c = this.collections.get(key);
160
+ if (!c) {
161
+ const newC = {
162
+ path: path.join(DATA_DIR, objectType, prefixedJsonFilename(buildDataFilePrefix(ct), ct)),
163
+ data: [],
164
+ };
165
+ this.collections.set(key, newC);
166
+ return newC.data;
167
+ }
168
+ return c.data;
169
+ }
170
+ setCollection(objectType, ct, data) {
171
+ validateRouteId(objectType, "object type");
172
+ const key = `${objectType}/${ct}`;
173
+ const c = this.collections.get(key);
174
+ if (c) {
175
+ c.data = data;
176
+ c[DIRTY] = true;
177
+ }
178
+ else {
179
+ this.collections.set(key, {
180
+ path: path.join(DATA_DIR, objectType, prefixedJsonFilename(buildDataFilePrefix(ct), ct)),
181
+ data,
182
+ [DIRTY]: true,
183
+ });
184
+ }
185
+ this.scheduleCollectionFlush(key);
186
+ }
187
+ getCollectionKeys() {
188
+ return [...this.collections.keys()];
189
+ }
190
+ // ---- Flush machinery ----
191
+ scheduleCollectionFlush(key) {
192
+ const existing = this.flushTimers.get(key);
193
+ if (existing)
194
+ clearTimeout(existing);
195
+ const t = setTimeout(() => {
196
+ this.flushCollectionNow(key);
197
+ this.flushTimers.delete(key);
198
+ }, DISK_FLUSH_DEBOUNCE_MS);
199
+ this.flushTimers.set(key, t);
200
+ }
201
+ scheduleMetaFlush(key) {
202
+ const fkey = `meta:${key}`;
203
+ const existing = this.flushTimers.get(fkey);
204
+ if (existing)
205
+ clearTimeout(existing);
206
+ const t = setTimeout(() => {
207
+ this.flushMetaNow(key);
208
+ this.flushTimers.delete(fkey);
209
+ }, DISK_FLUSH_DEBOUNCE_MS);
210
+ this.flushTimers.set(fkey, t);
211
+ }
212
+ flushCollectionNow(key) {
213
+ const c = this.collections.get(key);
214
+ if (c && c[DIRTY]) {
215
+ writeJson(c.path, c.data);
216
+ c[DIRTY] = false;
217
+ }
218
+ }
219
+ flushMetaNow(key) {
220
+ const m = this.metadata.get(key);
221
+ if (m && m[DIRTY]) {
222
+ writeJson(m.path, m.data);
223
+ m[DIRTY] = false;
224
+ }
225
+ }
226
+ /**
227
+ * Flush every dirty file immediately and clear timers.
228
+ */
229
+ async flushAll() {
230
+ for (const t of this.flushTimers.values())
231
+ clearTimeout(t);
232
+ this.flushTimers.clear();
233
+ for (const key of this.collections.keys())
234
+ this.flushCollectionNow(key);
235
+ for (const key of this.metadata.keys())
236
+ this.flushMetaNow(key);
237
+ }
238
+ get mocksDir() {
239
+ return MOCKS_DIR;
240
+ }
241
+ }
242
+ export const store = new InMemoryStore();
@@ -0,0 +1,6 @@
1
+ export declare function ensureDir(dir: string): void;
2
+ export declare function isDir(p: string): boolean;
3
+ export declare function readJson<T = any>(p: string, fallback: T): T;
4
+ export declare function writeJson(p: string, data: unknown): void;
5
+ export declare function listJsonFiles(dir: string): string[];
6
+ export declare function basenameNoExt(p: string): string;
@@ -0,0 +1,54 @@
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
+ throw new Error("file is empty");
19
+ return JSON.parse(raw);
20
+ }
21
+ catch (error) {
22
+ if (error.code === "ENOENT")
23
+ return fallback;
24
+ const message = error instanceof Error ? error.message : String(error);
25
+ throw new Error(`Failed to read JSON fixture ${p}: ${message}`);
26
+ }
27
+ }
28
+ export function writeJson(p, data) {
29
+ ensureDir(path.dirname(p));
30
+ const serialized = JSON.stringify(data, null, 2);
31
+ if (serialized === undefined) {
32
+ throw new Error(`Cannot serialize undefined JSON fixture: ${p}`);
33
+ }
34
+ const temp = path.join(path.dirname(p), `.${path.basename(p)}.${process.pid}.tmp`);
35
+ try {
36
+ fs.writeFileSync(temp, serialized + "\n", "utf8");
37
+ fs.renameSync(temp, p);
38
+ }
39
+ finally {
40
+ if (fs.existsSync(temp))
41
+ fs.unlinkSync(temp);
42
+ }
43
+ }
44
+ export function listJsonFiles(dir) {
45
+ if (!isDir(dir))
46
+ return [];
47
+ return fs
48
+ .readdirSync(dir)
49
+ .filter((f) => f.endsWith(".json"))
50
+ .map((f) => path.join(dir, f));
51
+ }
52
+ export function basenameNoExt(p) {
53
+ return path.basename(p, ".json");
54
+ }
@@ -0,0 +1,88 @@
1
+ export type ContentType = "snapshots" | "analysis" | "decisions" | "events" | "memory";
2
+ export declare const CONTENT_TYPES: ContentType[];
3
+ /**
4
+ * The fourth tier of data alongside entities, events, and knowledge.
5
+ * `memory` collections are keyed by `memoryId`.
6
+ */
7
+ export declare const MEMORY_CONTENT_TYPE: ContentType;
8
+ /** Primary identity keys accepted across fetch/write engines. */
9
+ export declare const IDENTITY_KEYS: readonly ["recordId", "entityId", "eventId", "knowledgeId", "memoryId"];
10
+ export type IdentityKey = (typeof IDENTITY_KEYS)[number];
11
+ /** Map a target type to its canonical id field. */
12
+ export declare const TARGET_TO_ID_FIELD: Record<string, IdentityKey>;
13
+ export declare const INCLUDE_FLAGS: readonly ["data", "analytics", "enrichment", "insights", "system", "associated", "discovery", "analysis"];
14
+ export type IncludeFlag = (typeof INCLUDE_FLAGS)[number];
15
+ export interface ExplorerQueryParams {
16
+ filter?: string | string[];
17
+ sort?: string | string[];
18
+ searchText?: string;
19
+ q?: string;
20
+ limit?: string | number;
21
+ offset?: string | number;
22
+ skip?: string | number;
23
+ includeTotal?: boolean;
24
+ include?: string | string[];
25
+ contentType?: string;
26
+ mode?: "descriptor" | "raw" | "auto";
27
+ listId?: string;
28
+ entityName?: string;
29
+ sourceLens?: "db-first" | "catalox-first";
30
+ target?: "entity" | "event" | "knowledge" | "memory" | string;
31
+ includeExactCounts?: boolean;
32
+ includeInventory?: boolean | string;
33
+ memoryId?: string;
34
+ recordId?: string;
35
+ entityId?: string;
36
+ eventId?: string;
37
+ knowledgeId?: string;
38
+ }
39
+ export interface CatalogRelation {
40
+ sourceProperty?: string;
41
+ targetObject?: string;
42
+ targetProperty?: string;
43
+ relationType?: string;
44
+ }
45
+ export interface ObjectTypeMeta {
46
+ name: string;
47
+ summary?: string;
48
+ rootPropertyCatalog?: RootPropertyCatalogEntry[];
49
+ catalogRelations?: CatalogRelation[];
50
+ [key: string]: unknown;
51
+ }
52
+ export interface RootPropertyCatalogEntry {
53
+ path: string;
54
+ occurrencePct?: number;
55
+ types?: string[];
56
+ sampleValues?: unknown[];
57
+ [key: string]: unknown;
58
+ }
59
+ export interface ListDescriptor {
60
+ id?: string;
61
+ listId?: string;
62
+ entity?: string;
63
+ entityName?: string;
64
+ fields?: string[];
65
+ projections?: Record<string, unknown>;
66
+ extensions?: string[];
67
+ sort?: Record<string, 1 | -1>;
68
+ query?: Record<string, unknown>;
69
+ [key: string]: unknown;
70
+ }
71
+ export interface WriteDescriptor {
72
+ writeDescriptorId?: string;
73
+ id?: string;
74
+ targetCollection?: string;
75
+ schema?: unknown;
76
+ [key: string]: unknown;
77
+ }
78
+ export interface NarrativeDefinition {
79
+ key: string;
80
+ label?: string;
81
+ description?: string;
82
+ [key: string]: unknown;
83
+ }
84
+ export interface AgentEntry {
85
+ id?: string;
86
+ name?: string;
87
+ [key: string]: unknown;
88
+ }
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
+ ];