@nylorun/runtime 0.13.0-beta → 0.15.0-beta

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 (309) hide show
  1. package/CHANGELOG.md +434 -0
  2. package/README.md +137 -41
  3. package/dist/adapters/execution/restate.d.ts +7 -2
  4. package/dist/adapters/execution/restate.js +40 -10
  5. package/dist/adapters/replication/pgoutput.d.ts +21 -0
  6. package/dist/adapters/replication/pgoutput.js +170 -0
  7. package/dist/adapters/streams/s2.js +37 -20
  8. package/dist/admin-openapi.json +570 -0
  9. package/dist/api/a2a/card.d.ts +15 -0
  10. package/dist/api/a2a/card.js +24 -0
  11. package/dist/api/a2a/endpoint.d.ts +8 -0
  12. package/dist/api/a2a/endpoint.js +81 -0
  13. package/dist/api/a2a/protocol.d.ts +144 -0
  14. package/dist/api/a2a/protocol.js +188 -0
  15. package/dist/api/a2a/routes.d.ts +19 -0
  16. package/dist/api/a2a/routes.js +289 -0
  17. package/dist/api/a2a/tasks.d.ts +50 -0
  18. package/dist/api/a2a/tasks.js +186 -0
  19. package/dist/api/ag-ui/endpoint.d.ts +8 -0
  20. package/dist/api/ag-ui/endpoint.js +126 -0
  21. package/dist/api/ag-ui/history.d.ts +7 -0
  22. package/dist/api/ag-ui/history.js +60 -0
  23. package/dist/api/ag-ui/routes.d.ts +32 -0
  24. package/dist/api/ag-ui/routes.js +305 -0
  25. package/dist/api/ag-ui/session-id.d.ts +1 -0
  26. package/dist/api/ag-ui/session-id.js +12 -0
  27. package/dist/api/ag-ui/sse.d.ts +9 -0
  28. package/dist/api/ag-ui/sse.js +7 -0
  29. package/dist/api/ag-ui/translate.d.ts +38 -0
  30. package/dist/api/ag-ui/translate.js +222 -0
  31. package/dist/api/components.d.ts +8065 -0
  32. package/dist/api/components.js +82 -0
  33. package/dist/api/gate/keys.d.ts +14 -0
  34. package/dist/api/gate/keys.js +59 -0
  35. package/dist/api/gate/routes.d.ts +34 -0
  36. package/dist/api/gate/routes.js +235 -0
  37. package/dist/api/http/app.d.ts +18 -0
  38. package/dist/api/http/app.js +58 -0
  39. package/dist/api/http/body.d.ts +4 -0
  40. package/dist/api/http/body.js +37 -0
  41. package/dist/api/http/define.d.ts +39 -0
  42. package/dist/api/http/define.js +122 -0
  43. package/dist/api/http/respond.d.ts +9 -0
  44. package/dist/api/http/respond.js +44 -0
  45. package/dist/api/http/routes/access.d.ts +24 -0
  46. package/dist/api/http/routes/access.js +266 -0
  47. package/dist/api/http/routes/actions.d.ts +8 -0
  48. package/dist/api/http/routes/actions.js +76 -0
  49. package/dist/api/http/routes/endpoints.d.ts +7 -0
  50. package/dist/api/http/routes/endpoints.js +100 -0
  51. package/dist/api/http/routes/sessions.d.ts +3 -0
  52. package/dist/api/http/routes/sessions.js +205 -0
  53. package/dist/api/http/routes/tenant.d.ts +11 -0
  54. package/dist/api/http/routes/tenant.js +244 -0
  55. package/dist/api/http/routes/vaults.d.ts +8 -0
  56. package/dist/api/http/routes/vaults.js +167 -0
  57. package/dist/api/openapi.d.ts +19 -0
  58. package/dist/api/openapi.js +137 -0
  59. package/dist/api/route.d.ts +12 -0
  60. package/dist/api/route.js +4 -0
  61. package/dist/core/bearer.d.ts +8 -0
  62. package/dist/core/bearer.js +11 -0
  63. package/dist/core/flow-host.d.ts +7 -15
  64. package/dist/core/flow-host.js +16 -41
  65. package/dist/core/sandbox-routes.d.ts +12 -7
  66. package/dist/core/sandbox-routes.js +24 -25
  67. package/dist/execution/memory.d.ts +8 -5
  68. package/dist/execution/memory.js +33 -13
  69. package/dist/execution/types.d.ts +38 -6
  70. package/dist/execution/types.js +12 -3
  71. package/dist/gates/contract.d.ts +51 -0
  72. package/dist/gates/contract.js +37 -0
  73. package/dist/gates/handler.d.ts +21 -0
  74. package/dist/gates/handler.js +55 -0
  75. package/dist/gates/http-client.d.ts +12 -0
  76. package/dist/gates/http-client.js +160 -0
  77. package/dist/gates/in-process.d.ts +32 -0
  78. package/dist/gates/in-process.js +42 -0
  79. package/dist/gates/inflight.d.ts +39 -0
  80. package/dist/gates/inflight.js +70 -0
  81. package/dist/gates/mcp-handler.d.ts +35 -0
  82. package/dist/gates/mcp-handler.js +142 -0
  83. package/dist/gates/meter.d.ts +24 -0
  84. package/dist/gates/meter.js +165 -0
  85. package/dist/gates/model-gate.d.ts +45 -0
  86. package/dist/gates/model-gate.js +1 -0
  87. package/dist/gates/tenant-vaults.d.ts +64 -0
  88. package/dist/gates/tenant-vaults.js +94 -0
  89. package/dist/gates/tool-calls.d.ts +51 -0
  90. package/dist/gates/tool-calls.js +64 -0
  91. package/dist/gates/tool-client.d.ts +12 -0
  92. package/dist/gates/tool-client.js +201 -0
  93. package/dist/gates/tool-contract.d.ts +116 -0
  94. package/dist/gates/tool-contract.js +64 -0
  95. package/dist/gates/tool-gate.d.ts +45 -0
  96. package/dist/gates/tool-gate.js +10 -0
  97. package/dist/host/admin-api.d.ts +23 -0
  98. package/dist/host/admin-api.js +74 -0
  99. package/dist/host/app.d.ts +34 -0
  100. package/dist/host/app.js +181 -0
  101. package/dist/host/config-for.d.ts +3 -1
  102. package/dist/host/config-for.js +3 -2
  103. package/dist/host/config.d.ts +11 -0
  104. package/dist/host/cors.d.ts +27 -0
  105. package/dist/host/cors.js +64 -0
  106. package/dist/host/create-host.d.ts +36 -8
  107. package/dist/host/create-host.js +161 -330
  108. package/dist/host/execution.d.ts +20 -18
  109. package/dist/host/execution.js +2 -2
  110. package/dist/host/gates.d.ts +58 -0
  111. package/dist/host/gates.js +127 -0
  112. package/dist/host/http.d.ts +20 -4
  113. package/dist/host/http.js +81 -23
  114. package/dist/host/index.d.ts +1 -1
  115. package/dist/host/main.js +189 -58
  116. package/dist/host/stack-config.d.ts +91 -19
  117. package/dist/host/stack-config.js +246 -17
  118. package/dist/infra/database.d.ts +2 -2
  119. package/dist/infra/execution.d.ts +5 -5
  120. package/dist/infra/execution.js +8 -6
  121. package/dist/infra/index.d.ts +1 -1
  122. package/dist/infra/workers.d.ts +17 -17
  123. package/dist/infra/workers.js +9 -9
  124. package/dist/keys/client.d.ts +9 -0
  125. package/dist/keys/client.js +70 -0
  126. package/dist/keys/contract.d.ts +24 -0
  127. package/dist/keys/contract.js +12 -0
  128. package/dist/keys/keys.d.ts +41 -0
  129. package/dist/keys/keys.js +50 -0
  130. package/dist/mcp/connect.d.ts +43 -3
  131. package/dist/mcp/connect.js +12 -5
  132. package/dist/mcp/pool.d.ts +39 -3
  133. package/dist/mcp/pool.js +74 -11
  134. package/dist/model/catalog.d.ts +2 -10
  135. package/dist/model/classify.d.ts +13 -0
  136. package/dist/model/classify.js +61 -0
  137. package/dist/model/models.d.ts +10 -1
  138. package/dist/model/models.js +14 -6
  139. package/dist/model/pi-model.d.ts +17 -2
  140. package/dist/model/pi-model.js +366 -222
  141. package/dist/openapi.json +25889 -0
  142. package/dist/record/append.d.ts +51 -0
  143. package/dist/record/append.js +20 -0
  144. package/dist/record/envelope.d.ts +19 -0
  145. package/dist/record/envelope.js +49 -0
  146. package/dist/record/index.d.ts +11 -0
  147. package/dist/record/index.js +11 -0
  148. package/dist/sandbox/manager.d.ts +13 -1
  149. package/dist/sandbox/manager.js +8 -11
  150. package/dist/sandbox/resolve.d.ts +33 -0
  151. package/dist/sandbox/resolve.js +81 -0
  152. package/dist/sandbox/session-sandbox.d.ts +11 -0
  153. package/dist/sandbox/session-sandbox.js +51 -0
  154. package/dist/sandbox/share.d.ts +14 -2
  155. package/dist/sandbox/share.js +33 -3
  156. package/dist/sandbox/tenant-config.d.ts +37 -0
  157. package/dist/sandbox/tenant-config.js +51 -0
  158. package/dist/sandbox/tools.d.ts +7 -9
  159. package/dist/sandbox/tools.js +11 -3
  160. package/dist/sandbox/types.d.ts +8 -2
  161. package/dist/store/postgres/connect.d.ts +25 -9
  162. package/dist/store/postgres/connect.js +27 -10
  163. package/dist/store/postgres/db.d.ts +16 -0
  164. package/dist/store/postgres/db.js +28 -0
  165. package/dist/store/postgres/drizzle/0000_foundation.sql +22 -0
  166. package/dist/store/postgres/drizzle/0001_baseline.sql +255 -0
  167. package/dist/store/postgres/drizzle/0002_stream_relay.sql +4 -0
  168. package/dist/store/postgres/drizzle/0003_tool_crossings.sql +9 -0
  169. package/dist/store/postgres/drizzle/meta/_journal.json +34 -0
  170. package/dist/store/postgres/migrate.d.ts +68 -0
  171. package/dist/store/postgres/migrate.js +183 -0
  172. package/dist/store/postgres/record-writer.d.ts +4 -0
  173. package/dist/store/postgres/record-writer.js +34 -0
  174. package/dist/store/postgres/record.d.ts +5 -0
  175. package/dist/store/postgres/record.js +60 -0
  176. package/dist/store/postgres/schema.d.ts +3096 -0
  177. package/dist/store/postgres/schema.js +363 -0
  178. package/dist/store/postgres/store.d.ts +2 -4
  179. package/dist/store/postgres/store.js +590 -434
  180. package/dist/store/postgres/tenant.d.ts +56 -0
  181. package/dist/store/postgres/tenant.js +154 -0
  182. package/dist/store/types.d.ts +183 -132
  183. package/dist/store/types.js +0 -1
  184. package/dist/streams/basin.d.ts +18 -1
  185. package/dist/streams/basin.js +34 -2
  186. package/dist/streams/control.d.ts +18 -0
  187. package/dist/streams/control.js +33 -0
  188. package/dist/streams/memory.d.ts +3 -1
  189. package/dist/streams/memory.js +14 -8
  190. package/dist/streams/relay/core.d.ts +35 -0
  191. package/dist/streams/relay/core.js +366 -0
  192. package/dist/streams/relay/types.d.ts +78 -0
  193. package/dist/streams/relay/types.js +8 -0
  194. package/dist/streams/types.d.ts +71 -36
  195. package/dist/streams/types.js +18 -53
  196. package/dist/tenant/access-policy.d.ts +30 -0
  197. package/dist/tenant/access-policy.js +52 -0
  198. package/dist/tenant/actions.d.ts +10 -149
  199. package/dist/tenant/actions.js +5 -167
  200. package/dist/tenant/advance.d.ts +7 -2
  201. package/dist/tenant/advance.js +125 -14
  202. package/dist/tenant/auth.d.ts +19 -17
  203. package/dist/tenant/auth.js +90 -93
  204. package/dist/tenant/browser.d.ts +15 -0
  205. package/dist/tenant/browser.js +39 -0
  206. package/dist/tenant/cause.d.ts +25 -0
  207. package/dist/tenant/cause.js +45 -0
  208. package/dist/tenant/commands.d.ts +18 -2
  209. package/dist/tenant/commands.js +111 -115
  210. package/dist/tenant/context.d.ts +101 -21
  211. package/dist/tenant/context.js +14 -10
  212. package/dist/tenant/delivery-token.d.ts +41 -0
  213. package/dist/tenant/delivery-token.js +104 -0
  214. package/dist/tenant/delivery.d.ts +62 -0
  215. package/dist/tenant/delivery.js +481 -0
  216. package/dist/tenant/effects.d.ts +20 -1
  217. package/dist/tenant/effects.js +159 -42
  218. package/dist/tenant/endpoints.d.ts +67 -0
  219. package/dist/tenant/endpoints.js +90 -0
  220. package/dist/tenant/ephemeral.d.ts +22 -7
  221. package/dist/tenant/ephemeral.js +83 -125
  222. package/dist/tenant/history.d.ts +64 -0
  223. package/dist/tenant/history.js +120 -0
  224. package/dist/tenant/http.d.ts +8 -9
  225. package/dist/tenant/http.js +13 -27
  226. package/dist/tenant/jwt.d.ts +16 -0
  227. package/dist/tenant/jwt.js +50 -0
  228. package/dist/tenant/model-setting.d.ts +2 -2
  229. package/dist/tenant/module.d.ts +17 -28
  230. package/dist/tenant/module.js +133 -279
  231. package/dist/tenant/outbound.d.ts +44 -0
  232. package/dist/tenant/outbound.js +127 -0
  233. package/dist/tenant/paths.d.ts +11 -16
  234. package/dist/tenant/paths.js +24 -68
  235. package/dist/tenant/pool.d.ts +1 -3
  236. package/dist/tenant/pool.js +1 -19
  237. package/dist/tenant/principals.d.ts +23 -11
  238. package/dist/tenant/principals.js +61 -49
  239. package/dist/tenant/reset.d.ts +0 -4
  240. package/dist/tenant/reset.js +0 -5
  241. package/dist/tenant/runtime.d.ts +28 -20
  242. package/dist/tenant/runtime.js +95 -64
  243. package/dist/tenant/scheduler.d.ts +10 -3
  244. package/dist/tenant/scheduler.js +9 -5
  245. package/dist/tenant/session-streams.d.ts +127 -0
  246. package/dist/tenant/session-streams.js +404 -0
  247. package/dist/tenant/sessions.d.ts +28 -4
  248. package/dist/tenant/sessions.js +118 -24
  249. package/dist/tenant/signing-keys.d.ts +55 -0
  250. package/dist/tenant/signing-keys.js +172 -0
  251. package/dist/tenant/slim.d.ts +8 -0
  252. package/dist/tenant/slim.js +21 -0
  253. package/dist/tenant/status.d.ts +3 -3
  254. package/dist/tenant/status.js +20 -13
  255. package/dist/tenant/store-pg.d.ts +9 -6
  256. package/dist/tenant/store-pg.js +60 -125
  257. package/dist/tenant/streams.d.ts +62 -85
  258. package/dist/tenant/streams.js +143 -226
  259. package/dist/tenant/subject-limits.d.ts +16 -0
  260. package/dist/tenant/subject-limits.js +45 -0
  261. package/dist/tenant/sweep.d.ts +1 -10
  262. package/dist/tenant/sweep.js +29 -69
  263. package/dist/tenant/tokens.d.ts +14 -0
  264. package/dist/tenant/tokens.js +180 -0
  265. package/dist/tenant/transcript.d.ts +48 -5
  266. package/dist/tenant/transcript.js +58 -1
  267. package/dist/tenant/types.d.ts +69 -98
  268. package/dist/tenant/types.js +4 -15
  269. package/dist/tenant/worker.d.ts +3 -1
  270. package/dist/tenant/worker.js +13 -7
  271. package/dist/vault/host-model.d.ts +31 -0
  272. package/dist/vault/host-model.js +57 -0
  273. package/dist/vault/service.d.ts +44 -17
  274. package/dist/vault/service.js +126 -121
  275. package/dist/version.d.ts +1 -1
  276. package/dist/version.js +1 -1
  277. package/package.json +25 -9
  278. package/dist/core/executors.d.ts +0 -44
  279. package/dist/core/executors.js +0 -95
  280. package/dist/store/memory.d.ts +0 -57
  281. package/dist/store/memory.js +0 -645
  282. package/dist/store/postgres/migrations/001_initial.d.ts +0 -27
  283. package/dist/store/postgres/migrations/001_initial.js +0 -196
  284. package/dist/store/postgres/migrations/002_session_owner.d.ts +0 -6
  285. package/dist/store/postgres/migrations/002_session_owner.js +0 -13
  286. package/dist/store/postgres/migrations/index.d.ts +0 -49
  287. package/dist/store/postgres/migrations/index.js +0 -76
  288. package/dist/store/postgres/names.d.ts +0 -11
  289. package/dist/store/postgres/names.js +0 -42
  290. package/dist/store/postgres/tenants.d.ts +0 -90
  291. package/dist/store/postgres/tenants.js +0 -214
  292. package/dist/streams/relay.d.ts +0 -37
  293. package/dist/streams/relay.js +0 -254
  294. package/dist/tenant/envelope.d.ts +0 -7
  295. package/dist/tenant/envelope.js +0 -22
  296. package/dist/tenant/live.d.ts +0 -92
  297. package/dist/tenant/live.js +0 -290
  298. package/dist/tenant/quarantine-error.d.ts +0 -6
  299. package/dist/tenant/quarantine-error.js +0 -9
  300. package/dist/tenant/quarantine.d.ts +0 -27
  301. package/dist/tenant/quarantine.js +0 -72
  302. package/dist/tenant/routes-tenant.d.ts +0 -12
  303. package/dist/tenant/routes-tenant.js +0 -137
  304. package/dist/tenant/routes.d.ts +0 -9
  305. package/dist/tenant/routes.js +0 -151
  306. package/dist/tenant/store-memory.d.ts +0 -16
  307. package/dist/tenant/store-memory.js +0 -111
  308. /package/dist/{store → record}/cursor.d.ts +0 -0
  309. /package/dist/{store → record}/cursor.js +0 -0
@@ -0,0 +1,183 @@
1
+ /**
2
+ * The migrations of a Tenant database (session-store.md §3) and the runner that applies them.
3
+ *
4
+ * drizzle-kit generates the migrations from `schema.ts` into `drizzle/` (SQL files and
5
+ * `meta/_journal.json`); custom SQL migrations carry what it does not model (the schemas,
6
+ * `nylorun.doc()`, the relay's publication). The build copies `drizzle/` next to this module
7
+ * in `dist/`, and the Runtime reads it from there.
8
+ *
9
+ * The runner applies them at Host startup, before any service opens the store
10
+ * (`tenant.ts`), in one transaction:
11
+ *
12
+ * 1. It lifts `statement_timeout` for the transaction and takes the database's migration
13
+ * lock (a transaction-level advisory lock), so two processes never migrate at once.
14
+ * 2. It refuses an old layout (`database-layout-old`): `tenant_<id>` schemas, a record keyed
15
+ * by Tenant, or the hand-written migrations' `schema_version` tables of the pre-release
16
+ * builds of one Tenant per database. It never changes such a database.
17
+ * 3. It reads the journal, `nylorun.__drizzle_migrations` (Drizzle's format: `id`, `hash`,
18
+ * `created_at`, so `drizzle-kit migrate` and Drizzle Studio read it too). A journal holding
19
+ * a migration this Runtime does not ship was written by a newer Runtime: `schema-too-new`,
20
+ * and nothing is changed. Drizzle's own migrator does not refuse; this is why the Runtime
21
+ * applies the files itself.
22
+ * 4. It applies every missing migration in order and records each in the journal.
23
+ *
24
+ * The database's schema version is the number of migrations its journal records; this
25
+ * Runtime expects `shippedMigrations().length`.
26
+ */
27
+ import { readFileSync } from "node:fs";
28
+ import { fileURLToPath } from "node:url";
29
+ import { sql } from "drizzle-orm";
30
+ import { readMigrationFiles } from "drizzle-orm/migrator";
31
+ import { openError } from "../../tenant/cause.js";
32
+ import { database, driverError } from "./db.js";
33
+ import { STREAMS_SCHEMA, TENANT_SCHEMA } from "./schema.js";
34
+ /** Where the migrations are, next to this module (`src/` in development, `dist/` built). */
35
+ export const MIGRATIONS_FOLDER = fileURLToPath(new URL("./drizzle", import.meta.url));
36
+ /** The journal of applied migrations: `nylorun.__drizzle_migrations`. */
37
+ export const MIGRATIONS_SCHEMA = TENANT_SCHEMA;
38
+ export const MIGRATIONS_TABLE = "__drizzle_migrations";
39
+ const JOURNAL = sql.raw(`"${MIGRATIONS_SCHEMA}"."${MIGRATIONS_TABLE}"`);
40
+ /**
41
+ * What schemas were called when one database held several Tenants (`"tenant_<id>"`). A
42
+ * database that still has one was written by an older Runtime and is refused.
43
+ */
44
+ export const OLD_TENANT_SCHEMA_PREFIX = "tenant_";
45
+ let shipped;
46
+ /** The migrations this Runtime ships, in order. */
47
+ export function shippedMigrations() {
48
+ if (!shipped) {
49
+ const journal = JSON.parse(readFileSync(`${MIGRATIONS_FOLDER}/meta/_journal.json`, "utf8"));
50
+ shipped = readMigrationFiles({ migrationsFolder: MIGRATIONS_FOLDER }).map((file, index) => ({
51
+ tag: journal.entries[index].tag,
52
+ hash: file.hash,
53
+ when: file.folderMillis,
54
+ statements: file.sql,
55
+ }));
56
+ }
57
+ return shipped;
58
+ }
59
+ /** The schema version this Runtime writes and expects: the number of shipped migrations. */
60
+ export function expectedSchemaVersion() {
61
+ return shippedMigrations().length;
62
+ }
63
+ /**
64
+ * Lifts `statement_timeout` for the transaction and takes the database's migration lock
65
+ * until it ends. Waiting for the lock, and the migrations done under it, may outlast the
66
+ * pool's statement timeout; a migration that timed out would fail the Host's readiness.
67
+ */
68
+ export async function lockMigrations(tx) {
69
+ await tx.execute(sql `SET LOCAL statement_timeout = 0`);
70
+ await tx.execute(sql `SELECT pg_advisory_xact_lock(hashtextextended('nylorun.migrations', 0))`);
71
+ }
72
+ /**
73
+ * Throws `database-layout-old` when the database was written by an older Runtime: one that
74
+ * kept several Tenants in one database (`tenant_<id>` schemas, a record keyed by Tenant), or a
75
+ * pre-release build of one Tenant per database whose hand-written migrations recorded
76
+ * themselves in `schema_version` tables.
77
+ */
78
+ export async function assertCurrentLayout(q) {
79
+ const [row] = await q.execute(sql `
80
+ SELECT
81
+ EXISTS (
82
+ SELECT 1 FROM pg_namespace WHERE starts_with(nspname, ${OLD_TENANT_SCHEMA_PREFIX})
83
+ ) AS tenant_schemas,
84
+ EXISTS (
85
+ SELECT 1 FROM information_schema.columns
86
+ WHERE table_schema = ${STREAMS_SCHEMA} AND table_name = 'session_events'
87
+ AND column_name = 'tenant_id'
88
+ ) AS keyed_record,
89
+ to_regclass(${`${TENANT_SCHEMA}.schema_version`}) IS NOT NULL
90
+ OR to_regclass(${`${STREAMS_SCHEMA}.schema_version`}) IS NOT NULL AS versioned`);
91
+ if (row?.tenant_schemas || row?.keyed_record)
92
+ throw openError("database-layout-old", "The database was written by an older Runtime that kept several Tenants in one database " +
93
+ "(tenant_<id> schemas). This release starts fresh with one Tenant per database and " +
94
+ "never changes the old one: point the Runtime at a new database (with the local stack, " +
95
+ "start a new stack).");
96
+ if (row?.versioned)
97
+ throw openError("database-layout-old", "The database was created by a pre-release build of one Tenant per database, whose " +
98
+ "migrations this release replaces. It is never changed: point the Runtime at a new " +
99
+ "database (with the local stack, delete the stack and start it again).");
100
+ }
101
+ /**
102
+ * The database's schema version: the number of migrations its journal records, or undefined
103
+ * when it has no journal (a database never migrated).
104
+ */
105
+ export async function readSchemaVersion(q) {
106
+ const [exists] = await q.execute(sql `
107
+ SELECT to_regclass(${`${MIGRATIONS_SCHEMA}.${MIGRATIONS_TABLE}`}) IS NOT NULL AS journal`);
108
+ if (!exists?.journal)
109
+ return undefined;
110
+ const [row] = await q.execute(sql `SELECT count(*)::int AS n FROM ${JOURNAL}`);
111
+ return row?.n ?? 0;
112
+ }
113
+ /**
114
+ * Applies every migration of `migrations` the journal does not record, in `tx`. Take
115
+ * `lockMigrations` first (`migrateDatabase` does). Throws `schema-too-new` when the journal
116
+ * records a migration `migrations` does not have, and `migration-failed` when Postgres
117
+ * rejects a migration.
118
+ */
119
+ export async function applyMigrations(tx, migrations = shippedMigrations()) {
120
+ await tx.execute(sql `CREATE SCHEMA IF NOT EXISTS ${sql.raw(`"${MIGRATIONS_SCHEMA}"`)}`);
121
+ // Drizzle's journal table, as its migrator creates it.
122
+ await tx.execute(sql `
123
+ CREATE TABLE IF NOT EXISTS ${JOURNAL} (
124
+ id SERIAL PRIMARY KEY,
125
+ hash text NOT NULL,
126
+ created_at bigint
127
+ )`);
128
+ const applied = await tx.execute(sql `SELECT hash FROM ${JOURNAL} ORDER BY id`);
129
+ const known = new Set(migrations.map((migration) => migration.hash));
130
+ const unknown = applied.filter((row) => !known.has(row.hash)).length;
131
+ if (unknown > 0)
132
+ throw openError("schema-too-new", `The database has ${unknown} migration${unknown === 1 ? "" : "s"} this Runtime does not ` +
133
+ `know (it is at schema version ${applied.length}; this Runtime's is ${migrations.length}): ` +
134
+ "it was migrated by a newer Runtime");
135
+ applied.forEach((row, index) => {
136
+ if (row.hash !== migrations[index].hash)
137
+ throw openError("migration-failed", `The database's migration ${index + 1} is not this Runtime's ${migrations[index].tag}`);
138
+ });
139
+ for (const migration of migrations.slice(applied.length)) {
140
+ try {
141
+ for (const statement of migration.statements)
142
+ if (statement.trim())
143
+ await tx.execute(sql.raw(statement));
144
+ }
145
+ catch (thrown) {
146
+ const error = driverError(thrown);
147
+ if (!isStatementError(error))
148
+ throw error;
149
+ throw openError("migration-failed", `Migration ${migration.tag} failed: ${error.message}`);
150
+ }
151
+ await tx.execute(sql `
152
+ INSERT INTO ${JOURNAL} (hash, created_at) VALUES (${migration.hash}, ${migration.when})`);
153
+ }
154
+ return { from: applied.length, to: migrations.length };
155
+ }
156
+ /**
157
+ * Refuses an old layout and applies the missing migrations, in one transaction under the
158
+ * migration lock. For tests and tools; the Host migrates through `openTenantDatabase`.
159
+ */
160
+ export async function migrateDatabase(pool, migrations = shippedMigrations()) {
161
+ const db = database(pool);
162
+ try {
163
+ return await db.transaction(async (tx) => {
164
+ await lockMigrations(tx);
165
+ await assertCurrentLayout(tx);
166
+ return applyMigrations(tx, migrations);
167
+ });
168
+ }
169
+ catch (error) {
170
+ throw driverError(error);
171
+ }
172
+ }
173
+ /**
174
+ * Whether Postgres rejected a statement (a SQLSTATE), rather than the connection or the
175
+ * server failing (classes 08, 53, 57, and the driver's own connection errors). Only the
176
+ * first says something about the Tenant's database.
177
+ */
178
+ export function isStatementError(error) {
179
+ const code = error?.code;
180
+ return (typeof code === "string" &&
181
+ /^[0-9A-Z]{5}$/.test(code) &&
182
+ !/^(08|53|57)/.test(code));
183
+ }
@@ -0,0 +1,4 @@
1
+ import type { RecordWriter } from "../../record/index.js";
2
+ import type { Transaction } from "./db.js";
3
+ /** The record writer of transaction `db`. */
4
+ export declare function postgresRecordWriter(db: Transaction): RecordWriter;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The record's Postgres statements (Durable Streams §6), which the record module runs through
3
+ * `RecordWriter` (`record/append.ts`, blueprint D27): the only inserts into `session_events`
4
+ * and `session_log_heads`. They sit here, behind the driver boundary (session-store.md §3), and
5
+ * run in the store's transaction under the caller's session row lock.
6
+ */
7
+ import { sql } from "drizzle-orm";
8
+ import { sessionEvents, sessionLogHeads, tenant } from "./schema.js";
9
+ /** The record writer of transaction `db`. */
10
+ export function postgresRecordWriter(db) {
11
+ return {
12
+ async advance(sessionId) {
13
+ const [row] = await db
14
+ .insert(sessionLogHeads)
15
+ .values({
16
+ sessionId,
17
+ generation: sql `coalesce((SELECT ${tenant.basinGeneration} FROM ${tenant}), 0)`,
18
+ head: 1,
19
+ })
20
+ .onConflictDoUpdate({
21
+ target: sessionLogHeads.sessionId,
22
+ set: { head: sql `${sessionLogHeads.head} + 1` },
23
+ })
24
+ .returning({
25
+ seq: sql `${sessionLogHeads.head} - 1`.mapWith(Number),
26
+ generation: sessionLogHeads.generation,
27
+ });
28
+ return row;
29
+ },
30
+ async insert(row) {
31
+ await db.insert(sessionEvents).values(row);
32
+ },
33
+ };
34
+ }
@@ -0,0 +1,5 @@
1
+ import type { Sql } from "postgres";
2
+ import type { RecordReader } from "../../streams/relay/types.js";
3
+ export declare function createPostgresRecordReader(sql: Sql, options: {
4
+ tenantId: string;
5
+ }): RecordReader;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Reads the record back for the stream relay (Durable Streams §7.2–7.3): a session's rows to
3
+ * refill a gap in S2, every log head to reconcile after a fresh slot, and the Tenant's current
4
+ * basin generation to tell obsolete rows apart.
5
+ *
6
+ * The database holds one Tenant, so the record has no Tenant column: the reader is made for
7
+ * that Tenant (`tenantId`), fills `RecordRow.tenantId` and `LogHead.tenantId` with it, and
8
+ * reads nothing for any other id.
9
+ */
10
+ import { and, asc, eq, gt, gte, lt } from "drizzle-orm";
11
+ import { database, driverError } from "./db.js";
12
+ import { sessionEvents, sessionLogHeads, tenant } from "./schema.js";
13
+ export function createPostgresRecordReader(sql, options) {
14
+ const own = options.tenantId;
15
+ const db = database(sql);
16
+ const read = (query) => Promise.resolve(query).catch((error) => {
17
+ throw driverError(error);
18
+ });
19
+ return {
20
+ async readRange(tenantId, sessionId, from, to) {
21
+ if (tenantId !== own)
22
+ return [];
23
+ const rows = await read(db
24
+ .select({
25
+ seq: sessionEvents.seq,
26
+ generation: sessionEvents.generation,
27
+ body: sessionEvents.body,
28
+ })
29
+ .from(sessionEvents)
30
+ .where(and(eq(sessionEvents.sessionId, sessionId), gte(sessionEvents.seq, from), lt(sessionEvents.seq, to)))
31
+ .orderBy(asc(sessionEvents.seq)));
32
+ return rows.map((row) => ({ tenantId, sessionId, ...row }));
33
+ },
34
+ async heads(after, limit) {
35
+ // Heads are ordered by (Tenant, session): every one of this Tenant's comes after a
36
+ // Tenant id that sorts before it, and none after one that sorts after it.
37
+ if (after && after.tenantId > own)
38
+ return [];
39
+ const from = after?.tenantId === own ? after.sessionId : undefined;
40
+ const rows = await read(db
41
+ .select({
42
+ sessionId: sessionLogHeads.sessionId,
43
+ generation: sessionLogHeads.generation,
44
+ head: sessionLogHeads.head,
45
+ })
46
+ .from(sessionLogHeads)
47
+ .where(and(gt(sessionLogHeads.head, 0), from === undefined ? undefined : gt(sessionLogHeads.sessionId, from)))
48
+ .orderBy(asc(sessionLogHeads.sessionId))
49
+ .limit(limit));
50
+ return rows.map((row) => ({ tenantId: own, ...row }));
51
+ },
52
+ async generation(tenantId) {
53
+ if (tenantId !== own)
54
+ return undefined;
55
+ const [row] = await read(db.select({ generation: tenant.basinGeneration }).from(tenant));
56
+ // A database whose Tenant row is not written yet is at generation 0.
57
+ return row?.generation ?? 0;
58
+ },
59
+ };
60
+ }