@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
|
@@ -80,11 +80,11 @@ interface ObservedState {
|
|
|
80
80
|
}[];
|
|
81
81
|
}
|
|
82
82
|
/**
|
|
83
|
-
* 봉투를 벗긴다 — **체크포인트가 겹포장돼 복원이
|
|
83
|
+
* 봉투를 벗긴다 — **체크포인트가 겹포장돼 복원이 알리지 않고 죽던 자리.**
|
|
84
84
|
*
|
|
85
85
|
* `TwinEngine.snapshot()` 은 라이브에서는 상태를, 시뮬에서는 `runtime.resync()` 봉투(`{revision,state}`)를
|
|
86
86
|
* 돌려준다. 그 봉투를 다시 `{revision, state}` 로 감싸 캐시에 넣었으므로, 꺼낸 `state` 는 축이 하나도
|
|
87
|
-
* 없는 껍데기였다 → `planWarmStart` 가 "심을 것이 없다" 로 판정하고
|
|
87
|
+
* 없는 껍데기였다 → `planWarmStart` 가 "심을 것이 없다" 로 판정하고 **알리지 않고** 넘어갔다. 저널에 수천 건이
|
|
88
88
|
* 있고 캐시도 최신인데 트윈이 빈 채로 떴다(오류도 로그도 없다).
|
|
89
89
|
*
|
|
90
90
|
* 반환 모양을 통일하는 것이 뿌리 수정이지만, 그 값은 State 구독 페이로드로도 나가므로 클라이언트 계약을
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* ── 왜 떼어냈나 ─────────────────────────────────────────────────────────────
|
|
7
7
|
* 이 판정이 틀리면 증상이 정반대 두 방향으로 나온다: 주입해야 할 때 안 주입하면 **있는 재고가 0 으로**
|
|
8
8
|
* 보이고(2026-07-31 hatiolab-wms: 저널에 입고 540·출고 94 인데 재고 화면이 비어 있었다), 심지
|
|
9
|
-
* 말아야 할 벤치에 주입하면 **용량 측정이 오염된다.** 둘 다
|
|
9
|
+
* 말아야 할 벤치에 주입하면 **용량 측정이 오염된다.** 둘 다 알리지 않고 틀리는 종류라 규칙을 고정한다.
|
|
10
10
|
*
|
|
11
11
|
* ── 오더를 버리던 전제가 낡았다 (2026-08-05) ────────────────────────────────
|
|
12
12
|
* 예전 주석은 "스냅샷의 오더에는 progress 밖에 없어서 역산하면 없는 숫자를 지어내는 셈이므로
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* `StateProjector` 다. 사람·자산·작업도 같은 스냅샷에 있고 커널이 심을 수 있는데 넘기지 않고 있었다.
|
|
19
19
|
* 그래서 씨앗을 **관측 스냅샷 전체**로 넓힌다.
|
|
20
20
|
*
|
|
21
|
-
* 다만 넓히면서
|
|
21
|
+
* 다만 넓히면서 **들어온 것이 없는 누락을 만들지 않는다.** 원값이 없는 오더는 커널이 걸러 내므로(지어내지
|
|
22
22
|
* 않는다), 그 수를 여기서 세어 호출부가 말할 수 있게 한다. 세지 않으면 "주문이 없다" 와 "주문을
|
|
23
23
|
* 못 심었다" 가 화면에서 같아진다.
|
|
24
24
|
*/
|
|
@@ -29,11 +29,11 @@ exports.planLiveContinuity = planLiveContinuity;
|
|
|
29
29
|
/** 관측 상태가 실려 있는지 — 축 하나라도 있으면 상태다(봉투에는 없다). */
|
|
30
30
|
const looksLikeState = (x) => !!x && typeof x === 'object' && ['locations', 'items', 'equipment', 'orders', 'tasks', 'persons', 'assets', 'acked'].some(k => k in x);
|
|
31
31
|
/**
|
|
32
|
-
* 봉투를 벗긴다 — **체크포인트가 겹포장돼 복원이
|
|
32
|
+
* 봉투를 벗긴다 — **체크포인트가 겹포장돼 복원이 알리지 않고 죽던 자리.**
|
|
33
33
|
*
|
|
34
34
|
* `TwinEngine.snapshot()` 은 라이브에서는 상태를, 시뮬에서는 `runtime.resync()` 봉투(`{revision,state}`)를
|
|
35
35
|
* 돌려준다. 그 봉투를 다시 `{revision, state}` 로 감싸 캐시에 넣었으므로, 꺼낸 `state` 는 축이 하나도
|
|
36
|
-
* 없는 껍데기였다 → `planWarmStart` 가 "심을 것이 없다" 로 판정하고
|
|
36
|
+
* 없는 껍데기였다 → `planWarmStart` 가 "심을 것이 없다" 로 판정하고 **알리지 않고** 넘어갔다. 저널에 수천 건이
|
|
37
37
|
* 있고 캐시도 최신인데 트윈이 빈 채로 떴다(오류도 로그도 없다).
|
|
38
38
|
*
|
|
39
39
|
* 반환 모양을 통일하는 것이 뿌리 수정이지만, 그 값은 State 구독 페이로드로도 나가므로 클라이언트 계약을
|
|
@@ -63,7 +63,7 @@ function planWarmStart(state, purpose, canHydrate) {
|
|
|
63
63
|
return { action: 'skip', reason: 'bench' };
|
|
64
64
|
if (!state)
|
|
65
65
|
return { action: 'skip', reason: 'no-state' };
|
|
66
|
-
/* 봉투가 들어오면 스스로 벗는다 — 호출부가 한 곳이라도 잊으면 그 경로가
|
|
66
|
+
/* 봉투가 들어오면 스스로 벗는다 — 호출부가 한 곳이라도 잊으면 그 경로가 알리지 않고 죽는다(실제로 그랬다). */
|
|
67
67
|
state = unwrapState(state);
|
|
68
68
|
const locations = arr(state.locations);
|
|
69
69
|
const items = arr(state.items);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"warm-start.js","sourceRoot":"","sources":["../../server/engine/warm-start.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 웜스타트 판정 — **순수**. \"기동하는 커널에 직전 상태를 주입할 것인가, 주입한다면 무엇을\" 만 정한다.\n * 실제 주입(hydrateObserved 호출)과 로그는 엔진이 한다.\n *\n * ── 왜 떼어냈나 ─────────────────────────────────────────────────────────────\n * 이 판정이 틀리면 증상이 정반대 두 방향으로 나온다: 주입해야 할 때 안 주입하면 **있는 재고가 0 으로**\n * 보이고(2026-07-31 hatiolab-wms: 저널에 입고 540·출고 94 인데 재고 화면이 비어 있었다), 심지\n * 말아야 할 벤치에 주입하면 **용량 측정이 오염된다.** 둘 다 조용히 틀리는 종류라 규칙을 고정한다.\n *\n * ── 오더를 버리던 전제가 낡았다 (2026-08-05) ────────────────────────────────\n * 예전 주석은 \"스냅샷의 오더에는 progress 밖에 없어서 역산하면 없는 숫자를 지어내는 셈이므로\n * 넘기지 않는다\" 였다. 그때는 맞았다. 그러나 커널 `OrderState` 가 **원값을 되찾은 뒤**\n * (`requested`·`fulfilled`·`lines`) 이 판정만 그대로 남아, 재기동하면 재고는 맞는데 **진행 중\n * 주문이 통째로 사라진** 화면이 나왔다. 오류는 나지 않는다 — 그래서 오래 남아 있었다.\n *\n * 두 스냅샷 생산 경로가 모두 원값을 담는다: 시뮬은 커널 `snapshot()`, 라이브(미러)는\n * `StateProjector` 다. 사람·자산·작업도 같은 스냅샷에 있고 커널이 심을 수 있는데 넘기지 않고 있었다.\n * 그래서 씨앗을 **관측 스냅샷 전체**로 넓힌다.\n *\n * 다만 넓히면서 **조용한 누락을 만들지 않는다.** 원값이 없는 오더는 커널이 걸러 내므로(지어내지\n * 않는다), 그 수를 여기서 세어 호출부가 말할 수 있게 한다. 세지 않으면 \"주문이 없다\" 와 \"주문을\n * 못 심었다\" 가 화면에서 같아진다.\n */\n\n/**\n * 커널에 심을 관측 상태 — 구조가 아니라 \"무엇이 어디에 얼마나\".\n *\n * 커널 `hydrateObserved` 가 받는 스냅샷 모양을 그대로 따른다. 여기서 골라 담지 않는 이유: 무엇을\n * 심을 수 있는지는 커널이 알고, 그 규칙(고아 작업 제외·이행 완료 오더 제외 등)도 커널에 있다.\n * 호스트가 미리 골라내면 규칙이 두 곳으로 갈린다.\n */\nexport interface ObservedSeed {\n locations: unknown[]\n items: unknown[]\n equipment: unknown[]\n persons?: unknown[]\n assets?: unknown[]\n tasks?: unknown[]\n orders?: unknown[]\n /** 확인해 둔 주목 신호 id — 상태에서 파생되지 않아 반드시 함께 넘긴다. */\n acked?: string[]\n /**\n * 주목 신호가 **처음 성립한 시각** — 확인과 같은 성질이다(계산으로 되살릴 수 없다).\n *\n * 넘기지 않으면 재기동 뒤 세 시간째 지속된 조건이 「0초째」로 되살아난다. 방금 찬 자리와 세 시간째\n * 막힌 자리는 할 일이 다르므로, 그 값이 거짓이면 화면이 사람을 잘못된 순서로 움직인다.\n */\n attentionSince?: { id: string; since: string }[]\n}\n\n/** 심은 것의 내역 — 로그와 테스트가 같은 숫자를 본다. */\nexport interface WarmStartCounts {\n itemCount: number\n equipmentCount: number\n personCount: number\n assetCount: number\n taskCount: number\n /** 원값(requested)이 있어 커널이 실제로 심을 수 있는 오더. */\n orderCount: number\n /** 이어받은 확인 처리 수 — 잃으면 확인해 둔 신호가 다시 빨개진다. */\n ackedCount: number\n /** 이어받은 시작 시각 수 — 잃으면 지속된 조건이 모두 「방금」이 된다. */\n attentionSinceCount: number\n /**\n * 원값이 없어 심지 못하는 오더 — **0 이 아니면 반드시 말한다.**\n * progress 만 있는 오더는 남은 수량을 알 수 없어 재계획의 근거가 되지 못한다. 지어내지 않고 뺀다.\n */\n ordersWithoutDemand: number\n}\n\nexport type WarmStartPlan =\n | ({ action: 'hydrate'; seed: ObservedSeed } & WarmStartCounts)\n /** 벤치 트윈 — 새 시작에서 용량을 재는 게 목적이라 현재 상태를 주입하면 측정이 오염된다. */\n | { action: 'skip'; reason: 'bench' }\n /** 심을 상태가 없다 — 처음 만든 트윈이거나 저널·체크포인트가 비었다. 정상이다. */\n | { action: 'skip'; reason: 'no-state' }\n /** 커널이 관측 주입을 지원하지 않는다 — 구조만으로 시작하므로 보유량은 0 으로 읽힌다(알려야 한다). */\n | { action: 'skip'; reason: 'unsupported' }\n\n/** 스냅샷이 들고 올 수 있는 관측 축 — 어느 것이든 있으면 주입할 가치가 있다. */\ninterface ObservedState {\n locations?: unknown[]\n items?: unknown[]\n equipment?: unknown[]\n persons?: unknown[]\n assets?: unknown[]\n tasks?: unknown[]\n orders?: unknown[]\n acked?: string[]\n attentionSince?: { id: string; since: string }[]\n}\n\n/** 관측 상태가 실려 있는지 — 축 하나라도 있으면 상태다(봉투에는 없다). */\nconst looksLikeState = (x: any): boolean =>\n !!x && typeof x === 'object' && ['locations', 'items', 'equipment', 'orders', 'tasks', 'persons', 'assets', 'acked'].some(k => k in x)\n\n/**\n * 봉투를 벗긴다 — **체크포인트가 겹포장돼 복원이 조용히 죽던 자리.**\n *\n * `TwinEngine.snapshot()` 은 라이브에서는 상태를, 시뮬에서는 `runtime.resync()` 봉투(`{revision,state}`)를\n * 돌려준다. 그 봉투를 다시 `{revision, state}` 로 감싸 캐시에 넣었으므로, 꺼낸 `state` 는 축이 하나도\n * 없는 껍데기였다 → `planWarmStart` 가 \"심을 것이 없다\" 로 판정하고 **조용히** 넘어갔다. 저널에 수천 건이\n * 있고 캐시도 최신인데 트윈이 빈 채로 떴다(오류도 로그도 없다).\n *\n * 반환 모양을 통일하는 것이 뿌리 수정이지만, 그 값은 State 구독 페이로드로도 나가므로 클라이언트 계약을\n * 건드린다. 그래서 **복원 경로에서만** 벗긴다. 겹겹이 싸여 있어도 벗긴다(과거에 저장된 값이 남아 있다).\n */\nexport function unwrapState(x: any, depth = 4): any {\n if (!x || typeof x !== 'object') return x\n if (looksLikeState(x)) return x\n if (depth > 0 && x.state && typeof x.state === 'object') return unwrapState(x.state, depth - 1)\n return x\n}\n\nconst arr = (v: unknown[] | undefined): unknown[] => (Array.isArray(v) ? v : [])\n\n/** 원값이 있는 오더인가 — 커널 `hydrateObserved` 의 판정(`requested !== undefined`)과 같은 기준. */\nconst hasDemand = (o: unknown): boolean =>\n !!o && typeof o === 'object' && (o as { requested?: unknown }).requested !== undefined\n\n/**\n * 무엇을 할지 정한다.\n *\n * 오더·작업·사람·자산도 함께 주입한다. 골라내는 규칙은 커널이 들고 있고(이행 완료 오더, 고아 작업),\n * 여기서는 **심을 수 없는 오더의 수만 센다** — 호출부가 그 사실을 말할 수 있어야 하기 때문이다.\n */\nexport function planWarmStart(state: ObservedState | null | undefined, purpose: string | undefined, canHydrate: boolean): WarmStartPlan {\n /* 벤치 판정이 먼저다 — 상태가 있든 없든 벤치에는 주입하지 않는다는 사실이 바뀌지 않는다. */\n if (purpose === 'bench') return { action: 'skip', reason: 'bench' }\n if (!state) return { action: 'skip', reason: 'no-state' }\n /* 봉투가 들어오면 스스로 벗는다 — 호출부가 한 곳이라도 잊으면 그 경로가 조용히 죽는다(실제로 그랬다). */\n state = unwrapState(state)\n\n const locations = arr(state.locations)\n const items = arr(state.items)\n const equipment = arr(state.equipment)\n const persons = arr(state.persons)\n const assets = arr(state.assets)\n const tasks = arr(state.tasks)\n const orders = arr(state.orders)\n /* 확인 처리는 문자열 id 목록이다 — 배열이 아니면 없는 것으로 본다(지어내지 않는다). */\n const acked = Array.isArray(state.acked) ? state.acked.filter((x): x is string => typeof x === 'string') : []\n /* 시작 시각은 {id, since} 쌍 목록이다 — 모양이 아니면 없는 것으로 본다(지어낸 시각을 심으면 화면이\n 「세 시간째」라고 거짓을 말한다. 모르면 그 신호는 이어붙는 순간이 새 시작이다). */\n const attentionSince = Array.isArray(state.attentionSince)\n ? state.attentionSince.filter((x: any): x is { id: string; since: string } => !!x && typeof x.id === 'string' && typeof x.since === 'string')\n : []\n\n /* 어느 축도 없으면 심을 것이 없다 — 빈 주입으로 로그만 남기지 않는다. */\n if (!locations.length && !items.length && !equipment.length && !persons.length && !assets.length && !tasks.length && !orders.length && !acked.length && !attentionSince.length) {\n return { action: 'skip', reason: 'no-state' }\n }\n\n /* 지원 여부는 마지막에 본다 — 주입할 것이 있는데 주입하지 못하는 상황이라야 경고할 가치가 있다. */\n if (!canHydrate) return { action: 'skip', reason: 'unsupported' }\n\n const seedable = orders.filter(hasDemand)\n\n return {\n action: 'hydrate',\n seed: {\n locations,\n items,\n equipment,\n ...(persons.length ? { persons } : {}),\n ...(assets.length ? { assets } : {}),\n ...(tasks.length ? { tasks } : {}),\n /* 원값 없는 오더는 넘기지 않는다 — 커널도 걸러 내지만, 넘기지 않으면 세어 둔 수와 어긋날 일이 없다. */\n ...(seedable.length ? { orders: seedable } : {}),\n /* 확인 처리는 조건이 사라진 id 가 섞여 있어도 해롭지 않다 — 그 신호가 없으면 표시할 대상이 없다. */\n ...(acked.length ? { acked } : {}),\n /* 조건이 사라진 id 가 섞여 있어도 해롭지 않다 — 커널이 그 신호가 없으면 함께 지운다. */\n ...(attentionSince.length ? { attentionSince } : {})\n },\n itemCount: items.length,\n equipmentCount: equipment.length,\n personCount: persons.length,\n assetCount: assets.length,\n taskCount: tasks.length,\n orderCount: seedable.length,\n ordersWithoutDemand: orders.length - seedable.length,\n ackedCount: acked.length,\n attentionSinceCount: attentionSince.length\n }\n}\n\n/*\n * ── 미러(관측 구동) 재기동의 연속성 ─────────────────────────────────────────\n *\n * 미러의 진실은 원천이다. 그래서 재고·위치·설비 상태는 심지 않는다 — 다음 계측·다음 마스터 재읽기가\n * 곧 정정하므로, 심으면 **떠난 물건이 되살아나는** 위험만 남는다(그래서 `startLive` 는 오랫동안 아무것도\n * 심지 않았다).\n *\n * 그런데 원천이 **애초에 다시 말해 주지 않는 축**이 있다. 우리가 접어 만든 것들이다:\n * · 확인(ack)·조건이 처음 성립한 시각 — 사람이 한 일과 우리가 기억한 시각\n * · 에너지의 열린 구간 누적·적산 기준점·관측 이후 최대 — SCADA 는 「이번 15분에 지금까지 얼마」를 모른다\n *\n * 잃으면 **오류 없이 값이 작아진다**(실측 2026-08-18: 재기동 뒤 그 구간의 전력량·피크가 작아지고, 세\n * 시간째 지속된 경보가 「0초째」가 됐다). 그래서 축을 갈라 되풀어 주지 않는 것만 이어받는다.\n *\n * 무엇을 이어받을지는 여기서 고르고, **어떻게 흡수할지는 커널이 정한다**(`hydrateContinuity`) — 도메인\n * 규칙이 호스트로 새면 트윈마다 다른 답이 된다.\n */\nexport interface ContinuitySeed {\n acked?: string[]\n attentionSince?: { id: string; since: string }[]\n energy?: unknown\n}\n\nexport interface ContinuityPlan {\n seed: ContinuitySeed\n /** 무엇을 이어받았나 — 로그와 시험이 같은 숫자를 본다(0 이면 이어받을 것이 없었다는 사실이다). */\n ackedCount: number\n attentionSinceCount: number\n /** 에너지 연속성을 이어받았나 — 열린 구간이 있으면 그 구간의 요금 근거가 살아난다. */\n energy: boolean\n}\n\n/** 미러가 이어받을 축만 골라낸다 — 관측 축(재고·위치·설비)은 일부러 넣지 않는다. */\nexport function planLiveContinuity(raw: unknown): ContinuityPlan {\n const state = raw ? (unwrapState(raw as any) as any) : undefined\n const acked = Array.isArray(state?.acked) ? state.acked.filter((x: any): x is string => typeof x === 'string') : []\n const attentionSince = Array.isArray(state?.attentionSince)\n ? state.attentionSince.filter((x: any): x is { id: string; since: string } => !!x && typeof x.id === 'string' && typeof x.since === 'string')\n : []\n /* 에너지는 커널이 자기 모양을 안다 — 호스트는 「있나」만 본다(골라 담으면 규칙이 두 곳으로 갈린다). */\n const energy = state?.energy && typeof state.energy === 'object' ? state.energy : undefined\n return {\n seed: {\n ...(acked.length ? { acked } : {}),\n ...(attentionSince.length ? { attentionSince } : {}),\n ...(energy ? { energy } : {})\n },\n ackedCount: acked.length,\n attentionSinceCount: attentionSince.length,\n energy: !!energy\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"warm-start.js","sourceRoot":"","sources":["../../server/engine/warm-start.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 웜스타트 판정 — **순수**. \"기동하는 커널에 직전 상태를 주입할 것인가, 주입한다면 무엇을\" 만 정한다.\n * 실제 주입(hydrateObserved 호출)과 로그는 엔진이 한다.\n *\n * ── 왜 떼어냈나 ─────────────────────────────────────────────────────────────\n * 이 판정이 틀리면 증상이 정반대 두 방향으로 나온다: 주입해야 할 때 안 주입하면 **있는 재고가 0 으로**\n * 보이고(2026-07-31 hatiolab-wms: 저널에 입고 540·출고 94 인데 재고 화면이 비어 있었다), 심지\n * 말아야 할 벤치에 주입하면 **용량 측정이 오염된다.** 둘 다 알리지 않고 틀리는 종류라 규칙을 고정한다.\n *\n * ── 오더를 버리던 전제가 낡았다 (2026-08-05) ────────────────────────────────\n * 예전 주석은 \"스냅샷의 오더에는 progress 밖에 없어서 역산하면 없는 숫자를 지어내는 셈이므로\n * 넘기지 않는다\" 였다. 그때는 맞았다. 그러나 커널 `OrderState` 가 **원값을 되찾은 뒤**\n * (`requested`·`fulfilled`·`lines`) 이 판정만 그대로 남아, 재기동하면 재고는 맞는데 **진행 중\n * 주문이 통째로 사라진** 화면이 나왔다. 오류는 나지 않는다 — 그래서 오래 남아 있었다.\n *\n * 두 스냅샷 생산 경로가 모두 원값을 담는다: 시뮬은 커널 `snapshot()`, 라이브(미러)는\n * `StateProjector` 다. 사람·자산·작업도 같은 스냅샷에 있고 커널이 심을 수 있는데 넘기지 않고 있었다.\n * 그래서 씨앗을 **관측 스냅샷 전체**로 넓힌다.\n *\n * 다만 넓히면서 **들어온 것이 없는 누락을 만들지 않는다.** 원값이 없는 오더는 커널이 걸러 내므로(지어내지\n * 않는다), 그 수를 여기서 세어 호출부가 말할 수 있게 한다. 세지 않으면 \"주문이 없다\" 와 \"주문을\n * 못 심었다\" 가 화면에서 같아진다.\n */\n\n/**\n * 커널에 심을 관측 상태 — 구조가 아니라 \"무엇이 어디에 얼마나\".\n *\n * 커널 `hydrateObserved` 가 받는 스냅샷 모양을 그대로 따른다. 여기서 골라 담지 않는 이유: 무엇을\n * 심을 수 있는지는 커널이 알고, 그 규칙(고아 작업 제외·이행 완료 오더 제외 등)도 커널에 있다.\n * 호스트가 미리 골라내면 규칙이 두 곳으로 갈린다.\n */\nexport interface ObservedSeed {\n locations: unknown[]\n items: unknown[]\n equipment: unknown[]\n persons?: unknown[]\n assets?: unknown[]\n tasks?: unknown[]\n orders?: unknown[]\n /** 확인해 둔 주목 신호 id — 상태에서 파생되지 않아 반드시 함께 넘긴다. */\n acked?: string[]\n /**\n * 주목 신호가 **처음 성립한 시각** — 확인과 같은 성질이다(계산으로 되살릴 수 없다).\n *\n * 넘기지 않으면 재기동 뒤 세 시간째 지속된 조건이 「0초째」로 되살아난다. 방금 찬 자리와 세 시간째\n * 막힌 자리는 할 일이 다르므로, 그 값이 거짓이면 화면이 사람을 잘못된 순서로 움직인다.\n */\n attentionSince?: { id: string; since: string }[]\n}\n\n/** 심은 것의 내역 — 로그와 테스트가 같은 숫자를 본다. */\nexport interface WarmStartCounts {\n itemCount: number\n equipmentCount: number\n personCount: number\n assetCount: number\n taskCount: number\n /** 원값(requested)이 있어 커널이 실제로 심을 수 있는 오더. */\n orderCount: number\n /** 이어받은 확인 처리 수 — 잃으면 확인해 둔 신호가 다시 빨개진다. */\n ackedCount: number\n /** 이어받은 시작 시각 수 — 잃으면 지속된 조건이 모두 「방금」이 된다. */\n attentionSinceCount: number\n /**\n * 원값이 없어 심지 못하는 오더 — **0 이 아니면 반드시 말한다.**\n * progress 만 있는 오더는 남은 수량을 알 수 없어 재계획의 근거가 되지 못한다. 지어내지 않고 뺀다.\n */\n ordersWithoutDemand: number\n}\n\nexport type WarmStartPlan =\n | ({ action: 'hydrate'; seed: ObservedSeed } & WarmStartCounts)\n /** 벤치 트윈 — 새 시작에서 용량을 재는 게 목적이라 현재 상태를 주입하면 측정이 오염된다. */\n | { action: 'skip'; reason: 'bench' }\n /** 심을 상태가 없다 — 처음 만든 트윈이거나 저널·체크포인트가 비었다. 정상이다. */\n | { action: 'skip'; reason: 'no-state' }\n /** 커널이 관측 주입을 지원하지 않는다 — 구조만으로 시작하므로 보유량은 0 으로 읽힌다(알려야 한다). */\n | { action: 'skip'; reason: 'unsupported' }\n\n/** 스냅샷이 들고 올 수 있는 관측 축 — 어느 것이든 있으면 주입할 가치가 있다. */\ninterface ObservedState {\n locations?: unknown[]\n items?: unknown[]\n equipment?: unknown[]\n persons?: unknown[]\n assets?: unknown[]\n tasks?: unknown[]\n orders?: unknown[]\n acked?: string[]\n attentionSince?: { id: string; since: string }[]\n}\n\n/** 관측 상태가 실려 있는지 — 축 하나라도 있으면 상태다(봉투에는 없다). */\nconst looksLikeState = (x: any): boolean =>\n !!x && typeof x === 'object' && ['locations', 'items', 'equipment', 'orders', 'tasks', 'persons', 'assets', 'acked'].some(k => k in x)\n\n/**\n * 봉투를 벗긴다 — **체크포인트가 겹포장돼 복원이 알리지 않고 죽던 자리.**\n *\n * `TwinEngine.snapshot()` 은 라이브에서는 상태를, 시뮬에서는 `runtime.resync()` 봉투(`{revision,state}`)를\n * 돌려준다. 그 봉투를 다시 `{revision, state}` 로 감싸 캐시에 넣었으므로, 꺼낸 `state` 는 축이 하나도\n * 없는 껍데기였다 → `planWarmStart` 가 \"심을 것이 없다\" 로 판정하고 **알리지 않고** 넘어갔다. 저널에 수천 건이\n * 있고 캐시도 최신인데 트윈이 빈 채로 떴다(오류도 로그도 없다).\n *\n * 반환 모양을 통일하는 것이 뿌리 수정이지만, 그 값은 State 구독 페이로드로도 나가므로 클라이언트 계약을\n * 건드린다. 그래서 **복원 경로에서만** 벗긴다. 겹겹이 싸여 있어도 벗긴다(과거에 저장된 값이 남아 있다).\n */\nexport function unwrapState(x: any, depth = 4): any {\n if (!x || typeof x !== 'object') return x\n if (looksLikeState(x)) return x\n if (depth > 0 && x.state && typeof x.state === 'object') return unwrapState(x.state, depth - 1)\n return x\n}\n\nconst arr = (v: unknown[] | undefined): unknown[] => (Array.isArray(v) ? v : [])\n\n/** 원값이 있는 오더인가 — 커널 `hydrateObserved` 의 판정(`requested !== undefined`)과 같은 기준. */\nconst hasDemand = (o: unknown): boolean =>\n !!o && typeof o === 'object' && (o as { requested?: unknown }).requested !== undefined\n\n/**\n * 무엇을 할지 정한다.\n *\n * 오더·작업·사람·자산도 함께 주입한다. 골라내는 규칙은 커널이 들고 있고(이행 완료 오더, 고아 작업),\n * 여기서는 **심을 수 없는 오더의 수만 센다** — 호출부가 그 사실을 말할 수 있어야 하기 때문이다.\n */\nexport function planWarmStart(state: ObservedState | null | undefined, purpose: string | undefined, canHydrate: boolean): WarmStartPlan {\n /* 벤치 판정이 먼저다 — 상태가 있든 없든 벤치에는 주입하지 않는다는 사실이 바뀌지 않는다. */\n if (purpose === 'bench') return { action: 'skip', reason: 'bench' }\n if (!state) return { action: 'skip', reason: 'no-state' }\n /* 봉투가 들어오면 스스로 벗는다 — 호출부가 한 곳이라도 잊으면 그 경로가 알리지 않고 죽는다(실제로 그랬다). */\n state = unwrapState(state)\n\n const locations = arr(state.locations)\n const items = arr(state.items)\n const equipment = arr(state.equipment)\n const persons = arr(state.persons)\n const assets = arr(state.assets)\n const tasks = arr(state.tasks)\n const orders = arr(state.orders)\n /* 확인 처리는 문자열 id 목록이다 — 배열이 아니면 없는 것으로 본다(지어내지 않는다). */\n const acked = Array.isArray(state.acked) ? state.acked.filter((x): x is string => typeof x === 'string') : []\n /* 시작 시각은 {id, since} 쌍 목록이다 — 모양이 아니면 없는 것으로 본다(지어낸 시각을 심으면 화면이\n 「세 시간째」라고 거짓을 말한다. 모르면 그 신호는 이어붙는 순간이 새 시작이다). */\n const attentionSince = Array.isArray(state.attentionSince)\n ? state.attentionSince.filter((x: any): x is { id: string; since: string } => !!x && typeof x.id === 'string' && typeof x.since === 'string')\n : []\n\n /* 어느 축도 없으면 심을 것이 없다 — 빈 주입으로 로그만 남기지 않는다. */\n if (!locations.length && !items.length && !equipment.length && !persons.length && !assets.length && !tasks.length && !orders.length && !acked.length && !attentionSince.length) {\n return { action: 'skip', reason: 'no-state' }\n }\n\n /* 지원 여부는 마지막에 본다 — 주입할 것이 있는데 주입하지 못하는 상황이라야 경고할 가치가 있다. */\n if (!canHydrate) return { action: 'skip', reason: 'unsupported' }\n\n const seedable = orders.filter(hasDemand)\n\n return {\n action: 'hydrate',\n seed: {\n locations,\n items,\n equipment,\n ...(persons.length ? { persons } : {}),\n ...(assets.length ? { assets } : {}),\n ...(tasks.length ? { tasks } : {}),\n /* 원값 없는 오더는 넘기지 않는다 — 커널도 걸러 내지만, 넘기지 않으면 세어 둔 수와 어긋날 일이 없다. */\n ...(seedable.length ? { orders: seedable } : {}),\n /* 확인 처리는 조건이 사라진 id 가 섞여 있어도 해롭지 않다 — 그 신호가 없으면 표시할 대상이 없다. */\n ...(acked.length ? { acked } : {}),\n /* 조건이 사라진 id 가 섞여 있어도 해롭지 않다 — 커널이 그 신호가 없으면 함께 지운다. */\n ...(attentionSince.length ? { attentionSince } : {})\n },\n itemCount: items.length,\n equipmentCount: equipment.length,\n personCount: persons.length,\n assetCount: assets.length,\n taskCount: tasks.length,\n orderCount: seedable.length,\n ordersWithoutDemand: orders.length - seedable.length,\n ackedCount: acked.length,\n attentionSinceCount: attentionSince.length\n }\n}\n\n/*\n * ── 미러(관측 구동) 재기동의 연속성 ─────────────────────────────────────────\n *\n * 미러의 진실은 원천이다. 그래서 재고·위치·설비 상태는 심지 않는다 — 다음 계측·다음 마스터 재읽기가\n * 곧 정정하므로, 심으면 **떠난 물건이 되살아나는** 위험만 남는다(그래서 `startLive` 는 오랫동안 아무것도\n * 심지 않았다).\n *\n * 그런데 원천이 **애초에 다시 말해 주지 않는 축**이 있다. 우리가 계산해 만든 것들이다:\n * · 확인(ack)·조건이 처음 성립한 시각 — 사람이 한 일과 우리가 기억한 시각\n * · 에너지의 열린 구간 누적·적산 기준점·관측 이후 최대 — SCADA 는 「이번 15분에 지금까지 얼마」를 모른다\n *\n * 잃으면 **오류 없이 값이 작아진다**(실측 2026-08-18: 재기동 뒤 그 구간의 전력량·피크가 작아지고, 세\n * 시간째 지속된 경보가 「0초째」가 됐다). 그래서 축을 갈라 되풀어 주지 않는 것만 이어받는다.\n *\n * 무엇을 이어받을지는 여기서 고르고, **어떻게 흡수할지는 커널이 정한다**(`hydrateContinuity`) — 도메인\n * 규칙이 호스트로 새면 트윈마다 다른 답이 된다.\n */\nexport interface ContinuitySeed {\n acked?: string[]\n attentionSince?: { id: string; since: string }[]\n energy?: unknown\n}\n\nexport interface ContinuityPlan {\n seed: ContinuitySeed\n /** 무엇을 이어받았나 — 로그와 시험이 같은 숫자를 본다(0 이면 이어받을 것이 없었다는 사실이다). */\n ackedCount: number\n attentionSinceCount: number\n /** 에너지 연속성을 이어받았나 — 열린 구간이 있으면 그 구간의 요금 근거가 살아난다. */\n energy: boolean\n}\n\n/** 미러가 이어받을 축만 골라낸다 — 관측 축(재고·위치·설비)은 일부러 넣지 않는다. */\nexport function planLiveContinuity(raw: unknown): ContinuityPlan {\n const state = raw ? (unwrapState(raw as any) as any) : undefined\n const acked = Array.isArray(state?.acked) ? state.acked.filter((x: any): x is string => typeof x === 'string') : []\n const attentionSince = Array.isArray(state?.attentionSince)\n ? state.attentionSince.filter((x: any): x is { id: string; since: string } => !!x && typeof x.id === 'string' && typeof x.since === 'string')\n : []\n /* 에너지는 커널이 자기 모양을 안다 — 호스트는 「있나」만 본다(골라 담으면 규칙이 두 곳으로 갈린다). */\n const energy = state?.energy && typeof state.energy === 'object' ? state.energy : undefined\n return {\n seed: {\n ...(acked.length ? { acked } : {}),\n ...(attentionSince.length ? { attentionSince } : {}),\n ...(energy ? { energy } : {})\n },\n ackedCount: acked.length,\n attentionSinceCount: attentionSince.length,\n energy: !!energy\n }\n}\n"]}
|
package/dist-server/index.js
CHANGED
|
@@ -34,7 +34,7 @@ process.on('bootstrap-module-start', async ({ app, config, client }) => {
|
|
|
34
34
|
* 실측: 개발 저널이 시간당 581,794행 · 0.85GB(하루 20GB) 로 자랐고 지우는 것이 없었다. 그런데
|
|
35
35
|
* 「며칠까지 남기나」는 **배포마다 갈리는 사실**이다 — 개발은 짧아도 되고 실증은 길어야 한다.
|
|
36
36
|
*
|
|
37
|
-
* 선언이 없으면 **아무것도 지우지 않는다.**
|
|
37
|
+
* 선언이 없으면 **아무것도 지우지 않는다.** 알리지 않고 지우는 편이 알리지 않고 쌓는 것보다 나쁘다.
|
|
38
38
|
* 값은 스냅샷 TTL(7일)보다 짧을 수 없다 — 엔진이 그것을 올려 주고 그 사실을 말한다.
|
|
39
39
|
*/
|
|
40
40
|
/*
|
package/dist-server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;;AAAA,4DAAiC;AACjC,6DAAkC;AAClC,qEAAqE;AACrE,kDAAkD;AAAzC,sGAAA,UAAU,OAAA;AAEnB,4CAAoD;AACpD,uBAAoB;AAEpB,gDAA8C;AAC9C,6EAAgF;AAChF,oEAAiF;AAEjF;;;;;;;;GAQG;AACH,OAAO,CAAC,EAAE,CAAC,wBAA+B,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAO,EAAE,EAAE;IACjF,IAAI,CAAC;QACH;;;WAGG;QACH,IAAA,oCAAyB,GAAE,CAAA;QAC3B;;;;;;;;WAQG;QACH;;;;;;;;WAQG;QACH,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,2BAA2B,IAAI,CAAC,CAAC,CAAA;QACpE,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,EAAE,oBAAoB,IAAI,CAAC,CAAC,CAAA;QAC3E,MAAM,aAAa,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAA;QACxD,IAAI,aAAa,GAAG,CAAC,EAAE,CAAC;YACtB,qBAAU,CAAC,sBAAsB,GAAG,aAAa,CAAA;YACjD,IAAA,gBAAO,EAAC,4BAA4B,aAAa,MAAM,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAA;QACxF,CAAC;QAED,MAAM,qBAAU,CAAC,SAAS,EAAE,CAAA;QAC5B,MAAM,IAAA,4CAAwB,GAAE,CAAA;QAChC,IAAA,gBAAO,EAAC,iCAAiC,CAAC,CAAA;IAC5C,CAAC;IAAC,OAAO,EAAE,EAAE,CAAC;QACZ,IAAA,kBAAS,EAAC,qCAAqC,EAAE,EAAE,CAAC,CAAA;IACtD,CAAC;AACH,CAAC,CAAC,CAAA","sourcesContent":["export * from './engine/index.js'\nexport * from './service/index.js'\n/* 스키마 이력 — 프레임워크가 `migrations` export 를 모아 실행한다(`run migration`). */\nexport { migrations } from './migrations/index.js'\n\nimport { twinLog, twinError } from './engine/log.js'\nimport './routes.js'\n\nimport { TwinEngine } from './engine/index.js'\nimport { resumeReferenceLiveFeeds } from './service/reference/reference-live.js'\nimport { installIngestWindowWriter } from './service/twin-ingest-window/index.js'\n\n/*\n * 모듈 부팅 — **도는 트윈을 되살린다.**\n *\n * 두 층이 순서대로다: ① 엔진이 등록부의 `running` 행을 선언된 `restartPolicy` 그대로 되살리고\n * (`bootstrap`), ② 레퍼런스 계층이 미러 트윈의 커넥터 피드를 다시 붙인다. 순서가 뒤집히면 피드가\n * 아직 없는 커널에 계측을 붓게 된다.\n *\n * 예전에는 ①도 ②도 없었다 — 재기동 한 번에 등록부만 「도는 중」이라 말하는 트윈이 남았다.\n */\nprocess.on('bootstrap-module-start' as any, async ({ app, config, client }: any) => {\n try {\n /*\n * 닫힌 인제스트 창을 표로 옮기는 문을 먼저 연다 — 트윈을 되살리기 **전에** 등록해야 첫 창부터\n * 남는다. 커널 층은 저장 계층을 모르므로 이 등록이 그 둘을 잇는 유일한 자리다.\n */\n installIngestWindowWriter()\n /*\n * **저널 보존 기간은 선언에서 온다** — 코드에 박아 두지 않는다(2026-08-22).\n *\n * 실측: 개발 저널이 시간당 581,794행 · 0.85GB(하루 20GB) 로 자랐고 지우는 것이 없었다. 그런데\n * 「며칠까지 남기나」는 **배포마다 갈리는 사실**이다 — 개발은 짧아도 되고 실증은 길어야 한다.\n *\n * 선언이 없으면 **아무것도 지우지 않는다.**
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;;AAAA,4DAAiC;AACjC,6DAAkC;AAClC,qEAAqE;AACrE,kDAAkD;AAAzC,sGAAA,UAAU,OAAA;AAEnB,4CAAoD;AACpD,uBAAoB;AAEpB,gDAA8C;AAC9C,6EAAgF;AAChF,oEAAiF;AAEjF;;;;;;;;GAQG;AACH,OAAO,CAAC,EAAE,CAAC,wBAA+B,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAO,EAAE,EAAE;IACjF,IAAI,CAAC;QACH;;;WAGG;QACH,IAAA,oCAAyB,GAAE,CAAA;QAC3B;;;;;;;;WAQG;QACH;;;;;;;;WAQG;QACH,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,2BAA2B,IAAI,CAAC,CAAC,CAAA;QACpE,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,EAAE,oBAAoB,IAAI,CAAC,CAAC,CAAA;QAC3E,MAAM,aAAa,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAA;QACxD,IAAI,aAAa,GAAG,CAAC,EAAE,CAAC;YACtB,qBAAU,CAAC,sBAAsB,GAAG,aAAa,CAAA;YACjD,IAAA,gBAAO,EAAC,4BAA4B,aAAa,MAAM,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAA;QACxF,CAAC;QAED,MAAM,qBAAU,CAAC,SAAS,EAAE,CAAA;QAC5B,MAAM,IAAA,4CAAwB,GAAE,CAAA;QAChC,IAAA,gBAAO,EAAC,iCAAiC,CAAC,CAAA;IAC5C,CAAC;IAAC,OAAO,EAAE,EAAE,CAAC;QACZ,IAAA,kBAAS,EAAC,qCAAqC,EAAE,EAAE,CAAC,CAAA;IACtD,CAAC;AACH,CAAC,CAAC,CAAA","sourcesContent":["export * from './engine/index.js'\nexport * from './service/index.js'\n/* 스키마 이력 — 프레임워크가 `migrations` export 를 모아 실행한다(`run migration`). */\nexport { migrations } from './migrations/index.js'\n\nimport { twinLog, twinError } from './engine/log.js'\nimport './routes.js'\n\nimport { TwinEngine } from './engine/index.js'\nimport { resumeReferenceLiveFeeds } from './service/reference/reference-live.js'\nimport { installIngestWindowWriter } from './service/twin-ingest-window/index.js'\n\n/*\n * 모듈 부팅 — **도는 트윈을 되살린다.**\n *\n * 두 층이 순서대로다: ① 엔진이 등록부의 `running` 행을 선언된 `restartPolicy` 그대로 되살리고\n * (`bootstrap`), ② 레퍼런스 계층이 미러 트윈의 커넥터 피드를 다시 붙인다. 순서가 뒤집히면 피드가\n * 아직 없는 커널에 계측을 붓게 된다.\n *\n * 예전에는 ①도 ②도 없었다 — 재기동 한 번에 등록부만 「도는 중」이라 말하는 트윈이 남았다.\n */\nprocess.on('bootstrap-module-start' as any, async ({ app, config, client }: any) => {\n try {\n /*\n * 닫힌 인제스트 창을 표로 옮기는 문을 먼저 연다 — 트윈을 되살리기 **전에** 등록해야 첫 창부터\n * 남는다. 커널 층은 저장 계층을 모르므로 이 등록이 그 둘을 잇는 유일한 자리다.\n */\n installIngestWindowWriter()\n /*\n * **저널 보존 기간은 선언에서 온다** — 코드에 박아 두지 않는다(2026-08-22).\n *\n * 실측: 개발 저널이 시간당 581,794행 · 0.85GB(하루 20GB) 로 자랐고 지우는 것이 없었다. 그런데\n * 「며칠까지 남기나」는 **배포마다 갈리는 사실**이다 — 개발은 짧아도 되고 실증은 길어야 한다.\n *\n * 선언이 없으면 **아무것도 지우지 않는다.** 알리지 않고 지우는 편이 알리지 않고 쌓는 것보다 나쁘다.\n * 값은 스냅샷 TTL(7일)보다 짧을 수 없다 — 엔진이 그것을 올려 주고 그 사실을 말한다.\n */\n /*\n * 읽는 순서: **환경변수 → 설정 → 없음.**\n *\n * 설정(`config.twin.journalRetentionDays`)이 배포의 선언이고, 환경변수\n * (`TWIN_JOURNAL_RETENTION_DAYS`)는 그것을 **그 자리에서** 덮는다. 컨테이너로 도는 배포에서는\n * 파일을 고치지 않고 값을 바꿀 수 있어야 하고, 개발에서도 그 편이 빠르다.\n *\n * 둘 다 없으면 **아무것도 지우지 않는다.**\n */\n const fromEnv = Number(process.env.TWIN_JOURNAL_RETENTION_DAYS ?? 0)\n const fromConfig = Number(config?.get?.('twin')?.journalRetentionDays ?? 0)\n const retentionDays = fromEnv > 0 ? fromEnv : fromConfig\n if (retentionDays > 0) {\n TwinEngine.JOURNAL_RETENTION_DAYS = retentionDays\n twinLog(`[twin] journal retention ${retentionDays}일 (${fromEnv > 0 ? '환경변수' : '설정'})`)\n }\n\n await TwinEngine.bootstrap()\n await resumeReferenceLiveFeeds()\n twinLog('Headless Twin host has started.')\n } catch (ex) {\n twinError('Headless Twin host failed to start.', ex)\n }\n})\n"]}
|
|
@@ -18,7 +18,7 @@ exports.RenameTwinInstanceBoardToModel1786000000000 = void 0;
|
|
|
18
18
|
*
|
|
19
19
|
* ── 멱등 ──────────────────────────────────────────────────────────────────────
|
|
20
20
|
* 이미 `model` 이 있으면(엔티티 선언 + synchronize 가 먼저 지나간 경우) 아무것도 하지 않는다.
|
|
21
|
-
* 그 경우 옛 컬럼이 남아 있을 수 있는데 **지우지 않는다** — 값이 든 컬럼을 마이그레이션이
|
|
21
|
+
* 그 경우 옛 컬럼이 남아 있을 수 있는데 **지우지 않는다** — 값이 든 컬럼을 마이그레이션이 알리지 않고
|
|
22
22
|
* 버리면 안 된다. 남은 컬럼의 처분은 사람이 확인한 뒤의 일이다.
|
|
23
23
|
*/
|
|
24
24
|
const TABLE_NAME = 'twin_instances';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"1786000000000-RenameTwinInstanceBoardToModel.js","sourceRoot":"","sources":["../../server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,GAAG,gBAAgB,CAAA;AACnC,MAAM,GAAG,GAAG,OAAO,CAAA;AACnB,MAAM,GAAG,GAAG,OAAO,CAAA;AAEnB,MAAa,2CAA2C;IAC/C,KAAK,CAAC,EAAE,CAAC,WAAwB;QACtC,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAA;QACpD,IAAI,CAAC,KAAK;YAAE,OAAM;QAClB,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;QACtE,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAM;QACjC,MAAM,WAAW,CAAC,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;IACjD,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,WAAwB;QACxC,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAA;QACpD,IAAI,CAAC,KAAK;YAAE,OAAM;QAClB,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;QACtE,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAM;QACjC,MAAM,WAAW,CAAC,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;IACjD,CAAC;CACF;AAhBD,kGAgBC","sourcesContent":["import { MigrationInterface, QueryRunner } from 'typeorm'\n\n/**\n * `twin_instances.model` → `twin_instances.model`.\n *\n * ── 왜 개명하나 ────────────────────────────────────────────────────────────────\n * 이 컬럼이 담는 것은 커널이 `TwinModelDef` 라 부르는 **트윈 모델**이다. `board` 는 저장 위치의 옛\n * 이름이고, 이 앱에는 **진짜 board 가 따로 있다**(things-scene 보드 — 공간 표현의 `boardId`). 같은\n * 낱말이 두 가지를 뜻하니 읽는 사람이 매번 어느 쪽인지 판단해야 했고, 코드와 주석이 \"board(마스터\n * 청사진)\" 처럼 번역을 달아 설명해 왔다. 커널이 이미 옳은 이름을 갖고 있으므로 발명할 이름도 없다.\n *\n * ── 데이터를 잃지 않는다 ──────────────────────────────────────────────────────\n * `renameColumn` 은 값을 그대로 둔 채 이름만 바꾼다. 지우고 다시 만들면(엔티티 변경 + synchronize)\n * 기존 트윈의 모델이 통째로 사라지고, 원천이 없는 트윈은 되살릴 방법이 없다.\n *\n * `renameColumn` API 만 쓰므로 지원 대상 5개 드라이버가 각자의 DDL 로 처리한다(raw SQL 금지 규약).\n *\n * ── 멱등 ──────────────────────────────────────────────────────────────────────\n * 이미 `model` 이 있으면(엔티티 선언 + synchronize 가 먼저 지나간 경우) 아무것도 하지 않는다.\n * 그 경우 옛 컬럼이 남아 있을 수 있는데 **지우지 않는다** — 값이 든 컬럼을 마이그레이션이
|
|
1
|
+
{"version":3,"file":"1786000000000-RenameTwinInstanceBoardToModel.js","sourceRoot":"","sources":["../../server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,GAAG,gBAAgB,CAAA;AACnC,MAAM,GAAG,GAAG,OAAO,CAAA;AACnB,MAAM,GAAG,GAAG,OAAO,CAAA;AAEnB,MAAa,2CAA2C;IAC/C,KAAK,CAAC,EAAE,CAAC,WAAwB;QACtC,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAA;QACpD,IAAI,CAAC,KAAK;YAAE,OAAM;QAClB,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;QACtE,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAM;QACjC,MAAM,WAAW,CAAC,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;IACjD,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,WAAwB;QACxC,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAA;QACpD,IAAI,CAAC,KAAK;YAAE,OAAM;QAClB,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;QACtE,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAM;QACjC,MAAM,WAAW,CAAC,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;IACjD,CAAC;CACF;AAhBD,kGAgBC","sourcesContent":["import { MigrationInterface, QueryRunner } from 'typeorm'\n\n/**\n * `twin_instances.model` → `twin_instances.model`.\n *\n * ── 왜 개명하나 ────────────────────────────────────────────────────────────────\n * 이 컬럼이 담는 것은 커널이 `TwinModelDef` 라 부르는 **트윈 모델**이다. `board` 는 저장 위치의 옛\n * 이름이고, 이 앱에는 **진짜 board 가 따로 있다**(things-scene 보드 — 공간 표현의 `boardId`). 같은\n * 낱말이 두 가지를 뜻하니 읽는 사람이 매번 어느 쪽인지 판단해야 했고, 코드와 주석이 \"board(마스터\n * 청사진)\" 처럼 번역을 달아 설명해 왔다. 커널이 이미 옳은 이름을 갖고 있으므로 발명할 이름도 없다.\n *\n * ── 데이터를 잃지 않는다 ──────────────────────────────────────────────────────\n * `renameColumn` 은 값을 그대로 둔 채 이름만 바꾼다. 지우고 다시 만들면(엔티티 변경 + synchronize)\n * 기존 트윈의 모델이 통째로 사라지고, 원천이 없는 트윈은 되살릴 방법이 없다.\n *\n * `renameColumn` API 만 쓰므로 지원 대상 5개 드라이버가 각자의 DDL 로 처리한다(raw SQL 금지 규약).\n *\n * ── 멱등 ──────────────────────────────────────────────────────────────────────\n * 이미 `model` 이 있으면(엔티티 선언 + synchronize 가 먼저 지나간 경우) 아무것도 하지 않는다.\n * 그 경우 옛 컬럼이 남아 있을 수 있는데 **지우지 않는다** — 값이 든 컬럼을 마이그레이션이 알리지 않고\n * 버리면 안 된다. 남은 컬럼의 처분은 사람이 확인한 뒤의 일이다.\n */\nconst TABLE_NAME = 'twin_instances'\nconst OLD = 'board'\nconst NEW = 'model'\n\nexport class RenameTwinInstanceBoardToModel1786000000000 implements MigrationInterface {\n public async up(queryRunner: QueryRunner): Promise<void> {\n const table = await queryRunner.getTable(TABLE_NAME)\n if (!table) return\n const has = (name: string) => table.columns.some(c => c.name === name)\n if (has(NEW) || !has(OLD)) return\n await queryRunner.renameColumn(table, OLD, NEW)\n }\n\n public async down(queryRunner: QueryRunner): Promise<void> {\n const table = await queryRunner.getTable(TABLE_NAME)\n if (!table) return\n const has = (name: string) => table.columns.some(c => c.name === name)\n if (has(OLD) || !has(NEW)) return\n await queryRunner.renameColumn(table, NEW, OLD)\n }\n}\n"]}
|
|
@@ -7,17 +7,17 @@ const typeorm_1 = require("typeorm");
|
|
|
7
7
|
*
|
|
8
8
|
* ① `twin_events.action` — EPCIS 행위(ADD/OBSERVE/DELETE) 승격.
|
|
9
9
|
* 되풀어 읽는 스냅샷은 원리적으로 `DELETE` 를 못 낸다. 그래서 「이 트윈이 반출을 한 번이라도
|
|
10
|
-
*
|
|
10
|
+
* 계산했는가」가 진짜 delta 피드의 증거인데, 값이 `payload`(simple-json = TEXT) 안에 있는 동안에는
|
|
11
11
|
* 5개 드라이버 어디에서도 그 질문을 할 수 없었다(DB별 JSON 연산자 금지).
|
|
12
12
|
*
|
|
13
13
|
* ② `twin_references.last_warning_count` — 마지막 동기에서 못 옮긴 값의 수.
|
|
14
|
-
* 세지 않은 구조 인제스트는 도크를 랙으로 바꿔 놓고도
|
|
14
|
+
* 세지 않은 구조 인제스트는 도크를 랙으로 바꿔 놓고도 성공이라 답하면서 아무 일도 하지 않는다.
|
|
15
15
|
*
|
|
16
16
|
* **둘 다 nullable, 기본값 없음.** 기본값 0 을 깔면 「없었다」와 「세지 않았다」가 같아지고, 그
|
|
17
17
|
* 구분이 단계 판정의 전부다(옛 행은 모르는 것이 맞다 — 채워 넣지 않는다).
|
|
18
18
|
*
|
|
19
19
|
* 인덱스 `ix_twin_event_6` 는 저널이 큰 환경에서 생성에 시간이 걸린다. 그래도 만드는 이유:
|
|
20
|
-
* 없으면 「반출을
|
|
20
|
+
* 없으면 「반출을 계산했나」가 그 인스턴스 저널 전량 주사가 되고, 반출이 **없는** 경우(초기 상태이자
|
|
21
21
|
* 가장 흔한 경우)가 항상 최악이다.
|
|
22
22
|
*
|
|
23
23
|
* 멱등: 이미 있으면 아무것도 하지 않는다. TypeORM API 만 쓴다(raw SQL 금지 — 드라이버 5종).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"1786100000000-PromoteEventActionAndSyncWarnings.js","sourceRoot":"","sources":["../../server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts"],"names":[],"mappings":";;;AAAA,qCAAkF;AAElF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,GAAG,aAAa,CAAA;AAC5B,MAAM,IAAI,GAAG,iBAAiB,CAAA;AAC9B,MAAM,MAAM,GAAG,QAAQ,CAAA;AACvB,MAAM,IAAI,GAAG,oBAAoB,CAAA;AACjC,MAAM,EAAE,GAAG,iBAAiB,CAAA;AAE5B,MAAa,8CAA8C;IAClD,KAAK,CAAC,EAAE,CAAC,WAAwB;QACtC,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;QACjD,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,EAAE,CAAC;YAC3D,MAAM,WAAW,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,qBAAW,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QACzH,CAAC;QACD,MAAM,UAAU,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;QACrD,IAAI,UAAU,IAAI,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,EAAE,CAAC;YAClH,MAAM,WAAW,CAAC,WAAW,CAAC,UAAU,EAAE,IAAI,oBAAU,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC,WAAW,EAAE,aAAa,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAA;QAC5H,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;QAC7C,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;YACrD,MAAM,WAAW,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,qBAAW,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QACnG,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,WAAwB;QACxC,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;QACjD,IAAI,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC;YAAE,MAAM,WAAW,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;QACrF,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;QAClD,IAAI,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC;YAAE,MAAM,WAAW,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAChG,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;QAC7C,IAAI,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC;YAAE,MAAM,WAAW,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IACxF,CAAC;CACF;AAzBD,wGAyBC","sourcesContent":["import { MigrationInterface, QueryRunner, TableColumn, TableIndex } from 'typeorm'\n\n/**\n * 두 계측을 위한 컬럼 둘.\n *\n * ① `twin_events.action` — EPCIS 행위(ADD/OBSERVE/DELETE) 승격.\n * 되풀어 읽는 스냅샷은 원리적으로 `DELETE` 를 못 낸다. 그래서 「이 트윈이 반출을 한 번이라도\n *
|
|
1
|
+
{"version":3,"file":"1786100000000-PromoteEventActionAndSyncWarnings.js","sourceRoot":"","sources":["../../server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts"],"names":[],"mappings":"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","sourcesContent":["import { MigrationInterface, QueryRunner, TableColumn, TableIndex } from 'typeorm'\n\n/**\n * 두 계측을 위한 컬럼 둘.\n *\n * ① `twin_events.action` — EPCIS 행위(ADD/OBSERVE/DELETE) 승격.\n * 되풀어 읽는 스냅샷은 원리적으로 `DELETE` 를 못 낸다. 그래서 「이 트윈이 반출을 한 번이라도\n * 계산했는가」가 진짜 delta 피드의 증거인데, 값이 `payload`(simple-json = TEXT) 안에 있는 동안에는\n * 5개 드라이버 어디에서도 그 질문을 할 수 없었다(DB별 JSON 연산자 금지).\n *\n * ② `twin_references.last_warning_count` — 마지막 동기에서 못 옮긴 값의 수.\n * 세지 않은 구조 인제스트는 도크를 랙으로 바꿔 놓고도 성공이라 답하면서 아무 일도 하지 않는다.\n *\n * **둘 다 nullable, 기본값 없음.** 기본값 0 을 깔면 「없었다」와 「세지 않았다」가 같아지고, 그\n * 구분이 단계 판정의 전부다(옛 행은 모르는 것이 맞다 — 채워 넣지 않는다).\n *\n * 인덱스 `ix_twin_event_6` 는 저널이 큰 환경에서 생성에 시간이 걸린다. 그래도 만드는 이유:\n * 없으면 「반출을 계산했나」가 그 인스턴스 저널 전량 주사가 되고, 반출이 **없는** 경우(초기 상태이자\n * 가장 흔한 경우)가 항상 최악이다.\n *\n * 멱등: 이미 있으면 아무것도 하지 않는다. TypeORM API 만 쓴다(raw SQL 금지 — 드라이버 5종).\n */\nconst EVENTS = 'twin_events'\nconst REFS = 'twin_references'\nconst ACTION = 'action'\nconst WARN = 'last_warning_count'\nconst IX = 'ix_twin_event_6'\n\nexport class PromoteEventActionAndSyncWarnings1786100000000 implements MigrationInterface {\n public async up(queryRunner: QueryRunner): Promise<void> {\n const events = await queryRunner.getTable(EVENTS)\n if (events && !events.columns.some(c => c.name === ACTION)) {\n await queryRunner.addColumn(events, new TableColumn({ name: ACTION, type: 'varchar', length: '16', isNullable: true }))\n }\n const withColumn = await queryRunner.getTable(EVENTS)\n if (withColumn && withColumn.columns.some(c => c.name === ACTION) && !withColumn.indices.some(i => i.name === IX)) {\n await queryRunner.createIndex(withColumn, new TableIndex({ name: IX, columnNames: ['domain_id', 'instance_id', ACTION] }))\n }\n\n const refs = await queryRunner.getTable(REFS)\n if (refs && !refs.columns.some(c => c.name === WARN)) {\n await queryRunner.addColumn(refs, new TableColumn({ name: WARN, type: 'int', isNullable: true }))\n }\n }\n\n public async down(queryRunner: QueryRunner): Promise<void> {\n const events = await queryRunner.getTable(EVENTS)\n if (events?.indices.some(i => i.name === IX)) await queryRunner.dropIndex(events, IX)\n const events2 = await queryRunner.getTable(EVENTS)\n if (events2?.columns.some(c => c.name === ACTION)) await queryRunner.dropColumn(events2, ACTION)\n const refs = await queryRunner.getTable(REFS)\n if (refs?.columns.some(c => c.name === WARN)) await queryRunner.dropColumn(refs, WARN)\n }\n}\n"]}
|
|
@@ -5,7 +5,7 @@ const typeorm_1 = require("typeorm");
|
|
|
5
5
|
/**
|
|
6
6
|
* `twin_references.live_cursor` — **어디까지 읽었나**를 재기동 너머로 잇는다.
|
|
7
7
|
*
|
|
8
|
-
* ── 무엇이
|
|
8
|
+
* ── 무엇이 오류 없이 사라지고 있었나 (2026-08-23 실측) ─────────────────────────
|
|
9
9
|
* 라이브 어댑터가 붙을 때마다 `Date.now() − 되돌아볼 날수` 로 창을 새로 만들었다. 그래서 **재기동마다
|
|
10
10
|
* 미러의 과거가 잘렸다** — 작업 2,855 → 2,820(그 사이 8시간이 흐른 만큼). 미러가 아는 것이 「원본의
|
|
11
11
|
* 사실」이 아니라 「창의 함수」였고, 화면은 그 수를 보이면서 그 사실을 말하지 않았다.
|
|
@@ -17,7 +17,7 @@ const typeorm_1 = require("typeorm");
|
|
|
17
17
|
* 재기동 연속성으로 이어받는다. 「우리가 어디까지 읽었나」가 정확히 그 성질이다.
|
|
18
18
|
*
|
|
19
19
|
* 그리고 스냅샷 체크포인트와 성질이 다르다: 그쪽은 잃어도 원천이 정정해 준다(그래서 TTL 캐시로 족하다).
|
|
20
|
-
* **이 값은 잃으면 원천에 묻지 않게 된다** — 창이 다시 미끄러지고, 그 손실은 오류 없이
|
|
20
|
+
* **이 값은 잃으면 원천에 묻지 않게 된다** — 창이 다시 미끄러지고, 그 손실은 오류 없이 들어온 것이 없다.
|
|
21
21
|
* 만료되는 자리에 둘 수 없다.
|
|
22
22
|
*
|
|
23
23
|
* ── 모양 ────────────────────────────────────────────────────────────────────
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"1786200000000-CarryLiveFeedCursor.js","sourceRoot":"","sources":["../../server/migrations/1786200000000-CarryLiveFeedCursor.ts"],"names":[],"mappings":";;;AAAA,qCAAsE;AAEtE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,IAAI,GAAG,iBAAiB,CAAA;AAC9B,MAAM,MAAM,GAAG,aAAa,CAAA;AAE5B,MAAa,gCAAgC;IACpC,KAAK,CAAC,EAAE,CAAC,WAAwB;QACtC,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;QAC7C,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,EAAE,CAAC;YACvD,MAAM,WAAW,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,qBAAW,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QACtG,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,WAAwB;QACxC,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;QAC7C,IAAI,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,EAAE,CAAC;YACtD,MAAM,WAAW,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;QAC5C,CAAC;IACH,CAAC;CACF;AAdD,4EAcC","sourcesContent":["import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm'\n\n/**\n * `twin_references.live_cursor` — **어디까지 읽었나**를 재기동 너머로 잇는다.\n *\n * ── 무엇이
|
|
1
|
+
{"version":3,"file":"1786200000000-CarryLiveFeedCursor.js","sourceRoot":"","sources":["../../server/migrations/1786200000000-CarryLiveFeedCursor.ts"],"names":[],"mappings":";;;AAAA,qCAAsE;AAEtE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,IAAI,GAAG,iBAAiB,CAAA;AAC9B,MAAM,MAAM,GAAG,aAAa,CAAA;AAE5B,MAAa,gCAAgC;IACpC,KAAK,CAAC,EAAE,CAAC,WAAwB;QACtC,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;QAC7C,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,EAAE,CAAC;YACvD,MAAM,WAAW,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,qBAAW,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QACtG,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,WAAwB;QACxC,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;QAC7C,IAAI,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,EAAE,CAAC;YACtD,MAAM,WAAW,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;QAC5C,CAAC;IACH,CAAC;CACF;AAdD,4EAcC","sourcesContent":["import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm'\n\n/**\n * `twin_references.live_cursor` — **어디까지 읽었나**를 재기동 너머로 잇는다.\n *\n * ── 무엇이 오류 없이 사라지고 있었나 (2026-08-23 실측) ─────────────────────────\n * 라이브 어댑터가 붙을 때마다 `Date.now() − 되돌아볼 날수` 로 창을 새로 만들었다. 그래서 **재기동마다\n * 미러의 과거가 잘렸다** — 작업 2,855 → 2,820(그 사이 8시간이 흐른 만큼). 미러가 아는 것이 「원본의\n * 사실」이 아니라 「창의 함수」였고, 화면은 그 수를 보이면서 그 사실을 말하지 않았다.\n *\n * 같은 원본에서 잰 규모가 그 값을 말한다: 작업지시 77,594건 중 창이 읽는 것이 3,039건(3.9%)이다.\n *\n * ── 왜 표인가 (캐시가 아니라) ───────────────────────────────────────────────\n * 커널이 갈림을 이미 적어 두었다(§`hydrateContinuity`): 「원천이 **애초에 다시 말해 주지 않는 축**」은\n * 재기동 연속성으로 이어받는다. 「우리가 어디까지 읽었나」가 정확히 그 성질이다.\n *\n * 그리고 스냅샷 체크포인트와 성질이 다르다: 그쪽은 잃어도 원천이 정정해 준다(그래서 TTL 캐시로 족하다).\n * **이 값은 잃으면 원천에 묻지 않게 된다** — 창이 다시 미끄러지고, 그 손실은 오류 없이 들어온 것이 없다.\n * 만료되는 자리에 둘 수 없다.\n *\n * ── 모양 ────────────────────────────────────────────────────────────────────\n * `{ streams: { [흐름]: { since?, seen: string[] } }, firstAttachedAt? }`\n *\n * **흐름 이름은 어댑터가 정한다** — 원본마다 흐름 수와 뜻이 다르다(첫 실 원본은 재고·오더·로트·투입 넷).\n * 이 층은 열쇠로만 다룬다. 계약이 이름을 닫으면 새 원본마다 계약을 고치게 된다.\n *\n * `firstAttachedAt` 은 「언제부터 아는가」다 — 커널은 상한만 안다(`nowTime`). 둘이 함께 「이 트윈이\n * 아는 구간」이고, 화면이 수를 보일 때 그 구간을 말해야 한다.\n *\n * **nullable, 기본값 없음.** `null` = 한 번도 붙지 않았다는 사실이고, 그때만 되돌아볼 날수로 첫 창을\n * 만든다. 빈 객체를 기본으로 깔면 「첫 붙음」과 「이어 붙음」이 같아지고, 그 구분이 이 컬럼의 전부다.\n *\n * `simple-json` = TEXT 이므로 드라이버 다섯을 그대로 지난다(DB별 JSON 타입·연산자 금지).\n * 멱등: 이미 있으면 아무것도 하지 않는다. TypeORM API 만 쓴다(raw SQL 금지).\n */\nconst REFS = 'twin_references'\nconst CURSOR = 'live_cursor'\n\nexport class CarryLiveFeedCursor1786200000000 implements MigrationInterface {\n public async up(queryRunner: QueryRunner): Promise<void> {\n const refs = await queryRunner.getTable(REFS)\n if (refs && !refs.columns.some(c => c.name === CURSOR)) {\n await queryRunner.addColumn(refs, new TableColumn({ name: CURSOR, type: 'text', isNullable: true }))\n }\n }\n\n public async down(queryRunner: QueryRunner): Promise<void> {\n const refs = await queryRunner.getTable(REFS)\n if (refs && refs.columns.some(c => c.name === CURSOR)) {\n await queryRunner.dropColumn(refs, CURSOR)\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IndexEventTypeByTime1786300000000 = void 0;
|
|
4
|
+
const typeorm_1 = require("typeorm");
|
|
5
|
+
/**
|
|
6
|
+
* 「이 종류의 사실을 시각순으로」 — 그 축에 인덱스가 없었다.
|
|
7
|
+
*
|
|
8
|
+
* ── 무엇이 났나 (2026-08-25 실측 · 승화푸드) ─────────────────────────────────
|
|
9
|
+
* 지난 기록 조회는 필터가 없을 때 `revision DESC`(적힌 순서)로 잘랐다. 연결된 시스템을 되읽는
|
|
10
|
+
* 트윈에서는 그 순서가 일어난 순서와 달라서, 공정 타임라인이 4월 사실을 최근 것으로 그렸다. 그래서
|
|
11
|
+
* 기본을 시각순으로 고쳤는데(`journal-order.ts`), 그때 이 인덱스가 없는 것이 드러났다.
|
|
12
|
+
*
|
|
13
|
+
* ix_2 (domain, instance, eventType, revision) 종류는 걸러지고 정렬이 적힌 순서다
|
|
14
|
+
* ix_1 (domain, instance, eventTime) 정렬은 맞고 종류를 못 거른다
|
|
15
|
+
*
|
|
16
|
+
* 종류로 거르고 시각으로 정렬하면 `ix_1` 로 걷다가 행마다 테이블을 열어 종류를 확인한다. 찾는 종류가
|
|
17
|
+
* 며칠 전에 멈춘 트윈에서는 그 사이 행을 전부 읽고 버린다. 실측: 200건에 1.7초(같은 조회가 옛 정렬로는
|
|
18
|
+
* 0.001초). 승화푸드에서 작업 사실은 8월 22일에 멈춰 있고 그 뒤로 리비전이 110만 늘었다.
|
|
19
|
+
*
|
|
20
|
+
* ── 그 1.7초가 서버 전체를 막았다 ───────────────────────────────────────────
|
|
21
|
+
* 공정 타임라인은 5초마다 세 종류를 부른다. sqlite 드라이버는 연결이 하나라서, 5초 주기에 5초가 넘는
|
|
22
|
+
* 일감이 들어오면 나머지 질의가 전부 뒤에 선다. 39분 동안 느린 질의 3,152건이 쌓였고, 한 줄짜리
|
|
23
|
+
* `domains` 조회가 중간 7.4초로 찍혔다 — 그 질의의 문제가 아니라 줄을 선 값이다.
|
|
24
|
+
*
|
|
25
|
+
* ── 왜 이제 얹나 ────────────────────────────────────────────────────────────
|
|
26
|
+
* 엔티티 주석이 「저널 하나가 아주 커져서 이 축들의 조회가 느려지면 그때 측정을 근거로 인덱스를 추가할
|
|
27
|
+
* 일이지, 지레 얹어 쓰기를 무겁게 할 일은 아니다」라고 적어 두었다. 그 측정이 나왔다.
|
|
28
|
+
*
|
|
29
|
+
* 큰 기록에서는 생성에 시간이 걸린다(47GB 개발 파일에서 수분). 멱등이므로 다시 돌려도 안전하고,
|
|
30
|
+
* TypeORM API 만 쓴다(드라이버 다섯을 다 지나가야 하므로 raw SQL 금지).
|
|
31
|
+
*/
|
|
32
|
+
const EVENTS = 'twin_events';
|
|
33
|
+
const IX = 'ix_twin_event_7';
|
|
34
|
+
const COLUMNS = ['domain_id', 'instance_id', 'event_type', 'event_time'];
|
|
35
|
+
class IndexEventTypeByTime1786300000000 {
|
|
36
|
+
async up(queryRunner) {
|
|
37
|
+
const events = await queryRunner.getTable(EVENTS);
|
|
38
|
+
if (!events)
|
|
39
|
+
return;
|
|
40
|
+
if (events.indices.some(i => i.name === IX))
|
|
41
|
+
return;
|
|
42
|
+
await queryRunner.createIndex(events, new typeorm_1.TableIndex({ name: IX, columnNames: COLUMNS }));
|
|
43
|
+
}
|
|
44
|
+
async down(queryRunner) {
|
|
45
|
+
const events = await queryRunner.getTable(EVENTS);
|
|
46
|
+
if (!events)
|
|
47
|
+
return;
|
|
48
|
+
if (!events.indices.some(i => i.name === IX))
|
|
49
|
+
return;
|
|
50
|
+
await queryRunner.dropIndex(events, IX);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.IndexEventTypeByTime1786300000000 = IndexEventTypeByTime1786300000000;
|
|
54
|
+
//# sourceMappingURL=1786300000000-IndexEventTypeByTime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"1786300000000-IndexEventTypeByTime.js","sourceRoot":"","sources":["../../server/migrations/1786300000000-IndexEventTypeByTime.ts"],"names":[],"mappings":";;;AAAA,qCAAqE;AAErE;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,MAAM,GAAG,aAAa,CAAA;AAC5B,MAAM,EAAE,GAAG,iBAAiB,CAAA;AAC5B,MAAM,OAAO,GAAG,CAAC,WAAW,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,CAAC,CAAA;AAExE,MAAa,iCAAiC;IACrC,KAAK,CAAC,EAAE,CAAC,WAAwB;QACtC,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;QACjD,IAAI,CAAC,MAAM;YAAE,OAAM;QACnB,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC;YAAE,OAAM;QACnD,MAAM,WAAW,CAAC,WAAW,CAAC,MAAM,EAAE,IAAI,oBAAU,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC,CAAA;IAC3F,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,WAAwB;QACxC,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;QACjD,IAAI,CAAC,MAAM;YAAE,OAAM;QACnB,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC;YAAE,OAAM;QACpD,MAAM,WAAW,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IACzC,CAAC;CACF;AAdD,8EAcC","sourcesContent":["import { MigrationInterface, QueryRunner, TableIndex } from 'typeorm'\n\n/**\n * 「이 종류의 사실을 시각순으로」 — 그 축에 인덱스가 없었다.\n *\n * ── 무엇이 났나 (2026-08-25 실측 · 승화푸드) ─────────────────────────────────\n * 지난 기록 조회는 필터가 없을 때 `revision DESC`(적힌 순서)로 잘랐다. 연결된 시스템을 되읽는\n * 트윈에서는 그 순서가 일어난 순서와 달라서, 공정 타임라인이 4월 사실을 최근 것으로 그렸다. 그래서\n * 기본을 시각순으로 고쳤는데(`journal-order.ts`), 그때 이 인덱스가 없는 것이 드러났다.\n *\n * ix_2 (domain, instance, eventType, revision) 종류는 걸러지고 정렬이 적힌 순서다\n * ix_1 (domain, instance, eventTime) 정렬은 맞고 종류를 못 거른다\n *\n * 종류로 거르고 시각으로 정렬하면 `ix_1` 로 걷다가 행마다 테이블을 열어 종류를 확인한다. 찾는 종류가\n * 며칠 전에 멈춘 트윈에서는 그 사이 행을 전부 읽고 버린다. 실측: 200건에 1.7초(같은 조회가 옛 정렬로는\n * 0.001초). 승화푸드에서 작업 사실은 8월 22일에 멈춰 있고 그 뒤로 리비전이 110만 늘었다.\n *\n * ── 그 1.7초가 서버 전체를 막았다 ───────────────────────────────────────────\n * 공정 타임라인은 5초마다 세 종류를 부른다. sqlite 드라이버는 연결이 하나라서, 5초 주기에 5초가 넘는\n * 일감이 들어오면 나머지 질의가 전부 뒤에 선다. 39분 동안 느린 질의 3,152건이 쌓였고, 한 줄짜리\n * `domains` 조회가 중간 7.4초로 찍혔다 — 그 질의의 문제가 아니라 줄을 선 값이다.\n *\n * ── 왜 이제 얹나 ────────────────────────────────────────────────────────────\n * 엔티티 주석이 「저널 하나가 아주 커져서 이 축들의 조회가 느려지면 그때 측정을 근거로 인덱스를 추가할\n * 일이지, 지레 얹어 쓰기를 무겁게 할 일은 아니다」라고 적어 두었다. 그 측정이 나왔다.\n *\n * 큰 기록에서는 생성에 시간이 걸린다(47GB 개발 파일에서 수분). 멱등이므로 다시 돌려도 안전하고,\n * TypeORM API 만 쓴다(드라이버 다섯을 다 지나가야 하므로 raw SQL 금지).\n */\nconst EVENTS = 'twin_events'\nconst IX = 'ix_twin_event_7'\nconst COLUMNS = ['domain_id', 'instance_id', 'event_type', 'event_time']\n\nexport class IndexEventTypeByTime1786300000000 implements MigrationInterface {\n public async up(queryRunner: QueryRunner): Promise<void> {\n const events = await queryRunner.getTable(EVENTS)\n if (!events) return\n if (events.indices.some(i => i.name === IX)) return\n await queryRunner.createIndex(events, new TableIndex({ name: IX, columnNames: COLUMNS }))\n }\n\n public async down(queryRunner: QueryRunner): Promise<void> {\n const events = await queryRunner.getTable(EVENTS)\n if (!events) return\n if (!events.indices.some(i => i.name === IX)) return\n await queryRunner.dropIndex(events, IX)\n }\n}\n"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IndexEventCreatedAt1786400000000 = void 0;
|
|
4
|
+
const typeorm_1 = require("typeorm");
|
|
5
|
+
/**
|
|
6
|
+
* 「이 트윈의 오래된 기록」 — 보관 기간 정리가 쓰는 축에 인덱스가 없었다.
|
|
7
|
+
*
|
|
8
|
+
* ── 무엇이 났나 (2026-08-27 실측 · 개발 파일 51GB · 3,128만 행) ───────────────
|
|
9
|
+
* 정리는 `created_at` 으로 자른다(보관은 저장 나이의 문제이고 저장 나이는 실제 시계다). 그런데 그 축에
|
|
10
|
+
* 인덱스가 없어서, 매 배치마다 그 트윈의 저널을 전량 주사했다.
|
|
11
|
+
*
|
|
12
|
+
* hatio-mx2 의 7일 밖 행을 세기 208초 ← 1,029만 행을 전부 읽는다(7일 밖은 2,446건뿐이다)
|
|
13
|
+
* revision 으로 한 행 짚기 0.004초
|
|
14
|
+
*
|
|
15
|
+
* 정리는 한 창에 2초만 쓰도록 예산이 걸려 있다. 그 2초를 주사에 다 쓰고 44건만 지우고 멈췄다. 지금은
|
|
16
|
+
* 지울 것이 적어서 크게 드러나지 않지만, 보관 기간을 줄이거나 쓰는 양이 늘면 따라가지 못한다.
|
|
17
|
+
*
|
|
18
|
+
* ── 큰 기록에서는 만드는 데 시간이 걸린다 ───────────────────────────────────
|
|
19
|
+
* 3,128만 행에 인덱스를 만드는 동안 쓰기가 막힌다(sqlite 는 쓰기가 하나다). 서버를 내린 뒤 돌린다.
|
|
20
|
+
* 멱등이므로 다시 돌려도 안전하고, TypeORM API 만 쓴다(드라이버 다섯을 다 지나가야 하므로 raw SQL 금지).
|
|
21
|
+
*/
|
|
22
|
+
const EVENTS = 'twin_events';
|
|
23
|
+
const IX = 'ix_twin_event_8';
|
|
24
|
+
const COLUMNS = ['domain_id', 'instance_id', 'created_at'];
|
|
25
|
+
class IndexEventCreatedAt1786400000000 {
|
|
26
|
+
async up(queryRunner) {
|
|
27
|
+
const events = await queryRunner.getTable(EVENTS);
|
|
28
|
+
if (!events)
|
|
29
|
+
return;
|
|
30
|
+
if (events.indices.some(i => i.name === IX))
|
|
31
|
+
return;
|
|
32
|
+
await queryRunner.createIndex(events, new typeorm_1.TableIndex({ name: IX, columnNames: COLUMNS }));
|
|
33
|
+
}
|
|
34
|
+
async down(queryRunner) {
|
|
35
|
+
const events = await queryRunner.getTable(EVENTS);
|
|
36
|
+
if (!events)
|
|
37
|
+
return;
|
|
38
|
+
if (!events.indices.some(i => i.name === IX))
|
|
39
|
+
return;
|
|
40
|
+
await queryRunner.dropIndex(events, IX);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.IndexEventCreatedAt1786400000000 = IndexEventCreatedAt1786400000000;
|
|
44
|
+
//# sourceMappingURL=1786400000000-IndexEventCreatedAt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"1786400000000-IndexEventCreatedAt.js","sourceRoot":"","sources":["../../server/migrations/1786400000000-IndexEventCreatedAt.ts"],"names":[],"mappings":";;;AAAA,qCAAqE;AAErE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,GAAG,aAAa,CAAA;AAC5B,MAAM,EAAE,GAAG,iBAAiB,CAAA;AAC5B,MAAM,OAAO,GAAG,CAAC,WAAW,EAAE,aAAa,EAAE,YAAY,CAAC,CAAA;AAE1D,MAAa,gCAAgC;IACpC,KAAK,CAAC,EAAE,CAAC,WAAwB;QACtC,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;QACjD,IAAI,CAAC,MAAM;YAAE,OAAM;QACnB,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC;YAAE,OAAM;QACnD,MAAM,WAAW,CAAC,WAAW,CAAC,MAAM,EAAE,IAAI,oBAAU,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC,CAAA;IAC3F,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,WAAwB;QACxC,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;QACjD,IAAI,CAAC,MAAM;YAAE,OAAM;QACnB,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC;YAAE,OAAM;QACpD,MAAM,WAAW,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IACzC,CAAC;CACF;AAdD,4EAcC","sourcesContent":["import { MigrationInterface, QueryRunner, TableIndex } from 'typeorm'\n\n/**\n * 「이 트윈의 오래된 기록」 — 보관 기간 정리가 쓰는 축에 인덱스가 없었다.\n *\n * ── 무엇이 났나 (2026-08-27 실측 · 개발 파일 51GB · 3,128만 행) ───────────────\n * 정리는 `created_at` 으로 자른다(보관은 저장 나이의 문제이고 저장 나이는 실제 시계다). 그런데 그 축에\n * 인덱스가 없어서, 매 배치마다 그 트윈의 저널을 전량 주사했다.\n *\n * hatio-mx2 의 7일 밖 행을 세기 208초 ← 1,029만 행을 전부 읽는다(7일 밖은 2,446건뿐이다)\n * revision 으로 한 행 짚기 0.004초\n *\n * 정리는 한 창에 2초만 쓰도록 예산이 걸려 있다. 그 2초를 주사에 다 쓰고 44건만 지우고 멈췄다. 지금은\n * 지울 것이 적어서 크게 드러나지 않지만, 보관 기간을 줄이거나 쓰는 양이 늘면 따라가지 못한다.\n *\n * ── 큰 기록에서는 만드는 데 시간이 걸린다 ───────────────────────────────────\n * 3,128만 행에 인덱스를 만드는 동안 쓰기가 막힌다(sqlite 는 쓰기가 하나다). 서버를 내린 뒤 돌린다.\n * 멱등이므로 다시 돌려도 안전하고, TypeORM API 만 쓴다(드라이버 다섯을 다 지나가야 하므로 raw SQL 금지).\n */\nconst EVENTS = 'twin_events'\nconst IX = 'ix_twin_event_8'\nconst COLUMNS = ['domain_id', 'instance_id', 'created_at']\n\nexport class IndexEventCreatedAt1786400000000 implements MigrationInterface {\n public async up(queryRunner: QueryRunner): Promise<void> {\n const events = await queryRunner.getTable(EVENTS)\n if (!events) return\n if (events.indices.some(i => i.name === IX)) return\n await queryRunner.createIndex(events, new TableIndex({ name: IX, columnNames: COLUMNS }))\n }\n\n public async down(queryRunner: QueryRunner): Promise<void> {\n const events = await queryRunner.getTable(EVENTS)\n if (!events) return\n if (!events.indices.some(i => i.name === IX)) return\n await queryRunner.dropIndex(events, IX)\n }\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { MigrationInterface, QueryRunner } from 'typeorm';
|
|
2
|
+
export declare class CreateTwinSubjectEvents1786500000000 implements MigrationInterface {
|
|
3
|
+
up(queryRunner: QueryRunner): Promise<void>;
|
|
4
|
+
private createSubjectEvents;
|
|
5
|
+
down(queryRunner: QueryRunner): Promise<void>;
|
|
6
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CreateTwinSubjectEvents1786500000000 = void 0;
|
|
4
|
+
const typeorm_1 = require("typeorm");
|
|
5
|
+
/**
|
|
6
|
+
* **대상별 사건 목록** — 조회용 파생 표를 만든다.
|
|
7
|
+
*
|
|
8
|
+
* ── 왜 (2026-08-27 실측) ────────────────────────────────────────────────────
|
|
9
|
+
* 360 화면이 「이 대상의 이력」을 묻고 그 조회가 64초다. 「무엇으로 걸러 시각순으로 정렬해 앞에서
|
|
10
|
+
* 30개」라는 모양에 맞는 목차가 저널에 없고, 얹어도 풀리지 않는다.
|
|
11
|
+
*
|
|
12
|
+
* 여러 값으로 거르면(IN) 정렬 때문에 목차를 못 쓴다
|
|
13
|
+
* 축이 늘 때마다 저널의 목차가 늘어난다 — 360 의 축 넷이면 13개가 된다
|
|
14
|
+
* 품목과 로트가 한 문자열에 붙어 있어 품목만으로는 찾을 수 없다
|
|
15
|
+
*
|
|
16
|
+
* ── 비어서 시작한다 ────────────────────────────────────────────────────────
|
|
17
|
+
* 이 표는 **앞으로만 채운다.** 기존 3,128만 건을 소급해서 채우지 않는다 — 그래야 새 조회 축이 필요할
|
|
18
|
+
* 때마다 전체를 다시 만드는 구조가 되지 않는다. 담지 않은 구간은 조회가 `coveredFrom` 으로 말한다.
|
|
19
|
+
*
|
|
20
|
+
* 그래서 이 마이그레이션은 큰 기록에서도 값싸다(표와 목차를 만들 뿐, 채우지 않는다).
|
|
21
|
+
*
|
|
22
|
+
* 멱등이고 TypeORM API 만 쓴다(드라이버 다섯을 다 지나가야 하므로 raw SQL 금지). 컬럼 타입 이름을
|
|
23
|
+
* 적지 않는 규율은 엔티티와 같다 — `datetime` 은 Postgres 가 거절한다.
|
|
24
|
+
*
|
|
25
|
+
* 설계: `operato-twin/design/plans/read-models.md`
|
|
26
|
+
*/
|
|
27
|
+
const TABLE = 'twin_subject_events';
|
|
28
|
+
class CreateTwinSubjectEvents1786500000000 {
|
|
29
|
+
async up(queryRunner) {
|
|
30
|
+
/*
|
|
31
|
+
* 표마다 따로 본다 — 하나가 이미 있다고 여기서 돌아가면 나머지가 만들어지지 않는다. 실제로 그렇게
|
|
32
|
+
* 적어 두었다가, 사건 목록만 있고 진행 지점 표가 없는 상태를 만들었다.
|
|
33
|
+
*/
|
|
34
|
+
if (!(await queryRunner.hasTable(TABLE))) {
|
|
35
|
+
await this.createSubjectEvents(queryRunner);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async createSubjectEvents(queryRunner) {
|
|
39
|
+
await queryRunner.createTable(new typeorm_1.Table({
|
|
40
|
+
name: TABLE,
|
|
41
|
+
columns: [
|
|
42
|
+
{ name: 'id', type: 'varchar', length: '36', isPrimary: true },
|
|
43
|
+
{ name: 'domain_id', type: 'varchar', length: '36', isNullable: true },
|
|
44
|
+
{ name: 'instance_id', type: 'varchar', isNullable: false },
|
|
45
|
+
{ name: 'subject_kind', type: 'varchar', isNullable: false },
|
|
46
|
+
{ name: 'subject_id', type: 'varchar', isNullable: false },
|
|
47
|
+
/* 시각 컬럼의 타입 이름은 드라이버가 정한다 — `timestamp` 는 sqlite 가 모르고 `datetime` 은 Postgres 가 거절한다. */
|
|
48
|
+
{ name: 'event_time', type: queryRunner.connection.driver.mappedDataTypes.createDate, isNullable: false },
|
|
49
|
+
{ name: 'revision', type: 'int', isNullable: false },
|
|
50
|
+
{ name: 'event_type', type: 'varchar', isNullable: false },
|
|
51
|
+
{ name: 'event_id', type: 'varchar', length: '36', isNullable: true },
|
|
52
|
+
{
|
|
53
|
+
name: 'created_at',
|
|
54
|
+
type: queryRunner.connection.driver.mappedDataTypes.createDate,
|
|
55
|
+
isNullable: true
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
}), true);
|
|
59
|
+
const table = await queryRunner.getTable(TABLE);
|
|
60
|
+
if (!table)
|
|
61
|
+
return;
|
|
62
|
+
/* 이 표의 존재 이유 — 「이 대상의 것을 최신순으로」가 목차를 위에서 읽고 끝난다(정렬 없음). */
|
|
63
|
+
if (!table.indices.some(i => i.name === 'ix_twin_subject_event_0')) {
|
|
64
|
+
await queryRunner.createIndex(table, new typeorm_1.TableIndex({
|
|
65
|
+
name: 'ix_twin_subject_event_0',
|
|
66
|
+
columnNames: ['domain_id', 'instance_id', 'subject_kind', 'subject_id', 'event_time']
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
/* 보관 상한을 지우는 축 — 「이 트윈에서 오래된 줄」. */
|
|
70
|
+
if (!table.indices.some(i => i.name === 'ix_twin_subject_event_1')) {
|
|
71
|
+
await queryRunner.createIndex(table, new typeorm_1.TableIndex({
|
|
72
|
+
name: 'ix_twin_subject_event_1',
|
|
73
|
+
columnNames: ['domain_id', 'instance_id', 'event_time']
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async down(queryRunner) {
|
|
78
|
+
if (!(await queryRunner.hasTable(TABLE)))
|
|
79
|
+
return;
|
|
80
|
+
await queryRunner.dropTable(TABLE);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
exports.CreateTwinSubjectEvents1786500000000 = CreateTwinSubjectEvents1786500000000;
|
|
84
|
+
//# sourceMappingURL=1786500000000-CreateTwinSubjectEvents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"1786500000000-CreateTwinSubjectEvents.js","sourceRoot":"","sources":["../../server/migrations/1786500000000-CreateTwinSubjectEvents.ts"],"names":[],"mappings":"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","sourcesContent":["import { MigrationInterface, QueryRunner, Table, TableIndex } from 'typeorm'\n\n/**\n * **대상별 사건 목록** — 조회용 파생 표를 만든다.\n *\n * ── 왜 (2026-08-27 실측) ────────────────────────────────────────────────────\n * 360 화면이 「이 대상의 이력」을 묻고 그 조회가 64초다. 「무엇으로 걸러 시각순으로 정렬해 앞에서\n * 30개」라는 모양에 맞는 목차가 저널에 없고, 얹어도 풀리지 않는다.\n *\n * 여러 값으로 거르면(IN) 정렬 때문에 목차를 못 쓴다\n * 축이 늘 때마다 저널의 목차가 늘어난다 — 360 의 축 넷이면 13개가 된다\n * 품목과 로트가 한 문자열에 붙어 있어 품목만으로는 찾을 수 없다\n *\n * ── 비어서 시작한다 ────────────────────────────────────────────────────────\n * 이 표는 **앞으로만 채운다.** 기존 3,128만 건을 소급해서 채우지 않는다 — 그래야 새 조회 축이 필요할\n * 때마다 전체를 다시 만드는 구조가 되지 않는다. 담지 않은 구간은 조회가 `coveredFrom` 으로 말한다.\n *\n * 그래서 이 마이그레이션은 큰 기록에서도 값싸다(표와 목차를 만들 뿐, 채우지 않는다).\n *\n * 멱등이고 TypeORM API 만 쓴다(드라이버 다섯을 다 지나가야 하므로 raw SQL 금지). 컬럼 타입 이름을\n * 적지 않는 규율은 엔티티와 같다 — `datetime` 은 Postgres 가 거절한다.\n *\n * 설계: `operato-twin/design/plans/read-models.md`\n */\nconst TABLE = 'twin_subject_events'\n\nexport class CreateTwinSubjectEvents1786500000000 implements MigrationInterface {\n public async up(queryRunner: QueryRunner): Promise<void> {\n /*\n * 표마다 따로 본다 — 하나가 이미 있다고 여기서 돌아가면 나머지가 만들어지지 않는다. 실제로 그렇게\n * 적어 두었다가, 사건 목록만 있고 진행 지점 표가 없는 상태를 만들었다.\n */\n if (!(await queryRunner.hasTable(TABLE))) {\n await this.createSubjectEvents(queryRunner)\n }\n }\n\n private async createSubjectEvents(queryRunner: QueryRunner): Promise<void> {\n await queryRunner.createTable(\n new Table({\n name: TABLE,\n columns: [\n { name: 'id', type: 'varchar', length: '36', isPrimary: true },\n { name: 'domain_id', type: 'varchar', length: '36', isNullable: true },\n { name: 'instance_id', type: 'varchar', isNullable: false },\n { name: 'subject_kind', type: 'varchar', isNullable: false },\n { name: 'subject_id', type: 'varchar', isNullable: false },\n /* 시각 컬럼의 타입 이름은 드라이버가 정한다 — `timestamp` 는 sqlite 가 모르고 `datetime` 은 Postgres 가 거절한다. */\n { name: 'event_time', type: queryRunner.connection.driver.mappedDataTypes.createDate as string, isNullable: false },\n { name: 'revision', type: 'int', isNullable: false },\n { name: 'event_type', type: 'varchar', isNullable: false },\n { name: 'event_id', type: 'varchar', length: '36', isNullable: true },\n {\n name: 'created_at',\n type: queryRunner.connection.driver.mappedDataTypes.createDate as string,\n isNullable: true\n }\n ]\n }),\n true\n )\n\n const table = await queryRunner.getTable(TABLE)\n if (!table) return\n\n /* 이 표의 존재 이유 — 「이 대상의 것을 최신순으로」가 목차를 위에서 읽고 끝난다(정렬 없음). */\n if (!table.indices.some(i => i.name === 'ix_twin_subject_event_0')) {\n await queryRunner.createIndex(\n table,\n new TableIndex({\n name: 'ix_twin_subject_event_0',\n columnNames: ['domain_id', 'instance_id', 'subject_kind', 'subject_id', 'event_time']\n })\n )\n }\n /* 보관 상한을 지우는 축 — 「이 트윈에서 오래된 줄」. */\n if (!table.indices.some(i => i.name === 'ix_twin_subject_event_1')) {\n await queryRunner.createIndex(\n table,\n new TableIndex({\n name: 'ix_twin_subject_event_1',\n columnNames: ['domain_id', 'instance_id', 'event_time']\n })\n )\n }\n\n }\n\n public async down(queryRunner: QueryRunner): Promise<void> {\n if (!(await queryRunner.hasTable(TABLE))) return\n await queryRunner.dropTable(TABLE)\n }\n}\n"]}
|
|
@@ -8,9 +8,15 @@ exports.migrations = void 0;
|
|
|
8
8
|
const _1786000000000_RenameTwinInstanceBoardToModel_js_1 = require("./1786000000000-RenameTwinInstanceBoardToModel.js");
|
|
9
9
|
const _1786100000000_PromoteEventActionAndSyncWarnings_js_1 = require("./1786100000000-PromoteEventActionAndSyncWarnings.js");
|
|
10
10
|
const _1786200000000_CarryLiveFeedCursor_js_1 = require("./1786200000000-CarryLiveFeedCursor.js");
|
|
11
|
+
const _1786300000000_IndexEventTypeByTime_js_1 = require("./1786300000000-IndexEventTypeByTime.js");
|
|
12
|
+
const _1786400000000_IndexEventCreatedAt_js_1 = require("./1786400000000-IndexEventCreatedAt.js");
|
|
13
|
+
const _1786500000000_CreateTwinSubjectEvents_js_1 = require("./1786500000000-CreateTwinSubjectEvents.js");
|
|
11
14
|
exports.migrations = [
|
|
12
15
|
_1786000000000_RenameTwinInstanceBoardToModel_js_1.RenameTwinInstanceBoardToModel1786000000000,
|
|
13
16
|
_1786100000000_PromoteEventActionAndSyncWarnings_js_1.PromoteEventActionAndSyncWarnings1786100000000,
|
|
14
|
-
_1786200000000_CarryLiveFeedCursor_js_1.CarryLiveFeedCursor1786200000000
|
|
17
|
+
_1786200000000_CarryLiveFeedCursor_js_1.CarryLiveFeedCursor1786200000000,
|
|
18
|
+
_1786300000000_IndexEventTypeByTime_js_1.IndexEventTypeByTime1786300000000,
|
|
19
|
+
_1786400000000_IndexEventCreatedAt_js_1.IndexEventCreatedAt1786400000000,
|
|
20
|
+
_1786500000000_CreateTwinSubjectEvents_js_1.CreateTwinSubjectEvents1786500000000
|
|
15
21
|
];
|
|
16
22
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/migrations/index.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACH,wHAA+G;AAC/G,8HAAqH;AACrH,kGAAyF;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/migrations/index.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACH,wHAA+G;AAC/G,8HAAqH;AACrH,kGAAyF;AACzF,oGAA2F;AAC3F,kGAAyF;AACzF,0GAAiG;AAEpF,QAAA,UAAU,GAAG;IACxB,8FAA2C;IAC3C,oGAA8C;IAC9C,wEAAgC;IAChC,0EAAiC;IACjC,wEAAgC;IAChC,gFAAoC;CACrC,CAAA","sourcesContent":["/*\n * 마이그레이션 묶음 — 프레임워크 규약(`@things-factory/shell` · `auth-base` 와 같은 방식).\n * 파일마다 하나씩 export 하고 여기서 모아 `server/index.ts` 가 내보낸다.\n */\nimport { RenameTwinInstanceBoardToModel1786000000000 } from './1786000000000-RenameTwinInstanceBoardToModel.js'\nimport { PromoteEventActionAndSyncWarnings1786100000000 } from './1786100000000-PromoteEventActionAndSyncWarnings.js'\nimport { CarryLiveFeedCursor1786200000000 } from './1786200000000-CarryLiveFeedCursor.js'\nimport { IndexEventTypeByTime1786300000000 } from './1786300000000-IndexEventTypeByTime.js'\nimport { IndexEventCreatedAt1786400000000 } from './1786400000000-IndexEventCreatedAt.js'\nimport { CreateTwinSubjectEvents1786500000000 } from './1786500000000-CreateTwinSubjectEvents.js'\n\nexport const migrations = [\n RenameTwinInstanceBoardToModel1786000000000,\n PromoteEventActionAndSyncWarnings1786100000000,\n CarryLiveFeedCursor1786200000000,\n IndexEventTypeByTime1786300000000,\n IndexEventCreatedAt1786400000000,\n CreateTwinSubjectEvents1786500000000\n]\n"]}
|
package/dist-server/routes.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|