@geekmidas/cli 10.0.0-alpha.91 → 10.0.0-alpha.92

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 (303) hide show
  1. package/dist/{EndpointGenerator-CVc6Oi1Y.mjs → EndpointGenerator-Bor2tZCm.mjs} +14 -4
  2. package/dist/EndpointGenerator-Bor2tZCm.mjs.map +1 -0
  3. package/dist/{EndpointGenerator-CwLjBCFE.cjs → EndpointGenerator-De03t1hd.cjs} +14 -4
  4. package/dist/EndpointGenerator-De03t1hd.cjs.map +1 -0
  5. package/dist/{GoDaddyProvider-DSStJVAv.mjs → GoDaddyProvider-BqYGk9XT.mjs} +11 -10
  6. package/dist/GoDaddyProvider-BqYGk9XT.mjs.map +1 -0
  7. package/dist/{GoDaddyProvider-DqZ1qD_F.cjs → GoDaddyProvider-CJwg2r_9.cjs} +11 -10
  8. package/dist/GoDaddyProvider-CJwg2r_9.cjs.map +1 -0
  9. package/dist/{HostingerProvider-BI6J7oJr.mjs → HostingerProvider-1mqD6aG8.mjs} +2 -2
  10. package/dist/{HostingerProvider-BI6J7oJr.mjs.map → HostingerProvider-1mqD6aG8.mjs.map} +1 -1
  11. package/dist/{HostingerProvider-CyTpG_jy.cjs → HostingerProvider-C6t-PSNe.cjs} +2 -2
  12. package/dist/{HostingerProvider-CyTpG_jy.cjs.map → HostingerProvider-C6t-PSNe.cjs.map} +1 -1
  13. package/dist/{LegacyStateStore-DXmNA7hy.cjs → LegacyStateStore-CeEwayMB.cjs} +2 -2
  14. package/dist/{LegacyStateStore-DXmNA7hy.cjs.map → LegacyStateStore-CeEwayMB.cjs.map} +1 -1
  15. package/dist/{LegacyStateStore-ZkdmiUFZ.mjs → LegacyStateStore-DdJ1bCRV.mjs} +2 -2
  16. package/dist/{LegacyStateStore-ZkdmiUFZ.mjs.map → LegacyStateStore-DdJ1bCRV.mjs.map} +1 -1
  17. package/dist/{LocalStateStore-BBeTlRTH.cjs → LocalStateStore-BMfseje2.cjs} +2 -2
  18. package/dist/{LocalStateStore-BBeTlRTH.cjs.map → LocalStateStore-BMfseje2.cjs.map} +1 -1
  19. package/dist/{LocalStateStore-CU-O0SBp.mjs → LocalStateStore-Y8XHdoCR.mjs} +2 -2
  20. package/dist/{LocalStateStore-CU-O0SBp.mjs.map → LocalStateStore-Y8XHdoCR.mjs.map} +1 -1
  21. package/dist/{OpenApiTsGenerator-B--HV-sW.cjs → OpenApiTsGenerator-CS_RtWOz.cjs} +2 -2
  22. package/dist/{OpenApiTsGenerator-B--HV-sW.cjs.map → OpenApiTsGenerator-CS_RtWOz.cjs.map} +1 -1
  23. package/dist/{OpenApiTsGenerator-CkvsPdgg.mjs → OpenApiTsGenerator-DWAF-7Fu.mjs} +2 -2
  24. package/dist/{OpenApiTsGenerator-CkvsPdgg.mjs.map → OpenApiTsGenerator-DWAF-7Fu.mjs.map} +1 -1
  25. package/dist/{S3StateStore-bwxcI_wQ.mjs → S3StateStore-BfoY4MvD.mjs} +2 -2
  26. package/dist/{S3StateStore-bwxcI_wQ.mjs.map → S3StateStore-BfoY4MvD.mjs.map} +1 -1
  27. package/dist/{S3StateStore-Br8VMY22.cjs → S3StateStore-BmMt2HhW.cjs} +2 -2
  28. package/dist/{S3StateStore-Br8VMY22.cjs.map → S3StateStore-BmMt2HhW.cjs.map} +1 -1
  29. package/dist/{SSMStateStore-CsOS0W1y.mjs → SSMStateStore-Bdyw_0Jj.mjs} +2 -2
  30. package/dist/{SSMStateStore-CsOS0W1y.mjs.map → SSMStateStore-Bdyw_0Jj.mjs.map} +1 -1
  31. package/dist/{SSMStateStore-DoZE1Wi1.cjs → SSMStateStore-D9K62wM4.cjs} +2 -2
  32. package/dist/{SSMStateStore-DoZE1Wi1.cjs.map → SSMStateStore-D9K62wM4.cjs.map} +1 -1
  33. package/dist/{SubscriberGenerator-BPqQf8fB.mjs → SubscriberGenerator-CyN2VF5X.mjs} +6 -5
  34. package/dist/SubscriberGenerator-CyN2VF5X.mjs.map +1 -0
  35. package/dist/{SubscriberGenerator-BVn7cQ-2.cjs → SubscriberGenerator-Dvz7EzqK.cjs} +6 -5
  36. package/dist/SubscriberGenerator-Dvz7EzqK.cjs.map +1 -0
  37. package/dist/{add-BSHk5Ngn.mjs → add-2Opn2VVE.mjs} +7 -6
  38. package/dist/add-2Opn2VVE.mjs.map +1 -0
  39. package/dist/{add-DmmBDV2e.cjs → add-oN0b18HN.cjs} +7 -6
  40. package/dist/add-oN0b18HN.cjs.map +1 -0
  41. package/dist/{aws-BTawF0F-.cjs → aws-BTReGjpY.cjs} +24 -6
  42. package/dist/aws-BTReGjpY.cjs.map +1 -0
  43. package/dist/{aws-B-6uEhgP.mjs → aws-Dz2bYxK1.mjs} +24 -6
  44. package/dist/aws-Dz2bYxK1.mjs.map +1 -0
  45. package/dist/awsStore-B5We7bMg.cjs +136 -0
  46. package/dist/awsStore-B5We7bMg.cjs.map +1 -0
  47. package/dist/awsStore-BWa1KNY-.mjs +119 -0
  48. package/dist/awsStore-BWa1KNY-.mjs.map +1 -0
  49. package/dist/{bundler-D6eBfVkl.mjs → bundler-C-AdesaG.mjs} +5 -5
  50. package/dist/{bundler-D6eBfVkl.mjs.map → bundler-C-AdesaG.mjs.map} +1 -1
  51. package/dist/{bundler-BQJiB7Ub.cjs → bundler-CXQDrhzN.cjs} +5 -5
  52. package/dist/{bundler-BQJiB7Ub.cjs.map → bundler-CXQDrhzN.cjs.map} +1 -1
  53. package/dist/{cli-BE9mqeX8.cjs → cli-BRZFYvdb.cjs} +5 -5
  54. package/dist/{cli-BE9mqeX8.cjs.map → cli-BRZFYvdb.cjs.map} +1 -1
  55. package/dist/{cli-DD-Yk-I5.mjs → cli-BjhaVObS.mjs} +5 -5
  56. package/dist/{cli-DD-Yk-I5.mjs.map → cli-BjhaVObS.mjs.map} +1 -1
  57. package/dist/{compose-Bhmf0n4o.mjs → compose-CTdVeeVk.mjs} +45 -34
  58. package/dist/compose-CTdVeeVk.mjs.map +1 -0
  59. package/dist/{compose-CvpU6ooi.cjs → compose-fnYmPk_c.cjs} +45 -34
  60. package/dist/compose-fnYmPk_c.cjs.map +1 -0
  61. package/dist/{config-pST7E0CS.d.cts → config-BJo8Wggp.d.mts} +8 -8
  62. package/dist/{config-CdEcTrjG.d.mts.map → config-BJo8Wggp.d.mts.map} +1 -1
  63. package/dist/{config-CdEcTrjG.d.mts → config-C9zx7KsX.d.cts} +8 -8
  64. package/dist/{config-pST7E0CS.d.cts.map → config-C9zx7KsX.d.cts.map} +1 -1
  65. package/dist/config.cjs +8 -7
  66. package/dist/config.cjs.map +1 -1
  67. package/dist/config.d.cts +3 -3
  68. package/dist/config.d.mts +3 -3
  69. package/dist/config.mjs +8 -7
  70. package/dist/config.mjs.map +1 -1
  71. package/dist/{credentialSchemas-BJqRJNK4.mjs → credentialSchemas-BJFvfkaz.mjs} +5 -4
  72. package/dist/credentialSchemas-BJFvfkaz.mjs.map +1 -0
  73. package/dist/{credentialSchemas-DCR4Wd1W.cjs → credentialSchemas-CAPKFkrG.cjs} +5 -4
  74. package/dist/credentialSchemas-CAPKFkrG.cjs.map +1 -0
  75. package/dist/{credentials-CnVqoXMb.mjs → credentials-C4KII3PD.mjs} +3 -2
  76. package/dist/{credentials-CnVqoXMb.mjs.map → credentials-C4KII3PD.mjs.map} +1 -1
  77. package/dist/{credentials-BKfIugu2.cjs → credentials-QBu7VgaQ.cjs} +3 -2
  78. package/dist/credentials-QBu7VgaQ.cjs.map +1 -0
  79. package/dist/{databases-CzszQV0N.mjs → databases-BeGP5eDh.mjs} +11 -10
  80. package/dist/databases-BeGP5eDh.mjs.map +1 -0
  81. package/dist/{databases-BY-pEImp.cjs → databases-DGALFDG3.cjs} +11 -10
  82. package/dist/databases-DGALFDG3.cjs.map +1 -0
  83. package/dist/{define-DR8xGYfL.mjs → define-6poNqm5O.mjs} +12 -3
  84. package/dist/define-6poNqm5O.mjs.map +1 -0
  85. package/dist/{define-DzRz8FgH.cjs → define-crmMcXFS.cjs} +12 -3
  86. package/dist/define-crmMcXFS.cjs.map +1 -0
  87. package/dist/deploy/public.cjs +17 -16
  88. package/dist/deploy/public.cjs.map +1 -1
  89. package/dist/deploy/public.d.cts +30 -30
  90. package/dist/deploy/public.d.cts.map +1 -1
  91. package/dist/deploy/public.d.mts +30 -30
  92. package/dist/deploy/public.d.mts.map +1 -1
  93. package/dist/deploy/public.mjs +17 -16
  94. package/dist/deploy/public.mjs.map +1 -1
  95. package/dist/{deploy-CuHasEA4.cjs → deploy-6ToUHJmM.cjs} +14 -13
  96. package/dist/deploy-6ToUHJmM.cjs.map +1 -0
  97. package/dist/{deploy-QNtx_eWR.mjs → deploy-yuJHlkY0.mjs} +14 -13
  98. package/dist/deploy-yuJHlkY0.mjs.map +1 -0
  99. package/dist/{discover-CU0paUyH.d.cts → discover-CRMhvHG9.d.cts} +2 -2
  100. package/dist/{discover-CU0paUyH.d.cts.map → discover-CRMhvHG9.d.cts.map} +1 -1
  101. package/dist/{discover-D1It5r_h.cjs → discover-DPgu-ScS.cjs} +4 -3
  102. package/dist/discover-DPgu-ScS.cjs.map +1 -0
  103. package/dist/{discover-DFiJxwfv.d.mts → discover-G76EQ4vY.d.mts} +2 -2
  104. package/dist/{discover-DFiJxwfv.d.mts.map → discover-G76EQ4vY.d.mts.map} +1 -1
  105. package/dist/{discover-BPelzeGW.mjs → discover-qjFapetL.mjs} +4 -3
  106. package/dist/discover-qjFapetL.mjs.map +1 -0
  107. package/dist/{dokploy-CYcMLh9-.mjs → dokploy-BvbJ_A4Q.mjs} +3 -3
  108. package/dist/{dokploy-CYcMLh9-.mjs.map → dokploy-BvbJ_A4Q.mjs.map} +1 -1
  109. package/dist/{dokploy-DIowwyIB.cjs → dokploy-Bvvfj2qe.cjs} +3 -3
  110. package/dist/{dokploy-DIowwyIB.cjs.map → dokploy-Bvvfj2qe.cjs.map} +1 -1
  111. package/dist/{drivers-DTZzxeIl.mjs → drivers-CTaWrA_L.mjs} +2 -2
  112. package/dist/{drivers-DTZzxeIl.mjs.map → drivers-CTaWrA_L.mjs.map} +1 -1
  113. package/dist/{drivers-LYSdQDmd.cjs → drivers-DDojgzz0.cjs} +2 -2
  114. package/dist/{drivers-LYSdQDmd.cjs.map → drivers-DDojgzz0.cjs.map} +1 -1
  115. package/dist/{encryption-BrEef7Jk.mjs → encryption-C9Jm9dfn.mjs} +7 -6
  116. package/dist/encryption-C9Jm9dfn.mjs.map +1 -0
  117. package/dist/{encryption-CjYdUb2s.cjs → encryption-CgW167LO.cjs} +7 -6
  118. package/dist/encryption-CgW167LO.cjs.map +1 -0
  119. package/dist/{engine-2h4k5ICq.mjs → engine-C8Ye9MUm.mjs} +36 -25
  120. package/dist/engine-C8Ye9MUm.mjs.map +1 -0
  121. package/dist/{engine-BhJ7ZP9K.cjs → engine-Dj7ZKO8H.cjs} +36 -25
  122. package/dist/engine-Dj7ZKO8H.cjs.map +1 -0
  123. package/dist/errors-D8Gwp5kU.mjs +23 -0
  124. package/dist/errors-D8Gwp5kU.mjs.map +1 -0
  125. package/dist/errors-DMC4Umsb.cjs +34 -0
  126. package/dist/errors-DMC4Umsb.cjs.map +1 -0
  127. package/dist/{github-BCDF6xVH.mjs → github-B1k42hIt.mjs} +31 -9
  128. package/dist/github-B1k42hIt.mjs.map +1 -0
  129. package/dist/{github-B5Bw4HIu.cjs → github-BD051KZZ.cjs} +31 -9
  130. package/dist/github-BD051KZZ.cjs.map +1 -0
  131. package/dist/{index-YFNxR5Ns.d.mts → index-B5uuwx-Y.d.cts} +6 -6
  132. package/dist/{index-D-F_hTDz.d.cts.map → index-B5uuwx-Y.d.cts.map} +1 -1
  133. package/dist/{index-D-F_hTDz.d.cts → index-D1pHwqkK.d.mts} +6 -6
  134. package/dist/{index-YFNxR5Ns.d.mts.map → index-D1pHwqkK.d.mts.map} +1 -1
  135. package/dist/index.cjs +159 -208
  136. package/dist/index.cjs.map +1 -1
  137. package/dist/index.mjs +159 -208
  138. package/dist/index.mjs.map +1 -1
  139. package/dist/{local-BjxDfPvy.cjs → local-B61kUh0w.cjs} +3 -3
  140. package/dist/{local-BjxDfPvy.cjs.map → local-B61kUh0w.cjs.map} +1 -1
  141. package/dist/{local-IuoIF3CT.mjs → local-Otp74ak0.mjs} +3 -3
  142. package/dist/{local-IuoIF3CT.mjs.map → local-Otp74ak0.mjs.map} +1 -1
  143. package/dist/{migrate-DbtcuOr_.mjs → migrate-BSbZ7-9n.mjs} +8 -7
  144. package/dist/migrate-BSbZ7-9n.mjs.map +1 -0
  145. package/dist/{migrate-q2YYLJLE.cjs → migrate-D3VVsiHp.cjs} +8 -7
  146. package/dist/migrate-D3VVsiHp.cjs.map +1 -0
  147. package/dist/{migrate-DWZz9kah.mjs → migrate-IZsYyZ5F.mjs} +8 -7
  148. package/dist/migrate-IZsYyZ5F.mjs.map +1 -0
  149. package/dist/{migrate-CGaancHY.cjs → migrate-f6JDjyCp.cjs} +8 -7
  150. package/dist/migrate-f6JDjyCp.cjs.map +1 -0
  151. package/dist/openapi.cjs +3 -3
  152. package/dist/openapi.d.cts +1 -10
  153. package/dist/openapi.d.cts.map +1 -1
  154. package/dist/openapi.d.mts +1 -10
  155. package/dist/openapi.d.mts.map +1 -1
  156. package/dist/openapi.mjs +3 -3
  157. package/dist/{provider-C9_-45d5.cjs → provider-BqpjG9QM.cjs} +4 -3
  158. package/dist/provider-BqpjG9QM.cjs.map +1 -0
  159. package/dist/{provider-q4lsYzYG.mjs → provider-CEOKk8NS.mjs} +4 -3
  160. package/dist/provider-CEOKk8NS.mjs.map +1 -0
  161. package/dist/{ready-BpNbORRB.cjs → ready-D3PIjx3S.cjs} +9 -8
  162. package/dist/ready-D3PIjx3S.cjs.map +1 -0
  163. package/dist/{ready-b6vm0-W-.mjs → ready-naOSJpeJ.mjs} +9 -8
  164. package/dist/ready-naOSJpeJ.mjs.map +1 -0
  165. package/dist/reconcile/public.cjs +1 -1
  166. package/dist/reconcile/public.d.cts +2 -2
  167. package/dist/reconcile/public.d.mts +2 -2
  168. package/dist/reconcile/public.mjs +1 -1
  169. package/dist/{resolve-DNg0icnh.mjs → resolve-Be4bF1w4.mjs} +12 -11
  170. package/dist/resolve-Be4bF1w4.mjs.map +1 -0
  171. package/dist/{resolve-Cns0v6dN.cjs → resolve-DUp65SnJ.cjs} +12 -11
  172. package/dist/resolve-DUp65SnJ.cjs.map +1 -0
  173. package/dist/{rollback-CFrNLmxs.mjs → rollback-D1d-fW0r.mjs} +11 -10
  174. package/dist/rollback-D1d-fW0r.mjs.map +1 -0
  175. package/dist/{rollback-UIjS25F1.cjs → rollback-DDHtd0GJ.cjs} +11 -10
  176. package/dist/rollback-DDHtd0GJ.cjs.map +1 -0
  177. package/dist/{runtime-CN_kmqxo.d.cts → runtime-DDrHlYe5.d.cts} +11 -19
  178. package/dist/runtime-DDrHlYe5.d.cts.map +1 -0
  179. package/dist/{runtime-Cjvon3oj.d.mts → runtime-Es1V4e9M.d.mts} +11 -19
  180. package/dist/runtime-Es1V4e9M.d.mts.map +1 -0
  181. package/dist/sandbox/credentials-worker.cjs +3 -3
  182. package/dist/sandbox/credentials-worker.mjs +3 -3
  183. package/dist/sandbox/discover-worker.cjs +1 -1
  184. package/dist/sandbox/discover-worker.mjs +1 -1
  185. package/dist/sandbox/migrate-worker.cjs +3 -3
  186. package/dist/sandbox/migrate-worker.mjs +3 -3
  187. package/dist/{sandbox-M8UnLhXE.d.cts → sandbox-C3WsTa5m.d.mts} +4 -26
  188. package/dist/sandbox-C3WsTa5m.d.mts.map +1 -0
  189. package/dist/{sandbox-CL--ZDui.cjs → sandbox-CHv3OYXg.cjs} +4 -3
  190. package/dist/sandbox-CHv3OYXg.cjs.map +1 -0
  191. package/dist/{sandbox-Dm247NGx.mjs → sandbox-CbVB7uOD.mjs} +4 -3
  192. package/dist/sandbox-CbVB7uOD.mjs.map +1 -0
  193. package/dist/{sandbox-M8UnLhXE.d.mts → sandbox-MaxSeU92.d.cts} +4 -26
  194. package/dist/sandbox-MaxSeU92.d.cts.map +1 -0
  195. package/dist/{secretsManager-Chs9VV2r.mjs → secretsManager-C6wm-9uZ.mjs} +24 -3
  196. package/dist/secretsManager-C6wm-9uZ.mjs.map +1 -0
  197. package/dist/{secretsManager-DcMlHnRC.cjs → secretsManager-Cwwgs81g.cjs} +24 -3
  198. package/dist/secretsManager-Cwwgs81g.cjs.map +1 -0
  199. package/dist/{seeds-CkIPFGNO.cjs → seeds-BKoJYsE7.cjs} +58 -57
  200. package/dist/seeds-BKoJYsE7.cjs.map +1 -0
  201. package/dist/{seeds-odfgSCmu.mjs → seeds-CvGQJnPO.mjs} +58 -57
  202. package/dist/seeds-CvGQJnPO.mjs.map +1 -0
  203. package/dist/{sniffer-BTtX3NUM.mjs → sniffer-BTtGAtm3.mjs} +14 -8
  204. package/dist/sniffer-BTtGAtm3.mjs.map +1 -0
  205. package/dist/{sniffer-IAQEpsQb.cjs → sniffer-Dq5BS98f.cjs} +14 -8
  206. package/dist/{sniffer-IAQEpsQb.cjs.map → sniffer-Dq5BS98f.cjs.map} +1 -1
  207. package/dist/{sst-CJzvyrEz.mjs → sst-BaRXp2Dd.mjs} +18 -8
  208. package/dist/sst-BaRXp2Dd.mjs.map +1 -0
  209. package/dist/{sst-BgcOsaBi.cjs → sst-CnKY-CSh.cjs} +18 -8
  210. package/dist/sst-CnKY-CSh.cjs.map +1 -0
  211. package/dist/{stages-CUn6gvXL.cjs → stages-BI8JvPzI.cjs} +6 -5
  212. package/dist/stages-BI8JvPzI.cjs.map +1 -0
  213. package/dist/{stages-CnZRYmkI.mjs → stages-DY-fQKmo.mjs} +6 -5
  214. package/dist/stages-DY-fQKmo.mjs.map +1 -0
  215. package/dist/target/public.cjs +3 -3
  216. package/dist/target/public.d.cts +2 -2
  217. package/dist/target/public.d.mts +2 -2
  218. package/dist/target/public.mjs +3 -3
  219. package/dist/{terminal-DuapRxh6.cjs → terminal-8XngdSTo.cjs} +6 -5
  220. package/dist/terminal-8XngdSTo.cjs.map +1 -0
  221. package/dist/{terminal-D8TPK7o9.mjs → terminal-Dtd6BZX9.mjs} +6 -5
  222. package/dist/terminal-Dtd6BZX9.mjs.map +1 -0
  223. package/dist/{types-ZHuZ89hv.d.mts → types-B3Gqs175.d.mts} +19 -23
  224. package/dist/types-B3Gqs175.d.mts.map +1 -0
  225. package/dist/{types-NqX1Hz4P.d.cts → types-Bk9st2cd.d.cts} +19 -23
  226. package/dist/types-Bk9st2cd.d.cts.map +1 -0
  227. package/dist/vitest.cjs +1 -1
  228. package/dist/vitest.mjs +1 -1
  229. package/dist/workspace/index.cjs +1 -1
  230. package/dist/workspace/index.d.cts +2 -2
  231. package/dist/workspace/index.d.mts +2 -2
  232. package/dist/workspace/index.mjs +1 -1
  233. package/dist/{workspace-CJ0lKFq-.mjs → workspace-Bzw7Zs82.mjs} +34 -33
  234. package/dist/workspace-Bzw7Zs82.mjs.map +1 -0
  235. package/dist/{workspace-CwW3Gkru.cjs → workspace-CHOB2Xg3.cjs} +34 -33
  236. package/dist/workspace-CHOB2Xg3.cjs.map +1 -0
  237. package/package.json +15 -15
  238. package/dist/EndpointGenerator-CVc6Oi1Y.mjs.map +0 -1
  239. package/dist/EndpointGenerator-CwLjBCFE.cjs.map +0 -1
  240. package/dist/GoDaddyProvider-DSStJVAv.mjs.map +0 -1
  241. package/dist/GoDaddyProvider-DqZ1qD_F.cjs.map +0 -1
  242. package/dist/SubscriberGenerator-BPqQf8fB.mjs.map +0 -1
  243. package/dist/SubscriberGenerator-BVn7cQ-2.cjs.map +0 -1
  244. package/dist/add-BSHk5Ngn.mjs.map +0 -1
  245. package/dist/add-DmmBDV2e.cjs.map +0 -1
  246. package/dist/aws-B-6uEhgP.mjs.map +0 -1
  247. package/dist/aws-BTawF0F-.cjs.map +0 -1
  248. package/dist/awsStore-7VmS_cZR.cjs +0 -54
  249. package/dist/awsStore-7VmS_cZR.cjs.map +0 -1
  250. package/dist/awsStore-B529csle.mjs +0 -43
  251. package/dist/awsStore-B529csle.mjs.map +0 -1
  252. package/dist/compose-Bhmf0n4o.mjs.map +0 -1
  253. package/dist/compose-CvpU6ooi.cjs.map +0 -1
  254. package/dist/credentialSchemas-BJqRJNK4.mjs.map +0 -1
  255. package/dist/credentialSchemas-DCR4Wd1W.cjs.map +0 -1
  256. package/dist/credentials-BKfIugu2.cjs.map +0 -1
  257. package/dist/databases-BY-pEImp.cjs.map +0 -1
  258. package/dist/databases-CzszQV0N.mjs.map +0 -1
  259. package/dist/define-DR8xGYfL.mjs.map +0 -1
  260. package/dist/define-DzRz8FgH.cjs.map +0 -1
  261. package/dist/deploy-CuHasEA4.cjs.map +0 -1
  262. package/dist/deploy-QNtx_eWR.mjs.map +0 -1
  263. package/dist/discover-BPelzeGW.mjs.map +0 -1
  264. package/dist/discover-D1It5r_h.cjs.map +0 -1
  265. package/dist/encryption-BrEef7Jk.mjs.map +0 -1
  266. package/dist/encryption-CjYdUb2s.cjs.map +0 -1
  267. package/dist/engine-2h4k5ICq.mjs.map +0 -1
  268. package/dist/engine-BhJ7ZP9K.cjs.map +0 -1
  269. package/dist/github-B5Bw4HIu.cjs.map +0 -1
  270. package/dist/github-BCDF6xVH.mjs.map +0 -1
  271. package/dist/migrate-CGaancHY.cjs.map +0 -1
  272. package/dist/migrate-DWZz9kah.mjs.map +0 -1
  273. package/dist/migrate-DbtcuOr_.mjs.map +0 -1
  274. package/dist/migrate-q2YYLJLE.cjs.map +0 -1
  275. package/dist/provider-C9_-45d5.cjs.map +0 -1
  276. package/dist/provider-q4lsYzYG.mjs.map +0 -1
  277. package/dist/ready-BpNbORRB.cjs.map +0 -1
  278. package/dist/ready-b6vm0-W-.mjs.map +0 -1
  279. package/dist/resolve-Cns0v6dN.cjs.map +0 -1
  280. package/dist/resolve-DNg0icnh.mjs.map +0 -1
  281. package/dist/rollback-CFrNLmxs.mjs.map +0 -1
  282. package/dist/rollback-UIjS25F1.cjs.map +0 -1
  283. package/dist/runtime-CN_kmqxo.d.cts.map +0 -1
  284. package/dist/runtime-Cjvon3oj.d.mts.map +0 -1
  285. package/dist/sandbox-CL--ZDui.cjs.map +0 -1
  286. package/dist/sandbox-Dm247NGx.mjs.map +0 -1
  287. package/dist/sandbox-M8UnLhXE.d.cts.map +0 -1
  288. package/dist/sandbox-M8UnLhXE.d.mts.map +0 -1
  289. package/dist/secretsManager-Chs9VV2r.mjs.map +0 -1
  290. package/dist/secretsManager-DcMlHnRC.cjs.map +0 -1
  291. package/dist/seeds-CkIPFGNO.cjs.map +0 -1
  292. package/dist/seeds-odfgSCmu.mjs.map +0 -1
  293. package/dist/sniffer-BTtX3NUM.mjs.map +0 -1
  294. package/dist/sst-BgcOsaBi.cjs.map +0 -1
  295. package/dist/sst-CJzvyrEz.mjs.map +0 -1
  296. package/dist/stages-CUn6gvXL.cjs.map +0 -1
  297. package/dist/stages-CnZRYmkI.mjs.map +0 -1
  298. package/dist/terminal-D8TPK7o9.mjs.map +0 -1
  299. package/dist/terminal-DuapRxh6.cjs.map +0 -1
  300. package/dist/types-NqX1Hz4P.d.cts.map +0 -1
  301. package/dist/types-ZHuZ89hv.d.mts.map +0 -1
  302. package/dist/workspace-CJ0lKFq-.mjs.map +0 -1
  303. package/dist/workspace-CwW3Gkru.cjs.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"EndpointGenerator-CVc6Oi1Y.mjs","names":[],"sources":["../src/generators/telemetry.ts","../src/generators/clientTelemetry.ts","../src/generators/EndpointGenerator.ts"],"sourcesContent":["import { existsSync, realpathSync } from 'node:fs';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join, relative, resolve } from 'node:path';\nimport type { TelemetryDeclaration } from '@geekmidas/manifest';\n\n/**\n * One route's own telemetry — an endpoint's `.telemetry({ ignore, attributes })`.\n */\nexport interface TelemetryRoute {\n\tmethod: string;\n\t/** The route as the surface serves it — `/users/:id`. */\n\tpath: string;\n\tignore?: boolean;\n\tattributes?: Readonly<Record<string, string>>;\n}\n\n/** Each endpoint's own `.telemetry({ ignore, attributes })`, as routes. */\nexport function routeTelemetry(\n\tendpoints: readonly {\n\t\tmethod: string;\n\t\troute: string;\n\t\ttelemetry?: Omit<TelemetryRoute, 'method' | 'path'>;\n\t}[],\n): TelemetryRoute[] {\n\treturn endpoints.flatMap((endpoint) =>\n\t\tendpoint.telemetry\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tmethod: endpoint.method,\n\t\t\t\t\t\tpath: endpoint.route,\n\t\t\t\t\t\t...endpoint.telemetry,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: [],\n\t);\n}\n\n/**\n * What an entry starts OpenTelemetry with, decided at build time from the\n * process's edge to a `Telemetry` node.\n *\n * Only built for a process with that edge — every other process gets the\n * stub, which loads nothing — and only once the packages it needs resolve:\n * a process that uses the node and cannot load them fails the build with\n * {@link TelemetryPackagesMissing}.\n */\nexport interface TelemetryContext {\n\t/** `service.name` — the surface or worker; `OTEL_SERVICE_NAME` overrides it. */\n\tserviceName: string;\n\t/** `service.namespace` — the workspace the app is part of. */\n\tserviceNamespace?: string;\n\t/** The node's `ignorePaths`. */\n\tignorePaths: readonly string[];\n\t/** The node's `attributes`, on every span and log record. */\n\tattributes: Readonly<Record<string, string>>;\n\t/** Each route that says something of its own. */\n\troutes: readonly TelemetryRoute[];\n}\n\n/**\n * Every package `@geekmidas/telescope/instrumentation` loads at runtime.\n *\n * All of them, rather than one as a proxy: the production bundle follows each\n * import, and a single missing one fails the build rather than the telemetry.\n */\nexport const TELEMETRY_PACKAGES = [\n\t'@opentelemetry/api',\n\t'@opentelemetry/auto-instrumentations-node',\n\t'@opentelemetry/exporter-logs-otlp-http',\n\t'@opentelemetry/exporter-trace-otlp-http',\n\t'@opentelemetry/instrumentation-pino',\n\t'@opentelemetry/resources',\n\t'@opentelemetry/sdk-logs',\n\t'@opentelemetry/sdk-node',\n\t'@opentelemetry/sdk-trace-base',\n\t'@opentelemetry/sdk-trace-node',\n\t'@opentelemetry/semantic-conventions',\n] as const;\n\n/**\n * The directory of `name` as `node_modules` lookup finds it from `fromDir`,\n * symlinks resolved — or undefined.\n *\n * The directory walk alone, not `require.resolve`: that also searches\n * `NODE_PATH`, which a package manager's bin shim sets to its own store, so a\n * `gkm build` run through one would find packages the app never installed —\n * and the bundler, which does not look there, would then fail on them.\n */\nfunction packageDir(fromDir: string, name: string): string | undefined {\n\tfor (let dir = resolve(fromDir); ; dir = dirname(dir)) {\n\t\tconst candidate = join(dir, 'node_modules', name);\n\t\tif (existsSync(join(candidate, 'package.json'))) {\n\t\t\treturn realpathSync(candidate);\n\t\t}\n\t\tif (dirname(dir) === dir) return undefined;\n\t}\n}\n\n/**\n * The telemetry packages that do not resolve the way the bundler will resolve\n * them — telescope from the app, and the OpenTelemetry packages from\n * telescope, where a peer is linked under pnpm, which need not be where the\n * app sees it. Empty when every one does.\n */\nexport function missingTelemetryPackages(appRoot: string): string[] {\n\tconst telescope = packageDir(appRoot, '@geekmidas/telescope');\n\tif (!telescope) return ['@geekmidas/telescope', ...TELEMETRY_PACKAGES];\n\n\treturn TELEMETRY_PACKAGES.filter((pkg) => !packageDir(telescope, pkg));\n}\n\n/**\n * An app uses a `Telemetry` construct and cannot load what it needs to.\n *\n * Failed at build, not at startup: a process that was given telemetry and\n * quietly ran without it is an outage nobody can see into.\n */\nexport class TelemetryPackagesMissing extends Error {\n\treadonly command: string;\n\n\tconstructor(\n\t\treadonly app: string,\n\t\treadonly missing: readonly string[],\n\t\t/** The app's directory, relative to where the command is run. */\n\t\treadonly dir: string,\n\t) {\n\t\tconst command = `pnpm --dir ${dir || '.'} add ${missing.join(' ')}`;\n\t\tsuper(\n\t\t\t`'${app}' uses a Telemetry construct, and ${missing.join(', ')} ` +\n\t\t\t\t`${missing.length === 1 ? 'does' : 'do'} not resolve from it — its ` +\n\t\t\t\t'server would start without exporting a span. Add them to the app ' +\n\t\t\t\t`and build again:\\n\\n ${command}\\n`,\n\t\t);\n\t\tthis.name = 'TelemetryPackagesMissing';\n\t\tthis.command = command;\n\t}\n}\n\n/**\n * The telemetry half of a build context: undefined for a process with no\n * edge to a `Telemetry` node, and — for one with an edge — what its entry\n * starts with, once every package it needs resolves.\n */\nexport function telemetryFor(options: {\n\t/** The node the process emits through — see `telemetryOf`. */\n\tnode: TelemetryDeclaration | undefined;\n\tappRoot: string;\n\t/** The app's name, for the error. */\n\tapp: string;\n\t/** Where `pnpm` is run from, for the error's command. */\n\tcwd?: string;\n\tserviceName: string;\n\tworkspaceName?: string;\n\troutes?: readonly TelemetryRoute[];\n}): TelemetryContext | undefined {\n\tconst { node } = options;\n\tif (!node) return undefined;\n\n\tconst missing = missingTelemetryPackages(options.appRoot);\n\tif (missing.length > 0) {\n\t\tthrow new TelemetryPackagesMissing(\n\t\t\toptions.app,\n\t\t\tmissing,\n\t\t\trelative(options.cwd ?? process.cwd(), options.appRoot),\n\t\t);\n\t}\n\n\treturn {\n\t\tserviceName: options.serviceName,\n\t\t...(options.workspaceName && { serviceNamespace: options.workspaceName }),\n\t\tignorePaths: [...(node.ignorePaths ?? [])],\n\t\tattributes: { ...node.attributes },\n\t\troutes: [...(options.routes ?? [])],\n\t};\n}\n\nconst HEADER = `/**\n * Generated by gkm, for the production entry and the \\`gkm dev\\` one alike.\n *\n * Starts OpenTelemetry when OTEL_EXPORTER_OTLP_ENDPOINT names a collector, and\n * does nothing otherwise: without it, the telemetry packages are not loaded.\n * The standard OTEL_* variables (OTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARG,\n * OTEL_EXPORTER_OTLP_HEADERS, OTEL_RESOURCE_ATTRIBUTES, OTEL_SERVICE_NAME)\n * configure the rest.\n */\n\nimport type { MiddlewareHandler } from 'hono';\n\nexport interface StartTelemetryOptions {\n /** Requests whose spans would only be noise: health checks, by default. */\n ignorePaths?: string[];\n /**\n * The API's own sites — the origins its CORS allows — read on each request.\n * Their \\`traceparent\\` is continued, as is an internal caller's; anyone\n * else's starts a new trace linked to the one it claimed.\n */\n trustedOrigins?: () => readonly string[];\n}\n`;\n\n/** `/users/:id` as a pattern over a request's path. */\nfunction routePattern(path: string): string {\n\tconst body = path\n\t\t.split('/')\n\t\t.map((segment) => {\n\t\t\tif (segment.startsWith(':')) return '[^/]+';\n\t\t\tif (segment === '*') return '.*';\n\t\t\treturn segment.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\t\t})\n\t\t.join('/');\n\treturn `^${body}/?$`;\n}\n\n/**\n * The entry's `telemetry.ts`: a `startTelemetry()` the server awaits before it\n * imports the app, so the libraries the app loads are instrumented. It returns\n * the Hono middleware that traces each request, or undefined when telemetry is\n * off — the server mounts it ahead of every route.\n *\n * Without a context — a process with no edge to a `Telemetry` node — it is a\n * stub that loads nothing and says nothing: telemetry is not something that\n * process was given.\n *\n * Failing to start telemetry warns (a named `TelemetryUnavailable`, through\n * `process.emitWarning`) and never stops the server: an outage in observability\n * is not a reason to have an outage in the service.\n */\nexport function generateTelemetryModule(\n\ttelemetry: TelemetryContext | undefined,\n): string {\n\tif (!telemetry) {\n\t\treturn `${HEADER}\n// This process has no Telemetry construct, so there is nothing to start.\nexport async function startTelemetry(\n _options: StartTelemetryOptions = {},\n): Promise<MiddlewareHandler | undefined> {\n return undefined;\n}\n`;\n\t}\n\n\tconst options = [\n\t\t`serviceName: ${JSON.stringify(telemetry.serviceName)},`,\n\t\t...(telemetry.serviceNamespace\n\t\t\t? [`serviceNamespace: ${JSON.stringify(telemetry.serviceNamespace)},`]\n\t\t\t: []),\n\t\t// The stage the deploy set, so one service's stages stay apart.\n\t\t'deploymentEnvironment: process.env.STAGE,',\n\t\t...(Object.keys(telemetry.attributes).length\n\t\t\t? [`resourceAttributes: ${JSON.stringify(telemetry.attributes)},`]\n\t\t\t: []),\n\t\t// The exporters read OTEL_EXPORTER_OTLP_* themselves, and the SDK reads\n\t\t// OTEL_TRACES_SAMPLER — so no endpoint and no ratio are printed here.\n\t\t// The server's own shutdown decides when the process exits.\n\t\t'handleSignals: false,',\n\t\t// Neither is left to module-load hooks, which see nothing inside a\n\t\t// bundle: @geekmidas/logger emits each record through the logs API\n\t\t// itself, and the middleware returned below opens each request's span.\n\t\t// Hooked as well where they can be, every record and request would be\n\t\t// sent twice.\n\t\t'instrumentPino: false,',\n\t\t'incomingHttpSpans: false,',\n\t];\n\n\tconst routes = telemetry.routes.map((route) => ({\n\t\tmethod: route.method.toUpperCase(),\n\t\tpattern: routePattern(route.path),\n\t\t...(route.ignore ? { ignore: true } : {}),\n\t\t...(route.attributes && Object.keys(route.attributes).length\n\t\t\t? { attributes: route.attributes }\n\t\t\t: {}),\n\t}));\n\tconst withAttributes = routes.some((route) => 'attributes' in route);\n\n\treturn `${HEADER}\n/** Telemetry was asked for and cannot start. The server runs without it. */\nexport class TelemetryUnavailable extends Error {\n constructor(readonly reason: string, options?: { cause?: unknown }) {\n super(\n \\`OTEL_EXPORTER_OTLP_ENDPOINT is set, but \\${reason} This server exports no traces or logs until that is fixed.\\`,\n options,\n );\n this.name = 'TelemetryUnavailable';\n }\n}\n\n/** The Telemetry construct's own ignored paths, beside the entry's. */\nconst IGNORE_PATHS: string[] = ${JSON.stringify(telemetry.ignorePaths)};\n\n/** Each route's own .telemetry({ ignore, attributes }). */\nconst ROUTES: {\n method: string;\n pattern: string;\n ignore?: boolean;\n attributes?: Record<string, string>;\n}[] = ${JSON.stringify(routes)};\n\nconst MATCHERS = ROUTES.map((route) => ({ ...route, regex: new RegExp(route.pattern) }));\n\nfunction routeOf(method: string, path: string) {\n return MATCHERS.find(\n (route) => (route.method === method || route.method === 'ALL' || route.method === 'ANY') && route.regex.test(path),\n );\n}\n\nexport async function startTelemetry(\n options: StartTelemetryOptions = {},\n): Promise<MiddlewareHandler | undefined> {\n if (!process.env.OTEL_EXPORTER_OTLP_ENDPOINT) return undefined;\n\n let instrumentation;\n try {\n instrumentation = await import('@geekmidas/telescope/instrumentation');\n } catch (cause) {\n process.emitWarning(\n new TelemetryUnavailable(\n '@geekmidas/telescope/instrumentation or an @opentelemetry package it needs could not be loaded. Install them in the app and rebuild.',\n { cause },\n ),\n );\n return undefined;\n }\n\n instrumentation.setupTelemetry({\n ${options.join('\\n ')}\n });\n\n // Flush what is buffered when the process is told to stop. When nothing\n // else handles the signal, re-raise it once flushed, so the process still\n // stops the way it would have without this listener.\n for (const signal of ['SIGTERM', 'SIGINT'] as const) {\n process.once(signal, () => {\n void instrumentation.flushTelemetry().finally(() => {\n if (process.listenerCount(signal) === 0) process.kill(process.pid, signal);\n });\n });\n }\n\n // A SERVER span per request, opened by the app itself rather than by\n // hooking node:http: \\`GET /users/:id\\`, continuing an incoming traceparent\n // from the API's own sites and internal callers (a new, linked trace from\n // anyone else), with the handler — its logs, fetches and queries — running\n // inside it.\n const spans = instrumentation.honoTelemetryMiddleware({\n ignorePaths: [...(options.ignorePaths ?? []), ...IGNORE_PATHS],\n trustedOrigins: options.trustedOrigins,\n ...(MATCHERS.some((route) => route.ignore)\n ? { shouldSkip: (c) => routeOf(c.req.method, c.req.path)?.ignore === true }\n : {}),\n });\n${\n\twithAttributes\n\t\t? `\n // A route's own attributes, on the span the middleware just opened.\n const { trace } = await import('@opentelemetry/api');\n return (c, next) =>\n spans(c, async () => {\n const attributes = routeOf(c.req.method, c.req.path)?.attributes;\n if (attributes) trace.getActiveSpan()?.setAttributes(attributes);\n await next();\n });\n`\n\t\t: ` return spans;\n`\n}}\n`;\n}\n\n/**\n * What an entry starts telemetry with, before it imports the app — shared by\n * the production server, a self-serving surface's server and the `gkm dev`\n * entry, so the three cannot start it differently. Defines `requestSpans`.\n */\nexport function startTelemetryCode(ignorePaths: readonly string[]): string {\n\treturn `// The API's own sites, known once the app has read its configuration: the\n// origins its CORS allows are the ones whose trace context is continued.\nlet trustedOrigins: readonly string[] = [];\n\n// Before the app is imported, so the libraries it loads are instrumented.\nconst { startTelemetry } = await import('./telemetry.js');\nconst requestSpans = await startTelemetry({\n ignorePaths: ${JSON.stringify(ignorePaths)},\n trustedOrigins: () => trustedOrigins,\n});\n`;\n}\n\n/** Mounts `requestSpans` on `app` ahead of every route. */\nexport function mountRequestSpansCode(app: string): string {\n\treturn `// The span middleware goes on before any route, so every request runs in it.\nif (requestSpans) ${app}.use('*', requestSpans);\n`;\n}\n\n/** Writes the entry's `telemetry.ts` beside it. */\nexport async function writeTelemetryModule(\n\toutputDir: string,\n\ttelemetry: TelemetryContext | undefined,\n): Promise<void> {\n\tawait mkdir(outputDir, { recursive: true });\n\tawait writeFile(\n\t\tjoin(outputDir, 'telemetry.ts'),\n\t\tgenerateTelemetryModule(telemetry),\n\t);\n}\n","/**\n * Whether a generated client propagates trace context by default.\n *\n * `false` (the default) prints a client that sends no trace headers unless\n * its caller passes `telemetry`. Anything else prints that default into\n * `createApi`: `true`, or `{ sampleRate }` — the stage's rate, for the page\n * views no OpenTelemetry SDK is tracing. A caller's own `telemetry` option\n * still wins.\n */\nexport type ClientTelemetryDefault = boolean | { sampleRate: number };\n\n/** A `sampleRate` outside 0–1, which no sampler can honour. */\nexport class InvalidClientTelemetrySampleRate extends Error {\n\tconstructor(readonly sampleRate: number) {\n\t\tsuper(\n\t\t\t`The client's telemetry sample rate must be a number from 0 to 1, got ${sampleRate}. Pass the stage's rate, such as 0.1, or leave it out for 1.`,\n\t\t);\n\t\tthis.name = 'InvalidClientTelemetrySampleRate';\n\t}\n}\n\n/** The `telemetry` default as the generated module prints it. */\nexport function printTelemetryDefault(\n\ttelemetry: ClientTelemetryDefault,\n): string {\n\tif (typeof telemetry === 'boolean') return String(telemetry);\n\tconst { sampleRate } = telemetry;\n\tif (!Number.isFinite(sampleRate) || sampleRate < 0 || sampleRate > 1) {\n\t\tthrow new InvalidClientTelemetrySampleRate(sampleRate);\n\t}\n\treturn `{ sampleRate: ${sampleRate} }`;\n}\n\n/**\n * `--telemetry [rate]` for a client default, as `gkm openapi` takes it — and\n * as a site's Dockerfile passes it to the `gkm openapi --app` it runs.\n */\nexport function telemetryArgs(telemetry?: ClientTelemetryDefault): string[] {\n\tif (!telemetry) return [];\n\tif (telemetry === true) return ['--telemetry'];\n\treturn ['--telemetry', String(telemetry.sampleRate)];\n}\n\n/** What `--telemetry [rate]` parsed to: on, at a rate when one was given. */\nexport function parseTelemetryFlag(\n\tvalue: boolean | string | undefined,\n): ClientTelemetryDefault | undefined {\n\tif (value === undefined || value === false) return undefined;\n\tif (value === true) return true;\n\treturn { sampleRate: Number(value) };\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join, relative } from 'node:path';\nimport { Endpoint } from '@geekmidas/constructs/endpoints';\nimport type { BuildContext } from '../build/types';\nimport type { RouteInfo } from '../types';\nimport type { StorageDrivers } from './drivers';\nimport {\n\tConstructGenerator,\n\ttype GeneratedConstruct,\n\ttype GeneratorOptions,\n} from './Generator';\nimport {\n\tmountRequestSpansCode,\n\tstartTelemetryCode,\n\twriteTelemetryModule,\n} from './telemetry';\n\n/**\n * How a generated entry gets its logger and environment parser.\n *\n * From the surface it serves, when there is one: the surface holds both as\n * *objects*, and discovery already recorded which module declares it, so the\n * entry imports that module and reads them off it.\n *\n * The alternative — the shape this replaces — was naming both in config as\n * module paths, `envParser: './config/env#envParser'`, and printing an import\n * for each. Two strings per app, checked by nothing, describing files the\n * surface was already holding the contents of.\n *\n * Falls back to the configured paths for an app that declares no surface.\n */\nexport function runtimeFor(\n\tcontext: BuildContext,\n\tfromDir: string,\n\t/**\n\t * The construct that owns the thing being generated.\n\t *\n\t * An endpoint's surface, or the worker that handed out the cron factory.\n\t * Omitted for the app entry itself, which belongs to the primary surface.\n\t */\n\towner?: string,\n): { imports: string; bindings: string } {\n\tconst module = owner\n\t\t? (context.owners?.[owner] ?? context.surface?.module)\n\t\t: context.surface?.module;\n\n\tif (module) {\n\t\tconst specifier = importSpecifier(fromDir, module.specifier);\n\n\t\t// Bound to the same names the rest of the entry already uses, so what\n\t\t// changes is where they come from and nothing else.\n\t\treturn {\n\t\t\timports: `import { ${module.exportName} as __surface } from '${specifier}';`,\n\t\t\tbindings: [\n\t\t\t\t'// What this construct was declared with. Objects, not module paths:',\n\t\t\t\t'// the entry imports the owner, so there is nothing to print.',\n\t\t\t\t'const envParser = __surface.envParser;',\n\t\t\t\t'const logger = __surface.logger;',\n\t\t\t].join('\\n'),\n\t\t};\n\t}\n\n\t// No fallback. Every runnable comes from a factory a `RestApi` or a `Worker`\n\t// handed out, and that construct holds the objects — so there is no longer a\n\t// case where the only thing we know about a logger is a module path somebody\n\t// typed into config.\n\tthrow new Error(\n\t\t`No owner to take a logger and an environment parser from${\n\t\t\towner ? ` for \"${owner}\"` : ''\n\t\t}. Build it from a \\`RestApi\\` or a \\`Worker\\`.`,\n\t);\n}\n\n/** A relative specifier ESM will accept: always prefixed, never bare. */\nfunction importSpecifier(fromDir: string, target: string): string {\n\tconst rel = relative(fromDir, target).replace(/\\.ts$/, '.js');\n\n\treturn rel.startsWith('.') ? rel : `./${rel}`;\n}\n\n/**\n * CORS for a surface, derived rather than hand-written.\n *\n * Who may call a surface is already in the graph — every construct that\n * declared an edge to it — and reaches the process as one comma-separated\n * value. An application that maintained this by hand maintained a list that was\n * wrong in two directions at once: it trusted origins nothing was serving, and\n * it could not name hosts a deploy had not chosen yet.\n *\n * Only the parts a graph cannot answer are configurable, and every one has a\n * default: a surface that says nothing about CORS still gets it.\n */\nexport function corsFor(surface: BuildContext['surface']): {\n\timports: string;\n\tsetup: string;\n} {\n\tif (!surface) return { imports: '', setup: '' };\n\n\tconst cors = surface.cors ?? {};\n\t// `traceparent` and `tracestate` always: a site's client sends them when\n\t// its telemetry is on, and a preflight that refused them would fail every\n\t// request it makes rather than only its tracing. The API decides whether\n\t// to continue the trace (it does only for these same origins), so allowing\n\t// the headers grants nothing on its own.\n\tconst allowHeaders = [\n\t\t...new Set([\n\t\t\t'content-type',\n\t\t\t'authorization',\n\t\t\t'traceparent',\n\t\t\t'tracestate',\n\t\t\t...(cors.allowHeaders ?? []),\n\t\t]),\n\t];\n\n\treturn {\n\t\timports: `import { cors } from 'hono/cors';`,\n\t\tsetup: `\n // Who may call ${surface.id}, read off the graph rather than listed here.\n {\n const { origins } = envParser\n .create((get) => ({\n origins: get('${surface.trustedOriginsKey}')\n .string()\n .default('')\n .transform((value: string) =>\n value.split(',').map((origin) => origin.trim()).filter(Boolean),\n ),\n }))\n .parse();\n\n // The same origins are the ones whose trace context the request spans\n // continue — the server reads them back from \\`createApp\\`.\n trustedOrigins.push(...origins);\n\n honoApp.use('*', cors({\n origin: origins,\n allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],\n allowHeaders: ${JSON.stringify(allowHeaders)},${\n\t\t\t\tcors.exposeHeaders\n\t\t\t\t\t? `\\n exposeHeaders: ${JSON.stringify(cors.exposeHeaders)},`\n\t\t\t\t\t: ''\n\t\t\t}\n credentials: ${cors.credentials ?? true},\n maxAge: ${cors.maxAge ?? 86400},\n }));\n }\n`,\n\t};\n}\n\nexport class EndpointGenerator extends ConstructGenerator<\n\tEndpoint<any, any, any, any, any, any, any, any, any, any, any, any>,\n\tRouteInfo[]\n> {\n\tisConstruct(\n\t\tvalue: any,\n\t): value is Endpoint<\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany\n\t> {\n\t\treturn Endpoint.isEndpoint(value);\n\t}\n\n\tasync build(\n\t\tcontext: BuildContext,\n\t\tconstructs: GeneratedConstruct<\n\t\t\tEndpoint<any, any, any, any, any, any, any, any, any, any, any, any>\n\t\t>[],\n\t\toutputDir: string,\n\t\toptions?: GeneratorOptions,\n\t): Promise<RouteInfo[]> {\n\t\tconst target = options?.target ?? 'aws';\n\t\tconst root = options?.root ?? process.cwd();\n\t\tconst enableOpenApi = options?.enableOpenApi || false;\n\t\tconst logger = console;\n\t\tconst routes: RouteInfo[] = [];\n\n\t\tif (constructs.length === 0) {\n\t\t\treturn routes;\n\t\t}\n\n\t\tif (target === 'server') {\n\t\t\t// Generate endpoints.ts and app.ts\n\t\t\tawait this.generateEndpointsFile(outputDir, constructs);\n\t\t\tconst appFile = await this.generateAppFile(outputDir, context);\n\n\t\t\troutes.push({\n\t\t\t\tpath: '*',\n\t\t\t\tmethod: 'ALL',\n\t\t\t\thandler: relative(root, appFile),\n\t\t\t\tauthorizer: 'none',\n\t\t\t});\n\n\t\t\tlogger.log(\n\t\t\t\t`Generated server with ${constructs.length} endpoints${enableOpenApi ? ' (OpenAPI enabled)' : ''}`,\n\t\t\t);\n\n\t\t\treturn routes;\n\t\t}\n\n\t\t// One Lambda per endpoint, behind an HTTP API.\n\t\tconst routesDir = join(outputDir, 'routes');\n\t\tawait mkdir(routesDir, { recursive: true });\n\n\t\tfor (const { key, construct, path } of constructs) {\n\t\t\tconst handlerFile = await this.generateHandlerFile(\n\t\t\t\troutesDir,\n\t\t\t\tpath.relative,\n\t\t\t\tkey,\n\t\t\t\tcontext,\n\t\t\t);\n\n\t\t\tconst routeInfo: RouteInfo = {\n\t\t\t\tpath: construct._path,\n\t\t\t\tmethod: construct.method,\n\t\t\t\thandler: relative(root, handlerFile).replace(/\\.ts$/, '.handler'),\n\t\t\t\ttimeout: construct.timeout,\n\t\t\t\tmemorySize: construct.memorySize,\n\t\t\t\tenvironment: await construct.getEnvironment({\n\t\t\t\t\tmarkOptional: context.markOptional,\n\t\t\t\t}),\n\t\t\t\tdependencies: construct.constructs,\n\t\t\t\tauthorizer: construct.authorizer?.name ?? 'none',\n\t\t\t};\n\n\t\t\troutes.push(routeInfo);\n\t\t\tlogger.log(`Generated handler for ${routeInfo.method} ${routeInfo.path}`);\n\t\t}\n\n\t\treturn routes;\n\t}\n\n\tprivate async generateHandlerFile(\n\t\toutputDir: string,\n\t\tsourceFile: string,\n\t\texportName: string,\n\t\tcontext: BuildContext,\n\t): Promise<string> {\n\t\tconst handlerPath = join(outputDir, `${exportName}.ts`);\n\n\t\tconst relativePath = relative(dirname(handlerPath), sourceFile);\n\t\tconst importPath = relativePath.replace(/\\.ts$/, '.js');\n\n\t\tawait writeFile(\n\t\t\thandlerPath,\n\t\t\tthis.generateAWSApiGatewayV2Handler(\n\t\t\t\timportPath,\n\t\t\t\texportName,\n\t\t\t\tcontext.storageDrivers,\n\t\t\t),\n\t\t);\n\t\treturn handlerPath;\n\t}\n\n\tprivate async generateEndpointsFile(\n\t\toutputDir: string,\n\t\tendpoints: GeneratedConstruct<\n\t\t\tEndpoint<any, any, any, any, any, any, any, any, any, any, any, any>\n\t\t>[],\n\t): Promise<string> {\n\t\tconst endpointsFileName = 'endpoints.ts';\n\t\tconst endpointsPath = join(outputDir, endpointsFileName);\n\n\t\t// Group imports by file\n\t\tconst importsByFile = new Map<string, string[]>();\n\n\t\tfor (const { path, key } of endpoints) {\n\t\t\tconst relativePath = relative(dirname(endpointsPath), path.relative);\n\t\t\tconst importPath = relativePath.replace(/\\.ts$/, '.js');\n\n\t\t\tif (!importsByFile.has(importPath)) {\n\t\t\t\timportsByFile.set(importPath, []);\n\t\t\t}\n\t\t\timportsByFile.get(importPath)?.push(key);\n\t\t}\n\n\t\t// Generate import statements for endpoints\n\t\tconst endpointImports = Array.from(importsByFile.entries())\n\t\t\t.map(\n\t\t\t\t([importPath, exports]) =>\n\t\t\t\t\t`import { ${exports.join(', ')} } from '${importPath}';`,\n\t\t\t)\n\t\t\t.join('\\n');\n\n\t\tconst allExportNames = endpoints.map(({ key }) => key);\n\n\t\t// One registration for every build, production included: each endpoint\n\t\t// is served by HonoEndpoint, which is what `gkm dev` and a feature test\n\t\t// serve it with. Production used to generate its own handlers per\n\t\t// \"tier\", and each was a hand-copy of the adaptor that drifted from it —\n\t\t// a session left undefined, an HttpError answered 500, an auditor\n\t\t// hard-coded to undefined, cookies a handler set never sent.\n\t\tconst content = `import type { EnvironmentParser } from '@geekmidas/envkit';\nimport type { Logger } from '@geekmidas/logger';\nimport { HonoEndpoint } from '@geekmidas/constructs/hono';\nimport { Endpoint } from '@geekmidas/constructs/endpoints';\nimport { ServiceDiscovery } from '@geekmidas/services';\nimport type { Hono } from 'hono';\n${endpointImports}\n\nexport const endpoints: Endpoint<any, any, any, any, any, any, any, any, any, any, any, any>[] = [\n ${allExportNames.join(',\\n ')}\n];\n\nexport async function setupEndpoints(\n app: Hono,\n envParser: EnvironmentParser<any>,\n logger: Logger,\n enableOpenApi: boolean = true,\n): Promise<void> {\n const serviceDiscovery = ServiceDiscovery.getInstance(envParser);\n\n // Configure OpenAPI options based on enableOpenApi flag\n const openApiOptions: any = enableOpenApi ? {\n docsPath: '/__docs',\n openApiOptions: {\n title: 'API Documentation',\n version: '1.0.0',\n description: 'Generated API documentation'\n }\n } : { docsPath: false };\n\n HonoEndpoint.addRoutes(endpoints, serviceDiscovery, app, openApiOptions);\n\n // Add Swagger UI if OpenAPI is enabled\n if (enableOpenApi) {\n try {\n const { swaggerUI } = await import('@hono/swagger-ui');\n app.get('/__docs/ui', swaggerUI({ url: '/__docs' }));\n } catch {\n // @hono/swagger-ui not installed, skip Swagger UI\n }\n }\n}\n`;\n\n\t\tawait writeFile(endpointsPath, content);\n\n\t\treturn endpointsPath;\n\t}\n\n\tprivate async generateAppFile(\n\t\toutputDir: string,\n\t\tcontext: BuildContext,\n\t): Promise<string> {\n\t\t// What both entries start telemetry from — the production server and\n\t\t// `gkm dev`'s — decided by the surface's edge to a `Telemetry` node.\n\t\tawait writeTelemetryModule(outputDir, context.telemetry);\n\n\t\t// Use production generator if in production mode\n\t\tif (context.production?.enabled) {\n\t\t\treturn this.generateProductionAppFile(outputDir, context);\n\t\t}\n\n\t\tconst appFileName = 'app.ts';\n\t\tconst appPath = join(outputDir, appFileName);\n\n\t\t// The logger and the env parser, from the surface that already holds them.\n\t\tconst runtime = runtimeFor(context, dirname(appPath));\n\n\t\t// Generate telescope imports and setup if enabled\n\t\tconst telescopeEnabled = context.telescope?.enabled;\n\t\tconst telescopeWebSocketEnabled = context.telescope?.websocket;\n\t\tconst usesExternalTelescope = !!context.telescope?.telescopePath;\n\n\t\t// Generate imports based on whether telescope is external or inline\n\t\tconst telescopeFromSurface =\n\t\t\t!!context.surface?.module && !usesExternalTelescope;\n\n\t\tlet telescopeImports = '';\n\t\tif (telescopeEnabled) {\n\t\t\tif (telescopeFromSurface) {\n\t\t\t\t// The entry already imports the surface, which may or may not have\n\t\t\t\t// been given a Telescope — an object, so only the running entry can\n\t\t\t\t// tell. The inline one stands in when it was not.\n\t\t\t\ttelescopeImports = `import { Telescope, InMemoryStorage } from '@geekmidas/telescope';\nimport { createApi, createMiddleware } from '@geekmidas/telescope/hono';`;\n\t\t\t} else if (usesExternalTelescope) {\n\t\t\t\tconst relativeTelescopePath = relative(\n\t\t\t\t\tdirname(appPath),\n\t\t\t\t\tcontext.telescope?.telescopePath!,\n\t\t\t\t);\n\t\t\t\ttelescopeImports = `import ${context.telescope?.telescopeImportPattern} from '${relativeTelescopePath}';\nimport { createApi, createMiddleware } from '@geekmidas/telescope/hono';`;\n\t\t\t} else {\n\t\t\t\ttelescopeImports = `import { Telescope, InMemoryStorage } from '@geekmidas/telescope';\nimport { createApi, createMiddleware } from '@geekmidas/telescope/hono';`;\n\t\t\t}\n\t\t}\n\n\t\t// The database's read-only JSON API, from the declared database. The\n\t\t// client comes from the construct, so what it reads is by definition what\n\t\t// the handlers write to.\n\t\tconst databaseApi = context.databaseApi;\n\t\tconst databaseApiImports = databaseApi\n\t\t\t? `import { snifferContext } from '@geekmidas/constructs';\nimport { createIntrospectionHandler } from '@geekmidas/db/introspect';\nimport { ${databaseApi.database.exportName} as __introspectedDb } from '${importSpecifier(dirname(appPath), databaseApi.database.specifier)}';`\n\t\t\t: '';\n\n\t\t// Generate imports for server hooks\n\t\tconst cors = corsFor(context.surface);\n\t\tlet hooksImports = '';\n\t\tlet beforeSetupCall = '';\n\t\tlet afterSetupCall = '';\n\t\tif (context.hooks?.serverHooksPath) {\n\t\t\tconst relativeHooksPath = relative(\n\t\t\t\tdirname(appPath),\n\t\t\t\tcontext.hooks.serverHooksPath,\n\t\t\t);\n\t\t\thooksImports = `import * as serverHooks from '${relativeHooksPath}';`;\n\t\t\tbeforeSetupCall = `\n // Call beforeSetup hook if defined\n if (typeof serverHooks.beforeSetup === 'function') {\n await serverHooks.beforeSetup(honoApp, { envParser, logger });\n }\n`;\n\t\t\tafterSetupCall = `\n // Call afterSetup hook if defined\n if (typeof serverHooks.afterSetup === 'function') {\n await serverHooks.afterSetup(honoApp, { envParser, logger });\n }\n`;\n\t\t}\n\n\t\tconst telescopeWebSocketSetupCode = telescopeWebSocketEnabled\n\t\t\t? `\n // Setup WebSocket for real-time telescope updates\n try {\n const { createNodeWebSocket } = await import('@hono/node-ws');\n const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app: honoApp });\n // Add WebSocket route directly to main app (sub-app routes don't support WS upgrade)\n honoApp.get('${context.telescope?.path}/ws', upgradeWebSocket(() => ({\n onOpen: (_event: Event, ws: any) => {\n telescope.addWsClient(ws);\n },\n onClose: (_event: Event, ws: any) => {\n telescope.removeWsClient(ws);\n },\n onMessage: (event: MessageEvent, ws: any) => {\n try {\n const data = JSON.parse(event.data);\n if (data.type === 'ping') {\n ws.send(JSON.stringify({ type: 'pong' }));\n }\n } catch {\n // Ignore invalid messages\n }\n },\n })));\n // Store injectWebSocket for server entry to call after serve()\n (honoApp as any).__injectWebSocket = injectWebSocket;\n logger.info('Telescope WebSocket enabled');\n } catch (e) {\n logger.warn({ err: e }, 'WebSocket support not available - install @hono/node-ws for real-time updates');\n }\n`\n\t\t\t: '';\n\n\t\t// Generate telescope setup - either use external instance or create inline\n\t\tlet telescopeSetup = '';\n\t\tif (telescopeEnabled) {\n\t\t\tif (telescopeFromSurface || usesExternalTelescope) {\n\t\t\t\t// Use external telescope instance - no need to create one\n\t\t\t\ttelescopeSetup = `\n${telescopeWebSocketSetupCode}\n // Add telescope middleware (before endpoints to capture all requests)\n honoApp.use('*', createMiddleware(telescope));\n\n // Mount Telescope's JSON API\n const telescopeApi = createApi(telescope);\n honoApp.route('${context.telescope?.path}', telescopeApi);\n`;\n\t\t\t} else {\n\t\t\t\t// Create inline telescope instance\n\t\t\t\ttelescopeSetup = `\n // Setup Telescope for debugging/monitoring\n const telescopeStorage = new InMemoryStorage({ maxEntries: ${context.telescope?.maxEntries} });\n const telescope = new Telescope({\n enabled: true,\n path: '${context.telescope?.path}',\n ignorePatterns: ${JSON.stringify(context.telescope?.ignore)},\n recordBody: ${context.telescope?.recordBody},\n storage: telescopeStorage,\n });\n${telescopeWebSocketSetupCode}\n // Add telescope middleware (before endpoints to capture all requests)\n honoApp.use('*', createMiddleware(telescope));\n\n // Mount Telescope's JSON API\n const telescopeApi = createApi(telescope);\n honoApp.route('${context.telescope?.path}', telescopeApi);\n`;\n\t\t\t}\n\t\t}\n\n\t\tconst databaseApiSetup = databaseApi\n\t\t\t? `\n // The database's structure and rows as JSON, read-only, for whatever tool\n // the developer points at it. Dev only: it answers anyone who can reach the\n // port.\n const databaseApi = createIntrospectionHandler({\n db: await __introspectedDb.service.register({ envParser, context: snifferContext }),\n basePath: '${databaseApi.path}',\n });\n honoApp.all('${databaseApi.path}/*', (c) => databaseApi(c.req.raw));\n`\n\t\t\t: '';\n\n\t\tconst content = `/**\n * Generated server application\n *\n * ⚠️ WARNING: This is for LOCAL DEVELOPMENT ONLY\n * The subscriber polling mechanism is not production-ready.\n * For production, use AWS Lambda with SQS/SNS event sources.\n */\nimport { Hono } from 'hono';\nimport type { Hono as HonoType } from 'hono';\nimport { setupEndpoints } from './endpoints.js';\nimport { setupSubscribers } from './subscribers.js';\nimport { setupQueues } from './queues.js';\nimport { setupCrons } from './crons.js';\n${runtime.imports}\n${telescopeImports}\n${databaseApiImports}\n${hooksImports}\n${cors.imports}\n${context.storageDrivers?.imports ?? ''}\n\n${runtime.bindings}\n${\n\ttelescopeEnabled && telescopeFromSurface\n\t\t? `// The surface's own Telescope, or the one \\`telescope\\` config describes.\nconst telescope =\n __surface.telescope ??\n new Telescope({\n enabled: true,\n path: '${context.telescope?.path}',\n ignorePatterns: ${JSON.stringify(context.telescope?.ignore)},\n recordBody: ${context.telescope?.recordBody},\n storage: new InMemoryStorage({ maxEntries: ${context.telescope?.maxEntries} }),\n });`\n\t\t: ''\n}\n\n${\n\tcontext.storageDrivers?.setup\n\t\t? `// Which drivers exist — storage, cache, events — is the entry point's\n// decision: the scheme in each injected URL picks one, so no construct and no\n// application module names a provider.\n${context.storageDrivers.setup}\n`\n\t\t: ''\n}\n${\n\tcontext.surface\n\t\t? `// Who this server is to Postgres: application_name on every connection.\nprocess.env.GKM_APP_NAME ??= ${JSON.stringify(context.surface.id)};\n\n`\n\t\t: ''\n}export interface ServerApp {\n app: HonoType;\n /** The origins this API's CORS allows: its own sites. */\n trustedOrigins: readonly string[];\n start: (options?: {\n port?: number;\n /** Returns the server, so a shutdown can stop it taking requests. */\n serve: (app: HonoType, port: number) => unknown;\n }) => Promise<void>;\n}\n\n/**\n * Create and configure the Hono application\n *\n * @param app - Optional Hono app instance to configure (creates new one if not provided)\n * @param enableOpenApi - Enable OpenAPI documentation (default: true)\n * @returns Server app with configured Hono app and start function\n *\n * @example\n * // With Bun\n * import { createApp } from './.gkm/server/app.js';\n *\n * const { app, start } = await createApp();\n *\n * await start({\n * port: 3000,\n * serve: (app, port) => {\n * Bun.serve({ port, fetch: app.fetch });\n * }\n * });\n *\n * @example\n * // With Node.js (using @hono/node-server)\n * import { serve } from '@hono/node-server';\n * import { createApp } from './.gkm/server/app.js';\n *\n * const { app, start } = await createApp();\n *\n * await start({\n * port: 3000,\n * serve: (app, port) => {\n * serve({ fetch: app.fetch, port });\n * }\n * });\n */\nexport async function createApp(app?: HonoType, enableOpenApi: boolean = true): Promise<ServerApp> {\n const honoApp = app || new Hono();\n const trustedOrigins: string[] = [];\n${telescopeSetup}${cors.setup}${beforeSetupCall}${databaseApiSetup}\n // Setup HTTP endpoints\n await setupEndpoints(honoApp, envParser, logger, enableOpenApi);\n${afterSetupCall}\n\n return {\n app: honoApp,\n trustedOrigins,\n async start(options) {\n if (!options?.serve) {\n throw new Error(\n 'serve function is required. Pass a serve function for your runtime:\\\\n' +\n ' - Bun: (app, port) => Bun.serve({ port, fetch: app.fetch })\\\\n' +\n ' - Node: (app, port) => serve({ fetch: app.fetch, port })'\n );\n }\n\n const port = options.port ?? 3000;\n\n // Mount pushed subscribers' routes and start polled ones. What comes\n // back subscribes the routes, once the server can answer them.\n const subscribeForPush = await setupSubscribers(honoApp, envParser, logger).catch((error) => {\n logger.error({ err: error }, 'Failed to start subscribers');\n return async (_port: number) => {};\n });\n\n // Start queue workers in background (non-blocking, local development only)\n await setupQueues(envParser, logger).catch((error) => {\n logger.error({ err: error }, 'Failed to start queue workers');\n });\n\n // Schedule this app's crons. Caught like the others: a scheduler that\n // cannot start is not a reason for the HTTP server not to.\n await setupCrons(envParser, logger).catch((error) => {\n logger.error({ err: error }, 'Failed to schedule crons');\n });\n\n logger.info({ port }, 'Starting server');\n\n // Start HTTP server using provided serve function\n await options.serve(honoApp, port);\n\n logger.info({ port }, 'Server started');\n\n await subscribeForPush(port);\n }\n };\n}\n\n// Default export for convenience\nexport default createApp;\n`;\n\n\t\tawait writeFile(appPath, content);\n\n\t\treturn appPath;\n\t}\n\n\t/**\n\t * One Lambda, one endpoint — and no environment import.\n\t *\n\t * The endpoint was built from its surface, so it carries the parser the\n\t * adaptor needs. This used to read\n\t * `import { envParser } from '../../config/env'`, a line the build wrote\n\t * because a generator can print a module specifier and cannot print an\n\t * object. Nothing has to be printed now, which is also why nothing has to be\n\t * named in config.\n\t */\n\tprivate generateAWSApiGatewayV2Handler(\n\t\timportPath: string,\n\t\texportName: string,\n\t\tdrivers?: StorageDrivers,\n\t): string {\n\t\treturn `import { AmazonApiGatewayV2Endpoint } from '@geekmidas/constructs/aws';\nimport { ${exportName} } from '${importPath}';\n${drivers?.imports ?? ''}\n${drivers?.setup ? `\\n// The handler registers the drivers its target needs.\\n${drivers.setup}\\n` : ''}\nconst adapter = new AmazonApiGatewayV2Endpoint(${exportName});\n\nexport const handler = adapter.handler;\n`;\n\t}\n\n\t/**\n\t * Generate a production-optimized app.ts file\n\t * No dev tools (Telescope, Studio, WebSocket), includes health checks and graceful shutdown\n\t */\n\tprivate async generateProductionAppFile(\n\t\toutputDir: string,\n\t\tcontext: BuildContext,\n\t): Promise<string> {\n\t\tconst appFileName = 'app.ts';\n\t\tconst appPath = join(outputDir, appFileName);\n\n\t\t// The app entry belongs to the primary surface, so no owner is named.\n\t\tconst runtime = runtimeFor(context, dirname(appPath));\n\n\t\tconst production = context.production!;\n\t\tconst healthCheckPath = production.healthCheck;\n\t\tconst enableGracefulShutdown = production.gracefulShutdown;\n\t\tconst enableOpenApi = production.openapi;\n\t\tconst includeSubscribers = production.subscribers === 'include';\n\n\t\t// Generate imports for server hooks\n\t\tconst cors = corsFor(context.surface);\n\t\tlet hooksImports = '';\n\t\tlet beforeSetupCall = '';\n\t\tlet afterSetupCall = '';\n\t\tif (context.hooks?.serverHooksPath) {\n\t\t\tconst relativeHooksPath = relative(\n\t\t\t\tdirname(appPath),\n\t\t\t\tcontext.hooks.serverHooksPath,\n\t\t\t);\n\t\t\thooksImports = `import * as serverHooks from '${relativeHooksPath}';`;\n\t\t\tbeforeSetupCall = `\n // Call beforeSetup hook if defined\n if (typeof serverHooks.beforeSetup === 'function') {\n await serverHooks.beforeSetup(honoApp, { envParser, logger });\n }\n`;\n\t\t\tafterSetupCall = `\n // Call afterSetup hook if defined\n if (typeof serverHooks.afterSetup === 'function') {\n await serverHooks.afterSetup(honoApp, { envParser, logger });\n }\n`;\n\t\t}\n\n\t\t// Subscriber + queue setup code (background workers share the same flag)\n\t\tconst subscriberSetup = includeSubscribers\n\t\t\t? `\n // Mount pushed subscribers' routes and start polled ones. What comes\n // back subscribes the routes, once the server can answer them.\n const subscribeForPush = await setupSubscribers(honoApp, envParser, logger).catch((error) => {\n logger.error({ err: error }, 'Failed to start subscribers');\n return async (_port: number) => {};\n });\n\n // Start queue workers in background\n await setupQueues(envParser, logger).catch((error) => {\n logger.error({ err: error }, 'Failed to start queue workers');\n });\n\n // Schedule this app's crons. Caught like the others: a scheduler that\n // cannot start is not a reason for the HTTP server not to.\n await setupCrons(envParser, logger).catch((error) => {\n logger.error({ err: error }, 'Failed to schedule crons');\n });\n`\n\t\t\t: '';\n\n\t\tconst subscriberImport = includeSubscribers\n\t\t\t? `import { setupSubscribers } from './subscribers.js';\nimport { setupQueues } from './queues.js';\nimport { setupCrons } from './crons.js';`\n\t\t\t: '';\n\n\t\t// Graceful shutdown code\n\t\tconst gracefulShutdownCode = enableGracefulShutdown\n\t\t\t? `\n // Graceful shutdown: stop taking requests, let in-flight ones finish, then\n // close what constructs opened (database pools) — so a rolling deploy does\n // not leave the old task holding connections. Bounded by\n // GKM_SHUTDOWN_TIMEOUT_MS, 8s by default: under Docker's 10s stop timeout,\n // so the process exits on its own terms rather than being killed mid-drain.\n let isShuttingDown = false;\n\n const shutdown = async () => {\n if (isShuttingDown) return;\n isShuttingDown = true;\n const deadline = Number(process.env.GKM_SHUTDOWN_TIMEOUT_MS) || 8000;\n logger.info({ deadline }, 'Graceful shutdown initiated');\n setTimeout(() => {\n logger.warn({ deadline }, 'Shutdown deadline reached, exiting');\n process.exit(1);\n }, deadline).unref();\n await new Promise<void>((resolve) => {\n const close = (server as { close?: (done: () => void) => void } | undefined)?.close;\n if (typeof close === 'function') close.call(server, () => resolve());\n else resolve();\n });\n await runShutdownHooks((error) => logger.error({ err: error }, 'Shutdown hook failed'));\n process.exit(0);\n };\n\n process.on('SIGTERM', shutdown);\n process.on('SIGINT', shutdown);\n`\n\t\t\t: '';\n\n\t\tconst content = `/**\n * Generated production server application\n *\n * This is a production-optimized build without dev tools.\n * - No Telescope debugging dashboard\n * - No Studio database browser\n * - No WebSocket updates\n * - Includes health checks and graceful shutdown\n */\nimport { Hono } from 'hono';\nimport type { Hono as HonoType } from 'hono';\n${enableGracefulShutdown ? \"import { runShutdownHooks } from '@geekmidas/constructs';\\n\" : ''}import { setupEndpoints } from './endpoints.js';\n${subscriberImport}\n${runtime.imports}\n${runtime.bindings}\n${hooksImports}\n${cors.imports}\n${context.storageDrivers?.imports ?? ''}\n\n${\n\tcontext.storageDrivers?.setup\n\t\t? `// The entry point registers the drivers its target needs.\n${context.storageDrivers.setup}\n`\n\t\t: ''\n}\n${\n\tcontext.surface\n\t\t? `// Who this server is to Postgres: application_name on every connection.\nprocess.env.GKM_APP_NAME ??= ${JSON.stringify(context.surface.id)};\n\n`\n\t\t: ''\n}export interface ServerApp {\n app: HonoType;\n /** The origins this API's CORS allows: its own sites. */\n trustedOrigins: readonly string[];\n start: (options?: {\n port?: number;\n serve: (app: HonoType, port: number) => void | Promise<void>;\n }) => Promise<void>;\n}\n\n/**\n * Create and configure the production Hono application\n */\nexport async function createApp(app?: HonoType): Promise<ServerApp> {\n const honoApp = app || new Hono();\n const trustedOrigins: string[] = [];\n\n // Health check endpoint (always first)\n honoApp.get('${healthCheckPath}', (c) => c.json({ status: 'ok', timestamp: Date.now() }));\n honoApp.get('/ready', (c) => c.json({ ready: true }));\n\n // An HttpError a handler or a session callback throws — a 401, a 404 —\n // answers with its own status, as it does under gkm dev. The optimized\n // handlers do not catch, so without this every one of them was a 500.\n // Anything else is a 500 that says nothing about why, and is logged.\n honoApp.onError((error, c) => {\n const http = error as {\n isHttpError?: boolean;\n statusCode?: number;\n statusMessage?: string;\n code?: string;\n details?: unknown;\n };\n if (http.isHttpError === true && typeof http.statusCode === 'number') {\n return c.json(\n {\n name: error.name,\n message: error.message,\n statusCode: http.statusCode,\n statusMessage: http.statusMessage,\n code: http.code,\n details: http.details,\n },\n http.statusCode as 500,\n );\n }\n logger.error({ err: error }, 'Unhandled error');\n return c.json({ message: 'Internal Server Error' }, 500);\n });\n${cors.setup}${beforeSetupCall}\n // Setup HTTP endpoints (OpenAPI: ${enableOpenApi})\n await setupEndpoints(honoApp, envParser, logger, ${enableOpenApi});\n${afterSetupCall}\n return {\n app: honoApp,\n trustedOrigins,\n async start(options) {\n if (!options?.serve) {\n throw new Error(\n 'serve function is required. Pass a serve function for your runtime:\\\\n' +\n ' - Bun: (app, port) => Bun.serve({ port, fetch: app.fetch })\\\\n' +\n ' - Node: (app, port) => serve({ fetch: app.fetch, port })'\n );\n }\n\n const port = options.port ?? Number(process.env.PORT) ?? 3000;\n let server: unknown;\n${gracefulShutdownCode}${subscriberSetup}\n logger.info({ port }, 'Starting production server');\n\n // Start HTTP server using provided serve function\n server = await options.serve(honoApp, port);\n\n logger.info({ port }, 'Production server started');\n${includeSubscribers ? '\\n await subscribeForPush(port);' : ''}\n }\n };\n}\n\n// Default export for convenience\nexport default createApp;\n`;\n\n\t\tawait writeFile(appPath, content);\n\n\t\t// Also generate the production server entry point\n\t\tawait this.generateProductionServerEntry(outputDir, context);\n\n\t\treturn appPath;\n\t}\n\n\t/**\n\t * Generate production server.ts entry point\n\t */\n\tprivate async generateProductionServerEntry(\n\t\toutputDir: string,\n\t\tcontext: BuildContext,\n\t): Promise<void> {\n\t\tconst serverPath = join(outputDir, 'server.ts');\n\n\t\t// `telemetry.ts` beside it was written with `app.ts`, for this entry\n\t\t// and the `gkm dev` one alike, which start it the same way.\n\t\tconst content = `#!/usr/bin/env node\n/**\n * Production server entry point\n * Generated by 'gkm build --production'\n */\nimport { serve } from '@hono/node-server';\nimport { Hono } from 'hono';\n\n${startTelemetryCode([context.production?.healthCheck ?? '/health', '/ready'])}\nconst { createApp } = await import('./app.js');\n\nconst port = Number(process.env.PORT) || 3000;\n\nconst app = new Hono();\n${mountRequestSpansCode('app')}\nconst created = await createApp(app);\ntrustedOrigins = created.trustedOrigins;\nconst { start } = created;\n\nawait start({\n port,\n serve: (app, port) => serve({ fetch: app.fetch, port }),\n});\n`;\n\n\t\tawait writeFile(serverPath, content);\n\t}\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"EndpointGenerator-CwLjBCFE.cjs","names":["resolve","dirname","join","existsSync","realpathSync","relative","mkdir","writeFile","relative","ConstructGenerator","Endpoint","join","mkdir","dirname","writeFile"],"sources":["../src/generators/telemetry.ts","../src/generators/clientTelemetry.ts","../src/generators/EndpointGenerator.ts"],"sourcesContent":["import { existsSync, realpathSync } from 'node:fs';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join, relative, resolve } from 'node:path';\nimport type { TelemetryDeclaration } from '@geekmidas/manifest';\n\n/**\n * One route's own telemetry — an endpoint's `.telemetry({ ignore, attributes })`.\n */\nexport interface TelemetryRoute {\n\tmethod: string;\n\t/** The route as the surface serves it — `/users/:id`. */\n\tpath: string;\n\tignore?: boolean;\n\tattributes?: Readonly<Record<string, string>>;\n}\n\n/** Each endpoint's own `.telemetry({ ignore, attributes })`, as routes. */\nexport function routeTelemetry(\n\tendpoints: readonly {\n\t\tmethod: string;\n\t\troute: string;\n\t\ttelemetry?: Omit<TelemetryRoute, 'method' | 'path'>;\n\t}[],\n): TelemetryRoute[] {\n\treturn endpoints.flatMap((endpoint) =>\n\t\tendpoint.telemetry\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tmethod: endpoint.method,\n\t\t\t\t\t\tpath: endpoint.route,\n\t\t\t\t\t\t...endpoint.telemetry,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: [],\n\t);\n}\n\n/**\n * What an entry starts OpenTelemetry with, decided at build time from the\n * process's edge to a `Telemetry` node.\n *\n * Only built for a process with that edge — every other process gets the\n * stub, which loads nothing — and only once the packages it needs resolve:\n * a process that uses the node and cannot load them fails the build with\n * {@link TelemetryPackagesMissing}.\n */\nexport interface TelemetryContext {\n\t/** `service.name` — the surface or worker; `OTEL_SERVICE_NAME` overrides it. */\n\tserviceName: string;\n\t/** `service.namespace` — the workspace the app is part of. */\n\tserviceNamespace?: string;\n\t/** The node's `ignorePaths`. */\n\tignorePaths: readonly string[];\n\t/** The node's `attributes`, on every span and log record. */\n\tattributes: Readonly<Record<string, string>>;\n\t/** Each route that says something of its own. */\n\troutes: readonly TelemetryRoute[];\n}\n\n/**\n * Every package `@geekmidas/telescope/instrumentation` loads at runtime.\n *\n * All of them, rather than one as a proxy: the production bundle follows each\n * import, and a single missing one fails the build rather than the telemetry.\n */\nexport const TELEMETRY_PACKAGES = [\n\t'@opentelemetry/api',\n\t'@opentelemetry/auto-instrumentations-node',\n\t'@opentelemetry/exporter-logs-otlp-http',\n\t'@opentelemetry/exporter-trace-otlp-http',\n\t'@opentelemetry/instrumentation-pino',\n\t'@opentelemetry/resources',\n\t'@opentelemetry/sdk-logs',\n\t'@opentelemetry/sdk-node',\n\t'@opentelemetry/sdk-trace-base',\n\t'@opentelemetry/sdk-trace-node',\n\t'@opentelemetry/semantic-conventions',\n] as const;\n\n/**\n * The directory of `name` as `node_modules` lookup finds it from `fromDir`,\n * symlinks resolved — or undefined.\n *\n * The directory walk alone, not `require.resolve`: that also searches\n * `NODE_PATH`, which a package manager's bin shim sets to its own store, so a\n * `gkm build` run through one would find packages the app never installed —\n * and the bundler, which does not look there, would then fail on them.\n */\nfunction packageDir(fromDir: string, name: string): string | undefined {\n\tfor (let dir = resolve(fromDir); ; dir = dirname(dir)) {\n\t\tconst candidate = join(dir, 'node_modules', name);\n\t\tif (existsSync(join(candidate, 'package.json'))) {\n\t\t\treturn realpathSync(candidate);\n\t\t}\n\t\tif (dirname(dir) === dir) return undefined;\n\t}\n}\n\n/**\n * The telemetry packages that do not resolve the way the bundler will resolve\n * them — telescope from the app, and the OpenTelemetry packages from\n * telescope, where a peer is linked under pnpm, which need not be where the\n * app sees it. Empty when every one does.\n */\nexport function missingTelemetryPackages(appRoot: string): string[] {\n\tconst telescope = packageDir(appRoot, '@geekmidas/telescope');\n\tif (!telescope) return ['@geekmidas/telescope', ...TELEMETRY_PACKAGES];\n\n\treturn TELEMETRY_PACKAGES.filter((pkg) => !packageDir(telescope, pkg));\n}\n\n/**\n * An app uses a `Telemetry` construct and cannot load what it needs to.\n *\n * Failed at build, not at startup: a process that was given telemetry and\n * quietly ran without it is an outage nobody can see into.\n */\nexport class TelemetryPackagesMissing extends Error {\n\treadonly command: string;\n\n\tconstructor(\n\t\treadonly app: string,\n\t\treadonly missing: readonly string[],\n\t\t/** The app's directory, relative to where the command is run. */\n\t\treadonly dir: string,\n\t) {\n\t\tconst command = `pnpm --dir ${dir || '.'} add ${missing.join(' ')}`;\n\t\tsuper(\n\t\t\t`'${app}' uses a Telemetry construct, and ${missing.join(', ')} ` +\n\t\t\t\t`${missing.length === 1 ? 'does' : 'do'} not resolve from it — its ` +\n\t\t\t\t'server would start without exporting a span. Add them to the app ' +\n\t\t\t\t`and build again:\\n\\n ${command}\\n`,\n\t\t);\n\t\tthis.name = 'TelemetryPackagesMissing';\n\t\tthis.command = command;\n\t}\n}\n\n/**\n * The telemetry half of a build context: undefined for a process with no\n * edge to a `Telemetry` node, and — for one with an edge — what its entry\n * starts with, once every package it needs resolves.\n */\nexport function telemetryFor(options: {\n\t/** The node the process emits through — see `telemetryOf`. */\n\tnode: TelemetryDeclaration | undefined;\n\tappRoot: string;\n\t/** The app's name, for the error. */\n\tapp: string;\n\t/** Where `pnpm` is run from, for the error's command. */\n\tcwd?: string;\n\tserviceName: string;\n\tworkspaceName?: string;\n\troutes?: readonly TelemetryRoute[];\n}): TelemetryContext | undefined {\n\tconst { node } = options;\n\tif (!node) return undefined;\n\n\tconst missing = missingTelemetryPackages(options.appRoot);\n\tif (missing.length > 0) {\n\t\tthrow new TelemetryPackagesMissing(\n\t\t\toptions.app,\n\t\t\tmissing,\n\t\t\trelative(options.cwd ?? process.cwd(), options.appRoot),\n\t\t);\n\t}\n\n\treturn {\n\t\tserviceName: options.serviceName,\n\t\t...(options.workspaceName && { serviceNamespace: options.workspaceName }),\n\t\tignorePaths: [...(node.ignorePaths ?? [])],\n\t\tattributes: { ...node.attributes },\n\t\troutes: [...(options.routes ?? [])],\n\t};\n}\n\nconst HEADER = `/**\n * Generated by gkm, for the production entry and the \\`gkm dev\\` one alike.\n *\n * Starts OpenTelemetry when OTEL_EXPORTER_OTLP_ENDPOINT names a collector, and\n * does nothing otherwise: without it, the telemetry packages are not loaded.\n * The standard OTEL_* variables (OTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARG,\n * OTEL_EXPORTER_OTLP_HEADERS, OTEL_RESOURCE_ATTRIBUTES, OTEL_SERVICE_NAME)\n * configure the rest.\n */\n\nimport type { MiddlewareHandler } from 'hono';\n\nexport interface StartTelemetryOptions {\n /** Requests whose spans would only be noise: health checks, by default. */\n ignorePaths?: string[];\n /**\n * The API's own sites — the origins its CORS allows — read on each request.\n * Their \\`traceparent\\` is continued, as is an internal caller's; anyone\n * else's starts a new trace linked to the one it claimed.\n */\n trustedOrigins?: () => readonly string[];\n}\n`;\n\n/** `/users/:id` as a pattern over a request's path. */\nfunction routePattern(path: string): string {\n\tconst body = path\n\t\t.split('/')\n\t\t.map((segment) => {\n\t\t\tif (segment.startsWith(':')) return '[^/]+';\n\t\t\tif (segment === '*') return '.*';\n\t\t\treturn segment.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\t\t})\n\t\t.join('/');\n\treturn `^${body}/?$`;\n}\n\n/**\n * The entry's `telemetry.ts`: a `startTelemetry()` the server awaits before it\n * imports the app, so the libraries the app loads are instrumented. It returns\n * the Hono middleware that traces each request, or undefined when telemetry is\n * off — the server mounts it ahead of every route.\n *\n * Without a context — a process with no edge to a `Telemetry` node — it is a\n * stub that loads nothing and says nothing: telemetry is not something that\n * process was given.\n *\n * Failing to start telemetry warns (a named `TelemetryUnavailable`, through\n * `process.emitWarning`) and never stops the server: an outage in observability\n * is not a reason to have an outage in the service.\n */\nexport function generateTelemetryModule(\n\ttelemetry: TelemetryContext | undefined,\n): string {\n\tif (!telemetry) {\n\t\treturn `${HEADER}\n// This process has no Telemetry construct, so there is nothing to start.\nexport async function startTelemetry(\n _options: StartTelemetryOptions = {},\n): Promise<MiddlewareHandler | undefined> {\n return undefined;\n}\n`;\n\t}\n\n\tconst options = [\n\t\t`serviceName: ${JSON.stringify(telemetry.serviceName)},`,\n\t\t...(telemetry.serviceNamespace\n\t\t\t? [`serviceNamespace: ${JSON.stringify(telemetry.serviceNamespace)},`]\n\t\t\t: []),\n\t\t// The stage the deploy set, so one service's stages stay apart.\n\t\t'deploymentEnvironment: process.env.STAGE,',\n\t\t...(Object.keys(telemetry.attributes).length\n\t\t\t? [`resourceAttributes: ${JSON.stringify(telemetry.attributes)},`]\n\t\t\t: []),\n\t\t// The exporters read OTEL_EXPORTER_OTLP_* themselves, and the SDK reads\n\t\t// OTEL_TRACES_SAMPLER — so no endpoint and no ratio are printed here.\n\t\t// The server's own shutdown decides when the process exits.\n\t\t'handleSignals: false,',\n\t\t// Neither is left to module-load hooks, which see nothing inside a\n\t\t// bundle: @geekmidas/logger emits each record through the logs API\n\t\t// itself, and the middleware returned below opens each request's span.\n\t\t// Hooked as well where they can be, every record and request would be\n\t\t// sent twice.\n\t\t'instrumentPino: false,',\n\t\t'incomingHttpSpans: false,',\n\t];\n\n\tconst routes = telemetry.routes.map((route) => ({\n\t\tmethod: route.method.toUpperCase(),\n\t\tpattern: routePattern(route.path),\n\t\t...(route.ignore ? { ignore: true } : {}),\n\t\t...(route.attributes && Object.keys(route.attributes).length\n\t\t\t? { attributes: route.attributes }\n\t\t\t: {}),\n\t}));\n\tconst withAttributes = routes.some((route) => 'attributes' in route);\n\n\treturn `${HEADER}\n/** Telemetry was asked for and cannot start. The server runs without it. */\nexport class TelemetryUnavailable extends Error {\n constructor(readonly reason: string, options?: { cause?: unknown }) {\n super(\n \\`OTEL_EXPORTER_OTLP_ENDPOINT is set, but \\${reason} This server exports no traces or logs until that is fixed.\\`,\n options,\n );\n this.name = 'TelemetryUnavailable';\n }\n}\n\n/** The Telemetry construct's own ignored paths, beside the entry's. */\nconst IGNORE_PATHS: string[] = ${JSON.stringify(telemetry.ignorePaths)};\n\n/** Each route's own .telemetry({ ignore, attributes }). */\nconst ROUTES: {\n method: string;\n pattern: string;\n ignore?: boolean;\n attributes?: Record<string, string>;\n}[] = ${JSON.stringify(routes)};\n\nconst MATCHERS = ROUTES.map((route) => ({ ...route, regex: new RegExp(route.pattern) }));\n\nfunction routeOf(method: string, path: string) {\n return MATCHERS.find(\n (route) => (route.method === method || route.method === 'ALL' || route.method === 'ANY') && route.regex.test(path),\n );\n}\n\nexport async function startTelemetry(\n options: StartTelemetryOptions = {},\n): Promise<MiddlewareHandler | undefined> {\n if (!process.env.OTEL_EXPORTER_OTLP_ENDPOINT) return undefined;\n\n let instrumentation;\n try {\n instrumentation = await import('@geekmidas/telescope/instrumentation');\n } catch (cause) {\n process.emitWarning(\n new TelemetryUnavailable(\n '@geekmidas/telescope/instrumentation or an @opentelemetry package it needs could not be loaded. Install them in the app and rebuild.',\n { cause },\n ),\n );\n return undefined;\n }\n\n instrumentation.setupTelemetry({\n ${options.join('\\n ')}\n });\n\n // Flush what is buffered when the process is told to stop. When nothing\n // else handles the signal, re-raise it once flushed, so the process still\n // stops the way it would have without this listener.\n for (const signal of ['SIGTERM', 'SIGINT'] as const) {\n process.once(signal, () => {\n void instrumentation.flushTelemetry().finally(() => {\n if (process.listenerCount(signal) === 0) process.kill(process.pid, signal);\n });\n });\n }\n\n // A SERVER span per request, opened by the app itself rather than by\n // hooking node:http: \\`GET /users/:id\\`, continuing an incoming traceparent\n // from the API's own sites and internal callers (a new, linked trace from\n // anyone else), with the handler — its logs, fetches and queries — running\n // inside it.\n const spans = instrumentation.honoTelemetryMiddleware({\n ignorePaths: [...(options.ignorePaths ?? []), ...IGNORE_PATHS],\n trustedOrigins: options.trustedOrigins,\n ...(MATCHERS.some((route) => route.ignore)\n ? { shouldSkip: (c) => routeOf(c.req.method, c.req.path)?.ignore === true }\n : {}),\n });\n${\n\twithAttributes\n\t\t? `\n // A route's own attributes, on the span the middleware just opened.\n const { trace } = await import('@opentelemetry/api');\n return (c, next) =>\n spans(c, async () => {\n const attributes = routeOf(c.req.method, c.req.path)?.attributes;\n if (attributes) trace.getActiveSpan()?.setAttributes(attributes);\n await next();\n });\n`\n\t\t: ` return spans;\n`\n}}\n`;\n}\n\n/**\n * What an entry starts telemetry with, before it imports the app — shared by\n * the production server, a self-serving surface's server and the `gkm dev`\n * entry, so the three cannot start it differently. Defines `requestSpans`.\n */\nexport function startTelemetryCode(ignorePaths: readonly string[]): string {\n\treturn `// The API's own sites, known once the app has read its configuration: the\n// origins its CORS allows are the ones whose trace context is continued.\nlet trustedOrigins: readonly string[] = [];\n\n// Before the app is imported, so the libraries it loads are instrumented.\nconst { startTelemetry } = await import('./telemetry.js');\nconst requestSpans = await startTelemetry({\n ignorePaths: ${JSON.stringify(ignorePaths)},\n trustedOrigins: () => trustedOrigins,\n});\n`;\n}\n\n/** Mounts `requestSpans` on `app` ahead of every route. */\nexport function mountRequestSpansCode(app: string): string {\n\treturn `// The span middleware goes on before any route, so every request runs in it.\nif (requestSpans) ${app}.use('*', requestSpans);\n`;\n}\n\n/** Writes the entry's `telemetry.ts` beside it. */\nexport async function writeTelemetryModule(\n\toutputDir: string,\n\ttelemetry: TelemetryContext | undefined,\n): Promise<void> {\n\tawait mkdir(outputDir, { recursive: true });\n\tawait writeFile(\n\t\tjoin(outputDir, 'telemetry.ts'),\n\t\tgenerateTelemetryModule(telemetry),\n\t);\n}\n","/**\n * Whether a generated client propagates trace context by default.\n *\n * `false` (the default) prints a client that sends no trace headers unless\n * its caller passes `telemetry`. Anything else prints that default into\n * `createApi`: `true`, or `{ sampleRate }` — the stage's rate, for the page\n * views no OpenTelemetry SDK is tracing. A caller's own `telemetry` option\n * still wins.\n */\nexport type ClientTelemetryDefault = boolean | { sampleRate: number };\n\n/** A `sampleRate` outside 0–1, which no sampler can honour. */\nexport class InvalidClientTelemetrySampleRate extends Error {\n\tconstructor(readonly sampleRate: number) {\n\t\tsuper(\n\t\t\t`The client's telemetry sample rate must be a number from 0 to 1, got ${sampleRate}. Pass the stage's rate, such as 0.1, or leave it out for 1.`,\n\t\t);\n\t\tthis.name = 'InvalidClientTelemetrySampleRate';\n\t}\n}\n\n/** The `telemetry` default as the generated module prints it. */\nexport function printTelemetryDefault(\n\ttelemetry: ClientTelemetryDefault,\n): string {\n\tif (typeof telemetry === 'boolean') return String(telemetry);\n\tconst { sampleRate } = telemetry;\n\tif (!Number.isFinite(sampleRate) || sampleRate < 0 || sampleRate > 1) {\n\t\tthrow new InvalidClientTelemetrySampleRate(sampleRate);\n\t}\n\treturn `{ sampleRate: ${sampleRate} }`;\n}\n\n/**\n * `--telemetry [rate]` for a client default, as `gkm openapi` takes it — and\n * as a site's Dockerfile passes it to the `gkm openapi --app` it runs.\n */\nexport function telemetryArgs(telemetry?: ClientTelemetryDefault): string[] {\n\tif (!telemetry) return [];\n\tif (telemetry === true) return ['--telemetry'];\n\treturn ['--telemetry', String(telemetry.sampleRate)];\n}\n\n/** What `--telemetry [rate]` parsed to: on, at a rate when one was given. */\nexport function parseTelemetryFlag(\n\tvalue: boolean | string | undefined,\n): ClientTelemetryDefault | undefined {\n\tif (value === undefined || value === false) return undefined;\n\tif (value === true) return true;\n\treturn { sampleRate: Number(value) };\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join, relative } from 'node:path';\nimport { Endpoint } from '@geekmidas/constructs/endpoints';\nimport type { BuildContext } from '../build/types';\nimport type { RouteInfo } from '../types';\nimport type { StorageDrivers } from './drivers';\nimport {\n\tConstructGenerator,\n\ttype GeneratedConstruct,\n\ttype GeneratorOptions,\n} from './Generator';\nimport {\n\tmountRequestSpansCode,\n\tstartTelemetryCode,\n\twriteTelemetryModule,\n} from './telemetry';\n\n/**\n * How a generated entry gets its logger and environment parser.\n *\n * From the surface it serves, when there is one: the surface holds both as\n * *objects*, and discovery already recorded which module declares it, so the\n * entry imports that module and reads them off it.\n *\n * The alternative — the shape this replaces — was naming both in config as\n * module paths, `envParser: './config/env#envParser'`, and printing an import\n * for each. Two strings per app, checked by nothing, describing files the\n * surface was already holding the contents of.\n *\n * Falls back to the configured paths for an app that declares no surface.\n */\nexport function runtimeFor(\n\tcontext: BuildContext,\n\tfromDir: string,\n\t/**\n\t * The construct that owns the thing being generated.\n\t *\n\t * An endpoint's surface, or the worker that handed out the cron factory.\n\t * Omitted for the app entry itself, which belongs to the primary surface.\n\t */\n\towner?: string,\n): { imports: string; bindings: string } {\n\tconst module = owner\n\t\t? (context.owners?.[owner] ?? context.surface?.module)\n\t\t: context.surface?.module;\n\n\tif (module) {\n\t\tconst specifier = importSpecifier(fromDir, module.specifier);\n\n\t\t// Bound to the same names the rest of the entry already uses, so what\n\t\t// changes is where they come from and nothing else.\n\t\treturn {\n\t\t\timports: `import { ${module.exportName} as __surface } from '${specifier}';`,\n\t\t\tbindings: [\n\t\t\t\t'// What this construct was declared with. Objects, not module paths:',\n\t\t\t\t'// the entry imports the owner, so there is nothing to print.',\n\t\t\t\t'const envParser = __surface.envParser;',\n\t\t\t\t'const logger = __surface.logger;',\n\t\t\t].join('\\n'),\n\t\t};\n\t}\n\n\t// No fallback. Every runnable comes from a factory a `RestApi` or a `Worker`\n\t// handed out, and that construct holds the objects — so there is no longer a\n\t// case where the only thing we know about a logger is a module path somebody\n\t// typed into config.\n\tthrow new Error(\n\t\t`No owner to take a logger and an environment parser from${\n\t\t\towner ? ` for \"${owner}\"` : ''\n\t\t}. Build it from a \\`RestApi\\` or a \\`Worker\\`.`,\n\t);\n}\n\n/** A relative specifier ESM will accept: always prefixed, never bare. */\nfunction importSpecifier(fromDir: string, target: string): string {\n\tconst rel = relative(fromDir, target).replace(/\\.ts$/, '.js');\n\n\treturn rel.startsWith('.') ? rel : `./${rel}`;\n}\n\n/**\n * CORS for a surface, derived rather than hand-written.\n *\n * Who may call a surface is already in the graph — every construct that\n * declared an edge to it — and reaches the process as one comma-separated\n * value. An application that maintained this by hand maintained a list that was\n * wrong in two directions at once: it trusted origins nothing was serving, and\n * it could not name hosts a deploy had not chosen yet.\n *\n * Only the parts a graph cannot answer are configurable, and every one has a\n * default: a surface that says nothing about CORS still gets it.\n */\nexport function corsFor(surface: BuildContext['surface']): {\n\timports: string;\n\tsetup: string;\n} {\n\tif (!surface) return { imports: '', setup: '' };\n\n\tconst cors = surface.cors ?? {};\n\t// `traceparent` and `tracestate` always: a site's client sends them when\n\t// its telemetry is on, and a preflight that refused them would fail every\n\t// request it makes rather than only its tracing. The API decides whether\n\t// to continue the trace (it does only for these same origins), so allowing\n\t// the headers grants nothing on its own.\n\tconst allowHeaders = [\n\t\t...new Set([\n\t\t\t'content-type',\n\t\t\t'authorization',\n\t\t\t'traceparent',\n\t\t\t'tracestate',\n\t\t\t...(cors.allowHeaders ?? []),\n\t\t]),\n\t];\n\n\treturn {\n\t\timports: `import { cors } from 'hono/cors';`,\n\t\tsetup: `\n // Who may call ${surface.id}, read off the graph rather than listed here.\n {\n const { origins } = envParser\n .create((get) => ({\n origins: get('${surface.trustedOriginsKey}')\n .string()\n .default('')\n .transform((value: string) =>\n value.split(',').map((origin) => origin.trim()).filter(Boolean),\n ),\n }))\n .parse();\n\n // The same origins are the ones whose trace context the request spans\n // continue — the server reads them back from \\`createApp\\`.\n trustedOrigins.push(...origins);\n\n honoApp.use('*', cors({\n origin: origins,\n allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],\n allowHeaders: ${JSON.stringify(allowHeaders)},${\n\t\t\t\tcors.exposeHeaders\n\t\t\t\t\t? `\\n exposeHeaders: ${JSON.stringify(cors.exposeHeaders)},`\n\t\t\t\t\t: ''\n\t\t\t}\n credentials: ${cors.credentials ?? true},\n maxAge: ${cors.maxAge ?? 86400},\n }));\n }\n`,\n\t};\n}\n\nexport class EndpointGenerator extends ConstructGenerator<\n\tEndpoint<any, any, any, any, any, any, any, any, any, any, any, any>,\n\tRouteInfo[]\n> {\n\tisConstruct(\n\t\tvalue: any,\n\t): value is Endpoint<\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany,\n\t\tany\n\t> {\n\t\treturn Endpoint.isEndpoint(value);\n\t}\n\n\tasync build(\n\t\tcontext: BuildContext,\n\t\tconstructs: GeneratedConstruct<\n\t\t\tEndpoint<any, any, any, any, any, any, any, any, any, any, any, any>\n\t\t>[],\n\t\toutputDir: string,\n\t\toptions?: GeneratorOptions,\n\t): Promise<RouteInfo[]> {\n\t\tconst target = options?.target ?? 'aws';\n\t\tconst root = options?.root ?? process.cwd();\n\t\tconst enableOpenApi = options?.enableOpenApi || false;\n\t\tconst logger = console;\n\t\tconst routes: RouteInfo[] = [];\n\n\t\tif (constructs.length === 0) {\n\t\t\treturn routes;\n\t\t}\n\n\t\tif (target === 'server') {\n\t\t\t// Generate endpoints.ts and app.ts\n\t\t\tawait this.generateEndpointsFile(outputDir, constructs);\n\t\t\tconst appFile = await this.generateAppFile(outputDir, context);\n\n\t\t\troutes.push({\n\t\t\t\tpath: '*',\n\t\t\t\tmethod: 'ALL',\n\t\t\t\thandler: relative(root, appFile),\n\t\t\t\tauthorizer: 'none',\n\t\t\t});\n\n\t\t\tlogger.log(\n\t\t\t\t`Generated server with ${constructs.length} endpoints${enableOpenApi ? ' (OpenAPI enabled)' : ''}`,\n\t\t\t);\n\n\t\t\treturn routes;\n\t\t}\n\n\t\t// One Lambda per endpoint, behind an HTTP API.\n\t\tconst routesDir = join(outputDir, 'routes');\n\t\tawait mkdir(routesDir, { recursive: true });\n\n\t\tfor (const { key, construct, path } of constructs) {\n\t\t\tconst handlerFile = await this.generateHandlerFile(\n\t\t\t\troutesDir,\n\t\t\t\tpath.relative,\n\t\t\t\tkey,\n\t\t\t\tcontext,\n\t\t\t);\n\n\t\t\tconst routeInfo: RouteInfo = {\n\t\t\t\tpath: construct._path,\n\t\t\t\tmethod: construct.method,\n\t\t\t\thandler: relative(root, handlerFile).replace(/\\.ts$/, '.handler'),\n\t\t\t\ttimeout: construct.timeout,\n\t\t\t\tmemorySize: construct.memorySize,\n\t\t\t\tenvironment: await construct.getEnvironment({\n\t\t\t\t\tmarkOptional: context.markOptional,\n\t\t\t\t}),\n\t\t\t\tdependencies: construct.constructs,\n\t\t\t\tauthorizer: construct.authorizer?.name ?? 'none',\n\t\t\t};\n\n\t\t\troutes.push(routeInfo);\n\t\t\tlogger.log(`Generated handler for ${routeInfo.method} ${routeInfo.path}`);\n\t\t}\n\n\t\treturn routes;\n\t}\n\n\tprivate async generateHandlerFile(\n\t\toutputDir: string,\n\t\tsourceFile: string,\n\t\texportName: string,\n\t\tcontext: BuildContext,\n\t): Promise<string> {\n\t\tconst handlerPath = join(outputDir, `${exportName}.ts`);\n\n\t\tconst relativePath = relative(dirname(handlerPath), sourceFile);\n\t\tconst importPath = relativePath.replace(/\\.ts$/, '.js');\n\n\t\tawait writeFile(\n\t\t\thandlerPath,\n\t\t\tthis.generateAWSApiGatewayV2Handler(\n\t\t\t\timportPath,\n\t\t\t\texportName,\n\t\t\t\tcontext.storageDrivers,\n\t\t\t),\n\t\t);\n\t\treturn handlerPath;\n\t}\n\n\tprivate async generateEndpointsFile(\n\t\toutputDir: string,\n\t\tendpoints: GeneratedConstruct<\n\t\t\tEndpoint<any, any, any, any, any, any, any, any, any, any, any, any>\n\t\t>[],\n\t): Promise<string> {\n\t\tconst endpointsFileName = 'endpoints.ts';\n\t\tconst endpointsPath = join(outputDir, endpointsFileName);\n\n\t\t// Group imports by file\n\t\tconst importsByFile = new Map<string, string[]>();\n\n\t\tfor (const { path, key } of endpoints) {\n\t\t\tconst relativePath = relative(dirname(endpointsPath), path.relative);\n\t\t\tconst importPath = relativePath.replace(/\\.ts$/, '.js');\n\n\t\t\tif (!importsByFile.has(importPath)) {\n\t\t\t\timportsByFile.set(importPath, []);\n\t\t\t}\n\t\t\timportsByFile.get(importPath)?.push(key);\n\t\t}\n\n\t\t// Generate import statements for endpoints\n\t\tconst endpointImports = Array.from(importsByFile.entries())\n\t\t\t.map(\n\t\t\t\t([importPath, exports]) =>\n\t\t\t\t\t`import { ${exports.join(', ')} } from '${importPath}';`,\n\t\t\t)\n\t\t\t.join('\\n');\n\n\t\tconst allExportNames = endpoints.map(({ key }) => key);\n\n\t\t// One registration for every build, production included: each endpoint\n\t\t// is served by HonoEndpoint, which is what `gkm dev` and a feature test\n\t\t// serve it with. Production used to generate its own handlers per\n\t\t// \"tier\", and each was a hand-copy of the adaptor that drifted from it —\n\t\t// a session left undefined, an HttpError answered 500, an auditor\n\t\t// hard-coded to undefined, cookies a handler set never sent.\n\t\tconst content = `import type { EnvironmentParser } from '@geekmidas/envkit';\nimport type { Logger } from '@geekmidas/logger';\nimport { HonoEndpoint } from '@geekmidas/constructs/hono';\nimport { Endpoint } from '@geekmidas/constructs/endpoints';\nimport { ServiceDiscovery } from '@geekmidas/services';\nimport type { Hono } from 'hono';\n${endpointImports}\n\nexport const endpoints: Endpoint<any, any, any, any, any, any, any, any, any, any, any, any>[] = [\n ${allExportNames.join(',\\n ')}\n];\n\nexport async function setupEndpoints(\n app: Hono,\n envParser: EnvironmentParser<any>,\n logger: Logger,\n enableOpenApi: boolean = true,\n): Promise<void> {\n const serviceDiscovery = ServiceDiscovery.getInstance(envParser);\n\n // Configure OpenAPI options based on enableOpenApi flag\n const openApiOptions: any = enableOpenApi ? {\n docsPath: '/__docs',\n openApiOptions: {\n title: 'API Documentation',\n version: '1.0.0',\n description: 'Generated API documentation'\n }\n } : { docsPath: false };\n\n HonoEndpoint.addRoutes(endpoints, serviceDiscovery, app, openApiOptions);\n\n // Add Swagger UI if OpenAPI is enabled\n if (enableOpenApi) {\n try {\n const { swaggerUI } = await import('@hono/swagger-ui');\n app.get('/__docs/ui', swaggerUI({ url: '/__docs' }));\n } catch {\n // @hono/swagger-ui not installed, skip Swagger UI\n }\n }\n}\n`;\n\n\t\tawait writeFile(endpointsPath, content);\n\n\t\treturn endpointsPath;\n\t}\n\n\tprivate async generateAppFile(\n\t\toutputDir: string,\n\t\tcontext: BuildContext,\n\t): Promise<string> {\n\t\t// What both entries start telemetry from — the production server and\n\t\t// `gkm dev`'s — decided by the surface's edge to a `Telemetry` node.\n\t\tawait writeTelemetryModule(outputDir, context.telemetry);\n\n\t\t// Use production generator if in production mode\n\t\tif (context.production?.enabled) {\n\t\t\treturn this.generateProductionAppFile(outputDir, context);\n\t\t}\n\n\t\tconst appFileName = 'app.ts';\n\t\tconst appPath = join(outputDir, appFileName);\n\n\t\t// The logger and the env parser, from the surface that already holds them.\n\t\tconst runtime = runtimeFor(context, dirname(appPath));\n\n\t\t// Generate telescope imports and setup if enabled\n\t\tconst telescopeEnabled = context.telescope?.enabled;\n\t\tconst telescopeWebSocketEnabled = context.telescope?.websocket;\n\t\tconst usesExternalTelescope = !!context.telescope?.telescopePath;\n\n\t\t// Generate imports based on whether telescope is external or inline\n\t\tconst telescopeFromSurface =\n\t\t\t!!context.surface?.module && !usesExternalTelescope;\n\n\t\tlet telescopeImports = '';\n\t\tif (telescopeEnabled) {\n\t\t\tif (telescopeFromSurface) {\n\t\t\t\t// The entry already imports the surface, which may or may not have\n\t\t\t\t// been given a Telescope — an object, so only the running entry can\n\t\t\t\t// tell. The inline one stands in when it was not.\n\t\t\t\ttelescopeImports = `import { Telescope, InMemoryStorage } from '@geekmidas/telescope';\nimport { createApi, createMiddleware } from '@geekmidas/telescope/hono';`;\n\t\t\t} else if (usesExternalTelescope) {\n\t\t\t\tconst relativeTelescopePath = relative(\n\t\t\t\t\tdirname(appPath),\n\t\t\t\t\tcontext.telescope?.telescopePath!,\n\t\t\t\t);\n\t\t\t\ttelescopeImports = `import ${context.telescope?.telescopeImportPattern} from '${relativeTelescopePath}';\nimport { createApi, createMiddleware } from '@geekmidas/telescope/hono';`;\n\t\t\t} else {\n\t\t\t\ttelescopeImports = `import { Telescope, InMemoryStorage } from '@geekmidas/telescope';\nimport { createApi, createMiddleware } from '@geekmidas/telescope/hono';`;\n\t\t\t}\n\t\t}\n\n\t\t// The database's read-only JSON API, from the declared database. The\n\t\t// client comes from the construct, so what it reads is by definition what\n\t\t// the handlers write to.\n\t\tconst databaseApi = context.databaseApi;\n\t\tconst databaseApiImports = databaseApi\n\t\t\t? `import { snifferContext } from '@geekmidas/constructs';\nimport { createIntrospectionHandler } from '@geekmidas/db/introspect';\nimport { ${databaseApi.database.exportName} as __introspectedDb } from '${importSpecifier(dirname(appPath), databaseApi.database.specifier)}';`\n\t\t\t: '';\n\n\t\t// Generate imports for server hooks\n\t\tconst cors = corsFor(context.surface);\n\t\tlet hooksImports = '';\n\t\tlet beforeSetupCall = '';\n\t\tlet afterSetupCall = '';\n\t\tif (context.hooks?.serverHooksPath) {\n\t\t\tconst relativeHooksPath = relative(\n\t\t\t\tdirname(appPath),\n\t\t\t\tcontext.hooks.serverHooksPath,\n\t\t\t);\n\t\t\thooksImports = `import * as serverHooks from '${relativeHooksPath}';`;\n\t\t\tbeforeSetupCall = `\n // Call beforeSetup hook if defined\n if (typeof serverHooks.beforeSetup === 'function') {\n await serverHooks.beforeSetup(honoApp, { envParser, logger });\n }\n`;\n\t\t\tafterSetupCall = `\n // Call afterSetup hook if defined\n if (typeof serverHooks.afterSetup === 'function') {\n await serverHooks.afterSetup(honoApp, { envParser, logger });\n }\n`;\n\t\t}\n\n\t\tconst telescopeWebSocketSetupCode = telescopeWebSocketEnabled\n\t\t\t? `\n // Setup WebSocket for real-time telescope updates\n try {\n const { createNodeWebSocket } = await import('@hono/node-ws');\n const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app: honoApp });\n // Add WebSocket route directly to main app (sub-app routes don't support WS upgrade)\n honoApp.get('${context.telescope?.path}/ws', upgradeWebSocket(() => ({\n onOpen: (_event: Event, ws: any) => {\n telescope.addWsClient(ws);\n },\n onClose: (_event: Event, ws: any) => {\n telescope.removeWsClient(ws);\n },\n onMessage: (event: MessageEvent, ws: any) => {\n try {\n const data = JSON.parse(event.data);\n if (data.type === 'ping') {\n ws.send(JSON.stringify({ type: 'pong' }));\n }\n } catch {\n // Ignore invalid messages\n }\n },\n })));\n // Store injectWebSocket for server entry to call after serve()\n (honoApp as any).__injectWebSocket = injectWebSocket;\n logger.info('Telescope WebSocket enabled');\n } catch (e) {\n logger.warn({ err: e }, 'WebSocket support not available - install @hono/node-ws for real-time updates');\n }\n`\n\t\t\t: '';\n\n\t\t// Generate telescope setup - either use external instance or create inline\n\t\tlet telescopeSetup = '';\n\t\tif (telescopeEnabled) {\n\t\t\tif (telescopeFromSurface || usesExternalTelescope) {\n\t\t\t\t// Use external telescope instance - no need to create one\n\t\t\t\ttelescopeSetup = `\n${telescopeWebSocketSetupCode}\n // Add telescope middleware (before endpoints to capture all requests)\n honoApp.use('*', createMiddleware(telescope));\n\n // Mount Telescope's JSON API\n const telescopeApi = createApi(telescope);\n honoApp.route('${context.telescope?.path}', telescopeApi);\n`;\n\t\t\t} else {\n\t\t\t\t// Create inline telescope instance\n\t\t\t\ttelescopeSetup = `\n // Setup Telescope for debugging/monitoring\n const telescopeStorage = new InMemoryStorage({ maxEntries: ${context.telescope?.maxEntries} });\n const telescope = new Telescope({\n enabled: true,\n path: '${context.telescope?.path}',\n ignorePatterns: ${JSON.stringify(context.telescope?.ignore)},\n recordBody: ${context.telescope?.recordBody},\n storage: telescopeStorage,\n });\n${telescopeWebSocketSetupCode}\n // Add telescope middleware (before endpoints to capture all requests)\n honoApp.use('*', createMiddleware(telescope));\n\n // Mount Telescope's JSON API\n const telescopeApi = createApi(telescope);\n honoApp.route('${context.telescope?.path}', telescopeApi);\n`;\n\t\t\t}\n\t\t}\n\n\t\tconst databaseApiSetup = databaseApi\n\t\t\t? `\n // The database's structure and rows as JSON, read-only, for whatever tool\n // the developer points at it. Dev only: it answers anyone who can reach the\n // port.\n const databaseApi = createIntrospectionHandler({\n db: await __introspectedDb.service.register({ envParser, context: snifferContext }),\n basePath: '${databaseApi.path}',\n });\n honoApp.all('${databaseApi.path}/*', (c) => databaseApi(c.req.raw));\n`\n\t\t\t: '';\n\n\t\tconst content = `/**\n * Generated server application\n *\n * ⚠️ WARNING: This is for LOCAL DEVELOPMENT ONLY\n * The subscriber polling mechanism is not production-ready.\n * For production, use AWS Lambda with SQS/SNS event sources.\n */\nimport { Hono } from 'hono';\nimport type { Hono as HonoType } from 'hono';\nimport { setupEndpoints } from './endpoints.js';\nimport { setupSubscribers } from './subscribers.js';\nimport { setupQueues } from './queues.js';\nimport { setupCrons } from './crons.js';\n${runtime.imports}\n${telescopeImports}\n${databaseApiImports}\n${hooksImports}\n${cors.imports}\n${context.storageDrivers?.imports ?? ''}\n\n${runtime.bindings}\n${\n\ttelescopeEnabled && telescopeFromSurface\n\t\t? `// The surface's own Telescope, or the one \\`telescope\\` config describes.\nconst telescope =\n __surface.telescope ??\n new Telescope({\n enabled: true,\n path: '${context.telescope?.path}',\n ignorePatterns: ${JSON.stringify(context.telescope?.ignore)},\n recordBody: ${context.telescope?.recordBody},\n storage: new InMemoryStorage({ maxEntries: ${context.telescope?.maxEntries} }),\n });`\n\t\t: ''\n}\n\n${\n\tcontext.storageDrivers?.setup\n\t\t? `// Which drivers exist — storage, cache, events — is the entry point's\n// decision: the scheme in each injected URL picks one, so no construct and no\n// application module names a provider.\n${context.storageDrivers.setup}\n`\n\t\t: ''\n}\n${\n\tcontext.surface\n\t\t? `// Who this server is to Postgres: application_name on every connection.\nprocess.env.GKM_APP_NAME ??= ${JSON.stringify(context.surface.id)};\n\n`\n\t\t: ''\n}export interface ServerApp {\n app: HonoType;\n /** The origins this API's CORS allows: its own sites. */\n trustedOrigins: readonly string[];\n start: (options?: {\n port?: number;\n /** Returns the server, so a shutdown can stop it taking requests. */\n serve: (app: HonoType, port: number) => unknown;\n }) => Promise<void>;\n}\n\n/**\n * Create and configure the Hono application\n *\n * @param app - Optional Hono app instance to configure (creates new one if not provided)\n * @param enableOpenApi - Enable OpenAPI documentation (default: true)\n * @returns Server app with configured Hono app and start function\n *\n * @example\n * // With Bun\n * import { createApp } from './.gkm/server/app.js';\n *\n * const { app, start } = await createApp();\n *\n * await start({\n * port: 3000,\n * serve: (app, port) => {\n * Bun.serve({ port, fetch: app.fetch });\n * }\n * });\n *\n * @example\n * // With Node.js (using @hono/node-server)\n * import { serve } from '@hono/node-server';\n * import { createApp } from './.gkm/server/app.js';\n *\n * const { app, start } = await createApp();\n *\n * await start({\n * port: 3000,\n * serve: (app, port) => {\n * serve({ fetch: app.fetch, port });\n * }\n * });\n */\nexport async function createApp(app?: HonoType, enableOpenApi: boolean = true): Promise<ServerApp> {\n const honoApp = app || new Hono();\n const trustedOrigins: string[] = [];\n${telescopeSetup}${cors.setup}${beforeSetupCall}${databaseApiSetup}\n // Setup HTTP endpoints\n await setupEndpoints(honoApp, envParser, logger, enableOpenApi);\n${afterSetupCall}\n\n return {\n app: honoApp,\n trustedOrigins,\n async start(options) {\n if (!options?.serve) {\n throw new Error(\n 'serve function is required. Pass a serve function for your runtime:\\\\n' +\n ' - Bun: (app, port) => Bun.serve({ port, fetch: app.fetch })\\\\n' +\n ' - Node: (app, port) => serve({ fetch: app.fetch, port })'\n );\n }\n\n const port = options.port ?? 3000;\n\n // Mount pushed subscribers' routes and start polled ones. What comes\n // back subscribes the routes, once the server can answer them.\n const subscribeForPush = await setupSubscribers(honoApp, envParser, logger).catch((error) => {\n logger.error({ err: error }, 'Failed to start subscribers');\n return async (_port: number) => {};\n });\n\n // Start queue workers in background (non-blocking, local development only)\n await setupQueues(envParser, logger).catch((error) => {\n logger.error({ err: error }, 'Failed to start queue workers');\n });\n\n // Schedule this app's crons. Caught like the others: a scheduler that\n // cannot start is not a reason for the HTTP server not to.\n await setupCrons(envParser, logger).catch((error) => {\n logger.error({ err: error }, 'Failed to schedule crons');\n });\n\n logger.info({ port }, 'Starting server');\n\n // Start HTTP server using provided serve function\n await options.serve(honoApp, port);\n\n logger.info({ port }, 'Server started');\n\n await subscribeForPush(port);\n }\n };\n}\n\n// Default export for convenience\nexport default createApp;\n`;\n\n\t\tawait writeFile(appPath, content);\n\n\t\treturn appPath;\n\t}\n\n\t/**\n\t * One Lambda, one endpoint — and no environment import.\n\t *\n\t * The endpoint was built from its surface, so it carries the parser the\n\t * adaptor needs. This used to read\n\t * `import { envParser } from '../../config/env'`, a line the build wrote\n\t * because a generator can print a module specifier and cannot print an\n\t * object. Nothing has to be printed now, which is also why nothing has to be\n\t * named in config.\n\t */\n\tprivate generateAWSApiGatewayV2Handler(\n\t\timportPath: string,\n\t\texportName: string,\n\t\tdrivers?: StorageDrivers,\n\t): string {\n\t\treturn `import { AmazonApiGatewayV2Endpoint } from '@geekmidas/constructs/aws';\nimport { ${exportName} } from '${importPath}';\n${drivers?.imports ?? ''}\n${drivers?.setup ? `\\n// The handler registers the drivers its target needs.\\n${drivers.setup}\\n` : ''}\nconst adapter = new AmazonApiGatewayV2Endpoint(${exportName});\n\nexport const handler = adapter.handler;\n`;\n\t}\n\n\t/**\n\t * Generate a production-optimized app.ts file\n\t * No dev tools (Telescope, Studio, WebSocket), includes health checks and graceful shutdown\n\t */\n\tprivate async generateProductionAppFile(\n\t\toutputDir: string,\n\t\tcontext: BuildContext,\n\t): Promise<string> {\n\t\tconst appFileName = 'app.ts';\n\t\tconst appPath = join(outputDir, appFileName);\n\n\t\t// The app entry belongs to the primary surface, so no owner is named.\n\t\tconst runtime = runtimeFor(context, dirname(appPath));\n\n\t\tconst production = context.production!;\n\t\tconst healthCheckPath = production.healthCheck;\n\t\tconst enableGracefulShutdown = production.gracefulShutdown;\n\t\tconst enableOpenApi = production.openapi;\n\t\tconst includeSubscribers = production.subscribers === 'include';\n\n\t\t// Generate imports for server hooks\n\t\tconst cors = corsFor(context.surface);\n\t\tlet hooksImports = '';\n\t\tlet beforeSetupCall = '';\n\t\tlet afterSetupCall = '';\n\t\tif (context.hooks?.serverHooksPath) {\n\t\t\tconst relativeHooksPath = relative(\n\t\t\t\tdirname(appPath),\n\t\t\t\tcontext.hooks.serverHooksPath,\n\t\t\t);\n\t\t\thooksImports = `import * as serverHooks from '${relativeHooksPath}';`;\n\t\t\tbeforeSetupCall = `\n // Call beforeSetup hook if defined\n if (typeof serverHooks.beforeSetup === 'function') {\n await serverHooks.beforeSetup(honoApp, { envParser, logger });\n }\n`;\n\t\t\tafterSetupCall = `\n // Call afterSetup hook if defined\n if (typeof serverHooks.afterSetup === 'function') {\n await serverHooks.afterSetup(honoApp, { envParser, logger });\n }\n`;\n\t\t}\n\n\t\t// Subscriber + queue setup code (background workers share the same flag)\n\t\tconst subscriberSetup = includeSubscribers\n\t\t\t? `\n // Mount pushed subscribers' routes and start polled ones. What comes\n // back subscribes the routes, once the server can answer them.\n const subscribeForPush = await setupSubscribers(honoApp, envParser, logger).catch((error) => {\n logger.error({ err: error }, 'Failed to start subscribers');\n return async (_port: number) => {};\n });\n\n // Start queue workers in background\n await setupQueues(envParser, logger).catch((error) => {\n logger.error({ err: error }, 'Failed to start queue workers');\n });\n\n // Schedule this app's crons. Caught like the others: a scheduler that\n // cannot start is not a reason for the HTTP server not to.\n await setupCrons(envParser, logger).catch((error) => {\n logger.error({ err: error }, 'Failed to schedule crons');\n });\n`\n\t\t\t: '';\n\n\t\tconst subscriberImport = includeSubscribers\n\t\t\t? `import { setupSubscribers } from './subscribers.js';\nimport { setupQueues } from './queues.js';\nimport { setupCrons } from './crons.js';`\n\t\t\t: '';\n\n\t\t// Graceful shutdown code\n\t\tconst gracefulShutdownCode = enableGracefulShutdown\n\t\t\t? `\n // Graceful shutdown: stop taking requests, let in-flight ones finish, then\n // close what constructs opened (database pools) — so a rolling deploy does\n // not leave the old task holding connections. Bounded by\n // GKM_SHUTDOWN_TIMEOUT_MS, 8s by default: under Docker's 10s stop timeout,\n // so the process exits on its own terms rather than being killed mid-drain.\n let isShuttingDown = false;\n\n const shutdown = async () => {\n if (isShuttingDown) return;\n isShuttingDown = true;\n const deadline = Number(process.env.GKM_SHUTDOWN_TIMEOUT_MS) || 8000;\n logger.info({ deadline }, 'Graceful shutdown initiated');\n setTimeout(() => {\n logger.warn({ deadline }, 'Shutdown deadline reached, exiting');\n process.exit(1);\n }, deadline).unref();\n await new Promise<void>((resolve) => {\n const close = (server as { close?: (done: () => void) => void } | undefined)?.close;\n if (typeof close === 'function') close.call(server, () => resolve());\n else resolve();\n });\n await runShutdownHooks((error) => logger.error({ err: error }, 'Shutdown hook failed'));\n process.exit(0);\n };\n\n process.on('SIGTERM', shutdown);\n process.on('SIGINT', shutdown);\n`\n\t\t\t: '';\n\n\t\tconst content = `/**\n * Generated production server application\n *\n * This is a production-optimized build without dev tools.\n * - No Telescope debugging dashboard\n * - No Studio database browser\n * - No WebSocket updates\n * - Includes health checks and graceful shutdown\n */\nimport { Hono } from 'hono';\nimport type { Hono as HonoType } from 'hono';\n${enableGracefulShutdown ? \"import { runShutdownHooks } from '@geekmidas/constructs';\\n\" : ''}import { setupEndpoints } from './endpoints.js';\n${subscriberImport}\n${runtime.imports}\n${runtime.bindings}\n${hooksImports}\n${cors.imports}\n${context.storageDrivers?.imports ?? ''}\n\n${\n\tcontext.storageDrivers?.setup\n\t\t? `// The entry point registers the drivers its target needs.\n${context.storageDrivers.setup}\n`\n\t\t: ''\n}\n${\n\tcontext.surface\n\t\t? `// Who this server is to Postgres: application_name on every connection.\nprocess.env.GKM_APP_NAME ??= ${JSON.stringify(context.surface.id)};\n\n`\n\t\t: ''\n}export interface ServerApp {\n app: HonoType;\n /** The origins this API's CORS allows: its own sites. */\n trustedOrigins: readonly string[];\n start: (options?: {\n port?: number;\n serve: (app: HonoType, port: number) => void | Promise<void>;\n }) => Promise<void>;\n}\n\n/**\n * Create and configure the production Hono application\n */\nexport async function createApp(app?: HonoType): Promise<ServerApp> {\n const honoApp = app || new Hono();\n const trustedOrigins: string[] = [];\n\n // Health check endpoint (always first)\n honoApp.get('${healthCheckPath}', (c) => c.json({ status: 'ok', timestamp: Date.now() }));\n honoApp.get('/ready', (c) => c.json({ ready: true }));\n\n // An HttpError a handler or a session callback throws — a 401, a 404 —\n // answers with its own status, as it does under gkm dev. The optimized\n // handlers do not catch, so without this every one of them was a 500.\n // Anything else is a 500 that says nothing about why, and is logged.\n honoApp.onError((error, c) => {\n const http = error as {\n isHttpError?: boolean;\n statusCode?: number;\n statusMessage?: string;\n code?: string;\n details?: unknown;\n };\n if (http.isHttpError === true && typeof http.statusCode === 'number') {\n return c.json(\n {\n name: error.name,\n message: error.message,\n statusCode: http.statusCode,\n statusMessage: http.statusMessage,\n code: http.code,\n details: http.details,\n },\n http.statusCode as 500,\n );\n }\n logger.error({ err: error }, 'Unhandled error');\n return c.json({ message: 'Internal Server Error' }, 500);\n });\n${cors.setup}${beforeSetupCall}\n // Setup HTTP endpoints (OpenAPI: ${enableOpenApi})\n await setupEndpoints(honoApp, envParser, logger, ${enableOpenApi});\n${afterSetupCall}\n return {\n app: honoApp,\n trustedOrigins,\n async start(options) {\n if (!options?.serve) {\n throw new Error(\n 'serve function is required. Pass a serve function for your runtime:\\\\n' +\n ' - Bun: (app, port) => Bun.serve({ port, fetch: app.fetch })\\\\n' +\n ' - Node: (app, port) => serve({ fetch: app.fetch, port })'\n );\n }\n\n const port = options.port ?? Number(process.env.PORT) ?? 3000;\n let server: unknown;\n${gracefulShutdownCode}${subscriberSetup}\n logger.info({ port }, 'Starting production server');\n\n // Start HTTP server using provided serve function\n server = await options.serve(honoApp, port);\n\n logger.info({ port }, 'Production server started');\n${includeSubscribers ? '\\n await subscribeForPush(port);' : ''}\n }\n };\n}\n\n// Default export for convenience\nexport default createApp;\n`;\n\n\t\tawait writeFile(appPath, content);\n\n\t\t// Also generate the production server entry point\n\t\tawait this.generateProductionServerEntry(outputDir, context);\n\n\t\treturn appPath;\n\t}\n\n\t/**\n\t * Generate production server.ts entry point\n\t */\n\tprivate async generateProductionServerEntry(\n\t\toutputDir: string,\n\t\tcontext: BuildContext,\n\t): Promise<void> {\n\t\tconst serverPath = join(outputDir, 'server.ts');\n\n\t\t// `telemetry.ts` beside it was written with `app.ts`, for this entry\n\t\t// and the `gkm dev` one alike, which start it the same way.\n\t\tconst content = `#!/usr/bin/env node\n/**\n * Production server entry point\n * Generated by 'gkm build --production'\n */\nimport { serve } from '@hono/node-server';\nimport { Hono } from 'hono';\n\n${startTelemetryCode([context.production?.healthCheck ?? '/health', '/ready'])}\nconst { createApp } = await import('./app.js');\n\nconst port = Number(process.env.PORT) || 3000;\n\nconst app = new Hono();\n${mountRequestSpansCode('app')}\nconst created = await createApp(app);\ntrustedOrigins = created.trustedOrigins;\nconst { start } = created;\n\nawait start({\n port,\n serve: (app, port) => serve({ fetch: app.fetch, port }),\n});\n`;\n\n\t\tawait writeFile(serverPath, content);\n\t}\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"GoDaddyProvider-DSStJVAv.mjs","names":[],"sources":["../src/target/dokploy/dns/godaddy-api.ts","../src/target/dokploy/dns/GoDaddyProvider.ts"],"sourcesContent":["/**\n * GoDaddy's v1 DNS API, as gkm uses it: one record set — a name and a type —\n * at a time.\n *\n * - read: `GET /v1/domains/{domain}/records/{type}/{name}`\n * - write: `PUT /v1/domains/{domain}/records/{type}/{name}` with `[{ data, ttl }]`,\n * which replaces that one name's records of that one type\n * - delete: `DELETE /v1/domains/{domain}/records/{type}/{name}`\n *\n * Never the zone-wide `PUT /v1/domains/{domain}/records`, which replaces every\n * record the domain has — mail, verification TXT records and all — and never\n * a domain or account endpoint (`GET /v1/domains`, `GET /v1/domains/{domain}`):\n * a token scoped to `domains.dns:update` alone is enough. A domain that is not\n * in the token's account is told apart by the records endpoint's own 404 or 422.\n *\n * Authenticated with a Personal Access Token — `Authorization: Bearer <token>`,\n * the API's one security scheme. GoDaddy allows about 60 requests a minute per\n * token; a 429 carries `retryAfterSec`, which is waited out a few times before\n * the run gives up with {@link GoDaddyRateLimited}. A 504 is retried the same\n * way. GoDaddy refuses a TTL under 600 seconds (a 422), so every write is\n * sent with at least that.\n */\n\nimport type { GoDaddyCredential } from '../../../deploy/credentials';\n\nexport const GODADDY_API_BASE = 'https://api.godaddy.com';\n\n/** GoDaddy refuses a TTL below this. */\nexport const GODADDY_MIN_TTL = 600;\n\n/** The record types gkm writes. Never MX, TXT, NS, SOA, SRV or CAA. */\nexport const GODADDY_MANAGED_TYPES = ['A', 'AAAA', 'CNAME'] as const;\n\nexport type GoDaddyRecordType = (typeof GODADDY_MANAGED_TYPES)[number];\n\n/** One record as GoDaddy returns it. */\nexport interface GoDaddyRecord {\n\ttype: string;\n\tname: string;\n\tdata: string;\n\tttl?: number;\n}\n\n/** GoDaddy's error body. */\ninterface GoDaddyErrorBody {\n\tcode?: string;\n\tmessage?: string;\n\tretryAfterSec?: number;\n}\n\n/**\n * The token is good, and GoDaddy will not let it use the API: since 2024 the\n * Domains and DNS APIs answer only accounts with 10 or more domains, or a\n * Discount Domain Club Premier membership.\n */\nexport class GoDaddyApiAccessDenied extends Error {\n\tconstructor(\n\t\treadonly domain: string,\n\t\treadonly detail?: string,\n\t) {\n\t\tsuper(\n\t\t\t`GoDaddy refused API access for ${domain} (403 ACCESS_DENIED${detail ? `: ${detail}` : ''}). ` +\n\t\t\t\t'GoDaddy only opens its DNS API to accounts with 10 or more domains, or ' +\n\t\t\t\t'with a Discount Domain Club Premier membership, so a valid token on a ' +\n\t\t\t\t'smaller account is refused. Either qualify the account, move the ' +\n\t\t\t\t\"domain's DNS hosting to Route53 (provider: 'route53') or Cloudflare \" +\n\t\t\t\t\"by changing its nameservers at GoDaddy, or set provider: 'manual' \" +\n\t\t\t\t'and create the records gkm prints yourself. (A scoped token without ' +\n\t\t\t\t'domains.dns:update is refused the same way — check its scopes too.)',\n\t\t);\n\t\tthis.name = 'GoDaddyApiAccessDenied';\n\t}\n}\n\n/** The token lacks the scope a records call needs. */\nexport class GoDaddyScopeMissing extends Error {\n\tconstructor(\n\t\treadonly method: string,\n\t\treadonly domain: string,\n\t\treadonly scope: string,\n\t\treadonly detail?: string,\n\t) {\n\t\tsuper(\n\t\t\t`GoDaddy refused ${method} on ${domain}'s DNS records (403${detail ? `: ${detail}` : ''}): ` +\n\t\t\t\t`the token lacks the ${scope} scope. Create a Personal Access Token ` +\n\t\t\t\t'in the GoDaddy developer dashboard with domains.dns:update — no ' +\n\t\t\t\t'domain or account scope is needed — and set GODADDY_API_TOKEN or ' +\n\t\t\t\t'run `gkm login --provider godaddy` again.',\n\t\t);\n\t\tthis.name = 'GoDaddyScopeMissing';\n\t}\n}\n\n/** GoDaddy did not accept the token at all. */\nexport class GoDaddyCredentialsInvalid extends Error {\n\tconstructor(readonly code?: string) {\n\t\tsuper(\n\t\t\t`GoDaddy did not accept the API token (401${code ? ` ${code}` : ''}). ` +\n\t\t\t\t'Check GODADDY_API_TOKEN, or run `gkm login --provider godaddy` ' +\n\t\t\t\t'again with a Personal Access Token from the GoDaddy developer ' +\n\t\t\t\t'dashboard that has not expired or been revoked.',\n\t\t);\n\t\tthis.name = 'GoDaddyCredentialsInvalid';\n\t}\n}\n\n/** The domain is not in the account the token belongs to. */\nexport class GoDaddyDomainNotFound extends Error {\n\tconstructor(\n\t\treadonly domain: string,\n\t\treadonly code?: string,\n\t) {\n\t\tsuper(\n\t\t\t`GoDaddy has no domain ${domain} in this token's account${code ? ` (${code})` : ''}. ` +\n\t\t\t\t'Check the root domain in dns, and that the token belongs to the account the domain is registered in.',\n\t\t);\n\t\tthis.name = 'GoDaddyDomainNotFound';\n\t}\n}\n\n/** Still rate limited after waiting it out. */\nexport class GoDaddyRateLimited extends Error {\n\tconstructor(\n\t\treadonly attempts: number,\n\t\treadonly retryAfterSec?: number,\n\t) {\n\t\tsuper(\n\t\t\t`GoDaddy is still rate limiting this token after ${attempts} attempts ` +\n\t\t\t\t'(429 TOO_MANY_REQUESTS — about 60 requests a minute are allowed). ' +\n\t\t\t\t`Wait${retryAfterSec ? ` ${retryAfterSec} seconds` : ' a minute'} and run it again; ` +\n\t\t\t\t'records already written are left as they are.',\n\t\t);\n\t\tthis.name = 'GoDaddyRateLimited';\n\t}\n}\n\n/** Any other failure from GoDaddy. */\nexport class GoDaddyRequestFailed extends Error {\n\tconstructor(\n\t\treadonly method: string,\n\t\treadonly path: string,\n\t\treadonly status: number,\n\t\treadonly code?: string,\n\t\treadonly detail?: string,\n\t) {\n\t\tsuper(\n\t\t\t`GoDaddy answered ${method} ${path} with ${status}${code ? ` ${code}` : ''}` +\n\t\t\t\t`${detail ? `: ${detail}` : ''}.` +\n\t\t\t\t(status === 422\n\t\t\t\t\t? ' A name with a CNAME cannot also have an A or AAAA record, and the reverse — remove the other one first.'\n\t\t\t\t\t: ''),\n\t\t);\n\t\tthis.name = 'GoDaddyRequestFailed';\n\t}\n}\n\n/**\n * A write gkm was about to make to a record type it never manages, or to a\n * name it was not asked for.\n */\nexport class GoDaddyRecordNotAllowed extends Error {\n\tconstructor(\n\t\treadonly domain: string,\n\t\treadonly type: string,\n\t\treadonly recordName: string,\n\t\treadonly reason: string,\n\t) {\n\t\tsuper(\n\t\t\t`Refused to change the ${type} record '${recordName}' on ${domain}: ${reason}. ` +\n\t\t\t\t'gkm only ever writes the A, AAAA and CNAME records of the hosts it serves.',\n\t\t);\n\t\tthis.name = 'GoDaddyRecordNotAllowed';\n\t}\n}\n\nexport interface GoDaddyApiOptions {\n\tcredential: GoDaddyCredential;\n\t/** The API's base URL. GoDaddy's production API by default. */\n\tbaseUrl?: string;\n\t/** How a 429 or 504 is waited out — `setTimeout` by default; tests pass their own. */\n\tsleep?: (ms: number) => Promise<void>;\n\t/** How many times one request is sent before a 429 or 504 is final. Default 4. */\n\tmaxAttempts?: number;\n}\n\n/** A record name GoDaddy's path takes: `@` for the apex, else relative. */\nfunction isRecordName(name: string): boolean {\n\treturn (\n\t\tname === '@' ||\n\t\t/^(\\*\\.)?[a-z0-9_](?:[a-z0-9_-]{0,61}[a-z0-9])?(\\.[a-z0-9_](?:[a-z0-9_-]{0,61}[a-z0-9])?)*$/i.test(\n\t\t\tname,\n\t\t)\n\t);\n}\n\n/** The client. Every method touches one record set, by type and name. */\nexport class GoDaddyApi {\n\tprivate readonly baseUrl: string;\n\tprivate readonly sleep: (ms: number) => Promise<void>;\n\tprivate readonly maxAttempts: number;\n\tprivate readonly authorization: string;\n\t/** Whether a read has succeeded: a later 403 is then the token's scope. */\n\tprivate readSucceeded = false;\n\n\tconstructor(options: GoDaddyApiOptions) {\n\t\tthis.baseUrl = (options.baseUrl ?? GODADDY_API_BASE).replace(/\\/$/, '');\n\t\tthis.sleep =\n\t\t\toptions.sleep ??\n\t\t\t((ms) => new Promise((resolve) => setTimeout(resolve, ms)));\n\t\tthis.maxAttempts = options.maxAttempts ?? 4;\n\t\tthis.authorization = `Bearer ${options.credential.token}`;\n\t}\n\n\t/** The records of one name and type — empty when there are none. */\n\tasync getRecordSet(\n\t\tdomain: string,\n\t\ttype: GoDaddyRecordType,\n\t\tname: string,\n\t): Promise<GoDaddyRecord[]> {\n\t\tthis.guard(domain, type, name);\n\t\tconst records = await this.request<GoDaddyRecord[]>(\n\t\t\t'GET',\n\t\t\tdomain,\n\t\t\tthis.recordPath(domain, type, name),\n\t\t);\n\t\treturn Array.isArray(records) ? records : [];\n\t}\n\n\t/** Replace one name's records of one type with `values`. */\n\tasync replaceRecordSet(\n\t\tdomain: string,\n\t\ttype: GoDaddyRecordType,\n\t\tname: string,\n\t\tvalues: readonly string[],\n\t\tttl: number,\n\t): Promise<void> {\n\t\tthis.guard(domain, type, name);\n\t\tawait this.request(\n\t\t\t'PUT',\n\t\t\tdomain,\n\t\t\tthis.recordPath(domain, type, name),\n\t\t\tvalues.map((data) => ({\n\t\t\t\tdata,\n\t\t\t\tttl: Math.max(ttl, GODADDY_MIN_TTL),\n\t\t\t})),\n\t\t);\n\t}\n\n\t/** Delete one name's records of one type. */\n\tasync deleteRecordSet(\n\t\tdomain: string,\n\t\ttype: GoDaddyRecordType,\n\t\tname: string,\n\t): Promise<void> {\n\t\tthis.guard(domain, type, name);\n\t\tawait this.request('DELETE', domain, this.recordPath(domain, type, name));\n\t}\n\n\tprivate recordPath(domain: string, type: string, name: string): string {\n\t\treturn `/v1/domains/${encodeURIComponent(domain)}/records/${type}/${encodeURIComponent(name)}`;\n\t}\n\n\t/** Only A, AAAA and CNAME, at a real record name. */\n\tprivate guard(domain: string, type: string, name: string): void {\n\t\tif (!(GODADDY_MANAGED_TYPES as readonly string[]).includes(type)) {\n\t\t\tthrow new GoDaddyRecordNotAllowed(\n\t\t\t\tdomain,\n\t\t\t\ttype,\n\t\t\t\tname,\n\t\t\t\t'it is not a type gkm manages',\n\t\t\t);\n\t\t}\n\t\tif (!isRecordName(name)) {\n\t\t\tthrow new GoDaddyRecordNotAllowed(\n\t\t\t\tdomain,\n\t\t\t\ttype,\n\t\t\t\tname,\n\t\t\t\t'it is not a record name relative to the domain',\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate async request<T = unknown>(\n\t\tmethod: 'GET' | 'PUT' | 'DELETE',\n\t\tdomain: string,\n\t\tpath: string,\n\t\tbody?: unknown,\n\t): Promise<T> {\n\t\tlet retryAfterSec: number | undefined;\n\t\tfor (let attempt = 1; attempt <= this.maxAttempts; attempt++) {\n\t\t\tconst response = await fetch(`${this.baseUrl}${path}`, {\n\t\t\t\tmethod,\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: this.authorization,\n\t\t\t\t\tAccept: 'application/json',\n\t\t\t\t\t...(body !== undefined ? { 'Content-Type': 'application/json' } : {}),\n\t\t\t\t},\n\t\t\t\t...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n\t\t\t});\n\n\t\t\tif (response.ok) {\n\t\t\t\tif (method === 'GET') this.readSucceeded = true;\n\t\t\t\tif (response.status === 204) return undefined as T;\n\t\t\t\tconst text = await response.text();\n\t\t\t\treturn (text ? JSON.parse(text) : undefined) as T;\n\t\t\t}\n\n\t\t\tconst error = await errorBody(response);\n\t\t\tif (response.status === 504 && attempt < this.maxAttempts) {\n\t\t\t\tawait this.sleep(Math.min(2 ** attempt * 1000, 30_000));\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (response.status === 429) {\n\t\t\t\tretryAfterSec = error.retryAfterSec;\n\t\t\t\tif (attempt === this.maxAttempts) break;\n\t\t\t\t// GoDaddy says how long; without it, back off exponentially.\n\t\t\t\tconst waitMs =\n\t\t\t\t\tretryAfterSec !== undefined\n\t\t\t\t\t\t? Math.min(retryAfterSec, 60) * 1000\n\t\t\t\t\t\t: Math.min(2 ** attempt * 1000, 60_000);\n\t\t\t\tawait this.sleep(waitMs);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (response.status === 401) {\n\t\t\t\tthrow new GoDaddyCredentialsInvalid(error.code);\n\t\t\t}\n\t\t\tif (response.status === 403) {\n\t\t\t\t// The account restriction says ACCESS_DENIED; a scoped token that\n\t\t\t\t// may not make this call says so, or — after a read went\n\t\t\t\t// through — can only be that.\n\t\t\t\tconst scoped =\n\t\t\t\t\t/scope/i.test(`${error.code ?? ''} ${error.message ?? ''}`) ||\n\t\t\t\t\t(method !== 'GET' && this.readSucceeded);\n\t\t\t\tif (scoped) {\n\t\t\t\t\tthrow new GoDaddyScopeMissing(\n\t\t\t\t\t\tmethod,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\tmethod === 'GET' ? 'DNS records read' : 'domains.dns:update',\n\t\t\t\t\t\terror.message ?? error.code,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tthrow new GoDaddyApiAccessDenied(domain, error.message ?? error.code);\n\t\t\t}\n\t\t\tif (response.status === 404) {\n\t\t\t\tthrow new GoDaddyDomainNotFound(domain, error.code);\n\t\t\t}\n\t\t\tif (\n\t\t\t\tresponse.status === 422 &&\n\t\t\t\t/domain/i.test(error.code ?? '') &&\n\t\t\t\t!/record/i.test(`${error.code ?? ''} ${error.message ?? ''}`)\n\t\t\t) {\n\t\t\t\tthrow new GoDaddyDomainNotFound(domain, error.code);\n\t\t\t}\n\t\t\tthrow new GoDaddyRequestFailed(\n\t\t\t\tmethod,\n\t\t\t\tpath,\n\t\t\t\tresponse.status,\n\t\t\t\terror.code,\n\t\t\t\terror.message,\n\t\t\t);\n\t\t}\n\t\tthrow new GoDaddyRateLimited(this.maxAttempts, retryAfterSec);\n\t}\n}\n\nasync function errorBody(response: Response): Promise<GoDaddyErrorBody> {\n\ttry {\n\t\tconst parsed = (await response.json()) as GoDaddyErrorBody;\n\t\treturn parsed && typeof parsed === 'object' ? parsed : {};\n\t} catch {\n\t\treturn {};\n\t}\n}\n","/**\n * GoDaddy DNS Provider\n *\n * Implements DnsProvider against GoDaddy's v1 API, one record set — a name\n * and a type — at a time: it reads and writes only the A, AAAA and CNAME\n * records of the names it is handed, and never the zone as a whole, a domain\n * or the account. A Personal Access Token scoped to `domains.dns:update` is all it needs.\n *\n * A token that may write records and not read them puts the provider in\n * write-only mode: every record is PUT — idempotent, so one that already has\n * its value is left as it was — and nothing is compared or deleted.\n *\n * Credentials: `GODADDY_API_TOKEN`, else what `gkm login --provider godaddy`\n * stored. Never logged.\n */\n\nimport {\n\ttype GoDaddyCredential,\n\tMissingCredential,\n\tstoredCredentials,\n} from '../../../deploy/credentials';\nimport {\n\ttype DeleteDnsRecord,\n\ttype DeleteResult,\n\ttype DnsProvider,\n\ttype DnsRecord,\n\tDnsRecordsUnreadable,\n\ttype UpsertDnsRecord,\n\ttype UpsertResult,\n} from './DnsProvider';\nimport {\n\tGODADDY_MANAGED_TYPES,\n\tGODADDY_MIN_TTL,\n\tGoDaddyApi,\n\tGoDaddyApiAccessDenied,\n\ttype GoDaddyApiOptions,\n\tGoDaddyDomainNotFound,\n\ttype GoDaddyRecord,\n\tGoDaddyRecordNotAllowed,\n\ttype GoDaddyRecordType,\n\tGoDaddyScopeMissing,\n} from './godaddy-api';\n\nexport interface GoDaddyProviderOptions {\n\t/** The token, when the caller already has it. */\n\tcredential?: GoDaddyCredential;\n\t/** Where the variables are read — `process.env` by default. */\n\tenv?: NodeJS.ProcessEnv;\n\t/** The CLI's home, whose `credentials.json` holds a stored login. */\n\thome?: string;\n\t/** The API's base URL, for tests. */\n\tbaseUrl?: string;\n\t/** How a 429 is waited out, for tests. */\n\tsleep?: GoDaddyApiOptions['sleep'];\n\tmaxAttempts?: number;\n\t/** Where the write-only notice goes — `console.log` by default. */\n\tlog?: (line: string) => void;\n}\n\n/** GoDaddy cannot list a zone through the records-by-name endpoints. */\nexport class GoDaddyZoneListingUnsupported extends Error {\n\tconstructor(readonly domain: string) {\n\t\tsuper(\n\t\t\t`gkm reads GoDaddy records by name and type only — it never lists ` +\n\t\t\t\t`${domain}'s whole zone, so a token scoped to DNS updates is enough. ` +\n\t\t\t\t'Use readRecords(domain, [{ name, type }]) instead of getRecords.',\n\t\t);\n\t\tthis.name = 'GoDaddyZoneListingUnsupported';\n\t}\n}\n\n/** A value compared the way DNS does: case-insensitive, no trailing dot. */\nfunction normalize(value: string): string {\n\treturn value.trim().replace(/\\.$/, '').toLowerCase();\n}\n\nfunction managed(type: string): type is GoDaddyRecordType {\n\treturn (GODADDY_MANAGED_TYPES as readonly string[]).includes(type);\n}\n\n/** A read GoDaddy refused: write-only, rather than a failure. */\nfunction readDenied(error: unknown): boolean {\n\treturn (\n\t\terror instanceof GoDaddyScopeMissing ||\n\t\terror instanceof GoDaddyApiAccessDenied\n\t);\n}\n\nexport class GoDaddyProvider implements DnsProvider {\n\treadonly name = 'godaddy';\n\tprivate api: GoDaddyApi | null = null;\n\t/** Set once a read is refused: every write is then a blind PUT. */\n\tprivate writeOnly = false;\n\n\tconstructor(private readonly options: GoDaddyProviderOptions = {}) {}\n\n\t/** Whether the token turned out unable to read records. */\n\tget readsDenied(): boolean {\n\t\treturn this.writeOnly;\n\t}\n\n\tprivate async getApi(): Promise<GoDaddyApi> {\n\t\tif (this.api) return this.api;\n\t\tconst credential =\n\t\t\tthis.options.credential ??\n\t\t\t(await storedCredentials({\n\t\t\t\tenv: this.options.env ?? process.env,\n\t\t\t\t...(this.options.home ? { home: this.options.home } : {}),\n\t\t\t}).get({ kind: 'godaddy' }));\n\t\tif (!credential) throw new MissingCredential('godaddy', undefined);\n\t\tthis.api = new GoDaddyApi({\n\t\t\tcredential,\n\t\t\t...(this.options.baseUrl ? { baseUrl: this.options.baseUrl } : {}),\n\t\t\t...(this.options.sleep ? { sleep: this.options.sleep } : {}),\n\t\t\t...(this.options.maxAttempts\n\t\t\t\t? { maxAttempts: this.options.maxAttempts }\n\t\t\t\t: {}),\n\t\t});\n\t\treturn this.api;\n\t}\n\n\tprivate assertManaged(domain: string, type: string, name: string): void {\n\t\tif (!managed(type)) {\n\t\t\tthrow new GoDaddyRecordNotAllowed(\n\t\t\t\tdomain,\n\t\t\t\ttype,\n\t\t\t\tname,\n\t\t\t\t'it is not a type gkm manages',\n\t\t\t);\n\t\t}\n\t}\n\n\t/** Never: the whole zone is not read. See {@link GoDaddyZoneListingUnsupported}. */\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\tthrow new GoDaddyZoneListingUnsupported(domain);\n\t}\n\n\t/**\n\t * The records of exactly these names and types, one request each.\n\t *\n\t * @throws {DnsRecordsUnreadable} when the token may not read records\n\t */\n\tasync readRecords(\n\t\tdomain: string,\n\t\twanted: readonly DeleteDnsRecord[],\n\t): Promise<DnsRecord[]> {\n\t\tfor (const { name, type } of wanted) this.assertManaged(domain, type, name);\n\t\tconst api = await this.getApi();\n\t\tconst found: DnsRecord[] = [];\n\t\tfor (const { name, type } of wanted) {\n\t\t\tlet records: GoDaddyRecord[];\n\t\t\ttry {\n\t\t\t\trecords = await api.getRecordSet(\n\t\t\t\t\tdomain,\n\t\t\t\t\ttype as GoDaddyRecordType,\n\t\t\t\t\tname,\n\t\t\t\t);\n\t\t\t} catch (error) {\n\t\t\t\tif (!readDenied(error)) throw error;\n\t\t\t\tthis.writeOnly = true;\n\t\t\t\tthrow new DnsRecordsUnreadable(\n\t\t\t\t\tthis.name,\n\t\t\t\t\tdomain,\n\t\t\t\t\t(error as Error).name,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (records.length === 0) continue;\n\t\t\tfound.push({\n\t\t\t\tname,\n\t\t\t\ttype,\n\t\t\t\tttl: records[0]!.ttl ?? GODADDY_MIN_TTL,\n\t\t\t\tvalues: records.map((r) => r.data),\n\t\t\t});\n\t\t}\n\t\treturn found;\n\t}\n\n\t/**\n\t * Each record set to exactly its one value: read it, and write it only\n\t * when it differs — or, with a token that cannot read, write it. Only the\n\t * name and type of each record handed in is touched.\n\t */\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: UpsertDnsRecord[],\n\t): Promise<UpsertResult[]> {\n\t\t// Every record is checked before any is written, so a refused one\n\t\t// leaves the zone as it was.\n\t\tfor (const record of records) {\n\t\t\tthis.assertManaged(domain, record.type, record.name);\n\t\t}\n\t\tconst api = await this.getApi();\n\n\t\tconst results: UpsertResult[] = [];\n\t\tfor (const record of records) {\n\t\t\tconst type = record.type as GoDaddyRecordType;\n\t\t\tlet existing: GoDaddyRecord[] | undefined;\n\t\t\tif (!this.writeOnly) {\n\t\t\t\ttry {\n\t\t\t\t\texisting = await api.getRecordSet(domain, type, record.name);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (!readDenied(error)) throw error;\n\t\t\t\t\tthis.writeOnly = true;\n\t\t\t\t\t(this.options.log ?? console.log)(\n\t\t\t\t\t\t` ℹ The GoDaddy token cannot read ${domain}'s DNS records, so changes cannot be diffed: writing each record (idempotent).`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (existing) {\n\t\t\t\tconst values = existing.map((r) => normalize(r.data));\n\t\t\t\tif (values.length === 1 && values[0] === normalize(record.value)) {\n\t\t\t\t\tresults.push({ record, created: false, unchanged: true });\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait api.replaceRecordSet(\n\t\t\t\tdomain,\n\t\t\t\ttype,\n\t\t\t\trecord.name,\n\t\t\t\t[record.value],\n\t\t\t\tMath.max(record.ttl, GODADDY_MIN_TTL),\n\t\t\t);\n\t\t\tresults.push({\n\t\t\t\trecord,\n\t\t\t\tcreated: existing !== undefined && existing.length === 0,\n\t\t\t\tunchanged: false,\n\t\t\t});\n\t\t}\n\t\treturn results;\n\t}\n\n\tasync deleteRecords(\n\t\tdomain: string,\n\t\trecords: DeleteDnsRecord[],\n\t): Promise<DeleteResult[]> {\n\t\tfor (const record of records) {\n\t\t\tthis.assertManaged(domain, record.type, record.name);\n\t\t}\n\t\tconst api = await this.getApi();\n\t\tconst results: DeleteResult[] = [];\n\t\tfor (const record of records) {\n\t\t\tconst type = record.type as GoDaddyRecordType;\n\t\t\tif (!this.writeOnly) {\n\t\t\t\tlet existing: GoDaddyRecord[] | undefined;\n\t\t\t\ttry {\n\t\t\t\t\texisting = await api.getRecordSet(domain, type, record.name);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (!readDenied(error)) throw error;\n\t\t\t\t\tthis.writeOnly = true;\n\t\t\t\t}\n\t\t\t\tif (existing && existing.length === 0) {\n\t\t\t\t\tresults.push({ record, deleted: false, notFound: true });\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait api.deleteRecordSet(domain, type, record.name);\n\t\t\t} catch (error) {\n\t\t\t\t// Write-only, nothing was read: a record that is not there is a 404.\n\t\t\t\tif (this.writeOnly && error instanceof GoDaddyDomainNotFound) {\n\t\t\t\t\tresults.push({ record, deleted: false, notFound: true });\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tresults.push({ record, deleted: true, notFound: false });\n\t\t}\n\t\treturn results;\n\t}\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"GoDaddyProvider-DqZ1qD_F.cjs","names":["storedCredentials","MissingCredential","DnsRecordsUnreadable"],"sources":["../src/target/dokploy/dns/godaddy-api.ts","../src/target/dokploy/dns/GoDaddyProvider.ts"],"sourcesContent":["/**\n * GoDaddy's v1 DNS API, as gkm uses it: one record set — a name and a type —\n * at a time.\n *\n * - read: `GET /v1/domains/{domain}/records/{type}/{name}`\n * - write: `PUT /v1/domains/{domain}/records/{type}/{name}` with `[{ data, ttl }]`,\n * which replaces that one name's records of that one type\n * - delete: `DELETE /v1/domains/{domain}/records/{type}/{name}`\n *\n * Never the zone-wide `PUT /v1/domains/{domain}/records`, which replaces every\n * record the domain has — mail, verification TXT records and all — and never\n * a domain or account endpoint (`GET /v1/domains`, `GET /v1/domains/{domain}`):\n * a token scoped to `domains.dns:update` alone is enough. A domain that is not\n * in the token's account is told apart by the records endpoint's own 404 or 422.\n *\n * Authenticated with a Personal Access Token — `Authorization: Bearer <token>`,\n * the API's one security scheme. GoDaddy allows about 60 requests a minute per\n * token; a 429 carries `retryAfterSec`, which is waited out a few times before\n * the run gives up with {@link GoDaddyRateLimited}. A 504 is retried the same\n * way. GoDaddy refuses a TTL under 600 seconds (a 422), so every write is\n * sent with at least that.\n */\n\nimport type { GoDaddyCredential } from '../../../deploy/credentials';\n\nexport const GODADDY_API_BASE = 'https://api.godaddy.com';\n\n/** GoDaddy refuses a TTL below this. */\nexport const GODADDY_MIN_TTL = 600;\n\n/** The record types gkm writes. Never MX, TXT, NS, SOA, SRV or CAA. */\nexport const GODADDY_MANAGED_TYPES = ['A', 'AAAA', 'CNAME'] as const;\n\nexport type GoDaddyRecordType = (typeof GODADDY_MANAGED_TYPES)[number];\n\n/** One record as GoDaddy returns it. */\nexport interface GoDaddyRecord {\n\ttype: string;\n\tname: string;\n\tdata: string;\n\tttl?: number;\n}\n\n/** GoDaddy's error body. */\ninterface GoDaddyErrorBody {\n\tcode?: string;\n\tmessage?: string;\n\tretryAfterSec?: number;\n}\n\n/**\n * The token is good, and GoDaddy will not let it use the API: since 2024 the\n * Domains and DNS APIs answer only accounts with 10 or more domains, or a\n * Discount Domain Club Premier membership.\n */\nexport class GoDaddyApiAccessDenied extends Error {\n\tconstructor(\n\t\treadonly domain: string,\n\t\treadonly detail?: string,\n\t) {\n\t\tsuper(\n\t\t\t`GoDaddy refused API access for ${domain} (403 ACCESS_DENIED${detail ? `: ${detail}` : ''}). ` +\n\t\t\t\t'GoDaddy only opens its DNS API to accounts with 10 or more domains, or ' +\n\t\t\t\t'with a Discount Domain Club Premier membership, so a valid token on a ' +\n\t\t\t\t'smaller account is refused. Either qualify the account, move the ' +\n\t\t\t\t\"domain's DNS hosting to Route53 (provider: 'route53') or Cloudflare \" +\n\t\t\t\t\"by changing its nameservers at GoDaddy, or set provider: 'manual' \" +\n\t\t\t\t'and create the records gkm prints yourself. (A scoped token without ' +\n\t\t\t\t'domains.dns:update is refused the same way — check its scopes too.)',\n\t\t);\n\t\tthis.name = 'GoDaddyApiAccessDenied';\n\t}\n}\n\n/** The token lacks the scope a records call needs. */\nexport class GoDaddyScopeMissing extends Error {\n\tconstructor(\n\t\treadonly method: string,\n\t\treadonly domain: string,\n\t\treadonly scope: string,\n\t\treadonly detail?: string,\n\t) {\n\t\tsuper(\n\t\t\t`GoDaddy refused ${method} on ${domain}'s DNS records (403${detail ? `: ${detail}` : ''}): ` +\n\t\t\t\t`the token lacks the ${scope} scope. Create a Personal Access Token ` +\n\t\t\t\t'in the GoDaddy developer dashboard with domains.dns:update — no ' +\n\t\t\t\t'domain or account scope is needed — and set GODADDY_API_TOKEN or ' +\n\t\t\t\t'run `gkm login --provider godaddy` again.',\n\t\t);\n\t\tthis.name = 'GoDaddyScopeMissing';\n\t}\n}\n\n/** GoDaddy did not accept the token at all. */\nexport class GoDaddyCredentialsInvalid extends Error {\n\tconstructor(readonly code?: string) {\n\t\tsuper(\n\t\t\t`GoDaddy did not accept the API token (401${code ? ` ${code}` : ''}). ` +\n\t\t\t\t'Check GODADDY_API_TOKEN, or run `gkm login --provider godaddy` ' +\n\t\t\t\t'again with a Personal Access Token from the GoDaddy developer ' +\n\t\t\t\t'dashboard that has not expired or been revoked.',\n\t\t);\n\t\tthis.name = 'GoDaddyCredentialsInvalid';\n\t}\n}\n\n/** The domain is not in the account the token belongs to. */\nexport class GoDaddyDomainNotFound extends Error {\n\tconstructor(\n\t\treadonly domain: string,\n\t\treadonly code?: string,\n\t) {\n\t\tsuper(\n\t\t\t`GoDaddy has no domain ${domain} in this token's account${code ? ` (${code})` : ''}. ` +\n\t\t\t\t'Check the root domain in dns, and that the token belongs to the account the domain is registered in.',\n\t\t);\n\t\tthis.name = 'GoDaddyDomainNotFound';\n\t}\n}\n\n/** Still rate limited after waiting it out. */\nexport class GoDaddyRateLimited extends Error {\n\tconstructor(\n\t\treadonly attempts: number,\n\t\treadonly retryAfterSec?: number,\n\t) {\n\t\tsuper(\n\t\t\t`GoDaddy is still rate limiting this token after ${attempts} attempts ` +\n\t\t\t\t'(429 TOO_MANY_REQUESTS — about 60 requests a minute are allowed). ' +\n\t\t\t\t`Wait${retryAfterSec ? ` ${retryAfterSec} seconds` : ' a minute'} and run it again; ` +\n\t\t\t\t'records already written are left as they are.',\n\t\t);\n\t\tthis.name = 'GoDaddyRateLimited';\n\t}\n}\n\n/** Any other failure from GoDaddy. */\nexport class GoDaddyRequestFailed extends Error {\n\tconstructor(\n\t\treadonly method: string,\n\t\treadonly path: string,\n\t\treadonly status: number,\n\t\treadonly code?: string,\n\t\treadonly detail?: string,\n\t) {\n\t\tsuper(\n\t\t\t`GoDaddy answered ${method} ${path} with ${status}${code ? ` ${code}` : ''}` +\n\t\t\t\t`${detail ? `: ${detail}` : ''}.` +\n\t\t\t\t(status === 422\n\t\t\t\t\t? ' A name with a CNAME cannot also have an A or AAAA record, and the reverse — remove the other one first.'\n\t\t\t\t\t: ''),\n\t\t);\n\t\tthis.name = 'GoDaddyRequestFailed';\n\t}\n}\n\n/**\n * A write gkm was about to make to a record type it never manages, or to a\n * name it was not asked for.\n */\nexport class GoDaddyRecordNotAllowed extends Error {\n\tconstructor(\n\t\treadonly domain: string,\n\t\treadonly type: string,\n\t\treadonly recordName: string,\n\t\treadonly reason: string,\n\t) {\n\t\tsuper(\n\t\t\t`Refused to change the ${type} record '${recordName}' on ${domain}: ${reason}. ` +\n\t\t\t\t'gkm only ever writes the A, AAAA and CNAME records of the hosts it serves.',\n\t\t);\n\t\tthis.name = 'GoDaddyRecordNotAllowed';\n\t}\n}\n\nexport interface GoDaddyApiOptions {\n\tcredential: GoDaddyCredential;\n\t/** The API's base URL. GoDaddy's production API by default. */\n\tbaseUrl?: string;\n\t/** How a 429 or 504 is waited out — `setTimeout` by default; tests pass their own. */\n\tsleep?: (ms: number) => Promise<void>;\n\t/** How many times one request is sent before a 429 or 504 is final. Default 4. */\n\tmaxAttempts?: number;\n}\n\n/** A record name GoDaddy's path takes: `@` for the apex, else relative. */\nfunction isRecordName(name: string): boolean {\n\treturn (\n\t\tname === '@' ||\n\t\t/^(\\*\\.)?[a-z0-9_](?:[a-z0-9_-]{0,61}[a-z0-9])?(\\.[a-z0-9_](?:[a-z0-9_-]{0,61}[a-z0-9])?)*$/i.test(\n\t\t\tname,\n\t\t)\n\t);\n}\n\n/** The client. Every method touches one record set, by type and name. */\nexport class GoDaddyApi {\n\tprivate readonly baseUrl: string;\n\tprivate readonly sleep: (ms: number) => Promise<void>;\n\tprivate readonly maxAttempts: number;\n\tprivate readonly authorization: string;\n\t/** Whether a read has succeeded: a later 403 is then the token's scope. */\n\tprivate readSucceeded = false;\n\n\tconstructor(options: GoDaddyApiOptions) {\n\t\tthis.baseUrl = (options.baseUrl ?? GODADDY_API_BASE).replace(/\\/$/, '');\n\t\tthis.sleep =\n\t\t\toptions.sleep ??\n\t\t\t((ms) => new Promise((resolve) => setTimeout(resolve, ms)));\n\t\tthis.maxAttempts = options.maxAttempts ?? 4;\n\t\tthis.authorization = `Bearer ${options.credential.token}`;\n\t}\n\n\t/** The records of one name and type — empty when there are none. */\n\tasync getRecordSet(\n\t\tdomain: string,\n\t\ttype: GoDaddyRecordType,\n\t\tname: string,\n\t): Promise<GoDaddyRecord[]> {\n\t\tthis.guard(domain, type, name);\n\t\tconst records = await this.request<GoDaddyRecord[]>(\n\t\t\t'GET',\n\t\t\tdomain,\n\t\t\tthis.recordPath(domain, type, name),\n\t\t);\n\t\treturn Array.isArray(records) ? records : [];\n\t}\n\n\t/** Replace one name's records of one type with `values`. */\n\tasync replaceRecordSet(\n\t\tdomain: string,\n\t\ttype: GoDaddyRecordType,\n\t\tname: string,\n\t\tvalues: readonly string[],\n\t\tttl: number,\n\t): Promise<void> {\n\t\tthis.guard(domain, type, name);\n\t\tawait this.request(\n\t\t\t'PUT',\n\t\t\tdomain,\n\t\t\tthis.recordPath(domain, type, name),\n\t\t\tvalues.map((data) => ({\n\t\t\t\tdata,\n\t\t\t\tttl: Math.max(ttl, GODADDY_MIN_TTL),\n\t\t\t})),\n\t\t);\n\t}\n\n\t/** Delete one name's records of one type. */\n\tasync deleteRecordSet(\n\t\tdomain: string,\n\t\ttype: GoDaddyRecordType,\n\t\tname: string,\n\t): Promise<void> {\n\t\tthis.guard(domain, type, name);\n\t\tawait this.request('DELETE', domain, this.recordPath(domain, type, name));\n\t}\n\n\tprivate recordPath(domain: string, type: string, name: string): string {\n\t\treturn `/v1/domains/${encodeURIComponent(domain)}/records/${type}/${encodeURIComponent(name)}`;\n\t}\n\n\t/** Only A, AAAA and CNAME, at a real record name. */\n\tprivate guard(domain: string, type: string, name: string): void {\n\t\tif (!(GODADDY_MANAGED_TYPES as readonly string[]).includes(type)) {\n\t\t\tthrow new GoDaddyRecordNotAllowed(\n\t\t\t\tdomain,\n\t\t\t\ttype,\n\t\t\t\tname,\n\t\t\t\t'it is not a type gkm manages',\n\t\t\t);\n\t\t}\n\t\tif (!isRecordName(name)) {\n\t\t\tthrow new GoDaddyRecordNotAllowed(\n\t\t\t\tdomain,\n\t\t\t\ttype,\n\t\t\t\tname,\n\t\t\t\t'it is not a record name relative to the domain',\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate async request<T = unknown>(\n\t\tmethod: 'GET' | 'PUT' | 'DELETE',\n\t\tdomain: string,\n\t\tpath: string,\n\t\tbody?: unknown,\n\t): Promise<T> {\n\t\tlet retryAfterSec: number | undefined;\n\t\tfor (let attempt = 1; attempt <= this.maxAttempts; attempt++) {\n\t\t\tconst response = await fetch(`${this.baseUrl}${path}`, {\n\t\t\t\tmethod,\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: this.authorization,\n\t\t\t\t\tAccept: 'application/json',\n\t\t\t\t\t...(body !== undefined ? { 'Content-Type': 'application/json' } : {}),\n\t\t\t\t},\n\t\t\t\t...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n\t\t\t});\n\n\t\t\tif (response.ok) {\n\t\t\t\tif (method === 'GET') this.readSucceeded = true;\n\t\t\t\tif (response.status === 204) return undefined as T;\n\t\t\t\tconst text = await response.text();\n\t\t\t\treturn (text ? JSON.parse(text) : undefined) as T;\n\t\t\t}\n\n\t\t\tconst error = await errorBody(response);\n\t\t\tif (response.status === 504 && attempt < this.maxAttempts) {\n\t\t\t\tawait this.sleep(Math.min(2 ** attempt * 1000, 30_000));\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (response.status === 429) {\n\t\t\t\tretryAfterSec = error.retryAfterSec;\n\t\t\t\tif (attempt === this.maxAttempts) break;\n\t\t\t\t// GoDaddy says how long; without it, back off exponentially.\n\t\t\t\tconst waitMs =\n\t\t\t\t\tretryAfterSec !== undefined\n\t\t\t\t\t\t? Math.min(retryAfterSec, 60) * 1000\n\t\t\t\t\t\t: Math.min(2 ** attempt * 1000, 60_000);\n\t\t\t\tawait this.sleep(waitMs);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (response.status === 401) {\n\t\t\t\tthrow new GoDaddyCredentialsInvalid(error.code);\n\t\t\t}\n\t\t\tif (response.status === 403) {\n\t\t\t\t// The account restriction says ACCESS_DENIED; a scoped token that\n\t\t\t\t// may not make this call says so, or — after a read went\n\t\t\t\t// through — can only be that.\n\t\t\t\tconst scoped =\n\t\t\t\t\t/scope/i.test(`${error.code ?? ''} ${error.message ?? ''}`) ||\n\t\t\t\t\t(method !== 'GET' && this.readSucceeded);\n\t\t\t\tif (scoped) {\n\t\t\t\t\tthrow new GoDaddyScopeMissing(\n\t\t\t\t\t\tmethod,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\tmethod === 'GET' ? 'DNS records read' : 'domains.dns:update',\n\t\t\t\t\t\terror.message ?? error.code,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tthrow new GoDaddyApiAccessDenied(domain, error.message ?? error.code);\n\t\t\t}\n\t\t\tif (response.status === 404) {\n\t\t\t\tthrow new GoDaddyDomainNotFound(domain, error.code);\n\t\t\t}\n\t\t\tif (\n\t\t\t\tresponse.status === 422 &&\n\t\t\t\t/domain/i.test(error.code ?? '') &&\n\t\t\t\t!/record/i.test(`${error.code ?? ''} ${error.message ?? ''}`)\n\t\t\t) {\n\t\t\t\tthrow new GoDaddyDomainNotFound(domain, error.code);\n\t\t\t}\n\t\t\tthrow new GoDaddyRequestFailed(\n\t\t\t\tmethod,\n\t\t\t\tpath,\n\t\t\t\tresponse.status,\n\t\t\t\terror.code,\n\t\t\t\terror.message,\n\t\t\t);\n\t\t}\n\t\tthrow new GoDaddyRateLimited(this.maxAttempts, retryAfterSec);\n\t}\n}\n\nasync function errorBody(response: Response): Promise<GoDaddyErrorBody> {\n\ttry {\n\t\tconst parsed = (await response.json()) as GoDaddyErrorBody;\n\t\treturn parsed && typeof parsed === 'object' ? parsed : {};\n\t} catch {\n\t\treturn {};\n\t}\n}\n","/**\n * GoDaddy DNS Provider\n *\n * Implements DnsProvider against GoDaddy's v1 API, one record set — a name\n * and a type — at a time: it reads and writes only the A, AAAA and CNAME\n * records of the names it is handed, and never the zone as a whole, a domain\n * or the account. A Personal Access Token scoped to `domains.dns:update` is all it needs.\n *\n * A token that may write records and not read them puts the provider in\n * write-only mode: every record is PUT — idempotent, so one that already has\n * its value is left as it was — and nothing is compared or deleted.\n *\n * Credentials: `GODADDY_API_TOKEN`, else what `gkm login --provider godaddy`\n * stored. Never logged.\n */\n\nimport {\n\ttype GoDaddyCredential,\n\tMissingCredential,\n\tstoredCredentials,\n} from '../../../deploy/credentials';\nimport {\n\ttype DeleteDnsRecord,\n\ttype DeleteResult,\n\ttype DnsProvider,\n\ttype DnsRecord,\n\tDnsRecordsUnreadable,\n\ttype UpsertDnsRecord,\n\ttype UpsertResult,\n} from './DnsProvider';\nimport {\n\tGODADDY_MANAGED_TYPES,\n\tGODADDY_MIN_TTL,\n\tGoDaddyApi,\n\tGoDaddyApiAccessDenied,\n\ttype GoDaddyApiOptions,\n\tGoDaddyDomainNotFound,\n\ttype GoDaddyRecord,\n\tGoDaddyRecordNotAllowed,\n\ttype GoDaddyRecordType,\n\tGoDaddyScopeMissing,\n} from './godaddy-api';\n\nexport interface GoDaddyProviderOptions {\n\t/** The token, when the caller already has it. */\n\tcredential?: GoDaddyCredential;\n\t/** Where the variables are read — `process.env` by default. */\n\tenv?: NodeJS.ProcessEnv;\n\t/** The CLI's home, whose `credentials.json` holds a stored login. */\n\thome?: string;\n\t/** The API's base URL, for tests. */\n\tbaseUrl?: string;\n\t/** How a 429 is waited out, for tests. */\n\tsleep?: GoDaddyApiOptions['sleep'];\n\tmaxAttempts?: number;\n\t/** Where the write-only notice goes — `console.log` by default. */\n\tlog?: (line: string) => void;\n}\n\n/** GoDaddy cannot list a zone through the records-by-name endpoints. */\nexport class GoDaddyZoneListingUnsupported extends Error {\n\tconstructor(readonly domain: string) {\n\t\tsuper(\n\t\t\t`gkm reads GoDaddy records by name and type only — it never lists ` +\n\t\t\t\t`${domain}'s whole zone, so a token scoped to DNS updates is enough. ` +\n\t\t\t\t'Use readRecords(domain, [{ name, type }]) instead of getRecords.',\n\t\t);\n\t\tthis.name = 'GoDaddyZoneListingUnsupported';\n\t}\n}\n\n/** A value compared the way DNS does: case-insensitive, no trailing dot. */\nfunction normalize(value: string): string {\n\treturn value.trim().replace(/\\.$/, '').toLowerCase();\n}\n\nfunction managed(type: string): type is GoDaddyRecordType {\n\treturn (GODADDY_MANAGED_TYPES as readonly string[]).includes(type);\n}\n\n/** A read GoDaddy refused: write-only, rather than a failure. */\nfunction readDenied(error: unknown): boolean {\n\treturn (\n\t\terror instanceof GoDaddyScopeMissing ||\n\t\terror instanceof GoDaddyApiAccessDenied\n\t);\n}\n\nexport class GoDaddyProvider implements DnsProvider {\n\treadonly name = 'godaddy';\n\tprivate api: GoDaddyApi | null = null;\n\t/** Set once a read is refused: every write is then a blind PUT. */\n\tprivate writeOnly = false;\n\n\tconstructor(private readonly options: GoDaddyProviderOptions = {}) {}\n\n\t/** Whether the token turned out unable to read records. */\n\tget readsDenied(): boolean {\n\t\treturn this.writeOnly;\n\t}\n\n\tprivate async getApi(): Promise<GoDaddyApi> {\n\t\tif (this.api) return this.api;\n\t\tconst credential =\n\t\t\tthis.options.credential ??\n\t\t\t(await storedCredentials({\n\t\t\t\tenv: this.options.env ?? process.env,\n\t\t\t\t...(this.options.home ? { home: this.options.home } : {}),\n\t\t\t}).get({ kind: 'godaddy' }));\n\t\tif (!credential) throw new MissingCredential('godaddy', undefined);\n\t\tthis.api = new GoDaddyApi({\n\t\t\tcredential,\n\t\t\t...(this.options.baseUrl ? { baseUrl: this.options.baseUrl } : {}),\n\t\t\t...(this.options.sleep ? { sleep: this.options.sleep } : {}),\n\t\t\t...(this.options.maxAttempts\n\t\t\t\t? { maxAttempts: this.options.maxAttempts }\n\t\t\t\t: {}),\n\t\t});\n\t\treturn this.api;\n\t}\n\n\tprivate assertManaged(domain: string, type: string, name: string): void {\n\t\tif (!managed(type)) {\n\t\t\tthrow new GoDaddyRecordNotAllowed(\n\t\t\t\tdomain,\n\t\t\t\ttype,\n\t\t\t\tname,\n\t\t\t\t'it is not a type gkm manages',\n\t\t\t);\n\t\t}\n\t}\n\n\t/** Never: the whole zone is not read. See {@link GoDaddyZoneListingUnsupported}. */\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\tthrow new GoDaddyZoneListingUnsupported(domain);\n\t}\n\n\t/**\n\t * The records of exactly these names and types, one request each.\n\t *\n\t * @throws {DnsRecordsUnreadable} when the token may not read records\n\t */\n\tasync readRecords(\n\t\tdomain: string,\n\t\twanted: readonly DeleteDnsRecord[],\n\t): Promise<DnsRecord[]> {\n\t\tfor (const { name, type } of wanted) this.assertManaged(domain, type, name);\n\t\tconst api = await this.getApi();\n\t\tconst found: DnsRecord[] = [];\n\t\tfor (const { name, type } of wanted) {\n\t\t\tlet records: GoDaddyRecord[];\n\t\t\ttry {\n\t\t\t\trecords = await api.getRecordSet(\n\t\t\t\t\tdomain,\n\t\t\t\t\ttype as GoDaddyRecordType,\n\t\t\t\t\tname,\n\t\t\t\t);\n\t\t\t} catch (error) {\n\t\t\t\tif (!readDenied(error)) throw error;\n\t\t\t\tthis.writeOnly = true;\n\t\t\t\tthrow new DnsRecordsUnreadable(\n\t\t\t\t\tthis.name,\n\t\t\t\t\tdomain,\n\t\t\t\t\t(error as Error).name,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (records.length === 0) continue;\n\t\t\tfound.push({\n\t\t\t\tname,\n\t\t\t\ttype,\n\t\t\t\tttl: records[0]!.ttl ?? GODADDY_MIN_TTL,\n\t\t\t\tvalues: records.map((r) => r.data),\n\t\t\t});\n\t\t}\n\t\treturn found;\n\t}\n\n\t/**\n\t * Each record set to exactly its one value: read it, and write it only\n\t * when it differs — or, with a token that cannot read, write it. Only the\n\t * name and type of each record handed in is touched.\n\t */\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: UpsertDnsRecord[],\n\t): Promise<UpsertResult[]> {\n\t\t// Every record is checked before any is written, so a refused one\n\t\t// leaves the zone as it was.\n\t\tfor (const record of records) {\n\t\t\tthis.assertManaged(domain, record.type, record.name);\n\t\t}\n\t\tconst api = await this.getApi();\n\n\t\tconst results: UpsertResult[] = [];\n\t\tfor (const record of records) {\n\t\t\tconst type = record.type as GoDaddyRecordType;\n\t\t\tlet existing: GoDaddyRecord[] | undefined;\n\t\t\tif (!this.writeOnly) {\n\t\t\t\ttry {\n\t\t\t\t\texisting = await api.getRecordSet(domain, type, record.name);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (!readDenied(error)) throw error;\n\t\t\t\t\tthis.writeOnly = true;\n\t\t\t\t\t(this.options.log ?? console.log)(\n\t\t\t\t\t\t` ℹ The GoDaddy token cannot read ${domain}'s DNS records, so changes cannot be diffed: writing each record (idempotent).`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (existing) {\n\t\t\t\tconst values = existing.map((r) => normalize(r.data));\n\t\t\t\tif (values.length === 1 && values[0] === normalize(record.value)) {\n\t\t\t\t\tresults.push({ record, created: false, unchanged: true });\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait api.replaceRecordSet(\n\t\t\t\tdomain,\n\t\t\t\ttype,\n\t\t\t\trecord.name,\n\t\t\t\t[record.value],\n\t\t\t\tMath.max(record.ttl, GODADDY_MIN_TTL),\n\t\t\t);\n\t\t\tresults.push({\n\t\t\t\trecord,\n\t\t\t\tcreated: existing !== undefined && existing.length === 0,\n\t\t\t\tunchanged: false,\n\t\t\t});\n\t\t}\n\t\treturn results;\n\t}\n\n\tasync deleteRecords(\n\t\tdomain: string,\n\t\trecords: DeleteDnsRecord[],\n\t): Promise<DeleteResult[]> {\n\t\tfor (const record of records) {\n\t\t\tthis.assertManaged(domain, record.type, record.name);\n\t\t}\n\t\tconst api = await this.getApi();\n\t\tconst results: DeleteResult[] = [];\n\t\tfor (const record of records) {\n\t\t\tconst type = record.type as GoDaddyRecordType;\n\t\t\tif (!this.writeOnly) {\n\t\t\t\tlet existing: GoDaddyRecord[] | undefined;\n\t\t\t\ttry {\n\t\t\t\t\texisting = await api.getRecordSet(domain, type, record.name);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (!readDenied(error)) throw error;\n\t\t\t\t\tthis.writeOnly = true;\n\t\t\t\t}\n\t\t\t\tif (existing && existing.length === 0) {\n\t\t\t\t\tresults.push({ record, deleted: false, notFound: true });\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait api.deleteRecordSet(domain, type, record.name);\n\t\t\t} catch (error) {\n\t\t\t\t// Write-only, nothing was read: a record that is not there is a 404.\n\t\t\t\tif (this.writeOnly && error instanceof GoDaddyDomainNotFound) {\n\t\t\t\t\tresults.push({ record, deleted: false, notFound: true });\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tresults.push({ record, deleted: true, notFound: false });\n\t\t}\n\t\treturn results;\n\t}\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"SubscriberGenerator-BPqQf8fB.mjs","names":[],"sources":["../src/types.ts","../src/reconcile/fakes.ts","../src/generators/SubscriberGenerator.ts"],"sourcesContent":["import type {\n\tDevConfig,\n\tSecretsConfig,\n\tStagesConfig,\n\tTestConfig,\n} from './workspace/types.js';\n\nexport type MainProvider = 'aws' | 'server';\n\nexport type Routes = string | string[];\n\n/**\n * Supported event backend types.\n *\n * - `pgboss`: Reuses PostgreSQL with a dedicated user/schema. Auto-enables db service.\n * - `sns`: Adds the AWS emulator (floci) container; each topic and queue is\n * created on it and addressed by its own string.\n * - `rabbitmq`: Adds RabbitMQ container.\n *\n * Each topic and queue resolves its own `<ID>_PUBLISHER_CONNECTION_STRING`,\n * which its producers and its consumers both read. pg-boss and RabbitMQ also\n * resolve `EVENT_PUBLISHER_CONNECTION_STRING`, the one broker crons schedule\n * through.\n */\nexport type EventsBackend = 'pgboss' | 'sns' | 'rabbitmq';\n\n/**\n * Where a declared cache actually lives.\n *\n * A deployment choice, not an application one — the same code caches into any\n * of these — so it is config here beside `events` rather than a field on the\n * construct. Each backend is consistent between local and deployed, which is\n * the property worth having: a cache that behaves differently in the two places\n * is worse than a slower one.\n *\n * - `upstash` (default) — HTTP with a token. Reachable from a Lambda with no\n * VPC and no connection pool, which is why it is the default; locally the\n * `serverless-redis-http` proxy in front of Redis speaks the same protocol.\n * - `elasticache` — the Redis wire protocol, inside a VPC. Locally, plain Redis.\n * - `db` — a table in the database the app already declared. No infrastructure\n * at all, and the same relationship pg-boss has to Postgres: it creates its\n * own table on first use and needs a declared database to live in.\n * - `redis` — a Redis the stack runs beside the apps, on its network. It holds\n * *every* declared cache, one declared from a database included, so the\n * cache never competes with the database for its connections. Only the\n * compose target selects it today (`gkm compose`).\n */\nexport type CacheBackend = 'upstash' | 'elasticache' | 'db' | 'redis';\n\n/**\n * The backends a deploy target defaults to — every one but the compose\n * stack's own Redis, which only that target runs.\n */\nexport type TargetCacheBackend = Exclude<CacheBackend, 'redis'>;\n\n/** Whether `value` names a cache backend. */\nexport function isCacheBackend(value: string): value is CacheBackend {\n\treturn (\n\t\tvalue === 'upstash' ||\n\t\tvalue === 'elasticache' ||\n\t\tvalue === 'db' ||\n\t\tvalue === 'redis'\n\t);\n}\n\n/**\n * Whether a backend holds every declared cache, a cache declared from a\n * database (`database.cache('Sessions')`) included.\n *\n * Every other backend leaves such a cache where its declaration put it: the\n * declaration is the stronger statement. The stack's own Redis is the\n * exception, because a stack that runs one runs it *for* the cache — a table\n * beside it would be a second cache nobody asked for.\n */\nexport function holdsEveryCache(backend: CacheBackend): boolean {\n\treturn backend === 'redis';\n}\n\n/**\n * Where a declared bucket actually lives.\n *\n * The fourth of these selectors and the last to arrive, which is why\n * `UPLOADS_URL` had no answer on a container target: there was nothing to\n * choose with. Like the others it changes no application code — the `s3://`\n * scheme picks the driver, and every one of these speaks it.\n *\n * - `s3` — a real bucket. Nothing is created on the host; the URL is composed\n * against credentials you already hold, the same way `resend` is an API key\n * rather than infrastructure.\n * - `r2` — Cloudflare's, reached through the same S3 API and differing only in\n * its endpoint.\n * - `minio` — a container. Everything the app declares lives on the box, which\n * is what makes a single-host deploy self-contained, and what makes it a\n * single disk with no replication. Locally this is what every backend is.\n */\nexport type StorageBackend = 'minio' | 's3' | 'r2';\n\n/**\n * The backend a project gets when it says nothing, by where it deploys.\n *\n * Flat defaults could not be right for both: `upstash` on a single box means a\n * SaaS round-trip for a cache the database could have held, and `minio` on AWS\n * means running a bucket beside S3. So the default is a function of the target,\n * and an app that says nothing gets the obvious answer in both places — which\n * is the whole of what \"zero config\" can mean once there is more than one\n * target.\n *\n * `server` covers Dokploy and a bare host alike: the question these answer is\n * whether the app is next to a managed cloud or next to its own containers, and\n * on that question the two are the same.\n *\n * A per-construct, per-stage override is the layer above this — see\n * `constructs-outstanding.md` — so two buckets in one app can differ. This is\n * only what happens when nobody has said anything at all.\n */\nexport const DEFAULT_CACHE: Record<MainProvider, TargetCacheBackend> = {\n\t// Reachable from a Lambda with no VPC and no connection pool.\n\taws: 'upstash',\n\t// The database is already there and already has a connection pool open to\n\t// it. Anything else is a second service to run for a table.\n\tserver: 'db',\n};\n\n/**\n * Which broker carries a declared queue or topic, by target.\n *\n * Whether there are events at all is the manifest's answer — a declared\n * `Topic` or `Queue` — so this only ever answers the second question. AWS\n * deploys them to SNS and SQS, and locally the AWS emulator stands in, so the\n * broker a developer runs is the one production has. A server has a Postgres\n * already, and pg-boss keeps its queues there.\n */\nexport const DEFAULT_EVENTS: Record<MainProvider, EventsBackend> = {\n\taws: 'sns',\n\tserver: 'pgboss',\n};\n\n/** Where a bucket lives when nobody said — see {@link DEFAULT_CACHE}. */\nexport const DEFAULT_STORAGE: Record<MainProvider, StorageBackend> = {\n\taws: 's3',\n\tserver: 'minio',\n};\n\n/**\n * Who delivers a declared app's mail.\n *\n * Unlike a cache, this changes *nothing* about the client. Every backend speaks\n * SMTP, so the declaration's `smtp://` URL is true of all of them and only the\n * provisioner differs — which is the same reason the declaration has no\n * `provider` field and there is no `ses://` scheme.\n *\n * - `ses` (default) — the only backend that can be a *chain* of resources: an\n * identity, a user, an access key, and an SMTP password derived from it,\n * because SES issues IAM credentials rather than SMTP ones. Hand it a URL and\n * it uses that instead, which is what you want when the credentials already\n * exist — provisioning a second set of sending credentials for an identity\n * that already has them is not a thing to do by default.\n * - `resend` — an API key you hold, composed into an SMTP URL. Nothing to\n * provision: a SaaS account is not infrastructure.\n * - `smtp` — a URL supplied whole, for a relay somebody else runs.\n *\n * Locally every one of them is Mailpit, because locally none of it matters.\n */\nexport type EmailBackend = 'resend' | 'ses' | 'smtp';\n\n/**\n * Who delivers mail when nobody said.\n *\n * Flat, unlike the others, and deliberately: mail is a SaaS on every target.\n * There is no self-hosted preset to default a container deploy to — Mailpit is\n * a development inbox, not a sender — so `server` has no answer of its own to\n * give and would only be `ses` written twice.\n */\nexport const DEFAULT_EMAIL: EmailBackend = 'ses';\n\n/** Supported docker-compose service names */\nexport type ComposeServiceName =\n\t| 'postgres'\n\t| 'redis'\n\t| 'rabbitmq'\n\t| 'minio'\n\t| 'mailpit'\n\t| 'localstack';\n\nexport interface DockerConfig {\n\t/** Container registry URL (e.g., 'ghcr.io/myorg') */\n\tregistry?: string;\n\t/** Docker image name (default: derived from package.json name) */\n\timageName?: string;\n\t/** Base Docker image (default: 'node:22-alpine') */\n\tbaseImage?: string;\n\t/** Container port (default: 3000) */\n\tport?: number;\n}\n\nexport type Runtime = 'node' | 'bun';\n\nexport interface TelescopeConfig {\n\t/** Enable/disable telescope (default: true in development) */\n\tenabled?: boolean;\n\t/** Port for telescope to use (defaults to server port) */\n\tport?: number;\n\t/** Path prefix for telescope UI (default: /__telescope) */\n\tpath?: string;\n\t/** Ignore patterns for telescope (e.g., ['/health', '/metrics']) */\n\tignore?: string[];\n\t/** Record request/response bodies (default: true) */\n\trecordBody?: boolean;\n\t/** Maximum entries to keep in memory (default: 1000) */\n\tmaxEntries?: number;\n\t/** Enable WebSocket for real-time updates (default: true, requires @hono/node-ws for Node.js) */\n\twebsocket?: boolean;\n}\n\nexport interface OpenApiConfig {\n\t/** Enable OpenAPI generation (default: true) */\n\tenabled?: boolean;\n\t/** API title */\n\ttitle?: string;\n\t/** API version */\n\tversion?: string;\n\t/** API description */\n\tdescription?: string;\n}\n\nexport interface HooksConfig {\n\t/**\n\t * Path to a module exporting server lifecycle hooks.\n\t * The module should export `beforeSetup` and/or `afterSetup` functions.\n\t *\n\t * @example\n\t * ```typescript\n\t * // src/config/hooks.ts\n\t * import type { Hono } from 'hono';\n\t * import type { Logger } from '@geekmidas/logger';\n\t * import type { EnvironmentParser } from '@geekmidas/envkit';\n\t *\n\t * // Called BEFORE gkm endpoints are registered\n\t * export function beforeSetup(app: Hono, ctx: { envParser: EnvironmentParser; logger: Logger }) {\n\t * app.use('*', cors());\n\t * app.get('/custom/health', (c) => c.json({ status: 'ok' }));\n\t * }\n\t *\n\t * // Called AFTER gkm endpoints are registered\n\t * export function afterSetup(app: Hono, ctx: { envParser: EnvironmentParser; logger: Logger }) {\n\t * app.notFound((c) => c.json({ error: 'Not found' }, 404));\n\t * app.onError((err, c) => c.json({ error: err.message }, 500));\n\t * }\n\t * ```\n\t */\n\tserver?: string;\n}\n\nexport interface GkmConfig {\n\t/**\n\t * What this app is called, and therefore how everything it declares is named.\n\t *\n\t * The same field `sst.config.ts` carries, doing the same job: every physical\n\t * name is `{stage}-{name}-{construct}`, so `Database` in the `production`\n\t * stage of `kitchen-sink` is `production-kitchen-sink-database` on AWS and on\n\t * Dokploy alike. One statement, in the config, rather than a rule each\n\t * provider restates.\n\t *\n\t * `defineWorkspace` has always had this. A single-app config did not, so its\n\t * names fell back to whatever the directory happened to be called — the\n\t * monorepo's package name for the project and the app's for the application,\n\t * two accidents of layout standing in for a decision.\n\t *\n\t * Defaults to the package.json name with any scope stripped, then the\n\t * directory name.\n\t */\n\tname?: string;\n\t/** The project's stages: which one is local, which deploy */\n\tstages: StagesConfig;\n\t/** Encrypted secrets, and where deployed stages' secrets are stored */\n\tsecrets?: SecretsConfig;\n\t/** What `gkm test` hands a feature test: where the factories are. */\n\ttest?: TestConfig;\n\t/** How `gkm dev` is reached: the discovery endpoint's port and origins. */\n\tdev?: DevConfig;\n\t/**\n\t * Constructs glob pattern — one glob, every kind.\n\t *\n\t * What reconcile reads to derive the containers a stage needs: a database\n\t * implies Postgres, objects imply MinIO, mail implies Mailpit. A glob per\n\t * kind cannot find a resource, because a declared `ObjectStorage` has no\n\t * kind to be listed under.\n\t *\n\t * @example './src/**\\/*.ts'\n\t */\n\tconstructs: Routes;\n\t/**\n\t * Server lifecycle hooks for customizing the Hono app.\n\t * Allows adding custom routes, middleware, error handlers, etc.\n\t *\n\t * @example\n\t * hooks: {\n\t * server: './src/config/hooks'\n\t * }\n\t */\n\thooks?: HooksConfig;\n\t/**\n\t * Telescope configuration for debugging/monitoring.\n\t * Can be:\n\t * - A string path to a module that exports a Telescope instance (recommended)\n\t * - A boolean to enable/disable with defaults\n\t * - A TelescopeConfig object for inline configuration\n\t */\n\ttelescope?: string | boolean | TelescopeConfig;\n\t/**\n\t * OpenAPI generation configuration.\n\t * Can be:\n\t * - A boolean to enable/disable with defaults (output: ./src/api/openapi.ts)\n\t * - An OpenApiConfig object for customization\n\t *\n\t * When enabled, OpenAPI spec is generated on startup and regenerated on route changes.\n\t *\n\t * @example\n\t * openapi: true\n\t *\n\t * @example\n\t * openapi: {\n\t * output: './src/api/openapi.ts',\n\t * title: 'My API',\n\t * version: '1.0.0',\n\t * }\n\t */\n\topenapi?: boolean | OpenApiConfig;\n\t/** Runtime to use for dev server (default: 'node') */\n\truntime?: Runtime;\n\t/**\n\t * Environment file(s) to load for development.\n\t * Can be:\n\t * - A string path to a single env file (e.g., '.env.local')\n\t * - An array of paths to load in order (later files override earlier)\n\t * - Defaults to '.env' if not specified\n\t *\n\t * @example\n\t * env: '.env.local'\n\t *\n\t * @example\n\t * env: ['.env', '.env.local']\n\t */\n\tenv?: string | string[];\n\t/**\n\t * Docker deployment configuration.\n\t * Used by `gkm docker` and `gkm prepack` commands.\n\t *\n\t * @example\n\t * docker: {\n\t * registry: 'ghcr.io/myorg',\n\t * imageName: 'my-api',\n\t * compose: {\n\t * services: ['postgres', 'redis']\n\t * }\n\t * }\n\t */\n\tdocker?: DockerConfig;\n\t/**\n\t * Where this app deploys, and what the target needs to know.\n\t *\n\t * Absent until now, which meant a single-app project could not configure a\n\t * Dokploy deploy at all: the wrap that turns one into a workspace hardcoded\n\t * `{ default: 'dokploy' }` and dropped everything else, so there was no\n\t * endpoint, no registry and no domain — and `resolveHost` failed with \"no\n\t * domain configured for stage\" on a config that had nowhere to put one.\n\t *\n\t * The same shape a workspace config uses, so moving from one to the other is\n\t * a rename rather than a rewrite.\n\t */\n\tdeploy?: import('./workspace/types').DeployConfig;\n\t/** Each deployed stage's base domain — the same field a workspace has. */\n\tdomains?: import('./workspace/types').DomainsConfig;\n\t/** Each root domain's DNS provider — the same field a workspace has. */\n\tdns?: import('./workspace/types').WorkspaceDnsConfig;\n}\n\nexport interface BuildOptions {\n\t/** Overrides the deploy target's: a Dockerfile builds a server regardless. */\n\tprovider?: MainProvider;\n\t/**\n\t * Overrides the target's cache backend for the drivers the entry\n\t * registers — set by the Dockerfiles `gkm compose` writes, whose stack runs\n\t * its own Redis.\n\t */\n\tcache?: CacheBackend;\n\tenableOpenApi?: boolean;\n\t/** Build for production (no dev tools, bundled output) */\n\tproduction?: boolean;\n\t/** Skip bundling step in production build */\n\tskipBundle?: boolean;\n\t/** Stage for secrets injection (e.g., 'production', 'staging') */\n\tstage?: string;\n\t/**\n\t * When true, optional environment variables (those with `.optional()` or\n\t * `.default()` in the envParser) are suffixed with `?` within each\n\t * construct's `environment` array in the manifest (e.g. `PORT?`).\n\t * Defaults to false.\n\t */\n\tmarkOptional?: boolean;\n\t/**\n\t * Where the apps' own build scripts (turbo) run. Defaults to a\n\t * `LocalSandbox` on the workspace: this machine, with an allowlisted\n\t * environment.\n\t */\n\tsandbox?: import('./sandbox/sandbox').Sandbox;\n}\n\n/** Result from build command when secrets are injected */\nexport interface BuildResult {\n\t/** Ephemeral master key for deployment (only if stage was provided) */\n\tmasterKey?: string;\n}\n\n// The deployment manifest types are a shared, dependency-free data contract\n// in `@geekmidas/manifest`, re-exported here for back-compat with existing\n// `@geekmidas/cli` imports.\nexport type {\n\tCronInfo,\n\tFunctionInfo,\n\tQueueInfo,\n\tRouteInfo,\n\tSubscriberInfo,\n\tTopicInfo,\n} from '@geekmidas/manifest';\n","/**\n * External APIs' fakes, read from where the convention puts them.\n *\n * `test/fakes/<id>.ts` default-exports `fake.app(…)` or `fake.image(…)`. The\n * construct never names it — that is what keeps a fake out of a deployed\n * bundle — so this is the one place it is found, by the same rule that finds a\n * database's factory in `test/factories/<id>.ts`.\n */\n\nimport { access } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { ConstructManifest } from '@geekmidas/manifest';\nimport { kebabCase } from '@geekmidas/manifest';\n\n/** Where a project keeps its external APIs' fakes, relative to its root. */\nexport const DEFAULT_FAKES_DIR = 'test/fakes';\n\n/** One external API's fake, as the local target needs it. */\nexport interface LocalFake {\n\t/** The file it was read from — what the harness and `gkm dev` import. */\n\tfile: string;\n\t/** The image to run, for a fake the provider publishes. */\n\timage?: string;\n\t/** The port that image listens on inside its container. */\n\tport?: number;\n\t/** What the fake accepts, as the JSON `<ID>_CREDENTIALS` holds. */\n\tcredentials: string;\n}\n\n/** The marker `fake.app` and `fake.image` set — the same registered symbol. */\nconst FAKE = Symbol.for('@geekmidas/constructs/fake');\n\ninterface FakeExport {\n\tkind: 'app' | 'image';\n\timage?: string;\n\tport?: number;\n\tcredentials: unknown;\n}\n\n/**\n * The fake for every external API the manifest declares, keyed by id.\n *\n * @throws {NoFake} when an external API has no file, since a local stage would\n * otherwise have nothing to call.\n * @throws {NotAFake} when the file's default export is not one.\n */\nexport async function readFakes(\n\troot: string,\n\tmanifest: ConstructManifest,\n): Promise<Record<string, LocalFake>> {\n\tconst fakes: Record<string, LocalFake> = {};\n\n\tfor (const [id, declaration] of Object.entries(manifest)) {\n\t\tif (declaration?.kind !== 'external-api') continue;\n\n\t\tconst file = await fakeFile(join(root, DEFAULT_FAKES_DIR), id);\n\t\tconst module = (await import(pathToFileURL(file).href)) as {\n\t\t\tdefault?: unknown;\n\t\t};\n\t\tconst value = module.default;\n\t\tif (!isFakeExport(value)) throw new NotAFake(file);\n\n\t\tfakes[id] = {\n\t\t\tfile,\n\t\t\t...(value.kind === 'image'\n\t\t\t\t? { image: value.image, port: value.port }\n\t\t\t\t: {}),\n\t\t\tcredentials: JSON.stringify(value.credentials),\n\t\t};\n\t}\n\n\treturn fakes;\n}\n\n/** The first of `<id>.ts`, `.mts`, `.js`, `.mjs` that exists. */\nasync function fakeFile(folder: string, id: string): Promise<string> {\n\tconst base = join(folder, kebabCase(id));\n\tfor (const extension of ['.ts', '.mts', '.js', '.mjs']) {\n\t\tconst file = `${base}${extension}`;\n\t\tconst exists = await access(file).then(\n\t\t\t() => true,\n\t\t\t() => false,\n\t\t);\n\t\tif (exists) return file;\n\t}\n\n\tthrow new NoFake(id, `${base}.ts`);\n}\n\nfunction isFakeExport(value: unknown): value is FakeExport {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\t(value as { [FAKE]?: unknown })[FAKE] === true\n\t);\n}\n\n/** An external API with no fake for a local stage to call. */\nexport class NoFake extends Error {\n\tconstructor(\n\t\treadonly id: string,\n\t\treadonly file: string,\n\t) {\n\t\tsuper(\n\t\t\t`'${id}' is an external API, and a local stage calls its fake rather ` +\n\t\t\t\t`than the provider — but there is none. Create ${file}, ` +\n\t\t\t\t`default-exporting fake.app(…) or fake.image(…) from ` +\n\t\t\t\t`@geekmidas/constructs/external-api.`,\n\t\t);\n\t\tthis.name = 'NoFake';\n\t}\n}\n\n/** A file in the fakes folder whose default export is not a fake. */\nexport class NotAFake extends Error {\n\tconstructor(readonly file: string) {\n\t\tsuper(\n\t\t\t`${file} is where a fake lives, and its default export is not one. ` +\n\t\t\t\t`Export default fake.app(handler, { credentials }) or ` +\n\t\t\t\t`fake.image(image, { port, credentials }).`,\n\t\t);\n\t\tthis.name = 'NotAFake';\n\t}\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join, relative } from 'node:path';\nimport { Subscriber } from '@geekmidas/constructs/subscribers';\nimport { canonicalId, provideKey } from '@geekmidas/manifest';\nimport type { BuildContext } from '../build/types';\nimport type { SubscriberInfo } from '../types';\nimport { runtimeFor } from './EndpointGenerator.js';\nimport {\n\tConstructGenerator,\n\ttype GeneratedConstruct,\n\ttype GeneratorOptions,\n} from './Generator';\n\nexport class SubscriberGenerator extends ConstructGenerator<\n\tSubscriber<any, any, any, any, any, any, any>,\n\tSubscriberInfo[]\n> {\n\tisConstruct(\n\t\tvalue: any,\n\t): value is Subscriber<any, any, any, any, any, any, any> {\n\t\treturn Subscriber.isSubscriber(value);\n\t}\n\n\tasync build(\n\t\tcontext: BuildContext,\n\t\tconstructs: GeneratedConstruct<\n\t\t\tSubscriber<any, any, any, any, any, any, any>\n\t\t>[],\n\t\toutputDir: string,\n\t\toptions?: GeneratorOptions,\n\t): Promise<SubscriberInfo[]> {\n\t\tconst target = options?.target ?? 'aws';\n\t\tconst logger = console;\n\t\tconst subscriberInfos: SubscriberInfo[] = [];\n\n\t\tif (target === 'server') {\n\t\t\t// Generate subscribers.ts for server-based polling (even if empty)\n\t\t\tawait this.generateServerSubscribersFile(\n\t\t\t\toutputDir,\n\t\t\t\tconstructs,\n\t\t\t\tcontext.eventsBackends?.includes('sns') ?? false,\n\t\t\t);\n\n\t\t\tlogger.log(\n\t\t\t\t`Generated server subscribers file with ${constructs.length} subscribers (polling mode)`,\n\t\t\t);\n\n\t\t\t// Return empty array as server subscribers don't have individual handlers\n\t\t\treturn subscriberInfos;\n\t\t}\n\n\t\tif (constructs.length === 0) {\n\t\t\treturn subscriberInfos;\n\t\t}\n\n\t\t// Create subscribers subdirectory\n\t\tconst subscribersDir = join(outputDir, 'subscribers');\n\t\tawait mkdir(subscribersDir, { recursive: true });\n\n\t\t// Generate subscriber handlers\n\t\tfor (const { key, construct, path } of constructs) {\n\t\t\tconst handlerFile = await this.generateSubscriberHandler(\n\t\t\t\tsubscribersDir,\n\t\t\t\tpath.relative,\n\t\t\t\tkey,\n\t\t\t\tconstruct,\n\t\t\t\tcontext,\n\t\t\t\tconstruct.owner,\n\t\t\t);\n\n\t\t\tsubscriberInfos.push({\n\t\t\t\tname: key,\n\t\t\t\thandler: relative(options?.root ?? process.cwd(), handlerFile).replace(\n\t\t\t\t\t/\\.ts$/,\n\t\t\t\t\t'.handler',\n\t\t\t\t),\n\t\t\t\tsubscribedEvents: construct.subscribedEvents || [],\n\t\t\t\t// A subscriber bound to a topic (via `s.topic(topic)`) records the\n\t\t\t\t// binding so infra wires the SNS subscription.\n\t\t\t\t...(construct.topicName\n\t\t\t\t\t? { transport: 'topic' as const, topic: construct.topicName }\n\t\t\t\t\t: {}),\n\t\t\t\ttimeout: construct.timeout,\n\t\t\t\tmemorySize: construct.memorySize,\n\t\t\t\tenvironment: await construct.getEnvironment({\n\t\t\t\t\tmarkOptional: context.markOptional,\n\t\t\t\t}),\n\t\t\t\tdependencies: construct.constructs,\n\t\t\t});\n\n\t\t\tlogger.log(`Generated subscriber handler: ${key}`);\n\t\t}\n\n\t\treturn subscriberInfos;\n\t}\n\n\tprivate async generateSubscriberHandler(\n\t\toutputDir: string,\n\t\tsourceFile: string,\n\t\texportName: string,\n\t\t_subscriber: Subscriber<any, any, any, any, any, any, any>,\n\t\tcontext: BuildContext,\n\t\t/** The construct that owns this — its worker, or its surface. */\n\t\towner: string | undefined,\n\t): Promise<string> {\n\t\tconst handlerFileName = `${exportName}.ts`;\n\t\tconst handlerPath = join(outputDir, handlerFileName);\n\n\t\tconst relativePath = relative(dirname(handlerPath), sourceFile);\n\t\tconst importPath = relativePath.replace(/\\.ts$/, '.js');\n\n\t\t// Imports the construct that owns this, and binds `envParser` and\n\t\t// `logger` off it — the objects it was declared with, rather than a\n\t\t// module path printed in from config.\n\t\tconst runtime = runtimeFor(context, dirname(handlerPath), owner);\n\n\t\tconst content = `import { AWSLambdaSubscriber } from '@geekmidas/constructs/aws';\nimport { ${exportName} } from '${importPath}';\n${runtime.imports}\n${context.storageDrivers?.imports ?? ''}\n${runtime.bindings}\n${context.storageDrivers?.setup ? `\\n// The handler registers the drivers its target needs.\\n${context.storageDrivers.setup}\\n` : ''}\nconst adapter = new AWSLambdaSubscriber(envParser, ${exportName});\n\nexport const handler = adapter.handler;\n`;\n\n\t\tawait writeFile(handlerPath, content);\n\t\treturn handlerPath;\n\t}\n\n\tprivate async generateServerSubscribersFile(\n\t\toutputDir: string,\n\t\tsubscribers: GeneratedConstruct<\n\t\t\tSubscriber<any, any, any, any, any, any, any>\n\t\t>[],\n\t\t/**\n\t\t * The broker is SNS, so a subscriber is pushed to over HTTP. Only then is\n\t\t * the push code generated: it imports the SNS client, which a bundle for\n\t\t * any other broker must never have to resolve.\n\t\t */\n\t\tpush: boolean,\n\t): Promise<string> {\n\t\tawait mkdir(outputDir, { recursive: true });\n\t\tconst subscribersPath = join(outputDir, 'subscribers.ts');\n\n\t\t// Nothing to run, so nothing to import. Every app gets this file, and\n\t\t// `@geekmidas/events` is only installed by one that declared a Topic or\n\t\t// a Queue — importing it here crashed every dev server that had not.\n\t\tif (subscribers.length === 0) {\n\t\t\tawait writeFile(\n\t\t\t\tsubscribersPath,\n\t\t\t\t`/**\n * Generated subscribers setup — this app declares none.\n */\nimport type { EnvironmentParser } from '@geekmidas/envkit';\nimport type { Logger } from '@geekmidas/logger';\n\nexport async function setupSubscribers(\n _app: unknown,\n _envParser: EnvironmentParser<any>,\n _logger: Logger,\n): Promise<(port: number) => Promise<void>> {\n return async () => {};\n}\n\nexport function subscribersStatus(): { failed: string[]; connections: unknown[] } {\n return { failed: [], connections: [] };\n}\n\nexport async function stopSubscribers(): Promise<void> {}\n`,\n\t\t\t);\n\t\t\treturn subscribersPath;\n\t\t}\n\n\t\tconst importsByFile = new Map<string, string[]>();\n\t\tfor (const { path, key } of subscribers) {\n\t\t\tconst importPath = relative(\n\t\t\t\tdirname(subscribersPath),\n\t\t\t\tpath.relative,\n\t\t\t).replace(/\\.ts$/, '.js');\n\t\t\timportsByFile.set(importPath, [\n\t\t\t\t...(importsByFile.get(importPath) ?? []),\n\t\t\t\tkey,\n\t\t\t]);\n\t\t}\n\t\tconst imports = Array.from(importsByFile.entries())\n\t\t\t.map(\n\t\t\t\t([importPath, exports]) =>\n\t\t\t\t\t`import { ${exports.join(', ')} } from '${importPath}';`,\n\t\t\t)\n\t\t\t.join('\\n');\n\n\t\t// Each subscriber reaches the topic it is bound to, by that topic's own\n\t\t// key — resolved here, at build time, from the binding.\n\t\tconst entries = subscribers\n\t\t\t.map(({ key, construct }) => {\n\t\t\t\tconst connectionKey = construct.topicName\n\t\t\t\t\t? provideKey(\n\t\t\t\t\t\t\tcanonicalId(construct.topicName),\n\t\t\t\t\t\t\t'publisherConnectionString',\n\t\t\t\t\t\t)\n\t\t\t\t\t: undefined;\n\t\t\t\treturn ` { id: '${key}', subscriber: ${key}, topic: ${\n\t\t\t\t\tconstruct.topicName ? `'${construct.topicName}'` : 'undefined'\n\t\t\t\t}, connectionKey: ${\n\t\t\t\t\tconnectionKey ? `'${connectionKey}'` : 'undefined'\n\t\t\t\t} },`;\n\t\t\t})\n\t\t\t.join('\\n');\n\n\t\tconst content = `/**\n * Generated subscribers setup.\n *\n * Each subscriber is run against the topic it is bound to, through that\n * topic's own connection string:\n *\n * - **sns://** — pushed, not polled. The subscriber gets an HTTP route on this\n * server, and the route is subscribed to the topic with a filter policy of\n * the events it named, so SNS fans out to each subscriber. The route runs the\n * subscriber through the same adaptor a Lambda subscription does.\n * - **anything else** (pgboss://, rabbitmq://, sqs://) — polled. On pg-boss\n * each subscriber drains a queue of its own, \\`<topic>/<subscriber>\\`, so\n * every subscriber sees every event; replicas of one subscriber share it.\n *\n * GKM_SUBSCRIBERS=off (\\`gkm dev --no-subscribers\\`) runs none of them.\n * GKM_SUBSCRIBER_PUSH_URL is the address SNS pushes to; against the local\n * emulator it defaults to this server through host.docker.internal.\n */\nimport { connect } from 'node:net';\nimport type { EnvironmentParser } from '@geekmidas/envkit';\nimport type { Logger } from '@geekmidas/logger';\nimport { EventConnectionFactory, Subscriber } from '@geekmidas/events';\nimport type { EventConnection } from '@geekmidas/events';\nimport { ServiceDiscovery } from '@geekmidas/services';\n${imports}\n\nconst subscribers = [\n${entries}\n];\n\n// What was opened, and what could not be: a worker's health check reads both,\n// and its shutdown closes what was opened.\nconst connections = new Map<string, EventConnection>();\nconst failed: string[] = [];\n\n/** The route a subscriber's pushes arrive on. */\nexport const SUBSCRIBER_ROUTE = (id: string) => \\`/__gkm/subscribers/\\${id}\\`;\n\n/** Resolves once something is accepting connections on the port. */\nasync function listening(port: number): Promise<void> {\n for (let attempt = 0; attempt < 100; attempt++) {\n const open = await new Promise<boolean>((resolve) => {\n const socket = connect(port, '127.0.0.1');\n socket.once('connect', () => { socket.end(); resolve(true); });\n socket.once('error', () => resolve(false));\n });\n if (open) return;\n await new Promise((resolve) => setTimeout(resolve, 50));\n }\n}\n\n/**\n * Mount each pushed subscriber's route and start each polled one. Returns what\n * to run once the server is listening: subscribing a route makes SNS send its\n * confirmation at once, so the route has to be reachable first.\n */\nexport async function setupSubscribers(\n app: { post: (path: string, handler: (c: any) => unknown) => unknown },\n envParser: EnvironmentParser<any>,\n logger: Logger,\n): Promise<(port: number) => Promise<void>> {\n if (process.env.GKM_SUBSCRIBERS === 'off') {\n logger.info('Subscribers are off (--no-subscribers)');\n return async () => {};\n }\n\n const serviceDiscovery = ServiceDiscovery.getInstance(envParser);\n const afterListening: ((port: number) => Promise<void>)[] = [];\n\n for (const { id, subscriber, topic, connectionKey } of subscribers) {\n const events = subscriber.subscribedEvents || [];\n if (events.length === 0) {\n logger.warn({ subscriber: id }, 'Subscriber has no subscribed events, skipping');\n continue;\n }\n const connectionString = connectionKey ? process.env[connectionKey] : undefined;\n if (!connectionString) {\n logger.error(\n { subscriber: id, connectionKey },\n 'No connection string for the topic this subscriber is bound to',\n );\n failed.push(id);\n continue;\n }\n\n try {\n${\n\tpush\n\t\t? ` if (connectionString.startsWith('sns:')) {\n const { SNSConnection, snsUrl, subscribeHttpEndpoint } = await import('@geekmidas/events/sns');\n const { SnsPushSubscriberAdaptor } = await import('@geekmidas/constructs/subscribers');\n const address = snsUrl.parse(connectionString);\n const route = SUBSCRIBER_ROUTE(id);\n const adaptor = new SnsPushSubscriberAdaptor(envParser, subscriber, {\n topicArn: address.topicArn,\n // An emulator signs nothing. Decided by the address this server was\n // configured with, never by anything a request says about itself.\n verify: !address.endpoint,\n ...(address.endpoint ? { emulatorEndpoint: address.endpoint } : {}),\n });\n\n app.post(route, async (c: any) => {\n const body = JSON.parse(await c.req.text());\n const response = await adaptor.handle(body);\n return c.json(response.body, response.status);\n });\n\n afterListening.push(async (port) => {\n const base =\n process.env.GKM_SUBSCRIBER_PUSH_URL ??\n (address.endpoint ? \\`http://host.docker.internal:\\${port}\\` : undefined);\n if (!base) {\n logger.error(\n { subscriber: id },\n 'Set GKM_SUBSCRIBER_PUSH_URL to the public URL of this worker, which SNS pushes to',\n );\n return;\n }\n const connection = await SNSConnection.fromConnectionString(connectionString);\n await subscribeHttpEndpoint(connection, {\n endpoint: new URL(route, base).toString(),\n events,\n });\n logger.info({ subscriber: id, events }, 'Subscriber subscribed for push');\n });\n continue;\n }\n`\n\t\t: ` if (connectionString.startsWith('sns:')) {\n // Built for a broker other than SNS, so this entry registered no SNS\n // driver and has no push route to offer.\n logger.error(\n { subscriber: id, connectionKey },\n 'This build has no SNS driver: it was built for a target whose broker is not SNS',\n );\n failed.push(id);\n continue;\n }\n`\n}\n\n let connection = connections.get(connectionString);\n if (!connection) {\n connection = await EventConnectionFactory.fromConnectionString(connectionString);\n connections.set(connectionString, connection);\n }\n // Named, so a broker with one address for everything (pg-boss) gives\n // this subscriber a queue of its own: fan-out, not competition.\n const eventSubscriber = await Subscriber.fromConnection(connection, {\n topic,\n subscription: id,\n });\n const services = subscriber.services.length > 0\n ? await serviceDiscovery.register(subscriber.services)\n : {};\n // The subscriber's database — the worker's, or its own — as \\`db\\`. A\n // pushed subscriber gets it from the adaptor above instead.\n const db = subscriber.databaseService\n ? (await serviceDiscovery.register([subscriber.databaseService]))[\n subscriber.databaseService.serviceName\n ]\n : undefined;\n\n await eventSubscriber.subscribe(events, async (event) => {\n try {\n await subscriber.handler({\n events: [event],\n services: services as any,\n logger: subscriber.logger,\n db,\n } as any);\n } catch (error) {\n logger.error({ err: error, event, subscriber: id }, 'Failed to process event');\n // Rethrown so the transport keeps the event for a retry — as a queue\n // consumer's failure does, and as a pushed subscriber's 500 does.\n throw error;\n }\n });\n logger.info({ subscriber: id, events }, 'Subscriber started polling');\n } catch (error) {\n // The message too: a missing module's code alone does not say which.\n logger.error(\n { err: error, subscriber: id },\n 'Failed to set up subscriber',\n );\n failed.push(id);\n }\n }\n\n return async (port) => {\n if (afterListening.length === 0) return;\n await listening(port);\n for (const subscribe of afterListening) {\n await subscribe(port).catch((error) => {\n logger.error({ err: error }, 'Failed to subscribe for push');\n });\n }\n };\n}\n\n/** The subscribers that could not start, and the connections that did. */\nexport function subscribersStatus(): { failed: string[]; connections: EventConnection[] } {\n return { failed: [...failed], connections: [...connections.values()] };\n}\n\n/**\n * Stop polling, let the events in flight finish, and close each broker\n * connection — pg-boss's graceful stop does all three. The caller bounds it.\n */\nexport async function stopSubscribers(): Promise<void> {\n const open = [...connections.values()];\n connections.clear();\n await Promise.all(open.map((connection) => connection.close()));\n}\n`;\n\n\t\tawait writeFile(subscribersPath, content);\n\t\treturn subscribersPath;\n\t}\n}\n"],"mappings":"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"}