@fonderie/client 0.17.1 → 0.19.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.
@@ -48,6 +48,7 @@ new FonderieClient(opts: IFonderieClientOptions): FonderieClient
48
48
  .audit: AuditClient
49
49
  .webhooks: WebhooksClient
50
50
  .customers: CustomersClient
51
+ .media: MediaClient
51
52
  .setAccessToken(token: string | undefined): void
52
53
  .clearCache(): void
53
54
  .setWorkspaceId(workspaceId: string | undefined): void
@@ -58,11 +59,12 @@ new FonderieClient(opts: IFonderieClientOptions): FonderieClient
58
59
  .patch<T = unknown>(path: string, body?: unknown, config?: IRequestConfig | undefined): Promise<IApiResponse<T>>
59
60
  .delete<T = unknown>(path: string, config?: IRequestConfig | undefined): Promise<IApiResponse<T>>
60
61
 
61
- new FonderieApiError(reason: string, explanation: string, status: number, details?: unknown): FonderieApiError
62
+ new FonderieApiError(reason: string, explanation: string, status: number, details?: unknown, requestId?: string | undefined): FonderieApiError
62
63
  .reason: string
63
64
  .explanation: string
64
65
  .status: number
65
66
  .details: unknown
67
+ .requestId: string | undefined
66
68
  .name: string
67
69
  .message: string
68
70
  .stack: string
@@ -377,6 +379,20 @@ new CustomersClient(http: HttpClient, tokens: TokenStore): CustomersClient
377
379
  .listLabels(type: CustomerLabelType, opts?: IReadOptions | undefined): Promise<IApiResponse<ICustomerLabelListResult>>
378
380
  .removeLabel(labelId: string): Promise<IApiResponse<undefined>>
379
381
 
382
+ interface IUploadMediaInput {
383
+ dataBase64: string;
384
+ purpose?: string;
385
+ ownerType?: string;
386
+ ownerId?: string;
387
+ }
388
+
389
+ new MediaClient(http: HttpClient, tokens: TokenStore): MediaClient
390
+ .setAccessToken(token: string | undefined): void
391
+ .upload(input: IUploadMediaInput): Promise<IApiResponse<IMediaAssetResult>>
392
+ .delete(id: string): Promise<IApiResponse<{ id: string; }>>
393
+ .assetUrl(id: string): string
394
+ .assetIdFromUrl(url: string | null | undefined): string | null
395
+
380
396
  interface ICreateWebhookEndpointInput {
381
397
  url: string;
382
398
  events?: string[];
@@ -800,6 +816,21 @@ interface ILoginResult {
800
816
  requiresVerification?: boolean;
801
817
  }
802
818
 
819
+ interface IMediaAssetDTO {
820
+ id: string;
821
+ url: string;
822
+ contentType: string;
823
+ byteSize: number;
824
+ ownerType: string;
825
+ ownerId: string;
826
+ purpose: string;
827
+ createdAt: string;
828
+ }
829
+
830
+ interface IMediaAssetResult {
831
+ asset: IMediaAssetDTO;
832
+ }
833
+
803
834
  interface IMemberDTO {
804
835
  userId: string;
805
836
  workspaceId: string;
package/dist/index.cjs CHANGED
@@ -28,6 +28,7 @@ __export(index_exports, {
28
28
  CustomersClient: () => CustomersClient,
29
29
  FonderieApiError: () => FonderieApiError,
30
30
  FonderieClient: () => FonderieClient,
31
+ MediaClient: () => MediaClient,
31
32
  WebhooksClient: () => WebhooksClient,
32
33
  WorkspacesClient: () => WorkspacesClient,
33
34
  createMemoryCache: () => createMemoryCache,
@@ -74,19 +75,26 @@ function createMemoryCache(opts = {}) {
74
75
 
75
76
  // src/http.ts
76
77
  var FonderieApiError = class extends Error {
77
- constructor(reason, explanation, status, details) {
78
+ constructor(reason, explanation, status, details, requestId) {
78
79
  super(explanation);
79
80
  this.reason = reason;
80
81
  this.explanation = explanation;
81
82
  this.status = status;
82
83
  this.details = details;
84
+ this.requestId = requestId;
83
85
  this.name = "FonderieApiError";
84
86
  }
85
87
  reason;
86
88
  explanation;
87
89
  status;
88
90
  details;
91
+ requestId;
89
92
  };
93
+ function newRequestId() {
94
+ const c = globalThis.crypto;
95
+ if (c?.randomUUID) return c.randomUUID();
96
+ return `r-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
97
+ }
90
98
  var HttpClient = class {
91
99
  constructor(baseUrl, deps = {}) {
92
100
  this.baseUrl = baseUrl;
@@ -98,6 +106,12 @@ var HttpClient = class {
98
106
  cache;
99
107
  defaultTtlMs;
100
108
  refresh;
109
+ // Absolute URL for a path on this client's origin — for endpoints a browser
110
+ // navigates to or embeds (e.g. a public `<img src>`) rather than fetches
111
+ // through request(). Mirrors the `${baseUrl}${path}` join request() uses.
112
+ absolute(path) {
113
+ return `${this.baseUrl}${path}`;
114
+ }
101
115
  // Drop all cached responses — sign-out must not leave one session's data
102
116
  // servable to the next.
103
117
  clearCache() {
@@ -128,7 +142,11 @@ var HttpClient = class {
128
142
  return data;
129
143
  }
130
144
  async exec(opts, retried = false) {
131
- const headers = { "Content-Type": "application/json" };
145
+ const requestId = newRequestId();
146
+ const headers = {
147
+ "Content-Type": "application/json",
148
+ "X-Request-ID": requestId
149
+ };
132
150
  if (opts.token) headers["Authorization"] = `Bearer ${opts.token}`;
133
151
  if (opts.cookie) headers["Cookie"] = opts.cookie;
134
152
  if (opts.workspaceId) headers["X-Workspace-ID"] = opts.workspaceId;
@@ -136,18 +154,19 @@ var HttpClient = class {
136
154
  const fetchInit = { method: opts.method, headers, credentials: "include" };
137
155
  if (opts.body !== void 0) fetchInit.body = JSON.stringify(opts.body);
138
156
  const res = await fetch(`${this.baseUrl}${opts.path}`, fetchInit);
157
+ const rid = res.headers.get("x-request-id") ?? requestId;
139
158
  if (res.status === 401 && this.refresh && !retried && !opts.path.startsWith("/auth/")) {
140
159
  const newToken = await this.refresh();
141
160
  if (newToken) return this.exec({ ...opts, token: newToken }, true);
142
161
  }
143
162
  if (res.status === 204) {
144
- if (!res.ok) throw new FonderieApiError("unknown", res.statusText, res.status);
163
+ if (!res.ok) throw new FonderieApiError("unknown", res.statusText, res.status, void 0, rid);
145
164
  return void 0;
146
165
  }
147
166
  const data = await res.json();
148
167
  if (!res.ok || res.status === 202) {
149
168
  const err = data;
150
- throw new FonderieApiError(err.reason, err.explanation, res.status, err.details);
169
+ throw new FonderieApiError(err.reason, err.explanation, res.status, err.details, rid);
151
170
  }
152
171
  return data;
153
172
  }
@@ -1020,6 +1039,55 @@ var CustomersClient = class {
1020
1039
  }
1021
1040
  };
1022
1041
 
1042
+ // src/modules/media.ts
1043
+ var MediaClient = class {
1044
+ constructor(http, tokens) {
1045
+ this.http = http;
1046
+ this.tokens = tokens;
1047
+ }
1048
+ http;
1049
+ tokens;
1050
+ setAccessToken(token) {
1051
+ this.tokens.set(token);
1052
+ }
1053
+ // POST /media — store an image; returns the asset with its monomorphic
1054
+ // /media/:id url. requireAuth server-side.
1055
+ upload(input) {
1056
+ return this.http.request({
1057
+ method: "POST",
1058
+ path: "/media",
1059
+ body: input,
1060
+ token: this.tokens.get()
1061
+ });
1062
+ }
1063
+ // DELETE /media/:id — uploader-only (403 for anyone else).
1064
+ delete(id) {
1065
+ return this.http.request({
1066
+ method: "DELETE",
1067
+ path: `/media/${encodeURIComponent(id)}`,
1068
+ token: this.tokens.get()
1069
+ });
1070
+ }
1071
+ // Absolute URL for a stored asset — an `<img src>` target. The GET /media/:id
1072
+ // route is public and cached (it can't carry a Bearer token), so turn an
1073
+ // upload's `asset.id` into a browser-loadable URL with this, and use it as
1074
+ // the avatarUrl handed to auth's updateProfile.
1075
+ assetUrl(id) {
1076
+ return this.http.absolute(`/media/${encodeURIComponent(id)}`);
1077
+ }
1078
+ // Inverse of assetUrl: pull the asset id out of one of THIS brick's
1079
+ // /media/:id URLs (absolute or relative); null for empty/external URLs. Lets
1080
+ // avatar cleanup delete only assets we minted (matched as a UUID), never a
1081
+ // placeholder or third-party avatar URL.
1082
+ assetIdFromUrl(url) {
1083
+ if (!url) return null;
1084
+ const m = url.match(
1085
+ /\/media\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:[/?#]|$)/i
1086
+ );
1087
+ return m ? m[1] : null;
1088
+ }
1089
+ };
1090
+
1023
1091
  // src/modules/webhooks.ts
1024
1092
  var WebhooksClient = class {
1025
1093
  constructor(http, tokens) {
@@ -1355,6 +1423,7 @@ var FonderieClient = class {
1355
1423
  audit;
1356
1424
  webhooks;
1357
1425
  customers;
1426
+ media;
1358
1427
  http;
1359
1428
  tokens;
1360
1429
  workspaceId;
@@ -1377,6 +1446,7 @@ var FonderieClient = class {
1377
1446
  this.audit = new AuditClient(this.http, this.tokens);
1378
1447
  this.webhooks = new WebhooksClient(this.http, this.tokens);
1379
1448
  this.customers = new CustomersClient(this.http, this.tokens);
1449
+ this.media = new MediaClient(this.http, this.tokens);
1380
1450
  if (opts.workspaceId !== void 0) this.setWorkspaceId(opts.workspaceId);
1381
1451
  }
1382
1452
  // Single-flight refresh: POST /auth/refresh with the app-supplied refresh
@@ -1665,6 +1735,7 @@ function isMfaRequired(result) {
1665
1735
  CustomersClient,
1666
1736
  FonderieApiError,
1667
1737
  FonderieClient,
1738
+ MediaClient,
1668
1739
  WebhooksClient,
1669
1740
  WorkspacesClient,
1670
1741
  createMemoryCache,