@things-factory/figure-service 10.1.205 → 10.1.206
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/service/figure/event-subscriber.js +5 -0
- package/dist-server/service/figure/event-subscriber.js.map +1 -1
- package/dist-server/service/figure/figure-format.d.ts +7 -0
- package/dist-server/service/figure/figure-format.js +25 -3
- package/dist-server/service/figure/figure-format.js.map +1 -1
- package/dist-server/service/figure/figure-mutation.js +2 -0
- package/dist-server/service/figure/figure-mutation.js.map +1 -1
- package/dist-server/service/figure/figure-query.js +3 -5
- package/dist-server/service/figure/figure-query.js.map +1 -1
- package/dist-server/service/figure/figure-revert.js +1 -1
- package/dist-server/service/figure/figure-revert.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -8
- package/recipes/base-frame/recipe.json +4 -8
- package/recipes/belt-surface/recipe.json +2 -4
- package/recipes/bin-row/recipe.json +1 -2
- package/recipes/bollard-row/recipe.json +1 -2
- package/recipes/cabinet-doors/recipe.json +5 -10
- package/recipes/cable-tray/recipe.json +3 -6
- package/recipes/canopy/recipe.json +2 -4
- package/recipes/cart-handle/recipe.json +1 -2
- package/recipes/ceiling-rail-carriage/recipe.json +1 -2
- package/recipes/chain-rails/recipe.json +2 -4
- package/recipes/column-row/recipe.json +1 -2
- package/recipes/conveyor-skirts/recipe.json +4 -8
- package/recipes/double-hinged-door/recipe.json +6 -12
- package/recipes/drawer-stack/recipe.json +13 -26
- package/recipes/drip-tray/recipe.json +2 -4
- package/recipes/end-covers/recipe.json +4 -8
- package/recipes/ffu-row/recipe.json +1 -2
- package/recipes/floor-zone/recipe.json +4 -8
- package/recipes/folding-door/recipe.json +3 -6
- package/recipes/gantry-frame/recipe.json +1 -2
- package/recipes/guard-cover/recipe.json +2 -4
- package/recipes/handrail/recipe.json +2 -4
- package/recipes/hatch/recipe.json +4 -8
- package/recipes/hinged-door/recipe.json +4 -8
- package/recipes/hopper/recipe.json +1 -2
- package/recipes/ladder/recipe.json +3 -6
- package/recipes/lift-updown/recipe.json +2 -4
- package/recipes/linear-guide/recipe.json +1 -2
- package/recipes/locker-row/recipe.json +1 -2
- package/recipes/observation-window/recipe.json +4 -8
- package/recipes/operator-panel/recipe.json +4 -8
- package/recipes/partition-wall/recipe.json +3 -6
- package/recipes/peg-board/facts.md +1 -1
- package/recipes/peg-board/recipe.json +1 -2
- package/recipes/pipe-run/recipe.json +1 -2
- package/recipes/platform/recipe.json +2 -4
- package/recipes/pull-cord/recipe.json +1 -2
- package/recipes/roll-up-shutter/recipe.json +3 -6
- package/recipes/roller-row/recipe.json +2 -4
- package/recipes/room-shell/recipe.json +6 -12
- package/recipes/safety-fence/recipe.json +2 -4
- package/recipes/safety-mat/recipe.json +1 -2
- package/recipes/shelf-tiers/recipe.json +5 -10
- package/recipes/side-guides/recipe.json +2 -4
- package/recipes/sign-board/recipe.json +1 -2
- package/recipes/sliding-door/recipe.json +7 -14
- package/recipes/stairs/recipe.json +1 -2
- package/recipes/stopper/recipe.json +1 -2
- package/recipes/suction-pads/recipe.json +1 -2
- package/recipes/tank/recipe.json +2 -4
- package/recipes/toe-board/recipe.json +1 -2
- package/recipes/traffic-cones/recipe.json +1 -2
- package/recipes/transfer-belts/recipe.json +2 -4
- package/recipes/trough/recipe.json +1 -2
- package/recipes/turntable/recipe.json +1 -2
- package/recipes/workbench-top/recipe.json +2 -4
- package/server/service/figure/event-subscriber.ts +4 -0
- package/server/service/figure/figure-blueprint-query.test.ts +3 -0
- package/server/service/figure/figure-context.test.ts +1 -1
- package/server/service/figure/figure-format.ts +27 -5
- package/server/service/figure/figure-mutation.ts +2 -0
- package/server/service/figure/figure-query.ts +3 -6
- package/server/service/figure/figure-release-size-db.test.ts +156 -0
- package/server/service/figure/figure-revert.ts +2 -2
|
@@ -65,8 +65,7 @@
|
|
|
65
65
|
"of": "part",
|
|
66
66
|
"part": "{target}",
|
|
67
67
|
"axis": "z"
|
|
68
|
-
}
|
|
69
|
-
"narrowRange": true
|
|
68
|
+
}
|
|
70
69
|
},
|
|
71
70
|
{
|
|
72
71
|
"kind": "link-dimension",
|
|
@@ -76,8 +75,7 @@
|
|
|
76
75
|
"of": "part",
|
|
77
76
|
"part": "{target}",
|
|
78
77
|
"axis": "z"
|
|
79
|
-
}
|
|
80
|
-
"narrowRange": true
|
|
78
|
+
}
|
|
81
79
|
},
|
|
82
80
|
{
|
|
83
81
|
"kind": "set-joint",
|
|
@@ -55,8 +55,7 @@
|
|
|
55
55
|
"of": "part",
|
|
56
56
|
"part": "{target}",
|
|
57
57
|
"axis": "x"
|
|
58
|
-
}
|
|
59
|
-
"narrowRange": true
|
|
58
|
+
}
|
|
60
59
|
},
|
|
61
60
|
{
|
|
62
61
|
"kind": "link-dimension",
|
|
@@ -66,8 +65,7 @@
|
|
|
66
65
|
"of": "part",
|
|
67
66
|
"part": "{target}",
|
|
68
67
|
"axis": "z"
|
|
69
|
-
}
|
|
70
|
-
"narrowRange": true
|
|
68
|
+
}
|
|
71
69
|
},
|
|
72
70
|
{
|
|
73
71
|
"kind": "set-joint",
|
|
@@ -12,6 +12,8 @@ import { FigureHistory } from './figure-history.js'
|
|
|
12
12
|
* 그 전부를 남기면 판본 목록이 「내가 오늘 몇 번 눌렀나」가 된다. 사람이 판이라고 부르는 것은
|
|
13
13
|
* **발행한 것**이므로 그때만 베낀다. board 가 같은 규칙으로 돈다(`board-service` 의 같은 이름 파일).
|
|
14
14
|
*
|
|
15
|
+
* 발행판의 정본(`__releaseSource`)은 크기 범위를 적은 발행판 사본이다 — Figure 행에는 작성자가 좁힌 끝만 남는다.
|
|
16
|
+
*
|
|
15
17
|
* 발행 메모는 Figure 의 칸이 아니다. `releaseFigure` 가 저장할 엔티티에 `__releaseComment` 로 얹어
|
|
16
18
|
* 두면 여기서 판본의 `comment` 로 옮긴다 — 메모는 그 판의 것이지 도형의 것이 아니다. 정본 형식
|
|
17
19
|
* (`__releaseFormat`), 잰 커널의 판(`__releaseKernel`, ADR-0109 C1), 직전 판에 견준 변경(`__releaseChanges`), 발행 때 잰 값(`__releaseMetrics`)과
|
|
@@ -20,6 +22,8 @@ import { FigureHistory } from './figure-history.js'
|
|
|
20
22
|
/** What `releaseFigure` laid on the entity for this release, moved onto the history row. */
|
|
21
23
|
function releaseRecordOnto(history: FigureHistory, entity: Figure): FigureHistory {
|
|
22
24
|
const e = entity as any
|
|
25
|
+
/* What a board reads of this release: its own copy, with the size range written down (ADR-0087 decision 6). */
|
|
26
|
+
if (e.__releaseSource) history.source = e.__releaseSource
|
|
23
27
|
;(history as any).comment = e.__releaseComment ?? null
|
|
24
28
|
history.assetFormat = e.__releaseFormat ?? null
|
|
25
29
|
history.kernelVersion = e.__releaseKernel ?? null
|
|
@@ -44,6 +44,8 @@ beforeAll(async () => {
|
|
|
44
44
|
Figure = loadCompiled('service/figure/figure').Figure
|
|
45
45
|
FigureHistory = loadCompiled('service/figure/figure-history').FigureHistory
|
|
46
46
|
FigureQuery = loadCompiled('service/figure/figure-query').FigureQuery
|
|
47
|
+
/* A released row is saved with its history row, as releasing does: a board reads the release's own copy there (ADR-0087 decision 6). */
|
|
48
|
+
const Subscriber = loadCompiled('service/figure/event-subscriber').FigureHistoryEntitySubscriber
|
|
47
49
|
|
|
48
50
|
dataSource = new TypeOrmDataSource({
|
|
49
51
|
type: 'sqlite',
|
|
@@ -52,6 +54,7 @@ beforeAll(async () => {
|
|
|
52
54
|
dropSchema: true,
|
|
53
55
|
namingStrategy: new shell.NamingStrategy(),
|
|
54
56
|
entities: [...shell.entities, ...authBase.entities, Figure, FigureHistory],
|
|
57
|
+
subscribers: [Subscriber],
|
|
55
58
|
logging: false
|
|
56
59
|
})
|
|
57
60
|
|
|
@@ -193,7 +193,7 @@ describe('a proposal changes only what it was shown', () => {
|
|
|
193
193
|
it('a shown part may change, a new part may be fastened to any part, the figure as a whole may change', () => {
|
|
194
194
|
const steps = [
|
|
195
195
|
{ kind: 'set-material', target: 'shelf-2', material: { token: 'palette.primary' } },
|
|
196
|
-
{ kind: 'add-part', part: { id: 'lamp', kind: 'sphere', dimensions: {
|
|
196
|
+
{ kind: 'add-part', part: { id: 'lamp', kind: 'sphere', dimensions: { radius: 40, radiusY: 40 } } },
|
|
197
197
|
{ kind: 'attach', part: 'lamp', to: { part: 'shelf-9', face: 'top' } },
|
|
198
198
|
{ kind: 'link-dimension', part: 'lamp', axis: 'x', source: { of: 'part', part: 'shelf-7', axis: 'x' }, times: 0.1 },
|
|
199
199
|
{ kind: 'fit-room', placement: 'floor' }
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* The doors a stored source goes through: saving (`parseSource`), publishing (`inspectSource
|
|
3
|
-
*
|
|
2
|
+
* The doors a stored source goes through: saving (`parseSource`), publishing (`inspectSource`, and the release's own
|
|
3
|
+
* copy in `releaseRecordOf`), bringing a release back as a draft (`draftSourceOfRelease`) and handing it to a board
|
|
4
|
+
* (`consumablePayloadOf`). The format is `figure-asset-1`, and the asset says so in its `version`; a source
|
|
4
5
|
* that does not is refused by name, because a source the server cannot identify is one it must not save.
|
|
5
6
|
*/
|
|
6
|
-
import { FIGURE_KERNEL_VERSION, compileFigureAsset, inspectFigureAsset, isPlaceholderType, parseFigureAsset, serializeFigureAsset, figureContractChanges, figureCostOf, figureOriginCentredOf, figurePlaneGapOf, figureReleaseMetricsOf, figureScoreOf, figureTopViewOf } from '@hatiolab/figure-model'
|
|
7
|
+
import { FIGURE_KERNEL_VERSION, compileFigureAsset, inspectFigureAsset, isPlaceholderType, parseFigureAsset, serializeFigureAsset, figureContractChanges, figureCostOf, figureOriginCentredOf, figurePlaneGapOf, figureReleaseMetricsOf, figureScoreOf, figureTopViewOf, forgetRecordedInstanceSize, recordFigureInstanceSize } from '@hatiolab/figure-model'
|
|
7
8
|
import type { FigureAsset, FigureContractChange, FigureContractError, FigureReleaseMetrics, FigureTopView, FigureViolation } from '@hatiolab/figure-model'
|
|
8
9
|
|
|
9
10
|
import type { FigureFinding } from './figure-type.js'
|
|
@@ -711,9 +712,20 @@ function figureContractWording(change: FigureContractChange, say: Say): Pick<Gat
|
|
|
711
712
|
export function releaseRecordOf(
|
|
712
713
|
sourceJson: string,
|
|
713
714
|
releasedJson?: string | null
|
|
714
|
-
): { format: string; kernel: string; changes: FigureContractChange[] | null; metrics: FigureReleaseMetrics; topView: FigureTopView } {
|
|
715
|
+
): { source: string; format: string; kernel: string; changes: FigureContractChange[] | null; metrics: FigureReleaseMetrics; topView: FigureTopView } {
|
|
715
716
|
const asset = parseFigureAsset(sourceJson)
|
|
716
|
-
|
|
717
|
+
/*
|
|
718
|
+
The release's own copy carries its size range as it is now: the author's ends, and the rest as far as the parts
|
|
719
|
+
allow (ADR-0087 decision 6). A board reads the ends from it and does not measure; the figure's row keeps only what
|
|
720
|
+
the author narrowed.
|
|
721
|
+
*/
|
|
722
|
+
const measured = {
|
|
723
|
+
source: serializeFigureAsset(recordFigureInstanceSize(asset)),
|
|
724
|
+
format: asset.version,
|
|
725
|
+
kernel: FIGURE_KERNEL_VERSION,
|
|
726
|
+
metrics: figureReleaseMetricsOf(asset),
|
|
727
|
+
topView: figureTopViewOf(asset, null)
|
|
728
|
+
}
|
|
717
729
|
if (!releasedJson) return { ...measured, changes: [] }
|
|
718
730
|
let before: FigureAsset
|
|
719
731
|
try {
|
|
@@ -724,6 +736,16 @@ export function releaseRecordOf(
|
|
|
724
736
|
return { ...measured, changes: figureContractChanges(before, asset) }
|
|
725
737
|
}
|
|
726
738
|
|
|
739
|
+
/**
|
|
740
|
+
* A release's source made a draft's again (a revert): the size ends its release wrote down, as far as the parts allow,
|
|
741
|
+
* are opened, and the author's own are kept (ADR-0087 decision 6). An author's end that equals the computed one is
|
|
742
|
+
* opened with them.
|
|
743
|
+
*/
|
|
744
|
+
export function draftSourceOfRelease(releasedJson: string): string {
|
|
745
|
+
assertFormat(releasedJson)
|
|
746
|
+
return serializeFigureAsset(forgetRecordedInstanceSize(parseFigureAsset(releasedJson)))
|
|
747
|
+
}
|
|
748
|
+
|
|
727
749
|
/**
|
|
728
750
|
* What a consumer is handed for a released figure: the asset itself, because what draws it -- `registerFigure` in
|
|
729
751
|
* things-scene -- takes the asset and compiles it with the kernel.
|
|
@@ -447,6 +447,8 @@ export class FigureMutation {
|
|
|
447
447
|
state: 'released',
|
|
448
448
|
updater: user,
|
|
449
449
|
/* 판본 구독자가 이것들을 판본의 comment · assetFormat · kernelVersion · changes 로 옮긴다. Figure 의 칸이 아니다. */
|
|
450
|
+
/* The release's own copy, its size range written down; the figure keeps the author's (ADR-0087 decision 6). */
|
|
451
|
+
__releaseSource: record.source,
|
|
450
452
|
__releaseComment: comment ?? null,
|
|
451
453
|
__releaseFormat: record.format,
|
|
452
454
|
__releaseKernel: record.kernel,
|
|
@@ -442,8 +442,9 @@ async function releaseNumbered(figure: Figure, version: number): Promise<{ figur
|
|
|
442
442
|
/**
|
|
443
443
|
* Finds the released source of a type in one domain, or null.
|
|
444
444
|
*
|
|
445
|
-
*
|
|
446
|
-
*
|
|
445
|
+
* Always the latest release's own copy, from history: it carries the size range written down at release (ADR-0087
|
|
446
|
+
* decision 6), and the figure's row -- released or a draft -- keeps only the ends its author narrowed. A figure that
|
|
447
|
+
* was never released gives null, because drafts never reach consumers.
|
|
447
448
|
*/
|
|
448
449
|
async function releasedSourceOf(
|
|
449
450
|
domainId: string,
|
|
@@ -452,10 +453,6 @@ async function releasedSourceOf(
|
|
|
452
453
|
const figure = await getRepository(Figure).findOne({ where: { domain: { id: domainId }, type } })
|
|
453
454
|
if (!figure) return null
|
|
454
455
|
|
|
455
|
-
if (figure.state === 'released' && figure.source) {
|
|
456
|
-
return { figure, source: figure.source, version: figure.version }
|
|
457
|
-
}
|
|
458
|
-
|
|
459
456
|
const latestReleased = await getRepository(FigureHistory).findOne({
|
|
460
457
|
where: { domain: { id: domainId }, originalId: figure.id, state: 'released' },
|
|
461
458
|
order: { version: 'DESC' }
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The size range a board reads is the release's own copy (ADR-0087 decision 10, user 2026-10-21). The figure's row keeps
|
|
3
|
+
* only the ends its author narrowed; a release writes the rest down as far as the parts allow, on its history row. Every
|
|
4
|
+
* door a release leaves by -- one figure for a board, several by full name, the package another server imports -- hands
|
|
5
|
+
* out that copy, never the row. A release made a draft again opens what the release wrote down.
|
|
6
|
+
*/
|
|
7
|
+
import type { DataSource } from 'typeorm'
|
|
8
|
+
|
|
9
|
+
const { DataSource: TypeOrmDataSource } = require('typeorm')
|
|
10
|
+
const { createHash } = require('crypto')
|
|
11
|
+
const JSZip = require('jszip')
|
|
12
|
+
const shell = require('@things-factory/shell')
|
|
13
|
+
const authBase = require('@things-factory/auth-base')
|
|
14
|
+
const settingBase = require('@things-factory/setting-base')
|
|
15
|
+
const { createFigureAsset, addFigureBox, serializeFigureAsset, applyFigureAuthoring, proposeFigureOccupancy, readFigurePackage } = require('@hatiolab/figure-model')
|
|
16
|
+
|
|
17
|
+
let dataSource: DataSource | null = null
|
|
18
|
+
let Figure: any
|
|
19
|
+
let FigureMutation: any
|
|
20
|
+
let FigureQuery: any
|
|
21
|
+
let figurePackageOf: any
|
|
22
|
+
let revertKeepingDraft: any
|
|
23
|
+
let FigureHistory: any
|
|
24
|
+
let FigureNamespace: any
|
|
25
|
+
|
|
26
|
+
function loadCompiled(modulePath: string): any {
|
|
27
|
+
const { existsSync } = require('fs')
|
|
28
|
+
const { join } = require('path')
|
|
29
|
+
const resolved = join(__dirname, '..', '..', '..', 'dist-server', `${modulePath}.js`)
|
|
30
|
+
if (!existsSync(resolved))
|
|
31
|
+
throw new Error(`figure-service 산출물이 없다: ${resolved}\n먼저 빌드할 것:\n yarn workspace @things-factory/figure-service build:server`)
|
|
32
|
+
return require(resolved)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
beforeAll(async () => {
|
|
36
|
+
Figure = loadCompiled('service/figure/figure').Figure
|
|
37
|
+
FigureHistory = loadCompiled('service/figure/figure-history').FigureHistory
|
|
38
|
+
FigureNamespace = loadCompiled('service/figure/figure-namespace-entity').FigureNamespace
|
|
39
|
+
const FigureReplacedDraft = loadCompiled('service/figure/figure-replaced-draft').FigureReplacedDraft
|
|
40
|
+
const Subscriber = loadCompiled('service/figure/event-subscriber').FigureHistoryEntitySubscriber
|
|
41
|
+
;({ FigureMutation } = loadCompiled('service/figure/figure-mutation'))
|
|
42
|
+
;({ FigureQuery } = loadCompiled('service/figure/figure-query'))
|
|
43
|
+
;({ figurePackageOf } = loadCompiled('service/figure/figure-package-export'))
|
|
44
|
+
;({ revertKeepingDraft } = loadCompiled('service/figure/figure-revert'))
|
|
45
|
+
dataSource = new TypeOrmDataSource({
|
|
46
|
+
type: 'sqlite',
|
|
47
|
+
database: ':memory:',
|
|
48
|
+
synchronize: true,
|
|
49
|
+
dropSchema: true,
|
|
50
|
+
namingStrategy: new shell.NamingStrategy(),
|
|
51
|
+
entities: [...shell.entities, ...authBase.entities, ...settingBase.entities, Figure, FigureHistory, FigureNamespace, FigureReplacedDraft],
|
|
52
|
+
subscribers: [Subscriber],
|
|
53
|
+
logging: false
|
|
54
|
+
})
|
|
55
|
+
await dataSource.initialize()
|
|
56
|
+
shell.addDataSource('default', dataSource)
|
|
57
|
+
shell.addDataSource('tx', dataSource)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
afterAll(async () => {
|
|
61
|
+
await dataSource?.destroy()
|
|
62
|
+
dataSource = null
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
/*
|
|
66
|
+
The user's figure (2026-10-21): a box that follows the figure's size, with a cavity ticked on it. Below twice its wall it
|
|
67
|
+
has no inside, so its width has a smallest size its parts set; nothing limits how large it grows. The height is
|
|
68
|
+
narrowed by the author, 50 to 400 mm.
|
|
69
|
+
*/
|
|
70
|
+
const buttonBox = () => {
|
|
71
|
+
let a = addFigureBox(createFigureAsset('Button box'), { id: 'box', size: { x: 120, y: 60, z: 80 }, position: { x: 0, y: 30, z: 0 } })
|
|
72
|
+
a = applyFigureAuthoring(a, { kind: 'attach', part: 'box', to: { plane: 'mounting-plane' } })
|
|
73
|
+
a = applyFigureAuthoring(a, { kind: 'follow-size', part: 'box' })
|
|
74
|
+
a = applyFigureAuthoring(a, { kind: 'set-cavity', part: 'box', cavity: { wall: 10, start: { closed: 10 }, end: 'open' } })
|
|
75
|
+
a = applyFigureAuthoring(a, { kind: 'declare-instance-size', range: { y: { min: 50, max: 400 } }, replace: true })
|
|
76
|
+
return serializeFigureAsset(applyFigureAuthoring(a, { kind: 'declare-occupancy', proposal: proposeFigureOccupancy(a, { over: 'range' }), placement: 'floor' }))
|
|
77
|
+
}
|
|
78
|
+
const sizeOf = (source: string | object, axis: 'x' | 'y' | 'z') => {
|
|
79
|
+
const asset = typeof source === 'string' ? JSON.parse(source) : source
|
|
80
|
+
const { min, max } = asset.document.model.inputs.find((i: any) => i.id === `size.${axis}`)
|
|
81
|
+
return { min, max }
|
|
82
|
+
}
|
|
83
|
+
/** The width's smallest size as the parts set it: twice the wall, and the kernel's numerical step above it. */
|
|
84
|
+
const widthFloor = (min: number | undefined) => typeof min === 'number' && min > 20 && min < 20.001
|
|
85
|
+
|
|
86
|
+
let seq = 0
|
|
87
|
+
async function domainWith(namespace: string) {
|
|
88
|
+
const name = `size-${++seq}`
|
|
89
|
+
const domain = await dataSource!.getRepository(shell.Domain).save({ name, subdomain: name })
|
|
90
|
+
await dataSource!.getRepository(FigureNamespace).save({ domain, name: namespace, state: 'active', isDefault: true })
|
|
91
|
+
return { domain, context: { state: { domain, user: undefined, tx: dataSource!.manager } } }
|
|
92
|
+
}
|
|
93
|
+
const creationId = () => require('crypto').randomUUID()
|
|
94
|
+
|
|
95
|
+
async function released(type: string) {
|
|
96
|
+
const ns = `size-${++seq}.hatiolab.com`
|
|
97
|
+
const { domain, context } = await domainWith(ns)
|
|
98
|
+
const saved = await new FigureMutation().saveFigure(buttonBox(), context, undefined, undefined, creationId(), undefined, type, undefined, undefined)
|
|
99
|
+
await new FigureMutation().releaseFigure(saved.id, 'first', context)
|
|
100
|
+
const figure = await dataSource!.getRepository(Figure).findOneOrFail({ where: { id: saved.id } })
|
|
101
|
+
const history = await dataSource!.getRepository(FigureHistory).findOneOrFail({ where: { originalId: saved.id, version: 1 } })
|
|
102
|
+
return { ns, domain, context, figure, history }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
describe('the size range a release carries', () => {
|
|
106
|
+
it('is written on the release, and the figure keeps only what its author narrowed', async () => {
|
|
107
|
+
const { figure, history } = await released('button-box')
|
|
108
|
+
expect(figure.state).toBe('released')
|
|
109
|
+
expect(sizeOf(figure.source, 'x')).toEqual({ min: undefined, max: undefined })
|
|
110
|
+
expect(sizeOf(figure.source, 'y')).toEqual({ min: 50, max: 400 })
|
|
111
|
+
const x = sizeOf(history.source, 'x')
|
|
112
|
+
expect(widthFloor(x.min)).toBe(true)
|
|
113
|
+
expect(x.max).toBeUndefined()
|
|
114
|
+
expect(sizeOf(history.source, 'y')).toEqual({ min: 50, max: 400 })
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('is what every door a release leaves by hands out: one for a board, several by full name, a package', async () => {
|
|
118
|
+
const { ns, domain, context, history } = await released('button-box')
|
|
119
|
+
const one = await new FigureQuery().figureBlueprint('button-box', context)
|
|
120
|
+
expect(widthFloor(sizeOf(one.asset, 'x').min)).toBe(true)
|
|
121
|
+
const pinned = await new FigureQuery().figureBlueprint('button-box', context, 1)
|
|
122
|
+
expect(widthFloor(sizeOf(pinned.asset, 'x').min)).toBe(true)
|
|
123
|
+
const several = await new FigureQuery().figureBlueprints([`${ns}/button-box`], context)
|
|
124
|
+
expect(widthFloor(sizeOf(several.items[0].asset, 'x').min)).toBe(true)
|
|
125
|
+
|
|
126
|
+
const { bytes } = await figurePackageOf({
|
|
127
|
+
refs: [`${ns}/button-box@1`],
|
|
128
|
+
domain,
|
|
129
|
+
exportedBy: 'author',
|
|
130
|
+
sourceUrl: 'https://figure.hatiolab.com',
|
|
131
|
+
files: { put: async () => ({}), send: async () => {}, read: async () => Buffer.alloc(0) }
|
|
132
|
+
})
|
|
133
|
+
const zip = await JSZip.loadAsync(bytes)
|
|
134
|
+
const files = new Map<string, Uint8Array>()
|
|
135
|
+
for (const path of Object.keys(zip.files)) if (!zip.files[path].dir) files.set(path, await zip.files[path].async('uint8array'))
|
|
136
|
+
const read = readFigurePackage(files, (b: Uint8Array) => createHash('sha256').update(b).digest('hex'))
|
|
137
|
+
expect(read.releases[0].source).toBe(history.source)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('released again unchanged, nothing is said to have narrowed', async () => {
|
|
141
|
+
const { figure, context } = await released('button-box')
|
|
142
|
+
await dataSource!.getRepository(Figure).save({ ...figure, state: 'draft' })
|
|
143
|
+
await new FigureMutation().releaseFigure(figure.id, 'again', context)
|
|
144
|
+
const second = await dataSource!.getRepository(FigureHistory).findOneOrFail({ where: { originalId: figure.id, version: 2 } })
|
|
145
|
+
expect(JSON.parse(second.changes)).toEqual([])
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('made a draft again by a revert, what the release wrote down is opened and the author keeps theirs', async () => {
|
|
149
|
+
const { figure, domain } = await released('button-box')
|
|
150
|
+
const draft = await dataSource!.getRepository(Figure).save({ ...figure, state: 'draft' })
|
|
151
|
+
await dataSource!.transaction(tx => revertKeepingDraft(tx, domain, undefined, { id: figure.id, version: 1, requestId: creationId(), expectedSource: draft.source }))
|
|
152
|
+
const back = await dataSource!.getRepository(Figure).findOneOrFail({ where: { id: figure.id } })
|
|
153
|
+
expect(sizeOf(back.source, 'x')).toEqual({ min: undefined, max: undefined })
|
|
154
|
+
expect(sizeOf(back.source, 'y')).toEqual({ min: 50, max: 400 })
|
|
155
|
+
})
|
|
156
|
+
})
|
|
@@ -5,7 +5,7 @@ import { GraphQLError } from 'graphql'
|
|
|
5
5
|
import { Figure } from './figure.js'
|
|
6
6
|
import { FigureHistory } from './figure-history.js'
|
|
7
7
|
import { FigureReplacedDraft } from './figure-replaced-draft.js'
|
|
8
|
-
import { parseSource } from './figure-format.js'
|
|
8
|
+
import { draftSourceOfRelease, parseSource } from './figure-format.js'
|
|
9
9
|
import { sourceConflict } from './figure-write.js'
|
|
10
10
|
|
|
11
11
|
/*
|
|
@@ -77,7 +77,7 @@ export async function revertKeepingDraft(
|
|
|
77
77
|
})
|
|
78
78
|
if (!history) throw refused('FIGURE_VERSION_NOT_FOUND', `Figure '${figure.type}' 에 ${request.version} 판이 없습니다.`)
|
|
79
79
|
/* The format is checked again: an old version may break today's rules, and is not let in quietly. */
|
|
80
|
-
const { source, derived, violations } = parseSource(history.source)
|
|
80
|
+
const { source, derived, violations } = parseSource(draftSourceOfRelease(history.source!))
|
|
81
81
|
|
|
82
82
|
const record = await keep(kept, {
|
|
83
83
|
domain: { id: domain.id },
|