@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,1302 @@
1
+ import { MAX_LIMIT } from './reads.js';
2
+ import { LIKE_ESCAPE, likePattern } from './like.js';
3
+ import { distance, nearest } from './suggest.js';
4
+ import { columnKind, findTable } from './schema.js';
5
+ import { dateWindow } from './queryDates.js';
6
+ import { maskQuoted } from './maskQuoted.js';
7
+ import { ME_SET, columnOperand, meOperand, meSetOperand } from './queryOperands.js';
8
+ import { TAGS_TYPE } from './tagLists.js';
9
+ import { DEFINED_PATH_TYPE } from './definedFields.js';
10
+ const RANGE_OPS = new Set(['<', '<=', '>', '>=']);
11
+ export { dateWindow } from './queryDates.js';
12
+ /**
13
+ * A query's problem as an App's author reads it: the message, then where in
14
+ * the query it stands. The query bar underlines the word; a query written in
15
+ * an App's source is read in a terminal, where "character 23" and the words
16
+ * from there are what find it. `text` is what the positions count in, and the
17
+ * author's own words start at `from` (after a table name the caller added).
18
+ */
19
+ export function saidAt(error, text, from = 0) {
20
+ const own = text.slice(from);
21
+ const at = error.at === undefined ? -1 : error.at - from;
22
+ if (at < 0 || at >= own.length)
23
+ return error.message;
24
+ const rest = own.slice(at).trimStart();
25
+ const start = own.length - rest.length;
26
+ return `${error.message} (at character ${start + 1}: "${rest.length > 32 ? `${rest.slice(0, 32)}…` : rest}")`;
27
+ }
28
+ /**
29
+ * What can appear in a field position.
30
+ *
31
+ * `\w`, plus `.` for reference paths, `$` because some dialects allow it in
32
+ * identifiers, and `+` for the composite-hop syntax a view uses:
33
+ * `invoice_id+line_no.sku` follows a two-column foreign key. Without `+`
34
+ * those hops were addressable only by their constraint name, which is a
35
+ * database artefact nobody wants to type.
36
+ *
37
+ * `+` is safe to admit because the language has no arithmetic — there is
38
+ * nothing it could otherwise have meant — and it cannot appear in a real
39
+ * column name, so it can never be ambiguous with one.
40
+ */
41
+ const FIELD = String.raw `[\w.$+]+`;
42
+ /** Operator words users type, mapped to the filter's operators. */
43
+ const OPERATORS = {
44
+ '=': '=',
45
+ '!=': '!=',
46
+ '<': '<',
47
+ '<=': '<=',
48
+ '>': '>',
49
+ '>=': '>=',
50
+ contains: 'contains',
51
+ startswith: 'startsWith',
52
+ endswith: 'endsWith',
53
+ like: 'like',
54
+ in: 'in',
55
+ has: 'has',
56
+ };
57
+ /* Re-exported: the classifier moved to schema.ts to keep adapters from
58
+ importing this module, but callers here have always got it from here. */
59
+ export { columnKind } from './schema.js';
60
+ /**
61
+ * Split on a keyword at depth zero, honouring quotes and parentheses so that
62
+ * `a and (b or c)` and `name = "Smith and Sons"` both survive intact.
63
+ */
64
+ /** Whether `word` sits at `i` as a whole word rather than inside a longer one. */
65
+ function isWord(text, i, word) {
66
+ const before = text[i - 1];
67
+ const after = text[i + word.length];
68
+ return (before === undefined || /[\s(]/.test(before)) && (after === undefined || /[\s(]/.test(after));
69
+ }
70
+ /* The same few conditions are split on every request that carries them (a view's filter, a saved query);
71
+ the answer is a pure function of its arguments, so a bounded recency map keeps it. The parts are only
72
+ read by the callers, and frozen so that stays true. */
73
+ const splits = new Map();
74
+ const MAX_SPLITS = 512;
75
+ function splitTopLevel(text, keyword, offset) {
76
+ if (text.length > 2000)
77
+ return splitTopLevelFresh(text, keyword, offset);
78
+ const key = `${keyword}\u0000${offset}\u0000${text}`;
79
+ const hit = splits.get(key);
80
+ if (hit) {
81
+ splits.delete(key);
82
+ splits.set(key, hit);
83
+ return hit;
84
+ }
85
+ const parts = splitTopLevelFresh(text, keyword, offset);
86
+ for (const part of parts)
87
+ Object.freeze(part);
88
+ Object.freeze(parts);
89
+ splits.set(key, parts);
90
+ if (splits.size > MAX_SPLITS)
91
+ splits.delete(splits.keys().next().value);
92
+ return parts;
93
+ }
94
+ function splitTopLevelFresh(text, keyword, offset) {
95
+ const parts = [];
96
+ let current = '';
97
+ let start = 0;
98
+ let quote = null;
99
+ let depth = 0;
100
+ /** Set when a `between` is open and its `and` has not been seen yet. */
101
+ let pendingBetween = false;
102
+ const lower = text.toLowerCase();
103
+ for (let i = 0; i < text.length; i++) {
104
+ const ch = text[i];
105
+ if (quote) {
106
+ /* An escaped character cannot end the quote — the same rule
107
+ `maskQuoted` applies below, and the reason this loop needed it:
108
+ without it `name = "a \\" and b"` closed its quote at the escaped
109
+ one, found a top-level `and` inside the value, and split a single
110
+ condition in half. Two quote scanners in one file disagreeing about
111
+ whether the grammar has escapes. */
112
+ if (ch === '\\' && i + 1 < text.length) {
113
+ current += ch + text[i + 1];
114
+ i += 1;
115
+ continue;
116
+ }
117
+ current += ch;
118
+ if (ch === quote)
119
+ quote = null;
120
+ continue;
121
+ }
122
+ if (ch === '"' || ch === "'") {
123
+ quote = ch;
124
+ current += ch;
125
+ continue;
126
+ }
127
+ if (ch === '(')
128
+ depth++;
129
+ if (ch === ')')
130
+ depth = Math.max(0, depth - 1);
131
+ /* `between 10 and 20` contains an `and` that joins a range rather than two
132
+ conditions. Splitting there cut one condition in half and left `20` as
133
+ a condition of its own, which parsed as nothing and reported an error
134
+ about `20` — so the word had to be tracked rather than the grammar
135
+ reworded. The first top-level `and` after a `between` belongs to it. */
136
+ if (keyword === 'and' && lower.startsWith('between', i) && isWord(text, i, 'between')) {
137
+ pendingBetween = true;
138
+ }
139
+ const atBoundary = depth === 0 &&
140
+ lower.startsWith(keyword, i) &&
141
+ (i === 0 || /[\s)]/.test(text[i - 1])) &&
142
+ /[\s(]/.test(text[i + keyword.length] ?? ' ');
143
+ if (atBoundary && pendingBetween) {
144
+ pendingBetween = false;
145
+ current += text[i];
146
+ continue;
147
+ }
148
+ if (atBoundary) {
149
+ parts.push({ text: current, at: offset + start });
150
+ i += keyword.length - 1;
151
+ current = '';
152
+ start = i + 1;
153
+ continue;
154
+ }
155
+ current += ch;
156
+ }
157
+ if (current.trim())
158
+ parts.push({ text: current, at: offset + start });
159
+ return parts.filter((p) => p.text.trim());
160
+ }
161
+ /** Strip one layer of wrapping parentheses, when they wrap the whole term. */
162
+ function unwrap(text) {
163
+ const t = text.trim();
164
+ const lead = text.length - text.trimStart().length;
165
+ if (!t.startsWith('(') || !t.endsWith(')'))
166
+ return { text: t, shift: lead };
167
+ let depth = 0;
168
+ for (let i = 0; i < t.length; i++) {
169
+ if (t[i] === '(')
170
+ depth++;
171
+ else if (t[i] === ')') {
172
+ depth--;
173
+ if (depth === 0 && i < t.length - 1)
174
+ return { text: t, shift: lead }; // `)(` — not one wrap
175
+ }
176
+ }
177
+ return { text: t.slice(1, -1).trim(), shift: lead + 1 };
178
+ }
179
+ /**
180
+ * Whether the text carries an `or` at depth zero — the one case where
181
+ * appending `and …` textually would regroup its meaning, since `or`
182
+ * binds loosest. Mirrored in the client (core.js `andQuery`), kept
183
+ * honest by test/mirrors.test.ts.
184
+ */
185
+ function hasTopLevelOr(text) {
186
+ let depth = 0;
187
+ let quote = null;
188
+ const lower = text.toLowerCase();
189
+ for (let i = 0; i < text.length; i++) {
190
+ const ch = text[i];
191
+ if (quote) {
192
+ if (ch === '\\') {
193
+ i += 1;
194
+ continue;
195
+ }
196
+ if (ch === quote)
197
+ quote = null;
198
+ continue;
199
+ }
200
+ if (ch === '"' || ch === "'") {
201
+ quote = ch;
202
+ continue;
203
+ }
204
+ if (ch === '(')
205
+ depth++;
206
+ else if (ch === ')')
207
+ depth = Math.max(0, depth - 1);
208
+ else if (depth === 0 && lower.startsWith('or', i) && (i === 0 || /[\s)]/.test(text[i - 1])) && /[\s(]/.test(text[i + 2] ?? ' '))
209
+ return true;
210
+ }
211
+ return false;
212
+ }
213
+ /**
214
+ * Join query fragments with `and`, parenthesizing any fragment whose
215
+ * top-level `or` would otherwise regroup: `a or b` composed with `c`
216
+ * must mean `(a or b) and c`, never `a or (b and c)`. Every place that
217
+ * folds an authored query into a narrower one goes through this.
218
+ */
219
+ export function andQuery(...parts) {
220
+ const heads = [];
221
+ const tails = [];
222
+ for (const raw of parts) {
223
+ const p = typeof raw === 'string' ? raw.trim() : '';
224
+ if (!p)
225
+ continue;
226
+ /* Conditions and clauses part company here. A tab written `kind = a or
227
+ kind = b sort happened_on desc` is conditions *and* a sort; bracketing
228
+ the whole text put the sort inside the parentheses and the parser
229
+ could not read it. Only the conditions are composed; the clauses
230
+ follow, in the order their fragments came. */
231
+ const { head, tail } = splitClauseTail(p);
232
+ if (head)
233
+ heads.push(hasTopLevelOr(head) ? `(${head})` : head);
234
+ if (tail)
235
+ tails.push(tail);
236
+ }
237
+ return [heads.join(' and '), ...tails].filter(Boolean).join(' ');
238
+ }
239
+ /**
240
+ * A fragment's conditions, and the clauses that follow them: `a = 1 sort b
241
+ * limit 5` is head `a = 1`, tail `sort b limit 5`. A leading `filter`/`where`
242
+ * is dropped from the head, since the composition supplies its own. Quoted
243
+ * values are masked before scanning so `name = "sort of"` stays a value; a
244
+ * clause word followed by an operator is a column (`limit > 5`), as the
245
+ * parser itself decides. Mirrored in the client (core.js), kept honest by
246
+ * test/mirrors.test.ts.
247
+ */
248
+ export function splitClauseTail(text) {
249
+ const masked = maskQuoted(text);
250
+ const re = /\b(filter|where|show|select|sort|order\s+by|limit)\b/gi;
251
+ let m;
252
+ while ((m = re.exec(masked))) {
253
+ if (FOLLOWED_BY_OPERATOR.test(masked.slice(re.lastIndex)))
254
+ continue;
255
+ /* `any opportunity where stage = won` is one condition: its `where` opens the child rows' conditions, not a clause. */
256
+ if (/^(filter|where)$/i.test(m[1]) && ANY_BEFORE.test(masked.slice(0, m.index)))
257
+ continue;
258
+ if (m.index === 0 && /^(filter|where)$/i.test(m[1])) {
259
+ const rest = text.slice(re.lastIndex);
260
+ const inner = splitClauseTail(rest);
261
+ return { head: inner.head, tail: inner.tail };
262
+ }
263
+ return { head: text.slice(0, m.index).trim(), tail: text.slice(m.index).trim() };
264
+ }
265
+ return { head: text.trim(), tail: '' };
266
+ }
267
+ /* The start of a condition about the rows that point at a record (`any opportunity via account_id`), up to where its own `where` would stand. */
268
+ const ANY_BEFORE = /(?:^|[\s(])(?:any|no)\s+[A-Za-z_$][\w$.]*(?:\s+via\s+[A-Za-z_$][\w$+]*)?\s*$/i;
269
+ /**
270
+ * A condition about the rows that point at the record: `any opportunity where
271
+ * stage = won`, `no task via owner_id`. The child table, the reference it
272
+ * points through where it has several, and its own conditions — several of
273
+ * them in parentheses, since a top-level `and` belongs to the record's.
274
+ */
275
+ export const ANY_CONDITION = /^(any|no)\s+([A-Za-z_$][\w$.]*)(?:\s+via\s+([A-Za-z_$][\w$+]*))?(?:\s+(?:where|filter)\s+([\s\S]+))?$/i;
276
+ /** Each `any …`/`no …` condition in a condition text, where it stands; a column called `any` (`any = 3`) is not one. */
277
+ export function anyConditions(text) {
278
+ const found = [];
279
+ const visit = (node) => {
280
+ if (node.kind !== 'cond')
281
+ return node.children.forEach(visit);
282
+ const word = /^(any|no)\s/i.exec(node.text);
283
+ if (word && ANY_CONDITION.test(node.text) && !FOLLOWED_BY_OPERATOR.test(node.text.slice(word[1].length)))
284
+ found.push({ at: node.at, text: node.text });
285
+ };
286
+ visit(parseExpression(text, 0));
287
+ return found;
288
+ }
289
+ /** `or` binds loosest, then `and`, then parentheses. */
290
+ function parseExpression(text, offset) {
291
+ const { text: inner, shift } = unwrap(text);
292
+ const base = offset + shift;
293
+ const ors = splitTopLevel(inner, 'or', base);
294
+ if (ors.length > 1)
295
+ return { kind: 'or', children: ors.map((p) => parseExpression(p.text, p.at)) };
296
+ const ands = splitTopLevel(inner, 'and', base);
297
+ if (ands.length > 1)
298
+ return { kind: 'and', children: ands.map((p) => parseExpression(p.text, p.at)) };
299
+ return { kind: 'cond', text: inner, at: base };
300
+ }
301
+ /**
302
+ * The conditions a query holds at its top level, segments written out and nested `and`s flattened, each in a
303
+ * spelling that does not depend on spacing: `a = 1 and (b = 2 and c = 3)` is three. An `or` stays one
304
+ * condition, its branches in order, since which branch holds is not a question of more or fewer conditions. It is
305
+ * how one query is shown to ask no more than another (a metric's whole, dashboards: `targetWhole`).
306
+ */
307
+ export function topLevelConditions(text, segments) {
308
+ const head = splitClauseTail(expandSegments(text, segments).text).head;
309
+ if (!head)
310
+ return [];
311
+ const spell = (node) => node.kind === 'cond' ? node.text.replace(/\s+/g, ' ').trim() : `(${node.children.map(spell).join(` ${node.kind} `)})`;
312
+ const flat = (node) => (node.kind === 'and' ? node.children.flatMap(flat) : [spell(node)]);
313
+ return flat(parseExpression(head, 0));
314
+ }
315
+ /**
316
+ * Flatten to OR-of-ANDs, which is exactly the shape `Filter` holds.
317
+ *
318
+ * The distribution can blow up on a pathological query — `(a or b) and (c or
319
+ * d) and (e or f)` is eight groups — so it is capped. Better to refuse than
320
+ * to build a statement with ten thousand clauses in it.
321
+ */
322
+ const MAX_GROUPS = 64;
323
+ function toGroups(node) {
324
+ if (node.kind === 'cond')
325
+ return [[node]];
326
+ if (node.kind === 'or')
327
+ return node.children.flatMap(toGroups);
328
+ return node.children.reduce((groups, child) => {
329
+ const childGroups = toGroups(child);
330
+ const out = groups.flatMap((g) => childGroups.map((c) => [...g, ...c]));
331
+ return out.slice(0, MAX_GROUPS + 1);
332
+ }, [[]]);
333
+ }
334
+ /* ---------- segments: a name for conditions ----------
335
+
336
+ An App's segment (appSegments.ts) is a word the language reads where a
337
+ condition stands — `overdue and mine` — and writes inline, in parentheses,
338
+ before anything else is read. So it is the same statement as the conditions
339
+ written out, by construction: the paths, rollups and computed columns it
340
+ names are found by the view parser exactly as if they had been typed, and
341
+ `me` and `today` are this request's. Only a whole condition is a segment;
342
+ `status = open` keeps `open` a value. */
343
+ /** `me` read by a parse that was handed no person (ParseOptions.me). */
344
+ export const NO_ONE_HERE = 'me is the signed-in person, and nobody is named here to be it. Quote it ("me") to mean the text.';
345
+ const SEGMENT_WORD = /^[A-Za-z_][A-Za-z0-9_]*$/;
346
+ /** How deep segments may name segments: a chain this long is a cycle the load did not catch. */
347
+ const SEGMENT_DEPTH = 8;
348
+ /**
349
+ * A condition text with each segment it names written out, nested ones too.
350
+ * The positions of what was written stay readable through `at`, so a mistake
351
+ * in the author's own words is still underlined where they typed it.
352
+ */
353
+ export function expandSegments(text, segments, chain = []) {
354
+ const same = { text, at: (position) => position };
355
+ if (!segments || !text.trim())
356
+ return same;
357
+ const words = [];
358
+ const visit = (node) => {
359
+ if (node.kind === 'cond') {
360
+ if (SEGMENT_WORD.test(node.text) && Object.hasOwn(segments, node.text))
361
+ words.push({ at: node.at, name: node.text });
362
+ }
363
+ else
364
+ node.children.forEach(visit);
365
+ };
366
+ visit(parseExpression(text, 0));
367
+ if (!words.length)
368
+ return same;
369
+ const splices = [];
370
+ let out = '';
371
+ let last = 0;
372
+ for (const word of words.sort((a, b) => a.at - b.at)) {
373
+ if (chain.includes(word.name) || chain.length >= SEGMENT_DEPTH) {
374
+ return {
375
+ ...same,
376
+ error: {
377
+ message: `The segment "${word.name}" names itself through ${[...chain, word.name].map((name) => `"${name}"`).join(' → ')}.`,
378
+ at: word.at,
379
+ },
380
+ };
381
+ }
382
+ const inner = expandSegments(segments[word.name], segments, [...chain, word.name]);
383
+ if (inner.error)
384
+ return { ...same, error: { message: inner.error.message, at: word.at } };
385
+ out += text.slice(last, word.at);
386
+ const written = `(${inner.text})`;
387
+ splices.push({ at: word.at, was: word.name.length, from: out.length, length: written.length });
388
+ out += written;
389
+ last = word.at + word.name.length;
390
+ }
391
+ out += text.slice(last);
392
+ return {
393
+ text: out,
394
+ at: (position) => {
395
+ let shift = 0;
396
+ for (const splice of splices) {
397
+ if (position < splice.from)
398
+ break;
399
+ if (position < splice.from + splice.length)
400
+ return splice.at;
401
+ shift = splice.from + splice.length - (splice.at + splice.was);
402
+ }
403
+ return position - shift;
404
+ },
405
+ };
406
+ }
407
+ /* ---------- value coercion ----------
408
+
409
+ The schema is the reason this is possible at all. Knowing the column's
410
+ declared type lets `active = true` mean the boolean `true` on Postgres and
411
+ the integer `1` on SQLite, without the user thinking about either. */
412
+ /**
413
+ * Take the quotes off a value, and the escapes out of it.
414
+ *
415
+ * The unescaping half was missing, and its absence was invisible because the
416
+ * *tokenizer* has always understood `\"` — `maskQuoted` skips an
417
+ * escaped character precisely so it cannot end the quote. So `name =
418
+ * "Harbour \"HQ\""` was split into a clause correctly and then bound as the
419
+ * literal `Harbour \"HQ\"`, backslashes and all, which matches no row. Two
420
+ * halves of one grammar disagreeing about whether it has escapes.
421
+ *
422
+ * Only `\"`, `\'` and `\\` are unescaped. This is not C: `\n` in a value
423
+ * is a backslash and an n, because that is what someone typing a Windows path
424
+ * into a filter means by it.
425
+ */
426
+ function unquoteValue(raw) {
427
+ const t = raw.trim();
428
+ if ((t.startsWith('"') && t.endsWith('"') && t.length > 1) || (t.startsWith("'") && t.endsWith("'") && t.length > 1)) {
429
+ return { value: unescapeValue(t.slice(1, -1)), quoted: true };
430
+ }
431
+ return { value: t, quoted: false };
432
+ }
433
+ /**
434
+ * Write a value back into the grammar, quoting only when it needs it.
435
+ *
436
+ * The exact counterpart of `unquoteValue` above, and the reason it lives here
437
+ * rather than beside the caller: quoting used to be written out wherever a
438
+ * value had to be turned back into query text — three times in the client and
439
+ * once here — and the four disagreed about an embedded `"`, so the same value
440
+ * produced a different filter depending on which button was pressed.
441
+ *
442
+ * The backslash is escaped before the quote, or a value ending in one would
443
+ * escape its own closing quote and swallow the rest of the query. The client
444
+ * has its own copy in `core.quote`, because the browser cannot import this
445
+ * file; `mirrors.test.ts` asserts the two agree character for character.
446
+ */
447
+ export function quoteValue(value) {
448
+ const text = String(value);
449
+ /* A dot is left bare only in a number: `a.b` is a path wherever `a` is a
450
+ column, which the probe below cannot know about a real table. */
451
+ if (/^(?:[\w-]+|-?\d+\.\d+)$/.test(text) && readsAsItself(text))
452
+ return text;
453
+ return `"${text.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
454
+ }
455
+ /* The probe `readsAsItself` parses against: one text column, so a bare value
456
+ is bound as the text it is and nothing about a real table can change the
457
+ answer, and a session, so `me` is read as the person it would name. */
458
+ const PROBE_SCHEMA = {
459
+ label: 'probe',
460
+ dialect: 'sqlite',
461
+ foreignKeys: [],
462
+ readAt: '',
463
+ tables: [
464
+ {
465
+ name: 'probe',
466
+ schema: '',
467
+ id: 'probe',
468
+ isView: false,
469
+ columns: [{ name: 'value', type: 'text', nullable: true, primaryKey: false }],
470
+ },
471
+ ],
472
+ };
473
+ const PROBE_ME = { email: 'probe@example.invalid' };
474
+ const PROBE_NOW = new Date(0);
475
+ /**
476
+ * Whether a bare value would be read back as exactly itself, both after `=`
477
+ * and inside `in (…)`.
478
+ *
479
+ * Asked of the parser rather than of a list of its words, because a list is a
480
+ * second copy of the grammar and the copy was the bug: the old rule was "any
481
+ * run of word characters, dots and hyphens is safe", and a card photo's slug
482
+ * `2023-mazda-cx-5-select-lf1015` carried `select`, which the parser reads as
483
+ * a clause. The request was refused and every card on the page lost its
484
+ * photo. A word the grammar learns later (`me`, a date phrase, a clause) is
485
+ * quoted here the day it is learned, with nothing to keep in step.
486
+ */
487
+ function readsAsItself(text) {
488
+ const read = (query) => parseQuery(query, { schema: PROBE_SCHEMA, me: PROBE_ME, now: PROBE_NOW });
489
+ const equal = read(`probe value = ${text}`);
490
+ const listed = read(`probe value in (${text})`);
491
+ const only = (result) => result.errors.length === 0 && result.query?.filter.groups.length === 1 && result.query.filter.groups[0].length === 1
492
+ ? result.query.filter.groups[0][0]
493
+ : undefined;
494
+ const one = only(equal);
495
+ const inList = only(listed);
496
+ return (one?.op === '=' &&
497
+ one.value === text &&
498
+ inList?.op === 'in' &&
499
+ Array.isArray(inList.value) &&
500
+ inList.value.length === 1 &&
501
+ inList.value[0] === text);
502
+ }
503
+ function unescapeValue(text) {
504
+ return text.replace(/\\(["'\\])/g, '$1');
505
+ }
506
+ /**
507
+ * Dates are bound as ISO strings because both target dialects compare them
508
+ * correctly that way: Postgres casts on the way in, and SQLite's documented
509
+ * text-date format is exactly ISO-8601. A `Date` object handed to a driver
510
+ * is at the mercy of whatever it decides to do with the local timezone.
511
+ */
512
+ function bindDate(d) {
513
+ const pad = (n) => String(n).padStart(2, '0');
514
+ const day = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
515
+ /* A midnight boundary is bound as the bare day. Text comparison sorts
516
+ '2026-08-30' before '2026-08-30 00:00:00', so appending the zero time
517
+ made every bare-DATE column compare wrong at the boundary — SQLite
518
+ counted a task due *today* as `< today`, i.e. overdue. Bare, the bound
519
+ is right for every storage shape at once: it sorts before any datetime
520
+ of its day, and a driver casting for a real date/timestamp column reads
521
+ it as midnight exactly as before. */
522
+ if (!d.getHours() && !d.getMinutes() && !d.getSeconds())
523
+ return day;
524
+ return `${day} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
525
+ }
526
+ function coerce(column, value, quoted, op) {
527
+ const kind = column ? columnKind(column.type) : 'other';
528
+ if (quoted)
529
+ return value; // an explicit quote is an explicit "this is text"
530
+ /* A key of a JSON column (`specs.capacity`) is compared as its text, which
531
+ is how an equality reads the same on every engine and for every value a
532
+ key holds (`true` for a yes); only an ordering against a number compares
533
+ numbers (viewLabels.ts picks the expression). */
534
+ if (column?.type === DEFINED_PATH_TYPE) {
535
+ const n = Number(value);
536
+ return op && RANGE_OPS.has(op) && value !== '' && Number.isFinite(n) ? n : value;
537
+ }
538
+ const truthy = /^(true|yes)$/i.test(value);
539
+ const falsy = /^(false|no)$/i.test(value);
540
+ if (kind === 'boolean') {
541
+ if (truthy || value === '1')
542
+ return true;
543
+ if (falsy || value === '0')
544
+ return false;
545
+ }
546
+ /* An integer past 2^53 is not a JavaScript number. `id = 9007199254740993`
547
+ through Number() binds …992 and finds the wrong row, or none, with
548
+ nothing about it that looks wrong — the same rounding the adapters
549
+ guard against on the way *out* (ids past 2^53 come back as strings).
550
+ Left as text, every driver hands the digits to the database whole and
551
+ the column's own type does the comparison. */
552
+ if (/^-?\d+$/.test(value) && !Number.isSafeInteger(Number(value)))
553
+ return value;
554
+ if (kind === 'number') {
555
+ /* SQLite has no boolean type, so a flag column is declared INTEGER and
556
+ stores 1 and 0. Someone typing `active = true` against it means the
557
+ integer, and binding the string 'true' would match nothing while
558
+ looking exactly like an empty table. */
559
+ if (truthy)
560
+ return 1;
561
+ if (falsy)
562
+ return 0;
563
+ const n = Number(value);
564
+ if (Number.isFinite(n) && value !== '')
565
+ return n;
566
+ }
567
+ if (kind === 'other') {
568
+ /* An untyped column compared to a bare number is still probably a number
569
+ — SQLite's dynamic typing makes untyped columns common. */
570
+ if (/^-?\d+(\.\d+)?$/.test(value))
571
+ return Number(value);
572
+ if (truthy)
573
+ return true;
574
+ if (falsy)
575
+ return false;
576
+ }
577
+ return value;
578
+ }
579
+ /* ---------- condition compilation ---------- */
580
+ function resolveColumn(table, name) {
581
+ if (!table)
582
+ return {};
583
+ const exact = table.columns.find((c) => c.name === name);
584
+ if (exact)
585
+ return { column: exact };
586
+ // Case-insensitive next: people type `Name` for `name` constantly, and
587
+ // both dialects fold unquoted identifiers anyway.
588
+ const insensitive = table.columns.find((c) => c.name.toLowerCase() === name.toLowerCase());
589
+ if (insensitive)
590
+ return { column: insensitive };
591
+ /* A reference path read where only the table's own columns can be: the
592
+ plain query has no joins, and "not a column" is true and useless when
593
+ the first hop plainly is one. The view parser admits paths before this
594
+ is reached, so this is said only where a path genuinely cannot go. */
595
+ const hop = name.split('.')[0];
596
+ if (name.includes('.') && table.columns.some((c) => c.name === hop)) {
597
+ return { error: `"${name}" is a path, and a condition here reads only ${table.name}'s own columns.` };
598
+ }
599
+ const near = nearest(name, table.columns.map((c) => c.name));
600
+ const suggestion = near ? ` Did you mean "${near}"?` : '';
601
+ return { error: `"${name}" is not a column on ${table.name}.${suggestion}` };
602
+ }
603
+ export { ANYONE } from './queryOperands.js';
604
+ /* A record's tags are asked only whether they hold a label: `tags = bug` would
605
+ compare the list itself, which is no question anyone means. */
606
+ function compileCondition(raw, ctx) {
607
+ const compiled = compileClause(raw, ctx);
608
+ const tagged = compiled.conditions?.find((c) => c.op !== 'has' && ctx.table?.columns.find((col) => col.name === c.column)?.type === TAGS_TYPE);
609
+ if (tagged)
610
+ return { error: `"${tagged.column}" is a record's tags: ask it with has, as in ${tagged.column} has bug.` };
611
+ return compiled;
612
+ }
613
+ function compileClause(raw, ctx) {
614
+ const { table, now } = ctx;
615
+ const text = raw.trim();
616
+ /**
617
+ * `column between a and b`.
618
+ *
619
+ * Expanded into `>= a` and `<= b` rather than carried as an operator of its
620
+ * own. The filter model would need a new `Op` in three adapters to express
621
+ * a range it can already express, and every one of them would have to get
622
+ * the inclusive ends right. Two conditions is the same set of rows, needs
623
+ * nothing new, and shows in the SQL panel as exactly what it does.
624
+ *
625
+ * Inclusive at both ends, which is what SQL's BETWEEN means and what
626
+ * someone typing `total between 100 and 500` expects of `500`.
627
+ */
628
+ const between = text.match(new RegExp(String.raw `^(${FIELD})\s+(not\s+)?between\s+(.+?)\s+and\s+(.+)$`, 'i'));
629
+ if (between) {
630
+ const [, field, not, lowRaw, highRaw] = between;
631
+ const { column, error } = resolveColumn(table, field);
632
+ if (error)
633
+ return { error };
634
+ const name = column?.name ?? field;
635
+ if (not) {
636
+ return {
637
+ error: `"not between" is not supported. Write it as two conditions: ` + `${name} < ${lowRaw.trim()} or ${name} > ${highRaw.trim()}`,
638
+ };
639
+ }
640
+ const low = unquoteValue(lowRaw);
641
+ const high = unquoteValue(highRaw);
642
+ if (!low.value || !high.value) {
643
+ return { error: `"between" needs two values, as in ${name} between 10 and 20` };
644
+ }
645
+ /* Each end coerced on its own, so a date range gets the same treatment a
646
+ single date comparison does — `between 2024-01-01 and 2024-06-30` binds
647
+ two timestamps, not two strings. */
648
+ return {
649
+ conditions: [
650
+ { column: name, op: '>=', value: coerce(column, low.value, low.quoted, '>=') },
651
+ { column: name, op: '<=', value: coerce(column, high.value, high.quoted, '<=') },
652
+ ],
653
+ };
654
+ }
655
+ /* `status is open`: the machine's states that are not final; `is not open`, the final ones (appMachine.ts). A
656
+ group the machine names reads the same way: `status is onBench` is its states, `is not onBench` the others. */
657
+ const open = text.match(new RegExp(String.raw `^(${FIELD})\s+is\s+(not\s+)?([A-Za-z]\w*)$`, 'i'));
658
+ if (open && open[3].toLowerCase() !== 'empty') {
659
+ const [, field, not, word] = open;
660
+ const { column, error } = resolveColumn(table, field);
661
+ if (error)
662
+ return { error };
663
+ const name = column?.name ?? field;
664
+ const states = column?.states ?? ctx.states?.[name];
665
+ if (!states)
666
+ return {
667
+ error: `"${name} is ${not ? 'not ' : ''}${word}" asks a machine's column, and "${name}" has no machine here. Name its states: ${name} in (…).`,
668
+ };
669
+ if (word.toLowerCase() !== 'open') {
670
+ const group = states.groups && Object.hasOwn(states.groups, word) ? states.groups[word] : undefined;
671
+ if (!group) {
672
+ const groups = Object.keys(states.groups ?? {});
673
+ return {
674
+ error: `"${name}" has no group "${word}": \`is\` reads open or one of its machine's groups` +
675
+ `${groups.length ? ` (${groups.join(', ')})` : ', and it names none'}. A single state is ${name} = ${word}.`,
676
+ };
677
+ }
678
+ return not
679
+ ? { conditions: group.map((state) => ({ column: name, op: '!=', value: state })) }
680
+ : { conditions: [{ column: name, op: 'in', value: [...group] }] };
681
+ }
682
+ const values = not ? states.final : states.open;
683
+ if (!values.length)
684
+ return {
685
+ conditions: [
686
+ { column: name, op: 'isNull' },
687
+ { column: name, op: 'isNotNull' },
688
+ ],
689
+ };
690
+ return { conditions: [{ column: name, op: 'in', value: [...values] }] };
691
+ }
692
+ const empty = text.match(new RegExp(String.raw `^([\w.$+"]+)\s+is\s+(not\s+)?empty$`, 'i'));
693
+ if (empty) {
694
+ const [, field, not] = empty;
695
+ const { column, error } = resolveColumn(table, field);
696
+ if (error)
697
+ return { error };
698
+ return { conditions: [{ column: column?.name ?? field, op: not ? 'isNotNull' : 'isNull' }] };
699
+ }
700
+ // Word operators are tried first because they are multi-character and a
701
+ // symbol match would otherwise never see them.
702
+ const word = text.match(new RegExp(String.raw `^(${FIELD})\s+(not\s+contains|contains|startswith|endswith|like|not\s+in|in|has)\s+(.+)$`, 'i'));
703
+ if (word) {
704
+ const [, field, opRaw, valueRaw] = word;
705
+ const { column, error } = resolveColumn(table, field);
706
+ if (error)
707
+ return { error };
708
+ const name = column?.name ?? field;
709
+ const key = opRaw.toLowerCase().replace(/\s+/g, ' ');
710
+ const negated = key.startsWith('not ');
711
+ const op = OPERATORS[negated ? key.slice(4) : key];
712
+ if (!op)
713
+ return { error: `Unknown operator "${opRaw}"` };
714
+ /* `tags has bug`, `tags has (bug, docs)`: a record's tags (TableMeta.tags)
715
+ hold any of the labels. A set, so a list never needs `in`. */
716
+ if (op === 'has') {
717
+ if (column?.type !== TAGS_TYPE && column?.type !== DEFINED_PATH_TYPE)
718
+ return { error: `"has" asks a record's tags or a key of a JSON column, and "${name}" is neither.` };
719
+ const list = valueRaw
720
+ .trim()
721
+ .replace(/^[([]\s*/, '')
722
+ .replace(/\s*[)\]]$/, '');
723
+ const labels = list
724
+ .split(',')
725
+ .map((s) => unquoteValue(s).value)
726
+ .filter((s) => s !== '');
727
+ if (!labels.length)
728
+ return { error: `"has" needs a tag, as in ${name} has bug` };
729
+ return { conditions: [{ column: name, op: 'has', value: labels }] };
730
+ }
731
+ if (op === 'in') {
732
+ // `in (a, b)` and `in [a, b]` read more naturally than a bare list.
733
+ const list = valueRaw
734
+ .trim()
735
+ .replace(/^[([]\s*/, '')
736
+ .replace(/\s*[)\]]$/, '');
737
+ const items = list
738
+ .split(',')
739
+ .map((s) => unquoteValue(s))
740
+ .filter((s) => s.value !== '');
741
+ if (!items.length)
742
+ return { error: `"${opRaw}" needs at least one value` };
743
+ const values = [];
744
+ for (const item of items) {
745
+ /* The signed-in person's memberships (`assignment_group_id in (me.group_member.group_id)`): the values read for this request through its own reader. Where nobody is meant, it is no text. */
746
+ if (!item.quoted && ME_SET.test(item.value)) {
747
+ if (ctx.me === undefined)
748
+ return {
749
+ error: `${item.value} is the signed-in person's rows, and nobody is signed in here. Quote it ("${item.value}") to mean the text.`,
750
+ };
751
+ const set = meSetOperand(ctx, column, item.value);
752
+ if (set.error)
753
+ return { error: set.error };
754
+ values.push(...set.values);
755
+ continue;
756
+ }
757
+ /* A group the machine names is asked with `is` (`status is onBench`), as `is open` is: one way to say it. */
758
+ const groups = item.quoted ? undefined : (column?.states ?? ctx.states?.[name])?.groups;
759
+ if (groups && Object.hasOwn(groups, item.value))
760
+ return { error: `"${item.value}" is a group of ${name}'s states; ask it with is: ${name} is ${item.value}.` };
761
+ if (!item.quoted && item.value === 'me') {
762
+ if (ctx.me === undefined)
763
+ return { error: NO_ONE_HERE };
764
+ const me = meOperand(ctx, column, '=');
765
+ if (me.error)
766
+ return { error: me.error };
767
+ if (me.value !== undefined)
768
+ values.push(me.value);
769
+ continue;
770
+ }
771
+ values.push(coerce(column, item.value, item.quoted));
772
+ }
773
+ if (negated) {
774
+ /* There is no NOT IN in the filter model, and adding one would mean a
775
+ new operator in every adapter. An expanded chain of != is the same
776
+ set and needs nothing new. */
777
+ return { conditions: values.map((v) => ({ column: name, op: '!=', value: v })) };
778
+ }
779
+ return { conditions: [{ column: name, op: 'in', value: values }] };
780
+ }
781
+ if (negated) {
782
+ return { error: `"not ${op}" is not supported yet — use "is empty" or a different operator` };
783
+ }
784
+ const { value, quoted } = unquoteValue(valueRaw);
785
+ // LIKE operands stay text even on a numeric column; `contains 5` on an
786
+ // integer is a substring question, not an equality one.
787
+ return { conditions: [{ column: name, op, value: quoted ? value : value }] };
788
+ }
789
+ const symbol = text.match(new RegExp(String.raw `^(${FIELD})\s*(!=|<=|>=|=|<|>)\s*(.+)$`));
790
+ if (symbol) {
791
+ const [, field, opRaw, valueRaw] = symbol;
792
+ const { column, error } = resolveColumn(table, field);
793
+ if (error)
794
+ return { error };
795
+ const name = column?.name ?? field;
796
+ const op = OPERATORS[opRaw];
797
+ const { value, quoted } = unquoteValue(valueRaw);
798
+ /* SQL's NULL is not a word this language compares with: `= null` bound
799
+ the four letters and matched nothing, which reads as an empty list
800
+ rather than a mistake — and in an App, as a count that is always 0. */
801
+ if (!quoted && /^null$/i.test(value)) {
802
+ return {
803
+ error: `"${field} ${opRaw} ${value}" compares with the word "${value}": write "${field} is ${op === '!=' ? 'not ' : ''}empty" for a missing value, or quote it ("${value}") to mean the text.`,
804
+ };
805
+ }
806
+ /* `me` is resolved here, in the parse the server runs for this request,
807
+ so the person a tab means is the session's and never the browser's. */
808
+ if (!quoted && ME_SET.test(value) && ctx.me !== undefined)
809
+ return { error: `${value} is a set of values: ask it with in, as in ${field} in (${value}).` };
810
+ /* A parse that was handed no person cannot say whose `me` it is. Read as the two letters, `owner != me` was
811
+ every row, silently, on any path that forgot the person: refused here, so it forgets loudly. A path where
812
+ nobody is signed in says so (`me: null`); a check before any request reads it as anyone's (ANYONE). */
813
+ if (!quoted && value === 'me' && ctx.me === undefined)
814
+ return { error: NO_ONE_HERE };
815
+ if (!quoted && value === 'me') {
816
+ const me = meOperand(ctx, column, op);
817
+ if (me.error)
818
+ return { error: me.error };
819
+ if (me.conditions)
820
+ return { conditions: me.conditions };
821
+ return { conditions: [{ column: name, op, value: me.value }] };
822
+ }
823
+ if (!quoted) {
824
+ const operand = columnOperand(ctx, column, op, value);
825
+ if (operand.error)
826
+ return { error: operand.error };
827
+ if (operand.other)
828
+ return { conditions: [{ column: name, op, other: operand.other.name }] };
829
+ }
830
+ /* `now` beside a date is the instant, bound as a date phrase's edges are, so `resolution_due_at < now` reads
831
+ the same on every engine. Bound as the word, PostgreSQL alone took it for the clock (SQLite compared the
832
+ text, and every row was before it), and the typed query refused what the segment had passed. */
833
+ if (!quoted && value.toLowerCase() === 'now' && column && columnKind(column.type) === 'date') {
834
+ nowRead = 2;
835
+ return { conditions: [{ column: name, op, value: bindDate(now) }] };
836
+ }
837
+ /* A date phrase is a range, not a point, so `=` becomes two conditions.
838
+ Getting this wrong is the failure this language is built to avoid: an
839
+ unparseable phrase sent through as a literal matches nothing, and a
840
+ filter that silently matches nothing looks exactly like a table that is
841
+ genuinely empty. */
842
+ if (!quoted) {
843
+ const window = dateWindow(value, now);
844
+ if (window) {
845
+ nowRead = /\b(?:hour|minute)s?$/.test(window.label) ? 2 : Math.max(nowRead, 1);
846
+ if (op === '=') {
847
+ return {
848
+ conditions: [
849
+ { column: name, op: '>=', value: bindDate(window.start) },
850
+ { column: name, op: '<', value: bindDate(window.end) },
851
+ ],
852
+ };
853
+ }
854
+ if (op === '>=' || op === '<')
855
+ return { conditions: [{ column: name, op, value: bindDate(window.start) }] };
856
+ /* The inclusive side of a range is half-open against the *next* day:
857
+ `<= today` must take everything before tomorrow (a datetime late
858
+ tonight included), and `> today` everything from tomorrow on.
859
+ Bound with their literal operators they read the boundary itself
860
+ as a row, which admitted tomorrow into "<= today". */
861
+ if (op === '>')
862
+ return { conditions: [{ column: name, op: '>=', value: bindDate(window.end) }] };
863
+ if (op === '<=')
864
+ return { conditions: [{ column: name, op: '<', value: bindDate(window.end) }] };
865
+ return { error: `"${value}" is a date range — use =, >=, >, <= or < with it, not ${opRaw}` };
866
+ }
867
+ }
868
+ /* A bare multi-word value is nearly always a missing pair of quotes. It
869
+ would otherwise bind as a literal that matches nothing, which reads as
870
+ "no results" rather than "you made a mistake". The message shows the
871
+ whole condition quoted, because an author (or an agent) copies the fix
872
+ rather than reading it; a date phrase is offered only where the column
873
+ holds dates, since on a status column it sent people looking for one. */
874
+ /* `total >> 5` leaves the value `> 5`. Telling an author to quote that, as the message below would, sends
875
+ them to search for a literal "> 5" and match nothing; the mistake is a second operator, so say so. */
876
+ if (!quoted && /^[<>=!]/.test(value)) {
877
+ return { error: `${opRaw}${value.match(/^[<>=!]+/)[0]} is not an operator; use one of =, !=, >, >=, < or <= before the value.` };
878
+ }
879
+ if (!quoted && /\s/.test(value)) {
880
+ const dated = column && columnKind(column.type) === 'date';
881
+ return {
882
+ error: `The value ${value} has a space, so quote it: ${field} ${opRaw} "${value}"${dated ? ', or use a date phrase like today or last 30 days' : ''}.`,
883
+ };
884
+ }
885
+ return { conditions: [{ column: name, op, value: coerce(column, value, quoted, op) }] };
886
+ }
887
+ /* A bare column name is a common half-typed state, and it is ambiguous in a
888
+ useful way: the user may be starting a filter, or may want projection and
889
+ not know the word for it. Naming both is how `show` gets discovered —
890
+ an error that only suggests filtering teaches that projection does not
891
+ exist. */
892
+ if (new RegExp(String.raw `^${FIELD}$`).test(text) && table) {
893
+ const { column } = resolveColumn(table, text);
894
+ if (column) {
895
+ return {
896
+ error: `What about "${text}"? To filter, try ${text} = … or ${text} is not empty. ` +
897
+ `To show only that column, put "show" first: show ${text}`,
898
+ };
899
+ }
900
+ /* A word alone where a condition stands is a segment's name (expandSegments wrote out every one it knew). */
901
+ const names = Object.keys(table.segments ?? {});
902
+ if (names.length && SEGMENT_WORD.test(text)) {
903
+ const near = nearest(text, names);
904
+ return {
905
+ error: `"${text}" is not a segment of ${table.name}. Its segments: ${names.join(', ')}.${near ? ` Did you mean "${near}"?` : ''}`,
906
+ };
907
+ }
908
+ }
909
+ return { error: `Could not read "${text}" — try e.g. status = open, or amount > 100` };
910
+ }
911
+ /* ---------- the parser ---------- */
912
+ /**
913
+ * What makes a clause word a column name instead: an operator right after it.
914
+ * A table with a column called `filter`, `sort` or `limit` is not unusual, and
915
+ * eating the word as a clause turns `limit is empty` into a nonsense query.
916
+ */
917
+ const FOLLOWED_BY_OPERATOR = /^\s*(?:=|!=|>=|<=|>|<|\bis\s+not\s+empty\b|\bis\s+empty\b|\bis\b|\bcontains\b|\bstartswith\b|\bendswith\b|\blike\b|\bnot\s+in\b|\bin\b|\bhas\b)/i;
918
+ const CLAUSE_RE = /\b(filter|where|show|select|sort|order\s+by|limit)\b/gi;
919
+ /** The hyphenated word a clause keyword at [start, end) is part of (`no-show`), or undefined where it stands alone. */
920
+ function hyphenatedValue(text, start, end) {
921
+ if (text[start - 1] !== '-' && text[end] !== '-')
922
+ return undefined;
923
+ let from = start;
924
+ let to = end;
925
+ while (from > 0 && /[\w-]/.test(text[from - 1]))
926
+ from -= 1;
927
+ while (to < text.length && /[\w-]/.test(text[to]))
928
+ to += 1;
929
+ return { start: from, end: to };
930
+ }
931
+ /**
932
+ * What to say about a clause given twice.
933
+ *
934
+ * `filter` is the one that combines, and it is not routed here: two conditions
935
+ * genuinely narrow one result, so they are joined with `and`. Nothing else
936
+ * does. `show a show b` used to compile as a single column literally named
937
+ * "a and b" and then fail as an unknown column — an error naming a word the
938
+ * user never typed, about a mistake it never mentioned.
939
+ *
940
+ * Refusing beats picking one. There is no reading of two `limit`s under which
941
+ * the discarded number was not asked for, and a silently dropped clause is the
942
+ * failure this language exists to avoid: the answer looks like an answer. So
943
+ * the clause is named, and — where the language has a way to say both things
944
+ * at once — the comma that does it is shown.
945
+ */
946
+ function repeatedClause(keyword) {
947
+ const combine = {
948
+ show: 'Name every column in one, separated by commas: show first_name, last_name',
949
+ sort: 'Name every field in one, separated by commas: sort invoice_date desc, id',
950
+ limit: 'Only one number can be the number of rows — keep the one you meant.',
951
+ };
952
+ return `Two "${keyword}" clauses, and the second cannot add to the first. ${combine[keyword] ?? 'Say it once.'}`;
953
+ }
954
+ function normalizeClause(word) {
955
+ const w = word.toLowerCase().replace(/\s+/g, ' ');
956
+ if (w === 'where')
957
+ return 'filter';
958
+ if (w === 'order by')
959
+ return 'sort';
960
+ if (w === 'select')
961
+ return 'show';
962
+ return w;
963
+ }
964
+ /* ---------- the parse, remembered ----------
965
+
966
+ A parse is a pure function of the text, the schema, `me`, `columnValues`
967
+ and — only when a date phrase is read — `now`. A busy server parses the same
968
+ few hundred texts over and over (every list tab, every dashboard tile), so
969
+ the answer is kept, per schema object (a reloaded App or a new catalog is a
970
+ different object and starts empty) and bounded, least recently used first.
971
+
972
+ `now` is left out of the key until a condition actually reads it. A phrase
973
+ that resolves by calendar day (`today`, `last 30 days`) is kept under the
974
+ local day, which is how `startOfDay` reads it, so a cached `today` is
975
+ re-resolved when the day turns. One that resolves to the millisecond
976
+ (`last 2 hours`, `last 30 minutes`) is never kept. The result is frozen: it is shared. */
977
+ const PARSE_KEPT = 512;
978
+ const NO_SCHEMA = {};
979
+ const parsesBySchema = new WeakMap();
980
+ /** Set by `compileCondition` when a date phrase resolved: 1 by day, 2 by the millisecond. */
981
+ let nowRead = 0;
982
+ let watching;
983
+ /** `work`'s value, and how finely it read the clock (0 not, 1 by day, 2 the instant) and whether it read the reader. */
984
+ export function readsOf(work) {
985
+ const outer = watching;
986
+ const reads = (watching = { clock: 0, reader: false });
987
+ try {
988
+ return { value: work(), ...reads };
989
+ }
990
+ finally {
991
+ watching = outer;
992
+ if (outer)
993
+ noteRead(reads.clock, reads.reader);
994
+ }
995
+ }
996
+ /** A read of the clock (1 the day, 2 the instant) or of the reader, told to whatever work is watching (readsOf). */
997
+ export function noteRead(clock, reader = false) {
998
+ if (!watching)
999
+ return;
1000
+ if (clock > watching.clock)
1001
+ watching.clock = clock;
1002
+ if (reader)
1003
+ watching.reader = true;
1004
+ }
1005
+ function freezeDeep(value) {
1006
+ if (value && typeof value === 'object' && !Object.isFrozen(value)) {
1007
+ Object.freeze(value);
1008
+ for (const inner of Object.values(value))
1009
+ freezeDeep(inner);
1010
+ }
1011
+ return value;
1012
+ }
1013
+ function dayKey(base, now) {
1014
+ const at = now ?? new Date();
1015
+ return `${base}\0${at.getFullYear()}-${at.getMonth()}-${at.getDate()}`;
1016
+ }
1017
+ export function parseQuery(input, options = {}) {
1018
+ // States handed in beside the text are rare (a board's lanes) and never kept: the cache is keyed by the catalog.
1019
+ if (options.states) {
1020
+ nowRead = 0;
1021
+ const result = freezeDeep(parseUncached(input, options));
1022
+ noteRead(nowRead);
1023
+ return result;
1024
+ }
1025
+ const schemaKey = options.schema ?? NO_SCHEMA;
1026
+ let kept = parsesBySchema.get(schemaKey);
1027
+ if (!kept)
1028
+ parsesBySchema.set(schemaKey, (kept = new Map()));
1029
+ const base = `${input}\0${options.columnValues === true ? 1 : 0}\0${options.me === undefined ? '' : JSON.stringify(options.me)}`;
1030
+ const marker = kept.get(base);
1031
+ const key = marker && 'dated' in marker ? dayKey(base, options.now) : base;
1032
+ const hit = marker && 'dated' in marker ? kept.get(key) : marker;
1033
+ if (hit && !('dated' in hit)) {
1034
+ kept.delete(key);
1035
+ kept.set(key, hit);
1036
+ noteRead(marker && 'dated' in marker ? 1 : 0);
1037
+ return hit;
1038
+ }
1039
+ nowRead = 0;
1040
+ const result = freezeDeep(parseUncached(input, options));
1041
+ const used = nowRead;
1042
+ noteRead(used);
1043
+ if (used !== 2) {
1044
+ if (used === 1)
1045
+ kept.set(base, { dated: true });
1046
+ const at = used === 1 ? dayKey(base, options.now) : base;
1047
+ kept.set(at, result);
1048
+ while (kept.size > PARSE_KEPT)
1049
+ kept.delete(kept.keys().next().value);
1050
+ }
1051
+ return result;
1052
+ }
1053
+ function parseUncached(input, options) {
1054
+ const text = input.trim();
1055
+ const now = options.now ?? new Date();
1056
+ if (!text)
1057
+ return { errors: [{ message: 'Start with a table name.' }] };
1058
+ const errors = [];
1059
+ /* Clause keywords are found in a copy with the *insides* of quoted values
1060
+ blanked out, and then used as offsets into the real text.
1061
+
1062
+ Scanning the raw string made a value that happens to contain a clause word
1063
+ into a clause: `name = "order by"` split at the quote, and the error came
1064
+ back "\"\"\" is not a column on customer. Did you mean \"id\"?" — confident,
1065
+ specific, and about a column nobody typed. The blanking preserves length
1066
+ and the quote characters themselves, so every offset still lines up with
1067
+ the original and error positions stay honest. */
1068
+ const scannable = maskQuoted(text);
1069
+ CLAUSE_RE.lastIndex = 0;
1070
+ const marks = [];
1071
+ let m;
1072
+ while ((m = CLAUSE_RE.exec(scannable))) {
1073
+ if (FOLLOWED_BY_OPERATOR.test(scannable.slice(CLAUSE_RE.lastIndex)))
1074
+ continue;
1075
+ /* A clause word inside an unquoted hyphenated value — `status = no-show` —
1076
+ is the value's, not a clause: taken as one, the query read `status = no-`
1077
+ and nothing more, or said the conditions after it were "not a column". */
1078
+ const glued = hyphenatedValue(scannable, m.index, CLAUSE_RE.lastIndex);
1079
+ if (glued) {
1080
+ const word = text.slice(glued.start, glued.end);
1081
+ const lead = /([\w.]+\s*(?:!=|>=|<=|=|>|<|\bcontains\b|\bstartswith\b|\bendswith\b|\blike\b))\s*$/i.exec(text.slice(0, glued.start))?.[1];
1082
+ errors.push({
1083
+ message: `"${word}" is a value, and unquoted its "${m[1]}" starts a clause: quote it — ${lead ? `${lead} ` : ''}"${word}".`,
1084
+ at: glued.start,
1085
+ });
1086
+ continue;
1087
+ }
1088
+ marks.push({ keyword: normalizeClause(m[1]), start: m.index, end: CLAUSE_RE.lastIndex });
1089
+ }
1090
+ const head = (marks.length ? text.slice(0, marks[0].start) : text).trim();
1091
+ const tableName = head.split(/\s+/)[0] ?? '';
1092
+ if (!tableName)
1093
+ return { errors: [{ message: 'Which table?' }] };
1094
+ /* The table has to resolve before anything else can be checked, since every
1095
+ column error is relative to it. An unqualified name is matched on the
1096
+ bare table name too, so `customers` finds `public.customers`. */
1097
+ let table;
1098
+ if (options.schema) {
1099
+ table = findTable(options.schema, tableName);
1100
+ if (!table) {
1101
+ const candidates = options.schema.tables.filter((t) => t.name === tableName);
1102
+ if (candidates.length === 1)
1103
+ table = candidates[0];
1104
+ else if (candidates.length > 1) {
1105
+ errors.push({
1106
+ message: `"${tableName}" is ambiguous — it exists in ${candidates.map((c) => c.schema).join(', ')}. Qualify it, e.g. ${candidates[0].id}.`,
1107
+ at: 0,
1108
+ });
1109
+ }
1110
+ else {
1111
+ const near = options.schema.tables
1112
+ .map((t) => ({ id: t.id, d: Math.min(distance(tableName, t.name), distance(tableName, t.id)) }))
1113
+ .sort((a, b) => a.d - b.d)[0];
1114
+ errors.push({
1115
+ message: `No table called "${tableName}".${near && near.d <= 3 ? ` Did you mean "${near.id}"?` : ''}`,
1116
+ at: 0,
1117
+ });
1118
+ }
1119
+ }
1120
+ }
1121
+ // Anything after the table name and before the first clause word is already
1122
+ // a filter. `where` stays as optional sugar, but making it mandatory would
1123
+ // be ceremony with nothing to disambiguate.
1124
+ const implied = head.slice(tableName.length).trim();
1125
+ const clauses = {};
1126
+ const clauseAt = {};
1127
+ if (implied) {
1128
+ clauses.filter = implied;
1129
+ clauseAt.filter = tableName.length + 1;
1130
+ }
1131
+ marks.forEach((mark, i) => {
1132
+ const bodyEnd = i + 1 < marks.length ? marks[i + 1].start : text.length;
1133
+ const body = text.slice(mark.end, bodyEnd).trim();
1134
+ if (clauses[mark.keyword] === undefined) {
1135
+ clauses[mark.keyword] = body;
1136
+ clauseAt[mark.keyword] = mark.end;
1137
+ return;
1138
+ }
1139
+ // Repeated conditions concatenate: `where a where b` is `a and b`, which
1140
+ // is also how the implied filter and an explicit `where` are joined.
1141
+ if (mark.keyword === 'filter') {
1142
+ clauses.filter = `${clauses.filter} and ${body}`;
1143
+ return;
1144
+ }
1145
+ /* Everything else refuses. Located at the repeat rather than the first
1146
+ one, because that is the word that made the query ambiguous — and the
1147
+ first clause is left standing, so the partial result the editor shows
1148
+ is still something that was typed. */
1149
+ errors.push({ message: repeatedClause(mark.keyword), at: mark.start });
1150
+ });
1151
+ const filter = { groups: [] };
1152
+ /* A segment's name is written out before the conditions are read (expandSegments), and an error inside what
1153
+ it wrote is said at the name, where the author can see it. */
1154
+ let said = (at) => at;
1155
+ if (clauses.filter && table?.segments) {
1156
+ const base = clauseAt.filter ?? 0;
1157
+ const expanded = expandSegments(clauses.filter, table.segments);
1158
+ if (expanded.error) {
1159
+ errors.push({ message: expanded.error.message, at: base + (expanded.error.at ?? 0) });
1160
+ clauses.filter = '';
1161
+ }
1162
+ else if (expanded.text !== clauses.filter) {
1163
+ clauses.filter = expanded.text;
1164
+ said = (at) => base + expanded.at(at - base);
1165
+ }
1166
+ }
1167
+ if (clauses.filter) {
1168
+ const tree = parseExpression(clauses.filter, clauseAt.filter ?? 0);
1169
+ const groups = toGroups(tree);
1170
+ if (groups.length > MAX_GROUPS) {
1171
+ errors.push({ message: `That expands to more than ${MAX_GROUPS} OR branches. Simplify it or filter in two steps.` });
1172
+ }
1173
+ else {
1174
+ for (const group of groups) {
1175
+ const conditions = [];
1176
+ for (const node of group) {
1177
+ if (node.kind !== 'cond')
1178
+ continue;
1179
+ const { conditions: got, error } = compileCondition(node.text, {
1180
+ table,
1181
+ now,
1182
+ columnValues: options.columnValues === true,
1183
+ ...(options.me !== undefined ? { me: options.me } : {}),
1184
+ ...(options.states ? { states: options.states } : {}),
1185
+ });
1186
+ if (error)
1187
+ errors.push({ message: error, at: said(node.at) });
1188
+ else if (got)
1189
+ conditions.push(...got);
1190
+ }
1191
+ if (conditions.length)
1192
+ filter.groups.push(conditions);
1193
+ }
1194
+ }
1195
+ }
1196
+ const orderBy = [];
1197
+ if (clauses.sort) {
1198
+ for (const part of clauses.sort.split(/\s*,\s*|\s+and\s+/i)) {
1199
+ const [field, dir] = part.trim().split(/\s+/);
1200
+ if (!field)
1201
+ continue;
1202
+ const { column, error } = resolveColumn(table, field);
1203
+ if (error)
1204
+ errors.push({ message: error, at: clauseAt.sort });
1205
+ else
1206
+ orderBy.push({ column: column?.name ?? field, direction: (dir ?? '').toLowerCase() === 'desc' ? 'desc' : 'asc' });
1207
+ }
1208
+ }
1209
+ const columns = [];
1210
+ for (const raw of (clauses.show ?? '').split(',')) {
1211
+ const field = raw.trim();
1212
+ if (!field)
1213
+ continue;
1214
+ const { column, error } = resolveColumn(table, field);
1215
+ if (error)
1216
+ errors.push({ message: error, at: clauseAt.show });
1217
+ else
1218
+ columns.push(column?.name ?? field);
1219
+ }
1220
+ let limit;
1221
+ if (clauses.limit) {
1222
+ const n = Number(clauses.limit.split(/\s+/)[0]);
1223
+ if (Number.isFinite(n) && n > 0)
1224
+ limit = Math.min(Math.floor(n), MAX_LIMIT);
1225
+ else
1226
+ errors.push({ message: `limit expects a number, got "${clauses.limit}"`, at: clauseAt.limit });
1227
+ }
1228
+ /* A query is returned even when there are errors, so the editor can show a
1229
+ partial result while someone is still typing. The caller decides whether
1230
+ errors are fatal; only a missing table makes the query unusable. */
1231
+ if (!tableName)
1232
+ return { errors };
1233
+ return {
1234
+ query: { table: table?.id ?? tableName, filter, columns, orderBy, limit },
1235
+ errors,
1236
+ };
1237
+ }
1238
+ /**
1239
+ * Render a parsed query back as the SQL it will become.
1240
+ *
1241
+ * This is for the explain panel, not for execution — the adapter builds the
1242
+ * real statement from the same `Filter` with bound parameters. Showing values
1243
+ * inline here is safe because the string never reaches a database, and it is
1244
+ * the whole point: seeing `country = $1` next to `country = 'Australia'` is
1245
+ * how someone learns what the tool is doing on their behalf.
1246
+ */
1247
+ export function explain(query) {
1248
+ const cols = query.columns.length ? query.columns.join(', ') : '*';
1249
+ const lines = [`SELECT ${cols}`, ` FROM ${query.table}`];
1250
+ if (query.filter.groups.length) {
1251
+ const groups = query.filter.groups.map((g) => g
1252
+ .map((c) => {
1253
+ if (c.op === 'isNull')
1254
+ return `${c.column} IS NULL`;
1255
+ if (c.op === 'isNotNull')
1256
+ return `${c.column} IS NOT NULL`;
1257
+ if (c.other !== undefined)
1258
+ return `${c.column} ${c.op} ${c.other}`;
1259
+ if (c.op === 'in') {
1260
+ const list = (Array.isArray(c.value) ? c.value : [c.value]).map(literal).join(', ');
1261
+ return `${c.column} IN (${list})`;
1262
+ }
1263
+ if (c.op === 'has') {
1264
+ const list = (Array.isArray(c.value) ? c.value : [c.value]).map(literal).join(', ');
1265
+ return `${c.column} HAS (${list})`;
1266
+ }
1267
+ if (c.op === 'like') {
1268
+ /* Shown with its ESCAPE clause always, unlike the searches above.
1269
+ There the clause is plumbing behind a plain search; here the
1270
+ escape character is part of the operator someone chose to write
1271
+ by hand, and the preview is where they find out it exists. */
1272
+ return `${c.column} LIKE ${literal(String(c.value ?? ''))} ESCAPE '${LIKE_ESCAPE}'`;
1273
+ }
1274
+ if (c.op === 'contains' || c.op === 'startsWith' || c.op === 'endsWith') {
1275
+ const pattern = likePattern(c.value, c.op);
1276
+ /* The ESCAPE clause is shown only when the value needed escaping.
1277
+ It is always sent — but printing it on every plain search would
1278
+ put dialect plumbing in front of the thing this preview exists
1279
+ to teach, and printing the escaped pattern *without* it would
1280
+ be a statement that means something else. */
1281
+ const escape = pattern.includes(LIKE_ESCAPE) ? ` ESCAPE '${LIKE_ESCAPE}'` : '';
1282
+ return `${c.column} LIKE ${literal(pattern)}${escape}`;
1283
+ }
1284
+ return `${c.column} ${c.op} ${literal(c.value)}`;
1285
+ })
1286
+ .join(' AND '));
1287
+ lines.push(` WHERE ${groups.length === 1 ? groups[0] : groups.map((g) => `(${g})`).join('\n OR ')}`);
1288
+ }
1289
+ if (query.orderBy.length) {
1290
+ lines.push(` ORDER BY ${query.orderBy.map((o) => `${o.column} ${o.direction.toUpperCase()}`).join(', ')}`);
1291
+ }
1292
+ if (query.limit !== undefined)
1293
+ lines.push(` LIMIT ${query.limit}`);
1294
+ return lines.join('\n');
1295
+ }
1296
+ function literal(value) {
1297
+ if (value === null || value === undefined)
1298
+ return 'NULL';
1299
+ if (typeof value === 'number' || typeof value === 'boolean')
1300
+ return String(value);
1301
+ return `'${String(value).replace(/'/g, "''")}'`;
1302
+ }