@classytic/arc-next 0.11.1 → 0.12.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/README.md +50 -0
- package/dist/api.d.ts +40 -23
- package/dist/api.js +14 -1
- package/dist/cache.d.ts +20 -2
- package/dist/cache.js +52 -1
- package/dist/client.d.ts +82 -12
- package/dist/client.js +110 -22
- package/dist/encryption.d.ts +1 -1
- package/dist/hooks.d.ts +10 -10
- package/dist/hooks.js +99 -27
- package/dist/mutation.d.ts +29 -1
- package/dist/mutation.js +28 -11
- package/dist/presets/history.d.ts +1 -1
- package/dist/query.d.ts +1 -1
- package/dist/sse.js +1 -1
- package/dist/upload.d.ts +9 -0
- package/dist/upload.js +6 -3
- package/dist/ws.d.ts +1 -1
- package/dist/ws.js +1 -1
- package/package.json +18 -7
package/dist/mutation.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import { getQuotaDetails, isArcApiError, isAutoIdempotency } from "./client.js";
|
|
4
4
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
5
|
-
import {
|
|
5
|
+
import { useRef, useTransition } from "react";
|
|
6
6
|
|
|
7
7
|
//#region src/mutation.ts
|
|
8
8
|
let toastHandler = {
|
|
9
|
-
success: (
|
|
10
|
-
error: (
|
|
9
|
+
success: () => {},
|
|
10
|
+
error: () => {}
|
|
11
11
|
};
|
|
12
12
|
/**
|
|
13
13
|
* Configure toast handler. Call once at app init.
|
|
@@ -78,9 +78,11 @@ function useMutationWithTransition(config) {
|
|
|
78
78
|
},
|
|
79
79
|
onSuccess: (data, variables) => {
|
|
80
80
|
const invalidate = () => {
|
|
81
|
-
invalidateQueries.forEach((key) =>
|
|
81
|
+
invalidateQueries.forEach((key) => {
|
|
82
|
+
queryClient.invalidateQueries({ queryKey: key });
|
|
83
|
+
});
|
|
82
84
|
};
|
|
83
|
-
if (
|
|
85
|
+
if (invalidateQueries.length > 0 && (config.shouldInvalidate?.(data) ?? true)) if (withTransition) startTransition(invalidate);
|
|
84
86
|
else invalidate();
|
|
85
87
|
if (toast && (config.shouldToast?.() ?? true)) showToast("success", messages, data, variables, void 0, instanceToast);
|
|
86
88
|
onSuccess?.(data, variables);
|
|
@@ -121,12 +123,16 @@ function useMutationWithOptimistic(config) {
|
|
|
121
123
|
return { previous };
|
|
122
124
|
},
|
|
123
125
|
onSuccess: (data, variables) => {
|
|
124
|
-
queryKeys.forEach((key) =>
|
|
126
|
+
queryKeys.forEach((key) => {
|
|
127
|
+
queryClient.invalidateQueries({ queryKey: key });
|
|
128
|
+
});
|
|
125
129
|
if (toast) showToast("success", messages, data, variables, void 0, instanceToast);
|
|
126
130
|
onSuccess?.(data, variables);
|
|
127
131
|
},
|
|
128
132
|
onError: (error, variables, context) => {
|
|
129
|
-
context?.previous?.forEach(({ key, data }) =>
|
|
133
|
+
context?.previous?.forEach(({ key, data }) => {
|
|
134
|
+
queryClient.setQueryData(key, data);
|
|
135
|
+
});
|
|
130
136
|
if (toast) showToast("error", messages, null, variables, error, instanceToast);
|
|
131
137
|
onError?.(error, variables);
|
|
132
138
|
},
|
|
@@ -146,9 +152,15 @@ function useMutationWithOptimistic(config) {
|
|
|
146
152
|
};
|
|
147
153
|
}
|
|
148
154
|
function useOptimisticMutation(config) {
|
|
149
|
-
const { mutationFn, queryClient, queryKeys, optimisticUpdate, onSuccess, onError, onSettled, messages, toastHandler: instanceToast } = config;
|
|
155
|
+
const { mutationFn, queryClient, queryKeys, optimisticUpdate, mutationKey, reconcile, onSuccess, onError, onSettled, messages, toastHandler: instanceToast } = config;
|
|
156
|
+
const invalidateAll = () => {
|
|
157
|
+
queryKeys.forEach((key) => {
|
|
158
|
+
queryClient.invalidateQueries({ queryKey: key });
|
|
159
|
+
});
|
|
160
|
+
};
|
|
150
161
|
return useMutation({
|
|
151
162
|
mutationFn,
|
|
163
|
+
...mutationKey ? { mutationKey } : {},
|
|
152
164
|
onMutate: async (variables) => {
|
|
153
165
|
await Promise.all(queryKeys.map((key) => queryClient.cancelQueries({
|
|
154
166
|
queryKey: key,
|
|
@@ -160,24 +172,29 @@ function useOptimisticMutation(config) {
|
|
|
160
172
|
}));
|
|
161
173
|
if (optimisticUpdate) queryKeys.forEach((key) => {
|
|
162
174
|
queryClient.getQueriesData({ queryKey: key }).forEach(([qKey, qData]) => {
|
|
163
|
-
|
|
175
|
+
const next = optimisticUpdate(qData, variables, qKey);
|
|
176
|
+
if (next !== qData) queryClient.setQueryData(qKey, next);
|
|
164
177
|
});
|
|
165
178
|
});
|
|
166
179
|
return { previous };
|
|
167
180
|
},
|
|
168
181
|
onSuccess: (data, variables) => {
|
|
169
182
|
if ((config.shouldToast?.() ?? true) && messages?.success) showToast("success", messages, data, variables, void 0, instanceToast);
|
|
170
|
-
|
|
183
|
+
reconcile?.(data, variables);
|
|
184
|
+
if (!mutationKey) invalidateAll();
|
|
171
185
|
onSuccess?.(data, variables);
|
|
172
186
|
},
|
|
173
187
|
onError: (error, variables, context) => {
|
|
174
188
|
context?.previous?.forEach(({ data }) => {
|
|
175
|
-
data.forEach(([qKey, qData]) =>
|
|
189
|
+
data.forEach(([qKey, qData]) => {
|
|
190
|
+
queryClient.setQueryData(qKey, qData);
|
|
191
|
+
});
|
|
176
192
|
});
|
|
177
193
|
if (config.shouldToast?.() ?? true) showToast("error", messages, null, variables, error, instanceToast);
|
|
178
194
|
onError?.(error, variables);
|
|
179
195
|
},
|
|
180
196
|
onSettled: (data, error, variables) => {
|
|
197
|
+
if (mutationKey && queryClient.isMutating({ mutationKey }) === 1) invalidateAll();
|
|
181
198
|
onSettled?.(data, error, variables);
|
|
182
199
|
}
|
|
183
200
|
});
|
|
@@ -6,7 +6,7 @@ interface HistoryEntry {
|
|
|
6
6
|
id: string;
|
|
7
7
|
resource: string;
|
|
8
8
|
documentId: string;
|
|
9
|
-
action:
|
|
9
|
+
action: "create" | "update" | "delete" | "restore" | "custom";
|
|
10
10
|
userId?: string;
|
|
11
11
|
organizationId?: string;
|
|
12
12
|
before?: Record<string, unknown>;
|
package/dist/query.d.ts
CHANGED
|
@@ -286,7 +286,7 @@ interface UseApiQueryOptions {
|
|
|
286
286
|
refetchIntervalInBackground?: boolean;
|
|
287
287
|
retry?: boolean | number;
|
|
288
288
|
/** Limit re-renders to changes in these specific fields (perf optimization). */
|
|
289
|
-
notifyOnChangeProps?: (
|
|
289
|
+
notifyOnChangeProps?: ("data" | "error" | "isLoading" | "isFetching" | "isError" | "isSuccess" | "isStale")[];
|
|
290
290
|
}
|
|
291
291
|
interface UseApiQueryConfig<TResponse, TData> {
|
|
292
292
|
queryKey: QueryKey;
|
package/dist/sse.js
CHANGED
|
@@ -166,7 +166,7 @@ function subscribeToEvents(options) {
|
|
|
166
166
|
const scheduleReconnect = () => {
|
|
167
167
|
if (reconnectAttempts < maxReconnectAttempts) {
|
|
168
168
|
reconnectAttempts += 1;
|
|
169
|
-
const delay = Math.min(reconnectDelay *
|
|
169
|
+
const delay = Math.min(reconnectDelay * 1.5 ** (reconnectAttempts - 1), 3e4);
|
|
170
170
|
reconnectTimer = setTimeout(connect, delay);
|
|
171
171
|
}
|
|
172
172
|
};
|
package/dist/upload.d.ts
CHANGED
|
@@ -71,6 +71,15 @@ interface UploadWithProgressOptions {
|
|
|
71
71
|
* an image and returns the resized binary). Default: false (parse JSON).
|
|
72
72
|
*/
|
|
73
73
|
responseType?: "json" | "text" | "blob";
|
|
74
|
+
/**
|
|
75
|
+
* Upload timeout in ms, assigned to `xhr.timeout` — the whole request
|
|
76
|
+
* (upload + server processing + response) must finish within this window
|
|
77
|
+
* or the promise rejects with a `TimeoutError`. Default: disabled (0) —
|
|
78
|
+
* large files on slow links legitimately take minutes; size it to your
|
|
79
|
+
* payload ceiling when you enable it. (`ClientConfig.timeoutMs` does NOT
|
|
80
|
+
* apply to uploads — the fetch pipeline and XHR transport stay isolated.)
|
|
81
|
+
*/
|
|
82
|
+
timeoutMs?: number;
|
|
74
83
|
}
|
|
75
84
|
/**
|
|
76
85
|
* Upload a `FormData` payload via XHR with native progress events.
|
package/dist/upload.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
1
3
|
import { ArcApiError, _getAuthErrorHandler, _isAuthRecoverable, _resolveRefreshedToken, _runAuthRecovery, getAuthMode, getBaseUrl, getClientAuthContext } from "./client.js";
|
|
2
4
|
import { getToastHandler } from "./mutation.js";
|
|
3
5
|
import { useQueryClient } from "@tanstack/react-query";
|
|
@@ -110,7 +112,7 @@ async function uploadWithProgress(options) {
|
|
|
110
112
|
* in the outer loop; this function just performs one upload.
|
|
111
113
|
*/
|
|
112
114
|
function uploadAttempt(options) {
|
|
113
|
-
const { url, formData, method = "POST", onProgress, signal, client, headers: extraHeaders, elevated, idempotencyKey, responseType = "json" } = options;
|
|
115
|
+
const { url, formData, method = "POST", onProgress, signal, client, headers: extraHeaders, elevated, idempotencyKey, responseType = "json", timeoutMs = 0 } = options;
|
|
114
116
|
return new Promise((resolve, reject) => {
|
|
115
117
|
if (signal?.aborted) {
|
|
116
118
|
reject(abortReason(signal));
|
|
@@ -119,6 +121,7 @@ function uploadAttempt(options) {
|
|
|
119
121
|
const xhr = new XMLHttpRequest();
|
|
120
122
|
const fullUrl = resolveUrl(url, client);
|
|
121
123
|
xhr.open(method, fullUrl, true);
|
|
124
|
+
if (timeoutMs > 0) xhr.timeout = timeoutMs;
|
|
122
125
|
xhr.responseType = responseType === "blob" ? "blob" : "";
|
|
123
126
|
const authMode = client?.config?.authMode ?? getAuthMode();
|
|
124
127
|
if (authMode === "cookie") xhr.withCredentials = true;
|
|
@@ -130,7 +133,7 @@ function uploadAttempt(options) {
|
|
|
130
133
|
if (authMode === "header") {
|
|
131
134
|
const headerName = client?.auth?.headerName ?? "x-api-key";
|
|
132
135
|
builtHeaders[headerName] = token;
|
|
133
|
-
} else if (authMode !== "cookie") builtHeaders
|
|
136
|
+
} else if (authMode !== "cookie") builtHeaders.Authorization = `Bearer ${token}`;
|
|
134
137
|
}
|
|
135
138
|
if (orgId) builtHeaders["x-organization-id"] = orgId;
|
|
136
139
|
if (elevated ?? client?.config?.elevated) builtHeaders["x-arc-scope"] = "platform";
|
|
@@ -191,7 +194,7 @@ function uploadAttempt(options) {
|
|
|
191
194
|
};
|
|
192
195
|
xhr.ontimeout = () => {
|
|
193
196
|
if (abortListener && signal) signal.removeEventListener("abort", abortListener);
|
|
194
|
-
reject(/* @__PURE__ */ new Error(`Upload timed out: ${fullUrl}`));
|
|
197
|
+
reject(Object.assign(/* @__PURE__ */ new Error(`Upload timed out after ${timeoutMs}ms: ${fullUrl}`), { name: "TimeoutError" }));
|
|
195
198
|
};
|
|
196
199
|
xhr.send(formData);
|
|
197
200
|
});
|
package/dist/ws.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ interface ArcWsMessage<TData = unknown> {
|
|
|
21
21
|
* Backend also accepts `channel` as alias.
|
|
22
22
|
*/
|
|
23
23
|
interface ArcSubscribeFrame {
|
|
24
|
-
type:
|
|
24
|
+
type: "subscribe" | "unsubscribe";
|
|
25
25
|
resource?: string;
|
|
26
26
|
channel?: string;
|
|
27
27
|
/** Free-form additional fields the backend may use (token, filters, etc.). */
|
package/dist/ws.js
CHANGED
|
@@ -150,7 +150,7 @@ function connectWs(options = {}) {
|
|
|
150
150
|
}
|
|
151
151
|
if (reconnectAttempts < maxReconnectAttempts) {
|
|
152
152
|
reconnectAttempts += 1;
|
|
153
|
-
const delay = Math.min(reconnectDelay *
|
|
153
|
+
const delay = Math.min(reconnectDelay * 1.5 ** (reconnectAttempts - 1), 3e4);
|
|
154
154
|
reconnectTimer = setTimeout(connect, delay);
|
|
155
155
|
}
|
|
156
156
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/arc-next",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "React + TanStack Query SDK for Arc resources",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"author": "Classytic",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
|
-
"url": "https://github.com/classytic/arc-next"
|
|
11
|
+
"url": "git+https://github.com/classytic/arc-next.git"
|
|
12
12
|
},
|
|
13
13
|
"keywords": [
|
|
14
14
|
"react",
|
|
@@ -136,11 +136,19 @@
|
|
|
136
136
|
"push": "classytic-push",
|
|
137
137
|
"release:tag": "node -e \"require('child_process').execSync('npm run push -- v'+require('./package.json').version,{stdio:'inherit'})\"",
|
|
138
138
|
"release": "npm run push -- main && npm run release:tag && npm publish",
|
|
139
|
-
"prepublishOnly": "npm run typecheck && npm test && npm run build",
|
|
140
|
-
"typecheck:tests": "tsc --noEmit -p tsconfig.test.json"
|
|
139
|
+
"prepublishOnly": "npm run typecheck && npm run typecheck:tests && npm run lint && npm run check:dead-code && npm test && npm run build && npm run check:package && npm run check:api-surface && npm run check:server-import && npm run check:bundle && npm run check:fixtures",
|
|
140
|
+
"typecheck:tests": "tsc --noEmit -p tsconfig.test.json",
|
|
141
|
+
"check:package": "publint --strict && attw --pack . --profile esm-only",
|
|
142
|
+
"check:api-surface": "node scripts/check-api-surface.mjs",
|
|
143
|
+
"api:surface": "node scripts/check-api-surface.mjs --update",
|
|
144
|
+
"check:server-import": "node scripts/check-server-import.mjs",
|
|
145
|
+
"check:bundle": "node scripts/check-bundle-size.mjs",
|
|
146
|
+
"check:fixtures": "tsc -p fixtures/tsconfig.json",
|
|
147
|
+
"lint": "biome check src/ tests/ scripts/ fixtures/",
|
|
148
|
+
"check:dead-code": "knip"
|
|
141
149
|
},
|
|
142
150
|
"peerDependencies": {
|
|
143
|
-
"@classytic/repo-core": ">=0.
|
|
151
|
+
"@classytic/repo-core": ">=0.14.0",
|
|
144
152
|
"@tanstack/react-query": ">=5.62.0",
|
|
145
153
|
"jose": ">=5.0.0",
|
|
146
154
|
"react": ">=19.0.0"
|
|
@@ -160,15 +168,18 @@
|
|
|
160
168
|
}
|
|
161
169
|
},
|
|
162
170
|
"devDependencies": {
|
|
171
|
+
"@arethetypeswrong/cli": "^0.18.5",
|
|
172
|
+
"@biomejs/biome": "^2.5.5",
|
|
163
173
|
"@classytic/dev-tools": "^0.2.0",
|
|
164
|
-
"@classytic/repo-core": "^0.
|
|
174
|
+
"@classytic/repo-core": "^0.14.0",
|
|
165
175
|
"@tanstack/react-query": "^5.97.0",
|
|
166
|
-
"@testing-library/jest-dom": "^6.9.1",
|
|
167
176
|
"@testing-library/react": "^16.3.2",
|
|
168
177
|
"@types/react": "^19.2.14",
|
|
169
178
|
"@types/react-dom": "^19.2.3",
|
|
170
179
|
"jose": "^6.2.3",
|
|
171
180
|
"jsdom": "^29.0.2",
|
|
181
|
+
"knip": "^6.27.0",
|
|
182
|
+
"publint": "^0.3.21",
|
|
172
183
|
"react": "^19.2.5",
|
|
173
184
|
"react-dom": "^19.2.5",
|
|
174
185
|
"tsdown": "^0.21.7",
|