@underpostnet/cyberia 3.3.73 → 3.3.77
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +17 -5
- package/.github/workflows/coverall.cyberia.ci.yml +10 -0
- package/.github/workflows/docker-image.cyberia-client.ci.yml +1 -1
- package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +1 -1
- package/.github/workflows/docker-image.cyberia-server.ci.yml +1 -1
- package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +1 -1
- package/.github/workflows/docker-image.engine-cyberia.ci.yml +1 -1
- package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +1 -1
- package/.github/workflows/engine-cyberia.ci.yml +1 -1
- package/.github/workflows/publish.cyberia.ci.yml +44 -0
- package/AGENTS.md +61 -12
- package/CHANGELOG.md +159 -161
- package/CLI-HELP.md +20 -13
- package/Dockerfile +1 -1
- package/Dockerfile.dev +1 -1
- package/Dockerfile.test +2 -2
- package/README.md +28 -28
- package/bin/build.js +96 -21
- package/bin/cyberia.js +1247 -1373
- package/bin/index.js +1247 -1373
- package/compose.env +17 -5
- package/conf.js +7 -4
- package/deploy/cyberia-client/deploy.sh +10 -11
- package/deploy/cyberia-client/package.sh +0 -1
- package/deploy/cyberia-client/state.sh +3 -2
- package/deploy/cyberia-server/deploy.sh +10 -11
- package/deploy/cyberia-server/package.sh +0 -1
- package/deploy/cyberia-server/state.sh +3 -2
- package/deploy/dd-cyberia/deploy.sh +5 -5
- package/deploy/dd-cyberia/init.sh +15 -14
- package/deploy/dd-cyberia/package.sh +0 -1
- package/deploy/dd-cyberia/state.sh +2 -2
- package/deploy/dd-cyberia/sync-deploy.sh +139 -75
- package/deploy/lib/config.sh +29 -0
- package/deploy/lib/github-actions-logging.sh +92 -76
- package/deploy/lib/host.sh +28 -19
- package/deploy/lib/state.sh +4 -1
- package/deploy/pwa-microservices-template/deploy.sh +10 -8
- package/deploy/release/deploy.sh +0 -2
- package/deployment.yaml +1 -1
- package/docker-compose.yml +23 -17
- package/docs/coverage/cyberia/api/cyberia-instance/cyberia-fallback-capture.js.html +1591 -0
- package/docs/coverage/cyberia/api/cyberia-instance/index.html +116 -0
- package/docs/coverage/cyberia/api/cyberia-server-defaults/cyberia-server-defaults.js.html +4138 -0
- package/docs/coverage/cyberia/api/cyberia-server-defaults/index.html +116 -0
- package/docs/coverage/cyberia/api/object-layer/index.html +116 -0
- package/docs/coverage/cyberia/api/object-layer/object-layer.model.js.html +1393 -0
- package/docs/coverage/cyberia/base.css +362 -0
- package/docs/coverage/cyberia/block-navigation.js +82 -0
- package/docs/coverage/cyberia/favicon.png +0 -0
- package/docs/coverage/cyberia/index.html +161 -0
- package/docs/coverage/cyberia/prettify.css +101 -0
- package/docs/coverage/cyberia/prettify.js +937 -0
- package/docs/coverage/cyberia/projects/cyberia/atlas-sprite-sheet-generator.js.html +1315 -0
- package/docs/coverage/cyberia/projects/cyberia/index.html +176 -0
- package/docs/coverage/cyberia/projects/cyberia/instance-backup.js.html +715 -0
- package/docs/coverage/cyberia/projects/cyberia/instance-data.js.html +2182 -0
- package/docs/coverage/cyberia/projects/cyberia/shape-generator.js.html +3265 -0
- package/docs/coverage/cyberia/projects/cyberia/stat-balance.js.html +328 -0
- package/docs/coverage/cyberia/sort-arrow-sprite.png +0 -0
- package/docs/coverage/cyberia/sorter.js +205 -0
- package/hardhat/package-lock.json +2 -2
- package/hardhat/package.json +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +1 -1
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/mongo-express/deployment.yaml +46 -27
- package/manifests/deployment/mongo-express/kustomization.yaml +9 -0
- package/manifests/deployment/mongo-express/mongo-express-nodeport.yaml +18 -0
- package/manifests/deployment/mongo-express/service.yaml +16 -0
- package/manifests/deployment/mongo-express-no-auth/disable-mongodb-auth.yaml +16 -0
- package/manifests/deployment/mongo-express-no-auth/kustomization.yaml +16 -0
- package/package.json +19 -17
- package/scripts/test-monitor.sh +3 -3
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +14 -9
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +36 -5
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +5 -0
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +88 -184
- package/src/api/cyberia-action/cyberia-action.router.js +2 -2
- package/src/api/cyberia-action/cyberia-action.service.js +3 -4
- package/src/api/cyberia-audio/cyberia-audio.controller.js +6 -0
- package/src/api/cyberia-audio/cyberia-audio.model.js +61 -0
- package/src/api/cyberia-audio/cyberia-audio.router.js +21 -0
- package/src/api/cyberia-audio/cyberia-audio.service.js +220 -0
- package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +1 -3
- package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +10 -19
- package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +12 -34
- package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +15 -32
- package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +1 -1
- package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +1 -1
- package/src/api/cyberia-entity/cyberia-entity.model.js +2 -0
- package/src/api/cyberia-entity/cyberia-entity.service.js +1 -1
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +8 -2
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.model.js +31 -20
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +16 -1
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +466 -3
- package/src/api/cyberia-instance/cyberia-fallback-capture.js +87 -51
- package/src/api/cyberia-instance/cyberia-fallback-world.js +21 -40
- package/src/api/cyberia-instance/cyberia-instance-boot.service.js +5 -5
- package/src/api/cyberia-instance/cyberia-instance-items.js +98 -0
- package/src/api/cyberia-instance/cyberia-instance-map.service.js +34 -141
- package/src/api/cyberia-instance/cyberia-instance.controller.js +1 -4
- package/src/api/cyberia-instance/cyberia-instance.model.js +0 -14
- package/src/api/cyberia-instance/cyberia-instance.router.js +3 -16
- package/src/api/cyberia-instance/cyberia-instance.service.js +3 -43
- package/src/api/cyberia-instance/cyberia-world-generator.js +8 -13
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +49 -75
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +74 -1
- package/src/api/cyberia-map/cyberia-map.service.js +6 -2
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.controller.js +31 -0
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.model.js +56 -0
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.router.js +33 -0
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +189 -0
- package/src/api/cyberia-quest/cyberia-quest.router.js +3 -3
- package/src/api/cyberia-quest/cyberia-quest.service.js +3 -3
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.model.js +1 -1
- package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +403 -188
- package/src/api/cyberia-skill/cyberia-skill.model.js +6 -6
- package/src/api/file/file.ref.js +153 -0
- package/src/api/file/file.ref.json +11 -2
- package/src/api/object-layer/object-layer.model.js +47 -12
- package/src/api/object-layer/object-layer.router.js +10 -3
- package/src/api/object-layer/object-layer.service.js +113 -120
- package/src/cli/app.js +12 -1
- package/src/cli/client.js +2 -2
- package/src/cli/cluster.js +80 -18
- package/src/cli/deploy.js +215 -34
- package/src/cli/docker-compose.js +1 -1
- package/src/cli/fs-selection.js +115 -0
- package/src/cli/fs.js +158 -342
- package/src/cli/index.js +22 -10
- package/src/cli/release.js +3 -1
- package/src/cli/repository.js +35 -9
- package/src/cli/run.js +80 -199
- package/src/cli/test.js +4 -0
- package/src/cli/wireguard.js +32 -31
- package/src/client/components/core/Badge.js +0 -1
- package/src/client/components/core/CssCore.js +8 -1
- package/src/client/components/core/Docs.js +77 -100
- package/src/client/components/core/Modal.js +77 -23
- package/src/client/components/core/ToolTip.js +15 -0
- package/src/client/components/cryptokoyn/AppShellCryptokoyn.js +0 -2
- package/src/client/components/cyberia/ActionEngineCyberia.js +12 -36
- package/src/client/components/cyberia/EntityEngineCyberia.js +492 -121
- package/src/client/components/cyberia/InstanceEngineCyberia.js +129 -160
- package/src/client/components/cyberia/InstanceSelectionView.js +7 -1
- package/src/client/components/cyberia/MapEngineCyberia.js +175 -69
- package/src/client/components/cyberia/ObjectLayerEngine.js +1011 -97
- package/src/client/components/cyberia/ObjectLayerEngineModal.js +81 -155
- package/src/client/components/cyberia/ObjectLayerEngineViewer.js +113 -98
- package/src/client/components/cyberia/SharedDefaultsCyberia.js +215 -56
- package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +0 -34
- package/src/client/components/cyberia-portal/CssCyberiaPortal.js +1 -1
- package/src/client/components/cyberia-portal/MainBodyCyberiaPortal.js +712 -76
- package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +0 -4
- package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +0 -4
- package/src/client/components/default/AppShellDefault.js +1 -4
- package/src/client/components/itemledger/AppShellItemledger.js +0 -2
- package/src/client/components/underpost/AppShellUnderpost.js +1 -1
- package/src/client/public/cyberia-docs/ARCHITECTURE.md +15 -15
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +280 -24
- package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +51 -29
- package/src/client/public/cyberia-docs/CYBERIA-SAGA.md +52 -53
- package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +16 -11
- package/src/client/public/cyberia-docs/CYBERIA.md +28 -28
- package/src/client/public/cyberia-docs/ENTITY-PROFILE.md +11 -5
- package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
- package/src/client/public/cyberia-docs/STATS-PROGRESSION.md +217 -0
- package/src/client/public/cyberia-docs/WHITE-PAPER.md +20 -12
- package/src/client/services/cyberia-audio/cyberia-audio.service.js +99 -0
- package/src/client/services/cyberia-entity-type-default/cyberia-entity-type-default.service.js +38 -0
- package/src/client/services/cyberia-instance/cyberia-instance.management.js +5 -4
- package/src/client/services/cyberia-instance/cyberia-instance.service.js +0 -40
- package/src/client/services/cyberia-map/cyberia-map.management.js +5 -4
- package/src/client/services/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +138 -0
- package/src/client/services/object-layer/object-layer.management.js +6 -10
- package/src/client/services/object-layer/object-layer.service.js +3 -1
- package/src/client/ssr/views/Cyberia404.js +3 -3
- package/src/client-builder/client-build-docs.js +52 -52
- package/src/client-builder/client-build.js +67 -44
- package/src/client-builder/client-bundle.js +347 -0
- package/src/db/mongo/MongoExpress.js +187 -0
- package/src/grpc/cyberia/grpc-server.js +5 -7
- package/src/index.js +1 -1
- package/src/projects/cyberia/atlas-sprite-sheet-generator.js +143 -146
- package/src/projects/cyberia/atlas-sprite-sheet-store.js +391 -0
- package/src/projects/cyberia/besu-genesis-generator.js +5 -14
- package/src/projects/cyberia/boot-contract-fixtures.js +32 -0
- package/src/projects/cyberia/catalog-cyberia.js +3 -2
- package/src/projects/cyberia/gemini-client.js +3 -3
- package/src/projects/cyberia/generate-saga.js +4 -11
- package/src/projects/cyberia/hot-reload-trigger.js +17 -28
- package/src/projects/cyberia/instance-backup.js +210 -0
- package/src/projects/cyberia/instance-data.js +177 -257
- package/src/projects/cyberia/map-preview-generator.js +32 -68
- package/src/projects/cyberia/object-layer.js +152 -319
- package/src/projects/cyberia/seed-audio.js +139 -0
- package/src/projects/cyberia/semantic-layer-generator-skin.js +2 -8
- package/src/projects/cyberia/semantic-layer-generator.js +2 -18
- package/src/projects/cyberia/stat-balance.js +81 -0
- package/src/projects/cyberia/stat-commands.js +23 -0
- package/src/projects/cyberia/stat-contract-generator.js +101 -0
- package/src/runtime/cyberia-client/Dockerfile +8 -4
- package/src/runtime/cyberia-client/Dockerfile.dev +5 -2
- package/src/runtime/cyberia-server/Dockerfile +9 -2
- package/src/runtime/cyberia-server/Dockerfile.dev +8 -1
- package/src/runtime/engine-cyberia/Dockerfile +1 -1
- package/src/runtime/engine-cyberia/Dockerfile.dev +1 -1
- package/src/runtime/engine-cyberia/Dockerfile.test +2 -2
- package/src/runtime/engine-cyberia/compose.env +17 -5
- package/src/runtime/engine-cyberia/docker-compose.yml +23 -17
- package/src/server/build/coverage.js +113 -42
- package/src/server/build/package.js +55 -12
- package/src/server/build/testing.js +59 -9
- package/src/server/network/middlewares.js +8 -2
- package/src/server/network/underpost-gateway.js +113 -14
- package/src/server/ops/logger.js +70 -35
- package/src/server/runtime/conf.js +40 -9
- package/src/server/runtime/start.js +11 -2
- package/src/server/storage/data-query.js +16 -0
- package/src/server/storage/downloader.js +62 -14
- package/src/server/storage/zip.js +153 -0
- package/test/integration/app/cyberia/atlas-sprite-sheet-store.test.js +402 -0
- package/test/integration/app/cyberia/atlas-sprite-sheet.test.js +86 -0
- package/test/integration/app/cyberia/cyberia-cli-plain-reads.test.js +34 -0
- package/test/integration/app/cyberia/cyberia-entity-type-default.test.js +842 -0
- package/test/integration/app/cyberia/cyberia-instance-conf-coerce.test.js +53 -0
- package/test/integration/app/cyberia/cyberia-instance-conf-defaults.test.js +16 -22
- package/test/integration/app/cyberia/cyberia-instance-items.test.js +90 -0
- package/test/integration/app/cyberia/cyberia-load.test.js +6 -7
- package/test/integration/app/cyberia/cyberia-map-audio-conf.test.js +256 -0
- package/test/integration/app/cyberia/cyberia-stats.test.js +150 -0
- package/test/integration/app/cyberia/fallback-world-capture.test.js +87 -2
- package/test/integration/app/cyberia/object-layer-item-selection.test.js +56 -0
- package/test/integration/app/cyberia/object-layer-natural-key.test.js +32 -0
- package/test/integration/app/cyberia/seed-audio.test.js +275 -0
- package/test/integration/infra/2-network/wireguard-cli.test.js +33 -9
- package/test/integration/infra/2-network/wireguard-edge.test.js +58 -18
- package/test/integration/infra/3-cluster/mongo-express-deploy.test.js +156 -0
- package/test/integration/infra/4-ingress/api-cross-origin.test.js +79 -0
- package/test/integration/infra/4-ingress/underpost-ingress.test.js +1 -1
- package/test/unit/catalog.test.js +13 -2
- package/test/unit/client-build-docs.test.js +97 -70
- package/test/unit/client-bundle.test.js +313 -0
- package/test/unit/conf-resolution.test.js +58 -7
- package/test/unit/coverage-artifact.test.js +120 -29
- package/test/unit/cyberia/instance-backup.test.js +183 -0
- package/test/unit/cyberia/instance-data.test.js +314 -0
- package/test/unit/cyberia/instance-object-layer-items.test.js +46 -0
- package/test/unit/cyberia/publish-workflow.test.js +22 -0
- package/test/unit/cyberia/stat-balance.test.js +69 -0
- package/test/unit/deploy-legacy-gateway-sweep.test.js +63 -0
- package/test/unit/deploy-log-table.test.js +76 -5
- package/test/unit/downloader-integrity.test.js +98 -0
- package/test/unit/file-reference-registry.test.js +141 -0
- package/test/unit/fs-storage-paths.test.js +596 -0
- package/test/unit/gateway-pod-fetch.test.js +95 -0
- package/test/unit/letsencrypt-issuer.test.js +101 -0
- package/test/unit/logger-redaction.test.js +29 -0
- package/test/unit/package.test.js +70 -16
- package/test/unit/prepare-host.test.js +84 -1
- package/test/unit/release-bump.test.js +3 -1
- package/test/unit/start-options.test.js +12 -0
- package/test/unit/test-tiers.test.js +27 -0
- package/test/unit/zip-archive.test.js +142 -0
- package/vitest.config.js +11 -3
- package/src/api/cyberia-instance/cyberia-fallback-default-items.js +0 -63
- package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +0 -368
|
@@ -1,14 +1,462 @@
|
|
|
1
1
|
import { DataBaseProviderService } from '../../db/DataBaseProvider.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';
|
|
2
8
|
import { loggerFactory } from '../../server/ops/logger.js';
|
|
3
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
|
-
|
|
42
|
-
|
|
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 };
|