@engenty/wizards-runtime 0.0.0-stage → 0.7.6

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 (523) hide show
  1. package/LICENSE +110 -0
  2. package/README.md +214 -2
  3. package/apps/runtime/dist/agents/architect.js +444 -0
  4. package/apps/runtime/dist/agents/studio-assistant.js +883 -0
  5. package/apps/runtime/dist/agents/subscription.js +207 -0
  6. package/apps/runtime/dist/agents/thought.js +21 -0
  7. package/apps/runtime/dist/agents/website.js +480 -0
  8. package/apps/runtime/dist/app.js +359 -0
  9. package/apps/runtime/dist/apple/wizards-apple +0 -0
  10. package/apps/runtime/dist/assistant/browser.js +26 -0
  11. package/apps/runtime/dist/assistant/docs.js +158 -0
  12. package/apps/runtime/dist/assistant/knows.js +353 -0
  13. package/apps/runtime/dist/assistant/primer.js +26 -0
  14. package/apps/runtime/dist/assistant/runs.js +24 -0
  15. package/apps/runtime/dist/auth/account.js +175 -0
  16. package/apps/runtime/dist/auth/device.js +112 -0
  17. package/apps/runtime/dist/auth/index.js +358 -0
  18. package/apps/runtime/dist/auth/keys.js +55 -0
  19. package/apps/runtime/dist/auth/local-ticket.js +26 -0
  20. package/apps/runtime/dist/authoring/example.js +74 -0
  21. package/apps/runtime/dist/authoring/guide.js +433 -0
  22. package/apps/runtime/dist/authoring/ops.js +117 -0
  23. package/apps/runtime/dist/browser/fast/action-space.js +160 -0
  24. package/apps/runtime/dist/browser/fast/classifier.js +91 -0
  25. package/apps/runtime/dist/browser/fast/decide.js +171 -0
  26. package/apps/runtime/dist/browser/fast/driver.js +141 -0
  27. package/apps/runtime/dist/browser/fast/instructions.js +33 -0
  28. package/apps/runtime/dist/browser/fast/run.js +249 -0
  29. package/apps/runtime/dist/browser/fast/span-picker.js +153 -0
  30. package/apps/runtime/dist/browser/fast/text-helper.js +178 -0
  31. package/apps/runtime/dist/browser/snapshot.js +335 -0
  32. package/apps/runtime/dist/cli/account.js +385 -0
  33. package/apps/runtime/dist/cli/autostart.js +182 -0
  34. package/apps/runtime/dist/cli/autoupdate.js +221 -0
  35. package/apps/runtime/dist/cli/clients.js +44 -0
  36. package/apps/runtime/dist/cli/connect.js +540 -0
  37. package/apps/runtime/dist/cli/environment.js +95 -0
  38. package/apps/runtime/dist/cli/exec.js +45 -0
  39. package/apps/runtime/dist/cli/features.js +50 -0
  40. package/apps/runtime/dist/cli/home.js +88 -0
  41. package/apps/runtime/dist/cli/install-env.js +33 -0
  42. package/apps/runtime/dist/cli/machine.js +107 -0
  43. package/apps/runtime/dist/cli/main.js +231 -0
  44. package/apps/runtime/dist/cli/mcp.js +136 -0
  45. package/apps/runtime/dist/cli/menu.js +202 -0
  46. package/apps/runtime/dist/cli/processes.js +72 -0
  47. package/apps/runtime/dist/cli/report.js +66 -0
  48. package/apps/runtime/dist/cli/sandbox.js +77 -0
  49. package/apps/runtime/dist/cli/setup.js +335 -0
  50. package/apps/runtime/dist/cli/start.js +276 -0
  51. package/apps/runtime/dist/cli/status.js +212 -0
  52. package/apps/runtime/dist/cli/ui.js +59 -0
  53. package/apps/runtime/dist/cli/uninstall-main.js +20 -0
  54. package/apps/runtime/dist/cli/uninstall.js +231 -0
  55. package/apps/runtime/dist/cli/update.js +86 -0
  56. package/apps/runtime/dist/cli/validate.js +75 -0
  57. package/apps/runtime/dist/cli/versions.js +123 -0
  58. package/apps/runtime/dist/connectors/builtin.js +86 -0
  59. package/apps/runtime/dist/connectors/external.js +409 -0
  60. package/apps/runtime/dist/connectors/gmail.js +107 -0
  61. package/apps/runtime/dist/connectors/imap.js +261 -0
  62. package/apps/runtime/dist/connectors/index.js +325 -0
  63. package/apps/runtime/dist/connectors/mail.js +72 -0
  64. package/apps/runtime/dist/connectors/outlook.js +90 -0
  65. package/apps/runtime/dist/connectors/plugin-connections.js +343 -0
  66. package/apps/runtime/dist/connectors/via-account.js +65 -0
  67. package/apps/runtime/dist/credits/credits.js +100 -0
  68. package/apps/runtime/dist/credits/estimate.js +152 -0
  69. package/apps/runtime/dist/db/adopt.js +149 -0
  70. package/apps/runtime/dist/db/client.js +173 -0
  71. package/apps/runtime/dist/db/control-schema.js +133 -0
  72. package/apps/runtime/dist/db/migrations/control/0000_last_absorbing_man.sql +47 -0
  73. package/apps/runtime/dist/db/migrations/control/0001_gorgeous_nicolaos.sql +41 -0
  74. package/apps/runtime/dist/db/migrations/control/0002_secret_penance.sql +1 -0
  75. package/apps/runtime/dist/db/migrations/control/0003_nasty_mordo.sql +3 -0
  76. package/apps/runtime/dist/db/migrations/control/0004_breezy_eddie_brock.sql +6 -0
  77. package/apps/runtime/dist/db/migrations/control/0005_chilly_oracle.sql +1 -0
  78. package/apps/runtime/dist/db/migrations/control/0006_sleepy_mojo.sql +1 -0
  79. package/apps/runtime/dist/db/migrations/control/0007_common_the_hand.sql +15 -0
  80. package/apps/runtime/dist/db/migrations/control/0008_runner.sql +8 -0
  81. package/apps/runtime/dist/db/migrations/control/0009_plugin_claim.sql +9 -0
  82. package/apps/runtime/dist/db/migrations/control/0010_tan_snowbird.sql +6 -0
  83. package/apps/runtime/dist/db/migrations/control/meta/0000_snapshot.json +311 -0
  84. package/apps/runtime/dist/db/migrations/control/meta/0001_snapshot.json +599 -0
  85. package/apps/runtime/dist/db/migrations/control/meta/0002_snapshot.json +607 -0
  86. package/apps/runtime/dist/db/migrations/control/meta/0003_snapshot.json +311 -0
  87. package/apps/runtime/dist/db/migrations/control/meta/0004_snapshot.json +350 -0
  88. package/apps/runtime/dist/db/migrations/control/meta/0005_snapshot.json +358 -0
  89. package/apps/runtime/dist/db/migrations/control/meta/0006_snapshot.json +365 -0
  90. package/apps/runtime/dist/db/migrations/control/meta/0007_snapshot.json +460 -0
  91. package/apps/runtime/dist/db/migrations/control/meta/0008_snapshot.json +505 -0
  92. package/apps/runtime/dist/db/migrations/control/meta/0009_snapshot.json +561 -0
  93. package/apps/runtime/dist/db/migrations/control/meta/0010_snapshot.json +600 -0
  94. package/apps/runtime/dist/db/migrations/control/meta/_journal.json +83 -0
  95. package/apps/runtime/dist/db/migrations/tenant/0000_eminent_freak.sql +188 -0
  96. package/apps/runtime/dist/db/migrations/tenant/0001_salty_master_chief.sql +1 -0
  97. package/apps/runtime/dist/db/migrations/tenant/0002_cloudy_firebird.sql +1 -0
  98. package/apps/runtime/dist/db/migrations/tenant/0003_brown_warbound.sql +52 -0
  99. package/apps/runtime/dist/db/migrations/tenant/0004_romantic_nocturne.sql +6 -0
  100. package/apps/runtime/dist/db/migrations/tenant/0005_reflective_forgotten_one.sql +2 -0
  101. package/apps/runtime/dist/db/migrations/tenant/0006_hard_war_machine.sql +30 -0
  102. package/apps/runtime/dist/db/migrations/tenant/0007_space_data.sql +43 -0
  103. package/apps/runtime/dist/db/migrations/tenant/0008_run_event_note.sql +1 -0
  104. package/apps/runtime/dist/db/migrations/tenant/0009_knowledge.sql +104 -0
  105. package/apps/runtime/dist/db/migrations/tenant/0010_plugin_connection.sql +15 -0
  106. package/apps/runtime/dist/db/migrations/tenant/0011_runners.sql +2 -0
  107. package/apps/runtime/dist/db/migrations/tenant/0012_assistant_chat.sql +40 -0
  108. package/apps/runtime/dist/db/migrations/tenant/0013_run_origin.sql +1 -0
  109. package/apps/runtime/dist/db/migrations/tenant/0014_run_answer.sql +2 -0
  110. package/apps/runtime/dist/db/migrations/tenant/0015_device_calls.sql +13 -0
  111. package/apps/runtime/dist/db/migrations/tenant/0016_even_trish_tilby.sql +12 -0
  112. package/apps/runtime/dist/db/migrations/tenant/meta/0000_snapshot.json +1323 -0
  113. package/apps/runtime/dist/db/migrations/tenant/meta/0001_snapshot.json +1330 -0
  114. package/apps/runtime/dist/db/migrations/tenant/meta/0002_snapshot.json +1337 -0
  115. package/apps/runtime/dist/db/migrations/tenant/meta/0003_snapshot.json +1605 -0
  116. package/apps/runtime/dist/db/migrations/tenant/meta/0004_snapshot.json +1645 -0
  117. package/apps/runtime/dist/db/migrations/tenant/meta/0005_snapshot.json +1659 -0
  118. package/apps/runtime/dist/db/migrations/tenant/meta/0006_snapshot.json +1696 -0
  119. package/apps/runtime/dist/db/migrations/tenant/meta/0007_snapshot.json +2012 -0
  120. package/apps/runtime/dist/db/migrations/tenant/meta/0008_snapshot.json +2019 -0
  121. package/apps/runtime/dist/db/migrations/tenant/meta/0009_snapshot.json +2837 -0
  122. package/apps/runtime/dist/db/migrations/tenant/meta/0010_snapshot.json +2942 -0
  123. package/apps/runtime/dist/db/migrations/tenant/meta/0011_snapshot.json +2956 -0
  124. package/apps/runtime/dist/db/migrations/tenant/meta/0012_snapshot.json +3132 -0
  125. package/apps/runtime/dist/db/migrations/tenant/meta/0013_snapshot.json +3139 -0
  126. package/apps/runtime/dist/db/migrations/tenant/meta/0014_snapshot.json +3154 -0
  127. package/apps/runtime/dist/db/migrations/tenant/meta/0015_snapshot.json +3243 -0
  128. package/apps/runtime/dist/db/migrations/tenant/meta/0016_snapshot.json +3326 -0
  129. package/apps/runtime/dist/db/migrations/tenant/meta/_journal.json +125 -0
  130. package/apps/runtime/dist/db/schema.js +602 -0
  131. package/apps/runtime/dist/debug.js +197 -0
  132. package/apps/runtime/dist/docs/dev/index.md +41 -0
  133. package/apps/runtime/dist/docs/dev/meta.json +7 -0
  134. package/apps/runtime/dist/docs/dev/plugins/connections.md +74 -0
  135. package/apps/runtime/dist/docs/dev/plugins/events.md +89 -0
  136. package/apps/runtime/dist/docs/dev/plugins/examples.md +101 -0
  137. package/apps/runtime/dist/docs/dev/plugins/index.md +78 -0
  138. package/apps/runtime/dist/docs/dev/plugins/layout.md +90 -0
  139. package/apps/runtime/dist/docs/dev/plugins/meta.json +28 -0
  140. package/apps/runtime/dist/docs/dev/plugins/quick-start.md +94 -0
  141. package/apps/runtime/dist/docs/dev/plugins/reference.md +348 -0
  142. package/apps/runtime/dist/docs/dev/plugins/reloading.md +76 -0
  143. package/apps/runtime/dist/docs/dev/plugins/routes.md +185 -0
  144. package/apps/runtime/dist/docs/dev/plugins/runners.md +230 -0
  145. package/apps/runtime/dist/docs/dev/plugins/server.md +281 -0
  146. package/apps/runtime/dist/docs/dev/plugins/shipping.md +79 -0
  147. package/apps/runtime/dist/docs/dev/plugins/space.md +297 -0
  148. package/apps/runtime/dist/docs/dev/plugins/starters.md +70 -0
  149. package/apps/runtime/dist/docs/dev/plugins/studio.md +363 -0
  150. package/apps/runtime/dist/docs/dev/plugins/styling.md +129 -0
  151. package/apps/runtime/dist/docs/dev/plugins/tables.md +104 -0
  152. package/apps/runtime/dist/docs/dev/plugins/tools.md +139 -0
  153. package/apps/runtime/dist/docs/dev/wizard-format.md +125 -0
  154. package/apps/runtime/dist/docs/meta.json +3 -0
  155. package/apps/runtime/dist/docs/user/ai-apps.md +112 -0
  156. package/apps/runtime/dist/docs/user/assistant.md +77 -0
  157. package/apps/runtime/dist/docs/user/build-a-wizard.md +116 -0
  158. package/apps/runtime/dist/docs/user/command-line.md +86 -0
  159. package/apps/runtime/dist/docs/user/connectors.md +90 -0
  160. package/apps/runtime/dist/docs/user/create-account.md +52 -0
  161. package/apps/runtime/dist/docs/user/data.md +66 -0
  162. package/apps/runtime/dist/docs/user/first-start.md +60 -0
  163. package/apps/runtime/dist/docs/user/index.md +49 -0
  164. package/apps/runtime/dist/docs/user/install.md +123 -0
  165. package/apps/runtime/dist/docs/user/local-only.md +42 -0
  166. package/apps/runtime/dist/docs/user/meta.json +32 -0
  167. package/apps/runtime/dist/docs/user/models.md +156 -0
  168. package/apps/runtime/dist/docs/user/plugins.md +71 -0
  169. package/apps/runtime/dist/docs/user/run.md +98 -0
  170. package/apps/runtime/dist/docs/user/server.md +52 -0
  171. package/apps/runtime/dist/docs/user/space.md +126 -0
  172. package/apps/runtime/dist/docs/user/steps.md +220 -0
  173. package/apps/runtime/dist/docs/user/test-and-share.md +224 -0
  174. package/apps/runtime/dist/docs/user/troubleshooting.md +67 -0
  175. package/apps/runtime/dist/docs/user/wordpress.md +121 -0
  176. package/apps/runtime/dist/documents/parse.js +246 -0
  177. package/apps/runtime/dist/engenty/connections-external/build-connector.js +193 -0
  178. package/apps/runtime/dist/engenty/connections-external/errors.js +11 -0
  179. package/apps/runtime/dist/engenty/connections-external/import-service.js +270 -0
  180. package/apps/runtime/dist/engenty/connections-external/importer/classify.js +34 -0
  181. package/apps/runtime/dist/engenty/connections-external/importer/map-auth.js +185 -0
  182. package/apps/runtime/dist/engenty/connections-external/importer/normalize-mcp.js +63 -0
  183. package/apps/runtime/dist/engenty/connections-external/importer/normalize-openapi.js +130 -0
  184. package/apps/runtime/dist/engenty/connections-external/invoke/http-invoker.js +143 -0
  185. package/apps/runtime/dist/engenty/connections-external/invoke/mcp-client.js +143 -0
  186. package/apps/runtime/dist/engenty/connections-external/net/guarded-fetch.js +119 -0
  187. package/apps/runtime/dist/engenty/connections-external/oauth-dcr.js +88 -0
  188. package/apps/runtime/dist/engenty/connections-external/registry-client.js +336 -0
  189. package/apps/runtime/dist/engenty/connections-external/registry-source.js +82 -0
  190. package/apps/runtime/dist/engenty/connections-external/types.js +7 -0
  191. package/apps/runtime/dist/engenty/connections-github/connector.js +209 -0
  192. package/apps/runtime/dist/engenty/connections-google/connectors/calendar.js +381 -0
  193. package/apps/runtime/dist/engenty/connections-google/connectors/contacts.js +94 -0
  194. package/apps/runtime/dist/engenty/connections-google/connectors/drive.js +290 -0
  195. package/apps/runtime/dist/engenty/connections-google/connectors/gmail.js +567 -0
  196. package/apps/runtime/dist/engenty/connections-google/definitions.js +11 -0
  197. package/apps/runtime/dist/engenty/connections-google/shared.js +73 -0
  198. package/apps/runtime/dist/engenty/connections-hubspot/connector.js +140 -0
  199. package/apps/runtime/dist/engenty/connections-microsoft/action.js +17 -0
  200. package/apps/runtime/dist/engenty/connections-microsoft/graph.js +114 -0
  201. package/apps/runtime/dist/engenty/connections-microsoft/onedrive.js +286 -0
  202. package/apps/runtime/dist/engenty/connections-microsoft/outlook.js +492 -0
  203. package/apps/runtime/dist/engenty/connections-s3/s3.js +328 -0
  204. package/apps/runtime/dist/engenty/connections-sdk/files-capability.js +86 -0
  205. package/apps/runtime/dist/engenty/connections-sdk/oauth2.js +150 -0
  206. package/apps/runtime/dist/engenty/connections-sdk/registry.js +89 -0
  207. package/apps/runtime/dist/engenty/connections-sdk/storage-capability.js +84 -0
  208. package/apps/runtime/dist/engenty/connections-sdk/types.js +30 -0
  209. package/apps/runtime/dist/engenty/connections-slack/connector.js +360 -0
  210. package/apps/runtime/dist/engenty/csv-import/csv-matrix.js +178 -0
  211. package/apps/runtime/dist/engenty/csv-import/import-sources.js +87 -0
  212. package/apps/runtime/dist/engenty/csv-import/parse-csv.js +129 -0
  213. package/apps/runtime/dist/engenty/csv-import/serialize-csv.js +20 -0
  214. package/apps/runtime/dist/engenty/csv-import/types.js +1 -0
  215. package/apps/runtime/dist/engenty/csv-import/xlsx-workbook.js +159 -0
  216. package/apps/runtime/dist/engenty/doc-converter/interface.js +9 -0
  217. package/apps/runtime/dist/engenty/doc-converter/page-break.js +92 -0
  218. package/apps/runtime/dist/engenty/doc-converter/providers/local/index.js +72 -0
  219. package/apps/runtime/dist/engenty/document-scanner/schemas/classifier.js +30 -0
  220. package/apps/runtime/dist/engenty/document-scanner/schemas/index.js +4 -0
  221. package/apps/runtime/dist/engenty/document-scanner/schemas/invoice.js +73 -0
  222. package/apps/runtime/dist/engenty/document-scanner/schemas/receipt.js +63 -0
  223. package/apps/runtime/dist/engenty/document-scanner/schemas/shared.js +40 -0
  224. package/apps/runtime/dist/engenty/search-index/chunks.js +164 -0
  225. package/apps/runtime/dist/engenty/search-index/contracts.js +9 -0
  226. package/apps/runtime/dist/engenty/shims/connections-sdk.js +39 -0
  227. package/apps/runtime/dist/engenty/shims/plugin-sdk.js +1 -0
  228. package/apps/runtime/dist/engenty/shims/web-ingest.js +19 -0
  229. package/apps/runtime/dist/engenty/web-ingest/ssrf.js +277 -0
  230. package/apps/runtime/dist/engine/asks.js +82 -0
  231. package/apps/runtime/dist/engine/decide.js +82 -0
  232. package/apps/runtime/dist/engine/device.js +194 -0
  233. package/apps/runtime/dist/engine/events.js +128 -0
  234. package/apps/runtime/dist/engine/input.js +208 -0
  235. package/apps/runtime/dist/engine/owner.js +32 -0
  236. package/apps/runtime/dist/engine/prepare.js +90 -0
  237. package/apps/runtime/dist/engine/push.js +58 -0
  238. package/apps/runtime/dist/engine/requirements.js +355 -0
  239. package/apps/runtime/dist/engine/resources.js +198 -0
  240. package/apps/runtime/dist/engine/runner.js +970 -0
  241. package/apps/runtime/dist/engine/steps.js +1000 -0
  242. package/apps/runtime/dist/engine/surface.js +91 -0
  243. package/apps/runtime/dist/engine/template.js +240 -0
  244. package/apps/runtime/dist/engine/types.js +4 -0
  245. package/apps/runtime/dist/env.js +279 -0
  246. package/apps/runtime/dist/features.js +123 -0
  247. package/apps/runtime/dist/files/blobs.js +22 -0
  248. package/apps/runtime/dist/files/objects.js +86 -0
  249. package/apps/runtime/dist/files/storage.js +120 -0
  250. package/apps/runtime/dist/files/web-images.js +101 -0
  251. package/apps/runtime/dist/film/project.js +257 -0
  252. package/apps/runtime/dist/film/skills.js +91 -0
  253. package/apps/runtime/dist/film/sounds.js +122 -0
  254. package/apps/runtime/dist/film/step.js +289 -0
  255. package/apps/runtime/dist/film/tools.js +99 -0
  256. package/apps/runtime/dist/geocode.js +58 -0
  257. package/apps/runtime/dist/harness/antigravity.js +129 -0
  258. package/apps/runtime/dist/harness/apple.js +270 -0
  259. package/apps/runtime/dist/harness/claude.js +147 -0
  260. package/apps/runtime/dist/harness/codex.js +229 -0
  261. package/apps/runtime/dist/harness/cursor.js +93 -0
  262. package/apps/runtime/dist/harness/env.js +124 -0
  263. package/apps/runtime/dist/harness/index.js +95 -0
  264. package/apps/runtime/dist/harness/model.js +279 -0
  265. package/apps/runtime/dist/harness/prompt.js +121 -0
  266. package/apps/runtime/dist/harness/terminal.js +194 -0
  267. package/apps/runtime/dist/harness/types.js +7 -0
  268. package/apps/runtime/dist/index.js +113 -0
  269. package/apps/runtime/dist/limits.js +81 -0
  270. package/apps/runtime/dist/link-preview.js +60 -0
  271. package/apps/runtime/dist/log.js +106 -0
  272. package/apps/runtime/dist/manage.js +249 -0
  273. package/apps/runtime/dist/mcp/auth.js +161 -0
  274. package/apps/runtime/dist/mcp/bridge.js +59 -0
  275. package/apps/runtime/dist/mcp/flow-app.js +97 -0
  276. package/apps/runtime/dist/mcp/handler.js +54 -0
  277. package/apps/runtime/dist/mcp/scopes.js +14 -0
  278. package/apps/runtime/dist/mcp/seen.js +65 -0
  279. package/apps/runtime/dist/mcp/tools.js +572 -0
  280. package/apps/runtime/dist/media/conversation.js +167 -0
  281. package/apps/runtime/dist/media/ffmpeg.js +121 -0
  282. package/apps/runtime/dist/media/generate.js +120 -0
  283. package/apps/runtime/dist/media/marking.js +121 -0
  284. package/apps/runtime/dist/media/transcribe.js +198 -0
  285. package/apps/runtime/dist/model-errors.js +42 -0
  286. package/apps/runtime/dist/models.js +1164 -0
  287. package/apps/runtime/dist/plugin.js +61 -0
  288. package/apps/runtime/dist/plugins/channels.js +78 -0
  289. package/apps/runtime/dist/plugins/claims.js +56 -0
  290. package/apps/runtime/dist/plugins/discovery.js +155 -0
  291. package/apps/runtime/dist/plugins/events.js +63 -0
  292. package/apps/runtime/dist/plugins/jobs.js +64 -0
  293. package/apps/runtime/dist/plugins/loader.js +508 -0
  294. package/apps/runtime/dist/plugins/migrations.js +69 -0
  295. package/apps/runtime/dist/plugins/pro-keys.js +34 -0
  296. package/apps/runtime/dist/plugins/pro-state.js +83 -0
  297. package/apps/runtime/dist/plugins/pro.js +310 -0
  298. package/apps/runtime/dist/plugins/public.js +44 -0
  299. package/apps/runtime/dist/plugins/registry.js +122 -0
  300. package/apps/runtime/dist/plugins/runs.js +210 -0
  301. package/apps/runtime/dist/render/browser-path.js +54 -0
  302. package/apps/runtime/dist/render/chromium.js +98 -0
  303. package/apps/runtime/dist/render/convert.js +158 -0
  304. package/apps/runtime/dist/render/downloads.js +299 -0
  305. package/apps/runtime/dist/render/guard.js +25 -0
  306. package/apps/runtime/dist/render/shaped.js +137 -0
  307. package/apps/runtime/dist/routes/api.js +440 -0
  308. package/apps/runtime/dist/routes/debug.js +42 -0
  309. package/apps/runtime/dist/routes/delivery.js +90 -0
  310. package/apps/runtime/dist/routes/internal.js +81 -0
  311. package/apps/runtime/dist/routes/lang.js +25 -0
  312. package/apps/runtime/dist/routes/marketplace.js +43 -0
  313. package/apps/runtime/dist/routes/plugin-public.js +113 -0
  314. package/apps/runtime/dist/routes/plugins.js +212 -0
  315. package/apps/runtime/dist/routes/runs.js +1422 -0
  316. package/apps/runtime/dist/routes/studio.js +1629 -0
  317. package/apps/runtime/dist/routes/wizard-stream.js +30 -0
  318. package/apps/runtime/dist/running.js +39 -0
  319. package/apps/runtime/dist/sandbox/agentos-package.js +73 -0
  320. package/apps/runtime/dist/sandbox/agentos.js +162 -0
  321. package/apps/runtime/dist/sandbox/docker.js +45 -0
  322. package/apps/runtime/dist/sandbox/index.js +52 -0
  323. package/apps/runtime/dist/sandbox/install.js +122 -0
  324. package/apps/runtime/dist/sandbox/mastra.js +71 -0
  325. package/apps/runtime/dist/sandbox/types.js +1 -0
  326. package/apps/runtime/dist/secrets/crypto.js +28 -0
  327. package/apps/runtime/dist/secrets/signing.js +52 -0
  328. package/apps/runtime/dist/secrets/vault.js +59 -0
  329. package/apps/runtime/dist/services/access.js +88 -0
  330. package/apps/runtime/dist/services/api.js +248 -0
  331. package/apps/runtime/dist/services/architect.js +95 -0
  332. package/apps/runtime/dist/services/assistant-chats.js +311 -0
  333. package/apps/runtime/dist/services/brand.js +85 -0
  334. package/apps/runtime/dist/services/cloud.js +456 -0
  335. package/apps/runtime/dist/services/draft-events.js +27 -0
  336. package/apps/runtime/dist/services/engenty-shapes.js +71 -0
  337. package/apps/runtime/dist/services/errors.js +29 -0
  338. package/apps/runtime/dist/services/files.js +178 -0
  339. package/apps/runtime/dist/services/knowledge-model.js +559 -0
  340. package/apps/runtime/dist/services/knowledge.js +721 -0
  341. package/apps/runtime/dist/services/links.js +16 -0
  342. package/apps/runtime/dist/services/marketplace.js +492 -0
  343. package/apps/runtime/dist/services/onboarding.js +152 -0
  344. package/apps/runtime/dist/services/package-file.js +82 -0
  345. package/apps/runtime/dist/services/package.js +170 -0
  346. package/apps/runtime/dist/services/profile.js +51 -0
  347. package/apps/runtime/dist/services/project-files.js +439 -0
  348. package/apps/runtime/dist/services/project-index.js +707 -0
  349. package/apps/runtime/dist/services/projects.js +387 -0
  350. package/apps/runtime/dist/services/relevance.js +58 -0
  351. package/apps/runtime/dist/services/runners.js +178 -0
  352. package/apps/runtime/dist/services/runs.js +524 -0
  353. package/apps/runtime/dist/services/shares.js +130 -0
  354. package/apps/runtime/dist/services/site.js +270 -0
  355. package/apps/runtime/dist/services/space-categories.js +861 -0
  356. package/apps/runtime/dist/services/space-data.js +792 -0
  357. package/apps/runtime/dist/services/space-origin.js +207 -0
  358. package/apps/runtime/dist/services/spaces.js +657 -0
  359. package/apps/runtime/dist/services/translations.js +306 -0
  360. package/apps/runtime/dist/services/widgets.js +34 -0
  361. package/apps/runtime/dist/services/wizard-icon.js +157 -0
  362. package/apps/runtime/dist/services/wizards.js +420 -0
  363. package/apps/runtime/dist/settings.js +16 -0
  364. package/apps/runtime/dist/store/errors.js +4 -0
  365. package/apps/runtime/dist/store/index.js +299 -0
  366. package/apps/runtime/dist/store/shared-lists.js +123 -0
  367. package/apps/runtime/dist/tenants/control.js +238 -0
  368. package/apps/runtime/dist/tenants/tenant.js +26 -0
  369. package/apps/runtime/dist/texts.js +207 -0
  370. package/apps/runtime/dist/tools/browser.js +703 -0
  371. package/apps/runtime/dist/tools/connector.js +204 -0
  372. package/apps/runtime/dist/tools/index.js +291 -0
  373. package/apps/runtime/dist/tools/mail.js +104 -0
  374. package/apps/runtime/dist/tools/net-guard.js +170 -0
  375. package/apps/runtime/dist/tools/pages.js +81 -0
  376. package/apps/runtime/dist/tools/plugin.js +180 -0
  377. package/apps/runtime/dist/tools/project.js +154 -0
  378. package/apps/runtime/dist/tools/shared.js +69 -0
  379. package/apps/runtime/dist/tools/store.js +238 -0
  380. package/apps/runtime/dist/update.js +113 -0
  381. package/apps/runtime/dist/widgets/bundle.js +99 -0
  382. package/apps/runtime/dist/widgets/render.js +329 -0
  383. package/apps/runtime/dist/widgets/runtime.js +108 -0
  384. package/apps/runtime/dist/widgets/script.js +30 -0
  385. package/apps/runtime/dist/widgets/step.js +223 -0
  386. package/apps/web/dist/ai-labels/ai-black.svg +20 -0
  387. package/apps/web/dist/ai-labels/ai-generated-black.svg +31 -0
  388. package/apps/web/dist/ai-labels/ai-generated-white.svg +32 -0
  389. package/apps/web/dist/ai-labels/ai-modified-black.svg +30 -0
  390. package/apps/web/dist/ai-labels/ai-modified-white.svg +31 -0
  391. package/apps/web/dist/ai-labels/ai-white.svg +21 -0
  392. package/apps/web/dist/assets/AboutDialog-DYgXPagX.js +1 -0
  393. package/apps/web/dist/assets/ChangelogDialog-BXqBW8j3.js +1 -0
  394. package/apps/web/dist/assets/ChatView-CK2--tsE.js +10 -0
  395. package/apps/web/dist/assets/EditorPage-jvcwyXbU.js +3 -0
  396. package/apps/web/dist/assets/FlowPreview-CusP7ci9.js +1 -0
  397. package/apps/web/dist/assets/Harness-BrP-ENHg.css +1 -0
  398. package/apps/web/dist/assets/Harness-CV9uYzbv.js +16 -0
  399. package/apps/web/dist/assets/NewWizardChannels-BakW5q6s.js +1 -0
  400. package/apps/web/dist/assets/NewWizardPage-BoA-LJuW.js +7 -0
  401. package/apps/web/dist/assets/OnboardingPage-DaD1XnOe.js +1 -0
  402. package/apps/web/dist/assets/ProjectPage-BFgk1DEv.js +1 -0
  403. package/apps/web/dist/assets/PublicRunner-C9MwEvxy.js +1 -0
  404. package/apps/web/dist/assets/ReadOnly-DC2Kp7l8.js +1 -0
  405. package/apps/web/dist/assets/RunDrawer-CY6NZSrI.js +4 -0
  406. package/apps/web/dist/assets/RunnerView-BT26XgcY.js +1 -0
  407. package/apps/web/dist/assets/SettingsPage-AFt9IDLt.js +1 -0
  408. package/apps/web/dist/assets/SetupPage-DtFYAbwJ.js +1 -0
  409. package/apps/web/dist/assets/SharePage-t2xwNS61.js +1 -0
  410. package/apps/web/dist/assets/changelog-CATQFxCQ.js +1 -0
  411. package/apps/web/dist/assets/engenty-eXZ-EAwd.js +1 -0
  412. package/apps/web/dist/assets/esm-DXeMu8Lb.js +7 -0
  413. package/apps/web/dist/assets/fluffy-engenty-ClRou7yu.js +456 -0
  414. package/apps/web/dist/assets/index-AfUa8xKD.css +1 -0
  415. package/apps/web/dist/assets/index-Drw7FMTQ.js +14 -0
  416. package/apps/web/dist/assets/jsx-runtime-BNakU3Ej.js +1 -0
  417. package/apps/web/dist/assets/lock-R_CfU0Xo.js +1 -0
  418. package/apps/web/dist/assets/oss-credits-cZ9NUaLZ.js +1 -0
  419. package/apps/web/dist/assets/pdfmake-Cx9scTbD.js +228 -0
  420. package/apps/web/dist/assets/phone-CgrIS4_-.js +1 -0
  421. package/apps/web/dist/assets/preload-helper-CNEI7N0N.js +1 -0
  422. package/apps/web/dist/assets/qr-scanner-worker.min-CJaXo3yu.js +98 -0
  423. package/apps/web/dist/assets/qr-scanner.min-C_UI0gH-.js +1 -0
  424. package/apps/web/dist/assets/rolldown-runtime-hePW80VL.js +1 -0
  425. package/apps/web/dist/assets/settings-menu-2IMOOlLl.js +1 -0
  426. package/apps/web/dist/assets/ui-dzGvc3xk.js +137 -0
  427. package/apps/web/dist/assets/upload-BSuTilFF.js +1 -0
  428. package/apps/web/dist/assets/vfs_fonts-DllDSYt1.js +1 -0
  429. package/apps/web/dist/embed.js +266 -0
  430. package/apps/web/dist/favicon.svg +1 -0
  431. package/apps/web/dist/icons/apple-touch-icon.png +0 -0
  432. package/apps/web/dist/icons/icon-192.png +0 -0
  433. package/apps/web/dist/icons/icon-512.png +0 -0
  434. package/apps/web/dist/icons/icon-maskable-512.png +0 -0
  435. package/apps/web/dist/index.html +48 -0
  436. package/apps/web/dist/install.sh +577 -0
  437. package/apps/web/dist/manifest.webmanifest +21 -0
  438. package/apps/web/dist/mcp/flow.html +980 -0
  439. package/apps/web/dist/sw.js +15 -0
  440. package/bin/wizards.mjs +288 -0
  441. package/modules/README.md +15 -0
  442. package/node_modules/@engenty/wizards/dist/plugin/index.d.ts +1150 -0
  443. package/node_modules/@engenty/wizards/dist/plugin/index.js +4 -0
  444. package/node_modules/@engenty/wizards/dist/plugin/studio.d.ts +322 -0
  445. package/node_modules/@engenty/wizards/dist/plugin/studio.js +4 -0
  446. package/node_modules/@engenty/wizards/dist/plugin/thread.d.ts +174 -0
  447. package/node_modules/@engenty/wizards/dist/plugin/thread.js +821 -0
  448. package/node_modules/@engenty/wizards/package.json +11 -0
  449. package/node_modules/@engenty-wizards/shared/dist/assistant.d.ts +174 -0
  450. package/node_modules/@engenty-wizards/shared/dist/assistant.js +65 -0
  451. package/node_modules/@engenty-wizards/shared/dist/changes.d.ts +22 -0
  452. package/node_modules/@engenty-wizards/shared/dist/changes.js +61 -0
  453. package/node_modules/@engenty-wizards/shared/dist/connectors.d.ts +77 -0
  454. package/node_modules/@engenty-wizards/shared/dist/connectors.js +2 -0
  455. package/node_modules/@engenty-wizards/shared/dist/decision.d.ts +76 -0
  456. package/node_modules/@engenty-wizards/shared/dist/decision.js +147 -0
  457. package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +2922 -0
  458. package/node_modules/@engenty-wizards/shared/dist/definition.js +1920 -0
  459. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-format.d.ts +5 -0
  460. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-format.js +132 -0
  461. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-value.d.ts +11 -0
  462. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-value.js +209 -0
  463. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns.d.ts +532 -0
  464. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns.js +217 -0
  465. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/index.d.ts +3 -0
  466. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/index.js +3 -0
  467. package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-lexical.d.ts +14 -0
  468. package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-lexical.js +166 -0
  469. package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-rank.d.ts +33 -0
  470. package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-rank.js +99 -0
  471. package/node_modules/@engenty-wizards/shared/dist/feature-flags.d.ts +78 -0
  472. package/node_modules/@engenty-wizards/shared/dist/feature-flags.js +70 -0
  473. package/node_modules/@engenty-wizards/shared/dist/features.d.ts +41 -0
  474. package/node_modules/@engenty-wizards/shared/dist/features.js +89 -0
  475. package/node_modules/@engenty-wizards/shared/dist/fit.d.ts +71 -0
  476. package/node_modules/@engenty-wizards/shared/dist/fit.js +382 -0
  477. package/node_modules/@engenty-wizards/shared/dist/json-schema.d.ts +14 -0
  478. package/node_modules/@engenty-wizards/shared/dist/json-schema.js +66 -0
  479. package/node_modules/@engenty-wizards/shared/dist/knowledge.d.ts +199 -0
  480. package/node_modules/@engenty-wizards/shared/dist/knowledge.js +68 -0
  481. package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.d.ts +35 -0
  482. package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.js +107 -0
  483. package/node_modules/@engenty-wizards/shared/dist/marketplace-search.d.ts +20 -0
  484. package/node_modules/@engenty-wizards/shared/dist/marketplace-search.js +136 -0
  485. package/node_modules/@engenty-wizards/shared/dist/marketplace.d.ts +227 -0
  486. package/node_modules/@engenty-wizards/shared/dist/marketplace.js +354 -0
  487. package/node_modules/@engenty-wizards/shared/dist/merge.d.ts +7 -0
  488. package/node_modules/@engenty-wizards/shared/dist/merge.js +40 -0
  489. package/node_modules/@engenty-wizards/shared/dist/numbers.d.ts +24 -0
  490. package/node_modules/@engenty-wizards/shared/dist/numbers.js +54 -0
  491. package/node_modules/@engenty-wizards/shared/dist/projects.d.ts +224 -0
  492. package/node_modules/@engenty-wizards/shared/dist/projects.js +319 -0
  493. package/node_modules/@engenty-wizards/shared/dist/providers.d.ts +101 -0
  494. package/node_modules/@engenty-wizards/shared/dist/providers.js +390 -0
  495. package/node_modules/@engenty-wizards/shared/dist/run.d.ts +680 -0
  496. package/node_modules/@engenty-wizards/shared/dist/run.js +620 -0
  497. package/node_modules/@engenty-wizards/shared/dist/runner-texts.d.ts +546 -0
  498. package/node_modules/@engenty-wizards/shared/dist/runner-texts.js +523 -0
  499. package/node_modules/@engenty-wizards/shared/dist/runners.d.ts +93 -0
  500. package/node_modules/@engenty-wizards/shared/dist/runners.js +146 -0
  501. package/node_modules/@engenty-wizards/shared/dist/shape.d.ts +102 -0
  502. package/node_modules/@engenty-wizards/shared/dist/shape.js +318 -0
  503. package/node_modules/@engenty-wizards/shared/dist/space-data.d.ts +89 -0
  504. package/node_modules/@engenty-wizards/shared/dist/space-data.js +47 -0
  505. package/node_modules/@engenty-wizards/shared/dist/store.d.ts +117 -0
  506. package/node_modules/@engenty-wizards/shared/dist/store.js +81 -0
  507. package/node_modules/@engenty-wizards/shared/dist/surface.d.ts +115 -0
  508. package/node_modules/@engenty-wizards/shared/dist/surface.js +442 -0
  509. package/node_modules/@engenty-wizards/shared/dist/texts.d.ts +45 -0
  510. package/node_modules/@engenty-wizards/shared/dist/texts.js +54 -0
  511. package/node_modules/@engenty-wizards/shared/dist/translation.d.ts +23 -0
  512. package/node_modules/@engenty-wizards/shared/dist/translation.js +154 -0
  513. package/node_modules/@engenty-wizards/shared/dist/wizard-package.d.ts +48 -0
  514. package/node_modules/@engenty-wizards/shared/dist/wizard-package.js +49 -0
  515. package/node_modules/@engenty-wizards/shared/dist/workspace.d.ts +16 -0
  516. package/node_modules/@engenty-wizards/shared/dist/workspace.js +51 -0
  517. package/node_modules/@engenty-wizards/shared/package.json +10 -0
  518. package/package.json +77 -4
  519. package/plugin/.claude-plugin/plugin.json +9 -0
  520. package/plugin/.mcp.json +8 -0
  521. package/plugin/commands/wizard.md +8 -0
  522. package/plugin/skills/build-wizard/SKILL.md +27 -0
  523. package/plugin/skills/use-wizard/SKILL.md +18 -0
@@ -0,0 +1,861 @@
1
+ import { CATEGORY_TYPES, dayOf, } from "@engenty-wizards/shared/knowledge";
2
+ import { readNumber } from "@engenty-wizards/shared/numbers";
3
+ import { note } from "@engenty-wizards/shared/run";
4
+ import { and, asc, eq, inArray, isNotNull, or, sql } from "drizzle-orm";
5
+ import { nanoid } from "nanoid";
6
+ import { db, schema } from "../db/client.js";
7
+ import { currentTenant } from "../tenants/tenant.js";
8
+ import { requireWritableProject } from "./access.js";
9
+ import { notFound, ServiceError } from "./errors.js";
10
+ import { queueIndex } from "./project-index.js";
11
+ export function itemKey(ref) {
12
+ if ("pageId" in ref) {
13
+ return `p:${ref.pageId}`;
14
+ }
15
+ if ("rowId" in ref) {
16
+ return `r:${ref.rowId}`;
17
+ }
18
+ if ("tableId" in ref) {
19
+ return `t:${ref.tableId}`;
20
+ }
21
+ return `f:${ref.fileId}`;
22
+ }
23
+ function keyOfRow(r) {
24
+ return r.pageId
25
+ ? `p:${r.pageId}`
26
+ : r.rowId
27
+ ? `r:${r.rowId}`
28
+ : r.tableId
29
+ ? `t:${r.tableId}`
30
+ : `f:${r.fileId}`;
31
+ }
32
+ function refWhere(ref) {
33
+ const t = schema.spaceItemCategory;
34
+ if ("pageId" in ref) {
35
+ return eq(t.pageId, ref.pageId);
36
+ }
37
+ if ("rowId" in ref) {
38
+ return eq(t.rowId, ref.rowId);
39
+ }
40
+ if ("tableId" in ref) {
41
+ return eq(t.tableId, ref.tableId);
42
+ }
43
+ return eq(t.fileId, ref.fileId);
44
+ }
45
+ // --- typed values ---------------------------------------------------------------
46
+ /** A number with what stands around it, as `readNumber` reads it: `38 h`, `19 %`, `1,5`. */
47
+ export function numberOf(value) {
48
+ return readNumber(value, { units: true });
49
+ }
50
+ /** A day, or a day and a time, as milliseconds (UTC, as written: no time zone is applied). */
51
+ export function dateOf(value) {
52
+ if (typeof value === "number") {
53
+ return Number.isFinite(value) ? value : null;
54
+ }
55
+ const day = dayOf(value);
56
+ if (!day) {
57
+ return null;
58
+ }
59
+ const [y, m, d] = day.split("-").map(Number);
60
+ const time = typeof value === "string" ? /[T\s](\d{1,2}):(\d{2})/.exec(value) : null;
61
+ const at = Date.UTC(y, m - 1, d, time ? Number(time[1]) : 0, time ? Number(time[2]) : 0);
62
+ return Number.isNaN(at) ? null : at;
63
+ }
64
+ /** A date as it is shown and given to steps: `2026-03-14`, or `2026-03-14 07:30`. */
65
+ export function dateText(at) {
66
+ const iso = new Date(at).toISOString();
67
+ return iso.slice(11, 16) === "00:00"
68
+ ? iso.slice(0, 10)
69
+ : `${iso.slice(0, 10)} ${iso.slice(11, 16)}`;
70
+ }
71
+ function boolOf(value) {
72
+ if (typeof value === "boolean") {
73
+ return value;
74
+ }
75
+ const s = String(value ?? "")
76
+ .trim()
77
+ .toLowerCase();
78
+ if (["ja", "yes", "true", "x", "1", "wahr", "y", "j"].includes(s)) {
79
+ return true;
80
+ }
81
+ if (["nein", "no", "false", "0", "falsch", "n", "-"].includes(s)) {
82
+ return false;
83
+ }
84
+ return null;
85
+ }
86
+ /** A value checked against its category's type; null when it is not one. */
87
+ function typed(category, value) {
88
+ if (value === null || value === undefined || value === "") {
89
+ return null;
90
+ }
91
+ switch (category.type) {
92
+ case "choice": {
93
+ const s = String(value).replace(/\s+/g, " ").trim().slice(0, 200);
94
+ return s ? { choice: s } : null;
95
+ }
96
+ case "text": {
97
+ const s = String(value).replace(/\s+/g, " ").trim().slice(0, 300);
98
+ return s ? { text: s } : null;
99
+ }
100
+ case "number": {
101
+ const n = numberOf(value);
102
+ return n === null ? null : { num: n };
103
+ }
104
+ case "date": {
105
+ const at = dateOf(value);
106
+ return at === null ? null : { at };
107
+ }
108
+ case "boolean": {
109
+ const b = boolOf(value);
110
+ return b === null ? null : { bool: b };
111
+ }
112
+ default:
113
+ return null;
114
+ }
115
+ }
116
+ function cellOf(row, values) {
117
+ if (row.valueId) {
118
+ return values.get(row.valueId) ?? null;
119
+ }
120
+ if (row.at !== null) {
121
+ return dateText(row.at);
122
+ }
123
+ if (row.num !== null) {
124
+ return row.num;
125
+ }
126
+ if (row.bool !== null) {
127
+ return row.bool;
128
+ }
129
+ return row.text;
130
+ }
131
+ // --- the categories of a space ---------------------------------------------------
132
+ export async function categoryRows(projectId) {
133
+ return db.query.spaceCategory.findMany({
134
+ where: eq(schema.spaceCategory.projectId, projectId),
135
+ orderBy: [asc(schema.spaceCategory.position), asc(schema.spaceCategory.name)],
136
+ });
137
+ }
138
+ /** A category by its name, as a step or a plugin writes it: case and spaces do not matter. */
139
+ function byName(rows, name) {
140
+ const want = name.replace(/\s+/g, " ").trim().toLowerCase();
141
+ return rows.find((c) => c.name.toLowerCase() === want);
142
+ }
143
+ const norm = (v) => v
144
+ .toLowerCase()
145
+ .normalize("NFKD")
146
+ .replace(/[̀-ͯ]/g, "")
147
+ .replace(/[^a-z0-9]/g, "");
148
+ /** Whether two values look like one written two ways: "Graz-Süd" and "Graz Süd". */
149
+ function alike(a, b) {
150
+ const x = norm(a);
151
+ const y = norm(b);
152
+ if (!(x && y)) {
153
+ return false;
154
+ }
155
+ if (x === y) {
156
+ return true;
157
+ }
158
+ if (Math.min(x.length, y.length) < 6 || Math.abs(x.length - y.length) > 1) {
159
+ return false;
160
+ }
161
+ // One letter apart.
162
+ let i = 0;
163
+ while (i < x.length && x[i] === y[i]) {
164
+ i++;
165
+ }
166
+ return (x.slice(i + 1) === y.slice(i + 1) ||
167
+ x.slice(i) === y.slice(i + 1) ||
168
+ x.slice(i + 1) === y.slice(i));
169
+ }
170
+ /** The space's categories with their values, how many items have each, and their ranges. */
171
+ export async function listCategories(projectId) {
172
+ const rows = await categoryRows(projectId);
173
+ if (!rows.length) {
174
+ return [];
175
+ }
176
+ const ids = rows.map((r) => r.id);
177
+ const t = schema.spaceItemCategory;
178
+ const [values, counts, ranges] = await Promise.all([
179
+ db.all(sql `
180
+ SELECT v.id, v.category_id, v.value, v.position, v.summary IS NOT NULL AS summary,
181
+ (SELECT count(*) FROM space_item_category i WHERE i.value_id = v.id) AS n
182
+ FROM space_category_value v
183
+ WHERE v.category_id IN (${sql.join(ids.map((id) => sql `${id}`), sql `, `)})
184
+ ORDER BY v.position, v.value`),
185
+ db
186
+ .select({
187
+ categoryId: t.categoryId,
188
+ n: sql `count(DISTINCT coalesce(${t.pageId}, ${t.rowId}, ${t.tableId}, ${t.fileId}))`,
189
+ })
190
+ .from(t)
191
+ .where(inArray(t.categoryId, ids))
192
+ .groupBy(t.categoryId),
193
+ db
194
+ .select({
195
+ categoryId: t.categoryId,
196
+ minNum: sql `min(${t.num})`,
197
+ maxNum: sql `max(${t.num})`,
198
+ minAt: sql `min(${t.at})`,
199
+ maxAt: sql `max(${t.at})`,
200
+ })
201
+ .from(t)
202
+ .where(inArray(t.categoryId, ids))
203
+ .groupBy(t.categoryId),
204
+ ]);
205
+ return rows.map((c) => {
206
+ const own = values.filter((v) => v.category_id === c.id);
207
+ const range = ranges.find((r) => r.categoryId === c.id);
208
+ const category = {
209
+ id: c.id,
210
+ name: c.name,
211
+ type: c.type,
212
+ unit: c.unit,
213
+ multiple: c.multiple,
214
+ ordered: c.ordered,
215
+ hint: c.hint,
216
+ origin: c.origin,
217
+ proposed: c.proposed,
218
+ tableId: c.tableId,
219
+ columnId: c.columnId,
220
+ count: counts.find((x) => x.categoryId === c.id)?.n ?? 0,
221
+ values: own.map((v) => ({
222
+ id: v.id,
223
+ value: v.value,
224
+ count: v.n,
225
+ summary: Boolean(v.summary),
226
+ })),
227
+ range: c.type === "number" && range?.minNum !== null && range?.minNum !== undefined
228
+ ? { min: range.minNum, max: range.maxNum ?? range.minNum }
229
+ : c.type === "date" && range?.minAt !== null && range?.minAt !== undefined
230
+ ? { min: dateText(range.minAt), max: dateText(range.maxAt ?? range.minAt) }
231
+ : null,
232
+ alike: [],
233
+ };
234
+ category.alike = alikeValues(category.values);
235
+ return category;
236
+ });
237
+ }
238
+ /** Pairs of a choice's values that look like one value written two ways, to offer merging. */
239
+ function alikeValues(list) {
240
+ const pairs = [];
241
+ for (let i = 0; i < list.length; i++) {
242
+ for (let j = i + 1; j < list.length; j++) {
243
+ if (alike(list[i].value, list[j].value)) {
244
+ pairs.push([list[i].id, list[j].id]);
245
+ }
246
+ }
247
+ }
248
+ return pairs.slice(0, 20);
249
+ }
250
+ const MAX_CATEGORIES = 60;
251
+ function checkDraft(draft) {
252
+ const name = draft.name.replace(/\s+/g, " ").trim().slice(0, 80);
253
+ if (!name) {
254
+ throw new ServiceError("invalid", note("categoryNeedsName"));
255
+ }
256
+ if (!CATEGORY_TYPES.includes(draft.type)) {
257
+ throw new ServiceError("invalid", note("unknownCategoryType", { type: draft.type }));
258
+ }
259
+ return name;
260
+ }
261
+ async function addValues(categoryId, values) {
262
+ const existing = await db.query.spaceCategoryValue.findMany({
263
+ where: eq(schema.spaceCategoryValue.categoryId, categoryId),
264
+ columns: { id: true, value: true, position: true },
265
+ });
266
+ let position = existing.reduce((max, v) => Math.max(max, v.position + 1), 0);
267
+ const ids = new Map(existing.map((v) => [v.value, v.id]));
268
+ for (const raw of values) {
269
+ const value = raw.replace(/\s+/g, " ").trim().slice(0, 200);
270
+ if (!value || ids.has(value)) {
271
+ continue;
272
+ }
273
+ // Differently cased, it is the same value.
274
+ const same = [...ids.keys()].find((v) => v.toLowerCase() === value.toLowerCase());
275
+ if (same) {
276
+ ids.set(value, ids.get(same));
277
+ continue;
278
+ }
279
+ const id = nanoid(12);
280
+ await db
281
+ .insert(schema.spaceCategoryValue)
282
+ .values({ id, categoryId, value, position: position++ })
283
+ .onConflictDoNothing();
284
+ ids.set(value, id);
285
+ }
286
+ return ids;
287
+ }
288
+ /**
289
+ * Adds a category, or, by a name the space has already, leaves that one as it is apart from
290
+ * values it does not know yet. `proposed`: shown to be confirmed, never filled before.
291
+ */
292
+ export async function putCategory(projectId, draft, opts = {}) {
293
+ const name = checkDraft(draft);
294
+ const rows = await categoryRows(projectId);
295
+ const known = byName(rows, name);
296
+ if (known) {
297
+ if (draft.values?.length && known.type === "choice") {
298
+ await addValues(known.id, draft.values);
299
+ }
300
+ // A proposal taken again by its origin, or made by a person, is taken.
301
+ if (known.proposed && !opts.proposed) {
302
+ await db
303
+ .update(schema.spaceCategory)
304
+ .set({ proposed: false, updatedAt: new Date() })
305
+ .where(eq(schema.spaceCategory.id, known.id));
306
+ return { ...known, proposed: false };
307
+ }
308
+ return known;
309
+ }
310
+ if (rows.length >= MAX_CATEGORIES) {
311
+ throw new ServiceError("refused", note("spaceMostCategories", { count: MAX_CATEGORIES }));
312
+ }
313
+ const id = nanoid(12);
314
+ const [row] = await db
315
+ .insert(schema.spaceCategory)
316
+ .values({
317
+ id,
318
+ tenantId: currentTenant(),
319
+ projectId,
320
+ name,
321
+ type: draft.type,
322
+ unit: draft.type === "number" ? (draft.unit?.trim().slice(0, 20) ?? null) : null,
323
+ multiple: draft.type === "choice" && Boolean(draft.multiple),
324
+ ordered: draft.type === "choice" && Boolean(draft.ordered),
325
+ hint: draft.hint?.trim().slice(0, 120) || null,
326
+ origin: opts.origin ?? null,
327
+ proposed: Boolean(opts.proposed),
328
+ tableId: draft.tableId ?? null,
329
+ columnId: draft.tableId ? (draft.columnId ?? null) : null,
330
+ position: rows.reduce((max, r) => Math.max(max, r.position + 1), 0),
331
+ })
332
+ .returning();
333
+ if (draft.type === "choice" && draft.values?.length) {
334
+ await addValues(id, draft.values);
335
+ }
336
+ if (row.tableId && row.columnId && !row.proposed) {
337
+ await syncColumnCategory(row);
338
+ }
339
+ return row;
340
+ }
341
+ export async function categoryRow(categoryId) {
342
+ const row = await db.query.spaceCategory.findFirst({
343
+ where: and(eq(schema.spaceCategory.id, categoryId), eq(schema.spaceCategory.tenantId, currentTenant())),
344
+ });
345
+ if (!row) {
346
+ throw notFound();
347
+ }
348
+ return row;
349
+ }
350
+ /** Every item that has the category: their text in the index starts with it. */
351
+ async function reindexHolders(categoryId) {
352
+ const rows = await db
353
+ .selectDistinct({
354
+ pageId: schema.spaceItemCategory.pageId,
355
+ rowId: schema.spaceItemCategory.rowId,
356
+ tableId: schema.spaceItemCategory.tableId,
357
+ fileId: schema.spaceItemCategory.fileId,
358
+ })
359
+ .from(schema.spaceItemCategory)
360
+ .where(eq(schema.spaceItemCategory.categoryId, categoryId));
361
+ const tables = new Set();
362
+ for (const r of rows) {
363
+ if (r.rowId) {
364
+ const row = await db.query.spaceTableRow.findFirst({
365
+ where: eq(schema.spaceTableRow.id, r.rowId),
366
+ columns: { tableId: true },
367
+ });
368
+ if (row) {
369
+ tables.add(row.tableId);
370
+ }
371
+ }
372
+ else if (r.tableId) {
373
+ tables.add(r.tableId);
374
+ }
375
+ else {
376
+ queueIndex(keyOfRow(r));
377
+ }
378
+ }
379
+ for (const id of tables) {
380
+ queueIndex(`t:${id}`);
381
+ }
382
+ }
383
+ export async function updateCategory(categoryId, patch) {
384
+ const row = await categoryRow(categoryId);
385
+ await requireWritableProject(row.projectId);
386
+ const name = patch.name !== undefined ? checkDraft({ name: patch.name, type: row.type }) : row.name;
387
+ if (name !== row.name) {
388
+ const clash = byName(await categoryRows(row.projectId), name);
389
+ if (clash && clash.id !== row.id) {
390
+ throw new ServiceError("invalid", note("categoryAlreadyExists", { name }));
391
+ }
392
+ }
393
+ await db
394
+ .update(schema.spaceCategory)
395
+ .set({
396
+ name,
397
+ ...(patch.unit !== undefined && row.type === "number"
398
+ ? { unit: patch.unit?.trim().slice(0, 20) || null }
399
+ : {}),
400
+ ...(patch.multiple !== undefined && row.type === "choice"
401
+ ? { multiple: patch.multiple }
402
+ : {}),
403
+ ...(patch.ordered !== undefined && row.type === "choice" ? { ordered: patch.ordered } : {}),
404
+ ...(patch.hint !== undefined ? { hint: patch.hint?.trim().slice(0, 120) || null } : {}),
405
+ ...(patch.proposed === false ? { proposed: false } : {}),
406
+ ...(patch.position !== undefined ? { position: patch.position } : {}),
407
+ updatedAt: new Date(),
408
+ })
409
+ .where(eq(schema.spaceCategory.id, row.id));
410
+ if (patch.proposed === false && row.tableId && row.columnId) {
411
+ await syncColumnCategory({ ...row, proposed: false });
412
+ }
413
+ if (name !== row.name || patch.unit !== undefined) {
414
+ await reindexHolders(row.id);
415
+ }
416
+ }
417
+ export async function deleteCategory(categoryId) {
418
+ const row = await categoryRow(categoryId);
419
+ await requireWritableProject(row.projectId);
420
+ await reindexHolders(row.id);
421
+ await db.delete(schema.spaceCategory).where(eq(schema.spaceCategory.id, row.id));
422
+ }
423
+ async function valueRow(valueId) {
424
+ const value = await db.query.spaceCategoryValue.findFirst({
425
+ where: eq(schema.spaceCategoryValue.id, valueId),
426
+ });
427
+ if (!value) {
428
+ throw notFound();
429
+ }
430
+ const category = await categoryRow(value.categoryId);
431
+ return { value, category };
432
+ }
433
+ export async function categoryValue(valueId) {
434
+ return valueRow(valueId);
435
+ }
436
+ export async function addCategoryValue(categoryId, value) {
437
+ const category = await categoryRow(categoryId);
438
+ await requireWritableProject(category.projectId);
439
+ if (category.type !== "choice") {
440
+ throw new ServiceError("invalid", note("onlyChoiceCategoriesValues"));
441
+ }
442
+ const ids = await addValues(category.id, [value]);
443
+ return { id: [...ids.values()].at(-1) };
444
+ }
445
+ export async function renameCategoryValue(valueId, value) {
446
+ const { value: row, category } = await valueRow(valueId);
447
+ await requireWritableProject(category.projectId);
448
+ const next = value.replace(/\s+/g, " ").trim().slice(0, 200);
449
+ if (!next || next === row.value) {
450
+ return;
451
+ }
452
+ const clash = await db.query.spaceCategoryValue.findFirst({
453
+ where: and(eq(schema.spaceCategoryValue.categoryId, category.id), eq(schema.spaceCategoryValue.value, next)),
454
+ });
455
+ if (clash) {
456
+ await mergeCategoryValues(row.id, clash.id);
457
+ return;
458
+ }
459
+ await db
460
+ .update(schema.spaceCategoryValue)
461
+ .set({ value: next })
462
+ .where(eq(schema.spaceCategoryValue.id, row.id));
463
+ await reindexHolders(category.id);
464
+ }
465
+ /** Puts the items of one value under another and drops the first: two spellings become one. */
466
+ export async function mergeCategoryValues(fromId, intoId) {
467
+ const from = await valueRow(fromId);
468
+ const into = await valueRow(intoId);
469
+ if (from.category.id !== into.category.id || fromId === intoId) {
470
+ throw new ServiceError("invalid", note("onlyValuesOneCategory"));
471
+ }
472
+ await requireWritableProject(from.category.projectId);
473
+ const t = schema.spaceItemCategory;
474
+ const moving = await db.query.spaceItemCategory.findMany({ where: eq(t.valueId, fromId) });
475
+ const having = new Set((await db.query.spaceItemCategory.findMany({ where: eq(t.valueId, intoId) })).map(keyOfRow));
476
+ for (const row of moving) {
477
+ if (having.has(keyOfRow(row))) {
478
+ await db.delete(t).where(eq(t.id, row.id));
479
+ }
480
+ else {
481
+ await db.update(t).set({ valueId: intoId }).where(eq(t.id, row.id));
482
+ }
483
+ }
484
+ await db.delete(schema.spaceCategoryValue).where(eq(schema.spaceCategoryValue.id, fromId));
485
+ // The summary of the value that grew is no longer all of it.
486
+ await db
487
+ .update(schema.spaceCategoryValue)
488
+ .set({ summaryAt: null })
489
+ .where(eq(schema.spaceCategoryValue.id, intoId));
490
+ await reindexHolders(from.category.id);
491
+ }
492
+ export async function deleteCategoryValue(valueId) {
493
+ const { value, category } = await valueRow(valueId);
494
+ await requireWritableProject(category.projectId);
495
+ await reindexHolders(category.id);
496
+ await db.delete(schema.spaceCategoryValue).where(eq(schema.spaceCategoryValue.id, value.id));
497
+ }
498
+ export async function orderCategoryValues(categoryId, ids) {
499
+ const category = await categoryRow(categoryId);
500
+ await requireWritableProject(category.projectId);
501
+ for (const [position, id] of ids.entries()) {
502
+ await db
503
+ .update(schema.spaceCategoryValue)
504
+ .set({ position })
505
+ .where(and(eq(schema.spaceCategoryValue.id, id), eq(schema.spaceCategoryValue.categoryId, category.id)));
506
+ }
507
+ }
508
+ // --- on items ---------------------------------------------------------------------
509
+ /**
510
+ * Sets categories on an item, by their names. A value null takes the category off. What a
511
+ * person set stays when an origin or a model writes again. Names the space has no taken
512
+ * category for are skipped and returned. True in `changed` when anything changed.
513
+ */
514
+ export async function setItemCategories(projectId, ref, input, by) {
515
+ const rows = (await categoryRows(projectId)).filter((c) => !c.proposed);
516
+ const t = schema.spaceItemCategory;
517
+ const current = await db.query.spaceItemCategory.findMany({ where: refWhere(ref) });
518
+ const unknown = [];
519
+ let changed = false;
520
+ for (const [name, raw] of Object.entries(input)) {
521
+ const category = byName(rows, name);
522
+ if (!category) {
523
+ unknown.push(name);
524
+ continue;
525
+ }
526
+ const mine = current.filter((r) => r.categoryId === category.id);
527
+ if (by !== "person" && mine.some((r) => r.by === "person")) {
528
+ continue;
529
+ }
530
+ const list = (Array.isArray(raw) ? raw : [raw])
531
+ .map((v) => typed(category, v))
532
+ .filter((v) => v !== null);
533
+ const values = category.type === "choice" && category.multiple ? list : list.slice(0, 1);
534
+ const ids = category.type === "choice"
535
+ ? await addValues(category.id, values.map((v) => v.choice))
536
+ : null;
537
+ const next = values.map((v) => ({
538
+ valueId: v.choice !== undefined ? (ids?.get(v.choice) ?? null) : null,
539
+ text: v.text ?? null,
540
+ num: v.num ?? null,
541
+ at: v.at ?? null,
542
+ bool: v.bool ?? null,
543
+ }));
544
+ const same = mine.length === next.length &&
545
+ next.every((n) => mine.some((m) => m.valueId === n.valueId &&
546
+ m.text === n.text &&
547
+ m.num === n.num &&
548
+ m.at === n.at &&
549
+ m.bool === n.bool));
550
+ if (same && mine.every((m) => m.by === by)) {
551
+ continue;
552
+ }
553
+ changed = true;
554
+ if (mine.length) {
555
+ await db.delete(t).where(inArray(t.id, mine.map((m) => m.id)));
556
+ }
557
+ if (next.length) {
558
+ await db.insert(t).values(next.map((n) => ({
559
+ projectId,
560
+ categoryId: category.id,
561
+ ...("pageId" in ref ? { pageId: ref.pageId } : {}),
562
+ ...("rowId" in ref ? { rowId: ref.rowId } : {}),
563
+ ...("tableId" in ref ? { tableId: ref.tableId } : {}),
564
+ ...("fileId" in ref ? { fileId: ref.fileId } : {}),
565
+ ...n,
566
+ by,
567
+ })));
568
+ }
569
+ }
570
+ return { changed, unknown };
571
+ }
572
+ /** Whether an item is of the space's own Knowledge. */
573
+ async function itemInProject(projectId, ref) {
574
+ if ("pageId" in ref) {
575
+ const page = await db.query.spacePage.findFirst({
576
+ where: eq(schema.spacePage.id, ref.pageId),
577
+ columns: { projectId: true, wizardId: true },
578
+ });
579
+ return page?.projectId === projectId && !page.wizardId;
580
+ }
581
+ if ("fileId" in ref) {
582
+ const file = await db.query.projectFile.findFirst({
583
+ where: eq(schema.projectFile.id, ref.fileId),
584
+ columns: { projectId: true },
585
+ });
586
+ return file?.projectId === projectId;
587
+ }
588
+ const tableId = "tableId" in ref
589
+ ? ref.tableId
590
+ : (await db.query.spaceTableRow.findFirst({
591
+ where: eq(schema.spaceTableRow.id, ref.rowId),
592
+ columns: { tableId: true },
593
+ }))?.tableId;
594
+ const table = tableId
595
+ ? await db.query.spaceTable.findFirst({
596
+ where: eq(schema.spaceTable.id, tableId),
597
+ columns: { projectId: true, wizardId: true },
598
+ })
599
+ : undefined;
600
+ return table?.projectId === projectId && !table.wizardId;
601
+ }
602
+ /** What a person set from the studio: by the category's id, a list of values or none. */
603
+ export async function setItemCategory(projectId, ref, categoryId, values) {
604
+ const category = await categoryRow(categoryId);
605
+ if (category.projectId !== projectId || !(await itemInProject(projectId, ref))) {
606
+ throw notFound();
607
+ }
608
+ const { changed } = await setItemCategories(projectId, ref, { [category.name]: values.length ? values : null }, "person");
609
+ return changed;
610
+ }
611
+ /** The categories of items, by `itemKey`. */
612
+ export async function itemCategoriesOf(refs) {
613
+ const out = new Map();
614
+ if (!refs.length) {
615
+ return out;
616
+ }
617
+ const t = schema.spaceItemCategory;
618
+ const ids = (k) => refs
619
+ .filter((r) => k in r)
620
+ .map((r) => r[k]);
621
+ const conditions = [];
622
+ for (const [k, column] of [
623
+ ["pageId", t.pageId],
624
+ ["rowId", t.rowId],
625
+ ["tableId", t.tableId],
626
+ ["fileId", t.fileId],
627
+ ]) {
628
+ const list = ids(k);
629
+ for (let i = 0; i < list.length; i += 500) {
630
+ conditions.push(inArray(column, list.slice(i, i + 500)));
631
+ }
632
+ }
633
+ const rows = await db.query.spaceItemCategory.findMany({ where: or(...conditions) });
634
+ const valueIds = [...new Set(rows.map((r) => r.valueId).filter((v) => Boolean(v)))];
635
+ const values = new Map();
636
+ for (let i = 0; i < valueIds.length; i += 500) {
637
+ for (const v of await db.query.spaceCategoryValue.findMany({
638
+ where: inArray(schema.spaceCategoryValue.id, valueIds.slice(i, i + 500)),
639
+ columns: { id: true, value: true },
640
+ })) {
641
+ values.set(v.id, v.value);
642
+ }
643
+ }
644
+ for (const row of rows) {
645
+ const key = keyOfRow(row);
646
+ const list = out.get(key) ?? [];
647
+ const cell = cellOf(row, values);
648
+ if (cell === null) {
649
+ continue;
650
+ }
651
+ const known = list.find((c) => c.categoryId === row.categoryId);
652
+ if (known) {
653
+ known.values.push(cell);
654
+ }
655
+ else {
656
+ list.push({ categoryId: row.categoryId, values: [cell], by: row.by });
657
+ }
658
+ out.set(key, list);
659
+ }
660
+ return out;
661
+ }
662
+ /** `Name: Wert` for each category an item has, in the order of the space's categories. */
663
+ export function categoryLines(categories, has) {
664
+ if (!has?.length) {
665
+ return [];
666
+ }
667
+ return categories.flatMap((c) => {
668
+ const mine = has.find((h) => h.categoryId === c.id);
669
+ if (!mine) {
670
+ return [];
671
+ }
672
+ const shown = mine.values
673
+ .map((v) => typeof v === "boolean"
674
+ ? v
675
+ ? "ja"
676
+ : "nein"
677
+ : `${v}${c.unit && typeof v === "number" ? ` ${c.unit}` : ""}`)
678
+ .join(", ");
679
+ return [`${c.name}: ${shown}`];
680
+ });
681
+ }
682
+ // --- a column as a category --------------------------------------------------------------
683
+ /** Writes the category of a column onto the table's rows: their cells are its values. */
684
+ export async function syncColumnCategory(category, rowIds) {
685
+ if (!(category.tableId && category.columnId) || category.proposed) {
686
+ return;
687
+ }
688
+ const rows = await db.query.spaceTableRow.findMany({
689
+ where: rowIds
690
+ ? and(eq(schema.spaceTableRow.tableId, category.tableId), inArray(schema.spaceTableRow.id, rowIds))
691
+ : eq(schema.spaceTableRow.tableId, category.tableId),
692
+ columns: { id: true, cells: true },
693
+ });
694
+ for (const row of rows) {
695
+ await setItemCategories(category.projectId, { rowId: row.id }, {
696
+ [category.name]: row.cells[category.columnId] ?? null,
697
+ }, "origin");
698
+ }
699
+ }
700
+ /** After rows of a table changed: the categories its columns are. */
701
+ export async function syncTableRowCategories(tableId, rowIds) {
702
+ const categories = await db.query.spaceCategory.findMany({
703
+ where: and(eq(schema.spaceCategory.tableId, tableId), isNotNull(schema.spaceCategory.columnId)),
704
+ });
705
+ for (const category of categories) {
706
+ await syncColumnCategory(category, rowIds);
707
+ }
708
+ }
709
+ function condition(category, w, valueIds) {
710
+ const t = schema.spaceItemCategory;
711
+ const list = Array.isArray(w) ? w : null;
712
+ const bad = () => new ServiceError("invalid", `“${category.name}” is a ${category.type}: ${{
713
+ choice: "give a value, a list of values or { atLeast }",
714
+ text: "give a text, a list or { contains }",
715
+ number: "give a number, a list or { gte, lte }",
716
+ date: 'give a day ("YYYY-MM-DD"), a list or { after, before }',
717
+ boolean: "give true or false",
718
+ }[category.type]}.`);
719
+ switch (category.type) {
720
+ case "choice": {
721
+ if (typeof w === "object" && w !== null && !Array.isArray(w)) {
722
+ const floor = w.atLeast ? valueIds.get(w.atLeast.toLowerCase()) : undefined;
723
+ if (!(floor && category.ordered)) {
724
+ throw bad();
725
+ }
726
+ return sql `${t.valueId} IN (SELECT id FROM space_category_value WHERE category_id = ${category.id}
727
+ AND position >= (SELECT position FROM space_category_value WHERE id = ${floor}))`;
728
+ }
729
+ const wanted = (list ?? [w]).map((v) => valueIds.get(String(v).toLowerCase()) ?? "-");
730
+ return inArray(t.valueId, wanted);
731
+ }
732
+ case "text": {
733
+ if (typeof w === "object" && w !== null && !Array.isArray(w)) {
734
+ if (!w.contains) {
735
+ throw bad();
736
+ }
737
+ return sql `lower(${t.text}) LIKE ${`%${w.contains.toLowerCase()}%`}`;
738
+ }
739
+ return inArray(sql `lower(${t.text})`, (list ?? [w]).map((v) => String(v).toLowerCase()));
740
+ }
741
+ case "number": {
742
+ if (typeof w === "object" && w !== null && !Array.isArray(w)) {
743
+ if (w.gte === undefined && w.lte === undefined) {
744
+ throw bad();
745
+ }
746
+ return and(w.gte !== undefined ? sql `${t.num} >= ${w.gte}` : undefined, w.lte !== undefined ? sql `${t.num} <= ${w.lte}` : undefined);
747
+ }
748
+ const nums = (list ?? [w]).map(numberOf).filter((n) => n !== null);
749
+ if (!nums.length) {
750
+ throw bad();
751
+ }
752
+ return inArray(t.num, nums);
753
+ }
754
+ case "date": {
755
+ const DAY = 86_400_000;
756
+ if (typeof w === "object" && w !== null && !Array.isArray(w)) {
757
+ const after = w.after ? dateOf(w.after) : null;
758
+ const before = w.before ? dateOf(w.before) : null;
759
+ if (after === null && before === null) {
760
+ throw bad();
761
+ }
762
+ return and(after !== null ? sql `${t.at} >= ${after}` : undefined, before !== null ? sql `${t.at} < ${before + DAY}` : undefined);
763
+ }
764
+ const days = (list ?? [w]).map(dateOf).filter((n) => n !== null);
765
+ if (!days.length) {
766
+ throw bad();
767
+ }
768
+ return or(...days.map((d) => sql `(${t.at} >= ${d} AND ${t.at} < ${d + DAY})`));
769
+ }
770
+ case "boolean": {
771
+ const b = boolOf(list ? list[0] : w);
772
+ if (b === null) {
773
+ throw bad();
774
+ }
775
+ return eq(t.bool, b);
776
+ }
777
+ default:
778
+ throw bad();
779
+ }
780
+ }
781
+ /** Every page under the given ones, the given ones included. */
782
+ async function withSubPages(pageIds) {
783
+ if (!pageIds.length) {
784
+ return [];
785
+ }
786
+ const rows = await db.all(sql `
787
+ WITH RECURSIVE tree(id) AS (
788
+ SELECT id FROM space_page WHERE id IN (${sql.join(pageIds.map((id) => sql `${id}`), sql `, `)})
789
+ UNION SELECT p.id FROM space_page p JOIN tree ON p.parent_id = tree.id
790
+ ) SELECT id FROM tree`);
791
+ return rows.map((r) => r.id);
792
+ }
793
+ /**
794
+ * The items of the space a filter leaves: an item matches when it, or the page or table it
795
+ * belongs to, has every category as asked. Null for no filter. A name the space has no
796
+ * category for is an error that lists the names there are.
797
+ */
798
+ export async function filterItems(projectId, where) {
799
+ const entries = Object.entries(where ?? {});
800
+ if (!entries.length) {
801
+ return null;
802
+ }
803
+ const rows = (await categoryRows(projectId)).filter((c) => !c.proposed);
804
+ let result = null;
805
+ for (const [name, w] of entries) {
806
+ const category = byName(rows, name);
807
+ if (!category) {
808
+ throw new ServiceError("invalid", `No category “${name}”. There are: ${rows.map((c) => `${c.name} (${c.type})`).join(", ") || "none"}.`);
809
+ }
810
+ const valueIds = new Map((await db.query.spaceCategoryValue.findMany({
811
+ where: eq(schema.spaceCategoryValue.categoryId, category.id),
812
+ columns: { id: true, value: true },
813
+ })).map((v) => [v.value.toLowerCase(), v.id]));
814
+ const t = schema.spaceItemCategory;
815
+ const matched = await db
816
+ .select({ pageId: t.pageId, rowId: t.rowId, tableId: t.tableId, fileId: t.fileId })
817
+ .from(t)
818
+ .where(and(eq(t.categoryId, category.id), condition(category, w, valueIds)));
819
+ const keys = new Set();
820
+ const pages = await withSubPages(matched.flatMap((m) => (m.pageId ? [m.pageId] : [])));
821
+ for (const id of pages) {
822
+ keys.add(`p:${id}`);
823
+ }
824
+ const tables = matched.flatMap((m) => (m.tableId ? [m.tableId] : []));
825
+ for (const id of tables) {
826
+ keys.add(`t:${id}`);
827
+ }
828
+ for (let i = 0; i < tables.length; i += 500) {
829
+ for (const r of await db.query.spaceTableRow.findMany({
830
+ where: inArray(schema.spaceTableRow.tableId, tables.slice(i, i + 500)),
831
+ columns: { id: true },
832
+ })) {
833
+ keys.add(`r:${r.id}`);
834
+ }
835
+ }
836
+ for (const m of matched) {
837
+ if (m.rowId) {
838
+ keys.add(`r:${m.rowId}`);
839
+ }
840
+ if (m.fileId) {
841
+ keys.add(`f:${m.fileId}`);
842
+ }
843
+ }
844
+ result = result ? new Set([...result].filter((k) => keys.has(k))) : keys;
845
+ }
846
+ const out = {
847
+ pages: new Set(),
848
+ rows: new Set(),
849
+ tables: new Set(),
850
+ files: new Set(),
851
+ size: 0,
852
+ };
853
+ for (const key of result ?? []) {
854
+ const id = key.slice(2);
855
+ ({ p: out.pages, r: out.rows, t: out.tables, f: out.files })[key[0]].add(id);
856
+ }
857
+ // A table counts once, however many of its rows are left.
858
+ out.size =
859
+ out.pages.size + out.files.size + out.tables.size + (out.tables.size ? 0 : out.rows.size);
860
+ return out;
861
+ }