@stigmer/server 3.24.0 → 3.26.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 (341) hide show
  1. package/dist/authorization/authorizer.d.ts +10 -4
  2. package/dist/authorization/authorizer.d.ts.map +1 -1
  3. package/dist/authorization/authorizer.js +11 -4
  4. package/dist/authorization/authorizer.js.map +1 -1
  5. package/dist/authorization/model/index.d.ts +4 -4
  6. package/dist/authorization/model/index.d.ts.map +1 -1
  7. package/dist/authorization/model/index.js +2 -0
  8. package/dist/authorization/model/index.js.map +1 -1
  9. package/dist/authorization/model/oauth_app.d.ts.map +1 -1
  10. package/dist/authorization/model/oauth_app.js +3 -2
  11. package/dist/authorization/model/oauth_app.js.map +1 -1
  12. package/dist/authorization/model/platform_client.d.ts +2 -0
  13. package/dist/authorization/model/platform_client.d.ts.map +1 -0
  14. package/dist/authorization/model/platform_client.js +31 -0
  15. package/dist/authorization/model/platform_client.js.map +1 -0
  16. package/dist/boot/compose.d.ts.map +1 -1
  17. package/dist/boot/compose.js +99 -13
  18. package/dist/boot/compose.js.map +1 -1
  19. package/dist/boot/list-indexes.d.ts +3 -0
  20. package/dist/boot/list-indexes.d.ts.map +1 -0
  21. package/dist/boot/list-indexes.js +24 -0
  22. package/dist/boot/list-indexes.js.map +1 -0
  23. package/dist/boot/route-shadowing.d.ts +25 -0
  24. package/dist/boot/route-shadowing.d.ts.map +1 -0
  25. package/dist/boot/route-shadowing.js +17 -0
  26. package/dist/boot/route-shadowing.js.map +1 -0
  27. package/dist/domain/agentexecution/controller.d.ts.map +1 -1
  28. package/dist/domain/agentexecution/controller.js +6 -8
  29. package/dist/domain/agentexecution/controller.js.map +1 -1
  30. package/dist/domain/agentexecution/list-index.d.ts +2 -0
  31. package/dist/domain/agentexecution/list-index.d.ts.map +1 -0
  32. package/dist/domain/agentexecution/list-index.js +23 -0
  33. package/dist/domain/agentexecution/list-index.js.map +1 -0
  34. package/dist/domain/agentexecution/steps.d.ts +17 -33
  35. package/dist/domain/agentexecution/steps.d.ts.map +1 -1
  36. package/dist/domain/agentexecution/steps.js +94 -95
  37. package/dist/domain/agentexecution/steps.js.map +1 -1
  38. package/dist/domain/agentexecution/usage.d.ts.map +1 -1
  39. package/dist/domain/agentexecution/usage.js +37 -14
  40. package/dist/domain/agentexecution/usage.js.map +1 -1
  41. package/dist/domain/artifact/controller.d.ts.map +1 -1
  42. package/dist/domain/artifact/controller.js +27 -15
  43. package/dist/domain/artifact/controller.js.map +1 -1
  44. package/dist/domain/artifact/list-index.d.ts +2 -0
  45. package/dist/domain/artifact/list-index.d.ts.map +1 -0
  46. package/dist/domain/artifact/list-index.js +21 -0
  47. package/dist/domain/artifact/list-index.js.map +1 -0
  48. package/dist/domain/iampolicy/membership.d.ts +6 -1
  49. package/dist/domain/iampolicy/membership.d.ts.map +1 -1
  50. package/dist/domain/iampolicy/membership.js +8 -2
  51. package/dist/domain/iampolicy/membership.js.map +1 -1
  52. package/dist/domain/identityaccount/constants.d.ts +13 -0
  53. package/dist/domain/identityaccount/constants.d.ts.map +1 -1
  54. package/dist/domain/identityaccount/constants.js +17 -0
  55. package/dist/domain/identityaccount/constants.js.map +1 -1
  56. package/dist/domain/identityaccount/controller.d.ts +3 -1
  57. package/dist/domain/identityaccount/controller.d.ts.map +1 -1
  58. package/dist/domain/identityaccount/controller.js +15 -6
  59. package/dist/domain/identityaccount/controller.js.map +1 -1
  60. package/dist/domain/identityaccount/operator.d.ts.map +1 -1
  61. package/dist/domain/identityaccount/operator.js +1 -0
  62. package/dist/domain/identityaccount/operator.js.map +1 -1
  63. package/dist/domain/identityaccount/provisioning.d.ts +18 -0
  64. package/dist/domain/identityaccount/provisioning.d.ts.map +1 -1
  65. package/dist/domain/identityaccount/provisioning.js +1 -0
  66. package/dist/domain/identityaccount/provisioning.js.map +1 -1
  67. package/dist/domain/identityaccount/resource-store.js +9 -5
  68. package/dist/domain/identityaccount/resource-store.js.map +1 -1
  69. package/dist/domain/identityaccount/steps.d.ts +11 -2
  70. package/dist/domain/identityaccount/steps.d.ts.map +1 -1
  71. package/dist/domain/identityaccount/steps.js +42 -4
  72. package/dist/domain/identityaccount/steps.js.map +1 -1
  73. package/dist/domain/identityaccount/store-contract.d.ts.map +1 -1
  74. package/dist/domain/identityaccount/store-contract.js +33 -0
  75. package/dist/domain/identityaccount/store-contract.js.map +1 -1
  76. package/dist/domain/identityaccount/store.d.ts +7 -5
  77. package/dist/domain/identityaccount/store.d.ts.map +1 -1
  78. package/dist/domain/identityaccount/store.js.map +1 -1
  79. package/dist/domain/platform/controller.d.ts +10 -0
  80. package/dist/domain/platform/controller.d.ts.map +1 -1
  81. package/dist/domain/platform/controller.js +2 -1
  82. package/dist/domain/platform/controller.js.map +1 -1
  83. package/dist/domain/platformclient/constants.d.ts +60 -0
  84. package/dist/domain/platformclient/constants.d.ts.map +1 -0
  85. package/dist/domain/platformclient/constants.js +78 -0
  86. package/dist/domain/platformclient/constants.js.map +1 -0
  87. package/dist/domain/platformclient/controller.d.ts +59 -0
  88. package/dist/domain/platformclient/controller.d.ts.map +1 -0
  89. package/dist/domain/platformclient/controller.js +191 -0
  90. package/dist/domain/platformclient/controller.js.map +1 -0
  91. package/dist/domain/platformclient/credentials.d.ts +13 -0
  92. package/dist/domain/platformclient/credentials.d.ts.map +1 -0
  93. package/dist/domain/platformclient/credentials.js +56 -0
  94. package/dist/domain/platformclient/credentials.js.map +1 -0
  95. package/dist/domain/platformclient/mint.d.ts +23 -0
  96. package/dist/domain/platformclient/mint.d.ts.map +1 -0
  97. package/dist/domain/platformclient/mint.js +254 -0
  98. package/dist/domain/platformclient/mint.js.map +1 -0
  99. package/dist/domain/platformclient/origin-guard.d.ts +9 -0
  100. package/dist/domain/platformclient/origin-guard.d.ts.map +1 -0
  101. package/dist/domain/platformclient/origin-guard.js +81 -0
  102. package/dist/domain/platformclient/origin-guard.js.map +1 -0
  103. package/dist/domain/platformclient/resource-store.d.ts +4 -0
  104. package/dist/domain/platformclient/resource-store.d.ts.map +1 -0
  105. package/dist/domain/platformclient/resource-store.js +117 -0
  106. package/dist/domain/platformclient/resource-store.js.map +1 -0
  107. package/dist/domain/platformclient/steps.d.ts +51 -0
  108. package/dist/domain/platformclient/steps.d.ts.map +1 -0
  109. package/dist/domain/platformclient/steps.js +339 -0
  110. package/dist/domain/platformclient/steps.js.map +1 -0
  111. package/dist/domain/platformclient/store-contract.d.ts +8 -0
  112. package/dist/domain/platformclient/store-contract.d.ts.map +1 -0
  113. package/dist/domain/platformclient/store-contract.js +208 -0
  114. package/dist/domain/platformclient/store-contract.js.map +1 -0
  115. package/dist/domain/platformclient/store.d.ts +58 -0
  116. package/dist/domain/platformclient/store.d.ts.map +1 -0
  117. package/dist/domain/platformclient/store.js +13 -0
  118. package/dist/domain/platformclient/store.js.map +1 -0
  119. package/dist/domain/platformclient/system-managed.d.ts +18 -0
  120. package/dist/domain/platformclient/system-managed.d.ts.map +1 -0
  121. package/dist/domain/platformclient/system-managed.js +80 -0
  122. package/dist/domain/platformclient/system-managed.js.map +1 -0
  123. package/dist/domain/platformclient/token-controller.d.ts +11 -0
  124. package/dist/domain/platformclient/token-controller.d.ts.map +1 -0
  125. package/dist/domain/platformclient/token-controller.js +32 -0
  126. package/dist/domain/platformclient/token-controller.js.map +1 -0
  127. package/dist/domain/platformclient/verifier.d.ts +10 -0
  128. package/dist/domain/platformclient/verifier.d.ts.map +1 -0
  129. package/dist/domain/platformclient/verifier.js +76 -0
  130. package/dist/domain/platformclient/verifier.js.map +1 -0
  131. package/dist/domain/session/list-index.d.ts +4 -0
  132. package/dist/domain/session/list-index.d.ts.map +1 -0
  133. package/dist/domain/session/list-index.js +26 -0
  134. package/dist/domain/session/list-index.js.map +1 -0
  135. package/dist/domain/session/steps.d.ts +7 -8
  136. package/dist/domain/session/steps.d.ts.map +1 -1
  137. package/dist/domain/session/steps.js +76 -78
  138. package/dist/domain/session/steps.js.map +1 -1
  139. package/dist/domain/workflowexecution/controller.d.ts.map +1 -1
  140. package/dist/domain/workflowexecution/controller.js +53 -55
  141. package/dist/domain/workflowexecution/controller.js.map +1 -1
  142. package/dist/domain/workflowexecution/execution-filter.d.ts +4 -0
  143. package/dist/domain/workflowexecution/execution-filter.d.ts.map +1 -1
  144. package/dist/domain/workflowexecution/execution-filter.js +11 -5
  145. package/dist/domain/workflowexecution/execution-filter.js.map +1 -1
  146. package/dist/domain/workflowexecution/get-execution-summary.d.ts.map +1 -1
  147. package/dist/domain/workflowexecution/get-execution-summary.js +23 -17
  148. package/dist/domain/workflowexecution/get-execution-summary.js.map +1 -1
  149. package/dist/domain/workflowexecution/list-index.d.ts +2 -0
  150. package/dist/domain/workflowexecution/list-index.d.ts.map +1 -0
  151. package/dist/domain/workflowexecution/list-index.js +21 -0
  152. package/dist/domain/workflowexecution/list-index.js.map +1 -0
  153. package/dist/domain/workflowexecution/list-pending-approvals.d.ts.map +1 -1
  154. package/dist/domain/workflowexecution/list-pending-approvals.js +69 -32
  155. package/dist/domain/workflowexecution/list-pending-approvals.js.map +1 -1
  156. package/dist/domain/workflowexecution/queries.d.ts +30 -4
  157. package/dist/domain/workflowexecution/queries.d.ts.map +1 -1
  158. package/dist/domain/workflowexecution/queries.js +76 -22
  159. package/dist/domain/workflowexecution/queries.js.map +1 -1
  160. package/dist/encryption/key-manager.d.ts +20 -3
  161. package/dist/encryption/key-manager.d.ts.map +1 -1
  162. package/dist/encryption/key-manager.js +64 -42
  163. package/dist/encryption/key-manager.js.map +1 -1
  164. package/dist/extensions/caller-guards.d.ts +8 -3
  165. package/dist/extensions/caller-guards.d.ts.map +1 -1
  166. package/dist/extensions/drivers.d.ts +37 -0
  167. package/dist/extensions/drivers.d.ts.map +1 -1
  168. package/dist/extensions/guest-token-minting.d.ts +26 -0
  169. package/dist/extensions/guest-token-minting.d.ts.map +1 -0
  170. package/dist/extensions/guest-token-minting.js +2 -0
  171. package/dist/extensions/guest-token-minting.js.map +1 -0
  172. package/dist/extensions/identity.d.ts +6 -4
  173. package/dist/extensions/identity.d.ts.map +1 -1
  174. package/dist/extensions/identity.js.map +1 -1
  175. package/dist/extensions/list-read-scope.d.ts +9 -12
  176. package/dist/extensions/list-read-scope.d.ts.map +1 -1
  177. package/dist/extensions/list-read-scope.js.map +1 -1
  178. package/dist/extensions/registry.d.ts +30 -1
  179. package/dist/extensions/registry.d.ts.map +1 -1
  180. package/dist/extensions/registry.js +34 -1
  181. package/dist/extensions/registry.js.map +1 -1
  182. package/dist/index.d.ts +15 -2
  183. package/dist/index.d.ts.map +1 -1
  184. package/dist/index.js +6 -0
  185. package/dist/index.js.map +1 -1
  186. package/dist/pipeline/interceptors/auth.d.ts +14 -0
  187. package/dist/pipeline/interceptors/auth.d.ts.map +1 -1
  188. package/dist/pipeline/interceptors/auth.js +24 -0
  189. package/dist/pipeline/interceptors/auth.js.map +1 -1
  190. package/dist/pipeline/steps/build-update-state.d.ts +11 -1
  191. package/dist/pipeline/steps/build-update-state.d.ts.map +1 -1
  192. package/dist/pipeline/steps/build-update-state.js +5 -2
  193. package/dist/pipeline/steps/build-update-state.js.map +1 -1
  194. package/dist/pipeline/steps/list-page.d.ts +63 -0
  195. package/dist/pipeline/steps/list-page.d.ts.map +1 -0
  196. package/dist/pipeline/steps/list-page.js +187 -0
  197. package/dist/pipeline/steps/list-page.js.map +1 -0
  198. package/dist/platformtoken/envelope.d.ts +100 -0
  199. package/dist/platformtoken/envelope.d.ts.map +1 -0
  200. package/dist/platformtoken/envelope.js +184 -0
  201. package/dist/platformtoken/envelope.js.map +1 -0
  202. package/dist/platformtoken/key-ring.d.ts +85 -0
  203. package/dist/platformtoken/key-ring.d.ts.map +1 -0
  204. package/dist/platformtoken/key-ring.js +164 -0
  205. package/dist/platformtoken/key-ring.js.map +1 -0
  206. package/dist/query/activity/handler.d.ts +6 -7
  207. package/dist/query/activity/handler.d.ts.map +1 -1
  208. package/dist/query/activity/handler.js +42 -59
  209. package/dist/query/activity/handler.js.map +1 -1
  210. package/dist/store/interface.d.ts +29 -0
  211. package/dist/store/interface.d.ts.map +1 -1
  212. package/dist/store/interface.js.map +1 -1
  213. package/dist/store/list-index.d.ts +123 -0
  214. package/dist/store/list-index.d.ts.map +1 -0
  215. package/dist/store/list-index.js +277 -0
  216. package/dist/store/list-index.js.map +1 -0
  217. package/dist/store/logger.d.ts +3 -0
  218. package/dist/store/logger.d.ts.map +1 -1
  219. package/dist/store/logger.js +5 -1
  220. package/dist/store/logger.js.map +1 -1
  221. package/dist/store/postgres/migrations.d.ts +10 -6
  222. package/dist/store/postgres/migrations.d.ts.map +1 -1
  223. package/dist/store/postgres/migrations.js +64 -6
  224. package/dist/store/postgres/migrations.js.map +1 -1
  225. package/dist/store/postgres/store.d.ts +26 -5
  226. package/dist/store/postgres/store.d.ts.map +1 -1
  227. package/dist/store/postgres/store.js +377 -22
  228. package/dist/store/postgres/store.js.map +1 -1
  229. package/dist/store/sqlite/migrations.d.ts +11 -5
  230. package/dist/store/sqlite/migrations.d.ts.map +1 -1
  231. package/dist/store/sqlite/migrations.js +50 -4
  232. package/dist/store/sqlite/migrations.js.map +1 -1
  233. package/dist/store/sqlite/store.d.ts +18 -3
  234. package/dist/store/sqlite/store.d.ts.map +1 -1
  235. package/dist/store/sqlite/store.js +321 -15
  236. package/dist/store/sqlite/store.js.map +1 -1
  237. package/package.json +6 -6
  238. package/src/authorization/README.md +1 -1
  239. package/src/authorization/__tests__/authorizer.test.ts +6 -2
  240. package/src/authorization/__tests__/drivers.ts +6 -1
  241. package/src/authorization/__tests__/wire-permissions.test.ts +1 -0
  242. package/src/authorization/authorizer.ts +11 -4
  243. package/src/authorization/model/__tests__/registry.test.ts +8 -1
  244. package/src/authorization/model/index.ts +6 -4
  245. package/src/authorization/model/oauth_app.ts +3 -2
  246. package/src/authorization/model/platform_client.ts +43 -0
  247. package/src/boot/__tests__/auth-enabled-composition.test.ts +2 -1
  248. package/src/boot/__tests__/list-indexes.test.ts +65 -0
  249. package/src/boot/__tests__/route-shadowing.test.ts +67 -0
  250. package/src/boot/compose.ts +113 -18
  251. package/src/boot/list-indexes.ts +25 -0
  252. package/src/boot/route-shadowing.ts +47 -0
  253. package/src/domain/agentexecution/__tests__/agentexecution.test.ts +85 -3
  254. package/src/domain/agentexecution/controller.ts +8 -10
  255. package/src/domain/agentexecution/list-index.ts +24 -0
  256. package/src/domain/agentexecution/steps.ts +118 -115
  257. package/src/domain/agentexecution/usage.ts +38 -17
  258. package/src/domain/artifact/controller.ts +28 -18
  259. package/src/domain/artifact/list-index.ts +22 -0
  260. package/src/domain/iampolicy/__tests__/membership.test.ts +7 -0
  261. package/src/domain/iampolicy/membership.ts +8 -2
  262. package/src/domain/identityaccount/__tests__/platform-client-accounts.test.ts +124 -0
  263. package/src/domain/identityaccount/__tests__/resource-store.test.ts +1 -0
  264. package/src/domain/identityaccount/constants.ts +20 -0
  265. package/src/domain/identityaccount/controller.ts +16 -4
  266. package/src/domain/identityaccount/operator.ts +1 -0
  267. package/src/domain/identityaccount/provisioning.ts +17 -0
  268. package/src/domain/identityaccount/resource-store.ts +9 -5
  269. package/src/domain/identityaccount/steps.ts +58 -3
  270. package/src/domain/identityaccount/store-contract.ts +51 -0
  271. package/src/domain/identityaccount/store.ts +7 -5
  272. package/src/domain/platform/__tests__/platform.test.ts +7 -1
  273. package/src/domain/platform/controller.ts +12 -1
  274. package/src/domain/platformclient/__tests__/controller.test.ts +397 -0
  275. package/src/domain/platformclient/__tests__/credentials.test.ts +46 -0
  276. package/src/domain/platformclient/__tests__/mint.test.ts +415 -0
  277. package/src/domain/platformclient/__tests__/resource-store.test.ts +140 -0
  278. package/src/domain/platformclient/__tests__/system-managed.test.ts +101 -0
  279. package/src/domain/platformclient/__tests__/verifier-and-guard.test.ts +207 -0
  280. package/src/domain/platformclient/constants.ts +120 -0
  281. package/src/domain/platformclient/controller.ts +449 -0
  282. package/src/domain/platformclient/credentials.ts +68 -0
  283. package/src/domain/platformclient/mint.ts +412 -0
  284. package/src/domain/platformclient/origin-guard.ts +117 -0
  285. package/src/domain/platformclient/resource-store.ts +150 -0
  286. package/src/domain/platformclient/steps.ts +472 -0
  287. package/src/domain/platformclient/store-contract.ts +318 -0
  288. package/src/domain/platformclient/store.ts +63 -0
  289. package/src/domain/platformclient/system-managed.ts +115 -0
  290. package/src/domain/platformclient/token-controller.ts +49 -0
  291. package/src/domain/platformclient/verifier.ts +106 -0
  292. package/src/domain/session/list-index.ts +28 -0
  293. package/src/domain/session/steps.ts +108 -123
  294. package/src/domain/workflowexecution/__tests__/list-read-scope-summaries.test.ts +155 -99
  295. package/src/domain/workflowexecution/__tests__/workflowexecution.test.ts +193 -31
  296. package/src/domain/workflowexecution/controller.ts +54 -82
  297. package/src/domain/workflowexecution/execution-filter.ts +20 -7
  298. package/src/domain/workflowexecution/get-execution-summary.ts +31 -24
  299. package/src/domain/workflowexecution/list-index.ts +22 -0
  300. package/src/domain/workflowexecution/list-pending-approvals.ts +82 -25
  301. package/src/domain/workflowexecution/queries.ts +134 -23
  302. package/src/encryption/key-manager.ts +87 -44
  303. package/src/extensions/__tests__/extension-composition.test.ts +3 -2
  304. package/src/extensions/__tests__/platform-client-composed.test.ts +288 -0
  305. package/src/extensions/__tests__/registry.test.ts +4 -1
  306. package/src/extensions/__tests__/tier-truthfulness.test.ts +8 -0
  307. package/src/extensions/caller-guards.ts +8 -3
  308. package/src/extensions/drivers.ts +37 -0
  309. package/src/extensions/guest-token-minting.ts +32 -0
  310. package/src/extensions/identity.ts +6 -4
  311. package/src/extensions/list-read-scope.ts +9 -12
  312. package/src/extensions/registry.ts +77 -3
  313. package/src/index.ts +65 -1
  314. package/src/pipeline/interceptors/auth.ts +27 -0
  315. package/src/pipeline/steps/__tests__/list-page.test.ts +186 -0
  316. package/src/pipeline/steps/__tests__/references.test.ts +2 -0
  317. package/src/pipeline/steps/build-update-state.ts +5 -2
  318. package/src/pipeline/steps/list-page.ts +284 -0
  319. package/src/platformtoken/__tests__/envelope.test.ts +277 -0
  320. package/src/platformtoken/__tests__/key-ring.test.ts +205 -0
  321. package/src/platformtoken/envelope.ts +276 -0
  322. package/src/platformtoken/key-ring.ts +266 -0
  323. package/src/query/activity/handler.ts +57 -78
  324. package/src/runnerauth/__tests__/runner-subject-composed.test.ts +9 -3
  325. package/src/store/README.md +51 -32
  326. package/src/store/__tests__/list-index.measure.test.ts +293 -0
  327. package/src/store/__tests__/list-index.test.ts +328 -0
  328. package/src/store/__tests__/store-contract.ts +346 -3
  329. package/src/store/interface.ts +39 -0
  330. package/src/store/list-index.ts +465 -0
  331. package/src/store/logger.ts +8 -1
  332. package/src/store/postgres/__tests__/list-index-repair.test.ts +133 -0
  333. package/src/store/postgres/__tests__/migrations.test.ts +27 -19
  334. package/src/store/postgres/__tests__/store-contract.test.ts +50 -3
  335. package/src/store/postgres/migrations.ts +69 -7
  336. package/src/store/postgres/store.ts +485 -29
  337. package/src/store/sqlite/__tests__/migrations.test.ts +51 -7
  338. package/src/store/sqlite/__tests__/store-contract.test.ts +70 -16
  339. package/src/store/sqlite/__tests__/support.ts +12 -3
  340. package/src/store/sqlite/migrations.ts +57 -6
  341. package/src/store/sqlite/store.ts +462 -21
@@ -1,6 +1,7 @@
1
1
  import type { DescMessage, MessageShape } from "@bufbuild/protobuf";
2
2
  import type { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
3
- import type { AuditRecord, BootstrapStateStore, OAuthGrantStore, PendingOAuthStateStore, RawResourceDocument, ScheduleRunRecord, SearchIndexEntry, SearchIndexQuery, SearchIndexQueryResult, SignalDedupeStore, Store, WorkflowExecutionEventRecord } from "../interface.js";
3
+ import type { AuditRecord, BootstrapStateStore, OAuthGrantStore, PendingOAuthStateStore, RawResourceDocument, ScheduleRunRecord, SearchIndexEntry, SearchIndexQuery, SearchIndexQueryResult, SignalDedupeStore, Store, StoreOpenOptions, WorkflowExecutionEventRecord } from "../interface.js";
4
+ import type { ListIndexDeclaration, ListIndexQuery, ListIndexRow } from "../list-index.js";
4
5
  import type { StoreLogger } from "../logger.js";
5
6
  export declare class PostgresStore implements Store {
6
7
  readonly bootstrapState: BootstrapStateStore;
@@ -9,18 +10,23 @@ export declare class PostgresStore implements Store {
9
10
  readonly pendingOAuthStates: PendingOAuthStateStore;
10
11
  private pool;
11
12
  private readonly logger;
13
+ private readonly listIndexes;
12
14
  private constructor();
13
15
  /**
14
16
  * Connects to databaseUrl, runs migrations (advisory-locked, so
15
- * concurrent boots serialize), and returns the ready store. Any failure
16
- * here is a loud boot throw — never a degraded server (the same posture
17
- * as SqliteStore.open; the composition root does not catch it).
17
+ * concurrent boots serialize), reconciles the list index (derives the
18
+ * facts of every unproven row of a declared kind; compare-and-set, so
19
+ * replicas opening together cannot conflict), and returns the ready
20
+ * store. Any failure here is a loud boot throw — never a degraded server
21
+ * (the same posture as SqliteStore.open; the composition root does not
22
+ * catch it).
18
23
  */
19
- static open(databaseUrl: string, logger?: StoreLogger): Promise<PostgresStore>;
24
+ static open(databaseUrl: string, logger?: StoreLogger, options?: StoreOpenOptions): Promise<PostgresStore>;
20
25
  saveResource<Desc extends DescMessage>(kind: ApiResourceKind, id: string, schema: Desc, msg: MessageShape<Desc>): Promise<void>;
21
26
  getResource<Desc extends DescMessage>(kind: ApiResourceKind, id: string, schema: Desc): Promise<MessageShape<Desc>>;
22
27
  updateResource<Desc extends DescMessage>(kind: ApiResourceKind, id: string, schema: Desc, modify: (msg: MessageShape<Desc>) => void): Promise<MessageShape<Desc>>;
23
28
  listResources(kind: ApiResourceKind): Promise<Uint8Array[]>;
29
+ queryResources<K extends string>(declaration: ListIndexDeclaration<K>, query: ListIndexQuery<K>): Promise<ListIndexRow[]>;
24
30
  deleteResource(kind: ApiResourceKind, id: string): Promise<void>;
25
31
  findByField<Desc extends DescMessage>(kind: ApiResourceKind, fieldPath: string, value: string, schema: Desc): Promise<MessageShape<Desc>>;
26
32
  findAllByField(kind: ApiResourceKind, fieldPath: string, value: string): Promise<Uint8Array[]>;
@@ -29,6 +35,21 @@ export declare class PostgresStore implements Store {
29
35
  replaceResourceDataIfUnchanged(kind: ApiResourceKind, id: string, expectedData: Uint8Array, newData: Uint8Array): Promise<boolean>;
30
36
  deleteResourcesByKind(kind: ApiResourceKind): Promise<number>;
31
37
  deleteResourcesByIdPrefix(kind: ApiResourceKind, idPrefix: string): Promise<number>;
38
+ /**
39
+ * An unproven row's facts, derived from its bytes, and the row repaired
40
+ * when those bytes are still the stored ones — the compare-and-set on
41
+ * `data` means a newer write is never overwritten, and two replicas
42
+ * repairing one row write the same facts. Undefined when the bytes do
43
+ * not decode; the row stays unproven and is skipped, as a lane skips it.
44
+ */
45
+ private deriveAndRepair;
46
+ /**
47
+ * Derives every unproven row of each declared kind, in id-keyed batches,
48
+ * and stamps every other kind's unstamped rows (they carry no facts), so
49
+ * the unproven set is empty when the server starts serving. Reads stay
50
+ * exact without it; it keeps them cheap.
51
+ */
52
+ private reconcileListIndexes;
32
53
  saveAudit<Desc extends DescMessage>(kind: ApiResourceKind, resourceId: string, schema: Desc, msg: MessageShape<Desc>, versionHash: string, tag: string): Promise<void>;
33
54
  getAuditByHash<Desc extends DescMessage>(kind: ApiResourceKind, resourceId: string, versionHash: string, schema: Desc): Promise<MessageShape<Desc>>;
34
55
  getAuditByTag<Desc extends DescMessage>(kind: ApiResourceKind, resourceId: string, tag: string, schema: Desc): Promise<MessageShape<Desc>>;
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/store/postgres/store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/store/postgres/store.ts"],"names":[],"mappings":"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"}
@@ -36,6 +36,7 @@
36
36
  import { fromBinary, toBinary } from "@bufbuild/protobuf";
37
37
  import pg from "pg";
38
38
  import { AuditNotFoundError, DELIVERED_SIGNAL_DEDUPE_TTL_MS, PENDING_OAUTH_STATE_TTL_MS, ResourceNotFoundError, } from "../interface.js";
39
+ import { ListIndexRegistry, assertListIndexLimit, listIndexFactsOf, matchesListIndexQuery, mergeListIndexRows, sameListKeyRows, } from "../list-index.js";
39
40
  import { NOOP_STORE_LOGGER } from "../logger.js";
40
41
  import { apiResourceKindName, filterRowsByLabel, scanForFieldMatch, } from "../proto-fields.js";
41
42
  import { rfc3339Seconds } from "../rfc3339.js";
@@ -48,6 +49,46 @@ import { normalizeTsRankScore, renderTsQueryExpression } from "./tsquery.js";
48
49
  * silently returns wrong membership).
49
50
  */
50
51
  const TEXT_SEARCH_CONFIG = "english";
52
+ /**
53
+ * The one upsert every resource write goes through: the row, its
54
+ * `updated_at`, and the list index's facts stamped with the SAME `now()`
55
+ * (constant within a transaction), so a later writer that bumps
56
+ * `updated_at` without knowing the index leaves the row unproven. An
57
+ * undeclared kind writes NULL facts and is stamped all the same, which
58
+ * keeps it out of the unproven partial index.
59
+ */
60
+ const UPSERT_RESOURCE_SQL = `
61
+ INSERT INTO resources
62
+ (kind, id, data, updated_at, list_org, list_created_at, list_index_revision, list_indexed_at)
63
+ VALUES ($1, $2, $3, now(), $4, $5, $6, now())
64
+ ON CONFLICT (kind, id) DO UPDATE SET
65
+ data = excluded.data,
66
+ updated_at = excluded.updated_at,
67
+ list_org = excluded.list_org,
68
+ list_created_at = excluded.list_created_at,
69
+ list_index_revision = excluded.list_index_revision,
70
+ list_indexed_at = excluded.list_indexed_at`;
71
+ /**
72
+ * The rows of kind `$1` whose facts are not proven current under revision
73
+ * `$2` (list-index.ts), each arm one index range that fetches a row only
74
+ * when it matches: the stamp arm repeats the partial index's predicate
75
+ * verbatim (the only form the planner matches it in), the revision arms
76
+ * are three ranges of the revision index. A single OR over the revision
77
+ * is answered, without statistics, by visiting every heap row of the kind
78
+ * (measured), which grows with the kind; these arms are empty in steady
79
+ * state. They overlap (an unstamped row has no revision either), so the
80
+ * caller keeps one row per id.
81
+ */
82
+ const UNPROVEN_ROWS_SQL = `
83
+ SELECT id, data FROM resources WHERE kind = $1 AND list_indexed_at IS DISTINCT FROM updated_at
84
+ UNION ALL
85
+ SELECT id, data FROM resources WHERE kind = $1 AND list_index_revision IS NULL
86
+ UNION ALL
87
+ SELECT id, data FROM resources WHERE kind = $1 AND list_index_revision < $2
88
+ UNION ALL
89
+ SELECT id, data FROM resources WHERE kind = $1 AND list_index_revision > $2`;
90
+ /** How many unproven rows the reconciliation at open decodes per round trip. */
91
+ const RECONCILE_BATCH = 500;
51
92
  export class PostgresStore {
52
93
  bootstrapState;
53
94
  signalDedupe;
@@ -55,9 +96,11 @@ export class PostgresStore {
55
96
  pendingOAuthStates;
56
97
  pool;
57
98
  logger;
58
- constructor(pool, logger) {
99
+ listIndexes;
100
+ constructor(pool, logger, listIndexes) {
59
101
  this.pool = pool;
60
102
  this.logger = logger;
103
+ this.listIndexes = listIndexes;
61
104
  this.bootstrapState = new PostgresBootstrapStateStore(() => this.open());
62
105
  this.signalDedupe = new PostgresSignalDedupeStore(() => this.open(), logger);
63
106
  this.oauthGrants = new PostgresOAuthGrantStore(() => this.open());
@@ -65,11 +108,15 @@ export class PostgresStore {
65
108
  }
66
109
  /**
67
110
  * Connects to databaseUrl, runs migrations (advisory-locked, so
68
- * concurrent boots serialize), and returns the ready store. Any failure
69
- * here is a loud boot throw — never a degraded server (the same posture
70
- * as SqliteStore.open; the composition root does not catch it).
111
+ * concurrent boots serialize), reconciles the list index (derives the
112
+ * facts of every unproven row of a declared kind; compare-and-set, so
113
+ * replicas opening together cannot conflict), and returns the ready
114
+ * store. Any failure here is a loud boot throw — never a degraded server
115
+ * (the same posture as SqliteStore.open; the composition root does not
116
+ * catch it).
71
117
  */
72
- static async open(databaseUrl, logger = NOOP_STORE_LOGGER) {
118
+ static async open(databaseUrl, logger = NOOP_STORE_LOGGER, options = {}) {
119
+ const listIndexes = new ListIndexRegistry(options.listIndexes ?? []);
73
120
  const pool = new pg.Pool({ connectionString: databaseUrl });
74
121
  // An idle pooled connection dropping (server restart, network blip)
75
122
  // emits 'error' on the pool; without a listener that is a process
@@ -91,15 +138,45 @@ export class PostgresStore {
91
138
  throw error;
92
139
  }
93
140
  client.release();
94
- return new PostgresStore(pool, logger);
141
+ const store = new PostgresStore(pool, logger, listIndexes);
142
+ try {
143
+ await store.reconcileListIndexes();
144
+ }
145
+ catch (error) {
146
+ await store.close();
147
+ throw error;
148
+ }
149
+ return store;
95
150
  }
96
151
  // ---------------------------------------------------------------------------
97
152
  // Resource operations
98
153
  // ---------------------------------------------------------------------------
99
154
  async saveResource(kind, id, schema, msg) {
100
- const data = toBinary(schema, msg);
101
- await this.open().query(`INSERT INTO resources (kind, id, data, updated_at) VALUES ($1, $2, $3, now())
102
- ON CONFLICT (kind, id) DO UPDATE SET data = excluded.data, updated_at = now()`, [apiResourceKindName(kind), id, Buffer.from(data)]);
155
+ const data = Buffer.from(toBinary(schema, msg));
156
+ const kindName = apiResourceKindName(kind);
157
+ const facts = this.listIndexes.factsOf(kind, msg);
158
+ if (facts === undefined) {
159
+ await this.open().query(UPSERT_RESOURCE_SQL, [
160
+ kindName,
161
+ id,
162
+ data,
163
+ null,
164
+ null,
165
+ null,
166
+ ]);
167
+ return;
168
+ }
169
+ await this.withTransaction(async (client) => {
170
+ await client.query(UPSERT_RESOURCE_SQL, [
171
+ kindName,
172
+ id,
173
+ data,
174
+ facts.org,
175
+ facts.createdAt,
176
+ facts.revision,
177
+ ]);
178
+ await replaceListKeys(client, kindName, id, facts);
179
+ });
103
180
  }
104
181
  async getResource(kind, id, schema) {
105
182
  const kindName = apiResourceKindName(kind);
@@ -116,15 +193,34 @@ export class PostgresStore {
116
193
  // per-resource atomicity contract. `modify` is synchronous by contract
117
194
  // (interface.ts), so no caller I/O can extend the lock's hold time.
118
195
  return this.withTransaction(async (client) => {
119
- const result = await client.query(`SELECT data FROM resources WHERE kind = $1 AND id = $2 FOR UPDATE`, [kindName, id]);
196
+ const result = await client.query(`SELECT data, list_indexed_at IS NOT DISTINCT FROM updated_at AS stamped, list_index_revision
197
+ FROM resources WHERE kind = $1 AND id = $2 FOR UPDATE`, [kindName, id]);
120
198
  const row = result.rows[0];
121
199
  if (row === undefined) {
122
200
  throw new ResourceNotFoundError(`${kindName}/${id}`);
123
201
  }
124
202
  const msg = fromBinary(schema, row.data);
203
+ const before = this.listIndexes.factsOf(kind, msg);
125
204
  modify(msg);
205
+ const after = this.listIndexes.factsOf(kind, msg);
126
206
  const data = toBinary(schema, msg);
127
- await client.query(`UPDATE resources SET data = $3, updated_at = now() WHERE kind = $1 AND id = $2`, [kindName, id, Buffer.from(data)]);
207
+ await client.query(`UPDATE resources SET data = $3, updated_at = now(),
208
+ list_org = $4, list_created_at = $5, list_index_revision = $6, list_indexed_at = now()
209
+ WHERE kind = $1 AND id = $2`, [
210
+ kindName,
211
+ id,
212
+ Buffer.from(data),
213
+ after?.org ?? null,
214
+ after?.createdAt ?? null,
215
+ after?.revision ?? null,
216
+ ]);
217
+ if (after !== undefined &&
218
+ (!row.stamped ||
219
+ row.list_index_revision !== after.revision ||
220
+ before === undefined ||
221
+ !sameListKeyRows(before, after))) {
222
+ await replaceListKeys(client, kindName, id, after);
223
+ }
128
224
  return msg;
129
225
  });
130
226
  }
@@ -132,8 +228,53 @@ export class PostgresStore {
132
228
  const result = await this.open().query(`SELECT data FROM resources WHERE kind = $1`, [apiResourceKindName(kind)]);
133
229
  return result.rows.map((row) => row.data);
134
230
  }
231
+ async queryResources(declaration, query) {
232
+ this.listIndexes.require(declaration);
233
+ assertListIndexLimit(query.limit);
234
+ if (query.anyKey !== undefined && query.anyKey.length === 0) {
235
+ return [];
236
+ }
237
+ const kindName = apiResourceKindName(declaration.kind);
238
+ // A key read is driven from the key table, ordered by that index's own
239
+ // columns, so the plan is one index range whatever the planner's
240
+ // statistics say (a freshly written table has none); several keys are
241
+ // one such read each, merged below. Without a key the organization
242
+ // index is the range.
243
+ const provenRows = [];
244
+ const reads = query.anyKey === undefined
245
+ ? [provenReadSql(kindName, declaration.revision, query, undefined)]
246
+ : query.anyKey.map((key) => provenReadSql(kindName, declaration.revision, query, key));
247
+ for (const read of reads) {
248
+ const result = await this.open().query(read.sql, read.params);
249
+ for (const row of result.rows) {
250
+ provenRows.push({
251
+ id: row.id,
252
+ data: row.data,
253
+ cursor: { createdAt: row.created_at, id: row.id },
254
+ });
255
+ }
256
+ }
257
+ const unproven = await this.open().query(UNPROVEN_ROWS_SQL, [
258
+ kindName,
259
+ declaration.revision,
260
+ ]);
261
+ const unprovenRows = [];
262
+ for (const row of oneRowPerId(unproven.rows)) {
263
+ const facts = await this.deriveAndRepair(declaration, row.id, row.data);
264
+ if (facts !== undefined && matchesListIndexQuery(row.id, facts, query)) {
265
+ unprovenRows.push({
266
+ id: row.id,
267
+ data: row.data,
268
+ cursor: { createdAt: facts.createdAt, id: row.id },
269
+ });
270
+ }
271
+ }
272
+ return mergeListIndexRows(provenRows, unprovenRows, query.limit);
273
+ }
135
274
  async deleteResource(kind, id) {
136
- await this.open().query(`DELETE FROM resources WHERE kind = $1 AND id = $2`, [apiResourceKindName(kind), id]);
275
+ const kindName = apiResourceKindName(kind);
276
+ await this.open().query(`DELETE FROM resources WHERE kind = $1 AND id = $2`, [kindName, id]);
277
+ await this.open().query(`DELETE FROM resource_list_keys WHERE kind = $1 AND id = $2`, [kindName, id]);
137
278
  }
138
279
  async findByField(kind, fieldPath, value, schema) {
139
280
  const kindName = apiResourceKindName(kind);
@@ -168,26 +309,156 @@ export class PostgresStore {
168
309
  async replaceResourceDataIfUnchanged(kind, id, expectedData, newData) {
169
310
  // BYTEA equality in the WHERE clause makes the compare-and-swap one
170
311
  // atomic statement: zero rows changed means the row moved on (or was
171
- // deleted) since the read — a lost swap, never an upsert.
172
- const result = await this.open().query(`UPDATE resources SET data = $1, updated_at = now() WHERE kind = $2 AND id = $3 AND data = $4`, [
173
- Buffer.from(newData),
174
- apiResourceKindName(kind),
175
- id,
176
- Buffer.from(expectedData),
177
- ]);
178
- return result.rowCount === 1;
312
+ // deleted) since the read — a lost swap, never an upsert. The new bytes'
313
+ // list facts ride the same statement; bytes a declared kind's schema
314
+ // cannot decode leave the row unproven (stamp NULL), for a read to skip.
315
+ const kindName = apiResourceKindName(kind);
316
+ const declaration = this.listIndexes.declarationOf(kind);
317
+ const facts = declaration === undefined
318
+ ? undefined
319
+ : factsOfBytes(declaration, newData);
320
+ const swap = async (client) => {
321
+ const result = await client.query(`UPDATE resources SET data = $1, updated_at = now(),
322
+ list_org = $5, list_created_at = $6, list_index_revision = $7,
323
+ list_indexed_at = CASE WHEN $8::boolean THEN now() ELSE NULL END
324
+ WHERE kind = $2 AND id = $3 AND data = $4`, [
325
+ Buffer.from(newData),
326
+ kindName,
327
+ id,
328
+ Buffer.from(expectedData),
329
+ facts?.org ?? null,
330
+ facts?.createdAt ?? null,
331
+ facts?.revision ?? null,
332
+ declaration === undefined || facts !== undefined,
333
+ ]);
334
+ return result.rowCount === 1;
335
+ };
336
+ if (facts === undefined) {
337
+ return swap(this.open());
338
+ }
339
+ return this.withTransaction(async (client) => {
340
+ const swapped = await swap(client);
341
+ if (swapped) {
342
+ await replaceListKeys(client, kindName, id, facts);
343
+ }
344
+ return swapped;
345
+ });
179
346
  }
180
347
  async deleteResourcesByKind(kind) {
181
- const result = await this.open().query(`DELETE FROM resources WHERE kind = $1`, [apiResourceKindName(kind)]);
348
+ const kindName = apiResourceKindName(kind);
349
+ const result = await this.open().query(`DELETE FROM resources WHERE kind = $1`, [kindName]);
350
+ await this.open().query(`DELETE FROM resource_list_keys WHERE kind = $1`, [
351
+ kindName,
352
+ ]);
182
353
  return result.rowCount ?? 0;
183
354
  }
184
355
  async deleteResourcesByIdPrefix(kind, idPrefix) {
185
356
  // LIKE with the wildcard characters of the PREFIX escaped — sqlite's
186
357
  // GLOB has no LIKE-metacharacter hazard, this port must not either.
187
- const result = await this.open().query(`DELETE FROM resources WHERE kind = $1 AND id LIKE $2 ESCAPE '\\'`, [apiResourceKindName(kind), `${escapeLikePattern(idPrefix)}%`]);
358
+ const params = [
359
+ apiResourceKindName(kind),
360
+ `${escapeLikePattern(idPrefix)}%`,
361
+ ];
362
+ const result = await this.open().query(`DELETE FROM resources WHERE kind = $1 AND id LIKE $2 ESCAPE '\\'`, params);
363
+ await this.open().query(`DELETE FROM resource_list_keys WHERE kind = $1 AND id LIKE $2 ESCAPE '\\'`, params);
188
364
  return result.rowCount ?? 0;
189
365
  }
190
366
  // ---------------------------------------------------------------------------
367
+ // The list index's repair (list-index.ts)
368
+ // ---------------------------------------------------------------------------
369
+ /**
370
+ * An unproven row's facts, derived from its bytes, and the row repaired
371
+ * when those bytes are still the stored ones — the compare-and-set on
372
+ * `data` means a newer write is never overwritten, and two replicas
373
+ * repairing one row write the same facts. Undefined when the bytes do
374
+ * not decode; the row stays unproven and is skipped, as a lane skips it.
375
+ */
376
+ async deriveAndRepair(declaration, id, data) {
377
+ const kindName = apiResourceKindName(declaration.kind);
378
+ const facts = factsOfBytes(declaration, data);
379
+ if (facts === undefined) {
380
+ this.logger.warn("list index: an unproven row does not decode, skipping", {
381
+ kind: kindName,
382
+ id,
383
+ });
384
+ return undefined;
385
+ }
386
+ try {
387
+ await this.withTransaction(async (client) => {
388
+ const result = await client.query(`UPDATE resources SET list_org = $4, list_created_at = $5,
389
+ list_index_revision = $6, list_indexed_at = updated_at
390
+ WHERE kind = $1 AND id = $2 AND data = $3`, [
391
+ kindName,
392
+ id,
393
+ Buffer.from(data),
394
+ facts.org,
395
+ facts.createdAt,
396
+ facts.revision,
397
+ ]);
398
+ if (result.rowCount === 1) {
399
+ await replaceListKeys(client, kindName, id, facts);
400
+ }
401
+ });
402
+ }
403
+ catch (error) {
404
+ // The facts are right whether or not they were written back; a
405
+ // failed repair only leaves the row for the next read to repair.
406
+ this.logger.warn("list index: repairing a row failed", {
407
+ kind: kindName,
408
+ id,
409
+ error: error instanceof Error ? error.message : String(error),
410
+ });
411
+ }
412
+ return facts;
413
+ }
414
+ /**
415
+ * Derives every unproven row of each declared kind, in id-keyed batches,
416
+ * and stamps every other kind's unstamped rows (they carry no facts), so
417
+ * the unproven set is empty when the server starts serving. Reads stay
418
+ * exact without it; it keeps them cheap.
419
+ */
420
+ async reconcileListIndexes() {
421
+ const declarations = this.listIndexes.declarations();
422
+ let reconciled = false;
423
+ for (const declaration of declarations) {
424
+ const kindName = apiResourceKindName(declaration.kind);
425
+ let after = "";
426
+ let derived = 0;
427
+ for (;;) {
428
+ const batch = await this.open().query(`SELECT id, data FROM (${UNPROVEN_ROWS_SQL}) unproven WHERE id > $3
429
+ ORDER BY id LIMIT $4`, [kindName, declaration.revision, after, RECONCILE_BATCH]);
430
+ // Duplicates from overlapping arms sit side by side in id order and
431
+ // the next batch starts after the last id, so none is repaired twice
432
+ // across batches and the count still ends the loop.
433
+ const rows = batch.rows;
434
+ for (const row of oneRowPerId(rows)) {
435
+ if ((await this.deriveAndRepair(declaration, row.id, row.data)) !==
436
+ undefined) {
437
+ derived += 1;
438
+ }
439
+ after = row.id;
440
+ }
441
+ if (rows.length < RECONCILE_BATCH) {
442
+ break;
443
+ }
444
+ }
445
+ if (derived > 0) {
446
+ this.logger.info("list index reconciled", {
447
+ kind: kindName,
448
+ rows: derived,
449
+ });
450
+ reconciled = true;
451
+ }
452
+ }
453
+ if (reconciled) {
454
+ // Every derived row's list columns changed at once; the planner's
455
+ // statistics for them are refreshed before the first read plans.
456
+ await this.open().query(`ANALYZE resources, resource_list_keys`);
457
+ }
458
+ await this.open().query(`UPDATE resources SET list_indexed_at = updated_at
459
+ WHERE list_indexed_at IS DISTINCT FROM updated_at AND NOT (kind = ANY($1::text[]))`, [declarations.map((d) => apiResourceKindName(d.kind))]);
460
+ }
461
+ // ---------------------------------------------------------------------------
191
462
  // Audit operations
192
463
  // ---------------------------------------------------------------------------
193
464
  async saveAudit(kind, resourceId, schema, msg, versionHash, tag) {
@@ -858,6 +1129,90 @@ class PostgresPendingOAuthStateStore {
858
1129
  return result.rowCount ?? 0;
859
1130
  }
860
1131
  }
1132
+ /**
1133
+ * A row's key rows made equal to its facts: one row per key the row
1134
+ * names, the creation instant copied in so a parent's rows are one index
1135
+ * range. Runs on the caller's transaction, beside the row write it
1136
+ * belongs to.
1137
+ */
1138
+ async function replaceListKeys(client, kindName, id, facts) {
1139
+ await client.query(`DELETE FROM resource_list_keys WHERE kind = $1 AND id = $2`, [kindName, id]);
1140
+ if (facts.keys.length === 0) {
1141
+ return;
1142
+ }
1143
+ const params = [kindName, id, facts.createdAt];
1144
+ const tuples = facts.keys.map((entry) => {
1145
+ params.push(entry.key, entry.value);
1146
+ return `($1, $2, $${params.length - 1}, $${params.length}, $3)`;
1147
+ });
1148
+ await client.query(`INSERT INTO resource_list_keys (kind, id, key, value, created_at) VALUES ${tuples.join(", ")}`, params);
1149
+ }
1150
+ /**
1151
+ * One proven read of `queryResources`: through one key's index range when
1152
+ * `key` is given (the key table drives, its copied creation instant is the
1153
+ * order), else through the organization index. Only rows whose facts are
1154
+ * proven current are returned; the unproven arm answers for the rest.
1155
+ */
1156
+ function provenReadSql(kindName, revision, query, key) {
1157
+ const params = [kindName, revision];
1158
+ const param = (value) => {
1159
+ params.push(value);
1160
+ return `$${params.length}`;
1161
+ };
1162
+ const created = key === undefined ? "r.list_created_at" : "k.created_at";
1163
+ const id = key === undefined ? `r.id COLLATE "C"` : `k.id COLLATE "C"`;
1164
+ const where = [
1165
+ `r.list_indexed_at = r.updated_at`,
1166
+ `r.list_index_revision = $2`,
1167
+ ];
1168
+ let from = `resources r`;
1169
+ if (key === undefined) {
1170
+ where.unshift(`r.kind = $1`);
1171
+ }
1172
+ else {
1173
+ // The row behind each key is fetched by primary key and nothing else:
1174
+ // `OFFSET 0` keeps the subquery from being flattened, so a planner
1175
+ // without statistics cannot reach it through the revision index
1176
+ // instead (measured: a scan of the kind per key row).
1177
+ from = `resource_list_keys k CROSS JOIN LATERAL (
1178
+ SELECT id, data, updated_at, list_indexed_at, list_index_revision, list_org
1179
+ FROM resources WHERE kind = k.kind AND id = k.id OFFSET 0
1180
+ ) r`;
1181
+ where.unshift(`k.kind = $1`, `k.key = ${param(key.name)}`, `k.value = ${param(key.value)}`);
1182
+ }
1183
+ if (query.org !== undefined && query.org !== "") {
1184
+ where.push(`r.list_org = ${param(query.org)}`);
1185
+ }
1186
+ if (query.createdAtOrAfter !== undefined) {
1187
+ where.push(`(${created} >= ${param(query.createdAtOrAfter)} OR ${created} = '')`);
1188
+ }
1189
+ if (query.after !== undefined) {
1190
+ const afterCreated = param(query.after.createdAt);
1191
+ const afterId = param(query.after.id);
1192
+ where.push(`(${created} < ${afterCreated} OR (${created} = ${afterCreated} AND ${id} < ${afterId}))`);
1193
+ }
1194
+ const limit = query.limit === undefined ? "" : ` LIMIT ${param(query.limit)}`;
1195
+ return {
1196
+ sql: `SELECT r.id, r.data, ${created} AS created_at FROM ${from}
1197
+ WHERE ${where.join(" AND ")}
1198
+ ORDER BY ${created} DESC, ${id} DESC${limit}`,
1199
+ params,
1200
+ };
1201
+ }
1202
+ /** Keeps the first row per id; the unproven arms overlap. */
1203
+ function oneRowPerId(rows) {
1204
+ const seen = new Set();
1205
+ return rows.filter((row) => seen.has(row.id) ? false : (seen.add(row.id), true));
1206
+ }
1207
+ /** A declared kind's facts from stored bytes; undefined when they do not decode. */
1208
+ function factsOfBytes(declaration, data) {
1209
+ try {
1210
+ return listIndexFactsOf(declaration, fromBinary(declaration.schema, data));
1211
+ }
1212
+ catch {
1213
+ return undefined;
1214
+ }
1215
+ }
861
1216
  /** Escapes LIKE metacharacters so a prefix matches literally. */
862
1217
  function escapeLikePattern(value) {
863
1218
  return value