@fmsim/machine 2.0.0-beta.88 → 2.0.0-beta.89

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.
@@ -0,0 +1,55 @@
1
+ /*
2
+ * Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * Carrier 데이터 키 정규화 — 데이터 명세의 키 대소문자가 흔들려도(CARRIERNAME /
5
+ * carrierName / multiCarriers / MULTICARRIERS …) 동일하게 읽기 위함.
6
+ *
7
+ * 성능: 케이스 무관 처리를 "매 읽기"가 아니라 **데이터 진입 경계에서 1회**만 수행.
8
+ * 이후 다운스트림은 캐노니컬(대문자) 키를 평범한 O(1) 직접 접근으로 읽는다.
9
+ */
10
+ import { ciGet } from './crane-position';
11
+ /** 다중 carrier 배열의 캐노니컬 키. (구 명세의 ADDITIONS 는 더 이상 지원하지 않음) */
12
+ export const MULTI_CARRIER_KEY = 'multiCarriers';
13
+ /** carrier 식별 4 키 — 캐노니컬(대문자) 표기. */
14
+ const CARRIER_FIELDS = ['CARRIERNAME', 'CARRIERTYPE', 'EMPTYTYPE', 'CARRIERSTATUS'];
15
+ const FIELD_BY_LOWER = new Map(CARRIER_FIELDS.map(f => [f.toLowerCase(), f]));
16
+ /**
17
+ * carrier 객체의 키를 캐노니컬(대문자)로 승격 — 식별 4 키만 매핑하고 값/기타 키는 보존.
18
+ * 대소문자 무관 (carrierName → CARRIERNAME). 비객체면 그대로 반환.
19
+ */
20
+ export function normalizeCarrierItem(raw) {
21
+ var _a;
22
+ if (!raw || typeof raw !== 'object')
23
+ return raw;
24
+ const out = {};
25
+ for (const k of Object.keys(raw)) {
26
+ out[(_a = FIELD_BY_LOWER.get(k.toLowerCase())) !== null && _a !== void 0 ? _a : k] = raw[k];
27
+ }
28
+ return out;
29
+ }
30
+ /**
31
+ * holder.data 정규화 (진입 경계 1회):
32
+ * - 대표 carrier 4 키를 캐노니컬로 승격
33
+ * - 다중 carrier 배열을 캐노니컬 키 `multiCarriers` 로 통일 (대소문자 무관), 각 항목도 정규화
34
+ *
35
+ * 비객체면 그대로 반환. 정규화 결과는 idempotent (이미 캐노니컬이면 동일 형태).
36
+ */
37
+ export function normalizeCarrierData(raw) {
38
+ var _a;
39
+ if (!raw || typeof raw !== 'object')
40
+ return raw;
41
+ const multiLower = MULTI_CARRIER_KEY.toLowerCase();
42
+ const out = {};
43
+ for (const k of Object.keys(raw)) {
44
+ // 다중 carrier 배열 키는 아래에서 캐노니컬로 통일 처리하므로 여기선 건너뜀.
45
+ if (k.toLowerCase() === multiLower)
46
+ continue;
47
+ out[(_a = FIELD_BY_LOWER.get(k.toLowerCase())) !== null && _a !== void 0 ? _a : k] = raw[k];
48
+ }
49
+ const list = ciGet(raw, MULTI_CARRIER_KEY);
50
+ if (Array.isArray(list)) {
51
+ out[MULTI_CARRIER_KEY] = list.map(normalizeCarrierItem);
52
+ }
53
+ return out;
54
+ }
55
+ //# sourceMappingURL=carrier-data.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"carrier-data.js","sourceRoot":"","sources":["../../src/utils/carrier-data.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;AAExC,8DAA8D;AAC9D,MAAM,CAAC,MAAM,iBAAiB,GAAG,eAAe,CAAA;AAEhD,qCAAqC;AACrC,MAAM,cAAc,GAAG,CAAC,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,eAAe,CAAU,CAAA;AAC5F,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;AAE7E;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAQ;;IAC3C,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,GAAG,CAAA;IAC/C,MAAM,GAAG,GAAQ,EAAE,CAAA;IACnB,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACjC,GAAG,CAAC,MAAA,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,mCAAI,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAA;IACxD,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAQ;;IAC3C,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,GAAG,CAAA;IAE/C,MAAM,UAAU,GAAG,iBAAiB,CAAC,WAAW,EAAE,CAAA;IAClD,MAAM,GAAG,GAAQ,EAAE,CAAA;IACnB,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACjC,gDAAgD;QAChD,IAAI,CAAC,CAAC,WAAW,EAAE,KAAK,UAAU;YAAE,SAAQ;QAC5C,GAAG,CAAC,MAAA,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,mCAAI,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAA;IACxD,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAA;IAC1C,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,GAAG,CAAC,iBAAiB,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAA;IACzD,CAAC;IAED,OAAO,GAAG,CAAA;AACZ,CAAC","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Carrier 데이터 키 정규화 — 데이터 명세의 키 대소문자가 흔들려도(CARRIERNAME /\n * carrierName / multiCarriers / MULTICARRIERS …) 동일하게 읽기 위함.\n *\n * 성능: 케이스 무관 처리를 \"매 읽기\"가 아니라 **데이터 진입 경계에서 1회**만 수행.\n * 이후 다운스트림은 캐노니컬(대문자) 키를 평범한 O(1) 직접 접근으로 읽는다.\n */\n\nimport { ciGet } from './crane-position'\n\n/** 다중 carrier 배열의 캐노니컬 키. (구 명세의 ADDITIONS 는 더 이상 지원하지 않음) */\nexport const MULTI_CARRIER_KEY = 'multiCarriers'\n\n/** carrier 식별 4 키 — 캐노니컬(대문자) 표기. */\nconst CARRIER_FIELDS = ['CARRIERNAME', 'CARRIERTYPE', 'EMPTYTYPE', 'CARRIERSTATUS'] as const\nconst FIELD_BY_LOWER = new Map(CARRIER_FIELDS.map(f => [f.toLowerCase(), f]))\n\n/**\n * carrier 객체의 키를 캐노니컬(대문자)로 승격 — 식별 4 키만 매핑하고 값/기타 키는 보존.\n * 대소문자 무관 (carrierName → CARRIERNAME). 비객체면 그대로 반환.\n */\nexport function normalizeCarrierItem(raw: any): any {\n if (!raw || typeof raw !== 'object') return raw\n const out: any = {}\n for (const k of Object.keys(raw)) {\n out[FIELD_BY_LOWER.get(k.toLowerCase()) ?? k] = raw[k]\n }\n return out\n}\n\n/**\n * holder.data 정규화 (진입 경계 1회):\n * - 대표 carrier 4 키를 캐노니컬로 승격\n * - 다중 carrier 배열을 캐노니컬 키 `multiCarriers` 로 통일 (대소문자 무관), 각 항목도 정규화\n *\n * 비객체면 그대로 반환. 정규화 결과는 idempotent (이미 캐노니컬이면 동일 형태).\n */\nexport function normalizeCarrierData(raw: any): any {\n if (!raw || typeof raw !== 'object') return raw\n\n const multiLower = MULTI_CARRIER_KEY.toLowerCase()\n const out: any = {}\n for (const k of Object.keys(raw)) {\n // 다중 carrier 배열 키는 아래에서 캐노니컬로 통일 처리하므로 여기선 건너뜀.\n if (k.toLowerCase() === multiLower) continue\n out[FIELD_BY_LOWER.get(k.toLowerCase()) ?? k] = raw[k]\n }\n\n const list = ciGet(raw, MULTI_CARRIER_KEY)\n if (Array.isArray(list)) {\n out[MULTI_CARRIER_KEY] = list.map(normalizeCarrierItem)\n }\n\n return out\n}\n"]}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@fmsim/machine",
3
3
  "description": "Layout View를 구성하는 설비(machine)들과 유닛을 구현한 모듈입니다.",
4
4
  "author": "heartyoh",
5
- "version": "2.0.0-beta.88",
5
+ "version": "2.0.0-beta.89",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",
8
8
  "things-scene": true,
@@ -55,5 +55,5 @@
55
55
  "prettier --write"
56
56
  ]
57
57
  },
58
- "gitHead": "a85bfc6794b783dd619448e2cc07cca8eafbb467"
58
+ "gitHead": "e65994c03e8dc1c33b4466a61f12e9135f1bc59f"
59
59
  }
@@ -1,6 +1,7 @@
1
1
  import { Model } from '@hatiolab/things-scene'
2
2
  import type Carrier from './carrier'
3
3
  import type MCSCarrierHolder from './mcs-carrier-holder'
4
+ import { normalizeCarrierData, MULTI_CARRIER_KEY } from './utils/carrier-data'
4
5
 
5
6
  // 사라진 carrier 의 dispose 지연. 같은 macrotask 안에서 holder A 의 사라짐
6
7
  // 보고와 holder B 의 등장 보고가 순차로 들어오는 reparent 시나리오 (= 같은
@@ -57,6 +58,11 @@ export default class CarrierManager {
57
58
  * holder 가 같은 이름을 보고하면 cancel 되어 reparent 로 전환.
58
59
  */
59
60
  update(holder: MCSCarrierHolder, after: Record<string, any>, before: Record<string, any>): void {
61
+ // 진입 경계 1회 정규화 — 키 대소문자 무관 + multiCarriers 통일 (idempotent).
62
+ // 이후 _buildStack / diffing 은 캐노니컬 키를 O(1) 직접 접근으로 읽는다.
63
+ after = normalizeCarrierData(after)
64
+ before = normalizeCarrierData(before)
65
+
60
66
  const afterStack = this._buildStack(after)
61
67
  const beforeStack = this._buildStack(before)
62
68
 
@@ -127,16 +133,17 @@ export default class CarrierManager {
127
133
  }
128
134
 
129
135
  /**
130
- * data.CARRIERNAME (대표) + data.ADDITIONS[] 를 하나의 stack list 로 정규화.
131
- * 적층 순서: ADDITIONS[0..n-1] (bottom → up) + 대표 (top, 맨 마지막).
136
+ * data.CARRIERNAME (대표) + data.multiCarriers[] 를 하나의 stack list 로 정규화.
137
+ * 적층 순서: multiCarriers[0..n-1] (bottom → up) + 대표 (top, 맨 마지막).
138
+ * (data 는 update() 에서 이미 정규화되어 캐노니컬 키 + multiCarriers 로 통일된 상태)
132
139
  */
133
140
  private _buildStack(data: any): CarrierStackItem[] {
134
141
  if (!data) return []
135
142
 
136
143
  const stack: CarrierStackItem[] = []
137
144
 
138
- if (Array.isArray(data.ADDITIONS)) {
139
- for (const a of data.ADDITIONS) {
145
+ if (Array.isArray(data[MULTI_CARRIER_KEY])) {
146
+ for (const a of data[MULTI_CARRIER_KEY]) {
140
147
  if (a && typeof a === 'object' && a.CARRIERNAME) {
141
148
  // 항목 *원본 그대로* — 외부 시스템이 임의 키를 추가로 보낼 수 있고,
142
149
  // 누락된 4 키는 default 없이 그대로 비워둔다 (carrier.data 에 흘리는
@@ -1,16 +1,17 @@
1
1
  import { themesColorMap, themesAnimationMap } from '@fmsim/api'
2
2
  import MCSUnit from './mcs-unit.js'
3
+ import { normalizeCarrierData, MULTI_CARRIER_KEY } from './utils/carrier-data'
3
4
 
4
5
  /**
5
- * Holder.data 변경 감지 시 carrierManager 에 전달할 4 키 묶음 + ADDITIONS 배열.
6
- * 비교 결과 단 하나라도 다르면 carrierManager.update 호출.
6
+ * Holder.data 변경 감지 시 carrierManager 에 전달할 4 키 묶음 + multiCarriers 배열.
7
+ * 비교 결과 단 하나라도 다르면 carrierManager.update 호출. (정규화된 캐노니컬 데이터로 비교)
7
8
  */
8
9
  const CARRIER_DATA_KEYS = ['CARRIERNAME', 'CARRIERTYPE', 'EMPTYTYPE', 'CARRIERSTATUS'] as const
9
10
 
10
- function additionsChanged(a: any, b: any): boolean {
11
+ function multiCarriersChanged(a: any, b: any): boolean {
11
12
  // shallow JSON 비교로 충분 — 각 항목이 4 키 primitive 만 가짐. 깊은 ref 비교
12
13
  // 보다 빠르고 정확. 빈/누락은 같은 '[]' 직렬화로 동치 취급.
13
- return JSON.stringify(a?.ADDITIONS ?? []) !== JSON.stringify(b?.ADDITIONS ?? [])
14
+ return JSON.stringify(a?.[MULTI_CARRIER_KEY] ?? []) !== JSON.stringify(b?.[MULTI_CARRIER_KEY] ?? [])
14
15
  }
15
16
 
16
17
  export default class MCSCarrierHolder extends MCSUnit {
@@ -68,8 +69,9 @@ export default class MCSCarrierHolder extends MCSUnit {
68
69
  }
69
70
 
70
71
  onchangeData(after: Record<string, any>, before: Record<string, any>): void {
71
- const afterData = after.data || {}
72
- const beforeData = before.data || {}
72
+ // 진입 경계 1회 정규화 — 키 대소문자 무관 + multiCarriers 통일. 이후 비교/update 는 캐노니컬 O(1).
73
+ const afterData = normalizeCarrierData(after.data || {})
74
+ const beforeData = normalizeCarrierData(before.data || {})
73
75
 
74
76
  let changed = false
75
77
  for (const k of CARRIER_DATA_KEYS) {
@@ -79,7 +81,7 @@ export default class MCSCarrierHolder extends MCSUnit {
79
81
  }
80
82
  }
81
83
  if (!changed) {
82
- changed = additionsChanged(afterData, beforeData)
84
+ changed = multiCarriersChanged(afterData, beforeData)
83
85
  }
84
86
 
85
87
  if (changed) {
@@ -89,11 +91,12 @@ export default class MCSCarrierHolder extends MCSUnit {
89
91
  super.onchangeData(after, before)
90
92
  }
91
93
 
92
- /** data 안에 carrier 정보 (대표 또는 ADDITIONS) 가 하나라도 있는지. */
93
- private _hasCarrierData(data: any): boolean {
94
+ /** data 안에 carrier 정보 (대표 또는 multiCarriers) 가 하나라도 있는지. (정규화 후 판정) */
95
+ private _hasCarrierData(raw: any): boolean {
96
+ const data = normalizeCarrierData(raw)
94
97
  if (!data) return false
95
98
  if (data.CARRIERNAME) return true
96
- if (Array.isArray(data.ADDITIONS) && data.ADDITIONS.some((a: any) => a?.CARRIERNAME)) return true
99
+ if (Array.isArray(data[MULTI_CARRIER_KEY]) && data[MULTI_CARRIER_KEY].some((a: any) => a?.CARRIERNAME)) return true
97
100
  return false
98
101
  }
99
102
  }
@@ -0,0 +1,57 @@
1
+ /*
2
+ * Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * Carrier 데이터 키 정규화 — 데이터 명세의 키 대소문자가 흔들려도(CARRIERNAME /
5
+ * carrierName / multiCarriers / MULTICARRIERS …) 동일하게 읽기 위함.
6
+ *
7
+ * 성능: 케이스 무관 처리를 "매 읽기"가 아니라 **데이터 진입 경계에서 1회**만 수행.
8
+ * 이후 다운스트림은 캐노니컬(대문자) 키를 평범한 O(1) 직접 접근으로 읽는다.
9
+ */
10
+
11
+ import { ciGet } from './crane-position'
12
+
13
+ /** 다중 carrier 배열의 캐노니컬 키. (구 명세의 ADDITIONS 는 더 이상 지원하지 않음) */
14
+ export const MULTI_CARRIER_KEY = 'multiCarriers'
15
+
16
+ /** carrier 식별 4 키 — 캐노니컬(대문자) 표기. */
17
+ const CARRIER_FIELDS = ['CARRIERNAME', 'CARRIERTYPE', 'EMPTYTYPE', 'CARRIERSTATUS'] as const
18
+ const FIELD_BY_LOWER = new Map(CARRIER_FIELDS.map(f => [f.toLowerCase(), f]))
19
+
20
+ /**
21
+ * carrier 객체의 키를 캐노니컬(대문자)로 승격 — 식별 4 키만 매핑하고 값/기타 키는 보존.
22
+ * 대소문자 무관 (carrierName → CARRIERNAME). 비객체면 그대로 반환.
23
+ */
24
+ export function normalizeCarrierItem(raw: any): any {
25
+ if (!raw || typeof raw !== 'object') return raw
26
+ const out: any = {}
27
+ for (const k of Object.keys(raw)) {
28
+ out[FIELD_BY_LOWER.get(k.toLowerCase()) ?? k] = raw[k]
29
+ }
30
+ return out
31
+ }
32
+
33
+ /**
34
+ * holder.data 정규화 (진입 경계 1회):
35
+ * - 대표 carrier 4 키를 캐노니컬로 승격
36
+ * - 다중 carrier 배열을 캐노니컬 키 `multiCarriers` 로 통일 (대소문자 무관), 각 항목도 정규화
37
+ *
38
+ * 비객체면 그대로 반환. 정규화 결과는 idempotent (이미 캐노니컬이면 동일 형태).
39
+ */
40
+ export function normalizeCarrierData(raw: any): any {
41
+ if (!raw || typeof raw !== 'object') return raw
42
+
43
+ const multiLower = MULTI_CARRIER_KEY.toLowerCase()
44
+ const out: any = {}
45
+ for (const k of Object.keys(raw)) {
46
+ // 다중 carrier 배열 키는 아래에서 캐노니컬로 통일 처리하므로 여기선 건너뜀.
47
+ if (k.toLowerCase() === multiLower) continue
48
+ out[FIELD_BY_LOWER.get(k.toLowerCase()) ?? k] = raw[k]
49
+ }
50
+
51
+ const list = ciGet(raw, MULTI_CARRIER_KEY)
52
+ if (Array.isArray(list)) {
53
+ out[MULTI_CARRIER_KEY] = list.map(normalizeCarrierItem)
54
+ }
55
+
56
+ return out
57
+ }