@mistriwork/engine 0.0.0-stage → 1.0.0-rc.10

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 (546) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +17 -2
  3. package/dist/adapters/adapter.d.ts +693 -0
  4. package/dist/adapters/adapter.js +444 -0
  5. package/dist/adapters/cache/cacheStore.d.ts +75 -0
  6. package/dist/adapters/cache/cacheStore.js +160 -0
  7. package/dist/adapters/cache/resp.d.ts +52 -0
  8. package/dist/adapters/cache/resp.js +237 -0
  9. package/dist/adapters/commands/canonicalJson.d.ts +19 -0
  10. package/dist/adapters/commands/canonicalJson.js +154 -0
  11. package/dist/adapters/connect.d.ts +29 -0
  12. package/dist/adapters/connect.js +40 -0
  13. package/dist/adapters/connectionBudget.d.ts +101 -0
  14. package/dist/adapters/connectionBudget.js +128 -0
  15. package/dist/adapters/dbLatency.d.ts +35 -0
  16. package/dist/adapters/dbLatency.js +79 -0
  17. package/dist/adapters/epochCache.d.ts +50 -0
  18. package/dist/adapters/epochCache.js +85 -0
  19. package/dist/adapters/http/egress.d.ts +48 -0
  20. package/dist/adapters/http/egress.js +175 -0
  21. package/dist/adapters/http/httpAdapter.d.ts +71 -0
  22. package/dist/adapters/http/httpAdapter.js +512 -0
  23. package/dist/adapters/http/httpDescription.d.ts +32 -0
  24. package/dist/adapters/http/httpDescription.js +246 -0
  25. package/dist/adapters/http/openapi.d.ts +98 -0
  26. package/dist/adapters/http/openapi.js +487 -0
  27. package/dist/adapters/layoutRefusal.d.ts +24 -0
  28. package/dist/adapters/layoutRefusal.js +35 -0
  29. package/dist/adapters/net/networkPolicy.d.ts +44 -0
  30. package/dist/adapters/net/networkPolicy.js +123 -0
  31. package/dist/adapters/net/untrustedDatabase.d.ts +18 -0
  32. package/dist/adapters/net/untrustedDatabase.js +120 -0
  33. package/dist/adapters/poolAdmission.d.ts +43 -0
  34. package/dist/adapters/poolAdmission.js +184 -0
  35. package/dist/adapters/poolMetrics.d.ts +121 -0
  36. package/dist/adapters/poolMetrics.js +185 -0
  37. package/dist/adapters/postgres.d.ts +436 -0
  38. package/dist/adapters/postgres.js +1890 -0
  39. package/dist/adapters/postgresCatalog.d.ts +96 -0
  40. package/dist/adapters/postgresCatalog.js +387 -0
  41. package/dist/adapters/postgresCatalogDigest.d.ts +30 -0
  42. package/dist/adapters/postgresCatalogDigest.js +87 -0
  43. package/dist/adapters/postgresCatalogRows.d.ts +62 -0
  44. package/dist/adapters/postgresCatalogRows.js +37 -0
  45. package/dist/adapters/postgresDates.d.ts +1 -0
  46. package/dist/adapters/postgresDates.js +39 -0
  47. package/dist/adapters/postgresMaintenance.d.ts +33 -0
  48. package/dist/adapters/postgresMaintenance.js +179 -0
  49. package/dist/adapters/postgresPool.d.ts +255 -0
  50. package/dist/adapters/postgresPool.js +838 -0
  51. package/dist/adapters/postgresPooler.d.ts +28 -0
  52. package/dist/adapters/postgresPooler.js +267 -0
  53. package/dist/adapters/postgresReadCatalog.d.ts +31 -0
  54. package/dist/adapters/postgresReadCatalog.js +226 -0
  55. package/dist/adapters/postgresReadSnapshot.d.ts +46 -0
  56. package/dist/adapters/postgresReadSnapshot.js +133 -0
  57. package/dist/adapters/postgresSchemaEpoch.d.ts +24 -0
  58. package/dist/adapters/postgresSchemaEpoch.js +149 -0
  59. package/dist/adapters/postgresTransactionHandle.d.ts +173 -0
  60. package/dist/adapters/postgresTransactionHandle.js +524 -0
  61. package/dist/adapters/postgresValues.d.ts +30 -0
  62. package/dist/adapters/postgresValues.js +75 -0
  63. package/dist/adapters/replicas.d.ts +70 -0
  64. package/dist/adapters/replicas.js +208 -0
  65. package/dist/adapters/reservedSql.d.ts +38 -0
  66. package/dist/adapters/reservedSql.js +264 -0
  67. package/dist/adapters/rolePrivileges.d.ts +17 -0
  68. package/dist/adapters/rolePrivileges.js +22 -0
  69. package/dist/adapters/scoped/keyRows.d.ts +65 -0
  70. package/dist/adapters/scoped/keyRows.js +173 -0
  71. package/dist/adapters/scoped/postgresScopeCatalog.d.ts +16 -0
  72. package/dist/adapters/scoped/postgresScopeCatalog.js +117 -0
  73. package/dist/adapters/scoped/scopedCommandOperations.d.ts +5 -0
  74. package/dist/adapters/scoped/scopedCommandOperations.js +48 -0
  75. package/dist/adapters/scoped/scopedReads.d.ts +20 -0
  76. package/dist/adapters/scoped/scopedReads.js +174 -0
  77. package/dist/adapters/scoped/scopedTransaction.d.ts +89 -0
  78. package/dist/adapters/scoped/scopedTransaction.js +303 -0
  79. package/dist/adapters/scoped/sqliteScopeCatalog.d.ts +16 -0
  80. package/dist/adapters/scoped/sqliteScopeCatalog.js +241 -0
  81. package/dist/adapters/scoped/updateGuard.d.ts +3 -0
  82. package/dist/adapters/scoped/updateGuard.js +40 -0
  83. package/dist/adapters/serverSettings.d.ts +45 -0
  84. package/dist/adapters/serverSettings.js +46 -0
  85. package/dist/adapters/sqlite.d.ts +262 -0
  86. package/dist/adapters/sqlite.js +1434 -0
  87. package/dist/adapters/sqliteCostlyReads.d.ts +33 -0
  88. package/dist/adapters/sqliteCostlyReads.js +49 -0
  89. package/dist/adapters/sqliteDecimals.d.ts +22 -0
  90. package/dist/adapters/sqliteDecimals.js +57 -0
  91. package/dist/adapters/sqliteHandles.d.ts +43 -0
  92. package/dist/adapters/sqliteHandles.js +143 -0
  93. package/dist/adapters/sqliteStatementProcess.d.ts +29 -0
  94. package/dist/adapters/sqliteStatementProcess.js +131 -0
  95. package/dist/adapters/statementControl.d.ts +34 -0
  96. package/dist/adapters/statementControl.js +90 -0
  97. package/dist/adapters/storeHost.d.ts +130 -0
  98. package/dist/adapters/storeHost.js +13 -0
  99. package/dist/adapters/telemetry/drivers.d.ts +21 -0
  100. package/dist/adapters/telemetry/drivers.js +321 -0
  101. package/dist/adapters/telemetry/metrics.d.ts +63 -0
  102. package/dist/adapters/telemetry/metrics.js +122 -0
  103. package/dist/adapters/telemetry/otlp.d.ts +12 -0
  104. package/dist/adapters/telemetry/otlp.js +320 -0
  105. package/dist/adapters/telemetry/otlpEncode.d.ts +48 -0
  106. package/dist/adapters/telemetry/otlpEncode.js +366 -0
  107. package/dist/adapters/telemetry/otlpEnv.d.ts +1 -0
  108. package/dist/adapters/telemetry/otlpEnv.js +12 -0
  109. package/dist/adapters/telemetry/serverTiming.d.ts +83 -0
  110. package/dist/adapters/telemetry/serverTiming.js +503 -0
  111. package/dist/adapters/telemetry/start.d.ts +10 -0
  112. package/dist/adapters/telemetry/start.js +25 -0
  113. package/dist/adapters/telemetry/statement.d.ts +16 -0
  114. package/dist/adapters/telemetry/statement.js +134 -0
  115. package/dist/adapters/telemetry/trace.d.ts +127 -0
  116. package/dist/adapters/telemetry/trace.js +295 -0
  117. package/dist/adapters/writeAhead.d.ts +27 -0
  118. package/dist/adapters/writeAhead.js +46 -0
  119. package/dist/adapters/writeAttribution.d.ts +18 -0
  120. package/dist/adapters/writeAttribution.js +23 -0
  121. package/dist/adapters/writeCapture.d.ts +73 -0
  122. package/dist/adapters/writeCapture.js +20 -0
  123. package/dist/adapters/writeLanes.d.ts +94 -0
  124. package/dist/adapters/writeLanes.js +232 -0
  125. package/dist/engine.d.ts +15 -0
  126. package/dist/engine.js +13 -0
  127. package/dist/server/auth/credentials.d.ts +61 -0
  128. package/dist/server/auth/credentials.js +260 -0
  129. package/dist/server/cacheService.d.ts +50 -0
  130. package/dist/server/cacheService.js +110 -0
  131. package/dist/server/cli/args.d.ts +176 -0
  132. package/dist/server/cli/args.js +342 -0
  133. package/dist/server/cli/bin.d.ts +5 -0
  134. package/dist/server/cli/bin.js +66 -0
  135. package/dist/server/cli/browser.d.ts +15 -0
  136. package/dist/server/cli/browser.js +37 -0
  137. package/dist/server/cli/config.d.ts +50 -0
  138. package/dist/server/cli/config.js +206 -0
  139. package/dist/server/cli/startup.d.ts +36 -0
  140. package/dist/server/cli/startup.js +128 -0
  141. package/dist/server/cli/startupOptions.d.ts +63 -0
  142. package/dist/server/cli/startupOptions.js +143 -0
  143. package/dist/server/cli/subcommand.d.ts +28 -0
  144. package/dist/server/cli/subcommand.js +32 -0
  145. package/dist/server/cli/usage.d.ts +13 -0
  146. package/dist/server/cli/usage.js +31 -0
  147. package/dist/server/clientHttp.d.ts +184 -0
  148. package/dist/server/clientHttp.js +700 -0
  149. package/dist/server/clientSurface.d.ts +128 -0
  150. package/dist/server/clientSurface.js +182 -0
  151. package/dist/server/columnGrants.d.ts +103 -0
  152. package/dist/server/columnGrants.js +730 -0
  153. package/dist/server/compileCache.d.ts +1 -0
  154. package/dist/server/compileCache.js +30 -0
  155. package/dist/server/config/locations.d.ts +1 -0
  156. package/dist/server/config/locations.js +15 -0
  157. package/dist/server/configPaths.d.ts +7 -0
  158. package/dist/server/configPaths.js +34 -0
  159. package/dist/server/configSections.d.ts +64 -0
  160. package/dist/server/configSections.js +72 -0
  161. package/dist/server/connectionRedaction.d.ts +30 -0
  162. package/dist/server/connectionRedaction.js +77 -0
  163. package/dist/server/connections.d.ts +353 -0
  164. package/dist/server/connections.js +978 -0
  165. package/dist/server/constraints.d.ts +82 -0
  166. package/dist/server/constraints.js +169 -0
  167. package/dist/server/demo.d.ts +9 -0
  168. package/dist/server/demo.js +554 -0
  169. package/dist/server/devclient.d.ts +108 -0
  170. package/dist/server/devclient.js +361 -0
  171. package/dist/server/evidence.d.ts +55 -0
  172. package/dist/server/evidence.js +323 -0
  173. package/dist/server/hiddenColumns.d.ts +32 -0
  174. package/dist/server/hiddenColumns.js +238 -0
  175. package/dist/server/http/baselineHeaders.d.ts +24 -0
  176. package/dist/server/http/baselineHeaders.js +12 -0
  177. package/dist/server/http/captured.d.ts +35 -0
  178. package/dist/server/http/captured.js +73 -0
  179. package/dist/server/http/clock.d.ts +1 -0
  180. package/dist/server/http/clock.js +14 -0
  181. package/dist/server/http/connectionSource.d.ts +4 -0
  182. package/dist/server/http/connectionSource.js +48 -0
  183. package/dist/server/http/context.d.ts +189 -0
  184. package/dist/server/http/context.js +11 -0
  185. package/dist/server/http/crawlerText.d.ts +15 -0
  186. package/dist/server/http/crawlerText.js +32 -0
  187. package/dist/server/http/dataRoutes.d.ts +88 -0
  188. package/dist/server/http/dataRoutes.js +137 -0
  189. package/dist/server/http/door.d.ts +57 -0
  190. package/dist/server/http/door.js +39 -0
  191. package/dist/server/http/exportRoute.d.ts +15 -0
  192. package/dist/server/http/exportRoute.js +320 -0
  193. package/dist/server/http/firstPaint.d.ts +108 -0
  194. package/dist/server/http/firstPaint.js +262 -0
  195. package/dist/server/http/httpOperationRoutes.d.ts +11 -0
  196. package/dist/server/http/httpOperationRoutes.js +50 -0
  197. package/dist/server/http/pageReads.d.ts +20 -0
  198. package/dist/server/http/pageReads.js +205 -0
  199. package/dist/server/http/pageRoutes.d.ts +39 -0
  200. package/dist/server/http/pageRoutes.js +291 -0
  201. package/dist/server/http/probes.d.ts +22 -0
  202. package/dist/server/http/probes.js +27 -0
  203. package/dist/server/http/rateLimit.d.ts +131 -0
  204. package/dist/server/http/rateLimit.js +351 -0
  205. package/dist/server/http/readRoutes.d.ts +9 -0
  206. package/dist/server/http/readRoutes.js +131 -0
  207. package/dist/server/http/readRouting.d.ts +20 -0
  208. package/dist/server/http/readRouting.js +80 -0
  209. package/dist/server/http/recordCompose.d.ts +67 -0
  210. package/dist/server/http/recordCompose.js +167 -0
  211. package/dist/server/http/recordWrites.d.ts +89 -0
  212. package/dist/server/http/recordWrites.js +298 -0
  213. package/dist/server/http/refusalReport.d.ts +38 -0
  214. package/dist/server/http/refusalReport.js +63 -0
  215. package/dist/server/http/reply.d.ts +130 -0
  216. package/dist/server/http/reply.js +394 -0
  217. package/dist/server/http/requestOrigin.d.ts +11 -0
  218. package/dist/server/http/requestOrigin.js +8 -0
  219. package/dist/server/http/router.d.ts +45 -0
  220. package/dist/server/http/router.js +22 -0
  221. package/dist/server/http/serverCore.d.ts +68 -0
  222. package/dist/server/http/serverCore.js +177 -0
  223. package/dist/server/http/traceRequest.d.ts +30 -0
  224. package/dist/server/http/traceRequest.js +224 -0
  225. package/dist/server/http/trust.d.ts +261 -0
  226. package/dist/server/http/trust.js +472 -0
  227. package/dist/server/http/writeRefusal.d.ts +33 -0
  228. package/dist/server/http/writeRefusal.js +24 -0
  229. package/dist/server/httpConnection.d.ts +49 -0
  230. package/dist/server/httpConnection.js +170 -0
  231. package/dist/server/impact.d.ts +55 -0
  232. package/dist/server/impact.js +47 -0
  233. package/dist/server/journal.d.ts +134 -0
  234. package/dist/server/journal.js +398 -0
  235. package/dist/server/keysetReadHttp.d.ts +52 -0
  236. package/dist/server/keysetReadHttp.js +85 -0
  237. package/dist/server/layouts.d.ts +49 -0
  238. package/dist/server/layouts.js +192 -0
  239. package/dist/server/migrate/cli.d.ts +55 -0
  240. package/dist/server/migrate/cli.js +517 -0
  241. package/dist/server/migrate/database.d.ts +37 -0
  242. package/dist/server/migrate/database.js +247 -0
  243. package/dist/server/migrate/desired.d.ts +76 -0
  244. package/dist/server/migrate/desired.js +292 -0
  245. package/dist/server/migrate/files.d.ts +34 -0
  246. package/dist/server/migrate/files.js +141 -0
  247. package/dist/server/migrate/flyway.d.ts +7 -0
  248. package/dist/server/migrate/flyway.js +69 -0
  249. package/dist/server/migrate/history.d.ts +70 -0
  250. package/dist/server/migrate/history.js +211 -0
  251. package/dist/server/migrate/runner.d.ts +100 -0
  252. package/dist/server/migrate/runner.js +409 -0
  253. package/dist/server/migrate/snapshot.d.ts +30 -0
  254. package/dist/server/migrate/snapshot.js +239 -0
  255. package/dist/server/migrate/target.d.ts +33 -0
  256. package/dist/server/migrate/target.js +82 -0
  257. package/dist/server/openapiDraft.d.ts +25 -0
  258. package/dist/server/openapiDraft.js +171 -0
  259. package/dist/server/operations/diagnostics.d.ts +101 -0
  260. package/dist/server/operations/diagnostics.js +284 -0
  261. package/dist/server/operations/requestMetrics.d.ts +33 -0
  262. package/dist/server/operations/requestMetrics.js +94 -0
  263. package/dist/server/packageVersion.d.ts +1 -0
  264. package/dist/server/packageVersion.js +11 -0
  265. package/dist/server/poolConfig.d.ts +34 -0
  266. package/dist/server/poolConfig.js +114 -0
  267. package/dist/server/product.d.ts +20 -0
  268. package/dist/server/product.js +12 -0
  269. package/dist/server/recordHistoryOperations.d.ts +39 -0
  270. package/dist/server/recordHistoryOperations.js +43 -0
  271. package/dist/server/recordWriteOperations.d.ts +111 -0
  272. package/dist/server/recordWriteOperations.js +329 -0
  273. package/dist/server/resourceReadHttp.d.ts +81 -0
  274. package/dist/server/resourceReadHttp.js +606 -0
  275. package/dist/server/resourceReadOperations.d.ts +147 -0
  276. package/dist/server/resourceReadOperations.js +452 -0
  277. package/dist/server/scaffoldMachine.d.ts +54 -0
  278. package/dist/server/scaffoldMachine.js +184 -0
  279. package/dist/server/seed/cli.d.ts +20 -0
  280. package/dist/server/seed/cli.js +164 -0
  281. package/dist/server/seed/lexicon.d.ts +26 -0
  282. package/dist/server/seed/lexicon.js +585 -0
  283. package/dist/server/seed/seed.d.ts +70 -0
  284. package/dist/server/seed/seed.js +779 -0
  285. package/dist/server/seed/words.d.ts +21 -0
  286. package/dist/server/seed/words.js +164 -0
  287. package/dist/server/serverRequests.d.ts +6 -0
  288. package/dist/server/serverRequests.js +33 -0
  289. package/dist/server/serverTimeouts.d.ts +33 -0
  290. package/dist/server/serverTimeouts.js +32 -0
  291. package/dist/server/shapeonly.d.ts +32 -0
  292. package/dist/server/shapeonly.js +110 -0
  293. package/dist/server/startupWarnings.d.ts +32 -0
  294. package/dist/server/startupWarnings.js +92 -0
  295. package/dist/shared/addressParams.d.ts +28 -0
  296. package/dist/shared/addressParams.js +47 -0
  297. package/dist/shared/appCards.d.ts +123 -0
  298. package/dist/shared/appCards.js +385 -0
  299. package/dist/shared/appComponents.d.ts +177 -0
  300. package/dist/shared/appComponents.js +422 -0
  301. package/dist/shared/appDocuments.d.ts +43 -0
  302. package/dist/shared/appDocuments.js +131 -0
  303. package/dist/shared/appEmbeds.d.ts +21 -0
  304. package/dist/shared/appEmbeds.js +24 -0
  305. package/dist/shared/appIcons.d.ts +28 -0
  306. package/dist/shared/appIcons.js +223 -0
  307. package/dist/shared/appLinks.d.ts +17 -0
  308. package/dist/shared/appLinks.js +124 -0
  309. package/dist/shared/appListColumns.d.ts +3 -0
  310. package/dist/shared/appListColumns.js +66 -0
  311. package/dist/shared/appLocale.d.ts +36 -0
  312. package/dist/shared/appLocale.js +86 -0
  313. package/dist/shared/appMessages.d.ts +44 -0
  314. package/dist/shared/appMessages.js +78 -0
  315. package/dist/shared/appNoun.d.ts +21 -0
  316. package/dist/shared/appNoun.js +32 -0
  317. package/dist/shared/autoBucket.d.ts +98 -0
  318. package/dist/shared/autoBucket.js +305 -0
  319. package/dist/shared/autoValues.d.ts +25 -0
  320. package/dist/shared/autoValues.js +25 -0
  321. package/dist/shared/breakdown.d.ts +228 -0
  322. package/dist/shared/breakdown.js +557 -0
  323. package/dist/shared/breakdowntext.d.ts +41 -0
  324. package/dist/shared/breakdowntext.js +400 -0
  325. package/dist/shared/builtInThemes.d.ts +2 -0
  326. package/dist/shared/builtInThemes.js +771 -0
  327. package/dist/shared/chartSeparation.d.ts +11 -0
  328. package/dist/shared/chartSeparation.js +58 -0
  329. package/dist/shared/clauses.d.ts +22 -0
  330. package/dist/shared/clauses.js +97 -0
  331. package/dist/shared/columnCaption.d.ts +16 -0
  332. package/dist/shared/columnCaption.js +90 -0
  333. package/dist/shared/commandFieldPath.d.ts +2 -0
  334. package/dist/shared/commandFieldPath.js +7 -0
  335. package/dist/shared/commandWire.d.ts +26 -0
  336. package/dist/shared/commandWire.js +41 -0
  337. package/dist/shared/computed.d.ts +161 -0
  338. package/dist/shared/computed.js +378 -0
  339. package/dist/shared/computedParse.d.ts +13 -0
  340. package/dist/shared/computedParse.js +278 -0
  341. package/dist/shared/computedValue.d.ts +47 -0
  342. package/dist/shared/computedValue.js +191 -0
  343. package/dist/shared/connection.d.ts +30 -0
  344. package/dist/shared/connection.js +1 -0
  345. package/dist/shared/csv.d.ts +20 -0
  346. package/dist/shared/csv.js +63 -0
  347. package/dist/shared/dashLayout.d.ts +30 -0
  348. package/dist/shared/dashLayout.js +132 -0
  349. package/dist/shared/dashboardAsks.d.ts +93 -0
  350. package/dist/shared/dashboardAsks.js +129 -0
  351. package/dist/shared/dashboardTrend.d.ts +20 -0
  352. package/dist/shared/dashboardTrend.js +50 -0
  353. package/dist/shared/declaredReferences.d.ts +50 -0
  354. package/dist/shared/declaredReferences.js +202 -0
  355. package/dist/shared/definedFields.d.ts +63 -0
  356. package/dist/shared/definedFields.js +103 -0
  357. package/dist/shared/definedValues.d.ts +63 -0
  358. package/dist/shared/definedValues.js +184 -0
  359. package/dist/shared/displayDefaults.d.ts +135 -0
  360. package/dist/shared/displayDefaults.js +529 -0
  361. package/dist/shared/dueDate.d.ts +6 -0
  362. package/dist/shared/dueDate.js +6 -0
  363. package/dist/shared/duration.d.ts +4 -0
  364. package/dist/shared/duration.js +23 -0
  365. package/dist/shared/fileBounds.d.ts +9 -0
  366. package/dist/shared/fileBounds.js +9 -0
  367. package/dist/shared/filter.d.ts +217 -0
  368. package/dist/shared/filter.js +305 -0
  369. package/dist/shared/firstPaint.d.ts +24 -0
  370. package/dist/shared/firstPaint.js +33 -0
  371. package/dist/shared/form.d.ts +21 -0
  372. package/dist/shared/form.js +25 -0
  373. package/dist/shared/formValues.d.ts +104 -0
  374. package/dist/shared/formValues.js +352 -0
  375. package/dist/shared/funnel.d.ts +45 -0
  376. package/dist/shared/funnel.js +108 -0
  377. package/dist/shared/httpOperations.d.ts +48 -0
  378. package/dist/shared/httpOperations.js +9 -0
  379. package/dist/shared/iconGlyphs.d.ts +4 -0
  380. package/dist/shared/iconGlyphs.js +151 -0
  381. package/dist/shared/iconWeights.d.ts +3 -0
  382. package/dist/shared/iconWeights.js +162 -0
  383. package/dist/shared/imageWidths.d.ts +32 -0
  384. package/dist/shared/imageWidths.js +38 -0
  385. package/dist/shared/keysetPage.d.ts +64 -0
  386. package/dist/shared/keysetPage.js +161 -0
  387. package/dist/shared/like.d.ts +20 -0
  388. package/dist/shared/like.js +29 -0
  389. package/dist/shared/listEmpty.d.ts +12 -0
  390. package/dist/shared/listEmpty.js +36 -0
  391. package/dist/shared/listFilters.d.ts +28 -0
  392. package/dist/shared/listFilters.js +84 -0
  393. package/dist/shared/listPresentations.d.ts +199 -0
  394. package/dist/shared/listPresentations.js +7 -0
  395. package/dist/shared/listQuestion.d.ts +52 -0
  396. package/dist/shared/listQuestion.js +137 -0
  397. package/dist/shared/lookPalettes.d.ts +62 -0
  398. package/dist/shared/lookPalettes.js +306 -0
  399. package/dist/shared/maskQuoted.d.ts +24 -0
  400. package/dist/shared/maskQuoted.js +48 -0
  401. package/dist/shared/mentionLabel.d.ts +19 -0
  402. package/dist/shared/mentionLabel.js +38 -0
  403. package/dist/shared/mentionLookup.d.ts +17 -0
  404. package/dist/shared/mentionLookup.js +64 -0
  405. package/dist/shared/menuCounts.d.ts +16 -0
  406. package/dist/shared/menuCounts.js +74 -0
  407. package/dist/shared/model.d.ts +79 -0
  408. package/dist/shared/model.js +153 -0
  409. package/dist/shared/money.d.ts +44 -0
  410. package/dist/shared/money.js +81 -0
  411. package/dist/shared/moneyEntry.d.ts +7 -0
  412. package/dist/shared/moneyEntry.js +27 -0
  413. package/dist/shared/mount.d.ts +118 -0
  414. package/dist/shared/mount.js +248 -0
  415. package/dist/shared/niceTicks.d.ts +26 -0
  416. package/dist/shared/niceTicks.js +50 -0
  417. package/dist/shared/order.d.ts +59 -0
  418. package/dist/shared/order.js +133 -0
  419. package/dist/shared/page.d.ts +135 -0
  420. package/dist/shared/page.js +1187 -0
  421. package/dist/shared/pageAnatomy.d.ts +99 -0
  422. package/dist/shared/pageAnatomy.js +370 -0
  423. package/dist/shared/pageConfig.d.ts +25 -0
  424. package/dist/shared/pageConfig.js +1308 -0
  425. package/dist/shared/pageEmptyActions.d.ts +12 -0
  426. package/dist/shared/pageEmptyActions.js +20 -0
  427. package/dist/shared/pageLayouts.d.ts +72 -0
  428. package/dist/shared/pageLayouts.js +205 -0
  429. package/dist/shared/pageModel.d.ts +785 -0
  430. package/dist/shared/pageModel.js +121 -0
  431. package/dist/shared/pagePaths.d.ts +180 -0
  432. package/dist/shared/pagePaths.js +869 -0
  433. package/dist/shared/pagePlans.d.ts +52 -0
  434. package/dist/shared/pagePlans.js +108 -0
  435. package/dist/shared/pageRoutes.d.ts +9 -0
  436. package/dist/shared/pageRoutes.js +9 -0
  437. package/dist/shared/query.d.ts +166 -0
  438. package/dist/shared/query.js +1302 -0
  439. package/dist/shared/queryDates.d.ts +12 -0
  440. package/dist/shared/queryDates.js +101 -0
  441. package/dist/shared/queryOperands.d.ts +99 -0
  442. package/dist/shared/queryOperands.js +131 -0
  443. package/dist/shared/reads.d.ts +103 -0
  444. package/dist/shared/reads.js +154 -0
  445. package/dist/shared/recordLabel.d.ts +51 -0
  446. package/dist/shared/recordLabel.js +123 -0
  447. package/dist/shared/recordReadKeys.d.ts +30 -0
  448. package/dist/shared/recordReadKeys.js +51 -0
  449. package/dist/shared/recordReads.d.ts +268 -0
  450. package/dist/shared/recordReads.js +368 -0
  451. package/dist/shared/recordview.d.ts +198 -0
  452. package/dist/shared/recordview.js +356 -0
  453. package/dist/shared/referenceModel.d.ts +58 -0
  454. package/dist/shared/referenceModel.js +194 -0
  455. package/dist/shared/refusal.d.ts +64 -0
  456. package/dist/shared/refusal.js +144 -0
  457. package/dist/shared/relatedCreate.d.ts +30 -0
  458. package/dist/shared/relatedCreate.js +162 -0
  459. package/dist/shared/resourceHttp.d.ts +464 -0
  460. package/dist/shared/resourceHttp.js +1 -0
  461. package/dist/shared/richText.d.ts +85 -0
  462. package/dist/shared/richText.js +305 -0
  463. package/dist/shared/rollups.d.ts +143 -0
  464. package/dist/shared/rollups.js +64 -0
  465. package/dist/shared/rowScope.d.ts +72 -0
  466. package/dist/shared/rowScope.js +376 -0
  467. package/dist/shared/runtimeTables.d.ts +44 -0
  468. package/dist/shared/runtimeTables.js +79 -0
  469. package/dist/shared/schema.d.ts +523 -0
  470. package/dist/shared/schema.js +627 -0
  471. package/dist/shared/schematext.d.ts +38 -0
  472. package/dist/shared/schematext.js +101 -0
  473. package/dist/shared/scopeCatalog.d.ts +29 -0
  474. package/dist/shared/scopeCatalog.js +468 -0
  475. package/dist/shared/servedApp.d.ts +681 -0
  476. package/dist/shared/servedApp.js +45 -0
  477. package/dist/shared/sessionLease.d.ts +34 -0
  478. package/dist/shared/sessionLease.js +3 -0
  479. package/dist/shared/shape.d.ts +55 -0
  480. package/dist/shared/shape.js +168 -0
  481. package/dist/shared/slug.d.ts +1 -0
  482. package/dist/shared/slug.js +12 -0
  483. package/dist/shared/sortText.d.ts +14 -0
  484. package/dist/shared/sortText.js +26 -0
  485. package/dist/shared/sqlRelation.d.ts +4 -0
  486. package/dist/shared/sqlRelation.js +8 -0
  487. package/dist/shared/sqlsaved.d.ts +44 -0
  488. package/dist/shared/sqlsaved.js +63 -0
  489. package/dist/shared/stitchedPaths.d.ts +27 -0
  490. package/dist/shared/stitchedPaths.js +38 -0
  491. package/dist/shared/structuredFilter.d.ts +22 -0
  492. package/dist/shared/structuredFilter.js +1 -0
  493. package/dist/shared/subtitlePath.d.ts +20 -0
  494. package/dist/shared/subtitlePath.js +24 -0
  495. package/dist/shared/suggest.d.ts +18 -0
  496. package/dist/shared/suggest.js +59 -0
  497. package/dist/shared/tableSizing.d.ts +4 -0
  498. package/dist/shared/tableSizing.js +9 -0
  499. package/dist/shared/tagLists.d.ts +57 -0
  500. package/dist/shared/tagLists.js +34 -0
  501. package/dist/shared/textRules.d.ts +54 -0
  502. package/dist/shared/textRules.js +133 -0
  503. package/dist/shared/theme.d.ts +94 -0
  504. package/dist/shared/theme.js +800 -0
  505. package/dist/shared/themeColor.d.ts +34 -0
  506. package/dist/shared/themeColor.js +109 -0
  507. package/dist/shared/themeFaces.d.ts +30 -0
  508. package/dist/shared/themeFaces.js +42 -0
  509. package/dist/shared/themeInk.d.ts +20 -0
  510. package/dist/shared/themeInk.js +52 -0
  511. package/dist/shared/themeLooks.d.ts +9 -0
  512. package/dist/shared/themeLooks.js +572 -0
  513. package/dist/shared/themeParts.d.ts +26 -0
  514. package/dist/shared/themeParts.js +71 -0
  515. package/dist/shared/themePartsCheck.d.ts +21 -0
  516. package/dist/shared/themePartsCheck.js +91 -0
  517. package/dist/shared/themePresets.d.ts +85 -0
  518. package/dist/shared/themePresets.js +139 -0
  519. package/dist/shared/themeRecipeWords.d.ts +156 -0
  520. package/dist/shared/themeRecipeWords.js +156 -0
  521. package/dist/shared/themeRecipes.d.ts +213 -0
  522. package/dist/shared/themeRecipes.js +234 -0
  523. package/dist/shared/themeResolve.d.ts +55 -0
  524. package/dist/shared/themeResolve.js +52 -0
  525. package/dist/shared/themeTokens.d.ts +107 -0
  526. package/dist/shared/themeTokens.js +116 -0
  527. package/dist/shared/themeType.d.ts +38 -0
  528. package/dist/shared/themeType.js +205 -0
  529. package/dist/shared/unitWord.d.ts +4 -0
  530. package/dist/shared/unitWord.js +37 -0
  531. package/dist/shared/view.d.ts +195 -0
  532. package/dist/shared/view.js +987 -0
  533. package/dist/shared/viewLabels.d.ts +47 -0
  534. package/dist/shared/viewLabels.js +459 -0
  535. package/dist/shared/viewPaths.d.ts +248 -0
  536. package/dist/shared/viewPaths.js +294 -0
  537. package/dist/shared/viewtext.d.ts +57 -0
  538. package/dist/shared/viewtext.js +282 -0
  539. package/dist/shared/vocabulary.d.ts +71 -0
  540. package/dist/shared/vocabulary.js +249 -0
  541. package/dist/shared/weekStart.d.ts +7 -0
  542. package/dist/shared/weekStart.js +36 -0
  543. package/mark.svg +1 -0
  544. package/package.json +261 -4
  545. package/schema/model.d.ts +298 -0
  546. package/schema/theme.d.ts +587 -0
@@ -0,0 +1,1434 @@
1
+ /**
2
+ * SQLite adapter, on Node's built-in `node:sqlite`.
3
+ *
4
+ * SQLite comes first because it needs no server, which means the tool has a
5
+ * demo you can run in one command and a test target that does not depend on
6
+ * anything being installed. It is also the dialect that proves the model is
7
+ * general: its catalog is nothing like `information_schema`, so anything that
8
+ * works for both is unlikely to be accidentally Postgres-shaped.
9
+ */
10
+ import { statSync } from 'node:fs';
11
+ import { waitForLocks, assertIndependentWrite, writeToFile, prepareGuarded, transactionEpoch, storeEpoch } from './sqliteHandles.js';
12
+ import { captureScopeCatalog, mapScopeCatalog } from '../shared/scopeCatalog.js';
13
+ import { DatabaseSync } from 'node:sqlite';
14
+ import { isPromise, isProxy } from 'node:util/types';
15
+ import { ANSI_STYLE, buildWhere, clampLimit, clampOffset, boundStatement, looksWrappable, dedupe, refuseStacked, trimStatement, Refusal, WriteConflict, TransactionOutcomeUnknown, Serial, TransactionOperations, queueRecordLock, recordLockFilter, refuseAnalyze, countStatement, countEachStatement, countEachRows, countsTogether, orderClause, } from './adapter.js';
16
+ import { buildUpdateGuard } from './scoped/updateGuard.js';
17
+ import { createScopedReads } from './scoped/scopedReads.js';
18
+ import { scopedCommandInvoker } from './scoped/scopedCommandOperations.js';
19
+ import { createScopedTransaction, wholeWithin } from './scoped/scopedTransaction.js';
20
+ import { readSqliteScopeCatalog } from './scoped/sqliteScopeCatalog.js';
21
+ import { EpochCache } from './epochCache.js';
22
+ import { captureRowScope } from '../shared/rowScope.js';
23
+ import { isRuntimeTable } from '../shared/runtimeTables.js';
24
+ import { beginWriteCapture, capturesWrites, committedWriteCapture, finishWriteCapture } from './writeCapture.js';
25
+ import { lendStoreHost, } from './storeHost.js';
26
+ import { cancellation, statementCap, throwIfCancelled } from './statementControl.js';
27
+ import { refuseReservedText, reservedRefusal } from './reservedSql.js';
28
+ import { runInStatementProcess } from './sqliteStatementProcess.js';
29
+ import { heavyRelations, readInProcess, stalls } from './sqliteCostlyReads.js';
30
+ import { scaleArrays, scaleRows, scalesOf } from './sqliteDecimals.js';
31
+ import { compileView, compileViewCount } from '../shared/view.js';
32
+ import { roundTrip } from './dbLatency.js';
33
+ import { compileBreakdown, compileBreakdownCount } from '../shared/breakdown.js';
34
+ import { flagsFromDDL, vocabularyFromDDL } from '../shared/vocabulary.js';
35
+ import { coerceForColumn, columnKind, deleteRuleOf, } from '../shared/schema.js';
36
+ /** A generated column, virtual (2) or stored (3): the database writes it from its expression. */
37
+ const generated = (column) => column.hidden === 2 || column.hidden === 3;
38
+ const verifiedCommandAdapters = new WeakSet();
39
+ /** Identity-only native provenance; no caller can register a lookalike. */
40
+ export const isSqliteVerifiedCommandSource = (value) => !!value && typeof value === 'object' && !isProxy(value) && verifiedCommandAdapters.has(value);
41
+ export class SqliteAdapter {
42
+ file;
43
+ label;
44
+ dialect = 'sqlite';
45
+ db;
46
+ /** Opened lazily, only when an edit is actually applied. */
47
+ writeDb = null;
48
+ closed = false;
49
+ /* Every write on that handle, one at a time. See `Serial`. */
50
+ writes = new Serial();
51
+ writeIdentity;
52
+ introspectedVersions = new WeakMap();
53
+ /* Catalog answers a write transaction trusts — the journal's layout, the
54
+ scope catalog, a table's existence and its columns — kept under the
55
+ schema epoch the transaction read (transactionEpoch), so a warm command
56
+ asks the catalog nothing (epochCache.ts). */
57
+ catalogCache = new EpochCache();
58
+ /** The epoch the open write transaction read, on the handle it read it on. */
59
+ txEpoch;
60
+ constructor(file, label = file) {
61
+ this.file = file;
62
+ this.label = label;
63
+ // Read-only by intent as well as by API: this is a browsing tool, and a
64
+ // browsing tool that can write is a browsing tool that can destroy.
65
+ this.db = new DatabaseSync(file, { readOnly: true });
66
+ try {
67
+ // SQLite reports its resolved main filename; in-memory databases have
68
+ // no file and must not share a global ':memory:' queue.
69
+ const filename = this.db.prepare('PRAGMA database_list').get()?.file;
70
+ if (typeof filename === 'string' && filename) {
71
+ const info = statSync(filename, { bigint: true });
72
+ this.writeIdentity = `${info.dev}:${info.ino}`;
73
+ }
74
+ else
75
+ this.writeIdentity = Symbol('sqlite-memory');
76
+ waitForLocks(this.db);
77
+ }
78
+ catch (error) {
79
+ this.db.close();
80
+ throw error;
81
+ }
82
+ verifiedCommandAdapters.add(this);
83
+ /* The App stores this file may hold read and write through these (stores/appStores.ts). */
84
+ lendStoreHost(this, {
85
+ dialect: 'sqlite',
86
+ reader: () => this.db,
87
+ queueWrite: (work) => this.queueWrite(async () => work(this.writeHandle())),
88
+ attestedStore: (store, read, db) => this.attestedStore(store, read, db),
89
+ file: () => this.file,
90
+ closed: () => this.closed,
91
+ assertWriteIdentity: () => this.assertWriteIdentity(),
92
+ journalTransaction: (journal, run, options) => this.journalTransaction(journal, run, options),
93
+ });
94
+ }
95
+ queueWrite(work) {
96
+ // Check before even the handle-local queue: awaiting our own outer
97
+ // transaction would otherwise deadlock rather than yield to a sibling.
98
+ assertIndependentWrite();
99
+ this.assertWriteIdentity();
100
+ return this.writes.run(() => writeToFile(this.writeIdentity, async () => {
101
+ this.assertWriteIdentity();
102
+ return work();
103
+ }));
104
+ }
105
+ assertWriteIdentity() {
106
+ if (typeof this.writeIdentity !== 'string')
107
+ return;
108
+ try {
109
+ const info = statSync(this.file, { bigint: true });
110
+ if (`${info.dev}:${info.ino}` === this.writeIdentity)
111
+ return;
112
+ }
113
+ catch {
114
+ /* Removed/replaced paths cannot silently acquire a new writer. */
115
+ }
116
+ throw new Refusal('The SQLite file changed. Reopen the connection before writing.');
117
+ }
118
+ schemaVersion() {
119
+ if (this.closed)
120
+ throw new Refusal('This adapter is closed.');
121
+ this.assertWriteIdentity();
122
+ const version = this.db.prepare('PRAGMA main.schema_version').get()?.schema_version;
123
+ if (typeof version !== 'number' || !Number.isSafeInteger(version) || version < 0 || version > 2147483647) {
124
+ throw new Refusal('The SQLite schema version is unavailable.');
125
+ }
126
+ return version;
127
+ }
128
+ /** Opt-in physical-schema lifetime fence. No logical clone or another
129
+ * adapter's schema is accepted. Ordinary DDL invalidates it synchronously;
130
+ * malicious manual schema_version resets are outside this contract. */
131
+ captureSchemaLifetime(schema) {
132
+ const version = this.introspectedVersions.get(schema);
133
+ const current = () => {
134
+ if (version === undefined || this.introspectedVersions.get(schema) !== version || this.schemaVersion() !== version) {
135
+ throw new Refusal('The SQLite schema changed. Reopen the connection before reading.');
136
+ }
137
+ };
138
+ current();
139
+ return current;
140
+ }
141
+ async introspect() {
142
+ const version = this.schemaVersion();
143
+ const tableRows = this.db
144
+ .prepare(`SELECT name, type, sql FROM sqlite_master
145
+ WHERE type IN ('table','view') AND name NOT LIKE 'sqlite_%'
146
+ ORDER BY name`)
147
+ .all();
148
+ const tables = [];
149
+ const foreignKeys = [];
150
+ /* Approximate rows, the way SQLite can afford them: sqlite_stat1 where
151
+ ANALYZE has run — free, and as current as the last ANALYZE — and a
152
+ COUNT(*) otherwise, which on a local file is a b-tree scan the once-
153
+ per-open introspection can absorb. Views stay uncounted: counting one
154
+ runs its query, and a view is allowed to be expensive. */
155
+ const analyzed = new Map();
156
+ try {
157
+ const stats = this.db.prepare('SELECT tbl, stat FROM sqlite_stat1 WHERE stat IS NOT NULL').all();
158
+ for (const s of stats) {
159
+ // The first number of `stat` is the row estimate; a partial index
160
+ // reports fewer, so the largest claim across indexes wins.
161
+ const n = Number(String(s.stat).split(' ')[0]);
162
+ if (Number.isFinite(n))
163
+ analyzed.set(s.tbl, Math.max(analyzed.get(s.tbl) ?? 0, n));
164
+ }
165
+ }
166
+ catch {
167
+ /* No sqlite_stat1 means no ANALYZE yet, not an error. */
168
+ }
169
+ /* A COUNT(*) per table is a full scan per table, on the synchronous
170
+ driver, at connect and at every refresh: on a multi-gigabyte file that
171
+ was seconds of the whole server frozen for a number the sidebar shows
172
+ in grey. Above a quarter gigabyte the count is left unknown unless
173
+ ANALYZE has run — sqlite_stat1 is read above and costs nothing. */
174
+ let bigFile = false;
175
+ try {
176
+ bigFile = statSync(this.file).size > 256 * 1024 * 1024;
177
+ }
178
+ catch {
179
+ /* An in-memory or unusual path: count as before. */
180
+ }
181
+ const approxRowsOf = (name, type) => {
182
+ if (type !== 'table')
183
+ return undefined;
184
+ const known = analyzed.get(name);
185
+ if (known !== undefined)
186
+ return known;
187
+ if (bigFile)
188
+ return undefined;
189
+ try {
190
+ const row = this.db.prepare(`SELECT COUNT(*) AS n FROM ${ANSI_STYLE.quote(name)}`).get();
191
+ return row.n;
192
+ }
193
+ catch {
194
+ // A table the catalog lists can still refuse a scan (a broken
195
+ // virtual table, say); no count beats no schema.
196
+ return undefined;
197
+ }
198
+ };
199
+ for (const t of tableRows) {
200
+ /* table_xinfo, not table_info: table_info leaves out generated columns,
201
+ so a table's computed figures were missing from every list and form.
202
+ A virtual table's hidden columns (1) are its module's plumbing. */
203
+ const cols = this.db.prepare(`PRAGMA table_xinfo(${ANSI_STYLE.quote(t.name)})`).all().filter((c) => c.hidden !== 1);
204
+ const indexes = this.indexesOf(t.name);
205
+ const pk = cols.filter((c) => c.pk > 0);
206
+ // Rowid aliases have no primary-key index. This excludes WITHOUT ROWID
207
+ // and the inline INTEGER PRIMARY KEY DESC exception without parsing DDL.
208
+ const identity = t.type === 'table' && indexes && !indexes.some((i) => i.origin === 'pk') && pk.length === 1 && /^INTEGER$/i.test(pk[0].type.trim())
209
+ ? pk[0].name
210
+ : undefined;
211
+ const fks = this.db.prepare(`PRAGMA foreign_key_list(${ANSI_STYLE.quote(t.name)})`).all();
212
+ /* PRAGMA returns one row per column of a composite key, grouped by `id`.
213
+ Collapsing them here means the model never has to know that. */
214
+ const byId = new Map();
215
+ for (const fk of fks) {
216
+ const list = byId.get(fk.id) ?? [];
217
+ list.push(fk);
218
+ byId.set(fk.id, list);
219
+ }
220
+ for (const [id, group] of byId) {
221
+ const ordered = [...group].sort((a, b) => a.seq - b.seq);
222
+ foreignKeys.push({
223
+ name: `${t.name}_fk_${id}`,
224
+ onDelete: deleteRuleOf(ordered[0].on_delete),
225
+ from: { table: t.name, columns: ordered.map((f) => f.from) },
226
+ // A null `to` means the FK targets the referenced table's primary
227
+ // key implicitly, which SQLite allows and reports as null.
228
+ to: {
229
+ table: ordered[0].table,
230
+ columns: ordered.map((f) => f.to ?? 'rowid'),
231
+ },
232
+ });
233
+ }
234
+ const refByColumn = new Map();
235
+ for (const [, group] of byId) {
236
+ for (const f of group) {
237
+ refByColumn.set(f.from, { table: f.table, column: f.to ?? 'rowid' });
238
+ }
239
+ }
240
+ /* SQLite keeps the `CREATE TABLE` text it was given, checks and all, so
241
+ the vocabulary is right there in the statement — no second query. */
242
+ const vocabulary = t.sql
243
+ ? vocabularyFromDDL(t.sql, cols.map((c) => c.name))
244
+ : new Map();
245
+ /* An integer a CHECK pins to 0 and 1 is SQLite's boolean. */
246
+ const flags = t.sql
247
+ ? flagsFromDDL(t.sql, cols.map((c) => c.name))
248
+ : new Set();
249
+ /* An untyped column of a *table* really does have BLOB affinity —
250
+ `CREATE TABLE t (a)` is a column SQLite will happily store anything
251
+ in, and saying BLOB is saying what the database will do with it.
252
+
253
+ A *view* is a different question. `PRAGMA table_info` reports no type
254
+ for a computed column because there is no declaration to report, not
255
+ because the values are blobs: `COUNT(*)` came back as BLOB and so did
256
+ `SUM(total)`, which is a wrong answer to "what does this column hold"
257
+ rather than an unhelpful one. Left empty instead — `columnKind`
258
+ already reads that as unknown, exactly as it read BLOB, so nothing
259
+ renders differently; the tool simply stops claiming a type it was
260
+ never told. */
261
+ const untyped = t.type === 'view' ? '' : 'BLOB';
262
+ const columns = cols.map((c) => ({
263
+ name: c.name,
264
+ type: c.type || untyped,
265
+ nullable: c.notnull === 0 && c.name !== identity,
266
+ primaryKey: c.pk > 0,
267
+ references: refByColumn.get(c.name),
268
+ default: c.dflt_value ?? undefined,
269
+ identity: c.name === identity ? 'by-default' : undefined,
270
+ // The database computes a generated column; nothing may write it.
271
+ ...(generated(c) ? { writable: false } : {}),
272
+ allowed: vocabulary.get(c.name),
273
+ ...(flags.has(c.name) && columnKind(c.type) === 'number' ? { flag: true } : {}),
274
+ }));
275
+ tables.push({
276
+ name: t.name,
277
+ schema: '',
278
+ id: t.name,
279
+ columns,
280
+ approxRows: approxRowsOf(t.name, t.type),
281
+ isView: t.type === 'view',
282
+ indexed: t.type === 'view' ? [] : this.leadingIndexColumns(columns, indexes),
283
+ ...(() => {
284
+ const unique = t.type === 'view' ? [] : this.uniqueColumns(indexes);
285
+ return unique.length ? { unique } : {};
286
+ })(),
287
+ ...(() => {
288
+ const pairs = t.type === 'view' ? [] : this.uniquePairs(indexes);
289
+ return pairs.length ? { uniquePairs: pairs } : {};
290
+ })(),
291
+ });
292
+ }
293
+ const schema = {
294
+ label: this.label,
295
+ dialect: this.dialect,
296
+ tables,
297
+ foreignKeys,
298
+ readAt: new Date().toISOString(),
299
+ };
300
+ if (this.schemaVersion() !== version)
301
+ throw new Refusal('The SQLite schema changed during introspection.');
302
+ this.introspectedVersions.set(schema, version);
303
+ /* The registry introspects on connect and keeps what it read; the view,
304
+ breakdown and read paths below resolve against `cachedSchema`. Seeding
305
+ it here is what stops the first of them from reading the whole catalog
306
+ a second time — on a wide file that was a statement per table and
307
+ index, on the synchronous driver, with the server frozen for it. */
308
+ this.cachedSchema = schema;
309
+ return schema;
310
+ }
311
+ indexesOf(table) {
312
+ try {
313
+ return this.db.prepare(`PRAGMA index_list(${ANSI_STYLE.quote(table)})`).all();
314
+ }
315
+ catch {
316
+ return undefined;
317
+ }
318
+ }
319
+ /**
320
+ * The columns a unique index makes unique alone. `PRAGMA index_list`
321
+ * reports UNIQUE constraints as the indexes SQLite made for them, so one
322
+ * reading covers both; a partial index proves nothing about the rows it
323
+ * leaves out, and an expression column has no name.
324
+ */
325
+ uniqueColumns(indexes) {
326
+ const unique = new Set();
327
+ try {
328
+ for (const index of indexes ?? []) {
329
+ if (!index.unique || index.partial)
330
+ continue;
331
+ const info = this.db.prepare(`PRAGMA index_info(${ANSI_STYLE.quote(index.name)})`).all();
332
+ if (info.length === 1 && info[0].name)
333
+ unique.add(info[0].name);
334
+ }
335
+ }
336
+ catch {
337
+ // As leadingIndexColumns: nothing proven is the safe answer.
338
+ }
339
+ return [...unique];
340
+ }
341
+ /** The column pairs a unique index of exactly two named columns keeps
342
+ * unique together, read as uniqueColumns reads one alone. */
343
+ uniquePairs(indexes) {
344
+ const pairs = [];
345
+ try {
346
+ for (const index of indexes ?? []) {
347
+ if (!index.unique || index.partial)
348
+ continue;
349
+ const info = this.db.prepare(`PRAGMA index_info(${ANSI_STYLE.quote(index.name)})`).all();
350
+ if (info.length === 2 && info[0].name && info[1].name)
351
+ pairs.push([info[0].name, info[1].name]);
352
+ }
353
+ }
354
+ catch {
355
+ return [];
356
+ }
357
+ return pairs;
358
+ }
359
+ /**
360
+ * The columns an ORDER BY can lean on.
361
+ *
362
+ * `PRAGMA index_list` reports every index including the implicit ones
363
+ * SQLite creates for UNIQUE and for a non-integer primary key; `index_info`
364
+ * gives their columns in order and only the first of each is useful for
365
+ * sorting.
366
+ *
367
+ * `INTEGER PRIMARY KEY` is special and has no index at all — it *is* the
368
+ * rowid, so ordering by it is free — which is exactly the case a naive
369
+ * index scan would report as unindexed and then refuse to sort by.
370
+ */
371
+ leadingIndexColumns(columns, indexes) {
372
+ const leading = new Set();
373
+ const rowidAlias = columns.find((c) => c.identity);
374
+ if (rowidAlias)
375
+ leading.add(rowidAlias.name);
376
+ try {
377
+ for (const index of indexes ?? []) {
378
+ const info = this.db.prepare(`PRAGMA index_info(${ANSI_STYLE.quote(index.name)})`).all();
379
+ const first = info.find((c) => c.seqno === 0)?.name;
380
+ if (first)
381
+ leading.add(first);
382
+ }
383
+ }
384
+ catch {
385
+ /* A virtual table, or one that vanished between listing and reading.
386
+ Reporting no indexes is the safe answer: it only ever costs a
387
+ default sort falling back to the key. */
388
+ }
389
+ return [...leading];
390
+ }
391
+ async query(request) {
392
+ const result = await this.queryOn(request, this.db, true);
393
+ await this.turn();
394
+ return result;
395
+ }
396
+ async scopeReads(input) {
397
+ const scope = captureRowScope(input);
398
+ const schema = await this.schema();
399
+ return createScopedReads(schema, scope, ANSI_STYLE, async (compiled, hint) => {
400
+ await roundTrip();
401
+ const started = performance.now();
402
+ const statement = this.db.prepare(compiled.text);
403
+ statement.setReadBigInts(true);
404
+ const described = statement.columns();
405
+ const rows = scaleRows(statement.all(...sqliteParams(compiled.params)), described.map((c) => c.name), scalesOf(described));
406
+ return {
407
+ columns: described.map((column) => column.name),
408
+ rows: rows.map(hint?.binary ? withBytes : toJsonSafe),
409
+ ms: Math.round(performance.now() - started),
410
+ sql: { text: compiled.text, params: compiled.params },
411
+ };
412
+ });
413
+ }
414
+ async queryOn(request, db, read = false) {
415
+ const table = read ? this.readTable(request.table) : this.assertTable(request.table, db);
416
+ const where = buildWhere(request.filter, ANSI_STYLE);
417
+ const cols = request.columns?.length ? request.columns.map((c) => ANSI_STYLE.quote(c)).join(', ') : '*';
418
+ /* Every read is bounded. There is no code path that reaches the database
419
+ without a LIMIT, including one that forgot to ask for one. Limit and
420
+ offset are read before the first await: a scoped transaction snapshots
421
+ its operation input, and the schema wait below must not outlive it. */
422
+ const limit = clampLimit(request.limit);
423
+ const offset = clampOffset(request.offset);
424
+ /* A sort by declared vocabulary needs the catalog, but an ordinary read must
425
+ not depend on the whole catalog introspecting on this handle (a view over
426
+ a function only the writer registered cannot be prepared here). Without
427
+ it the column sorts by its value. */
428
+ const orderBy = request.orderBy;
429
+ const meta = orderBy?.length ? (await this.schema().catch(() => undefined))?.tables.find((t) => t.id === request.table) : undefined;
430
+ const order = orderClause(orderBy, meta, ANSI_STYLE, 1);
431
+ const text = `SELECT ${cols} FROM ${ANSI_STYLE.quote(table)}${where.text}${order.text} LIMIT ? OFFSET ?`;
432
+ const params = [...where.params, ...order.params, limit, offset];
433
+ const started = performance.now();
434
+ /* Integers past 2^53 are read as BigInt rather than throwing; valueSafe
435
+ turns the safe ones back into numbers and keeps the rest as strings. */
436
+ const rows = await this.readRows(text, params, false, db);
437
+ const ms = Math.round(performance.now() - started);
438
+ return {
439
+ columns: rows.length ? Object.keys(rows[0]) : (request.columns ?? []),
440
+ rows: rows.map(request.binary ? withBytes : toJsonSafe),
441
+ ms,
442
+ sql: { text, params },
443
+ };
444
+ }
445
+ /**
446
+ * Run a compiled view.
447
+ *
448
+ * Everything hard happened in `compileView`: paths resolved against the
449
+ * catalog, joins planned with collision-free aliases, values reduced to
450
+ * bound parameters. What is left here is picking the dialect's style and
451
+ * handing the statement to the driver — which is the point. An adapter that
452
+ * built its own joins would be an adapter that could get LEFT wrong on its
453
+ * own.
454
+ */
455
+ async runView(view, offset = 0) {
456
+ const schema = await this.schema();
457
+ /* Every read is bounded, views included: `compileView` runs the limit
458
+ through `clampLimit` and emits LIMIT unconditionally, so there is no
459
+ shape of ViewDef that reaches the database without one. */
460
+ const compiled = compileView(schema, view, ANSI_STYLE, offset);
461
+ const started = performance.now();
462
+ const rows = await this.readRows(compiled.text, compiled.params);
463
+ const ms = Math.round(performance.now() - started);
464
+ await this.turn();
465
+ return {
466
+ // From the compiled column list, not the first row: an empty result
467
+ // still has to render its headers rather than collapsing to nothing.
468
+ columns: compiled.columns.map((c) => c.name),
469
+ rows: rows.map(toJsonSafe),
470
+ ms,
471
+ sql: { text: compiled.text, params: compiled.params },
472
+ resolved: compiled.columns,
473
+ joins: compiled.joins,
474
+ };
475
+ }
476
+ async runBreakdown(breakdown, offset = 0) {
477
+ const schema = await this.schema();
478
+ const compiled = compileBreakdown(schema, breakdown, ANSI_STYLE, offset);
479
+ const started = performance.now();
480
+ const rows = await this.readRows(compiled.text, compiled.params, true);
481
+ await this.turn();
482
+ return {
483
+ columns: compiled.columns.map((c) => c.name),
484
+ rows: rows.map(toJsonSafe),
485
+ ms: Math.round(performance.now() - started),
486
+ sql: { text: compiled.text, params: compiled.params },
487
+ resolved: compiled.columns,
488
+ };
489
+ }
490
+ async countBreakdown(breakdown, limit) {
491
+ const compiled = compileBreakdownCount(await this.schema(), breakdown, ANSI_STYLE, undefined, limit);
492
+ const [row] = await this.readRows(compiled.text, compiled.params, true);
493
+ await this.turn();
494
+ return Number(row?.n ?? 0);
495
+ }
496
+ async countView(view, limit) {
497
+ const compiled = compileViewCount(await this.schema(), view, ANSI_STYLE, undefined, limit);
498
+ const [row] = await this.readRows(compiled.text, compiled.params, true);
499
+ await this.turn();
500
+ return Number(row?.n ?? 0);
501
+ }
502
+ /**
503
+ * EXPLAIN QUERY PLAN, indented the way the tree nests.
504
+ *
505
+ * Prepared and read, never executed: the rows come from the EXPLAIN
506
+ * statement itself, so even a write handed here is only ever planned — on
507
+ * the read-only handle, at that.
508
+ */
509
+ async explainSql(request) {
510
+ const stacked = refuseStacked(request.text, 'sqlite');
511
+ if (stacked)
512
+ throw new Error(stacked);
513
+ const analyze = refuseAnalyze(request.text);
514
+ if (analyze)
515
+ throw new Error(analyze);
516
+ if (request.refuseReserved)
517
+ refuseReservedText(request.text, 'sqlite');
518
+ const started = performance.now();
519
+ const planned = `EXPLAIN QUERY PLAN ${trimStatement(request.text)}`;
520
+ const rows = (request.refuseReserved ? prepareGuarded(this.db, planned) : this.db.prepare(planned)).all(...sqliteParams(request.params ?? []));
521
+ /* The driver hands back a flat list with parent pointers; the indentation
522
+ is the tree, said in text. */
523
+ const depth = new Map();
524
+ const plan = rows.map((r) => {
525
+ const d = (depth.get(r.parent) ?? 0) + 1;
526
+ depth.set(r.id, d);
527
+ return `${' '.repeat(d - 1)}${r.detail}`;
528
+ });
529
+ await this.turn();
530
+ return { plan, ms: Math.round(performance.now() - started) };
531
+ }
532
+ /**
533
+ * SQLite keeps no column statistics worth reading — sqlite_stat1 knows
534
+ * index selectivity, not null shares — so this looks: one aggregate pass
535
+ * for counts and edges, then one small GROUP BY per column that provably
536
+ * repeats. `source` tells the caller what the look cost.
537
+ *
538
+ * Above `scanAtMost` rows it stops scanning and samples, because
539
+ * node:sqlite is synchronous and a full pass over a ten-million-row table
540
+ * holds the whole process for seconds. Every k-th rowid rather than the
541
+ * first N: insertion order is not a sample, it is a history, and a min
542
+ * taken from the first month of data would be presented as the table's.
543
+ * A view — or a WITHOUT ROWID table, which refuses the arithmetic — falls
544
+ * back to the first N with the same honest label, because running a view
545
+ * twice to randomise it would cost what sampling exists to avoid.
546
+ */
547
+ async profile(table, limits) {
548
+ const name = this.readTable(table);
549
+ const meta = (await this.schema()).tables.find((t) => t.id === name);
550
+ if (!meta)
551
+ throw new Error(`No table or view called "${name}" in this database.`);
552
+ const q = (c) => ANSI_STYLE.quote(c);
553
+ const scanAtMost = limits?.scanAtMost ?? 200_000;
554
+ const sampleSize = limits?.sampleSize ?? 100_000;
555
+ const started = performance.now();
556
+ /* COUNT(*) walks an index, not the data pages — cheap enough to decide
557
+ with, and the number the answer reports either way. */
558
+ const total = Number(this.db.prepare(`SELECT COUNT(*) AS n FROM ${q(name)}`).get().n ?? 0);
559
+ let from = q(name);
560
+ let source = 'scan';
561
+ if (total > scanAtMost) {
562
+ source = 'sample';
563
+ const k = Math.max(2, Math.ceil(total / sampleSize));
564
+ try {
565
+ this.db.prepare(`SELECT rowid FROM ${q(name)} LIMIT 1`).get();
566
+ /* Hashed, not bare `rowid % k`: data is allowed to be periodic —
567
+ every tenth row null is a perfectly ordinary import — and a bare
568
+ modulo resonates with exactly that, sampling only the pattern.
569
+ Knuth's multiplicative constant scatters the rowids first, so the
570
+ stride cannot alias with anything the rows themselves repeat. */
571
+ from = `(SELECT * FROM ${q(name)} WHERE ((rowid * 2654435761) & 0x7fffffff) % ${k} = 0)`;
572
+ }
573
+ catch {
574
+ from = `(SELECT * FROM ${q(name)} LIMIT ${sampleSize})`;
575
+ }
576
+ }
577
+ const aggs = meta.columns.map((c, i) => `COUNT(${q(c.name)}) AS n${i}, COUNT(DISTINCT ${q(c.name)}) AS d${i}, ` +
578
+ `MIN(${q(c.name)}) AS mn${i}, MAX(${q(c.name)}) AS mx${i}`);
579
+ const profileStmt = this.db.prepare(`SELECT COUNT(*) AS total${aggs.length ? `, ${aggs.join(', ')}` : ''} FROM ${from}`);
580
+ /* MIN/MAX over a 64-bit id column would throw on read without this;
581
+ valueSafe normalises what comes back. */
582
+ profileStmt.setReadBigInts(true);
583
+ const row = profileStmt.get();
584
+ /* The denominator the fractions are honest against: the rows actually
585
+ read, which is the whole table only when nothing was skipped. */
586
+ const basis = Number(row.total ?? 0);
587
+ const columns = meta.columns.map((c, i) => {
588
+ const nonNull = Number(row[`n${i}`] ?? 0);
589
+ const distinct = Number(row[`d${i}`] ?? 0);
590
+ const profiled = {
591
+ column: c.name,
592
+ type: c.type,
593
+ nullFrac: basis ? (basis - nonNull) / basis : 0,
594
+ distinct,
595
+ min: valueSafe(row[`mn${i}`]) ?? undefined,
596
+ max: valueSafe(row[`mx${i}`]) ?? undefined,
597
+ };
598
+ // Only where a value provably repeats; a unique column's most common
599
+ // value is noise.
600
+ if (nonNull > 0 && distinct < nonNull) {
601
+ const topStmt = this.db.prepare(`SELECT ${q(c.name)} AS v, COUNT(*) AS n FROM ${from} ` +
602
+ `WHERE ${q(c.name)} IS NOT NULL GROUP BY ${q(c.name)} ORDER BY n DESC, v LIMIT 5`);
603
+ topStmt.setReadBigInts(true);
604
+ const top = topStmt.all();
605
+ profiled.common = top.map((t) => ({ value: valueSafe(t.v), frac: Number(t.n) / basis }));
606
+ }
607
+ return profiled;
608
+ });
609
+ await this.turn();
610
+ return {
611
+ table: name,
612
+ rows: total,
613
+ columns,
614
+ source,
615
+ ...(source === 'sample' ? { sampled: basis } : {}),
616
+ ms: Math.round(performance.now() - started),
617
+ };
618
+ }
619
+ /**
620
+ * After each statement a call runs: a turn of the event loop — a cancel
621
+ * can only be read when there is one, and node:sqlite gives none while a
622
+ * statement runs — then a stop if the call was cancelled, before its next
623
+ * statement starts. After, not before, so a read takes its arguments as it
624
+ * is called. A read nobody can cancel does not pay for it.
625
+ */
626
+ async turn() {
627
+ const signal = cancellation();
628
+ if (!signal)
629
+ return;
630
+ await new Promise((resolve) => setImmediate(resolve));
631
+ throwIfCancelled(signal);
632
+ }
633
+ /** Introspection is cheap here but not free; the view path re-uses it. */
634
+ cachedSchema = null;
635
+ async schema() {
636
+ if (!this.cachedSchema)
637
+ this.cachedSchema = await this.introspect();
638
+ return this.cachedSchema;
639
+ }
640
+ async count(table, filter, limit) {
641
+ const counted = await this.countOn(table, filter, this.db, limit, true);
642
+ await this.turn();
643
+ return counted;
644
+ }
645
+ async countEach(items, limit) {
646
+ const relations = items.map((item) => ({ relation: ANSI_STYLE.quote(this.readTable(item.table)), filter: item.filter }));
647
+ const statement = countEachStatement(relations, ANSI_STYLE, { limit, together: countsTogether(this.cachedSchema, items, limit) });
648
+ const rows = await this.readRows(statement.text, statement.params, true);
649
+ await this.turn();
650
+ return countEachRows(rows, items.length);
651
+ }
652
+ async countOn(table, filter, db, limit, read = false) {
653
+ const name = read ? this.readTable(table) : this.assertTable(table, db);
654
+ const where = buildWhere(filter, ANSI_STYLE);
655
+ const text = countStatement(`${ANSI_STYLE.quote(name)}${where.text}`, limit);
656
+ const [row] = await this.readRows(text, where.params, true, db);
657
+ return Number(row?.n ?? 0);
658
+ }
659
+ /**
660
+ * One compiled read's rows, BigInts and all. A read that could hold the
661
+ * server's one thread — a count, a filter or a sort over a large table —
662
+ * runs in the statement process instead (sqliteCostlyReads.ts); one on the
663
+ * write handle, inside a transaction, stays where its transaction is.
664
+ */
665
+ async readRows(text, params, aggregate = false, db = this.db) {
666
+ await roundTrip();
667
+ const bound = sqliteParams(params);
668
+ if (db === this.db && typeof this.writeIdentity === 'string' && stalls(db, text, bound, heavyRelations(this.cachedSchema), aggregate))
669
+ return readInProcess(this.file, text, bound);
670
+ const statement = db.prepare(text);
671
+ statement.setReadBigInts(true);
672
+ const described = statement.columns();
673
+ return scaleRows(statement.all(...bound), described.map((c) => c.name), scalesOf(described));
674
+ }
675
+ /**
676
+ * SQLite stores the original statement text, so this is the real thing —
677
+ * whitespace, comments, check constraints and all — not a reconstruction.
678
+ * Indexes are appended because they are separate statements in
679
+ * `sqlite_master` and are usually the first thing you want after the shape.
680
+ */
681
+ async ddl(table) {
682
+ const name = this.assertTable(table);
683
+ const row = this.db.prepare(`SELECT sql FROM sqlite_master WHERE name = ? AND type IN ('table','view')`).get(name);
684
+ if (!row?.sql)
685
+ return undefined;
686
+ const indexes = this.db
687
+ .prepare(`SELECT sql FROM sqlite_master
688
+ WHERE type = 'index' AND tbl_name = ? AND sql IS NOT NULL
689
+ ORDER BY name`)
690
+ .all(name);
691
+ const parts = [`${row.sql.trim()};`];
692
+ if (indexes.length)
693
+ parts.push('', ...indexes.map((i) => `${i.sql.trim()};`));
694
+ return parts.join('\n');
695
+ }
696
+ /**
697
+ * The write path, on its own handle.
698
+ *
699
+ * `this.db` is opened read-only and stays that way for the life of the
700
+ * process, so browsing can never write no matter what the UI believes. A
701
+ * second handle is opened the first time an edit is actually applied.
702
+ */
703
+ async update(request) {
704
+ // A table that keeps a history is written with its row, or not at all.
705
+ if (this.recordsHistory(request))
706
+ return this.transaction((tx) => tx.update(request));
707
+ return this.queueWrite(() => this.updateNow(request));
708
+ }
709
+ async updateNow(request, read = this.db, scoped = false) {
710
+ const table = this.assertTable(request.table, read);
711
+ /* A view with an INSTEAD OF trigger takes an UPDATE, and the trigger
712
+ decides what changes. Refused as the other dialects refuse it: what the
713
+ editor showed and what changed are then two different objects. Its
714
+ columns carry no key either, but that refusal named the wrong reason. */
715
+ if (this.isView(table, read))
716
+ throw new Refusal(`${table} is a view, so no single row can be identified.`);
717
+ const meta = this.tableInfo(table, read);
718
+ const pk = meta.filter((c) => c.pk > 0).map((c) => c.name);
719
+ if (!pk.length)
720
+ throw new Refusal(`${table} has no primary key, so no single row can be identified.`);
721
+ const keyColumns = Object.keys(request.key);
722
+ /* The key must be the whole primary key and nothing else. Accepting a
723
+ partial key would let a two-column key match a whole group of rows,
724
+ and accepting extra columns would let the caller widen the match. */
725
+ const same = keyColumns.length === pk.length && pk.every((c) => keyColumns.includes(c));
726
+ if (!same) {
727
+ throw new Refusal(`Updating ${table} needs exactly its primary key (${pk.join(', ')}).`);
728
+ }
729
+ const entries = Object.entries(request.values ?? {});
730
+ if (!entries.length)
731
+ throw new Refusal('Nothing to update.');
732
+ const applied = {};
733
+ for (const [column, raw] of entries) {
734
+ const declared = meta.find((c) => c.name === column);
735
+ if (!declared)
736
+ throw new Refusal(`${table} has no column called "${column}".`);
737
+ if (pk.includes(column)) {
738
+ // Editing a key in place silently repoints every row that referenced
739
+ // it, or fails on the constraint. Neither belongs behind an editor.
740
+ throw new Refusal(`"${column}" is part of the primary key and cannot be edited here.`);
741
+ }
742
+ if (generated(declared))
743
+ throw new Refusal(`"${column}" is generated by the database and cannot be written.`);
744
+ /* Coerced against the declared type, because everything typed into a
745
+ text box arrives as a string — including "17901" for a REAL column. */
746
+ applied[column] = coerceForColumn(declared.type || '', raw);
747
+ if (applied[column] === null && declared.notnull === 1) {
748
+ throw new Refusal(`"${column}" is NOT NULL and cannot be emptied.`);
749
+ }
750
+ }
751
+ const filter = {
752
+ groups: [pk.map((column) => ({ column, op: '=', value: request.key[column] }))],
753
+ };
754
+ /* Outside a transaction the count runs first, on the read handle, so the
755
+ check that decides whether to write cannot itself write. Inside one it
756
+ is the UPDATE's own count: the key is the whole primary key, so it
757
+ matches one row or none, and a refusal rolls the transaction back. */
758
+ const transactional = read !== this.db;
759
+ const matches = transactional ? 1 : await this.count(table, filter);
760
+ const refuseMatches = (count) => {
761
+ if (request.expected !== undefined && count === 0)
762
+ throw new WriteConflict();
763
+ throw new Refusal(`Refused: that key matches ${count} rows, not 1. An update is only allowed when it is certain which row it changes.`);
764
+ };
765
+ if (matches !== 1)
766
+ refuseMatches(matches);
767
+ const where = buildWhere(filter, ANSI_STYLE);
768
+ const assignments = Object.keys(applied)
769
+ .map((c) => `${ANSI_STYLE.quote(c)} = ?`)
770
+ .join(', ');
771
+ const guard = buildUpdateGuard(request, Object.fromEntries(meta.map((column) => [column.name, column.type])), ANSI_STYLE, 'sqlite', 1);
772
+ const text = `UPDATE ${scoped ? 'OR ABORT ' : ''}${ANSI_STYLE.quote(table)} SET ${assignments}${where.text}${guard.text}`;
773
+ const params = [...Object.values(applied), ...where.params, ...guard.params];
774
+ const write = read === this.db ? this.writeHandle() : read;
775
+ const result = write.prepare(text).run(...sqliteParams(params));
776
+ if (request.expected !== undefined && Number(result.changes) === 0)
777
+ throw new WriteConflict();
778
+ if (transactional && Number(result.changes) !== 1)
779
+ refuseMatches(Number(result.changes));
780
+ return { affected: Number(result.changes), applied, sql: { text, params } };
781
+ }
782
+ /**
783
+ * Add one row.
784
+ *
785
+ * The guards are `update`'s where they apply — real columns, coerced
786
+ * values, a NOT NULL that cannot be handed null — but the key rules
787
+ * invert: a primary key may be supplied (currency.code is one somebody
788
+ * types) or left for the database to assign. What is deliberately *not*
789
+ * pre-checked is completeness: a missing NOT NULL column with no default
790
+ * is the database's refusal to make, and its message names the column
791
+ * better than a paraphrase would.
792
+ *
793
+ * `RETURNING *` rather than last_insert_rowid plus a re-read: one
794
+ * statement returns the row as stored — defaults filled, the assigned key
795
+ * included — with no window for another write to land in between.
796
+ */
797
+ async insert(request) {
798
+ if (this.recordsHistory(request))
799
+ return this.transaction((tx) => tx.insert(request));
800
+ return this.queueWrite(() => this.insertNow(request));
801
+ }
802
+ async insertNow(request, read = this.db, scoped = false) {
803
+ const table = this.assertTable(request.table, read);
804
+ const meta = this.tableInfo(table, read);
805
+ const entries = Object.entries(request.values ?? {});
806
+ const applied = {};
807
+ for (const [column, raw] of entries) {
808
+ const declared = meta.find((c) => c.name === column);
809
+ if (!declared)
810
+ throw new Refusal(`${table} has no column called "${column}".`);
811
+ if (generated(declared))
812
+ throw new Refusal(`"${column}" is generated by the database and cannot be written.`);
813
+ applied[column] = coerceForColumn(declared.type || '', raw);
814
+ if (applied[column] === null && declared.notnull === 1) {
815
+ throw new Refusal(`"${column}" is NOT NULL and cannot be null. Leave it out to use its default, if it has one.`);
816
+ }
817
+ }
818
+ const columns = Object.keys(applied);
819
+ // A schema-level ON CONFLICT REPLACE must never turn creation/restoration
820
+ // into deletion of an existing row. This statement overrides that policy.
821
+ const text = `INSERT OR ABORT INTO ${ANSI_STYLE.quote(table)}` +
822
+ (columns.length
823
+ ? ` (${columns.map((c) => ANSI_STYLE.quote(c)).join(', ')}) VALUES (${columns.map(() => '?').join(', ')})`
824
+ : ' DEFAULT VALUES') +
825
+ ' RETURNING *';
826
+ const params = Object.values(applied);
827
+ const write = read === this.db ? this.writeHandle() : read;
828
+ const inserted = write.prepare(text);
829
+ /* The RETURNING row can carry a 64-bit id — most often exactly here, on a
830
+ table whose key is one — so it is read as BigInt rather than throwing,
831
+ and normalised on the way out like every other read. */
832
+ inserted.setReadBigInts(true);
833
+ const described = inserted.columns();
834
+ const row = scaleRows([inserted.get(...sqliteParams(params))], described.map((c) => c.name), scalesOf(described))[0];
835
+ const safe = toJsonSafe(row);
836
+ const pk = meta.filter((c) => c.pk > 0).map((c) => c.name);
837
+ if (scoped &&
838
+ pk.some((column) => {
839
+ const value = row[column];
840
+ return !(typeof value === 'string' ||
841
+ (typeof value === 'number' && Number.isFinite(value)) ||
842
+ (typeof value === 'bigint' && value >= -9007199254740991n && value <= 9007199254740991n));
843
+ }))
844
+ throw new Refusal('The stored key cannot be checked in this row scope.');
845
+ const key = Object.fromEntries(pk.map((c) => [c, safe[c]]));
846
+ return { key, row: safe, applied, sql: { text, params } };
847
+ }
848
+ /**
849
+ * Run a statement the user wrote.
850
+ *
851
+ * The read handle is opened `readOnly: true` at the file level, which SQLite
852
+ * enforces in the engine — there is no session setting for a statement to
853
+ * reset, so unlike the other two adapters no transaction wrapper is needed
854
+ * to make the guarantee real.
855
+ *
856
+ * What SQLite does not have is a statement timeout, and `node:sqlite` is
857
+ * synchronous with no interrupt, so a pathological statement on this handle
858
+ * blocks the whole process rather than one request. The LIMIT wrap covers
859
+ * the ordinary case — a streaming SELECT stops once it has enough rows — but
860
+ * not an aggregate over a cross join, which has to finish before it returns
861
+ * anything. So a read whose caller can stop it — one run inside
862
+ * `withCancellation`, as every MCP tool call is — runs in a process of its
863
+ * own instead, which its cap or a cancel kills (sqliteStatementProcess.ts).
864
+ * The browser's SQL editor runs its reads that way too (dataWriteRoutes.ts);
865
+ * only an in-memory database, which has no second handle, and a caller with
866
+ * no signal keep the in-process handle and its limit.
867
+ */
868
+ async runSql(request) {
869
+ const captured = { ...request };
870
+ return captured.write ? this.queueWrite(() => this.runSqlNow(captured)) : this.runSqlNow(captured);
871
+ }
872
+ async runSqlNow(request) {
873
+ await roundTrip();
874
+ const stacked = refuseStacked(request.text, 'sqlite');
875
+ if (stacked)
876
+ throw new Error(stacked);
877
+ if (!request.write) {
878
+ const outside = refuseOutsideTheFile(request.text);
879
+ if (outside)
880
+ throw new Refusal(outside);
881
+ }
882
+ if (request.refuseReserved)
883
+ refuseReservedText(request.text, 'sqlite');
884
+ const limit = clampLimit(request.limit);
885
+ const bounded = !request.write && looksWrappable(request.text);
886
+ const text = bounded ? boundStatement(request.text, limit) : trimStatement(request.text);
887
+ /* A file, that is: an in-memory database has no second handle to open. */
888
+ const signal = cancellation();
889
+ if (!request.write && signal && typeof this.writeIdentity === 'string') {
890
+ const started = performance.now();
891
+ const answer = await runInStatementProcess({
892
+ file: this.file,
893
+ text,
894
+ fallback: trimStatement(request.text),
895
+ bounded,
896
+ limit,
897
+ refuseReserved: request.refuseReserved === true,
898
+ }, statementCap(request.timeoutMs), signal);
899
+ if (answer.reserved !== undefined)
900
+ throw reservedRefusal(answer.reserved);
901
+ const truncated = answer.rows.length > limit;
902
+ return {
903
+ columns: dedupe(answer.columns),
904
+ rows: scaleArrays(truncated ? answer.rows.slice(0, limit) : answer.rows, scalesOf(answer.types.map((type) => ({ type })))).map((row) => row.map(valueSafe)),
905
+ ms: Math.round(performance.now() - started),
906
+ truncated,
907
+ bounded: answer.wrapped,
908
+ sql: { text: answer.wrapped ? text : trimStatement(request.text), params: [] },
909
+ };
910
+ }
911
+ const db = request.write ? this.writeHandle() : this.db;
912
+ const prepare = (sql) => (request.refuseReserved ? prepareGuarded(db, sql) : db.prepare(sql));
913
+ const started = performance.now();
914
+ /* The wrap is an optimisation that is allowed to fail. Not every legal
915
+ statement survives being made a derived table, and rather than enumerate
916
+ which, a statement that will not compile wrapped is run as written and
917
+ reported as unbounded. Safe to prepare twice because this handle cannot
918
+ write. */
919
+ let wrapped = bounded;
920
+ let statement;
921
+ try {
922
+ statement = prepare(text);
923
+ }
924
+ catch (err) {
925
+ if (!bounded)
926
+ throw err;
927
+ wrapped = false;
928
+ statement = prepare(trimStatement(request.text));
929
+ }
930
+ if (request.write) {
931
+ const info = statement.run();
932
+ return {
933
+ columns: [],
934
+ rows: [],
935
+ ms: Math.round(performance.now() - started),
936
+ truncated: false,
937
+ bounded: false,
938
+ affected: Number(info.changes ?? 0),
939
+ sql: { text, params: [] },
940
+ };
941
+ }
942
+ /* Arrays and a real column list, both from the driver.
943
+
944
+ `columns()` reports every output column including two of the same name,
945
+ and `setReturnArrays` makes the rows positional so both values survive —
946
+ an object keyed by name can only hold one of them. Deriving names from
947
+ the first row's keys, which is what `query()` does, loses the duplicate
948
+ and returns nothing at all for an empty result. */
949
+ statement.setReturnArrays(true);
950
+ /* Same reason as the table reads: a raw `SELECT` can return a 64-bit id,
951
+ and without this the driver throws on the first one past 2^53. */
952
+ statement.setReadBigInts(true);
953
+ const described = statement.columns();
954
+ const columns = dedupe(described.map((c) => c.name ?? c.column ?? '?'));
955
+ /* Stepped, and stopped one past the limit: a statement the wrap could not bound (one that starts with a
956
+ parenthesis SQLite will not take as a derived table, or one the wrap made invalid) used to be read whole
957
+ with all() and sliced after, every row in memory first. */
958
+ const stepped = [];
959
+ for (const row of statement.iterate()) {
960
+ stepped.push(row);
961
+ if (stepped.length > limit)
962
+ break;
963
+ }
964
+ const raw = scaleArrays(stepped, scalesOf(described));
965
+ /* Capped whether or not the wrap applied. `bounded` says who did the
966
+ limiting — the database, or us after it had already done the work — and
967
+ the caller shows that difference. What it never means is that more rows
968
+ than asked for reach the client. */
969
+ const truncated = raw.length > limit;
970
+ const kept = truncated ? raw.slice(0, limit) : raw;
971
+ return {
972
+ columns,
973
+ rows: kept.map((row) => row.map(valueSafe)),
974
+ ms: Math.round(performance.now() - started),
975
+ truncated,
976
+ bounded: wrapped,
977
+ sql: { text: wrapped ? text : trimStatement(request.text), params: [] },
978
+ };
979
+ }
980
+ /**
981
+ * Delete one row.
982
+ *
983
+ * The guards are `update`'s, for the same reason they are `update`'s: the
984
+ * danger in an editor is never injection, it is a statement whose WHERE is
985
+ * broader than the author believed. A delete has the sharper version of that
986
+ * problem, so nothing here is relaxed.
987
+ *
988
+ * Foreign keys are the database's business. `node:sqlite` enables
989
+ * `foreign_keys` by default — checked, not assumed — so a RESTRICT fires and
990
+ * a CASCADE cascades exactly as the schema says, and the error the user sees
991
+ * is SQLite's own.
992
+ */
993
+ async remove(request) {
994
+ if (this.recordsHistory(request))
995
+ return this.transaction((tx) => tx.remove(request));
996
+ return this.queueWrite(() => this.removeNow(request));
997
+ }
998
+ recordsHistory(request) {
999
+ return capturesWrites(this, request?.table);
1000
+ }
1001
+ /**
1002
+ * A store's layout attested once per schema epoch rather than on every
1003
+ * read: its three catalog reads were two of every room's statements in an
1004
+ * App that keeps notices. The epoch — the schema versions, and the store's
1005
+ * meta row, a data fact — is one statement read first; the layout is
1006
+ * described again whenever it has moved.
1007
+ */
1008
+ attestedStore(store, read, db = this.db) {
1009
+ const epoch = storeEpoch(db, { table: store.table, version: store.version });
1010
+ const attested = this.catalogCache.get(epoch, `store:${store.name}`) === true;
1011
+ const result = read(attested);
1012
+ // Unattested, `read` attested the layout itself, and threw if it was not this version's.
1013
+ if (!attested)
1014
+ this.catalogCache.set(epoch, `store:${store.name}`, true);
1015
+ return result;
1016
+ }
1017
+ /** A write capture's reads on a transaction's own handle (writeCapture.ts). */
1018
+ captureSource(handle) {
1019
+ return {
1020
+ writer: { dialect: 'sqlite', db: handle },
1021
+ read: async (table, key, columns) => {
1022
+ const result = await this.queryOn({
1023
+ table,
1024
+ ...(columns ? { columns } : {}),
1025
+ limit: 2,
1026
+ offset: 0,
1027
+ filter: { groups: [Object.entries(key).map(([column, value]) => ({ column, op: '=', value }))] },
1028
+ }, handle);
1029
+ return result.rows.length === 1 ? result.rows[0] : undefined;
1030
+ },
1031
+ rows: async (table, where, columns, limit) => (await this.queryOn({
1032
+ table,
1033
+ columns,
1034
+ limit,
1035
+ offset: 0,
1036
+ filter: { groups: [Object.entries(where).map(([column, value]) => ({ column, op: '=', value }))] },
1037
+ }, handle)).rows,
1038
+ };
1039
+ }
1040
+ async lockRecordNow(request, write) {
1041
+ // BEGIN IMMEDIATE already fences every other writer, including absent keys.
1042
+ // Still validate the exact main-catalog table and complete scalar primary key.
1043
+ const table = this.assertTable(request.table, write);
1044
+ const meta = this.tableInfo(table, write);
1045
+ recordLockFilter(request, meta.filter((column) => column.pk > 0).map((column) => column.name));
1046
+ }
1047
+ async removeNow(request, read = this.db) {
1048
+ const table = this.assertTable(request.table, read);
1049
+ const meta = this.tableInfo(table, read);
1050
+ const pk = meta.filter((c) => c.pk > 0).map((c) => c.name);
1051
+ if (!pk.length)
1052
+ throw new Refusal(`${table} has no primary key, so no single row can be identified.`);
1053
+ const keyColumns = Object.keys(request.key);
1054
+ const same = keyColumns.length === pk.length && pk.every((c) => keyColumns.includes(c));
1055
+ if (!same) {
1056
+ throw new Refusal(`Deleting from ${table} needs exactly its primary key (${pk.join(', ')}).`);
1057
+ }
1058
+ const filter = {
1059
+ groups: [pk.map((column) => ({ column, op: '=', value: request.key[column] }))],
1060
+ };
1061
+ // As an update's: inside a transaction the DELETE's own count is the check.
1062
+ const transactional = read !== this.db;
1063
+ const refuseMatches = (count) => {
1064
+ throw new Refusal(`Refused: that key matches ${count} rows, not 1. A delete is only allowed when it is certain which row it removes.`);
1065
+ };
1066
+ const matches = transactional ? 1 : await this.count(table, filter);
1067
+ if (matches !== 1)
1068
+ refuseMatches(matches);
1069
+ const where = buildWhere(filter, ANSI_STYLE);
1070
+ const text = `DELETE FROM ${ANSI_STYLE.quote(table)}${where.text}`;
1071
+ const write = read === this.db ? this.writeHandle() : read;
1072
+ const result = write.prepare(text).run(...sqliteParams(where.params));
1073
+ if (transactional && Number(result.changes) !== 1)
1074
+ refuseMatches(Number(result.changes));
1075
+ return { affected: Number(result.changes), sql: { text, params: where.params } };
1076
+ }
1077
+ writeHandle() {
1078
+ if (this.closed)
1079
+ throw new Refusal('This adapter is closed.');
1080
+ this.assertWriteIdentity();
1081
+ if (!this.writeDb) {
1082
+ const write = new DatabaseSync(this.file);
1083
+ try {
1084
+ this.assertWriteIdentity();
1085
+ waitForLocks(write);
1086
+ this.writeDb = write;
1087
+ }
1088
+ catch (error) {
1089
+ write.close();
1090
+ throw error;
1091
+ }
1092
+ }
1093
+ return this.writeDb;
1094
+ }
1095
+ /**
1096
+ * Several writes as one, or none of them.
1097
+ *
1098
+ * `BEGIN IMMEDIATE` serializes writers across processes, not just this queue.
1099
+ * Reads and mutation preflights inside the callback share its write handle.
1100
+ * Ordinary browsing remains on the separate read-only handle.
1101
+ *
1102
+ * `node:sqlite` is synchronous, so nothing can interleave *within* one
1103
+ * statement; the queue is what stops another request's write interleaving
1104
+ * *between* two of these.
1105
+ */
1106
+ async transaction(run) {
1107
+ return this.transact((tx) => run(tx), undefined);
1108
+ }
1109
+ /**
1110
+ * Records and a durable journal in one transaction, the journal lent by
1111
+ * the App framework (storeHost.ts TransactionJournal): the command journal,
1112
+ * or a public placement's. Lent through the store host, never on the
1113
+ * class, so the database browser has no journal path at all. A verified
1114
+ * ordinary transaction's check is trusted host code, never a
1115
+ * handler/request: it runs after BEGIN and journal attestation and after
1116
+ * the operations drain, before COMMIT, and infers no tenant namespace, row
1117
+ * scope or bearer authority.
1118
+ */
1119
+ async journalTransaction(journal, run, options = {}) {
1120
+ const { scope, verify, ordinaryVerify, bound } = options;
1121
+ if (ordinaryVerify !== undefined) {
1122
+ if (!isSqliteVerifiedCommandSource(this))
1123
+ throw new Refusal('This verified command transaction is unavailable.');
1124
+ if (typeof ordinaryVerify !== 'function' || isProxy(ordinaryVerify) || typeof run !== 'function' || isProxy(run) || this.closed)
1125
+ throw new Refusal('This verified command transaction is unavailable.');
1126
+ }
1127
+ return this.transact(run, journal, scope, verify, undefined, ordinaryVerify, bound);
1128
+ }
1129
+ async scopeTransaction(input, run) {
1130
+ const scope = captureRowScope(input);
1131
+ if (this.closed)
1132
+ throw new Refusal('This adapter is closed.');
1133
+ return this.transact((tx, _journal, catalog) => run(tx, catalog), undefined, scope);
1134
+ }
1135
+ /** Private source-owner boundary, deliberately absent from Adapter. The
1136
+ * verifier is trusted host code, never a business callback or wire value.
1137
+ * It must be synchronous and may not retain the raw handle. */
1138
+ async scopeVerifiedTransaction(input, verify, run) {
1139
+ const scope = captureRowScope(input);
1140
+ if (typeof verify !== 'function' || typeof run !== 'function' || this.closed || Object.keys(scope).some(isRuntimeTable))
1141
+ throw new Refusal('This verified transaction is unavailable.');
1142
+ return this.transact((tx, _journal, catalog) => run(tx, catalog), undefined, scope, verify);
1143
+ }
1144
+ /** Internal runtime-owner composition, deliberately absent from Adapter.
1145
+ * Only trusted runtime code may create bounded maintenance facades here; raw
1146
+ * handles must never reach business handlers, policies or request callbacks.
1147
+ * Runtime edits share the ordinary writer queue, drain and outcome handling. */
1148
+ async runtimeMaintenanceTransaction(verify, run) {
1149
+ if (this.closed || typeof verify !== 'function' || typeof run !== 'function')
1150
+ throw new Refusal('Runtime maintenance is unavailable.');
1151
+ return this.transact(async () => {
1152
+ throw new Refusal('Runtime maintenance is unavailable.');
1153
+ }, undefined, {}, verify, run);
1154
+ }
1155
+ async transact(run, journal, scope, verify, maintenance, ordinaryVerify, bound) {
1156
+ return this.queueWrite(async () => {
1157
+ const operations = new TransactionOperations();
1158
+ let write;
1159
+ let began = false;
1160
+ let commitAttempted = false;
1161
+ try {
1162
+ if (this.closed)
1163
+ throw new Refusal('This adapter is closed.');
1164
+ write = this.writeHandle();
1165
+ write.exec('BEGIN IMMEDIATE');
1166
+ began = true;
1167
+ const handle = write;
1168
+ /* One statement for the epoch the whole transaction trusts: BEGIN
1169
+ IMMEDIATE holds the file's write lock, so no connection can change
1170
+ the schema until COMMIT, and what is cached under this epoch is
1171
+ what the transaction will see. */
1172
+ const meta = journal?.meta;
1173
+ const read = transactionEpoch(handle, meta && { table: meta.table, version: meta.version });
1174
+ const epoch = read.epoch;
1175
+ /* A journal that names a meta row is attested once per epoch; one that does not, in every transaction. */
1176
+ if (journal && !meta)
1177
+ journal.attest(handle);
1178
+ if (meta && !(read.journal && this.catalogCache.get(epoch, `journal:${meta.name}`))) {
1179
+ journal.attest(handle);
1180
+ this.catalogCache.set(epoch, `journal:${meta.name}`, true);
1181
+ }
1182
+ if (epoch !== undefined)
1183
+ this.txEpoch = { handle, epoch };
1184
+ // Separate from scoped verification: no physical scope catalog is
1185
+ // required, and ordinary CommandTransactionFailure outcomes remain.
1186
+ const verifyOrdinary = () => {
1187
+ if (!ordinaryVerify)
1188
+ return;
1189
+ if (!handle.isTransaction)
1190
+ throw new Refusal('The verified command transaction is no longer active.');
1191
+ const result = ordinaryVerify();
1192
+ if (isPromise(result))
1193
+ void Promise.prototype.then.call(result, undefined, () => { });
1194
+ if (result !== undefined || !handle.isTransaction)
1195
+ throw new Refusal('Transaction verification must be synchronous.');
1196
+ };
1197
+ /* Beneath every facade, so a scoped writer, a command and an Undo
1198
+ record exactly as a Save does (history/recordHistory.ts). */
1199
+ const history = maintenance ? undefined : beginWriteCapture(this, this.captureSource(handle));
1200
+ const insert = (r) => this.insertNow(r, handle, scope !== undefined);
1201
+ const remove = (r) => this.removeNow(r, handle);
1202
+ const update = (r) => this.updateNow(r, handle, scope !== undefined);
1203
+ let records = {
1204
+ query: (r) => this.queryOn(r, handle),
1205
+ count: (table, filter) => this.countOn(table, filter, handle),
1206
+ insert: history ? (r) => history.insert(r, insert) : insert,
1207
+ remove: history ? (r) => history.remove(r, remove) : remove,
1208
+ update: history ? (r) => history.update(r, update) : update,
1209
+ };
1210
+ let evidence;
1211
+ let physicalCatalog;
1212
+ const verified = () => {
1213
+ if (!verify)
1214
+ return;
1215
+ if (!handle.isTransaction || !physicalCatalog)
1216
+ throw new Refusal('The verified transaction is no longer active.');
1217
+ const result = verify(handle, physicalCatalog);
1218
+ if (isPromise(result))
1219
+ void Promise.prototype.then.call(result, undefined, () => { });
1220
+ if (result !== undefined || !handle.isTransaction)
1221
+ throw new Refusal('Transaction verification must be synchronous.');
1222
+ };
1223
+ if (scope !== undefined) {
1224
+ const catalog = this.catalogCache.throughSync(epoch, 'scope', () => readSqliteScopeCatalog(handle));
1225
+ physicalCatalog = catalog;
1226
+ journal?.admit?.(handle, catalog, scope);
1227
+ // Keep the complete physical graph for row/FK enforcement. Runtime
1228
+ // state is not command-handler evidence, and an incident edge may
1229
+ // not disappear merely because its endpoint belongs to the runtime.
1230
+ evidence = journal
1231
+ ? mapScopeCatalog(catalog.schema, new Map(catalog.schema.tables.filter((table) => !isRuntimeTable(table.id)).map((table) => [table.id, table.id])))
1232
+ : captureScopeCatalog(catalog.schema);
1233
+ const scopedReads = (guard, within = scope) => createScopedReads(catalog.schema, within, ANSI_STYLE, async (sql, hint) => {
1234
+ const started = performance.now();
1235
+ const statement = handle.prepare(sql.text);
1236
+ statement.setReadBigInts(true);
1237
+ const described = statement.columns();
1238
+ const rows = scaleRows(statement.all(...sqliteParams(sql.params)), described.map((c) => c.name), scalesOf(described));
1239
+ return {
1240
+ columns: described.map((column) => column.name),
1241
+ rows: rows.map((row) => guard
1242
+ ? Object.fromEntries(Object.entries(row).map(([key, value]) => [
1243
+ key,
1244
+ typeof value === 'bigint' && value >= -9007199254740991n && value <= 9007199254740991n ? Number(value) : value,
1245
+ ]))
1246
+ : hint?.binary
1247
+ ? withBytes(row)
1248
+ : toJsonSafe(row)),
1249
+ ms: Math.round(performance.now() - started),
1250
+ sql,
1251
+ };
1252
+ });
1253
+ records = createScopedTransaction(catalog, records, scopedReads(false), scopedReads(true), undefined, bound && { reads: scopedReads(true, bound), whole: wholeWithin(scope, bound) },
1254
+ // A public intake writes exactly its fixed rows; only a principal's own transaction counts its new parents in.
1255
+ journal?.fixedRecords ? undefined : { scope, reads: (within, guard) => scopedReads(guard, within) });
1256
+ if (journal?.fixedRecords)
1257
+ records = journal.fixedRecords(records);
1258
+ }
1259
+ verified();
1260
+ verifyOrdinary();
1261
+ const invoke = scopedCommandInvoker(operations, scope !== undefined, journal !== undefined, isRuntimeTable, verified);
1262
+ const out = maintenance
1263
+ ? await maintenance(handle, operations, verified)
1264
+ : await run(Object.freeze({
1265
+ // The scoped policy facade deliberately has no generic lock capability.
1266
+ ...(scope === undefined
1267
+ ? { lockRecord: (r) => queueRecordLock(operations, r, (request) => this.lockRecordNow(request, handle)) }
1268
+ : {}),
1269
+ query: (r) => invoke(r, (r) => records.query(r)),
1270
+ count: (table, filter) => invoke({ table, filter }, (r) => records.count(r.table, r.filter)),
1271
+ insert: (r) => invoke(r, (r) => records.insert(r)),
1272
+ remove: (r) => invoke(r, (r) => records.remove(r)),
1273
+ update: (r) => invoke(r, (r) => records.update(r)),
1274
+ // Beneath the row scope: the flows' command tells only of its own target, which it read through the scope.
1275
+ ...(history ? { tell: (r) => invoke(r, (r) => history.tell(r)) } : {}),
1276
+ }), journal?.open(handle, operations, verify ? verified : undefined), evidence);
1277
+ await operations.finish();
1278
+ await finishWriteCapture(history);
1279
+ verified();
1280
+ verifyOrdinary();
1281
+ commitAttempted = true;
1282
+ write.exec('COMMIT');
1283
+ committedWriteCapture(history);
1284
+ return out;
1285
+ }
1286
+ catch (err) {
1287
+ await operations.finish().catch(() => { });
1288
+ let rolledBack = false;
1289
+ if (began) {
1290
+ try {
1291
+ write.exec('ROLLBACK');
1292
+ rolledBack = true;
1293
+ }
1294
+ catch {
1295
+ /* A failed acknowledgement cannot establish the outcome. */
1296
+ }
1297
+ }
1298
+ const unknown = commitAttempted || (began && !rolledBack);
1299
+ if (write && (unknown || !began)) {
1300
+ // Never let queued writes inherit a transaction with an uncertain state.
1301
+ try {
1302
+ write.close();
1303
+ }
1304
+ catch {
1305
+ /* Preserve the original failure. */
1306
+ }
1307
+ if (this.writeDb === write)
1308
+ this.writeDb = null;
1309
+ }
1310
+ // The private verified owner composes native transaction outcomes like
1311
+ // ordinary tenant writes. Do not wrap its existing unknown marker in a
1312
+ // legacy command refusal merely because the outer rollback succeeded.
1313
+ if (journal && !verify)
1314
+ throw journal.failure(err, unknown ? 'unknown' : 'not_applied');
1315
+ if (unknown)
1316
+ throw new TransactionOutcomeUnknown(err);
1317
+ throw err;
1318
+ }
1319
+ finally {
1320
+ if (this.txEpoch?.handle === write)
1321
+ this.txEpoch = undefined;
1322
+ }
1323
+ });
1324
+ }
1325
+ async close() {
1326
+ this.closed = true;
1327
+ this.db.close();
1328
+ this.writeDb?.close();
1329
+ this.writeDb = null;
1330
+ }
1331
+ /**
1332
+ * Table names are the one identifier that cannot be a bound parameter, so
1333
+ * they are checked against the catalog rather than escaped and hoped for.
1334
+ * An unknown name is rejected before it reaches a statement.
1335
+ */
1336
+ /**
1337
+ * The same check for a read on the browsing handle, answered from the
1338
+ * catalog read on connect when it names the table: a name found there is
1339
+ * one the catalog gave, so the probe would only say so again, once per
1340
+ * statement. A name it does not hold — a table made since — still gets the
1341
+ * live probe, and so does every write (assertTable).
1342
+ */
1343
+ readTable(name) {
1344
+ if (this.cachedSchema?.tables.some((table) => table.id === name))
1345
+ return name;
1346
+ return this.assertTable(name);
1347
+ }
1348
+ assertTable(name, db = this.db) {
1349
+ /* Inside a write transaction, every name at once, kept under its epoch:
1350
+ the first write of an epoch reads the list, and no later one asks. */
1351
+ const epoch = this.epochOf(db);
1352
+ if (epoch !== undefined) {
1353
+ const names = this.catalogCache.throughSync(epoch, 'tables', () => new Set(db.prepare(`SELECT name FROM sqlite_master WHERE type IN ('table','view')`).all().map((row) => row.name)));
1354
+ if (!names.has(name))
1355
+ throw new Error(`No table or view called "${name}" in this database.`);
1356
+ return name;
1357
+ }
1358
+ const exists = db.prepare(`SELECT 1 FROM sqlite_master WHERE type IN ('table','view') AND name = ?`).get(name);
1359
+ if (!exists)
1360
+ throw new Error(`No table or view called "${name}" in this database.`);
1361
+ return name;
1362
+ }
1363
+ /** A table's columns, as a write reads them: from the catalog, or inside a
1364
+ * write transaction from what was read under the epoch it holds. */
1365
+ tableInfo(table, db) {
1366
+ return this.catalogCache.throughSync(this.epochOf(db), `columns:${table}`, () => db.prepare(`PRAGMA main.table_xinfo(${ANSI_STYLE.quote(table)})`).all().filter((c) => c.hidden !== 1));
1367
+ }
1368
+ /** Whether a relation is a view, as a write reads it: kept under the epoch, as its columns are. */
1369
+ isView(table, db) {
1370
+ return this.catalogCache.throughSync(this.epochOf(db), `view:${table}`, () => db.prepare(`SELECT 1 FROM sqlite_master WHERE type = 'view' AND name = ?`).get(table) !== undefined);
1371
+ }
1372
+ /** The epoch the open write transaction holds, when `db` is its handle and it is still open. */
1373
+ epochOf(db) {
1374
+ return this.txEpoch?.handle === db && db.isTransaction ? this.txEpoch.epoch : undefined;
1375
+ }
1376
+ }
1377
+ // SQL booleans remain portable in the compiler; node:sqlite binds them as 0/1.
1378
+ export function sqliteParams(params) {
1379
+ return params.map((value) => (typeof value === 'boolean' ? Number(value) : value));
1380
+ }
1381
+ /**
1382
+ * One value from the database, made safe to send as JSON.
1383
+ *
1384
+ * Two things do not survive `JSON.stringify`, and one of them does not survive
1385
+ * being *read* by default. `node:sqlite` throws on any integer past 2^53
1386
+ * unless the statement is told to read big integers — so the data statements
1387
+ * ask for that (`setReadBigInts`), and everything comes back as `bigint`.
1388
+ *
1389
+ * A bigint inside the safe range is turned back into a plain number, because
1390
+ * the client formats numbers — thousands separators, right alignment — and a
1391
+ * string would lose all of that on the id column of every ordinary table. One
1392
+ * *past* the safe range is kept as a string, because that is the whole point:
1393
+ * a Postgres/Snowflake-style 64-bit id shown as `9007199254740992` when it is
1394
+ * really `...993` looks right and matches nothing when clicked. Passing it
1395
+ * through as a number would be the silent corruption this exists to prevent.
1396
+ *
1397
+ * Blobs become `<n bytes>`: the grid has no use for the bytes and every use
1398
+ * for the size.
1399
+ */
1400
+ /**
1401
+ * The statements a read-only handle still runs that reach past its file.
1402
+ *
1403
+ * `readOnly: true` stops writes to this database, not to others: `VACUUM
1404
+ * INTO` writes a whole copy — runtime and sign-in tables included — to a path
1405
+ * the statement names, and `ATTACH` opens another file (and, on the shared
1406
+ * in-process handle, leaves it open for the next statement). SQLite's
1407
+ * authorizer is told about an ATTACH but not about a VACUUM, so all three are
1408
+ * refused here by their first keyword, the only place each can stand.
1409
+ */
1410
+ function refuseOutsideTheFile(text) {
1411
+ const first = trimStatement(text).replace(/^\s*(?:--[^\n]*\n|\/\*[\s\S]*?\*\/|\s)+/g, '');
1412
+ const verb = /^(attach|detach|vacuum)\b/i.exec(first)?.[1];
1413
+ if (!verb)
1414
+ return null;
1415
+ return `${verb.toUpperCase()} reaches past this database file, so a read-only connection refuses it.`;
1416
+ }
1417
+ function valueSafe(v) {
1418
+ if (typeof v === 'bigint') {
1419
+ return v >= -9007199254740991n && v <= 9007199254740991n ? Number(v) : v.toString();
1420
+ }
1421
+ if (v instanceof Uint8Array)
1422
+ return `<${v.byteLength} bytes>`;
1423
+ return v;
1424
+ }
1425
+ /** As toJsonSafe, but a blob stays its bytes: a file download asked for them (`QueryRequest.binary`). */
1426
+ export function withBytes(row) {
1427
+ return Object.fromEntries(Object.entries(row).map(([key, value]) => [key, value instanceof Uint8Array ? value : valueSafe(value)]));
1428
+ }
1429
+ export function toJsonSafe(row) {
1430
+ const out = {};
1431
+ for (const [k, v] of Object.entries(row))
1432
+ out[k] = valueSafe(v);
1433
+ return out;
1434
+ }