@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,876 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, isAbsolute, resolve } from "node:path";
3
+ import ts from "typescript";
4
+ export function extractSchemaAstFromTypes(schemaPath, cwd = process.cwd()) {
5
+ const absPath = resolve(cwd, schemaPath);
6
+ if (!existsSync(absPath)) {
7
+ throw new Error(`Schema file not found: ${absPath}`);
8
+ }
9
+ const sourceFiles = loadSchemaSourceFiles(absPath);
10
+ const bucketAliases = new Map();
11
+ const bucketsById = new Map();
12
+ for (const sourceFile of sourceFiles) {
13
+ const bucketContext = collectBucketContext(sourceFile);
14
+ for (const [alias, bucketId] of bucketContext.aliases) {
15
+ bucketAliases.set(alias, bucketId);
16
+ }
17
+ for (const [bucketId, bucket] of bucketContext.bucketsById) {
18
+ const existing = bucketsById.get(bucketId);
19
+ if (existing !== undefined && !bucketsEqual(existing, bucket)) {
20
+ throw new Error(`Conflicting Bucket<> declarations for id "${bucketId}". Use a single export per bucket id.`);
21
+ }
22
+ bucketsById.set(bucketId, bucket);
23
+ }
24
+ }
25
+ const blockAliases = new Map();
26
+ for (const sourceFile of sourceFiles) {
27
+ const next = collectBlockAliases(sourceFile, bucketAliases, bucketsById);
28
+ for (const [name, block] of next) {
29
+ blockAliases.set(name, block);
30
+ }
31
+ }
32
+ const models = [];
33
+ for (const sourceFile of sourceFiles) {
34
+ for (const stmt of sourceFile.statements) {
35
+ if (!ts.isTypeAliasDeclaration(stmt))
36
+ continue;
37
+ if (!hasExportModifier(stmt))
38
+ continue;
39
+ if (!ts.isTypeReferenceNode(stmt.type))
40
+ continue;
41
+ if (stmt.type.typeName.getText(sourceFile) !== "Model")
42
+ continue;
43
+ const [fieldsArg, metaArg] = stmt.type.typeArguments ?? [];
44
+ if (!fieldsArg)
45
+ continue;
46
+ const fieldsLiteral = unwrapModelFields(fieldsArg);
47
+ if (!fieldsLiteral)
48
+ continue;
49
+ const fields = {};
50
+ for (const member of fieldsLiteral.members) {
51
+ if (!ts.isPropertySignature(member) || !member.type)
52
+ continue;
53
+ const name = getPropertyName(member.name);
54
+ if (!name)
55
+ continue;
56
+ fields[name] = parseFieldType(name, member.type, sourceFile, blockAliases, bucketAliases, bucketsById);
57
+ }
58
+ models.push({
59
+ name: stmt.name.text,
60
+ tableName: toSnakeCase(stmt.name.text),
61
+ fields,
62
+ access: parseModelAccess(metaArg, sourceFile),
63
+ indexes: [],
64
+ options: {},
65
+ });
66
+ }
67
+ }
68
+ if (models.length === 0)
69
+ return null;
70
+ const storageBuckets = bucketsById.size > 0 ? [...bucketsById.values()].sort((a, b) => a.id.localeCompare(b.id)) : undefined;
71
+ return {
72
+ models,
73
+ ...(storageBuckets !== undefined && storageBuckets.length > 0 && { storageBuckets }),
74
+ };
75
+ }
76
+ function loadSchemaSourceFiles(entryPath) {
77
+ const visited = new Set();
78
+ const sourceFiles = [];
79
+ const queue = [entryPath];
80
+ while (queue.length > 0) {
81
+ const currentPath = queue.shift();
82
+ if (!currentPath)
83
+ continue;
84
+ if (visited.has(currentPath))
85
+ continue;
86
+ visited.add(currentPath);
87
+ if (!existsSync(currentPath))
88
+ continue;
89
+ const sourceText = readFileSync(currentPath, "utf8");
90
+ const sourceFile = ts.createSourceFile(currentPath, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
91
+ sourceFiles.push(sourceFile);
92
+ const baseDir = dirname(currentPath);
93
+ for (const stmt of sourceFile.statements) {
94
+ if (!ts.isExportDeclaration(stmt))
95
+ continue;
96
+ if (!stmt.moduleSpecifier || !ts.isStringLiteral(stmt.moduleSpecifier))
97
+ continue;
98
+ const nextPath = resolveTypeModulePath(baseDir, stmt.moduleSpecifier.text);
99
+ if (!nextPath)
100
+ continue;
101
+ if (!visited.has(nextPath))
102
+ queue.push(nextPath);
103
+ }
104
+ }
105
+ return sourceFiles;
106
+ }
107
+ function resolveTypeModulePath(fromDir, specifier) {
108
+ const basePath = isAbsolute(specifier) ? specifier : resolve(fromDir, specifier);
109
+ const candidates = specifier.endsWith(".js")
110
+ ? [
111
+ basePath,
112
+ basePath.replace(/\.js$/i, ".ts"),
113
+ basePath.replace(/\.js$/i, ".tsx"),
114
+ basePath.replace(/\.js$/i, ".d.ts"),
115
+ ]
116
+ : [
117
+ basePath,
118
+ `${basePath}.ts`,
119
+ `${basePath}.tsx`,
120
+ `${basePath}.d.ts`,
121
+ resolve(basePath, "index.ts"),
122
+ resolve(basePath, "index.tsx"),
123
+ resolve(basePath, "index.d.ts"),
124
+ ];
125
+ for (const candidate of candidates) {
126
+ if (existsSync(candidate))
127
+ return candidate;
128
+ }
129
+ return null;
130
+ }
131
+ function hasExportModifier(node) {
132
+ if (!ts.canHaveModifiers(node))
133
+ return false;
134
+ return (ts.getModifiers(node)?.some((mod) => mod.kind === ts.SyntaxKind.ExportKeyword) ?? false);
135
+ }
136
+ function getPropertyName(name) {
137
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name))
138
+ return name.text;
139
+ return null;
140
+ }
141
+ function unwrapModelFields(typeNode) {
142
+ if (ts.isTypeLiteralNode(typeNode))
143
+ return typeNode;
144
+ if (!ts.isTypeReferenceNode(typeNode) || !ts.isIdentifier(typeNode.typeName))
145
+ return null;
146
+ // Composite helpers in @supatype/types wrap the concrete field object.
147
+ if (typeNode.typeName.text === "WithTimestamps" ||
148
+ typeNode.typeName.text === "WithSoftDelete" ||
149
+ typeNode.typeName.text === "WithPublishable") {
150
+ const inner = typeNode.typeArguments?.[0];
151
+ if (!inner)
152
+ return null;
153
+ return unwrapModelFields(inner);
154
+ }
155
+ return null;
156
+ }
157
+ function parseFieldType(fieldName, typeNode, sourceFile, blockAliases, bucketAliases, bucketsById) {
158
+ const flags = {
159
+ required: true,
160
+ unique: false,
161
+ index: false,
162
+ primaryKey: false,
163
+ serverGenerated: false,
164
+ autoIncrement: false,
165
+ relationCardinality: undefined,
166
+ relationTarget: undefined,
167
+ editorReadOnly: false,
168
+ /** When set from `ComputedFrom`, Studio previews from these sources until edited on create */
169
+ computedFromSources: undefined,
170
+ /** When set, second arg was a template literal with `{field}` / `{truncate(f, n)}` */
171
+ computedFromTemplate: undefined,
172
+ };
173
+ let current = typeNode;
174
+ while (ts.isTypeReferenceNode(current) && ts.isIdentifier(current.typeName)) {
175
+ const typeName = current.typeName.text;
176
+ switch (typeName) {
177
+ case "Optional":
178
+ flags.required = false;
179
+ current = current.typeArguments?.[0] ?? current;
180
+ continue;
181
+ case "Unique":
182
+ flags.unique = true;
183
+ current = current.typeArguments?.[0] ?? current;
184
+ continue;
185
+ case "Indexed":
186
+ flags.index = true;
187
+ current = current.typeArguments?.[0] ?? current;
188
+ continue;
189
+ case "ServerDefault":
190
+ flags.serverGenerated = true;
191
+ current = current.typeArguments?.[0] ?? current;
192
+ continue;
193
+ case "AutoIncrement":
194
+ flags.serverGenerated = true;
195
+ flags.autoIncrement = true;
196
+ current = current.typeArguments?.[0] ?? current;
197
+ continue;
198
+ case "PrimaryKey":
199
+ flags.primaryKey = true;
200
+ flags.required = true;
201
+ flags.unique = true;
202
+ current = current.typeArguments?.[0] ?? current;
203
+ continue;
204
+ case "Default":
205
+ // Default<T, V> — unwrap to T so `Default<boolean, true>` resolves as boolean, not text.
206
+ current = current.typeArguments?.[0] ?? current;
207
+ continue;
208
+ case "Searchable":
209
+ current = current.typeArguments?.[0] ?? current;
210
+ continue;
211
+ case "EditorReadOnly":
212
+ flags.editorReadOnly = true;
213
+ current = current.typeArguments?.[0] ?? current;
214
+ continue;
215
+ case "Computed":
216
+ flags.editorReadOnly = true;
217
+ flags.serverGenerated = true;
218
+ current = current.typeArguments?.[0] ?? current;
219
+ continue;
220
+ case "ComputedFrom": {
221
+ const valueArg = current.typeArguments?.[0];
222
+ const sourcesArg = current.typeArguments?.[1];
223
+ const parsed = parseComputedFromSecondArg(sourcesArg, sourceFile);
224
+ if (parsed) {
225
+ flags.computedFromSources = parsed.sources;
226
+ flags.computedFromTemplate = parsed.template;
227
+ }
228
+ else {
229
+ flags.computedFromSources = ["title"];
230
+ }
231
+ current = valueArg ?? current;
232
+ continue;
233
+ }
234
+ case "MaxLength":
235
+ case "MinLength":
236
+ case "Between":
237
+ current = current.typeArguments?.[0] ?? current;
238
+ continue;
239
+ case "RelatedTo":
240
+ flags.relationCardinality = "one";
241
+ flags.relationTarget = relationTargetFromTypeArg(current.typeArguments?.[0], sourceFile);
242
+ // `target` must match `ModelAst.name` to satisfy validator resolution.
243
+ // FK column follows the field name (two relations to the same model need distinct columns).
244
+ return {
245
+ kind: "relation",
246
+ cardinality: "belongsTo",
247
+ target: flags.relationTarget,
248
+ foreignKey: relationForeignKeyFromField(fieldName),
249
+ ...(flags.editorReadOnly && { readOnly: true }),
250
+ };
251
+ case "HasOne":
252
+ flags.relationCardinality = "one";
253
+ flags.relationTarget = current.typeArguments?.[0]?.getText(sourceFile).replace(/\W/g, "") ?? "unknown";
254
+ return {
255
+ kind: "relation",
256
+ cardinality: "hasOne",
257
+ target: flags.relationTarget,
258
+ ...(flags.editorReadOnly && { readOnly: true }),
259
+ };
260
+ case "HasMany":
261
+ case "ManyToMany":
262
+ flags.relationCardinality = "many";
263
+ flags.relationTarget = current.typeArguments?.[0]?.getText(sourceFile).replace(/\W/g, "") ?? "unknown";
264
+ return {
265
+ kind: "relation",
266
+ cardinality: "hasMany",
267
+ target: flags.relationTarget,
268
+ ...(flags.editorReadOnly && { readOnly: true }),
269
+ };
270
+ default:
271
+ break;
272
+ }
273
+ break;
274
+ }
275
+ const scalar = parseScalarType(current, sourceFile, blockAliases, bucketAliases, bucketsById);
276
+ const parsed = {
277
+ ...scalar,
278
+ required: flags.required,
279
+ unique: flags.unique,
280
+ index: flags.index,
281
+ ...(flags.primaryKey && { primaryKey: true }),
282
+ ...(flags.editorReadOnly && { readOnly: true }),
283
+ };
284
+ if (flags.autoIncrement && parsed.kind === "integer") {
285
+ parsed.kind = "serial";
286
+ parsed.pgType = "SERIAL";
287
+ }
288
+ // RFC parity with existing examples: `id: UUID` should be the model PK unless
289
+ // explicitly overridden via wrappers such as PrimaryKey<> in source types.
290
+ if (fieldName === "id" &&
291
+ parsed.kind === "uuid" &&
292
+ flags.primaryKey === false) {
293
+ parsed.primaryKey = true;
294
+ parsed.unique = true;
295
+ parsed.required = true;
296
+ }
297
+ // Align with engine fixtures: PK UUID is created by the database unless the author supplies one.
298
+ if (parsed.primaryKey === true && parsed.kind === "uuid") {
299
+ parsed.default = { kind: "genRandomUuid" };
300
+ }
301
+ else if (parsed.primaryKey === true && (parsed.kind === "serial" || parsed.kind === "bigSerial")) {
302
+ flags.serverGenerated = true;
303
+ }
304
+ if (flags.serverGenerated === true) {
305
+ parsed.serverGenerated = true;
306
+ }
307
+ // Convention: standard audit columns are filled by the DB on insert/update.
308
+ const auditTs = fieldName === "created_at" ||
309
+ fieldName === "updated_at" ||
310
+ fieldName === "createdAt" ||
311
+ fieldName === "updatedAt";
312
+ if (auditTs) {
313
+ parsed.serverGenerated = true;
314
+ if ((parsed.kind === "datetime" || parsed.kind === "date") &&
315
+ parsed.default === undefined) {
316
+ parsed.default = { kind: "now" };
317
+ }
318
+ }
319
+ // `ServerDefault<Date>` etc. → DEFAULT NOW() for column types Postgres handles with NOW().
320
+ if (flags.serverGenerated &&
321
+ (parsed.kind === "datetime" || parsed.kind === "date") &&
322
+ parsed.default === undefined) {
323
+ parsed.default = { kind: "now" };
324
+ }
325
+ const hasCfTemplate = flags.computedFromTemplate !== undefined;
326
+ const hasCfSources = Boolean(flags.computedFromSources && flags.computedFromSources.length > 0);
327
+ if (parsed.kind === "text" && (hasCfTemplate || hasCfSources)) {
328
+ return {
329
+ ...parsed,
330
+ ...(hasCfSources && { sources: flags.computedFromSources }),
331
+ ...(hasCfTemplate && { template: flags.computedFromTemplate }),
332
+ };
333
+ }
334
+ return parsed;
335
+ }
336
+ function parseScalarType(typeNode, sourceFile, blockAliases, bucketAliases, bucketsById) {
337
+ if (ts.isArrayTypeNode(typeNode)) {
338
+ const element = parseScalarType(typeNode.elementType, sourceFile, blockAliases, bucketAliases, bucketsById);
339
+ const elementKind = typeof element.kind === "string" ? element.kind : "text";
340
+ // Keep arrays as native SQL arrays (old `arrayOf(...)` parity), not JSONB.
341
+ return {
342
+ kind: "array",
343
+ pgType: "ARRAY",
344
+ elementType: elementKind,
345
+ };
346
+ }
347
+ if (ts.isUnionTypeNode(typeNode)) {
348
+ const literals = typeNode.types.filter(ts.isLiteralTypeNode);
349
+ if (literals.length === typeNode.types.length && literals.every((lit) => ts.isStringLiteral(lit.literal))) {
350
+ return {
351
+ kind: "enum",
352
+ pgType: "TEXT",
353
+ values: literals.map((lit) => lit.literal.text),
354
+ };
355
+ }
356
+ const nonNull = typeNode.types.find((t) => t.kind !== ts.SyntaxKind.NullKeyword);
357
+ if (nonNull)
358
+ return parseScalarType(nonNull, sourceFile, blockAliases, bucketAliases, bucketsById);
359
+ }
360
+ if (ts.isTypeReferenceNode(typeNode)) {
361
+ const ref = typeNode.typeName.getText(sourceFile);
362
+ switch (ref) {
363
+ case "UUID":
364
+ case "SupatypeAuthUserId":
365
+ return { kind: "uuid", pgType: "UUID" };
366
+ case "RichText":
367
+ return { kind: "richText", pgType: "JSONB" };
368
+ case "Slug": {
369
+ const fromArg = typeNode.typeArguments?.[0];
370
+ const fromLiteral = fromArg ? literalStringType(fromArg) : null;
371
+ const from = fromLiteral ?? "title";
372
+ return { kind: "slug", pgType: "TEXT", from };
373
+ }
374
+ case "Email":
375
+ return { kind: "email", pgType: "TEXT" };
376
+ case "URL":
377
+ return { kind: "url", pgType: "TEXT" };
378
+ case "Markdown":
379
+ return { kind: "text", pgType: "TEXT" };
380
+ case "Color":
381
+ return { kind: "color", pgType: "TEXT" };
382
+ case "PhoneNumber":
383
+ return { kind: "text", pgType: "TEXT" };
384
+ case "IPAddress":
385
+ return { kind: "ip", pgType: "TEXT" };
386
+ case "CIDR":
387
+ return { kind: "cidr", pgType: "TEXT" };
388
+ case "MacAddress":
389
+ return { kind: "macaddr", pgType: "TEXT" };
390
+ case "XML":
391
+ return { kind: "xml", pgType: "TEXT" };
392
+ case "TSQuery":
393
+ return { kind: "tsQuery", pgType: "TEXT" };
394
+ case "TSVector":
395
+ return { kind: "tsVector", pgType: "TEXT" };
396
+ case "Money":
397
+ return { kind: "money", pgType: "TEXT" };
398
+ case "Decimal":
399
+ return { kind: "decimal", pgType: "TEXT" };
400
+ case "DateOnly":
401
+ return { kind: "date", pgType: "DATE" };
402
+ case "Date":
403
+ case "DateTime":
404
+ case "Timestamp":
405
+ return { kind: "datetime", pgType: "TIMESTAMP WITH TIME ZONE" };
406
+ case "Int":
407
+ return { kind: "integer", pgType: "INTEGER" };
408
+ case "SmallInt":
409
+ return { kind: "smallInt", pgType: "SMALLINT" };
410
+ case "BigInt":
411
+ return { kind: "bigInt", pgType: "BIGINT" };
412
+ case "Float":
413
+ return { kind: "float", pgType: "DOUBLE PRECISION" };
414
+ case "Bytea":
415
+ return { kind: "bytes", pgType: "BYTEA" };
416
+ case "JSON":
417
+ return { kind: "json", pgType: "JSONB" };
418
+ case "GeoPoint":
419
+ return { kind: "geo", pgType: "GEOGRAPHY", geoType: "point", srid: 4326 };
420
+ case "Geo":
421
+ return { kind: "geo", pgType: "GEOGRAPHY", geoType: "point", srid: 4326 };
422
+ case "Asset":
423
+ case "FileAsset": {
424
+ const bucket = resolveBucketName(typeNode.typeArguments?.[0], sourceFile, bucketAliases, "assets");
425
+ return attachStorageFieldMeta({ kind: "file", pgType: "TEXT", bucket }, bucket, bucketsById);
426
+ }
427
+ case "ImageAsset": {
428
+ const bucket = resolveBucketName(typeNode.typeArguments?.[0], sourceFile, bucketAliases, "images");
429
+ return attachStorageFieldMeta({ kind: "image", pgType: "TEXT", bucket }, bucket, bucketsById);
430
+ }
431
+ case "Blocks":
432
+ return {
433
+ kind: "blocks",
434
+ pgType: "JSONB",
435
+ blocks: parseBlocksTypeDefinitions(typeNode.typeArguments?.[0], sourceFile, blockAliases, bucketAliases, bucketsById),
436
+ };
437
+ case "Vector": {
438
+ const dimensions = typeNode.typeArguments?.[0]?.getText(sourceFile);
439
+ return { kind: "vector", pgType: "VECTOR", dimensions: Number(dimensions ?? "1536") };
440
+ }
441
+ default:
442
+ return { kind: "text", pgType: "TEXT" };
443
+ }
444
+ }
445
+ switch (typeNode.kind) {
446
+ case ts.SyntaxKind.StringKeyword:
447
+ return { kind: "text", pgType: "TEXT" };
448
+ case ts.SyntaxKind.NumberKeyword:
449
+ return { kind: "float", pgType: "DOUBLE PRECISION" };
450
+ case ts.SyntaxKind.BooleanKeyword:
451
+ return { kind: "boolean", pgType: "BOOLEAN" };
452
+ default:
453
+ return { kind: "json", pgType: "JSONB" };
454
+ }
455
+ }
456
+ function collectBlockAliases(sourceFile, bucketAliases, bucketsById) {
457
+ const blocks = new Map();
458
+ for (const stmt of sourceFile.statements) {
459
+ if (!ts.isTypeAliasDeclaration(stmt))
460
+ continue;
461
+ if (!ts.isTypeReferenceNode(stmt.type))
462
+ continue;
463
+ if (!ts.isIdentifier(stmt.type.typeName) || stmt.type.typeName.text !== "Block")
464
+ continue;
465
+ const block = parseInlineBlockDefinition(stmt.type, sourceFile, new Map(), bucketAliases, bucketsById);
466
+ if (!block)
467
+ continue;
468
+ blocks.set(stmt.name.text, block);
469
+ }
470
+ return blocks;
471
+ }
472
+ function collectBucketContext(sourceFile) {
473
+ const aliases = new Map();
474
+ const bucketsById = new Map();
475
+ for (const stmt of sourceFile.statements) {
476
+ if (!ts.isTypeAliasDeclaration(stmt))
477
+ continue;
478
+ if (!ts.isTypeReferenceNode(stmt.type))
479
+ continue;
480
+ if (!ts.isIdentifier(stmt.type.typeName) || stmt.type.typeName.text !== "Bucket")
481
+ continue;
482
+ const [nameArg, configArg] = stmt.type.typeArguments ?? [];
483
+ if (!nameArg || !ts.isLiteralTypeNode(nameArg) || !ts.isStringLiteral(nameArg.literal))
484
+ continue;
485
+ const id = nameArg.literal.text;
486
+ aliases.set(stmt.name.text, id);
487
+ const parsed = configArg && ts.isTypeLiteralNode(configArg)
488
+ ? parseBucketTypeLiteral(configArg, sourceFile)
489
+ : {};
490
+ const next = buildExtractedBucketAst(id, parsed);
491
+ const existing = bucketsById.get(id);
492
+ if (existing !== undefined && !bucketsEqual(existing, next)) {
493
+ throw new Error(`Conflicting Bucket<> declarations for id "${id}". Use a single export per bucket id.`);
494
+ }
495
+ bucketsById.set(id, next);
496
+ }
497
+ return { aliases, bucketsById };
498
+ }
499
+ function buildExtractedBucketAst(id, parsed) {
500
+ const mode = parsed.accessMode ?? "private";
501
+ const pub = mode === "public";
502
+ const row = {
503
+ id,
504
+ public: pub,
505
+ accessMode: mode,
506
+ ...(parsed.allowedMimeTypes !== undefined && parsed.allowedMimeTypes.length > 0
507
+ ? { allowedMimeTypes: parsed.allowedMimeTypes }
508
+ : {}),
509
+ ...(parsed.fileSizeLimit !== undefined ? { fileSizeLimit: parsed.fileSizeLimit } : {}),
510
+ ...(parsed.access !== undefined &&
511
+ Object.keys(parsed.access).length > 0 && { access: parsed.access }),
512
+ ...(parsed.s3BucketPolicy !== undefined ? { s3BucketPolicy: parsed.s3BucketPolicy } : {}),
513
+ };
514
+ return row;
515
+ }
516
+ function parseBucketTypeLiteral(lit, sourceFile) {
517
+ const out = {};
518
+ for (const member of lit.members) {
519
+ if (!ts.isPropertySignature(member) || !member.type)
520
+ continue;
521
+ const key = getPropertyName(member.name);
522
+ if (!key)
523
+ continue;
524
+ if (key === "accessMode") {
525
+ const mode = parseAccessModeLiteral(member.type, sourceFile);
526
+ if (mode !== undefined)
527
+ out.accessMode = mode;
528
+ continue;
529
+ }
530
+ if (key === "maxSize") {
531
+ const s = parseSizeStringLiteral(member.type, sourceFile);
532
+ if (s !== undefined) {
533
+ const bytes = parseDataSizeBytes(s);
534
+ out.fileSizeLimit = bytes;
535
+ }
536
+ continue;
537
+ }
538
+ if (key === "accept") {
539
+ const types = parseMimeAcceptList(member.type, sourceFile);
540
+ if (types !== undefined)
541
+ out.allowedMimeTypes = types;
542
+ continue;
543
+ }
544
+ if (key === "access") {
545
+ const acc = parsePartialBucketAccess(member.type, sourceFile);
546
+ if (acc !== undefined && Object.keys(acc).length > 0)
547
+ out.access = acc;
548
+ continue;
549
+ }
550
+ if (key === "s3BucketPolicy") {
551
+ const pol = parseJsonStringLiteral(member.type, sourceFile);
552
+ if (pol !== undefined)
553
+ out.s3BucketPolicy = pol;
554
+ continue;
555
+ }
556
+ }
557
+ return out;
558
+ }
559
+ function parseAccessModeLiteral(typeNode, sourceFile) {
560
+ const text = stripQuotes(typeNode.getText(sourceFile));
561
+ if (text === "public" || text === "private" || text === "custom")
562
+ return text;
563
+ return undefined;
564
+ }
565
+ function parseSizeStringLiteral(typeNode, sourceFile) {
566
+ if (ts.isLiteralTypeNode(typeNode) && ts.isStringLiteral(typeNode.literal)) {
567
+ return typeNode.literal.text;
568
+ }
569
+ return stripQuotes(typeNode.getText(sourceFile)) || undefined;
570
+ }
571
+ function parseJsonStringLiteral(typeNode, sourceFile) {
572
+ return parseSizeStringLiteral(typeNode, sourceFile);
573
+ }
574
+ function parseMimeAcceptList(typeNode, sourceFile) {
575
+ if (ts.isTypeOperatorNode(typeNode) && typeNode.operator === ts.SyntaxKind.ReadonlyKeyword) {
576
+ return parseMimeAcceptList(typeNode.type, sourceFile);
577
+ }
578
+ if (ts.isTupleTypeNode(typeNode)) {
579
+ const values = [];
580
+ for (const el of typeNode.elements) {
581
+ const node = ts.isNamedTupleMember(el) ? el.type : el;
582
+ const s = literalStringType(node);
583
+ if (!s)
584
+ return undefined;
585
+ values.push(s);
586
+ }
587
+ return values.length > 0 ? values : undefined;
588
+ }
589
+ if (ts.isUnionTypeNode(typeNode)) {
590
+ const values = [];
591
+ for (const u of typeNode.types) {
592
+ const s = literalStringType(u);
593
+ if (!s)
594
+ return undefined;
595
+ values.push(s);
596
+ }
597
+ return values.length > 0 ? values : undefined;
598
+ }
599
+ return undefined;
600
+ }
601
+ function parsePartialBucketAccess(typeNode, sourceFile) {
602
+ if (!ts.isTypeLiteralNode(typeNode))
603
+ return undefined;
604
+ const access = {};
605
+ for (const member of typeNode.members) {
606
+ if (!ts.isPropertySignature(member) || !member.type)
607
+ continue;
608
+ const key = getPropertyName(member.name);
609
+ if (key !== "read" && key !== "create" && key !== "delete")
610
+ continue;
611
+ access[key] = parseAccessRule(member.type, sourceFile);
612
+ }
613
+ return access;
614
+ }
615
+ /** Parse human-readable size from schema types, e.g. `50MB`. */
616
+ function parseDataSizeBytes(lit) {
617
+ const m = lit.trim().match(/^(\d+(?:\.\d+)?)\s*(B|KB|MB|GB)$/i);
618
+ if (!m?.[1] || !m[2])
619
+ throw new Error(`Invalid maxSize literal: "${lit}". Use forms like "50MB", "100KB".`);
620
+ const n = Number(m[1]);
621
+ if (!Number.isFinite(n) || n < 0)
622
+ throw new Error(`Invalid maxSize number in: "${lit}"`);
623
+ const pow = {
624
+ B: 0,
625
+ KB: 10,
626
+ MB: 20,
627
+ GB: 30,
628
+ };
629
+ const unit = m[2].toUpperCase();
630
+ const exp = pow[unit];
631
+ if (exp === undefined)
632
+ throw new Error(`Unsupported maxSize unit in: "${lit}"`);
633
+ return Math.round(n * Math.pow(2, exp));
634
+ }
635
+ function stripQuotes(s) {
636
+ return s.replace(/^['"]|['"]$/g, "");
637
+ }
638
+ function bucketsEqual(a, b) {
639
+ return (a.public === b.public &&
640
+ (a.accessMode ?? "private") === (b.accessMode ?? "private") &&
641
+ JSON.stringify(a.access ?? null) === JSON.stringify(b.access ?? null) &&
642
+ JSON.stringify(a.allowedMimeTypes ?? null) === JSON.stringify(b.allowedMimeTypes ?? null) &&
643
+ (a.fileSizeLimit ?? null) === (b.fileSizeLimit ?? null) &&
644
+ (a.s3BucketPolicy ?? null) === (b.s3BucketPolicy ?? null));
645
+ }
646
+ function attachStorageFieldMeta(field, bucketId, bucketsById) {
647
+ const cfg = bucketsById.get(bucketId);
648
+ if (cfg?.accessMode !== undefined) {
649
+ return {
650
+ ...field,
651
+ ...(cfg.accessMode !== undefined && { accessMode: cfg.accessMode }),
652
+ };
653
+ }
654
+ return field;
655
+ }
656
+ function parseBlocksTypeDefinitions(blocksArg, sourceFile, blockAliases, bucketAliases, bucketsById) {
657
+ if (!blocksArg)
658
+ return [];
659
+ const parts = ts.isUnionTypeNode(blocksArg) ? blocksArg.types : [blocksArg];
660
+ const out = [];
661
+ for (const part of parts) {
662
+ if (ts.isTypeReferenceNode(part) && ts.isIdentifier(part.typeName)) {
663
+ if (part.typeName.text === "Block") {
664
+ const inline = parseInlineBlockDefinition(part, sourceFile, blockAliases, bucketAliases, bucketsById);
665
+ if (inline)
666
+ out.push(inline);
667
+ continue;
668
+ }
669
+ const aliased = blockAliases.get(part.typeName.text);
670
+ if (aliased)
671
+ out.push(aliased);
672
+ }
673
+ }
674
+ return out;
675
+ }
676
+ function parseInlineBlockDefinition(ref, sourceFile, blockAliases, bucketAliases, bucketsById) {
677
+ const [nameArg, fieldsArg, metaArg] = ref.typeArguments ?? [];
678
+ const name = literalStringType(nameArg);
679
+ if (!name || !fieldsArg || !ts.isTypeLiteralNode(fieldsArg))
680
+ return null;
681
+ const fields = {};
682
+ for (const member of fieldsArg.members) {
683
+ if (!ts.isPropertySignature(member) || !member.type)
684
+ continue;
685
+ const fieldName = getPropertyName(member.name);
686
+ if (!fieldName)
687
+ continue;
688
+ fields[fieldName] = parseFieldType(fieldName, member.type, sourceFile, blockAliases, bucketAliases, bucketsById);
689
+ }
690
+ let label;
691
+ let icon;
692
+ if (metaArg && ts.isTypeLiteralNode(metaArg)) {
693
+ for (const m of metaArg.members) {
694
+ if (!ts.isPropertySignature(m) || !m.type)
695
+ continue;
696
+ const key = getPropertyName(m.name);
697
+ if (!key)
698
+ continue;
699
+ const value = literalStringType(m.type);
700
+ if (!value)
701
+ continue;
702
+ if (key === "label")
703
+ label = value;
704
+ if (key === "icon")
705
+ icon = value;
706
+ }
707
+ }
708
+ return {
709
+ name,
710
+ ...(label !== undefined && { label }),
711
+ ...(icon !== undefined && { icon }),
712
+ fields,
713
+ };
714
+ }
715
+ function literalStringType(typeNode) {
716
+ if (!typeNode)
717
+ return null;
718
+ if (ts.isLiteralTypeNode(typeNode) && ts.isStringLiteral(typeNode.literal))
719
+ return typeNode.literal.text;
720
+ return null;
721
+ }
722
+ /** Field names referenced in `{name}` and `{truncate(name, n)}` (case-sensitive, same as model fields). */
723
+ function fieldNamesInComputedTemplate(template) {
724
+ const fields = new Set();
725
+ const reTrunc = /\{truncate\s*\(\s*([a-zA-Z_]\w*)\s*,\s*(\d+)\s*\)\}/gi;
726
+ let m;
727
+ while ((m = reTrunc.exec(template)) !== null) {
728
+ const ref = m[1];
729
+ if (ref)
730
+ fields.add(ref);
731
+ }
732
+ const reSimple = /\{([a-zA-Z_]\w*)\}/g;
733
+ while ((m = reSimple.exec(template)) !== null) {
734
+ const ref = m[1];
735
+ if (ref)
736
+ fields.add(ref);
737
+ }
738
+ return [...fields];
739
+ }
740
+ function looksLikeComputedTemplateLiteral(lit) {
741
+ return /\{truncate\s*\(/i.test(lit) || /\{[a-zA-Z_]\w*\}/g.test(lit);
742
+ }
743
+ /** Resolves second type arg of `ComputedFrom<Value, Sources>` — tuple concat, single field, or template literal. */
744
+ function parseComputedFromSecondArg(sourcesArg, sourceFile) {
745
+ if (!sourcesArg)
746
+ return null;
747
+ const single = literalStringType(sourcesArg);
748
+ if (single) {
749
+ if (looksLikeComputedTemplateLiteral(single)) {
750
+ return { sources: fieldNamesInComputedTemplate(single), template: single };
751
+ }
752
+ return { sources: [single] };
753
+ }
754
+ const elemsFromTupleType = (tuple) => {
755
+ const nodes = [];
756
+ for (const el of tuple.elements) {
757
+ if (ts.isNamedTupleMember(el)) {
758
+ if (!el.type)
759
+ return null;
760
+ nodes.push(el.type);
761
+ continue;
762
+ }
763
+ nodes.push(el);
764
+ }
765
+ return nodes;
766
+ };
767
+ const tupleElems = () => {
768
+ if (ts.isTupleTypeNode(sourcesArg))
769
+ return elemsFromTupleType(sourcesArg);
770
+ if (ts.isTypeOperatorNode(sourcesArg) && sourcesArg.operator === ts.SyntaxKind.ReadonlyKeyword) {
771
+ const inner = sourcesArg.type;
772
+ if (inner && ts.isTupleTypeNode(inner))
773
+ return elemsFromTupleType(inner);
774
+ }
775
+ return null;
776
+ };
777
+ const elems = tupleElems();
778
+ if (!elems || elems.length === 0)
779
+ return null;
780
+ const keys = [];
781
+ for (const node of elems) {
782
+ const k = literalStringType(node);
783
+ if (!k)
784
+ return null;
785
+ keys.push(k);
786
+ }
787
+ return { sources: keys };
788
+ }
789
+ function resolveBucketName(typeArg, sourceFile, bucketAliases, fallback) {
790
+ if (!typeArg)
791
+ return fallback;
792
+ if (ts.isTypeReferenceNode(typeArg) && ts.isIdentifier(typeArg.typeName)) {
793
+ return bucketAliases.get(typeArg.typeName.text) ?? typeArg.typeName.text;
794
+ }
795
+ if (ts.isLiteralTypeNode(typeArg) && ts.isStringLiteral(typeArg.literal)) {
796
+ return typeArg.literal.text;
797
+ }
798
+ return typeArg.getText(sourceFile).replace(/^['"]|['"]$/g, "") || fallback;
799
+ }
800
+ function parseModelAccess(metaArg, sourceFile) {
801
+ if (!metaArg || !ts.isTypeLiteralNode(metaArg))
802
+ return {};
803
+ const accessProp = metaArg.members.find((member) => ts.isPropertySignature(member) && getPropertyName(member.name) === "access");
804
+ if (!accessProp || !ts.isPropertySignature(accessProp) || !accessProp.type || !ts.isTypeLiteralNode(accessProp.type)) {
805
+ return {};
806
+ }
807
+ const access = {};
808
+ for (const member of accessProp.type.members) {
809
+ if (!ts.isPropertySignature(member) || !member.type)
810
+ continue;
811
+ const key = getPropertyName(member.name);
812
+ if (!key)
813
+ continue;
814
+ access[key] = parseAccessRule(member.type, sourceFile);
815
+ }
816
+ return access;
817
+ }
818
+ function parseAccessRule(typeNode, sourceFile) {
819
+ if (!ts.isTypeReferenceNode(typeNode))
820
+ return { type: "private" };
821
+ const ref = typeNode.typeName.getText(sourceFile);
822
+ switch (ref) {
823
+ case "Public":
824
+ case "BucketPublic":
825
+ return { type: "public" };
826
+ case "LoggedIn":
827
+ case "BucketLoggedIn":
828
+ return { type: "authenticated" };
829
+ case "Private":
830
+ case "BucketPrivate":
831
+ return { type: "private" };
832
+ case "BucketOwner":
833
+ return { type: "owner", field: "owner_id" };
834
+ case "Owner": {
835
+ const args = typeNode.typeArguments ?? [];
836
+ const keyArg = args.length >= 2 ? args[1] : args[0];
837
+ // Must match engine `AccessRule::Owner { field }` (see supatype-schema-engine parser/ast.rs).
838
+ return { type: "owner", field: keyArg?.getText(sourceFile).replace(/['"]/g, "") ?? "user_id" };
839
+ }
840
+ case "OwnerFrom": {
841
+ const relationArg = typeNode.typeArguments?.[0];
842
+ const relationField = relationArg?.getText(sourceFile).replace(/['"]/g, "") ?? "owner";
843
+ return { type: "owner", field: relationForeignKeyFromField(relationField) };
844
+ }
845
+ case "Role": {
846
+ const roleArg = typeNode.typeArguments?.[0];
847
+ return { type: "role", roles: [roleArg?.getText(sourceFile).replace(/['"]/g, "") ?? "admin"] };
848
+ }
849
+ case "BucketRole": {
850
+ const roleArg = typeNode.typeArguments?.[0];
851
+ return { type: "role", roles: [roleArg?.getText(sourceFile).replace(/['"]/g, "") ?? "admin"] };
852
+ }
853
+ default:
854
+ return { type: "private" };
855
+ }
856
+ }
857
+ function relationTargetFromTypeArg(typeArg, sourceFile) {
858
+ if (!typeArg)
859
+ return "unknown";
860
+ const raw = typeArg.getText(sourceFile).replace(/\s/g, "");
861
+ if (raw === "SupatypeAuthUser")
862
+ return "supatype:user";
863
+ return raw.replace(/\W/g, "");
864
+ }
865
+ function toSnakeCase(s) {
866
+ return s.replace(/([A-Z])/g, "_$1").replace(/^_/, "").toLowerCase();
867
+ }
868
+ function relationForeignKeyFromField(fieldName) {
869
+ const snake = fieldName
870
+ .replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2")
871
+ .replace(/([a-z0-9])([A-Z])/g, "$1_$2")
872
+ .toLowerCase();
873
+ const base = snake.replace(/_id$/i, "");
874
+ return `${base}_id`;
875
+ }
876
+ //# sourceMappingURL=type-extractor.js.map