@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 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().filter((record) => matchesInquiryWeaveFilters(record, filters));
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
- return false;
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
- const records = await this.getAllInquiryWeaves();
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.1",
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.1"
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
  }