@things-factory/station 10.1.141 → 10.1.143
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-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/activities/activity-station-screen-control-withdrawal.js +3 -7
- package/dist-server/activities/activity-station-screen-control-withdrawal.js.map +1 -1
- package/dist-server/service/index.d.ts +0 -1
- package/dist-server/service/index.js +0 -1
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/station/index.d.ts +2 -2
- package/dist-server/service/station/station-product.d.ts +4 -31
- package/dist-server/service/station/station-product.js +5 -5
- package/dist-server/service/station/station-product.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -7
- package/dist-server/service/station/deletion-changes.d.ts +0 -14
- package/dist-server/service/station/deletion-changes.js +0 -25
- package/dist-server/service/station/deletion-changes.js.map +0 -1
|
@@ -4,8 +4,7 @@ exports.ActivityStationScreenControlWithdrawal = exports.STATION_SCREEN_CONTROL_
|
|
|
4
4
|
const worklist_1 = require("@things-factory/worklist");
|
|
5
5
|
const station_screen_spec_1 = require("../service/station/station-screen-spec");
|
|
6
6
|
const station_screen_control_withdrawal_1 = require("../service/station/station-screen-control-withdrawal");
|
|
7
|
-
const
|
|
8
|
-
const station_product_1 = require("../service/station/station-product");
|
|
7
|
+
const fact_change_1 = require("@things-factory/fact-change");
|
|
9
8
|
const shell_1 = require("@things-factory/shell");
|
|
10
9
|
exports.STATION_SCREEN_CONTROL_WITHDRAWAL_ACTIVITY = 'StationScreenControlWithdrawal';
|
|
11
10
|
/** 사람이 볼 사실이 다르므로 코드가 다르다. 문장은 요청의 언어로 `error.<code>` 에서 만든다. */
|
|
@@ -55,11 +54,8 @@ async function callback(instance, _addendum, context) {
|
|
|
55
54
|
* **지우는 그 순간의 설정**을 이력에 남긴다. 상신의 `before` 는 결재자가 본 모습이고, 그 사이 저장된
|
|
56
55
|
* 흐름까지 여기서 읽은 행으로 적는다(ADR-0064 ③).
|
|
57
56
|
*/
|
|
58
|
-
/*
|
|
59
|
-
|
|
60
|
-
* 지워지는가」뿐이고, plant 는 그것을 `fact-change` 로 보낸다.
|
|
61
|
-
*/
|
|
62
|
-
await (0, station_product_1.stationProduct)().recordChange(tx, domain, { factKind: 'station-screen-spec', factName: standing.boardId, changes: (0, deletion_changes_1.deletionChanges)((0, station_screen_control_withdrawal_1.stationScreenSpecBefore)(standing)) }, { user, activityInstanceId: instance.id });
|
|
57
|
+
/* The shared change log (ADR-0124); products no longer declare where it goes. */
|
|
58
|
+
await (0, fact_change_1.recordFactChanges)(tx, domain, { factKind: 'station-screen-spec', factName: standing.boardId, changes: (0, fact_change_1.deletionChanges)((0, station_screen_control_withdrawal_1.stationScreenSpecBefore)(standing)) }, { user, activityInstanceId: instance.id });
|
|
63
59
|
await repository.delete({ id: standing.id });
|
|
64
60
|
return;
|
|
65
61
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"activity-station-screen-control-withdrawal.js","sourceRoot":"","sources":["../../server/activities/activity-station-screen-control-withdrawal.ts"],"names":[],"mappings":"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","sourcesContent":["import { ActivityInstance, ActivityInstanceStatus, UpdateActivityInstanceStateAddendum } from '@things-factory/worklist'\n\nimport { StationScreenSpec } from '../service/station/station-screen-spec'\nimport {\n stationScreenSpecBefore,\n whyNotWithdraw,\n type ControlWithdrawalAction,\n type ControlWithdrawalRefusal\n} from '../service/station/station-screen-control-withdrawal'\nimport { deletionChanges } from '../service/station/deletion-changes'\nimport { stationProduct } from '../service/station/station-product'\nimport { Refusal, speaksRefusals } from '@things-factory/shell'\n\nexport const STATION_SCREEN_CONTROL_WITHDRAWAL_ACTIVITY = 'StationScreenControlWithdrawal'\n\n/** 사람이 볼 사실이 다르므로 코드가 다르다. 문장은 요청의 언어로 `error.<code>` 에서 만든다. */\nfunction withdrawalRefusal(refusal: ControlWithdrawalRefusal): Refusal {\n switch (refusal) {\n case 'control-withdrawal-spec-gone':\n return new Refusal(\n 'control-withdrawal-spec-gone',\n {},\n '[control withdrawal] the requested screen setting no longer exists. reject this item.'\n )\n case 'control-already-off':\n return new Refusal(\n 'control-already-off',\n {},\n '[control withdrawal] this screen is no longer a controlled document. reject this item.'\n )\n }\n}\n\n/**\n * 승인되면 **올릴 때 굳힌 것만** 한다 — 그 사이 들어온 다른 변경을 덮지 않는다.\n *\n * ── 왜 id 로 다시 읽나 ─────────────────────────────────────────────────────\n * boardId 로 찾으면, 그 사이 지우고 새로 만든 spec 을 끄거나 지운다. 결재자가 본 것은 그 spec 이\n * 아니다.\n *\n * ── 왜 통째로 저장하지 않나 ────────────────────────────────────────────────\n * 읽은 행을 고쳐 `save` 하면 그 사이 누가 저장한 흐름이 **읽은 시점의 흐름으로** 되돌아간다. 끄는\n * 것은 `controlled` 한 칸이므로 그 칸만 쓴다.\n *\n * ── 할 수 없으면 조용히 끝내지 않는다 ──────────────────────────────────────\n * 던진다. 결재가 끝나지 않고 결재자가 사유를 본다 — 「승인됨」으로 끝났는데 아무 일도 없던 것이 가장\n * 나쁘다.\n */\nasync function callback(\n instance: ActivityInstance,\n _addendum: UpdateActivityInstanceStateAddendum,\n context: ResolverContext\n) {\n if (instance.state !== ActivityInstanceStatus.Ended) return\n\n const withdrawal = instance.input?.withdrawal as { specId?: string; action?: ControlWithdrawalAction } | undefined\n\n if (!withdrawal?.specId || (withdrawal.action !== 'turn-off' && withdrawal.action !== 'delete')) {\n throw new Refusal(\n 'control-withdrawal-carried-missing',\n {},\n '[control withdrawal] the approved item has no screen setting, so it was not applied.'\n )\n }\n\n const { domain, user, tx } = context.state\n const repository = tx.getRepository(StationScreenSpec)\n const standing = await repository.findOne({ where: { domain: { id: domain.id }, id: withdrawal.specId } })\n const refusal = whyNotWithdraw(standing)\n\n if (refusal) {\n throw withdrawalRefusal(refusal)\n }\n\n /*\n * 범위는 위에서 도메인으로 좁혀 읽은 그 행이다. 여기서 id(uuid) 하나로 쓰는 것은 관계 칸을 조건에\n * 넣은 `update` · `delete` 가 드라이버마다 다르게 풀리기 때문이다 — 읽기는 이미 도메인을 확인했다.\n */\n if (withdrawal.action === 'delete') {\n /*\n * **지우는 그 순간의 설정**을 이력에 남긴다. 상신의 `before` 는 결재자가 본 모습이고, 그 사이 저장된\n * 흐름까지 여기서 읽은 행으로 적는다(ADR-0064 ③).\n */\n /*\n * 어느 이력으로 가는지는 제품이 정한다 — 선언 다섯(ADR-0083 결정 7). 여기가 만드는 것은 「무엇이\n * 지워지는가」뿐이고, plant 는 그것을 `fact-change` 로 보낸다.\n */\n await stationProduct().recordChange(\n tx,\n domain,\n { factKind: 'station-screen-spec', factName: standing!.boardId, changes: deletionChanges(stationScreenSpecBefore(standing!)) },\n { user, activityInstanceId: instance.id }\n )\n\n await repository.delete({ id: standing!.id })\n return\n }\n\n await repository.update({ id: standing!.id }, { controlled: false, updater: user } as any)\n}\n\n/**\n * 통제 해제 승인 — **통제 문서를 통제 밖으로 내보내는 결재.**\n *\n * ── 왜 발행 승인과 다른 문인가 ─────────────────────────────────────────────\n * `station_screen_releases` 한 행은 「이 도안 지문에 서명했다」 하나만 뜻해야 한다. 발행 관문이 그\n * 행을 그대로 서명으로 센다. 해제를 거기 섞으면 읽는 모든 자리가 종류로 걸러야 하고, 한 곳이\n * 빠지면 지문 없는 행이 서명이 된다. 그래서 문을 가르고, 해제는 별도 표를 두지 않는다 — 누가 언제\n * 승인했는지는 이 활동 인스턴스가 말한다(아키텍트 판정 2026-09-17).\n *\n * ── 결재선은 이 문의 것이 아니다 ───────────────────────────────────────────\n * 발행 승인의 결재선을 **올리는 순간 읽어서** 싣는다(`requestStationScreenControlWithdrawal`). 같은\n * 결재선을 값으로 두 번 적으면 언젠가 갈린다 — 그러면 켤 때와 끌 때 승인하는 사람이 달라진다.\n * 문 현황은 이 문을 「화면 발행 승인의 결재선을 따른다」로 그린다(`LINE_FOLLOWS`).\n */\nexport const ActivityStationScreenControlWithdrawal = {\n name: STATION_SCREEN_CONTROL_WITHDRAWAL_ACTIVITY,\n /* label and guide are drawn from the translations (activity.<door>.label · .guide.<n>); change them together. */\n label: 'control withdrawal approval',\n description: 'Approve taking one station screen out of document control',\n guide: [\n 'take a station screen that was put under document control out of control. after approval it is published without approval.',\n 'the approval line is the same as for screen publication approval — the same people decide turning it on and off.',\n 'until it is approved it is still a controlled document. an unsigned edition does not go out in the meantime either.'\n ],\n release: '1.0.0',\n provider: 'hatiolab.com',\n category: 'quality',\n activityType: 'user',\n priority: 1,\n startingType: 'post',\n multiple: 'single',\n standardTime: 8 * 60 * 60,\n searchKeys: [\n { name: 'board', description: 'Which screen leaves document control', inputKey: 'boardId', tKey: 'field.board' }\n ],\n /*\n * 전용 결재 화면을 두지 않는다 — 결재자가 판단할 것이 선언한 자리로 다 그려진다. 앞서 전용 화면을\n * 가리켜 놓고 안 만든 양식이 빈 화면에 승인 단추만 남겼다(§`activity-lot-hold-release`). 둘 다 비운다.\n */\n viewType: '',\n viewSource: '',\n decisionKeys: [\n { role: 'headline', inputPath: 'headline', name: 'which screen leaves control' },\n { role: 'subject', inputPath: 'withdrawal.screenName', tKey: 'field.board', name: 'which screen' },\n { role: 'reason', inputPath: 'withdrawal.reason', tKey: 'field.reason', name: 'why control is no longer needed' },\n { role: 'consequence', inputPath: 'consequences.after', name: 'what goes out without approval afterwards' },\n { role: 'reversibility', inputPath: 'reversibility', name: 'whether this can be taken back' }\n ],\n model: [\n {\n /* 찾기 칸이 읽는 자리 — 맨 위에 있어야 채워진다. 이름이 아니라 식별자로 건다. */\n name: 'boardId',\n tag: 'boardId',\n description: 'Which screen, by the identifier that survives renaming',\n hidden: true,\n mandatory: true,\n inout: 'in',\n type: 'string',\n options: {},\n unit: null,\n quantifier: [1],\n spec: {}\n },\n {\n name: 'withdrawal',\n tag: 'withdrawal',\n description: 'Which spec, and whether it is turned off or deleted — fixed when this was raised',\n hidden: true,\n mandatory: true,\n inout: 'in',\n type: 'object',\n options: {},\n unit: null,\n quantifier: [1],\n spec: {}\n }\n ],\n callback: speaksRefusals(callback)\n}\n"]}
|
|
1
|
+
{"version":3,"file":"activity-station-screen-control-withdrawal.js","sourceRoot":"","sources":["../../server/activities/activity-station-screen-control-withdrawal.ts"],"names":[],"mappings":"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","sourcesContent":["import { ActivityInstance, ActivityInstanceStatus, UpdateActivityInstanceStateAddendum } from '@things-factory/worklist'\n\nimport { StationScreenSpec } from '../service/station/station-screen-spec'\nimport {\n stationScreenSpecBefore,\n whyNotWithdraw,\n type ControlWithdrawalAction,\n type ControlWithdrawalRefusal\n} from '../service/station/station-screen-control-withdrawal'\nimport { deletionChanges, recordFactChanges } from '@things-factory/fact-change'\nimport { Refusal, speaksRefusals } from '@things-factory/shell'\n\nexport const STATION_SCREEN_CONTROL_WITHDRAWAL_ACTIVITY = 'StationScreenControlWithdrawal'\n\n/** 사람이 볼 사실이 다르므로 코드가 다르다. 문장은 요청의 언어로 `error.<code>` 에서 만든다. */\nfunction withdrawalRefusal(refusal: ControlWithdrawalRefusal): Refusal {\n switch (refusal) {\n case 'control-withdrawal-spec-gone':\n return new Refusal(\n 'control-withdrawal-spec-gone',\n {},\n '[control withdrawal] the requested screen setting no longer exists. reject this item.'\n )\n case 'control-already-off':\n return new Refusal(\n 'control-already-off',\n {},\n '[control withdrawal] this screen is no longer a controlled document. reject this item.'\n )\n }\n}\n\n/**\n * 승인되면 **올릴 때 굳힌 것만** 한다 — 그 사이 들어온 다른 변경을 덮지 않는다.\n *\n * ── 왜 id 로 다시 읽나 ─────────────────────────────────────────────────────\n * boardId 로 찾으면, 그 사이 지우고 새로 만든 spec 을 끄거나 지운다. 결재자가 본 것은 그 spec 이\n * 아니다.\n *\n * ── 왜 통째로 저장하지 않나 ────────────────────────────────────────────────\n * 읽은 행을 고쳐 `save` 하면 그 사이 누가 저장한 흐름이 **읽은 시점의 흐름으로** 되돌아간다. 끄는\n * 것은 `controlled` 한 칸이므로 그 칸만 쓴다.\n *\n * ── 할 수 없으면 조용히 끝내지 않는다 ──────────────────────────────────────\n * 던진다. 결재가 끝나지 않고 결재자가 사유를 본다 — 「승인됨」으로 끝났는데 아무 일도 없던 것이 가장\n * 나쁘다.\n */\nasync function callback(\n instance: ActivityInstance,\n _addendum: UpdateActivityInstanceStateAddendum,\n context: ResolverContext\n) {\n if (instance.state !== ActivityInstanceStatus.Ended) return\n\n const withdrawal = instance.input?.withdrawal as { specId?: string; action?: ControlWithdrawalAction } | undefined\n\n if (!withdrawal?.specId || (withdrawal.action !== 'turn-off' && withdrawal.action !== 'delete')) {\n throw new Refusal(\n 'control-withdrawal-carried-missing',\n {},\n '[control withdrawal] the approved item has no screen setting, so it was not applied.'\n )\n }\n\n const { domain, user, tx } = context.state\n const repository = tx.getRepository(StationScreenSpec)\n const standing = await repository.findOne({ where: { domain: { id: domain.id }, id: withdrawal.specId } })\n const refusal = whyNotWithdraw(standing)\n\n if (refusal) {\n throw withdrawalRefusal(refusal)\n }\n\n /*\n * 범위는 위에서 도메인으로 좁혀 읽은 그 행이다. 여기서 id(uuid) 하나로 쓰는 것은 관계 칸을 조건에\n * 넣은 `update` · `delete` 가 드라이버마다 다르게 풀리기 때문이다 — 읽기는 이미 도메인을 확인했다.\n */\n if (withdrawal.action === 'delete') {\n /*\n * **지우는 그 순간의 설정**을 이력에 남긴다. 상신의 `before` 는 결재자가 본 모습이고, 그 사이 저장된\n * 흐름까지 여기서 읽은 행으로 적는다(ADR-0064 ③).\n */\n /* The shared change log (ADR-0124); products no longer declare where it goes. */\n await recordFactChanges(\n tx,\n domain,\n { factKind: 'station-screen-spec', factName: standing!.boardId, changes: deletionChanges(stationScreenSpecBefore(standing!)) },\n { user, activityInstanceId: instance.id }\n )\n\n await repository.delete({ id: standing!.id })\n return\n }\n\n await repository.update({ id: standing!.id }, { controlled: false, updater: user } as any)\n}\n\n/**\n * 통제 해제 승인 — **통제 문서를 통제 밖으로 내보내는 결재.**\n *\n * ── 왜 발행 승인과 다른 문인가 ─────────────────────────────────────────────\n * `station_screen_releases` 한 행은 「이 도안 지문에 서명했다」 하나만 뜻해야 한다. 발행 관문이 그\n * 행을 그대로 서명으로 센다. 해제를 거기 섞으면 읽는 모든 자리가 종류로 걸러야 하고, 한 곳이\n * 빠지면 지문 없는 행이 서명이 된다. 그래서 문을 가르고, 해제는 별도 표를 두지 않는다 — 누가 언제\n * 승인했는지는 이 활동 인스턴스가 말한다(아키텍트 판정 2026-09-17).\n *\n * ── 결재선은 이 문의 것이 아니다 ───────────────────────────────────────────\n * 발행 승인의 결재선을 **올리는 순간 읽어서** 싣는다(`requestStationScreenControlWithdrawal`). 같은\n * 결재선을 값으로 두 번 적으면 언젠가 갈린다 — 그러면 켤 때와 끌 때 승인하는 사람이 달라진다.\n * 문 현황은 이 문을 「화면 발행 승인의 결재선을 따른다」로 그린다(`LINE_FOLLOWS`).\n */\nexport const ActivityStationScreenControlWithdrawal = {\n name: STATION_SCREEN_CONTROL_WITHDRAWAL_ACTIVITY,\n /* label and guide are drawn from the translations (activity.<door>.label · .guide.<n>); change them together. */\n label: 'control withdrawal approval',\n description: 'Approve taking one station screen out of document control',\n guide: [\n 'take a station screen that was put under document control out of control. after approval it is published without approval.',\n 'the approval line is the same as for screen publication approval — the same people decide turning it on and off.',\n 'until it is approved it is still a controlled document. an unsigned edition does not go out in the meantime either.'\n ],\n release: '1.0.0',\n provider: 'hatiolab.com',\n category: 'quality',\n activityType: 'user',\n priority: 1,\n startingType: 'post',\n multiple: 'single',\n standardTime: 8 * 60 * 60,\n searchKeys: [\n { name: 'board', description: 'Which screen leaves document control', inputKey: 'boardId', tKey: 'field.board' }\n ],\n /*\n * 전용 결재 화면을 두지 않는다 — 결재자가 판단할 것이 선언한 자리로 다 그려진다. 앞서 전용 화면을\n * 가리켜 놓고 안 만든 양식이 빈 화면에 승인 단추만 남겼다(§`activity-lot-hold-release`). 둘 다 비운다.\n */\n viewType: '',\n viewSource: '',\n decisionKeys: [\n { role: 'headline', inputPath: 'headline', name: 'which screen leaves control' },\n { role: 'subject', inputPath: 'withdrawal.screenName', tKey: 'field.board', name: 'which screen' },\n { role: 'reason', inputPath: 'withdrawal.reason', tKey: 'field.reason', name: 'why control is no longer needed' },\n { role: 'consequence', inputPath: 'consequences.after', name: 'what goes out without approval afterwards' },\n { role: 'reversibility', inputPath: 'reversibility', name: 'whether this can be taken back' }\n ],\n model: [\n {\n /* 찾기 칸이 읽는 자리 — 맨 위에 있어야 채워진다. 이름이 아니라 식별자로 건다. */\n name: 'boardId',\n tag: 'boardId',\n description: 'Which screen, by the identifier that survives renaming',\n hidden: true,\n mandatory: true,\n inout: 'in',\n type: 'string',\n options: {},\n unit: null,\n quantifier: [1],\n spec: {}\n },\n {\n name: 'withdrawal',\n tag: 'withdrawal',\n description: 'Which spec, and whether it is turned off or deleted — fixed when this was raised',\n hidden: true,\n mandatory: true,\n inout: 'in',\n type: 'object',\n options: {},\n unit: null,\n quantifier: [1],\n spec: {}\n }\n ],\n callback: speaksRefusals(callback)\n}\n"]}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export * from './station/station-product.js';
|
|
2
2
|
export * from './station/station-axes.js';
|
|
3
|
-
export * from './station/deletion-changes.js';
|
|
4
3
|
export * from './station/station-screen-spec.js';
|
|
5
4
|
export * from './station/station-screen-assignment.js';
|
|
6
5
|
export * from './station/station-screen-release-record.js';
|
|
@@ -12,7 +12,6 @@ const tslib_1 = require("tslib");
|
|
|
12
12
|
/* 제품이 선언하는 자리 — 이것이 안 나가면 앱이 「그런 export 가 없다」로 멈춘다 (ADR-0083 결정 2·7). */
|
|
13
13
|
tslib_1.__exportStar(require("./station/station-product.js"), exports);
|
|
14
14
|
tslib_1.__exportStar(require("./station/station-axes.js"), exports);
|
|
15
|
-
tslib_1.__exportStar(require("./station/deletion-changes.js"), exports);
|
|
16
15
|
/* 엔티티 */
|
|
17
16
|
tslib_1.__exportStar(require("./station/station-screen-spec.js"), exports);
|
|
18
17
|
tslib_1.__exportStar(require("./station/station-screen-assignment.js"), exports);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;;;AAEH,yEAAyE;AACzE,uEAA4C;AAC5C,oEAAyC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;;;AAEH,yEAAyE;AACzE,uEAA4C;AAC5C,oEAAyC;AAEzC,SAAS;AACT,2EAAgD;AAChD,iFAAsD;AACtD,qFAA0D;AAE1D,iCAAiC;AACjC,iFAAsD;AACtD,kFAAuD;AACvD,oEAAyC;AACzC,wEAA6C;AAC7C,+EAAoD;AACpD,6EAAkD;AAClD,mFAAwD;AACxD,wFAA6D;AAC7D,gFAAqD;AACrD,gFAAqD;AACrD,yFAA8D;AAC9D,yFAA8D;AAC9D,gFAAqD;AACrD,2EAAgD;AAChD,uEAA4C;AAE5C,yBAAyB;AACzB,iDAAqE;AAE5D,yFAFA,mBAAQ,OAEA;AAAE,0FAFA,oBAAS,OAEA;AAAE,4FAFA,sBAAW,OAEA","sourcesContent":["/*\n * ★ 낼 것을 파일마다 적는다 — 묶음의 `index` 를 통째로 내보내지 않는다.\n *\n * 그 파일은 `entities` · `resolvers` · `subscribers` 라는 이름을 갖고 있고 이 파일도 같은 이름을\n * 갖는다. `export *` 로 섞으면 그 이름들이 조용히 가려지거나 빠진다 — 오류는 안 나고 서버가 반쯤\n * 선다(worklist 가 같은 자리에 같은 주석을 달아 두었다).\n */\n\n/* 제품이 선언하는 자리 — 이것이 안 나가면 앱이 「그런 export 가 없다」로 멈춘다 (ADR-0083 결정 2·7). */\nexport * from './station/station-product.js'\nexport * from './station/station-axes.js'\n\n/* 엔티티 */\nexport * from './station/station-screen-spec.js'\nexport * from './station/station-screen-assignment.js'\nexport * from './station/station-screen-release-record.js'\n\n/* 저작·배정·흐름의 판단 — 제품과 시험이 읽는다. */\nexport * from './station/station-assignment-choice.js'\nexport * from './station/station-assignment-service.js'\nexport * from './station/station-flow.js'\nexport * from './station/station-generate.js'\nexport * from './station/station-runtime-binding.js'\nexport * from './station/station-screen-layout.js'\nexport * from './station/station-screen-release-gate.js'\nexport * from './station/station-screen-release-signature.js'\nexport * from './station/station-screen-scorecard.js'\nexport * from './station/station-screen-thumbnail.js'\nexport * from './station/station-screen-control-withdrawal.js'\nexport * from './station/station-screen-publish-subscriber.js'\nexport * from './station/station-argument-sources.js'\nexport * from './station/screen-model-digest.js'\nexport * from './station/staged-material.js'\n\n/* 리졸버는 이름으로 받아 다시 낸다. */\nimport { entities, resolvers, subscribers } from './station/index.js'\n\nexport { entities, resolvers, subscribers }\n"]}
|
|
@@ -8,6 +8,6 @@ import { StationServiceResolver } from './station-service-resolver.js';
|
|
|
8
8
|
import { StationAssignmentResolver } from './station-assignment-resolver.js';
|
|
9
9
|
import { StationScreenControlWithdrawalResolver } from './station-screen-control-withdrawal-resolver.js';
|
|
10
10
|
import { StationScreenPublishSubscriber } from './station-screen-publish-subscriber.js';
|
|
11
|
-
export declare const entities: (typeof
|
|
12
|
-
export declare const resolvers: (typeof
|
|
11
|
+
export declare const entities: (typeof StationScreenRelease | typeof StationScreenSpec | typeof StationScreenAssignment)[];
|
|
12
|
+
export declare const resolvers: (typeof StationScreenControlWithdrawalResolver | typeof StationScreenSpecResolver | typeof StationScreenDraftResolver | typeof StationScreenListResolver | typeof StationServiceResolver | typeof StationAssignmentResolver)[];
|
|
13
13
|
export declare const subscribers: (typeof StationScreenPublishSubscriber)[];
|
|
@@ -68,32 +68,6 @@ export type StationGroundReader = (manager: EntityManager, domain: Domain, stati
|
|
|
68
68
|
operatorName?: string;
|
|
69
69
|
jobOrderName?: string;
|
|
70
70
|
}) => Promise<Pick<StationAssignmentSituation, 'workCenterName' | 'routeKey' | 'materialClassIds' | 'personnelClassIds'>>;
|
|
71
|
-
/**
|
|
72
|
-
* 기계가 만든 변경 목록을 제품의 이력으로 보내는 자리 (ADR-0083 결정 7, 선언 다섯).
|
|
73
|
-
*
|
|
74
|
-
* **무엇이 바뀌었는지는 기계가 만든다** — 자기 엔티티를 자기가 안다. 화면 정의가 지워질 때 어느 칸이
|
|
75
|
-
* 무엇이었는지는 `deletionChanges` 가 이 모듈 안에서 만든다.
|
|
76
|
-
*
|
|
77
|
-
* **그것이 어느 이력으로 가는지는 제품이 정한다.** plant 는 `fact-change` 로 보내고, 창고는 자기
|
|
78
|
-
* 이력을 갖는다. 그 표와 그 낱말(`source` 같은 것)은 제품의 것이라 모듈이 알 까닭이 없다.
|
|
79
|
-
*
|
|
80
|
-
* 필수다. 선언하지 않으면 통제 문서를 지운 이력이 **오류 없이 안 남는다** — 통제 문서에서 그것은
|
|
81
|
-
* 「누가 언제 무엇을 지웠나」를 못 답하는 것이고, 감사에서 드러날 때는 이미 늦다.
|
|
82
|
-
*/
|
|
83
|
-
export type StationChangeRecorder = (manager: EntityManager, domain: Domain, what: {
|
|
84
|
-
factKind: string;
|
|
85
|
-
factName: string;
|
|
86
|
-
changes: {
|
|
87
|
-
field: string;
|
|
88
|
-
fromValue?: string;
|
|
89
|
-
toValue?: string;
|
|
90
|
-
}[];
|
|
91
|
-
at?: Date;
|
|
92
|
-
}, by: {
|
|
93
|
-
user?: unknown;
|
|
94
|
-
/** 결재를 지나 온 것이면 그 건. 누가 승인했는지는 워크리스트가 들고 이것으로 찾아간다. */
|
|
95
|
-
activityInstanceId?: string;
|
|
96
|
-
}) => Promise<unknown>;
|
|
97
71
|
export interface StationProduct {
|
|
98
72
|
/** For the message when something is missing — the product says its own name. */
|
|
99
73
|
product: string;
|
|
@@ -105,15 +79,14 @@ export interface StationProduct {
|
|
|
105
79
|
axes?: readonly string[];
|
|
106
80
|
services: StationServiceDeclaration[];
|
|
107
81
|
readGround: StationGroundReader;
|
|
108
|
-
recordChange: StationChangeRecorder;
|
|
109
82
|
}
|
|
110
83
|
/**
|
|
111
84
|
* Called once at boot, from the product's own declaration file.
|
|
112
85
|
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
86
|
+
* A missing slot is caught here rather than when it is first called: the server runs with
|
|
87
|
+
* `strict: false`, so one `as any` in a declaration file gets past the type (ADR-0083 decision 2,
|
|
88
|
+
* "declarations are required and the module has no default"). Where a withdrawal's history goes is no
|
|
89
|
+
* longer a product slot — it is the shared change log (ADR-0124).
|
|
117
90
|
*/
|
|
118
91
|
export declare function declareStationProduct(product: StationProduct): void;
|
|
119
92
|
/**
|
|
@@ -11,13 +11,13 @@ let declared;
|
|
|
11
11
|
/**
|
|
12
12
|
* Called once at boot, from the product's own declaration file.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* A missing slot is caught here rather than when it is first called: the server runs with
|
|
15
|
+
* `strict: false`, so one `as any` in a declaration file gets past the type (ADR-0083 decision 2,
|
|
16
|
+
* "declarations are required and the module has no default"). Where a withdrawal's history goes is no
|
|
17
|
+
* longer a product slot — it is the shared change log (ADR-0124).
|
|
18
18
|
*/
|
|
19
19
|
function declareStationProduct(product) {
|
|
20
|
-
const missing = ['services', 'readGround'
|
|
20
|
+
const missing = ['services', 'readGround'].filter(one => !product?.[one]);
|
|
21
21
|
if (missing.length) {
|
|
22
22
|
throw new Error(`station: ${product?.product ?? '이 제품'} 의 선언에 ${missing.join(' · ')} 이(가) 없습니다 — 현장 화면이 서려면 다 채워야 합니다.`);
|
|
23
23
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"station-product.js","sourceRoot":"","sources":["../../../server/service/station/station-product.ts"],"names":[],"mappings":"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","sourcesContent":["import { getMetadataStorage } from 'type-graphql'\nimport type { EntityManager } from 'typeorm'\nimport type { Domain } from '@things-factory/shell'\n\nimport type { StationAssignmentSituation } from './station-assignment-choice.js'\nimport type { StationServiceField } from './station-flow.js'\nimport { STATION_AXES, wrongDeclaredAxes } from './station-axes.js'\n\n/**\n * What a product has to say before the station machinery will run for it (ADR-0083 decision 2).\n *\n * ═══════════════════════════════════════════════════════════════════════════\n * ## Why this file exists\n *\n * The station machinery is going to `@things-factory/station`, where operato-warehouse, and later\n * a yard and an energy product, will use the same authoring, the same flow model and the same\n * release approval. Nothing of that is about a factory. What *is* about a factory is small: which\n * values a screen may read, which mutations a step may call, who signs a release, and where those\n * values actually come from.\n *\n * This is that boundary, drawn **before** anything moves. Splitting while moving would leave the\n * first failure unattributable — was it the split or the move? — which is the order ADR-0078 used\n * when it lifted the outbox judgement out one layer at a time.\n *\n * ## Declared, never defaulted\n *\n * A product that has not declared gets an error naming what is missing, not an empty list. A\n * default here would mean a warehouse screen quietly offering a factory's mutations, or offering\n * none and reading as \"this product has no services\" — the silent empty this codebase keeps\n * finding. The same reason `registerWorkDoors` has no default door list.\n *\n * ## The assignment columns are the standard's general words (ADR-0083 decision 5)\n *\n * They used to be a factory's: `productionLineName` became `workCenterName` because the contract\n * already says `WorkCenter` is the general term over `ProcessCell` · `ProductionLine` ·\n * `ProductionUnit` · `StorageZone`. A warehouse zone and a yard block have somewhere to stand now.\n * `routeKey` and `operationKey` stay — a warehouse has a picking route too. No union of both\n * spellings: that would grow four products every time an axis is added.\n * ═══════════════════════════════════════════════════════════════════════════\n */\n\n/** Something a station step can call. The product owns the list; the machinery reads the shape. */\nexport interface StationServiceDeclaration {\n /** The mutation name, exactly as the schema spells it. */\n name: string\n /** What an author is doing when they pick this — their words, not the mutation's. */\n describes: string\n /**\n * The `@InputType` whose fields this call takes — **when it takes one.**\n *\n * Absent, the machine reads the mutation's own arguments by `name` (ADR-0083 ruling ②): a warehouse's\n * floor doors take a task name and a scanned place, not one input object, and wrapping them in one would\n * give the same door two shapes.\n */\n input?: Function\n /**\n * Nullable fields that still must not be left empty.\n *\n * Absent means \"nobody wrote it down\", not zero — and the two are different facts on a\n * production report.\n */\n notable?: string[]\n}\n\n/**\n * The facts this product knows about one station, in the words assignment selects on.\n *\n * The machinery reads what the platform owns (the equipment and its classes) itself. This answers\n * only what the product's own domain knows — for a factory, which line it stands on and what the\n * order running there is made of.\n */\nexport type StationGroundReader = (\n manager: EntityManager,\n domain: Domain,\n stationName: string,\n at?: { operationKey?: string; operatorName?: string; jobOrderName?: string }\n) => Promise<Pick<StationAssignmentSituation, 'workCenterName' | 'routeKey' | 'materialClassIds' | 'personnelClassIds'>>\n\n/**\n * 기계가 만든 변경 목록을 제품의 이력으로 보내는 자리 (ADR-0083 결정 7, 선언 다섯).\n *\n * **무엇이 바뀌었는지는 기계가 만든다** — 자기 엔티티를 자기가 안다. 화면 정의가 지워질 때 어느 칸이\n * 무엇이었는지는 `deletionChanges` 가 이 모듈 안에서 만든다.\n *\n * **그것이 어느 이력으로 가는지는 제품이 정한다.** plant 는 `fact-change` 로 보내고, 창고는 자기\n * 이력을 갖는다. 그 표와 그 낱말(`source` 같은 것)은 제품의 것이라 모듈이 알 까닭이 없다.\n *\n * 필수다. 선언하지 않으면 통제 문서를 지운 이력이 **오류 없이 안 남는다** — 통제 문서에서 그것은\n * 「누가 언제 무엇을 지웠나」를 못 답하는 것이고, 감사에서 드러날 때는 이미 늦다.\n */\nexport type StationChangeRecorder = (\n manager: EntityManager,\n domain: Domain,\n what: {\n factKind: string\n factName: string\n changes: { field: string; fromValue?: string; toValue?: string }[]\n at?: Date\n },\n by: {\n user?: unknown\n /** 결재를 지나 온 것이면 그 건. 누가 승인했는지는 워크리스트가 들고 이것으로 찾아간다. */\n activityInstanceId?: string\n }\n) => Promise<unknown>\n\nexport interface StationProduct {\n /** For the message when something is missing — the product says its own name. */\n product: string\n /**\n * The axes this product's screens read beyond the machine's own (ADR-0083 ruling ①) — `group.name`,\n * e.g. `unit.epc` · `task.kind`. The closed set a screen is checked against is the machine's list and\n * this. Absent means the product reads only the machine's.\n */\n axes?: readonly string[]\n services: StationServiceDeclaration[]\n readGround: StationGroundReader\n recordChange: StationChangeRecorder\n}\n\nlet declared: StationProduct | undefined\n\n/**\n * Called once at boot, from the product's own declaration file.\n *\n * 빠진 자리를 여기서 잡는다. 타입이 이미 다섯을 다 요구하지만 서버 쪽은 `strict: false` 로 돌고\n * 선언 파일에 `as any` 가 한 번 끼면 타입이 못 막는다. 그때 빠진 자리는 **부를 때**에야 드러나고,\n * 그 자리가 `recordChange` 면 통제 문서를 지운 이력이 오류 없이 안 남는다 — 기동 때 말하는 쪽이\n * 낫다(ADR-0083 결정 2 「선언은 필수이고 모듈에 기본값은 없다」).\n */\nexport function declareStationProduct(product: StationProduct): void {\n const missing = (['services', 'readGround', 'recordChange'] as const).filter(one => !product?.[one])\n\n if (missing.length) {\n throw new Error(\n `station: ${product?.product ?? '이 제품'} 의 선언에 ${missing.join(' · ')} 이(가) 없습니다 — 현장 화면이 서려면 다 채워야 합니다.`\n )\n }\n\n /* 규칙은 한 자리다 — 화면의 `registerStationAxes` 가 같은 것을 부른다(`shared/station-axes.js`). */\n const wrong = wrongDeclaredAxes(product.axes ?? [])\n\n if (wrong.length) {\n throw new Error(\n `station: ${product.product} 의 axes 선언이 틀렸습니다 — ${wrong.map(one => JSON.stringify(one)).join(' · ')}. ` +\n '축은 `무리.이름` 모양이고, 기계가 이미 가진 축은 다시 선언하지 않습니다.'\n )\n }\n\n declared = product\n}\n\n/**\n * Every axis a screen of this product may read — the machine's and the product's (ADR-0083 ruling ①).\n * The authoring surface lists these; `checkFlow` refuses anything else.\n */\nexport function stationAxesOf(product: Pick<StationProduct, 'axes'>): string[] {\n return [...STATION_AXES, ...(product.axes ?? [])]\n}\n\n/**\n * What the product declared — **or an error saying it has not.**\n *\n * Reading this before the declaration is loaded is a wiring mistake and shows as one. Answering\n * with an empty product instead would draw a screen with no services on it and no reason given.\n */\nexport function stationProduct(): StationProduct {\n if (!declared) {\n throw new Error(\n 'station: 이 제품이 선언을 하지 않았습니다 — 서비스 목록 · 바탕을 읽는 자리 · 기록을 남기는 자리를 declareStationProduct 로 등록해야 현장 화면이 섭니다.'\n )\n }\n\n return declared\n}\n\n/** Tests only — a declaration is meant to be made once, at boot. */\nexport function forgetStationProduct(): void {\n declared = undefined\n}\n\n/**\n * The fields of one service, as the running schema has them.\n *\n * Reads type-graphql's own metadata rather than issuing an introspection query: it is the same\n * declaration the schema is built from, it needs no round trip, and it works before the server\n * has finished starting.\n */\nexport function fieldsOfService(declared: StationServiceDeclaration): StationServiceField[] {\n const storage = getMetadataStorage()\n const notable = new Set(declared.notable ?? [])\n\n if (!declared.input) {\n return argumentsOfMutation(declared.name, notable)\n }\n\n /*\n * Fields are stored against the class, and a class that extends another has its parent's fields\n * under the parent's target. Walking the prototype chain keeps an inherited field from going\n * missing — which would look exactly like the schema not having it.\n */\n const targets = new Set<Function>()\n\n for (let at: Function | null = declared.input; at && at !== Function.prototype; at = Object.getPrototypeOf(at)) {\n targets.add(at)\n }\n\n return storage.fields\n .filter(field => targets.has(field.target as Function))\n .map(field => ({\n name: field.name,\n required: !field.typeOptions?.nullable,\n /*\n * The description the field's own owner wrote.\n *\n * `goodCount` on a screen is a raw schema name to a plant manager. The input type already\n * carries `@Field({ description })` for many of them — the person who added the field is the\n * one who can say what it means, and they already did. Carrying it through beats a second\n * table here that goes stale the day a field is added: a field with no description shows its\n * name, which is what happened anyway before.\n */\n ...(field.description ? { describes: field.description } : {}),\n ...(notable.has(field.name) ? { notable: true } : {})\n }))\n}\n\n/**\n * A door that takes no input object — **its own arguments, as the schema has them** (ADR-0083 ruling ②).\n *\n * The same metadata the schema is built from, read in the same place as the input-type fields, so a door's\n * arguments cannot drift from what the running server takes. `@Ctx()` and other non-argument parameters are\n * not fields an author fills.\n *\n * A name the schema does not have is said. Answered with no fields, a misspelt door would look like a door\n * that needs nothing, and the screen built on it would call something that is not there.\n */\nfunction argumentsOfMutation(name: string, notable: Set<string>): StationServiceField[] {\n const storage = getMetadataStorage()\n const mutation = storage.mutations.find(one => one.schemaName === name)\n\n if (!mutation) {\n throw new Error(`station: 선언한 문 ${name} 이(가) 스키마에 없습니다 — 이름이 틀렸거나 그 리졸버가 등록되지 않았습니다.`)\n }\n\n return storage.params\n .filter(param => param.kind === 'arg' && param.target === mutation.target && param.methodName === mutation.methodName)\n .sort((a, b) => a.index - b.index)\n .map(param => {\n const arg = param as Extract<typeof param, { kind: 'arg' }>\n return {\n name: arg.name,\n required: !arg.typeOptions?.nullable,\n ...(arg.description ? { describes: arg.description } : {}),\n ...(notable.has(arg.name) ? { notable: true } : {})\n }\n })\n}\n\n"]}
|
|
1
|
+
{"version":3,"file":"station-product.js","sourceRoot":"","sources":["../../../server/service/station/station-product.ts"],"names":[],"mappings":"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","sourcesContent":["import { getMetadataStorage } from 'type-graphql'\nimport type { EntityManager } from 'typeorm'\nimport type { Domain } from '@things-factory/shell'\n\nimport type { StationAssignmentSituation } from './station-assignment-choice.js'\nimport type { StationServiceField } from './station-flow.js'\nimport { STATION_AXES, wrongDeclaredAxes } from './station-axes.js'\n\n/**\n * What a product has to say before the station machinery will run for it (ADR-0083 decision 2).\n *\n * ═══════════════════════════════════════════════════════════════════════════\n * ## Why this file exists\n *\n * The station machinery is going to `@things-factory/station`, where operato-warehouse, and later\n * a yard and an energy product, will use the same authoring, the same flow model and the same\n * release approval. Nothing of that is about a factory. What *is* about a factory is small: which\n * values a screen may read, which mutations a step may call, who signs a release, and where those\n * values actually come from.\n *\n * This is that boundary, drawn **before** anything moves. Splitting while moving would leave the\n * first failure unattributable — was it the split or the move? — which is the order ADR-0078 used\n * when it lifted the outbox judgement out one layer at a time.\n *\n * ## Declared, never defaulted\n *\n * A product that has not declared gets an error naming what is missing, not an empty list. A\n * default here would mean a warehouse screen quietly offering a factory's mutations, or offering\n * none and reading as \"this product has no services\" — the silent empty this codebase keeps\n * finding. The same reason `registerWorkDoors` has no default door list.\n *\n * ## The assignment columns are the standard's general words (ADR-0083 decision 5)\n *\n * They used to be a factory's: `productionLineName` became `workCenterName` because the contract\n * already says `WorkCenter` is the general term over `ProcessCell` · `ProductionLine` ·\n * `ProductionUnit` · `StorageZone`. A warehouse zone and a yard block have somewhere to stand now.\n * `routeKey` and `operationKey` stay — a warehouse has a picking route too. No union of both\n * spellings: that would grow four products every time an axis is added.\n * ═══════════════════════════════════════════════════════════════════════════\n */\n\n/** Something a station step can call. The product owns the list; the machinery reads the shape. */\nexport interface StationServiceDeclaration {\n /** The mutation name, exactly as the schema spells it. */\n name: string\n /** What an author is doing when they pick this — their words, not the mutation's. */\n describes: string\n /**\n * The `@InputType` whose fields this call takes — **when it takes one.**\n *\n * Absent, the machine reads the mutation's own arguments by `name` (ADR-0083 ruling ②): a warehouse's\n * floor doors take a task name and a scanned place, not one input object, and wrapping them in one would\n * give the same door two shapes.\n */\n input?: Function\n /**\n * Nullable fields that still must not be left empty.\n *\n * Absent means \"nobody wrote it down\", not zero — and the two are different facts on a\n * production report.\n */\n notable?: string[]\n}\n\n/**\n * The facts this product knows about one station, in the words assignment selects on.\n *\n * The machinery reads what the platform owns (the equipment and its classes) itself. This answers\n * only what the product's own domain knows — for a factory, which line it stands on and what the\n * order running there is made of.\n */\nexport type StationGroundReader = (\n manager: EntityManager,\n domain: Domain,\n stationName: string,\n at?: { operationKey?: string; operatorName?: string; jobOrderName?: string }\n) => Promise<Pick<StationAssignmentSituation, 'workCenterName' | 'routeKey' | 'materialClassIds' | 'personnelClassIds'>>\n\nexport interface StationProduct {\n /** For the message when something is missing — the product says its own name. */\n product: string\n /**\n * The axes this product's screens read beyond the machine's own (ADR-0083 ruling ①) — `group.name`,\n * e.g. `unit.epc` · `task.kind`. The closed set a screen is checked against is the machine's list and\n * this. Absent means the product reads only the machine's.\n */\n axes?: readonly string[]\n services: StationServiceDeclaration[]\n readGround: StationGroundReader\n}\n\nlet declared: StationProduct | undefined\n\n/**\n * Called once at boot, from the product's own declaration file.\n *\n * A missing slot is caught here rather than when it is first called: the server runs with\n * `strict: false`, so one `as any` in a declaration file gets past the type (ADR-0083 decision 2,\n * \"declarations are required and the module has no default\"). Where a withdrawal's history goes is no\n * longer a product slot — it is the shared change log (ADR-0124).\n */\nexport function declareStationProduct(product: StationProduct): void {\n const missing = (['services', 'readGround'] as const).filter(one => !product?.[one])\n\n if (missing.length) {\n throw new Error(\n `station: ${product?.product ?? '이 제품'} 의 선언에 ${missing.join(' · ')} 이(가) 없습니다 — 현장 화면이 서려면 다 채워야 합니다.`\n )\n }\n\n /* 규칙은 한 자리다 — 화면의 `registerStationAxes` 가 같은 것을 부른다(`shared/station-axes.js`). */\n const wrong = wrongDeclaredAxes(product.axes ?? [])\n\n if (wrong.length) {\n throw new Error(\n `station: ${product.product} 의 axes 선언이 틀렸습니다 — ${wrong.map(one => JSON.stringify(one)).join(' · ')}. ` +\n '축은 `무리.이름` 모양이고, 기계가 이미 가진 축은 다시 선언하지 않습니다.'\n )\n }\n\n declared = product\n}\n\n/**\n * Every axis a screen of this product may read — the machine's and the product's (ADR-0083 ruling ①).\n * The authoring surface lists these; `checkFlow` refuses anything else.\n */\nexport function stationAxesOf(product: Pick<StationProduct, 'axes'>): string[] {\n return [...STATION_AXES, ...(product.axes ?? [])]\n}\n\n/**\n * What the product declared — **or an error saying it has not.**\n *\n * Reading this before the declaration is loaded is a wiring mistake and shows as one. Answering\n * with an empty product instead would draw a screen with no services on it and no reason given.\n */\nexport function stationProduct(): StationProduct {\n if (!declared) {\n throw new Error(\n 'station: 이 제품이 선언을 하지 않았습니다 — 서비스 목록 · 바탕을 읽는 자리 · 기록을 남기는 자리를 declareStationProduct 로 등록해야 현장 화면이 섭니다.'\n )\n }\n\n return declared\n}\n\n/** Tests only — a declaration is meant to be made once, at boot. */\nexport function forgetStationProduct(): void {\n declared = undefined\n}\n\n/**\n * The fields of one service, as the running schema has them.\n *\n * Reads type-graphql's own metadata rather than issuing an introspection query: it is the same\n * declaration the schema is built from, it needs no round trip, and it works before the server\n * has finished starting.\n */\nexport function fieldsOfService(declared: StationServiceDeclaration): StationServiceField[] {\n const storage = getMetadataStorage()\n const notable = new Set(declared.notable ?? [])\n\n if (!declared.input) {\n return argumentsOfMutation(declared.name, notable)\n }\n\n /*\n * Fields are stored against the class, and a class that extends another has its parent's fields\n * under the parent's target. Walking the prototype chain keeps an inherited field from going\n * missing — which would look exactly like the schema not having it.\n */\n const targets = new Set<Function>()\n\n for (let at: Function | null = declared.input; at && at !== Function.prototype; at = Object.getPrototypeOf(at)) {\n targets.add(at)\n }\n\n return storage.fields\n .filter(field => targets.has(field.target as Function))\n .map(field => ({\n name: field.name,\n required: !field.typeOptions?.nullable,\n /*\n * The description the field's own owner wrote.\n *\n * `goodCount` on a screen is a raw schema name to a plant manager. The input type already\n * carries `@Field({ description })` for many of them — the person who added the field is the\n * one who can say what it means, and they already did. Carrying it through beats a second\n * table here that goes stale the day a field is added: a field with no description shows its\n * name, which is what happened anyway before.\n */\n ...(field.description ? { describes: field.description } : {}),\n ...(notable.has(field.name) ? { notable: true } : {})\n }))\n}\n\n/**\n * A door that takes no input object — **its own arguments, as the schema has them** (ADR-0083 ruling ②).\n *\n * The same metadata the schema is built from, read in the same place as the input-type fields, so a door's\n * arguments cannot drift from what the running server takes. `@Ctx()` and other non-argument parameters are\n * not fields an author fills.\n *\n * A name the schema does not have is said. Answered with no fields, a misspelt door would look like a door\n * that needs nothing, and the screen built on it would call something that is not there.\n */\nfunction argumentsOfMutation(name: string, notable: Set<string>): StationServiceField[] {\n const storage = getMetadataStorage()\n const mutation = storage.mutations.find(one => one.schemaName === name)\n\n if (!mutation) {\n throw new Error(`station: 선언한 문 ${name} 이(가) 스키마에 없습니다 — 이름이 틀렸거나 그 리졸버가 등록되지 않았습니다.`)\n }\n\n return storage.params\n .filter(param => param.kind === 'arg' && param.target === mutation.target && param.methodName === mutation.methodName)\n .sort((a, b) => a.index - b.index)\n .map(param => {\n const arg = param as Extract<typeof param, { kind: 'arg' }>\n return {\n name: arg.name,\n required: !arg.typeOptions?.nullable,\n ...(arg.description ? { describes: arg.description } : {}),\n ...(notable.has(arg.name) ? { notable: true } : {})\n }\n })\n}\n\n"]}
|