@medicine-wheel/storage-provider 0.4.11 → 0.4.13

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,8 @@
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, PlanPerspectiveRecord, PlanPerspectiveEpisode, PlanPerspectiveFilters, ProviderType, } from './interface.js';
19
+ export { mergePlanPerspectiveRecords, unionPlanPerspectiveEpisodes, matchesPlanPerspectiveFilters, } from './plan-perspectives.js';
19
20
  export { createProvider, detectProvider } from './factory.js';
20
21
  export { JsonlProvider } from './jsonl.js';
21
22
  export { NeonProvider } from './neon.js';
package/dist/index.js CHANGED
@@ -15,6 +15,8 @@
15
15
  * const node = await store.getNode('elder-1');
16
16
  * ```
17
17
  */
18
+ // Plan Perspective merge/filter semantics (shared across providers)
19
+ export { mergePlanPerspectiveRecords, unionPlanPerspectiveEpisodes, matchesPlanPerspectiveFilters, } from './plan-perspectives.js';
18
20
  // Factory
19
21
  export { createProvider, detectProvider } from './factory.js';
20
22
  // Providers (for direct instantiation)
@@ -13,6 +13,94 @@ 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
+ }
54
+ export interface PlanPerspectiveEpisode {
55
+ path: string;
56
+ number?: number;
57
+ [key: string]: unknown;
58
+ }
59
+ export interface PlanPerspectiveRecord extends Record<string, unknown> {
60
+ id: string;
61
+ perspective: 1;
62
+ plan: {
63
+ session_id: string;
64
+ plan_path?: string;
65
+ plan_filename: string;
66
+ plan_sha256: string;
67
+ captured_at?: string;
68
+ [key: string]: unknown;
69
+ };
70
+ narrative: {
71
+ title: string;
72
+ body_markdown: string;
73
+ mia_context?: string;
74
+ [key: string]: unknown;
75
+ };
76
+ lineage?: {
77
+ user_inputs_path?: string;
78
+ input_count?: number;
79
+ first_input_at?: string;
80
+ last_input_at?: string;
81
+ excerpts?: string[];
82
+ [key: string]: unknown;
83
+ };
84
+ episodes: PlanPerspectiveEpisode[];
85
+ source: {
86
+ package?: string;
87
+ generator?: {
88
+ system?: string;
89
+ agent?: string;
90
+ model?: string;
91
+ producer_session_id?: string;
92
+ [key: string]: unknown;
93
+ };
94
+ registered_at: string;
95
+ updated_at: string;
96
+ [key: string]: unknown;
97
+ };
98
+ }
99
+ export interface PlanPerspectiveFilters {
100
+ episode_path?: string;
101
+ session_id?: string;
102
+ id?: string;
103
+ }
16
104
  export interface StorageProvider {
17
105
  readonly name: string;
18
106
  connect(): Promise<void>;
@@ -33,5 +121,11 @@ export interface StorageProvider {
33
121
  getCeremoniesByDirection(direction: DirectionName): Promise<CeremonyLog[]>;
34
122
  getCeremoniesByType(type: CeremonyType): Promise<CeremonyLog[]>;
35
123
  getAllCeremonies(limit?: number): Promise<CeremonyLog[]>;
124
+ registerInquiryWeave(record: WeaveRecord): Promise<void>;
125
+ getInquiryWeave(id: string): Promise<WeaveRecord | null>;
126
+ listInquiryWeaves(filters?: InquiryWeaveFilters): Promise<WeaveRecord[]>;
127
+ registerPlanPerspective(record: PlanPerspectiveRecord): Promise<PlanPerspectiveRecord>;
128
+ getPlanPerspective(id: string): Promise<PlanPerspectiveRecord | null>;
129
+ listPlanPerspectives(filters?: PlanPerspectiveFilters): Promise<PlanPerspectiveRecord[]>;
36
130
  }
37
131
  export type ProviderType = 'jsonl' | 'neon' | 'redis' | 'auto';
package/dist/jsonl.d.ts CHANGED
@@ -1,11 +1,13 @@
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, PlanPerspectiveRecord, PlanPerspectiveFilters } 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;
10
+ private readonly planPerspectivesFile;
9
11
  constructor(dataDir?: string);
10
12
  connect(): Promise<void>;
11
13
  disconnect(): Promise<void>;
@@ -25,9 +27,17 @@ export declare class JsonlProvider implements StorageProvider {
25
27
  getCeremoniesByDirection(direction: DirectionName): Promise<CeremonyLog[]>;
26
28
  getCeremoniesByType(type: CeremonyType): Promise<CeremonyLog[]>;
27
29
  getAllCeremonies(limit?: number): Promise<CeremonyLog[]>;
30
+ registerInquiryWeave(record: WeaveRecord): Promise<void>;
31
+ getInquiryWeave(id: string): Promise<WeaveRecord | null>;
32
+ listInquiryWeaves(filters?: InquiryWeaveFilters): Promise<WeaveRecord[]>;
33
+ registerPlanPerspective(record: PlanPerspectiveRecord): Promise<PlanPerspectiveRecord>;
34
+ getPlanPerspective(id: string): Promise<PlanPerspectiveRecord | null>;
35
+ listPlanPerspectives(filters?: PlanPerspectiveFilters): Promise<PlanPerspectiveRecord[]>;
28
36
  private readNodes;
29
37
  private readEdges;
30
38
  private readCeremonies;
39
+ private readInquiryWeaves;
40
+ private readPlanPerspectives;
31
41
  private upsertById;
32
42
  private upsertEdge;
33
43
  private parseNode;
package/dist/jsonl.js CHANGED
@@ -1,17 +1,22 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { randomUUID } from 'crypto';
4
+ import { mergePlanPerspectiveRecords, matchesPlanPerspectiveFilters, } from './plan-perspectives.js';
4
5
  export class JsonlProvider {
5
6
  name = 'jsonl';
6
7
  dataDir;
7
8
  nodesFile;
8
9
  edgesFile;
9
10
  ceremoniesFile;
11
+ inquiryWeavesFile;
12
+ planPerspectivesFile;
10
13
  constructor(dataDir) {
11
14
  this.dataDir = path.resolve(dataDir ?? resolveProjectDataDir());
12
15
  this.nodesFile = path.join(this.dataDir, 'nodes.jsonl');
13
16
  this.edgesFile = path.join(this.dataDir, 'edges.jsonl');
14
17
  this.ceremoniesFile = path.join(this.dataDir, 'ceremonies.jsonl');
18
+ this.inquiryWeavesFile = path.join(this.dataDir, 'inquiry-weaves.jsonl');
19
+ this.planPerspectivesFile = path.join(this.dataDir, 'plan-perspectives.jsonl');
15
20
  }
16
21
  async connect() {
17
22
  ensureDirectory(this.dataDir);
@@ -118,6 +123,33 @@ export class JsonlProvider {
118
123
  .slice(0, limit)
119
124
  .map((ceremony) => this.parseCeremony(ceremony));
120
125
  }
126
+ async registerInquiryWeave(record) {
127
+ await this.upsertById(this.inquiryWeavesFile, record);
128
+ }
129
+ async getInquiryWeave(id) {
130
+ return this.readInquiryWeaves().find((record) => record.id === id) ?? null;
131
+ }
132
+ async listInquiryWeaves(filters = {}) {
133
+ return this.readInquiryWeaves().filter((record) => matchesInquiryWeaveFilters(record, filters));
134
+ }
135
+ async registerPlanPerspective(record) {
136
+ let merged = record;
137
+ await withWriteLock(this.planPerspectivesFile, () => {
138
+ const records = readJsonl(this.planPerspectivesFile);
139
+ const existing = records.find((candidate) => candidate.id === record.id);
140
+ merged = existing ? mergePlanPerspectiveRecords(existing, record) : record;
141
+ const nextRecords = records.filter((candidate) => candidate.id !== record.id);
142
+ nextRecords.push(merged);
143
+ writeJsonl(this.planPerspectivesFile, nextRecords);
144
+ });
145
+ return merged;
146
+ }
147
+ async getPlanPerspective(id) {
148
+ return this.readPlanPerspectives().find((record) => record.id === id) ?? null;
149
+ }
150
+ async listPlanPerspectives(filters = {}) {
151
+ return this.readPlanPerspectives().filter((record) => matchesPlanPerspectiveFilters(record, filters));
152
+ }
121
153
  readNodes() {
122
154
  return readJsonl(this.nodesFile);
123
155
  }
@@ -127,6 +159,12 @@ export class JsonlProvider {
127
159
  readCeremonies() {
128
160
  return readJsonl(this.ceremoniesFile);
129
161
  }
162
+ readInquiryWeaves() {
163
+ return readJsonl(this.inquiryWeavesFile);
164
+ }
165
+ readPlanPerspectives() {
166
+ return readJsonl(this.planPerspectivesFile);
167
+ }
130
168
  async upsertById(filePath, item) {
131
169
  await withWriteLock(filePath, () => {
132
170
  const nextRecords = readJsonl(filePath).filter((candidate) => candidate.id !== item.id);
@@ -254,6 +292,21 @@ function edgeKey(edge) {
254
292
  function sortByNewest(field) {
255
293
  return (left, right) => Date.parse(right[field]) - Date.parse(left[field]);
256
294
  }
295
+ function matchesInquiryWeaveFilters(record, filters) {
296
+ if (filters.episode_path !== undefined && record.episode?.path !== filters.episode_path) {
297
+ return false;
298
+ }
299
+ if (filters.episode_number !== undefined && record.episode?.number !== filters.episode_number) {
300
+ return false;
301
+ }
302
+ if (filters.issue !== undefined && record.issue !== filters.issue) {
303
+ return false;
304
+ }
305
+ if (filters.artefact !== undefined && record.artefact?.id !== filters.artefact) {
306
+ return false;
307
+ }
308
+ return true;
309
+ }
257
310
  function isProcessAlive(pid) {
258
311
  try {
259
312
  // 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, PlanPerspectiveRecord, PlanPerspectiveFilters } from './interface.js';
6
6
  export declare class NeonProvider implements StorageProvider {
7
7
  readonly name = "neon";
8
8
  private sql;
@@ -28,4 +28,13 @@ 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;
36
+ registerPlanPerspective(record: PlanPerspectiveRecord): Promise<PlanPerspectiveRecord>;
37
+ getPlanPerspective(id: string): Promise<PlanPerspectiveRecord | null>;
38
+ listPlanPerspectives(filters?: PlanPerspectiveFilters): Promise<PlanPerspectiveRecord[]>;
39
+ private ensurePlanPerspectivesTable;
31
40
  }
package/dist/neon.js CHANGED
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Neon (Postgres) Storage Provider
3
3
  */
4
+ import { mergePlanPerspectiveRecords, matchesPlanPerspectiveFilters, } from './plan-perspectives.js';
4
5
  export class NeonProvider {
5
6
  name = 'neon';
6
7
  sql = null;
@@ -10,6 +11,8 @@ export class NeonProvider {
10
11
  throw new Error('DATABASE_URL, POSTGRES_URL, or NEON_DATABASE_URL must be set');
11
12
  const { neon } = await import('@neondatabase/serverless');
12
13
  this.sql = neon(url);
14
+ await this.ensureInquiryWeavesTable();
15
+ await this.ensurePlanPerspectivesTable();
13
16
  }
14
17
  async disconnect() {
15
18
  this.sql = null;
@@ -174,6 +177,126 @@ export class NeonProvider {
174
177
  research_context: row.research_context || undefined,
175
178
  };
176
179
  }
180
+ // ── Inquiry Weave Operations ──
181
+ async registerInquiryWeave(record) {
182
+ await this.db `
183
+ INSERT INTO inquiry_weaves (id, payload, episode_path, episode_number, issue, artefact_id, updated_at)
184
+ VALUES (
185
+ ${record.id},
186
+ ${JSON.stringify(record)},
187
+ ${record.episode.path},
188
+ ${record.episode.number},
189
+ ${record.issue},
190
+ ${record.artefact.id},
191
+ NOW()
192
+ )
193
+ ON CONFLICT (id) DO UPDATE SET
194
+ payload = EXCLUDED.payload,
195
+ episode_path = EXCLUDED.episode_path,
196
+ episode_number = EXCLUDED.episode_number,
197
+ issue = EXCLUDED.issue,
198
+ artefact_id = EXCLUDED.artefact_id,
199
+ updated_at = NOW()
200
+ `;
201
+ }
202
+ async getInquiryWeave(id) {
203
+ const rows = await this.db `SELECT payload FROM inquiry_weaves WHERE id = ${id}`;
204
+ if (rows.length === 0)
205
+ return null;
206
+ return parseJsonValue(rows[0].payload, null);
207
+ }
208
+ async listInquiryWeaves(filters = {}) {
209
+ const records = await this.getAllInquiryWeaves();
210
+ return records.filter((record) => matchesInquiryWeaveFilters(record, filters));
211
+ }
212
+ async getAllInquiryWeaves() {
213
+ const rows = await this.db `SELECT payload FROM inquiry_weaves ORDER BY updated_at DESC`;
214
+ return rows.map((row) => parseJsonValue(row.payload, null));
215
+ }
216
+ async ensureInquiryWeavesTable() {
217
+ await this.db `
218
+ CREATE TABLE IF NOT EXISTS inquiry_weaves (
219
+ id TEXT PRIMARY KEY,
220
+ payload JSONB NOT NULL,
221
+ episode_path TEXT NOT NULL,
222
+ episode_number INTEGER NOT NULL,
223
+ issue TEXT NOT NULL,
224
+ artefact_id TEXT NOT NULL,
225
+ updated_at TIMESTAMPTZ DEFAULT NOW()
226
+ )
227
+ `;
228
+ await this.db `CREATE INDEX IF NOT EXISTS idx_inquiry_weaves_episode_path ON inquiry_weaves(episode_path)`;
229
+ await this.db `CREATE INDEX IF NOT EXISTS idx_inquiry_weaves_episode_number ON inquiry_weaves(episode_number)`;
230
+ await this.db `CREATE INDEX IF NOT EXISTS idx_inquiry_weaves_issue ON inquiry_weaves(issue)`;
231
+ await this.db `CREATE INDEX IF NOT EXISTS idx_inquiry_weaves_artefact_id ON inquiry_weaves(artefact_id)`;
232
+ }
233
+ // ── Plan Perspective Operations ──
234
+ async registerPlanPerspective(record) {
235
+ const existing = await this.getPlanPerspective(record.id);
236
+ const merged = existing ? mergePlanPerspectiveRecords(existing, record) : record;
237
+ const episodePaths = merged.episodes.map((episode) => episode.path);
238
+ await this.db `
239
+ INSERT INTO plan_perspectives (id, payload, session_id, episode_paths, updated_at)
240
+ VALUES (
241
+ ${merged.id},
242
+ ${JSON.stringify(merged)},
243
+ ${merged.plan.session_id},
244
+ ${episodePaths},
245
+ NOW()
246
+ )
247
+ ON CONFLICT (id) DO UPDATE SET
248
+ payload = EXCLUDED.payload,
249
+ session_id = EXCLUDED.session_id,
250
+ episode_paths = EXCLUDED.episode_paths,
251
+ updated_at = NOW()
252
+ `;
253
+ return merged;
254
+ }
255
+ async getPlanPerspective(id) {
256
+ const rows = await this.db `SELECT payload FROM plan_perspectives WHERE id = ${id}`;
257
+ if (rows.length === 0)
258
+ return null;
259
+ return parseJsonValue(rows[0].payload, null);
260
+ }
261
+ async listPlanPerspectives(filters = {}) {
262
+ let rows;
263
+ if (filters.id !== undefined) {
264
+ rows = await this.db `SELECT payload FROM plan_perspectives WHERE id = ${filters.id}`;
265
+ }
266
+ else if (filters.session_id !== undefined) {
267
+ rows = await this.db `
268
+ SELECT payload FROM plan_perspectives
269
+ WHERE session_id = ${filters.session_id}
270
+ ORDER BY updated_at DESC
271
+ `;
272
+ }
273
+ else if (filters.episode_path !== undefined) {
274
+ rows = await this.db `
275
+ SELECT payload FROM plan_perspectives
276
+ WHERE episode_paths @> ARRAY[${filters.episode_path}]
277
+ ORDER BY updated_at DESC
278
+ `;
279
+ }
280
+ else {
281
+ rows = await this.db `SELECT payload FROM plan_perspectives ORDER BY updated_at DESC`;
282
+ }
283
+ return rows
284
+ .map((row) => parseJsonValue(row.payload, null))
285
+ .filter((record) => record !== null && matchesPlanPerspectiveFilters(record, filters));
286
+ }
287
+ async ensurePlanPerspectivesTable() {
288
+ await this.db `
289
+ CREATE TABLE IF NOT EXISTS plan_perspectives (
290
+ id TEXT PRIMARY KEY,
291
+ payload JSONB NOT NULL,
292
+ session_id TEXT NOT NULL,
293
+ episode_paths TEXT[] NOT NULL DEFAULT '{}',
294
+ updated_at TIMESTAMPTZ DEFAULT NOW()
295
+ )
296
+ `;
297
+ await this.db `CREATE INDEX IF NOT EXISTS idx_plan_perspectives_session_id ON plan_perspectives(session_id)`;
298
+ await this.db `CREATE INDEX IF NOT EXISTS idx_plan_perspectives_episode_paths ON plan_perspectives USING GIN(episode_paths)`;
299
+ }
177
300
  }
178
301
  function getConnectionString() {
179
302
  return process.env.DATABASE_URL ?? process.env.POSTGRES_URL ?? process.env.NEON_DATABASE_URL;
@@ -198,3 +321,18 @@ function parseJsonValue(value, fallback) {
198
321
  }
199
322
  return value;
200
323
  }
324
+ function matchesInquiryWeaveFilters(record, filters) {
325
+ if (filters.episode_path !== undefined && record.episode?.path !== filters.episode_path) {
326
+ return false;
327
+ }
328
+ if (filters.episode_number !== undefined && record.episode?.number !== filters.episode_number) {
329
+ return false;
330
+ }
331
+ if (filters.issue !== undefined && record.issue !== filters.issue) {
332
+ return false;
333
+ }
334
+ if (filters.artefact !== undefined && record.artefact?.id !== filters.artefact) {
335
+ return false;
336
+ }
337
+ return true;
338
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Plan Perspective merge and filter semantics shared by every provider.
3
+ *
4
+ * Medicine-wheel stores relational projections of Miette perspectives; the
5
+ * session file remains authoritative. Nothing here generates or rewrites
6
+ * narrative prose — records pass through untouched apart from the episode
7
+ * union and source timestamps pinned by @miadi/plan-insight's
8
+ * mergePerspectiveRecords contract.
9
+ */
10
+ import type { PlanPerspectiveEpisode, PlanPerspectiveFilters, PlanPerspectiveRecord } from './interface.js';
11
+ /** Union episode relations by exact path; a known number backfills an entry missing one. */
12
+ export declare function unionPlanPerspectiveEpisodes(episodes: readonly PlanPerspectiveEpisode[]): PlanPerspectiveEpisode[];
13
+ /**
14
+ * Upsert merge: the incoming projection wins, episodes union by path, and the
15
+ * original registration timestamp survives while updated_at refreshes.
16
+ */
17
+ export declare function mergePlanPerspectiveRecords(existing: PlanPerspectiveRecord, incoming: PlanPerspectiveRecord): PlanPerspectiveRecord;
18
+ export declare function matchesPlanPerspectiveFilters(record: PlanPerspectiveRecord, filters: PlanPerspectiveFilters): boolean;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Plan Perspective merge and filter semantics shared by every provider.
3
+ *
4
+ * Medicine-wheel stores relational projections of Miette perspectives; the
5
+ * session file remains authoritative. Nothing here generates or rewrites
6
+ * narrative prose — records pass through untouched apart from the episode
7
+ * union and source timestamps pinned by @miadi/plan-insight's
8
+ * mergePerspectiveRecords contract.
9
+ */
10
+ /** Union episode relations by exact path; a known number backfills an entry missing one. */
11
+ export function unionPlanPerspectiveEpisodes(episodes) {
12
+ const byPath = new Map();
13
+ for (const episode of episodes) {
14
+ if (typeof episode?.path !== 'string' || episode.path.length === 0)
15
+ continue;
16
+ const existing = byPath.get(episode.path);
17
+ if (!existing) {
18
+ byPath.set(episode.path, { ...episode });
19
+ }
20
+ else if (existing.number === undefined && typeof episode.number === 'number') {
21
+ existing.number = episode.number;
22
+ }
23
+ }
24
+ return [...byPath.values()];
25
+ }
26
+ /**
27
+ * Upsert merge: the incoming projection wins, episodes union by path, and the
28
+ * original registration timestamp survives while updated_at refreshes.
29
+ */
30
+ export function mergePlanPerspectiveRecords(existing, incoming) {
31
+ return {
32
+ ...incoming,
33
+ episodes: unionPlanPerspectiveEpisodes([...existing.episodes, ...incoming.episodes]),
34
+ source: {
35
+ ...incoming.source,
36
+ registered_at: existing.source.registered_at,
37
+ updated_at: incoming.source.updated_at,
38
+ },
39
+ };
40
+ }
41
+ export function matchesPlanPerspectiveFilters(record, filters) {
42
+ if (filters.id !== undefined && record.id !== filters.id) {
43
+ return false;
44
+ }
45
+ if (filters.session_id !== undefined && record.plan?.session_id !== filters.session_id) {
46
+ return false;
47
+ }
48
+ if (filters.episode_path !== undefined &&
49
+ !record.episodes?.some((episode) => episode?.path === filters.episode_path)) {
50
+ return false;
51
+ }
52
+ return true;
53
+ }
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.13",
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.13"
20
20
  },
21
21
  "devDependencies": {
22
22
  "typescript": "^5.7.0"