@things-factory/headless-twin 10.0.12 → 10.0.14
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/canonical-ingest.d.ts +57 -2
- package/dist-server/engine/canonical-ingest.js +43 -2
- 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/index.d.ts +3 -0
- package/dist-server/engine/index.js +4 -0
- package/dist-server/engine/index.js.map +1 -1
- package/dist-server/engine/ingest-health.d.ts +376 -0
- package/dist-server/engine/ingest-health.js +482 -0
- package/dist-server/engine/ingest-health.js.map +1 -0
- package/dist-server/engine/integration-coverage.d.ts +76 -0
- package/dist-server/engine/integration-coverage.js +73 -0
- package/dist-server/engine/integration-coverage.js.map +1 -0
- package/dist-server/engine/integration-probes.d.ts +65 -0
- package/dist-server/engine/integration-probes.js +100 -0
- package/dist-server/engine/integration-probes.js.map +1 -0
- package/dist-server/engine/integration-runner.d.ts +45 -0
- package/dist-server/engine/integration-runner.js +59 -0
- package/dist-server/engine/integration-runner.js.map +1 -0
- package/dist-server/engine/integration-target-profile.d.ts +57 -0
- package/dist-server/engine/integration-target-profile.js +79 -0
- package/dist-server/engine/integration-target-profile.js.map +1 -0
- package/dist-server/engine/kpi-fold.d.ts +27 -0
- package/dist-server/engine/kpi-fold.js +47 -4
- package/dist-server/engine/kpi-fold.js.map +1 -1
- package/dist-server/engine/kpi-query.js +128 -14
- package/dist-server/engine/kpi-query.js.map +1 -1
- package/dist-server/engine/live-feed-registry.js +2 -16
- 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 +12 -3
- package/dist-server/engine/load-meter.js.map +1 -1
- package/dist-server/engine/log.d.ts +18 -0
- package/dist-server/engine/log.js +80 -0
- package/dist-server/engine/log.js.map +1 -0
- package/dist-server/engine/loop-lag.d.ts +54 -0
- package/dist-server/engine/loop-lag.js +87 -0
- package/dist-server/engine/loop-lag.js.map +1 -0
- package/dist-server/engine/model-gap.d.ts +108 -0
- package/dist-server/engine/model-gap.js +95 -0
- package/dist-server/engine/model-gap.js.map +1 -0
- package/dist-server/engine/restart-policy.d.ts +3 -3
- package/dist-server/engine/restart-policy.js +4 -4
- 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/stage-path.d.ts +83 -0
- package/dist-server/engine/stage-path.js +118 -0
- package/dist-server/engine/stage-path.js.map +1 -0
- package/dist-server/engine/twin-engine.d.ts +386 -16
- package/dist-server/engine/twin-engine.js +1217 -172
- package/dist-server/engine/twin-engine.js.map +1 -1
- package/dist-server/index.js +34 -2
- package/dist-server/index.js.map +1 -1
- package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.d.ts +5 -0
- package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js +58 -0
- package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js.map +1 -0
- package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.d.ts +5 -0
- package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js +55 -0
- package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js.map +1 -0
- package/dist-server/migrations/index.js +7 -1
- package/dist-server/migrations/index.js.map +1 -1
- package/dist-server/service/index.d.ts +5 -2
- package/dist-server/service/index.js +18 -7
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/reference/discovery-result.d.ts +1 -1
- package/dist-server/service/reference/discovery-result.js +1 -1
- package/dist-server/service/reference/discovery-result.js.map +1 -1
- package/dist-server/service/reference/index.d.ts +4 -1
- package/dist-server/service/reference/index.js +6 -1
- package/dist-server/service/reference/index.js.map +1 -1
- package/dist-server/service/reference/reference-adapter.d.ts +210 -3
- package/dist-server/service/reference/reference-adapter.js +28 -4
- package/dist-server/service/reference/reference-adapter.js.map +1 -1
- package/dist-server/service/reference/reference-assessment.d.ts +78 -0
- package/dist-server/service/reference/reference-assessment.js +156 -0
- package/dist-server/service/reference/reference-assessment.js.map +1 -0
- package/dist-server/service/reference/reference-live.d.ts +12 -1
- package/dist-server/service/reference/reference-live.js +134 -11
- package/dist-server/service/reference/reference-live.js.map +1 -1
- package/dist-server/service/reference/reference-master.d.ts +59 -3
- package/dist-server/service/reference/reference-master.js +34 -4
- package/dist-server/service/reference/reference-master.js.map +1 -1
- package/dist-server/service/reference/reference-probe.d.ts +92 -0
- package/dist-server/service/reference/reference-probe.js +186 -0
- package/dist-server/service/reference/reference-probe.js.map +1 -0
- package/dist-server/service/reference/reference-progress-subscription.d.ts +17 -0
- package/dist-server/service/reference/reference-progress-subscription.js +94 -0
- package/dist-server/service/reference/reference-progress-subscription.js.map +1 -0
- package/dist-server/service/reference/reference-progress.d.ts +38 -0
- package/dist-server/service/reference/reference-progress.js +71 -0
- package/dist-server/service/reference/reference-progress.js.map +1 -0
- package/dist-server/service/reference/reference-resolver.js +210 -43
- package/dist-server/service/reference/reference-resolver.js.map +1 -1
- package/dist-server/service/reference/twin-reference.d.ts +32 -0
- package/dist-server/service/reference/twin-reference.js +10 -0
- package/dist-server/service/reference/twin-reference.js.map +1 -1
- package/dist-server/service/twin-control/twin-control-mutation.js +4 -3
- package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
- package/dist-server/service/twin-event/journal-count.d.ts +21 -0
- package/dist-server/service/twin-event/journal-count.js +26 -0
- package/dist-server/service/twin-event/journal-count.js.map +1 -0
- package/dist-server/service/twin-event/twin-event-keys.d.ts +9 -0
- package/dist-server/service/twin-event/twin-event-keys.js +15 -18
- package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
- package/dist-server/service/twin-event/twin-event-type.d.ts +3 -1
- package/dist-server/service/twin-event/twin-event-type.js +18 -1
- package/dist-server/service/twin-event/twin-event-type.js.map +1 -1
- package/dist-server/service/twin-event/twin-event.d.ts +1 -0
- package/dist-server/service/twin-event/twin-event.js +9 -1
- package/dist-server/service/twin-event/twin-event.js.map +1 -1
- package/dist-server/service/twin-forecast/twin-forecast-query.js +26 -2
- package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
- package/dist-server/service/twin-ingest-window/index.d.ts +5 -0
- package/dist-server/service/twin-ingest-window/index.js +10 -0
- package/dist-server/service/twin-ingest-window/index.js.map +1 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.d.ts +12 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js +123 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js.map +1 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.d.ts +1 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js +51 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js.map +1 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window.d.ts +16 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window.js +108 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window.js.map +1 -0
- package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
- package/dist-server/service/twin-journal/twin-journal-query.js +58 -5
- package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +14 -0
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +72 -2
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
- package/dist-server/service/twin-metrics/twin-metrics-query.d.ts +1 -0
- package/dist-server/service/twin-metrics/twin-metrics-query.js +78 -3
- package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
- package/dist-server/service/twin-model/axis-journal-evidence.d.ts +2 -0
- package/dist-server/service/twin-model/axis-journal-evidence.js +89 -0
- package/dist-server/service/twin-model/axis-journal-evidence.js.map +1 -0
- package/dist-server/service/twin-model/epcis-coverage.d.ts +1 -1
- package/dist-server/service/twin-model/epcis-coverage.js +46 -7
- package/dist-server/service/twin-model/epcis-coverage.js.map +1 -1
- package/dist-server/service/twin-model/iec61850-coverage.d.ts +1 -1
- package/dist-server/service/twin-model/iec61850-coverage.js +18 -7
- package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -1
- package/dist-server/service/twin-model/isa95-coverage.d.ts +14 -0
- package/dist-server/service/twin-model/isa95-coverage.js +108 -14
- package/dist-server/service/twin-model/isa95-coverage.js.map +1 -1
- package/dist-server/service/twin-model/item-ref.d.ts +24 -0
- package/dist-server/service/twin-model/item-ref.js +88 -0
- package/dist-server/service/twin-model/item-ref.js.map +1 -0
- package/dist-server/service/twin-model/name-index.d.ts +36 -0
- package/dist-server/service/twin-model/name-index.js +116 -0
- package/dist-server/service/twin-model/name-index.js.map +1 -0
- package/dist-server/service/twin-model/project-structure.js +1 -1
- package/dist-server/service/twin-model/project-structure.js.map +1 -1
- package/dist-server/service/twin-model/status-tally.d.ts +9 -0
- package/dist-server/service/twin-model/status-tally.js +37 -0
- package/dist-server/service/twin-model/status-tally.js.map +1 -0
- package/dist-server/service/twin-model/twin-lineage-query.js +40 -12
- package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-item-query.js +38 -39
- package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-query.js +156 -4
- package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-tree-query.js +7 -0
- package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -1
- package/dist-server/service/twin-readiness/index.d.ts +2 -0
- package/dist-server/service/twin-readiness/index.js +6 -0
- package/dist-server/service/twin-readiness/index.js.map +1 -0
- package/dist-server/service/twin-readiness/twin-readiness-query.d.ts +3 -0
- package/dist-server/service/twin-readiness/twin-readiness-query.js +103 -0
- package/dist-server/service/twin-readiness/twin-readiness-query.js.map +1 -0
- package/dist-server/service/twin-space/twin-space-resolver.d.ts +2 -2
- package/dist-server/service/twin-space/twin-space-resolver.js +5 -4
- package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
- package/dist-shared/entity-delta.d.ts +23 -3
- package/dist-shared/entity-delta.js +12 -8
- package/dist-shared/entity-delta.js.map +1 -1
- package/dist-shared/kpi-broadcast.js +1 -1
- package/dist-shared/kpi-broadcast.js.map +1 -1
- package/dist-shared/touched-items.d.ts +7 -0
- package/dist-shared/touched-items.js +80 -0
- package/dist-shared/touched-items.js.map +1 -0
- package/package.json +7 -7
- package/server/engine/canonical-ingest.ts +92 -7
- package/server/engine/command-routing.ts +1 -1
- package/server/engine/index.ts +4 -0
- package/server/engine/ingest-health.ts +782 -0
- package/server/engine/integration-coverage.ts +147 -0
- package/server/engine/integration-probes.ts +144 -0
- package/server/engine/integration-runner.ts +95 -0
- package/server/engine/integration-target-profile.ts +103 -0
- package/server/engine/kpi-fold.ts +72 -4
- package/server/engine/kpi-query.ts +129 -15
- package/server/engine/live-feed-registry.ts +2 -1
- package/server/engine/load-meter.ts +12 -3
- package/server/engine/log.ts +72 -0
- package/server/engine/loop-lag.ts +120 -0
- package/server/engine/model-gap.ts +168 -0
- package/server/engine/restart-policy.ts +4 -4
- package/server/engine/runtime-key.ts +1 -1
- package/server/engine/stage-path.ts +172 -0
- package/server/engine/twin-engine.ts +1334 -168
- package/server/index.ts +35 -2
- package/server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts +54 -0
- package/server/migrations/1786200000000-CarryLiveFeedCursor.ts +53 -0
- package/server/migrations/index.ts +7 -1
- package/server/service/index.ts +11 -0
- package/server/service/reference/discovery-result.ts +1 -1
- package/server/service/reference/index.ts +6 -1
- package/server/service/reference/reference-adapter.ts +220 -5
- package/server/service/reference/reference-assessment.ts +250 -0
- package/server/service/reference/reference-live.ts +143 -11
- package/server/service/reference/reference-master.ts +76 -6
- package/server/service/reference/reference-probe.ts +264 -0
- package/server/service/reference/reference-progress-subscription.ts +73 -0
- package/server/service/reference/reference-progress.ts +95 -0
- package/server/service/reference/reference-resolver.ts +200 -12
- package/server/service/reference/twin-reference.ts +39 -1
- package/server/service/twin-control/twin-control-mutation.ts +4 -3
- package/server/service/twin-event/journal-count.ts +53 -0
- package/server/service/twin-event/twin-event-keys.ts +16 -1
- package/server/service/twin-event/twin-event-type.ts +26 -2
- package/server/service/twin-event/twin-event.ts +7 -0
- package/server/service/twin-forecast/twin-forecast-query.ts +29 -2
- package/server/service/twin-ingest-window/index.ts +7 -0
- package/server/service/twin-ingest-window/twin-ingest-window-query.ts +125 -0
- package/server/service/twin-ingest-window/twin-ingest-window-writer.ts +57 -0
- package/server/service/twin-ingest-window/twin-ingest-window.ts +113 -0
- package/server/service/twin-instance/twin-instance.ts +1 -1
- package/server/service/twin-journal/twin-journal-query.ts +59 -5
- package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +77 -3
- package/server/service/twin-metrics/twin-metrics-query.ts +81 -3
- package/server/service/twin-model/axis-journal-evidence.ts +57 -0
- package/server/service/twin-model/epcis-coverage.ts +29 -8
- package/server/service/twin-model/iec61850-coverage.ts +2 -8
- package/server/service/twin-model/isa95-coverage.ts +107 -14
- package/server/service/twin-model/item-ref.ts +80 -0
- package/server/service/twin-model/name-index.ts +94 -0
- package/server/service/twin-model/project-structure.ts +1 -1
- package/server/service/twin-model/status-tally.ts +37 -0
- package/server/service/twin-model/twin-lineage-query.ts +38 -9
- package/server/service/twin-model/twin-model-item-query.ts +26 -27
- package/server/service/twin-model/twin-model-query.ts +155 -4
- package/server/service/twin-model/twin-model-tree-query.ts +7 -0
- package/server/service/twin-readiness/index.ts +3 -0
- package/server/service/twin-readiness/twin-readiness-query.ts +96 -0
- package/server/service/twin-space/twin-space-resolver.ts +5 -4
- package/shared/entity-delta.ts +31 -7
- package/shared/kpi-broadcast.ts +1 -1
- package/shared/touched-items.ts +73 -0
- package/test/axis-journal-evidence.test.ts +71 -0
- package/test/axis-read.test.ts +101 -1
- package/test/boot-resume.test.ts +92 -26
- package/test/broadcast-cost-baseline.test.ts +200 -0
- package/test/broadcast-period.test.ts +58 -0
- package/test/canonical-ingest-vocabularies.test.ts +36 -1
- package/test/canonical-quantity-door.test.ts +61 -0
- package/test/checkpoint-refuses-empty.test.ts +103 -0
- package/test/command-routing.test.ts +1 -1
- package/test/cursor-stall-not-read-failure.test.ts +126 -0
- package/test/declared-location-types.test.ts +89 -0
- package/test/discovery-result.test.ts +1 -1
- package/test/duration-estimators.test.ts +1 -1
- package/test/entity-delta.test.ts +2 -2
- package/test/ingest-bench.test.ts +3 -3
- package/test/ingest-health-engine.test.ts +248 -0
- package/test/ingest-health-wiring.test.ts +119 -0
- package/test/ingest-health.test.ts +306 -0
- package/test/ingest-history.test.ts +247 -0
- package/test/integration-probes.test.ts +103 -0
- package/test/integration-runner.test.ts +95 -0
- package/test/item-ref.test.ts +78 -0
- package/test/journal-read-discipline.test.ts +177 -0
- package/test/journal-retention.test.ts +133 -0
- package/test/journal-sort-axis.test.ts +142 -0
- package/test/journal-write-door.test.ts +110 -0
- package/test/journal-write-trend.test.ts +115 -0
- package/test/kernel-kind-guard.test.ts +4 -4
- package/test/kpi-fold.test.ts +90 -3
- package/test/kpi-query-bench.test.ts +2 -2
- package/test/lineage-survives-restart.test.ts +17 -1
- package/test/live-cursor-wiring.test.ts +87 -0
- package/test/live-feed-registry.test.ts +9 -1
- package/test/live-kernel-facts.test.ts +7 -7
- package/test/live-mirror-parity.test.ts +6 -0
- package/test/load-meter.test.ts +29 -15
- package/test/local-declarations.test.ts +1 -1
- package/test/log-stamp.test.ts +59 -0
- package/test/loop-lag.test.ts +82 -0
- package/test/mirror-resumes-from-checkpoint.test.ts +192 -0
- package/test/model-gap.test.ts +153 -0
- package/test/oee-accumulator.test.ts +4 -0
- package/test/operational-vocabulary.test.ts +4 -4
- package/test/read-failure-visible.test.ts +145 -0
- package/test/reference-grounding.test.ts +70 -0
- package/test/resolve-ts-siblings.mjs +52 -0
- package/test/restart-policy.test.ts +7 -7
- package/test/revision-axis.test.ts +104 -0
- package/test/runtime-key.test.ts +2 -2
- package/test/scale-twin-bench.test.ts +2 -2
- package/test/spec-coverage.test.ts +1 -1
- package/test/stage-path.test.ts +95 -0
- package/test/standard-coverage.test.ts +15 -3
- package/test/status-tally.test.ts +55 -0
- package/test/structure-revision-db.test.ts +4 -0
- package/test/tenant-registry-db.test.ts +1 -1
- package/test/time-range.test.ts +152 -0
- package/test/touched-items.test.ts +61 -0
- package/test/twin-event-keys.test.ts +10 -2
- package/test/twin-model-item-db.test.ts +2 -0
- package/test/warm-start-seam.test.ts +4 -0
- package/test/yield-loop.test.ts +57 -2
- package/tsconfig.shared.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ObjectLiteral, Repository } from 'typeorm';
|
|
2
|
+
/** 표시용 세기의 기본 상한 — 화면이 한 번에 보여 줄 범위를 훨씬 넘는 값. */
|
|
3
|
+
export declare const JOURNAL_COUNT_CAP = 10000;
|
|
4
|
+
export interface CappedCount {
|
|
5
|
+
/** 센 수. `capped` 면 상한까지만 센 값이므로 **전체가 아니다**. */
|
|
6
|
+
count: number;
|
|
7
|
+
/** 상한을 넘었나 — 그때 전체 수는 모르는 것이 사실이다. */
|
|
8
|
+
capped: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* 상한까지만 세기 — 키 컬럼만 읽으므로 행 본문을 만들지 않는다.
|
|
12
|
+
*
|
|
13
|
+
* `count()` 를 쓰지 않는 이유: 그것은 상한을 받지 않는다(조건에 맞는 전부를 센다).
|
|
14
|
+
*/
|
|
15
|
+
export declare function countUpTo<T extends ObjectLiteral>(repo: Repository<T>, where: any, cap?: number): Promise<CappedCount>;
|
|
16
|
+
/**
|
|
17
|
+
* 한 건이라도 있나 — **판정에는 수가 필요 없다.**
|
|
18
|
+
*
|
|
19
|
+
* 예전에는 전부 센 뒤 `> 0` 을 봤다. 그 답을 얻는 데 358만 행을 세고 있었다.
|
|
20
|
+
*/
|
|
21
|
+
export declare function anyJournalRow<T extends ObjectLiteral>(repo: Repository<T>, where: any): Promise<boolean>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.JOURNAL_COUNT_CAP = void 0;
|
|
4
|
+
exports.countUpTo = countUpTo;
|
|
5
|
+
exports.anyJournalRow = anyJournalRow;
|
|
6
|
+
/** 표시용 세기의 기본 상한 — 화면이 한 번에 보여 줄 범위를 훨씬 넘는 값. */
|
|
7
|
+
exports.JOURNAL_COUNT_CAP = 10_000;
|
|
8
|
+
/**
|
|
9
|
+
* 상한까지만 세기 — 키 컬럼만 읽으므로 행 본문을 만들지 않는다.
|
|
10
|
+
*
|
|
11
|
+
* `count()` 를 쓰지 않는 이유: 그것은 상한을 받지 않는다(조건에 맞는 전부를 센다).
|
|
12
|
+
*/
|
|
13
|
+
async function countUpTo(repo, where, cap = exports.JOURNAL_COUNT_CAP) {
|
|
14
|
+
const rows = await repo.find({ where, select: ['id'], take: cap + 1 });
|
|
15
|
+
return rows.length > cap ? { count: cap, capped: true } : { count: rows.length, capped: false };
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 한 건이라도 있나 — **판정에는 수가 필요 없다.**
|
|
19
|
+
*
|
|
20
|
+
* 예전에는 전부 센 뒤 `> 0` 을 봤다. 그 답을 얻는 데 358만 행을 세고 있었다.
|
|
21
|
+
*/
|
|
22
|
+
async function anyJournalRow(repo, where) {
|
|
23
|
+
const rows = await repo.find({ where, select: ['id'], take: 1 });
|
|
24
|
+
return rows.length > 0;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=journal-count.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journal-count.js","sourceRoot":"","sources":["../../../server/service/twin-event/journal-count.ts"],"names":[],"mappings":";;;AAmCA,8BAOC;AAOD,sCAGC;AAhCD,iDAAiD;AACpC,QAAA,iBAAiB,GAAG,MAAM,CAAA;AASvC;;;;GAIG;AACI,KAAK,UAAU,SAAS,CAC7B,IAAmB,EACnB,KAAU,EACV,GAAG,GAAG,yBAAiB;IAEvB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,IAAI,CAAQ,EAAE,IAAI,EAAE,GAAG,GAAG,CAAC,EAAE,CAAC,CAAA;IAC7E,OAAO,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;AACjG,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,aAAa,CAA0B,IAAmB,EAAE,KAAU;IAC1F,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,IAAI,CAAQ,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAA;IACvE,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAA;AACxB,CAAC","sourcesContent":["/*\n * 저널을 **상한까지만 센다** — 세는 것도 읽는 것이다 (2026-08-22).\n *\n * ── 왜 (실측) ───────────────────────────────────────────────────────────────\n * 조건에 맞는 행을 전부 세는 질의는 행을 실어 오지 않아도 비싸다. 개발 저널에서 트윈 하나가 358만\n * 행이었고 그 한 번이 **5.32초**였다. 색인만 상한까지 읽으면 같은 조건에서 **0.04초**다(130배).\n *\n * 그 5초 동안 이벤트 루프는 다른 요청을 처리하지 못한다 — 이력 화면 한 번, 준비도 화면 한 번이\n * 사용자에게는 「눌렀는데 아무 반응이 없다」로 나타난다. 준비도 화면은 그런 세기를 **넷** 했다.\n *\n * ── 규율 ────────────────────────────────────────────────────────────────────\n * 세는 목적이 둘 중 하나다.\n * ① **판정** — 「한 건이라도 있나」. 그때는 하나만 찾으면 끝이다(`anyJournalRow`).\n * ② **표시** — 사람에게 수를 보인다. 그때는 상한까지 세고, 넘으면 **모른다고 말한다**(`countUpTo`).\n *\n * 상한을 넘었을 때 상한값을 전체로 적으면 화면이 거짓을 말한다. 그래서 `capped` 를 함께 낸다 —\n * 없는 것과 모르는 것을 가르는 이 프로젝트의 규율이 세기에도 같이 적용된다.\n */\nimport type { ObjectLiteral, Repository } from 'typeorm'\n\n/** 표시용 세기의 기본 상한 — 화면이 한 번에 보여 줄 범위를 훨씬 넘는 값. */\nexport const JOURNAL_COUNT_CAP = 10_000\n\nexport interface CappedCount {\n /** 센 수. `capped` 면 상한까지만 센 값이므로 **전체가 아니다**. */\n count: number\n /** 상한을 넘었나 — 그때 전체 수는 모르는 것이 사실이다. */\n capped: boolean\n}\n\n/**\n * 상한까지만 세기 — 키 컬럼만 읽으므로 행 본문을 만들지 않는다.\n *\n * `count()` 를 쓰지 않는 이유: 그것은 상한을 받지 않는다(조건에 맞는 전부를 센다).\n */\nexport async function countUpTo<T extends ObjectLiteral>(\n repo: Repository<T>,\n where: any,\n cap = JOURNAL_COUNT_CAP\n): Promise<CappedCount> {\n const rows = await repo.find({ where, select: ['id'] as any, take: cap + 1 })\n return rows.length > cap ? { count: cap, capped: true } : { count: rows.length, capped: false }\n}\n\n/**\n * 한 건이라도 있나 — **판정에는 수가 필요 없다.**\n *\n * 예전에는 전부 센 뒤 `> 0` 을 봤다. 그 답을 얻는 데 358만 행을 세고 있었다.\n */\nexport async function anyJournalRow<T extends ObjectLiteral>(repo: Repository<T>, where: any): Promise<boolean> {\n const rows = await repo.find({ where, select: ['id'] as any, take: 1 })\n return rows.length > 0\n}\n"]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** 승격된 검색 키 — 전부 선택적. 뽑히지 않으면 **빈 문자열이 아니라 undefined**(결측≠빈값). */
|
|
2
2
|
export interface TwinEventKeys {
|
|
3
|
+
action?: string;
|
|
3
4
|
bizStep?: string;
|
|
4
5
|
epc?: string;
|
|
5
6
|
orderId?: string;
|
|
@@ -31,5 +32,13 @@ export declare function locationOf(envelope: any): string | undefined;
|
|
|
31
32
|
* 서버에서 물을 방법이 없어, 화면이 저널을 통째로 받아 전수 조회하는 수밖에 없다.
|
|
32
33
|
*/
|
|
33
34
|
export declare function equipmentIdOf(envelope: any): string | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* EPCIS 행위 — `ADD`·`OBSERVE`·`DELETE`. 세 값이 아니면 `undefined`(원천의 잡값을 색인에 넣지 않는다).
|
|
37
|
+
*
|
|
38
|
+
* 이 키는 검색 축이 아니라 **판별식**이다: 되풀어 읽는 스냅샷은 원리적으로 `DELETE` 를 못 낸다.
|
|
39
|
+
* 그래서 「이 트윈이 반출을 한 번이라도 접었는가」가 진짜 delta 피드의 증거인데, payload 안에 있는
|
|
40
|
+
* 동안에는 5개 드라이버 어디에서도 그 질문을 할 수 없었다. 운영 델타에는 행위가 없다 — 비운다.
|
|
41
|
+
*/
|
|
42
|
+
export declare function actionOf(envelope: any): string | undefined;
|
|
34
43
|
/** 한 이벤트에서 승격 키 전부 — 기록 경로가 이 함수 하나만 부른다. */
|
|
35
44
|
export declare function twinEventKeys(envelope: any): TwinEventKeys;
|
|
@@ -1,28 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/*
|
|
3
|
-
* 저널 검색 키 추출 — **순수**. 인제스트가 기록할 때 한 번 뽑아 인덱스 가능한 실컬럼으로 승격한다.
|
|
4
|
-
*
|
|
5
|
-
* ── 왜 승격하는가 ───────────────────────────────────────────────────────────
|
|
6
|
-
* 사용자가 저널에서 실제로 찾는 것은 "이 팔레트의 이력", "이 오더가 어디까지 갔나", "이 도크에서
|
|
7
|
-
* 무슨 일이 있었나" 다. 그런데 그 값들은 전부 `payload`(simple-json = TEXT) **안**에 있었다.
|
|
8
|
-
* things-factory 는 5개 DB 드라이버를 지원해야 해서 DB별 JSON 연산자를 쓸 수 없다 —
|
|
9
|
-
* 즉 승격 없이는 **어떤 방법으로도 서버에서 그 조건으로 거를 수 없었다**. 클라이언트가 받아온
|
|
10
|
-
* 몇 천 건 안에서만 찾는 시늉이 최선이었고, 저널이 커질수록 그 시늉은 거짓말에 가까워진다.
|
|
11
|
-
*
|
|
12
|
-
* 그래서 검색 축이 되는 값만 골라 컬럼으로 꺼낸다. payload 는 그대로 둔다(정본은 여전히 payload —
|
|
13
|
-
* 이건 파생 색인이지 새로운 진실이 아니다).
|
|
14
|
-
*
|
|
15
|
-
* ── 왜 여기(순수 모듈)인가 ──────────────────────────────────────────────────
|
|
16
|
-
* 기록 경로가 둘이다(`persistBatch` 라이브 벌크 · `persist` 심 단건). 두 곳에 각자 적으면
|
|
17
|
-
* 반드시 어긋나고, 어긋난 색인은 "없는 것처럼 보이는 이벤트" 를 만든다 — 저널에서 가장 나쁜 결함이다.
|
|
18
|
-
*/
|
|
19
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
3
|
exports.bizStepOf = bizStepOf;
|
|
21
4
|
exports.epcOf = epcOf;
|
|
22
5
|
exports.orderOf = orderOf;
|
|
23
6
|
exports.locationOf = locationOf;
|
|
24
7
|
exports.equipmentIdOf = equipmentIdOf;
|
|
8
|
+
exports.actionOf = actionOf;
|
|
25
9
|
exports.twinEventKeys = twinEventKeys;
|
|
10
|
+
const log_js_1 = require("../../engine/log.js");
|
|
26
11
|
/*
|
|
27
12
|
* 컬럼 길이 상한. GS1 식별자(EPC URN·GDTI·SGLN)는 규격상 이보다 훨씬 짧다.
|
|
28
13
|
* 넘치는 값이 오면 **조용히 자르지 않고** 경고를 남긴다 — 색인이 원본과 다르면 검색 결과가 거짓이 되는데,
|
|
@@ -37,7 +22,7 @@ function clip(v, field) {
|
|
|
37
22
|
return undefined;
|
|
38
23
|
if (s.length <= MAX_KEY)
|
|
39
24
|
return s;
|
|
40
|
-
|
|
25
|
+
(0, log_js_1.twinWarn)(`[twin-event-keys] ${field} exceeds ${MAX_KEY} chars and was clipped for indexing — ` +
|
|
41
26
|
`search on this value may be incomplete. payload keeps the full value. (${s.slice(0, 60)}…)`);
|
|
42
27
|
return s.slice(0, MAX_KEY);
|
|
43
28
|
}
|
|
@@ -82,9 +67,21 @@ function equipmentIdOf(envelope) {
|
|
|
82
67
|
const d = envelope?.data ?? envelope ?? {};
|
|
83
68
|
return d.moverId ?? undefined;
|
|
84
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* EPCIS 행위 — `ADD`·`OBSERVE`·`DELETE`. 세 값이 아니면 `undefined`(원천의 잡값을 색인에 넣지 않는다).
|
|
72
|
+
*
|
|
73
|
+
* 이 키는 검색 축이 아니라 **판별식**이다: 되풀어 읽는 스냅샷은 원리적으로 `DELETE` 를 못 낸다.
|
|
74
|
+
* 그래서 「이 트윈이 반출을 한 번이라도 접었는가」가 진짜 delta 피드의 증거인데, payload 안에 있는
|
|
75
|
+
* 동안에는 5개 드라이버 어디에서도 그 질문을 할 수 없었다. 운영 델타에는 행위가 없다 — 비운다.
|
|
76
|
+
*/
|
|
77
|
+
function actionOf(envelope) {
|
|
78
|
+
const a = (envelope?.data ?? envelope ?? {}).action;
|
|
79
|
+
return a === 'ADD' || a === 'OBSERVE' || a === 'DELETE' ? a : undefined;
|
|
80
|
+
}
|
|
85
81
|
/** 한 이벤트에서 승격 키 전부 — 기록 경로가 이 함수 하나만 부른다. */
|
|
86
82
|
function twinEventKeys(envelope) {
|
|
87
83
|
return {
|
|
84
|
+
action: actionOf(envelope),
|
|
88
85
|
bizStep: clip(bizStepOf(envelope), 'bizStep'),
|
|
89
86
|
epc: clip(epcOf(envelope), 'epc'),
|
|
90
87
|
orderId: clip(orderOf(envelope), 'orderId'),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"twin-event-keys.js","sourceRoot":"","sources":["../../../server/service/twin-event/twin-event-keys.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"twin-event-keys.js","sourceRoot":"","sources":["../../../server/service/twin-event/twin-event-keys.ts"],"names":[],"mappings":"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","sourcesContent":["import { twinWarn } from '../../engine/log.js'\n/*\n * 저널 검색 키 추출 — **순수**. 인제스트가 기록할 때 한 번 뽑아 인덱스 가능한 실컬럼으로 승격한다.\n *\n * ── 왜 승격하는가 ───────────────────────────────────────────────────────────\n * 사용자가 저널에서 실제로 찾는 것은 \"이 팔레트의 이력\", \"이 오더가 어디까지 갔나\", \"이 도크에서\n * 무슨 일이 있었나\" 다. 그런데 그 값들은 전부 `payload`(simple-json = TEXT) **안**에 있었다.\n * things-factory 는 5개 DB 드라이버를 지원해야 해서 DB별 JSON 연산자를 쓸 수 없다 —\n * 즉 승격 없이는 **어떤 방법으로도 서버에서 그 조건으로 거를 수 없었다**. 클라이언트가 받아온\n * 몇 천 건 안에서만 찾는 시늉이 최선이었고, 저널이 커질수록 그 시늉은 거짓말에 가까워진다.\n *\n * 그래서 검색 축이 되는 값만 골라 컬럼으로 꺼낸다. payload 는 그대로 둔다(정본은 여전히 payload —\n * 이건 파생 색인이지 새로운 진실이 아니다).\n *\n * ── 왜 여기(순수 모듈)인가 ──────────────────────────────────────────────────\n * 기록 경로가 둘이다(`persistBatch` 라이브 벌크 · `persist` 심 단건). 두 곳에 각자 적으면\n * 반드시 어긋나고, 어긋난 색인은 \"없는 것처럼 보이는 이벤트\" 를 만든다 — 저널에서 가장 나쁜 결함이다.\n */\n\n/** 승격된 검색 키 — 전부 선택적. 뽑히지 않으면 **빈 문자열이 아니라 undefined**(결측≠빈값). */\nexport interface TwinEventKeys {\n action?: string\n bizStep?: string\n epc?: string\n orderId?: string\n locationId?: string\n moverId?: string\n}\n\n/*\n * 컬럼 길이 상한. GS1 식별자(EPC URN·GDTI·SGLN)는 규격상 이보다 훨씬 짧다.\n * 넘치는 값이 오면 **조용히 자르지 않고** 경고를 남긴다 — 색인이 원본과 다르면 검색 결과가 거짓이 되는데,\n * 그 사실이 어디에도 안 남으면 아무도 모른다.\n */\nconst MAX_KEY = 255\n\nfunction clip(v: unknown, field: string): string | undefined {\n if (v === undefined || v === null) return undefined\n const s = String(v)\n if (!s) return undefined\n if (s.length <= MAX_KEY) return s\n twinWarn(\n `[twin-event-keys] ${field} exceeds ${MAX_KEY} chars and was clipped for indexing — ` +\n `search on this value may be incomplete. payload keeps the full value. (${s.slice(0, 60)}…)`\n )\n return s.slice(0, MAX_KEY)\n}\n\n/** CBV bizStep URN 의 끝마디. 없으면 이벤트 타입에서 유추(`epcis.` 접두 제거). */\nexport function bizStepOf(envelope: any): string | undefined {\n const d = envelope?.data ?? envelope ?? {}\n const tail = String(d.bizStep ?? '').split(':').pop()\n return tail || String(envelope?.eventType ?? '').replace('epcis.', '') || undefined\n}\n\n/**\n * 품목 식별자 — **전체 값**을 저장한다(끝마디만 저장하지 않는다).\n *\n * 표시용 축약은 화면이 하고, 색인은 원본을 갖는다. `search` 는 부분일치(contains)라\n * 전체를 저장해 두면 끝마디(\"402.2\")로도 URN 전체로도 찾힌다. 반대로 끝마디만 저장하면\n * URN 으로 찾는 경로가 사라진다.\n */\nexport function epcOf(envelope: any): string | undefined {\n const d = envelope?.data ?? envelope ?? {}\n return d.epcList?.[0] ?? d.parentID ?? d.quantityList?.[0]?.epcClass ?? undefined\n}\n\n/** 거래 식별자(PO/SO) — EPCIS bizTransactionList 우선, 운영 델타는 `order`. */\nexport function orderOf(envelope: any): string | undefined {\n const d = envelope?.data ?? envelope ?? {}\n return d.bizTransactionList?.[0]?.bizTransaction ?? d.order ?? undefined\n}\n\n/**\n * 위치 — EPCIS 는 읽은 지점(readPoint) 우선, 없으면 업무 위치(bizLocation).\n * 운영 델타(무버 이동 등)는 그 둘이 없고 평범한 `location` 을 쓴다 — 빠뜨리면 설비가 어디서\n * 무엇을 했는지가 위치 축에서 통째로 사라진다.\n */\nexport function locationOf(envelope: any): string | undefined {\n const d = envelope?.data ?? envelope ?? {}\n return d.readPoint?.id ?? d.bizLocation?.id ?? d.location ?? undefined\n}\n\n/**\n * 설비·무버 — 운영 델타(equipment.status·task.status)가 대상을 가리키는 축.\n *\n * EPCIS 어휘가 아니라서 다른 축 어디에도 안 잡힌다. 이게 없으면 \"이 지게차가 오늘 무엇을 했나\" 를\n * 서버에서 물을 방법이 없어, 화면이 저널을 통째로 받아 전수 조회하는 수밖에 없다.\n */\nexport function equipmentIdOf(envelope: any): string | undefined {\n const d = envelope?.data ?? envelope ?? {}\n return d.moverId ?? undefined\n}\n\n/**\n * EPCIS 행위 — `ADD`·`OBSERVE`·`DELETE`. 세 값이 아니면 `undefined`(원천의 잡값을 색인에 넣지 않는다).\n *\n * 이 키는 검색 축이 아니라 **판별식**이다: 되풀어 읽는 스냅샷은 원리적으로 `DELETE` 를 못 낸다.\n * 그래서 「이 트윈이 반출을 한 번이라도 접었는가」가 진짜 delta 피드의 증거인데, payload 안에 있는\n * 동안에는 5개 드라이버 어디에서도 그 질문을 할 수 없었다. 운영 델타에는 행위가 없다 — 비운다.\n */\nexport function actionOf(envelope: any): string | undefined {\n const a = (envelope?.data ?? envelope ?? {}).action\n return a === 'ADD' || a === 'OBSERVE' || a === 'DELETE' ? a : undefined\n}\n\n/** 한 이벤트에서 승격 키 전부 — 기록 경로가 이 함수 하나만 부른다. */\nexport function twinEventKeys(envelope: any): TwinEventKeys {\n return {\n action: actionOf(envelope),\n bizStep: clip(bizStepOf(envelope), 'bizStep'),\n epc: clip(epcOf(envelope), 'epc'),\n orderId: clip(orderOf(envelope), 'orderId'),\n locationId: clip(locationOf(envelope), 'locationId'),\n moverId: clip(equipmentIdOf(envelope), 'moverId')\n }\n}\n"]}
|
|
@@ -19,9 +19,26 @@ tslib_1.__decorate([
|
|
|
19
19
|
tslib_1.__metadata("design:type", Array)
|
|
20
20
|
], TwinEventList.prototype, "items", void 0);
|
|
21
21
|
tslib_1.__decorate([
|
|
22
|
-
(0, type_graphql_1.Field)(type => type_graphql_1.Int, {
|
|
22
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, {
|
|
23
|
+
nullable: true,
|
|
24
|
+
description: 'Total number of events matching the filters, ignoring pagination. Lets the caller show an honest "shown of total" count instead of silently truncating. NULL when the server stopped counting at countCap: the exact total is then unknown, and capped says so — do not render the cap as if it were the total.'
|
|
25
|
+
}),
|
|
23
26
|
tslib_1.__metadata("design:type", Number)
|
|
24
27
|
], TwinEventList.prototype, "total", void 0);
|
|
28
|
+
tslib_1.__decorate([
|
|
29
|
+
(0, type_graphql_1.Field)({
|
|
30
|
+
nullable: true,
|
|
31
|
+
description: 'True when there are more matching events than countCap, so total could not be established. The caller should say "first N shown" and offer narrowing rather than implying it received everything.'
|
|
32
|
+
}),
|
|
33
|
+
tslib_1.__metadata("design:type", Boolean)
|
|
34
|
+
], TwinEventList.prototype, "capped", void 0);
|
|
35
|
+
tslib_1.__decorate([
|
|
36
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, {
|
|
37
|
+
nullable: true,
|
|
38
|
+
description: 'The counting limit the server applied. Reported so the screen can say how far the count is trustworthy instead of guessing.'
|
|
39
|
+
}),
|
|
40
|
+
tslib_1.__metadata("design:type", Number)
|
|
41
|
+
], TwinEventList.prototype, "countCap", void 0);
|
|
25
42
|
tslib_1.__decorate([
|
|
26
43
|
(0, type_graphql_1.Field)({ nullable: true, description: 'Opaque cursor for the next page. Pass it back as pagination.after to continue exactly where this page ended, without the duplicate or skipped rows that offset paging produces on a journal that keeps growing at the head. Null when this page is the last one.' }),
|
|
27
44
|
tslib_1.__metadata("design:type", String)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"twin-event-type.js","sourceRoot":"","sources":["../../../server/service/twin-event/twin-event-type.ts"],"names":[],"mappings":";;;;AAAA,+CAAqD;AAErD,mDAA2C;AAE3C;;;;;;GAMG;AAEI,IAAM,aAAa,GAAnB,MAAM,aAAa;
|
|
1
|
+
{"version":3,"file":"twin-event-type.js","sourceRoot":"","sources":["../../../server/service/twin-event/twin-event-type.ts"],"names":[],"mappings":";;;;AAAA,+CAAqD;AAErD,mDAA2C;AAE3C;;;;;;GAMG;AAEI,IAAM,aAAa,GAAnB,MAAM,aAAa;CAsCzB,CAAA;AAtCY,sCAAa;AAExB;IADC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,CAAC,yBAAS,CAAC,EAAE,EAAE,WAAW,EAAE,6FAA6F,EAAE,CAAC;;4CACzH;AAclB;IALC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,kBAAG,EAAE;QAClB,QAAQ,EAAE,IAAI;QACd,WAAW,EACT,iTAAiT;KACpT,CAAC;;4CACY;AAOd;IALC,IAAA,oBAAK,EAAC;QACL,QAAQ,EAAE,IAAI;QACd,WAAW,EACT,mMAAmM;KACtM,CAAC;;6CACc;AAMhB;IAJC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,kBAAG,EAAE;QAClB,QAAQ,EAAE,IAAI;QACd,WAAW,EAAE,6HAA6H;KAC3I,CAAC;;+CACe;AAQjB;IADC,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,kQAAkQ,EAAE,CAAC;;iDACxR;wBArCR,aAAa;IADzB,IAAA,yBAAU,EAAC,EAAE,WAAW,EAAE,mFAAmF,EAAE,CAAC;GACpG,aAAa,CAsCzB","sourcesContent":["import { Field, Int, ObjectType } from 'type-graphql'\n\nimport { TwinEvent } from './twin-event.js'\n\n/*\n * 저널 목록 반환형 — things-factory 표준 `{ items, total }`(AttributeSetList·DomainList 등과 동형).\n *\n * `total` 이 이 타입의 존재 이유다. 기존 `twinEvents` 는 배열만 돌려줘서 **화면이 자기가 전체를 받은\n * 건지 잘린 건지 알 방법이 없었다** — 그래서 리스트가 조용히 잘린 채로 \"이게 전부\" 처럼 보였다.\n * 총건수를 함께 주면 화면은 \"48 / 12,904\" 라고 정직하게 말할 수 있고, 사용자는 좁혀야 한다는 걸 안다.\n */\n@ObjectType({ description: 'A page of twin journal events together with the total number of matching records.' })\nexport class TwinEventList {\n @Field(type => [TwinEvent], { description: 'The events on this page, ordered by the requested sorting (revision descending by default).' })\n items: TwinEvent[]\n\n /*\n * 전체 수 — **상한을 넘으면 비운다.**\n *\n * 세는 데도 값이 든다(2026-08-22 실측: 저널 358만 행인 트윈에서 전량 세기 5.32초 · 상한 1만까지\n * 0.04초). 그래서 상한까지만 세고, 넘으면 「모른다」로 둔다. 그때 상한값을 전체로 적으면 화면이\n * 거짓을 말한다 — 없는 것과 모르는 것을 가르는 것이 이 필드의 규율이다.\n */\n @Field(type => Int, {\n nullable: true,\n description:\n 'Total number of events matching the filters, ignoring pagination. Lets the caller show an honest \"shown of total\" count instead of silently truncating. NULL when the server stopped counting at countCap: the exact total is then unknown, and capped says so — do not render the cap as if it were the total.'\n })\n total?: number\n\n @Field({\n nullable: true,\n description:\n 'True when there are more matching events than countCap, so total could not be established. The caller should say \"first N shown\" and offer narrowing rather than implying it received everything.'\n })\n capped?: boolean\n\n @Field(type => Int, {\n nullable: true,\n description: 'The counting limit the server applied. Reported so the screen can say how far the count is trustworthy instead of guessing.'\n })\n countCap?: number\n\n /*\n * 다음 페이지 커서. 저널은 **머리에 계속 쌓이는 목록**이라 offset 으로 뒤를 읽으면\n * 읽는 사이 들어온 이벤트만큼 밀려 본 행이 또 나오거나 못 본 행이 사라진다.\n * 이 값을 그대로 다음 요청의 `pagination.after` 로 돌려주면 그 문제가 없다.\n */\n @Field({ nullable: true, description: 'Opaque cursor for the next page. Pass it back as pagination.after to continue exactly where this page ended, without the duplicate or skipped rows that offset paging produces on a journal that keeps growing at the head. Null when this page is the last one.' })\n nextCursor?: string\n}\n"]}
|
|
@@ -104,6 +104,11 @@ tslib_1.__decorate([
|
|
|
104
104
|
(0, type_graphql_1.Field)({ nullable: true, description: 'Id of the command that caused this fact (kernel correlation id; defaults to the commandId). Empty for facts that simulation time produced rather than a command.' }),
|
|
105
105
|
tslib_1.__metadata("design:type", String)
|
|
106
106
|
], TwinEvent.prototype, "correlationId", void 0);
|
|
107
|
+
tslib_1.__decorate([
|
|
108
|
+
(0, typeorm_1.Column)({ length: 16, nullable: true }),
|
|
109
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'EPCIS action (ADD / OBSERVE / DELETE), promoted from the payload. Empty for operational deltas, and for rows journaled before this column existed.' }),
|
|
110
|
+
tslib_1.__metadata("design:type", String)
|
|
111
|
+
], TwinEvent.prototype, "action", void 0);
|
|
107
112
|
tslib_1.__decorate([
|
|
108
113
|
(0, typeorm_1.Column)({ length: 255, nullable: true }),
|
|
109
114
|
(0, type_graphql_1.Field)({ nullable: true, description: 'Business step (CBV bizStep tail), promoted from the payload for indexed filtering.' }),
|
|
@@ -148,7 +153,10 @@ exports.TwinEvent = TwinEvent = tslib_1.__decorate([
|
|
|
148
153
|
(0, typeorm_1.Index)('ix_twin_event_4', (e) => [e.domain, e.instanceId, e.orderId], { unique: false })
|
|
149
154
|
/* ix_5 — "이 지시가 무엇을 일으켰나". 감사 기록의 commandId 로 저널을 다시 계산하는 축. */
|
|
150
155
|
,
|
|
151
|
-
(0, typeorm_1.Index)('ix_twin_event_5', (e) => [e.domain, e.correlationId], { unique: false })
|
|
156
|
+
(0, typeorm_1.Index)('ix_twin_event_5', (e) => [e.domain, e.correlationId], { unique: false })
|
|
157
|
+
/* ix_6 — "반출을 한 번이라도 접었나". 없으면 그 질문이 인스턴스 저널 전량 주사가 된다. */
|
|
158
|
+
,
|
|
159
|
+
(0, typeorm_1.Index)('ix_twin_event_6', (e) => [e.domain, e.instanceId, e.action], { unique: false }),
|
|
152
160
|
(0, type_graphql_1.ObjectType)({ description: 'Append-only twin event journal record (EPCIS event or operational delta).' })
|
|
153
161
|
], TwinEvent);
|
|
154
162
|
//# sourceMappingURL=twin-event.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"twin-event.js","sourceRoot":"","sources":["../../../server/service/twin-event/twin-event.ts"],"names":[],"mappings":"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","sourcesContent":["import { CreateDateColumn, Entity, Index, Column, RelationId, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'\nimport { ObjectType, Field, ID, Int } from 'type-graphql'\n\nimport { Domain, ScalarObject } from '@things-factory/shell'\n\n/*\n * TwinEvent — append-only 트윈 이벤트 저널(EPCIS 이벤트 + 운영 델타).\n * 커널 EventJournal 의 영속 대응 — 상태=이벤트열의 함수(재부팅 시 DB→replay 재구성).\n * append-only 이므로 updater/deletedAt 없음(이력 CRUD 가 아니라 불변 이벤트 스트림).\n * payload 는 simple-json(멀티DB 이식 — postgres/mysql/sqlite/mssql/oracle 공통, DB-specific JSON 타입 금지).\n * (CLAUDE.md: 모든 @ObjectType/@Field 는 영문 description 필수.)\n */\n/*\n * ── 인덱스 설계 (2026-07-31) ────────────────────────────────────────────────\n * 이 표는 **인제스트 경로의 뜨거운 append-only 테이블**이다. 인덱스 하나하나가 쓰기 증폭이므로\n * \"있으면 좋을\" 인덱스를 붙이지 않는다. 실제 질의 패턴에 대응하는 것만 둔다.\n *\n * ix_0 (domain, instanceId, revision) 원장 기본 정렬·커서 페이징·replay(ASC 주사)\n * ix_1 (domain, instanceId, eventTime) 시각 커서(untilTime)·시간창 KPI — 거의 모든 조회가 탄다\n * ix_2 (domain, instanceId, eventType, revision) 타입 필터 + 정렬 동시 충족(스케줄 화면 task/equipment)\n * ix_3 (domain, instanceId, epc) \"이 물건의 이력\" — Entity360 의 본질 질문\n * ix_4 (domain, instanceId, orderId) \"이 오더가 어디까지 갔나\"\n *\n * bizStep·locationId·moverId 는 컬럼만 두고 인덱스는 두지 않는다 — 한 트윈 안에서 카디널리티가\n * 낮아(업무단계 몇 개, 위치 수백, 설비 수십) (domain,instanceId) 로 이미 좁혀진 뒤의 잔여 필터로\n * 충분하고, 뜨거운 표에 인덱스를 더 얹을 가치가 없다. 저널 하나가 아주 커져서 이 축들의 조회가\n * 느려지면 그때 측정을 근거로 인덱스를 추가할 일이지, 지레 얹어 쓰기를 무겁게 할 일은 아니다.\n */\n@Entity()\n@Index('ix_twin_event_0', (e: TwinEvent) => [e.domain, e.instanceId, e.revision], { unique: false })\n@Index('ix_twin_event_1', (e: TwinEvent) => [e.domain, e.instanceId, e.eventTime], { unique: false })\n@Index('ix_twin_event_2', (e: TwinEvent) => [e.domain, e.instanceId, e.eventType, e.revision], { unique: false })\n@Index('ix_twin_event_3', (e: TwinEvent) => [e.domain, e.instanceId, e.epc], { unique: false })\n@Index('ix_twin_event_4', (e: TwinEvent) => [e.domain, e.instanceId, e.orderId], { unique: false })\n/* ix_5 — \"이 지시가 무엇을 일으켰나\". 감사 기록의 commandId 로 저널을 다시 계산하는 축. */\n@Index('ix_twin_event_5', (e: TwinEvent) => [e.domain, e.correlationId], { unique: false })\n@ObjectType({ description: 'Append-only twin event journal record (EPCIS event or operational delta).' })\nexport class TwinEvent {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Unique identifier of the event record.' })\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true, description: 'Owning tenant domain.' })\n domain?: Domain\n\n @RelationId((e: TwinEvent) => e.domain)\n domainId?: string\n\n /*\n * 이 사실을 낸 트윈. **빌 수 없다** — 저널 행을 만드는 곳은 `journalRow` 하나뿐이고 거기서\n * 필수 인자로 받는다. nullable 로 두면 읽는 쪽이 매번 `?? ''` 를 달게 되고, 그 방어가 실제로는\n * 일어나지 않는 경우를 대비하느라 \"빈 인스턴스의 이벤트\" 라는 없는 개념을 코드에 남긴다.\n */\n @Column()\n @Field({ description: 'Twin runtime instance id that emitted the event.' })\n instanceId: string\n\n /* 커널 봉투의 필수 필드(`CanonicalEnvelope.eventType`)라 빌 수 없다 — 계약을 그대로 옮긴다. */\n @Column()\n @Field({ description: 'Event type: epcis.* | task.status | equipment.status | order.status.' })\n eventType: string\n\n @Column({ type: 'int', nullable: true })\n @Field(type => Int, { nullable: true, description: 'Monotonic state revision at which the event was emitted.' })\n revision?: number\n\n /**\n * 이 사실이 **어느 공장에서** 일어났나 — `TwinStructure.rev`.\n *\n * 이것이 없던 시절에는 구조가 바뀌면 저널을 통째로 지우는 것이 유일한 길이었다(안 지우면 옛\n * 이벤트를 새 공장에 대고 접어 이력이 거짓말을 한다). 이 한 칸이 셋째 길을 연다 — 재생이\n * 구조가 바뀌는 지점에서 전환한 뒤 이어 접는다.\n *\n * **비어 있을 수 있다**: 컬럼이 생기기 전에 쓰인 행이다. 그 행들은 **가장 오래된 리비전**에\n * 속한다(0 을 채워 넣지 않는다 — 0 은 \"모름\" 이 아니라 유효한 번호처럼 보인다).\n */\n @Column({ type: 'int', nullable: true })\n @Field(type => Int, { nullable: true, description: 'Structure revision (TwinStructure.rev) in force when this event happened. Absent on rows written before structure revisions existed; those belong to the oldest revision.' })\n structureRev?: number\n\n /*\n * 이 사실이 일어난 시각(트윈의 시계). **날짜 타입이다.**\n *\n * 예전에는 문자열이었다. 읽을 때마다 `Date.parse` 했고, 정렬은 UTC ISO 만 들어온다는 전제 위에서\n * 사전식 비교가 우연히 맞았다 — `+09:00` 오프셋 표기가 하나 섞이면 조용히 순서가 틀린다.\n * 그 전제는 컬럼 어디에도 적혀 있지 않았다.\n *\n * **타입 이름을 적지 않는다** (2026-08-19 Postgres 부팅 실패로 고침).\n *\n * `datetime` 을 적었더니 Postgres 가 부팅 중에 거절했다(`DataTypeNotSupportedError`) — 그 이름은\n * sqlite·MySQL 의 것이고 Postgres 는 `timestamp` 만 안다. 거꾸로 `timestamp` 는 sqlite 가 모른다:\n * **다섯 드라이버에 다 통하는 이름이 아예 없다.**\n *\n * 그래서 이름을 고르는 일을 TypeORM 에 맡긴다. `emitDecoratorMetadata` 가 켜져 있으므로 이 속성의\n * TS 타입(`Date`)에서 드라이버별 타입을 스스로 고른다(Postgres `timestamp` · sqlite·MySQL `datetime`).\n * 이 레포의 다중 DB 규약이 요구하는 것이 바로 그것이다 — 드라이버 방언을 코드에 적지 않는다.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Time at which the fact happened on the twin clock.' })\n eventTime?: Date\n\n /*\n * ── 승격된 검색 축 ────────────────────────────────────────────────────────\n * payload 안에 있던 값을 인제스트 시점에 꺼내 실컬럼으로 둔다(`twin-event-keys.ts` 가 단독 소유).\n * payload 가 여전히 정본이고 이것들은 **파생 색인**이다 — 새로운 진실이 아니라 찾을 수 있게 하는 장치.\n * simple-json(TEXT) 안의 값은 5개 DB 드라이버 공통으로 거를 방법이 없어서(멀티DB 호환 규칙상\n * DB별 JSON 연산자 금지) 승격 외의 선택지가 없다.\n * 길이 상한 255 는 GS1 식별자 규격 대비 충분하며, 넘치는 값은 조용히 잘리지 않고 경고를 남긴다.\n *\n * ── 축을 새로 추가할 때 ──────────────────────────────────────────────────\n * 새 승격 컬럼을 만들면 **이미 쌓인 행은 비어 있다.** 그 행들은 새 축으로 검색되지 않고,\n * 사용자에게는 \"과거가 없는 것\" 으로 보인다(조용히 빠진 데이터).\n *\n * 예전에는 기동 훅이 매번 테이블을 살펴 채웠다. 그건 두 가지로 틀렸다 — 저널이 커질수록 부팅이\n * 느려지고, \"bizStep 이 비었으면 옛 행\" 이라는 전제를 코드에 고정해 그 전제가 깨지는 순간\n * (감사 행처럼 업무 단계가 없는 행) 매 부팅마다 헛돌았다. 그래서 걷어냈다(git: backfill-keys.ts).\n *\n * 채우는 일은 **마이그레이션 한 번**이 맞다(`yarn migration`). 그 컬럼에 맞는 것을 그때 쓴다 —\n * 범용 스캐너를 되살리지 말 것. 개발 데이타라면 그냥 버리는 것도 답이다.\n */\n /**\n * 이 사실을 일으킨 **지시** — 커널이 커맨드에서 이어 준 상관값(기본은 commandId).\n *\n * 이것이 감사 기록(TwinAuditEvent.commandId)과 저널을 잇는 유일한 다리다. 없으면 승인 기록은\n * \"사람이 허락했다\" 까지이고, 그 뒤 공장이 실제로 어떻게 움직였는지와 이어지지 않는다.\n *\n * **대부분의 행은 비어 있다** — 커맨드가 아니라 시뮬 시간이 낳은 사실이기 때문이다. 그것이 정상이고,\n * 비어 있다고 \"덜 채워진 옛 행\" 으로 보면 안 된다(같은 오해를 bizStep 에서 이미 한 번 했다).\n */\n @Column({ length: 255, nullable: true })\n @Field({ nullable: true, description: 'Id of the command that caused this fact (kernel correlation id; defaults to the commandId). Empty for facts that simulation time produced rather than a command.' })\n correlationId?: string\n\n @Column({ length: 255, nullable: true })\n @Field({ nullable: true, description: 'Business step (CBV bizStep tail), promoted from the payload for indexed filtering.' })\n bizStep?: string\n\n @Column({ length: 255, nullable: true })\n @Field({ nullable: true, description: 'Item identifier (EPC / EPC class / parent id), promoted from the payload for indexed lookup of one item history.' })\n epc?: string\n\n @Column({ length: 255, nullable: true })\n @Field({ nullable: true, description: 'Business transaction identifier (PO / SO), promoted from the payload for indexed lookup of one order history.' })\n orderId?: string\n\n @Column({ length: 255, nullable: true })\n @Field({ nullable: true, description: 'Location identifier (read point, business location, or the plain location an operational delta carries), promoted from the payload for indexed filtering.' })\n locationId?: string\n\n @Column({ length: 255, nullable: true })\n @Field({ nullable: true, description: 'Equipment identifier carried by operational deltas, promoted from the payload so one machine history can be asked for on the server. The column keeps the legacy name `moverId`: the journal is append-only history and mixing two names for the same fact across time is worse than an outdated name. Renaming belongs with event-schema versioning.' })\n moverId?: string\n\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, { nullable: true, description: 'Raw canonical envelope (EPCIS event or operational delta) as JSON.' })\n payload?: any\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'Wall-clock timestamp when the record was persisted.' })\n createdAt?: Date\n}\n"]}
|
|
1
|
+
{"version":3,"file":"twin-event.js","sourceRoot":"","sources":["../../../server/service/twin-event/twin-event.ts"],"names":[],"mappings":"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","sourcesContent":["import { CreateDateColumn, Entity, Index, Column, RelationId, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'\nimport { ObjectType, Field, ID, Int } from 'type-graphql'\n\nimport { Domain, ScalarObject } from '@things-factory/shell'\n\n/*\n * TwinEvent — append-only 트윈 이벤트 저널(EPCIS 이벤트 + 운영 델타).\n * 커널 EventJournal 의 영속 대응 — 상태=이벤트열의 함수(재부팅 시 DB→replay 재구성).\n * append-only 이므로 updater/deletedAt 없음(이력 CRUD 가 아니라 불변 이벤트 스트림).\n * payload 는 simple-json(멀티DB 이식 — postgres/mysql/sqlite/mssql/oracle 공통, DB-specific JSON 타입 금지).\n * (CLAUDE.md: 모든 @ObjectType/@Field 는 영문 description 필수.)\n */\n/*\n * ── 인덱스 설계 (2026-07-31) ────────────────────────────────────────────────\n * 이 표는 **인제스트 경로의 뜨거운 append-only 테이블**이다. 인덱스 하나하나가 쓰기 증폭이므로\n * \"있으면 좋을\" 인덱스를 붙이지 않는다. 실제 질의 패턴에 대응하는 것만 둔다.\n *\n * ix_0 (domain, instanceId, revision) 원장 기본 정렬·커서 페이징·replay(ASC 주사)\n * ix_1 (domain, instanceId, eventTime) 시각 커서(untilTime)·시간창 KPI — 거의 모든 조회가 탄다\n * ix_2 (domain, instanceId, eventType, revision) 타입 필터 + 정렬 동시 충족(스케줄 화면 task/equipment)\n * ix_3 (domain, instanceId, epc) \"이 물건의 이력\" — Entity360 의 본질 질문\n * ix_4 (domain, instanceId, orderId) \"이 오더가 어디까지 갔나\"\n *\n * bizStep·locationId·moverId 는 컬럼만 두고 인덱스는 두지 않는다 — 한 트윈 안에서 카디널리티가\n * 낮아(업무단계 몇 개, 위치 수백, 설비 수십) (domain,instanceId) 로 이미 좁혀진 뒤의 잔여 필터로\n * 충분하고, 뜨거운 표에 인덱스를 더 얹을 가치가 없다. 저널 하나가 아주 커져서 이 축들의 조회가\n * 느려지면 그때 측정을 근거로 인덱스를 추가할 일이지, 지레 얹어 쓰기를 무겁게 할 일은 아니다.\n */\n@Entity()\n@Index('ix_twin_event_0', (e: TwinEvent) => [e.domain, e.instanceId, e.revision], { unique: false })\n@Index('ix_twin_event_1', (e: TwinEvent) => [e.domain, e.instanceId, e.eventTime], { unique: false })\n@Index('ix_twin_event_2', (e: TwinEvent) => [e.domain, e.instanceId, e.eventType, e.revision], { unique: false })\n@Index('ix_twin_event_3', (e: TwinEvent) => [e.domain, e.instanceId, e.epc], { unique: false })\n@Index('ix_twin_event_4', (e: TwinEvent) => [e.domain, e.instanceId, e.orderId], { unique: false })\n/* ix_5 — \"이 지시가 무엇을 일으켰나\". 감사 기록의 commandId 로 저널을 다시 계산하는 축. */\n@Index('ix_twin_event_5', (e: TwinEvent) => [e.domain, e.correlationId], { unique: false })\n/* ix_6 — \"반출을 한 번이라도 접었나\". 없으면 그 질문이 인스턴스 저널 전량 주사가 된다. */\n@Index('ix_twin_event_6', (e: TwinEvent) => [e.domain, e.instanceId, e.action], { unique: false })\n@ObjectType({ description: 'Append-only twin event journal record (EPCIS event or operational delta).' })\nexport class TwinEvent {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Unique identifier of the event record.' })\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true, description: 'Owning tenant domain.' })\n domain?: Domain\n\n @RelationId((e: TwinEvent) => e.domain)\n domainId?: string\n\n /*\n * 이 사실을 낸 트윈. **빌 수 없다** — 저널 행을 만드는 곳은 `journalRow` 하나뿐이고 거기서\n * 필수 인자로 받는다. nullable 로 두면 읽는 쪽이 매번 `?? ''` 를 달게 되고, 그 방어가 실제로는\n * 일어나지 않는 경우를 대비하느라 \"빈 인스턴스의 이벤트\" 라는 없는 개념을 코드에 남긴다.\n */\n @Column()\n @Field({ description: 'Twin runtime instance id that emitted the event.' })\n instanceId: string\n\n /* 커널 봉투의 필수 필드(`CanonicalEnvelope.eventType`)라 빌 수 없다 — 계약을 그대로 옮긴다. */\n @Column()\n @Field({ description: 'Event type: epcis.* | task.status | equipment.status | order.status.' })\n eventType: string\n\n @Column({ type: 'int', nullable: true })\n @Field(type => Int, { nullable: true, description: 'Monotonic state revision at which the event was emitted.' })\n revision?: number\n\n /**\n * 이 사실이 **어느 공장에서** 일어났나 — `TwinStructure.rev`.\n *\n * 이것이 없던 시절에는 구조가 바뀌면 저널을 통째로 지우는 것이 유일한 길이었다(안 지우면 옛\n * 이벤트를 새 공장에 대고 접어 이력이 거짓말을 한다). 이 한 칸이 셋째 길을 연다 — 재생이\n * 구조가 바뀌는 지점에서 전환한 뒤 이어 접는다.\n *\n * **비어 있을 수 있다**: 컬럼이 생기기 전에 쓰인 행이다. 그 행들은 **가장 오래된 리비전**에\n * 속한다(0 을 채워 넣지 않는다 — 0 은 \"모름\" 이 아니라 유효한 번호처럼 보인다).\n */\n @Column({ type: 'int', nullable: true })\n @Field(type => Int, { nullable: true, description: 'Structure revision (TwinStructure.rev) in force when this event happened. Absent on rows written before structure revisions existed; those belong to the oldest revision.' })\n structureRev?: number\n\n /*\n * 이 사실이 일어난 시각(트윈의 시계). **날짜 타입이다.**\n *\n * 예전에는 문자열이었다. 읽을 때마다 `Date.parse` 했고, 정렬은 UTC ISO 만 들어온다는 전제 위에서\n * 사전식 비교가 우연히 맞았다 — `+09:00` 오프셋 표기가 하나 섞이면 조용히 순서가 틀린다.\n * 그 전제는 컬럼 어디에도 적혀 있지 않았다.\n *\n * **타입 이름을 적지 않는다** (2026-08-19 Postgres 부팅 실패로 고침).\n *\n * `datetime` 을 적었더니 Postgres 가 부팅 중에 거절했다(`DataTypeNotSupportedError`) — 그 이름은\n * sqlite·MySQL 의 것이고 Postgres 는 `timestamp` 만 안다. 거꾸로 `timestamp` 는 sqlite 가 모른다:\n * **다섯 드라이버에 다 통하는 이름이 아예 없다.**\n *\n * 그래서 이름을 고르는 일을 TypeORM 에 맡긴다. `emitDecoratorMetadata` 가 켜져 있으므로 이 속성의\n * TS 타입(`Date`)에서 드라이버별 타입을 스스로 고른다(Postgres `timestamp` · sqlite·MySQL `datetime`).\n * 이 레포의 다중 DB 규약이 요구하는 것이 바로 그것이다 — 드라이버 방언을 코드에 적지 않는다.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Time at which the fact happened on the twin clock.' })\n eventTime?: Date\n\n /*\n * ── 승격된 검색 축 ────────────────────────────────────────────────────────\n * payload 안에 있던 값을 인제스트 시점에 꺼내 실컬럼으로 둔다(`twin-event-keys.ts` 가 단독 소유).\n * payload 가 여전히 정본이고 이것들은 **파생 색인**이다 — 새로운 진실이 아니라 찾을 수 있게 하는 장치.\n * simple-json(TEXT) 안의 값은 5개 DB 드라이버 공통으로 거를 방법이 없어서(멀티DB 호환 규칙상\n * DB별 JSON 연산자 금지) 승격 외의 선택지가 없다.\n * 길이 상한 255 는 GS1 식별자 규격 대비 충분하며, 넘치는 값은 조용히 잘리지 않고 경고를 남긴다.\n *\n * ── 축을 새로 추가할 때 ──────────────────────────────────────────────────\n * 새 승격 컬럼을 만들면 **이미 쌓인 행은 비어 있다.** 그 행들은 새 축으로 검색되지 않고,\n * 사용자에게는 \"과거가 없는 것\" 으로 보인다(조용히 빠진 데이터).\n *\n * 예전에는 기동 훅이 매번 테이블을 살펴 채웠다. 그건 두 가지로 틀렸다 — 저널이 커질수록 부팅이\n * 느려지고, \"bizStep 이 비었으면 옛 행\" 이라는 전제를 코드에 고정해 그 전제가 깨지는 순간\n * (감사 행처럼 업무 단계가 없는 행) 매 부팅마다 헛돌았다. 그래서 걷어냈다(git: backfill-keys.ts).\n *\n * 채우는 일은 **마이그레이션 한 번**이 맞다(`yarn migration`). 그 컬럼에 맞는 것을 그때 쓴다 —\n * 범용 스캐너를 되살리지 말 것. 개발 데이타라면 그냥 버리는 것도 답이다.\n */\n /**\n * 이 사실을 일으킨 **지시** — 커널이 커맨드에서 이어 준 상관값(기본은 commandId).\n *\n * 이것이 감사 기록(TwinAuditEvent.commandId)과 저널을 잇는 유일한 다리다. 없으면 승인 기록은\n * \"사람이 허락했다\" 까지이고, 그 뒤 공장이 실제로 어떻게 움직였는지와 이어지지 않는다.\n *\n * **대부분의 행은 비어 있다** — 커맨드가 아니라 시뮬 시간이 낳은 사실이기 때문이다. 그것이 정상이고,\n * 비어 있다고 \"덜 채워진 옛 행\" 으로 보면 안 된다(같은 오해를 bizStep 에서 이미 한 번 했다).\n */\n @Column({ length: 255, nullable: true })\n @Field({ nullable: true, description: 'Id of the command that caused this fact (kernel correlation id; defaults to the commandId). Empty for facts that simulation time produced rather than a command.' })\n correlationId?: string\n\n /* 옛 행은 비어 있다 — 승격 전에 기록된 것이라 **모른다**(0 이나 OBSERVE 로 위장하지 않는다). */\n @Column({ length: 16, nullable: true })\n @Field({ nullable: true, description: 'EPCIS action (ADD / OBSERVE / DELETE), promoted from the payload. Empty for operational deltas, and for rows journaled before this column existed.' })\n action?: string\n\n @Column({ length: 255, nullable: true })\n @Field({ nullable: true, description: 'Business step (CBV bizStep tail), promoted from the payload for indexed filtering.' })\n bizStep?: string\n\n @Column({ length: 255, nullable: true })\n @Field({ nullable: true, description: 'Item identifier (EPC / EPC class / parent id), promoted from the payload for indexed lookup of one item history.' })\n epc?: string\n\n @Column({ length: 255, nullable: true })\n @Field({ nullable: true, description: 'Business transaction identifier (PO / SO), promoted from the payload for indexed lookup of one order history.' })\n orderId?: string\n\n @Column({ length: 255, nullable: true })\n @Field({ nullable: true, description: 'Location identifier (read point, business location, or the plain location an operational delta carries), promoted from the payload for indexed filtering.' })\n locationId?: string\n\n @Column({ length: 255, nullable: true })\n @Field({ nullable: true, description: 'Equipment identifier carried by operational deltas, promoted from the payload so one machine history can be asked for on the server. The column keeps the legacy name `moverId`: the journal is append-only history and mixing two names for the same fact across time is worse than an outdated name. Renaming belongs with event-schema versioning.' })\n moverId?: string\n\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, { nullable: true, description: 'Raw canonical envelope (EPCIS event or operational delta) as JSON.' })\n payload?: any\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'Wall-clock timestamp when the record was persisted.' })\n createdAt?: Date\n}\n"]}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.TwinForecastQuery = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
|
+
const log_js_1 = require("../../engine/log.js");
|
|
5
6
|
const type_graphql_1 = require("type-graphql");
|
|
6
7
|
const shell_1 = require("@things-factory/shell");
|
|
7
8
|
const index_js_1 = require("../../engine/index.js");
|
|
@@ -179,7 +180,7 @@ let TwinForecastQuery = class TwinForecastQuery {
|
|
|
179
180
|
const basis = await index_js_1.TwinEngine.modelBasis(domainId, instanceId);
|
|
180
181
|
const stale = !!stored && (0, model_basis_js_1.modelBasisChanged)(stored.basis, basis);
|
|
181
182
|
if (stale) {
|
|
182
|
-
|
|
183
|
+
(0, log_js_1.twinLog)(`[twin-forecast] "${instanceId}": calibration not applied — model changed since it was learned.` +
|
|
183
184
|
` learned on: ${(0, model_basis_js_1.describeModelBasis)(stored.basis)} / now: ${(0, model_basis_js_1.describeModelBasis)(basis)}`);
|
|
184
185
|
}
|
|
185
186
|
/*
|
|
@@ -314,7 +315,30 @@ let TwinForecastQuery = class TwinForecastQuery {
|
|
|
314
315
|
calibrated: !!cal,
|
|
315
316
|
/* 학습해 뒀지만 모델이 바뀌어 적용하지 않았다 — 사용자가 다시 배울 수 있게 알린다. */
|
|
316
317
|
...(stale ? { calibrationStale: true } : {}),
|
|
317
|
-
...(specCoverage ? { specCoverage } : {})
|
|
318
|
+
...(specCoverage ? { specCoverage } : {}),
|
|
319
|
+
/*
|
|
320
|
+
* **계보에 구멍이 몇 칸 생겼나** — 개체를 잇지 못한 채 넘어간 공정 수.
|
|
321
|
+
*
|
|
322
|
+
* ── 왜 이 값을 예측 응답에 싣나 (2026-08-24) ──────────────────────────────
|
|
323
|
+
* 미러의 오더는 확보분을 갖지 않는다(원본이 「어느 개체가 잡혀 있나」를 말하지 않는 것이 그
|
|
324
|
+
* 시스템의 정상이다). 커널은 그 자리에서 **던지지 않고 넘긴다** — 단계를 넘는 것은 공정의
|
|
325
|
+
* 진행이고 개체 번호가 필요 없다. 대신 넘긴 횟수를 센다.
|
|
326
|
+
*
|
|
327
|
+
* 그런데 그 카운터는 **fork 안에** 있고 fork 는 버려진다. 여기서 싣지 않으면 아무에게도 닿지
|
|
328
|
+
* 않는다 — 리듀서가 오래 세어 온 값을 스냅샷이 떨어뜨려 그것을 읽도록 쓰인 검사가 영원히
|
|
329
|
+
* 조용했던 것과 **같은 실수**다. 세어 놓고 내보내지 않으면 안 센 것과 같다.
|
|
330
|
+
*
|
|
331
|
+
* 왜 화면이 이것을 알아야 하나: 계보는 **회수 범위**를 정하는 사슬이다. 구멍이 있는데 온전한
|
|
332
|
+
* 것처럼 보이면 회수가 조용히 좁아진다. 식품에서 그것은 되돌릴 수 없다.
|
|
333
|
+
*
|
|
334
|
+
* 두 곳에서 읽어 **큰 값**을 낸다 — 분포(회차 R개의 최대)와 궤적 fork 는 **둘 다 실제로 실행됐다**.
|
|
335
|
+
* 한쪽만 보면 다른 쪽이 겪은 구멍을 잃는다.
|
|
336
|
+
*/
|
|
337
|
+
...(() => {
|
|
338
|
+
const gaps = Math.max(distribution?.stepsWithoutMaterial ?? 0, fc?.getSnapshot?.()?.stepsWithoutMaterial ?? 0);
|
|
339
|
+
/* 0 은 싣지 않는다 — 「구멍이 없다」와 「이 축을 모른다」를 화면이 구별할 수 있게. */
|
|
340
|
+
return gaps > 0 ? { stepsWithoutMaterial: gaps } : {};
|
|
341
|
+
})()
|
|
318
342
|
};
|
|
319
343
|
}
|
|
320
344
|
/*
|