@kronos-ts/postgres 0.1.1 → 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.
@@ -0,0 +1,92 @@
1
+ /**
2
+ * postgresTransactionManager — bridges the framework's TransactionManager
3
+ * lifecycle to the postgres adapter's callback-shaped `adapter.transaction`.
4
+ *
5
+ * adapter.transaction(IL, fn) opens a pg tx, runs `fn(tx)`, and COMMITs on
6
+ * fn-resolve / ROLLBACKs on fn-reject. The framework needs a tx whose
7
+ * commit/rollback is callable LATER (at the UoW's COMMIT or onError phase),
8
+ * not when fn returns. We bridge by parking `fn` on a deferred completion
9
+ * promise — `commit()` resolves it (→ adapter commits), `rollback()`
10
+ * rejects it (→ adapter rolls back).
11
+ *
12
+ * Same shape as kysely/prisma transaction managers in this repo.
13
+ */
14
+ import { getOrBeginActiveTransaction } from "@kronos-ts/messaging";
15
+ import { IsolationLevel } from "./adapter.js";
16
+ /**
17
+ * Module-private symbol attaching commit/rollback control to a tx handle.
18
+ * Consumers via `getActiveTransaction<PostgresAdapterTransaction>()` see
19
+ * only `{ query }` — they cannot read this without importing the symbol.
20
+ */
21
+ const TX_CONTROL = Symbol("kronos.postgresTxControl");
22
+ /** Marker error: signals an intentional rollback so the .catch can suppress it. */
23
+ const ROLLBACK_MARKER = "__kronos_postgres_tx_rollback__";
24
+ export function postgresTransactionManager(adapter, isolationLevel = IsolationLevel.READ_COMMITTED) {
25
+ return {
26
+ async begin() {
27
+ let captureTx;
28
+ const txReady = new Promise((res) => {
29
+ captureTx = res;
30
+ });
31
+ let resolveCommit;
32
+ let rejectRollback;
33
+ const completion = new Promise((res, rej) => {
34
+ resolveCommit = res;
35
+ rejectRollback = rej;
36
+ });
37
+ const txPromise = adapter
38
+ .transaction(isolationLevel, async (tx) => {
39
+ captureTx(tx);
40
+ await completion;
41
+ })
42
+ .then(() => undefined, (err) => {
43
+ // Suppress the marker — rollback is an expected outcome.
44
+ if (err instanceof Error && err.message === ROLLBACK_MARKER)
45
+ return;
46
+ throw err;
47
+ });
48
+ const tx = (await txReady);
49
+ tx[TX_CONTROL] = { resolveCommit, rejectRollback, txPromise };
50
+ return tx;
51
+ },
52
+ async commit(tx) {
53
+ const ctrl = tx[TX_CONTROL];
54
+ ctrl.resolveCommit();
55
+ await ctrl.txPromise;
56
+ },
57
+ async rollback(tx) {
58
+ const ctrl = tx[TX_CONTROL];
59
+ ctrl.rejectRollback(new Error(ROLLBACK_MARKER));
60
+ try {
61
+ await ctrl.txPromise;
62
+ }
63
+ catch (err) {
64
+ // A real follow-up error during ROLLBACK execution. Don't throw
65
+ // from rollback() — the UoW is already in an error path and a
66
+ // cascading throw masks the original failure.
67
+ console.warn("postgresTransactionManager: rollback path threw:", err);
68
+ }
69
+ },
70
+ };
71
+ }
72
+ /**
73
+ * Run `fn` inside a postgres tx, joining a UoW-scoped tx if one is active
74
+ * (or installed lazily), otherwise opening an ad-hoc tx via `adapter.transaction`.
75
+ *
76
+ * This is what every postgres-extension write path should funnel through —
77
+ * event store appends, snapshot writes, scheduler inserts — so that all
78
+ * writes inside a single UoW land in one pg tx and commit/roll back atomically.
79
+ * Calls from outside any UoW (e.g., the scheduler worker loop, projection
80
+ * queries, lifecycle bootstraps) get their own short-lived tx.
81
+ *
82
+ * Returns whatever `fn` returns. Tx commit/rollback happens when the
83
+ * surrounding UoW's COMMIT/onError fires (joined path) or when `fn` resolves/
84
+ * rejects (ad-hoc path).
85
+ */
86
+ export async function withSharedOrOwnTx(adapter, fn, isolationLevel = IsolationLevel.READ_COMMITTED) {
87
+ const shared = await getOrBeginActiveTransaction();
88
+ if (shared !== undefined)
89
+ return fn(shared);
90
+ return adapter.transaction(isolationLevel, fn);
91
+ }
92
+ //# sourceMappingURL=postgres-transaction-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres-transaction-manager.js","sourceRoot":"","sources":["../src/postgres-transaction-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,2BAA2B,EAA2B,MAAM,sBAAsB,CAAA;AAE3F,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAE7C;;;;GAIG;AACH,MAAM,UAAU,GAAG,MAAM,CAAC,0BAA0B,CAAC,CAAA;AAYrD,mFAAmF;AACnF,MAAM,eAAe,GAAG,iCAAiC,CAAA;AAEzD,MAAM,UAAU,0BAA0B,CACxC,OAAwB,EACxB,iBAAiC,cAAc,CAAC,cAAc;IAE9D,OAAO;QACL,KAAK,CAAC,KAAK;YACT,IAAI,SAAoD,CAAA;YACxD,MAAM,OAAO,GAAG,IAAI,OAAO,CAA6B,CAAC,GAAG,EAAE,EAAE;gBAC9D,SAAS,GAAG,GAAG,CAAA;YACjB,CAAC,CAAC,CAAA;YAEF,IAAI,aAA0B,CAAA;YAC9B,IAAI,cAAuC,CAAA;YAC3C,MAAM,UAAU,GAAG,IAAI,OAAO,CAAO,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;gBAChD,aAAa,GAAG,GAAG,CAAA;gBACnB,cAAc,GAAG,GAAG,CAAA;YACtB,CAAC,CAAC,CAAA;YAEF,MAAM,SAAS,GAAG,OAAO;iBACtB,WAAW,CAAC,cAAc,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE;gBACxC,SAAS,CAAC,EAAE,CAAC,CAAA;gBACb,MAAM,UAAU,CAAA;YAClB,CAAC,CAAC;iBACD,IAAI,CACH,GAAG,EAAE,CAAC,SAAS,EACf,CAAC,GAAG,EAAE,EAAE;gBACN,yDAAyD;gBACzD,IAAI,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,OAAO,KAAK,eAAe;oBAAE,OAAM;gBACnE,MAAM,GAAG,CAAA;YACX,CAAC,CACF,CAAA;YAEH,MAAM,EAAE,GAAG,CAAC,MAAM,OAAO,CAA+B,CAAA;YACxD,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,aAAa,EAAE,cAAc,EAAE,SAAS,EAAE,CAAA;YAC7D,OAAO,EAAE,CAAA;QACX,CAAC;QAED,KAAK,CAAC,MAAM,CAAC,EAA8B;YACzC,MAAM,IAAI,GAAI,EAAiC,CAAC,UAAU,CAAC,CAAA;YAC3D,IAAI,CAAC,aAAa,EAAE,CAAA;YACpB,MAAM,IAAI,CAAC,SAAS,CAAA;QACtB,CAAC;QAED,KAAK,CAAC,QAAQ,CAAC,EAA8B;YAC3C,MAAM,IAAI,GAAI,EAAiC,CAAC,UAAU,CAAC,CAAA;YAC3D,IAAI,CAAC,cAAc,CAAC,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC,CAAA;YAC/C,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,SAAS,CAAA;YACtB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,gEAAgE;gBAChE,8DAA8D;gBAC9D,8CAA8C;gBAC9C,OAAO,CAAC,IAAI,CAAC,kDAAkD,EAAE,GAAG,CAAC,CAAA;YACvE,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,OAAwB,EACxB,EAAkD,EAClD,iBAAiC,cAAc,CAAC,cAAc;IAE9D,MAAM,MAAM,GAAG,MAAM,2BAA2B,EAA8B,CAAA;IAC9E,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC,MAAM,CAAC,CAAA;IAC3C,OAAO,OAAO,CAAC,WAAW,CAAC,cAAc,EAAE,EAAE,CAAC,CAAA;AAChD,CAAC"}
@@ -1,9 +1,21 @@
1
1
  /**
2
2
  * postgres(config) — Extension factory for @kronos-ts/postgres.
3
3
  *
4
- * Populates two slots (D-12.01):
5
- * - eventStore : EventStorageEngine via createPostgresEventStore
6
- * - snapshotStore : SnapshotStore via createPostgresSnapshotStore
4
+ * Populates five slots:
5
+ * - eventStore : EventStorageEngine via createPostgresEventStore
6
+ * - snapshotStore : SnapshotStore via createPostgresSnapshotStore
7
+ * - transactionManager : postgresTransactionManager(adapter)
8
+ * - unitOfWorkFactory : lazyTransactionalUnitOfWorkFactory(runInNewUoW, tm)
9
+ * - eventScheduler : createPostgresEventScheduler(...) (durable,
10
+ * background worker started in "processors" stage)
11
+ *
12
+ * Setting the last two together is what gives `append() + schedule()` (and
13
+ * any future postgres-extension writer) a SHARED UoW transaction —
14
+ * everything that writes inside one UoW commits or rolls back atomically.
15
+ * Lazy variant chosen so pure-read UoWs (queries, projections that don't
16
+ * write) never claim a pool connection. Users who need different
17
+ * composition (e.g., eager for benchmarking, custom UoW wrapping) can
18
+ * override with `app.forceSet(...)`.
7
19
  *
8
20
  * Lifecycle (mirrors @kronos-ts/kronosdb extension shape):
9
21
  * - app.onStart("connect", ...) — adapter.connect() with withRetry; then
@@ -11,8 +23,8 @@
11
23
  * their own migration tooling are not surprised)
12
24
  * - app.onStop("connect", ...) — adapter.disconnect()
13
25
  *
14
- * Does NOT populate eventBus, commandBus, queryBus, tokenStore, or
15
- * transactionManager (D-12.01out of scope for this extension).
26
+ * Does NOT populate eventBus, commandBus, queryBus, or tokenStore (out of
27
+ * scope for this extension postgres token store is a separate package).
16
28
  */
17
29
  import type { App } from "@kronos-ts/app";
18
30
  import type { ResilienceConfig } from "@kronos-ts/common";
@@ -29,6 +41,11 @@ export interface PostgresConfig {
29
41
  /** Retry policy for the initial connect + bootstrap. Defaults to
30
42
  * framework defaults via withRetry. */
31
43
  readonly resilience?: Partial<ResilienceConfig>;
44
+ /** Tuning for the durable scheduler's polling worker. */
45
+ readonly scheduler?: {
46
+ readonly pollIntervalMs?: number;
47
+ readonly batchSize?: number;
48
+ };
32
49
  }
33
50
  export declare function postgres(config: PostgresConfig): (app: App) => void;
34
51
  //# sourceMappingURL=postgres.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../src/postgres.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAEzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAGnD,OAAO,EAAwC,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAEnF,MAAM,WAAW,cAAc;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAA;IACjC;uCACmC;IACnC,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAA;IAC5B,uEAAuE;IACvE,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAA;IAChC;4CACwC;IACxC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAA;CAChD;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CA2BnE"}
1
+ {"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../src/postgres.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAoB,MAAM,gBAAgB,CAAA;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAOzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAQnD,OAAO,EAAwC,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAEnF,MAAM,WAAW,cAAc;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAA;IACjC;uCACmC;IACnC,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAA;IAC5B,uEAAuE;IACvE,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAA;IAChC;4CACwC;IACxC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAA;IAC/C,yDAAyD;IACzD,QAAQ,CAAC,SAAS,CAAC,EAAE;QACnB,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;QAChC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAC5B,CAAA;CACF;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CA0DnE"}
package/dist/postgres.js CHANGED
@@ -1,9 +1,21 @@
1
1
  /**
2
2
  * postgres(config) — Extension factory for @kronos-ts/postgres.
3
3
  *
4
- * Populates two slots (D-12.01):
5
- * - eventStore : EventStorageEngine via createPostgresEventStore
6
- * - snapshotStore : SnapshotStore via createPostgresSnapshotStore
4
+ * Populates five slots:
5
+ * - eventStore : EventStorageEngine via createPostgresEventStore
6
+ * - snapshotStore : SnapshotStore via createPostgresSnapshotStore
7
+ * - transactionManager : postgresTransactionManager(adapter)
8
+ * - unitOfWorkFactory : lazyTransactionalUnitOfWorkFactory(runInNewUoW, tm)
9
+ * - eventScheduler : createPostgresEventScheduler(...) (durable,
10
+ * background worker started in "processors" stage)
11
+ *
12
+ * Setting the last two together is what gives `append() + schedule()` (and
13
+ * any future postgres-extension writer) a SHARED UoW transaction —
14
+ * everything that writes inside one UoW commits or rolls back atomically.
15
+ * Lazy variant chosen so pure-read UoWs (queries, projections that don't
16
+ * write) never claim a pool connection. Users who need different
17
+ * composition (e.g., eager for benchmarking, custom UoW wrapping) can
18
+ * override with `app.forceSet(...)`.
7
19
  *
8
20
  * Lifecycle (mirrors @kronos-ts/kronosdb extension shape):
9
21
  * - app.onStart("connect", ...) — adapter.connect() with withRetry; then
@@ -11,12 +23,15 @@
11
23
  * their own migration tooling are not surprised)
12
24
  * - app.onStop("connect", ...) — adapter.disconnect()
13
25
  *
14
- * Does NOT populate eventBus, commandBus, queryBus, tokenStore, or
15
- * transactionManager (D-12.01out of scope for this extension).
26
+ * Does NOT populate eventBus, commandBus, queryBus, or tokenStore (out of
27
+ * scope for this extension postgres token store is a separate package).
16
28
  */
17
29
  import { withRetry } from "@kronos-ts/common";
30
+ import { lazyTransactionalUnitOfWorkFactory, runInNewUoW, } from "@kronos-ts/messaging";
18
31
  import { createPostgresEventStore } from "./postgres-event-store.js";
19
32
  import { createPostgresSnapshotStore } from "./postgres-snapshot-store.js";
33
+ import { postgresTransactionManager } from "./postgres-transaction-manager.js";
34
+ import { createPostgresEventScheduler, } from "./postgres-event-scheduler.js";
20
35
  import { bootstrapSchema, DEFAULT_TABLE_NAMES } from "./schema.js";
21
36
  export function postgres(config) {
22
37
  const { adapter, resilience } = config;
@@ -25,6 +40,23 @@ export function postgres(config) {
25
40
  return (app) => {
26
41
  app.set("eventStore", ({ serializer, tagResolver }) => createPostgresEventStore({ adapter, serializer, tagResolver, tableNames: tables }));
27
42
  app.set("snapshotStore", ({ serializer }) => createPostgresSnapshotStore({ adapter, serializer, tableNames: tables }));
43
+ app.set("transactionManager", () => postgresTransactionManager(adapter));
44
+ app.set("unitOfWorkFactory", (resolved) => lazyTransactionalUnitOfWorkFactory(runInNewUoW, resolved.transactionManager));
45
+ // Durable scheduler — closure captures the instance so the worker can be
46
+ // start()'d in "processors" and stop()'d in "connect" symmetric to other
47
+ // background workers.
48
+ let scheduler;
49
+ app.set("eventScheduler", ({ eventStore, unitOfWorkFactory, tagResolver }) => {
50
+ scheduler = createPostgresEventScheduler({
51
+ adapter,
52
+ eventStore,
53
+ uowFactory: unitOfWorkFactory,
54
+ tagResolver,
55
+ tableNames: tables,
56
+ ...config.scheduler,
57
+ });
58
+ return scheduler;
59
+ });
28
60
  app.onStart("connect", async () => {
29
61
  await withRetry(() => adapter.connect(), { event: "initial-connect", ...resilience });
30
62
  if (bootstrap) {
@@ -34,7 +66,16 @@ export function postgres(config) {
34
66
  });
35
67
  }
36
68
  });
69
+ // Worker spins up after registration/warmup so all slots are resolved and
70
+ // any user-supplied processors are in place before due schedules start
71
+ // firing into the event store.
72
+ app.onStart("processors", async () => {
73
+ if (scheduler)
74
+ await scheduler.start();
75
+ });
37
76
  app.onStop("connect", async () => {
77
+ if (scheduler)
78
+ await scheduler.stop();
38
79
  await adapter.disconnect();
39
80
  });
40
81
  };
@@ -1 +1 @@
1
- {"version":3,"file":"postgres.js","sourceRoot":"","sources":["../src/postgres.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAE7C,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAA;AACpE,OAAO,EAAE,2BAA2B,EAAE,MAAM,8BAA8B,CAAA;AAC1E,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAmB,MAAM,aAAa,CAAA;AAenF,MAAM,UAAU,QAAQ,CAAC,MAAsB;IAC7C,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,CAAA;IACtC,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,IAAI,IAAI,CAAA;IAC1C,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,IAAI,mBAAmB,CAAA;IAEvD,OAAO,CAAC,GAAQ,EAAE,EAAE;QAClB,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,EAAE,UAAU,EAAE,WAAW,EAAE,EAAE,EAAE,CACpD,wBAAwB,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,CACnF,CAAA;QACD,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAC1C,2BAA2B,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,CACzE,CAAA;QAED,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE;YAChC,MAAM,SAAS,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,GAAG,UAAU,EAAE,CAAC,CAAA;YACrF,IAAI,SAAS,EAAE,CAAC;gBACd,MAAM,SAAS,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,EAAE;oBACtE,KAAK,EAAE,iBAAiB;oBACxB,GAAG,UAAU;iBACd,CAAC,CAAA;YACJ,CAAC;QACH,CAAC,CAAC,CAAA;QAEF,GAAG,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE;YAC/B,MAAM,OAAO,CAAC,UAAU,EAAE,CAAA;QAC5B,CAAC,CAAC,CAAA;IACJ,CAAC,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"postgres.js","sourceRoot":"","sources":["../src/postgres.ts"],"names":[],"mappings":"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"}
package/dist/schema.d.ts CHANGED
@@ -16,6 +16,7 @@
16
16
  export interface TableNames {
17
17
  readonly events: string;
18
18
  readonly snapshots: string;
19
+ readonly scheduled: string;
19
20
  }
20
21
  export declare const DEFAULT_TABLE_NAMES: TableNames;
21
22
  /**
@@ -32,6 +33,52 @@ export declare const KRONOS_SCHEMA_LOCK_KEY: bigint;
32
33
  export declare function buildEventsTableDDL(tables: TableNames): string;
33
34
  export declare function buildEventsIndexesDDL(tables: TableNames): string;
34
35
  export declare function buildSnapshotsTableDDL(tables: TableNames): string;
36
+ /**
37
+ * Scheduled-events table — holds events parked for future append.
38
+ *
39
+ * # Row lifecycle (tombstone model)
40
+ *
41
+ * INSERT (status='pending') ← schedule() inside a UoW
42
+ * ├── UPDATE → 'appended' ← worker fires the schedule; row stays as tombstone
43
+ * └── UPDATE → 'cancelled' ← cancel(token) succeeds; row stays as tombstone
44
+ *
45
+ * Tombstones (rather than DELETE-on-fire) give cancel() three distinct
46
+ * outcomes — `cancelled` / `already-appended` / `not-found` — by inspecting
47
+ * the row's terminal status. The events table already grows unboundedly,
48
+ * so a parallel tombstone table is no worse from a retention perspective.
49
+ *
50
+ * # Schedule id = event id
51
+ *
52
+ * `schedule_id` is the same UUID as the eventual `event_id` written to the
53
+ * events table at fire-time. One UUID identifies the schedule pre-fire and
54
+ * the event post-fire, so callers tracking the materialised event can
55
+ * correlate back to the original schedule without an extra column.
56
+ *
57
+ * # Payload columns
58
+ *
59
+ * The whole EventMessage shape is captured inline (event_id, type, tags,
60
+ * payload, metadata, version, message_timestamp) so the fire-time worker
61
+ * can reconstruct it from a single row read. `message_timestamp` is the
62
+ * EventMessage's authored timestamp (epoch ms) — distinct from
63
+ * `created_at` (when the row was inserted) and `fire_at` (when it should
64
+ * fire). At append-time, the worker MAY overwrite message_timestamp with
65
+ * `now()` so consumers see the actual append time; that is an
66
+ * implementation decision left to the scheduler.
67
+ */
68
+ export declare function buildScheduledEventsTableDDL(tables: TableNames): string;
69
+ /**
70
+ * Indexes for the scheduled-events table.
71
+ *
72
+ * The single critical index is the partial btree on `fire_at WHERE status =
73
+ * 'pending'`. The worker's hot query — `SELECT … WHERE status = 'pending'
74
+ * AND fire_at <= now() ORDER BY fire_at LIMIT n FOR UPDATE SKIP LOCKED` —
75
+ * scans only pending rows, so a partial index keeps the hot path B-tree
76
+ * tiny regardless of how many appended/cancelled tombstones accumulate.
77
+ *
78
+ * No index on `status` alone — every status query also filters by either
79
+ * schedule_id (PK lookup) or fire_at (the partial index above).
80
+ */
81
+ export declare function buildScheduledEventsIndexesDDL(tables: TableNames): string;
35
82
  /**
36
83
  * Minimal adapter contract bootstrapSchema needs. A subset of the full
37
84
  * PostgresAdapter interface authored in Plan 12-03 — structurally
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED,eAAO,MAAM,mBAAmB,EAAE,UAGjC,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,EAAE,MAAgB,CAAA;AAErD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAkB9D;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAMhE;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAUjE;AAED;;;;;GAKG;AACH,MAAM,WAAW,sBAAsB;IACrC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CACzD;AAED,MAAM,WAAW,sBAAsB;IACrC,qDAAqD;IACrD,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAA;CACjC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAyCxE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,sBAAsB,EAC/B,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,IAAI,CAAC,CAkBf"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED,eAAO,MAAM,mBAAmB,EAAE,UAIjC,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,EAAE,MAAgB,CAAA;AAErD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAkB9D;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAMhE;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAUjE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAcvE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAIzE;AAED;;;;;GAKG;AACH,MAAM,WAAW,sBAAsB;IACrC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CACzD;AAED,MAAM,WAAW,sBAAsB;IACrC,qDAAqD;IACrD,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAA;CACjC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAyCxE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,sBAAsB,EAC/B,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,IAAI,CAAC,CAoBf"}
package/dist/schema.js CHANGED
@@ -16,6 +16,7 @@
16
16
  export const DEFAULT_TABLE_NAMES = {
17
17
  events: "kronos_events",
18
18
  snapshots: "kronos_snapshots",
19
+ scheduled: "kronos_scheduled_events",
19
20
  };
20
21
  /**
21
22
  * Session-scoped advisory lock key for the schema bootstrap.
@@ -65,6 +66,70 @@ export function buildSnapshotsTableDDL(tables) {
65
66
  PRIMARY KEY (state_name, state_id)
66
67
  );`;
67
68
  }
69
+ /**
70
+ * Scheduled-events table — holds events parked for future append.
71
+ *
72
+ * # Row lifecycle (tombstone model)
73
+ *
74
+ * INSERT (status='pending') ← schedule() inside a UoW
75
+ * ├── UPDATE → 'appended' ← worker fires the schedule; row stays as tombstone
76
+ * └── UPDATE → 'cancelled' ← cancel(token) succeeds; row stays as tombstone
77
+ *
78
+ * Tombstones (rather than DELETE-on-fire) give cancel() three distinct
79
+ * outcomes — `cancelled` / `already-appended` / `not-found` — by inspecting
80
+ * the row's terminal status. The events table already grows unboundedly,
81
+ * so a parallel tombstone table is no worse from a retention perspective.
82
+ *
83
+ * # Schedule id = event id
84
+ *
85
+ * `schedule_id` is the same UUID as the eventual `event_id` written to the
86
+ * events table at fire-time. One UUID identifies the schedule pre-fire and
87
+ * the event post-fire, so callers tracking the materialised event can
88
+ * correlate back to the original schedule without an extra column.
89
+ *
90
+ * # Payload columns
91
+ *
92
+ * The whole EventMessage shape is captured inline (event_id, type, tags,
93
+ * payload, metadata, version, message_timestamp) so the fire-time worker
94
+ * can reconstruct it from a single row read. `message_timestamp` is the
95
+ * EventMessage's authored timestamp (epoch ms) — distinct from
96
+ * `created_at` (when the row was inserted) and `fire_at` (when it should
97
+ * fire). At append-time, the worker MAY overwrite message_timestamp with
98
+ * `now()` so consumers see the actual append time; that is an
99
+ * implementation decision left to the scheduler.
100
+ */
101
+ export function buildScheduledEventsTableDDL(tables) {
102
+ return `CREATE TABLE IF NOT EXISTS ${tables.scheduled} (
103
+ schedule_id UUID PRIMARY KEY,
104
+ fire_at TIMESTAMPTZ NOT NULL,
105
+ status TEXT NOT NULL DEFAULT 'pending'
106
+ CHECK (status IN ('pending', 'appended', 'cancelled')),
107
+ type TEXT COLLATE "C" NOT NULL,
108
+ tags TEXT[] NOT NULL DEFAULT '{}',
109
+ payload JSONB NOT NULL,
110
+ metadata JSONB NOT NULL DEFAULT '{}',
111
+ version TEXT NOT NULL,
112
+ message_timestamp BIGINT NOT NULL,
113
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
114
+ );`;
115
+ }
116
+ /**
117
+ * Indexes for the scheduled-events table.
118
+ *
119
+ * The single critical index is the partial btree on `fire_at WHERE status =
120
+ * 'pending'`. The worker's hot query — `SELECT … WHERE status = 'pending'
121
+ * AND fire_at <= now() ORDER BY fire_at LIMIT n FOR UPDATE SKIP LOCKED` —
122
+ * scans only pending rows, so a partial index keeps the hot path B-tree
123
+ * tiny regardless of how many appended/cancelled tombstones accumulate.
124
+ *
125
+ * No index on `status` alone — every status query also filters by either
126
+ * schedule_id (PK lookup) or fire_at (the partial index above).
127
+ */
128
+ export function buildScheduledEventsIndexesDDL(tables) {
129
+ return `CREATE INDEX IF NOT EXISTS ${tables.scheduled}_pending_fire_at_idx
130
+ ON ${tables.scheduled} (fire_at)
131
+ WHERE status = 'pending';`;
132
+ }
68
133
  /**
69
134
  * Append-with-DCB-check stored procedure.
70
135
  *
@@ -163,6 +228,8 @@ export async function bootstrapSchema(adapter, options = {}) {
163
228
  await adapter.query(buildEventsTableDDL(tables));
164
229
  await adapter.query(buildEventsIndexesDDL(tables));
165
230
  await adapter.query(buildSnapshotsTableDDL(tables));
231
+ await adapter.query(buildScheduledEventsTableDDL(tables));
232
+ await adapter.query(buildScheduledEventsIndexesDDL(tables));
166
233
  await adapter.query(buildAppendStoredProcedureDDL(tables));
167
234
  }
168
235
  finally {
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAOH,MAAM,CAAC,MAAM,mBAAmB,GAAe;IAC7C,MAAM,EAAE,eAAe;IACvB,SAAS,EAAE,kBAAkB;CAC9B,CAAA;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAW,CAAC,MAAM,CAAA;AAErD,MAAM,UAAU,mBAAmB,CAAC,MAAkB;IACpD,mFAAmF;IACnF,+EAA+E;IAC/E,0EAA0E;IAC1E,OAAO,8BAA8B,MAAM,CAAC,MAAM;;;;;;;;;;;;;GAajD,CAAA;AACH,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,MAAkB;IACtD,OAAO,qCAAqC,MAAM,CAAC,MAAM;OACpD,MAAM,CAAC,MAAM;;6BAES,MAAM,CAAC,MAAM;OACnC,MAAM,CAAC,MAAM,2CAA2C,CAAA;AAC/D,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,MAAkB;IACvD,OAAO,8BAA8B,MAAM,CAAC,SAAS;;;;;;;;GAQpD,CAAA;AACH,CAAC;AAiBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,UAAU,6BAA6B,CAAC,MAAkB;IAC9D,OAAO;;;;;;;;;;;;;;;;;8BAiBqB,MAAM,CAAC,MAAM;;;;;;;;;;;;;;;;kBAgBzB,MAAM,CAAC,MAAM;;;;;;qBAMV,CAAA;AACrB,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAA+B,EAC/B,UAAkC,EAAE;IAEpC,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,IAAI,mBAAmB,CAAA;IAExD,uEAAuE;IACvE,qEAAqE;IACrE,sBAAsB;IACtB,MAAM,OAAO,CAAC,KAAK,CAAC,6BAA6B,EAAE,CAAC,sBAAsB,CAAC,CAAC,CAAA;IAE5E,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,KAAK,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAAA;QAChD,MAAM,OAAO,CAAC,KAAK,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAC,CAAA;QAClD,MAAM,OAAO,CAAC,KAAK,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAC,CAAA;QACnD,MAAM,OAAO,CAAC,KAAK,CAAC,6BAA6B,CAAC,MAAM,CAAC,CAAC,CAAA;IAC5D,CAAC;YAAS,CAAC;QACT,qEAAqE;QACrE,+CAA+C;QAC/C,MAAM,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,CAAC,sBAAsB,CAAC,CAAC,CAAA;IAChF,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAQH,MAAM,CAAC,MAAM,mBAAmB,GAAe;IAC7C,MAAM,EAAE,eAAe;IACvB,SAAS,EAAE,kBAAkB;IAC7B,SAAS,EAAE,yBAAyB;CACrC,CAAA;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAW,CAAC,MAAM,CAAA;AAErD,MAAM,UAAU,mBAAmB,CAAC,MAAkB;IACpD,mFAAmF;IACnF,+EAA+E;IAC/E,0EAA0E;IAC1E,OAAO,8BAA8B,MAAM,CAAC,MAAM;;;;;;;;;;;;;GAajD,CAAA;AACH,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,MAAkB;IACtD,OAAO,qCAAqC,MAAM,CAAC,MAAM;OACpD,MAAM,CAAC,MAAM;;6BAES,MAAM,CAAC,MAAM;OACnC,MAAM,CAAC,MAAM,2CAA2C,CAAA;AAC/D,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,MAAkB;IACvD,OAAO,8BAA8B,MAAM,CAAC,SAAS;;;;;;;;GAQpD,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,UAAU,4BAA4B,CAAC,MAAkB;IAC7D,OAAO,8BAA8B,MAAM,CAAC,SAAS;;;;;;;;;;;;GAYpD,CAAA;AACH,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,8BAA8B,CAAC,MAAkB;IAC/D,OAAO,8BAA8B,MAAM,CAAC,SAAS;OAChD,MAAM,CAAC,SAAS;4BACK,CAAA;AAC5B,CAAC;AAiBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,UAAU,6BAA6B,CAAC,MAAkB;IAC9D,OAAO;;;;;;;;;;;;;;;;;8BAiBqB,MAAM,CAAC,MAAM;;;;;;;;;;;;;;;;kBAgBzB,MAAM,CAAC,MAAM;;;;;;qBAMV,CAAA;AACrB,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAA+B,EAC/B,UAAkC,EAAE;IAEpC,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,IAAI,mBAAmB,CAAA;IAExD,uEAAuE;IACvE,qEAAqE;IACrE,sBAAsB;IACtB,MAAM,OAAO,CAAC,KAAK,CAAC,6BAA6B,EAAE,CAAC,sBAAsB,CAAC,CAAC,CAAA;IAE5E,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,KAAK,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAAA;QAChD,MAAM,OAAO,CAAC,KAAK,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAC,CAAA;QAClD,MAAM,OAAO,CAAC,KAAK,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAC,CAAA;QACnD,MAAM,OAAO,CAAC,KAAK,CAAC,4BAA4B,CAAC,MAAM,CAAC,CAAC,CAAA;QACzD,MAAM,OAAO,CAAC,KAAK,CAAC,8BAA8B,CAAC,MAAM,CAAC,CAAC,CAAA;QAC3D,MAAM,OAAO,CAAC,KAAK,CAAC,6BAA6B,CAAC,MAAM,CAAC,CAAC,CAAA;IAC5D,CAAC;YAAS,CAAC;QACT,qEAAqE;QACrE,+CAA+C;QAC/C,MAAM,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,CAAC,sBAAsB,CAAC,CAAC,CAAA;IAChF,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kronos-ts/postgres",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "PostgreSQL extension for Kronos — event store and snapshot store adapters.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
package/src/index.ts CHANGED
@@ -43,6 +43,22 @@ export {
43
43
  // Extension factory (Plan 05)
44
44
  export { postgres, type PostgresConfig } from "./postgres.js"
45
45
 
46
+ // Transaction manager — bridges the framework's TransactionManager lifecycle
47
+ // to adapter.transaction(). Users typically get this wired automatically via
48
+ // `postgres(config)`; exported for direct use when composing UoW runners by
49
+ // hand.
50
+ export { postgresTransactionManager } from "./postgres-transaction-manager.js"
51
+
52
+ // Postgres event scheduler — durable schedule() + cancel() + polling worker
53
+ // that fires due schedules into the event store. Wired into postgres()
54
+ // automatically when a uowFactory with the lazy postgres tx is in place;
55
+ // exported here so users who compose their own wiring can construct one.
56
+ export {
57
+ createPostgresEventScheduler,
58
+ type PostgresEventScheduler,
59
+ type PostgresEventSchedulerConfig,
60
+ } from "./postgres-event-scheduler.js"
61
+
46
62
  // Schema bootstrap + DDL builders — exposed for users who want to run their
47
63
  // own migrations (set `postgres({ bootstrap: false })`) or drive the store
48
64
  // directly without going through the extension factory.
@@ -51,6 +67,8 @@ export {
51
67
  buildEventsTableDDL,
52
68
  buildEventsIndexesDDL,
53
69
  buildSnapshotsTableDDL,
70
+ buildScheduledEventsTableDDL,
71
+ buildScheduledEventsIndexesDDL,
54
72
  DEFAULT_TABLE_NAMES,
55
73
  type TableNames,
56
74
  } from "./schema.js"