@checkstack/common 0.2.0 → 0.3.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/CHANGELOG.md +54 -0
- package/package.json +1 -1
- package/src/access-utils.ts +13 -1
- package/src/client-definition.ts +39 -12
- package/src/index.ts +1 -0
- package/src/procedure-builder.ts +52 -0
- package/src/types.ts +11 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,59 @@
|
|
|
1
1
|
# @checkstack/common
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 7a23261: ## TanStack Query Integration
|
|
8
|
+
|
|
9
|
+
Migrated all frontend components to use `usePluginClient` hook with TanStack Query integration, replacing the legacy `forPlugin()` pattern.
|
|
10
|
+
|
|
11
|
+
### New Features
|
|
12
|
+
|
|
13
|
+
- **`usePluginClient` hook**: Provides type-safe access to plugin APIs with `.useQuery()` and `.useMutation()` methods
|
|
14
|
+
- **Automatic request deduplication**: Multiple components requesting the same data share a single network request
|
|
15
|
+
- **Built-in caching**: Configurable stale time and cache duration per query
|
|
16
|
+
- **Loading/error states**: TanStack Query provides `isLoading`, `error`, `isRefetching` states automatically
|
|
17
|
+
- **Background refetching**: Stale data is automatically refreshed when components mount
|
|
18
|
+
|
|
19
|
+
### Contract Changes
|
|
20
|
+
|
|
21
|
+
All RPC contracts now require `operationType: "query"` or `operationType: "mutation"` metadata:
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
const getItems = proc()
|
|
25
|
+
.meta({ operationType: "query", access: [access.read] })
|
|
26
|
+
.output(z.array(itemSchema))
|
|
27
|
+
.query();
|
|
28
|
+
|
|
29
|
+
const createItem = proc()
|
|
30
|
+
.meta({ operationType: "mutation", access: [access.manage] })
|
|
31
|
+
.input(createItemSchema)
|
|
32
|
+
.output(itemSchema)
|
|
33
|
+
.mutation();
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Migration
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// Before (forPlugin pattern)
|
|
40
|
+
const api = useApi(myPluginApiRef);
|
|
41
|
+
const [items, setItems] = useState<Item[]>([]);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
api.getItems().then(setItems);
|
|
44
|
+
}, [api]);
|
|
45
|
+
|
|
46
|
+
// After (usePluginClient pattern)
|
|
47
|
+
const client = usePluginClient(MyPluginApi);
|
|
48
|
+
const { data: items, isLoading } = client.getItems.useQuery({});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Bug Fixes
|
|
52
|
+
|
|
53
|
+
- Fixed `rpc.test.ts` test setup for middleware type inference
|
|
54
|
+
- Fixed `SearchDialog` to use `setQuery` instead of deprecated `search` method
|
|
55
|
+
- Fixed null→undefined warnings in notification and queue frontends
|
|
56
|
+
|
|
3
57
|
## 0.2.0
|
|
4
58
|
|
|
5
59
|
### Minor Changes
|
package/package.json
CHANGED
package/src/access-utils.ts
CHANGED
|
@@ -26,6 +26,13 @@ export interface InstanceAccessConfig {
|
|
|
26
26
|
* Example: "systems" for response { systems: [...] }
|
|
27
27
|
*/
|
|
28
28
|
listKey?: string;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* For bulk record endpoints: key in response containing a Record<resourceId, data>.
|
|
32
|
+
* When set, post-filters the record keys based on team grants.
|
|
33
|
+
* Example: "statuses" for response { statuses: { [systemId]: {...} } }
|
|
34
|
+
*/
|
|
35
|
+
recordKey?: string;
|
|
29
36
|
}
|
|
30
37
|
|
|
31
38
|
/**
|
|
@@ -154,11 +161,13 @@ export function access(
|
|
|
154
161
|
options?: {
|
|
155
162
|
idParam?: string;
|
|
156
163
|
listKey?: string;
|
|
164
|
+
recordKey?: string;
|
|
157
165
|
isDefault?: boolean;
|
|
158
166
|
isPublic?: boolean;
|
|
159
167
|
}
|
|
160
168
|
): AccessRule {
|
|
161
|
-
const hasInstanceAccess =
|
|
169
|
+
const hasInstanceAccess =
|
|
170
|
+
options?.idParam || options?.listKey || options?.recordKey;
|
|
162
171
|
|
|
163
172
|
return {
|
|
164
173
|
id: `${resource}.${level}`,
|
|
@@ -171,6 +180,7 @@ export function access(
|
|
|
171
180
|
? {
|
|
172
181
|
idParam: options?.idParam,
|
|
173
182
|
listKey: options?.listKey,
|
|
183
|
+
recordKey: options?.recordKey,
|
|
174
184
|
}
|
|
175
185
|
: undefined,
|
|
176
186
|
};
|
|
@@ -208,6 +218,7 @@ export function accessPair(
|
|
|
208
218
|
options?: {
|
|
209
219
|
idParam?: string;
|
|
210
220
|
listKey?: string;
|
|
221
|
+
recordKey?: string;
|
|
211
222
|
readIsDefault?: boolean;
|
|
212
223
|
readIsPublic?: boolean;
|
|
213
224
|
}
|
|
@@ -216,6 +227,7 @@ export function accessPair(
|
|
|
216
227
|
read: access(resource, "read", descriptions.read, {
|
|
217
228
|
idParam: options?.idParam,
|
|
218
229
|
listKey: options?.listKey,
|
|
230
|
+
recordKey: options?.recordKey,
|
|
219
231
|
isDefault: options?.readIsDefault,
|
|
220
232
|
isPublic: options?.readIsPublic,
|
|
221
233
|
}),
|
package/src/client-definition.ts
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
import type { ContractRouterClient, AnyContractRouter } from "@orpc/contract";
|
|
2
2
|
import type { PluginMetadata } from "./plugin-metadata";
|
|
3
|
+
import type { ProcedureMetadata } from "./types";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
|
-
* A client definition that bundles an RPC contract
|
|
6
|
-
* Used for type-safe plugin RPC consumption.
|
|
6
|
+
* A client definition that bundles an RPC contract with its plugin metadata.
|
|
7
|
+
* Used for type-safe plugin RPC consumption and TanStack Query integration.
|
|
7
8
|
*
|
|
8
9
|
* @example
|
|
9
10
|
* ```typescript
|
|
10
11
|
* // Define in auth-common
|
|
11
12
|
* export const AuthApi = createClientDefinition(authContract, pluginMetadata);
|
|
12
13
|
*
|
|
13
|
-
* // Use in frontend
|
|
14
|
-
* const authClient =
|
|
14
|
+
* // Use in frontend with TanStack Query hooks
|
|
15
|
+
* const authClient = usePluginClient(AuthApi);
|
|
16
|
+
* const { data } = authClient.getUser.useQuery({ id });
|
|
15
17
|
* ```
|
|
16
18
|
*/
|
|
17
19
|
export interface ClientDefinition<
|
|
18
20
|
TContract extends AnyContractRouter = AnyContractRouter
|
|
19
21
|
> {
|
|
20
22
|
readonly pluginId: string;
|
|
23
|
+
readonly contract: TContract;
|
|
21
24
|
/**
|
|
22
25
|
* Phantom type for contract type inference.
|
|
23
26
|
* This property doesn't exist at runtime - it's only used by TypeScript
|
|
@@ -38,15 +41,37 @@ export interface ClientDefinition<
|
|
|
38
41
|
export type InferClient<T extends ClientDefinition> =
|
|
39
42
|
T extends ClientDefinition<infer C> ? ContractRouterClient<C> : never;
|
|
40
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Type helper to extract operationType from a procedure's metadata.
|
|
46
|
+
*/
|
|
47
|
+
export type ExtractOperationType<TProcedure> = TProcedure extends {
|
|
48
|
+
"~orpc": { meta: { operationType: infer T } };
|
|
49
|
+
}
|
|
50
|
+
? T
|
|
51
|
+
: "query"; // Default to query if not specified
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Extracts the contract's metadata for a specific procedure path.
|
|
55
|
+
*/
|
|
56
|
+
export type InferProcedureMetadata<
|
|
57
|
+
TContract extends AnyContractRouter,
|
|
58
|
+
TPath extends keyof TContract
|
|
59
|
+
> = TContract[TPath] extends { "~orpc": { meta: infer M } }
|
|
60
|
+
? M extends ProcedureMetadata
|
|
61
|
+
? M
|
|
62
|
+
: never
|
|
63
|
+
: never;
|
|
64
|
+
|
|
41
65
|
/**
|
|
42
66
|
* Create a typed client definition for a plugin's RPC contract.
|
|
43
67
|
*
|
|
44
|
-
* This bundles the contract
|
|
45
|
-
*
|
|
68
|
+
* This bundles the contract with the plugin metadata, enabling type-safe
|
|
69
|
+
* TanStack Query hooks that expose only .useQuery() or .useMutation() based
|
|
70
|
+
* on the procedure's operationType.
|
|
46
71
|
*
|
|
47
|
-
* @param
|
|
72
|
+
* @param contract - The RPC contract object
|
|
48
73
|
* @param metadata - The plugin metadata from the plugin's plugin-metadata.ts
|
|
49
|
-
* @returns A ClientDefinition object that can be passed to
|
|
74
|
+
* @returns A ClientDefinition object that can be passed to usePluginClient()
|
|
50
75
|
*
|
|
51
76
|
* @example
|
|
52
77
|
* ```typescript
|
|
@@ -56,16 +81,18 @@ export type InferClient<T extends ClientDefinition> =
|
|
|
56
81
|
*
|
|
57
82
|
* export const AuthApi = createClientDefinition(authContract, pluginMetadata);
|
|
58
83
|
*
|
|
59
|
-
* // In consumer (frontend
|
|
60
|
-
* const authClient =
|
|
61
|
-
*
|
|
84
|
+
* // In consumer (frontend)
|
|
85
|
+
* const authClient = usePluginClient(AuthApi);
|
|
86
|
+
* const { data } = authClient.getUsers.useQuery({}); // Type-safe!
|
|
87
|
+
* const mutation = authClient.deleteUser.useMutation(); // Type-safe!
|
|
62
88
|
* ```
|
|
63
89
|
*/
|
|
64
90
|
export function createClientDefinition<TContract extends AnyContractRouter>(
|
|
65
|
-
|
|
91
|
+
contract: TContract,
|
|
66
92
|
metadata: PluginMetadata
|
|
67
93
|
): ClientDefinition<TContract> {
|
|
68
94
|
return {
|
|
69
95
|
pluginId: metadata.pluginId,
|
|
96
|
+
contract,
|
|
70
97
|
} as ClientDefinition<TContract>;
|
|
71
98
|
}
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { oc } from "@orpc/contract";
|
|
2
|
+
import type { ContractProcedureBuilder } from "@orpc/contract";
|
|
3
|
+
import type { Schema, ErrorMap } from "@orpc/contract";
|
|
4
|
+
import type { ProcedureMetadata } from "./types";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Return type of proc() that preserves the operationType in the type system.
|
|
8
|
+
* This extends ContractProcedureBuilder but with our specific ProcedureMetadata.
|
|
9
|
+
*/
|
|
10
|
+
export type TypedContractProcedureBuilder<TMeta extends ProcedureMetadata> =
|
|
11
|
+
ContractProcedureBuilder<
|
|
12
|
+
Schema<unknown, unknown>,
|
|
13
|
+
Schema<unknown, unknown>,
|
|
14
|
+
ErrorMap,
|
|
15
|
+
TMeta
|
|
16
|
+
>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Creates an oRPC procedure builder with required metadata.
|
|
20
|
+
* All procedures MUST provide userType, operationType, and access.
|
|
21
|
+
*
|
|
22
|
+
* The return type preserves the operationType in the generic parameter,
|
|
23
|
+
* enabling type-safe hook selection (useQuery vs useMutation) in usePluginClient.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* import { proc } from "@checkstack/common";
|
|
28
|
+
*
|
|
29
|
+
* export const myContract = {
|
|
30
|
+
* // Returns TypedContractProcedureBuilder<{ operationType: "query", ... }>
|
|
31
|
+
* getItems: proc({
|
|
32
|
+
* operationType: "query",
|
|
33
|
+
* userType: "public",
|
|
34
|
+
* access: [myAccess.items.read],
|
|
35
|
+
* }).output(z.array(ItemSchema)),
|
|
36
|
+
*
|
|
37
|
+
* // Returns TypedContractProcedureBuilder<{ operationType: "mutation", ... }>
|
|
38
|
+
* createItem: proc({
|
|
39
|
+
* operationType: "mutation",
|
|
40
|
+
* userType: "authenticated",
|
|
41
|
+
* access: [myAccess.items.manage],
|
|
42
|
+
* })
|
|
43
|
+
* .input(CreateItemSchema)
|
|
44
|
+
* .output(ItemSchema),
|
|
45
|
+
* };
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export function proc<TMeta extends ProcedureMetadata>(
|
|
49
|
+
meta: TMeta
|
|
50
|
+
): TypedContractProcedureBuilder<TMeta> {
|
|
51
|
+
return oc.$meta<TMeta>(meta) as TypedContractProcedureBuilder<TMeta>;
|
|
52
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -42,7 +42,16 @@ export interface ProcedureMetadata {
|
|
|
42
42
|
* - "service": Only services (backend-to-backend)
|
|
43
43
|
* - "authenticated": Either users or services, but must be authenticated (default)
|
|
44
44
|
*/
|
|
45
|
-
userType
|
|
45
|
+
userType: "anonymous" | "public" | "user" | "service" | "authenticated";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Operation type for TanStack Query integration.
|
|
49
|
+
* - "query": Read-only operation, uses useQuery hook
|
|
50
|
+
* - "mutation": Write operation, uses useMutation hook
|
|
51
|
+
*
|
|
52
|
+
* This is REQUIRED for all procedures to enable type-safe frontend hooks.
|
|
53
|
+
*/
|
|
54
|
+
operationType: "query" | "mutation";
|
|
46
55
|
|
|
47
56
|
/**
|
|
48
57
|
* Unified access rules combining access rules and resource-level access control.
|
|
@@ -57,5 +66,5 @@ export interface ProcedureMetadata {
|
|
|
57
66
|
* access: [catalogAccess.groups.manage, catalogAccess.systems.manage] // Both required
|
|
58
67
|
* ```
|
|
59
68
|
*/
|
|
60
|
-
access
|
|
69
|
+
access: AccessRule[];
|
|
61
70
|
}
|