@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.
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/postgres-event-scheduler.d.ts +88 -0
- package/dist/postgres-event-scheduler.d.ts.map +1 -0
- package/dist/postgres-event-scheduler.js +226 -0
- package/dist/postgres-event-scheduler.js.map +1 -0
- package/dist/postgres-event-store.d.ts.map +1 -1
- package/dist/postgres-event-store.js +70 -35
- package/dist/postgres-event-store.js.map +1 -1
- package/dist/postgres-transaction-manager.d.ts +33 -0
- package/dist/postgres-transaction-manager.d.ts.map +1 -0
- package/dist/postgres-transaction-manager.js +92 -0
- package/dist/postgres-transaction-manager.js.map +1 -0
- package/dist/postgres.d.ts +22 -5
- package/dist/postgres.d.ts.map +1 -1
- package/dist/postgres.js +46 -5
- package/dist/postgres.js.map +1 -1
- package/dist/schema.d.ts +47 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +67 -0
- package/dist/schema.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +18 -0
- package/src/postgres-event-scheduler.ts +314 -0
- package/src/postgres-event-store.ts +77 -30
- package/src/postgres-transaction-manager.ts +120 -0
- package/src/postgres.ts +64 -6
- package/src/schema.ts +70 -0
package/src/schema.ts
CHANGED
|
@@ -17,11 +17,13 @@
|
|
|
17
17
|
export interface TableNames {
|
|
18
18
|
readonly events: string
|
|
19
19
|
readonly snapshots: string
|
|
20
|
+
readonly scheduled: string
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export const DEFAULT_TABLE_NAMES: TableNames = {
|
|
23
24
|
events: "kronos_events",
|
|
24
25
|
snapshots: "kronos_snapshots",
|
|
26
|
+
scheduled: "kronos_scheduled_events",
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
/**
|
|
@@ -76,6 +78,72 @@ export function buildSnapshotsTableDDL(tables: TableNames): string {
|
|
|
76
78
|
);`
|
|
77
79
|
}
|
|
78
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Scheduled-events table — holds events parked for future append.
|
|
83
|
+
*
|
|
84
|
+
* # Row lifecycle (tombstone model)
|
|
85
|
+
*
|
|
86
|
+
* INSERT (status='pending') ← schedule() inside a UoW
|
|
87
|
+
* ├── UPDATE → 'appended' ← worker fires the schedule; row stays as tombstone
|
|
88
|
+
* └── UPDATE → 'cancelled' ← cancel(token) succeeds; row stays as tombstone
|
|
89
|
+
*
|
|
90
|
+
* Tombstones (rather than DELETE-on-fire) give cancel() three distinct
|
|
91
|
+
* outcomes — `cancelled` / `already-appended` / `not-found` — by inspecting
|
|
92
|
+
* the row's terminal status. The events table already grows unboundedly,
|
|
93
|
+
* so a parallel tombstone table is no worse from a retention perspective.
|
|
94
|
+
*
|
|
95
|
+
* # Schedule id = event id
|
|
96
|
+
*
|
|
97
|
+
* `schedule_id` is the same UUID as the eventual `event_id` written to the
|
|
98
|
+
* events table at fire-time. One UUID identifies the schedule pre-fire and
|
|
99
|
+
* the event post-fire, so callers tracking the materialised event can
|
|
100
|
+
* correlate back to the original schedule without an extra column.
|
|
101
|
+
*
|
|
102
|
+
* # Payload columns
|
|
103
|
+
*
|
|
104
|
+
* The whole EventMessage shape is captured inline (event_id, type, tags,
|
|
105
|
+
* payload, metadata, version, message_timestamp) so the fire-time worker
|
|
106
|
+
* can reconstruct it from a single row read. `message_timestamp` is the
|
|
107
|
+
* EventMessage's authored timestamp (epoch ms) — distinct from
|
|
108
|
+
* `created_at` (when the row was inserted) and `fire_at` (when it should
|
|
109
|
+
* fire). At append-time, the worker MAY overwrite message_timestamp with
|
|
110
|
+
* `now()` so consumers see the actual append time; that is an
|
|
111
|
+
* implementation decision left to the scheduler.
|
|
112
|
+
*/
|
|
113
|
+
export function buildScheduledEventsTableDDL(tables: TableNames): string {
|
|
114
|
+
return `CREATE TABLE IF NOT EXISTS ${tables.scheduled} (
|
|
115
|
+
schedule_id UUID PRIMARY KEY,
|
|
116
|
+
fire_at TIMESTAMPTZ NOT NULL,
|
|
117
|
+
status TEXT NOT NULL DEFAULT 'pending'
|
|
118
|
+
CHECK (status IN ('pending', 'appended', 'cancelled')),
|
|
119
|
+
type TEXT COLLATE "C" NOT NULL,
|
|
120
|
+
tags TEXT[] NOT NULL DEFAULT '{}',
|
|
121
|
+
payload JSONB NOT NULL,
|
|
122
|
+
metadata JSONB NOT NULL DEFAULT '{}',
|
|
123
|
+
version TEXT NOT NULL,
|
|
124
|
+
message_timestamp BIGINT NOT NULL,
|
|
125
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
126
|
+
);`
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Indexes for the scheduled-events table.
|
|
131
|
+
*
|
|
132
|
+
* The single critical index is the partial btree on `fire_at WHERE status =
|
|
133
|
+
* 'pending'`. The worker's hot query — `SELECT … WHERE status = 'pending'
|
|
134
|
+
* AND fire_at <= now() ORDER BY fire_at LIMIT n FOR UPDATE SKIP LOCKED` —
|
|
135
|
+
* scans only pending rows, so a partial index keeps the hot path B-tree
|
|
136
|
+
* tiny regardless of how many appended/cancelled tombstones accumulate.
|
|
137
|
+
*
|
|
138
|
+
* No index on `status` alone — every status query also filters by either
|
|
139
|
+
* schedule_id (PK lookup) or fire_at (the partial index above).
|
|
140
|
+
*/
|
|
141
|
+
export function buildScheduledEventsIndexesDDL(tables: TableNames): string {
|
|
142
|
+
return `CREATE INDEX IF NOT EXISTS ${tables.scheduled}_pending_fire_at_idx
|
|
143
|
+
ON ${tables.scheduled} (fire_at)
|
|
144
|
+
WHERE status = 'pending';`
|
|
145
|
+
}
|
|
146
|
+
|
|
79
147
|
/**
|
|
80
148
|
* Minimal adapter contract bootstrapSchema needs. A subset of the full
|
|
81
149
|
* PostgresAdapter interface authored in Plan 12-03 — structurally
|
|
@@ -195,6 +263,8 @@ export async function bootstrapSchema(
|
|
|
195
263
|
await adapter.query(buildEventsTableDDL(tables))
|
|
196
264
|
await adapter.query(buildEventsIndexesDDL(tables))
|
|
197
265
|
await adapter.query(buildSnapshotsTableDDL(tables))
|
|
266
|
+
await adapter.query(buildScheduledEventsTableDDL(tables))
|
|
267
|
+
await adapter.query(buildScheduledEventsIndexesDDL(tables))
|
|
198
268
|
await adapter.query(buildAppendStoredProcedureDDL(tables))
|
|
199
269
|
} finally {
|
|
200
270
|
// Release even on partial-DDL failure. The error (if any) propagates
|