@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.
Files changed (568) hide show
  1. package/.dockerignore +1 -0
  2. package/.env.example +17 -5
  3. package/.github/workflows/coverall.cyberia.ci.yml +39 -14
  4. package/.github/workflows/cyberia-client.cd.yml +5 -0
  5. package/.github/workflows/cyberia-server.cd.yml +5 -0
  6. package/.github/workflows/docker-image.cyberia-client.ci.yml +8 -5
  7. package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +8 -3
  8. package/.github/workflows/docker-image.cyberia-server.ci.yml +8 -5
  9. package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +8 -5
  10. package/.github/workflows/docker-image.engine-cyberia.ci.yml +15 -1
  11. package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +15 -1
  12. package/.github/workflows/engine-cyberia.cd.yml +3 -0
  13. package/.github/workflows/engine-cyberia.ci.yml +4 -5
  14. package/.github/workflows/ghpkg.ci.yml +35 -20
  15. package/.github/workflows/hardhat.ci.yml +12 -11
  16. package/.github/workflows/npmpkg.ci.yml +17 -15
  17. package/.github/workflows/publish.cyberia.ci.yml +44 -0
  18. package/.github/workflows/pwa-microservices-template-test.ci.yml +2 -2
  19. package/.github/workflows/release.cd.yml +1 -0
  20. package/.prettierignore +1 -0
  21. package/AGENTS.md +96 -0
  22. package/CHANGELOG.md +653 -2513
  23. package/CLI-HELP.md +323 -157
  24. package/Dockerfile +25 -9
  25. package/Dockerfile.dev +25 -9
  26. package/Dockerfile.test +5 -5
  27. package/README.md +48 -28
  28. package/bin/build.js +86 -41
  29. package/bin/build.template.js +8 -3
  30. package/bin/cyberia.js +1494 -1403
  31. package/bin/deploy.js +18 -104
  32. package/bin/index.js +1494 -1403
  33. package/compose.env +17 -5
  34. package/conf.js +98 -7
  35. package/deploy/cyberia-client/deploy.sh +46 -0
  36. package/deploy/cyberia-client/package.sh +17 -0
  37. package/deploy/cyberia-client/state.sh +15 -0
  38. package/deploy/cyberia-server/deploy.sh +46 -0
  39. package/deploy/cyberia-server/package.sh +17 -0
  40. package/deploy/cyberia-server/state.sh +15 -0
  41. package/deploy/dd-cyberia/deploy.sh +9 -29
  42. package/deploy/dd-cyberia/init.sh +38 -45
  43. package/deploy/dd-cyberia/package.sh +16 -0
  44. package/deploy/dd-cyberia/state.sh +14 -0
  45. package/deploy/dd-cyberia/sync-deploy.sh +133 -147
  46. package/deploy/lib/config.sh +29 -0
  47. package/deploy/lib/github-actions-logging.sh +610 -0
  48. package/deploy/lib/host.sh +194 -0
  49. package/deploy/lib/state.sh +65 -0
  50. package/deploy/pwa-microservices-template/deploy.sh +17 -42
  51. package/deploy/release/deploy.sh +9 -41
  52. package/deployment.yaml +7 -2
  53. package/docker-compose.yml +23 -17
  54. package/examples/static-page/README.md +19 -19
  55. package/examples/static-page/ssr-components/CustomPage.js +2 -2
  56. package/hardhat/hardhat.config.js +1 -6
  57. package/hardhat/package-lock.json +101 -40
  58. package/hardhat/package.json +2 -2
  59. package/manifests/alertmanager/deployment.yaml +81 -0
  60. package/manifests/blackbox-exporter/deployment.yaml +75 -0
  61. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +32 -7
  62. package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +32 -7
  63. package/manifests/deployment/dd-cyberia-development/deployment.yaml +7 -2
  64. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  65. package/manifests/deployment/mongo-express/deployment.yaml +46 -27
  66. package/manifests/deployment/mongo-express/kustomization.yaml +9 -0
  67. package/manifests/deployment/mongo-express/mongo-express-nodeport.yaml +18 -0
  68. package/manifests/deployment/mongo-express/service.yaml +16 -0
  69. package/manifests/deployment/mongo-express-no-auth/disable-mongodb-auth.yaml +16 -0
  70. package/manifests/deployment/mongo-express-no-auth/kustomization.yaml +16 -0
  71. package/manifests/grafana/deployment.yaml +26 -7
  72. package/manifests/grafana/kustomization.yaml +1 -0
  73. package/manifests/grafana/service.yaml +6 -1
  74. package/manifests/mariadb/pv.yaml +1 -0
  75. package/manifests/mongodb/statefulset.yaml +9 -2
  76. package/manifests/mysql/pv-pvc.yaml +14 -1
  77. package/manifests/postgresql/pv.yaml +12 -1
  78. package/manifests/postgresql/pvc.yaml +1 -0
  79. package/manifests/prometheus/deployment.yaml +18 -22
  80. package/package.json +87 -12
  81. package/scripts/event-monitor.sh +56 -0
  82. package/scripts/k3s-node-setup.sh +27 -14
  83. package/scripts/kubeadm-node-setup.sh +31 -11
  84. package/scripts/maas-setup.sh +8 -8
  85. package/scripts/selinux-normalize.sh +907 -0
  86. package/scripts/shutdown-machine.sh +843 -0
  87. package/scripts/test-monitor.sh +5 -5
  88. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +15 -10
  89. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +36 -5
  90. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +7 -2
  91. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +90 -186
  92. package/src/api/core/core.controller.js +1 -1
  93. package/src/api/core/core.router.js +2 -2
  94. package/src/api/core/core.service.js +2 -2
  95. package/src/api/crypto/crypto.controller.js +1 -1
  96. package/src/api/crypto/crypto.service.js +1 -1
  97. package/src/api/cyberia-action/cyberia-action.controller.js +1 -1
  98. package/src/api/cyberia-action/cyberia-action.router.js +3 -3
  99. package/src/api/cyberia-action/cyberia-action.service.js +5 -6
  100. package/src/api/cyberia-audio/cyberia-audio.controller.js +6 -0
  101. package/src/api/cyberia-audio/cyberia-audio.model.js +61 -0
  102. package/src/api/cyberia-audio/cyberia-audio.router.js +21 -0
  103. package/src/api/cyberia-audio/cyberia-audio.service.js +220 -0
  104. package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +2 -4
  105. package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +10 -19
  106. package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +13 -35
  107. package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +17 -34
  108. package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +1 -1
  109. package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +1 -1
  110. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +2 -2
  111. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +2 -2
  112. package/src/api/cyberia-entity/cyberia-entity.controller.js +1 -1
  113. package/src/api/cyberia-entity/cyberia-entity.model.js +2 -0
  114. package/src/api/cyberia-entity/cyberia-entity.router.js +1 -1
  115. package/src/api/cyberia-entity/cyberia-entity.service.js +3 -3
  116. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +8 -2
  117. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.model.js +31 -20
  118. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +17 -2
  119. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +468 -5
  120. package/src/api/cyberia-instance/cyberia-fallback-capture.js +502 -0
  121. package/src/api/cyberia-instance/cyberia-fallback-world.js +29 -40
  122. package/src/api/cyberia-instance/cyberia-instance-boot.service.js +5 -5
  123. package/src/api/cyberia-instance/cyberia-instance-items.js +98 -0
  124. package/src/api/cyberia-instance/cyberia-instance-map.service.js +35 -142
  125. package/src/api/cyberia-instance/cyberia-instance.controller.js +2 -5
  126. package/src/api/cyberia-instance/cyberia-instance.model.js +0 -14
  127. package/src/api/cyberia-instance/cyberia-instance.router.js +5 -18
  128. package/src/api/cyberia-instance/cyberia-instance.service.js +5 -45
  129. package/src/api/cyberia-instance/cyberia-world-generator.js +8 -13
  130. package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +1 -1
  131. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +49 -75
  132. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +1 -1
  133. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +76 -3
  134. package/src/api/cyberia-map/cyberia-map.controller.js +1 -1
  135. package/src/api/cyberia-map/cyberia-map.router.js +1 -1
  136. package/src/api/cyberia-map/cyberia-map.service.js +8 -4
  137. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.controller.js +31 -0
  138. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.model.js +56 -0
  139. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.router.js +33 -0
  140. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +189 -0
  141. package/src/api/cyberia-quest/cyberia-quest.controller.js +1 -1
  142. package/src/api/cyberia-quest/cyberia-quest.router.js +4 -4
  143. package/src/api/cyberia-quest/cyberia-quest.service.js +5 -5
  144. package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.js +1 -1
  145. package/src/api/cyberia-quest-progress/cyberia-quest-progress.model.js +1 -1
  146. package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +1 -1
  147. package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +2 -2
  148. package/src/api/cyberia-saga/cyberia-saga.controller.js +1 -1
  149. package/src/api/cyberia-saga/cyberia-saga.router.js +1 -1
  150. package/src/api/cyberia-saga/cyberia-saga.service.js +2 -2
  151. package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +403 -214
  152. package/src/api/cyberia-skill/cyberia-skill.controller.js +1 -1
  153. package/src/api/cyberia-skill/cyberia-skill.model.js +6 -6
  154. package/src/api/cyberia-skill/cyberia-skill.router.js +1 -1
  155. package/src/api/cyberia-skill/cyberia-skill.service.js +2 -2
  156. package/src/api/default/default.controller.js +1 -1
  157. package/src/api/default/default.router.js +1 -1
  158. package/src/api/default/default.service.js +2 -2
  159. package/src/api/document/document.controller.js +1 -1
  160. package/src/api/document/document.router.js +1 -1
  161. package/src/api/document/document.service.js +2 -2
  162. package/src/api/file/file.controller.js +1 -1
  163. package/src/api/file/file.ref.js +153 -0
  164. package/src/api/file/file.ref.json +11 -2
  165. package/src/api/file/file.router.js +1 -1
  166. package/src/api/file/file.service.js +2 -2
  167. package/src/api/instance/instance.controller.js +1 -1
  168. package/src/api/instance/instance.router.js +1 -1
  169. package/src/api/instance/instance.service.js +2 -2
  170. package/src/api/ipfs/ipfs.controller.js +1 -1
  171. package/src/api/ipfs/ipfs.router.js +2 -2
  172. package/src/api/ipfs/ipfs.service.js +2 -2
  173. package/src/api/object-layer/object-layer.controller.js +1 -1
  174. package/src/api/object-layer/object-layer.model.js +47 -12
  175. package/src/api/object-layer/object-layer.router.js +12 -5
  176. package/src/api/object-layer/object-layer.service.js +116 -123
  177. package/src/api/object-layer-render-frames/object-layer-render-frames.controller.js +1 -1
  178. package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +1 -1
  179. package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +2 -2
  180. package/src/api/test/test.controller.js +1 -1
  181. package/src/api/test/test.service.js +1 -1
  182. package/src/api/user/guest.service.js +3 -3
  183. package/src/api/user/user.controller.js +1 -1
  184. package/src/api/user/user.router.js +2 -2
  185. package/src/api/user/user.service.js +4 -4
  186. package/src/api.js +5 -9
  187. package/src/cli/app.js +402 -0
  188. package/src/cli/baremetal.js +30 -35
  189. package/src/cli/client.js +306 -0
  190. package/src/cli/cloud-init.js +3 -3
  191. package/src/cli/cluster.js +191 -82
  192. package/src/cli/db.js +120 -68
  193. package/src/cli/deploy.js +157 -75
  194. package/src/cli/docker-compose.js +5 -5
  195. package/src/cli/domains.js +184 -0
  196. package/src/cli/dotenv-store.js +143 -0
  197. package/src/cli/event.js +2281 -0
  198. package/src/cli/fs-selection.js +115 -0
  199. package/src/cli/fs.js +159 -343
  200. package/src/cli/host.js +672 -0
  201. package/src/cli/image.js +4 -6
  202. package/src/cli/index.js +343 -151
  203. package/src/cli/ipfs.js +34 -30
  204. package/src/cli/kickstart.js +1 -1
  205. package/src/cli/kubectl.js +218 -41
  206. package/src/cli/lxd.js +3 -3
  207. package/src/cli/monitor.js +1359 -26
  208. package/src/cli/package.js +90 -0
  209. package/src/cli/release.js +16 -12
  210. package/src/cli/repository.js +586 -363
  211. package/src/cli/run.js +398 -843
  212. package/src/cli/secrets.js +2158 -964
  213. package/src/cli/ssh.js +478 -279
  214. package/src/cli/state.js +394 -0
  215. package/src/cli/static.js +2 -2
  216. package/src/cli/system.js +1 -1
  217. package/src/cli/test.js +203 -125
  218. package/src/cli/vultr.js +61 -21
  219. package/src/cli/wireguard.js +1112 -306
  220. package/src/client/components/core/CalendarCore.js +53 -96
  221. package/src/client/components/core/Docs.js +327 -303
  222. package/src/client/components/core/FileExplorer.js +0 -21
  223. package/src/client/components/core/Modal.js +56 -1
  224. package/src/client/components/core/PanelForm.js +4 -4
  225. package/src/client/components/core/Repository.js +56 -0
  226. package/src/client/components/core/Translate.js +7 -0
  227. package/src/client/components/core/{FullScreen.js → ViewModeController.js} +42 -39
  228. package/src/client/components/core/Wallet.js +0 -11
  229. package/src/client/components/core/Worker.js +7 -1
  230. package/src/client/components/cryptokoyn/SettingsCryptokoyn.js +2 -2
  231. package/src/client/components/cyberia/ActionEngineCyberia.js +12 -36
  232. package/src/client/components/cyberia/EntityEngineCyberia.js +492 -121
  233. package/src/client/components/cyberia/InstanceEngineCyberia.js +129 -160
  234. package/src/client/components/cyberia/InstanceSelectionView.js +7 -1
  235. package/src/client/components/cyberia/MapEngineCyberia.js +175 -69
  236. package/src/client/components/cyberia/ObjectLayerEngine.js +1011 -97
  237. package/src/client/components/cyberia/ObjectLayerEngineModal.js +81 -155
  238. package/src/client/components/cyberia/ObjectLayerEngineViewer.js +113 -98
  239. package/src/client/components/cyberia/SharedDefaultsCyberia.js +215 -56
  240. package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +2 -35
  241. package/src/client/components/cyberia-portal/CssCyberiaPortal.js +1 -1
  242. package/src/client/components/cyberia-portal/MainBodyCyberiaPortal.js +712 -76
  243. package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +2 -2
  244. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +0 -4
  245. package/src/client/components/default/AppShellDefault.js +1 -1
  246. package/src/client/components/default/SettingsDefault.js +2 -2
  247. package/src/client/components/itemledger/SettingsItemledger.js +2 -2
  248. package/src/client/components/underpost/AppShellUnderpost.js +3 -2
  249. package/src/client/components/underpost/SettingsUnderpost.js +2 -2
  250. package/src/client/public/cyberia-docs/ARCHITECTURE.md +15 -15
  251. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +313 -30
  252. package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +51 -29
  253. package/src/client/public/cyberia-docs/CYBERIA-SAGA.md +52 -53
  254. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +16 -11
  255. package/src/client/public/cyberia-docs/CYBERIA.md +48 -28
  256. package/src/client/public/cyberia-docs/ENTITY-PROFILE.md +11 -5
  257. package/src/client/public/cyberia-docs/HARDHAT-MODULE.md +23 -6
  258. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  259. package/src/client/public/cyberia-docs/STATS-PROGRESSION.md +217 -0
  260. package/src/client/public/cyberia-docs/WHITE-PAPER.md +20 -12
  261. package/src/client/public/default/android-chrome-144x144.png +0 -0
  262. package/src/client/public/default/android-chrome-192x192.png +0 -0
  263. package/src/client/public/default/android-chrome-256x256.png +0 -0
  264. package/src/client/public/default/android-chrome-36x36.png +0 -0
  265. package/src/client/public/default/android-chrome-384x384.png +0 -0
  266. package/src/client/public/default/android-chrome-48x48.png +0 -0
  267. package/src/client/public/default/android-chrome-512x512.png +0 -0
  268. package/src/client/public/default/android-chrome-72x72.png +0 -0
  269. package/src/client/public/default/android-chrome-96x96.png +0 -0
  270. package/src/client/public/default/apple-touch-icon-1024x1024.png +0 -0
  271. package/src/client/public/default/apple-touch-icon-114x114.png +0 -0
  272. package/src/client/public/default/apple-touch-icon-120x120.png +0 -0
  273. package/src/client/public/default/apple-touch-icon-144x144.png +0 -0
  274. package/src/client/public/default/apple-touch-icon-152x152.png +0 -0
  275. package/src/client/public/default/apple-touch-icon-167x167.png +0 -0
  276. package/src/client/public/default/apple-touch-icon-180x180.png +0 -0
  277. package/src/client/public/default/apple-touch-icon-57x57.png +0 -0
  278. package/src/client/public/default/apple-touch-icon-60x60.png +0 -0
  279. package/src/client/public/default/apple-touch-icon-72x72.png +0 -0
  280. package/src/client/public/default/apple-touch-icon-76x76.png +0 -0
  281. package/src/client/public/default/apple-touch-icon-precomposed.png +0 -0
  282. package/src/client/public/default/apple-touch-icon.png +0 -0
  283. package/src/client/public/default/apple-touch-startup-image-1125x2436.png +0 -0
  284. package/src/client/public/default/apple-touch-startup-image-1136x640.png +0 -0
  285. package/src/client/public/default/apple-touch-startup-image-1170x2532.png +0 -0
  286. package/src/client/public/default/apple-touch-startup-image-1179x2556.png +0 -0
  287. package/src/client/public/default/apple-touch-startup-image-1242x2208.png +0 -0
  288. package/src/client/public/default/apple-touch-startup-image-1242x2688.png +0 -0
  289. package/src/client/public/default/apple-touch-startup-image-1284x2778.png +0 -0
  290. package/src/client/public/default/apple-touch-startup-image-1290x2796.png +0 -0
  291. package/src/client/public/default/apple-touch-startup-image-1334x750.png +0 -0
  292. package/src/client/public/default/apple-touch-startup-image-1488x2266.png +0 -0
  293. package/src/client/public/default/apple-touch-startup-image-1536x2048.png +0 -0
  294. package/src/client/public/default/apple-touch-startup-image-1620x2160.png +0 -0
  295. package/src/client/public/default/apple-touch-startup-image-1640x2160.png +0 -0
  296. package/src/client/public/default/apple-touch-startup-image-1668x2224.png +0 -0
  297. package/src/client/public/default/apple-touch-startup-image-1668x2388.png +0 -0
  298. package/src/client/public/default/apple-touch-startup-image-1792x828.png +0 -0
  299. package/src/client/public/default/apple-touch-startup-image-2048x1536.png +0 -0
  300. package/src/client/public/default/apple-touch-startup-image-2048x2732.png +0 -0
  301. package/src/client/public/default/apple-touch-startup-image-2160x1620.png +0 -0
  302. package/src/client/public/default/apple-touch-startup-image-2160x1640.png +0 -0
  303. package/src/client/public/default/apple-touch-startup-image-2208x1242.png +0 -0
  304. package/src/client/public/default/apple-touch-startup-image-2224x1668.png +0 -0
  305. package/src/client/public/default/apple-touch-startup-image-2266x1488.png +0 -0
  306. package/src/client/public/default/apple-touch-startup-image-2388x1668.png +0 -0
  307. package/src/client/public/default/apple-touch-startup-image-2436x1125.png +0 -0
  308. package/src/client/public/default/apple-touch-startup-image-2532x1170.png +0 -0
  309. package/src/client/public/default/apple-touch-startup-image-2556x1179.png +0 -0
  310. package/src/client/public/default/apple-touch-startup-image-2688x1242.png +0 -0
  311. package/src/client/public/default/apple-touch-startup-image-2732x2048.png +0 -0
  312. package/src/client/public/default/apple-touch-startup-image-2778x1284.png +0 -0
  313. package/src/client/public/default/apple-touch-startup-image-2796x1290.png +0 -0
  314. package/src/client/public/default/apple-touch-startup-image-640x1136.png +0 -0
  315. package/src/client/public/default/apple-touch-startup-image-750x1334.png +0 -0
  316. package/src/client/public/default/apple-touch-startup-image-828x1792.png +0 -0
  317. package/src/client/public/default/assets/background/white.jpg +0 -0
  318. package/src/client/public/default/browserconfig.xml +1 -1
  319. package/src/client/public/default/favicon-16x16.png +0 -0
  320. package/src/client/public/default/favicon-32x32.png +0 -0
  321. package/src/client/public/default/favicon-48x48.png +0 -0
  322. package/src/client/public/default/favicon.ico +0 -0
  323. package/src/client/public/default/manifest.webmanifest +5 -5
  324. package/src/client/public/default/mstile-144x144.png +0 -0
  325. package/src/client/public/default/mstile-150x150.png +0 -0
  326. package/src/client/public/default/mstile-310x150.png +0 -0
  327. package/src/client/public/default/mstile-310x310.png +0 -0
  328. package/src/client/public/default/mstile-70x70.png +0 -0
  329. package/src/client/public/default/yandex-browser-50x50.png +0 -0
  330. package/src/client/public/default/yandex-browser-manifest.json +1 -1
  331. package/src/client/services/cyberia-audio/cyberia-audio.service.js +99 -0
  332. package/src/client/services/cyberia-entity-type-default/cyberia-entity-type-default.service.js +38 -0
  333. package/src/client/services/cyberia-instance/cyberia-instance.management.js +5 -4
  334. package/src/client/services/cyberia-instance/cyberia-instance.service.js +0 -40
  335. package/src/client/services/cyberia-map/cyberia-map.management.js +5 -4
  336. package/src/client/services/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +138 -0
  337. package/src/client/services/object-layer/object-layer.management.js +6 -10
  338. package/src/client/services/object-layer/object-layer.service.js +3 -1
  339. package/src/client/ssr/RootDocument.js +1 -1
  340. package/src/client/ssr/body/404.js +1 -1
  341. package/src/client/ssr/body/500.js +1 -1
  342. package/src/client/ssr/body/CacheControl.js +1 -1
  343. package/src/client/ssr/body/CyberiaDefaultSplashScreen.js +1 -1
  344. package/src/client/ssr/body/DefaultSplashScreen.js +1 -1
  345. package/src/client/ssr/body/SwaggerDarkMode.js +1 -1
  346. package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +1 -1
  347. package/src/client/ssr/head/CryptokoynScripts.js +1 -1
  348. package/src/client/ssr/head/Css.js +1 -1
  349. package/src/client/ssr/head/CyberiaPortalScripts.js +1 -1
  350. package/src/client/ssr/head/DefaultScripts.js +1 -1
  351. package/src/client/ssr/head/ItemledgerScripts.js +1 -1
  352. package/src/client/ssr/head/Microdata.js +1 -1
  353. package/src/client/ssr/head/Production.js +1 -1
  354. package/src/client/ssr/head/Pwa.js +1 -1
  355. package/src/client/ssr/head/PwaItemledger.js +1 -1
  356. package/src/client/ssr/head/Seo.js +1 -1
  357. package/src/client/ssr/head/UnderpostScripts.js +1 -1
  358. package/src/client/ssr/mailer/DefaultRecoverEmail.js +1 -1
  359. package/src/client/ssr/mailer/DefaultVerifyEmail.js +1 -1
  360. package/src/client/ssr/views/Cyberia404.js +7 -5
  361. package/src/client/ssr/views/CyberiaServerMetrics.js +1 -1
  362. package/src/client/ssr/views/Maintenance.js +1 -1
  363. package/src/client/ssr/views/NoNetworkConnection.js +1 -1
  364. package/src/client/ssr/views/Test.js +1 -1
  365. package/src/client/sw/core.sw.js +20 -14
  366. package/src/client-builder/client-build-docs.js +92 -77
  367. package/src/client-builder/client-build-live.js +2 -2
  368. package/src/client-builder/client-build.js +105 -74
  369. package/src/client-builder/client-bundle.js +347 -0
  370. package/src/client-builder/client-dev-server.js +3 -3
  371. package/src/client-builder/client-icons.js +2 -2
  372. package/src/client-builder/ssr.js +3 -3
  373. package/src/client.build.js +3 -6
  374. package/src/client.dev.js +3 -6
  375. package/src/db/DataBaseProvider.js +186 -3
  376. package/src/db/mariadb/MariaDB.js +3 -2
  377. package/src/db/mongo/MongoBootstrap.js +273 -101
  378. package/src/db/mongo/MongoExpress.js +187 -0
  379. package/src/db/mongo/MongooseDB.js +7 -8
  380. package/src/{server/valkey.js → db/valkey/Valkey.js} +5 -4
  381. package/src/grpc/cyberia/grpc-server.js +6 -8
  382. package/src/index.js +79 -23
  383. package/src/mailer/EmailRender.js +2 -2
  384. package/src/mailer/MailerInterceptor.js +87 -0
  385. package/src/mailer/MailerProvider.js +50 -6
  386. package/src/projects/cyberia/atlas-sprite-sheet-generator.js +144 -147
  387. package/src/projects/cyberia/atlas-sprite-sheet-store.js +391 -0
  388. package/src/projects/cyberia/besu-genesis-generator.js +8 -17
  389. package/src/projects/cyberia/boot-contract-fixtures.js +32 -0
  390. package/src/projects/cyberia/catalog-cyberia.js +54 -10
  391. package/src/projects/cyberia/gemini-client.js +4 -4
  392. package/src/projects/cyberia/generate-saga.js +5 -12
  393. package/src/projects/cyberia/hot-reload-trigger.js +18 -29
  394. package/src/projects/cyberia/instance-backup.js +210 -0
  395. package/src/projects/cyberia/instance-data.js +178 -258
  396. package/src/projects/cyberia/ipfs-client.js +4 -4
  397. package/src/projects/cyberia/map-preview-generator.js +33 -69
  398. package/src/projects/cyberia/object-layer.js +153 -320
  399. package/src/projects/cyberia/seed-audio.js +139 -0
  400. package/src/projects/cyberia/semantic-layer-generator-skin.js +2 -8
  401. package/src/projects/cyberia/semantic-layer-generator.js +3 -19
  402. package/src/projects/cyberia/stat-balance.js +81 -0
  403. package/src/projects/cyberia/stat-commands.js +23 -0
  404. package/src/projects/cyberia/stat-contract-generator.js +101 -0
  405. package/src/projects/underpost/catalog-underpost.js +9 -1
  406. package/src/proxy.js +4 -7
  407. package/src/runtime/cyberia-client/Dockerfile +17 -8
  408. package/src/runtime/cyberia-client/Dockerfile.dev +13 -4
  409. package/src/runtime/cyberia-server/Dockerfile +18 -6
  410. package/src/runtime/cyberia-server/Dockerfile.dev +16 -3
  411. package/src/runtime/engine-cyberia/Dockerfile +25 -9
  412. package/src/runtime/engine-cyberia/Dockerfile.dev +25 -9
  413. package/src/runtime/engine-cyberia/Dockerfile.test +5 -5
  414. package/src/runtime/engine-cyberia/compose.env +17 -5
  415. package/src/runtime/engine-cyberia/docker-compose.yml +23 -17
  416. package/src/runtime/express/Express.js +32 -27
  417. package/src/runtime/lampp/Lampp.js +22 -70
  418. package/src/runtime/nginx/Nginx.js +1 -1
  419. package/src/runtime/wp/Wp.js +16 -48
  420. package/src/server/{catalog.js → build/catalog.js} +15 -5
  421. package/src/server/build/coverage.js +188 -0
  422. package/src/server/build/execution.js +402 -0
  423. package/src/server/build/package.js +508 -0
  424. package/src/server/build/testing.js +578 -0
  425. package/src/server/{dns.js → network/dns.js} +144 -11
  426. package/src/server/{forward-proxy.js → network/forward-proxy.js} +10 -18
  427. package/src/server/{middlewares.js → network/middlewares.js} +11 -5
  428. package/src/server/network/node-capability.js +98 -0
  429. package/src/server/{peer.js → network/peer.js} +3 -3
  430. package/src/server/{proxy.js → network/proxy.js} +7 -13
  431. package/src/server/network/router.js +356 -0
  432. package/src/server/{tls.js → network/tls.js} +2 -2
  433. package/src/server/{underpost-compression.js → network/underpost-compression.js} +1 -1
  434. package/src/server/{underpost-gateway.js → network/underpost-gateway.js} +7 -7
  435. package/src/server/{underpost-ingress.js → network/underpost-ingress.js} +2 -2
  436. package/src/server/{cri.js → ops/cri.js} +2 -2
  437. package/src/server/{cron.js → ops/cron.js} +349 -74
  438. package/src/server/ops/event-notification.js +284 -0
  439. package/src/server/{logger.js → ops/logger.js} +143 -23
  440. package/src/server/ops/monitoring.js +1724 -0
  441. package/src/server/{systemd.js → ops/systemd.js} +55 -1
  442. package/src/server/{conf.js → runtime/conf.js} +516 -423
  443. package/src/server/runtime/config-scope.js +215 -0
  444. package/src/server/runtime/environment.js +149 -0
  445. package/src/server/{process.js → runtime/process.js} +153 -12
  446. package/src/server/{runtime-status.js → runtime/runtime-status.js} +155 -16
  447. package/src/server/{runtime.js → runtime/runtime.js} +12 -8
  448. package/src/server/{start.js → runtime/start.js} +128 -39
  449. package/src/server/{auth.js → security/auth.js} +7 -8
  450. package/src/server/security/container-storage.js +382 -0
  451. package/src/server/{crypto.js → security/crypto.js} +1 -1
  452. package/src/server/{selinux.js → security/selinux.js} +1 -1
  453. package/src/server/{backup.js → storage/backup.js} +17 -8
  454. package/src/server/{data-query.js → storage/data-query.js} +17 -1
  455. package/src/server/storage/downloader.js +121 -0
  456. package/src/server/storage/repository.js +68 -0
  457. package/src/server/storage/zip.js +153 -0
  458. package/src/server.js +4 -7
  459. package/src/ws/IoInterface.js +1 -1
  460. package/src/ws/IoServer.js +1 -1
  461. package/src/ws/core/core.ws.connection.js +1 -1
  462. package/src/ws/core/core.ws.emit.js +1 -1
  463. package/src/ws/core/core.ws.server.js +1 -1
  464. package/src/ws/default/default.ws.connection.js +1 -1
  465. package/src/ws/default/default.ws.emit.js +1 -1
  466. package/src/ws/default/default.ws.server.js +1 -1
  467. package/test/e2e/event-e2e-public-ingress-down.js +45 -0
  468. package/test/e2e/event-e2e-wireguard-server-down.js +38 -0
  469. package/test/e2e/event-e2e-wireguard-spoke-down.js +40 -0
  470. package/test/{api.test.js → integration/app/api.test.js} +33 -14
  471. package/test/integration/app/cyberia/atlas-sprite-sheet-store.test.js +402 -0
  472. package/test/integration/app/cyberia/atlas-sprite-sheet.test.js +86 -0
  473. package/test/integration/app/cyberia/cyberia-cli-plain-reads.test.js +64 -0
  474. package/test/integration/app/cyberia/cyberia-entity-type-default.test.js +842 -0
  475. package/test/integration/app/cyberia/cyberia-instance-conf-coerce.test.js +53 -0
  476. package/test/{cyberia-instance-conf-defaults.test.js → integration/app/cyberia/cyberia-instance-conf-defaults.test.js} +17 -23
  477. package/test/integration/app/cyberia/cyberia-instance-items.test.js +90 -0
  478. package/test/integration/app/cyberia/cyberia-load.test.js +466 -0
  479. package/test/integration/app/cyberia/cyberia-map-audio-conf.test.js +256 -0
  480. package/test/integration/app/cyberia/cyberia-stats.test.js +150 -0
  481. package/test/integration/app/cyberia/fallback-world-capture.test.js +338 -0
  482. package/test/integration/app/cyberia/object-layer-item-id.test.js +327 -0
  483. package/test/integration/app/cyberia/object-layer-item-selection.test.js +56 -0
  484. package/test/integration/app/cyberia/object-layer-natural-key.test.js +32 -0
  485. package/test/integration/app/cyberia/seed-audio.test.js +275 -0
  486. package/test/{shape-generator.test.js → integration/app/cyberia/shape-generator.test.js} +1 -1
  487. package/test/integration/infra/1-security/config-scope.test.js +305 -0
  488. package/test/integration/infra/1-security/container-storage.test.js +356 -0
  489. package/test/integration/infra/1-security/in-pod-cli-surface.test.js +86 -0
  490. package/test/integration/infra/1-security/secret-onboarding.test.js +930 -0
  491. package/test/{selinux.test.js → integration/infra/1-security/selinux.test.js} +2 -4
  492. package/test/{sops-secret-store.test.js → integration/infra/1-security/sops-secret-store.test.js} +415 -61
  493. package/test/integration/infra/1-security/systemd-service.test.js +70 -0
  494. package/test/integration/infra/1-security/underpost-config-secret.test.js +367 -0
  495. package/test/integration/infra/2-network/dns-firewall.test.js +566 -0
  496. package/test/integration/infra/2-network/node-capability.test.js +182 -0
  497. package/test/integration/infra/2-network/wireguard-cli.test.js +1187 -0
  498. package/test/{wireguard-edge.test.js → integration/infra/2-network/wireguard-edge.test.js} +649 -55
  499. package/test/{cluster-instances.test.js → integration/infra/3-cluster/cluster-instances.test.js} +11 -15
  500. package/test/{deploy-node-placement.test.js → integration/infra/3-cluster/deploy-node-placement.test.js} +3 -3
  501. package/test/integration/infra/3-cluster/docker-compose-stack.test.js +313 -0
  502. package/test/integration/infra/3-cluster/mongo-express-deploy.test.js +156 -0
  503. package/test/integration/infra/4-ingress/api-cross-origin.test.js +79 -0
  504. package/test/integration/infra/4-ingress/deploy-routes.test.js +379 -0
  505. package/test/integration/infra/4-ingress/gateway-static-assets.test.js +453 -0
  506. package/test/{instance-traffic-plan.test.js → integration/infra/4-ingress/instance-traffic-plan.test.js} +10 -6
  507. package/test/{underpost-gateway.test.js → integration/infra/4-ingress/underpost-gateway.test.js} +6 -6
  508. package/test/{underpost-ingress.test.js → integration/infra/4-ingress/underpost-ingress.test.js} +4 -4
  509. package/test/integration/infra/5-observability/cron-jobs.test.js +652 -0
  510. package/test/{deploy-monitor.test.js → integration/infra/5-observability/deploy-monitor.test.js} +29 -31
  511. package/test/integration/infra/5-observability/event-notification.test.js +197 -0
  512. package/test/integration/infra/5-observability/event-remediation.test.js +502 -0
  513. package/test/integration/infra/5-observability/event-targets.test.js +1174 -0
  514. package/test/integration/infra/5-observability/monitoring-stack.test.js +902 -0
  515. package/test/support/shell-harness.js +73 -0
  516. package/test/unit/build-template.test.js +97 -0
  517. package/test/unit/build-workflow-offline.test.js +132 -0
  518. package/test/unit/catalog.test.js +71 -0
  519. package/test/unit/client-build-docs.test.js +361 -0
  520. package/test/unit/client-bundle.test.js +313 -0
  521. package/test/unit/conf-loading.test.js +303 -0
  522. package/test/unit/conf-resolution.test.js +996 -0
  523. package/test/unit/coverage-artifact.test.js +221 -0
  524. package/test/{crypto.test.js → unit/crypto.test.js} +1 -1
  525. package/test/unit/cyberia/instance-backup.test.js +183 -0
  526. package/test/unit/cyberia/instance-object-layer-items.test.js +46 -0
  527. package/test/unit/cyberia/publish-workflow.test.js +22 -0
  528. package/test/unit/cyberia/stat-balance.test.js +69 -0
  529. package/test/unit/deploy-env-resolution.test.js +38 -0
  530. package/test/unit/deploy-log-table.test.js +518 -0
  531. package/test/unit/downloader-integrity.test.js +98 -0
  532. package/test/unit/execution-profiles.test.js +219 -0
  533. package/test/unit/file-reference-registry.test.js +141 -0
  534. package/test/unit/fleet-sync-source.test.js +149 -0
  535. package/test/unit/fs-storage-paths.test.js +596 -0
  536. package/test/unit/logger-redaction.test.js +141 -0
  537. package/test/unit/package.test.js +457 -0
  538. package/test/unit/prepare-host.test.js +382 -0
  539. package/test/unit/process-environment.test.js +18 -0
  540. package/test/unit/propagation-message.test.js +78 -0
  541. package/test/unit/redeploy-plan.test.js +24 -0
  542. package/test/unit/release-bump.test.js +474 -0
  543. package/test/unit/ssh-output-redaction.test.js +28 -0
  544. package/test/unit/start-options.test.js +46 -0
  545. package/test/unit/test-tiers.test.js +220 -0
  546. package/test/unit/zip-archive.test.js +142 -0
  547. package/vitest.config.js +66 -0
  548. package/.nycrc +0 -9
  549. package/deploy/lib/logging.sh +0 -96
  550. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +0 -48
  551. package/manifests/mongodb/pv-pvc.yaml +0 -59
  552. package/scripts/coverall-test-cyberia.sh +0 -24
  553. package/scripts/coverall-test.sh +0 -24
  554. package/scripts/link-local-underpost-cli.sh +0 -6
  555. package/src/api/cyberia-instance/cyberia-fallback-default-items.js +0 -63
  556. package/src/api/user/user.build.js +0 -16
  557. package/src/cli/env.js +0 -177
  558. package/src/client/components/core/Blockchain.js +0 -41
  559. package/src/client/components/core/Blog.js +0 -9
  560. package/src/client/components/core/KeyboardAvoidance.js +0 -145
  561. package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +0 -368
  562. package/src/client/public/default/assets/background/dark.jpg +0 -0
  563. package/src/client/public/default/assets/background/white0-min.jpg +0 -0
  564. package/src/client/public/default/assets/background/white0.jpg +0 -0
  565. package/src/client/public/doc/favicon.ico +0 -0
  566. package/src/client/public/doc/sitemap +0 -148
  567. package/src/server/downloader.js +0 -73
  568. package/src/server/environment.js +0 -98
@@ -1,6 +1,12 @@
1
- import { buildCrudController } from '../../server/middlewares.js';
1
+ import { buildCrudController, serviceHandler } from '../../server/network/middlewares.js';
2
2
  import { CyberiaEntityTypeDefaultService } from './cyberia-entity-type-default.service.js';
3
3
 
4
- const CyberiaEntityTypeDefaultController = buildCrudController(CyberiaEntityTypeDefaultService);
4
+ const CyberiaEntityTypeDefaultController = buildCrudController(CyberiaEntityTypeDefaultService, {
5
+ // Which instances run on this default. The link is stored on CyberiaInstanceConf.entityDefaults,
6
+ // so reading it is an ordinary conf read; only the write needs a route of its own.
7
+ setInstances: serviceHandler(CyberiaEntityTypeDefaultService.setInstances),
8
+ // Point one instance's conf at every default its own map content needs.
9
+ sync: serviceHandler(CyberiaEntityTypeDefaultService.sync),
10
+ });
5
11
 
6
12
  export { CyberiaEntityTypeDefaultController };
@@ -6,24 +6,27 @@ import { Schema, model } from 'mongoose';
6
6
  // item defaults that ship in cyberia-server-defaults.js (ENTITY_TYPE_DEFAULTS).
7
7
  // A document binds an entity category (entityType) to the item ids the runtime
8
8
  // rotates through by lifecycle state:
9
- // liveItemIds — ObjectLayer item ids while the entity is alive.
10
- // deadItemIds — ids swapped in on death / ghost state.
11
- // dropItemIds — ids granted to the killer on depletion (resources).
12
- // defaultObjectLayers seed inventory rows ({ itemId, active, quantity }).
9
+ // liveItemIds — ObjectLayer item ids while the entity is alive.
10
+ // deadItemIds — ids swapped in on death / ghost state.
11
+ // dropItemIds — ids granted to the killer on depletion (resources).
12
+ // inventoryItemsIds ids the entity carries but no lifecycle state activates.
13
+ // overrideItemsIdsState — per-id overrides of what the lists would otherwise derive.
13
14
  //
14
- // Resolution rule (authoritative): defaults are looked up by the entity's ACTIVE
15
- // itemId (usually the skin) — the system finds the document whose `liveItemIds`
16
- // contains that itemId, then applies the document's deadItemIds / dropItemIds /
17
- // defaultObjectLayers to drive what is shown as the entity's state changes.
15
+ // The three lifecycle lists are discriminators, not separate inventories. The entity holds one
16
+ // inventory the union of all four lists and the runtime activates the slots the context
17
+ // calls for. An id's active flag and quantity follow from the list it belongs to, so neither is
18
+ // stored per row `overrideItemsIdsState` is the one exception, and it only ever adjusts an id
19
+ // the lists already carry. See resolveEntityInventory() in cyberia-server-defaults.js.
18
20
  //
19
- // Resolution (subset matching): a document matches an entity when ALL of its
20
- // `liveItemIds` are present in the entity's active item ids, and the MOST SPECIFIC
21
- // match wins the document requiring the largest item set. This lets the same
22
- // skin map to different defaults by its full active set, e.g.
21
+ // Resolution: a document matches an entity when every one of its `liveItemIds`
22
+ // is in the entity's active item ids. The most specific match wins, the document
23
+ // that requires the largest item set. Its remaining lists then drive the entity
24
+ // through its states. So one skin can map to different defaults by its full
25
+ // active set, e.g.
23
26
  // { bot, liveItemIds:[purple, atlas_pistol_mk2], behavior:hostile }
24
27
  // { bot, liveItemIds:[purple], behavior:passive }
25
28
  // where a purple bot carrying the pistol is hostile and a bare purple bot is
26
- // passive. The Go simulation (game/entity_defaults.go) implements the same rule.
29
+ // passive. The simulation applies the same rule.
27
30
  //
28
31
  // Invariants:
29
32
  // - `entityType` is a label, not a unique key — a category appears in many
@@ -33,11 +36,18 @@ import { Schema, model } from 'mongoose';
33
36
  // at different specificity levels). `liveItemIds` is indexed (non-unique
34
37
  // multikey) only for fast membership lookups.
35
38
 
36
- const ObjectLayerDefaultSchema = new Schema(
39
+ // Overrides what the lists derive for one carried id: `active` forces the spawn state (a skin the
40
+ // equipment rules would otherwise leave inactive), `quantity` sizes a stack (a drop bundle).
41
+ // Omitting `active` keeps the derived value; an id no list carries is ignored.
42
+ const OverrideItemStateSchema = new Schema(
37
43
  {
38
- itemId: { type: String, required: true, trim: true },
39
- active: { type: Boolean, default: false },
40
- quantity: { type: Number, default: 0 },
44
+ itemId: { type: String, trim: true },
45
+ active: { type: Boolean },
46
+ quantity: { type: Number, default: 1, min: 1 },
47
+ // Probability in [0,1] that this id actually scatters when the entity dies. Absent means 1 —
48
+ // the drop always happens, which is what every world did before this existed. Only meaningful
49
+ // for an id the build carries in `dropItemIds`; on any other row it is inert.
50
+ dropChance: { type: Number, min: 0, max: 1 },
41
51
  },
42
52
  { _id: false },
43
53
  );
@@ -48,11 +58,12 @@ const CyberiaEntityTypeDefaultSchema = new Schema(
48
58
  liveItemIds: { type: [{ type: String, trim: true }], index: true },
49
59
  deadItemIds: [{ type: String, trim: true }],
50
60
  dropItemIds: [{ type: String, trim: true }],
51
- defaultObjectLayers: [ObjectLayerDefaultSchema],
61
+ inventoryItemsIds: [{ type: String, trim: true }],
62
+ overrideItemsIdsState: [OverrideItemStateSchema],
52
63
  // Canonical entity behavior bound to entities matched by liveItemIds (see
53
64
  // SharedDefaultsCyberia.ENTITY_BEHAVIORS). Empty = let the runtime derive it
54
- // (armed → hostile, else passive). The Go simulation resolves this with the
55
- // same liveItemIds matching used for the live/dead/drop sets.
65
+ // (armed → hostile, else passive). The simulation resolves it with the same
66
+ // liveItemIds match it uses for the live/dead/drop sets.
56
67
  behavior: { type: String, trim: true },
57
68
  },
58
69
  {
@@ -1,5 +1,6 @@
1
1
  import express from 'express';
2
- import { registerCrudRoutes } from '../../server/middlewares.js';
2
+ import { registerCrudRoutes } from '../../server/network/middlewares.js';
3
+ import { moderatorGuard } from '../../server/security/auth.js';
3
4
  import { CyberiaEntityTypeDefaultController } from './cyberia-entity-type-default.controller.js';
4
5
 
5
6
  class CyberiaEntityTypeDefaultRouter {
@@ -8,7 +9,21 @@ class CyberiaEntityTypeDefaultRouter {
8
9
  * @returns {import('express').Router}
9
10
  */
10
11
  static router(options) {
11
- return registerCrudRoutes(express.Router(), CyberiaEntityTypeDefaultController, options);
12
+ const router = express.Router();
13
+ // Custom actions first: the generic /:id routes below capture everything.
14
+ router.post(
15
+ `/sync-instance/:instanceCode`,
16
+ options.authMiddleware,
17
+ moderatorGuard,
18
+ async (req, res) => await CyberiaEntityTypeDefaultController.sync(req, res, options),
19
+ );
20
+ router.post(
21
+ `/:id/instances`,
22
+ options.authMiddleware,
23
+ moderatorGuard,
24
+ async (req, res) => await CyberiaEntityTypeDefaultController.setInstances(req, res, options),
25
+ );
26
+ return registerCrudRoutes(router, CyberiaEntityTypeDefaultController, options);
12
27
  }
13
28
  }
14
29
 
@@ -1,14 +1,462 @@
1
1
  import { DataBaseProviderService } from '../../db/DataBaseProvider.js';
2
- import { loggerFactory } from '../../server/logger.js';
3
- import { DataQuery } from '../../server/data-query.js';
2
+ import {
3
+ ENTITY_TYPE_DEFAULTS,
4
+ resolveEntityDefaultBuild,
5
+ resolveEntityInventory,
6
+ } from '../cyberia-server-defaults/cyberia-server-defaults.js';
7
+ import { collectInstanceItemIds, selectInstanceSkills } from '../cyberia-instance/cyberia-instance-items.js';
8
+ import { loggerFactory } from '../../server/ops/logger.js';
9
+ import { DataQuery } from '../../server/storage/data-query.js';
4
10
 
5
11
  const logger = loggerFactory(import.meta);
6
12
 
13
+ /** The stored shape, as every consumer of a resolved default expects to read it. */
14
+ const toEntityDefault = (source) => ({
15
+ entityType: source.entityType || '',
16
+ liveItemIds: [...(source.liveItemIds || [])],
17
+ deadItemIds: [...(source.deadItemIds || [])],
18
+ dropItemIds: [...(source.dropItemIds || [])],
19
+ inventoryItemsIds: [...(source.inventoryItemsIds || [])],
20
+ overrideItemsIdsState: (source.overrideItemsIdsState || [])
21
+ .filter((rule) => rule?.itemId)
22
+ .map((rule) => ({
23
+ itemId: rule.itemId,
24
+ ...('boolean' === typeof rule.active ? { active: rule.active } : {}),
25
+ ...(Number.isFinite(rule.quantity) ? { quantity: rule.quantity } : {}),
26
+ ...(Number.isFinite(rule.dropChance) ? { dropChance: rule.dropChance } : {}),
27
+ })),
28
+ behavior: source.behavior || '',
29
+ });
30
+
31
+ /** The key resolution ties on: same entity type, same live set, whatever order it is written in. */
32
+ const buildKey = (doc) => `${doc?.entityType || ''}|${[...(doc?.liveItemIds || [])].sort().join(',')}`;
33
+
34
+ /**
35
+ * Names the references that answer for the same entities, changing nothing.
36
+ *
37
+ * Resolution is by containment with the most specific match winning, so two documents declaring
38
+ * the same type and the same live ids are not a refinement of each other — whichever the conf
39
+ * lists first wins, which is not a decision anyone made. That is worth telling an operator about.
40
+ *
41
+ * It is not worth unlinking over. A reference is a world's declared membership, and picking which
42
+ * of two to discard by timestamp guesses at intent and throws away the other instance's wiring
43
+ * when it guesses wrong. The report says which record to delete; the deleting stays deliberate.
44
+ *
45
+ * @param {string[]} references - Document ids, in conf order.
46
+ * @param {Array<{_id: any, entityType?: string, liveItemIds?: string[]}>} defaults
47
+ * @returns {string[]} Each reference after the first that answers for an already-answered build.
48
+ */
49
+ const findDuplicateBuilds = (references, defaults) => {
50
+ const byId = new Map(defaults.map((doc) => [String(doc._id), doc]));
51
+ const answered = new Set();
52
+ // A reference with no document is the `dropped` path's business: it cannot be compared, so it
53
+ // is never called a duplicate of something unseen.
54
+ return references.filter((id) => {
55
+ if (!byId.has(id)) return false;
56
+ const key = buildKey(byId.get(id));
57
+ if (answered.has(key)) return true;
58
+ answered.add(key);
59
+ return false;
60
+ });
61
+ };
62
+
63
+ /**
64
+ * itemId → item type for the ids a world carries, or an empty map when this provider has no
65
+ * ObjectLayer model. Types only settle a contested equipment slot, so a world without them keeps
66
+ * every row the state its list derives — the same "unknown types pass through" the runtime applies.
67
+ *
68
+ * @param {Set<string>} itemIds - Ids to resolve.
69
+ * @param {{host: string, path: string}} options - Provider context.
70
+ * @returns {Promise<Object<string,string>>}
71
+ */
72
+ const readItemTypes = async (itemIds, options) => {
73
+ const types = {};
74
+ if (itemIds.size === 0) return types;
75
+ let ObjectLayer;
76
+ try {
77
+ ObjectLayer = DataBaseProviderService.getModel('ObjectLayer', options);
78
+ } catch {
79
+ return types;
80
+ }
81
+ if (!ObjectLayer?.find) return types;
82
+ for (const doc of await ObjectLayer.find({ 'data.item.id': { $in: [...itemIds] } }, { 'data.item': 1 }).lean()) {
83
+ const item = doc?.data?.item;
84
+ if (item?.id && item?.type) types[item.id] = item.type;
85
+ }
86
+ return types;
87
+ };
88
+
89
+ /**
90
+ * Rewrites one map's entities so each carries what its default says it wears.
91
+ *
92
+ * A placed entity names itself by the ids the author put on it, and the simulation seeds the rest
93
+ * from the default that name resolves to. The map document has to agree: an override that
94
+ * activates a carried item is a decision about the entity, and it is only real once the entity on
95
+ * the map holds that item too. Everything reading a map — the editor, the instance payload, the
96
+ * spawn — then sees one answer instead of two.
97
+ *
98
+ * The rewrite is a replacement, not a merge: a matched entity ends up carrying its build's live
99
+ * ids and whatever that build wears, and nothing else. Merging cannot converge — an id the sync
100
+ * added stops being recognisable the moment the default stops naming it, and it would sit on the
101
+ * map for good. Stating the whole set is what makes the second run a no-op and what makes removing
102
+ * an item from a default actually remove it from the world.
103
+ *
104
+ * The live ids are always kept, even when an override unseats one: they are the key the entity
105
+ * resolves by, and dropping one would send the next sync to a different, less specific default.
106
+ * Which of them is actually worn is settled at spawn, from the same inventory this reads.
107
+ *
108
+ * Only a default the entity genuinely carries speaks for it. The fallback match — the first of its
109
+ * type — answers what a bot looks like in general, never what is on this one, so an entity that
110
+ * contains no default's live set is left exactly as its author placed it.
111
+ *
112
+ * @param {Array<object>} entities - The map's entities.
113
+ * @param {Array<object>} defaults - The world's entity-type defaults, most specific first.
114
+ * @param {Object<string,string>} itemTypes - itemId → item type, for the equipment rules.
115
+ * @returns {{entities: Array<object>, changed: number}} The rewritten entities and how many moved.
116
+ */
117
+ const applyDefaultsToEntities = (entities = [], defaults = [], itemTypes = {}) => {
118
+ let changed = 0;
119
+ const next = entities.map((entity) => {
120
+ const placed = entity.objectLayerItemIds || [];
121
+ const build = resolveEntityDefaultBuild({ entityType: entity.entityType, itemIds: placed }, defaults);
122
+ const identity = build?.liveItemIds || [];
123
+ if (identity.length === 0 || !identity.every((itemId) => placed.includes(itemId))) return entity;
124
+ const worn = resolveEntityInventory(build, { itemTypes })
125
+ .filter((row) => row.active)
126
+ .map((row) => row.itemId);
127
+ const itemIds = [...new Set([...identity, ...worn])];
128
+ if (itemIds.length === placed.length && itemIds.every((id, index) => id === placed[index])) return entity;
129
+ changed++;
130
+ return { ...entity, objectLayerItemIds: itemIds };
131
+ });
132
+ return { entities: next, changed };
133
+ };
134
+
7
135
  class CyberiaEntityTypeDefaultService {
8
136
  // No per-itemId uniqueness: resolution is by subset containment (most-specific
9
137
  // match wins), so the same itemId may appear in many documents — e.g.
10
138
  // [purple, atlas_pistol_mk2]→hostile alongside [purple]→passive. See the model.
11
139
 
140
+ /**
141
+ * Materialises the entity-type defaults an instance conf references.
142
+ *
143
+ * This is the only way a conf's `entityDefaults` becomes usable data. It reads the ids the conf
144
+ * names and nothing else: a document belongs to a world because that world points at it, never
145
+ * because their item ids happen to overlap. That distinction is the whole fix — matching by
146
+ * item id is what let two instances sharing a skin resolve into each other's wiring.
147
+ *
148
+ * Order follows the conf, so the most specific documents an operator listed first stay first.
149
+ * An id naming no document is dropped and reported: the reference outliving its document is a
150
+ * broken world, not a silent one.
151
+ *
152
+ * @param {import('mongoose').Model} Model - CyberiaEntityTypeDefault model.
153
+ * @param {Array<string|import('mongoose').Types.ObjectId>} entityDefaultIds - Conf references.
154
+ * @returns {Promise<Array<object>>} Referenced defaults, in reference order.
155
+ */
156
+ static resolveWith = async (Model, entityDefaultIds = []) => {
157
+ const ids = (entityDefaultIds || []).map((id) => String(id?._id ?? id)).filter(Boolean);
158
+ if (!Model || ids.length === 0) return [];
159
+ const docs = await Model.find({ _id: { $in: ids } }).lean();
160
+ const byId = new Map(docs.map((doc) => [String(doc._id), doc]));
161
+ const missing = ids.filter((id) => !byId.has(id));
162
+ if (missing.length > 0) {
163
+ logger.warn('Instance conf references entity-type defaults that no longer exist', { missing });
164
+ }
165
+ return ids.filter((id) => byId.has(id)).map((id) => toEntityDefault(byId.get(id)));
166
+ };
167
+
168
+ /** {@link resolveWith}, addressing the model through the provider context. */
169
+ static resolve = async (entityDefaultIds = [], options) =>
170
+ CyberiaEntityTypeDefaultService.resolveWith(
171
+ DataBaseProviderService.getModel('CyberiaEntityTypeDefault', options),
172
+ entityDefaultIds,
173
+ );
174
+
175
+ /**
176
+ * The defaults a world runs on: what its conf references, completed by the canonical set.
177
+ *
178
+ * Every entity type the referenced documents do not cover falls back to ENTITY_TYPE_DEFAULTS, so
179
+ * an instance only has to own the rows it actually changes.
180
+ *
181
+ * @param {Array<object>} resolved - Defaults an instance references, already materialised.
182
+ * @returns {Array<object>} Those defaults followed by the uncovered canonical ones.
183
+ */
184
+ static completeWithCanonical = (resolved = []) => {
185
+ const covered = new Set(resolved.map((entityDefault) => entityDefault.entityType));
186
+ return [
187
+ ...resolved.map(toEntityDefault),
188
+ ...ENTITY_TYPE_DEFAULTS.filter((canonical) => !covered.has(canonical.entityType)).map(toEntityDefault),
189
+ ];
190
+ };
191
+
192
+ /** {@link completeWithCanonical} over what a conf references. */
193
+ static resolveWithCanonical = async (entityDefaultIds = [], options) =>
194
+ CyberiaEntityTypeDefaultService.completeWithCanonical(
195
+ await CyberiaEntityTypeDefaultService.resolve(entityDefaultIds, options),
196
+ );
197
+
198
+ /**
199
+ * Makes exactly the named instances reference this default.
200
+ *
201
+ * States the whole membership, so re-sending converges: a conf that drops out of the list has
202
+ * the reference removed, and one that joins gets it appended. Order inside a conf is
203
+ * significant (most specific first), so an existing reference is left where it is.
204
+ *
205
+ * @param {object} req - Express request; `params.id` is the default's _id, `body.instanceCodes`
206
+ * the complete set of instances that should reference it.
207
+ * @param {object} res
208
+ * @param {{host: string, path: string}} options - Provider context.
209
+ * @returns {Promise<{instanceCodes: string[], linked: string[], unlinked: string[]}>}
210
+ */
211
+ static setInstances = async (req, res, options) => {
212
+ const CyberiaEntityTypeDefault = DataBaseProviderService.getModel('CyberiaEntityTypeDefault', options);
213
+ const CyberiaInstanceConf = DataBaseProviderService.getModel('CyberiaInstanceConf', options);
214
+ const id = String(req.params.id);
215
+ if (!(await CyberiaEntityTypeDefault.exists({ _id: id }))) {
216
+ throw new Error(`cyberia-entity-type-default not found for _id="${id}"`);
217
+ }
218
+ const wanted = new Set((req.body?.instanceCodes || []).map(String));
219
+
220
+ const confs = await CyberiaInstanceConf.find({}, { instanceCode: 1, entityDefaults: 1 }).lean();
221
+ const unknown = [...wanted].filter((code) => !confs.some((conf) => conf.instanceCode === code));
222
+ if (unknown.length > 0) throw new Error(`cyberia-instance-conf not found for: ${unknown.join(', ')}`);
223
+
224
+ const linked = [];
225
+ const unlinked = [];
226
+ for (const conf of confs) {
227
+ const refs = (conf.entityDefaults || []).map(String);
228
+ const has = refs.includes(id);
229
+ const should = wanted.has(conf.instanceCode);
230
+ if (has === should) continue;
231
+ await CyberiaInstanceConf.updateOne(
232
+ { _id: conf._id },
233
+ { $set: { entityDefaults: should ? [...refs, id] : refs.filter((ref) => ref !== id), updatedAt: new Date() } },
234
+ );
235
+ (should ? linked : unlinked).push(conf.instanceCode);
236
+ }
237
+ const compacted = await CyberiaEntityTypeDefaultService.compactInstanceRefs(options);
238
+ logger.info('Updated entity-type default instance links', { id, linked, unlinked });
239
+ return { instanceCodes: [...wanted].sort(), linked, unlinked, compacted };
240
+ };
241
+
242
+ /**
243
+ * Drops conf references whose entity-type default no longer exists.
244
+ *
245
+ * A reference outliving its document is a world half-declared: the boot path skips it, the
246
+ * export cannot find it, and nobody can tell from the conf whether the wiring was meant to be
247
+ * there. Deleting a default unlinks it everywhere (see `delete`), so this exists for what
248
+ * slipped past that — a document removed straight from the collection, or a conf restored from
249
+ * a backup whose defaults were not.
250
+ *
251
+ * @param {{host: string, path: string}} options - Provider context.
252
+ * @param {{instanceCode?: string}} [scope] - Limit to one instance; omit for the whole collection.
253
+ * @returns {Promise<Array<{instanceCode: string, dropped: string[]}>>} What each conf lost.
254
+ */
255
+ static compactInstanceRefs = async (options, { instanceCode } = {}) => {
256
+ const CyberiaEntityTypeDefault = DataBaseProviderService.getModel('CyberiaEntityTypeDefault', options);
257
+ const CyberiaInstanceConf = DataBaseProviderService.getModel('CyberiaInstanceConf', options);
258
+ const confs = await CyberiaInstanceConf.find(instanceCode ? { instanceCode } : {}, {
259
+ instanceCode: 1,
260
+ entityDefaults: 1,
261
+ }).lean();
262
+ const referenced = [...new Set(confs.flatMap((conf) => (conf.entityDefaults || []).map(String)))];
263
+ if (referenced.length === 0) return [];
264
+ const existing = new Set(
265
+ (await CyberiaEntityTypeDefault.find({ _id: { $in: referenced } }, { _id: 1 }).lean()).map((doc) =>
266
+ String(doc._id),
267
+ ),
268
+ );
269
+
270
+ const compacted = [];
271
+ for (const conf of confs) {
272
+ const refs = (conf.entityDefaults || []).map(String);
273
+ const kept = refs.filter((ref) => existing.has(ref));
274
+ if (kept.length === refs.length) continue;
275
+ await CyberiaInstanceConf.updateOne(
276
+ { _id: conf._id },
277
+ { $set: { entityDefaults: kept, updatedAt: new Date() } },
278
+ );
279
+ compacted.push({ instanceCode: conf.instanceCode, dropped: refs.filter((ref) => !existing.has(ref)) });
280
+ }
281
+ if (compacted.length > 0) logger.info('Dropped orphaned entity-type default references', { compacted });
282
+ return compacted;
283
+ };
284
+
285
+ /**
286
+ * Points an instance's conf at every entity-type default its own content needs.
287
+ *
288
+ * Matching is the runtime's rule, not a guess: a document belongs to this world when all of its
289
+ * `liveItemIds` appear together on some entity its maps place — the same subset containment
290
+ * `game/entity_defaults.go` resolves an entity with. So whatever an author drops on a map, the
291
+ * conf ends up referencing the wiring that will actually be applied to it.
292
+ *
293
+ * Additive, then compacting: references already there are kept in place (order carries
294
+ * specificity), matches missing from the conf are appended, and references whose document is
295
+ * gone are dropped. Nothing an author linked by hand is lost — a player or coin default is
296
+ * never placed on a map, so a wholesale replace would silently discard it.
297
+ *
298
+ * A match is reported rather than linked whenever linking it would decide something for the
299
+ * operator. Two worlds built on the same art hold documents with identical `liveItemIds`, so
300
+ * matching alone cannot tell them apart: one another instance references is left to that
301
+ * instance (`skipped`), and one answering for a build this conf already references is left
302
+ * where it is (`conflicts`), since the two would then differ only by list order. Both stay
303
+ * claimable by hand in the Entity engine. That is the cross-instance contamination the
304
+ * reference model exists to prevent — a document is in a world because the world points at it.
305
+ *
306
+ * Membership is only ever added here. A reference an operator made is never withdrawn on a
307
+ * guess: the sole removal is `dropped`, a reference whose document no longer exists.
308
+ *
309
+ * The skills the world runs are reported, not written. They follow from its content by the
310
+ * shared rule in cyberia-instance-items.js — a skill belongs when its trigger item is named by
311
+ * a map entity, an entity-type default, a vendor or assembler catalog, or a quest. Nothing to
312
+ * store means nothing to keep in step: the export and the boot payload derive the same list
313
+ * from the same content.
314
+ *
315
+ * @param {{instanceCode: string, mapCodes?: string[]}} params - Target instance; `mapCodes`
316
+ * overrides the stored set, which is what lets an editor sync against an unsaved selection.
317
+ * @param {{host: string, path: string}} options - Provider context.
318
+ * Placed entities are rewritten too: each carries the items the default it resolves to says it
319
+ * wears, so a map document and the simulation agree on what is on an entity.
320
+ *
321
+ * @returns {Promise<{instanceCode: string, mapCodes: string[], linked: string[], skipped: string[],
322
+ * dropped: string[], entityDefaults: string[], skills: string[],
323
+ * entitiesUpdated: Array<{mapCode: string, entities: number}>, conflicts: string[],
324
+ * duplicates: string[]}>}
325
+ */
326
+ static syncInstance = async ({ instanceCode, mapCodes }, options) => {
327
+ if (!instanceCode) throw new Error('instanceCode is required to sync entity-type defaults');
328
+ const CyberiaAction = DataBaseProviderService.getModel('CyberiaAction', options);
329
+ const CyberiaEntityTypeDefault = DataBaseProviderService.getModel('CyberiaEntityTypeDefault', options);
330
+ const CyberiaInstance = DataBaseProviderService.getModel('CyberiaInstance', options);
331
+ const CyberiaInstanceConf = DataBaseProviderService.getModel('CyberiaInstanceConf', options);
332
+ const CyberiaMap = DataBaseProviderService.getModel('CyberiaMap', options);
333
+ const CyberiaQuest = DataBaseProviderService.getModel('CyberiaQuest', options);
334
+ const CyberiaSkill = DataBaseProviderService.getModel('CyberiaSkill', options);
335
+
336
+ const confs = await CyberiaInstanceConf.find({}, { instanceCode: 1, entityDefaults: 1 }).lean();
337
+ const conf = confs.find((entry) => entry.instanceCode === instanceCode);
338
+ if (!conf) throw new Error(`cyberia-instance-conf not found for instanceCode="${instanceCode}"`);
339
+ const codes = mapCodes?.length
340
+ ? [...mapCodes]
341
+ : (await CyberiaInstance.findOne({ code: instanceCode }).select('cyberiaMapCodes').lean())?.cyberiaMapCodes ??
342
+ [];
343
+
344
+ const maps = codes.length ? await CyberiaMap.find({ code: { $in: codes } }, { code: 1, entities: 1 }).lean() : [];
345
+ const placed = maps.flatMap((map) => (map.entities || []).map((entity) => new Set(entity.objectLayerItemIds || [])));
346
+
347
+ const defaults = await CyberiaEntityTypeDefault.find(
348
+ {},
349
+ { entityType: 1, liveItemIds: 1, updatedAt: 1 },
350
+ ).lean();
351
+ const matched = defaults
352
+ .filter(({ liveItemIds }) => (liveItemIds || []).length > 0)
353
+ .filter(({ liveItemIds }) => placed.some((itemIds) => liveItemIds.every((id) => itemIds.has(id))))
354
+ .map((doc) => String(doc._id));
355
+
356
+ const claimedElsewhere = new Set(
357
+ confs
358
+ .filter((entry) => entry.instanceCode !== instanceCode)
359
+ .flatMap((entry) => (entry.entityDefaults || []).map(String)),
360
+ );
361
+ const byId = new Map(defaults.map((doc) => [String(doc._id), doc]));
362
+ const existing = new Set(byId.keys());
363
+ const refs = (conf.entityDefaults || []).map(String);
364
+ const dropped = refs.filter((ref) => !existing.has(ref));
365
+ const kept = refs.filter((ref) => existing.has(ref));
366
+ // What this world already has an answer for. A match that answers for a build a reference
367
+ // already covers is a second opinion, never an upgrade: adopting it would put a document the
368
+ // operator never linked into the world, and — sharing the winner's key — it would decide by
369
+ // list order which of the two the entities actually resolve to. Reported, not linked.
370
+ const answered = new Set(kept.map((ref) => buildKey(byId.get(ref))));
371
+ const candidates = matched.filter((id) => !kept.includes(id));
372
+ const skipped = candidates.filter((id) => claimedElsewhere.has(id));
373
+ const linked = [];
374
+ const conflicts = [];
375
+ for (const id of candidates.filter((candidate) => !claimedElsewhere.has(candidate))) {
376
+ const key = buildKey(byId.get(id));
377
+ if (answered.has(key)) {
378
+ conflicts.push(id);
379
+ continue;
380
+ }
381
+ answered.add(key);
382
+ linked.push(id);
383
+ }
384
+ const entityDefaults = [...kept, ...linked];
385
+ const duplicates = findDuplicateBuilds(entityDefaults, defaults);
386
+
387
+ // Every item the world can hold: what it references, completed by the canonical defaults for
388
+ // the entity types it does not cover. A skill triggers from one of these or from nothing.
389
+ const referenced = await CyberiaEntityTypeDefault.find({ _id: { $in: entityDefaults } }).lean();
390
+ const running = CyberiaEntityTypeDefaultService.completeWithCanonical(referenced.map(toEntityDefault));
391
+ const carried = new Set(running.flatMap((doc) => resolveEntityInventory(doc).map((row) => row.itemId)));
392
+
393
+ // Which skills the world runs is derived, never stored. Reported so an operator can see what
394
+ // a sync changed, and computed by the one rule the export and the boot payload also use — a
395
+ // stored list was a second answer to the same question, and it disagreed: a trigger only a
396
+ // quest or a vendor named was reachable in play, exported with the world, and missing here.
397
+ const [actions, quests] = codes.length
398
+ ? await Promise.all([
399
+ CyberiaAction.find({ sourceMapCode: { $in: codes } }, { shopItems: 1, craftRecipes: 1 }).lean(),
400
+ CyberiaQuest.find({ sourceMapCode: { $in: codes } }, { steps: 1, rewards: 1 }).lean(),
401
+ ])
402
+ : [[], []];
403
+ const owned = collectInstanceItemIds({ maps, entityDefaults: running, actions, quests });
404
+ const skills = selectInstanceSkills(await CyberiaSkill.find({}, { triggerItemId: 1 }).lean(), owned)
405
+ .map((skill) => skill.triggerItemId)
406
+ .sort();
407
+
408
+ // Maps carry what their entities wear. The equipment rules settle a contested slot, so the
409
+ // types come along: an override that names a second skin decides which one the entity has on.
410
+ const itemTypes = await readItemTypes(carried, options);
411
+ const entitiesUpdated = [];
412
+ for (const map of maps) {
413
+ const { entities, changed } = applyDefaultsToEntities(map.entities, running, itemTypes);
414
+ if (changed === 0) continue;
415
+ await CyberiaMap.updateOne({ _id: map._id }, { $set: { entities } });
416
+ entitiesUpdated.push({ mapCode: map.code, entities: changed });
417
+ }
418
+
419
+ if (linked.length > 0 || dropped.length > 0) {
420
+ // A conf written before skills were derived still carries the field. It reaches nothing —
421
+ // it is off the schema, and fillInstanceConfDefaults strips it from every read that matters
422
+ // — so it is cleared here rather than in a write of its own.
423
+ await CyberiaInstanceConf.updateOne(
424
+ { _id: conf._id },
425
+ { $set: { entityDefaults, updatedAt: new Date() }, $unset: { skillConfig: '' } },
426
+ );
427
+ }
428
+ logger.info('Synced entity-type defaults', {
429
+ instanceCode,
430
+ maps: codes.length,
431
+ linked,
432
+ skipped,
433
+ dropped,
434
+ skills,
435
+ entitiesUpdated,
436
+ ...(conflicts.length > 0 ? { conflicts } : {}),
437
+ ...(duplicates.length > 0 ? { duplicates } : {}),
438
+ });
439
+ return {
440
+ instanceCode,
441
+ mapCodes: codes,
442
+ linked,
443
+ skipped,
444
+ dropped,
445
+ entityDefaults,
446
+ skills,
447
+ entitiesUpdated,
448
+ conflicts,
449
+ duplicates,
450
+ };
451
+ };
452
+
453
+ /** REST form of {@link syncInstance}: `params.instanceCode`, optional `body.mapCodes`. */
454
+ static sync = async (req, res, options) =>
455
+ await CyberiaEntityTypeDefaultService.syncInstance(
456
+ { instanceCode: req.params.instanceCode, mapCodes: req.body?.mapCodes },
457
+ options,
458
+ );
459
+
12
460
  static post = async (req, res, options) => {
13
461
  /** @type {import('./cyberia-entity-type-default.model.js').CyberiaEntityTypeDefaultModel} */
14
462
  const CyberiaEntityTypeDefault = DataBaseProviderService.getModel('CyberiaEntityTypeDefault', options);
@@ -35,12 +483,27 @@ class CyberiaEntityTypeDefaultService {
35
483
  const CyberiaEntityTypeDefault = DataBaseProviderService.getModel('CyberiaEntityTypeDefault', options);
36
484
  return await CyberiaEntityTypeDefault.findByIdAndUpdate(req.params.id, req.body);
37
485
  };
486
+ // Unlinks before it deletes: a conf pointing at a document that is gone is the one way this
487
+ // collection produces orphans, and the delete is where it would happen.
38
488
  static delete = async (req, res, options) => {
39
489
  /** @type {import('./cyberia-entity-type-default.model.js').CyberiaEntityTypeDefaultModel} */
40
490
  const CyberiaEntityTypeDefault = DataBaseProviderService.getModel('CyberiaEntityTypeDefault', options);
41
- if (req.params.id) return await CyberiaEntityTypeDefault.findByIdAndDelete(req.params.id);
42
- else return await CyberiaEntityTypeDefault.deleteMany();
491
+ const CyberiaInstanceConf = DataBaseProviderService.getModel('CyberiaInstanceConf', options);
492
+ if (req.params.id) {
493
+ const id = String(req.params.id);
494
+ const { modifiedCount } = await CyberiaInstanceConf.updateMany(
495
+ { entityDefaults: id },
496
+ { $pull: { entityDefaults: id }, $set: { updatedAt: new Date() } },
497
+ );
498
+ if (modifiedCount > 0) logger.info('Unlinked deleted entity-type default', { id, confs: modifiedCount });
499
+ return await CyberiaEntityTypeDefault.findByIdAndDelete(id);
500
+ }
501
+ await CyberiaInstanceConf.updateMany(
502
+ { entityDefaults: { $ne: [] } },
503
+ { $set: { entityDefaults: [], updatedAt: new Date() } },
504
+ );
505
+ return await CyberiaEntityTypeDefault.deleteMany();
43
506
  };
44
507
  }
45
508
 
46
- export { CyberiaEntityTypeDefaultService };
509
+ export { applyDefaultsToEntities, buildKey, findDuplicateBuilds, CyberiaEntityTypeDefaultService, toEntityDefault };