@aws-blocks/core 0.1.0

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 (394) hide show
  1. package/LICENSE +174 -0
  2. package/dist/api.d.ts +147 -0
  3. package/dist/api.d.ts.map +1 -0
  4. package/dist/api.js +10 -0
  5. package/dist/api.test.d.ts +2 -0
  6. package/dist/api.test.d.ts.map +1 -0
  7. package/dist/api.test.js +19 -0
  8. package/dist/bb-utils.d.ts +11 -0
  9. package/dist/bb-utils.d.ts.map +1 -0
  10. package/dist/bb-utils.js +12 -0
  11. package/dist/builtin-routes.d.ts +6 -0
  12. package/dist/builtin-routes.d.ts.map +1 -0
  13. package/dist/builtin-routes.js +57 -0
  14. package/dist/cdk/__fixtures__/factory-backend.d.ts +3 -0
  15. package/dist/cdk/__fixtures__/factory-backend.d.ts.map +1 -0
  16. package/dist/cdk/__fixtures__/factory-backend.js +6 -0
  17. package/dist/cdk/__fixtures__/fullid-construct-backend.d.ts +2 -0
  18. package/dist/cdk/__fixtures__/fullid-construct-backend.d.ts.map +1 -0
  19. package/dist/cdk/__fixtures__/fullid-construct-backend.js +37 -0
  20. package/dist/cdk/__fixtures__/handler.d.ts +5 -0
  21. package/dist/cdk/__fixtures__/handler.d.ts.map +1 -0
  22. package/dist/cdk/__fixtures__/handler.js +3 -0
  23. package/dist/cdk/__fixtures__/side-effect-backend.d.ts +2 -0
  24. package/dist/cdk/__fixtures__/side-effect-backend.d.ts.map +1 -0
  25. package/dist/cdk/__fixtures__/side-effect-backend.js +5 -0
  26. package/dist/cdk/blocks-backend.d.ts +68 -0
  27. package/dist/cdk/blocks-backend.d.ts.map +1 -0
  28. package/dist/cdk/blocks-backend.js +214 -0
  29. package/dist/cdk/blocks-backend.test.d.ts +2 -0
  30. package/dist/cdk/blocks-backend.test.d.ts.map +1 -0
  31. package/dist/cdk/blocks-backend.test.js +151 -0
  32. package/dist/cdk/blocks-stack.test.d.ts +2 -0
  33. package/dist/cdk/blocks-stack.test.d.ts.map +1 -0
  34. package/dist/cdk/blocks-stack.test.js +78 -0
  35. package/dist/cdk/config-registry.d.ts +26 -0
  36. package/dist/cdk/config-registry.d.ts.map +1 -0
  37. package/dist/cdk/config-registry.js +80 -0
  38. package/dist/cdk/index.d.ts +35 -0
  39. package/dist/cdk/index.d.ts.map +1 -0
  40. package/dist/cdk/index.js +90 -0
  41. package/dist/cdk/mixins.d.ts +28 -0
  42. package/dist/cdk/mixins.d.ts.map +1 -0
  43. package/dist/cdk/mixins.js +37 -0
  44. package/dist/cdk/mixins.test.d.ts +2 -0
  45. package/dist/cdk/mixins.test.d.ts.map +1 -0
  46. package/dist/cdk/mixins.test.js +108 -0
  47. package/dist/cdk/node-version.d.ts +14 -0
  48. package/dist/cdk/node-version.d.ts.map +1 -0
  49. package/dist/cdk/node-version.js +15 -0
  50. package/dist/cdk/stack-metadata.d.ts +12 -0
  51. package/dist/cdk/stack-metadata.d.ts.map +1 -0
  52. package/dist/cdk/stack-metadata.js +22 -0
  53. package/dist/cdk/stack-metadata.test.d.ts +2 -0
  54. package/dist/cdk/stack-metadata.test.d.ts.map +1 -0
  55. package/dist/cdk/stack-metadata.test.js +56 -0
  56. package/dist/cdk/synth-guard.d.ts +14 -0
  57. package/dist/cdk/synth-guard.d.ts.map +1 -0
  58. package/dist/cdk/synth-guard.js +20 -0
  59. package/dist/cdk/synth-guard.test.d.ts +2 -0
  60. package/dist/cdk/synth-guard.test.d.ts.map +1 -0
  61. package/dist/cdk/synth-guard.test.js +15 -0
  62. package/dist/client/index.d.ts +97 -0
  63. package/dist/client/index.d.ts.map +1 -0
  64. package/dist/client/index.js +238 -0
  65. package/dist/client/index.test.d.ts +2 -0
  66. package/dist/client/index.test.d.ts.map +1 -0
  67. package/dist/client/index.test.js +113 -0
  68. package/dist/common/config.d.ts +45 -0
  69. package/dist/common/config.d.ts.map +1 -0
  70. package/dist/common/config.js +154 -0
  71. package/dist/common/config.test.d.ts +2 -0
  72. package/dist/common/config.test.d.ts.map +1 -0
  73. package/dist/common/config.test.js +173 -0
  74. package/dist/common/constants.d.ts +11 -0
  75. package/dist/common/constants.d.ts.map +1 -0
  76. package/dist/common/constants.js +12 -0
  77. package/dist/common/crypto.d.ts +6 -0
  78. package/dist/common/crypto.d.ts.map +1 -0
  79. package/dist/common/crypto.js +14 -0
  80. package/dist/common/index.d.ts +220 -0
  81. package/dist/common/index.d.ts.map +1 -0
  82. package/dist/common/index.js +180 -0
  83. package/dist/common/index.test.d.ts +2 -0
  84. package/dist/common/index.test.d.ts.map +1 -0
  85. package/dist/common/index.test.js +195 -0
  86. package/dist/common/mock-data.d.ts +40 -0
  87. package/dist/common/mock-data.d.ts.map +1 -0
  88. package/dist/common/mock-data.js +39 -0
  89. package/dist/common/official-bb-names.generated.d.ts +7 -0
  90. package/dist/common/official-bb-names.generated.d.ts.map +1 -0
  91. package/dist/common/official-bb-names.generated.js +28 -0
  92. package/dist/common/sdk-registry.d.ts +136 -0
  93. package/dist/common/sdk-registry.d.ts.map +1 -0
  94. package/dist/common/sdk-registry.js +125 -0
  95. package/dist/constants.d.ts +27 -0
  96. package/dist/constants.d.ts.map +1 -0
  97. package/dist/constants.js +28 -0
  98. package/dist/cors.d.ts +39 -0
  99. package/dist/cors.d.ts.map +1 -0
  100. package/dist/cors.js +85 -0
  101. package/dist/cors.test.d.ts +2 -0
  102. package/dist/cors.test.d.ts.map +1 -0
  103. package/dist/cors.test.js +241 -0
  104. package/dist/db-naming.d.ts +21 -0
  105. package/dist/db-naming.d.ts.map +1 -0
  106. package/dist/db-naming.js +38 -0
  107. package/dist/db-naming.test.d.ts +2 -0
  108. package/dist/db-naming.test.d.ts.map +1 -0
  109. package/dist/db-naming.test.js +29 -0
  110. package/dist/errors.d.ts +67 -0
  111. package/dist/errors.d.ts.map +1 -0
  112. package/dist/errors.js +69 -0
  113. package/dist/hosting.d.ts +258 -0
  114. package/dist/hosting.d.ts.map +1 -0
  115. package/dist/hosting.js +340 -0
  116. package/dist/hosting.test.d.ts +2 -0
  117. package/dist/hosting.test.d.ts.map +1 -0
  118. package/dist/hosting.test.js +1077 -0
  119. package/dist/index.cdk.d.ts +13 -0
  120. package/dist/index.cdk.d.ts.map +1 -0
  121. package/dist/index.cdk.js +13 -0
  122. package/dist/index.d.ts +9 -0
  123. package/dist/index.d.ts.map +1 -0
  124. package/dist/index.js +10 -0
  125. package/dist/lambda-handler.d.ts +131 -0
  126. package/dist/lambda-handler.d.ts.map +1 -0
  127. package/dist/lambda-handler.js +610 -0
  128. package/dist/lambda-handler.test.d.ts +2 -0
  129. package/dist/lambda-handler.test.d.ts.map +1 -0
  130. package/dist/lambda-handler.test.js +835 -0
  131. package/dist/pipeline/index.d.ts +4 -0
  132. package/dist/pipeline/index.d.ts.map +1 -0
  133. package/dist/pipeline/index.js +3 -0
  134. package/dist/pipeline/pipeline-construct.d.ts +130 -0
  135. package/dist/pipeline/pipeline-construct.d.ts.map +1 -0
  136. package/dist/pipeline/pipeline-construct.js +484 -0
  137. package/dist/pipeline/pipeline.test.d.ts +2 -0
  138. package/dist/pipeline/pipeline.test.d.ts.map +1 -0
  139. package/dist/pipeline/pipeline.test.js +1231 -0
  140. package/dist/pipeline/types.d.ts +351 -0
  141. package/dist/pipeline/types.d.ts.map +1 -0
  142. package/dist/pipeline/types.js +3 -0
  143. package/dist/raw-route.cdk.d.ts +47 -0
  144. package/dist/raw-route.cdk.d.ts.map +1 -0
  145. package/dist/raw-route.cdk.js +51 -0
  146. package/dist/raw-route.d.ts +119 -0
  147. package/dist/raw-route.d.ts.map +1 -0
  148. package/dist/raw-route.js +239 -0
  149. package/dist/raw-route.mock.d.ts +47 -0
  150. package/dist/raw-route.mock.d.ts.map +1 -0
  151. package/dist/raw-route.mock.js +51 -0
  152. package/dist/raw-route.test.d.ts +2 -0
  153. package/dist/raw-route.test.d.ts.map +1 -0
  154. package/dist/raw-route.test.js +597 -0
  155. package/dist/redact.d.ts +51 -0
  156. package/dist/redact.d.ts.map +1 -0
  157. package/dist/redact.js +134 -0
  158. package/dist/redact.test.d.ts +2 -0
  159. package/dist/redact.test.d.ts.map +1 -0
  160. package/dist/redact.test.js +199 -0
  161. package/dist/rpc.d.ts +54 -0
  162. package/dist/rpc.d.ts.map +1 -0
  163. package/dist/rpc.js +127 -0
  164. package/dist/rpc.test.d.ts +2 -0
  165. package/dist/rpc.test.d.ts.map +1 -0
  166. package/dist/rpc.test.js +60 -0
  167. package/dist/scripts/cdk-telemetry-env.d.ts +13 -0
  168. package/dist/scripts/cdk-telemetry-env.d.ts.map +1 -0
  169. package/dist/scripts/cdk-telemetry-env.js +21 -0
  170. package/dist/scripts/cdk-telemetry-env.test.d.ts +2 -0
  171. package/dist/scripts/cdk-telemetry-env.test.d.ts.map +1 -0
  172. package/dist/scripts/cdk-telemetry-env.test.js +56 -0
  173. package/dist/scripts/cleanup.d.ts +3 -0
  174. package/dist/scripts/cleanup.d.ts.map +1 -0
  175. package/dist/scripts/cleanup.js +35 -0
  176. package/dist/scripts/console.d.ts +6 -0
  177. package/dist/scripts/console.d.ts.map +1 -0
  178. package/dist/scripts/console.js +25 -0
  179. package/dist/scripts/deploy.d.ts +6 -0
  180. package/dist/scripts/deploy.d.ts.map +1 -0
  181. package/dist/scripts/deploy.js +82 -0
  182. package/dist/scripts/destroy.d.ts +6 -0
  183. package/dist/scripts/destroy.d.ts.map +1 -0
  184. package/dist/scripts/destroy.js +30 -0
  185. package/dist/scripts/dev-server-cors.test.d.ts +2 -0
  186. package/dist/scripts/dev-server-cors.test.d.ts.map +1 -0
  187. package/dist/scripts/dev-server-cors.test.js +23 -0
  188. package/dist/scripts/dev-server-telemetry.test.d.ts +2 -0
  189. package/dist/scripts/dev-server-telemetry.test.d.ts.map +1 -0
  190. package/dist/scripts/dev-server-telemetry.test.js +113 -0
  191. package/dist/scripts/dev-server.d.ts +21 -0
  192. package/dist/scripts/dev-server.d.ts.map +1 -0
  193. package/dist/scripts/dev-server.js +456 -0
  194. package/dist/scripts/ensure-secrets.d.ts +31 -0
  195. package/dist/scripts/ensure-secrets.d.ts.map +1 -0
  196. package/dist/scripts/ensure-secrets.js +100 -0
  197. package/dist/scripts/ensure-secrets.test.d.ts +2 -0
  198. package/dist/scripts/ensure-secrets.test.d.ts.map +1 -0
  199. package/dist/scripts/ensure-secrets.test.js +44 -0
  200. package/dist/scripts/external-migrations-step.d.ts +62 -0
  201. package/dist/scripts/external-migrations-step.d.ts.map +1 -0
  202. package/dist/scripts/external-migrations-step.js +243 -0
  203. package/dist/scripts/external-migrations-step.test.d.ts +2 -0
  204. package/dist/scripts/external-migrations-step.test.d.ts.map +1 -0
  205. package/dist/scripts/external-migrations-step.test.js +47 -0
  206. package/dist/scripts/extract-ts-types.d.ts +65 -0
  207. package/dist/scripts/extract-ts-types.d.ts.map +1 -0
  208. package/dist/scripts/extract-ts-types.js +664 -0
  209. package/dist/scripts/extract-ts-types.test.d.ts +2 -0
  210. package/dist/scripts/extract-ts-types.test.d.ts.map +1 -0
  211. package/dist/scripts/extract-ts-types.test.js +497 -0
  212. package/dist/scripts/generate-client-worker.d.ts +2 -0
  213. package/dist/scripts/generate-client-worker.d.ts.map +1 -0
  214. package/dist/scripts/generate-client-worker.js +13 -0
  215. package/dist/scripts/generate-client.d.ts +14 -0
  216. package/dist/scripts/generate-client.d.ts.map +1 -0
  217. package/dist/scripts/generate-client.js +73 -0
  218. package/dist/scripts/generate-client.test.d.ts +2 -0
  219. package/dist/scripts/generate-client.test.d.ts.map +1 -0
  220. package/dist/scripts/generate-client.test.js +48 -0
  221. package/dist/scripts/generate-spec-cli.d.ts +3 -0
  222. package/dist/scripts/generate-spec-cli.d.ts.map +1 -0
  223. package/dist/scripts/generate-spec-cli.js +77 -0
  224. package/dist/scripts/generate-spec-cli.test.d.ts +14 -0
  225. package/dist/scripts/generate-spec-cli.test.d.ts.map +1 -0
  226. package/dist/scripts/generate-spec-cli.test.js +90 -0
  227. package/dist/scripts/generate-spec.d.ts +61 -0
  228. package/dist/scripts/generate-spec.d.ts.map +1 -0
  229. package/dist/scripts/generate-spec.js +746 -0
  230. package/dist/scripts/generate-spec.test.d.ts +12 -0
  231. package/dist/scripts/generate-spec.test.d.ts.map +1 -0
  232. package/dist/scripts/generate-spec.test.js +88 -0
  233. package/dist/scripts/index.d.ts +12 -0
  234. package/dist/scripts/index.d.ts.map +1 -0
  235. package/dist/scripts/index.js +13 -0
  236. package/dist/scripts/sandbox.d.ts +11 -0
  237. package/dist/scripts/sandbox.d.ts.map +1 -0
  238. package/dist/scripts/sandbox.js +200 -0
  239. package/dist/scripts/telemetry-cli.d.ts +3 -0
  240. package/dist/scripts/telemetry-cli.d.ts.map +1 -0
  241. package/dist/scripts/telemetry-cli.js +8 -0
  242. package/dist/scripts/telemetry.d.ts +17 -0
  243. package/dist/scripts/telemetry.d.ts.map +1 -0
  244. package/dist/scripts/telemetry.js +150 -0
  245. package/dist/scripts/telemetry.test.d.ts +2 -0
  246. package/dist/scripts/telemetry.test.d.ts.map +1 -0
  247. package/dist/scripts/telemetry.test.js +203 -0
  248. package/dist/scripts/validate-spec.d.ts +22 -0
  249. package/dist/scripts/validate-spec.d.ts.map +1 -0
  250. package/dist/scripts/validate-spec.js +159 -0
  251. package/dist/server/index.d.ts +3 -0
  252. package/dist/server/index.d.ts.map +1 -0
  253. package/dist/server/index.js +3 -0
  254. package/dist/server/withAuth.d.ts +142 -0
  255. package/dist/server/withAuth.d.ts.map +1 -0
  256. package/dist/server/withAuth.js +261 -0
  257. package/dist/server/withAuth.test.d.ts +2 -0
  258. package/dist/server/withAuth.test.d.ts.map +1 -0
  259. package/dist/server/withAuth.test.js +278 -0
  260. package/dist/telemetry/client.d.ts +59 -0
  261. package/dist/telemetry/client.d.ts.map +1 -0
  262. package/dist/telemetry/client.js +215 -0
  263. package/dist/telemetry/config-writer.d.ts +16 -0
  264. package/dist/telemetry/config-writer.d.ts.map +1 -0
  265. package/dist/telemetry/config-writer.js +36 -0
  266. package/dist/telemetry/consent-commands.test.d.ts +2 -0
  267. package/dist/telemetry/consent-commands.test.d.ts.map +1 -0
  268. package/dist/telemetry/consent-commands.test.js +161 -0
  269. package/dist/telemetry/consent.d.ts +60 -0
  270. package/dist/telemetry/consent.d.ts.map +1 -0
  271. package/dist/telemetry/consent.js +122 -0
  272. package/dist/telemetry/environment.d.ts +33 -0
  273. package/dist/telemetry/environment.d.ts.map +1 -0
  274. package/dist/telemetry/environment.js +87 -0
  275. package/dist/telemetry/identifiers.d.ts +20 -0
  276. package/dist/telemetry/identifiers.d.ts.map +1 -0
  277. package/dist/telemetry/identifiers.js +86 -0
  278. package/dist/telemetry/index.d.ts +6 -0
  279. package/dist/telemetry/index.d.ts.map +1 -0
  280. package/dist/telemetry/index.js +4 -0
  281. package/dist/telemetry/telemetry.test.d.ts +2 -0
  282. package/dist/telemetry/telemetry.test.d.ts.map +1 -0
  283. package/dist/telemetry/telemetry.test.js +1131 -0
  284. package/dist/telemetry/trackCommand.d.ts +32 -0
  285. package/dist/telemetry/trackCommand.d.ts.map +1 -0
  286. package/dist/telemetry/trackCommand.js +102 -0
  287. package/dist/telemetry/types.d.ts +92 -0
  288. package/dist/telemetry/types.d.ts.map +1 -0
  289. package/dist/telemetry/types.js +1 -0
  290. package/dist/types.d.ts +24 -0
  291. package/dist/types.d.ts.map +1 -0
  292. package/dist/types.js +3 -0
  293. package/dist/version.d.ts +2 -0
  294. package/dist/version.d.ts.map +1 -0
  295. package/dist/version.js +2 -0
  296. package/package.json +83 -0
  297. package/src/api.test.ts +25 -0
  298. package/src/api.ts +163 -0
  299. package/src/bb-utils.ts +13 -0
  300. package/src/builtin-routes.ts +62 -0
  301. package/src/cdk/__fixtures__/factory-backend.ts +8 -0
  302. package/src/cdk/__fixtures__/fullid-construct-backend.ts +42 -0
  303. package/src/cdk/__fixtures__/handler.ts +4 -0
  304. package/src/cdk/__fixtures__/side-effect-backend.ts +7 -0
  305. package/src/cdk/blocks-backend.test.ts +207 -0
  306. package/src/cdk/blocks-backend.ts +249 -0
  307. package/src/cdk/blocks-stack.test.ts +106 -0
  308. package/src/cdk/config-registry.ts +106 -0
  309. package/src/cdk/index.ts +115 -0
  310. package/src/cdk/mixins.test.ts +129 -0
  311. package/src/cdk/mixins.ts +41 -0
  312. package/src/cdk/node-version.ts +17 -0
  313. package/src/cdk/stack-metadata.test.ts +71 -0
  314. package/src/cdk/stack-metadata.ts +29 -0
  315. package/src/cdk/synth-guard.test.ts +21 -0
  316. package/src/cdk/synth-guard.ts +23 -0
  317. package/src/client/index.test.ts +118 -0
  318. package/src/client/index.ts +320 -0
  319. package/src/common/config.test.ts +236 -0
  320. package/src/common/config.ts +169 -0
  321. package/src/common/constants.ts +15 -0
  322. package/src/common/crypto.ts +15 -0
  323. package/src/common/index.test.ts +242 -0
  324. package/src/common/index.ts +335 -0
  325. package/src/common/mock-data.ts +52 -0
  326. package/src/common/official-bb-names.generated.ts +29 -0
  327. package/src/common/sdk-registry.ts +150 -0
  328. package/src/constants.ts +31 -0
  329. package/src/cors.test.ts +279 -0
  330. package/src/cors.ts +91 -0
  331. package/src/db-naming.test.ts +48 -0
  332. package/src/db-naming.ts +41 -0
  333. package/src/errors.ts +72 -0
  334. package/src/hosting.test.ts +1431 -0
  335. package/src/hosting.ts +626 -0
  336. package/src/index.cdk.ts +45 -0
  337. package/src/index.ts +22 -0
  338. package/src/lambda-handler.test.ts +1005 -0
  339. package/src/lambda-handler.ts +706 -0
  340. package/src/pipeline/index.ts +12 -0
  341. package/src/pipeline/pipeline-construct.ts +662 -0
  342. package/src/pipeline/pipeline.test.ts +1565 -0
  343. package/src/pipeline/types.ts +384 -0
  344. package/src/raw-route.cdk.ts +56 -0
  345. package/src/raw-route.mock.ts +56 -0
  346. package/src/raw-route.test.ts +819 -0
  347. package/src/raw-route.ts +316 -0
  348. package/src/redact.test.ts +222 -0
  349. package/src/redact.ts +143 -0
  350. package/src/rpc.test.ts +73 -0
  351. package/src/rpc.ts +174 -0
  352. package/src/scripts/cdk-telemetry-env.test.ts +75 -0
  353. package/src/scripts/cdk-telemetry-env.ts +23 -0
  354. package/src/scripts/cleanup.ts +39 -0
  355. package/src/scripts/console.ts +34 -0
  356. package/src/scripts/deploy.ts +106 -0
  357. package/src/scripts/destroy.ts +42 -0
  358. package/src/scripts/dev-server-cors.test.ts +28 -0
  359. package/src/scripts/dev-server-telemetry.test.ts +127 -0
  360. package/src/scripts/dev-server.ts +515 -0
  361. package/src/scripts/ensure-secrets.test.ts +50 -0
  362. package/src/scripts/ensure-secrets.ts +113 -0
  363. package/src/scripts/external-migrations-step.test.ts +64 -0
  364. package/src/scripts/external-migrations-step.ts +269 -0
  365. package/src/scripts/extract-ts-types.test.ts +516 -0
  366. package/src/scripts/extract-ts-types.ts +773 -0
  367. package/src/scripts/generate-client-worker.ts +15 -0
  368. package/src/scripts/generate-client.test.ts +69 -0
  369. package/src/scripts/generate-client.ts +86 -0
  370. package/src/scripts/generate-spec-cli.test.ts +106 -0
  371. package/src/scripts/generate-spec-cli.ts +92 -0
  372. package/src/scripts/generate-spec.test.ts +99 -0
  373. package/src/scripts/generate-spec.ts +833 -0
  374. package/src/scripts/index.ts +21 -0
  375. package/src/scripts/sandbox.ts +230 -0
  376. package/src/scripts/telemetry-cli.ts +10 -0
  377. package/src/scripts/telemetry.test.ts +239 -0
  378. package/src/scripts/telemetry.ts +173 -0
  379. package/src/scripts/validate-spec.ts +199 -0
  380. package/src/server/index.ts +5 -0
  381. package/src/server/withAuth.test.ts +348 -0
  382. package/src/server/withAuth.ts +323 -0
  383. package/src/telemetry/client.ts +226 -0
  384. package/src/telemetry/config-writer.ts +52 -0
  385. package/src/telemetry/consent-commands.test.ts +196 -0
  386. package/src/telemetry/consent.ts +154 -0
  387. package/src/telemetry/environment.ts +100 -0
  388. package/src/telemetry/identifiers.ts +94 -0
  389. package/src/telemetry/index.ts +11 -0
  390. package/src/telemetry/telemetry.test.ts +1343 -0
  391. package/src/telemetry/trackCommand.ts +110 -0
  392. package/src/telemetry/types.ts +92 -0
  393. package/src/types.ts +33 -0
  394. package/src/version.ts +2 -0
@@ -0,0 +1,773 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ /**
5
+ * Extract TypeScript type information from ApiNamespace method signatures.
6
+ *
7
+ * Uses `ts.createProgram` with a full type checker to extract both explicit
8
+ * and inferred types — including return types that have no annotation.
9
+ * Converts TS types to JSON Schema for the OpenRPC spec.
10
+ *
11
+ * Zod-validated methods still take priority in generate-spec.ts — this
12
+ * module provides the fallback for plain TS methods so they emit real
13
+ * types instead of { type: "unknown" }.
14
+ */
15
+
16
+ import * as ts from 'typescript';
17
+ import { dirname, resolve } from 'path';
18
+ import { existsSync } from 'fs';
19
+
20
+ // ── Public types ────────────────────────────────────────────────────────────
21
+
22
+ export interface TransferableInfo {
23
+ /** The __blocks literal value, e.g. "realtime/channel" */
24
+ blocksTag: string;
25
+ /** JSON Schemas of the generic type arguments (e.g., [T] in RealtimeChannel<T>), empty array if non-generic */
26
+ typeArgs: JsonSchema[];
27
+ }
28
+
29
+ export interface MethodTypeInfo {
30
+ params: ParamTypeInfo[];
31
+ returnType: JsonSchema;
32
+ /** Present when the return type is a transferable type */
33
+ transferable?: TransferableInfo;
34
+ /**
35
+ * True when the method's JSDoc carries a `@blocksSkipCodegen` tag. Methods so
36
+ * marked are still callable through the TS client (the runtime Proxy in
37
+ * `generate-client.ts` doesn't enumerate methods), but the OpenRPC spec
38
+ * emitter drops them, so native generators (Swift / Kotlin) never see
39
+ * them. Use for mock-only / dev-only helpers like `api.getLastCode()`.
40
+ */
41
+ skipCodegen?: boolean;
42
+ }
43
+
44
+ /**
45
+ * Public so `generate-spec.ts` and any future emitter can share the same
46
+ * vocabulary. Spelt as a single token to avoid hyphenation surprises in
47
+ * JSDoc parsers.
48
+ */
49
+ export const BLOCKS_SKIP_CODEGEN_TAG = 'blocksSkipCodegen';
50
+
51
+ function hasKitSkipCodegenTag(node: ts.Node): boolean {
52
+ for (const tag of ts.getJSDocTags(node)) {
53
+ if (tag.tagName.text === BLOCKS_SKIP_CODEGEN_TAG) return true;
54
+ }
55
+ return false;
56
+ }
57
+
58
+ /**
59
+ * Pure-AST scan for method names tagged with `@blocksSkipCodegen` inside the
60
+ * `ApiNamespace(...)` calls of a TS source file. Returns a Set of method
61
+ * names (the OpenRPC emitter qualifies them by namespace later).
62
+ *
63
+ * Distinct from {@link extractMethodTypes} because that function builds a
64
+ * full `ts.Program` + type checker, and any failure inside type
65
+ * resolution (an imported module that doesn't typecheck, a third-party
66
+ * `.d.ts` mismatch) silently empties the returned Map. The skip set must
67
+ * survive those crashes, so this function uses only the parser.
68
+ */
69
+ export function extractSkipCodegenMethods(sourcePath: string): Set<string> {
70
+ const result = new Set<string>();
71
+ const absPath = resolve(sourcePath);
72
+ const fileText = ts.sys.readFile(absPath);
73
+ if (!fileText) return result;
74
+
75
+ const sourceFile = ts.createSourceFile(
76
+ absPath,
77
+ fileText,
78
+ ts.ScriptTarget.ESNext,
79
+ /*setParentNodes*/ true,
80
+ ts.ScriptKind.TS,
81
+ );
82
+
83
+ function visitObjectLiteral(obj: ts.ObjectLiteralExpression) {
84
+ for (const prop of obj.properties) {
85
+ if (
86
+ ts.isMethodDeclaration(prop) &&
87
+ prop.name &&
88
+ ts.isIdentifier(prop.name) &&
89
+ hasKitSkipCodegenTag(prop)
90
+ ) {
91
+ result.add(prop.name.text);
92
+ }
93
+ }
94
+ }
95
+
96
+ function visit(node: ts.Node) {
97
+ if (ts.isNewExpression(node)) {
98
+ const callee = node.expression;
99
+ if (ts.isIdentifier(callee) && callee.text === 'ApiNamespace' && node.arguments && node.arguments.length >= 2) {
100
+ const handler = node.arguments[node.arguments.length - 1];
101
+ if (ts.isArrowFunction(handler) || ts.isFunctionExpression(handler)) {
102
+ const body = handler.body;
103
+ if (ts.isParenthesizedExpression(body) && ts.isObjectLiteralExpression(body.expression)) {
104
+ visitObjectLiteral(body.expression);
105
+ } else if (ts.isObjectLiteralExpression(body)) {
106
+ visitObjectLiteral(body);
107
+ } else if (ts.isBlock(body)) {
108
+ for (const stmt of body.statements) {
109
+ if (ts.isReturnStatement(stmt) && stmt.expression) {
110
+ if (ts.isObjectLiteralExpression(stmt.expression)) {
111
+ visitObjectLiteral(stmt.expression);
112
+ } else if (
113
+ ts.isParenthesizedExpression(stmt.expression) &&
114
+ ts.isObjectLiteralExpression(stmt.expression.expression)
115
+ ) {
116
+ visitObjectLiteral(stmt.expression.expression);
117
+ }
118
+ }
119
+ }
120
+ }
121
+ }
122
+ }
123
+ }
124
+ ts.forEachChild(node, visit);
125
+ }
126
+
127
+ visit(sourceFile);
128
+ return result;
129
+ }
130
+
131
+ export interface ParamTypeInfo {
132
+ name: string;
133
+ required: boolean;
134
+ schema: JsonSchema;
135
+ }
136
+
137
+ export interface JsonSchema {
138
+ type?: string;
139
+ properties?: Record<string, JsonSchema>;
140
+ items?: JsonSchema | JsonSchema[];
141
+ required?: string[];
142
+ $ref?: string;
143
+ nullable?: boolean;
144
+ enum?: (string | number | boolean | null)[];
145
+ oneOf?: JsonSchema[];
146
+ additionalProperties?: JsonSchema | boolean;
147
+ title?: string;
148
+ [key: string]: unknown;
149
+ }
150
+
151
+ // ── Main entry point ────────────────────────────────────────────────────────
152
+
153
+ /**
154
+ * Parse a TypeScript source file with full type checking and extract type
155
+ * info for all methods inside `ApiNamespace(...)` calls.
156
+ *
157
+ * Uses `ts.createProgram` to resolve imports and infer return types.
158
+ * Falls back to AST-only parsing if program creation fails.
159
+ */
160
+ export function extractMethodTypes(sourcePath: string): Map<string, MethodTypeInfo> {
161
+ const result = new Map<string, MethodTypeInfo>();
162
+ const absPath = resolve(sourcePath);
163
+
164
+ // Find tsconfig.json by walking up from the source file
165
+ const configPath = findTsConfig(dirname(absPath));
166
+
167
+ let program: ts.Program;
168
+ let checker: ts.TypeChecker;
169
+ let sourceFile: ts.SourceFile | undefined;
170
+
171
+ if (configPath) {
172
+ const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
173
+ const parsed = ts.parseJsonConfigFileContent(configFile.config, ts.sys, dirname(configPath));
174
+ // Only include our target file — the compiler resolves imports on demand.
175
+ // This is much faster than passing the full fileNames list from tsconfig.
176
+ const options = { ...parsed.options, skipLibCheck: true, skipDefaultLibCheck: true };
177
+ program = ts.createProgram([absPath], options);
178
+ } else {
179
+ // No tsconfig — create a minimal program
180
+ program = ts.createProgram([absPath], {
181
+ target: ts.ScriptTarget.ESNext,
182
+ module: ts.ModuleKind.ESNext,
183
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
184
+ strict: true,
185
+ esModuleInterop: true,
186
+ skipLibCheck: true,
187
+ });
188
+ }
189
+
190
+ checker = program.getTypeChecker();
191
+ sourceFile = program.getSourceFile(absPath);
192
+ if (!sourceFile) return result;
193
+
194
+ // Find new ApiNamespace(scope, name, handler) and extract methods using the type checker
195
+ ts.forEachChild(sourceFile, function visit(node) {
196
+ if (ts.isNewExpression(node)) {
197
+ const fnName = node.expression;
198
+ if (ts.isIdentifier(fnName) && fnName.text === 'ApiNamespace' && node.arguments && node.arguments.length >= 2) {
199
+ const handlerArg = node.arguments[node.arguments.length - 1];
200
+ extractMethodsFromHandler(handlerArg, checker, sourceFile!, result);
201
+ }
202
+ }
203
+ ts.forEachChild(node, visit);
204
+ });
205
+
206
+ // Second pass: resolve types for indirect ApiNamespace exports (e.g., auth.createApi()).
207
+ // The AST walk above only finds `new ApiNamespace(...)` in this file.
208
+ // For methods created by Building Block helpers in other files, use the type
209
+ // checker to resolve the call's return type and extract method signatures.
210
+ ts.forEachChild(sourceFile, (node) => {
211
+ if (!ts.isVariableStatement(node)) return;
212
+ for (const decl of node.declarationList.declarations) {
213
+ if (!decl.initializer || !ts.isIdentifier(decl.name)) continue;
214
+ // Skip if we already extracted methods for this via the AST walk
215
+ if (ts.isNewExpression(decl.initializer)) {
216
+ const callee = decl.initializer.expression;
217
+ if (ts.isIdentifier(callee) && callee.text === 'ApiNamespace') continue;
218
+ }
219
+
220
+ // Use the type checker to get the type of the initializer expression.
221
+ // For `auth.createApi()`, this resolves through the method's return type
222
+ // to the `AsyncAPI<T>` type that ApiNamespace returns.
223
+ const initType = checker.getTypeAtLocation(decl.initializer);
224
+ extractMethodsFromResolvedType(initType, checker, result);
225
+ }
226
+ });
227
+
228
+ return result;
229
+ }
230
+
231
+ // ── tsconfig discovery ──────────────────────────────────────────────────────
232
+
233
+ function findTsConfig(dir: string): string | undefined {
234
+ let current = dir;
235
+ for (let i = 0; i < 10; i++) {
236
+ const candidate = resolve(current, 'tsconfig.json');
237
+ if (existsSync(candidate)) return candidate;
238
+ const parent = dirname(current);
239
+ if (parent === current) break;
240
+ current = parent;
241
+ }
242
+ return undefined;
243
+ }
244
+
245
+ // ── Handler extraction ──────────────────────────────────────────────────────
246
+
247
+ function extractMethodsFromHandler(
248
+ handlerNode: ts.Node,
249
+ checker: ts.TypeChecker,
250
+ sourceFile: ts.SourceFile,
251
+ result: Map<string, MethodTypeInfo>,
252
+ ): void {
253
+ let objectLiteral: ts.ObjectLiteralExpression | undefined;
254
+
255
+ if (ts.isArrowFunction(handlerNode) || ts.isFunctionExpression(handlerNode)) {
256
+ const body = handlerNode.body;
257
+ if (ts.isParenthesizedExpression(body)) {
258
+ if (ts.isObjectLiteralExpression(body.expression)) {
259
+ objectLiteral = body.expression;
260
+ }
261
+ } else if (ts.isObjectLiteralExpression(body)) {
262
+ objectLiteral = body;
263
+ } else if (ts.isBlock(body)) {
264
+ for (const stmt of body.statements) {
265
+ if (ts.isReturnStatement(stmt) && stmt.expression) {
266
+ if (ts.isObjectLiteralExpression(stmt.expression)) {
267
+ objectLiteral = stmt.expression;
268
+ } else if (ts.isParenthesizedExpression(stmt.expression) &&
269
+ ts.isObjectLiteralExpression(stmt.expression.expression)) {
270
+ objectLiteral = stmt.expression.expression;
271
+ }
272
+ }
273
+ }
274
+ }
275
+ }
276
+
277
+ if (!objectLiteral) return;
278
+
279
+ for (const prop of objectLiteral.properties) {
280
+ if (ts.isMethodDeclaration(prop) && prop.name && ts.isIdentifier(prop.name)) {
281
+ const methodName = prop.name.text;
282
+ const info = extractMethodTypeInfo(prop, checker, sourceFile);
283
+ if (info) result.set(methodName, info);
284
+ }
285
+ }
286
+ }
287
+
288
+ function extractMethodTypeInfo(
289
+ method: ts.MethodDeclaration,
290
+ checker: ts.TypeChecker,
291
+ sourceFile: ts.SourceFile,
292
+ ): MethodTypeInfo | null {
293
+ const params: ParamTypeInfo[] = [];
294
+
295
+ for (const param of method.parameters) {
296
+ const name = ts.isIdentifier(param.name) ? param.name.text
297
+ : param.name.getText(sourceFile);
298
+ const required = !param.questionToken && !param.initializer;
299
+
300
+ // Use the type checker to get the resolved type (handles imported types, aliases, etc.)
301
+ const symbol = checker.getSymbolAtLocation(param.name);
302
+ let schema: JsonSchema = { type: 'unknown' };
303
+ if (symbol) {
304
+ const paramType = checker.getTypeOfSymbolAtLocation(symbol, param);
305
+ schema = tsTypeToJsonSchema(paramType, checker);
306
+ } else if (param.type) {
307
+ // Fallback to AST-based extraction
308
+ schema = typeNodeToJsonSchema(param.type, sourceFile);
309
+ }
310
+ params.push({ name, required, schema });
311
+ }
312
+
313
+ // Return type: use the type checker to get the inferred return type
314
+ let returnType: JsonSchema = { type: 'unknown' };
315
+ let transferable: TransferableInfo | undefined;
316
+ const signature = checker.getSignatureFromDeclaration(method);
317
+ if (signature) {
318
+ let retType = checker.getReturnTypeOfSignature(signature);
319
+ // Unwrap Promise<T>
320
+ retType = unwrapPromise(retType, checker);
321
+ // Check if the return type is a transferable before converting to schema.
322
+ // Pass the syntactic return-type node so we can distinguish between
323
+ // `RealtimeChannel` (default `T = unknown` filled in) and
324
+ // `RealtimeChannel<unknown>` (explicit) — the resolved `ts.Type` looks
325
+ // identical for both, but only the latter should emit `typeArgs: [{}]`.
326
+ const retTypeNode = unwrapPromiseTypeNode(method.type);
327
+ transferable = detectTransferable(retType, checker, retTypeNode);
328
+ returnType = tsTypeToJsonSchema(retType, checker);
329
+ }
330
+
331
+ const skipCodegen = hasKitSkipCodegenTag(method) || undefined;
332
+ return { params, returnType, transferable, skipCodegen };
333
+ }
334
+
335
+ /**
336
+ * Walk a return-type AST node, unwrapping a single layer of `Promise<T>`,
337
+ * and return the inner `TypeReferenceNode` if any. Used to recover the
338
+ * syntactic type-arguments the customer wrote — `Promise<RealtimeChannel>`
339
+ * has no inner `<...>`, while `Promise<RealtimeChannel<X>>` does.
340
+ */
341
+ function unwrapPromiseTypeNode(node: ts.TypeNode | undefined): ts.TypeReferenceNode | undefined {
342
+ if (!node || !ts.isTypeReferenceNode(node)) return undefined;
343
+ const name = ts.isIdentifier(node.typeName) ? node.typeName.text : node.typeName.getText();
344
+ if (name === 'Promise' && node.typeArguments && node.typeArguments.length === 1) {
345
+ const inner = node.typeArguments[0];
346
+ return ts.isTypeReferenceNode(inner) ? inner : undefined;
347
+ }
348
+ return node;
349
+ }
350
+
351
+ // ── Type-checker-based extraction for indirect ApiNamespace calls ────────────
352
+
353
+ /**
354
+ * Extract method type info from a resolved `ts.Type` (e.g., the return type
355
+ * of `auth.createApi()`). This handles the case where `ApiNamespace(...)` is
356
+ * called inside another module — the AST walk can't see it, but the type
357
+ * checker resolves the full return type including method signatures.
358
+ *
359
+ * The type is the `AsyncAPI<T>` wrapper returned by `ApiNamespace`, which is
360
+ * a mapped type where each property is `(...args: Args) => Promise<R>`.
361
+ * We also handle the case where the type is callable (the raw handler function
362
+ * before `AsyncAPI` wrapping) — we call-resolve it and inspect the result.
363
+ */
364
+ function extractMethodsFromResolvedType(
365
+ type: ts.Type,
366
+ checker: ts.TypeChecker,
367
+ result: Map<string, MethodTypeInfo>,
368
+ ): void {
369
+ // The runtime value of an ApiNamespace export is the handler function itself
370
+ // (with the marker symbol attached). Its TS type is `AsyncAPI<T>`, which is
371
+ // an object type with method-like properties. But it could also be typed as
372
+ // the handler function `(context: BlocksContext) => T`. Try both:
373
+ // 1. If the type has call signatures, resolve the return type (handler fn case)
374
+ // 2. Otherwise, treat it as the AsyncAPI<T> object directly
375
+
376
+ let methodsType = type;
377
+
378
+ // If the type is callable (handler function), resolve its return type
379
+ const callSigs = type.getCallSignatures();
380
+ if (callSigs.length > 0) {
381
+ const retType = checker.getReturnTypeOfSignature(callSigs[0]);
382
+ // The return type should be the object with method properties
383
+ if (retType.getProperties().length > 0) {
384
+ methodsType = retType;
385
+ }
386
+ }
387
+
388
+ const props = methodsType.getProperties();
389
+ if (props.length === 0) return;
390
+
391
+ for (const prop of props) {
392
+ const propName = prop.getName();
393
+ // Skip internal symbols and the marker
394
+ if (propName.startsWith('_') || propName === 'Symbol(blocks:ApiNamespace)') continue;
395
+ // Don't overwrite methods already found by the AST walk
396
+ if (result.has(propName)) continue;
397
+
398
+ const propType = checker.getTypeOfSymbol(prop);
399
+ const methodSigs = propType.getCallSignatures();
400
+ if (methodSigs.length === 0) continue;
401
+
402
+ const sig = methodSigs[0];
403
+
404
+ // Extract params
405
+ const params: ParamTypeInfo[] = [];
406
+ for (const paramSymbol of sig.getParameters()) {
407
+ const paramType = checker.getTypeOfSymbol(paramSymbol);
408
+ const schema = tsTypeToJsonSchema(paramType, checker);
409
+ const paramDecl = paramSymbol.valueDeclaration;
410
+ const isOptional = paramDecl && ts.isParameter(paramDecl)
411
+ ? !!(paramDecl.questionToken || paramDecl.initializer)
412
+ : (paramSymbol.flags & ts.SymbolFlags.Optional) !== 0;
413
+ params.push({
414
+ name: paramSymbol.getName(),
415
+ required: !isOptional,
416
+ schema,
417
+ });
418
+ }
419
+
420
+ // Extract return type (unwrap Promise<T>)
421
+ let retType = checker.getReturnTypeOfSignature(sig);
422
+ retType = unwrapPromise(retType, checker);
423
+ // Check if the return type is a transferable before converting to schema
424
+ const transferable = detectTransferable(retType, checker);
425
+ const returnType = tsTypeToJsonSchema(retType, checker);
426
+
427
+ // JSDoc lives on the original method declaration, which we can reach
428
+ // via the symbol's `valueDeclaration` (the `MethodDeclaration` node in
429
+ // the BB-helper file that minted the AsyncAPI<T> shape).
430
+ let skipCodegen: true | undefined;
431
+ const declaration = prop.valueDeclaration;
432
+ if (declaration && hasKitSkipCodegenTag(declaration)) {
433
+ skipCodegen = true;
434
+ }
435
+
436
+ result.set(propName, { params, returnType, transferable, skipCodegen });
437
+ }
438
+ }
439
+
440
+ // ── Transferable detection ───────────────────────────────────────────────────
441
+
442
+ /**
443
+ * Inspect a resolved ts.Type for the transferable protocol:
444
+ * - Has a method named `toJSON`
445
+ * - toJSON's return type has a property `__blocks` with a string LITERAL type
446
+ *
447
+ * If detected, extracts the blocks tag and resolves generic type arguments.
448
+ * Returns undefined if the type is not transferable.
449
+ *
450
+ * `syntacticNode` is the AST `TypeReferenceNode` the customer wrote (e.g.
451
+ * `RealtimeChannel<CursorPosition>`). It's passed in alongside the resolved
452
+ * `ts.Type` so we can tell the difference between an unwritten default
453
+ * (`RealtimeChannel`, where `T = unknown` is filled in by the checker) and
454
+ * an explicit `RealtimeChannel<unknown>`. The two look identical in the
455
+ * resolved `ts.Type`, but only the explicit form should emit a `[{}]` slot.
456
+ */
457
+ function detectTransferable(
458
+ type: ts.Type,
459
+ checker: ts.TypeChecker,
460
+ syntacticNode?: ts.TypeReferenceNode,
461
+ ): TransferableInfo | undefined {
462
+ // Step 1-2: Get properties of the type and find `toJSON`
463
+ const props = checker.getPropertiesOfType(type);
464
+ const toJsonSymbol = props.find(p => p.getName() === 'toJSON');
465
+ if (!toJsonSymbol) return undefined;
466
+
467
+ // Step 3: Get the type of `toJSON`
468
+ const toJsonType = checker.getTypeOfSymbol(toJsonSymbol);
469
+
470
+ // Step 4: Get call signatures of the `toJSON` type
471
+ const callSignatures = toJsonType.getCallSignatures();
472
+ if (callSignatures.length === 0) return undefined;
473
+
474
+ // Step 5: Get the return type of the first call signature
475
+ const returnType = checker.getReturnTypeOfSignature(callSignatures[0]);
476
+
477
+ // Step 6: Get properties of the return type, find `__blocks`
478
+ const returnProps = checker.getPropertiesOfType(returnType);
479
+ const blocksSymbol = returnProps.find(p => p.getName() === '__blocks');
480
+ if (!blocksSymbol) return undefined;
481
+
482
+ // Step 7: Get the type of `__blocks`
483
+ const blocksType = checker.getTypeOfSymbol(blocksSymbol);
484
+
485
+ // Step 8: Check if it's a string literal type
486
+ if (!(blocksType.getFlags() & ts.TypeFlags.StringLiteral)) return undefined;
487
+
488
+ // Step 9: Extract the literal value as `blocksTag`
489
+ const blocksTag = (blocksType as ts.StringLiteralType).value;
490
+
491
+ // Step 10-11: For generic type arguments — only emit them when the
492
+ // customer actually wrote `<...>` in the source. If `syntacticNode` has
493
+ // no `typeArguments`, treat the type as non-generic (defaults filled in
494
+ // by the checker shouldn't appear in the spec).
495
+ const typeArgs: JsonSchema[] = [];
496
+ const wroteTypeArgs = syntacticNode?.typeArguments && syntacticNode.typeArguments.length > 0;
497
+ if (wroteTypeArgs) {
498
+ const typeRef = type as ts.TypeReference;
499
+ const resolvedArgs = typeRef.typeArguments ?? [];
500
+ for (const arg of resolvedArgs) {
501
+ // Explicit `unknown` / `any` → empty schema (matches Req 4.3).
502
+ const argFlags = arg.getFlags();
503
+ if (argFlags & ts.TypeFlags.Unknown || argFlags & ts.TypeFlags.Any) {
504
+ typeArgs.push({});
505
+ } else {
506
+ typeArgs.push(tsTypeToJsonSchema(arg, checker));
507
+ }
508
+ }
509
+ }
510
+
511
+ return { blocksTag, typeArgs };
512
+ }
513
+
514
+ // ── ts.Type → JSON Schema conversion ────────────────────────────────────────
515
+
516
+ /** Recursion guard to prevent infinite loops on recursive types. */
517
+ const MAX_DEPTH = 6;
518
+
519
+ /**
520
+ * Convert a resolved `ts.Type` to JSON Schema.
521
+ * This handles inferred types, imported types, type aliases, etc.
522
+ */
523
+ function tsTypeToJsonSchema(type: ts.Type, checker: ts.TypeChecker, depth = 0): JsonSchema {
524
+ if (depth > MAX_DEPTH) return { type: 'object' };
525
+
526
+ const flags = type.getFlags();
527
+
528
+ // Primitives
529
+ if (flags & ts.TypeFlags.String) return { type: 'string' };
530
+ if (flags & ts.TypeFlags.Number) return { type: 'number' };
531
+ if (flags & ts.TypeFlags.Boolean) return { type: 'boolean' };
532
+ if (flags & ts.TypeFlags.Null) return { type: 'null' };
533
+ if (flags & ts.TypeFlags.Undefined) return { type: 'null' };
534
+ if (flags & ts.TypeFlags.Void) return { type: 'null' };
535
+ if (flags & ts.TypeFlags.Any) return {};
536
+ if (flags & ts.TypeFlags.Unknown) return { type: 'unknown' };
537
+ if (flags & ts.TypeFlags.Never) return { type: 'unknown' };
538
+
539
+ // String/number/boolean literal
540
+ if (flags & ts.TypeFlags.StringLiteral) {
541
+ return { type: 'string', enum: [(type as ts.StringLiteralType).value] };
542
+ }
543
+ if (flags & ts.TypeFlags.NumberLiteral) {
544
+ return { type: 'number', enum: [(type as ts.NumberLiteralType).value] };
545
+ }
546
+ if (flags & ts.TypeFlags.BooleanLiteral) {
547
+ // ts.TypeFlags doesn't expose the value directly; use the intrinsicName
548
+ const intrinsic = (type as any).intrinsicName;
549
+ if (intrinsic === 'true') return { type: 'boolean', enum: [true] };
550
+ if (intrinsic === 'false') return { type: 'boolean', enum: [false] };
551
+ return { type: 'boolean' };
552
+ }
553
+
554
+ // Union type
555
+ if (type.isUnion()) {
556
+ return unionTypeToJsonSchema(type, checker, depth);
557
+ }
558
+
559
+ // Intersection type. Merge fixed properties from each arm and also
560
+ // preserve `additionalProperties` if any arm contributes an index
561
+ // signature (e.g. `{ username; password } & Record<string, string>`).
562
+ // This is how Cognito sign-up declares an open shape — without merging
563
+ // the index signature, native client codegen drops every key the
564
+ // type system can't statically enumerate (email, custom:*, etc.) and
565
+ // the wire format ends up missing them.
566
+ if (type.isIntersection()) {
567
+ const allProps: Record<string, JsonSchema> = {};
568
+ const allRequired: string[] = [];
569
+ let additionalProps: JsonSchema | undefined;
570
+ for (const member of type.types) {
571
+ const schema = tsTypeToJsonSchema(member, checker, depth + 1);
572
+ if (schema.properties) {
573
+ Object.assign(allProps, schema.properties);
574
+ if (schema.required) allRequired.push(...schema.required);
575
+ }
576
+ if (schema.additionalProperties && typeof schema.additionalProperties === 'object') {
577
+ additionalProps = schema.additionalProperties as JsonSchema;
578
+ }
579
+ }
580
+ if (Object.keys(allProps).length > 0) {
581
+ const schema: JsonSchema = { type: 'object', properties: allProps };
582
+ if (allRequired.length > 0) schema.required = allRequired;
583
+ if (additionalProps) schema.additionalProperties = additionalProps;
584
+ return schema;
585
+ }
586
+ if (additionalProps) {
587
+ return { type: 'object', additionalProperties: additionalProps };
588
+ }
589
+ return { type: 'object' };
590
+ }
591
+
592
+ // Array type
593
+ if (checker.isArrayType(type)) {
594
+ const typeArgs = (type as ts.TypeReference).typeArguments;
595
+ if (typeArgs && typeArgs.length === 1) {
596
+ return { type: 'array', items: tsTypeToJsonSchema(typeArgs[0], checker, depth + 1) };
597
+ }
598
+ return { type: 'array' };
599
+ }
600
+
601
+ // Object type (interfaces, type literals, classes)
602
+ if (flags & ts.TypeFlags.Object) {
603
+ // Special-case built-in types that serialize to JSON primitives.
604
+ // Without this, Date emits as an object with getTime/toISOString/etc. properties.
605
+ const symbol = type.getSymbol();
606
+ const typeName = symbol?.getName();
607
+ if (typeName === 'Date') return { type: 'string', format: 'date-time' };
608
+ if (typeName === 'RegExp') return { type: 'string', format: 'regex' };
609
+ if (typeName === 'URL') return { type: 'string', format: 'uri' };
610
+ if (typeName === 'Buffer' || typeName === 'ArrayBuffer' || typeName === 'Uint8Array') return { type: 'string', format: 'binary' };
611
+
612
+ return objectTypeToJsonSchema(type as ts.ObjectType, checker, depth);
613
+ }
614
+
615
+ return { type: 'unknown' };
616
+ }
617
+
618
+ function objectTypeToJsonSchema(type: ts.ObjectType, checker: ts.TypeChecker, depth: number): JsonSchema {
619
+ const properties: Record<string, JsonSchema> = {};
620
+ const required: string[] = [];
621
+
622
+ const props = checker.getPropertiesOfType(type);
623
+
624
+ // Index signatures (`Record<string, V>` or `{ [key: string]: V }`) carry
625
+ // the value type for any unnamed key. Capture once up front so we can
626
+ // emit `additionalProperties` on both the pure-map case (no fixed
627
+ // properties) and the mixed case (fixed properties + index sig).
628
+ const stringIndexType = checker.getIndexTypeOfType(type, ts.IndexKind.String);
629
+ const additionalProps: JsonSchema | undefined = stringIndexType
630
+ ? tsTypeToJsonSchema(stringIndexType, checker, depth + 1)
631
+ : undefined;
632
+
633
+ if (props.length === 0 && additionalProps) {
634
+ return { type: 'object', additionalProperties: additionalProps };
635
+ }
636
+
637
+ if (props.length === 0) return { type: 'object' };
638
+
639
+ for (const prop of props) {
640
+ // Skip internal/private properties
641
+ if (prop.name.startsWith('_')) continue;
642
+
643
+ // Mapped-type properties (e.g. from `Partial<Record<K, V>>` and the
644
+ // per-variant intersections produced when unioning over an action
645
+ // payload map) are synthetic and have no `valueDeclaration`. Casting
646
+ // the `Symbol` to a `ts.Node` to satisfy `getTypeOfSymbolAtLocation`
647
+ // crashes inside the compiler's `isDeclaration` walk. `getTypeOfSymbol`
648
+ // resolves the property type without a location and works for both
649
+ // real and synthetic properties.
650
+ const propType = checker.getTypeOfSymbol(prop);
651
+ const propSchema = tsTypeToJsonSchema(propType, checker, depth + 1);
652
+ properties[prop.name] = propSchema;
653
+
654
+ const isOptional = (prop.flags & ts.SymbolFlags.Optional) !== 0;
655
+ if (!isOptional) {
656
+ required.push(prop.name);
657
+ }
658
+ }
659
+
660
+ const schema: JsonSchema = { type: 'object', properties };
661
+ if (required.length > 0) schema.required = required;
662
+ if (additionalProps) schema.additionalProperties = additionalProps;
663
+
664
+ // Preserve the original type name for named types (interfaces, type aliases).
665
+ // This enables $ref extraction in generate-spec.ts and Phase 4 hydration detection.
666
+ const symbol = type.getSymbol() || type.aliasSymbol;
667
+ if (symbol) {
668
+ const name = symbol.getName();
669
+ // Skip anonymous/synthetic names
670
+ if (name && name !== '__type' && name !== '__object' && !name.startsWith('__')) {
671
+ schema.title = name;
672
+ }
673
+ }
674
+
675
+ return schema;
676
+ }
677
+
678
+ function unionTypeToJsonSchema(type: ts.UnionType, checker: ts.TypeChecker, depth: number): JsonSchema {
679
+ const members = type.types.map(t => tsTypeToJsonSchema(t, checker, depth + 1));
680
+
681
+ const nullish = members.filter(m => m.type === 'null');
682
+ const nonNullish = members.filter(m => m.type !== 'null');
683
+
684
+ // boolean is represented as true | false union in TS
685
+ if (nonNullish.length === 2 &&
686
+ nonNullish.every(m => m.type === 'boolean' && m.enum)) {
687
+ if (nullish.length > 0) {
688
+ return { oneOf: [{ type: 'boolean' }, { type: 'null' }] };
689
+ }
690
+ return { type: 'boolean' };
691
+ }
692
+
693
+ // T | null → oneOf [T, null]
694
+ if (nonNullish.length === 1 && nullish.length > 0) {
695
+ return { oneOf: [nonNullish[0], { type: 'null' }] };
696
+ }
697
+
698
+ // All same base type with enum values
699
+ const allEnums = nonNullish.every(m => m.enum);
700
+ if (allEnums && nonNullish.length > 0) {
701
+ const baseType = nonNullish[0].type;
702
+ if (nonNullish.every(m => m.type === baseType)) {
703
+ const enumValues = nonNullish.flatMap(m => m.enum || []);
704
+ const schema: JsonSchema = { type: baseType, enum: enumValues };
705
+ if (nullish.length > 0) {
706
+ return { oneOf: [schema, { type: 'null' }] };
707
+ }
708
+ return schema;
709
+ }
710
+ }
711
+
712
+ // General union → oneOf
713
+ if (nonNullish.length > 1) {
714
+ const allMembers = nullish.length > 0 ? [...nonNullish, { type: 'null' }] : nonNullish;
715
+ return { oneOf: allMembers };
716
+ }
717
+
718
+ return members[0] || { type: 'unknown' };
719
+ }
720
+
721
+ // ── Helpers ─────────────────────────────────────────────────────────────────
722
+
723
+ /**
724
+ * Unwrap Promise<T> → T. If the type is not a Promise, returns it unchanged.
725
+ */
726
+ function unwrapPromise(type: ts.Type, checker: ts.TypeChecker): ts.Type {
727
+ // Check if it's a Promise by looking at the symbol name
728
+ const symbol = type.getSymbol();
729
+ if (symbol && symbol.getName() === 'Promise') {
730
+ const typeArgs = (type as ts.TypeReference).typeArguments;
731
+ if (typeArgs && typeArgs.length === 1) {
732
+ return typeArgs[0];
733
+ }
734
+ }
735
+ return type;
736
+ }
737
+
738
+ // ── AST-only fallback (for explicit type annotations without checker) ───────
739
+
740
+ function typeNodeToJsonSchema(typeNode: ts.TypeNode, sourceFile: ts.SourceFile): JsonSchema {
741
+ if (ts.isToken(typeNode)) {
742
+ switch (typeNode.kind) {
743
+ case ts.SyntaxKind.StringKeyword: return { type: 'string' };
744
+ case ts.SyntaxKind.NumberKeyword: return { type: 'number' };
745
+ case ts.SyntaxKind.BooleanKeyword: return { type: 'boolean' };
746
+ case ts.SyntaxKind.NullKeyword: return { type: 'null' };
747
+ case ts.SyntaxKind.VoidKeyword: return { type: 'null' };
748
+ case ts.SyntaxKind.AnyKeyword: return {};
749
+ default: return { type: 'unknown' };
750
+ }
751
+ }
752
+
753
+ if (ts.isTypeLiteralNode(typeNode)) {
754
+ const properties: Record<string, JsonSchema> = {};
755
+ const required: string[] = [];
756
+ for (const member of typeNode.members) {
757
+ if (ts.isPropertySignature(member) && member.name) {
758
+ const propName = member.name.getText(sourceFile);
759
+ properties[propName] = member.type ? typeNodeToJsonSchema(member.type, sourceFile) : { type: 'unknown' };
760
+ if (!member.questionToken) required.push(propName);
761
+ }
762
+ }
763
+ const schema: JsonSchema = { type: 'object', properties };
764
+ if (required.length > 0) schema.required = required;
765
+ return schema;
766
+ }
767
+
768
+ if (ts.isArrayTypeNode(typeNode)) {
769
+ return { type: 'array', items: typeNodeToJsonSchema(typeNode.elementType, sourceFile) };
770
+ }
771
+
772
+ return { type: 'unknown' };
773
+ }