@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/brain/signatures.md +27 -1
- package/dist/index.cjs +136 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -2
- package/dist/index.d.ts +46 -2
- package/dist/index.js +134 -18
- package/dist/index.js.map +1 -1
- package/package.json +6 -4
package/brain/signatures.md
CHANGED
|
@@ -5,16 +5,41 @@
|
|
|
5
5
|
## @fonderie/client
|
|
6
6
|
|
|
7
7
|
```ts
|
|
8
|
+
interface IClientAuthConfig {
|
|
9
|
+
getRefreshToken?: () => string | undefined;
|
|
10
|
+
onTokensChanged?: (tokens: ITokens) => void;
|
|
11
|
+
onAuthError?: () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
8
14
|
interface IFonderieClientOptions {
|
|
9
15
|
baseUrl: string;
|
|
10
16
|
accessToken?: string;
|
|
11
17
|
workspaceId?: string;
|
|
18
|
+
cache?: ICache;
|
|
19
|
+
auth?: IClientAuthConfig;
|
|
12
20
|
}
|
|
13
21
|
|
|
14
22
|
interface IRequestConfig {
|
|
15
23
|
workspaceId?: string;
|
|
24
|
+
cache?: number | false;
|
|
25
|
+
bust?: boolean;
|
|
26
|
+
invalidate?: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface ICache {
|
|
30
|
+
get<T>(key: string): T | undefined;
|
|
31
|
+
set<T>(key: string, value: T, ttlMs: number): void;
|
|
32
|
+
dedupe<T>(key: string, fn: () => Promise<T>): Promise<T>;
|
|
33
|
+
invalidate(fragment: string): void;
|
|
34
|
+
clear(): void;
|
|
16
35
|
}
|
|
17
36
|
|
|
37
|
+
interface IMemoryCacheOptions {
|
|
38
|
+
defaultTtlMs?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function createMemoryCache(opts?: IMemoryCacheOptions): ICache & { defaultTtlMs: number; }
|
|
42
|
+
|
|
18
43
|
new FonderieClient(opts: IFonderieClientOptions): FonderieClient
|
|
19
44
|
.auth: AuthClient
|
|
20
45
|
.billing: BillingClient
|
|
@@ -23,8 +48,9 @@ new FonderieClient(opts: IFonderieClientOptions): FonderieClient
|
|
|
23
48
|
.webhooks: WebhooksClient
|
|
24
49
|
.customers: CustomersClient
|
|
25
50
|
.setAccessToken(token: string | undefined): void
|
|
51
|
+
.clearCache(): void
|
|
26
52
|
.setWorkspaceId(workspaceId: string | undefined): void
|
|
27
|
-
.request<T = unknown>(opts: { method: string; path: string; body?: unknown; workspaceId?: string | undefined; }): Promise<IApiResponse
|
|
53
|
+
.request<T = unknown>(opts: { method: string; path: string; body?: unknown; workspaceId?: string | undefined; cache?: number | false | undefined; bust?: boolean | undefined; invalidate?: string[] | undefined; }): Promise<IApiResponse<...>>
|
|
28
54
|
.get<T = unknown>(path: string, config?: IRequestConfig | undefined): Promise<IApiResponse<T>>
|
|
29
55
|
.post<T = unknown>(path: string, body?: unknown, config?: IRequestConfig | undefined): Promise<IApiResponse<T>>
|
|
30
56
|
.put<T = unknown>(path: string, body?: unknown, config?: IRequestConfig | undefined): Promise<IApiResponse<T>>
|
package/dist/index.cjs
CHANGED
|
@@ -29,10 +29,48 @@ __export(index_exports, {
|
|
|
29
29
|
FonderieApiError: () => FonderieApiError,
|
|
30
30
|
FonderieClient: () => FonderieClient,
|
|
31
31
|
WebhooksClient: () => WebhooksClient,
|
|
32
|
-
WorkspacesClient: () => WorkspacesClient
|
|
32
|
+
WorkspacesClient: () => WorkspacesClient,
|
|
33
|
+
createMemoryCache: () => createMemoryCache
|
|
33
34
|
});
|
|
34
35
|
module.exports = __toCommonJS(index_exports);
|
|
35
36
|
|
|
37
|
+
// src/cache.ts
|
|
38
|
+
function createMemoryCache(opts = {}) {
|
|
39
|
+
const store = /* @__PURE__ */ new Map();
|
|
40
|
+
const inflight = /* @__PURE__ */ new Map();
|
|
41
|
+
const defaultTtlMs = opts.defaultTtlMs ?? 6e4;
|
|
42
|
+
return {
|
|
43
|
+
defaultTtlMs,
|
|
44
|
+
get(key) {
|
|
45
|
+
const entry = store.get(key);
|
|
46
|
+
if (!entry) return void 0;
|
|
47
|
+
if (Date.now() > entry.expiresAt) {
|
|
48
|
+
store.delete(key);
|
|
49
|
+
return void 0;
|
|
50
|
+
}
|
|
51
|
+
return entry.value;
|
|
52
|
+
},
|
|
53
|
+
set(key, value, ttlMs) {
|
|
54
|
+
store.set(key, { value, expiresAt: Date.now() + ttlMs });
|
|
55
|
+
},
|
|
56
|
+
dedupe(key, fn) {
|
|
57
|
+
const existing = inflight.get(key);
|
|
58
|
+
if (existing) return existing;
|
|
59
|
+
const req = fn().finally(() => inflight.delete(key));
|
|
60
|
+
inflight.set(key, req);
|
|
61
|
+
return req;
|
|
62
|
+
},
|
|
63
|
+
invalidate(fragment) {
|
|
64
|
+
for (const key of store.keys()) if (key.includes(fragment)) store.delete(key);
|
|
65
|
+
for (const key of inflight.keys()) if (key.includes(fragment)) inflight.delete(key);
|
|
66
|
+
},
|
|
67
|
+
clear() {
|
|
68
|
+
store.clear();
|
|
69
|
+
inflight.clear();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
36
74
|
// src/http.ts
|
|
37
75
|
var FonderieApiError = class extends Error {
|
|
38
76
|
constructor(reason, explanation, status, details) {
|
|
@@ -49,24 +87,52 @@ var FonderieApiError = class extends Error {
|
|
|
49
87
|
details;
|
|
50
88
|
};
|
|
51
89
|
var HttpClient = class {
|
|
52
|
-
constructor(baseUrl) {
|
|
90
|
+
constructor(baseUrl, deps = {}) {
|
|
53
91
|
this.baseUrl = baseUrl;
|
|
92
|
+
this.cache = deps.cache;
|
|
93
|
+
this.defaultTtlMs = deps.defaultTtlMs ?? 6e4;
|
|
94
|
+
this.refresh = deps.refresh;
|
|
54
95
|
}
|
|
55
96
|
baseUrl;
|
|
97
|
+
cache;
|
|
98
|
+
defaultTtlMs;
|
|
99
|
+
refresh;
|
|
56
100
|
async request(opts) {
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
101
|
+
const method = opts.method.toUpperCase();
|
|
102
|
+
const cache = this.cache;
|
|
103
|
+
if (cache && method === "GET" && opts.cache !== false) {
|
|
104
|
+
const key = `GET ${opts.path}::ws=${opts.workspaceId ?? ""}`;
|
|
105
|
+
if (!opts.bust) {
|
|
106
|
+
const hit = cache.get(key);
|
|
107
|
+
if (hit !== void 0) return hit;
|
|
108
|
+
}
|
|
109
|
+
const ttl = typeof opts.cache === "number" ? opts.cache : this.defaultTtlMs;
|
|
110
|
+
return cache.dedupe(key, async () => {
|
|
111
|
+
const data2 = await this.exec(opts);
|
|
112
|
+
cache.set(key, data2, ttl);
|
|
113
|
+
return data2;
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
const data = await this.exec(opts);
|
|
117
|
+
if (cache && method !== "GET") {
|
|
118
|
+
const resource = opts.path.split("?")[0]?.split("/").filter(Boolean)[0];
|
|
119
|
+
if (resource) cache.invalidate(`/${resource}`);
|
|
120
|
+
for (const fragment of opts.invalidate ?? []) cache.invalidate(fragment);
|
|
121
|
+
}
|
|
122
|
+
return data;
|
|
123
|
+
}
|
|
124
|
+
async exec(opts, retried = false) {
|
|
125
|
+
const headers = { "Content-Type": "application/json" };
|
|
60
126
|
if (opts.token) headers["Authorization"] = `Bearer ${opts.token}`;
|
|
61
127
|
if (opts.cookie) headers["Cookie"] = opts.cookie;
|
|
62
128
|
if (opts.workspaceId) headers["X-Workspace-ID"] = opts.workspaceId;
|
|
63
|
-
const fetchInit = {
|
|
64
|
-
method: opts.method,
|
|
65
|
-
headers,
|
|
66
|
-
credentials: "include"
|
|
67
|
-
};
|
|
129
|
+
const fetchInit = { method: opts.method, headers, credentials: "include" };
|
|
68
130
|
if (opts.body !== void 0) fetchInit.body = JSON.stringify(opts.body);
|
|
69
131
|
const res = await fetch(`${this.baseUrl}${opts.path}`, fetchInit);
|
|
132
|
+
if (res.status === 401 && this.refresh && !retried && !opts.path.startsWith("/auth/")) {
|
|
133
|
+
const newToken = await this.refresh();
|
|
134
|
+
if (newToken) return this.exec({ ...opts, token: newToken }, true);
|
|
135
|
+
}
|
|
70
136
|
if (res.status === 204) {
|
|
71
137
|
if (!res.ok) throw new FonderieApiError("unknown", res.statusText, res.status);
|
|
72
138
|
return void 0;
|
|
@@ -1064,10 +1130,19 @@ var FonderieClient = class {
|
|
|
1064
1130
|
http;
|
|
1065
1131
|
tokens;
|
|
1066
1132
|
workspaceId;
|
|
1133
|
+
cache;
|
|
1134
|
+
authConfig;
|
|
1135
|
+
refreshing = null;
|
|
1067
1136
|
constructor(opts) {
|
|
1068
|
-
this.http = new HttpClient(opts.baseUrl);
|
|
1069
1137
|
this.tokens = new TokenStore(opts.accessToken);
|
|
1070
1138
|
this.workspaceId = opts.workspaceId;
|
|
1139
|
+
this.cache = opts.cache;
|
|
1140
|
+
this.authConfig = opts.auth;
|
|
1141
|
+
this.http = new HttpClient(opts.baseUrl, {
|
|
1142
|
+
cache: opts.cache,
|
|
1143
|
+
defaultTtlMs: opts.cache?.defaultTtlMs,
|
|
1144
|
+
refresh: opts.auth ? () => this.doRefresh() : void 0
|
|
1145
|
+
});
|
|
1071
1146
|
this.auth = new AuthClient(this.http, this.tokens);
|
|
1072
1147
|
this.billing = new BillingClient(this.http, this.tokens);
|
|
1073
1148
|
this.workspaces = new WorkspacesClient(this.http, this.tokens);
|
|
@@ -1075,10 +1150,42 @@ var FonderieClient = class {
|
|
|
1075
1150
|
this.webhooks = new WebhooksClient(this.http, this.tokens);
|
|
1076
1151
|
this.customers = new CustomersClient(this.http, this.tokens);
|
|
1077
1152
|
}
|
|
1153
|
+
// Single-flight refresh: POST /auth/refresh with the app-supplied refresh
|
|
1154
|
+
// token, store the new access token, notify the app, return it for the retry.
|
|
1155
|
+
doRefresh() {
|
|
1156
|
+
if (this.refreshing) return this.refreshing;
|
|
1157
|
+
this.refreshing = (async () => {
|
|
1158
|
+
const refreshToken = this.authConfig?.getRefreshToken?.();
|
|
1159
|
+
if (!refreshToken) {
|
|
1160
|
+
this.authConfig?.onAuthError?.();
|
|
1161
|
+
return void 0;
|
|
1162
|
+
}
|
|
1163
|
+
try {
|
|
1164
|
+
const { result } = await this.auth.refreshTokens(refreshToken);
|
|
1165
|
+
const tokens = result.tokens;
|
|
1166
|
+
this.tokens.set(tokens.access);
|
|
1167
|
+
this.authConfig?.onTokensChanged?.(tokens);
|
|
1168
|
+
return tokens.access;
|
|
1169
|
+
} catch {
|
|
1170
|
+
this.tokens.set(void 0);
|
|
1171
|
+
this.authConfig?.onAuthError?.();
|
|
1172
|
+
return void 0;
|
|
1173
|
+
} finally {
|
|
1174
|
+
this.refreshing = null;
|
|
1175
|
+
}
|
|
1176
|
+
})();
|
|
1177
|
+
return this.refreshing;
|
|
1178
|
+
}
|
|
1078
1179
|
// The JWT used to authenticate every request — the typed modules and the
|
|
1079
|
-
// generic transport share one token store.
|
|
1180
|
+
// generic transport share one token store. Passing undefined signs out and
|
|
1181
|
+
// clears the cache (so no session's data survives a logout).
|
|
1080
1182
|
setAccessToken(token) {
|
|
1081
1183
|
this.tokens.set(token);
|
|
1184
|
+
if (!token) this.clearCache();
|
|
1185
|
+
}
|
|
1186
|
+
// Drop all cached responses (e.g. on switching accounts).
|
|
1187
|
+
clearCache() {
|
|
1188
|
+
this.cache?.clear();
|
|
1082
1189
|
}
|
|
1083
1190
|
// Default X-Workspace-ID for the generic transport, also propagated to the
|
|
1084
1191
|
// workspace-scoped modules so one call configures the whole client.
|
|
@@ -1099,23 +1206,32 @@ var FonderieClient = class {
|
|
|
1099
1206
|
path: opts.path,
|
|
1100
1207
|
body: opts.body,
|
|
1101
1208
|
token: this.tokens.get(),
|
|
1102
|
-
workspaceId: opts.workspaceId ?? this.workspaceId
|
|
1209
|
+
workspaceId: opts.workspaceId ?? this.workspaceId,
|
|
1210
|
+
cache: opts.cache,
|
|
1211
|
+
bust: opts.bust,
|
|
1212
|
+
invalidate: opts.invalidate
|
|
1103
1213
|
});
|
|
1104
1214
|
}
|
|
1105
1215
|
get(path, config) {
|
|
1106
|
-
return this.request({
|
|
1216
|
+
return this.request({
|
|
1217
|
+
method: "GET",
|
|
1218
|
+
path,
|
|
1219
|
+
workspaceId: config?.workspaceId,
|
|
1220
|
+
cache: config?.cache,
|
|
1221
|
+
bust: config?.bust
|
|
1222
|
+
});
|
|
1107
1223
|
}
|
|
1108
1224
|
post(path, body, config) {
|
|
1109
|
-
return this.request({ method: "POST", path, body, workspaceId: config?.workspaceId });
|
|
1225
|
+
return this.request({ method: "POST", path, body, workspaceId: config?.workspaceId, invalidate: config?.invalidate });
|
|
1110
1226
|
}
|
|
1111
1227
|
put(path, body, config) {
|
|
1112
|
-
return this.request({ method: "PUT", path, body, workspaceId: config?.workspaceId });
|
|
1228
|
+
return this.request({ method: "PUT", path, body, workspaceId: config?.workspaceId, invalidate: config?.invalidate });
|
|
1113
1229
|
}
|
|
1114
1230
|
patch(path, body, config) {
|
|
1115
|
-
return this.request({ method: "PATCH", path, body, workspaceId: config?.workspaceId });
|
|
1231
|
+
return this.request({ method: "PATCH", path, body, workspaceId: config?.workspaceId, invalidate: config?.invalidate });
|
|
1116
1232
|
}
|
|
1117
1233
|
delete(path, config) {
|
|
1118
|
-
return this.request({ method: "DELETE", path, workspaceId: config?.workspaceId });
|
|
1234
|
+
return this.request({ method: "DELETE", path, workspaceId: config?.workspaceId, invalidate: config?.invalidate });
|
|
1119
1235
|
}
|
|
1120
1236
|
};
|
|
1121
1237
|
|
|
@@ -1299,6 +1415,7 @@ var CourierAdminClient = class {
|
|
|
1299
1415
|
FonderieApiError,
|
|
1300
1416
|
FonderieClient,
|
|
1301
1417
|
WebhooksClient,
|
|
1302
|
-
WorkspacesClient
|
|
1418
|
+
WorkspacesClient,
|
|
1419
|
+
createMemoryCache
|
|
1303
1420
|
});
|
|
1304
1421
|
//# sourceMappingURL=index.cjs.map
|