@things-factory/station 10.1.33
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 +124 -0
- package/dist-client/index.d.ts +11 -0
- package/dist-client/index.js +13 -0
- package/dist-client/index.js.map +1 -0
- package/dist-client/station/scene/index.d.ts +11 -0
- package/dist-client/station/scene/index.js +12 -0
- package/dist-client/station/scene/index.js.map +1 -0
- package/dist-client/station/scene/station-context-binding.d.ts +25 -0
- package/dist-client/station/scene/station-context-binding.js +39 -0
- package/dist-client/station/scene/station-context-binding.js.map +1 -0
- package/dist-client/station/scene/station-context-scene.d.ts +28 -0
- package/dist-client/station/scene/station-context-scene.js +138 -0
- package/dist-client/station/scene/station-context-scene.js.map +1 -0
- package/dist-client/station/scene/station-counts-scene.d.ts +6 -0
- package/dist-client/station/scene/station-counts-scene.js +51 -0
- package/dist-client/station/scene/station-counts-scene.js.map +1 -0
- package/dist-client/station/scene/station-field-scene.d.ts +6 -0
- package/dist-client/station/scene/station-field-scene.js +89 -0
- package/dist-client/station/scene/station-field-scene.js.map +1 -0
- package/dist-client/station/scene/station-icons.d.ts +82 -0
- package/dist-client/station/scene/station-icons.js +131 -0
- package/dist-client/station/scene/station-icons.js.map +1 -0
- package/dist-client/station/scene/station-numpad-scene.d.ts +9 -0
- package/dist-client/station/scene/station-numpad-scene.js +144 -0
- package/dist-client/station/scene/station-numpad-scene.js.map +1 -0
- package/dist-client/station/scene/station-scene-style.d.ts +51 -0
- package/dist-client/station/scene/station-scene-style.js +108 -0
- package/dist-client/station/scene/station-scene-style.js.map +1 -0
- package/dist-client/station/scene/station-scene.d.ts +295 -0
- package/dist-client/station/scene/station-scene.js +68 -0
- package/dist-client/station/scene/station-scene.js.map +1 -0
- package/dist-client/station/scene/station-who-scene.d.ts +6 -0
- package/dist-client/station/scene/station-who-scene.js +48 -0
- package/dist-client/station/scene/station-who-scene.js.map +1 -0
- package/dist-client/station/scene/station-work-panel-scene.d.ts +28 -0
- package/dist-client/station/scene/station-work-panel-scene.js +37 -0
- package/dist-client/station/scene/station-work-panel-scene.js.map +1 -0
- package/dist-client/station/station-board-binding.d.ts +16 -0
- package/dist-client/station/station-board-binding.js +20 -0
- package/dist-client/station/station-board-binding.js.map +1 -0
- package/dist-client/station/station-context.d.ts +178 -0
- package/dist-client/station/station-context.js +30 -0
- package/dist-client/station/station-context.js.map +1 -0
- package/dist-client/station/station-palette.d.ts +34 -0
- package/dist-client/station/station-palette.js +119 -0
- package/dist-client/station/station-palette.js.map +1 -0
- package/dist-client/station/station-runtime.d.ts +47 -0
- package/dist-client/station/station-runtime.js +47 -0
- package/dist-client/station/station-runtime.js.map +1 -0
- package/dist-client/station/station-scene-context.d.ts +68 -0
- package/dist-client/station/station-scene-context.js +62 -0
- package/dist-client/station/station-scene-context.js.map +1 -0
- package/dist-client/station/station-screen-flow.d.ts +16 -0
- package/dist-client/station/station-screen-flow.js +58 -0
- package/dist-client/station/station-screen-flow.js.map +1 -0
- package/dist-client/station/station-tile-registry.d.ts +89 -0
- package/dist-client/station/station-tile-registry.js +86 -0
- package/dist-client/station/station-tile-registry.js.map +1 -0
- package/dist-client/station/station-work-panel-view.d.ts +23 -0
- package/dist-client/station/station-work-panel-view.js +125 -0
- package/dist-client/station/station-work-panel-view.js.map +1 -0
- package/dist-client/station/station-work-runtime.d.ts +47 -0
- package/dist-client/station/station-work-runtime.js +161 -0
- package/dist-client/station/station-work-runtime.js.map +1 -0
- package/dist-client/station/station-work-scenarios.d.ts +41 -0
- package/dist-client/station/station-work-scenarios.js +59 -0
- package/dist-client/station/station-work-scenarios.js.map +1 -0
- package/dist-client/station/task-counts.d.ts +32 -0
- package/dist-client/station/task-counts.js +26 -0
- package/dist-client/station/task-counts.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -0
- package/dist-server/activities/activity-station-screen-control-withdrawal.d.ts +62 -0
- package/dist-server/activities/activity-station-screen-control-withdrawal.js +147 -0
- package/dist-server/activities/activity-station-screen-control-withdrawal.js.map +1 -0
- package/dist-server/activities/activity-station-screen-release.d.ts +81 -0
- package/dist-server/activities/activity-station-screen-release.js +147 -0
- package/dist-server/activities/activity-station-screen-release.js.map +1 -0
- package/dist-server/activities/index.d.ts +49 -0
- package/dist-server/activities/index.js +21 -0
- package/dist-server/activities/index.js.map +1 -0
- package/dist-server/index.d.ts +3 -0
- package/dist-server/index.js +8 -0
- package/dist-server/index.js.map +1 -0
- package/dist-server/service/index.d.ts +23 -0
- package/dist-server/service/index.js +41 -0
- package/dist-server/service/index.js.map +1 -0
- package/dist-server/service/station/deletion-changes.d.ts +14 -0
- package/dist-server/service/station/deletion-changes.js +25 -0
- package/dist-server/service/station/deletion-changes.js.map +1 -0
- package/dist-server/service/station/index.d.ts +13 -0
- package/dist-server/service/station/index.js +25 -0
- package/dist-server/service/station/index.js.map +1 -0
- package/dist-server/service/station/reversibility.d.ts +21 -0
- package/dist-server/service/station/reversibility.js +24 -0
- package/dist-server/service/station/reversibility.js.map +1 -0
- package/dist-server/service/station/screen-model-digest.d.ts +60 -0
- package/dist-server/service/station/screen-model-digest.js +97 -0
- package/dist-server/service/station/screen-model-digest.js.map +1 -0
- package/dist-server/service/station/staged-material.d.ts +62 -0
- package/dist-server/service/station/staged-material.js +44 -0
- package/dist-server/service/station/staged-material.js.map +1 -0
- package/dist-server/service/station/station-argument-sources.d.ts +31 -0
- package/dist-server/service/station/station-argument-sources.js +66 -0
- package/dist-server/service/station/station-argument-sources.js.map +1 -0
- package/dist-server/service/station/station-assignment-choice.d.ts +81 -0
- package/dist-server/service/station/station-assignment-choice.js +105 -0
- package/dist-server/service/station/station-assignment-choice.js.map +1 -0
- package/dist-server/service/station/station-assignment-resolver.d.ts +104 -0
- package/dist-server/service/station/station-assignment-resolver.js +426 -0
- package/dist-server/service/station/station-assignment-resolver.js.map +1 -0
- package/dist-server/service/station/station-assignment-service.d.ts +33 -0
- package/dist-server/service/station/station-assignment-service.js +64 -0
- package/dist-server/service/station/station-assignment-service.js.map +1 -0
- package/dist-server/service/station/station-axes.d.ts +9 -0
- package/dist-server/service/station/station-axes.js +16 -0
- package/dist-server/service/station/station-axes.js.map +1 -0
- package/dist-server/service/station/station-flow.d.ts +156 -0
- package/dist-server/service/station/station-flow.js +255 -0
- package/dist-server/service/station/station-flow.js.map +1 -0
- package/dist-server/service/station/station-generate.d.ts +76 -0
- package/dist-server/service/station/station-generate.js +178 -0
- package/dist-server/service/station/station-generate.js.map +1 -0
- package/dist-server/service/station/station-product.d.ts +123 -0
- package/dist-server/service/station/station-product.js +77 -0
- package/dist-server/service/station/station-product.js.map +1 -0
- package/dist-server/service/station/station-runtime-binding.d.ts +6 -0
- package/dist-server/service/station/station-runtime-binding.js +46 -0
- package/dist-server/service/station/station-runtime-binding.js.map +1 -0
- package/dist-server/service/station/station-screen-assignment.d.ts +94 -0
- package/dist-server/service/station/station-screen-assignment.js +154 -0
- package/dist-server/service/station/station-screen-assignment.js.map +1 -0
- package/dist-server/service/station/station-screen-control-withdrawal-resolver.d.ts +37 -0
- package/dist-server/service/station/station-screen-control-withdrawal-resolver.js +130 -0
- package/dist-server/service/station/station-screen-control-withdrawal-resolver.js.map +1 -0
- package/dist-server/service/station/station-screen-control-withdrawal.d.ts +104 -0
- package/dist-server/service/station/station-screen-control-withdrawal.js +128 -0
- package/dist-server/service/station/station-screen-control-withdrawal.js.map +1 -0
- package/dist-server/service/station/station-screen-draft-resolver.d.ts +47 -0
- package/dist-server/service/station/station-screen-draft-resolver.js +153 -0
- package/dist-server/service/station/station-screen-draft-resolver.js.map +1 -0
- package/dist-server/service/station/station-screen-layout.d.ts +119 -0
- package/dist-server/service/station/station-screen-layout.js +202 -0
- package/dist-server/service/station/station-screen-layout.js.map +1 -0
- package/dist-server/service/station/station-screen-list-resolver.d.ts +69 -0
- package/dist-server/service/station/station-screen-list-resolver.js +215 -0
- package/dist-server/service/station/station-screen-list-resolver.js.map +1 -0
- package/dist-server/service/station/station-screen-publish-subscriber.d.ts +22 -0
- package/dist-server/service/station/station-screen-publish-subscriber.js +90 -0
- package/dist-server/service/station/station-screen-publish-subscriber.js.map +1 -0
- package/dist-server/service/station/station-screen-release-gate.d.ts +66 -0
- package/dist-server/service/station/station-screen-release-gate.js +67 -0
- package/dist-server/service/station/station-screen-release-gate.js.map +1 -0
- package/dist-server/service/station/station-screen-release-record.d.ts +62 -0
- package/dist-server/service/station/station-screen-release-record.js +139 -0
- package/dist-server/service/station/station-screen-release-record.js.map +1 -0
- package/dist-server/service/station/station-screen-release-signature.d.ts +23 -0
- package/dist-server/service/station/station-screen-release-signature.js +32 -0
- package/dist-server/service/station/station-screen-release-signature.js.map +1 -0
- package/dist-server/service/station/station-screen-scorecard.d.ts +15 -0
- package/dist-server/service/station/station-screen-scorecard.js +35 -0
- package/dist-server/service/station/station-screen-scorecard.js.map +1 -0
- package/dist-server/service/station/station-screen-spec-resolver.d.ts +36 -0
- package/dist-server/service/station/station-screen-spec-resolver.js +202 -0
- package/dist-server/service/station/station-screen-spec-resolver.js.map +1 -0
- package/dist-server/service/station/station-screen-spec.d.ts +104 -0
- package/dist-server/service/station/station-screen-spec.js +114 -0
- package/dist-server/service/station/station-screen-spec.js.map +1 -0
- package/dist-server/service/station/station-screen-thumbnail.d.ts +11 -0
- package/dist-server/service/station/station-screen-thumbnail.js +36 -0
- package/dist-server/service/station/station-screen-thumbnail.js.map +1 -0
- package/dist-server/service/station/station-service-resolver.d.ts +27 -0
- package/dist-server/service/station/station-service-resolver.js +95 -0
- package/dist-server/service/station/station-service-resolver.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -0
- package/package.json +44 -0
- package/shared/station-axes.d.ts +4 -0
- package/shared/station-axes.js +155 -0
- package/shared/station-screen-flow-check.d.ts +12 -0
- package/shared/station-screen-flow-check.js +74 -0
- package/things-scene.config.js +15 -0
- package/translations/en.json +42 -0
- package/translations/ja.json +42 -0
- package/translations/ko.json +42 -0
- package/translations/ms.json +42 -0
- package/translations/zh.json +42 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { StationFlow } from './station-flow';
|
|
2
|
+
import { StationScreenSpec } from './station-screen-spec';
|
|
3
|
+
export declare class StationScreenSpecInput {
|
|
4
|
+
boardId: string;
|
|
5
|
+
requires?: string[];
|
|
6
|
+
target?: Record<string, unknown>;
|
|
7
|
+
controlled?: boolean;
|
|
8
|
+
kind?: 'work' | 'signal';
|
|
9
|
+
flow?: StationFlow;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Reading and writing the manufacturing side of a board.
|
|
13
|
+
*
|
|
14
|
+
* The drawing stays in board-service; this is only what the drawing needs in order to stand in
|
|
15
|
+
* front of an operator. Written by the modeller when a screen is saved, read by the assignment
|
|
16
|
+
* screen and by whoever is deciding whether a release needs approval.
|
|
17
|
+
*/
|
|
18
|
+
export declare class StationScreenSpecResolver {
|
|
19
|
+
stationScreenSpec(boardId: string, context: any): Promise<StationScreenSpec | null>;
|
|
20
|
+
/**
|
|
21
|
+
* Specs for several boards at once.
|
|
22
|
+
*
|
|
23
|
+
* The assignment screen needs one per row. Asked one at a time it is a round trip per assignment,
|
|
24
|
+
* and that list is the screen a plant manager keeps open.
|
|
25
|
+
*/
|
|
26
|
+
stationScreenSpecs(boardIds: string[], context: any): Promise<StationScreenSpec[]>;
|
|
27
|
+
/**
|
|
28
|
+
* Write the spec for a board — one row per board, replaced rather than appended.
|
|
29
|
+
*
|
|
30
|
+
* There is no version history here on purpose. The spec describes whatever the board currently
|
|
31
|
+
* is; the board keeps its own versions, and a spec that drifted from the drawing would answer a
|
|
32
|
+
* question nobody asked.
|
|
33
|
+
*/
|
|
34
|
+
saveStationScreenSpec(patch: StationScreenSpecInput, context: any): Promise<StationScreenSpec>;
|
|
35
|
+
deleteStationScreenSpec(boardId: string, context: any): Promise<boolean>;
|
|
36
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StationScreenSpecResolver = exports.StationScreenSpecInput = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const type_graphql_1 = require("type-graphql");
|
|
6
|
+
const typeorm_1 = require("typeorm");
|
|
7
|
+
const shell_1 = require("@things-factory/shell");
|
|
8
|
+
const station_flow_1 = require("./station-flow");
|
|
9
|
+
const station_screen_spec_1 = require("./station-screen-spec");
|
|
10
|
+
const station_screen_release_gate_1 = require("./station-screen-release-gate");
|
|
11
|
+
const station_screen_control_withdrawal_1 = require("./station-screen-control-withdrawal");
|
|
12
|
+
const station_screen_control_withdrawal_resolver_1 = require("./station-screen-control-withdrawal-resolver");
|
|
13
|
+
const worklist_1 = require("@things-factory/worklist");
|
|
14
|
+
/**
|
|
15
|
+
* 통제를 **이 저장으로는** 거둘 수 없다 — 결재로 세운 것은 결재로 거둔다(ADR-0047 ⑨).
|
|
16
|
+
*
|
|
17
|
+
* 저장과 삭제는 작성자의 권한(`station:mutation`)으로 열린다. 그 문으로 통제를 끄거나 통제 중인 설정을
|
|
18
|
+
* 지우면, 발행 관문은 통제가 아닌 화면을 그대로 내보낸다 — 작성자가 서명 없는 판을 내보내는 길이다.
|
|
19
|
+
* 그래서 이 둘은 막고 「통제 해제 승인」으로 보낸다.
|
|
20
|
+
*/
|
|
21
|
+
function controlWithdrawalNeedsApproval() {
|
|
22
|
+
return new shell_1.Refusal('control-withdrawal-needs-approval', {}, 'document control can only be removed through a control withdrawal approval.');
|
|
23
|
+
}
|
|
24
|
+
let StationScreenSpecInput = class StationScreenSpecInput {
|
|
25
|
+
};
|
|
26
|
+
exports.StationScreenSpecInput = StationScreenSpecInput;
|
|
27
|
+
tslib_1.__decorate([
|
|
28
|
+
(0, type_graphql_1.Field)({ description: "the board's id in board-service" }),
|
|
29
|
+
tslib_1.__metadata("design:type", String)
|
|
30
|
+
], StationScreenSpecInput.prototype, "boardId", void 0);
|
|
31
|
+
tslib_1.__decorate([
|
|
32
|
+
(0, type_graphql_1.Field)(type => [String], { nullable: true, description: 'station context axes the drawing reads' }),
|
|
33
|
+
tslib_1.__metadata("design:type", Array)
|
|
34
|
+
], StationScreenSpecInput.prototype, "requires", void 0);
|
|
35
|
+
tslib_1.__decorate([
|
|
36
|
+
(0, type_graphql_1.Field)(type => shell_1.ScalarObject, { nullable: true }),
|
|
37
|
+
tslib_1.__metadata("design:type", Object)
|
|
38
|
+
], StationScreenSpecInput.prototype, "target", void 0);
|
|
39
|
+
tslib_1.__decorate([
|
|
40
|
+
(0, type_graphql_1.Field)({ nullable: true }),
|
|
41
|
+
tslib_1.__metadata("design:type", Boolean)
|
|
42
|
+
], StationScreenSpecInput.prototype, "controlled", void 0);
|
|
43
|
+
tslib_1.__decorate([
|
|
44
|
+
(0, type_graphql_1.Field)({ nullable: true, description: "'work' or 'signal'" }),
|
|
45
|
+
tslib_1.__metadata("design:type", String)
|
|
46
|
+
], StationScreenSpecInput.prototype, "kind", void 0);
|
|
47
|
+
tslib_1.__decorate([
|
|
48
|
+
(0, type_graphql_1.Field)(type => shell_1.ScalarObject, { nullable: true, description: 'the steps, and where each argument comes from' }),
|
|
49
|
+
tslib_1.__metadata("design:type", Object)
|
|
50
|
+
], StationScreenSpecInput.prototype, "flow", void 0);
|
|
51
|
+
exports.StationScreenSpecInput = StationScreenSpecInput = tslib_1.__decorate([
|
|
52
|
+
(0, type_graphql_1.InputType)({ description: 'what a board needs in order to work as a station screen' })
|
|
53
|
+
], StationScreenSpecInput);
|
|
54
|
+
/**
|
|
55
|
+
* Reading and writing the manufacturing side of a board.
|
|
56
|
+
*
|
|
57
|
+
* The drawing stays in board-service; this is only what the drawing needs in order to stand in
|
|
58
|
+
* front of an operator. Written by the modeller when a screen is saved, read by the assignment
|
|
59
|
+
* screen and by whoever is deciding whether a release needs approval.
|
|
60
|
+
*/
|
|
61
|
+
let StationScreenSpecResolver = class StationScreenSpecResolver {
|
|
62
|
+
async stationScreenSpec(boardId, context) {
|
|
63
|
+
const { domain } = context.state;
|
|
64
|
+
return await (0, shell_1.getRepository)(station_screen_spec_1.StationScreenSpec).findOne({ where: { domain: { id: domain.id }, boardId } });
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Specs for several boards at once.
|
|
68
|
+
*
|
|
69
|
+
* The assignment screen needs one per row. Asked one at a time it is a round trip per assignment,
|
|
70
|
+
* and that list is the screen a plant manager keeps open.
|
|
71
|
+
*/
|
|
72
|
+
async stationScreenSpecs(boardIds, context) {
|
|
73
|
+
const { domain } = context.state;
|
|
74
|
+
if (!boardIds.length) {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
return await (0, shell_1.getRepository)(station_screen_spec_1.StationScreenSpec).find({ where: { domain: { id: domain.id }, boardId: (0, typeorm_1.In)(boardIds) } });
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Write the spec for a board — one row per board, replaced rather than appended.
|
|
81
|
+
*
|
|
82
|
+
* There is no version history here on purpose. The spec describes whatever the board currently
|
|
83
|
+
* is; the board keeps its own versions, and a spec that drifted from the drawing would answer a
|
|
84
|
+
* question nobody asked.
|
|
85
|
+
*/
|
|
86
|
+
async saveStationScreenSpec(patch, context) {
|
|
87
|
+
const { domain, user, tx } = context.state;
|
|
88
|
+
const repository = tx.getRepository(station_screen_spec_1.StationScreenSpec);
|
|
89
|
+
const existing = await repository.findOne({ where: { domain: { id: domain.id }, boardId: patch.boardId } });
|
|
90
|
+
/*
|
|
91
|
+
* `requires` 는 **흐름에서 나온다.** 저작자가 따로 적지 않는다 — 손으로 적으면 부품을 고칠 때
|
|
92
|
+
* 어긋나고, 그러면 배정 검증이 이미 사실이 아닌 것을 검사한다.
|
|
93
|
+
*
|
|
94
|
+
* 흐름을 안 보낸 저장(예: `controlled` 만 켜는 것)에서는 손대지 않는다. 그때 다시 계산하면
|
|
95
|
+
* 이미 있던 축이 빈 흐름 때문에 지워진다.
|
|
96
|
+
*/
|
|
97
|
+
const derived = patch.flow ? { requires: (0, station_flow_1.axesRead)(patch.flow) } : {};
|
|
98
|
+
if ((0, station_screen_release_gate_1.turnsControlOn)(existing?.controlled, patch.controlled)) {
|
|
99
|
+
await refuseControlWithoutApprovers(context);
|
|
100
|
+
}
|
|
101
|
+
if ((0, station_screen_control_withdrawal_1.turnsControlOff)(existing?.controlled, patch.controlled)) {
|
|
102
|
+
throw controlWithdrawalNeedsApproval();
|
|
103
|
+
}
|
|
104
|
+
return await repository.save({
|
|
105
|
+
domain,
|
|
106
|
+
...(existing ?? { creator: user }),
|
|
107
|
+
...patch,
|
|
108
|
+
...derived,
|
|
109
|
+
updater: user
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
async deleteStationScreenSpec(boardId, context) {
|
|
113
|
+
const { domain, tx } = context.state;
|
|
114
|
+
const repository = tx.getRepository(station_screen_spec_1.StationScreenSpec);
|
|
115
|
+
/*
|
|
116
|
+
* **통제 중인 설정은 여기서 안 지운다.** 지우면 발행 관문이 「통제 아님」으로 읽고 서명 없는 판을
|
|
117
|
+
* 내보낸다 — 통제를 끄는 것과 같은 일이다(ADR-0047 ⑨). 통제가 아닌 설정은 지금처럼 바로 지운다.
|
|
118
|
+
*/
|
|
119
|
+
const existing = await repository.findOne({ where: { domain: { id: domain.id }, boardId } });
|
|
120
|
+
if (existing?.controlled) {
|
|
121
|
+
throw controlWithdrawalNeedsApproval();
|
|
122
|
+
}
|
|
123
|
+
await repository.delete({ domain: { id: domain.id }, boardId });
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
exports.StationScreenSpecResolver = StationScreenSpecResolver;
|
|
128
|
+
tslib_1.__decorate([
|
|
129
|
+
(0, type_graphql_1.Directive)('@privilege(category: "station", privilege: "query", domainOwnerGranted: true)'),
|
|
130
|
+
(0, type_graphql_1.Query)(returns => station_screen_spec_1.StationScreenSpec, { nullable: true, description: 'the station spec for one board' }),
|
|
131
|
+
tslib_1.__param(0, (0, type_graphql_1.Arg)('boardId')),
|
|
132
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
133
|
+
tslib_1.__metadata("design:type", Function),
|
|
134
|
+
tslib_1.__metadata("design:paramtypes", [String, Object]),
|
|
135
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
136
|
+
], StationScreenSpecResolver.prototype, "stationScreenSpec", null);
|
|
137
|
+
tslib_1.__decorate([
|
|
138
|
+
(0, type_graphql_1.Directive)('@privilege(category: "station", privilege: "query", domainOwnerGranted: true)'),
|
|
139
|
+
(0, type_graphql_1.Query)(returns => [station_screen_spec_1.StationScreenSpec], { description: 'station specs for these boards' }),
|
|
140
|
+
tslib_1.__param(0, (0, type_graphql_1.Arg)('boardIds', type => [String])),
|
|
141
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
142
|
+
tslib_1.__metadata("design:type", Function),
|
|
143
|
+
tslib_1.__metadata("design:paramtypes", [Array, Object]),
|
|
144
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
145
|
+
], StationScreenSpecResolver.prototype, "stationScreenSpecs", null);
|
|
146
|
+
tslib_1.__decorate([
|
|
147
|
+
(0, type_graphql_1.Directive)('@transaction'),
|
|
148
|
+
(0, type_graphql_1.Directive)('@privilege(category: "station", privilege: "mutation", domainOwnerGranted: true)'),
|
|
149
|
+
(0, type_graphql_1.Mutation)(returns => station_screen_spec_1.StationScreenSpec, { description: 'record what a board needs as a station screen' }),
|
|
150
|
+
tslib_1.__param(0, (0, type_graphql_1.Arg)('patch', type => StationScreenSpecInput)),
|
|
151
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
152
|
+
tslib_1.__metadata("design:type", Function),
|
|
153
|
+
tslib_1.__metadata("design:paramtypes", [StationScreenSpecInput, Object]),
|
|
154
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
155
|
+
], StationScreenSpecResolver.prototype, "saveStationScreenSpec", null);
|
|
156
|
+
tslib_1.__decorate([
|
|
157
|
+
(0, type_graphql_1.Directive)('@transaction'),
|
|
158
|
+
(0, type_graphql_1.Directive)('@privilege(category: "station", privilege: "mutation", domainOwnerGranted: true)'),
|
|
159
|
+
(0, type_graphql_1.Mutation)(returns => Boolean, { description: 'forget the station spec for a board' }),
|
|
160
|
+
tslib_1.__param(0, (0, type_graphql_1.Arg)('boardId')),
|
|
161
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
162
|
+
tslib_1.__metadata("design:type", Function),
|
|
163
|
+
tslib_1.__metadata("design:paramtypes", [String, Object]),
|
|
164
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
165
|
+
], StationScreenSpecResolver.prototype, "deleteStationScreenSpec", null);
|
|
166
|
+
exports.StationScreenSpecResolver = StationScreenSpecResolver = tslib_1.__decorate([
|
|
167
|
+
(0, type_graphql_1.Resolver)(station_screen_spec_1.StationScreenSpec),
|
|
168
|
+
(0, type_graphql_1.UseMiddleware)(shell_1.SpeakRefusals)
|
|
169
|
+
], StationScreenSpecResolver);
|
|
170
|
+
/**
|
|
171
|
+
* 통제 문서로 켜려면 **결재할 사람이 실제로 있어야 한다** (ADR-0047 ②).
|
|
172
|
+
*
|
|
173
|
+
* ── 왜 못 켜게 하는 쪽인가 ─────────────────────────────────────────────────
|
|
174
|
+
* 「켜 두되 발행할 때 막는다」도 규칙이 될 수 있었다. 그러면 결재선이 없는 공장에서 켠 화면은 영원히
|
|
175
|
+
* 발행이 안 되고, 현장이 낼 수 있는 답은 하나뿐이다 — 통제를 끈다. **통제를 위해 넣은 칸이 통제를
|
|
176
|
+
* 끄는 손잡이가 된다.** 그래서 못 지킬 약속은 애초에 못 켜게 한다.
|
|
177
|
+
*
|
|
178
|
+
* ── 왜 서버인가 ────────────────────────────────────────────────────────────
|
|
179
|
+
* 저작면도 이 판정을 보여 준다. 그러나 **브라우저가 건너뛸 수 있는 검사는 닫힌 것이 아니다** —
|
|
180
|
+
* `saveStationScreenSpec` 은 공개된 mutation 이라 화면을 거치지 않고 부를 수 있다.
|
|
181
|
+
*
|
|
182
|
+
* ── 왜 거절이 셋인가 ───────────────────────────────────────────────────────
|
|
183
|
+
* 관리자가 할 일이 셋 다 다르다. 결재선을 세우는 것 · 역할에 사람을 넣는 것 · 지목된 사람을 이 공장에
|
|
184
|
+
* 들이는 것. 하나로 묶으면 무엇을 해야 하는지 말하지 않고 막기만 한다. 그리고 이 말은 화면을 만드는
|
|
185
|
+
* 저작자가 아니라 **관리자**에게 가야 하는 말이라, 문장이 그렇게 되어 있다.
|
|
186
|
+
*/
|
|
187
|
+
async function refuseControlWithoutApprovers(context) {
|
|
188
|
+
/* 끌 때(`requestStationScreenControlWithdrawal`)와 **같은 함수로** 판정한다. */
|
|
189
|
+
const { standing } = await (0, station_screen_control_withdrawal_resolver_1.controlLineStanding)(context);
|
|
190
|
+
/*
|
|
191
|
+
* 판정은 **한 객체에 선택 칸**이다 — `refusal` 이 있으면 그 결재선은 안 선다(ADR-0047 ⑤ 개정).
|
|
192
|
+
* 앞서는 `{ok:true}|{ok:false,…}` union 이었는데 이 패키지의 서버가 `strict: false` 로 컴파일해
|
|
193
|
+
* **boolean 판별자로 안 좁혀졌고**, 이 자리에서 오늘 서버 빌드가 한 번 섰다. 그때 넓은 타입으로
|
|
194
|
+
* 받아 두었던 것을 지금 지운다.
|
|
195
|
+
*/
|
|
196
|
+
if (!standing.refusal) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
/* 켤 수 없는 까닭은 결재선이 안 서는 까닭 그대로다 — 관리자가 할 일이 셋 다르다. */
|
|
200
|
+
throw (0, worklist_1.approvalLineRefusal)(standing.refusal);
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=station-screen-spec-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"station-screen-spec-resolver.js","sourceRoot":"","sources":["../../../server/service/station/station-screen-spec-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Directive, Field, InputType, Mutation, Query, Resolver, UseMiddleware } from 'type-graphql'\n\nimport { In } from 'typeorm'\n\nimport { ScalarObject, getRepository, Refusal, SpeakRefusals } from '@things-factory/shell'\n\nimport { axesRead } from './station-flow'\nimport type { StationFlow } from './station-flow'\nimport { StationScreenSpec } from './station-screen-spec'\nimport { turnsControlOn } from './station-screen-release-gate'\nimport { turnsControlOff } from './station-screen-control-withdrawal'\nimport { controlLineStanding } from './station-screen-control-withdrawal-resolver'\nimport { approvalLineRefusal } from '@things-factory/worklist'\n\n/**\n * 통제를 **이 저장으로는** 거둘 수 없다 — 결재로 세운 것은 결재로 거둔다(ADR-0047 ⑨).\n *\n * 저장과 삭제는 작성자의 권한(`station:mutation`)으로 열린다. 그 문으로 통제를 끄거나 통제 중인 설정을\n * 지우면, 발행 관문은 통제가 아닌 화면을 그대로 내보낸다 — 작성자가 서명 없는 판을 내보내는 길이다.\n * 그래서 이 둘은 막고 「통제 해제 승인」으로 보낸다.\n */\nfunction controlWithdrawalNeedsApproval(): Refusal {\n return new Refusal(\n 'control-withdrawal-needs-approval',\n {},\n 'document control can only be removed through a control withdrawal approval.'\n )\n}\n\n@InputType({ description: 'what a board needs in order to work as a station screen' })\nexport class StationScreenSpecInput {\n @Field({ description: \"the board's id in board-service\" })\n boardId: string\n\n @Field(type => [String], { nullable: true, description: 'station context axes the drawing reads' })\n requires?: string[]\n\n @Field(type => ScalarObject, { nullable: true })\n target?: Record<string, unknown>\n\n @Field({ nullable: true })\n controlled?: boolean\n\n @Field({ nullable: true, description: \"'work' or 'signal'\" })\n kind?: 'work' | 'signal'\n\n @Field(type => ScalarObject, { nullable: true, description: 'the steps, and where each argument comes from' })\n flow?: StationFlow\n}\n\n/**\n * Reading and writing the manufacturing side of a board.\n *\n * The drawing stays in board-service; this is only what the drawing needs in order to stand in\n * front of an operator. Written by the modeller when a screen is saved, read by the assignment\n * screen and by whoever is deciding whether a release needs approval.\n */\n@Resolver(StationScreenSpec)\n@UseMiddleware(SpeakRefusals)\nexport class StationScreenSpecResolver {\n @Directive('@privilege(category: \"station\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => StationScreenSpec, { nullable: true, description: 'the station spec for one board' })\n async stationScreenSpec(@Arg('boardId') boardId: string, @Ctx() context: any): Promise<StationScreenSpec | null> {\n const { domain } = context.state\n\n return await getRepository(StationScreenSpec).findOne({ where: { domain: { id: domain.id }, boardId } })\n }\n\n /**\n * Specs for several boards at once.\n *\n * The assignment screen needs one per row. Asked one at a time it is a round trip per assignment,\n * and that list is the screen a plant manager keeps open.\n */\n @Directive('@privilege(category: \"station\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => [StationScreenSpec], { description: 'station specs for these boards' })\n async stationScreenSpecs(@Arg('boardIds', type => [String]) boardIds: string[], @Ctx() context: any): Promise<StationScreenSpec[]> {\n const { domain } = context.state\n\n if (!boardIds.length) {\n return []\n }\n\n return await getRepository(StationScreenSpec).find({ where: { domain: { id: domain.id }, boardId: In(boardIds) } })\n }\n\n /**\n * Write the spec for a board — one row per board, replaced rather than appended.\n *\n * There is no version history here on purpose. The spec describes whatever the board currently\n * is; the board keeps its own versions, and a spec that drifted from the drawing would answer a\n * question nobody asked.\n */\n @Directive('@transaction')\n @Directive('@privilege(category: \"station\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => StationScreenSpec, { description: 'record what a board needs as a station screen' })\n async saveStationScreenSpec(@Arg('patch', type => StationScreenSpecInput) patch: StationScreenSpecInput, @Ctx() context: any): Promise<StationScreenSpec> {\n const { domain, user, tx } = context.state\n const repository = tx.getRepository(StationScreenSpec)\n\n const existing = await repository.findOne({ where: { domain: { id: domain.id }, boardId: patch.boardId } })\n\n /*\n * `requires` 는 **흐름에서 나온다.** 저작자가 따로 적지 않는다 — 손으로 적으면 부품을 고칠 때\n * 어긋나고, 그러면 배정 검증이 이미 사실이 아닌 것을 검사한다.\n *\n * 흐름을 안 보낸 저장(예: `controlled` 만 켜는 것)에서는 손대지 않는다. 그때 다시 계산하면\n * 이미 있던 축이 빈 흐름 때문에 지워진다.\n */\n const derived = patch.flow ? { requires: axesRead(patch.flow) } : {}\n\n if (turnsControlOn(existing?.controlled, patch.controlled)) {\n await refuseControlWithoutApprovers(context)\n }\n\n if (turnsControlOff(existing?.controlled, patch.controlled)) {\n throw controlWithdrawalNeedsApproval()\n }\n\n return await repository.save({\n domain,\n ...(existing ?? { creator: user }),\n ...patch,\n ...derived,\n updater: user\n })\n }\n\n @Directive('@transaction')\n @Directive('@privilege(category: \"station\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => Boolean, { description: 'forget the station spec for a board' })\n async deleteStationScreenSpec(@Arg('boardId') boardId: string, @Ctx() context: any): Promise<boolean> {\n const { domain, tx } = context.state\n const repository = tx.getRepository(StationScreenSpec)\n\n /*\n * **통제 중인 설정은 여기서 안 지운다.** 지우면 발행 관문이 「통제 아님」으로 읽고 서명 없는 판을\n * 내보낸다 — 통제를 끄는 것과 같은 일이다(ADR-0047 ⑨). 통제가 아닌 설정은 지금처럼 바로 지운다.\n */\n const existing = await repository.findOne({ where: { domain: { id: domain.id }, boardId } })\n\n if (existing?.controlled) {\n throw controlWithdrawalNeedsApproval()\n }\n\n await repository.delete({ domain: { id: domain.id }, boardId })\n\n return true\n }\n}\n\n/**\n * 통제 문서로 켜려면 **결재할 사람이 실제로 있어야 한다** (ADR-0047 ②).\n *\n * ── 왜 못 켜게 하는 쪽인가 ─────────────────────────────────────────────────\n * 「켜 두되 발행할 때 막는다」도 규칙이 될 수 있었다. 그러면 결재선이 없는 공장에서 켠 화면은 영원히\n * 발행이 안 되고, 현장이 낼 수 있는 답은 하나뿐이다 — 통제를 끈다. **통제를 위해 넣은 칸이 통제를\n * 끄는 손잡이가 된다.** 그래서 못 지킬 약속은 애초에 못 켜게 한다.\n *\n * ── 왜 서버인가 ────────────────────────────────────────────────────────────\n * 저작면도 이 판정을 보여 준다. 그러나 **브라우저가 건너뛸 수 있는 검사는 닫힌 것이 아니다** —\n * `saveStationScreenSpec` 은 공개된 mutation 이라 화면을 거치지 않고 부를 수 있다.\n *\n * ── 왜 거절이 셋인가 ───────────────────────────────────────────────────────\n * 관리자가 할 일이 셋 다 다르다. 결재선을 세우는 것 · 역할에 사람을 넣는 것 · 지목된 사람을 이 공장에\n * 들이는 것. 하나로 묶으면 무엇을 해야 하는지 말하지 않고 막기만 한다. 그리고 이 말은 화면을 만드는\n * 저작자가 아니라 **관리자**에게 가야 하는 말이라, 문장이 그렇게 되어 있다.\n */\nasync function refuseControlWithoutApprovers(context: any): Promise<void> {\n /* 끌 때(`requestStationScreenControlWithdrawal`)와 **같은 함수로** 판정한다. */\n const { standing } = await controlLineStanding(context)\n\n /*\n * 판정은 **한 객체에 선택 칸**이다 — `refusal` 이 있으면 그 결재선은 안 선다(ADR-0047 ⑤ 개정).\n * 앞서는 `{ok:true}|{ok:false,…}` union 이었는데 이 패키지의 서버가 `strict: false` 로 컴파일해\n * **boolean 판별자로 안 좁혀졌고**, 이 자리에서 오늘 서버 빌드가 한 번 섰다. 그때 넓은 타입으로\n * 받아 두었던 것을 지금 지운다.\n */\n\n if (!standing.refusal) {\n return\n }\n\n /* 켤 수 없는 까닭은 결재선이 안 서는 까닭 그대로다 — 관리자가 할 일이 셋 다르다. */\n throw approvalLineRefusal(standing.refusal)\n}\n"]}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Domain } from '@things-factory/shell';
|
|
2
|
+
import type { StationFlow } from './station-flow';
|
|
3
|
+
import { User } from '@things-factory/auth-base';
|
|
4
|
+
/**
|
|
5
|
+
* What a board needs in order to work as a station screen.
|
|
6
|
+
*
|
|
7
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
8
|
+
* ## Why this is beside a board and not instead of one
|
|
9
|
+
*
|
|
10
|
+
* The drawing, its versions, releasing and reverting are `Board`'s job and it already does all of
|
|
11
|
+
* them. This table was very nearly a copy of it under different words — same columns, same verbs —
|
|
12
|
+
* which would have meant tracking board's fixes forever for no difference.
|
|
13
|
+
*
|
|
14
|
+
* What is genuinely ours is not the drawing. It is what the drawing **needs** to be usable in front
|
|
15
|
+
* of an operator, and that is manufacturing vocabulary that has no business in shared
|
|
16
|
+
* infrastructure. So it sits here, keyed by the board.
|
|
17
|
+
*
|
|
18
|
+
* The monorepo already works this way in both directions: `board-ai` anchors its tables with a
|
|
19
|
+
* plain `boardId`, and `Board.sourceImportSessionId` is a plain string for the same stated reason —
|
|
20
|
+
* "모듈 의존을 강제하지 않기 위해 ManyToOne 대신 단순 string id 만 보관".
|
|
21
|
+
*
|
|
22
|
+
* ## Why not on the assignment
|
|
23
|
+
*
|
|
24
|
+
* An assignment is a pairing: this situation shows that drawing. Everything here is a property of
|
|
25
|
+
* the **drawing**, so putting it on the assignment would copy it into every assignment pointing at
|
|
26
|
+
* the same board, let those copies disagree, and ask the person doing the assigning to know things
|
|
27
|
+
* only the person who drew it knows.
|
|
28
|
+
*/
|
|
29
|
+
export declare class StationScreenSpec {
|
|
30
|
+
readonly id: string;
|
|
31
|
+
domain?: Domain;
|
|
32
|
+
domainId?: string;
|
|
33
|
+
/** The board this describes. One spec per board — a plain id, not a relation. */
|
|
34
|
+
boardId: string;
|
|
35
|
+
/**
|
|
36
|
+
* Which station context axes the drawing reads — `equipment`, `operation`, `workDirective`, …
|
|
37
|
+
*
|
|
38
|
+
* Filled by the modeller from the parts actually placed on the board, so it is a fact about the
|
|
39
|
+
* drawing rather than something someone remembered to type.
|
|
40
|
+
*
|
|
41
|
+
* ## What it is for
|
|
42
|
+
*
|
|
43
|
+
* A screen that reads `operation` shows nothing useful at a station whose assignment does not
|
|
44
|
+
* constrain an operation. Today that surfaces as a blank tile on a bench, discovered by whoever
|
|
45
|
+
* is standing there. With this declared, the assignment screen can say so before saving —
|
|
46
|
+
* "this screen reads the operation, and this assignment does not pin one".
|
|
47
|
+
*
|
|
48
|
+
* This is what makes composing screens with no code safe rather than merely possible.
|
|
49
|
+
*/
|
|
50
|
+
requires?: string[];
|
|
51
|
+
/**
|
|
52
|
+
* What it was drawn for — a bench tablet, a wall andon, a rugged panel.
|
|
53
|
+
*
|
|
54
|
+
* Left open on purpose. A shop floor's device mix is not something we can enumerate up front, and
|
|
55
|
+
* narrowing it here would push whatever we left out into somebody's screen name.
|
|
56
|
+
*/
|
|
57
|
+
target?: Record<string, unknown>;
|
|
58
|
+
/**
|
|
59
|
+
* Whether this screen is a controlled document.
|
|
60
|
+
*
|
|
61
|
+
* When it is, releasing it is not one person clicking: a change to what an operator reads off the
|
|
62
|
+
* screen — a torque figure, a tolerance — has to be reviewed and approved, and the approval has
|
|
63
|
+
* to be findable afterwards. This package already has that machinery
|
|
64
|
+
* (`report/disposition-approval-*`); this flag is what sends a release through it.
|
|
65
|
+
*/
|
|
66
|
+
controlled?: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* What this screen is for — **declared, not derived.**
|
|
69
|
+
*
|
|
70
|
+
* The rule was going to be "no write step means it is a signal board". An andon's acknowledge is
|
|
71
|
+
* a write step, so the day somebody can answer a call from the screen, its kind would change with
|
|
72
|
+
* no error anywhere. A value that decides wiring cannot be a value that flips when somebody edits
|
|
73
|
+
* an unrelated field [아키텍트 판정 2026-09-10].
|
|
74
|
+
*
|
|
75
|
+
* Named for what the screen does, not for how it looks. "Andon" is a presentation and
|
|
76
|
+
* presentations change; `signal` still describes it when the presentation does not.
|
|
77
|
+
*
|
|
78
|
+
* A closed set. Growing it means a new kind of screen exists, which is an architect's call.
|
|
79
|
+
*/
|
|
80
|
+
kind?: 'work' | 'signal';
|
|
81
|
+
/**
|
|
82
|
+
* What the operator does here, and what each step calls.
|
|
83
|
+
*
|
|
84
|
+
* ## Why it lives beside the board rather than in it
|
|
85
|
+
*
|
|
86
|
+
* The drawing is board-service's. **Which services get called is ours** — putting it in
|
|
87
|
+
* `Board.model` would have board carry vocabulary it cannot read, and a board opened in the board
|
|
88
|
+
* modeller would show data nobody there can explain.
|
|
89
|
+
*
|
|
90
|
+
* Keeping it here also means the flow can be read without the drawing, which is what lets a
|
|
91
|
+
* generator build the flow first and compute the drawing after.
|
|
92
|
+
*
|
|
93
|
+
* `requires` above is derived from this — see `axesRead()`. An author who has to keep a list in
|
|
94
|
+
* step with a screen will not, and then the assignment check is checking something that used to
|
|
95
|
+
* be true.
|
|
96
|
+
*/
|
|
97
|
+
flow?: StationFlow;
|
|
98
|
+
createdAt?: Date;
|
|
99
|
+
updatedAt?: Date;
|
|
100
|
+
creator?: User;
|
|
101
|
+
creatorId?: string;
|
|
102
|
+
updater?: User;
|
|
103
|
+
updaterId?: string;
|
|
104
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StationScreenSpec = 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
|
+
const auth_base_1 = require("@things-factory/auth-base");
|
|
9
|
+
/**
|
|
10
|
+
* What a board needs in order to work as a station screen.
|
|
11
|
+
*
|
|
12
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
13
|
+
* ## Why this is beside a board and not instead of one
|
|
14
|
+
*
|
|
15
|
+
* The drawing, its versions, releasing and reverting are `Board`'s job and it already does all of
|
|
16
|
+
* them. This table was very nearly a copy of it under different words — same columns, same verbs —
|
|
17
|
+
* which would have meant tracking board's fixes forever for no difference.
|
|
18
|
+
*
|
|
19
|
+
* What is genuinely ours is not the drawing. It is what the drawing **needs** to be usable in front
|
|
20
|
+
* of an operator, and that is manufacturing vocabulary that has no business in shared
|
|
21
|
+
* infrastructure. So it sits here, keyed by the board.
|
|
22
|
+
*
|
|
23
|
+
* The monorepo already works this way in both directions: `board-ai` anchors its tables with a
|
|
24
|
+
* plain `boardId`, and `Board.sourceImportSessionId` is a plain string for the same stated reason —
|
|
25
|
+
* "모듈 의존을 강제하지 않기 위해 ManyToOne 대신 단순 string id 만 보관".
|
|
26
|
+
*
|
|
27
|
+
* ## Why not on the assignment
|
|
28
|
+
*
|
|
29
|
+
* An assignment is a pairing: this situation shows that drawing. Everything here is a property of
|
|
30
|
+
* the **drawing**, so putting it on the assignment would copy it into every assignment pointing at
|
|
31
|
+
* the same board, let those copies disagree, and ask the person doing the assigning to know things
|
|
32
|
+
* only the person who drew it knows.
|
|
33
|
+
*/
|
|
34
|
+
let StationScreenSpec = class StationScreenSpec {
|
|
35
|
+
};
|
|
36
|
+
exports.StationScreenSpec = StationScreenSpec;
|
|
37
|
+
tslib_1.__decorate([
|
|
38
|
+
(0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
|
|
39
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.ID),
|
|
40
|
+
tslib_1.__metadata("design:type", String)
|
|
41
|
+
], StationScreenSpec.prototype, "id", void 0);
|
|
42
|
+
tslib_1.__decorate([
|
|
43
|
+
(0, typeorm_1.ManyToOne)(type => shell_1.Domain),
|
|
44
|
+
(0, type_graphql_1.Field)(type => shell_1.Domain, { nullable: true }),
|
|
45
|
+
tslib_1.__metadata("design:type", shell_1.Domain)
|
|
46
|
+
], StationScreenSpec.prototype, "domain", void 0);
|
|
47
|
+
tslib_1.__decorate([
|
|
48
|
+
(0, typeorm_1.RelationId)((spec) => spec.domain),
|
|
49
|
+
tslib_1.__metadata("design:type", String)
|
|
50
|
+
], StationScreenSpec.prototype, "domainId", void 0);
|
|
51
|
+
tslib_1.__decorate([
|
|
52
|
+
(0, typeorm_1.Column)(),
|
|
53
|
+
(0, type_graphql_1.Field)({ description: "the board's id in board-service" }),
|
|
54
|
+
tslib_1.__metadata("design:type", String)
|
|
55
|
+
], StationScreenSpec.prototype, "boardId", void 0);
|
|
56
|
+
tslib_1.__decorate([
|
|
57
|
+
(0, typeorm_1.Column)({ type: 'simple-array', nullable: true }),
|
|
58
|
+
(0, type_graphql_1.Field)(type => [String], { nullable: true, description: 'station context axes the drawing reads' }),
|
|
59
|
+
tslib_1.__metadata("design:type", Array)
|
|
60
|
+
], StationScreenSpec.prototype, "requires", void 0);
|
|
61
|
+
tslib_1.__decorate([
|
|
62
|
+
(0, typeorm_1.Column)({ type: 'simple-json', nullable: true }),
|
|
63
|
+
(0, type_graphql_1.Field)(type => shell_1.ScalarObject, { nullable: true, description: 'what this screen was drawn for — size, input, viewing distance' }),
|
|
64
|
+
tslib_1.__metadata("design:type", Object)
|
|
65
|
+
], StationScreenSpec.prototype, "target", void 0);
|
|
66
|
+
tslib_1.__decorate([
|
|
67
|
+
(0, typeorm_1.Column)({ nullable: true, default: false }),
|
|
68
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'releasing this screen goes through approval' }),
|
|
69
|
+
tslib_1.__metadata("design:type", Boolean)
|
|
70
|
+
], StationScreenSpec.prototype, "controlled", void 0);
|
|
71
|
+
tslib_1.__decorate([
|
|
72
|
+
(0, typeorm_1.Column)({ nullable: true, default: 'work' }),
|
|
73
|
+
(0, type_graphql_1.Field)({ nullable: true, description: "'work' — the operator works here · 'signal' — the operator watches it" }),
|
|
74
|
+
tslib_1.__metadata("design:type", String)
|
|
75
|
+
], StationScreenSpec.prototype, "kind", void 0);
|
|
76
|
+
tslib_1.__decorate([
|
|
77
|
+
(0, typeorm_1.Column)({ type: 'simple-json', nullable: true }),
|
|
78
|
+
(0, type_graphql_1.Field)(type => shell_1.ScalarObject, { nullable: true, description: 'the steps, and where each argument comes from' }),
|
|
79
|
+
tslib_1.__metadata("design:type", Object)
|
|
80
|
+
], StationScreenSpec.prototype, "flow", void 0);
|
|
81
|
+
tslib_1.__decorate([
|
|
82
|
+
(0, typeorm_1.CreateDateColumn)(),
|
|
83
|
+
(0, type_graphql_1.Field)({ nullable: true }),
|
|
84
|
+
tslib_1.__metadata("design:type", Date)
|
|
85
|
+
], StationScreenSpec.prototype, "createdAt", void 0);
|
|
86
|
+
tslib_1.__decorate([
|
|
87
|
+
(0, typeorm_1.UpdateDateColumn)(),
|
|
88
|
+
(0, type_graphql_1.Field)({ nullable: true }),
|
|
89
|
+
tslib_1.__metadata("design:type", Date)
|
|
90
|
+
], StationScreenSpec.prototype, "updatedAt", void 0);
|
|
91
|
+
tslib_1.__decorate([
|
|
92
|
+
(0, typeorm_1.ManyToOne)(type => auth_base_1.User, { nullable: true }),
|
|
93
|
+
(0, type_graphql_1.Field)(type => auth_base_1.User, { nullable: true }),
|
|
94
|
+
tslib_1.__metadata("design:type", auth_base_1.User)
|
|
95
|
+
], StationScreenSpec.prototype, "creator", void 0);
|
|
96
|
+
tslib_1.__decorate([
|
|
97
|
+
(0, typeorm_1.RelationId)((spec) => spec.creator),
|
|
98
|
+
tslib_1.__metadata("design:type", String)
|
|
99
|
+
], StationScreenSpec.prototype, "creatorId", void 0);
|
|
100
|
+
tslib_1.__decorate([
|
|
101
|
+
(0, typeorm_1.ManyToOne)(type => auth_base_1.User, { nullable: true }),
|
|
102
|
+
(0, type_graphql_1.Field)(type => auth_base_1.User, { nullable: true }),
|
|
103
|
+
tslib_1.__metadata("design:type", auth_base_1.User)
|
|
104
|
+
], StationScreenSpec.prototype, "updater", void 0);
|
|
105
|
+
tslib_1.__decorate([
|
|
106
|
+
(0, typeorm_1.RelationId)((spec) => spec.updater),
|
|
107
|
+
tslib_1.__metadata("design:type", String)
|
|
108
|
+
], StationScreenSpec.prototype, "updaterId", void 0);
|
|
109
|
+
exports.StationScreenSpec = StationScreenSpec = tslib_1.__decorate([
|
|
110
|
+
(0, typeorm_1.Entity)(),
|
|
111
|
+
(0, typeorm_1.Index)('ix_mes_station_screen_spec_0', (spec) => [spec.domain, spec.boardId], { unique: true }),
|
|
112
|
+
(0, type_graphql_1.ObjectType)({ description: 'what a board needs in order to work as a station screen' })
|
|
113
|
+
], StationScreenSpec);
|
|
114
|
+
//# sourceMappingURL=station-screen-spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"station-screen-spec.js","sourceRoot":"","sources":["../../../server/service/station/station-screen-spec.ts"],"names":[],"mappings":"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","sourcesContent":["import { Column, CreateDateColumn, Entity, Index, ManyToOne, PrimaryGeneratedColumn, RelationId, UpdateDateColumn } from 'typeorm'\nimport { Field, ID, ObjectType } from 'type-graphql'\n\nimport { Domain, ScalarObject } from '@things-factory/shell'\n\nimport type { StationFlow } from './station-flow'\nimport { User } from '@things-factory/auth-base'\n\n/**\n * What a board needs in order to work as a station screen.\n *\n * ═══════════════════════════════════════════════════════════════════════════\n * ## Why this is beside a board and not instead of one\n *\n * The drawing, its versions, releasing and reverting are `Board`'s job and it already does all of\n * them. This table was very nearly a copy of it under different words — same columns, same verbs —\n * which would have meant tracking board's fixes forever for no difference.\n *\n * What is genuinely ours is not the drawing. It is what the drawing **needs** to be usable in front\n * of an operator, and that is manufacturing vocabulary that has no business in shared\n * infrastructure. So it sits here, keyed by the board.\n *\n * The monorepo already works this way in both directions: `board-ai` anchors its tables with a\n * plain `boardId`, and `Board.sourceImportSessionId` is a plain string for the same stated reason —\n * \"모듈 의존을 강제하지 않기 위해 ManyToOne 대신 단순 string id 만 보관\".\n *\n * ## Why not on the assignment\n *\n * An assignment is a pairing: this situation shows that drawing. Everything here is a property of\n * the **drawing**, so putting it on the assignment would copy it into every assignment pointing at\n * the same board, let those copies disagree, and ask the person doing the assigning to know things\n * only the person who drew it knows.\n */\n@Entity()\n@Index('ix_mes_station_screen_spec_0', (spec: StationScreenSpec) => [spec.domain, spec.boardId], { unique: true })\n@ObjectType({ description: 'what a board needs in order to work as a station screen' })\nexport class StationScreenSpec {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID)\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true })\n domain?: Domain\n\n @RelationId((spec: StationScreenSpec) => spec.domain)\n domainId?: string\n\n /** The board this describes. One spec per board — a plain id, not a relation. */\n @Column()\n @Field({ description: \"the board's id in board-service\" })\n boardId: string\n\n /**\n * Which station context axes the drawing reads — `equipment`, `operation`, `workDirective`, …\n *\n * Filled by the modeller from the parts actually placed on the board, so it is a fact about the\n * drawing rather than something someone remembered to type.\n *\n * ## What it is for\n *\n * A screen that reads `operation` shows nothing useful at a station whose assignment does not\n * constrain an operation. Today that surfaces as a blank tile on a bench, discovered by whoever\n * is standing there. With this declared, the assignment screen can say so before saving —\n * \"this screen reads the operation, and this assignment does not pin one\".\n *\n * This is what makes composing screens with no code safe rather than merely possible.\n */\n @Column({ type: 'simple-array', nullable: true })\n @Field(type => [String], { nullable: true, description: 'station context axes the drawing reads' })\n requires?: string[]\n\n /**\n * What it was drawn for — a bench tablet, a wall andon, a rugged panel.\n *\n * Left open on purpose. A shop floor's device mix is not something we can enumerate up front, and\n * narrowing it here would push whatever we left out into somebody's screen name.\n */\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, { nullable: true, description: 'what this screen was drawn for — size, input, viewing distance' })\n target?: Record<string, unknown>\n\n /**\n * Whether this screen is a controlled document.\n *\n * When it is, releasing it is not one person clicking: a change to what an operator reads off the\n * screen — a torque figure, a tolerance — has to be reviewed and approved, and the approval has\n * to be findable afterwards. This package already has that machinery\n * (`report/disposition-approval-*`); this flag is what sends a release through it.\n */\n @Column({ nullable: true, default: false })\n @Field({ nullable: true, description: 'releasing this screen goes through approval' })\n controlled?: boolean\n\n /**\n * What this screen is for — **declared, not derived.**\n *\n * The rule was going to be \"no write step means it is a signal board\". An andon's acknowledge is\n * a write step, so the day somebody can answer a call from the screen, its kind would change with\n * no error anywhere. A value that decides wiring cannot be a value that flips when somebody edits\n * an unrelated field [아키텍트 판정 2026-09-10].\n *\n * Named for what the screen does, not for how it looks. \"Andon\" is a presentation and\n * presentations change; `signal` still describes it when the presentation does not.\n *\n * A closed set. Growing it means a new kind of screen exists, which is an architect's call.\n */\n @Column({ nullable: true, default: 'work' })\n @Field({ nullable: true, description: \"'work' — the operator works here · 'signal' — the operator watches it\" })\n kind?: 'work' | 'signal'\n\n /**\n * What the operator does here, and what each step calls.\n *\n * ## Why it lives beside the board rather than in it\n *\n * The drawing is board-service's. **Which services get called is ours** — putting it in\n * `Board.model` would have board carry vocabulary it cannot read, and a board opened in the board\n * modeller would show data nobody there can explain.\n *\n * Keeping it here also means the flow can be read without the drawing, which is what lets a\n * generator build the flow first and compute the drawing after.\n *\n * `requires` above is derived from this — see `axesRead()`. An author who has to keep a list in\n * step with a screen will not, and then the assignment check is checking something that used to\n * be true.\n */\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, { nullable: true, description: 'the steps, and where each argument comes from' })\n flow?: StationFlow\n\n @CreateDateColumn()\n @Field({ nullable: true })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true })\n updatedAt?: Date\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n creator?: User\n\n @RelationId((spec: StationScreenSpec) => spec.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n updater?: User\n\n @RelationId((spec: StationScreenSpec) => spec.updater)\n updaterId?: string\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** `data:image/png;base64,...` → image response metadata. */
|
|
2
|
+
export declare function decodeStationThumbnail(dataUrl: string): {
|
|
3
|
+
type: string;
|
|
4
|
+
buffer: Buffer;
|
|
5
|
+
} | undefined;
|
|
6
|
+
/**
|
|
7
|
+
* Old seed records predate Board's background image scheduler. A blank/broken card makes a real
|
|
8
|
+
* Station Screen indistinguishable from a failed request, so render a safe structural preview from
|
|
9
|
+
* the persisted model until a raster thumbnail exists. It deliberately exposes labels/types only.
|
|
10
|
+
*/
|
|
11
|
+
export declare function renderStationThumbnail(name: string, model: string | undefined): Buffer | undefined;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.decodeStationThumbnail = decodeStationThumbnail;
|
|
4
|
+
exports.renderStationThumbnail = renderStationThumbnail;
|
|
5
|
+
/** `data:image/png;base64,...` → image response metadata. */
|
|
6
|
+
function decodeStationThumbnail(dataUrl) {
|
|
7
|
+
const matched = /^data:([^;,]+);base64,(.*)$/s.exec(dataUrl);
|
|
8
|
+
return matched ? { type: matched[1], buffer: Buffer.from(matched[2], 'base64') } : undefined;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Old seed records predate Board's background image scheduler. A blank/broken card makes a real
|
|
12
|
+
* Station Screen indistinguishable from a failed request, so render a safe structural preview from
|
|
13
|
+
* the persisted model until a raster thumbnail exists. It deliberately exposes labels/types only.
|
|
14
|
+
*/
|
|
15
|
+
function renderStationThumbnail(name, model) {
|
|
16
|
+
try {
|
|
17
|
+
const parsed = JSON.parse(model ?? '');
|
|
18
|
+
const components = Array.isArray(parsed?.components) ? parsed.components : [];
|
|
19
|
+
const labels = components
|
|
20
|
+
.filter((component) => component?.top < 720)
|
|
21
|
+
.map((component) => String(component?.label || component?.field || component?.type || ''))
|
|
22
|
+
.filter(Boolean)
|
|
23
|
+
.slice(0, 6);
|
|
24
|
+
const escape = (value) => value.replace(/[&<>"']/g, character => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[character]);
|
|
25
|
+
const rows = labels.length ? labels : ['화면 구성 없음'];
|
|
26
|
+
const cards = rows.map((label, index) => {
|
|
27
|
+
const y = 84 + index * 42;
|
|
28
|
+
return `<rect x="30" y="${y}" width="580" height="30" rx="5" fill="#f2f5f7"/><text x="46" y="${y + 20}" fill="#334155" font-size="15">${escape(label)}</text>`;
|
|
29
|
+
});
|
|
30
|
+
return Buffer.from(`<svg xmlns="http://www.w3.org/2000/svg" width="640" height="360" viewBox="0 0 640 360"><rect width="640" height="360" fill="#ffffff"/><rect width="640" height="64" fill="#164e63"/><text x="30" y="40" fill="#ffffff" font-family="Arial, sans-serif" font-size="20" font-weight="700">${escape(name)}</text>${cards.join('')}</svg>`);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=station-screen-thumbnail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"station-screen-thumbnail.js","sourceRoot":"","sources":["../../../server/service/station/station-screen-thumbnail.ts"],"names":[],"mappings":";;AACA,wDAIC;AAOD,wDAuBC;AAnCD,6DAA6D;AAC7D,SAAgB,sBAAsB,CAAC,OAAe;IACpD,MAAM,OAAO,GAAG,8BAA8B,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAE5D,OAAO,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;AAC9F,CAAC;AAED;;;;GAIG;AACH,SAAgB,sBAAsB,CAAC,IAAY,EAAE,KAAyB;IAC5E,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,CAAA;QACtC,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAA;QAC7E,MAAM,MAAM,GAAG,UAAU;aACtB,MAAM,CAAC,CAAC,SAAc,EAAE,EAAE,CAAC,SAAS,EAAE,GAAG,GAAG,GAAG,CAAC;aAChD,GAAG,CAAC,CAAC,SAAc,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,IAAI,SAAS,EAAE,KAAK,IAAI,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;aAC9F,MAAM,CAAC,OAAO,CAAC;aACf,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;QAEd,MAAM,MAAM,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC,SAAS,CAAE,CAAC,CAAA;QAC/J,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAA;QAClD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;YACtC,MAAM,CAAC,GAAG,EAAE,GAAG,KAAK,GAAG,EAAE,CAAA;YACzB,OAAO,mBAAmB,CAAC,oEAAoE,CAAC,GAAG,EAAE,mCAAmC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAA;QAChK,CAAC,CAAC,CAAA;QAEF,OAAO,MAAM,CAAC,IAAI,CAChB,2RAA2R,MAAM,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CACxU,CAAA;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC","sourcesContent":["/** `data:image/png;base64,...` → image response metadata. */\nexport function decodeStationThumbnail(dataUrl: string): { type: string; buffer: Buffer } | undefined {\n const matched = /^data:([^;,]+);base64,(.*)$/s.exec(dataUrl)\n\n return matched ? { type: matched[1], buffer: Buffer.from(matched[2], 'base64') } : undefined\n}\n\n/**\n * Old seed records predate Board's background image scheduler. A blank/broken card makes a real\n * Station Screen indistinguishable from a failed request, so render a safe structural preview from\n * the persisted model until a raster thumbnail exists. It deliberately exposes labels/types only.\n */\nexport function renderStationThumbnail(name: string, model: string | undefined): Buffer | undefined {\n try {\n const parsed = JSON.parse(model ?? '')\n const components = Array.isArray(parsed?.components) ? parsed.components : []\n const labels = components\n .filter((component: any) => component?.top < 720)\n .map((component: any) => String(component?.label || component?.field || component?.type || ''))\n .filter(Boolean)\n .slice(0, 6)\n\n const escape = (value: string) => value.replace(/[&<>\"']/g, character => ({ '&': '&', '<': '<', '>': '>', '\"': '"', \"'\": ''' })[character]!)\n const rows = labels.length ? labels : ['화면 구성 없음']\n const cards = rows.map((label, index) => {\n const y = 84 + index * 42\n return `<rect x=\"30\" y=\"${y}\" width=\"580\" height=\"30\" rx=\"5\" fill=\"#f2f5f7\"/><text x=\"46\" y=\"${y + 20}\" fill=\"#334155\" font-size=\"15\">${escape(label)}</text>`\n })\n\n return Buffer.from(\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"640\" height=\"360\" viewBox=\"0 0 640 360\"><rect width=\"640\" height=\"360\" fill=\"#ffffff\"/><rect width=\"640\" height=\"64\" fill=\"#164e63\"/><text x=\"30\" y=\"40\" fill=\"#ffffff\" font-family=\"Arial, sans-serif\" font-size=\"20\" font-weight=\"700\">${escape(name)}</text>${cards.join('')}</svg>`\n )\n } catch {\n return undefined\n }\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare class StationServiceFieldType {
|
|
2
|
+
name: string;
|
|
3
|
+
required: boolean;
|
|
4
|
+
describes?: string;
|
|
5
|
+
notable: boolean;
|
|
6
|
+
suggested?: Record<string, unknown>;
|
|
7
|
+
}
|
|
8
|
+
export declare class StationServiceType {
|
|
9
|
+
name: string;
|
|
10
|
+
describes: string;
|
|
11
|
+
fields: StationServiceFieldType[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* What a step can call, and what each call needs.
|
|
15
|
+
*
|
|
16
|
+
* ── Why the server answers this and not introspection in the browser ─────
|
|
17
|
+
* The browser could ask the schema itself. It would get every mutation in the app, and then the
|
|
18
|
+
* composer would have to decide which of them belong at a bench — a judgement, made in a place
|
|
19
|
+
* nobody would think to look. Worse, introspection alone cannot say that `goodCount` is nullable
|
|
20
|
+
* *and* must not be left empty; that is domain knowledge, and it lives beside the declaration.
|
|
21
|
+
*
|
|
22
|
+
* The field names and their nullability still come from the schema's own metadata. Nothing here
|
|
23
|
+
* is a second copy of the input types.
|
|
24
|
+
*/
|
|
25
|
+
export declare class StationServiceResolver {
|
|
26
|
+
stationServices(): Promise<StationServiceType[]>;
|
|
27
|
+
}
|