@medicine-wheel/storage-provider 0.4.12 → 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 +2 -1
- package/dist/index.js +2 -0
- package/dist/interface.d.ts +53 -0
- package/dist/jsonl.d.ts +6 -1
- package/dist/jsonl.js +24 -0
- package/dist/neon.d.ts +5 -1
- package/dist/neon.js +69 -0
- package/dist/plan-perspectives.d.ts +18 -0
- package/dist/plan-perspectives.js +53 -0
- package/package.json +2 -2
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, WeaveRecord, WeaveSyncState, InquiryWeaveFilters, 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)
|
package/dist/interface.d.ts
CHANGED
|
@@ -51,6 +51,56 @@ export interface InquiryWeaveFilters {
|
|
|
51
51
|
issue?: string;
|
|
52
52
|
artefact?: string;
|
|
53
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
|
+
}
|
|
54
104
|
export interface StorageProvider {
|
|
55
105
|
readonly name: string;
|
|
56
106
|
connect(): Promise<void>;
|
|
@@ -74,5 +124,8 @@ export interface StorageProvider {
|
|
|
74
124
|
registerInquiryWeave(record: WeaveRecord): Promise<void>;
|
|
75
125
|
getInquiryWeave(id: string): Promise<WeaveRecord | null>;
|
|
76
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[]>;
|
|
77
130
|
}
|
|
78
131
|
export type ProviderType = 'jsonl' | 'neon' | 'redis' | 'auto';
|
package/dist/jsonl.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DirectionName, NodeType, CeremonyType } from '@medicine-wheel/ontology-core';
|
|
2
|
-
import type { StorageProvider, RelationalNode, RelationalEdge, CeremonyLog, WeaveRecord, InquiryWeaveFilters } 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;
|
|
@@ -7,6 +7,7 @@ export declare class JsonlProvider implements StorageProvider {
|
|
|
7
7
|
private readonly edgesFile;
|
|
8
8
|
private readonly ceremoniesFile;
|
|
9
9
|
private readonly inquiryWeavesFile;
|
|
10
|
+
private readonly planPerspectivesFile;
|
|
10
11
|
constructor(dataDir?: string);
|
|
11
12
|
connect(): Promise<void>;
|
|
12
13
|
disconnect(): Promise<void>;
|
|
@@ -29,10 +30,14 @@ export declare class JsonlProvider implements StorageProvider {
|
|
|
29
30
|
registerInquiryWeave(record: WeaveRecord): Promise<void>;
|
|
30
31
|
getInquiryWeave(id: string): Promise<WeaveRecord | null>;
|
|
31
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[]>;
|
|
32
36
|
private readNodes;
|
|
33
37
|
private readEdges;
|
|
34
38
|
private readCeremonies;
|
|
35
39
|
private readInquiryWeaves;
|
|
40
|
+
private readPlanPerspectives;
|
|
36
41
|
private upsertById;
|
|
37
42
|
private upsertEdge;
|
|
38
43
|
private parseNode;
|
package/dist/jsonl.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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;
|
|
@@ -8,12 +9,14 @@ export class JsonlProvider {
|
|
|
8
9
|
edgesFile;
|
|
9
10
|
ceremoniesFile;
|
|
10
11
|
inquiryWeavesFile;
|
|
12
|
+
planPerspectivesFile;
|
|
11
13
|
constructor(dataDir) {
|
|
12
14
|
this.dataDir = path.resolve(dataDir ?? resolveProjectDataDir());
|
|
13
15
|
this.nodesFile = path.join(this.dataDir, 'nodes.jsonl');
|
|
14
16
|
this.edgesFile = path.join(this.dataDir, 'edges.jsonl');
|
|
15
17
|
this.ceremoniesFile = path.join(this.dataDir, 'ceremonies.jsonl');
|
|
16
18
|
this.inquiryWeavesFile = path.join(this.dataDir, 'inquiry-weaves.jsonl');
|
|
19
|
+
this.planPerspectivesFile = path.join(this.dataDir, 'plan-perspectives.jsonl');
|
|
17
20
|
}
|
|
18
21
|
async connect() {
|
|
19
22
|
ensureDirectory(this.dataDir);
|
|
@@ -129,6 +132,24 @@ export class JsonlProvider {
|
|
|
129
132
|
async listInquiryWeaves(filters = {}) {
|
|
130
133
|
return this.readInquiryWeaves().filter((record) => matchesInquiryWeaveFilters(record, filters));
|
|
131
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
|
+
}
|
|
132
153
|
readNodes() {
|
|
133
154
|
return readJsonl(this.nodesFile);
|
|
134
155
|
}
|
|
@@ -141,6 +162,9 @@ export class JsonlProvider {
|
|
|
141
162
|
readInquiryWeaves() {
|
|
142
163
|
return readJsonl(this.inquiryWeavesFile);
|
|
143
164
|
}
|
|
165
|
+
readPlanPerspectives() {
|
|
166
|
+
return readJsonl(this.planPerspectivesFile);
|
|
167
|
+
}
|
|
144
168
|
async upsertById(filePath, item) {
|
|
145
169
|
await withWriteLock(filePath, () => {
|
|
146
170
|
const nextRecords = readJsonl(filePath).filter((candidate) => candidate.id !== item.id);
|
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, WeaveRecord, InquiryWeaveFilters } 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;
|
|
@@ -33,4 +33,8 @@ export declare class NeonProvider implements StorageProvider {
|
|
|
33
33
|
listInquiryWeaves(filters?: InquiryWeaveFilters): Promise<WeaveRecord[]>;
|
|
34
34
|
private getAllInquiryWeaves;
|
|
35
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;
|
|
36
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;
|
|
@@ -11,6 +12,7 @@ export class NeonProvider {
|
|
|
11
12
|
const { neon } = await import('@neondatabase/serverless');
|
|
12
13
|
this.sql = neon(url);
|
|
13
14
|
await this.ensureInquiryWeavesTable();
|
|
15
|
+
await this.ensurePlanPerspectivesTable();
|
|
14
16
|
}
|
|
15
17
|
async disconnect() {
|
|
16
18
|
this.sql = null;
|
|
@@ -228,6 +230,73 @@ export class NeonProvider {
|
|
|
228
230
|
await this.db `CREATE INDEX IF NOT EXISTS idx_inquiry_weaves_issue ON inquiry_weaves(issue)`;
|
|
229
231
|
await this.db `CREATE INDEX IF NOT EXISTS idx_inquiry_weaves_artefact_id ON inquiry_weaves(artefact_id)`;
|
|
230
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
|
+
}
|
|
231
300
|
}
|
|
232
301
|
function getConnectionString() {
|
|
233
302
|
return process.env.DATABASE_URL ?? process.env.POSTGRES_URL ?? process.env.NEON_DATABASE_URL;
|
|
@@ -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.
|
|
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.
|
|
19
|
+
"@medicine-wheel/ontology-core": "^0.4.13"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"typescript": "^5.7.0"
|