@things-factory/figure-service 10.1.110 → 10.1.112
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-server/index.js +15 -0
- package/dist-server/index.js.map +1 -1
- package/dist-server/routers/figure-thumbnail-router.d.ts +12 -3
- package/dist-server/routers/figure-thumbnail-router.js +36 -30
- package/dist-server/routers/figure-thumbnail-router.js.map +1 -1
- package/dist-server/service/figure/event-subscriber.js +4 -1
- package/dist-server/service/figure/event-subscriber.js.map +1 -1
- package/dist-server/service/figure/figure-format.d.ts +5 -3
- package/dist-server/service/figure/figure-format.js +21 -5
- package/dist-server/service/figure/figure-format.js.map +1 -1
- package/dist-server/service/figure/figure-history.d.ts +4 -0
- package/dist-server/service/figure/figure-history.js +27 -0
- package/dist-server/service/figure/figure-history.js.map +1 -1
- package/dist-server/service/figure/figure-mutation.d.ts +10 -0
- package/dist-server/service/figure/figure-mutation.js +65 -9
- package/dist-server/service/figure/figure-mutation.js.map +1 -1
- package/dist-server/service/figure/figure-replaced-draft.d.ts +1 -0
- package/dist-server/service/figure/figure-replaced-draft.js +5 -0
- package/dist-server/service/figure/figure-replaced-draft.js.map +1 -1
- package/dist-server/service/figure/figure-revert.js +9 -4
- package/dist-server/service/figure/figure-revert.js.map +1 -1
- package/dist-server/service/figure/figure-thumbnail-store.d.ts +35 -0
- package/dist-server/service/figure/figure-thumbnail-store.js +96 -0
- package/dist-server/service/figure/figure-thumbnail-store.js.map +1 -0
- package/dist-server/service/figure/figure-thumbnail-upgrade.d.ts +8 -0
- package/dist-server/service/figure/figure-thumbnail-upgrade.js +56 -0
- package/dist-server/service/figure/figure-thumbnail-upgrade.js.map +1 -0
- package/dist-server/service/figure/figure-type.d.ts +1 -0
- package/dist-server/service/figure/figure-type.js +5 -0
- package/dist-server/service/figure/figure-type.js.map +1 -1
- package/dist-server/service/figure/figure-write.d.ts +14 -10
- package/dist-server/service/figure/figure-write.js +9 -4
- package/dist-server/service/figure/figure-write.js.map +1 -1
- package/dist-server/service/figure/figure.d.ts +4 -0
- package/dist-server/service/figure/figure.js +21 -0
- package/dist-server/service/figure/figure.js.map +1 -1
- package/dist-server/service/figure/index.d.ts +1 -1
- package/dist-server/service/index.d.ts +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -4
- package/server/index.ts +15 -0
- package/server/routers/figure-thumbnail-router.ts +40 -36
- package/server/service/figure/event-subscriber.ts +4 -1
- package/server/service/figure/figure-deprecation-db.test.ts +107 -0
- package/server/service/figure/figure-format.test.ts +19 -1
- package/server/service/figure/figure-format.ts +27 -8
- package/server/service/figure/figure-history.ts +29 -0
- package/server/service/figure/figure-mutation.ts +56 -10
- package/server/service/figure/figure-release-record-db.test.ts +5 -0
- package/server/service/figure/figure-replaced-draft.ts +9 -0
- package/server/service/figure/figure-revert-db.test.ts +6 -5
- package/server/service/figure/figure-revert.ts +9 -4
- package/server/service/figure/figure-thumbnail-db.test.ts +218 -0
- package/server/service/figure/figure-thumbnail-store.ts +107 -0
- package/server/service/figure/figure-thumbnail-upgrade.pg.test.ts +93 -0
- package/server/service/figure/figure-thumbnail-upgrade.ts +70 -0
- package/server/service/figure/figure-type.ts +5 -0
- package/server/service/figure/figure-write.ts +15 -4
- package/server/service/figure/figure.ts +30 -0
- package/translations/en.json +4 -1
- package/translations/ko.json +4 -1
package/dist-server/index.js
CHANGED
|
@@ -21,6 +21,21 @@ process.on('bootstrap-module-start', () => {
|
|
|
21
21
|
const { templates, refused } = (0, figure_recipe_templates_js_1.recipeTemplates)();
|
|
22
22
|
console.log(`[figure-service:bootstrap] recipe templates ${templates.length}${refused.length ? `, left out: ${refused.join(' | ')}` : ''}`);
|
|
23
23
|
});
|
|
24
|
+
/*
|
|
25
|
+
* The pictures an install kept as base64 move into attachment-base by their content, once, at start (ADR-0104 decision
|
|
26
|
+
* 6). In the boot's one line: it writes, and sqlite holds one runner. A failure is said and the server goes on.
|
|
27
|
+
*/
|
|
28
|
+
const shell_1 = require("@things-factory/shell");
|
|
29
|
+
const figure_thumbnail_upgrade_js_1 = require("./service/figure/figure-thumbnail-upgrade.js");
|
|
30
|
+
process.on('bootstrap-module-start', () => {
|
|
31
|
+
(0, shell_1.inBootTurn)(() => (0, figure_thumbnail_upgrade_js_1.moveBase64Thumbnails)((0, shell_1.getDataSource)()))
|
|
32
|
+
.then(moved => {
|
|
33
|
+
const any = moved.filter(m => m.moved || m.kept);
|
|
34
|
+
if (any.length)
|
|
35
|
+
console.log(`[figure-service:bootstrap] thumbnails moved: ${any.map(m => `${m.table} ${m.moved}${m.kept ? ` (kept as base64: ${m.kept})` : ''}`).join(', ')}`);
|
|
36
|
+
})
|
|
37
|
+
.catch(error => console.error(`[figure-service] 썸네일을 옮기지 못했다 — ${error?.message ?? error}`));
|
|
38
|
+
});
|
|
24
39
|
tslib_1.__exportStar(require("./service/index.js"), exports);
|
|
25
40
|
tslib_1.__exportStar(require("./migrations/index.js"), exports);
|
|
26
41
|
var figure_tools_js_2 = require("./service/figure/figure-tools.js");
|
package/dist-server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;;AAAA,uBAAoB;AAEpB;;;;;;GAMG;AACH,sEAA+E;AAC/E,IAAA,8CAA4B,GAAE,CAAA;AAE9B;;;GAGG;AACH,mGAAoF;AACpF,OAAO,CAAC,EAAE,CAAC,wBAA+B,EAAE,GAAG,EAAE;IAC/C,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,IAAA,4CAAe,GAAE,CAAA;IAChD,OAAO,CAAC,GAAG,CAAC,+CAA+C,SAAS,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,eAAe,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;AAC7I,CAAC,CAAC,CAAA;AAEF,6DAAkC;AAClC,gEAAqC;AACrC,oEAA+E;AAAtE,+HAAA,4BAA4B,OAAA","sourcesContent":["import './routes.js'\n\n/*\n * 저작 도구를 어시스턴트 레지스트리에 등록한다.\n *\n * 이 패키지가 적재되면 대화면(도크)이 저작 중인 Figure 의 정본·비용·발행 판정을 읽고 후보를\n * 요청할 수 있다. 능력을 복제하지 않고 figure-service 가 이미 가진 진입점을 투영한다 —\n * 노출 범위는 대화면이 `toolCategories` 로 고른다(twin-ai · board-import 와 같은 방식).\n */\nimport { registerFigureToolCategories } from './service/figure/figure-tools.js'\nregisterFigureToolCategories()\n\n/*\n * The recipe templates are read once at start (ADR-0095, amended): what was served, and any file whose form is wrong,\n * said here rather than found by a person as a missing template.\n */\nimport { recipeTemplates } from './service/figure-recipe/figure-recipe-templates.js'\nprocess.on('bootstrap-module-start' as any, () => {\n const { templates, refused } = recipeTemplates()\n console.log(`[figure-service:bootstrap] recipe templates ${templates.length}${refused.length ? `, left out: ${refused.join(' | ')}` : ''}`)\n})\n\nexport * from './service/index.js'\nexport * from './migrations/index.js'\nexport { registerFigureToolCategories } from './service/figure/figure-tools.js'\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;;AAAA,uBAAoB;AAEpB;;;;;;GAMG;AACH,sEAA+E;AAC/E,IAAA,8CAA4B,GAAE,CAAA;AAE9B;;;GAGG;AACH,mGAAoF;AACpF,OAAO,CAAC,EAAE,CAAC,wBAA+B,EAAE,GAAG,EAAE;IAC/C,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,IAAA,4CAAe,GAAE,CAAA;IAChD,OAAO,CAAC,GAAG,CAAC,+CAA+C,SAAS,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,eAAe,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;AAC7I,CAAC,CAAC,CAAA;AAEF;;;GAGG;AACH,iDAAiE;AACjE,8FAAmF;AACnF,OAAO,CAAC,EAAE,CAAC,wBAA+B,EAAE,GAAG,EAAE;IAC/C,IAAA,kBAAU,EAAC,GAAG,EAAE,CAAC,IAAA,kDAAoB,EAAC,IAAA,qBAAa,GAAE,CAAC,CAAC;SACpD,IAAI,CAAC,KAAK,CAAC,EAAE;QACZ,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC,CAAA;QAChD,IAAI,GAAG,CAAC,MAAM;YAAE,OAAO,CAAC,GAAG,CAAC,gDAAgD,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAChL,CAAC,CAAC;SACD,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,mCAAmC,KAAK,EAAE,OAAO,IAAI,KAAK,EAAE,CAAC,CAAC,CAAA;AAChG,CAAC,CAAC,CAAA;AAEF,6DAAkC;AAClC,gEAAqC;AACrC,oEAA+E;AAAtE,+HAAA,4BAA4B,OAAA","sourcesContent":["import './routes.js'\n\n/*\n * 저작 도구를 어시스턴트 레지스트리에 등록한다.\n *\n * 이 패키지가 적재되면 대화면(도크)이 저작 중인 Figure 의 정본·비용·발행 판정을 읽고 후보를\n * 요청할 수 있다. 능력을 복제하지 않고 figure-service 가 이미 가진 진입점을 투영한다 —\n * 노출 범위는 대화면이 `toolCategories` 로 고른다(twin-ai · board-import 와 같은 방식).\n */\nimport { registerFigureToolCategories } from './service/figure/figure-tools.js'\nregisterFigureToolCategories()\n\n/*\n * The recipe templates are read once at start (ADR-0095, amended): what was served, and any file whose form is wrong,\n * said here rather than found by a person as a missing template.\n */\nimport { recipeTemplates } from './service/figure-recipe/figure-recipe-templates.js'\nprocess.on('bootstrap-module-start' as any, () => {\n const { templates, refused } = recipeTemplates()\n console.log(`[figure-service:bootstrap] recipe templates ${templates.length}${refused.length ? `, left out: ${refused.join(' | ')}` : ''}`)\n})\n\n/*\n * The pictures an install kept as base64 move into attachment-base by their content, once, at start (ADR-0104 decision\n * 6). In the boot's one line: it writes, and sqlite holds one runner. A failure is said and the server goes on.\n */\nimport { getDataSource, inBootTurn } from '@things-factory/shell'\nimport { moveBase64Thumbnails } from './service/figure/figure-thumbnail-upgrade.js'\nprocess.on('bootstrap-module-start' as any, () => {\n inBootTurn(() => moveBase64Thumbnails(getDataSource()))\n .then(moved => {\n const any = moved.filter(m => m.moved || m.kept)\n if (any.length) console.log(`[figure-service:bootstrap] thumbnails moved: ${any.map(m => `${m.table} ${m.moved}${m.kept ? ` (kept as base64: ${m.kept})` : ''}`).join(', ')}`)\n })\n .catch(error => console.error(`[figure-service] 썸네일을 옮기지 못했다 — ${error?.message ?? error}`))\n})\n\nexport * from './service/index.js'\nexport * from './migrations/index.js'\nexport { registerFigureToolCategories } from './service/figure/figure-tools.js'\n"]}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { type ThumbnailFiles } from '../service/figure/figure-thumbnail-store.js';
|
|
1
2
|
/**
|
|
2
3
|
* 썸네일을 **그림으로** 내보내는 길.
|
|
3
4
|
*
|
|
4
5
|
* ## 왜 필요한가
|
|
5
6
|
*
|
|
6
|
-
* 썸네일은
|
|
7
|
+
* 썸네일은 attachment-base 가 내용 해시로 갖는다(ADR-0104 결정 6). 옛날에는 base64 문자열로 칸에 있었고, 그것을 목록 질의가
|
|
7
8
|
* 함께 실어 오면 카탈로그를 열 때마다 그림 전부가 JSON 안에 담겨 다시 온다 — data URL 은
|
|
8
9
|
* 브라우저가 캐시할 수 없다. [확인함] 표본 14개에 227KB, 한 장 최대 20KB.
|
|
9
10
|
*
|
|
@@ -18,7 +19,15 @@
|
|
|
18
19
|
* `/thumbnail/:id` 로 두면 먼저 등록된 쪽이 이기고, 남의 표에서 id 를 찾아 404 를 낸다.
|
|
19
20
|
* 2. **행 전체를 읽는다.** board 는 `findOneBy` 로 행을 다 읽는다. figure 행에는 정본(`source`)이
|
|
20
21
|
* 붙어 있어, 그림 한 장에 정본까지 읽게 된다. 필요한 칸만 고른다.
|
|
21
|
-
* 3. **png 로 못 박는다.** figure 썸네일은 webp 다(모델러가 그렇게 만든다). data URL 이 스스로
|
|
22
|
-
*
|
|
22
|
+
* 3. **png 로 못 박는다.** figure 썸네일은 webp 다(모델러가 그렇게 만든다). 저장할 때 data URL 이 스스로
|
|
23
|
+
* 말한 형식을 attachment 의 mimetype 으로 쓴다 — 못 박으면 브라우저가 확장자와 다른 바이트를 받는다.
|
|
23
24
|
*/
|
|
24
25
|
export declare const figureThumbnailRouter: any;
|
|
26
|
+
/**
|
|
27
|
+
* The picture of a figure the caller may read, by its hash (ADR-0104 decision 6). Exported for tests, which give
|
|
28
|
+
* their own `files`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function sendFigureThumbnail(context: any, next: () => Promise<any>, id: string, domain: {
|
|
31
|
+
id: string;
|
|
32
|
+
parentId?: string;
|
|
33
|
+
}, files?: ThumbnailFiles): Promise<void>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.figureThumbnailRouter = void 0;
|
|
4
|
+
exports.sendFigureThumbnail = sendFigureThumbnail;
|
|
4
5
|
const tslib_1 = require("tslib");
|
|
5
6
|
const koa_router_1 = tslib_1.__importDefault(require("koa-router"));
|
|
6
7
|
const typeorm_1 = require("typeorm");
|
|
@@ -8,13 +9,14 @@ const auth_base_1 = require("@things-factory/auth-base");
|
|
|
8
9
|
const shell_1 = require("@things-factory/shell");
|
|
9
10
|
const fs_1 = require("fs");
|
|
10
11
|
const figure_js_1 = require("../service/figure/figure.js");
|
|
12
|
+
const figure_thumbnail_store_js_1 = require("../service/figure/figure-thumbnail-store.js");
|
|
11
13
|
const figure_recipe_templates_js_1 = require("../service/figure-recipe/figure-recipe-templates.js");
|
|
12
14
|
/**
|
|
13
15
|
* 썸네일을 **그림으로** 내보내는 길.
|
|
14
16
|
*
|
|
15
17
|
* ## 왜 필요한가
|
|
16
18
|
*
|
|
17
|
-
* 썸네일은
|
|
19
|
+
* 썸네일은 attachment-base 가 내용 해시로 갖는다(ADR-0104 결정 6). 옛날에는 base64 문자열로 칸에 있었고, 그것을 목록 질의가
|
|
18
20
|
* 함께 실어 오면 카탈로그를 열 때마다 그림 전부가 JSON 안에 담겨 다시 온다 — data URL 은
|
|
19
21
|
* 브라우저가 캐시할 수 없다. [확인함] 표본 14개에 227KB, 한 장 최대 20KB.
|
|
20
22
|
*
|
|
@@ -29,18 +31,10 @@ const figure_recipe_templates_js_1 = require("../service/figure-recipe/figure-re
|
|
|
29
31
|
* `/thumbnail/:id` 로 두면 먼저 등록된 쪽이 이기고, 남의 표에서 id 를 찾아 404 를 낸다.
|
|
30
32
|
* 2. **행 전체를 읽는다.** board 는 `findOneBy` 로 행을 다 읽는다. figure 행에는 정본(`source`)이
|
|
31
33
|
* 붙어 있어, 그림 한 장에 정본까지 읽게 된다. 필요한 칸만 고른다.
|
|
32
|
-
* 3. **png 로 못 박는다.** figure 썸네일은 webp 다(모델러가 그렇게 만든다). data URL 이 스스로
|
|
33
|
-
*
|
|
34
|
+
* 3. **png 로 못 박는다.** figure 썸네일은 webp 다(모델러가 그렇게 만든다). 저장할 때 data URL 이 스스로
|
|
35
|
+
* 말한 형식을 attachment 의 mimetype 으로 쓴다 — 못 박으면 브라우저가 확장자와 다른 바이트를 받는다.
|
|
34
36
|
*/
|
|
35
37
|
exports.figureThumbnailRouter = new koa_router_1.default();
|
|
36
|
-
/** `data:image/webp;base64,....` → 형식과 바이트. 아니면 undefined. */
|
|
37
|
-
function decodeDataUrl(dataUrl) {
|
|
38
|
-
const matched = /^data:([^;,]+);base64,(.*)$/s.exec(dataUrl);
|
|
39
|
-
if (!matched) {
|
|
40
|
-
return undefined;
|
|
41
|
-
}
|
|
42
|
-
return { type: matched[1], buffer: Buffer.from(matched[2], 'base64') };
|
|
43
|
-
}
|
|
44
38
|
exports.figureThumbnailRouter.get('/figure-thumbnail/:id', async (context, next) => {
|
|
45
39
|
const { domain, user, unsafeIP, prohibitedPrivileges } = context.state;
|
|
46
40
|
/*
|
|
@@ -59,39 +53,51 @@ exports.figureThumbnailRouter.get('/figure-thumbnail/:id', async (context, next)
|
|
|
59
53
|
context.status = 403;
|
|
60
54
|
return;
|
|
61
55
|
}
|
|
62
|
-
|
|
56
|
+
await sendFigureThumbnail(context, next, context.params.id, domain);
|
|
57
|
+
});
|
|
58
|
+
/**
|
|
59
|
+
* The picture of a figure the caller may read, by its hash (ADR-0104 decision 6). Exported for tests, which give
|
|
60
|
+
* their own `files`.
|
|
61
|
+
*/
|
|
62
|
+
async function sendFigureThumbnail(context, next, id, domain, files = (0, figure_thumbnail_store_js_1.thumbnailFiles)()) {
|
|
63
63
|
/*
|
|
64
64
|
읽기는 부모 도메인까지 본다 — `figure` 질의와 같은 범위다. 물려받은 형상을 목록에는 보여 주고
|
|
65
65
|
그림만 404 를 내면, 화면은 「그림이 아직 없다」고 거짓말하게 된다.
|
|
66
66
|
*/
|
|
67
67
|
const found = await (0, shell_1.getRepository)(figure_js_1.Figure).findOne({
|
|
68
68
|
where: { domain: { id: (0, typeorm_1.In)([domain.id, domain.parentId].filter(Boolean)) }, id },
|
|
69
|
-
|
|
69
|
+
relations: { domain: true },
|
|
70
|
+
select: { id: true, thumbnailHash: true, thumbnailUpdatedAt: true, domain: { id: true } }
|
|
70
71
|
});
|
|
71
|
-
|
|
72
|
-
if (!decoded) {
|
|
72
|
+
if (!found?.thumbnailHash) {
|
|
73
73
|
context.status = 404;
|
|
74
74
|
return;
|
|
75
75
|
}
|
|
76
76
|
/*
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
경우를 위해 시각을 함께 실어 다시 물어볼 수 있게 한다.
|
|
77
|
+
그림은 **그 figure 의 도메인**에서 찾는다 — 묻는 도메인이 아니다(판정 조건 4). 해시가 같아도 다른 도메인의 행을
|
|
78
|
+
집지 않는다. 물려받은 figure 의 그림은 부모 도메인에 있다.
|
|
80
79
|
*/
|
|
81
|
-
const
|
|
82
|
-
|
|
80
|
+
const picture = await (0, figure_thumbnail_store_js_1.thumbnailAttachmentOf)({ getRepository: shell_1.getRepository }, found.domain.id, found.thumbnailHash);
|
|
81
|
+
if (!picture) {
|
|
82
|
+
context.status = 404;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
/*
|
|
86
|
+
**개인 캐시만 허락한다.** 이 그림은 권한을 본 뒤에 내주는 것이므로 공용 캐시(프록시)에 남으면 안 된다 —
|
|
87
|
+
attachment 의 공개 경로가 쓰는 `public` 을 따라 쓰지 않는다(판정 조건 1). 같은 내용은 같은 해시이므로 ETag 가
|
|
88
|
+
곧 내용이다. 부르는 쪽이 `?v=` 로 판을 가리므로 오래 두어도 낡은 그림이 남지 않는다.
|
|
89
|
+
*/
|
|
90
|
+
context.type = picture.mimetype;
|
|
83
91
|
context.set('Cache-Control', 'private, max-age=31536000');
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
context.set('
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
+
context.set('ETag', `"${found.thumbnailHash}"`);
|
|
93
|
+
if (found.thumbnailUpdatedAt)
|
|
94
|
+
context.set('Last-Modified', new Date(found.thumbnailUpdatedAt).toUTCString());
|
|
95
|
+
if (context.fresh) {
|
|
96
|
+
context.status = 304;
|
|
97
|
+
return;
|
|
92
98
|
}
|
|
93
|
-
context.
|
|
94
|
-
}
|
|
99
|
+
await files.send(context, picture.path, next);
|
|
100
|
+
}
|
|
95
101
|
/**
|
|
96
102
|
* A recipe template's thumbnail (ADR-0095 amended): the file drawn from its model, served behind the same door as the
|
|
97
103
|
* templates themselves. The file changes only with a release, so a day's private cache is enough.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-thumbnail-router.js","sourceRoot":"","sources":["../../server/routers/figure-thumbnail-router.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"figure-thumbnail-router.js","sourceRoot":"","sources":["../../server/routers/figure-thumbnail-router.ts"],"names":[],"mappings":";;;AAuEA,kDA6CC;;AApHD,oEAA+B;AAC/B,qCAA4B;AAE5B,yDAA2D;AAC3D,iDAAqD;AAErD,2BAAiC;AAEjC,2DAAoD;AACpD,2FAAwH;AACxH,oGAAqF;AAErF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACU,QAAA,qBAAqB,GAAG,IAAI,oBAAM,EAAE,CAAA;AAEjD,6BAAqB,CAAC,GAAG,CAAC,uBAAuB,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;IACzE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,oBAAoB,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IAEtE;;;;;;;;;;MAUE;IACF,MAAM,OAAO,GAAG,MAAM,IAAA,2BAAe,EACnC,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EACvD,IAAI,EACJ,MAAM,EACN,QAAQ,EACR,oBAAoB,CACrB,CAAA;IAED,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,CAAC,MAAM,GAAG,GAAG,CAAA;QACpB,OAAM;IACR,CAAC;IAED,MAAM,mBAAmB,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,CAAA;AACrE,CAAC,CAAC,CAAA;AAEF;;;GAGG;AACI,KAAK,UAAU,mBAAmB,CACvC,OAAY,EACZ,IAAwB,EACxB,EAAU,EACV,MAAyC,EACzC,QAAwB,IAAA,0CAAc,GAAE;IAExC;;;MAGE;IACF,MAAM,KAAK,GAAG,MAAM,IAAA,qBAAa,EAAC,kBAAM,CAAC,CAAC,OAAO,CAAC;QAChD,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAA,YAAE,EAAC,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE;QAC/E,SAAS,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;QAC3B,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE;KAC1F,CAAC,CAAA;IACF,IAAI,CAAC,KAAK,EAAE,aAAa,EAAE,CAAC;QAC1B,OAAO,CAAC,MAAM,GAAG,GAAG,CAAA;QACpB,OAAM;IACR,CAAC;IAED;;;MAGE;IACF,MAAM,OAAO,GAAG,MAAM,IAAA,iDAAqB,EAAC,EAAE,aAAa,EAAb,qBAAa,EAAE,EAAE,KAAK,CAAC,MAAO,CAAC,EAAE,EAAE,KAAK,CAAC,aAAa,CAAC,CAAA;IACrG,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,CAAC,MAAM,GAAG,GAAG,CAAA;QACpB,OAAM;IACR,CAAC;IAED;;;;MAIE;IACF,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAA;IAC/B,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,2BAA2B,CAAC,CAAA;IACzD,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,KAAK,CAAC,aAAa,GAAG,CAAC,CAAA;IAC/C,IAAI,KAAK,CAAC,kBAAkB;QAAE,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC,WAAW,EAAE,CAAC,CAAA;IAC5G,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,OAAO,CAAC,MAAM,GAAG,GAAG,CAAA;QACpB,OAAM;IACR,CAAC;IACD,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,IAAK,EAAE,IAAI,CAAC,CAAA;AAChD,CAAC;AAED;;;GAGG;AACH,6BAAqB,CAAC,GAAG,CAAC,gCAAgC,EAAE,KAAK,EAAC,OAAO,EAAC,EAAE;IAC1E,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,oBAAoB,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IACtE,MAAM,OAAO,GAAG,MAAM,IAAA,2BAAe,EAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,oBAAoB,CAAC,CAAA;IAC5I,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,CAAC,MAAM,GAAG,GAAG,CAAA;QACpB,OAAM;IACR,CAAC;IACD,MAAM,QAAQ,GAAG,IAAA,4CAAe,GAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAC7F,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,CAAC;QACzB,OAAO,CAAC,MAAM,GAAG,GAAG,CAAA;QACpB,OAAM;IACR,CAAC;IACD,OAAO,CAAC,IAAI,GAAG,WAAW,CAAA;IAC1B,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,wBAAwB,CAAC,CAAA;IACtD,OAAO,CAAC,IAAI,GAAG,IAAA,iBAAY,EAAC,QAAQ,CAAC,SAAS,CAAC,CAAA;AACjD,CAAC,CAAC,CAAA","sourcesContent":["import Router from 'koa-router'\nimport { In } from 'typeorm'\n\nimport { checkPermission } from '@things-factory/auth-base'\nimport { getRepository } from '@things-factory/shell'\n\nimport { readFileSync } from 'fs'\n\nimport { Figure } from '../service/figure/figure.js'\nimport { thumbnailAttachmentOf, thumbnailFiles, type ThumbnailFiles } from '../service/figure/figure-thumbnail-store.js'\nimport { recipeTemplates } from '../service/figure-recipe/figure-recipe-templates.js'\n\n/**\n * 썸네일을 **그림으로** 내보내는 길.\n *\n * ## 왜 필요한가\n *\n * 썸네일은 attachment-base 가 내용 해시로 갖는다(ADR-0104 결정 6). 옛날에는 base64 문자열로 칸에 있었고, 그것을 목록 질의가\n * 함께 실어 오면 카탈로그를 열 때마다 그림 전부가 JSON 안에 담겨 다시 온다 — data URL 은\n * 브라우저가 캐시할 수 없다. [확인함] 표본 14개에 227KB, 한 장 최대 20KB.\n *\n * 그림으로 내보내면 브라우저가 캐시하고, 부르는 쪽이 `?v=thumbnailUpdatedAt` 을 붙이므로 그림이\n * 다시 만들어진 것만 다시 받는다. 목록 질의에서는 `thumbnail` 을 아예 고르지 않는다.\n *\n * ## board 를 베끼면서 고친 것 셋\n *\n * `standalone-board-service-router` 의 `/thumbnail/:id` 가 본이지만 그대로 두면 안 되는 것들이 있다.\n *\n * 1. **길 이름이 겹친다.** 이 앱은 board 도 함께 싣는다(메뉴가 board-ui 를 끌어온다).\n * `/thumbnail/:id` 로 두면 먼저 등록된 쪽이 이기고, 남의 표에서 id 를 찾아 404 를 낸다.\n * 2. **행 전체를 읽는다.** board 는 `findOneBy` 로 행을 다 읽는다. figure 행에는 정본(`source`)이\n * 붙어 있어, 그림 한 장에 정본까지 읽게 된다. 필요한 칸만 고른다.\n * 3. **png 로 못 박는다.** figure 썸네일은 webp 다(모델러가 그렇게 만든다). 저장할 때 data URL 이 스스로\n * 말한 형식을 attachment 의 mimetype 으로 쓴다 — 못 박으면 브라우저가 확장자와 다른 바이트를 받는다.\n */\nexport const figureThumbnailRouter = new Router()\n\nfigureThumbnailRouter.get('/figure-thumbnail/:id', async (context, next) => {\n const { domain, user, unsafeIP, prohibitedPrivileges } = context.state\n\n /*\n **질의와 같은 문을 쓴다.**\n\n `User.hasPrivilege('query','figure',…)` 만 보면 안 된다 — figure 질의는\n `@privilege(category: \"figure\", privilege: \"query\", domainOwnerGranted: true)` 로 선언되어 있고,\n 도메인 소유자는 `figure:query` 권한 줄이 없어도 통과한다. 그 허용을 여기서 빼면 목록은 보이는데\n 그림만 403 이 되어, 화면은 「그림이 아직 없다」고 거짓말한다. [확인함] 실제로 카드 14장이 전부\n 403 이었다.\n\n 조건을 손으로 다시 적지 않고 지시자가 쓰는 함수를 그대로 쓴다. 선언이 바뀌면 여기도 함께 바뀐다.\n */\n const allowed = await checkPermission(\n { category: 'figure', privilege: 'query', owner: true },\n user,\n domain,\n unsafeIP,\n prohibitedPrivileges\n )\n\n if (!allowed) {\n context.status = 403\n return\n }\n\n await sendFigureThumbnail(context, next, context.params.id, domain)\n})\n\n/**\n * The picture of a figure the caller may read, by its hash (ADR-0104 decision 6). Exported for tests, which give\n * their own `files`.\n */\nexport async function sendFigureThumbnail(\n context: any,\n next: () => Promise<any>,\n id: string,\n domain: { id: string; parentId?: string },\n files: ThumbnailFiles = thumbnailFiles()\n): Promise<void> {\n /*\n 읽기는 부모 도메인까지 본다 — `figure` 질의와 같은 범위다. 물려받은 형상을 목록에는 보여 주고\n 그림만 404 를 내면, 화면은 「그림이 아직 없다」고 거짓말하게 된다.\n */\n const found = await getRepository(Figure).findOne({\n where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }, id },\n relations: { domain: true },\n select: { id: true, thumbnailHash: true, thumbnailUpdatedAt: true, domain: { id: true } }\n })\n if (!found?.thumbnailHash) {\n context.status = 404\n return\n }\n\n /*\n 그림은 **그 figure 의 도메인**에서 찾는다 — 묻는 도메인이 아니다(판정 조건 4). 해시가 같아도 다른 도메인의 행을\n 집지 않는다. 물려받은 figure 의 그림은 부모 도메인에 있다.\n */\n const picture = await thumbnailAttachmentOf({ getRepository }, found.domain!.id, found.thumbnailHash)\n if (!picture) {\n context.status = 404\n return\n }\n\n /*\n **개인 캐시만 허락한다.** 이 그림은 권한을 본 뒤에 내주는 것이므로 공용 캐시(프록시)에 남으면 안 된다 —\n attachment 의 공개 경로가 쓰는 `public` 을 따라 쓰지 않는다(판정 조건 1). 같은 내용은 같은 해시이므로 ETag 가\n 곧 내용이다. 부르는 쪽이 `?v=` 로 판을 가리므로 오래 두어도 낡은 그림이 남지 않는다.\n */\n context.type = picture.mimetype\n context.set('Cache-Control', 'private, max-age=31536000')\n context.set('ETag', `\"${found.thumbnailHash}\"`)\n if (found.thumbnailUpdatedAt) context.set('Last-Modified', new Date(found.thumbnailUpdatedAt).toUTCString())\n if (context.fresh) {\n context.status = 304\n return\n }\n await files.send(context, picture.path!, next)\n}\n\n/**\n * A recipe template's thumbnail (ADR-0095 amended): the file drawn from its model, served behind the same door as the\n * templates themselves. The file changes only with a release, so a day's private cache is enough.\n */\nfigureThumbnailRouter.get('/figure-recipe-thumbnail/:name', async context => {\n const { domain, user, unsafeIP, prohibitedPrivileges } = context.state\n const allowed = await checkPermission({ category: 'figure', privilege: 'query', owner: true }, user, domain, unsafeIP, prohibitedPrivileges)\n if (!allowed) {\n context.status = 403\n return\n }\n const template = recipeTemplates().templates.find(t => t.recipe.name === context.params.name)\n if (!template?.thumbnail) {\n context.status = 404\n return\n }\n context.type = 'image/png'\n context.set('Cache-Control', 'private, max-age=86400')\n context.body = readFileSync(template.thumbnail)\n})\n"]}
|
|
@@ -15,7 +15,8 @@ const figure_history_js_1 = require("./figure-history.js");
|
|
|
15
15
|
*
|
|
16
16
|
* 발행 메모는 Figure 의 칸이 아니다. `releaseFigure` 가 저장할 엔티티에 `__releaseComment` 로 얹어
|
|
17
17
|
* 두면 여기서 판본의 `comment` 로 옮긴다 — 메모는 그 판의 것이지 도형의 것이 아니다. 정본 형식
|
|
18
|
-
* (`__releaseFormat`)
|
|
18
|
+
* (`__releaseFormat`), 직전 판에 견준 변경(`__releaseChanges`), 발행 때 잰 값(`__releaseMetrics`)과
|
|
19
|
+
* 윗모습(`__releaseTopView`)도 같은 길로 옮긴다(ADR-0104).
|
|
19
20
|
*/
|
|
20
21
|
/** What `releaseFigure` laid on the entity for this release, moved onto the history row. */
|
|
21
22
|
function releaseRecordOnto(history, entity) {
|
|
@@ -24,6 +25,8 @@ function releaseRecordOnto(history, entity) {
|
|
|
24
25
|
history.assetFormat = e.__releaseFormat ?? null;
|
|
25
26
|
/* [] stays "[]" (nothing changed); null stays null (not compared). */
|
|
26
27
|
history.changes = e.__releaseChanges == null ? null : JSON.stringify(e.__releaseChanges);
|
|
28
|
+
history.metrics = e.__releaseMetrics == null ? null : JSON.stringify(e.__releaseMetrics);
|
|
29
|
+
history.topView = e.__releaseTopView == null ? null : JSON.stringify(e.__releaseTopView);
|
|
27
30
|
return history;
|
|
28
31
|
}
|
|
29
32
|
let FigureHistoryEntitySubscriber = class FigureHistoryEntitySubscriber extends typeorm_history_1.HistoryEntitySubscriber {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-subscriber.js","sourceRoot":"","sources":["../../../server/service/figure/event-subscriber.ts"],"names":[],"mappings":";;;;AAAA,qCAAyC;AAEzC,8DAAkE;AAElE,2CAAoC;AACpC,2DAAmD;AAEnD
|
|
1
|
+
{"version":3,"file":"event-subscriber.js","sourceRoot":"","sources":["../../../server/service/figure/event-subscriber.ts"],"names":[],"mappings":";;;;AAAA,qCAAyC;AAEzC,8DAAkE;AAElE,2CAAoC;AACpC,2DAAmD;AAEnD;;;;;;;;;;;GAWG;AACH,4FAA4F;AAC5F,SAAS,iBAAiB,CAAC,OAAsB,EAAE,MAAc;IAC/D,MAAM,CAAC,GAAG,MAAa,CACtB;IAAC,OAAe,CAAC,OAAO,GAAG,CAAC,CAAC,gBAAgB,IAAI,IAAI,CAAA;IACtD,OAAO,CAAC,WAAW,GAAG,CAAC,CAAC,eAAe,IAAI,IAAI,CAAA;IAC/C,sEAAsE;IACtE,OAAO,CAAC,OAAO,GAAG,CAAC,CAAC,gBAAgB,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAA;IACxF,OAAO,CAAC,OAAO,GAAG,CAAC,CAAC,gBAAgB,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAA;IACxF,OAAO,CAAC,OAAO,GAAG,CAAC,CAAC,gBAAgB,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAA;IACxF,OAAO,OAAO,CAAA;AAChB,CAAC;AAGM,IAAM,6BAA6B,GAAnC,MAAM,6BAA8B,SAAQ,yCAA8C;IAC/F,IAAW,MAAM;QACf,OAAO,kBAAM,CAAA;IACf,CAAC;IAED,IAAW,aAAa;QACtB,OAAO,iCAAa,CAAA;IACtB,CAAC;IAEM,mBAAmB,CAAC,OAAsB,EAAE,MAAc;QAC/D,OAAO,iBAAiB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;IAC3C,CAAC;IAEM,mBAAmB,CAAC,OAAsB,EAAE,MAAc;QAC/D,OAAO,iBAAiB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;IAC3C,CAAC;IAEM,KAAK,CAAC,WAAW,CAAC,KAAU;QACjC,IAAI,KAAK,CAAC,MAAM,EAAE,KAAK,IAAI,UAAU,EAAE,CAAC;YACtC,MAAM,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;QAChC,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,WAAW,CAAC,KAAU;QACjC,IAAI,KAAK,CAAC,MAAM,EAAE,KAAK,IAAI,UAAU,EAAE,CAAC;YACtC,MAAM,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;QAChC,CAAC;IACH,CAAC;CACF,CAAA;AA5BY,sEAA6B;wCAA7B,6BAA6B;IADzC,IAAA,yBAAe,GAAE;GACL,6BAA6B,CA4BzC","sourcesContent":["import { EventSubscriber } from 'typeorm'\n\nimport { HistoryEntitySubscriber } from '@operato/typeorm-history'\n\nimport { Figure } from './figure.js'\nimport { FigureHistory } from './figure-history.js'\n\n/**\n * 판본을 남긴다 — **발행된 저장에만.**\n *\n * `@operato/typeorm-history` 는 원래 모든 저장을 베낀다. 그런데 도형은 저작 중에 수십 번 저장되고,\n * 그 전부를 남기면 판본 목록이 「내가 오늘 몇 번 눌렀나」가 된다. 사람이 판이라고 부르는 것은\n * **발행한 것**이므로 그때만 베낀다. board 가 같은 규칙으로 돈다(`board-service` 의 같은 이름 파일).\n *\n * 발행 메모는 Figure 의 칸이 아니다. `releaseFigure` 가 저장할 엔티티에 `__releaseComment` 로 얹어\n * 두면 여기서 판본의 `comment` 로 옮긴다 — 메모는 그 판의 것이지 도형의 것이 아니다. 정본 형식\n * (`__releaseFormat`), 직전 판에 견준 변경(`__releaseChanges`), 발행 때 잰 값(`__releaseMetrics`)과\n * 윗모습(`__releaseTopView`)도 같은 길로 옮긴다(ADR-0104).\n */\n/** What `releaseFigure` laid on the entity for this release, moved onto the history row. */\nfunction releaseRecordOnto(history: FigureHistory, entity: Figure): FigureHistory {\n const e = entity as any\n ;(history as any).comment = e.__releaseComment ?? null\n history.assetFormat = e.__releaseFormat ?? null\n /* [] stays \"[]\" (nothing changed); null stays null (not compared). */\n history.changes = e.__releaseChanges == null ? null : JSON.stringify(e.__releaseChanges)\n history.metrics = e.__releaseMetrics == null ? null : JSON.stringify(e.__releaseMetrics)\n history.topView = e.__releaseTopView == null ? null : JSON.stringify(e.__releaseTopView)\n return history\n}\n\n@EventSubscriber()\nexport class FigureHistoryEntitySubscriber extends HistoryEntitySubscriber<Figure, FigureHistory> {\n public get entity() {\n return Figure\n }\n\n public get historyEntity() {\n return FigureHistory\n }\n\n public beforeInsertHistory(history: FigureHistory, entity: Figure): FigureHistory {\n return releaseRecordOnto(history, entity)\n }\n\n public beforeUpdateHistory(history: FigureHistory, entity: Figure): FigureHistory {\n return releaseRecordOnto(history, entity)\n }\n\n public async afterInsert(event: any): Promise<void> {\n if (event.entity?.state == 'released') {\n await super.afterInsert(event)\n }\n }\n\n public async afterUpdate(event: any): Promise<void> {\n if (event.entity?.state == 'released') {\n await super.afterUpdate(event)\n }\n }\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FigureAsset, FigureContractChange } from '@hatiolab/figure-model';
|
|
1
|
+
import type { FigureAsset, FigureContractChange, FigureReleaseMetrics, FigureTopView } from '@hatiolab/figure-model';
|
|
2
2
|
import type { FigureFinding } from './figure-type.js';
|
|
3
3
|
/** The version the format stamps on its own source. */
|
|
4
4
|
export declare const FIGURE_ASSET_VERSION = "figure-asset-1";
|
|
@@ -59,14 +59,16 @@ export declare function inspectSource(sourceJson?: string | null, releasedJson?:
|
|
|
59
59
|
/** The row's `type` -- the only place the name lives, since the asset carries none. */
|
|
60
60
|
rowType?: string): Inspection;
|
|
61
61
|
/**
|
|
62
|
-
* What a release records about itself (ADR-0104 decision 5): the format its asset is written in,
|
|
63
|
-
* against the previous release -- the kernel's own comparison, kept as it was then so a consumer that pins releases
|
|
62
|
+
* What a release records about itself (ADR-0104 decision 5): the format its asset is written in, what it measured
|
|
63
|
+
* (its cost at its largest, whether resizing is stretching), its plan from above, and what changed against the previous release -- the kernel's own comparison, kept as it was then so a consumer that pins releases
|
|
64
64
|
* reads it rather than computing it again. `[]` for a first release; `null` when the previous release could not be
|
|
65
65
|
* read, which is a different fact from "nothing changed".
|
|
66
66
|
*/
|
|
67
67
|
export declare function releaseRecordOf(sourceJson: string, releasedJson?: string | null): {
|
|
68
68
|
format: string;
|
|
69
69
|
changes: FigureContractChange[] | null;
|
|
70
|
+
metrics: FigureReleaseMetrics;
|
|
71
|
+
topView: FigureTopView;
|
|
70
72
|
};
|
|
71
73
|
/**
|
|
72
74
|
* What a consumer is handed for a released figure: the asset itself, because what draws it -- `registerFigure` in
|
|
@@ -230,6 +230,21 @@ rowType) {
|
|
|
230
230
|
blocking: false
|
|
231
231
|
});
|
|
232
232
|
}
|
|
233
|
+
/*
|
|
234
|
+
Said, never refused (ADR-0104 decision 4): the origin is where the figure is fastened, and a door fastened at its
|
|
235
|
+
hinge is a right model. A drawing that places figures by the middle of their room reads this before it pins one.
|
|
236
|
+
*/
|
|
237
|
+
const origin = (0, figure_model_1.figureOriginCentredOf)(asset);
|
|
238
|
+
if (origin && !origin.centred) {
|
|
239
|
+
const off = origin.off.map(o => `${o.axis.toUpperCase()} ${o.mm} mm`).join(', ');
|
|
240
|
+
findings.push({
|
|
241
|
+
code: 'origin-off-centre',
|
|
242
|
+
message: say('figure.gate.origin-off-centre.message', { off }, `The origin is off the middle of the declared room: ${off}.`),
|
|
243
|
+
why: say('figure.gate.origin-off-centre.why', {}, 'A drawing that places figures by the middle of their room puts this one that far from where it is drawn.'),
|
|
244
|
+
how: say('figure.gate.origin-off-centre.how', {}, 'Keep it when the figure is fastened there, as a door at its hinge. Move the origin to the middle to place it by its room.'),
|
|
245
|
+
blocking: false
|
|
246
|
+
});
|
|
247
|
+
}
|
|
233
248
|
if (releasedJson)
|
|
234
249
|
findings.push(...figureContractFindings(asset, releasedJson, say));
|
|
235
250
|
return { blocked: findings.some(finding => finding.blocking), findings };
|
|
@@ -399,23 +414,24 @@ function figureContractWording(change, say) {
|
|
|
399
414
|
}
|
|
400
415
|
}
|
|
401
416
|
/**
|
|
402
|
-
* What a release records about itself (ADR-0104 decision 5): the format its asset is written in,
|
|
403
|
-
* against the previous release -- the kernel's own comparison, kept as it was then so a consumer that pins releases
|
|
417
|
+
* What a release records about itself (ADR-0104 decision 5): the format its asset is written in, what it measured
|
|
418
|
+
* (its cost at its largest, whether resizing is stretching), its plan from above, and what changed against the previous release -- the kernel's own comparison, kept as it was then so a consumer that pins releases
|
|
404
419
|
* reads it rather than computing it again. `[]` for a first release; `null` when the previous release could not be
|
|
405
420
|
* read, which is a different fact from "nothing changed".
|
|
406
421
|
*/
|
|
407
422
|
function releaseRecordOf(sourceJson, releasedJson) {
|
|
408
423
|
const asset = (0, figure_model_1.parseFigureAsset)(sourceJson);
|
|
424
|
+
const measured = { format: asset.version, metrics: (0, figure_model_1.figureReleaseMetricsOf)(asset), topView: (0, figure_model_1.figureTopViewOf)(asset, null) };
|
|
409
425
|
if (!releasedJson)
|
|
410
|
-
return {
|
|
426
|
+
return { ...measured, changes: [] };
|
|
411
427
|
let before;
|
|
412
428
|
try {
|
|
413
429
|
before = (0, figure_model_1.parseFigureAsset)(releasedJson);
|
|
414
430
|
}
|
|
415
431
|
catch {
|
|
416
|
-
return {
|
|
432
|
+
return { ...measured, changes: null };
|
|
417
433
|
}
|
|
418
|
-
return {
|
|
434
|
+
return { ...measured, changes: (0, figure_model_1.figureContractChanges)(before, asset) };
|
|
419
435
|
}
|
|
420
436
|
/**
|
|
421
437
|
* What a consumer is handed for a released figure: the asset itself, because what draws it -- `registerFigure` in
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-format.js","sourceRoot":"","sources":["../../../server/service/figure/figure-format.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The doors a stored source goes through: saving (`parseSource`), publishing (`inspectSource`) and handing it to a\n * board (`consumablePayloadOf`). The format is `figure-asset-1`, and the asset says so in its `version`; a source\n * that does not is refused by name, because a source the server cannot identify is one it must not save.\n */\nimport { compileFigureAsset, inspectFigureAsset, isPlaceholderType, parseFigureAsset, serializeFigureAsset, figureContractChanges, figureCostOf, figureScoreOf } from '@hatiolab/figure-model'\nimport type { FigureAsset, FigureContractChange, FigureContractError, FigureViolation } from '@hatiolab/figure-model'\n\nimport type { FigureFinding } from './figure-type.js'\n\n/** The version the format stamps on its own source. */\nexport const FIGURE_ASSET_VERSION = 'figure-asset-1'\n\n/** A source that passed the save door: what is stored, and the numbers the catalogue shows, computed here. */\nexport interface ParsedSource {\n /** The source to store, as the kernel serializes it. */\n source: string\n /** Computed from the source, never taken from the client -- a client's numbers could lie to the catalogue. */\n derived: { score: number; triangles: number; groups: number }\n /** Findings that do not block the save; the caller returns them with the answer. */\n violations: FigureFinding[]\n}\n\n/** The source breaks the format. It is not stored. */\nexport class FigureSourceError extends Error {\n readonly findings: FigureFinding[]\n\n constructor(findings: FigureFinding[]) {\n super(`정본이 형식을 어겼다 — ${findings.length}건: ${findings.map(f => f.code).join(', ')}`)\n this.name = 'FigureSourceError'\n this.findings = findings\n }\n}\n\n/** One thing the release gate found. `blocking` true means the figure cannot be published. */\nexport interface GateFinding {\n code: string\n message: string\n /** **Why it matters.** A measurement alone leaves the reader at \"so what?\". */\n why?: string\n /** **What to change.** By part name and value. */\n how?: string\n /** Where it points: the parts, or the path in the source. */\n at?: string\n blocking: boolean\n}\n\nexport interface Inspection {\n findings: GateFinding[]\n /** Whether anything blocks. The screen locks the button with this. */\n blocked: boolean\n}\n\n/**\n * That the source is JSON and names the format.\n *\n * @throws {FigureSourceError} when the text is not JSON, or does not carry `version: 'figure-asset-1'`.\n */\nfunction assertFormat(sourceJson: string): void {\n let parsed: unknown\n try {\n parsed = JSON.parse(sourceJson)\n } catch (e) {\n throw new FigureSourceError([\n { code: 'source-not-json', message: `정본이 JSON 이 아니다: ${(e as Error).message}`, at: 'source' }\n ])\n }\n if ((parsed as { version?: unknown } | null)?.version !== FIGURE_ASSET_VERSION)\n throw new FigureSourceError([\n { code: 'source-format-unknown', message: `정본의 판을 알 수 없다 — 도형 자산은 version '${FIGURE_ASSET_VERSION}' 을 적는다`, at: 'version' }\n ])\n}\n\n/**\n * Validates a source and computes what the catalogue shows: the cost from `figureCostOf`, and the score from `figureScoreOf`\n * -- the headroom against placing `LIMITS.screenInstances` of it on one screen (chief architect, 2026-09-24), the\n * same number the modeller's maturity tab shows. An asset that draws nothing has no score, not a full one.\n *\n * @throws {FigureSourceError} when the source breaks the format.\n */\nexport function parseSource(sourceJson: string): ParsedSource {\n assertFormat(sourceJson)\n let asset\n try {\n asset = parseFigureAsset(sourceJson)\n compileFigureAsset(asset)\n } catch (e) {\n const error = e as FigureContractError & { path?: string }\n throw new FigureSourceError([\n { code: error.code ?? 'asset-invalid', message: error.message ?? String(e), at: error.path }\n ])\n }\n const cost = figureCostOf(asset)\n const score = figureScoreOf(asset)\n return {\n source: serializeFigureAsset(asset),\n derived: { score: (score?.score ?? null) as unknown as number, triangles: cost.triangles, groups: cost.groups },\n violations: []\n }\n}\n\n/**\n * Whether this source may be published, and why not.\n *\n * The judgement is `inspectFigureAsset`: every part inside the volume the asset declares, over the whole\n * state range, and the face it says it is mounted on lying in that plane. A verdict reached by sampling is\n * reported as a finding of its own rather than hidden — an author releasing a figure the gate could only\n * sample should be told so, because nothing proved it holds between the states that were checked.\n */\n/** The request's translator. Always called with params — `t(key, undefined)` loses the locale. */\nexport type Translate = (key: string, options?: Record<string, unknown>) => string\n\n/**\n * A finding in the words of the release panel.\n *\n * The gate says what it measured as a code and values (`part`, `axis`, `mm`, `degrees`) and an English `detail`\n * for logs. The sentence an author reads is made here, where the request's language is known (ADR-0054) — not in\n * figure-model, which knows no locale, and not on the screen, which would then have a second account of the same\n * refusal. The English line is the fallback when a key is missing, so a missing translation reads as English\n * rather than as the key printed raw.\n *\n * The words are the modeller's: 「공간 선언」 for the volume a figure takes up and 「장착면」 for the face it stands on\n * (`figure.text.mounting-face-explained`). A fact, then the way out.\n */\nfunction figureWording(violation: FigureViolation, say: Say, hasParts: boolean): Pick<GateFinding, 'message' | 'why' | 'how'> {\n const part = violation.part ?? ''\n const axis = (violation.axis ?? '').toUpperCase()\n const mm = violation.mm ?? 0\n const degrees = violation.degrees ?? 0\n const key = (field: string) => `figure.gate.${violation.code.toLowerCase().replace(/_/g, '-')}.${field}`\n\n switch (violation.code) {\n case 'PART_OUTSIDE_OCCUPANCY':\n return {\n message: say(key('message'), { part, axis, mm }, `'${part}' goes ${mm} mm past the declared space on ${axis}.`),\n why: say(key('why'), {}, 'The declared space sets the scale a figure is placed at. What lies past it is left out of that scale, so on a board this part lands outside the place given to the figure.'),\n how: say(key('how'), { part, axis, mm }, `Use \"fit to parts\" to declare the space from the parts again, or move '${part}' ${mm} mm inward on ${axis}.`)\n }\n case 'OCCUPANCY_UNDECLARED':\n return violation.axis\n ? {\n message: say(`figure.gate.occupancy-empty-axis.message`, { axis }, `The declared space is empty on ${axis}.`),\n why: say(key('why'), {}, 'Without a declared space the scale a figure is placed at cannot be set.'),\n how: say(key('how'), {}, 'Use \"fit to parts\" to declare the space from the parts as they are now.')\n }\n : {\n message: say(key('message'), {}, 'This figure has not declared the space it takes up.'),\n why: say(key('why'), {}, 'Without a declared space the scale a figure is placed at cannot be set.'),\n /*\n A new figure is refused this way before it has a part, and 「fit to parts」 has nothing to measure then —\n the screen does not offer it. Telling the author to press it sent them looking for a button that was not\n there (:3300, 2026-09-24).\n */\n how: hasParts\n ? say(key('how'), {}, 'Use \"fit to parts\" to declare the space from the parts as they are now.')\n : say(key('how-no-parts'), {}, 'Add a shape first, then use \"fit to parts\" to declare the space from it.')\n }\n case 'PARTS_OFF_PLACEMENT_FACE':\n return {\n message: say(key('message'), { part, mm }, `The mounting face of '${part}' is ${mm} mm off the plane the figure stands on.`),\n why: say(key('why'), {}, 'The figure is declared to stand on that face. If the face does not reach the plane, the figure floats on the board by that much, and whatever is placed on it floats too.'),\n how: say(key('how'), { part, mm }, `Move '${part}' ${mm} mm on Y so the face meets the plane, or choose a different mounting face.`)\n }\n case 'PLACEMENT_FACE_TILTED':\n return {\n message: say(key('message'), { part, degrees }, `The mounting face of '${part}' is tilted ${degrees}° from the plane the figure stands on.`),\n why: say(key('why'), {}, 'A tilted face cannot lie on the plane, so how the figure stands is not settled.'),\n how: say(key('how'), { part }, `Choose a mounting face parallel to the plane, or turn '${part}' upright.`)\n }\n case 'PARTS_SPLIT_ON_RESIZE': {\n /*\n What comes off is every piece but the largest — the figure as the author sees it is the big piece, and the\n small ones are what they will see hanging in the air (2026-09-24).\n */\n const resize = violation.resize!\n const pieces = [...resize.pieces].sort((a, b) => b.length - a.length)\n const parts = pieces.slice(1).flat().map(one => `'${one}'`).join(', ')\n const axes = resize.axes.map(one => one.toUpperCase()).join('·')\n const factor = resize.factor\n return {\n message: say(key('message'), { parts, axes, factor }, `At ${factor}x on ${axes}, ${parts} come away from the rest of the figure.`),\n why: say(key('why'), {}, 'A figure placed at another size shows those parts hanging in the air — to a person, it looks broken.'),\n how: say(key('how'), { parts }, `Fasten ${parts} to a face of the part they rest on, or press \"fix here\" under placement, so they stay put when the figure is resized.`)\n }\n }\n case 'SIZING_NOT_UNIFORM': {\n /* The figure declares it grows by one ratio; these parts keep a size or place of their own (ruling 2026-09-24). */\n const resize = violation.resize!\n const parts = (violation.parts ?? []).map(one => `'${one}'`).join(', ')\n const axes = resize.axes.map(one => one.toUpperCase()).join('·')\n const factor = resize.factor\n return {\n message: say(key('message'), { parts, axes, factor }, `The figure grows by one ratio, but at ${factor}x on ${axes} ${parts} do not grow with it.`),\n why: say(key('why'), {}, 'A figure that grows by one ratio keeps its proportions at any size. A part with a size or place of its own comes apart from the rest.'),\n how: say(key('how'), { parts }, `Let ${parts} follow the figure's size (\"in proportion to size\" under size and placement), or remove their own fixed numbers.`)\n }\n }\n }\n}\n\ntype Say = (key: string, params: Record<string, string | number>, english: string) => string\n\n/*\n The sentence is shown as text, never as HTML, so the values go in as they are. i18next escapes interpolated\n values by default, and the screen then showed `'lamp-in'` as `'lamp-in'`.\n*/\nfunction sayerOf(t?: Translate): Say {\n return (key, params, english) =>\n t ? t(key, { ...params, defaultValue: english, interpolation: { escapeValue: false } }) : english\n}\n\nexport function inspectSource(\n sourceJson?: string | null,\n releasedJson?: string | null,\n t?: Translate,\n /** The row's `type` -- the only place the name lives, since the asset carries none. */\n rowType?: string\n): Inspection {\n if (!sourceJson) {\n return { blocked: true, findings: [{ code: 'no-source', message: '저장된 정본이 없다. 저장하면 발행할 수 있다', blocking: true }] }\n }\n try {\n assertFormat(sourceJson)\n } catch (e) {\n const findings = (e as FigureSourceError).findings ?? [{ code: 'source-unreadable', message: String(e) }]\n return { blocked: true, findings: findings.map(f => ({ ...f, blocking: true })) }\n }\n\n let inspection\n let asset: FigureAsset | undefined\n let hasParts = true\n try {\n asset = parseFigureAsset(sourceJson)\n inspection = inspectFigureAsset(asset)\n const cost = figureCostOf(asset)\n hasParts = cost.groups + cost.hidden > 0\n } catch (e) {\n const error = e as FigureContractError & { path?: string }\n return {\n blocked: true,\n findings: [{ code: error.code ?? 'asset-invalid', message: error.message ?? String(e), at: error.path, blocking: true }]\n }\n }\n\n const say = sayerOf(t)\n const findings: GateFinding[] = inspection.violations.map(violation => ({\n code: violation.code.toLowerCase().replace(/_/g, '-'),\n ...figureWording(violation, say, hasParts),\n at: violation.part,\n blocking: true\n }))\n /*\n A machine-made name is refused at release. The asset has no `type`, so the name is read off the row; a new figure\n was once published as `FIGURE_<uuid>` (:3300, 2026-09-24). Publishing freezes the name (ADR-0063 decision 4) and\n boards call a figure by it.\n */\n if (rowType && isPlaceholderType(rowType)) {\n findings.push({\n code: 'type-is-placeholder',\n message: say('figure.gate.type-is-placeholder.message', { type: rowType }, `'${rowType}' is a temporary type given when the figure was first saved.`),\n why: say('figure.gate.type-is-placeholder.why', {}, 'Boards call a figure by its type, and it cannot change once the figure is published.'),\n how: say('figure.gate.type-is-placeholder.how', {}, 'Set the type in the type field, then publish.'),\n blocking: true\n })\n }\n if (inspection.coverage.method === 'sampled') {\n const states = inspection.coverage.states\n findings.push({\n code: 'state-range-sampled',\n /* Sizes as well as motion: the instance size range is swept for every figure (figure-model, designer 2026-09-27). */\n message: say('figure.gate.state-range-sampled.message', { states }, `The whole range of sizes and motion could not be proven, so ${states} combinations of size and pose were checked.`),\n why: say('figure.gate.state-range-sampled.why', {}, 'Between the combinations that were checked, a part may still go past the declared space.'),\n how: say('figure.gate.state-range-sampled.how', {}, 'To cover the whole range, re-author what could not be proven.'),\n /*\n Which motions stopped the proof, by the name a person gave them: the kernel reports each operation it could\n not prove (`j2-shoulder.chain: compose@1 turns …`), and the screen showed that English list as it was. The\n name before the first dot is the joint or part; the operations stay in the logs.\n */\n at: [...new Set((inspection.coverage.unprovable ?? []).map(entry => entry.split(':')[0]!.split('.')[0]!.trim()).filter(Boolean))].join(' · ') || undefined,\n blocking: false\n })\n }\n if (releasedJson) findings.push(...figureContractFindings(asset!, releasedJson, say))\n return { blocked: findings.some(finding => finding.blocking), findings }\n}\n\n/**\n * The last release against this one — what the drawings that already hold this type lean on (ADR-0063 decision 3).\n *\n * figure-model names the changes and whether each blocks (`figureContractChanges`); the sentence is made here. When\n * the last release no longer parses, the gate says so rather than passing in silence: a release that was not checked\n * against the last one is a different fact from one that was checked and found safe.\n */\nfunction figureContractFindings(asset: FigureAsset, releasedJson: string, say: Say): GateFinding[] {\n let before: FigureAsset\n try {\n before = parseFigureAsset(releasedJson)\n } catch (e) {\n return [\n {\n code: 'released-unreadable',\n message: say('figure.gate.released-not-compared.message', { reason: (e as Error).message }, `The last release could not be read, so this release was not compared with it: ${(e as Error).message}`),\n why: say('figure.gate.released-not-compared.why', {}, 'Whether this release breaks the instances already placed has not been checked.'),\n blocking: false\n }\n ]\n }\n return figureContractChanges(before, asset).map(change => ({\n code: change.code,\n ...figureContractWording(change, say),\n at: change.subject,\n blocking: change.blocking\n }))\n}\n\n/** One contract change in the words of the release panel. The English line is the fallback for a missing key. */\nfunction figureContractWording(change: FigureContractChange, say: Say): Pick<GateFinding, 'message' | 'why' | 'how'> {\n const v = change.values\n const key = (field: string) => `figure.gate.contract.${change.code}.${field}`\n const gone = {\n how: say('figure.gate.contract.gone.how', {}, 'Put it back as it was, or publish this as a new figure type so the placed ones keep the old one.')\n }\n switch (change.code) {\n case 'placement-changed':\n return {\n message: say(key('message'), v, `Placement changed from ${v.before} to ${v.after}.`),\n why: say(key('why'), {}, 'Placement sets the height of every placed instance. They all move with it.'),\n ...gone\n }\n case 'size-inputs-gone':\n return {\n message: say(key('message'), v, 'The figure no longer takes its size from the place it is given.'),\n why: say(key('why'), {}, 'Placed instances were sized on the board. After this release they all draw at the authored size.'),\n ...gone\n }\n case 'size-inputs-added':\n return {\n message: say(key('message'), v, 'The figure now takes its size from the place it is given.'),\n why: say(key('why'), {}, 'Placed instances now follow their own size on the board.')\n }\n case 'size-range-narrowed':\n return {\n message: say(key('message'), { ...v, input: change.subject ?? '' }, `The size range of ${change.subject} narrowed from ${v.before} to ${v.after}.`),\n why: say(key('why'), {}, 'An instance placed at a size outside the new range will not draw. Which sizes are placed is not recorded.')\n }\n case 'authored-size-changed':\n return {\n message: say(key('message'), v, `The authored size changed from ${v.before} to ${v.after}.`),\n why: say(key('why'), {}, 'Placed instances keep their own size. An instance with no depth of its own takes its depth from this.')\n }\n case 'state-input-gone':\n return {\n message: say(key('message'), v, `The control '${v.id}' is gone.`),\n why: say(key('why'), {}, 'Placed instances and connectors give values under this name. After this release those values do nothing, and nothing on screen says why.'),\n ...gone\n }\n case 'state-unit-changed':\n return {\n message: say(key('message'), v, `The unit of the control '${v.id}' changed from ${v.before} to ${v.after}.`),\n why: say(key('why'), {}, 'Stored values keep their number. With another unit the same number means something else.'),\n ...gone\n }\n case 'state-range-narrowed':\n return {\n message: say(key('message'), v, `The range of the control '${v.id}' narrowed from ${v.before} to ${v.after}.`),\n why: say(key('why'), {}, 'A value outside the new range may already be stored on a placed instance.')\n }\n case 'state-input-added':\n return {\n message: say(key('message'), v, `The control '${v.id}' is new.`),\n why: say(key('why'), {}, 'Placed instances that give no value stand at its default.')\n }\n case 'clip-gone':\n return {\n message: say(key('message'), v, `The clip '${v.clip}' is gone.`),\n why: say(key('why'), {}, 'Placed instances play clips by name. After this release that one plays nothing.'),\n ...gone\n }\n case 'clip-added':\n return {\n message: say(key('message'), v, `The clip '${v.clip}' is new.`),\n why: say(key('why'), {}, 'It stays still until an instance plays it.')\n }\n case 'binding-gone':\n return {\n message: say(key('message'), v, `The place '${v.binding}' is gone.`),\n why: say(key('why'), {}, 'What sits in it on a placed instance loses its place.'),\n ...gone\n }\n case 'binding-role-changed':\n return {\n message: say(key('message'), v, `The place '${v.binding}' changed from ${v.before} to ${v.after}.`),\n why: say(key('why'), {}, 'What sits in it was placed for the old role.'),\n ...gone\n }\n case 'binding-capacity-reduced':\n return {\n message: say(key('message'), v, `The place '${v.binding}' holds ${v.after} instead of ${v.before}.`),\n why: say(key('why'), {}, 'What sits in the places that went has nowhere to go.'),\n ...gone\n }\n case 'binding-accepts-narrowed':\n return {\n message: say(key('message'), v, `The place '${v.binding}' no longer accepts ${v.types}.`),\n why: say(key('why'), {}, 'What already sits there stays. The next one of that type is refused.')\n }\n case 'binding-added':\n return {\n message: say(key('message'), v, `The place '${v.binding}' is new.`),\n why: say(key('why'), {}, 'Placed instances do not know it yet and stand as they are.')\n }\n case 'capability-gone':\n return {\n message: say(key('message'), v, `The capability '${v.name}' is gone.`),\n why: say(key('why'), {}, 'Scenes attach behaviour by this name. Placed instances lose what it did.'),\n ...gone\n }\n case 'capability-added':\n return {\n message: say(key('message'), v, `The capability '${v.name}' is new.`),\n why: say(key('why'), {}, 'It adds what the figure can do. Placed instances stand as they are.')\n }\n /* Said, never refused (ADR-0104): a consumer that pins a release reads these before it moves to this one. */\n case 'occupancy-changed':\n return {\n message: say(key('message'), v, `The room the figure takes changed from ${v.before || 'none'} to ${v.after || 'none'}.`),\n why: say(key('why'), {}, 'A consumer places the figure by this room. One that moves to this release sees it take the new room.')\n }\n case 'contact-face-changed':\n return {\n message: say(key('message'), v, 'The face the figure is mounted by changed.'),\n why: say(key('why'), {}, 'A consumer that moves to this release sees it stand on the new face.')\n }\n case 'public-input-gone':\n return {\n message: say(key('message'), { ...v, input: change.subject ?? '' }, `'${change.subject}' is no longer set on a board.`),\n why: say(key('why'), {}, 'Values placed instances give it do nothing after this release.')\n }\n case 'public-input-range-narrowed':\n return {\n message: say(key('message'), { ...v, input: change.subject ?? '' }, `The range of '${change.subject}' narrowed from ${v.before} to ${v.after}.`),\n why: say(key('why'), {}, 'An instance may have set a value outside the new range. Which values are placed is not recorded.')\n }\n case 'public-input-added':\n return {\n message: say(key('message'), { ...v, input: change.subject ?? '' }, `'${change.subject}' can now be set on a board.`),\n why: say(key('why'), {}, 'Placed instances that give no value stand at its default.')\n }\n }\n}\n\n/**\n * What a release records about itself (ADR-0104 decision 5): the format its asset is written in, and what changed\n * against the previous release -- the kernel's own comparison, kept as it was then so a consumer that pins releases\n * reads it rather than computing it again. `[]` for a first release; `null` when the previous release could not be\n * read, which is a different fact from \"nothing changed\".\n */\nexport function releaseRecordOf(sourceJson: string, releasedJson?: string | null): { format: string; changes: FigureContractChange[] | null } {\n const asset = parseFigureAsset(sourceJson)\n if (!releasedJson) return { format: asset.version, changes: [] }\n let before: FigureAsset\n try {\n before = parseFigureAsset(releasedJson)\n } catch {\n return { format: asset.version, changes: null }\n }\n return { format: asset.version, changes: figureContractChanges(before, asset) }\n}\n\n/**\n * What a consumer is handed for a released figure: the asset itself, because what draws it -- `registerFigure` in\n * things-scene -- takes the asset and compiles it with the kernel.\n */\nexport function consumablePayloadOf(sourceJson: string): { asset: FigureAsset } {\n assertFormat(sourceJson)\n const asset = parseFigureAsset(sourceJson)\n compileFigureAsset(asset)\n return { asset }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"figure-format.js","sourceRoot":"","sources":["../../../server/service/figure/figure-format.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The doors a stored source goes through: saving (`parseSource`), publishing (`inspectSource`) and handing it to a\n * board (`consumablePayloadOf`). The format is `figure-asset-1`, and the asset says so in its `version`; a source\n * that does not is refused by name, because a source the server cannot identify is one it must not save.\n */\nimport { compileFigureAsset, inspectFigureAsset, isPlaceholderType, parseFigureAsset, serializeFigureAsset, figureContractChanges, figureCostOf, figureOriginCentredOf, figureReleaseMetricsOf, figureScoreOf, figureTopViewOf } from '@hatiolab/figure-model'\nimport type { FigureAsset, FigureContractChange, FigureContractError, FigureReleaseMetrics, FigureTopView, FigureViolation } from '@hatiolab/figure-model'\n\nimport type { FigureFinding } from './figure-type.js'\n\n/** The version the format stamps on its own source. */\nexport const FIGURE_ASSET_VERSION = 'figure-asset-1'\n\n/** A source that passed the save door: what is stored, and the numbers the catalogue shows, computed here. */\nexport interface ParsedSource {\n /** The source to store, as the kernel serializes it. */\n source: string\n /** Computed from the source, never taken from the client -- a client's numbers could lie to the catalogue. */\n derived: { score: number; triangles: number; groups: number }\n /** Findings that do not block the save; the caller returns them with the answer. */\n violations: FigureFinding[]\n}\n\n/** The source breaks the format. It is not stored. */\nexport class FigureSourceError extends Error {\n readonly findings: FigureFinding[]\n\n constructor(findings: FigureFinding[]) {\n super(`정본이 형식을 어겼다 — ${findings.length}건: ${findings.map(f => f.code).join(', ')}`)\n this.name = 'FigureSourceError'\n this.findings = findings\n }\n}\n\n/** One thing the release gate found. `blocking` true means the figure cannot be published. */\nexport interface GateFinding {\n code: string\n message: string\n /** **Why it matters.** A measurement alone leaves the reader at \"so what?\". */\n why?: string\n /** **What to change.** By part name and value. */\n how?: string\n /** Where it points: the parts, or the path in the source. */\n at?: string\n blocking: boolean\n}\n\nexport interface Inspection {\n findings: GateFinding[]\n /** Whether anything blocks. The screen locks the button with this. */\n blocked: boolean\n}\n\n/**\n * That the source is JSON and names the format.\n *\n * @throws {FigureSourceError} when the text is not JSON, or does not carry `version: 'figure-asset-1'`.\n */\nfunction assertFormat(sourceJson: string): void {\n let parsed: unknown\n try {\n parsed = JSON.parse(sourceJson)\n } catch (e) {\n throw new FigureSourceError([\n { code: 'source-not-json', message: `정본이 JSON 이 아니다: ${(e as Error).message}`, at: 'source' }\n ])\n }\n if ((parsed as { version?: unknown } | null)?.version !== FIGURE_ASSET_VERSION)\n throw new FigureSourceError([\n { code: 'source-format-unknown', message: `정본의 판을 알 수 없다 — 도형 자산은 version '${FIGURE_ASSET_VERSION}' 을 적는다`, at: 'version' }\n ])\n}\n\n/**\n * Validates a source and computes what the catalogue shows: the cost from `figureCostOf`, and the score from `figureScoreOf`\n * -- the headroom against placing `LIMITS.screenInstances` of it on one screen (chief architect, 2026-09-24), the\n * same number the modeller's maturity tab shows. An asset that draws nothing has no score, not a full one.\n *\n * @throws {FigureSourceError} when the source breaks the format.\n */\nexport function parseSource(sourceJson: string): ParsedSource {\n assertFormat(sourceJson)\n let asset\n try {\n asset = parseFigureAsset(sourceJson)\n compileFigureAsset(asset)\n } catch (e) {\n const error = e as FigureContractError & { path?: string }\n throw new FigureSourceError([\n { code: error.code ?? 'asset-invalid', message: error.message ?? String(e), at: error.path }\n ])\n }\n const cost = figureCostOf(asset)\n const score = figureScoreOf(asset)\n return {\n source: serializeFigureAsset(asset),\n derived: { score: (score?.score ?? null) as unknown as number, triangles: cost.triangles, groups: cost.groups },\n violations: []\n }\n}\n\n/**\n * Whether this source may be published, and why not.\n *\n * The judgement is `inspectFigureAsset`: every part inside the volume the asset declares, over the whole\n * state range, and the face it says it is mounted on lying in that plane. A verdict reached by sampling is\n * reported as a finding of its own rather than hidden — an author releasing a figure the gate could only\n * sample should be told so, because nothing proved it holds between the states that were checked.\n */\n/** The request's translator. Always called with params — `t(key, undefined)` loses the locale. */\nexport type Translate = (key: string, options?: Record<string, unknown>) => string\n\n/**\n * A finding in the words of the release panel.\n *\n * The gate says what it measured as a code and values (`part`, `axis`, `mm`, `degrees`) and an English `detail`\n * for logs. The sentence an author reads is made here, where the request's language is known (ADR-0054) — not in\n * figure-model, which knows no locale, and not on the screen, which would then have a second account of the same\n * refusal. The English line is the fallback when a key is missing, so a missing translation reads as English\n * rather than as the key printed raw.\n *\n * The words are the modeller's: 「공간 선언」 for the volume a figure takes up and 「장착면」 for the face it stands on\n * (`figure.text.mounting-face-explained`). A fact, then the way out.\n */\nfunction figureWording(violation: FigureViolation, say: Say, hasParts: boolean): Pick<GateFinding, 'message' | 'why' | 'how'> {\n const part = violation.part ?? ''\n const axis = (violation.axis ?? '').toUpperCase()\n const mm = violation.mm ?? 0\n const degrees = violation.degrees ?? 0\n const key = (field: string) => `figure.gate.${violation.code.toLowerCase().replace(/_/g, '-')}.${field}`\n\n switch (violation.code) {\n case 'PART_OUTSIDE_OCCUPANCY':\n return {\n message: say(key('message'), { part, axis, mm }, `'${part}' goes ${mm} mm past the declared space on ${axis}.`),\n why: say(key('why'), {}, 'The declared space sets the scale a figure is placed at. What lies past it is left out of that scale, so on a board this part lands outside the place given to the figure.'),\n how: say(key('how'), { part, axis, mm }, `Use \"fit to parts\" to declare the space from the parts again, or move '${part}' ${mm} mm inward on ${axis}.`)\n }\n case 'OCCUPANCY_UNDECLARED':\n return violation.axis\n ? {\n message: say(`figure.gate.occupancy-empty-axis.message`, { axis }, `The declared space is empty on ${axis}.`),\n why: say(key('why'), {}, 'Without a declared space the scale a figure is placed at cannot be set.'),\n how: say(key('how'), {}, 'Use \"fit to parts\" to declare the space from the parts as they are now.')\n }\n : {\n message: say(key('message'), {}, 'This figure has not declared the space it takes up.'),\n why: say(key('why'), {}, 'Without a declared space the scale a figure is placed at cannot be set.'),\n /*\n A new figure is refused this way before it has a part, and 「fit to parts」 has nothing to measure then —\n the screen does not offer it. Telling the author to press it sent them looking for a button that was not\n there (:3300, 2026-09-24).\n */\n how: hasParts\n ? say(key('how'), {}, 'Use \"fit to parts\" to declare the space from the parts as they are now.')\n : say(key('how-no-parts'), {}, 'Add a shape first, then use \"fit to parts\" to declare the space from it.')\n }\n case 'PARTS_OFF_PLACEMENT_FACE':\n return {\n message: say(key('message'), { part, mm }, `The mounting face of '${part}' is ${mm} mm off the plane the figure stands on.`),\n why: say(key('why'), {}, 'The figure is declared to stand on that face. If the face does not reach the plane, the figure floats on the board by that much, and whatever is placed on it floats too.'),\n how: say(key('how'), { part, mm }, `Move '${part}' ${mm} mm on Y so the face meets the plane, or choose a different mounting face.`)\n }\n case 'PLACEMENT_FACE_TILTED':\n return {\n message: say(key('message'), { part, degrees }, `The mounting face of '${part}' is tilted ${degrees}° from the plane the figure stands on.`),\n why: say(key('why'), {}, 'A tilted face cannot lie on the plane, so how the figure stands is not settled.'),\n how: say(key('how'), { part }, `Choose a mounting face parallel to the plane, or turn '${part}' upright.`)\n }\n case 'PARTS_SPLIT_ON_RESIZE': {\n /*\n What comes off is every piece but the largest — the figure as the author sees it is the big piece, and the\n small ones are what they will see hanging in the air (2026-09-24).\n */\n const resize = violation.resize!\n const pieces = [...resize.pieces].sort((a, b) => b.length - a.length)\n const parts = pieces.slice(1).flat().map(one => `'${one}'`).join(', ')\n const axes = resize.axes.map(one => one.toUpperCase()).join('·')\n const factor = resize.factor\n return {\n message: say(key('message'), { parts, axes, factor }, `At ${factor}x on ${axes}, ${parts} come away from the rest of the figure.`),\n why: say(key('why'), {}, 'A figure placed at another size shows those parts hanging in the air — to a person, it looks broken.'),\n how: say(key('how'), { parts }, `Fasten ${parts} to a face of the part they rest on, or press \"fix here\" under placement, so they stay put when the figure is resized.`)\n }\n }\n case 'SIZING_NOT_UNIFORM': {\n /* The figure declares it grows by one ratio; these parts keep a size or place of their own (ruling 2026-09-24). */\n const resize = violation.resize!\n const parts = (violation.parts ?? []).map(one => `'${one}'`).join(', ')\n const axes = resize.axes.map(one => one.toUpperCase()).join('·')\n const factor = resize.factor\n return {\n message: say(key('message'), { parts, axes, factor }, `The figure grows by one ratio, but at ${factor}x on ${axes} ${parts} do not grow with it.`),\n why: say(key('why'), {}, 'A figure that grows by one ratio keeps its proportions at any size. A part with a size or place of its own comes apart from the rest.'),\n how: say(key('how'), { parts }, `Let ${parts} follow the figure's size (\"in proportion to size\" under size and placement), or remove their own fixed numbers.`)\n }\n }\n }\n}\n\ntype Say = (key: string, params: Record<string, string | number>, english: string) => string\n\n/*\n The sentence is shown as text, never as HTML, so the values go in as they are. i18next escapes interpolated\n values by default, and the screen then showed `'lamp-in'` as `'lamp-in'`.\n*/\nfunction sayerOf(t?: Translate): Say {\n return (key, params, english) =>\n t ? t(key, { ...params, defaultValue: english, interpolation: { escapeValue: false } }) : english\n}\n\nexport function inspectSource(\n sourceJson?: string | null,\n releasedJson?: string | null,\n t?: Translate,\n /** The row's `type` -- the only place the name lives, since the asset carries none. */\n rowType?: string\n): Inspection {\n if (!sourceJson) {\n return { blocked: true, findings: [{ code: 'no-source', message: '저장된 정본이 없다. 저장하면 발행할 수 있다', blocking: true }] }\n }\n try {\n assertFormat(sourceJson)\n } catch (e) {\n const findings = (e as FigureSourceError).findings ?? [{ code: 'source-unreadable', message: String(e) }]\n return { blocked: true, findings: findings.map(f => ({ ...f, blocking: true })) }\n }\n\n let inspection\n let asset: FigureAsset | undefined\n let hasParts = true\n try {\n asset = parseFigureAsset(sourceJson)\n inspection = inspectFigureAsset(asset)\n const cost = figureCostOf(asset)\n hasParts = cost.groups + cost.hidden > 0\n } catch (e) {\n const error = e as FigureContractError & { path?: string }\n return {\n blocked: true,\n findings: [{ code: error.code ?? 'asset-invalid', message: error.message ?? String(e), at: error.path, blocking: true }]\n }\n }\n\n const say = sayerOf(t)\n const findings: GateFinding[] = inspection.violations.map(violation => ({\n code: violation.code.toLowerCase().replace(/_/g, '-'),\n ...figureWording(violation, say, hasParts),\n at: violation.part,\n blocking: true\n }))\n /*\n A machine-made name is refused at release. The asset has no `type`, so the name is read off the row; a new figure\n was once published as `FIGURE_<uuid>` (:3300, 2026-09-24). Publishing freezes the name (ADR-0063 decision 4) and\n boards call a figure by it.\n */\n if (rowType && isPlaceholderType(rowType)) {\n findings.push({\n code: 'type-is-placeholder',\n message: say('figure.gate.type-is-placeholder.message', { type: rowType }, `'${rowType}' is a temporary type given when the figure was first saved.`),\n why: say('figure.gate.type-is-placeholder.why', {}, 'Boards call a figure by its type, and it cannot change once the figure is published.'),\n how: say('figure.gate.type-is-placeholder.how', {}, 'Set the type in the type field, then publish.'),\n blocking: true\n })\n }\n if (inspection.coverage.method === 'sampled') {\n const states = inspection.coverage.states\n findings.push({\n code: 'state-range-sampled',\n /* Sizes as well as motion: the instance size range is swept for every figure (figure-model, designer 2026-09-27). */\n message: say('figure.gate.state-range-sampled.message', { states }, `The whole range of sizes and motion could not be proven, so ${states} combinations of size and pose were checked.`),\n why: say('figure.gate.state-range-sampled.why', {}, 'Between the combinations that were checked, a part may still go past the declared space.'),\n how: say('figure.gate.state-range-sampled.how', {}, 'To cover the whole range, re-author what could not be proven.'),\n /*\n Which motions stopped the proof, by the name a person gave them: the kernel reports each operation it could\n not prove (`j2-shoulder.chain: compose@1 turns …`), and the screen showed that English list as it was. The\n name before the first dot is the joint or part; the operations stay in the logs.\n */\n at: [...new Set((inspection.coverage.unprovable ?? []).map(entry => entry.split(':')[0]!.split('.')[0]!.trim()).filter(Boolean))].join(' · ') || undefined,\n blocking: false\n })\n }\n /*\n Said, never refused (ADR-0104 decision 4): the origin is where the figure is fastened, and a door fastened at its\n hinge is a right model. A drawing that places figures by the middle of their room reads this before it pins one.\n */\n const origin = figureOriginCentredOf(asset!)\n if (origin && !origin.centred) {\n const off = origin.off.map(o => `${o.axis.toUpperCase()} ${o.mm} mm`).join(', ')\n findings.push({\n code: 'origin-off-centre',\n message: say('figure.gate.origin-off-centre.message', { off }, `The origin is off the middle of the declared room: ${off}.`),\n why: say('figure.gate.origin-off-centre.why', {}, 'A drawing that places figures by the middle of their room puts this one that far from where it is drawn.'),\n how: say('figure.gate.origin-off-centre.how', {}, 'Keep it when the figure is fastened there, as a door at its hinge. Move the origin to the middle to place it by its room.'),\n blocking: false\n })\n }\n if (releasedJson) findings.push(...figureContractFindings(asset!, releasedJson, say))\n return { blocked: findings.some(finding => finding.blocking), findings }\n}\n\n/**\n * The last release against this one — what the drawings that already hold this type lean on (ADR-0063 decision 3).\n *\n * figure-model names the changes and whether each blocks (`figureContractChanges`); the sentence is made here. When\n * the last release no longer parses, the gate says so rather than passing in silence: a release that was not checked\n * against the last one is a different fact from one that was checked and found safe.\n */\nfunction figureContractFindings(asset: FigureAsset, releasedJson: string, say: Say): GateFinding[] {\n let before: FigureAsset\n try {\n before = parseFigureAsset(releasedJson)\n } catch (e) {\n return [\n {\n code: 'released-unreadable',\n message: say('figure.gate.released-not-compared.message', { reason: (e as Error).message }, `The last release could not be read, so this release was not compared with it: ${(e as Error).message}`),\n why: say('figure.gate.released-not-compared.why', {}, 'Whether this release breaks the instances already placed has not been checked.'),\n blocking: false\n }\n ]\n }\n return figureContractChanges(before, asset).map(change => ({\n code: change.code,\n ...figureContractWording(change, say),\n at: change.subject,\n blocking: change.blocking\n }))\n}\n\n/** One contract change in the words of the release panel. The English line is the fallback for a missing key. */\nfunction figureContractWording(change: FigureContractChange, say: Say): Pick<GateFinding, 'message' | 'why' | 'how'> {\n const v = change.values\n const key = (field: string) => `figure.gate.contract.${change.code}.${field}`\n const gone = {\n how: say('figure.gate.contract.gone.how', {}, 'Put it back as it was, or publish this as a new figure type so the placed ones keep the old one.')\n }\n switch (change.code) {\n case 'placement-changed':\n return {\n message: say(key('message'), v, `Placement changed from ${v.before} to ${v.after}.`),\n why: say(key('why'), {}, 'Placement sets the height of every placed instance. They all move with it.'),\n ...gone\n }\n case 'size-inputs-gone':\n return {\n message: say(key('message'), v, 'The figure no longer takes its size from the place it is given.'),\n why: say(key('why'), {}, 'Placed instances were sized on the board. After this release they all draw at the authored size.'),\n ...gone\n }\n case 'size-inputs-added':\n return {\n message: say(key('message'), v, 'The figure now takes its size from the place it is given.'),\n why: say(key('why'), {}, 'Placed instances now follow their own size on the board.')\n }\n case 'size-range-narrowed':\n return {\n message: say(key('message'), { ...v, input: change.subject ?? '' }, `The size range of ${change.subject} narrowed from ${v.before} to ${v.after}.`),\n why: say(key('why'), {}, 'An instance placed at a size outside the new range will not draw. Which sizes are placed is not recorded.')\n }\n case 'authored-size-changed':\n return {\n message: say(key('message'), v, `The authored size changed from ${v.before} to ${v.after}.`),\n why: say(key('why'), {}, 'Placed instances keep their own size. An instance with no depth of its own takes its depth from this.')\n }\n case 'state-input-gone':\n return {\n message: say(key('message'), v, `The control '${v.id}' is gone.`),\n why: say(key('why'), {}, 'Placed instances and connectors give values under this name. After this release those values do nothing, and nothing on screen says why.'),\n ...gone\n }\n case 'state-unit-changed':\n return {\n message: say(key('message'), v, `The unit of the control '${v.id}' changed from ${v.before} to ${v.after}.`),\n why: say(key('why'), {}, 'Stored values keep their number. With another unit the same number means something else.'),\n ...gone\n }\n case 'state-range-narrowed':\n return {\n message: say(key('message'), v, `The range of the control '${v.id}' narrowed from ${v.before} to ${v.after}.`),\n why: say(key('why'), {}, 'A value outside the new range may already be stored on a placed instance.')\n }\n case 'state-input-added':\n return {\n message: say(key('message'), v, `The control '${v.id}' is new.`),\n why: say(key('why'), {}, 'Placed instances that give no value stand at its default.')\n }\n case 'clip-gone':\n return {\n message: say(key('message'), v, `The clip '${v.clip}' is gone.`),\n why: say(key('why'), {}, 'Placed instances play clips by name. After this release that one plays nothing.'),\n ...gone\n }\n case 'clip-added':\n return {\n message: say(key('message'), v, `The clip '${v.clip}' is new.`),\n why: say(key('why'), {}, 'It stays still until an instance plays it.')\n }\n case 'binding-gone':\n return {\n message: say(key('message'), v, `The place '${v.binding}' is gone.`),\n why: say(key('why'), {}, 'What sits in it on a placed instance loses its place.'),\n ...gone\n }\n case 'binding-role-changed':\n return {\n message: say(key('message'), v, `The place '${v.binding}' changed from ${v.before} to ${v.after}.`),\n why: say(key('why'), {}, 'What sits in it was placed for the old role.'),\n ...gone\n }\n case 'binding-capacity-reduced':\n return {\n message: say(key('message'), v, `The place '${v.binding}' holds ${v.after} instead of ${v.before}.`),\n why: say(key('why'), {}, 'What sits in the places that went has nowhere to go.'),\n ...gone\n }\n case 'binding-accepts-narrowed':\n return {\n message: say(key('message'), v, `The place '${v.binding}' no longer accepts ${v.types}.`),\n why: say(key('why'), {}, 'What already sits there stays. The next one of that type is refused.')\n }\n case 'binding-added':\n return {\n message: say(key('message'), v, `The place '${v.binding}' is new.`),\n why: say(key('why'), {}, 'Placed instances do not know it yet and stand as they are.')\n }\n case 'capability-gone':\n return {\n message: say(key('message'), v, `The capability '${v.name}' is gone.`),\n why: say(key('why'), {}, 'Scenes attach behaviour by this name. Placed instances lose what it did.'),\n ...gone\n }\n case 'capability-added':\n return {\n message: say(key('message'), v, `The capability '${v.name}' is new.`),\n why: say(key('why'), {}, 'It adds what the figure can do. Placed instances stand as they are.')\n }\n /* Said, never refused (ADR-0104): a consumer that pins a release reads these before it moves to this one. */\n case 'occupancy-changed':\n return {\n message: say(key('message'), v, `The room the figure takes changed from ${v.before || 'none'} to ${v.after || 'none'}.`),\n why: say(key('why'), {}, 'A consumer places the figure by this room. One that moves to this release sees it take the new room.')\n }\n case 'contact-face-changed':\n return {\n message: say(key('message'), v, 'The face the figure is mounted by changed.'),\n why: say(key('why'), {}, 'A consumer that moves to this release sees it stand on the new face.')\n }\n case 'public-input-gone':\n return {\n message: say(key('message'), { ...v, input: change.subject ?? '' }, `'${change.subject}' is no longer set on a board.`),\n why: say(key('why'), {}, 'Values placed instances give it do nothing after this release.')\n }\n case 'public-input-range-narrowed':\n return {\n message: say(key('message'), { ...v, input: change.subject ?? '' }, `The range of '${change.subject}' narrowed from ${v.before} to ${v.after}.`),\n why: say(key('why'), {}, 'An instance may have set a value outside the new range. Which values are placed is not recorded.')\n }\n case 'public-input-added':\n return {\n message: say(key('message'), { ...v, input: change.subject ?? '' }, `'${change.subject}' can now be set on a board.`),\n why: say(key('why'), {}, 'Placed instances that give no value stand at its default.')\n }\n }\n}\n\n/**\n * What a release records about itself (ADR-0104 decision 5): the format its asset is written in, what it measured\n * (its cost at its largest, whether resizing is stretching), its plan from above, and what changed against the previous release -- the kernel's own comparison, kept as it was then so a consumer that pins releases\n * reads it rather than computing it again. `[]` for a first release; `null` when the previous release could not be\n * read, which is a different fact from \"nothing changed\".\n */\nexport function releaseRecordOf(\n sourceJson: string,\n releasedJson?: string | null\n): { format: string; changes: FigureContractChange[] | null; metrics: FigureReleaseMetrics; topView: FigureTopView } {\n const asset = parseFigureAsset(sourceJson)\n const measured = { format: asset.version, metrics: figureReleaseMetricsOf(asset), topView: figureTopViewOf(asset, null) }\n if (!releasedJson) return { ...measured, changes: [] }\n let before: FigureAsset\n try {\n before = parseFigureAsset(releasedJson)\n } catch {\n return { ...measured, changes: null }\n }\n return { ...measured, changes: figureContractChanges(before, asset) }\n}\n\n/**\n * What a consumer is handed for a released figure: the asset itself, because what draws it -- `registerFigure` in\n * things-scene -- takes the asset and compiles it with the kernel.\n */\nexport function consumablePayloadOf(sourceJson: string): { asset: FigureAsset } {\n assertFormat(sourceJson)\n const asset = parseFigureAsset(sourceJson)\n compileFigureAsset(asset)\n return { asset }\n}\n"]}
|
|
@@ -22,17 +22,21 @@ export declare class FigureHistory implements HistoryEntityInterface<Figure> {
|
|
|
22
22
|
type?: string;
|
|
23
23
|
name?: string;
|
|
24
24
|
description?: string;
|
|
25
|
+
names?: Record<string, string>;
|
|
25
26
|
category?: string;
|
|
26
27
|
state?: 'draft' | 'released';
|
|
27
28
|
source?: string;
|
|
28
29
|
properties?: string;
|
|
29
30
|
thumbnail?: string;
|
|
31
|
+
thumbnailHash?: string;
|
|
30
32
|
score?: number;
|
|
31
33
|
triangles?: number;
|
|
32
34
|
groups?: number;
|
|
33
35
|
comment?: string;
|
|
34
36
|
assetFormat?: string;
|
|
35
37
|
changes?: string;
|
|
38
|
+
metrics?: string;
|
|
39
|
+
topView?: string;
|
|
36
40
|
createdAt?: Date;
|
|
37
41
|
updatedAt?: Date;
|
|
38
42
|
creator?: User;
|