@medicine-wheel/storage-provider 0.5.1 → 0.5.3
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/README.md +6 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/inquiry-weaves.d.ts +18 -0
- package/dist/inquiry-weaves.js +36 -0
- package/dist/jsonl.js +14 -17
- package/dist/neon.js +2 -17
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Abstract storage layer for Medicine Wheel data with JSONL and Neon backends, plus a future Redis path.
|
|
4
4
|
|
|
5
|
+
> [!WARNING]
|
|
6
|
+
> **Experimental alpha.** Part of the Medicine Wheel Developer Suite, which is
|
|
7
|
+
> under active development. APIs change between patch versions and all packages
|
|
8
|
+
> move in lockstep — pin exact versions. See
|
|
9
|
+
> [ALPHA.md](https://github.com/jgwill/medicine-wheel/blob/main/ALPHA.md).
|
|
10
|
+
|
|
5
11
|
## Install
|
|
6
12
|
|
|
7
13
|
```bash
|
package/dist/index.d.ts
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
export type { StorageProvider, RelationalNode, RelationalEdge, CeremonyLog, WeaveRecord, WeaveSyncState, InquiryWeaveFilters, PlanPerspectiveRecord, PlanPerspectiveEpisode, PlanPerspectiveFilters, CeremonialPhase, DiaryEntryType, DiaryEntryLocation, DiaryEntryMetadata, DiaryEntryRecord, DiaryEntryFilters, CeremonyEventKind, CeremonyEventParticipant, CeremonyEventRecord, CeremonyEventFilters, ProviderType, NodePatch, EdgePatch, } from './interface.js';
|
|
19
19
|
export { NodeNotFoundError, EdgeNotFoundError, NodeHasRelationsError, } from './interface.js';
|
|
20
20
|
export { mergePlanPerspectiveRecords, unionPlanPerspectiveEpisodes, matchesPlanPerspectiveFilters, } from './plan-perspectives.js';
|
|
21
|
+
export { matchesInquiryWeaveFilters, filterInquiryWeaves, } from './inquiry-weaves.js';
|
|
21
22
|
export { matchesDiaryEntryFilters, filterAndOrderDiaryEntries, } from './diary-records.js';
|
|
22
23
|
export { matchesCeremonyEventFilters } from './ceremony-events.js';
|
|
23
24
|
export { createProvider, detectProvider } from './factory.js';
|
package/dist/index.js
CHANGED
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
export { NodeNotFoundError, EdgeNotFoundError, NodeHasRelationsError, } from './interface.js';
|
|
20
20
|
// Plan Perspective merge/filter semantics (shared across providers)
|
|
21
21
|
export { mergePlanPerspectiveRecords, unionPlanPerspectiveEpisodes, matchesPlanPerspectiveFilters, } from './plan-perspectives.js';
|
|
22
|
+
// Inquiry Weave filter semantics (shared across providers)
|
|
23
|
+
export { matchesInquiryWeaveFilters, filterInquiryWeaves, } from './inquiry-weaves.js';
|
|
22
24
|
// Ceremonial Diary filter/ordering semantics (shared across providers)
|
|
23
25
|
export { matchesDiaryEntryFilters, filterAndOrderDiaryEntries, } from './diary-records.js';
|
|
24
26
|
// Ceremony Event filter semantics (shared across providers)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inquiry Weave filter semantics — shared across providers.
|
|
3
|
+
*
|
|
4
|
+
* Both the JSONL and Postgres providers answer the same questions about a
|
|
5
|
+
* weave; keeping the predicate in one place stops the two from drifting apart
|
|
6
|
+
* on what a filter means.
|
|
7
|
+
*/
|
|
8
|
+
import type { InquiryWeaveFilters, WeaveRecord } from './interface.js';
|
|
9
|
+
export declare function matchesInquiryWeaveFilters(record: WeaveRecord, filters: InquiryWeaveFilters): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Filter a batch of weave rows, dropping anything that did not survive the read.
|
|
12
|
+
*
|
|
13
|
+
* A Postgres payload that fails to parse arrives here as null, and every
|
|
14
|
+
* sibling record collection drops those before filtering. Without the guard a
|
|
15
|
+
* single unreadable row throws while reading `record.episode`, and the whole
|
|
16
|
+
* listing answers with an error instead of the weaves that are intact.
|
|
17
|
+
*/
|
|
18
|
+
export declare function filterInquiryWeaves(records: readonly (WeaveRecord | null | undefined)[], filters?: InquiryWeaveFilters): WeaveRecord[];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inquiry Weave filter semantics — shared across providers.
|
|
3
|
+
*
|
|
4
|
+
* Both the JSONL and Postgres providers answer the same questions about a
|
|
5
|
+
* weave; keeping the predicate in one place stops the two from drifting apart
|
|
6
|
+
* on what a filter means.
|
|
7
|
+
*/
|
|
8
|
+
export function matchesInquiryWeaveFilters(record, filters) {
|
|
9
|
+
if (filters.episode_path !== undefined && record.episode?.path !== filters.episode_path) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
if (filters.episode_number !== undefined && record.episode?.number !== filters.episode_number) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
if (filters.issue !== undefined && record.issue !== filters.issue) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
if (filters.artefact !== undefined && record.artefact?.id !== filters.artefact) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Filter a batch of weave rows, dropping anything that did not survive the read.
|
|
25
|
+
*
|
|
26
|
+
* A Postgres payload that fails to parse arrives here as null, and every
|
|
27
|
+
* sibling record collection drops those before filtering. Without the guard a
|
|
28
|
+
* single unreadable row throws while reading `record.episode`, and the whole
|
|
29
|
+
* listing answers with an error instead of the weaves that are intact.
|
|
30
|
+
*/
|
|
31
|
+
export function filterInquiryWeaves(records, filters = {}) {
|
|
32
|
+
return records.filter((record) => record !== null &&
|
|
33
|
+
record !== undefined &&
|
|
34
|
+
typeof record === 'object' &&
|
|
35
|
+
matchesInquiryWeaveFilters(record, filters));
|
|
36
|
+
}
|
package/dist/jsonl.js
CHANGED
|
@@ -3,6 +3,7 @@ import * as path from 'path';
|
|
|
3
3
|
import { randomUUID } from 'crypto';
|
|
4
4
|
import { NodeNotFoundError, EdgeNotFoundError, NodeHasRelationsError, } from './interface.js';
|
|
5
5
|
import { mergePlanPerspectiveRecords, matchesPlanPerspectiveFilters, } from './plan-perspectives.js';
|
|
6
|
+
import { filterInquiryWeaves } from './inquiry-weaves.js';
|
|
6
7
|
import { filterAndOrderDiaryEntries } from './diary-records.js';
|
|
7
8
|
import { matchesCeremonyEventFilters } from './ceremony-events.js';
|
|
8
9
|
export class JsonlProvider {
|
|
@@ -205,7 +206,7 @@ export class JsonlProvider {
|
|
|
205
206
|
return this.readInquiryWeaves().find((record) => record.id === id) ?? null;
|
|
206
207
|
}
|
|
207
208
|
async listInquiryWeaves(filters = {}) {
|
|
208
|
-
return this.readInquiryWeaves()
|
|
209
|
+
return filterInquiryWeaves(this.readInquiryWeaves(), filters);
|
|
209
210
|
}
|
|
210
211
|
async registerPlanPerspective(record) {
|
|
211
212
|
let merged = record;
|
|
@@ -402,21 +403,6 @@ function edgeKey(edge) {
|
|
|
402
403
|
function sortByNewest(field) {
|
|
403
404
|
return (left, right) => Date.parse(right[field]) - Date.parse(left[field]);
|
|
404
405
|
}
|
|
405
|
-
function matchesInquiryWeaveFilters(record, filters) {
|
|
406
|
-
if (filters.episode_path !== undefined && record.episode?.path !== filters.episode_path) {
|
|
407
|
-
return false;
|
|
408
|
-
}
|
|
409
|
-
if (filters.episode_number !== undefined && record.episode?.number !== filters.episode_number) {
|
|
410
|
-
return false;
|
|
411
|
-
}
|
|
412
|
-
if (filters.issue !== undefined && record.issue !== filters.issue) {
|
|
413
|
-
return false;
|
|
414
|
-
}
|
|
415
|
-
if (filters.artefact !== undefined && record.artefact?.id !== filters.artefact) {
|
|
416
|
-
return false;
|
|
417
|
-
}
|
|
418
|
-
return true;
|
|
419
|
-
}
|
|
420
406
|
function isProcessAlive(pid) {
|
|
421
407
|
try {
|
|
422
408
|
// Signal 0 checks process existence without sending a real signal.
|
|
@@ -428,6 +414,13 @@ function isProcessAlive(pid) {
|
|
|
428
414
|
}
|
|
429
415
|
}
|
|
430
416
|
function isLockStale(lockPath) {
|
|
417
|
+
let mtimeMs;
|
|
418
|
+
try {
|
|
419
|
+
mtimeMs = fs.statSync(lockPath).mtimeMs;
|
|
420
|
+
}
|
|
421
|
+
catch {
|
|
422
|
+
return false; // Lock already gone — nothing to reap.
|
|
423
|
+
}
|
|
431
424
|
try {
|
|
432
425
|
const content = fs.readFileSync(lockPath, 'utf-8').trim();
|
|
433
426
|
const parsed = JSON.parse(content);
|
|
@@ -442,7 +435,11 @@ function isLockStale(lockPath) {
|
|
|
442
435
|
return lockAgeMs > 30_000;
|
|
443
436
|
}
|
|
444
437
|
catch {
|
|
445
|
-
|
|
438
|
+
// A lock file we cannot read names no owner: a writer that died between
|
|
439
|
+
// creating the file and stamping it, or a foreign lock format. Judging it
|
|
440
|
+
// permanently held would leave the entity unwritable forever, so fall back
|
|
441
|
+
// to its age — the same grace period a stamped lock gets.
|
|
442
|
+
return Date.now() - mtimeMs > 30_000;
|
|
446
443
|
}
|
|
447
444
|
}
|
|
448
445
|
async function acquireWriteLock(lockPath) {
|
package/dist/neon.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { NodeNotFoundError, EdgeNotFoundError, NodeHasRelationsError, } from './interface.js';
|
|
5
5
|
import { mergePlanPerspectiveRecords, matchesPlanPerspectiveFilters, } from './plan-perspectives.js';
|
|
6
|
+
import { filterInquiryWeaves } from './inquiry-weaves.js';
|
|
6
7
|
import { filterAndOrderDiaryEntries } from './diary-records.js';
|
|
7
8
|
import { matchesCeremonyEventFilters } from './ceremony-events.js';
|
|
8
9
|
export class NeonProvider {
|
|
@@ -283,8 +284,7 @@ export class NeonProvider {
|
|
|
283
284
|
return parseJsonValue(rows[0].payload, null);
|
|
284
285
|
}
|
|
285
286
|
async listInquiryWeaves(filters = {}) {
|
|
286
|
-
|
|
287
|
-
return records.filter((record) => matchesInquiryWeaveFilters(record, filters));
|
|
287
|
+
return filterInquiryWeaves(await this.getAllInquiryWeaves(), filters);
|
|
288
288
|
}
|
|
289
289
|
async getAllInquiryWeaves() {
|
|
290
290
|
const rows = await this.db `SELECT payload FROM inquiry_weaves ORDER BY updated_at DESC`;
|
|
@@ -513,18 +513,3 @@ function parseJsonValue(value, fallback) {
|
|
|
513
513
|
}
|
|
514
514
|
return value;
|
|
515
515
|
}
|
|
516
|
-
function matchesInquiryWeaveFilters(record, filters) {
|
|
517
|
-
if (filters.episode_path !== undefined && record.episode?.path !== filters.episode_path) {
|
|
518
|
-
return false;
|
|
519
|
-
}
|
|
520
|
-
if (filters.episode_number !== undefined && record.episode?.number !== filters.episode_number) {
|
|
521
|
-
return false;
|
|
522
|
-
}
|
|
523
|
-
if (filters.issue !== undefined && record.issue !== filters.issue) {
|
|
524
|
-
return false;
|
|
525
|
-
}
|
|
526
|
-
if (filters.artefact !== undefined && record.artefact?.id !== filters.artefact) {
|
|
527
|
-
return false;
|
|
528
|
-
}
|
|
529
|
-
return true;
|
|
530
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@medicine-wheel/storage-provider",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -16,9 +16,14 @@
|
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@neondatabase/serverless": "^0.10.0",
|
|
18
18
|
"@upstash/redis": "^1.34.3",
|
|
19
|
-
"@medicine-wheel/ontology-core": "^0.5.
|
|
19
|
+
"@medicine-wheel/ontology-core": "^0.5.3"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"typescript": "^5.7.0"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/jgwill/medicine-wheel.git",
|
|
27
|
+
"directory": "src/storage-provider"
|
|
23
28
|
}
|
|
24
29
|
}
|