@letpeoplework/lighthouse-client 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/dist/index.js ADDED
@@ -0,0 +1,607 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ createLighthouseAuthContext: () => createLighthouseAuthContext,
24
+ createLighthouseClient: () => createLighthouseClient,
25
+ getClientPackageContract: () => getClientPackageContract,
26
+ parseStandaloneDiscoveryContract: () => parseStandaloneDiscoveryContract,
27
+ validateLighthouseConnectivity: () => validateLighthouseConnectivity
28
+ });
29
+ module.exports = __toCommonJS(index_exports);
30
+ var getClientPackageContract = () => ({
31
+ name: "@letpeoplework/lighthouse-client",
32
+ capabilities: [
33
+ "versioned-api-contracts",
34
+ "shared-domain-operations",
35
+ "connectivity-and-discovery-contracts",
36
+ "automation-auth-contracts"
37
+ ]
38
+ });
39
+ var SUPPORTED_DISCOVERY_CONTRACT_VERSION = 1;
40
+ var isObjectRecord = (value) => {
41
+ return typeof value === "object" && value !== null && !Array.isArray(value);
42
+ };
43
+ var getNormalizedLighthouseUrl = (value) => {
44
+ const trimmed = value.trim();
45
+ if (trimmed.length === 0) {
46
+ return null;
47
+ }
48
+ try {
49
+ const parsed = new URL(trimmed);
50
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
51
+ return null;
52
+ }
53
+ const pathname = parsed.pathname.replace(/\/+$/u, "");
54
+ if (pathname.length === 0 || pathname === "/") {
55
+ return parsed.origin;
56
+ }
57
+ return `${parsed.origin}${pathname}`;
58
+ } catch {
59
+ return null;
60
+ }
61
+ };
62
+ var getApiBaseUrl = (lighthouseUrl) => {
63
+ if (lighthouseUrl.endsWith("/api")) {
64
+ return lighthouseUrl;
65
+ }
66
+ return `${lighthouseUrl}/api`;
67
+ };
68
+ var validateDiscoveryTimestamp = (detectedAtUtc) => {
69
+ return !Number.isNaN(Date.parse(detectedAtUtc));
70
+ };
71
+ var getInvalidContractResult = (reason) => ({
72
+ isValid: false,
73
+ reason
74
+ });
75
+ var validateStandaloneDiscoveryContract = (contract) => {
76
+ if (contract.contractVersion !== SUPPORTED_DISCOVERY_CONTRACT_VERSION) {
77
+ return getInvalidContractResult(
78
+ "Discovery contractVersion is not supported."
79
+ );
80
+ }
81
+ const normalizedUrl = getNormalizedLighthouseUrl(contract.lighthouseUrl);
82
+ if (normalizedUrl === null) {
83
+ return getInvalidContractResult("Discovery lighthouseUrl is invalid.");
84
+ }
85
+ if (!validateDiscoveryTimestamp(contract.detectedAtUtc)) {
86
+ return getInvalidContractResult("Discovery detectedAtUtc is invalid.");
87
+ }
88
+ return {
89
+ isValid: true,
90
+ contract: {
91
+ contractVersion: SUPPORTED_DISCOVERY_CONTRACT_VERSION,
92
+ lighthouseUrl: normalizedUrl,
93
+ detectedAtUtc: contract.detectedAtUtc
94
+ }
95
+ };
96
+ };
97
+ var parseStandaloneDiscoveryContract = (serializedContract) => {
98
+ let parsed;
99
+ try {
100
+ parsed = JSON.parse(serializedContract);
101
+ } catch {
102
+ return getInvalidContractResult(
103
+ "Discovery contract payload is not valid JSON."
104
+ );
105
+ }
106
+ if (!isObjectRecord(parsed)) {
107
+ return getInvalidContractResult(
108
+ "Discovery contract payload must be an object."
109
+ );
110
+ }
111
+ const contractVersion = parsed.contractVersion;
112
+ if (typeof contractVersion !== "number") {
113
+ return getInvalidContractResult(
114
+ "Discovery contractVersion must be a number."
115
+ );
116
+ }
117
+ const lighthouseUrl = parsed.lighthouseUrl;
118
+ if (typeof lighthouseUrl !== "string") {
119
+ return getInvalidContractResult(
120
+ "Discovery lighthouseUrl must be a string."
121
+ );
122
+ }
123
+ const detectedAtUtc = parsed.detectedAtUtc;
124
+ if (typeof detectedAtUtc !== "string") {
125
+ return getInvalidContractResult(
126
+ "Discovery detectedAtUtc must be a string."
127
+ );
128
+ }
129
+ return validateStandaloneDiscoveryContract({
130
+ contractVersion,
131
+ lighthouseUrl,
132
+ detectedAtUtc
133
+ });
134
+ };
135
+ var getResolvedEndpoint = (mode, lighthouseUrl) => {
136
+ const apiBaseUrl = getApiBaseUrl(lighthouseUrl);
137
+ return {
138
+ mode,
139
+ lighthouseUrl,
140
+ apiBaseUrl,
141
+ healthCheckUrl: `${apiBaseUrl}/v1/version`
142
+ };
143
+ };
144
+ var getMisconfiguredResult = (reason) => ({
145
+ category: "misconfigured",
146
+ reason
147
+ });
148
+ var resolveConnectionEndpoint = async (configuration) => {
149
+ if (configuration.kind === "explicit") {
150
+ const normalizedUrl = getNormalizedLighthouseUrl(
151
+ configuration.lighthouseUrl
152
+ );
153
+ if (normalizedUrl === null) {
154
+ return {
155
+ isValid: false,
156
+ result: getMisconfiguredResult("Explicit Lighthouse URL is invalid.")
157
+ };
158
+ }
159
+ return {
160
+ isValid: true,
161
+ endpoint: getResolvedEndpoint("explicit", normalizedUrl)
162
+ };
163
+ }
164
+ const discoveryContract = await configuration.getDiscoveryContract();
165
+ if (discoveryContract === null) {
166
+ return {
167
+ isValid: false,
168
+ result: getMisconfiguredResult(
169
+ "Standalone discovery contract is not available."
170
+ )
171
+ };
172
+ }
173
+ const parsedContract = validateStandaloneDiscoveryContract(discoveryContract);
174
+ if (!parsedContract.isValid) {
175
+ return {
176
+ isValid: false,
177
+ result: getMisconfiguredResult(parsedContract.reason)
178
+ };
179
+ }
180
+ return {
181
+ isValid: true,
182
+ endpoint: getResolvedEndpoint(
183
+ "standalone",
184
+ parsedContract.contract.lighthouseUrl
185
+ )
186
+ };
187
+ };
188
+ var getFailureCategoryForStatus = (statusCode) => {
189
+ if (statusCode === 401 || statusCode === 403) {
190
+ return "unauthorized";
191
+ }
192
+ if (statusCode === 404) {
193
+ return "misconfigured";
194
+ }
195
+ if (statusCode >= 500 && statusCode <= 599) {
196
+ return "dependency-failure";
197
+ }
198
+ return "unexpected";
199
+ };
200
+ var validateLighthouseConnectivity = async (configuration, dependencies, requestInit) => {
201
+ const resolvedEndpoint = await resolveConnectionEndpoint(configuration);
202
+ if (!resolvedEndpoint.isValid) {
203
+ return resolvedEndpoint.result;
204
+ }
205
+ const endpoint = resolvedEndpoint.endpoint;
206
+ try {
207
+ const response = await dependencies.fetch(
208
+ endpoint.healthCheckUrl,
209
+ requestInit
210
+ );
211
+ if (response.ok) {
212
+ const serverVersion = (await response.text()).trim();
213
+ return {
214
+ category: "success",
215
+ endpoint,
216
+ serverVersion
217
+ };
218
+ }
219
+ return {
220
+ category: getFailureCategoryForStatus(response.status),
221
+ reason: `Connectivity check failed with status ${response.status}.`,
222
+ statusCode: response.status,
223
+ endpoint
224
+ };
225
+ } catch (error) {
226
+ const message = error instanceof Error ? error.message : "Connectivity request failed.";
227
+ return {
228
+ category: "unreachable",
229
+ reason: message,
230
+ endpoint
231
+ };
232
+ }
233
+ };
234
+ var getTrimmedValue = (value) => {
235
+ if (value === void 0) {
236
+ return void 0;
237
+ }
238
+ const trimmed = value.trim();
239
+ if (trimmed.length === 0) {
240
+ return void 0;
241
+ }
242
+ return trimmed;
243
+ };
244
+ var getAuthFromOverrides = (overrides) => {
245
+ const apiKey = getTrimmedValue(overrides?.apiKey);
246
+ const bearerToken = getTrimmedValue(overrides?.bearerToken);
247
+ if (apiKey !== void 0 && bearerToken !== void 0) {
248
+ throw new Error("Specify either --api-key or --bearer-token, not both.");
249
+ }
250
+ if (apiKey !== void 0) {
251
+ return {
252
+ kind: "api-key",
253
+ value: apiKey,
254
+ headerName: getTrimmedValue(overrides?.apiKeyHeaderName)
255
+ };
256
+ }
257
+ if (bearerToken !== void 0) {
258
+ return {
259
+ kind: "bearer-token",
260
+ token: bearerToken
261
+ };
262
+ }
263
+ return null;
264
+ };
265
+ var getStatusFromAuth = (auth, source) => {
266
+ if (auth.kind === "none") {
267
+ return {
268
+ isAuthenticated: false,
269
+ kind: "none",
270
+ source: "none"
271
+ };
272
+ }
273
+ return {
274
+ isAuthenticated: true,
275
+ kind: auth.kind,
276
+ source
277
+ };
278
+ };
279
+ var createLighthouseAuthContext = (store) => ({
280
+ resolveAuth: async (overrides) => {
281
+ const overrideAuth = getAuthFromOverrides(overrides);
282
+ if (overrideAuth !== null) {
283
+ return overrideAuth;
284
+ }
285
+ const storedAuth = await store.load();
286
+ if (storedAuth !== null) {
287
+ return storedAuth;
288
+ }
289
+ return {
290
+ kind: "none"
291
+ };
292
+ },
293
+ login: async (overrides) => {
294
+ const auth = getAuthFromOverrides(overrides);
295
+ if (auth === null) {
296
+ throw new Error("Provide --api-key or --bearer-token for auth login.");
297
+ }
298
+ await store.save(auth);
299
+ },
300
+ logout: async () => {
301
+ await store.clear();
302
+ },
303
+ getStatus: async (overrides) => {
304
+ const overrideAuth = getAuthFromOverrides(overrides);
305
+ if (overrideAuth !== null) {
306
+ return getStatusFromAuth(overrideAuth, "override");
307
+ }
308
+ const storedAuth = await store.load();
309
+ if (storedAuth !== null) {
310
+ return getStatusFromAuth(storedAuth, "stored");
311
+ }
312
+ return getStatusFromAuth({ kind: "none" }, "none");
313
+ }
314
+ });
315
+ var getAuthHeaders = (auth) => {
316
+ if (auth === void 0 || auth.kind === "none") {
317
+ return {};
318
+ }
319
+ if (auth.kind === "api-key") {
320
+ return {
321
+ [auth.headerName ?? "X-Api-Key"]: auth.value
322
+ };
323
+ }
324
+ return {
325
+ Authorization: `Bearer ${auth.token}`
326
+ };
327
+ };
328
+ var getRequestInit = (auth, requestOptions) => {
329
+ const authHeaders = getAuthHeaders(auth);
330
+ const hasBody = requestOptions?.body !== void 0;
331
+ const headers = hasBody ? {
332
+ ...authHeaders,
333
+ "content-type": "application/json"
334
+ } : authHeaders;
335
+ return {
336
+ method: requestOptions?.method,
337
+ headers,
338
+ body: hasBody ? JSON.stringify(requestOptions.body) : void 0
339
+ };
340
+ };
341
+ var getFetchDependency = (dependencies) => {
342
+ if (dependencies.fetch !== void 0) {
343
+ return dependencies.fetch;
344
+ }
345
+ return async (url, init) => {
346
+ const response = await fetch(url, init);
347
+ return response;
348
+ };
349
+ };
350
+ var getErrorResult = (error) => ({
351
+ ok: false,
352
+ error
353
+ });
354
+ var getErrorFromConnectivityResult = (result) => ({
355
+ category: result.category,
356
+ reason: result.reason,
357
+ statusCode: result.statusCode
358
+ });
359
+ var toApiError = (statusCode, reason) => ({
360
+ category: getFailureCategoryForStatus(statusCode),
361
+ reason,
362
+ statusCode
363
+ });
364
+ var requestText = async (configuration, dependencies, route) => {
365
+ const fetchDependency = getFetchDependency(dependencies);
366
+ const connectivityResult = await validateLighthouseConnectivity(
367
+ configuration.connection,
368
+ {
369
+ fetch: fetchDependency
370
+ },
371
+ getRequestInit(configuration.auth)
372
+ );
373
+ if (connectivityResult.category !== "success") {
374
+ return getErrorResult(getErrorFromConnectivityResult(connectivityResult));
375
+ }
376
+ try {
377
+ const response = await fetchDependency(
378
+ `${connectivityResult.endpoint.apiBaseUrl}${route}`,
379
+ getRequestInit(configuration.auth)
380
+ );
381
+ if (!response.ok) {
382
+ return getErrorResult(
383
+ toApiError(
384
+ response.status,
385
+ `Request failed with status ${response.status}.`
386
+ )
387
+ );
388
+ }
389
+ return {
390
+ ok: true,
391
+ value: (await response.text()).trim()
392
+ };
393
+ } catch (error) {
394
+ const reason = error instanceof Error ? error.message : "Request execution failed.";
395
+ return getErrorResult({
396
+ category: "unreachable",
397
+ reason
398
+ });
399
+ }
400
+ };
401
+ var requestJson = async (configuration, dependencies, route, requestOptions) => {
402
+ const fetchDependency = getFetchDependency(dependencies);
403
+ const connectivityResult = await validateLighthouseConnectivity(
404
+ configuration.connection,
405
+ {
406
+ fetch: fetchDependency
407
+ },
408
+ getRequestInit(configuration.auth, {
409
+ method: "GET"
410
+ })
411
+ );
412
+ if (connectivityResult.category !== "success") {
413
+ return getErrorResult(getErrorFromConnectivityResult(connectivityResult));
414
+ }
415
+ try {
416
+ const response = await fetchDependency(
417
+ `${connectivityResult.endpoint.apiBaseUrl}${route}`,
418
+ getRequestInit(configuration.auth, requestOptions)
419
+ );
420
+ if (!response.ok) {
421
+ return getErrorResult(
422
+ toApiError(
423
+ response.status,
424
+ `Request failed with status ${response.status}.`
425
+ )
426
+ );
427
+ }
428
+ const value = response.json !== void 0 ? await response.json() : JSON.parse(await response.text());
429
+ return {
430
+ ok: true,
431
+ value
432
+ };
433
+ } catch (error) {
434
+ const reason = error instanceof Error ? error.message : "Request execution failed.";
435
+ return getErrorResult({
436
+ category: "unreachable",
437
+ reason
438
+ });
439
+ }
440
+ };
441
+ var requestNoContent = async (configuration, dependencies, route, requestOptions) => {
442
+ const fetchDependency = getFetchDependency(dependencies);
443
+ const connectivityResult = await validateLighthouseConnectivity(
444
+ configuration.connection,
445
+ {
446
+ fetch: fetchDependency
447
+ },
448
+ getRequestInit(configuration.auth, {
449
+ method: "GET"
450
+ })
451
+ );
452
+ if (connectivityResult.category !== "success") {
453
+ return getErrorResult(getErrorFromConnectivityResult(connectivityResult));
454
+ }
455
+ try {
456
+ const response = await fetchDependency(
457
+ `${connectivityResult.endpoint.apiBaseUrl}${route}`,
458
+ getRequestInit(configuration.auth, requestOptions)
459
+ );
460
+ if (!response.ok) {
461
+ return getErrorResult(
462
+ toApiError(
463
+ response.status,
464
+ `Request failed with status ${response.status}.`
465
+ )
466
+ );
467
+ }
468
+ return {
469
+ ok: true,
470
+ value: void 0
471
+ };
472
+ } catch (error) {
473
+ const reason = error instanceof Error ? error.message : "Request execution failed.";
474
+ return getErrorResult({
475
+ category: "unreachable",
476
+ reason
477
+ });
478
+ }
479
+ };
480
+ var createLighthouseClient = (configuration, dependencies = {}) => ({
481
+ checkConnectivity: async () => {
482
+ const fetchDependency = getFetchDependency(dependencies);
483
+ return validateLighthouseConnectivity(
484
+ configuration.connection,
485
+ {
486
+ fetch: fetchDependency
487
+ },
488
+ getRequestInit(configuration.auth)
489
+ );
490
+ },
491
+ getVersion: async () => requestText(configuration, dependencies, "/v1/version"),
492
+ listWorkTrackingConnections: async () => requestJson(
493
+ configuration,
494
+ dependencies,
495
+ "/v1/worktrackingsystemconnections",
496
+ {
497
+ method: "GET"
498
+ }
499
+ ),
500
+ getWorkTrackingConnection: async (connectionId) => requestJson(
501
+ configuration,
502
+ dependencies,
503
+ `/v1/worktrackingsystemconnections/${connectionId}`,
504
+ {
505
+ method: "GET"
506
+ }
507
+ ),
508
+ createWorkTrackingConnection: async (payload) => requestJson(
509
+ configuration,
510
+ dependencies,
511
+ "/v1/worktrackingsystemconnections",
512
+ {
513
+ method: "POST",
514
+ body: payload
515
+ }
516
+ ),
517
+ updateWorkTrackingConnection: async (connectionId, payload) => requestJson(
518
+ configuration,
519
+ dependencies,
520
+ `/v1/worktrackingsystemconnections/${connectionId}`,
521
+ {
522
+ method: "PUT",
523
+ body: payload
524
+ }
525
+ ),
526
+ deleteWorkTrackingConnection: async (connectionId) => requestNoContent(
527
+ configuration,
528
+ dependencies,
529
+ `/v1/worktrackingsystemconnections/${connectionId}`,
530
+ {
531
+ method: "DELETE"
532
+ }
533
+ ),
534
+ listTeams: async () => requestJson(configuration, dependencies, "/v1/teams", {
535
+ method: "GET"
536
+ }),
537
+ getTeam: async (teamId) => requestJson(configuration, dependencies, `/v1/teams/${teamId}`, {
538
+ method: "GET"
539
+ }),
540
+ createTeam: async (payload) => requestJson(configuration, dependencies, "/v1/teams", {
541
+ method: "POST",
542
+ body: payload
543
+ }),
544
+ updateTeam: async (teamId, payload) => requestJson(configuration, dependencies, `/v1/teams/${teamId}`, {
545
+ method: "PUT",
546
+ body: payload
547
+ }),
548
+ deleteTeam: async (teamId) => requestNoContent(configuration, dependencies, `/v1/teams/${teamId}`, {
549
+ method: "DELETE"
550
+ }),
551
+ refreshTeam: async (teamId) => requestNoContent(configuration, dependencies, `/v1/teams/${teamId}`, {
552
+ method: "POST"
553
+ }),
554
+ listPortfolios: async () => requestJson(
555
+ configuration,
556
+ dependencies,
557
+ "/v1/portfolios",
558
+ {
559
+ method: "GET"
560
+ }
561
+ ),
562
+ getPortfolio: async (portfolioId) => requestJson(
563
+ configuration,
564
+ dependencies,
565
+ `/v1/portfolios/${portfolioId}`,
566
+ {
567
+ method: "GET"
568
+ }
569
+ ),
570
+ createPortfolio: async (payload) => requestJson(configuration, dependencies, "/v1/portfolios", {
571
+ method: "POST",
572
+ body: payload
573
+ }),
574
+ updatePortfolio: async (portfolioId, payload) => requestJson(
575
+ configuration,
576
+ dependencies,
577
+ `/v1/portfolios/${portfolioId}`,
578
+ {
579
+ method: "PUT",
580
+ body: payload
581
+ }
582
+ ),
583
+ deletePortfolio: async (portfolioId) => requestNoContent(
584
+ configuration,
585
+ dependencies,
586
+ `/v1/portfolios/${portfolioId}`,
587
+ {
588
+ method: "DELETE"
589
+ }
590
+ ),
591
+ refreshPortfolio: async (portfolioId) => requestNoContent(
592
+ configuration,
593
+ dependencies,
594
+ `/v1/portfolios/${portfolioId}/refresh`,
595
+ {
596
+ method: "POST"
597
+ }
598
+ )
599
+ });
600
+ // Annotate the CommonJS export names for ESM import in node:
601
+ 0 && (module.exports = {
602
+ createLighthouseAuthContext,
603
+ createLighthouseClient,
604
+ getClientPackageContract,
605
+ parseStandaloneDiscoveryContract,
606
+ validateLighthouseConnectivity
607
+ });