@supatype/cli 0.1.6 → 0.1.9

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 (477) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-test.log +152 -102
  3. package/.turbo/turbo-typecheck.log +1 -1
  4. package/LICENSE +202 -0
  5. package/deno.d.ts +52 -0
  6. package/dist/app/framework.js +1 -1
  7. package/dist/app/framework.js.map +1 -1
  8. package/dist/augmentation-generator.d.ts +22 -0
  9. package/dist/augmentation-generator.d.ts.map +1 -1
  10. package/dist/augmentation-generator.js +19 -5
  11. package/dist/augmentation-generator.js.map +1 -1
  12. package/dist/binary-cache.d.ts +42 -6
  13. package/dist/binary-cache.d.ts.map +1 -1
  14. package/dist/binary-cache.js +76 -44
  15. package/dist/binary-cache.js.map +1 -1
  16. package/dist/cache-clean.d.ts +15 -0
  17. package/dist/cache-clean.d.ts.map +1 -0
  18. package/dist/cache-clean.js +76 -0
  19. package/dist/cache-clean.js.map +1 -0
  20. package/dist/cache-pins.d.ts +51 -0
  21. package/dist/cache-pins.d.ts.map +1 -0
  22. package/dist/cache-pins.js +184 -0
  23. package/dist/cache-pins.js.map +1 -0
  24. package/dist/cli-artifacts.d.ts +32 -0
  25. package/dist/cli-artifacts.d.ts.map +1 -0
  26. package/dist/cli-artifacts.js +49 -0
  27. package/dist/cli-artifacts.js.map +1 -0
  28. package/dist/cli-install-method.d.ts +27 -0
  29. package/dist/cli-install-method.d.ts.map +1 -0
  30. package/dist/cli-install-method.js +78 -0
  31. package/dist/cli-install-method.js.map +1 -0
  32. package/dist/cli-package-version.d.ts +1 -1
  33. package/dist/cli-package-version.d.ts.map +1 -1
  34. package/dist/cli-package-version.js +6 -1
  35. package/dist/cli-package-version.js.map +1 -1
  36. package/dist/cli-standalone.d.ts +13 -4
  37. package/dist/cli-standalone.d.ts.map +1 -1
  38. package/dist/cli-standalone.js +86 -31
  39. package/dist/cli-standalone.js.map +1 -1
  40. package/dist/cli-version-embedded.d.ts +10 -0
  41. package/dist/cli-version-embedded.d.ts.map +1 -0
  42. package/dist/cli-version-embedded.js +13 -0
  43. package/dist/cli-version-embedded.js.map +1 -0
  44. package/dist/cli.d.ts.map +1 -1
  45. package/dist/cli.js +7 -1
  46. package/dist/cli.js.map +1 -1
  47. package/dist/cloud-credentials.d.ts +34 -0
  48. package/dist/cloud-credentials.d.ts.map +1 -0
  49. package/dist/cloud-credentials.js +139 -0
  50. package/dist/cloud-credentials.js.map +1 -0
  51. package/dist/commands/add.js +1 -1
  52. package/dist/commands/add.js.map +1 -1
  53. package/dist/commands/admin.d.ts +11 -0
  54. package/dist/commands/admin.d.ts.map +1 -1
  55. package/dist/commands/admin.js +123 -21
  56. package/dist/commands/admin.js.map +1 -1
  57. package/dist/commands/adopt.js +1 -1
  58. package/dist/commands/adopt.js.map +1 -1
  59. package/dist/commands/app.js +2 -2
  60. package/dist/commands/app.js.map +1 -1
  61. package/dist/commands/cache.d.ts +1 -1
  62. package/dist/commands/cache.d.ts.map +1 -1
  63. package/dist/commands/cache.js +69 -73
  64. package/dist/commands/cache.js.map +1 -1
  65. package/dist/commands/cloud.d.ts.map +1 -1
  66. package/dist/commands/cloud.js +32 -8
  67. package/dist/commands/cloud.js.map +1 -1
  68. package/dist/commands/db.d.ts +3 -2
  69. package/dist/commands/db.d.ts.map +1 -1
  70. package/dist/commands/db.js +149 -3
  71. package/dist/commands/db.js.map +1 -1
  72. package/dist/commands/deploy.d.ts +9 -9
  73. package/dist/commands/deploy.js +12 -12
  74. package/dist/commands/deploy.js.map +1 -1
  75. package/dist/commands/dev.d.ts +3 -3
  76. package/dist/commands/dev.d.ts.map +1 -1
  77. package/dist/commands/dev.js +63 -23
  78. package/dist/commands/dev.js.map +1 -1
  79. package/dist/commands/doctor.d.ts +28 -0
  80. package/dist/commands/doctor.d.ts.map +1 -1
  81. package/dist/commands/doctor.js +60 -3
  82. package/dist/commands/doctor.js.map +1 -1
  83. package/dist/commands/engine.d.ts +1 -1
  84. package/dist/commands/engine.js +1 -1
  85. package/dist/commands/functions.d.ts +7 -0
  86. package/dist/commands/functions.d.ts.map +1 -1
  87. package/dist/commands/functions.js +45 -11
  88. package/dist/commands/functions.js.map +1 -1
  89. package/dist/commands/generate.d.ts.map +1 -1
  90. package/dist/commands/generate.js +5 -1
  91. package/dist/commands/generate.js.map +1 -1
  92. package/dist/commands/hooks.d.ts +12 -0
  93. package/dist/commands/hooks.d.ts.map +1 -0
  94. package/dist/commands/hooks.js +118 -0
  95. package/dist/commands/hooks.js.map +1 -0
  96. package/dist/commands/init.d.ts.map +1 -1
  97. package/dist/commands/init.js +64 -30
  98. package/dist/commands/init.js.map +1 -1
  99. package/dist/commands/internal.d.ts +6 -0
  100. package/dist/commands/internal.d.ts.map +1 -0
  101. package/dist/commands/internal.js +22 -0
  102. package/dist/commands/internal.js.map +1 -0
  103. package/dist/commands/link-helpers.d.ts.map +1 -1
  104. package/dist/commands/link-helpers.js +8 -2
  105. package/dist/commands/link-helpers.js.map +1 -1
  106. package/dist/commands/login.d.ts +7 -0
  107. package/dist/commands/login.d.ts.map +1 -0
  108. package/dist/commands/login.js +110 -0
  109. package/dist/commands/login.js.map +1 -0
  110. package/dist/commands/logs.d.ts +1 -1
  111. package/dist/commands/migrate.js +5 -5
  112. package/dist/commands/migrate.js.map +1 -1
  113. package/dist/commands/pg.d.ts +1 -1
  114. package/dist/commands/pg.js +1 -1
  115. package/dist/commands/plugins.js +1 -1
  116. package/dist/commands/plugins.js.map +1 -1
  117. package/dist/commands/pull.js +2 -2
  118. package/dist/commands/pull.js.map +1 -1
  119. package/dist/commands/push.d.ts.map +1 -1
  120. package/dist/commands/push.js +110 -7
  121. package/dist/commands/push.js.map +1 -1
  122. package/dist/commands/self-host.d.ts +1 -1
  123. package/dist/commands/self-host.js +1 -1
  124. package/dist/commands/self-update.d.ts +3 -2
  125. package/dist/commands/self-update.d.ts.map +1 -1
  126. package/dist/commands/self-update.js +31 -16
  127. package/dist/commands/self-update.js.map +1 -1
  128. package/dist/commands/status.d.ts +1 -1
  129. package/dist/commands/update.d.ts +1 -1
  130. package/dist/commands/update.js +1 -1
  131. package/dist/config.d.ts +1 -1
  132. package/dist/config.d.ts.map +1 -1
  133. package/dist/config.js +19 -4
  134. package/dist/config.js.map +1 -1
  135. package/dist/db-preflight.d.ts +78 -0
  136. package/dist/db-preflight.d.ts.map +1 -0
  137. package/dist/db-preflight.js +452 -0
  138. package/dist/db-preflight.js.map +1 -0
  139. package/dist/dev-compose.d.ts +4 -4
  140. package/dist/dev-compose.d.ts.map +1 -1
  141. package/dist/dev-compose.js +143 -88
  142. package/dist/dev-compose.js.map +1 -1
  143. package/dist/dev-log-filter.d.ts +1 -1
  144. package/dist/dev-log-filter.d.ts.map +1 -1
  145. package/dist/dev-log-filter.js +1 -1
  146. package/dist/dev-log-filter.js.map +1 -1
  147. package/dist/dev-ports.d.ts +2 -2
  148. package/dist/dev-ports.d.ts.map +1 -1
  149. package/dist/dev-ports.js +2 -2
  150. package/dist/dev-ports.js.map +1 -1
  151. package/dist/dev-session.d.ts +5 -5
  152. package/dist/dev-session.d.ts.map +1 -1
  153. package/dist/dev-session.js +6 -6
  154. package/dist/dev-session.js.map +1 -1
  155. package/dist/dev-shutdown.d.ts +5 -5
  156. package/dist/dev-shutdown.d.ts.map +1 -1
  157. package/dist/dev-shutdown.js +7 -7
  158. package/dist/dev-shutdown.js.map +1 -1
  159. package/dist/dev-task-colors.d.ts +1 -1
  160. package/dist/dev-task-colors.d.ts.map +1 -1
  161. package/dist/dev-task-colors.js +1 -1
  162. package/dist/dev-task-colors.js.map +1 -1
  163. package/dist/docker-postgres.d.ts +5 -5
  164. package/dist/docker-postgres.d.ts.map +1 -1
  165. package/dist/docker-postgres.js +5 -5
  166. package/dist/docker-postgres.js.map +1 -1
  167. package/dist/docker-runtime.d.ts +2 -2
  168. package/dist/docker-runtime.d.ts.map +1 -1
  169. package/dist/docker-runtime.js +5 -5
  170. package/dist/docker-runtime.js.map +1 -1
  171. package/dist/engine-client.d.ts +1 -1
  172. package/dist/engine-client.js +3 -3
  173. package/dist/engine-client.js.map +1 -1
  174. package/dist/engine-push-output.js +1 -1
  175. package/dist/engine-push-output.js.map +1 -1
  176. package/dist/ensure-binary.d.ts.map +1 -1
  177. package/dist/ensure-binary.js +17 -0
  178. package/dist/ensure-binary.js.map +1 -1
  179. package/dist/ensure-component-binaries.js +1 -1
  180. package/dist/ensure-component-binaries.js.map +1 -1
  181. package/dist/env-file.d.ts +40 -2
  182. package/dist/env-file.d.ts.map +1 -1
  183. package/dist/env-file.js +132 -11
  184. package/dist/env-file.js.map +1 -1
  185. package/dist/field-masking-tier.d.ts +50 -0
  186. package/dist/field-masking-tier.d.ts.map +1 -0
  187. package/dist/field-masking-tier.js +91 -0
  188. package/dist/field-masking-tier.js.map +1 -0
  189. package/dist/functions-deno-types.d.ts +24 -0
  190. package/dist/functions-deno-types.d.ts.map +1 -0
  191. package/dist/functions-deno-types.js +155 -0
  192. package/dist/functions-deno-types.js.map +1 -0
  193. package/dist/functions-router-gen.js +1 -1
  194. package/dist/gitignore.d.ts +2 -2
  195. package/dist/gitignore.d.ts.map +1 -1
  196. package/dist/gitignore.js +3 -3
  197. package/dist/gitignore.js.map +1 -1
  198. package/dist/hook-upload.d.ts +30 -0
  199. package/dist/hook-upload.d.ts.map +1 -0
  200. package/dist/hook-upload.js +86 -0
  201. package/dist/hook-upload.js.map +1 -0
  202. package/dist/hooks-generator.d.ts +8 -0
  203. package/dist/hooks-generator.d.ts.map +1 -0
  204. package/dist/hooks-generator.js +332 -0
  205. package/dist/hooks-generator.js.map +1 -0
  206. package/dist/index.d.ts +1 -1
  207. package/dist/index.js +1 -1
  208. package/dist/init-project-detect.d.ts +1 -1
  209. package/dist/init-project-detect.d.ts.map +1 -1
  210. package/dist/init-project-detect.js +1 -1
  211. package/dist/init-project-detect.js.map +1 -1
  212. package/dist/link.d.ts +4 -0
  213. package/dist/link.d.ts.map +1 -1
  214. package/dist/link.js +2 -0
  215. package/dist/link.js.map +1 -1
  216. package/dist/local-secrets.d.ts +71 -0
  217. package/dist/local-secrets.d.ts.map +1 -0
  218. package/dist/local-secrets.js +100 -0
  219. package/dist/local-secrets.js.map +1 -0
  220. package/dist/main.d.ts +2 -0
  221. package/dist/main.d.ts.map +1 -0
  222. package/dist/main.js +20 -0
  223. package/dist/main.js.map +1 -0
  224. package/dist/model-hooks.d.ts +73 -0
  225. package/dist/model-hooks.d.ts.map +1 -0
  226. package/dist/model-hooks.js +211 -0
  227. package/dist/model-hooks.js.map +1 -0
  228. package/dist/pgbouncer-userlist.d.ts +1 -1
  229. package/dist/pgbouncer-userlist.d.ts.map +1 -1
  230. package/dist/pgbouncer-userlist.js +2 -2
  231. package/dist/pgbouncer-userlist.js.map +1 -1
  232. package/dist/postgres-ctl.d.ts +12 -1
  233. package/dist/postgres-ctl.d.ts.map +1 -1
  234. package/dist/postgres-ctl.js +28 -5
  235. package/dist/postgres-ctl.js.map +1 -1
  236. package/dist/process-manager.d.ts +2 -2
  237. package/dist/process-manager.d.ts.map +1 -1
  238. package/dist/process-manager.js +4 -4
  239. package/dist/process-manager.js.map +1 -1
  240. package/dist/project-config.d.ts +122 -2
  241. package/dist/project-config.d.ts.map +1 -1
  242. package/dist/project-config.js +150 -1
  243. package/dist/project-config.js.map +1 -1
  244. package/dist/pull-utils.d.ts +2 -2
  245. package/dist/pull-utils.d.ts.map +1 -1
  246. package/dist/pull-utils.js +4 -4
  247. package/dist/pull-utils.js.map +1 -1
  248. package/dist/realtime-launch.js +1 -1
  249. package/dist/realtime-launch.js.map +1 -1
  250. package/dist/release-pins.d.ts +1 -1
  251. package/dist/release-pins.js +2 -2
  252. package/dist/release-pins.js.map +1 -1
  253. package/dist/resolve-api-url.js +1 -1
  254. package/dist/resolve-api-url.js.map +1 -1
  255. package/dist/resolve-target.d.ts +4 -0
  256. package/dist/resolve-target.d.ts.map +1 -1
  257. package/dist/resolve-target.js +69 -73
  258. package/dist/resolve-target.js.map +1 -1
  259. package/dist/rest-cache-admin.js +2 -2
  260. package/dist/rest-cache-admin.js.map +1 -1
  261. package/dist/runtime-routes.d.ts +1 -1
  262. package/dist/runtime-routes.js +1 -1
  263. package/dist/schema-ast-v2.d.ts +5 -4
  264. package/dist/schema-ast-v2.d.ts.map +1 -1
  265. package/dist/schema-ast-v2.js +5 -3
  266. package/dist/schema-ast-v2.js.map +1 -1
  267. package/dist/schema-push-lock.d.ts.map +1 -1
  268. package/dist/schema-push-lock.js +43 -16
  269. package/dist/schema-push-lock.js.map +1 -1
  270. package/dist/scripts/postinstall.d.ts +1 -1
  271. package/dist/scripts/postinstall.js +1 -1
  272. package/dist/self-host-compose.d.ts +26 -2
  273. package/dist/self-host-compose.d.ts.map +1 -1
  274. package/dist/self-host-compose.js +305 -73
  275. package/dist/self-host-compose.js.map +1 -1
  276. package/dist/service-role-check.d.ts +21 -0
  277. package/dist/service-role-check.d.ts.map +1 -0
  278. package/dist/service-role-check.js +85 -0
  279. package/dist/service-role-check.js.map +1 -0
  280. package/dist/storage-provision.d.ts +1 -1
  281. package/dist/storage-provision.js +3 -3
  282. package/dist/storage-provision.js.map +1 -1
  283. package/dist/studio-dev-server.d.ts +1 -1
  284. package/dist/studio-dev-server.d.ts.map +1 -1
  285. package/dist/systemd.d.ts +1 -1
  286. package/dist/systemd.js +1 -1
  287. package/dist/target-client.d.ts +17 -0
  288. package/dist/target-client.d.ts.map +1 -1
  289. package/dist/target-client.js +69 -17
  290. package/dist/target-client.js.map +1 -1
  291. package/dist/tsx-runner.js +1 -1
  292. package/dist/tsx-runner.js.map +1 -1
  293. package/dist/type-extractor.d.ts.map +1 -1
  294. package/dist/type-extractor.js +738 -34
  295. package/dist/type-extractor.js.map +1 -1
  296. package/dist/type-resolver.js +1 -1
  297. package/dist/type-resolver.js.map +1 -1
  298. package/dist/ui/brand.d.ts +1 -1
  299. package/dist/ui/brand.js +1 -1
  300. package/dist/ui/components/LogoWordmark.d.ts +1 -1
  301. package/dist/ui/components/LogoWordmark.d.ts.map +1 -1
  302. package/dist/ui/components/LogoWordmark.js +1 -1
  303. package/dist/ui/components/LogoWordmark.js.map +1 -1
  304. package/dist/ui/components/SelectField.js +1 -1
  305. package/dist/ui/components/SelectField.js.map +1 -1
  306. package/dist/ui/confirm.d.ts +1 -1
  307. package/dist/ui/confirm.js +1 -1
  308. package/dist/ui/fatal.d.ts +1 -1
  309. package/dist/ui/fatal.d.ts.map +1 -1
  310. package/dist/ui/fatal.js +1 -1
  311. package/dist/ui/fatal.js.map +1 -1
  312. package/dist/ui/flows/prompt-fields.js +1 -1
  313. package/dist/ui/flows/prompt-fields.js.map +1 -1
  314. package/dist/ui/interactive.d.ts +1 -1
  315. package/dist/ui/interactive.d.ts.map +1 -1
  316. package/dist/ui/interactive.js +1 -1
  317. package/dist/ui/interactive.js.map +1 -1
  318. package/dist/ui/progress.d.ts +1 -1
  319. package/dist/ui/progress.js +1 -1
  320. package/dist/ui/runtime/command-chrome.js +2 -2
  321. package/dist/ui/runtime/command-chrome.js.map +1 -1
  322. package/dist/ui/runtime/run-clack-flow.js +1 -1
  323. package/dist/ui/runtime/run-clack-flow.js.map +1 -1
  324. package/dist/ui/runtime/stdin-after-ink.d.ts.map +1 -1
  325. package/dist/ui/runtime/stdin-after-ink.js +4 -2
  326. package/dist/ui/runtime/stdin-after-ink.js.map +1 -1
  327. package/dist/ui/theme.d.ts +1 -1
  328. package/dist/ui/theme.js +1 -1
  329. package/dist/valkey-sidecar.d.ts +1 -1
  330. package/dist/valkey-sidecar.d.ts.map +1 -1
  331. package/dist/valkey-sidecar.js +2 -2
  332. package/dist/valkey-sidecar.js.map +1 -1
  333. package/package.json +6 -2
  334. package/scripts/build-release-artifacts.sh +112 -0
  335. package/scripts/generate-homebrew-formula.mjs +123 -0
  336. package/scripts/mirror-deno-release.sh +1 -1
  337. package/src/TYPE-RESOLUTION.md +31 -31
  338. package/src/app/framework.ts +1 -1
  339. package/src/augmentation-generator.ts +19 -6
  340. package/src/binary-cache.ts +94 -47
  341. package/src/cache-clean.ts +81 -0
  342. package/src/cache-pins.ts +243 -0
  343. package/src/cli-artifacts.ts +65 -0
  344. package/src/cli-install-method.ts +87 -0
  345. package/src/cli-package-version.ts +5 -1
  346. package/src/cli-standalone.ts +116 -34
  347. package/src/cli-version-embedded.ts +12 -0
  348. package/src/cli.ts +7 -1
  349. package/src/cloud-credentials.ts +183 -0
  350. package/src/commands/add.ts +1 -1
  351. package/src/commands/admin.ts +142 -27
  352. package/src/commands/adopt.ts +1 -1
  353. package/src/commands/app.ts +2 -2
  354. package/src/commands/cache.ts +92 -77
  355. package/src/commands/cloud.ts +32 -8
  356. package/src/commands/db.ts +164 -3
  357. package/src/commands/deploy.ts +12 -12
  358. package/src/commands/dev.ts +72 -23
  359. package/src/commands/doctor.ts +69 -3
  360. package/src/commands/engine.ts +1 -1
  361. package/src/commands/functions.ts +47 -10
  362. package/src/commands/generate.ts +5 -1
  363. package/src/commands/hooks.ts +138 -0
  364. package/src/commands/init.ts +65 -31
  365. package/src/commands/internal.ts +23 -0
  366. package/src/commands/link-helpers.ts +9 -2
  367. package/src/commands/login.ts +127 -0
  368. package/src/commands/logs.ts +1 -1
  369. package/src/commands/migrate.ts +5 -5
  370. package/src/commands/pg.ts +1 -1
  371. package/src/commands/plugins.ts +1 -1
  372. package/src/commands/pull.ts +2 -2
  373. package/src/commands/push.ts +124 -7
  374. package/src/commands/self-host.ts +1 -1
  375. package/src/commands/self-update.ts +41 -19
  376. package/src/commands/status.ts +1 -1
  377. package/src/commands/update.ts +1 -1
  378. package/src/config.ts +23 -4
  379. package/src/db-preflight.ts +575 -0
  380. package/src/dev-compose.ts +158 -89
  381. package/src/dev-log-filter.ts +1 -1
  382. package/src/dev-ports.ts +3 -3
  383. package/src/dev-session.ts +6 -6
  384. package/src/dev-shutdown.ts +8 -8
  385. package/src/dev-task-colors.ts +1 -1
  386. package/src/docker-postgres.ts +6 -6
  387. package/src/docker-runtime.ts +5 -5
  388. package/src/engine-client.ts +3 -3
  389. package/src/engine-push-output.ts +1 -1
  390. package/src/ensure-binary.ts +20 -0
  391. package/src/ensure-component-binaries.ts +1 -1
  392. package/src/env-file.ts +157 -11
  393. package/src/field-masking-tier.ts +107 -0
  394. package/src/functions-deno-types.ts +188 -0
  395. package/src/functions-router-gen.ts +1 -1
  396. package/src/gitignore.ts +3 -3
  397. package/src/hook-upload.ts +107 -0
  398. package/src/hooks-generator.ts +352 -0
  399. package/src/index.ts +1 -1
  400. package/src/init-project-detect.ts +1 -1
  401. package/src/link.ts +7 -0
  402. package/src/local-secrets.ts +104 -0
  403. package/src/main.ts +20 -0
  404. package/src/model-hooks.ts +260 -0
  405. package/src/pgbouncer-userlist.ts +2 -2
  406. package/src/postgres-ctl.ts +32 -5
  407. package/src/process-manager.ts +4 -4
  408. package/src/project-config.ts +237 -3
  409. package/src/pull-utils.ts +4 -4
  410. package/src/realtime-launch.ts +1 -1
  411. package/src/release-pins.ts +2 -2
  412. package/src/resolve-api-url.ts +1 -1
  413. package/src/resolve-target.ts +113 -68
  414. package/src/rest-cache-admin.ts +2 -2
  415. package/src/runtime-routes.ts +2 -2
  416. package/src/schema-ast-v2.ts +8 -5
  417. package/src/schema-push-lock.ts +56 -17
  418. package/src/scripts/postinstall.ts +1 -1
  419. package/src/self-host-compose.ts +348 -72
  420. package/src/service-role-check.ts +111 -0
  421. package/src/storage-provision.ts +3 -3
  422. package/src/studio-dev-server.ts +1 -1
  423. package/src/systemd.ts +1 -1
  424. package/src/target-client.ts +105 -18
  425. package/src/tsx-runner.ts +1 -1
  426. package/src/type-extractor.ts +885 -30
  427. package/src/type-resolver.ts +1 -1
  428. package/src/ui/README.md +1 -1
  429. package/src/ui/brand.ts +1 -1
  430. package/src/ui/components/LogoWordmark.tsx +1 -1
  431. package/src/ui/components/SelectField.tsx +1 -1
  432. package/src/ui/confirm.ts +1 -1
  433. package/src/ui/fatal.ts +1 -1
  434. package/src/ui/flows/prompt-fields.tsx +1 -1
  435. package/src/ui/interactive.ts +1 -1
  436. package/src/ui/progress.ts +1 -1
  437. package/src/ui/runtime/command-chrome.ts +2 -2
  438. package/src/ui/runtime/run-clack-flow.tsx +1 -1
  439. package/src/ui/runtime/stdin-after-ink.ts +4 -2
  440. package/src/ui/theme.ts +1 -1
  441. package/src/valkey-sidecar.ts +2 -2
  442. package/tests/access-composition.test.ts +667 -0
  443. package/tests/admin-studio-membership.test.ts +121 -0
  444. package/tests/api-schemas.test.ts +83 -0
  445. package/tests/archive-name-alias.test.ts +42 -0
  446. package/tests/augmentation-generator.test.ts +28 -0
  447. package/tests/cache-clean.test.ts +206 -0
  448. package/tests/cli-help.test.ts +11 -2
  449. package/tests/cli-install-method.test.ts +66 -0
  450. package/tests/command-chrome-coverage.test.ts +31 -26
  451. package/tests/command-chrome-wrap.test.ts +1 -0
  452. package/tests/db-preflight.test.ts +72 -0
  453. package/tests/doctor-render.test.ts +62 -0
  454. package/tests/doctor-service-role.test.ts +55 -0
  455. package/tests/engine-push-output.test.ts +1 -1
  456. package/tests/env-file.test.ts +121 -0
  457. package/tests/external-database-compose.test.ts +184 -0
  458. package/tests/external-database-config.test.ts +154 -0
  459. package/tests/field-masking-tier.test.ts +184 -0
  460. package/tests/functions-deno-types.test.ts +197 -0
  461. package/tests/homebrew-formula.test.ts +100 -0
  462. package/tests/hook-upload.test.ts +125 -0
  463. package/tests/hooks-generator.test.ts +212 -0
  464. package/tests/init.test.ts +5 -2
  465. package/tests/keys.test.ts +8 -2
  466. package/tests/local-secrets.test.ts +92 -0
  467. package/tests/model-hooks.test.ts +322 -0
  468. package/tests/runtime-contract.test.ts +113 -1
  469. package/tests/service-role-check.test.ts +109 -0
  470. package/tests/service-role-routes.test.ts +51 -0
  471. package/tests/storage-provision.test.ts +1 -1
  472. package/tests/target-client-refresh.test.ts +94 -0
  473. package/tests/type-extractor.test.ts +187 -0
  474. package/tsconfig.tsbuildinfo +1 -1
  475. package/vitest.config.ts +7 -1
  476. package/scripts/build-release-bundle.mjs +0 -27
  477. package/tests/engine-distribution.test.ts +0 -418
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "vitest"
2
- import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"
2
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"
3
3
  import { join } from "node:path"
4
4
  import { tmpdir } from "node:os"
5
5
  import { runtimeRouteSpec } from "../src/runtime-routes.js"
@@ -89,6 +89,25 @@ describe("runtime contract", () => {
89
89
  expect(first).toBe(second)
90
90
  })
91
91
 
92
+ it("self-host compose exposes the managed schema over REST, not a hardcoded public", () => {
93
+ // The literal here was the reason a non-default pg_schema pushed correctly and then answered
94
+ // PGRST106 on every request: the engine had moved and PostgREST was never told.
95
+ const compose = renderSelfHostCompose({
96
+ ...baseConfig,
97
+ schema: { pg_schema: "app" },
98
+ })
99
+ expect(compose).toContain('PGRST_DB_SCHEMA: "app, supatype, graphql_public, auth"')
100
+ expect(compose).not.toContain('PGRST_DB_SCHEMA: "public,')
101
+ })
102
+
103
+ it("self-host compose honours an explicit api_schemas list verbatim", () => {
104
+ const compose = renderSelfHostCompose({
105
+ ...baseConfig,
106
+ schema: { pg_schema: "app", api_schemas: ["app", "reporting"] },
107
+ })
108
+ expect(compose).toContain('PGRST_DB_SCHEMA: "app, reporting"')
109
+ })
110
+
92
111
  it("self-host compose does not inject a synthetic app-proxy service", () => {
93
112
  const compose = renderSelfHostCompose({ ...baseConfig, app: { mode: "proxy", upstream: "http://app:3000" } })
94
113
  expect(compose).not.toContain("ghcr.io/supatype/app-proxy")
@@ -109,6 +128,49 @@ describe("runtime contract", () => {
109
128
  expect(compose).toContain("supatype/schema-engine:latest")
110
129
  })
111
130
 
131
+ // PostgREST must connect as `authenticator`, never as POSTGRES_USER. POSTGRES_USER is
132
+ // `supatype_admin`, a superuser, and a superuser session may SET ROLE to any role in the
133
+ // cluster: so a request whose JWT named one got it. Verified against a live stack: a
134
+ // token with `role: "supatype_admin"` returned every row of an RLS-protected table.
135
+ it("self-host compose connects PostgREST as authenticator, not the superuser", () => {
136
+ const compose = renderSelfHostCompose(baseConfig)
137
+ expect(compose).toContain("PGRST_DB_URI: postgresql://authenticator:")
138
+ expect(compose).not.toMatch(/PGRST_DB_URI:.*POSTGRES_USER/)
139
+ expect(compose).not.toMatch(/PGRST_DB_URI:.*supatype_admin/)
140
+ })
141
+
142
+ // A separate credential, so rotating the operator's POSTGRES_PASSWORD cannot take the REST
143
+ // API down with it. Unset is a hard compose error rather than an empty password.
144
+ it("gives PostgREST its own credential, not POSTGRES_PASSWORD", () => {
145
+ const compose = renderSelfHostCompose(baseConfig)
146
+ expect(compose).toMatch(/PGRST_DB_URI:.*\$\{AUTHENTICATOR_PASSWORD:\?/)
147
+ expect(compose).not.toMatch(/PGRST_DB_URI:.*POSTGRES_PASSWORD/)
148
+ expect(compose).toMatch(/AUTHENTICATOR_PASSWORD: \$\{AUTHENTICATOR_PASSWORD:\?/)
149
+ })
150
+
151
+ // `supatype dev --provider docker` renders *this* file, so a `${VAR:?}` with no default
152
+ // takes local dev down before anything starts, `docker compose` refuses to interpolate and
153
+ // exits 1. Adding one therefore means wiring it into `upsertDevComposeEnv` as well. This
154
+ // list is the reminder; if it fails, do that rather than just updating the list.
155
+ it("requires only variables the dev path also sets", () => {
156
+ const compose = renderSelfHostCompose(baseConfig)
157
+ const required = [...compose.matchAll(/\$\{([A-Z0-9_]+):\?/g)].map((m) => m[1])
158
+ expect([...new Set(required)].sort()).toEqual([
159
+ "AUTHENTICATOR_PASSWORD",
160
+ "JWT_SECRET",
161
+ "POSTGRES_PASSWORD",
162
+ ])
163
+ })
164
+
165
+ // The whole point of requiring them: a service must never fall back to a secret published in
166
+ // this repository. That default used to reach PostgREST, storage and GoTrue.
167
+ it("never defaults a secret to a published constant", () => {
168
+ const compose = renderSelfHostCompose(baseConfig)
169
+ expect(compose).not.toContain("super-secret-jwt-token")
170
+ expect(compose).not.toMatch(/\$\{JWT_SECRET:-/)
171
+ expect(compose).not.toMatch(/\$\{POSTGRES_PASSWORD:-/)
172
+ })
173
+
112
174
  it("composeDockerImageEnv maps pinned versions to SUPATYPE_*_IMAGE", () => {
113
175
  expect(composeDockerImageEnv(baseConfig)).toEqual({
114
176
  SUPATYPE_ENGINE_IMAGE: "supatype/schema-engine:v0.4.2",
@@ -251,6 +313,55 @@ describe("runtime contract", () => {
251
313
  }
252
314
  })
253
315
 
316
+ it("manifest switches functions on, because the compose file always runs a worker", () => {
317
+ // The generated compose file starts a `functions-worker` and hands the server
318
+ // SUPATYPE_FUNCTIONS_WORKER_URL, while the manifest beside it said functions were disabled. The
319
+ // server reads the manifest, so every function 404'd in a stack that was running a worker for
320
+ // them. Docker `supatype dev` renders this same file, so one flag broke both paths.
321
+ const dir = mkdtempSync(join(tmpdir(), "supatype-fn-manifest-"))
322
+ try {
323
+ writeSelfHostCompose(dir, baseConfig)
324
+ const compose = readFileSync(join(dir, ".supatype", "self-host", "docker-compose.yml"), "utf8")
325
+ expect(compose).toContain(" functions-worker:")
326
+ expect(compose).toContain("SUPATYPE_FUNCTIONS_WORKER_URL: http://functions-worker:8001")
327
+
328
+ const manifest = JSON.parse(
329
+ readFileSync(join(dir, ".supatype", "manifest.json"), "utf8"),
330
+ ) as Record<string, unknown>
331
+ expect(manifest["functions_enabled"]).toBe(true)
332
+ expect(manifest["functions_worker_url"]).toBe("http://functions-worker:8001")
333
+ } finally {
334
+ rmSync(dir, { recursive: true, force: true })
335
+ }
336
+ })
337
+
338
+ it("repairs a manifest written by an older CLI without touching its other keys", () => {
339
+ // Regenerating the compose file is the obvious fix to try, so it has to work: without the
340
+ // repair, only brand-new projects get functions and an existing stack stays broken.
341
+ const dir = mkdtempSync(join(tmpdir(), "supatype-fn-repair-"))
342
+ try {
343
+ mkdirSync(join(dir, ".supatype"), { recursive: true })
344
+ writeFileSync(
345
+ join(dir, ".supatype", "manifest.json"),
346
+ JSON.stringify({ schema: "tenant_7", postgrest_url: "http://pg:3000", functions_enabled: false }),
347
+ "utf8",
348
+ )
349
+
350
+ writeSelfHostCompose(dir, baseConfig)
351
+
352
+ const manifest = JSON.parse(
353
+ readFileSync(join(dir, ".supatype", "manifest.json"), "utf8"),
354
+ ) as Record<string, unknown>
355
+ expect(manifest["functions_enabled"]).toBe(true)
356
+ expect(manifest["functions_worker_url"]).toBe("http://functions-worker:8001")
357
+ // Values `push` put there survive: the schema is not "public" and must stay as found.
358
+ expect(manifest["schema"]).toBe("tenant_7")
359
+ expect(manifest["postgrest_url"]).toBe("http://pg:3000")
360
+ } finally {
361
+ rmSync(dir, { recursive: true, force: true })
362
+ }
363
+ })
364
+
254
365
  it("devLocal compose enables STUDIO_OPEN_DEV on supatype-server", () => {
255
366
  const compose = renderSelfHostCompose(baseConfig, process.cwd(), { devLocal: true })
256
367
  expect(compose).toContain('STUDIO_OPEN_DEV: "1"')
@@ -276,6 +387,7 @@ describe("runtime contract", () => {
276
387
  expect(compose).toContain("\n functions-worker:\n")
277
388
  expect(compose).toContain("SUPATYPE_FUNCTIONS_WORKER_URL: http://functions-worker:8001")
278
389
  expect(compose).toContain("SUPATYPE_FUNCTIONS_ROOT: /project/functions")
390
+ expect(compose).toContain("SUPATYPE_URL: http://kong:8000")
279
391
  expect(compose).not.toContain("deploy/functions")
280
392
  expect(compose).not.toContain("supatype-functions")
281
393
  })
@@ -0,0 +1,109 @@
1
+ import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs"
2
+ import { tmpdir } from "node:os"
3
+ import { join } from "node:path"
4
+ import { describe, expect, it } from "vitest"
5
+
6
+ import { checkServiceRoleRoutes, serviceRoleProblemLines } from "../src/service-role-check.js"
7
+ import type { SupatypeProjectConfig } from "../src/project-config.js"
8
+ import { DENO_RELEASE_PIN } from "../src/release-pins.js"
9
+
10
+ function config(serviceRole?: string[]): SupatypeProjectConfig {
11
+ return {
12
+ project: { name: "p" },
13
+ database: { provider: "docker" },
14
+ server: { mode: "dev" },
15
+ app: { mode: "none" },
16
+ versions: { engine: "0.4.2", server: "0.1.0", postgres: "17.2", deno: DENO_RELEASE_PIN },
17
+ ...(serviceRole !== undefined && { functions: { serviceRole } }),
18
+ } as SupatypeProjectConfig
19
+ }
20
+
21
+ /** A project whose functions/ holds the named directories, each with an index.ts. */
22
+ function projectWith(names: string[], bareFiles: string[] = []): string {
23
+ const cwd = mkdtempSync(join(tmpdir(), "supatype-svcrole-check-"))
24
+ const dir = join(cwd, "functions")
25
+ mkdirSync(dir, { recursive: true })
26
+ for (const name of names) {
27
+ mkdirSync(join(dir, name), { recursive: true })
28
+ writeFileSync(join(dir, name, "index.ts"), "export default () => new Response()\n", "utf8")
29
+ }
30
+ for (const file of bareFiles) {
31
+ writeFileSync(join(dir, `${file}.ts`), "export default () => new Response()\n", "utf8")
32
+ }
33
+ return cwd
34
+ }
35
+
36
+ describe("checkServiceRoleRoutes", () => {
37
+ it("says nothing when a project grants nothing", () => {
38
+ const problems = checkServiceRoleRoutes(config(), projectWith([]))
39
+ expect(problems.errors).toEqual([])
40
+ expect(problems.warnings).toEqual([])
41
+ })
42
+
43
+ it("accepts a name that matches a function directory", () => {
44
+ const problems = checkServiceRoleRoutes(config(["send-email"]), projectWith(["send-email"]))
45
+ expect(problems.errors).toEqual([])
46
+ })
47
+
48
+ it("accepts a bare <name>.ts function, which the worker also serves", () => {
49
+ const problems = checkServiceRoleRoutes(config(["audit"]), projectWith([], ["audit"]))
50
+ expect(problems.errors).toEqual([])
51
+ })
52
+
53
+ it("reports a typo, which would otherwise grant nothing in silence", () => {
54
+ // The failure this exists for: the grant fails closed, so the function reads no key at runtime in a
55
+ // deploy that reported success.
56
+ const problems = checkServiceRoleRoutes(config(["send-emial"]), projectWith(["send-email"]))
57
+ expect(problems.errors).toHaveLength(1)
58
+ expect(problems.errors[0]).toContain("send-emial")
59
+ // Structural too, so `doctor` can mark the entry without parsing the printed line back apart.
60
+ expect(problems.missing).toEqual(["send-emial"])
61
+ })
62
+
63
+ it("reports a name whose function was renamed away", () => {
64
+ const problems = checkServiceRoleRoutes(config(["send-email"]), projectWith(["mailer"]))
65
+ expect(problems.errors).toHaveLength(1)
66
+ })
67
+
68
+ it("warns rather than fails on a hook entry", () => {
69
+ // It was the documented form once, and the worker grants hooks whatever the list says, so it is
70
+ // harmless, and worth saying because a reader would assume the line is what grants.
71
+ const problems = checkServiceRoleRoutes(config(["hooks/moderate-post"]), projectWith([]))
72
+ expect(problems.errors).toEqual([])
73
+ expect(problems.warnings).toHaveLength(1)
74
+ expect(problems.warnings[0]).toContain("not needed")
75
+ })
76
+
77
+ it("ignores an index-less directory, matching how the worker discovers routes", () => {
78
+ const cwd = projectWith([])
79
+ mkdirSync(join(cwd, "functions", "half-written"), { recursive: true })
80
+ const problems = checkServiceRoleRoutes(config(["half-written"]), cwd)
81
+ expect(problems.errors).toHaveLength(1)
82
+ })
83
+ })
84
+
85
+ describe("serviceRoleProblemLines", () => {
86
+ it("is empty when nothing is wrong", () => {
87
+ expect(serviceRoleProblemLines({ errors: [], missing: [], warnings: [], available: ["a"] })).toEqual([])
88
+ })
89
+
90
+ it("lists what could have been named instead", () => {
91
+ const lines = serviceRoleProblemLines({
92
+ errors: [' "x": no functions/x/index.ts'],
93
+ missing: ["x"],
94
+ warnings: [],
95
+ available: ["audit", "send-email"],
96
+ })
97
+ expect(lines.join("\n")).toContain("Functions found: audit, send-email")
98
+ })
99
+
100
+ it("says so when there are no functions at all", () => {
101
+ const lines = serviceRoleProblemLines({
102
+ errors: [' "x": nope'],
103
+ missing: ["x"],
104
+ warnings: [],
105
+ available: [],
106
+ })
107
+ expect(lines.join("\n")).toContain("No functions found")
108
+ })
109
+ })
@@ -0,0 +1,51 @@
1
+ import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs"
2
+ import { tmpdir } from "node:os"
3
+ import { join } from "node:path"
4
+ import { describe, expect, it } from "vitest"
5
+
6
+ import { serviceRoleRoutesFor } from "../src/commands/functions.js"
7
+ import { DENO_RELEASE_PIN } from "../src/release-pins.js"
8
+
9
+ /** A config the loader accepts, plus whatever the test is actually about. */
10
+ function projectWith(extra: Record<string, unknown>): string {
11
+ const cwd = mkdtempSync(join(tmpdir(), "supatype-svcrole-"))
12
+ mkdirSync(cwd, { recursive: true })
13
+ const body = {
14
+ project: { name: "p" },
15
+ database: { provider: "docker" },
16
+ server: { mode: "dev" },
17
+ app: { mode: "none" },
18
+ versions: { engine: "0.4.2", server: "0.1.0", postgres: "17.2", deno: DENO_RELEASE_PIN },
19
+ schema: { path: "./schema/index.ts" },
20
+ ...extra,
21
+ }
22
+ writeFileSync(join(cwd, "supatype.config.ts"), `export default ${JSON.stringify(body)}\n`, "utf8")
23
+ return cwd
24
+ }
25
+
26
+ describe("serviceRoleRoutesFor", () => {
27
+ it("reads the declared list", () => {
28
+ const cwd = projectWith({ functions: { serviceRole: ["send-email"] } })
29
+ expect(serviceRoleRoutesFor(cwd)).toEqual(["send-email"])
30
+ })
31
+
32
+ it("is an empty list when a project declares none", () => {
33
+ // Distinct from the case below: this project *has* said something, and it said nobody. The platform
34
+ // stores that as a real revocation.
35
+ expect(serviceRoleRoutesFor(projectWith({}))).toEqual([])
36
+ })
37
+
38
+ it("is undefined when the config cannot be read at all", () => {
39
+ // Not []: the platform replaces its stored list wholesale whenever the field is present, so an
40
+ // unreadable config sending [] would revoke every grant the project has.
41
+ expect(serviceRoleRoutesFor(mkdtempSync(join(tmpdir(), "supatype-svcrole-none-")))).toBeUndefined()
42
+ })
43
+
44
+ it("sends what the project declared, hook entries included", () => {
45
+ // Filtering hook routes out is the platform's job, not this one's: a hook is granted the key by the
46
+ // worker anyway, and dropping the entry here would hide a mistake in the config rather than making
47
+ // it harmless. Sorted, so an unchanged config produces an unchanged request.
48
+ const cwd = projectWith({ functions: { serviceRole: ["send-email", "hooks/moderate-post"] } })
49
+ expect(serviceRoleRoutesFor(cwd)).toEqual(["hooks/moderate-post", "send-email"])
50
+ })
51
+ })
@@ -80,7 +80,7 @@ describe("provisionBuckets", () => {
80
80
 
81
81
  await provisionBuckets("http://localhost:54321/storage/v1", "key", [{ id: "photos", public: false }])
82
82
 
83
- expect(warn).toHaveBeenCalledWith('[storage] Storage API unreachable skipped bucket "photos"')
83
+ expect(warn).toHaveBeenCalledWith('[storage] Storage API unreachable, skipped bucket "photos"')
84
84
  })
85
85
  })
86
86
 
@@ -0,0 +1,94 @@
1
+ import { describe, expect, it, vi, afterEach } from "vitest"
2
+ import { targetFetch, TargetApiError } from "../src/target-client.js"
3
+
4
+ describe("targetFetch cloud auth refresh", () => {
5
+ afterEach(() => {
6
+ vi.unstubAllGlobals()
7
+ vi.restoreAllMocks()
8
+ })
9
+
10
+ it("refreshes once on TOKEN_EXPIRED then retries", async () => {
11
+ const fetchMock = vi
12
+ .fn()
13
+ .mockResolvedValueOnce({
14
+ ok: false,
15
+ status: 401,
16
+ json: async () => ({ message: "Token expired", code: "TOKEN_EXPIRED" }),
17
+ })
18
+ .mockResolvedValueOnce({
19
+ ok: true,
20
+ status: 200,
21
+ json: async () => ({
22
+ access_token: "new-access",
23
+ refresh_token: "new-refresh",
24
+ expires_in: 3600,
25
+ }),
26
+ })
27
+ .mockResolvedValueOnce({
28
+ ok: true,
29
+ status: 200,
30
+ json: async () => ({ data: { ok: true } }),
31
+ })
32
+
33
+ vi.stubGlobal("fetch", fetchMock)
34
+
35
+ const onRefreshed = vi.fn()
36
+ const result = await targetFetch<{ ok: boolean }>("http://api.test", "/api/v1", {
37
+ method: "GET",
38
+ path: "/projects/x/status",
39
+ token: "old-access",
40
+ authRefresh: {
41
+ cloudApiUrl: "http://api.test",
42
+ refreshToken: "old-refresh",
43
+ onRefreshed,
44
+ },
45
+ })
46
+
47
+ expect(result).toEqual({ ok: true })
48
+ expect(onRefreshed).toHaveBeenCalledWith({
49
+ accessToken: "new-access",
50
+ refreshToken: "new-refresh",
51
+ })
52
+ expect(fetchMock).toHaveBeenCalledTimes(3)
53
+ const retryAuth = (fetchMock.mock.calls[2]![1] as RequestInit).headers as Record<string, string>
54
+ expect(retryAuth.Authorization).toBe("Bearer new-access")
55
+ })
56
+
57
+ it("points to supatype login when expired without refresh token", async () => {
58
+ vi.stubGlobal(
59
+ "fetch",
60
+ vi.fn().mockResolvedValue({
61
+ ok: false,
62
+ status: 401,
63
+ json: async () => ({ message: "Token expired", code: "TOKEN_EXPIRED" }),
64
+ }),
65
+ )
66
+
67
+ await expect(
68
+ targetFetch("http://api.test", "/api/v1", {
69
+ method: "GET",
70
+ path: "/projects/x/status",
71
+ token: "old",
72
+ }),
73
+ ).rejects.toThrow(/supatype login/)
74
+ })
75
+
76
+ it("throws TargetApiError with status", async () => {
77
+ vi.stubGlobal(
78
+ "fetch",
79
+ vi.fn().mockResolvedValue({
80
+ ok: false,
81
+ status: 500,
82
+ json: async () => ({ message: "boom" }),
83
+ }),
84
+ )
85
+
86
+ await expect(
87
+ targetFetch("http://api.test", "/api/v1", {
88
+ method: "GET",
89
+ path: "/x",
90
+ token: "t",
91
+ }),
92
+ ).rejects.toMatchObject({ name: "TargetApiError", status: 500 } satisfies Partial<TargetApiError>)
93
+ })
94
+ })
@@ -1135,4 +1135,191 @@ export type User = Model<{ id: UUID; email: NonNullable<string> }>
1135
1135
 
1136
1136
  expect(() => extractSchemaAstFromTypes(schemaPath, dir)).toThrow(/Unknown Supatype type "NonNullable"/)
1137
1137
  })
1138
+ // Regression: access factored into a shared type alias silently produced NO
1139
+ // rules, so every model using the pattern was published without RLS.
1140
+ it("resolves model access from a shared type alias in another file", () => {
1141
+ const dir = mkdtempSync(join(tmpdir(), "supatype-access-alias-"))
1142
+ dirs.push(dir)
1143
+ writeFileSync(
1144
+ join(dir, "shared.ts"),
1145
+ `
1146
+ import type { Public, Role } from "@supatype/types"
1147
+
1148
+ export type CmsPublicReadAdminWrite = {
1149
+ read: Public
1150
+ create: Role<"admin">
1151
+ update: Role<"admin">
1152
+ delete: Role<"admin">
1153
+ }
1154
+ `,
1155
+ "utf8",
1156
+ )
1157
+ const schemaPath = join(dir, "schema.ts")
1158
+ writeFileSync(
1159
+ schemaPath,
1160
+ `
1161
+ import type { Model, UUID } from "@supatype/types"
1162
+ import type { CmsPublicReadAdminWrite } from "./shared.js"
1163
+
1164
+ export type Article = Model<{ id: UUID; title: string }, {
1165
+ tableName: "articles"
1166
+ access: CmsPublicReadAdminWrite
1167
+ }>
1168
+ `,
1169
+ "utf8",
1170
+ )
1171
+
1172
+ const ast = extractSchemaAstFromTypes(schemaPath, dir)
1173
+ const access = modelAccess(ast?.models.find((m) => m.name === "Article"))
1174
+ expect(access).toEqual({
1175
+ read: { type: "public" },
1176
+ create: { type: "role", roles: ["admin"] },
1177
+ update: { type: "role", roles: ["admin"] },
1178
+ delete: { type: "role", roles: ["admin"] },
1179
+ })
1180
+ })
1181
+
1182
+ it("resolves model access from an intersection of aliases", () => {
1183
+ const dir = mkdtempSync(join(tmpdir(), "supatype-access-intersection-"))
1184
+ dirs.push(dir)
1185
+ const schemaPath = join(dir, "schema.ts")
1186
+ writeFileSync(
1187
+ schemaPath,
1188
+ `
1189
+ import type { Model, UUID, Public, Role } from "@supatype/types"
1190
+
1191
+ type PublicRead = { read: Public }
1192
+ type AdminWrite = { create: Role<"admin">; delete: Role<"admin"> }
1193
+
1194
+ export type Article = Model<{ id: UUID; title: string }, {
1195
+ access: PublicRead & AdminWrite
1196
+ }>
1197
+ `,
1198
+ "utf8",
1199
+ )
1200
+
1201
+ const ast = extractSchemaAstFromTypes(schemaPath, dir)
1202
+ const access = modelAccess(ast?.models.find((m) => m.name === "Article"))
1203
+ expect(Object.keys(access).sort()).toEqual(["create", "delete", "read"])
1204
+ })
1205
+
1206
+ // The dangerous case: `const … as const` cannot express access (the rule names
1207
+ // are types), and silently ignoring it published an unprotected table.
1208
+ it("throws when model access cannot be resolved instead of dropping the rules", () => {
1209
+ const dir = mkdtempSync(join(tmpdir(), "supatype-access-unresolvable-"))
1210
+ dirs.push(dir)
1211
+ const schemaPath = join(dir, "schema.ts")
1212
+ writeFileSync(
1213
+ schemaPath,
1214
+ `
1215
+ import type { Model, UUID } from "@supatype/types"
1216
+
1217
+ export type Article = Model<{ id: UUID; title: string }, {
1218
+ access: typeof someRuntimeValue
1219
+ }>
1220
+ `,
1221
+ "utf8",
1222
+ )
1223
+
1224
+ expect(() => extractSchemaAstFromTypes(schemaPath, dir)).toThrow(
1225
+ /Model "Article": could not resolve its `access` rules/,
1226
+ )
1227
+ })
1228
+
1229
+ it("carries Decimal precision and scale through to the AST", () => {
1230
+ // `Decimal<10, 2>` used to extract as an unbounded NUMERIC: the engine renders `NUMERIC(p, s)`
1231
+ // from these two facts, and nothing read the type arguments. A money column silently lost its
1232
+ // constraint, which no error surfaced and no test caught.
1233
+ const dir = mkdtempSync(join(tmpdir(), "supatype-decimal-"))
1234
+ dirs.push(dir)
1235
+ const schemaPath = join(dir, "schema.ts")
1236
+ writeFileSync(
1237
+ schemaPath,
1238
+ `
1239
+ import type { Decimal, Model, UUID } from "@supatype/types"
1240
+
1241
+ export type Invoice = Model<{
1242
+ id: UUID
1243
+ total: Decimal<10, 2>
1244
+ rate: Decimal<5, 4>
1245
+ }>
1246
+ `,
1247
+ "utf8",
1248
+ )
1249
+
1250
+ const ast = extractSchemaAstFromTypes(schemaPath, dir)
1251
+ const invoice = ast?.models.find((m) => m.name === "Invoice")
1252
+ expect(invoice?.fields["total"]).toMatchObject({ kind: "decimal", precision: 10, scale: 2 })
1253
+ expect(invoice?.fields["rate"]).toMatchObject({ kind: "decimal", precision: 5, scale: 4 })
1254
+ })
1255
+
1256
+ it("extracts Code and Currency as JSONB carrying their declared shape", () => {
1257
+ // Both were exported from `@supatype/types` and rejected by the extractor, so a schema using
1258
+ // either failed the push with "unknown type". Each carries two values, a language with its
1259
+ // source, an amount with its currency, so JSONB is the column that does not drop one.
1260
+ const dir = mkdtempSync(join(tmpdir(), "supatype-code-currency-"))
1261
+ dirs.push(dir)
1262
+ const schemaPath = join(dir, "schema.ts")
1263
+ writeFileSync(
1264
+ schemaPath,
1265
+ `
1266
+ import type { Code, Currency, Model, UUID } from "@supatype/types"
1267
+
1268
+ export type Snippet = Model<{
1269
+ id: UUID
1270
+ body: Code<"sql">
1271
+ anyLang: Code
1272
+ price: Currency<"USD">
1273
+ anyCurrency: Currency
1274
+ }>
1275
+ `,
1276
+ "utf8",
1277
+ )
1278
+
1279
+ const ast = extractSchemaAstFromTypes(schemaPath, dir)
1280
+ const snippet = ast?.models.find((m) => m.name === "Snippet")
1281
+
1282
+ expect(snippet?.fields["body"]).toMatchObject({
1283
+ kind: "json",
1284
+ annotations: { db: { pgType: "JSONB" } },
1285
+ tsType: '{ lang: "sql"; source: string }',
1286
+ })
1287
+ expect(snippet?.fields["price"]).toMatchObject({
1288
+ kind: "json",
1289
+ annotations: { db: { pgType: "JSONB" } },
1290
+ tsType: '{ amount: string; code: "USD" }',
1291
+ })
1292
+
1293
+ // Without a literal argument the currency is not known statically, so the row carries it.
1294
+ expect(snippet?.fields["anyLang"]).toMatchObject({ tsType: "{ lang: string; source: string }" })
1295
+ expect(snippet?.fields["anyCurrency"]).toMatchObject({ tsType: "{ amount: string; code: string }" })
1296
+
1297
+ // Both are JSONB, so the column kind cannot tell Studio which editor to show. The `editor`
1298
+ // annotation is what separates a code snippet from a money amount from a raw JSON blob.
1299
+ expect(snippet?.fields["body"]).toMatchObject({ annotations: { platform: { editor: "code" } } })
1300
+ expect(snippet?.fields["price"]).toMatchObject({
1301
+ annotations: { platform: { editor: "currency" } },
1302
+ })
1303
+ })
1304
+
1305
+ it("throws when bucket access cannot be resolved", () => {
1306
+ const dir = mkdtempSync(join(tmpdir(), "supatype-bucket-access-"))
1307
+ dirs.push(dir)
1308
+ const schemaPath = join(dir, "schema.ts")
1309
+ writeFileSync(
1310
+ schemaPath,
1311
+ `
1312
+ import type { Bucket } from "@supatype/types"
1313
+
1314
+ export type Media = Bucket<"media", {
1315
+ access: typeof someRuntimeValue
1316
+ }>
1317
+ `,
1318
+ "utf8",
1319
+ )
1320
+
1321
+ expect(() => extractSchemaAstFromTypes(schemaPath, dir)).toThrow(
1322
+ /Bucket "media": could not resolve its `access` rules/,
1323
+ )
1324
+ })
1138
1325
  })