@operato/twin-kernel 0.7.26 → 0.7.27
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/event-journal.d.ts +24 -1
- package/dist/event-journal.js +28 -0
- package/dist/observed-reducer.d.ts +85 -1
- package/dist/observed-reducer.js +51 -0
- package/dist/state-projector.d.ts +1 -1
- package/dist-cjs/index.cjs +66 -0
- package/package.json +1 -1
package/dist/event-journal.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TwinModelDef, CanonicalEnvelope, StructureShift } from './contract.ts';
|
|
2
|
-
import { type ProjectedState } from './state-projector.ts';
|
|
2
|
+
import { type ProjectedState, type ReducerCheckpoint } from './state-projector.ts';
|
|
3
3
|
export declare class EventJournal {
|
|
4
4
|
private events;
|
|
5
5
|
/** 이벤트 1건 기록(추가 전용). runtime/kernel 의 onEvent 에 연결. */
|
|
@@ -15,6 +15,29 @@ export declare class EventJournal {
|
|
|
15
15
|
}
|
|
16
16
|
/** 이벤트열 → 상태 재구성(시간여행). `model` = 그 시점의 트윈 모델(토폴로지·자원). */
|
|
17
17
|
export declare function replay(model: TwinModelDef, events: readonly CanonicalEnvelope[]): ProjectedState;
|
|
18
|
+
/**
|
|
19
|
+
* **재개점에서 이어 접는다** — 0부터 다시 접지 않는다.
|
|
20
|
+
*
|
|
21
|
+
* ── 왜 (2026-08-18 실측) ────────────────────────────────────────────────────
|
|
22
|
+
* 저널이 27만 건인 트윈에서 과거 상태를 물으면 전부 다시 접어야 했다. 그런데 우리는 이미 주기적으로
|
|
23
|
+
* 재개점을 남기고 있다 — 그 지점부터 **뒤에 일어난 것만** 접으면 같은 답이 나온다.
|
|
24
|
+
*
|
|
25
|
+
* 「같은 답」은 말로 보장되지 않는다: 재개점이 리듀서의 **내부 상태 전부**여야 하고(보기가 아니라),
|
|
26
|
+
* 그 사실은 시험이 증명한다(0부터 접기 == 재개점 + 꼬리). 그래서 이 함수는 새 재개점도 함께 낸다 —
|
|
27
|
+
* 소비처가 그것을 저장해 다음 꼬리를 또 이어 붙일 수 있게.
|
|
28
|
+
*
|
|
29
|
+
* 구조가 바뀐 구간은 여기서 다루지 않는다(`replaySegments` 의 몫이다) — 재개점은 **한 구조 안에서**
|
|
30
|
+
* 이어 붙이는 것이다. 구조가 바뀌었으면 부르는 쪽이 그 경계에서 갈라야 한다.
|
|
31
|
+
*/
|
|
32
|
+
export declare function replayFrom(model: TwinModelDef, checkpoint: ReducerCheckpoint, events: readonly CanonicalEnvelope[]): {
|
|
33
|
+
state: ProjectedState;
|
|
34
|
+
checkpoint: ReducerCheckpoint;
|
|
35
|
+
};
|
|
36
|
+
/** 이벤트열을 접고 **재개점도 함께** 낸다 — 다음 번에 이어 붙일 수 있게. */
|
|
37
|
+
export declare function replayWithCheckpoint(model: TwinModelDef, events: readonly CanonicalEnvelope[]): {
|
|
38
|
+
state: ProjectedState;
|
|
39
|
+
checkpoint: ReducerCheckpoint;
|
|
40
|
+
};
|
|
18
41
|
/**
|
|
19
42
|
* 한 구조 아래에서 일어난 이벤트들 — 재생의 한 마디.
|
|
20
43
|
*
|
package/dist/event-journal.js
CHANGED
|
@@ -39,6 +39,34 @@ export function replay(model, events) {
|
|
|
39
39
|
proj.apply(e);
|
|
40
40
|
return proj.snapshot();
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* **재개점에서 이어 접는다** — 0부터 다시 접지 않는다.
|
|
44
|
+
*
|
|
45
|
+
* ── 왜 (2026-08-18 실측) ────────────────────────────────────────────────────
|
|
46
|
+
* 저널이 27만 건인 트윈에서 과거 상태를 물으면 전부 다시 접어야 했다. 그런데 우리는 이미 주기적으로
|
|
47
|
+
* 재개점을 남기고 있다 — 그 지점부터 **뒤에 일어난 것만** 접으면 같은 답이 나온다.
|
|
48
|
+
*
|
|
49
|
+
* 「같은 답」은 말로 보장되지 않는다: 재개점이 리듀서의 **내부 상태 전부**여야 하고(보기가 아니라),
|
|
50
|
+
* 그 사실은 시험이 증명한다(0부터 접기 == 재개점 + 꼬리). 그래서 이 함수는 새 재개점도 함께 낸다 —
|
|
51
|
+
* 소비처가 그것을 저장해 다음 꼬리를 또 이어 붙일 수 있게.
|
|
52
|
+
*
|
|
53
|
+
* 구조가 바뀐 구간은 여기서 다루지 않는다(`replaySegments` 의 몫이다) — 재개점은 **한 구조 안에서**
|
|
54
|
+
* 이어 붙이는 것이다. 구조가 바뀌었으면 부르는 쪽이 그 경계에서 갈라야 한다.
|
|
55
|
+
*/
|
|
56
|
+
export function replayFrom(model, checkpoint, events) {
|
|
57
|
+
const proj = new StateProjector(model);
|
|
58
|
+
proj.restore(checkpoint);
|
|
59
|
+
for (const e of events)
|
|
60
|
+
proj.apply(e);
|
|
61
|
+
return { state: proj.snapshot(), checkpoint: proj.serialize() };
|
|
62
|
+
}
|
|
63
|
+
/** 이벤트열을 접고 **재개점도 함께** 낸다 — 다음 번에 이어 붙일 수 있게. */
|
|
64
|
+
export function replayWithCheckpoint(model, events) {
|
|
65
|
+
const proj = new StateProjector(model);
|
|
66
|
+
for (const e of events)
|
|
67
|
+
proj.apply(e);
|
|
68
|
+
return { state: proj.snapshot(), checkpoint: proj.serialize() };
|
|
69
|
+
}
|
|
42
70
|
/**
|
|
43
71
|
* **구조가 바뀐 이력까지 이어서 재생한다.**
|
|
44
72
|
*
|
|
@@ -1,4 +1,22 @@
|
|
|
1
|
-
import type { AssetState, TwinModelDef, CanonicalEnvelope, LocationState, ItemState, EquipmentState, PersonState, TaskState, OrderState, StructureShift } from './contract.ts';
|
|
1
|
+
import type { AssetState, MaterialQuantity, TwinModelDef, CanonicalEnvelope, LocationState, ItemState, EquipmentState, PersonState, TaskState, OrderState, StructureShift } from './contract.ts';
|
|
2
|
+
interface ProjItem {
|
|
3
|
+
epc: string;
|
|
4
|
+
/** 로트의 부분(표준 MaterialSubLot.ID) — 비직렬 로트가 자리마다 갈릴 때만. */
|
|
5
|
+
subLotId?: string;
|
|
6
|
+
gtin?: string;
|
|
7
|
+
gtinKey?: string;
|
|
8
|
+
lot?: string;
|
|
9
|
+
location: string;
|
|
10
|
+
disposition?: string;
|
|
11
|
+
parent?: string;
|
|
12
|
+
qty?: number;
|
|
13
|
+
uom?: string;
|
|
14
|
+
/** 선언된 모든 수량 — 표준 `MaterialLot.Quantity`(복수). 첫 항목만 쓰던 것을 고쳤다. */
|
|
15
|
+
quantities?: MaterialQuantity[];
|
|
16
|
+
/** 받은 개체·로트 마스터데이터 원문 — 이름을 모르는 속성도 잃지 않는다. */
|
|
17
|
+
ilmd?: Record<string, unknown>;
|
|
18
|
+
expiry?: number;
|
|
19
|
+
}
|
|
2
20
|
/**
|
|
3
21
|
* 마스터 동기 — 선언적 로케이션 upsert/remove.
|
|
4
22
|
*
|
|
@@ -63,6 +81,50 @@ export interface ProjectedState {
|
|
|
63
81
|
*/
|
|
64
82
|
acked: string[];
|
|
65
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* 리듀서의 **완전한 재개점** — 이어 접기의 씨앗.
|
|
86
|
+
*
|
|
87
|
+
* `ProjectedState`(보기)와 구별한다: 여기에는 소비처가 보지 않는 것도 들어간다(보류된 담김·집합·
|
|
88
|
+
* 반영 못 한 사건 집계). 그것이 빠지면 「이어 접은 결과」가 「0부터 접은 결과」와 조용히 달라진다.
|
|
89
|
+
*/
|
|
90
|
+
export interface ReducerCheckpoint {
|
|
91
|
+
revision: number;
|
|
92
|
+
master: {
|
|
93
|
+
id: string;
|
|
94
|
+
type: string;
|
|
95
|
+
capacity?: number;
|
|
96
|
+
parallelism?: number;
|
|
97
|
+
parentId?: string;
|
|
98
|
+
origin: 'master' | 'observed';
|
|
99
|
+
}[];
|
|
100
|
+
items: ProjItem[];
|
|
101
|
+
aggregation: {
|
|
102
|
+
parent: string;
|
|
103
|
+
children: string[];
|
|
104
|
+
}[];
|
|
105
|
+
pendingParent: {
|
|
106
|
+
child: string;
|
|
107
|
+
parent: string;
|
|
108
|
+
}[];
|
|
109
|
+
tasks: TaskState[];
|
|
110
|
+
equipment: EquipmentState[];
|
|
111
|
+
persons: PersonState[];
|
|
112
|
+
assets: AssetState[];
|
|
113
|
+
orders: OrderState[];
|
|
114
|
+
acked: string[];
|
|
115
|
+
corrections: {
|
|
116
|
+
declaredAt: string;
|
|
117
|
+
reason?: string;
|
|
118
|
+
correctiveEventIDs: string[];
|
|
119
|
+
eventID?: string;
|
|
120
|
+
}[];
|
|
121
|
+
unhandled: {
|
|
122
|
+
eventType: string;
|
|
123
|
+
count: number;
|
|
124
|
+
firstAtMs?: number;
|
|
125
|
+
lastAtMs?: number;
|
|
126
|
+
}[];
|
|
127
|
+
}
|
|
66
128
|
export declare class ObservedReducer {
|
|
67
129
|
/** 로케이션 마스터 — 출처를 함께 들고 있다(마스터가 말한 자리 vs 관측으로 알게 된 자리). */
|
|
68
130
|
private master;
|
|
@@ -215,5 +277,27 @@ export declare class ObservedReducer {
|
|
|
215
277
|
* **이벤트 없이 시각만으로** 넘어간다. 델타로 받아 두면 만료된 자격이 영원히 유효하게 남는다.
|
|
216
278
|
*/
|
|
217
279
|
private capabilityPart;
|
|
280
|
+
/**
|
|
281
|
+
* **재개점(checkpoint)** — 스냅샷과 다르다.
|
|
282
|
+
*
|
|
283
|
+
* ── 왜 스냅샷으로는 이어 접을 수 없나 (2026-08-18) ─────────────────────────
|
|
284
|
+
* `snapshot()` 은 **소비처가 보는 값**이다(파생된 판정·정리된 목록). 그것으로 리듀서를 되세우면
|
|
285
|
+
* 보이지 않는 것들이 사라진다 — 아직 등장하지 않은 자식의 담김(`pendingParent`), 집합 관계
|
|
286
|
+
* (`aggregation`), 반영 못 한 사건 집계(`unhandled`), 정정 선언(`corrections`). 그 상태에서 뒤 이벤트를
|
|
287
|
+
* 접으면 **0부터 접은 결과와 달라진다.** 다르면 그 차이는 조용하다(오류가 없다).
|
|
288
|
+
*
|
|
289
|
+
* 그래서 재개점은 **내부 상태 전부**다. 「이어 접기」의 정합성은 시험이 증명한다:
|
|
290
|
+
* 0부터 접은 결과 == 앞부분 재개점 + 뒷부분 접기.
|
|
291
|
+
*
|
|
292
|
+
* 모델에서 오는 것(교대·등급·시간대)은 담지 않는다 — 되세울 때 같은 모델을 받기 때문이다.
|
|
293
|
+
*/
|
|
294
|
+
serialize(): ReducerCheckpoint;
|
|
295
|
+
/**
|
|
296
|
+
* 재개점에서 되세운다 — **모르는 것은 지어내지 않는다**(없는 축은 비운다).
|
|
297
|
+
*
|
|
298
|
+
* 모델에서 오는 판정 입력(교대·등급·시간대)은 생성자가 이미 세웠으므로 건드리지 않는다.
|
|
299
|
+
*/
|
|
300
|
+
restore(cp: ReducerCheckpoint): void;
|
|
218
301
|
snapshot(): ProjectedState;
|
|
219
302
|
}
|
|
303
|
+
export {};
|
package/dist/observed-reducer.js
CHANGED
|
@@ -635,6 +635,57 @@ export class ObservedReducer {
|
|
|
635
635
|
capability: capabilityOf({ ...r, ...(decl?.window ? { window: decl.window } : {}), ...(decl?.workCalendar ? { workCalendar: decl.workCalendar } : {}) }, { at, utcOffsetMinutes: this.utcOffsetMinutes, requiredTests: requiredTestsFor(directClassIds, defs, at) })
|
|
636
636
|
};
|
|
637
637
|
}
|
|
638
|
+
/**
|
|
639
|
+
* **재개점(checkpoint)** — 스냅샷과 다르다.
|
|
640
|
+
*
|
|
641
|
+
* ── 왜 스냅샷으로는 이어 접을 수 없나 (2026-08-18) ─────────────────────────
|
|
642
|
+
* `snapshot()` 은 **소비처가 보는 값**이다(파생된 판정·정리된 목록). 그것으로 리듀서를 되세우면
|
|
643
|
+
* 보이지 않는 것들이 사라진다 — 아직 등장하지 않은 자식의 담김(`pendingParent`), 집합 관계
|
|
644
|
+
* (`aggregation`), 반영 못 한 사건 집계(`unhandled`), 정정 선언(`corrections`). 그 상태에서 뒤 이벤트를
|
|
645
|
+
* 접으면 **0부터 접은 결과와 달라진다.** 다르면 그 차이는 조용하다(오류가 없다).
|
|
646
|
+
*
|
|
647
|
+
* 그래서 재개점은 **내부 상태 전부**다. 「이어 접기」의 정합성은 시험이 증명한다:
|
|
648
|
+
* 0부터 접은 결과 == 앞부분 재개점 + 뒷부분 접기.
|
|
649
|
+
*
|
|
650
|
+
* 모델에서 오는 것(교대·등급·시간대)은 담지 않는다 — 되세울 때 같은 모델을 받기 때문이다.
|
|
651
|
+
*/
|
|
652
|
+
serialize() {
|
|
653
|
+
return {
|
|
654
|
+
revision: this.revision,
|
|
655
|
+
master: [...this.master.values()].map(n => ({ ...n })),
|
|
656
|
+
items: [...this.items.values()].map(i => ({ ...i })),
|
|
657
|
+
aggregation: [...this.aggregation.entries()].map(([parent, children]) => ({ parent, children: [...children] })),
|
|
658
|
+
pendingParent: [...this.pendingParent.entries()].map(([child, parent]) => ({ child, parent })),
|
|
659
|
+
tasks: [...this.tasks.values()].map(t => ({ ...t })),
|
|
660
|
+
equipment: [...this.equipment.values()].map(m => ({ ...m })),
|
|
661
|
+
persons: [...this.persons.values()].map(x => ({ ...x })),
|
|
662
|
+
assets: [...this.assets.values()].map(x => ({ ...x })),
|
|
663
|
+
orders: [...this.orders.values()].map(o => ({ ...o })),
|
|
664
|
+
acked: [...this.acked],
|
|
665
|
+
corrections: this.corrections.map(c => ({ ...c })),
|
|
666
|
+
unhandled: [...this.unhandled.entries()].map(([eventType, v]) => ({ eventType, ...v }))
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* 재개점에서 되세운다 — **모르는 것은 지어내지 않는다**(없는 축은 비운다).
|
|
671
|
+
*
|
|
672
|
+
* 모델에서 오는 판정 입력(교대·등급·시간대)은 생성자가 이미 세웠으므로 건드리지 않는다.
|
|
673
|
+
*/
|
|
674
|
+
restore(cp) {
|
|
675
|
+
this.revision = cp?.revision ?? 0;
|
|
676
|
+
this.master = new Map((cp?.master ?? []).map(n => [n.id, { ...n }]));
|
|
677
|
+
this.items = new Map((cp?.items ?? []).map(i => [i.epc, { ...i }]));
|
|
678
|
+
this.aggregation = new Map((cp?.aggregation ?? []).map(a => [a.parent, [...a.children]]));
|
|
679
|
+
this.pendingParent = new Map((cp?.pendingParent ?? []).map(x => [x.child, x.parent]));
|
|
680
|
+
this.tasks = new Map((cp?.tasks ?? []).map(t => [t.id, { ...t }]));
|
|
681
|
+
this.equipment = new Map((cp?.equipment ?? []).map(m => [m.id, { ...m }]));
|
|
682
|
+
this.persons = new Map((cp?.persons ?? []).map(x => [x.id, { ...x }]));
|
|
683
|
+
this.assets = new Map((cp?.assets ?? []).map(x => [x.id, { ...x }]));
|
|
684
|
+
this.orders = new Map((cp?.orders ?? []).map(o => [o.id, { ...o }]));
|
|
685
|
+
this.acked = new Set(cp?.acked ?? []);
|
|
686
|
+
this.corrections = (cp?.corrections ?? []).map(c => ({ ...c }));
|
|
687
|
+
this.unhandled = new Map((cp?.unhandled ?? []).map(({ eventType, ...v }) => [eventType, { ...v }]));
|
|
688
|
+
}
|
|
638
689
|
snapshot() {
|
|
639
690
|
const occ = new Map();
|
|
640
691
|
for (const it of this.items.values())
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { ObservedReducer, ObservedReducer as StateProjector } from './observed-reducer.ts';
|
|
2
|
-
export type { MasterUpdate, ProjectedState } from './observed-reducer.ts';
|
|
2
|
+
export type { MasterUpdate, ProjectedState, ReducerCheckpoint } from './observed-reducer.ts';
|
package/dist-cjs/index.cjs
CHANGED
|
@@ -144,7 +144,9 @@ __export(index_exports, {
|
|
|
144
144
|
relationsFrom: () => relationsFrom,
|
|
145
145
|
relationsTo: () => relationsTo,
|
|
146
146
|
replay: () => replay,
|
|
147
|
+
replayFrom: () => replayFrom,
|
|
147
148
|
replaySegments: () => replaySegments,
|
|
149
|
+
replayWithCheckpoint: () => replayWithCheckpoint,
|
|
148
150
|
requiredTestsFor: () => requiredTestsFor,
|
|
149
151
|
retiredVocabularyIn: () => retiredVocabularyIn,
|
|
150
152
|
sgtinClass: () => sgtinClass,
|
|
@@ -1584,6 +1586,57 @@ var ObservedReducer = class {
|
|
|
1584
1586
|
)
|
|
1585
1587
|
};
|
|
1586
1588
|
}
|
|
1589
|
+
/**
|
|
1590
|
+
* **재개점(checkpoint)** — 스냅샷과 다르다.
|
|
1591
|
+
*
|
|
1592
|
+
* ── 왜 스냅샷으로는 이어 접을 수 없나 (2026-08-18) ─────────────────────────
|
|
1593
|
+
* `snapshot()` 은 **소비처가 보는 값**이다(파생된 판정·정리된 목록). 그것으로 리듀서를 되세우면
|
|
1594
|
+
* 보이지 않는 것들이 사라진다 — 아직 등장하지 않은 자식의 담김(`pendingParent`), 집합 관계
|
|
1595
|
+
* (`aggregation`), 반영 못 한 사건 집계(`unhandled`), 정정 선언(`corrections`). 그 상태에서 뒤 이벤트를
|
|
1596
|
+
* 접으면 **0부터 접은 결과와 달라진다.** 다르면 그 차이는 조용하다(오류가 없다).
|
|
1597
|
+
*
|
|
1598
|
+
* 그래서 재개점은 **내부 상태 전부**다. 「이어 접기」의 정합성은 시험이 증명한다:
|
|
1599
|
+
* 0부터 접은 결과 == 앞부분 재개점 + 뒷부분 접기.
|
|
1600
|
+
*
|
|
1601
|
+
* 모델에서 오는 것(교대·등급·시간대)은 담지 않는다 — 되세울 때 같은 모델을 받기 때문이다.
|
|
1602
|
+
*/
|
|
1603
|
+
serialize() {
|
|
1604
|
+
return {
|
|
1605
|
+
revision: this.revision,
|
|
1606
|
+
master: [...this.master.values()].map((n) => ({ ...n })),
|
|
1607
|
+
items: [...this.items.values()].map((i) => ({ ...i })),
|
|
1608
|
+
aggregation: [...this.aggregation.entries()].map(([parent, children]) => ({ parent, children: [...children] })),
|
|
1609
|
+
pendingParent: [...this.pendingParent.entries()].map(([child, parent]) => ({ child, parent })),
|
|
1610
|
+
tasks: [...this.tasks.values()].map((t) => ({ ...t })),
|
|
1611
|
+
equipment: [...this.equipment.values()].map((m) => ({ ...m })),
|
|
1612
|
+
persons: [...this.persons.values()].map((x) => ({ ...x })),
|
|
1613
|
+
assets: [...this.assets.values()].map((x) => ({ ...x })),
|
|
1614
|
+
orders: [...this.orders.values()].map((o) => ({ ...o })),
|
|
1615
|
+
acked: [...this.acked],
|
|
1616
|
+
corrections: this.corrections.map((c) => ({ ...c })),
|
|
1617
|
+
unhandled: [...this.unhandled.entries()].map(([eventType, v]) => ({ eventType, ...v }))
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
/**
|
|
1621
|
+
* 재개점에서 되세운다 — **모르는 것은 지어내지 않는다**(없는 축은 비운다).
|
|
1622
|
+
*
|
|
1623
|
+
* 모델에서 오는 판정 입력(교대·등급·시간대)은 생성자가 이미 세웠으므로 건드리지 않는다.
|
|
1624
|
+
*/
|
|
1625
|
+
restore(cp) {
|
|
1626
|
+
this.revision = cp?.revision ?? 0;
|
|
1627
|
+
this.master = new Map((cp?.master ?? []).map((n) => [n.id, { ...n }]));
|
|
1628
|
+
this.items = new Map((cp?.items ?? []).map((i) => [i.epc, { ...i }]));
|
|
1629
|
+
this.aggregation = new Map((cp?.aggregation ?? []).map((a) => [a.parent, [...a.children]]));
|
|
1630
|
+
this.pendingParent = new Map((cp?.pendingParent ?? []).map((x) => [x.child, x.parent]));
|
|
1631
|
+
this.tasks = new Map((cp?.tasks ?? []).map((t) => [t.id, { ...t }]));
|
|
1632
|
+
this.equipment = new Map((cp?.equipment ?? []).map((m) => [m.id, { ...m }]));
|
|
1633
|
+
this.persons = new Map((cp?.persons ?? []).map((x) => [x.id, { ...x }]));
|
|
1634
|
+
this.assets = new Map((cp?.assets ?? []).map((x) => [x.id, { ...x }]));
|
|
1635
|
+
this.orders = new Map((cp?.orders ?? []).map((o) => [o.id, { ...o }]));
|
|
1636
|
+
this.acked = new Set(cp?.acked ?? []);
|
|
1637
|
+
this.corrections = (cp?.corrections ?? []).map((c) => ({ ...c }));
|
|
1638
|
+
this.unhandled = new Map((cp?.unhandled ?? []).map(({ eventType, ...v }) => [eventType, { ...v }]));
|
|
1639
|
+
}
|
|
1587
1640
|
snapshot() {
|
|
1588
1641
|
const occ = /* @__PURE__ */ new Map();
|
|
1589
1642
|
for (const it of this.items.values()) occ.set(it.location, (occ.get(it.location) ?? 0) + 1);
|
|
@@ -1651,6 +1704,17 @@ function replay(model, events) {
|
|
|
1651
1704
|
for (const e of events) proj.apply(e);
|
|
1652
1705
|
return proj.snapshot();
|
|
1653
1706
|
}
|
|
1707
|
+
function replayFrom(model, checkpoint, events) {
|
|
1708
|
+
const proj = new ObservedReducer(model);
|
|
1709
|
+
proj.restore(checkpoint);
|
|
1710
|
+
for (const e of events) proj.apply(e);
|
|
1711
|
+
return { state: proj.snapshot(), checkpoint: proj.serialize() };
|
|
1712
|
+
}
|
|
1713
|
+
function replayWithCheckpoint(model, events) {
|
|
1714
|
+
const proj = new ObservedReducer(model);
|
|
1715
|
+
for (const e of events) proj.apply(e);
|
|
1716
|
+
return { state: proj.snapshot(), checkpoint: proj.serialize() };
|
|
1717
|
+
}
|
|
1654
1718
|
function replaySegments(segments) {
|
|
1655
1719
|
if (!segments.length) throw new Error("\uC7AC\uC0DD\uD560 \uB9C8\uB514\uAC00 \uC5C6\uB2E4 \u2014 \uAD6C\uC870\uB97C \uD558\uB098\uB3C4 \uC8FC\uC9C0 \uC54A\uC558\uB2E4");
|
|
1656
1720
|
const proj = new ObservedReducer(segments[0].model);
|
|
@@ -7236,7 +7300,9 @@ function retiredVocabularyIn(line) {
|
|
|
7236
7300
|
relationsFrom,
|
|
7237
7301
|
relationsTo,
|
|
7238
7302
|
replay,
|
|
7303
|
+
replayFrom,
|
|
7239
7304
|
replaySegments,
|
|
7305
|
+
replayWithCheckpoint,
|
|
7240
7306
|
requiredTestsFor,
|
|
7241
7307
|
retiredVocabularyIn,
|
|
7242
7308
|
sgtinClass,
|
package/package.json
CHANGED