simcapture-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 georgegiosue
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.
package/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # simcapture-sdk
2
+
3
+ SimCapture API client shared across the UPCH Simulation Center microservices
4
+ and frontends. Replaces the per-service hand-rolled `axios` + login code that was
5
+ duplicated across SIMCAPTURE-MS, REPORT-MS, ENGINEERING-MS and the frontend.
6
+
7
+ Published to GitHub Packages under the `@upch` scope. Add an `.npmrc`:
8
+
9
+ ```
10
+ @upch:registry=https://npm.pkg.github.com
11
+ //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
12
+ ```
13
+
14
+ ```bash
15
+ npm install simcapture-sdk
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ```ts
21
+ import { SimCaptureClient, SimCaptureError } from "simcapture-sdk";
22
+
23
+ const sc = new SimCaptureClient({
24
+ apiUrl: process.env.SIMCAPTURE_API!, // https://api.simcapture.com
25
+ inventoryUrl: process.env.SIMCAPTURE_INVENTORY_API!,
26
+ credentials: {
27
+ username: process.env.SIMCAPTURE_USER!,
28
+ password: process.env.SIMCAPTURE_PASSWORD!,
29
+ // clientSubdomain defaults to "simulacion-upch"
30
+ },
31
+ });
32
+
33
+ const orgs = await sc.organizations.findAll();
34
+ const reservations = await sc.reservations.findAll({
35
+ endAfterTs: "2025-07-01T05:00:00.000Z",
36
+ startBeforeTs: "2026-12-31T05:00:00.000Z",
37
+ });
38
+
39
+ try {
40
+ await sc.reservations.findOne("bad-id");
41
+ } catch (e) {
42
+ if (e instanceof SimCaptureError) {
43
+ console.error(e.status, e.code, e.body); // real upstream status, not a blanket 400
44
+ }
45
+ }
46
+ ```
47
+
48
+ ### Resources
49
+
50
+ | Resource | Methods |
51
+ |---|---|
52
+ | `organizations` | `findAll` |
53
+ | `locations` | `findAll` |
54
+ | `reservations` | `findAll`, `findOne`, `update`, `updateDetails`, `delete` |
55
+ | `courses` | `find`, `getItems` |
56
+ | `scenarios` | `findOne`, `updateSetup`, `updateDetails`, `getAttachment` |
57
+ | `simulators` | `findAll`, `findOne`, `update`, `getConfig` |
58
+ | `notifications` | `send` |
59
+ | `inventory` | `findAll`, `findOne`, `edit` *(inventory server)* |
60
+
61
+ ## Auth behaviour
62
+
63
+ - Tokens are cached and reused until a soft TTL (`tokenTtlMs`, default 30 min) elapses.
64
+ - A `401` clears the cache, re-logins **once**, and retries the failed request once.
65
+ - Concurrent callers share a single in-flight `/auth` request.
66
+
67
+ ## Architecture (DDD)
68
+
69
+ ```
70
+ domain/ entities, value-objects, ports (HttpClient, TokenStore), models, errors
71
+ application/ use-cases: Authenticator + one resource class per domain
72
+ infrastructure/ axios transport adapter + in-memory token store
73
+ config/ SimCaptureConfig + resolution/validation
74
+ client.ts composition root wiring infra → application
75
+ ```
76
+
77
+ ```mermaid
78
+ flowchart TB
79
+ Consumer["Consumer (MS / frontend)"] --> Client["SimCaptureClient<br/>(composition root)"]
80
+
81
+ subgraph application["application"]
82
+ Resources["Resources<br/>reservations · scenarios · …"]
83
+ Auth["Authenticator<br/>token cache · 401 retry"]
84
+ end
85
+
86
+ subgraph domain["domain (no deps)"]
87
+ Ports["Ports<br/>HttpClient · TokenStore"]
88
+ Models["Models · VOs · SimCaptureError"]
89
+ end
90
+
91
+ subgraph infrastructure["infrastructure"]
92
+ Axios["AxiosHttpClient"]
93
+ Store["InMemoryTokenStore"]
94
+ end
95
+
96
+ Client --> Resources
97
+ Client --> Auth
98
+ Resources --> Auth
99
+ Resources -.depends on.-> Ports
100
+ Auth -.depends on.-> Ports
101
+ Auth --> Models
102
+
103
+ Axios -.implements.-> Ports
104
+ Store -.implements.-> Ports
105
+ Axios --> SimCapture["SimCapture API<br/>api + inventory servers"]
106
+
107
+ Client -. injects .-> Axios
108
+ Client -. injects .-> Store
109
+ ```
110
+
111
+ Dependencies point inward — the domain/application layers depend only on the
112
+ `HttpClient`/`TokenStore` ports, so they are unit-tested with fakes (no network).
113
+ Advanced consumers can inject their own transport or a shared token store via the
114
+ second `SimCaptureClient` constructor argument.
115
+
116
+ ## Development (Bun)
117
+
118
+ ```bash
119
+ bun install
120
+ bun run typecheck # tsc --noEmit (strict)
121
+ bun test # unit tests against the ports (no real network)
122
+ bun run build # tsup → ESM + CJS + .d.ts in dist/ (Node-compatible)
123
+ ```
124
+
125
+ The published artifact is built with `tsup` for Node ≥18 (ESM + CJS + types). Bun is
126
+ the dev/build/test toolchain only — there are no `bun:*` imports in shipped code.
package/dist/index.cjs ADDED
@@ -0,0 +1,549 @@
1
+ 'use strict';
2
+
3
+ var axios = require('axios');
4
+
5
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
6
+
7
+ var axios__default = /*#__PURE__*/_interopDefault(axios);
8
+
9
+ // src/domain/errors/simcapture-error.ts
10
+ var SimCaptureError = class _SimCaptureError extends Error {
11
+ /** HTTP status returned by SimCapture, or 0 when the request never completed. */
12
+ status;
13
+ /** Stable machine-readable code for branching (e.g. `AUTH_FAILED`). */
14
+ code;
15
+ /** Raw upstream response body, when available. */
16
+ body;
17
+ constructor(params) {
18
+ super(params.message, params.cause !== void 0 ? { cause: params.cause } : void 0);
19
+ this.name = "SimCaptureError";
20
+ this.status = params.status;
21
+ this.code = params.code;
22
+ this.body = params.body ?? null;
23
+ Object.setPrototypeOf(this, _SimCaptureError.prototype);
24
+ }
25
+ };
26
+
27
+ // src/domain/auth/auth-credentials.vo.ts
28
+ var AuthCredentials = class _AuthCredentials {
29
+ static DEFAULT_SUBDOMAIN = "simulacion-upch";
30
+ username;
31
+ password;
32
+ clientSubdomain;
33
+ constructor(params) {
34
+ if (!params.username) {
35
+ throw new SimCaptureError({
36
+ message: "SimCapture credentials: `username` is required.",
37
+ status: 0,
38
+ code: "CONFIG_INVALID"
39
+ });
40
+ }
41
+ if (!params.password) {
42
+ throw new SimCaptureError({
43
+ message: "SimCapture credentials: `password` is required.",
44
+ status: 0,
45
+ code: "CONFIG_INVALID"
46
+ });
47
+ }
48
+ this.username = params.username;
49
+ this.password = params.password;
50
+ this.clientSubdomain = params.clientSubdomain ?? _AuthCredentials.DEFAULT_SUBDOMAIN;
51
+ }
52
+ toAuthRequest() {
53
+ return {
54
+ username: this.username,
55
+ password: this.password,
56
+ clientSubdomain: this.clientSubdomain
57
+ };
58
+ }
59
+ };
60
+
61
+ // src/domain/auth/token.vo.ts
62
+ var Token = class _Token {
63
+ value;
64
+ /** Epoch ms after which the token is considered stale, or null when no TTL. */
65
+ expiresAt;
66
+ constructor(value, expiresAt = null) {
67
+ this.value = value;
68
+ this.expiresAt = expiresAt;
69
+ }
70
+ static issue(value, ttlMs, now = Date.now()) {
71
+ const expiresAt = ttlMs > 0 ? now + ttlMs : null;
72
+ return new _Token(value, expiresAt);
73
+ }
74
+ isExpired(now = Date.now()) {
75
+ return this.expiresAt !== null && now >= this.expiresAt;
76
+ }
77
+ };
78
+
79
+ // src/application/auth/authenticator.ts
80
+ var Authenticator = class {
81
+ http;
82
+ store;
83
+ credentials;
84
+ tokenTtlMs;
85
+ now;
86
+ inFlightLogin = null;
87
+ constructor(options) {
88
+ this.http = options.http;
89
+ this.store = options.store;
90
+ this.credentials = options.credentials;
91
+ this.tokenTtlMs = options.tokenTtlMs;
92
+ this.now = options.now ?? Date.now;
93
+ }
94
+ /** Returns a valid token, logging in only when none is cached or it is stale. */
95
+ async getToken() {
96
+ const cached = this.store.get();
97
+ if (cached && !cached.isExpired(this.now())) return cached;
98
+ return this.login();
99
+ }
100
+ /** Forces a fresh login, deduping concurrent calls into one `/auth` request. */
101
+ async login() {
102
+ if (this.inFlightLogin) return this.inFlightLogin;
103
+ this.inFlightLogin = this.requestToken().then((token) => {
104
+ this.store.set(token);
105
+ return token;
106
+ }).finally(() => {
107
+ this.inFlightLogin = null;
108
+ });
109
+ return this.inFlightLogin;
110
+ }
111
+ /**
112
+ * Sends `req` with the cached token attached. On a 401 it refreshes the token
113
+ * once and retries the request a single time; a second 401 propagates.
114
+ */
115
+ async authorizedRequest(req) {
116
+ const token = await this.getToken();
117
+ try {
118
+ return await this.http.request(withToken(req, token.value));
119
+ } catch (error) {
120
+ if (!isUnauthorized(error)) throw error;
121
+ this.store.clear();
122
+ const fresh = await this.login();
123
+ return this.http.request(withToken(req, fresh.value));
124
+ }
125
+ }
126
+ async requestToken() {
127
+ const response = await this.http.request({
128
+ method: "POST",
129
+ path: "/auth",
130
+ body: this.credentials.toAuthRequest(),
131
+ skipAuth: true
132
+ });
133
+ const value = response.data?.token;
134
+ if (!value) {
135
+ throw new SimCaptureError({
136
+ message: "SimCapture /auth did not return a token.",
137
+ status: response.status,
138
+ code: "AUTH_FAILED",
139
+ body: response.data
140
+ });
141
+ }
142
+ return Token.issue(value, this.tokenTtlMs, this.now());
143
+ }
144
+ };
145
+ function withToken(req, token) {
146
+ return { ...req, headers: { ...req.headers, token } };
147
+ }
148
+ function isUnauthorized(error) {
149
+ return error instanceof SimCaptureError && error.status === 401;
150
+ }
151
+
152
+ // src/application/resources/base.resource.ts
153
+ var BaseResource = class {
154
+ constructor(auth) {
155
+ this.auth = auth;
156
+ }
157
+ auth;
158
+ async exec(req) {
159
+ const response = await this.auth.authorizedRequest(req);
160
+ return response.data;
161
+ }
162
+ };
163
+
164
+ // src/application/resources/courses.resource.ts
165
+ var CoursesResource = class extends BaseResource {
166
+ constructor(auth) {
167
+ super(auth);
168
+ }
169
+ /** `POST /courses` — one page of results. */
170
+ find(query) {
171
+ return this.exec({ method: "POST", path: "/courses", body: query });
172
+ }
173
+ /** `GET /courses/{courseId}/items` — scenarios / evaluation templates. */
174
+ getItems(courseId, params = {}) {
175
+ return this.exec({
176
+ method: "GET",
177
+ path: `/courses/${encodeURIComponent(courseId)}/items`,
178
+ query: {
179
+ includeScenarios: params.includeScenarios,
180
+ includeEvaluationTemplates: params.includeEvaluationTemplates,
181
+ hasScenarioReflections: params.hasScenarioReflections,
182
+ hasScenarioEvaluations: params.hasScenarioEvaluations
183
+ }
184
+ });
185
+ }
186
+ };
187
+
188
+ // src/application/resources/inventory.resource.ts
189
+ var InventoryResource = class extends BaseResource {
190
+ constructor(auth) {
191
+ super(auth);
192
+ }
193
+ /** `GET /items` (server B) */
194
+ findAll() {
195
+ return this.exec({ method: "GET", path: "/items", server: "inventory" });
196
+ }
197
+ /** `GET /item/{itemId}` (server B) */
198
+ findOne(itemId) {
199
+ return this.exec({
200
+ method: "GET",
201
+ path: `/item/${encodeURIComponent(itemId)}`,
202
+ server: "inventory"
203
+ });
204
+ }
205
+ /** `POST /item/{itemId}/edit` (server B) — stock adjustment. */
206
+ edit(itemId, body) {
207
+ return this.exec({
208
+ method: "POST",
209
+ path: `/item/${encodeURIComponent(itemId)}/edit`,
210
+ server: "inventory",
211
+ body
212
+ });
213
+ }
214
+ };
215
+
216
+ // src/application/resources/locations.resource.ts
217
+ var LocationsResource = class extends BaseResource {
218
+ constructor(auth) {
219
+ super(auth);
220
+ }
221
+ /** `GET /locations?order=...` */
222
+ findAll(params = {}) {
223
+ return this.exec({
224
+ method: "GET",
225
+ path: "/locations",
226
+ query: { order: params.order }
227
+ });
228
+ }
229
+ };
230
+
231
+ // src/application/resources/notifications.resource.ts
232
+ var NotificationsResource = class extends BaseResource {
233
+ constructor(auth) {
234
+ super(auth);
235
+ }
236
+ /** `POST /notifications` — send an email / notification. */
237
+ send(body) {
238
+ return this.exec({ method: "POST", path: "/notifications", body });
239
+ }
240
+ };
241
+
242
+ // src/application/resources/organizations.resource.ts
243
+ var OrganizationsResource = class extends BaseResource {
244
+ constructor(auth) {
245
+ super(auth);
246
+ }
247
+ /** `GET /organizations` */
248
+ findAll() {
249
+ return this.exec({ method: "GET", path: "/organizations" });
250
+ }
251
+ };
252
+
253
+ // src/application/resources/reservations.resource.ts
254
+ var ReservationsResource = class extends BaseResource {
255
+ constructor(auth) {
256
+ super(auth);
257
+ }
258
+ /** `GET /reservations?endAfterTs=&startBeforeTs=&limitFetchedReservationDetails=` */
259
+ findAll(params) {
260
+ return this.exec({
261
+ method: "GET",
262
+ path: "/reservations",
263
+ query: {
264
+ endAfterTs: params.endAfterTs,
265
+ startBeforeTs: params.startBeforeTs,
266
+ limitFetchedReservationDetails: params.limitFetchedReservationDetails ?? false
267
+ }
268
+ });
269
+ }
270
+ /** `GET /reservation/{id}` */
271
+ findOne(reservationId) {
272
+ return this.exec({
273
+ method: "GET",
274
+ path: `/reservation/${encodeURIComponent(reservationId)}`
275
+ });
276
+ }
277
+ /** `PUT /reservation/{id}` — overview or setup update. */
278
+ update(reservationId, body) {
279
+ return this.exec({
280
+ method: "PUT",
281
+ path: `/reservation/${encodeURIComponent(reservationId)}`,
282
+ body
283
+ });
284
+ }
285
+ /** `PUT /reservation/{id}/details` (ENGINEERING-MS flow). */
286
+ updateDetails(reservationId, body) {
287
+ return this.exec({
288
+ method: "PUT",
289
+ path: `/reservation/${encodeURIComponent(reservationId)}/details`,
290
+ body
291
+ });
292
+ }
293
+ /** `DELETE /reservation/{id}` */
294
+ delete(reservationId) {
295
+ return this.exec({
296
+ method: "DELETE",
297
+ path: `/reservation/${encodeURIComponent(reservationId)}`
298
+ });
299
+ }
300
+ };
301
+
302
+ // src/application/resources/scenarios.resource.ts
303
+ var ScenariosResource = class extends BaseResource {
304
+ constructor(auth) {
305
+ super(auth);
306
+ }
307
+ /** `GET /scenarios/{id}` */
308
+ findOne(scenarioId) {
309
+ return this.exec({
310
+ method: "GET",
311
+ path: `/scenarios/${encodeURIComponent(scenarioId)}`
312
+ });
313
+ }
314
+ /** `PUT /scenarios/{id}` — setup update. */
315
+ updateSetup(scenarioId, body) {
316
+ return this.exec({
317
+ method: "PUT",
318
+ path: `/scenarios/${encodeURIComponent(scenarioId)}`,
319
+ body
320
+ });
321
+ }
322
+ /** `PUT /scenarios/{id}/details` — detail update. */
323
+ updateDetails(scenarioId, body) {
324
+ return this.exec({
325
+ method: "PUT",
326
+ path: `/scenarios/${encodeURIComponent(scenarioId)}/details`,
327
+ body
328
+ });
329
+ }
330
+ /**
331
+ * `GET /scenarios/{id}/attachments/{assetName}` — returns the raw PDF bytes.
332
+ * The token is sent both as a query param and the header (matching the API).
333
+ */
334
+ async getAttachment(scenarioId, assetName, params) {
335
+ const token = await this.auth.getToken();
336
+ const response = await this.auth.authorizedRequest({
337
+ method: "GET",
338
+ path: `/scenarios/${encodeURIComponent(scenarioId)}/attachments/${encodeURIComponent(assetName)}`,
339
+ query: { "client-id": params.clientId, token: token.value },
340
+ responseType: "blob"
341
+ });
342
+ return response.data;
343
+ }
344
+ };
345
+
346
+ // src/application/resources/simulators.resource.ts
347
+ var SimulatorsResource = class extends BaseResource {
348
+ constructor(auth) {
349
+ super(auth);
350
+ }
351
+ /** `GET /simulators` */
352
+ findAll() {
353
+ return this.exec({ method: "GET", path: "/simulators" });
354
+ }
355
+ /** `GET /simulators/{id}` */
356
+ findOne(simulatorId) {
357
+ return this.exec({
358
+ method: "GET",
359
+ path: `/simulators/${encodeURIComponent(simulatorId)}`
360
+ });
361
+ }
362
+ /** `PUT /simulators/{id}` */
363
+ update(simulatorId, body) {
364
+ return this.exec({
365
+ method: "PUT",
366
+ path: `/simulators/${encodeURIComponent(simulatorId)}`,
367
+ body
368
+ });
369
+ }
370
+ /** `GET /simulator-configs/{id}` */
371
+ getConfig(simulatorId) {
372
+ return this.exec({
373
+ method: "GET",
374
+ path: `/simulator-configs/${encodeURIComponent(simulatorId)}`
375
+ });
376
+ }
377
+ };
378
+
379
+ // src/config/sdk-config.ts
380
+ var SIMCAPTURE_DEFAULTS = {
381
+ apiUrl: "https://api.simcapture.com",
382
+ inventoryUrl: "https://inventory-management-production.us-east-1.simcapture.com",
383
+ tokenTtlMs: 30 * 60 * 1e3,
384
+ timeoutMs: 30 * 1e3
385
+ };
386
+ function resolveConfig(config) {
387
+ const apiUrl = stripTrailingSlash(config.apiUrl || SIMCAPTURE_DEFAULTS.apiUrl);
388
+ const inventoryUrl = stripTrailingSlash(config.inventoryUrl || SIMCAPTURE_DEFAULTS.inventoryUrl);
389
+ if (!config.credentials?.username || !config.credentials?.password) {
390
+ throw new SimCaptureError({
391
+ message: "SimCapture config: `credentials.username` and `credentials.password` are required.",
392
+ status: 0,
393
+ code: "CONFIG_INVALID"
394
+ });
395
+ }
396
+ return {
397
+ apiUrl,
398
+ inventoryUrl,
399
+ credentials: config.credentials,
400
+ tokenTtlMs: config.tokenTtlMs ?? SIMCAPTURE_DEFAULTS.tokenTtlMs,
401
+ timeoutMs: config.timeoutMs ?? SIMCAPTURE_DEFAULTS.timeoutMs
402
+ };
403
+ }
404
+ function stripTrailingSlash(url) {
405
+ return url.replace(/\/+$/, "");
406
+ }
407
+
408
+ // src/infrastructure/cache/in-memory-token-store.ts
409
+ var InMemoryTokenStore = class {
410
+ token = null;
411
+ get() {
412
+ return this.token;
413
+ }
414
+ set(token) {
415
+ this.token = token;
416
+ }
417
+ clear() {
418
+ this.token = null;
419
+ }
420
+ };
421
+ var AxiosHttpClient = class {
422
+ instances;
423
+ constructor(options) {
424
+ this.instances = {
425
+ api: axios__default.default.create({ baseURL: options.apiUrl, timeout: options.timeoutMs }),
426
+ inventory: axios__default.default.create({ baseURL: options.inventoryUrl, timeout: options.timeoutMs })
427
+ };
428
+ }
429
+ async request(req) {
430
+ const instance = this.instances[req.server ?? "api"];
431
+ const config = {
432
+ method: req.method,
433
+ url: req.path,
434
+ responseType: req.responseType ?? "json"
435
+ };
436
+ const params = pruneUndefined(req.query);
437
+ if (params) config.params = params;
438
+ if (req.body !== void 0) config.data = req.body;
439
+ if (req.headers) config.headers = req.headers;
440
+ try {
441
+ const response = await instance.request(config);
442
+ return { status: response.status, data: response.data };
443
+ } catch (error) {
444
+ throw toSimCaptureError(error);
445
+ }
446
+ }
447
+ };
448
+ function pruneUndefined(query) {
449
+ if (!query) return void 0;
450
+ const out = {};
451
+ for (const [key, value] of Object.entries(query)) {
452
+ if (value !== void 0) out[key] = value;
453
+ }
454
+ return out;
455
+ }
456
+ function toSimCaptureError(error) {
457
+ if (error instanceof SimCaptureError) return error;
458
+ if (axios.isAxiosError(error)) {
459
+ const status = error.response?.status ?? 0;
460
+ if (status === 0) {
461
+ return new SimCaptureError({
462
+ message: `SimCapture request failed: ${error.message}`,
463
+ status: 0,
464
+ code: "NETWORK",
465
+ cause: error
466
+ });
467
+ }
468
+ return new SimCaptureError({
469
+ message: `SimCapture responded ${status}: ${error.message}`,
470
+ status,
471
+ code: status === 401 ? "UNAUTHORIZED" : "UPSTREAM_ERROR",
472
+ body: error.response?.data,
473
+ cause: error
474
+ });
475
+ }
476
+ return new SimCaptureError({
477
+ message: error instanceof Error ? error.message : "Unknown SimCapture error",
478
+ status: 0,
479
+ code: "NETWORK",
480
+ cause: error
481
+ });
482
+ }
483
+
484
+ // src/client.ts
485
+ var SimCaptureClient = class {
486
+ organizations;
487
+ locations;
488
+ reservations;
489
+ courses;
490
+ scenarios;
491
+ simulators;
492
+ notifications;
493
+ inventory;
494
+ authenticator;
495
+ constructor(config, deps = {}) {
496
+ const resolved = resolveConfig(config);
497
+ const http = deps.httpClient ?? new AxiosHttpClient({
498
+ apiUrl: resolved.apiUrl,
499
+ inventoryUrl: resolved.inventoryUrl,
500
+ timeoutMs: resolved.timeoutMs
501
+ });
502
+ const store = deps.tokenStore ?? new InMemoryTokenStore();
503
+ const authenticatorOptions = {
504
+ http,
505
+ store,
506
+ credentials: new AuthCredentials(resolved.credentials),
507
+ tokenTtlMs: resolved.tokenTtlMs,
508
+ ...deps.now ? { now: deps.now } : {}
509
+ };
510
+ this.authenticator = new Authenticator(authenticatorOptions);
511
+ this.organizations = new OrganizationsResource(this.authenticator);
512
+ this.locations = new LocationsResource(this.authenticator);
513
+ this.reservations = new ReservationsResource(this.authenticator);
514
+ this.courses = new CoursesResource(this.authenticator);
515
+ this.scenarios = new ScenariosResource(this.authenticator);
516
+ this.simulators = new SimulatorsResource(this.authenticator);
517
+ this.notifications = new NotificationsResource(this.authenticator);
518
+ this.inventory = new InventoryResource(this.authenticator);
519
+ }
520
+ /** Force a fresh login. Rarely needed — auth is handled lazily per request. */
521
+ login() {
522
+ return this.authenticator.login().then(() => void 0);
523
+ }
524
+ /** Returns a valid session token, logging in only if none is cached. */
525
+ async getToken() {
526
+ const token = await this.authenticator.getToken();
527
+ return { token: token.value };
528
+ }
529
+ };
530
+
531
+ exports.AuthCredentials = AuthCredentials;
532
+ exports.Authenticator = Authenticator;
533
+ exports.AxiosHttpClient = AxiosHttpClient;
534
+ exports.CoursesResource = CoursesResource;
535
+ exports.InMemoryTokenStore = InMemoryTokenStore;
536
+ exports.InventoryResource = InventoryResource;
537
+ exports.LocationsResource = LocationsResource;
538
+ exports.NotificationsResource = NotificationsResource;
539
+ exports.OrganizationsResource = OrganizationsResource;
540
+ exports.ReservationsResource = ReservationsResource;
541
+ exports.SIMCAPTURE_DEFAULTS = SIMCAPTURE_DEFAULTS;
542
+ exports.ScenariosResource = ScenariosResource;
543
+ exports.SimCaptureClient = SimCaptureClient;
544
+ exports.SimCaptureError = SimCaptureError;
545
+ exports.SimulatorsResource = SimulatorsResource;
546
+ exports.Token = Token;
547
+ exports.resolveConfig = resolveConfig;
548
+ //# sourceMappingURL=index.cjs.map
549
+ //# sourceMappingURL=index.cjs.map