@things-factory/figure-service 10.1.157 → 10.1.159
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.d.ts +1 -0
- package/dist-server/index.js +2 -0
- package/dist-server/index.js.map +1 -1
- package/dist-server/service/figure/figure-namespace.d.ts +4 -2
- package/dist-server/service/figure/figure-namespace.js +6 -3
- package/dist-server/service/figure/figure-namespace.js.map +1 -1
- package/dist-server/service/figure/figure-subject.d.ts +12 -0
- package/dist-server/service/figure/figure-subject.js +24 -0
- package/dist-server/service/figure/figure-subject.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/server/index.ts +2 -0
- package/server/service/figure/figure-namespace-db.test.ts +16 -6
- package/server/service/figure/figure-namespace.ts +6 -3
- package/server/service/figure/figure-subject.test.ts +41 -0
- package/server/service/figure/figure-subject.ts +21 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/figure-service",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.159",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"@things-factory:registry": "https://registry.npmjs.org"
|
|
@@ -27,16 +27,16 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@hatiolab/figure-model": "^0.6.5",
|
|
29
29
|
"@operato/typeorm-history": "^10.0.0",
|
|
30
|
-
"@things-factory/ai-client-base": "^10.1.
|
|
31
|
-
"@things-factory/attachment-base": "^10.1.
|
|
32
|
-
"@things-factory/auth-base": "^10.1.
|
|
30
|
+
"@things-factory/ai-client-base": "^10.1.159",
|
|
31
|
+
"@things-factory/attachment-base": "^10.1.159",
|
|
32
|
+
"@things-factory/auth-base": "^10.1.159",
|
|
33
33
|
"@things-factory/env": "^10.1.149",
|
|
34
|
-
"@things-factory/setting-base": "^10.1.
|
|
34
|
+
"@things-factory/setting-base": "^10.1.159",
|
|
35
35
|
"@things-factory/shell": "^10.1.157",
|
|
36
36
|
"graphql-scalars": "^1.22.4",
|
|
37
37
|
"graphql-upload": "^15.0.2",
|
|
38
38
|
"jszip": "^3.10.1",
|
|
39
39
|
"koa-router": "^7.4.0"
|
|
40
40
|
},
|
|
41
|
-
"gitHead": "
|
|
41
|
+
"gitHead": "fc15cdcb18e562ca1e723a1e1bf042f9582cbbfb"
|
|
42
42
|
}
|
package/server/index.ts
CHANGED
|
@@ -9,6 +9,8 @@ import './routes.js'
|
|
|
9
9
|
*/
|
|
10
10
|
import { registerFigureToolCategories } from './service/figure/figure-tools.js'
|
|
11
11
|
registerFigureToolCategories()
|
|
12
|
+
/* The figure the modeller has open, said to the AI each turn (design/ai/screen-pattern.md §2-1). */
|
|
13
|
+
import './service/figure/figure-subject.js'
|
|
12
14
|
|
|
13
15
|
/*
|
|
14
16
|
* The recipe templates are read once at start (ADR-0095, amended): what was served, and any file whose form is wrong,
|
|
@@ -90,15 +90,25 @@ describe('the namespace a figure is created under', () => {
|
|
|
90
90
|
expect(figure.type).toBe('conveyor')
|
|
91
91
|
})
|
|
92
92
|
|
|
93
|
-
it('
|
|
94
|
-
for (const value of [null, '
|
|
95
|
-
const { context } = await domainWith(value)
|
|
96
|
-
await
|
|
97
|
-
|
|
98
|
-
).rejects.toMatchObject({ code: 'figure-namespace-missing' })
|
|
93
|
+
it('with no setting, or an empty one, is the subdomain', async () => {
|
|
94
|
+
for (const value of [null, '', ' ']) {
|
|
95
|
+
const { domain, context } = await domainWith(value)
|
|
96
|
+
const figure = await new FigureMutation().saveFigure(releasable(), context, undefined, undefined, creationId(), undefined, 'conveyor')
|
|
97
|
+
expect(figure.namespace).toBe(domain.subdomain)
|
|
99
98
|
}
|
|
100
99
|
})
|
|
101
100
|
|
|
101
|
+
it('is refused by name when the setting is not a namespace, or there is none and the subdomain is not one either', async () => {
|
|
102
|
+
const { context } = await domainWith('Hatiolab.COM')
|
|
103
|
+
await expect(
|
|
104
|
+
new FigureMutation().saveFigure(releasable(), context, undefined, undefined, creationId(), undefined, 'conveyor')
|
|
105
|
+
).rejects.toMatchObject({ code: 'figure-namespace-missing' })
|
|
106
|
+
const odd = await dataSource!.getRepository(shell.Domain).save({ name: `odd-${++seq}`, subdomain: 'Odd_Sub' })
|
|
107
|
+
await expect(
|
|
108
|
+
new FigureMutation().saveFigure(releasable(), { state: { domain: odd, user: undefined, tx: dataSource!.manager } }, undefined, undefined, creationId(), undefined, 'conveyor')
|
|
109
|
+
).rejects.toMatchObject({ code: 'figure-namespace-missing' })
|
|
110
|
+
})
|
|
111
|
+
|
|
102
112
|
it('cannot change: a new figure or a release is refused while the setting disagrees with the figures already made', async () => {
|
|
103
113
|
const { domain, context } = await domainWith('hatiolab.com')
|
|
104
114
|
const draft = await new FigureMutation().saveFigure(releasable(), context, undefined, undefined, creationId(), undefined, 'conveyor')
|
|
@@ -13,14 +13,17 @@ export const FIGURE_NAMESPACE_SETTING = 'figure-namespace'
|
|
|
13
13
|
* The namespace a new figure is created under.
|
|
14
14
|
*
|
|
15
15
|
* It is read from the domain setting and copied onto the figure, so the full name `{namespace}/{type}` a board stores
|
|
16
|
-
* never moves.
|
|
17
|
-
*
|
|
16
|
+
* never moves. A domain with no setting, or an empty one, takes its subdomain (user, 2026-10-13: 「기본적으로는
|
|
17
|
+
* subdomain 명을 사용하면 되는데」) -- refused only when the subdomain is not a namespace either. A value written in
|
|
18
|
+
* the setting is used as it is, and refused when it is not a namespace. Either way it is set once: a value that differs
|
|
19
|
+
* from the namespace this domain's figures already carry is refused, so editing the setting cannot rename anything.
|
|
18
20
|
*/
|
|
19
21
|
export async function namespaceForNewFigure(tx: EntityManager, domain: Domain): Promise<string> {
|
|
20
22
|
const setting = await tx.getRepository(Setting).findOne({
|
|
21
23
|
where: { domain: { id: domain.id }, name: FIGURE_NAMESPACE_SETTING }
|
|
22
24
|
})
|
|
23
|
-
const
|
|
25
|
+
const written = setting?.value?.trim() ?? ''
|
|
26
|
+
const namespace = written || (domain.subdomain ?? '').trim()
|
|
24
27
|
if (!isFigureNamespace(namespace)) {
|
|
25
28
|
throw new Refusal(
|
|
26
29
|
'figure-namespace-missing',
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The modeller's open figure, named for the AI (operato-application design/ai/screen-pattern.md §2-1).
|
|
3
|
+
*/
|
|
4
|
+
const mockFigures: any[] = [
|
|
5
|
+
{ id: 'f1', domain: 'plant-a', name: '선풍기', type: 'system/fan' },
|
|
6
|
+
{ id: 'f2', domain: 'plant-a', name: '', type: 'system/agv-lift' },
|
|
7
|
+
{ id: 'f3', domain: 'plant-b', name: '남의 도형', type: 'other/one' }
|
|
8
|
+
]
|
|
9
|
+
const mockRegistered: any[] = []
|
|
10
|
+
|
|
11
|
+
jest.mock('@things-factory/shell', () => ({
|
|
12
|
+
getRepository: (name: string) => {
|
|
13
|
+
if (name !== 'Figure') throw new Error(`unexpected repository ${name}`)
|
|
14
|
+
return {
|
|
15
|
+
findOne: async ({ where }: any) => mockFigures.find(one => one.id === where.id && one.domain === where.domain.id) ?? null
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}))
|
|
19
|
+
jest.mock('@things-factory/ai-client-base', () => ({ registerAssistantSubject: (one: any) => mockRegistered.push(one) }))
|
|
20
|
+
|
|
21
|
+
/* Required after the mocks hold their lists - an import would load it before them. */
|
|
22
|
+
const { figureSubject } = require('./figure-subject')
|
|
23
|
+
|
|
24
|
+
describe('the figure the modeller has open', () => {
|
|
25
|
+
it('★ is registered for the figure anchor when the package loads', () => {
|
|
26
|
+
expect(mockRegistered).toEqual([figureSubject])
|
|
27
|
+
expect(figureSubject.anchorType).toBe('figure')
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('★ is named by its name, in this domain', async () => {
|
|
31
|
+
expect(await figureSubject.describe({ anchorId: 'f1', domain: { id: 'plant-a' } })).toEqual({ noun: 'figure', name: '선풍기' })
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('a figure with no name yet is called by its type', async () => {
|
|
35
|
+
expect(await figureSubject.describe({ anchorId: 'f2', domain: { id: 'plant-a' } })).toEqual({ noun: 'figure', name: 'system/agv-lift' })
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('★ another domain\'s figure is not named', async () => {
|
|
39
|
+
expect(await figureSubject.describe({ anchorId: 'f3', domain: { id: 'plant-a' } })).toBeUndefined()
|
|
40
|
+
})
|
|
41
|
+
})
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The figure the modeller has open, named for the AI (operato-application design/ai/screen-pattern.md §2-1).
|
|
3
|
+
*
|
|
4
|
+
* A conversation in the modeller is anchored to the open figure. Each turn the door reads this to say which figure
|
|
5
|
+
* that is, so the model starts from "the figure 선풍기 is open" and passes its id to the tools. A figure of another
|
|
6
|
+
* domain, or one that is gone, is not named.
|
|
7
|
+
*/
|
|
8
|
+
import { registerAssistantSubject } from '@things-factory/ai-client-base'
|
|
9
|
+
import { getRepository } from '@things-factory/shell'
|
|
10
|
+
|
|
11
|
+
/* By entity name - the entity's decorators stay out of what a test of this has to load. */
|
|
12
|
+
export const figureSubject = {
|
|
13
|
+
anchorType: 'figure',
|
|
14
|
+
async describe({ anchorId, domain }: { anchorId: string; domain: { id: string } }) {
|
|
15
|
+
const figure: any = await getRepository('Figure').findOne({ where: { id: anchorId, domain: { id: domain.id } } })
|
|
16
|
+
if (!figure) return undefined
|
|
17
|
+
return { noun: 'figure', name: figure.name || figure.type }
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
registerAssistantSubject(figureSubject)
|