@campfire-interactive/bom-client 0.1.1
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/client.d.ts +29 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +37 -0
- package/dist/client.js.map +1 -0
- package/dist/costBreakdown.d.ts +28 -0
- package/dist/costBreakdown.d.ts.map +1 -0
- package/dist/costBreakdown.js +41 -0
- package/dist/costBreakdown.js.map +1 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +39 -0
- package/dist/errors.js.map +1 -0
- package/dist/http.d.ts +25 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +88 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/internal.d.ts +15 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +39 -0
- package/dist/internal.js.map +1 -0
- package/dist/mock.d.ts +42 -0
- package/dist/mock.d.ts.map +1 -0
- package/dist/mock.js +79 -0
- package/dist/mock.js.map +1 -0
- package/dist/routing.d.ts +29 -0
- package/dist/routing.d.ts.map +1 -0
- package/dist/routing.js +46 -0
- package/dist/routing.js.map +1 -0
- package/dist/types.d.ts +182 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -0
- package/package.json +31 -0
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { CostBreakdownResource } from './costBreakdown';
|
|
2
|
+
import { RoutingResource } from './routing';
|
|
3
|
+
import type { BomClientConfig } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* Client for the BOM read contract — the costed and routed view of a part,
|
|
6
|
+
* resolved from a part reference the caller already has.
|
|
7
|
+
*
|
|
8
|
+
* const bom = new BomClient({
|
|
9
|
+
* baseUrl: process.env.BOM_BASE_URL!,
|
|
10
|
+
* getToken: () => mintSystemTokenForTenant(tenantId, 'bom'),
|
|
11
|
+
* });
|
|
12
|
+
* const cost = await bom.costBreakdown.get({ partNumber, programId });
|
|
13
|
+
* const routing = await bom.routing.get({ partNumber, programId });
|
|
14
|
+
*
|
|
15
|
+
* The contract's one rule: **a consumer addresses parts, never BOMs.** It has
|
|
16
|
+
* no bomId, no version, and no notion of the EBOM/CBOM/MBOM lenses; bom
|
|
17
|
+
* resolves those and reports what it picked in `provenance`. Display that —
|
|
18
|
+
* resolution is a live guess against the current Active version, so the same
|
|
19
|
+
* call can answer differently after the next import.
|
|
20
|
+
*
|
|
21
|
+
* Consumers: OMSF's quote viewer (read-only display of BOM data beside a
|
|
22
|
+
* quote), and CPQ once its BOM handling consolidates into the bom app.
|
|
23
|
+
*/
|
|
24
|
+
export declare class BomClient {
|
|
25
|
+
readonly costBreakdown: CostBreakdownResource;
|
|
26
|
+
readonly routing: RoutingResource;
|
|
27
|
+
constructor(config: BomClientConfig);
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,SAAS;IACpB,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC;IAC9C,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;gBAEtB,MAAM,EAAE,eAAe;CAKpC"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BomClient = void 0;
|
|
4
|
+
const http_1 = require("./http");
|
|
5
|
+
const costBreakdown_1 = require("./costBreakdown");
|
|
6
|
+
const routing_1 = require("./routing");
|
|
7
|
+
/**
|
|
8
|
+
* Client for the BOM read contract — the costed and routed view of a part,
|
|
9
|
+
* resolved from a part reference the caller already has.
|
|
10
|
+
*
|
|
11
|
+
* const bom = new BomClient({
|
|
12
|
+
* baseUrl: process.env.BOM_BASE_URL!,
|
|
13
|
+
* getToken: () => mintSystemTokenForTenant(tenantId, 'bom'),
|
|
14
|
+
* });
|
|
15
|
+
* const cost = await bom.costBreakdown.get({ partNumber, programId });
|
|
16
|
+
* const routing = await bom.routing.get({ partNumber, programId });
|
|
17
|
+
*
|
|
18
|
+
* The contract's one rule: **a consumer addresses parts, never BOMs.** It has
|
|
19
|
+
* no bomId, no version, and no notion of the EBOM/CBOM/MBOM lenses; bom
|
|
20
|
+
* resolves those and reports what it picked in `provenance`. Display that —
|
|
21
|
+
* resolution is a live guess against the current Active version, so the same
|
|
22
|
+
* call can answer differently after the next import.
|
|
23
|
+
*
|
|
24
|
+
* Consumers: OMSF's quote viewer (read-only display of BOM data beside a
|
|
25
|
+
* quote), and CPQ once its BOM handling consolidates into the bom app.
|
|
26
|
+
*/
|
|
27
|
+
class BomClient {
|
|
28
|
+
costBreakdown;
|
|
29
|
+
routing;
|
|
30
|
+
constructor(config) {
|
|
31
|
+
const http = new http_1.HttpTransport(config);
|
|
32
|
+
this.costBreakdown = new costBreakdown_1.CostBreakdownResource(http);
|
|
33
|
+
this.routing = new routing_1.RoutingResource(http);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.BomClient = BomClient;
|
|
37
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":";;;AAAA,iCAAuC;AACvC,mDAAwD;AACxD,uCAA4C;AAG5C;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAa,SAAS;IACX,aAAa,CAAwB;IACrC,OAAO,CAAkB;IAElC,YAAY,MAAuB;QACjC,MAAM,IAAI,GAAG,IAAI,oBAAa,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,CAAC,aAAa,GAAG,IAAI,qCAAqB,CAAC,IAAI,CAAC,CAAC;QACrD,IAAI,CAAC,OAAO,GAAG,IAAI,yBAAe,CAAC,IAAI,CAAC,CAAC;IAC3C,CAAC;CACF;AATD,8BASC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { HttpTransport } from './http';
|
|
2
|
+
import type { CostBreakdownResult, PartLookup } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* The costed view of one part: bom's own `CostModel` document plus the
|
|
5
|
+
* provenance saying which BOM, version and lens answered.
|
|
6
|
+
*
|
|
7
|
+
* This is a READ surface for DISPLAY. It does not produce a quote's cost — if
|
|
8
|
+
* an app needs a number to commit to a quote, that goes through its costing
|
|
9
|
+
* engine, not through here.
|
|
10
|
+
*/
|
|
11
|
+
export declare class CostBreakdownResource {
|
|
12
|
+
private readonly http;
|
|
13
|
+
constructor(http: HttpTransport);
|
|
14
|
+
/**
|
|
15
|
+
* Resolve a part reference to its cost breakdown.
|
|
16
|
+
*
|
|
17
|
+
* Never throws for "no BOM has this part" — that is a `resolved: false`
|
|
18
|
+
* result a viewer renders as a state. Narrow with `programId` (an OMSF quote
|
|
19
|
+
* has one) when a part number recurs across projects; without it, such a part
|
|
20
|
+
* comes back `reason: 'ambiguous'` with the candidate projects listed.
|
|
21
|
+
*
|
|
22
|
+
* const res = await bom.costBreakdown.get({ partNumber, programId });
|
|
23
|
+
* if (!res.resolved) return renderEmpty(res.reason);
|
|
24
|
+
* renderTabs(res.costModel, res.provenance);
|
|
25
|
+
*/
|
|
26
|
+
get(lookup: PartLookup): Promise<CostBreakdownResult>;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=costBreakdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"costBreakdown.d.ts","sourceRoot":"","sources":["../src/costBreakdown.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,KAAK,EAAe,mBAAmB,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAG5E;;;;;;;GAOG;AACH,qBAAa,qBAAqB;IACpB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,aAAa;IAEhD;;;;;;;;;;;OAWG;IACG,GAAG,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,mBAAmB,CAAC;CAS5D"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CostBreakdownResource = void 0;
|
|
4
|
+
const internal_1 = require("./internal");
|
|
5
|
+
/**
|
|
6
|
+
* The costed view of one part: bom's own `CostModel` document plus the
|
|
7
|
+
* provenance saying which BOM, version and lens answered.
|
|
8
|
+
*
|
|
9
|
+
* This is a READ surface for DISPLAY. It does not produce a quote's cost — if
|
|
10
|
+
* an app needs a number to commit to a quote, that goes through its costing
|
|
11
|
+
* engine, not through here.
|
|
12
|
+
*/
|
|
13
|
+
class CostBreakdownResource {
|
|
14
|
+
http;
|
|
15
|
+
constructor(http) {
|
|
16
|
+
this.http = http;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Resolve a part reference to its cost breakdown.
|
|
20
|
+
*
|
|
21
|
+
* Never throws for "no BOM has this part" — that is a `resolved: false`
|
|
22
|
+
* result a viewer renders as a state. Narrow with `programId` (an OMSF quote
|
|
23
|
+
* has one) when a part number recurs across projects; without it, such a part
|
|
24
|
+
* comes back `reason: 'ambiguous'` with the candidate projects listed.
|
|
25
|
+
*
|
|
26
|
+
* const res = await bom.costBreakdown.get({ partNumber, programId });
|
|
27
|
+
* if (!res.resolved) return renderEmpty(res.reason);
|
|
28
|
+
* renderTabs(res.costModel, res.provenance);
|
|
29
|
+
*/
|
|
30
|
+
async get(lookup) {
|
|
31
|
+
(0, internal_1.assertPartRef)(lookup);
|
|
32
|
+
const res = await this.http.request({
|
|
33
|
+
method: 'GET',
|
|
34
|
+
path: '/api/v1/cost-breakdown',
|
|
35
|
+
query: (0, internal_1.lookupQuery)(lookup),
|
|
36
|
+
});
|
|
37
|
+
return res.data;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.CostBreakdownResource = CostBreakdownResource;
|
|
41
|
+
//# sourceMappingURL=costBreakdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"costBreakdown.js","sourceRoot":"","sources":["../src/costBreakdown.ts"],"names":[],"mappings":";;;AAEA,yCAAwD;AAExD;;;;;;;GAOG;AACH,MAAa,qBAAqB;IACH;IAA7B,YAA6B,IAAmB;QAAnB,SAAI,GAAJ,IAAI,CAAe;IAAG,CAAC;IAEpD;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,GAAG,CAAC,MAAkB;QAC1B,IAAA,wBAAa,EAAC,MAAM,CAAC,CAAC;QACtB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAmC;YACpE,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,wBAAwB;YAC9B,KAAK,EAAE,IAAA,sBAAW,EAAC,MAAM,CAAC;SAC3B,CAAC,CAAC;QACH,OAAO,GAAG,CAAC,IAAI,CAAC;IAClB,CAAC;CACF;AAxBD,sDAwBC"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare class BomError extends Error {
|
|
2
|
+
readonly statusCode: number;
|
|
3
|
+
readonly code: string;
|
|
4
|
+
readonly requestId: string | undefined;
|
|
5
|
+
constructor(message: string, statusCode: number, code: string, requestId?: string);
|
|
6
|
+
}
|
|
7
|
+
export declare class NotFoundError extends BomError {
|
|
8
|
+
constructor(message?: string, requestId?: string);
|
|
9
|
+
}
|
|
10
|
+
export declare class UnauthorizedError extends BomError {
|
|
11
|
+
constructor(message?: string, requestId?: string);
|
|
12
|
+
}
|
|
13
|
+
export declare class ForbiddenError extends BomError {
|
|
14
|
+
constructor(message?: string, requestId?: string);
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAKA,qBAAa,QAAS,SAAQ,KAAK;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;gBAE3B,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM;CAOlF;AAED,qBAAa,aAAc,SAAQ,QAAQ;gBAC7B,OAAO,SAAuB,EAAE,SAAS,CAAC,EAAE,MAAM;CAG/D;AAED,qBAAa,iBAAkB,SAAQ,QAAQ;gBACjC,OAAO,SAAiB,EAAE,SAAS,CAAC,EAAE,MAAM;CAGzD;AAED,qBAAa,cAAe,SAAQ,QAAQ;gBAC9B,OAAO,SAAc,EAAE,SAAS,CAAC,EAAE,MAAM;CAGtD"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// =============================================================================
|
|
3
|
+
// Client-side error hierarchy — mirrors bom's AppError shape (and the
|
|
4
|
+
// VI / master-data / dms clients).
|
|
5
|
+
// =============================================================================
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.ForbiddenError = exports.UnauthorizedError = exports.NotFoundError = exports.BomError = void 0;
|
|
8
|
+
class BomError extends Error {
|
|
9
|
+
statusCode;
|
|
10
|
+
code;
|
|
11
|
+
requestId;
|
|
12
|
+
constructor(message, statusCode, code, requestId) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = this.constructor.name;
|
|
15
|
+
this.statusCode = statusCode;
|
|
16
|
+
this.code = code;
|
|
17
|
+
this.requestId = requestId;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.BomError = BomError;
|
|
21
|
+
class NotFoundError extends BomError {
|
|
22
|
+
constructor(message = 'Resource not found', requestId) {
|
|
23
|
+
super(message, 404, 'NOT_FOUND', requestId);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.NotFoundError = NotFoundError;
|
|
27
|
+
class UnauthorizedError extends BomError {
|
|
28
|
+
constructor(message = 'Unauthorized', requestId) {
|
|
29
|
+
super(message, 401, 'UNAUTHORIZED', requestId);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.UnauthorizedError = UnauthorizedError;
|
|
33
|
+
class ForbiddenError extends BomError {
|
|
34
|
+
constructor(message = 'Forbidden', requestId) {
|
|
35
|
+
super(message, 403, 'FORBIDDEN', requestId);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
exports.ForbiddenError = ForbiddenError;
|
|
39
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":";AAAA,gFAAgF;AAChF,sEAAsE;AACtE,mCAAmC;AACnC,gFAAgF;;;AAEhF,MAAa,QAAS,SAAQ,KAAK;IACxB,UAAU,CAAS;IACnB,IAAI,CAAS;IACb,SAAS,CAAqB;IAEvC,YAAY,OAAe,EAAE,UAAkB,EAAE,IAAY,EAAE,SAAkB;QAC/E,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;QAClC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;CACF;AAZD,4BAYC;AAED,MAAa,aAAc,SAAQ,QAAQ;IACzC,YAAY,OAAO,GAAG,oBAAoB,EAAE,SAAkB;QAC5D,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,SAAS,CAAC,CAAC;IAC9C,CAAC;CACF;AAJD,sCAIC;AAED,MAAa,iBAAkB,SAAQ,QAAQ;IAC7C,YAAY,OAAO,GAAG,cAAc,EAAE,SAAkB;QACtD,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,cAAc,EAAE,SAAS,CAAC,CAAC;IACjD,CAAC;CACF;AAJD,8CAIC;AAED,MAAa,cAAe,SAAQ,QAAQ;IAC1C,YAAY,OAAO,GAAG,WAAW,EAAE,SAAkB;QACnD,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,SAAS,CAAC,CAAC;IAC9C,CAAC;CACF;AAJD,wCAIC"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { BomClientConfig } from './types';
|
|
2
|
+
interface HttpRequestOptions {
|
|
3
|
+
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
4
|
+
path: string;
|
|
5
|
+
body?: unknown;
|
|
6
|
+
query?: Record<string, string | undefined>;
|
|
7
|
+
}
|
|
8
|
+
/** Auth-aware fetch transport. Mirrors the integrations / VI / master-data
|
|
9
|
+
* clients: `{ baseUrl, getToken, timeout }`, bearer injection, timeout via
|
|
10
|
+
* AbortController, and status→error mapping. */
|
|
11
|
+
export declare class HttpTransport {
|
|
12
|
+
private readonly baseUrl;
|
|
13
|
+
private readonly getToken;
|
|
14
|
+
private readonly timeout;
|
|
15
|
+
constructor(config: BomClientConfig);
|
|
16
|
+
request<T>(options: HttpRequestOptions): Promise<T>;
|
|
17
|
+
private buildUrl;
|
|
18
|
+
/** bom's error envelope is `{ error: string, code: string }` (see
|
|
19
|
+
* core/apiService.ts) — flatter than the integrations one, and with no
|
|
20
|
+
* requestId on the error path. Object-shaped `error` is tolerated in case
|
|
21
|
+
* that changes. */
|
|
22
|
+
private throwForStatus;
|
|
23
|
+
}
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAK/C,UAAU,kBAAkB;IAC1B,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;CAC5C;AAED;;iDAEiD;AACjD,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiC;IAC1D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAErB,MAAM,EAAE,eAAe;IAM7B,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,CAAC,CAAC;IAoCzD,OAAO,CAAC,QAAQ;IAUhB;;;wBAGoB;IACpB,OAAO,CAAC,cAAc;CAwBvB"}
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HttpTransport = void 0;
|
|
4
|
+
const errors_1 = require("./errors");
|
|
5
|
+
const DEFAULT_TIMEOUT = 30_000;
|
|
6
|
+
/** Auth-aware fetch transport. Mirrors the integrations / VI / master-data
|
|
7
|
+
* clients: `{ baseUrl, getToken, timeout }`, bearer injection, timeout via
|
|
8
|
+
* AbortController, and status→error mapping. */
|
|
9
|
+
class HttpTransport {
|
|
10
|
+
baseUrl;
|
|
11
|
+
getToken;
|
|
12
|
+
timeout;
|
|
13
|
+
constructor(config) {
|
|
14
|
+
this.baseUrl = config.baseUrl.replace(/\/+$/, '');
|
|
15
|
+
this.getToken = config.getToken;
|
|
16
|
+
this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
|
|
17
|
+
}
|
|
18
|
+
async request(options) {
|
|
19
|
+
const { method, path, body, query } = options;
|
|
20
|
+
const url = this.buildUrl(path, query);
|
|
21
|
+
const token = await this.getToken();
|
|
22
|
+
const headers = {
|
|
23
|
+
Authorization: `Bearer ${token}`,
|
|
24
|
+
'Content-Type': 'application/json',
|
|
25
|
+
};
|
|
26
|
+
const controller = new AbortController();
|
|
27
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
28
|
+
let response;
|
|
29
|
+
try {
|
|
30
|
+
response = await fetch(url, {
|
|
31
|
+
method,
|
|
32
|
+
headers,
|
|
33
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
34
|
+
signal: controller.signal,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
if (error.name === 'AbortError') {
|
|
39
|
+
throw new errors_1.BomError('Request timed out', 0, 'TIMEOUT');
|
|
40
|
+
}
|
|
41
|
+
throw new errors_1.BomError(`Network error: ${error.message}`, 0, 'NETWORK_ERROR');
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
clearTimeout(timeoutId);
|
|
45
|
+
}
|
|
46
|
+
if (response.status === 204)
|
|
47
|
+
return undefined;
|
|
48
|
+
const responseBody = await response.json().catch(() => undefined);
|
|
49
|
+
if (!response.ok)
|
|
50
|
+
this.throwForStatus(response.status, responseBody);
|
|
51
|
+
return responseBody;
|
|
52
|
+
}
|
|
53
|
+
buildUrl(path, query) {
|
|
54
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
55
|
+
if (query) {
|
|
56
|
+
for (const [key, value] of Object.entries(query)) {
|
|
57
|
+
if (value !== undefined)
|
|
58
|
+
url.searchParams.set(key, value);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return url.toString();
|
|
62
|
+
}
|
|
63
|
+
/** bom's error envelope is `{ error: string, code: string }` (see
|
|
64
|
+
* core/apiService.ts) — flatter than the integrations one, and with no
|
|
65
|
+
* requestId on the error path. Object-shaped `error` is tolerated in case
|
|
66
|
+
* that changes. */
|
|
67
|
+
throwForStatus(status, body) {
|
|
68
|
+
const errorBody = body;
|
|
69
|
+
const rawError = errorBody?.error;
|
|
70
|
+
const message = typeof rawError === 'string' ? rawError : rawError?.message || `Request failed with status ${status}`;
|
|
71
|
+
const code = errorBody?.code ||
|
|
72
|
+
(typeof rawError === 'object' ? rawError?.code : undefined) ||
|
|
73
|
+
'UNKNOWN_ERROR';
|
|
74
|
+
const requestId = errorBody?.meta?.requestId;
|
|
75
|
+
switch (status) {
|
|
76
|
+
case 404:
|
|
77
|
+
throw new errors_1.NotFoundError(message, requestId);
|
|
78
|
+
case 401:
|
|
79
|
+
throw new errors_1.UnauthorizedError(message, requestId);
|
|
80
|
+
case 403:
|
|
81
|
+
throw new errors_1.ForbiddenError(message, requestId);
|
|
82
|
+
default:
|
|
83
|
+
throw new errors_1.BomError(message, status, code, requestId);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
exports.HttpTransport = HttpTransport;
|
|
88
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { BomClient } from './client';
|
|
2
|
+
export { MockBomClient } from './mock';
|
|
3
|
+
export { CostBreakdownResource } from './costBreakdown';
|
|
4
|
+
export { RoutingResource } from './routing';
|
|
5
|
+
export { BomError, NotFoundError, UnauthorizedError, ForbiddenError } from './errors';
|
|
6
|
+
export type { BomClientConfig, BomEnvelope, BomTypeName, SourcingType, BomProvenance, AmbiguousProject, PartLookup, MakeBuy, CostSource, CostBasis, RateSet, CostItem, CostCategory, CostModelMeta, CostModel, CostBreakdown, CostBreakdownResult, RoutingStep, Routing, RoutingResult, Unresolved, } from './types';
|
|
7
|
+
export type { MockSeed } from './mock';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AACtF,YAAY,EACV,eAAe,EACf,WAAW,EACX,WAAW,EACX,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,UAAU,EACV,OAAO,EACP,UAAU,EACV,SAAS,EACT,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,aAAa,EACb,SAAS,EACT,aAAa,EACb,mBAAmB,EACnB,WAAW,EACX,OAAO,EACP,aAAa,EACb,UAAU,GACX,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ForbiddenError = exports.UnauthorizedError = exports.NotFoundError = exports.BomError = exports.RoutingResource = exports.CostBreakdownResource = exports.MockBomClient = exports.BomClient = void 0;
|
|
4
|
+
var client_1 = require("./client");
|
|
5
|
+
Object.defineProperty(exports, "BomClient", { enumerable: true, get: function () { return client_1.BomClient; } });
|
|
6
|
+
var mock_1 = require("./mock");
|
|
7
|
+
Object.defineProperty(exports, "MockBomClient", { enumerable: true, get: function () { return mock_1.MockBomClient; } });
|
|
8
|
+
var costBreakdown_1 = require("./costBreakdown");
|
|
9
|
+
Object.defineProperty(exports, "CostBreakdownResource", { enumerable: true, get: function () { return costBreakdown_1.CostBreakdownResource; } });
|
|
10
|
+
var routing_1 = require("./routing");
|
|
11
|
+
Object.defineProperty(exports, "RoutingResource", { enumerable: true, get: function () { return routing_1.RoutingResource; } });
|
|
12
|
+
var errors_1 = require("./errors");
|
|
13
|
+
Object.defineProperty(exports, "BomError", { enumerable: true, get: function () { return errors_1.BomError; } });
|
|
14
|
+
Object.defineProperty(exports, "NotFoundError", { enumerable: true, get: function () { return errors_1.NotFoundError; } });
|
|
15
|
+
Object.defineProperty(exports, "UnauthorizedError", { enumerable: true, get: function () { return errors_1.UnauthorizedError; } });
|
|
16
|
+
Object.defineProperty(exports, "ForbiddenError", { enumerable: true, get: function () { return errors_1.ForbiddenError; } });
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,mCAAqC;AAA5B,mGAAA,SAAS,OAAA;AAClB,+BAAuC;AAA9B,qGAAA,aAAa,OAAA;AACtB,iDAAwD;AAA/C,sHAAA,qBAAqB,OAAA;AAC9B,qCAA4C;AAAnC,0GAAA,eAAe,OAAA;AACxB,mCAAsF;AAA7E,kGAAA,QAAQ,OAAA;AAAE,uGAAA,aAAa,OAAA;AAAE,2GAAA,iBAAiB,OAAA;AAAE,wGAAA,cAAc,OAAA"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { PartLookup } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Prisma `Decimal` columns serialize as STRINGS. `ManufacturingStep.yieldPct`
|
|
4
|
+
* is one, so an uncoerced consumer doing `yieldPct * qty` gets a silent
|
|
5
|
+
* `NaN` — or worse, `"99.8" + 1 === "99.81"`. Coerce at the boundary so the
|
|
6
|
+
* row types are honest.
|
|
7
|
+
*/
|
|
8
|
+
export declare function toNum(v: unknown): number | null;
|
|
9
|
+
/** Part reference + narrowing → query params. Blank strings are dropped so a
|
|
10
|
+
* caller passing `programId: ''` doesn't accidentally filter on empty. */
|
|
11
|
+
export declare function lookupQuery(lookup: PartLookup): Record<string, string | undefined>;
|
|
12
|
+
/** Guard against a caller passing neither identifier — the server answers 400,
|
|
13
|
+
* but failing here costs no round trip and gives a better message. */
|
|
14
|
+
export declare function assertPartRef(lookup: PartLookup): void;
|
|
15
|
+
//# sourceMappingURL=internal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAI/C;AAED;2EAC2E;AAC3E,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAWlF;AAED;uEACuE;AACvE,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAItD"}
|
package/dist/internal.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toNum = toNum;
|
|
4
|
+
exports.lookupQuery = lookupQuery;
|
|
5
|
+
exports.assertPartRef = assertPartRef;
|
|
6
|
+
/**
|
|
7
|
+
* Prisma `Decimal` columns serialize as STRINGS. `ManufacturingStep.yieldPct`
|
|
8
|
+
* is one, so an uncoerced consumer doing `yieldPct * qty` gets a silent
|
|
9
|
+
* `NaN` — or worse, `"99.8" + 1 === "99.81"`. Coerce at the boundary so the
|
|
10
|
+
* row types are honest.
|
|
11
|
+
*/
|
|
12
|
+
function toNum(v) {
|
|
13
|
+
if (v == null)
|
|
14
|
+
return null;
|
|
15
|
+
const n = typeof v === 'number' ? v : Number(v);
|
|
16
|
+
return Number.isFinite(n) ? n : null;
|
|
17
|
+
}
|
|
18
|
+
/** Part reference + narrowing → query params. Blank strings are dropped so a
|
|
19
|
+
* caller passing `programId: ''` doesn't accidentally filter on empty. */
|
|
20
|
+
function lookupQuery(lookup) {
|
|
21
|
+
const clean = (v) => {
|
|
22
|
+
const t = v?.trim();
|
|
23
|
+
return t ? t : undefined;
|
|
24
|
+
};
|
|
25
|
+
return {
|
|
26
|
+
partNumber: clean(lookup.partNumber),
|
|
27
|
+
partRevisionId: clean(lookup.partRevisionId),
|
|
28
|
+
projectId: clean(lookup.projectId),
|
|
29
|
+
programId: clean(lookup.programId),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** Guard against a caller passing neither identifier — the server answers 400,
|
|
33
|
+
* but failing here costs no round trip and gives a better message. */
|
|
34
|
+
function assertPartRef(lookup) {
|
|
35
|
+
if (!lookup.partNumber?.trim() && !lookup.partRevisionId?.trim()) {
|
|
36
|
+
throw new TypeError('bom-client: partNumber or partRevisionId is required');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=internal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal.js","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":";;AAQA,sBAIC;AAID,kCAWC;AAID,sCAIC;AAjCD;;;;;GAKG;AACH,SAAgB,KAAK,CAAC,CAAU;IAC9B,IAAI,CAAC,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IAC3B,MAAM,CAAC,GAAG,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAChD,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACvC,CAAC;AAED;2EAC2E;AAC3E,SAAgB,WAAW,CAAC,MAAkB;IAC5C,MAAM,KAAK,GAAG,CAAC,CAAU,EAAE,EAAE;QAC3B,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC;QACpB,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC3B,CAAC,CAAC;IACF,OAAO;QACL,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC;QACpC,cAAc,EAAE,KAAK,CAAC,MAAM,CAAC,cAAc,CAAC;QAC5C,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC;QAClC,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC;KACnC,CAAC;AACJ,CAAC;AAED;uEACuE;AACvE,SAAgB,aAAa,CAAC,MAAkB;IAC9C,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,IAAI,EAAE,EAAE,CAAC;QACjE,MAAM,IAAI,SAAS,CAAC,sDAAsD,CAAC,CAAC;IAC9E,CAAC;AACH,CAAC"}
|
package/dist/mock.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { CostBreakdown, CostBreakdownResult, PartLookup, Routing, RoutingResult } from './types';
|
|
2
|
+
/** Seed data for the mock client, keyed the way the real API resolves. */
|
|
3
|
+
export interface MockSeed {
|
|
4
|
+
/** Cost answers. Matched on partNumber (or partRevisionId when the seed row
|
|
5
|
+
* is addressed that way — see `partRevisionIds`). */
|
|
6
|
+
costBreakdowns?: CostBreakdown[];
|
|
7
|
+
routings?: Routing[];
|
|
8
|
+
/** partRevisionId → partNumber, so a consumer's test can exercise the
|
|
9
|
+
* canonical-part join without restating the seed. */
|
|
10
|
+
partRevisionIds?: Record<string, string>;
|
|
11
|
+
/** Part numbers that should answer `ambiguous` unless narrowed by
|
|
12
|
+
* projectId/programId — for testing the picker path. */
|
|
13
|
+
ambiguous?: string[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* In-memory stand-in mirroring the BomClient surface, for a consumer's tests.
|
|
17
|
+
*
|
|
18
|
+
* Reproduces the three answers that matter: resolved, `not-found`, and
|
|
19
|
+
* `ambiguous`-until-narrowed. A consumer whose tests only ever see the happy
|
|
20
|
+
* path will ship a viewer that renders nothing for the other two.
|
|
21
|
+
*/
|
|
22
|
+
export declare class MockBomClient {
|
|
23
|
+
private readonly _cost;
|
|
24
|
+
private readonly _routing;
|
|
25
|
+
private readonly _revisions;
|
|
26
|
+
private readonly _ambiguous;
|
|
27
|
+
constructor(seed?: MockSeed);
|
|
28
|
+
readonly costBreakdown: {
|
|
29
|
+
get: (lookup: PartLookup) => Promise<CostBreakdownResult>;
|
|
30
|
+
};
|
|
31
|
+
readonly routing: {
|
|
32
|
+
get: (lookup: PartLookup & {
|
|
33
|
+
plantId?: string;
|
|
34
|
+
}) => Promise<RoutingResult>;
|
|
35
|
+
};
|
|
36
|
+
private resolveRef;
|
|
37
|
+
/** Mirrors the server: ambiguity is only reported when the caller gave
|
|
38
|
+
* nothing to narrow by. */
|
|
39
|
+
private checkUnresolvable;
|
|
40
|
+
private notFound;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=mock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock.d.ts","sourceRoot":"","sources":["../src/mock.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,mBAAmB,EACnB,UAAU,EACV,OAAO,EACP,aAAa,EACd,MAAM,SAAS,CAAC;AAEjB,0EAA0E;AAC1E,MAAM,WAAW,QAAQ;IACvB;0DACsD;IACtD,cAAc,CAAC,EAAE,aAAa,EAAE,CAAC;IACjC,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;IACrB;0DACsD;IACtD,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC;6DACyD;IACzD,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;;;;;GAMG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkB;IACxC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAY;IACrC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyB;IACpD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAc;gBAE7B,IAAI,GAAE,QAAa;IAO/B,QAAQ,CAAC,aAAa;sBACA,UAAU,KAAG,OAAO,CAAC,mBAAmB,CAAC;MAO7D;IAEF,QAAQ,CAAC,OAAO;sBACM,UAAU,GAAG;YAAE,OAAO,CAAC,EAAE,MAAM,CAAA;SAAE,KAAG,OAAO,CAAC,aAAa,CAAC;MAS9E;IAEF,OAAO,CAAC,UAAU;IAMlB;gCAC4B;IAC5B,OAAO,CAAC,iBAAiB;IAezB,OAAO,CAAC,QAAQ;CAQjB"}
|
package/dist/mock.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MockBomClient = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* In-memory stand-in mirroring the BomClient surface, for a consumer's tests.
|
|
6
|
+
*
|
|
7
|
+
* Reproduces the three answers that matter: resolved, `not-found`, and
|
|
8
|
+
* `ambiguous`-until-narrowed. A consumer whose tests only ever see the happy
|
|
9
|
+
* path will ship a viewer that renders nothing for the other two.
|
|
10
|
+
*/
|
|
11
|
+
class MockBomClient {
|
|
12
|
+
_cost;
|
|
13
|
+
_routing;
|
|
14
|
+
_revisions;
|
|
15
|
+
_ambiguous;
|
|
16
|
+
constructor(seed = {}) {
|
|
17
|
+
this._cost = seed.costBreakdowns ?? [];
|
|
18
|
+
this._routing = seed.routings ?? [];
|
|
19
|
+
this._revisions = seed.partRevisionIds ?? {};
|
|
20
|
+
this._ambiguous = new Set(seed.ambiguous ?? []);
|
|
21
|
+
}
|
|
22
|
+
costBreakdown = {
|
|
23
|
+
get: async (lookup) => {
|
|
24
|
+
const partNumber = this.resolveRef(lookup);
|
|
25
|
+
const unresolved = this.checkUnresolvable(lookup, partNumber);
|
|
26
|
+
if (unresolved)
|
|
27
|
+
return unresolved;
|
|
28
|
+
const hit = this._cost.find((c) => c.partNumber === partNumber);
|
|
29
|
+
return hit ?? this.notFound(lookup);
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
routing = {
|
|
33
|
+
get: async (lookup) => {
|
|
34
|
+
const partNumber = this.resolveRef(lookup);
|
|
35
|
+
const unresolved = this.checkUnresolvable(lookup, partNumber);
|
|
36
|
+
if (unresolved)
|
|
37
|
+
return unresolved;
|
|
38
|
+
const hit = this._routing.find((r) => r.partNumber === partNumber);
|
|
39
|
+
if (!hit)
|
|
40
|
+
return this.notFound(lookup);
|
|
41
|
+
if (!lookup.plantId)
|
|
42
|
+
return hit;
|
|
43
|
+
return { ...hit, steps: hit.steps.filter((s) => s.plantId === lookup.plantId) };
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
resolveRef(lookup) {
|
|
47
|
+
if (lookup.partNumber?.trim())
|
|
48
|
+
return lookup.partNumber.trim();
|
|
49
|
+
const rev = lookup.partRevisionId?.trim();
|
|
50
|
+
return rev ? this._revisions[rev] : undefined;
|
|
51
|
+
}
|
|
52
|
+
/** Mirrors the server: ambiguity is only reported when the caller gave
|
|
53
|
+
* nothing to narrow by. */
|
|
54
|
+
checkUnresolvable(lookup, partNumber) {
|
|
55
|
+
if (!partNumber)
|
|
56
|
+
return this.notFound(lookup);
|
|
57
|
+
const narrowed = Boolean(lookup.projectId?.trim() || lookup.programId?.trim());
|
|
58
|
+
if (this._ambiguous.has(partNumber) && !narrowed) {
|
|
59
|
+
return {
|
|
60
|
+
resolved: false,
|
|
61
|
+
reason: 'ambiguous',
|
|
62
|
+
partNumber,
|
|
63
|
+
partRevisionId: lookup.partRevisionId ?? null,
|
|
64
|
+
candidates: [],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
notFound(lookup) {
|
|
70
|
+
return {
|
|
71
|
+
resolved: false,
|
|
72
|
+
reason: 'not-found',
|
|
73
|
+
partNumber: lookup.partNumber ?? null,
|
|
74
|
+
partRevisionId: lookup.partRevisionId ?? null,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.MockBomClient = MockBomClient;
|
|
79
|
+
//# sourceMappingURL=mock.js.map
|
package/dist/mock.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock.js","sourceRoot":"","sources":["../src/mock.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { HttpTransport } from './http';
|
|
2
|
+
import type { RoutingResult, PartLookup } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Manufacturing routing for one part — step number, process, cycle time, yield,
|
|
5
|
+
* equipment, plant.
|
|
6
|
+
*
|
|
7
|
+
* Routing has ONE storage home in bom: the Active **MBOM** version. Whatever
|
|
8
|
+
* lens answered for cost, routing comes from MBOM, so the provenance on this
|
|
9
|
+
* response can legitimately name a different version than the cost call's.
|
|
10
|
+
* Show both rather than assuming they match.
|
|
11
|
+
*/
|
|
12
|
+
export declare class RoutingResource {
|
|
13
|
+
private readonly http;
|
|
14
|
+
constructor(http: HttpTransport);
|
|
15
|
+
/**
|
|
16
|
+
* Resolve a part reference to its routing steps, ordered by step number.
|
|
17
|
+
*
|
|
18
|
+
* `plantId` filters to one plant's steps — a multi-plant part has a routing
|
|
19
|
+
* per plant. Omit it to get every step bom holds.
|
|
20
|
+
*
|
|
21
|
+
* As with cost, an unresolvable part is a `resolved: false` result, not a
|
|
22
|
+
* throw. A resolved part with no routing is `steps: []` — it is in the MBOM,
|
|
23
|
+
* nobody has imported routing for it.
|
|
24
|
+
*/
|
|
25
|
+
get(lookup: PartLookup & {
|
|
26
|
+
plantId?: string;
|
|
27
|
+
}): Promise<RoutingResult>;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=routing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../src/routing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,KAAK,EAAe,aAAa,EAAe,UAAU,EAAE,MAAM,SAAS,CAAC;AAOnF;;;;;;;;GAQG;AACH,qBAAa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,aAAa;IAEhD;;;;;;;;;OASG;IACG,GAAG,CAAC,MAAM,EAAE,UAAU,GAAG;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,aAAa,CAAC;CAc7E"}
|
package/dist/routing.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RoutingResource = void 0;
|
|
4
|
+
const internal_1 = require("./internal");
|
|
5
|
+
/**
|
|
6
|
+
* Manufacturing routing for one part — step number, process, cycle time, yield,
|
|
7
|
+
* equipment, plant.
|
|
8
|
+
*
|
|
9
|
+
* Routing has ONE storage home in bom: the Active **MBOM** version. Whatever
|
|
10
|
+
* lens answered for cost, routing comes from MBOM, so the provenance on this
|
|
11
|
+
* response can legitimately name a different version than the cost call's.
|
|
12
|
+
* Show both rather than assuming they match.
|
|
13
|
+
*/
|
|
14
|
+
class RoutingResource {
|
|
15
|
+
http;
|
|
16
|
+
constructor(http) {
|
|
17
|
+
this.http = http;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolve a part reference to its routing steps, ordered by step number.
|
|
21
|
+
*
|
|
22
|
+
* `plantId` filters to one plant's steps — a multi-plant part has a routing
|
|
23
|
+
* per plant. Omit it to get every step bom holds.
|
|
24
|
+
*
|
|
25
|
+
* As with cost, an unresolvable part is a `resolved: false` result, not a
|
|
26
|
+
* throw. A resolved part with no routing is `steps: []` — it is in the MBOM,
|
|
27
|
+
* nobody has imported routing for it.
|
|
28
|
+
*/
|
|
29
|
+
async get(lookup) {
|
|
30
|
+
(0, internal_1.assertPartRef)(lookup);
|
|
31
|
+
const res = await this.http.request({
|
|
32
|
+
method: 'GET',
|
|
33
|
+
path: '/api/v1/routing',
|
|
34
|
+
query: { ...(0, internal_1.lookupQuery)(lookup), plantId: lookup.plantId?.trim() || undefined },
|
|
35
|
+
});
|
|
36
|
+
const data = res.data;
|
|
37
|
+
if (!data.resolved)
|
|
38
|
+
return data;
|
|
39
|
+
return {
|
|
40
|
+
...data,
|
|
41
|
+
steps: (data.steps ?? []).map((s) => ({ ...s, yieldPct: (0, internal_1.toNum)(s.yieldPct) })),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.RoutingResource = RoutingResource;
|
|
46
|
+
//# sourceMappingURL=routing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routing.js","sourceRoot":"","sources":["../src/routing.ts"],"names":[],"mappings":";;;AAEA,yCAA+D;AAM/D;;;;;;;;GAQG;AACH,MAAa,eAAe;IACG;IAA7B,YAA6B,IAAmB;QAAnB,SAAI,GAAJ,IAAI,CAAe;IAAG,CAAC;IAEpD;;;;;;;;;OASG;IACH,KAAK,CAAC,GAAG,CAAC,MAAyC;QACjD,IAAA,wBAAa,EAAC,MAAM,CAAC,CAAC;QACtB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAqD;YACtF,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,iBAAiB;YACvB,KAAK,EAAE,EAAE,GAAG,IAAA,sBAAW,EAAC,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,SAAS,EAAE;SAChF,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE,OAAO,IAAI,CAAC;QAChC,OAAO;YACL,GAAG,IAAI;YACP,KAAK,EAAE,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAU,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,QAAQ,EAAE,IAAA,gBAAK,EAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;SACvF,CAAC;IACJ,CAAC;CACF;AA3BD,0CA2BC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
export interface BomClientConfig {
|
|
2
|
+
/** Base URL of the bom API, e.g. https://bom.dev.campfiresuite.com */
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
/** Bearer token provider. For app-to-app calls this is a system token minted
|
|
5
|
+
* for the tenant — never an impersonated end user. */
|
|
6
|
+
getToken: () => string | Promise<string>;
|
|
7
|
+
/** Request timeout in ms. Default 30_000. */
|
|
8
|
+
timeout?: number;
|
|
9
|
+
}
|
|
10
|
+
/** bom's response envelope. */
|
|
11
|
+
export interface BomEnvelope<T> {
|
|
12
|
+
data: T;
|
|
13
|
+
meta: {
|
|
14
|
+
requestId: string;
|
|
15
|
+
timestamp: string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export type BomTypeName = 'EBOM' | 'CBOM' | 'MBOM';
|
|
19
|
+
export type SourcingType = 'make' | 'buy' | 'subcontract';
|
|
20
|
+
/**
|
|
21
|
+
* Which BOM, version and lens produced this answer.
|
|
22
|
+
*
|
|
23
|
+
* Not decoration: resolution is a GUESS bom makes on the caller's behalf (the
|
|
24
|
+
* latest Active version of the one project that matched), and a consumer is
|
|
25
|
+
* expected to SHOW this so a reader can check it. Resolution is also live — the
|
|
26
|
+
* same call can answer differently once a new version is imported, which is
|
|
27
|
+
* what `resolvedAt` is for.
|
|
28
|
+
*/
|
|
29
|
+
export interface BomProvenance {
|
|
30
|
+
bomId: string;
|
|
31
|
+
bomType: BomTypeName;
|
|
32
|
+
versionId: string;
|
|
33
|
+
versionNumber: number;
|
|
34
|
+
versionLabel: string | null;
|
|
35
|
+
versionState: string | null;
|
|
36
|
+
projectId: string;
|
|
37
|
+
projectCode: string;
|
|
38
|
+
projectName: string;
|
|
39
|
+
/** Master-data Program UUID, when the project is linked to one. */
|
|
40
|
+
programId: string | null;
|
|
41
|
+
nodeId: string;
|
|
42
|
+
/** ISO timestamp of the lookup itself. */
|
|
43
|
+
resolvedAt: string;
|
|
44
|
+
}
|
|
45
|
+
/** One project a part number turned up in, when the reference was ambiguous. */
|
|
46
|
+
export interface AmbiguousProject {
|
|
47
|
+
projectId: string;
|
|
48
|
+
projectCode: string;
|
|
49
|
+
projectName: string;
|
|
50
|
+
programId: string | null;
|
|
51
|
+
bomType: BomTypeName;
|
|
52
|
+
versionNumber: number;
|
|
53
|
+
}
|
|
54
|
+
export type MakeBuy = 'make' | 'buy';
|
|
55
|
+
export type CostSource = 'import' | 'reimport' | 'manual';
|
|
56
|
+
/** Why a value is soft: computed from tenant defaults rather than real data.
|
|
57
|
+
* A consumer MUST surface this — presenting a defaulted number as a routed one
|
|
58
|
+
* is the misuse the field exists to prevent. */
|
|
59
|
+
export type CostBasis = 'default-process' | 'default-rate';
|
|
60
|
+
export interface RateSet {
|
|
61
|
+
materialRatePerKg: number;
|
|
62
|
+
scrapPct: number;
|
|
63
|
+
purchasedScrapPct: number;
|
|
64
|
+
laborRatePerHr: number;
|
|
65
|
+
operators: number;
|
|
66
|
+
setupHrs: number;
|
|
67
|
+
cycleTimeSec: number;
|
|
68
|
+
yieldPct: number;
|
|
69
|
+
machineRatePerHr: number;
|
|
70
|
+
factoryOhPct: number;
|
|
71
|
+
materialHandlingPerKg: number;
|
|
72
|
+
sgaPct: number;
|
|
73
|
+
packagingPerPc: number;
|
|
74
|
+
freightPerPc: number;
|
|
75
|
+
}
|
|
76
|
+
export interface CostItem {
|
|
77
|
+
key: string;
|
|
78
|
+
name: string;
|
|
79
|
+
/** e.g. "$/pc" */
|
|
80
|
+
uom: string;
|
|
81
|
+
/** Decimal MAJOR units (dollars), never cents. */
|
|
82
|
+
value: number;
|
|
83
|
+
formula?: string;
|
|
84
|
+
/** Present only when the value is a tenant-default estimate. Render it. */
|
|
85
|
+
basis?: CostBasis;
|
|
86
|
+
}
|
|
87
|
+
export interface CostCategory {
|
|
88
|
+
/** MAT | PP | LAB | MOH | SGA | FP */
|
|
89
|
+
code: string;
|
|
90
|
+
name: string;
|
|
91
|
+
items: CostItem[];
|
|
92
|
+
subtotal: number;
|
|
93
|
+
}
|
|
94
|
+
export interface CostModelMeta {
|
|
95
|
+
computedAt: string;
|
|
96
|
+
source: CostSource;
|
|
97
|
+
currency: string;
|
|
98
|
+
moneyUnit: 'major';
|
|
99
|
+
ratesAsOf: string;
|
|
100
|
+
ratesFingerprint: string;
|
|
101
|
+
}
|
|
102
|
+
export interface CostModel {
|
|
103
|
+
version: 1;
|
|
104
|
+
meta: CostModelMeta;
|
|
105
|
+
makeBuy: MakeBuy;
|
|
106
|
+
/** True when this node's cost is a rollup of its children (assembly). */
|
|
107
|
+
rolledUp: boolean;
|
|
108
|
+
inputs: {
|
|
109
|
+
weightKg: number | null;
|
|
110
|
+
buyPricePerPc: number | null;
|
|
111
|
+
qty: number;
|
|
112
|
+
material: string | null;
|
|
113
|
+
};
|
|
114
|
+
rates: RateSet;
|
|
115
|
+
categories: CostCategory[];
|
|
116
|
+
pieceCost: number;
|
|
117
|
+
/** Written for humans and safe to render verbatim. Includes the one that
|
|
118
|
+
* matters most to an upload-driven tenant: "buy part X has no unit cost;
|
|
119
|
+
* purchased cost = 0" — a silent zero reads as free. */
|
|
120
|
+
warnings: string[];
|
|
121
|
+
}
|
|
122
|
+
export interface CostBreakdown {
|
|
123
|
+
resolved: true;
|
|
124
|
+
partNumber: string;
|
|
125
|
+
name: string;
|
|
126
|
+
sourcingType: SourcingType | null;
|
|
127
|
+
quantity: number;
|
|
128
|
+
costCents: number | null;
|
|
129
|
+
/** null when the node exists but was never costed — NOT the same as the part
|
|
130
|
+
* being absent from every BOM. */
|
|
131
|
+
costModel: CostModel | null;
|
|
132
|
+
provenance: BomProvenance;
|
|
133
|
+
}
|
|
134
|
+
export interface RoutingStep {
|
|
135
|
+
id: string;
|
|
136
|
+
stepNumber: number;
|
|
137
|
+
process: string;
|
|
138
|
+
cycleTimeSec: number | null;
|
|
139
|
+
/** Coerced from the wire's Decimal string. */
|
|
140
|
+
yieldPct: number | null;
|
|
141
|
+
equipment: string | null;
|
|
142
|
+
qualityMethod: string | null;
|
|
143
|
+
plantId: string | null;
|
|
144
|
+
plantCode: string | null;
|
|
145
|
+
plantName: string | null;
|
|
146
|
+
}
|
|
147
|
+
export interface Routing {
|
|
148
|
+
resolved: true;
|
|
149
|
+
partNumber: string;
|
|
150
|
+
steps: RoutingStep[];
|
|
151
|
+
/** Always an MBOM version — routing's single storage home. May be a different
|
|
152
|
+
* version (even a different lens) than the cost answer for the same part. */
|
|
153
|
+
provenance: BomProvenance;
|
|
154
|
+
}
|
|
155
|
+
/** Why a part reference produced no answer. `not-found` = no Active BOM
|
|
156
|
+
* anywhere holds it. `ambiguous` = it is in several projects; re-ask with
|
|
157
|
+
* `projectId` or `programId`. */
|
|
158
|
+
export interface Unresolved {
|
|
159
|
+
resolved: false;
|
|
160
|
+
reason: 'not-found' | 'ambiguous';
|
|
161
|
+
partNumber: string | null;
|
|
162
|
+
partRevisionId: string | null;
|
|
163
|
+
/** Present when reason is 'ambiguous'. */
|
|
164
|
+
candidates?: AmbiguousProject[];
|
|
165
|
+
}
|
|
166
|
+
export type CostBreakdownResult = CostBreakdown | Unresolved;
|
|
167
|
+
export type RoutingResult = Routing | Unresolved;
|
|
168
|
+
/**
|
|
169
|
+
* How to address a part. One of `partNumber` / `partRevisionId` is required;
|
|
170
|
+
* `partRevisionId` (the canonical master-data PartRevision) is the stronger
|
|
171
|
+
* join where the caller has one.
|
|
172
|
+
*
|
|
173
|
+
* `projectId` / `programId` disambiguate a part number that recurs across
|
|
174
|
+
* projects. An OMSF quote has the program, so pass `programId` by default.
|
|
175
|
+
*/
|
|
176
|
+
export interface PartLookup {
|
|
177
|
+
partNumber?: string;
|
|
178
|
+
partRevisionId?: string;
|
|
179
|
+
projectId?: string;
|
|
180
|
+
programId?: string;
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// =============================================================================
|
|
3
|
+
// Wire types for the BOM read contract.
|
|
4
|
+
//
|
|
5
|
+
// THE STANDING TRAP: `CostModel` and its sub-types below are a HAND-MIRROR of
|
|
6
|
+
// app/backend/src/modules/costing/costModel.types.ts. Nothing enforces that they
|
|
7
|
+
// agree — the backend is a separate build. Change one, change the other, and
|
|
8
|
+
// publish. (Same discipline as master-data-client's hand-mirrored Prisma enums.)
|
|
9
|
+
// =============================================================================
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA,gFAAgF;AAChF,wCAAwC;AACxC,EAAE;AACF,8EAA8E;AAC9E,iFAAiF;AACjF,6EAA6E;AAC7E,iFAAiF;AACjF,gFAAgF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@campfire-interactive/bom-client",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "TypeScript client for the BOM read contract — resolved cost breakdowns and routing by part reference",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"clean": "rm -rf dist",
|
|
13
|
+
"test": "vitest run"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/Campfire-Interactive/packages.git"
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"registry": "https://registry.npmjs.org",
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^25.5.0",
|
|
25
|
+
"typescript": "^5.3.3",
|
|
26
|
+
"vitest": "^3.2.7"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18.0.0"
|
|
30
|
+
}
|
|
31
|
+
}
|