@checkstack/frontend-api 0.3.8 → 0.3.10
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/CHANGELOG.md +43 -0
- package/package.json +4 -4
- package/src/components/ExtensionSlot.tsx +1 -2
- package/src/orpc-query.tsx +50 -29
- package/src/runtime-config.tsx +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
# @checkstack/frontend-api
|
|
2
2
|
|
|
3
|
+
## 0.3.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- c4e7560: Fix data integrity, cache invalidation, and mobile UI issues
|
|
8
|
+
|
|
9
|
+
- **Centralized mutation cache invalidation**: Every mutation now automatically invalidates its plugin's query cache on success via the shared `createProcedureHook` in `orpc-query.tsx`. This ensures all views stay in sync without requiring individual components to remember manual `invalidateQueries` calls.
|
|
10
|
+
- **Fixed oRPC query key matching**: Query keys use nested arrays (`[["pluginId"]]`) to correctly match oRPC's `[pathArray, options]` key structure. Fixed the broken flat-string pattern in `SystemBadgeDataProvider`.
|
|
11
|
+
- **Fixed hourly aggregation duplication**: Added `NULLS NOT DISTINCT` to the `health_check_aggregates` unique constraint so local runs (`source_id = NULL`) correctly conflict-match instead of creating duplicate hourly buckets. Includes a migration to clean up existing duplicates.
|
|
12
|
+
- **Fixed modal scrolling on mobile**: Added `max-height` + `overflow-y-auto` to `ConfirmationModal`, and refactored `Dialog` from translate-centering to flex-centering with `dvh` units for reliable mobile scroll containment.
|
|
13
|
+
|
|
14
|
+
## 0.3.9
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- d1a2796: Enforce stricter code quality standards and eliminate AI slop anti-patterns.
|
|
19
|
+
|
|
20
|
+
**New utility**
|
|
21
|
+
|
|
22
|
+
- `extractErrorMessage(error, fallback?)` in `@checkstack/common` for consistent error extraction
|
|
23
|
+
|
|
24
|
+
**ESLint rules**
|
|
25
|
+
|
|
26
|
+
- `react-hooks/rules-of-hooks` and `exhaustive-deps` for hook correctness
|
|
27
|
+
- `no-console` in frontend packages — forces `toast` over silent `console.error`
|
|
28
|
+
- `no-restricted-syntax` banning `instanceof Error` — forces `extractErrorMessage`
|
|
29
|
+
- Custom `no-eslint-disable-any` rule preventing `@typescript-eslint/no-explicit-any` circumvention
|
|
30
|
+
|
|
31
|
+
**Refactoring**
|
|
32
|
+
|
|
33
|
+
- Replace 141 `instanceof Error` boilerplate patterns across the codebase
|
|
34
|
+
- Replace swallowed `console.error` with user-visible `toast.error()` feedback
|
|
35
|
+
- Remove 15 redundant `as` type casts in IntegrationsPage and ProviderConnectionsPage
|
|
36
|
+
- Consolidate 3 identical callback handlers into `handleDialogClose`
|
|
37
|
+
- Fix conditional React hook call in `FormField.tsx`
|
|
38
|
+
- Fix unstable useMemo deps in `Dashboard.tsx`
|
|
39
|
+
- Replace `useEffect`→`setState` with derived `useMemo` in `RegisterPage.tsx`
|
|
40
|
+
- Rewrite `keystore.test.ts` with typed `DrizzleMockChain` (eliminating 7 `any` suppressions)
|
|
41
|
+
- Delete obvious comments in `encryption.ts` and Teams `provider.ts`
|
|
42
|
+
|
|
43
|
+
- Updated dependencies [d1a2796]
|
|
44
|
+
- @checkstack/common@0.6.5
|
|
45
|
+
|
|
3
46
|
## 0.3.8
|
|
4
47
|
|
|
5
48
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/frontend-api",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"scripts": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"react": "^18.0.0"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@checkstack/common": "0.6.
|
|
15
|
+
"@checkstack/common": "0.6.5",
|
|
16
16
|
"@orpc/client": "^1.13.14",
|
|
17
17
|
"@orpc/react-query": "1.13.4",
|
|
18
18
|
"@orpc/tanstack-query": "^1.13.2",
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"@types/bun": "^1.3.5",
|
|
23
23
|
"@types/react": "^18.0.0",
|
|
24
24
|
"typescript": "^5.0.0",
|
|
25
|
-
"@checkstack/tsconfig": "0.0.
|
|
26
|
-
"@checkstack/scripts": "0.1.
|
|
25
|
+
"@checkstack/tsconfig": "0.0.5",
|
|
26
|
+
"@checkstack/scripts": "0.1.2"
|
|
27
27
|
},
|
|
28
28
|
"checkstack": {
|
|
29
29
|
"type": "tooling"
|
|
@@ -37,8 +37,7 @@ export function ExtensionSlot<TSlot extends SlotDefinition<unknown>>({
|
|
|
37
37
|
return (
|
|
38
38
|
<>
|
|
39
39
|
{extensions.map((ext) => {
|
|
40
|
-
|
|
41
|
-
const Component = ext.component as React.ComponentType<any>;
|
|
40
|
+
const Component = ext.component as React.ComponentType<Record<string, unknown>>;
|
|
42
41
|
return <Component key={ext.id} {...(context ?? {})} />;
|
|
43
42
|
})}
|
|
44
43
|
</>
|
package/src/orpc-query.tsx
CHANGED
|
@@ -14,9 +14,8 @@ import {
|
|
|
14
14
|
type UseMutationOptions,
|
|
15
15
|
} from "@tanstack/react-query";
|
|
16
16
|
|
|
17
|
-
// Re-export useQueryClient for cache invalidation in consuming packages
|
|
18
|
-
// This ensures all packages use the same React Query instance
|
|
19
17
|
export { useQueryClient } from "@tanstack/react-query";
|
|
18
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
20
19
|
import { useApi } from "./api-context";
|
|
21
20
|
import { rpcApiRef } from "./core-apis";
|
|
22
21
|
import type { ClientDefinition, InferClient } from "@checkstack/common";
|
|
@@ -71,7 +70,7 @@ function useOrpcUtils(): OrpcUtils {
|
|
|
71
70
|
if (!context) {
|
|
72
71
|
throw new Error(
|
|
73
72
|
"usePluginClient must be used within OrpcQueryProvider. " +
|
|
74
|
-
"Wrap your app with <OrpcQueryProvider>."
|
|
73
|
+
"Wrap your app with <OrpcQueryProvider>.",
|
|
75
74
|
);
|
|
76
75
|
}
|
|
77
76
|
return context;
|
|
@@ -88,7 +87,7 @@ function useOrpcUtils(): OrpcUtils {
|
|
|
88
87
|
interface QueryProcedure<TInput, TOutput> {
|
|
89
88
|
useQuery: (
|
|
90
89
|
input?: TInput,
|
|
91
|
-
options?: Omit<UseQueryOptions<TOutput, Error>, "queryKey" | "queryFn"
|
|
90
|
+
options?: Omit<UseQueryOptions<TOutput, Error>, "queryKey" | "queryFn">,
|
|
92
91
|
) => UseQueryResult<TOutput, Error>;
|
|
93
92
|
}
|
|
94
93
|
|
|
@@ -101,7 +100,7 @@ interface MutationProcedure<TInput, TOutput> {
|
|
|
101
100
|
options?: Omit<
|
|
102
101
|
UseMutationOptions<TOutput, Error, TInput>,
|
|
103
102
|
"mutationFn" | "mutationKey"
|
|
104
|
-
|
|
103
|
+
>,
|
|
105
104
|
) => UseMutationResult<TOutput, Error, TInput>;
|
|
106
105
|
}
|
|
107
106
|
|
|
@@ -148,14 +147,15 @@ type InferSchemaOutputType<T> = T extends { _output: infer O } ? O : unknown;
|
|
|
148
147
|
/**
|
|
149
148
|
* Check if a contract type is a procedure (not a nested router).
|
|
150
149
|
*/
|
|
151
|
-
type IsContractProcedure<T> =
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
150
|
+
type IsContractProcedure<T> =
|
|
151
|
+
T extends ContractProcedure<
|
|
152
|
+
infer _TInput,
|
|
153
|
+
infer _TOutput,
|
|
154
|
+
infer _TErrors,
|
|
155
|
+
infer _TMeta
|
|
156
|
+
>
|
|
157
|
+
? true
|
|
158
|
+
: false;
|
|
159
159
|
|
|
160
160
|
/**
|
|
161
161
|
* Maps a contract router to wrapped procedures based on operationType.
|
|
@@ -167,10 +167,10 @@ type WrappedClient<TContract extends AnyContractRouter, TClient> = {
|
|
|
167
167
|
> extends true
|
|
168
168
|
? WrappedProcedure<TContract[K], TClient[K]>
|
|
169
169
|
: TClient[K] extends object
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
170
|
+
? TContract[K] extends AnyContractRouter
|
|
171
|
+
? WrappedClient<TContract[K], TClient[K]>
|
|
172
|
+
: never
|
|
173
|
+
: never;
|
|
174
174
|
};
|
|
175
175
|
|
|
176
176
|
// =============================================================================
|
|
@@ -202,7 +202,7 @@ type WrappedClient<TContract extends AnyContractRouter, TClient> = {
|
|
|
202
202
|
* ```
|
|
203
203
|
*/
|
|
204
204
|
export function usePluginClient<T extends ClientDefinition>(
|
|
205
|
-
definition: T
|
|
205
|
+
definition: T,
|
|
206
206
|
): WrappedClient<NonNullable<T["__contractType"]>, InferClient<T>> {
|
|
207
207
|
const orpcUtils = useOrpcUtils();
|
|
208
208
|
|
|
@@ -213,7 +213,7 @@ export function usePluginClient<T extends ClientDefinition>(
|
|
|
213
213
|
if (!pluginUtils) {
|
|
214
214
|
throw new Error(
|
|
215
215
|
`Plugin "${definition.pluginId}" not found. ` +
|
|
216
|
-
`Ensure the plugin is registered and the backend is running
|
|
216
|
+
`Ensure the plugin is registered and the backend is running.`,
|
|
217
217
|
);
|
|
218
218
|
}
|
|
219
219
|
|
|
@@ -221,8 +221,8 @@ export function usePluginClient<T extends ClientDefinition>(
|
|
|
221
221
|
const contract = definition.contract as Record<string, unknown>;
|
|
222
222
|
|
|
223
223
|
return useMemo(() => {
|
|
224
|
-
return wrapPluginUtils(pluginUtils, contract);
|
|
225
|
-
}, [pluginUtils, contract]) as WrappedClient<
|
|
224
|
+
return wrapPluginUtils(pluginUtils, contract, definition.pluginId);
|
|
225
|
+
}, [pluginUtils, contract, definition.pluginId]) as WrappedClient<
|
|
226
226
|
NonNullable<T["__contractType"]>,
|
|
227
227
|
InferClient<T>
|
|
228
228
|
>;
|
|
@@ -234,7 +234,8 @@ export function usePluginClient<T extends ClientDefinition>(
|
|
|
234
234
|
|
|
235
235
|
function wrapPluginUtils(
|
|
236
236
|
utils: Record<string, unknown>,
|
|
237
|
-
contract: Record<string, unknown
|
|
237
|
+
contract: Record<string, unknown>,
|
|
238
|
+
pluginId: string,
|
|
238
239
|
): Record<string, unknown> {
|
|
239
240
|
const wrapped: Record<string, unknown> = {};
|
|
240
241
|
|
|
@@ -276,13 +277,15 @@ function wrapPluginUtils(
|
|
|
276
277
|
unknown,
|
|
277
278
|
Error
|
|
278
279
|
>,
|
|
279
|
-
operationType
|
|
280
|
+
operationType,
|
|
281
|
+
pluginId,
|
|
280
282
|
);
|
|
281
283
|
} else {
|
|
282
284
|
// Nested namespace - recurse
|
|
283
285
|
wrapped[key] = wrapPluginUtils(
|
|
284
286
|
procedureUtils as Record<string, unknown>,
|
|
285
|
-
(contractProcedure || {}) as Record<string, unknown
|
|
287
|
+
(contractProcedure || {}) as Record<string, unknown>,
|
|
288
|
+
pluginId,
|
|
286
289
|
);
|
|
287
290
|
}
|
|
288
291
|
}
|
|
@@ -296,7 +299,7 @@ function wrapPluginUtils(
|
|
|
296
299
|
*/
|
|
297
300
|
function getOperationType(
|
|
298
301
|
contractProcedure: Record<string, unknown> | undefined,
|
|
299
|
-
procedureName?: string
|
|
302
|
+
procedureName?: string,
|
|
300
303
|
): "query" | "mutation" {
|
|
301
304
|
const orpcMeta = contractProcedure?.["~orpc"] as
|
|
302
305
|
| { meta?: { operationType?: "query" | "mutation" } }
|
|
@@ -309,7 +312,7 @@ function getOperationType(
|
|
|
309
312
|
`Procedure ${
|
|
310
313
|
procedureName ? `"${procedureName}" ` : ""
|
|
311
314
|
}is missing required "operationType" in contract metadata. ` +
|
|
312
|
-
`Add operationType: "query" or operationType: "mutation" to the procedure's .meta() call
|
|
315
|
+
`Add operationType: "query" or operationType: "mutation" to the procedure's .meta() call.`,
|
|
313
316
|
);
|
|
314
317
|
}
|
|
315
318
|
|
|
@@ -321,13 +324,31 @@ function getOperationType(
|
|
|
321
324
|
*/
|
|
322
325
|
function createProcedureHook<TInput, TOutput>(
|
|
323
326
|
proc: ProcedureUtils<ClientContext, TInput, TOutput, Error>,
|
|
324
|
-
operationType: "query" | "mutation"
|
|
327
|
+
operationType: "query" | "mutation",
|
|
328
|
+
pluginId: string,
|
|
325
329
|
): QueryProcedure<TInput, TOutput> | MutationProcedure<TInput, TOutput> {
|
|
326
330
|
if (operationType === "mutation") {
|
|
327
331
|
return {
|
|
328
332
|
useMutation: (options) => {
|
|
329
|
-
const
|
|
330
|
-
|
|
333
|
+
const queryClient = useQueryClient();
|
|
334
|
+
const mutationOpts = proc.mutationOptions({
|
|
335
|
+
...options,
|
|
336
|
+
onSuccess: (...args) => {
|
|
337
|
+
// Automatically invalidate all queries for this plugin
|
|
338
|
+
// so every view showing this plugin's data stays fresh.
|
|
339
|
+
// oRPC query keys are [pathArray, options] where pathArray
|
|
340
|
+
// starts with the pluginId, e.g. ["healthcheck", "getConfigurations"].
|
|
341
|
+
void queryClient.invalidateQueries({
|
|
342
|
+
queryKey: [[pluginId]],
|
|
343
|
+
});
|
|
344
|
+
// Call the user-provided onSuccess handler if present
|
|
345
|
+
options?.onSuccess?.(...args);
|
|
346
|
+
},
|
|
347
|
+
});
|
|
348
|
+
// Remove the duplicate onSuccess from the outer options to avoid
|
|
349
|
+
// double-invocation — it's already integrated above.
|
|
350
|
+
const { onSuccess: _, ...restOptions } = options ?? {};
|
|
351
|
+
return useMutation({ ...mutationOpts, ...restOptions });
|
|
331
352
|
},
|
|
332
353
|
};
|
|
333
354
|
}
|
package/src/runtime-config.tsx
CHANGED
|
@@ -116,6 +116,7 @@ export const RuntimeConfigProvider: React.FC<RuntimeConfigProviderProps> = ({
|
|
|
116
116
|
} catch (error_) {
|
|
117
117
|
console.error("RuntimeConfigProvider: Failed to load config", error_);
|
|
118
118
|
setError(
|
|
119
|
+
// eslint-disable-next-line no-restricted-syntax -- Needs Error object for state, not just message
|
|
119
120
|
error_ instanceof Error ? error_ : new Error(String(error_))
|
|
120
121
|
);
|
|
121
122
|
// Do NOT set a fallback — surface the error visibly.
|