@medicine-wheel/storage-provider 0.6.0 → 0.6.2

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.
@@ -291,6 +291,14 @@ export interface StorageProvider {
291
291
  getNodesByType(type: NodeType): Promise<RelationalNode[]>;
292
292
  getNodesByDirection(direction: DirectionName): Promise<RelationalNode[]>;
293
293
  getAllNodes(limit?: number): Promise<RelationalNode[]>;
294
+ /**
295
+ * Cardinality of the whole node collection, never a page of it.
296
+ * `getAllNodes()` answers with a page (default 100), so its length is
297
+ * `min(actual, limit)` and cannot be used as a total. Implementations answer
298
+ * this without materialising the collection — a line count on jsonl, a
299
+ * `COUNT(*)` on Postgres.
300
+ */
301
+ countNodes(): Promise<number>;
294
302
  /** Throws NodeNotFoundError when the node does not exist. */
295
303
  updateNode(id: string, patch: NodePatch): Promise<RelationalNode>;
296
304
  /** Throws NodeNotFoundError or NodeHasRelationsError (refusal, never cascade). */
@@ -310,6 +318,8 @@ export interface StorageProvider {
310
318
  getCeremoniesByDirection(direction: DirectionName): Promise<CeremonyLog[]>;
311
319
  getCeremoniesByType(type: CeremonyType): Promise<CeremonyLog[]>;
312
320
  getAllCeremonies(limit?: number): Promise<CeremonyLog[]>;
321
+ /** Cardinality of the whole ceremony collection — see `countNodes()`. */
322
+ countCeremonies(): Promise<number>;
313
323
  registerInquiryWeave(record: WeaveRecord): Promise<void>;
314
324
  getInquiryWeave(id: string): Promise<WeaveRecord | null>;
315
325
  listInquiryWeaves(filters?: InquiryWeaveFilters): Promise<WeaveRecord[]>;
package/dist/jsonl.d.ts CHANGED
@@ -19,6 +19,7 @@ export declare class JsonlProvider implements StorageProvider {
19
19
  getNodesByType(type: NodeType): Promise<RelationalNode[]>;
20
20
  getNodesByDirection(direction: DirectionName): Promise<RelationalNode[]>;
21
21
  getAllNodes(limit?: number): Promise<RelationalNode[]>;
22
+ countNodes(): Promise<number>;
22
23
  updateNode(id: string, patch: NodePatch): Promise<RelationalNode>;
23
24
  deleteNode(id: string): Promise<void>;
24
25
  createEdge(edge: RelationalEdge): Promise<void>;
@@ -34,6 +35,7 @@ export declare class JsonlProvider implements StorageProvider {
34
35
  getCeremoniesByDirection(direction: DirectionName): Promise<CeremonyLog[]>;
35
36
  getCeremoniesByType(type: CeremonyType): Promise<CeremonyLog[]>;
36
37
  getAllCeremonies(limit?: number): Promise<CeremonyLog[]>;
38
+ countCeremonies(): Promise<number>;
37
39
  registerInquiryWeave(record: WeaveRecord): Promise<void>;
38
40
  getInquiryWeave(id: string): Promise<WeaveRecord | null>;
39
41
  listInquiryWeaves(filters?: InquiryWeaveFilters): Promise<WeaveRecord[]>;
package/dist/jsonl.js CHANGED
@@ -60,6 +60,9 @@ export class JsonlProvider {
60
60
  .slice(0, limit)
61
61
  .map((node) => this.parseNode(node));
62
62
  }
63
+ async countNodes() {
64
+ return countJsonl(this.nodesFile);
65
+ }
63
66
  async updateNode(id, patch) {
64
67
  return withWriteLock(this.nodesFile, () => {
65
68
  const nodes = this.readNodes();
@@ -202,6 +205,9 @@ export class JsonlProvider {
202
205
  .slice(0, limit)
203
206
  .map((ceremony) => this.parseCeremony(ceremony));
204
207
  }
208
+ async countCeremonies() {
209
+ return countJsonl(this.ceremoniesFile);
210
+ }
205
211
  async registerInquiryWeave(record) {
206
212
  await this.upsertById(this.inquiryWeavesFile, record);
207
213
  }
@@ -375,6 +381,34 @@ function readJsonl(filePath) {
375
381
  }
376
382
  return records;
377
383
  }
384
+ /**
385
+ * How many records the file holds, without building them.
386
+ *
387
+ * Counts exactly what `readJsonl` would have returned — a malformed line is
388
+ * skipped by both, so a count can never disagree with a read — but retains no
389
+ * objects, sorts nothing, and parses no record into its domain shape. Skipping
390
+ * is silent here: `readJsonl` already warns about the same line, and counting
391
+ * must not double the noise.
392
+ */
393
+ function countJsonl(filePath) {
394
+ if (!fs.existsSync(filePath))
395
+ return 0;
396
+ let count = 0;
397
+ const content = fs.readFileSync(filePath, 'utf-8');
398
+ for (const line of content.split('\n')) {
399
+ const trimmed = line.trim();
400
+ if (!trimmed)
401
+ continue;
402
+ try {
403
+ JSON.parse(trimmed);
404
+ count += 1;
405
+ }
406
+ catch {
407
+ // Same skip readJsonl performs; it owns the warning.
408
+ }
409
+ }
410
+ return count;
411
+ }
378
412
  function writeJsonl(filePath, records) {
379
413
  ensureDirectory(path.dirname(filePath));
380
414
  const tmpPath = `${filePath}.tmp.${process.pid}`;
package/dist/neon.d.ts CHANGED
@@ -14,6 +14,7 @@ export declare class NeonProvider implements StorageProvider {
14
14
  getNodesByType(type: NodeType): Promise<RelationalNode[]>;
15
15
  getNodesByDirection(direction: DirectionName): Promise<RelationalNode[]>;
16
16
  getAllNodes(limit?: number): Promise<RelationalNode[]>;
17
+ countNodes(): Promise<number>;
17
18
  updateNode(id: string, patch: NodePatch): Promise<RelationalNode>;
18
19
  deleteNode(id: string): Promise<void>;
19
20
  private parseNode;
@@ -31,6 +32,7 @@ export declare class NeonProvider implements StorageProvider {
31
32
  getCeremoniesByDirection(direction: DirectionName): Promise<CeremonyLog[]>;
32
33
  getCeremoniesByType(type: CeremonyType): Promise<CeremonyLog[]>;
33
34
  getAllCeremonies(limit?: number): Promise<CeremonyLog[]>;
35
+ countCeremonies(): Promise<number>;
34
36
  private parseCeremony;
35
37
  registerInquiryWeave(record: WeaveRecord): Promise<void>;
36
38
  getInquiryWeave(id: string): Promise<WeaveRecord | null>;
package/dist/neon.js CHANGED
@@ -64,6 +64,10 @@ export class NeonProvider {
64
64
  const rows = await this.db `SELECT * FROM nodes ORDER BY created_at DESC LIMIT ${limit}`;
65
65
  return rows.map((row) => this.parseNode(row));
66
66
  }
67
+ async countNodes() {
68
+ const counts = await this.db `SELECT COUNT(*)::int AS count FROM nodes`;
69
+ return Number(counts[0]?.count ?? 0);
70
+ }
67
71
  async updateNode(id, patch) {
68
72
  const existing = await this.getNode(id);
69
73
  if (!existing)
@@ -245,6 +249,10 @@ export class NeonProvider {
245
249
  async getAllCeremonies(limit = 100) {
246
250
  return this.getCeremoniesTimeline(limit);
247
251
  }
252
+ async countCeremonies() {
253
+ const counts = await this.db `SELECT COUNT(*)::int AS count FROM ceremonies`;
254
+ return Number(counts[0]?.count ?? 0);
255
+ }
248
256
  parseCeremony(row) {
249
257
  return {
250
258
  id: row.id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@medicine-wheel/storage-provider",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
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.6.0"
19
+ "@medicine-wheel/ontology-core": "^0.6.2"
20
20
  },
21
21
  "devDependencies": {
22
22
  "typescript": "^5.7.0"