@things-factory/headless-twin 10.0.14 → 10.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (478) hide show
  1. package/dist-server/engine/attention-digest.d.ts +4 -4
  2. package/dist-server/engine/attention-digest.js +7 -7
  3. package/dist-server/engine/attention-digest.js.map +1 -1
  4. package/dist-server/engine/canonical-ingest.d.ts +11 -2
  5. package/dist-server/engine/canonical-ingest.js +137 -8
  6. package/dist-server/engine/canonical-ingest.js.map +1 -1
  7. package/dist-server/engine/command-routing.js +1 -1
  8. package/dist-server/engine/command-routing.js.map +1 -1
  9. package/dist-server/engine/declared-stimulus.js +1 -1
  10. package/dist-server/engine/declared-stimulus.js.map +1 -1
  11. package/dist-server/engine/energy-topology.js +1 -1
  12. package/dist-server/engine/energy-topology.js.map +1 -1
  13. package/dist-server/engine/ingest-dedupe.d.ts +38 -0
  14. package/dist-server/engine/ingest-dedupe.js +145 -0
  15. package/dist-server/engine/ingest-dedupe.js.map +1 -0
  16. package/dist-server/engine/ingest-health.d.ts +124 -9
  17. package/dist-server/engine/ingest-health.js +169 -15
  18. package/dist-server/engine/ingest-health.js.map +1 -1
  19. package/dist-server/engine/integration-probes.d.ts +3 -3
  20. package/dist-server/engine/integration-probes.js +6 -6
  21. package/dist-server/engine/integration-probes.js.map +1 -1
  22. package/dist-server/engine/kpi-baseline.js +4 -4
  23. package/dist-server/engine/kpi-baseline.js.map +1 -1
  24. package/dist-server/engine/kpi-fold.d.ts +122 -12
  25. package/dist-server/engine/kpi-fold.js +247 -18
  26. package/dist-server/engine/kpi-fold.js.map +1 -1
  27. package/dist-server/engine/kpi-query.d.ts +11 -11
  28. package/dist-server/engine/kpi-query.js +185 -28
  29. package/dist-server/engine/kpi-query.js.map +1 -1
  30. package/dist-server/engine/live-feed-registry.d.ts +1 -1
  31. package/dist-server/engine/live-feed-registry.js +1 -1
  32. package/dist-server/engine/live-feed-registry.js.map +1 -1
  33. package/dist-server/engine/load-meter.d.ts +1 -1
  34. package/dist-server/engine/load-meter.js.map +1 -1
  35. package/dist-server/engine/local-declarations.d.ts +7 -7
  36. package/dist-server/engine/local-declarations.js +9 -9
  37. package/dist-server/engine/local-declarations.js.map +1 -1
  38. package/dist-server/engine/log.d.ts +1 -1
  39. package/dist-server/engine/log.js +1 -1
  40. package/dist-server/engine/log.js.map +1 -1
  41. package/dist-server/engine/measured-estimator.js +1 -1
  42. package/dist-server/engine/measured-estimator.js.map +1 -1
  43. package/dist-server/engine/measured-yield.js +1 -1
  44. package/dist-server/engine/measured-yield.js.map +1 -1
  45. package/dist-server/engine/model-basis.d.ts +1 -1
  46. package/dist-server/engine/model-basis.js +1 -1
  47. package/dist-server/engine/model-basis.js.map +1 -1
  48. package/dist-server/engine/model-gap.d.ts +3 -3
  49. package/dist-server/engine/model-gap.js +1 -1
  50. package/dist-server/engine/model-gap.js.map +1 -1
  51. package/dist-server/engine/model-vocabulary.js +1 -1
  52. package/dist-server/engine/model-vocabulary.js.map +1 -1
  53. package/dist-server/engine/oee-accumulator.d.ts +4 -4
  54. package/dist-server/engine/oee-accumulator.js +3 -3
  55. package/dist-server/engine/oee-accumulator.js.map +1 -1
  56. package/dist-server/engine/property-effects.js +2 -2
  57. package/dist-server/engine/property-effects.js.map +1 -1
  58. package/dist-server/engine/read-time.d.ts +22 -0
  59. package/dist-server/engine/read-time.js +81 -0
  60. package/dist-server/engine/read-time.js.map +1 -0
  61. package/dist-server/engine/restart-policy.js +1 -1
  62. package/dist-server/engine/restart-policy.js.map +1 -1
  63. package/dist-server/engine/runtime-key.js +1 -1
  64. package/dist-server/engine/runtime-key.js.map +1 -1
  65. package/dist-server/engine/spec-coverage.d.ts +2 -2
  66. package/dist-server/engine/spec-coverage.js +1 -1
  67. package/dist-server/engine/spec-coverage.js.map +1 -1
  68. package/dist-server/engine/stage-path.d.ts +4 -4
  69. package/dist-server/engine/stage-path.js +1 -1
  70. package/dist-server/engine/stage-path.js.map +1 -1
  71. package/dist-server/engine/state-axes.d.ts +1 -1
  72. package/dist-server/engine/state-axes.js +2 -2
  73. package/dist-server/engine/state-axes.js.map +1 -1
  74. package/dist-server/engine/travel-estimator.d.ts +2 -2
  75. package/dist-server/engine/travel-estimator.js +1 -1
  76. package/dist-server/engine/travel-estimator.js.map +1 -1
  77. package/dist-server/engine/twin-engine.d.ts +243 -53
  78. package/dist-server/engine/twin-engine.js +690 -191
  79. package/dist-server/engine/twin-engine.js.map +1 -1
  80. package/dist-server/engine/warm-start.d.ts +2 -2
  81. package/dist-server/engine/warm-start.js +5 -5
  82. package/dist-server/engine/warm-start.js.map +1 -1
  83. package/dist-server/index.js +1 -1
  84. package/dist-server/index.js.map +1 -1
  85. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js +1 -1
  86. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js.map +1 -1
  87. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js +3 -3
  88. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js.map +1 -1
  89. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js +2 -2
  90. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js.map +1 -1
  91. package/dist-server/migrations/1786300000000-IndexEventTypeByTime.d.ts +5 -0
  92. package/dist-server/migrations/1786300000000-IndexEventTypeByTime.js +54 -0
  93. package/dist-server/migrations/1786300000000-IndexEventTypeByTime.js.map +1 -0
  94. package/dist-server/migrations/1786400000000-IndexEventCreatedAt.d.ts +5 -0
  95. package/dist-server/migrations/1786400000000-IndexEventCreatedAt.js +44 -0
  96. package/dist-server/migrations/1786400000000-IndexEventCreatedAt.js.map +1 -0
  97. package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.d.ts +6 -0
  98. package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.js +84 -0
  99. package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.js.map +1 -0
  100. package/dist-server/migrations/index.js +7 -1
  101. package/dist-server/migrations/index.js.map +1 -1
  102. package/dist-server/routes.d.ts +1 -0
  103. package/dist-server/routes.js +69 -5
  104. package/dist-server/routes.js.map +1 -1
  105. package/dist-server/service/index.d.ts +2 -2
  106. package/dist-server/service/index.js +27 -24
  107. package/dist-server/service/index.js.map +1 -1
  108. package/dist-server/service/reference/control-routing.d.ts +1 -1
  109. package/dist-server/service/reference/control-routing.js +1 -1
  110. package/dist-server/service/reference/control-routing.js.map +1 -1
  111. package/dist-server/service/reference/discovery-result.d.ts +2 -2
  112. package/dist-server/service/reference/discovery-result.js +3 -3
  113. package/dist-server/service/reference/discovery-result.js.map +1 -1
  114. package/dist-server/service/reference/hook-contract.d.ts +43 -0
  115. package/dist-server/service/reference/hook-contract.js +59 -0
  116. package/dist-server/service/reference/hook-contract.js.map +1 -0
  117. package/dist-server/service/reference/ingest-space.d.ts +1 -1
  118. package/dist-server/service/reference/ingest-space.js +4 -4
  119. package/dist-server/service/reference/ingest-space.js.map +1 -1
  120. package/dist-server/service/reference/knob-defaults.d.ts +1 -1
  121. package/dist-server/service/reference/knob-defaults.js +4 -4
  122. package/dist-server/service/reference/knob-defaults.js.map +1 -1
  123. package/dist-server/service/reference/reference-adapter.d.ts +140 -8
  124. package/dist-server/service/reference/reference-adapter.js +27 -0
  125. package/dist-server/service/reference/reference-adapter.js.map +1 -1
  126. package/dist-server/service/reference/reference-hook.d.ts +20 -0
  127. package/dist-server/service/reference/reference-hook.js +133 -0
  128. package/dist-server/service/reference/reference-hook.js.map +1 -0
  129. package/dist-server/service/reference/reference-live.d.ts +2 -2
  130. package/dist-server/service/reference/reference-live.js +88 -13
  131. package/dist-server/service/reference/reference-live.js.map +1 -1
  132. package/dist-server/service/reference/reference-master.d.ts +14 -14
  133. package/dist-server/service/reference/reference-master.js +34 -12
  134. package/dist-server/service/reference/reference-master.js.map +1 -1
  135. package/dist-server/service/reference/reference-resolver.d.ts +1 -1
  136. package/dist-server/service/reference/reference-resolver.js +10 -10
  137. package/dist-server/service/reference/reference-resolver.js.map +1 -1
  138. package/dist-server/service/reference/twin-reference.d.ts +3 -3
  139. package/dist-server/service/reference/twin-reference.js.map +1 -1
  140. package/dist-server/service/twin-attention/twin-attention-query.d.ts +1 -1
  141. package/dist-server/service/twin-attention/twin-attention-query.js +1 -1
  142. package/dist-server/service/twin-attention/twin-attention-query.js.map +1 -1
  143. package/dist-server/service/twin-audit/command-audit.d.ts +1 -1
  144. package/dist-server/service/twin-audit/command-audit.js.map +1 -1
  145. package/dist-server/service/twin-audit/twin-audit-event.d.ts +1 -1
  146. package/dist-server/service/twin-audit/twin-audit-event.js.map +1 -1
  147. package/dist-server/service/twin-audit/twin-audit-query.js +1 -1
  148. package/dist-server/service/twin-audit/twin-audit-query.js.map +1 -1
  149. package/dist-server/service/twin-control/twin-control-mutation.js +1 -1
  150. package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
  151. package/dist-server/service/twin-event/twin-event-keys.d.ts +22 -2
  152. package/dist-server/service/twin-event/twin-event-keys.js +37 -5
  153. package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
  154. package/dist-server/service/twin-event/twin-event-type.d.ts +1 -0
  155. package/dist-server/service/twin-event/twin-event-type.js +8 -1
  156. package/dist-server/service/twin-event/twin-event-type.js.map +1 -1
  157. package/dist-server/service/twin-event/twin-event.d.ts +2 -2
  158. package/dist-server/service/twin-event/twin-event.js +32 -4
  159. package/dist-server/service/twin-event/twin-event.js.map +1 -1
  160. package/dist-server/service/twin-forecast/forecast-metrics.js +1 -1
  161. package/dist-server/service/twin-forecast/forecast-metrics.js.map +1 -1
  162. package/dist-server/service/twin-forecast/gap-analytics.d.ts +1 -1
  163. package/dist-server/service/twin-forecast/gap-analytics.js.map +1 -1
  164. package/dist-server/service/twin-forecast/twin-forecast-query.js +29 -11
  165. package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
  166. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.d.ts +1 -1
  167. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js +1 -1
  168. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js.map +1 -1
  169. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js +1 -1
  170. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js.map +1 -1
  171. package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
  172. package/dist-server/service/twin-journal/event-type-split-shape.d.ts +50 -0
  173. package/dist-server/service/twin-journal/event-type-split-shape.js +111 -0
  174. package/dist-server/service/twin-journal/event-type-split-shape.js.map +1 -0
  175. package/dist-server/service/twin-journal/event-type-split.d.ts +15 -0
  176. package/dist-server/service/twin-journal/event-type-split.js +102 -0
  177. package/dist-server/service/twin-journal/event-type-split.js.map +1 -0
  178. package/dist-server/service/twin-journal/journal-order.d.ts +15 -0
  179. package/dist-server/service/twin-journal/journal-order.js +17 -0
  180. package/dist-server/service/twin-journal/journal-order.js.map +1 -0
  181. package/dist-server/service/twin-journal/twin-journal-query.d.ts +5 -5
  182. package/dist-server/service/twin-journal/twin-journal-query.js +232 -21
  183. package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
  184. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +27 -1
  185. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +59 -3
  186. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
  187. package/dist-server/service/twin-metrics/twin-metrics-query.js +14 -1
  188. package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
  189. package/dist-server/service/twin-model/axis-journal-evidence.js +1 -1
  190. package/dist-server/service/twin-model/axis-journal-evidence.js.map +1 -1
  191. package/dist-server/service/twin-model/epcis-coverage.js +36 -1
  192. package/dist-server/service/twin-model/epcis-coverage.js.map +1 -1
  193. package/dist-server/service/twin-model/iec61850-coverage.js +1 -1
  194. package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -1
  195. package/dist-server/service/twin-model/isa95-coverage.d.ts +15 -2
  196. package/dist-server/service/twin-model/isa95-coverage.js +32 -8
  197. package/dist-server/service/twin-model/isa95-coverage.js.map +1 -1
  198. package/dist-server/service/twin-model/item-ref.js +2 -2
  199. package/dist-server/service/twin-model/item-ref.js.map +1 -1
  200. package/dist-server/service/twin-model/name-index.d.ts +1 -1
  201. package/dist-server/service/twin-model/name-index.js +1 -1
  202. package/dist-server/service/twin-model/name-index.js.map +1 -1
  203. package/dist-server/service/twin-model/project-structure.d.ts +1 -1
  204. package/dist-server/service/twin-model/project-structure.js +3 -3
  205. package/dist-server/service/twin-model/project-structure.js.map +1 -1
  206. package/dist-server/service/twin-model/twin-equipment.d.ts +1 -1
  207. package/dist-server/service/twin-model/twin-equipment.js +1 -1
  208. package/dist-server/service/twin-model/twin-equipment.js.map +1 -1
  209. package/dist-server/service/twin-model/twin-lineage-query.js +16 -5
  210. package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -1
  211. package/dist-server/service/twin-model/twin-location.d.ts +1 -1
  212. package/dist-server/service/twin-model/twin-location.js.map +1 -1
  213. package/dist-server/service/twin-model/twin-model-item-query.d.ts +3 -3
  214. package/dist-server/service/twin-model/twin-model-item-query.js +14 -14
  215. package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
  216. package/dist-server/service/twin-model/twin-model-mutation.d.ts +2 -2
  217. package/dist-server/service/twin-model/twin-model-mutation.js +4 -4
  218. package/dist-server/service/twin-model/twin-model-mutation.js.map +1 -1
  219. package/dist-server/service/twin-model/twin-model-query.js +117 -28
  220. package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
  221. package/dist-server/service/twin-model/twin-model-tree-query.js +22 -0
  222. package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -1
  223. package/dist-server/service/twin-model/twin-operation.d.ts +1 -1
  224. package/dist-server/service/twin-model/twin-operation.js +1 -1
  225. package/dist-server/service/twin-model/twin-operation.js.map +1 -1
  226. package/dist-server/service/twin-space/move-space.js +2 -2
  227. package/dist-server/service/twin-space/move-space.js.map +1 -1
  228. package/dist-server/service/twin-space/representation-role.d.ts +2 -0
  229. package/dist-server/service/twin-space/representation-role.js +18 -0
  230. package/dist-server/service/twin-space/representation-role.js.map +1 -0
  231. package/dist-server/service/twin-space/space-integrity.d.ts +1 -1
  232. package/dist-server/service/twin-space/space-integrity.js.map +1 -1
  233. package/dist-server/service/twin-space/twin-space-representation.d.ts +2 -0
  234. package/dist-server/service/twin-space/twin-space-representation.js +16 -0
  235. package/dist-server/service/twin-space/twin-space-representation.js.map +1 -1
  236. package/dist-server/service/twin-space/twin-space-resolver.d.ts +2 -2
  237. package/dist-server/service/twin-space/twin-space-resolver.js +36 -7
  238. package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
  239. package/dist-server/service/twin-structure/twin-structure.js +3 -3
  240. package/dist-server/service/twin-structure/twin-structure.js.map +1 -1
  241. package/dist-server/service/twin-subject/event-subjects.d.ts +43 -0
  242. package/dist-server/service/twin-subject/event-subjects.js +137 -0
  243. package/dist-server/service/twin-subject/event-subjects.js.map +1 -0
  244. package/dist-server/service/twin-subject/index.d.ts +9 -0
  245. package/dist-server/service/twin-subject/index.js +16 -0
  246. package/dist-server/service/twin-subject/index.js.map +1 -0
  247. package/dist-server/service/twin-subject/item-resolver.d.ts +10 -0
  248. package/dist-server/service/twin-subject/item-resolver.js +93 -0
  249. package/dist-server/service/twin-subject/item-resolver.js.map +1 -0
  250. package/dist-server/service/twin-subject/subject-query-shape.d.ts +22 -0
  251. package/dist-server/service/twin-subject/subject-query-shape.js +88 -0
  252. package/dist-server/service/twin-subject/subject-query-shape.js.map +1 -0
  253. package/dist-server/service/twin-subject/twin-subject-backfill.d.ts +23 -0
  254. package/dist-server/service/twin-subject/twin-subject-backfill.js +159 -0
  255. package/dist-server/service/twin-subject/twin-subject-backfill.js.map +1 -0
  256. package/dist-server/service/twin-subject/twin-subject-event.d.ts +14 -0
  257. package/dist-server/service/twin-subject/twin-subject-event.js +111 -0
  258. package/dist-server/service/twin-subject/twin-subject-event.js.map +1 -0
  259. package/dist-server/service/twin-subject/twin-subject-fast-path.d.ts +39 -0
  260. package/dist-server/service/twin-subject/twin-subject-fast-path.js +295 -0
  261. package/dist-server/service/twin-subject/twin-subject-fast-path.js.map +1 -0
  262. package/dist-server/service/twin-subject/twin-subject-query.d.ts +27 -0
  263. package/dist-server/service/twin-subject/twin-subject-query.js +232 -0
  264. package/dist-server/service/twin-subject/twin-subject-query.js.map +1 -0
  265. package/dist-server/service/twin-subject/twin-subject-writer.d.ts +27 -0
  266. package/dist-server/service/twin-subject/twin-subject-writer.js +166 -0
  267. package/dist-server/service/twin-subject/twin-subject-writer.js.map +1 -0
  268. package/dist-server/service/twin-target/twin-target-resolver.js +1 -1
  269. package/dist-server/service/twin-target/twin-target-resolver.js.map +1 -1
  270. package/dist-shared/entity-delta.d.ts +1 -1
  271. package/dist-shared/entity-delta.js +3 -3
  272. package/dist-shared/entity-delta.js.map +1 -1
  273. package/dist-shared/touched-items.js +2 -2
  274. package/dist-shared/touched-items.js.map +1 -1
  275. package/dist-shared/twin-level.js +1 -1
  276. package/dist-shared/twin-level.js.map +1 -1
  277. package/package.json +3 -3
  278. package/server/engine/attention-digest.ts +9 -9
  279. package/server/engine/canonical-ingest.ts +170 -13
  280. package/server/engine/command-routing.ts +1 -1
  281. package/server/engine/declared-stimulus.ts +1 -1
  282. package/server/engine/energy-topology.ts +1 -1
  283. package/server/engine/ingest-dedupe.ts +149 -0
  284. package/server/engine/ingest-health.ts +234 -20
  285. package/server/engine/integration-probes.ts +7 -7
  286. package/server/engine/kpi-baseline.ts +4 -4
  287. package/server/engine/kpi-fold.ts +395 -27
  288. package/server/engine/kpi-query.ts +212 -46
  289. package/server/engine/live-feed-registry.ts +1 -1
  290. package/server/engine/load-meter.ts +1 -1
  291. package/server/engine/local-declarations.ts +13 -13
  292. package/server/engine/log.ts +1 -1
  293. package/server/engine/measured-estimator.ts +1 -1
  294. package/server/engine/measured-yield.ts +1 -1
  295. package/server/engine/model-basis.ts +2 -2
  296. package/server/engine/model-gap.ts +3 -3
  297. package/server/engine/model-vocabulary.ts +1 -1
  298. package/server/engine/oee-accumulator.ts +4 -4
  299. package/server/engine/property-effects.ts +2 -2
  300. package/server/engine/read-time.ts +77 -0
  301. package/server/engine/restart-policy.ts +1 -1
  302. package/server/engine/runtime-key.ts +1 -1
  303. package/server/engine/spec-coverage.ts +3 -3
  304. package/server/engine/stage-path.ts +5 -5
  305. package/server/engine/state-axes.ts +2 -2
  306. package/server/engine/travel-estimator.ts +2 -2
  307. package/server/engine/twin-engine.ts +764 -201
  308. package/server/engine/warm-start.ts +6 -6
  309. package/server/index.ts +1 -1
  310. package/server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts +1 -1
  311. package/server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts +3 -3
  312. package/server/migrations/1786200000000-CarryLiveFeedCursor.ts +2 -2
  313. package/server/migrations/1786300000000-IndexEventTypeByTime.ts +48 -0
  314. package/server/migrations/1786400000000-IndexEventCreatedAt.ts +38 -0
  315. package/server/migrations/1786500000000-CreateTwinSubjectEvents.ts +93 -0
  316. package/server/migrations/index.ts +7 -1
  317. package/server/routes.ts +68 -5
  318. package/server/service/index.ts +4 -1
  319. package/server/service/reference/control-routing.ts +1 -1
  320. package/server/service/reference/discovery-result.ts +3 -3
  321. package/server/service/reference/hook-contract.ts +103 -0
  322. package/server/service/reference/ingest-space.ts +4 -4
  323. package/server/service/reference/knob-defaults.ts +4 -4
  324. package/server/service/reference/reference-adapter.ts +166 -9
  325. package/server/service/reference/reference-hook.ts +145 -0
  326. package/server/service/reference/reference-live.ts +92 -14
  327. package/server/service/reference/reference-master.ts +39 -17
  328. package/server/service/reference/reference-resolver.ts +10 -10
  329. package/server/service/reference/twin-reference.ts +3 -3
  330. package/server/service/twin-attention/twin-attention-query.ts +1 -1
  331. package/server/service/twin-audit/command-audit.ts +1 -1
  332. package/server/service/twin-audit/twin-audit-event.ts +2 -2
  333. package/server/service/twin-audit/twin-audit-query.ts +1 -1
  334. package/server/service/twin-control/twin-control-mutation.ts +1 -1
  335. package/server/service/twin-event/twin-event-keys.ts +37 -5
  336. package/server/service/twin-event/twin-event-type.ts +18 -1
  337. package/server/service/twin-event/twin-event.ts +32 -6
  338. package/server/service/twin-forecast/forecast-metrics.ts +1 -1
  339. package/server/service/twin-forecast/gap-analytics.ts +1 -1
  340. package/server/service/twin-forecast/twin-forecast-query.ts +30 -11
  341. package/server/service/twin-ingest-window/twin-ingest-window-query.ts +1 -1
  342. package/server/service/twin-ingest-window/twin-ingest-window-writer.ts +1 -1
  343. package/server/service/twin-instance/twin-instance.ts +4 -4
  344. package/server/service/twin-journal/event-type-split-shape.ts +112 -0
  345. package/server/service/twin-journal/event-type-split.ts +144 -0
  346. package/server/service/twin-journal/journal-order.ts +20 -0
  347. package/server/service/twin-journal/twin-journal-query.ts +224 -20
  348. package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +57 -3
  349. package/server/service/twin-metrics/twin-metrics-query.ts +14 -1
  350. package/server/service/twin-model/axis-journal-evidence.ts +1 -1
  351. package/server/service/twin-model/epcis-coverage.ts +36 -1
  352. package/server/service/twin-model/iec61850-coverage.ts +1 -1
  353. package/server/service/twin-model/isa95-coverage.ts +45 -8
  354. package/server/service/twin-model/item-ref.ts +2 -2
  355. package/server/service/twin-model/name-index.ts +1 -1
  356. package/server/service/twin-model/project-structure.ts +4 -4
  357. package/server/service/twin-model/twin-equipment.ts +2 -2
  358. package/server/service/twin-model/twin-lineage-query.ts +16 -5
  359. package/server/service/twin-model/twin-location.ts +1 -1
  360. package/server/service/twin-model/twin-model-item-query.ts +14 -14
  361. package/server/service/twin-model/twin-model-mutation.ts +4 -4
  362. package/server/service/twin-model/twin-model-query.ts +117 -28
  363. package/server/service/twin-model/twin-model-tree-query.ts +25 -1
  364. package/server/service/twin-model/twin-operation.ts +2 -2
  365. package/server/service/twin-space/move-space.ts +3 -3
  366. package/server/service/twin-space/representation-role.ts +15 -0
  367. package/server/service/twin-space/space-integrity.ts +1 -1
  368. package/server/service/twin-space/twin-space-representation.ts +37 -0
  369. package/server/service/twin-space/twin-space-resolver.ts +39 -7
  370. package/server/service/twin-structure/twin-structure.ts +3 -3
  371. package/server/service/twin-subject/event-subjects.ts +155 -0
  372. package/server/service/twin-subject/index.ts +10 -0
  373. package/server/service/twin-subject/item-resolver.ts +92 -0
  374. package/server/service/twin-subject/subject-query-shape.ts +84 -0
  375. package/server/service/twin-subject/twin-subject-backfill.ts +196 -0
  376. package/server/service/twin-subject/twin-subject-event.ts +101 -0
  377. package/server/service/twin-subject/twin-subject-fast-path.ts +346 -0
  378. package/server/service/twin-subject/twin-subject-query.ts +210 -0
  379. package/server/service/twin-subject/twin-subject-writer.ts +189 -0
  380. package/server/service/twin-target/twin-target-resolver.ts +1 -1
  381. package/shared/entity-delta.ts +3 -3
  382. package/shared/touched-items.ts +2 -2
  383. package/shared/twin-level.ts +1 -1
  384. package/test/adopt-structure-live.test.ts +3 -3
  385. package/test/aggregate-not-findone.test.ts +112 -0
  386. package/test/attention-digest.test.ts +6 -6
  387. package/test/axis-read.test.ts +2 -2
  388. package/test/boot-resume.test.ts +20 -7
  389. package/test/broadcast-period.test.ts +2 -2
  390. package/test/broadcast-queue-overflow.test.ts +244 -0
  391. package/test/canonical-ingest-vocabularies.test.ts +179 -7
  392. package/test/canonical-quantity-door.test.ts +2 -2
  393. package/test/checkpoint-refuses-empty.test.ts +2 -2
  394. package/test/connector-capability-declaration.test.ts +88 -0
  395. package/test/control-capability.test.ts +1 -1
  396. package/test/cursor-stall-not-read-failure.test.ts +2 -2
  397. package/test/declaration-reaches-model.test.ts +5 -5
  398. package/test/declared-stimulus.test.ts +4 -4
  399. package/test/discovery-result.test.ts +2 -2
  400. package/test/entity-delta.test.ts +4 -4
  401. package/test/event-subjects.test.ts +147 -0
  402. package/test/event-time-column.test.ts +3 -3
  403. package/test/event-type-split.test.ts +155 -0
  404. package/test/fold-gap-speaks.test.ts +53 -0
  405. package/test/forecast-metrics.test.ts +1 -1
  406. package/test/forecast-tuning.test.ts +1 -1
  407. package/test/ingest-health-engine.test.ts +2 -2
  408. package/test/ingest-health-wiring.test.ts +7 -5
  409. package/test/ingest-health.test.ts +6 -6
  410. package/test/ingest-history.test.ts +2 -2
  411. package/test/ingest-idempotent.test.ts +132 -0
  412. package/test/ingest-space.test.ts +3 -3
  413. package/test/integration-probes.test.ts +3 -3
  414. package/test/integration-runner.test.ts +1 -1
  415. package/test/item-ref.test.ts +1 -1
  416. package/test/journal-order-axis.test.ts +80 -0
  417. package/test/journal-read-discipline.test.ts +16 -6
  418. package/test/journal-retention.test.ts +171 -29
  419. package/test/journal-sort-axis.test.ts +1 -1
  420. package/test/journal-write-door.test.ts +3 -3
  421. package/test/journal-write-trend.test.ts +1 -1
  422. package/test/kernel-kind-guard.test.ts +3 -3
  423. package/test/knob-defaults.test.ts +1 -1
  424. package/test/kpi-baseline-db.test.ts +2 -2
  425. package/test/kpi-fold.test.ts +185 -14
  426. package/test/kpi-query-bench.test.ts +3 -3
  427. package/test/live-cursor-wiring.test.ts +6 -6
  428. package/test/live-kernel-facts.test.ts +11 -11
  429. package/test/live-mirror-parity.test.ts +3 -3
  430. package/test/load-meter.test.ts +1 -1
  431. package/test/local-declarations.test.ts +10 -10
  432. package/test/log-stamp.test.ts +1 -1
  433. package/test/measured-yield.test.ts +2 -2
  434. package/test/merge-streams.test.ts +70 -0
  435. package/test/mirror-resumes-from-checkpoint.test.ts +13 -13
  436. package/test/model-basis.test.ts +2 -2
  437. package/test/model-gap.test.ts +4 -4
  438. package/test/model-vocabulary.test.ts +2 -2
  439. package/test/move-space.test.ts +1 -1
  440. package/test/oee-accumulator.test.ts +2 -2
  441. package/test/operational-vocabulary.test.ts +2 -2
  442. package/test/project-structure-db.test.ts +2 -2
  443. package/test/projection-reaches-screen.test.ts +2 -2
  444. package/test/property-effects.test.ts +3 -3
  445. package/test/read-failure-ledger.test.ts +106 -0
  446. package/test/read-failure-visible.test.ts +7 -7
  447. package/test/read-time.test.ts +118 -0
  448. package/test/reference-grounding.test.ts +1 -1
  449. package/test/reference-hook.test.ts +86 -0
  450. package/test/registry-key-guard.test.ts +1 -1
  451. package/test/rename-twin.test.ts +81 -0
  452. package/test/restart-policy.test.ts +3 -3
  453. package/test/resync-origin-site.test.ts +3 -3
  454. package/test/revision-axis.test.ts +14 -8
  455. package/test/runtime-key.test.ts +3 -3
  456. package/test/snapshot-freshness.test.ts +4 -4
  457. package/test/source-outcome-audit.test.ts +1 -1
  458. package/test/space-integrity.test.ts +1 -1
  459. package/test/space-representation.test.ts +66 -0
  460. package/test/space-scope-includes-stopped.test.ts +55 -0
  461. package/test/spec-coverage.test.ts +1 -1
  462. package/test/standard-coverage.test.ts +43 -1
  463. package/test/status-tally.test.ts +1 -1
  464. package/test/structure-revision-db.test.ts +7 -7
  465. package/test/subject-fast-path.test.ts +148 -0
  466. package/test/subject-read-model.test.ts +175 -0
  467. package/test/touched-items.test.ts +2 -2
  468. package/test/twin-audit.test.ts +1 -1
  469. package/test/twin-event-keys.test.ts +49 -3
  470. package/test/twin-model-item-db.test.ts +5 -5
  471. package/test/twin-model-tree-db.test.ts +35 -2
  472. package/test/twin-origin-resync.test.ts +1 -1
  473. package/test/warm-start-seam.test.ts +1 -1
  474. package/test/warm-start.test.ts +6 -6
  475. package/test/withheld-door.test.ts +95 -0
  476. package/test/yield-loop.test.ts +4 -4
  477. package/tsconfig.shared.tsbuildinfo +1 -1
  478. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,10 @@
1
+ import type { ItemResolver } from './event-subjects.js';
2
+ /**
3
+ * 그 트윈의 품목 판정 함수 — 선언이 없으면 `undefined`(그때는 품목 줄을 만들지 않는다).
4
+ *
5
+ * 선언을 모르는 트윈에서 품목을 지어내지 않는다. 개체 줄(`lot`)은 그대로 적히므로 이력 조회는 로트로
6
+ * 답할 수 있고, 품목 축만 비어 있다. 그 사실을 조회하는 쪽이 말한다.
7
+ */
8
+ export declare function itemResolverFor(domainId: string, instanceId: string, model: any): ItemResolver | undefined;
9
+ /** 시험이 쓰는 초기화 — 프로세스 안의 기억이므로 시험마다 비운다. */
10
+ export declare function resetItemResolverMemo(): void;
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.itemResolverFor = itemResolverFor;
4
+ exports.resetItemResolverMemo = resetItemResolverMemo;
5
+ const item_ref_js_1 = require("../twin-model/item-ref.js");
6
+ /*
7
+ * **개체 식별자 → 선언된 품목** — 대상별 사건 목록에 품목 줄을 적기 위한 것.
8
+ *
9
+ * ── 왜 문자열을 자르지 않나 ─────────────────────────────────────────────────
10
+ * 저장된 식별자에 품목과 로트가 붙어 있다.
11
+ *
12
+ * https://seunghwa.net/class/product-M0000438-lot-L2403090386
13
+ *
14
+ * 여기서 품목을 꺼내려면 `-lot-` 이 로트를 가리킨다는 것을 알아야 하는데, 그 부호는 연결된 시스템마다
15
+ * 다르고 커널 계약이 정한 적이 없다. 한 시스템의 관습을 규칙으로 굳히면 이 코드가 그 시스템 위에 서게
16
+ * 된다(`item-ref.ts` 가 같은 판단을 적어 두었다).
17
+ *
18
+ * 그래서 **그 트윈의 선언과 견주어** 판정한다. 선언된 품목 중 이 식별자가 가리키는 것을 찾고, 후보가
19
+ * 둘 이상이면 답하지 않는다 — 짐작으로 이으면 남의 품목 이력에 이 사건이 섞인다.
20
+ *
21
+ * ── 왜 기억해 두나 ──────────────────────────────────────────────────────────
22
+ * 사건마다 부르는 함수다(초당 130건). 같은 개체 식별자가 계속 되풀어 오므로, 한 번 판정한 것은 기억해
23
+ * 둔다. 트윈의 선언이 바뀌면 그 기억을 버려야 하므로 **선언의 개수와 첫 항목으로 세대**를 만든다 —
24
+ * 선언이 바뀌면 세대가 달라져 다시 판정한다.
25
+ */
26
+ /** 트윈마다 기억해 둔 판정 — 세대가 바뀌면 버린다. */
27
+ const memo = new Map();
28
+ /** 한 트윈이 기억할 개체 식별자 수 상한 — 넘으면 비우고 다시 채운다(무한히 자라지 않게). */
29
+ const MEMO_LIMIT = 20_000;
30
+ /**
31
+ * 선언된 품목의 식별자들을 모은다 — 트윈 모델이 말하는 것만.
32
+ *
33
+ * `materialDefinitions` 가 이 트윈이 다루는 품목의 선언이다. 그 항목의 `id` 와, 연결된 시스템의
34
+ * 어휘로 이어 둔 값(`binding`)을 함께 후보로 둔다 — 개체 식별자는 후자를 앞부분으로 갖는다.
35
+ */
36
+ function declaredItems(model) {
37
+ const out = new Map();
38
+ const defs = model?.productionSpec?.materialDefinitions ?? model?.materialDefinitions;
39
+ if (!Array.isArray(defs))
40
+ return out;
41
+ for (const d of defs) {
42
+ const id = String(d?.id ?? '').trim();
43
+ if (!id)
44
+ continue;
45
+ out.set(id, id);
46
+ const binding = d?.binding;
47
+ const bound = typeof binding === 'string' ? binding : binding?.epcClass ?? binding?.id;
48
+ const b = String(bound ?? '').trim();
49
+ if (b)
50
+ out.set(b, id);
51
+ }
52
+ return out;
53
+ }
54
+ /** 선언이 바뀌었는지 값싸게 가리는 표식 — 개수와 첫·끝 항목이면 실제 변경을 잡는다. */
55
+ function generationOf(declared) {
56
+ const keys = [...declared.keys()];
57
+ return `${keys.length}:${keys[0] ?? ''}:${keys[keys.length - 1] ?? ''}`;
58
+ }
59
+ /**
60
+ * 그 트윈의 품목 판정 함수 — 선언이 없으면 `undefined`(그때는 품목 줄을 만들지 않는다).
61
+ *
62
+ * 선언을 모르는 트윈에서 품목을 지어내지 않는다. 개체 줄(`lot`)은 그대로 적히므로 이력 조회는 로트로
63
+ * 답할 수 있고, 품목 축만 비어 있다. 그 사실을 조회하는 쪽이 말한다.
64
+ */
65
+ function itemResolverFor(domainId, instanceId, model) {
66
+ const declared = declaredItems(model);
67
+ if (!declared.size)
68
+ return undefined;
69
+ const key = `${domainId}:${instanceId}`;
70
+ const generation = generationOf(declared);
71
+ let entry = memo.get(key);
72
+ if (!entry || entry.generation !== generation) {
73
+ entry = { generation, declared, seen: new Map() };
74
+ memo.set(key, entry);
75
+ }
76
+ const state = entry;
77
+ return (epc) => {
78
+ const hit = state.seen.get(epc);
79
+ if (hit !== undefined)
80
+ return hit;
81
+ /* 판정은 선언이 한다 — 후보가 둘 이상이면 `undefined` 를 내고, 그것을 「모른다」로 기억한다. */
82
+ const found = (0, item_ref_js_1.resolveByDeclaration)((0, item_ref_js_1.itemRefOf)(epc), state.declared) ?? null;
83
+ if (state.seen.size >= MEMO_LIMIT)
84
+ state.seen.clear();
85
+ state.seen.set(epc, found);
86
+ return found;
87
+ };
88
+ }
89
+ /** 시험이 쓰는 초기화 — 프로세스 안의 기억이므로 시험마다 비운다. */
90
+ function resetItemResolverMemo() {
91
+ memo.clear();
92
+ }
93
+ //# sourceMappingURL=item-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"item-resolver.js","sourceRoot":"","sources":["../../../server/service/twin-subject/item-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import { itemRefOf, resolveByDeclaration } from '../twin-model/item-ref.js'\nimport type { ItemResolver } from './event-subjects.js'\n\n/*\n * **개체 식별자 → 선언된 품목** — 대상별 사건 목록에 품목 줄을 적기 위한 것.\n *\n * ── 왜 문자열을 자르지 않나 ─────────────────────────────────────────────────\n * 저장된 식별자에 품목과 로트가 붙어 있다.\n *\n * https://seunghwa.net/class/product-M0000438-lot-L2403090386\n *\n * 여기서 품목을 꺼내려면 `-lot-` 이 로트를 가리킨다는 것을 알아야 하는데, 그 부호는 연결된 시스템마다\n * 다르고 커널 계약이 정한 적이 없다. 한 시스템의 관습을 규칙으로 굳히면 이 코드가 그 시스템 위에 서게\n * 된다(`item-ref.ts` 가 같은 판단을 적어 두었다).\n *\n * 그래서 **그 트윈의 선언과 견주어** 판정한다. 선언된 품목 중 이 식별자가 가리키는 것을 찾고, 후보가\n * 둘 이상이면 답하지 않는다 — 짐작으로 이으면 남의 품목 이력에 이 사건이 섞인다.\n *\n * ── 왜 기억해 두나 ──────────────────────────────────────────────────────────\n * 사건마다 부르는 함수다(초당 130건). 같은 개체 식별자가 계속 되풀어 오므로, 한 번 판정한 것은 기억해\n * 둔다. 트윈의 선언이 바뀌면 그 기억을 버려야 하므로 **선언의 개수와 첫 항목으로 세대**를 만든다 —\n * 선언이 바뀌면 세대가 달라져 다시 판정한다.\n */\n\n/** 트윈마다 기억해 둔 판정 — 세대가 바뀌면 버린다. */\nconst memo = new Map<string, { generation: string; declared: Map<string, string>; seen: Map<string, string | null> }>()\n\n/** 한 트윈이 기억할 개체 식별자 수 상한 — 넘으면 비우고 다시 채운다(무한히 자라지 않게). */\nconst MEMO_LIMIT = 20_000\n\n/**\n * 선언된 품목의 식별자들을 모은다 — 트윈 모델이 말하는 것만.\n *\n * `materialDefinitions` 가 이 트윈이 다루는 품목의 선언이다. 그 항목의 `id` 와, 연결된 시스템의\n * 어휘로 이어 둔 값(`binding`)을 함께 후보로 둔다 — 개체 식별자는 후자를 앞부분으로 갖는다.\n */\nfunction declaredItems(model: any): Map<string, string> {\n const out = new Map<string, string>()\n const defs = model?.productionSpec?.materialDefinitions ?? model?.materialDefinitions\n if (!Array.isArray(defs)) return out\n for (const d of defs) {\n const id = String(d?.id ?? '').trim()\n if (!id) continue\n out.set(id, id)\n const binding = d?.binding\n const bound = typeof binding === 'string' ? binding : binding?.epcClass ?? binding?.id\n const b = String(bound ?? '').trim()\n if (b) out.set(b, id)\n }\n return out\n}\n\n/** 선언이 바뀌었는지 값싸게 가리는 표식 — 개수와 첫·끝 항목이면 실제 변경을 잡는다. */\nfunction generationOf(declared: Map<string, string>): string {\n const keys = [...declared.keys()]\n return `${keys.length}:${keys[0] ?? ''}:${keys[keys.length - 1] ?? ''}`\n}\n\n/**\n * 그 트윈의 품목 판정 함수 — 선언이 없으면 `undefined`(그때는 품목 줄을 만들지 않는다).\n *\n * 선언을 모르는 트윈에서 품목을 지어내지 않는다. 개체 줄(`lot`)은 그대로 적히므로 이력 조회는 로트로\n * 답할 수 있고, 품목 축만 비어 있다. 그 사실을 조회하는 쪽이 말한다.\n */\nexport function itemResolverFor(domainId: string, instanceId: string, model: any): ItemResolver | undefined {\n const declared = declaredItems(model)\n if (!declared.size) return undefined\n\n const key = `${domainId}:${instanceId}`\n const generation = generationOf(declared)\n let entry = memo.get(key)\n if (!entry || entry.generation !== generation) {\n entry = { generation, declared, seen: new Map() }\n memo.set(key, entry)\n }\n const state = entry\n\n return (epc: string) => {\n const hit = state.seen.get(epc)\n if (hit !== undefined) return hit\n /* 판정은 선언이 한다 — 후보가 둘 이상이면 `undefined` 를 내고, 그것을 「모른다」로 기억한다. */\n const found = resolveByDeclaration(itemRefOf(epc), state.declared) ?? null\n if (state.seen.size >= MEMO_LIMIT) state.seen.clear()\n state.seen.set(epc, found)\n return found\n }\n}\n\n/** 시험이 쓰는 초기화 — 프로세스 안의 기억이므로 시험마다 비운다. */\nexport function resetItemResolverMemo(): void {\n memo.clear()\n}\n"]}
@@ -0,0 +1,22 @@
1
+ import type { SubjectKind } from './event-subjects.js';
2
+ /** 화면이 저널에 묻던 축 → 파생 테이블의 대상 종류. */
3
+ export declare const AXIS_KIND: Record<string, SubjectKind>;
4
+ export interface SubjectQueryShape {
5
+ kind: SubjectKind;
6
+ /** 화면이 건넨 축 값 — 짧게 줄어 있을 수 있다(되돌리는 일은 조회하는 쪽이 한다). */
7
+ value: string;
8
+ untilTime?: Date;
9
+ fromTime?: Date;
10
+ }
11
+ /**
12
+ * 목록 계약의 조건을 읽어 「대상 하나의 시각순 이력」인지 가린다 — 아니면 `null`.
13
+ *
14
+ * 받는 모양
15
+ * 축 하나(품목·오더·자리·설비)를 `eq` 또는 `search` 로 · 시각 상한이나 구간 · 시각 내림차순 정렬
16
+ *
17
+ * 보내는 모양(저널로)
18
+ * 자유 검색어 · 종류 필터 · 상관 식별자 · 축 둘 이상 · 그 밖의 정렬이나 연산자
19
+ */
20
+ export declare function readModelShape(params: any, sortings: any[]): SubjectQueryShape | null;
21
+ /** 값을 날짜로 — 못 읽으면 `undefined`(지어내지 않는다). */
22
+ export declare function toDate(v: unknown): Date | undefined;
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AXIS_KIND = void 0;
4
+ exports.readModelShape = readModelShape;
5
+ exports.toDate = toDate;
6
+ /*
7
+ * **이 물음을 파생 테이블로 답할 수 있나** — 순수 판단만 여기 둔다.
8
+ *
9
+ * 데이터베이스도 커널도 부르지 않는다. 그래서 시험이 이 파일을 그대로 부를 수 있다(데코레이터가 없다).
10
+ * 판단이 조회 코드 안에 섞여 있으면 그 판단만 확인할 방법이 없고, 그러면 「어떤 물음이 빠른 경로로
11
+ * 가는가」가 아무도 읽지 못하는 규칙이 된다.
12
+ *
13
+ * 좁게 잡는다. 파생 테이블은 「대상 하나를 시각순으로」에 맞춰 생겼고, 그 모양이 아닌 물음에 억지로
14
+ * 답하면 목록이 알리지 않고 달라진다. 맞지 않으면 `null` 이고, 부르는 쪽은 저널로 간다.
15
+ */
16
+ /** 화면이 저널에 묻던 축 → 파생 테이블의 대상 종류. */
17
+ exports.AXIS_KIND = {
18
+ epc: 'item',
19
+ orderId: 'order',
20
+ locationId: 'location',
21
+ moverId: 'equipment' // vocabulary-guard: allow — 저널의 승격 컬럼 이름(저장된 계약)
22
+ };
23
+ /** 이 경로가 다룰 수 있는 필터 이름 — 이 밖의 필터가 하나라도 있으면 저널로 보낸다. */
24
+ const ALLOWED_FILTERS = new Set(Object.keys(exports.AXIS_KIND).concat('eventTime'));
25
+ /**
26
+ * 목록 계약의 조건을 읽어 「대상 하나의 시각순 이력」인지 가린다 — 아니면 `null`.
27
+ *
28
+ * 받는 모양
29
+ * 축 하나(품목·오더·자리·설비)를 `eq` 또는 `search` 로 · 시각 상한이나 구간 · 시각 내림차순 정렬
30
+ *
31
+ * 보내는 모양(저널로)
32
+ * 자유 검색어 · 종류 필터 · 상관 식별자 · 축 둘 이상 · 그 밖의 정렬이나 연산자
33
+ */
34
+ function readModelShape(params, sortings) {
35
+ const filters = Array.isArray(params?.filters) ? params.filters : [];
36
+ if (typeof params?.search === 'string' && params.search.trim())
37
+ return null;
38
+ for (const f of filters)
39
+ if (!ALLOWED_FILTERS.has(f?.name))
40
+ return null;
41
+ /* 정렬은 시각 내림차순 하나일 때만 — 파생 테이블의 목차가 그 축이다. */
42
+ if (sortings.length !== 1 || sortings[0]?.name !== 'eventTime' || sortings[0]?.desc !== true)
43
+ return null;
44
+ const axisFilters = filters.filter(f => f.name !== 'eventTime');
45
+ if (axisFilters.length !== 1)
46
+ return null;
47
+ const axis = axisFilters[0];
48
+ const kind = exports.AXIS_KIND[axis.name];
49
+ if (!kind)
50
+ return null;
51
+ /* 축은 값 하나여야 한다 — 여러 값(`in`)이면 목차를 한 번에 타지 못한다. */
52
+ if (axis.operator !== 'eq' && axis.operator !== 'search')
53
+ return null;
54
+ const value = typeof axis.value === 'string' ? axis.value.trim() : '';
55
+ if (!value)
56
+ return null;
57
+ /* 시각은 상한·구간만 받는다(시간여행). 그 밖의 연산자는 뜻이 달라지므로 저널로 보낸다. */
58
+ let untilTime;
59
+ let fromTime;
60
+ for (const f of filters) {
61
+ if (f.name !== 'eventTime')
62
+ continue;
63
+ if (f.operator === 'lte' || f.operator === 'lt') {
64
+ untilTime = toDate(f.value);
65
+ if (!untilTime)
66
+ return null;
67
+ }
68
+ else if (f.operator === 'between' && Array.isArray(f.value)) {
69
+ fromTime = toDate(f.value[0]);
70
+ untilTime = toDate(f.value[1]);
71
+ if (!fromTime || !untilTime)
72
+ return null;
73
+ }
74
+ else
75
+ return null;
76
+ }
77
+ return { kind, value, untilTime, fromTime };
78
+ }
79
+ /** 값을 날짜로 — 못 읽으면 `undefined`(지어내지 않는다). */
80
+ function toDate(v) {
81
+ if (v instanceof Date)
82
+ return Number.isNaN(v.getTime()) ? undefined : v;
83
+ if (typeof v !== 'string' && typeof v !== 'number')
84
+ return undefined;
85
+ const d = new Date(v);
86
+ return Number.isNaN(d.getTime()) ? undefined : d;
87
+ }
88
+ //# sourceMappingURL=subject-query-shape.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subject-query-shape.js","sourceRoot":"","sources":["../../../server/service/twin-subject/subject-query-shape.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SubjectKind } from './event-subjects.js'\n\n/*\n * **이 물음을 파생 테이블로 답할 수 있나** — 순수 판단만 여기 둔다.\n *\n * 데이터베이스도 커널도 부르지 않는다. 그래서 시험이 이 파일을 그대로 부를 수 있다(데코레이터가 없다).\n * 판단이 조회 코드 안에 섞여 있으면 그 판단만 확인할 방법이 없고, 그러면 「어떤 물음이 빠른 경로로\n * 가는가」가 아무도 읽지 못하는 규칙이 된다.\n *\n * 좁게 잡는다. 파생 테이블은 「대상 하나를 시각순으로」에 맞춰 생겼고, 그 모양이 아닌 물음에 억지로\n * 답하면 목록이 알리지 않고 달라진다. 맞지 않으면 `null` 이고, 부르는 쪽은 저널로 간다.\n */\n\n/** 화면이 저널에 묻던 축 → 파생 테이블의 대상 종류. */\nexport const AXIS_KIND: Record<string, SubjectKind> = {\n epc: 'item',\n orderId: 'order',\n locationId: 'location',\n moverId: 'equipment' // vocabulary-guard: allow — 저널의 승격 컬럼 이름(저장된 계약)\n}\n\n/** 이 경로가 다룰 수 있는 필터 이름 — 이 밖의 필터가 하나라도 있으면 저널로 보낸다. */\nconst ALLOWED_FILTERS = new Set(Object.keys(AXIS_KIND).concat('eventTime'))\n\nexport interface SubjectQueryShape {\n kind: SubjectKind\n /** 화면이 건넨 축 값 — 짧게 줄어 있을 수 있다(되돌리는 일은 조회하는 쪽이 한다). */\n value: string\n untilTime?: Date\n fromTime?: Date\n}\n\n/**\n * 목록 계약의 조건을 읽어 「대상 하나의 시각순 이력」인지 가린다 — 아니면 `null`.\n *\n * 받는 모양\n * 축 하나(품목·오더·자리·설비)를 `eq` 또는 `search` 로 · 시각 상한이나 구간 · 시각 내림차순 정렬\n *\n * 보내는 모양(저널로)\n * 자유 검색어 · 종류 필터 · 상관 식별자 · 축 둘 이상 · 그 밖의 정렬이나 연산자\n */\nexport function readModelShape(params: any, sortings: any[]): SubjectQueryShape | null {\n const filters: any[] = Array.isArray(params?.filters) ? params.filters : []\n if (typeof params?.search === 'string' && params.search.trim()) return null\n for (const f of filters) if (!ALLOWED_FILTERS.has(f?.name)) return null\n\n /* 정렬은 시각 내림차순 하나일 때만 — 파생 테이블의 목차가 그 축이다. */\n if (sortings.length !== 1 || sortings[0]?.name !== 'eventTime' || sortings[0]?.desc !== true) return null\n\n const axisFilters = filters.filter(f => f.name !== 'eventTime')\n if (axisFilters.length !== 1) return null\n const axis = axisFilters[0]\n const kind = AXIS_KIND[axis.name]\n if (!kind) return null\n /* 축은 값 하나여야 한다 — 여러 값(`in`)이면 목차를 한 번에 타지 못한다. */\n if (axis.operator !== 'eq' && axis.operator !== 'search') return null\n const value = typeof axis.value === 'string' ? axis.value.trim() : ''\n if (!value) return null\n\n /* 시각은 상한·구간만 받는다(시간여행). 그 밖의 연산자는 뜻이 달라지므로 저널로 보낸다. */\n let untilTime: Date | undefined\n let fromTime: Date | undefined\n for (const f of filters) {\n if (f.name !== 'eventTime') continue\n if (f.operator === 'lte' || f.operator === 'lt') {\n untilTime = toDate(f.value)\n if (!untilTime) return null\n } else if (f.operator === 'between' && Array.isArray(f.value)) {\n fromTime = toDate(f.value[0])\n untilTime = toDate(f.value[1])\n if (!fromTime || !untilTime) return null\n } else return null\n }\n\n return { kind, value, untilTime, fromTime }\n}\n\n/** 값을 날짜로 — 못 읽으면 `undefined`(지어내지 않는다). */\nexport function toDate(v: unknown): Date | undefined {\n if (v instanceof Date) return Number.isNaN(v.getTime()) ? undefined : v\n if (typeof v !== 'string' && typeof v !== 'number') return undefined\n const d = new Date(v as any)\n return Number.isNaN(d.getTime()) ? undefined : d\n}\n"]}
@@ -0,0 +1,23 @@
1
+ export interface BackfillResult {
2
+ instanceId: string;
3
+ /** 읽은 저널 행 수. */
4
+ read: number;
5
+ /** 이 표에 적은 줄 수. */
6
+ written: number;
7
+ /** 여기까지 옮겼다(저널 리비전). */
8
+ upToRevision: number;
9
+ /** 목표 — 채우기를 시작한 순간의 저널 끝. */
10
+ targetRevision: number;
11
+ /** 다 옮겼나. */
12
+ complete: boolean;
13
+ /** 예산을 다 써서 멈췄나. */
14
+ paused: boolean;
15
+ }
16
+ /**
17
+ * 한 트윈의 저장된 사건을 이 표로 옮긴다 — 예산 안에서.
18
+ *
19
+ * 되풀어 부르면 이어서 한다. 넘길 인자가 없다 — 진행 지점을 적어 두기 때문이다.
20
+ */
21
+ export declare function backfillSubjects(domainId: string, instanceId: string, opts?: {
22
+ budgetMs?: number;
23
+ }): Promise<BackfillResult>;
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.backfillSubjects = backfillSubjects;
4
+ const shell_1 = require("@things-factory/shell");
5
+ const log_js_1 = require("../../engine/log.js");
6
+ const twin_event_js_1 = require("../twin-event/twin-event.js");
7
+ const twin_instance_js_1 = require("../twin-instance/twin-instance.js");
8
+ const twin_subject_writer_js_1 = require("./twin-subject-writer.js");
9
+ const item_resolver_js_1 = require("./item-resolver.js");
10
+ /*
11
+ * **이미 저장된 사건을 이 표로 옮긴다** — 요청받은 트윈만.
12
+ *
13
+ * ── 왜 기본이 아닌가 ────────────────────────────────────────────────────────
14
+ * 이 표의 규칙은 「앞으로만 채운다」다. 그래야 새 조회 축이 필요할 때마다 3,128만 줄을 다시 만드는
15
+ * 구조가 되지 않는다(설계: read-models.md §4).
16
+ *
17
+ * 그런데 이미 쌓인 트윈에서 이력을 보려면 그 구간이 있어야 한다. 그래서 **부르면 채우는 도구**로 둔다 —
18
+ * 마이그레이션이 아니고, 부팅에 자동으로 돌지도 않는다. 트윈을 지정해 부른다.
19
+ *
20
+ * ── 어디까지 옮겼는지 적어 둔다 (2026-08-27 · 세 번 틀린 뒤) ─────────────────
21
+ * 저장하지 않고 표의 내용으로 진행 지점을 알아내려 했고 세 번 틀렸다.
22
+ *
23
+ * ① 표의 최대 리비전 뒤부터 앞으로 채워진 줄의 최댓값이 곧 저널의 끝이라 소급을 다 건너뛴다
24
+ * ② 표의 최소 리비전 앞까지 소급이 1부터 적으면 최솟값이 1이 되어 구간이 0으로 접힌다
25
+ * ③ 표의 최소 시각 아래로 사건이 시각순으로 오지 않는 트윈에서 곧바로 끝난다
26
+ *
27
+ * ③이 결정적이었다. 승화푸드 커넥터는 과거 기록을 되읽으므로 방금 들어온 사건의 시각이 2024년 10월이다.
28
+ * 그래서 표의 가장 오래된 시각이 이미 저널의 시작과 같아진다.
29
+ *
30
+ * 원인은 하나다. **채워진 것은 구간이 아니라 집합이다.** 리비전으로 보든 시각으로 보든 「어디까지」를
31
+ * 하나의 경계로 말할 수 없다. 그래서 알아내려 하지 않고 **이 프로세스가 든다**(§`inFlight`).
32
+ *
33
+ * 리비전 1부터 순서대로 올라간다. 목표는 **채우기를 시작한 순간의 저널 끝**이다 — 그보다 뒤는 앞으로
34
+ * 채우는 쪽이 이미 적고 있다.
35
+ *
36
+ * ── 규율 ────────────────────────────────────────────────────────────────────
37
+ * **나눠서 옮긴다.** 한 번에 통째로 읽으면 그동안 호스트가 아무 일도 못 한다(sqlite 는 쓰기가 하나다).
38
+ * 배치 사이에 루프를 비워 준다.
39
+ *
40
+ * **같은 시각의 줄을 반쪽만 적지 않는다.** 한 배치의 가장 오래된 시각에 걸친 줄들은 다음 배치로 넘긴다 —
41
+ * 반쪽만 적으면 그 시각이 「채워진 것」으로 보이면서 일부가 빠진다.
42
+ *
43
+ * **몇 줄을 옮겼고 어디까지 왔는지 말한다.** 알리지 않고 절반만 채우면 화면이 「그 앞에는 사건이 없다」고
44
+ * 말하게 된다.
45
+ */
46
+ /** 한 번에 읽는 저널 행 수 — 저널 정리와 같은 규율. */
47
+ const READ_BATCH = 5_000;
48
+ /**
49
+ * 옮기는 중인 트윈의 목표와 지점 — **이 프로세스 안에서만** 든다.
50
+ *
51
+ * 테이블에 적지 않는다(사용자 판단, 2026-08-27). 서버가 내려가면 사라지고, 다음에 부르면 처음부터
52
+ * 다시 읽는다. 그 대가를 아는 채로 두는 것이다.
53
+ */
54
+ const inFlight = new Map();
55
+ /**
56
+ * 한 트윈의 저장된 사건을 이 표로 옮긴다 — 예산 안에서.
57
+ *
58
+ * 되풀어 부르면 이어서 한다. 넘길 인자가 없다 — 진행 지점을 적어 두기 때문이다.
59
+ */
60
+ async function backfillSubjects(domainId, instanceId, opts = {}) {
61
+ const budgetMs = opts.budgetMs ?? 30_000;
62
+ const startedMs = Date.now();
63
+ const reg = await (0, shell_1.getRepository)(twin_instance_js_1.TwinInstance)
64
+ .findOne({ where: { domain: { id: domainId }, instanceId } })
65
+ .catch(() => null);
66
+ if (!reg?.model) {
67
+ throw new Error(`twin "${instanceId}" is not registered — no model to resolve declared items with`);
68
+ }
69
+ const itemOf = (0, item_resolver_js_1.itemResolverFor)(domainId, instanceId, reg.model);
70
+ if (!itemOf) {
71
+ (0, log_js_1.twinWarn)(`[twin-subject] "${instanceId}" 선언된 품목이 없어 품목 줄은 만들지 않는다 — 개체·자리·설비 줄만 옮긴다.`);
72
+ }
73
+ /*
74
+ * ── 진행 지점을 **판 안에서만** 든다 (2026-08-27 · 사용자 판단) ────────────────
75
+ *
76
+ * 처음에는 테이블에 적었다. 사용자 판단으로 없앴다 — 사건이 들어올 때 파생 테이블도 같이 적히므로,
77
+ * 옛 사건을 옮기는 일은 새 파생 테이블을 만들 때와 계산 규칙을 고쳐 다시 만들 때뿐이다. 그 두 경우가
78
+ * 흔하지 않다.
79
+ *
80
+ * 대가는 이것이다. 옮기는 중에 서버가 내려가면 그 몫을 다시 읽는다. 335만 건이면 20분쯤이다.
81
+ *
82
+ * 그래서 **목표와 지점을 이 프로세스가 든다.** 트윈마다 한 줄이고, 서버가 내려가면 사라진다.
83
+ */
84
+ const events = (0, shell_1.getRepository)(twin_event_js_1.TwinEvent);
85
+ const key = `${domainId}:${instanceId}`;
86
+ let held = inFlight.get(key);
87
+ if (!held) {
88
+ /*
89
+ * 목표는 **처음 부를 때의 저널 끝**이다. 매번 다시 재면 그 사이에 새 사건이 들어와 끝나지 않는다.
90
+ * 그 뒤의 사건은 저널을 쓸 때 파생 테이블에도 같이 적히므로 이 작업의 몫이 아니다.
91
+ */
92
+ const tip = await events
93
+ .createQueryBuilder('e')
94
+ .select('MAX(e.revision)', 'max')
95
+ .where('e.domain = :domainId', { domainId })
96
+ .andWhere('e.instanceId = :instanceId', { instanceId })
97
+ .getRawOne();
98
+ held = { upToRevision: 0, targetRevision: Number(tip?.max ?? 0) };
99
+ inFlight.set(key, held);
100
+ }
101
+ let read = 0;
102
+ let written = 0;
103
+ let paused = false;
104
+ let cursor = held.upToRevision;
105
+ const target = held.targetRevision;
106
+ for (;;) {
107
+ if (cursor >= target)
108
+ break;
109
+ if (Date.now() - startedMs >= budgetMs) {
110
+ paused = true;
111
+ break;
112
+ }
113
+ const rows = await events
114
+ .createQueryBuilder('e')
115
+ .where('e.domain = :domainId', { domainId })
116
+ .andWhere('e.instanceId = :instanceId', { instanceId })
117
+ .andWhere('e.revision > :cursor', { cursor })
118
+ .andWhere('e.revision <= :target', { target })
119
+ .orderBy('e.revision', 'ASC')
120
+ .take(READ_BATCH)
121
+ .getMany();
122
+ if (!rows.length) {
123
+ cursor = target;
124
+ break;
125
+ }
126
+ read += rows.length;
127
+ written += await writeRows(domainId, instanceId, rows, itemOf);
128
+ cursor = Number(rows[rows.length - 1].revision);
129
+ /* 진행 지점을 그때그때 든다 — 중간에 멈춰도 다음 호출이 여기서 잇는다(같은 프로세스 안에서). */
130
+ held.upToRevision = cursor;
131
+ /* 배치 사이에 루프를 비워 준다 — 이 한 줄이 없으면 나눈 뜻이 없다. */
132
+ await new Promise(resolve => setImmediate(resolve));
133
+ }
134
+ held.upToRevision = cursor;
135
+ const complete = cursor >= target;
136
+ /* 다 옮겼으면 들고 있을 이유가 없다 — 다시 부르면 목표를 새로 잡는다(그때는 옮길 것이 없다). */
137
+ if (complete)
138
+ inFlight.delete(key);
139
+ (0, log_js_1.twinLog)(`[twin-subject] backfill "${instanceId}" — 저널 ${read}행을 읽어 ${written}줄 적었다 · ` +
140
+ `리비전 ${cursor}/${target}` +
141
+ `${complete ? ' · 다 옮겼다' : paused ? ' · 예산을 다 써서 멈췄다 — 다시 부르면 이어서 한다' : ''}`);
142
+ return { instanceId, read, written, upToRevision: cursor, targetRevision: target, complete, paused };
143
+ }
144
+ /** 저널 행들을 봉투로 되돌려 적는다 — `payload` 가 봉투 전체다. */
145
+ async function writeRows(domainId, instanceId, rows, itemOf) {
146
+ return await (0, twin_subject_writer_js_1.writeSubjectRows)(domainId, instanceId, rows.map(r => ({
147
+ /*
148
+ * `payload` 가 봉투 전체다(`{ eventId, eventType, eventTime, tenantId, data: {…} }`). 한 겹 더
149
+ * 감싸면 키가 묻혀 아무것도 찾지 못한다 — 실측으로 825,000행을 읽어 0줄을 적었다.
150
+ *
151
+ * 시각과 종류는 승격 컬럼을 믿는다. payload 안의 값과 컬럼이 어긋난 옛 행이 있을 수 있고,
152
+ * 이 표의 정렬 축은 컬럼이 정본이다.
153
+ */
154
+ envelope: { ...(r.payload ?? {}), eventType: r.eventType, eventTime: r.eventTime },
155
+ revision: Number(r.revision),
156
+ eventId: r.id
157
+ })), itemOf);
158
+ }
159
+ //# sourceMappingURL=twin-subject-backfill.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"twin-subject-backfill.js","sourceRoot":"","sources":["../../../server/service/twin-subject/twin-subject-backfill.ts"],"names":[],"mappings":";;AA8EA,4CAgGC;AA9KD,iDAAqD;AAErD,gDAAuD;AACvD,+DAAuD;AACvD,wEAAgE;AAEhE,qEAA2D;AAC3D,yDAAoD;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,qCAAqC;AACrC,MAAM,UAAU,GAAG,KAAK,CAAA;AAkBxB;;;;;GAKG;AACH,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA4D,CAAA;AAEpF;;;;GAIG;AACI,KAAK,UAAU,gBAAgB,CACpC,QAAgB,EAChB,UAAkB,EAClB,OAA8B,EAAE;IAEhC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,MAAM,CAAA;IACxC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IAE5B,MAAM,GAAG,GAAG,MAAM,IAAA,qBAAa,EAAC,+BAAY,CAAC;SAC1C,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,UAAU,EAAE,EAAE,CAAC;SAC5D,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAA;IACpB,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,SAAS,UAAU,+DAA+D,CAAC,CAAA;IACrG,CAAC;IACD,MAAM,MAAM,GAAG,IAAA,kCAAe,EAAC,QAAQ,EAAE,UAAU,EAAE,GAAG,CAAC,KAAK,CAAC,CAAA;IAC/D,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,IAAA,iBAAQ,EACN,mBAAmB,UAAU,+CAA+C,CAC7E,CAAA;IACH,CAAC;IAED;;;;;;;;;;OAUG;IACH,MAAM,MAAM,GAAG,IAAA,qBAAa,EAAC,yBAAS,CAAC,CAAA;IACvC,MAAM,GAAG,GAAG,GAAG,QAAQ,IAAI,UAAU,EAAE,CAAA;IACvC,IAAI,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC5B,IAAI,CAAC,IAAI,EAAE,CAAC;QACV;;;WAGG;QACH,MAAM,GAAG,GAAG,MAAM,MAAM;aACrB,kBAAkB,CAAC,GAAG,CAAC;aACvB,MAAM,CAAC,iBAAiB,EAAE,KAAK,CAAC;aAChC,KAAK,CAAC,sBAAsB,EAAE,EAAE,QAAQ,EAAE,CAAC;aAC3C,QAAQ,CAAC,4BAA4B,EAAE,EAAE,UAAU,EAAE,CAAC;aACtD,SAAS,EAAoB,CAAA;QAChC,IAAI,GAAG,EAAE,YAAY,EAAE,CAAC,EAAE,cAAc,EAAE,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,EAAE,CAAA;QACjE,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;IACzB,CAAC;IAED,IAAI,IAAI,GAAG,CAAC,CAAA;IACZ,IAAI,OAAO,GAAG,CAAC,CAAA;IACf,IAAI,MAAM,GAAG,KAAK,CAAA;IAClB,IAAI,MAAM,GAAG,IAAI,CAAC,YAAY,CAAA;IAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,CAAA;IAElC,SAAS,CAAC;QACR,IAAI,MAAM,IAAI,MAAM;YAAE,MAAK;QAC3B,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,IAAI,QAAQ,EAAE,CAAC;YACvC,MAAM,GAAG,IAAI,CAAA;YACb,MAAK;QACP,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,MAAM;aACtB,kBAAkB,CAAC,GAAG,CAAC;aACvB,KAAK,CAAC,sBAAsB,EAAE,EAAE,QAAQ,EAAE,CAAC;aAC3C,QAAQ,CAAC,4BAA4B,EAAE,EAAE,UAAU,EAAE,CAAC;aACtD,QAAQ,CAAC,sBAAsB,EAAE,EAAE,MAAM,EAAE,CAAC;aAC5C,QAAQ,CAAC,uBAAuB,EAAE,EAAE,MAAM,EAAE,CAAC;aAC7C,OAAO,CAAC,YAAY,EAAE,KAAK,CAAC;aAC5B,IAAI,CAAC,UAAU,CAAC;aAChB,OAAO,EAAE,CAAA;QACZ,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,MAAM,GAAG,MAAM,CAAA;YACf,MAAK;QACP,CAAC;QAED,IAAI,IAAI,IAAI,CAAC,MAAM,CAAA;QACnB,OAAO,IAAI,MAAM,SAAS,CAAC,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,CAAC,CAAA;QAC9D,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAA;QAC/C,2DAA2D;QAC3D,IAAI,CAAC,YAAY,GAAG,MAAM,CAAA;QAC1B,6CAA6C;QAC7C,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAA;IACrD,CAAC;IAED,IAAI,CAAC,YAAY,GAAG,MAAM,CAAA;IAC1B,MAAM,QAAQ,GAAG,MAAM,IAAI,MAAM,CAAA;IACjC,4DAA4D;IAC5D,IAAI,QAAQ;QAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAClC,IAAA,gBAAO,EACL,4BAA4B,UAAU,UAAU,IAAI,SAAS,OAAO,UAAU;QAC5E,OAAO,MAAM,IAAI,MAAM,EAAE;QACzB,GAAG,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,iCAAiC,CAAC,CAAC,CAAC,EAAE,EAAE,CAC/E,CAAA;IAED,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAA;AACtG,CAAC;AAED,+CAA+C;AAC/C,KAAK,UAAU,SAAS,CAAC,QAAgB,EAAE,UAAkB,EAAE,IAAiB,EAAE,MAAY;IAC5F,OAAO,MAAM,IAAA,yCAAgB,EAC3B,QAAQ,EACR,UAAU,EACV,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACb;;;;;;WAMG;QACH,QAAQ,EAAE,EAAE,GAAG,CAAE,CAAS,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE;QAC3F,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;QAC5B,OAAO,EAAE,CAAC,CAAC,EAAE;KACd,CAAC,CAAC,EACH,MAAM,CACP,CAAA;AACH,CAAC","sourcesContent":["import { getRepository } from '@things-factory/shell'\n\nimport { twinLog, twinWarn } from '../../engine/log.js'\nimport { TwinEvent } from '../twin-event/twin-event.js'\nimport { TwinInstance } from '../twin-instance/twin-instance.js'\nimport { TwinSubjectEvent } from './twin-subject-event.js'\nimport { writeSubjectRows } from './twin-subject-writer.js'\nimport { itemResolverFor } from './item-resolver.js'\n\n/*\n * **이미 저장된 사건을 이 표로 옮긴다** — 요청받은 트윈만.\n *\n * ── 왜 기본이 아닌가 ────────────────────────────────────────────────────────\n * 이 표의 규칙은 「앞으로만 채운다」다. 그래야 새 조회 축이 필요할 때마다 3,128만 줄을 다시 만드는\n * 구조가 되지 않는다(설계: read-models.md §4).\n *\n * 그런데 이미 쌓인 트윈에서 이력을 보려면 그 구간이 있어야 한다. 그래서 **부르면 채우는 도구**로 둔다 —\n * 마이그레이션이 아니고, 부팅에 자동으로 돌지도 않는다. 트윈을 지정해 부른다.\n *\n * ── 어디까지 옮겼는지 적어 둔다 (2026-08-27 · 세 번 틀린 뒤) ─────────────────\n * 저장하지 않고 표의 내용으로 진행 지점을 알아내려 했고 세 번 틀렸다.\n *\n * ① 표의 최대 리비전 뒤부터 앞으로 채워진 줄의 최댓값이 곧 저널의 끝이라 소급을 다 건너뛴다\n * ② 표의 최소 리비전 앞까지 소급이 1부터 적으면 최솟값이 1이 되어 구간이 0으로 접힌다\n * ③ 표의 최소 시각 아래로 사건이 시각순으로 오지 않는 트윈에서 곧바로 끝난다\n *\n * ③이 결정적이었다. 승화푸드 커넥터는 과거 기록을 되읽으므로 방금 들어온 사건의 시각이 2024년 10월이다.\n * 그래서 표의 가장 오래된 시각이 이미 저널의 시작과 같아진다.\n *\n * 원인은 하나다. **채워진 것은 구간이 아니라 집합이다.** 리비전으로 보든 시각으로 보든 「어디까지」를\n * 하나의 경계로 말할 수 없다. 그래서 알아내려 하지 않고 **이 프로세스가 든다**(§`inFlight`).\n *\n * 리비전 1부터 순서대로 올라간다. 목표는 **채우기를 시작한 순간의 저널 끝**이다 — 그보다 뒤는 앞으로\n * 채우는 쪽이 이미 적고 있다.\n *\n * ── 규율 ────────────────────────────────────────────────────────────────────\n * **나눠서 옮긴다.** 한 번에 통째로 읽으면 그동안 호스트가 아무 일도 못 한다(sqlite 는 쓰기가 하나다).\n * 배치 사이에 루프를 비워 준다.\n *\n * **같은 시각의 줄을 반쪽만 적지 않는다.** 한 배치의 가장 오래된 시각에 걸친 줄들은 다음 배치로 넘긴다 —\n * 반쪽만 적으면 그 시각이 「채워진 것」으로 보이면서 일부가 빠진다.\n *\n * **몇 줄을 옮겼고 어디까지 왔는지 말한다.** 알리지 않고 절반만 채우면 화면이 「그 앞에는 사건이 없다」고\n * 말하게 된다.\n */\n\n/** 한 번에 읽는 저널 행 수 — 저널 정리와 같은 규율. */\nconst READ_BATCH = 5_000\n\nexport interface BackfillResult {\n instanceId: string\n /** 읽은 저널 행 수. */\n read: number\n /** 이 표에 적은 줄 수. */\n written: number\n /** 여기까지 옮겼다(저널 리비전). */\n upToRevision: number\n /** 목표 — 채우기를 시작한 순간의 저널 끝. */\n targetRevision: number\n /** 다 옮겼나. */\n complete: boolean\n /** 예산을 다 써서 멈췄나. */\n paused: boolean\n}\n\n/**\n * 옮기는 중인 트윈의 목표와 지점 — **이 프로세스 안에서만** 든다.\n *\n * 테이블에 적지 않는다(사용자 판단, 2026-08-27). 서버가 내려가면 사라지고, 다음에 부르면 처음부터\n * 다시 읽는다. 그 대가를 아는 채로 두는 것이다.\n */\nconst inFlight = new Map<string, { upToRevision: number; targetRevision: number }>()\n\n/**\n * 한 트윈의 저장된 사건을 이 표로 옮긴다 — 예산 안에서.\n *\n * 되풀어 부르면 이어서 한다. 넘길 인자가 없다 — 진행 지점을 적어 두기 때문이다.\n */\nexport async function backfillSubjects(\n domainId: string,\n instanceId: string,\n opts: { budgetMs?: number } = {}\n): Promise<BackfillResult> {\n const budgetMs = opts.budgetMs ?? 30_000\n const startedMs = Date.now()\n\n const reg = await getRepository(TwinInstance)\n .findOne({ where: { domain: { id: domainId }, instanceId } })\n .catch(() => null)\n if (!reg?.model) {\n throw new Error(`twin \"${instanceId}\" is not registered — no model to resolve declared items with`)\n }\n const itemOf = itemResolverFor(domainId, instanceId, reg.model)\n if (!itemOf) {\n twinWarn(\n `[twin-subject] \"${instanceId}\" 선언된 품목이 없어 품목 줄은 만들지 않는다 — 개체·자리·설비 줄만 옮긴다.`\n )\n }\n\n /*\n * ── 진행 지점을 **판 안에서만** 든다 (2026-08-27 · 사용자 판단) ────────────────\n *\n * 처음에는 테이블에 적었다. 사용자 판단으로 없앴다 — 사건이 들어올 때 파생 테이블도 같이 적히므로,\n * 옛 사건을 옮기는 일은 새 파생 테이블을 만들 때와 계산 규칙을 고쳐 다시 만들 때뿐이다. 그 두 경우가\n * 흔하지 않다.\n *\n * 대가는 이것이다. 옮기는 중에 서버가 내려가면 그 몫을 다시 읽는다. 335만 건이면 20분쯤이다.\n *\n * 그래서 **목표와 지점을 이 프로세스가 든다.** 트윈마다 한 줄이고, 서버가 내려가면 사라진다.\n */\n const events = getRepository(TwinEvent)\n const key = `${domainId}:${instanceId}`\n let held = inFlight.get(key)\n if (!held) {\n /*\n * 목표는 **처음 부를 때의 저널 끝**이다. 매번 다시 재면 그 사이에 새 사건이 들어와 끝나지 않는다.\n * 그 뒤의 사건은 저널을 쓸 때 파생 테이블에도 같이 적히므로 이 작업의 몫이 아니다.\n */\n const tip = await events\n .createQueryBuilder('e')\n .select('MAX(e.revision)', 'max')\n .where('e.domain = :domainId', { domainId })\n .andWhere('e.instanceId = :instanceId', { instanceId })\n .getRawOne<{ max: unknown }>()\n held = { upToRevision: 0, targetRevision: Number(tip?.max ?? 0) }\n inFlight.set(key, held)\n }\n\n let read = 0\n let written = 0\n let paused = false\n let cursor = held.upToRevision\n const target = held.targetRevision\n\n for (;;) {\n if (cursor >= target) break\n if (Date.now() - startedMs >= budgetMs) {\n paused = true\n break\n }\n const rows = await events\n .createQueryBuilder('e')\n .where('e.domain = :domainId', { domainId })\n .andWhere('e.instanceId = :instanceId', { instanceId })\n .andWhere('e.revision > :cursor', { cursor })\n .andWhere('e.revision <= :target', { target })\n .orderBy('e.revision', 'ASC')\n .take(READ_BATCH)\n .getMany()\n if (!rows.length) {\n cursor = target\n break\n }\n\n read += rows.length\n written += await writeRows(domainId, instanceId, rows, itemOf)\n cursor = Number(rows[rows.length - 1].revision)\n /* 진행 지점을 그때그때 든다 — 중간에 멈춰도 다음 호출이 여기서 잇는다(같은 프로세스 안에서). */\n held.upToRevision = cursor\n /* 배치 사이에 루프를 비워 준다 — 이 한 줄이 없으면 나눈 뜻이 없다. */\n await new Promise(resolve => setImmediate(resolve))\n }\n\n held.upToRevision = cursor\n const complete = cursor >= target\n /* 다 옮겼으면 들고 있을 이유가 없다 — 다시 부르면 목표를 새로 잡는다(그때는 옮길 것이 없다). */\n if (complete) inFlight.delete(key)\n twinLog(\n `[twin-subject] backfill \"${instanceId}\" — 저널 ${read}행을 읽어 ${written}줄 적었다 · ` +\n `리비전 ${cursor}/${target}` +\n `${complete ? ' · 다 옮겼다' : paused ? ' · 예산을 다 써서 멈췄다 — 다시 부르면 이어서 한다' : ''}`\n )\n\n return { instanceId, read, written, upToRevision: cursor, targetRevision: target, complete, paused }\n}\n\n/** 저널 행들을 봉투로 되돌려 적는다 — `payload` 가 봉투 전체다. */\nasync function writeRows(domainId: string, instanceId: string, rows: TwinEvent[], itemOf?: any): Promise<number> {\n return await writeSubjectRows(\n domainId,\n instanceId,\n rows.map(r => ({\n /*\n * `payload` 가 봉투 전체다(`{ eventId, eventType, eventTime, tenantId, data: {…} }`). 한 겹 더\n * 감싸면 키가 묻혀 아무것도 찾지 못한다 — 실측으로 825,000행을 읽어 0줄을 적었다.\n *\n * 시각과 종류는 승격 컬럼을 믿는다. payload 안의 값과 컬럼이 어긋난 옛 행이 있을 수 있고,\n * 이 표의 정렬 축은 컬럼이 정본이다.\n */\n envelope: { ...((r as any).payload ?? {}), eventType: r.eventType, eventTime: r.eventTime },\n revision: Number(r.revision),\n eventId: r.id\n })),\n itemOf\n )\n}\n"]}
@@ -0,0 +1,14 @@
1
+ import { Domain } from '@things-factory/shell';
2
+ export declare class TwinSubjectEvent {
3
+ readonly id: string;
4
+ domain: Domain;
5
+ domainId?: string;
6
+ instanceId: string;
7
+ subjectKind: string;
8
+ subjectId: string;
9
+ eventTime: Date;
10
+ revision: number;
11
+ eventType: string;
12
+ eventId?: string;
13
+ createdAt?: Date;
14
+ }
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TwinSubjectEvent = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const type_graphql_1 = require("type-graphql");
6
+ const typeorm_1 = require("typeorm");
7
+ const shell_1 = require("@things-factory/shell");
8
+ /*
9
+ * **대상별 사건 목록** — 조회용 파생 표. 사실의 원본은 `twin_events` 다.
10
+ *
11
+ * ── 왜 만드나 (2026-08-27 실측) ─────────────────────────────────────────────
12
+ * 360 화면이 「이 대상의 이력」을 묻고, 그 조회가 64초다. 원인은 「무엇으로 걸러 시각순으로 정렬해
13
+ * 앞에서 30개」라는 모양에 맞는 목차가 없다는 것이다. 그리고 목차를 얹는 방식으로는 풀리지 않는다.
14
+ *
15
+ * 정렬이 목차를 무력화한다 여러 값으로 거르면(IN) 합칠 때 다시 정렬해야 한다
16
+ * 축이 늘 때마다 목차가 늘어난다 360 의 축 넷을 다 빠르게 하면 `twin_events` 목차가 13개가 된다
17
+ * 소급 채우기가 매번 든다 목차 하나 만드는 데 3분 36초 · 파일 +2.4GB
18
+ * 품목 축은 그래도 안 풀린다 저장된 식별자에 품목과 로트가 한 문자열로 붙어 있다
19
+ *
20
+ * ── 이 표가 무엇을 담나 ─────────────────────────────────────────────────────
21
+ * 사건 하나가 가리키는 **대상마다 한 줄**이다. 품목 이동 사건이면 품목·로트·자리로 세 줄이다.
22
+ * 종류(`subjectKind`)를 값으로 밝히므로 읽는 쪽이 사건 종류표를 들고 있지 않아도 된다. 뜻이
23
+ * 달라지는 키 칸을 쓰지 않는 이유이고, 그 방식이 틀렸다는 뜻은 아니다 — 정의가 함께 있는
24
+ * 프레임워크에서는 합리적이다. 이 표는 볼 정의가 없다.
25
+ *
26
+ * ── 규칙 넷 (설계: read-models.md §4) ───────────────────────────────────────
27
+ * ① 조회는 이 표가 답한다. `twin_events` 에 목차를 얹어 답하지 않는다
28
+ * ② **앞으로만 채운다.** 소급하지 않는다 — 새 키가 필요할 때 3,128만 줄을 다시 만들지 않는다
29
+ * ③ 준비되지 않은 구간을 화면이 말한다. 비어 있는 것을 「없다」로 그리지 않는다
30
+ * ④ 언제든 버릴 수 있다. 사건에서 파생된 값이므로 지워도 사실이 사라지지 않는다
31
+ *
32
+ * ── 왜 사건 본문을 베끼지 않나 ──────────────────────────────────────────────
33
+ * `eventId` 로 원본을 가리키고, 목록을 그리는 데 필요한 값만 함께 둔다(종류·시각·리비전). 본문까지
34
+ * 베끼면 이 표가 원본만큼 커지고, 원본을 고칠 때 두 곳이 어긋난다.
35
+ */
36
+ let TwinSubjectEvent = class TwinSubjectEvent {
37
+ };
38
+ exports.TwinSubjectEvent = TwinSubjectEvent;
39
+ tslib_1.__decorate([
40
+ (0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
41
+ (0, type_graphql_1.Field)(type => String, { description: 'Row id.' }),
42
+ tslib_1.__metadata("design:type", String)
43
+ ], TwinSubjectEvent.prototype, "id", void 0);
44
+ tslib_1.__decorate([
45
+ (0, typeorm_1.ManyToOne)(type => shell_1.Domain),
46
+ tslib_1.__metadata("design:type", shell_1.Domain)
47
+ ], TwinSubjectEvent.prototype, "domain", void 0);
48
+ tslib_1.__decorate([
49
+ (0, typeorm_1.RelationId)((e) => e.domain),
50
+ tslib_1.__metadata("design:type", String)
51
+ ], TwinSubjectEvent.prototype, "domainId", void 0);
52
+ tslib_1.__decorate([
53
+ (0, typeorm_1.Column)(),
54
+ (0, type_graphql_1.Field)({ description: 'The twin instance this row belongs to.' }),
55
+ tslib_1.__metadata("design:type", String)
56
+ ], TwinSubjectEvent.prototype, "instanceId", void 0);
57
+ tslib_1.__decorate([
58
+ (0, typeorm_1.Column)(),
59
+ (0, type_graphql_1.Field)({
60
+ description: 'What kind of thing this row points at: item, lot, order, location, equipment or task. The kind is stored as a value so a reader never has to know which event types put what where.'
61
+ }),
62
+ tslib_1.__metadata("design:type", String)
63
+ ], TwinSubjectEvent.prototype, "subjectKind", void 0);
64
+ tslib_1.__decorate([
65
+ (0, typeorm_1.Column)(),
66
+ (0, type_graphql_1.Field)({ description: 'Identifier of that thing, within its kind.' }),
67
+ tslib_1.__metadata("design:type", String)
68
+ ], TwinSubjectEvent.prototype, "subjectId", void 0);
69
+ tslib_1.__decorate([
70
+ (0, typeorm_1.Column)(),
71
+ (0, type_graphql_1.Field)({ description: 'When the event happened, by the twin clock. This is the axis the history list is ordered by.' }),
72
+ tslib_1.__metadata("design:type", Date)
73
+ ], TwinSubjectEvent.prototype, "eventTime", void 0);
74
+ tslib_1.__decorate([
75
+ (0, typeorm_1.Column)({ type: 'int' }),
76
+ (0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'Journal revision of the event — ties this row to its place in the record.' }),
77
+ tslib_1.__metadata("design:type", Number)
78
+ ], TwinSubjectEvent.prototype, "revision", void 0);
79
+ tslib_1.__decorate([
80
+ (0, typeorm_1.Column)(),
81
+ (0, type_graphql_1.Field)({ description: 'Event type, so the list can be drawn without reading the journal row.' }),
82
+ tslib_1.__metadata("design:type", String)
83
+ ], TwinSubjectEvent.prototype, "eventType", void 0);
84
+ tslib_1.__decorate([
85
+ (0, typeorm_1.Column)({ nullable: true }),
86
+ (0, type_graphql_1.Field)({ nullable: true, description: 'Journal row this points at. Null when the journal row has been pruned.' }),
87
+ tslib_1.__metadata("design:type", String)
88
+ ], TwinSubjectEvent.prototype, "eventId", void 0);
89
+ tslib_1.__decorate([
90
+ (0, typeorm_1.CreateDateColumn)(),
91
+ (0, type_graphql_1.Field)({ description: 'When this row was written.' }),
92
+ tslib_1.__metadata("design:type", Date)
93
+ ], TwinSubjectEvent.prototype, "createdAt", void 0);
94
+ exports.TwinSubjectEvent = TwinSubjectEvent = tslib_1.__decorate([
95
+ (0, typeorm_1.Entity)()
96
+ /*
97
+ * 이 표의 유일한 목차 — 이 순서가 곧 이 표의 존재 이유다.
98
+ *
99
+ * 「이 트윈의 이 대상의 것을 최신순으로 30개」가 목차를 위에서 30줄 읽고 끝난다. 정렬이 없다 —
100
+ * 거르는 값이 하나이고 그 다음이 시각이기 때문이다.
101
+ */
102
+ ,
103
+ (0, typeorm_1.Index)('ix_twin_subject_event_0', (e) => [e.domain, e.instanceId, e.subjectKind, e.subjectId, e.eventTime], { unique: false })
104
+ /* 보관 상한을 지우는 축 — 「이 트윈에서 오래된 줄」을 찾는다(§`twin-subject-writer` 의 정리). */
105
+ ,
106
+ (0, typeorm_1.Index)('ix_twin_subject_event_1', (e) => [e.domain, e.instanceId, e.eventTime], {
107
+ unique: false
108
+ }),
109
+ (0, type_graphql_1.ObjectType)({ description: 'One row per subject an event refers to — the read model behind entity history.' })
110
+ ], TwinSubjectEvent);
111
+ //# sourceMappingURL=twin-subject-event.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"twin-subject-event.js","sourceRoot":"","sources":["../../../server/service/twin-subject/twin-subject-event.ts"],"names":[],"mappings":";;;;AAAA,+CAAqD;AACrD,qCAAgH;AAChH,iDAA8C;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAkBI,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;CAmD5B,CAAA;AAnDY,4CAAgB;AAGlB;IAFR,IAAA,gCAAsB,EAAC,MAAM,CAAC;IAC9B,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC;;4CAC/B;AAGnB;IADC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,cAAM,CAAC;sCAClB,cAAM;gDAAA;AAGd;IADC,IAAA,oBAAU,EAAC,CAAC,CAAmB,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;;kDAC7B;AAIjB;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,wCAAwC,EAAE,CAAC;;oDAC/C;AAOlB;IALC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,EAAC;QACL,WAAW,EACT,qLAAqL;KACxL,CAAC;;qDACiB;AAInB;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,4CAA4C,EAAE,CAAC;;mDACpD;AAUjB;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,8FAA8F,EAAE,CAAC;sCAC5G,IAAI;mDAAA;AAIf;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACvB,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,kBAAG,EAAE,EAAE,WAAW,EAAE,2EAA2E,EAAE,CAAC;;kDACjG;AAIhB;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,uEAAuE,EAAE,CAAC;;mDAC/E;AAIjB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,wEAAwE,EAAE,CAAC;;iDACjG;AAIhB;IAFC,IAAA,0BAAgB,GAAE;IAClB,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,4BAA4B,EAAE,CAAC;sCACzC,IAAI;mDAAA;2BAlDL,gBAAgB;IAjB5B,IAAA,gBAAM,GAAE;IACT;;;;;OAKG;;IACF,IAAA,eAAK,EACJ,yBAAyB,EACzB,CAAC,CAAmB,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,EAC1F,EAAE,MAAM,EAAE,KAAK,EAAE,CAClB;IACD,sEAAsE;;IACrE,IAAA,eAAK,EAAC,yBAAyB,EAAE,CAAC,CAAmB,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,SAAS,CAAC,EAAE;QAChG,MAAM,EAAE,KAAK;KACd,CAAC;IACD,IAAA,yBAAU,EAAC,EAAE,WAAW,EAAE,gFAAgF,EAAE,CAAC;GACjG,gBAAgB,CAmD5B","sourcesContent":["import { Field, Int, ObjectType } from 'type-graphql'\nimport { Column, CreateDateColumn, Entity, Index, ManyToOne, PrimaryGeneratedColumn, RelationId } from 'typeorm'\nimport { Domain } from '@things-factory/shell'\n\n/*\n * **대상별 사건 목록** — 조회용 파생 표. 사실의 원본은 `twin_events` 다.\n *\n * ── 왜 만드나 (2026-08-27 실측) ─────────────────────────────────────────────\n * 360 화면이 「이 대상의 이력」을 묻고, 그 조회가 64초다. 원인은 「무엇으로 걸러 시각순으로 정렬해\n * 앞에서 30개」라는 모양에 맞는 목차가 없다는 것이다. 그리고 목차를 얹는 방식으로는 풀리지 않는다.\n *\n * 정렬이 목차를 무력화한다 여러 값으로 거르면(IN) 합칠 때 다시 정렬해야 한다\n * 축이 늘 때마다 목차가 늘어난다 360 의 축 넷을 다 빠르게 하면 `twin_events` 목차가 13개가 된다\n * 소급 채우기가 매번 든다 목차 하나 만드는 데 3분 36초 · 파일 +2.4GB\n * 품목 축은 그래도 안 풀린다 저장된 식별자에 품목과 로트가 한 문자열로 붙어 있다\n *\n * ── 이 표가 무엇을 담나 ─────────────────────────────────────────────────────\n * 사건 하나가 가리키는 **대상마다 한 줄**이다. 품목 이동 사건이면 품목·로트·자리로 세 줄이다.\n * 종류(`subjectKind`)를 값으로 밝히므로 읽는 쪽이 사건 종류표를 들고 있지 않아도 된다. 뜻이\n * 달라지는 키 칸을 쓰지 않는 이유이고, 그 방식이 틀렸다는 뜻은 아니다 — 정의가 함께 있는\n * 프레임워크에서는 합리적이다. 이 표는 볼 정의가 없다.\n *\n * ── 규칙 넷 (설계: read-models.md §4) ───────────────────────────────────────\n * ① 조회는 이 표가 답한다. `twin_events` 에 목차를 얹어 답하지 않는다\n * ② **앞으로만 채운다.** 소급하지 않는다 — 새 키가 필요할 때 3,128만 줄을 다시 만들지 않는다\n * ③ 준비되지 않은 구간을 화면이 말한다. 비어 있는 것을 「없다」로 그리지 않는다\n * ④ 언제든 버릴 수 있다. 사건에서 파생된 값이므로 지워도 사실이 사라지지 않는다\n *\n * ── 왜 사건 본문을 베끼지 않나 ──────────────────────────────────────────────\n * `eventId` 로 원본을 가리키고, 목록을 그리는 데 필요한 값만 함께 둔다(종류·시각·리비전). 본문까지\n * 베끼면 이 표가 원본만큼 커지고, 원본을 고칠 때 두 곳이 어긋난다.\n */\n@Entity()\n/*\n * 이 표의 유일한 목차 — 이 순서가 곧 이 표의 존재 이유다.\n *\n * 「이 트윈의 이 대상의 것을 최신순으로 30개」가 목차를 위에서 30줄 읽고 끝난다. 정렬이 없다 —\n * 거르는 값이 하나이고 그 다음이 시각이기 때문이다.\n */\n@Index(\n 'ix_twin_subject_event_0',\n (e: TwinSubjectEvent) => [e.domain, e.instanceId, e.subjectKind, e.subjectId, e.eventTime],\n { unique: false }\n)\n/* 보관 상한을 지우는 축 — 「이 트윈에서 오래된 줄」을 찾는다(§`twin-subject-writer` 의 정리). */\n@Index('ix_twin_subject_event_1', (e: TwinSubjectEvent) => [e.domain, e.instanceId, e.eventTime], {\n unique: false\n})\n@ObjectType({ description: 'One row per subject an event refers to — the read model behind entity history.' })\nexport class TwinSubjectEvent {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => String, { description: 'Row id.' })\n readonly id: string\n\n @ManyToOne(type => Domain)\n domain: Domain\n\n @RelationId((e: TwinSubjectEvent) => e.domain)\n domainId?: string\n\n @Column()\n @Field({ description: 'The twin instance this row belongs to.' })\n instanceId: string\n\n @Column()\n @Field({\n description:\n 'What kind of thing this row points at: item, lot, order, location, equipment or task. The kind is stored as a value so a reader never has to know which event types put what where.'\n })\n subjectKind: string\n\n @Column()\n @Field({ description: 'Identifier of that thing, within its kind.' })\n subjectId: string\n\n /*\n * **타입 이름을 적지 않는다** — `twin-event.ts` 의 `eventTime` 과 같은 이유다.\n *\n * `datetime` 은 sqlite·MySQL 의 이름이고 Postgres 는 `timestamp` 만 안다. 다섯 드라이버에 다\n * 통하는 이름이 없어서, 이름을 고르는 일을 TypeORM 에 맡긴다(TS 타입 `Date` 에서 고른다).\n */\n @Column()\n @Field({ description: 'When the event happened, by the twin clock. This is the axis the history list is ordered by.' })\n eventTime: Date\n\n @Column({ type: 'int' })\n @Field(type => Int, { description: 'Journal revision of the event — ties this row to its place in the record.' })\n revision: number\n\n @Column()\n @Field({ description: 'Event type, so the list can be drawn without reading the journal row.' })\n eventType: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Journal row this points at. Null when the journal row has been pruned.' })\n eventId?: string\n\n @CreateDateColumn()\n @Field({ description: 'When this row was written.' })\n createdAt?: Date\n}\n"]}