@things-factory/headless-twin 10.0.15 → 10.0.18
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/dist-server/engine/attention-digest.d.ts +4 -4
- package/dist-server/engine/attention-digest.js +7 -7
- package/dist-server/engine/attention-digest.js.map +1 -1
- package/dist-server/engine/canonical-ingest.d.ts +18 -82
- package/dist-server/engine/canonical-ingest.js +183 -18
- package/dist-server/engine/canonical-ingest.js.map +1 -1
- package/dist-server/engine/command-routing.js +1 -1
- package/dist-server/engine/command-routing.js.map +1 -1
- package/dist-server/engine/declared-stimulus.js +1 -1
- package/dist-server/engine/declared-stimulus.js.map +1 -1
- package/dist-server/engine/energy-topology.js +4 -4
- package/dist-server/engine/energy-topology.js.map +1 -1
- package/dist-server/engine/ingest-dedupe.d.ts +38 -0
- package/dist-server/engine/ingest-dedupe.js +145 -0
- package/dist-server/engine/ingest-dedupe.js.map +1 -0
- package/dist-server/engine/ingest-health.d.ts +90 -10
- package/dist-server/engine/ingest-health.js +125 -16
- package/dist-server/engine/ingest-health.js.map +1 -1
- package/dist-server/engine/integration-probes.d.ts +3 -3
- package/dist-server/engine/integration-probes.js +6 -6
- package/dist-server/engine/integration-probes.js.map +1 -1
- package/dist-server/engine/kpi-baseline.js +4 -4
- package/dist-server/engine/kpi-baseline.js.map +1 -1
- package/dist-server/engine/kpi-fold.d.ts +212 -14
- package/dist-server/engine/kpi-fold.js +374 -21
- package/dist-server/engine/kpi-fold.js.map +1 -1
- package/dist-server/engine/kpi-query.d.ts +11 -11
- package/dist-server/engine/kpi-query.js +281 -36
- package/dist-server/engine/kpi-query.js.map +1 -1
- package/dist-server/engine/live-feed-registry.d.ts +1 -1
- package/dist-server/engine/live-feed-registry.js +1 -1
- package/dist-server/engine/live-feed-registry.js.map +1 -1
- package/dist-server/engine/load-meter.d.ts +1 -1
- package/dist-server/engine/load-meter.js.map +1 -1
- package/dist-server/engine/local-declarations.d.ts +7 -7
- package/dist-server/engine/local-declarations.js +15 -14
- package/dist-server/engine/local-declarations.js.map +1 -1
- package/dist-server/engine/log.d.ts +1 -1
- package/dist-server/engine/log.js +1 -1
- package/dist-server/engine/log.js.map +1 -1
- package/dist-server/engine/measured-estimator.js +1 -1
- package/dist-server/engine/measured-estimator.js.map +1 -1
- package/dist-server/engine/measured-yield.js +1 -1
- package/dist-server/engine/measured-yield.js.map +1 -1
- package/dist-server/engine/model-basis.d.ts +1 -1
- package/dist-server/engine/model-basis.js +1 -1
- package/dist-server/engine/model-basis.js.map +1 -1
- package/dist-server/engine/model-gap.d.ts +3 -3
- package/dist-server/engine/model-gap.js +1 -1
- package/dist-server/engine/model-gap.js.map +1 -1
- package/dist-server/engine/model-vocabulary.js +3 -3
- package/dist-server/engine/model-vocabulary.js.map +1 -1
- package/dist-server/engine/oee-accumulator.d.ts +6 -5
- package/dist-server/engine/oee-accumulator.js +6 -5
- package/dist-server/engine/oee-accumulator.js.map +1 -1
- package/dist-server/engine/property-effects.js +2 -2
- package/dist-server/engine/property-effects.js.map +1 -1
- package/dist-server/engine/read-time.d.ts +22 -0
- package/dist-server/engine/read-time.js +81 -0
- package/dist-server/engine/read-time.js.map +1 -0
- package/dist-server/engine/restart-policy.js +1 -1
- package/dist-server/engine/restart-policy.js.map +1 -1
- package/dist-server/engine/runtime-key.js +1 -1
- package/dist-server/engine/runtime-key.js.map +1 -1
- package/dist-server/engine/spec-coverage.d.ts +2 -2
- package/dist-server/engine/spec-coverage.js +1 -1
- package/dist-server/engine/spec-coverage.js.map +1 -1
- package/dist-server/engine/stage-path.d.ts +4 -4
- package/dist-server/engine/stage-path.js +1 -1
- package/dist-server/engine/stage-path.js.map +1 -1
- package/dist-server/engine/state-axes.d.ts +1 -1
- package/dist-server/engine/state-axes.js +2 -2
- package/dist-server/engine/state-axes.js.map +1 -1
- package/dist-server/engine/travel-estimator.d.ts +2 -2
- package/dist-server/engine/travel-estimator.js +1 -1
- package/dist-server/engine/travel-estimator.js.map +1 -1
- package/dist-server/engine/twin-engine.d.ts +307 -54
- package/dist-server/engine/twin-engine.js +822 -196
- package/dist-server/engine/twin-engine.js.map +1 -1
- package/dist-server/engine/warm-start.d.ts +2 -2
- package/dist-server/engine/warm-start.js +5 -5
- package/dist-server/engine/warm-start.js.map +1 -1
- package/dist-server/index.js +1 -1
- package/dist-server/index.js.map +1 -1
- package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js +1 -1
- package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js.map +1 -1
- package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js +3 -3
- package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js.map +1 -1
- package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js +2 -2
- package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js.map +1 -1
- package/dist-server/migrations/1786300000000-IndexEventTypeByTime.d.ts +5 -0
- package/dist-server/migrations/1786300000000-IndexEventTypeByTime.js +54 -0
- package/dist-server/migrations/1786300000000-IndexEventTypeByTime.js.map +1 -0
- package/dist-server/migrations/1786400000000-IndexEventCreatedAt.d.ts +5 -0
- package/dist-server/migrations/1786400000000-IndexEventCreatedAt.js +44 -0
- package/dist-server/migrations/1786400000000-IndexEventCreatedAt.js.map +1 -0
- package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.d.ts +6 -0
- package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.js +84 -0
- package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.js.map +1 -0
- package/dist-server/migrations/index.js +7 -1
- package/dist-server/migrations/index.js.map +1 -1
- package/dist-server/routes.d.ts +1 -0
- package/dist-server/routes.js +69 -5
- package/dist-server/routes.js.map +1 -1
- package/dist-server/service/index.d.ts +2 -2
- package/dist-server/service/index.js +30 -24
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/reference/control-routing.d.ts +1 -1
- package/dist-server/service/reference/control-routing.js +1 -1
- package/dist-server/service/reference/control-routing.js.map +1 -1
- package/dist-server/service/reference/discovery-result.d.ts +2 -2
- package/dist-server/service/reference/discovery-result.js +3 -3
- package/dist-server/service/reference/discovery-result.js.map +1 -1
- package/dist-server/service/reference/hook-contract.d.ts +43 -0
- package/dist-server/service/reference/hook-contract.js +59 -0
- package/dist-server/service/reference/hook-contract.js.map +1 -0
- package/dist-server/service/reference/ingest-space.d.ts +1 -1
- package/dist-server/service/reference/ingest-space.js +4 -4
- package/dist-server/service/reference/ingest-space.js.map +1 -1
- package/dist-server/service/reference/knob-defaults.d.ts +1 -1
- package/dist-server/service/reference/knob-defaults.js +4 -4
- package/dist-server/service/reference/knob-defaults.js.map +1 -1
- package/dist-server/service/reference/reference-adapter.d.ts +116 -9
- package/dist-server/service/reference/reference-adapter.js +27 -0
- package/dist-server/service/reference/reference-adapter.js.map +1 -1
- package/dist-server/service/reference/reference-hook.d.ts +20 -0
- package/dist-server/service/reference/reference-hook.js +133 -0
- package/dist-server/service/reference/reference-hook.js.map +1 -0
- package/dist-server/service/reference/reference-live.d.ts +2 -2
- package/dist-server/service/reference/reference-live.js +76 -13
- package/dist-server/service/reference/reference-live.js.map +1 -1
- package/dist-server/service/reference/reference-master.d.ts +25 -16
- package/dist-server/service/reference/reference-master.js +35 -13
- package/dist-server/service/reference/reference-master.js.map +1 -1
- package/dist-server/service/reference/reference-resolver.d.ts +1 -1
- package/dist-server/service/reference/reference-resolver.js +12 -12
- package/dist-server/service/reference/reference-resolver.js.map +1 -1
- package/dist-server/service/reference/template-registry.d.ts +1 -1
- package/dist-server/service/reference/template-registry.js.map +1 -1
- package/dist-server/service/reference/twin-reference.d.ts +3 -3
- package/dist-server/service/reference/twin-reference.js.map +1 -1
- package/dist-server/service/twin-attention/twin-attention-query.d.ts +1 -1
- package/dist-server/service/twin-attention/twin-attention-query.js +1 -1
- package/dist-server/service/twin-attention/twin-attention-query.js.map +1 -1
- package/dist-server/service/twin-audit/command-audit.d.ts +1 -1
- package/dist-server/service/twin-audit/command-audit.js.map +1 -1
- package/dist-server/service/twin-audit/twin-audit-event.d.ts +1 -1
- package/dist-server/service/twin-audit/twin-audit-event.js.map +1 -1
- package/dist-server/service/twin-audit/twin-audit-query.js +1 -1
- package/dist-server/service/twin-audit/twin-audit-query.js.map +1 -1
- package/dist-server/service/twin-backfill/backfill-period-facts.d.ts +24 -0
- package/dist-server/service/twin-backfill/backfill-period-facts.js +96 -0
- package/dist-server/service/twin-backfill/backfill-period-facts.js.map +1 -0
- package/dist-server/service/twin-backfill/backfill-shape.d.ts +34 -0
- package/dist-server/service/twin-backfill/backfill-shape.js +75 -0
- package/dist-server/service/twin-backfill/backfill-shape.js.map +1 -0
- package/dist-server/service/twin-backfill/index.d.ts +2 -0
- package/dist-server/service/twin-backfill/index.js +6 -0
- package/dist-server/service/twin-backfill/index.js.map +1 -0
- package/dist-server/service/twin-backfill/twin-backfill-resolver.d.ts +10 -0
- package/dist-server/service/twin-backfill/twin-backfill-resolver.js +44 -0
- package/dist-server/service/twin-backfill/twin-backfill-resolver.js.map +1 -0
- package/dist-server/service/twin-control/twin-control-mutation.js +3 -3
- package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
- package/dist-server/service/twin-event/twin-event-keys.d.ts +1 -1
- package/dist-server/service/twin-event/twin-event-keys.js +2 -2
- package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
- package/dist-server/service/twin-event/twin-event-type.d.ts +1 -0
- package/dist-server/service/twin-event/twin-event-type.js +8 -1
- package/dist-server/service/twin-event/twin-event-type.js.map +1 -1
- package/dist-server/service/twin-event/twin-event.d.ts +2 -2
- package/dist-server/service/twin-event/twin-event.js +32 -4
- package/dist-server/service/twin-event/twin-event.js.map +1 -1
- package/dist-server/service/twin-forecast/forecast-metrics.js +1 -1
- package/dist-server/service/twin-forecast/forecast-metrics.js.map +1 -1
- package/dist-server/service/twin-forecast/gap-analytics.d.ts +1 -1
- package/dist-server/service/twin-forecast/gap-analytics.js.map +1 -1
- package/dist-server/service/twin-forecast/twin-forecast-query.js +29 -11
- package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.d.ts +1 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js +1 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js.map +1 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js +1 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js.map +1 -1
- package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
- package/dist-server/service/twin-journal/event-type-split-shape.d.ts +50 -0
- package/dist-server/service/twin-journal/event-type-split-shape.js +111 -0
- package/dist-server/service/twin-journal/event-type-split-shape.js.map +1 -0
- package/dist-server/service/twin-journal/event-type-split.d.ts +15 -0
- package/dist-server/service/twin-journal/event-type-split.js +102 -0
- package/dist-server/service/twin-journal/event-type-split.js.map +1 -0
- package/dist-server/service/twin-journal/journal-order.d.ts +15 -0
- package/dist-server/service/twin-journal/journal-order.js +17 -0
- package/dist-server/service/twin-journal/journal-order.js.map +1 -0
- package/dist-server/service/twin-journal/twin-journal-query.d.ts +5 -5
- package/dist-server/service/twin-journal/twin-journal-query.js +232 -21
- package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
- package/dist-server/service/twin-lifecycle/domain-catalog.js +2 -1
- package/dist-server/service/twin-lifecycle/domain-catalog.js.map +1 -1
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +27 -1
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +59 -3
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
- package/dist-server/service/twin-metrics/twin-metrics-query.js +14 -1
- package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
- package/dist-server/service/twin-model/axis-journal-evidence.js +1 -1
- package/dist-server/service/twin-model/axis-journal-evidence.js.map +1 -1
- package/dist-server/service/twin-model/epcis-coverage.js +34 -3
- package/dist-server/service/twin-model/epcis-coverage.js.map +1 -1
- package/dist-server/service/twin-model/iec61850-coverage.js +1 -1
- package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -1
- package/dist-server/service/twin-model/isa95-coverage.d.ts +12 -9
- package/dist-server/service/twin-model/isa95-coverage.js +33 -9
- package/dist-server/service/twin-model/isa95-coverage.js.map +1 -1
- package/dist-server/service/twin-model/item-ref.js +2 -2
- package/dist-server/service/twin-model/item-ref.js.map +1 -1
- package/dist-server/service/twin-model/name-index.d.ts +1 -1
- package/dist-server/service/twin-model/name-index.js +2 -2
- package/dist-server/service/twin-model/name-index.js.map +1 -1
- package/dist-server/service/twin-model/project-structure.d.ts +1 -1
- package/dist-server/service/twin-model/project-structure.js +7 -6
- package/dist-server/service/twin-model/project-structure.js.map +1 -1
- package/dist-server/service/twin-model/twin-equipment.d.ts +2 -2
- package/dist-server/service/twin-model/twin-equipment.js +3 -3
- package/dist-server/service/twin-model/twin-equipment.js.map +1 -1
- package/dist-server/service/twin-model/twin-lineage-query.js +16 -5
- package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-location.d.ts +1 -1
- package/dist-server/service/twin-model/twin-location.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-item-query.d.ts +3 -3
- package/dist-server/service/twin-model/twin-model-item-query.js +15 -15
- package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-mutation.d.ts +2 -2
- package/dist-server/service/twin-model/twin-model-mutation.js +4 -4
- package/dist-server/service/twin-model/twin-model-mutation.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-query.js +51 -30
- package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-tree-query.js +23 -1
- package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-operation.d.ts +1 -1
- package/dist-server/service/twin-model/twin-operation.js +1 -1
- package/dist-server/service/twin-model/twin-operation.js.map +1 -1
- package/dist-server/service/twin-space/move-space.js +2 -2
- package/dist-server/service/twin-space/move-space.js.map +1 -1
- package/dist-server/service/twin-space/representation-role.d.ts +2 -0
- package/dist-server/service/twin-space/representation-role.js +18 -0
- package/dist-server/service/twin-space/representation-role.js.map +1 -0
- package/dist-server/service/twin-space/space-integrity.d.ts +1 -1
- package/dist-server/service/twin-space/space-integrity.js.map +1 -1
- package/dist-server/service/twin-space/twin-space-representation.d.ts +2 -0
- package/dist-server/service/twin-space/twin-space-representation.js +16 -0
- package/dist-server/service/twin-space/twin-space-representation.js.map +1 -1
- package/dist-server/service/twin-space/twin-space-resolver.d.ts +2 -2
- package/dist-server/service/twin-space/twin-space-resolver.js +37 -8
- package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
- package/dist-server/service/twin-structure/twin-structure.js +3 -3
- package/dist-server/service/twin-structure/twin-structure.js.map +1 -1
- package/dist-server/service/twin-subject/event-subjects.d.ts +43 -0
- package/dist-server/service/twin-subject/event-subjects.js +137 -0
- package/dist-server/service/twin-subject/event-subjects.js.map +1 -0
- package/dist-server/service/twin-subject/index.d.ts +9 -0
- package/dist-server/service/twin-subject/index.js +16 -0
- package/dist-server/service/twin-subject/index.js.map +1 -0
- package/dist-server/service/twin-subject/item-resolver.d.ts +10 -0
- package/dist-server/service/twin-subject/item-resolver.js +93 -0
- package/dist-server/service/twin-subject/item-resolver.js.map +1 -0
- package/dist-server/service/twin-subject/subject-query-shape.d.ts +22 -0
- package/dist-server/service/twin-subject/subject-query-shape.js +88 -0
- package/dist-server/service/twin-subject/subject-query-shape.js.map +1 -0
- package/dist-server/service/twin-subject/twin-subject-backfill.d.ts +23 -0
- package/dist-server/service/twin-subject/twin-subject-backfill.js +159 -0
- package/dist-server/service/twin-subject/twin-subject-backfill.js.map +1 -0
- package/dist-server/service/twin-subject/twin-subject-event.d.ts +14 -0
- package/dist-server/service/twin-subject/twin-subject-event.js +111 -0
- package/dist-server/service/twin-subject/twin-subject-event.js.map +1 -0
- package/dist-server/service/twin-subject/twin-subject-fast-path.d.ts +39 -0
- package/dist-server/service/twin-subject/twin-subject-fast-path.js +295 -0
- package/dist-server/service/twin-subject/twin-subject-fast-path.js.map +1 -0
- package/dist-server/service/twin-subject/twin-subject-query.d.ts +27 -0
- package/dist-server/service/twin-subject/twin-subject-query.js +232 -0
- package/dist-server/service/twin-subject/twin-subject-query.js.map +1 -0
- package/dist-server/service/twin-subject/twin-subject-writer.d.ts +27 -0
- package/dist-server/service/twin-subject/twin-subject-writer.js +166 -0
- package/dist-server/service/twin-subject/twin-subject-writer.js.map +1 -0
- package/dist-server/service/twin-target/twin-target-resolver.js +1 -1
- package/dist-server/service/twin-target/twin-target-resolver.js.map +1 -1
- package/dist-shared/entity-delta.d.ts +1 -1
- package/dist-shared/entity-delta.js +4 -4
- package/dist-shared/entity-delta.js.map +1 -1
- package/dist-shared/touched-items.js +2 -2
- package/dist-shared/touched-items.js.map +1 -1
- package/dist-shared/twin-level.js +1 -1
- package/dist-shared/twin-level.js.map +1 -1
- package/package.json +7 -6
- package/server/engine/attention-digest.ts +9 -9
- package/server/engine/canonical-ingest.ts +202 -87
- package/server/engine/command-routing.ts +1 -1
- package/server/engine/declared-stimulus.ts +1 -1
- package/server/engine/energy-topology.ts +2 -2
- package/server/engine/ingest-dedupe.ts +149 -0
- package/server/engine/ingest-health.ts +171 -22
- package/server/engine/integration-probes.ts +7 -7
- package/server/engine/kpi-baseline.ts +4 -4
- package/server/engine/kpi-fold.ts +592 -41
- package/server/engine/kpi-query.ts +307 -48
- package/server/engine/live-feed-registry.ts +1 -1
- package/server/engine/load-meter.ts +1 -1
- package/server/engine/local-declarations.ts +15 -14
- package/server/engine/log.ts +1 -1
- package/server/engine/measured-estimator.ts +1 -1
- package/server/engine/measured-yield.ts +1 -1
- package/server/engine/model-basis.ts +2 -2
- package/server/engine/model-gap.ts +3 -3
- package/server/engine/model-vocabulary.ts +2 -2
- package/server/engine/oee-accumulator.ts +8 -6
- package/server/engine/property-effects.ts +2 -2
- package/server/engine/read-time.ts +77 -0
- package/server/engine/restart-policy.ts +1 -1
- package/server/engine/runtime-key.ts +1 -1
- package/server/engine/spec-coverage.ts +3 -3
- package/server/engine/stage-path.ts +5 -5
- package/server/engine/state-axes.ts +2 -2
- package/server/engine/travel-estimator.ts +2 -2
- package/server/engine/twin-engine.ts +906 -205
- package/server/engine/warm-start.ts +6 -6
- package/server/index.ts +1 -1
- package/server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts +1 -1
- package/server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts +3 -3
- package/server/migrations/1786200000000-CarryLiveFeedCursor.ts +2 -2
- package/server/migrations/1786300000000-IndexEventTypeByTime.ts +48 -0
- package/server/migrations/1786400000000-IndexEventCreatedAt.ts +38 -0
- package/server/migrations/1786500000000-CreateTwinSubjectEvents.ts +93 -0
- package/server/migrations/index.ts +7 -1
- package/server/routes.ts +73 -5
- package/server/service/index.ts +7 -1
- package/server/service/reference/control-routing.ts +1 -1
- package/server/service/reference/discovery-result.ts +3 -3
- package/server/service/reference/hook-contract.ts +103 -0
- package/server/service/reference/ingest-space.ts +4 -4
- package/server/service/reference/knob-defaults.ts +4 -4
- package/server/service/reference/reference-adapter.ts +145 -10
- package/server/service/reference/reference-hook.ts +145 -0
- package/server/service/reference/reference-live.ts +87 -15
- package/server/service/reference/reference-master.ts +51 -20
- package/server/service/reference/reference-resolver.ts +11 -11
- package/server/service/reference/template-registry.ts +1 -1
- package/server/service/reference/twin-reference.ts +3 -3
- package/server/service/twin-attention/twin-attention-query.ts +1 -1
- package/server/service/twin-audit/command-audit.ts +1 -1
- package/server/service/twin-audit/twin-audit-event.ts +2 -2
- package/server/service/twin-audit/twin-audit-query.ts +1 -1
- package/server/service/twin-backfill/backfill-period-facts.ts +168 -0
- package/server/service/twin-backfill/backfill-shape.ts +86 -0
- package/server/service/twin-backfill/index.ts +3 -0
- package/server/service/twin-backfill/twin-backfill-resolver.ts +35 -0
- package/server/service/twin-control/twin-control-mutation.ts +2 -2
- package/server/service/twin-event/twin-event-keys.ts +2 -2
- package/server/service/twin-event/twin-event-type.ts +18 -1
- package/server/service/twin-event/twin-event.ts +32 -6
- package/server/service/twin-forecast/forecast-metrics.ts +1 -1
- package/server/service/twin-forecast/gap-analytics.ts +1 -1
- package/server/service/twin-forecast/twin-forecast-query.ts +30 -11
- package/server/service/twin-ingest-window/twin-ingest-window-query.ts +1 -1
- package/server/service/twin-ingest-window/twin-ingest-window-writer.ts +1 -1
- package/server/service/twin-instance/twin-instance.ts +4 -4
- package/server/service/twin-journal/event-type-split-shape.ts +112 -0
- package/server/service/twin-journal/event-type-split.ts +144 -0
- package/server/service/twin-journal/journal-order.ts +20 -0
- package/server/service/twin-journal/twin-journal-query.ts +224 -20
- package/server/service/twin-lifecycle/domain-catalog.ts +2 -1
- package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +58 -4
- package/server/service/twin-metrics/twin-metrics-query.ts +14 -1
- package/server/service/twin-model/axis-journal-evidence.ts +1 -1
- package/server/service/twin-model/epcis-coverage.ts +34 -3
- package/server/service/twin-model/iec61850-coverage.ts +1 -1
- package/server/service/twin-model/isa95-coverage.ts +43 -16
- package/server/service/twin-model/item-ref.ts +2 -2
- package/server/service/twin-model/name-index.ts +2 -2
- package/server/service/twin-model/project-structure.ts +7 -6
- package/server/service/twin-model/twin-equipment.ts +16 -4
- package/server/service/twin-model/twin-lineage-query.ts +16 -5
- package/server/service/twin-model/twin-location.ts +1 -1
- package/server/service/twin-model/twin-model-item-query.ts +15 -15
- package/server/service/twin-model/twin-model-mutation.ts +4 -4
- package/server/service/twin-model/twin-model-query.ts +49 -28
- package/server/service/twin-model/twin-model-tree-query.ts +26 -2
- package/server/service/twin-model/twin-operation.ts +2 -2
- package/server/service/twin-space/move-space.ts +3 -3
- package/server/service/twin-space/representation-role.ts +15 -0
- package/server/service/twin-space/space-integrity.ts +1 -1
- package/server/service/twin-space/twin-space-representation.ts +37 -0
- package/server/service/twin-space/twin-space-resolver.ts +40 -8
- package/server/service/twin-structure/twin-structure.ts +3 -3
- package/server/service/twin-subject/event-subjects.ts +155 -0
- package/server/service/twin-subject/index.ts +10 -0
- package/server/service/twin-subject/item-resolver.ts +92 -0
- package/server/service/twin-subject/subject-query-shape.ts +84 -0
- package/server/service/twin-subject/twin-subject-backfill.ts +196 -0
- package/server/service/twin-subject/twin-subject-event.ts +101 -0
- package/server/service/twin-subject/twin-subject-fast-path.ts +346 -0
- package/server/service/twin-subject/twin-subject-query.ts +210 -0
- package/server/service/twin-subject/twin-subject-writer.ts +189 -0
- package/server/service/twin-target/twin-target-resolver.ts +1 -1
- package/shared/entity-delta.ts +4 -4
- package/shared/touched-items.ts +2 -2
- package/shared/twin-level.ts +1 -1
- package/test/adopt-structure-live.test.ts +3 -3
- package/test/aggregate-not-findone.test.ts +112 -0
- package/test/attention-digest.test.ts +6 -6
- package/test/axis-read.test.ts +6 -5
- package/test/backfill-shape.test.ts +89 -0
- package/test/boot-resume.test.ts +20 -7
- package/test/broadcast-cost-baseline.test.ts +2 -1
- package/test/broadcast-period.test.ts +2 -2
- package/test/broadcast-queue-overflow.test.ts +244 -0
- package/test/canonical-ingest-vocabularies.test.ts +259 -8
- package/test/canonical-quantity-door.test.ts +2 -2
- package/test/capability-mapping.test.ts +1 -1
- package/test/checkpoint-refuses-empty.test.ts +2 -2
- package/test/connector-capability-declaration.test.ts +88 -0
- package/test/contract-layer-guard.test.ts +48 -0
- package/test/control-capability.test.ts +1 -1
- package/test/cursor-stall-not-read-failure.test.ts +2 -2
- package/test/declaration-reaches-model.test.ts +7 -5
- package/test/declared-stimulus.test.ts +4 -4
- package/test/discovery-result.test.ts +2 -2
- package/test/entity-delta.test.ts +4 -4
- package/test/equipment-identity-reaches.test.ts +57 -0
- package/test/event-subjects.test.ts +147 -0
- package/test/event-time-column.test.ts +3 -3
- package/test/event-type-split.test.ts +155 -0
- package/test/fact-scope-wiring.test.ts +96 -0
- package/test/fold-gap-speaks.test.ts +53 -0
- package/test/forecast-metrics.test.ts +1 -1
- package/test/forecast-tuning.test.ts +1 -1
- package/test/generated-not-counted.test.ts +99 -0
- package/test/ingest-bench.test.ts +2 -1
- package/test/ingest-health-engine.test.ts +2 -2
- package/test/ingest-health-wiring.test.ts +7 -5
- package/test/ingest-health.test.ts +6 -6
- package/test/ingest-history.test.ts +2 -2
- package/test/ingest-idempotent.test.ts +132 -0
- package/test/ingest-space.test.ts +3 -3
- package/test/ingest-wiring-guard.test.ts +197 -0
- package/test/integration-probes.test.ts +3 -3
- package/test/integration-runner.test.ts +1 -1
- package/test/item-ref.test.ts +1 -1
- package/test/journal-order-axis.test.ts +80 -0
- package/test/journal-read-discipline.test.ts +16 -6
- package/test/journal-retention.test.ts +171 -29
- package/test/journal-sort-axis.test.ts +1 -1
- package/test/journal-write-door.test.ts +3 -3
- package/test/journal-write-trend.test.ts +1 -1
- package/test/kernel-kind-guard.test.ts +3 -3
- package/test/knob-defaults.test.ts +1 -1
- package/test/kpi-baseline-db.test.ts +2 -2
- package/test/kpi-fold.test.ts +266 -14
- package/test/kpi-query-bench.test.ts +3 -3
- package/test/live-cursor-wiring.test.ts +6 -6
- package/test/live-kernel-facts.test.ts +11 -11
- package/test/live-mirror-parity.test.ts +9 -6
- package/test/load-meter.test.ts +1 -1
- package/test/local-declarations.test.ts +13 -12
- package/test/log-stamp.test.ts +1 -1
- package/test/master-to-twin.test.ts +2 -2
- package/test/measured-yield.test.ts +2 -2
- package/test/merge-streams.test.ts +70 -0
- package/test/mirror-resumes-from-checkpoint.test.ts +13 -13
- package/test/model-basis.test.ts +2 -2
- package/test/model-gap.test.ts +4 -4
- package/test/model-vocabulary.test.ts +2 -2
- package/test/move-space.test.ts +1 -1
- package/test/oee-accumulator.test.ts +20 -5
- package/test/operational-vocabulary.test.ts +2 -2
- package/test/project-structure-db.test.ts +2 -2
- package/test/projection-reaches-screen.test.ts +7 -2
- package/test/projection-reads-declared.test.ts +109 -0
- package/test/property-effects.test.ts +10 -6
- package/test/read-failure-ledger.test.ts +106 -0
- package/test/read-failure-visible.test.ts +7 -7
- package/test/read-time.test.ts +118 -0
- package/test/reference-grounding.test.ts +1 -1
- package/test/reference-hook.test.ts +86 -0
- package/test/registry-key-guard.test.ts +1 -1
- package/test/rename-twin.test.ts +81 -0
- package/test/restart-policy.test.ts +3 -3
- package/test/resync-origin-site.test.ts +3 -3
- package/test/revision-axis.test.ts +14 -8
- package/test/runtime-key.test.ts +3 -3
- package/test/scale-twin-bench.test.ts +2 -1
- package/test/snapshot-freshness.test.ts +4 -4
- package/test/source-outcome-audit.test.ts +1 -1
- package/test/space-integrity.test.ts +1 -1
- package/test/space-representation.test.ts +66 -0
- package/test/space-scope-includes-stopped.test.ts +55 -0
- package/test/spec-coverage.test.ts +1 -1
- package/test/standard-coverage.test.ts +19 -21
- package/test/status-tally.test.ts +1 -1
- package/test/structure-revision-db.test.ts +7 -7
- package/test/subject-fast-path.test.ts +148 -0
- package/test/subject-read-model.test.ts +175 -0
- package/test/touched-items.test.ts +2 -2
- package/test/twin-audit.test.ts +1 -1
- package/test/twin-event-keys.test.ts +1 -1
- package/test/twin-model-item-db.test.ts +5 -5
- package/test/twin-model-tree-db.test.ts +35 -2
- package/test/twin-origin-resync.test.ts +1 -1
- package/test/vocabulary-guard.test.ts +1 -1
- package/test/warm-start-seam.test.ts +1 -1
- package/test/warm-start.test.ts +6 -6
- package/test/withheld-door.test.ts +2 -2
- package/test/yield-loop.test.ts +4 -4
- package/tsconfig.shared.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"move-space.js","sourceRoot":"","sources":["../../../server/service/twin-space/move-space.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 이미 있는 트윈을 다른 현장으로 옮긴다 — **판정만**. 순수.\n *\n * ── 왜 있나 (2026-08-13) ────────────────────────────────────────────────────\n * 트윈↔현장 연결은 **만들 때만** 정할 수 있었다(템플릿 위자드·import 의 `spaceId`). 잘못 넣었거나,\n * 나중에 \"이 창고는 사실 저 공장과 같은 부지다\" 를 알게 되면 지우고 다시 만드는 것이 유일한 길이었다 —\n * 다시 만들면 그 트윈의 저널(일어난 일)이 사라진다. 고칠 수 있는 실수를 데이터 손실로 갚게 하는 설계다.\n *\n * ── 옮기는 것이 무엇을 옮기나 (엔티티 키를 보고 정했다) ──────────────────────\n * 무엇이 트윈에 매여 있고 무엇이 공간에 매여 있는지가 답을 정한다:\n *\n * · `TwinEvent` `(domain, instanceId, …)` → **따라온다.** 저널을 다시 쓰지 않는다.\n * · `TwinLocation/Equipment/Operation` `(domain, instance, …)` → **따라온다.**\n * · `TwinArea` `(domain, space, areaId)` → **남는다.** 구역은 공간의 것이다.\n * · `TwinSpaceRepresentation`(지도·이미지·보드) → **남는다.** 표현도 공간의 것이다.\n *\n * 그래서 옮기기는 이력 이전이 아니라 **소속 갱신**이다(`instance.spaceId` + `model.spaceId`).\n * 대신 공간에 매인 것들과의 연결이 끊길 수 있고, 그것을 **조용히 넘기지 않는 것**이 이 모듈의 일이다.\n *\n * ── 거절과 경고를 가른다 ────────────────────────────────────────────────────\n * 거절: 되돌릴 수 없거나 뜻이 성립하지 않는 것(없는 공간·같은 공간·벤치 사본).\n * 경고: 사용자가 알고 나면 정당할 수 있는 것(구역 소속이 끊긴다·시간대가 다르다·옛 표현이 남는다).\n * 경고를 거절로 올리면 정당한 이전을 막고, 거절을 경고로 낮추면 조용한 사고가 된다.\n */\n\n/** 옮길 수 없는 이유 — 언어 중립 코드(발견 오류·인제스트 거절과 같은 규약). */\nexport type MoveSpaceRefusalCode = 'twin-not-found' | 'space-not-found' | 'same-space' | 'bench-copy'\n\n/** 옮겨도 되지만 알아야 하는 것 — 같은 규약. */\nexport type MoveSpaceWarningCode = 'areas-left-behind' | 'timezone-differs' | 'representations-left-behind' | 'running'\n\nexport interface MoveSpaceNote<C extends string> {\n code: C\n params?: Record<string, string | number>\n /** 영어 canonical — 로그에 남고, 번역이 없으면 그대로 보인다. */\n message: string\n}\n\nexport type MoveSpaceRefusal = MoveSpaceNote<MoveSpaceRefusalCode>\nexport type MoveSpaceWarning = MoveSpaceNote<MoveSpaceWarningCode>\n\nexport interface MoveSpaceInput {\n /** 옮길 트윈. 없으면 거절한다. */\n instanceId?: string\n /** 지금 속한 공간(없을 수 있다 — 옛 트윈). */\n fromSpaceId?: string\n /** 옮겨 갈 공간. */\n toSpaceId: string\n /** 그 공간이 이 테넌트에 실제로 있나(호출측이 조회해 넘긴다). */\n toSpaceExists: boolean\n /** 운영 트윈인가 벤치 사본인가 — 사본은 원본의 공간을 따라야 하므로 옮기지 않는다. */\n purpose?: string\n /** 지금 실행 중인가 — 시간대 변화는 재기동 뒤에 커널에 실린다. */\n running?: boolean\n /** 두 공간의 시간대(IANA). 다르면 앞으로의 시각 해석이 달라진다. */\n fromTimezone?: string\n toTimezone?: string\n /** 이 트윈의 로케이션들이 소속으로 가리키는 구역 id 들(중복 포함 가능). */\n areaRefs?: readonly (string | undefined | null)[]\n /** 옮겨 갈 공간에 있는 구역 id 들. */\n toAreaIds?: readonly string[]\n /** 옛 공간의 표현 수 — 그 표현의 폴리곤이 이 트윈을 가리키고 있었을 수 있다. */\n fromRepresentationCount?: number\n}\n\nexport interface MoveSpaceJudgement {\n /** 있으면 옮기지 않는다. */\n refusal?: MoveSpaceRefusal\n /** 옮기되 함께 알린다. 비어 있으면 잃는 것이 없다. */\n warnings: MoveSpaceWarning[]\n}\n\n/** 옮겨도 되나 — 되면 무엇을 알려야 하나. */\nexport function judgeMoveTwinSpace(input: MoveSpaceInput): MoveSpaceJudgement {\n const to = (input.toSpaceId ?? '').trim()\n const from = (input.fromSpaceId ?? '').trim()\n\n if (!input.instanceId) {\n return {\n refusal: { code: 'twin-not-found', message: 'no such twin in this tenant' },\n warnings: []\n }\n }\n if (!to || !input.toSpaceExists) {\n return {\n refusal: {\n code: 'space-not-found',\n params: { spaceId: to },\n message: `space \"${to}\" not found — cannot move a twin into a space that does not exist`\n },\n warnings: []\n }\n }\n if (from && from === to) {\n return {\n refusal: {\n code: 'same-space',\n params: { spaceId: to },\n message: `twin \"${input.instanceId}\" is already in space \"${to}\"`\n },\n warnings: []\n }\n }\n /* 벤치 사본은 \"이 공장이 이만큼 커지면\" 을 재는 사본이라 원본의 현장에 매여 있다. 옮기면 그 측정이\n 무엇의 측정인지 말할 수 없게 된다 — 사본을 옮기는 대신 원본을 옮기고 사본을 다시 뜬다. */\n if (input.purpose === 'bench') {\n return {\n refusal: {\n code: 'bench-copy',\n params: { instanceId: input.instanceId },\n message: `twin \"${input.instanceId}\" is a bench copy — move its source twin instead`\n },\n warnings: []\n }\n }\n\n const warnings: MoveSpaceWarning[] = []\n\n /* 로케이션의 구역 소속 — 구역은 공간의 것이라 새 공간에 같은 id 가 없으면 소속이 끊긴다.\n 막지는 않는다(새 공간에서 구역을 다시 그리는 것이 정상 경로다). 다만 몇 개가 끊기는지 말한다. */\n const refs = [...new Set((input.areaRefs ?? []).filter((x): x is string => !!x && !!x.trim()))]\n const have = new Set(input.toAreaIds ?? [])\n const orphaned = refs.filter(r => !have.has(r)).sort()\n if (orphaned.length) {\n warnings.push({\n code: 'areas-left-behind',\n params: { count: orphaned.length, areas: orphaned.slice(0, 5).join(', ') },\n message: `${orphaned.length} area reference(s) do not exist in the target space — those locations lose their grouping: ${orphaned\n .slice(0, 5)\n .join(', ')}`\n })\n }\n\n /* 시간대 — 공간이 정하고 커널이 분 오프셋으로 받는다. 다르면 같은 저널이 다른 시각으로 읽힌다. */\n const tzFrom = (input.fromTimezone ?? '').trim()\n const tzTo = (input.toTimezone ?? '').trim()\n if (tzFrom !== tzTo) {\n warnings.push({\n code: 'timezone-differs',\n params: { from: tzFrom || '—', to: tzTo || '—' },\n message: `time zone changes from \"${tzFrom || 'unset'}\" to \"${tzTo || 'unset'}\" — shift boundaries and day buckets will read differently`\n })\n }\n\n /* 옛 공간의 표현 — 폴리곤 바인딩이 이 트윈의 로케이션·그룹을 가리키고 있었을 수 있다. 표현은\n 공간에 남으므로, 그 화면에서 이 트윈이 사라진다. 몇 개가 남는지 말해 두면 사람이 찾아갈 수 있다. */\n if ((input.fromRepresentationCount ?? 0) > 0) {\n warnings.push({\n code: 'representations-left-behind',\n params: { count: input.fromRepresentationCount as number, spaceId: from || '—' },\n message: `${input.fromRepresentationCount} representation(s) stay with space \"${from}\" — bindings that pointed at this twin will no longer resolve there`\n })\n }\n\n /* 실행 중 — 소속은 즉시 바뀌지만 시간대는 커널을 세울 때 실린다. 시간대가 같으면 말할 것이 없다. */\n if (input.running && tzFrom !== tzTo) {\n warnings.push({\n code: 'running',\n params: { instanceId: input.instanceId },\n message: `twin \"${input.instanceId}\" is running — the new time zone applies after it is restarted`\n })\n }\n\n return { warnings }\n}\n\n/*\n * ── 현장에서 떼기 (2026-08-13) ──────────────────────────────────────────────\n *\n * 더하기·옮기기만 있으면 잘못 넣은 것을 되돌릴 수 없다. 떼기는 \"어느 현장에도 속하지 않음\" 으로\n * 되돌리는 일이고, 데이터 모델이 그 상태를 이미 허용한다(`TwinInstance.spaceId` 는 선택 컬럼).\n *\n * 떼면 무엇이 달라지나 — 옮기기와 같은 규칙이지만 **갈 곳이 없다**는 점이 다르다:\n * · 저널·로케이션·설비는 그대로 트윈에 남는다(잃는 것 없음).\n * · 구역·표현은 옛 현장에 남는다 → 그 화면에서 이 트윈이 사라진다.\n * · **볼 무대가 없어진다** — 현장 현황(지도·보드)은 현장의 표현으로 그리므로, 뗀 트윈은 목록과\n * 조회 화면에서만 보인다. 이것을 말하지 않으면 사용자는 트윈이 고장 났다고 읽는다.\n * · 시간 기준(`utcOffsetMinutes`)은 **옛 현장의 것으로 남는다.** 지우면 조용히 UTC 로 읽히므로\n * 그쪽이 더 위험하다 — 남기고 그 사실을 말한다.\n */\n\n/** 뗄 수 없는 이유 — 언어 중립 코드. */\nexport type DetachSpaceRefusalCode = 'twin-not-found' | 'no-space' | 'bench-copy'\n\n/** 떼도 되지만 알아야 하는 것. */\nexport type DetachSpaceWarningCode = 'no-stage' | 'representations-left-behind' | 'timebase-kept'\n\nexport type DetachSpaceRefusal = MoveSpaceNote<DetachSpaceRefusalCode>\nexport type DetachSpaceWarning = MoveSpaceNote<DetachSpaceWarningCode>\n\nexport interface DetachSpaceInput {\n instanceId?: string\n /** 지금 속한 공간 — 없으면 뗄 것이 없다. */\n fromSpaceId?: string\n purpose?: string\n /** 옛 공간의 표현 수. */\n fromRepresentationCount?: number\n /** 옛 공간의 시간대 — 있으면 그 기준이 model 에 남는다. */\n fromTimezone?: string\n}\n\nexport interface DetachSpaceJudgement {\n refusal?: DetachSpaceRefusal\n warnings: DetachSpaceWarning[]\n}\n\n/** 떼도 되나 — 되면 무엇을 알려야 하나. */\nexport function judgeDetachTwinSpace(input: DetachSpaceInput): DetachSpaceJudgement {\n const from = (input.fromSpaceId ?? '').trim()\n\n if (!input.instanceId) {\n return { refusal: { code: 'twin-not-found', message: 'no such twin in this tenant' }, warnings: [] }\n }\n if (!from) {\n return {\n refusal: {\n code: 'no-space',\n params: { instanceId: input.instanceId },\n message: `twin \"${input.instanceId}\" is not on any site — nothing to detach`\n },\n warnings: []\n }\n }\n /* 벤치 사본은 원본의 현장을 따라야 측정이 무엇의 측정인지 말할 수 있다(옮기기와 같은 이유). */\n if (input.purpose === 'bench') {\n return {\n refusal: {\n code: 'bench-copy',\n params: { instanceId: input.instanceId },\n message: `twin \"${input.instanceId}\" is a bench copy — it must stay on the site it measures`\n },\n warnings: []\n }\n }\n\n const warnings: DetachSpaceWarning[] = [\n {\n code: 'no-stage',\n params: { instanceId: input.instanceId },\n message: `twin \"${input.instanceId}\" will have no site to be drawn on — it stays visible in lists and inspection only`\n }\n ]\n\n if ((input.fromRepresentationCount ?? 0) > 0) {\n warnings.push({\n code: 'representations-left-behind',\n params: { count: input.fromRepresentationCount as number, spaceId: from },\n message: `${input.fromRepresentationCount} representation(s) stay with space \"${from}\" — bindings that pointed at this twin will no longer resolve there`\n })\n }\n\n const tz = (input.fromTimezone ?? '').trim()\n if (tz) {\n warnings.push({\n code: 'timebase-kept',\n params: { timezone: tz },\n message: `the time base stays as \"${tz}\" (the site it came from) — clearing it would silently read times as UTC`\n })\n }\n\n return { warnings }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"move-space.js","sourceRoot":"","sources":["../../../server/service/twin-space/move-space.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 이미 있는 트윈을 다른 현장으로 옮긴다 — **판정만**. 순수.\n *\n * ── 왜 있나 (2026-08-13) ────────────────────────────────────────────────────\n * 트윈↔현장 연결은 **만들 때만** 정할 수 있었다(템플릿 위자드·import 의 `spaceId`). 잘못 넣었거나,\n * 나중에 \"이 창고는 사실 저 공장과 같은 부지다\" 를 알게 되면 지우고 다시 만드는 것이 유일한 길이었다 —\n * 다시 만들면 그 트윈의 저널(일어난 일)이 사라진다. 고칠 수 있는 실수를 데이터 손실로 갚게 하는 설계다.\n *\n * ── 옮기는 것이 무엇을 옮기나 (엔티티 키를 보고 정했다) ──────────────────────\n * 무엇이 트윈에 매여 있고 무엇이 공간에 매여 있는지가 답을 정한다:\n *\n * · `TwinEvent` `(domain, instanceId, …)` → **따라온다.** 저널을 다시 쓰지 않는다.\n * · `TwinLocation/Equipment/Operation` `(domain, instance, …)` → **따라온다.**\n * · `TwinArea` `(domain, space, areaId)` → **남는다.** 구역은 공간의 것이다.\n * · `TwinSpaceRepresentation`(지도·이미지·보드) → **남는다.** 표현도 공간의 것이다.\n *\n * 그래서 옮기기는 이력 이전이 아니라 **소속 갱신**이다(`instance.spaceId` + `model.spaceId`).\n * 대신 공간에 매인 것들과의 연결이 끊길 수 있고, 그것을 **알리지 않고 넘기지 않는 것**이 이 모듈의 일이다.\n *\n * ── 거절과 경고를 가른다 ────────────────────────────────────────────────────\n * 거절: 되돌릴 수 없거나 뜻이 성립하지 않는 것(없는 공간·같은 공간·벤치 사본).\n * 경고: 사용자가 알고 나면 정당할 수 있는 것(구역 소속이 끊긴다·시간대가 다르다·옛 표현이 남는다).\n * 경고를 거절로 올리면 정당한 이전을 막고, 거절을 경고로 낮추면 들어온 것이 없는 사고가 된다.\n */\n\n/** 옮길 수 없는 이유 — 언어 중립 코드(발견 오류·인제스트 거절과 같은 규약). */\nexport type MoveSpaceRefusalCode = 'twin-not-found' | 'space-not-found' | 'same-space' | 'bench-copy'\n\n/** 옮겨도 되지만 알아야 하는 것 — 같은 규약. */\nexport type MoveSpaceWarningCode = 'areas-left-behind' | 'timezone-differs' | 'representations-left-behind' | 'running'\n\nexport interface MoveSpaceNote<C extends string> {\n code: C\n params?: Record<string, string | number>\n /** 영어 canonical — 로그에 남고, 번역이 없으면 그대로 보인다. */\n message: string\n}\n\nexport type MoveSpaceRefusal = MoveSpaceNote<MoveSpaceRefusalCode>\nexport type MoveSpaceWarning = MoveSpaceNote<MoveSpaceWarningCode>\n\nexport interface MoveSpaceInput {\n /** 옮길 트윈. 없으면 거절한다. */\n instanceId?: string\n /** 지금 속한 공간(없을 수 있다 — 옛 트윈). */\n fromSpaceId?: string\n /** 옮겨 갈 공간. */\n toSpaceId: string\n /** 그 공간이 이 테넌트에 실제로 있나(호출측이 조회해 넘긴다). */\n toSpaceExists: boolean\n /** 운영 트윈인가 벤치 사본인가 — 사본은 원본의 공간을 따라야 하므로 옮기지 않는다. */\n purpose?: string\n /** 지금 실행 중인가 — 시간대 변화는 재기동 뒤에 커널에 실린다. */\n running?: boolean\n /** 두 공간의 시간대(IANA). 다르면 앞으로의 시각 해석이 달라진다. */\n fromTimezone?: string\n toTimezone?: string\n /** 이 트윈의 로케이션들이 소속으로 가리키는 구역 id 들(중복 포함 가능). */\n areaRefs?: readonly (string | undefined | null)[]\n /** 옮겨 갈 공간에 있는 구역 id 들. */\n toAreaIds?: readonly string[]\n /** 옛 공간의 표현 수 — 그 표현의 폴리곤이 이 트윈을 가리키고 있었을 수 있다. */\n fromRepresentationCount?: number\n}\n\nexport interface MoveSpaceJudgement {\n /** 있으면 옮기지 않는다. */\n refusal?: MoveSpaceRefusal\n /** 옮기되 함께 알린다. 비어 있으면 잃는 것이 없다. */\n warnings: MoveSpaceWarning[]\n}\n\n/** 옮겨도 되나 — 되면 무엇을 알려야 하나. */\nexport function judgeMoveTwinSpace(input: MoveSpaceInput): MoveSpaceJudgement {\n const to = (input.toSpaceId ?? '').trim()\n const from = (input.fromSpaceId ?? '').trim()\n\n if (!input.instanceId) {\n return {\n refusal: { code: 'twin-not-found', message: 'no such twin in this tenant' },\n warnings: []\n }\n }\n if (!to || !input.toSpaceExists) {\n return {\n refusal: {\n code: 'space-not-found',\n params: { spaceId: to },\n message: `space \"${to}\" not found — cannot move a twin into a space that does not exist`\n },\n warnings: []\n }\n }\n if (from && from === to) {\n return {\n refusal: {\n code: 'same-space',\n params: { spaceId: to },\n message: `twin \"${input.instanceId}\" is already in space \"${to}\"`\n },\n warnings: []\n }\n }\n /* 벤치 사본은 \"이 공장이 이만큼 커지면\" 을 재는 사본이라 원본의 현장에 매여 있다. 옮기면 그 측정이\n 무엇의 측정인지 말할 수 없게 된다 — 사본을 옮기는 대신 원본을 옮기고 사본을 다시 뜬다. */\n if (input.purpose === 'bench') {\n return {\n refusal: {\n code: 'bench-copy',\n params: { instanceId: input.instanceId },\n message: `twin \"${input.instanceId}\" is a bench copy — move its source twin instead`\n },\n warnings: []\n }\n }\n\n const warnings: MoveSpaceWarning[] = []\n\n /* 로케이션의 구역 소속 — 구역은 공간의 것이라 새 공간에 같은 id 가 없으면 소속이 끊긴다.\n 막지는 않는다(새 공간에서 구역을 다시 그리는 것이 정상 경로다). 다만 몇 개가 끊기는지 말한다. */\n const refs = [...new Set((input.areaRefs ?? []).filter((x): x is string => !!x && !!x.trim()))]\n const have = new Set(input.toAreaIds ?? [])\n const orphaned = refs.filter(r => !have.has(r)).sort()\n if (orphaned.length) {\n warnings.push({\n code: 'areas-left-behind',\n params: { count: orphaned.length, areas: orphaned.slice(0, 5).join(', ') },\n message: `${orphaned.length} area reference(s) do not exist in the target space — those locations lose their grouping: ${orphaned\n .slice(0, 5)\n .join(', ')}`\n })\n }\n\n /* 시간대 — 공간이 정하고 커널이 분 오프셋으로 받는다. 다르면 같은 저널이 다른 시각으로 읽힌다. */\n const tzFrom = (input.fromTimezone ?? '').trim()\n const tzTo = (input.toTimezone ?? '').trim()\n if (tzFrom !== tzTo) {\n warnings.push({\n code: 'timezone-differs',\n params: { from: tzFrom || '—', to: tzTo || '—' },\n message: `time zone changes from \"${tzFrom || 'unset'}\" to \"${tzTo || 'unset'}\" — shift boundaries and day buckets will read differently`\n })\n }\n\n /* 옛 공간의 표현 — 폴리곤 바인딩이 이 트윈의 로케이션·그룹을 가리키고 있었을 수 있다. 표현은\n 공간에 남으므로, 그 화면에서 이 트윈이 사라진다. 몇 개가 남는지 말해 두면 사람이 찾아갈 수 있다. */\n if ((input.fromRepresentationCount ?? 0) > 0) {\n warnings.push({\n code: 'representations-left-behind',\n params: { count: input.fromRepresentationCount as number, spaceId: from || '—' },\n message: `${input.fromRepresentationCount} representation(s) stay with space \"${from}\" — bindings that pointed at this twin will no longer resolve there`\n })\n }\n\n /* 실행 중 — 소속은 즉시 바뀌지만 시간대는 커널을 세울 때 실린다. 시간대가 같으면 말할 것이 없다. */\n if (input.running && tzFrom !== tzTo) {\n warnings.push({\n code: 'running',\n params: { instanceId: input.instanceId },\n message: `twin \"${input.instanceId}\" is running — the new time zone applies after it is restarted`\n })\n }\n\n return { warnings }\n}\n\n/*\n * ── 현장에서 떼기 (2026-08-13) ──────────────────────────────────────────────\n *\n * 더하기·옮기기만 있으면 잘못 넣은 것을 되돌릴 수 없다. 떼기는 \"어느 현장에도 속하지 않음\" 으로\n * 되돌리는 일이고, 데이터 모델이 그 상태를 이미 허용한다(`TwinInstance.spaceId` 는 선택 컬럼).\n *\n * 떼면 무엇이 달라지나 — 옮기기와 같은 규칙이지만 **갈 곳이 없다**는 점이 다르다:\n * · 저널·로케이션·설비는 그대로 트윈에 남는다(잃는 것 없음).\n * · 구역·표현은 옛 현장에 남는다 → 그 화면에서 이 트윈이 사라진다.\n * · **볼 무대가 없어진다** — 현장 현황(지도·보드)은 현장의 표현으로 그리므로, 뗀 트윈은 목록과\n * 조회 화면에서만 보인다. 이것을 말하지 않으면 사용자는 트윈이 고장 났다고 읽는다.\n * · 시간 기준(`utcOffsetMinutes`)은 **옛 현장의 것으로 남는다.** 지우면 알리지 않고 UTC 로 읽히므로\n * 그쪽이 더 위험하다 — 남기고 그 사실을 말한다.\n */\n\n/** 뗄 수 없는 이유 — 언어 중립 코드. */\nexport type DetachSpaceRefusalCode = 'twin-not-found' | 'no-space' | 'bench-copy'\n\n/** 떼도 되지만 알아야 하는 것. */\nexport type DetachSpaceWarningCode = 'no-stage' | 'representations-left-behind' | 'timebase-kept'\n\nexport type DetachSpaceRefusal = MoveSpaceNote<DetachSpaceRefusalCode>\nexport type DetachSpaceWarning = MoveSpaceNote<DetachSpaceWarningCode>\n\nexport interface DetachSpaceInput {\n instanceId?: string\n /** 지금 속한 공간 — 없으면 뗄 것이 없다. */\n fromSpaceId?: string\n purpose?: string\n /** 옛 공간의 표현 수. */\n fromRepresentationCount?: number\n /** 옛 공간의 시간대 — 있으면 그 기준이 model 에 남는다. */\n fromTimezone?: string\n}\n\nexport interface DetachSpaceJudgement {\n refusal?: DetachSpaceRefusal\n warnings: DetachSpaceWarning[]\n}\n\n/** 떼도 되나 — 되면 무엇을 알려야 하나. */\nexport function judgeDetachTwinSpace(input: DetachSpaceInput): DetachSpaceJudgement {\n const from = (input.fromSpaceId ?? '').trim()\n\n if (!input.instanceId) {\n return { refusal: { code: 'twin-not-found', message: 'no such twin in this tenant' }, warnings: [] }\n }\n if (!from) {\n return {\n refusal: {\n code: 'no-space',\n params: { instanceId: input.instanceId },\n message: `twin \"${input.instanceId}\" is not on any site — nothing to detach`\n },\n warnings: []\n }\n }\n /* 벤치 사본은 원본의 현장을 따라야 측정이 무엇의 측정인지 말할 수 있다(옮기기와 같은 이유). */\n if (input.purpose === 'bench') {\n return {\n refusal: {\n code: 'bench-copy',\n params: { instanceId: input.instanceId },\n message: `twin \"${input.instanceId}\" is a bench copy — it must stay on the site it measures`\n },\n warnings: []\n }\n }\n\n const warnings: DetachSpaceWarning[] = [\n {\n code: 'no-stage',\n params: { instanceId: input.instanceId },\n message: `twin \"${input.instanceId}\" will have no site to be drawn on — it stays visible in lists and inspection only`\n }\n ]\n\n if ((input.fromRepresentationCount ?? 0) > 0) {\n warnings.push({\n code: 'representations-left-behind',\n params: { count: input.fromRepresentationCount as number, spaceId: from },\n message: `${input.fromRepresentationCount} representation(s) stay with space \"${from}\" — bindings that pointed at this twin will no longer resolve there`\n })\n }\n\n const tz = (input.fromTimezone ?? '').trim()\n if (tz) {\n warnings.push({\n code: 'timebase-kept',\n params: { timezone: tz },\n message: `the time base stays as \"${tz}\" (the site it came from) — clearing it would silently read times as UTC`\n })\n }\n\n return { warnings }\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.REPRESENTATION_ROLES = void 0;
|
|
4
|
+
/*
|
|
5
|
+
* 표현이 무엇을 위한 것인가 — **닫힌 목록** (2026-08-27 · 사용자 지시).
|
|
6
|
+
*
|
|
7
|
+
* index 표현 목록에 나온다. 비어 있으면 이것이다(흔한 쪽을 기본으로 둔다)
|
|
8
|
+
* derived 다른 표현에서 열린다(팝업·드릴 대상). 목록에서는 접어 세어 보인다 — 감추지 않는다
|
|
9
|
+
*
|
|
10
|
+
* 열어 두면 넣는 쪽마다 다른 이름을 쓰고, 그러면 화면이 어느 이름을 목록에 넣어야 하는지 알 수 없다.
|
|
11
|
+
* 늘려야 하면 이 목록에 더한다.
|
|
12
|
+
*
|
|
13
|
+
* ── 왜 엔티티가 아니라 이 파일인가 ──────────────────────────────────────────
|
|
14
|
+
* 엔티티에는 데코레이터가 있어서 테스트가 그 파일을 읽을 수 없다(런타임의 타입 제거가 데코레이터를
|
|
15
|
+
* 파싱하지 못한다). 값으로 확인해야 하는 것은 데코레이터 없는 파일에 둔다.
|
|
16
|
+
*/
|
|
17
|
+
exports.REPRESENTATION_ROLES = ['index', 'derived'];
|
|
18
|
+
//# sourceMappingURL=representation-role.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"representation-role.js","sourceRoot":"","sources":["../../../server/service/twin-space/representation-role.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;GAYG;AACU,QAAA,oBAAoB,GAAG,CAAC,OAAO,EAAE,SAAS,CAAU,CAAA","sourcesContent":["/*\n * 표현이 무엇을 위한 것인가 — **닫힌 목록** (2026-08-27 · 사용자 지시).\n *\n * index 표현 목록에 나온다. 비어 있으면 이것이다(흔한 쪽을 기본으로 둔다)\n * derived 다른 표현에서 열린다(팝업·드릴 대상). 목록에서는 접어 세어 보인다 — 감추지 않는다\n *\n * 열어 두면 넣는 쪽마다 다른 이름을 쓰고, 그러면 화면이 어느 이름을 목록에 넣어야 하는지 알 수 없다.\n * 늘려야 하면 이 목록에 더한다.\n *\n * ── 왜 엔티티가 아니라 이 파일인가 ──────────────────────────────────────────\n * 엔티티에는 데코레이터가 있어서 테스트가 그 파일을 읽을 수 없다(런타임의 타입 제거가 데코레이터를\n * 파싱하지 못한다). 값으로 확인해야 하는 것은 데코레이터 없는 파일에 둔다.\n */\nexport const REPRESENTATION_ROLES = ['index', 'derived'] as const\nexport type RepresentationRole = (typeof REPRESENTATION_ROLES)[number]\n"]}
|
|
@@ -22,7 +22,7 @@ export type SpaceIntegrityCode =
|
|
|
22
22
|
*
|
|
23
23
|
* 커널은 모르는 `eventType` 을 버리고(전방 호환) 종류별로 센다(`state.unhandled`). 그 수가 0이
|
|
24
24
|
* 아니면 둘 중 하나다: 커넥터가 엉뚱한 트윈에 붙었거나(예: `energy.*` 가 창고 트윈으로), 커널이
|
|
25
|
-
* 낡아 새 어휘를 모른다. 둘 다 사람이 손봐야 하는 것이고,
|
|
25
|
+
* 낡아 새 어휘를 모른다. 둘 다 사람이 손봐야 하는 것이고, 알리지 않고 두면 「정상인데 빈」 트윈이 된다.
|
|
26
26
|
*/
|
|
27
27
|
| 'twin-unhandled-vocabulary';
|
|
28
28
|
export type SpaceIntegritySeverity = 'broken' | 'warn';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"space-integrity.js","sourceRoot":"","sources":["../../../server/service/twin-space/space-integrity.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 공간 ↔ 트윈 ↔ 가시화 정합성 — **순수**. 세 축이 서로 어긋난 자리를 찾는다.\n *\n * ── 왜 있나 (2026-08-13) ────────────────────────────────────────────────────\n * 이 셋은 서로를 id 로만 가리킨다. 참조 무결성을 DB 가 지켜 주지 않는 지점이 많다:\n * 공간·구역은 공간에 매이고, 로케이션·설비는 트윈에 매이고, 폴리곤은 **양쪽을 가리킨다.**\n * 그래서 트윈을 옮기거나 떼거나(2026-08-13 추가) 원본을 다시 읽으면, 아무 오류 없이 **가리킬 것이\n * 없는 도형**과 **소속 없는 로케이션**이 남는다. 증상은 \"지도가 비었다\" 뿐이고 원인은 화면에 없다.\n *\n * 그 어긋남을 사람이 눈으로 맞추고 있었다(spaceId 를 목록에서 대조). 기계가 대신 본다.\n *\n * ── 무엇을 사실로 보나 ─────────────────────────────────────────────────────\n * 판정은 **조회 결과만** 받는다(DB 를 물지 않는다). 그래서 단위테스트로 전부 부를 수 있고, 호출측은\n * 사실을 모으는 일만 한다. 각 발견은 언어 중립 코드 + 파라미터이고, 화면이 사람 말을 만든다.\n *\n * ── 심각도를 가른다 ────────────────────────────────────────────────────────\n * `broken` 은 무언가 이미 보이지 않는 상태다(가리킬 대상이 없다). `warn` 은 아직 보이지만 뜻이\n * 흐려진 상태다(소속 없음·대표 없음). 섞으면 사람이 무엇을 먼저 볼지 정할 수 없다.\n */\n\nexport type SpaceIntegrityCode =\n /** 트윈이 없는 공간을 가리킨다 — 그 트윈은 무대가 사라졌다. */\n | 'twin-space-missing'\n /** 레지스트리와 model 의 공간이 다르다 — 어느 쪽이 사실인지 알 수 없다. */\n | 'twin-space-mismatch'\n /** 폴리곤이 이 공간에 없는 구역을 가리킨다. */\n | 'polygon-area-missing'\n /** 폴리곤이 이 공간에 사는 어느 트윈에도 없는 로케이션·설비를 가리킨다. */\n | 'polygon-target-missing'\n /** 로케이션이 이 공간에 없는 구역에 소속되어 있다. */\n | 'location-area-missing'\n /** 대표 표현이 이 공간의 표현이 아니다. */\n | 'primary-representation-missing'\n /** 보드 표현이 보드를 가리키지 않는다(또는 없는 보드를 가리킨다). */\n | 'board-representation-unbound'\n /** 표현이 하나도 없는 공간에 트윈이 산다 — 볼 화면이 없다. */\n | 'space-without-representation'\n /** 어느 공간에도 속하지 않은 트윈 — 뗀 결과일 수 있다(사실이면 정상). */\n | 'twin-without-space'\n /**\n * 이 트윈이 **담을 줄 모르는 어휘**를 받고 있다 — 리비전은 오르는데 상태가 채워지지 않는다.\n *\n * 커널은 모르는 `eventType` 을 버리고(전방 호환) 종류별로 센다(`state.unhandled`). 그 수가 0이\n * 아니면 둘 중 하나다: 커넥터가 엉뚱한 트윈에 붙었거나(예: `energy.*` 가 창고 트윈으로), 커널이\n * 낡아 새 어휘를 모른다. 둘 다 사람이 손봐야 하는 것이고, 조용히 두면 「정상인데 빈」 트윈이 된다.\n */\n | 'twin-unhandled-vocabulary'\n\nexport type SpaceIntegritySeverity = 'broken' | 'warn'\n\nexport interface SpaceIntegrityFinding {\n code: SpaceIntegrityCode\n severity: SpaceIntegritySeverity\n /** 어디의 문제인가 — 화면이 그 자리로 데려갈 수 있게. */\n spaceId?: string\n instanceId?: string\n representationId?: string\n params?: Record<string, string | number>\n /** 영어 canonical — 로그·폴백. */\n message: string\n}\n\n/* ── 입력(조회 결과) ──────────────────────────────────────────────────────── */\n\nexport interface IntegritySpace {\n spaceId: string\n primaryRepresentationId?: string | null\n}\n\nexport interface IntegrityInstance {\n instanceId: string\n /** 레지스트리 컬럼. */\n spaceId?: string | null\n /** 저장된 model 의 공간 — 레지스트리와 같아야 한다. */\n modelSpaceId?: string | null\n purpose?: string | null\n /** 이 트윈의 로케이션 id 들. */\n locationIds?: readonly string[]\n /** 이 트윈의 설비 id 들. */\n equipmentIds?: readonly string[]\n /** 로케이션의 소속 구역 id 들(중복 가능). */\n areaRefs?: readonly (string | undefined | null)[]\n /**\n * 커널이 담을 줄 몰라 버린 사건들 — 실행 중인 트윈의 상태에서 온다(`state.unhandled`).\n *\n * 정지한 트윈에서는 알 수 없으므로 `undefined` 다 — **없는 것과 모르는 것을 구별한다**(빈 배열을\n * 넣으면 「받은 것이 없다」는 사실 주장이 되고, 그것은 우리가 확인한 바가 아니다).\n */\n unhandled?: readonly { eventType: string; count: number }[]\n}\n\nexport interface IntegrityRepresentation {\n id: string\n spaceId: string\n type: string\n /** 보드 표현이면 보드 id. */\n boardId?: string | null\n /** 폴리곤 바인딩들. */\n polygons?: readonly IntegrityPolygon[]\n}\n\nexport interface IntegrityPolygon {\n id?: string\n /** 논리 구역 바인딩(그룹). */\n groupId?: string | null\n /** 개체 바인딩 — 로케이션 또는 설비. */\n targetKind?: 'location' | 'equipment' | null\n targetId?: string | null\n}\n\nexport interface SpaceIntegrityInput {\n spaces: readonly IntegritySpace[]\n instances: readonly IntegrityInstance[]\n representations: readonly IntegrityRepresentation[]\n /** 공간별 구역 id 들. */\n areasBySpace: Readonly<Record<string, readonly string[]>>\n /** 존재하는 보드 id 들 — 보드 표현이 실제 보드를 가리키는지 본다. */\n boardIds?: readonly string[]\n}\n\n/** 세 축을 맞대어 어긋난 자리를 모두 낸다. 비어 있으면 정합하다. */\nexport function checkSpaceIntegrity(input: SpaceIntegrityInput): SpaceIntegrityFinding[] {\n const out: SpaceIntegrityFinding[] = []\n const spaceIds = new Set(input.spaces.map(s => s.spaceId))\n const areasOf = (spaceId: string) => new Set(input.areasBySpace[spaceId] ?? [])\n const repsOf = (spaceId: string) => input.representations.filter(r => r.spaceId === spaceId)\n const boards = new Set(input.boardIds ?? [])\n\n /* 벤치 사본은 측정용 사본이라 이 검사의 대상이 아니다 — 사본의 공간은 잠깐 살고 지워진다. */\n const instances = input.instances.filter(i => i.purpose !== 'bench')\n\n /* ── 트윈 → 공간 ─────────────────────────────────────────────────────── */\n for (const t of instances) {\n const sid = (t.spaceId ?? '').trim()\n const msid = (t.modelSpaceId ?? '').trim()\n\n if (!sid) {\n out.push({\n code: 'twin-without-space',\n severity: 'warn',\n instanceId: t.instanceId,\n message: `twin \"${t.instanceId}\" is on no site — it has no map or board to be drawn on`\n })\n } else if (!spaceIds.has(sid)) {\n out.push({\n code: 'twin-space-missing',\n severity: 'broken',\n instanceId: t.instanceId,\n spaceId: sid,\n message: `twin \"${t.instanceId}\" points at site \"${sid}\" which does not exist`\n })\n }\n\n /* 두 곳이 어긋나면 어느 쪽이 사실인지 알 수 없다 — 보드는 model 을, 목록은 컬럼을 읽는다. */\n if (sid !== msid) {\n out.push({\n code: 'twin-space-mismatch',\n severity: 'broken',\n instanceId: t.instanceId,\n spaceId: sid || undefined,\n params: { registry: sid || '—', model: msid || '—' },\n message: `twin \"${t.instanceId}\" registry site \"${sid || 'none'}\" differs from its model site \"${msid || 'none'}\"`\n })\n }\n\n /* 로케이션의 소속 구역은 **트윈이 속한 공간**의 것이어야 한다. */\n if (sid && spaceIds.has(sid)) {\n const have = areasOf(sid)\n const refs = [...new Set((t.areaRefs ?? []).filter((x): x is string => !!x && !!x.trim()))]\n const missing = refs.filter(r => !have.has(r)).sort()\n if (missing.length) {\n out.push({\n code: 'location-area-missing',\n severity: 'warn',\n instanceId: t.instanceId,\n spaceId: sid,\n params: { count: missing.length, areas: missing.slice(0, 5).join(', ') },\n message: `${missing.length} area(s) referenced by locations of \"${t.instanceId}\" do not exist in site \"${sid}\": ${missing\n .slice(0, 5)\n .join(', ')}`\n })\n }\n }\n\n /*\n * 어휘가 맞는가 — 저널이 들어오는데 상태가 채워지지 않는 경우를 잡는다.\n *\n * `undefined` 는 「모른다」다(정지한 트윈). 세지 않는다 — 확인하지 않은 것을 정합으로도, 결함으로도\n * 세지 않는 것이 이 하네스의 규율이다.\n */\n if (t.unhandled?.length) {\n const total = t.unhandled.reduce((n, u) => n + u.count, 0)\n const kinds = [...t.unhandled].sort((a, b) => b.count - a.count)\n out.push({\n code: 'twin-unhandled-vocabulary',\n severity: 'warn',\n instanceId: t.instanceId,\n spaceId: sid || undefined,\n params: { events: total, kinds: kinds.length, types: kinds.slice(0, 3).map(u => u.eventType).join(', ') },\n message: `twin \"${t.instanceId}\" received ${total} event(s) of ${kinds.length} type(s) its kernel does not handle: ${kinds\n .slice(0, 3)\n .map(u => u.eventType)\n .join(', ')}`\n })\n }\n }\n\n /* ── 공간 → 가시화 ───────────────────────────────────────────────────── */\n for (const s of input.spaces) {\n const reps = repsOf(s.spaceId)\n const livesHere = instances.filter(t => (t.spaceId ?? '') === s.spaceId)\n\n /* 트윈이 사는데 표현이 없으면 볼 화면이 없다. 트윈이 없는 빈 현장은 정상이므로 세지 않는다. */\n if (livesHere.length && !reps.length) {\n out.push({\n code: 'space-without-representation',\n severity: 'warn',\n spaceId: s.spaceId,\n params: { twins: livesHere.length },\n message: `site \"${s.spaceId}\" has ${livesHere.length} twin(s) but no representation — there is nothing to draw them on`\n })\n }\n\n const primary = (s.primaryRepresentationId ?? '').trim()\n if (primary && !reps.some(r => r.id === primary)) {\n out.push({\n code: 'primary-representation-missing',\n severity: 'broken',\n spaceId: s.spaceId,\n params: { representationId: primary },\n message: `site \"${s.spaceId}\" points at primary representation \"${primary}\" which is not one of its representations`\n })\n }\n }\n\n /* ── 가시화 → 공간·트윈 ─────────────────────────────────────────────── */\n for (const rep of input.representations) {\n const have = areasOf(rep.spaceId)\n /* 이 공간에 사는 트윈들의 개체 id — 폴리곤이 가리킬 수 있는 전부. */\n const here = instances.filter(t => (t.spaceId ?? '') === rep.spaceId)\n const locationIds = new Set(here.flatMap(t => [...(t.locationIds ?? [])]))\n const equipmentIds = new Set(here.flatMap(t => [...(t.equipmentIds ?? [])]))\n\n if (rep.type === 'board' && !(rep.boardId && boards.has(rep.boardId))) {\n out.push({\n code: 'board-representation-unbound',\n severity: 'broken',\n spaceId: rep.spaceId,\n representationId: rep.id,\n params: { boardId: rep.boardId ?? '—' },\n message: `board representation \"${rep.id}\" of site \"${rep.spaceId}\" does not point at an existing board (${rep.boardId ?? 'none'})`\n })\n }\n\n for (const p of rep.polygons ?? []) {\n const gid = (p.groupId ?? '').trim()\n if (gid && !have.has(gid)) {\n out.push({\n code: 'polygon-area-missing',\n severity: 'broken',\n spaceId: rep.spaceId,\n representationId: rep.id,\n params: { areaId: gid, polygonId: p.id ?? '—' },\n message: `a polygon of representation \"${rep.id}\" binds area \"${gid}\" which does not exist in site \"${rep.spaceId}\"`\n })\n }\n const tid = (p.targetId ?? '').trim()\n if (tid && p.targetKind) {\n const pool = p.targetKind === 'equipment' ? equipmentIds : locationIds\n if (!pool.has(tid)) {\n out.push({\n code: 'polygon-target-missing',\n severity: 'broken',\n spaceId: rep.spaceId,\n representationId: rep.id,\n params: { kind: p.targetKind, targetId: tid, polygonId: p.id ?? '—' },\n message: `a polygon of representation \"${rep.id}\" binds ${p.targetKind} \"${tid}\", which no twin on site \"${rep.spaceId}\" has`\n })\n }\n }\n }\n }\n\n return out\n}\n\n/** 심각한 것부터 — 화면이 정렬을 다시 정하지 않게 한 곳에서 정한다. */\nexport function sortFindings(findings: readonly SpaceIntegrityFinding[]): SpaceIntegrityFinding[] {\n const rank = (s: SpaceIntegritySeverity) => (s === 'broken' ? 0 : 1)\n return [...findings].sort(\n (a, b) => rank(a.severity) - rank(b.severity) || a.code.localeCompare(b.code) || (a.spaceId ?? '').localeCompare(b.spaceId ?? '')\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"space-integrity.js","sourceRoot":"","sources":["../../../server/service/twin-space/space-integrity.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 공간 ↔ 트윈 ↔ 가시화 정합성 — **순수**. 세 축이 서로 어긋난 자리를 찾는다.\n *\n * ── 왜 있나 (2026-08-13) ────────────────────────────────────────────────────\n * 이 셋은 서로를 id 로만 가리킨다. 참조 무결성을 DB 가 지켜 주지 않는 지점이 많다:\n * 공간·구역은 공간에 매이고, 로케이션·설비는 트윈에 매이고, 폴리곤은 **양쪽을 가리킨다.**\n * 그래서 트윈을 옮기거나 떼거나(2026-08-13 추가) 원본을 다시 읽으면, 아무 오류 없이 **가리킬 것이\n * 없는 도형**과 **소속 없는 로케이션**이 남는다. 증상은 \"지도가 비었다\" 뿐이고 원인은 화면에 없다.\n *\n * 그 어긋남을 사람이 눈으로 맞추고 있었다(spaceId 를 목록에서 대조). 기계가 대신 본다.\n *\n * ── 무엇을 사실로 보나 ─────────────────────────────────────────────────────\n * 판정은 **조회 결과만** 받는다(DB 를 물지 않는다). 그래서 단위테스트로 전부 부를 수 있고, 호출측은\n * 사실을 모으는 일만 한다. 각 발견은 언어 중립 코드 + 파라미터이고, 화면이 사람 말을 만든다.\n *\n * ── 심각도를 가른다 ────────────────────────────────────────────────────────\n * `broken` 은 무언가 이미 보이지 않는 상태다(가리킬 대상이 없다). `warn` 은 아직 보이지만 뜻이\n * 흐려진 상태다(소속 없음·대표 없음). 섞으면 사람이 무엇을 먼저 볼지 정할 수 없다.\n */\n\nexport type SpaceIntegrityCode =\n /** 트윈이 없는 공간을 가리킨다 — 그 트윈은 무대가 사라졌다. */\n | 'twin-space-missing'\n /** 레지스트리와 model 의 공간이 다르다 — 어느 쪽이 사실인지 알 수 없다. */\n | 'twin-space-mismatch'\n /** 폴리곤이 이 공간에 없는 구역을 가리킨다. */\n | 'polygon-area-missing'\n /** 폴리곤이 이 공간에 사는 어느 트윈에도 없는 로케이션·설비를 가리킨다. */\n | 'polygon-target-missing'\n /** 로케이션이 이 공간에 없는 구역에 소속되어 있다. */\n | 'location-area-missing'\n /** 대표 표현이 이 공간의 표현이 아니다. */\n | 'primary-representation-missing'\n /** 보드 표현이 보드를 가리키지 않는다(또는 없는 보드를 가리킨다). */\n | 'board-representation-unbound'\n /** 표현이 하나도 없는 공간에 트윈이 산다 — 볼 화면이 없다. */\n | 'space-without-representation'\n /** 어느 공간에도 속하지 않은 트윈 — 뗀 결과일 수 있다(사실이면 정상). */\n | 'twin-without-space'\n /**\n * 이 트윈이 **담을 줄 모르는 어휘**를 받고 있다 — 리비전은 오르는데 상태가 채워지지 않는다.\n *\n * 커널은 모르는 `eventType` 을 버리고(전방 호환) 종류별로 센다(`state.unhandled`). 그 수가 0이\n * 아니면 둘 중 하나다: 커넥터가 엉뚱한 트윈에 붙었거나(예: `energy.*` 가 창고 트윈으로), 커널이\n * 낡아 새 어휘를 모른다. 둘 다 사람이 손봐야 하는 것이고, 알리지 않고 두면 「정상인데 빈」 트윈이 된다.\n */\n | 'twin-unhandled-vocabulary'\n\nexport type SpaceIntegritySeverity = 'broken' | 'warn'\n\nexport interface SpaceIntegrityFinding {\n code: SpaceIntegrityCode\n severity: SpaceIntegritySeverity\n /** 어디의 문제인가 — 화면이 그 자리로 데려갈 수 있게. */\n spaceId?: string\n instanceId?: string\n representationId?: string\n params?: Record<string, string | number>\n /** 영어 canonical — 로그·폴백. */\n message: string\n}\n\n/* ── 입력(조회 결과) ──────────────────────────────────────────────────────── */\n\nexport interface IntegritySpace {\n spaceId: string\n primaryRepresentationId?: string | null\n}\n\nexport interface IntegrityInstance {\n instanceId: string\n /** 레지스트리 컬럼. */\n spaceId?: string | null\n /** 저장된 model 의 공간 — 레지스트리와 같아야 한다. */\n modelSpaceId?: string | null\n purpose?: string | null\n /** 이 트윈의 로케이션 id 들. */\n locationIds?: readonly string[]\n /** 이 트윈의 설비 id 들. */\n equipmentIds?: readonly string[]\n /** 로케이션의 소속 구역 id 들(중복 가능). */\n areaRefs?: readonly (string | undefined | null)[]\n /**\n * 커널이 담을 줄 몰라 버린 사건들 — 실행 중인 트윈의 상태에서 온다(`state.unhandled`).\n *\n * 정지한 트윈에서는 알 수 없으므로 `undefined` 다 — **없는 것과 모르는 것을 구별한다**(빈 배열을\n * 넣으면 「받은 것이 없다」는 사실 주장이 되고, 그것은 우리가 확인한 바가 아니다).\n */\n unhandled?: readonly { eventType: string; count: number }[]\n}\n\nexport interface IntegrityRepresentation {\n id: string\n spaceId: string\n type: string\n /** 보드 표현이면 보드 id. */\n boardId?: string | null\n /** 폴리곤 바인딩들. */\n polygons?: readonly IntegrityPolygon[]\n}\n\nexport interface IntegrityPolygon {\n id?: string\n /** 논리 구역 바인딩(그룹). */\n groupId?: string | null\n /** 개체 바인딩 — 로케이션 또는 설비. */\n targetKind?: 'location' | 'equipment' | null\n targetId?: string | null\n}\n\nexport interface SpaceIntegrityInput {\n spaces: readonly IntegritySpace[]\n instances: readonly IntegrityInstance[]\n representations: readonly IntegrityRepresentation[]\n /** 공간별 구역 id 들. */\n areasBySpace: Readonly<Record<string, readonly string[]>>\n /** 존재하는 보드 id 들 — 보드 표현이 실제 보드를 가리키는지 본다. */\n boardIds?: readonly string[]\n}\n\n/** 세 축을 맞대어 어긋난 자리를 모두 낸다. 비어 있으면 정합하다. */\nexport function checkSpaceIntegrity(input: SpaceIntegrityInput): SpaceIntegrityFinding[] {\n const out: SpaceIntegrityFinding[] = []\n const spaceIds = new Set(input.spaces.map(s => s.spaceId))\n const areasOf = (spaceId: string) => new Set(input.areasBySpace[spaceId] ?? [])\n const repsOf = (spaceId: string) => input.representations.filter(r => r.spaceId === spaceId)\n const boards = new Set(input.boardIds ?? [])\n\n /* 벤치 사본은 측정용 사본이라 이 검사의 대상이 아니다 — 사본의 공간은 잠깐 살고 지워진다. */\n const instances = input.instances.filter(i => i.purpose !== 'bench')\n\n /* ── 트윈 → 공간 ─────────────────────────────────────────────────────── */\n for (const t of instances) {\n const sid = (t.spaceId ?? '').trim()\n const msid = (t.modelSpaceId ?? '').trim()\n\n if (!sid) {\n out.push({\n code: 'twin-without-space',\n severity: 'warn',\n instanceId: t.instanceId,\n message: `twin \"${t.instanceId}\" is on no site — it has no map or board to be drawn on`\n })\n } else if (!spaceIds.has(sid)) {\n out.push({\n code: 'twin-space-missing',\n severity: 'broken',\n instanceId: t.instanceId,\n spaceId: sid,\n message: `twin \"${t.instanceId}\" points at site \"${sid}\" which does not exist`\n })\n }\n\n /* 두 곳이 어긋나면 어느 쪽이 사실인지 알 수 없다 — 보드는 model 을, 목록은 컬럼을 읽는다. */\n if (sid !== msid) {\n out.push({\n code: 'twin-space-mismatch',\n severity: 'broken',\n instanceId: t.instanceId,\n spaceId: sid || undefined,\n params: { registry: sid || '—', model: msid || '—' },\n message: `twin \"${t.instanceId}\" registry site \"${sid || 'none'}\" differs from its model site \"${msid || 'none'}\"`\n })\n }\n\n /* 로케이션의 소속 구역은 **트윈이 속한 공간**의 것이어야 한다. */\n if (sid && spaceIds.has(sid)) {\n const have = areasOf(sid)\n const refs = [...new Set((t.areaRefs ?? []).filter((x): x is string => !!x && !!x.trim()))]\n const missing = refs.filter(r => !have.has(r)).sort()\n if (missing.length) {\n out.push({\n code: 'location-area-missing',\n severity: 'warn',\n instanceId: t.instanceId,\n spaceId: sid,\n params: { count: missing.length, areas: missing.slice(0, 5).join(', ') },\n message: `${missing.length} area(s) referenced by locations of \"${t.instanceId}\" do not exist in site \"${sid}\": ${missing\n .slice(0, 5)\n .join(', ')}`\n })\n }\n }\n\n /*\n * 어휘가 맞는가 — 저널이 들어오는데 상태가 채워지지 않는 경우를 잡는다.\n *\n * `undefined` 는 「모른다」다(정지한 트윈). 세지 않는다 — 확인하지 않은 것을 정합으로도, 결함으로도\n * 세지 않는 것이 이 하네스의 규율이다.\n */\n if (t.unhandled?.length) {\n const total = t.unhandled.reduce((n, u) => n + u.count, 0)\n const kinds = [...t.unhandled].sort((a, b) => b.count - a.count)\n out.push({\n code: 'twin-unhandled-vocabulary',\n severity: 'warn',\n instanceId: t.instanceId,\n spaceId: sid || undefined,\n params: { events: total, kinds: kinds.length, types: kinds.slice(0, 3).map(u => u.eventType).join(', ') },\n message: `twin \"${t.instanceId}\" received ${total} event(s) of ${kinds.length} type(s) its kernel does not handle: ${kinds\n .slice(0, 3)\n .map(u => u.eventType)\n .join(', ')}`\n })\n }\n }\n\n /* ── 공간 → 가시화 ───────────────────────────────────────────────────── */\n for (const s of input.spaces) {\n const reps = repsOf(s.spaceId)\n const livesHere = instances.filter(t => (t.spaceId ?? '') === s.spaceId)\n\n /* 트윈이 사는데 표현이 없으면 볼 화면이 없다. 트윈이 없는 빈 현장은 정상이므로 세지 않는다. */\n if (livesHere.length && !reps.length) {\n out.push({\n code: 'space-without-representation',\n severity: 'warn',\n spaceId: s.spaceId,\n params: { twins: livesHere.length },\n message: `site \"${s.spaceId}\" has ${livesHere.length} twin(s) but no representation — there is nothing to draw them on`\n })\n }\n\n const primary = (s.primaryRepresentationId ?? '').trim()\n if (primary && !reps.some(r => r.id === primary)) {\n out.push({\n code: 'primary-representation-missing',\n severity: 'broken',\n spaceId: s.spaceId,\n params: { representationId: primary },\n message: `site \"${s.spaceId}\" points at primary representation \"${primary}\" which is not one of its representations`\n })\n }\n }\n\n /* ── 가시화 → 공간·트윈 ─────────────────────────────────────────────── */\n for (const rep of input.representations) {\n const have = areasOf(rep.spaceId)\n /* 이 공간에 사는 트윈들의 개체 id — 폴리곤이 가리킬 수 있는 전부. */\n const here = instances.filter(t => (t.spaceId ?? '') === rep.spaceId)\n const locationIds = new Set(here.flatMap(t => [...(t.locationIds ?? [])]))\n const equipmentIds = new Set(here.flatMap(t => [...(t.equipmentIds ?? [])]))\n\n if (rep.type === 'board' && !(rep.boardId && boards.has(rep.boardId))) {\n out.push({\n code: 'board-representation-unbound',\n severity: 'broken',\n spaceId: rep.spaceId,\n representationId: rep.id,\n params: { boardId: rep.boardId ?? '—' },\n message: `board representation \"${rep.id}\" of site \"${rep.spaceId}\" does not point at an existing board (${rep.boardId ?? 'none'})`\n })\n }\n\n for (const p of rep.polygons ?? []) {\n const gid = (p.groupId ?? '').trim()\n if (gid && !have.has(gid)) {\n out.push({\n code: 'polygon-area-missing',\n severity: 'broken',\n spaceId: rep.spaceId,\n representationId: rep.id,\n params: { areaId: gid, polygonId: p.id ?? '—' },\n message: `a polygon of representation \"${rep.id}\" binds area \"${gid}\" which does not exist in site \"${rep.spaceId}\"`\n })\n }\n const tid = (p.targetId ?? '').trim()\n if (tid && p.targetKind) {\n const pool = p.targetKind === 'equipment' ? equipmentIds : locationIds\n if (!pool.has(tid)) {\n out.push({\n code: 'polygon-target-missing',\n severity: 'broken',\n spaceId: rep.spaceId,\n representationId: rep.id,\n params: { kind: p.targetKind, targetId: tid, polygonId: p.id ?? '—' },\n message: `a polygon of representation \"${rep.id}\" binds ${p.targetKind} \"${tid}\", which no twin on site \"${rep.spaceId}\" has`\n })\n }\n }\n }\n }\n\n return out\n}\n\n/** 심각한 것부터 — 화면이 정렬을 다시 정하지 않게 한 곳에서 정한다. */\nexport function sortFindings(findings: readonly SpaceIntegrityFinding[]): SpaceIntegrityFinding[] {\n const rank = (s: SpaceIntegritySeverity) => (s === 'broken' ? 0 : 1)\n return [...findings].sort(\n (a, b) => rank(a.severity) - rank(b.severity) || a.code.localeCompare(b.code) || (a.spaceId ?? '').localeCompare(b.spaceId ?? '')\n )\n}\n"]}
|
|
@@ -57,6 +57,22 @@ tslib_1.__decorate([
|
|
|
57
57
|
(0, type_graphql_1.Field)({ nullable: true, description: 'Whether this is the primary representation.' }),
|
|
58
58
|
tslib_1.__metadata("design:type", Boolean)
|
|
59
59
|
], TwinSpaceRepresentation.prototype, "isPrimary", void 0);
|
|
60
|
+
tslib_1.__decorate([
|
|
61
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
62
|
+
(0, type_graphql_1.Field)({
|
|
63
|
+
nullable: true,
|
|
64
|
+
description: 'Free-form group name for arranging a long representation list. Sites group differently (by building, by purpose, by floor), so this is not a fixed vocabulary. The order of the groups themselves is not stored: it is the smallest seq within each group, computed where the list is drawn.'
|
|
65
|
+
}),
|
|
66
|
+
tslib_1.__metadata("design:type", String)
|
|
67
|
+
], TwinSpaceRepresentation.prototype, "group", void 0);
|
|
68
|
+
tslib_1.__decorate([
|
|
69
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
70
|
+
(0, type_graphql_1.Field)({
|
|
71
|
+
nullable: true,
|
|
72
|
+
description: 'What this representation is for: "index" (default when absent — it appears in the representation list) or "derived" (it is opened from another representation, such as a popup or drill target, and is folded out of the list rather than hidden). Kept separate from isPrimary, which answers a different question: which single representation stands for the site.'
|
|
73
|
+
}),
|
|
74
|
+
tslib_1.__metadata("design:type", String)
|
|
75
|
+
], TwinSpaceRepresentation.prototype, "role", void 0);
|
|
60
76
|
tslib_1.__decorate([
|
|
61
77
|
(0, typeorm_1.Column)({ type: 'simple-json', nullable: true }),
|
|
62
78
|
(0, type_graphql_1.Field)(type => shell_1.ScalarObject, { nullable: true, description: 'Type config: model {boardId} / image {attachmentId|boardId,width,height} / map {center,zoom,provider}.' }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"twin-space-representation.js","sourceRoot":"","sources":["../../../server/service/twin-space/twin-space-representation.ts"],"names":[],"mappings":";;;;AAAA,qCAAkI;AAClI,+CAAyD;AAEzD,iDAA4D;AAE5D,mDAA2C;AAE3C;;;;GAIG;AAII,IAAM,uBAAuB,GAA7B,MAAM,uBAAuB;
|
|
1
|
+
{"version":3,"file":"twin-space-representation.js","sourceRoot":"","sources":["../../../server/service/twin-space/twin-space-representation.ts"],"names":[],"mappings":"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","sourcesContent":["import { CreateDateColumn, Entity, Index, Column, RelationId, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'\nimport { ObjectType, Field, ID, Int } from 'type-graphql'\n\nimport { Domain, ScalarObject } from '@things-factory/shell'\n\nimport { TwinSpace } from './twin-space.js'\n\n/*\n * TwinSpaceRepresentation — 공간을 보는 한 방식(map|image|model), 복수·1개 primary(공간구성 아키텍처, design/ui/space-composition.md).\n * config(simple-json): model={boardId}(board-service 참조) / image={attachmentId|boardId,width,height} / map={center,zoom,provider}.\n * 폴리곤 영역은 TwinSpaceArea(map/image), model 표현은 씬 컴포넌트가 대신.\n */\n@Entity()\n@Index('ix_twin_space_rep_0', (e: TwinSpaceRepresentation) => [e.domain, e.space])\n@ObjectType({ description: 'A way to view a Space (map | image | model). Multiple per space, one primary.' })\nexport class TwinSpaceRepresentation {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID)\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true, description: 'Owning tenant domain.' })\n domain?: Domain\n\n @RelationId((e: TwinSpaceRepresentation) => e.domain)\n domainId?: string\n\n @ManyToOne(type => TwinSpace)\n @Field(type => TwinSpace, { nullable: true, description: 'Owning space.' })\n space?: TwinSpace\n\n @RelationId((e: TwinSpaceRepresentation) => e.space)\n spaceId?: string\n\n @Column()\n @Field({ description: 'Representation type: map | image | model.' })\n type: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Representation name.' })\n name?: string\n\n @Column({ type: 'int', nullable: true })\n @Field(type => Int, { nullable: true, description: 'Display order.' })\n seq?: number\n\n @Column({ nullable: true, default: false })\n @Field({ nullable: true, description: 'Whether this is the primary representation.' })\n isPrimary?: boolean\n\n /*\n * ── 묶는 이름 · 목록에 나오는지 (2026-08-27 · 사용자 지시) ──────────────────\n *\n * 표현이 많아지면 목록만으로는 관리가 안 된다는 요구다.\n *\n * `group` 을 **자유 문자열**로 둔다. 묶는 축이 현장마다 다르다(건물별·용도별·층별). 고정 목록으로\n * 두면 현장마다 맞지 않는다. 별도 표로 올리지 않는 이유는 그룹에 붙을 속성이 아직 없다는 것이고,\n * 생기면 그때 올린다.\n *\n * **그룹 자체의 순서는 컬럼으로 두지 않는다.** 그 그룹에 속한 `seq` 의 최솟값이 그 순서다 —\n * 화면이 계산한다. 컬럼을 두면 그룹 이름을 바꿀 때 두 곳을 맞춰야 한다.\n */\n @Column({ nullable: true })\n @Field({\n nullable: true,\n description:\n 'Free-form group name for arranging a long representation list. Sites group differently (by building, by purpose, by floor), so this is not a fixed vocabulary. The order of the groups themselves is not stored: it is the smallest seq within each group, computed where the list is drawn.'\n })\n group?: string\n\n /*\n * 목록에 나올 것과 다른 표현에서 열리는 것을 가른다.\n *\n * `isPrimary` 에 얹지 않는다 — 그것은 「현장 현황에 나올 하나」를 정하는 다른 질문이다. 하나의 칸이\n * 두 질문에 답하면 한쪽을 고칠 때 다른 쪽이 오류 없이 바뀐다.\n *\n * **없으면 `index` 다.** 그 값이 흔한 쪽이라 비어 있는 것을 기본으로 읽는다. 대신 값이 있을 때는\n * 아는 값이어야 한다 — 모르는 값을 그냥 두면 화면이 그것을 어느 쪽으로도 그리지 못한다.\n */\n @Column({ nullable: true })\n @Field({\n nullable: true,\n description:\n 'What this representation is for: \"index\" (default when absent — it appears in the representation list) or \"derived\" (it is opened from another representation, such as a popup or drill target, and is folded out of the list rather than hidden). Kept separate from isPrimary, which answers a different question: which single representation stands for the site.'\n })\n role?: string\n\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, { nullable: true, description: 'Type config: model {boardId} / image {attachmentId|boardId,width,height} / map {center,zoom,provider}.' })\n config?: any\n\n @CreateDateColumn()\n @Field({ nullable: true })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true })\n updatedAt?: Date\n}\n"]}
|
|
@@ -20,7 +20,7 @@ export declare class TwinSpaceResolver {
|
|
|
20
20
|
* 예전에는 `Boolean` 을 돌려주고 거절은 **한국어 문장을 냈다.** 두 가지가 틀렸다:
|
|
21
21
|
* ① 서버가 화면의 말을 정했다 — 일본어·스페인어 사용자에게 한국어가 새어 나간다.
|
|
22
22
|
* ② 오류를 낸 예외를 화면이 받지 않아 **아무 일도 없는 것처럼** 보였다. 사용자는 삭제를 눌렀고
|
|
23
|
-
* 화면은 그대로였다. 거절을
|
|
23
|
+
* 화면은 그대로였다. 거절을 알리지 않고 하는 것은 실패보다 나쁘다.
|
|
24
24
|
* 이제 `{ok, errorCode, errorParams}` 로 낸다 — 화면이 `twin.compose.del.<code>` 로 말을 만든다.
|
|
25
25
|
*/
|
|
26
26
|
deleteTwinSpace(spaceId: string, context: ResolverContext): Promise<any>;
|
|
@@ -63,7 +63,7 @@ export declare class TwinSpaceResolver {
|
|
|
63
63
|
*
|
|
64
64
|
* 폴리곤이 없는 구역·개체를 가리키는 것은 **고치지 않는다.** 그 도형을 지워야 하는지, 트윈을 다시
|
|
65
65
|
* 데려와야 하는지, 구역을 다시 그려야 하는지는 사람이 정할 일이다 — 기계가 고르면 사용자가 그린
|
|
66
|
-
* 도형이
|
|
66
|
+
* 도형이 오류 없이 사라진다.
|
|
67
67
|
*/
|
|
68
68
|
repairTwinSpaceIntegrity(context: ResolverContext, spaceId?: string): Promise<any>;
|
|
69
69
|
twinAreas(spaceId: string, context: ResolverContext): Promise<any[]>;
|
|
@@ -7,6 +7,7 @@ const type_graphql_1 = require("type-graphql");
|
|
|
7
7
|
const shell_1 = require("@things-factory/shell");
|
|
8
8
|
const twin_space_js_1 = require("./twin-space.js");
|
|
9
9
|
const twin_space_representation_js_1 = require("./twin-space-representation.js");
|
|
10
|
+
const representation_role_js_1 = require("./representation-role.js");
|
|
10
11
|
const twin_space_area_js_1 = require("./twin-space-area.js");
|
|
11
12
|
const twin_area_js_1 = require("./twin-area.js");
|
|
12
13
|
const twin_instance_js_1 = require("../twin-instance/twin-instance.js");
|
|
@@ -73,11 +74,18 @@ let TwinSpaceResolver = class TwinSpaceResolver {
|
|
|
73
74
|
const r = await (0, shell_1.getRepository)(twin_space_js_1.TwinSpace).findOne({ where: { domain: { id: domainId }, spaceId } });
|
|
74
75
|
if (!r)
|
|
75
76
|
return null;
|
|
76
|
-
|
|
77
|
+
/*
|
|
78
|
+
* 묶은 이름 다음에 순서로 정렬한다. `group` 이 비어 있는 행이 앞에 오는지 뒤에 오는지는 드라이버마다
|
|
79
|
+
* 다르므로 그 순서에 기대지 않는다 — 화면이 다시 묶는다(UX 레인과 그렇게 정했다).
|
|
80
|
+
*/
|
|
81
|
+
const reps = await (0, shell_1.getRepository)(twin_space_representation_js_1.TwinSpaceRepresentation).find({
|
|
82
|
+
where: { domain: { id: domainId }, space: { id: r.id } },
|
|
83
|
+
order: { group: 'ASC', seq: 'ASC' }
|
|
84
|
+
});
|
|
77
85
|
const representations = [];
|
|
78
86
|
for (const rep of reps) {
|
|
79
87
|
const areas = await (0, shell_1.getRepository)(twin_space_area_js_1.TwinSpaceArea).find({ where: { domain: { id: domainId }, representation: { id: rep.id } }, order: { createdAt: 'ASC' } });
|
|
80
|
-
representations.push({ id: rep.id, type: rep.type, name: rep.name, seq: rep.seq, isPrimary: rep.isPrimary, config: rep.config, areas: areas.map(a => ({ id: a.id, name: a.name, type: a.type, geometry: a.geometry, drillTo: a.drillTo, binding: a.binding, style: a.style })) });
|
|
88
|
+
representations.push({ id: rep.id, type: rep.type, name: rep.name, seq: rep.seq, group: rep.group, role: rep.role, isPrimary: rep.isPrimary, config: rep.config, areas: areas.map(a => ({ id: a.id, name: a.name, type: a.type, geometry: a.geometry, drillTo: a.drillTo, binding: a.binding, style: a.style })) });
|
|
81
89
|
}
|
|
82
90
|
return { spaceId: r.spaceId, name: r.name, description: r.description, geo: r.geo, timezone: r.timezone, primaryRepresentationId: r.primaryRepresentationId, extentX: r.extentX, extentY: r.extentY, representations };
|
|
83
91
|
}
|
|
@@ -97,7 +105,7 @@ let TwinSpaceResolver = class TwinSpaceResolver {
|
|
|
97
105
|
* 예전에는 `Boolean` 을 돌려주고 거절은 **한국어 문장을 냈다.** 두 가지가 틀렸다:
|
|
98
106
|
* ① 서버가 화면의 말을 정했다 — 일본어·스페인어 사용자에게 한국어가 새어 나간다.
|
|
99
107
|
* ② 오류를 낸 예외를 화면이 받지 않아 **아무 일도 없는 것처럼** 보였다. 사용자는 삭제를 눌렀고
|
|
100
|
-
* 화면은 그대로였다. 거절을
|
|
108
|
+
* 화면은 그대로였다. 거절을 알리지 않고 하는 것은 실패보다 나쁘다.
|
|
101
109
|
* 이제 `{ok, errorCode, errorParams}` 로 낸다 — 화면이 `twin.compose.del.<code>` 로 말을 만든다.
|
|
102
110
|
*/
|
|
103
111
|
async deleteTwinSpace(spaceId, context) {
|
|
@@ -227,7 +235,7 @@ let TwinSpaceResolver = class TwinSpaceResolver {
|
|
|
227
235
|
if (dryRun)
|
|
228
236
|
return { ok: true, dryRun: true, warnings: judgement.warnings };
|
|
229
237
|
/* 두 곳을 함께 비운다 — 레지스트리와 model 이 어긋나면 어느 쪽이 사실인지 알 수 없다.
|
|
230
|
-
시간 기준(utcOffsetMinutes)은 **남긴다**: 지우면
|
|
238
|
+
시간 기준(utcOffsetMinutes)은 **남긴다**: 지우면 알리지 않고 UTC 로 읽힌다(판정이 그렇게 말했다). */
|
|
231
239
|
const { spaceId: _dropped, ...model } = inst?.model ?? {};
|
|
232
240
|
await instRepo.update({ id: inst.id }, { spaceId: null, model });
|
|
233
241
|
try {
|
|
@@ -296,7 +304,7 @@ let TwinSpaceResolver = class TwinSpaceResolver {
|
|
|
296
304
|
const model = i.model ?? {};
|
|
297
305
|
/*
|
|
298
306
|
* 어휘가 맞는지는 **실행 중인 트윈만** 답할 수 있다 — 커널이 무엇을 버렸는지는 런타임의 사실이다.
|
|
299
|
-
* 정지한 트윈은 `undefined`(모른다)로 둔다. 저널을 다시
|
|
307
|
+
* 정지한 트윈은 `undefined`(모른다)로 둔다. 저널을 다시 계산해 알아내지는 않는다: 이 질의는
|
|
300
308
|
* 테넌트 전체를 훑으므로 트윈마다 폴드를 걸면 정합성 점검이 가장 무거운 질의가 된다.
|
|
301
309
|
*/
|
|
302
310
|
const live = index_js_1.TwinEngine.isRunning(domainId, i.instanceId)
|
|
@@ -335,7 +343,7 @@ let TwinSpaceResolver = class TwinSpaceResolver {
|
|
|
335
343
|
*
|
|
336
344
|
* 폴리곤이 없는 구역·개체를 가리키는 것은 **고치지 않는다.** 그 도형을 지워야 하는지, 트윈을 다시
|
|
337
345
|
* 데려와야 하는지, 구역을 다시 그려야 하는지는 사람이 정할 일이다 — 기계가 고르면 사용자가 그린
|
|
338
|
-
* 도형이
|
|
346
|
+
* 도형이 오류 없이 사라진다.
|
|
339
347
|
*/
|
|
340
348
|
async repairTwinSpaceIntegrity(context, spaceId) {
|
|
341
349
|
const domainId = context.state.domain.id;
|
|
@@ -408,7 +416,7 @@ let TwinSpaceResolver = class TwinSpaceResolver {
|
|
|
408
416
|
polygonRefs += polys.filter(p => p.binding?.groupId === areaId || p.binding?.areaId === areaId).length;
|
|
409
417
|
}
|
|
410
418
|
if (children > 0 || polygonRefs > 0) {
|
|
411
|
-
throw new Error(`area "${areaId}"
|
|
419
|
+
throw new Error(`deleteArea: area "${areaId}" cannot be deleted due to existing references (children: ${children}, polygonRefs: ${polygonRefs}). Remove references first.`);
|
|
412
420
|
}
|
|
413
421
|
await (0, shell_1.getRepository)(twin_area_js_1.TwinArea).delete({ domain: { id: domainId }, space: { id: space.id }, areaId });
|
|
414
422
|
return true;
|
|
@@ -421,7 +429,28 @@ let TwinSpaceResolver = class TwinSpaceResolver {
|
|
|
421
429
|
return { ok: false, error: `space not found: ${spaceId}` };
|
|
422
430
|
const repo = (0, shell_1.getRepository)(twin_space_representation_js_1.TwinSpaceRepresentation);
|
|
423
431
|
const existing = rep?.id ? await repo.findOne({ where: { id: rep.id } }) : null;
|
|
424
|
-
|
|
432
|
+
/*
|
|
433
|
+
* `role` 은 닫힌 값이다 — 모르는 값을 그냥 저장하면 화면이 그것을 어느 쪽으로도 그리지 못하고,
|
|
434
|
+
* 그 표현이 목록에서 오류 없이 사라진다. 비어 있는 것은 `index` 를 뜻하므로 그대로 둔다.
|
|
435
|
+
*/
|
|
436
|
+
const role = rep.role == null || String(rep.role).trim() === '' ? undefined : String(rep.role).trim();
|
|
437
|
+
if (role && !representation_role_js_1.REPRESENTATION_ROLES.includes(role)) {
|
|
438
|
+
return { ok: false, error: `unknown representation role "${role}" — one of: ${representation_role_js_1.REPRESENTATION_ROLES.join(', ')}` };
|
|
439
|
+
}
|
|
440
|
+
/* 묶는 이름은 자유 문자열이다. 다만 공백만 있는 값은 「묶이지 않음」과 같으므로 비운다. */
|
|
441
|
+
const group = rep.group == null || String(rep.group).trim() === '' ? undefined : String(rep.group).trim();
|
|
442
|
+
const saved = await repo.save(repo.create({
|
|
443
|
+
...(existing ?? {}),
|
|
444
|
+
domain: { id: domainId },
|
|
445
|
+
space: { id: space.id },
|
|
446
|
+
type: rep.type,
|
|
447
|
+
name: rep.name,
|
|
448
|
+
seq: rep.seq ?? 0,
|
|
449
|
+
isPrimary: !!rep.isPrimary,
|
|
450
|
+
config: rep.config,
|
|
451
|
+
group: group ?? null,
|
|
452
|
+
role: role ?? null
|
|
453
|
+
}));
|
|
425
454
|
return { ok: true, id: saved.id };
|
|
426
455
|
}
|
|
427
456
|
async deleteSpaceRepresentation(id, context) {
|