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

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 (350) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-test.log +208 -1
  3. package/.turbo/turbo-typecheck.log +1 -1
  4. package/dist/app/proxy-dev-app.d.ts +13 -0
  5. package/dist/app/proxy-dev-app.d.ts.map +1 -0
  6. package/dist/app/proxy-dev-app.js +53 -0
  7. package/dist/app/proxy-dev-app.js.map +1 -0
  8. package/dist/app-config.d.ts +7 -0
  9. package/dist/app-config.d.ts.map +1 -0
  10. package/dist/app-config.js +113 -0
  11. package/dist/app-config.js.map +1 -0
  12. package/dist/augmentation-generator.d.ts +2 -0
  13. package/dist/augmentation-generator.d.ts.map +1 -0
  14. package/dist/augmentation-generator.js +111 -0
  15. package/dist/augmentation-generator.js.map +1 -0
  16. package/dist/binary-cache.d.ts +94 -0
  17. package/dist/binary-cache.d.ts.map +1 -0
  18. package/dist/binary-cache.js +669 -0
  19. package/dist/binary-cache.js.map +1 -0
  20. package/dist/cli.d.ts.map +1 -1
  21. package/dist/cli.js +13 -7
  22. package/dist/cli.js.map +1 -1
  23. package/dist/commands/admin.d.ts.map +1 -1
  24. package/dist/commands/admin.js +4 -3
  25. package/dist/commands/admin.js.map +1 -1
  26. package/dist/commands/app.d.ts.map +1 -1
  27. package/dist/commands/app.js +56 -209
  28. package/dist/commands/app.js.map +1 -1
  29. package/dist/commands/cache.d.ts +6 -0
  30. package/dist/commands/cache.d.ts.map +1 -0
  31. package/dist/commands/cache.js +105 -0
  32. package/dist/commands/cache.js.map +1 -0
  33. package/dist/commands/cloud.d.ts +20 -0
  34. package/dist/commands/cloud.d.ts.map +1 -1
  35. package/dist/commands/cloud.js +50 -52
  36. package/dist/commands/cloud.js.map +1 -1
  37. package/dist/commands/db.d.ts.map +1 -1
  38. package/dist/commands/db.js +47 -54
  39. package/dist/commands/db.js.map +1 -1
  40. package/dist/commands/deploy.d.ts +2 -1
  41. package/dist/commands/deploy.d.ts.map +1 -1
  42. package/dist/commands/deploy.js +79 -52
  43. package/dist/commands/deploy.js.map +1 -1
  44. package/dist/commands/dev.d.ts +11 -0
  45. package/dist/commands/dev.d.ts.map +1 -1
  46. package/dist/commands/dev.js +759 -385
  47. package/dist/commands/dev.js.map +1 -1
  48. package/dist/commands/diff.d.ts.map +1 -1
  49. package/dist/commands/diff.js +30 -15
  50. package/dist/commands/diff.js.map +1 -1
  51. package/dist/commands/engine.d.ts +1 -3
  52. package/dist/commands/engine.d.ts.map +1 -1
  53. package/dist/commands/engine.js +13 -85
  54. package/dist/commands/engine.js.map +1 -1
  55. package/dist/commands/functions.d.ts.map +1 -1
  56. package/dist/commands/functions.js +92 -105
  57. package/dist/commands/functions.js.map +1 -1
  58. package/dist/commands/generate.d.ts.map +1 -1
  59. package/dist/commands/generate.js +22 -12
  60. package/dist/commands/generate.js.map +1 -1
  61. package/dist/commands/init.d.ts +1 -1
  62. package/dist/commands/init.d.ts.map +1 -1
  63. package/dist/commands/init.js +137 -410
  64. package/dist/commands/init.js.map +1 -1
  65. package/dist/commands/migrate-from-v1.d.ts +5 -0
  66. package/dist/commands/migrate-from-v1.d.ts.map +1 -0
  67. package/dist/commands/migrate-from-v1.js +125 -0
  68. package/dist/commands/migrate-from-v1.js.map +1 -0
  69. package/dist/commands/migrate.d.ts.map +1 -1
  70. package/dist/commands/migrate.js +27 -23
  71. package/dist/commands/migrate.js.map +1 -1
  72. package/dist/commands/pg.d.ts +8 -0
  73. package/dist/commands/pg.d.ts.map +1 -0
  74. package/dist/commands/pg.js +102 -0
  75. package/dist/commands/pg.js.map +1 -0
  76. package/dist/commands/pull.d.ts.map +1 -1
  77. package/dist/commands/pull.js +5 -66
  78. package/dist/commands/pull.js.map +1 -1
  79. package/dist/commands/push.d.ts.map +1 -1
  80. package/dist/commands/push.js +128 -38
  81. package/dist/commands/push.js.map +1 -1
  82. package/dist/commands/seed.d.ts +2 -0
  83. package/dist/commands/seed.d.ts.map +1 -1
  84. package/dist/commands/seed.js +44 -11
  85. package/dist/commands/seed.js.map +1 -1
  86. package/dist/commands/self-host.d.ts +7 -1
  87. package/dist/commands/self-host.d.ts.map +1 -1
  88. package/dist/commands/self-host.js +272 -758
  89. package/dist/commands/self-host.js.map +1 -1
  90. package/dist/commands/self-update.d.ts +9 -0
  91. package/dist/commands/self-update.d.ts.map +1 -0
  92. package/dist/commands/self-update.js +33 -0
  93. package/dist/commands/self-update.js.map +1 -0
  94. package/dist/commands/status.d.ts.map +1 -1
  95. package/dist/commands/status.js +4 -3
  96. package/dist/commands/status.js.map +1 -1
  97. package/dist/commands/types.d.ts +3 -0
  98. package/dist/commands/types.d.ts.map +1 -0
  99. package/dist/commands/types.js +62 -0
  100. package/dist/commands/types.js.map +1 -0
  101. package/dist/commands/update.d.ts +7 -0
  102. package/dist/commands/update.d.ts.map +1 -0
  103. package/dist/commands/update.js +93 -0
  104. package/dist/commands/update.js.map +1 -0
  105. package/dist/components.d.ts +5 -0
  106. package/dist/components.d.ts.map +1 -0
  107. package/dist/components.js +3 -0
  108. package/dist/components.js.map +1 -0
  109. package/dist/config.d.ts +10 -51
  110. package/dist/config.d.ts.map +1 -1
  111. package/dist/config.js +101 -33
  112. package/dist/config.js.map +1 -1
  113. package/dist/dev-compose.d.ts +17 -0
  114. package/dist/dev-compose.d.ts.map +1 -0
  115. package/dist/dev-compose.js +374 -0
  116. package/dist/dev-compose.js.map +1 -0
  117. package/dist/diff-output.d.ts +4 -0
  118. package/dist/diff-output.d.ts.map +1 -0
  119. package/dist/diff-output.js +12 -0
  120. package/dist/diff-output.js.map +1 -0
  121. package/dist/docker-postgres.d.ts +57 -0
  122. package/dist/docker-postgres.d.ts.map +1 -0
  123. package/dist/docker-postgres.js +208 -0
  124. package/dist/docker-postgres.js.map +1 -0
  125. package/dist/engine-client.d.ts +69 -0
  126. package/dist/engine-client.d.ts.map +1 -0
  127. package/dist/engine-client.js +157 -0
  128. package/dist/engine-client.js.map +1 -0
  129. package/dist/ensure-binary.d.ts +7 -0
  130. package/dist/ensure-binary.d.ts.map +1 -0
  131. package/dist/ensure-binary.js +17 -0
  132. package/dist/ensure-binary.js.map +1 -0
  133. package/dist/functions-router-gen.d.ts +14 -0
  134. package/dist/functions-router-gen.d.ts.map +1 -0
  135. package/dist/functions-router-gen.js +199 -0
  136. package/dist/functions-router-gen.js.map +1 -0
  137. package/dist/index.d.ts +4 -5
  138. package/dist/index.d.ts.map +1 -1
  139. package/dist/index.js +2 -3
  140. package/dist/index.js.map +1 -1
  141. package/dist/kong-config.d.ts +25 -0
  142. package/dist/kong-config.d.ts.map +1 -0
  143. package/dist/kong-config.js +71 -0
  144. package/dist/kong-config.js.map +1 -0
  145. package/dist/local-gateway.d.ts +7 -0
  146. package/dist/local-gateway.d.ts.map +1 -0
  147. package/dist/local-gateway.js +9 -0
  148. package/dist/local-gateway.js.map +1 -0
  149. package/dist/local-storage.d.ts +8 -0
  150. package/dist/local-storage.d.ts.map +1 -0
  151. package/dist/local-storage.js +14 -0
  152. package/dist/local-storage.js.map +1 -0
  153. package/dist/pgbouncer-userlist.d.ts +5 -0
  154. package/dist/pgbouncer-userlist.d.ts.map +1 -0
  155. package/dist/pgbouncer-userlist.js +14 -0
  156. package/dist/pgbouncer-userlist.js.map +1 -0
  157. package/dist/postgres-ctl.d.ts +44 -0
  158. package/dist/postgres-ctl.d.ts.map +1 -0
  159. package/dist/postgres-ctl.js +137 -0
  160. package/dist/postgres-ctl.js.map +1 -0
  161. package/dist/process-manager.d.ts +43 -0
  162. package/dist/process-manager.d.ts.map +1 -0
  163. package/dist/process-manager.js +135 -0
  164. package/dist/process-manager.js.map +1 -0
  165. package/dist/project-config.d.ts +235 -0
  166. package/dist/project-config.d.ts.map +1 -0
  167. package/dist/project-config.js +160 -0
  168. package/dist/project-config.js.map +1 -0
  169. package/dist/pull-utils.d.ts +15 -0
  170. package/dist/pull-utils.d.ts.map +1 -1
  171. package/dist/pull-utils.js +12 -0
  172. package/dist/pull-utils.js.map +1 -1
  173. package/dist/release-pins.d.ts +7 -0
  174. package/dist/release-pins.d.ts.map +1 -0
  175. package/dist/release-pins.js +27 -0
  176. package/dist/release-pins.js.map +1 -0
  177. package/dist/release-public-key.d.ts +8 -0
  178. package/dist/release-public-key.d.ts.map +1 -0
  179. package/dist/release-public-key.js +13 -0
  180. package/dist/release-public-key.js.map +1 -0
  181. package/dist/runtime-routes.d.ts +34 -0
  182. package/dist/runtime-routes.d.ts.map +1 -0
  183. package/dist/runtime-routes.js +252 -0
  184. package/dist/runtime-routes.js.map +1 -0
  185. package/dist/schema-ast-v2.d.ts +127 -0
  186. package/dist/schema-ast-v2.d.ts.map +1 -0
  187. package/dist/schema-ast-v2.js +226 -0
  188. package/dist/schema-ast-v2.js.map +1 -0
  189. package/dist/scripts/postinstall.d.ts +5 -6
  190. package/dist/scripts/postinstall.d.ts.map +1 -1
  191. package/dist/scripts/postinstall.js +36 -20
  192. package/dist/scripts/postinstall.js.map +1 -1
  193. package/dist/self-host-compose.d.ts +22 -0
  194. package/dist/self-host-compose.d.ts.map +1 -0
  195. package/dist/self-host-compose.js +347 -0
  196. package/dist/self-host-compose.js.map +1 -0
  197. package/dist/storage-provision.d.ts +24 -0
  198. package/dist/storage-provision.d.ts.map +1 -0
  199. package/dist/storage-provision.js +44 -0
  200. package/dist/storage-provision.js.map +1 -0
  201. package/dist/studio-admin-roles.d.ts +7 -0
  202. package/dist/studio-admin-roles.d.ts.map +1 -0
  203. package/dist/studio-admin-roles.js +14 -0
  204. package/dist/studio-admin-roles.js.map +1 -0
  205. package/dist/studio-dev-server.d.ts +22 -0
  206. package/dist/studio-dev-server.d.ts.map +1 -0
  207. package/dist/studio-dev-server.js +28 -0
  208. package/dist/studio-dev-server.js.map +1 -0
  209. package/dist/systemd.d.ts +26 -0
  210. package/dist/systemd.d.ts.map +1 -0
  211. package/dist/systemd.js +102 -0
  212. package/dist/systemd.js.map +1 -0
  213. package/dist/tsx-runner.d.ts.map +1 -1
  214. package/dist/tsx-runner.js +9 -2
  215. package/dist/tsx-runner.js.map +1 -1
  216. package/dist/type-extractor.d.ts +4 -0
  217. package/dist/type-extractor.d.ts.map +1 -0
  218. package/dist/type-extractor.js +1213 -0
  219. package/dist/type-extractor.js.map +1 -0
  220. package/dist/type-resolver.d.ts +33 -0
  221. package/dist/type-resolver.d.ts.map +1 -0
  222. package/dist/type-resolver.js +338 -0
  223. package/dist/type-resolver.js.map +1 -0
  224. package/package.json +4 -3
  225. package/releases/deno/VERSION +1 -0
  226. package/scripts/mirror-deno-release.sh +76 -0
  227. package/src/TYPE-RESOLUTION.md +294 -0
  228. package/src/app/proxy-dev-app.ts +67 -0
  229. package/src/app-config.ts +128 -0
  230. package/src/augmentation-generator.ts +126 -0
  231. package/src/binary-cache.ts +822 -0
  232. package/src/cli.ts +13 -8
  233. package/src/commands/admin.ts +4 -3
  234. package/src/commands/app.ts +67 -231
  235. package/src/commands/cache.ts +117 -0
  236. package/src/commands/cloud.ts +63 -64
  237. package/src/commands/db.ts +54 -63
  238. package/src/commands/deploy.ts +96 -62
  239. package/src/commands/dev.ts +933 -405
  240. package/src/commands/diff.ts +31 -29
  241. package/src/commands/engine.ts +13 -116
  242. package/src/commands/functions.ts +97 -115
  243. package/src/commands/generate.ts +23 -10
  244. package/src/commands/init.ts +149 -414
  245. package/src/commands/migrate-from-v1.ts +131 -0
  246. package/src/commands/migrate.ts +27 -23
  247. package/src/commands/pg.ts +133 -0
  248. package/src/commands/pull.ts +6 -85
  249. package/src/commands/push.ts +161 -56
  250. package/src/commands/seed.ts +54 -12
  251. package/src/commands/self-host.ts +312 -880
  252. package/src/commands/self-update.ts +45 -0
  253. package/src/commands/status.ts +4 -3
  254. package/src/commands/types.ts +76 -0
  255. package/src/commands/update.ts +109 -0
  256. package/src/components.ts +6 -0
  257. package/src/config.ts +127 -94
  258. package/src/dev-compose.ts +455 -0
  259. package/src/diff-output.ts +12 -0
  260. package/src/docker-postgres.ts +295 -0
  261. package/src/engine-client.ts +236 -0
  262. package/src/ensure-binary.ts +28 -0
  263. package/src/functions-router-gen.ts +224 -0
  264. package/src/index.ts +4 -12
  265. package/src/kong-config.ts +93 -0
  266. package/src/local-gateway.ts +9 -0
  267. package/src/local-storage.ts +14 -0
  268. package/src/pgbouncer-userlist.ts +15 -0
  269. package/src/postgres-ctl.ts +171 -0
  270. package/src/process-manager.ts +168 -0
  271. package/src/project-config.ts +386 -0
  272. package/src/pull-utils.ts +24 -0
  273. package/src/release-pins.ts +31 -0
  274. package/src/release-public-key.ts +12 -0
  275. package/src/runtime-routes.ts +291 -0
  276. package/src/schema-ast-v2.ts +324 -0
  277. package/src/scripts/postinstall.ts +36 -25
  278. package/src/self-host-compose.ts +389 -0
  279. package/src/storage-provision.ts +58 -0
  280. package/src/studio-admin-roles.ts +16 -0
  281. package/src/studio-dev-server.ts +53 -0
  282. package/src/systemd.ts +137 -0
  283. package/src/tsx-runner.ts +11 -1
  284. package/src/type-extractor.ts +1479 -0
  285. package/src/type-resolver.ts +457 -0
  286. package/tests/app-command.test.ts +54 -0
  287. package/tests/augmentation-generator.test.ts +59 -0
  288. package/tests/binary-cache-cloud-overrides.test.ts +123 -0
  289. package/tests/cached-artifact-format.test.ts +84 -0
  290. package/tests/cli-help.test.ts +40 -14
  291. package/tests/config.test.ts +171 -37
  292. package/tests/docker-postgres.test.ts +39 -0
  293. package/tests/engine-distribution.test.ts +3 -3
  294. package/tests/ensure-binary.test.ts +59 -0
  295. package/tests/init.test.ts +28 -86
  296. package/tests/migrate-from-v1.test.ts +29 -0
  297. package/tests/normalize-admin-config.test.ts +48 -0
  298. package/tests/pg-spawn-env.test.ts +18 -0
  299. package/tests/postgres-archive-tag.test.ts +9 -0
  300. package/tests/proxy-dev-app.test.ts +33 -0
  301. package/tests/pull-utils.test.ts +36 -1
  302. package/tests/release-pins.test.ts +28 -0
  303. package/tests/runtime-contract.test.ts +351 -0
  304. package/tests/seed-discover.test.ts +31 -0
  305. package/tests/studio-admin-roles.test.ts +27 -0
  306. package/tests/tsconfig.json +9 -0
  307. package/tests/type-extractor.test.ts +985 -0
  308. package/tests/type-resolver.test.ts +59 -0
  309. package/tsconfig.tsbuildinfo +1 -1
  310. package/vitest.config.ts +12 -0
  311. package/dist/engine/cache.d.ts +0 -37
  312. package/dist/engine/cache.d.ts.map +0 -1
  313. package/dist/engine/cache.js +0 -121
  314. package/dist/engine/cache.js.map +0 -1
  315. package/dist/engine/download.d.ts +0 -19
  316. package/dist/engine/download.d.ts.map +0 -1
  317. package/dist/engine/download.js +0 -108
  318. package/dist/engine/download.js.map +0 -1
  319. package/dist/engine/platform.d.ts +0 -24
  320. package/dist/engine/platform.d.ts.map +0 -1
  321. package/dist/engine/platform.js +0 -50
  322. package/dist/engine/platform.js.map +0 -1
  323. package/dist/engine/resolve.d.ts +0 -37
  324. package/dist/engine/resolve.d.ts.map +0 -1
  325. package/dist/engine/resolve.js +0 -133
  326. package/dist/engine/resolve.js.map +0 -1
  327. package/dist/engine/update-notify.d.ts +0 -11
  328. package/dist/engine/update-notify.d.ts.map +0 -1
  329. package/dist/engine/update-notify.js +0 -43
  330. package/dist/engine/update-notify.js.map +0 -1
  331. package/dist/engine/verify.d.ts +0 -50
  332. package/dist/engine/verify.d.ts.map +0 -1
  333. package/dist/engine/verify.js +0 -161
  334. package/dist/engine/verify.js.map +0 -1
  335. package/dist/engine-version.d.ts +0 -35
  336. package/dist/engine-version.d.ts.map +0 -1
  337. package/dist/engine-version.js +0 -35
  338. package/dist/engine-version.js.map +0 -1
  339. package/dist/engine.d.ts +0 -34
  340. package/dist/engine.d.ts.map +0 -1
  341. package/dist/engine.js +0 -76
  342. package/dist/engine.js.map +0 -1
  343. package/src/engine/cache.ts +0 -135
  344. package/src/engine/download.ts +0 -143
  345. package/src/engine/platform.ts +0 -66
  346. package/src/engine/resolve.ts +0 -197
  347. package/src/engine/update-notify.ts +0 -50
  348. package/src/engine/verify.ts +0 -206
  349. package/src/engine-version.ts +0 -39
  350. package/src/engine.ts +0 -99
@@ -0,0 +1,1213 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, isAbsolute, resolve } from "node:path";
3
+ import ts from "typescript";
4
+ import { applyImportRename, createResolveContext, needsChecker, resolveTypeNode, tryResolveTypeReference, unknownTypeError, } from "./type-resolver.js";
5
+ import { emitField, emitModel, emitSchema, defaultPgTypeForKind, scalar, } from "./schema-ast-v2.js";
6
+ export function extractSchemaAstFromTypes(schemaPath, cwd = process.cwd()) {
7
+ const absPath = resolve(cwd, schemaPath);
8
+ if (!existsSync(absPath)) {
9
+ throw new Error(`Schema file not found: ${absPath}`);
10
+ }
11
+ const sourceFiles = loadSchemaSourceFiles(absPath);
12
+ const resolveCtx = createResolveContext(sourceFiles);
13
+ const bucketAliases = new Map();
14
+ const bucketsById = new Map();
15
+ for (const sourceFile of sourceFiles) {
16
+ const bucketContext = collectBucketContext(sourceFile);
17
+ for (const [alias, bucketId] of bucketContext.aliases) {
18
+ bucketAliases.set(alias, bucketId);
19
+ }
20
+ for (const [bucketId, bucket] of bucketContext.bucketsById) {
21
+ const existing = bucketsById.get(bucketId);
22
+ if (existing !== undefined && !bucketsEqual(existing, bucket)) {
23
+ throw new Error(`Conflicting Bucket<> declarations for id "${bucketId}". Use a single export per bucket id.`);
24
+ }
25
+ bucketsById.set(bucketId, bucket);
26
+ }
27
+ }
28
+ const blockAliases = new Map();
29
+ for (const sourceFile of sourceFiles) {
30
+ const next = collectBlockAliases(sourceFile, bucketAliases, bucketsById, resolveCtx);
31
+ for (const [name, block] of next) {
32
+ blockAliases.set(name, block);
33
+ }
34
+ }
35
+ const models = [];
36
+ for (const sourceFile of sourceFiles) {
37
+ for (const stmt of sourceFile.statements) {
38
+ if (!ts.isTypeAliasDeclaration(stmt))
39
+ continue;
40
+ if (!hasExportModifier(stmt))
41
+ continue;
42
+ if (!ts.isTypeReferenceNode(stmt.type))
43
+ continue;
44
+ const modelTypeName = stmt.type.typeName.getText(sourceFile);
45
+ if (modelTypeName !== "Model" && modelTypeName !== "LocalizedModel")
46
+ continue;
47
+ const [fieldsArg, metaArg] = stmt.type.typeArguments ?? [];
48
+ if (!fieldsArg)
49
+ continue;
50
+ const fieldsLiteral = unwrapModelFields(fieldsArg, sourceFile, resolveCtx);
51
+ if (!fieldsLiteral)
52
+ continue;
53
+ const metaHints = parseMetaLiteral(metaArg, sourceFile);
54
+ const fieldContext = {
55
+ autoLocalize: modelTypeName === "LocalizedModel" || metaHints.autoLocalize === true,
56
+ };
57
+ const fields = {};
58
+ for (const member of fieldsLiteral.members) {
59
+ if (!ts.isPropertySignature(member) || !member.type)
60
+ continue;
61
+ const name = getPropertyName(member.name);
62
+ if (!name)
63
+ continue;
64
+ fields[name] = parseFieldType(name, member.type, sourceFile, blockAliases, bucketAliases, bucketsById, fieldContext, resolveCtx);
65
+ }
66
+ const { tableName, access, options } = parseModelMeta(metaArg, sourceFile, stmt.name.text, fieldsArg, fields);
67
+ models.push(emitModel(stmt.name.text, fields, options, tableName, access));
68
+ }
69
+ }
70
+ if (models.length === 0)
71
+ return null;
72
+ const storageBuckets = bucketsById.size > 0 ? [...bucketsById.values()].sort((a, b) => a.id.localeCompare(b.id)) : undefined;
73
+ let localeConfig;
74
+ for (const sourceFile of sourceFiles) {
75
+ const found = collectLocaleConfig(sourceFile);
76
+ if (!found)
77
+ continue;
78
+ if (localeConfig !== undefined) {
79
+ throw new Error("Conflicting LocaleConfig declarations. Export at most one `localeConfig` type alias.");
80
+ }
81
+ localeConfig = found;
82
+ }
83
+ return emitSchema(models, {
84
+ ...(storageBuckets !== undefined && storageBuckets.length > 0 && { storageBuckets }),
85
+ ...(localeConfig !== undefined && {
86
+ locales: localeConfig.locales,
87
+ defaultLocale: localeConfig.defaultLocale,
88
+ }),
89
+ });
90
+ }
91
+ function loadSchemaSourceFiles(entryPath) {
92
+ const visited = new Set();
93
+ const sourceFiles = [];
94
+ const queue = [entryPath];
95
+ while (queue.length > 0) {
96
+ const currentPath = queue.shift();
97
+ if (!currentPath)
98
+ continue;
99
+ if (visited.has(currentPath))
100
+ continue;
101
+ visited.add(currentPath);
102
+ if (!existsSync(currentPath))
103
+ continue;
104
+ const sourceText = readFileSync(currentPath, "utf8");
105
+ const sourceFile = ts.createSourceFile(currentPath, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
106
+ sourceFiles.push(sourceFile);
107
+ const baseDir = dirname(currentPath);
108
+ for (const stmt of sourceFile.statements) {
109
+ let specifier;
110
+ if (ts.isExportDeclaration(stmt)) {
111
+ if (!stmt.moduleSpecifier || !ts.isStringLiteral(stmt.moduleSpecifier))
112
+ continue;
113
+ specifier = stmt.moduleSpecifier.text;
114
+ }
115
+ else if (ts.isImportDeclaration(stmt)) {
116
+ if (!stmt.moduleSpecifier || !ts.isStringLiteral(stmt.moduleSpecifier))
117
+ continue;
118
+ specifier = stmt.moduleSpecifier.text;
119
+ }
120
+ else {
121
+ continue;
122
+ }
123
+ if (!specifier.startsWith("."))
124
+ continue;
125
+ const nextPath = resolveTypeModulePath(baseDir, specifier);
126
+ if (!nextPath)
127
+ continue;
128
+ if (!visited.has(nextPath))
129
+ queue.push(nextPath);
130
+ }
131
+ }
132
+ return sourceFiles;
133
+ }
134
+ function resolveTypeModulePath(fromDir, specifier) {
135
+ const basePath = isAbsolute(specifier) ? specifier : resolve(fromDir, specifier);
136
+ const candidates = specifier.endsWith(".js")
137
+ ? [
138
+ basePath,
139
+ basePath.replace(/\.js$/i, ".ts"),
140
+ basePath.replace(/\.js$/i, ".tsx"),
141
+ basePath.replace(/\.js$/i, ".d.ts"),
142
+ ]
143
+ : [
144
+ basePath,
145
+ `${basePath}.ts`,
146
+ `${basePath}.tsx`,
147
+ `${basePath}.d.ts`,
148
+ resolve(basePath, "index.ts"),
149
+ resolve(basePath, "index.tsx"),
150
+ resolve(basePath, "index.d.ts"),
151
+ ];
152
+ for (const candidate of candidates) {
153
+ if (existsSync(candidate))
154
+ return candidate;
155
+ }
156
+ return null;
157
+ }
158
+ function hasExportModifier(node) {
159
+ if (!ts.canHaveModifiers(node))
160
+ return false;
161
+ return (ts.getModifiers(node)?.some((mod) => mod.kind === ts.SyntaxKind.ExportKeyword) ?? false);
162
+ }
163
+ function getPropertyName(name) {
164
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name))
165
+ return name.text;
166
+ return null;
167
+ }
168
+ function unwrapModelFields(typeNode, sourceFile, resolveCtx, depth = 0) {
169
+ if (depth > 16)
170
+ return null;
171
+ if (ts.isTypeLiteralNode(typeNode))
172
+ return typeNode;
173
+ if (needsChecker(typeNode)) {
174
+ const resolved = resolveTypeNode(typeNode, sourceFile, resolveCtx);
175
+ if (ts.isTypeLiteralNode(resolved))
176
+ return resolved;
177
+ return unwrapModelFields(resolved, sourceFile, resolveCtx, depth + 1);
178
+ }
179
+ if (!ts.isTypeReferenceNode(typeNode) || !ts.isIdentifier(typeNode.typeName))
180
+ return null;
181
+ const typeName = applyImportRename(typeNode.typeName.text, sourceFile, resolveCtx.renameMap);
182
+ // Composite helpers in @supatype/types wrap the concrete field object.
183
+ if (typeName === "WithTimestamps" ||
184
+ typeName === "WithSoftDelete" ||
185
+ typeName === "WithPublishable") {
186
+ const inner = typeNode.typeArguments?.[0];
187
+ if (!inner)
188
+ return null;
189
+ return unwrapModelFields(inner, sourceFile, resolveCtx, depth + 1);
190
+ }
191
+ const expanded = tryResolveTypeReference(typeNode, sourceFile, resolveCtx);
192
+ if (expanded) {
193
+ if (ts.isTypeLiteralNode(expanded))
194
+ return expanded;
195
+ return unwrapModelFields(expanded, sourceFile, resolveCtx, depth + 1);
196
+ }
197
+ return null;
198
+ }
199
+ /** Parse `Default<T, V>` second type argument into a JSON-serializable literal. */
200
+ function parseDefaultLiteral(node, sourceFile) {
201
+ if (ts.isLiteralTypeNode(node)) {
202
+ const lit = node.literal;
203
+ if (ts.isStringLiteral(lit) || ts.isNoSubstitutionTemplateLiteral(lit))
204
+ return lit.text;
205
+ if (ts.isNumericLiteral(lit))
206
+ return Number(lit.text);
207
+ if (lit.kind === ts.SyntaxKind.TrueKeyword)
208
+ return true;
209
+ if (lit.kind === ts.SyntaxKind.FalseKeyword)
210
+ return false;
211
+ if (lit.kind === ts.SyntaxKind.NullKeyword)
212
+ return null;
213
+ }
214
+ if (node.kind === ts.SyntaxKind.TrueKeyword)
215
+ return true;
216
+ if (node.kind === ts.SyntaxKind.FalseKeyword)
217
+ return false;
218
+ if (node.kind === ts.SyntaxKind.NullKeyword)
219
+ return null;
220
+ // Negative numeric literals appear as PrefixUnaryExpression in some TS versions.
221
+ if (ts.isPrefixUnaryExpression(node) && node.operator === ts.SyntaxKind.MinusToken) {
222
+ const inner = parseDefaultLiteral(node.operand, sourceFile);
223
+ if (typeof inner === "number")
224
+ return -inner;
225
+ }
226
+ return undefined;
227
+ }
228
+ function parseFieldType(fieldName, typeNode, sourceFile, blockAliases, bucketAliases, bucketsById, context = {}, resolveCtx) {
229
+ const flags = {
230
+ required: true,
231
+ unique: false,
232
+ index: false,
233
+ primaryKey: false,
234
+ serverGenerated: false,
235
+ autoIncrement: false,
236
+ relationCardinality: undefined,
237
+ relationTarget: undefined,
238
+ editorReadOnly: false,
239
+ computedFromSources: undefined,
240
+ computedFromTemplate: undefined,
241
+ fieldDefault: undefined,
242
+ localized: false,
243
+ notLocalized: false,
244
+ };
245
+ const resolving = new Set();
246
+ let current = typeNode;
247
+ while (ts.isTypeReferenceNode(current) && ts.isIdentifier(current.typeName)) {
248
+ const typeName = applyImportRename(current.typeName.text, sourceFile, resolveCtx.renameMap);
249
+ switch (typeName) {
250
+ case "Optional":
251
+ flags.required = false;
252
+ current = current.typeArguments?.[0] ?? current;
253
+ continue;
254
+ case "Unique":
255
+ flags.unique = true;
256
+ current = current.typeArguments?.[0] ?? current;
257
+ continue;
258
+ case "Indexed":
259
+ flags.index = true;
260
+ current = current.typeArguments?.[0] ?? current;
261
+ continue;
262
+ case "ServerDefault":
263
+ flags.serverGenerated = true;
264
+ current = current.typeArguments?.[0] ?? current;
265
+ continue;
266
+ case "AutoIncrement":
267
+ flags.serverGenerated = true;
268
+ flags.autoIncrement = true;
269
+ current = current.typeArguments?.[0] ?? current;
270
+ continue;
271
+ case "PrimaryKey":
272
+ flags.primaryKey = true;
273
+ flags.required = true;
274
+ flags.unique = true;
275
+ current = current.typeArguments?.[0] ?? current;
276
+ continue;
277
+ case "Default": {
278
+ const valueArg = current.typeArguments?.[1];
279
+ if (valueArg !== undefined) {
280
+ const literal = parseDefaultLiteral(valueArg, sourceFile);
281
+ if (literal !== undefined) {
282
+ flags.fieldDefault = literal;
283
+ }
284
+ }
285
+ // Unwrap to T so `Default<boolean, true>` resolves as boolean, not text.
286
+ current = current.typeArguments?.[0] ?? current;
287
+ continue;
288
+ }
289
+ case "Searchable":
290
+ current = current.typeArguments?.[0] ?? current;
291
+ continue;
292
+ case "EditorReadOnly":
293
+ flags.editorReadOnly = true;
294
+ current = current.typeArguments?.[0] ?? current;
295
+ continue;
296
+ case "Computed":
297
+ flags.editorReadOnly = true;
298
+ flags.serverGenerated = true;
299
+ current = current.typeArguments?.[0] ?? current;
300
+ continue;
301
+ case "ComputedFrom": {
302
+ const valueArg = current.typeArguments?.[0];
303
+ const sourcesArg = current.typeArguments?.[1];
304
+ const parsed = parseComputedFromSecondArg(sourcesArg, sourceFile);
305
+ if (parsed) {
306
+ flags.computedFromSources = parsed.sources;
307
+ flags.computedFromTemplate = parsed.template;
308
+ }
309
+ else {
310
+ flags.computedFromSources = ["title"];
311
+ }
312
+ current = valueArg ?? current;
313
+ continue;
314
+ }
315
+ case "MaxLength":
316
+ case "MinLength":
317
+ case "Between":
318
+ current = current.typeArguments?.[0] ?? current;
319
+ continue;
320
+ case "Localized":
321
+ flags.localized = true;
322
+ current = current.typeArguments?.[0] ?? current;
323
+ continue;
324
+ case "NotLocalized":
325
+ flags.notLocalized = true;
326
+ current = current.typeArguments?.[0] ?? current;
327
+ continue;
328
+ case "RelatedTo":
329
+ flags.relationCardinality = "one";
330
+ flags.relationTarget = relationTargetFromTypeArg(current.typeArguments?.[0], sourceFile);
331
+ // `target` must match `ModelAst.name` to satisfy validator resolution.
332
+ // FK column follows the field name (two relations to the same model need distinct columns).
333
+ return emitField({
334
+ kind: "relation",
335
+ kernel: { cardinality: "belongsTo", target: flags.relationTarget },
336
+ db: { foreignKey: relationForeignKeyFromField(fieldName) },
337
+ platform: flags.editorReadOnly ? { readOnly: true } : {},
338
+ });
339
+ case "HasOne":
340
+ flags.relationCardinality = "one";
341
+ flags.relationTarget = current.typeArguments?.[0]?.getText(sourceFile).replace(/\W/g, "") ?? "unknown";
342
+ return emitField({
343
+ kind: "relation",
344
+ kernel: { cardinality: "hasOne", target: flags.relationTarget },
345
+ db: {},
346
+ platform: flags.editorReadOnly ? { readOnly: true } : {},
347
+ });
348
+ case "HasMany":
349
+ case "ManyToMany":
350
+ flags.relationCardinality = "many";
351
+ flags.relationTarget = current.typeArguments?.[0]?.getText(sourceFile).replace(/\W/g, "") ?? "unknown";
352
+ return emitField({
353
+ kind: "relation",
354
+ kernel: { cardinality: "hasMany", target: flags.relationTarget },
355
+ db: {},
356
+ platform: flags.editorReadOnly ? { readOnly: true } : {},
357
+ });
358
+ default: {
359
+ const resolved = tryResolveTypeReference(current, sourceFile, resolveCtx, { fieldName, resolving });
360
+ if (resolved) {
361
+ current = resolved;
362
+ continue;
363
+ }
364
+ break;
365
+ }
366
+ }
367
+ break;
368
+ }
369
+ const scalarBase = parseScalarType(current, sourceFile, blockAliases, bucketAliases, bucketsById, context, resolveCtx, fieldName, resolving);
370
+ let parsed = {
371
+ kind: scalarBase.kind,
372
+ kernel: {
373
+ ...scalarBase.kernel,
374
+ required: flags.required,
375
+ ...(flags.primaryKey && { primaryKey: true }),
376
+ },
377
+ db: {
378
+ ...scalarBase.db,
379
+ unique: flags.unique,
380
+ index: flags.index,
381
+ },
382
+ platform: {
383
+ ...scalarBase.platform,
384
+ ...(flags.editorReadOnly && { readOnly: true }),
385
+ },
386
+ };
387
+ if (flags.autoIncrement && parsed.kind === "integer") {
388
+ parsed = { ...parsed, kind: "serial", db: { ...parsed.db, pgType: "SERIAL" } };
389
+ }
390
+ if (fieldName === "id" && parsed.kind === "uuid" && flags.primaryKey === false) {
391
+ parsed = {
392
+ ...parsed,
393
+ kernel: { ...parsed.kernel, primaryKey: true, required: true },
394
+ db: { ...parsed.db, unique: true },
395
+ };
396
+ }
397
+ if (flags.fieldDefault !== undefined) {
398
+ if (parsed.kernel.default !== undefined) {
399
+ throw new Error(`Field "${fieldName}": use either Default<…> or an inline type default (e.g. RichText<"…">), not both.`);
400
+ }
401
+ parsed = {
402
+ ...parsed,
403
+ kernel: { ...parsed.kernel, default: { kind: "value", value: flags.fieldDefault } },
404
+ };
405
+ }
406
+ if (parsed.kernel.primaryKey === true && parsed.kind === "uuid" && parsed.kernel.default === undefined) {
407
+ parsed = {
408
+ ...parsed,
409
+ kernel: { ...parsed.kernel, default: { kind: "genRandomUuid" } },
410
+ };
411
+ }
412
+ else if (parsed.kernel.primaryKey === true &&
413
+ (parsed.kind === "serial" || parsed.kind === "bigSerial")) {
414
+ flags.serverGenerated = true;
415
+ }
416
+ if (flags.serverGenerated === true) {
417
+ parsed = { ...parsed, db: { ...parsed.db, serverGenerated: true } };
418
+ }
419
+ const auditTs = fieldName === "created_at" ||
420
+ fieldName === "updated_at" ||
421
+ fieldName === "createdAt" ||
422
+ fieldName === "updatedAt";
423
+ if (auditTs) {
424
+ parsed = { ...parsed, db: { ...parsed.db, serverGenerated: true } };
425
+ if ((parsed.kind === "datetime" || parsed.kind === "date") &&
426
+ parsed.kernel.default === undefined) {
427
+ parsed = { ...parsed, kernel: { ...parsed.kernel, default: { kind: "now" } } };
428
+ }
429
+ }
430
+ if (flags.serverGenerated &&
431
+ (parsed.kind === "datetime" || parsed.kind === "date") &&
432
+ parsed.kernel.default === undefined) {
433
+ parsed = { ...parsed, kernel: { ...parsed.kernel, default: { kind: "now" } } };
434
+ }
435
+ const hasCfTemplate = flags.computedFromTemplate !== undefined;
436
+ const hasCfSources = Boolean(flags.computedFromSources && flags.computedFromSources.length > 0);
437
+ if (parsed.kind === "text" && (hasCfTemplate || hasCfSources)) {
438
+ const kernel = { ...parsed.kernel };
439
+ if (hasCfSources && flags.computedFromSources) {
440
+ kernel.sources = flags.computedFromSources;
441
+ }
442
+ if (hasCfTemplate && flags.computedFromTemplate !== undefined) {
443
+ kernel.template = flags.computedFromTemplate;
444
+ }
445
+ parsed = { ...parsed, kernel };
446
+ }
447
+ return emitField(finalizeParsedField(parsed, flags, context));
448
+ }
449
+ function finalizeParsedField(parsed, flags, context) {
450
+ let localized = flags.localized;
451
+ if (!localized &&
452
+ !flags.notLocalized &&
453
+ context.autoLocalize &&
454
+ shouldAutoLocalizeFieldKind(parsed.kind)) {
455
+ localized = true;
456
+ }
457
+ if (parsed.kind === "blocks" && parsed.kernel.blocks && context.autoLocalize && !localized) {
458
+ return {
459
+ ...parsed,
460
+ kernel: {
461
+ ...parsed.kernel,
462
+ blocks: parsed.kernel.blocks.map((blockDef) => ({
463
+ ...blockDef,
464
+ fields: Object.fromEntries(Object.entries(blockDef.fields).map(([name, fieldWire]) => [
465
+ name,
466
+ localizeFieldWire(fieldWire),
467
+ ])),
468
+ })),
469
+ },
470
+ };
471
+ }
472
+ if (localized) {
473
+ return {
474
+ ...parsed,
475
+ kernel: { ...parsed.kernel, localized: true },
476
+ db: { ...parsed.db, pgType: "JSONB" },
477
+ };
478
+ }
479
+ return parsed;
480
+ }
481
+ function shouldAutoLocalizeFieldKind(kind) {
482
+ return kind === "text" || kind === "richText";
483
+ }
484
+ function localizeFieldWire(field) {
485
+ if (field.localized === true)
486
+ return field;
487
+ if (!shouldAutoLocalizeFieldKind(field.kind))
488
+ return field;
489
+ const annotations = (field.annotations ?? {});
490
+ return {
491
+ ...field,
492
+ localized: true,
493
+ annotations: {
494
+ ...annotations,
495
+ db: { ...annotations.db, pgType: "JSONB" },
496
+ },
497
+ };
498
+ }
499
+ function parseScalarType(typeNode, sourceFile, blockAliases, bucketAliases, bucketsById, context = {}, resolveCtx, fieldName = "?", resolving = new Set()) {
500
+ if (ts.isArrayTypeNode(typeNode)) {
501
+ const element = parseScalarType(typeNode.elementType, sourceFile, blockAliases, bucketAliases, bucketsById, context, resolveCtx, fieldName, resolving);
502
+ return scalar("array", {
503
+ db: { elementType: defaultPgTypeForKind(element.kind) },
504
+ });
505
+ }
506
+ if (ts.isUnionTypeNode(typeNode)) {
507
+ const literals = typeNode.types.filter(ts.isLiteralTypeNode);
508
+ if (literals.length === typeNode.types.length && literals.every((lit) => ts.isStringLiteral(lit.literal))) {
509
+ return scalar("enum", {
510
+ kernel: {
511
+ values: literals.map((lit) => lit.literal.text),
512
+ },
513
+ });
514
+ }
515
+ const nonNull = typeNode.types.find((t) => t.kind !== ts.SyntaxKind.NullKeyword);
516
+ if (nonNull) {
517
+ return parseScalarType(nonNull, sourceFile, blockAliases, bucketAliases, bucketsById, context, resolveCtx, fieldName, resolving);
518
+ }
519
+ }
520
+ if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {
521
+ const resolved = tryResolveTypeReference(typeNode, sourceFile, resolveCtx, { fieldName, resolving });
522
+ if (resolved) {
523
+ return parseScalarType(resolved, sourceFile, blockAliases, bucketAliases, bucketsById, context, resolveCtx, fieldName, resolving);
524
+ }
525
+ }
526
+ if (ts.isTypeReferenceNode(typeNode)) {
527
+ const ref = ts.isIdentifier(typeNode.typeName)
528
+ ? applyImportRename(typeNode.typeName.text, sourceFile, resolveCtx.renameMap)
529
+ : typeNode.typeName.getText(sourceFile);
530
+ switch (ref) {
531
+ case "UUID":
532
+ case "SupatypeAuthUserId":
533
+ return scalar("uuid");
534
+ case "RichText": {
535
+ const defaultArg = typeNode.typeArguments?.[0];
536
+ if (!defaultArg)
537
+ return scalar("richText");
538
+ const literal = parseDefaultLiteral(defaultArg, sourceFile);
539
+ if (literal === undefined) {
540
+ throw new Error(`RichText default must be a string literal (plain text or Lexical JSON string), not HTML.`);
541
+ }
542
+ if (typeof literal !== "string") {
543
+ throw new Error(`RichText<…> default must be a string literal (plain text or Lexical JSON string).`);
544
+ }
545
+ return scalar("richText", {
546
+ kernel: { default: { kind: "value", value: literal } },
547
+ });
548
+ }
549
+ case "Slug": {
550
+ const fromArg = typeNode.typeArguments?.[0];
551
+ const fromLiteral = fromArg ? literalStringType(fromArg) : null;
552
+ return scalar("slug", { kernel: { from: fromLiteral ?? "title" } });
553
+ }
554
+ case "Email":
555
+ return scalar("email");
556
+ case "URL":
557
+ return scalar("url");
558
+ case "Markdown":
559
+ case "PhoneNumber":
560
+ return scalar("text");
561
+ case "Color":
562
+ return scalar("color");
563
+ case "IPAddress":
564
+ return scalar("ip");
565
+ case "CIDR":
566
+ return scalar("cidr");
567
+ case "MacAddress":
568
+ return scalar("macaddr");
569
+ case "XML":
570
+ return scalar("xml");
571
+ case "TSQuery":
572
+ return scalar("tsQuery");
573
+ case "TSVector":
574
+ return scalar("tsVector");
575
+ case "Money":
576
+ return scalar("money");
577
+ case "Decimal":
578
+ return scalar("decimal");
579
+ case "DateOnly":
580
+ return scalar("date");
581
+ case "Date":
582
+ case "DateTime":
583
+ case "Timestamp":
584
+ return scalar("datetime", { db: { pgType: "TIMESTAMP WITH TIME ZONE" } });
585
+ case "Int":
586
+ return scalar("integer");
587
+ case "SmallInt":
588
+ return scalar("smallInt");
589
+ case "BigInt":
590
+ return scalar("bigInt");
591
+ case "Float":
592
+ return scalar("float");
593
+ case "Bytea":
594
+ return scalar("bytes");
595
+ case "JSON":
596
+ return scalar("json");
597
+ case "Button":
598
+ return scalar("button", { db: { pgType: "JSONB" } });
599
+ case "Duration":
600
+ return scalar("json", { db: { pgType: "JSONB" } });
601
+ case "GeoPoint":
602
+ case "Geo":
603
+ return scalar("geo", { kernel: { geoType: "point", srid: 4326 } });
604
+ case "Asset":
605
+ case "FileAsset": {
606
+ const bucket = resolveBucketName(typeNode.typeArguments?.[0], sourceFile, bucketAliases, "assets");
607
+ const assetOpts = parseAssetFieldOptions(typeNode.typeArguments?.[1], sourceFile);
608
+ return attachStorageFieldMeta(scalar("file", {
609
+ db: { pgType: "TEXT" },
610
+ kernel: { bucket, ...(assetOpts.localized && { localized: true }) },
611
+ }), bucket, bucketsById);
612
+ }
613
+ case "ImageAsset": {
614
+ const bucket = resolveBucketName(typeNode.typeArguments?.[0], sourceFile, bucketAliases, "images");
615
+ const assetOpts = parseAssetFieldOptions(typeNode.typeArguments?.[1], sourceFile);
616
+ return attachStorageFieldMeta(scalar("image", {
617
+ db: { pgType: "TEXT" },
618
+ kernel: { bucket, ...(assetOpts.localized && { localized: true }) },
619
+ }), bucket, bucketsById);
620
+ }
621
+ case "Blocks":
622
+ return scalar("blocks", {
623
+ kernel: {
624
+ index: true,
625
+ blocks: parseBlocksTypeDefinitions(typeNode.typeArguments?.[0], sourceFile, blockAliases, bucketAliases, bucketsById, context, resolveCtx),
626
+ },
627
+ });
628
+ case "Vector": {
629
+ const dimensions = typeNode.typeArguments?.[0]?.getText(sourceFile);
630
+ return scalar("vector", {
631
+ kernel: { dimensions: Number(dimensions ?? "1536") },
632
+ });
633
+ }
634
+ default:
635
+ throw unknownTypeError(ref, fieldName);
636
+ }
637
+ }
638
+ switch (typeNode.kind) {
639
+ case ts.SyntaxKind.StringKeyword:
640
+ return scalar("text");
641
+ case ts.SyntaxKind.NumberKeyword:
642
+ return scalar("float");
643
+ case ts.SyntaxKind.BooleanKeyword:
644
+ return scalar("boolean");
645
+ default:
646
+ return scalar("json");
647
+ }
648
+ }
649
+ function collectBlockAliases(sourceFile, bucketAliases, bucketsById, resolveCtx) {
650
+ const blocks = new Map();
651
+ for (const stmt of sourceFile.statements) {
652
+ if (!ts.isTypeAliasDeclaration(stmt))
653
+ continue;
654
+ if (!ts.isTypeReferenceNode(stmt.type))
655
+ continue;
656
+ if (!ts.isIdentifier(stmt.type.typeName) || stmt.type.typeName.text !== "Block")
657
+ continue;
658
+ const block = parseInlineBlockDefinition(stmt.type, sourceFile, new Map(), bucketAliases, bucketsById, {}, resolveCtx);
659
+ if (!block)
660
+ continue;
661
+ blocks.set(stmt.name.text, block);
662
+ }
663
+ return blocks;
664
+ }
665
+ function collectLocaleConfig(sourceFile) {
666
+ for (const stmt of sourceFile.statements) {
667
+ if (!ts.isTypeAliasDeclaration(stmt))
668
+ continue;
669
+ if (!hasExportModifier(stmt))
670
+ continue;
671
+ if (!ts.isTypeReferenceNode(stmt.type))
672
+ continue;
673
+ if (stmt.type.typeName.getText(sourceFile) !== "LocaleConfig")
674
+ continue;
675
+ const parsed = parseLocaleConfigTypeRef(stmt.type, sourceFile);
676
+ if (parsed)
677
+ return parsed;
678
+ }
679
+ return undefined;
680
+ }
681
+ function parseLocaleConfigTypeRef(typeRef, sourceFile) {
682
+ const [localesArg, defaultArg] = typeRef.typeArguments ?? [];
683
+ if (!localesArg || !defaultArg)
684
+ return null;
685
+ const locales = parseStringLiteralTuple(localesArg, sourceFile);
686
+ const defaultLocale = literalStringType(defaultArg);
687
+ if (!locales || locales.length === 0 || !defaultLocale)
688
+ return null;
689
+ if (!locales.includes(defaultLocale)) {
690
+ throw new Error(`LocaleConfig defaultLocale "${defaultLocale}" must be one of: ${locales.join(", ")}`);
691
+ }
692
+ return { locales, defaultLocale };
693
+ }
694
+ function parseStringLiteralTuple(node, sourceFile) {
695
+ if (!ts.isTupleTypeNode(node))
696
+ return null;
697
+ const out = [];
698
+ for (const el of node.elements) {
699
+ const lit = literalStringType(el);
700
+ if (!lit)
701
+ return null;
702
+ out.push(lit);
703
+ }
704
+ return out;
705
+ }
706
+ function collectBucketContext(sourceFile) {
707
+ const aliases = new Map();
708
+ const bucketsById = new Map();
709
+ for (const stmt of sourceFile.statements) {
710
+ if (!ts.isTypeAliasDeclaration(stmt))
711
+ continue;
712
+ if (!ts.isTypeReferenceNode(stmt.type))
713
+ continue;
714
+ if (!ts.isIdentifier(stmt.type.typeName) || stmt.type.typeName.text !== "Bucket")
715
+ continue;
716
+ const [nameArg, configArg] = stmt.type.typeArguments ?? [];
717
+ if (!nameArg || !ts.isLiteralTypeNode(nameArg) || !ts.isStringLiteral(nameArg.literal))
718
+ continue;
719
+ const id = nameArg.literal.text;
720
+ aliases.set(stmt.name.text, id);
721
+ const parsed = configArg && ts.isTypeLiteralNode(configArg)
722
+ ? parseBucketTypeLiteral(configArg, sourceFile)
723
+ : {};
724
+ const next = buildExtractedBucketAst(id, parsed);
725
+ const existing = bucketsById.get(id);
726
+ if (existing !== undefined && !bucketsEqual(existing, next)) {
727
+ throw new Error(`Conflicting Bucket<> declarations for id "${id}". Use a single export per bucket id.`);
728
+ }
729
+ bucketsById.set(id, next);
730
+ }
731
+ return { aliases, bucketsById };
732
+ }
733
+ function buildExtractedBucketAst(id, parsed) {
734
+ const mode = parsed.accessMode ?? "private";
735
+ const pub = mode === "public";
736
+ const row = {
737
+ id,
738
+ public: pub,
739
+ accessMode: mode,
740
+ ...(parsed.allowedMimeTypes !== undefined && parsed.allowedMimeTypes.length > 0
741
+ ? { allowedMimeTypes: parsed.allowedMimeTypes }
742
+ : {}),
743
+ ...(parsed.fileSizeLimit !== undefined ? { fileSizeLimit: parsed.fileSizeLimit } : {}),
744
+ ...(parsed.access !== undefined &&
745
+ Object.keys(parsed.access).length > 0 && { access: parsed.access }),
746
+ ...(parsed.s3BucketPolicy !== undefined ? { s3BucketPolicy: parsed.s3BucketPolicy } : {}),
747
+ };
748
+ return row;
749
+ }
750
+ function parseBucketTypeLiteral(lit, sourceFile) {
751
+ const out = {};
752
+ for (const member of lit.members) {
753
+ if (!ts.isPropertySignature(member) || !member.type)
754
+ continue;
755
+ const key = getPropertyName(member.name);
756
+ if (!key)
757
+ continue;
758
+ if (key === "accessMode") {
759
+ const mode = parseAccessModeLiteral(member.type, sourceFile);
760
+ if (mode !== undefined)
761
+ out.accessMode = mode;
762
+ continue;
763
+ }
764
+ if (key === "maxSize") {
765
+ const s = parseSizeStringLiteral(member.type, sourceFile);
766
+ if (s !== undefined) {
767
+ const bytes = parseDataSizeBytes(s);
768
+ out.fileSizeLimit = bytes;
769
+ }
770
+ continue;
771
+ }
772
+ if (key === "accept") {
773
+ const types = parseMimeAcceptList(member.type, sourceFile);
774
+ if (types !== undefined)
775
+ out.allowedMimeTypes = types;
776
+ continue;
777
+ }
778
+ if (key === "access") {
779
+ const acc = parsePartialBucketAccess(member.type, sourceFile);
780
+ if (acc !== undefined && Object.keys(acc).length > 0)
781
+ out.access = acc;
782
+ continue;
783
+ }
784
+ if (key === "s3BucketPolicy") {
785
+ const pol = parseJsonStringLiteral(member.type, sourceFile);
786
+ if (pol !== undefined)
787
+ out.s3BucketPolicy = pol;
788
+ continue;
789
+ }
790
+ }
791
+ return out;
792
+ }
793
+ function parseAccessModeLiteral(typeNode, sourceFile) {
794
+ const text = stripQuotes(typeNode.getText(sourceFile));
795
+ if (text === "public" || text === "private" || text === "custom")
796
+ return text;
797
+ return undefined;
798
+ }
799
+ function parseSizeStringLiteral(typeNode, sourceFile) {
800
+ if (ts.isLiteralTypeNode(typeNode) && ts.isStringLiteral(typeNode.literal)) {
801
+ return typeNode.literal.text;
802
+ }
803
+ return stripQuotes(typeNode.getText(sourceFile)) || undefined;
804
+ }
805
+ function parseJsonStringLiteral(typeNode, sourceFile) {
806
+ return parseSizeStringLiteral(typeNode, sourceFile);
807
+ }
808
+ function parseMimeAcceptList(typeNode, sourceFile) {
809
+ if (ts.isTypeOperatorNode(typeNode) && typeNode.operator === ts.SyntaxKind.ReadonlyKeyword) {
810
+ return parseMimeAcceptList(typeNode.type, sourceFile);
811
+ }
812
+ if (ts.isTupleTypeNode(typeNode)) {
813
+ const values = [];
814
+ for (const el of typeNode.elements) {
815
+ const node = ts.isNamedTupleMember(el) ? el.type : el;
816
+ const s = literalStringType(node);
817
+ if (!s)
818
+ return undefined;
819
+ values.push(s);
820
+ }
821
+ return values.length > 0 ? values : undefined;
822
+ }
823
+ if (ts.isUnionTypeNode(typeNode)) {
824
+ const values = [];
825
+ for (const u of typeNode.types) {
826
+ const s = literalStringType(u);
827
+ if (!s)
828
+ return undefined;
829
+ values.push(s);
830
+ }
831
+ return values.length > 0 ? values : undefined;
832
+ }
833
+ return undefined;
834
+ }
835
+ function parsePartialBucketAccess(typeNode, sourceFile) {
836
+ if (!ts.isTypeLiteralNode(typeNode))
837
+ return undefined;
838
+ const access = {};
839
+ for (const member of typeNode.members) {
840
+ if (!ts.isPropertySignature(member) || !member.type)
841
+ continue;
842
+ const key = getPropertyName(member.name);
843
+ if (key !== "read" && key !== "create" && key !== "delete")
844
+ continue;
845
+ access[key] = parseAccessRule(member.type, sourceFile);
846
+ }
847
+ return access;
848
+ }
849
+ /** Parse human-readable size from schema types, e.g. `50MB`. */
850
+ function parseDataSizeBytes(lit) {
851
+ const m = lit.trim().match(/^(\d+(?:\.\d+)?)\s*(B|KB|MB|GB)$/i);
852
+ if (!m?.[1] || !m[2])
853
+ throw new Error(`Invalid maxSize literal: "${lit}". Use forms like "50MB", "100KB".`);
854
+ const n = Number(m[1]);
855
+ if (!Number.isFinite(n) || n < 0)
856
+ throw new Error(`Invalid maxSize number in: "${lit}"`);
857
+ const pow = {
858
+ B: 0,
859
+ KB: 10,
860
+ MB: 20,
861
+ GB: 30,
862
+ };
863
+ const unit = m[2].toUpperCase();
864
+ const exp = pow[unit];
865
+ if (exp === undefined)
866
+ throw new Error(`Unsupported maxSize unit in: "${lit}"`);
867
+ return Math.round(n * Math.pow(2, exp));
868
+ }
869
+ function stripQuotes(s) {
870
+ return s.replace(/^['"]|['"]$/g, "");
871
+ }
872
+ function bucketsEqual(a, b) {
873
+ return (a.public === b.public &&
874
+ (a.accessMode ?? "private") === (b.accessMode ?? "private") &&
875
+ JSON.stringify(a.access ?? null) === JSON.stringify(b.access ?? null) &&
876
+ JSON.stringify(a.allowedMimeTypes ?? null) === JSON.stringify(b.allowedMimeTypes ?? null) &&
877
+ (a.fileSizeLimit ?? null) === (b.fileSizeLimit ?? null) &&
878
+ (a.s3BucketPolicy ?? null) === (b.s3BucketPolicy ?? null));
879
+ }
880
+ function attachStorageFieldMeta(field, bucketId, bucketsById) {
881
+ const cfg = bucketsById.get(bucketId);
882
+ if (cfg?.accessMode !== undefined) {
883
+ return {
884
+ ...field,
885
+ kernel: { ...field.kernel, accessMode: cfg.accessMode },
886
+ };
887
+ }
888
+ return field;
889
+ }
890
+ function parseBlocksTypeDefinitions(blocksArg, sourceFile, blockAliases, bucketAliases, bucketsById, context = {}, resolveCtx) {
891
+ if (!blocksArg)
892
+ return [];
893
+ const parts = ts.isUnionTypeNode(blocksArg) ? blocksArg.types : [blocksArg];
894
+ const out = [];
895
+ for (const part of parts) {
896
+ if (ts.isTypeReferenceNode(part) && ts.isIdentifier(part.typeName)) {
897
+ if (part.typeName.text === "Block") {
898
+ const inline = parseInlineBlockDefinition(part, sourceFile, blockAliases, bucketAliases, bucketsById, context, resolveCtx);
899
+ if (inline)
900
+ out.push(inline);
901
+ continue;
902
+ }
903
+ const aliased = blockAliases.get(part.typeName.text);
904
+ if (aliased)
905
+ out.push(aliased);
906
+ }
907
+ }
908
+ return out;
909
+ }
910
+ function parseInlineBlockDefinition(ref, sourceFile, blockAliases, bucketAliases, bucketsById, context = {}, resolveCtx) {
911
+ const [nameArg, fieldsArg, metaArg] = ref.typeArguments ?? [];
912
+ const name = literalStringType(nameArg);
913
+ if (!name || !fieldsArg || !ts.isTypeLiteralNode(fieldsArg))
914
+ return null;
915
+ const fields = {};
916
+ for (const member of fieldsArg.members) {
917
+ if (!ts.isPropertySignature(member) || !member.type)
918
+ continue;
919
+ const fieldName = getPropertyName(member.name);
920
+ if (!fieldName)
921
+ continue;
922
+ fields[fieldName] = parseFieldType(fieldName, member.type, sourceFile, blockAliases, bucketAliases, bucketsById, context, resolveCtx);
923
+ }
924
+ let label;
925
+ let icon;
926
+ if (metaArg && ts.isTypeLiteralNode(metaArg)) {
927
+ for (const m of metaArg.members) {
928
+ if (!ts.isPropertySignature(m) || !m.type)
929
+ continue;
930
+ const key = getPropertyName(m.name);
931
+ if (!key)
932
+ continue;
933
+ const value = literalStringType(m.type);
934
+ if (!value)
935
+ continue;
936
+ if (key === "label")
937
+ label = value;
938
+ if (key === "icon")
939
+ icon = value;
940
+ }
941
+ }
942
+ return {
943
+ name,
944
+ ...(label !== undefined && { label }),
945
+ ...(icon !== undefined && { icon }),
946
+ fields,
947
+ };
948
+ }
949
+ function literalStringType(typeNode) {
950
+ if (!typeNode)
951
+ return null;
952
+ if (ts.isLiteralTypeNode(typeNode) && ts.isStringLiteral(typeNode.literal))
953
+ return typeNode.literal.text;
954
+ return null;
955
+ }
956
+ /** Field names referenced in `{name}` and `{truncate(name, n)}` (case-sensitive, same as model fields). */
957
+ function fieldNamesInComputedTemplate(template) {
958
+ const fields = new Set();
959
+ const reTrunc = /\{truncate\s*\(\s*([a-zA-Z_]\w*)\s*,\s*(\d+)\s*\)\}/gi;
960
+ let m;
961
+ while ((m = reTrunc.exec(template)) !== null) {
962
+ const ref = m[1];
963
+ if (ref)
964
+ fields.add(ref);
965
+ }
966
+ const reSimple = /\{([a-zA-Z_]\w*)\}/g;
967
+ while ((m = reSimple.exec(template)) !== null) {
968
+ const ref = m[1];
969
+ if (ref)
970
+ fields.add(ref);
971
+ }
972
+ return [...fields];
973
+ }
974
+ function looksLikeComputedTemplateLiteral(lit) {
975
+ return /\{truncate\s*\(/i.test(lit) || /\{[a-zA-Z_]\w*\}/g.test(lit);
976
+ }
977
+ /** Resolves second type arg of `ComputedFrom<Value, Sources>` — tuple concat, single field, or template literal. */
978
+ function parseComputedFromSecondArg(sourcesArg, sourceFile) {
979
+ if (!sourcesArg)
980
+ return null;
981
+ const single = literalStringType(sourcesArg);
982
+ if (single) {
983
+ if (looksLikeComputedTemplateLiteral(single)) {
984
+ return { sources: fieldNamesInComputedTemplate(single), template: single };
985
+ }
986
+ return { sources: [single] };
987
+ }
988
+ const elemsFromTupleType = (tuple) => {
989
+ const nodes = [];
990
+ for (const el of tuple.elements) {
991
+ if (ts.isNamedTupleMember(el)) {
992
+ if (!el.type)
993
+ return null;
994
+ nodes.push(el.type);
995
+ continue;
996
+ }
997
+ nodes.push(el);
998
+ }
999
+ return nodes;
1000
+ };
1001
+ const tupleElems = () => {
1002
+ if (ts.isTupleTypeNode(sourcesArg))
1003
+ return elemsFromTupleType(sourcesArg);
1004
+ if (ts.isTypeOperatorNode(sourcesArg) && sourcesArg.operator === ts.SyntaxKind.ReadonlyKeyword) {
1005
+ const inner = sourcesArg.type;
1006
+ if (inner && ts.isTupleTypeNode(inner))
1007
+ return elemsFromTupleType(inner);
1008
+ }
1009
+ return null;
1010
+ };
1011
+ const elems = tupleElems();
1012
+ if (!elems || elems.length === 0)
1013
+ return null;
1014
+ const keys = [];
1015
+ for (const node of elems) {
1016
+ const k = literalStringType(node);
1017
+ if (!k)
1018
+ return null;
1019
+ keys.push(k);
1020
+ }
1021
+ return { sources: keys };
1022
+ }
1023
+ function resolveBucketName(typeArg, sourceFile, bucketAliases, fallback) {
1024
+ if (!typeArg)
1025
+ return fallback;
1026
+ if (ts.isTypeReferenceNode(typeArg) && ts.isIdentifier(typeArg.typeName)) {
1027
+ return bucketAliases.get(typeArg.typeName.text) ?? typeArg.typeName.text;
1028
+ }
1029
+ if (ts.isLiteralTypeNode(typeArg) && ts.isStringLiteral(typeArg.literal)) {
1030
+ return typeArg.literal.text;
1031
+ }
1032
+ return typeArg.getText(sourceFile).replace(/^['"]|['"]$/g, "") || fallback;
1033
+ }
1034
+ function isBooleanLiteralType(typeNode, value) {
1035
+ if (value) {
1036
+ if (typeNode.kind === ts.SyntaxKind.TrueKeyword)
1037
+ return true;
1038
+ if (ts.isLiteralTypeNode(typeNode) && typeNode.literal.kind === ts.SyntaxKind.TrueKeyword) {
1039
+ return true;
1040
+ }
1041
+ return false;
1042
+ }
1043
+ if (typeNode.kind === ts.SyntaxKind.FalseKeyword)
1044
+ return true;
1045
+ if (ts.isLiteralTypeNode(typeNode) && typeNode.literal.kind === ts.SyntaxKind.FalseKeyword) {
1046
+ return true;
1047
+ }
1048
+ return false;
1049
+ }
1050
+ function parseAssetFieldOptions(optionsArg, sourceFile) {
1051
+ if (!optionsArg || !ts.isTypeLiteralNode(optionsArg))
1052
+ return { localized: false };
1053
+ for (const member of optionsArg.members) {
1054
+ if (!ts.isPropertySignature(member) || !member.type)
1055
+ continue;
1056
+ const key = getPropertyName(member.name);
1057
+ if (key === "localized" && isBooleanLiteralType(member.type, true)) {
1058
+ return { localized: true };
1059
+ }
1060
+ }
1061
+ return { localized: false };
1062
+ }
1063
+ function parseMetaLiteral(metaArg, sourceFile) {
1064
+ const result = {};
1065
+ if (!metaArg || !ts.isTypeLiteralNode(metaArg))
1066
+ return result;
1067
+ for (const member of metaArg.members) {
1068
+ if (!ts.isPropertySignature(member) || !member.type)
1069
+ continue;
1070
+ const key = getPropertyName(member.name);
1071
+ if (!key)
1072
+ continue;
1073
+ if (key === "singleton" && isBooleanLiteralType(member.type, true)) {
1074
+ result.singleton = true;
1075
+ }
1076
+ else if (key === "timestamps") {
1077
+ if (isBooleanLiteralType(member.type, true))
1078
+ result.timestamps = true;
1079
+ if (isBooleanLiteralType(member.type, false))
1080
+ result.timestamps = false;
1081
+ }
1082
+ else if (key === "softDelete") {
1083
+ if (isBooleanLiteralType(member.type, true))
1084
+ result.softDelete = true;
1085
+ if (isBooleanLiteralType(member.type, false))
1086
+ result.softDelete = false;
1087
+ }
1088
+ else if (key === "autoLocalize" && isBooleanLiteralType(member.type, true)) {
1089
+ result.autoLocalize = true;
1090
+ }
1091
+ else if (key === "tableName" &&
1092
+ ts.isLiteralTypeNode(member.type) &&
1093
+ ts.isStringLiteral(member.type.literal)) {
1094
+ result.tableName = member.type.literal.text;
1095
+ }
1096
+ }
1097
+ return result;
1098
+ }
1099
+ function hasCompositeWrapper(typeNode, wrapperName) {
1100
+ if (!ts.isTypeReferenceNode(typeNode) || !ts.isIdentifier(typeNode.typeName))
1101
+ return false;
1102
+ if (typeNode.typeName.text === wrapperName)
1103
+ return true;
1104
+ if (typeNode.typeName.text === "WithTimestamps" ||
1105
+ typeNode.typeName.text === "WithSoftDelete" ||
1106
+ typeNode.typeName.text === "WithPublishable") {
1107
+ const inner = typeNode.typeArguments?.[0];
1108
+ if (inner)
1109
+ return hasCompositeWrapper(inner, wrapperName);
1110
+ }
1111
+ return false;
1112
+ }
1113
+ function parseModelMeta(metaArg, sourceFile, modelName, fieldsArg, fields) {
1114
+ const literal = parseMetaLiteral(metaArg, sourceFile);
1115
+ const singleton = literal.singleton === true;
1116
+ const tableName = literal.tableName ?? (singleton ? `_global_${toSnakeCase(modelName)}` : toSnakeCase(modelName));
1117
+ const timestamps = literal.timestamps ??
1118
+ (hasCompositeWrapper(fieldsArg, "WithTimestamps") ||
1119
+ (fields["created_at"] !== undefined && fields["updated_at"] !== undefined));
1120
+ const softDelete = literal.softDelete ??
1121
+ (hasCompositeWrapper(fieldsArg, "WithSoftDelete") || fields["deleted_at"] !== undefined);
1122
+ const options = {};
1123
+ if (singleton)
1124
+ options.singleton = true;
1125
+ if (timestamps)
1126
+ options.timestamps = true;
1127
+ if (softDelete)
1128
+ options.softDelete = true;
1129
+ if (literal.autoLocalize === true)
1130
+ options.autoLocalize = true;
1131
+ return {
1132
+ tableName,
1133
+ access: parseModelAccess(metaArg, sourceFile),
1134
+ options,
1135
+ };
1136
+ }
1137
+ function parseModelAccess(metaArg, sourceFile) {
1138
+ if (!metaArg || !ts.isTypeLiteralNode(metaArg))
1139
+ return {};
1140
+ const accessProp = metaArg.members.find((member) => ts.isPropertySignature(member) && getPropertyName(member.name) === "access");
1141
+ if (!accessProp || !ts.isPropertySignature(accessProp) || !accessProp.type || !ts.isTypeLiteralNode(accessProp.type)) {
1142
+ return {};
1143
+ }
1144
+ const access = {};
1145
+ for (const member of accessProp.type.members) {
1146
+ if (!ts.isPropertySignature(member) || !member.type)
1147
+ continue;
1148
+ const key = getPropertyName(member.name);
1149
+ if (!key)
1150
+ continue;
1151
+ access[key] = parseAccessRule(member.type, sourceFile);
1152
+ }
1153
+ return access;
1154
+ }
1155
+ function parseAccessRule(typeNode, sourceFile) {
1156
+ if (!ts.isTypeReferenceNode(typeNode))
1157
+ return { type: "private" };
1158
+ const ref = typeNode.typeName.getText(sourceFile);
1159
+ switch (ref) {
1160
+ case "Public":
1161
+ case "BucketPublic":
1162
+ return { type: "public" };
1163
+ case "LoggedIn":
1164
+ case "BucketLoggedIn":
1165
+ return { type: "authenticated" };
1166
+ case "Private":
1167
+ case "BucketPrivate":
1168
+ return { type: "private" };
1169
+ case "BucketOwner":
1170
+ return { type: "owner", field: "owner_id" };
1171
+ case "Owner": {
1172
+ const args = typeNode.typeArguments ?? [];
1173
+ const keyArg = args.length >= 2 ? args[1] : args[0];
1174
+ // Must match engine `AccessRule::Owner { field }` (see supatype-schema-engine parser/ast.rs).
1175
+ return { type: "owner", field: keyArg?.getText(sourceFile).replace(/['"]/g, "") ?? "user_id" };
1176
+ }
1177
+ case "OwnerFrom": {
1178
+ const relationArg = typeNode.typeArguments?.[0];
1179
+ const relationField = relationArg?.getText(sourceFile).replace(/['"]/g, "") ?? "owner";
1180
+ return { type: "owner", field: relationField };
1181
+ }
1182
+ case "Role": {
1183
+ const roleArg = typeNode.typeArguments?.[0];
1184
+ return { type: "role", roles: [roleArg?.getText(sourceFile).replace(/['"]/g, "") ?? "admin"] };
1185
+ }
1186
+ case "BucketRole": {
1187
+ const roleArg = typeNode.typeArguments?.[0];
1188
+ return { type: "role", roles: [roleArg?.getText(sourceFile).replace(/['"]/g, "") ?? "admin"] };
1189
+ }
1190
+ default:
1191
+ return { type: "private" };
1192
+ }
1193
+ }
1194
+ function relationTargetFromTypeArg(typeArg, sourceFile) {
1195
+ if (!typeArg)
1196
+ return "unknown";
1197
+ const raw = typeArg.getText(sourceFile).replace(/\s/g, "");
1198
+ if (raw === "SupatypeAuthUser")
1199
+ return "supatype:user";
1200
+ return raw.replace(/\W/g, "");
1201
+ }
1202
+ function toSnakeCase(s) {
1203
+ return s.replace(/([A-Z])/g, "_$1").replace(/^_/, "").toLowerCase();
1204
+ }
1205
+ function relationForeignKeyFromField(fieldName) {
1206
+ const snake = fieldName
1207
+ .replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2")
1208
+ .replace(/([a-z0-9])([A-Z])/g, "$1_$2")
1209
+ .toLowerCase();
1210
+ const base = snake.replace(/_id$/i, "");
1211
+ return `${base}_id`;
1212
+ }
1213
+ //# sourceMappingURL=type-extractor.js.map