@wandelbots/nova-js 3.9.0-pr.280.641b7e2 → 3.9.0-pr.280.74e07a8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/index.cjs +1 -1
  2. package/dist/index.d.cts +1 -1
  3. package/dist/index.d.mts +1 -1
  4. package/dist/index.mjs +1 -1
  5. package/dist/lib/deprecated/v1/index.cjs +1 -1
  6. package/dist/lib/deprecated/v1/index.d.cts +1 -1
  7. package/dist/lib/deprecated/v1/index.d.mts +1 -1
  8. package/dist/lib/deprecated/v1/index.mjs +1 -1
  9. package/dist/lib/v2/index.cjs +24 -34
  10. package/dist/lib/v2/index.cjs.map +1 -1
  11. package/dist/lib/v2/index.d.cts +26 -30
  12. package/dist/lib/v2/index.d.cts.map +1 -1
  13. package/dist/lib/v2/index.d.mts +26 -30
  14. package/dist/lib/v2/index.d.mts.map +1 -1
  15. package/dist/lib/v2/index.mjs +23 -34
  16. package/dist/lib/v2/index.mjs.map +1 -1
  17. package/dist/{wandelscriptUtils-pySXnBlX.d.mts → wandelscriptUtils-CPA7fIA5.d.mts} +2 -1
  18. package/dist/{wandelscriptUtils-pySXnBlX.d.mts.map → wandelscriptUtils-CPA7fIA5.d.mts.map} +1 -1
  19. package/dist/{wandelscriptUtils-1A9uFPV_.d.cts → wandelscriptUtils-DxH6xiW6.d.cts} +2 -1
  20. package/dist/{wandelscriptUtils-1A9uFPV_.d.cts.map → wandelscriptUtils-DxH6xiW6.d.cts.map} +1 -1
  21. package/dist/{wandelscriptUtils-C7hQePlo.mjs → wandelscriptUtils-W_rEVq6i.mjs} +5 -4
  22. package/dist/{wandelscriptUtils-C7hQePlo.mjs.map → wandelscriptUtils-W_rEVq6i.mjs.map} +1 -1
  23. package/dist/{wandelscriptUtils-P0WYSZww.cjs → wandelscriptUtils-wnSvJKp0.cjs} +6 -4
  24. package/dist/{wandelscriptUtils-P0WYSZww.cjs.map → wandelscriptUtils-wnSvJKp0.cjs.map} +1 -1
  25. package/package.json +3 -2
  26. package/src/lib/deprecated/v1/NovaClient.ts +9 -7
  27. package/src/lib/deprecated/v2/NovaClient.ts +8 -7
  28. package/src/lib/v2/Nova.ts +4 -3
  29. package/src/lib/v2/NovaAPIClient.ts +93 -81
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.641b7e2",
4
+ "version": "3.9.0-pr.280.74e07a8",
5
5
  "description": "Official JS client for the Wandelbots API",
6
6
  "sideEffects": false,
7
7
  "packageManager": "pnpm@11.1.3+sha512.c85357fe17ca12dd23dd7071822666dfd7e3cb76fe214e3370b5ea2fb34f2a231185509b63e717f3cd0acb38dd3f8d82bcd5e8172400ae678b70ea4fbed0896d",
@@ -74,6 +74,7 @@
74
74
  "mobx": "^6.15.0",
75
75
  "path-to-regexp": "^8.4.0",
76
76
  "reconnecting-websocket": "^4.4.0",
77
- "three": "^0.183.2"
77
+ "three": "^0.183.2",
78
+ "url-join": "^5.0.0"
78
79
  }
79
80
  }
@@ -6,11 +6,11 @@
6
6
  import type { Configuration as BaseConfiguration } from "@wandelbots/nova-api/v1"
7
7
  import type { AxiosRequestConfig } from "axios"
8
8
  import axios, { isAxiosError } from "axios"
9
+ import urlJoin from "url-join"
9
10
  import { loginWithAuth0 } from "../../../LoginWithAuth0.js"
10
11
  import { AutoReconnectingWebsocket } from "../../AutoReconnectingWebsocket.js"
11
12
  import { availableStorage } from "../../availableStorage.js"
12
13
  import { parseNovaInstanceUrl } from "../../converters.js"
13
-
14
14
  import { ConnectedMotionGroup } from "./ConnectedMotionGroup.js"
15
15
  import { JoggerConnection } from "./JoggerConnection.js"
16
16
  import { MotionStreamConnection } from "./MotionStreamConnection.js"
@@ -45,6 +45,7 @@ export type NovaClientConfig = {
45
45
 
46
46
  /**
47
47
  * Access token for Bearer authentication.
48
+ * @deprecated use accessToken instead
48
49
  */
49
50
  accessToken?: string
50
51
  } & Omit<BaseConfiguration, "isJsonMime" | "basePath">
@@ -81,7 +82,7 @@ export class NovaClient {
81
82
 
82
83
  // Set up Axios instance with interceptor for token fetching
83
84
  const axiosInstance = axios.create({
84
- baseURL: new URL("/api/v1", this.config.instanceUrl).href,
85
+ baseURL: urlJoin(this.config.instanceUrl, "/api/v1"),
85
86
  // TODO - backend needs to set proper CORS headers for this
86
87
  headers:
87
88
  typeof window !== "undefined" &&
@@ -143,7 +144,7 @@ export class NovaClient {
143
144
 
144
145
  this.api = new NovaCellAPIClient(cellId, {
145
146
  ...config,
146
- basePath: new URL("/api/v1", this.instanceUrl).href,
147
+ basePath: urlJoin(this.instanceUrl.href, "/api/v1"),
147
148
  isJsonMime: (mime: string) => {
148
149
  return mime === "application/json"
149
150
  },
@@ -192,10 +193,11 @@ export class NovaClient {
192
193
 
193
194
  makeWebsocketURL(path: string): string {
194
195
  const url = new URL(
195
- new URL(
196
- `/api/v1/cells/${this.config.cellId}/${path.replace(/^\/+/, "")}`,
197
- this.instanceUrl,
198
- ).href,
196
+ urlJoin(
197
+ this.instanceUrl.href,
198
+ `/api/v1/cells/${this.config.cellId}`,
199
+ path,
200
+ ),
199
201
  )
200
202
  url.protocol = url.protocol.replace("http", "ws")
201
203
  url.protocol = url.protocol.replace("https", "wss")
@@ -2,11 +2,11 @@
2
2
  import type { Configuration as BaseConfiguration } from "@wandelbots/nova-api/v2"
3
3
  import type { AxiosRequestConfig } from "axios"
4
4
  import axios, { isAxiosError } from "axios"
5
+ import urlJoin from "url-join"
5
6
  import { loginWithAuth0 } from "../../../LoginWithAuth0"
6
7
  import { AutoReconnectingWebsocket } from "../../AutoReconnectingWebsocket"
7
8
  import { availableStorage } from "../../availableStorage"
8
9
  import { parseNovaInstanceUrl } from "../../converters"
9
-
10
10
  import { MockNovaInstance } from "../../v2/mock/MockNovaInstance"
11
11
  import { NovaCellAPIClient } from "./NovaCellAPIClient"
12
12
 
@@ -74,7 +74,7 @@ export class NovaClient {
74
74
 
75
75
  // Set up Axios instance with interceptor for token fetching
76
76
  const axiosInstance = axios.create({
77
- baseURL: new URL("/api/v2", this.instanceUrl).href,
77
+ baseURL: urlJoin(this.instanceUrl.href, "/api/v2"),
78
78
  // TODO - backend needs to set proper CORS headers for this
79
79
  headers:
80
80
  typeof window !== "undefined" &&
@@ -136,7 +136,7 @@ export class NovaClient {
136
136
 
137
137
  this.api = new NovaCellAPIClient(cellId, {
138
138
  ...config,
139
- basePath: new URL("/api/v2", this.instanceUrl).href,
139
+ basePath: urlJoin(this.instanceUrl.href, "/api/v2"),
140
140
  isJsonMime: (mime: string) => {
141
141
  return mime === "application/json"
142
142
  },
@@ -184,10 +184,11 @@ export class NovaClient {
184
184
 
185
185
  makeWebsocketURL(path: string): string {
186
186
  const url = new URL(
187
- new URL(
188
- `/api/v2/cells/${this.config.cellId}/${path.replace(/^\/+/, "")}`,
189
- this.instanceUrl,
190
- ).href,
187
+ urlJoin(
188
+ this.instanceUrl.href,
189
+ `/api/v2/cells/${this.config.cellId}`,
190
+ path,
191
+ ),
191
192
  )
192
193
  url.protocol = url.protocol.replace("http", "ws")
193
194
  url.protocol = url.protocol.replace("https", "wss")
@@ -1,6 +1,7 @@
1
1
  import type { Configuration as BaseConfiguration } from "@wandelbots/nova-api/v2"
2
2
  import type { AxiosRequestConfig } from "axios"
3
3
  import axios, { isAxiosError } from "axios"
4
+ import urlJoin from "url-join"
4
5
  import { loginWithAuth0 } from "../../LoginWithAuth0"
5
6
  import { AutoReconnectingWebsocket } from "../AutoReconnectingWebsocket"
6
7
  import { availableStorage } from "../availableStorage"
@@ -49,7 +50,7 @@ export class Nova {
49
50
 
50
51
  // Set up Axios instance with interceptor for token fetching
51
52
  const axiosInstance = axios.create({
52
- baseURL: new URL("/api/v2", this.instanceUrl).href,
53
+ baseURL: urlJoin(this.instanceUrl.href, "/api/v2"),
53
54
  // TODO - backend needs to set proper CORS headers for this
54
55
  headers:
55
56
  typeof window !== "undefined" &&
@@ -111,7 +112,7 @@ export class Nova {
111
112
 
112
113
  this.api = new NovaAPIClient({
113
114
  ...config,
114
- basePath: new URL("/api/v2", this.instanceUrl).href,
115
+ basePath: urlJoin(this.instanceUrl.href, "/api/v2"),
115
116
  isJsonMime: (mime: string) => {
116
117
  return mime === "application/json"
117
118
  },
@@ -161,7 +162,7 @@ export class Nova {
161
162
  }
162
163
 
163
164
  makeWebsocketURL(path: string): string {
164
- const url = new URL(`/api/v2/${path.replace(/^\/+/, "")}`, this.instanceUrl)
165
+ const url = new URL(urlJoin(this.instanceUrl.href, `/api/v2`, path))
165
166
  url.protocol = url.protocol.replace("http", "ws")
166
167
  url.protocol = url.protocol.replace("https", "wss")
167
168
 
@@ -2,33 +2,7 @@ import type {
2
2
  BaseAPI,
3
3
  Configuration as BaseConfiguration,
4
4
  } from "@wandelbots/nova-api/v2"
5
- import {
6
- ApplicationApi,
7
- BUSInputsOutputsApi,
8
- CellApi,
9
- ControllerApi,
10
- ControllerInputsOutputsApi,
11
- JoggingApi,
12
- KinematicsApi,
13
- LicenseApi,
14
- MotionGroupApi,
15
- MotionGroupModelsApi,
16
- NOVACloudApi,
17
- ProgramApi,
18
- RobotConfigurationsApi,
19
- SessionApi,
20
- StoreCollisionComponentsApi,
21
- StoreCollisionSetupsApi,
22
- StoreObjectApi,
23
- SystemApi,
24
- TrajectoryCachingApi,
25
- TrajectoryExecutionApi,
26
- TrajectoryPlanningApi,
27
- VersionApi,
28
- VirtualControllerApi,
29
- VirtualControllerBehaviorApi,
30
- VirtualControllerInputsOutputsApi,
31
- } from "@wandelbots/nova-api/v2"
5
+ import * as novaApiV2 from "@wandelbots/nova-api/v2"
32
6
  import type { AxiosInstance } from "axios"
33
7
  import axios from "axios"
34
8
 
@@ -43,12 +17,71 @@ type WithUnwrappedAxiosResponse<T> = {
43
17
  [P in keyof T]: UnwrapAxiosResponseReturn<T[P]>
44
18
  }
45
19
 
20
+ /**
21
+ * Filters nova-api/v2 exports to just the API class constructors,
22
+ * excluding helper factories and param creators.
23
+ */
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
+ }
34
+
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>
46
+
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
+ }
61
+
46
62
  type NovaAPIClientOpts = BaseConfiguration & {
47
63
  axiosInstance?: AxiosInstance
48
64
  mock?: boolean
49
65
  }
50
66
 
51
- function unwrap<T extends BaseAPI>(
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")
81
+ )
82
+ }
83
+
84
+ function wrapApi<T extends BaseAPI>(
52
85
  ApiConstructor: new (
53
86
  config: BaseConfiguration,
54
87
  basePath: string,
@@ -82,64 +115,43 @@ function unwrap<T extends BaseAPI>(
82
115
 
83
116
  /**
84
117
  * API client providing type-safe access to all the endpoints of a NOVA
85
- * instance.
118
+ * instance. API sections are auto-discovered from @wandelbots/nova-api/v2
119
+ * at runtime, so new sections added upstream are exposed automatically.
86
120
  */
87
- export class NovaAPIClient {
121
+ export const NovaAPIClient = class NovaAPIClient {
88
122
  readonly opts: NovaAPIClientOpts
89
123
 
90
- readonly application: WithUnwrappedAxiosResponse<ApplicationApi>
91
- readonly busIOs: WithUnwrappedAxiosResponse<BUSInputsOutputsApi>
92
- readonly cell: WithUnwrappedAxiosResponse<CellApi>
93
- readonly controller: WithUnwrappedAxiosResponse<ControllerApi>
94
- readonly controllerIOs: WithUnwrappedAxiosResponse<ControllerInputsOutputsApi>
95
- readonly jogging: WithUnwrappedAxiosResponse<JoggingApi>
96
- readonly kinematics: WithUnwrappedAxiosResponse<KinematicsApi>
97
- readonly license: WithUnwrappedAxiosResponse<LicenseApi>
98
- readonly motionGroup: WithUnwrappedAxiosResponse<MotionGroupApi>
99
- readonly motionGroupModels: WithUnwrappedAxiosResponse<MotionGroupModelsApi>
100
- readonly novaCloud: WithUnwrappedAxiosResponse<NOVACloudApi>
101
- readonly program: WithUnwrappedAxiosResponse<ProgramApi>
102
- readonly robotConfigurations: WithUnwrappedAxiosResponse<RobotConfigurationsApi>
103
- readonly session: WithUnwrappedAxiosResponse<SessionApi>
104
- readonly storeCollisionComponents: WithUnwrappedAxiosResponse<StoreCollisionComponentsApi>
105
- readonly storeCollisionSetups: WithUnwrappedAxiosResponse<StoreCollisionSetupsApi>
106
- readonly storeObject: WithUnwrappedAxiosResponse<StoreObjectApi>
107
- readonly system: WithUnwrappedAxiosResponse<SystemApi>
108
- readonly trajectoryCaching: WithUnwrappedAxiosResponse<TrajectoryCachingApi>
109
- readonly trajectoryExecution: WithUnwrappedAxiosResponse<TrajectoryExecutionApi>
110
- readonly trajectoryPlanning: WithUnwrappedAxiosResponse<TrajectoryPlanningApi>
111
- readonly version: WithUnwrappedAxiosResponse<VersionApi>
112
- readonly virtualController: WithUnwrappedAxiosResponse<VirtualControllerApi>
113
- readonly virtualControllerBehavior: WithUnwrappedAxiosResponse<VirtualControllerBehaviorApi>
114
- readonly virtualControllerIOs: WithUnwrappedAxiosResponse<VirtualControllerInputsOutputsApi>
115
-
116
124
  constructor(opts: NovaAPIClientOpts) {
117
125
  this.opts = opts
118
126
 
119
- this.application = unwrap(ApplicationApi, opts)
120
- this.busIOs = unwrap(BUSInputsOutputsApi, opts)
121
- this.cell = unwrap(CellApi, opts)
122
- this.controller = unwrap(ControllerApi, opts)
123
- this.controllerIOs = unwrap(ControllerInputsOutputsApi, opts)
124
- this.jogging = unwrap(JoggingApi, opts)
125
- this.kinematics = unwrap(KinematicsApi, opts)
126
- this.license = unwrap(LicenseApi, opts)
127
- this.motionGroup = unwrap(MotionGroupApi, opts)
128
- this.motionGroupModels = unwrap(MotionGroupModelsApi, opts)
129
- this.novaCloud = unwrap(NOVACloudApi, opts)
130
- this.program = unwrap(ProgramApi, opts)
131
- this.robotConfigurations = unwrap(RobotConfigurationsApi, opts)
132
- this.session = unwrap(SessionApi, opts)
133
- this.storeCollisionComponents = unwrap(StoreCollisionComponentsApi, opts)
134
- this.storeCollisionSetups = unwrap(StoreCollisionSetupsApi, opts)
135
- this.storeObject = unwrap(StoreObjectApi, opts)
136
- this.system = unwrap(SystemApi, opts)
137
- this.trajectoryCaching = unwrap(TrajectoryCachingApi, opts)
138
- this.trajectoryExecution = unwrap(TrajectoryExecutionApi, opts)
139
- this.trajectoryPlanning = unwrap(TrajectoryPlanningApi, opts)
140
- this.version = unwrap(VersionApi, opts)
141
- this.virtualController = unwrap(VirtualControllerApi, opts)
142
- this.virtualControllerBehavior = unwrap(VirtualControllerBehaviorApi, opts)
143
- this.virtualControllerIOs = unwrap(VirtualControllerInputsOutputsApi, opts)
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
144
154
  }
145
155
  }
156
+
157
+ export type NovaAPIClient = InstanceType<typeof NovaAPIClient>