@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 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';
@@ -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.11",
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.11"
19
+ "@medicine-wheel/ontology-core": "^0.4.12"
20
20
  },
21
21
  "devDependencies": {
22
22
  "typescript": "^5.7.0"