nucleus-core-ts 0.9.994 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (286) hide show
  1. package/README.md +177 -0
  2. package/bin/cli.ts +12 -7
  3. package/bin/lib/commands/add.ts +26 -13
  4. package/bin/lib/commands/dev.ts +57 -18
  5. package/bin/lib/commands/doctor.ts +22 -9
  6. package/bin/lib/commands/init.ts +31 -12
  7. package/bin/lib/commands/validate.ts +34 -10
  8. package/bin/lib/config-builder.ts +24 -6
  9. package/bin/lib/env.ts +10 -2
  10. package/bin/lib/presets.ts +6 -2
  11. package/bin/lib/schema.ts +18 -5
  12. package/bin/lib/templates.ts +26 -17
  13. package/bin/lib/ui.ts +5 -2
  14. package/dist/.build-ok +1 -1
  15. package/dist/bin/lib/commands/add.d.ts +12 -0
  16. package/dist/bin/lib/commands/dev.d.ts +12 -0
  17. package/dist/bin/lib/commands/doctor.d.ts +6 -0
  18. package/dist/bin/lib/config-builder.d.ts +5 -0
  19. package/dist/client.js +2 -2
  20. package/dist/dist/index.d.ts +49 -623
  21. package/dist/fe/components/AbstractAnimatedBackground/index.js +2 -1
  22. package/dist/index.d.ts +13 -3
  23. package/dist/index.js +44 -1473
  24. package/dist/nucleus.config.d.ts +3 -2
  25. package/dist/server.d.ts +27 -0
  26. package/dist/server.js +609 -0
  27. package/dist/src/Client/ApiCaller/deriveAuthEndpoints.d.ts +108 -0
  28. package/dist/src/Client/ApiCaller/generator.d.ts +32 -1
  29. package/dist/src/Client/ApiCaller/index.d.ts +1 -1
  30. package/dist/src/Client/ApiCaller/system-tables.d.ts +5 -5
  31. package/dist/src/Client/ApiCaller/types.d.ts +158 -115
  32. package/dist/src/Client/Proxy/httpProxy.js +1 -1
  33. package/dist/src/Managers/Redis/index.d.ts +18 -0
  34. package/dist/src/Nucleus/index.d.ts +71 -0
  35. package/dist/src/Nucleus/publicRoutes.d.ts +48 -0
  36. package/dist/src/Nucleus/routes/auth/admin/changeUserId.d.ts +2 -0
  37. package/dist/src/Nucleus/routes/auth/admin/createUser.d.ts +8 -0
  38. package/dist/src/Nucleus/routes/auth/admin/hardDeleteUser.d.ts +2 -0
  39. package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/admin/impersonate.d.ts +3 -30
  40. package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/admin/passwordAdmin.d.ts +1 -29
  41. package/dist/src/Nucleus/routes/auth/admin/requireGodmin.d.ts +33 -0
  42. package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/admin/roleAssignAuthz.d.ts +1 -3
  43. package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/admin/userState.d.ts +1 -29
  44. package/dist/src/Nucleus/routes/auth/apiKeys/index.d.ts +4 -0
  45. package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/captcha/index.d.ts +1 -29
  46. package/dist/src/Nucleus/routes/auth/check/index.d.ts +8 -0
  47. package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/cohort/handlers/activateUsers.d.ts +2 -3
  48. package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/cohort/handlers/bulkCreate.d.ts +2 -3
  49. package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/cohort/handlers/create.d.ts +2 -3
  50. package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/cohort/handlers/deactivateUsers.d.ts +2 -3
  51. package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/cohort/handlers/deleteUsers.d.ts +2 -3
  52. package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/cohort/handlers/detail.d.ts +2 -3
  53. package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/cohort/handlers/exportUsers.d.ts +2 -3
  54. package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/cohort/handlers/list.d.ts +2 -3
  55. package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/cohort/handlers/remove.d.ts +2 -3
  56. package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/cohort/handlers/update.d.ts +2 -3
  57. package/dist/src/Nucleus/routes/auth/cohort/index.d.ts +2 -0
  58. package/dist/src/Nucleus/routes/auth/cohort/types.d.ts +55 -0
  59. package/dist/src/Nucleus/routes/auth/cohort/utils.d.ts +43 -0
  60. package/dist/src/Nucleus/routes/auth/emailVerification/index.d.ts +9 -0
  61. package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/index.d.ts +6 -4
  62. package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/invite/index.d.ts +1 -29
  63. package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/login/index.d.ts +1 -29
  64. package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/logout/index.d.ts +1 -29
  65. package/dist/src/Nucleus/routes/auth/magicLink/index.d.ts +25 -0
  66. package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/me/index.d.ts +1 -29
  67. package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/oauth/index.d.ts +1 -29
  68. package/dist/src/Nucleus/routes/auth/passwordChange/index.d.ts +4 -0
  69. package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/passwordReset/index.d.ts +1 -29
  70. package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/passwordSet/index.d.ts +1 -29
  71. package/dist/src/Nucleus/routes/auth/refresh/index.d.ts +11 -0
  72. package/dist/src/Nucleus/routes/auth/refresh/sessionBinding.d.ts +29 -0
  73. package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/register/index.d.ts +3 -31
  74. package/dist/src/Nucleus/routes/auth/sessions/index.d.ts +4 -0
  75. package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/types.d.ts +22 -0
  76. package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/webauthn/index.d.ts +3 -31
  77. package/dist/src/{ElysiaPlugin → Nucleus}/routes/authorization/index.d.ts +1 -29
  78. package/dist/src/Nucleus/routes/backup/index.d.ts +7 -0
  79. package/dist/src/Nucleus/routes/backup/types.d.ts +24 -0
  80. package/dist/src/Nucleus/routes/chat/conversations.routes.d.ts +3 -0
  81. package/dist/src/Nucleus/routes/chat/index.d.ts +12 -0
  82. package/dist/src/Nucleus/routes/chat/messages.routes.d.ts +3 -0
  83. package/dist/src/Nucleus/routes/config/helpers.d.ts +152 -0
  84. package/dist/src/Nucleus/routes/config/index.d.ts +2 -0
  85. package/dist/src/{ElysiaPlugin → Nucleus}/routes/config/types.d.ts +12 -4
  86. package/dist/src/Nucleus/routes/domain/hostnames.d.ts +8 -0
  87. package/dist/src/Nucleus/routes/domain/index.d.ts +5 -0
  88. package/dist/src/Nucleus/routes/domain/registrations.d.ts +8 -0
  89. package/dist/src/Nucleus/routes/domain/resolve.d.ts +7 -0
  90. package/dist/src/{ElysiaPlugin → Nucleus}/routes/entity/index.d.ts +22 -2
  91. package/dist/src/{ElysiaPlugin → Nucleus}/routes/entity/utils.d.ts +1 -1
  92. package/dist/src/{ElysiaPlugin → Nucleus}/routes/integrations/index.d.ts +1 -29
  93. package/dist/src/{ElysiaPlugin → Nucleus}/routes/integrations/inspectRoutes.d.ts +2 -29
  94. package/dist/src/{ElysiaPlugin → Nucleus}/routes/monitoring/index.d.ts +1 -29
  95. package/dist/src/{ElysiaPlugin → Nucleus}/routes/monitoring/live.d.ts +1 -29
  96. package/dist/src/{ElysiaPlugin → Nucleus}/routes/monitoring/serverLogs.d.ts +1 -29
  97. package/dist/src/Nucleus/routes/monitoring/sse.d.ts +28 -0
  98. package/dist/src/{ElysiaPlugin → Nucleus}/routes/notification/index.d.ts +1 -29
  99. package/dist/src/Nucleus/routes/payment/index.d.ts +4 -0
  100. package/dist/src/Nucleus/routes/payment/marketplace/index.d.ts +5 -0
  101. package/dist/src/Nucleus/routes/payment/marketplace/payouts.d.ts +3 -0
  102. package/dist/src/Nucleus/routes/payment/marketplace/splits.d.ts +3 -0
  103. package/dist/src/{ElysiaPlugin → Nucleus}/routes/payment/webhook.d.ts +16 -2
  104. package/dist/src/Nucleus/routes/pubsub/handshakeAuth.d.ts +77 -0
  105. package/dist/src/{ElysiaPlugin → Nucleus}/routes/pubsub/index.d.ts +16 -29
  106. package/dist/src/Nucleus/routes/pubsub/refusalReason.d.ts +49 -0
  107. package/dist/src/{ElysiaPlugin → Nucleus}/routes/pubsub/types.d.ts +7 -1
  108. package/dist/src/Nucleus/routes/secrets/index.d.ts +11 -0
  109. package/dist/src/{ElysiaPlugin → Nucleus}/routes/shared/adminGuard.d.ts +14 -7
  110. package/dist/src/{ElysiaPlugin → Nucleus}/routes/shared/godminGuard.d.ts +1 -1
  111. package/dist/src/{ElysiaPlugin → Nucleus}/routes/storage/cdn.d.ts +16 -29
  112. package/dist/src/{ElysiaPlugin → Nucleus}/routes/storage/helpers.d.ts +15 -0
  113. package/dist/src/{ElysiaPlugin → Nucleus}/routes/storage/index.d.ts +1 -1
  114. package/dist/src/{ElysiaPlugin → Nucleus}/routes/storage/resumable/index.d.ts +2 -30
  115. package/dist/src/Nucleus/routes/tenant/check-subdomain.d.ts +6 -0
  116. package/dist/src/{ElysiaPlugin → Nucleus}/routes/tenant/index.d.ts +3 -2
  117. package/dist/src/Nucleus/routes/tenant/manage.d.ts +6 -0
  118. package/dist/src/Nucleus/routes/tenant/provision.d.ts +2 -0
  119. package/dist/src/Nucleus/routes/tenant/self-signup.d.ts +2 -0
  120. package/dist/src/{ElysiaPlugin → Nucleus}/routes/verification/index.d.ts +1 -29
  121. package/dist/src/Server/NucleusServer.d.ts +141 -0
  122. package/dist/src/Server/createNucleusServer.d.ts +75 -0
  123. package/dist/src/Server/defineRoute.d.ts +185 -0
  124. package/dist/src/Server/fileSchema.d.ts +35 -0
  125. package/dist/src/Server/formats.d.ts +4 -0
  126. package/dist/src/Server/inboundGuard.d.ts +26 -0
  127. package/dist/src/Server/internalHeaders.d.ts +26 -0
  128. package/dist/src/Server/openapi.d.ts +52 -0
  129. package/dist/src/Server/parseBody.d.ts +39 -0
  130. package/dist/src/Server/response.d.ts +72 -0
  131. package/dist/src/Server/routeCollector.d.ts +51 -0
  132. package/dist/src/Server/routeMount.d.ts +104 -0
  133. package/dist/src/Server/routeRecording.d.ts +107 -0
  134. package/dist/src/Server/router.d.ts +140 -0
  135. package/dist/src/Server/testApp.d.ts +82 -0
  136. package/dist/src/Server/validate.d.ts +76 -0
  137. package/dist/src/Server/websocket.d.ts +70 -0
  138. package/dist/src/Services/Authorization/activeRows.d.ts +33 -0
  139. package/dist/src/Services/Backup/BackupService.d.ts +22 -0
  140. package/dist/src/Services/Chat/ChatService.d.ts +3 -1
  141. package/dist/src/Services/Chat/attachmentAccess.d.ts +28 -0
  142. package/dist/src/Services/Chat/index.d.ts +1 -0
  143. package/dist/src/Services/Chat/types.d.ts +13 -0
  144. package/dist/src/Services/Logger/auditTaxonomy.d.ts +1 -1
  145. package/dist/src/Services/Logger/index.d.ts +1 -1
  146. package/dist/src/Services/Notification/index.d.ts +37 -2
  147. package/dist/src/Services/Notification/types.d.ts +17 -0
  148. package/dist/src/Services/SchemaTables/resolveEntities.d.ts +1 -1
  149. package/dist/src/Services/WebAuthn/types.d.ts +15 -0
  150. package/dist/src/types.d.ts +22 -2
  151. package/infra/templates/backend/package.json +0 -1
  152. package/infra/templates/backend/src/index.ts +25 -22
  153. package/package.json +9 -6
  154. package/public/components/docs/content/advanced.ts +37 -34
  155. package/public/components/docs/content/gettingStarted.ts +59 -11
  156. package/schemas/config.nucleus.json +54 -235
  157. package/schemas/nucleus.tables.schema.json +1 -3
  158. package/schemas/table.schema.json +22 -115
  159. package/scripts/build.ts +21 -4
  160. package/scripts/importCase.test.ts +174 -0
  161. package/scripts/packagedScripts.test.ts +4 -1
  162. package/scripts/scaffoldParity.test.ts +126 -0
  163. package/scripts/skillCitations.test.ts +106 -0
  164. package/src/Services/Logger/auditTaxonomy.ts +1 -1
  165. package/src/Services/SchemaTables/resolveEntities.ts +1 -1
  166. package/src/system.tables.json +5 -5
  167. package/dist/src/ElysiaPlugin/index.d.ts +0 -23
  168. package/dist/src/ElysiaPlugin/publicRoutes.d.ts +0 -8
  169. package/dist/src/ElysiaPlugin/routes/auth/admin/changeUserId.d.ts +0 -30
  170. package/dist/src/ElysiaPlugin/routes/auth/admin/createUser.d.ts +0 -36
  171. package/dist/src/ElysiaPlugin/routes/auth/admin/hardDeleteUser.d.ts +0 -30
  172. package/dist/src/ElysiaPlugin/routes/auth/admin/requireGodmin.d.ts +0 -17
  173. package/dist/src/ElysiaPlugin/routes/auth/apiKeys/index.d.ts +0 -3
  174. package/dist/src/ElysiaPlugin/routes/auth/check/index.d.ts +0 -62
  175. package/dist/src/ElysiaPlugin/routes/auth/cohort/index.d.ts +0 -30
  176. package/dist/src/ElysiaPlugin/routes/auth/cohort/types.d.ts +0 -26
  177. package/dist/src/ElysiaPlugin/routes/auth/cohort/utils.d.ts +0 -21
  178. package/dist/src/ElysiaPlugin/routes/auth/emailVerification/index.d.ts +0 -37
  179. package/dist/src/ElysiaPlugin/routes/auth/magicLink/index.d.ts +0 -53
  180. package/dist/src/ElysiaPlugin/routes/auth/passwordChange/index.d.ts +0 -32
  181. package/dist/src/ElysiaPlugin/routes/auth/refresh/index.d.ts +0 -38
  182. package/dist/src/ElysiaPlugin/routes/auth/sessions/index.d.ts +0 -32
  183. package/dist/src/ElysiaPlugin/routes/backup/index.d.ts +0 -35
  184. package/dist/src/ElysiaPlugin/routes/backup/types.d.ts +0 -14
  185. package/dist/src/ElysiaPlugin/routes/chat/conversations.routes.d.ts +0 -31
  186. package/dist/src/ElysiaPlugin/routes/chat/index.d.ts +0 -40
  187. package/dist/src/ElysiaPlugin/routes/chat/messages.routes.d.ts +0 -31
  188. package/dist/src/ElysiaPlugin/routes/config/helpers.d.ts +0 -60
  189. package/dist/src/ElysiaPlugin/routes/config/index.d.ts +0 -30
  190. package/dist/src/ElysiaPlugin/routes/domain/hostnames.d.ts +0 -36
  191. package/dist/src/ElysiaPlugin/routes/domain/index.d.ts +0 -4
  192. package/dist/src/ElysiaPlugin/routes/domain/registrations.d.ts +0 -36
  193. package/dist/src/ElysiaPlugin/routes/domain/resolve.d.ts +0 -35
  194. package/dist/src/ElysiaPlugin/routes/payment/index.d.ts +0 -32
  195. package/dist/src/ElysiaPlugin/routes/payment/marketplace/index.d.ts +0 -4
  196. package/dist/src/ElysiaPlugin/routes/payment/marketplace/payouts.d.ts +0 -31
  197. package/dist/src/ElysiaPlugin/routes/payment/marketplace/splits.d.ts +0 -31
  198. package/dist/src/ElysiaPlugin/routes/pubsub/refusalReason.d.ts +0 -18
  199. package/dist/src/ElysiaPlugin/routes/secrets/index.d.ts +0 -39
  200. package/dist/src/ElysiaPlugin/routes/tenant/check-subdomain.d.ts +0 -34
  201. package/dist/src/ElysiaPlugin/routes/tenant/manage.d.ts +0 -34
  202. package/dist/src/ElysiaPlugin/routes/tenant/provision.d.ts +0 -30
  203. package/dist/src/ElysiaPlugin/routes/tenant/self-signup.d.ts +0 -30
  204. package/dist/src/ElysiaPlugin/swagger.d.ts +0 -27
  205. /package/dist/src/{ElysiaPlugin → Nucleus}/authRouteClassifier.d.ts +0 -0
  206. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/accountState.d.ts +0 -0
  207. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/admin/createUserAuthz.d.ts +0 -0
  208. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/admin/lockoutState.d.ts +0 -0
  209. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/admin/resolveMutationSchema.d.ts +0 -0
  210. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/admin/rotationPlan.d.ts +0 -0
  211. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/apiKeys/types.d.ts +0 -0
  212. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/assignDefaultRole.d.ts +0 -0
  213. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/cohort/schemas.d.ts +0 -0
  214. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/emailExempt.d.ts +0 -0
  215. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/emailLinks/index.d.ts +0 -0
  216. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/fetchUserRolesAndClaims.d.ts +0 -0
  217. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/impersonationCookies.d.ts +0 -0
  218. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/login/deviceInfo.d.ts +0 -0
  219. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/login/identifier.d.ts +0 -0
  220. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/login/identifierQuery.d.ts +0 -0
  221. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/login/notifyDecision.d.ts +0 -0
  222. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/login/types.d.ts +0 -0
  223. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/login/utils.d.ts +0 -0
  224. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/logout/types.d.ts +0 -0
  225. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/logout/utils.d.ts +0 -0
  226. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/magicLink/types.d.ts +0 -0
  227. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/magicLink/utils.d.ts +0 -0
  228. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/oauth/applyProfileSync.d.ts +0 -0
  229. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/passwordChange/types.d.ts +0 -0
  230. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/passwordChange/utils.d.ts +0 -0
  231. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/passwordReset/outcome.d.ts +0 -0
  232. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/passwordReset/types.d.ts +0 -0
  233. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/passwordReset/utils.d.ts +0 -0
  234. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/passwordSet/types.d.ts +0 -0
  235. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/refresh/types.d.ts +0 -0
  236. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/refresh/utils.d.ts +0 -0
  237. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/register/types.d.ts +0 -0
  238. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/register/utils.d.ts +0 -0
  239. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/resolveAuthTables.d.ts +0 -0
  240. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/sessions/types.d.ts +0 -0
  241. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/sessions/utils.d.ts +0 -0
  242. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/webauthn/cookies.d.ts +0 -0
  243. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/webauthn/types.d.ts +0 -0
  244. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/chat/helpers.d.ts +0 -0
  245. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/chat/types.d.ts +0 -0
  246. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/domain/types.d.ts +0 -0
  247. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/entity/anonymousCreate.d.ts +0 -0
  248. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/entity/createScope.d.ts +0 -0
  249. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/entity/relationAuthz.d.ts +0 -0
  250. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/entity/types.d.ts +0 -0
  251. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/entity/userRoleWrite.d.ts +0 -0
  252. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/index.d.ts +0 -0
  253. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/integrations/inspect.d.ts +0 -0
  254. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/integrations/repository.d.ts +0 -0
  255. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/integrations/types.d.ts +0 -0
  256. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/payment/marketplace/types.d.ts +0 -0
  257. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/payment/types.d.ts +0 -0
  258. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/payment/validation.d.ts +0 -0
  259. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/pubsub/ack-manager.d.ts +0 -0
  260. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/pubsub/client-manager.d.ts +0 -0
  261. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/pubsub/frame.d.ts +0 -0
  262. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/secrets/types.d.ts +0 -0
  263. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/shared/basePathCollision.d.ts +0 -0
  264. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/shared/bodyCeiling.d.ts +0 -0
  265. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/shared/clientIp.d.ts +0 -0
  266. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/shared/csrf.d.ts +0 -0
  267. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/shared/redact.d.ts +0 -0
  268. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/shared/requestLimits.d.ts +0 -0
  269. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/shared/sensitiveColumns.d.ts +0 -0
  270. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/shared/sensitiveTables.d.ts +0 -0
  271. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/shared/tenantBinding.d.ts +0 -0
  272. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/shared/writeLock.d.ts +0 -0
  273. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/storage/boundedStream.d.ts +0 -0
  274. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/storage/cpuBudget.d.ts +0 -0
  275. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/storage/file-record.d.ts +0 -0
  276. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/storage/imageTransform.d.ts +0 -0
  277. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/storage/mediaPostProcess.d.ts +0 -0
  278. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/storage/pdfTransform.d.ts +0 -0
  279. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/storage/resumable/session.d.ts +0 -0
  280. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/storage/resumable/store.d.ts +0 -0
  281. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/storage/videoTransform.d.ts +0 -0
  282. /package/dist/src/{ElysiaPlugin → Nucleus}/routes/tenant/types.d.ts +0 -0
  283. /package/dist/src/{ElysiaPlugin → Nucleus}/runtime.d.ts +0 -0
  284. /package/dist/src/{ElysiaPlugin → Nucleus}/schemaReconcile.d.ts +0 -0
  285. /package/dist/src/{ElysiaPlugin → Nucleus}/utils.d.ts +0 -0
  286. /package/dist/src/{ElysiaPlugin → Nucleus}/validationError.d.ts +0 -0
package/README.md ADDED
@@ -0,0 +1,177 @@
1
+ # nucleus-core-ts
2
+
3
+ A TypeScript framework for multi-tenant APIs on Bun: authentication and
4
+ authorization, entity CRUD generated from a config file, storage, payments,
5
+ chat, pub/sub, monitoring — and a typed client generated from the same
6
+ declarations the server serves.
7
+
8
+ ```bash
9
+ bunx nucleus-core-ts init
10
+ ```
11
+
12
+ ## Where the documentation is
13
+
14
+ This file is an index, not a manual. The maintained documentation is:
15
+
16
+ - **`.claude/skills/`** — 21 skills, one per subsystem (`nucleus-authentication`,
17
+ `nucleus-entities`, `nucleus-payment`, …). These are the reference.
18
+ - **`public/`** — the docs site shipped in the package.
19
+ - **`src/types.ts`** — the configuration surface, and the last word when a
20
+ document and the code disagree.
21
+
22
+ Other `.md` files in the repo are working notes or historical audit records.
23
+ `docs/` in particular is an archive: it records what was true when it was
24
+ written and is not updated.
25
+
26
+ ## Entry points
27
+
28
+ | import | what |
29
+ |---|---|
30
+ | `nucleus-core-ts` | the server plugin and the services it lends a host app |
31
+ | `nucleus-core-ts/client` | the generated, typed API client |
32
+ | `nucleus-core-ts/fe` | React components (auth pages, admin panels) |
33
+ | `nucleus-core-ts/proxy` | server-side proxy client |
34
+
35
+ `client`, `fe` and `proxy` are independent of the HTTP layer below.
36
+
37
+ ## The HTTP layer is ours
38
+
39
+ The server half used to run on [Elysia](https://elysiajs.com). It does not any
40
+ more, and not partially: there is no adapter, no swagger plugin, no static
41
+ plugin, no peer dependency, no import anywhere in the source, and nothing in
42
+ `node_modules`. The reason was never performance — nucleus-core is a product of
43
+ its own, and a product's HTTP layer, type system and public API shape should not
44
+ belong to a third project we do not control.
45
+
46
+ **What that means, concretely:**
47
+
48
+ - **One entry point.** `createNucleusServer({ config, port })` serves the whole
49
+ product on `Bun.serve`. `NucleusElysiaPlugin` is gone; a consumer that used it
50
+ changes its boot line and its route modules and keeps everything else.
51
+
52
+ - **A host brings its own routes.** This is what took longest and it is what
53
+ every consumer was blocked on:
54
+
55
+ ```ts
56
+ await createNucleusServer({
57
+ config: { options, schema, relations },
58
+ port: 3000,
59
+ routes: [{ prefix: '/reports', routes: [defineRoute({ … })] }],
60
+ onRequest: (ctx) => Response | undefined, // BEFORE the inbound guard and auth
61
+ socket: defineWebSocketRoute({ … }),
62
+ onStop: [async () => { /* close your pool */ }],
63
+ })
64
+ ```
65
+
66
+ A group's routes sit behind nucleus's auth, exactly as they did when mounted on
67
+ an Elysia carrying the plugin — that is the inherited contract. Something that
68
+ must answer without authentication goes in `onRequest`, which runs first: a
69
+ public webhook that needs a verbatim body, an anonymous share link.
70
+
71
+ - **"No Elysia" is measured, not asserted.** `noFrameworkGraph.test.ts` checks it
72
+ two ways that miss different things: every entry point is bundled with packages
73
+ left external and must contain no `from "elysia"`, and every source file plus
74
+ the manifest is scanned, because a dynamic import never reaches a bundle.
75
+
76
+ - **The suites that compared two servers now state one.** With no second arm to
77
+ agree with, "the two answer alike" became "this is the answer" — and the claims
78
+ got stronger doing it, from 53 tests and 189 assertions to 74 and 422. Parity
79
+ passed whenever both arms were wrong in the same way; an absolute cannot.
80
+
81
+ - `src/Server/` holds the replacement — response semantics, `defineRoute`, the
82
+ router, the WebSocket upgrade — and every route in the repo declares against
83
+ it. See the `nucleus-routing` skill for how to declare one.
84
+
85
+ - The work, its measurements and its open questions live in
86
+ [`ELYSIA_MIGRATION.md`](./ELYSIA_MIGRATION.md). Read that before touching the
87
+ server's HTTP edge.
88
+
89
+ **What the migration found.** Twenty defects that existed in the shipped product
90
+ and that no test caught. Seventeen came from making the two paths answer the same
91
+ question; the last three came from removing Elysia from the TESTS, which moved
92
+ ~58 suites off Elysia's routing and validation and onto the router that actually
93
+ serves them. The ones worth naming:
94
+
95
+ | | |
96
+ |---|---|
97
+ | login sent ONE cookie | `access_token` and `refresh_token` never reached the browser |
98
+ | the 3-D Secure callback was dead | `Body already used` since the bridge landed |
99
+ | a resumable upload wrote nothing | `application/octet-stream` bodies arrived as `undefined`, so the chunk was appended as nothing and the request answered success |
100
+ | auth ran AFTER validation | an unauthenticated caller was handed the schema of a protected endpoint — property names, types, and the sample the validator builds |
101
+ | a thrown handler *or guard* leaked | Bun's `DevErrorPage` carried the raw error text where the product answers a JSON envelope whose whole job is withholding it |
102
+ | 422s and 404s shipped bare | no `nosniff`, no HSTS, no `x-frame-options`, no request id — on 151 routes |
103
+ | `stop()` leaked and killed | the DB pool, Redis and a live interval survived every restart, and in-flight requests were cut where Elysia drains them |
104
+ | monitoring was blind | the request log, the collector and the live store recorded nothing on the Bun path |
105
+ | form bodies were not parsed | `application/x-www-form-urlencoded` fell through every branch |
106
+ | schema `default:` never applied | a defaulted field left out became a 422; so did an extra property, an absent optional body, and a malformed one |
107
+ | nested form keys stayed flat | `a.b=1` arrived as a key called `a.b`, so `body.a.b` found nothing |
108
+ | a WebSocket upgrade skipped the guards | no rate limit, no tenant resolution, no header strip |
109
+
110
+ Two of those were the OTHER way round, with the SHIPPED path broken and the
111
+ replacement correct: multipart uploads never worked on Elysia at all, and a
112
+ failed insert came back from it as `500 text/plain` carrying the schema name,
113
+ the table name and every column, because Elysia discarded the `Response` the
114
+ product's error responder returned. Both are gone with the path that had them.
115
+
116
+ **And three found by taking Elysia out of the tests** — all of them live on the
117
+ only path that remains, all now fixed:
118
+
119
+ | | |
120
+ |---|---|
121
+ | the 422 handed the caller their own submitted values | `POST /login` with a bad email answered `"password": "hunter2-the-real-one"` — into the response, and into any log that records one. Every value in a refusal is its TYPE now. The parity walk could not see it: it sends an empty body, so `found` was always `{}` |
122
+ | a malformed body on a route with NO body schema | reached the handler as `undefined` and 500'd where the handler dereferenced it. 69 of 134 write declarations carry no body schema and 36 read the body anyway — a client error was becoming a server error. Now a 400 |
123
+ | a JSON body claimed to be `text/plain` | seventeen returned-`Error` sites, including the whole authentication middleware. Copied from Elysia so the two arms agreed byte for byte; with Elysia gone the only argument for it went too |
124
+
125
+ `ELYSIA_MIGRATION.md` §11 has the rest, including the four audit claims that did
126
+ NOT reproduce when measured, and one regression this work introduced and then
127
+ removed.
128
+
129
+ Breaking changes will be announced here first. There is no 1.0 date; the 0.9.x
130
+ line continues.
131
+
132
+ ## Gates
133
+
134
+ Every push runs, and all of these block:
135
+
136
+ ```bash
137
+ bun test src scripts fe # unit tests
138
+ bunx tsc --noEmit -p tsconfig.check.json # the core library must type-check clean
139
+ ```
140
+
141
+ **The lint gate is currently not one of them.** CI runs `bunx biome check .`,
142
+ and `biome` on npm is not Biome — it is an unrelated 0.3.3 package for managing
143
+ environment variables. `@biomejs/biome` is the real one and is not declared as a
144
+ dependency, so the step resolves the wrong package, prints no findings and exits
145
+ 0. It passes on every commit and checks nothing. Locally it works only for
146
+ whoever has Biome installed globally.
147
+
148
+ Repairing it means declaring `@biomejs/biome`, pointing CI at it, and then
149
+ making the repo clean under the real tool — which is a repo-wide change, so it
150
+ is written down here rather than done quietly. See `ELYSIA_MIGRATION.md` §7.
151
+
152
+ **What that costs, measured** (`bunx @biomejs/biome check .`, 1144 files):
153
+
154
+ | | count |
155
+ |---|---|
156
+ | errors today | 234 |
157
+ | fixed by `--write` | 90, touching 83 files |
158
+ | left needing a judgement call | 144 errors + 29 warnings + 9 infos |
159
+
160
+ The biggest groups are `assist/source/organizeImports` (33) and
161
+ `lint/suspicious/noThenProperty` (12 — mostly chainable drizzle stubs in tests,
162
+ where a `then` property is the point). So the repair is two commits, not one: a
163
+ mechanical `--write` pass, then a smaller pass that decides what to keep.
164
+
165
+ CI additionally runs every `*.integration.test.ts` against a real Postgres and
166
+ Redis (`RUN_INTEGRATION=1`); they self-skip otherwise, because the races they
167
+ catch — over-commit, non-atomic payout
168
+ bookkeeping — cannot be reproduced against a fake backend.
169
+
170
+ `tsconfig.check.json` covers `src` only — `fe/` imports from the package root,
171
+ which only exists after a build, so it cannot be type-checked on a fresh
172
+ checkout. The build emits its declarations best-effort.
173
+
174
+ A local green is not a CI green. Path separators, case sensitivity, musl memory
175
+ behaviour and `/proc` fixtures all differ between a developer machine and the
176
+ Alpine image that runs in production; `ELYSIA_MIGRATION.md` §6 has the matrix
177
+ and the container command that reproduces it.
package/bin/cli.ts CHANGED
@@ -14,24 +14,26 @@
14
14
  import { spawn } from 'node:child_process'
15
15
  import { dirname, join } from 'node:path'
16
16
  import { fileURLToPath } from 'node:url'
17
- import { banner, c, log } from './lib/ui'
17
+ import { addCommand } from './lib/commands/add'
18
+ import { devCommand } from './lib/commands/dev'
18
19
  import { doctorCommand } from './lib/commands/doctor'
19
- import { validateCommand } from './lib/commands/validate'
20
20
  import { initCommand } from './lib/commands/init'
21
- import { devCommand } from './lib/commands/dev'
22
- import { addCommand } from './lib/commands/add'
21
+ import { validateCommand } from './lib/commands/validate'
22
+ import { banner, c, log } from './lib/ui'
23
23
 
24
24
  const here = dirname(fileURLToPath(import.meta.url))
25
25
  const rootDir = join(here, '..')
26
26
 
27
27
  const argv = process.argv.slice(2)
28
28
  const firstArg = argv[0] ?? ''
29
- const looksLikeFile = firstArg.endsWith('.json') || firstArg.startsWith('./') || firstArg.startsWith('/')
29
+ const looksLikeFile =
30
+ firstArg.endsWith('.json') || firstArg.startsWith('./') || firstArg.startsWith('/')
30
31
  const command = looksLikeFile ? 'generate' : firstArg
31
32
 
32
33
  async function pkgVersion(): Promise<string> {
33
34
  try {
34
- return (JSON.parse(await Bun.file(join(rootDir, 'package.json')).text()) as { version: string }).version
35
+ return (JSON.parse(await Bun.file(join(rootDir, 'package.json')).text()) as { version: string })
36
+ .version
35
37
  } catch {
36
38
  return '?'
37
39
  }
@@ -63,7 +65,10 @@ ${c.dim('init = preset → runnable backend · scaffold (= new) = full project
63
65
 
64
66
  function runScript(scriptRelPath: string, args: string[]): Promise<never> {
65
67
  return new Promise<never>(() => {
66
- const proc = spawn('bun', ['run', join(rootDir, scriptRelPath), ...args], { cwd: process.cwd(), stdio: 'inherit' })
68
+ const proc = spawn('bun', ['run', join(rootDir, scriptRelPath), ...args], {
69
+ cwd: process.cwd(),
70
+ stdio: 'inherit',
71
+ })
67
72
  proc.on('close', (code) => process.exit(code ?? 1))
68
73
  })
69
74
  }
@@ -10,13 +10,13 @@
10
10
  * After editing, it re-validates and (for schema-affecting changes) regenerates
11
11
  * the Drizzle schema so `nucleus dev` picks the change up.
12
12
  */
13
- import { dirname, join, relative } from 'node:path'
13
+
14
14
  import { spawnSync } from 'node:child_process'
15
- import { c, fail, info, log, ok, step, warn } from '../ui'
16
- import { prompts } from '../ui'
17
- import { buildEntity, slug } from '../config-builder'
18
- import { findConfigPath, crossFieldChecks } from './validate'
15
+ import { dirname, join, relative } from 'node:path'
16
+ import { buildEntity } from '../config-builder'
19
17
  import { loadConfigSchema, validateAgainstSchema } from '../schema'
18
+ import { c, fail, info, log, ok, prompts, step, warn } from '../ui'
19
+ import { crossFieldChecks, findConfigPath } from './validate'
20
20
 
21
21
  type Cfg = Record<string, unknown>
22
22
 
@@ -77,8 +77,13 @@ const FEATURES: Record<string, { summary: string; apply: (cfg: Cfg) => void | Pr
77
77
  const provider = await p.choose(' Provider', ['iyzico', 'stripe'], 0)
78
78
  const block: Cfg = { enabled: true, provider }
79
79
  if (provider === 'iyzico')
80
- block.iyzico = { apiKey: 'IYZICO_API_KEY', secretKey: 'IYZICO_SECRET_KEY', baseUrl: 'IYZICO_BASE_URL' }
81
- else block.stripe = { secretKey: 'STRIPE_SECRET_KEY', webhookSecret: 'STRIPE_WEBHOOK_SECRET' }
80
+ block.iyzico = {
81
+ apiKey: 'IYZICO_API_KEY',
82
+ secretKey: 'IYZICO_SECRET_KEY',
83
+ baseUrl: 'IYZICO_BASE_URL',
84
+ }
85
+ else
86
+ block.stripe = { secretKey: 'STRIPE_SECRET_KEY', webhookSecret: 'STRIPE_WEBHOOK_SECRET' }
82
87
  cfg.payment = block
83
88
  } finally {
84
89
  p.close()
@@ -113,7 +118,7 @@ async function persist(path: string, cfg: Cfg, regen: boolean): Promise<number>
113
118
  fail('Change would make the config invalid — nothing written')
114
119
  return 1
115
120
  }
116
- await Bun.write(path, JSON.stringify(cfg, null, 2) + '\n')
121
+ await Bun.write(path, `${JSON.stringify(cfg, null, 2)}\n`)
117
122
  ok(`Updated ${c.cyan(relative(process.cwd(), path))}`)
118
123
 
119
124
  if (regen) {
@@ -168,7 +173,8 @@ async function addFeature(name?: string): Promise<number> {
168
173
  if (!key || !FEATURES[key]) {
169
174
  fail(`Unknown feature "${name ?? ''}"`)
170
175
  log(` Available features:`)
171
- for (const [k, v] of Object.entries(FEATURES)) log(` ${c.cyan(k.padEnd(14))} ${c.dim(v.summary)}`)
176
+ for (const [k, v] of Object.entries(FEATURES))
177
+ log(` ${c.cyan(k.padEnd(14))} ${c.dim(v.summary)}`)
172
178
  return 1
173
179
  }
174
180
  const loaded = await readConfig()
@@ -188,7 +194,9 @@ async function addFeature(name?: string): Promise<number> {
188
194
  async function addOauth(provider?: string): Promise<number> {
189
195
  const prov = (provider || '').toLowerCase()
190
196
  if (!prov || !OAUTH_PROVIDERS.includes(prov)) {
191
- fail(`Unknown OAuth provider "${provider ?? ''}" — expected one of ${OAUTH_PROVIDERS.join(', ')}`)
197
+ fail(
198
+ `Unknown OAuth provider "${provider ?? ''}" — expected one of ${OAUTH_PROVIDERS.join(', ')}`
199
+ )
192
200
  return 1
193
201
  }
194
202
  const loaded = await readConfig()
@@ -210,7 +218,8 @@ async function addOauth(provider?: string): Promise<number> {
210
218
  cfg.authentication = auth
211
219
  step(`Adding OAuth provider: ${c.cyan(prov)}`)
212
220
  const rc = await persist(path, cfg, false)
213
- if (rc === 0) info(`Set ${up}_CLIENT_ID and ${up}_CLIENT_SECRET in .env, plus the provider redirect URI.`)
221
+ if (rc === 0)
222
+ info(`Set ${up}_CLIENT_ID and ${up}_CLIENT_SECRET in .env, plus the provider redirect URI.`)
214
223
  return rc
215
224
  }
216
225
 
@@ -227,9 +236,13 @@ export async function addCommand(args: string[]): Promise<number> {
227
236
  default:
228
237
  fail(`Unknown: nucleus add ${thing ?? ''}`)
229
238
  log(` Usage:`)
230
- log(` ${c.cyan('nucleus add entity')} ${c.dim('add one or more entities (interactive)')}`)
239
+ log(
240
+ ` ${c.cyan('nucleus add entity')} ${c.dim('add one or more entities (interactive)')}`
241
+ )
231
242
  log(` ${c.cyan('nucleus add feature <name>')} ${c.dim('enable a feature block')}`)
232
- log(` ${c.cyan('nucleus add oauth <provider>')} ${c.dim(`add OAuth (${OAUTH_PROVIDERS.join('|')})`)}`)
243
+ log(
244
+ ` ${c.cyan('nucleus add oauth <provider>')} ${c.dim(`add OAuth (${OAUTH_PROVIDERS.join('|')})`)}`
245
+ )
233
246
  return 1
234
247
  }
235
248
  }
@@ -10,15 +10,22 @@
10
10
  * If your Postgres/Redis are already running (the common local case) `dev up`
11
11
  * detects them and starts nothing — Docker is only a fallback.
12
12
  */
13
- import { existsSync } from 'node:fs'
13
+
14
14
  import { spawnSync } from 'node:child_process'
15
- import { c, fail, info, log, ok, step, warn } from '../ui'
15
+ import { existsSync } from 'node:fs'
16
16
  import { pgConn, readEnv, redisConn, tcpReachable } from '../env'
17
+ import { c, fail, info, log, ok, step, warn } from '../ui'
17
18
 
18
19
  const PG_CONTAINER = 'nucleus-dev-postgres'
19
20
  const REDIS_CONTAINER = 'nucleus-dev-redis'
20
21
 
21
- type Svc = { name: string; container: string; host: string; port: number; run: (port: number) => string[] }
22
+ type Svc = {
23
+ name: string
24
+ container: string
25
+ host: string
26
+ port: number
27
+ run: (port: number) => string[]
28
+ }
22
29
 
23
30
  function services(env: Record<string, string>): Svc[] {
24
31
  const pg = pgConn(env)
@@ -30,11 +37,18 @@ function services(env: Record<string, string>): Svc[] {
30
37
  host: pg.host,
31
38
  port: pg.port,
32
39
  run: (port) => [
33
- 'run', '-d', '--name', PG_CONTAINER,
34
- '-e', 'POSTGRES_PASSWORD=postgres',
35
- '-e', 'POSTGRES_USER=postgres',
36
- '-e', 'POSTGRES_DB=nucleus',
37
- '-p', `${port}:5432`,
40
+ 'run',
41
+ '-d',
42
+ '--name',
43
+ PG_CONTAINER,
44
+ '-e',
45
+ 'POSTGRES_PASSWORD=postgres',
46
+ '-e',
47
+ 'POSTGRES_USER=postgres',
48
+ '-e',
49
+ 'POSTGRES_DB=nucleus',
50
+ '-p',
51
+ `${port}:5432`,
38
52
  'postgres:16-alpine',
39
53
  ],
40
54
  },
@@ -43,7 +57,15 @@ function services(env: Record<string, string>): Svc[] {
43
57
  container: REDIS_CONTAINER,
44
58
  host: r.host,
45
59
  port: r.port,
46
- run: (port) => ['run', '-d', '--name', REDIS_CONTAINER, '-p', `${port}:6379`, 'redis:7-alpine'],
60
+ run: (port) => [
61
+ 'run',
62
+ '-d',
63
+ '--name',
64
+ REDIS_CONTAINER,
65
+ '-p',
66
+ `${port}:6379`,
67
+ 'redis:7-alpine',
68
+ ],
47
69
  },
48
70
  ]
49
71
  }
@@ -68,7 +90,9 @@ async function devUp(): Promise<number> {
68
90
  if (await tcpReachable(s.host, s.port)) {
69
91
  ok(`${s.name} already reachable at ${c.dim(`${s.host}:${s.port}`)}`)
70
92
  } else if (!isLocal(s.host)) {
71
- fail(`${s.name} not reachable at ${s.host}:${s.port} (remote host — can't start a container for it)`)
93
+ fail(
94
+ `${s.name} not reachable at ${s.host}:${s.port} (remote host — can't start a container for it)`
95
+ )
72
96
  return 1
73
97
  } else {
74
98
  need.push(s)
@@ -82,13 +106,17 @@ async function devUp(): Promise<number> {
82
106
  }
83
107
 
84
108
  if (!Bun.which('docker')) {
85
- fail(`docker not found — install Docker, or start ${need.map((s) => s.name).join(' + ')} yourself`)
109
+ fail(
110
+ `docker not found — install Docker, or start ${need.map((s) => s.name).join(' + ')} yourself`
111
+ )
86
112
  return 1
87
113
  }
88
114
 
89
115
  for (const s of need) {
90
116
  // Reuse a stopped container of the same name if present, else create one.
91
- const existing = spawnSync('docker', ['ps', '-aq', '-f', `name=^${s.container}$`], { encoding: 'utf-8' })
117
+ const existing = spawnSync('docker', ['ps', '-aq', '-f', `name=^${s.container}$`], {
118
+ encoding: 'utf-8',
119
+ })
92
120
  const id = (existing.stdout || '').trim()
93
121
  if (id) {
94
122
  info(`Starting existing container ${c.dim(s.container)}`)
@@ -105,14 +133,18 @@ async function devUp(): Promise<number> {
105
133
 
106
134
  step('Waiting for datastores to accept connections')
107
135
  for (const s of need) {
108
- if (await waitReachable(s.host, s.port)) ok(`${s.name} ready at ${c.dim(`${s.host}:${s.port}`)}`)
136
+ if (await waitReachable(s.host, s.port))
137
+ ok(`${s.name} ready at ${c.dim(`${s.host}:${s.port}`)}`)
109
138
  else {
110
139
  fail(`${s.name} did not become reachable — check \`docker logs ${s.container}\``)
111
140
  return 1
112
141
  }
113
142
  }
114
143
  log()
115
- ok(c.green('Datastores up.') + c.dim(' Run `nucleus dev` to start the app, `nucleus dev down` to stop them.'))
144
+ ok(
145
+ c.green('Datastores up.') +
146
+ c.dim(' Run `nucleus dev` to start the app, `nucleus dev down` to stop them.')
147
+ )
116
148
  return 0
117
149
  }
118
150
 
@@ -124,7 +156,9 @@ function devDown(): number {
124
156
  }
125
157
  let stopped = 0
126
158
  for (const container of [PG_CONTAINER, REDIS_CONTAINER]) {
127
- const found = spawnSync('docker', ['ps', '-aq', '-f', `name=^${container}$`], { encoding: 'utf-8' })
159
+ const found = spawnSync('docker', ['ps', '-aq', '-f', `name=^${container}$`], {
160
+ encoding: 'utf-8',
161
+ })
128
162
  if ((found.stdout || '').trim()) {
129
163
  spawnSync('docker', ['rm', '-f', container], { stdio: 'ignore' })
130
164
  ok(`Removed ${c.dim(container)}`)
@@ -144,14 +178,19 @@ async function devRun(): Promise<number> {
144
178
  const env = readEnv()
145
179
  for (const s of services(env)) {
146
180
  if (!(await tcpReachable(s.host, s.port)))
147
- warn(`${s.name} not reachable at ${s.host}:${s.port} — run \`nucleus dev up\` first (the app may fail to boot)`)
181
+ warn(
182
+ `${s.name} not reachable at ${s.host}:${s.port} — run \`nucleus dev up\` first (the app may fail to boot)`
183
+ )
148
184
  }
149
185
 
150
186
  // Prefer the project's own dev script; fall back to running the entrypoint.
151
187
  let cmd = ['bun', 'run', 'dev']
152
188
  try {
153
- const pkg = JSON.parse(await Bun.file('package.json').text()) as { scripts?: Record<string, string> }
154
- if (!pkg.scripts?.dev) cmd = ['bun', '--watch', existsSync('src/index.ts') ? 'src/index.ts' : 'index.ts']
189
+ const pkg = JSON.parse(await Bun.file('package.json').text()) as {
190
+ scripts?: Record<string, string>
191
+ }
192
+ if (!pkg.scripts?.dev)
193
+ cmd = ['bun', '--watch', existsSync('src/index.ts') ? 'src/index.ts' : 'index.ts']
155
194
  } catch {
156
195
  /* use default */
157
196
  }
@@ -4,10 +4,11 @@
4
4
  * config. Prints a green/red checklist with fix hints. Exit 1 if a REQUIRED
5
5
  * check fails.
6
6
  */
7
- import { c, fail, info, log, ok, step, warn } from '../ui'
7
+
8
8
  import { pgConn, readEnv, redisConn, tcpReachable } from '../env'
9
- import { collectEnvRefs, crossFieldChecks, findConfigPath } from './validate'
10
9
  import { loadConfigSchema, validateAgainstSchema } from '../schema'
10
+ import { c, fail, info, log, ok, step, warn } from '../ui'
11
+ import { collectEnvRefs, crossFieldChecks, findConfigPath } from './validate'
11
12
 
12
13
  export async function doctorCommand(): Promise<number> {
13
14
  step('Nucleus doctor — local environment check')
@@ -24,9 +25,12 @@ export async function doctorCommand(): Promise<number> {
24
25
 
25
26
  // Postgres
26
27
  const pg = pgConn(env)
27
- if (await tcpReachable(pg.host, pg.port)) ok(`Postgres reachable at ${c.dim(`${pg.host}:${pg.port}`)}`)
28
+ if (await tcpReachable(pg.host, pg.port))
29
+ ok(`Postgres reachable at ${c.dim(`${pg.host}:${pg.port}`)}`)
28
30
  else {
29
- fail(`Postgres NOT reachable at ${pg.host}:${pg.port} — start it (\`nucleus dev up\`) or set DATABASE_URL`)
31
+ fail(
32
+ `Postgres NOT reachable at ${pg.host}:${pg.port} — start it (\`nucleus dev up\`) or set DATABASE_URL`
33
+ )
30
34
  requiredFailures++
31
35
  }
32
36
 
@@ -34,13 +38,19 @@ export async function doctorCommand(): Promise<number> {
34
38
  const r = redisConn(env)
35
39
  if (await tcpReachable(r.host, r.port)) ok(`Redis reachable at ${c.dim(`${r.host}:${r.port}`)}`)
36
40
  else {
37
- fail(`Redis NOT reachable at ${r.host}:${r.port} — start it (\`nucleus dev up\`) or set REDIS_URL / REDIS_HOST`)
41
+ fail(
42
+ `Redis NOT reachable at ${r.host}:${r.port} — start it (\`nucleus dev up\`) or set REDIS_URL / REDIS_HOST`
43
+ )
38
44
  requiredFailures++
39
45
  }
40
46
 
41
47
  // Optional tooling
42
- Bun.which('docker') ? ok(`docker ${c.dim('(for nucleus dev up)')}`) : warn('docker not found — needed only for `nucleus dev up`')
43
- Bun.which('dapr') ? ok(`dapr ${c.dim('(optional pub/sub sidecar)')}`) : info('dapr not found (optional — only for Dapr pub/sub)')
48
+ Bun.which('docker')
49
+ ? ok(`docker ${c.dim('(for nucleus dev up)')}`)
50
+ : warn('docker not found — needed only for `nucleus dev up`')
51
+ Bun.which('dapr')
52
+ ? ok(`dapr ${c.dim('(optional pub/sub sidecar)')}`)
53
+ : info('dapr not found (optional — only for Dapr pub/sub)')
44
54
  Bun.which('createdb')
45
55
  ? ok(`createdb ${c.dim('(for nucleus db create)')}`)
46
56
  : info('createdb not found (optional — psql client tools)')
@@ -59,7 +69,8 @@ export async function doctorCommand(): Promise<number> {
59
69
  requiredFailures++
60
70
  }
61
71
  const missing = [...collectEnvRefs(cfg)].filter((e) => !env[e])
62
- if (missing.length) warn(`${missing.length} referenced env var(s) unset: ${c.dim(missing.join(', '))}`)
72
+ if (missing.length)
73
+ warn(`${missing.length} referenced env var(s) unset: ${c.dim(missing.join(', '))}`)
63
74
  } catch {
64
75
  fail(`Config ${cfgPath} is not valid JSON`)
65
76
  requiredFailures++
@@ -73,6 +84,8 @@ export async function doctorCommand(): Promise<number> {
73
84
  ok(c.green(c.bold('Environment ready ✔')))
74
85
  return 0
75
86
  }
76
- fail(c.red(c.bold(`${requiredFailures} blocking issue(s) — fix the above before running the app`)))
87
+ fail(
88
+ c.red(c.bold(`${requiredFailures} blocking issue(s) — fix the above before running the app`))
89
+ )
77
90
  return 1
78
91
  }
@@ -15,14 +15,21 @@
15
15
  import { spawnSync } from 'node:child_process'
16
16
  import { existsSync, readdirSync } from 'node:fs'
17
17
  import { resolve } from 'node:path'
18
- import { banner, c, fail, info, log, ok, prompts, step, warn } from '../ui'
19
- import { buildPreset, PRESET_NAMES, type PresetName } from '../presets'
20
18
  import { buildConfigInteractive } from '../config-builder'
19
+ import { buildPreset, PRESET_NAMES, type PresetName } from '../presets'
20
+ import { loadConfigSchema, validateAgainstSchema } from '../schema'
21
21
  import { writeBackend } from '../templates'
22
+ import { banner, c, fail, info, log, ok, prompts, step, warn } from '../ui'
22
23
  import { crossFieldChecks } from './validate'
23
- import { loadConfigSchema, validateAgainstSchema } from '../schema'
24
24
 
25
- type Flags = { preset?: string; config?: string; appId?: string; output?: string; yes: boolean; install: boolean }
25
+ type Flags = {
26
+ preset?: string
27
+ config?: string
28
+ appId?: string
29
+ output?: string
30
+ yes: boolean
31
+ install: boolean
32
+ }
26
33
 
27
34
  function parseFlags(args: string[]): Flags {
28
35
  const f: Flags = { yes: false, install: true }
@@ -38,7 +45,12 @@ function parseFlags(args: string[]): Flags {
38
45
  return f
39
46
  }
40
47
 
41
- const slug = (s: string) => s.toLowerCase().replace(/[^a-z0-9_-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '')
48
+ const slug = (s: string) =>
49
+ s
50
+ .toLowerCase()
51
+ .replace(/[^a-z0-9_-]/g, '-')
52
+ .replace(/-+/g, '-')
53
+ .replace(/^-|-$/g, '')
42
54
 
43
55
  export async function initCommand(args: string[]): Promise<number> {
44
56
  const f = parseFlags(args)
@@ -56,7 +68,8 @@ export async function initCommand(args: string[]): Promise<number> {
56
68
  appId = slug((config.appId as string) || f.appId || 'my-app')
57
69
  } else if (f.preset || f.yes) {
58
70
  const name = (f.preset || 'saas') as PresetName
59
- if (!PRESET_NAMES.includes(name)) return fail(`Unknown preset "${name}" (choose ${PRESET_NAMES.join(', ')})`), 1
71
+ if (!PRESET_NAMES.includes(name))
72
+ return fail(`Unknown preset "${name}" (choose ${PRESET_NAMES.join(', ')})`), 1
60
73
  appId = slug(f.appId || 'my-app')
61
74
  config = buildPreset(name, appId)
62
75
  step(`Seeded a "${name}" config for ${c.cyan(appId)}`)
@@ -98,7 +111,8 @@ export async function initCommand(args: string[]): Promise<number> {
98
111
  step(`Generating backend at ${c.cyan(outDir)}`)
99
112
  const missing = writeBackend(outDir, appId, config)
100
113
  ok('package.json, src/index.ts, src/config.json, tsconfig, .env written')
101
- if (missing.length) warn(`.env has ${missing.length} secret(s) to fill: ${c.dim(missing.join(', '))}`)
114
+ if (missing.length)
115
+ warn(`.env has ${missing.length} secret(s) to fill: ${c.dim(missing.join(', '))}`)
102
116
 
103
117
  if (f.install) {
104
118
  step('Installing dependencies')
@@ -108,12 +122,17 @@ export async function initCommand(args: string[]): Promise<number> {
108
122
  } else {
109
123
  ok('dependencies installed')
110
124
  step('Generating Drizzle schema')
111
- const gen = spawnSync('bunx', ['nucleus-core-ts', 'generate', 'src/config.json', 'src/drizzle'], {
112
- cwd: outDir,
113
- stdio: 'inherit',
114
- })
125
+ const gen = spawnSync(
126
+ 'bunx',
127
+ ['nucleus-core-ts', 'generate', 'src/config.json', 'src/drizzle'],
128
+ {
129
+ cwd: outDir,
130
+ stdio: 'inherit',
131
+ }
132
+ )
115
133
  if (gen.status === 0) ok('schema generated')
116
- else warn('schema generation skipped/failed — run `bun run generate` after setting DATABASE_URL')
134
+ else
135
+ warn('schema generation skipped/failed — run `bun run generate` after setting DATABASE_URL')
117
136
  }
118
137
  }
119
138