@faable/sdk-base 1.4.0 → 1.5.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/README.md CHANGED
@@ -16,3 +16,75 @@
16
16
  ```bash
17
17
  npm install @faable/sdk-base
18
18
  ```
19
+
20
+ ## Building an API client
21
+
22
+ Extend `FaableApi` and call the static `create` factory:
23
+
24
+ ```ts
25
+ import { FaableApi } from "@faable/sdk-base";
26
+ import type { ApiParams } from "@faable/sdk-base";
27
+
28
+ export class MyApi extends FaableApi {
29
+ constructor(params?: ApiParams) {
30
+ super({ baseURL: "https://api.example.com", ...params });
31
+ }
32
+ getThing(id: string) {
33
+ return this.fetcher.get(`/thing/${id}`);
34
+ }
35
+ }
36
+ ```
37
+
38
+ ## Authentication (pluggable, Octokit-style)
39
+
40
+ Auth is **explicit** — strategies never read environment variables. You pick an
41
+ `authStrategy` and pass its config in `auth`; the chosen strategy drives the
42
+ type of `auth`, so the editor autocompletes the right fields and rejects the
43
+ wrong ones.
44
+
45
+ ```ts
46
+ import { authClientCredentials, authApikey } from "@faable/sdk-base";
47
+
48
+ // client_credentials — the DEFAULT strategy. `auth` requires
49
+ // { client_id, client_secret }.
50
+ const api = MyApi.create({
51
+ baseURL: "https://my-account.auth.faable.link",
52
+ authStrategy: authClientCredentials,
53
+ auth: { client_id: "...", client_secret: "..." },
54
+ });
55
+
56
+ // api key — `auth` requires { apikey }.
57
+ const api2 = MyApi.create({
58
+ baseURL: "https://api.example.com",
59
+ authStrategy: authApikey,
60
+ auth: { apikey: "fak_xxx" },
61
+ });
62
+ ```
63
+
64
+ `authClientCredentials` requests its token from `<baseURL>/oauth/token` by
65
+ default. When the API host is **not** the auth server, point the token request
66
+ at the right host with `auth.domain`:
67
+
68
+ ```ts
69
+ auth: { client_id: "...", client_secret: "...", domain: "https://auth.example.com" }
70
+ ```
71
+
72
+ ### Writing a custom strategy
73
+
74
+ A strategy is a builder `(config, context) => { headers() }`:
75
+
76
+ ```ts
77
+ import type { AuthStrategyBuilder } from "@faable/sdk-base";
78
+
79
+ type BearerConfig = { token: string };
80
+
81
+ export const authBearer: AuthStrategyBuilder<BearerConfig> = (config) => {
82
+ if (!config?.token) throw new Error("authBearer: `auth.token` is required");
83
+ return {
84
+ headers: async () => ({ authorization: `Bearer ${config.token}` }),
85
+ };
86
+ };
87
+ ```
88
+
89
+ `context` carries the api's `domain` (its `baseURL`) and `debug` flag, so a
90
+ token-minting strategy can reach the auth server without extra config.
@@ -1,16 +1,18 @@
1
- import type { AuthInterface } from "./types/AuthInterface.js";
2
1
  import type { Fetcher, FetcherCreateParams } from "./fetcher/Fetcher.js";
3
2
  import type { Paginator, PaginatorOptions } from "./helpers/paginator.js";
3
+ import type { AuthStrategyBuilder } from "./types/AuthStrategy.js";
4
+ import { type ClientCredentialsConfig } from "./auth/client_credentials/client_credentials.js";
4
5
  export type RequestObserver = (info: {
5
6
  method: string;
6
7
  url: string;
7
8
  status: number;
8
9
  durationMs: number;
9
10
  }) => void;
10
- export type ApiParams = {
11
+ export type ApiParams<TAuth = ClientCredentialsConfig> = {
11
12
  baseURL?: string;
12
13
  fetcher?: FetcherCreateParams;
13
- auth?: AuthInterface<any, any>;
14
+ authStrategy?: AuthStrategyBuilder<TAuth>;
15
+ auth?: TAuth;
14
16
  debug?: boolean;
15
17
  paginator?: Partial<PaginatorOptions>;
16
18
  /**
@@ -18,28 +20,30 @@ export type ApiParams = {
18
20
  * remembers each GET's `ETag` and replays it as `If-None-Match`; a `304 Not
19
21
  * Modified` reuses the previously parsed body instead of re-downloading it.
20
22
  * Correctness relies on the server changing the ETag whenever the resource
21
- * changes (no client-side invalidation needed a write bumps the server
22
- * ETag, so the next GET gets a fresh `200`). Off by default.
23
+ * changes (no client-side invalidation needed). Off by default.
23
24
  *
24
25
  * `true` uses the default cap; pass `{ maxEntries }` to size the bounded LRU
25
- * for the client's key cardinality (e.g. a controller polling many apps +
26
- * deployments wants a larger cap than a one-shot script).
26
+ * for the client's key cardinality.
27
27
  */
28
28
  etagCache?: boolean | {
29
29
  maxEntries?: number;
30
30
  };
31
31
  /**
32
32
  * Pluggable per-response observer. Called once per completed HTTP response
33
- * with `{ method, url, status, durationMs }`. Use it to collect metrics
34
- * (calls per endpoint, `304` ETag-hit rate, latency, …). Errors thrown by the
35
- * observer are swallowed so instrumentation can never break a request.
33
+ * with `{ method, url, status, durationMs }`. Errors thrown by the observer
34
+ * are swallowed so instrumentation can never break a request.
36
35
  */
37
36
  requestObserver?: RequestObserver;
38
37
  };
38
+ type CreateParams<Cls extends abstract new (params?: any) => any, TAuth> = Omit<NonNullable<ConstructorParameters<Cls>[0]>, "auth" | "authStrategy"> & {
39
+ authStrategy?: AuthStrategyBuilder<TAuth>;
40
+ auth?: TAuth;
41
+ };
39
42
  export declare abstract class FaableApi<Params extends ApiParams = ApiParams> {
40
43
  fetcher: Fetcher;
41
44
  protected paginator: Paginator;
42
45
  protected constructor(params?: Params);
43
- static create<T extends abstract new (args: Params) => any, Params = any>(this: T, params?: Params): InstanceType<T>;
46
+ static create<Cls extends abstract new (params?: any) => any, TAuth = ClientCredentialsConfig>(this: Cls, params?: CreateParams<Cls, TAuth>): InstanceType<Cls>;
44
47
  }
48
+ export {};
45
49
  //# sourceMappingURL=FaableApi.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"FaableApi.d.ts","sourceRoot":"","sources":["../src/FaableApi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAGzE,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAQ1E,MAAM,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB,KAAK,IAAI,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,IAAI,CAAC,EAAE,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAC/B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACtC;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C;;;;;OAKG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;CACnC,CAAC;AAEF,8BAAsB,SAAS,CAAC,MAAM,SAAS,SAAS,GAAG,SAAS;IAClE,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,SAAS,EAAE,SAAS,CAAC;IAC/B,SAAS,aAAa,MAAM,CAAC,EAAE,MAAM;IAMrC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,QAAQ,MAAM,IAAI,EAAE,MAAM,KAAK,GAAG,EAAE,MAAM,GAAG,GAAG,EACtE,IAAI,EAAE,CAAC,EACP,MAAM,CAAC,EAAE,MAAM,GACd,YAAY,CAAC,CAAC,CAAC;CAInB"}
1
+ {"version":3,"file":"FaableApi.d.ts","sourceRoot":"","sources":["../src/FaableApi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAGzE,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1E,OAAO,KAAK,EAEV,mBAAmB,EACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,iDAAiD,CAAC;AAQzD,MAAM,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB,KAAK,IAAI,CAAC;AAMX,MAAM,MAAM,SAAS,CAAC,KAAK,GAAG,uBAAuB,IAAI;IACvD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAI9B,YAAY,CAAC,EAAE,mBAAmB,CAAC,KAAK,CAAC,CAAC;IAC1C,IAAI,CAAC,EAAE,KAAK,CAAC;IACb,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACtC;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C;;;;OAIG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;CACnC,CAAC;AAKF,KAAK,YAAY,CACf,GAAG,SAAS,QAAQ,MAAM,MAAM,CAAC,EAAE,GAAG,KAAK,GAAG,EAC9C,KAAK,IACH,IAAI,CAAC,WAAW,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,GAAG,cAAc,CAAC,GAAG;IAC9E,YAAY,CAAC,EAAE,mBAAmB,CAAC,KAAK,CAAC,CAAC;IAC1C,IAAI,CAAC,EAAE,KAAK,CAAC;CACd,CAAC;AAEF,8BAAsB,SAAS,CAAC,MAAM,SAAS,SAAS,GAAG,SAAS;IAClE,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,SAAS,EAAE,SAAS,CAAC;IAE/B,SAAS,aAAa,MAAM,CAAC,EAAE,MAAM;IAmBrC,MAAM,CAAC,MAAM,CACX,GAAG,SAAS,QAAQ,MAAM,MAAM,CAAC,EAAE,GAAG,KAAK,GAAG,EAC9C,KAAK,GAAG,uBAAuB,EAC/B,IAAI,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,YAAY,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,YAAY,CAAC,GAAG,CAAC;CAInE"}
package/dist/FaableApi.js CHANGED
@@ -1,15 +1,27 @@
1
1
  import { fetcher_axios } from "./fetcher/fetcher_axios.js";
2
2
  import { buildPaginator } from "./helpers/paginator.js";
3
+ import { authClientCredentials, } from "./auth/client_credentials/client_credentials.js";
3
4
  export class FaableApi {
4
5
  fetcher;
5
6
  paginator;
6
7
  constructor(params) {
7
- const options = params || {};
8
- this.fetcher = fetcher_axios(options);
8
+ const options = (params ?? {});
9
+ // Resolve the auth strategy: explicit `authStrategy`, else default to
10
+ // client_credentials when `auth` is given, else anonymous (no headers).
11
+ const builder = options.authStrategy ?? (options.auth ? authClientCredentials : undefined);
12
+ const strategy = builder
13
+ ? builder(options.auth ?? {}, {
14
+ domain: options.baseURL,
15
+ debug: options.debug,
16
+ })
17
+ : undefined;
18
+ this.fetcher = fetcher_axios({ ...options, strategy });
9
19
  this.paginator = buildPaginator(this.fetcher, options.paginator);
10
20
  }
21
+ // Octokit-style factory. Infers `TAuth` from the passed `authStrategy` so
22
+ // `auth` is typed (and autocompleted) accordingly.
11
23
  static create(params) {
12
- let Cns = this;
24
+ const Cns = this;
13
25
  return new Cns(params);
14
26
  }
15
27
  }
@@ -1,4 +1,6 @@
1
- import type { StrategyInterface } from "../types/StrategyInterface.js";
2
- export type ApikeyAuthParams = Partial<[apikey: string]>;
3
- export declare const createApikeyAuth: StrategyInterface<ApikeyAuthParams>;
1
+ import type { AuthStrategyBuilder } from "../types/AuthStrategy.js";
2
+ export type ApikeyConfig = {
3
+ apikey: string;
4
+ };
5
+ export declare const authApikey: AuthStrategyBuilder<ApikeyConfig>;
4
6
  //# sourceMappingURL=apikey.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"apikey.d.ts","sourceRoot":"","sources":["../../src/auth/apikey.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAGvE,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAEzD,eAAO,MAAM,gBAAgB,EAAE,iBAAiB,CAAC,gBAAgB,CAoBhE,CAAC"}
1
+ {"version":3,"file":"apikey.d.ts","sourceRoot":"","sources":["../../src/auth/apikey.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAIpE,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,mBAAmB,CAAC,YAAY,CAUxD,CAAC"}
@@ -1,16 +1,11 @@
1
- const APIKEY_ENV_NAME = "FAABLE_APIKEY";
2
- export const createApikeyAuth = (apikey) => {
3
- // Default to environment variable
4
- apikey = apikey || process.env[APIKEY_ENV_NAME];
1
+ export const authApikey = (config) => {
2
+ const apikey = config?.apikey;
5
3
  if (!apikey) {
6
- throw new Error(`[@faable/deploy-sdk] No apikey passed to createApikeyAuth. Pass an apikey or use ${APIKEY_ENV_NAME} environment variable`);
4
+ throw new Error("authApikey: `auth.apikey` is required (no env fallback)");
7
5
  }
8
- const auth = async () => {
9
- return {
10
- headers: {
11
- authorization: `basic ${btoa(`${apikey}:${apikey}`)}`,
12
- },
13
- };
6
+ return {
7
+ headers: async () => ({
8
+ authorization: `basic ${btoa(`${apikey}:${apikey}`)}`,
9
+ }),
14
10
  };
15
- return Object.assign(auth, { hook: auth });
16
11
  };
@@ -1,11 +1,11 @@
1
- import type { StrategyInterface } from "../../types/StrategyInterface.js";
2
- type Params = {
1
+ import type { AuthStrategyBuilder } from "../../types/AuthStrategy.js";
2
+ export type AudienceResolver = () => string | undefined | Promise<string | undefined>;
3
+ export type ClientCredentialsConfig = {
3
4
  client_id: string;
4
5
  client_secret: string;
5
- domain: string;
6
- debug?: boolean;
6
+ domain?: string;
7
+ audience?: string | AudienceResolver;
8
+ scope?: string | string[];
7
9
  };
8
- export type ClientCredentialsAuthParams = Partial<[Partial<Params>]>;
9
- export declare const createClientCredentials: StrategyInterface<ClientCredentialsAuthParams>;
10
- export {};
10
+ export declare const authClientCredentials: AuthStrategyBuilder<ClientCredentialsConfig>;
11
11
  //# sourceMappingURL=client_credentials.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client_credentials.d.ts","sourceRoot":"","sources":["../../../src/auth/client_credentials/client_credentials.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAK1E,KAAK,MAAM,GAAG;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG,OAAO,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AAWrE,eAAO,MAAM,uBAAuB,EAAE,iBAAiB,CACrD,2BAA2B,CAgG5B,CAAC"}
1
+ {"version":3,"file":"client_credentials.d.ts","sourceRoot":"","sources":["../../../src/auth/client_credentials/client_credentials.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAcvE,MAAM,MAAM,gBAAgB,GAAG,MAC3B,MAAM,GACN,SAAS,GACT,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;AAEhC,MAAM,MAAM,uBAAuB,GAAG;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IAMtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAKhB,QAAQ,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAAC;IAIrC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAC3B,CAAC;AAOF,eAAO,MAAM,qBAAqB,EAAE,mBAAmB,CACrD,uBAAuB,CAkGxB,CAAC"}
@@ -1,57 +1,71 @@
1
1
  import { fetcher_axios } from "../../fetcher/fetcher_axios.js";
2
2
  import { createTokenStore } from "./store.js";
3
3
  import PQueue from "p-queue";
4
- const CLIENT_ID_ENV = "FAABLEAUTH_CLIENT_ID";
5
- const CLIENT_SECRET_ENV = "FAABLEAUTH_CLIENT_SECRET";
6
- const AUTH_DOMAIN = "FAABLEAUTH_DOMAIN";
7
4
  // Refresh the cached token this many seconds before its real expiry, so a
8
5
  // request that starts just before the boundary doesn't ship a token that
9
6
  // expires mid-flight.
10
7
  const EXPIRY_SKEW_SECONDS = 60;
11
- export const createClientCredentials = (params) => {
12
- const client_id = params?.client_id || process.env[CLIENT_ID_ENV];
13
- const client_secret = params?.client_secret || process.env[CLIENT_SECRET_ENV];
14
- const auth_domain = params?.domain || process.env[AUTH_DOMAIN];
15
- const debug = params?.debug;
8
+ export const authClientCredentials = (config, context) => {
9
+ const client_id = config?.client_id;
10
+ const client_secret = config?.client_secret;
11
+ // `domain` (the auth host whose /oauth/token we hit) defaults to the api's
12
+ // own `domain` (injected via context), so for auth-sdk the SDK and the token
13
+ // endpoint always agree. An explicit `config.domain` overrides it for apis
14
+ // whose baseURL is not the auth server (e.g. the deploy api).
15
+ const auth_domain = config?.domain ?? context?.domain;
16
+ const debug = context?.debug;
16
17
  if (!client_id) {
17
- throw new Error(`No client_id set in createClientCredentials. Use ${CLIENT_ID_ENV} environment variable`);
18
+ throw new Error("authClientCredentials: `auth.client_id` is required (no env fallback)");
18
19
  }
19
20
  if (!client_secret) {
20
- throw new Error(`No client_secret set in createClientCredentials. Use ${CLIENT_SECRET_ENV} environment variable`);
21
+ throw new Error("authClientCredentials: `auth.client_secret` is required (no env fallback)");
21
22
  }
22
23
  if (!auth_domain) {
23
- throw new Error(`No domain set in createClientCredentials. Use ${AUTH_DOMAIN} environment variable`);
24
+ throw new Error("authClientCredentials: `domain` is required pass it to the api (FaableAuthApi.create({ domain, ... }))");
24
25
  }
25
26
  const store = createTokenStore({ debug });
26
27
  const queue = new PQueue({ concurrency: 1, timeout: 20 * 1000 });
27
28
  const isTokenExpired = (token, issued_at) => {
28
29
  const { expires_in } = token;
29
- // `expires_in` is in SECONDS (OAuth 2.0 §5.1), but `issued_at` and
30
- // `Date.now()` are in MILLISECONDS. The previous code compared
31
- // `issued_at + expires_in` (ms + s) against `Date.now()` (ms), so a
32
- // 24h (86400s) token was treated as expired after ~86s — causing the
33
- // SDK to re-request a token ~1000× more often than needed and hammer
34
- // the /oauth/token endpoint. Convert to ms and refresh a bit early
35
- // (skew) so an in-flight request never ships an about-to-expire token.
30
+ // `expires_in` is SECONDS (OAuth 2.0 §5.1); `issued_at`/`Date.now()` are ms.
31
+ // Convert and refresh a bit early (skew) so an in-flight request never
32
+ // ships an about-to-expire token.
36
33
  const expires_at = issued_at + (expires_in - EXPIRY_SKEW_SECONDS) * 1000;
37
34
  return expires_at < Date.now();
38
35
  };
39
- const fetcher = fetcher_axios({
40
- baseURL: auth_domain,
41
- });
36
+ const fetcher = fetcher_axios({ baseURL: auth_domain });
37
+ // Resolve the audience once (the resolver may do I/O, e.g. OIDC discovery)
38
+ // and memoize it for the strategy's lifetime so token refreshes don't re-run
39
+ // it. A resolver returning `undefined` is retried (treated as "not resolved").
40
+ let resolved_audience;
41
+ const resolveAudience = async () => {
42
+ if (resolved_audience !== undefined)
43
+ return resolved_audience;
44
+ resolved_audience =
45
+ typeof config.audience === "function"
46
+ ? await config.audience()
47
+ : config.audience;
48
+ return resolved_audience;
49
+ };
42
50
  const requestToken = async () => {
43
51
  const params = new URLSearchParams();
44
52
  params.append("grant_type", "client_credentials");
45
53
  params.append("client_id", client_id);
46
54
  params.append("client_secret", client_secret);
55
+ const audience = await resolveAudience();
56
+ if (audience)
57
+ params.append("audience", audience);
58
+ const scope = Array.isArray(config.scope)
59
+ ? config.scope.join(" ")
60
+ : config.scope;
61
+ if (scope)
62
+ params.append("scope", scope);
47
63
  const token_response = await fetcher.post("/oauth/token", params);
48
- // Save token in store
49
64
  store.saveTokenResponse(token_response);
50
65
  return token_response;
51
66
  };
52
67
  const getToken = async () => {
53
- let res = store.getTokenResponse();
54
- // We don't have a token or it's expired
68
+ const res = store.getTokenResponse();
55
69
  if (!res || isTokenExpired(res.response, res.iat)) {
56
70
  return requestToken();
57
71
  }
@@ -64,16 +78,10 @@ export const createClientCredentials = (params) => {
64
78
  }
65
79
  return token;
66
80
  };
67
- const auth = async () => {
68
- const key = `token-${Math.floor(Math.random() * 1000).toFixed(0)}`;
69
- debug && console.time(key);
70
- let token = await getTokenWithLimits();
71
- debug && console.timeLog(key, "completed token fetch");
72
- return {
73
- headers: {
74
- authorization: `Bearer ${token.access_token}`,
75
- },
76
- };
81
+ return {
82
+ headers: async () => {
83
+ const token = await getTokenWithLimits();
84
+ return { authorization: `Bearer ${token.access_token}` };
85
+ },
77
86
  };
78
- return Object.assign(auth, { hook: auth });
79
87
  };
@@ -1,4 +1,9 @@
1
1
  import type { Fetcher } from "./Fetcher.js";
2
2
  import type { ApiParams } from "../FaableApi.js";
3
- export declare const fetcher_axios: (params?: ApiParams) => Fetcher;
3
+ import type { AuthStrategy } from "../types/AuthStrategy.js";
4
+ type FetcherParams = ApiParams<any> & {
5
+ strategy?: AuthStrategy;
6
+ };
7
+ export declare const fetcher_axios: (params?: FetcherParams) => Fetcher;
8
+ export {};
4
9
  //# sourceMappingURL=fetcher_axios.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fetcher_axios.d.ts","sourceRoot":"","sources":["../../src/fetcher/fetcher_axios.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAiB,MAAM,cAAc,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAiBjD,eAAO,MAAM,aAAa,YAAY,SAAS,KAAQ,OAmJtD,CAAC"}
1
+ {"version":3,"file":"fetcher_axios.d.ts","sourceRoot":"","sources":["../../src/fetcher/fetcher_axios.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAiB,MAAM,cAAc,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAI7D,KAAK,aAAa,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG;IAAE,QAAQ,CAAC,EAAE,YAAY,CAAA;CAAE,CAAC;AAiBlE,eAAO,MAAM,aAAa,YAAY,aAAa,KAAQ,OAqJ1D,CAAC"}
@@ -20,8 +20,10 @@ export const fetcher_axios = (params = {}) => {
20
20
  ...(params.fetcher || {}),
21
21
  });
22
22
  instance.interceptors.request.use(async (req) => {
23
- const auth_data = params.auth && (await params.auth.hook());
24
- req.headers.set(auth_data?.headers || {});
23
+ const headers = params.strategy
24
+ ? await params.strategy.headers()
25
+ : undefined;
26
+ req.headers.set(headers || {});
25
27
  // Stamp a start time so the response interceptor can report latency to the
26
28
  // pluggable observer.
27
29
  req.__startTime = Date.now();
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./FaableApi.js";
2
+ export * from "./types/AuthStrategy.js";
2
3
  export * from "./auth/apikey.js";
3
4
  export * from "./auth/client_credentials/client_credentials.js";
4
5
  export * from "./helpers/FaableApiError.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,iDAAiD,CAAC;AAChE,cAAc,6BAA6B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,yBAAyB,CAAC;AACxC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iDAAiD,CAAC;AAChE,cAAc,6BAA6B,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./FaableApi.js";
2
+ export * from "./types/AuthStrategy.js";
2
3
  export * from "./auth/apikey.js";
3
4
  export * from "./auth/client_credentials/client_credentials.js";
4
5
  export * from "./helpers/FaableApiError.js";
@@ -0,0 +1,9 @@
1
+ export type AuthStrategy = {
2
+ headers: () => Promise<Record<string, string>>;
3
+ };
4
+ export type AuthStrategyContext = {
5
+ domain?: string;
6
+ debug?: boolean;
7
+ };
8
+ export type AuthStrategyBuilder<TConfig = unknown> = (config: TConfig, context?: AuthStrategyContext) => AuthStrategy;
9
+ //# sourceMappingURL=AuthStrategy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AuthStrategy.d.ts","sourceRoot":"","sources":["../../src/types/AuthStrategy.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,YAAY,GAAG;IAGzB,OAAO,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CAChD,CAAC;AAIF,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,mBAAmB,CAAC,OAAO,GAAG,OAAO,IAAI,CACnD,MAAM,EAAE,OAAO,EACf,OAAO,CAAC,EAAE,mBAAmB,KAC1B,YAAY,CAAC"}
@@ -0,0 +1,13 @@
1
+ // Octokit-style pluggable authentication.
2
+ //
3
+ // An `AuthStrategyBuilder<TConfig>` is a factory the caller passes as
4
+ // `authStrategy`. It receives the explicit `auth` config (TConfig) as its first
5
+ // argument so TypeScript can INFER TConfig from the chosen strategy — i.e.
6
+ // passing `authStrategy: authClientCredentials` makes the editor require/
7
+ // autocomplete `auth: { client_id, client_secret }`. The api `domain`/`debug`
8
+ // are injected separately as `context` (kept out of TConfig so they don't
9
+ // pollute the inferred `auth` shape).
10
+ //
11
+ // Strategies are EXPLICIT: they never read environment variables. The caller
12
+ // sources credentials (env, secret manager, …) and passes them in `auth`.
13
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faable/sdk-base",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "author": "Marc Pomar <marc@faable.com>",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -34,6 +34,7 @@
34
34
  "generate-types": "openapi-typescript https://api.faable.com/docs/json -o src/api/types.ts",
35
35
  "prebuild": "rimraf dist",
36
36
  "build": "tsc",
37
+ "typecheck:tests": "tsc -p tsconfig.test.json",
37
38
  "test": "ava",
38
39
  "release": "semantic-release"
39
40
  },
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "noEmit": true
5
+ },
6
+ "include": ["src/**/*"]
7
+ }
@@ -1,5 +0,0 @@
1
- export interface AuthInterface<AuthOptions extends any[], Authentication extends any> {
2
- (...args: AuthOptions): Promise<Authentication>;
3
- hook: (...args: AuthOptions) => Promise<Authentication>;
4
- }
5
- //# sourceMappingURL=AuthInterface.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"AuthInterface.d.ts","sourceRoot":"","sources":["../../src/types/AuthInterface.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa,CAC5B,WAAW,SAAS,GAAG,EAAE,EACzB,cAAc,SAAS,GAAG;IAE1B,CAAC,GAAG,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAChD,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;CACzD"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,5 +0,0 @@
1
- import type { AuthInterface } from "./AuthInterface.js";
2
- export interface StrategyInterface<StrategyOptions extends any[], AuthOptions extends any[] = any, Authentication extends any = any> {
3
- (...args: StrategyOptions): AuthInterface<AuthOptions, Authentication>;
4
- }
5
- //# sourceMappingURL=StrategyInterface.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"StrategyInterface.d.ts","sourceRoot":"","sources":["../../src/types/StrategyInterface.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,MAAM,WAAW,iBAAiB,CAChC,eAAe,SAAS,GAAG,EAAE,EAC7B,WAAW,SAAS,GAAG,EAAE,GAAG,GAAG,EAC/B,cAAc,SAAS,GAAG,GAAG,GAAG;IAEhC,CAAC,GAAG,IAAI,EAAE,eAAe,GAAG,aAAa,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;CACxE"}
@@ -1 +0,0 @@
1
- export {};