cyberia 3.1.3 → 3.2.5

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 (208) hide show
  1. package/.env.example +0 -2
  2. package/.github/workflows/engine-cyberia.cd.yml +10 -8
  3. package/.github/workflows/engine-cyberia.ci.yml +12 -29
  4. package/.github/workflows/ghpkg.ci.yml +4 -4
  5. package/.github/workflows/npmpkg.ci.yml +28 -11
  6. package/.github/workflows/publish.ci.yml +21 -2
  7. package/.github/workflows/pwa-microservices-template-page.cd.yml +4 -5
  8. package/.github/workflows/pwa-microservices-template-test.ci.yml +3 -3
  9. package/.github/workflows/release.cd.yml +13 -8
  10. package/CHANGELOG.md +433 -1
  11. package/CLI-HELP.md +57 -7
  12. package/Dockerfile +4 -2
  13. package/README.md +347 -22
  14. package/bin/build.js +5 -2
  15. package/bin/cyberia.js +1789 -112
  16. package/bin/deploy.js +177 -124
  17. package/bin/file.js +3 -0
  18. package/bin/index.js +1789 -112
  19. package/conf.js +64 -8
  20. package/deployment.yaml +92 -20
  21. package/hardhat/hardhat.config.js +13 -13
  22. package/hardhat/ignition/modules/ObjectLayerToken.js +1 -1
  23. package/hardhat/package-lock.json +2554 -5859
  24. package/hardhat/package.json +13 -22
  25. package/hardhat/scripts/deployObjectLayerToken.js +1 -1
  26. package/hardhat/test/ObjectLayerToken.js +4 -2
  27. package/hardhat/types/ethers-contracts/ObjectLayerToken.ts +690 -0
  28. package/hardhat/types/ethers-contracts/common.ts +92 -0
  29. package/hardhat/types/ethers-contracts/factories/ObjectLayerToken__factory.ts +1055 -0
  30. package/hardhat/types/ethers-contracts/factories/index.ts +4 -0
  31. package/hardhat/types/ethers-contracts/hardhat.d.ts +47 -0
  32. package/hardhat/types/ethers-contracts/index.ts +6 -0
  33. package/jsdoc.dd-cyberia.json +64 -55
  34. package/jsdoc.json +64 -55
  35. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +5 -4
  36. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +5 -4
  37. package/manifests/deployment/dd-cyberia-development/deployment.yaml +92 -20
  38. package/manifests/deployment/dd-cyberia-development/proxy.yaml +54 -18
  39. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  40. package/manifests/deployment/dd-test-development/deployment.yaml +88 -74
  41. package/manifests/deployment/dd-test-development/proxy.yaml +13 -4
  42. package/manifests/deployment/playwright/deployment.yaml +1 -1
  43. package/nodemon.json +1 -1
  44. package/package.json +22 -16
  45. package/proxy.yaml +54 -18
  46. package/scripts/rhel-grpc-setup.sh +56 -0
  47. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +44 -0
  48. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +16 -0
  49. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +5 -0
  50. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +80 -7
  51. package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +93 -0
  52. package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +36 -0
  53. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +29 -0
  54. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +51 -0
  55. package/src/api/cyberia-entity/cyberia-entity.controller.js +74 -0
  56. package/src/api/cyberia-entity/cyberia-entity.model.js +24 -0
  57. package/src/api/cyberia-entity/cyberia-entity.router.js +27 -0
  58. package/src/api/cyberia-entity/cyberia-entity.service.js +42 -0
  59. package/src/api/cyberia-instance/cyberia-fallback-world.js +368 -0
  60. package/src/api/cyberia-instance/cyberia-instance.controller.js +92 -0
  61. package/src/api/cyberia-instance/cyberia-instance.model.js +84 -0
  62. package/src/api/cyberia-instance/cyberia-instance.router.js +63 -0
  63. package/src/api/cyberia-instance/cyberia-instance.service.js +191 -0
  64. package/src/api/cyberia-instance/cyberia-portal-connector.js +486 -0
  65. package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +74 -0
  66. package/src/api/cyberia-instance-conf/cyberia-instance-conf.defaults.js +413 -0
  67. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +228 -0
  68. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +27 -0
  69. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +42 -0
  70. package/src/api/cyberia-map/cyberia-map.controller.js +79 -0
  71. package/src/api/cyberia-map/cyberia-map.model.js +30 -0
  72. package/src/api/cyberia-map/cyberia-map.router.js +40 -0
  73. package/src/api/cyberia-map/cyberia-map.service.js +74 -0
  74. package/src/api/file/file.ref.json +18 -0
  75. package/src/api/ipfs/ipfs.controller.js +4 -25
  76. package/src/api/ipfs/ipfs.model.js +43 -34
  77. package/src/api/ipfs/ipfs.router.js +8 -13
  78. package/src/api/ipfs/ipfs.service.js +54 -102
  79. package/src/api/object-layer/README.md +347 -22
  80. package/src/api/object-layer/object-layer.router.js +30 -0
  81. package/src/api/object-layer/object-layer.service.js +114 -31
  82. package/src/api/user/user.service.js +8 -7
  83. package/src/cli/cluster.js +7 -7
  84. package/src/cli/db.js +710 -827
  85. package/src/cli/deploy.js +151 -93
  86. package/src/cli/env.js +29 -0
  87. package/src/cli/fs.js +5 -2
  88. package/src/cli/index.js +48 -2
  89. package/src/cli/kubectl.js +211 -0
  90. package/src/cli/release.js +284 -0
  91. package/src/cli/repository.js +438 -75
  92. package/src/cli/run.js +195 -35
  93. package/src/cli/secrets.js +73 -0
  94. package/src/cli/test.js +3 -3
  95. package/src/client/Cryptokoyn.index.js +3 -4
  96. package/src/client/CyberiaPortal.index.js +3 -4
  97. package/src/client/Default.index.js +3 -4
  98. package/src/client/Itemledger.index.js +3 -4
  99. package/src/client/Underpost.index.js +3 -4
  100. package/src/client/components/core/AppStore.js +69 -0
  101. package/src/client/components/core/CalendarCore.js +2 -2
  102. package/src/client/components/core/DropDown.js +137 -17
  103. package/src/client/components/core/Keyboard.js +2 -2
  104. package/src/client/components/core/LogIn.js +2 -2
  105. package/src/client/components/core/LogOut.js +2 -2
  106. package/src/client/components/core/Modal.js +0 -1
  107. package/src/client/components/core/Panel.js +0 -1
  108. package/src/client/components/core/PanelForm.js +19 -19
  109. package/src/client/components/core/SocketIo.js +82 -29
  110. package/src/client/components/core/SocketIoHandler.js +75 -0
  111. package/src/client/components/core/Stream.js +143 -95
  112. package/src/client/components/core/Webhook.js +40 -7
  113. package/src/client/components/cryptokoyn/AppStoreCryptokoyn.js +5 -0
  114. package/src/client/components/cryptokoyn/LogInCryptokoyn.js +3 -3
  115. package/src/client/components/cryptokoyn/LogOutCryptokoyn.js +2 -2
  116. package/src/client/components/cryptokoyn/MenuCryptokoyn.js +3 -3
  117. package/src/client/components/cryptokoyn/SocketIoCryptokoyn.js +3 -51
  118. package/src/client/components/cyberia/InstanceEngineCyberia.js +700 -0
  119. package/src/client/components/cyberia/MapEngineCyberia.js +1359 -2
  120. package/src/client/components/cyberia/ObjectLayerEngineModal.js +17 -6
  121. package/src/client/components/cyberia/ObjectLayerEngineViewer.js +92 -54
  122. package/src/client/components/cyberia-portal/AppStoreCyberiaPortal.js +5 -0
  123. package/src/client/components/cyberia-portal/CommonCyberiaPortal.js +216 -30
  124. package/src/client/components/cyberia-portal/LogInCyberiaPortal.js +3 -3
  125. package/src/client/components/cyberia-portal/LogOutCyberiaPortal.js +2 -2
  126. package/src/client/components/cyberia-portal/MenuCyberiaPortal.js +40 -7
  127. package/src/client/components/cyberia-portal/RoutesCyberiaPortal.js +4 -0
  128. package/src/client/components/cyberia-portal/SocketIoCyberiaPortal.js +3 -49
  129. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +4 -0
  130. package/src/client/components/default/AppStoreDefault.js +5 -0
  131. package/src/client/components/default/LogInDefault.js +3 -3
  132. package/src/client/components/default/LogOutDefault.js +2 -2
  133. package/src/client/components/default/MenuDefault.js +5 -5
  134. package/src/client/components/default/SocketIoDefault.js +3 -51
  135. package/src/client/components/itemledger/AppStoreItemledger.js +5 -0
  136. package/src/client/components/itemledger/LogInItemledger.js +3 -3
  137. package/src/client/components/itemledger/LogOutItemledger.js +2 -2
  138. package/src/client/components/itemledger/MenuItemledger.js +3 -3
  139. package/src/client/components/itemledger/SocketIoItemledger.js +3 -51
  140. package/src/client/components/underpost/AppStoreUnderpost.js +5 -0
  141. package/src/client/components/underpost/LogInUnderpost.js +3 -3
  142. package/src/client/components/underpost/LogOutUnderpost.js +2 -2
  143. package/src/client/components/underpost/MenuUnderpost.js +5 -5
  144. package/src/client/components/underpost/SocketIoUnderpost.js +3 -51
  145. package/src/client/services/core/core.service.js +20 -8
  146. package/src/client/services/cyberia-dialogue/cyberia-dialogue.service.js +105 -0
  147. package/src/client/services/cyberia-entity/cyberia-entity.management.js +57 -0
  148. package/src/client/services/cyberia-entity/cyberia-entity.service.js +105 -0
  149. package/src/client/services/cyberia-instance/cyberia-instance.management.js +194 -0
  150. package/src/client/services/cyberia-instance/cyberia-instance.service.js +122 -0
  151. package/src/client/services/cyberia-instance-conf/cyberia-instance-conf.service.js +105 -0
  152. package/src/client/services/cyberia-map/cyberia-map.management.js +193 -0
  153. package/src/client/services/cyberia-map/cyberia-map.service.js +126 -0
  154. package/src/client/services/instance/instance.management.js +2 -2
  155. package/src/client/services/ipfs/ipfs.service.js +3 -23
  156. package/src/client/services/object-layer/object-layer.management.js +3 -3
  157. package/src/client/services/object-layer/object-layer.service.js +21 -0
  158. package/src/client/services/user/user.management.js +2 -2
  159. package/src/client/ssr/pages/CyberiaServerMetrics.js +1 -1
  160. package/src/grpc/cyberia/OFF_CHAIN_ECONOMY.md +305 -0
  161. package/src/grpc/cyberia/README.md +326 -0
  162. package/src/grpc/cyberia/grpc-server.js +530 -0
  163. package/src/index.js +24 -1
  164. package/src/runtime/express/Dockerfile +4 -0
  165. package/src/runtime/express/Express.js +18 -1
  166. package/src/runtime/lampp/Dockerfile +13 -2
  167. package/src/runtime/lampp/Lampp.js +27 -4
  168. package/src/runtime/wp/Dockerfile +68 -0
  169. package/src/runtime/wp/Wp.js +639 -0
  170. package/src/server/auth.js +24 -1
  171. package/src/server/backup.js +37 -9
  172. package/src/server/client-build-docs.js +9 -2
  173. package/src/server/client-build.js +31 -31
  174. package/src/server/client-formatted.js +109 -57
  175. package/src/server/conf.js +24 -9
  176. package/src/server/cron.js +25 -23
  177. package/src/server/dns.js +2 -1
  178. package/src/server/ipfs-client.js +24 -1
  179. package/src/server/object-layer.js +149 -108
  180. package/src/server/peer.js +8 -0
  181. package/src/server/runtime.js +25 -1
  182. package/src/server/semantic-layer-generator-floor.js +359 -0
  183. package/src/server/semantic-layer-generator-skin.js +1294 -0
  184. package/src/server/semantic-layer-generator.js +116 -555
  185. package/src/server/start.js +2 -2
  186. package/src/ws/IoInterface.js +1 -10
  187. package/src/ws/IoServer.js +14 -33
  188. package/src/ws/core/channels/core.ws.chat.js +65 -20
  189. package/src/ws/core/channels/core.ws.mailer.js +113 -32
  190. package/src/ws/core/channels/core.ws.stream.js +90 -31
  191. package/src/ws/core/core.ws.connection.js +12 -33
  192. package/src/ws/core/core.ws.emit.js +10 -26
  193. package/src/ws/core/core.ws.server.js +25 -58
  194. package/src/ws/default/channels/default.ws.main.js +53 -12
  195. package/src/ws/default/default.ws.connection.js +26 -13
  196. package/src/ws/default/default.ws.server.js +30 -12
  197. package/src/client/components/cryptokoyn/CommonCryptokoyn.js +0 -29
  198. package/src/client/components/cryptokoyn/ElementsCryptokoyn.js +0 -38
  199. package/src/client/components/cyberia-portal/ElementsCyberiaPortal.js +0 -38
  200. package/src/client/components/default/ElementsDefault.js +0 -38
  201. package/src/client/components/itemledger/CommonItemledger.js +0 -29
  202. package/src/client/components/itemledger/ElementsItemledger.js +0 -38
  203. package/src/client/components/underpost/CommonUnderpost.js +0 -29
  204. package/src/client/components/underpost/ElementsUnderpost.js +0 -38
  205. package/src/ws/core/management/core.ws.chat.js +0 -8
  206. package/src/ws/core/management/core.ws.mailer.js +0 -16
  207. package/src/ws/core/management/core.ws.stream.js +0 -8
  208. package/src/ws/default/management/default.ws.main.js +0 -8
@@ -244,7 +244,11 @@ const unpinCid = async (cid) => {
244
244
  });
245
245
  if (!clusterRes.ok) {
246
246
  const text = await clusterRes.text();
247
- logger.warn(`IPFS Cluster unpin failed (${clusterRes.status}): ${text}`);
247
+ if (clusterRes.status === 404) {
248
+ logger.info(`IPFS Cluster unpin – CID already not pinned: ${cid}`);
249
+ } else {
250
+ logger.warn(`IPFS Cluster unpin failed (${clusterRes.status}): ${text}`);
251
+ }
248
252
  } else {
249
253
  logger.info(`IPFS Cluster unpin OK – CID: ${cid}`);
250
254
  }
@@ -428,6 +432,25 @@ const IpfsClient = {
428
432
  listClusterPins,
429
433
  listKuboPins,
430
434
  removeMfsPath,
435
+ /**
436
+ * Check whether a single CID is currently pinned on the local Kubo node.
437
+ * Uses the pin/ls?arg=<cid> endpoint which returns only that one pin
438
+ * (much cheaper than fetching the full list).
439
+ *
440
+ * @param {string} cid - IPFS Content Identifier to check.
441
+ * @returns {Promise<boolean>} true when the CID is pinned.
442
+ */
443
+ isCidPinned: async (cid) => {
444
+ const kuboUrl = getIpfsApiUrl();
445
+ try {
446
+ const res = await fetch(`${kuboUrl}/api/v0/pin/ls?arg=${encodeURIComponent(cid)}&type=all`, { method: 'POST' });
447
+ if (!res.ok) return false;
448
+ const json = await res.json();
449
+ return !!(json.Keys && json.Keys[cid]);
450
+ } catch {
451
+ return false;
452
+ }
453
+ },
431
454
  };
432
455
 
433
456
  export { IpfsClient };
@@ -639,11 +639,12 @@ export class ObjectLayerEngine {
639
639
 
640
640
  /**
641
641
  * Creates new ObjectLayerRenderFrames and ObjectLayer documents in MongoDB from the
642
- * provided data, computes an initial SHA-256, and optionally generates the atlas sprite sheet.
642
+ * provided data, with cut-over consistency: all CIDs (atlas PNG, atlas metadata, data JSON)
643
+ * are computed and pinned to IPFS BEFORE the ObjectLayer document is created or updated in
644
+ * MongoDB, so the object layer is never visible in queries with empty CIDs.
643
645
  *
644
- * When `generateAtlas` is `true` (the default) the method delegates to
645
- * `AtlasSpriteSheetService.generate` and then recomputes the definitive SHA-256
646
- * (which now includes `data.render.cid`) and persists an IPFS CID.
646
+ * If an existing ObjectLayer with the same `data.item.id` is found, it is atomically
647
+ * replaced (findByIdAndUpdate) and the old ObjectLayerRenderFrames is cleaned up.
647
648
  *
648
649
  * @static
649
650
  * @param {Object} params - Parameters.
@@ -664,44 +665,95 @@ export class ObjectLayerEngine {
664
665
  }) {
665
666
  const { generateAtlas = true, atlasServiceContext = null } = createOptions;
666
667
 
667
- // 1. Persist ObjectLayerRenderFrames
668
+ // 1. Persist ObjectLayerRenderFrames (not queried by the viewer table)
668
669
  const objectLayerRenderFramesDoc = await ObjectLayerRenderFrames.create(objectLayerRenderFramesData);
669
670
 
670
- // 2. Attach reference + compute temporary SHA-256
671
+ // 2. Set up references in memory
671
672
  objectLayerData.objectLayerRenderFramesId = objectLayerRenderFramesDoc._id;
672
673
  if (!objectLayerData.data.render) objectLayerData.data.render = {};
673
- objectLayerData.data.render.cid = objectLayerData.data.render.cid || '';
674
- objectLayerData.sha256 = ObjectLayerEngine.computeSha256(objectLayerData.data);
674
+ objectLayerData.data.render.cid = '';
675
+ objectLayerData.data.render.metadataCid = '';
675
676
 
676
- // 3. Upsert ObjectLayer (handle duplicate sha256 gracefully)
677
+ // 3. Stage atlas + IPFS CIDs BEFORE creating/updating the ObjectLayer
678
+ if (generateAtlas && atlasServiceContext) {
679
+ const {
680
+ req,
681
+ res,
682
+ options,
683
+ AtlasSpriteSheetService,
684
+ IpfsClient: ipfsClient,
685
+ createPinRecord,
686
+ } = atlasServiceContext;
687
+
688
+ try {
689
+ const stagingOL = {
690
+ data: objectLayerData.data,
691
+ objectLayerRenderFramesId: objectLayerRenderFramesDoc,
692
+ };
693
+ const result = await AtlasSpriteSheetService.generate(
694
+ { objectLayer: stagingOL, auth: req ? req.auth : undefined },
695
+ res,
696
+ options,
697
+ { skipObjectLayerSave: true },
698
+ );
699
+ objectLayerData.data.render.cid = result.atlasCid;
700
+ objectLayerData.data.render.metadataCid = result.atlasMetadataCid;
701
+ objectLayerData.atlasSpriteSheetId = result.atlasDoc._id;
702
+ } catch (atlasError) {
703
+ logger.error('Failed to generate atlas during staging:', atlasError);
704
+ }
705
+
706
+ // Compute final SHA-256 (includes render CIDs)
707
+ objectLayerData.sha256 = ObjectLayerEngine.computeSha256(objectLayerData.data);
708
+
709
+ // Pin data JSON to IPFS
710
+ if (ipfsClient) {
711
+ try {
712
+ const itemId = objectLayerData.data.item.id;
713
+ const mfsPath = `/object-layer/${itemId}/${itemId}_data.json`;
714
+ const ipfsResult = await ipfsClient.addJsonToIpfs(objectLayerData.data, `${itemId}_data.json`, mfsPath);
715
+ if (ipfsResult) {
716
+ objectLayerData.cid = ipfsResult.cid;
717
+ if (createPinRecord) {
718
+ await createPinRecord({ cid: ipfsResult.cid, resourceType: 'object-layer-data', mfsPath, options });
719
+ }
720
+ }
721
+ } catch (ipfsError) {
722
+ logger.warn('Failed to pin data JSON to IPFS:', ipfsError.message);
723
+ }
724
+ }
725
+ } else {
726
+ // No atlas generation - compute SHA-256 without render CIDs
727
+ objectLayerData.sha256 = ObjectLayerEngine.computeSha256(objectLayerData.data);
728
+ }
729
+
730
+ // 4. Atomic create/upsert - ObjectLayer is fully populated with all CIDs
677
731
  let objectLayer;
678
- const existingObjectLayer = await ObjectLayer.findOne({ sha256: objectLayerData.sha256 });
679
- if (existingObjectLayer) {
680
- logger.info(`ObjectLayer with sha256 ${objectLayerData.sha256} already exists, updating...`);
681
- objectLayer = await ObjectLayer.findByIdAndUpdate(existingObjectLayer._id, objectLayerData, {
732
+ const existingByItemId = await ObjectLayer.findOne({ 'data.item.id': objectLayerData.data.item.id });
733
+ if (existingByItemId) {
734
+ const oldRenderFramesId = existingByItemId.objectLayerRenderFramesId;
735
+ logger.info(
736
+ `ObjectLayer for item "${objectLayerData.data.item.id}" exists (${existingByItemId._id}), replacing atomically...`,
737
+ );
738
+ objectLayer = await ObjectLayer.findByIdAndUpdate(existingByItemId._id, objectLayerData, {
682
739
  returnDocument: 'after',
683
740
  }).populate('objectLayerRenderFramesId');
741
+ if (oldRenderFramesId && !oldRenderFramesId.equals(objectLayerRenderFramesDoc._id)) {
742
+ await ObjectLayerRenderFrames.findByIdAndDelete(oldRenderFramesId);
743
+ }
684
744
  } else {
685
745
  objectLayer = await (await ObjectLayer.create(objectLayerData)).populate('objectLayerRenderFramesId');
686
746
  logger.info(`ObjectLayer created successfully with id: ${objectLayer._id}`);
687
747
  }
688
748
 
689
- // 4. Optional atlas generation + final SHA-256 / IPFS CID
690
- if (generateAtlas && atlasServiceContext) {
691
- objectLayer = await ObjectLayerEngine._generateAtlasAndFinalize({
692
- objectLayer,
693
- ObjectLayer,
694
- atlasServiceContext,
695
- isNew: true,
696
- });
697
- }
698
-
699
749
  return { objectLayer, objectLayerRenderFramesDoc };
700
750
  }
701
751
 
702
752
  /**
703
753
  * Updates an existing ObjectLayer and its ObjectLayerRenderFrames from the provided data,
704
- * recomputes SHA-256, and optionally regenerates the atlas sprite sheet.
754
+ * with cut-over consistency: a new ObjectLayerRenderFrames is created, then atlas and all
755
+ * CIDs are staged BEFORE the live ObjectLayer is touched. The live document is updated
756
+ * atomically via findByIdAndUpdate only after all CIDs are computed.
705
757
  *
706
758
  * @static
707
759
  * @param {Object} params - Parameters.
@@ -724,27 +776,75 @@ export class ObjectLayerEngine {
724
776
  }) {
725
777
  const { generateAtlas = true, atlasServiceContext = null } = updateOptions;
726
778
 
727
- // 1. Update or create ObjectLayerRenderFrames
728
- let objectLayerRenderFramesDoc;
779
+ // 1. Load existing ObjectLayer to get old references
729
780
  const existingObjectLayer = await ObjectLayer.findById(objectLayerId);
730
- if (existingObjectLayer && existingObjectLayer.objectLayerRenderFramesId) {
731
- objectLayerRenderFramesDoc = await ObjectLayerRenderFrames.findByIdAndUpdate(
732
- existingObjectLayer.objectLayerRenderFramesId,
733
- objectLayerRenderFramesData,
734
- { returnDocument: 'after' },
735
- );
736
- objectLayerData.objectLayerRenderFramesId = existingObjectLayer.objectLayerRenderFramesId;
737
- } else {
738
- objectLayerRenderFramesDoc = await ObjectLayerRenderFrames.create(objectLayerRenderFramesData);
739
- objectLayerData.objectLayerRenderFramesId = objectLayerRenderFramesDoc._id;
781
+ if (!existingObjectLayer) {
782
+ throw new Error('ObjectLayer not found for update');
740
783
  }
784
+ const oldRenderFramesId = existingObjectLayer.objectLayerRenderFramesId;
785
+
786
+ // 2. Create NEW RenderFrames (avoid mutating old doc mid-update)
787
+ const objectLayerRenderFramesDoc = await ObjectLayerRenderFrames.create(objectLayerRenderFramesData);
788
+ objectLayerData.objectLayerRenderFramesId = objectLayerRenderFramesDoc._id;
741
789
 
742
- // 2. Compute temporary SHA-256
790
+ // 3. Set up render CIDs
743
791
  if (!objectLayerData.data.render) objectLayerData.data.render = {};
744
- objectLayerData.data.render.cid = objectLayerData.data.render.cid || '';
745
- objectLayerData.sha256 = ObjectLayerEngine.computeSha256(objectLayerData.data);
792
+ objectLayerData.data.render.cid = '';
793
+ objectLayerData.data.render.metadataCid = '';
794
+
795
+ // 4. Stage atlas + IPFS CIDs BEFORE updating the ObjectLayer
796
+ if (generateAtlas && atlasServiceContext) {
797
+ const {
798
+ req,
799
+ res,
800
+ options,
801
+ AtlasSpriteSheetService,
802
+ IpfsClient: ipfsClient,
803
+ createPinRecord,
804
+ } = atlasServiceContext;
805
+
806
+ try {
807
+ const stagingOL = {
808
+ data: objectLayerData.data,
809
+ objectLayerRenderFramesId: objectLayerRenderFramesDoc,
810
+ };
811
+ const result = await AtlasSpriteSheetService.generate(
812
+ { objectLayer: stagingOL, auth: req ? req.auth : undefined },
813
+ res,
814
+ options,
815
+ { skipObjectLayerSave: true },
816
+ );
817
+ objectLayerData.data.render.cid = result.atlasCid;
818
+ objectLayerData.data.render.metadataCid = result.atlasMetadataCid;
819
+ objectLayerData.atlasSpriteSheetId = result.atlasDoc._id;
820
+ } catch (atlasError) {
821
+ logger.error('Failed to generate atlas during update staging:', atlasError);
822
+ }
823
+
824
+ // Compute final SHA-256 (includes render CIDs)
825
+ objectLayerData.sha256 = ObjectLayerEngine.computeSha256(objectLayerData.data);
746
826
 
747
- // 3. Persist ObjectLayer update
827
+ // Pin data JSON to IPFS
828
+ if (ipfsClient) {
829
+ try {
830
+ const itemId = objectLayerData.data.item.id;
831
+ const mfsPath = `/object-layer/${itemId}/${itemId}_data.json`;
832
+ const ipfsResult = await ipfsClient.addJsonToIpfs(objectLayerData.data, `${itemId}_data.json`, mfsPath);
833
+ if (ipfsResult) {
834
+ objectLayerData.cid = ipfsResult.cid;
835
+ if (createPinRecord) {
836
+ await createPinRecord({ cid: ipfsResult.cid, resourceType: 'object-layer-data', mfsPath, options });
837
+ }
838
+ }
839
+ } catch (ipfsError) {
840
+ logger.warn('Failed to pin data JSON to IPFS:', ipfsError.message);
841
+ }
842
+ }
843
+ } else {
844
+ objectLayerData.sha256 = ObjectLayerEngine.computeSha256(objectLayerData.data);
845
+ }
846
+
847
+ // 5. Atomic update - ObjectLayer is fully populated with all CIDs
748
848
  let objectLayer;
749
849
  try {
750
850
  objectLayer = await ObjectLayer.findByIdAndUpdate(objectLayerId, objectLayerData, {
@@ -753,20 +853,15 @@ export class ObjectLayerEngine {
753
853
  if (!objectLayer) {
754
854
  throw new Error('ObjectLayer not found for update');
755
855
  }
756
- logger.info(`ObjectLayer updated successfully with id: ${objectLayerId}`);
856
+ logger.info(`ObjectLayer updated atomically with id: ${objectLayerId}`);
757
857
  } catch (error) {
758
858
  logger.error('Error updating ObjectLayer:', error);
759
859
  throw error;
760
860
  }
761
861
 
762
- // 4. Optional atlas generation + final SHA-256 / IPFS CID
763
- if (generateAtlas && atlasServiceContext) {
764
- objectLayer = await ObjectLayerEngine._generateAtlasAndFinalize({
765
- objectLayer,
766
- ObjectLayer,
767
- atlasServiceContext,
768
- isNew: false,
769
- });
862
+ // 6. Clean up old RenderFrames
863
+ if (oldRenderFramesId && !oldRenderFramesId.equals(objectLayerRenderFramesDoc._id)) {
864
+ await ObjectLayerRenderFrames.findByIdAndDelete(oldRenderFramesId);
770
865
  }
771
866
 
772
867
  return { objectLayer, objectLayerRenderFramesDoc };
@@ -783,26 +878,22 @@ export class ObjectLayerEngine {
783
878
  * @param {Object} params.objectLayer - The mongoose ObjectLayer document (must be populated).
784
879
  * @param {Object} [params.ipfsClient=null] - The IpfsClient module; when `null`, IPFS pinning is skipped.
785
880
  * @param {function} [params.createPinRecord=null] - The `createPinRecord` helper; when `null`, pin records are skipped.
786
- * @param {string} [params.userId] - Authenticated user ID for IPFS pin record creation.
787
881
  * @param {Object} [params.options] - Server options (host, path) forwarded to `createPinRecord`.
788
882
  * @returns {Promise<Object>} The saved ObjectLayer document.
789
883
  * @memberof CyberiaObjectLayer
790
884
  */
791
- static async computeAndSaveFinalSha256({ objectLayer, ipfsClient = null, createPinRecord = null, userId, options }) {
885
+ static async computeAndSaveFinalSha256({ objectLayer, ipfsClient = null, createPinRecord = null, options }) {
792
886
  const finalSha256 = ObjectLayerEngine.computeSha256(objectLayer.data);
793
887
 
794
888
  if (ipfsClient) {
795
889
  try {
796
890
  const itemId = objectLayer.data.item.id;
797
- const ipfsResult = await ipfsClient.addJsonToIpfs(
798
- objectLayer.data,
799
- `${itemId}_data.json`,
800
- `/object-layer/${itemId}/${itemId}_data.json`,
801
- );
891
+ const mfsPath = `/object-layer/${itemId}/${itemId}_data.json`;
892
+ const ipfsResult = await ipfsClient.addJsonToIpfs(objectLayer.data, `${itemId}_data.json`, mfsPath);
802
893
  if (ipfsResult) {
803
894
  objectLayer.cid = ipfsResult.cid;
804
- if (userId && createPinRecord) {
805
- await createPinRecord({ cid: ipfsResult.cid, userId, options });
895
+ if (createPinRecord) {
896
+ await createPinRecord({ cid: ipfsResult.cid, resourceType: 'object-layer-data', mfsPath, options });
806
897
  }
807
898
  }
808
899
  } catch (ipfsError) {
@@ -882,58 +973,8 @@ export class ObjectLayerEngine {
882
973
  );
883
974
  return { cid: `sha256:${sha256}`, sha256, source: 'sha256-fallback' };
884
975
  }
885
-
886
- /**
887
- * Internal helper that generates an atlas sprite sheet and then finalizes the SHA-256 / IPFS CID.
888
- * @static
889
- * @param {Object} params - Parameters.
890
- * @param {Object} params.objectLayer - The mongoose ObjectLayer document.
891
- * @param {Object} params.ObjectLayer - Mongoose ObjectLayer model (for re-reading).
892
- * @param {Object} params.atlasServiceContext - Context with `{ req, res, options, AtlasSpriteSheetService, IpfsClient, createPinRecord }`.
893
- * @param {boolean} params.isNew - Whether this is a newly created object layer (used for logging).
894
- * @returns {Promise<Object>} The finalized ObjectLayer document.
895
- * @memberof CyberiaObjectLayer
896
- * @private
897
- */
898
- static async _generateAtlasAndFinalize({ objectLayer, ObjectLayer, atlasServiceContext, isNew }) {
899
- const { req, res, options, AtlasSpriteSheetService, IpfsClient: ipfsClient, createPinRecord } = atlasServiceContext;
900
-
901
- // Generate atlas sprite sheet
902
- try {
903
- await AtlasSpriteSheetService.generate(
904
- { params: { id: objectLayer._id }, objectLayer, auth: req ? req.auth : undefined },
905
- res,
906
- options,
907
- );
908
- } catch (atlasError) {
909
- logger.error(`Failed to auto-${isNew ? 'generate' : 'update'} atlas for ObjectLayer:`, atlasError);
910
- }
911
-
912
- // Re-read the objectLayer so data.render.cid is up-to-date
913
- objectLayer = await ObjectLayer.findById(objectLayer._id).populate('objectLayerRenderFramesId');
914
-
915
- // Compute definitive SHA-256 and IPFS CID
916
- const userId = req && req.auth && req.auth.user ? req.auth.user._id : undefined;
917
- objectLayer = await ObjectLayerEngine.computeAndSaveFinalSha256({
918
- objectLayer,
919
- ipfsClient: ipfsClient || null,
920
- createPinRecord: createPinRecord || null,
921
- userId,
922
- options,
923
- });
924
-
925
- return objectLayer;
926
- }
927
976
  }
928
977
 
929
- /**
930
- * Mapping of item type names to numerical IDs.
931
- * @constant
932
- * @type {{floor: number, skin: number, weapon: number, skill: number, coin: number}}
933
- * @memberof CyberiaObjectLayer
934
- */
935
- export const itemTypes = { floor: 0, skin: 1, weapon: 2, skill: 3, coin: 4 };
936
-
937
978
  // ──────────────────────────────────────────────────────────────────────────
938
979
  // Backward-compatible named exports matching the original destructured imports.
939
980
  // ──────────────────────────────────────────────────────────────────────────
@@ -44,6 +44,14 @@ const logger = loggerFactory(import.meta);
44
44
  */
45
45
  const createPeerServer = async ({ port, origins, path }) => {
46
46
  logger.info('origins', origins);
47
+
48
+ // In development, allow the local client origin (peer runs on port+1 relative to the client)
49
+ if (process.env.NODE_ENV === 'development') {
50
+ const clientPort = port - 1;
51
+ const devOrigin = `http://localhost:${clientPort}`;
52
+ if (!origins.includes(devOrigin)) origins.push(devOrigin);
53
+ }
54
+
47
55
  /** @type {import('peer').IConfig} */
48
56
  const options = {
49
57
  port,
@@ -11,6 +11,7 @@ import * as promClient from 'prom-client';
11
11
  import { loggerFactory } from './logger.js';
12
12
  import { newInstance } from '../client/components/core/CommonJs.js';
13
13
  import { Lampp } from '../runtime/lampp/Lampp.js';
14
+ import { WpService } from '../runtime/wp/Wp.js';
14
15
  import { getInstanceContext, readConfJson } from './conf.js';
15
16
 
16
17
  import ExpressService from '../runtime/express/Express.js';
@@ -71,6 +72,9 @@ const buildRuntime = async () => {
71
72
  valkey,
72
73
  apiBaseHost,
73
74
  useLocalSsl,
75
+ grpc,
76
+ repository,
77
+ wp,
74
78
  } = confServer[host][path];
75
79
 
76
80
  // Calculate context data
@@ -116,6 +120,7 @@ const buildRuntime = async () => {
116
120
  peer,
117
121
  valkey,
118
122
  apiBaseHost,
123
+ grpc,
119
124
  redirectTarget,
120
125
  rootHostPath,
121
126
  confSSR,
@@ -129,7 +134,7 @@ const buildRuntime = async () => {
129
134
 
130
135
  case 'lampp':
131
136
  {
132
- const { disabled } = await Lampp.createApp({
137
+ const { disabled } = Lampp.createApp({
133
138
  port,
134
139
  host,
135
140
  path,
@@ -143,6 +148,25 @@ const buildRuntime = async () => {
143
148
  await Underpost.start.listenPortController(Underpost.start.listenServerFactory(), port, runningData);
144
149
  }
145
150
  break;
151
+
152
+ case 'wp':
153
+ {
154
+ const { disabled } = WpService.createApp({
155
+ port,
156
+ host,
157
+ pathRoute: path,
158
+ repository,
159
+ db,
160
+ wp,
161
+ redirect,
162
+ redirectTarget,
163
+ resetRouter: currentPort === initPort,
164
+ });
165
+ if (disabled) continue;
166
+ await Underpost.start.listenPortController(Underpost.start.listenServerFactory(), port, runningData);
167
+ }
168
+ break;
169
+
146
170
  default:
147
171
  break;
148
172
  }