@olc-sales/api-client 0.0.0-stage → 1.0.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/README.md CHANGED
@@ -1,3 +1,33 @@
1
- # Temporary Holding Version
1
+ # @olc-sales/api-client
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Shared service-to-service HTTP client pieces, extracted from the near-identical
4
+ copies in `olc-events`, `olc-search`, `olc-notifications`, `olc-data` and
5
+ `olc-entitlements`.
6
+
7
+ - `createJsonApiClient(opts)` – `getResource` / `getResources` / `getRelated`
8
+ against an OLC JSON API. 404 on single/related fetches resolves to `null`.
9
+ `version: 'auto'` uses v2 for capitalised resource types, else v1.
10
+ `maxConcurrent` optionally throttles via Bottleneck. Memoisation and logging
11
+ are left to the consumer.
12
+ - `createServiceToken(tokenUri, clientId, clientSecret)` – OAuth
13
+ client-credentials token provider (cached until 1 min before expiry,
14
+ concurrent callers share one fetch). Pass it as `getToken`.
15
+
16
+ ```js
17
+ const { createJsonApiClient, createServiceToken } = require('@olc-sales/api-client')
18
+
19
+ const identity = createJsonApiClient({
20
+ baseUrl: config.api.identity,
21
+ applicationId: 'olc-events',
22
+ apiKey: config.api.key,
23
+ getToken: createServiceToken(`${config.api.identity}/oauth/token`, id, secret)
24
+ })
25
+ ```
26
+
27
+ `axios`, `qs` and `bottleneck` are peer dependencies.
28
+
29
+ ## Build
30
+
31
+ ```bash
32
+ yarn build # tsc -> dist/
33
+ ```
@@ -0,0 +1,5 @@
1
+ export { createJsonApiClient } from './jsonApiClient';
2
+ export type { JsonApiClient, JsonApiClientOptions, Query } from './jsonApiClient';
3
+ export { createServiceToken } from './serviceToken';
4
+ export type { TokenProvider } from './serviceToken';
5
+ //# 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,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AACrD,YAAY,EAAE,aAAa,EAAE,oBAAoB,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AACjF,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AACnD,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createServiceToken = exports.createJsonApiClient = void 0;
4
+ var jsonApiClient_1 = require("./jsonApiClient");
5
+ Object.defineProperty(exports, "createJsonApiClient", { enumerable: true, get: function () { return jsonApiClient_1.createJsonApiClient; } });
6
+ var serviceToken_1 = require("./serviceToken");
7
+ Object.defineProperty(exports, "createServiceToken", { enumerable: true, get: function () { return serviceToken_1.createServiceToken; } });
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,iDAAqD;AAA5C,oHAAA,mBAAmB,OAAA;AAE5B,+CAAmD;AAA1C,kHAAA,kBAAkB,OAAA"}
@@ -0,0 +1,26 @@
1
+ import type { TokenProvider } from './serviceToken';
2
+ export interface JsonApiClientOptions {
3
+ baseUrl: string;
4
+ /** Sent as `X-Application-ID`. */
5
+ applicationId: string;
6
+ /** Needed when developing locally and connecting through the API gateway. */
7
+ apiKey?: string;
8
+ bearerToken?: string;
9
+ /** Resolved per request and sent as the bearer token; overrides `bearerToken`. */
10
+ getToken?: TokenProvider;
11
+ /** `'auto'` picks v2 for capitalised resource types, otherwise v1. */
12
+ version?: 'v1' | 'v2' | 'auto';
13
+ headers?: Record<string, string>;
14
+ /** Caps in-flight requests when set. */
15
+ maxConcurrent?: number;
16
+ }
17
+ export type Query = Record<string, unknown>;
18
+ type Headers = Record<string, string>;
19
+ export interface JsonApiClient {
20
+ getResource: (type: string, id: string, query?: Query, headers?: Headers) => Promise<any>;
21
+ getResources: (type: string, query?: Query, headers?: Headers) => Promise<any>;
22
+ getRelated: (type: string, id: string, relationship: string, query?: Query, headers?: Headers) => Promise<any>;
23
+ }
24
+ export declare const createJsonApiClient: ({ baseUrl, applicationId, apiKey, bearerToken, getToken, version, headers: extraHeaders, maxConcurrent }: JsonApiClientOptions) => JsonApiClient;
25
+ export {};
26
+ //# sourceMappingURL=jsonApiClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonApiClient.d.ts","sourceRoot":"","sources":["../src/jsonApiClient.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAA;IACf,kCAAkC;IAClC,aAAa,EAAE,MAAM,CAAA;IACrB,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kFAAkF;IAClF,QAAQ,CAAC,EAAE,aAAa,CAAA;IACxB,sEAAsE;IACtE,OAAO,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,CAAA;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,wCAAwC;IACxC,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAC3C,KAAK,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAErC,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,CACX,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,KAAK,CAAC,EAAE,KAAK,EACb,OAAO,CAAC,EAAE,OAAO,KACd,OAAO,CAAC,GAAG,CAAC,CAAA;IACjB,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,GAAG,CAAC,CAAA;IAC9E,UAAU,EAAE,CACV,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,YAAY,EAAE,MAAM,EACpB,KAAK,CAAC,EAAE,KAAK,EACb,OAAO,CAAC,EAAE,OAAO,KACd,OAAO,CAAC,GAAG,CAAC,CAAA;CAClB;AAUD,eAAO,MAAM,mBAAmB,GAAI,0GASjC,oBAAoB,KAAG,aAmDzB,CAAA"}
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createJsonApiClient = void 0;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const bottleneck_1 = __importDefault(require("bottleneck"));
9
+ const qs_1 = __importDefault(require("qs"));
10
+ // A missing resource is a null result rather than an error.
11
+ const handleGetFailure = (err) => {
12
+ if (err.response && err.response.status === 404) {
13
+ return null;
14
+ }
15
+ throw err;
16
+ };
17
+ const createJsonApiClient = ({ baseUrl, applicationId, apiKey, bearerToken, getToken, version = 'v1', headers: extraHeaders = {}, maxConcurrent }) => {
18
+ const defaultHeaders = {
19
+ 'X-Application-ID': applicationId,
20
+ ...extraHeaders
21
+ };
22
+ if (apiKey) {
23
+ defaultHeaders['x-api-key'] = apiKey;
24
+ }
25
+ if (bearerToken) {
26
+ defaultHeaders.Authorization = `Bearer ${bearerToken}`;
27
+ }
28
+ const axiosInstance = axios_1.default.create({ headers: defaultHeaders });
29
+ if (getToken) {
30
+ axiosInstance.interceptors.request.use(async (request) => {
31
+ request.headers.Authorization = `Bearer ${await getToken()}`;
32
+ return request;
33
+ });
34
+ }
35
+ const limiter = maxConcurrent ? new bottleneck_1.default({ maxConcurrent }) : null;
36
+ const schedule = (fn) => limiter ? limiter.schedule(fn) : fn();
37
+ const apiVersion = (type) => version === 'auto' ? (/^[A-Z]/.test(type) ? 'v2' : 'v1') : version;
38
+ const get = (uri, headers) => schedule(() => axiosInstance
39
+ .get(uri, { headers })
40
+ .then(res => res.data));
41
+ return {
42
+ getResource: (type, id, query = {}, headers = {}) => get(`${baseUrl}/${apiVersion(type)}/${type}/${id}?${qs_1.default.stringify(query)}`, headers).catch(handleGetFailure),
43
+ getResources: (type, query = {}, headers = {}) => get(`${baseUrl}/${apiVersion(type)}/${type}?${qs_1.default.stringify(query)}`, headers),
44
+ getRelated: (type, id, relationship, query = {}, headers = {}) => get(`${baseUrl}/${apiVersion(type)}/${type}/${id}/${relationship}?${qs_1.default.stringify(query)}`, headers).catch(handleGetFailure)
45
+ };
46
+ };
47
+ exports.createJsonApiClient = createJsonApiClient;
48
+ //# sourceMappingURL=jsonApiClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonApiClient.js","sourceRoot":"","sources":["../src/jsonApiClient.ts"],"names":[],"mappings":";;;;;;AAAA,kDAAiD;AACjD,4DAAmC;AACnC,4CAAmB;AAuCnB,4DAA4D;AAC5D,MAAM,gBAAgB,GAAG,CAAC,GAAQ,EAAE,EAAE;IACpC,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QAChD,OAAO,IAAI,CAAA;IACb,CAAC;IACD,MAAM,GAAG,CAAA;AACX,CAAC,CAAA;AAEM,MAAM,mBAAmB,GAAG,CAAC,EAClC,OAAO,EACP,aAAa,EACb,MAAM,EACN,WAAW,EACX,QAAQ,EACR,OAAO,GAAG,IAAI,EACd,OAAO,EAAE,YAAY,GAAG,EAAE,EAC1B,aAAa,EACQ,EAAiB,EAAE;IACxC,MAAM,cAAc,GAAY;QAC9B,kBAAkB,EAAE,aAAa;QACjC,GAAG,YAAY;KAChB,CAAA;IACD,IAAI,MAAM,EAAE,CAAC;QACX,cAAc,CAAC,WAAW,CAAC,GAAG,MAAM,CAAA;IACtC,CAAC;IACD,IAAI,WAAW,EAAE,CAAC;QAChB,cAAc,CAAC,aAAa,GAAG,UAAU,WAAW,EAAE,CAAA;IACxD,CAAC;IAED,MAAM,aAAa,GAAG,eAAK,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC,CAAA;IAC/D,IAAI,QAAQ,EAAE,CAAC;QACb,aAAa,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAC,OAAO,EAAC,EAAE;YACrD,OAAO,CAAC,OAAO,CAAC,aAAa,GAAG,UAAU,MAAM,QAAQ,EAAE,EAAE,CAAA;YAC5D,OAAO,OAAO,CAAA;QAChB,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,aAAa,CAAC,CAAC,CAAC,IAAI,oBAAU,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IACxE,MAAM,QAAQ,GAAG,CAAI,EAAoB,EAAc,EAAE,CACvD,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAA;IAEvC,MAAM,UAAU,GAAG,CAAC,IAAY,EAAE,EAAE,CAClC,OAAO,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAA;IAEpE,MAAM,GAAG,GAAG,CAAC,GAAW,EAAE,OAAgB,EAAE,EAAE,CAC5C,QAAQ,CAAC,GAAG,EAAE,CACZ,aAAa;SACV,GAAG,CAAC,GAAG,EAAE,EAAE,OAAO,EAAwB,CAAC;SAC3C,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CACzB,CAAA;IAEH,OAAO;QACL,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,KAAK,GAAG,EAAE,EAAE,OAAO,GAAG,EAAE,EAAE,EAAE,CAClD,GAAG,CACD,GAAG,OAAO,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,EAAE,IAAI,YAAE,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,EACrE,OAAO,CACR,CAAC,KAAK,CAAC,gBAAgB,CAAC;QAC3B,YAAY,EAAE,CAAC,IAAI,EAAE,KAAK,GAAG,EAAE,EAAE,OAAO,GAAG,EAAE,EAAE,EAAE,CAC/C,GAAG,CACD,GAAG,OAAO,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,YAAE,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,EAC/D,OAAO,CACR;QACH,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,YAAY,EAAE,KAAK,GAAG,EAAE,EAAE,OAAO,GAAG,EAAE,EAAE,EAAE,CAC/D,GAAG,CACD,GAAG,OAAO,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,EAAE,IAAI,YAAY,IAAI,YAAE,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,EACrF,OAAO,CACR,CAAC,KAAK,CAAC,gBAAgB,CAAC;KAC5B,CAAA;AACH,CAAC,CAAA;AA5DY,QAAA,mBAAmB,uBA4D/B"}
@@ -0,0 +1,7 @@
1
+ export type TokenProvider = () => Promise<string>;
2
+ /**
3
+ * Client-credentials token for an OAuth server (olc-identity), cached until a
4
+ * minute before expiry. Concurrent callers share one in-flight fetch.
5
+ */
6
+ export declare const createServiceToken: (tokenUri: string, clientId: string, clientSecret: string) => TokenProvider;
7
+ //# sourceMappingURL=serviceToken.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serviceToken.d.ts","sourceRoot":"","sources":["../src/serviceToken.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,aAAa,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;AAEjD;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAC7B,UAAU,MAAM,EAChB,UAAU,MAAM,EAChB,cAAc,MAAM,KACnB,aAiCF,CAAA"}
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createServiceToken = void 0;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ /**
9
+ * Client-credentials token for an OAuth server (olc-identity), cached until a
10
+ * minute before expiry. Concurrent callers share one in-flight fetch.
11
+ */
12
+ const createServiceToken = (tokenUri, clientId, clientSecret) => {
13
+ let token;
14
+ let expiresAt = 0;
15
+ let pending = null;
16
+ const fetchToken = () => axios_1.default
17
+ .post(tokenUri, {
18
+ grant_type: 'client_credentials',
19
+ client_id: clientId,
20
+ client_secret: clientSecret
21
+ }, { timeout: 10000 })
22
+ .then(({ data: { access_token: accessToken, expires_in: expiresIn } }) => {
23
+ token = accessToken;
24
+ expiresAt = Date.now() + (expiresIn - 60) * 1000;
25
+ return token;
26
+ });
27
+ return () => {
28
+ if (token && Date.now() < expiresAt) {
29
+ return Promise.resolve(token);
30
+ }
31
+ pending =
32
+ pending ||
33
+ fetchToken().finally(() => {
34
+ pending = null;
35
+ });
36
+ return pending;
37
+ };
38
+ };
39
+ exports.createServiceToken = createServiceToken;
40
+ //# sourceMappingURL=serviceToken.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serviceToken.js","sourceRoot":"","sources":["../src/serviceToken.ts"],"names":[],"mappings":";;;;;;AAAA,kDAAyB;AAIzB;;;GAGG;AACI,MAAM,kBAAkB,GAAG,CAChC,QAAgB,EAChB,QAAgB,EAChB,YAAoB,EACL,EAAE;IACjB,IAAI,KAAyB,CAAA;IAC7B,IAAI,SAAS,GAAG,CAAC,CAAA;IACjB,IAAI,OAAO,GAA2B,IAAI,CAAA;IAE1C,MAAM,UAAU,GAAG,GAAG,EAAE,CACtB,eAAK;SACF,IAAI,CACH,QAAQ,EACR;QACE,UAAU,EAAE,oBAAoB;QAChC,SAAS,EAAE,QAAQ;QACnB,aAAa,EAAE,YAAY;KAC5B,EACD,EAAE,OAAO,EAAE,KAAK,EAAE,CACnB;SACA,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,EAAE,EAAE;QACvE,KAAK,GAAG,WAAqB,CAAA;QAC7B,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,SAAS,GAAG,EAAE,CAAC,GAAG,IAAI,CAAA;QAChD,OAAO,KAAK,CAAA;IACd,CAAC,CAAC,CAAA;IAEN,OAAO,GAAG,EAAE;QACV,IAAI,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,EAAE,CAAC;YACpC,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;QAC/B,CAAC;QACD,OAAO;YACL,OAAO;gBACP,UAAU,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;oBACxB,OAAO,GAAG,IAAI,CAAA;gBAChB,CAAC,CAAC,CAAA;QACJ,OAAO,OAAO,CAAA;IAChB,CAAC,CAAA;AACH,CAAC,CAAA;AArCY,QAAA,kBAAkB,sBAqC9B"}
package/package.json CHANGED
@@ -1,6 +1,38 @@
1
1
  {
2
2
  "name": "@olc-sales/api-client",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "Shared OLC service-to-service HTTP clients - JSON API read client and OAuth client-credentials token provider",
5
+ "keywords": [
6
+ "api",
7
+ "client",
8
+ "oauth",
9
+ "olc"
10
+ ],
11
+ "author": "Joel Woods <joel@miaem.com>",
12
+ "license": "ISC",
13
+ "main": "dist/index.js",
14
+ "types": "dist/index.d.ts",
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "scripts": {
22
+ "build": "tsc -p tsconfig.json",
23
+ "prepublishOnly": "yarn build"
24
+ },
25
+ "peerDependencies": {
26
+ "axios": ">=0.18.0",
27
+ "bottleneck": "^2.0.0",
28
+ "qs": "^6.5.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^20.14.0",
32
+ "@types/qs": "^6.9.15",
33
+ "axios": "^1.7.0",
34
+ "bottleneck": "^2.19.5",
35
+ "qs": "^6.12.0",
36
+ "typescript": "^5.5.4"
37
+ }
38
+ }