@classytic/arc-next 0.1.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.
@@ -0,0 +1,172 @@
1
+ //#region src/client.d.ts
2
+ interface ToastHandler {
3
+ success: (message: string) => void;
4
+ error: (message: string) => void;
5
+ }
6
+ type UseRouterHook = () => {
7
+ push: (href: string, options?: {
8
+ scroll?: boolean;
9
+ }) => void;
10
+ replace: (href: string, options?: {
11
+ scroll?: boolean;
12
+ }) => void;
13
+ };
14
+ interface ArcApiErrorOptions {
15
+ status: number;
16
+ statusText: string;
17
+ json: unknown;
18
+ endpoint: string;
19
+ method: HttpMethod;
20
+ }
21
+ /**
22
+ * Rich API error with status code, response payload, and request metadata.
23
+ * Extends Error so existing `catch (e) { if (e instanceof Error) }` still works.
24
+ *
25
+ * @example
26
+ * try {
27
+ * await handleApiRequest('POST', '/users', { body: data });
28
+ * } catch (error) {
29
+ * if (isArcApiError(error)) {
30
+ * console.log(error.status); // 422
31
+ * console.log(error.fieldErrors); // { email: 'already taken' }
32
+ * }
33
+ * }
34
+ */
35
+ declare class ArcApiError extends Error {
36
+ readonly status: number;
37
+ readonly statusText: string;
38
+ readonly json: unknown;
39
+ readonly endpoint: string;
40
+ readonly method: HttpMethod;
41
+ constructor(message: string, options: ArcApiErrorOptions);
42
+ /** Extract field-level validation errors if present (e.g. `{ field: "message" }`) */
43
+ get fieldErrors(): Record<string, string> | null;
44
+ }
45
+ /**
46
+ * Type guard for ArcApiError.
47
+ */
48
+ declare function isArcApiError(error: unknown): error is ArcApiError;
49
+ interface ClientConfig {
50
+ baseUrl: string;
51
+ internalApiKey?: string;
52
+ defaultHeaders?: Record<string, string>;
53
+ /**
54
+ * Auth mode for the API client.
55
+ * - 'bearer' (default): Requires a token for authenticated requests. Queries are disabled until a token is provided.
56
+ * - 'cookie': Auth is handled via HTTP-only cookies (e.g. Better Auth). Queries are always enabled — no token needed.
57
+ */
58
+ authMode?: 'bearer' | 'cookie';
59
+ }
60
+ /**
61
+ * Configure the API client. Call once at app init before any API requests.
62
+ *
63
+ * @example
64
+ * // Bearer token auth (default)
65
+ * configureClient({ baseUrl: process.env.NEXT_PUBLIC_API_URL! });
66
+ *
67
+ * // Cookie-based auth (e.g. Better Auth)
68
+ * configureClient({ baseUrl: process.env.NEXT_PUBLIC_API_URL!, authMode: 'cookie' });
69
+ */
70
+ declare function configureClient(config: ClientConfig): void;
71
+ /**
72
+ * Get the configured auth mode. Returns 'bearer' if not configured.
73
+ */
74
+ declare function getAuthMode(): 'bearer' | 'cookie';
75
+ interface AuthConfig {
76
+ getToken?: () => string | null;
77
+ getOrgId?: () => string | null;
78
+ }
79
+ /**
80
+ * Configure auth context for automatic token/orgId injection.
81
+ * When configured, hooks auto-inject these values so you don't need to pass them manually.
82
+ *
83
+ * @example
84
+ * // Cookie auth (no token needed)
85
+ * configureAuth({ getOrgId: () => currentOrg.id });
86
+ *
87
+ * // Bearer auth
88
+ * configureAuth({
89
+ * getToken: () => session?.accessToken ?? null,
90
+ * getOrgId: () => currentOrg?.id ?? null,
91
+ * });
92
+ */
93
+ declare function configureAuth(config: AuthConfig): void;
94
+ /**
95
+ * Get the current auth context. Returns nulls when not configured.
96
+ */
97
+ declare function getAuthContext(): {
98
+ token: string | null;
99
+ organizationId: string | null;
100
+ };
101
+ type HttpMethod = 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
102
+ interface ApiRequestOptions {
103
+ body?: unknown;
104
+ token?: string | null;
105
+ organizationId?: string | null;
106
+ revalidate?: number;
107
+ headerOptions?: Record<string, string>;
108
+ tags?: string[];
109
+ cache?: RequestCache;
110
+ signal?: AbortSignal;
111
+ }
112
+ interface BlobResponse {
113
+ data: Blob;
114
+ response: Response;
115
+ }
116
+ interface TextResponse {
117
+ data: string;
118
+ response: Response;
119
+ }
120
+ interface ArcClientConfig extends ClientConfig {
121
+ toast?: ToastHandler;
122
+ navigation?: UseRouterHook;
123
+ }
124
+ interface ArcClient {
125
+ request: <T = unknown>(method: HttpMethod, endpoint: string, options?: ApiRequestOptions) => Promise<T>;
126
+ config: ClientConfig;
127
+ toast?: ToastHandler;
128
+ navigation?: UseRouterHook;
129
+ }
130
+ /**
131
+ * Create an isolated API client for a specific backend.
132
+ * Use this when your app needs to talk to multiple APIs.
133
+ *
134
+ * @example
135
+ * const analyticsClient = createClient({
136
+ * baseUrl: 'https://analytics.example.com',
137
+ * toast: { success: toast.success, error: toast.error },
138
+ * navigation: useRouter,
139
+ * });
140
+ *
141
+ * const eventsApi = createCrudApi('events', { client: analyticsClient });
142
+ */
143
+ declare function createClient(config: ArcClientConfig): ArcClient;
144
+ /**
145
+ * Universal API request handler.
146
+ * Handles JSON, binary (PDF, images), CSV, and text responses.
147
+ *
148
+ * @example
149
+ * const { success, data } = await handleApiRequest<ApiResponse<User>>('GET', '/users/me');
150
+ * const response = await handleApiRequest<PaginatedResponse<Product>>('GET', '/products?page=1');
151
+ */
152
+ declare function handleApiRequest<T = unknown>(method: HttpMethod, endpoint: string, options?: ApiRequestOptions): Promise<T>;
153
+ /**
154
+ * Creates query string from parameters.
155
+ * Supports: arrays as field[in]=value1,value2, populateOptions with field selection.
156
+ *
157
+ * @example
158
+ * createQueryString({ page: 1, limit: 10, status: 'active' })
159
+ * // => 'page=1&limit=10&status=active'
160
+ *
161
+ * createQueryString({ roles: ['admin', 'user'] })
162
+ * // => 'roles[in]=admin,user'
163
+ *
164
+ * createQueryString({
165
+ * populateOptions: [{ path: 'employeeId', select: 'name email' }]
166
+ * })
167
+ * // => 'populate[employeeId][select]=name,email'
168
+ */
169
+ declare function createQueryString<T extends Record<string, unknown>>(params?: T): string;
170
+ //#endregion
171
+ export { ApiRequestOptions, ArcApiError, ArcApiErrorOptions, ArcClient, ArcClientConfig, AuthConfig, BlobResponse, ClientConfig, HttpMethod, TextResponse, ToastHandler, UseRouterHook, configureAuth, configureClient, createClient, createQueryString, getAuthContext, getAuthMode, handleApiRequest, isArcApiError };
172
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","names":[],"sources":["../src/client.ts"],"mappings":";UAIiB,YAAA;EACf,OAAA,GAAU,OAAA;EACV,KAAA,GAAQ,OAAA;AAAA;AAAA,KAGE,aAAA;EACV,IAAA,GAAO,IAAA,UAAc,OAAA;IAAY,MAAA;EAAA;EACjC,OAAA,GAAU,IAAA,UAAc,OAAA;IAAY,MAAA;EAAA;AAAA;AAAA,UAOrB,kBAAA;EACf,MAAA;EACA,UAAA;EACA,IAAA;EACA,QAAA;EACA,MAAA,EAAQ,UAAA;AAAA;;;;;;;AALV;;;;;;;;cAsBa,WAAA,SAAoB,KAAA;EAAA,SACtB,MAAA;EAAA,SACA,UAAA;EAAA,SACA,IAAA;EAAA,SACA,QAAA;EAAA,SACA,MAAA,EAAQ,UAAA;cAEL,OAAA,UAAiB,OAAA,EAAS,kBAAA;EAFrB;EAAA,IAab,WAAA,CAAA,GAAe,MAAA;AAAA;;;;iBASL,aAAA,CAAc,KAAA,YAAiB,KAAA,IAAS,WAAA;AAAA,UAQvC,YAAA;EACf,OAAA;EACA,cAAA;EACA,cAAA,GAAiB,MAAA;EAjCR;;;;;EAuCT,QAAA;AAAA;;;;AAjBF;;;;;;;iBAgCgB,eAAA,CAAgB,MAAA,EAAQ,YAAA;;AAxBxC;;iBA+BgB,WAAA,CAAA;AAAA,UAQC,UAAA;EACf,QAAA;EACA,QAAA;AAAA;;;;;AAjBF;;;;;AAOA;;;;;iBA6BgB,aAAA,CAAc,MAAA,EAAQ,UAAA;;;;iBAOtB,cAAA,CAAA;EAAoB,KAAA;EAAsB,cAAA;AAAA;AAAA,KAW9C,UAAA;AAAA,UAEK,iBAAA;EACf,IAAA;EACA,KAAA;EACA,cAAA;EACA,UAAA;EACA,aAAA,GAAgB,MAAA;EAChB,IAAA;EACA,KAAA,GAAQ,YAAA;EACR,MAAA,GAAS,WAAA;AAAA;AAAA,UAGM,YAAA;EACf,IAAA,EAAM,IAAA;EACN,QAAA,EAAU,QAAA;AAAA;AAAA,UAGK,YAAA;EACf,IAAA;EACA,QAAA,EAAU,QAAA;AAAA;AAAA,UAOK,eAAA,SAAwB,YAAA;EACvC,KAAA,GAAQ,YAAA;EACR,UAAA,GAAa,aAAA;AAAA;AAAA,UAGE,SAAA;EACf,OAAA,gBAAuB,MAAA,EAAQ,UAAA,EAAY,QAAA,UAAkB,OAAA,GAAU,iBAAA,KAAsB,OAAA,CAAQ,CAAA;EACrG,MAAA,EAAQ,YAAA;EACR,KAAA,GAAQ,YAAA;EACR,UAAA,GAAa,aAAA;AAAA;;;;;AAvBf;;;;;;;;;iBAuCgB,YAAA,CAAa,MAAA,EAAQ,eAAA,GAAkB,SAAA;AAlCvD;;;;;;;;AAAA,iBA2KsB,gBAAA,aAAA,CACpB,MAAA,EAAQ,UAAA,EACR,QAAA,UACA,OAAA,GAAS,iBAAA,GACR,OAAA,CAAQ,CAAA;AAtKX;;;;;;;;;;;;;;;AAKA;AALA,iBAmMgB,iBAAA,WAA4B,MAAA,kBAAA,CAAyB,MAAA,GAAQ,CAAA"}
package/dist/client.js ADDED
@@ -0,0 +1,242 @@
1
+ //#region src/client.ts
2
+ /**
3
+ * Rich API error with status code, response payload, and request metadata.
4
+ * Extends Error so existing `catch (e) { if (e instanceof Error) }` still works.
5
+ *
6
+ * @example
7
+ * try {
8
+ * await handleApiRequest('POST', '/users', { body: data });
9
+ * } catch (error) {
10
+ * if (isArcApiError(error)) {
11
+ * console.log(error.status); // 422
12
+ * console.log(error.fieldErrors); // { email: 'already taken' }
13
+ * }
14
+ * }
15
+ */
16
+ var ArcApiError = class extends Error {
17
+ status;
18
+ statusText;
19
+ json;
20
+ endpoint;
21
+ method;
22
+ constructor(message, options) {
23
+ super(message);
24
+ this.name = "ArcApiError";
25
+ this.status = options.status;
26
+ this.statusText = options.statusText;
27
+ this.json = options.json;
28
+ this.endpoint = options.endpoint;
29
+ this.method = options.method;
30
+ }
31
+ /** Extract field-level validation errors if present (e.g. `{ field: "message" }`) */
32
+ get fieldErrors() {
33
+ return this.json?.errors ?? null;
34
+ }
35
+ };
36
+ /**
37
+ * Type guard for ArcApiError.
38
+ */
39
+ function isArcApiError(error) {
40
+ return error instanceof ArcApiError;
41
+ }
42
+ let clientConfig = null;
43
+ /**
44
+ * Configure the API client. Call once at app init before any API requests.
45
+ *
46
+ * @example
47
+ * // Bearer token auth (default)
48
+ * configureClient({ baseUrl: process.env.NEXT_PUBLIC_API_URL! });
49
+ *
50
+ * // Cookie-based auth (e.g. Better Auth)
51
+ * configureClient({ baseUrl: process.env.NEXT_PUBLIC_API_URL!, authMode: 'cookie' });
52
+ */
53
+ function configureClient(config) {
54
+ clientConfig = config;
55
+ }
56
+ /**
57
+ * Get the configured auth mode. Returns 'bearer' if not configured.
58
+ */
59
+ function getAuthMode() {
60
+ return clientConfig?.authMode ?? "bearer";
61
+ }
62
+ let authConfig = null;
63
+ /**
64
+ * Configure auth context for automatic token/orgId injection.
65
+ * When configured, hooks auto-inject these values so you don't need to pass them manually.
66
+ *
67
+ * @example
68
+ * // Cookie auth (no token needed)
69
+ * configureAuth({ getOrgId: () => currentOrg.id });
70
+ *
71
+ * // Bearer auth
72
+ * configureAuth({
73
+ * getToken: () => session?.accessToken ?? null,
74
+ * getOrgId: () => currentOrg?.id ?? null,
75
+ * });
76
+ */
77
+ function configureAuth(config) {
78
+ authConfig = config;
79
+ }
80
+ /**
81
+ * Get the current auth context. Returns nulls when not configured.
82
+ */
83
+ function getAuthContext() {
84
+ return {
85
+ token: authConfig?.getToken?.() ?? null,
86
+ organizationId: authConfig?.getOrgId?.() ?? null
87
+ };
88
+ }
89
+ /**
90
+ * Create an isolated API client for a specific backend.
91
+ * Use this when your app needs to talk to multiple APIs.
92
+ *
93
+ * @example
94
+ * const analyticsClient = createClient({
95
+ * baseUrl: 'https://analytics.example.com',
96
+ * toast: { success: toast.success, error: toast.error },
97
+ * navigation: useRouter,
98
+ * });
99
+ *
100
+ * const eventsApi = createCrudApi('events', { client: analyticsClient });
101
+ */
102
+ function createClient(config) {
103
+ const { toast, navigation, ...clientCfg } = config;
104
+ return {
105
+ request: (method, endpoint, options) => executeRequest(clientCfg, method, endpoint, options),
106
+ config: clientCfg,
107
+ toast,
108
+ navigation
109
+ };
110
+ }
111
+ async function executeRequest(config, method, endpoint, options = {}) {
112
+ const { body, token, organizationId, revalidate, headerOptions, tags, cache, signal } = options;
113
+ try {
114
+ let headers = {
115
+ ...organizationId ? { "x-organization-id": organizationId } : {},
116
+ ...config.defaultHeaders ?? {}
117
+ };
118
+ if (config.internalApiKey) headers["x-internal-api-key"] = config.internalApiKey;
119
+ if (token) headers["Authorization"] = `Bearer ${token}`;
120
+ if (body !== void 0 && body !== null && !(body instanceof FormData)) headers["Content-Type"] = "application/json";
121
+ if (headerOptions) headers = {
122
+ ...headers,
123
+ ...headerOptions
124
+ };
125
+ const fetchOptions = {
126
+ method,
127
+ headers,
128
+ credentials: "include",
129
+ ...signal ? { signal } : {}
130
+ };
131
+ if (body !== void 0 && body !== null) fetchOptions.body = body instanceof FormData ? body : JSON.stringify(body);
132
+ if (cache) fetchOptions.cache = cache;
133
+ if (revalidate !== void 0) fetchOptions.next = {
134
+ ...fetchOptions.next,
135
+ revalidate
136
+ };
137
+ if (tags) fetchOptions.next = {
138
+ ...fetchOptions.next,
139
+ tags
140
+ };
141
+ const response = await fetch(`${config.baseUrl}${endpoint}`, fetchOptions);
142
+ if (!response.ok) {
143
+ const json = await response.json().catch(() => null);
144
+ throw new ArcApiError(json?.message || response.statusText, {
145
+ status: response.status,
146
+ statusText: response.statusText,
147
+ json,
148
+ endpoint,
149
+ method
150
+ });
151
+ }
152
+ const contentType = response.headers.get("Content-Type");
153
+ let data;
154
+ if (contentType?.includes("application/json")) data = await response.json();
155
+ else if (contentType?.includes("application/pdf") || contentType?.includes("image/")) data = {
156
+ data: await response.blob(),
157
+ response
158
+ };
159
+ else if (contentType?.includes("text/csv")) data = {
160
+ data: await response.blob(),
161
+ response
162
+ };
163
+ else if (contentType?.includes("text/")) data = {
164
+ data: await response.text(),
165
+ response
166
+ };
167
+ else try {
168
+ data = {
169
+ data: await response.clone().blob(),
170
+ response
171
+ };
172
+ } catch {
173
+ data = {
174
+ data: await response.text(),
175
+ response
176
+ };
177
+ }
178
+ return data;
179
+ } catch (error) {
180
+ if (error instanceof ArcApiError) throw error;
181
+ throw new Error(error instanceof Error ? error.message : "An error occurred while fetching data.");
182
+ }
183
+ }
184
+ /**
185
+ * Universal API request handler.
186
+ * Handles JSON, binary (PDF, images), CSV, and text responses.
187
+ *
188
+ * @example
189
+ * const { success, data } = await handleApiRequest<ApiResponse<User>>('GET', '/users/me');
190
+ * const response = await handleApiRequest<PaginatedResponse<Product>>('GET', '/products?page=1');
191
+ */
192
+ async function handleApiRequest(method, endpoint, options = {}) {
193
+ if (!clientConfig) throw new Error("arc-next: Client not configured. Call configureClient({ baseUrl }) before making API requests.");
194
+ return executeRequest(clientConfig, method, endpoint, options);
195
+ }
196
+ /**
197
+ * Creates query string from parameters.
198
+ * Supports: arrays as field[in]=value1,value2, populateOptions with field selection.
199
+ *
200
+ * @example
201
+ * createQueryString({ page: 1, limit: 10, status: 'active' })
202
+ * // => 'page=1&limit=10&status=active'
203
+ *
204
+ * createQueryString({ roles: ['admin', 'user'] })
205
+ * // => 'roles[in]=admin,user'
206
+ *
207
+ * createQueryString({
208
+ * populateOptions: [{ path: 'employeeId', select: 'name email' }]
209
+ * })
210
+ * // => 'populate[employeeId][select]=name,email'
211
+ */
212
+ function createQueryString(params = {}) {
213
+ const searchParams = new URLSearchParams();
214
+ Object.entries(params).forEach(([key, value]) => {
215
+ if (value === void 0 || value === "") return;
216
+ if (key === "populateOptions" && Array.isArray(value)) {
217
+ value.forEach((opt) => {
218
+ if (opt.select) {
219
+ const selectFields = opt.select.replace(/\s+/g, ",");
220
+ searchParams.append(`populate[${opt.path}][select]`, selectFields);
221
+ }
222
+ if (opt.match) searchParams.append(`populate[${opt.path}][match]`, JSON.stringify(opt.match));
223
+ if (!opt.select && !opt.match) {
224
+ const existing = searchParams.get("populate");
225
+ if (existing) searchParams.set("populate", `${existing},${opt.path}`);
226
+ else searchParams.append("populate", opt.path);
227
+ }
228
+ });
229
+ return;
230
+ }
231
+ if (Array.isArray(value)) {
232
+ if (value.length > 1) searchParams.append(`${key}[in]`, value.join(","));
233
+ else if (value.length === 1) searchParams.append(key, String(value[0]));
234
+ } else if (value === null) searchParams.append(key, "null");
235
+ else searchParams.append(key, String(value));
236
+ });
237
+ return searchParams.toString();
238
+ }
239
+
240
+ //#endregion
241
+ export { ArcApiError, configureAuth, configureClient, createClient, createQueryString, getAuthContext, getAuthMode, handleApiRequest, isArcApiError };
242
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","names":[],"sources":["../src/client.ts"],"sourcesContent":["// ============================================================================\n// Shared Types (used by client, mutation, and hooks)\n// ============================================================================\n\nexport interface ToastHandler {\n success: (message: string) => void;\n error: (message: string) => void;\n}\n\nexport type UseRouterHook = () => {\n push: (href: string, options?: { scroll?: boolean }) => void;\n replace: (href: string, options?: { scroll?: boolean }) => void;\n};\n\n// ============================================================================\n// ArcApiError\n// ============================================================================\n\nexport interface ArcApiErrorOptions {\n status: number;\n statusText: string;\n json: unknown;\n endpoint: string;\n method: HttpMethod;\n}\n\n/**\n * Rich API error with status code, response payload, and request metadata.\n * Extends Error so existing `catch (e) { if (e instanceof Error) }` still works.\n *\n * @example\n * try {\n * await handleApiRequest('POST', '/users', { body: data });\n * } catch (error) {\n * if (isArcApiError(error)) {\n * console.log(error.status); // 422\n * console.log(error.fieldErrors); // { email: 'already taken' }\n * }\n * }\n */\nexport class ArcApiError extends Error {\n readonly status: number;\n readonly statusText: string;\n readonly json: unknown;\n readonly endpoint: string;\n readonly method: HttpMethod;\n\n constructor(message: string, options: ArcApiErrorOptions) {\n super(message);\n this.name = 'ArcApiError';\n this.status = options.status;\n this.statusText = options.statusText;\n this.json = options.json;\n this.endpoint = options.endpoint;\n this.method = options.method;\n }\n\n /** Extract field-level validation errors if present (e.g. `{ field: \"message\" }`) */\n get fieldErrors(): Record<string, string> | null {\n const j = this.json as { errors?: Record<string, string> } | null;\n return j?.errors ?? null;\n }\n}\n\n/**\n * Type guard for ArcApiError.\n */\nexport function isArcApiError(error: unknown): error is ArcApiError {\n return error instanceof ArcApiError;\n}\n\n// ============================================================================\n// Client Configuration\n// ============================================================================\n\nexport interface ClientConfig {\n baseUrl: string;\n internalApiKey?: string;\n defaultHeaders?: Record<string, string>;\n /**\n * Auth mode for the API client.\n * - 'bearer' (default): Requires a token for authenticated requests. Queries are disabled until a token is provided.\n * - 'cookie': Auth is handled via HTTP-only cookies (e.g. Better Auth). Queries are always enabled — no token needed.\n */\n authMode?: 'bearer' | 'cookie';\n}\n\nlet clientConfig: ClientConfig | null = null;\n\n/**\n * Configure the API client. Call once at app init before any API requests.\n *\n * @example\n * // Bearer token auth (default)\n * configureClient({ baseUrl: process.env.NEXT_PUBLIC_API_URL! });\n *\n * // Cookie-based auth (e.g. Better Auth)\n * configureClient({ baseUrl: process.env.NEXT_PUBLIC_API_URL!, authMode: 'cookie' });\n */\nexport function configureClient(config: ClientConfig): void {\n clientConfig = config;\n}\n\n/**\n * Get the configured auth mode. Returns 'bearer' if not configured.\n */\nexport function getAuthMode(): 'bearer' | 'cookie' {\n return clientConfig?.authMode ?? 'bearer';\n}\n\n// ============================================================================\n// Auth Configuration\n// ============================================================================\n\nexport interface AuthConfig {\n getToken?: () => string | null;\n getOrgId?: () => string | null;\n}\n\nlet authConfig: AuthConfig | null = null;\n\n/**\n * Configure auth context for automatic token/orgId injection.\n * When configured, hooks auto-inject these values so you don't need to pass them manually.\n *\n * @example\n * // Cookie auth (no token needed)\n * configureAuth({ getOrgId: () => currentOrg.id });\n *\n * // Bearer auth\n * configureAuth({\n * getToken: () => session?.accessToken ?? null,\n * getOrgId: () => currentOrg?.id ?? null,\n * });\n */\nexport function configureAuth(config: AuthConfig): void {\n authConfig = config;\n}\n\n/**\n * Get the current auth context. Returns nulls when not configured.\n */\nexport function getAuthContext(): { token: string | null; organizationId: string | null } {\n return {\n token: authConfig?.getToken?.() ?? null,\n organizationId: authConfig?.getOrgId?.() ?? null,\n };\n}\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type HttpMethod = 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';\n\nexport interface ApiRequestOptions {\n body?: unknown;\n token?: string | null;\n organizationId?: string | null;\n revalidate?: number;\n headerOptions?: Record<string, string>;\n tags?: string[];\n cache?: RequestCache;\n signal?: AbortSignal;\n}\n\nexport interface BlobResponse {\n data: Blob;\n response: Response;\n}\n\nexport interface TextResponse {\n data: string;\n response: Response;\n}\n\n// ============================================================================\n// Multi-Client Support\n// ============================================================================\n\nexport interface ArcClientConfig extends ClientConfig {\n toast?: ToastHandler;\n navigation?: UseRouterHook;\n}\n\nexport interface ArcClient {\n request: <T = unknown>(method: HttpMethod, endpoint: string, options?: ApiRequestOptions) => Promise<T>;\n config: ClientConfig;\n toast?: ToastHandler;\n navigation?: UseRouterHook;\n}\n\n/**\n * Create an isolated API client for a specific backend.\n * Use this when your app needs to talk to multiple APIs.\n *\n * @example\n * const analyticsClient = createClient({\n * baseUrl: 'https://analytics.example.com',\n * toast: { success: toast.success, error: toast.error },\n * navigation: useRouter,\n * });\n *\n * const eventsApi = createCrudApi('events', { client: analyticsClient });\n */\nexport function createClient(config: ArcClientConfig): ArcClient {\n const { toast, navigation, ...clientCfg } = config;\n return {\n request: <T = unknown>(method: HttpMethod, endpoint: string, options?: ApiRequestOptions) =>\n executeRequest<T>(clientCfg, method, endpoint, options),\n config: clientCfg,\n toast,\n navigation,\n };\n}\n\n// ============================================================================\n// Core Request Logic\n// ============================================================================\n\nasync function executeRequest<T = unknown>(\n config: ClientConfig,\n method: HttpMethod,\n endpoint: string,\n options: ApiRequestOptions = {},\n): Promise<T> {\n const {\n body,\n token,\n organizationId,\n revalidate,\n headerOptions,\n tags,\n cache,\n signal,\n } = options;\n\n try {\n let headers: Record<string, string> = {\n ...(organizationId ? { 'x-organization-id': organizationId } : {}),\n ...(config.defaultHeaders ?? {}),\n };\n\n if (config.internalApiKey) {\n headers['x-internal-api-key'] = config.internalApiKey;\n }\n\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n\n if (body !== undefined && body !== null && !(body instanceof FormData)) {\n headers['Content-Type'] = 'application/json';\n }\n\n if (headerOptions) {\n headers = { ...headers, ...headerOptions };\n }\n\n const fetchOptions: RequestInit & { next?: { revalidate?: number; tags?: string[] } } = {\n method,\n headers,\n credentials: 'include',\n ...(signal ? { signal } : {}),\n };\n\n if (body !== undefined && body !== null) {\n fetchOptions.body = body instanceof FormData ? body : JSON.stringify(body);\n }\n\n if (cache) {\n fetchOptions.cache = cache;\n }\n if (revalidate !== undefined) {\n fetchOptions.next = { ...fetchOptions.next, revalidate };\n }\n if (tags) {\n fetchOptions.next = { ...fetchOptions.next, tags };\n }\n\n const response = await fetch(`${config.baseUrl}${endpoint}`, fetchOptions);\n\n if (!response.ok) {\n const json = await response.json().catch(() => null);\n throw new ArcApiError(\n (json as { message?: string } | null)?.message || response.statusText,\n {\n status: response.status,\n statusText: response.statusText,\n json,\n endpoint,\n method,\n }\n );\n }\n\n const contentType = response.headers.get('Content-Type');\n\n let data: unknown;\n\n if (contentType?.includes('application/json')) {\n data = await response.json();\n } else if (contentType?.includes('application/pdf') || contentType?.includes('image/')) {\n const blobData = await response.blob();\n data = { data: blobData, response };\n } else if (contentType?.includes('text/csv')) {\n const csvData = await response.blob();\n data = { data: csvData, response };\n } else if (contentType?.includes('text/')) {\n const text = await response.text();\n data = { data: text, response };\n } else {\n try {\n const blob = await response.clone().blob();\n data = { data: blob, response };\n } catch {\n const text = await response.text();\n data = { data: text, response };\n }\n }\n\n return data as T;\n } catch (error) {\n if (error instanceof ArcApiError) throw error;\n throw new Error(\n error instanceof Error ? error.message : 'An error occurred while fetching data.'\n );\n }\n}\n\n// ============================================================================\n// API Request Handler (Global — uses configureClient singleton)\n// ============================================================================\n\n/**\n * Universal API request handler.\n * Handles JSON, binary (PDF, images), CSV, and text responses.\n *\n * @example\n * const { success, data } = await handleApiRequest<ApiResponse<User>>('GET', '/users/me');\n * const response = await handleApiRequest<PaginatedResponse<Product>>('GET', '/products?page=1');\n */\nexport async function handleApiRequest<T = unknown>(\n method: HttpMethod,\n endpoint: string,\n options: ApiRequestOptions = {},\n): Promise<T> {\n if (!clientConfig) {\n throw new Error(\n 'arc-next: Client not configured. Call configureClient({ baseUrl }) before making API requests.'\n );\n }\n return executeRequest<T>(clientConfig, method, endpoint, options);\n}\n\n// ============================================================================\n// Query String Utilities\n// ============================================================================\n\n/**\n * Creates query string from parameters.\n * Supports: arrays as field[in]=value1,value2, populateOptions with field selection.\n *\n * @example\n * createQueryString({ page: 1, limit: 10, status: 'active' })\n * // => 'page=1&limit=10&status=active'\n *\n * createQueryString({ roles: ['admin', 'user'] })\n * // => 'roles[in]=admin,user'\n *\n * createQueryString({\n * populateOptions: [{ path: 'employeeId', select: 'name email' }]\n * })\n * // => 'populate[employeeId][select]=name,email'\n */\nexport function createQueryString<T extends Record<string, unknown>>(params: T = {} as T): string {\n const searchParams = new URLSearchParams();\n\n Object.entries(params).forEach(([key, value]) => {\n if (value === undefined || value === '') return;\n\n if (key === 'populateOptions' && Array.isArray(value)) {\n (value as Array<{ path: string; select?: string; match?: Record<string, unknown> }>).forEach(\n (opt) => {\n if (opt.select) {\n const selectFields = opt.select.replace(/\\s+/g, ',');\n searchParams.append(`populate[${opt.path}][select]`, selectFields);\n }\n if (opt.match) {\n searchParams.append(`populate[${opt.path}][match]`, JSON.stringify(opt.match));\n }\n if (!opt.select && !opt.match) {\n const existing = searchParams.get('populate');\n if (existing) {\n searchParams.set('populate', `${existing},${opt.path}`);\n } else {\n searchParams.append('populate', opt.path);\n }\n }\n }\n );\n return;\n }\n\n if (Array.isArray(value)) {\n if (value.length > 1) {\n searchParams.append(`${key}[in]`, value.join(','));\n } else if (value.length === 1) {\n searchParams.append(key, String(value[0]));\n }\n } else if (value === null) {\n searchParams.append(key, 'null');\n } else {\n searchParams.append(key, String(value));\n }\n });\n\n return searchParams.toString();\n}\n"],"mappings":"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"}
@@ -0,0 +1,143 @@
1
+ import { ArcClient, UseRouterHook } from "./client.js";
2
+ import { CacheUtils, DetailQueryOptions, DetailQueryResult, InfiniteListQueryOptions, InfiniteListQueryResult, ListQueryOptions, ListQueryResult, QueryKeys } from "./query.js";
3
+ import { MutationCallbacks } from "./mutation.js";
4
+
5
+ //#region src/hooks.d.ts
6
+ interface CrudApi<T, TCreate = Partial<T>, TUpdate = Partial<T>> {
7
+ getAll: (options: {
8
+ token?: string | null;
9
+ organizationId?: string | null;
10
+ params?: Record<string, unknown>;
11
+ options?: {
12
+ signal?: AbortSignal;
13
+ [key: string]: unknown;
14
+ };
15
+ }) => Promise<unknown>;
16
+ getById: (options: {
17
+ id: string;
18
+ token?: string | null;
19
+ organizationId?: string | null;
20
+ options?: {
21
+ signal?: AbortSignal;
22
+ [key: string]: unknown;
23
+ };
24
+ }) => Promise<unknown>;
25
+ create: (options: {
26
+ token?: string | null;
27
+ organizationId?: string | null;
28
+ data: TCreate;
29
+ }) => Promise<unknown>;
30
+ update: (options: {
31
+ token?: string | null;
32
+ organizationId?: string | null;
33
+ id: string;
34
+ data: TUpdate;
35
+ }) => Promise<unknown>;
36
+ delete: (options: {
37
+ token?: string | null;
38
+ organizationId?: string | null;
39
+ id: string;
40
+ }) => Promise<unknown>;
41
+ }
42
+ interface CrudHooksConfig<T, TCreate = Partial<T>, TUpdate = Partial<T>> {
43
+ api: CrudApi<T, TCreate, TUpdate>;
44
+ entityKey: string;
45
+ singular: string;
46
+ plural?: string;
47
+ defaults?: {
48
+ staleTime?: number;
49
+ gcTime?: number;
50
+ refetchOnWindowFocus?: boolean;
51
+ structuralSharing?: boolean;
52
+ messages?: {
53
+ createSuccess?: string;
54
+ createError?: string;
55
+ updateSuccess?: string;
56
+ updateError?: string;
57
+ deleteSuccess?: string;
58
+ deleteError?: string;
59
+ };
60
+ };
61
+ callbacks?: {
62
+ onCreate?: MutationCallbacks<T, {
63
+ data: TCreate;
64
+ }>;
65
+ onUpdate?: MutationCallbacks<T, {
66
+ id: string;
67
+ data: TUpdate;
68
+ }>;
69
+ onDelete?: MutationCallbacks<unknown, {
70
+ id: string;
71
+ }>;
72
+ };
73
+ client?: ArcClient;
74
+ }
75
+ interface MutationParams<TData> {
76
+ token?: string | null;
77
+ organizationId?: string | null;
78
+ data: TData;
79
+ }
80
+ interface UpdateParams<TData> extends MutationParams<TData> {
81
+ id: string;
82
+ }
83
+ interface DeleteParams {
84
+ token?: string | null;
85
+ organizationId?: string | null;
86
+ id: string;
87
+ }
88
+ interface CallOptions<TData = unknown> {
89
+ onSuccess?: (data: TData) => void;
90
+ onError?: (error: Error) => void;
91
+ silent?: boolean;
92
+ }
93
+ interface CrudActions<T, TCreate, TUpdate> {
94
+ create: (params: MutationParams<TCreate>, options?: CallOptions<T>) => Promise<T>;
95
+ update: (params: UpdateParams<TUpdate>, options?: CallOptions<T>) => Promise<T>;
96
+ remove: (params: DeleteParams, options?: CallOptions) => Promise<unknown>;
97
+ isCreating: boolean;
98
+ isUpdating: boolean;
99
+ isDeleting: boolean;
100
+ isMutating: boolean;
101
+ }
102
+ interface NavigationOptions {
103
+ scroll?: boolean;
104
+ replace?: boolean;
105
+ }
106
+ type NavigateFn<T> = (href: string, item: T, options?: NavigationOptions) => void;
107
+ interface CrudHooksReturn<T, TCreate, TUpdate> {
108
+ KEYS: QueryKeys;
109
+ cache: CacheUtils<T>;
110
+ useList: {
111
+ /** New signature — auto-injects token/orgId from configureAuth() context */(params?: Record<string, unknown>, options?: ListQueryOptions): ListQueryResult<T>; /** Legacy signature — explicit token */
112
+ (token: string | null, params?: Record<string, unknown>, options?: ListQueryOptions): ListQueryResult<T>;
113
+ };
114
+ useDetail: {
115
+ /** New signature — auto-injects token from configureAuth() context */(id: string | null, options?: DetailQueryOptions): DetailQueryResult<T>; /** Legacy signature — explicit token */
116
+ (id: string | null, token: string | null, options?: DetailQueryOptions): DetailQueryResult<T>;
117
+ };
118
+ useInfiniteList: {
119
+ /** New signature — auto-injects token/orgId from configureAuth() context */(params?: Record<string, unknown>, options?: InfiniteListQueryOptions): InfiniteListQueryResult<T>; /** Legacy signature — explicit token */
120
+ (token: string | null, params?: Record<string, unknown>, options?: InfiniteListQueryOptions): InfiniteListQueryResult<T>;
121
+ };
122
+ useActions: () => CrudActions<T, TCreate, TUpdate>;
123
+ useNavigation: () => NavigateFn<T>;
124
+ }
125
+ /**
126
+ * Configure the router hook for useNavigation. Call once at app init.
127
+ *
128
+ * @example
129
+ * import { useRouter } from "next/navigation";
130
+ * configureNavigation(useRouter);
131
+ */
132
+ declare function configureNavigation(hook: UseRouterHook): void;
133
+ declare function createCrudHooks<T, TCreate = Partial<T>, TUpdate = Partial<T>>({
134
+ api,
135
+ entityKey,
136
+ singular,
137
+ defaults,
138
+ callbacks,
139
+ client
140
+ }: CrudHooksConfig<T, TCreate, TUpdate>): CrudHooksReturn<T, TCreate, TUpdate>;
141
+ //#endregion
142
+ export { CallOptions, CrudActions, CrudApi, CrudHooksConfig, CrudHooksReturn, DeleteParams, MutationParams, NavigateFn, NavigationOptions, UpdateParams, type UseRouterHook, configureNavigation, createCrudHooks };
143
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.ts","names":[],"sources":["../src/hooks.ts"],"mappings":"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"}