@fonderie/client 0.3.0 → 0.4.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.d.ts CHANGED
@@ -1,3 +1,17 @@
1
+ interface ICache {
2
+ get<T>(key: string): T | undefined;
3
+ set<T>(key: string, value: T, ttlMs: number): void;
4
+ dedupe<T>(key: string, fn: () => Promise<T>): Promise<T>;
5
+ invalidate(fragment: string): void;
6
+ clear(): void;
7
+ }
8
+ interface IMemoryCacheOptions {
9
+ defaultTtlMs?: number;
10
+ }
11
+ declare function createMemoryCache(opts?: IMemoryCacheOptions): ICache & {
12
+ defaultTtlMs: number;
13
+ };
14
+
1
15
  declare class FonderieApiError extends Error {
2
16
  readonly reason: string;
3
17
  readonly explanation: string;
@@ -12,11 +26,23 @@ interface IRequestOptions {
12
26
  token?: string | undefined;
13
27
  cookie?: string | undefined;
14
28
  workspaceId?: string | undefined;
29
+ cache?: number | false | undefined;
30
+ bust?: boolean | undefined;
31
+ invalidate?: string[] | undefined;
32
+ }
33
+ interface IHttpDeps {
34
+ cache?: ICache | undefined;
35
+ defaultTtlMs?: number | undefined;
36
+ refresh?: (() => Promise<string | undefined>) | undefined;
15
37
  }
16
38
  declare class HttpClient {
17
39
  private baseUrl;
18
- constructor(baseUrl: string);
40
+ private cache;
41
+ private defaultTtlMs;
42
+ private refresh;
43
+ constructor(baseUrl: string, deps?: IHttpDeps);
19
44
  request<T>(opts: IRequestOptions): Promise<T>;
45
+ private exec;
20
46
  }
21
47
 
22
48
  declare class TokenStore {
@@ -834,13 +860,23 @@ declare class WorkspacesClient {
834
860
  updateSettings(input: IUpdateSettingsInput): Promise<IApiResponse<IWorkspaceSettingsResult>>;
835
861
  }
836
862
 
863
+ interface IClientAuthConfig {
864
+ getRefreshToken?: () => string | undefined;
865
+ onTokensChanged?: (tokens: ITokens) => void;
866
+ onAuthError?: () => void;
867
+ }
837
868
  interface IFonderieClientOptions {
838
869
  baseUrl: string;
839
870
  accessToken?: string;
840
871
  workspaceId?: string;
872
+ cache?: ICache;
873
+ auth?: IClientAuthConfig;
841
874
  }
842
875
  interface IRequestConfig {
843
876
  workspaceId?: string;
877
+ cache?: number | false;
878
+ bust?: boolean;
879
+ invalidate?: string[];
844
880
  }
845
881
  declare class FonderieClient {
846
882
  readonly auth: AuthClient;
@@ -852,14 +888,22 @@ declare class FonderieClient {
852
888
  private http;
853
889
  private tokens;
854
890
  private workspaceId;
891
+ private cache;
892
+ private authConfig;
893
+ private refreshing;
855
894
  constructor(opts: IFonderieClientOptions);
895
+ private doRefresh;
856
896
  setAccessToken(token: string | undefined): void;
897
+ clearCache(): void;
857
898
  setWorkspaceId(workspaceId: string | undefined): void;
858
899
  request<T = unknown>(opts: {
859
900
  method: string;
860
901
  path: string;
861
902
  body?: unknown;
862
903
  workspaceId?: string | undefined;
904
+ cache?: number | false | undefined;
905
+ bust?: boolean | undefined;
906
+ invalidate?: string[] | undefined;
863
907
  }): Promise<IApiResponse<T>>;
864
908
  get<T = unknown>(path: string, config?: IRequestConfig): Promise<IApiResponse<T>>;
865
909
  post<T = unknown>(path: string, body?: unknown, config?: IRequestConfig): Promise<IApiResponse<T>>;
@@ -930,4 +974,4 @@ declare class CourierAdminClient {
930
974
  rollback(type: string, input: IRollbackTemplateInput, locale?: string | null): Promise<IApiResponse<ITemplateEntry>>;
931
975
  }
932
976
 
933
- export { AuditClient, AuthClient, BillingClient, ConfigAdminClient, CourierAdminClient, type CustomerLabelType, type CustomerSex, type CustomerType, CustomersClient, FonderieApiError, FonderieClient, type IAcceptInvitationResult, type IAddAddressInput, type IAddEmailInput, type IAddPhoneInput, type IAddRelationshipInput, type IAddressDTO, type IApiError, type IApiResponse, type IAuditEventDTO, type IAuditPageResult, type IBlacklistCustomerInput, type IChangePasswordInput, type ICheckoutInput, type ICheckoutUrlResult, type IConfigAdminClientOptions, type IConfigEntry, type IConfigRevision, type ICourierAdminClientOptions, type ICreateCustomerInput, type ICreatePlanInput, type ICreateRoleInput, type ICreateWebhookEndpointInput, type ICreateWorkspaceInput, type ICustomerAddressDTO, type ICustomerAddressListResult, type ICustomerAddressResult, type ICustomerDTO, type ICustomerDetailD2DTO, type ICustomerDetailDTO, type ICustomerEmailDTO, type ICustomerEmailListResult, type ICustomerEmailResult, type ICustomerLabelDTO, type ICustomerLabelListResult, type ICustomerListResult, type ICustomerNoteDTO, type ICustomerNoteListResult, type ICustomerNoteResult, type ICustomerPhoneDTO, type ICustomerPhoneListResult, type ICustomerPhoneResult, type ICustomerRelationshipDTO, type ICustomerRelationshipExpandedD2DTO, type ICustomerRelationshipExpandedDTO, type ICustomerRelationshipListResult, type ICustomerRelationshipResult, type ICustomerResult, type ICustomerShallowDTO, type ICustomerTagListResult, type IFonderieClientOptions, type IGetCustomerInput, type IInvitationDTO, type IInvitationListResult, type IInviteEntry, type IInviteResult, type IListAuditEventsInput, type IListCustomersInput, type ILoginInput, type ILoginResult, type IMeResult, type IMemberDTO, type IMemberListResult, type IMfaEnabledResult, type IMfaSetupResult, type IPlanDTO, type IPlanFeature, type IPlanListResult, type IPlanResult, type IPortalUrlResult, type IRecordUsageInput, type IRefreshResult, type IRegisterInput, type IRegisterResult, type IRequestConfig, type IResendVerificationResult, type IResetPasswordInput, type IRevealSecretResult, type IRoleDTO, type IRoleListResult, type IRolePermission, type IRolePermissionInput, type IRolePermissionsResult, type IRoleResult, type IRollbackInput, type IRollbackTemplateInput, type ISecretEntry, type ISecretRevision, type ISetConfigInput, type ISetSecretInput, type ISetTemplateInput, type ISubscriptionDTO, type ISubscriptionResult, type ITemplateEntry, type ITemplateRevision, type ITestWebhookResult, type ITokens, type IUpdateCustomerInput, type IUpdatePlanInput, type IUpdatePreferencesInput, type IUpdateProfileInput, type IUpdateRoleInput, type IUpdateSettingsInput, type IUpdateWebhookEndpointInput, type IUpdateWorkspaceInput, type IUsageResult, type IUserDTO, type IUserPreferences, type IUserSkill, type IVerifyEmailResult, type IWebhookDeliveryDTO, type IWebhookDeliveryListResult, type IWebhookEndpointCreatedDTO, type IWebhookEndpointDTO, type IWebhookEndpointListResult, type IWorkspaceAddressDTO, type IWorkspaceDTO, type IWorkspaceListResult, type IWorkspaceResult, type IWorkspaceSettingsDTO, type IWorkspaceSettingsResult, type SubscriberType, WebhooksClient, WorkspacesClient };
977
+ export { AuditClient, AuthClient, BillingClient, ConfigAdminClient, CourierAdminClient, type CustomerLabelType, type CustomerSex, type CustomerType, CustomersClient, FonderieApiError, FonderieClient, type IAcceptInvitationResult, type IAddAddressInput, type IAddEmailInput, type IAddPhoneInput, type IAddRelationshipInput, type IAddressDTO, type IApiError, type IApiResponse, type IAuditEventDTO, type IAuditPageResult, type IBlacklistCustomerInput, type ICache, type IChangePasswordInput, type ICheckoutInput, type ICheckoutUrlResult, type IClientAuthConfig, type IConfigAdminClientOptions, type IConfigEntry, type IConfigRevision, type ICourierAdminClientOptions, type ICreateCustomerInput, type ICreatePlanInput, type ICreateRoleInput, type ICreateWebhookEndpointInput, type ICreateWorkspaceInput, type ICustomerAddressDTO, type ICustomerAddressListResult, type ICustomerAddressResult, type ICustomerDTO, type ICustomerDetailD2DTO, type ICustomerDetailDTO, type ICustomerEmailDTO, type ICustomerEmailListResult, type ICustomerEmailResult, type ICustomerLabelDTO, type ICustomerLabelListResult, type ICustomerListResult, type ICustomerNoteDTO, type ICustomerNoteListResult, type ICustomerNoteResult, type ICustomerPhoneDTO, type ICustomerPhoneListResult, type ICustomerPhoneResult, type ICustomerRelationshipDTO, type ICustomerRelationshipExpandedD2DTO, type ICustomerRelationshipExpandedDTO, type ICustomerRelationshipListResult, type ICustomerRelationshipResult, type ICustomerResult, type ICustomerShallowDTO, type ICustomerTagListResult, type IFonderieClientOptions, type IGetCustomerInput, type IInvitationDTO, type IInvitationListResult, type IInviteEntry, type IInviteResult, type IListAuditEventsInput, type IListCustomersInput, type ILoginInput, type ILoginResult, type IMeResult, type IMemberDTO, type IMemberListResult, type IMemoryCacheOptions, type IMfaEnabledResult, type IMfaSetupResult, type IPlanDTO, type IPlanFeature, type IPlanListResult, type IPlanResult, type IPortalUrlResult, type IRecordUsageInput, type IRefreshResult, type IRegisterInput, type IRegisterResult, type IRequestConfig, type IResendVerificationResult, type IResetPasswordInput, type IRevealSecretResult, type IRoleDTO, type IRoleListResult, type IRolePermission, type IRolePermissionInput, type IRolePermissionsResult, type IRoleResult, type IRollbackInput, type IRollbackTemplateInput, type ISecretEntry, type ISecretRevision, type ISetConfigInput, type ISetSecretInput, type ISetTemplateInput, type ISubscriptionDTO, type ISubscriptionResult, type ITemplateEntry, type ITemplateRevision, type ITestWebhookResult, type ITokens, type IUpdateCustomerInput, type IUpdatePlanInput, type IUpdatePreferencesInput, type IUpdateProfileInput, type IUpdateRoleInput, type IUpdateSettingsInput, type IUpdateWebhookEndpointInput, type IUpdateWorkspaceInput, type IUsageResult, type IUserDTO, type IUserPreferences, type IUserSkill, type IVerifyEmailResult, type IWebhookDeliveryDTO, type IWebhookDeliveryListResult, type IWebhookEndpointCreatedDTO, type IWebhookEndpointDTO, type IWebhookEndpointListResult, type IWorkspaceAddressDTO, type IWorkspaceDTO, type IWorkspaceListResult, type IWorkspaceResult, type IWorkspaceSettingsDTO, type IWorkspaceSettingsResult, type SubscriberType, WebhooksClient, WorkspacesClient, createMemoryCache };
package/dist/index.js CHANGED
@@ -1,3 +1,40 @@
1
+ // src/cache.ts
2
+ function createMemoryCache(opts = {}) {
3
+ const store = /* @__PURE__ */ new Map();
4
+ const inflight = /* @__PURE__ */ new Map();
5
+ const defaultTtlMs = opts.defaultTtlMs ?? 6e4;
6
+ return {
7
+ defaultTtlMs,
8
+ get(key) {
9
+ const entry = store.get(key);
10
+ if (!entry) return void 0;
11
+ if (Date.now() > entry.expiresAt) {
12
+ store.delete(key);
13
+ return void 0;
14
+ }
15
+ return entry.value;
16
+ },
17
+ set(key, value, ttlMs) {
18
+ store.set(key, { value, expiresAt: Date.now() + ttlMs });
19
+ },
20
+ dedupe(key, fn) {
21
+ const existing = inflight.get(key);
22
+ if (existing) return existing;
23
+ const req = fn().finally(() => inflight.delete(key));
24
+ inflight.set(key, req);
25
+ return req;
26
+ },
27
+ invalidate(fragment) {
28
+ for (const key of store.keys()) if (key.includes(fragment)) store.delete(key);
29
+ for (const key of inflight.keys()) if (key.includes(fragment)) inflight.delete(key);
30
+ },
31
+ clear() {
32
+ store.clear();
33
+ inflight.clear();
34
+ }
35
+ };
36
+ }
37
+
1
38
  // src/http.ts
2
39
  var FonderieApiError = class extends Error {
3
40
  constructor(reason, explanation, status, details) {
@@ -14,24 +51,52 @@ var FonderieApiError = class extends Error {
14
51
  details;
15
52
  };
16
53
  var HttpClient = class {
17
- constructor(baseUrl) {
54
+ constructor(baseUrl, deps = {}) {
18
55
  this.baseUrl = baseUrl;
56
+ this.cache = deps.cache;
57
+ this.defaultTtlMs = deps.defaultTtlMs ?? 6e4;
58
+ this.refresh = deps.refresh;
19
59
  }
20
60
  baseUrl;
61
+ cache;
62
+ defaultTtlMs;
63
+ refresh;
21
64
  async request(opts) {
22
- const headers = {
23
- "Content-Type": "application/json"
24
- };
65
+ const method = opts.method.toUpperCase();
66
+ const cache = this.cache;
67
+ if (cache && method === "GET" && opts.cache !== false) {
68
+ const key = `GET ${opts.path}::ws=${opts.workspaceId ?? ""}`;
69
+ if (!opts.bust) {
70
+ const hit = cache.get(key);
71
+ if (hit !== void 0) return hit;
72
+ }
73
+ const ttl = typeof opts.cache === "number" ? opts.cache : this.defaultTtlMs;
74
+ return cache.dedupe(key, async () => {
75
+ const data2 = await this.exec(opts);
76
+ cache.set(key, data2, ttl);
77
+ return data2;
78
+ });
79
+ }
80
+ const data = await this.exec(opts);
81
+ if (cache && method !== "GET") {
82
+ const resource = opts.path.split("?")[0]?.split("/").filter(Boolean)[0];
83
+ if (resource) cache.invalidate(`/${resource}`);
84
+ for (const fragment of opts.invalidate ?? []) cache.invalidate(fragment);
85
+ }
86
+ return data;
87
+ }
88
+ async exec(opts, retried = false) {
89
+ const headers = { "Content-Type": "application/json" };
25
90
  if (opts.token) headers["Authorization"] = `Bearer ${opts.token}`;
26
91
  if (opts.cookie) headers["Cookie"] = opts.cookie;
27
92
  if (opts.workspaceId) headers["X-Workspace-ID"] = opts.workspaceId;
28
- const fetchInit = {
29
- method: opts.method,
30
- headers,
31
- credentials: "include"
32
- };
93
+ const fetchInit = { method: opts.method, headers, credentials: "include" };
33
94
  if (opts.body !== void 0) fetchInit.body = JSON.stringify(opts.body);
34
95
  const res = await fetch(`${this.baseUrl}${opts.path}`, fetchInit);
96
+ if (res.status === 401 && this.refresh && !retried && !opts.path.startsWith("/auth/")) {
97
+ const newToken = await this.refresh();
98
+ if (newToken) return this.exec({ ...opts, token: newToken }, true);
99
+ }
35
100
  if (res.status === 204) {
36
101
  if (!res.ok) throw new FonderieApiError("unknown", res.statusText, res.status);
37
102
  return void 0;
@@ -1029,10 +1094,19 @@ var FonderieClient = class {
1029
1094
  http;
1030
1095
  tokens;
1031
1096
  workspaceId;
1097
+ cache;
1098
+ authConfig;
1099
+ refreshing = null;
1032
1100
  constructor(opts) {
1033
- this.http = new HttpClient(opts.baseUrl);
1034
1101
  this.tokens = new TokenStore(opts.accessToken);
1035
1102
  this.workspaceId = opts.workspaceId;
1103
+ this.cache = opts.cache;
1104
+ this.authConfig = opts.auth;
1105
+ this.http = new HttpClient(opts.baseUrl, {
1106
+ cache: opts.cache,
1107
+ defaultTtlMs: opts.cache?.defaultTtlMs,
1108
+ refresh: opts.auth ? () => this.doRefresh() : void 0
1109
+ });
1036
1110
  this.auth = new AuthClient(this.http, this.tokens);
1037
1111
  this.billing = new BillingClient(this.http, this.tokens);
1038
1112
  this.workspaces = new WorkspacesClient(this.http, this.tokens);
@@ -1040,10 +1114,42 @@ var FonderieClient = class {
1040
1114
  this.webhooks = new WebhooksClient(this.http, this.tokens);
1041
1115
  this.customers = new CustomersClient(this.http, this.tokens);
1042
1116
  }
1117
+ // Single-flight refresh: POST /auth/refresh with the app-supplied refresh
1118
+ // token, store the new access token, notify the app, return it for the retry.
1119
+ doRefresh() {
1120
+ if (this.refreshing) return this.refreshing;
1121
+ this.refreshing = (async () => {
1122
+ const refreshToken = this.authConfig?.getRefreshToken?.();
1123
+ if (!refreshToken) {
1124
+ this.authConfig?.onAuthError?.();
1125
+ return void 0;
1126
+ }
1127
+ try {
1128
+ const { result } = await this.auth.refreshTokens(refreshToken);
1129
+ const tokens = result.tokens;
1130
+ this.tokens.set(tokens.access);
1131
+ this.authConfig?.onTokensChanged?.(tokens);
1132
+ return tokens.access;
1133
+ } catch {
1134
+ this.tokens.set(void 0);
1135
+ this.authConfig?.onAuthError?.();
1136
+ return void 0;
1137
+ } finally {
1138
+ this.refreshing = null;
1139
+ }
1140
+ })();
1141
+ return this.refreshing;
1142
+ }
1043
1143
  // The JWT used to authenticate every request — the typed modules and the
1044
- // generic transport share one token store. Pass undefined to sign out.
1144
+ // generic transport share one token store. Passing undefined signs out and
1145
+ // clears the cache (so no session's data survives a logout).
1045
1146
  setAccessToken(token) {
1046
1147
  this.tokens.set(token);
1148
+ if (!token) this.clearCache();
1149
+ }
1150
+ // Drop all cached responses (e.g. on switching accounts).
1151
+ clearCache() {
1152
+ this.cache?.clear();
1047
1153
  }
1048
1154
  // Default X-Workspace-ID for the generic transport, also propagated to the
1049
1155
  // workspace-scoped modules so one call configures the whole client.
@@ -1064,23 +1170,32 @@ var FonderieClient = class {
1064
1170
  path: opts.path,
1065
1171
  body: opts.body,
1066
1172
  token: this.tokens.get(),
1067
- workspaceId: opts.workspaceId ?? this.workspaceId
1173
+ workspaceId: opts.workspaceId ?? this.workspaceId,
1174
+ cache: opts.cache,
1175
+ bust: opts.bust,
1176
+ invalidate: opts.invalidate
1068
1177
  });
1069
1178
  }
1070
1179
  get(path, config) {
1071
- return this.request({ method: "GET", path, workspaceId: config?.workspaceId });
1180
+ return this.request({
1181
+ method: "GET",
1182
+ path,
1183
+ workspaceId: config?.workspaceId,
1184
+ cache: config?.cache,
1185
+ bust: config?.bust
1186
+ });
1072
1187
  }
1073
1188
  post(path, body, config) {
1074
- return this.request({ method: "POST", path, body, workspaceId: config?.workspaceId });
1189
+ return this.request({ method: "POST", path, body, workspaceId: config?.workspaceId, invalidate: config?.invalidate });
1075
1190
  }
1076
1191
  put(path, body, config) {
1077
- return this.request({ method: "PUT", path, body, workspaceId: config?.workspaceId });
1192
+ return this.request({ method: "PUT", path, body, workspaceId: config?.workspaceId, invalidate: config?.invalidate });
1078
1193
  }
1079
1194
  patch(path, body, config) {
1080
- return this.request({ method: "PATCH", path, body, workspaceId: config?.workspaceId });
1195
+ return this.request({ method: "PATCH", path, body, workspaceId: config?.workspaceId, invalidate: config?.invalidate });
1081
1196
  }
1082
1197
  delete(path, config) {
1083
- return this.request({ method: "DELETE", path, workspaceId: config?.workspaceId });
1198
+ return this.request({ method: "DELETE", path, workspaceId: config?.workspaceId, invalidate: config?.invalidate });
1084
1199
  }
1085
1200
  };
1086
1201
 
@@ -1263,6 +1378,7 @@ export {
1263
1378
  FonderieApiError,
1264
1379
  FonderieClient,
1265
1380
  WebhooksClient,
1266
- WorkspacesClient
1381
+ WorkspacesClient,
1382
+ createMemoryCache
1267
1383
  };
1268
1384
  //# sourceMappingURL=index.js.map