@things-factory/headless-twin 10.0.7 → 10.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-server/engine/attention-digest.d.ts +52 -0
- package/dist-server/engine/attention-digest.js +76 -0
- package/dist-server/engine/attention-digest.js.map +1 -0
- package/dist-server/engine/board-vocabulary.d.ts +17 -0
- package/dist-server/engine/board-vocabulary.js +63 -0
- package/dist-server/engine/board-vocabulary.js.map +1 -0
- package/dist-server/engine/command-routing.d.ts +33 -0
- package/dist-server/engine/command-routing.js +53 -0
- package/dist-server/engine/command-routing.js.map +1 -0
- package/dist-server/engine/entity-delta.d.ts +13 -6
- package/dist-server/engine/entity-delta.js +38 -12
- package/dist-server/engine/entity-delta.js.map +1 -1
- package/dist-server/engine/index.d.ts +6 -0
- package/dist-server/engine/index.js +10 -0
- package/dist-server/engine/index.js.map +1 -1
- package/dist-server/engine/kpi-baseline.d.ts +78 -0
- package/dist-server/engine/kpi-baseline.js +123 -0
- package/dist-server/engine/kpi-baseline.js.map +1 -0
- package/dist-server/engine/kpi-broadcast.d.ts +4 -0
- package/dist-server/engine/kpi-broadcast.js +16 -0
- package/dist-server/engine/kpi-broadcast.js.map +1 -0
- package/dist-server/engine/kpi-query.d.ts +31 -0
- package/dist-server/engine/kpi-query.js +50 -2
- package/dist-server/engine/kpi-query.js.map +1 -1
- package/dist-server/engine/live-attentions.d.ts +1 -0
- package/dist-server/engine/live-attentions.js +7 -1
- package/dist-server/engine/live-attentions.js.map +1 -1
- package/dist-server/engine/runtime-key.d.ts +15 -0
- package/dist-server/engine/runtime-key.js +64 -0
- package/dist-server/engine/runtime-key.js.map +1 -0
- package/dist-server/engine/state-axes.d.ts +16 -0
- package/dist-server/engine/state-axes.js +54 -0
- package/dist-server/engine/state-axes.js.map +1 -0
- package/dist-server/engine/twin-engine.d.ts +92 -16
- package/dist-server/engine/twin-engine.js +335 -77
- package/dist-server/engine/twin-engine.js.map +1 -1
- package/dist-server/engine/twin-level.d.ts +23 -0
- package/dist-server/engine/twin-level.js +52 -0
- package/dist-server/engine/twin-level.js.map +1 -0
- package/dist-server/engine/warm-start.d.ts +58 -12
- package/dist-server/engine/warm-start.js +80 -9
- package/dist-server/engine/warm-start.js.map +1 -1
- package/dist-server/service/reference/discovery-result.d.ts +34 -0
- package/dist-server/service/reference/discovery-result.js +84 -0
- package/dist-server/service/reference/discovery-result.js.map +1 -0
- package/dist-server/service/reference/ingest-space.d.ts +30 -0
- package/dist-server/service/reference/ingest-space.js +63 -0
- package/dist-server/service/reference/ingest-space.js.map +1 -0
- package/dist-server/service/reference/knob-defaults.d.ts +20 -0
- package/dist-server/service/reference/knob-defaults.js +59 -0
- package/dist-server/service/reference/knob-defaults.js.map +1 -0
- package/dist-server/service/reference/reference-live.js +2 -2
- package/dist-server/service/reference/reference-live.js.map +1 -1
- package/dist-server/service/reference/reference-master.d.ts +37 -2
- package/dist-server/service/reference/reference-master.js +55 -5
- package/dist-server/service/reference/reference-master.js.map +1 -1
- package/dist-server/service/reference/reference-resolver.d.ts +2 -2
- package/dist-server/service/reference/reference-resolver.js +71 -15
- package/dist-server/service/reference/reference-resolver.js.map +1 -1
- package/dist-server/service/twin-attention/twin-attention-query.d.ts +8 -1
- package/dist-server/service/twin-attention/twin-attention-query.js +39 -8
- package/dist-server/service/twin-attention/twin-attention-query.js.map +1 -1
- package/dist-server/service/twin-control/twin-control-mutation.d.ts +2 -0
- package/dist-server/service/twin-control/twin-control-mutation.js +29 -10
- package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
- package/dist-server/service/twin-forecast/twin-forecast-query.js +2 -1
- package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
- package/dist-server/service/twin-instance/twin-instance.js +4 -2
- package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
- package/dist-server/service/twin-journal/twin-journal-query.d.ts +6 -2
- package/dist-server/service/twin-journal/twin-journal-query.js +25 -7
- package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +8 -0
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +23 -1
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
- package/dist-server/service/twin-metrics/twin-metrics-query.js +1 -1
- package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
- package/dist-server/service/twin-space/twin-space-resolver.js +9 -0
- package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
- package/dist-server/service/twin-state/twin-state-subscription.js +1 -1
- package/dist-server/service/twin-state/twin-state-subscription.js.map +1 -1
- package/dist-server/service/twin-structure/twin-structure.js +2 -1
- package/dist-server/service/twin-structure/twin-structure.js.map +1 -1
- package/dist-server/service/twin-target/twin-target-resolver.js +21 -4
- package/dist-server/service/twin-target/twin-target-resolver.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/server/engine/attention-digest.ts +102 -0
- package/server/engine/board-vocabulary.ts +61 -0
- package/server/engine/command-routing.ts +67 -0
- package/server/engine/entity-delta.ts +33 -11
- package/server/engine/index.ts +10 -0
- package/server/engine/kpi-baseline.ts +202 -0
- package/server/engine/kpi-broadcast.ts +13 -0
- package/server/engine/kpi-query.ts +81 -3
- package/server/engine/live-attentions.ts +7 -2
- package/server/engine/runtime-key.ts +58 -0
- package/server/engine/state-axes.ts +55 -0
- package/server/engine/twin-engine.ts +350 -77
- package/server/engine/twin-level.ts +48 -0
- package/server/engine/warm-start.ts +130 -16
- package/server/service/reference/discovery-result.ts +95 -0
- package/server/service/reference/ingest-space.ts +70 -0
- package/server/service/reference/knob-defaults.ts +59 -0
- package/server/service/reference/reference-live.ts +2 -2
- package/server/service/reference/reference-master.ts +94 -8
- package/server/service/reference/reference-resolver.ts +80 -16
- package/server/service/twin-attention/twin-attention-query.ts +43 -6
- package/server/service/twin-control/twin-control-mutation.ts +31 -12
- package/server/service/twin-forecast/twin-forecast-query.ts +3 -2
- package/server/service/twin-instance/twin-instance.ts +6 -2
- package/server/service/twin-journal/twin-journal-query.ts +35 -5
- package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +18 -2
- package/server/service/twin-metrics/twin-metrics-query.ts +1 -1
- package/server/service/twin-space/twin-space-resolver.ts +10 -1
- package/server/service/twin-state/twin-state-subscription.ts +1 -1
- package/server/service/twin-structure/twin-structure.ts +4 -1
- package/server/service/twin-target/twin-target-resolver.ts +22 -4
- package/test/attention-digest.test.ts +135 -0
- package/test/board-vocabulary.test.ts +114 -0
- package/test/capability-mapping.test.ts +4 -4
- package/test/command-routing.test.ts +61 -0
- package/test/discovery-result.test.ts +75 -0
- package/test/entity-delta.test.ts +25 -25
- package/test/ingest-bench.test.ts +3 -3
- package/test/ingest-space.test.ts +50 -0
- package/test/knob-defaults.test.ts +72 -0
- package/test/kpi-baseline-db.test.ts +214 -0
- package/test/kpi-baseline.test.ts +196 -0
- package/test/kpi-query-bench.test.ts +128 -0
- package/test/live-mirror-parity.test.ts +35 -2
- package/test/master-to-twin.test.ts +7 -3
- package/test/mutation-gate.test.ts +108 -0
- package/test/oee-accumulator.test.ts +65 -1
- package/test/registry-key-guard.test.ts +80 -0
- package/test/runtime-key.test.ts +66 -0
- package/test/scale-twin-bench.test.ts +2 -2
- package/test/state-axes.test.ts +74 -0
- package/test/streamline-e2e.test.ts +2 -2
- package/test/structure-revision-db.test.ts +5 -4
- package/test/tenant-registry-db.test.ts +149 -0
- package/test/warm-start-seam.test.ts +140 -0
- package/test/warm-start.test.ts +138 -4
|
@@ -33,7 +33,7 @@ let TwinLifecycleMutation = class TwinLifecycleMutation {
|
|
|
33
33
|
async stopTwinInstance(instanceId, context) {
|
|
34
34
|
if (!index_js_1.TwinEngine.owns(context.state.domain.id, instanceId))
|
|
35
35
|
return false; // 테넌트 격리 — 남의 도메인 트윈 정지 차단
|
|
36
|
-
await index_js_1.TwinEngine.stop(instanceId);
|
|
36
|
+
await index_js_1.TwinEngine.stop(context.state.domain.id, instanceId);
|
|
37
37
|
return true;
|
|
38
38
|
}
|
|
39
39
|
/** 삭제 — 정지 + 레지스트리 삭제 + 저널 purge(domain 스코프). */
|
|
@@ -42,6 +42,16 @@ let TwinLifecycleMutation = class TwinLifecycleMutation {
|
|
|
42
42
|
return true;
|
|
43
43
|
}
|
|
44
44
|
/** 관리 목록 — 등록된 인스턴스 전체(kind/status/running/revision/노드·무버 수). 데이터 없으면 빈 배열. */
|
|
45
|
+
/**
|
|
46
|
+
* **이 공간의 트윈 id 들** — 한 현실을 여러 렌즈가 비추므로 공간을 물으면 그 렌즈 전부를 답한다.
|
|
47
|
+
*
|
|
48
|
+
* 화면이 전체 목록을 받아 걸러 쓰던 규칙을 서버로 모은다. 예측 화면은 같은 확장을 한 파일에서
|
|
49
|
+
* **두 번** 복제하고 있었고, 규칙이 흩어지면 한쪽만 고쳐진다. 떠 있지 않은 것도 포함한다 —
|
|
50
|
+
* 공간에 무엇이 있는지는 기동 여부와 다른 사실이다.
|
|
51
|
+
*/
|
|
52
|
+
async twinSpaceInstanceIds(spaceId, context) {
|
|
53
|
+
return index_js_1.TwinEngine.instanceIdsOfSpace(context.state.domain.id, spaceId);
|
|
54
|
+
}
|
|
45
55
|
async twinInstanceList(context) {
|
|
46
56
|
return index_js_1.TwinEngine.list(context.state.domain.id);
|
|
47
57
|
}
|
|
@@ -68,6 +78,7 @@ let TwinLifecycleMutation = class TwinLifecycleMutation {
|
|
|
68
78
|
};
|
|
69
79
|
exports.TwinLifecycleMutation = TwinLifecycleMutation;
|
|
70
80
|
tslib_1.__decorate([
|
|
81
|
+
(0, type_graphql_1.Directive)('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)'),
|
|
71
82
|
(0, type_graphql_1.Mutation)(returns => Boolean, {
|
|
72
83
|
description: 'Provision (save) a twin instance structure (kind: wms|yms|mes) from a BoardDef without starting it.'
|
|
73
84
|
}),
|
|
@@ -80,6 +91,7 @@ tslib_1.__decorate([
|
|
|
80
91
|
tslib_1.__metadata("design:returntype", Promise)
|
|
81
92
|
], TwinLifecycleMutation.prototype, "saveTwinInstance", null);
|
|
82
93
|
tslib_1.__decorate([
|
|
94
|
+
(0, type_graphql_1.Directive)('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)'),
|
|
83
95
|
(0, type_graphql_1.Mutation)(returns => Boolean, {
|
|
84
96
|
description: 'Start a twin runtime instance and begin the worker tick. If board is given it provisions first; otherwise it starts from the saved registry board.'
|
|
85
97
|
}),
|
|
@@ -92,6 +104,7 @@ tslib_1.__decorate([
|
|
|
92
104
|
tslib_1.__metadata("design:returntype", Promise)
|
|
93
105
|
], TwinLifecycleMutation.prototype, "startTwinInstance", null);
|
|
94
106
|
tslib_1.__decorate([
|
|
107
|
+
(0, type_graphql_1.Directive)('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)'),
|
|
95
108
|
(0, type_graphql_1.Mutation)(returns => Boolean, { description: 'Stop a running twin runtime instance by its id (only if it belongs to the current domain).' }),
|
|
96
109
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('instanceId')),
|
|
97
110
|
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
@@ -100,6 +113,7 @@ tslib_1.__decorate([
|
|
|
100
113
|
tslib_1.__metadata("design:returntype", Promise)
|
|
101
114
|
], TwinLifecycleMutation.prototype, "stopTwinInstance", null);
|
|
102
115
|
tslib_1.__decorate([
|
|
116
|
+
(0, type_graphql_1.Directive)('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)'),
|
|
103
117
|
(0, type_graphql_1.Mutation)(returns => Boolean, { description: 'Delete a twin instance: stop it, remove the registry entry and purge its event journal.' }),
|
|
104
118
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('instanceId')),
|
|
105
119
|
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
@@ -107,6 +121,14 @@ tslib_1.__decorate([
|
|
|
107
121
|
tslib_1.__metadata("design:paramtypes", [String, Object]),
|
|
108
122
|
tslib_1.__metadata("design:returntype", Promise)
|
|
109
123
|
], TwinLifecycleMutation.prototype, "deleteTwinInstance", null);
|
|
124
|
+
tslib_1.__decorate([
|
|
125
|
+
(0, type_graphql_1.Query)(returns => [String], { description: 'Ids of the twins that live in a space (all its lenses), running or not.' }),
|
|
126
|
+
tslib_1.__param(0, (0, type_graphql_1.Arg)('spaceId')),
|
|
127
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
128
|
+
tslib_1.__metadata("design:type", Function),
|
|
129
|
+
tslib_1.__metadata("design:paramtypes", [String, Object]),
|
|
130
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
131
|
+
], TwinLifecycleMutation.prototype, "twinSpaceInstanceIds", null);
|
|
110
132
|
tslib_1.__decorate([
|
|
111
133
|
(0, type_graphql_1.Query)(returns => [shell_1.ScalarObject], { description: 'List provisioned twin instances in the current domain with management metadata.' }),
|
|
112
134
|
tslib_1.__param(0, (0, type_graphql_1.Ctx)()),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"twin-lifecycle-mutation.js","sourceRoot":"","sources":["../../../server/service/twin-lifecycle/twin-lifecycle-mutation.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Mutation, Query, Resolver } from 'type-graphql'\n\nimport { ScalarObject } from '@things-factory/shell'\n\nimport { TwinEngine } from '../../engine/index.js'\n\nimport type { BoardDef } from '@operato/twin-kernel'\n\n/* 도메인 어휘 카탈로그(시스템별 노드 타입) — 커널이 SSOT. 앱/UI 는 이걸 소싱하고 재선언하지 않는다(방언 금지).\n * 조합은 domain-catalog.ts 가 소유 — 리졸버와 서버측 소비자(board-ai 도구 등)가 같은 형태를 본다. */\nimport { domainCatalog } from './domain-catalog.js'\n\n/*\n * 트윈 인스턴스 생명주기 채널 — 프로비저닝(상향, ADR-0015).\n * State/Command/Scenario 는 이미 running 인 인스턴스를 다루지만, 이 채널은 인스턴스 자체를 만들고/설정하고/없앤다.\n * 생명주기: provision(save, 미기동) → start → stop → delete. 모두 domain 스코프(context.state.domain.id).\n * board(BoardDef+layout)=구조, scenario=자극 은 분리 — 여기선 구조만 세운다. 자극은 controlTwinScenario 로.\n */\n@Resolver()\nexport class TwinLifecycleMutation {\n /** 프로비저닝(저장, 미기동) — 구조를 레지스트리에 등록만. 재프로비전 시 구조 변경이면 저널 purge(엔진). */\n @Mutation(returns => Boolean, {\n description: 'Provision (save) a twin instance structure (kind: wms|yms|mes) from a BoardDef without starting it.'\n })\n async saveTwinInstance(\n @Arg('instanceId') instanceId: string,\n @Arg('kind') kind: string,\n @Arg('board', type => ScalarObject) board: any,\n @Ctx() context: ResolverContext\n ): Promise<boolean> {\n await TwinEngine.provision(context.state.domain.id, instanceId, kind, board as BoardDef)\n return true\n }\n\n /** 기동 — board 를 주면 provision+start, 없으면 레지스트리에 저장된 구조로 start. */\n @Mutation(returns => Boolean, {\n description: 'Start a twin runtime instance and begin the worker tick. If board is given it provisions first; otherwise it starts from the saved registry board.'\n })\n async startTwinInstance(\n @Arg('instanceId') instanceId: string,\n @Ctx() context: ResolverContext,\n @Arg('kind', { nullable: true }) kind?: string,\n @Arg('board', type => ScalarObject, { nullable: true }) board?: any\n ): Promise<boolean> {\n const domainId = context.state.domain.id\n // 시각 기준은 **공간**이 갖는다(테넌트 아님) — 교대의 HH:MM 을 어느 기준으로 읽나.\n if (board) TwinEngine.start(instanceId, domainId, kind ?? 'wms', await TwinEngine.withSpaceTimeBase(board as BoardDef, domainId))\n else await TwinEngine.startFromRegistry(domainId, instanceId)\n return true\n }\n\n @Mutation(returns => Boolean, { description: 'Stop a running twin runtime instance by its id (only if it belongs to the current domain).' })\n async stopTwinInstance(@Arg('instanceId') instanceId: string, @Ctx() context: ResolverContext): Promise<boolean> {\n if (!TwinEngine.owns(context.state.domain.id, instanceId)) return false // 테넌트 격리 — 남의 도메인 트윈 정지 차단\n await TwinEngine.stop(instanceId)\n return true\n }\n\n /** 삭제 — 정지 + 레지스트리 삭제 + 저널 purge(domain 스코프). */\n @Mutation(returns => Boolean, { description: 'Delete a twin instance: stop it, remove the registry entry and purge its event journal.' })\n async deleteTwinInstance(@Arg('instanceId') instanceId: string, @Ctx() context: ResolverContext): Promise<boolean> {\n await TwinEngine.remove(context.state.domain.id, instanceId)\n return true\n }\n\n /** 관리 목록 — 등록된 인스턴스 전체(kind/status/running/revision/노드·무버 수). 데이터 없으면 빈 배열. */\n @Query(returns => [ScalarObject], { description: 'List provisioned twin instances in the current domain with management metadata.' })\n async twinInstanceList(@Ctx() context: ResolverContext): Promise<any[]> {\n return TwinEngine.list(context.state.domain.id)\n }\n\n /** 단건 상세 — 프로비저닝 에디터용 board(구조+layout) 포함. */\n @Query(returns => ScalarObject, { nullable: true, description: 'Get one provisioned twin instance with its BoardDef (structure + layout) for editing.' })\n async twinInstanceDetail(@Arg('instanceId') instanceId: string, @Ctx() context: ResolverContext): Promise<any | null> {\n return TwinEngine.detail(context.state.domain.id, instanceId)\n }\n\n /**\n * 라이브(메모리) 인스턴스 id 목록 — 콘솔 셀렉터·포트폴리오용. **현재 도메인만**(테넌트 격리).\n * 과거엔 Object.keys(TwinEngine.instances) 로 전역 반환 → 다른 도메인 트윈이 종합현황/셀렉터에 새던 버그.\n * 도메인 스코프 목록(twinInstanceList 와 동일 소스)에서 running 만 추린다.\n */\n @Query(returns => [String], { description: 'List ids of running (in-memory) twin runtime instances in the current domain (tenant-isolated).' })\n async twinInstances(@Ctx() context: ResolverContext): Promise<string[]> {\n const list = await TwinEngine.list(context.state.domain.id)\n return list.filter((x: any) => x.running).map((x: any) => x.instanceId)\n }\n\n /**\n * 도메인 어휘 카탈로그 — 커널(레퍼런스) SSOT 를 그대로 노출. { wms:{label,locationTypes[]}, yms:…, mes:… }.\n * 프로비저닝 UI 는 이걸 소싱해 노드 타입/시스템을 채운다(앱 내 하드코딩 금지).\n */\n @Query(returns => ScalarObject, { description: 'Domain vocabulary catalog (per-system location location types) sourced from the twin kernel — SSOT for provisioning UI.' })\n twinDomainCatalog(): any {\n return domainCatalog()\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"twin-lifecycle-mutation.js","sourceRoot":"","sources":["../../../server/service/twin-lifecycle/twin-lifecycle-mutation.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Mutation, Query, Resolver, Directive} from 'type-graphql'\n\nimport { ScalarObject } from '@things-factory/shell'\n\nimport { TwinEngine } from '../../engine/index.js'\n\nimport type { BoardDef } from '@operato/twin-kernel'\n\n/* 도메인 어휘 카탈로그(시스템별 노드 타입) — 커널이 SSOT. 앱/UI 는 이걸 소싱하고 재선언하지 않는다(방언 금지).\n * 조합은 domain-catalog.ts 가 소유 — 리졸버와 서버측 소비자(board-ai 도구 등)가 같은 형태를 본다. */\nimport { domainCatalog } from './domain-catalog.js'\n\n/*\n * 트윈 인스턴스 생명주기 채널 — 프로비저닝(상향, ADR-0015).\n * State/Command/Scenario 는 이미 running 인 인스턴스를 다루지만, 이 채널은 인스턴스 자체를 만들고/설정하고/없앤다.\n * 생명주기: provision(save, 미기동) → start → stop → delete. 모두 domain 스코프(context.state.domain.id).\n * board(BoardDef+layout)=구조, scenario=자극 은 분리 — 여기선 구조만 세운다. 자극은 controlTwinScenario 로.\n */\n@Resolver()\nexport class TwinLifecycleMutation {\n /** 프로비저닝(저장, 미기동) — 구조를 레지스트리에 등록만. 재프로비전 시 구조 변경이면 저널 purge(엔진). */\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => Boolean, {\n description: 'Provision (save) a twin instance structure (kind: wms|yms|mes) from a BoardDef without starting it.'\n })\n async saveTwinInstance(\n @Arg('instanceId') instanceId: string,\n @Arg('kind') kind: string,\n @Arg('board', type => ScalarObject) board: any,\n @Ctx() context: ResolverContext\n ): Promise<boolean> {\n await TwinEngine.provision(context.state.domain.id, instanceId, kind, board as BoardDef)\n return true\n }\n\n /** 기동 — board 를 주면 provision+start, 없으면 레지스트리에 저장된 구조로 start. */\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => Boolean, {\n description: 'Start a twin runtime instance and begin the worker tick. If board is given it provisions first; otherwise it starts from the saved registry board.'\n })\n async startTwinInstance(\n @Arg('instanceId') instanceId: string,\n @Ctx() context: ResolverContext,\n @Arg('kind', { nullable: true }) kind?: string,\n @Arg('board', type => ScalarObject, { nullable: true }) board?: any\n ): Promise<boolean> {\n const domainId = context.state.domain.id\n // 시각 기준은 **공간**이 갖는다(테넌트 아님) — 교대의 HH:MM 을 어느 기준으로 읽나.\n if (board) TwinEngine.start(instanceId, domainId, kind ?? 'wms', await TwinEngine.withSpaceTimeBase(board as BoardDef, domainId))\n else await TwinEngine.startFromRegistry(domainId, instanceId)\n return true\n }\n\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => Boolean, { description: 'Stop a running twin runtime instance by its id (only if it belongs to the current domain).' })\n async stopTwinInstance(@Arg('instanceId') instanceId: string, @Ctx() context: ResolverContext): Promise<boolean> {\n if (!TwinEngine.owns(context.state.domain.id, instanceId)) return false // 테넌트 격리 — 남의 도메인 트윈 정지 차단\n await TwinEngine.stop(context.state.domain.id, instanceId)\n return true\n }\n\n /** 삭제 — 정지 + 레지스트리 삭제 + 저널 purge(domain 스코프). */\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => Boolean, { description: 'Delete a twin instance: stop it, remove the registry entry and purge its event journal.' })\n async deleteTwinInstance(@Arg('instanceId') instanceId: string, @Ctx() context: ResolverContext): Promise<boolean> {\n await TwinEngine.remove(context.state.domain.id, instanceId)\n return true\n }\n\n /** 관리 목록 — 등록된 인스턴스 전체(kind/status/running/revision/노드·무버 수). 데이터 없으면 빈 배열. */\n /**\n * **이 공간의 트윈 id 들** — 한 현실을 여러 렌즈가 비추므로 공간을 물으면 그 렌즈 전부를 답한다.\n *\n * 화면이 전체 목록을 받아 걸러 쓰던 규칙을 서버로 모은다. 예측 화면은 같은 확장을 한 파일에서\n * **두 번** 복제하고 있었고, 규칙이 흩어지면 한쪽만 고쳐진다. 떠 있지 않은 것도 포함한다 —\n * 공간에 무엇이 있는지는 기동 여부와 다른 사실이다.\n */\n @Query(returns => [String], { description: 'Ids of the twins that live in a space (all its lenses), running or not.' })\n async twinSpaceInstanceIds(@Arg('spaceId') spaceId: string, @Ctx() context: ResolverContext): Promise<string[]> {\n return TwinEngine.instanceIdsOfSpace(context.state.domain.id, spaceId)\n }\n\n @Query(returns => [ScalarObject], { description: 'List provisioned twin instances in the current domain with management metadata.' })\n async twinInstanceList(@Ctx() context: ResolverContext): Promise<any[]> {\n return TwinEngine.list(context.state.domain.id)\n }\n\n /** 단건 상세 — 프로비저닝 에디터용 board(구조+layout) 포함. */\n @Query(returns => ScalarObject, { nullable: true, description: 'Get one provisioned twin instance with its BoardDef (structure + layout) for editing.' })\n async twinInstanceDetail(@Arg('instanceId') instanceId: string, @Ctx() context: ResolverContext): Promise<any | null> {\n return TwinEngine.detail(context.state.domain.id, instanceId)\n }\n\n /**\n * 라이브(메모리) 인스턴스 id 목록 — 콘솔 셀렉터·포트폴리오용. **현재 도메인만**(테넌트 격리).\n * 과거엔 Object.keys(TwinEngine.instances) 로 전역 반환 → 다른 도메인 트윈이 종합현황/셀렉터에 새던 버그.\n * 도메인 스코프 목록(twinInstanceList 와 동일 소스)에서 running 만 추린다.\n */\n @Query(returns => [String], { description: 'List ids of running (in-memory) twin runtime instances in the current domain (tenant-isolated).' })\n async twinInstances(@Ctx() context: ResolverContext): Promise<string[]> {\n const list = await TwinEngine.list(context.state.domain.id)\n return list.filter((x: any) => x.running).map((x: any) => x.instanceId)\n }\n\n /**\n * 도메인 어휘 카탈로그 — 커널(레퍼런스) SSOT 를 그대로 노출. { wms:{label,locationTypes[]}, yms:…, mes:… }.\n * 프로비저닝 UI 는 이걸 소싱해 노드 타입/시스템을 채운다(앱 내 하드코딩 금지).\n */\n @Query(returns => ScalarObject, { description: 'Domain vocabulary catalog (per-system location location types) sourced from the twin kernel — SSOT for provisioning UI.' })\n twinDomainCatalog(): any {\n return domainCatalog()\n }\n}\n"]}
|
|
@@ -14,7 +14,7 @@ let TwinMetricsQuery = class TwinMetricsQuery {
|
|
|
14
14
|
twinMetrics(instanceId, context) {
|
|
15
15
|
if (!index_js_1.TwinEngine.owns(context.state.domain.id, instanceId))
|
|
16
16
|
return null; // 테넌트 격리
|
|
17
|
-
return index_js_1.TwinEngine.metrics(instanceId);
|
|
17
|
+
return index_js_1.TwinEngine.metrics(context.state.domain.id, instanceId);
|
|
18
18
|
}
|
|
19
19
|
async twinMetricsAll(context) {
|
|
20
20
|
return index_js_1.TwinEngine.allMetrics(context.state.domain.id); // 도메인 스코프
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"twin-metrics-query.js","sourceRoot":"","sources":["../../../server/service/twin-metrics/twin-metrics-query.ts"],"names":[],"mappings":";;;;AAAA,+CAAwD;AAExD,iDAAoD;AAEpD,oDAAkD;AAElD;;;;GAIG;AAEI,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;IAE3B,WAAW,CAAoB,UAAkB,EAAS,OAAwB;QAChF,IAAI,CAAC,qBAAU,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,UAAU,CAAC;YAAE,OAAO,IAAI,CAAA,CAAC,SAAS;QAChF,OAAO,qBAAU,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"twin-metrics-query.js","sourceRoot":"","sources":["../../../server/service/twin-metrics/twin-metrics-query.ts"],"names":[],"mappings":";;;;AAAA,+CAAwD;AAExD,iDAAoD;AAEpD,oDAAkD;AAElD;;;;GAIG;AAEI,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;IAE3B,WAAW,CAAoB,UAAkB,EAAS,OAAwB;QAChF,IAAI,CAAC,qBAAU,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,UAAU,CAAC;YAAE,OAAO,IAAI,CAAA,CAAC,SAAS;QAChF,OAAO,qBAAU,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,UAAU,CAAC,CAAA;IAChE,CAAC;IAGK,AAAN,KAAK,CAAC,cAAc,CAAQ,OAAwB;QAClD,OAAO,qBAAU,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA,CAAC,UAAU;IAClE,CAAC;CACF,CAAA;AAXY,4CAAgB;AAE3B;IADC,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,oBAAY,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,kJAAkJ,EAAE,CAAC;IACvM,mBAAA,IAAA,kBAAG,EAAC,YAAY,CAAC,CAAA;IAAsB,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;mDAGxD;AAGK;IADL,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,CAAC,oBAAY,CAAC,EAAE,EAAE,WAAW,EAAE,uGAAuG,EAAE,CAAC;IACrI,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;sDAE1B;2BAVU,gBAAgB;IAD5B,IAAA,uBAAQ,GAAE;GACE,gBAAgB,CAW5B","sourcesContent":["import { Arg, Ctx, Query, Resolver } from 'type-graphql'\n\nimport { ScalarObject } from '@things-factory/shell'\n\nimport { TwinEngine } from '../../engine/index.js'\n\n/*\n * 트윈 처리량 계측(모니터, ④-1) — 라이브 동기 인터페이스 부하를 읽는 일급 지표.\n * 유입률(ingestRate)·방송률(broadcastRate)·저널률(journalRate)·백로그(backlog)·누적. 폴링으로 라이브 관측.\n * (부하가 화면에 안 보인다던 문제의 정공법 — \"얼마나 들어오고 얼마나 방송/기록되나\"를 수치로.)\n */\n@Resolver()\nexport class TwinMetricsQuery {\n @Query(returns => ScalarObject, { nullable: true, description: 'Live throughput metrics of one twin instance (ingest/broadcast/journal rate, backlog). Null if not a live instance or not in the current domain.' })\n twinMetrics(@Arg('instanceId') instanceId: string, @Ctx() context: ResolverContext): any {\n if (!TwinEngine.owns(context.state.domain.id, instanceId)) return null // 테넌트 격리\n return TwinEngine.metrics(context.state.domain.id, instanceId)\n }\n\n @Query(returns => [ScalarObject], { description: 'Live throughput metrics of all running live twin instances in the current domain (monitor dashboard).' })\n async twinMetricsAll(@Ctx() context: ResolverContext): Promise<any[]> {\n return TwinEngine.allMetrics(context.state.domain.id) // 도메인 스코프\n }\n}\n"]}
|
|
@@ -185,6 +185,7 @@ tslib_1.__decorate([
|
|
|
185
185
|
tslib_1.__metadata("design:returntype", Promise)
|
|
186
186
|
], TwinSpaceResolver.prototype, "twinSpace", null);
|
|
187
187
|
tslib_1.__decorate([
|
|
188
|
+
(0, type_graphql_1.Directive)('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)'),
|
|
188
189
|
(0, type_graphql_1.Mutation)(returns => Boolean, { description: 'Create or update a Space (name/description/geo/timezone/primaryRepresentationId).' }),
|
|
189
190
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('spaceId')),
|
|
190
191
|
tslib_1.__param(1, (0, type_graphql_1.Arg)('patch', type => shell_1.ScalarObject)),
|
|
@@ -194,6 +195,7 @@ tslib_1.__decorate([
|
|
|
194
195
|
tslib_1.__metadata("design:returntype", Promise)
|
|
195
196
|
], TwinSpaceResolver.prototype, "saveTwinSpace", null);
|
|
196
197
|
tslib_1.__decorate([
|
|
198
|
+
(0, type_graphql_1.Directive)('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)'),
|
|
197
199
|
(0, type_graphql_1.Mutation)(returns => Boolean, { description: 'Delete a Space and its representations/areas.' }),
|
|
198
200
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('spaceId')),
|
|
199
201
|
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
@@ -210,6 +212,7 @@ tslib_1.__decorate([
|
|
|
210
212
|
tslib_1.__metadata("design:returntype", Promise)
|
|
211
213
|
], TwinSpaceResolver.prototype, "twinAreas", null);
|
|
212
214
|
tslib_1.__decorate([
|
|
215
|
+
(0, type_graphql_1.Directive)('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)'),
|
|
213
216
|
(0, type_graphql_1.Mutation)(returns => Boolean, { description: 'Create/update a logical area (shared across representations). (area unification)' }),
|
|
214
217
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('spaceId')),
|
|
215
218
|
tslib_1.__param(1, (0, type_graphql_1.Arg)('areaId')),
|
|
@@ -220,6 +223,7 @@ tslib_1.__decorate([
|
|
|
220
223
|
tslib_1.__metadata("design:returntype", Promise)
|
|
221
224
|
], TwinSpaceResolver.prototype, "saveTwinArea", null);
|
|
222
225
|
tslib_1.__decorate([
|
|
226
|
+
(0, type_graphql_1.Directive)('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)'),
|
|
223
227
|
(0, type_graphql_1.Mutation)(returns => Boolean, { description: 'Delete a logical area only if unreferenced (reference-counted). Throws if referenced. (area unification)' }),
|
|
224
228
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('spaceId')),
|
|
225
229
|
tslib_1.__param(1, (0, type_graphql_1.Arg)('areaId')),
|
|
@@ -229,6 +233,7 @@ tslib_1.__decorate([
|
|
|
229
233
|
tslib_1.__metadata("design:returntype", Promise)
|
|
230
234
|
], TwinSpaceResolver.prototype, "deleteTwinArea", null);
|
|
231
235
|
tslib_1.__decorate([
|
|
236
|
+
(0, type_graphql_1.Directive)('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)'),
|
|
232
237
|
(0, type_graphql_1.Mutation)(returns => shell_1.ScalarObject, { description: 'Create/update a representation (map|image|board). Returns {id}.' }),
|
|
233
238
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('spaceId')),
|
|
234
239
|
tslib_1.__param(1, (0, type_graphql_1.Arg)('rep', type => shell_1.ScalarObject)),
|
|
@@ -238,6 +243,7 @@ tslib_1.__decorate([
|
|
|
238
243
|
tslib_1.__metadata("design:returntype", Promise)
|
|
239
244
|
], TwinSpaceResolver.prototype, "saveSpaceRepresentation", null);
|
|
240
245
|
tslib_1.__decorate([
|
|
246
|
+
(0, type_graphql_1.Directive)('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)'),
|
|
241
247
|
(0, type_graphql_1.Mutation)(returns => Boolean, { description: 'Delete a representation and its areas.' }),
|
|
242
248
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('id')),
|
|
243
249
|
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
@@ -246,6 +252,7 @@ tslib_1.__decorate([
|
|
|
246
252
|
tslib_1.__metadata("design:returntype", Promise)
|
|
247
253
|
], TwinSpaceResolver.prototype, "deleteSpaceRepresentation", null);
|
|
248
254
|
tslib_1.__decorate([
|
|
255
|
+
(0, type_graphql_1.Directive)('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)'),
|
|
249
256
|
(0, type_graphql_1.Mutation)(returns => Boolean, { description: 'Set the primary representation of a space.' }),
|
|
250
257
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('spaceId')),
|
|
251
258
|
tslib_1.__param(1, (0, type_graphql_1.Arg)('repId')),
|
|
@@ -255,6 +262,7 @@ tslib_1.__decorate([
|
|
|
255
262
|
tslib_1.__metadata("design:returntype", Promise)
|
|
256
263
|
], TwinSpaceResolver.prototype, "setPrimaryRepresentation", null);
|
|
257
264
|
tslib_1.__decorate([
|
|
265
|
+
(0, type_graphql_1.Directive)('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)'),
|
|
258
266
|
(0, type_graphql_1.Mutation)(returns => shell_1.ScalarObject, { description: 'Create/update a polygon area (geometry + drillTo board + binding). Returns {id}.' }),
|
|
259
267
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('representationId')),
|
|
260
268
|
tslib_1.__param(1, (0, type_graphql_1.Arg)('area', type => shell_1.ScalarObject)),
|
|
@@ -264,6 +272,7 @@ tslib_1.__decorate([
|
|
|
264
272
|
tslib_1.__metadata("design:returntype", Promise)
|
|
265
273
|
], TwinSpaceResolver.prototype, "saveSpaceArea", null);
|
|
266
274
|
tslib_1.__decorate([
|
|
275
|
+
(0, type_graphql_1.Directive)('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)'),
|
|
267
276
|
(0, type_graphql_1.Mutation)(returns => Boolean, { description: 'Delete a polygon area.' }),
|
|
268
277
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('id')),
|
|
269
278
|
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"twin-space-resolver.js","sourceRoot":"","sources":["../../../server/service/twin-space/twin-space-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Mutation, Query, Resolver } from 'type-graphql'\n\nimport { ScalarObject, getRepository } from '@things-factory/shell'\n\nimport { TwinSpace } from './twin-space.js'\nimport { TwinSpaceRepresentation } from './twin-space-representation.js'\nimport { TwinSpaceArea } from './twin-space-area.js'\nimport { TwinArea } from './twin-area.js'\nimport { TwinInstance } from '../twin-instance/twin-instance.js'\n\n/*\n * 공간 구성 채널(design/ui/space-composition.md) — Site(Space) + 복수 표현(Representation) + 영역(Area).\n * board 내용은 board-service Board 에(표현 config.boardId 참조, 중복 저장 금지). 모두 domain 스코프.\n */\n@Resolver()\nexport class TwinSpaceResolver {\n /* ── Space(Site) ────────────────────────────────────────────────── */\n @Query(returns => [ScalarObject], { description: 'List tenant Spaces (Sites) in the current domain.' })\n async twinSpaces(@Ctx() context: ResolverContext): Promise<any[]> {\n const domainId = context.state.domain.id\n const rows = await getRepository(TwinSpace).find({ where: { domain: { id: domainId } }, order: { createdAt: 'ASC' } })\n const out: any[] = []\n for (const r of rows) {\n const representationCount = await getRepository(TwinSpaceRepresentation).count({ where: { domain: { id: domainId } as any, space: { id: r.id } as any } })\n // 벤치 여부·원본 참조 — 인스턴스 purpose 근거(1급). 사본 공간을 헤더 선택기에서 구별·선택 가능하게.\n const insts = await getRepository(TwinInstance).find({ where: { domain: { id: domainId } as any, spaceId: r.spaceId } })\n const benchInst = insts.find(i => (i as any).purpose === 'bench')\n const bench = !!benchInst\n const copyOf = benchInst?.copyOf\n out.push({ spaceId: r.spaceId, name: r.name, description: r.description, geo: r.geo, timezone: r.timezone, primaryRepresentationId: r.primaryRepresentationId, content: r.content, representationCount, bench, copyOf })\n }\n return out\n }\n\n /** 단건 — 표현(+영역) 트리 포함. */\n @Query(returns => ScalarObject, { nullable: true, description: 'Get one Space with its representations (and their areas).' })\n async twinSpace(@Arg('spaceId') spaceId: string, @Ctx() context: ResolverContext): Promise<any | null> {\n const domainId = context.state.domain.id\n const r = await getRepository(TwinSpace).findOne({ where: { domain: { id: domainId }, spaceId } })\n if (!r) return null\n const reps = await getRepository(TwinSpaceRepresentation).find({ where: { domain: { id: domainId }, space: { id: r.id } }, order: { seq: 'ASC' } })\n const representations: any[] = []\n for (const rep of reps) {\n const areas = await getRepository(TwinSpaceArea).find({ where: { domain: { id: domainId }, representation: { id: rep.id } }, order: { createdAt: 'ASC' } })\n representations.push({ id: rep.id, type: rep.type, name: rep.name, seq: rep.seq, isPrimary: rep.isPrimary, config: rep.config, areas: areas.map(a => ({ id: a.id, name: a.name, type: a.type, geometry: a.geometry, drillTo: a.drillTo, binding: a.binding, style: a.style })) })\n }\n return { spaceId: r.spaceId, name: r.name, description: r.description, geo: r.geo, timezone: r.timezone, primaryRepresentationId: r.primaryRepresentationId, content: r.content, representations }\n }\n\n @Mutation(returns => Boolean, { description: 'Create or update a Space (name/description/geo/timezone/primaryRepresentationId).' })\n async saveTwinSpace(\n @Arg('spaceId') spaceId: string,\n @Arg('patch', type => ScalarObject) patch: any,\n @Ctx() context: ResolverContext\n ): Promise<boolean> {\n const repo = getRepository(TwinSpace)\n const domainId = context.state.domain.id\n const existing = await repo.findOne({ where: { domain: { id: domainId }, spaceId } })\n /* 진짜 부분 병합 — patch 에 준 키만 갱신(나머지 필드 보존). id 는 관계로 재설정하지 않도록 제외. */\n const { id: _drop, ...clean } = patch ?? {}\n await repo.save(repo.create({ ...(existing ?? {}), ...clean, domain: { id: domainId } as any, spaceId }))\n return true\n }\n\n @Mutation(returns => Boolean, { description: 'Delete a Space and its representations/areas.' })\n async deleteTwinSpace(@Arg('spaceId') spaceId: string, @Ctx() context: ResolverContext): Promise<boolean> {\n const domainId = context.state.domain.id\n // 라이프사이클 가드 — 이 공간에 바인딩된 트윈 인스턴스가 있으면 삭제 차단(dangling 방지).\n // spaceId 컬럼(space #3) 로 직접 질의 — 전 인스턴스 로드+JS필터 제거(효율·1급 질의).\n const bound = await getRepository(TwinInstance).find({ where: { domain: { id: domainId }, spaceId } })\n if (bound.length) {\n throw new Error(\n `공간 \"${spaceId}\" 에 트윈 인스턴스 ${bound.length}개가 바인딩돼 있어 삭제할 수 없습니다. 먼저 트윈을 제거하세요 (${bound.map(i => i.instanceId).join(', ')}).`\n )\n }\n const space = await getRepository(TwinSpace).findOne({ where: { domain: { id: domainId }, spaceId } })\n if (space) {\n const reps = await getRepository(TwinSpaceRepresentation).find({ where: { domain: { id: domainId }, space: { id: space.id } } })\n for (const rep of reps) await getRepository(TwinSpaceArea).delete({ domain: { id: domainId } as any, representation: { id: rep.id } as any })\n await getRepository(TwinSpaceRepresentation).delete({ domain: { id: domainId } as any, space: { id: space.id } as any })\n await getRepository(TwinArea).delete({ domain: { id: domainId } as any, space: { id: space.id } as any }) // 논리 area 도 함께 정리\n }\n await getRepository(TwinSpace).delete({ domain: { id: domainId } as any, spaceId })\n return true\n }\n\n /* ── Area(논리 구역, 표현 무관·공유·계층) — area 단일화 space #2 ─────── */\n @Query(returns => [ScalarObject], { description: 'List logical areas (representation-agnostic hierarchy) of a space. (area unification)' })\n async twinAreas(@Arg('spaceId') spaceId: string, @Ctx() context: ResolverContext): Promise<any[]> {\n const domainId = context.state.domain.id\n const rows = await getRepository(TwinArea).find({ where: { domain: { id: domainId }, space: { spaceId } }, order: { createdAt: 'ASC' } })\n return rows.map(a => ({ areaId: a.areaId, name: a.name, type: a.type, parentId: a.parentId, layout: a.layout }))\n }\n\n @Mutation(returns => Boolean, { description: 'Create/update a logical area (shared across representations). (area unification)' })\n async saveTwinArea(\n @Arg('spaceId') spaceId: string,\n @Arg('areaId') areaId: string,\n @Arg('patch', type => ScalarObject) patch: any,\n @Ctx() context: ResolverContext\n ): Promise<boolean> {\n const domainId = context.state.domain.id\n const space = await getRepository(TwinSpace).findOne({ where: { domain: { id: domainId }, spaceId } })\n if (!space) throw new Error(`space \"${spaceId}\" not found`)\n const repo = getRepository(TwinArea)\n const existing = await repo.findOne({ where: { domain: { id: domainId }, space: { id: space.id }, areaId } })\n await repo.save(\n repo.create({\n ...(existing ?? {}),\n domain: { id: domainId } as any,\n space: { id: space.id } as any,\n areaId,\n name: patch?.name ?? existing?.name ?? areaId,\n type: patch?.type ?? existing?.type,\n parentId: patch?.parentId !== undefined ? patch.parentId : existing?.parentId,\n layout: patch?.layout ?? existing?.layout\n })\n )\n return true\n }\n\n /*\n * 참조기반 회수(reference-counted) — area 는 참조가 남아 있으면 삭제 차단, 고아면 제거.\n * 참조: 자식 area(parentId) · 표현별 폴리곤(TwinSpaceArea binding.groupId/areaId).\n * (노드/엔티티 담김·drillTo 참조는 P2 areaId 배선 후 확장.)\n */\n @Mutation(returns => Boolean, { description: 'Delete a logical area only if unreferenced (reference-counted). Throws if referenced. (area unification)' })\n async deleteTwinArea(@Arg('spaceId') spaceId: string, @Arg('areaId') areaId: string, @Ctx() context: ResolverContext): Promise<boolean> {\n const domainId = context.state.domain.id\n const space = await getRepository(TwinSpace).findOne({ where: { domain: { id: domainId }, spaceId } })\n if (!space) return false\n // 참조 1: 자식 area\n const children = await getRepository(TwinArea).count({ where: { domain: { id: domainId }, space: { id: space.id }, parentId: areaId } })\n // 참조 2: 표현별 폴리곤(이 공간의 표현들에 이 area 를 그린 것). binding.groupId/areaId 는 JSON → 로드 후 JS 필터(멀티DB).\n const reps = await getRepository(TwinSpaceRepresentation).find({ where: { domain: { id: domainId }, space: { id: space.id } } })\n let polygonRefs = 0\n for (const rep of reps) {\n const polys = await getRepository(TwinSpaceArea).find({ where: { domain: { id: domainId }, representation: { id: rep.id } } })\n polygonRefs += polys.filter(p => (p.binding as any)?.groupId === areaId || (p.binding as any)?.areaId === areaId).length\n }\n if (children > 0 || polygonRefs > 0) {\n throw new Error(`area \"${areaId}\" 는 참조가 남아 삭제할 수 없습니다 (자식 ${children}, 폴리곤 ${polygonRefs}). 참조 제거 후 삭제하세요.`)\n }\n await getRepository(TwinArea).delete({ domain: { id: domainId } as any, space: { id: space.id } as any, areaId })\n return true\n }\n\n /* ── Representation ─────────────────────────────────────────────── */\n @Mutation(returns => ScalarObject, { description: 'Create/update a representation (map|image|board). Returns {id}.' })\n async saveSpaceRepresentation(\n @Arg('spaceId') spaceId: string,\n @Arg('rep', type => ScalarObject) rep: any,\n @Ctx() context: ResolverContext\n ): Promise<any> {\n const domainId = context.state.domain.id\n const space = await getRepository(TwinSpace).findOne({ where: { domain: { id: domainId }, spaceId } })\n if (!space) return { ok: false, error: `space not found: ${spaceId}` }\n const repo = getRepository(TwinSpaceRepresentation)\n const existing = rep?.id ? await repo.findOne({ where: { id: rep.id } }) : null\n const saved = await repo.save(repo.create({ ...(existing ?? {}), domain: { id: domainId } as any, space: { id: space.id } as any, type: rep.type, name: rep.name, seq: rep.seq ?? 0, isPrimary: !!rep.isPrimary, config: rep.config }))\n return { ok: true, id: saved.id }\n }\n\n @Mutation(returns => Boolean, { description: 'Delete a representation and its areas.' })\n async deleteSpaceRepresentation(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<boolean> {\n const domainId = context.state.domain.id\n await getRepository(TwinSpaceArea).delete({ domain: { id: domainId } as any, representation: { id } as any })\n await getRepository(TwinSpaceRepresentation).delete({ domain: { id: domainId } as any, id } as any)\n return true\n }\n\n @Mutation(returns => Boolean, { description: 'Set the primary representation of a space.' })\n async setPrimaryRepresentation(@Arg('spaceId') spaceId: string, @Arg('repId') repId: string, @Ctx() context: ResolverContext): Promise<boolean> {\n const domainId = context.state.domain.id\n const spaceRepo = getRepository(TwinSpace)\n const space = await spaceRepo.findOne({ where: { domain: { id: domainId }, spaceId } })\n if (!space) return false\n await spaceRepo.save(spaceRepo.create({ ...space, primaryRepresentationId: repId }))\n const repRepo = getRepository(TwinSpaceRepresentation)\n const reps = await repRepo.find({ where: { domain: { id: domainId }, space: { id: space.id } } })\n for (const rep of reps) if (!!rep.isPrimary !== (rep.id === repId)) await repRepo.save(repRepo.create({ ...rep, isPrimary: rep.id === repId }))\n return true\n }\n\n /* ── Area (map/image 폴리곤) ────────────────────────────────────── */\n @Mutation(returns => ScalarObject, { description: 'Create/update a polygon area (geometry + drillTo board + binding). Returns {id}.' })\n async saveSpaceArea(\n @Arg('representationId') representationId: string,\n @Arg('area', type => ScalarObject) area: any,\n @Ctx() context: ResolverContext\n ): Promise<any> {\n const domainId = context.state.domain.id\n const repo = getRepository(TwinSpaceArea)\n const existing = area?.id ? await repo.findOne({ where: { id: area.id } }) : null\n /* drillTo/binding 은 선택적 링크 — 클라가 해제(undefined)로 보내면 JSON 에서 키가 빠져 TypeORM 이 \"변경 없음\"으로\n * 무시하므로 기존 값이 남는다. null 로 강제해 명시적으로 컬럼을 비운다(저장은 늘 전체 필드를 보냄 → 덮어쓰기 안전). */\n const saved = await repo.save(repo.create({ ...(existing ?? {}), domain: { id: domainId } as any, representation: { id: representationId } as any, name: area.name, type: area.type, geometry: area.geometry, drillTo: area.drillTo ?? null, binding: area.binding ?? null, style: area.style }))\n return { ok: true, id: saved.id }\n }\n\n @Mutation(returns => Boolean, { description: 'Delete a polygon area.' })\n async deleteSpaceArea(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<boolean> {\n await getRepository(TwinSpaceArea).delete({ domain: { id: context.state.domain.id } as any, id } as any)\n return true\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"twin-space-resolver.js","sourceRoot":"","sources":["../../../server/service/twin-space/twin-space-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Mutation, Query, Resolver, Directive} from 'type-graphql'\n\nimport { ScalarObject, getRepository } from '@things-factory/shell'\n\nimport { TwinSpace } from './twin-space.js'\nimport { TwinSpaceRepresentation } from './twin-space-representation.js'\nimport { TwinSpaceArea } from './twin-space-area.js'\nimport { TwinArea } from './twin-area.js'\nimport { TwinInstance } from '../twin-instance/twin-instance.js'\n\n/*\n * 공간 구성 채널(design/ui/space-composition.md) — Site(Space) + 복수 표현(Representation) + 영역(Area).\n * board 내용은 board-service Board 에(표현 config.boardId 참조, 중복 저장 금지). 모두 domain 스코프.\n */\n@Resolver()\nexport class TwinSpaceResolver {\n /* ── Space(Site) ────────────────────────────────────────────────── */\n @Query(returns => [ScalarObject], { description: 'List tenant Spaces (Sites) in the current domain.' })\n async twinSpaces(@Ctx() context: ResolverContext): Promise<any[]> {\n const domainId = context.state.domain.id\n const rows = await getRepository(TwinSpace).find({ where: { domain: { id: domainId } }, order: { createdAt: 'ASC' } })\n const out: any[] = []\n for (const r of rows) {\n const representationCount = await getRepository(TwinSpaceRepresentation).count({ where: { domain: { id: domainId } as any, space: { id: r.id } as any } })\n // 벤치 여부·원본 참조 — 인스턴스 purpose 근거(1급). 사본 공간을 헤더 선택기에서 구별·선택 가능하게.\n const insts = await getRepository(TwinInstance).find({ where: { domain: { id: domainId } as any, spaceId: r.spaceId } })\n const benchInst = insts.find(i => (i as any).purpose === 'bench')\n const bench = !!benchInst\n const copyOf = benchInst?.copyOf\n out.push({ spaceId: r.spaceId, name: r.name, description: r.description, geo: r.geo, timezone: r.timezone, primaryRepresentationId: r.primaryRepresentationId, content: r.content, representationCount, bench, copyOf })\n }\n return out\n }\n\n /** 단건 — 표현(+영역) 트리 포함. */\n @Query(returns => ScalarObject, { nullable: true, description: 'Get one Space with its representations (and their areas).' })\n async twinSpace(@Arg('spaceId') spaceId: string, @Ctx() context: ResolverContext): Promise<any | null> {\n const domainId = context.state.domain.id\n const r = await getRepository(TwinSpace).findOne({ where: { domain: { id: domainId }, spaceId } })\n if (!r) return null\n const reps = await getRepository(TwinSpaceRepresentation).find({ where: { domain: { id: domainId }, space: { id: r.id } }, order: { seq: 'ASC' } })\n const representations: any[] = []\n for (const rep of reps) {\n const areas = await getRepository(TwinSpaceArea).find({ where: { domain: { id: domainId }, representation: { id: rep.id } }, order: { createdAt: 'ASC' } })\n representations.push({ id: rep.id, type: rep.type, name: rep.name, seq: rep.seq, isPrimary: rep.isPrimary, config: rep.config, areas: areas.map(a => ({ id: a.id, name: a.name, type: a.type, geometry: a.geometry, drillTo: a.drillTo, binding: a.binding, style: a.style })) })\n }\n return { spaceId: r.spaceId, name: r.name, description: r.description, geo: r.geo, timezone: r.timezone, primaryRepresentationId: r.primaryRepresentationId, content: r.content, representations }\n }\n\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => Boolean, { description: 'Create or update a Space (name/description/geo/timezone/primaryRepresentationId).' })\n async saveTwinSpace(\n @Arg('spaceId') spaceId: string,\n @Arg('patch', type => ScalarObject) patch: any,\n @Ctx() context: ResolverContext\n ): Promise<boolean> {\n const repo = getRepository(TwinSpace)\n const domainId = context.state.domain.id\n const existing = await repo.findOne({ where: { domain: { id: domainId }, spaceId } })\n /* 진짜 부분 병합 — patch 에 준 키만 갱신(나머지 필드 보존). id 는 관계로 재설정하지 않도록 제외. */\n const { id: _drop, ...clean } = patch ?? {}\n await repo.save(repo.create({ ...(existing ?? {}), ...clean, domain: { id: domainId } as any, spaceId }))\n return true\n }\n\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => Boolean, { description: 'Delete a Space and its representations/areas.' })\n async deleteTwinSpace(@Arg('spaceId') spaceId: string, @Ctx() context: ResolverContext): Promise<boolean> {\n const domainId = context.state.domain.id\n // 라이프사이클 가드 — 이 공간에 바인딩된 트윈 인스턴스가 있으면 삭제 차단(dangling 방지).\n // spaceId 컬럼(space #3) 로 직접 질의 — 전 인스턴스 로드+JS필터 제거(효율·1급 질의).\n const bound = await getRepository(TwinInstance).find({ where: { domain: { id: domainId }, spaceId } })\n if (bound.length) {\n throw new Error(\n `공간 \"${spaceId}\" 에 트윈 인스턴스 ${bound.length}개가 바인딩돼 있어 삭제할 수 없습니다. 먼저 트윈을 제거하세요 (${bound.map(i => i.instanceId).join(', ')}).`\n )\n }\n const space = await getRepository(TwinSpace).findOne({ where: { domain: { id: domainId }, spaceId } })\n if (space) {\n const reps = await getRepository(TwinSpaceRepresentation).find({ where: { domain: { id: domainId }, space: { id: space.id } } })\n for (const rep of reps) await getRepository(TwinSpaceArea).delete({ domain: { id: domainId } as any, representation: { id: rep.id } as any })\n await getRepository(TwinSpaceRepresentation).delete({ domain: { id: domainId } as any, space: { id: space.id } as any })\n await getRepository(TwinArea).delete({ domain: { id: domainId } as any, space: { id: space.id } as any }) // 논리 area 도 함께 정리\n }\n await getRepository(TwinSpace).delete({ domain: { id: domainId } as any, spaceId })\n return true\n }\n\n /* ── Area(논리 구역, 표현 무관·공유·계층) — area 단일화 space #2 ─────── */\n @Query(returns => [ScalarObject], { description: 'List logical areas (representation-agnostic hierarchy) of a space. (area unification)' })\n async twinAreas(@Arg('spaceId') spaceId: string, @Ctx() context: ResolverContext): Promise<any[]> {\n const domainId = context.state.domain.id\n const rows = await getRepository(TwinArea).find({ where: { domain: { id: domainId }, space: { spaceId } }, order: { createdAt: 'ASC' } })\n return rows.map(a => ({ areaId: a.areaId, name: a.name, type: a.type, parentId: a.parentId, layout: a.layout }))\n }\n\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => Boolean, { description: 'Create/update a logical area (shared across representations). (area unification)' })\n async saveTwinArea(\n @Arg('spaceId') spaceId: string,\n @Arg('areaId') areaId: string,\n @Arg('patch', type => ScalarObject) patch: any,\n @Ctx() context: ResolverContext\n ): Promise<boolean> {\n const domainId = context.state.domain.id\n const space = await getRepository(TwinSpace).findOne({ where: { domain: { id: domainId }, spaceId } })\n if (!space) throw new Error(`space \"${spaceId}\" not found`)\n const repo = getRepository(TwinArea)\n const existing = await repo.findOne({ where: { domain: { id: domainId }, space: { id: space.id }, areaId } })\n await repo.save(\n repo.create({\n ...(existing ?? {}),\n domain: { id: domainId } as any,\n space: { id: space.id } as any,\n areaId,\n name: patch?.name ?? existing?.name ?? areaId,\n type: patch?.type ?? existing?.type,\n parentId: patch?.parentId !== undefined ? patch.parentId : existing?.parentId,\n layout: patch?.layout ?? existing?.layout\n })\n )\n return true\n }\n\n /*\n * 참조기반 회수(reference-counted) — area 는 참조가 남아 있으면 삭제 차단, 고아면 제거.\n * 참조: 자식 area(parentId) · 표현별 폴리곤(TwinSpaceArea binding.groupId/areaId).\n * (노드/엔티티 담김·drillTo 참조는 P2 areaId 배선 후 확장.)\n */\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => Boolean, { description: 'Delete a logical area only if unreferenced (reference-counted). Throws if referenced. (area unification)' })\n async deleteTwinArea(@Arg('spaceId') spaceId: string, @Arg('areaId') areaId: string, @Ctx() context: ResolverContext): Promise<boolean> {\n const domainId = context.state.domain.id\n const space = await getRepository(TwinSpace).findOne({ where: { domain: { id: domainId }, spaceId } })\n if (!space) return false\n // 참조 1: 자식 area\n const children = await getRepository(TwinArea).count({ where: { domain: { id: domainId }, space: { id: space.id }, parentId: areaId } })\n // 참조 2: 표현별 폴리곤(이 공간의 표현들에 이 area 를 그린 것). binding.groupId/areaId 는 JSON → 로드 후 JS 필터(멀티DB).\n const reps = await getRepository(TwinSpaceRepresentation).find({ where: { domain: { id: domainId }, space: { id: space.id } } })\n let polygonRefs = 0\n for (const rep of reps) {\n const polys = await getRepository(TwinSpaceArea).find({ where: { domain: { id: domainId }, representation: { id: rep.id } } })\n polygonRefs += polys.filter(p => (p.binding as any)?.groupId === areaId || (p.binding as any)?.areaId === areaId).length\n }\n if (children > 0 || polygonRefs > 0) {\n throw new Error(`area \"${areaId}\" 는 참조가 남아 삭제할 수 없습니다 (자식 ${children}, 폴리곤 ${polygonRefs}). 참조 제거 후 삭제하세요.`)\n }\n await getRepository(TwinArea).delete({ domain: { id: domainId } as any, space: { id: space.id } as any, areaId })\n return true\n }\n\n /* ── Representation ─────────────────────────────────────────────── */\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => ScalarObject, { description: 'Create/update a representation (map|image|board). Returns {id}.' })\n async saveSpaceRepresentation(\n @Arg('spaceId') spaceId: string,\n @Arg('rep', type => ScalarObject) rep: any,\n @Ctx() context: ResolverContext\n ): Promise<any> {\n const domainId = context.state.domain.id\n const space = await getRepository(TwinSpace).findOne({ where: { domain: { id: domainId }, spaceId } })\n if (!space) return { ok: false, error: `space not found: ${spaceId}` }\n const repo = getRepository(TwinSpaceRepresentation)\n const existing = rep?.id ? await repo.findOne({ where: { id: rep.id } }) : null\n const saved = await repo.save(repo.create({ ...(existing ?? {}), domain: { id: domainId } as any, space: { id: space.id } as any, type: rep.type, name: rep.name, seq: rep.seq ?? 0, isPrimary: !!rep.isPrimary, config: rep.config }))\n return { ok: true, id: saved.id }\n }\n\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => Boolean, { description: 'Delete a representation and its areas.' })\n async deleteSpaceRepresentation(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<boolean> {\n const domainId = context.state.domain.id\n await getRepository(TwinSpaceArea).delete({ domain: { id: domainId } as any, representation: { id } as any })\n await getRepository(TwinSpaceRepresentation).delete({ domain: { id: domainId } as any, id } as any)\n return true\n }\n\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => Boolean, { description: 'Set the primary representation of a space.' })\n async setPrimaryRepresentation(@Arg('spaceId') spaceId: string, @Arg('repId') repId: string, @Ctx() context: ResolverContext): Promise<boolean> {\n const domainId = context.state.domain.id\n const spaceRepo = getRepository(TwinSpace)\n const space = await spaceRepo.findOne({ where: { domain: { id: domainId }, spaceId } })\n if (!space) return false\n await spaceRepo.save(spaceRepo.create({ ...space, primaryRepresentationId: repId }))\n const repRepo = getRepository(TwinSpaceRepresentation)\n const reps = await repRepo.find({ where: { domain: { id: domainId }, space: { id: space.id } } })\n for (const rep of reps) if (!!rep.isPrimary !== (rep.id === repId)) await repRepo.save(repRepo.create({ ...rep, isPrimary: rep.id === repId }))\n return true\n }\n\n /* ── Area (map/image 폴리곤) ────────────────────────────────────── */\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => ScalarObject, { description: 'Create/update a polygon area (geometry + drillTo board + binding). Returns {id}.' })\n async saveSpaceArea(\n @Arg('representationId') representationId: string,\n @Arg('area', type => ScalarObject) area: any,\n @Ctx() context: ResolverContext\n ): Promise<any> {\n const domainId = context.state.domain.id\n const repo = getRepository(TwinSpaceArea)\n const existing = area?.id ? await repo.findOne({ where: { id: area.id } }) : null\n /* drillTo/binding 은 선택적 링크 — 클라가 해제(undefined)로 보내면 JSON 에서 키가 빠져 TypeORM 이 \"변경 없음\"으로\n * 무시하므로 기존 값이 남는다. null 로 강제해 명시적으로 컬럼을 비운다(저장은 늘 전체 필드를 보냄 → 덮어쓰기 안전). */\n const saved = await repo.save(repo.create({ ...(existing ?? {}), domain: { id: domainId } as any, representation: { id: representationId } as any, name: area.name, type: area.type, geometry: area.geometry, drillTo: area.drillTo ?? null, binding: area.binding ?? null, style: area.style }))\n return { ok: true, id: saved.id }\n }\n\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => Boolean, { description: 'Delete a polygon area.' })\n async deleteSpaceArea(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<boolean> {\n await getRepository(TwinSpaceArea).delete({ domain: { id: context.state.domain.id } as any, id } as any)\n return true\n }\n}\n"]}
|
|
@@ -50,7 +50,7 @@ tslib_1.__decorate([
|
|
|
50
50
|
}
|
|
51
51
|
/* 구독 순간 현재 상태를 먼저 발행(late 구독자도 snapshot→delta 순서 보장). */
|
|
52
52
|
process.nextTick(() => {
|
|
53
|
-
const snap = index_js_1.TwinEngine.snapshot(instanceId);
|
|
53
|
+
const snap = index_js_1.TwinEngine.snapshot(domainId, instanceId);
|
|
54
54
|
if (snap) {
|
|
55
55
|
shell_1.pubsub.publish('twin-state', {
|
|
56
56
|
twinState: { instanceId, kind: 'snapshot', revision: snap.revision, payload: snap }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"twin-state-subscription.js","sourceRoot":"","sources":["../../../server/service/twin-state/twin-state-subscription.ts"],"names":[],"mappings":";;;;AAAA,+CAA2C;AAC3C,+CAAwF;AAExF,iDAA4D;AAE5D,oDAAkD;AAElD,+EAA+E;AAExE,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;CAY5B,CAAA;AAZY,4CAAgB;AAE3B;IADC,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,mDAAmD,EAAE,CAAC;;oDAC1D;AAGlB;IADC,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,yCAAyC,EAAE,CAAC;;8CACtD;AAGZ;IADC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,kBAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,2BAA2B,EAAE,CAAC;;kDAChE;AAGjB;IADC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,oBAAY,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,uFAAuF,EAAE,CAAC;;iDACzI;2BAXF,gBAAgB;IAD5B,IAAA,yBAAU,EAAC,EAAE,WAAW,EAAE,mEAAmE,EAAE,CAAC;GACpF,gBAAgB,CAY5B;AAED;;;GAGG;AAEI,IAAM,qBAAqB,GAA3B,MAAM,qBAAqB;IA2BhC,SAAS,CAAS,OAAwC,EAAqB,UAAkB;QAC/F,OAAO,OAAO,CAAC,SAAS,CAAA;IAC1B,CAAC;CACF,CAAA;AA9BY,sDAAqB;AA2BhC;IA1BC,IAAA,2BAAY,EAAC,OAAO,CAAC,EAAE,CAAC,gBAAgB,EAAE;QACzC,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE;YAC/B,MAAM,EAAE,UAAU,EAAE,GAAG,IAAI,CAAA;YAC3B,MAAM,QAAQ,GAAI,OAAe,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CAAA;YAEpD,0DAA0D;YAC1D,IAAI,CAAC,QAAQ,IAAI,CAAC,qBAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,EAAE,CAAC;gBACxD,OAAO,IAAA,mBAAI,EAAC,cAAM,CAAC,SAAS,CAAC,YAAY,CAAC,EAAE,IAAA,qBAAM,EAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;YAClE,CAAC;YAED,yDAAyD;YACzD,OAAO,CAAC,QAAQ,CAAC,GAAG,EAAE;gBACpB,MAAM,IAAI,GAAG,qBAAU,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"twin-state-subscription.js","sourceRoot":"","sources":["../../../server/service/twin-state/twin-state-subscription.ts"],"names":[],"mappings":";;;;AAAA,+CAA2C;AAC3C,+CAAwF;AAExF,iDAA4D;AAE5D,oDAAkD;AAElD,+EAA+E;AAExE,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;CAY5B,CAAA;AAZY,4CAAgB;AAE3B;IADC,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,mDAAmD,EAAE,CAAC;;oDAC1D;AAGlB;IADC,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,yCAAyC,EAAE,CAAC;;8CACtD;AAGZ;IADC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,kBAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,2BAA2B,EAAE,CAAC;;kDAChE;AAGjB;IADC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,oBAAY,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,uFAAuF,EAAE,CAAC;;iDACzI;2BAXF,gBAAgB;IAD5B,IAAA,yBAAU,EAAC,EAAE,WAAW,EAAE,mEAAmE,EAAE,CAAC;GACpF,gBAAgB,CAY5B;AAED;;;GAGG;AAEI,IAAM,qBAAqB,GAA3B,MAAM,qBAAqB;IA2BhC,SAAS,CAAS,OAAwC,EAAqB,UAAkB;QAC/F,OAAO,OAAO,CAAC,SAAS,CAAA;IAC1B,CAAC;CACF,CAAA;AA9BY,sDAAqB;AA2BhC;IA1BC,IAAA,2BAAY,EAAC,OAAO,CAAC,EAAE,CAAC,gBAAgB,EAAE;QACzC,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE;YAC/B,MAAM,EAAE,UAAU,EAAE,GAAG,IAAI,CAAA;YAC3B,MAAM,QAAQ,GAAI,OAAe,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CAAA;YAEpD,0DAA0D;YAC1D,IAAI,CAAC,QAAQ,IAAI,CAAC,qBAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,EAAE,CAAC;gBACxD,OAAO,IAAA,mBAAI,EAAC,cAAM,CAAC,SAAS,CAAC,YAAY,CAAC,EAAE,IAAA,qBAAM,EAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;YAClE,CAAC;YAED,yDAAyD;YACzD,OAAO,CAAC,QAAQ,CAAC,GAAG,EAAE;gBACpB,MAAM,IAAI,GAAG,qBAAU,CAAC,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAA;gBACtD,IAAI,IAAI,EAAE,CAAC;oBACT,cAAM,CAAC,OAAO,CAAC,YAAY,EAAE;wBAC3B,SAAS,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE;qBACpF,CAAC,CAAA;gBACJ,CAAC;YACH,CAAC,CAAC,CAAA;YAEF,OAAO,IAAA,mBAAI,EACT,cAAM,CAAC,SAAS,CAAC,YAAY,CAAC,EAC9B,IAAA,qBAAM,EAAC,CAAC,OAAwC,EAAE,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,UAAU,KAAK,UAAU,CAAC,CAClG,CAAA;QACH,CAAC;KACF,CAAC;IACS,mBAAA,IAAA,mBAAI,GAAE,CAAA;IAA4C,mBAAA,IAAA,kBAAG,EAAC,YAAY,CAAC,CAAA;;;4CAAsB,gBAAgB;sDAEnH;gCA7BU,qBAAqB;IADjC,IAAA,uBAAQ,GAAE;GACE,qBAAqB,CA8BjC","sourcesContent":["import { filter, pipe } from 'graphql-yoga'\nimport { Arg, Field, Int, ObjectType, Resolver, Root, Subscription } from 'type-graphql'\n\nimport { pubsub, ScalarObject } from '@things-factory/shell'\n\nimport { TwinEngine } from '../../engine/index.js'\n\n/* State 채널 메시지 — snapshot | delta | clock 멀티플렉싱(커널 SubscriptionMessage 대응). */\n@ObjectType({ description: 'Twin State channel message multiplexing snapshot | delta | clock.' })\nexport class TwinStateMessage {\n @Field({ description: 'Twin runtime instance id this message belongs to.' })\n instanceId: string\n\n @Field({ description: 'Message kind: snapshot | delta | clock.' })\n kind: string\n\n @Field(type => Int, { nullable: true, description: 'Monotonic state revision.' })\n revision?: number\n\n @Field(type => ScalarObject, { nullable: true, description: 'Payload — snapshot: StateSnapshot / delta: CanonicalEnvelope / clock: { simClockMs }.' })\n payload?: any\n}\n\n/*\n * 트윈 State 구독 — state-register/data-resolver 패턴 미러.\n * 구독 즉시 현재 snapshot 발행(process.nextTick) 후 delta/clock 스트림. instanceId 로 필터.\n */\n@Resolver()\nexport class TwinStateSubscription {\n @Subscription(returns => TwinStateMessage, {\n subscribe: ({ args, context }) => {\n const { instanceId } = args\n const domainId = (context as any)?.state?.domain?.id\n\n /* 테넌트 격리 — 이 도메인 소유 인스턴스가 아니면 스냅샷도 스트림도 흘리지 않는다(빈 구독). */\n if (!domainId || !TwinEngine.owns(domainId, instanceId)) {\n return pipe(pubsub.subscribe('twin-state'), filter(() => false))\n }\n\n /* 구독 순간 현재 상태를 먼저 발행(late 구독자도 snapshot→delta 순서 보장). */\n process.nextTick(() => {\n const snap = TwinEngine.snapshot(domainId, instanceId)\n if (snap) {\n pubsub.publish('twin-state', {\n twinState: { instanceId, kind: 'snapshot', revision: snap.revision, payload: snap }\n })\n }\n })\n\n return pipe(\n pubsub.subscribe('twin-state'),\n filter((payload: { twinState: TwinStateMessage }) => payload.twinState.instanceId === instanceId)\n )\n }\n })\n twinState(@Root() payload: { twinState: TwinStateMessage }, @Arg('instanceId') instanceId: string): TwinStateMessage {\n return payload.twinState\n }\n}\n"]}
|
|
@@ -6,6 +6,7 @@ const typeorm_1 = require("typeorm");
|
|
|
6
6
|
const type_graphql_1 = require("type-graphql");
|
|
7
7
|
const shell_1 = require("@things-factory/shell");
|
|
8
8
|
const auth_base_1 = require("@things-factory/auth-base");
|
|
9
|
+
const board_vocabulary_js_1 = require("../../engine/board-vocabulary.js");
|
|
9
10
|
/*
|
|
10
11
|
* TwinStructure — **한 트윈이 거쳐 온 공장 구조들.** 리비전 하나가 그 시절의 공장이다.
|
|
11
12
|
*
|
|
@@ -64,7 +65,7 @@ tslib_1.__decorate([
|
|
|
64
65
|
tslib_1.__metadata("design:type", String)
|
|
65
66
|
], TwinStructure.prototype, "signature", void 0);
|
|
66
67
|
tslib_1.__decorate([
|
|
67
|
-
(0, typeorm_1.Column)({ type: 'simple-json' }),
|
|
68
|
+
(0, typeorm_1.Column)({ type: 'simple-json', transformer: board_vocabulary_js_1.boardColumnTransformer }),
|
|
68
69
|
(0, type_graphql_1.Field)(type => shell_1.ScalarObject, { description: 'The full BoardDef as it stood for this revision. Stored whole rather than as a diff: replay needs the entire topology, and rebuilding it from diffs would be a second rule that can drift.' }),
|
|
69
70
|
tslib_1.__metadata("design:type", Object)
|
|
70
71
|
], TwinStructure.prototype, "board", void 0);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"twin-structure.js","sourceRoot":"","sources":["../../../server/service/twin-structure/twin-structure.ts"],"names":[],"mappings":";;;;AAAA,qCAAgH;AAChH,+CAAyD;AAEzD,iDAA4D;AAC5D,yDAAgD;
|
|
1
|
+
{"version":3,"file":"twin-structure.js","sourceRoot":"","sources":["../../../server/service/twin-structure/twin-structure.ts"],"names":[],"mappings":";;;;AAAA,qCAAgH;AAChH,+CAAyD;AAEzD,iDAA4D;AAC5D,yDAAgD;AAChD,0EAAyE;AAEzE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAOI,IAAM,aAAa,GAAnB,MAAM,aAAa;CAyDzB,CAAA;AAzDY,sCAAa;AAGf;IAFR,IAAA,gCAAsB,EAAC,MAAM,CAAC;IAC9B,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,iBAAE,EAAE,EAAE,WAAW,EAAE,qDAAqD,EAAE,CAAC;;yCACvE;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;6CAAA;AAGf;IADC,IAAA,oBAAU,EAAC,CAAC,CAAgB,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;;+CAC1B;AAIjB;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,kDAAkD,EAAE,CAAC;;iDACzD;AAUlB;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACvB,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,kBAAG,EAAE,EAAE,WAAW,EAAE,uKAAuK,EAAE,CAAC;;0CAClM;AAUX;IAFC,IAAA,gBAAM,EAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACtB,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,mNAAmN,EAAE,CAAC;;gDAC3N;AAMjB;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,aAAa,EAAE,WAAW,EAAE,4CAAsB,EAAE,CAAC;IACpE,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,oBAAY,EAAE,EAAE,WAAW,EAAE,4LAA4L,EAAE,CAAC;;4CACjO;AAKV;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,8EAA8E,EAAE,CAAC;;8CACvG;AAIhB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,wCAAwC,EAAE,CAAC;sCACrF,gBAAI;8CAAA;AAGd;IADC,IAAA,oBAAU,EAAC,CAAC,CAAgB,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;;gDAC1B;AAIlB;IAFC,IAAA,0BAAgB,GAAE;IAClB,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,iCAAiC,EAAE,CAAC;sCAC9D,IAAI;gDAAA;wBAxDL,aAAa;IANzB,IAAA,gBAAM,GAAE;IACR,IAAA,eAAK,EAAC,qBAAqB,EAAE,CAAC,CAAgB,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IACrG,IAAA,yBAAU,EAAC;QACV,WAAW,EACT,0QAA0Q;KAC7Q,CAAC;GACW,aAAa,CAyDzB","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'\nimport { User } from '@things-factory/auth-base'\nimport { boardColumnTransformer } from '../../engine/board-vocabulary.js'\n\n/*\n * TwinStructure — **한 트윈이 거쳐 온 공장 구조들.** 리비전 하나가 그 시절의 공장이다.\n *\n * ── 왜 필요한가: 역사를 잃거나 거짓말을 하거나 ──────────────────────────────\n * 공장은 바뀐다. 도장 부스를 넷 더 놓고, 라인을 하나 접는다. 그런데 지금까지는 구조 서명이 바뀌면\n * **그 트윈의 저널을 통째로 지우는 것**이 유일한 길이었다. 안 지우면 옛 이벤트를 새 공장에 대고\n * 접게 되어 이력이 거짓말을 하기 때문이다 — 부스가 둘이던 시절의 사실을 여섯 개짜리 공장에 접으면\n * 그때 없던 부스에서 일이 있었던 것처럼 보인다.\n *\n * 그래서 선택지가 둘뿐이었다: **역사를 잃거나, 거짓말을 하거나.**\n *\n * 셋째 길이 이 표다. 이벤트가 `structureRev` 로 자기 구조를 달고 다니고, 재생은 구조가 바뀌는\n * 지점에서 갈아탄 뒤 이어 접는다(`replaySegments`). 이력은 \"그때 그 공장의 사실\" 로 계속 읽힌다.\n *\n * ── 왜 board 를 통째로 담나 ─────────────────────────────────────────────────\n * 재생하려면 **그때의 토폴로지 전체**가 필요하다. 차이(diff)만 담으면 리비전 1부터 되짚어 쌓아야\n * 하고, 그 되짚기 자체가 또 하나의 규칙이 되어 갈라진다. 구조는 이벤트에 비해 드물게 바뀌므로\n * 스냅샷을 통째로 두는 편이 싸고 정직하다(보드 이력 `BoardHistory` 와 같은 선택).\n *\n * ── audit 컬럼을 다 두지 않는 이유 ──────────────────────────────────────────\n * 리비전은 **한 번 쓰이면 바뀌지 않는다** — 그 시절의 사실이라 고칠 수가 없다. 그래서 updater·\n * deletedAt 이 없다(TwinEvent 와 같은 규율). 대신 **누가 구조를 바꿨는지**는 남긴다.\n *\n * (CLAUDE.md: 모든 @ObjectType/@Field 는 영문 description 필수.)\n */\n@Entity()\n@Index('ix_twin_structure_0', (e: TwinStructure) => [e.domain, e.instanceId, e.rev], { unique: true })\n@ObjectType({\n description:\n 'One structural revision of a twin — the plant as it stood for a stretch of its journal. Events carry the revision they were produced under, so history can be replayed against the structure that was actually in force instead of being deleted when the plant changes.'\n})\nexport class TwinStructure {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Unique identifier of the structure revision 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: TwinStructure) => e.domain)\n domainId?: string\n\n @Column()\n @Field({ description: 'Twin runtime instance this structure belongs to.' })\n instanceId: string\n\n /**\n * 리비전 번호 — 1부터. 이벤트의 `structureRev` 가 이것을 가리킨다.\n *\n * 0 을 안 쓰는 이유: 컬럼이 없던 시절의 행은 `structureRev` 가 비어 있고, 그 행들은 **가장 오래된\n * 리비전**에 속한다. 0 을 유효 번호로 쓰면 \"모름\" 과 \"1번 구조\" 가 같은 값이 된다.\n */\n @Column({ type: 'int' })\n @Field(type => Int, { description: 'Revision number, starting at 1. Events reference it via structureRev; absent on rows written before structure revisions existed, which belong to the oldest revision.' })\n rev: number\n\n /**\n * 구조 서명 — 이 리비전이 앞 리비전과 **정말 다른지** 판정한 근거.\n *\n * 서명이 같으면 새 리비전을 만들지 않는다. 프로비저닝은 자주 다시 도는데(부팅마다) 그때마다\n * 리비전이 하나씩 늘면 이력이 뜻 없는 마디로 잘게 쪼개진다.\n */\n @Column({ length: 64 })\n @Field({ description: 'Short hash of the structural signature (locations + equipment). A re-provision with the same signature does not create a new revision — otherwise every reboot would slice the history into meaningless segments.' })\n signature: string\n\n /* 읽을 때 어휘 세대를 흡수한다 — 옛 리비전의 보드로 지문을 다시 계산할 때 빈 공장으로 읽히면\n 구조 변경 감지가 죽는다(`engine/board-vocabulary.ts`). */\n @Column({ type: 'simple-json', transformer: boardColumnTransformer })\n @Field(type => ScalarObject, { description: 'The full BoardDef as it stood for this revision. Stored whole rather than as a diff: replay needs the entire topology, and rebuilding it from diffs would be a second rule that can drift.' })\n board: any\n\n /** 왜 바뀌었나 — 사람이 남기는 한 줄. 없을 수 있다(자동 프로비저닝은 이유를 모른다). */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Why the structure changed — a human note, absent for automatic provisioning.' })\n comment?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'Who changed the structure, when known.' })\n creator?: User\n\n @RelationId((e: TwinStructure) => e.creator)\n creatorId?: string\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'When this revision took effect.' })\n createdAt?: Date\n}\n"]}
|
|
@@ -12,7 +12,26 @@ const kpi_target_js_1 = require("../../engine/kpi-target.js");
|
|
|
12
12
|
* 경영 KPI 관리메뉴가 아니라 여기인 이유: 목표가 숫자 옆에 있어야 맥락이 살고, 운영 임계는 자주
|
|
13
13
|
* 조정된다. 조직 단위 기간 평가는 `@things-factory/kpi` 의 일이고 이쪽으로 베껴 오지 않는다.
|
|
14
14
|
*/
|
|
15
|
+
/*
|
|
16
|
+
* **쓰기는 막고 읽기는 연다** (2026-08-06 · ADR-0027).
|
|
17
|
+
*
|
|
18
|
+
* 이 리졸버는 트윈 리졸버 중 **유일하게** 권한 게이트를 갖는데, 처음에는 우회 표시가 없었다
|
|
19
|
+
* (`@privilege(category:"twin", privilege:"query")`). 그러면 `twin/query`·`twin/mutation` 권한을
|
|
20
|
+
* 실제로 부여받은 사용자만 쓸 수 있는데 — **저장소 어디에도 그 권한을 만드는 곳이 없다.** 결과는
|
|
21
|
+
* 도메인 소유자도 슈퍼유저도 목표를 걸 수 없는 상태였다: 성과 화면의 목표 버튼이 "Unauthorized" 로
|
|
22
|
+
* 떨어지고, 이 관점의 최상위 기능(§5 목표 프레임)이 **아무도 쓸 수 없는 채로** 놓여 있었다.
|
|
23
|
+
*
|
|
24
|
+
* 그래서 프레임워크의 통용 관용구를 따른다 — 가장 가까운 형제인 `@things-factory/kpi`(목표·지표 관리)가
|
|
25
|
+
* 74곳 전부 `domainOwnerGranted: true, superUserGranted: true` 를 쓴다. 같은 성격의 조작(무엇을 잘한
|
|
26
|
+
* 것으로 볼지 정하는 일)이므로 같은 자세를 쓴다: **현장을 소유한 사람은 바로 쓰고**, 다른 사람에게
|
|
27
|
+
* 맡기려면 `twin` 권한을 부여한다.
|
|
28
|
+
*
|
|
29
|
+
* 이제 트윈의 **모든 변경**에 같은 게이트가 있다(ADR-0027) — 처음에는 이 리졸버 하나만 게이트를 갖는
|
|
30
|
+
* 반쪽이었다. 조회 게이트는 걷었다: 같은 판정이 `twinKpi` 로 이미 열려 있어 지키는 것이 없었다.
|
|
31
|
+
*/
|
|
15
32
|
let TwinTargetResolver = class TwinTargetResolver {
|
|
33
|
+
/* 조회에는 게이트를 두지 않는다 — 같은 판정이 `twinKpi.targets` 로 이미 열려 있어(그쪽은 게이트가
|
|
34
|
+
없다) 여기만 막으면 지키는 것 없이 화면만 깨진다. 읽기의 경계는 **테넌트 소속**이다. */
|
|
16
35
|
async twinTargetMetrics() {
|
|
17
36
|
return Object.keys(kpi_target_js_1.TWIN_METRIC);
|
|
18
37
|
}
|
|
@@ -88,7 +107,6 @@ let TwinTargetResolver = class TwinTargetResolver {
|
|
|
88
107
|
};
|
|
89
108
|
exports.TwinTargetResolver = TwinTargetResolver;
|
|
90
109
|
tslib_1.__decorate([
|
|
91
|
-
(0, type_graphql_1.Directive)('@privilege(category: "twin", privilege: "query")'),
|
|
92
110
|
(0, type_graphql_1.Query)(returns => [String], {
|
|
93
111
|
description: 'The metric ids a twin target may refer to. Fixed in one place so a target and the fact it judges can never drift into two names.'
|
|
94
112
|
}),
|
|
@@ -97,7 +115,6 @@ tslib_1.__decorate([
|
|
|
97
115
|
tslib_1.__metadata("design:returntype", Promise)
|
|
98
116
|
], TwinTargetResolver.prototype, "twinTargetMetrics", null);
|
|
99
117
|
tslib_1.__decorate([
|
|
100
|
-
(0, type_graphql_1.Directive)('@privilege(category: "twin", privilege: "query")'),
|
|
101
118
|
(0, type_graphql_1.Query)(returns => [twin_target_js_1.TwinTarget], {
|
|
102
119
|
description: 'Operational targets in scope — site-wide targets for the space plus targets bound to the given instance. Callers judge with the narrower one when both exist.'
|
|
103
120
|
}),
|
|
@@ -109,7 +126,7 @@ tslib_1.__decorate([
|
|
|
109
126
|
tslib_1.__metadata("design:returntype", Promise)
|
|
110
127
|
], TwinTargetResolver.prototype, "twinTargets", null);
|
|
111
128
|
tslib_1.__decorate([
|
|
112
|
-
(0, type_graphql_1.Directive)('@privilege(category: "twin", privilege: "mutation")'),
|
|
129
|
+
(0, type_graphql_1.Directive)('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)'),
|
|
113
130
|
(0, type_graphql_1.Mutation)(returns => Boolean, {
|
|
114
131
|
description: 'Create or update one operational target. The metric must be one of twinTargetMetrics — an unknown name is rejected rather than stored, because a target nobody can evaluate reads as "no target" and hides the gap.'
|
|
115
132
|
}),
|
|
@@ -125,7 +142,7 @@ tslib_1.__decorate([
|
|
|
125
142
|
tslib_1.__metadata("design:returntype", Promise)
|
|
126
143
|
], TwinTargetResolver.prototype, "saveTwinTarget", null);
|
|
127
144
|
tslib_1.__decorate([
|
|
128
|
-
(0, type_graphql_1.Directive)('@privilege(category: "twin", privilege: "mutation")'),
|
|
145
|
+
(0, type_graphql_1.Directive)('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)'),
|
|
129
146
|
(0, type_graphql_1.Mutation)(returns => Boolean, { description: 'Remove an operational target. Removing a target removes the judgement, not the fact — the metric keeps being reported without a verdict.' }),
|
|
130
147
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('metric')),
|
|
131
148
|
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"twin-target-resolver.js","sourceRoot":"","sources":["../../../server/service/twin-target/twin-target-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Directive, Float, Mutation, Query, Resolver } from 'type-graphql'\n\nimport { getRepository } from '@things-factory/shell'\n\nimport { TwinTarget } from './twin-target.js'\nimport { TWIN_METRIC, isTwinMetric } from '../../engine/kpi-target.js'\n\n/*\n * 운영 목표 조회·편집 — **현장 관리자가 트윈 화면에서** 고친다(사용자 결정 2026-08-02).\n *\n * 경영 KPI 관리메뉴가 아니라 여기인 이유: 목표가 숫자 옆에 있어야 맥락이 살고, 운영 임계는 자주\n * 조정된다. 조직 단위 기간 평가는 `@things-factory/kpi` 의 일이고 이쪽으로 베껴 오지 않는다.\n */\n@Resolver()\nexport class TwinTargetResolver {\n @Directive('@privilege(category: \"twin\", privilege: \"query\")')\n @Query(returns => [String], {\n description:\n 'The metric ids a twin target may refer to. Fixed in one place so a target and the fact it judges can never drift into two names.'\n })\n async twinTargetMetrics(): Promise<string[]> {\n return Object.keys(TWIN_METRIC)\n }\n\n @Directive('@privilege(category: \"twin\", privilege: \"query\")')\n @Query(returns => [TwinTarget], {\n description:\n 'Operational targets in scope — site-wide targets for the space plus targets bound to the given instance. Callers judge with the narrower one when both exist.'\n })\n async twinTargets(\n @Ctx() context: ResolverContext,\n @Arg('spaceId', { nullable: true }) spaceId?: string,\n @Arg('instanceId', { nullable: true }) instanceId?: string\n ): Promise<TwinTarget[]> {\n const domainId = context.state.domain.id\n const rows = await getRepository(TwinTarget).find({ where: { domain: { id: domainId } } })\n return rows.filter(r => (spaceId && r.spaceId === spaceId) || (instanceId && r.instanceId === instanceId))\n }\n\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\")')\n @Mutation(returns => Boolean, {\n description:\n 'Create or update one operational target. The metric must be one of twinTargetMetrics — an unknown name is rejected rather than stored, because a target nobody can evaluate reads as \"no target\" and hides the gap.'\n })\n async saveTwinTarget(\n @Arg('metric') metric: string,\n @Arg('target', type => Float) target: number,\n @Ctx() context: ResolverContext,\n @Arg('spaceId', { nullable: true }) spaceId?: string,\n @Arg('instanceId', { nullable: true }) instanceId?: string,\n @Arg('axis', { nullable: true }) axis?: string,\n @Arg('axisKey', { nullable: true }) axisKey?: string\n ): Promise<boolean> {\n if (!isTwinMetric(metric)) throw new Error(`unknown twin metric \"${metric}\" — see twinTargetMetrics`)\n if (!Number.isFinite(target)) throw new Error('target must be a finite number')\n if (!spaceId && !instanceId) throw new Error('a target must be scoped to a space or an instance')\n /* 축만 있고 값이 없으면 **어디에 거는 목표인지 알 수 없다** — 조용히 전체 목표로 만들지 않는다\n (그러면 사용자는 구역 목표를 걸었다고 믿는데 전체가 바뀐다). */\n if (axis && !axisKey) throw new Error('axisKey is required when axis is given')\n if (!axis && axisKey) throw new Error('axis is required when axisKey is given')\n const domainId = context.state.domain.id\n const repo = getRepository(TwinTarget)\n const existing = await repo.findOne({\n where: { domain: { id: domainId }, spaceId: spaceId ?? null, instanceId: instanceId ?? null, metric, axis: axis ?? null, axisKey: axisKey ?? null } as any\n })\n await repo.save(\n repo.create({\n ...(existing ?? {}),\n domain: { id: domainId } as any,\n spaceId: spaceId ?? null as any,\n instanceId: instanceId ?? null as any,\n axis: axis ?? null as any,\n axisKey: axisKey ?? null as any,\n metric,\n target,\n ...(existing ? { updater: context.state.user } : { creator: context.state.user, updater: context.state.user })\n })\n )\n return true\n }\n\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\")')\n @Mutation(returns => Boolean, { description: 'Remove an operational target. Removing a target removes the judgement, not the fact — the metric keeps being reported without a verdict.' })\n async deleteTwinTarget(\n @Arg('metric') metric: string,\n @Ctx() context: ResolverContext,\n @Arg('spaceId', { nullable: true }) spaceId?: string,\n @Arg('instanceId', { nullable: true }) instanceId?: string,\n @Arg('axis', { nullable: true }) axis?: string,\n @Arg('axisKey', { nullable: true }) axisKey?: string\n ): Promise<boolean> {\n const domainId = context.state.domain.id\n const repo = getRepository(TwinTarget)\n\n /*\n * **찾아서 그 행만 지운다.** 예전에는 조건 객체를 `delete()` 에 그대로 넘겼는데 **아무 행도\n * 안 지워지면서 `true` 를 돌려줬다.** 사용자는 목표를 지웠다고 믿고 화면을 떠나는데 판정은 그대로\n * 남는다(실화면에서 그렇게 당했다).\n *\n * 무엇이 조건을 깨뜨렸는지는 단정하지 않는다 — 이 조건에는 `domain` 관계 객체와 `null` 값들이\n * 함께 들어 있었고(`instanceId`·`axis`·`axisKey`), 둘 다 `delete()` 의 조건 해석에서 흔한 함정이다.\n * 같은 관계-객체 패턴을 쓰는 `resetJournal` 은 실제로 잘 지우므로 관계 객체 하나만으로 설명되지\n * 않는다. `null` 조건이 `IS NULL` 이 아니라 `= NULL` 로 나갔을 가능성이 크다.\n *\n * 그래서 조건을 고쳐 맞추는 대신 **행을 찾아 id 로 지운다**: 저장 경로와 **같은 조회 규칙**을\n * 쓰므로 규칙이 갈라지지 않고(`findOne` 은 `null` 을 제대로 다룬다), 지우는 대상이 정확히 한\n * 행이라 넓게 지울 위험도 없다.\n */\n const row = await repo.findOne({\n where: {\n domain: { id: domainId },\n spaceId: spaceId ?? null,\n instanceId: instanceId ?? null,\n axis: axis ?? null,\n axisKey: axisKey ?? null,\n metric\n } as any\n })\n /* 지울 것이 없으면 **없었다고 답한다.** 지우지 않고 true 를 주면 위와 같은 거짓말이 된다. */\n if (!row) return false\n await repo.delete(row.id)\n return true\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"twin-target-resolver.js","sourceRoot":"","sources":["../../../server/service/twin-target/twin-target-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Directive, Float, Mutation, Query, Resolver } from 'type-graphql'\n\nimport { getRepository } from '@things-factory/shell'\n\nimport { TwinTarget } from './twin-target.js'\nimport { TWIN_METRIC, isTwinMetric } from '../../engine/kpi-target.js'\n\n/*\n * 운영 목표 조회·편집 — **현장 관리자가 트윈 화면에서** 고친다(사용자 결정 2026-08-02).\n *\n * 경영 KPI 관리메뉴가 아니라 여기인 이유: 목표가 숫자 옆에 있어야 맥락이 살고, 운영 임계는 자주\n * 조정된다. 조직 단위 기간 평가는 `@things-factory/kpi` 의 일이고 이쪽으로 베껴 오지 않는다.\n */\n/*\n * **쓰기는 막고 읽기는 연다** (2026-08-06 · ADR-0027).\n *\n * 이 리졸버는 트윈 리졸버 중 **유일하게** 권한 게이트를 갖는데, 처음에는 우회 표시가 없었다\n * (`@privilege(category:\"twin\", privilege:\"query\")`). 그러면 `twin/query`·`twin/mutation` 권한을\n * 실제로 부여받은 사용자만 쓸 수 있는데 — **저장소 어디에도 그 권한을 만드는 곳이 없다.** 결과는\n * 도메인 소유자도 슈퍼유저도 목표를 걸 수 없는 상태였다: 성과 화면의 목표 버튼이 \"Unauthorized\" 로\n * 떨어지고, 이 관점의 최상위 기능(§5 목표 프레임)이 **아무도 쓸 수 없는 채로** 놓여 있었다.\n *\n * 그래서 프레임워크의 통용 관용구를 따른다 — 가장 가까운 형제인 `@things-factory/kpi`(목표·지표 관리)가\n * 74곳 전부 `domainOwnerGranted: true, superUserGranted: true` 를 쓴다. 같은 성격의 조작(무엇을 잘한\n * 것으로 볼지 정하는 일)이므로 같은 자세를 쓴다: **현장을 소유한 사람은 바로 쓰고**, 다른 사람에게\n * 맡기려면 `twin` 권한을 부여한다.\n *\n * 이제 트윈의 **모든 변경**에 같은 게이트가 있다(ADR-0027) — 처음에는 이 리졸버 하나만 게이트를 갖는\n * 반쪽이었다. 조회 게이트는 걷었다: 같은 판정이 `twinKpi` 로 이미 열려 있어 지키는 것이 없었다.\n */\n@Resolver()\nexport class TwinTargetResolver {\n /* 조회에는 게이트를 두지 않는다 — 같은 판정이 `twinKpi.targets` 로 이미 열려 있어(그쪽은 게이트가\n 없다) 여기만 막으면 지키는 것 없이 화면만 깨진다. 읽기의 경계는 **테넌트 소속**이다. */\n @Query(returns => [String], {\n description:\n 'The metric ids a twin target may refer to. Fixed in one place so a target and the fact it judges can never drift into two names.'\n })\n async twinTargetMetrics(): Promise<string[]> {\n return Object.keys(TWIN_METRIC)\n }\n\n\n @Query(returns => [TwinTarget], {\n description:\n 'Operational targets in scope — site-wide targets for the space plus targets bound to the given instance. Callers judge with the narrower one when both exist.'\n })\n async twinTargets(\n @Ctx() context: ResolverContext,\n @Arg('spaceId', { nullable: true }) spaceId?: string,\n @Arg('instanceId', { nullable: true }) instanceId?: string\n ): Promise<TwinTarget[]> {\n const domainId = context.state.domain.id\n const rows = await getRepository(TwinTarget).find({ where: { domain: { id: domainId } } })\n return rows.filter(r => (spaceId && r.spaceId === spaceId) || (instanceId && r.instanceId === instanceId))\n }\n\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => Boolean, {\n description:\n 'Create or update one operational target. The metric must be one of twinTargetMetrics — an unknown name is rejected rather than stored, because a target nobody can evaluate reads as \"no target\" and hides the gap.'\n })\n async saveTwinTarget(\n @Arg('metric') metric: string,\n @Arg('target', type => Float) target: number,\n @Ctx() context: ResolverContext,\n @Arg('spaceId', { nullable: true }) spaceId?: string,\n @Arg('instanceId', { nullable: true }) instanceId?: string,\n @Arg('axis', { nullable: true }) axis?: string,\n @Arg('axisKey', { nullable: true }) axisKey?: string\n ): Promise<boolean> {\n if (!isTwinMetric(metric)) throw new Error(`unknown twin metric \"${metric}\" — see twinTargetMetrics`)\n if (!Number.isFinite(target)) throw new Error('target must be a finite number')\n if (!spaceId && !instanceId) throw new Error('a target must be scoped to a space or an instance')\n /* 축만 있고 값이 없으면 **어디에 거는 목표인지 알 수 없다** — 조용히 전체 목표로 만들지 않는다\n (그러면 사용자는 구역 목표를 걸었다고 믿는데 전체가 바뀐다). */\n if (axis && !axisKey) throw new Error('axisKey is required when axis is given')\n if (!axis && axisKey) throw new Error('axis is required when axisKey is given')\n const domainId = context.state.domain.id\n const repo = getRepository(TwinTarget)\n const existing = await repo.findOne({\n where: { domain: { id: domainId }, spaceId: spaceId ?? null, instanceId: instanceId ?? null, metric, axis: axis ?? null, axisKey: axisKey ?? null } as any\n })\n await repo.save(\n repo.create({\n ...(existing ?? {}),\n domain: { id: domainId } as any,\n spaceId: spaceId ?? null as any,\n instanceId: instanceId ?? null as any,\n axis: axis ?? null as any,\n axisKey: axisKey ?? null as any,\n metric,\n target,\n ...(existing ? { updater: context.state.user } : { creator: context.state.user, updater: context.state.user })\n })\n )\n return true\n }\n\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => Boolean, { description: 'Remove an operational target. Removing a target removes the judgement, not the fact — the metric keeps being reported without a verdict.' })\n async deleteTwinTarget(\n @Arg('metric') metric: string,\n @Ctx() context: ResolverContext,\n @Arg('spaceId', { nullable: true }) spaceId?: string,\n @Arg('instanceId', { nullable: true }) instanceId?: string,\n @Arg('axis', { nullable: true }) axis?: string,\n @Arg('axisKey', { nullable: true }) axisKey?: string\n ): Promise<boolean> {\n const domainId = context.state.domain.id\n const repo = getRepository(TwinTarget)\n\n /*\n * **찾아서 그 행만 지운다.** 예전에는 조건 객체를 `delete()` 에 그대로 넘겼는데 **아무 행도\n * 안 지워지면서 `true` 를 돌려줬다.** 사용자는 목표를 지웠다고 믿고 화면을 떠나는데 판정은 그대로\n * 남는다(실화면에서 그렇게 당했다).\n *\n * 무엇이 조건을 깨뜨렸는지는 단정하지 않는다 — 이 조건에는 `domain` 관계 객체와 `null` 값들이\n * 함께 들어 있었고(`instanceId`·`axis`·`axisKey`), 둘 다 `delete()` 의 조건 해석에서 흔한 함정이다.\n * 같은 관계-객체 패턴을 쓰는 `resetJournal` 은 실제로 잘 지우므로 관계 객체 하나만으로 설명되지\n * 않는다. `null` 조건이 `IS NULL` 이 아니라 `= NULL` 로 나갔을 가능성이 크다.\n *\n * 그래서 조건을 고쳐 맞추는 대신 **행을 찾아 id 로 지운다**: 저장 경로와 **같은 조회 규칙**을\n * 쓰므로 규칙이 갈라지지 않고(`findOne` 은 `null` 을 제대로 다룬다), 지우는 대상이 정확히 한\n * 행이라 넓게 지울 위험도 없다.\n */\n const row = await repo.findOne({\n where: {\n domain: { id: domainId },\n spaceId: spaceId ?? null,\n instanceId: instanceId ?? null,\n axis: axis ?? null,\n axisKey: axisKey ?? null,\n metric\n } as any\n })\n /* 지울 것이 없으면 **없었다고 답한다.** 지우지 않고 true 를 주면 위와 같은 거짓말이 된다. */\n if (!row) return false\n await repo.delete(row.id)\n return true\n }\n}\n"]}
|