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
@@ -1,24 +1,39 @@
1
- 'use strict';
2
-
3
- /**
4
- * Configuration for the Dalux Build API client.
5
- */
6
- class Configuration {
7
- /**
8
- * @param {object} options
9
- * @param {string} options.baseUrl - The API base URL provided by Dalux (e.g. https://api.dalux.com/build)
10
- * @param {string} options.apiKey - Your company-specific X-API-KEY
11
- */
12
- constructor({ baseUrl, apiKey } = {}) {
13
- if (!baseUrl) {
14
- throw new Error('baseUrl is required');
15
- }
16
- if (!apiKey) {
17
- throw new Error('apiKey is required');
18
- }
19
- this.baseUrl = baseUrl.replace(/\/$/, '');
20
- this.apiKey = apiKey;
21
- }
22
- }
23
-
24
- module.exports = Configuration;
1
+ 'use strict';
2
+
3
+ // Load .env file when dotenv is available (optional peer dep)
4
+ try {
5
+ require('dotenv').config();
6
+ } catch (_) { /* dotenv not installed – skip */ }
7
+
8
+ /**
9
+ * Configuration for the Dalux Build API client.
10
+ *
11
+ * When *baseUrl* or *apiKey* are not provided they are read from the
12
+ * environment variables ``DALUX_BASE_URL`` and ``DALUX_API_KEY`` respectively,
13
+ * matching the behaviour of the Python client.
14
+ */
15
+ class Configuration {
16
+ /**
17
+ * @param {object} [options]
18
+ * @param {string} [options.baseUrl] - The API base URL provided by Dalux
19
+ * (e.g. ``https://<company>.dalux.com/api``).
20
+ * Falls back to the ``DALUX_BASE_URL`` environment variable.
21
+ * @param {string} [options.apiKey] - Your company-specific X-API-KEY.
22
+ * Falls back to the ``DALUX_API_KEY`` environment variable.
23
+ */
24
+ constructor({ baseUrl, apiKey } = {}) {
25
+ const resolvedBaseUrl = baseUrl || process.env.DALUX_BASE_URL;
26
+ const resolvedApiKey = apiKey || process.env.DALUX_API_KEY;
27
+
28
+ if (!resolvedBaseUrl) {
29
+ throw new Error('baseUrl is required (or set DALUX_BASE_URL env var)');
30
+ }
31
+ if (!resolvedApiKey) {
32
+ throw new Error('apiKey is required (or set DALUX_API_KEY env var)');
33
+ }
34
+ this.baseUrl = resolvedBaseUrl.replace(/\/$/, '');
35
+ this.apiKey = resolvedApiKey;
36
+ }
37
+ }
38
+
39
+ module.exports = Configuration;
package/src/index.js CHANGED
@@ -1,92 +1,133 @@
1
- 'use strict';
2
-
3
- const Configuration = require('./configuration');
4
- const ApiClient = require('./apiClient');
5
-
6
- const CompaniesApi = require('./api/CompaniesApi');
7
- const CompanyCatalogApi = require('./api/CompanyCatalogApi');
8
- const FileAreasApi = require('./api/FileAreasApi');
9
- const FileRevisionsApi = require('./api/FileRevisionsApi');
10
- const FileUploadApi = require('./api/FileUploadApi');
11
- const FilesApi = require('./api/FilesApi');
12
- const FoldersApi = require('./api/FoldersApi');
13
- const FormsApi = require('./api/FormsApi');
14
- const InspectionPlansApi = require('./api/InspectionPlansApi');
15
- const ProjectTemplatesApi = require('./api/ProjectTemplatesApi');
16
- const ProjectsApi = require('./api/ProjectsApi');
17
- const TasksApi = require('./api/TasksApi');
18
- const TestPlansApi = require('./api/TestPlansApi');
19
- const UsersApi = require('./api/UsersApi');
20
- const VersionSetsApi = require('./api/VersionSetsApi');
21
- const WorkPackagesApi = require('./api/WorkPackagesApi');
22
-
23
- /**
24
- * Create a fully configured Dalux Build API client.
25
- *
26
- * @param {object} options
27
- * @param {string} options.baseUrl - The API base URL (obtain from Dalux support)
28
- * @param {string} options.apiKey - Your X-API-KEY (manage via Dalux Settings › Integrations › API Identities)
29
- * @returns {{
30
- * projects: ProjectsApi,
31
- * companies: CompaniesApi,
32
- * companyCatalog: CompanyCatalogApi,
33
- * fileAreas: FileAreasApi,
34
- * fileRevisions: FileRevisionsApi,
35
- * fileUpload: FileUploadApi,
36
- * files: FilesApi,
37
- * folders: FoldersApi,
38
- * forms: FormsApi,
39
- * inspectionPlans: InspectionPlansApi,
40
- * projectTemplates: ProjectTemplatesApi,
41
- * tasks: TasksApi,
42
- * testPlans: TestPlansApi,
43
- * users: UsersApi,
44
- * versionSets: VersionSetsApi,
45
- * workPackages: WorkPackagesApi
46
- * }}
47
- */
48
- function createClient({ baseUrl, apiKey } = {}) {
49
- const configuration = new Configuration({ baseUrl, apiKey });
50
- const apiClient = new ApiClient(configuration);
51
-
52
- return {
53
- projects: new ProjectsApi(apiClient),
54
- companies: new CompaniesApi(apiClient),
55
- companyCatalog: new CompanyCatalogApi(apiClient),
56
- fileAreas: new FileAreasApi(apiClient),
57
- fileRevisions: new FileRevisionsApi(apiClient),
58
- fileUpload: new FileUploadApi(apiClient),
59
- files: new FilesApi(apiClient),
60
- folders: new FoldersApi(apiClient),
61
- forms: new FormsApi(apiClient),
62
- inspectionPlans: new InspectionPlansApi(apiClient),
63
- projectTemplates: new ProjectTemplatesApi(apiClient),
64
- tasks: new TasksApi(apiClient),
65
- testPlans: new TestPlansApi(apiClient),
66
- users: new UsersApi(apiClient),
67
- versionSets: new VersionSetsApi(apiClient),
68
- workPackages: new WorkPackagesApi(apiClient),
69
- };
70
- }
71
-
72
- module.exports = {
73
- createClient,
74
- Configuration,
75
- ApiClient,
76
- CompaniesApi,
77
- CompanyCatalogApi,
78
- FileAreasApi,
79
- FileRevisionsApi,
80
- FileUploadApi,
81
- FilesApi,
82
- FoldersApi,
83
- FormsApi,
84
- InspectionPlansApi,
85
- ProjectTemplatesApi,
86
- ProjectsApi,
87
- TasksApi,
88
- TestPlansApi,
89
- UsersApi,
90
- VersionSetsApi,
91
- WorkPackagesApi,
92
- };
1
+ 'use strict';
2
+
3
+ const Configuration = require('./configuration');
4
+ const ApiClient = require('./apiClient');
5
+
6
+ const CompaniesApi = require('./api/CompaniesApi');
7
+ const CompanyCatalogApi = require('./api/CompanyCatalogApi');
8
+ const FileAreasApi = require('./api/FileAreasApi');
9
+ const FileRevisionsApi = require('./api/FileRevisionsApi');
10
+ const FileUploadApi = require('./api/FileUploadApi');
11
+ const FilesApi = require('./api/FilesApi');
12
+ const FoldersApi = require('./api/FoldersApi');
13
+ const FormsApi = require('./api/FormsApi');
14
+ const InspectionPlansApi = require('./api/InspectionPlansApi');
15
+ const ProjectTemplatesApi = require('./api/ProjectTemplatesApi');
16
+ const ProjectsApi = require('./api/ProjectsApi');
17
+ const TasksApi = require('./api/TasksApi');
18
+ const TestPlansApi = require('./api/TestPlansApi');
19
+ const UsersApi = require('./api/UsersApi');
20
+ const VersionSetsApi = require('./api/VersionSetsApi');
21
+ const WorkPackagesApi = require('./api/WorkPackagesApi');
22
+
23
+ const {
24
+ DaluxError,
25
+ NotFoundError,
26
+ ApiError,
27
+ ValidationError,
28
+ AuthenticationError,
29
+ RateLimitError,
30
+ hasNextPage,
31
+ getNextBookmark,
32
+ paginate,
33
+ findByField,
34
+ findAllByField,
35
+ validateProjectId,
36
+ validateFileAreaId,
37
+ validateFolderId,
38
+ resolveFileAreaByName,
39
+ resolveFolderIdFromNamedPath,
40
+ } = require('./utils');
41
+
42
+ const models = require('./models');
43
+
44
+ /**
45
+ * Create a fully configured Dalux Build API client.
46
+ *
47
+ * @param {object} [options]
48
+ * @param {string} [options.baseUrl] - The API base URL (falls back to DALUX_BASE_URL env var)
49
+ * @param {string} [options.apiKey] - Your X-API-KEY (falls back to DALUX_API_KEY env var)
50
+ * @returns {{
51
+ * projects: ProjectsApi,
52
+ * companies: CompaniesApi,
53
+ * companyCatalog: CompanyCatalogApi,
54
+ * fileAreas: FileAreasApi,
55
+ * fileRevisions: FileRevisionsApi,
56
+ * fileUpload: FileUploadApi,
57
+ * files: FilesApi,
58
+ * folders: FoldersApi,
59
+ * forms: FormsApi,
60
+ * inspectionPlans: InspectionPlansApi,
61
+ * projectTemplates: ProjectTemplatesApi,
62
+ * tasks: TasksApi,
63
+ * testPlans: TestPlansApi,
64
+ * users: UsersApi,
65
+ * versionSets: VersionSetsApi,
66
+ * workPackages: WorkPackagesApi
67
+ * }}
68
+ */
69
+ function createClient({ baseUrl, apiKey } = {}) {
70
+ const configuration = new Configuration({ baseUrl, apiKey });
71
+ const apiClient = new ApiClient(configuration);
72
+
73
+ return {
74
+ projects: new ProjectsApi(apiClient),
75
+ companies: new CompaniesApi(apiClient),
76
+ companyCatalog: new CompanyCatalogApi(apiClient),
77
+ fileAreas: new FileAreasApi(apiClient),
78
+ fileRevisions: new FileRevisionsApi(apiClient),
79
+ fileUpload: new FileUploadApi(apiClient),
80
+ files: new FilesApi(apiClient),
81
+ folders: new FoldersApi(apiClient),
82
+ forms: new FormsApi(apiClient),
83
+ inspectionPlans: new InspectionPlansApi(apiClient),
84
+ projectTemplates: new ProjectTemplatesApi(apiClient),
85
+ tasks: new TasksApi(apiClient),
86
+ testPlans: new TestPlansApi(apiClient),
87
+ users: new UsersApi(apiClient),
88
+ versionSets: new VersionSetsApi(apiClient),
89
+ workPackages: new WorkPackagesApi(apiClient),
90
+ };
91
+ }
92
+
93
+ module.exports = {
94
+ createClient,
95
+ Configuration,
96
+ ApiClient,
97
+ CompaniesApi,
98
+ CompanyCatalogApi,
99
+ FileAreasApi,
100
+ FileRevisionsApi,
101
+ FileUploadApi,
102
+ FilesApi,
103
+ FoldersApi,
104
+ FormsApi,
105
+ InspectionPlansApi,
106
+ ProjectTemplatesApi,
107
+ ProjectsApi,
108
+ TasksApi,
109
+ TestPlansApi,
110
+ UsersApi,
111
+ VersionSetsApi,
112
+ WorkPackagesApi,
113
+ // Utilities
114
+ DaluxError,
115
+ NotFoundError,
116
+ ApiError,
117
+ ValidationError,
118
+ AuthenticationError,
119
+ RateLimitError,
120
+ hasNextPage,
121
+ getNextBookmark,
122
+ paginate,
123
+ findByField,
124
+ findAllByField,
125
+ validateProjectId,
126
+ validateFileAreaId,
127
+ validateFolderId,
128
+ resolveFileAreaByName,
129
+ resolveFolderIdFromNamedPath,
130
+ // Data models (zod schemas) - both `models.FolderSchema` and top-level `FolderSchema` work
131
+ models,
132
+ ...models,
133
+ };
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ const { z } = require('zod');
4
+
5
+ /**
6
+ * API response link. Mirrors python/dalux_build/models/common.py::Link.
7
+ */
8
+ const LinkSchema = z.object({
9
+ rel: z.string(),
10
+ href: z.string(),
11
+ method: z.string().nullish(),
12
+ });
13
+
14
+ /**
15
+ * Response metadata with pagination info. Mirrors common.py::Metadata.
16
+ */
17
+ const MetadataSchema = z.object({
18
+ totalItems: z.number().nullish(),
19
+ totalRemainingItems: z.number().nullish(),
20
+ });
21
+
22
+ module.exports = { LinkSchema, MetadataSchema };
@@ -0,0 +1,14 @@
1
+ 'use strict';
2
+
3
+ const { ProjectCompanySchema } = require('../projects');
4
+ const { listResponseSchema, singleResponseSchema } = require('../helpers');
5
+
6
+ /** Mirrors models/companies/models.py, which re-exports ProjectCompany. */
7
+ const CompaniesListResponseSchema = listResponseSchema(ProjectCompanySchema);
8
+ const CompanyResponseSchema = singleResponseSchema(ProjectCompanySchema);
9
+
10
+ module.exports = {
11
+ ProjectCompanySchema,
12
+ CompaniesListResponseSchema,
13
+ CompanyResponseSchema,
14
+ };
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+
3
+ const { z } = require('zod');
4
+ const { listResponseSchema, singleResponseSchema } = require('../helpers');
5
+
6
+ /**
7
+ * Mirrors models/company_catalog/responses.py. These are exported for name
8
+ * parity but, like their Python counterparts, are NOT what the API layer
9
+ * actually uses — company_catalog.py's get_companies/get_company return
10
+ * CompaniesListResponse/CompanyResponse (typed ProjectCompany) instead.
11
+ * See src/api/CompanyCatalogApi.js.
12
+ */
13
+ const CompanyCatalogListResponseSchema = listResponseSchema(z.any());
14
+ const CompanyCatalogResponseSchema = singleResponseSchema(z.any());
15
+
16
+ module.exports = {
17
+ CompanyCatalogListResponseSchema,
18
+ CompanyCatalogResponseSchema,
19
+ };
@@ -0,0 +1,44 @@
1
+ 'use strict';
2
+
3
+ const { z } = require('zod');
4
+ const { ValidationError } = require('../utils/errors');
5
+ const { unwrapData } = require('./helpers');
6
+
7
+ /**
8
+ * Validate and convert a raw API response into a typed model, throwing a
9
+ * ValidationError if the response doesn't match `schema`. Mirrors
10
+ * response_converter.py::convert_to_model, without its legacy-compat
11
+ * fallback branches (the JS test suite ships schema-valid fixtures).
12
+ * @param {*} response
13
+ * @param {import('zod').ZodTypeAny} schema
14
+ * @param {string} [schemaName]
15
+ * @returns {*}
16
+ */
17
+ function convertToModel(response, schema, schemaName = 'model') {
18
+ if (response === null || response === undefined) return null;
19
+ try {
20
+ return schema.parse(response);
21
+ } catch (err) {
22
+ if (err instanceof z.ZodError) {
23
+ throw new ValidationError(`Failed to convert response to ${schemaName}: ${err.message}`);
24
+ }
25
+ throw err;
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Validate and convert a list of raw items into typed models.
31
+ * Used by get_all_* pagination methods, which convert each item after
32
+ * pagination has already collected the raw list.
33
+ * @param {Array} items
34
+ * @param {import('zod').ZodTypeAny} itemSchema
35
+ * @param {string} [schemaName]
36
+ * @returns {Array}
37
+ */
38
+ function convertToModelList(items, itemSchema, schemaName = 'model') {
39
+ if (!Array.isArray(items)) return [];
40
+ const wrapped = unwrapData(itemSchema);
41
+ return items.map((item) => convertToModel(item, wrapped, schemaName));
42
+ }
43
+
44
+ module.exports = { convertToModel, convertToModelList };
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+
3
+ const { z } = require('zod');
4
+ const { listResponseSchema, singleResponseSchema } = require('../helpers');
5
+
6
+ /** Mirrors models/file_areas/models.py::FileArea (all fields required). */
7
+ const FileAreaSchema = z.object({
8
+ fileAreaId: z.string(),
9
+ fileAreaName: z.string(),
10
+ fileAreaType: z.string(),
11
+ });
12
+
13
+ const FileAreasListResponseSchema = listResponseSchema(FileAreaSchema);
14
+ const FileAreaResponseSchema = singleResponseSchema(FileAreaSchema);
15
+
16
+ module.exports = {
17
+ FileAreaSchema,
18
+ FileAreasListResponseSchema,
19
+ FileAreaResponseSchema,
20
+ };
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ const { z } = require('zod');
4
+
5
+ /**
6
+ * Mirrors models/file_revisions/models.py::FileRevision — an empty
7
+ * placeholder in Python too (get_file_revision_content returns raw
8
+ * binary/content in both packages, so there's nothing to validate).
9
+ */
10
+ const FileRevisionSchema = z.object({}).passthrough();
11
+
12
+ module.exports = { FileRevisionSchema };
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ const { z } = require('zod');
4
+
5
+ /**
6
+ * Mirrors models/file_upload/models.py::FileUpload — empty placeholder in
7
+ * Python too; upload/part/finalize responses are provider-specific and
8
+ * stay raw in both packages.
9
+ */
10
+ const FileUploadSchema = z.object({}).passthrough();
11
+
12
+ module.exports = { FileUploadSchema };
@@ -0,0 +1,97 @@
1
+ 'use strict';
2
+
3
+ const { z } = require('zod');
4
+ const { listResponseSchema, singleResponseSchema, nullableDefault } = require('../helpers');
5
+
6
+ /** Mirrors models/files/models.py::Reference. */
7
+ const ReferenceSchema = z.object({
8
+ key: z.string(),
9
+ value: z.string(),
10
+ });
11
+
12
+ /** Mirrors models/files/models.py::FileIntegerProperty. */
13
+ const FileIntegerPropertySchema = z.object({
14
+ integer: z.number().nullish(),
15
+ });
16
+
17
+ /** Mirrors models/files/models.py::FileDateProperty. */
18
+ const FileDatePropertySchema = z.object({
19
+ date: z.string().nullish(),
20
+ });
21
+
22
+ /** Mirrors models/files/models.py::FileTextProperty. */
23
+ const FileTextPropertySchema = z.object({
24
+ text: z.string().nullish(),
25
+ });
26
+
27
+ /** Mirrors models/files/models.py::FileReferenceProperty. */
28
+ const FileReferencePropertySchema = z.object({
29
+ reference: ReferenceSchema.nullish(),
30
+ });
31
+
32
+ /** Mirrors models/files/models.py::FilePropertyField. */
33
+ const FilePropertyFieldSchema = z.object({
34
+ key: z.string(),
35
+ name: z.string(),
36
+ values: z.array(z.any()).nullish(),
37
+ });
38
+
39
+ /**
40
+ * Mirrors models/files/models.py::FileNameFilter. Client-side filter params
41
+ * (not part of any API response), so field names stay plain camelCase with
42
+ * no aliasing concerns on either side.
43
+ */
44
+ const FileNameFilterSchema = z.object({
45
+ contains: z.array(z.string()).optional(),
46
+ containsMatch: z.enum(['any', 'all']).default('any'),
47
+ notContains: z.array(z.string()).optional(),
48
+ startswith: z.array(z.string()).optional(),
49
+ notStartswith: z.array(z.string()).optional(),
50
+ endswith: z.array(z.string()).optional(),
51
+ notEndswith: z.array(z.string()).optional(),
52
+ extensions: z.array(z.string()).optional(),
53
+ notExtensions: z.array(z.string()).optional(),
54
+ });
55
+
56
+ /**
57
+ * Mirrors models/files/models.py::File. `uploaded`/`lastModified` are kept
58
+ * as ISO date strings (not coerced to JS Date) so JSON round-tripping and
59
+ * existing string-based consumer code keeps working.
60
+ */
61
+ const FileSchema = z.object({
62
+ fileId: z.string(),
63
+ fileRevisionId: z.string().nullish(),
64
+ fileName: z.string(),
65
+ fileAreaId: z.string(),
66
+ folderId: z.string().nullish(),
67
+ uploadedByUserId: z.string().nullish(),
68
+ uploaded: z.string().nullish(),
69
+ lastModifiedByUserId: z.string().nullish(),
70
+ lastModified: z.string().nullish(),
71
+ version: z.string().nullish(),
72
+ deleted: nullableDefault(z.boolean(), false),
73
+ fileType: z.string().nullish(),
74
+ fileSize: z.number().nullish(),
75
+ contentHash: z.string().nullish(),
76
+ downloadLink: z.string().nullish(),
77
+ properties: z.array(FilePropertyFieldSchema).nullish(),
78
+ // Local-only fields set by bulk-download helpers, not present in raw API responses.
79
+ savedFilePath: z.string().nullish(),
80
+ savedMetadataPath: z.string().nullish(),
81
+ });
82
+
83
+ const FilesListResponseSchema = listResponseSchema(FileSchema);
84
+ const FileResponseSchema = singleResponseSchema(FileSchema);
85
+
86
+ module.exports = {
87
+ ReferenceSchema,
88
+ FileIntegerPropertySchema,
89
+ FileDatePropertySchema,
90
+ FileTextPropertySchema,
91
+ FileReferencePropertySchema,
92
+ FilePropertyFieldSchema,
93
+ FileNameFilterSchema,
94
+ FileSchema,
95
+ FilesListResponseSchema,
96
+ FileResponseSchema,
97
+ };
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+
3
+ const { z } = require('zod');
4
+ const { listResponseSchema, singleResponseSchema } = require('../helpers');
5
+
6
+ /** Mirrors models/folders/models.py::Folder. */
7
+ const FolderSchema = z.object({
8
+ folderId: z.string(),
9
+ folderName: z.string(),
10
+ parentFolderId: z.string().nullish(),
11
+ });
12
+
13
+ const FoldersListResponseSchema = listResponseSchema(FolderSchema);
14
+ const FolderResponseSchema = singleResponseSchema(FolderSchema);
15
+
16
+ module.exports = {
17
+ FolderSchema,
18
+ FoldersListResponseSchema,
19
+ FolderResponseSchema,
20
+ };
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+
3
+ const { z } = require('zod');
4
+ const { listResponseSchema, singleResponseSchema } = require('../helpers');
5
+
6
+ /**
7
+ * Mirrors models/forms/models.py::Form — empty placeholder in Python, so
8
+ * items stay untyped.
9
+ */
10
+ const FormSchema = z.any();
11
+
12
+ const FormsListResponseSchema = listResponseSchema(FormSchema);
13
+ const FormResponseSchema = singleResponseSchema(FormSchema);
14
+
15
+ module.exports = {
16
+ FormSchema,
17
+ FormsListResponseSchema,
18
+ FormResponseSchema,
19
+ };
@@ -0,0 +1,62 @@
1
+ 'use strict';
2
+
3
+ const { z } = require('zod');
4
+ const { LinkSchema, MetadataSchema } = require('./common');
5
+
6
+ /**
7
+ * Unwraps a `{ data: {...} }` wrapper before validating against `schema`.
8
+ * Bare objects (no `data` wrapper) pass straight through.
9
+ * Mirrors the `unwrap_and_convert_items` field_validator repeated across
10
+ * every Python `responses.py` file.
11
+ * @param {import('zod').ZodTypeAny} schema
12
+ */
13
+ function unwrapData(schema) {
14
+ return z.preprocess((value) => {
15
+ if (value && typeof value === 'object' && !Array.isArray(value) && 'data' in value) {
16
+ return value.data;
17
+ }
18
+ return value;
19
+ }, schema);
20
+ }
21
+
22
+ /**
23
+ * Builds a `{ items, metadata?, links? }` list-response schema.
24
+ * Accepts a bare array payload too (wraps it as `{ items: [...] }`) —
25
+ * mirrors TaskChanges' `wrap_list_payloads` model_validator.
26
+ * @param {import('zod').ZodTypeAny} itemSchema
27
+ */
28
+ function listResponseSchema(itemSchema) {
29
+ return z.preprocess(
30
+ (value) => (Array.isArray(value) ? { items: value } : value),
31
+ z.object({
32
+ items: z.array(unwrapData(itemSchema)).default([]),
33
+ metadata: MetadataSchema.optional(),
34
+ links: z.array(LinkSchema).optional(),
35
+ }),
36
+ );
37
+ }
38
+
39
+ /**
40
+ * Builds a `{ data, links? }` single-item response schema.
41
+ * @param {import('zod').ZodTypeAny} itemSchema
42
+ */
43
+ function singleResponseSchema(itemSchema) {
44
+ return z.object({
45
+ data: itemSchema,
46
+ links: z.array(LinkSchema).optional(),
47
+ });
48
+ }
49
+
50
+ /**
51
+ * A defaulted field that also accepts an explicit JSON `null` (treated the
52
+ * same as an absent field) — mirrors Pydantic's `Optional[T] = default`,
53
+ * which accepts both a missing key and an explicit `null`. Plain
54
+ * `schema.default(x)` alone only fires on `undefined`, not `null`.
55
+ * @param {import('zod').ZodTypeAny} schema
56
+ * @param {*} defaultValue
57
+ */
58
+ function nullableDefault(schema, defaultValue) {
59
+ return z.preprocess((v) => (v === null ? undefined : v), schema.default(defaultValue));
60
+ }
61
+
62
+ module.exports = { unwrapData, listResponseSchema, singleResponseSchema, nullableDefault };