@fonderie/client 0.2.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,9 +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;
871
+ workspaceId?: string;
872
+ cache?: ICache;
873
+ auth?: IClientAuthConfig;
874
+ }
875
+ interface IRequestConfig {
876
+ workspaceId?: string;
877
+ cache?: number | false;
878
+ bust?: boolean;
879
+ invalidate?: string[];
840
880
  }
841
881
  declare class FonderieClient {
842
882
  readonly auth: AuthClient;
@@ -846,7 +886,30 @@ declare class FonderieClient {
846
886
  readonly webhooks: WebhooksClient;
847
887
  readonly customers: CustomersClient;
848
888
  private http;
889
+ private tokens;
890
+ private workspaceId;
891
+ private cache;
892
+ private authConfig;
893
+ private refreshing;
849
894
  constructor(opts: IFonderieClientOptions);
895
+ private doRefresh;
896
+ setAccessToken(token: string | undefined): void;
897
+ clearCache(): void;
898
+ setWorkspaceId(workspaceId: string | undefined): void;
899
+ request<T = unknown>(opts: {
900
+ method: string;
901
+ path: string;
902
+ body?: unknown;
903
+ workspaceId?: string | undefined;
904
+ cache?: number | false | undefined;
905
+ bust?: boolean | undefined;
906
+ invalidate?: string[] | undefined;
907
+ }): Promise<IApiResponse<T>>;
908
+ get<T = unknown>(path: string, config?: IRequestConfig): Promise<IApiResponse<T>>;
909
+ post<T = unknown>(path: string, body?: unknown, config?: IRequestConfig): Promise<IApiResponse<T>>;
910
+ put<T = unknown>(path: string, body?: unknown, config?: IRequestConfig): Promise<IApiResponse<T>>;
911
+ patch<T = unknown>(path: string, body?: unknown, config?: IRequestConfig): Promise<IApiResponse<T>>;
912
+ delete<T = unknown>(path: string, config?: IRequestConfig): Promise<IApiResponse<T>>;
850
913
  }
851
914
 
852
915
  interface ISetConfigInput {
@@ -911,4 +974,4 @@ declare class CourierAdminClient {
911
974
  rollback(type: string, input: IRollbackTemplateInput, locale?: string | null): Promise<IApiResponse<ITemplateEntry>>;
912
975
  }
913
976
 
914
- 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 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;
@@ -1027,15 +1092,110 @@ var FonderieClient = class {
1027
1092
  webhooks;
1028
1093
  customers;
1029
1094
  http;
1095
+ tokens;
1096
+ workspaceId;
1097
+ cache;
1098
+ authConfig;
1099
+ refreshing = null;
1030
1100
  constructor(opts) {
1031
- this.http = new HttpClient(opts.baseUrl);
1032
- const tokens = new TokenStore(opts.accessToken);
1033
- this.auth = new AuthClient(this.http, tokens);
1034
- this.billing = new BillingClient(this.http, tokens);
1035
- this.workspaces = new WorkspacesClient(this.http, tokens);
1036
- this.audit = new AuditClient(this.http, tokens);
1037
- this.webhooks = new WebhooksClient(this.http, tokens);
1038
- this.customers = new CustomersClient(this.http, tokens);
1101
+ this.tokens = new TokenStore(opts.accessToken);
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
+ });
1110
+ this.auth = new AuthClient(this.http, this.tokens);
1111
+ this.billing = new BillingClient(this.http, this.tokens);
1112
+ this.workspaces = new WorkspacesClient(this.http, this.tokens);
1113
+ this.audit = new AuditClient(this.http, this.tokens);
1114
+ this.webhooks = new WebhooksClient(this.http, this.tokens);
1115
+ this.customers = new CustomersClient(this.http, this.tokens);
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
+ }
1143
+ // The JWT used to authenticate every request — the typed modules and the
1144
+ // generic transport share one token store. Passing undefined signs out and
1145
+ // clears the cache (so no session's data survives a logout).
1146
+ setAccessToken(token) {
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();
1153
+ }
1154
+ // Default X-Workspace-ID for the generic transport, also propagated to the
1155
+ // workspace-scoped modules so one call configures the whole client.
1156
+ setWorkspaceId(workspaceId) {
1157
+ this.workspaceId = workspaceId;
1158
+ this.billing.setWorkspaceId(workspaceId);
1159
+ this.workspaces.setWorkspaceId(workspaceId);
1160
+ this.customers.setWorkspaceId(workspaceId);
1161
+ }
1162
+ // ── Generic transport ──────────────────────────────────────────────────────
1163
+ // A Fonderie-aware HTTP client for endpoints outside the typed modules (an
1164
+ // app's own routes on the same backend). It attaches the shared JWT and
1165
+ // X-Workspace-ID automatically and returns Fonderie's { reason, explanation,
1166
+ // result } envelope — so you don't hand-roll auth for custom endpoints.
1167
+ request(opts) {
1168
+ return this.http.request({
1169
+ method: opts.method,
1170
+ path: opts.path,
1171
+ body: opts.body,
1172
+ token: this.tokens.get(),
1173
+ workspaceId: opts.workspaceId ?? this.workspaceId,
1174
+ cache: opts.cache,
1175
+ bust: opts.bust,
1176
+ invalidate: opts.invalidate
1177
+ });
1178
+ }
1179
+ get(path, config) {
1180
+ return this.request({
1181
+ method: "GET",
1182
+ path,
1183
+ workspaceId: config?.workspaceId,
1184
+ cache: config?.cache,
1185
+ bust: config?.bust
1186
+ });
1187
+ }
1188
+ post(path, body, config) {
1189
+ return this.request({ method: "POST", path, body, workspaceId: config?.workspaceId, invalidate: config?.invalidate });
1190
+ }
1191
+ put(path, body, config) {
1192
+ return this.request({ method: "PUT", path, body, workspaceId: config?.workspaceId, invalidate: config?.invalidate });
1193
+ }
1194
+ patch(path, body, config) {
1195
+ return this.request({ method: "PATCH", path, body, workspaceId: config?.workspaceId, invalidate: config?.invalidate });
1196
+ }
1197
+ delete(path, config) {
1198
+ return this.request({ method: "DELETE", path, workspaceId: config?.workspaceId, invalidate: config?.invalidate });
1039
1199
  }
1040
1200
  };
1041
1201
 
@@ -1218,6 +1378,7 @@ export {
1218
1378
  FonderieApiError,
1219
1379
  FonderieClient,
1220
1380
  WebhooksClient,
1221
- WorkspacesClient
1381
+ WorkspacesClient,
1382
+ createMemoryCache
1222
1383
  };
1223
1384
  //# sourceMappingURL=index.js.map