@seedgrid/fe-core 2026.4.18 → 2026.4.20

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.
@@ -0,0 +1,50 @@
1
+ import { type ExceptionContext } from "./api-exception-handler";
2
+ type Primitive = boolean | number | string;
3
+ type QueryValue = Primitive | null | undefined;
4
+ type QueryParams = Record<string, QueryValue>;
5
+ type NextFetchOptions = {
6
+ revalidate?: false | number;
7
+ tags?: string[];
8
+ };
9
+ export type ApiParseMode = "json" | "response" | "text";
10
+ export type ApiRetryOptions = {
11
+ attempts?: number;
12
+ baseDelayMs?: number;
13
+ retryOn?: number[];
14
+ };
15
+ export type ApiRequestOptions = Omit<RequestInit, "body" | "headers"> & {
16
+ body?: BodyInit | Record<string, unknown> | null;
17
+ headers?: HeadersInit;
18
+ next?: NextFetchOptions;
19
+ parseAs?: ApiParseMode;
20
+ query?: QueryParams;
21
+ requireAuth?: boolean;
22
+ retry?: boolean | ApiRetryOptions;
23
+ captureExceptions?: boolean;
24
+ };
25
+ export type ApiClientConfig = {
26
+ baseUrl: string;
27
+ defaultHeaders?: HeadersInit;
28
+ defaultCache?: RequestCache;
29
+ defaultNext?: NextFetchOptions;
30
+ getAccessToken?: () => Promise<string | null>;
31
+ refreshAccessToken?: () => Promise<string | null>;
32
+ onAuthFailure?: (context: ExceptionContext) => void | Promise<void>;
33
+ onException?: (error: Error, context: ExceptionContext) => void | Promise<void>;
34
+ };
35
+ export declare class ApiClientError extends Error {
36
+ readonly responseBody: unknown;
37
+ readonly status: number;
38
+ constructor(message: string, status: number, responseBody?: unknown);
39
+ }
40
+ export declare function createApiClient(config: ApiClientConfig): {
41
+ request: <T = unknown>(path: string, options?: ApiRequestOptions) => Promise<T>;
42
+ get: <T = unknown>(path: string, options?: ApiRequestOptions) => Promise<T>;
43
+ post: <T = unknown>(path: string, options?: ApiRequestOptions) => Promise<T>;
44
+ put: <T = unknown>(path: string, options?: ApiRequestOptions) => Promise<T>;
45
+ patch: <T = unknown>(path: string, options?: ApiRequestOptions) => Promise<T>;
46
+ delete: <T = unknown>(path: string, options?: ApiRequestOptions) => Promise<T>;
47
+ };
48
+ export declare function formatCurlCommand(url: string, init: RequestInit): string;
49
+ export {};
50
+ //# sourceMappingURL=api-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../../src/http/api-client.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AAEjC,KAAK,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AAE3C,KAAK,UAAU,GAAG,SAAS,GAAG,IAAI,GAAG,SAAS,CAAC;AAE/C,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAE9C,KAAK,gBAAgB,GAAG;IACtB,UAAU,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;AAExD,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG;IACtE,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACjD,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,GAAG,eAAe,CAAC;IAClC,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,WAAW,CAAC;IAC7B,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9C,kBAAkB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAClD,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,WAAW,CAAC,EAAE,CACZ,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,gBAAgB,KACtB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B,CAAC;AAEF,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,OAAO;CAMpE;AAID,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe;cAK9B,CAAC,kBAChB,MAAM,YACH,iBAAiB,KACzB,OAAO,CAAC,CAAC,CAAC;UAgIL,CAAC,kBAAkB,MAAM,YAAW,iBAAiB;WAKpD,CAAC,kBAAkB,MAAM,YAAW,iBAAiB;UAKtD,CAAC,kBAAkB,MAAM,YAAW,iBAAiB;YAKnD,CAAC,kBAAkB,MAAM,YAAW,iBAAiB;aAKpD,CAAC,kBAAkB,MAAM,YAAW,iBAAiB;EAMjE;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,UAmB/D"}
@@ -0,0 +1,281 @@
1
+ // seedgrid:managed
2
+ import { captureException, runGuarded, suppressException, } from "./api-exception-handler";
3
+ export class ApiClientError extends Error {
4
+ responseBody;
5
+ status;
6
+ constructor(message, status, responseBody) {
7
+ super(message);
8
+ this.name = "ApiClientError";
9
+ this.status = status;
10
+ this.responseBody = responseBody;
11
+ }
12
+ }
13
+ const DEFAULT_RETRY_STATUSES = [429, 502, 503, 504];
14
+ export function createApiClient(config) {
15
+ function shouldCapture(options) {
16
+ return options.captureExceptions !== false;
17
+ }
18
+ async function request(path, options = {}) {
19
+ return runGuarded(async () => {
20
+ const retry = resolveRetryOptions(options.retry);
21
+ let refreshedToken = false;
22
+ let latestAccessToken;
23
+ const requiresAuth = options.requireAuth ?? true;
24
+ for (let attempt = 0; attempt <= retry.attempts; attempt += 1) {
25
+ try {
26
+ latestAccessToken ??= await readAccessToken(config.getAccessToken, requiresAuth);
27
+ const url = buildUrl(config.baseUrl, path, options.query);
28
+ const requestInit = buildRequestInit(config, options, latestAccessToken);
29
+ if (process.env.NODE_ENV !== "production") {
30
+ console.debug(formatCurlCommand(url, requestInit));
31
+ }
32
+ const response = await fetch(url, requestInit);
33
+ if (response.status === 401 &&
34
+ !refreshedToken &&
35
+ typeof config.refreshAccessToken === "function" &&
36
+ requiresAuth) {
37
+ refreshedToken = true;
38
+ latestAccessToken = await config.refreshAccessToken();
39
+ if (!latestAccessToken) {
40
+ const responseBody = await readResponseBody(response);
41
+ const error = new ApiClientError(`API request failed with status ${response.status}`, response.status, responseBody);
42
+ const context = {
43
+ area: "api",
44
+ source: path,
45
+ retryable: false,
46
+ status: response.status,
47
+ };
48
+ await config.onAuthFailure?.(context);
49
+ if (shouldCapture(options)) {
50
+ await config.onException?.(error, context);
51
+ await captureException(error, context);
52
+ }
53
+ else {
54
+ suppressException(error);
55
+ }
56
+ throw error;
57
+ }
58
+ attempt -= 1;
59
+ continue;
60
+ }
61
+ if (response.ok) {
62
+ return parseResponse(response, options.parseAs ?? "json");
63
+ }
64
+ const responseBody = await readResponseBody(response);
65
+ const error = new ApiClientError(`API request failed with status ${response.status}`, response.status, responseBody);
66
+ if (shouldRetryStatus(response.status, retry, attempt)) {
67
+ await sleep(backoffDelay(retry.baseDelayMs, attempt));
68
+ continue;
69
+ }
70
+ const context = {
71
+ area: "api",
72
+ source: path,
73
+ retryable: false,
74
+ status: response.status,
75
+ };
76
+ if (response.status === 401 && requiresAuth) {
77
+ await config.onAuthFailure?.(context);
78
+ }
79
+ if (shouldCapture(options)) {
80
+ await config.onException?.(error, context);
81
+ await captureException(error, context);
82
+ }
83
+ else {
84
+ suppressException(error);
85
+ }
86
+ throw error;
87
+ }
88
+ catch (error) {
89
+ if (shouldRetryNetworkFailure(error, retry, attempt)) {
90
+ await sleep(backoffDelay(retry.baseDelayMs, attempt));
91
+ continue;
92
+ }
93
+ const normalized = error instanceof Error ? error : new Error(String(error));
94
+ const context = {
95
+ area: "api",
96
+ source: path,
97
+ retryable: isNetworkError(error),
98
+ };
99
+ if (shouldCapture(options)) {
100
+ await config.onException?.(normalized, context);
101
+ await captureException(normalized, context);
102
+ }
103
+ else {
104
+ suppressException(normalized);
105
+ }
106
+ throw normalized;
107
+ }
108
+ }
109
+ throw new Error("API request exhausted retry attempts");
110
+ }, {
111
+ area: "api",
112
+ source: path,
113
+ });
114
+ }
115
+ return {
116
+ request,
117
+ get: (path, options = {}) => request(path, {
118
+ ...options,
119
+ method: "GET",
120
+ }),
121
+ post: (path, options = {}) => request(path, {
122
+ ...options,
123
+ method: "POST",
124
+ }),
125
+ put: (path, options = {}) => request(path, {
126
+ ...options,
127
+ method: "PUT",
128
+ }),
129
+ patch: (path, options = {}) => request(path, {
130
+ ...options,
131
+ method: "PATCH",
132
+ }),
133
+ delete: (path, options = {}) => request(path, {
134
+ ...options,
135
+ method: "DELETE",
136
+ }),
137
+ };
138
+ }
139
+ export function formatCurlCommand(url, init) {
140
+ const method = (init.method ?? "GET").toUpperCase();
141
+ const sanitizedUrl = escapeSingleQuotes(url);
142
+ const headers = new Headers(init.headers ?? {});
143
+ const headerArgs = Array.from(headers.entries())
144
+ .map(([key, value]) => `-H "${key}: ${value}"`)
145
+ .join(" ");
146
+ const bodyArg = buildBodyArg(init.body);
147
+ const parts = [`curl -X ${method} '${sanitizedUrl}'`];
148
+ if (headerArgs) {
149
+ parts.push(headerArgs);
150
+ }
151
+ if (bodyArg) {
152
+ parts.push(bodyArg);
153
+ }
154
+ return parts.join(" ");
155
+ }
156
+ function buildBodyArg(body) {
157
+ if (typeof body === "string") {
158
+ return `--data-raw '${escapeSingleQuotes(body)}'`;
159
+ }
160
+ if (body instanceof URLSearchParams) {
161
+ return `--data-raw '${escapeSingleQuotes(body.toString())}'`;
162
+ }
163
+ return "";
164
+ }
165
+ function escapeSingleQuotes(value) {
166
+ return value.replace(/'/g, "'\\''");
167
+ }
168
+ async function parseResponse(response, parseAs) {
169
+ if (parseAs === "response") {
170
+ return response;
171
+ }
172
+ if (parseAs === "text") {
173
+ return (await response.text());
174
+ }
175
+ if (response.status === 204) {
176
+ return null;
177
+ }
178
+ return (await response.json());
179
+ }
180
+ function buildRequestInit(config, options, accessToken) {
181
+ const headers = new Headers(config.defaultHeaders ?? {});
182
+ mergeHeaders(headers, options.headers);
183
+ const body = normalizeBody(options.body, headers);
184
+ if (accessToken) {
185
+ headers.set("Authorization", `Bearer ${accessToken}`);
186
+ }
187
+ return {
188
+ ...options,
189
+ body,
190
+ cache: options.cache ?? config.defaultCache,
191
+ headers,
192
+ next: options.next ?? config.defaultNext,
193
+ };
194
+ }
195
+ function normalizeBody(body, headers) {
196
+ if (body == null) {
197
+ return body;
198
+ }
199
+ if (typeof body === "string" ||
200
+ body instanceof Blob ||
201
+ body instanceof FormData ||
202
+ body instanceof URLSearchParams ||
203
+ body instanceof ArrayBuffer) {
204
+ return body;
205
+ }
206
+ headers.set("Content-Type", "application/json");
207
+ return JSON.stringify(body);
208
+ }
209
+ function mergeHeaders(target, source) {
210
+ if (!source) {
211
+ return;
212
+ }
213
+ new Headers(source).forEach((value, key) => {
214
+ target.set(key, value);
215
+ });
216
+ }
217
+ function buildUrl(baseUrl, path, query) {
218
+ const url = new URL(path, baseUrl);
219
+ for (const [key, value] of Object.entries(query ?? {})) {
220
+ if (value == null) {
221
+ continue;
222
+ }
223
+ url.searchParams.set(key, String(value));
224
+ }
225
+ return url.toString();
226
+ }
227
+ function resolveRetryOptions(retry) {
228
+ if (retry === false || retry == null) {
229
+ return {
230
+ attempts: 0,
231
+ baseDelayMs: 250,
232
+ retryOn: DEFAULT_RETRY_STATUSES,
233
+ };
234
+ }
235
+ if (retry === true) {
236
+ return {
237
+ attempts: 2,
238
+ baseDelayMs: 250,
239
+ retryOn: DEFAULT_RETRY_STATUSES,
240
+ };
241
+ }
242
+ return {
243
+ attempts: retry.attempts ?? 2,
244
+ baseDelayMs: retry.baseDelayMs ?? 250,
245
+ retryOn: retry.retryOn ?? DEFAULT_RETRY_STATUSES,
246
+ };
247
+ }
248
+ function shouldRetryStatus(status, retry, attempt) {
249
+ return attempt < retry.attempts && retry.retryOn.includes(status);
250
+ }
251
+ function shouldRetryNetworkFailure(error, retry, attempt) {
252
+ return attempt < retry.attempts && isNetworkError(error);
253
+ }
254
+ function isNetworkError(error) {
255
+ return error instanceof TypeError;
256
+ }
257
+ function backoffDelay(baseDelayMs, attempt) {
258
+ return baseDelayMs * (attempt + 1);
259
+ }
260
+ async function sleep(ms) {
261
+ await new Promise((resolve) => {
262
+ setTimeout(resolve, ms);
263
+ });
264
+ }
265
+ async function readAccessToken(getAccessToken, requireAuth) {
266
+ if (!requireAuth || typeof getAccessToken !== "function") {
267
+ return null;
268
+ }
269
+ return getAccessToken();
270
+ }
271
+ async function readResponseBody(response) {
272
+ const contentType = response.headers.get("Content-Type") ?? "";
273
+ if (isJsonContentType(contentType)) {
274
+ return response.json();
275
+ }
276
+ return response.text();
277
+ }
278
+ function isJsonContentType(contentType) {
279
+ const normalized = contentType.trim().toLowerCase();
280
+ return normalized.includes("/json") || normalized.includes("+json");
281
+ }
@@ -0,0 +1,61 @@
1
+ type SgMetaPropV0 = {
2
+ name: string;
3
+ type: string;
4
+ required?: boolean;
5
+ default?: unknown;
6
+ description?: string;
7
+ semanticRole?: "value" | "label" | "validation" | "behavior" | "appearance" | "event" | "data";
8
+ bindable?: boolean;
9
+ };
10
+ type SgMetaExampleV0 = {
11
+ id: string;
12
+ title: string;
13
+ file: string;
14
+ kind: "sample" | "playground";
15
+ };
16
+ type SgMetaV0 = {
17
+ version: "0.1";
18
+ componentId: string;
19
+ package: string;
20
+ exportName: string;
21
+ slug: string;
22
+ displayName: string;
23
+ category: string;
24
+ subcategory?: string;
25
+ description: string;
26
+ tags?: string[];
27
+ capabilities?: string[];
28
+ fieldSemantics?: string[];
29
+ props?: SgMetaPropV0[];
30
+ states?: string[];
31
+ examples?: SgMetaExampleV0[];
32
+ showcase?: {
33
+ route: string;
34
+ hasPlayground: boolean;
35
+ hasPropsTable: boolean;
36
+ };
37
+ sdui?: {
38
+ rendererType: string;
39
+ acceptsDataBinding?: boolean;
40
+ defaultProps?: Record<string, unknown>;
41
+ };
42
+ };
43
+ type SgAiHintsV0 = {
44
+ version: "0.1";
45
+ preferredUseCases: string[];
46
+ avoidUseCases?: string[];
47
+ synonyms?: string[];
48
+ relatedEntityFields?: string[];
49
+ compositionHints?: string[];
50
+ rankingSignals?: {
51
+ freeText?: number;
52
+ structuredChoice?: number;
53
+ date?: number;
54
+ number?: number;
55
+ denseLayout?: number;
56
+ };
57
+ };
58
+ export declare const sgMeta: SgMetaV0;
59
+ export declare const aiHints: SgAiHintsV0;
60
+ export {};
61
+ //# sourceMappingURL=api-client.meta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-client.meta.d.ts","sourceRoot":"","sources":["../../src/http/api-client.meta.ts"],"names":[],"mappings":"AAAA,KAAK,YAAY,GAAG;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,YAAY,GAAG,UAAU,GAAG,YAAY,GAAG,OAAO,GAAG,MAAM,CAAC;IAC/F,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,GAAG,YAAY,CAAC;CAC/B,CAAC;AAEF,KAAK,QAAQ,GAAG;IACd,OAAO,EAAE,KAAK,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,eAAe,EAAE,CAAC;IAC7B,QAAQ,CAAC,EAAE;QACT,KAAK,EAAE,MAAM,CAAC;QACd,aAAa,EAAE,OAAO,CAAC;QACvB,aAAa,EAAE,OAAO,CAAC;KACxB,CAAC;IACF,IAAI,CAAC,EAAE;QACL,YAAY,EAAE,MAAM,CAAC;QACrB,kBAAkB,CAAC,EAAE,OAAO,CAAC;QAC7B,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACxC,CAAC;CACH,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,OAAO,EAAE,KAAK,CAAC;IACf,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,cAAc,CAAC,EAAE;QACf,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;CACH,CAAC;AAEF,eAAO,MAAM,MAAM,EAAE,QAyEpB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,WAyBrB,CAAC"}
@@ -0,0 +1,99 @@
1
+ export const sgMeta = {
2
+ version: "0.1",
3
+ componentId: "factory.api-client",
4
+ package: "@seedgrid/fe-core",
5
+ exportName: "createApiClient",
6
+ slug: "create-api-client",
7
+ displayName: "API Client Factory",
8
+ category: "factory",
9
+ subcategory: "http",
10
+ description: "Factory function that creates a fetch-based API client with auth token injection, retry policies, response parsing, cURL debug output, and typed request helpers.",
11
+ tags: ["api", "http", "fetch", "auth", "retry", "json"],
12
+ capabilities: ["get", "post", "put", "patch", "delete", "refresh-token", "response-parse", "query-params"],
13
+ fieldSemantics: ["api", "request", "authentication", "retry", "error-handling"],
14
+ props: [
15
+ {
16
+ name: "baseUrl",
17
+ type: "string",
18
+ required: true,
19
+ description: "Base URL used to resolve relative API paths.",
20
+ semanticRole: "data",
21
+ },
22
+ {
23
+ name: "getAccessToken",
24
+ type: "() => Promise<string | null>",
25
+ required: false,
26
+ description: "Async callback used to read the current access token before authenticated requests.",
27
+ semanticRole: "behavior",
28
+ },
29
+ {
30
+ name: "refreshAccessToken",
31
+ type: "() => Promise<string | null>",
32
+ required: false,
33
+ description: "Async callback used after a 401 to obtain a fresh access token and retry once.",
34
+ semanticRole: "behavior",
35
+ },
36
+ {
37
+ name: "defaultHeaders",
38
+ type: "HeadersInit",
39
+ required: false,
40
+ description: "Headers merged into every request before per-request overrides.",
41
+ semanticRole: "data",
42
+ },
43
+ {
44
+ name: "defaultCache",
45
+ type: "RequestCache",
46
+ required: false,
47
+ description: "Default fetch cache mode used when a request does not override cache.",
48
+ semanticRole: "behavior",
49
+ },
50
+ {
51
+ name: "defaultNext",
52
+ type: "{ revalidate?: false | number; tags?: string[] }",
53
+ required: false,
54
+ description: "Next.js fetch options reused across requests.",
55
+ semanticRole: "behavior",
56
+ },
57
+ {
58
+ name: "onAuthFailure",
59
+ type: "(context: ExceptionContext) => void | Promise<void>",
60
+ required: false,
61
+ description: "Optional callback fired when an authenticated request still fails with 401.",
62
+ semanticRole: "event",
63
+ },
64
+ {
65
+ name: "onException",
66
+ type: "(error: Error, context: ExceptionContext) => void | Promise<void>",
67
+ required: false,
68
+ description: "Optional callback invoked before captured API exceptions are rethrown.",
69
+ semanticRole: "event",
70
+ },
71
+ ],
72
+ states: ["ready", "requesting", "retrying", "failed"],
73
+ };
74
+ export const aiHints = {
75
+ version: "0.1",
76
+ preferredUseCases: [
77
+ "Server or client HTTP access where auth tokens, retries, and typed JSON parsing should be standardized.",
78
+ "Application modules that need consistent GET/POST/PUT/PATCH/DELETE helpers instead of ad hoc fetch calls.",
79
+ "Next.js integrations that need cache/next options to flow through a shared API layer.",
80
+ ],
81
+ avoidUseCases: [
82
+ "Very small one-off fetch calls where a dedicated client adds no value.",
83
+ "Streaming or multipart-heavy workflows that need low-level fetch control.",
84
+ ],
85
+ synonyms: ["api client", "fetch wrapper", "http client", "authenticated fetch", "request factory"],
86
+ relatedEntityFields: ["api", "request", "auth", "token", "retry", "http"],
87
+ compositionHints: [
88
+ "Pair createApiClient with captureException or module-level error translation when surfacing server errors to the UI.",
89
+ "Use requireAuth: false for public endpoints; leave it enabled by default for private endpoints.",
90
+ "Use parseAs: 'response' when the caller needs raw headers or binary body handling.",
91
+ ],
92
+ rankingSignals: {
93
+ freeText: 0.35,
94
+ structuredChoice: 0.3,
95
+ date: 0,
96
+ number: 0.05,
97
+ denseLayout: 0.1,
98
+ },
99
+ };
@@ -0,0 +1,15 @@
1
+ export type ExceptionArea = "api" | "client" | "server";
2
+ export type ExceptionContext = {
3
+ area?: ExceptionArea;
4
+ source?: string;
5
+ status?: number;
6
+ retryable?: boolean;
7
+ metadata?: Record<string, unknown>;
8
+ };
9
+ export type ExceptionReporter = (error: Error, context: ExceptionContext) => void | Promise<void>;
10
+ export declare function suppressException(error: Error): void;
11
+ export declare function registerExceptionReporter(reporter: ExceptionReporter): () => void;
12
+ export declare function normalizeError(error: unknown): Error;
13
+ export declare function captureException(error: unknown, context?: ExceptionContext): Promise<Error>;
14
+ export declare function runGuarded<T>(action: () => Promise<T> | T, context?: ExceptionContext): Promise<T>;
15
+ //# sourceMappingURL=api-exception-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-exception-handler.d.ts","sourceRoot":"","sources":["../../src/http/api-exception-handler.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAExD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,CAC9B,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,gBAAgB,KACtB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAK1B,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,KAAK,QAE7C;AAED,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,iBAAiB,cAMpE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAUpD;AAED,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,gBAAqB,kBAqB/B;AAED,wBAAsB,UAAU,CAAC,CAAC,EAChC,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,EAC5B,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,CAAC,CAAC,CAOZ"}
@@ -0,0 +1,53 @@
1
+ // seedgrid:managed
2
+ const reporters = new Set();
3
+ const CAPTURED_ERROR_SYMBOL = Symbol.for("seedgrid.captured_exception");
4
+ export function suppressException(error) {
5
+ markAsCaptured(error);
6
+ }
7
+ export function registerExceptionReporter(reporter) {
8
+ reporters.add(reporter);
9
+ return () => {
10
+ reporters.delete(reporter);
11
+ };
12
+ }
13
+ export function normalizeError(error) {
14
+ if (error instanceof Error) {
15
+ return error;
16
+ }
17
+ if (typeof error === "string") {
18
+ return new Error(error);
19
+ }
20
+ return new Error("Unknown application error");
21
+ }
22
+ export async function captureException(error, context = {}) {
23
+ const normalized = normalizeError(error);
24
+ if (wasCaptured(normalized)) {
25
+ return normalized;
26
+ }
27
+ markAsCaptured(normalized);
28
+ console.error("[seedgrid]", normalized, context);
29
+ for (const reporter of reporters) {
30
+ try {
31
+ await reporter(normalized, context);
32
+ }
33
+ catch (reporterError) {
34
+ console.error("[seedgrid] reporter failed", reporterError);
35
+ }
36
+ }
37
+ return normalized;
38
+ }
39
+ export async function runGuarded(action, context = {}) {
40
+ try {
41
+ return await action();
42
+ }
43
+ catch (error) {
44
+ await captureException(error, context);
45
+ throw error;
46
+ }
47
+ }
48
+ function wasCaptured(error) {
49
+ return Boolean(error[CAPTURED_ERROR_SYMBOL]);
50
+ }
51
+ function markAsCaptured(error) {
52
+ error[CAPTURED_ERROR_SYMBOL] = true;
53
+ }
@@ -0,0 +1,61 @@
1
+ type SgMetaPropV0 = {
2
+ name: string;
3
+ type: string;
4
+ required?: boolean;
5
+ default?: unknown;
6
+ description?: string;
7
+ semanticRole?: "value" | "label" | "validation" | "behavior" | "appearance" | "event" | "data";
8
+ bindable?: boolean;
9
+ };
10
+ type SgMetaExampleV0 = {
11
+ id: string;
12
+ title: string;
13
+ file: string;
14
+ kind: "sample" | "playground";
15
+ };
16
+ type SgMetaV0 = {
17
+ version: "0.1";
18
+ componentId: string;
19
+ package: string;
20
+ exportName: string;
21
+ slug: string;
22
+ displayName: string;
23
+ category: string;
24
+ subcategory?: string;
25
+ description: string;
26
+ tags?: string[];
27
+ capabilities?: string[];
28
+ fieldSemantics?: string[];
29
+ props?: SgMetaPropV0[];
30
+ states?: string[];
31
+ examples?: SgMetaExampleV0[];
32
+ showcase?: {
33
+ route: string;
34
+ hasPlayground: boolean;
35
+ hasPropsTable: boolean;
36
+ };
37
+ sdui?: {
38
+ rendererType: string;
39
+ acceptsDataBinding?: boolean;
40
+ defaultProps?: Record<string, unknown>;
41
+ };
42
+ };
43
+ type SgAiHintsV0 = {
44
+ version: "0.1";
45
+ preferredUseCases: string[];
46
+ avoidUseCases?: string[];
47
+ synonyms?: string[];
48
+ relatedEntityFields?: string[];
49
+ compositionHints?: string[];
50
+ rankingSignals?: {
51
+ freeText?: number;
52
+ structuredChoice?: number;
53
+ date?: number;
54
+ number?: number;
55
+ denseLayout?: number;
56
+ };
57
+ };
58
+ export declare const sgMeta: SgMetaV0;
59
+ export declare const aiHints: SgAiHintsV0;
60
+ export {};
61
+ //# sourceMappingURL=api-exception-handler.meta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-exception-handler.meta.d.ts","sourceRoot":"","sources":["../../src/http/api-exception-handler.meta.ts"],"names":[],"mappings":"AAAA,KAAK,YAAY,GAAG;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,YAAY,GAAG,UAAU,GAAG,YAAY,GAAG,OAAO,GAAG,MAAM,CAAC;IAC/F,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,GAAG,YAAY,CAAC;CAC/B,CAAC;AAEF,KAAK,QAAQ,GAAG;IACd,OAAO,EAAE,KAAK,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,eAAe,EAAE,CAAC;IAC7B,QAAQ,CAAC,EAAE;QACT,KAAK,EAAE,MAAM,CAAC;QACd,aAAa,EAAE,OAAO,CAAC;QACvB,aAAa,EAAE,OAAO,CAAC;KACxB,CAAC;IACF,IAAI,CAAC,EAAE;QACL,YAAY,EAAE,MAAM,CAAC;QACrB,kBAAkB,CAAC,EAAE,OAAO,CAAC;QAC7B,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACxC,CAAC;CACH,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,OAAO,EAAE,KAAK,CAAC;IACf,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,cAAc,CAAC,EAAE;QACf,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;CACH,CAAC;AAEF,eAAO,MAAM,MAAM,EAAE,QA+BpB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,WAyBrB,CAAC"}