@things-factory/headless-twin 10.0.14 → 10.0.17
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 +11 -2
- package/dist-server/engine/canonical-ingest.js +137 -8
- 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 +1 -1
- 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 +124 -9
- package/dist-server/engine/ingest-health.js +169 -15
- 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 +122 -12
- package/dist-server/engine/kpi-fold.js +247 -18
- 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 +185 -28
- 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 +9 -9
- 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 +1 -1
- package/dist-server/engine/model-vocabulary.js.map +1 -1
- package/dist-server/engine/oee-accumulator.d.ts +4 -4
- package/dist-server/engine/oee-accumulator.js +3 -3
- 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 +243 -53
- package/dist-server/engine/twin-engine.js +690 -191
- 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 +27 -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 +140 -8
- 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 +88 -13
- package/dist-server/service/reference/reference-live.js.map +1 -1
- package/dist-server/service/reference/reference-master.d.ts +14 -14
- package/dist-server/service/reference/reference-master.js +34 -12
- 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 +10 -10
- package/dist-server/service/reference/reference-resolver.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-control/twin-control-mutation.js +1 -1
- package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
- package/dist-server/service/twin-event/twin-event-keys.d.ts +22 -2
- package/dist-server/service/twin-event/twin-event-keys.js +37 -5
- 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/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 +36 -1
- 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 +15 -2
- package/dist-server/service/twin-model/isa95-coverage.js +32 -8
- 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 +1 -1
- 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 +3 -3
- package/dist-server/service/twin-model/project-structure.js.map +1 -1
- package/dist-server/service/twin-model/twin-equipment.d.ts +1 -1
- package/dist-server/service/twin-model/twin-equipment.js +1 -1
- 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 +14 -14
- 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 +117 -28
- package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-tree-query.js +22 -0
- 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 +36 -7
- 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 +3 -3
- 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 +3 -3
- package/server/engine/attention-digest.ts +9 -9
- package/server/engine/canonical-ingest.ts +170 -13
- package/server/engine/command-routing.ts +1 -1
- package/server/engine/declared-stimulus.ts +1 -1
- package/server/engine/energy-topology.ts +1 -1
- package/server/engine/ingest-dedupe.ts +149 -0
- package/server/engine/ingest-health.ts +234 -20
- package/server/engine/integration-probes.ts +7 -7
- package/server/engine/kpi-baseline.ts +4 -4
- package/server/engine/kpi-fold.ts +395 -27
- package/server/engine/kpi-query.ts +212 -46
- package/server/engine/live-feed-registry.ts +1 -1
- package/server/engine/load-meter.ts +1 -1
- package/server/engine/local-declarations.ts +13 -13
- 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 +1 -1
- package/server/engine/oee-accumulator.ts +4 -4
- 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 +764 -201
- 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 +68 -5
- package/server/service/index.ts +4 -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 +166 -9
- package/server/service/reference/reference-hook.ts +145 -0
- package/server/service/reference/reference-live.ts +92 -14
- package/server/service/reference/reference-master.ts +39 -17
- package/server/service/reference/reference-resolver.ts +10 -10
- 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-control/twin-control-mutation.ts +1 -1
- package/server/service/twin-event/twin-event-keys.ts +37 -5
- 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/twin-lifecycle-mutation.ts +57 -3
- 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 +36 -1
- package/server/service/twin-model/iec61850-coverage.ts +1 -1
- package/server/service/twin-model/isa95-coverage.ts +45 -8
- package/server/service/twin-model/item-ref.ts +2 -2
- package/server/service/twin-model/name-index.ts +1 -1
- package/server/service/twin-model/project-structure.ts +4 -4
- package/server/service/twin-model/twin-equipment.ts +2 -2
- 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 +14 -14
- package/server/service/twin-model/twin-model-mutation.ts +4 -4
- package/server/service/twin-model/twin-model-query.ts +117 -28
- package/server/service/twin-model/twin-model-tree-query.ts +25 -1
- 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 +39 -7
- 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 +3 -3
- 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 +2 -2
- package/test/boot-resume.test.ts +20 -7
- package/test/broadcast-period.test.ts +2 -2
- package/test/broadcast-queue-overflow.test.ts +244 -0
- package/test/canonical-ingest-vocabularies.test.ts +179 -7
- package/test/canonical-quantity-door.test.ts +2 -2
- package/test/checkpoint-refuses-empty.test.ts +2 -2
- package/test/connector-capability-declaration.test.ts +88 -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 +5 -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/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/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/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/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 +185 -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 +3 -3
- package/test/load-meter.test.ts +1 -1
- package/test/local-declarations.test.ts +10 -10
- package/test/log-stamp.test.ts +1 -1
- 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 +2 -2
- package/test/operational-vocabulary.test.ts +2 -2
- package/test/project-structure-db.test.ts +2 -2
- package/test/projection-reaches-screen.test.ts +2 -2
- package/test/property-effects.test.ts +3 -3
- 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/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 +43 -1
- 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 +49 -3
- 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/warm-start-seam.test.ts +1 -1
- package/test/warm-start.test.ts +6 -6
- package/test/withheld-door.test.ts +95 -0
- package/test/yield-loop.test.ts +4 -4
- package/tsconfig.shared.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -21,7 +21,7 @@ const { TWIN_RELATIONS, axisInfo } = require('@operato/twin-kernel')
|
|
|
21
21
|
* 대신 **선언 그래프에서 자기 축으로 돌아오는 두 홉 순환을 찾는다.** 자재만이 아니라 앞으로
|
|
22
22
|
* 같은 모양을 갖는 어떤 축이든 그대로 펼쳐진다.
|
|
23
23
|
*
|
|
24
|
-
* ──
|
|
24
|
+
* ── 알리지 않고 고르지 않는다 ───────────────────────────────────────────────────────
|
|
25
25
|
* 순환이 여럿이면 **아무거나 집지 않는다.** 후보를 돌려주고 부르는 쪽이 고르게 한다 — 한쪽을
|
|
26
26
|
* 집으면 역전개(누가 나를 쓰나)를 전개(내가 무엇으로 되나)로 보여 주고도 아무도 모른다.
|
|
27
27
|
*/
|
|
@@ -102,12 +102,36 @@ export class TwinModelTreeQuery {
|
|
|
102
102
|
const all = patternsFor(axis)
|
|
103
103
|
if (!all.length) return { axis, itemId, patterns: [], items: [], edges: [], reason: 'no-recursive-relation' }
|
|
104
104
|
|
|
105
|
+
|
|
105
106
|
const chosen = inVia && outVia ? all.find(p => p.inVia === inVia && p.outVia === outVia) : all.length === 1 ? all[0] : undefined
|
|
106
107
|
if (!chosen)
|
|
107
108
|
/* 여럿인데 안 고르면 **아무것도 펼치지 않는다.** 한쪽을 집으면 역전개를 전개로 보여 주고도
|
|
108
109
|
아무도 모른다 — 방향이 뒤집힌 BOM 은 틀린 답이 아니라 반대의 답이다. */
|
|
109
110
|
return { axis, itemId, patterns: all, items: [], edges: [], reason: 'ambiguous-pattern' }
|
|
110
111
|
|
|
112
|
+
/*
|
|
113
|
+
* ── ★ **선언에 없는 축은 펼칠 것이 없다** (2026-08-24 실측으로 터짐) ────────
|
|
114
|
+
*
|
|
115
|
+
* 이 전개는 **트윈 모델의 선언**을 읽는다(`axisInfo(...).path` 로 모델 안을 찾아 들어간다). 그런데
|
|
116
|
+
* 커널 카탈로그의 축 중 셋은 **선언되는 것이 아니라 일어나는 것**이다 — `orders`·`tasks`·`items`
|
|
117
|
+
* 는 사건이 만들고 끝나면 사라진다. 모델이 그 목록을 가질 수 없으므로 `path` 가 비어 있다.
|
|
118
|
+
*
|
|
119
|
+
* 그래서 그런 축을 지나려 하면 `at(model, undefined)` 에서 터졌다:
|
|
120
|
+
*
|
|
121
|
+
* Cannot read properties of undefined (reading 'split')
|
|
122
|
+
*
|
|
123
|
+
* ★ **터진 것은 요청한 축이 아니라 「거쳐 가는 축」이다.** `locations` 를 물으면 그 순환이
|
|
124
|
+
* `tasks` 를 거치는데(작업이 `from`·`at` 으로 자리를 두 번 가리킨다) 그 `tasks` 에 경로가 없다.
|
|
125
|
+
* 그래서 요청한 축만 지켜서는 막히지 않는다 — 둘 다 본다.
|
|
126
|
+
*
|
|
127
|
+
* ── 터지지 않게 하는 것만으로는 모자라다 ──────────────────────────────────
|
|
128
|
+
* 빈 트리를 돌려주면 화면이 **「이어진 것이 없다」**로 읽는다. 그것은 거짓이다 — 작업은 자리를
|
|
129
|
+
* 가리키고 있고, 다만 **이 질의가 그것을 아는 곳이 아니다**(그 관계는 상태와 저널이 안다).
|
|
130
|
+
* 그래서 이유를 말한다. 왜 못 하는지 말하지 않는 빈 화면이 이 저장소가 반복해 고친 결함이다.
|
|
131
|
+
*/
|
|
132
|
+
if (!info.path || !axisInfo(chosen.through)?.path)
|
|
133
|
+
return { axis, itemId, patterns: all, items: [], edges: [], reason: 'not-declared-in-model' }
|
|
134
|
+
|
|
111
135
|
const model = inst.model ?? {}
|
|
112
136
|
const throughInfo = axisInfo(chosen.through)
|
|
113
137
|
const listOf = (i: any) => {
|
|
@@ -11,7 +11,7 @@ import { TwinInstance } from '../twin-instance/twin-instance.js'
|
|
|
11
11
|
*
|
|
12
12
|
* ── 왜 이 표가 인스펙션의 중심인가 ──────────────────────────────────────────
|
|
13
13
|
* 인스펙션 화면의 존재 이유는 `선언 | 관측` 두 열이고(`plans/twin-inspection.md` §2.4),
|
|
14
|
-
* 그 첫 열이 여기서 온다. 둘째 열은 저널에서
|
|
14
|
+
* 그 첫 열이 여기서 온다. 둘째 열은 저널에서 계산된다(`kpi-fold.workTime`).
|
|
15
15
|
* 실적은 보이는데 기준이 안 보이면 "절단이 5분 걸렸다" 를 보고도 빠른지 느린지 알 수 없다.
|
|
16
16
|
*
|
|
17
17
|
* 자리·설비와 같은 규율 — **원본에서 온 캐시**, `sourceRef`·`ingestedAt` 필수,
|
|
@@ -83,7 +83,7 @@ export class TwinOperation {
|
|
|
83
83
|
/**
|
|
84
84
|
* 소요시간 — **ISO 8601 기간 문자열이 정본**(`PT1H10M`).
|
|
85
85
|
*
|
|
86
|
-
* 분으로 저장하면 `PT1H10M30S` 의 초가 사라지고 그 손실은
|
|
86
|
+
* 분으로 저장하면 `PT1H10M30S` 의 초가 사라지고 그 손실은 들어온 것이 없다. 사람이 읽을 분은
|
|
87
87
|
* 화면이 곁들여 보여 주고, 못 읽는 값은 `—` 로 낸다(0 으로 채우면 "즉시 끝나는 공정" 이라는 거짓).
|
|
88
88
|
*/
|
|
89
89
|
@Column({ nullable: true })
|
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
* · `TwinSpaceRepresentation`(지도·이미지·보드) → **남는다.** 표현도 공간의 것이다.
|
|
16
16
|
*
|
|
17
17
|
* 그래서 옮기기는 이력 이전이 아니라 **소속 갱신**이다(`instance.spaceId` + `model.spaceId`).
|
|
18
|
-
* 대신 공간에 매인 것들과의 연결이 끊길 수 있고, 그것을
|
|
18
|
+
* 대신 공간에 매인 것들과의 연결이 끊길 수 있고, 그것을 **알리지 않고 넘기지 않는 것**이 이 모듈의 일이다.
|
|
19
19
|
*
|
|
20
20
|
* ── 거절과 경고를 가른다 ────────────────────────────────────────────────────
|
|
21
21
|
* 거절: 되돌릴 수 없거나 뜻이 성립하지 않는 것(없는 공간·같은 공간·벤치 사본).
|
|
22
22
|
* 경고: 사용자가 알고 나면 정당할 수 있는 것(구역 소속이 끊긴다·시간대가 다르다·옛 표현이 남는다).
|
|
23
|
-
* 경고를 거절로 올리면 정당한 이전을 막고, 거절을 경고로 낮추면
|
|
23
|
+
* 경고를 거절로 올리면 정당한 이전을 막고, 거절을 경고로 낮추면 들어온 것이 없는 사고가 된다.
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
/** 옮길 수 없는 이유 — 언어 중립 코드(발견 오류·인제스트 거절과 같은 규약). */
|
|
@@ -175,7 +175,7 @@ export function judgeMoveTwinSpace(input: MoveSpaceInput): MoveSpaceJudgement {
|
|
|
175
175
|
* · 구역·표현은 옛 현장에 남는다 → 그 화면에서 이 트윈이 사라진다.
|
|
176
176
|
* · **볼 무대가 없어진다** — 현장 현황(지도·보드)은 현장의 표현으로 그리므로, 뗀 트윈은 목록과
|
|
177
177
|
* 조회 화면에서만 보인다. 이것을 말하지 않으면 사용자는 트윈이 고장 났다고 읽는다.
|
|
178
|
-
* · 시간 기준(`utcOffsetMinutes`)은 **옛 현장의 것으로 남는다.** 지우면
|
|
178
|
+
* · 시간 기준(`utcOffsetMinutes`)은 **옛 현장의 것으로 남는다.** 지우면 알리지 않고 UTC 로 읽히므로
|
|
179
179
|
* 그쪽이 더 위험하다 — 남기고 그 사실을 말한다.
|
|
180
180
|
*/
|
|
181
181
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 표현이 무엇을 위한 것인가 — **닫힌 목록** (2026-08-27 · 사용자 지시).
|
|
3
|
+
*
|
|
4
|
+
* index 표현 목록에 나온다. 비어 있으면 이것이다(흔한 쪽을 기본으로 둔다)
|
|
5
|
+
* derived 다른 표현에서 열린다(팝업·드릴 대상). 목록에서는 접어 세어 보인다 — 감추지 않는다
|
|
6
|
+
*
|
|
7
|
+
* 열어 두면 넣는 쪽마다 다른 이름을 쓰고, 그러면 화면이 어느 이름을 목록에 넣어야 하는지 알 수 없다.
|
|
8
|
+
* 늘려야 하면 이 목록에 더한다.
|
|
9
|
+
*
|
|
10
|
+
* ── 왜 엔티티가 아니라 이 파일인가 ──────────────────────────────────────────
|
|
11
|
+
* 엔티티에는 데코레이터가 있어서 테스트가 그 파일을 읽을 수 없다(런타임의 타입 제거가 데코레이터를
|
|
12
|
+
* 파싱하지 못한다). 값으로 확인해야 하는 것은 데코레이터 없는 파일에 둔다.
|
|
13
|
+
*/
|
|
14
|
+
export const REPRESENTATION_ROLES = ['index', 'derived'] as const
|
|
15
|
+
export type RepresentationRole = (typeof REPRESENTATION_ROLES)[number]
|
|
@@ -42,7 +42,7 @@ export type SpaceIntegrityCode =
|
|
|
42
42
|
*
|
|
43
43
|
* 커널은 모르는 `eventType` 을 버리고(전방 호환) 종류별로 센다(`state.unhandled`). 그 수가 0이
|
|
44
44
|
* 아니면 둘 중 하나다: 커넥터가 엉뚱한 트윈에 붙었거나(예: `energy.*` 가 창고 트윈으로), 커널이
|
|
45
|
-
* 낡아 새 어휘를 모른다. 둘 다 사람이 손봐야 하는 것이고,
|
|
45
|
+
* 낡아 새 어휘를 모른다. 둘 다 사람이 손봐야 하는 것이고, 알리지 않고 두면 「정상인데 빈」 트윈이 된다.
|
|
46
46
|
*/
|
|
47
47
|
| 'twin-unhandled-vocabulary'
|
|
48
48
|
|
|
@@ -48,6 +48,43 @@ export class TwinSpaceRepresentation {
|
|
|
48
48
|
@Field({ nullable: true, description: 'Whether this is the primary representation.' })
|
|
49
49
|
isPrimary?: boolean
|
|
50
50
|
|
|
51
|
+
/*
|
|
52
|
+
* ── 묶는 이름 · 목록에 나오는지 (2026-08-27 · 사용자 지시) ──────────────────
|
|
53
|
+
*
|
|
54
|
+
* 표현이 많아지면 목록만으로는 관리가 안 된다는 요구다.
|
|
55
|
+
*
|
|
56
|
+
* `group` 을 **자유 문자열**로 둔다. 묶는 축이 현장마다 다르다(건물별·용도별·층별). 고정 목록으로
|
|
57
|
+
* 두면 현장마다 맞지 않는다. 별도 표로 올리지 않는 이유는 그룹에 붙을 속성이 아직 없다는 것이고,
|
|
58
|
+
* 생기면 그때 올린다.
|
|
59
|
+
*
|
|
60
|
+
* **그룹 자체의 순서는 컬럼으로 두지 않는다.** 그 그룹에 속한 `seq` 의 최솟값이 그 순서다 —
|
|
61
|
+
* 화면이 계산한다. 컬럼을 두면 그룹 이름을 바꿀 때 두 곳을 맞춰야 한다.
|
|
62
|
+
*/
|
|
63
|
+
@Column({ nullable: true })
|
|
64
|
+
@Field({
|
|
65
|
+
nullable: true,
|
|
66
|
+
description:
|
|
67
|
+
'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.'
|
|
68
|
+
})
|
|
69
|
+
group?: string
|
|
70
|
+
|
|
71
|
+
/*
|
|
72
|
+
* 목록에 나올 것과 다른 표현에서 열리는 것을 가른다.
|
|
73
|
+
*
|
|
74
|
+
* `isPrimary` 에 얹지 않는다 — 그것은 「현장 현황에 나올 하나」를 정하는 다른 질문이다. 하나의 칸이
|
|
75
|
+
* 두 질문에 답하면 한쪽을 고칠 때 다른 쪽이 오류 없이 바뀐다.
|
|
76
|
+
*
|
|
77
|
+
* **없으면 `index` 다.** 그 값이 흔한 쪽이라 비어 있는 것을 기본으로 읽는다. 대신 값이 있을 때는
|
|
78
|
+
* 아는 값이어야 한다 — 모르는 값을 그냥 두면 화면이 그것을 어느 쪽으로도 그리지 못한다.
|
|
79
|
+
*/
|
|
80
|
+
@Column({ nullable: true })
|
|
81
|
+
@Field({
|
|
82
|
+
nullable: true,
|
|
83
|
+
description:
|
|
84
|
+
'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.'
|
|
85
|
+
})
|
|
86
|
+
role?: string
|
|
87
|
+
|
|
51
88
|
@Column({ type: 'simple-json', nullable: true })
|
|
52
89
|
@Field(type => ScalarObject, { nullable: true, description: 'Type config: model {boardId} / image {attachmentId|boardId,width,height} / map {center,zoom,provider}.' })
|
|
53
90
|
config?: any
|
|
@@ -5,6 +5,7 @@ import { ScalarObject, getRepository } from '@things-factory/shell'
|
|
|
5
5
|
|
|
6
6
|
import { TwinSpace } from './twin-space.js'
|
|
7
7
|
import { TwinSpaceRepresentation } from './twin-space-representation.js'
|
|
8
|
+
import { REPRESENTATION_ROLES } from './representation-role.js'
|
|
8
9
|
import { TwinSpaceArea } from './twin-space-area.js'
|
|
9
10
|
import { TwinArea } from './twin-area.js'
|
|
10
11
|
import { TwinInstance } from '../twin-instance/twin-instance.js'
|
|
@@ -74,11 +75,18 @@ export class TwinSpaceResolver {
|
|
|
74
75
|
const domainId = context.state.domain.id
|
|
75
76
|
const r = await getRepository(TwinSpace).findOne({ where: { domain: { id: domainId }, spaceId } })
|
|
76
77
|
if (!r) return null
|
|
77
|
-
|
|
78
|
+
/*
|
|
79
|
+
* 묶은 이름 다음에 순서로 정렬한다. `group` 이 비어 있는 행이 앞에 오는지 뒤에 오는지는 드라이버마다
|
|
80
|
+
* 다르므로 그 순서에 기대지 않는다 — 화면이 다시 묶는다(UX 레인과 그렇게 정했다).
|
|
81
|
+
*/
|
|
82
|
+
const reps = await getRepository(TwinSpaceRepresentation).find({
|
|
83
|
+
where: { domain: { id: domainId }, space: { id: r.id } },
|
|
84
|
+
order: { group: 'ASC', seq: 'ASC' }
|
|
85
|
+
})
|
|
78
86
|
const representations: any[] = []
|
|
79
87
|
for (const rep of reps) {
|
|
80
88
|
const areas = await getRepository(TwinSpaceArea).find({ where: { domain: { id: domainId }, representation: { id: rep.id } }, order: { createdAt: 'ASC' } })
|
|
81
|
-
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 })) })
|
|
89
|
+
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 })) })
|
|
82
90
|
}
|
|
83
91
|
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 }
|
|
84
92
|
}
|
|
@@ -106,7 +114,7 @@ export class TwinSpaceResolver {
|
|
|
106
114
|
* 예전에는 `Boolean` 을 돌려주고 거절은 **한국어 문장을 냈다.** 두 가지가 틀렸다:
|
|
107
115
|
* ① 서버가 화면의 말을 정했다 — 일본어·스페인어 사용자에게 한국어가 새어 나간다.
|
|
108
116
|
* ② 오류를 낸 예외를 화면이 받지 않아 **아무 일도 없는 것처럼** 보였다. 사용자는 삭제를 눌렀고
|
|
109
|
-
* 화면은 그대로였다. 거절을
|
|
117
|
+
* 화면은 그대로였다. 거절을 알리지 않고 하는 것은 실패보다 나쁘다.
|
|
110
118
|
* 이제 `{ok, errorCode, errorParams}` 로 낸다 — 화면이 `twin.compose.del.<code>` 로 말을 만든다.
|
|
111
119
|
*/
|
|
112
120
|
@Directive('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
@@ -268,7 +276,7 @@ export class TwinSpaceResolver {
|
|
|
268
276
|
if (dryRun) return { ok: true, dryRun: true, warnings: judgement.warnings }
|
|
269
277
|
|
|
270
278
|
/* 두 곳을 함께 비운다 — 레지스트리와 model 이 어긋나면 어느 쪽이 사실인지 알 수 없다.
|
|
271
|
-
시간 기준(utcOffsetMinutes)은 **남긴다**: 지우면
|
|
279
|
+
시간 기준(utcOffsetMinutes)은 **남긴다**: 지우면 알리지 않고 UTC 로 읽힌다(판정이 그렇게 말했다). */
|
|
272
280
|
const { spaceId: _dropped, ...model } = (inst?.model as any) ?? {}
|
|
273
281
|
await instRepo.update({ id: (inst as any).id }, { spaceId: null as any, model })
|
|
274
282
|
|
|
@@ -348,7 +356,7 @@ export class TwinSpaceResolver {
|
|
|
348
356
|
const model: any = i.model ?? {}
|
|
349
357
|
/*
|
|
350
358
|
* 어휘가 맞는지는 **실행 중인 트윈만** 답할 수 있다 — 커널이 무엇을 버렸는지는 런타임의 사실이다.
|
|
351
|
-
* 정지한 트윈은 `undefined`(모른다)로 둔다. 저널을 다시
|
|
359
|
+
* 정지한 트윈은 `undefined`(모른다)로 둔다. 저널을 다시 계산해 알아내지는 않는다: 이 질의는
|
|
352
360
|
* 테넌트 전체를 훑으므로 트윈마다 폴드를 걸면 정합성 점검이 가장 무거운 질의가 된다.
|
|
353
361
|
*/
|
|
354
362
|
const live: any = TwinEngine.isRunning(domainId, i.instanceId)
|
|
@@ -389,7 +397,7 @@ export class TwinSpaceResolver {
|
|
|
389
397
|
*
|
|
390
398
|
* 폴리곤이 없는 구역·개체를 가리키는 것은 **고치지 않는다.** 그 도형을 지워야 하는지, 트윈을 다시
|
|
391
399
|
* 데려와야 하는지, 구역을 다시 그려야 하는지는 사람이 정할 일이다 — 기계가 고르면 사용자가 그린
|
|
392
|
-
* 도형이
|
|
400
|
+
* 도형이 오류 없이 사라진다.
|
|
393
401
|
*/
|
|
394
402
|
@Directive('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
395
403
|
@Mutation(returns => ScalarObject, {
|
|
@@ -497,7 +505,31 @@ export class TwinSpaceResolver {
|
|
|
497
505
|
if (!space) return { ok: false, error: `space not found: ${spaceId}` }
|
|
498
506
|
const repo = getRepository(TwinSpaceRepresentation)
|
|
499
507
|
const existing = rep?.id ? await repo.findOne({ where: { id: rep.id } }) : null
|
|
500
|
-
|
|
508
|
+
/*
|
|
509
|
+
* `role` 은 닫힌 값이다 — 모르는 값을 그냥 저장하면 화면이 그것을 어느 쪽으로도 그리지 못하고,
|
|
510
|
+
* 그 표현이 목록에서 오류 없이 사라진다. 비어 있는 것은 `index` 를 뜻하므로 그대로 둔다.
|
|
511
|
+
*/
|
|
512
|
+
const role = rep.role == null || String(rep.role).trim() === '' ? undefined : String(rep.role).trim()
|
|
513
|
+
if (role && !(REPRESENTATION_ROLES as readonly string[]).includes(role)) {
|
|
514
|
+
return { ok: false, error: `unknown representation role "${role}" — one of: ${REPRESENTATION_ROLES.join(', ')}` }
|
|
515
|
+
}
|
|
516
|
+
/* 묶는 이름은 자유 문자열이다. 다만 공백만 있는 값은 「묶이지 않음」과 같으므로 비운다. */
|
|
517
|
+
const group = rep.group == null || String(rep.group).trim() === '' ? undefined : String(rep.group).trim()
|
|
518
|
+
|
|
519
|
+
const saved = await repo.save(
|
|
520
|
+
repo.create({
|
|
521
|
+
...(existing ?? {}),
|
|
522
|
+
domain: { id: domainId } as any,
|
|
523
|
+
space: { id: space.id } as any,
|
|
524
|
+
type: rep.type,
|
|
525
|
+
name: rep.name,
|
|
526
|
+
seq: rep.seq ?? 0,
|
|
527
|
+
isPrimary: !!rep.isPrimary,
|
|
528
|
+
config: rep.config,
|
|
529
|
+
group: group ?? null,
|
|
530
|
+
role: role ?? null
|
|
531
|
+
} as Partial<TwinSpaceRepresentation>)
|
|
532
|
+
)
|
|
501
533
|
return { ok: true, id: saved.id }
|
|
502
534
|
}
|
|
503
535
|
|
|
@@ -9,15 +9,15 @@ import { modelColumnTransformer } from '../../engine/model-vocabulary.js'
|
|
|
9
9
|
* TwinStructure — **한 트윈이 거쳐 온 공장 구조들.** 리비전 하나가 그 시절의 공장이다.
|
|
10
10
|
*
|
|
11
11
|
* ── 왜 필요한가: 역사를 잃거나 거짓말을 하거나 ──────────────────────────────
|
|
12
|
-
* 공장은 바뀐다. 도장 부스를 넷 더 놓고,
|
|
12
|
+
* 공장은 바뀐다. 도장 부스를 넷 더 놓고, 라인 하나를 닫는다. 그런데 지금까지는 구조 서명이 바뀌면
|
|
13
13
|
* **그 트윈의 저널을 통째로 지우는 것**이 유일한 길이었다. 안 지우면 옛 이벤트를 새 공장에 대고
|
|
14
|
-
*
|
|
14
|
+
* 계산하게 되어 이력이 거짓말을 하기 때문이다 — 부스가 둘이던 시절의 사실을 여섯 개짜리 공장에 계산하면
|
|
15
15
|
* 그때 없던 부스에서 일이 있었던 것처럼 보인다.
|
|
16
16
|
*
|
|
17
17
|
* 그래서 선택지가 둘뿐이었다: **역사를 잃거나, 거짓말을 하거나.**
|
|
18
18
|
*
|
|
19
19
|
* 셋째 길이 이 표다. 이벤트가 `structureRev` 로 자기 구조를 달고 다니고, 재생은 구조가 바뀌는
|
|
20
|
-
* 지점에서 전환한 뒤
|
|
20
|
+
* 지점에서 전환한 뒤 이어서 계산한다(`replaySegments`). 이력은 "그때 그 공장의 사실" 로 계속 읽힌다.
|
|
21
21
|
*
|
|
22
22
|
* ── 왜 model 를 통째로 담나 ─────────────────────────────────────────────────
|
|
23
23
|
* 재생하려면 **그때의 토폴로지 전체**가 필요하다. 차이(diff)만 담으면 리비전 1부터 다시 계산해 쌓아야
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* **이 사건은 무엇에 대한 것인가** — 사건 하나에서 가리키는 대상들을 뽑는다.
|
|
3
|
+
*
|
|
4
|
+
* ── 왜 필요한가 (2026-08-27 실측) ───────────────────────────────────────────
|
|
5
|
+
* 360 화면이 「이 대상의 이력」을 묻는다. 그 조회가 64초다. 대상으로 걸러 시각순으로 정렬하는 목차가
|
|
6
|
+
* 없기 때문이고, 목차를 얹어도 풀리지 않는다.
|
|
7
|
+
*
|
|
8
|
+
* 부분일치(지금 화면) 63.998초 앞에 와일드카드가 붙어 목차를 쓸 수 없다
|
|
9
|
+
* 정확일치로 바꾸면 67.316초 · 0건 품목 하나에 로트가 여럿이라 성립하지 않는다
|
|
10
|
+
* 앞부분 범위로 바꾸면 68.402초 목차는 쓰지만 정렬 때문에 맞는 행을 다 가져온다
|
|
11
|
+
*
|
|
12
|
+
* 저장된 식별자에 품목과 로트가 한 문자열로 붙어 있는 것이 그 원인이다.
|
|
13
|
+
*
|
|
14
|
+
* https://seunghwa.net/class/product-M0000618-lot-L2604100071
|
|
15
|
+
*
|
|
16
|
+
* 그래서 문자열을 자르는 대신 **대상마다 한 줄**을 적는다. 같은 사건이 품목과 로트를 함께 가리키면
|
|
17
|
+
* 두 줄이다. 자를 규칙이 필요 없다 — 넣는 쪽이 이미 둘을 안다.
|
|
18
|
+
*
|
|
19
|
+
* ── 왜 「뜻이 달라지는 키 칸」이 아닌가 ─────────────────────────────────────
|
|
20
|
+
* `key01~05` 처럼 칸을 두고 사건 종류에 따라 뜻을 달리 쓰는 방식을 **이 표에는** 쓰지 않는다.
|
|
21
|
+
*
|
|
22
|
+
* 그 방식 자체가 틀린 것은 아니다. 데이터셋마다 정의가 따로 있는 프레임워크에서는 합리적이다 — 읽는
|
|
23
|
+
* 쪽이 그 정의를 보고 해석한다. 우리 저널은 표 하나를 화면 여럿이 읽고 볼 정의가 없다. 그래서
|
|
24
|
+
* 「M0000438 을 찾아 줘」가 어떤 줄에서는 품목, 다른 줄에서는 오더가 되고, 묻는 쪽이 사건 종류마다
|
|
25
|
+
* 어느 칸에 무엇이 들어가는지 외워야 한다.
|
|
26
|
+
*
|
|
27
|
+
* **종류를 값으로 밝힌다.** 그러면 읽는 쪽이 사건 종류표를 들고 있지 않아도 된다.
|
|
28
|
+
*
|
|
29
|
+
* ── 왜 순수 모듈인가 ────────────────────────────────────────────────────────
|
|
30
|
+
* 기록 경로에서 불리므로 DB·데코레이터를 알지 않는다. 그래야 테스트가 값으로 확인할 수 있다
|
|
31
|
+
* (`twin-event-keys` 와 같은 이유다).
|
|
32
|
+
*
|
|
33
|
+
* 설계: `operato-twin/design/plans/read-models.md`
|
|
34
|
+
*/
|
|
35
|
+
import { twinEventKeys } from '../twin-event/twin-event-keys.js'
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 대상의 종류 — **닫힌 목록**이다.
|
|
39
|
+
*
|
|
40
|
+
* 열어 두면 넣는 쪽마다 다른 이름을 쓰게 되고(설비를 세 가지 이름으로 부르는 식), 그러면 조회가
|
|
41
|
+
* 어느 이름으로 물어야 하는지 알 수 없다. 늘려야 하면 이 목록에 더한다.
|
|
42
|
+
*
|
|
43
|
+
* item 선언된 품목. 로트 여럿이 같은 품목을 가리킨다
|
|
44
|
+
* lot 개체 하나. 저장된 식별자 그대로다
|
|
45
|
+
* order 오더
|
|
46
|
+
* location 자리
|
|
47
|
+
* equipment 설비
|
|
48
|
+
* task 작업
|
|
49
|
+
*/
|
|
50
|
+
export const SUBJECT_KINDS = ['item', 'lot', 'order', 'location', 'equipment', 'task'] as const
|
|
51
|
+
export type SubjectKind = (typeof SUBJECT_KINDS)[number]
|
|
52
|
+
|
|
53
|
+
export interface EventSubject {
|
|
54
|
+
kind: SubjectKind
|
|
55
|
+
id: string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 개체 식별자에서 **선언된 품목**을 찾아 주는 함수 — 부르는 쪽이 넘긴다.
|
|
60
|
+
*
|
|
61
|
+
* 이 모듈이 문자열을 자르지 않는 이유는 그 규칙이 연결된 시스템마다 다르기 때문이다. 어느 부호가
|
|
62
|
+
* 로트를 가리키는지 커널 계약이 정한 적이 없고, chef 의 `-lot-` 을 여기에 굳히면 이 파일이 그
|
|
63
|
+
* 시스템 위에 서게 된다(`item-ref.ts` 가 같은 판단을 적어 두었다).
|
|
64
|
+
*
|
|
65
|
+
* 그래서 판정은 **그 트윈의 선언**과 견주어 하고(`resolveByDeclaration`), 그 선언을 아는 것은
|
|
66
|
+
* 부르는 쪽이다. 답하지 못하면 `undefined` — 그때는 품목 줄을 만들지 않는다. 짐작으로 이으면 남의
|
|
67
|
+
* 품목 이력에 이 사건이 섞인다.
|
|
68
|
+
*/
|
|
69
|
+
export type ItemResolver = (epc: string) => string | null | undefined
|
|
70
|
+
|
|
71
|
+
/** 컬럼 길이 상한 — `twin-event-keys` 와 같은 값이다(같은 표에 나란히 서는 값이므로). */
|
|
72
|
+
const MAX_ID = 255
|
|
73
|
+
|
|
74
|
+
function put(out: EventSubject[], kind: SubjectKind, raw: unknown): void {
|
|
75
|
+
if (raw === undefined || raw === null) return
|
|
76
|
+
const id = String(raw).trim()
|
|
77
|
+
if (!id || id.length > MAX_ID) return
|
|
78
|
+
/* 같은 대상을 두 번 적지 않는다 — 사건 하나에 같은 자리가 두 번 실려 올 수 있다. */
|
|
79
|
+
if (out.some(s => s.kind === kind && s.id === id)) return
|
|
80
|
+
out.push({ kind, id })
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 사건 하나가 가리키는 대상들 — 없으면 빈 배열이다.
|
|
85
|
+
*
|
|
86
|
+
* 승격된 검색 키(`twinEventKeys`)를 그대로 쓴다. 그 함수가 이미 사건 종류별로 어디에서 값을 꺼낼지
|
|
87
|
+
* 알고 있으므로, 여기서 다시 판단하면 두 곳이 어긋난다.
|
|
88
|
+
*
|
|
89
|
+
* **작업은 승격 키에 없어서 봉투에서 직접 읽는다.** 승격 컬럼을 늘리는 것은 이 작업의 범위가 아니다
|
|
90
|
+
* (그것이 바로 이 파생 표로 대신하려는 일이다).
|
|
91
|
+
*/
|
|
92
|
+
export function subjectsOf(envelope: any, opts?: { itemOf?: ItemResolver }): EventSubject[] {
|
|
93
|
+
const keys = twinEventKeys(envelope)
|
|
94
|
+
const out: EventSubject[] = []
|
|
95
|
+
|
|
96
|
+
/*
|
|
97
|
+
* ── 개체로 온 것과 클래스로 온 것을 갈라 적는다 (2026-08-27 실측) ────────────
|
|
98
|
+
*
|
|
99
|
+
* `epcOf` 는 셋을 한 칸에 담는다 — `epcList[0]` · `parentID` · `quantityList[0].epcClass`.
|
|
100
|
+
* 승격 컬럼이 하나라서 그렇게 되어 있고, 그 칸으로는 「개체인가 클래스인가」를 알 수 없다.
|
|
101
|
+
*
|
|
102
|
+
* 실측한 chef 의 사건 하나가 그 경우다. 물건을 개체로 세지 않고 무게로 담는 현장이라 `epcList` 가
|
|
103
|
+
* 비어 있고 `quantityList` 에 클래스가 있다.
|
|
104
|
+
*
|
|
105
|
+
* epcList []
|
|
106
|
+
* quantityList [{ epcClass: 'https://…/product-M0000879', quantity: 11797.6, uom: 'KGM' }]
|
|
107
|
+
*
|
|
108
|
+
* 그것을 로트로 적으면 뜻이 틀린다 — 개체가 아니라 품목이다. 이 표는 종류를 값으로 밝히므로
|
|
109
|
+
* 갈라 적을 수 있다.
|
|
110
|
+
*/
|
|
111
|
+
const d = envelope?.data ?? envelope ?? {}
|
|
112
|
+
const instanceEpc = d.epcList?.[0] ?? d.parentID
|
|
113
|
+
const classEpc = d.quantityList?.[0]?.epcClass
|
|
114
|
+
|
|
115
|
+
if (instanceEpc) {
|
|
116
|
+
/* 개체는 저장된 식별자 그대로다 — 자르지 않는다. */
|
|
117
|
+
put(out, 'lot', instanceEpc)
|
|
118
|
+
/*
|
|
119
|
+
* 품목은 선언과 견주어 찾는다. 답이 개체 식별자와 같으면 품목을 찾지 못한 것이므로 줄을 만들지
|
|
120
|
+
* 않는다 — `itemRefOf` 는 URI 세계에서 문자열을 그대로 돌려주기 때문에 그 값이 그대로 올 수 있다.
|
|
121
|
+
*/
|
|
122
|
+
const item = opts?.itemOf?.(String(instanceEpc))
|
|
123
|
+
if (item && item !== instanceEpc) put(out, 'item', item)
|
|
124
|
+
}
|
|
125
|
+
if (classEpc) {
|
|
126
|
+
/*
|
|
127
|
+
* 클래스로 온 것은 개체 줄을 만들지 않는다 — 그 사건에 개체가 없다. 품목 줄만 만든다.
|
|
128
|
+
*
|
|
129
|
+
* 선언과 견주어 품목을 찾고, 찾지 못하면 클래스 문자열을 그대로 품목으로 쓴다. 클래스는 이미 품목을
|
|
130
|
+
* 가리키는 값이므로 그것이 짐작이 아니다(개체 식별자를 그대로 품목으로 쓰는 것과 다르다).
|
|
131
|
+
*/
|
|
132
|
+
const item = opts?.itemOf?.(String(classEpc)) ?? null
|
|
133
|
+
put(out, 'item', item || classEpc)
|
|
134
|
+
}
|
|
135
|
+
put(out, 'order', keys.orderId)
|
|
136
|
+
put(out, 'location', keys.locationId)
|
|
137
|
+
put(out, 'equipment', keys.moverId)
|
|
138
|
+
|
|
139
|
+
const data = envelope?.data
|
|
140
|
+
put(out, 'task', data?.taskId)
|
|
141
|
+
/*
|
|
142
|
+
* ── 작업이 오간 두 자리도 적는다 ────────────────────────────────────────────
|
|
143
|
+
*
|
|
144
|
+
* 승격 컬럼(`locationId`)은 EPCIS 자리만 담는다(`readPoint` · `bizLocation` · `location`). 작업이
|
|
145
|
+
* A 에서 B 로 갈 때 그 컬럼에는 **둘 중 하나만** 넣을 수 있어서 아무것도 넣지 않는다.
|
|
146
|
+
*
|
|
147
|
+
* 여기서는 둘 다 적는다. 이것이 승격 컬럼과의 어긋남이 아니라 **이 표가 할 수 있는 일**이다 —
|
|
148
|
+
* 자리 하나에 줄 하나이므로 두 자리를 함께 담는다. 「그 자리에서 무슨 일이 있었나」를 묻는 화면은
|
|
149
|
+
* 출발지와 도착지를 둘 다 그 자리의 사건으로 본다.
|
|
150
|
+
*/
|
|
151
|
+
put(out, 'location', data?.fromNode)
|
|
152
|
+
put(out, 'location', data?.toNode)
|
|
153
|
+
|
|
154
|
+
return out
|
|
155
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { TwinSubjectEvent } from './twin-subject-event.js'
|
|
2
|
+
import { TwinSubjectQuery, TwinSubjectMutation } from './twin-subject-query.js'
|
|
3
|
+
|
|
4
|
+
export const entities = [TwinSubjectEvent]
|
|
5
|
+
export const resolvers = [TwinSubjectQuery, TwinSubjectMutation]
|
|
6
|
+
export { TwinSubjectEvent, TwinSubjectQuery, TwinSubjectMutation }
|
|
7
|
+
export * from './event-subjects.js'
|
|
8
|
+
export * from './item-resolver.js'
|
|
9
|
+
export * from './twin-subject-writer.js'
|
|
10
|
+
export * from './twin-subject-backfill.js'
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { itemRefOf, resolveByDeclaration } from '../twin-model/item-ref.js'
|
|
2
|
+
import type { ItemResolver } from './event-subjects.js'
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* **개체 식별자 → 선언된 품목** — 대상별 사건 목록에 품목 줄을 적기 위한 것.
|
|
6
|
+
*
|
|
7
|
+
* ── 왜 문자열을 자르지 않나 ─────────────────────────────────────────────────
|
|
8
|
+
* 저장된 식별자에 품목과 로트가 붙어 있다.
|
|
9
|
+
*
|
|
10
|
+
* https://seunghwa.net/class/product-M0000438-lot-L2403090386
|
|
11
|
+
*
|
|
12
|
+
* 여기서 품목을 꺼내려면 `-lot-` 이 로트를 가리킨다는 것을 알아야 하는데, 그 부호는 연결된 시스템마다
|
|
13
|
+
* 다르고 커널 계약이 정한 적이 없다. 한 시스템의 관습을 규칙으로 굳히면 이 코드가 그 시스템 위에 서게
|
|
14
|
+
* 된다(`item-ref.ts` 가 같은 판단을 적어 두었다).
|
|
15
|
+
*
|
|
16
|
+
* 그래서 **그 트윈의 선언과 견주어** 판정한다. 선언된 품목 중 이 식별자가 가리키는 것을 찾고, 후보가
|
|
17
|
+
* 둘 이상이면 답하지 않는다 — 짐작으로 이으면 남의 품목 이력에 이 사건이 섞인다.
|
|
18
|
+
*
|
|
19
|
+
* ── 왜 기억해 두나 ──────────────────────────────────────────────────────────
|
|
20
|
+
* 사건마다 부르는 함수다(초당 130건). 같은 개체 식별자가 계속 되풀어 오므로, 한 번 판정한 것은 기억해
|
|
21
|
+
* 둔다. 트윈의 선언이 바뀌면 그 기억을 버려야 하므로 **선언의 개수와 첫 항목으로 세대**를 만든다 —
|
|
22
|
+
* 선언이 바뀌면 세대가 달라져 다시 판정한다.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** 트윈마다 기억해 둔 판정 — 세대가 바뀌면 버린다. */
|
|
26
|
+
const memo = new Map<string, { generation: string; declared: Map<string, string>; seen: Map<string, string | null> }>()
|
|
27
|
+
|
|
28
|
+
/** 한 트윈이 기억할 개체 식별자 수 상한 — 넘으면 비우고 다시 채운다(무한히 자라지 않게). */
|
|
29
|
+
const MEMO_LIMIT = 20_000
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 선언된 품목의 식별자들을 모은다 — 트윈 모델이 말하는 것만.
|
|
33
|
+
*
|
|
34
|
+
* `materialDefinitions` 가 이 트윈이 다루는 품목의 선언이다. 그 항목의 `id` 와, 연결된 시스템의
|
|
35
|
+
* 어휘로 이어 둔 값(`binding`)을 함께 후보로 둔다 — 개체 식별자는 후자를 앞부분으로 갖는다.
|
|
36
|
+
*/
|
|
37
|
+
function declaredItems(model: any): Map<string, string> {
|
|
38
|
+
const out = new Map<string, string>()
|
|
39
|
+
const defs = model?.productionSpec?.materialDefinitions ?? model?.materialDefinitions
|
|
40
|
+
if (!Array.isArray(defs)) return out
|
|
41
|
+
for (const d of defs) {
|
|
42
|
+
const id = String(d?.id ?? '').trim()
|
|
43
|
+
if (!id) continue
|
|
44
|
+
out.set(id, id)
|
|
45
|
+
const binding = d?.binding
|
|
46
|
+
const bound = typeof binding === 'string' ? binding : binding?.epcClass ?? binding?.id
|
|
47
|
+
const b = String(bound ?? '').trim()
|
|
48
|
+
if (b) out.set(b, id)
|
|
49
|
+
}
|
|
50
|
+
return out
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 선언이 바뀌었는지 값싸게 가리는 표식 — 개수와 첫·끝 항목이면 실제 변경을 잡는다. */
|
|
54
|
+
function generationOf(declared: Map<string, string>): string {
|
|
55
|
+
const keys = [...declared.keys()]
|
|
56
|
+
return `${keys.length}:${keys[0] ?? ''}:${keys[keys.length - 1] ?? ''}`
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 그 트윈의 품목 판정 함수 — 선언이 없으면 `undefined`(그때는 품목 줄을 만들지 않는다).
|
|
61
|
+
*
|
|
62
|
+
* 선언을 모르는 트윈에서 품목을 지어내지 않는다. 개체 줄(`lot`)은 그대로 적히므로 이력 조회는 로트로
|
|
63
|
+
* 답할 수 있고, 품목 축만 비어 있다. 그 사실을 조회하는 쪽이 말한다.
|
|
64
|
+
*/
|
|
65
|
+
export function itemResolverFor(domainId: string, instanceId: string, model: any): ItemResolver | undefined {
|
|
66
|
+
const declared = declaredItems(model)
|
|
67
|
+
if (!declared.size) return undefined
|
|
68
|
+
|
|
69
|
+
const key = `${domainId}:${instanceId}`
|
|
70
|
+
const generation = generationOf(declared)
|
|
71
|
+
let entry = memo.get(key)
|
|
72
|
+
if (!entry || entry.generation !== generation) {
|
|
73
|
+
entry = { generation, declared, seen: new Map() }
|
|
74
|
+
memo.set(key, entry)
|
|
75
|
+
}
|
|
76
|
+
const state = entry
|
|
77
|
+
|
|
78
|
+
return (epc: string) => {
|
|
79
|
+
const hit = state.seen.get(epc)
|
|
80
|
+
if (hit !== undefined) return hit
|
|
81
|
+
/* 판정은 선언이 한다 — 후보가 둘 이상이면 `undefined` 를 내고, 그것을 「모른다」로 기억한다. */
|
|
82
|
+
const found = resolveByDeclaration(itemRefOf(epc), state.declared) ?? null
|
|
83
|
+
if (state.seen.size >= MEMO_LIMIT) state.seen.clear()
|
|
84
|
+
state.seen.set(epc, found)
|
|
85
|
+
return found
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** 시험이 쓰는 초기화 — 프로세스 안의 기억이므로 시험마다 비운다. */
|
|
90
|
+
export function resetItemResolverMemo(): void {
|
|
91
|
+
memo.clear()
|
|
92
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { SubjectKind } from './event-subjects.js'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* **이 물음을 파생 테이블로 답할 수 있나** — 순수 판단만 여기 둔다.
|
|
5
|
+
*
|
|
6
|
+
* 데이터베이스도 커널도 부르지 않는다. 그래서 시험이 이 파일을 그대로 부를 수 있다(데코레이터가 없다).
|
|
7
|
+
* 판단이 조회 코드 안에 섞여 있으면 그 판단만 확인할 방법이 없고, 그러면 「어떤 물음이 빠른 경로로
|
|
8
|
+
* 가는가」가 아무도 읽지 못하는 규칙이 된다.
|
|
9
|
+
*
|
|
10
|
+
* 좁게 잡는다. 파생 테이블은 「대상 하나를 시각순으로」에 맞춰 생겼고, 그 모양이 아닌 물음에 억지로
|
|
11
|
+
* 답하면 목록이 알리지 않고 달라진다. 맞지 않으면 `null` 이고, 부르는 쪽은 저널로 간다.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** 화면이 저널에 묻던 축 → 파생 테이블의 대상 종류. */
|
|
15
|
+
export const AXIS_KIND: Record<string, SubjectKind> = {
|
|
16
|
+
epc: 'item',
|
|
17
|
+
orderId: 'order',
|
|
18
|
+
locationId: 'location',
|
|
19
|
+
moverId: 'equipment' // vocabulary-guard: allow — 저널의 승격 컬럼 이름(저장된 계약)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** 이 경로가 다룰 수 있는 필터 이름 — 이 밖의 필터가 하나라도 있으면 저널로 보낸다. */
|
|
23
|
+
const ALLOWED_FILTERS = new Set(Object.keys(AXIS_KIND).concat('eventTime'))
|
|
24
|
+
|
|
25
|
+
export interface SubjectQueryShape {
|
|
26
|
+
kind: SubjectKind
|
|
27
|
+
/** 화면이 건넨 축 값 — 짧게 줄어 있을 수 있다(되돌리는 일은 조회하는 쪽이 한다). */
|
|
28
|
+
value: string
|
|
29
|
+
untilTime?: Date
|
|
30
|
+
fromTime?: Date
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 목록 계약의 조건을 읽어 「대상 하나의 시각순 이력」인지 가린다 — 아니면 `null`.
|
|
35
|
+
*
|
|
36
|
+
* 받는 모양
|
|
37
|
+
* 축 하나(품목·오더·자리·설비)를 `eq` 또는 `search` 로 · 시각 상한이나 구간 · 시각 내림차순 정렬
|
|
38
|
+
*
|
|
39
|
+
* 보내는 모양(저널로)
|
|
40
|
+
* 자유 검색어 · 종류 필터 · 상관 식별자 · 축 둘 이상 · 그 밖의 정렬이나 연산자
|
|
41
|
+
*/
|
|
42
|
+
export function readModelShape(params: any, sortings: any[]): SubjectQueryShape | null {
|
|
43
|
+
const filters: any[] = Array.isArray(params?.filters) ? params.filters : []
|
|
44
|
+
if (typeof params?.search === 'string' && params.search.trim()) return null
|
|
45
|
+
for (const f of filters) if (!ALLOWED_FILTERS.has(f?.name)) return null
|
|
46
|
+
|
|
47
|
+
/* 정렬은 시각 내림차순 하나일 때만 — 파생 테이블의 목차가 그 축이다. */
|
|
48
|
+
if (sortings.length !== 1 || sortings[0]?.name !== 'eventTime' || sortings[0]?.desc !== true) return null
|
|
49
|
+
|
|
50
|
+
const axisFilters = filters.filter(f => f.name !== 'eventTime')
|
|
51
|
+
if (axisFilters.length !== 1) return null
|
|
52
|
+
const axis = axisFilters[0]
|
|
53
|
+
const kind = AXIS_KIND[axis.name]
|
|
54
|
+
if (!kind) return null
|
|
55
|
+
/* 축은 값 하나여야 한다 — 여러 값(`in`)이면 목차를 한 번에 타지 못한다. */
|
|
56
|
+
if (axis.operator !== 'eq' && axis.operator !== 'search') return null
|
|
57
|
+
const value = typeof axis.value === 'string' ? axis.value.trim() : ''
|
|
58
|
+
if (!value) return null
|
|
59
|
+
|
|
60
|
+
/* 시각은 상한·구간만 받는다(시간여행). 그 밖의 연산자는 뜻이 달라지므로 저널로 보낸다. */
|
|
61
|
+
let untilTime: Date | undefined
|
|
62
|
+
let fromTime: Date | undefined
|
|
63
|
+
for (const f of filters) {
|
|
64
|
+
if (f.name !== 'eventTime') continue
|
|
65
|
+
if (f.operator === 'lte' || f.operator === 'lt') {
|
|
66
|
+
untilTime = toDate(f.value)
|
|
67
|
+
if (!untilTime) return null
|
|
68
|
+
} else if (f.operator === 'between' && Array.isArray(f.value)) {
|
|
69
|
+
fromTime = toDate(f.value[0])
|
|
70
|
+
untilTime = toDate(f.value[1])
|
|
71
|
+
if (!fromTime || !untilTime) return null
|
|
72
|
+
} else return null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return { kind, value, untilTime, fromTime }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** 값을 날짜로 — 못 읽으면 `undefined`(지어내지 않는다). */
|
|
79
|
+
export function toDate(v: unknown): Date | undefined {
|
|
80
|
+
if (v instanceof Date) return Number.isNaN(v.getTime()) ? undefined : v
|
|
81
|
+
if (typeof v !== 'string' && typeof v !== 'number') return undefined
|
|
82
|
+
const d = new Date(v as any)
|
|
83
|
+
return Number.isNaN(d.getTime()) ? undefined : d
|
|
84
|
+
}
|