dalux-build-api 2.0.0 → 2.0.2

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/README.md CHANGED
@@ -1,17 +1,11 @@
1
- # Dalux Build API
1
+ # Dalux Build API – Node.js Client
2
2
 
3
- API clients for the [Dalux Build REST API](https://app.swaggerhub.com/apis-docs/Dalux/DaluxBuild-api/4.14), available in two languages:
3
+ A lightweight Node.js client for the [Dalux Build REST API](https://app.swaggerhub.com/apis-docs/Dalux/DaluxBuild-api/4.14).
4
4
 
5
- | Language | Location | Documentation |
6
- | -------- | -------------------- | ------------------------------------ |
7
- | Node.js | [`/`](.) | See below |
8
- | Python | [`/python`](python/) | [python/README.md](python/README.md) |
9
-
10
- ---
11
-
12
- # Node.js Client
13
-
14
- A lightweight Node.js client for the Dalux Build REST API.
5
+ See the [repo root README](../README.md) for the Python client
6
+ (`dalux-build`) and the [webhook server](../webhook-server/README.md) built
7
+ on it the two clients are versioned, tested, and released together; see
8
+ [../CONTRIBUTING.md](../CONTRIBUTING.md).
15
9
 
16
10
  ## Requirements
17
11
 
@@ -41,6 +35,53 @@ const dalux = createClient({
41
35
 
42
36
  The returned object exposes one namespace per API group (see [API Reference](#api-reference) below).
43
37
 
38
+ ## Next.js (browser-safe, no CORS)
39
+
40
+ Dalux API keys must stay server-side, and CORS is controlled by the Dalux API rather than this SDK.
41
+ For Client Components, use the package's same-origin Next.js adapter: the browser calls your own
42
+ App Router endpoint, and that endpoint uses the regular Dalux client on the server.
43
+
44
+ ```js
45
+ // app/api/dalux/route.js
46
+ import { createClient } from "dalux-build-api";
47
+ import { createDaluxRouteHandler } from "dalux-build-api/next";
48
+ import { getCurrentUser } from "@/lib/auth";
49
+
50
+ const dalux = createClient({
51
+ baseUrl: process.env.DALUX_BASE_URL,
52
+ apiKey: process.env.DALUX_API_KEY,
53
+ });
54
+
55
+ export const POST = createDaluxRouteHandler({
56
+ client: dalux,
57
+ // Required: expose only the methods this application needs.
58
+ allowedMethods: {
59
+ projects: ["listProjects", "getProject"],
60
+ tasks: ["getProjectTasks", "getAllProjectTasks"],
61
+ },
62
+ // Recommended: connect this to your application's session/authorization.
63
+ authorize: async (request) => Boolean(await getCurrentUser(request)),
64
+ });
65
+ ```
66
+
67
+ ```js
68
+ // app/projects/Projects.jsx
69
+ "use client";
70
+
71
+ import { createBrowserClient } from "dalux-build-api/browser";
72
+
73
+ const dalux = createBrowserClient({ url: "/api/dalux" });
74
+
75
+ export async function loadProjects() {
76
+ return dalux.projects.listProjects();
77
+ }
78
+ ```
79
+
80
+ The browser entry contains no Axios, Node.js filesystem modules, base URL, or API key. Calls use
81
+ same-origin `fetch`, so the browser does not make a cross-origin Dalux request. Write methods are
82
+ supported only when you explicitly add them to `allowedMethods`; protect the route with your app's
83
+ authorization before exposing them.
84
+
44
85
  ### Examples
45
86
 
46
87
  **List all projects**
@@ -85,7 +126,7 @@ const allTasks = await dalux.tasks.getAllProjectTasks(
85
126
 
86
127
  **Files: browse (6.1), fetch all pages, download**
87
128
 
88
- `listFiles` and `getAllFiles` call **GET `/6.1/projects/{projectId}/file_areas/{fileAreaId}/files`** (Dalux `listFiles`). Supported query parameters include `folderId`, `updatedAfter`, and `includeProperties` (see the official spec). Single-file metadata still uses **GET `/5.0/.../files/{fileId}`** (`getFile`).
129
+ `listFiles` and `getAllFiles` call **GET `/6.1/projects/{projectId}/file_areas/{fileAreaId}/files`** (Dalux `listFiles`). The documented optional query parameter is `includeProperties`; pagination uses Dalux's returned bookmark links. Single-file metadata still uses **GET `/5.0/.../files/{fileId}`** (`getFile`).
89
130
 
90
131
  ```js
91
132
  const allFiles = await dalux.files.getAllFiles(
@@ -192,10 +233,15 @@ All API namespaces are accessible on the object returned by `createClient`:
192
233
  | `versionSets` | `VersionSetsApi` | Version sets and version set files |
193
234
  | `workPackages` | `WorkPackagesApi` | Work packages on a project |
194
235
 
236
+ `InspectionPlansApi` and `TestPlansApi` mirror the Python client's list behavior: their `list*`
237
+ methods return the typed `items` array by default and accept `fullResponse = true` as the third
238
+ argument to retain pagination metadata and links. Each collection also has a `getAll*` method that
239
+ follows Dalux bookmark pagination automatically.
240
+
195
241
  ### ProjectsApi
196
242
 
197
243
  | Method | HTTP | Path |
198
- | ------------------------------------------- | ----- | -------------------------------------------------------------- |
244
+ | -------------------------------------------- | ----- | ---------------------------------------------------------------- |
199
245
  | `listProjects(params?)` | GET | `/5.1/projects` |
200
246
  | `getProject(projectId)` | GET | `/5.0/projects/{projectId}` |
201
247
  | `createProject(body)` | POST | `/5.0/projects` |
@@ -218,7 +264,7 @@ All API namespaces are accessible on the object returned by `createClient`:
218
264
  ### CompanyCatalogApi
219
265
 
220
266
  | Method | HTTP | Path |
221
- | -------------------------------------------------- | ----- | --------------------------------------------------------------------------- |
267
+ | -------------------------------------------------- | ----- | ----------------------------------------------------------------------------- |
222
268
  | `getCompanies(params?)` | GET | `/2.2/companyCatalog` |
223
269
  | `getCompany(catalogCompanyId)` | GET | `/1.2/companyCatalog/{catalogCompanyId}` |
224
270
  | `createCompany(body)` | POST | `/2.2/companyCatalog` |
@@ -244,7 +290,7 @@ Query params: pass OData options on `getProjectTasks` / `getAllProjectTasks` (fo
244
290
  ### FileAreasApi
245
291
 
246
292
  | Method | HTTP | Path |
247
- | ------------------------------------ | ---- | --------------------------------------------------- |
293
+ | ------------------------------------- | ---- | ----------------------------------------------------- |
248
294
  | `getFileAreas(projectId, params?)` | GET | `/5.1/projects/{projectId}/file_areas` |
249
295
  | `getFileArea(projectId, fileAreaId)` | GET | `/1.0/projects/{projectId}/file_areas/{fileAreaId}` |
250
296
 
@@ -253,7 +299,7 @@ Query params: pass OData options on `getProjectTasks` / `getAllProjectTasks` (fo
253
299
  Browse and paginated helpers use **route version 6.1** for the file collection. `getFile` (one file by id) uses **5.0**, as in the [Dalux Build API](https://app.swaggerhub.com/apis-docs/Dalux/DaluxBuild-api/4.14).
254
300
 
255
301
  | Method | HTTP | Path |
256
- | ------------------------------------------------------------------------- | --------------- | --------------------------------------------------------------------------------------------------------------- |
302
+ | --------------------------------------------------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------ |
257
303
  | `listFiles(projectId, fileAreaId, params?)` | GET | `/6.1/projects/{projectId}/file_areas/{fileAreaId}/files` |
258
304
  | `getAllFiles(projectId, fileAreaId, params?, verbose?)` | GET (paginated) | Same — all file `items` via bookmark / `metadata.totalRemainingItems` |
259
305
  | `getAllFilesInFolder(projectId, fileAreaId, folderId, params?, verbose?)` | — | Uses `getAllFiles` then filters by `data.folderId` |
@@ -267,7 +313,7 @@ Browse and paginated helpers use **route version 6.1** for the file collection.
267
313
  ### FoldersApi
268
314
 
269
315
  | Method | HTTP | Path |
270
- | ----------------------------------------------------------- | ---- | ---------------------------------------------------------------------- |
316
+ | ------------------------------------------------------------- | ---- | -------------------------------------------------------------------------- |
271
317
  | `listFolders(projectId, fileAreaId, params?)` | GET | `/5.1/projects/{projectId}/file_areas/{fileAreaId}/folders` |
272
318
  | `getFolder(projectId, fileAreaId, folderId)` | GET | `/5.0/projects/{projectId}/file_areas/{fileAreaId}/folders/{folderId}` |
273
319
  | `getFolderFilesProperties(projectId, fileAreaId, folderId)` | GET | `/1.0/.../folders/{folderId}/files/properties/1.0/mappings` |
@@ -275,7 +321,7 @@ Browse and paginated helpers use **route version 6.1** for the file collection.
275
321
  ### FileUploadApi
276
322
 
277
323
  | Method | HTTP | Path |
278
- | ---------------------------------------------------------- | ---- | ---------------------------------------------------------- |
324
+ | ------------------------------------------------------------ | ---- | -------------------------------------------------------------- |
279
325
  | `createUpload(projectId, fileAreaId, body)` | POST | `/1.0/projects/{projectId}/file_areas/{fileAreaId}/upload` |
280
326
  | `uploadFilePart(projectId, fileAreaId, uploadGuid, chunk)` | POST | `/1.0/.../upload/{uploadGuid}` |
281
327
  | `finishUpload(projectId, fileAreaId, uploadGuid, body)` | POST | `/2.0/.../upload/{uploadGuid}/finalize` |
@@ -283,13 +329,13 @@ Browse and paginated helpers use **route version 6.1** for the file collection.
283
329
  ### FileRevisionsApi
284
330
 
285
331
  | Method | HTTP | Path |
286
- | ----------------------------------------------------------------------- | ---- | ------------------------------------------------------------ |
332
+ | --------------------------------------------------------------------------- | ---- | ------------------------------------------------------------ |
287
333
  | `getFileRevisionContent(projectId, fileAreaId, fileId, fileRevisionId)` | GET | `/2.0/.../files/{fileId}/revisions/{fileRevisionId}/content` |
288
334
 
289
335
  ### FormsApi
290
336
 
291
337
  | Method | HTTP | Path |
292
- | ----------------------------------------------- | ---- | --------------------------------------------- |
338
+ | -------------------------------------------------- | ---- | ------------------------------------------------ |
293
339
  | `getProjectForms(projectId, params?)` | GET | `/2.1/projects/{projectId}/forms` |
294
340
  | `getForm(projectId, formId)` | GET | `/1.2/projects/{projectId}/forms/{formId}` |
295
341
  | `getProjectFormAttachments(projectId, params?)` | GET | `/2.1/projects/{projectId}/forms/attachments` |
@@ -297,7 +343,7 @@ Browse and paginated helpers use **route version 6.1** for the file collection.
297
343
  ### UsersApi
298
344
 
299
345
  | Method | HTTP | Path |
300
- | -------------------------------------- | ---- | ------------------------------------------ |
346
+ | ----------------------------------------- | ---- | ---------------------------------------------- |
301
347
  | `getUser(userId)` | GET | `/1.1/users/{userId}` |
302
348
  | `listProjectUsers(projectId, params?)` | GET | `/1.2/projects/{projectId}/users` |
303
349
  | `getProjectUser(projectId, userId)` | GET | `/1.1/projects/{projectId}/users/{userId}` |
@@ -305,13 +351,13 @@ Browse and paginated helpers use **route version 6.1** for the file collection.
305
351
  ### ProjectTemplatesApi
306
352
 
307
353
  | Method | HTTP | Path |
308
- | ------------------------------- | ---- | ----------------------- |
354
+ | ---------------------------------- | ---- | --------------------------- |
309
355
  | `listProjectTemplates(params?)` | GET | `/1.1/projectTemplates` |
310
356
 
311
357
  ### InspectionPlansApi
312
358
 
313
359
  | Method | HTTP | Path |
314
- | ----------------------------------------------------- | ---- | ------------------------------------------------------- |
360
+ | -------------------------------------------------------- | ---- | ------------------------------------------------------------ |
315
361
  | `listInspectionPlans(projectId, params?)` | GET | `/1.2/projects/{projectId}/inspectionPlans` |
316
362
  | `listInspectionPlanItems(projectId, params?)` | GET | `/1.1/projects/{projectId}/inspectionPlanItems` |
317
363
  | `listInspectionPlanItemZones(projectId, params?)` | GET | `/1.1/projects/{projectId}/inspectionPlanItemZones` |
@@ -320,7 +366,7 @@ Browse and paginated helpers use **route version 6.1** for the file collection.
320
366
  ### TestPlansApi
321
367
 
322
368
  | Method | HTTP | Path |
323
- | ----------------------------------------------- | ---- | ------------------------------------------------- |
369
+ | ---------------------------------------------------- | ---- | -------------------------------------------------- |
324
370
  | `listTestPlans(projectId, params?)` | GET | `/1.2/projects/{projectId}/testPlans` |
325
371
  | `listTestPlanItems(projectId, params?)` | GET | `/1.1/projects/{projectId}/testPlanItems` |
326
372
  | `listTestPlanItemZones(projectId, params?)` | GET | `/1.1/projects/{projectId}/testPlanItemZones` |
@@ -329,7 +375,7 @@ Browse and paginated helpers use **route version 6.1** for the file collection.
329
375
  ### VersionSetsApi
330
376
 
331
377
  | Method | HTTP | Path |
332
- | --------------------------------------------------------- | ---- | ---------------------------------------------------------------- |
378
+ | ------------------------------------------------------------- | ---- | ---------------------------------------------------------------- |
333
379
  | `getVersionSets(projectId, params?)` | GET | `/2.1/projects/{projectId}/version_sets` |
334
380
  | `getVersionSet(projectId, versionSetId)` | GET | `/2.0/projects/{projectId}/version_sets/{versionSetId}` |
335
381
  | `listFileAreaVersionSets(projectId, fileAreaId, params?)` | GET | `/2.1/projects/{projectId}/file_areas/{fileAreaId}/version_sets` |
@@ -338,7 +384,7 @@ Browse and paginated helpers use **route version 6.1** for the file collection.
338
384
  ### WorkPackagesApi
339
385
 
340
386
  | Method | HTTP | Path |
341
- | -------------------------------------- | ---- | ---------------------------------------- |
387
+ | ----------------------------------------- | ---- | --------------------------------------------- |
342
388
  | `listWorkPackages(projectId, params?)` | GET | `/1.0/projects/{projectId}/workpackages` |
343
389
 
344
390
  ## Advanced Usage
@@ -365,39 +411,24 @@ const projects = new ProjectsApi(apiClient);
365
411
  const tasks = new TasksApi(apiClient);
366
412
  ```
367
413
 
368
- ## Development
414
+ ## Testing
369
415
 
370
416
  ```bash
371
- # Install dependencies
372
- npm install
373
-
374
- # Run tests with coverage
375
- npm test
417
+ npm install # from the repo root (this is an npm workspace)
418
+ npm test --workspace=javascript # Jest, with coverage
376
419
  ```
377
420
 
378
- ## Maintainer: npm releases
379
-
380
- ### Automatic (push to `main`)
381
-
382
- 1. Merge to `main` (or push directly). **CI** must pass.
383
- 2. After **both** the Node and Python test jobs succeed, **CI** calls **Publish npm Package** via `workflow_call` (same run as CI, so it does not depend on `workflow_run`).
384
- 3. The publish job only runs when that push changes `package.json`, `package-lock.json`, `src/`, or `test/`.
385
- 4. **Version bump** uses the same rules as the Python publisher ([commit message tokens](python/README.md#maintainer-pypi-releases)), but the **current** semver is **`max(python/pyproject.toml, package.json)`** so npm catches up when the Python package is ahead.
386
- 5. It runs tests again, publishes to npm, then pushes `chore: release vX.Y.Z [skip npm]`, tag `vX.Y.Z`, and a **GitHub Release** (unless the tag already exists). **`[skip npm]`** skips a second publish pass and skips **CI** on that sync commit.
387
-
388
- ### Manual
389
-
390
- - **Actions → Publish npm Package → Run workflow** for **patch / minor / major** (same test → publish → sync commit → tag → **GitHub Release**). The entry workflow is [npm-publish.yml](.github/workflows/npm-publish.yml); it delegates to [npm-publish-reusable.yml](.github/workflows/npm-publish-reusable.yml) (GitHub requires `workflow_call` in its own file).
391
- - **GitHub Release (published)** publishes the version already in `package.json` at that tag (no version bump in the workflow).
392
-
393
- Create a GitHub **environment** named `npm` with secret **`NPM_TOKEN`**. If pushes or releases from `GITHUB_TOKEN` are blocked, set **`RELEASE_PAT`** as described in [python/README.md](python/README.md#maintainer-pypi-releases).
394
-
395
- ### First publish and `E404` on `npm publish`
421
+ CI runs this on Node.js 20/22/24 — see
422
+ [`../.github/workflows/tests.yml`](../.github/workflows/tests.yml).
396
423
 
397
- If CI fails with **`404 Not Found - PUT …/dalux-build-api`** and **`is not in this registry`**, the package does not exist on npm yet **or** your token is not allowed to **create** it.
424
+ ## Releasing
398
425
 
399
- - **Classic [Automation](https://docs.npmjs.com/about-access-tokens#automation-tokens)** tokens only publish packages your account **already** maintains. They cannot create a **new** package name. Fix: use a **granular access token** with write access to the `@bruadam` scope (or all packages), or run **`npm publish --access public`** once locally while logged in as the owner, then switch CI back to an automation token.
400
- - Confirm the package is not taken by someone else: [https://www.npmjs.com/package/dalux-build-api](https://www.npmjs.com/package/dalux-build-api).
426
+ This package is versioned and published together with the Python client by
427
+ [Changesets](https://github.com/changesets/changesets) — there is no manual
428
+ edit of `version` in `package.json`, and nothing publishes to npm unless the
429
+ full test suite (Node.js, Python, webhook server) passes first. See
430
+ [../CONTRIBUTING.md](../CONTRIBUTING.md#how-releases-work) for the full flow
431
+ and [../README.md](../README.md#releasing) for the PyPI side of it.
401
432
 
402
433
  ## License
403
434
 
package/package.json CHANGED
@@ -1,8 +1,13 @@
1
1
  {
2
2
  "name": "dalux-build-api",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Node.js client for the Dalux Build API",
5
5
  "main": "src/index.js",
6
+ "exports": {
7
+ ".": "./src/index.js",
8
+ "./browser": "./src/browser.js",
9
+ "./next": "./src/next.js"
10
+ },
6
11
  "files": [
7
12
  "src",
8
13
  "README.md"
@@ -24,12 +29,13 @@
24
29
  "license": "MIT",
25
30
  "repository": {
26
31
  "type": "git",
27
- "url": "git+https://github.com/bruadam/dalux-build.git"
32
+ "url": "git+https://github.com/bruadam/dalux-build.git",
33
+ "directory": "javascript"
28
34
  },
29
35
  "bugs": {
30
36
  "url": "https://github.com/bruadam/dalux-build/issues"
31
37
  },
32
- "homepage": "https://github.com/bruadam/dalux-build#readme",
38
+ "homepage": "https://github.com/bruadam/dalux-build/tree/main/javascript#readme",
33
39
  "publishConfig": {
34
40
  "access": "public"
35
41
  },
@@ -20,11 +20,13 @@ class FileRevisionsApi {
20
20
  * @param {string} fileRevisionId
21
21
  * @returns {Promise<Buffer>} Raw binary content
22
22
  */
23
- getFileRevisionContent(projectId, fileAreaId, fileId, fileRevisionId) {
24
- return this._client.get(
23
+ async getFileRevisionContent(projectId, fileAreaId, fileId, fileRevisionId) {
24
+ const data = await this._client.get(
25
25
  `/2.0/projects/${projectId}/file_areas/${fileAreaId}/files/${fileId}/revisions/${fileRevisionId}/content`,
26
26
  {},
27
+ { responseType: 'arraybuffer' },
27
28
  );
29
+ return Buffer.from(data);
28
30
  }
29
31
  }
30
32
 
@@ -26,7 +26,7 @@ class FilesApi {
26
26
  * GET /6.1/projects/{projectId}/file_areas/{fileAreaId}/files
27
27
  * @param {string} projectId
28
28
  * @param {string} fileAreaId
29
- * @param {object} [params] - Optional params (e.g. folderId, updatedAfter, includeProperties). The files endpoint does not support OData $filter.
29
+ * @param {object} [params] - Optional documented params such as includeProperties. The files endpoint does not support OData $filter.
30
30
  * @returns {Promise<object>}
31
31
  */
32
32
  async listFiles(projectId, fileAreaId, params = {}) {
@@ -161,6 +161,26 @@ class FilesApi {
161
161
  return path.resolve(filePath);
162
162
  }
163
163
 
164
+ /**
165
+ * Fetch a file's raw bytes from a direct download URL using X-API-KEY, without
166
+ * writing to disk. Useful when the caller needs to relay the content onward
167
+ * (e.g. as an HTTP response body) rather than save it to a local filesystem.
168
+ * @param {string} downloadLink
169
+ * @returns {Promise<{ buffer: Buffer, contentType: string|undefined }>}
170
+ */
171
+ async downloadFileBuffer(downloadLink) {
172
+ const apiKey = this._client.configuration.apiKey;
173
+ const response = await axios.get(downloadLink, {
174
+ headers: { 'X-API-KEY': apiKey },
175
+ responseType: 'arraybuffer',
176
+ validateStatus: () => true,
177
+ });
178
+ if (response.status !== 200) {
179
+ throw new Error(`Failed to download file. Status code: ${response.status}`);
180
+ }
181
+ return { buffer: Buffer.from(response.data), contentType: response.headers['content-type'] };
182
+ }
183
+
164
184
  /**
165
185
  * Get a file by IDs or by full path.
166
186
  *
@@ -1,7 +1,17 @@
1
1
  'use strict';
2
2
 
3
- const { convertToModel } = require('../models/convert');
4
- const { InspectionPlansListResponseSchema } = require('../models/inspectionPlans');
3
+ const { convertToModel, convertToModelList } = require('../models/convert');
4
+ const { paginate } = require('../utils/pagination');
5
+ const {
6
+ InspectionPlanSchema,
7
+ InspectionPlanItemSchema,
8
+ InspectionPlanItemZoneSchema,
9
+ InspectionPlanRegistrationSchema,
10
+ InspectionPlansListResponseSchema,
11
+ InspectionPlanItemsListResponseSchema,
12
+ InspectionPlanItemZonesListResponseSchema,
13
+ InspectionPlanRegistrationsListResponseSchema,
14
+ } = require('../models/inspectionPlans');
5
15
 
6
16
  /**
7
17
  * API methods for inspection plans.
@@ -19,11 +29,27 @@ class InspectionPlansApi {
19
29
  * GET /1.2/projects/{projectId}/inspectionPlans
20
30
  * @param {string} projectId
21
31
  * @param {object} [params]
22
- * @returns {Promise<object>} InspectionPlansListResponse ({ items, metadata?, links? })
32
+ * @param {boolean} [fullResponse=false]
33
+ * @returns {Promise<object[]|object>}
23
34
  */
24
- async listInspectionPlans(projectId, params = {}) {
35
+ async listInspectionPlans(projectId, params = {}, fullResponse = false) {
25
36
  const response = await this._client.get(`/1.2/projects/${projectId}/inspectionPlans`, params);
26
- return convertToModel(response, InspectionPlansListResponseSchema, 'InspectionPlansListResponse');
37
+ const result = convertToModel(
38
+ response,
39
+ InspectionPlansListResponseSchema,
40
+ 'InspectionPlansListResponse',
41
+ );
42
+ return fullResponse ? result : (result?.items || []);
43
+ }
44
+
45
+ async getAllInspectionPlans(projectId, params = {}, verbose = false) {
46
+ const items = await paginate(
47
+ `/1.2/projects/${projectId}/inspectionPlans`,
48
+ this._client,
49
+ params,
50
+ verbose,
51
+ );
52
+ return convertToModelList(items, InspectionPlanSchema, 'InspectionPlan');
27
53
  }
28
54
 
29
55
  /**
@@ -33,8 +59,24 @@ class InspectionPlansApi {
33
59
  * @param {object} [params]
34
60
  * @returns {Promise<object>}
35
61
  */
36
- listInspectionPlanItems(projectId, params = {}) {
37
- return this._client.get(`/1.1/projects/${projectId}/inspectionPlanItems`, params);
62
+ async listInspectionPlanItems(projectId, params = {}, fullResponse = false) {
63
+ const response = await this._client.get(`/1.1/projects/${projectId}/inspectionPlanItems`, params);
64
+ const result = convertToModel(
65
+ response,
66
+ InspectionPlanItemsListResponseSchema,
67
+ 'InspectionPlanItemsListResponse',
68
+ );
69
+ return fullResponse ? result : (result?.items || []);
70
+ }
71
+
72
+ async getAllInspectionPlanItems(projectId, params = {}, verbose = false) {
73
+ const items = await paginate(
74
+ `/1.1/projects/${projectId}/inspectionPlanItems`,
75
+ this._client,
76
+ params,
77
+ verbose,
78
+ );
79
+ return convertToModelList(items, InspectionPlanItemSchema, 'InspectionPlanItem');
38
80
  }
39
81
 
40
82
  /**
@@ -44,8 +86,24 @@ class InspectionPlansApi {
44
86
  * @param {object} [params]
45
87
  * @returns {Promise<object>}
46
88
  */
47
- listInspectionPlanItemZones(projectId, params = {}) {
48
- return this._client.get(`/1.1/projects/${projectId}/inspectionPlanItemZones`, params);
89
+ async listInspectionPlanItemZones(projectId, params = {}, fullResponse = false) {
90
+ const response = await this._client.get(`/1.1/projects/${projectId}/inspectionPlanItemZones`, params);
91
+ const result = convertToModel(
92
+ response,
93
+ InspectionPlanItemZonesListResponseSchema,
94
+ 'InspectionPlanItemZonesListResponse',
95
+ );
96
+ return fullResponse ? result : (result?.items || []);
97
+ }
98
+
99
+ async getAllInspectionPlanItemZones(projectId, params = {}, verbose = false) {
100
+ const items = await paginate(
101
+ `/1.1/projects/${projectId}/inspectionPlanItemZones`,
102
+ this._client,
103
+ params,
104
+ verbose,
105
+ );
106
+ return convertToModelList(items, InspectionPlanItemZoneSchema, 'InspectionPlanItemZone');
49
107
  }
50
108
 
51
109
  /**
@@ -55,11 +113,27 @@ class InspectionPlansApi {
55
113
  * @param {object} [params]
56
114
  * @returns {Promise<object>}
57
115
  */
58
- listInspectionPlanRegistrations(projectId, params = {}) {
59
- return this._client.get(
116
+ async listInspectionPlanRegistrations(projectId, params = {}, fullResponse = false) {
117
+ const response = await this._client.get(
118
+ `/2.1/projects/${projectId}/inspectionPlanRegistrations`,
119
+ params,
120
+ );
121
+ const result = convertToModel(
122
+ response,
123
+ InspectionPlanRegistrationsListResponseSchema,
124
+ 'InspectionPlanRegistrationsListResponse',
125
+ );
126
+ return fullResponse ? result : (result?.items || []);
127
+ }
128
+
129
+ async getAllInspectionPlanRegistrations(projectId, params = {}, verbose = false) {
130
+ const items = await paginate(
60
131
  `/2.1/projects/${projectId}/inspectionPlanRegistrations`,
132
+ this._client,
61
133
  params,
134
+ verbose,
62
135
  );
136
+ return convertToModelList(items, InspectionPlanRegistrationSchema, 'InspectionPlanRegistration');
63
137
  }
64
138
  }
65
139
 
@@ -1,7 +1,17 @@
1
1
  'use strict';
2
2
 
3
- const { convertToModel } = require('../models/convert');
4
- const { TestPlansListResponseSchema } = require('../models/testPlans');
3
+ const { convertToModel, convertToModelList } = require('../models/convert');
4
+ const { paginate } = require('../utils/pagination');
5
+ const {
6
+ TestPlanSchema,
7
+ TestPlanItemSchema,
8
+ TestPlanItemZoneSchema,
9
+ TestPlanRegistrationSchema,
10
+ TestPlansListResponseSchema,
11
+ TestPlanItemsListResponseSchema,
12
+ TestPlanItemZonesListResponseSchema,
13
+ TestPlanRegistrationsListResponseSchema,
14
+ } = require('../models/testPlans');
5
15
 
6
16
  /**
7
17
  * API methods for test plans.
@@ -19,11 +29,27 @@ class TestPlansApi {
19
29
  * GET /1.2/projects/{projectId}/testPlans
20
30
  * @param {string} projectId
21
31
  * @param {object} [params]
22
- * @returns {Promise<object>} TestPlansListResponse ({ items, metadata?, links? })
32
+ * @param {boolean} [fullResponse=false]
33
+ * @returns {Promise<object[]|object>}
23
34
  */
24
- async listTestPlans(projectId, params = {}) {
35
+ async listTestPlans(projectId, params = {}, fullResponse = false) {
25
36
  const response = await this._client.get(`/1.2/projects/${projectId}/testPlans`, params);
26
- return convertToModel(response, TestPlansListResponseSchema, 'TestPlansListResponse');
37
+ const result = convertToModel(
38
+ response,
39
+ TestPlansListResponseSchema,
40
+ 'TestPlansListResponse',
41
+ );
42
+ return fullResponse ? result : (result?.items || []);
43
+ }
44
+
45
+ async getAllTestPlans(projectId, params = {}, verbose = false) {
46
+ const items = await paginate(
47
+ `/1.2/projects/${projectId}/testPlans`,
48
+ this._client,
49
+ params,
50
+ verbose,
51
+ );
52
+ return convertToModelList(items, TestPlanSchema, 'TestPlan');
27
53
  }
28
54
 
29
55
  /**
@@ -33,8 +59,24 @@ class TestPlansApi {
33
59
  * @param {object} [params]
34
60
  * @returns {Promise<object>}
35
61
  */
36
- listTestPlanItems(projectId, params = {}) {
37
- return this._client.get(`/1.1/projects/${projectId}/testPlanItems`, params);
62
+ async listTestPlanItems(projectId, params = {}, fullResponse = false) {
63
+ const response = await this._client.get(`/1.1/projects/${projectId}/testPlanItems`, params);
64
+ const result = convertToModel(
65
+ response,
66
+ TestPlanItemsListResponseSchema,
67
+ 'TestPlanItemsListResponse',
68
+ );
69
+ return fullResponse ? result : (result?.items || []);
70
+ }
71
+
72
+ async getAllTestPlanItems(projectId, params = {}, verbose = false) {
73
+ const items = await paginate(
74
+ `/1.1/projects/${projectId}/testPlanItems`,
75
+ this._client,
76
+ params,
77
+ verbose,
78
+ );
79
+ return convertToModelList(items, TestPlanItemSchema, 'TestPlanItem');
38
80
  }
39
81
 
40
82
  /**
@@ -44,8 +86,24 @@ class TestPlansApi {
44
86
  * @param {object} [params]
45
87
  * @returns {Promise<object>}
46
88
  */
47
- listTestPlanItemZones(projectId, params = {}) {
48
- return this._client.get(`/1.1/projects/${projectId}/testPlanItemZones`, params);
89
+ async listTestPlanItemZones(projectId, params = {}, fullResponse = false) {
90
+ const response = await this._client.get(`/1.1/projects/${projectId}/testPlanItemZones`, params);
91
+ const result = convertToModel(
92
+ response,
93
+ TestPlanItemZonesListResponseSchema,
94
+ 'TestPlanItemZonesListResponse',
95
+ );
96
+ return fullResponse ? result : (result?.items || []);
97
+ }
98
+
99
+ async getAllTestPlanItemZones(projectId, params = {}, verbose = false) {
100
+ const items = await paginate(
101
+ `/1.1/projects/${projectId}/testPlanItemZones`,
102
+ this._client,
103
+ params,
104
+ verbose,
105
+ );
106
+ return convertToModelList(items, TestPlanItemZoneSchema, 'TestPlanItemZone');
49
107
  }
50
108
 
51
109
  /**
@@ -55,8 +113,24 @@ class TestPlansApi {
55
113
  * @param {object} [params]
56
114
  * @returns {Promise<object>}
57
115
  */
58
- listTestPlanRegistrations(projectId, params = {}) {
59
- return this._client.get(`/1.1/projects/${projectId}/testPlanRegistrations`, params);
116
+ async listTestPlanRegistrations(projectId, params = {}, fullResponse = false) {
117
+ const response = await this._client.get(`/1.1/projects/${projectId}/testPlanRegistrations`, params);
118
+ const result = convertToModel(
119
+ response,
120
+ TestPlanRegistrationsListResponseSchema,
121
+ 'TestPlanRegistrationsListResponse',
122
+ );
123
+ return fullResponse ? result : (result?.items || []);
124
+ }
125
+
126
+ async getAllTestPlanRegistrations(projectId, params = {}, verbose = false) {
127
+ const items = await paginate(
128
+ `/1.1/projects/${projectId}/testPlanRegistrations`,
129
+ this._client,
130
+ params,
131
+ verbose,
132
+ );
133
+ return convertToModelList(items, TestPlanRegistrationSchema, 'TestPlanRegistration');
60
134
  }
61
135
  }
62
136
 
package/src/apiClient.js CHANGED
@@ -44,6 +44,9 @@ class ApiClient {
44
44
  _getErrorDetail(response) {
45
45
  try {
46
46
  const data = response.data;
47
+ if (Buffer.isBuffer(data) || data instanceof ArrayBuffer) {
48
+ return Buffer.from(data).toString('utf8').slice(0, 200);
49
+ }
47
50
  if (data && typeof data === 'object') {
48
51
  if (data.message) return data.message;
49
52
  if (data.error) return data.error;
@@ -76,11 +79,12 @@ class ApiClient {
76
79
  * Perform a GET request.
77
80
  * @param {string} path - URL path (e.g. '/5.1/projects')
78
81
  * @param {object} [params] - Query string parameters
82
+ * @param {object} [config] - Extra axios config (e.g. { responseType: 'arraybuffer' } for binary content)
79
83
  * @returns {Promise<any>} Parsed response body
80
84
  */
81
- async get(path, params = {}) {
85
+ async get(path, params = {}, config = {}) {
82
86
  try {
83
- const response = await this._axios.get(path, { params });
87
+ const response = await this._axios.get(path, { params, ...config });
84
88
  return response.data;
85
89
  } catch (err) {
86
90
  this._handleAxiosError(err, path);
package/src/browser.js ADDED
@@ -0,0 +1,84 @@
1
+ 'use strict';
2
+
3
+ const RESOURCES = [
4
+ 'projects',
5
+ 'companies',
6
+ 'companyCatalog',
7
+ 'fileAreas',
8
+ 'fileRevisions',
9
+ 'fileUpload',
10
+ 'files',
11
+ 'folders',
12
+ 'forms',
13
+ 'inspectionPlans',
14
+ 'projectTemplates',
15
+ 'tasks',
16
+ 'testPlans',
17
+ 'users',
18
+ 'versionSets',
19
+ 'workPackages',
20
+ ];
21
+
22
+ class DaluxProxyError extends Error {
23
+ constructor(message, { name = 'DaluxProxyError', status, details } = {}) {
24
+ super(message);
25
+ this.name = name;
26
+ this.status = status;
27
+ this.details = details;
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Create a browser-safe Dalux client backed by a same-origin server route.
33
+ * The Dalux API key remains on the server; this client never accepts one.
34
+ *
35
+ * @param {object} [options]
36
+ * @param {string} [options.url='/api/dalux']
37
+ * @param {typeof fetch} [options.fetch]
38
+ * @param {Record<string, string>} [options.headers]
39
+ */
40
+ function createBrowserClient({ url = '/api/dalux', fetch: fetchImpl, headers = {} } = {}) {
41
+ const request = fetchImpl || globalThis.fetch;
42
+ if (typeof request !== 'function') {
43
+ throw new Error('A fetch implementation is required');
44
+ }
45
+
46
+ const invoke = async (resource, method, args) => {
47
+ const response = await request(url, {
48
+ method: 'POST',
49
+ headers: { 'Content-Type': 'application/json', ...headers },
50
+ body: JSON.stringify({ resource, method, args }),
51
+ });
52
+
53
+ let payload;
54
+ try {
55
+ payload = await response.json();
56
+ } catch {
57
+ throw new DaluxProxyError(`Dalux proxy returned HTTP ${response.status} without JSON`, {
58
+ status: response.status,
59
+ });
60
+ }
61
+
62
+ if (!response.ok || !payload.ok) {
63
+ const error = payload.error || {};
64
+ throw new DaluxProxyError(error.message || `Dalux proxy returned HTTP ${response.status}`, {
65
+ name: error.name,
66
+ status: response.status,
67
+ details: error.details,
68
+ });
69
+ }
70
+ return payload.data;
71
+ };
72
+
73
+ return Object.fromEntries(RESOURCES.map((resource) => [
74
+ resource,
75
+ new Proxy({}, {
76
+ get(_target, method) {
77
+ if (typeof method !== 'string' || method === 'then') return undefined;
78
+ return (...args) => invoke(resource, method, args);
79
+ },
80
+ }),
81
+ ]));
82
+ }
83
+
84
+ module.exports = { createBrowserClient, DaluxProxyError };
@@ -43,9 +43,27 @@ const { CompaniesListResponseSchema, CompanyResponseSchema } = require('./compan
43
43
 
44
44
  const { CompanyCatalogListResponseSchema, CompanyCatalogResponseSchema } = require('./companyCatalog');
45
45
 
46
- const { InspectionPlanSchema, InspectionPlansListResponseSchema } = require('./inspectionPlans');
46
+ const {
47
+ InspectionPlanSchema,
48
+ InspectionPlanItemSchema,
49
+ InspectionPlanItemZoneSchema,
50
+ InspectionPlanRegistrationSchema,
51
+ InspectionPlansListResponseSchema,
52
+ InspectionPlanItemsListResponseSchema,
53
+ InspectionPlanItemZonesListResponseSchema,
54
+ InspectionPlanRegistrationsListResponseSchema,
55
+ } = require('./inspectionPlans');
47
56
 
48
- const { TestPlanSchema, TestPlansListResponseSchema } = require('./testPlans');
57
+ const {
58
+ TestPlanSchema,
59
+ TestPlanItemSchema,
60
+ TestPlanItemZoneSchema,
61
+ TestPlanRegistrationSchema,
62
+ TestPlansListResponseSchema,
63
+ TestPlanItemsListResponseSchema,
64
+ TestPlanItemZonesListResponseSchema,
65
+ TestPlanRegistrationsListResponseSchema,
66
+ } = require('./testPlans');
49
67
 
50
68
  const { FormSchema, FormsListResponseSchema, FormResponseSchema } = require('./forms');
51
69
 
@@ -116,10 +134,22 @@ module.exports = {
116
134
  CompanyCatalogResponseSchema,
117
135
  // Inspection Plans
118
136
  InspectionPlanSchema,
137
+ InspectionPlanItemSchema,
138
+ InspectionPlanItemZoneSchema,
139
+ InspectionPlanRegistrationSchema,
119
140
  InspectionPlansListResponseSchema,
141
+ InspectionPlanItemsListResponseSchema,
142
+ InspectionPlanItemZonesListResponseSchema,
143
+ InspectionPlanRegistrationsListResponseSchema,
120
144
  // Test Plans
121
145
  TestPlanSchema,
146
+ TestPlanItemSchema,
147
+ TestPlanItemZoneSchema,
148
+ TestPlanRegistrationSchema,
122
149
  TestPlansListResponseSchema,
150
+ TestPlanItemsListResponseSchema,
151
+ TestPlanItemZonesListResponseSchema,
152
+ TestPlanRegistrationsListResponseSchema,
123
153
  // Forms
124
154
  FormSchema,
125
155
  FormsListResponseSchema,
@@ -3,16 +3,59 @@
3
3
  const { z } = require('zod');
4
4
  const { listResponseSchema } = require('../helpers');
5
5
 
6
- /**
7
- * Mirrors models/inspection_plans/models.py::InspectionPlan — an empty
8
- * placeholder in Python, so list items stay untyped (unwrapped but
9
- * otherwise unvalidated).
10
- */
11
- const InspectionPlanSchema = z.any();
6
+ const optionalString = z.string().nullish();
7
+ const optionalInteger = z.number().int().nullish();
8
+
9
+ const InspectionPlanSchema = z.object({
10
+ inspectionPlanId: optionalString,
11
+ name: optionalString,
12
+ workpackageId: optionalString,
13
+ }).passthrough();
14
+
15
+ const InspectionPlanItemSchema = z.object({
16
+ inspectionPlanItemId: optionalString,
17
+ inspectionPlanId: optionalString,
18
+ number: optionalString,
19
+ subject: optionalString,
20
+ heading: optionalString,
21
+ subHeading: optionalString,
22
+ extentType: optionalString,
23
+ planned: optionalInteger,
24
+ ongoing: optionalInteger,
25
+ completed: optionalInteger,
26
+ nonPlannedOngoing: optionalInteger,
27
+ nonPlannedCompleted: optionalInteger,
28
+ }).passthrough();
29
+
30
+ const InspectionPlanItemZoneSchema = z.object({
31
+ inspectionPlanItemId: optionalString,
32
+ inspectionPlanItemZoneId: optionalString,
33
+ name: optionalString,
34
+ planned: optionalInteger,
35
+ ongoing: optionalInteger,
36
+ completed: optionalInteger,
37
+ }).passthrough();
38
+
39
+ const InspectionPlanRegistrationSchema = z.object({
40
+ status: optionalString,
41
+ formId: optionalString,
42
+ taskId: optionalString,
43
+ inspectionPlanItemId: optionalString,
44
+ inspectionPlanItemZoneId: optionalString,
45
+ }).passthrough();
12
46
 
13
47
  const InspectionPlansListResponseSchema = listResponseSchema(InspectionPlanSchema);
48
+ const InspectionPlanItemsListResponseSchema = listResponseSchema(InspectionPlanItemSchema);
49
+ const InspectionPlanItemZonesListResponseSchema = listResponseSchema(InspectionPlanItemZoneSchema);
50
+ const InspectionPlanRegistrationsListResponseSchema = listResponseSchema(InspectionPlanRegistrationSchema);
14
51
 
15
52
  module.exports = {
16
53
  InspectionPlanSchema,
54
+ InspectionPlanItemSchema,
55
+ InspectionPlanItemZoneSchema,
56
+ InspectionPlanRegistrationSchema,
17
57
  InspectionPlansListResponseSchema,
58
+ InspectionPlanItemsListResponseSchema,
59
+ InspectionPlanItemZonesListResponseSchema,
60
+ InspectionPlanRegistrationsListResponseSchema,
18
61
  };
@@ -3,15 +3,59 @@
3
3
  const { z } = require('zod');
4
4
  const { listResponseSchema } = require('../helpers');
5
5
 
6
- /**
7
- * Mirrors models/test_plans/models.py::TestPlan — empty placeholder in
8
- * Python, so list items stay untyped.
9
- */
10
- const TestPlanSchema = z.any();
6
+ const optionalString = z.string().nullish();
7
+ const optionalInteger = z.number().int().nullish();
8
+
9
+ const TestPlanSchema = z.object({
10
+ testPlanId: optionalString,
11
+ name: optionalString,
12
+ workpackageId: optionalString,
13
+ }).passthrough();
14
+
15
+ const TestPlanItemSchema = z.object({
16
+ testPlanItemId: optionalString,
17
+ testPlanId: optionalString,
18
+ number: optionalString,
19
+ subject: optionalString,
20
+ heading: optionalString,
21
+ subHeading: optionalString,
22
+ extentType: optionalString,
23
+ planned: optionalInteger,
24
+ ongoing: optionalInteger,
25
+ completed: optionalInteger,
26
+ nonPlannedOngoing: optionalInteger,
27
+ nonPlannedCompleted: optionalInteger,
28
+ }).passthrough();
29
+
30
+ const TestPlanItemZoneSchema = z.object({
31
+ testPlanItemId: optionalString,
32
+ testPlanItemZoneId: optionalString,
33
+ name: optionalString,
34
+ planned: optionalInteger,
35
+ ongoing: optionalInteger,
36
+ completed: optionalInteger,
37
+ }).passthrough();
38
+
39
+ const TestPlanRegistrationSchema = z.object({
40
+ status: optionalString,
41
+ formId: optionalString,
42
+ taskId: optionalString,
43
+ testPlanItemId: optionalString,
44
+ testPlanItemZoneId: optionalString,
45
+ }).passthrough();
11
46
 
12
47
  const TestPlansListResponseSchema = listResponseSchema(TestPlanSchema);
48
+ const TestPlanItemsListResponseSchema = listResponseSchema(TestPlanItemSchema);
49
+ const TestPlanItemZonesListResponseSchema = listResponseSchema(TestPlanItemZoneSchema);
50
+ const TestPlanRegistrationsListResponseSchema = listResponseSchema(TestPlanRegistrationSchema);
13
51
 
14
52
  module.exports = {
15
53
  TestPlanSchema,
54
+ TestPlanItemSchema,
55
+ TestPlanItemZoneSchema,
56
+ TestPlanRegistrationSchema,
16
57
  TestPlansListResponseSchema,
58
+ TestPlanItemsListResponseSchema,
59
+ TestPlanItemZonesListResponseSchema,
60
+ TestPlanRegistrationsListResponseSchema,
17
61
  };
package/src/next.js ADDED
@@ -0,0 +1,89 @@
1
+ 'use strict';
2
+
3
+ function normalizeAllowedMethods(allowedMethods) {
4
+ const normalized = new Set();
5
+ if (Array.isArray(allowedMethods)) {
6
+ for (const method of allowedMethods) normalized.add(method);
7
+ } else if (allowedMethods && typeof allowedMethods === 'object') {
8
+ for (const [resource, methods] of Object.entries(allowedMethods)) {
9
+ for (const method of methods) normalized.add(`${resource}.${method}`);
10
+ }
11
+ }
12
+ if (normalized.size === 0) {
13
+ throw new Error('allowedMethods must explicitly expose at least one Dalux client method');
14
+ }
15
+ return normalized;
16
+ }
17
+
18
+ function json(body, status = 200) {
19
+ return Response.json(body, { status });
20
+ }
21
+
22
+ function errorStatus(error) {
23
+ if (error?.name === 'AuthenticationError') return 401;
24
+ if (error?.name === 'NotFoundError') return 404;
25
+ if (error?.name === 'RateLimitError') return 429;
26
+ if (error?.name === 'ValidationError') return 422;
27
+ return 502;
28
+ }
29
+
30
+ /**
31
+ * Create a Next.js App Router POST handler for a browser Dalux client.
32
+ *
33
+ * @param {object} options
34
+ * @param {object} options.client A server-side client from createClient().
35
+ * @param {string[]|Record<string, string[]>} options.allowedMethods Explicit RPC allow-list.
36
+ * @param {(request: Request) => boolean|Response|Promise<boolean|Response>} [options.authorize]
37
+ * @returns {(request: Request) => Promise<Response>}
38
+ */
39
+ function createDaluxRouteHandler({ client, allowedMethods, authorize } = {}) {
40
+ if (!client || typeof client !== 'object') {
41
+ throw new Error('A server-side Dalux client is required');
42
+ }
43
+ const allowed = normalizeAllowedMethods(allowedMethods);
44
+
45
+ return async function POST(request) {
46
+ if (authorize) {
47
+ const authorization = await authorize(request);
48
+ if (authorization instanceof Response) return authorization;
49
+ if (!authorization) return json({ ok: false, error: { name: 'AuthenticationError', message: 'Unauthorized' } }, 401);
50
+ }
51
+
52
+ let payload;
53
+ try {
54
+ payload = await request.json();
55
+ } catch {
56
+ return json({ ok: false, error: { name: 'ValidationError', message: 'Request body must be JSON' } }, 400);
57
+ }
58
+
59
+ const { resource, method, args = [] } = payload || {};
60
+ const qualifiedMethod = `${resource}.${method}`;
61
+ if (!allowed.has(qualifiedMethod)) {
62
+ return json({ ok: false, error: { name: 'NotFoundError', message: `Method ${qualifiedMethod} is not exposed` } }, 404);
63
+ }
64
+ if (!Array.isArray(args)) {
65
+ return json({ ok: false, error: { name: 'ValidationError', message: 'args must be an array' } }, 400);
66
+ }
67
+
68
+ const target = client[resource];
69
+ const operation = target?.[method];
70
+ if (typeof operation !== 'function') {
71
+ return json({ ok: false, error: { name: 'NotFoundError', message: `Unknown method ${qualifiedMethod}` } }, 404);
72
+ }
73
+
74
+ try {
75
+ const data = await operation.apply(target, args);
76
+ return json({ ok: true, data: data === undefined ? null : data });
77
+ } catch (error) {
78
+ return json({
79
+ ok: false,
80
+ error: {
81
+ name: error?.name || 'ApiError',
82
+ message: error?.message || String(error),
83
+ },
84
+ }, errorStatus(error));
85
+ }
86
+ };
87
+ }
88
+
89
+ module.exports = { createDaluxRouteHandler };
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Bruno Adam
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.