@supatype/cli 0.1.5 → 0.1.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 (247) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-test.log +143 -93
  3. package/.turbo/turbo-typecheck.log +1 -1
  4. package/deno.d.ts +52 -0
  5. package/dist/augmentation-generator.d.ts +22 -0
  6. package/dist/augmentation-generator.d.ts.map +1 -1
  7. package/dist/augmentation-generator.js +43 -7
  8. package/dist/augmentation-generator.js.map +1 -1
  9. package/dist/binary-cache.d.ts +14 -0
  10. package/dist/binary-cache.d.ts.map +1 -1
  11. package/dist/binary-cache.js +33 -13
  12. package/dist/binary-cache.js.map +1 -1
  13. package/dist/cache-clean.d.ts +15 -0
  14. package/dist/cache-clean.d.ts.map +1 -0
  15. package/dist/cache-clean.js +76 -0
  16. package/dist/cache-clean.js.map +1 -0
  17. package/dist/cache-pins.d.ts +51 -0
  18. package/dist/cache-pins.d.ts.map +1 -0
  19. package/dist/cache-pins.js +184 -0
  20. package/dist/cache-pins.js.map +1 -0
  21. package/dist/cli-install-method.d.ts +19 -0
  22. package/dist/cli-install-method.d.ts.map +1 -0
  23. package/dist/cli-install-method.js +60 -0
  24. package/dist/cli-install-method.js.map +1 -0
  25. package/dist/cli.d.ts.map +1 -1
  26. package/dist/cli.js +6 -0
  27. package/dist/cli.js.map +1 -1
  28. package/dist/cloud-credentials.d.ts +34 -0
  29. package/dist/cloud-credentials.d.ts.map +1 -0
  30. package/dist/cloud-credentials.js +139 -0
  31. package/dist/cloud-credentials.js.map +1 -0
  32. package/dist/commands/admin.d.ts +11 -0
  33. package/dist/commands/admin.d.ts.map +1 -1
  34. package/dist/commands/admin.js +131 -22
  35. package/dist/commands/admin.js.map +1 -1
  36. package/dist/commands/cache.d.ts.map +1 -1
  37. package/dist/commands/cache.js +67 -71
  38. package/dist/commands/cache.js.map +1 -1
  39. package/dist/commands/cloud.d.ts.map +1 -1
  40. package/dist/commands/cloud.js +32 -8
  41. package/dist/commands/cloud.js.map +1 -1
  42. package/dist/commands/db.d.ts +1 -0
  43. package/dist/commands/db.d.ts.map +1 -1
  44. package/dist/commands/db.js +147 -1
  45. package/dist/commands/db.js.map +1 -1
  46. package/dist/commands/dev.d.ts.map +1 -1
  47. package/dist/commands/dev.js +50 -10
  48. package/dist/commands/dev.js.map +1 -1
  49. package/dist/commands/doctor.d.ts +28 -0
  50. package/dist/commands/doctor.d.ts.map +1 -1
  51. package/dist/commands/doctor.js +60 -3
  52. package/dist/commands/doctor.js.map +1 -1
  53. package/dist/commands/functions.d.ts +7 -0
  54. package/dist/commands/functions.d.ts.map +1 -1
  55. package/dist/commands/functions.js +40 -6
  56. package/dist/commands/functions.js.map +1 -1
  57. package/dist/commands/generate.d.ts.map +1 -1
  58. package/dist/commands/generate.js +5 -1
  59. package/dist/commands/generate.js.map +1 -1
  60. package/dist/commands/hooks.d.ts +12 -0
  61. package/dist/commands/hooks.d.ts.map +1 -0
  62. package/dist/commands/hooks.js +118 -0
  63. package/dist/commands/hooks.js.map +1 -0
  64. package/dist/commands/init.d.ts.map +1 -1
  65. package/dist/commands/init.js +41 -7
  66. package/dist/commands/init.js.map +1 -1
  67. package/dist/commands/internal.d.ts +6 -0
  68. package/dist/commands/internal.d.ts.map +1 -0
  69. package/dist/commands/internal.js +22 -0
  70. package/dist/commands/internal.js.map +1 -0
  71. package/dist/commands/keys.d.ts.map +1 -1
  72. package/dist/commands/keys.js +2 -0
  73. package/dist/commands/keys.js.map +1 -1
  74. package/dist/commands/link-helpers.d.ts.map +1 -1
  75. package/dist/commands/link-helpers.js +8 -2
  76. package/dist/commands/link-helpers.js.map +1 -1
  77. package/dist/commands/login.d.ts +7 -0
  78. package/dist/commands/login.d.ts.map +1 -0
  79. package/dist/commands/login.js +110 -0
  80. package/dist/commands/login.js.map +1 -0
  81. package/dist/commands/push.d.ts.map +1 -1
  82. package/dist/commands/push.js +110 -7
  83. package/dist/commands/push.js.map +1 -1
  84. package/dist/commands/self-update.d.ts +7 -0
  85. package/dist/commands/self-update.d.ts.map +1 -1
  86. package/dist/commands/self-update.js +18 -11
  87. package/dist/commands/self-update.js.map +1 -1
  88. package/dist/config.d.ts.map +1 -1
  89. package/dist/config.js +18 -3
  90. package/dist/config.js.map +1 -1
  91. package/dist/db-preflight.d.ts +78 -0
  92. package/dist/db-preflight.d.ts.map +1 -0
  93. package/dist/db-preflight.js +452 -0
  94. package/dist/db-preflight.js.map +1 -0
  95. package/dist/dev-compose.d.ts +1 -1
  96. package/dist/dev-compose.d.ts.map +1 -1
  97. package/dist/dev-compose.js +129 -72
  98. package/dist/dev-compose.js.map +1 -1
  99. package/dist/env-file.d.ts +40 -2
  100. package/dist/env-file.d.ts.map +1 -1
  101. package/dist/env-file.js +132 -11
  102. package/dist/env-file.js.map +1 -1
  103. package/dist/field-masking-tier.d.ts +50 -0
  104. package/dist/field-masking-tier.d.ts.map +1 -0
  105. package/dist/field-masking-tier.js +91 -0
  106. package/dist/field-masking-tier.js.map +1 -0
  107. package/dist/functions-deno-types.d.ts +24 -0
  108. package/dist/functions-deno-types.d.ts.map +1 -0
  109. package/dist/functions-deno-types.js +155 -0
  110. package/dist/functions-deno-types.js.map +1 -0
  111. package/dist/hook-upload.d.ts +30 -0
  112. package/dist/hook-upload.d.ts.map +1 -0
  113. package/dist/hook-upload.js +86 -0
  114. package/dist/hook-upload.js.map +1 -0
  115. package/dist/hooks-generator.d.ts +8 -0
  116. package/dist/hooks-generator.d.ts.map +1 -0
  117. package/dist/hooks-generator.js +332 -0
  118. package/dist/hooks-generator.js.map +1 -0
  119. package/dist/link.d.ts +4 -0
  120. package/dist/link.d.ts.map +1 -1
  121. package/dist/link.js +2 -0
  122. package/dist/link.js.map +1 -1
  123. package/dist/local-secrets.d.ts +71 -0
  124. package/dist/local-secrets.d.ts.map +1 -0
  125. package/dist/local-secrets.js +100 -0
  126. package/dist/local-secrets.js.map +1 -0
  127. package/dist/model-hooks.d.ts +73 -0
  128. package/dist/model-hooks.d.ts.map +1 -0
  129. package/dist/model-hooks.js +211 -0
  130. package/dist/model-hooks.js.map +1 -0
  131. package/dist/postgres-ctl.d.ts +11 -0
  132. package/dist/postgres-ctl.d.ts.map +1 -1
  133. package/dist/postgres-ctl.js +25 -2
  134. package/dist/postgres-ctl.js.map +1 -1
  135. package/dist/project-config.d.ts +121 -1
  136. package/dist/project-config.d.ts.map +1 -1
  137. package/dist/project-config.js +150 -1
  138. package/dist/project-config.js.map +1 -1
  139. package/dist/resolve-target.d.ts +4 -0
  140. package/dist/resolve-target.d.ts.map +1 -1
  141. package/dist/resolve-target.js +69 -73
  142. package/dist/resolve-target.js.map +1 -1
  143. package/dist/schema-ast-v2.d.ts +2 -1
  144. package/dist/schema-ast-v2.d.ts.map +1 -1
  145. package/dist/schema-ast-v2.js +4 -2
  146. package/dist/schema-ast-v2.js.map +1 -1
  147. package/dist/schema-push-lock.d.ts.map +1 -1
  148. package/dist/schema-push-lock.js +43 -16
  149. package/dist/schema-push-lock.js.map +1 -1
  150. package/dist/self-host-compose.d.ts +24 -0
  151. package/dist/self-host-compose.d.ts.map +1 -1
  152. package/dist/self-host-compose.js +301 -69
  153. package/dist/self-host-compose.js.map +1 -1
  154. package/dist/service-role-check.d.ts +21 -0
  155. package/dist/service-role-check.d.ts.map +1 -0
  156. package/dist/service-role-check.js +85 -0
  157. package/dist/service-role-check.js.map +1 -0
  158. package/dist/target-client.d.ts +17 -0
  159. package/dist/target-client.d.ts.map +1 -1
  160. package/dist/target-client.js +69 -17
  161. package/dist/target-client.js.map +1 -1
  162. package/dist/type-extractor.d.ts.map +1 -1
  163. package/dist/type-extractor.js +736 -32
  164. package/dist/type-extractor.js.map +1 -1
  165. package/dist/ui/runtime/command-chrome.js +1 -1
  166. package/dist/ui/runtime/command-chrome.js.map +1 -1
  167. package/dist/ui/runtime/stdin-after-ink.d.ts.map +1 -1
  168. package/dist/ui/runtime/stdin-after-ink.js +3 -1
  169. package/dist/ui/runtime/stdin-after-ink.js.map +1 -1
  170. package/package.json +4 -1
  171. package/scripts/generate-homebrew-formula.mjs +121 -0
  172. package/src/augmentation-generator.ts +46 -9
  173. package/src/binary-cache.ts +47 -15
  174. package/src/cache-clean.ts +81 -0
  175. package/src/cache-pins.ts +243 -0
  176. package/src/cli-install-method.ts +67 -0
  177. package/src/cli.ts +6 -0
  178. package/src/cloud-credentials.ts +183 -0
  179. package/src/commands/admin.ts +153 -28
  180. package/src/commands/cache.ts +90 -75
  181. package/src/commands/cloud.ts +32 -8
  182. package/src/commands/db.ts +162 -1
  183. package/src/commands/dev.ts +57 -8
  184. package/src/commands/doctor.ts +69 -3
  185. package/src/commands/functions.ts +42 -5
  186. package/src/commands/generate.ts +5 -1
  187. package/src/commands/hooks.ts +138 -0
  188. package/src/commands/init.ts +42 -8
  189. package/src/commands/internal.ts +23 -0
  190. package/src/commands/keys.ts +2 -0
  191. package/src/commands/link-helpers.ts +9 -2
  192. package/src/commands/login.ts +127 -0
  193. package/src/commands/push.ts +124 -7
  194. package/src/commands/self-update.ts +85 -13
  195. package/src/config.ts +22 -3
  196. package/src/db-preflight.ts +575 -0
  197. package/src/dev-compose.ts +144 -73
  198. package/src/env-file.ts +157 -11
  199. package/src/field-masking-tier.ts +107 -0
  200. package/src/functions-deno-types.ts +188 -0
  201. package/src/hook-upload.ts +107 -0
  202. package/src/hooks-generator.ts +352 -0
  203. package/src/link.ts +7 -0
  204. package/src/local-secrets.ts +104 -0
  205. package/src/model-hooks.ts +260 -0
  206. package/src/postgres-ctl.ts +29 -2
  207. package/src/project-config.ts +236 -2
  208. package/src/resolve-target.ts +113 -68
  209. package/src/schema-ast-v2.ts +5 -2
  210. package/src/schema-push-lock.ts +56 -17
  211. package/src/self-host-compose.ts +344 -68
  212. package/src/service-role-check.ts +111 -0
  213. package/src/target-client.ts +105 -18
  214. package/src/type-extractor.ts +883 -28
  215. package/src/ui/runtime/command-chrome.ts +1 -1
  216. package/src/ui/runtime/stdin-after-ink.ts +3 -1
  217. package/tests/access-composition.test.ts +667 -0
  218. package/tests/admin-studio-membership.test.ts +121 -0
  219. package/tests/api-schemas.test.ts +83 -0
  220. package/tests/archive-name-alias.test.ts +42 -0
  221. package/tests/augmentation-generator.test.ts +72 -4
  222. package/tests/cache-clean.test.ts +206 -0
  223. package/tests/cli-help.test.ts +3 -0
  224. package/tests/cli-install-method.test.ts +66 -0
  225. package/tests/command-chrome-coverage.test.ts +31 -26
  226. package/tests/command-chrome-wrap.test.ts +1 -0
  227. package/tests/db-preflight.test.ts +72 -0
  228. package/tests/doctor-render.test.ts +62 -0
  229. package/tests/doctor-service-role.test.ts +55 -0
  230. package/tests/env-file.test.ts +121 -0
  231. package/tests/external-database-compose.test.ts +184 -0
  232. package/tests/external-database-config.test.ts +154 -0
  233. package/tests/field-masking-tier.test.ts +184 -0
  234. package/tests/functions-deno-types.test.ts +197 -0
  235. package/tests/homebrew-formula.test.ts +58 -0
  236. package/tests/hook-upload.test.ts +125 -0
  237. package/tests/hooks-generator.test.ts +212 -0
  238. package/tests/init.test.ts +3 -0
  239. package/tests/local-secrets.test.ts +92 -0
  240. package/tests/model-hooks.test.ts +322 -0
  241. package/tests/runtime-contract.test.ts +113 -1
  242. package/tests/service-role-check.test.ts +109 -0
  243. package/tests/service-role-routes.test.ts +51 -0
  244. package/tests/target-client-refresh.test.ts +94 -0
  245. package/tests/type-extractor.test.ts +187 -0
  246. package/tsconfig.tsbuildinfo +1 -1
  247. package/tests/engine-distribution.test.ts +0 -418
@@ -6,6 +6,7 @@ import { ensureEngine, engineRequest } from "./engine-client.js"
6
6
  import type { SchemaSourcesPayload } from "./schema-sources.js"
7
7
  import {
8
8
  connectionString,
9
+ pgSchema,
9
10
  resolveRuntimeProvider,
10
11
  schemaPathFromProject,
11
12
  serverBaseUrl,
@@ -20,7 +21,12 @@ import {
20
21
  type LocalEnvironment,
21
22
  type ProjectLink,
22
23
  } from "./link.js"
23
- import { targetFetch } from "./target-client.js"
24
+ import {
25
+ persistCloudSession,
26
+ resolveCloudAccessToken,
27
+ resolveCloudRefreshToken,
28
+ } from "./cloud-credentials.js"
29
+ import { targetFetch, type TargetFetchOptions } from "./target-client.js"
24
30
 
25
31
  export interface ResolveTargetFlags {
26
32
  env?: string | undefined
@@ -38,8 +44,12 @@ export interface DeployTarget {
38
44
  apiBaseUrl: string
39
45
  apiPrefix: "/api/v1" | "/platform/v1"
40
46
  token?: string | undefined
47
+ /** Cloud GoTrue refresh token (mode === "cloud" only). */
48
+ refreshToken?: string | undefined
41
49
  orgId?: string | undefined
42
50
  link: ProjectLink | null
51
+ /** Project cwd — needed to persist refreshed cloud tokens. */
52
+ cwd?: string
43
53
  /** Engine subprocess path when mode is direct or local without control plane. */
44
54
  databaseUrl?: string
45
55
  }
@@ -147,12 +157,19 @@ export function resolveTarget(cwd: string, flags: ResolveTargetFlags = {}): Depl
147
157
  )
148
158
  }
149
159
 
150
- const token = resolveEnvironmentToken(link, envTarget)
160
+ const token =
161
+ (link.kind === "cloud" ? resolveCloudAccessToken(link) : undefined) ??
162
+ resolveEnvironmentToken(link, envTarget)
151
163
  if (!token) {
152
- throw new Error(`No token for environment "${envName}". Re-run supatype link --token ...`)
164
+ throw new Error(
165
+ link.kind === "cloud"
166
+ ? `No cloud session for environment "${envName}". Run: supatype login`
167
+ : `No token for environment "${envName}". Re-run supatype link --token ...`,
168
+ )
153
169
  }
154
170
 
155
171
  if (link.kind === "cloud") {
172
+ const refreshToken = resolveCloudRefreshToken(link)
156
173
  return {
157
174
  mode: "cloud",
158
175
  environment: envName,
@@ -161,6 +178,8 @@ export function resolveTarget(cwd: string, flags: ResolveTargetFlags = {}): Depl
161
178
  apiPrefix: "/api/v1",
162
179
  token,
163
180
  link,
181
+ cwd,
182
+ ...(refreshToken !== undefined ? { refreshToken } : {}),
164
183
  ...(link.orgId !== undefined ? { orgId: link.orgId } : {}),
165
184
  }
166
185
  }
@@ -173,6 +192,44 @@ export function resolveTarget(cwd: string, flags: ResolveTargetFlags = {}): Depl
173
192
  apiPrefix: "/platform/v1",
174
193
  token,
175
194
  link,
195
+ cwd,
196
+ }
197
+ }
198
+
199
+ function cloudAuthRefresh(target: DeployTarget): TargetFetchOptions["authRefresh"] | undefined {
200
+ if (target.mode !== "cloud" || !target.refreshToken || !target.cwd) return undefined
201
+ const cloudApiUrl = (target.link?.cloudApiUrl ?? target.apiBaseUrl).replace(/\/$/, "")
202
+ const cwd = target.cwd
203
+ return {
204
+ cloudApiUrl,
205
+ refreshToken: target.refreshToken,
206
+ onRefreshed: ({ accessToken, refreshToken }) => {
207
+ persistCloudSession(cwd, {
208
+ apiUrl: cloudApiUrl,
209
+ accessToken,
210
+ ...(refreshToken !== undefined ? { refreshToken } : {}),
211
+ })
212
+ target.token = accessToken
213
+ if (refreshToken !== undefined) target.refreshToken = refreshToken
214
+ },
215
+ }
216
+ }
217
+
218
+ function apiFetchOpts(
219
+ target: DeployTarget,
220
+ method: string,
221
+ path: string,
222
+ body?: unknown,
223
+ ): TargetFetchOptions {
224
+ const authRefresh = cloudAuthRefresh(target)
225
+ return {
226
+ method,
227
+ path,
228
+ token: target.token!,
229
+ ...(body !== undefined ? { body } : {}),
230
+ ...(target.orgId !== undefined ? { orgId: target.orgId } : {}),
231
+ ...(target.mode === "cloud" ? { environment: target.environment } : {}),
232
+ ...(authRefresh !== undefined ? { authRefresh } : {}),
176
233
  }
177
234
  }
178
235
 
@@ -194,14 +251,14 @@ export async function targetSchemaDiff(
194
251
  })
195
252
  }
196
253
 
197
- return targetFetch<DiffResult>(target.apiBaseUrl, target.apiPrefix, {
198
- method: "POST",
199
- path: projectPath(target, "/schema/diff"),
200
- body: { ast, schema: opts?.schema ?? "public" },
201
- token: target.token!,
202
- orgId: target.orgId,
203
- environment: target.mode === "cloud" ? target.environment : undefined,
204
- })
254
+ return targetFetch<DiffResult>(
255
+ target.apiBaseUrl,
256
+ target.apiPrefix,
257
+ apiFetchOpts(target, "POST", projectPath(target, "/schema/diff"), {
258
+ ast,
259
+ schema: opts?.schema ?? "public",
260
+ }),
261
+ )
205
262
  }
206
263
 
207
264
  export async function targetSchemaPush(
@@ -236,14 +293,11 @@ export async function targetSchemaPush(
236
293
  }
237
294
  }
238
295
 
239
- return targetFetch(target.apiBaseUrl, target.apiPrefix, {
240
- method: "POST",
241
- path: projectPath(target, "/schema/push"),
242
- body: pushBody,
243
- token: target.token!,
244
- orgId: target.orgId,
245
- environment: target.mode === "cloud" ? target.environment : undefined,
246
- })
296
+ return targetFetch(
297
+ target.apiBaseUrl,
298
+ target.apiPrefix,
299
+ apiFetchOpts(target, "POST", projectPath(target, "/schema/push"), pushBody),
300
+ )
247
301
  }
248
302
 
249
303
  export interface SchemaRollbackResult {
@@ -286,14 +340,13 @@ export async function targetSchemaRollback(
286
340
  })
287
341
  }
288
342
 
289
- return targetFetch<SchemaRollbackResult>(target.apiBaseUrl, target.apiPrefix, {
290
- method: "POST",
291
- path: projectPath(target, "/schema/rollback"),
292
- body: { schema: opts?.schema ?? "public" },
293
- token: target.token!,
294
- orgId: target.orgId,
295
- environment: target.mode === "cloud" ? target.environment : undefined,
296
- })
343
+ return targetFetch<SchemaRollbackResult>(
344
+ target.apiBaseUrl,
345
+ target.apiPrefix,
346
+ apiFetchOpts(target, "POST", projectPath(target, "/schema/rollback"), {
347
+ schema: opts?.schema ?? "public",
348
+ }),
349
+ )
297
350
  }
298
351
 
299
352
  export async function targetListMigrations(
@@ -308,13 +361,11 @@ export async function targetListMigrations(
308
361
  return Array.isArray(result) ? result : (result.migrations ?? [])
309
362
  }
310
363
 
311
- return targetFetch<MigrationListEntry[]>(target.apiBaseUrl, target.apiPrefix, {
312
- method: "GET",
313
- path: projectPath(target, "/schema/migrations"),
314
- token: target.token!,
315
- orgId: target.orgId,
316
- environment: target.mode === "cloud" ? target.environment : undefined,
317
- })
364
+ return targetFetch<MigrationListEntry[]>(
365
+ target.apiBaseUrl,
366
+ target.apiPrefix,
367
+ apiFetchOpts(target, "GET", projectPath(target, "/schema/migrations")),
368
+ )
318
369
  }
319
370
 
320
371
  export async function targetSchemaDoctor(
@@ -332,14 +383,15 @@ export async function targetSchemaDoctor(
332
383
  })
333
384
  }
334
385
 
335
- return targetFetch(target.apiBaseUrl, target.apiPrefix, {
336
- method: "POST",
337
- path: projectPath(target, "/schema/doctor"),
338
- body: { ast, no_cache: opts?.noCache ?? false, schema: opts?.schema ?? "public" },
339
- token: target.token!,
340
- orgId: target.orgId,
341
- environment: target.mode === "cloud" ? target.environment : undefined,
342
- })
386
+ return targetFetch(
387
+ target.apiBaseUrl,
388
+ target.apiPrefix,
389
+ apiFetchOpts(target, "POST", projectPath(target, "/schema/doctor"), {
390
+ ast,
391
+ no_cache: opts?.noCache ?? false,
392
+ schema: opts?.schema ?? "public",
393
+ }),
394
+ )
343
395
  }
344
396
 
345
397
  export async function targetSchemaIntrospect(
@@ -354,14 +406,13 @@ export async function targetSchemaIntrospect(
354
406
  })
355
407
  }
356
408
 
357
- return targetFetch(target.apiBaseUrl, target.apiPrefix, {
358
- method: "POST",
359
- path: projectPath(target, "/schema/introspect"),
360
- body: { schema: opts?.schema ?? "public" },
361
- token: target.token!,
362
- orgId: target.orgId,
363
- environment: target.mode === "cloud" ? target.environment : undefined,
364
- })
409
+ return targetFetch(
410
+ target.apiBaseUrl,
411
+ target.apiPrefix,
412
+ apiFetchOpts(target, "POST", projectPath(target, "/schema/introspect"), {
413
+ schema: opts?.schema ?? "public",
414
+ }),
415
+ )
365
416
  }
366
417
 
367
418
  export async function targetSchemaAdopt(
@@ -381,20 +432,17 @@ export async function targetSchemaAdopt(
381
432
  })
382
433
  }
383
434
 
384
- return targetFetch(target.apiBaseUrl, target.apiPrefix, {
385
- method: "POST",
386
- path: projectPath(target, "/schema/adopt"),
387
- body: {
435
+ return targetFetch(
436
+ target.apiBaseUrl,
437
+ target.apiPrefix,
438
+ apiFetchOpts(target, "POST", projectPath(target, "/schema/adopt"), {
388
439
  ast,
389
440
  schema: opts?.schema ?? "public",
390
441
  yes: opts?.yes ?? false,
391
442
  no_cache: opts?.noCache ?? false,
392
443
  ...(opts?.names !== undefined ? { names: opts.names } : {}),
393
- },
394
- token: target.token!,
395
- orgId: target.orgId,
396
- environment: target.mode === "cloud" ? target.environment : undefined,
397
- })
444
+ }),
445
+ )
398
446
  }
399
447
 
400
448
  export async function targetStatus(target: DeployTarget): Promise<unknown> {
@@ -402,18 +450,15 @@ export async function targetStatus(target: DeployTarget): Promise<unknown> {
402
450
  return { mode: "direct", environment: target.environment }
403
451
  }
404
452
 
405
- return targetFetch(target.apiBaseUrl, target.apiPrefix, {
406
- method: "GET",
407
- path: projectPath(target, "/status"),
408
- token: target.token!,
409
- orgId: target.orgId,
410
- environment: target.mode === "cloud" ? target.environment : undefined,
411
- })
453
+ return targetFetch(
454
+ target.apiBaseUrl,
455
+ target.apiPrefix,
456
+ apiFetchOpts(target, "GET", projectPath(target, "/status")),
457
+ )
412
458
  }
413
459
 
414
460
  export function schemaPgSchema(cwd: string): string {
415
- const config = loadConfig(cwd)
416
- return config.schema?.pg_schema ?? "public"
461
+ return pgSchema(loadConfig(cwd))
417
462
  }
418
463
 
419
464
  export { schemaPathFromProject }
@@ -88,7 +88,7 @@ export interface ModelAstV2 {
88
88
  options: Record<string, unknown>
89
89
  annotations: {
90
90
  db: { tableName: string; indexes: unknown[] }
91
- platform: { access: Record<string, unknown> }
91
+ platform: { access: Record<string, unknown>; hooks?: Record<string, unknown> }
92
92
  }
93
93
  }
94
94
 
@@ -294,6 +294,7 @@ export function emitModel(
294
294
  tableName: string,
295
295
  access: Record<string, unknown>,
296
296
  indexes: unknown[] = [],
297
+ hooks: Record<string, unknown> = {},
297
298
  ): ModelAstV2 {
298
299
  return {
299
300
  name,
@@ -301,7 +302,9 @@ export function emitModel(
301
302
  options,
302
303
  annotations: {
303
304
  db: { tableName, indexes },
304
- platform: { access },
305
+ // Hooks sit in `platform` beside `access`: they are an API-layer concern, not a column one —
306
+ // supatype-server reads them, Postgres never sees them.
307
+ platform: { access, ...(Object.keys(hooks).length > 0 && { hooks }) },
305
308
  },
306
309
  }
307
310
  }
@@ -83,16 +83,26 @@ export async function withComposeSchemaPushLock<T>(
83
83
  composeProject: string,
84
84
  fn: () => Promise<T>,
85
85
  ): Promise<T> {
86
- const holdSql =
87
- `SELECT pg_advisory_lock(${SCHEMA_PUSH_LOCK_CLASSID}, ${SCHEMA_PUSH_LOCK_OBJID});` +
88
- ` SELECT pg_sleep(86400);`
89
- const args = dbExecArgs(paths, cwd, composeProject, ["-c", holdSql])
86
+ // The lock is held by a psql session reading from **stdin**, not by `-c … pg_sleep(86400)`.
87
+ //
88
+ // Killing the local `docker` client does not stop the process inside the container: `docker
89
+ // compose exec` does not forward the signal. With a sleeping session that meant every push
90
+ // left a backend holding this advisory lock for 24 hours — so `supatype push` never exited and
91
+ // the next push blocked on it. Observed end-to-end before this change.
92
+ //
93
+ // Closing stdin *does* propagate: psql sees EOF, exits, and the session ends, which is what
94
+ // releases an advisory lock. Deterministic, and it needs no signal to cross the boundary.
95
+ const args = dbExecArgs(paths, cwd, composeProject, ["-f", "-"])
90
96
 
91
97
  let stderrBuf = ""
92
98
  const holder: ChildProcess = spawn("docker", args, {
93
99
  cwd,
94
- stdio: ["ignore", "pipe", "pipe"],
100
+ stdio: ["pipe", "pipe", "pipe"],
95
101
  })
102
+ holder.stdin?.write(
103
+ `SELECT pg_advisory_lock(${SCHEMA_PUSH_LOCK_CLASSID}, ${SCHEMA_PUSH_LOCK_OBJID});\n`,
104
+ )
105
+ // Deliberately not ended — the open pipe is what keeps the session, and the lock, alive.
96
106
  holder.stderr?.on("data", (chunk: Buffer) => {
97
107
  stderrBuf += chunk.toString()
98
108
  })
@@ -114,18 +124,47 @@ export async function withComposeSchemaPushLock<T>(
114
124
  try {
115
125
  return await fn()
116
126
  } finally {
117
- if (holder.exitCode === null) {
118
- holder.kill("SIGTERM")
119
- await new Promise<void>((resolveDone) => {
120
- const t = setTimeout(() => {
121
- holder.kill("SIGKILL")
122
- resolveDone()
123
- }, 3000)
124
- holder.once("exit", () => {
125
- clearTimeout(t)
126
- resolveDone()
127
- })
127
+ await releaseHolder(holder, paths, cwd, composeProject)
128
+ }
129
+ }
130
+
131
+ /**
132
+ * End the holder session, then confirm the lock is actually gone.
133
+ *
134
+ * Closing stdin is the mechanism; the check is because a leaked holder is silent and expensive —
135
+ * it blocks every later push. If EOF did not do it, terminate the backend directly.
136
+ */
137
+ async function releaseHolder(
138
+ holder: ChildProcess,
139
+ paths: SelfHostComposePaths,
140
+ cwd: string,
141
+ composeProject: string,
142
+ ): Promise<void> {
143
+ if (holder.exitCode === null) {
144
+ holder.stdin?.end()
145
+ await new Promise<void>((resolveDone) => {
146
+ const t = setTimeout(() => {
147
+ holder.kill("SIGKILL")
148
+ resolveDone()
149
+ }, 5000)
150
+ holder.once("exit", () => {
151
+ clearTimeout(t)
152
+ resolveDone()
128
153
  })
129
- }
154
+ })
130
155
  }
156
+
157
+ if (!schemaPushLockIsHeld(paths, cwd, composeProject)) return
158
+
159
+ // Last resort: the session outlived its client. Ending it releases the lock.
160
+ spawnSync(
161
+ "docker",
162
+ dbExecArgs(paths, cwd, composeProject, [
163
+ "-tAc",
164
+ `SELECT pg_terminate_backend(pid) FROM pg_stat_activity
165
+ WHERE pid <> pg_backend_pid()
166
+ AND query LIKE '%pg_advisory_lock(${SCHEMA_PUSH_LOCK_CLASSID}, ${SCHEMA_PUSH_LOCK_OBJID})%'`,
167
+ ]),
168
+ { cwd, stdio: "ignore" },
169
+ )
131
170
  }