@underpostnet/cyberia 3.3.0 → 3.3.76
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/.dockerignore +1 -0
- package/.env.example +17 -5
- package/.github/workflows/coverall.cyberia.ci.yml +39 -14
- package/.github/workflows/cyberia-client.cd.yml +5 -0
- package/.github/workflows/cyberia-server.cd.yml +5 -0
- package/.github/workflows/docker-image.cyberia-client.ci.yml +8 -5
- package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +8 -3
- package/.github/workflows/docker-image.cyberia-server.ci.yml +8 -5
- package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +8 -5
- package/.github/workflows/docker-image.engine-cyberia.ci.yml +15 -1
- package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +15 -1
- package/.github/workflows/engine-cyberia.cd.yml +3 -0
- package/.github/workflows/engine-cyberia.ci.yml +4 -5
- package/.github/workflows/ghpkg.ci.yml +35 -20
- package/.github/workflows/hardhat.ci.yml +12 -11
- package/.github/workflows/npmpkg.ci.yml +17 -15
- package/.github/workflows/publish.cyberia.ci.yml +44 -0
- package/.github/workflows/pwa-microservices-template-test.ci.yml +2 -2
- package/.github/workflows/release.cd.yml +1 -0
- package/.prettierignore +1 -0
- package/AGENTS.md +96 -0
- package/CHANGELOG.md +653 -2513
- package/CLI-HELP.md +323 -157
- package/Dockerfile +25 -9
- package/Dockerfile.dev +25 -9
- package/Dockerfile.test +5 -5
- package/README.md +48 -28
- package/bin/build.js +86 -41
- package/bin/build.template.js +8 -3
- package/bin/cyberia.js +1494 -1403
- package/bin/deploy.js +18 -104
- package/bin/index.js +1494 -1403
- package/compose.env +17 -5
- package/conf.js +98 -7
- package/deploy/cyberia-client/deploy.sh +46 -0
- package/deploy/cyberia-client/package.sh +17 -0
- package/deploy/cyberia-client/state.sh +15 -0
- package/deploy/cyberia-server/deploy.sh +46 -0
- package/deploy/cyberia-server/package.sh +17 -0
- package/deploy/cyberia-server/state.sh +15 -0
- package/deploy/dd-cyberia/deploy.sh +9 -29
- package/deploy/dd-cyberia/init.sh +38 -45
- package/deploy/dd-cyberia/package.sh +16 -0
- package/deploy/dd-cyberia/state.sh +14 -0
- package/deploy/dd-cyberia/sync-deploy.sh +133 -147
- package/deploy/lib/config.sh +29 -0
- package/deploy/lib/github-actions-logging.sh +610 -0
- package/deploy/lib/host.sh +194 -0
- package/deploy/lib/state.sh +65 -0
- package/deploy/pwa-microservices-template/deploy.sh +17 -42
- package/deploy/release/deploy.sh +9 -41
- package/deployment.yaml +7 -2
- package/docker-compose.yml +23 -17
- package/examples/static-page/README.md +19 -19
- package/examples/static-page/ssr-components/CustomPage.js +2 -2
- package/hardhat/hardhat.config.js +1 -6
- package/hardhat/package-lock.json +101 -40
- package/hardhat/package.json +2 -2
- package/manifests/alertmanager/deployment.yaml +81 -0
- package/manifests/blackbox-exporter/deployment.yaml +75 -0
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +32 -7
- package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +32 -7
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +7 -2
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/mongo-express/deployment.yaml +46 -27
- package/manifests/deployment/mongo-express/kustomization.yaml +9 -0
- package/manifests/deployment/mongo-express/mongo-express-nodeport.yaml +18 -0
- package/manifests/deployment/mongo-express/service.yaml +16 -0
- package/manifests/deployment/mongo-express-no-auth/disable-mongodb-auth.yaml +16 -0
- package/manifests/deployment/mongo-express-no-auth/kustomization.yaml +16 -0
- package/manifests/grafana/deployment.yaml +26 -7
- package/manifests/grafana/kustomization.yaml +1 -0
- package/manifests/grafana/service.yaml +6 -1
- package/manifests/mariadb/pv.yaml +1 -0
- package/manifests/mongodb/statefulset.yaml +9 -2
- package/manifests/mysql/pv-pvc.yaml +14 -1
- package/manifests/postgresql/pv.yaml +12 -1
- package/manifests/postgresql/pvc.yaml +1 -0
- package/manifests/prometheus/deployment.yaml +18 -22
- package/package.json +87 -12
- package/scripts/event-monitor.sh +56 -0
- package/scripts/k3s-node-setup.sh +27 -14
- package/scripts/kubeadm-node-setup.sh +31 -11
- package/scripts/maas-setup.sh +8 -8
- package/scripts/selinux-normalize.sh +907 -0
- package/scripts/shutdown-machine.sh +843 -0
- package/scripts/test-monitor.sh +5 -5
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +15 -10
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +36 -5
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +7 -2
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +90 -186
- package/src/api/core/core.controller.js +1 -1
- package/src/api/core/core.router.js +2 -2
- package/src/api/core/core.service.js +2 -2
- package/src/api/crypto/crypto.controller.js +1 -1
- package/src/api/crypto/crypto.service.js +1 -1
- package/src/api/cyberia-action/cyberia-action.controller.js +1 -1
- package/src/api/cyberia-action/cyberia-action.router.js +3 -3
- package/src/api/cyberia-action/cyberia-action.service.js +5 -6
- package/src/api/cyberia-audio/cyberia-audio.controller.js +6 -0
- package/src/api/cyberia-audio/cyberia-audio.model.js +61 -0
- package/src/api/cyberia-audio/cyberia-audio.router.js +21 -0
- package/src/api/cyberia-audio/cyberia-audio.service.js +220 -0
- package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +2 -4
- package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +10 -19
- package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +13 -35
- package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +17 -34
- package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +1 -1
- package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +1 -1
- package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +2 -2
- package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +2 -2
- package/src/api/cyberia-entity/cyberia-entity.controller.js +1 -1
- package/src/api/cyberia-entity/cyberia-entity.model.js +2 -0
- package/src/api/cyberia-entity/cyberia-entity.router.js +1 -1
- package/src/api/cyberia-entity/cyberia-entity.service.js +3 -3
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +8 -2
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.model.js +31 -20
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +17 -2
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +468 -5
- package/src/api/cyberia-instance/cyberia-fallback-capture.js +502 -0
- package/src/api/cyberia-instance/cyberia-fallback-world.js +29 -40
- package/src/api/cyberia-instance/cyberia-instance-boot.service.js +5 -5
- package/src/api/cyberia-instance/cyberia-instance-items.js +98 -0
- package/src/api/cyberia-instance/cyberia-instance-map.service.js +35 -142
- package/src/api/cyberia-instance/cyberia-instance.controller.js +2 -5
- package/src/api/cyberia-instance/cyberia-instance.model.js +0 -14
- package/src/api/cyberia-instance/cyberia-instance.router.js +5 -18
- package/src/api/cyberia-instance/cyberia-instance.service.js +5 -45
- package/src/api/cyberia-instance/cyberia-world-generator.js +8 -13
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +1 -1
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +49 -75
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +1 -1
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +76 -3
- package/src/api/cyberia-map/cyberia-map.controller.js +1 -1
- package/src/api/cyberia-map/cyberia-map.router.js +1 -1
- package/src/api/cyberia-map/cyberia-map.service.js +8 -4
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.controller.js +31 -0
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.model.js +56 -0
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.router.js +33 -0
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +189 -0
- package/src/api/cyberia-quest/cyberia-quest.controller.js +1 -1
- package/src/api/cyberia-quest/cyberia-quest.router.js +4 -4
- package/src/api/cyberia-quest/cyberia-quest.service.js +5 -5
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.js +1 -1
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.model.js +1 -1
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +1 -1
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +2 -2
- package/src/api/cyberia-saga/cyberia-saga.controller.js +1 -1
- package/src/api/cyberia-saga/cyberia-saga.router.js +1 -1
- package/src/api/cyberia-saga/cyberia-saga.service.js +2 -2
- package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +403 -214
- package/src/api/cyberia-skill/cyberia-skill.controller.js +1 -1
- package/src/api/cyberia-skill/cyberia-skill.model.js +6 -6
- package/src/api/cyberia-skill/cyberia-skill.router.js +1 -1
- package/src/api/cyberia-skill/cyberia-skill.service.js +2 -2
- package/src/api/default/default.controller.js +1 -1
- package/src/api/default/default.router.js +1 -1
- package/src/api/default/default.service.js +2 -2
- package/src/api/document/document.controller.js +1 -1
- package/src/api/document/document.router.js +1 -1
- package/src/api/document/document.service.js +2 -2
- package/src/api/file/file.controller.js +1 -1
- package/src/api/file/file.ref.js +153 -0
- package/src/api/file/file.ref.json +11 -2
- package/src/api/file/file.router.js +1 -1
- package/src/api/file/file.service.js +2 -2
- package/src/api/instance/instance.controller.js +1 -1
- package/src/api/instance/instance.router.js +1 -1
- package/src/api/instance/instance.service.js +2 -2
- package/src/api/ipfs/ipfs.controller.js +1 -1
- package/src/api/ipfs/ipfs.router.js +2 -2
- package/src/api/ipfs/ipfs.service.js +2 -2
- package/src/api/object-layer/object-layer.controller.js +1 -1
- package/src/api/object-layer/object-layer.model.js +47 -12
- package/src/api/object-layer/object-layer.router.js +12 -5
- package/src/api/object-layer/object-layer.service.js +116 -123
- package/src/api/object-layer-render-frames/object-layer-render-frames.controller.js +1 -1
- package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +1 -1
- package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +2 -2
- package/src/api/test/test.controller.js +1 -1
- package/src/api/test/test.service.js +1 -1
- package/src/api/user/guest.service.js +3 -3
- package/src/api/user/user.controller.js +1 -1
- package/src/api/user/user.router.js +2 -2
- package/src/api/user/user.service.js +4 -4
- package/src/api.js +5 -9
- package/src/cli/app.js +402 -0
- package/src/cli/baremetal.js +30 -35
- package/src/cli/client.js +306 -0
- package/src/cli/cloud-init.js +3 -3
- package/src/cli/cluster.js +191 -82
- package/src/cli/db.js +120 -68
- package/src/cli/deploy.js +157 -75
- package/src/cli/docker-compose.js +5 -5
- package/src/cli/domains.js +184 -0
- package/src/cli/dotenv-store.js +143 -0
- package/src/cli/event.js +2281 -0
- package/src/cli/fs-selection.js +115 -0
- package/src/cli/fs.js +159 -343
- package/src/cli/host.js +672 -0
- package/src/cli/image.js +4 -6
- package/src/cli/index.js +343 -151
- package/src/cli/ipfs.js +34 -30
- package/src/cli/kickstart.js +1 -1
- package/src/cli/kubectl.js +218 -41
- package/src/cli/lxd.js +3 -3
- package/src/cli/monitor.js +1359 -26
- package/src/cli/package.js +90 -0
- package/src/cli/release.js +16 -12
- package/src/cli/repository.js +586 -363
- package/src/cli/run.js +398 -843
- package/src/cli/secrets.js +2158 -964
- package/src/cli/ssh.js +478 -279
- package/src/cli/state.js +394 -0
- package/src/cli/static.js +2 -2
- package/src/cli/system.js +1 -1
- package/src/cli/test.js +203 -125
- package/src/cli/vultr.js +61 -21
- package/src/cli/wireguard.js +1112 -306
- package/src/client/components/core/CalendarCore.js +53 -96
- package/src/client/components/core/Docs.js +327 -303
- package/src/client/components/core/FileExplorer.js +0 -21
- package/src/client/components/core/Modal.js +56 -1
- package/src/client/components/core/PanelForm.js +4 -4
- package/src/client/components/core/Repository.js +56 -0
- package/src/client/components/core/Translate.js +7 -0
- package/src/client/components/core/{FullScreen.js → ViewModeController.js} +42 -39
- package/src/client/components/core/Wallet.js +0 -11
- package/src/client/components/core/Worker.js +7 -1
- package/src/client/components/cryptokoyn/SettingsCryptokoyn.js +2 -2
- package/src/client/components/cyberia/ActionEngineCyberia.js +12 -36
- package/src/client/components/cyberia/EntityEngineCyberia.js +492 -121
- package/src/client/components/cyberia/InstanceEngineCyberia.js +129 -160
- package/src/client/components/cyberia/InstanceSelectionView.js +7 -1
- package/src/client/components/cyberia/MapEngineCyberia.js +175 -69
- package/src/client/components/cyberia/ObjectLayerEngine.js +1011 -97
- package/src/client/components/cyberia/ObjectLayerEngineModal.js +81 -155
- package/src/client/components/cyberia/ObjectLayerEngineViewer.js +113 -98
- package/src/client/components/cyberia/SharedDefaultsCyberia.js +215 -56
- package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +2 -35
- package/src/client/components/cyberia-portal/CssCyberiaPortal.js +1 -1
- package/src/client/components/cyberia-portal/MainBodyCyberiaPortal.js +712 -76
- package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +2 -2
- package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +0 -4
- package/src/client/components/default/AppShellDefault.js +1 -1
- package/src/client/components/default/SettingsDefault.js +2 -2
- package/src/client/components/itemledger/SettingsItemledger.js +2 -2
- package/src/client/components/underpost/AppShellUnderpost.js +3 -2
- package/src/client/components/underpost/SettingsUnderpost.js +2 -2
- package/src/client/public/cyberia-docs/ARCHITECTURE.md +15 -15
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +313 -30
- package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +51 -29
- package/src/client/public/cyberia-docs/CYBERIA-SAGA.md +52 -53
- package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +16 -11
- package/src/client/public/cyberia-docs/CYBERIA.md +48 -28
- package/src/client/public/cyberia-docs/ENTITY-PROFILE.md +11 -5
- package/src/client/public/cyberia-docs/HARDHAT-MODULE.md +23 -6
- package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
- package/src/client/public/cyberia-docs/STATS-PROGRESSION.md +217 -0
- package/src/client/public/cyberia-docs/WHITE-PAPER.md +20 -12
- package/src/client/public/default/android-chrome-144x144.png +0 -0
- package/src/client/public/default/android-chrome-192x192.png +0 -0
- package/src/client/public/default/android-chrome-256x256.png +0 -0
- package/src/client/public/default/android-chrome-36x36.png +0 -0
- package/src/client/public/default/android-chrome-384x384.png +0 -0
- package/src/client/public/default/android-chrome-48x48.png +0 -0
- package/src/client/public/default/android-chrome-512x512.png +0 -0
- package/src/client/public/default/android-chrome-72x72.png +0 -0
- package/src/client/public/default/android-chrome-96x96.png +0 -0
- package/src/client/public/default/apple-touch-icon-1024x1024.png +0 -0
- package/src/client/public/default/apple-touch-icon-114x114.png +0 -0
- package/src/client/public/default/apple-touch-icon-120x120.png +0 -0
- package/src/client/public/default/apple-touch-icon-144x144.png +0 -0
- package/src/client/public/default/apple-touch-icon-152x152.png +0 -0
- package/src/client/public/default/apple-touch-icon-167x167.png +0 -0
- package/src/client/public/default/apple-touch-icon-180x180.png +0 -0
- package/src/client/public/default/apple-touch-icon-57x57.png +0 -0
- package/src/client/public/default/apple-touch-icon-60x60.png +0 -0
- package/src/client/public/default/apple-touch-icon-72x72.png +0 -0
- package/src/client/public/default/apple-touch-icon-76x76.png +0 -0
- package/src/client/public/default/apple-touch-icon-precomposed.png +0 -0
- package/src/client/public/default/apple-touch-icon.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1125x2436.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1136x640.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1170x2532.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1179x2556.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1242x2208.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1242x2688.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1284x2778.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1290x2796.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1334x750.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1488x2266.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1536x2048.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1620x2160.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1640x2160.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1668x2224.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1668x2388.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1792x828.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2048x1536.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2048x2732.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2160x1620.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2160x1640.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2208x1242.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2224x1668.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2266x1488.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2388x1668.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2436x1125.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2532x1170.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2556x1179.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2688x1242.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2732x2048.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2778x1284.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2796x1290.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-640x1136.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-750x1334.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-828x1792.png +0 -0
- package/src/client/public/default/assets/background/white.jpg +0 -0
- package/src/client/public/default/browserconfig.xml +1 -1
- package/src/client/public/default/favicon-16x16.png +0 -0
- package/src/client/public/default/favicon-32x32.png +0 -0
- package/src/client/public/default/favicon-48x48.png +0 -0
- package/src/client/public/default/favicon.ico +0 -0
- package/src/client/public/default/manifest.webmanifest +5 -5
- package/src/client/public/default/mstile-144x144.png +0 -0
- package/src/client/public/default/mstile-150x150.png +0 -0
- package/src/client/public/default/mstile-310x150.png +0 -0
- package/src/client/public/default/mstile-310x310.png +0 -0
- package/src/client/public/default/mstile-70x70.png +0 -0
- package/src/client/public/default/yandex-browser-50x50.png +0 -0
- package/src/client/public/default/yandex-browser-manifest.json +1 -1
- package/src/client/services/cyberia-audio/cyberia-audio.service.js +99 -0
- package/src/client/services/cyberia-entity-type-default/cyberia-entity-type-default.service.js +38 -0
- package/src/client/services/cyberia-instance/cyberia-instance.management.js +5 -4
- package/src/client/services/cyberia-instance/cyberia-instance.service.js +0 -40
- package/src/client/services/cyberia-map/cyberia-map.management.js +5 -4
- package/src/client/services/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +138 -0
- package/src/client/services/object-layer/object-layer.management.js +6 -10
- package/src/client/services/object-layer/object-layer.service.js +3 -1
- package/src/client/ssr/RootDocument.js +1 -1
- package/src/client/ssr/body/404.js +1 -1
- package/src/client/ssr/body/500.js +1 -1
- package/src/client/ssr/body/CacheControl.js +1 -1
- package/src/client/ssr/body/CyberiaDefaultSplashScreen.js +1 -1
- package/src/client/ssr/body/DefaultSplashScreen.js +1 -1
- package/src/client/ssr/body/SwaggerDarkMode.js +1 -1
- package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +1 -1
- package/src/client/ssr/head/CryptokoynScripts.js +1 -1
- package/src/client/ssr/head/Css.js +1 -1
- package/src/client/ssr/head/CyberiaPortalScripts.js +1 -1
- package/src/client/ssr/head/DefaultScripts.js +1 -1
- package/src/client/ssr/head/ItemledgerScripts.js +1 -1
- package/src/client/ssr/head/Microdata.js +1 -1
- package/src/client/ssr/head/Production.js +1 -1
- package/src/client/ssr/head/Pwa.js +1 -1
- package/src/client/ssr/head/PwaItemledger.js +1 -1
- package/src/client/ssr/head/Seo.js +1 -1
- package/src/client/ssr/head/UnderpostScripts.js +1 -1
- package/src/client/ssr/mailer/DefaultRecoverEmail.js +1 -1
- package/src/client/ssr/mailer/DefaultVerifyEmail.js +1 -1
- package/src/client/ssr/views/Cyberia404.js +7 -5
- package/src/client/ssr/views/CyberiaServerMetrics.js +1 -1
- package/src/client/ssr/views/Maintenance.js +1 -1
- package/src/client/ssr/views/NoNetworkConnection.js +1 -1
- package/src/client/ssr/views/Test.js +1 -1
- package/src/client/sw/core.sw.js +20 -14
- package/src/client-builder/client-build-docs.js +92 -77
- package/src/client-builder/client-build-live.js +2 -2
- package/src/client-builder/client-build.js +105 -74
- package/src/client-builder/client-bundle.js +347 -0
- package/src/client-builder/client-dev-server.js +3 -3
- package/src/client-builder/client-icons.js +2 -2
- package/src/client-builder/ssr.js +3 -3
- package/src/client.build.js +3 -6
- package/src/client.dev.js +3 -6
- package/src/db/DataBaseProvider.js +186 -3
- package/src/db/mariadb/MariaDB.js +3 -2
- package/src/db/mongo/MongoBootstrap.js +273 -101
- package/src/db/mongo/MongoExpress.js +187 -0
- package/src/db/mongo/MongooseDB.js +7 -8
- package/src/{server/valkey.js → db/valkey/Valkey.js} +5 -4
- package/src/grpc/cyberia/grpc-server.js +6 -8
- package/src/index.js +79 -23
- package/src/mailer/EmailRender.js +2 -2
- package/src/mailer/MailerInterceptor.js +87 -0
- package/src/mailer/MailerProvider.js +50 -6
- package/src/projects/cyberia/atlas-sprite-sheet-generator.js +144 -147
- package/src/projects/cyberia/atlas-sprite-sheet-store.js +391 -0
- package/src/projects/cyberia/besu-genesis-generator.js +8 -17
- package/src/projects/cyberia/boot-contract-fixtures.js +32 -0
- package/src/projects/cyberia/catalog-cyberia.js +54 -10
- package/src/projects/cyberia/gemini-client.js +4 -4
- package/src/projects/cyberia/generate-saga.js +5 -12
- package/src/projects/cyberia/hot-reload-trigger.js +18 -29
- package/src/projects/cyberia/instance-backup.js +210 -0
- package/src/projects/cyberia/instance-data.js +178 -258
- package/src/projects/cyberia/ipfs-client.js +4 -4
- package/src/projects/cyberia/map-preview-generator.js +33 -69
- package/src/projects/cyberia/object-layer.js +153 -320
- package/src/projects/cyberia/seed-audio.js +139 -0
- package/src/projects/cyberia/semantic-layer-generator-skin.js +2 -8
- package/src/projects/cyberia/semantic-layer-generator.js +3 -19
- package/src/projects/cyberia/stat-balance.js +81 -0
- package/src/projects/cyberia/stat-commands.js +23 -0
- package/src/projects/cyberia/stat-contract-generator.js +101 -0
- package/src/projects/underpost/catalog-underpost.js +9 -1
- package/src/proxy.js +4 -7
- package/src/runtime/cyberia-client/Dockerfile +17 -8
- package/src/runtime/cyberia-client/Dockerfile.dev +13 -4
- package/src/runtime/cyberia-server/Dockerfile +18 -6
- package/src/runtime/cyberia-server/Dockerfile.dev +16 -3
- package/src/runtime/engine-cyberia/Dockerfile +25 -9
- package/src/runtime/engine-cyberia/Dockerfile.dev +25 -9
- package/src/runtime/engine-cyberia/Dockerfile.test +5 -5
- package/src/runtime/engine-cyberia/compose.env +17 -5
- package/src/runtime/engine-cyberia/docker-compose.yml +23 -17
- package/src/runtime/express/Express.js +32 -27
- package/src/runtime/lampp/Lampp.js +22 -70
- package/src/runtime/nginx/Nginx.js +1 -1
- package/src/runtime/wp/Wp.js +16 -48
- package/src/server/{catalog.js → build/catalog.js} +15 -5
- package/src/server/build/coverage.js +188 -0
- package/src/server/build/execution.js +402 -0
- package/src/server/build/package.js +508 -0
- package/src/server/build/testing.js +578 -0
- package/src/server/{dns.js → network/dns.js} +144 -11
- package/src/server/{forward-proxy.js → network/forward-proxy.js} +10 -18
- package/src/server/{middlewares.js → network/middlewares.js} +11 -5
- package/src/server/network/node-capability.js +98 -0
- package/src/server/{peer.js → network/peer.js} +3 -3
- package/src/server/{proxy.js → network/proxy.js} +7 -13
- package/src/server/network/router.js +356 -0
- package/src/server/{tls.js → network/tls.js} +2 -2
- package/src/server/{underpost-compression.js → network/underpost-compression.js} +1 -1
- package/src/server/{underpost-gateway.js → network/underpost-gateway.js} +7 -7
- package/src/server/{underpost-ingress.js → network/underpost-ingress.js} +2 -2
- package/src/server/{cri.js → ops/cri.js} +2 -2
- package/src/server/{cron.js → ops/cron.js} +349 -74
- package/src/server/ops/event-notification.js +284 -0
- package/src/server/{logger.js → ops/logger.js} +143 -23
- package/src/server/ops/monitoring.js +1724 -0
- package/src/server/{systemd.js → ops/systemd.js} +55 -1
- package/src/server/{conf.js → runtime/conf.js} +516 -423
- package/src/server/runtime/config-scope.js +215 -0
- package/src/server/runtime/environment.js +149 -0
- package/src/server/{process.js → runtime/process.js} +153 -12
- package/src/server/{runtime-status.js → runtime/runtime-status.js} +155 -16
- package/src/server/{runtime.js → runtime/runtime.js} +12 -8
- package/src/server/{start.js → runtime/start.js} +128 -39
- package/src/server/{auth.js → security/auth.js} +7 -8
- package/src/server/security/container-storage.js +382 -0
- package/src/server/{crypto.js → security/crypto.js} +1 -1
- package/src/server/{selinux.js → security/selinux.js} +1 -1
- package/src/server/{backup.js → storage/backup.js} +17 -8
- package/src/server/{data-query.js → storage/data-query.js} +17 -1
- package/src/server/storage/downloader.js +121 -0
- package/src/server/storage/repository.js +68 -0
- package/src/server/storage/zip.js +153 -0
- package/src/server.js +4 -7
- package/src/ws/IoInterface.js +1 -1
- package/src/ws/IoServer.js +1 -1
- package/src/ws/core/core.ws.connection.js +1 -1
- package/src/ws/core/core.ws.emit.js +1 -1
- package/src/ws/core/core.ws.server.js +1 -1
- package/src/ws/default/default.ws.connection.js +1 -1
- package/src/ws/default/default.ws.emit.js +1 -1
- package/src/ws/default/default.ws.server.js +1 -1
- package/test/e2e/event-e2e-public-ingress-down.js +45 -0
- package/test/e2e/event-e2e-wireguard-server-down.js +38 -0
- package/test/e2e/event-e2e-wireguard-spoke-down.js +40 -0
- package/test/{api.test.js → integration/app/api.test.js} +33 -14
- package/test/integration/app/cyberia/atlas-sprite-sheet-store.test.js +402 -0
- package/test/integration/app/cyberia/atlas-sprite-sheet.test.js +86 -0
- package/test/integration/app/cyberia/cyberia-cli-plain-reads.test.js +64 -0
- package/test/integration/app/cyberia/cyberia-entity-type-default.test.js +842 -0
- package/test/integration/app/cyberia/cyberia-instance-conf-coerce.test.js +53 -0
- package/test/{cyberia-instance-conf-defaults.test.js → integration/app/cyberia/cyberia-instance-conf-defaults.test.js} +17 -23
- package/test/integration/app/cyberia/cyberia-instance-items.test.js +90 -0
- package/test/integration/app/cyberia/cyberia-load.test.js +466 -0
- package/test/integration/app/cyberia/cyberia-map-audio-conf.test.js +256 -0
- package/test/integration/app/cyberia/cyberia-stats.test.js +150 -0
- package/test/integration/app/cyberia/fallback-world-capture.test.js +338 -0
- package/test/integration/app/cyberia/object-layer-item-id.test.js +327 -0
- package/test/integration/app/cyberia/object-layer-item-selection.test.js +56 -0
- package/test/integration/app/cyberia/object-layer-natural-key.test.js +32 -0
- package/test/integration/app/cyberia/seed-audio.test.js +275 -0
- package/test/{shape-generator.test.js → integration/app/cyberia/shape-generator.test.js} +1 -1
- package/test/integration/infra/1-security/config-scope.test.js +305 -0
- package/test/integration/infra/1-security/container-storage.test.js +356 -0
- package/test/integration/infra/1-security/in-pod-cli-surface.test.js +86 -0
- package/test/integration/infra/1-security/secret-onboarding.test.js +930 -0
- package/test/{selinux.test.js → integration/infra/1-security/selinux.test.js} +2 -4
- package/test/{sops-secret-store.test.js → integration/infra/1-security/sops-secret-store.test.js} +415 -61
- package/test/integration/infra/1-security/systemd-service.test.js +70 -0
- package/test/integration/infra/1-security/underpost-config-secret.test.js +367 -0
- package/test/integration/infra/2-network/dns-firewall.test.js +566 -0
- package/test/integration/infra/2-network/node-capability.test.js +182 -0
- package/test/integration/infra/2-network/wireguard-cli.test.js +1187 -0
- package/test/{wireguard-edge.test.js → integration/infra/2-network/wireguard-edge.test.js} +649 -55
- package/test/{cluster-instances.test.js → integration/infra/3-cluster/cluster-instances.test.js} +11 -15
- package/test/{deploy-node-placement.test.js → integration/infra/3-cluster/deploy-node-placement.test.js} +3 -3
- package/test/integration/infra/3-cluster/docker-compose-stack.test.js +313 -0
- package/test/integration/infra/3-cluster/mongo-express-deploy.test.js +156 -0
- package/test/integration/infra/4-ingress/api-cross-origin.test.js +79 -0
- package/test/integration/infra/4-ingress/deploy-routes.test.js +379 -0
- package/test/integration/infra/4-ingress/gateway-static-assets.test.js +453 -0
- package/test/{instance-traffic-plan.test.js → integration/infra/4-ingress/instance-traffic-plan.test.js} +10 -6
- package/test/{underpost-gateway.test.js → integration/infra/4-ingress/underpost-gateway.test.js} +6 -6
- package/test/{underpost-ingress.test.js → integration/infra/4-ingress/underpost-ingress.test.js} +4 -4
- package/test/integration/infra/5-observability/cron-jobs.test.js +652 -0
- package/test/{deploy-monitor.test.js → integration/infra/5-observability/deploy-monitor.test.js} +29 -31
- package/test/integration/infra/5-observability/event-notification.test.js +197 -0
- package/test/integration/infra/5-observability/event-remediation.test.js +502 -0
- package/test/integration/infra/5-observability/event-targets.test.js +1174 -0
- package/test/integration/infra/5-observability/monitoring-stack.test.js +902 -0
- package/test/support/shell-harness.js +73 -0
- package/test/unit/build-template.test.js +97 -0
- package/test/unit/build-workflow-offline.test.js +132 -0
- package/test/unit/catalog.test.js +71 -0
- package/test/unit/client-build-docs.test.js +361 -0
- package/test/unit/client-bundle.test.js +313 -0
- package/test/unit/conf-loading.test.js +303 -0
- package/test/unit/conf-resolution.test.js +996 -0
- package/test/unit/coverage-artifact.test.js +221 -0
- package/test/{crypto.test.js → unit/crypto.test.js} +1 -1
- package/test/unit/cyberia/instance-backup.test.js +183 -0
- package/test/unit/cyberia/instance-object-layer-items.test.js +46 -0
- package/test/unit/cyberia/publish-workflow.test.js +22 -0
- package/test/unit/cyberia/stat-balance.test.js +69 -0
- package/test/unit/deploy-env-resolution.test.js +38 -0
- package/test/unit/deploy-log-table.test.js +518 -0
- package/test/unit/downloader-integrity.test.js +98 -0
- package/test/unit/execution-profiles.test.js +219 -0
- package/test/unit/file-reference-registry.test.js +141 -0
- package/test/unit/fleet-sync-source.test.js +149 -0
- package/test/unit/fs-storage-paths.test.js +596 -0
- package/test/unit/logger-redaction.test.js +141 -0
- package/test/unit/package.test.js +457 -0
- package/test/unit/prepare-host.test.js +382 -0
- package/test/unit/process-environment.test.js +18 -0
- package/test/unit/propagation-message.test.js +78 -0
- package/test/unit/redeploy-plan.test.js +24 -0
- package/test/unit/release-bump.test.js +474 -0
- package/test/unit/ssh-output-redaction.test.js +28 -0
- package/test/unit/start-options.test.js +46 -0
- package/test/unit/test-tiers.test.js +220 -0
- package/test/unit/zip-archive.test.js +142 -0
- package/vitest.config.js +66 -0
- package/.nycrc +0 -9
- package/deploy/lib/logging.sh +0 -96
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +0 -48
- package/manifests/mongodb/pv-pvc.yaml +0 -59
- package/scripts/coverall-test-cyberia.sh +0 -24
- package/scripts/coverall-test.sh +0 -24
- package/scripts/link-local-underpost-cli.sh +0 -6
- package/src/api/cyberia-instance/cyberia-fallback-default-items.js +0 -63
- package/src/api/user/user.build.js +0 -16
- package/src/cli/env.js +0 -177
- package/src/client/components/core/Blockchain.js +0 -41
- package/src/client/components/core/Blog.js +0 -9
- package/src/client/components/core/KeyboardAvoidance.js +0 -145
- package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +0 -368
- package/src/client/public/default/assets/background/dark.jpg +0 -0
- package/src/client/public/default/assets/background/white0-min.jpg +0 -0
- package/src/client/public/default/assets/background/white0.jpg +0 -0
- package/src/client/public/doc/favicon.ico +0 -0
- package/src/client/public/doc/sitemap +0 -148
- package/src/server/downloader.js +0 -73
- package/src/server/environment.js +0 -98
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client bundle transport: enumerates the zip artifacts a deployment's routes build, uploads
|
|
3
|
+
* them to file storage, and restores them into the served public directory.
|
|
4
|
+
*
|
|
5
|
+
* Push and pull are two directions of one contract, so both resolve their routes, bundle ids,
|
|
6
|
+
* storage keys and part files through the helpers here rather than re-deriving them. Route
|
|
7
|
+
* selection mirrors {@link buildClient}: a route that the build skips produces no artifact for
|
|
8
|
+
* either direction to carry.
|
|
9
|
+
* @module src/client-builder/client-bundle.js
|
|
10
|
+
* @namespace clientBundle
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import fs from 'fs-extra';
|
|
14
|
+
import UnderpostFileStorage from '../cli/fs.js';
|
|
15
|
+
import * as dir from 'path';
|
|
16
|
+
import { cli } from '../server/build/execution.js';
|
|
17
|
+
import { loadConfServerJson, loadReplicas } from '../server/runtime/conf.js';
|
|
18
|
+
import { loggerFactory } from '../server/ops/logger.js';
|
|
19
|
+
import { shellArgumentFactory, shellExec } from '../server/runtime/process.js';
|
|
20
|
+
import { clientBundleIdFactory, getZipPartPaths, mergeClientBuildZip, unzipClientBuild } from './client-build.js';
|
|
21
|
+
|
|
22
|
+
const logger = loggerFactory(import.meta);
|
|
23
|
+
|
|
24
|
+
/** @type {string} Directory `buildClient --build-zip` writes bundles to, and the only place push and pull look. */
|
|
25
|
+
const CLIENT_BUNDLE_DIRECTORY = './build';
|
|
26
|
+
|
|
27
|
+
/** @type {number} Part size in MB applied when the caller names none. */
|
|
28
|
+
const DEFAULT_SPLIT_MB = 8;
|
|
29
|
+
|
|
30
|
+
/** @type {string} `--split` value that turns splitting off. */
|
|
31
|
+
const SPLIT_DISABLED = 'none';
|
|
32
|
+
|
|
33
|
+
const CLIENT_BUNDLE_STORAGE_ID = 'bundle';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Resolves the conf.server path a deployment's routes are read from.
|
|
37
|
+
* @function clientBundleConfServerPath
|
|
38
|
+
* @param {string} deployId - The deployment identifier.
|
|
39
|
+
* @returns {string} Path to the deployment's `conf.server.json`.
|
|
40
|
+
* @memberof clientBundle
|
|
41
|
+
*/
|
|
42
|
+
const clientBundleConfServerPath = (deployId) => `./engine-private/conf/${deployId}/conf.server.json`;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Resolves the storage manifest that tracks a deployment's uploaded bundle parts. Push writes
|
|
46
|
+
* it and pull reads it, so both must name the same file.
|
|
47
|
+
* @function clientBundleStorageFilePath
|
|
48
|
+
* @param {string} deployId - The deployment identifier.
|
|
49
|
+
* @returns {string} Path to the deployment's bundle storage manifest.
|
|
50
|
+
* @memberof clientBundle
|
|
51
|
+
*/
|
|
52
|
+
const clientBundleStorageFilePath = (deployId) =>
|
|
53
|
+
UnderpostFileStorage.API.resolveManifest({ deployId, storageId: CLIENT_BUNDLE_STORAGE_ID });
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Parses the comma-separated host filter both runners accept as their positional argument.
|
|
57
|
+
* @function clientBundleHostFilter
|
|
58
|
+
* @param {string} [hosts] - Comma-separated host names, or empty for every host.
|
|
59
|
+
* @returns {string[]} The requested hosts, empty when no filter was given.
|
|
60
|
+
* @memberof clientBundle
|
|
61
|
+
*/
|
|
62
|
+
const clientBundleHostFilter = (hosts = '') =>
|
|
63
|
+
`${hosts ?? ''}`
|
|
64
|
+
.split(',')
|
|
65
|
+
.map((host) => host.trim())
|
|
66
|
+
.filter(Boolean);
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Resolves the `--split` option into the flag `underpost client` expects.
|
|
70
|
+
* @function clientBundleSplitFlag
|
|
71
|
+
* @param {string|number} [split] - Part size in MB, or `none` to disable splitting.
|
|
72
|
+
* @returns {string} The flag to append, empty when splitting is disabled.
|
|
73
|
+
* @memberof clientBundle
|
|
74
|
+
*/
|
|
75
|
+
const clientBundleSplitFlag = (split) => {
|
|
76
|
+
const requested = `${split ?? ''}`.trim();
|
|
77
|
+
if (!requested) return `--split ${DEFAULT_SPLIT_MB}`;
|
|
78
|
+
if (requested === SPLIT_DISABLED) return '';
|
|
79
|
+
|
|
80
|
+
const splitMb = Number(requested);
|
|
81
|
+
if (Number.isFinite(splitMb) && splitMb > 0) return `--split ${splitMb}`;
|
|
82
|
+
|
|
83
|
+
logger.warn('invalid split option, falling back to the default', { split: requested, default: DEFAULT_SPLIT_MB });
|
|
84
|
+
return `--split ${DEFAULT_SPLIT_MB}`;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Enumerates the bundles a deployment's routes build to.
|
|
89
|
+
*
|
|
90
|
+
* A route is skipped exactly where {@link buildClient} skips it — redirects and
|
|
91
|
+
* rebuild-disabled routes emit nothing, and a `singleReplica` route's replicas are built under
|
|
92
|
+
* their own deploy ids, so they carry their own manifests rather than the parent's.
|
|
93
|
+
* @function clientBundleEntriesFactory
|
|
94
|
+
* @param {object} options
|
|
95
|
+
* @param {string} options.deployId - The deployment identifier.
|
|
96
|
+
* @param {string[]} [options.hosts] - Optional host filter; empty selects every configured host.
|
|
97
|
+
* @returns {Array<{host: string, routePath: string, buildId: string, zipPath: string, publicPath: string}>}
|
|
98
|
+
* One entry per bundle, in configuration order.
|
|
99
|
+
* @memberof clientBundle
|
|
100
|
+
*/
|
|
101
|
+
const clientBundleEntriesFactory = ({ deployId, hosts = [] }) => {
|
|
102
|
+
const confServerPath = clientBundleConfServerPath(deployId);
|
|
103
|
+
if (!fs.existsSync(confServerPath)) {
|
|
104
|
+
logger.warn('conf.server not found', { deployId, confServerPath });
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const confServer = loadReplicas(deployId, loadConfServerJson(confServerPath));
|
|
109
|
+
const selectedHosts = hosts.length > 0 ? hosts : Object.keys(confServer);
|
|
110
|
+
|
|
111
|
+
return selectedHosts.flatMap((host) => {
|
|
112
|
+
if (!confServer[host]) {
|
|
113
|
+
logger.warn('host not present in conf.server', { deployId, host });
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
return Object.keys(confServer[host])
|
|
117
|
+
.filter((routePath) => {
|
|
118
|
+
const { redirect, disabledRebuild, singleReplica } = confServer[host][routePath] ?? {};
|
|
119
|
+
return !redirect && !disabledRebuild && !singleReplica;
|
|
120
|
+
})
|
|
121
|
+
.map((routePath) => {
|
|
122
|
+
const buildId = clientBundleIdFactory(host, routePath);
|
|
123
|
+
return {
|
|
124
|
+
host,
|
|
125
|
+
routePath,
|
|
126
|
+
buildId,
|
|
127
|
+
zipPath: `${CLIENT_BUNDLE_DIRECTORY}/${buildId}.zip`,
|
|
128
|
+
publicPath: routePath === '/' ? `public/${host}` : `public/${host}${routePath}`,
|
|
129
|
+
};
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Reads the storage manifest as a plain lookup, for the recorded size of each uploaded artifact.
|
|
136
|
+
* Read-only and tolerant: a manifest that is absent or unreadable disables verification rather
|
|
137
|
+
* than failing the pull, since the artifacts themselves are still what the bundle is made of.
|
|
138
|
+
* @function clientBundleManifestFactory
|
|
139
|
+
* @param {string} deployId - The deployment identifier.
|
|
140
|
+
* @returns {Object<string, {bytes?: number}>} The tracked entries, empty when unavailable.
|
|
141
|
+
* @memberof clientBundle
|
|
142
|
+
*/
|
|
143
|
+
const clientBundleManifestFactory = (deployId) => {
|
|
144
|
+
const manifestPath = clientBundleStorageFilePath(deployId);
|
|
145
|
+
try {
|
|
146
|
+
return UnderpostFileStorage.API.readManifest({ deployId, storageId: CLIENT_BUNDLE_STORAGE_ID }).storage;
|
|
147
|
+
} catch (error) {
|
|
148
|
+
logger.warn('bundle manifest unreadable, size verification skipped', { manifestPath, error: error?.message });
|
|
149
|
+
return {};
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* The tracked keys that belong to one bundle.
|
|
155
|
+
*
|
|
156
|
+
* A bundle owns its zip and every split part of it, and nothing else: the id already carries the
|
|
157
|
+
* route, so `underpost.net-` and `underpost.net-peer` cannot claim each other's parts.
|
|
158
|
+
* @function clientBundleTrackedKeys
|
|
159
|
+
* @param {object} manifest - The bundle storage manifest.
|
|
160
|
+
* @param {string} buildId - The bundle's build id.
|
|
161
|
+
* @returns {string[]} The manifest keys for that bundle.
|
|
162
|
+
* @memberof clientBundle
|
|
163
|
+
*/
|
|
164
|
+
const clientBundleTrackedKeys = (manifest, buildId) =>
|
|
165
|
+
Object.keys(manifest).filter((key) =>
|
|
166
|
+
key.replace(/^\.\//, '').startsWith(`${CLIENT_BUNDLE_DIRECTORY.replace(/^\.\//, '')}/${buildId}.zip`),
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Lists the local artifacts that make up one bundle: its split parts when the build split it,
|
|
171
|
+
* otherwise the single zip.
|
|
172
|
+
* @function clientBundleArtifactPaths
|
|
173
|
+
* @param {string} zipPath - The bundle's zip path.
|
|
174
|
+
* @returns {{zipPath: string, partPaths: string[], artifactPaths: string[]}} The resolved artifacts.
|
|
175
|
+
* @memberof clientBundle
|
|
176
|
+
*/
|
|
177
|
+
const clientBundleArtifactPaths = (zipPath) => {
|
|
178
|
+
const partPaths = fs.existsSync(dir.dirname(zipPath)) ? getZipPartPaths(zipPath) : [];
|
|
179
|
+
const artifactPaths = partPaths.length > 0 ? partPaths : fs.existsSync(zipPath) ? [zipPath] : [];
|
|
180
|
+
return { zipPath, partPaths, artifactPaths };
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Builds a deployment's client bundles and uploads them to file storage.
|
|
185
|
+
*
|
|
186
|
+
* The build and the uploads run as child processes because `underpost app load` writes the
|
|
187
|
+
* `.env` this deployment's credentials live in, and only a freshly started process reads it.
|
|
188
|
+
* @function pushClientBundle
|
|
189
|
+
* @param {object} options
|
|
190
|
+
* @param {string} options.deployId - The deployment identifier.
|
|
191
|
+
* @param {string[]} [options.hosts] - Optional host filter; empty selects every configured host.
|
|
192
|
+
* @param {string|number} [options.split] - Part size in MB, or `none` to upload one zip.
|
|
193
|
+
* @param {boolean} [options.dev] - Build for development; defaults to production.
|
|
194
|
+
* @returns {{pushed: number, skipped: number}} How many bundles were uploaded and skipped.
|
|
195
|
+
* @memberof clientBundle
|
|
196
|
+
*/
|
|
197
|
+
const pushClientBundle = ({ deployId, hosts = [], split, dev = false }) => {
|
|
198
|
+
const baseCommand = cli('underpost', { local: true });
|
|
199
|
+
const env = dev ? 'development' : 'production';
|
|
200
|
+
const splitFlag = clientBundleSplitFlag(split);
|
|
201
|
+
|
|
202
|
+
shellExec(`${baseCommand} app load --env ${env} --args deploy-id=${deployId}`);
|
|
203
|
+
shellExec(`${baseCommand} client ${deployId} --env ${env} --build-zip${splitFlag ? ` ${splitFlag}` : ''}`);
|
|
204
|
+
|
|
205
|
+
const entries = clientBundleEntriesFactory({ deployId, hosts });
|
|
206
|
+
if (entries.length === 0) logger.warn('no bundle routes resolved', { deployId, hosts });
|
|
207
|
+
|
|
208
|
+
let pushed = 0;
|
|
209
|
+
let skipped = 0;
|
|
210
|
+
for (const { host, routePath, buildId, zipPath } of entries) {
|
|
211
|
+
const { artifactPaths } = clientBundleArtifactPaths(zipPath);
|
|
212
|
+
if (artifactPaths.length === 0) {
|
|
213
|
+
logger.warn('no bundle artifacts found', { host, routePath, buildId });
|
|
214
|
+
skipped++;
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
for (const artifactPath of artifactPaths) {
|
|
218
|
+
shellExec(
|
|
219
|
+
`${baseCommand} fs ${artifactPath} --deploy-id ${deployId} --storage-id ${CLIENT_BUNDLE_STORAGE_ID} --force`,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Remove obsolete manifest entries only after all current artifacts upload.
|
|
224
|
+
const current = new Set(artifactPaths.map((artifactPath) => artifactPath.replace(/^\.\//, '')));
|
|
225
|
+
const stale = clientBundleTrackedKeys(clientBundleManifestFactory(deployId), buildId).filter(
|
|
226
|
+
(key) => !current.has(key.replace(/^\.\//, '')),
|
|
227
|
+
);
|
|
228
|
+
for (const key of stale) {
|
|
229
|
+
shellExec(
|
|
230
|
+
`${baseCommand} fs --tracked --key ${shellArgumentFactory(key)} ` +
|
|
231
|
+
`--deploy-id ${deployId} --storage-id ${CLIENT_BUNDLE_STORAGE_ID} --rm`,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
if (stale.length > 0) logger.warn('removed bundle parts a smaller build no longer produces', { buildId, stale });
|
|
235
|
+
|
|
236
|
+
logger.info('pushed bundle', { host, routePath, artifacts: artifactPaths.length });
|
|
237
|
+
pushed++;
|
|
238
|
+
}
|
|
239
|
+
return { pushed, skipped };
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Downloads a deployment's client bundles from file storage and installs them into the served
|
|
244
|
+
* public directory.
|
|
245
|
+
*
|
|
246
|
+
* Merging and extraction run in-process through the build module; only the download needs a
|
|
247
|
+
* child process, for the same `.env` reason {@link pushClientBundle} documents.
|
|
248
|
+
* @function pullClientBundle
|
|
249
|
+
* @param {object} options
|
|
250
|
+
* @param {string} options.deployId - The deployment identifier.
|
|
251
|
+
* @param {string[]} [options.hosts] - Optional host filter; empty selects every configured host.
|
|
252
|
+
* @param {boolean} [options.dev] - Pull the development environment; defaults to production.
|
|
253
|
+
* @returns {Promise<{pulled: number, skipped: number}>} How many bundles were installed and skipped.
|
|
254
|
+
* @memberof clientBundle
|
|
255
|
+
*/
|
|
256
|
+
const expectedBundleBytes = ({ manifest, partPaths }) => {
|
|
257
|
+
const sizes = partPaths.map((partPath) => manifest[clientBundleManifestKey(partPath)]?.bytes);
|
|
258
|
+
return sizes.every((bytes) => Number.isFinite(bytes)) ? sizes.reduce((total, bytes) => total + bytes, 0) : undefined;
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* The manifest key a locally pulled part corresponds to. A pull writes the downloaded archive as
|
|
263
|
+
* `<key>.zip`, so the wrapper suffix is what has to come off to address the entry that produced it.
|
|
264
|
+
* @function clientBundleManifestKey
|
|
265
|
+
* @param {string} partPath - Local path of a pulled part.
|
|
266
|
+
* @returns {string} The tracked storage key.
|
|
267
|
+
* @memberof clientBundle
|
|
268
|
+
*/
|
|
269
|
+
const clientBundleManifestKey = (partPath) => partPath.replace(/^\.\//, '').replace(/\.zip$/i, '');
|
|
270
|
+
|
|
271
|
+
const pullClientBundle = async ({ deployId, hosts = [], dev = false }) => {
|
|
272
|
+
const baseCommand = cli('underpost', { local: true });
|
|
273
|
+
const env = dev ? 'development' : 'production';
|
|
274
|
+
|
|
275
|
+
const entries = clientBundleEntriesFactory({ deployId, hosts });
|
|
276
|
+
if (entries.length === 0) {
|
|
277
|
+
logger.error('no bundle routes resolved', { deployId, hosts });
|
|
278
|
+
return { pulled: 0, skipped: 0 };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
shellExec(`${baseCommand} app load --env ${env} --args deploy-id=${deployId}`);
|
|
282
|
+
fs.mkdirSync(CLIENT_BUNDLE_DIRECTORY, { recursive: true });
|
|
283
|
+
shellExec(
|
|
284
|
+
`${baseCommand} fs ${CLIENT_BUNDLE_DIRECTORY.replace(/^\.\//, '')} --tracked --deploy-id ${deployId} ` +
|
|
285
|
+
`--storage-id ${CLIENT_BUNDLE_STORAGE_ID} --pull --omit-unzip`,
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
const manifest = clientBundleManifestFactory(deployId);
|
|
289
|
+
|
|
290
|
+
let pulled = 0;
|
|
291
|
+
let skipped = 0;
|
|
292
|
+
for (const { host, routePath, buildId, zipPath, publicPath } of entries) {
|
|
293
|
+
const { partPaths, artifactPaths } = clientBundleArtifactPaths(zipPath);
|
|
294
|
+
if (artifactPaths.length === 0) {
|
|
295
|
+
logger.warn('bundle not found, skipping', { host, routePath, buildId, zipPath });
|
|
296
|
+
skipped++;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (partPaths.length > 0) {
|
|
301
|
+
const { mergedBytes } = await mergeClientBuildZip({ buildPrefix: zipPath, logger });
|
|
302
|
+
// The parts are reassembled blind, so a single stale or short one is only visible as a
|
|
303
|
+
// corrupt archive far downstream. The pushed sizes are what identify it here instead.
|
|
304
|
+
const expectedBytes = expectedBundleBytes({ manifest, partPaths });
|
|
305
|
+
if (expectedBytes !== undefined && expectedBytes !== mergedBytes)
|
|
306
|
+
throw new Error(
|
|
307
|
+
`Bundle ${buildId} reassembled to ${mergedBytes} bytes, expected ${expectedBytes} from ${partPaths.length} pushed parts`,
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
const { outputPath } = await unzipClientBuild({ buildPrefix: zipPath, logger });
|
|
311
|
+
|
|
312
|
+
fs.removeSync(zipPath);
|
|
313
|
+
for (const partPath of partPaths) fs.removeSync(partPath);
|
|
314
|
+
|
|
315
|
+
if (!fs.existsSync(outputPath)) {
|
|
316
|
+
logger.warn('extracted bundle directory not found, skipping', { host, routePath, outputPath });
|
|
317
|
+
skipped++;
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// The previous deployment's document root can be owned by the web server user, so the
|
|
322
|
+
// replace goes through sudo; the extracted tree this process just wrote does not.
|
|
323
|
+
shellExec(`sudo rm -rf ${publicPath}`);
|
|
324
|
+
if (routePath !== '/') shellExec(`sudo mkdir -p public/${host}`);
|
|
325
|
+
fs.copySync(outputPath, publicPath);
|
|
326
|
+
|
|
327
|
+
logger.info('pulled bundle', { host, routePath, publicPath });
|
|
328
|
+
pulled++;
|
|
329
|
+
}
|
|
330
|
+
return { pulled, skipped };
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
export {
|
|
334
|
+
CLIENT_BUNDLE_DIRECTORY,
|
|
335
|
+
clientBundleTrackedKeys,
|
|
336
|
+
clientBundleManifestFactory,
|
|
337
|
+
clientBundleManifestKey,
|
|
338
|
+
DEFAULT_SPLIT_MB,
|
|
339
|
+
clientBundleArtifactPaths,
|
|
340
|
+
clientBundleConfServerPath,
|
|
341
|
+
clientBundleEntriesFactory,
|
|
342
|
+
clientBundleHostFilter,
|
|
343
|
+
clientBundleSplitFlag,
|
|
344
|
+
clientBundleStorageFilePath,
|
|
345
|
+
pullClientBundle,
|
|
346
|
+
pushClientBundle,
|
|
347
|
+
};
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
import fs from 'fs-extra';
|
|
7
7
|
import nodemon from 'nodemon';
|
|
8
8
|
import dotenv from 'dotenv';
|
|
9
|
-
import { shellExec } from '../server/process.js';
|
|
10
|
-
import { loggerFactory } from '../server/logger.js';
|
|
9
|
+
import { shellExec } from '../server/runtime/process.js';
|
|
10
|
+
import { loggerFactory } from '../server/ops/logger.js';
|
|
11
11
|
import Underpost from '../index.js';
|
|
12
12
|
|
|
13
13
|
const logger = loggerFactory(import.meta);
|
|
@@ -32,7 +32,7 @@ const createClientDevServer = async (
|
|
|
32
32
|
const devClientEnvPath = `./engine-private/conf/${deployId}/.env.${process.env.NODE_ENV}.${subConf}-dev-client`;
|
|
33
33
|
if (fs.existsSync(devClientEnvPath)) dotenv.config({ path: devClientEnvPath, override: true });
|
|
34
34
|
|
|
35
|
-
await Underpost.
|
|
35
|
+
await Underpost.client.callback(deployId, `${subConf}-dev-client`.trim(), host, path);
|
|
36
36
|
|
|
37
37
|
shellExec(`node src/server ${deployId} ${subConf}-dev-client`.trim(), {
|
|
38
38
|
async: true,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @namespace clientIcons
|
|
5
5
|
*/
|
|
6
6
|
import { favicons } from 'favicons';
|
|
7
|
-
import { loggerFactory } from '../server/logger.js';
|
|
7
|
+
import { loggerFactory } from '../server/ops/logger.js';
|
|
8
8
|
import fs from 'fs-extra';
|
|
9
9
|
import { getCapVariableName } from '../client/components/core/CommonJs.js';
|
|
10
10
|
|
|
@@ -99,7 +99,7 @@ const buildIcons = async ({
|
|
|
99
99
|
|
|
100
100
|
const ssrPath = `./src/client/ssr/head/Pwa${getCapVariableName(publicClientId)}.js`;
|
|
101
101
|
if (!fs.existsSync(ssrPath))
|
|
102
|
-
fs.writeFileSync(ssrPath, '
|
|
102
|
+
fs.writeFileSync(ssrPath, 'SSRComponent = () => html`' + response.html.join(`\n`) + '`;', 'utf8');
|
|
103
103
|
} catch (error) {
|
|
104
104
|
logger.error(error.message); // Error description e.g. "An unknown error has occurred"
|
|
105
105
|
}
|
|
@@ -10,7 +10,7 @@ import vm from 'node:vm';
|
|
|
10
10
|
import Underpost from '../index.js';
|
|
11
11
|
|
|
12
12
|
import { srcFormatted } from './client-formatted.js';
|
|
13
|
-
import { loggerFactory } from '../server/logger.js';
|
|
13
|
+
import { loggerFactory } from '../server/ops/logger.js';
|
|
14
14
|
|
|
15
15
|
const logger = loggerFactory(import.meta);
|
|
16
16
|
|
|
@@ -22,10 +22,10 @@ const logger = loggerFactory(import.meta);
|
|
|
22
22
|
* @memberof ServerSideRendering
|
|
23
23
|
*/
|
|
24
24
|
const ssrFactory = async (componentPath = `./src/client/ssr/RootDocument.js`) => {
|
|
25
|
-
const context = {
|
|
25
|
+
const context = { SSRComponent: () => {}, npm_package_version: Underpost.version };
|
|
26
26
|
vm.createContext(context);
|
|
27
27
|
vm.runInContext(await srcFormatted(fs.readFileSync(componentPath, 'utf8')), context);
|
|
28
|
-
return context.
|
|
28
|
+
return context.SSRComponent;
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
/**
|
package/src/client.build.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import { loggerFactory } from './server/logger.js';
|
|
7
|
-
import { Config } from './server/conf.js';
|
|
8
|
-
import { ProcessController } from './server/process.js';
|
|
3
|
+
import { loggerFactory } from './server/ops/logger.js';
|
|
4
|
+
import { Config } from './server/runtime/conf.js';
|
|
5
|
+
import { ProcessController } from './server/runtime/process.js';
|
|
9
6
|
import { clientLiveBuild } from './client-builder/client-build-live.js';
|
|
10
7
|
|
|
11
8
|
await Config.build();
|
package/src/client.dev.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import { loggerFactory } from './server/logger.js';
|
|
7
|
-
import { ProcessController } from './server/process.js';
|
|
8
|
-
import { Config, buildClientStaticConf } from './server/conf.js';
|
|
3
|
+
import { loggerFactory } from './server/ops/logger.js';
|
|
4
|
+
import { ProcessController } from './server/runtime/process.js';
|
|
5
|
+
import { Config, buildClientStaticConf } from './server/runtime/conf.js';
|
|
9
6
|
import { createClientDevServer } from './client-builder/client-dev-server.js';
|
|
10
7
|
|
|
11
8
|
const logger = loggerFactory(import.meta);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { MongooseDB } from './mongo/MongooseDB.js';
|
|
2
|
-
import { loggerFactory } from '../server/logger.js';
|
|
2
|
+
import { loggerFactory } from '../server/ops/logger.js';
|
|
3
3
|
import { getCapVariableName } from '../client/components/core/CommonJs.js';
|
|
4
|
-
import { resolveHostKeyContext } from '../server/conf.js';
|
|
4
|
+
import { resolveHostKeyContext } from '../server/runtime/conf.js';
|
|
5
5
|
import Underpost from '../index.js';
|
|
6
|
+
import { latchRuntimeError } from '../server/runtime/runtime-status.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Module for managing and loading various database connections (e.g., Mongoose, MariaDB).
|
|
@@ -12,6 +13,60 @@ import Underpost from '../index.js';
|
|
|
12
13
|
|
|
13
14
|
const logger = loggerFactory(import.meta);
|
|
14
15
|
|
|
16
|
+
// Health watch defaults. `DB_HEALTH_INTERVAL_MS=0` disables the watch entirely.
|
|
17
|
+
const HEALTH_INTERVAL_MS = Number(process.env.DB_HEALTH_INTERVAL_MS ?? 15000);
|
|
18
|
+
const HEALTH_PING_TIMEOUT_MS = 5000;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Bounds a liveness probe that would otherwise wait on the driver's own connection timeout.
|
|
22
|
+
* @param {Promise<boolean>} probe - Provider probe resolving to liveness.
|
|
23
|
+
* @returns {Promise<boolean>} `false` when the probe throws or outlives the bound.
|
|
24
|
+
*/
|
|
25
|
+
const withPingTimeout = async (probe) => {
|
|
26
|
+
let timer;
|
|
27
|
+
try {
|
|
28
|
+
return await Promise.race([
|
|
29
|
+
probe,
|
|
30
|
+
new Promise((resolve) => {
|
|
31
|
+
timer = setTimeout(() => resolve(false), HEALTH_PING_TIMEOUT_MS);
|
|
32
|
+
}),
|
|
33
|
+
]);
|
|
34
|
+
} catch {
|
|
35
|
+
return false;
|
|
36
|
+
} finally {
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Per-provider health strategies, keyed by `db.provider`.
|
|
43
|
+
*
|
|
44
|
+
* A provider with no entry here gets no health watch and no reconnect: liveness probes,
|
|
45
|
+
* rebuild semantics, and teardown differ per client, so there is nothing generic to fall
|
|
46
|
+
* back on. Adding one (MariaDB, PostgreSQL, …) means adding its case here — the rest of the
|
|
47
|
+
* service dispatches through this table and needs no change.
|
|
48
|
+
*
|
|
49
|
+
* @type {object.<string, {isAlive: Function, rebuild: Function, close: Function}>}
|
|
50
|
+
*/
|
|
51
|
+
const PROVIDER_HEALTH = {
|
|
52
|
+
mongoose: {
|
|
53
|
+
isAlive: async (connection) => {
|
|
54
|
+
if (!connection || connection.readyState !== 1) return false;
|
|
55
|
+
return await withPingTimeout(
|
|
56
|
+
connection.db
|
|
57
|
+
.admin()
|
|
58
|
+
.command({ ping: 1 })
|
|
59
|
+
.then(() => true),
|
|
60
|
+
);
|
|
61
|
+
},
|
|
62
|
+
rebuild: async ({ apis, db }) => {
|
|
63
|
+
const connection = await MongooseDB.connect(db);
|
|
64
|
+
return { connection, models: await MongooseDB.loadModels({ conn: connection, apis }) };
|
|
65
|
+
},
|
|
66
|
+
close: async (connection) => await connection?.close(),
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
15
70
|
/**
|
|
16
71
|
* @class
|
|
17
72
|
* @alias DataBaseProviderService
|
|
@@ -126,6 +181,128 @@ class DataBaseProviderService {
|
|
|
126
181
|
});
|
|
127
182
|
}
|
|
128
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Timers for the per-provider health watch, keyed by `<contextKey>:<provider>`.
|
|
186
|
+
* @type {object.<string, {timer: object, attempts: number, rebuilding: boolean}>}
|
|
187
|
+
*/
|
|
188
|
+
static #watchers = {};
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Rebuilds a loaded provider in place: new connection, freshly bound models, old
|
|
192
|
+
* connection closed once the replacement is live.
|
|
193
|
+
*
|
|
194
|
+
* A reconnect is not the same as letting the driver heal itself. For mongoose: after a
|
|
195
|
+
* replica set is reconfigured the driver can hold a topology it will never accept a primary
|
|
196
|
+
* for again — it records the highest `setVersion`/`electionId` it has seen and rejects
|
|
197
|
+
* anything lower for the rest of the process — so only a new client recovers. Every consumer
|
|
198
|
+
* resolves models through this bucket on each call, so swapping the fields in place is
|
|
199
|
+
* enough; nothing has to be re-registered.
|
|
200
|
+
*
|
|
201
|
+
* Only providers present in {@link PROVIDER_HEALTH} can be rebuilt.
|
|
202
|
+
*
|
|
203
|
+
* @param {{host?: string, path?: string}|string} context - Context object or key.
|
|
204
|
+
* @param {string} [provider='mongoose'] - Provider name.
|
|
205
|
+
* @returns {Promise<boolean>} `true` when the provider was rebuilt.
|
|
206
|
+
*/
|
|
207
|
+
static async reconnect(context, provider = 'mongoose') {
|
|
208
|
+
const key = resolveHostKeyContext(context);
|
|
209
|
+
const strategy = PROVIDER_HEALTH[provider];
|
|
210
|
+
if (!strategy) {
|
|
211
|
+
logger.warn('No reconnect strategy for provider', { key, provider });
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const bucket = this.#instance[key]?.[provider];
|
|
216
|
+
if (!bucket?.rebuild) {
|
|
217
|
+
logger.warn('Cannot reconnect a provider that was never loaded', { key, provider });
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const previousConnection = bucket.connection;
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
const { connection, models } = await strategy.rebuild(bucket.rebuild);
|
|
225
|
+
|
|
226
|
+
bucket.connection = connection;
|
|
227
|
+
bucket.models = models;
|
|
228
|
+
logger.info('Database connection rebuilt', { key, provider });
|
|
229
|
+
} catch (error) {
|
|
230
|
+
logger.error('Database reconnect failed, keeping the existing connection', {
|
|
231
|
+
key,
|
|
232
|
+
provider,
|
|
233
|
+
error: error.message,
|
|
234
|
+
});
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Closed last and best-effort: the replacement is already serving, and a close that
|
|
239
|
+
// hangs on a dead server must not take the recovery with it.
|
|
240
|
+
try {
|
|
241
|
+
await strategy.close(previousConnection);
|
|
242
|
+
} catch (error) {
|
|
243
|
+
logger.warn('Could not close the replaced connection', { key, provider, error: error.message });
|
|
244
|
+
}
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Starts the health watch for a provider: pings on an interval and rebuilds the connection
|
|
250
|
+
* on every tick that finds it down, until it answers again. A provider with no
|
|
251
|
+
* {@link PROVIDER_HEALTH} entry is left unwatched.
|
|
252
|
+
* @param {string} key - Resolved context key.
|
|
253
|
+
* @param {string} provider - Provider name.
|
|
254
|
+
*/
|
|
255
|
+
static #startHealthWatch(key, provider) {
|
|
256
|
+
const strategy = PROVIDER_HEALTH[provider];
|
|
257
|
+
if (!strategy || !(HEALTH_INTERVAL_MS > 0)) return;
|
|
258
|
+
const watchKey = `${key}:${provider}`;
|
|
259
|
+
if (this.#watchers[watchKey]) return;
|
|
260
|
+
|
|
261
|
+
const state = { attempts: 0, rebuilding: false, timer: null };
|
|
262
|
+
state.timer = setInterval(async () => {
|
|
263
|
+
// A rebuild can outlast the interval (the driver's own connect timeout is longer), so
|
|
264
|
+
// ticks that land mid-attempt are skipped rather than stacking attempts.
|
|
265
|
+
if (state.rebuilding) return;
|
|
266
|
+
const bucket = this.#instance[key]?.[provider];
|
|
267
|
+
if (!bucket) return;
|
|
268
|
+
|
|
269
|
+
if (await strategy.isAlive(bucket.connection)) {
|
|
270
|
+
if (state.attempts > 0)
|
|
271
|
+
logger.info('Database connection restored', { key, provider, attempts: state.attempts });
|
|
272
|
+
state.attempts = 0;
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Every tick retries until the connection answers again: there is no attempt budget to
|
|
277
|
+
// exhaust, because giving up leaves the runtime permanently unable to reach its database.
|
|
278
|
+
state.rebuilding = true;
|
|
279
|
+
state.attempts++;
|
|
280
|
+
logger.warn('Database connection is down, rebuilding', { key, provider, attempt: state.attempts });
|
|
281
|
+
try {
|
|
282
|
+
await this.reconnect(key, provider);
|
|
283
|
+
} finally {
|
|
284
|
+
state.rebuilding = false;
|
|
285
|
+
}
|
|
286
|
+
}, HEALTH_INTERVAL_MS);
|
|
287
|
+
|
|
288
|
+
// Never hold the event loop open on account of a health check.
|
|
289
|
+
state.timer.unref?.();
|
|
290
|
+
this.#watchers[watchKey] = state;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Stops the health watch for a provider.
|
|
295
|
+
* @param {string} key - Resolved context key.
|
|
296
|
+
* @param {string} provider - Provider name.
|
|
297
|
+
*/
|
|
298
|
+
static #stopHealthWatch(key, provider) {
|
|
299
|
+
const watchKey = `${key}:${provider}`;
|
|
300
|
+
const state = this.#watchers[watchKey];
|
|
301
|
+
if (!state) return;
|
|
302
|
+
clearInterval(state.timer);
|
|
303
|
+
delete this.#watchers[watchKey];
|
|
304
|
+
}
|
|
305
|
+
|
|
129
306
|
/**
|
|
130
307
|
* Loads and initializes a database provider based on the configuration.
|
|
131
308
|
* If the connection is already loaded for the given host/path, it returns the existing instance.
|
|
@@ -166,9 +343,12 @@ class DataBaseProviderService {
|
|
|
166
343
|
const conn = await MongooseDB.connect(db);
|
|
167
344
|
this.#instance[key][db.provider] = {
|
|
168
345
|
dbSignature,
|
|
346
|
+
// Kept so the connection can be rebuilt later without the caller being present.
|
|
347
|
+
rebuild: { apis, host, path, db },
|
|
169
348
|
models: await MongooseDB.loadModels({ conn, apis }),
|
|
170
349
|
connection: conn,
|
|
171
350
|
close: async () => {
|
|
351
|
+
this.#stopHealthWatch(key, db.provider);
|
|
172
352
|
return await new Promise((resolve) => {
|
|
173
353
|
this.#instance[key][db.provider].connection.close().then(() => {
|
|
174
354
|
// logger.info('Mongoose connection is disconnected', db);
|
|
@@ -182,6 +362,9 @@ class DataBaseProviderService {
|
|
|
182
362
|
default:
|
|
183
363
|
break;
|
|
184
364
|
}
|
|
365
|
+
// Self-guarding: only providers with a PROVIDER_HEALTH entry are actually watched, so a
|
|
366
|
+
// provider added above does not have to remember to opt in here.
|
|
367
|
+
this.#startHealthWatch(key, db.provider);
|
|
185
368
|
return this.#instance[key][db.provider];
|
|
186
369
|
} catch (error) {
|
|
187
370
|
// Sanitize options to prevent credential exposure in logs
|
|
@@ -200,7 +383,7 @@ class DataBaseProviderService {
|
|
|
200
383
|
: {},
|
|
201
384
|
};
|
|
202
385
|
logger.error(error.message, { safeOptions });
|
|
203
|
-
|
|
386
|
+
latchRuntimeError();
|
|
204
387
|
return undefined;
|
|
205
388
|
}
|
|
206
389
|
}
|