dalux-build-api 1.1.3 → 2.0.0

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 (48) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +404 -365
  3. package/package.json +47 -46
  4. package/src/api/CompaniesApi.js +67 -60
  5. package/src/api/CompanyCatalogApi.js +129 -108
  6. package/src/api/FileAreasApi.js +57 -37
  7. package/src/api/FileRevisionsApi.js +31 -31
  8. package/src/api/FileUploadApi.js +64 -64
  9. package/src/api/FilesApi.js +701 -297
  10. package/src/api/FoldersApi.js +283 -58
  11. package/src/api/FormsApi.js +53 -48
  12. package/src/api/InspectionPlansApi.js +66 -62
  13. package/src/api/ProjectTemplatesApi.js +25 -25
  14. package/src/api/ProjectsApi.js +127 -106
  15. package/src/api/TasksApi.js +193 -161
  16. package/src/api/TestPlansApi.js +63 -59
  17. package/src/api/UsersApi.js +52 -47
  18. package/src/api/VersionSetsApi.js +75 -67
  19. package/src/api/WorkPackagesApi.js +30 -26
  20. package/src/apiClient.js +139 -75
  21. package/src/configuration.js +39 -24
  22. package/src/index.js +133 -92
  23. package/src/models/common.js +22 -0
  24. package/src/models/companies/index.js +14 -0
  25. package/src/models/companyCatalog/index.js +19 -0
  26. package/src/models/convert.js +44 -0
  27. package/src/models/fileAreas/index.js +20 -0
  28. package/src/models/fileRevisions/index.js +12 -0
  29. package/src/models/fileUpload/index.js +12 -0
  30. package/src/models/files/index.js +97 -0
  31. package/src/models/folders/index.js +20 -0
  32. package/src/models/forms/index.js +19 -0
  33. package/src/models/helpers.js +62 -0
  34. package/src/models/index.js +148 -0
  35. package/src/models/inspectionPlans/index.js +18 -0
  36. package/src/models/projectTemplates/index.js +11 -0
  37. package/src/models/projects/index.js +57 -0
  38. package/src/models/tasks/index.js +105 -0
  39. package/src/models/testPlans/index.js +17 -0
  40. package/src/models/users/index.js +29 -0
  41. package/src/models/versionSets/index.js +22 -0
  42. package/src/models/workPackages/index.js +19 -0
  43. package/src/utils/errors.js +45 -0
  44. package/src/utils/index.js +26 -0
  45. package/src/utils/pagination.js +82 -0
  46. package/src/utils/pathResolver.js +124 -0
  47. package/src/utils/search.js +61 -0
  48. package/src/utils/validation.js +36 -0
@@ -0,0 +1,124 @@
1
+ 'use strict';
2
+
3
+ const { findByField } = require('./search');
4
+
5
+ /**
6
+ * Resolve a file area by its displayed name for a project.
7
+ *
8
+ * @param {object} apiClient - ApiClient instance.
9
+ * @param {string} projectId
10
+ * @param {string} fileAreaName
11
+ * @param {object} [fileAreasCache] - Optional mutable cache {name -> fileArea item}.
12
+ * @returns {Promise<object|null>} File area item or null.
13
+ */
14
+ async function resolveFileAreaByName(apiClient, projectId, fileAreaName, fileAreasCache) {
15
+ if (fileAreasCache && fileAreasCache[fileAreaName]) {
16
+ return fileAreasCache[fileAreaName];
17
+ }
18
+
19
+ // Inline import to avoid circular deps
20
+ const FileAreasApi = require('../api/FileAreasApi');
21
+ const response = await new FileAreasApi(apiClient).getFileAreas(projectId);
22
+ const items = (response && response.items) || [];
23
+
24
+ if (fileAreasCache) {
25
+ for (const item of items) {
26
+ const name = (item.data || {}).fileAreaName || (item.data || {}).name || item.fileAreaName || item.name;
27
+ if (name) fileAreasCache[name] = item;
28
+ }
29
+ }
30
+
31
+ return (
32
+ findByField(items, 'fileAreaName', fileAreaName, (x) => x.data || x) ||
33
+ findByField(items, 'name', fileAreaName, (x) => x.data || x)
34
+ );
35
+ }
36
+
37
+ /**
38
+ * Resolve "FileAreaName/Folder/SubFolder" to { fileAreaId, folderId }.
39
+ *
40
+ * @param {object} apiClient - ApiClient instance.
41
+ * @param {string} projectId
42
+ * @param {string} path - e.g. "Files/4_Design/C07_Geometry"
43
+ * @param {object} [opts]
44
+ * @param {boolean} [opts.verbose=false]
45
+ * @param {object} [opts.fileAreasCache]
46
+ * @param {object} [opts.foldersCache]
47
+ * @param {object} [opts.resolvedPathsCache]
48
+ * @returns {Promise<{ fileAreaId: string|null, folderId: string|null }>}
49
+ */
50
+ async function resolveFolderIdFromNamedPath(apiClient, projectId, path, opts = {}) {
51
+ const { verbose = false, fileAreasCache, foldersCache, resolvedPathsCache } = opts;
52
+
53
+ if (resolvedPathsCache && resolvedPathsCache[path]) {
54
+ return resolvedPathsCache[path];
55
+ }
56
+
57
+ const parts = path.split('/').map((p) => p.trim()).filter(Boolean);
58
+ if (parts.length < 2) return { fileAreaId: null, folderId: null };
59
+
60
+ const fileAreaName = parts[0];
61
+ const folderNames = parts.slice(1);
62
+
63
+ const fileAreaItem = await resolveFileAreaByName(apiClient, projectId, fileAreaName, fileAreasCache);
64
+ if (!fileAreaItem) {
65
+ if (verbose) console.log(`Could not resolve file area: ${fileAreaName}`);
66
+ return { fileAreaId: null, folderId: null };
67
+ }
68
+
69
+ const fileAreaId =
70
+ (fileAreaItem.data || {}).fileAreaId ||
71
+ (fileAreaItem.data || {}).id ||
72
+ fileAreaItem.fileAreaId ||
73
+ fileAreaItem.id;
74
+
75
+ // Fetch all folders for this file area (with cache)
76
+ let folders = foldersCache && foldersCache[fileAreaId];
77
+ if (!folders) {
78
+ if (verbose) {
79
+ console.log(`GET /5.1/projects/${projectId}/file_areas/${fileAreaId}/folders`);
80
+ }
81
+ const FoldersApi = require('../api/FoldersApi');
82
+ folders = await new FoldersApi(apiClient).getAllFolders(projectId, fileAreaId);
83
+ if (foldersCache) foldersCache[fileAreaId] = folders;
84
+ }
85
+
86
+ // Build a lookup: (parentFolderId, folderName) -> folder
87
+ // Collect all valid folder IDs first
88
+ const allFolderIds = new Set();
89
+ for (const f of folders) {
90
+ const data = f.data || f;
91
+ const fid = data.folderId || data.id;
92
+ if (fid) allFolderIds.add(fid);
93
+ }
94
+
95
+ const folderIndex = new Map();
96
+ for (const f of folders) {
97
+ const data = f.data || f;
98
+ const fid = data.folderId || data.id;
99
+ const pid = data.parentFolderId || data.parentId || null;
100
+ const name = data.folderName || data.name || '';
101
+ // Treat root-level folders (parent not in folder list) as children of null
102
+ const parentKey = allFolderIds.has(pid) ? pid : null;
103
+ folderIndex.set(`${parentKey}|||${name}`, fid);
104
+ }
105
+
106
+ let parentFolderId = null;
107
+ for (const folderName of folderNames) {
108
+ const key = `${parentFolderId}|||${folderName}`;
109
+ const fid = folderIndex.get(key);
110
+ if (!fid) {
111
+ if (verbose) console.log(`Could not resolve folder segment: ${folderName}`);
112
+ const result = { fileAreaId, folderId: null };
113
+ if (resolvedPathsCache) resolvedPathsCache[path] = result;
114
+ return result;
115
+ }
116
+ parentFolderId = fid;
117
+ }
118
+
119
+ const result = { fileAreaId, folderId: parentFolderId };
120
+ if (resolvedPathsCache) resolvedPathsCache[path] = result;
121
+ return result;
122
+ }
123
+
124
+ module.exports = { resolveFileAreaByName, resolveFolderIdFromNamedPath };
@@ -0,0 +1,61 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Find the first item in an iterable where a field equals a value.
5
+ *
6
+ * @param {any[]} items
7
+ * @param {string} field - Field name (supports camelCase and snake_case).
8
+ * @param {any} value - Value to match.
9
+ * @param {Function} [accessor] - Optional function to extract data from each item.
10
+ * @returns {any|null}
11
+ */
12
+ function findByField(items, field, value, accessor) {
13
+ const get = accessor || ((x) => x);
14
+ for (const item of items) {
15
+ const itemData = get(item);
16
+ if (itemData && typeof itemData === 'object') {
17
+ if (itemData[field] === value) return item;
18
+ // Also try snake_case ↔ camelCase conversion
19
+ const camel = snakeToCamel(field);
20
+ if (camel !== field && itemData[camel] === value) return item;
21
+ const snake = camelToSnake(field);
22
+ if (snake !== field && itemData[snake] === value) return item;
23
+ }
24
+ }
25
+ return null;
26
+ }
27
+
28
+ /**
29
+ * Find all items in an iterable where a field equals a value.
30
+ *
31
+ * @param {any[]} items
32
+ * @param {string} field - Field name.
33
+ * @param {any} value - Value to match.
34
+ * @param {Function} [accessor] - Optional function to extract data from each item.
35
+ * @returns {any[]}
36
+ */
37
+ function findAllByField(items, field, value, accessor) {
38
+ const get = accessor || ((x) => x);
39
+ return items.filter((item) => {
40
+ const itemData = get(item);
41
+ if (!itemData || typeof itemData !== 'object') return false;
42
+ if (itemData[field] === value) return true;
43
+ const camel = snakeToCamel(field);
44
+ if (camel !== field && itemData[camel] === value) return true;
45
+ const snake = camelToSnake(field);
46
+ if (snake !== field && itemData[snake] === value) return true;
47
+ return false;
48
+ });
49
+ }
50
+
51
+ /** @param {string} s */
52
+ function snakeToCamel(s) {
53
+ return s.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
54
+ }
55
+
56
+ /** @param {string} s */
57
+ function camelToSnake(s) {
58
+ return s.replace(/([A-Z])/g, (c) => `_${c.toLowerCase()}`);
59
+ }
60
+
61
+ module.exports = { findByField, findAllByField };
@@ -0,0 +1,36 @@
1
+ 'use strict';
2
+
3
+ const { ValidationError } = require('./errors');
4
+
5
+ /**
6
+ * Validate that projectId is a non-empty string.
7
+ * @param {string} projectId
8
+ */
9
+ function validateProjectId(projectId) {
10
+ if (!projectId || typeof projectId !== 'string' || projectId.trim().length === 0) {
11
+ throw new ValidationError('projectId must be a non-empty string');
12
+ }
13
+ }
14
+
15
+ /**
16
+ * Validate that fileAreaId is a non-empty string.
17
+ * @param {string} fileAreaId
18
+ */
19
+ function validateFileAreaId(fileAreaId) {
20
+ if (!fileAreaId || typeof fileAreaId !== 'string' || fileAreaId.trim().length === 0) {
21
+ throw new ValidationError('fileAreaId must be a non-empty string');
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Validate that folderId is a non-empty string (or null/undefined).
27
+ * @param {string|null|undefined} folderId
28
+ */
29
+ function validateFolderId(folderId) {
30
+ if (folderId == null) return;
31
+ if (typeof folderId !== 'string' || folderId.trim().length === 0) {
32
+ throw new ValidationError('folderId must be a non-empty string');
33
+ }
34
+ }
35
+
36
+ module.exports = { validateProjectId, validateFileAreaId, validateFolderId };