@tenetkit/pg 0.28.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 (48) hide show
  1. package/README.md +3 -0
  2. package/dist/postgres/claims.d.ts +26 -0
  3. package/dist/postgres/claims.js +95 -0
  4. package/dist/postgres/event-stream.d.ts +20 -0
  5. package/dist/postgres/event-stream.js +16 -0
  6. package/dist/postgres/index.d.ts +3 -0
  7. package/dist/postgres/index.js +3 -0
  8. package/dist/postgres/locks.d.ts +18 -0
  9. package/dist/postgres/locks.js +51 -0
  10. package/dist/postgres/pg-helpers.d.ts +91 -0
  11. package/dist/postgres/pg-helpers.js +359 -0
  12. package/dist/postgres/run-schema.d.ts +23 -0
  13. package/dist/postgres/run-schema.js +92 -0
  14. package/dist/postgres/runtime-layer.d.ts +15 -0
  15. package/dist/postgres/runtime-layer.js +28 -0
  16. package/dist/postgres/schema.d.ts +6 -0
  17. package/dist/postgres/schema.js +274 -0
  18. package/dist/postgres/session-cancellation.d.ts +11 -0
  19. package/dist/postgres/session-cancellation.js +15 -0
  20. package/dist/postgres/session-storage.d.ts +32 -0
  21. package/dist/postgres/session-storage.js +63 -0
  22. package/dist/postgres/session-store.d.ts +24 -0
  23. package/dist/postgres/session-store.js +388 -0
  24. package/dist/postgres/store-admit.d.ts +21 -0
  25. package/dist/postgres/store-admit.js +87 -0
  26. package/dist/postgres/store-cancel.d.ts +14 -0
  27. package/dist/postgres/store-cancel.js +73 -0
  28. package/dist/postgres/store-claims.d.ts +17 -0
  29. package/dist/postgres/store-claims.js +72 -0
  30. package/dist/postgres/store-fan-out.d.ts +21 -0
  31. package/dist/postgres/store-fan-out.js +17 -0
  32. package/dist/postgres/store-inspection.d.ts +12 -0
  33. package/dist/postgres/store-inspection.js +44 -0
  34. package/dist/postgres/store-messaging.d.ts +14 -0
  35. package/dist/postgres/store-messaging.js +12 -0
  36. package/dist/postgres/store-model-response.d.ts +18 -0
  37. package/dist/postgres/store-model-response.js +156 -0
  38. package/dist/postgres/store-ops.d.ts +21 -0
  39. package/dist/postgres/store-ops.js +328 -0
  40. package/dist/postgres/store-program.d.ts +18 -0
  41. package/dist/postgres/store-program.js +36 -0
  42. package/dist/postgres/store-suspend.d.ts +13 -0
  43. package/dist/postgres/store-suspend.js +87 -0
  44. package/dist/postgres/store.d.ts +7 -0
  45. package/dist/postgres/store.js +373 -0
  46. package/dist/postgres/transaction-events.d.ts +15 -0
  47. package/dist/postgres/transaction-events.js +23 -0
  48. package/package.json +54 -0
@@ -0,0 +1,373 @@
1
+ import { Effect, Equal, Option } from "effect";
2
+ import { listRuns } from "tenetkit/runtime/driver/sql/store-list";
3
+ import { SqlClient } from "effect/unstable/sql";
4
+ import { claimExecution, loadExecution, requireExecutionClaim, retryExecution, saveExecution, } from "tenetkit/runtime/driver/sql/store-execution";
5
+ import { PgClient } from "@effect/sql-pg";
6
+ import { ApprovalStale, IdempotencyConflict, ResponseConflict } from "tenetkit/runtime/driver/errors";
7
+ import { ChildSelectionMissing, RunTerminal, RuntimeUnavailable, WaitNotOpen } from "tenetkit/runtime/driver/errors";
8
+ import { childDigest } from "tenetkit/runtime/driver/memory/digest";
9
+ import { enforceChildAdmission } from "tenetkit/runtime/driver/sql/store-admit-send";
10
+ import { equals, resolveChild } from "tenetkit/runtime/driver/executable-manifest";
11
+ import { isTerminal } from "tenetkit/runtime/driver/run";
12
+ import { PendingRunOutcome, RunStore } from "tenetkit/runtime/driver/run-store";
13
+ import { admitSteering, readSteering, saveCompletionContinuation } from "tenetkit/runtime/driver/sql/store-steering";
14
+ import { messagingStoreMethods } from "./store-messaging.js";
15
+ import { withSql } from "tenetkit/runtime/driver/sql/sql-effect";
16
+ import { makeEventHub } from "tenetkit/runtime/driver/sql/subscribers";
17
+ import { check as checkSchema } from "./run-schema.js";
18
+ import {} from "./schema.js";
19
+ import { makeTransactionRunner, nextId } from "./transaction-events.js";
20
+ import { makeEventStream } from "./event-stream.js";
21
+ import { makePostgresClaims } from "./store-claims.js";
22
+ import { postgresOperations } from "./store-ops.js";
23
+ import { releaseLeasedExecution as releaseExecution } from "tenetkit/runtime/driver/sql/store-release-leased";
24
+ import { hasAdmission, loadRunWait } from "tenetkit/runtime/driver/sql/store-helpers";
25
+ import { WaitResolution } from "tenetkit/runtime/driver/run-wait";
26
+ import { fanOutStoreMethods } from "./store-fan-out.js";
27
+ import { deferCancelledFanOutParent, makeCancelRun } from "./store-cancel.js";
28
+ import {} from "tenetkit/runtime/driver/sql/tree-history";
29
+ import {} from "tenetkit/runtime/driver/sql/inspection";
30
+ import { withConsistentSnapshot } from "tenetkit/runtime/driver/sql/inspection-transaction";
31
+ import { StringArray, decodePinnedEffect, decodeStoredPinnedEffect, encodeJson, } from "tenetkit/runtime/driver/sql/codecs";
32
+ import { suspend } from "./store-suspend.js";
33
+ import { afterTerminal, appendEvent, completeRun, emitAgentEvent, insertRun, loadEventsAfter, loadRun, requireRun, settleParent, } from "./pg-helpers.js";
34
+ import { lockMailbox, lockRun, lockRunHierarchy, lockSpawnParent } from "./locks.js";
35
+ import { inspectionStoreMethods } from "./store-inspection.js";
36
+ import { programStoreMethods } from "./store-program.js";
37
+ import { admitStart as admitExactStart } from "tenetkit/runtime/driver/sql/store-admit";
38
+ import { admitSend } from "./store-admit.js";
39
+ import { associateRegistrations, loadRegistrations } from "tenetkit/runtime/driver/sql/executable-registrations";
40
+ import { narrow } from "tenetkit/runtime/driver/executable-registration";
41
+ import { approvalResponse } from "tenetkit/runtime/driver/sql/respond-approval";
42
+ import { settlementNotifications } from "tenetkit/runtime/driver/sql/settlement-notifications";
43
+ import { reconcileCancellationRequested } from "tenetkit/runtime/driver/sql/session-lifecycle";
44
+ import { cancelSessionRuns } from "./session-cancellation.js";
45
+ import { makePostgresSessionStore } from "./session-store.js";
46
+ import { readinessForAdmission } from "tenetkit/runtime/driver/sql/store-child-capacity";
47
+ export const makePostgresServices = (options) => Effect.gen(function* () {
48
+ const source = options.source ?? "postgres";
49
+ const addressBindings = new Map(options.addresses.map((entry) => [entry.address, entry.executable]));
50
+ yield* checkSchema(source);
51
+ const hub = yield* makeEventHub;
52
+ yield* Effect.addFinalizer(() => hub.shutdown);
53
+ const capacity = options.subscriberQueueCapacity ?? 64;
54
+ const sql = yield* SqlClient.SqlClient;
55
+ yield* reconcileCancellationRequested;
56
+ const pg = yield* PgClient.PgClient;
57
+ const { run, runNoTxn, transactionHub } = makeTransactionRunner({ sql, pg, hub });
58
+ const runInspection = (effect) => withSql(sql, withConsistentSnapshot(sql, "postgres", effect.pipe(Effect.provideService(PgClient.PgClient, pg))));
59
+ const cancelRun = makeCancelRun({ sql, hub: transactionHub });
60
+ const operations = postgresOperations({
61
+ sql,
62
+ hub: transactionHub,
63
+ run,
64
+ runNoTxn,
65
+ requireRun,
66
+ requireClaim: requireExecutionClaim,
67
+ nextId,
68
+ });
69
+ const store = RunStore.of({
70
+ info: Effect.succeed({ durability: "durable", backend: "postgres", multiWorker: true }),
71
+ sessionStore: (sessionId) => Effect.succeed(Option.some(makePostgresSessionStore({ sessionId, run, runNoTxn }))),
72
+ hasAdmission: (input) => runNoTxn(hasAdmission(input)),
73
+ admitSend: (input) => run(admitSend(transactionHub, addressBindings, nextId, input)),
74
+ admitStart: (input) => run(sql `SELECT pg_advisory_xact_lock(hashtext('tenetkit:executable-registrations'))`.pipe(Effect.andThen(admitExactStart(transactionHub, input)))),
75
+ admitSpawn: (input) => run(Effect.gen(function* () {
76
+ const parent = yield* lockSpawnParent(input.parentRunId);
77
+ const executableRef = resolveChild(parent.executableRef, parent.executableManifest, input.selection);
78
+ if (executableRef === undefined) {
79
+ return yield* ChildSelectionMissing.make({ parentRunId: parent.runId, selection: input.selection });
80
+ }
81
+ const digest = childDigest(input.message, executableRef, {
82
+ parentRunId: parent.runId,
83
+ invocationId: input.invocationId,
84
+ ...(input.label === undefined ? {} : { label: input.label }),
85
+ ...(input.origin === undefined ? {} : { origin: input.origin }),
86
+ });
87
+ const executable = yield* decodePinnedEffect({
88
+ ref: executableRef,
89
+ manifest: parent.executableManifest,
90
+ });
91
+ const registrations = yield* narrow(executable, yield* loadRegistrations(parent.runId)).pipe(Effect.mapError((error) => RuntimeUnavailable.make({ message: String(error) })));
92
+ const existing = yield* sql `
93
+ SELECT * FROM baton_runs
94
+ WHERE address = ${input.message.to}
95
+ AND session_id = ${input.message.sessionId}
96
+ AND idempotency_key = ${input.message.idempotencyKey}
97
+ `;
98
+ const prior = existing[0];
99
+ if (prior !== undefined) {
100
+ const priorExecutable = yield* decodeStoredPinnedEffect(prior.executable_ref_json, prior.executable_manifest_json);
101
+ if (prior.message_digest !== digest || !equals(priorExecutable, executable)) {
102
+ return yield* IdempotencyConflict.make({
103
+ address: input.message.to,
104
+ sessionId: input.message.sessionId,
105
+ idempotencyKey: input.message.idempotencyKey,
106
+ existingRunId: prior.run_id,
107
+ });
108
+ }
109
+ return {
110
+ runId: prior.run_id,
111
+ messageId: prior.message_id,
112
+ acceptedSequence: Number(prior.accepted_sequence),
113
+ duplicate: true,
114
+ };
115
+ }
116
+ const runId = yield* nextId("run");
117
+ yield* enforceChildAdmission(parent, 1);
118
+ const childReadiness = yield* readinessForAdmission(parent);
119
+ yield* insertRun({
120
+ runId,
121
+ status: "queued",
122
+ message: input.message,
123
+ digest,
124
+ executableRef,
125
+ executableManifest: parent.executableManifest,
126
+ rootRunId: parent.rootRunId,
127
+ depth: parent.depth + 1,
128
+ treePolicy: parent.treePolicy,
129
+ parentRunId: parent.runId,
130
+ invocationId: input.invocationId,
131
+ acceptedSequence: 0,
132
+ });
133
+ yield* associateRegistrations(runId, registrations);
134
+ yield* sql `
135
+ INSERT INTO baton_run_links (parent_run_id, child_run_id, invocation_id, readiness, terminal_event_id, created_at, settled_at)
136
+ VALUES (${parent.runId}, ${runId}, ${input.invocationId}, ${childReadiness}, NULL, NOW(), NULL)
137
+ `;
138
+ yield* appendEvent(transactionHub, parent, {
139
+ _tag: "ChildLinked",
140
+ childRunId: runId,
141
+ invocationId: input.invocationId,
142
+ selection: input.selection,
143
+ prompt: input.message.prompt,
144
+ childDepth: parent.depth + 1,
145
+ readiness: childReadiness,
146
+ ...(input.label === undefined ? {} : { label: input.label }),
147
+ ...(input.origin === undefined ? {} : { origin: input.origin }),
148
+ });
149
+ const child = (yield* loadRun(runId));
150
+ yield* appendEvent(transactionHub, child, { _tag: "RunAccepted", messageId: input.message.id, address: input.message.to }, "queued");
151
+ if (childReadiness !== "ready") {
152
+ return { runId, messageId: input.message.id, acceptedSequence: 0, duplicate: false };
153
+ }
154
+ const started = (yield* loadRun(runId));
155
+ yield* sql `UPDATE baton_runs SET attempt_fence = 1, attempt = 1, status = 'running' WHERE run_id = ${runId}`;
156
+ yield* appendEvent(transactionHub, { ...started, attempt: 1 }, { _tag: "RunAttemptStarted", attempt: 1 }, "running");
157
+ return { runId, messageId: input.message.id, acceptedSequence: 0, duplicate: false };
158
+ })),
159
+ events: (input) => makeEventStream({
160
+ hub,
161
+ pg,
162
+ runId: input.runId,
163
+ cursor: input.cursor,
164
+ capacity,
165
+ loadReplay: runNoTxn(Effect.gen(function* () {
166
+ const loaded = yield* requireRun(input.runId);
167
+ const replay = yield* loadEventsAfter(input.runId, input.cursor);
168
+ return { replay, lastSequence: loaded.lastSequence };
169
+ })),
170
+ loadAfter: (cursor) => runNoTxn(loadEventsAfter(input.runId, cursor)),
171
+ }),
172
+ respond: (input) => run(Effect.gen(function* () {
173
+ yield* lockRun(input.runId);
174
+ const loaded = yield* requireRun(input.runId);
175
+ if (isTerminal(loaded.status))
176
+ return yield* RunTerminal.make({ runId: loaded.runId, status: loaded.status });
177
+ if (loaded.respondedWaitIds.has(input.waitId)) {
178
+ const prior = yield* loadRunWait(loaded.runId, input.waitId);
179
+ if (prior?.resolution !== undefined && Equal.equals(prior.resolution, input.resolution))
180
+ return;
181
+ return yield* ResponseConflict.make({ runId: loaded.runId, waitId: input.waitId });
182
+ }
183
+ if (loaded.cancellationRequested) {
184
+ return yield* WaitNotOpen.make({ runId: loaded.runId, waitId: input.waitId });
185
+ }
186
+ if (loaded.activeWaitId !== input.waitId) {
187
+ return yield* WaitNotOpen.make({ runId: loaded.runId, waitId: input.waitId });
188
+ }
189
+ const responded = [...loaded.respondedWaitIds, input.waitId];
190
+ const resolution = input.resolution;
191
+ const closed = yield* sql `
192
+ UPDATE baton_run_waits SET status = 'responded', response_json = ${encodeJson(WaitResolution, resolution)}, closed_at = NOW()
193
+ WHERE run_id = ${loaded.runId} AND wait_id = ${input.waitId} AND status = 'open'
194
+ RETURNING wait_id
195
+ `;
196
+ if (closed.length === 0) {
197
+ const prior = yield* loadRunWait(loaded.runId, input.waitId);
198
+ if (prior?.resolution !== undefined && Equal.equals(prior.resolution, resolution))
199
+ return;
200
+ return yield* ResponseConflict.make({ runId: loaded.runId, waitId: input.waitId });
201
+ }
202
+ yield* sql `
203
+ UPDATE baton_runs
204
+ SET responded_wait_ids_json = ${encodeJson(StringArray, responded)}, updated_at = NOW()
205
+ WHERE run_id = ${loaded.runId}
206
+ `;
207
+ yield* sql `
208
+ UPDATE baton_program_operations SET status = 'reserved'
209
+ WHERE run_id = ${loaded.runId} AND wait_id = ${input.waitId} AND status = 'waiting'
210
+ `;
211
+ const current = (yield* loadRun(loaded.runId));
212
+ yield* appendEvent(transactionHub, current, { _tag: "RunResumed", waitId: input.waitId, resolution }, "running");
213
+ })),
214
+ respondApproval: (input) => run(Effect.gen(function* () {
215
+ yield* lockRun(input.runId);
216
+ const response = yield* approvalResponse(input);
217
+ if (response._tag === "Duplicate")
218
+ return;
219
+ const loaded = yield* requireRun(input.runId);
220
+ const responded = [...loaded.respondedWaitIds, response.waitId];
221
+ const resolution = input.decision;
222
+ const closed = yield* sql `
223
+ UPDATE baton_run_waits SET status = 'responded', response_json = ${encodeJson(WaitResolution, resolution)}, closed_at = NOW()
224
+ WHERE run_id = ${loaded.runId} AND wait_id = ${response.waitId} AND status = 'open'
225
+ RETURNING wait_id
226
+ `;
227
+ if (closed.length === 0) {
228
+ return yield* ApprovalStale.make({ runId: loaded.runId, approvalId: input.approvalId });
229
+ }
230
+ yield* sql `
231
+ UPDATE baton_runs
232
+ SET responded_wait_ids_json = ${encodeJson(StringArray, responded)}, updated_at = NOW()
233
+ WHERE run_id = ${loaded.runId}
234
+ `;
235
+ yield* sql `
236
+ UPDATE baton_program_operations SET status = 'reserved'
237
+ WHERE run_id = ${loaded.runId} AND wait_id = ${response.waitId} AND status = 'waiting'
238
+ `;
239
+ const current = (yield* loadRun(loaded.runId));
240
+ yield* appendEvent(transactionHub, current, { _tag: "RunResumed", waitId: response.waitId, resolution }, "running");
241
+ })),
242
+ signal: (input) => run(Effect.gen(function* () {
243
+ yield* lockRun(input.runId);
244
+ const loaded = yield* requireRun(input.runId);
245
+ if (isTerminal(loaded.status)) {
246
+ return yield* RunTerminal.make({ runId: loaded.runId, status: loaded.status });
247
+ }
248
+ if (loaded.cancellationRequested)
249
+ return;
250
+ if (loaded.activeWaitId === undefined || loaded.activeWaitId !== input.name)
251
+ return;
252
+ const resolution = {
253
+ _tag: "Signal",
254
+ name: input.name,
255
+ ...(input.payload === undefined ? {} : { payload: input.payload }),
256
+ };
257
+ yield* sql `
258
+ UPDATE baton_run_waits SET status = 'signaled', response_json = ${encodeJson(WaitResolution, resolution)}, closed_at = NOW()
259
+ WHERE run_id = ${loaded.runId} AND wait_id = ${loaded.activeWaitId} AND status = 'open'
260
+ `;
261
+ yield* appendEvent(transactionHub, loaded, { _tag: "RunResumed", waitId: loaded.activeWaitId, resolution }, "running");
262
+ })),
263
+ cancel: (input) => run(lockRunHierarchy(input.runId).pipe(Effect.andThen(cancelRun(input.runId, input.reason)))),
264
+ cancelSession: (input) => run(cancelSessionRuns({ lockRun, cancelRun, ...input })),
265
+ admitSteering: (input) => run(lockRun(input.runId).pipe(Effect.andThen(admitSteering(transactionHub, input)))),
266
+ readSteering: (input) => run(requireExecutionClaim(input).pipe(Effect.andThen(readSteering(input)))),
267
+ ...messagingStoreMethods({ run, runNoTxn, hub: transactionHub, lockRun, lockMailbox }),
268
+ settlementNotifications: (input) => runNoTxn(settlementNotifications(input)),
269
+ ...inspectionStoreMethods({ hub, pg, run, runNoTxn, runInspection }),
270
+ list: (input) => runNoTxn(listRuns(input)),
271
+ complete: (input) => run(Effect.gen(function* () {
272
+ yield* lockRunHierarchy(input.runId);
273
+ yield* requireExecutionClaim(input);
274
+ const loaded = yield* requireRun(input.runId);
275
+ if (isTerminal(loaded.status)) {
276
+ return yield* RunTerminal.make({ runId: loaded.runId, status: loaded.status });
277
+ }
278
+ const continuation = yield* saveCompletionContinuation(input.runId, input.result);
279
+ if (continuation !== undefined)
280
+ return { _tag: "SteeringPending", continuation };
281
+ yield* completeRun(transactionHub, loaded, input.result);
282
+ return { _tag: "Completed" };
283
+ })),
284
+ fail: (input) => run(Effect.gen(function* () {
285
+ yield* lockRunHierarchy(input.runId);
286
+ yield* requireExecutionClaim(input);
287
+ const loaded = yield* requireRun(input.runId);
288
+ if (isTerminal(loaded.status)) {
289
+ return yield* RunTerminal.make({ runId: loaded.runId, status: loaded.status });
290
+ }
291
+ if (loaded.cancellationRequested) {
292
+ if (yield* deferCancelledFanOutParent(sql, loaded.runId))
293
+ return;
294
+ const event = yield* appendEvent(transactionHub, loaded, { _tag: "RunCancelled", ...(loaded.cancelReason === undefined ? {} : { reason: loaded.cancelReason }) }, "cancelled");
295
+ const settled = (yield* loadRun(loaded.runId));
296
+ yield* settleParent(transactionHub, settled, event.eventId);
297
+ yield* afterTerminal(transactionHub, settled);
298
+ return;
299
+ }
300
+ const runningFanOut = yield* sql `
301
+ SELECT fan_out_id FROM baton_fan_outs WHERE parent_run_id = ${loaded.runId} AND status = 'running' LIMIT 1
302
+ `;
303
+ if (runningFanOut.length > 0) {
304
+ yield* sql `
305
+ UPDATE baton_runs SET status = 'waiting', owner_worker_id = NULL, lease_expires_at = NULL,
306
+ suspension_json = NULL,
307
+ pending_outcome_json = ${encodeJson(PendingRunOutcome, { _tag: "Failed", error: input.error })}
308
+ WHERE run_id = ${loaded.runId}
309
+ `;
310
+ return;
311
+ }
312
+ const event = yield* appendEvent(transactionHub, loaded, { _tag: "RunFailed", error: input.error }, "failed");
313
+ const settled = (yield* loadRun(loaded.runId));
314
+ yield* settleParent(transactionHub, settled, event.eventId);
315
+ yield* afterTerminal(transactionHub, settled);
316
+ })),
317
+ suspend: (input) => run(suspend(transactionHub, input)),
318
+ resume: (input) => run(Effect.gen(function* () {
319
+ yield* lockRun(input.runId);
320
+ const loaded = yield* requireRun(input.runId);
321
+ if (isTerminal(loaded.status)) {
322
+ return yield* RunTerminal.make({ runId: loaded.runId, status: loaded.status });
323
+ }
324
+ if (loaded.respondedWaitIds.has(input.waitId)) {
325
+ const prior = yield* loadRunWait(loaded.runId, input.waitId);
326
+ if (prior?.resolution !== undefined && Equal.equals(prior.resolution, input.resolution))
327
+ return;
328
+ return yield* ResponseConflict.make({ runId: loaded.runId, waitId: input.waitId });
329
+ }
330
+ if (loaded.cancellationRequested) {
331
+ return yield* WaitNotOpen.make({ runId: loaded.runId, waitId: input.waitId });
332
+ }
333
+ if (loaded.activeWaitId !== input.waitId) {
334
+ return yield* WaitNotOpen.make({ runId: loaded.runId, waitId: input.waitId });
335
+ }
336
+ const responded = [...loaded.respondedWaitIds, input.waitId];
337
+ const resolution = input.resolution;
338
+ const closed = yield* sql `
339
+ UPDATE baton_run_waits SET status = 'responded', response_json = ${encodeJson(WaitResolution, resolution)}, closed_at = NOW()
340
+ WHERE run_id = ${loaded.runId} AND wait_id = ${input.waitId} AND status = 'open'
341
+ RETURNING wait_id
342
+ `;
343
+ if (closed.length === 0) {
344
+ const prior = yield* loadRunWait(loaded.runId, input.waitId);
345
+ if (prior?.resolution !== undefined && Equal.equals(prior.resolution, resolution))
346
+ return;
347
+ return yield* ResponseConflict.make({ runId: loaded.runId, waitId: input.waitId });
348
+ }
349
+ yield* sql `
350
+ UPDATE baton_runs
351
+ SET responded_wait_ids_json = ${encodeJson(StringArray, responded)}, updated_at = NOW()
352
+ WHERE run_id = ${loaded.runId}
353
+ `;
354
+ yield* sql `
355
+ UPDATE baton_program_operations SET status = 'reserved'
356
+ WHERE run_id = ${loaded.runId} AND wait_id = ${input.waitId} AND status = 'waiting'
357
+ `;
358
+ const current = (yield* loadRun(loaded.runId));
359
+ yield* appendEvent(transactionHub, current, { _tag: "RunResumed", waitId: input.waitId, resolution }, "running");
360
+ })),
361
+ emitAgentEvent: (input) => run(emitAgentEvent(transactionHub, input)),
362
+ claimExecution: (input) => run(claimExecution(transactionHub, input)),
363
+ loadExecution: (runId) => run(loadExecution(runId)),
364
+ releaseExecution: (input) => run(releaseExecution(input)),
365
+ saveExecution: (input) => run(saveExecution(input)),
366
+ retryExecution: (input) => run(lockRun(input.runId).pipe(Effect.andThen(retryExecution(transactionHub, input)))),
367
+ ...fanOutStoreMethods({ sql, pg, hub: transactionHub, run, runNoTxn }),
368
+ ...operations,
369
+ ...programStoreMethods({ sql, hub: transactionHub, run, runNoTxn, lockRunHierarchy }),
370
+ });
371
+ const claims = makePostgresClaims({ sql, hub: transactionHub, run, cancelRun });
372
+ return { store, claims };
373
+ });
@@ -0,0 +1,15 @@
1
+ import { Effect } from "effect";
2
+ import { PgClient } from "@effect/sql-pg";
3
+ import { SqlClient } from "effect/unstable/sql";
4
+ import type { EventHub } from "tenetkit/runtime/driver/sql/subscribers";
5
+ import type { RunFn } from "./store-ops.js";
6
+ export declare const makeTransactionRunner: (input: {
7
+ readonly sql: SqlClient.SqlClient;
8
+ readonly pg: PgClient.PgClient;
9
+ readonly hub: EventHub;
10
+ }) => {
11
+ run: RunFn;
12
+ runNoTxn: RunFn;
13
+ transactionHub: EventHub;
14
+ };
15
+ export declare const nextId: (prefix: string) => Effect.Effect<string>;
@@ -0,0 +1,23 @@
1
+ import { Context, Effect, Random } from "effect";
2
+ import { PgClient } from "@effect/sql-pg";
3
+ import { SqlClient } from "effect/unstable/sql";
4
+ import { withSql } from "tenetkit/runtime/driver/sql/sql-effect";
5
+ import { NOTIFY_CHANNEL } from "./schema.js";
6
+ const TransactionEvents = Context.Reference("tenetkit/runtime/sql/postgres/TransactionEvents", { defaultValue: () => [] });
7
+ export const makeTransactionRunner = (input) => {
8
+ const transactionHub = {
9
+ ...input.hub,
10
+ publish: (runId, event) => Effect.flatMap(TransactionEvents, (events) => Effect.sync(() => void events.push([runId, event]))),
11
+ };
12
+ const runRaw = (effect) => withSql(input.sql, input.sql.withTransaction(effect.pipe(Effect.provideService(PgClient.PgClient, input.pg))));
13
+ const run = (effect) => runRaw(Effect.gen(function* () {
14
+ const events = [];
15
+ const result = yield* effect.pipe(Effect.provideService(TransactionEvents, events));
16
+ return [result, events];
17
+ })).pipe(Effect.tap(([, events]) => Effect.forEach(new Set(events.map(([runId]) => runId)), (runId) => input.pg
18
+ .notify(NOTIFY_CHANNEL, runId)
19
+ .pipe(Effect.catch((error) => Effect.logWarning("runtime.postgres.event_notification.failed").pipe(Effect.annotateLogs({ "tenetkit.run.id": runId, "tenetkit.failure": String(error) })))), { discard: true })), Effect.map(([result]) => result));
20
+ const runNoTxn = (effect) => withSql(input.sql, effect.pipe(Effect.provideService(PgClient.PgClient, input.pg)));
21
+ return { run, runNoTxn, transactionHub };
22
+ };
23
+ export const nextId = (prefix) => Random.nextIntBetween(0, Number.MAX_SAFE_INTEGER).pipe(Effect.map((random) => `${prefix}_${random.toString(36)}`));
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/package.json",
3
+ "version": "0.28.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "peerDependencies": {
8
+ "effect": "4.0.0-beta.98"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md"
13
+ ],
14
+ "engines": {
15
+ "node": ">=22",
16
+ "bun": ">=1.3.0"
17
+ },
18
+ "homepage": "https://github.com/In-Time-Tec/tenetkit#readme",
19
+ "bugs": {
20
+ "url": "https://github.com/In-Time-Tec/tenetkit/issues"
21
+ },
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "name": "@tenetkit/pg",
26
+ "description": "PostgreSQL runtime driver for TenetKit",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/postgres/index.d.ts",
30
+ "import": "./dist/postgres/index.js"
31
+ }
32
+ },
33
+ "scripts": {
34
+ "build": "rm -rf dist && bun tsc -p tsconfig.build.json",
35
+ "typecheck": "bun tsc --noEmit",
36
+ "test": "bun --bun --cwd ../.. vitest run packages/pg/test --no-file-parallelism --maxWorkers=1"
37
+ },
38
+ "dependencies": {
39
+ "@effect/sql-pg": "4.0.0-beta.98",
40
+ "tenetkit": "0.28.0"
41
+ },
42
+ "devDependencies": {
43
+ "@effect/vitest": "4.0.0-beta.98",
44
+ "@types/bun": "1.3.13",
45
+ "effect": "4.0.0-beta.98",
46
+ "typescript": "7.0.2",
47
+ "vitest": "4.0.16"
48
+ },
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "git+https://github.com/In-Time-Tec/tenetkit.git",
52
+ "directory": "packages/pg"
53
+ }
54
+ }