@things-factory/headless-twin 10.0.0-zeta.10
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/README.md +35 -0
- package/dist-server/engine/index.d.ts +1 -0
- package/dist-server/engine/index.js +5 -0
- package/dist-server/engine/index.js.map +1 -0
- package/dist-server/engine/twin-engine.d.ts +34 -0
- package/dist-server/engine/twin-engine.js +135 -0
- package/dist-server/engine/twin-engine.js.map +1 -0
- package/dist-server/index.d.ts +3 -0
- package/dist-server/index.js +18 -0
- package/dist-server/index.js.map +1 -0
- package/dist-server/routes.d.ts +0 -0
- package/dist-server/routes.js +6 -0
- package/dist-server/routes.js.map +1 -0
- package/dist-server/service/index.d.ts +10 -0
- package/dist-server/service/index.js +33 -0
- package/dist-server/service/index.js.map +1 -0
- package/dist-server/service/twin-control/index.d.ts +2 -0
- package/dist-server/service/twin-control/index.js +6 -0
- package/dist-server/service/twin-control/index.js.map +1 -0
- package/dist-server/service/twin-control/twin-control-mutation.d.ts +6 -0
- package/dist-server/service/twin-control/twin-control-mutation.js +59 -0
- package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -0
- package/dist-server/service/twin-event/index.d.ts +2 -0
- package/dist-server/service/twin-event/index.js +6 -0
- package/dist-server/service/twin-event/index.js.map +1 -0
- package/dist-server/service/twin-event/twin-event.d.ts +13 -0
- package/dist-server/service/twin-event/twin-event.js +72 -0
- package/dist-server/service/twin-event/twin-event.js.map +1 -0
- package/dist-server/service/twin-instance/index.d.ts +2 -0
- package/dist-server/service/twin-instance/index.js +6 -0
- package/dist-server/service/twin-instance/index.js.map +1 -0
- package/dist-server/service/twin-instance/twin-instance.d.ts +13 -0
- package/dist-server/service/twin-instance/twin-instance.js +72 -0
- package/dist-server/service/twin-instance/twin-instance.js.map +1 -0
- package/dist-server/service/twin-journal/index.d.ts +2 -0
- package/dist-server/service/twin-journal/index.js +6 -0
- package/dist-server/service/twin-journal/index.js.map +1 -0
- package/dist-server/service/twin-journal/twin-journal-query.d.ts +5 -0
- package/dist-server/service/twin-journal/twin-journal-query.js +64 -0
- package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -0
- package/dist-server/service/twin-lifecycle/index.d.ts +2 -0
- package/dist-server/service/twin-lifecycle/index.js +6 -0
- package/dist-server/service/twin-lifecycle/index.js.map +1 -0
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +5 -0
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +60 -0
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -0
- package/dist-server/service/twin-state/index.d.ts +2 -0
- package/dist-server/service/twin-state/index.js +6 -0
- package/dist-server/service/twin-state/index.js.map +1 -0
- package/dist-server/service/twin-state/twin-state-subscription.d.ts +11 -0
- package/dist-server/service/twin-state/twin-state-subscription.js +67 -0
- package/dist-server/service/twin-state/twin-state-subscription.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -0
- package/package.json +33 -0
- package/server/engine/index.ts +1 -0
- package/server/engine/twin-engine.ts +160 -0
- package/server/index.ts +16 -0
- package/server/routes.ts +5 -0
- package/server/service/index.ts +32 -0
- package/server/service/twin-control/index.ts +3 -0
- package/server/service/twin-control/twin-control-mutation.ts +41 -0
- package/server/service/twin-event/index.ts +3 -0
- package/server/service/twin-event/twin-event.ts +55 -0
- package/server/service/twin-instance/index.ts +3 -0
- package/server/service/twin-instance/twin-instance.ts +55 -0
- package/server/service/twin-journal/index.ts +3 -0
- package/server/service/twin-journal/twin-journal-query.ts +49 -0
- package/server/service/twin-lifecycle/index.ts +3 -0
- package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +44 -0
- package/server/service/twin-state/index.ts +3 -0
- package/server/service/twin-state/twin-state-subscription.ts +53 -0
- package/server/types/global.d.ts +22 -0
- package/things-factory.config.js +2 -0
- package/tsconfig.json +10 -0
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# @things-factory/headless-twin
|
|
2
|
+
|
|
3
|
+
operato-twin 커널(WMS/YMS/MES, zero-dep·결정적)을 things-factory 모듈로 호스팅하는 **walking skeleton**.
|
|
4
|
+
State 를 GraphQL subscription 으로 스트리밍하고, 이벤트를 append-only 로 영속하며, 워커 tick 으로 시뮬을 진행하고, command/scenario 를 mutation 으로 받는다.
|
|
5
|
+
|
|
6
|
+
설계 SoT: `operato-twin/design/integration/things-factory-host.md`. Kernel↔Host 경계(§6): **성능·의존성·스케일·기하 = host/scene, 정합성·결정성·zero-dep = kernel**.
|
|
7
|
+
|
|
8
|
+
## 구조 (integration-base 미러)
|
|
9
|
+
- `engine/twin-engine.ts` — 메모리 인스턴스 맵 + `subscribe→pubsub` 브리지 + 워커 `tick` + append-only 영속. (ScenarioEngine 패턴)
|
|
10
|
+
- `service/twin-state/` — `@Subscription twinState(instanceId)`: 구독 즉시 snapshot 발행 후 delta/clock 스트림. (state-register 패턴)
|
|
11
|
+
- `service/twin-event/` — append-only `TwinEvent` 엔티티(멀티DB `simple-json` payload).
|
|
12
|
+
- `service/twin-control/` — `dispatchTwinCommand`·`controlTwinScenario` mutation.
|
|
13
|
+
|
|
14
|
+
## 커널 연동
|
|
15
|
+
- 커널은 ESM+CJS 이중배포. 이 모듈(CommonJS, Node 20)은 **CJS 번들을 `require`** 로 로드(require(ESM) 회피). 타입은 `import type` 로 `.d.ts` 참조. import·require 모두 패키지명 `@operato/twin-kernel`.
|
|
16
|
+
- `package.json` 의존: `"@operato/twin-kernel": "^0.0.1"` (npm 공개 배포). 로컬 개발은 `yarn link @operato/twin-kernel` 또는 file: 로 대체 가능.
|
|
17
|
+
|
|
18
|
+
## 빌드·등록·검증 (things-factory 환경)
|
|
19
|
+
1. **커널 준비**: `@operato/twin-kernel` 이 npm 에 배포돼 있어야 함(또는 로컬 링크). 소비되는 건 dist(ESM+d.ts) + dist-cjs(CJS).
|
|
20
|
+
2. **설치·빌드**: 모노레포에서 `yarn install`(→ `@operato/twin-kernel` 설치) 후 `npm run build`(tsc → `dist-server`).
|
|
21
|
+
3. **모듈 등록**: 앱 패키지가 `@things-factory/headless-twin` 를 의존해야 `orderedModuleNames` 가 로드(schema/entity 병합).
|
|
22
|
+
4. **인스턴스 기동**: `TwinEngine.start(id, domainId, 'wms', boardDef)` (부팅 설정 or 관리 mutation 에서). BoardDef→씬 저작물 어댑터는 향후 — 지금은 코드 BoardDef.
|
|
23
|
+
5. **검증**: `subscription { twinState(instanceId) { kind revision payload } }` → snapshot 먼저, 이후 delta/clock. `mutation controlTwinScenario(instanceId, "start")` → delta 흐름 + `TwinEvent` 행 적재.
|
|
24
|
+
|
|
25
|
+
## ⚠️ 이 환경(operato-twin)에서 미검증 — things-factory 첫 빌드에서 확인 필요
|
|
26
|
+
type-graphql/TypeORM/@things-factory/shell 툴체인이 없어 여기선 빌드 못 함. 첫 빌드에서 점검:
|
|
27
|
+
- `require('@operato/twin-kernel')` 타이핑(@types/node NodeRequire → any) 및 CJS 해상도.
|
|
28
|
+
- `ScalarObject` 를 `@Field` 타입 + `simple-json` Column 조합(엔티티/구독 payload).
|
|
29
|
+
- `@Subscription(returns => …, { subscribe, filter })` 시그니처, `pubsub.publish/subscribe` 페이로드 형태.
|
|
30
|
+
- `getRepository(TwinEvent)` 제너릭 + `bootstrap-module-start` 페이로드.
|
|
31
|
+
|
|
32
|
+
## 스케일 하드닝 (향후, §host 경계)
|
|
33
|
+
- 워커 tick 은 스켈레톤 `setInterval`(메인 루프). 긴 시뮬·다수 인스턴스는 **worker thread 오프-루프**.
|
|
34
|
+
- ingest 검증 throughput 필요 시 **host 에서 Ajv 컴파일**(커널 zero-dep 검증기는 정본 유지).
|
|
35
|
+
- 재부팅 복구: `TwinEvent`(DB) → 커널 `replay` 로 상태 재구성.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './twin-engine.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/engine/index.ts"],"names":[],"mappings":";;;AAAA,2DAAgC","sourcesContent":["export * from './twin-engine.js'\n"]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { BoardDef, TwinRuntime as TwinRuntimeType } from '@operato/twin-kernel';
|
|
2
|
+
interface InstanceRuntime {
|
|
3
|
+
id: string;
|
|
4
|
+
domainId: string;
|
|
5
|
+
runtime: TwinRuntimeType;
|
|
6
|
+
unsub: () => void;
|
|
7
|
+
timer?: any;
|
|
8
|
+
}
|
|
9
|
+
export declare class TwinEngine {
|
|
10
|
+
static instances: Record<string, InstanceRuntime>;
|
|
11
|
+
/** 라이브 런타임이 없을 때(복구 후 미기동) 저널에서 재구성한 상태 캐시. */
|
|
12
|
+
static recovered: Record<string, any>;
|
|
13
|
+
static TICK_MS: number;
|
|
14
|
+
/**
|
|
15
|
+
* 부팅 훅 — DB 에 status='running' 으로 남은 인스턴스를 저널에서 상태 재구성(DB→replay).
|
|
16
|
+
* 라이브 tick 재개(sim 결정적 re-run / live 인제스트 지속)는 후속. 여기선 상태 복원 + 노출.
|
|
17
|
+
*/
|
|
18
|
+
static bootstrap(): Promise<void>;
|
|
19
|
+
/** 트윈 인스턴스 시작 — 커널 생성 + 보드 로드 + State 스트림 브리지 + 워커 tick + 레지스트리 영속. */
|
|
20
|
+
static start(id: string, domainId: string, kind: string, board: BoardDef): InstanceRuntime;
|
|
21
|
+
/** 레지스트리 upsert(도메인+instanceId 유니크). */
|
|
22
|
+
static register(domainId: string, instanceId: string, kind: string, board: BoardDef): Promise<void>;
|
|
23
|
+
static persist(domainId: string, instanceId: string, msg: any): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* 재부팅 복구 / 시간여행 — DB 저널을 replay 해 상태 재구성.
|
|
26
|
+
* board 는 레지스트리(TwinInstance)에서, 이벤트는 TwinEvent(revision ASC)에서. untilRevision 지정 시 그 시점까지.
|
|
27
|
+
*/
|
|
28
|
+
static recover(domainId: string, instanceId: string, untilRevision?: number): Promise<any>;
|
|
29
|
+
static stop(id: string): Promise<void>;
|
|
30
|
+
static runtime(id: string): TwinRuntimeType | undefined;
|
|
31
|
+
/** 현재 전체 스냅샷 — 라이브 우선, 없으면 저널 복구 캐시. */
|
|
32
|
+
static snapshot(id: string): any;
|
|
33
|
+
}
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* TwinEngine — operato-twin 커널 인스턴스를 things-factory 위에서 호스팅.
|
|
4
|
+
* integration-base 의 ScenarioEngine 패턴 미러: 메모리 인스턴스 맵 + 백그라운드 tick + pubsub 상태 발행.
|
|
5
|
+
*
|
|
6
|
+
* 커널(zero-dep, ESM+CJS 이중배포)은 CJS require 로 로드(Node 20 CommonJS 소비 — require(ESM) 회피).
|
|
7
|
+
* 커널은 결정적 논리 연산 코어 — 이 host 가 전송(pubsub)·영속(TypeORM)·스케줄(tick)·복구(replay)만 얇게 래핑.
|
|
8
|
+
* (설계 SoT: operato-twin/design/integration/things-factory-host.md)
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.TwinEngine = void 0;
|
|
12
|
+
const shell_1 = require("@things-factory/shell");
|
|
13
|
+
const twin_event_js_1 = require("../service/twin-event/twin-event.js");
|
|
14
|
+
const twin_instance_js_1 = require("../service/twin-instance/twin-instance.js");
|
|
15
|
+
/* 커널 런타임 로드 — CJS 번들(dist-cjs). 타입은 위 import type 로. replay = 이벤트열→상태 재구성(복구·시간여행). */
|
|
16
|
+
const { WmsKernel, YmsKernel, MesKernel, TwinRuntime, replay } = require('@operato/twin-kernel');
|
|
17
|
+
const KERNELS = { wms: WmsKernel, yms: YmsKernel, mes: MesKernel };
|
|
18
|
+
class TwinEngine {
|
|
19
|
+
static { this.instances = {}; }
|
|
20
|
+
/** 라이브 런타임이 없을 때(복구 후 미기동) 저널에서 재구성한 상태 캐시. */
|
|
21
|
+
static { this.recovered = {}; }
|
|
22
|
+
static { this.TICK_MS = 1000; }
|
|
23
|
+
/**
|
|
24
|
+
* 부팅 훅 — DB 에 status='running' 으로 남은 인스턴스를 저널에서 상태 재구성(DB→replay).
|
|
25
|
+
* 라이브 tick 재개(sim 결정적 re-run / live 인제스트 지속)는 후속. 여기선 상태 복원 + 노출.
|
|
26
|
+
*/
|
|
27
|
+
static async bootstrap() {
|
|
28
|
+
try {
|
|
29
|
+
const rows = await (0, shell_1.getRepository)(twin_instance_js_1.TwinInstance).find({ where: { status: 'running' } });
|
|
30
|
+
for (const row of rows) {
|
|
31
|
+
if (!row.domainId || !row.instanceId)
|
|
32
|
+
continue;
|
|
33
|
+
const state = await this.recover(row.domainId, row.instanceId).catch(() => null);
|
|
34
|
+
if (state) {
|
|
35
|
+
this.recovered[row.instanceId] = { revision: state.revision, state };
|
|
36
|
+
console.log(`[twin-engine] recovered "${row.instanceId}" from journal → revision ${state.revision}.`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
console.error('[twin-engine] recovery scan failed', err);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** 트윈 인스턴스 시작 — 커널 생성 + 보드 로드 + State 스트림 브리지 + 워커 tick + 레지스트리 영속. */
|
|
45
|
+
static start(id, domainId, kind, board) {
|
|
46
|
+
if (this.instances[id])
|
|
47
|
+
return this.instances[id];
|
|
48
|
+
const Kernel = KERNELS[kind] ?? WmsKernel;
|
|
49
|
+
const kernel = new Kernel(domainId);
|
|
50
|
+
kernel.loadBoard(board);
|
|
51
|
+
const runtime = new TwinRuntime(kernel);
|
|
52
|
+
/* State 채널: runtime.subscribe(snapshot→delta→clock) → pubsub 방송(구독 리졸버가 instanceId 필터). */
|
|
53
|
+
const sub = runtime.subscribe((msg) => {
|
|
54
|
+
shell_1.pubsub.publish('twin-state', {
|
|
55
|
+
twinState: { instanceId: id, kind: msg.kind, revision: msg.revision, payload: msg }
|
|
56
|
+
});
|
|
57
|
+
/* 영속: 이벤트(delta)만 append-only 저장. 재부팅 시 이 저널을 replay 해 복구. */
|
|
58
|
+
if (msg.kind === 'delta')
|
|
59
|
+
this.persist(domainId, id, msg).catch(err => console.error('twin persist fail', err));
|
|
60
|
+
});
|
|
61
|
+
/* subscribe 는 RuntimeSubscription({ unsubscribe() }) 반환 → () => void 로 감쌈. */
|
|
62
|
+
const inst = { id, domainId, runtime, unsub: () => sub.unsubscribe() };
|
|
63
|
+
this.instances[id] = inst;
|
|
64
|
+
delete this.recovered[id]; // 라이브가 우선
|
|
65
|
+
/* 복구 앵커: 레지스트리에 board/kind/status 영속(재부팅 시 이게 있어야 replay 가능). */
|
|
66
|
+
this.register(domainId, id, kind, board).catch(err => console.error('twin register fail', err));
|
|
67
|
+
/* 워커 tick — 스켈레톤은 setInterval(메인 루프). 긴 시뮬 오프-루프(worker thread)는 스케일 하드닝(향후, §host 경계). */
|
|
68
|
+
inst.timer = setInterval(() => runtime.tick(this.TICK_MS), this.TICK_MS);
|
|
69
|
+
return inst;
|
|
70
|
+
}
|
|
71
|
+
/** 레지스트리 upsert(도메인+instanceId 유니크). */
|
|
72
|
+
static async register(domainId, instanceId, kind, board) {
|
|
73
|
+
const repo = (0, shell_1.getRepository)(twin_instance_js_1.TwinInstance);
|
|
74
|
+
const existing = await repo.findOne({ where: { domain: { id: domainId }, instanceId } });
|
|
75
|
+
await repo.save(repo.create({
|
|
76
|
+
...(existing ?? {}),
|
|
77
|
+
domain: { id: domainId },
|
|
78
|
+
instanceId,
|
|
79
|
+
kind,
|
|
80
|
+
board,
|
|
81
|
+
status: 'running'
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
static async persist(domainId, instanceId, msg) {
|
|
85
|
+
const repo = (0, shell_1.getRepository)(twin_event_js_1.TwinEvent);
|
|
86
|
+
const e = msg.event;
|
|
87
|
+
await repo.save(repo.create({
|
|
88
|
+
domain: { id: domainId },
|
|
89
|
+
instanceId,
|
|
90
|
+
tenantId: e?.tenantId,
|
|
91
|
+
eventType: e?.eventType,
|
|
92
|
+
revision: msg.revision,
|
|
93
|
+
eventTime: e?.eventTime,
|
|
94
|
+
payload: e
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* 재부팅 복구 / 시간여행 — DB 저널을 replay 해 상태 재구성.
|
|
99
|
+
* board 는 레지스트리(TwinInstance)에서, 이벤트는 TwinEvent(revision ASC)에서. untilRevision 지정 시 그 시점까지.
|
|
100
|
+
*/
|
|
101
|
+
static async recover(domainId, instanceId, untilRevision) {
|
|
102
|
+
const reg = await (0, shell_1.getRepository)(twin_instance_js_1.TwinInstance).findOne({ where: { domain: { id: domainId }, instanceId } });
|
|
103
|
+
if (!reg?.board)
|
|
104
|
+
throw new Error(`twin instance "${instanceId}" not registered (no board to replay)`);
|
|
105
|
+
const rows = await (0, shell_1.getRepository)(twin_event_js_1.TwinEvent).find({
|
|
106
|
+
where: { domain: { id: domainId }, instanceId },
|
|
107
|
+
order: { revision: 'ASC' }
|
|
108
|
+
});
|
|
109
|
+
const events = rows
|
|
110
|
+
.filter(r => untilRevision == null || (r.revision ?? 0) <= untilRevision)
|
|
111
|
+
.map(r => r.payload)
|
|
112
|
+
.filter(Boolean);
|
|
113
|
+
return replay(reg.board, events);
|
|
114
|
+
}
|
|
115
|
+
static async stop(id) {
|
|
116
|
+
const i = this.instances[id];
|
|
117
|
+
if (i) {
|
|
118
|
+
clearInterval(i.timer);
|
|
119
|
+
i.unsub();
|
|
120
|
+
delete this.instances[id];
|
|
121
|
+
await (0, shell_1.getRepository)(twin_instance_js_1.TwinInstance)
|
|
122
|
+
.update({ domain: { id: i.domainId }, instanceId: id }, { status: 'stopped' })
|
|
123
|
+
.catch(err => console.error('twin deregister fail', err));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
static runtime(id) {
|
|
127
|
+
return this.instances[id]?.runtime;
|
|
128
|
+
}
|
|
129
|
+
/** 현재 전체 스냅샷 — 라이브 우선, 없으면 저널 복구 캐시. */
|
|
130
|
+
static snapshot(id) {
|
|
131
|
+
return this.instances[id]?.runtime.resync() ?? this.recovered[id];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
exports.TwinEngine = TwinEngine;
|
|
135
|
+
//# sourceMappingURL=twin-engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"twin-engine.js","sourceRoot":"","sources":["../../server/engine/twin-engine.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * TwinEngine — operato-twin 커널 인스턴스를 things-factory 위에서 호스팅.\n * integration-base 의 ScenarioEngine 패턴 미러: 메모리 인스턴스 맵 + 백그라운드 tick + pubsub 상태 발행.\n *\n * 커널(zero-dep, ESM+CJS 이중배포)은 CJS require 로 로드(Node 20 CommonJS 소비 — require(ESM) 회피).\n * 커널은 결정적 논리 연산 코어 — 이 host 가 전송(pubsub)·영속(TypeORM)·스케줄(tick)·복구(replay)만 얇게 래핑.\n * (설계 SoT: operato-twin/design/integration/things-factory-host.md)\n */\n\nimport { pubsub, getRepository } from '@things-factory/shell'\n\nimport { TwinEvent } from '../service/twin-event/twin-event.js'\nimport { TwinInstance } from '../service/twin-instance/twin-instance.js'\n\nimport type { TwinKernel, BoardDef, SubscriptionMessage, TwinRuntime as TwinRuntimeType } from '@operato/twin-kernel'\n\n/* 커널 런타임 로드 — CJS 번들(dist-cjs). 타입은 위 import type 로. replay = 이벤트열→상태 재구성(복구·시간여행). */\nconst { WmsKernel, YmsKernel, MesKernel, TwinRuntime, replay } = require('@operato/twin-kernel')\n\nconst KERNELS: Record<string, any> = { wms: WmsKernel, yms: YmsKernel, mes: MesKernel }\n\n/* 인메모리 라이브 런타임 홀더(영속 엔티티 TwinInstance 와 구분). */\ninterface InstanceRuntime {\n id: string\n domainId: string\n runtime: TwinRuntimeType\n unsub: () => void\n timer?: any\n}\n\nexport class TwinEngine {\n static instances: Record<string, InstanceRuntime> = {}\n /** 라이브 런타임이 없을 때(복구 후 미기동) 저널에서 재구성한 상태 캐시. */\n static recovered: Record<string, any> = {}\n static TICK_MS = 1000\n\n /**\n * 부팅 훅 — DB 에 status='running' 으로 남은 인스턴스를 저널에서 상태 재구성(DB→replay).\n * 라이브 tick 재개(sim 결정적 re-run / live 인제스트 지속)는 후속. 여기선 상태 복원 + 노출.\n */\n static async bootstrap(): Promise<void> {\n try {\n const rows = await getRepository(TwinInstance).find({ where: { status: 'running' } })\n for (const row of rows) {\n if (!row.domainId || !row.instanceId) continue\n const state = await this.recover(row.domainId, row.instanceId).catch(() => null)\n if (state) {\n this.recovered[row.instanceId] = { revision: state.revision, state }\n console.log(`[twin-engine] recovered \"${row.instanceId}\" from journal → revision ${state.revision}.`)\n }\n }\n } catch (err) {\n console.error('[twin-engine] recovery scan failed', err)\n }\n }\n\n /** 트윈 인스턴스 시작 — 커널 생성 + 보드 로드 + State 스트림 브리지 + 워커 tick + 레지스트리 영속. */\n static start(id: string, domainId: string, kind: string, board: BoardDef): InstanceRuntime {\n if (this.instances[id]) return this.instances[id]\n\n const Kernel = KERNELS[kind] ?? WmsKernel\n const kernel: TwinKernel = new Kernel(domainId)\n kernel.loadBoard(board)\n const runtime: TwinRuntimeType = new TwinRuntime(kernel)\n\n /* State 채널: runtime.subscribe(snapshot→delta→clock) → pubsub 방송(구독 리졸버가 instanceId 필터). */\n const sub = runtime.subscribe((msg: SubscriptionMessage) => {\n pubsub.publish('twin-state', {\n twinState: { instanceId: id, kind: msg.kind, revision: (msg as any).revision, payload: msg }\n })\n /* 영속: 이벤트(delta)만 append-only 저장. 재부팅 시 이 저널을 replay 해 복구. */\n if (msg.kind === 'delta') this.persist(domainId, id, msg).catch(err => console.error('twin persist fail', err))\n })\n\n /* subscribe 는 RuntimeSubscription({ unsubscribe() }) 반환 → () => void 로 감쌈. */\n const inst: InstanceRuntime = { id, domainId, runtime, unsub: () => sub.unsubscribe() }\n this.instances[id] = inst\n delete this.recovered[id] // 라이브가 우선\n\n /* 복구 앵커: 레지스트리에 board/kind/status 영속(재부팅 시 이게 있어야 replay 가능). */\n this.register(domainId, id, kind, board).catch(err => console.error('twin register fail', err))\n\n /* 워커 tick — 스켈레톤은 setInterval(메인 루프). 긴 시뮬 오프-루프(worker thread)는 스케일 하드닝(향후, §host 경계). */\n inst.timer = setInterval(() => runtime.tick(this.TICK_MS), this.TICK_MS)\n return inst\n }\n\n /** 레지스트리 upsert(도메인+instanceId 유니크). */\n static async register(domainId: string, instanceId: string, kind: string, board: BoardDef): Promise<void> {\n const repo = getRepository(TwinInstance)\n const existing = await repo.findOne({ where: { domain: { id: domainId }, instanceId } })\n await repo.save(\n repo.create({\n ...(existing ?? {}),\n domain: { id: domainId } as any,\n instanceId,\n kind,\n board,\n status: 'running'\n })\n )\n }\n\n static async persist(domainId: string, instanceId: string, msg: any): Promise<void> {\n const repo = getRepository(TwinEvent)\n const e = msg.event\n await repo.save(\n repo.create({\n domain: { id: domainId } as any,\n instanceId,\n tenantId: e?.tenantId,\n eventType: e?.eventType,\n revision: msg.revision,\n eventTime: e?.eventTime,\n payload: e\n })\n )\n }\n\n /**\n * 재부팅 복구 / 시간여행 — DB 저널을 replay 해 상태 재구성.\n * board 는 레지스트리(TwinInstance)에서, 이벤트는 TwinEvent(revision ASC)에서. untilRevision 지정 시 그 시점까지.\n */\n static async recover(domainId: string, instanceId: string, untilRevision?: number): Promise<any> {\n const reg = await getRepository(TwinInstance).findOne({ where: { domain: { id: domainId }, instanceId } })\n if (!reg?.board) throw new Error(`twin instance \"${instanceId}\" not registered (no board to replay)`)\n\n const rows = await getRepository(TwinEvent).find({\n where: { domain: { id: domainId }, instanceId },\n order: { revision: 'ASC' }\n })\n const events = rows\n .filter(r => untilRevision == null || (r.revision ?? 0) <= untilRevision)\n .map(r => r.payload)\n .filter(Boolean)\n\n return replay(reg.board as BoardDef, events)\n }\n\n static async stop(id: string): Promise<void> {\n const i = this.instances[id]\n if (i) {\n clearInterval(i.timer)\n i.unsub()\n delete this.instances[id]\n await getRepository(TwinInstance)\n .update({ domain: { id: i.domainId }, instanceId: id }, { status: 'stopped' })\n .catch(err => console.error('twin deregister fail', err))\n }\n }\n\n static runtime(id: string): TwinRuntimeType | undefined {\n return this.instances[id]?.runtime\n }\n\n /** 현재 전체 스냅샷 — 라이브 우선, 없으면 저널 복구 캐시. */\n static snapshot(id: string): any {\n return this.instances[id]?.runtime.resync() ?? this.recovered[id]\n }\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
tslib_1.__exportStar(require("./engine/index.js"), exports);
|
|
5
|
+
tslib_1.__exportStar(require("./service/index.js"), exports);
|
|
6
|
+
require("./routes.js");
|
|
7
|
+
const index_js_1 = require("./engine/index.js");
|
|
8
|
+
/* 모듈 부팅 — 영속 인스턴스 복구 훅(향후). 지금은 명시 start(mutation/부팅 설정)로 인스턴스 생성. */
|
|
9
|
+
process.on('bootstrap-module-start', async ({ app, config, client }) => {
|
|
10
|
+
try {
|
|
11
|
+
await index_js_1.TwinEngine.bootstrap();
|
|
12
|
+
console.log('Headless Twin host has started.');
|
|
13
|
+
}
|
|
14
|
+
catch (ex) {
|
|
15
|
+
console.error('Headless Twin host failed to start.', ex);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;AAAA,4DAAiC;AACjC,6DAAkC;AAElC,uBAAoB;AAEpB,gDAA8C;AAE9C,sEAAsE;AACtE,OAAO,CAAC,EAAE,CAAC,wBAA+B,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAO,EAAE,EAAE;IACjF,IAAI,CAAC;QACH,MAAM,qBAAU,CAAC,SAAS,EAAE,CAAA;QAC5B,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAA;IAChD,CAAC;IAAC,OAAO,EAAE,EAAE,CAAC;QACZ,OAAO,CAAC,KAAK,CAAC,qCAAqC,EAAE,EAAE,CAAC,CAAA;IAC1D,CAAC;AACH,CAAC,CAAC,CAAA","sourcesContent":["export * from './engine/index.js'\nexport * from './service/index.js'\n\nimport './routes.js'\n\nimport { TwinEngine } from './engine/index.js'\n\n/* 모듈 부팅 — 영속 인스턴스 복구 훅(향후). 지금은 명시 start(mutation/부팅 설정)로 인스턴스 생성. */\nprocess.on('bootstrap-module-start' as any, async ({ app, config, client }: any) => {\n try {\n await TwinEngine.bootstrap()\n console.log('Headless Twin host has started.')\n } catch (ex) {\n console.error('Headless Twin host failed to start.', ex)\n }\n})\n"]}
|
|
File without changes
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/* 헤드리스 트윈은 GraphQL(subscription/mutation)로 동작 — REST 라우터는 미사용(빈 부트스트랩). */
|
|
2
|
+
process.on('bootstrap-module-global-public-route', (app, globalPublicRouter) => { });
|
|
3
|
+
process.on('bootstrap-module-global-private-route', (app, globalPrivateRouter) => { });
|
|
4
|
+
process.on('bootstrap-module-domain-public-route', (app, domainPublicRouter) => { });
|
|
5
|
+
process.on('bootstrap-module-domain-private-route', (app, domainPrivateRouter) => { });
|
|
6
|
+
//# sourceMappingURL=routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../server/routes.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,OAAO,CAAC,EAAE,CAAC,sCAA6C,EAAE,CAAC,GAAG,EAAE,kBAAkB,EAAE,EAAE,GAAE,CAAC,CAAC,CAAA;AAC1F,OAAO,CAAC,EAAE,CAAC,uCAA8C,EAAE,CAAC,GAAG,EAAE,mBAAmB,EAAE,EAAE,GAAE,CAAC,CAAC,CAAA;AAC5F,OAAO,CAAC,EAAE,CAAC,sCAA6C,EAAE,CAAC,GAAG,EAAE,kBAAkB,EAAE,EAAE,GAAE,CAAC,CAAC,CAAA;AAC1F,OAAO,CAAC,EAAE,CAAC,uCAA8C,EAAE,CAAC,GAAG,EAAE,mBAAmB,EAAE,EAAE,GAAE,CAAC,CAAC,CAAA","sourcesContent":["/* 헤드리스 트윈은 GraphQL(subscription/mutation)로 동작 — REST 라우터는 미사용(빈 부트스트랩). */\nprocess.on('bootstrap-module-global-public-route' as any, (app, globalPublicRouter) => {})\nprocess.on('bootstrap-module-global-private-route' as any, (app, globalPrivateRouter) => {})\nprocess.on('bootstrap-module-domain-public-route' as any, (app, domainPublicRouter) => {})\nprocess.on('bootstrap-module-domain-private-route' as any, (app, domainPrivateRouter) => {})\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './twin-event/twin-event.js';
|
|
2
|
+
export * from './twin-instance/twin-instance.js';
|
|
3
|
+
export * from './twin-state/twin-state-subscription.js';
|
|
4
|
+
export declare const entities: (typeof import("./twin-event/twin-event.js").TwinEvent | typeof import("./twin-instance/twin-instance.js").TwinInstance)[];
|
|
5
|
+
export declare const subscribers: any[];
|
|
6
|
+
export declare const schema: {
|
|
7
|
+
typeDefs: {};
|
|
8
|
+
resolverClasses: (typeof import("./twin-state/twin-state-subscription.js").TwinStateSubscription | typeof import("./twin-control/twin-control-mutation.js").TwinControlMutation | typeof import("./twin-lifecycle/twin-lifecycle-mutation.js").TwinLifecycleMutation | typeof import("./twin-journal/twin-journal-query.js").TwinJournalQuery)[];
|
|
9
|
+
directives: {};
|
|
10
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.schema = exports.subscribers = exports.entities = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
/* EXPORT ENTITY TYPES */
|
|
6
|
+
tslib_1.__exportStar(require("./twin-event/twin-event.js"), exports);
|
|
7
|
+
tslib_1.__exportStar(require("./twin-instance/twin-instance.js"), exports);
|
|
8
|
+
tslib_1.__exportStar(require("./twin-state/twin-state-subscription.js"), exports);
|
|
9
|
+
/* IMPORT ENTITIES AND RESOLVERS */
|
|
10
|
+
const index_js_1 = require("./twin-event/index.js");
|
|
11
|
+
const index_js_2 = require("./twin-instance/index.js");
|
|
12
|
+
const index_js_3 = require("./twin-state/index.js");
|
|
13
|
+
const index_js_4 = require("./twin-control/index.js");
|
|
14
|
+
const index_js_5 = require("./twin-lifecycle/index.js");
|
|
15
|
+
const index_js_6 = require("./twin-journal/index.js");
|
|
16
|
+
exports.entities = [
|
|
17
|
+
/* ENTITIES */
|
|
18
|
+
...index_js_1.entities,
|
|
19
|
+
...index_js_2.entities
|
|
20
|
+
];
|
|
21
|
+
exports.subscribers = [];
|
|
22
|
+
exports.schema = {
|
|
23
|
+
typeDefs: {},
|
|
24
|
+
resolverClasses: [
|
|
25
|
+
/* RESOLVER CLASSES */
|
|
26
|
+
...index_js_3.resolvers,
|
|
27
|
+
...index_js_4.resolvers,
|
|
28
|
+
...index_js_5.resolvers,
|
|
29
|
+
...index_js_6.resolvers
|
|
30
|
+
],
|
|
31
|
+
directives: {}
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,yBAAyB;AACzB,qEAA0C;AAC1C,2EAAgD;AAChD,kFAAuD;AAEvD,mCAAmC;AACnC,oDAAqE;AACrE,uDAA2E;AAC3E,oDAAuE;AACvE,sDAA2E;AAC3E,wDAA+E;AAC/E,sDAA2E;AAE9D,QAAA,QAAQ,GAAG;IACtB,cAAc;IACd,GAAG,mBAAiB;IACpB,GAAG,mBAAoB;CACxB,CAAA;AAEY,QAAA,WAAW,GAAG,EAAE,CAAA;AAEhB,QAAA,MAAM,GAAG;IACpB,QAAQ,EAAE,EAAE;IACZ,eAAe,EAAE;QACf,sBAAsB;QACtB,GAAG,oBAAkB;QACrB,GAAG,oBAAoB;QACvB,GAAG,oBAAsB;QACzB,GAAG,oBAAoB;KACxB;IACD,UAAU,EAAE,EAAE;CACf,CAAA","sourcesContent":["/* EXPORT ENTITY TYPES */\nexport * from './twin-event/twin-event.js'\nexport * from './twin-instance/twin-instance.js'\nexport * from './twin-state/twin-state-subscription.js'\n\n/* IMPORT ENTITIES AND RESOLVERS */\nimport { entities as TwinEventEntities } from './twin-event/index.js'\nimport { entities as TwinInstanceEntities } from './twin-instance/index.js'\nimport { resolvers as TwinStateResolvers } from './twin-state/index.js'\nimport { resolvers as TwinControlResolvers } from './twin-control/index.js'\nimport { resolvers as TwinLifecycleResolvers } from './twin-lifecycle/index.js'\nimport { resolvers as TwinJournalResolvers } from './twin-journal/index.js'\n\nexport const entities = [\n /* ENTITIES */\n ...TwinEventEntities,\n ...TwinInstanceEntities\n]\n\nexport const subscribers = []\n\nexport const schema = {\n typeDefs: {},\n resolverClasses: [\n /* RESOLVER CLASSES */\n ...TwinStateResolvers,\n ...TwinControlResolvers,\n ...TwinLifecycleResolvers,\n ...TwinJournalResolvers\n ],\n directives: {}\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolvers = void 0;
|
|
4
|
+
const twin_control_mutation_js_1 = require("./twin-control-mutation.js");
|
|
5
|
+
exports.resolvers = [twin_control_mutation_js_1.TwinControlMutation];
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/twin-control/index.ts"],"names":[],"mappings":";;;AAAA,yEAAgE;AAEnD,QAAA,SAAS,GAAG,CAAC,8CAAmB,CAAC,CAAA","sourcesContent":["import { TwinControlMutation } from './twin-control-mutation.js'\n\nexport const resolvers = [TwinControlMutation]\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare class TwinControlMutation {
|
|
2
|
+
/** Command 채널 — 오더 hold/resume/release 등. 커널 dispatch 로 위임, ack 반환. */
|
|
3
|
+
dispatchTwinCommand(instanceId: string, command: any): Promise<boolean>;
|
|
4
|
+
/** Scenario 채널 — 생성기 시나리오 제어(load/start/pause). */
|
|
5
|
+
controlTwinScenario(instanceId: string, action: string, scenario?: any): Promise<boolean>;
|
|
6
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TwinControlMutation = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const type_graphql_1 = require("type-graphql");
|
|
6
|
+
const shell_1 = require("@things-factory/shell");
|
|
7
|
+
const index_js_1 = require("../../engine/index.js");
|
|
8
|
+
/*
|
|
9
|
+
* 트윈 Command·Scenario 채널 — 상향 mutation.
|
|
10
|
+
* Command: runtime.dispatch(행위). Scenario: runtime.scenario.load/start/pause(예측 자극).
|
|
11
|
+
* (State 하향은 twin-state subscription. tick 은 워커.)
|
|
12
|
+
*/
|
|
13
|
+
let TwinControlMutation = class TwinControlMutation {
|
|
14
|
+
/** Command 채널 — 오더 hold/resume/release 등. 커널 dispatch 로 위임, ack 반환. */
|
|
15
|
+
async dispatchTwinCommand(instanceId, command) {
|
|
16
|
+
const runtime = index_js_1.TwinEngine.runtime(instanceId);
|
|
17
|
+
if (!runtime)
|
|
18
|
+
return false;
|
|
19
|
+
const ack = runtime.dispatch(command);
|
|
20
|
+
return !!ack?.accepted;
|
|
21
|
+
}
|
|
22
|
+
/** Scenario 채널 — 생성기 시나리오 제어(load/start/pause). */
|
|
23
|
+
async controlTwinScenario(instanceId, action, scenario) {
|
|
24
|
+
const runtime = index_js_1.TwinEngine.runtime(instanceId);
|
|
25
|
+
if (!runtime)
|
|
26
|
+
return false;
|
|
27
|
+
if (action === 'load' && scenario)
|
|
28
|
+
runtime.scenario.load(scenario);
|
|
29
|
+
else if (action === 'start')
|
|
30
|
+
runtime.scenario.start();
|
|
31
|
+
else if (action === 'pause')
|
|
32
|
+
runtime.scenario.pause();
|
|
33
|
+
else
|
|
34
|
+
return false;
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
exports.TwinControlMutation = TwinControlMutation;
|
|
39
|
+
tslib_1.__decorate([
|
|
40
|
+
(0, type_graphql_1.Mutation)(returns => Boolean, { description: 'dispatch a twin command (order.hold/resume/release …)' }),
|
|
41
|
+
tslib_1.__param(0, (0, type_graphql_1.Arg)('instanceId')),
|
|
42
|
+
tslib_1.__param(1, (0, type_graphql_1.Arg)('command', type => shell_1.ScalarObject)),
|
|
43
|
+
tslib_1.__metadata("design:type", Function),
|
|
44
|
+
tslib_1.__metadata("design:paramtypes", [String, Object]),
|
|
45
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
46
|
+
], TwinControlMutation.prototype, "dispatchTwinCommand", null);
|
|
47
|
+
tslib_1.__decorate([
|
|
48
|
+
(0, type_graphql_1.Mutation)(returns => Boolean, { description: 'control twin scenario (load|start|pause)' }),
|
|
49
|
+
tslib_1.__param(0, (0, type_graphql_1.Arg)('instanceId')),
|
|
50
|
+
tslib_1.__param(1, (0, type_graphql_1.Arg)('action')),
|
|
51
|
+
tslib_1.__param(2, (0, type_graphql_1.Arg)('scenario', type => shell_1.ScalarObject, { nullable: true })),
|
|
52
|
+
tslib_1.__metadata("design:type", Function),
|
|
53
|
+
tslib_1.__metadata("design:paramtypes", [String, String, Object]),
|
|
54
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
55
|
+
], TwinControlMutation.prototype, "controlTwinScenario", null);
|
|
56
|
+
exports.TwinControlMutation = TwinControlMutation = tslib_1.__decorate([
|
|
57
|
+
(0, type_graphql_1.Resolver)()
|
|
58
|
+
], TwinControlMutation);
|
|
59
|
+
//# sourceMappingURL=twin-control-mutation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"twin-control-mutation.js","sourceRoot":"","sources":["../../../server/service/twin-control/twin-control-mutation.ts"],"names":[],"mappings":";;;;AAAA,+CAAsD;AAEtD,iDAAoD;AAEpD,oDAAkD;AAElD;;;;GAIG;AAEI,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;IAC9B,uEAAuE;IAEjE,AAAN,KAAK,CAAC,mBAAmB,CACJ,UAAkB,EACC,OAAY;QAElD,MAAM,OAAO,GAAG,qBAAU,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;QAC9C,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAA;QAC1B,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;QACrC,OAAO,CAAC,CAAC,GAAG,EAAE,QAAQ,CAAA;IACxB,CAAC;IAED,mDAAmD;IAE7C,AAAN,KAAK,CAAC,mBAAmB,CACJ,UAAkB,EACtB,MAAc,EAC8B,QAAc;QAEzE,MAAM,OAAO,GAAG,qBAAU,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;QAC9C,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAA;QAC1B,IAAI,MAAM,KAAK,MAAM,IAAI,QAAQ;YAAE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;aAC7D,IAAI,MAAM,KAAK,OAAO;YAAE,OAAO,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAA;aAChD,IAAI,MAAM,KAAK,OAAO;YAAE,OAAO,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAA;;YAChD,OAAO,KAAK,CAAA;QACjB,OAAO,IAAI,CAAA;IACb,CAAC;CACF,CAAA;AA5BY,kDAAmB;AAGxB;IADL,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,uDAAuD,EAAE,CAAC;IAEpG,mBAAA,IAAA,kBAAG,EAAC,YAAY,CAAC,CAAA;IACjB,mBAAA,IAAA,kBAAG,EAAC,SAAS,EAAE,IAAI,CAAC,EAAE,CAAC,oBAAY,CAAC,CAAA;;;;8DAMtC;AAIK;IADL,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,0CAA0C,EAAE,CAAC;IAEvF,mBAAA,IAAA,kBAAG,EAAC,YAAY,CAAC,CAAA;IACjB,mBAAA,IAAA,kBAAG,EAAC,QAAQ,CAAC,CAAA;IACb,mBAAA,IAAA,kBAAG,EAAC,UAAU,EAAE,IAAI,CAAC,EAAE,CAAC,oBAAY,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;;;;8DAS3D;8BA3BU,mBAAmB;IAD/B,IAAA,uBAAQ,GAAE;GACE,mBAAmB,CA4B/B","sourcesContent":["import { Arg, Mutation, Resolver } from 'type-graphql'\n\nimport { ScalarObject } from '@things-factory/shell'\n\nimport { TwinEngine } from '../../engine/index.js'\n\n/*\n * 트윈 Command·Scenario 채널 — 상향 mutation.\n * Command: runtime.dispatch(행위). Scenario: runtime.scenario.load/start/pause(예측 자극).\n * (State 하향은 twin-state subscription. tick 은 워커.)\n */\n@Resolver()\nexport class TwinControlMutation {\n /** Command 채널 — 오더 hold/resume/release 등. 커널 dispatch 로 위임, ack 반환. */\n @Mutation(returns => Boolean, { description: 'dispatch a twin command (order.hold/resume/release …)' })\n async dispatchTwinCommand(\n @Arg('instanceId') instanceId: string,\n @Arg('command', type => ScalarObject) command: any\n ): Promise<boolean> {\n const runtime = TwinEngine.runtime(instanceId)\n if (!runtime) return false\n const ack = runtime.dispatch(command)\n return !!ack?.accepted\n }\n\n /** Scenario 채널 — 생성기 시나리오 제어(load/start/pause). */\n @Mutation(returns => Boolean, { description: 'control twin scenario (load|start|pause)' })\n async controlTwinScenario(\n @Arg('instanceId') instanceId: string,\n @Arg('action') action: string,\n @Arg('scenario', type => ScalarObject, { nullable: true }) scenario?: any\n ): Promise<boolean> {\n const runtime = TwinEngine.runtime(instanceId)\n if (!runtime) return false\n if (action === 'load' && scenario) runtime.scenario.load(scenario)\n else if (action === 'start') runtime.scenario.start()\n else if (action === 'pause') runtime.scenario.pause()\n else return false\n return true\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/twin-event/index.ts"],"names":[],"mappings":";;;AAAA,mDAA2C;AAE9B,QAAA,QAAQ,GAAG,CAAC,yBAAS,CAAC,CAAA","sourcesContent":["import { TwinEvent } from './twin-event.js'\n\nexport const entities = [TwinEvent]\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Domain } from '@things-factory/shell';
|
|
2
|
+
export declare class TwinEvent {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
domain?: Domain;
|
|
5
|
+
domainId?: string;
|
|
6
|
+
instanceId?: string;
|
|
7
|
+
tenantId?: string;
|
|
8
|
+
eventType?: string;
|
|
9
|
+
revision?: number;
|
|
10
|
+
eventTime?: string;
|
|
11
|
+
payload?: any;
|
|
12
|
+
createdAt?: Date;
|
|
13
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TwinEvent = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const typeorm_1 = require("typeorm");
|
|
6
|
+
const type_graphql_1 = require("type-graphql");
|
|
7
|
+
const shell_1 = require("@things-factory/shell");
|
|
8
|
+
/*
|
|
9
|
+
* TwinEvent — append-only 트윈 이벤트 저널(EPCIS 이벤트 + 운영 델타).
|
|
10
|
+
* 커널 EventJournal 의 영속 대응 — 상태=이벤트열의 함수(재부팅 시 DB→replay 재구성).
|
|
11
|
+
* append-only 이므로 updater/deletedAt 없음(이력 CRUD 가 아니라 불변 이벤트 스트림).
|
|
12
|
+
* payload 는 simple-json(멀티DB 이식 — postgres/mysql/sqlite/mssql/oracle 공통, DB-specific JSON 타입 금지).
|
|
13
|
+
* (CLAUDE.md: 모든 @ObjectType/@Field 는 영문 description 필수.)
|
|
14
|
+
*/
|
|
15
|
+
let TwinEvent = class TwinEvent {
|
|
16
|
+
};
|
|
17
|
+
exports.TwinEvent = TwinEvent;
|
|
18
|
+
tslib_1.__decorate([
|
|
19
|
+
(0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
|
|
20
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.ID, { description: 'Unique identifier of the event record.' }),
|
|
21
|
+
tslib_1.__metadata("design:type", String)
|
|
22
|
+
], TwinEvent.prototype, "id", void 0);
|
|
23
|
+
tslib_1.__decorate([
|
|
24
|
+
(0, typeorm_1.ManyToOne)(type => shell_1.Domain),
|
|
25
|
+
(0, type_graphql_1.Field)(type => shell_1.Domain, { nullable: true, description: 'Owning tenant domain.' }),
|
|
26
|
+
tslib_1.__metadata("design:type", shell_1.Domain)
|
|
27
|
+
], TwinEvent.prototype, "domain", void 0);
|
|
28
|
+
tslib_1.__decorate([
|
|
29
|
+
(0, typeorm_1.RelationId)((e) => e.domain),
|
|
30
|
+
tslib_1.__metadata("design:type", String)
|
|
31
|
+
], TwinEvent.prototype, "domainId", void 0);
|
|
32
|
+
tslib_1.__decorate([
|
|
33
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
34
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Twin runtime instance id that emitted the event.' }),
|
|
35
|
+
tslib_1.__metadata("design:type", String)
|
|
36
|
+
], TwinEvent.prototype, "instanceId", void 0);
|
|
37
|
+
tslib_1.__decorate([
|
|
38
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
39
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Kernel tenant id carried on the canonical envelope.' }),
|
|
40
|
+
tslib_1.__metadata("design:type", String)
|
|
41
|
+
], TwinEvent.prototype, "tenantId", void 0);
|
|
42
|
+
tslib_1.__decorate([
|
|
43
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
44
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Event type: epcis.* | task.status | equipment.status | order.status.' }),
|
|
45
|
+
tslib_1.__metadata("design:type", String)
|
|
46
|
+
], TwinEvent.prototype, "eventType", void 0);
|
|
47
|
+
tslib_1.__decorate([
|
|
48
|
+
(0, typeorm_1.Column)({ type: 'int', nullable: true }),
|
|
49
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { nullable: true, description: 'Monotonic state revision at which the event was emitted.' }),
|
|
50
|
+
tslib_1.__metadata("design:type", Number)
|
|
51
|
+
], TwinEvent.prototype, "revision", void 0);
|
|
52
|
+
tslib_1.__decorate([
|
|
53
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
54
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Simulation event time (ISO 8601).' }),
|
|
55
|
+
tslib_1.__metadata("design:type", String)
|
|
56
|
+
], TwinEvent.prototype, "eventTime", void 0);
|
|
57
|
+
tslib_1.__decorate([
|
|
58
|
+
(0, typeorm_1.Column)({ type: 'simple-json', nullable: true }),
|
|
59
|
+
(0, type_graphql_1.Field)(type => shell_1.ScalarObject, { nullable: true, description: 'Raw canonical envelope (EPCIS event or operational delta) as JSON.' }),
|
|
60
|
+
tslib_1.__metadata("design:type", Object)
|
|
61
|
+
], TwinEvent.prototype, "payload", void 0);
|
|
62
|
+
tslib_1.__decorate([
|
|
63
|
+
(0, typeorm_1.CreateDateColumn)(),
|
|
64
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Wall-clock timestamp when the record was persisted.' }),
|
|
65
|
+
tslib_1.__metadata("design:type", Date)
|
|
66
|
+
], TwinEvent.prototype, "createdAt", void 0);
|
|
67
|
+
exports.TwinEvent = TwinEvent = tslib_1.__decorate([
|
|
68
|
+
(0, typeorm_1.Entity)(),
|
|
69
|
+
(0, typeorm_1.Index)('ix_twin_event_0', (e) => [e.domain, e.instanceId, e.revision], { unique: false }),
|
|
70
|
+
(0, type_graphql_1.ObjectType)({ description: 'Append-only twin event journal record (EPCIS event or operational delta).' })
|
|
71
|
+
], TwinEvent);
|
|
72
|
+
//# sourceMappingURL=twin-event.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"twin-event.js","sourceRoot":"","sources":["../../../server/service/twin-event/twin-event.ts"],"names":[],"mappings":";;;;AAAA,qCAAgH;AAChH,+CAAyD;AAEzD,iDAA4D;AAE5D;;;;;;GAMG;AAII,IAAM,SAAS,GAAf,MAAM,SAAS;CAuCrB,CAAA;AAvCY,8BAAS;AAGX;IAFR,IAAA,gCAAsB,EAAC,MAAM,CAAC;IAC9B,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,iBAAE,EAAE,EAAE,WAAW,EAAE,wCAAwC,EAAE,CAAC;;qCAC1D;AAInB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,cAAM,CAAC;IACzB,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,cAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,uBAAuB,EAAE,CAAC;sCACvE,cAAM;yCAAA;AAGf;IADC,IAAA,oBAAU,EAAC,CAAC,CAAY,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;;2CACtB;AAIjB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,kDAAkD,EAAE,CAAC;;6CACxE;AAInB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,qDAAqD,EAAE,CAAC;;2CAC7E;AAIjB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,sEAAsE,EAAE,CAAC;;4CAC7F;AAIlB;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACvC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,kBAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,0DAA0D,EAAE,CAAC;;2CAC/F;AAIjB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,mCAAmC,EAAE,CAAC;;4CAC1D;AAIlB;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC/C,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,oBAAY,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,oEAAoE,EAAE,CAAC;;0CACtH;AAIb;IAFC,IAAA,0BAAgB,GAAE;IAClB,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,qDAAqD,EAAE,CAAC;sCAClF,IAAI;4CAAA;oBAtCL,SAAS;IAHrB,IAAA,gBAAM,GAAE;IACR,IAAA,eAAK,EAAC,iBAAiB,EAAE,CAAC,CAAY,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IACnG,IAAA,yBAAU,EAAC,EAAE,WAAW,EAAE,2EAA2E,EAAE,CAAC;GAC5F,SAAS,CAuCrB","sourcesContent":["import { CreateDateColumn, Entity, Index, Column, RelationId, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'\nimport { ObjectType, Field, ID, Int } from 'type-graphql'\n\nimport { Domain, ScalarObject } from '@things-factory/shell'\n\n/*\n * TwinEvent — append-only 트윈 이벤트 저널(EPCIS 이벤트 + 운영 델타).\n * 커널 EventJournal 의 영속 대응 — 상태=이벤트열의 함수(재부팅 시 DB→replay 재구성).\n * append-only 이므로 updater/deletedAt 없음(이력 CRUD 가 아니라 불변 이벤트 스트림).\n * payload 는 simple-json(멀티DB 이식 — postgres/mysql/sqlite/mssql/oracle 공통, DB-specific JSON 타입 금지).\n * (CLAUDE.md: 모든 @ObjectType/@Field 는 영문 description 필수.)\n */\n@Entity()\n@Index('ix_twin_event_0', (e: TwinEvent) => [e.domain, e.instanceId, e.revision], { unique: false })\n@ObjectType({ description: 'Append-only twin event journal record (EPCIS event or operational delta).' })\nexport class TwinEvent {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Unique identifier of the event record.' })\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true, description: 'Owning tenant domain.' })\n domain?: Domain\n\n @RelationId((e: TwinEvent) => e.domain)\n domainId?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Twin runtime instance id that emitted the event.' })\n instanceId?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Kernel tenant id carried on the canonical envelope.' })\n tenantId?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Event type: epcis.* | task.status | equipment.status | order.status.' })\n eventType?: string\n\n @Column({ type: 'int', nullable: true })\n @Field(type => Int, { nullable: true, description: 'Monotonic state revision at which the event was emitted.' })\n revision?: number\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Simulation event time (ISO 8601).' })\n eventTime?: string\n\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, { nullable: true, description: 'Raw canonical envelope (EPCIS event or operational delta) as JSON.' })\n payload?: any\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'Wall-clock timestamp when the record was persisted.' })\n createdAt?: Date\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/twin-instance/index.ts"],"names":[],"mappings":";;;AAAA,yDAAiD;AAEpC,QAAA,QAAQ,GAAG,CAAC,+BAAY,CAAC,CAAA","sourcesContent":["import { TwinInstance } from './twin-instance.js'\n\nexport const entities = [TwinInstance]\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Domain } from '@things-factory/shell';
|
|
2
|
+
export declare class TwinInstance {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
domain?: Domain;
|
|
5
|
+
domainId?: string;
|
|
6
|
+
instanceId: string;
|
|
7
|
+
kind?: string;
|
|
8
|
+
board?: any;
|
|
9
|
+
status?: string;
|
|
10
|
+
revision?: number;
|
|
11
|
+
createdAt?: Date;
|
|
12
|
+
updatedAt?: Date;
|
|
13
|
+
}
|