@wandelbots/nova-js 3.9.0-pr.280.2a86180 → 3.9.0-pr.280.5d405fb
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 +7 -10
- package/dist/lib/v2/index.cjs +29 -45
- package/dist/lib/v2/index.cjs.map +1 -1
- package/dist/lib/v2/index.d.cts +37 -49
- package/dist/lib/v2/index.d.cts.map +1 -1
- package/dist/lib/v2/index.d.mts +37 -49
- package/dist/lib/v2/index.d.mts.map +1 -1
- package/dist/lib/v2/index.mjs +29 -45
- package/dist/lib/v2/index.mjs.map +1 -1
- package/package.json +5 -3
- package/src/lib/v2/Nova.ts +9 -18
- package/src/lib/v2/NovaAPIClient.ts +130 -127
- package/src/lib/v2/index.ts +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wandelbots/nova-js",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.9.0-pr.280.
|
|
4
|
+
"version": "3.9.0-pr.280.5d405fb",
|
|
5
5
|
"description": "Official JS client for the Wandelbots API",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"packageManager": "pnpm@11.1.3+sha512.c85357fe17ca12dd23dd7071822666dfd7e3cb76fe214e3370b5ea2fb34f2a231185509b63e717f3cd0acb38dd3f8d82bcd5e8172400ae678b70ea4fbed0896d",
|
|
@@ -31,10 +31,10 @@
|
|
|
31
31
|
"lint": "biome check --error-on-warnings .",
|
|
32
32
|
"format": "biome format . --write",
|
|
33
33
|
"knip": "knip",
|
|
34
|
-
"build": "tsdown
|
|
34
|
+
"build": "tsdown",
|
|
35
35
|
"test": "pnpm run build && vitest run",
|
|
36
36
|
"e2e": "vitest run --config vitest.e2e.config.ts",
|
|
37
|
-
"checks": "pnpm format && concurrently --group --timings --names audit,lint,tsc,knip,test \"pnpm audit\" \"pnpm lint\" \"pnpm tsc\" \"pnpm knip\" \"
|
|
37
|
+
"checks": "pnpm format && pnpm build && concurrently --group --timings --names audit,lint,tsc,knip,test \"pnpm audit\" \"pnpm lint\" \"pnpm tsc\" \"pnpm knip\" \"vitest run\""
|
|
38
38
|
},
|
|
39
39
|
"repository": {
|
|
40
40
|
"type": "git",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"@biomejs/biome": "^2.4.15",
|
|
52
52
|
"@types/lodash-es": "^4.17.12",
|
|
53
53
|
"@types/node": "^25.9.1",
|
|
54
|
+
"@types/ws": "^8.18.1",
|
|
54
55
|
"concurrently": "^9.2.1",
|
|
55
56
|
"conventional-changelog-conventionalcommits": "^9.3.1",
|
|
56
57
|
"knip": "^6.14.2",
|
|
@@ -59,6 +60,7 @@
|
|
|
59
60
|
"semantic-release": "^25.0.3",
|
|
60
61
|
"semantic-release-export-data": "^1.2.0",
|
|
61
62
|
"tsdown": "^0.22.0",
|
|
63
|
+
"tsnapi": "^0.3.3",
|
|
62
64
|
"typescript": "^6.0.3",
|
|
63
65
|
"vite": "^8.0.14",
|
|
64
66
|
"vitest": "^4.1.7",
|
package/src/lib/v2/Nova.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/** biome-ignore-all lint/style/noNonNullAssertion: legacy code */
|
|
2
1
|
import type { Configuration as BaseConfiguration } from "@wandelbots/nova-api/v2"
|
|
3
2
|
import type { AxiosRequestConfig } from "axios"
|
|
4
3
|
import axios, { isAxiosError } from "axios"
|
|
@@ -10,18 +9,13 @@ import { parseNovaInstanceUrl } from "../converters"
|
|
|
10
9
|
import { NovaAPIClient } from "./NovaAPIClient"
|
|
11
10
|
import { MockNovaInstance } from "./mock/MockNovaInstance"
|
|
12
11
|
|
|
13
|
-
export type
|
|
12
|
+
export type NovaConfig = {
|
|
14
13
|
/**
|
|
15
14
|
* Url of the deployed NOVA instance to connect to
|
|
16
15
|
* e.g. https://saeattii.instance.wandelbots.io
|
|
17
16
|
*/
|
|
18
17
|
instanceUrl: string
|
|
19
18
|
|
|
20
|
-
/**
|
|
21
|
-
* Id of the cell to connect to
|
|
22
|
-
*/
|
|
23
|
-
cellId: string
|
|
24
|
-
|
|
25
19
|
/**
|
|
26
20
|
* Access token for Bearer authentication.
|
|
27
21
|
* If running on a NOVA instance, this can be automatically retrieved from
|
|
@@ -36,13 +30,13 @@ export type NovaClientConfig = {
|
|
|
36
30
|
*/
|
|
37
31
|
export class Nova {
|
|
38
32
|
readonly api: NovaAPIClient
|
|
39
|
-
readonly config:
|
|
33
|
+
readonly config: NovaConfig
|
|
40
34
|
readonly mock?: MockNovaInstance
|
|
41
35
|
readonly instanceUrl: URL
|
|
42
36
|
authPromise: Promise<string | null> | null = null
|
|
43
37
|
accessToken: string | null = null
|
|
44
38
|
|
|
45
|
-
constructor(config:
|
|
39
|
+
constructor(config: NovaConfig) {
|
|
46
40
|
this.config = config
|
|
47
41
|
this.accessToken =
|
|
48
42
|
config.accessToken ||
|
|
@@ -116,7 +110,7 @@ export class Nova {
|
|
|
116
110
|
)
|
|
117
111
|
}
|
|
118
112
|
|
|
119
|
-
this.api = new NovaAPIClient(
|
|
113
|
+
this.api = new NovaAPIClient({
|
|
120
114
|
...config,
|
|
121
115
|
basePath: urlJoin(this.instanceUrl.href, "/api/v2"),
|
|
122
116
|
isJsonMime: (mime: string) => {
|
|
@@ -126,7 +120,10 @@ export class Nova {
|
|
|
126
120
|
...(this.mock
|
|
127
121
|
? ({
|
|
128
122
|
adapter: (config) => {
|
|
129
|
-
|
|
123
|
+
if (!this.mock) {
|
|
124
|
+
throw new Error("Mock adapter used without a mock instance")
|
|
125
|
+
}
|
|
126
|
+
return this.mock.handleAPIRequest(config)
|
|
130
127
|
},
|
|
131
128
|
} satisfies AxiosRequestConfig)
|
|
132
129
|
: {}),
|
|
@@ -165,13 +162,7 @@ export class Nova {
|
|
|
165
162
|
}
|
|
166
163
|
|
|
167
164
|
makeWebsocketURL(path: string): string {
|
|
168
|
-
const url = new URL(
|
|
169
|
-
urlJoin(
|
|
170
|
-
this.instanceUrl.href,
|
|
171
|
-
`/api/v2/cells/${this.config.cellId}`,
|
|
172
|
-
path,
|
|
173
|
-
),
|
|
174
|
-
)
|
|
165
|
+
const url = new URL(urlJoin(this.instanceUrl.href, `/api/v2`, path))
|
|
175
166
|
url.protocol = url.protocol.replace("http", "ws")
|
|
176
167
|
url.protocol = url.protocol.replace("https", "wss")
|
|
177
168
|
|
|
@@ -1,43 +1,13 @@
|
|
|
1
|
-
/** biome-ignore-all lint/suspicious/noExplicitAny: legacy code */
|
|
2
|
-
/** biome-ignore-all lint/style/noNonNullAssertion: legacy code */
|
|
3
1
|
import type {
|
|
4
2
|
BaseAPI,
|
|
5
3
|
Configuration as BaseConfiguration,
|
|
6
4
|
} from "@wandelbots/nova-api/v2"
|
|
7
|
-
import
|
|
8
|
-
ApplicationApi,
|
|
9
|
-
BUSInputsOutputsApi,
|
|
10
|
-
CellApi,
|
|
11
|
-
ControllerApi,
|
|
12
|
-
ControllerInputsOutputsApi,
|
|
13
|
-
JoggingApi,
|
|
14
|
-
KinematicsApi,
|
|
15
|
-
LicenseApi,
|
|
16
|
-
MotionGroupApi,
|
|
17
|
-
MotionGroupModelsApi,
|
|
18
|
-
NOVACloudApi,
|
|
19
|
-
ProgramApi,
|
|
20
|
-
RobotConfigurationsApi,
|
|
21
|
-
StoreCollisionComponentsApi,
|
|
22
|
-
StoreCollisionSetupsApi,
|
|
23
|
-
StoreObjectApi,
|
|
24
|
-
SystemApi,
|
|
25
|
-
TrajectoryCachingApi,
|
|
26
|
-
TrajectoryExecutionApi,
|
|
27
|
-
TrajectoryPlanningApi,
|
|
28
|
-
VersionApi,
|
|
29
|
-
VirtualControllerApi,
|
|
30
|
-
VirtualControllerBehaviorApi,
|
|
31
|
-
VirtualControllerInputsOutputsApi,
|
|
32
|
-
} from "@wandelbots/nova-api/v2"
|
|
5
|
+
import * as novaApiV2 from "@wandelbots/nova-api/v2"
|
|
33
6
|
import type { AxiosInstance } from "axios"
|
|
34
7
|
import axios from "axios"
|
|
35
8
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
: never
|
|
39
|
-
|
|
40
|
-
type UnwrapAxiosResponseReturn<T> = T extends (...a: any) => any
|
|
9
|
+
// biome-ignore lint/suspicious/noExplicitAny: metamagic
|
|
10
|
+
type UnwrapAxiosResponseReturn<T> = T extends (...a: any[]) => any
|
|
41
11
|
? (
|
|
42
12
|
...a: Parameters<T>
|
|
43
13
|
) => Promise<Awaited<ReturnType<T>> extends { data: infer D } ? D : never>
|
|
@@ -48,107 +18,140 @@ type WithUnwrappedAxiosResponse<T> = {
|
|
|
48
18
|
}
|
|
49
19
|
|
|
50
20
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
21
|
+
* Filters nova-api/v2 exports to just the API class constructors,
|
|
22
|
+
* excluding helper factories and param creators.
|
|
53
23
|
*/
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
ApiConstructor: new (
|
|
65
|
-
config: BaseConfiguration,
|
|
66
|
-
basePath: string,
|
|
67
|
-
axios: AxiosInstance,
|
|
68
|
-
) => T,
|
|
69
|
-
) {
|
|
70
|
-
const apiClient = new ApiConstructor(
|
|
71
|
-
{
|
|
72
|
-
...this.opts,
|
|
73
|
-
isJsonMime: (mime: string) => {
|
|
74
|
-
return mime === "application/json"
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
this.opts.basePath ?? "",
|
|
78
|
-
this.opts.axiosInstance ?? axios.create(),
|
|
79
|
-
) as {
|
|
80
|
-
[key: string | symbol]: any
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
for (const key of Reflect.ownKeys(Reflect.getPrototypeOf(apiClient)!)) {
|
|
84
|
-
if (key !== "constructor" && typeof apiClient[key] === "function") {
|
|
85
|
-
const originalFunction = apiClient[key]
|
|
86
|
-
apiClient[key] = (...args: any[]) => {
|
|
87
|
-
return originalFunction
|
|
88
|
-
.apply(apiClient, args)
|
|
89
|
-
.then((res: any) => res.data)
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return apiClient as WithUnwrappedAxiosResponse<T>
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
readonly system = this.withUnwrappedResponsesOnly(SystemApi)
|
|
98
|
-
readonly cell = this.withUnwrappedResponsesOnly(CellApi)
|
|
99
|
-
|
|
100
|
-
readonly motionGroup = this.withUnwrappedResponsesOnly(MotionGroupApi)
|
|
101
|
-
readonly motionGroupModels =
|
|
102
|
-
this.withUnwrappedResponsesOnly(MotionGroupModelsApi)
|
|
103
|
-
|
|
104
|
-
readonly controller = this.withUnwrappedResponsesOnly(ControllerApi)
|
|
105
|
-
|
|
106
|
-
readonly controllerIOs = this.withUnwrappedResponsesOnly(
|
|
107
|
-
ControllerInputsOutputsApi,
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
readonly trajectoryPlanning = this.withUnwrappedResponsesOnly(
|
|
111
|
-
TrajectoryPlanningApi,
|
|
112
|
-
)
|
|
113
|
-
readonly trajectoryExecution = this.withUnwrappedResponsesOnly(
|
|
114
|
-
TrajectoryExecutionApi,
|
|
115
|
-
)
|
|
116
|
-
readonly trajectoryCaching =
|
|
117
|
-
this.withUnwrappedResponsesOnly(TrajectoryCachingApi)
|
|
118
|
-
|
|
119
|
-
readonly application = this.withUnwrappedResponsesOnly(ApplicationApi)
|
|
120
|
-
readonly applicationGlobal = this.withUnwrappedResponsesOnly(ApplicationApi)
|
|
24
|
+
type ApiConstructors = {
|
|
25
|
+
[K in keyof typeof novaApiV2 as K extends `${string}Api`
|
|
26
|
+
? K extends
|
|
27
|
+
| `${string}Factory${string}`
|
|
28
|
+
| `${string}Fp${string}`
|
|
29
|
+
| `${string}ParamCreator${string}`
|
|
30
|
+
? never
|
|
31
|
+
: K
|
|
32
|
+
: never]: (typeof novaApiV2)[K]
|
|
33
|
+
}
|
|
121
34
|
|
|
122
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Lowercases leading uppercase characters to produce a camelCase property name.
|
|
37
|
+
* e.g. "BUSInputsOutputs" -> "busInputsOutputs", "NOVACloud" -> "novaCloud",
|
|
38
|
+
* "Application" -> "application"
|
|
39
|
+
*/
|
|
40
|
+
type CamelCase<S extends string> =
|
|
41
|
+
S extends `${Uppercase<infer A>}${Uppercase<infer B>}${infer Rest}`
|
|
42
|
+
? Rest extends `${Lowercase<string>}${string}`
|
|
43
|
+
? `${Lowercase<A>}${CamelCase<`${Uppercase<B>}${Rest}`>}`
|
|
44
|
+
: `${Lowercase<A>}${CamelCase<`${Uppercase<B>}${Rest}`>}`
|
|
45
|
+
: Uncapitalize<S>
|
|
123
46
|
|
|
124
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Maps API class names to property names by stripping "Api" and converting
|
|
49
|
+
* the leading acronym to lowercase camelCase.
|
|
50
|
+
*/
|
|
51
|
+
type ApiProperties = {
|
|
52
|
+
readonly [K in keyof ApiConstructors as K extends `${infer P}Api`
|
|
53
|
+
? CamelCase<P>
|
|
54
|
+
: never]: ApiConstructors[K] extends new (
|
|
55
|
+
// biome-ignore lint/suspicious/noExplicitAny: needed for contravariant parameter matching
|
|
56
|
+
...args: any[]
|
|
57
|
+
) => infer I
|
|
58
|
+
? WithUnwrappedAxiosResponse<I>
|
|
59
|
+
: never
|
|
60
|
+
}
|
|
125
61
|
|
|
126
|
-
|
|
127
|
-
|
|
62
|
+
type NovaAPIClientOpts = BaseConfiguration & {
|
|
63
|
+
axiosInstance?: AxiosInstance
|
|
64
|
+
mock?: boolean
|
|
65
|
+
}
|
|
128
66
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
67
|
+
function isApiClass(
|
|
68
|
+
name: string,
|
|
69
|
+
value: unknown,
|
|
70
|
+
): value is new (
|
|
71
|
+
config: BaseConfiguration,
|
|
72
|
+
basePath: string,
|
|
73
|
+
axios: AxiosInstance,
|
|
74
|
+
) => BaseAPI {
|
|
75
|
+
return (
|
|
76
|
+
name.endsWith("Api") &&
|
|
77
|
+
typeof value === "function" &&
|
|
78
|
+
!name.includes("Factory") &&
|
|
79
|
+
!name.includes("Fp") &&
|
|
80
|
+
!name.includes("ParamCreator")
|
|
136
81
|
)
|
|
82
|
+
}
|
|
137
83
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
84
|
+
function wrapApi<T extends BaseAPI>(
|
|
85
|
+
ApiConstructor: new (
|
|
86
|
+
config: BaseConfiguration,
|
|
87
|
+
basePath: string,
|
|
88
|
+
axios: AxiosInstance,
|
|
89
|
+
) => T,
|
|
90
|
+
opts: NovaAPIClientOpts,
|
|
91
|
+
): WithUnwrappedAxiosResponse<T> {
|
|
92
|
+
const apiClient = new ApiConstructor(
|
|
93
|
+
{
|
|
94
|
+
...opts,
|
|
95
|
+
isJsonMime: (mime: string) => mime === "application/json",
|
|
96
|
+
},
|
|
97
|
+
opts.basePath ?? "",
|
|
98
|
+
opts.axiosInstance ?? axios.create(),
|
|
99
|
+
) as Record<string | symbol, unknown>
|
|
100
|
+
|
|
101
|
+
for (const key of Reflect.ownKeys(
|
|
102
|
+
Reflect.getPrototypeOf(apiClient) as object,
|
|
103
|
+
)) {
|
|
104
|
+
if (key !== "constructor" && typeof apiClient[key] === "function") {
|
|
105
|
+
const originalFunction = apiClient[key] as (
|
|
106
|
+
...args: unknown[]
|
|
107
|
+
) => Promise<{ data: unknown }>
|
|
108
|
+
apiClient[key] = (...args: unknown[]) =>
|
|
109
|
+
originalFunction.apply(apiClient, args).then((res) => res.data)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
145
112
|
|
|
146
|
-
|
|
113
|
+
return apiClient as WithUnwrappedAxiosResponse<T>
|
|
114
|
+
}
|
|
147
115
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
116
|
+
/**
|
|
117
|
+
* API client providing type-safe access to all the endpoints of a NOVA
|
|
118
|
+
* instance. API sections are auto-discovered from @wandelbots/nova-api/v2
|
|
119
|
+
* at runtime, so new sections added upstream are exposed automatically.
|
|
120
|
+
*/
|
|
121
|
+
export const NovaAPIClient = class NovaAPIClient {
|
|
122
|
+
readonly opts: NovaAPIClientOpts
|
|
123
|
+
|
|
124
|
+
constructor(opts: NovaAPIClientOpts) {
|
|
125
|
+
this.opts = opts
|
|
126
|
+
|
|
127
|
+
for (const [name, value] of Object.entries(novaApiV2)) {
|
|
128
|
+
if (isApiClass(name, value)) {
|
|
129
|
+
const stripped = name.slice(0, -3)
|
|
130
|
+
// Lowercase leading acronym run: "BUSInputsOutputs" -> "busInputsOutputs"
|
|
131
|
+
let i = 0
|
|
132
|
+
while (
|
|
133
|
+
i < stripped.length &&
|
|
134
|
+
stripped[i] === stripped[i].toUpperCase() &&
|
|
135
|
+
stripped[i] !== stripped[i].toLowerCase()
|
|
136
|
+
) {
|
|
137
|
+
i++
|
|
138
|
+
}
|
|
139
|
+
// If multiple uppercase chars, keep the last one uppercase (it starts the next word)
|
|
140
|
+
const boundary = i > 1 ? i - 1 : i
|
|
141
|
+
const propName =
|
|
142
|
+
stripped.slice(0, boundary).toLowerCase() + stripped.slice(boundary)
|
|
143
|
+
// biome-ignore lint/suspicious/noExplicitAny: dynamically assigning discovered API properties
|
|
144
|
+
;(this as any)[propName] = wrapApi(value, opts)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
} as {
|
|
149
|
+
new (
|
|
150
|
+
opts: NovaAPIClientOpts,
|
|
151
|
+
): ApiProperties & {
|
|
152
|
+
readonly cellId: string
|
|
153
|
+
readonly opts: NovaAPIClientOpts
|
|
154
|
+
}
|
|
154
155
|
}
|
|
156
|
+
|
|
157
|
+
export type NovaAPIClient = InstanceType<typeof NovaAPIClient>
|
package/src/lib/v2/index.ts
CHANGED
|
@@ -3,5 +3,5 @@ export * from "../deprecated/v2/NovaCellAPIClient"
|
|
|
3
3
|
export * from "../deprecated/v2/NovaClient"
|
|
4
4
|
export * from "../deprecated/v2/wandelscriptUtils"
|
|
5
5
|
export { Nova } from "./Nova"
|
|
6
|
-
export type {
|
|
6
|
+
export type { NovaConfig } from "./Nova"
|
|
7
7
|
export { NovaAPIClient } from "./NovaAPIClient"
|