@godspeedai/cognate-store-sqlite 0.1.2 → 0.2.0

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.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/src/index.ts +33 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@godspeedai/cognate-store-sqlite",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Authoritative DurableStore on bun:sqlite (spec §13).",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -22,6 +22,6 @@
22
22
  ".": "./src/index.ts"
23
23
  },
24
24
  "dependencies": {
25
- "@godspeedai/cognate-events": "^0.1.2"
25
+ "@godspeedai/cognate-events": "^0.2.0"
26
26
  }
27
27
  }
package/src/index.ts CHANGED
@@ -15,6 +15,7 @@ import type {
15
15
  OutboxSettlement,
16
16
  ProjectionCheckpoint,
17
17
  ProjectionCommit,
18
+ ProjectionSnapshot,
18
19
  Json,
19
20
  } from "@godspeedai/cognate-events";
20
21
  import { migrate } from "./schema.ts";
@@ -313,6 +314,15 @@ export function openSqliteStore(options: SqliteStoreOptions): DurableStore {
313
314
  db.query("DELETE FROM payloads WHERE event_id = ?").run(eventId);
314
315
  db.query("UPDATE events SET erasure_reason = ?, erasure_at = ? WHERE event_id = ?").run(reason, erasedAt, eventId);
315
316
 
317
+ // Idempotency results are derived copies: a stored result may quote the erased payload (the
318
+ // continuation-transition shape) or fold earlier events of the same stream (the updateState
319
+ // shape). Erasure tombstones such results so replay can never re-disclose the content.
320
+ db.query(
321
+ `UPDATE idempotency SET result = ?
322
+ WHERE EXISTS (SELECT 1 FROM json_each(idempotency.event_ids) je WHERE je.value = ?)
323
+ OR EXISTS (SELECT 1 FROM json_each(idempotency.event_ids) je JOIN events e ON e.event_id = je.value WHERE e.stream_id = ?)`,
324
+ ).run(JSON.stringify({ __erased: { reason, erasedAt } }), eventId, original.stream_id);
325
+
316
326
  ensureStream("$erasures");
317
327
  const sequence = streamVersionRow("$erasures") + 1;
318
328
  const tombstone = insertEventRow(
@@ -472,6 +482,29 @@ export function openSqliteStore(options: SqliteStoreOptions): DurableStore {
472
482
  return rows.map((row) => ({ key: row.key, value: JSON.parse(row.value) as Json }));
473
483
  },
474
484
 
485
+ async readProjectionSnapshot(name: string): Promise<ProjectionSnapshot> {
486
+ assertOpen();
487
+ // One statement, one snapshot: the checkpoint can never be ahead of the rows it is returned
488
+ // with, even with another process folding concurrently (DEBT-021).
489
+ const row = db
490
+ .query(
491
+ `SELECT
492
+ (SELECT json_object('name', name, 'version', version, 'position', position, 'status', status,
493
+ 'error_position', error_position, 'error_event_id', error_event_id, 'error_message', error_message)
494
+ FROM projection_checkpoints WHERE name = ?1) AS checkpoint,
495
+ (SELECT json_group_array(json_object('key', ordered.key, 'value', json(ordered.value)))
496
+ FROM (SELECT key, value FROM projection_state WHERE name = ?1 ORDER BY key) AS ordered) AS rows`,
497
+ )
498
+ .get(name) as Row;
499
+ const checkpoint = row.checkpoint ? JSON.parse(row.checkpoint as string) : undefined;
500
+ return {
501
+ checkpoint: checkpoint
502
+ ? { ...checkpoint, error: checkpoint.error_event_id != null ? { position: checkpoint.error_position, eventId: checkpoint.error_event_id, message: checkpoint.error_message } : null }
503
+ : undefined,
504
+ rows: (JSON.parse(row.rows as string) as { key: string; value: Json }[]).map((entry) => ({ key: entry.key, value: entry.value })),
505
+ };
506
+ },
507
+
475
508
  async resetProjection(name: string): Promise<void> {
476
509
  assertOpen();
477
510
  const txn = db.transaction((n: string): void => {