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.
- package/README.md +177 -0
- package/bin/cli.ts +12 -7
- package/bin/lib/commands/add.ts +26 -13
- package/bin/lib/commands/dev.ts +57 -18
- package/bin/lib/commands/doctor.ts +22 -9
- package/bin/lib/commands/init.ts +31 -12
- package/bin/lib/commands/validate.ts +34 -10
- package/bin/lib/config-builder.ts +24 -6
- package/bin/lib/env.ts +10 -2
- package/bin/lib/presets.ts +6 -2
- package/bin/lib/schema.ts +18 -5
- package/bin/lib/templates.ts +26 -17
- package/bin/lib/ui.ts +5 -2
- package/dist/.build-ok +1 -1
- package/dist/bin/lib/commands/add.d.ts +12 -0
- package/dist/bin/lib/commands/dev.d.ts +12 -0
- package/dist/bin/lib/commands/doctor.d.ts +6 -0
- package/dist/bin/lib/config-builder.d.ts +5 -0
- package/dist/client.js +2 -2
- package/dist/dist/index.d.ts +49 -623
- package/dist/fe/components/AbstractAnimatedBackground/index.js +2 -1
- package/dist/index.d.ts +13 -3
- package/dist/index.js +44 -1473
- package/dist/nucleus.config.d.ts +3 -2
- package/dist/server.d.ts +27 -0
- package/dist/server.js +609 -0
- package/dist/src/Client/ApiCaller/deriveAuthEndpoints.d.ts +108 -0
- package/dist/src/Client/ApiCaller/generator.d.ts +32 -1
- package/dist/src/Client/ApiCaller/index.d.ts +1 -1
- package/dist/src/Client/ApiCaller/system-tables.d.ts +5 -5
- package/dist/src/Client/ApiCaller/types.d.ts +158 -115
- package/dist/src/Client/Proxy/httpProxy.js +1 -1
- package/dist/src/Managers/Redis/index.d.ts +18 -0
- package/dist/src/Nucleus/index.d.ts +71 -0
- package/dist/src/Nucleus/publicRoutes.d.ts +48 -0
- package/dist/src/Nucleus/routes/auth/admin/changeUserId.d.ts +2 -0
- package/dist/src/Nucleus/routes/auth/admin/createUser.d.ts +8 -0
- package/dist/src/Nucleus/routes/auth/admin/hardDeleteUser.d.ts +2 -0
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/admin/impersonate.d.ts +3 -30
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/admin/passwordAdmin.d.ts +1 -29
- package/dist/src/Nucleus/routes/auth/admin/requireGodmin.d.ts +33 -0
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/admin/roleAssignAuthz.d.ts +1 -3
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/admin/userState.d.ts +1 -29
- package/dist/src/Nucleus/routes/auth/apiKeys/index.d.ts +4 -0
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/captcha/index.d.ts +1 -29
- package/dist/src/Nucleus/routes/auth/check/index.d.ts +8 -0
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/cohort/handlers/activateUsers.d.ts +2 -3
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/cohort/handlers/bulkCreate.d.ts +2 -3
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/cohort/handlers/create.d.ts +2 -3
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/cohort/handlers/deactivateUsers.d.ts +2 -3
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/cohort/handlers/deleteUsers.d.ts +2 -3
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/cohort/handlers/detail.d.ts +2 -3
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/cohort/handlers/exportUsers.d.ts +2 -3
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/cohort/handlers/list.d.ts +2 -3
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/cohort/handlers/remove.d.ts +2 -3
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/cohort/handlers/update.d.ts +2 -3
- package/dist/src/Nucleus/routes/auth/cohort/index.d.ts +2 -0
- package/dist/src/Nucleus/routes/auth/cohort/types.d.ts +55 -0
- package/dist/src/Nucleus/routes/auth/cohort/utils.d.ts +43 -0
- package/dist/src/Nucleus/routes/auth/emailVerification/index.d.ts +9 -0
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/index.d.ts +6 -4
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/invite/index.d.ts +1 -29
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/login/index.d.ts +1 -29
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/logout/index.d.ts +1 -29
- package/dist/src/Nucleus/routes/auth/magicLink/index.d.ts +25 -0
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/me/index.d.ts +1 -29
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/oauth/index.d.ts +1 -29
- package/dist/src/Nucleus/routes/auth/passwordChange/index.d.ts +4 -0
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/passwordReset/index.d.ts +1 -29
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/passwordSet/index.d.ts +1 -29
- package/dist/src/Nucleus/routes/auth/refresh/index.d.ts +11 -0
- package/dist/src/Nucleus/routes/auth/refresh/sessionBinding.d.ts +29 -0
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/register/index.d.ts +3 -31
- package/dist/src/Nucleus/routes/auth/sessions/index.d.ts +4 -0
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/types.d.ts +22 -0
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/webauthn/index.d.ts +3 -31
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/authorization/index.d.ts +1 -29
- package/dist/src/Nucleus/routes/backup/index.d.ts +7 -0
- package/dist/src/Nucleus/routes/backup/types.d.ts +24 -0
- package/dist/src/Nucleus/routes/chat/conversations.routes.d.ts +3 -0
- package/dist/src/Nucleus/routes/chat/index.d.ts +12 -0
- package/dist/src/Nucleus/routes/chat/messages.routes.d.ts +3 -0
- package/dist/src/Nucleus/routes/config/helpers.d.ts +152 -0
- package/dist/src/Nucleus/routes/config/index.d.ts +2 -0
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/config/types.d.ts +12 -4
- package/dist/src/Nucleus/routes/domain/hostnames.d.ts +8 -0
- package/dist/src/Nucleus/routes/domain/index.d.ts +5 -0
- package/dist/src/Nucleus/routes/domain/registrations.d.ts +8 -0
- package/dist/src/Nucleus/routes/domain/resolve.d.ts +7 -0
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/entity/index.d.ts +22 -2
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/entity/utils.d.ts +1 -1
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/integrations/index.d.ts +1 -29
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/integrations/inspectRoutes.d.ts +2 -29
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/monitoring/index.d.ts +1 -29
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/monitoring/live.d.ts +1 -29
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/monitoring/serverLogs.d.ts +1 -29
- package/dist/src/Nucleus/routes/monitoring/sse.d.ts +28 -0
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/notification/index.d.ts +1 -29
- package/dist/src/Nucleus/routes/payment/index.d.ts +4 -0
- package/dist/src/Nucleus/routes/payment/marketplace/index.d.ts +5 -0
- package/dist/src/Nucleus/routes/payment/marketplace/payouts.d.ts +3 -0
- package/dist/src/Nucleus/routes/payment/marketplace/splits.d.ts +3 -0
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/payment/webhook.d.ts +16 -2
- package/dist/src/Nucleus/routes/pubsub/handshakeAuth.d.ts +77 -0
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/pubsub/index.d.ts +16 -29
- package/dist/src/Nucleus/routes/pubsub/refusalReason.d.ts +49 -0
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/pubsub/types.d.ts +7 -1
- package/dist/src/Nucleus/routes/secrets/index.d.ts +11 -0
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/shared/adminGuard.d.ts +14 -7
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/shared/godminGuard.d.ts +1 -1
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/storage/cdn.d.ts +16 -29
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/storage/helpers.d.ts +15 -0
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/storage/index.d.ts +1 -1
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/storage/resumable/index.d.ts +2 -30
- package/dist/src/Nucleus/routes/tenant/check-subdomain.d.ts +6 -0
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/tenant/index.d.ts +3 -2
- package/dist/src/Nucleus/routes/tenant/manage.d.ts +6 -0
- package/dist/src/Nucleus/routes/tenant/provision.d.ts +2 -0
- package/dist/src/Nucleus/routes/tenant/self-signup.d.ts +2 -0
- package/dist/src/{ElysiaPlugin → Nucleus}/routes/verification/index.d.ts +1 -29
- package/dist/src/Server/NucleusServer.d.ts +141 -0
- package/dist/src/Server/createNucleusServer.d.ts +75 -0
- package/dist/src/Server/defineRoute.d.ts +185 -0
- package/dist/src/Server/fileSchema.d.ts +35 -0
- package/dist/src/Server/formats.d.ts +4 -0
- package/dist/src/Server/inboundGuard.d.ts +26 -0
- package/dist/src/Server/internalHeaders.d.ts +26 -0
- package/dist/src/Server/openapi.d.ts +52 -0
- package/dist/src/Server/parseBody.d.ts +39 -0
- package/dist/src/Server/response.d.ts +72 -0
- package/dist/src/Server/routeCollector.d.ts +51 -0
- package/dist/src/Server/routeMount.d.ts +104 -0
- package/dist/src/Server/routeRecording.d.ts +107 -0
- package/dist/src/Server/router.d.ts +140 -0
- package/dist/src/Server/testApp.d.ts +82 -0
- package/dist/src/Server/validate.d.ts +76 -0
- package/dist/src/Server/websocket.d.ts +70 -0
- package/dist/src/Services/Authorization/activeRows.d.ts +33 -0
- package/dist/src/Services/Backup/BackupService.d.ts +22 -0
- package/dist/src/Services/Chat/ChatService.d.ts +3 -1
- package/dist/src/Services/Chat/attachmentAccess.d.ts +28 -0
- package/dist/src/Services/Chat/index.d.ts +1 -0
- package/dist/src/Services/Chat/types.d.ts +13 -0
- package/dist/src/Services/Logger/auditTaxonomy.d.ts +1 -1
- package/dist/src/Services/Logger/index.d.ts +1 -1
- package/dist/src/Services/Notification/index.d.ts +37 -2
- package/dist/src/Services/Notification/types.d.ts +17 -0
- package/dist/src/Services/SchemaTables/resolveEntities.d.ts +1 -1
- package/dist/src/Services/WebAuthn/types.d.ts +15 -0
- package/dist/src/types.d.ts +22 -2
- package/infra/templates/backend/package.json +0 -1
- package/infra/templates/backend/src/index.ts +25 -22
- package/package.json +9 -6
- package/public/components/docs/content/advanced.ts +37 -34
- package/public/components/docs/content/gettingStarted.ts +59 -11
- package/schemas/config.nucleus.json +54 -235
- package/schemas/nucleus.tables.schema.json +1 -3
- package/schemas/table.schema.json +22 -115
- package/scripts/build.ts +21 -4
- package/scripts/importCase.test.ts +174 -0
- package/scripts/packagedScripts.test.ts +4 -1
- package/scripts/scaffoldParity.test.ts +126 -0
- package/scripts/skillCitations.test.ts +106 -0
- package/src/Services/Logger/auditTaxonomy.ts +1 -1
- package/src/Services/SchemaTables/resolveEntities.ts +1 -1
- package/src/system.tables.json +5 -5
- package/dist/src/ElysiaPlugin/index.d.ts +0 -23
- package/dist/src/ElysiaPlugin/publicRoutes.d.ts +0 -8
- package/dist/src/ElysiaPlugin/routes/auth/admin/changeUserId.d.ts +0 -30
- package/dist/src/ElysiaPlugin/routes/auth/admin/createUser.d.ts +0 -36
- package/dist/src/ElysiaPlugin/routes/auth/admin/hardDeleteUser.d.ts +0 -30
- package/dist/src/ElysiaPlugin/routes/auth/admin/requireGodmin.d.ts +0 -17
- package/dist/src/ElysiaPlugin/routes/auth/apiKeys/index.d.ts +0 -3
- package/dist/src/ElysiaPlugin/routes/auth/check/index.d.ts +0 -62
- package/dist/src/ElysiaPlugin/routes/auth/cohort/index.d.ts +0 -30
- package/dist/src/ElysiaPlugin/routes/auth/cohort/types.d.ts +0 -26
- package/dist/src/ElysiaPlugin/routes/auth/cohort/utils.d.ts +0 -21
- package/dist/src/ElysiaPlugin/routes/auth/emailVerification/index.d.ts +0 -37
- package/dist/src/ElysiaPlugin/routes/auth/magicLink/index.d.ts +0 -53
- package/dist/src/ElysiaPlugin/routes/auth/passwordChange/index.d.ts +0 -32
- package/dist/src/ElysiaPlugin/routes/auth/refresh/index.d.ts +0 -38
- package/dist/src/ElysiaPlugin/routes/auth/sessions/index.d.ts +0 -32
- package/dist/src/ElysiaPlugin/routes/backup/index.d.ts +0 -35
- package/dist/src/ElysiaPlugin/routes/backup/types.d.ts +0 -14
- package/dist/src/ElysiaPlugin/routes/chat/conversations.routes.d.ts +0 -31
- package/dist/src/ElysiaPlugin/routes/chat/index.d.ts +0 -40
- package/dist/src/ElysiaPlugin/routes/chat/messages.routes.d.ts +0 -31
- package/dist/src/ElysiaPlugin/routes/config/helpers.d.ts +0 -60
- package/dist/src/ElysiaPlugin/routes/config/index.d.ts +0 -30
- package/dist/src/ElysiaPlugin/routes/domain/hostnames.d.ts +0 -36
- package/dist/src/ElysiaPlugin/routes/domain/index.d.ts +0 -4
- package/dist/src/ElysiaPlugin/routes/domain/registrations.d.ts +0 -36
- package/dist/src/ElysiaPlugin/routes/domain/resolve.d.ts +0 -35
- package/dist/src/ElysiaPlugin/routes/payment/index.d.ts +0 -32
- package/dist/src/ElysiaPlugin/routes/payment/marketplace/index.d.ts +0 -4
- package/dist/src/ElysiaPlugin/routes/payment/marketplace/payouts.d.ts +0 -31
- package/dist/src/ElysiaPlugin/routes/payment/marketplace/splits.d.ts +0 -31
- package/dist/src/ElysiaPlugin/routes/pubsub/refusalReason.d.ts +0 -18
- package/dist/src/ElysiaPlugin/routes/secrets/index.d.ts +0 -39
- package/dist/src/ElysiaPlugin/routes/tenant/check-subdomain.d.ts +0 -34
- package/dist/src/ElysiaPlugin/routes/tenant/manage.d.ts +0 -34
- package/dist/src/ElysiaPlugin/routes/tenant/provision.d.ts +0 -30
- package/dist/src/ElysiaPlugin/routes/tenant/self-signup.d.ts +0 -30
- package/dist/src/ElysiaPlugin/swagger.d.ts +0 -27
- /package/dist/src/{ElysiaPlugin → Nucleus}/authRouteClassifier.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/accountState.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/admin/createUserAuthz.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/admin/lockoutState.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/admin/resolveMutationSchema.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/admin/rotationPlan.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/apiKeys/types.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/assignDefaultRole.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/cohort/schemas.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/emailExempt.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/emailLinks/index.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/fetchUserRolesAndClaims.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/impersonationCookies.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/login/deviceInfo.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/login/identifier.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/login/identifierQuery.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/login/notifyDecision.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/login/types.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/login/utils.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/logout/types.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/logout/utils.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/magicLink/types.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/magicLink/utils.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/oauth/applyProfileSync.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/passwordChange/types.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/passwordChange/utils.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/passwordReset/outcome.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/passwordReset/types.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/passwordReset/utils.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/passwordSet/types.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/refresh/types.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/refresh/utils.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/register/types.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/register/utils.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/resolveAuthTables.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/sessions/types.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/sessions/utils.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/webauthn/cookies.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/auth/webauthn/types.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/chat/helpers.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/chat/types.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/domain/types.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/entity/anonymousCreate.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/entity/createScope.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/entity/relationAuthz.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/entity/types.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/entity/userRoleWrite.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/index.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/integrations/inspect.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/integrations/repository.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/integrations/types.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/payment/marketplace/types.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/payment/types.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/payment/validation.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/pubsub/ack-manager.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/pubsub/client-manager.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/pubsub/frame.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/secrets/types.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/shared/basePathCollision.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/shared/bodyCeiling.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/shared/clientIp.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/shared/csrf.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/shared/redact.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/shared/requestLimits.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/shared/sensitiveColumns.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/shared/sensitiveTables.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/shared/tenantBinding.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/shared/writeLock.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/storage/boundedStream.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/storage/cpuBudget.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/storage/file-record.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/storage/imageTransform.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/storage/mediaPostProcess.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/storage/pdfTransform.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/storage/resumable/session.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/storage/resumable/store.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/storage/videoTransform.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/routes/tenant/types.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/runtime.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/schemaReconcile.d.ts +0 -0
- /package/dist/src/{ElysiaPlugin → Nucleus}/utils.d.ts +0 -0
- /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 {
|
|
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 {
|
|
22
|
-
import {
|
|
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 =
|
|
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 })
|
|
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], {
|
|
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
|
}
|
package/bin/lib/commands/add.ts
CHANGED
|
@@ -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
|
-
|
|
13
|
+
|
|
14
14
|
import { spawnSync } from 'node:child_process'
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
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 = {
|
|
81
|
-
|
|
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)
|
|
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))
|
|
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(
|
|
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)
|
|
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(
|
|
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(
|
|
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
|
}
|
package/bin/lib/commands/dev.ts
CHANGED
|
@@ -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
|
-
|
|
13
|
+
|
|
14
14
|
import { spawnSync } from 'node:child_process'
|
|
15
|
-
import {
|
|
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 = {
|
|
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',
|
|
34
|
-
'-
|
|
35
|
-
'
|
|
36
|
-
|
|
37
|
-
'-
|
|
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) => [
|
|
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(
|
|
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(
|
|
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}$`], {
|
|
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))
|
|
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(
|
|
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}$`], {
|
|
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(
|
|
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 {
|
|
154
|
-
|
|
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
|
-
|
|
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))
|
|
28
|
+
if (await tcpReachable(pg.host, pg.port))
|
|
29
|
+
ok(`Postgres reachable at ${c.dim(`${pg.host}:${pg.port}`)}`)
|
|
28
30
|
else {
|
|
29
|
-
fail(
|
|
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(
|
|
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')
|
|
43
|
-
|
|
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)
|
|
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(
|
|
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
|
}
|
package/bin/lib/commands/init.ts
CHANGED
|
@@ -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 = {
|
|
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) =>
|
|
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))
|
|
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)
|
|
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(
|
|
112
|
-
|
|
113
|
-
|
|
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
|
|
134
|
+
else
|
|
135
|
+
warn('schema generation skipped/failed — run `bun run generate` after setting DATABASE_URL')
|
|
117
136
|
}
|
|
118
137
|
}
|
|
119
138
|
|