@smthrs/run-store 0.0.0-stage → 1.0.0-rc.3

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 (157) hide show
  1. package/CHANGELOG.md +112 -0
  2. package/LICENSE +21 -0
  3. package/README.md +195 -2
  4. package/dist/cjs/AttemptStore.d.ts +359 -0
  5. package/dist/cjs/AttemptStore.d.ts.map +1 -0
  6. package/dist/cjs/AttemptStore.js +731 -0
  7. package/dist/cjs/AttemptStore.js.map +7 -0
  8. package/dist/cjs/Heartbeat.d.ts +46 -0
  9. package/dist/cjs/Heartbeat.d.ts.map +1 -0
  10. package/dist/cjs/Heartbeat.js +28 -0
  11. package/dist/cjs/Heartbeat.js.map +7 -0
  12. package/dist/cjs/Migrations.d.ts +42 -0
  13. package/dist/cjs/Migrations.d.ts.map +1 -0
  14. package/dist/cjs/Migrations.js +55 -0
  15. package/dist/cjs/Migrations.js.map +7 -0
  16. package/dist/cjs/Ownership.d.ts +275 -0
  17. package/dist/cjs/Ownership.d.ts.map +1 -0
  18. package/dist/cjs/Ownership.js +129 -0
  19. package/dist/cjs/Ownership.js.map +7 -0
  20. package/dist/cjs/RunStore.d.ts +571 -0
  21. package/dist/cjs/RunStore.d.ts.map +1 -0
  22. package/dist/cjs/RunStore.js +1079 -0
  23. package/dist/cjs/RunStore.js.map +7 -0
  24. package/dist/cjs/RunStoreMetrics.d.ts +151 -0
  25. package/dist/cjs/RunStoreMetrics.d.ts.map +1 -0
  26. package/dist/cjs/RunStoreMetrics.js +91 -0
  27. package/dist/cjs/RunStoreMetrics.js.map +7 -0
  28. package/dist/cjs/index.d.ts +47 -0
  29. package/dist/cjs/index.d.ts.map +1 -0
  30. package/dist/cjs/index.js +43 -0
  31. package/dist/cjs/index.js.map +7 -0
  32. package/dist/cjs/internal/Boundary.d.ts +80 -0
  33. package/dist/cjs/internal/Boundary.d.ts.map +1 -0
  34. package/dist/cjs/internal/Boundary.js +92 -0
  35. package/dist/cjs/internal/Boundary.js.map +7 -0
  36. package/dist/cjs/internal/Companion.d.ts +44 -0
  37. package/dist/cjs/internal/Companion.d.ts.map +1 -0
  38. package/dist/cjs/internal/Companion.js +50 -0
  39. package/dist/cjs/internal/Companion.js.map +7 -0
  40. package/dist/cjs/internal/SpanOutcome.d.ts +37 -0
  41. package/dist/cjs/internal/SpanOutcome.d.ts.map +1 -0
  42. package/dist/cjs/internal/SpanOutcome.js +62 -0
  43. package/dist/cjs/internal/SpanOutcome.js.map +7 -0
  44. package/dist/cjs/migrations/0001_initial.d.ts +15 -0
  45. package/dist/cjs/migrations/0001_initial.d.ts.map +1 -0
  46. package/dist/cjs/migrations/0001_initial.js +110 -0
  47. package/dist/cjs/migrations/0001_initial.js.map +7 -0
  48. package/dist/cjs/migrations/0002_lineage.d.ts +27 -0
  49. package/dist/cjs/migrations/0002_lineage.d.ts.map +1 -0
  50. package/dist/cjs/migrations/0002_lineage.js +43 -0
  51. package/dist/cjs/migrations/0002_lineage.js.map +7 -0
  52. package/dist/cjs/migrations/0003_execution_revisions.d.ts +15 -0
  53. package/dist/cjs/migrations/0003_execution_revisions.d.ts.map +1 -0
  54. package/dist/cjs/migrations/0003_execution_revisions.js +77 -0
  55. package/dist/cjs/migrations/0003_execution_revisions.js.map +7 -0
  56. package/dist/cjs/migrations/0004_waiting_request.d.ts +32 -0
  57. package/dist/cjs/migrations/0004_waiting_request.d.ts.map +1 -0
  58. package/dist/cjs/migrations/0004_waiting_request.js +42 -0
  59. package/dist/cjs/migrations/0004_waiting_request.js.map +7 -0
  60. package/dist/cjs/package.json +1 -0
  61. package/dist/cjs/test/TestRunStore.d.ts +17 -0
  62. package/dist/cjs/test/TestRunStore.d.ts.map +1 -0
  63. package/dist/cjs/test/TestRunStore.js +42 -0
  64. package/dist/cjs/test/TestRunStore.js.map +7 -0
  65. package/dist/esm/AttemptStore.d.ts +359 -0
  66. package/dist/esm/AttemptStore.d.ts.map +1 -0
  67. package/dist/esm/AttemptStore.js +835 -0
  68. package/dist/esm/AttemptStore.js.map +1 -0
  69. package/dist/esm/Heartbeat.d.ts +46 -0
  70. package/dist/esm/Heartbeat.d.ts.map +1 -0
  71. package/dist/esm/Heartbeat.js +46 -0
  72. package/dist/esm/Heartbeat.js.map +1 -0
  73. package/dist/esm/Migrations.d.ts +42 -0
  74. package/dist/esm/Migrations.d.ts.map +1 -0
  75. package/dist/esm/Migrations.js +56 -0
  76. package/dist/esm/Migrations.js.map +1 -0
  77. package/dist/esm/Ownership.d.ts +275 -0
  78. package/dist/esm/Ownership.d.ts.map +1 -0
  79. package/dist/esm/Ownership.js +299 -0
  80. package/dist/esm/Ownership.js.map +1 -0
  81. package/dist/esm/RunStore.d.ts +571 -0
  82. package/dist/esm/RunStore.d.ts.map +1 -0
  83. package/dist/esm/RunStore.js +1287 -0
  84. package/dist/esm/RunStore.js.map +1 -0
  85. package/dist/esm/RunStoreMetrics.d.ts +151 -0
  86. package/dist/esm/RunStoreMetrics.d.ts.map +1 -0
  87. package/dist/esm/RunStoreMetrics.js +120 -0
  88. package/dist/esm/RunStoreMetrics.js.map +1 -0
  89. package/dist/esm/index.d.ts +47 -0
  90. package/dist/esm/index.d.ts.map +1 -0
  91. package/dist/esm/index.js +47 -0
  92. package/dist/esm/index.js.map +1 -0
  93. package/dist/esm/internal/Boundary.d.ts +80 -0
  94. package/dist/esm/internal/Boundary.d.ts.map +1 -0
  95. package/dist/esm/internal/Boundary.js +109 -0
  96. package/dist/esm/internal/Boundary.js.map +1 -0
  97. package/dist/esm/internal/Companion.d.ts +44 -0
  98. package/dist/esm/internal/Companion.d.ts.map +1 -0
  99. package/dist/esm/internal/Companion.js +53 -0
  100. package/dist/esm/internal/Companion.js.map +1 -0
  101. package/dist/esm/internal/SpanOutcome.d.ts +37 -0
  102. package/dist/esm/internal/SpanOutcome.d.ts.map +1 -0
  103. package/dist/esm/internal/SpanOutcome.js +48 -0
  104. package/dist/esm/internal/SpanOutcome.js.map +1 -0
  105. package/dist/esm/migrations/0001_initial.d.ts +15 -0
  106. package/dist/esm/migrations/0001_initial.d.ts.map +1 -0
  107. package/dist/esm/migrations/0001_initial.js +88 -0
  108. package/dist/esm/migrations/0001_initial.js.map +1 -0
  109. package/dist/esm/migrations/0002_lineage.d.ts +27 -0
  110. package/dist/esm/migrations/0002_lineage.d.ts.map +1 -0
  111. package/dist/esm/migrations/0002_lineage.js +33 -0
  112. package/dist/esm/migrations/0002_lineage.js.map +1 -0
  113. package/dist/esm/migrations/0003_execution_revisions.d.ts +15 -0
  114. package/dist/esm/migrations/0003_execution_revisions.d.ts.map +1 -0
  115. package/dist/esm/migrations/0003_execution_revisions.js +58 -0
  116. package/dist/esm/migrations/0003_execution_revisions.js.map +1 -0
  117. package/dist/esm/migrations/0004_waiting_request.d.ts +32 -0
  118. package/dist/esm/migrations/0004_waiting_request.d.ts.map +1 -0
  119. package/dist/esm/migrations/0004_waiting_request.js +37 -0
  120. package/dist/esm/migrations/0004_waiting_request.js.map +1 -0
  121. package/dist/esm/test/TestRunStore.d.ts +17 -0
  122. package/dist/esm/test/TestRunStore.d.ts.map +1 -0
  123. package/dist/esm/test/TestRunStore.js +19 -0
  124. package/dist/esm/test/TestRunStore.js.map +1 -0
  125. package/docs/README.md +142 -0
  126. package/docs/api.md +901 -0
  127. package/docs/concepts/durable-values.md +140 -0
  128. package/docs/concepts/execution-revisions.md +53 -0
  129. package/docs/concepts/fencing.md +146 -0
  130. package/docs/concepts/leases.md +154 -0
  131. package/docs/concepts/liveness-evidence.md +142 -0
  132. package/docs/guides/cancel-a-run.md +156 -0
  133. package/docs/guides/claim-and-finish-a-run.md +175 -0
  134. package/docs/guides/compose-the-stores.md +142 -0
  135. package/docs/guides/observe-outcomes.md +182 -0
  136. package/docs/guides/record-step-attempts.md +170 -0
  137. package/docs/guides/recover-a-stalled-run.md +173 -0
  138. package/docs/guides/testing.md +141 -0
  139. package/docs/installation.md +109 -0
  140. package/docs/quickstart.md +201 -0
  141. package/docs/troubleshooting.md +255 -0
  142. package/package.json +183 -3
  143. package/src/AttemptStore.ts +1195 -0
  144. package/src/Heartbeat.ts +47 -0
  145. package/src/Migrations.ts +59 -0
  146. package/src/Ownership.ts +407 -0
  147. package/src/RunStore.ts +2063 -0
  148. package/src/RunStoreMetrics.ts +165 -0
  149. package/src/index.ts +51 -0
  150. package/src/internal/Boundary.ts +130 -0
  151. package/src/internal/Companion.ts +64 -0
  152. package/src/internal/SpanOutcome.ts +71 -0
  153. package/src/migrations/0001_initial.ts +114 -0
  154. package/src/migrations/0002_lineage.ts +38 -0
  155. package/src/migrations/0003_execution_revisions.ts +69 -0
  156. package/src/migrations/0004_waiting_request.ts +39 -0
  157. package/src/test/TestRunStore.ts +22 -0
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Durable database identity and coalesced execution change tracking.
3
+ *
4
+ * @since 1.0.0
5
+ */
6
+
7
+ import * as Dialect from "@smthrs/database/Dialect"
8
+ import * as Effect from "effect/Effect"
9
+ import * as SqlClient from "effect/unstable/sql/SqlClient"
10
+
11
+ /**
12
+ * Retains one current revision per run ID, including deleted IDs forever.
13
+ * Triggers participate in the mutating transaction, including raw SQL writes.
14
+ * @category migrations
15
+ * @since 1.0.0
16
+ */
17
+ export const executionRevisions: Effect.Effect<void, unknown, SqlClient.SqlClient> = Effect.gen(function*() {
18
+ const sql = yield* SqlClient.SqlClient
19
+ yield* sql`CREATE TABLE flows_run_source (
20
+ singleton ${Dialect.integer(sql)} PRIMARY KEY CHECK (singleton = 1),
21
+ source TEXT NOT NULL CHECK (length(source) = 32 AND ${
22
+ sql.literal(Dialect.isPostgres(sql) ? "source !~ '[^0-9a-f]'" : "source NOT GLOB '*[^0-9a-f]*'")
23
+ }),
24
+ revision ${Dialect.integer(sql)} NOT NULL CHECK (${
25
+ Dialect.isInteger(sql, sql`revision`)
26
+ } AND revision >= 0 AND revision <= 9007199254740991)
27
+ )`
28
+ yield* sql`INSERT INTO flows_run_source VALUES (1, ${
29
+ sql.literal(Dialect.isPostgres(sql) ? "replace(gen_random_uuid()::text, '-', '')" : "lower(hex(randomblob(16)))")
30
+ }, 0)`
31
+ yield* sql`CREATE TABLE flows_run_changes (
32
+ run_id TEXT PRIMARY KEY NOT NULL CHECK (length(run_id) > 0),
33
+ revision ${Dialect.integer(sql)} NOT NULL UNIQUE CHECK (${
34
+ Dialect.isInteger(sql, sql`revision`)
35
+ } AND revision > 0 AND revision <= 9007199254740991),
36
+ deleted ${Dialect.integer(sql)} NOT NULL CHECK (deleted IN (0, 1))
37
+ )`
38
+ yield* sql`ALTER TABLE flows_runs ADD COLUMN cancel_acknowledgement_json TEXT
39
+ CHECK (cancel_acknowledgement_json IS NULL OR ${Dialect.jsonValid(sql, sql`cancel_acknowledgement_json`)})`
40
+ // Old rows receive a real baseline revision; their prior mutation order is unknown.
41
+ yield* sql`INSERT INTO flows_run_changes (run_id, revision, deleted)
42
+ SELECT run_id, ROW_NUMBER() OVER (ORDER BY created_at_ms, run_id), 0 FROM flows_runs`
43
+ yield* sql`UPDATE flows_run_source SET revision = (SELECT COUNT(*) FROM flows_run_changes)`
44
+ for (const [operation, row, deleted] of [["INSERT", "NEW", 0], ["UPDATE", "NEW", 0], ["DELETE", "OLD", 1]] as const) {
45
+ yield* Dialect.trigger(sql, {
46
+ name: `flows_runs_revision_${operation.toLowerCase()}`,
47
+ table: "flows_runs",
48
+ event: `AFTER ${operation}`,
49
+ body: `
50
+ ${
51
+ Dialect.isPostgres(sql)
52
+ ? "IF NOT EXISTS (SELECT 1 FROM flows_run_source WHERE singleton = 1) THEN RAISE EXCEPTION 'execution source identity is missing' USING ERRCODE = '23514'; END IF;"
53
+ : "SELECT CASE WHEN NOT EXISTS (SELECT 1 FROM flows_run_source WHERE singleton = 1) THEN RAISE(ABORT, 'execution source identity is missing') END;"
54
+ }
55
+ UPDATE flows_run_source SET revision = revision + 1 WHERE singleton = 1;
56
+ INSERT INTO flows_run_changes (run_id, revision, deleted)
57
+ SELECT ${row}.run_id, revision, ${deleted} FROM flows_run_source WHERE singleton = 1
58
+ ON CONFLICT (run_id) DO UPDATE SET revision = excluded.revision, deleted = excluded.deleted;
59
+ `
60
+ })
61
+ }
62
+ yield* Dialect.trigger(sql, {
63
+ name: `flows_runs_identity_immutable`,
64
+ table: `flows_runs`,
65
+ event: "BEFORE UPDATE OF run_id",
66
+ when: `NEW.run_id <> OLD.run_id`,
67
+ reject: "run identity is immutable"
68
+ })
69
+ })
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The question a parked run is waiting on an answer to.
3
+ *
4
+ * `waiting_reason` and `waiting_token` say THAT a run is parked on a human
5
+ * decision and which wait point holds it; neither says what was asked. A
6
+ * `HumanTask` park therefore left the prompt, the kind of answer it wants, and
7
+ * the attempt budget nowhere a reader could find them: the question lived only
8
+ * in the action payload of an execution that had already released its owner,
9
+ * so the approvals inbox could describe the gate as "something is waiting" and
10
+ * no more (run-3, `coding-clarification`).
11
+ *
12
+ * This column is that missing half. It is the JSON the wait declared about
13
+ * itself — for `HumanTask`, `{kind, name, prompt, attempt, maxAttempts}` — and
14
+ * it is written and cleared with the other three waiting columns, so it can
15
+ * never describe a wait the run is no longer holding.
16
+ *
17
+ * Append-only, as every migration after the first must be: the column is
18
+ * nullable, so every row an earlier build wrote stays valid and reads back as
19
+ * a park that declared no question.
20
+ *
21
+ * @since 1.0.0
22
+ */
23
+
24
+ import * as Dialect from "@smthrs/database/Dialect"
25
+ import * as Effect from "effect/Effect"
26
+ import * as SqlClient from "effect/unstable/sql/SqlClient"
27
+
28
+ /**
29
+ * Adds `waiting_request` to `flows_runs`.
30
+ *
31
+ * @category migrations
32
+ * @since 1.0.0
33
+ */
34
+ export const waitingRequest: Effect.Effect<void, unknown, SqlClient.SqlClient> = Effect.gen(function*() {
35
+ const sql = yield* SqlClient.SqlClient
36
+
37
+ yield* sql`ALTER TABLE flows_runs ADD COLUMN waiting_request TEXT
38
+ CHECK (waiting_request IS NULL OR ${Dialect.jsonValid(sql, sql`waiting_request`)})`
39
+ })
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Deterministic bundle of the production run and attempt services.
3
+ *
4
+ * @since 0.1.0
5
+ */
6
+
7
+ import * as TestDatabase from "@smthrs/database/test/TestDatabase"
8
+ import * as Layer from "effect/Layer"
9
+ import * as AttemptStore from "../AttemptStore.ts"
10
+ import * as Migrations from "../Migrations.ts"
11
+ import * as RunStore from "../RunStore.ts"
12
+
13
+ /**
14
+ * Provides the production SQLite run and attempt stores over an in-memory
15
+ * database. Migrations run before either service is exposed.
16
+ *
17
+ * @category layers
18
+ * @since 0.1.0
19
+ */
20
+ export const layer = Layer.mergeAll(RunStore.layer, AttemptStore.layer).pipe(
21
+ Layer.provide(Layer.provideMerge(Migrations.layer, TestDatabase.layer))
22
+ )