@medicine-wheel/storage-provider 0.4.11 → 0.4.12
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/dist/index.d.ts +1 -1
- package/dist/interface.d.ts +41 -0
- package/dist/jsonl.d.ts +6 -1
- package/dist/jsonl.js +29 -0
- package/dist/neon.d.ts +6 -1
- package/dist/neon.js +69 -0
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* const node = await store.getNode('elder-1');
|
|
16
16
|
* ```
|
|
17
17
|
*/
|
|
18
|
-
export type { StorageProvider, RelationalNode, RelationalEdge, CeremonyLog, ProviderType, } from './interface.js';
|
|
18
|
+
export type { StorageProvider, RelationalNode, RelationalEdge, CeremonyLog, WeaveRecord, WeaveSyncState, InquiryWeaveFilters, ProviderType, } from './interface.js';
|
|
19
19
|
export { createProvider, detectProvider } from './factory.js';
|
|
20
20
|
export { JsonlProvider } from './jsonl.js';
|
|
21
21
|
export { NeonProvider } from './neon.js';
|
package/dist/interface.d.ts
CHANGED
|
@@ -13,6 +13,44 @@ export interface RelationalEdge extends Omit<OntologyRelationalEdge, 'id'> {
|
|
|
13
13
|
last_ceremony?: string;
|
|
14
14
|
}
|
|
15
15
|
export type CeremonyLog = OntologyCeremonyLog;
|
|
16
|
+
export type WeaveSyncState = 'never-synced' | 'in-sync' | 'stale' | 'episode-copy-diverged';
|
|
17
|
+
export interface WeaveRecord extends Record<string, unknown> {
|
|
18
|
+
id: string;
|
|
19
|
+
weave: 1;
|
|
20
|
+
artefact: {
|
|
21
|
+
id: string;
|
|
22
|
+
path?: string;
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
};
|
|
25
|
+
issue: string;
|
|
26
|
+
issue_url?: string;
|
|
27
|
+
episode: {
|
|
28
|
+
path: string;
|
|
29
|
+
number: number;
|
|
30
|
+
[key: string]: unknown;
|
|
31
|
+
};
|
|
32
|
+
last_sync: {
|
|
33
|
+
state: WeaveSyncState;
|
|
34
|
+
at?: string;
|
|
35
|
+
tree_sha256?: string;
|
|
36
|
+
file_count?: number;
|
|
37
|
+
bytes_total?: number;
|
|
38
|
+
[key: string]: unknown;
|
|
39
|
+
};
|
|
40
|
+
source: {
|
|
41
|
+
package: string;
|
|
42
|
+
record_path?: string;
|
|
43
|
+
registered_at: string;
|
|
44
|
+
updated_at: string;
|
|
45
|
+
[key: string]: unknown;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export interface InquiryWeaveFilters {
|
|
49
|
+
episode_path?: string;
|
|
50
|
+
episode_number?: number;
|
|
51
|
+
issue?: string;
|
|
52
|
+
artefact?: string;
|
|
53
|
+
}
|
|
16
54
|
export interface StorageProvider {
|
|
17
55
|
readonly name: string;
|
|
18
56
|
connect(): Promise<void>;
|
|
@@ -33,5 +71,8 @@ export interface StorageProvider {
|
|
|
33
71
|
getCeremoniesByDirection(direction: DirectionName): Promise<CeremonyLog[]>;
|
|
34
72
|
getCeremoniesByType(type: CeremonyType): Promise<CeremonyLog[]>;
|
|
35
73
|
getAllCeremonies(limit?: number): Promise<CeremonyLog[]>;
|
|
74
|
+
registerInquiryWeave(record: WeaveRecord): Promise<void>;
|
|
75
|
+
getInquiryWeave(id: string): Promise<WeaveRecord | null>;
|
|
76
|
+
listInquiryWeaves(filters?: InquiryWeaveFilters): Promise<WeaveRecord[]>;
|
|
36
77
|
}
|
|
37
78
|
export type ProviderType = 'jsonl' | 'neon' | 'redis' | 'auto';
|
package/dist/jsonl.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { DirectionName, NodeType, CeremonyType } from '@medicine-wheel/ontology-core';
|
|
2
|
-
import type { StorageProvider, RelationalNode, RelationalEdge, CeremonyLog } from './interface.js';
|
|
2
|
+
import type { StorageProvider, RelationalNode, RelationalEdge, CeremonyLog, WeaveRecord, InquiryWeaveFilters } from './interface.js';
|
|
3
3
|
export declare class JsonlProvider implements StorageProvider {
|
|
4
4
|
readonly name = "jsonl";
|
|
5
5
|
readonly dataDir: string;
|
|
6
6
|
private readonly nodesFile;
|
|
7
7
|
private readonly edgesFile;
|
|
8
8
|
private readonly ceremoniesFile;
|
|
9
|
+
private readonly inquiryWeavesFile;
|
|
9
10
|
constructor(dataDir?: string);
|
|
10
11
|
connect(): Promise<void>;
|
|
11
12
|
disconnect(): Promise<void>;
|
|
@@ -25,9 +26,13 @@ export declare class JsonlProvider implements StorageProvider {
|
|
|
25
26
|
getCeremoniesByDirection(direction: DirectionName): Promise<CeremonyLog[]>;
|
|
26
27
|
getCeremoniesByType(type: CeremonyType): Promise<CeremonyLog[]>;
|
|
27
28
|
getAllCeremonies(limit?: number): Promise<CeremonyLog[]>;
|
|
29
|
+
registerInquiryWeave(record: WeaveRecord): Promise<void>;
|
|
30
|
+
getInquiryWeave(id: string): Promise<WeaveRecord | null>;
|
|
31
|
+
listInquiryWeaves(filters?: InquiryWeaveFilters): Promise<WeaveRecord[]>;
|
|
28
32
|
private readNodes;
|
|
29
33
|
private readEdges;
|
|
30
34
|
private readCeremonies;
|
|
35
|
+
private readInquiryWeaves;
|
|
31
36
|
private upsertById;
|
|
32
37
|
private upsertEdge;
|
|
33
38
|
private parseNode;
|
package/dist/jsonl.js
CHANGED
|
@@ -7,11 +7,13 @@ export class JsonlProvider {
|
|
|
7
7
|
nodesFile;
|
|
8
8
|
edgesFile;
|
|
9
9
|
ceremoniesFile;
|
|
10
|
+
inquiryWeavesFile;
|
|
10
11
|
constructor(dataDir) {
|
|
11
12
|
this.dataDir = path.resolve(dataDir ?? resolveProjectDataDir());
|
|
12
13
|
this.nodesFile = path.join(this.dataDir, 'nodes.jsonl');
|
|
13
14
|
this.edgesFile = path.join(this.dataDir, 'edges.jsonl');
|
|
14
15
|
this.ceremoniesFile = path.join(this.dataDir, 'ceremonies.jsonl');
|
|
16
|
+
this.inquiryWeavesFile = path.join(this.dataDir, 'inquiry-weaves.jsonl');
|
|
15
17
|
}
|
|
16
18
|
async connect() {
|
|
17
19
|
ensureDirectory(this.dataDir);
|
|
@@ -118,6 +120,15 @@ export class JsonlProvider {
|
|
|
118
120
|
.slice(0, limit)
|
|
119
121
|
.map((ceremony) => this.parseCeremony(ceremony));
|
|
120
122
|
}
|
|
123
|
+
async registerInquiryWeave(record) {
|
|
124
|
+
await this.upsertById(this.inquiryWeavesFile, record);
|
|
125
|
+
}
|
|
126
|
+
async getInquiryWeave(id) {
|
|
127
|
+
return this.readInquiryWeaves().find((record) => record.id === id) ?? null;
|
|
128
|
+
}
|
|
129
|
+
async listInquiryWeaves(filters = {}) {
|
|
130
|
+
return this.readInquiryWeaves().filter((record) => matchesInquiryWeaveFilters(record, filters));
|
|
131
|
+
}
|
|
121
132
|
readNodes() {
|
|
122
133
|
return readJsonl(this.nodesFile);
|
|
123
134
|
}
|
|
@@ -127,6 +138,9 @@ export class JsonlProvider {
|
|
|
127
138
|
readCeremonies() {
|
|
128
139
|
return readJsonl(this.ceremoniesFile);
|
|
129
140
|
}
|
|
141
|
+
readInquiryWeaves() {
|
|
142
|
+
return readJsonl(this.inquiryWeavesFile);
|
|
143
|
+
}
|
|
130
144
|
async upsertById(filePath, item) {
|
|
131
145
|
await withWriteLock(filePath, () => {
|
|
132
146
|
const nextRecords = readJsonl(filePath).filter((candidate) => candidate.id !== item.id);
|
|
@@ -254,6 +268,21 @@ function edgeKey(edge) {
|
|
|
254
268
|
function sortByNewest(field) {
|
|
255
269
|
return (left, right) => Date.parse(right[field]) - Date.parse(left[field]);
|
|
256
270
|
}
|
|
271
|
+
function matchesInquiryWeaveFilters(record, filters) {
|
|
272
|
+
if (filters.episode_path !== undefined && record.episode?.path !== filters.episode_path) {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
if (filters.episode_number !== undefined && record.episode?.number !== filters.episode_number) {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
if (filters.issue !== undefined && record.issue !== filters.issue) {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
if (filters.artefact !== undefined && record.artefact?.id !== filters.artefact) {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
257
286
|
function isProcessAlive(pid) {
|
|
258
287
|
try {
|
|
259
288
|
// Signal 0 checks process existence without sending a real signal.
|
package/dist/neon.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Neon (Postgres) Storage Provider
|
|
3
3
|
*/
|
|
4
4
|
import type { DirectionName, NodeType, CeremonyType } from '@medicine-wheel/ontology-core';
|
|
5
|
-
import type { StorageProvider, RelationalNode, RelationalEdge, CeremonyLog } from './interface.js';
|
|
5
|
+
import type { StorageProvider, RelationalNode, RelationalEdge, CeremonyLog, WeaveRecord, InquiryWeaveFilters } from './interface.js';
|
|
6
6
|
export declare class NeonProvider implements StorageProvider {
|
|
7
7
|
readonly name = "neon";
|
|
8
8
|
private sql;
|
|
@@ -28,4 +28,9 @@ export declare class NeonProvider implements StorageProvider {
|
|
|
28
28
|
getCeremoniesByType(type: CeremonyType): Promise<CeremonyLog[]>;
|
|
29
29
|
getAllCeremonies(limit?: number): Promise<CeremonyLog[]>;
|
|
30
30
|
private parseCeremony;
|
|
31
|
+
registerInquiryWeave(record: WeaveRecord): Promise<void>;
|
|
32
|
+
getInquiryWeave(id: string): Promise<WeaveRecord | null>;
|
|
33
|
+
listInquiryWeaves(filters?: InquiryWeaveFilters): Promise<WeaveRecord[]>;
|
|
34
|
+
private getAllInquiryWeaves;
|
|
35
|
+
private ensureInquiryWeavesTable;
|
|
31
36
|
}
|
package/dist/neon.js
CHANGED
|
@@ -10,6 +10,7 @@ export class NeonProvider {
|
|
|
10
10
|
throw new Error('DATABASE_URL, POSTGRES_URL, or NEON_DATABASE_URL must be set');
|
|
11
11
|
const { neon } = await import('@neondatabase/serverless');
|
|
12
12
|
this.sql = neon(url);
|
|
13
|
+
await this.ensureInquiryWeavesTable();
|
|
13
14
|
}
|
|
14
15
|
async disconnect() {
|
|
15
16
|
this.sql = null;
|
|
@@ -174,6 +175,59 @@ export class NeonProvider {
|
|
|
174
175
|
research_context: row.research_context || undefined,
|
|
175
176
|
};
|
|
176
177
|
}
|
|
178
|
+
// ── Inquiry Weave Operations ──
|
|
179
|
+
async registerInquiryWeave(record) {
|
|
180
|
+
await this.db `
|
|
181
|
+
INSERT INTO inquiry_weaves (id, payload, episode_path, episode_number, issue, artefact_id, updated_at)
|
|
182
|
+
VALUES (
|
|
183
|
+
${record.id},
|
|
184
|
+
${JSON.stringify(record)},
|
|
185
|
+
${record.episode.path},
|
|
186
|
+
${record.episode.number},
|
|
187
|
+
${record.issue},
|
|
188
|
+
${record.artefact.id},
|
|
189
|
+
NOW()
|
|
190
|
+
)
|
|
191
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
192
|
+
payload = EXCLUDED.payload,
|
|
193
|
+
episode_path = EXCLUDED.episode_path,
|
|
194
|
+
episode_number = EXCLUDED.episode_number,
|
|
195
|
+
issue = EXCLUDED.issue,
|
|
196
|
+
artefact_id = EXCLUDED.artefact_id,
|
|
197
|
+
updated_at = NOW()
|
|
198
|
+
`;
|
|
199
|
+
}
|
|
200
|
+
async getInquiryWeave(id) {
|
|
201
|
+
const rows = await this.db `SELECT payload FROM inquiry_weaves WHERE id = ${id}`;
|
|
202
|
+
if (rows.length === 0)
|
|
203
|
+
return null;
|
|
204
|
+
return parseJsonValue(rows[0].payload, null);
|
|
205
|
+
}
|
|
206
|
+
async listInquiryWeaves(filters = {}) {
|
|
207
|
+
const records = await this.getAllInquiryWeaves();
|
|
208
|
+
return records.filter((record) => matchesInquiryWeaveFilters(record, filters));
|
|
209
|
+
}
|
|
210
|
+
async getAllInquiryWeaves() {
|
|
211
|
+
const rows = await this.db `SELECT payload FROM inquiry_weaves ORDER BY updated_at DESC`;
|
|
212
|
+
return rows.map((row) => parseJsonValue(row.payload, null));
|
|
213
|
+
}
|
|
214
|
+
async ensureInquiryWeavesTable() {
|
|
215
|
+
await this.db `
|
|
216
|
+
CREATE TABLE IF NOT EXISTS inquiry_weaves (
|
|
217
|
+
id TEXT PRIMARY KEY,
|
|
218
|
+
payload JSONB NOT NULL,
|
|
219
|
+
episode_path TEXT NOT NULL,
|
|
220
|
+
episode_number INTEGER NOT NULL,
|
|
221
|
+
issue TEXT NOT NULL,
|
|
222
|
+
artefact_id TEXT NOT NULL,
|
|
223
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
224
|
+
)
|
|
225
|
+
`;
|
|
226
|
+
await this.db `CREATE INDEX IF NOT EXISTS idx_inquiry_weaves_episode_path ON inquiry_weaves(episode_path)`;
|
|
227
|
+
await this.db `CREATE INDEX IF NOT EXISTS idx_inquiry_weaves_episode_number ON inquiry_weaves(episode_number)`;
|
|
228
|
+
await this.db `CREATE INDEX IF NOT EXISTS idx_inquiry_weaves_issue ON inquiry_weaves(issue)`;
|
|
229
|
+
await this.db `CREATE INDEX IF NOT EXISTS idx_inquiry_weaves_artefact_id ON inquiry_weaves(artefact_id)`;
|
|
230
|
+
}
|
|
177
231
|
}
|
|
178
232
|
function getConnectionString() {
|
|
179
233
|
return process.env.DATABASE_URL ?? process.env.POSTGRES_URL ?? process.env.NEON_DATABASE_URL;
|
|
@@ -198,3 +252,18 @@ function parseJsonValue(value, fallback) {
|
|
|
198
252
|
}
|
|
199
253
|
return value;
|
|
200
254
|
}
|
|
255
|
+
function matchesInquiryWeaveFilters(record, filters) {
|
|
256
|
+
if (filters.episode_path !== undefined && record.episode?.path !== filters.episode_path) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
if (filters.episode_number !== undefined && record.episode?.number !== filters.episode_number) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
if (filters.issue !== undefined && record.issue !== filters.issue) {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
if (filters.artefact !== undefined && record.artefact?.id !== filters.artefact) {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
return true;
|
|
269
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@medicine-wheel/storage-provider",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@neondatabase/serverless": "^0.10.0",
|
|
18
18
|
"@upstash/redis": "^1.34.3",
|
|
19
|
-
"@medicine-wheel/ontology-core": "^0.4.
|
|
19
|
+
"@medicine-wheel/ontology-core": "^0.4.12"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"typescript": "^5.7.0"
|