@checkstack/catalog-common 1.0.0 → 1.2.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 CHANGED
@@ -1,5 +1,140 @@
1
1
  # @checkstack/catalog-common
2
2
 
3
+ ## 1.2.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
+
57
+ ### Patch Changes
58
+
59
+ - Updated dependencies [7a23261]
60
+ - @checkstack/frontend-api@0.2.0
61
+ - @checkstack/common@0.3.0
62
+
63
+ ## 1.1.0
64
+
65
+ ### Minor Changes
66
+
67
+ - 9faec1f: # Unified AccessRule Terminology Refactoring
68
+
69
+ This release completes a comprehensive terminology refactoring from "permission" to "accessRule" across the entire codebase, establishing a consistent and modern access control vocabulary.
70
+
71
+ ## Changes
72
+
73
+ ### Core Infrastructure (`@checkstack/common`)
74
+
75
+ - Introduced `AccessRule` interface as the primary access control type
76
+ - Added `accessPair()` helper for creating read/manage access rule pairs
77
+ - Added `access()` builder for individual access rules
78
+ - Replaced `Permission` type with `AccessRule` throughout
79
+
80
+ ### API Changes
81
+
82
+ - `env.registerPermissions()` → `env.registerAccessRules()`
83
+ - `meta.permissions` → `meta.access` in RPC contracts
84
+ - `usePermission()` → `useAccess()` in frontend hooks
85
+ - Route `permission:` field → `accessRule:` field
86
+
87
+ ### UI Changes
88
+
89
+ - "Roles & Permissions" tab → "Roles & Access Rules"
90
+ - "You don't have permission..." → "You don't have access..."
91
+ - All permission-related UI text updated
92
+
93
+ ### Documentation & Templates
94
+
95
+ - Updated 18 documentation files with AccessRule terminology
96
+ - Updated 7 scaffolding templates with `accessPair()` pattern
97
+ - All code examples use new AccessRule API
98
+
99
+ ## Migration Guide
100
+
101
+ ### Backend Plugins
102
+
103
+ ```diff
104
+ - import { permissionList } from "./permissions";
105
+ - env.registerPermissions(permissionList);
106
+ + import { accessRules } from "./access";
107
+ + env.registerAccessRules(accessRules);
108
+ ```
109
+
110
+ ### RPC Contracts
111
+
112
+ ```diff
113
+ - .meta({ userType: "user", permissions: [permissions.read.id] })
114
+ + .meta({ userType: "user", access: [access.read] })
115
+ ```
116
+
117
+ ### Frontend Hooks
118
+
119
+ ```diff
120
+ - const canRead = accessApi.usePermission(permissions.read.id);
121
+ + const canRead = accessApi.useAccess(access.read);
122
+ ```
123
+
124
+ ### Routes
125
+
126
+ ```diff
127
+ - permission: permissions.entityRead.id,
128
+ + accessRule: access.read,
129
+ ```
130
+
131
+ ### Patch Changes
132
+
133
+ - Updated dependencies [9faec1f]
134
+ - Updated dependencies [f533141]
135
+ - @checkstack/common@0.2.0
136
+ - @checkstack/frontend-api@0.1.0
137
+
3
138
  ## 1.0.0
4
139
 
5
140
  ### Major Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/catalog-common",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
package/src/access.ts ADDED
@@ -0,0 +1,69 @@
1
+ import { accessPair } from "@checkstack/common";
2
+
3
+ /**
4
+ * Access rules for the Catalog plugin.
5
+ *
6
+ * Systems have instance-level access control (team-based filtering).
7
+ * Groups and views are global (no team-based filtering).
8
+ */
9
+ export const catalogAccess = {
10
+ /**
11
+ * System access with team-based filtering.
12
+ * - Read: View systems (filtered by team grants if no global access)
13
+ * - Manage: Create, update, delete systems
14
+ */
15
+ system: accessPair(
16
+ "system",
17
+ {
18
+ read: "View systems in catalog",
19
+ manage: "Create, update, and delete systems",
20
+ },
21
+ {
22
+ idParam: "systemId",
23
+ listKey: "systems",
24
+ readIsDefault: true,
25
+ readIsPublic: true,
26
+ }
27
+ ),
28
+
29
+ /**
30
+ * Group access (global, no team-based filtering).
31
+ */
32
+ group: accessPair(
33
+ "group",
34
+ {
35
+ read: "View groups",
36
+ manage: "Create, update, and delete groups",
37
+ },
38
+ {
39
+ readIsDefault: true,
40
+ readIsPublic: true,
41
+ }
42
+ ),
43
+
44
+ /**
45
+ * View access (global, user-only).
46
+ */
47
+ view: accessPair(
48
+ "view",
49
+ {
50
+ read: "View saved views",
51
+ manage: "Manage saved views",
52
+ },
53
+ {
54
+ readIsDefault: true,
55
+ }
56
+ ),
57
+ };
58
+
59
+ /**
60
+ * All access rules for registration with the plugin system.
61
+ */
62
+ export const catalogAccessRules = [
63
+ catalogAccess.system.read,
64
+ catalogAccess.system.manage,
65
+ catalogAccess.group.read,
66
+ catalogAccess.group.manage,
67
+ catalogAccess.view.read,
68
+ catalogAccess.view.manage,
69
+ ];
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { permissions, permissionList } from "./permissions";
1
+ export { catalogAccess, catalogAccessRules } from "./access";
2
2
  export * from "./rpc-contract";
3
3
  export * from "./types";
4
4
  export * from "./slots";
@@ -1,21 +1,8 @@
1
- import { oc } from "@orpc/contract";
2
- import {
3
- createClientDefinition,
4
- createResourceAccess,
5
- createResourceAccessList,
6
- type ProcedureMetadata,
7
- } from "@checkstack/common";
1
+ import { createClientDefinition, proc } from "@checkstack/common";
8
2
  import { pluginMetadata } from "./plugin-metadata";
9
3
  import { z } from "zod";
10
4
  import { SystemSchema, GroupSchema, ViewSchema } from "./types";
11
- import { permissions } from "./permissions";
12
-
13
- // Base builder with full metadata support
14
- const _base = oc.$meta<ProcedureMetadata>({});
15
-
16
- // Resource access configurations for team-based access control
17
- const systemAccess = createResourceAccess("system", "systemId");
18
- const systemListAccess = createResourceAccessList("system", "systems");
5
+ import { catalogAccess } from "./access";
19
6
 
20
7
  // Input schemas that match the service layer expectations
21
8
  const CreateSystemInputSchema = z.object({
@@ -29,9 +16,9 @@ const UpdateSystemInputSchema = z.object({
29
16
  id: z.string(),
30
17
  data: z.object({
31
18
  name: z.string().optional(),
32
- description: z.string().nullable().optional(), // Allow nullable for updates
19
+ description: z.string().nullable().optional(),
33
20
  owner: z.string().nullable().optional(),
34
- metadata: z.record(z.string(), z.unknown()).nullable().optional(), // Allow nullable
21
+ metadata: z.record(z.string(), z.unknown()).nullable().optional(),
35
22
  }),
36
23
  });
37
24
 
@@ -44,7 +31,7 @@ const UpdateGroupInputSchema = z.object({
44
31
  id: z.string(),
45
32
  data: z.object({
46
33
  name: z.string().optional(),
47
- metadata: z.record(z.string(), z.unknown()).nullable().optional(), // Allow nullable
34
+ metadata: z.record(z.string(), z.unknown()).nullable().optional(),
48
35
  }),
49
36
  });
50
37
 
@@ -57,108 +44,105 @@ const CreateViewInputSchema = z.object({
57
44
  // Catalog RPC Contract using oRPC's contract-first pattern
58
45
  export const catalogContract = {
59
46
  // ==========================================================================
60
- // ENTITY READ ENDPOINTS (userType: "public" - accessible by anyone with permission)
47
+ // ENTITY READ ENDPOINTS (userType: "public" - accessible by anyone with access)
61
48
  // ==========================================================================
62
49
 
63
- getEntities: _base
64
- .meta({
65
- userType: "public",
66
- permissions: [permissions.catalogRead.id],
67
- resourceAccess: [systemListAccess],
68
- })
69
- .output(
70
- z.object({
71
- systems: z.array(SystemSchema),
72
- groups: z.array(GroupSchema),
73
- })
74
- ),
75
-
76
- getSystems: _base
77
- .meta({
78
- userType: "public",
79
- permissions: [permissions.catalogRead.id],
80
- resourceAccess: [systemListAccess],
81
- })
82
- .output(z.object({ systems: z.array(SystemSchema) })),
83
-
84
- getSystem: _base
85
- .meta({
86
- userType: "public",
87
- permissions: [permissions.catalogRead.id],
88
- resourceAccess: [systemAccess],
50
+ getEntities: proc({
51
+ operationType: "query",
52
+ userType: "public",
53
+ access: [catalogAccess.system.read],
54
+ }).output(
55
+ z.object({
56
+ systems: z.array(SystemSchema),
57
+ groups: z.array(GroupSchema),
89
58
  })
59
+ ),
60
+
61
+ getSystems: proc({
62
+ operationType: "query",
63
+ userType: "public",
64
+ access: [catalogAccess.system.read],
65
+ }).output(z.object({ systems: z.array(SystemSchema) })),
66
+
67
+ getSystem: proc({
68
+ operationType: "query",
69
+ userType: "public",
70
+ access: [catalogAccess.system.read],
71
+ })
90
72
  .input(z.object({ systemId: z.string() }))
91
73
  .output(SystemSchema.nullable()),
92
74
 
93
- getGroups: _base
94
- .meta({ userType: "public", permissions: [permissions.catalogRead.id] })
95
- .output(z.array(GroupSchema)),
75
+ getGroups: proc({
76
+ operationType: "query",
77
+ userType: "public",
78
+ access: [catalogAccess.group.read],
79
+ }).output(z.array(GroupSchema)),
96
80
 
97
81
  // ==========================================================================
98
- // SYSTEM MANAGEMENT (userType: "authenticated" with manage permission)
82
+ // SYSTEM MANAGEMENT (userType: "authenticated" with manage access)
99
83
  // ==========================================================================
100
84
 
101
- createSystem: _base
102
- .meta({
103
- userType: "authenticated",
104
- permissions: [permissions.catalogManage.id],
105
- })
85
+ createSystem: proc({
86
+ operationType: "mutation",
87
+ userType: "authenticated",
88
+ access: [catalogAccess.system.manage],
89
+ })
106
90
  .input(CreateSystemInputSchema)
107
91
  .output(SystemSchema),
108
92
 
109
- updateSystem: _base
110
- .meta({
111
- userType: "authenticated",
112
- permissions: [permissions.catalogManage.id],
113
- })
93
+ updateSystem: proc({
94
+ operationType: "mutation",
95
+ userType: "authenticated",
96
+ access: [catalogAccess.system.manage],
97
+ })
114
98
  .input(UpdateSystemInputSchema)
115
99
  .output(SystemSchema),
116
100
 
117
- deleteSystem: _base
118
- .meta({
119
- userType: "authenticated",
120
- permissions: [permissions.catalogManage.id],
121
- })
101
+ deleteSystem: proc({
102
+ operationType: "mutation",
103
+ userType: "authenticated",
104
+ access: [catalogAccess.system.manage],
105
+ })
122
106
  .input(z.string())
123
107
  .output(z.object({ success: z.boolean() })),
124
108
 
125
109
  // ==========================================================================
126
- // GROUP MANAGEMENT (userType: "authenticated" with manage permission)
110
+ // GROUP MANAGEMENT (userType: "authenticated" with manage access)
127
111
  // ==========================================================================
128
112
 
129
- createGroup: _base
130
- .meta({
131
- userType: "authenticated",
132
- permissions: [permissions.catalogManage.id],
133
- })
113
+ createGroup: proc({
114
+ operationType: "mutation",
115
+ userType: "authenticated",
116
+ access: [catalogAccess.group.manage],
117
+ })
134
118
  .input(CreateGroupInputSchema)
135
119
  .output(GroupSchema),
136
120
 
137
- updateGroup: _base
138
- .meta({
139
- userType: "authenticated",
140
- permissions: [permissions.catalogManage.id],
141
- })
121
+ updateGroup: proc({
122
+ operationType: "mutation",
123
+ userType: "authenticated",
124
+ access: [catalogAccess.group.manage],
125
+ })
142
126
  .input(UpdateGroupInputSchema)
143
127
  .output(GroupSchema),
144
128
 
145
- deleteGroup: _base
146
- .meta({
147
- userType: "authenticated",
148
- permissions: [permissions.catalogManage.id],
149
- })
129
+ deleteGroup: proc({
130
+ operationType: "mutation",
131
+ userType: "authenticated",
132
+ access: [catalogAccess.group.manage],
133
+ })
150
134
  .input(z.string())
151
135
  .output(z.object({ success: z.boolean() })),
152
136
 
153
137
  // ==========================================================================
154
- // SYSTEM-GROUP RELATIONSHIPS (userType: "authenticated" with manage permission)
138
+ // SYSTEM-GROUP RELATIONSHIPS (userType: "authenticated" with manage access)
155
139
  // ==========================================================================
156
140
 
157
- addSystemToGroup: _base
158
- .meta({
159
- userType: "authenticated",
160
- permissions: [permissions.catalogManage.id],
161
- })
141
+ addSystemToGroup: proc({
142
+ operationType: "mutation",
143
+ userType: "authenticated",
144
+ access: [catalogAccess.system.manage],
145
+ })
162
146
  .input(
163
147
  z.object({
164
148
  groupId: z.string(),
@@ -167,11 +151,11 @@ export const catalogContract = {
167
151
  )
168
152
  .output(z.object({ success: z.boolean() })),
169
153
 
170
- removeSystemFromGroup: _base
171
- .meta({
172
- userType: "authenticated",
173
- permissions: [permissions.catalogManage.id],
174
- })
154
+ removeSystemFromGroup: proc({
155
+ operationType: "mutation",
156
+ userType: "authenticated",
157
+ access: [catalogAccess.system.manage],
158
+ })
175
159
  .input(
176
160
  z.object({
177
161
  groupId: z.string(),
@@ -184,12 +168,17 @@ export const catalogContract = {
184
168
  // VIEW MANAGEMENT (userType: "user")
185
169
  // ==========================================================================
186
170
 
187
- getViews: _base
188
- .meta({ userType: "user", permissions: [permissions.catalogRead.id] })
189
- .output(z.array(ViewSchema)),
190
-
191
- createView: _base
192
- .meta({ userType: "user", permissions: [permissions.catalogManage.id] })
171
+ getViews: proc({
172
+ operationType: "query",
173
+ userType: "user",
174
+ access: [catalogAccess.view.read],
175
+ }).output(z.array(ViewSchema)),
176
+
177
+ createView: proc({
178
+ operationType: "mutation",
179
+ userType: "user",
180
+ access: [catalogAccess.view.manage],
181
+ })
193
182
  .input(CreateViewInputSchema)
194
183
  .output(ViewSchema),
195
184
 
@@ -206,18 +195,19 @@ export const catalogContract = {
206
195
  * deduplicated so users subscribed to both the system AND its groups
207
196
  * receive only one notification.
208
197
  */
209
- notifySystemSubscribers: _base
210
- .meta({ userType: "service" })
198
+ notifySystemSubscribers: proc({
199
+ operationType: "mutation",
200
+ userType: "service",
201
+ access: [], // Service-to-service, no access rules needed
202
+ })
211
203
  .input(
212
204
  z.object({
213
205
  systemId: z
214
206
  .string()
215
207
  .describe("The system ID to notify subscribers for"),
216
208
  title: z.string().describe("Notification title"),
217
- /** Notification body in markdown format */
218
209
  body: z.string().describe("Notification body (supports markdown)"),
219
210
  importance: z.enum(["info", "warning", "critical"]).optional(),
220
- /** Primary action button */
221
211
  action: z
222
212
  .object({
223
213
  label: z.string(),
@@ -1,17 +0,0 @@
1
- import { createPermission } from "@checkstack/common";
2
-
3
- export const permissions = {
4
- catalogRead: createPermission(
5
- "catalog",
6
- "read",
7
- "Read Catalog (Systems and Groups)",
8
- { isAuthenticatedDefault: true, isPublicDefault: true }
9
- ),
10
- catalogManage: createPermission(
11
- "catalog",
12
- "manage",
13
- "Full management of Catalog (Systems and Groups)"
14
- ),
15
- };
16
-
17
- export const permissionList = Object.values(permissions);