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 +21 -0
- package/README.md +126 -0
- package/dist/index.cjs +549 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +694 -0
- package/dist/index.d.ts +694 -0
- package/dist/index.js +527 -0
- package/dist/index.js.map +1 -0
- package/package.json +46 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
import axios, { isAxiosError } from 'axios';
|
|
2
|
+
|
|
3
|
+
// src/domain/errors/simcapture-error.ts
|
|
4
|
+
var SimCaptureError = class _SimCaptureError extends Error {
|
|
5
|
+
/** HTTP status returned by SimCapture, or 0 when the request never completed. */
|
|
6
|
+
status;
|
|
7
|
+
/** Stable machine-readable code for branching (e.g. `AUTH_FAILED`). */
|
|
8
|
+
code;
|
|
9
|
+
/** Raw upstream response body, when available. */
|
|
10
|
+
body;
|
|
11
|
+
constructor(params) {
|
|
12
|
+
super(params.message, params.cause !== void 0 ? { cause: params.cause } : void 0);
|
|
13
|
+
this.name = "SimCaptureError";
|
|
14
|
+
this.status = params.status;
|
|
15
|
+
this.code = params.code;
|
|
16
|
+
this.body = params.body ?? null;
|
|
17
|
+
Object.setPrototypeOf(this, _SimCaptureError.prototype);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// src/domain/auth/auth-credentials.vo.ts
|
|
22
|
+
var AuthCredentials = class _AuthCredentials {
|
|
23
|
+
static DEFAULT_SUBDOMAIN = "simulacion-upch";
|
|
24
|
+
username;
|
|
25
|
+
password;
|
|
26
|
+
clientSubdomain;
|
|
27
|
+
constructor(params) {
|
|
28
|
+
if (!params.username) {
|
|
29
|
+
throw new SimCaptureError({
|
|
30
|
+
message: "SimCapture credentials: `username` is required.",
|
|
31
|
+
status: 0,
|
|
32
|
+
code: "CONFIG_INVALID"
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
if (!params.password) {
|
|
36
|
+
throw new SimCaptureError({
|
|
37
|
+
message: "SimCapture credentials: `password` is required.",
|
|
38
|
+
status: 0,
|
|
39
|
+
code: "CONFIG_INVALID"
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
this.username = params.username;
|
|
43
|
+
this.password = params.password;
|
|
44
|
+
this.clientSubdomain = params.clientSubdomain ?? _AuthCredentials.DEFAULT_SUBDOMAIN;
|
|
45
|
+
}
|
|
46
|
+
toAuthRequest() {
|
|
47
|
+
return {
|
|
48
|
+
username: this.username,
|
|
49
|
+
password: this.password,
|
|
50
|
+
clientSubdomain: this.clientSubdomain
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// src/domain/auth/token.vo.ts
|
|
56
|
+
var Token = class _Token {
|
|
57
|
+
value;
|
|
58
|
+
/** Epoch ms after which the token is considered stale, or null when no TTL. */
|
|
59
|
+
expiresAt;
|
|
60
|
+
constructor(value, expiresAt = null) {
|
|
61
|
+
this.value = value;
|
|
62
|
+
this.expiresAt = expiresAt;
|
|
63
|
+
}
|
|
64
|
+
static issue(value, ttlMs, now = Date.now()) {
|
|
65
|
+
const expiresAt = ttlMs > 0 ? now + ttlMs : null;
|
|
66
|
+
return new _Token(value, expiresAt);
|
|
67
|
+
}
|
|
68
|
+
isExpired(now = Date.now()) {
|
|
69
|
+
return this.expiresAt !== null && now >= this.expiresAt;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// src/application/auth/authenticator.ts
|
|
74
|
+
var Authenticator = class {
|
|
75
|
+
http;
|
|
76
|
+
store;
|
|
77
|
+
credentials;
|
|
78
|
+
tokenTtlMs;
|
|
79
|
+
now;
|
|
80
|
+
inFlightLogin = null;
|
|
81
|
+
constructor(options) {
|
|
82
|
+
this.http = options.http;
|
|
83
|
+
this.store = options.store;
|
|
84
|
+
this.credentials = options.credentials;
|
|
85
|
+
this.tokenTtlMs = options.tokenTtlMs;
|
|
86
|
+
this.now = options.now ?? Date.now;
|
|
87
|
+
}
|
|
88
|
+
/** Returns a valid token, logging in only when none is cached or it is stale. */
|
|
89
|
+
async getToken() {
|
|
90
|
+
const cached = this.store.get();
|
|
91
|
+
if (cached && !cached.isExpired(this.now())) return cached;
|
|
92
|
+
return this.login();
|
|
93
|
+
}
|
|
94
|
+
/** Forces a fresh login, deduping concurrent calls into one `/auth` request. */
|
|
95
|
+
async login() {
|
|
96
|
+
if (this.inFlightLogin) return this.inFlightLogin;
|
|
97
|
+
this.inFlightLogin = this.requestToken().then((token) => {
|
|
98
|
+
this.store.set(token);
|
|
99
|
+
return token;
|
|
100
|
+
}).finally(() => {
|
|
101
|
+
this.inFlightLogin = null;
|
|
102
|
+
});
|
|
103
|
+
return this.inFlightLogin;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Sends `req` with the cached token attached. On a 401 it refreshes the token
|
|
107
|
+
* once and retries the request a single time; a second 401 propagates.
|
|
108
|
+
*/
|
|
109
|
+
async authorizedRequest(req) {
|
|
110
|
+
const token = await this.getToken();
|
|
111
|
+
try {
|
|
112
|
+
return await this.http.request(withToken(req, token.value));
|
|
113
|
+
} catch (error) {
|
|
114
|
+
if (!isUnauthorized(error)) throw error;
|
|
115
|
+
this.store.clear();
|
|
116
|
+
const fresh = await this.login();
|
|
117
|
+
return this.http.request(withToken(req, fresh.value));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async requestToken() {
|
|
121
|
+
const response = await this.http.request({
|
|
122
|
+
method: "POST",
|
|
123
|
+
path: "/auth",
|
|
124
|
+
body: this.credentials.toAuthRequest(),
|
|
125
|
+
skipAuth: true
|
|
126
|
+
});
|
|
127
|
+
const value = response.data?.token;
|
|
128
|
+
if (!value) {
|
|
129
|
+
throw new SimCaptureError({
|
|
130
|
+
message: "SimCapture /auth did not return a token.",
|
|
131
|
+
status: response.status,
|
|
132
|
+
code: "AUTH_FAILED",
|
|
133
|
+
body: response.data
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return Token.issue(value, this.tokenTtlMs, this.now());
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
function withToken(req, token) {
|
|
140
|
+
return { ...req, headers: { ...req.headers, token } };
|
|
141
|
+
}
|
|
142
|
+
function isUnauthorized(error) {
|
|
143
|
+
return error instanceof SimCaptureError && error.status === 401;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// src/application/resources/base.resource.ts
|
|
147
|
+
var BaseResource = class {
|
|
148
|
+
constructor(auth) {
|
|
149
|
+
this.auth = auth;
|
|
150
|
+
}
|
|
151
|
+
auth;
|
|
152
|
+
async exec(req) {
|
|
153
|
+
const response = await this.auth.authorizedRequest(req);
|
|
154
|
+
return response.data;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// src/application/resources/courses.resource.ts
|
|
159
|
+
var CoursesResource = class extends BaseResource {
|
|
160
|
+
constructor(auth) {
|
|
161
|
+
super(auth);
|
|
162
|
+
}
|
|
163
|
+
/** `POST /courses` — one page of results. */
|
|
164
|
+
find(query) {
|
|
165
|
+
return this.exec({ method: "POST", path: "/courses", body: query });
|
|
166
|
+
}
|
|
167
|
+
/** `GET /courses/{courseId}/items` — scenarios / evaluation templates. */
|
|
168
|
+
getItems(courseId, params = {}) {
|
|
169
|
+
return this.exec({
|
|
170
|
+
method: "GET",
|
|
171
|
+
path: `/courses/${encodeURIComponent(courseId)}/items`,
|
|
172
|
+
query: {
|
|
173
|
+
includeScenarios: params.includeScenarios,
|
|
174
|
+
includeEvaluationTemplates: params.includeEvaluationTemplates,
|
|
175
|
+
hasScenarioReflections: params.hasScenarioReflections,
|
|
176
|
+
hasScenarioEvaluations: params.hasScenarioEvaluations
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// src/application/resources/inventory.resource.ts
|
|
183
|
+
var InventoryResource = class extends BaseResource {
|
|
184
|
+
constructor(auth) {
|
|
185
|
+
super(auth);
|
|
186
|
+
}
|
|
187
|
+
/** `GET /items` (server B) */
|
|
188
|
+
findAll() {
|
|
189
|
+
return this.exec({ method: "GET", path: "/items", server: "inventory" });
|
|
190
|
+
}
|
|
191
|
+
/** `GET /item/{itemId}` (server B) */
|
|
192
|
+
findOne(itemId) {
|
|
193
|
+
return this.exec({
|
|
194
|
+
method: "GET",
|
|
195
|
+
path: `/item/${encodeURIComponent(itemId)}`,
|
|
196
|
+
server: "inventory"
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
/** `POST /item/{itemId}/edit` (server B) — stock adjustment. */
|
|
200
|
+
edit(itemId, body) {
|
|
201
|
+
return this.exec({
|
|
202
|
+
method: "POST",
|
|
203
|
+
path: `/item/${encodeURIComponent(itemId)}/edit`,
|
|
204
|
+
server: "inventory",
|
|
205
|
+
body
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
// src/application/resources/locations.resource.ts
|
|
211
|
+
var LocationsResource = class extends BaseResource {
|
|
212
|
+
constructor(auth) {
|
|
213
|
+
super(auth);
|
|
214
|
+
}
|
|
215
|
+
/** `GET /locations?order=...` */
|
|
216
|
+
findAll(params = {}) {
|
|
217
|
+
return this.exec({
|
|
218
|
+
method: "GET",
|
|
219
|
+
path: "/locations",
|
|
220
|
+
query: { order: params.order }
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// src/application/resources/notifications.resource.ts
|
|
226
|
+
var NotificationsResource = class extends BaseResource {
|
|
227
|
+
constructor(auth) {
|
|
228
|
+
super(auth);
|
|
229
|
+
}
|
|
230
|
+
/** `POST /notifications` — send an email / notification. */
|
|
231
|
+
send(body) {
|
|
232
|
+
return this.exec({ method: "POST", path: "/notifications", body });
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// src/application/resources/organizations.resource.ts
|
|
237
|
+
var OrganizationsResource = class extends BaseResource {
|
|
238
|
+
constructor(auth) {
|
|
239
|
+
super(auth);
|
|
240
|
+
}
|
|
241
|
+
/** `GET /organizations` */
|
|
242
|
+
findAll() {
|
|
243
|
+
return this.exec({ method: "GET", path: "/organizations" });
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// src/application/resources/reservations.resource.ts
|
|
248
|
+
var ReservationsResource = class extends BaseResource {
|
|
249
|
+
constructor(auth) {
|
|
250
|
+
super(auth);
|
|
251
|
+
}
|
|
252
|
+
/** `GET /reservations?endAfterTs=&startBeforeTs=&limitFetchedReservationDetails=` */
|
|
253
|
+
findAll(params) {
|
|
254
|
+
return this.exec({
|
|
255
|
+
method: "GET",
|
|
256
|
+
path: "/reservations",
|
|
257
|
+
query: {
|
|
258
|
+
endAfterTs: params.endAfterTs,
|
|
259
|
+
startBeforeTs: params.startBeforeTs,
|
|
260
|
+
limitFetchedReservationDetails: params.limitFetchedReservationDetails ?? false
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
/** `GET /reservation/{id}` */
|
|
265
|
+
findOne(reservationId) {
|
|
266
|
+
return this.exec({
|
|
267
|
+
method: "GET",
|
|
268
|
+
path: `/reservation/${encodeURIComponent(reservationId)}`
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
/** `PUT /reservation/{id}` — overview or setup update. */
|
|
272
|
+
update(reservationId, body) {
|
|
273
|
+
return this.exec({
|
|
274
|
+
method: "PUT",
|
|
275
|
+
path: `/reservation/${encodeURIComponent(reservationId)}`,
|
|
276
|
+
body
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
/** `PUT /reservation/{id}/details` (ENGINEERING-MS flow). */
|
|
280
|
+
updateDetails(reservationId, body) {
|
|
281
|
+
return this.exec({
|
|
282
|
+
method: "PUT",
|
|
283
|
+
path: `/reservation/${encodeURIComponent(reservationId)}/details`,
|
|
284
|
+
body
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
/** `DELETE /reservation/{id}` */
|
|
288
|
+
delete(reservationId) {
|
|
289
|
+
return this.exec({
|
|
290
|
+
method: "DELETE",
|
|
291
|
+
path: `/reservation/${encodeURIComponent(reservationId)}`
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
// src/application/resources/scenarios.resource.ts
|
|
297
|
+
var ScenariosResource = class extends BaseResource {
|
|
298
|
+
constructor(auth) {
|
|
299
|
+
super(auth);
|
|
300
|
+
}
|
|
301
|
+
/** `GET /scenarios/{id}` */
|
|
302
|
+
findOne(scenarioId) {
|
|
303
|
+
return this.exec({
|
|
304
|
+
method: "GET",
|
|
305
|
+
path: `/scenarios/${encodeURIComponent(scenarioId)}`
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
/** `PUT /scenarios/{id}` — setup update. */
|
|
309
|
+
updateSetup(scenarioId, body) {
|
|
310
|
+
return this.exec({
|
|
311
|
+
method: "PUT",
|
|
312
|
+
path: `/scenarios/${encodeURIComponent(scenarioId)}`,
|
|
313
|
+
body
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
/** `PUT /scenarios/{id}/details` — detail update. */
|
|
317
|
+
updateDetails(scenarioId, body) {
|
|
318
|
+
return this.exec({
|
|
319
|
+
method: "PUT",
|
|
320
|
+
path: `/scenarios/${encodeURIComponent(scenarioId)}/details`,
|
|
321
|
+
body
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* `GET /scenarios/{id}/attachments/{assetName}` — returns the raw PDF bytes.
|
|
326
|
+
* The token is sent both as a query param and the header (matching the API).
|
|
327
|
+
*/
|
|
328
|
+
async getAttachment(scenarioId, assetName, params) {
|
|
329
|
+
const token = await this.auth.getToken();
|
|
330
|
+
const response = await this.auth.authorizedRequest({
|
|
331
|
+
method: "GET",
|
|
332
|
+
path: `/scenarios/${encodeURIComponent(scenarioId)}/attachments/${encodeURIComponent(assetName)}`,
|
|
333
|
+
query: { "client-id": params.clientId, token: token.value },
|
|
334
|
+
responseType: "blob"
|
|
335
|
+
});
|
|
336
|
+
return response.data;
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
// src/application/resources/simulators.resource.ts
|
|
341
|
+
var SimulatorsResource = class extends BaseResource {
|
|
342
|
+
constructor(auth) {
|
|
343
|
+
super(auth);
|
|
344
|
+
}
|
|
345
|
+
/** `GET /simulators` */
|
|
346
|
+
findAll() {
|
|
347
|
+
return this.exec({ method: "GET", path: "/simulators" });
|
|
348
|
+
}
|
|
349
|
+
/** `GET /simulators/{id}` */
|
|
350
|
+
findOne(simulatorId) {
|
|
351
|
+
return this.exec({
|
|
352
|
+
method: "GET",
|
|
353
|
+
path: `/simulators/${encodeURIComponent(simulatorId)}`
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
/** `PUT /simulators/{id}` */
|
|
357
|
+
update(simulatorId, body) {
|
|
358
|
+
return this.exec({
|
|
359
|
+
method: "PUT",
|
|
360
|
+
path: `/simulators/${encodeURIComponent(simulatorId)}`,
|
|
361
|
+
body
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
/** `GET /simulator-configs/{id}` */
|
|
365
|
+
getConfig(simulatorId) {
|
|
366
|
+
return this.exec({
|
|
367
|
+
method: "GET",
|
|
368
|
+
path: `/simulator-configs/${encodeURIComponent(simulatorId)}`
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
// src/config/sdk-config.ts
|
|
374
|
+
var SIMCAPTURE_DEFAULTS = {
|
|
375
|
+
apiUrl: "https://api.simcapture.com",
|
|
376
|
+
inventoryUrl: "https://inventory-management-production.us-east-1.simcapture.com",
|
|
377
|
+
tokenTtlMs: 30 * 60 * 1e3,
|
|
378
|
+
timeoutMs: 30 * 1e3
|
|
379
|
+
};
|
|
380
|
+
function resolveConfig(config) {
|
|
381
|
+
const apiUrl = stripTrailingSlash(config.apiUrl || SIMCAPTURE_DEFAULTS.apiUrl);
|
|
382
|
+
const inventoryUrl = stripTrailingSlash(config.inventoryUrl || SIMCAPTURE_DEFAULTS.inventoryUrl);
|
|
383
|
+
if (!config.credentials?.username || !config.credentials?.password) {
|
|
384
|
+
throw new SimCaptureError({
|
|
385
|
+
message: "SimCapture config: `credentials.username` and `credentials.password` are required.",
|
|
386
|
+
status: 0,
|
|
387
|
+
code: "CONFIG_INVALID"
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
return {
|
|
391
|
+
apiUrl,
|
|
392
|
+
inventoryUrl,
|
|
393
|
+
credentials: config.credentials,
|
|
394
|
+
tokenTtlMs: config.tokenTtlMs ?? SIMCAPTURE_DEFAULTS.tokenTtlMs,
|
|
395
|
+
timeoutMs: config.timeoutMs ?? SIMCAPTURE_DEFAULTS.timeoutMs
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
function stripTrailingSlash(url) {
|
|
399
|
+
return url.replace(/\/+$/, "");
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// src/infrastructure/cache/in-memory-token-store.ts
|
|
403
|
+
var InMemoryTokenStore = class {
|
|
404
|
+
token = null;
|
|
405
|
+
get() {
|
|
406
|
+
return this.token;
|
|
407
|
+
}
|
|
408
|
+
set(token) {
|
|
409
|
+
this.token = token;
|
|
410
|
+
}
|
|
411
|
+
clear() {
|
|
412
|
+
this.token = null;
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
var AxiosHttpClient = class {
|
|
416
|
+
instances;
|
|
417
|
+
constructor(options) {
|
|
418
|
+
this.instances = {
|
|
419
|
+
api: axios.create({ baseURL: options.apiUrl, timeout: options.timeoutMs }),
|
|
420
|
+
inventory: axios.create({ baseURL: options.inventoryUrl, timeout: options.timeoutMs })
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
async request(req) {
|
|
424
|
+
const instance = this.instances[req.server ?? "api"];
|
|
425
|
+
const config = {
|
|
426
|
+
method: req.method,
|
|
427
|
+
url: req.path,
|
|
428
|
+
responseType: req.responseType ?? "json"
|
|
429
|
+
};
|
|
430
|
+
const params = pruneUndefined(req.query);
|
|
431
|
+
if (params) config.params = params;
|
|
432
|
+
if (req.body !== void 0) config.data = req.body;
|
|
433
|
+
if (req.headers) config.headers = req.headers;
|
|
434
|
+
try {
|
|
435
|
+
const response = await instance.request(config);
|
|
436
|
+
return { status: response.status, data: response.data };
|
|
437
|
+
} catch (error) {
|
|
438
|
+
throw toSimCaptureError(error);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
function pruneUndefined(query) {
|
|
443
|
+
if (!query) return void 0;
|
|
444
|
+
const out = {};
|
|
445
|
+
for (const [key, value] of Object.entries(query)) {
|
|
446
|
+
if (value !== void 0) out[key] = value;
|
|
447
|
+
}
|
|
448
|
+
return out;
|
|
449
|
+
}
|
|
450
|
+
function toSimCaptureError(error) {
|
|
451
|
+
if (error instanceof SimCaptureError) return error;
|
|
452
|
+
if (isAxiosError(error)) {
|
|
453
|
+
const status = error.response?.status ?? 0;
|
|
454
|
+
if (status === 0) {
|
|
455
|
+
return new SimCaptureError({
|
|
456
|
+
message: `SimCapture request failed: ${error.message}`,
|
|
457
|
+
status: 0,
|
|
458
|
+
code: "NETWORK",
|
|
459
|
+
cause: error
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
return new SimCaptureError({
|
|
463
|
+
message: `SimCapture responded ${status}: ${error.message}`,
|
|
464
|
+
status,
|
|
465
|
+
code: status === 401 ? "UNAUTHORIZED" : "UPSTREAM_ERROR",
|
|
466
|
+
body: error.response?.data,
|
|
467
|
+
cause: error
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
return new SimCaptureError({
|
|
471
|
+
message: error instanceof Error ? error.message : "Unknown SimCapture error",
|
|
472
|
+
status: 0,
|
|
473
|
+
code: "NETWORK",
|
|
474
|
+
cause: error
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// src/client.ts
|
|
479
|
+
var SimCaptureClient = class {
|
|
480
|
+
organizations;
|
|
481
|
+
locations;
|
|
482
|
+
reservations;
|
|
483
|
+
courses;
|
|
484
|
+
scenarios;
|
|
485
|
+
simulators;
|
|
486
|
+
notifications;
|
|
487
|
+
inventory;
|
|
488
|
+
authenticator;
|
|
489
|
+
constructor(config, deps = {}) {
|
|
490
|
+
const resolved = resolveConfig(config);
|
|
491
|
+
const http = deps.httpClient ?? new AxiosHttpClient({
|
|
492
|
+
apiUrl: resolved.apiUrl,
|
|
493
|
+
inventoryUrl: resolved.inventoryUrl,
|
|
494
|
+
timeoutMs: resolved.timeoutMs
|
|
495
|
+
});
|
|
496
|
+
const store = deps.tokenStore ?? new InMemoryTokenStore();
|
|
497
|
+
const authenticatorOptions = {
|
|
498
|
+
http,
|
|
499
|
+
store,
|
|
500
|
+
credentials: new AuthCredentials(resolved.credentials),
|
|
501
|
+
tokenTtlMs: resolved.tokenTtlMs,
|
|
502
|
+
...deps.now ? { now: deps.now } : {}
|
|
503
|
+
};
|
|
504
|
+
this.authenticator = new Authenticator(authenticatorOptions);
|
|
505
|
+
this.organizations = new OrganizationsResource(this.authenticator);
|
|
506
|
+
this.locations = new LocationsResource(this.authenticator);
|
|
507
|
+
this.reservations = new ReservationsResource(this.authenticator);
|
|
508
|
+
this.courses = new CoursesResource(this.authenticator);
|
|
509
|
+
this.scenarios = new ScenariosResource(this.authenticator);
|
|
510
|
+
this.simulators = new SimulatorsResource(this.authenticator);
|
|
511
|
+
this.notifications = new NotificationsResource(this.authenticator);
|
|
512
|
+
this.inventory = new InventoryResource(this.authenticator);
|
|
513
|
+
}
|
|
514
|
+
/** Force a fresh login. Rarely needed — auth is handled lazily per request. */
|
|
515
|
+
login() {
|
|
516
|
+
return this.authenticator.login().then(() => void 0);
|
|
517
|
+
}
|
|
518
|
+
/** Returns a valid session token, logging in only if none is cached. */
|
|
519
|
+
async getToken() {
|
|
520
|
+
const token = await this.authenticator.getToken();
|
|
521
|
+
return { token: token.value };
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
export { AuthCredentials, Authenticator, AxiosHttpClient, CoursesResource, InMemoryTokenStore, InventoryResource, LocationsResource, NotificationsResource, OrganizationsResource, ReservationsResource, SIMCAPTURE_DEFAULTS, ScenariosResource, SimCaptureClient, SimCaptureError, SimulatorsResource, Token, resolveConfig };
|
|
526
|
+
//# sourceMappingURL=index.js.map
|
|
527
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/domain/errors/simcapture-error.ts","../src/domain/auth/auth-credentials.vo.ts","../src/domain/auth/token.vo.ts","../src/application/auth/authenticator.ts","../src/application/resources/base.resource.ts","../src/application/resources/courses.resource.ts","../src/application/resources/inventory.resource.ts","../src/application/resources/locations.resource.ts","../src/application/resources/notifications.resource.ts","../src/application/resources/organizations.resource.ts","../src/application/resources/reservations.resource.ts","../src/application/resources/scenarios.resource.ts","../src/application/resources/simulators.resource.ts","../src/config/sdk-config.ts","../src/infrastructure/cache/in-memory-token-store.ts","../src/infrastructure/http/axios-http-client.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Error raised when a SimCapture request fails. Unlike the old per-service\n * `RpcException({ status: 400 })`, this preserves the real upstream HTTP status\n * and response body so callers can translate it faithfully.\n */\nexport class SimCaptureError extends Error {\n /** HTTP status returned by SimCapture, or 0 when the request never completed. */\n readonly status: number;\n /** Stable machine-readable code for branching (e.g. `AUTH_FAILED`). */\n readonly code: SimCaptureErrorCode;\n /** Raw upstream response body, when available. */\n readonly body: unknown;\n\n constructor(params: {\n message: string;\n status: number;\n code: SimCaptureErrorCode;\n body?: unknown;\n cause?: unknown;\n }) {\n super(params.message, params.cause !== undefined ? { cause: params.cause } : undefined);\n this.name = \"SimCaptureError\";\n this.status = params.status;\n this.code = params.code;\n this.body = params.body ?? null;\n Object.setPrototypeOf(this, SimCaptureError.prototype);\n }\n}\n\nexport type SimCaptureErrorCode =\n | \"AUTH_FAILED\"\n | \"UNAUTHORIZED\"\n | \"NETWORK\"\n | \"UPSTREAM_ERROR\"\n | \"CONFIG_INVALID\";\n","import { SimCaptureError } from \"../errors/simcapture-error\";\n\n/**\n * Immutable login credentials. `clientSubdomain` defaults to the UPCH tenant\n * (`simulacion-upch`), matching what the old services hard-coded.\n */\nexport class AuthCredentials {\n static readonly DEFAULT_SUBDOMAIN = \"simulacion-upch\";\n\n readonly username: string;\n readonly password: string;\n readonly clientSubdomain: string;\n\n constructor(params: { username: string; password: string; clientSubdomain?: string }) {\n if (!params.username) {\n throw new SimCaptureError({\n message: \"SimCapture credentials: `username` is required.\",\n status: 0,\n code: \"CONFIG_INVALID\",\n });\n }\n if (!params.password) {\n throw new SimCaptureError({\n message: \"SimCapture credentials: `password` is required.\",\n status: 0,\n code: \"CONFIG_INVALID\",\n });\n }\n this.username = params.username;\n this.password = params.password;\n this.clientSubdomain = params.clientSubdomain ?? AuthCredentials.DEFAULT_SUBDOMAIN;\n }\n\n toAuthRequest(): { username: string; password: string; clientSubdomain: string } {\n return {\n username: this.username,\n password: this.password,\n clientSubdomain: this.clientSubdomain,\n };\n }\n}\n","/**\n * SimCapture session token.\n *\n * `POST /auth` returns only `{ token }` with no expiry, so we apply a *soft* TTL\n * to proactively refresh before the server would reject it. The authoritative\n * invalidation remains the 401 path (see Authenticator). `ttlMs <= 0` disables\n * the soft expiry and the token is reused until a 401.\n */\nexport class Token {\n readonly value: string;\n /** Epoch ms after which the token is considered stale, or null when no TTL. */\n readonly expiresAt: number | null;\n\n constructor(value: string, expiresAt: number | null = null) {\n this.value = value;\n this.expiresAt = expiresAt;\n }\n\n static issue(value: string, ttlMs: number, now: number = Date.now()): Token {\n const expiresAt = ttlMs > 0 ? now + ttlMs : null;\n return new Token(value, expiresAt);\n }\n\n isExpired(now: number = Date.now()): boolean {\n return this.expiresAt !== null && now >= this.expiresAt;\n }\n}\n","import { AuthCredentials } from \"../../domain/auth/auth-credentials.vo\";\nimport { Token } from \"../../domain/auth/token.vo\";\nimport { SimCaptureError } from \"../../domain/errors/simcapture-error\";\nimport type { AuthResponse } from \"../../domain/models/auth\";\nimport type { HttpClient, HttpRequest, HttpResponse } from \"../../domain/ports/http-client.port\";\nimport type { TokenStore } from \"../../domain/ports/token-store.port\";\n\nexport interface AuthenticatorOptions {\n http: HttpClient;\n store: TokenStore;\n credentials: AuthCredentials;\n tokenTtlMs: number;\n now?: () => number;\n}\n\n/**\n * Owns the SimCapture session. Fixes the old \"login on every call\" anti-pattern:\n *\n * - tokens are cached and reused until the soft TTL elapses or a 401 occurs;\n * - concurrent callers share a single in-flight `/auth` request;\n * - on 401 the token is cleared, re-issued **once**, and the request retried once.\n */\nexport class Authenticator {\n private readonly http: HttpClient;\n private readonly store: TokenStore;\n private readonly credentials: AuthCredentials;\n private readonly tokenTtlMs: number;\n private readonly now: () => number;\n private inFlightLogin: Promise<Token> | null = null;\n\n constructor(options: AuthenticatorOptions) {\n this.http = options.http;\n this.store = options.store;\n this.credentials = options.credentials;\n this.tokenTtlMs = options.tokenTtlMs;\n this.now = options.now ?? Date.now;\n }\n\n /** Returns a valid token, logging in only when none is cached or it is stale. */\n async getToken(): Promise<Token> {\n const cached = this.store.get();\n if (cached && !cached.isExpired(this.now())) return cached;\n return this.login();\n }\n\n /** Forces a fresh login, deduping concurrent calls into one `/auth` request. */\n async login(): Promise<Token> {\n if (this.inFlightLogin) return this.inFlightLogin;\n\n this.inFlightLogin = this.requestToken()\n .then((token) => {\n this.store.set(token);\n return token;\n })\n .finally(() => {\n this.inFlightLogin = null;\n });\n\n return this.inFlightLogin;\n }\n\n /**\n * Sends `req` with the cached token attached. On a 401 it refreshes the token\n * once and retries the request a single time; a second 401 propagates.\n */\n async authorizedRequest<T>(req: HttpRequest): Promise<HttpResponse<T>> {\n const token = await this.getToken();\n try {\n return await this.http.request<T>(withToken(req, token.value));\n } catch (error) {\n if (!isUnauthorized(error)) throw error;\n this.store.clear();\n const fresh = await this.login();\n return this.http.request<T>(withToken(req, fresh.value));\n }\n }\n\n private async requestToken(): Promise<Token> {\n const response = await this.http.request<AuthResponse>({\n method: \"POST\",\n path: \"/auth\",\n body: this.credentials.toAuthRequest(),\n skipAuth: true,\n });\n const value = response.data?.token;\n if (!value) {\n throw new SimCaptureError({\n message: \"SimCapture /auth did not return a token.\",\n status: response.status,\n code: \"AUTH_FAILED\",\n body: response.data,\n });\n }\n return Token.issue(value, this.tokenTtlMs, this.now());\n }\n}\n\nfunction withToken(req: HttpRequest, token: string): HttpRequest {\n return { ...req, headers: { ...req.headers, token } };\n}\n\nfunction isUnauthorized(error: unknown): boolean {\n return error instanceof SimCaptureError && error.status === 401;\n}\n","import type { HttpRequest, HttpResponse } from \"../../domain/ports/http-client.port\";\nimport type { Authenticator } from \"../auth/authenticator\";\n\n/** Shared base: every resource sends authorized requests through the Authenticator. */\nexport abstract class BaseResource {\n protected constructor(protected readonly auth: Authenticator) {}\n\n protected async exec<T>(req: HttpRequest): Promise<T> {\n const response: HttpResponse<T> = await this.auth.authorizedRequest<T>(req);\n return response.data;\n }\n}\n","import type {\n CourseItems,\n CourseItemsParams,\n CoursesPage,\n FindCoursesQuery,\n} from \"../../domain/models/course\";\nimport { BaseResource } from \"./base.resource\";\nimport type { Authenticator } from \"../auth/authenticator\";\n\nexport class CoursesResource extends BaseResource {\n constructor(auth: Authenticator) {\n super(auth);\n }\n\n /** `POST /courses` — one page of results. */\n find(query: FindCoursesQuery): Promise<CoursesPage> {\n return this.exec<CoursesPage>({ method: \"POST\", path: \"/courses\", body: query });\n }\n\n /** `GET /courses/{courseId}/items` — scenarios / evaluation templates. */\n getItems(courseId: string, params: CourseItemsParams = {}): Promise<CourseItems> {\n return this.exec<CourseItems>({\n method: \"GET\",\n path: `/courses/${encodeURIComponent(courseId)}/items`,\n query: {\n includeScenarios: params.includeScenarios,\n includeEvaluationTemplates: params.includeEvaluationTemplates,\n hasScenarioReflections: params.hasScenarioReflections,\n hasScenarioEvaluations: params.hasScenarioEvaluations,\n },\n });\n }\n}\n","import type { InventoryItem, InventoryItemEdit } from \"../../domain/models/inventory\";\nimport { BaseResource } from \"./base.resource\";\nimport type { Authenticator } from \"../auth/authenticator\";\n\n/** Inventory subsystem — every call targets server B (`inventoryUrl`). */\nexport class InventoryResource extends BaseResource {\n constructor(auth: Authenticator) {\n super(auth);\n }\n\n /** `GET /items` (server B) */\n findAll(): Promise<InventoryItem[]> {\n return this.exec<InventoryItem[]>({ method: \"GET\", path: \"/items\", server: \"inventory\" });\n }\n\n /** `GET /item/{itemId}` (server B) */\n findOne(itemId: string): Promise<InventoryItem> {\n return this.exec<InventoryItem>({\n method: \"GET\",\n path: `/item/${encodeURIComponent(itemId)}`,\n server: \"inventory\",\n });\n }\n\n /** `POST /item/{itemId}/edit` (server B) — stock adjustment. */\n edit(itemId: string, body: InventoryItemEdit): Promise<InventoryItem> {\n return this.exec<InventoryItem>({\n method: \"POST\",\n path: `/item/${encodeURIComponent(itemId)}/edit`,\n server: \"inventory\",\n body,\n });\n }\n}\n","import type { ListLocationsParams, Location } from \"../../domain/models/location\";\nimport { BaseResource } from \"./base.resource\";\nimport type { Authenticator } from \"../auth/authenticator\";\n\nexport class LocationsResource extends BaseResource {\n constructor(auth: Authenticator) {\n super(auth);\n }\n\n /** `GET /locations?order=...` */\n findAll(params: ListLocationsParams = {}): Promise<Location[]> {\n return this.exec<Location[]>({\n method: \"GET\",\n path: \"/locations\",\n query: { order: params.order },\n });\n }\n}\n","import type { NotificationRequest } from \"../../domain/models/notification\";\nimport { BaseResource } from \"./base.resource\";\nimport type { Authenticator } from \"../auth/authenticator\";\n\nexport class NotificationsResource extends BaseResource {\n constructor(auth: Authenticator) {\n super(auth);\n }\n\n /** `POST /notifications` — send an email / notification. */\n send(body: NotificationRequest): Promise<void> {\n return this.exec<void>({ method: \"POST\", path: \"/notifications\", body });\n }\n}\n","import type { Organization } from \"../../domain/models/organization\";\nimport { BaseResource } from \"./base.resource\";\nimport type { Authenticator } from \"../auth/authenticator\";\n\nexport class OrganizationsResource extends BaseResource {\n constructor(auth: Authenticator) {\n super(auth);\n }\n\n /** `GET /organizations` */\n findAll(): Promise<Organization[]> {\n return this.exec<Organization[]>({ method: \"GET\", path: \"/organizations\" });\n }\n}\n","import type {\n ListReservationsParams,\n Reservation,\n ReservationDetailsUpdate,\n ReservationUpdate,\n} from \"../../domain/models/reservation\";\nimport { BaseResource } from \"./base.resource\";\nimport type { Authenticator } from \"../auth/authenticator\";\n\nexport class ReservationsResource extends BaseResource {\n constructor(auth: Authenticator) {\n super(auth);\n }\n\n /** `GET /reservations?endAfterTs=&startBeforeTs=&limitFetchedReservationDetails=` */\n findAll(params: ListReservationsParams): Promise<Reservation[]> {\n return this.exec<Reservation[]>({\n method: \"GET\",\n path: \"/reservations\",\n query: {\n endAfterTs: params.endAfterTs,\n startBeforeTs: params.startBeforeTs,\n limitFetchedReservationDetails: params.limitFetchedReservationDetails ?? false,\n },\n });\n }\n\n /** `GET /reservation/{id}` */\n findOne(reservationId: string): Promise<Reservation> {\n return this.exec<Reservation>({\n method: \"GET\",\n path: `/reservation/${encodeURIComponent(reservationId)}`,\n });\n }\n\n /** `PUT /reservation/{id}` — overview or setup update. */\n update(reservationId: string, body: ReservationUpdate): Promise<Reservation> {\n return this.exec<Reservation>({\n method: \"PUT\",\n path: `/reservation/${encodeURIComponent(reservationId)}`,\n body,\n });\n }\n\n /** `PUT /reservation/{id}/details` (ENGINEERING-MS flow). */\n updateDetails(reservationId: string, body: ReservationDetailsUpdate): Promise<Reservation> {\n return this.exec<Reservation>({\n method: \"PUT\",\n path: `/reservation/${encodeURIComponent(reservationId)}/details`,\n body,\n });\n }\n\n /** `DELETE /reservation/{id}` */\n delete(reservationId: string): Promise<void> {\n return this.exec<void>({\n method: \"DELETE\",\n path: `/reservation/${encodeURIComponent(reservationId)}`,\n });\n }\n}\n","import type {\n Scenario,\n ScenarioAttachmentParams,\n ScenarioDetailsUpdate,\n ScenarioSetupUpdate,\n} from \"../../domain/models/scenario\";\nimport { BaseResource } from \"./base.resource\";\nimport type { Authenticator } from \"../auth/authenticator\";\n\nexport class ScenariosResource extends BaseResource {\n constructor(auth: Authenticator) {\n super(auth);\n }\n\n /** `GET /scenarios/{id}` */\n findOne(scenarioId: string): Promise<Scenario> {\n return this.exec<Scenario>({\n method: \"GET\",\n path: `/scenarios/${encodeURIComponent(scenarioId)}`,\n });\n }\n\n /** `PUT /scenarios/{id}` — setup update. */\n updateSetup(scenarioId: string, body: ScenarioSetupUpdate): Promise<Scenario> {\n return this.exec<Scenario>({\n method: \"PUT\",\n path: `/scenarios/${encodeURIComponent(scenarioId)}`,\n body,\n });\n }\n\n /** `PUT /scenarios/{id}/details` — detail update. */\n updateDetails(scenarioId: string, body: ScenarioDetailsUpdate): Promise<Scenario> {\n return this.exec<Scenario>({\n method: \"PUT\",\n path: `/scenarios/${encodeURIComponent(scenarioId)}/details`,\n body,\n });\n }\n\n /**\n * `GET /scenarios/{id}/attachments/{assetName}` — returns the raw PDF bytes.\n * The token is sent both as a query param and the header (matching the API).\n */\n async getAttachment(\n scenarioId: string,\n assetName: string,\n params: ScenarioAttachmentParams,\n ): Promise<ArrayBuffer> {\n const token = await this.auth.getToken();\n const response = await this.auth.authorizedRequest<ArrayBuffer>({\n method: \"GET\",\n path: `/scenarios/${encodeURIComponent(scenarioId)}/attachments/${encodeURIComponent(assetName)}`,\n query: { \"client-id\": params.clientId, token: token.value },\n responseType: \"blob\",\n });\n return response.data;\n }\n}\n","import type {\n Simulator,\n SimulatorConfig,\n SimulatorUpdate,\n} from \"../../domain/models/simulator\";\nimport { BaseResource } from \"./base.resource\";\nimport type { Authenticator } from \"../auth/authenticator\";\n\nexport class SimulatorsResource extends BaseResource {\n constructor(auth: Authenticator) {\n super(auth);\n }\n\n /** `GET /simulators` */\n findAll(): Promise<Simulator[]> {\n return this.exec<Simulator[]>({ method: \"GET\", path: \"/simulators\" });\n }\n\n /** `GET /simulators/{id}` */\n findOne(simulatorId: string): Promise<Simulator> {\n return this.exec<Simulator>({\n method: \"GET\",\n path: `/simulators/${encodeURIComponent(simulatorId)}`,\n });\n }\n\n /** `PUT /simulators/{id}` */\n update(simulatorId: string, body: SimulatorUpdate): Promise<Simulator> {\n return this.exec<Simulator>({\n method: \"PUT\",\n path: `/simulators/${encodeURIComponent(simulatorId)}`,\n body,\n });\n }\n\n /** `GET /simulator-configs/{id}` */\n getConfig(simulatorId: string): Promise<SimulatorConfig> {\n return this.exec<SimulatorConfig>({\n method: \"GET\",\n path: `/simulator-configs/${encodeURIComponent(simulatorId)}`,\n });\n }\n}\n","import { SimCaptureError } from \"../domain/errors/simcapture-error\";\n\nexport interface SimCaptureConfig {\n /** Main API base URL, e.g. `https://api.simcapture.com`. */\n apiUrl: string;\n /** Inventory subsystem base URL (server B). */\n inventoryUrl: string;\n credentials: {\n username: string;\n password: string;\n /** Defaults to `simulacion-upch`. */\n clientSubdomain?: string;\n };\n /**\n * Soft token TTL in ms. The token is proactively refreshed once stale; a 401\n * still forces a refresh regardless. `0` disables the soft TTL.\n * Default: 30 minutes.\n */\n tokenTtlMs?: number;\n /** Per-request timeout in ms. Default: 30s. */\n timeoutMs?: number;\n}\n\nexport const SIMCAPTURE_DEFAULTS = {\n apiUrl: \"https://api.simcapture.com\",\n inventoryUrl: \"https://inventory-management-production.us-east-1.simcapture.com\",\n tokenTtlMs: 30 * 60 * 1000,\n timeoutMs: 30 * 1000,\n} as const;\n\nexport interface ResolvedSimCaptureConfig {\n apiUrl: string;\n inventoryUrl: string;\n credentials: { username: string; password: string; clientSubdomain?: string };\n tokenTtlMs: number;\n timeoutMs: number;\n}\n\n/** Fills defaults and fails fast on missing required values. */\nexport function resolveConfig(config: SimCaptureConfig): ResolvedSimCaptureConfig {\n const apiUrl = stripTrailingSlash(config.apiUrl || SIMCAPTURE_DEFAULTS.apiUrl);\n const inventoryUrl = stripTrailingSlash(config.inventoryUrl || SIMCAPTURE_DEFAULTS.inventoryUrl);\n\n if (!config.credentials?.username || !config.credentials?.password) {\n throw new SimCaptureError({\n message: \"SimCapture config: `credentials.username` and `credentials.password` are required.\",\n status: 0,\n code: \"CONFIG_INVALID\",\n });\n }\n\n return {\n apiUrl,\n inventoryUrl,\n credentials: config.credentials,\n tokenTtlMs: config.tokenTtlMs ?? SIMCAPTURE_DEFAULTS.tokenTtlMs,\n timeoutMs: config.timeoutMs ?? SIMCAPTURE_DEFAULTS.timeoutMs,\n };\n}\n\nfunction stripTrailingSlash(url: string): string {\n return url.replace(/\\/+$/, \"\");\n}\n","import type { Token } from \"../../domain/auth/token.vo\";\nimport type { TokenStore } from \"../../domain/ports/token-store.port\";\n\n/** Default token store: keeps the active token in process memory. */\nexport class InMemoryTokenStore implements TokenStore {\n private token: Token | null = null;\n\n get(): Token | null {\n return this.token;\n }\n\n set(token: Token): void {\n this.token = token;\n }\n\n clear(): void {\n this.token = null;\n }\n}\n","import axios, { type AxiosInstance, type AxiosRequestConfig, isAxiosError } from \"axios\";\n\nimport { SimCaptureError } from \"../../domain/errors/simcapture-error\";\nimport type {\n HttpClient,\n HttpRequest,\n HttpResponse,\n SimCaptureServer,\n} from \"../../domain/ports/http-client.port\";\n\nexport interface AxiosHttpClientOptions {\n apiUrl: string;\n inventoryUrl: string;\n timeoutMs: number;\n}\n\n/**\n * Transport adapter. Pure request/response over two axios instances (main API +\n * inventory server). It is auth-agnostic: the caller decides whether a token\n * header is attached. Upstream failures are normalised into `SimCaptureError`\n * preserving the real status and body.\n */\nexport class AxiosHttpClient implements HttpClient {\n private readonly instances: Record<SimCaptureServer, AxiosInstance>;\n\n constructor(options: AxiosHttpClientOptions) {\n this.instances = {\n api: axios.create({ baseURL: options.apiUrl, timeout: options.timeoutMs }),\n inventory: axios.create({ baseURL: options.inventoryUrl, timeout: options.timeoutMs }),\n };\n }\n\n async request<T>(req: HttpRequest): Promise<HttpResponse<T>> {\n const instance = this.instances[req.server ?? \"api\"];\n const config: AxiosRequestConfig = {\n method: req.method,\n url: req.path,\n responseType: req.responseType ?? \"json\",\n };\n const params = pruneUndefined(req.query);\n if (params) config.params = params;\n if (req.body !== undefined) config.data = req.body;\n if (req.headers) config.headers = req.headers;\n\n try {\n const response = await instance.request<T>(config);\n return { status: response.status, data: response.data };\n } catch (error) {\n throw toSimCaptureError(error);\n }\n }\n}\n\nfunction pruneUndefined(\n query: HttpRequest[\"query\"],\n): Record<string, string | number | boolean> | undefined {\n if (!query) return undefined;\n const out: Record<string, string | number | boolean> = {};\n for (const [key, value] of Object.entries(query)) {\n if (value !== undefined) out[key] = value;\n }\n return out;\n}\n\nexport function toSimCaptureError(error: unknown): SimCaptureError {\n if (error instanceof SimCaptureError) return error;\n\n if (isAxiosError(error)) {\n const status = error.response?.status ?? 0;\n if (status === 0) {\n return new SimCaptureError({\n message: `SimCapture request failed: ${error.message}`,\n status: 0,\n code: \"NETWORK\",\n cause: error,\n });\n }\n return new SimCaptureError({\n message: `SimCapture responded ${status}: ${error.message}`,\n status,\n code: status === 401 ? \"UNAUTHORIZED\" : \"UPSTREAM_ERROR\",\n body: error.response?.data,\n cause: error,\n });\n }\n\n return new SimCaptureError({\n message: error instanceof Error ? error.message : \"Unknown SimCapture error\",\n status: 0,\n code: \"NETWORK\",\n cause: error,\n });\n}\n","import { Authenticator } from \"./application/auth/authenticator\";\nimport { CoursesResource } from \"./application/resources/courses.resource\";\nimport { InventoryResource } from \"./application/resources/inventory.resource\";\nimport { LocationsResource } from \"./application/resources/locations.resource\";\nimport { NotificationsResource } from \"./application/resources/notifications.resource\";\nimport { OrganizationsResource } from \"./application/resources/organizations.resource\";\nimport { ReservationsResource } from \"./application/resources/reservations.resource\";\nimport { ScenariosResource } from \"./application/resources/scenarios.resource\";\nimport { SimulatorsResource } from \"./application/resources/simulators.resource\";\nimport { resolveConfig, type SimCaptureConfig } from \"./config/sdk-config\";\nimport { AuthCredentials } from \"./domain/auth/auth-credentials.vo\";\nimport type { HttpClient } from \"./domain/ports/http-client.port\";\nimport type { TokenStore } from \"./domain/ports/token-store.port\";\nimport { InMemoryTokenStore } from \"./infrastructure/cache/in-memory-token-store\";\nimport { AxiosHttpClient } from \"./infrastructure/http/axios-http-client\";\n\nexport interface SimCaptureClientDeps {\n /** Override the transport (e.g. for tests). Defaults to AxiosHttpClient. */\n httpClient?: HttpClient;\n /** Override the token cache. Defaults to InMemoryTokenStore. */\n tokenStore?: TokenStore;\n /** Override the clock (tests). */\n now?: () => number;\n}\n\n/**\n * Single entrypoint to SimCapture. Wires the infrastructure adapters into the\n * application use-cases (composition root) and exposes one resource per domain.\n *\n * ```ts\n * const sc = new SimCaptureClient({\n * apiUrl: process.env.SIMCAPTURE_API!,\n * inventoryUrl: process.env.SIMCAPTURE_INVENTORY_API!,\n * credentials: { username: process.env.SIMCAPTURE_USER!, password: process.env.SIMCAPTURE_PASSWORD! },\n * });\n * const orgs = await sc.organizations.findAll();\n * ```\n */\nexport class SimCaptureClient {\n readonly organizations: OrganizationsResource;\n readonly locations: LocationsResource;\n readonly reservations: ReservationsResource;\n readonly courses: CoursesResource;\n readonly scenarios: ScenariosResource;\n readonly simulators: SimulatorsResource;\n readonly notifications: NotificationsResource;\n readonly inventory: InventoryResource;\n\n private readonly authenticator: Authenticator;\n\n constructor(config: SimCaptureConfig, deps: SimCaptureClientDeps = {}) {\n const resolved = resolveConfig(config);\n\n const http =\n deps.httpClient ??\n new AxiosHttpClient({\n apiUrl: resolved.apiUrl,\n inventoryUrl: resolved.inventoryUrl,\n timeoutMs: resolved.timeoutMs,\n });\n\n const store = deps.tokenStore ?? new InMemoryTokenStore();\n\n const authenticatorOptions = {\n http,\n store,\n credentials: new AuthCredentials(resolved.credentials),\n tokenTtlMs: resolved.tokenTtlMs,\n ...(deps.now ? { now: deps.now } : {}),\n };\n this.authenticator = new Authenticator(authenticatorOptions);\n\n this.organizations = new OrganizationsResource(this.authenticator);\n this.locations = new LocationsResource(this.authenticator);\n this.reservations = new ReservationsResource(this.authenticator);\n this.courses = new CoursesResource(this.authenticator);\n this.scenarios = new ScenariosResource(this.authenticator);\n this.simulators = new SimulatorsResource(this.authenticator);\n this.notifications = new NotificationsResource(this.authenticator);\n this.inventory = new InventoryResource(this.authenticator);\n }\n\n /** Force a fresh login. Rarely needed — auth is handled lazily per request. */\n login(): Promise<void> {\n return this.authenticator.login().then(() => undefined);\n }\n\n /** Returns a valid session token, logging in only if none is cached. */\n async getToken(): Promise<{ token: string }> {\n const token = await this.authenticator.getToken();\n return { token: token.value };\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "simcapture-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "SimCapture API client shared across UPCH microservices.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "georgegiosue",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/georgegiosue/simcapture-sdk.git"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "./dist/index.cjs",
|
|
13
|
+
"module": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js",
|
|
19
|
+
"require": "./dist/index.cjs"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsup",
|
|
28
|
+
"typecheck": "tsc --noEmit",
|
|
29
|
+
"test": "bun test",
|
|
30
|
+
"prepublishOnly": "bun run typecheck && bun run test && bun run build"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"axios": "^1.7.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/bun": "latest",
|
|
37
|
+
"tsup": "^8.0.0",
|
|
38
|
+
"typescript": "^5.5.0"
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=18"
|
|
45
|
+
}
|
|
46
|
+
}
|