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 +85 -54
- package/package.json +9 -3
- package/src/api/FileRevisionsApi.js +4 -2
- package/src/api/FilesApi.js +21 -1
- package/src/api/InspectionPlansApi.js +85 -11
- package/src/api/TestPlansApi.js +85 -11
- package/src/apiClient.js +6 -2
- package/src/browser.js +84 -0
- package/src/models/index.js +32 -2
- package/src/models/inspectionPlans/index.js +49 -6
- package/src/models/testPlans/index.js +49 -5
- package/src/next.js +89 -0
- package/LICENSE +0 -21
package/README.md
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
# Dalux Build API
|
|
1
|
+
# Dalux Build API – Node.js Client
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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`).
|
|
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
|
-
##
|
|
414
|
+
## Testing
|
|
369
415
|
|
|
370
416
|
```bash
|
|
371
|
-
#
|
|
372
|
-
npm
|
|
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
|
-
|
|
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
|
-
|
|
424
|
+
## Releasing
|
|
398
425
|
|
|
399
|
-
|
|
400
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
package/src/api/FilesApi.js
CHANGED
|
@@ -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
|
|
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 {
|
|
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
|
-
* @
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
package/src/api/TestPlansApi.js
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { convertToModel } = require('../models/convert');
|
|
4
|
-
const {
|
|
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
|
-
* @
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 };
|
package/src/models/index.js
CHANGED
|
@@ -43,9 +43,27 @@ const { CompaniesListResponseSchema, CompanyResponseSchema } = require('./compan
|
|
|
43
43
|
|
|
44
44
|
const { CompanyCatalogListResponseSchema, CompanyCatalogResponseSchema } = require('./companyCatalog');
|
|
45
45
|
|
|
46
|
-
const {
|
|
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 {
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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.
|