@things-factory/board-import 10.0.0-beta.64
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/config/config.development.js +44 -0
- package/config/config.production.js +18 -0
- package/dist-server/index.d.ts +7 -0
- package/dist-server/index.js +11 -0
- package/dist-server/index.js.map +1 -0
- package/dist-server/service/adapters/dxf-adapter.d.ts +8 -0
- package/dist-server/service/adapters/dxf-adapter.js +206 -0
- package/dist-server/service/adapters/dxf-adapter.js.map +1 -0
- package/dist-server/service/ai/types.d.ts +93 -0
- package/dist-server/service/ai/types.js +169 -0
- package/dist-server/service/ai/types.js.map +1 -0
- package/dist-server/service/converters/generic-to.d.ts +16 -0
- package/dist-server/service/converters/generic-to.js +78 -0
- package/dist-server/service/converters/generic-to.js.map +1 -0
- package/dist-server/service/import-session/import-session-resolver.d.ts +18 -0
- package/dist-server/service/import-session/import-session-resolver.js +135 -0
- package/dist-server/service/import-session/import-session-resolver.js.map +1 -0
- package/dist-server/service/import-session/import-session.d.ts +28 -0
- package/dist-server/service/import-session/import-session.js +152 -0
- package/dist-server/service/import-session/import-session.js.map +1 -0
- package/dist-server/service/import-session/import-worker.d.ts +1 -0
- package/dist-server/service/import-session/import-worker.js +79 -0
- package/dist-server/service/import-session/import-worker.js.map +1 -0
- package/dist-server/service/import-session/index.d.ts +6 -0
- package/dist-server/service/import-session/index.js +12 -0
- package/dist-server/service/import-session/index.js.map +1 -0
- package/dist-server/service/index.d.ts +22 -0
- package/dist-server/service/index.js +47 -0
- package/dist-server/service/index.js.map +1 -0
- package/dist-server/service/pipeline/index.d.ts +33 -0
- package/dist-server/service/pipeline/index.js +58 -0
- package/dist-server/service/pipeline/index.js.map +1 -0
- package/dist-server/service/pipeline/stage2-mapping.d.ts +14 -0
- package/dist-server/service/pipeline/stage2-mapping.js +173 -0
- package/dist-server/service/pipeline/stage2-mapping.js.map +1 -0
- package/dist-server/service/pipeline/stage3-board.d.ts +52 -0
- package/dist-server/service/pipeline/stage3-board.js +53 -0
- package/dist-server/service/pipeline/stage3-board.js.map +1 -0
- package/dist-server/service/pipeline/stage4-binding.d.ts +9 -0
- package/dist-server/service/pipeline/stage4-binding.js +28 -0
- package/dist-server/service/pipeline/stage4-binding.js.map +1 -0
- package/dist-server/service/registry/index.d.ts +27 -0
- package/dist-server/service/registry/index.js +108 -0
- package/dist-server/service/registry/index.js.map +1 -0
- package/dist-server/service/types/index.d.ts +117 -0
- package/dist-server/service/types/index.js +3 -0
- package/dist-server/service/types/index.js.map +1 -0
- package/dist-server/service/types/registry.d.ts +175 -0
- package/dist-server/service/types/registry.js +3 -0
- package/dist-server/service/types/registry.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -0
- package/package.json +34 -0
- package/server/index.ts +7 -0
- package/server/service/adapters/dxf-adapter.ts +217 -0
- package/server/service/ai/types.ts +284 -0
- package/server/service/converters/generic-to.ts +98 -0
- package/server/service/import-session/import-session-resolver.ts +113 -0
- package/server/service/import-session/import-session.ts +141 -0
- package/server/service/import-session/import-worker.ts +88 -0
- package/server/service/import-session/index.ts +8 -0
- package/server/service/index.ts +96 -0
- package/server/service/pipeline/index.ts +89 -0
- package/server/service/pipeline/stage2-mapping.ts +195 -0
- package/server/service/pipeline/stage3-board.ts +114 -0
- package/server/service/pipeline/stage4-binding.ts +31 -0
- package/server/service/registry/index.ts +127 -0
- package/server/service/types/index.ts +133 -0
- package/server/service/types/registry.ts +186 -0
- package/things-factory.config.js +1 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.convertGenericTo = convertGenericTo;
|
|
4
|
+
exports.revertToGeneric = revertToGeneric;
|
|
5
|
+
/**
|
|
6
|
+
* GenericObject ↔ 도메인 컴포넌트 변환.
|
|
7
|
+
*
|
|
8
|
+
* Import 시 매칭되지 않은 entity 는 GenericObject 로 보드에 배치된다.
|
|
9
|
+
* 사용자가 사후에 모델러 UI 에서 "Convert to Stocker" 등 액션으로 호출.
|
|
10
|
+
*
|
|
11
|
+
* 변환 시:
|
|
12
|
+
* - 좌표/크기/회전 보존
|
|
13
|
+
* - source (원본 entity attribs) 보존
|
|
14
|
+
* - 등록된 ImportRule.build 의 defaultState / idStrategy 적용
|
|
15
|
+
*/
|
|
16
|
+
const index_js_1 = require("../registry/index.js");
|
|
17
|
+
const stage2_mapping_js_1 = require("../pipeline/stage2-mapping.js");
|
|
18
|
+
/**
|
|
19
|
+
* GenericObject 를 지정된 도메인 type 으로 변환한다.
|
|
20
|
+
* 기존 좌표/크기/회전/source 보존.
|
|
21
|
+
*/
|
|
22
|
+
function convertGenericTo(generic, toType, options = {}) {
|
|
23
|
+
if (generic.type !== stage2_mapping_js_1.GENERIC_OBJECT_TYPE) {
|
|
24
|
+
throw new Error(`Source component is not GenericObject (type=${generic.type})`);
|
|
25
|
+
}
|
|
26
|
+
const rules = (0, index_js_1.getImportRules)(options.scopes);
|
|
27
|
+
const rule = rules.find(r => r.componentType === toType);
|
|
28
|
+
const category = rule?.category ?? 'unknown';
|
|
29
|
+
const newId = options.id ?? generic.id;
|
|
30
|
+
const preset = (0, index_js_1.find3DPreset)(toType, category, options.scopes);
|
|
31
|
+
const threeD = {
|
|
32
|
+
geometry: preset.geometry,
|
|
33
|
+
...(preset.material && { material: preset.material }),
|
|
34
|
+
origin: preset.origin ?? 'bottom'
|
|
35
|
+
};
|
|
36
|
+
const { strokeStyle, strokeDashArray, fillStyle, label, ...preservedState } = generic;
|
|
37
|
+
return {
|
|
38
|
+
...preservedState,
|
|
39
|
+
category,
|
|
40
|
+
type: toType,
|
|
41
|
+
id: newId,
|
|
42
|
+
threeD,
|
|
43
|
+
...(rule?.build.defaultState || {}),
|
|
44
|
+
/**
|
|
45
|
+
* 변환 추적 — 되돌리기 가능하게 source 보존.
|
|
46
|
+
* `convertedFrom` 키는 board-modeller UI 에서 "되돌리기" 액션 활성화 단서.
|
|
47
|
+
*/
|
|
48
|
+
convertedFrom: {
|
|
49
|
+
type: stage2_mapping_js_1.GENERIC_OBJECT_TYPE,
|
|
50
|
+
previousState: { strokeStyle, strokeDashArray, fillStyle, label }
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 변환된 컴포넌트를 다시 GenericObject 로 되돌린다.
|
|
56
|
+
* convertedFrom 정보가 있어야 가능.
|
|
57
|
+
*/
|
|
58
|
+
function revertToGeneric(component, options = {}) {
|
|
59
|
+
const previous = component.convertedFrom?.previousState;
|
|
60
|
+
if (!previous) {
|
|
61
|
+
throw new Error('이 컴포넌트는 GenericObject 에서 변환된 것이 아닙니다 (convertedFrom 없음).');
|
|
62
|
+
}
|
|
63
|
+
const preset = (0, index_js_1.find3DPreset)(stage2_mapping_js_1.GENERIC_OBJECT_TYPE, 'unknown', options.scopes);
|
|
64
|
+
const threeD = {
|
|
65
|
+
geometry: preset.geometry,
|
|
66
|
+
...(preset.material && { material: preset.material }),
|
|
67
|
+
origin: preset.origin ?? 'bottom'
|
|
68
|
+
};
|
|
69
|
+
const { convertedFrom, ...rest } = component;
|
|
70
|
+
return {
|
|
71
|
+
...rest,
|
|
72
|
+
category: 'unknown',
|
|
73
|
+
type: stage2_mapping_js_1.GENERIC_OBJECT_TYPE,
|
|
74
|
+
threeD,
|
|
75
|
+
...previous
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=generic-to.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generic-to.js","sourceRoot":"","sources":["../../../server/service/converters/generic-to.ts"],"names":[],"mappings":";;AA0BA,4CAwCC;AAMD,0CAyBC;AAjGD;;;;;;;;;;GAUG;AACH,mDAAmE;AAGnE,qEAAmE;AAQnE;;;GAGG;AACH,SAAgB,gBAAgB,CAC9B,OAAuB,EACvB,MAAc,EACd,UAA0B,EAAE;IAE5B,IAAI,OAAO,CAAC,IAAI,KAAK,uCAAmB,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CAAC,+CAA+C,OAAO,CAAC,IAAI,GAAG,CAAC,CAAA;IACjF,CAAC;IAED,MAAM,KAAK,GAAG,IAAA,yBAAc,EAAC,OAAO,CAAC,MAAM,CAAC,CAAA;IAC5C,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,KAAK,MAAM,CAAC,CAAA;IAExD,MAAM,QAAQ,GAAsB,IAAI,EAAE,QAAQ,IAAI,SAAS,CAAA;IAC/D,MAAM,KAAK,GAAG,OAAO,CAAC,EAAE,IAAI,OAAO,CAAC,EAAE,CAAA;IAEtC,MAAM,MAAM,GAAG,IAAA,uBAAY,EAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;IAC7D,MAAM,MAAM,GAAG;QACb,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,GAAG,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC;QACrD,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,QAAQ;KAClC,CAAA;IAED,MAAM,EAAE,WAAW,EAAE,eAAe,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,cAAc,EAAE,GAAG,OAAO,CAAA;IAErF,OAAO;QACL,GAAG,cAAc;QACjB,QAAQ;QACR,IAAI,EAAE,MAAM;QACZ,EAAE,EAAE,KAAK;QACT,MAAM;QACN,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,YAAY,IAAI,EAAE,CAAC;QACnC;;;WAGG;QACH,aAAa,EAAE;YACb,IAAI,EAAE,uCAAmB;YACzB,aAAa,EAAE,EAAE,WAAW,EAAE,eAAe,EAAE,SAAS,EAAE,KAAK,EAAE;SAClE;KACF,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,SAAgB,eAAe,CAC7B,SAAyB,EACzB,UAA0B,EAAE;IAE5B,MAAM,QAAQ,GAAI,SAAiB,CAAC,aAAa,EAAE,aAAa,CAAA;IAChE,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAA;IAC7E,CAAC;IAED,MAAM,MAAM,GAAG,IAAA,uBAAY,EAAC,uCAAmB,EAAE,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;IAC3E,MAAM,MAAM,GAAG;QACb,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,GAAG,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC;QACrD,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,QAAQ;KAClC,CAAA;IAED,MAAM,EAAE,aAAa,EAAE,GAAG,IAAI,EAAE,GAAG,SAAgB,CAAA;IAEnD,OAAO;QACL,GAAG,IAAI;QACP,QAAQ,EAAE,SAAS;QACnB,IAAI,EAAE,uCAAmB;QACzB,MAAM;QACN,GAAG,QAAQ;KACZ,CAAA;AACH,CAAC","sourcesContent":["/**\n * GenericObject ↔ 도메인 컴포넌트 변환.\n *\n * Import 시 매칭되지 않은 entity 는 GenericObject 로 보드에 배치된다.\n * 사용자가 사후에 모델러 UI 에서 \"Convert to Stocker\" 등 액션으로 호출.\n *\n * 변환 시:\n * - 좌표/크기/회전 보존\n * - source (원본 entity attribs) 보존\n * - 등록된 ImportRule.build 의 defaultState / idStrategy 적용\n */\nimport { find3DPreset, getImportRules } from '../registry/index.js'\nimport type { BoardComponent } from '../pipeline/stage3-board.js'\nimport type { ComponentCategory } from '../types/registry.js'\nimport { GENERIC_OBJECT_TYPE } from '../pipeline/stage2-mapping.js'\n\nexport interface ConvertOptions {\n scopes?: string[]\n /** 새 컴포넌트의 ID. 미지정 시 ImportRule 의 idStrategy 적용 또는 자동 생성 */\n id?: string\n}\n\n/**\n * GenericObject 를 지정된 도메인 type 으로 변환한다.\n * 기존 좌표/크기/회전/source 보존.\n */\nexport function convertGenericTo(\n generic: BoardComponent,\n toType: string,\n options: ConvertOptions = {}\n): BoardComponent {\n if (generic.type !== GENERIC_OBJECT_TYPE) {\n throw new Error(`Source component is not GenericObject (type=${generic.type})`)\n }\n\n const rules = getImportRules(options.scopes)\n const rule = rules.find(r => r.componentType === toType)\n\n const category: ComponentCategory = rule?.category ?? 'unknown'\n const newId = options.id ?? generic.id\n\n const preset = find3DPreset(toType, category, options.scopes)\n const threeD = {\n geometry: preset.geometry,\n ...(preset.material && { material: preset.material }),\n origin: preset.origin ?? 'bottom'\n }\n\n const { strokeStyle, strokeDashArray, fillStyle, label, ...preservedState } = generic\n\n return {\n ...preservedState,\n category,\n type: toType,\n id: newId,\n threeD,\n ...(rule?.build.defaultState || {}),\n /**\n * 변환 추적 — 되돌리기 가능하게 source 보존.\n * `convertedFrom` 키는 board-modeller UI 에서 \"되돌리기\" 액션 활성화 단서.\n */\n convertedFrom: {\n type: GENERIC_OBJECT_TYPE,\n previousState: { strokeStyle, strokeDashArray, fillStyle, label }\n }\n }\n}\n\n/**\n * 변환된 컴포넌트를 다시 GenericObject 로 되돌린다.\n * convertedFrom 정보가 있어야 가능.\n */\nexport function revertToGeneric(\n component: BoardComponent,\n options: ConvertOptions = {}\n): BoardComponent {\n const previous = (component as any).convertedFrom?.previousState\n if (!previous) {\n throw new Error('이 컴포넌트는 GenericObject 에서 변환된 것이 아닙니다 (convertedFrom 없음).')\n }\n\n const preset = find3DPreset(GENERIC_OBJECT_TYPE, 'unknown', options.scopes)\n const threeD = {\n geometry: preset.geometry,\n ...(preset.material && { material: preset.material }),\n origin: preset.origin ?? 'bottom'\n }\n\n const { convertedFrom, ...rest } = component as any\n\n return {\n ...rest,\n category: 'unknown',\n type: GENERIC_OBJECT_TYPE,\n threeD,\n ...previous\n }\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import '@things-factory/auth-base';
|
|
2
|
+
import { ImportSession } from './import-session.js';
|
|
3
|
+
declare class ImportBoardAsyncInput {
|
|
4
|
+
attachmentId: string;
|
|
5
|
+
chatSessionId?: string;
|
|
6
|
+
scopes?: string[];
|
|
7
|
+
normalizeOrigin?: boolean;
|
|
8
|
+
flipY?: boolean;
|
|
9
|
+
scale?: number;
|
|
10
|
+
applyBinding?: boolean;
|
|
11
|
+
parseOptions?: any;
|
|
12
|
+
}
|
|
13
|
+
export declare class ImportSessionResolver {
|
|
14
|
+
importSession(id: string, context: ResolverContext): Promise<ImportSession | null>;
|
|
15
|
+
importSessionsByChatSession(chatSessionId: string, context: ResolverContext): Promise<ImportSession[]>;
|
|
16
|
+
importBoardAsync(input: ImportBoardAsyncInput, context: ResolverContext): Promise<ImportSession>;
|
|
17
|
+
}
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ImportSessionResolver = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
/**
|
|
6
|
+
* ImportSession resolver + 비동기 worker.
|
|
7
|
+
*
|
|
8
|
+
* Mutation:
|
|
9
|
+
* - importBoardAsync(attachmentId, options): ImportSession (즉시 반환, 백그라운드에서 처리)
|
|
10
|
+
*
|
|
11
|
+
* Query:
|
|
12
|
+
* - importSession(id): ImportSession (진행 상황 polling)
|
|
13
|
+
*
|
|
14
|
+
* 비동기 worker 는 단일 프로세스 내 setImmediate 기반. 추후 큐(Redis/BullMQ) 로 이전 가능.
|
|
15
|
+
*/
|
|
16
|
+
const type_graphql_1 = require("type-graphql");
|
|
17
|
+
const graphql_scalars_1 = require("graphql-scalars");
|
|
18
|
+
require("@things-factory/auth-base");
|
|
19
|
+
const shell_1 = require("@things-factory/shell");
|
|
20
|
+
const import_session_js_1 = require("./import-session.js");
|
|
21
|
+
const import_worker_js_1 = require("./import-worker.js");
|
|
22
|
+
let ImportBoardAsyncInput = class ImportBoardAsyncInput {
|
|
23
|
+
};
|
|
24
|
+
tslib_1.__decorate([
|
|
25
|
+
(0, type_graphql_1.Field)({ description: 'Attachment id of the source drawing file (uploaded via attachment-base).' }),
|
|
26
|
+
tslib_1.__metadata("design:type", String)
|
|
27
|
+
], ImportBoardAsyncInput.prototype, "attachmentId", void 0);
|
|
28
|
+
tslib_1.__decorate([
|
|
29
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'ChatSession id when triggered from board-ai chat.' }),
|
|
30
|
+
tslib_1.__metadata("design:type", String)
|
|
31
|
+
], ImportBoardAsyncInput.prototype, "chatSessionId", void 0);
|
|
32
|
+
tslib_1.__decorate([
|
|
33
|
+
(0, type_graphql_1.Field)(() => [String], { nullable: true, description: 'Registry scopes to use (board-import).' }),
|
|
34
|
+
tslib_1.__metadata("design:type", Array)
|
|
35
|
+
], ImportBoardAsyncInput.prototype, "scopes", void 0);
|
|
36
|
+
tslib_1.__decorate([
|
|
37
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Normalize coordinates so minX,minY=0' }),
|
|
38
|
+
tslib_1.__metadata("design:type", Boolean)
|
|
39
|
+
], ImportBoardAsyncInput.prototype, "normalizeOrigin", void 0);
|
|
40
|
+
tslib_1.__decorate([
|
|
41
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Flip Y axis (CAD Y-up → scene Y-down).' }),
|
|
42
|
+
tslib_1.__metadata("design:type", Boolean)
|
|
43
|
+
], ImportBoardAsyncInput.prototype, "flipY", void 0);
|
|
44
|
+
tslib_1.__decorate([
|
|
45
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Scale factor (e.g. 1 for mm:1unit).' }),
|
|
46
|
+
tslib_1.__metadata("design:type", Number)
|
|
47
|
+
], ImportBoardAsyncInput.prototype, "scale", void 0);
|
|
48
|
+
tslib_1.__decorate([
|
|
49
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Apply Stage 4 (data binding).' }),
|
|
50
|
+
tslib_1.__metadata("design:type", Boolean)
|
|
51
|
+
], ImportBoardAsyncInput.prototype, "applyBinding", void 0);
|
|
52
|
+
tslib_1.__decorate([
|
|
53
|
+
(0, type_graphql_1.Field)(() => graphql_scalars_1.GraphQLJSON, { nullable: true, description: 'Adapter parse options (excludeLayers, maxEntities, ...).' }),
|
|
54
|
+
tslib_1.__metadata("design:type", Object)
|
|
55
|
+
], ImportBoardAsyncInput.prototype, "parseOptions", void 0);
|
|
56
|
+
ImportBoardAsyncInput = tslib_1.__decorate([
|
|
57
|
+
(0, type_graphql_1.InputType)()
|
|
58
|
+
], ImportBoardAsyncInput);
|
|
59
|
+
let ImportSessionResolver = class ImportSessionResolver {
|
|
60
|
+
async importSession(id, context) {
|
|
61
|
+
const { domain } = context.state;
|
|
62
|
+
return (await (0, shell_1.getRepository)(import_session_js_1.ImportSession).findOneBy({ id, domain: { id: domain.id } })) ?? null;
|
|
63
|
+
}
|
|
64
|
+
async importSessionsByChatSession(chatSessionId, context) {
|
|
65
|
+
const { domain } = context.state;
|
|
66
|
+
return await (0, shell_1.getRepository)(import_session_js_1.ImportSession).find({
|
|
67
|
+
where: { chatSessionId, domain: { id: domain.id } },
|
|
68
|
+
order: { createdAt: 'DESC' },
|
|
69
|
+
take: 50
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async importBoardAsync(input, context) {
|
|
73
|
+
const { domain, user } = context.state;
|
|
74
|
+
const repo = (0, shell_1.getRepository)(import_session_js_1.ImportSession);
|
|
75
|
+
const session = await repo.save({
|
|
76
|
+
domain,
|
|
77
|
+
attachmentId: input.attachmentId,
|
|
78
|
+
chatSessionId: input.chatSessionId,
|
|
79
|
+
status: 'queued',
|
|
80
|
+
progress: 0,
|
|
81
|
+
message: 'Queued',
|
|
82
|
+
optionsJson: JSON.stringify({
|
|
83
|
+
scopes: input.scopes,
|
|
84
|
+
normalizeOrigin: input.normalizeOrigin,
|
|
85
|
+
flipY: input.flipY,
|
|
86
|
+
scale: input.scale,
|
|
87
|
+
applyBinding: input.applyBinding,
|
|
88
|
+
parseOptions: input.parseOptions
|
|
89
|
+
}),
|
|
90
|
+
creator: user
|
|
91
|
+
});
|
|
92
|
+
// 비동기 worker 시작 — 응답은 이미 즉시 반환됨
|
|
93
|
+
setImmediate(() => {
|
|
94
|
+
(0, import_worker_js_1.runImportJob)(session.id).catch(err => {
|
|
95
|
+
// worker 내부에서 처리되지만 안전망
|
|
96
|
+
console.error('[board-import] runImportJob unhandled:', err);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
return session;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
exports.ImportSessionResolver = ImportSessionResolver;
|
|
103
|
+
tslib_1.__decorate([
|
|
104
|
+
(0, type_graphql_1.Query)(() => import_session_js_1.ImportSession, { nullable: true, description: 'Get import session progress.' }),
|
|
105
|
+
(0, type_graphql_1.Directive)('@privilege(category: "board-import", privilege: "query")'),
|
|
106
|
+
tslib_1.__param(0, (0, type_graphql_1.Arg)('id')),
|
|
107
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
108
|
+
tslib_1.__metadata("design:type", Function),
|
|
109
|
+
tslib_1.__metadata("design:paramtypes", [String, Object]),
|
|
110
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
111
|
+
], ImportSessionResolver.prototype, "importSession", null);
|
|
112
|
+
tslib_1.__decorate([
|
|
113
|
+
(0, type_graphql_1.Query)(() => [import_session_js_1.ImportSession], { description: 'List recent import sessions for a chat session.' }),
|
|
114
|
+
(0, type_graphql_1.Directive)('@privilege(category: "board-import", privilege: "query")'),
|
|
115
|
+
tslib_1.__param(0, (0, type_graphql_1.Arg)('chatSessionId')),
|
|
116
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
117
|
+
tslib_1.__metadata("design:type", Function),
|
|
118
|
+
tslib_1.__metadata("design:paramtypes", [String, Object]),
|
|
119
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
120
|
+
], ImportSessionResolver.prototype, "importSessionsByChatSession", null);
|
|
121
|
+
tslib_1.__decorate([
|
|
122
|
+
(0, type_graphql_1.Mutation)(() => import_session_js_1.ImportSession, {
|
|
123
|
+
description: '도면 → 보드 변환을 비동기로 시작. 즉시 ImportSession 반환, 백그라운드에서 처리.'
|
|
124
|
+
}),
|
|
125
|
+
(0, type_graphql_1.Directive)('@privilege(category: "board-import", privilege: "mutation")'),
|
|
126
|
+
tslib_1.__param(0, (0, type_graphql_1.Arg)('input')),
|
|
127
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
128
|
+
tslib_1.__metadata("design:type", Function),
|
|
129
|
+
tslib_1.__metadata("design:paramtypes", [ImportBoardAsyncInput, Object]),
|
|
130
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
131
|
+
], ImportSessionResolver.prototype, "importBoardAsync", null);
|
|
132
|
+
exports.ImportSessionResolver = ImportSessionResolver = tslib_1.__decorate([
|
|
133
|
+
(0, type_graphql_1.Resolver)()
|
|
134
|
+
], ImportSessionResolver);
|
|
135
|
+
//# sourceMappingURL=import-session-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import-session-resolver.js","sourceRoot":"","sources":["../../../server/service/import-session/import-session-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ImportSession resolver + 비동기 worker.\n *\n * Mutation:\n * - importBoardAsync(attachmentId, options): ImportSession (즉시 반환, 백그라운드에서 처리)\n *\n * Query:\n * - importSession(id): ImportSession (진행 상황 polling)\n *\n * 비동기 worker 는 단일 프로세스 내 setImmediate 기반. 추후 큐(Redis/BullMQ) 로 이전 가능.\n */\nimport { Resolver, Query, Mutation, Arg, Ctx, Directive, Field, InputType } from 'type-graphql'\nimport { GraphQLJSON } from 'graphql-scalars'\nimport '@things-factory/auth-base'\n\nimport { getRepository } from '@things-factory/shell'\n\nimport { ImportSession } from './import-session.js'\nimport { runImportJob } from './import-worker.js'\n\n@InputType()\nclass ImportBoardAsyncInput {\n @Field({ description: 'Attachment id of the source drawing file (uploaded via attachment-base).' })\n attachmentId!: string\n\n @Field({ nullable: true, description: 'ChatSession id when triggered from board-ai chat.' })\n chatSessionId?: string\n\n @Field(() => [String], { nullable: true, description: 'Registry scopes to use (board-import).' })\n scopes?: string[]\n\n @Field({ nullable: true, description: 'Normalize coordinates so minX,minY=0' })\n normalizeOrigin?: boolean\n\n @Field({ nullable: true, description: 'Flip Y axis (CAD Y-up → scene Y-down).' })\n flipY?: boolean\n\n @Field({ nullable: true, description: 'Scale factor (e.g. 1 for mm:1unit).' })\n scale?: number\n\n @Field({ nullable: true, description: 'Apply Stage 4 (data binding).' })\n applyBinding?: boolean\n\n @Field(() => GraphQLJSON, { nullable: true, description: 'Adapter parse options (excludeLayers, maxEntities, ...).' })\n parseOptions?: any\n}\n\n@Resolver()\nexport class ImportSessionResolver {\n @Query(() => ImportSession, { nullable: true, description: 'Get import session progress.' })\n @Directive('@privilege(category: \"board-import\", privilege: \"query\")')\n async importSession(\n @Arg('id') id: string,\n @Ctx() context: ResolverContext\n ): Promise<ImportSession | null> {\n const { domain } = context.state\n return (await getRepository(ImportSession).findOneBy({ id, domain: { id: domain.id } })) ?? null\n }\n\n @Query(() => [ImportSession], { description: 'List recent import sessions for a chat session.' })\n @Directive('@privilege(category: \"board-import\", privilege: \"query\")')\n async importSessionsByChatSession(\n @Arg('chatSessionId') chatSessionId: string,\n @Ctx() context: ResolverContext\n ): Promise<ImportSession[]> {\n const { domain } = context.state\n return await getRepository(ImportSession).find({\n where: { chatSessionId, domain: { id: domain.id } },\n order: { createdAt: 'DESC' },\n take: 50\n })\n }\n\n @Mutation(() => ImportSession, {\n description: '도면 → 보드 변환을 비동기로 시작. 즉시 ImportSession 반환, 백그라운드에서 처리.'\n })\n @Directive('@privilege(category: \"board-import\", privilege: \"mutation\")')\n async importBoardAsync(\n @Arg('input') input: ImportBoardAsyncInput,\n @Ctx() context: ResolverContext\n ): Promise<ImportSession> {\n const { domain, user } = context.state\n const repo = getRepository(ImportSession)\n\n const session = await repo.save({\n domain,\n attachmentId: input.attachmentId,\n chatSessionId: input.chatSessionId,\n status: 'queued',\n progress: 0,\n message: 'Queued',\n optionsJson: JSON.stringify({\n scopes: input.scopes,\n normalizeOrigin: input.normalizeOrigin,\n flipY: input.flipY,\n scale: input.scale,\n applyBinding: input.applyBinding,\n parseOptions: input.parseOptions\n }),\n creator: user\n } as any)\n\n // 비동기 worker 시작 — 응답은 이미 즉시 반환됨\n setImmediate(() => {\n runImportJob(session.id!).catch(err => {\n // worker 내부에서 처리되지만 안전망\n console.error('[board-import] runImportJob unhandled:', err)\n })\n })\n\n return session\n }\n}\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Domain } from '@things-factory/shell';
|
|
2
|
+
import { User } from '@things-factory/auth-base';
|
|
3
|
+
export declare class ImportSession {
|
|
4
|
+
readonly id?: string;
|
|
5
|
+
domain?: Domain;
|
|
6
|
+
domainId?: string;
|
|
7
|
+
/** Attachment.id (도면 파일). attachment-base 의존이지만 직접 ManyToOne 대신 ID 만 보유. */
|
|
8
|
+
attachmentId: string;
|
|
9
|
+
/** 어느 ChatSession 에서 발동됐는지 (board-ai 와 연동). 없으면 직접 호출. */
|
|
10
|
+
chatSessionId?: string;
|
|
11
|
+
/** 'queued' | 'parsing' | 'mapping' | 'assembling' | 'binding' | 'completed' | 'failed' */
|
|
12
|
+
status: string;
|
|
13
|
+
/** 0..100 */
|
|
14
|
+
progress: number;
|
|
15
|
+
/** 현재 단계 설명 또는 에러 메시지 */
|
|
16
|
+
message?: string;
|
|
17
|
+
/** import 옵션 (scopes, parseOptions, normalizeOrigin 등) */
|
|
18
|
+
optionsJson?: string;
|
|
19
|
+
get options(): any;
|
|
20
|
+
/** 결과: { boardModel, mapping: { stats, warnings } } */
|
|
21
|
+
resultJson?: string;
|
|
22
|
+
get result(): any;
|
|
23
|
+
totalEntities?: number;
|
|
24
|
+
creator?: User;
|
|
25
|
+
createdAt?: Date;
|
|
26
|
+
updatedAt?: Date;
|
|
27
|
+
completedAt?: Date;
|
|
28
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ImportSession = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
/**
|
|
6
|
+
* ImportSession — 도면(Attachment) → BoardModel 변환 작업 단위.
|
|
7
|
+
*
|
|
8
|
+
* 비동기 워크플로우:
|
|
9
|
+
* 1) 클라이언트가 importBoardAsync(attachmentId) 호출 → ImportSession 즉시 반환 (status:'queued')
|
|
10
|
+
* 2) 백그라운드 worker 가 attachment 본문 → importBoard() 실행 → status 갱신
|
|
11
|
+
* 3) 클라이언트는 polling 또는 subscription 으로 진행 상황 추적
|
|
12
|
+
* 4) status='completed' 시 result 에 BoardModel + stats + warnings 포함
|
|
13
|
+
*
|
|
14
|
+
* board-ai 의 ChatSession 과 연결 가능 (chatSessionId) — chat 안에서 자동 발동되면 진행 메시지가
|
|
15
|
+
* 자동으로 ChatMessage(role:'system') 으로 기록되어 AI 컨텍스트에 흐름.
|
|
16
|
+
*/
|
|
17
|
+
const typeorm_1 = require("typeorm");
|
|
18
|
+
const type_graphql_1 = require("type-graphql");
|
|
19
|
+
const graphql_scalars_1 = require("graphql-scalars");
|
|
20
|
+
const shell_1 = require("@things-factory/shell");
|
|
21
|
+
const auth_base_1 = require("@things-factory/auth-base");
|
|
22
|
+
const env_1 = require("@things-factory/env");
|
|
23
|
+
const ORMCONFIG = env_1.config.get('ormconfig', {});
|
|
24
|
+
const DATABASE_TYPE = ORMCONFIG.type;
|
|
25
|
+
let ImportSession = class ImportSession {
|
|
26
|
+
get options() {
|
|
27
|
+
try {
|
|
28
|
+
return this.optionsJson ? JSON.parse(this.optionsJson) : null;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
get result() {
|
|
35
|
+
try {
|
|
36
|
+
return this.resultJson ? JSON.parse(this.resultJson) : null;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
exports.ImportSession = ImportSession;
|
|
44
|
+
tslib_1.__decorate([
|
|
45
|
+
(0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
|
|
46
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.ID, { nullable: true }),
|
|
47
|
+
tslib_1.__metadata("design:type", String)
|
|
48
|
+
], ImportSession.prototype, "id", void 0);
|
|
49
|
+
tslib_1.__decorate([
|
|
50
|
+
(0, typeorm_1.ManyToOne)(type => shell_1.Domain),
|
|
51
|
+
(0, type_graphql_1.Field)(type => shell_1.Domain, { nullable: true }),
|
|
52
|
+
tslib_1.__metadata("design:type", shell_1.Domain)
|
|
53
|
+
], ImportSession.prototype, "domain", void 0);
|
|
54
|
+
tslib_1.__decorate([
|
|
55
|
+
(0, typeorm_1.RelationId)((session) => session.domain),
|
|
56
|
+
tslib_1.__metadata("design:type", String)
|
|
57
|
+
], ImportSession.prototype, "domainId", void 0);
|
|
58
|
+
tslib_1.__decorate([
|
|
59
|
+
(0, typeorm_1.Column)(),
|
|
60
|
+
(0, type_graphql_1.Field)({ description: 'Attachment id (the source drawing file)' }),
|
|
61
|
+
tslib_1.__metadata("design:type", String)
|
|
62
|
+
], ImportSession.prototype, "attachmentId", void 0);
|
|
63
|
+
tslib_1.__decorate([
|
|
64
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
65
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'ChatSession id when triggered from chat (board-ai).' }),
|
|
66
|
+
tslib_1.__metadata("design:type", String)
|
|
67
|
+
], ImportSession.prototype, "chatSessionId", void 0);
|
|
68
|
+
tslib_1.__decorate([
|
|
69
|
+
(0, typeorm_1.Column)({ type: 'varchar', length: 32, default: 'queued' }),
|
|
70
|
+
(0, type_graphql_1.Field)({ description: 'queued | parsing | mapping | assembling | binding | completed | failed' }),
|
|
71
|
+
tslib_1.__metadata("design:type", String)
|
|
72
|
+
], ImportSession.prototype, "status", void 0);
|
|
73
|
+
tslib_1.__decorate([
|
|
74
|
+
(0, typeorm_1.Column)({ type: 'float', default: 0 }),
|
|
75
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Float, { description: 'Progress percentage 0..100' }),
|
|
76
|
+
tslib_1.__metadata("design:type", Number)
|
|
77
|
+
], ImportSession.prototype, "progress", void 0);
|
|
78
|
+
tslib_1.__decorate([
|
|
79
|
+
(0, typeorm_1.Column)({ nullable: true, type: 'text' }),
|
|
80
|
+
(0, type_graphql_1.Field)({ nullable: true }),
|
|
81
|
+
tslib_1.__metadata("design:type", String)
|
|
82
|
+
], ImportSession.prototype, "message", void 0);
|
|
83
|
+
tslib_1.__decorate([
|
|
84
|
+
(0, typeorm_1.Column)({
|
|
85
|
+
nullable: true,
|
|
86
|
+
type: DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'
|
|
87
|
+
? 'longtext'
|
|
88
|
+
: DATABASE_TYPE == 'oracle'
|
|
89
|
+
? 'clob'
|
|
90
|
+
: DATABASE_TYPE == 'mssql'
|
|
91
|
+
? 'nvarchar'
|
|
92
|
+
: 'text',
|
|
93
|
+
length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined
|
|
94
|
+
}),
|
|
95
|
+
tslib_1.__metadata("design:type", String)
|
|
96
|
+
], ImportSession.prototype, "optionsJson", void 0);
|
|
97
|
+
tslib_1.__decorate([
|
|
98
|
+
(0, type_graphql_1.Field)(type => graphql_scalars_1.GraphQLJSON, { nullable: true, description: 'Options used (parsed JSON).' }),
|
|
99
|
+
tslib_1.__metadata("design:type", Object),
|
|
100
|
+
tslib_1.__metadata("design:paramtypes", [])
|
|
101
|
+
], ImportSession.prototype, "options", null);
|
|
102
|
+
tslib_1.__decorate([
|
|
103
|
+
(0, typeorm_1.Column)({
|
|
104
|
+
nullable: true,
|
|
105
|
+
type: DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'
|
|
106
|
+
? 'longtext'
|
|
107
|
+
: DATABASE_TYPE == 'oracle'
|
|
108
|
+
? 'clob'
|
|
109
|
+
: DATABASE_TYPE == 'mssql'
|
|
110
|
+
? 'nvarchar'
|
|
111
|
+
: 'text',
|
|
112
|
+
length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined
|
|
113
|
+
}),
|
|
114
|
+
tslib_1.__metadata("design:type", String)
|
|
115
|
+
], ImportSession.prototype, "resultJson", void 0);
|
|
116
|
+
tslib_1.__decorate([
|
|
117
|
+
(0, type_graphql_1.Field)(type => graphql_scalars_1.GraphQLJSON, { nullable: true, description: 'Result (parsed JSON): { boardModel, stats, warnings }.' }),
|
|
118
|
+
tslib_1.__metadata("design:type", Object),
|
|
119
|
+
tslib_1.__metadata("design:paramtypes", [])
|
|
120
|
+
], ImportSession.prototype, "result", null);
|
|
121
|
+
tslib_1.__decorate([
|
|
122
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
123
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { nullable: true, description: 'Total entities counted (when known).' }),
|
|
124
|
+
tslib_1.__metadata("design:type", Number)
|
|
125
|
+
], ImportSession.prototype, "totalEntities", void 0);
|
|
126
|
+
tslib_1.__decorate([
|
|
127
|
+
(0, typeorm_1.ManyToOne)(type => auth_base_1.User),
|
|
128
|
+
(0, type_graphql_1.Field)(type => auth_base_1.User, { nullable: true }),
|
|
129
|
+
tslib_1.__metadata("design:type", auth_base_1.User)
|
|
130
|
+
], ImportSession.prototype, "creator", void 0);
|
|
131
|
+
tslib_1.__decorate([
|
|
132
|
+
(0, typeorm_1.CreateDateColumn)(),
|
|
133
|
+
(0, type_graphql_1.Field)({ nullable: true }),
|
|
134
|
+
tslib_1.__metadata("design:type", Date)
|
|
135
|
+
], ImportSession.prototype, "createdAt", void 0);
|
|
136
|
+
tslib_1.__decorate([
|
|
137
|
+
(0, typeorm_1.UpdateDateColumn)(),
|
|
138
|
+
(0, type_graphql_1.Field)({ nullable: true }),
|
|
139
|
+
tslib_1.__metadata("design:type", Date)
|
|
140
|
+
], ImportSession.prototype, "updatedAt", void 0);
|
|
141
|
+
tslib_1.__decorate([
|
|
142
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
143
|
+
(0, type_graphql_1.Field)({ nullable: true }),
|
|
144
|
+
tslib_1.__metadata("design:type", Date)
|
|
145
|
+
], ImportSession.prototype, "completedAt", void 0);
|
|
146
|
+
exports.ImportSession = ImportSession = tslib_1.__decorate([
|
|
147
|
+
(0, typeorm_1.Entity)(),
|
|
148
|
+
(0, typeorm_1.Index)('ix_import_session_1', (session) => [session.domain, session.createdAt]),
|
|
149
|
+
(0, typeorm_1.Index)('ix_import_session_2', (session) => [session.chatSessionId]),
|
|
150
|
+
(0, type_graphql_1.ObjectType)({ description: '도면 → 보드 변환 작업의 영속 단위 (비동기 진행상태 추적).' })
|
|
151
|
+
], ImportSession);
|
|
152
|
+
//# sourceMappingURL=import-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import-session.js","sourceRoot":"","sources":["../../../server/service/import-session/import-session.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ImportSession — 도면(Attachment) → BoardModel 변환 작업 단위.\n *\n * 비동기 워크플로우:\n * 1) 클라이언트가 importBoardAsync(attachmentId) 호출 → ImportSession 즉시 반환 (status:'queued')\n * 2) 백그라운드 worker 가 attachment 본문 → importBoard() 실행 → status 갱신\n * 3) 클라이언트는 polling 또는 subscription 으로 진행 상황 추적\n * 4) status='completed' 시 result 에 BoardModel + stats + warnings 포함\n *\n * board-ai 의 ChatSession 과 연결 가능 (chatSessionId) — chat 안에서 자동 발동되면 진행 메시지가\n * 자동으로 ChatMessage(role:'system') 으로 기록되어 AI 컨텍스트에 흐름.\n */\nimport {\n Column,\n CreateDateColumn,\n Entity,\n Index,\n ManyToOne,\n PrimaryGeneratedColumn,\n RelationId,\n UpdateDateColumn\n} from 'typeorm'\nimport { Field, Float, ID, Int, ObjectType } from 'type-graphql'\nimport { GraphQLJSON } from 'graphql-scalars'\n\nimport { Domain } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\nimport { config } from '@things-factory/env'\n\nconst ORMCONFIG = config.get('ormconfig', {})\nconst DATABASE_TYPE = ORMCONFIG.type\n\n@Entity()\n@Index('ix_import_session_1', (session: ImportSession) => [session.domain, session.createdAt])\n@Index('ix_import_session_2', (session: ImportSession) => [session.chatSessionId])\n@ObjectType({ description: '도면 → 보드 변환 작업의 영속 단위 (비동기 진행상태 추적).' })\nexport class ImportSession {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { nullable: true })\n readonly id?: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true })\n domain?: Domain\n\n @RelationId((session: ImportSession) => session.domain)\n domainId?: string\n\n /** Attachment.id (도면 파일). attachment-base 의존이지만 직접 ManyToOne 대신 ID 만 보유. */\n @Column()\n @Field({ description: 'Attachment id (the source drawing file)' })\n attachmentId!: string\n\n /** 어느 ChatSession 에서 발동됐는지 (board-ai 와 연동). 없으면 직접 호출. */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'ChatSession id when triggered from chat (board-ai).' })\n chatSessionId?: string\n\n /** 'queued' | 'parsing' | 'mapping' | 'assembling' | 'binding' | 'completed' | 'failed' */\n @Column({ type: 'varchar', length: 32, default: 'queued' })\n @Field({ description: 'queued | parsing | mapping | assembling | binding | completed | failed' })\n status!: string\n\n /** 0..100 */\n @Column({ type: 'float', default: 0 })\n @Field(type => Float, { description: 'Progress percentage 0..100' })\n progress!: number\n\n /** 현재 단계 설명 또는 에러 메시지 */\n @Column({ nullable: true, type: 'text' })\n @Field({ nullable: true })\n message?: string\n\n /** import 옵션 (scopes, parseOptions, normalizeOrigin 등) */\n @Column({\n nullable: true,\n type:\n DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? 'longtext'\n : DATABASE_TYPE == 'oracle'\n ? 'clob'\n : DATABASE_TYPE == 'mssql'\n ? 'nvarchar'\n : 'text',\n length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined\n })\n optionsJson?: string\n\n @Field(type => GraphQLJSON, { nullable: true, description: 'Options used (parsed JSON).' })\n get options(): any {\n try {\n return this.optionsJson ? JSON.parse(this.optionsJson) : null\n } catch {\n return null\n }\n }\n\n /** 결과: { boardModel, mapping: { stats, warnings } } */\n @Column({\n nullable: true,\n type:\n DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? 'longtext'\n : DATABASE_TYPE == 'oracle'\n ? 'clob'\n : DATABASE_TYPE == 'mssql'\n ? 'nvarchar'\n : 'text',\n length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined\n })\n resultJson?: string\n\n @Field(type => GraphQLJSON, { nullable: true, description: 'Result (parsed JSON): { boardModel, stats, warnings }.' })\n get result(): any {\n try {\n return this.resultJson ? JSON.parse(this.resultJson) : null\n } catch {\n return null\n }\n }\n\n @Column({ nullable: true })\n @Field(type => Int, { nullable: true, description: 'Total entities counted (when known).' })\n totalEntities?: number\n\n @ManyToOne(type => User)\n @Field(type => User, { nullable: true })\n creator?: User\n\n @CreateDateColumn()\n @Field({ nullable: true })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true })\n updatedAt?: Date\n\n @Column({ nullable: true })\n @Field({ nullable: true })\n completedAt?: Date\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runImportJob(sessionId: string): Promise<void>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runImportJob = runImportJob;
|
|
4
|
+
/**
|
|
5
|
+
* Background worker — ImportSession 처리.
|
|
6
|
+
*
|
|
7
|
+
* 흐름:
|
|
8
|
+
* 1) ImportSession 로드 (status: queued)
|
|
9
|
+
* 2) Attachment 본문 읽기 (attachment-base STORAGE.readFile)
|
|
10
|
+
* 3) importBoard() 실행 (단계별로 status/progress 갱신)
|
|
11
|
+
* 4) result 저장 + status='completed'
|
|
12
|
+
* 5) 실패 시 status='failed' + message
|
|
13
|
+
*
|
|
14
|
+
* 단일 프로세스 내 setImmediate 기반. 추후 외부 큐(Redis/BullMQ) 로 이전 가능.
|
|
15
|
+
*/
|
|
16
|
+
const shell_1 = require("@things-factory/shell");
|
|
17
|
+
const attachment_base_1 = require("@things-factory/attachment-base");
|
|
18
|
+
const attachment_base_2 = require("@things-factory/attachment-base");
|
|
19
|
+
const index_js_1 = require("../pipeline/index.js");
|
|
20
|
+
const import_session_js_1 = require("./import-session.js");
|
|
21
|
+
async function runImportJob(sessionId) {
|
|
22
|
+
const repo = (0, shell_1.getRepository)(import_session_js_1.ImportSession);
|
|
23
|
+
const session = await repo.findOneBy({ id: sessionId });
|
|
24
|
+
if (!session) {
|
|
25
|
+
console.error(`[board-import] ImportSession ${sessionId} not found`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
await update(repo, sessionId, { status: 'parsing', progress: 5, message: 'Reading attachment' });
|
|
30
|
+
const attachment = await (0, shell_1.getRepository)(attachment_base_1.Attachment).findOneBy({ id: session.attachmentId });
|
|
31
|
+
if (!attachment) {
|
|
32
|
+
throw new Error(`Attachment ${session.attachmentId} not found`);
|
|
33
|
+
}
|
|
34
|
+
// attachment-base STORAGE.readFile 으로 본문 로드 (Buffer)
|
|
35
|
+
const fileBuffer = attachment_base_2.STORAGE.readFile(attachment.path, null);
|
|
36
|
+
await update(repo, sessionId, { status: 'parsing', progress: 20, message: 'Parsing drawing' });
|
|
37
|
+
const opts = session.options || {};
|
|
38
|
+
const filename = attachment.name || attachment.path;
|
|
39
|
+
// importBoard 는 내부에서 stage 1~4 모두 수행. 단계별 progress 는 우선 거시적 업데이트.
|
|
40
|
+
await update(repo, sessionId, { status: 'mapping', progress: 50, message: 'Mapping entities' });
|
|
41
|
+
const result = await (0, index_js_1.importBoard)({
|
|
42
|
+
file: fileBuffer,
|
|
43
|
+
filename,
|
|
44
|
+
scopes: opts.scopes,
|
|
45
|
+
normalizeOrigin: opts.normalizeOrigin,
|
|
46
|
+
flipY: opts.flipY,
|
|
47
|
+
scale: opts.scale,
|
|
48
|
+
applyBinding: opts.applyBinding,
|
|
49
|
+
parseOptions: opts.parseOptions
|
|
50
|
+
});
|
|
51
|
+
await update(repo, sessionId, { status: 'assembling', progress: 80, message: 'Assembling board' });
|
|
52
|
+
const resultJson = JSON.stringify({
|
|
53
|
+
boardModel: result.boardModel,
|
|
54
|
+
stats: result.mapping.stats,
|
|
55
|
+
warnings: result.mapping.warnings
|
|
56
|
+
});
|
|
57
|
+
await update(repo, sessionId, {
|
|
58
|
+
status: 'completed',
|
|
59
|
+
progress: 100,
|
|
60
|
+
message: `Completed: ${result.mapping.stats.matched} matched, ${result.mapping.stats.generic} generic`,
|
|
61
|
+
totalEntities: result.mapping.stats.total,
|
|
62
|
+
resultJson,
|
|
63
|
+
completedAt: new Date()
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
const message = err?.message ?? String(err);
|
|
68
|
+
console.error(`[board-import] ImportSession ${sessionId} failed:`, err);
|
|
69
|
+
await update(repo, sessionId, {
|
|
70
|
+
status: 'failed',
|
|
71
|
+
message: `Failed: ${message}`,
|
|
72
|
+
completedAt: new Date()
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function update(repo, id, patch) {
|
|
77
|
+
await repo.update(id, patch);
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=import-worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import-worker.js","sourceRoot":"","sources":["../../../server/service/import-session/import-worker.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Background worker — ImportSession 처리.\n *\n * 흐름:\n * 1) ImportSession 로드 (status: queued)\n * 2) Attachment 본문 읽기 (attachment-base STORAGE.readFile)\n * 3) importBoard() 실행 (단계별로 status/progress 갱신)\n * 4) result 저장 + status='completed'\n * 5) 실패 시 status='failed' + message\n *\n * 단일 프로세스 내 setImmediate 기반. 추후 외부 큐(Redis/BullMQ) 로 이전 가능.\n */\nimport { getRepository } from '@things-factory/shell'\n\nimport { Attachment } from '@things-factory/attachment-base'\nimport { STORAGE } from '@things-factory/attachment-base'\n\nimport { importBoard } from '../pipeline/index.js'\nimport { ImportSession } from './import-session.js'\n\nexport async function runImportJob(sessionId: string): Promise<void> {\n const repo = getRepository(ImportSession)\n const session = await repo.findOneBy({ id: sessionId })\n if (!session) {\n console.error(`[board-import] ImportSession ${sessionId} not found`)\n return\n }\n\n try {\n await update(repo, sessionId, { status: 'parsing', progress: 5, message: 'Reading attachment' })\n\n const attachment = await getRepository(Attachment).findOneBy({ id: session.attachmentId })\n if (!attachment) {\n throw new Error(`Attachment ${session.attachmentId} not found`)\n }\n\n // attachment-base STORAGE.readFile 으로 본문 로드 (Buffer)\n const fileBuffer = STORAGE.readFile(attachment.path!, null) as Buffer\n\n await update(repo, sessionId, { status: 'parsing', progress: 20, message: 'Parsing drawing' })\n\n const opts = session.options || {}\n const filename = attachment.name || attachment.path\n\n // importBoard 는 내부에서 stage 1~4 모두 수행. 단계별 progress 는 우선 거시적 업데이트.\n await update(repo, sessionId, { status: 'mapping', progress: 50, message: 'Mapping entities' })\n\n const result = await importBoard({\n file: fileBuffer,\n filename,\n scopes: opts.scopes,\n normalizeOrigin: opts.normalizeOrigin,\n flipY: opts.flipY,\n scale: opts.scale,\n applyBinding: opts.applyBinding,\n parseOptions: opts.parseOptions\n })\n\n await update(repo, sessionId, { status: 'assembling', progress: 80, message: 'Assembling board' })\n\n const resultJson = JSON.stringify({\n boardModel: result.boardModel,\n stats: result.mapping.stats,\n warnings: result.mapping.warnings\n })\n\n await update(repo, sessionId, {\n status: 'completed',\n progress: 100,\n message: `Completed: ${result.mapping.stats.matched} matched, ${result.mapping.stats.generic} generic`,\n totalEntities: result.mapping.stats.total,\n resultJson,\n completedAt: new Date()\n })\n } catch (err) {\n const message = (err as Error)?.message ?? String(err)\n console.error(`[board-import] ImportSession ${sessionId} failed:`, err)\n await update(repo, sessionId, {\n status: 'failed',\n message: `Failed: ${message}`,\n completedAt: new Date()\n })\n }\n}\n\nasync function update(repo: any, id: string, patch: Partial<ImportSession>): Promise<void> {\n await repo.update(id, patch as any)\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ImportSession } from './import-session.js';
|
|
2
|
+
import { ImportSessionResolver } from './import-session-resolver.js';
|
|
3
|
+
export { ImportSession, ImportSessionResolver };
|
|
4
|
+
export { runImportJob } from './import-worker.js';
|
|
5
|
+
export declare const entities: (typeof ImportSession)[];
|
|
6
|
+
export declare const resolverClasses: (typeof ImportSessionResolver)[];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolverClasses = exports.entities = exports.runImportJob = exports.ImportSessionResolver = exports.ImportSession = void 0;
|
|
4
|
+
const import_session_js_1 = require("./import-session.js");
|
|
5
|
+
Object.defineProperty(exports, "ImportSession", { enumerable: true, get: function () { return import_session_js_1.ImportSession; } });
|
|
6
|
+
const import_session_resolver_js_1 = require("./import-session-resolver.js");
|
|
7
|
+
Object.defineProperty(exports, "ImportSessionResolver", { enumerable: true, get: function () { return import_session_resolver_js_1.ImportSessionResolver; } });
|
|
8
|
+
var import_worker_js_1 = require("./import-worker.js");
|
|
9
|
+
Object.defineProperty(exports, "runImportJob", { enumerable: true, get: function () { return import_worker_js_1.runImportJob; } });
|
|
10
|
+
exports.entities = [import_session_js_1.ImportSession];
|
|
11
|
+
exports.resolverClasses = [import_session_resolver_js_1.ImportSessionResolver];
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/import-session/index.ts"],"names":[],"mappings":";;;AAAA,2DAAmD;AAG1C,8FAHA,iCAAa,OAGA;AAFtB,6EAAoE;AAE5C,sGAFf,kDAAqB,OAEe;AAC7C,uDAAiD;AAAxC,gHAAA,YAAY,OAAA;AAER,QAAA,QAAQ,GAAG,CAAC,iCAAa,CAAC,CAAA;AAC1B,QAAA,eAAe,GAAG,CAAC,kDAAqB,CAAC,CAAA","sourcesContent":["import { ImportSession } from './import-session.js'\nimport { ImportSessionResolver } from './import-session-resolver.js'\n\nexport { ImportSession, ImportSessionResolver }\nexport { runImportJob } from './import-worker.js'\n\nexport const entities = [ImportSession]\nexport const resolverClasses = [ImportSessionResolver]\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export * from './import-session/index.js';
|
|
2
|
+
export declare const entities: typeof import("./import-session/import-session.js").ImportSession[];
|
|
3
|
+
export declare const schema: {
|
|
4
|
+
resolverClasses: typeof import("./import-session/import-session-resolver.js").ImportSessionResolver[];
|
|
5
|
+
};
|
|
6
|
+
export { importBoard } from './pipeline/index.js';
|
|
7
|
+
export type { ImportOptions, ImportResult } from './pipeline/index.js';
|
|
8
|
+
export { applyMapping, GENERIC_OBJECT_TYPE } from './pipeline/stage2-mapping.js';
|
|
9
|
+
export { assembleBoard } from './pipeline/stage3-board.js';
|
|
10
|
+
export { applyDataBinding } from './pipeline/stage4-binding.js';
|
|
11
|
+
export type { BoardModel, BoardComponent, AssembleOptions } from './pipeline/stage3-board.js';
|
|
12
|
+
export type { MappingOptions } from './pipeline/stage2-mapping.js';
|
|
13
|
+
export type { BindingOptions } from './pipeline/stage4-binding.js';
|
|
14
|
+
export { convertGenericTo, revertToGeneric } from './converters/generic-to.js';
|
|
15
|
+
export type { ConvertOptions } from './converters/generic-to.js';
|
|
16
|
+
export { DxfAdapter } from './adapters/dxf-adapter.js';
|
|
17
|
+
export { registerImportRules, registerSubscriptionRules, register3DPresets, getImportRules, getSubscriptionRules, get3DPresets, find3DPreset, findSubscriptionRule, clearImportRules, clearSubscriptionRules, clear3DPresets, clearAllRegistries } from './registry/index.js';
|
|
18
|
+
export type { EntityKind, EntityAttribs, EntityRelation, Vec2, Vec3, Size2, Size3, BoundingBox, UniversalEntity, ParseResult, AdapterMetadata, ParseOptions, AdapterInput, FormatAdapter, AdapterCapabilities } from './types/index.js';
|
|
19
|
+
export type { ComponentCategory, ImportRule, ImportMatcher, ImportBuildSpec, IdStrategy, ComponentDraft, ThreeDPreset, ThreeDGeometry, ThreeDMaterial, SubscriptionRule, MappingResult, MappingWarning } from './types/registry.js';
|
|
20
|
+
export { setAIClient, getAIClient, DefaultBoardImportAI } from './ai/types.js';
|
|
21
|
+
export type { BoardImportAI, AIClient, // deprecated alias for BoardImportAI
|
|
22
|
+
ConventionInferenceOptions, RuleSuggestion, ClassificationOptions, LabeledEntity, EntityClassification, ImageParseOptions } from './ai/types.js';
|