@things-factory/figure-service 10.1.178 → 10.1.179
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 +2 -0
- package/dist-server/index.js +6 -1
- package/dist-server/index.js.map +1 -1
- package/dist-server/routers/figure-mesh-router.js +4 -1
- package/dist-server/routers/figure-mesh-router.js.map +1 -1
- package/dist-server/routers/figure-thumbnail-router.js +3 -7
- package/dist-server/routers/figure-thumbnail-router.js.map +1 -1
- package/dist-server/service/figure/figure-categories.d.ts +10 -0
- package/dist-server/service/figure/figure-categories.js +53 -0
- package/dist-server/service/figure/figure-categories.js.map +1 -0
- package/dist-server/service/figure/figure-format.d.ts +18 -1
- package/dist-server/service/figure/figure-format.js +28 -1
- package/dist-server/service/figure/figure-format.js.map +1 -1
- package/dist-server/service/figure/figure-mutation.d.ts +8 -2
- package/dist-server/service/figure/figure-mutation.js +83 -34
- package/dist-server/service/figure/figure-mutation.js.map +1 -1
- package/dist-server/service/figure/figure-namespace-entity.d.ts +24 -0
- package/dist-server/service/figure/figure-namespace-entity.js +87 -0
- package/dist-server/service/figure/figure-namespace-entity.js.map +1 -0
- package/dist-server/service/figure/figure-namespace-resolver.d.ts +5 -8
- package/dist-server/service/figure/figure-namespace-resolver.js +52 -36
- package/dist-server/service/figure/figure-namespace-resolver.js.map +1 -1
- package/dist-server/service/figure/figure-namespace.d.ts +24 -28
- package/dist-server/service/figure/figure-namespace.js +99 -64
- package/dist-server/service/figure/figure-namespace.js.map +1 -1
- package/dist-server/service/figure/figure-package-export.js +4 -2
- package/dist-server/service/figure/figure-package-export.js.map +1 -1
- package/dist-server/service/figure/figure-public.d.ts +19 -0
- package/dist-server/service/figure/figure-public.js +54 -0
- package/dist-server/service/figure/figure-public.js.map +1 -0
- package/dist-server/service/figure/figure-query.d.ts +7 -0
- package/dist-server/service/figure/figure-query.js +59 -5
- package/dist-server/service/figure/figure-query.js.map +1 -1
- package/dist-server/service/figure/figure-rename.d.ts +25 -2
- package/dist-server/service/figure/figure-rename.js +21 -2
- package/dist-server/service/figure/figure-rename.js.map +1 -1
- package/dist-server/service/figure/figure-tools.js +1 -1
- package/dist-server/service/figure/figure-tools.js.map +1 -1
- package/dist-server/service/figure/figure-type.d.ts +1 -0
- package/dist-server/service/figure/figure-type.js +4 -0
- package/dist-server/service/figure/figure-type.js.map +1 -1
- package/dist-server/service/figure/figure.d.ts +1 -0
- package/dist-server/service/figure/figure.js +5 -0
- package/dist-server/service/figure/figure.js.map +1 -1
- package/dist-server/service/figure/index.d.ts +2 -1
- package/dist-server/service/figure/index.js +2 -1
- package/dist-server/service/figure/index.js.map +1 -1
- package/dist-server/service/index.d.ts +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/server/index.ts +2 -0
- package/server/routers/figure-mesh-router.ts +5 -1
- package/server/routers/figure-thumbnail-router.ts +3 -5
- package/server/service/figure/figure-categories.ts +50 -0
- package/server/service/figure/figure-category-db.test.ts +127 -0
- package/server/service/figure/figure-deprecation-db.test.ts +110 -6
- package/server/service/figure/figure-format.ts +43 -1
- package/server/service/figure/figure-mesh-db.test.ts +4 -2
- package/server/service/figure/figure-mutation.ts +79 -37
- package/server/service/figure/figure-namespace-db.test.ts +120 -74
- package/server/service/figure/figure-namespace-entity.ts +67 -0
- package/server/service/figure/figure-namespace-resolver.ts +39 -26
- package/server/service/figure/figure-namespace.ts +116 -82
- package/server/service/figure/figure-package-export.ts +6 -3
- package/server/service/figure/figure-proposal-db.test.ts +5 -3
- package/server/service/figure/figure-public-db.test.ts +180 -0
- package/server/service/figure/figure-public.ts +60 -0
- package/server/service/figure/figure-query.ts +48 -5
- package/server/service/figure/figure-release-check.test.ts +63 -0
- package/server/service/figure/figure-release-record-db.test.ts +7 -5
- package/server/service/figure/figure-rename-db.test.ts +50 -4
- package/server/service/figure/figure-rename.ts +37 -3
- package/server/service/figure/figure-tools.ts +1 -1
- package/server/service/figure/figure-type.ts +3 -0
- package/server/service/figure/figure.ts +8 -0
- package/server/service/figure/index.ts +2 -1
- package/translations/en.json +12 -3
- package/translations/es.json +12 -3
- package/translations/ja.json +12 -3
- package/translations/ko.json +12 -3
- package/translations/ms.json +12 -3
- package/translations/zh.json +12 -3
|
@@ -1,35 +1,31 @@
|
|
|
1
1
|
import { EntityManager } from 'typeorm';
|
|
2
2
|
import { Domain } from '@things-factory/shell';
|
|
3
3
|
import { Figure } from './figure.js';
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import { FigureNamespace } from './figure-namespace-entity.js';
|
|
5
|
+
type Actor = {
|
|
6
|
+
id?: string;
|
|
7
|
+
} | undefined;
|
|
8
|
+
/** The domain's namespaces, its default first. */
|
|
9
|
+
export declare function namespacesOf(tx: EntityManager, domain: {
|
|
10
|
+
id: string;
|
|
11
|
+
}): Promise<FigureNamespace[]>;
|
|
6
12
|
/**
|
|
7
|
-
* The namespace a new figure is created under
|
|
8
|
-
*
|
|
9
|
-
* It is read from the domain setting and copied onto the figure, so the full name `{namespace}/{type}` a board stores
|
|
10
|
-
* never moves. A domain with no setting, or an empty one, takes its subdomain (user, 2026-10-13: 「기본적으로는
|
|
11
|
-
* subdomain 명을 사용하면 되는데」) -- refused only when the subdomain is not a namespace either. A value written in
|
|
12
|
-
* 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
|
|
13
|
-
* from the namespace this domain's figures already carry is refused, so editing the setting cannot rename anything.
|
|
13
|
+
* The namespace a new figure is created under: the one named, when it is the domain's own and active; otherwise the
|
|
14
|
+
* domain's default, made from the subdomain when the domain owns none yet.
|
|
14
15
|
*/
|
|
15
|
-
export declare function namespaceForNewFigure(tx: EntityManager, domain: Domain): Promise<string>;
|
|
16
|
-
/**
|
|
17
|
-
export declare function
|
|
16
|
+
export declare function namespaceForNewFigure(tx: EntityManager, domain: Domain, chosen?: string | null, user?: Actor): Promise<string>;
|
|
17
|
+
/** A release promises boards `{namespace}/{type}`: it goes out only under a namespace the domain owns and has active. */
|
|
18
|
+
export declare function assertReleasableNamespace(tx: EntityManager, domain: Domain, namespace: string): Promise<void>;
|
|
18
19
|
/** The namespace a figure already carries, refused when it has none (a figure made before namespaces). */
|
|
19
20
|
export declare function namespaceOf(figure: Figure): string;
|
|
20
|
-
/**
|
|
21
|
-
export
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
export
|
|
31
|
-
/**
|
|
32
|
-
* Writes the domain's figure namespace (ADR-0114). Empty takes the subdomain. Refused when the value, or the subdomain
|
|
33
|
-
* it falls back to, is not a namespace, and when the domain's figures already carry another one.
|
|
34
|
-
*/
|
|
35
|
-
export declare function setFigureNamespace(tx: EntityManager, domain: Domain, value: string): Promise<FigureNamespaceState>;
|
|
21
|
+
/** Adds a namespace to the domain. The domain's first is its default; `isDefault` makes another the default. */
|
|
22
|
+
export declare function createFigureNamespace(tx: EntityManager, domain: Domain, user: Actor, input: {
|
|
23
|
+
name: string;
|
|
24
|
+
description?: string | null;
|
|
25
|
+
isDefault?: boolean | null;
|
|
26
|
+
}): Promise<FigureNamespace>;
|
|
27
|
+
/** Makes one of the domain's active namespaces its default. */
|
|
28
|
+
export declare function setDefaultFigureNamespace(tx: EntityManager, domain: Domain, user: Actor, name: string): Promise<FigureNamespace>;
|
|
29
|
+
/** Suspends a namespace or makes it active again. The default namespace is not suspended: make another the default first. */
|
|
30
|
+
export declare function setFigureNamespaceState(tx: EntityManager, domain: Domain, user: Actor, name: string, state: 'active' | 'suspended'): Promise<FigureNamespace>;
|
|
31
|
+
export {};
|
|
@@ -1,87 +1,122 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.namespacesOf = namespacesOf;
|
|
4
4
|
exports.namespaceForNewFigure = namespaceForNewFigure;
|
|
5
|
-
exports.
|
|
5
|
+
exports.assertReleasableNamespace = assertReleasableNamespace;
|
|
6
6
|
exports.namespaceOf = namespaceOf;
|
|
7
|
-
exports.
|
|
8
|
-
exports.
|
|
9
|
-
|
|
7
|
+
exports.createFigureNamespace = createFigureNamespace;
|
|
8
|
+
exports.setDefaultFigureNamespace = setDefaultFigureNamespace;
|
|
9
|
+
exports.setFigureNamespaceState = setFigureNamespaceState;
|
|
10
10
|
const figure_model_1 = require("@hatiolab/figure-model");
|
|
11
11
|
const shell_1 = require("@things-factory/shell");
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
/** The domain
|
|
15
|
-
|
|
12
|
+
const figure_namespace_entity_js_1 = require("./figure-namespace-entity.js");
|
|
13
|
+
const ORDER = { isDefault: 'DESC', name: 'ASC' };
|
|
14
|
+
/** The domain's namespaces, its default first. */
|
|
15
|
+
async function namespacesOf(tx, domain) {
|
|
16
|
+
return tx.getRepository(figure_namespace_entity_js_1.FigureNamespace).find({ where: { domain: { id: domain.id } }, order: ORDER });
|
|
17
|
+
}
|
|
16
18
|
/**
|
|
17
|
-
* The namespace a new figure is created under
|
|
18
|
-
*
|
|
19
|
-
* It is read from the domain setting and copied onto the figure, so the full name `{namespace}/{type}` a board stores
|
|
20
|
-
* never moves. A domain with no setting, or an empty one, takes its subdomain (user, 2026-10-13: 「기본적으로는
|
|
21
|
-
* subdomain 명을 사용하면 되는데」) -- refused only when the subdomain is not a namespace either. A value written in
|
|
22
|
-
* 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
|
|
23
|
-
* from the namespace this domain's figures already carry is refused, so editing the setting cannot rename anything.
|
|
19
|
+
* The namespace a new figure is created under: the one named, when it is the domain's own and active; otherwise the
|
|
20
|
+
* domain's default, made from the subdomain when the domain owns none yet.
|
|
24
21
|
*/
|
|
25
|
-
async function namespaceForNewFigure(tx, domain) {
|
|
26
|
-
const
|
|
27
|
-
if (
|
|
28
|
-
|
|
22
|
+
async function namespaceForNewFigure(tx, domain, chosen, user) {
|
|
23
|
+
const named = chosen?.trim();
|
|
24
|
+
if (named) {
|
|
25
|
+
return (await activeOwn(tx, domain, named)).name;
|
|
29
26
|
}
|
|
30
|
-
await
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
});
|
|
38
|
-
if (other?.namespace) {
|
|
39
|
-
throw new shell_1.Refusal('figure-namespace-changed', { setting: exports.FIGURE_NAMESPACE_SETTING, value: namespace, namespace: other.namespace }, 'the figure namespace of this domain is {namespace} and cannot change. set {setting} back to {namespace}.');
|
|
27
|
+
const owned = await namespacesOf(tx, domain);
|
|
28
|
+
const fallback = owned.length ? owned.find(namespace => namespace.isDefault) : await firstNamespace(tx, domain, user);
|
|
29
|
+
if (!fallback) {
|
|
30
|
+
throw new shell_1.Refusal('figure-namespace-missing', { domain: domain.subdomain ?? '' }, 'this domain has no default figure namespace. add one first.');
|
|
31
|
+
}
|
|
32
|
+
if (fallback.state !== 'active') {
|
|
33
|
+
throw new shell_1.Refusal('figure-namespace-suspended', { namespace: fallback.name }, 'the figure namespace {namespace} is suspended.');
|
|
40
34
|
}
|
|
35
|
+
return fallback.name;
|
|
36
|
+
}
|
|
37
|
+
/** A release promises boards `{namespace}/{type}`: it goes out only under a namespace the domain owns and has active. */
|
|
38
|
+
async function assertReleasableNamespace(tx, domain, namespace) {
|
|
39
|
+
await activeOwn(tx, domain, namespace);
|
|
41
40
|
}
|
|
42
41
|
/** The namespace a figure already carries, refused when it has none (a figure made before namespaces). */
|
|
43
42
|
function namespaceOf(figure) {
|
|
44
43
|
if (!figure.namespace) {
|
|
45
|
-
throw new shell_1.Refusal('figure-namespace-missing', {
|
|
44
|
+
throw new shell_1.Refusal('figure-namespace-missing', { domain: '' }, 'this figure has no namespace. run the figure name migration first.');
|
|
46
45
|
}
|
|
47
46
|
return figure.namespace;
|
|
48
47
|
}
|
|
49
|
-
/**
|
|
50
|
-
async function
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
48
|
+
/** Adds a namespace to the domain. The domain's first is its default; `isDefault` makes another the default. */
|
|
49
|
+
async function createFigureNamespace(tx, domain, user, input) {
|
|
50
|
+
const name = (input.name ?? '').trim();
|
|
51
|
+
if (!(0, figure_model_1.isFigureNamespace)(name)) {
|
|
52
|
+
throw new shell_1.Refusal('figure-namespace-invalid', { value: name }, '{value} is not a figure namespace. use lower-case letters, digits, dots and hyphens, for example hatiolab.com.');
|
|
53
|
+
}
|
|
54
|
+
const repository = tx.getRepository(figure_namespace_entity_js_1.FigureNamespace);
|
|
55
|
+
if (await repository.findOne({ where: { name } })) {
|
|
56
|
+
throw new shell_1.Refusal('figure-namespace-taken', { namespace: name }, 'the figure namespace {namespace} is already taken on this server.');
|
|
57
|
+
}
|
|
58
|
+
const first = (await repository.count({ where: { domain: { id: domain.id } } })) === 0;
|
|
59
|
+
const isDefault = first || !!input.isDefault;
|
|
60
|
+
if (isDefault && !first)
|
|
61
|
+
await clearDefault(tx, domain);
|
|
62
|
+
const saved = await repository.save({
|
|
63
|
+
domain,
|
|
64
|
+
name,
|
|
65
|
+
description: input.description?.trim() || null,
|
|
66
|
+
state: 'active',
|
|
67
|
+
isDefault,
|
|
68
|
+
creator: user,
|
|
69
|
+
updater: user
|
|
56
70
|
});
|
|
57
|
-
return
|
|
71
|
+
return repository.findOneOrFail({ where: { id: saved.id } });
|
|
58
72
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return {
|
|
65
|
-
namespace: carried?.namespace ?? ((0, figure_model_1.isFigureNamespace)(chosen) ? chosen : null),
|
|
66
|
-
written,
|
|
67
|
-
subdomain,
|
|
68
|
-
fixed: !!carried?.namespace
|
|
69
|
-
};
|
|
73
|
+
/** Makes one of the domain's active namespaces its default. */
|
|
74
|
+
async function setDefaultFigureNamespace(tx, domain, user, name) {
|
|
75
|
+
const namespace = await activeOwn(tx, domain, name);
|
|
76
|
+
await clearDefault(tx, domain);
|
|
77
|
+
await tx.getRepository(figure_namespace_entity_js_1.FigureNamespace).update({ id: namespace.id }, { isDefault: true, updater: user });
|
|
78
|
+
return tx.getRepository(figure_namespace_entity_js_1.FigureNamespace).findOneOrFail({ where: { id: namespace.id } });
|
|
70
79
|
}
|
|
71
|
-
/**
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
async function setFigureNamespace(tx, domain, value) {
|
|
76
|
-
const written = value.trim();
|
|
77
|
-
const namespace = written || (domain.subdomain ?? '').trim();
|
|
78
|
-
if (!(0, figure_model_1.isFigureNamespace)(namespace)) {
|
|
79
|
-
throw new shell_1.Refusal('figure-namespace-invalid', { value: namespace }, '{value} is not a figure namespace. use lower-case letters, digits, dots and hyphens, for example hatiolab.com.');
|
|
80
|
+
/** Suspends a namespace or makes it active again. The default namespace is not suspended: make another the default first. */
|
|
81
|
+
async function setFigureNamespaceState(tx, domain, user, name, state) {
|
|
82
|
+
if (state !== 'active' && state !== 'suspended') {
|
|
83
|
+
throw new shell_1.Refusal('figure-namespace-state', { state: String(state) }, '{state} is not a namespace state. use active or suspended.');
|
|
80
84
|
}
|
|
81
|
-
await
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
const namespace = await own(tx, domain, name);
|
|
86
|
+
if (state === 'suspended' && namespace.isDefault) {
|
|
87
|
+
throw new shell_1.Refusal('figure-namespace-default-suspended', { namespace: namespace.name }, 'the default namespace {namespace} cannot be suspended. make another the default first.');
|
|
88
|
+
}
|
|
89
|
+
await tx.getRepository(figure_namespace_entity_js_1.FigureNamespace).update({ id: namespace.id }, { state, updater: user });
|
|
90
|
+
return tx.getRepository(figure_namespace_entity_js_1.FigureNamespace).findOneOrFail({ where: { id: namespace.id } });
|
|
91
|
+
}
|
|
92
|
+
/** One of the domain's namespaces by name; refused when the domain does not own it. */
|
|
93
|
+
async function own(tx, domain, name) {
|
|
94
|
+
const namespace = await tx.getRepository(figure_namespace_entity_js_1.FigureNamespace).findOne({ where: { domain: { id: domain.id }, name } });
|
|
95
|
+
if (!namespace) {
|
|
96
|
+
throw new shell_1.Refusal('figure-namespace-not-owned', { namespace: name }, 'this domain does not own the figure namespace {namespace}.');
|
|
97
|
+
}
|
|
98
|
+
return namespace;
|
|
99
|
+
}
|
|
100
|
+
async function activeOwn(tx, domain, name) {
|
|
101
|
+
const namespace = await own(tx, domain, name);
|
|
102
|
+
if (namespace.state !== 'active') {
|
|
103
|
+
throw new shell_1.Refusal('figure-namespace-suspended', { namespace: name }, 'the figure namespace {namespace} is suspended.');
|
|
104
|
+
}
|
|
105
|
+
return namespace;
|
|
106
|
+
}
|
|
107
|
+
async function clearDefault(tx, domain) {
|
|
108
|
+
const repository = tx.getRepository(figure_namespace_entity_js_1.FigureNamespace);
|
|
109
|
+
for (const namespace of await repository.find({ where: { domain: { id: domain.id }, isDefault: true } })) {
|
|
110
|
+
await repository.update({ id: namespace.id }, { isDefault: false });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** The domain's first namespace, made from its subdomain; none when the subdomain is not a namespace or is taken. */
|
|
114
|
+
async function firstNamespace(tx, domain, user) {
|
|
115
|
+
const name = (domain.subdomain ?? '').trim();
|
|
116
|
+
if (!(0, figure_model_1.isFigureNamespace)(name))
|
|
117
|
+
return undefined;
|
|
118
|
+
if (await tx.getRepository(figure_namespace_entity_js_1.FigureNamespace).findOne({ where: { name } }))
|
|
119
|
+
return undefined;
|
|
120
|
+
return createFigureNamespace(tx, domain, user, { name });
|
|
86
121
|
}
|
|
87
122
|
//# sourceMappingURL=figure-namespace.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-namespace.js","sourceRoot":"","sources":["../../../server/service/figure/figure-namespace.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntityManager, IsNull, Not } from 'typeorm'\n\nimport { isFigureNamespace } from '@hatiolab/figure-model'\nimport { Domain, Refusal } from '@things-factory/shell'\nimport { Setting } from '@things-factory/setting-base'\n\nimport { Figure } from './figure.js'\n\n/** The domain setting that names the namespace this domain's figures are published under (ADR-0114). */\nexport const FIGURE_NAMESPACE_SETTING = 'figure-namespace'\n\n/**\n * The namespace a new figure is created under.\n *\n * It is read from the domain setting and copied onto the figure, so the full name `{namespace}/{type}` a board stores\n * never moves. A domain with no setting, or an empty one, takes its subdomain (user, 2026-10-13: 「기본적으로는\n * subdomain 명을 사용하면 되는데」) -- refused only when the subdomain is not a namespace either. A value written in\n * 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\n * from the namespace this domain's figures already carry is refused, so editing the setting cannot rename anything.\n */\nexport async function namespaceForNewFigure(tx: EntityManager, domain: Domain): Promise<string> {\n const namespace = await chosenNamespace(tx, domain)\n if (!isFigureNamespace(namespace)) {\n throw new Refusal(\n 'figure-namespace-missing',\n { setting: FIGURE_NAMESPACE_SETTING, value: namespace },\n 'this domain has no valid figure namespace. set {setting} first, for example hatiolab.com.'\n )\n }\n await assertNamespaceUnchanged(tx, domain, namespace)\n return namespace\n}\n\n/** Refuses when the domain's figures already carry another namespace than the setting now says. */\nexport async function assertNamespaceUnchanged(tx: EntityManager, domain: Domain, namespace: string): Promise<void> {\n const other = await tx.getRepository(Figure).findOne({\n where: { domain: { id: domain.id }, namespace: Not(namespace) }\n })\n if (other?.namespace) {\n throw new Refusal(\n 'figure-namespace-changed',\n { setting: FIGURE_NAMESPACE_SETTING, value: namespace, namespace: other.namespace },\n 'the figure namespace of this domain is {namespace} and cannot change. set {setting} back to {namespace}.'\n )\n }\n}\n\n/** The namespace a figure already carries, refused when it has none (a figure made before namespaces). */\nexport function namespaceOf(figure: Figure): string {\n if (!figure.namespace) {\n throw new Refusal(\n 'figure-namespace-missing',\n { setting: FIGURE_NAMESPACE_SETTING, value: '' },\n 'this figure has no namespace. run the figure name migration first.'\n )\n }\n return figure.namespace\n}\n\n/** What the setting says, or the subdomain when it says nothing: the namespace new figures are created under. */\nasync function chosenNamespace(tx: EntityManager, domain: Domain): Promise<string> {\n return (await writtenNamespace(tx, domain)) || (domain.subdomain ?? '').trim()\n}\n\nasync function writtenNamespace(tx: EntityManager, domain: Domain): Promise<string> {\n const setting = await tx.getRepository(Setting).findOne({\n where: { domain: { id: domain.id }, name: FIGURE_NAMESPACE_SETTING }\n })\n return setting?.value?.trim() ?? ''\n}\n\n/** A domain's figure namespace as the setting screen shows it. */\nexport interface FigureNamespaceState {\n /** What new figures get, or null when neither the setting nor the subdomain is a namespace. */\n namespace: string | null\n /** The setting as written; empty when the subdomain is used. */\n written: string\n subdomain: string\n /** The domain's figures already carry the namespace, so it cannot be set any more. */\n fixed: boolean\n}\n\nexport async function figureNamespaceStateOf(tx: EntityManager, domain: Domain): Promise<FigureNamespaceState> {\n const written = await writtenNamespace(tx, domain)\n const subdomain = (domain.subdomain ?? '').trim()\n const carried = await tx.getRepository(Figure).findOne({ where: { domain: { id: domain.id }, namespace: Not(IsNull()) } })\n const chosen = written || subdomain\n return {\n namespace: carried?.namespace ?? (isFigureNamespace(chosen) ? chosen : null),\n written,\n subdomain,\n fixed: !!carried?.namespace\n }\n}\n\n/**\n * Writes the domain's figure namespace (ADR-0114). Empty takes the subdomain. Refused when the value, or the subdomain\n * it falls back to, is not a namespace, and when the domain's figures already carry another one.\n */\nexport async function setFigureNamespace(tx: EntityManager, domain: Domain, value: string): Promise<FigureNamespaceState> {\n const written = value.trim()\n const namespace = written || (domain.subdomain ?? '').trim()\n if (!isFigureNamespace(namespace)) {\n throw new Refusal(\n 'figure-namespace-invalid',\n { value: namespace },\n '{value} is not a figure namespace. use lower-case letters, digits, dots and hyphens, for example hatiolab.com.'\n )\n }\n await assertNamespaceUnchanged(tx, domain, namespace)\n const repository = tx.getRepository(Setting)\n const setting = await repository.findOne({ where: { domain: { id: domain.id }, name: FIGURE_NAMESPACE_SETTING } })\n await repository.save({ ...setting, domain, name: FIGURE_NAMESPACE_SETTING, category: 'figure', value: written })\n return figureNamespaceStateOf(tx, domain)\n}\n"]}
|
|
1
|
+
{"version":3,"file":"figure-namespace.js","sourceRoot":"","sources":["../../../server/service/figure/figure-namespace.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntityManager } from 'typeorm'\n\nimport { isFigureNamespace } from '@hatiolab/figure-model'\nimport { Domain, Refusal } from '@things-factory/shell'\n\nimport { Figure } from './figure.js'\nimport { FigureNamespace } from './figure-namespace-entity.js'\n\n/*\n * The namespaces a domain owns (LV figure-server design G1, ADR-0114). A full name `{namespace}/{type}` points at one\n * figure on the whole server because a namespace belongs to one domain and is unique across the server. A domain that\n * owns none gets its subdomain as its first, default namespace when it first needs one (user, 2026-10-13: 「기본적으로는\n * subdomain 명을 사용하면 되는데」).\n */\n\ntype Actor = { id?: string } | undefined\n\nconst ORDER = { isDefault: 'DESC', name: 'ASC' } as const\n\n/** The domain's namespaces, its default first. */\nexport async function namespacesOf(tx: EntityManager, domain: { id: string }): Promise<FigureNamespace[]> {\n return tx.getRepository(FigureNamespace).find({ where: { domain: { id: domain.id } }, order: ORDER })\n}\n\n/**\n * The namespace a new figure is created under: the one named, when it is the domain's own and active; otherwise the\n * domain's default, made from the subdomain when the domain owns none yet.\n */\nexport async function namespaceForNewFigure(tx: EntityManager, domain: Domain, chosen?: string | null, user?: Actor): Promise<string> {\n const named = chosen?.trim()\n if (named) {\n return (await activeOwn(tx, domain, named)).name!\n }\n const owned = await namespacesOf(tx, domain)\n const fallback = owned.length ? owned.find(namespace => namespace.isDefault) : await firstNamespace(tx, domain, user)\n if (!fallback) {\n throw new Refusal('figure-namespace-missing', { domain: domain.subdomain ?? '' }, 'this domain has no default figure namespace. add one first.')\n }\n if (fallback.state !== 'active') {\n throw new Refusal('figure-namespace-suspended', { namespace: fallback.name! }, 'the figure namespace {namespace} is suspended.')\n }\n return fallback.name!\n}\n\n/** A release promises boards `{namespace}/{type}`: it goes out only under a namespace the domain owns and has active. */\nexport async function assertReleasableNamespace(tx: EntityManager, domain: Domain, namespace: string): Promise<void> {\n await activeOwn(tx, domain, namespace)\n}\n\n/** The namespace a figure already carries, refused when it has none (a figure made before namespaces). */\nexport function namespaceOf(figure: Figure): string {\n if (!figure.namespace) {\n throw new Refusal('figure-namespace-missing', { domain: '' }, 'this figure has no namespace. run the figure name migration first.')\n }\n return figure.namespace\n}\n\n/** Adds a namespace to the domain. The domain's first is its default; `isDefault` makes another the default. */\nexport async function createFigureNamespace(\n tx: EntityManager,\n domain: Domain,\n user: Actor,\n input: { name: string; description?: string | null; isDefault?: boolean | null }\n): Promise<FigureNamespace> {\n const name = (input.name ?? '').trim()\n if (!isFigureNamespace(name)) {\n throw new Refusal(\n 'figure-namespace-invalid',\n { value: name },\n '{value} is not a figure namespace. use lower-case letters, digits, dots and hyphens, for example hatiolab.com.'\n )\n }\n const repository = tx.getRepository(FigureNamespace)\n if (await repository.findOne({ where: { name } })) {\n throw new Refusal('figure-namespace-taken', { namespace: name }, 'the figure namespace {namespace} is already taken on this server.')\n }\n const first = (await repository.count({ where: { domain: { id: domain.id } } })) === 0\n const isDefault = first || !!input.isDefault\n if (isDefault && !first) await clearDefault(tx, domain)\n const saved = await repository.save({\n domain,\n name,\n description: input.description?.trim() || null,\n state: 'active',\n isDefault,\n creator: user as any,\n updater: user as any\n })\n return repository.findOneOrFail({ where: { id: saved.id } })\n}\n\n/** Makes one of the domain's active namespaces its default. */\nexport async function setDefaultFigureNamespace(tx: EntityManager, domain: Domain, user: Actor, name: string): Promise<FigureNamespace> {\n const namespace = await activeOwn(tx, domain, name)\n await clearDefault(tx, domain)\n await tx.getRepository(FigureNamespace).update({ id: namespace.id }, { isDefault: true, updater: user as any })\n return tx.getRepository(FigureNamespace).findOneOrFail({ where: { id: namespace.id } })\n}\n\n/** Suspends a namespace or makes it active again. The default namespace is not suspended: make another the default first. */\nexport async function setFigureNamespaceState(\n tx: EntityManager,\n domain: Domain,\n user: Actor,\n name: string,\n state: 'active' | 'suspended'\n): Promise<FigureNamespace> {\n if (state !== 'active' && state !== 'suspended') {\n throw new Refusal('figure-namespace-state', { state: String(state) }, '{state} is not a namespace state. use active or suspended.')\n }\n const namespace = await own(tx, domain, name)\n if (state === 'suspended' && namespace.isDefault) {\n throw new Refusal('figure-namespace-default-suspended', { namespace: namespace.name! }, 'the default namespace {namespace} cannot be suspended. make another the default first.')\n }\n await tx.getRepository(FigureNamespace).update({ id: namespace.id }, { state, updater: user as any })\n return tx.getRepository(FigureNamespace).findOneOrFail({ where: { id: namespace.id } })\n}\n\n/** One of the domain's namespaces by name; refused when the domain does not own it. */\nasync function own(tx: EntityManager, domain: { id: string }, name: string): Promise<FigureNamespace> {\n const namespace = await tx.getRepository(FigureNamespace).findOne({ where: { domain: { id: domain.id }, name } })\n if (!namespace) {\n throw new Refusal('figure-namespace-not-owned', { namespace: name }, 'this domain does not own the figure namespace {namespace}.')\n }\n return namespace\n}\n\nasync function activeOwn(tx: EntityManager, domain: { id: string }, name: string): Promise<FigureNamespace> {\n const namespace = await own(tx, domain, name)\n if (namespace.state !== 'active') {\n throw new Refusal('figure-namespace-suspended', { namespace: name }, 'the figure namespace {namespace} is suspended.')\n }\n return namespace\n}\n\nasync function clearDefault(tx: EntityManager, domain: { id: string }): Promise<void> {\n const repository = tx.getRepository(FigureNamespace)\n for (const namespace of await repository.find({ where: { domain: { id: domain.id }, isDefault: true } })) {\n await repository.update({ id: namespace.id }, { isDefault: false })\n }\n}\n\n/** The domain's first namespace, made from its subdomain; none when the subdomain is not a namespace or is taken. */\nasync function firstNamespace(tx: EntityManager, domain: Domain, user: Actor): Promise<FigureNamespace | undefined> {\n const name = (domain.subdomain ?? '').trim()\n if (!isFigureNamespace(name)) return undefined\n if (await tx.getRepository(FigureNamespace).findOne({ where: { name } })) return undefined\n return createFigureNamespace(tx, domain, user, { name })\n}\n"]}
|
|
@@ -12,6 +12,7 @@ const figure_history_js_1 = require("./figure-history.js");
|
|
|
12
12
|
const figure_thumbnail_store_js_1 = require("./figure-thumbnail-store.js");
|
|
13
13
|
const figure_mesh_store_js_1 = require("./figure-mesh-store.js");
|
|
14
14
|
const figure_glb_js_1 = require("./figure-glb.js");
|
|
15
|
+
const figure_public_js_1 = require("./figure-public.js");
|
|
15
16
|
/** The most releases one package carries. */
|
|
16
17
|
exports.FIGURE_PACKAGE_MAX = 200;
|
|
17
18
|
/**
|
|
@@ -71,9 +72,10 @@ async function figurePackageOf(request) {
|
|
|
71
72
|
return { name, bytes };
|
|
72
73
|
}
|
|
73
74
|
async function releaseOf(ref, domain) {
|
|
74
|
-
|
|
75
|
+
/* Its own or its parent's, or another domain's public figure (G2): a package is made of what this domain may read. */
|
|
76
|
+
const figure = (await (0, shell_1.getRepository)(figure_js_1.Figure).findOne({
|
|
75
77
|
where: { domain: { id: (0, typeorm_1.In)([domain.id, domain.parentId].filter(Boolean)) }, type: ref.type, namespace: ref.namespace }
|
|
76
|
-
});
|
|
78
|
+
})) ?? (await (0, figure_public_js_1.publicFigureOfName)(ref.namespace, ref.type));
|
|
77
79
|
const history = figure
|
|
78
80
|
? await (0, shell_1.getRepository)(figure_history_js_1.FigureHistory).findOne({ where: { originalId: figure.id, state: 'released', version: ref.version } })
|
|
79
81
|
: null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-package-export.js","sourceRoot":"","sources":["../../../server/service/figure/figure-package-export.ts"],"names":[],"mappings":"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","sourcesContent":["import JSZip from 'jszip'\nimport { In } from 'typeorm'\n\nimport {\n figureFilesOf,\n formatFigureRef,\n parseFigureRef,\n writeFigurePackage,\n type FigurePackageBlobFile,\n type FigurePackageRelease,\n type FigureRef\n} from '@hatiolab/figure-model'\nimport { getRepository, Refusal } from '@things-factory/shell'\n\nimport { Figure } from './figure.js'\nimport { FigureHistory } from './figure-history.js'\nimport { sha256Of, thumbnailAttachmentOf, thumbnailFiles, type ThumbnailFiles } from './figure-thumbnail-store.js'\nimport { meshAttachmentOf } from './figure-mesh-store.js'\nimport { GLB_MIMETYPE } from './figure-glb.js'\n\n/** The most releases one package carries. */\nexport const FIGURE_PACKAGE_MAX = 200\n\nexport interface FigurePackageRequest {\n readonly refs: readonly string[]\n readonly domain: { id: string; parentId?: string; subdomain?: string }\n readonly exportedBy: string | null\n /** The address a board server can get these releases from again (`figure.publicUrl`). */\n readonly sourceUrl: string\n readonly now?: Date\n readonly files?: ThumbnailFiles\n}\n\n/**\n * Released figures as one package file a board server imports (ADR-0114 결정 3·16).\n *\n * Only released versions travel, read from the release history -- the draft being edited is never exported. The\n * figures are the ones this domain can read: its own and its parent's, the scope of `figureBlueprint`.\n */\nexport async function figurePackageOf(request: FigurePackageRequest): Promise<{ name: string; bytes: Buffer }> {\n const refs = [...new Set(request.refs)]\n if (refs.length === 0 || refs.length > FIGURE_PACKAGE_MAX) {\n throw new Refusal('figure-package-size', { count: refs.length, max: FIGURE_PACKAGE_MAX }, 'a package carries 1 to {max} releases, not {count}.')\n }\n\n const files = request.files ?? thumbnailFiles()\n const releases: FigurePackageRelease[] = []\n const blobs = new Map<string, FigurePackageBlobFile>()\n\n for (const text of refs) {\n const ref = parseFigureRef(text)\n if (!ref) throw new Refusal('figure-ref-malformed', { ref: text }, '{ref} is not a release reference of the form namespace/type@version.')\n const { figure, history } = await releaseOf(ref, request.domain)\n if (history.thumbnailHash && !blobs.has(history.thumbnailHash)) {\n const picture = await thumbnailAttachmentOf({ getRepository }, figure.domainId!, history.thumbnailHash)\n if (picture) {\n const bytes = await files.read(picture.path)\n if (sha256Of(bytes) === history.thumbnailHash) blobs.set(history.thumbnailHash, { bytes, contentType: picture.mimetype })\n }\n }\n /*\n Every file the release draws from goes with it (ADR-0129 decision 5). Looked for in the figure's own domain, then\n the asker's; one that is gone, or whose bytes are not its hash, refuses the package -- a board could not draw it.\n */\n for (const hash of figureFilesOf(JSON.parse(history.source!))) {\n if (blobs.has(hash)) continue\n const kept = await meshAttachmentOf({ getRepository }, [figure.domainId!, request.domain.id], hash)\n const bytes = kept ? await files.read(kept.path!) : undefined\n if (!bytes || sha256Of(bytes) !== hash)\n throw new Refusal('figure-package-mesh-missing', { ref: formatFigureRef(ref), hash }, '{ref} draws from a mesh file this server does not hold: {hash}.')\n blobs.set(hash, { bytes, contentType: GLB_MIMETYPE })\n }\n releases.push(packageReleaseOf(ref, figure, history, blobs))\n }\n\n const packed = writeFigurePackage(\n {\n source: { url: request.sourceUrl, domain: request.domain.subdomain ?? '' },\n exportedAt: (request.now ?? new Date()).toISOString(),\n exportedBy: request.exportedBy,\n releases,\n blobs\n },\n bytes => sha256Of(Buffer.from(bytes))\n )\n\n const zip = new JSZip()\n for (const [path, bytes] of packed) zip.file(path, bytes, { createFolders: false })\n const bytes = await zip.generateAsync({ type: 'nodebuffer', compression: 'DEFLATE' })\n const name = refs.length === 1 ? `${refs[0]!.replace(/[/@]/g, '_')}.figpkg` : `figures-${refs.length}.figpkg`\n return { name, bytes }\n}\n\nasync function releaseOf(ref: FigureRef, domain: FigurePackageRequest['domain']): Promise<{ figure: Figure; history: FigureHistory }> {\n const figure = await getRepository(Figure).findOne({\n where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }, type: ref.type, namespace: ref.namespace }\n })\n const history = figure\n ? await getRepository(FigureHistory).findOne({ where: { originalId: figure.id, state: 'released', version: ref.version } })\n : null\n if (!figure || !history?.source) {\n throw new Refusal('figure-package-release-not-found', { ref: formatFigureRef(ref) }, '{ref} is not a release this domain can read.')\n }\n return { figure, history }\n}\n\nfunction packageReleaseOf(\n ref: FigureRef,\n figure: Figure,\n history: FigureHistory,\n blobs: ReadonlyMap<string, FigurePackageBlobFile>\n): FigurePackageRelease {\n const json = (text: string | null | undefined) => (text ? JSON.parse(text) : null)\n return {\n ref: formatFigureRef(ref),\n name: history.name ?? figure.name ?? formatFigureRef(ref),\n names: history.names ?? null,\n description: history.description ?? null,\n category: history.category ?? null,\n source: history.source!,\n assetFormat: history.assetFormat ?? json(history.source)?.version ?? '',\n kernelVersion: history.kernelVersion ?? null,\n metrics: json(history.metrics),\n topView: json(history.topView),\n changes: json(history.changes),\n thumbnail: history.thumbnailHash && blobs.has(history.thumbnailHash) ? history.thumbnailHash : null,\n deprecated: !!figure.deprecated,\n supersededBy: figure.supersededBy && figure.namespace ? `${figure.namespace}/${figure.supersededBy}` : null,\n /* The history row copies the figure's own createdAt; the save that released this version is its updatedAt. */\n releasedAt: new Date(history.updatedAt ?? history.createdAt ?? Date.now()).toISOString(),\n comment: history.comment ?? null\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"figure-package-export.js","sourceRoot":"","sources":["../../../server/service/figure/figure-package-export.ts"],"names":[],"mappings":"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","sourcesContent":["import JSZip from 'jszip'\nimport { In } from 'typeorm'\n\nimport {\n figureFilesOf,\n formatFigureRef,\n parseFigureRef,\n writeFigurePackage,\n type FigurePackageBlobFile,\n type FigurePackageRelease,\n type FigureRef\n} from '@hatiolab/figure-model'\nimport { getRepository, Refusal } from '@things-factory/shell'\n\nimport { Figure } from './figure.js'\nimport { FigureHistory } from './figure-history.js'\nimport { sha256Of, thumbnailAttachmentOf, thumbnailFiles, type ThumbnailFiles } from './figure-thumbnail-store.js'\nimport { meshAttachmentOf } from './figure-mesh-store.js'\nimport { GLB_MIMETYPE } from './figure-glb.js'\nimport { publicFigureOfName } from './figure-public.js'\n\n/** The most releases one package carries. */\nexport const FIGURE_PACKAGE_MAX = 200\n\nexport interface FigurePackageRequest {\n readonly refs: readonly string[]\n readonly domain: { id: string; parentId?: string; subdomain?: string }\n readonly exportedBy: string | null\n /** The address a board server can get these releases from again (`figure.publicUrl`). */\n readonly sourceUrl: string\n readonly now?: Date\n readonly files?: ThumbnailFiles\n}\n\n/**\n * Released figures as one package file a board server imports (ADR-0114 결정 3·16).\n *\n * Only released versions travel, read from the release history -- the draft being edited is never exported. The\n * figures are the ones this domain can read: its own and its parent's, the scope of `figureBlueprint`.\n */\nexport async function figurePackageOf(request: FigurePackageRequest): Promise<{ name: string; bytes: Buffer }> {\n const refs = [...new Set(request.refs)]\n if (refs.length === 0 || refs.length > FIGURE_PACKAGE_MAX) {\n throw new Refusal('figure-package-size', { count: refs.length, max: FIGURE_PACKAGE_MAX }, 'a package carries 1 to {max} releases, not {count}.')\n }\n\n const files = request.files ?? thumbnailFiles()\n const releases: FigurePackageRelease[] = []\n const blobs = new Map<string, FigurePackageBlobFile>()\n\n for (const text of refs) {\n const ref = parseFigureRef(text)\n if (!ref) throw new Refusal('figure-ref-malformed', { ref: text }, '{ref} is not a release reference of the form namespace/type@version.')\n const { figure, history } = await releaseOf(ref, request.domain)\n if (history.thumbnailHash && !blobs.has(history.thumbnailHash)) {\n const picture = await thumbnailAttachmentOf({ getRepository }, figure.domainId!, history.thumbnailHash)\n if (picture) {\n const bytes = await files.read(picture.path)\n if (sha256Of(bytes) === history.thumbnailHash) blobs.set(history.thumbnailHash, { bytes, contentType: picture.mimetype })\n }\n }\n /*\n Every file the release draws from goes with it (ADR-0129 decision 5). Looked for in the figure's own domain, then\n the asker's; one that is gone, or whose bytes are not its hash, refuses the package -- a board could not draw it.\n */\n for (const hash of figureFilesOf(JSON.parse(history.source!))) {\n if (blobs.has(hash)) continue\n const kept = await meshAttachmentOf({ getRepository }, [figure.domainId!, request.domain.id], hash)\n const bytes = kept ? await files.read(kept.path!) : undefined\n if (!bytes || sha256Of(bytes) !== hash)\n throw new Refusal('figure-package-mesh-missing', { ref: formatFigureRef(ref), hash }, '{ref} draws from a mesh file this server does not hold: {hash}.')\n blobs.set(hash, { bytes, contentType: GLB_MIMETYPE })\n }\n releases.push(packageReleaseOf(ref, figure, history, blobs))\n }\n\n const packed = writeFigurePackage(\n {\n source: { url: request.sourceUrl, domain: request.domain.subdomain ?? '' },\n exportedAt: (request.now ?? new Date()).toISOString(),\n exportedBy: request.exportedBy,\n releases,\n blobs\n },\n bytes => sha256Of(Buffer.from(bytes))\n )\n\n const zip = new JSZip()\n for (const [path, bytes] of packed) zip.file(path, bytes, { createFolders: false })\n const bytes = await zip.generateAsync({ type: 'nodebuffer', compression: 'DEFLATE' })\n const name = refs.length === 1 ? `${refs[0]!.replace(/[/@]/g, '_')}.figpkg` : `figures-${refs.length}.figpkg`\n return { name, bytes }\n}\n\nasync function releaseOf(ref: FigureRef, domain: FigurePackageRequest['domain']): Promise<{ figure: Figure; history: FigureHistory }> {\n /* Its own or its parent's, or another domain's public figure (G2): a package is made of what this domain may read. */\n const figure =\n (await getRepository(Figure).findOne({\n where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }, type: ref.type, namespace: ref.namespace }\n })) ?? (await publicFigureOfName(ref.namespace, ref.type))\n const history = figure\n ? await getRepository(FigureHistory).findOne({ where: { originalId: figure.id, state: 'released', version: ref.version } })\n : null\n if (!figure || !history?.source) {\n throw new Refusal('figure-package-release-not-found', { ref: formatFigureRef(ref) }, '{ref} is not a release this domain can read.')\n }\n return { figure, history }\n}\n\nfunction packageReleaseOf(\n ref: FigureRef,\n figure: Figure,\n history: FigureHistory,\n blobs: ReadonlyMap<string, FigurePackageBlobFile>\n): FigurePackageRelease {\n const json = (text: string | null | undefined) => (text ? JSON.parse(text) : null)\n return {\n ref: formatFigureRef(ref),\n name: history.name ?? figure.name ?? formatFigureRef(ref),\n names: history.names ?? null,\n description: history.description ?? null,\n category: history.category ?? null,\n source: history.source!,\n assetFormat: history.assetFormat ?? json(history.source)?.version ?? '',\n kernelVersion: history.kernelVersion ?? null,\n metrics: json(history.metrics),\n topView: json(history.topView),\n changes: json(history.changes),\n thumbnail: history.thumbnailHash && blobs.has(history.thumbnailHash) ? history.thumbnailHash : null,\n deprecated: !!figure.deprecated,\n supersededBy: figure.supersededBy && figure.namespace ? `${figure.namespace}/${figure.supersededBy}` : null,\n /* The history row copies the figure's own createdAt; the save that released this version is its updatedAt. */\n releasedAt: new Date(history.updatedAt ?? history.createdAt ?? Date.now()).toISOString(),\n comment: history.comment ?? null\n }\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Figure } from './figure.js';
|
|
2
|
+
type Reader = {
|
|
3
|
+
id: string;
|
|
4
|
+
parentId?: string | null;
|
|
5
|
+
};
|
|
6
|
+
/** The domains a reader reads as its own: itself and its parent. */
|
|
7
|
+
export declare const ownScope: (domain: Reader) => import("typeorm").FindOperator<any>;
|
|
8
|
+
/** A figure the reader may read the releases of: its own or its parent's, or any domain's public one. */
|
|
9
|
+
export declare function readableFigure(domain: Reader, id: string, select?: Record<string, unknown>): Promise<Figure | null>;
|
|
10
|
+
/** Whether the figure is another domain's public one: the reader sees its releases, never its draft. */
|
|
11
|
+
export declare function foreignPublic(domain: Reader, figure: Figure): boolean;
|
|
12
|
+
/** The public figure of a full name, in whatever domain owns its namespace. */
|
|
13
|
+
export declare function publicFigureOfName(namespace: string, type: string): Promise<Figure | null>;
|
|
14
|
+
/**
|
|
15
|
+
* The domains whose kept file this is, among the public figures whose releases draw from it -- the places a reader may
|
|
16
|
+
* be given that file from beyond its own domain and its parent.
|
|
17
|
+
*/
|
|
18
|
+
export declare function publicDomainsOfFile(hash: string, reader: Reader): Promise<string[]>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ownScope = void 0;
|
|
4
|
+
exports.readableFigure = readableFigure;
|
|
5
|
+
exports.foreignPublic = foreignPublic;
|
|
6
|
+
exports.publicFigureOfName = publicFigureOfName;
|
|
7
|
+
exports.publicDomainsOfFile = publicDomainsOfFile;
|
|
8
|
+
const typeorm_1 = require("typeorm");
|
|
9
|
+
const shell_1 = require("@things-factory/shell");
|
|
10
|
+
const figure_js_1 = require("./figure.js");
|
|
11
|
+
const figure_history_js_1 = require("./figure-history.js");
|
|
12
|
+
/** The domains a reader reads as its own: itself and its parent. */
|
|
13
|
+
const ownScope = (domain) => (0, typeorm_1.In)([domain.id, domain.parentId].filter(Boolean));
|
|
14
|
+
exports.ownScope = ownScope;
|
|
15
|
+
/** A figure the reader may read the releases of: its own or its parent's, or any domain's public one. */
|
|
16
|
+
async function readableFigure(domain, id, select) {
|
|
17
|
+
return (0, shell_1.getRepository)(figure_js_1.Figure).findOne({
|
|
18
|
+
where: [
|
|
19
|
+
{ domain: { id: (0, exports.ownScope)(domain) }, id },
|
|
20
|
+
{ public: true, id }
|
|
21
|
+
],
|
|
22
|
+
relations: { domain: true },
|
|
23
|
+
...(select ? { select } : {})
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/** Whether the figure is another domain's public one: the reader sees its releases, never its draft. */
|
|
27
|
+
function foreignPublic(domain, figure) {
|
|
28
|
+
const owner = figure.domain?.id ?? figure.domainId;
|
|
29
|
+
return !!figure.public && owner !== domain.id && owner !== domain.parentId;
|
|
30
|
+
}
|
|
31
|
+
/** The public figure of a full name, in whatever domain owns its namespace. */
|
|
32
|
+
async function publicFigureOfName(namespace, type) {
|
|
33
|
+
return (0, shell_1.getRepository)(figure_js_1.Figure).findOne({ where: { namespace, type, public: true }, relations: { domain: true } });
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The domains whose kept file this is, among the public figures whose releases draw from it -- the places a reader may
|
|
37
|
+
* be given that file from beyond its own domain and its parent.
|
|
38
|
+
*/
|
|
39
|
+
async function publicDomainsOfFile(hash, reader) {
|
|
40
|
+
const figures = await (0, shell_1.getRepository)(figure_js_1.Figure).find({
|
|
41
|
+
where: { public: true, domain: { id: (0, typeorm_1.Not)((0, exports.ownScope)(reader)) } },
|
|
42
|
+
relations: { domain: true },
|
|
43
|
+
select: { id: true, domain: { id: true } }
|
|
44
|
+
});
|
|
45
|
+
if (!figures.length)
|
|
46
|
+
return [];
|
|
47
|
+
const histories = await (0, shell_1.getRepository)(figure_history_js_1.FigureHistory).find({
|
|
48
|
+
where: { originalId: (0, typeorm_1.In)(figures.map(figure => figure.id)), state: 'released', source: (0, typeorm_1.Like)(`%${hash}%`) },
|
|
49
|
+
select: { originalId: true }
|
|
50
|
+
});
|
|
51
|
+
const drawing = new Set(histories.map(history => history.originalId));
|
|
52
|
+
return [...new Set(figures.filter(figure => drawing.has(figure.id)).map(figure => figure.domain.id))];
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=figure-public.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"figure-public.js","sourceRoot":"","sources":["../../../server/service/figure/figure-public.ts"],"names":[],"mappings":"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","sourcesContent":["import { In, Like, Not } from 'typeorm'\n\nimport { getRepository } from '@things-factory/shell'\n\nimport { Figure } from './figure.js'\nimport { FigureHistory } from './figure-history.js'\n\n/*\n * Public figures (LV figure-server design G2): a figure its domain made public is read by every domain -- its releases,\n * its picture, the files its releases draw from -- and drawn by its full name {namespace}/{type}, which points at one\n * figure on the server because a namespace belongs to one domain (G1). Its draft stays its domain's. Writing it stays\n * its domain's (G3). Each reader asks its own domain and its parent first; a public figure is the last place looked.\n */\n\ntype Reader = { id: string; parentId?: string | null }\n\n/** The domains a reader reads as its own: itself and its parent. */\nexport const ownScope = (domain: Reader) => In([domain.id, domain.parentId].filter(Boolean) as string[])\n\n/** A figure the reader may read the releases of: its own or its parent's, or any domain's public one. */\nexport async function readableFigure(domain: Reader, id: string, select?: Record<string, unknown>): Promise<Figure | null> {\n return getRepository(Figure).findOne({\n where: [\n { domain: { id: ownScope(domain) }, id },\n { public: true, id }\n ],\n relations: { domain: true },\n ...(select ? { select } : {})\n })\n}\n\n/** Whether the figure is another domain's public one: the reader sees its releases, never its draft. */\nexport function foreignPublic(domain: Reader, figure: Figure): boolean {\n const owner = figure.domain?.id ?? figure.domainId\n return !!figure.public && owner !== domain.id && owner !== domain.parentId\n}\n\n/** The public figure of a full name, in whatever domain owns its namespace. */\nexport async function publicFigureOfName(namespace: string, type: string): Promise<Figure | null> {\n return getRepository(Figure).findOne({ where: { namespace, type, public: true }, relations: { domain: true } })\n}\n\n/**\n * The domains whose kept file this is, among the public figures whose releases draw from it -- the places a reader may\n * be given that file from beyond its own domain and its parent.\n */\nexport async function publicDomainsOfFile(hash: string, reader: Reader): Promise<string[]> {\n const figures = await getRepository(Figure).find({\n where: { public: true, domain: { id: Not(ownScope(reader)) } },\n relations: { domain: true },\n select: { id: true, domain: { id: true } }\n })\n if (!figures.length) return []\n const histories = await getRepository(FigureHistory).find({\n where: { originalId: In(figures.map(figure => figure.id)), state: 'released', source: Like(`%${hash}%`) },\n select: { originalId: true }\n })\n const drawing = new Set(histories.map(history => history.originalId))\n return [...new Set(figures.filter(figure => drawing.has(figure.id)).map(figure => figure.domain!.id))]\n}\n"]}
|
|
@@ -6,6 +6,11 @@ import { FigureReplacedDraft } from './figure-replaced-draft.js';
|
|
|
6
6
|
export declare class FigureQuery {
|
|
7
7
|
figure(id: string, context: ResolverContext): Promise<Figure>;
|
|
8
8
|
figures(params: ListParam, context: ResolverContext): Promise<FigureList>;
|
|
9
|
+
/**
|
|
10
|
+
* Other domains' public figures (LV figure-server design G2): what this domain may read, package and draw besides its
|
|
11
|
+
* own and its parent's. The rows carry their domain, so a screen says whose each is. Their drafts are not opened.
|
|
12
|
+
*/
|
|
13
|
+
publicFigures(params: ListParam, context: ResolverContext): Promise<FigureList>;
|
|
9
14
|
/**
|
|
10
15
|
* 등록된 타입 이름 목록.
|
|
11
16
|
*
|
|
@@ -51,6 +56,8 @@ export declare class FigureQuery {
|
|
|
51
56
|
* figure, its row type and last release are read as `inspectFigure` does; a figure never saved has neither.
|
|
52
57
|
*/
|
|
53
58
|
inspectFigureDraft(source: string, context: ResolverContext, id?: string): Promise<FigureInspection>;
|
|
59
|
+
/** The categories this server allows (G5), or null when a category is free text: the modeller offers them to choose from. */
|
|
60
|
+
figureCategories(): Promise<string[] | null>;
|
|
54
61
|
figureTypeNames(context: ResolverContext): Promise<string[]>;
|
|
55
62
|
/**
|
|
56
63
|
* 소비처(operato-board, operato-twin, things-scene)가 발행된 도형을 받아 가는 문.
|