@supatype/cli 0.1.0-alpha.6 → 0.1.0-alpha.7

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 (309) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-test.log +203 -1
  3. package/.turbo/turbo-typecheck.log +1 -1
  4. package/dist/app-config.d.ts +7 -0
  5. package/dist/app-config.d.ts.map +1 -0
  6. package/dist/app-config.js +113 -0
  7. package/dist/app-config.js.map +1 -0
  8. package/dist/augmentation-generator.d.ts +2 -0
  9. package/dist/augmentation-generator.d.ts.map +1 -0
  10. package/dist/augmentation-generator.js +111 -0
  11. package/dist/augmentation-generator.js.map +1 -0
  12. package/dist/binary-cache.d.ts +89 -0
  13. package/dist/binary-cache.d.ts.map +1 -0
  14. package/dist/binary-cache.js +656 -0
  15. package/dist/binary-cache.js.map +1 -0
  16. package/dist/cli.d.ts.map +1 -1
  17. package/dist/cli.js +13 -7
  18. package/dist/cli.js.map +1 -1
  19. package/dist/commands/admin.d.ts.map +1 -1
  20. package/dist/commands/admin.js +4 -3
  21. package/dist/commands/admin.js.map +1 -1
  22. package/dist/commands/app.d.ts.map +1 -1
  23. package/dist/commands/app.js +56 -209
  24. package/dist/commands/app.js.map +1 -1
  25. package/dist/commands/cache.d.ts +6 -0
  26. package/dist/commands/cache.d.ts.map +1 -0
  27. package/dist/commands/cache.js +105 -0
  28. package/dist/commands/cache.js.map +1 -0
  29. package/dist/commands/cloud.d.ts +12 -0
  30. package/dist/commands/cloud.d.ts.map +1 -1
  31. package/dist/commands/cloud.js +36 -46
  32. package/dist/commands/cloud.js.map +1 -1
  33. package/dist/commands/db.d.ts.map +1 -1
  34. package/dist/commands/db.js +47 -54
  35. package/dist/commands/db.js.map +1 -1
  36. package/dist/commands/deploy.d.ts +2 -1
  37. package/dist/commands/deploy.d.ts.map +1 -1
  38. package/dist/commands/deploy.js +92 -51
  39. package/dist/commands/deploy.js.map +1 -1
  40. package/dist/commands/dev.d.ts +11 -0
  41. package/dist/commands/dev.d.ts.map +1 -1
  42. package/dist/commands/dev.js +751 -384
  43. package/dist/commands/dev.js.map +1 -1
  44. package/dist/commands/diff.d.ts.map +1 -1
  45. package/dist/commands/diff.js +20 -15
  46. package/dist/commands/diff.js.map +1 -1
  47. package/dist/commands/engine.d.ts +1 -3
  48. package/dist/commands/engine.d.ts.map +1 -1
  49. package/dist/commands/engine.js +13 -85
  50. package/dist/commands/engine.js.map +1 -1
  51. package/dist/commands/functions.d.ts.map +1 -1
  52. package/dist/commands/functions.js +92 -105
  53. package/dist/commands/functions.js.map +1 -1
  54. package/dist/commands/generate.d.ts.map +1 -1
  55. package/dist/commands/generate.js +22 -12
  56. package/dist/commands/generate.js.map +1 -1
  57. package/dist/commands/init.d.ts +1 -1
  58. package/dist/commands/init.d.ts.map +1 -1
  59. package/dist/commands/init.js +124 -410
  60. package/dist/commands/init.js.map +1 -1
  61. package/dist/commands/migrate-from-v1.d.ts +5 -0
  62. package/dist/commands/migrate-from-v1.d.ts.map +1 -0
  63. package/dist/commands/migrate-from-v1.js +125 -0
  64. package/dist/commands/migrate-from-v1.js.map +1 -0
  65. package/dist/commands/migrate.d.ts.map +1 -1
  66. package/dist/commands/migrate.js +27 -23
  67. package/dist/commands/migrate.js.map +1 -1
  68. package/dist/commands/pg.d.ts +8 -0
  69. package/dist/commands/pg.d.ts.map +1 -0
  70. package/dist/commands/pg.js +102 -0
  71. package/dist/commands/pg.js.map +1 -0
  72. package/dist/commands/pull.d.ts.map +1 -1
  73. package/dist/commands/pull.js +5 -66
  74. package/dist/commands/pull.js.map +1 -1
  75. package/dist/commands/push.d.ts.map +1 -1
  76. package/dist/commands/push.js +99 -39
  77. package/dist/commands/push.js.map +1 -1
  78. package/dist/commands/seed.d.ts +2 -0
  79. package/dist/commands/seed.d.ts.map +1 -1
  80. package/dist/commands/seed.js +44 -11
  81. package/dist/commands/seed.js.map +1 -1
  82. package/dist/commands/self-host.d.ts +7 -1
  83. package/dist/commands/self-host.d.ts.map +1 -1
  84. package/dist/commands/self-host.js +272 -758
  85. package/dist/commands/self-host.js.map +1 -1
  86. package/dist/commands/self-update.d.ts +9 -0
  87. package/dist/commands/self-update.d.ts.map +1 -0
  88. package/dist/commands/self-update.js +33 -0
  89. package/dist/commands/self-update.js.map +1 -0
  90. package/dist/commands/status.d.ts.map +1 -1
  91. package/dist/commands/status.js +4 -3
  92. package/dist/commands/status.js.map +1 -1
  93. package/dist/commands/types.d.ts +3 -0
  94. package/dist/commands/types.d.ts.map +1 -0
  95. package/dist/commands/types.js +62 -0
  96. package/dist/commands/types.js.map +1 -0
  97. package/dist/commands/update.d.ts +7 -0
  98. package/dist/commands/update.d.ts.map +1 -0
  99. package/dist/commands/update.js +77 -0
  100. package/dist/commands/update.js.map +1 -0
  101. package/dist/components.d.ts +5 -0
  102. package/dist/components.d.ts.map +1 -0
  103. package/dist/components.js +3 -0
  104. package/dist/components.js.map +1 -0
  105. package/dist/config.d.ts +10 -51
  106. package/dist/config.d.ts.map +1 -1
  107. package/dist/config.js +101 -33
  108. package/dist/config.js.map +1 -1
  109. package/dist/docker-postgres.d.ts +39 -0
  110. package/dist/docker-postgres.d.ts.map +1 -0
  111. package/dist/docker-postgres.js +96 -0
  112. package/dist/docker-postgres.js.map +1 -0
  113. package/dist/engine-client.d.ts +67 -0
  114. package/dist/engine-client.d.ts.map +1 -0
  115. package/dist/engine-client.js +156 -0
  116. package/dist/engine-client.js.map +1 -0
  117. package/dist/ensure-binary.d.ts +7 -0
  118. package/dist/ensure-binary.d.ts.map +1 -0
  119. package/dist/ensure-binary.js +17 -0
  120. package/dist/ensure-binary.js.map +1 -0
  121. package/dist/functions-router-gen.d.ts +14 -0
  122. package/dist/functions-router-gen.d.ts.map +1 -0
  123. package/dist/functions-router-gen.js +199 -0
  124. package/dist/functions-router-gen.js.map +1 -0
  125. package/dist/index.d.ts +4 -5
  126. package/dist/index.d.ts.map +1 -1
  127. package/dist/index.js +2 -3
  128. package/dist/index.js.map +1 -1
  129. package/dist/kong-config.d.ts +21 -0
  130. package/dist/kong-config.d.ts.map +1 -0
  131. package/dist/kong-config.js +60 -0
  132. package/dist/kong-config.js.map +1 -0
  133. package/dist/local-gateway.d.ts +7 -0
  134. package/dist/local-gateway.d.ts.map +1 -0
  135. package/dist/local-gateway.js +9 -0
  136. package/dist/local-gateway.js.map +1 -0
  137. package/dist/local-storage.d.ts +8 -0
  138. package/dist/local-storage.d.ts.map +1 -0
  139. package/dist/local-storage.js +14 -0
  140. package/dist/local-storage.js.map +1 -0
  141. package/dist/pgbouncer-userlist.d.ts +5 -0
  142. package/dist/pgbouncer-userlist.d.ts.map +1 -0
  143. package/dist/pgbouncer-userlist.js +14 -0
  144. package/dist/pgbouncer-userlist.js.map +1 -0
  145. package/dist/postgres-ctl.d.ts +44 -0
  146. package/dist/postgres-ctl.d.ts.map +1 -0
  147. package/dist/postgres-ctl.js +137 -0
  148. package/dist/postgres-ctl.js.map +1 -0
  149. package/dist/process-manager.d.ts +41 -0
  150. package/dist/process-manager.d.ts.map +1 -0
  151. package/dist/process-manager.js +120 -0
  152. package/dist/process-manager.js.map +1 -0
  153. package/dist/project-config.d.ts +215 -0
  154. package/dist/project-config.d.ts.map +1 -0
  155. package/dist/project-config.js +145 -0
  156. package/dist/project-config.js.map +1 -0
  157. package/dist/pull-utils.d.ts +15 -0
  158. package/dist/pull-utils.d.ts.map +1 -1
  159. package/dist/pull-utils.js +12 -0
  160. package/dist/pull-utils.js.map +1 -1
  161. package/dist/release-pins.d.ts +7 -0
  162. package/dist/release-pins.d.ts.map +1 -0
  163. package/dist/release-pins.js +27 -0
  164. package/dist/release-pins.js.map +1 -0
  165. package/dist/release-public-key.d.ts +8 -0
  166. package/dist/release-public-key.d.ts.map +1 -0
  167. package/dist/release-public-key.js +13 -0
  168. package/dist/release-public-key.js.map +1 -0
  169. package/dist/runtime-routes.d.ts +25 -0
  170. package/dist/runtime-routes.d.ts.map +1 -0
  171. package/dist/runtime-routes.js +189 -0
  172. package/dist/runtime-routes.js.map +1 -0
  173. package/dist/scripts/postinstall.d.ts +5 -6
  174. package/dist/scripts/postinstall.d.ts.map +1 -1
  175. package/dist/scripts/postinstall.js +36 -20
  176. package/dist/scripts/postinstall.js.map +1 -1
  177. package/dist/self-host-compose.d.ts +14 -0
  178. package/dist/self-host-compose.d.ts.map +1 -0
  179. package/dist/self-host-compose.js +236 -0
  180. package/dist/self-host-compose.js.map +1 -0
  181. package/dist/storage-provision.d.ts +24 -0
  182. package/dist/storage-provision.d.ts.map +1 -0
  183. package/dist/storage-provision.js +44 -0
  184. package/dist/storage-provision.js.map +1 -0
  185. package/dist/systemd.d.ts +26 -0
  186. package/dist/systemd.d.ts.map +1 -0
  187. package/dist/systemd.js +102 -0
  188. package/dist/systemd.js.map +1 -0
  189. package/dist/tsx-runner.d.ts.map +1 -1
  190. package/dist/tsx-runner.js +9 -2
  191. package/dist/tsx-runner.js.map +1 -1
  192. package/dist/type-extractor.d.ts +31 -0
  193. package/dist/type-extractor.d.ts.map +1 -0
  194. package/dist/type-extractor.js +876 -0
  195. package/dist/type-extractor.js.map +1 -0
  196. package/package.json +4 -3
  197. package/releases/deno/VERSION +1 -0
  198. package/scripts/mirror-deno-release.sh +76 -0
  199. package/src/app-config.ts +128 -0
  200. package/src/augmentation-generator.ts +126 -0
  201. package/src/binary-cache.ts +802 -0
  202. package/src/cli.ts +13 -8
  203. package/src/commands/admin.ts +4 -3
  204. package/src/commands/app.ts +67 -231
  205. package/src/commands/cache.ts +117 -0
  206. package/src/commands/cloud.ts +46 -57
  207. package/src/commands/db.ts +54 -63
  208. package/src/commands/deploy.ts +110 -61
  209. package/src/commands/dev.ts +930 -405
  210. package/src/commands/diff.ts +21 -29
  211. package/src/commands/engine.ts +13 -116
  212. package/src/commands/functions.ts +97 -115
  213. package/src/commands/generate.ts +23 -10
  214. package/src/commands/init.ts +136 -414
  215. package/src/commands/migrate-from-v1.ts +131 -0
  216. package/src/commands/migrate.ts +27 -23
  217. package/src/commands/pg.ts +133 -0
  218. package/src/commands/pull.ts +6 -85
  219. package/src/commands/push.ts +128 -59
  220. package/src/commands/seed.ts +54 -12
  221. package/src/commands/self-host.ts +312 -880
  222. package/src/commands/self-update.ts +45 -0
  223. package/src/commands/status.ts +4 -3
  224. package/src/commands/types.ts +76 -0
  225. package/src/commands/update.ts +92 -0
  226. package/src/components.ts +6 -0
  227. package/src/config.ts +127 -94
  228. package/src/docker-postgres.ts +138 -0
  229. package/src/engine-client.ts +231 -0
  230. package/src/ensure-binary.ts +28 -0
  231. package/src/functions-router-gen.ts +224 -0
  232. package/src/index.ts +4 -12
  233. package/src/kong-config.ts +78 -0
  234. package/src/local-gateway.ts +9 -0
  235. package/src/local-storage.ts +14 -0
  236. package/src/pgbouncer-userlist.ts +15 -0
  237. package/src/postgres-ctl.ts +171 -0
  238. package/src/process-manager.ts +151 -0
  239. package/src/project-config.ts +353 -0
  240. package/src/pull-utils.ts +24 -0
  241. package/src/release-pins.ts +31 -0
  242. package/src/release-public-key.ts +12 -0
  243. package/src/runtime-routes.ts +216 -0
  244. package/src/scripts/postinstall.ts +36 -25
  245. package/src/self-host-compose.ts +257 -0
  246. package/src/storage-provision.ts +58 -0
  247. package/src/systemd.ts +137 -0
  248. package/src/tsx-runner.ts +11 -1
  249. package/src/type-extractor.ts +1016 -0
  250. package/tests/app-command.test.ts +54 -0
  251. package/tests/augmentation-generator.test.ts +59 -0
  252. package/tests/binary-cache-cloud-overrides.test.ts +123 -0
  253. package/tests/cached-artifact-format.test.ts +84 -0
  254. package/tests/cli-help.test.ts +40 -14
  255. package/tests/config.test.ts +140 -37
  256. package/tests/engine-distribution.test.ts +3 -3
  257. package/tests/ensure-binary.test.ts +59 -0
  258. package/tests/init.test.ts +28 -86
  259. package/tests/migrate-from-v1.test.ts +29 -0
  260. package/tests/pg-spawn-env.test.ts +18 -0
  261. package/tests/postgres-archive-tag.test.ts +9 -0
  262. package/tests/pull-utils.test.ts +36 -1
  263. package/tests/release-pins.test.ts +28 -0
  264. package/tests/runtime-contract.test.ts +236 -0
  265. package/tests/seed-discover.test.ts +31 -0
  266. package/tests/tsconfig.json +9 -0
  267. package/tests/type-extractor.test.ts +401 -0
  268. package/tsconfig.tsbuildinfo +1 -1
  269. package/vitest.config.ts +12 -0
  270. package/dist/engine/cache.d.ts +0 -37
  271. package/dist/engine/cache.d.ts.map +0 -1
  272. package/dist/engine/cache.js +0 -121
  273. package/dist/engine/cache.js.map +0 -1
  274. package/dist/engine/download.d.ts +0 -19
  275. package/dist/engine/download.d.ts.map +0 -1
  276. package/dist/engine/download.js +0 -108
  277. package/dist/engine/download.js.map +0 -1
  278. package/dist/engine/platform.d.ts +0 -24
  279. package/dist/engine/platform.d.ts.map +0 -1
  280. package/dist/engine/platform.js +0 -50
  281. package/dist/engine/platform.js.map +0 -1
  282. package/dist/engine/resolve.d.ts +0 -37
  283. package/dist/engine/resolve.d.ts.map +0 -1
  284. package/dist/engine/resolve.js +0 -133
  285. package/dist/engine/resolve.js.map +0 -1
  286. package/dist/engine/update-notify.d.ts +0 -11
  287. package/dist/engine/update-notify.d.ts.map +0 -1
  288. package/dist/engine/update-notify.js +0 -43
  289. package/dist/engine/update-notify.js.map +0 -1
  290. package/dist/engine/verify.d.ts +0 -50
  291. package/dist/engine/verify.d.ts.map +0 -1
  292. package/dist/engine/verify.js +0 -161
  293. package/dist/engine/verify.js.map +0 -1
  294. package/dist/engine-version.d.ts +0 -35
  295. package/dist/engine-version.d.ts.map +0 -1
  296. package/dist/engine-version.js +0 -35
  297. package/dist/engine-version.js.map +0 -1
  298. package/dist/engine.d.ts +0 -34
  299. package/dist/engine.d.ts.map +0 -1
  300. package/dist/engine.js +0 -76
  301. package/dist/engine.js.map +0 -1
  302. package/src/engine/cache.ts +0 -135
  303. package/src/engine/download.ts +0 -143
  304. package/src/engine/platform.ts +0 -66
  305. package/src/engine/resolve.ts +0 -197
  306. package/src/engine/update-notify.ts +0 -50
  307. package/src/engine/verify.ts +0 -206
  308. package/src/engine-version.ts +0 -39
  309. package/src/engine.ts +0 -99
@@ -0,0 +1,231 @@
1
+ /**
2
+ * engine-client.ts — subprocess-based engine invocation.
3
+ *
4
+ * Replaces the former HTTP-based engine client (Docker container API).
5
+ * All callers use the same interface; only the transport changed.
6
+ *
7
+ * The engine binary reads a request JSON file passed via --request-file and
8
+ * writes a response JSON to stdout.
9
+ */
10
+
11
+ import { spawnSync } from "node:child_process"
12
+ import { mkdirSync, writeFileSync, unlinkSync, existsSync, readdirSync } from "node:fs"
13
+ import { tmpdir, homedir } from "node:os"
14
+ import { join } from "node:path"
15
+ import { loadConfig } from "./config.js"
16
+ import { resolveBinary, currentPlatform, cachePath } from "./binary-cache.js"
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Types (kept for backward compatibility with existing callers)
20
+ // ---------------------------------------------------------------------------
21
+
22
+ export interface Operation {
23
+ kind: "create_table" | "alter_table" | "drop_table" | "create_index" | "drop_index" |
24
+ "create_policy" | "drop_policy" | "add_column" | "drop_column" | "alter_column"
25
+ description: string
26
+ risk?: "safe" | "warn" | "danger"
27
+ sql?: string
28
+ }
29
+
30
+ export interface DiffResult {
31
+ operations: Operation[]
32
+ warnings?: string[]
33
+ summary?: string
34
+ }
35
+
36
+ export interface IntrospectResult {
37
+ models: Array<{
38
+ name: string
39
+ table: string
40
+ columns: Array<{
41
+ name: string
42
+ type: string
43
+ nullable: boolean
44
+ default?: string
45
+ primaryKey?: boolean
46
+ unique?: boolean
47
+ references?: { table: string; column: string }
48
+ }>
49
+ }>
50
+ }
51
+
52
+ export interface EngineResult<T = unknown> {
53
+ ok: boolean
54
+ data: T
55
+ message?: string
56
+ error?: string
57
+ }
58
+
59
+ export class EngineError extends Error {
60
+ constructor(
61
+ message: string,
62
+ public readonly endpoint: string,
63
+ public readonly exitCode: number | null,
64
+ ) {
65
+ super(message)
66
+ this.name = "EngineError"
67
+ }
68
+ }
69
+
70
+ // ---------------------------------------------------------------------------
71
+ // Engine binary resolution
72
+ // ---------------------------------------------------------------------------
73
+
74
+ let _engineBin: string | null = null
75
+
76
+ async function getEngineBin(): Promise<string> {
77
+ if (_engineBin) return _engineBin
78
+
79
+ const cwd = process.cwd()
80
+
81
+ try {
82
+ const config = loadConfig(cwd)
83
+ _engineBin = await resolveBinary("engine", config)
84
+ return _engineBin
85
+ } catch {
86
+ // No valid project config — fall through to default cache path.
87
+ }
88
+
89
+ // No config found — scan the cache for any available engine binary.
90
+ const platform = currentPlatform()
91
+ const engineCacheDir = join(homedir(), ".supatype", "cache", "engine")
92
+ try {
93
+ const cachedVersions = readdirSync(engineCacheDir).sort()
94
+ for (const version of cachedVersions.reverse()) {
95
+ const bin = join(cachePath("engine", version), `supatype-engine-${platform.os}-${platform.arch}`)
96
+ if (existsSync(bin)) {
97
+ _engineBin = bin
98
+ return _engineBin
99
+ }
100
+ }
101
+ } catch { /* cache dir doesn't exist */ }
102
+
103
+ throw new Error(
104
+ "Engine binary not found. Run: supatype update",
105
+ )
106
+ }
107
+
108
+ // ---------------------------------------------------------------------------
109
+ // Public API
110
+ // ---------------------------------------------------------------------------
111
+
112
+ /** Verify the engine binary is accessible. Throws if not found. */
113
+ export async function ensureEngine(): Promise<void> {
114
+ await getEngineBin()
115
+ }
116
+
117
+ /** Check if the engine can be invoked. Returns true/false. */
118
+ export async function engineHealth(): Promise<boolean> {
119
+ try {
120
+ const bin = await getEngineBin()
121
+ const result = spawnSync(bin, ["--version"], { encoding: "utf8" })
122
+ return result.status === 0
123
+ } catch {
124
+ return false
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Invoke the engine and return a typed result.
130
+ *
131
+ * The endpoint maps to a subcommand:
132
+ * /diff → engine diff
133
+ * /push → engine push
134
+ * /generate → engine generate
135
+ * /migrations → engine migrations
136
+ * /introspect → engine introspect
137
+ * /validate → engine validate
138
+ * /admin → engine admin (admin-config JSON on stdout)
139
+ */
140
+ export async function engineRequest<T = unknown>(
141
+ endpoint: string,
142
+ body: Record<string, unknown>,
143
+ ): Promise<T> {
144
+ const bin = await getEngineBin()
145
+
146
+ // Write request to a temp file.
147
+ // For CLI-mode endpoints the engine reads the input file as a raw SchemaAst,
148
+ // so we extract the `ast` field when present, otherwise write the full body.
149
+ const tmpDir = join(tmpdir(), "supatype-engine")
150
+ mkdirSync(tmpDir, { recursive: true })
151
+ const reqFile = join(tmpDir, `req-${Date.now()}.json`)
152
+ const inputPayload = body["ast"] !== undefined ? body["ast"] : body
153
+ writeFileSync(reqFile, JSON.stringify(inputPayload))
154
+
155
+ const args = endpointToArgs(endpoint, body, reqFile)
156
+
157
+ const result = spawnSync(bin, args, {
158
+ encoding: "utf8",
159
+ cwd: process.cwd(),
160
+ })
161
+
162
+ // Clean up temp file.
163
+ try { unlinkSync(reqFile) } catch { /* ignore */ }
164
+
165
+ if (result.status !== 0) {
166
+ const stderr = result.stderr?.trim() || "(no output)"
167
+ throw new EngineError(
168
+ `Engine ${endpoint} failed (exit ${result.status}): ${stderr}`,
169
+ endpoint,
170
+ result.status,
171
+ )
172
+ }
173
+
174
+ if (!result.stdout?.trim()) {
175
+ // Some subcommands print nothing on success.
176
+ return {} as T
177
+ }
178
+
179
+ try {
180
+ return JSON.parse(result.stdout) as T
181
+ } catch {
182
+ // Non-JSON stdout — return as message.
183
+ return { message: result.stdout.trim() } as T
184
+ }
185
+ }
186
+
187
+ // ---------------------------------------------------------------------------
188
+ // Endpoint → CLI args mapping
189
+ // ---------------------------------------------------------------------------
190
+
191
+ function endpointToArgs(
192
+ endpoint: string,
193
+ body: Record<string, unknown>,
194
+ reqFile: string,
195
+ ): string[] {
196
+ const dbUrl = (body["database_url"] as string | undefined) ?? ""
197
+ const schema = (body["schema"] as string | undefined) ?? "public"
198
+ const force = body["force"] ? ["--force"] : []
199
+
200
+ switch (endpoint) {
201
+ case "/diff":
202
+ return ["diff", "--input", reqFile, "--database-url", dbUrl, "--schema", schema]
203
+
204
+ case "/push":
205
+ return ["push", "--input", reqFile, "--database-url", dbUrl, "--schema", schema, ...force]
206
+
207
+ case "/parse":
208
+ return ["parse", "--input", reqFile]
209
+
210
+ case "/generate": {
211
+ const lang = (body["lang"] as string | undefined) ?? "typescript"
212
+ return ["generate", "--input", reqFile, "--lang", lang]
213
+ }
214
+
215
+ case "/introspect":
216
+ return ["introspect", "--database-url", dbUrl, "--schema", schema]
217
+
218
+ case "/validate":
219
+ return ["validate", "--input", reqFile]
220
+
221
+ case "/admin":
222
+ return ["admin", "--input", reqFile]
223
+
224
+ default:
225
+ if (endpoint.startsWith("/migrations")) {
226
+ const action = (body["action"] as string | undefined) ?? "list"
227
+ return ["migrations", action, "--database-url", dbUrl]
228
+ }
229
+ return [endpoint.replace(/^\//, ""), "--input", reqFile]
230
+ }
231
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Resolve component binaries, downloading from the CDN when not cached.
3
+ */
4
+
5
+ import {
6
+ resolveBinary,
7
+ download,
8
+ currentPlatform,
9
+ versionFor,
10
+ type Component,
11
+ } from "./binary-cache.js"
12
+ import type { SupatypeProjectConfig } from "./project-config.js"
13
+
14
+ export async function ensureBinary(
15
+ component: Component,
16
+ config: SupatypeProjectConfig,
17
+ ): Promise<string> {
18
+ try {
19
+ return await resolveBinary(component, config)
20
+ } catch (err) {
21
+ const message = err instanceof Error ? err.message : String(err)
22
+ if (!message.includes("not found in cache")) {
23
+ throw err
24
+ }
25
+ }
26
+
27
+ return download(component, versionFor(component, config), currentPlatform())
28
+ }
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Generates a Deno router entrypoint so `deno run` (optionally `deno run --watch` in dev)
3
+ * can serve multiple handlers (same routing contract as `supatype functions serve`).
4
+ */
5
+ import {
6
+ basename,
7
+ join,
8
+ relative,
9
+ dirname,
10
+ resolve as pathResolve,
11
+ } from "node:path"
12
+ import { existsSync, readdirSync, statSync, mkdirSync, writeFileSync } from "node:fs"
13
+
14
+ export interface DiscoveredFunctionRoute {
15
+ name: string
16
+ entrypoint: string
17
+ }
18
+
19
+ /** Discover *.ts handlers and `{name}/index.ts` dirs; skips `_shared`, dotfiles. */
20
+ export function discoverTsFunctionsInDir(functionsDir: string): DiscoveredFunctionRoute[] {
21
+ if (!existsSync(functionsDir)) return []
22
+
23
+ const entries = readdirSync(functionsDir)
24
+ const out: DiscoveredFunctionRoute[] = []
25
+
26
+ for (const entry of entries) {
27
+ if (entry.startsWith("_") || entry.startsWith(".")) continue
28
+
29
+ const fullPath = join(functionsDir, entry)
30
+ const st = statSync(fullPath)
31
+
32
+ if (st.isDirectory()) {
33
+ const indexTs = join(fullPath, "index.ts")
34
+ if (existsSync(indexTs)) out.push({ name: entry, entrypoint: indexTs })
35
+ } else if (entry.endsWith(".ts") && !entry.endsWith(".d.ts")) {
36
+ const name = basename(entry, ".ts")
37
+ out.push({ name, entrypoint: fullPath })
38
+ }
39
+ }
40
+
41
+ return out.sort((a, b) => a.name.localeCompare(b.name))
42
+ }
43
+
44
+ function importSpecFromRouter(routerFilePath: string, entrypoint: string): string {
45
+ const rel = relative(dirname(routerFilePath), entrypoint).replace(/\\/g, "/")
46
+ if (!rel.startsWith(".")) return `./${rel}`
47
+ return rel
48
+ }
49
+
50
+ /** Router source rooted at routerFilePath (defines import paths). */
51
+ export function generateFunctionsRouterSource(
52
+ routerFilePath: string,
53
+ fns: DiscoveredFunctionRoute[],
54
+ ): string {
55
+ const imports = fns.map(
56
+ (fn, i) =>
57
+ `import handler_${i} from "${importSpecFromRouter(routerFilePath, fn.entrypoint)}"`,
58
+ )
59
+ const routes = fns.map((fn, i) => ` "${fn.name}": handler_${i},`)
60
+
61
+ return `// Auto-generated — do not edit (regenerated by supatype dev / functions serve)
62
+ ${imports.join("\n")}
63
+
64
+ const handlers: Record<string, (req: Request) => Response | Promise<Response>> = {
65
+ ${routes.join("\n")}
66
+ }
67
+
68
+ const port = parseInt(Deno.env.get("PORT") ?? "8001", 10)
69
+ const functionsDir = Deno.env.get("SUPATYPE_DENO_FUNCTIONS_DIR") ?? ""
70
+ const normalizedFunctionsDir = functionsDir.endsWith("/") ? functionsDir.slice(0, -1) : functionsDir
71
+ const sharedEnvPath = Deno.env.get("SUPATYPE_SHARED_ENV_FILE")
72
+ ?? (normalizedFunctionsDir ? normalizedFunctionsDir + "/.env.local" : "")
73
+
74
+ let envLock: Promise<void> = Promise.resolve()
75
+
76
+ async function withEnvLock<T>(run: () => Promise<T>): Promise<T> {
77
+ const prev = envLock
78
+ let release: () => void = () => {}
79
+ envLock = new Promise<void>(resolve => { release = resolve })
80
+ await prev
81
+ try {
82
+ return await run()
83
+ } finally {
84
+ release()
85
+ }
86
+ }
87
+
88
+ async function readEnvFile(path: string): Promise<Record<string, string>> {
89
+ if (!path) return {}
90
+ try {
91
+ const text = await Deno.readTextFile(path)
92
+ const out: Record<string, string> = {}
93
+ for (const line of text.split("\\n")) {
94
+ const trimmed = line.trim()
95
+ if (!trimmed || trimmed.startsWith("#")) continue
96
+ const eq = trimmed.indexOf("=")
97
+ if (eq <= 0) continue
98
+ out[trimmed.slice(0, eq)] = trimmed.slice(eq + 1)
99
+ }
100
+ return out
101
+ } catch {
102
+ return {}
103
+ }
104
+ }
105
+
106
+ async function scopedEnvForFunction(fnName: string): Promise<Record<string, string>> {
107
+ const shared = await readEnvFile(sharedEnvPath)
108
+ if (!normalizedFunctionsDir) return shared
109
+ const fnPath = normalizedFunctionsDir + "/.env." + fnName + ".local"
110
+ const fnVars = await readEnvFile(fnPath)
111
+ return { ...shared, ...fnVars }
112
+ }
113
+
114
+ async function runWithScopedEnv<T>(fnName: string, run: () => Promise<T>): Promise<T> {
115
+ return withEnvLock(async () => {
116
+ const scoped = await scopedEnvForFunction(fnName)
117
+ const prev = new Map<string, string | undefined>()
118
+ for (const [k, v] of Object.entries(scoped)) {
119
+ prev.set(k, Deno.env.get(k))
120
+ Deno.env.set(k, v)
121
+ }
122
+ try {
123
+ return await run()
124
+ } finally {
125
+ for (const k of Object.keys(scoped)) {
126
+ const old = prev.get(k)
127
+ if (old === undefined) Deno.env.delete(k)
128
+ else Deno.env.set(k, old)
129
+ }
130
+ }
131
+ })
132
+ }
133
+
134
+ Deno.serve({ port }, async (req: Request): Promise<Response> => {
135
+ const url = new URL(req.url)
136
+ const pathParts = url.pathname
137
+ .replace(/^\\/functions\\/v1\\/?/, "")
138
+ .split("/")
139
+ .filter(Boolean)
140
+ const fnName = pathParts[0] ?? ""
141
+
142
+ if (!fnName || !handlers[fnName]) {
143
+ return new Response(JSON.stringify({
144
+ error: "not_found",
145
+ message: fnName ? \`Function "\${fnName}" not found\` : "No function specified",
146
+ available: Object.keys(handlers),
147
+ }), { status: 404, headers: { "Content-Type": "application/json" } })
148
+ }
149
+
150
+ try {
151
+ const start = performance.now()
152
+ const response = await runWithScopedEnv(fnName, async () => {
153
+ const prev = new Map<string, string | undefined>()
154
+ const setScoped = (key: string, value: string | undefined) => {
155
+ if (value === undefined || value.length === 0) return
156
+ prev.set(key, Deno.env.get(key))
157
+ Deno.env.set(key, value)
158
+ }
159
+
160
+ // Supatype runtime defaults available to every invocation.
161
+ const supatypeUrl = Deno.env.get("SUPATYPE_URL")
162
+ const supatypeAnon = Deno.env.get("SUPATYPE_ANON_KEY")
163
+ const supatypeServiceRole = Deno.env.get("SUPATYPE_SERVICE_ROLE_KEY")
164
+ const supatypeDbUrl = Deno.env.get("SUPATYPE_DB_URL") ?? Deno.env.get("DATABASE_URL")
165
+ const supatypeJwks = Deno.env.get("SUPATYPE_JWKS")
166
+
167
+ setScoped("SUPATYPE_URL", supatypeUrl)
168
+ setScoped("SUPATYPE_ANON_KEY", supatypeAnon)
169
+ setScoped("SUPATYPE_SERVICE_ROLE_KEY", supatypeServiceRole)
170
+ setScoped("SUPATYPE_DB_URL", supatypeDbUrl)
171
+ setScoped("SUPATYPE_JWKS", supatypeJwks)
172
+ if (!Deno.env.get("SUPATYPE_PUBLISHABLE_KEYS") && supatypeAnon) {
173
+ setScoped("SUPATYPE_PUBLISHABLE_KEYS", JSON.stringify({ anon: supatypeAnon }))
174
+ }
175
+ if (!Deno.env.get("SUPATYPE_SECRET_KEYS") && supatypeServiceRole) {
176
+ setScoped("SUPATYPE_SECRET_KEYS", JSON.stringify({ service_role: supatypeServiceRole }))
177
+ }
178
+
179
+ setScoped("SUPATYPE_REGION", Deno.env.get("SUPATYPE_REGION") ?? "local")
180
+ setScoped("SUPATYPE_EXECUTION_ID", crypto.randomUUID())
181
+ setScoped("DENO_DEPLOYMENT_ID", Deno.env.get("DENO_DEPLOYMENT_ID") ?? "local-dev")
182
+
183
+ try {
184
+ return await handlers[fnName]!(req)
185
+ } finally {
186
+ for (const [key, old] of prev.entries()) {
187
+ if (old === undefined) Deno.env.delete(key)
188
+ else Deno.env.set(key, old)
189
+ }
190
+ }
191
+ })
192
+ const duration = (performance.now() - start).toFixed(1)
193
+ console.log(\`\${req.method} /functions/v1/\${fnName} → \${response.status} (\${duration}ms)\`)
194
+ return response
195
+ } catch (err) {
196
+ console.error(\`Error in function "\${fnName}":\`, err)
197
+ return new Response(JSON.stringify({
198
+ error: "function_error",
199
+ message: err instanceof Error ? err.message : "Unknown error",
200
+ }), { status: 500, headers: { "Content-Type": "application/json" } })
201
+ }
202
+ })
203
+ `
204
+ }
205
+
206
+ /**
207
+ * Writes `.supatype/functions-router.ts` under project cwd. Returns absolute path or null if no handlers.
208
+ * Pass `routes` when callers already scanned the dir to avoid duplicate I/O.
209
+ */
210
+ export function writeDevFunctionsRouter(
211
+ cwd: string,
212
+ functionsDir: string,
213
+ routes?: DiscoveredFunctionRoute[],
214
+ ): string | null {
215
+ const fns = routes ?? discoverTsFunctionsInDir(functionsDir)
216
+ if (fns.length === 0) return null
217
+
218
+ const supatypeDir = pathResolve(cwd, ".supatype")
219
+ mkdirSync(supatypeDir, { recursive: true })
220
+ const routerPath = join(supatypeDir, "functions-router.ts")
221
+ const src = generateFunctionsRouterSource(routerPath, fns)
222
+ writeFileSync(routerPath, src, "utf8")
223
+ return routerPath
224
+ }
package/src/index.ts CHANGED
@@ -4,16 +4,8 @@
4
4
  * For CLI usage: use the `supatype` binary.
5
5
  */
6
6
 
7
- export { invokeEngine, getEnginePath, ensureEngine, getEnginePathAsync } from "./engine.js"
8
- export {
9
- ENGINE_VERSION,
10
- ENGINE_REPO,
11
- ENGINE_DOWNLOAD_BASE,
12
- CDN_BASE_URL,
13
- ENGINE_RELEASES_REPO,
14
- GITHUB_RELEASES_FALLBACK_URL,
15
- } from "./engine-version.js"
7
+ export { ensureEngine, engineRequest, engineHealth, EngineError } from "./engine-client.js"
8
+ export type { EngineResult, DiffResult, Operation, IntrospectResult } from "./engine-client.js"
16
9
  export { defineConfig, loadConfig, loadSchemaAst } from "./config.js"
17
- export type { SupatypeConfig } from "./config.js"
18
- export { detectPlatform } from "./engine/platform.js"
19
- export type { PlatformInfo } from "./engine/platform.js"
10
+ export type { SupatypeConfig, SupatypeProjectConfig } from "./config.js"
11
+ export { schemaPathFromProject, localDSN, connectionString, serverBaseUrl } from "./project-config.js"
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Kong declarative config for local dev and self-hosted production.
3
+ * When `engineGatewayKey` is set, `/studio-config` and `/sql` require `apikey` (key-auth).
4
+ */
5
+
6
+ export interface KongDeclarativeOptions {
7
+ /**
8
+ * When non-empty, Kong `key-auth` is enabled on engine routes; clients must send
9
+ * header `apikey: <key>` (same convention as PostgREST). Omit for open local dev.
10
+ */
11
+ engineGatewayKey?: string | undefined
12
+ appUpstream?: string | undefined
13
+ staticAppServiceUrl?: string | undefined
14
+ functionsServiceUrl?: string | undefined
15
+ /** Self-host: route API paths through supatype-server (see runtime-routes). */
16
+ unifiedGateway?: boolean | undefined
17
+ }
18
+
19
+ /** Escape a string for use inside YAML double quotes. */
20
+ function yamlQuotedString(s: string): string {
21
+ return JSON.stringify(s)
22
+ }
23
+
24
+ import { runtimeRouteSpec } from "./runtime-routes.js"
25
+
26
+ /**
27
+ * Build full `kong.yml` content. Single source of truth for CLI `dev` and `self-host setup`.
28
+ */
29
+ export function buildKongDeclarative(opts: KongDeclarativeOptions = {}): string {
30
+ const gatewayKey = opts.engineGatewayKey?.trim()
31
+ const secured = Boolean(gatewayKey)
32
+ const routes = runtimeRouteSpec({
33
+ ...(opts.unifiedGateway === true && { unifiedGateway: true }),
34
+ ...(opts.unifiedGateway !== true && opts.appUpstream !== undefined && { appUpstream: opts.appUpstream }),
35
+ ...(opts.unifiedGateway !== true &&
36
+ opts.staticAppServiceUrl !== undefined && { staticAppServiceUrl: opts.staticAppServiceUrl }),
37
+ ...(opts.functionsServiceUrl !== undefined && { functionsServiceUrl: opts.functionsServiceUrl }),
38
+ })
39
+
40
+ const consumersBlock = secured
41
+ ? `
42
+ consumers:
43
+ - username: studio-engine-gateway
44
+ keyauth_credentials:
45
+ - key: ${yamlQuotedString(gatewayKey!)}
46
+ `
47
+ : ""
48
+
49
+ const servicesBlock = routes.map((route) => {
50
+ const routePlugins = route.engineProtected && secured
51
+ ? ` plugins:
52
+ - name: key-auth
53
+ config:
54
+ key_names:
55
+ - apikey
56
+ hide_credentials: true
57
+ `
58
+ : ""
59
+ const protocols = route.protocols && route.protocols.length > 0
60
+ ? ` protocols:\n${route.protocols.map((p) => ` - ${p}`).join("\n")}\n`
61
+ : ""
62
+ const stripPath = route.stripPath ?? false
63
+ return ` - name: ${route.serviceName}
64
+ url: ${route.serviceUrl}
65
+ routes:
66
+ - name: ${route.name}
67
+ strip_path: ${stripPath}
68
+ paths:
69
+ ${route.paths.map((path) => ` - ${path}`).join("\n")}
70
+ ${protocols}${routePlugins}`
71
+ }).join("\n")
72
+
73
+ return `_format_version: "3.0"
74
+ ${consumersBlock}
75
+ services:
76
+ ${servicesBlock}
77
+ `
78
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Host port published for Kong in local Docker Compose. Kong still listens on
3
+ * 8000 inside the container; this avoids clashing with other tools on :8000.
4
+ */
5
+ export const LOCAL_KONG_HOST_PORT = 18473
6
+
7
+ export function localKongBaseUrl(): string {
8
+ return `http://localhost:${LOCAL_KONG_HOST_PORT}`
9
+ }
@@ -0,0 +1,14 @@
1
+ import { join } from "node:path"
2
+
3
+ /**
4
+ * Returns env vars that configure supatype-server to use local-disk storage.
5
+ * Spread into the server process env when config.storage?.provider !== "s3".
6
+ *
7
+ * @param stateDir Per-project state directory (e.g. ~/.supatype/projects/{name}/)
8
+ */
9
+ export function localStorageEnv(stateDir: string): Record<string, string> {
10
+ return {
11
+ STORAGE_PROVIDER: "local",
12
+ STORAGE_PATH: join(stateDir, "storage"),
13
+ }
14
+ }
@@ -0,0 +1,15 @@
1
+ import { createHash } from "node:crypto"
2
+
3
+ /** PgBouncer md5 auth: "md5" + md5(password + username) — same as supatype-cloud / self-host. */
4
+ export function pgbouncerMd5Hash(password: string, username: string): string {
5
+ return "md5" + createHash("md5").update(password + username).digest("hex")
6
+ }
7
+
8
+ /** Two roles used by local compose: superuser pools (GoTrue, engine) and PostgREST. */
9
+ export function pgbouncerUserlistContent(pgPassword: string): string {
10
+ return `# PgBouncer md5 userlist — matches supatype-cloud transaction pool (auth_type = md5).
11
+ # Regenerated by supatype dev --local / init from POSTGRES_PASSWORD.
12
+ "supatype_admin" "${pgbouncerMd5Hash(pgPassword, "supatype_admin")}"
13
+ "authenticator" "${pgbouncerMd5Hash(pgPassword, "authenticator")}"
14
+ `
15
+ }