@things-factory/figure-library 10.1.121

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.
Files changed (66) hide show
  1. package/dist-client/bootstrap.d.ts +1 -0
  2. package/dist-client/bootstrap.js +2 -0
  3. package/dist-client/bootstrap.js.map +1 -0
  4. package/dist-client/index.d.ts +1 -0
  5. package/dist-client/index.js +2 -0
  6. package/dist-client/index.js.map +1 -0
  7. package/dist-client/pages/figure-library-page.d.ts +78 -0
  8. package/dist-client/pages/figure-library-page.js +781 -0
  9. package/dist-client/pages/figure-library-page.js.map +1 -0
  10. package/dist-client/route.d.ts +1 -0
  11. package/dist-client/route.js +8 -0
  12. package/dist-client/route.js.map +1 -0
  13. package/dist-client/tsconfig.tsbuildinfo +1 -0
  14. package/dist-server/index.d.ts +2 -0
  15. package/dist-server/index.js +6 -0
  16. package/dist-server/index.js.map +1 -0
  17. package/dist-server/routers/figure-library-file-router.d.ts +5 -0
  18. package/dist-server/routers/figure-library-file-router.js +43 -0
  19. package/dist-server/routers/figure-library-file-router.js.map +1 -0
  20. package/dist-server/routes.d.ts +1 -0
  21. package/dist-server/routes.js +16 -0
  22. package/dist-server/routes.js.map +1 -0
  23. package/dist-server/service/figure-library-release/figure-library-release.d.ts +54 -0
  24. package/dist-server/service/figure-library-release/figure-library-release.js +206 -0
  25. package/dist-server/service/figure-library-release/figure-library-release.js.map +1 -0
  26. package/dist-server/service/figure-library-resolver.d.ts +20 -0
  27. package/dist-server/service/figure-library-resolver.js +196 -0
  28. package/dist-server/service/figure-library-resolver.js.map +1 -0
  29. package/dist-server/service/figure-library-types.d.ts +23 -0
  30. package/dist-server/service/figure-library-types.js +85 -0
  31. package/dist-server/service/figure-library-types.js.map +1 -0
  32. package/dist-server/service/figure-type-map/figure-type-map-entry.d.ts +23 -0
  33. package/dist-server/service/figure-type-map/figure-type-map-entry.js +98 -0
  34. package/dist-server/service/figure-type-map/figure-type-map-entry.js.map +1 -0
  35. package/dist-server/service/figure-type-map/figure-type-map.d.ts +34 -0
  36. package/dist-server/service/figure-type-map/figure-type-map.js +120 -0
  37. package/dist-server/service/figure-type-map/figure-type-map.js.map +1 -0
  38. package/dist-server/service/index.d.ts +20 -0
  39. package/dist-server/service/index.js +28 -0
  40. package/dist-server/service/index.js.map +1 -0
  41. package/dist-server/service/library/blueprints.d.ts +28 -0
  42. package/dist-server/service/library/blueprints.js +53 -0
  43. package/dist-server/service/library/blueprints.js.map +1 -0
  44. package/dist-server/service/library/board-walk.d.ts +1 -0
  45. package/dist-server/service/library/board-walk.js +31 -0
  46. package/dist-server/service/library/board-walk.js.map +1 -0
  47. package/dist-server/service/library/derived.d.ts +16 -0
  48. package/dist-server/service/library/derived.js +45 -0
  49. package/dist-server/service/library/derived.js.map +1 -0
  50. package/dist-server/service/library/import.d.ts +30 -0
  51. package/dist-server/service/library/import.js +116 -0
  52. package/dist-server/service/library/import.js.map +1 -0
  53. package/dist-server/service/library/inspection.d.ts +63 -0
  54. package/dist-server/service/library/inspection.js +144 -0
  55. package/dist-server/service/library/inspection.js.map +1 -0
  56. package/dist-server/service/library/library-files.d.ts +32 -0
  57. package/dist-server/service/library/library-files.js +58 -0
  58. package/dist-server/service/library/library-files.js.map +1 -0
  59. package/dist-server/service/library/type-map.d.ts +35 -0
  60. package/dist-server/service/library/type-map.js +133 -0
  61. package/dist-server/service/library/type-map.js.map +1 -0
  62. package/dist-server/tsconfig.tsbuildinfo +1 -0
  63. package/package.json +51 -0
  64. package/things-factory.config.js +13 -0
  65. package/translations/en.json +98 -0
  66. package/translations/ko.json +98 -0
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.derivedOfSource = derivedOfSource;
4
+ exports.derivedIsStale = derivedIsStale;
5
+ const figure_model_1 = require("@hatiolab/figure-model");
6
+ /**
7
+ * Reads a source with this server's kernel. A source it cannot read is kept (ADR-0114 결정 5) and marked; the size is
8
+ * then unknown, never guessed.
9
+ */
10
+ function derivedOfSource(source) {
11
+ try {
12
+ const asset = (0, figure_model_1.parseFigureAsset)(source);
13
+ (0, figure_model_1.compileFigureAsset)(asset);
14
+ const { width, depth } = authoredPlanOf(asset);
15
+ return { formatSupported: true, checkedKernel: figure_model_1.FIGURE_KERNEL_VERSION, width, depth };
16
+ }
17
+ catch {
18
+ return { formatSupported: false, checkedKernel: figure_model_1.FIGURE_KERNEL_VERSION, width: null, depth: null };
19
+ }
20
+ }
21
+ /** Whether a row's derived values were made by another kernel than this server's and must be made again. */
22
+ function derivedIsStale(row) {
23
+ return row.checkedKernel !== figure_model_1.FIGURE_KERNEL_VERSION;
24
+ }
25
+ /**
26
+ * The plan size a figure is placed at: its size inputs when it declares them (what the scene scales against), else the
27
+ * extent of its top view at the authored size.
28
+ */
29
+ function authoredPlanOf(asset) {
30
+ const input = (axis) => {
31
+ const value = asset.designInputs?.[figure_model_1.FIGURE_SIZE_INPUTS[axis]];
32
+ return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : null;
33
+ };
34
+ const width = input('x');
35
+ const depth = input('z');
36
+ if (width !== null && depth !== null)
37
+ return { width, depth };
38
+ const points = (0, figure_model_1.figureTopViewOf)(asset, null).shapes.flatMap(shape => shape.outline);
39
+ if (!points.length)
40
+ return { width: null, depth: null };
41
+ const span = (i) => Math.max(...points.map(p => p[i])) - Math.min(...points.map(p => p[i]));
42
+ const round = (value) => (value > 0 ? Math.round(value * 1000) / 1000 : null);
43
+ return { width: width ?? round(span(0)), depth: depth ?? round(span(1)) };
44
+ }
45
+ //# sourceMappingURL=derived.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"derived.js","sourceRoot":"","sources":["../../../server/service/library/derived.ts"],"names":[],"mappings":";;AAcA,0CASC;AAGD,wCAEC;AA5BD,yDAA2J;AAU3J;;;GAGG;AACH,SAAgB,eAAe,CAAC,MAAc;IAC5C,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,IAAA,+BAAgB,EAAC,MAAM,CAAC,CAAA;QACtC,IAAA,iCAAkB,EAAC,KAAK,CAAC,CAAA;QACzB,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,cAAc,CAAC,KAAK,CAAC,CAAA;QAC9C,OAAO,EAAE,eAAe,EAAE,IAAI,EAAE,aAAa,EAAE,oCAAqB,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;IACtF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,aAAa,EAAE,oCAAqB,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;IACnG,CAAC;AACH,CAAC;AAED,4GAA4G;AAC5G,SAAgB,cAAc,CAAC,GAAsC;IACnE,OAAO,GAAG,CAAC,aAAa,KAAK,oCAAqB,CAAA;AACpD,CAAC;AAED;;;GAGG;AACH,SAAS,cAAc,CAAC,KAAkB;IACxC,MAAM,KAAK,GAAG,CAAC,IAAe,EAAE,EAAE;QAChC,MAAM,KAAK,GAAG,KAAK,CAAC,YAAY,EAAE,CAAC,iCAAkB,CAAC,IAAI,CAAC,CAAC,CAAA;QAC5D,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA;IACxF,CAAC,CAAA;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAA;IACxB,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAA;IACxB,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;IAE7D,MAAM,MAAM,GAAG,IAAA,8BAAe,EAAC,KAAK,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IAClF,IAAI,CAAC,MAAM,CAAC,MAAM;QAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;IACvD,MAAM,IAAI,GAAG,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAClG,MAAM,KAAK,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;IACrF,OAAO,EAAE,KAAK,EAAE,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;AAC3E,CAAC","sourcesContent":["import { compileFigureAsset, FIGURE_KERNEL_VERSION, FIGURE_SIZE_INPUTS, figureTopViewOf, parseFigureAsset, type FigureAsset } from '@hatiolab/figure-model'\n\n/** What this server derives from a release's source: whether its kernel reads it, and its authored size. */\nexport interface DerivedOfSource {\n formatSupported: boolean\n checkedKernel: string\n width: number | null\n depth: number | null\n}\n\n/**\n * Reads a source with this server's kernel. A source it cannot read is kept (ADR-0114 결정 5) and marked; the size is\n * then unknown, never guessed.\n */\nexport function derivedOfSource(source: string): DerivedOfSource {\n try {\n const asset = parseFigureAsset(source)\n compileFigureAsset(asset)\n const { width, depth } = authoredPlanOf(asset)\n return { formatSupported: true, checkedKernel: FIGURE_KERNEL_VERSION, width, depth }\n } catch {\n return { formatSupported: false, checkedKernel: FIGURE_KERNEL_VERSION, width: null, depth: null }\n }\n}\n\n/** Whether a row's derived values were made by another kernel than this server's and must be made again. */\nexport function derivedIsStale(row: { checkedKernel?: string | null }): boolean {\n return row.checkedKernel !== FIGURE_KERNEL_VERSION\n}\n\n/**\n * The plan size a figure is placed at: its size inputs when it declares them (what the scene scales against), else the\n * extent of its top view at the authored size.\n */\nfunction authoredPlanOf(asset: FigureAsset): { width: number | null; depth: number | null } {\n const input = (axis: 'x' | 'z') => {\n const value = asset.designInputs?.[FIGURE_SIZE_INPUTS[axis]]\n return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : null\n }\n const width = input('x')\n const depth = input('z')\n if (width !== null && depth !== null) return { width, depth }\n\n const points = figureTopViewOf(asset, null).shapes.flatMap(shape => shape.outline)\n if (!points.length) return { width: null, depth: null }\n const span = (i: 0 | 1) => Math.max(...points.map(p => p[i])) - Math.min(...points.map(p => p[i]))\n const round = (value: number) => (value > 0 ? Math.round(value * 1000) / 1000 : null)\n return { width: width ?? round(span(0)), depth: depth ?? round(span(1)) }\n}\n"]}
@@ -0,0 +1,30 @@
1
+ import { EntityManager } from 'typeorm';
2
+ import { type LibraryFiles } from './library-files';
3
+ export type ImportOutcomeKind = 'added' | 'skipped' | 'refused';
4
+ export interface ImportOutcome {
5
+ ref: string;
6
+ outcome: ImportOutcomeKind;
7
+ code?: string;
8
+ /** For a skipped release: the fields the package gave differently from the row already here. */
9
+ differs?: string[];
10
+ }
11
+ export interface ImportResult {
12
+ /** Problems that refuse the whole package; nothing is imported when there is one. */
13
+ problems: {
14
+ code: string;
15
+ path?: string;
16
+ }[];
17
+ outcomes: ImportOutcome[];
18
+ }
19
+ /**
20
+ * Imports a figure package into this domain's library (ADR-0114 결정 5 · 16).
21
+ *
22
+ * A release is known by its reference and the hash of its source, nothing else. Not here: added, its dependency files
23
+ * kept, and put into the candidate map. The same source: skipped, the row left as it is, the fields that differ named.
24
+ * Another source under the same reference: refused. A format this server cannot read is still imported.
25
+ */
26
+ export declare function importFigurePackage(tx: EntityManager, domain: {
27
+ id: string;
28
+ }, user: unknown, bytes: Buffer, packageName: string, files?: LibraryFiles): Promise<ImportResult>;
29
+ /** A file the import was given is not a package at all. */
30
+ export declare function assertPackageName(name: string): void;
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.importFigurePackage = importFigurePackage;
4
+ exports.assertPackageName = assertPackageName;
5
+ const tslib_1 = require("tslib");
6
+ const jszip_1 = tslib_1.__importDefault(require("jszip"));
7
+ const figure_model_1 = require("@hatiolab/figure-model");
8
+ const shell_1 = require("@things-factory/shell");
9
+ const figure_library_release_1 = require("../figure-library-release/figure-library-release");
10
+ const derived_1 = require("./derived");
11
+ const library_files_1 = require("./library-files");
12
+ const type_map_1 = require("./type-map");
13
+ const json = (value) => (value == null ? null : JSON.stringify(value));
14
+ /**
15
+ * Imports a figure package into this domain's library (ADR-0114 결정 5 · 16).
16
+ *
17
+ * A release is known by its reference and the hash of its source, nothing else. Not here: added, its dependency files
18
+ * kept, and put into the candidate map. The same source: skipped, the row left as it is, the fields that differ named.
19
+ * Another source under the same reference: refused. A format this server cannot read is still imported.
20
+ */
21
+ async function importFigurePackage(tx, domain, user, bytes, packageName, files = (0, library_files_1.libraryFiles)()) {
22
+ let zip;
23
+ try {
24
+ zip = await jszip_1.default.loadAsync(bytes);
25
+ }
26
+ catch {
27
+ return { problems: [{ code: 'package-unreadable' }], outcomes: [] };
28
+ }
29
+ const packed = new Map();
30
+ for (const [path, entry] of Object.entries(zip.files))
31
+ if (!entry.dir)
32
+ packed.set(path, await entry.async('uint8array'));
33
+ const read = (0, figure_model_1.readFigurePackage)(packed, library_files_1.sha256Of);
34
+ if (read.problems.length)
35
+ return { problems: read.problems.map(p => ({ code: p.code, path: p.path })), outcomes: [] };
36
+ const outcomes = read.refused.map(p => ({ ref: p.ref ?? '', outcome: 'refused', code: p.code }));
37
+ const entries = new Map(read.manifest.releases.map(entry => [entry.ref, entry]));
38
+ const repository = tx.getRepository(figure_library_release_1.FigureLibraryRelease);
39
+ for (const release of read.releases) {
40
+ const entry = entries.get(release.ref);
41
+ const contentHash = entry.contentHash;
42
+ const existing = await repository.findOne({ where: { domain: { id: domain.id }, ref: release.ref } });
43
+ if (existing) {
44
+ if (existing.contentHash !== contentHash) {
45
+ outcomes.push({ ref: release.ref, outcome: 'refused', code: 'figure-import-conflict' });
46
+ continue;
47
+ }
48
+ const differs = differingFields(existing, release, entry.blobs);
49
+ outcomes.push({ ref: release.ref, outcome: 'skipped', ...(differs.length ? { code: 'skipped-metadata-differs', differs } : {}) });
50
+ continue;
51
+ }
52
+ for (const blob of entry.blobs) {
53
+ const file = read.blobs.get(blob.hash);
54
+ await (0, library_files_1.keepLibraryFile)(tx, domain, user, { hash: blob.hash, bytes: file.bytes, contentType: file.contentType }, files);
55
+ }
56
+ const ref = (0, figure_model_1.parseFigureRef)(release.ref);
57
+ const saved = await repository.save({
58
+ domain,
59
+ name: release.name,
60
+ description: release.description ?? undefined,
61
+ ref: release.ref,
62
+ namespace: ref.namespace,
63
+ type: ref.type,
64
+ version: ref.version,
65
+ names: release.names ?? undefined,
66
+ category: release.category ?? undefined,
67
+ source: release.source,
68
+ contentHash,
69
+ assetFormat: release.assetFormat,
70
+ kernelVersion: release.kernelVersion ?? undefined,
71
+ metrics: json(release.metrics) ?? undefined,
72
+ topView: json(release.topView) ?? undefined,
73
+ changes: json(release.changes) ?? undefined,
74
+ ...(0, derived_1.derivedOfSource)(release.source),
75
+ dependencies: entry.blobs.map(blob => ({ ...blob })),
76
+ deprecated: !!release.deprecated,
77
+ supersededBy: release.supersededBy ?? undefined,
78
+ sourceUrl: read.manifest.source.url,
79
+ sourceDomain: read.manifest.source.domain,
80
+ packageName,
81
+ releasedAt: release.releasedAt ? new Date(release.releasedAt) : undefined,
82
+ creator: user,
83
+ updater: user
84
+ });
85
+ await (0, type_map_1.putIntoCandidate)(tx, domain, user, saved);
86
+ outcomes.push({ ref: release.ref, outcome: 'added' });
87
+ }
88
+ return { problems: [], outcomes };
89
+ }
90
+ /** The fields a package gives for an existing release that differ from the row: reported, never written. */
91
+ function differingFields(row, release, blobs) {
92
+ const same = (a, b) => JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
93
+ const parsed = (text) => (text ? JSON.parse(text) : null);
94
+ const pairs = [
95
+ ['name', row.name, release.name],
96
+ ['names', row.names, release.names],
97
+ ['description', row.description, release.description],
98
+ ['category', row.category, release.category],
99
+ ['assetFormat', row.assetFormat, release.assetFormat],
100
+ ['kernelVersion', row.kernelVersion, release.kernelVersion],
101
+ ['metrics', parsed(row.metrics), release.metrics],
102
+ ['topView', parsed(row.topView), release.topView],
103
+ ['changes', parsed(row.changes), release.changes],
104
+ ['dependencies', (row.dependencies ?? []).map(d => d.hash), blobs.map(b => b.hash)],
105
+ ['deprecated', !!row.deprecated, !!release.deprecated],
106
+ ['supersededBy', row.supersededBy, release.supersededBy]
107
+ ];
108
+ return pairs.filter(([, a, b]) => !same(a, b)).map(([field]) => field);
109
+ }
110
+ /** A file the import was given is not a package at all. */
111
+ function assertPackageName(name) {
112
+ if (!/\.(figpkg|zip)$/i.test(name)) {
113
+ throw new shell_1.Refusal('figure-package-invalid', { name }, '{name} is not a figure package (.figpkg).');
114
+ }
115
+ }
116
+ //# sourceMappingURL=import.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import.js","sourceRoot":"","sources":["../../../server/service/library/import.ts"],"names":[],"mappings":"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","sourcesContent":["import JSZip from 'jszip'\nimport { EntityManager } from 'typeorm'\n\nimport { parseFigureRef, readFigurePackage, type FigurePackageRelease } from '@hatiolab/figure-model'\nimport { Refusal } from '@things-factory/shell'\n\nimport { FigureLibraryRelease } from '../figure-library-release/figure-library-release'\nimport { derivedOfSource } from './derived'\nimport { keepLibraryFile, libraryFiles, sha256Of, type LibraryFiles } from './library-files'\nimport { putIntoCandidate } from './type-map'\n\nexport type ImportOutcomeKind = 'added' | 'skipped' | 'refused'\n\nexport interface ImportOutcome {\n ref: string\n outcome: ImportOutcomeKind\n code?: string\n /** For a skipped release: the fields the package gave differently from the row already here. */\n differs?: string[]\n}\n\nexport interface ImportResult {\n /** Problems that refuse the whole package; nothing is imported when there is one. */\n problems: { code: string; path?: string }[]\n outcomes: ImportOutcome[]\n}\n\nconst json = (value: unknown) => (value == null ? null : JSON.stringify(value))\n\n/**\n * Imports a figure package into this domain's library (ADR-0114 결정 5 · 16).\n *\n * A release is known by its reference and the hash of its source, nothing else. Not here: added, its dependency files\n * kept, and put into the candidate map. The same source: skipped, the row left as it is, the fields that differ named.\n * Another source under the same reference: refused. A format this server cannot read is still imported.\n */\nexport async function importFigurePackage(\n tx: EntityManager,\n domain: { id: string },\n user: unknown,\n bytes: Buffer,\n packageName: string,\n files: LibraryFiles = libraryFiles()\n): Promise<ImportResult> {\n let zip: JSZip\n try {\n zip = await JSZip.loadAsync(bytes)\n } catch {\n return { problems: [{ code: 'package-unreadable' }], outcomes: [] }\n }\n const packed = new Map<string, Uint8Array>()\n for (const [path, entry] of Object.entries(zip.files)) if (!entry.dir) packed.set(path, await entry.async('uint8array'))\n\n const read = readFigurePackage(packed, sha256Of)\n if (read.problems.length) return { problems: read.problems.map(p => ({ code: p.code, path: p.path })), outcomes: [] }\n\n const outcomes: ImportOutcome[] = read.refused.map(p => ({ ref: p.ref ?? '', outcome: 'refused', code: p.code }))\n const entries = new Map(read.manifest!.releases.map(entry => [entry.ref, entry]))\n const repository = tx.getRepository(FigureLibraryRelease)\n\n for (const release of read.releases) {\n const entry = entries.get(release.ref)!\n const contentHash = entry.contentHash\n const existing = await repository.findOne({ where: { domain: { id: domain.id }, ref: release.ref } })\n if (existing) {\n if (existing.contentHash !== contentHash) {\n outcomes.push({ ref: release.ref, outcome: 'refused', code: 'figure-import-conflict' })\n continue\n }\n const differs = differingFields(existing, release, entry.blobs)\n outcomes.push({ ref: release.ref, outcome: 'skipped', ...(differs.length ? { code: 'skipped-metadata-differs', differs } : {}) })\n continue\n }\n\n for (const blob of entry.blobs) {\n const file = read.blobs.get(blob.hash)!\n await keepLibraryFile(tx, domain, user, { hash: blob.hash, bytes: file.bytes, contentType: file.contentType }, files)\n }\n const ref = parseFigureRef(release.ref)!\n const saved = await repository.save({\n domain,\n name: release.name,\n description: release.description ?? undefined,\n ref: release.ref,\n namespace: ref.namespace,\n type: ref.type,\n version: ref.version,\n names: release.names ?? undefined,\n category: release.category ?? undefined,\n source: release.source,\n contentHash,\n assetFormat: release.assetFormat,\n kernelVersion: release.kernelVersion ?? undefined,\n metrics: json(release.metrics) ?? undefined,\n topView: json(release.topView) ?? undefined,\n changes: json(release.changes) ?? undefined,\n ...derivedOfSource(release.source),\n dependencies: entry.blobs.map(blob => ({ ...blob })),\n deprecated: !!release.deprecated,\n supersededBy: release.supersededBy ?? undefined,\n sourceUrl: read.manifest!.source.url,\n sourceDomain: read.manifest!.source.domain,\n packageName,\n releasedAt: release.releasedAt ? new Date(release.releasedAt) : undefined,\n creator: user,\n updater: user\n } as Partial<FigureLibraryRelease>)\n await putIntoCandidate(tx, domain, user, saved as FigureLibraryRelease)\n outcomes.push({ ref: release.ref, outcome: 'added' })\n }\n\n return { problems: [], outcomes }\n}\n\n/** The fields a package gives for an existing release that differ from the row: reported, never written. */\nfunction differingFields(row: FigureLibraryRelease, release: FigurePackageRelease, blobs: readonly { hash: string }[]): string[] {\n const same = (a: unknown, b: unknown) => JSON.stringify(a ?? null) === JSON.stringify(b ?? null)\n const parsed = (text?: string | null) => (text ? JSON.parse(text) : null)\n const pairs: [string, unknown, unknown][] = [\n ['name', row.name, release.name],\n ['names', row.names, release.names],\n ['description', row.description, release.description],\n ['category', row.category, release.category],\n ['assetFormat', row.assetFormat, release.assetFormat],\n ['kernelVersion', row.kernelVersion, release.kernelVersion],\n ['metrics', parsed(row.metrics), release.metrics],\n ['topView', parsed(row.topView), release.topView],\n ['changes', parsed(row.changes), release.changes],\n ['dependencies', (row.dependencies ?? []).map(d => d.hash), blobs.map(b => b.hash)],\n ['deprecated', !!row.deprecated, !!release.deprecated],\n ['supersededBy', row.supersededBy, release.supersededBy]\n ]\n return pairs.filter(([, a, b]) => !same(a, b)).map(([field]) => field)\n}\n\n/** A file the import was given is not a package at all. */\nexport function assertPackageName(name: string): void {\n if (!/\\.(figpkg|zip)$/i.test(name)) {\n throw new Refusal('figure-package-invalid', { name }, '{name} is not a figure package (.figpkg).')\n }\n}\n"]}
@@ -0,0 +1,63 @@
1
+ import { EntityManager } from 'typeorm';
2
+ import { FigureLibraryRelease } from '../figure-library-release/figure-library-release';
3
+ import { type LibraryFiles } from './library-files';
4
+ type Domain = {
5
+ id: string;
6
+ };
7
+ /** How much of the domain's boards a line touches. */
8
+ export interface Reach {
9
+ boards: number;
10
+ nodes: number;
11
+ }
12
+ /** Brings a row's derived values up to this server's kernel, writing them back when they changed. */
13
+ export declare function freshen(tx: EntityManager, release: FigureLibraryRelease): Promise<FigureLibraryRelease>;
14
+ /** The dependency files of a release that are missing or whose bytes no longer hash to their name. */
15
+ export declare function dependencyFaults(tx: EntityManager, release: FigureLibraryRelease, files?: LibraryFiles): Promise<{
16
+ hash: string;
17
+ fault: 'missing' | 'corrupt';
18
+ }[]>;
19
+ /** Figure nodes across the domain's boards: per name, how many boards and nodes use it. */
20
+ export declare function reachOf(tx: EntityManager, domain: Domain): Promise<Map<string, Reach>>;
21
+ /**
22
+ * The check before activation (ADR-0114 결정 8): the candidate against the active map and every board of the domain.
23
+ * It reports and never blocks; the activation screen shows it and asks to confirm.
24
+ */
25
+ export declare function typeMapCheck(tx: EntityManager, domain: Domain, files?: LibraryFiles): Promise<{
26
+ candidate: {
27
+ version: number;
28
+ revision: number;
29
+ };
30
+ active: {
31
+ version: number;
32
+ revision: number;
33
+ };
34
+ missing: {
35
+ boards: number;
36
+ nodes: number;
37
+ name: string;
38
+ }[];
39
+ added: {
40
+ boards: number;
41
+ nodes: number;
42
+ name: string;
43
+ ref: string;
44
+ }[];
45
+ changed: any[];
46
+ deprecated: any[];
47
+ unsupported: any[];
48
+ dependencies: any[];
49
+ }>;
50
+ export type RequirementStatus = 'drawn' | 'not-in-type-map' | 'release-unavailable' | 'dependency-missing' | 'format-unsupported';
51
+ /**
52
+ * What one board needs from the active map (ADR-0114 결정 8): each figure it holds, its status and node count. A
53
+ * figure not in the library has dependencies this server cannot know.
54
+ */
55
+ export declare function boardRequirements(tx: EntityManager, domain: Domain, boardId: string, files?: LibraryFiles): Promise<{
56
+ board: string;
57
+ typeMap: {
58
+ version: number;
59
+ revision: number;
60
+ };
61
+ figures: any[];
62
+ }>;
63
+ export {};
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.freshen = freshen;
4
+ exports.dependencyFaults = dependencyFaults;
5
+ exports.reachOf = reachOf;
6
+ exports.typeMapCheck = typeMapCheck;
7
+ exports.boardRequirements = boardRequirements;
8
+ const figure_model_1 = require("@hatiolab/figure-model");
9
+ const board_service_1 = require("@things-factory/board-service");
10
+ const figure_library_release_1 = require("../figure-library-release/figure-library-release");
11
+ const figure_type_map_1 = require("../figure-type-map/figure-type-map");
12
+ const derived_1 = require("./derived");
13
+ const board_walk_1 = require("./board-walk");
14
+ const library_files_1 = require("./library-files");
15
+ const type_map_1 = require("./type-map");
16
+ /** Brings a row's derived values up to this server's kernel, writing them back when they changed. */
17
+ async function freshen(tx, release) {
18
+ if (!(0, derived_1.derivedIsStale)(release))
19
+ return release;
20
+ const derived = (0, derived_1.derivedOfSource)(release.source);
21
+ await tx.getRepository(figure_library_release_1.FigureLibraryRelease).update({ id: release.id }, derived);
22
+ return Object.assign(release, derived);
23
+ }
24
+ /** The dependency files of a release that are missing or whose bytes no longer hash to their name. */
25
+ async function dependencyFaults(tx, release, files = (0, library_files_1.libraryFiles)()) {
26
+ const faults = [];
27
+ for (const dependency of release.dependencies ?? []) {
28
+ const attachment = await (0, library_files_1.libraryFileOf)(tx, release.domainId, dependency.hash);
29
+ if (!attachment) {
30
+ faults.push({ hash: dependency.hash, fault: 'missing' });
31
+ continue;
32
+ }
33
+ try {
34
+ const bytes = await files.read(attachment.path);
35
+ if ((0, library_files_1.sha256Of)(bytes) !== dependency.hash)
36
+ faults.push({ hash: dependency.hash, fault: 'corrupt' });
37
+ }
38
+ catch {
39
+ faults.push({ hash: dependency.hash, fault: 'missing' });
40
+ }
41
+ }
42
+ return faults;
43
+ }
44
+ /** Figure nodes across the domain's boards: per name, how many boards and nodes use it. */
45
+ async function reachOf(tx, domain) {
46
+ const reach = new Map();
47
+ const boards = await tx.getRepository(board_service_1.Board).find({ where: { domain: { id: domain.id } }, select: { id: true, model: true } });
48
+ for (const board of boards) {
49
+ for (const [name, nodes] of (0, board_walk_1.figureNodesOf)(board.model)) {
50
+ const now = reach.get(name) ?? { boards: 0, nodes: 0 };
51
+ reach.set(name, { boards: now.boards + 1, nodes: now.nodes + nodes });
52
+ }
53
+ }
54
+ return reach;
55
+ }
56
+ /**
57
+ * The check before activation (ADR-0114 결정 8): the candidate against the active map and every board of the domain.
58
+ * It reports and never blocks; the activation screen shows it and asks to confirm.
59
+ */
60
+ async function typeMapCheck(tx, domain, files) {
61
+ const candidate = await (0, type_map_1.mapOfState)(tx, domain, figure_type_map_1.FigureTypeMapState.CANDIDATE);
62
+ if (!candidate)
63
+ return null;
64
+ const active = await (0, type_map_1.mapOfState)(tx, domain, figure_type_map_1.FigureTypeMapState.ACTIVE);
65
+ const next = new Map((await (0, type_map_1.entriesOf)(tx, candidate)).map(line => [line.name, line.release]));
66
+ const now = new Map(active ? (await (0, type_map_1.entriesOf)(tx, active)).map(line => [line.name, line.release]) : []);
67
+ const reach = await reachOf(tx, domain);
68
+ const at = (name) => reach.get(name) ?? { boards: 0, nodes: 0 };
69
+ const missing = [...reach.keys()].filter(name => !next.has(name)).map(name => ({ name, ...at(name) }));
70
+ const added = [...next.keys()].filter(name => !now.has(name)).map(name => ({ name, ref: next.get(name).ref, ...at(name) }));
71
+ const changed = [];
72
+ for (const [name, release] of next) {
73
+ const before = now.get(name);
74
+ if (!before || before.id === release.id)
75
+ continue;
76
+ changed.push({ name, from: before.ref, to: release.ref, contract: contractOf(before, release), ...at(name) });
77
+ }
78
+ const deprecated = [];
79
+ const unsupported = [];
80
+ const dependencies = [];
81
+ for (const [name, release] of next) {
82
+ const fresh = await freshen(tx, release);
83
+ if (fresh.deprecated)
84
+ deprecated.push({ name, ref: fresh.ref, supersededBy: fresh.supersededBy ?? null, ...at(name) });
85
+ if (!fresh.formatSupported)
86
+ unsupported.push({ name, ref: fresh.ref, assetFormat: fresh.assetFormat, ...at(name) });
87
+ const faults = await dependencyFaults(tx, fresh, files);
88
+ if (faults.length)
89
+ dependencies.push({ name, ref: fresh.ref, faults, ...at(name) });
90
+ }
91
+ return {
92
+ candidate: { version: candidate.version, revision: candidate.revision },
93
+ active: active ? { version: active.version, revision: active.revision } : null,
94
+ missing,
95
+ added,
96
+ changed,
97
+ deprecated,
98
+ unsupported,
99
+ dependencies
100
+ };
101
+ }
102
+ /** The contract changes between two releases, or `uncheckable` when this server cannot read either. */
103
+ function contractOf(before, after) {
104
+ try {
105
+ return { checked: true, changes: (0, figure_model_1.figureContractChanges)((0, figure_model_1.parseFigureAsset)(before.source), (0, figure_model_1.parseFigureAsset)(after.source)) };
106
+ }
107
+ catch {
108
+ return { checked: false, changes: [] };
109
+ }
110
+ }
111
+ /**
112
+ * What one board needs from the active map (ADR-0114 결정 8): each figure it holds, its status and node count. A
113
+ * figure not in the library has dependencies this server cannot know.
114
+ */
115
+ async function boardRequirements(tx, domain, boardId, files) {
116
+ const board = await tx.getRepository(board_service_1.Board).findOne({ where: { id: boardId, domain: { id: domain.id } }, select: { id: true, model: true } });
117
+ if (!board)
118
+ return null;
119
+ const active = await (0, type_map_1.mapOfState)(tx, domain, figure_type_map_1.FigureTypeMapState.ACTIVE);
120
+ const lines = new Map(active ? (await (0, type_map_1.entriesOf)(tx, active)).map(line => [line.name, line]) : []);
121
+ const figures = [];
122
+ for (const [name, nodes] of (0, board_walk_1.figureNodesOf)(board.model)) {
123
+ const line = lines.get(name);
124
+ let status = 'drawn';
125
+ let ref = null;
126
+ let dependencies = 'unknown';
127
+ if (!line)
128
+ status = 'not-in-type-map';
129
+ else if (!line.release)
130
+ status = 'release-unavailable';
131
+ else {
132
+ const release = await freshen(tx, line.release);
133
+ ref = release.ref;
134
+ dependencies = release.dependencies ?? [];
135
+ if ((await dependencyFaults(tx, release, files)).length)
136
+ status = 'dependency-missing';
137
+ else if (!release.formatSupported)
138
+ status = 'format-unsupported';
139
+ }
140
+ figures.push({ name, status, ref, nodes, dependencies });
141
+ }
142
+ return { board: board.id, typeMap: active ? { version: active.version, revision: active.revision } : null, figures };
143
+ }
144
+ //# sourceMappingURL=inspection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspection.js","sourceRoot":"","sources":["../../../server/service/library/inspection.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntityManager } from 'typeorm'\n\nimport { figureContractChanges, parseFigureAsset } from '@hatiolab/figure-model'\nimport { Board } from '@things-factory/board-service'\n\nimport { FigureLibraryRelease } from '../figure-library-release/figure-library-release'\nimport { FigureTypeMapState } from '../figure-type-map/figure-type-map'\nimport { derivedIsStale, derivedOfSource } from './derived'\nimport { figureNodesOf } from './board-walk'\nimport { libraryFileOf, libraryFiles, sha256Of, type LibraryFiles } from './library-files'\nimport { entriesOf, mapOfState } from './type-map'\n\ntype Domain = { id: string }\n\n/** How much of the domain's boards a line touches. */\nexport interface Reach {\n boards: number\n nodes: number\n}\n\n/** Brings a row's derived values up to this server's kernel, writing them back when they changed. */\nexport async function freshen(tx: EntityManager, release: FigureLibraryRelease): Promise<FigureLibraryRelease> {\n if (!derivedIsStale(release)) return release\n const derived = derivedOfSource(release.source)\n await tx.getRepository(FigureLibraryRelease).update({ id: release.id }, derived)\n return Object.assign(release, derived)\n}\n\n/** The dependency files of a release that are missing or whose bytes no longer hash to their name. */\nexport async function dependencyFaults(\n tx: EntityManager,\n release: FigureLibraryRelease,\n files: LibraryFiles = libraryFiles()\n): Promise<{ hash: string; fault: 'missing' | 'corrupt' }[]> {\n const faults: { hash: string; fault: 'missing' | 'corrupt' }[] = []\n for (const dependency of release.dependencies ?? []) {\n const attachment = await libraryFileOf(tx, release.domainId!, dependency.hash)\n if (!attachment) {\n faults.push({ hash: dependency.hash, fault: 'missing' })\n continue\n }\n try {\n const bytes = await files.read(attachment.path)\n if (sha256Of(bytes) !== dependency.hash) faults.push({ hash: dependency.hash, fault: 'corrupt' })\n } catch {\n faults.push({ hash: dependency.hash, fault: 'missing' })\n }\n }\n return faults\n}\n\n/** Figure nodes across the domain's boards: per name, how many boards and nodes use it. */\nexport async function reachOf(tx: EntityManager, domain: Domain): Promise<Map<string, Reach>> {\n const reach = new Map<string, Reach>()\n const boards = await tx.getRepository(Board).find({ where: { domain: { id: domain.id } }, select: { id: true, model: true } })\n for (const board of boards) {\n for (const [name, nodes] of figureNodesOf(board.model)) {\n const now = reach.get(name) ?? { boards: 0, nodes: 0 }\n reach.set(name, { boards: now.boards + 1, nodes: now.nodes + nodes })\n }\n }\n return reach\n}\n\n/**\n * The check before activation (ADR-0114 결정 8): the candidate against the active map and every board of the domain.\n * It reports and never blocks; the activation screen shows it and asks to confirm.\n */\nexport async function typeMapCheck(tx: EntityManager, domain: Domain, files?: LibraryFiles) {\n const candidate = await mapOfState(tx, domain, FigureTypeMapState.CANDIDATE)\n if (!candidate) return null\n const active = await mapOfState(tx, domain, FigureTypeMapState.ACTIVE)\n\n const next = new Map((await entriesOf(tx, candidate)).map(line => [line.name, line.release!]))\n const now = new Map(active ? (await entriesOf(tx, active)).map(line => [line.name, line.release!]) : [])\n const reach = await reachOf(tx, domain)\n const at = (name: string): Reach => reach.get(name) ?? { boards: 0, nodes: 0 }\n\n const missing = [...reach.keys()].filter(name => !next.has(name)).map(name => ({ name, ...at(name) }))\n const added = [...next.keys()].filter(name => !now.has(name)).map(name => ({ name, ref: next.get(name)!.ref, ...at(name) }))\n const changed = []\n for (const [name, release] of next) {\n const before = now.get(name)\n if (!before || before.id === release.id) continue\n changed.push({ name, from: before.ref, to: release.ref, contract: contractOf(before, release), ...at(name) })\n }\n const deprecated = []\n const unsupported = []\n const dependencies = []\n for (const [name, release] of next) {\n const fresh = await freshen(tx, release)\n if (fresh.deprecated) deprecated.push({ name, ref: fresh.ref, supersededBy: fresh.supersededBy ?? null, ...at(name) })\n if (!fresh.formatSupported) unsupported.push({ name, ref: fresh.ref, assetFormat: fresh.assetFormat, ...at(name) })\n const faults = await dependencyFaults(tx, fresh, files)\n if (faults.length) dependencies.push({ name, ref: fresh.ref, faults, ...at(name) })\n }\n\n return {\n candidate: { version: candidate.version, revision: candidate.revision },\n active: active ? { version: active.version, revision: active.revision } : null,\n missing,\n added,\n changed,\n deprecated,\n unsupported,\n dependencies\n }\n}\n\n/** The contract changes between two releases, or `uncheckable` when this server cannot read either. */\nfunction contractOf(before: FigureLibraryRelease, after: FigureLibraryRelease) {\n try {\n return { checked: true, changes: figureContractChanges(parseFigureAsset(before.source), parseFigureAsset(after.source)) }\n } catch {\n return { checked: false, changes: [] }\n }\n}\n\nexport type RequirementStatus = 'drawn' | 'not-in-type-map' | 'release-unavailable' | 'dependency-missing' | 'format-unsupported'\n\n/**\n * What one board needs from the active map (ADR-0114 결정 8): each figure it holds, its status and node count. A\n * figure not in the library has dependencies this server cannot know.\n */\nexport async function boardRequirements(tx: EntityManager, domain: Domain, boardId: string, files?: LibraryFiles) {\n const board = await tx.getRepository(Board).findOne({ where: { id: boardId, domain: { id: domain.id } }, select: { id: true, model: true } })\n if (!board) return null\n const active = await mapOfState(tx, domain, FigureTypeMapState.ACTIVE)\n const lines = new Map(active ? (await entriesOf(tx, active)).map(line => [line.name, line]) : [])\n\n const figures = []\n for (const [name, nodes] of figureNodesOf(board.model)) {\n const line = lines.get(name)\n let status: RequirementStatus = 'drawn'\n let ref: string | null = null\n let dependencies: unknown = 'unknown'\n if (!line) status = 'not-in-type-map'\n else if (!line.release) status = 'release-unavailable'\n else {\n const release = await freshen(tx, line.release)\n ref = release.ref\n dependencies = release.dependencies ?? []\n if ((await dependencyFaults(tx, release, files)).length) status = 'dependency-missing'\n else if (!release.formatSupported) status = 'format-unsupported'\n }\n figures.push({ name, status, ref, nodes, dependencies })\n }\n return { board: board.id, typeMap: active ? { version: active.version, revision: active.revision } : null, figures }\n}\n"]}
@@ -0,0 +1,32 @@
1
+ import { Attachment } from '@things-factory/attachment-base';
2
+ /** Attachments that hold the files imported releases depend on, one per (domain, content hash). */
3
+ export declare const LIBRARY_FILE_REF_TYPE = "figure-library-file";
4
+ export declare const sha256Of: (bytes: Uint8Array) => string;
5
+ /** Where the bytes go: attachment-base's storage. Tests give another. */
6
+ export interface LibraryFiles {
7
+ put(bytes: Buffer, mimetype: string, name: string, context: any): Promise<{
8
+ id: string;
9
+ path: string;
10
+ size: number;
11
+ contents?: Buffer;
12
+ }>;
13
+ read(path: string): Promise<Buffer>;
14
+ }
15
+ export declare const attachmentFiles: LibraryFiles;
16
+ export declare const libraryFiles: () => LibraryFiles;
17
+ /** For tests: use these files until set back; returns the ones it replaced. */
18
+ export declare function useLibraryFiles(files: LibraryFiles): LibraryFiles;
19
+ type Tx = {
20
+ getRepository: (target: any) => any;
21
+ };
22
+ /** The attachment holding this content in this domain, or undefined. */
23
+ export declare function libraryFileOf(tx: Tx, domainId: string, hash: string): Promise<Attachment | undefined>;
24
+ /** Keeps one dependency file under its hash; a file already kept in this domain is not written again. */
25
+ export declare function keepLibraryFile(tx: Tx, domain: {
26
+ id: string;
27
+ }, user: unknown, file: {
28
+ hash: string;
29
+ bytes: Uint8Array;
30
+ contentType: string;
31
+ }, files?: LibraryFiles): Promise<void>;
32
+ export {};
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.libraryFiles = exports.attachmentFiles = exports.sha256Of = exports.LIBRARY_FILE_REF_TYPE = void 0;
4
+ exports.useLibraryFiles = useLibraryFiles;
5
+ exports.libraryFileOf = libraryFileOf;
6
+ exports.keepLibraryFile = keepLibraryFile;
7
+ const crypto_1 = require("crypto");
8
+ const stream_1 = require("stream");
9
+ const attachment_base_1 = require("@things-factory/attachment-base");
10
+ /** Attachments that hold the files imported releases depend on, one per (domain, content hash). */
11
+ exports.LIBRARY_FILE_REF_TYPE = 'figure-library-file';
12
+ const sha256Of = (bytes) => (0, crypto_1.createHash)('sha256').update(bytes).digest('hex');
13
+ exports.sha256Of = sha256Of;
14
+ exports.attachmentFiles = {
15
+ async put(bytes, mimetype, name, context) {
16
+ const file = Promise.resolve({ createReadStream: () => stream_1.Readable.from([bytes]), filename: name, mimetype, encoding: 'binary' });
17
+ const { id, path, size, contents } = await attachment_base_1.STORAGE.uploadFile({ file, context });
18
+ return { id, path, size, contents };
19
+ },
20
+ read: async (path) => Buffer.from(await attachment_base_1.STORAGE.readFile(path))
21
+ };
22
+ let inUse = exports.attachmentFiles;
23
+ const libraryFiles = () => inUse;
24
+ exports.libraryFiles = libraryFiles;
25
+ /** For tests: use these files until set back; returns the ones it replaced. */
26
+ function useLibraryFiles(files) {
27
+ const was = inUse;
28
+ inUse = files;
29
+ return was;
30
+ }
31
+ /** The attachment holding this content in this domain, or undefined. */
32
+ async function libraryFileOf(tx, domainId, hash) {
33
+ return (await tx.getRepository(attachment_base_1.Attachment).findOne({ where: { domain: { id: domainId }, refType: exports.LIBRARY_FILE_REF_TYPE, refBy: hash } })) ?? undefined;
34
+ }
35
+ /** Keeps one dependency file under its hash; a file already kept in this domain is not written again. */
36
+ async function keepLibraryFile(tx, domain, user, file, files = (0, exports.libraryFiles)()) {
37
+ if (await libraryFileOf(tx, domain.id, file.hash))
38
+ return;
39
+ const ext = file.contentType.split('/')[1]?.split('+')[0] || 'bin';
40
+ const name = `${file.hash}.${ext}`;
41
+ const put = await files.put(Buffer.from(file.bytes), file.contentType, name, { state: { domain, user, tx } });
42
+ await tx.getRepository(attachment_base_1.Attachment).save({
43
+ domain,
44
+ creator: user,
45
+ updater: user,
46
+ id: put.id,
47
+ name,
48
+ mimetype: file.contentType,
49
+ encoding: 'binary',
50
+ refType: exports.LIBRARY_FILE_REF_TYPE,
51
+ refBy: file.hash,
52
+ category: file.contentType.startsWith('image/') ? 'image' : 'etc',
53
+ size: String(put.size),
54
+ path: put.path,
55
+ ...(put.contents ? { contents: put.contents } : {})
56
+ });
57
+ }
58
+ //# sourceMappingURL=library-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"library-files.js","sourceRoot":"","sources":["../../../server/service/library/library-files.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'crypto'\nimport { Readable } from 'stream'\n\nimport { Attachment, STORAGE } from '@things-factory/attachment-base'\n\n/** Attachments that hold the files imported releases depend on, one per (domain, content hash). */\nexport const LIBRARY_FILE_REF_TYPE = 'figure-library-file'\n\nexport const sha256Of = (bytes: Uint8Array) => createHash('sha256').update(bytes).digest('hex')\n\n/** Where the bytes go: attachment-base's storage. Tests give another. */\nexport interface LibraryFiles {\n put(bytes: Buffer, mimetype: string, name: string, context: any): Promise<{ id: string; path: string; size: number; contents?: Buffer }>\n read(path: string): Promise<Buffer>\n}\n\nexport const attachmentFiles: LibraryFiles = {\n async put(bytes, mimetype, name, context) {\n const file = Promise.resolve({ createReadStream: () => Readable.from([bytes]), filename: name, mimetype, encoding: 'binary' })\n const { id, path, size, contents } = await STORAGE.uploadFile({ file, context })\n return { id, path, size, contents }\n },\n read: async path => Buffer.from(await STORAGE.readFile(path))\n}\n\nlet inUse: LibraryFiles = attachmentFiles\nexport const libraryFiles = (): LibraryFiles => inUse\n/** For tests: use these files until set back; returns the ones it replaced. */\nexport function useLibraryFiles(files: LibraryFiles): LibraryFiles {\n const was = inUse\n inUse = files\n return was\n}\n\ntype Tx = { getRepository: (target: any) => any }\n\n/** The attachment holding this content in this domain, or undefined. */\nexport async function libraryFileOf(tx: Tx, domainId: string, hash: string): Promise<Attachment | undefined> {\n return (await tx.getRepository(Attachment).findOne({ where: { domain: { id: domainId }, refType: LIBRARY_FILE_REF_TYPE, refBy: hash } })) ?? undefined\n}\n\n/** Keeps one dependency file under its hash; a file already kept in this domain is not written again. */\nexport async function keepLibraryFile(\n tx: Tx,\n domain: { id: string },\n user: unknown,\n file: { hash: string; bytes: Uint8Array; contentType: string },\n files: LibraryFiles = libraryFiles()\n): Promise<void> {\n if (await libraryFileOf(tx, domain.id, file.hash)) return\n const ext = file.contentType.split('/')[1]?.split('+')[0] || 'bin'\n const name = `${file.hash}.${ext}`\n const put = await files.put(Buffer.from(file.bytes), file.contentType, name, { state: { domain, user, tx } })\n await tx.getRepository(Attachment).save({\n domain,\n creator: user,\n updater: user,\n id: put.id,\n name,\n mimetype: file.contentType,\n encoding: 'binary',\n refType: LIBRARY_FILE_REF_TYPE,\n refBy: file.hash,\n category: file.contentType.startsWith('image/') ? 'image' : 'etc',\n size: String(put.size),\n path: put.path,\n ...(put.contents ? { contents: put.contents } : {})\n })\n}\n"]}
@@ -0,0 +1,35 @@
1
+ import { EntityManager } from 'typeorm';
2
+ import { FigureLibraryRelease } from '../figure-library-release/figure-library-release';
3
+ import { FigureTypeMap, FigureTypeMapState } from '../figure-type-map/figure-type-map';
4
+ import { FigureTypeMapEntry } from '../figure-type-map/figure-type-map-entry';
5
+ type Domain = {
6
+ id: string;
7
+ };
8
+ /** The full name a release is the release of. */
9
+ export declare const fullNameOf: (release: Pick<FigureLibraryRelease, "namespace" | "type">) => string;
10
+ export declare function mapOfState(tx: EntityManager, domain: Domain, state: FigureTypeMapState): Promise<FigureTypeMap | null>;
11
+ export declare function entriesOf(tx: EntityManager, map: FigureTypeMap): Promise<FigureTypeMapEntry[]>;
12
+ /**
13
+ * The domain's candidate. With `create`, a domain without one gets a new release numbered after the last, holding the
14
+ * active map's lines (ADR-0114 결정 6: editing starts from what draws now).
15
+ */
16
+ export declare function candidateOf(tx: EntityManager, domain: Domain, user: unknown, create: boolean): Promise<FigureTypeMap | null>;
17
+ export interface CandidateLine {
18
+ name: string;
19
+ releaseId: string;
20
+ }
21
+ /**
22
+ * Checks lines against the library (ADR-0114 결정 15): one line per name, a release of this domain's library, and the
23
+ * release of that very name. Refused whole by the first fault, named.
24
+ */
25
+ export declare function assertLines(tx: EntityManager, domain: Domain, lines: readonly CandidateLine[]): Promise<Map<string, FigureLibraryRelease>>;
26
+ /** Replaces the candidate's lines; each save raises the revision an open preview compares (결정 12). */
27
+ export declare function saveCandidate(tx: EntityManager, domain: Domain, user: unknown, lines: readonly CandidateLine[], description?: string): Promise<FigureTypeMap>;
28
+ /** Puts an imported release into the candidate: a new line, or its figure's line moved to it (결정 7). */
29
+ export declare function putIntoCandidate(tx: EntityManager, domain: Domain, user: unknown, release: FigureLibraryRelease): Promise<void>;
30
+ /**
31
+ * Makes a release ACTIVE in one transaction: the candidate after a check, or an ARCHIVED one to roll back. The active
32
+ * one before becomes ARCHIVED; a candidate left aside by a roll back stays a candidate (ADR-0114 결정 6).
33
+ */
34
+ export declare function activate(tx: EntityManager, domain: Domain, user: unknown, version: number): Promise<FigureTypeMap>;
35
+ export {};