@checkstack/healthcheck-common 0.2.0 → 0.4.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 +144 -0
- package/package.json +1 -1
- package/src/access.ts +55 -0
- package/src/index.ts +1 -1
- package/src/rpc-contract.ts +113 -144
- package/src/schemas.ts +2 -2
- package/src/zod-health-result.ts +113 -45
- package/src/permissions.ts +0 -39
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,149 @@
|
|
|
1
1
|
# @checkstack/healthcheck-common
|
|
2
2
|
|
|
3
|
+
## 0.4.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/common@0.3.0
|
|
61
|
+
- @checkstack/signal-common@0.1.1
|
|
62
|
+
|
|
63
|
+
## 0.3.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
|
+
- f533141: Enforce health result factory function usage via branded types
|
|
132
|
+
|
|
133
|
+
- Added `healthResultSchema()` builder that enforces the use of factory functions at compile-time
|
|
134
|
+
- Added `healthResultArray()` factory for array fields (e.g., DNS resolved values)
|
|
135
|
+
- Added branded `HealthResultField<T>` type to mark schemas created by factory functions
|
|
136
|
+
- Consolidated `ChartType` and `HealthResultMeta` into `@checkstack/common` as single source of truth
|
|
137
|
+
- Updated all 12 health check strategies and 11 collectors to use `healthResultSchema()`
|
|
138
|
+
- Using raw `z.number()` etc. inside `healthResultSchema()` now causes a TypeScript error
|
|
139
|
+
|
|
140
|
+
### Patch Changes
|
|
141
|
+
|
|
142
|
+
- Updated dependencies [9faec1f]
|
|
143
|
+
- Updated dependencies [f533141]
|
|
144
|
+
- @checkstack/common@0.2.0
|
|
145
|
+
- @checkstack/signal-common@0.1.0
|
|
146
|
+
|
|
3
147
|
## 0.2.0
|
|
4
148
|
|
|
5
149
|
### Minor Changes
|
package/package.json
CHANGED
package/src/access.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { access, accessPair } from "@checkstack/common";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Access rules for the Health Check plugin.
|
|
5
|
+
*/
|
|
6
|
+
export const healthCheckAccess = {
|
|
7
|
+
/**
|
|
8
|
+
* Status-only access for viewing health check status.
|
|
9
|
+
* Enabled by default for anonymous and authenticated users.
|
|
10
|
+
* Uses system-level instance access for team-based filtering.
|
|
11
|
+
*/
|
|
12
|
+
status: access("healthcheck.status", "read", "View Health Check Status", {
|
|
13
|
+
idParam: "systemId",
|
|
14
|
+
isDefault: true,
|
|
15
|
+
isPublic: true,
|
|
16
|
+
}),
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Bulk status access for viewing health check status for multiple systems.
|
|
20
|
+
* Uses recordKey for filtering the output record by accessible system IDs.
|
|
21
|
+
*/
|
|
22
|
+
bulkStatus: access("healthcheck.status", "read", "View Health Check Status", {
|
|
23
|
+
recordKey: "statuses",
|
|
24
|
+
isDefault: true,
|
|
25
|
+
isPublic: true,
|
|
26
|
+
}),
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Configuration access for viewing and managing health check configurations.
|
|
30
|
+
*/
|
|
31
|
+
configuration: accessPair("healthcheck", {
|
|
32
|
+
read: "Read Health Check Configurations",
|
|
33
|
+
manage: "Full management of Health Check Configurations",
|
|
34
|
+
}),
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Access for viewing detailed health check run data including metadata.
|
|
38
|
+
* Allows access to extended visualizations without full management access.
|
|
39
|
+
*/
|
|
40
|
+
details: access(
|
|
41
|
+
"healthcheck.details",
|
|
42
|
+
"read",
|
|
43
|
+
"View Detailed Health Check Run Data (Warning: This may expose sensitive data, depending on the health check strategy)"
|
|
44
|
+
),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* All access rules for registration with the plugin system.
|
|
49
|
+
*/
|
|
50
|
+
export const healthCheckAccessRules = [
|
|
51
|
+
healthCheckAccess.status,
|
|
52
|
+
healthCheckAccess.configuration.read,
|
|
53
|
+
healthCheckAccess.configuration.manage,
|
|
54
|
+
healthCheckAccess.details,
|
|
55
|
+
];
|
package/src/index.ts
CHANGED
package/src/rpc-contract.ts
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
createClientDefinition,
|
|
4
|
-
createResourceAccessList,
|
|
5
|
-
type ProcedureMetadata,
|
|
6
|
-
} from "@checkstack/common";
|
|
1
|
+
import { createClientDefinition, proc } from "@checkstack/common";
|
|
7
2
|
import { pluginMetadata } from "./plugin-metadata";
|
|
8
3
|
import { z } from "zod";
|
|
9
|
-
import {
|
|
4
|
+
import { healthCheckAccess } from "./access";
|
|
10
5
|
import {
|
|
11
6
|
HealthCheckStrategyDtoSchema,
|
|
12
7
|
CollectorDtoSchema,
|
|
@@ -23,15 +18,6 @@ import {
|
|
|
23
18
|
AggregatedBucketSchema,
|
|
24
19
|
} from "./schemas";
|
|
25
20
|
|
|
26
|
-
// Base builder with full metadata support
|
|
27
|
-
const _base = oc.$meta<ProcedureMetadata>({});
|
|
28
|
-
|
|
29
|
-
// Resource access configurations for team-based access control
|
|
30
|
-
const configListAccess = createResourceAccessList(
|
|
31
|
-
"configuration",
|
|
32
|
-
"configurations"
|
|
33
|
-
);
|
|
34
|
-
|
|
35
21
|
// --- Response Schemas for Evaluated Status ---
|
|
36
22
|
|
|
37
23
|
const SystemCheckStatusSchema = z.object({
|
|
@@ -48,28 +34,27 @@ const SystemHealthStatusResponseSchema = z.object({
|
|
|
48
34
|
checkStatuses: z.array(SystemCheckStatusSchema),
|
|
49
35
|
});
|
|
50
36
|
|
|
37
|
+
export type SystemHealthStatusResponse = z.infer<
|
|
38
|
+
typeof SystemHealthStatusResponseSchema
|
|
39
|
+
>;
|
|
40
|
+
|
|
51
41
|
// Health Check RPC Contract using oRPC's contract-first pattern
|
|
52
42
|
export const healthCheckContract = {
|
|
53
43
|
// ==========================================================================
|
|
54
|
-
// STRATEGY MANAGEMENT (userType: "authenticated" with read
|
|
44
|
+
// STRATEGY MANAGEMENT (userType: "authenticated" with read access)
|
|
55
45
|
// ==========================================================================
|
|
56
46
|
|
|
57
|
-
getStrategies:
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
.output(z.array(HealthCheckStrategyDtoSchema)),
|
|
47
|
+
getStrategies: proc({
|
|
48
|
+
operationType: "query",
|
|
49
|
+
userType: "authenticated",
|
|
50
|
+
access: [healthCheckAccess.configuration.read],
|
|
51
|
+
}).output(z.array(HealthCheckStrategyDtoSchema)),
|
|
63
52
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
.meta({
|
|
70
|
-
userType: "authenticated",
|
|
71
|
-
permissions: [permissions.healthCheckRead.id],
|
|
72
|
-
})
|
|
53
|
+
getCollectors: proc({
|
|
54
|
+
operationType: "query",
|
|
55
|
+
userType: "authenticated",
|
|
56
|
+
access: [healthCheckAccess.configuration.read],
|
|
57
|
+
})
|
|
73
58
|
.input(z.object({ strategyId: z.string() }))
|
|
74
59
|
.output(z.array(CollectorDtoSchema)),
|
|
75
60
|
|
|
@@ -77,29 +62,27 @@ export const healthCheckContract = {
|
|
|
77
62
|
// CONFIGURATION MANAGEMENT (userType: "authenticated")
|
|
78
63
|
// ==========================================================================
|
|
79
64
|
|
|
80
|
-
getConfigurations:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
z.object({ configurations: z.array(HealthCheckConfigurationSchema) })
|
|
88
|
-
),
|
|
65
|
+
getConfigurations: proc({
|
|
66
|
+
operationType: "query",
|
|
67
|
+
userType: "authenticated",
|
|
68
|
+
access: [healthCheckAccess.configuration.read],
|
|
69
|
+
}).output(
|
|
70
|
+
z.object({ configurations: z.array(HealthCheckConfigurationSchema) })
|
|
71
|
+
),
|
|
89
72
|
|
|
90
|
-
createConfiguration:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
73
|
+
createConfiguration: proc({
|
|
74
|
+
operationType: "mutation",
|
|
75
|
+
userType: "authenticated",
|
|
76
|
+
access: [healthCheckAccess.configuration.manage],
|
|
77
|
+
})
|
|
95
78
|
.input(CreateHealthCheckConfigurationSchema)
|
|
96
79
|
.output(HealthCheckConfigurationSchema),
|
|
97
80
|
|
|
98
|
-
updateConfiguration:
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
81
|
+
updateConfiguration: proc({
|
|
82
|
+
operationType: "mutation",
|
|
83
|
+
userType: "authenticated",
|
|
84
|
+
access: [healthCheckAccess.configuration.manage],
|
|
85
|
+
})
|
|
103
86
|
.input(
|
|
104
87
|
z.object({
|
|
105
88
|
id: z.string(),
|
|
@@ -108,11 +91,11 @@ export const healthCheckContract = {
|
|
|
108
91
|
)
|
|
109
92
|
.output(HealthCheckConfigurationSchema),
|
|
110
93
|
|
|
111
|
-
deleteConfiguration:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
94
|
+
deleteConfiguration: proc({
|
|
95
|
+
operationType: "mutation",
|
|
96
|
+
userType: "authenticated",
|
|
97
|
+
access: [healthCheckAccess.configuration.manage],
|
|
98
|
+
})
|
|
116
99
|
.input(z.string())
|
|
117
100
|
.output(z.void()),
|
|
118
101
|
|
|
@@ -120,23 +103,19 @@ export const healthCheckContract = {
|
|
|
120
103
|
// SYSTEM ASSOCIATION (userType: "authenticated")
|
|
121
104
|
// ==========================================================================
|
|
122
105
|
|
|
123
|
-
getSystemConfigurations:
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
106
|
+
getSystemConfigurations: proc({
|
|
107
|
+
operationType: "query",
|
|
108
|
+
userType: "authenticated",
|
|
109
|
+
access: [healthCheckAccess.configuration.read],
|
|
110
|
+
})
|
|
128
111
|
.input(z.string())
|
|
129
112
|
.output(z.array(HealthCheckConfigurationSchema)),
|
|
130
113
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
.meta({
|
|
137
|
-
userType: "authenticated",
|
|
138
|
-
permissions: [permissions.healthCheckRead.id],
|
|
139
|
-
})
|
|
114
|
+
getSystemAssociations: proc({
|
|
115
|
+
operationType: "query",
|
|
116
|
+
userType: "authenticated",
|
|
117
|
+
access: [healthCheckAccess.configuration.read],
|
|
118
|
+
})
|
|
140
119
|
.input(z.object({ systemId: z.string() }))
|
|
141
120
|
.output(
|
|
142
121
|
z.array(
|
|
@@ -149,11 +128,11 @@ export const healthCheckContract = {
|
|
|
149
128
|
)
|
|
150
129
|
),
|
|
151
130
|
|
|
152
|
-
associateSystem:
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
131
|
+
associateSystem: proc({
|
|
132
|
+
operationType: "mutation",
|
|
133
|
+
userType: "authenticated",
|
|
134
|
+
access: [healthCheckAccess.configuration.manage],
|
|
135
|
+
})
|
|
157
136
|
.input(
|
|
158
137
|
z.object({
|
|
159
138
|
systemId: z.string(),
|
|
@@ -162,11 +141,11 @@ export const healthCheckContract = {
|
|
|
162
141
|
)
|
|
163
142
|
.output(z.void()),
|
|
164
143
|
|
|
165
|
-
disassociateSystem:
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
144
|
+
disassociateSystem: proc({
|
|
145
|
+
operationType: "mutation",
|
|
146
|
+
userType: "authenticated",
|
|
147
|
+
access: [healthCheckAccess.configuration.manage],
|
|
148
|
+
})
|
|
170
149
|
.input(
|
|
171
150
|
z.object({
|
|
172
151
|
systemId: z.string(),
|
|
@@ -176,14 +155,14 @@ export const healthCheckContract = {
|
|
|
176
155
|
.output(z.void()),
|
|
177
156
|
|
|
178
157
|
// ==========================================================================
|
|
179
|
-
// RETENTION CONFIGURATION (userType: "authenticated" with manage
|
|
158
|
+
// RETENTION CONFIGURATION (userType: "authenticated" with manage access)
|
|
180
159
|
// ==========================================================================
|
|
181
160
|
|
|
182
|
-
getRetentionConfig:
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
161
|
+
getRetentionConfig: proc({
|
|
162
|
+
operationType: "query",
|
|
163
|
+
userType: "authenticated",
|
|
164
|
+
access: [healthCheckAccess.configuration.read],
|
|
165
|
+
})
|
|
187
166
|
.input(
|
|
188
167
|
z.object({
|
|
189
168
|
systemId: z.string(),
|
|
@@ -196,11 +175,11 @@ export const healthCheckContract = {
|
|
|
196
175
|
})
|
|
197
176
|
),
|
|
198
177
|
|
|
199
|
-
updateRetentionConfig:
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
178
|
+
updateRetentionConfig: proc({
|
|
179
|
+
operationType: "mutation",
|
|
180
|
+
userType: "authenticated",
|
|
181
|
+
access: [healthCheckAccess.configuration.manage],
|
|
182
|
+
})
|
|
204
183
|
.input(
|
|
205
184
|
z.object({
|
|
206
185
|
systemId: z.string(),
|
|
@@ -211,14 +190,14 @@ export const healthCheckContract = {
|
|
|
211
190
|
.output(z.void()),
|
|
212
191
|
|
|
213
192
|
// ==========================================================================
|
|
214
|
-
// HISTORY & STATUS (userType: "
|
|
193
|
+
// HISTORY & STATUS (userType: "public" with read access)
|
|
215
194
|
// ==========================================================================
|
|
216
195
|
|
|
217
|
-
getHistory:
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
196
|
+
getHistory: proc({
|
|
197
|
+
operationType: "query",
|
|
198
|
+
userType: "public",
|
|
199
|
+
access: [healthCheckAccess.status],
|
|
200
|
+
})
|
|
222
201
|
.input(
|
|
223
202
|
z.object({
|
|
224
203
|
systemId: z.string().optional(),
|
|
@@ -236,15 +215,11 @@ export const healthCheckContract = {
|
|
|
236
215
|
})
|
|
237
216
|
),
|
|
238
217
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
.meta({
|
|
245
|
-
userType: "authenticated",
|
|
246
|
-
permissions: [permissions.healthCheckDetailsRead.id],
|
|
247
|
-
})
|
|
218
|
+
getDetailedHistory: proc({
|
|
219
|
+
operationType: "query",
|
|
220
|
+
userType: "authenticated",
|
|
221
|
+
access: [healthCheckAccess.details],
|
|
222
|
+
})
|
|
248
223
|
.input(
|
|
249
224
|
z.object({
|
|
250
225
|
systemId: z.string().optional(),
|
|
@@ -262,16 +237,11 @@ export const healthCheckContract = {
|
|
|
262
237
|
})
|
|
263
238
|
),
|
|
264
239
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
getAggregatedHistory: _base
|
|
271
|
-
.meta({
|
|
272
|
-
userType: "public",
|
|
273
|
-
permissions: [permissions.healthCheckStatusRead.id],
|
|
274
|
-
})
|
|
240
|
+
getAggregatedHistory: proc({
|
|
241
|
+
operationType: "query",
|
|
242
|
+
userType: "public",
|
|
243
|
+
access: [healthCheckAccess.status],
|
|
244
|
+
})
|
|
275
245
|
.input(
|
|
276
246
|
z.object({
|
|
277
247
|
systemId: z.string(),
|
|
@@ -287,16 +257,11 @@ export const healthCheckContract = {
|
|
|
287
257
|
})
|
|
288
258
|
),
|
|
289
259
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
getDetailedAggregatedHistory: _base
|
|
296
|
-
.meta({
|
|
297
|
-
userType: "public",
|
|
298
|
-
permissions: [permissions.healthCheckDetailsRead.id],
|
|
299
|
-
})
|
|
260
|
+
getDetailedAggregatedHistory: proc({
|
|
261
|
+
operationType: "query",
|
|
262
|
+
userType: "public",
|
|
263
|
+
access: [healthCheckAccess.details],
|
|
264
|
+
})
|
|
300
265
|
.input(
|
|
301
266
|
z.object({
|
|
302
267
|
systemId: z.string(),
|
|
@@ -312,27 +277,31 @@ export const healthCheckContract = {
|
|
|
312
277
|
})
|
|
313
278
|
),
|
|
314
279
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
.meta({
|
|
321
|
-
userType: "public",
|
|
322
|
-
permissions: [permissions.healthCheckStatusRead.id],
|
|
323
|
-
})
|
|
280
|
+
getSystemHealthStatus: proc({
|
|
281
|
+
operationType: "query",
|
|
282
|
+
userType: "public",
|
|
283
|
+
access: [healthCheckAccess.status],
|
|
284
|
+
})
|
|
324
285
|
.input(z.object({ systemId: z.string() }))
|
|
325
286
|
.output(SystemHealthStatusResponseSchema),
|
|
326
287
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
.
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
288
|
+
getBulkSystemHealthStatus: proc({
|
|
289
|
+
operationType: "query",
|
|
290
|
+
userType: "public",
|
|
291
|
+
access: [healthCheckAccess.bulkStatus],
|
|
292
|
+
})
|
|
293
|
+
.input(z.object({ systemIds: z.array(z.string()) }))
|
|
294
|
+
.output(
|
|
295
|
+
z.object({
|
|
296
|
+
statuses: z.record(z.string(), SystemHealthStatusResponseSchema),
|
|
297
|
+
})
|
|
298
|
+
),
|
|
299
|
+
|
|
300
|
+
getSystemHealthOverview: proc({
|
|
301
|
+
operationType: "query",
|
|
302
|
+
userType: "public",
|
|
303
|
+
access: [healthCheckAccess.status],
|
|
304
|
+
})
|
|
336
305
|
.input(z.object({ systemId: z.string() }))
|
|
337
306
|
.output(
|
|
338
307
|
z.object({
|
package/src/schemas.ts
CHANGED
|
@@ -272,7 +272,7 @@ export const DEFAULT_RETENTION_CONFIG: RetentionConfig = {
|
|
|
272
272
|
/**
|
|
273
273
|
* Base schema for aggregated health check data buckets.
|
|
274
274
|
* Contains core metrics only (no strategy-specific data).
|
|
275
|
-
* Used by getAggregatedHistory endpoint (healthCheckStatusRead
|
|
275
|
+
* Used by getAggregatedHistory endpoint (healthCheckStatusRead access).
|
|
276
276
|
*/
|
|
277
277
|
export const AggregatedBucketBaseSchema = z.object({
|
|
278
278
|
bucketStart: z.date(),
|
|
@@ -292,7 +292,7 @@ export type AggregatedBucketBase = z.infer<typeof AggregatedBucketBaseSchema>;
|
|
|
292
292
|
|
|
293
293
|
/**
|
|
294
294
|
* Extended schema with strategy-specific aggregated result.
|
|
295
|
-
* Used by getDetailedAggregatedHistory endpoint (healthCheckDetailsRead
|
|
295
|
+
* Used by getDetailedAggregatedHistory endpoint (healthCheckDetailsRead access).
|
|
296
296
|
*/
|
|
297
297
|
export const AggregatedBucketSchema = AggregatedBucketBaseSchema.extend({
|
|
298
298
|
aggregatedResult: z.record(z.string(), z.unknown()).optional(),
|
package/src/zod-health-result.ts
CHANGED
|
@@ -1,54 +1,95 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import type { HealthResultMeta } from "@checkstack/common";
|
|
2
3
|
|
|
3
4
|
// ============================================================================
|
|
4
5
|
// HEALTH RESULT REGISTRY - Typed metadata for chart annotations
|
|
5
6
|
// ============================================================================
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* Numeric types:
|
|
11
|
-
* - line: Time series line chart for numeric metrics over time
|
|
12
|
-
* - bar: Bar chart for distributions (record of string to number)
|
|
13
|
-
* - pie: Pie chart for category distributions (record of string to number)
|
|
14
|
-
* - counter: Simple count display with trend indicator
|
|
15
|
-
* - gauge: Percentage gauge for rates/percentages (0-100)
|
|
16
|
-
*
|
|
17
|
-
* Non-numeric types:
|
|
18
|
-
* - boolean: Boolean indicator (success/failure, connected/disconnected)
|
|
19
|
-
* - text: Text display for string values
|
|
20
|
-
* - status: Status badge for error/warning states
|
|
9
|
+
* Registry for health result schema metadata.
|
|
10
|
+
* Used by auto-chart components for visualization inference.
|
|
21
11
|
*/
|
|
22
|
-
export
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
| "gauge"
|
|
28
|
-
| "boolean"
|
|
29
|
-
| "text"
|
|
30
|
-
| "status";
|
|
12
|
+
export const healthResultRegistry = z.registry<HealthResultMeta>();
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// BRANDED TYPES FOR COMPILE-TIME ENFORCEMENT
|
|
16
|
+
// ============================================================================
|
|
31
17
|
|
|
32
18
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
19
|
+
* Unique symbol for branding health result fields.
|
|
20
|
+
* This enables compile-time enforcement that developers use factory functions.
|
|
35
21
|
*/
|
|
36
|
-
|
|
37
|
-
/** The type of chart to render for this field */
|
|
38
|
-
"x-chart-type"?: ChartType;
|
|
39
|
-
/** Human-readable label for the chart (defaults to field name) */
|
|
40
|
-
"x-chart-label"?: string;
|
|
41
|
-
/** Unit suffix for values (e.g., 'ms', '%', 'req/s') */
|
|
42
|
-
"x-chart-unit"?: string;
|
|
43
|
-
/** Whether this field supports JSONPath assertions */
|
|
44
|
-
"x-jsonpath"?: boolean;
|
|
45
|
-
}
|
|
22
|
+
declare const HealthResultBrand: unique symbol;
|
|
46
23
|
|
|
47
24
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
25
|
+
* A branded Zod schema type that marks it as a health result field.
|
|
26
|
+
* Only schemas created via factory functions (healthResultNumber, healthResultString, etc.)
|
|
27
|
+
* carry this brand.
|
|
50
28
|
*/
|
|
51
|
-
export
|
|
29
|
+
export type HealthResultField<T extends z.ZodTypeAny> = T & {
|
|
30
|
+
[HealthResultBrand]: true;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Allowed field types in a health result schema.
|
|
35
|
+
* Supports direct fields, optional fields, and fields with defaults.
|
|
36
|
+
*/
|
|
37
|
+
export type HealthResultFieldType =
|
|
38
|
+
| HealthResultField<z.ZodTypeAny>
|
|
39
|
+
| z.ZodOptional<HealthResultField<z.ZodTypeAny>>
|
|
40
|
+
| z.ZodDefault<HealthResultField<z.ZodTypeAny>>
|
|
41
|
+
| z.ZodNullable<HealthResultField<z.ZodTypeAny>>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Constraint for health result schema shapes.
|
|
45
|
+
* All fields must use factory functions (healthResultNumber, healthResultString, etc.)
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* // ✅ Valid - uses factory functions
|
|
50
|
+
* const validSchema = z.object({
|
|
51
|
+
* value: healthResultNumber({ "x-chart-type": "line" }),
|
|
52
|
+
* error: healthResultString({ "x-chart-type": "status" }).optional(),
|
|
53
|
+
* });
|
|
54
|
+
*
|
|
55
|
+
* // ❌ Invalid - uses raw z.number()
|
|
56
|
+
* const invalidSchema = z.object({
|
|
57
|
+
* value: z.number(), // Type error!
|
|
58
|
+
* });
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export type HealthResultShape = Record<string, HealthResultFieldType>;
|
|
62
|
+
|
|
63
|
+
// ============================================================================
|
|
64
|
+
// HEALTH RESULT SCHEMA BUILDER
|
|
65
|
+
// ============================================================================
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Create a health result schema that enforces the use of factory functions.
|
|
69
|
+
*
|
|
70
|
+
* This builder ensures all fields use healthResultNumber(), healthResultString(),
|
|
71
|
+
* healthResultBoolean(), or healthResultJSONPath() - raw Zod schemas like z.number()
|
|
72
|
+
* will cause a compile-time error.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```typescript
|
|
76
|
+
* // ✅ Valid - all fields use factory functions
|
|
77
|
+
* const schema = healthResultSchema({
|
|
78
|
+
* responseTime: healthResultNumber({ "x-chart-type": "line", "x-chart-label": "Response Time" }),
|
|
79
|
+
* error: healthResultString({ "x-chart-type": "status" }).optional(),
|
|
80
|
+
* });
|
|
81
|
+
*
|
|
82
|
+
* // ❌ Invalid - raw z.number() causes type error
|
|
83
|
+
* const schema = healthResultSchema({
|
|
84
|
+
* value: z.number(), // Type error: not assignable to HealthResultFieldType
|
|
85
|
+
* });
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export function healthResultSchema<T extends HealthResultShape>(
|
|
89
|
+
shape: T
|
|
90
|
+
): z.ZodObject<T> {
|
|
91
|
+
return z.object(shape);
|
|
92
|
+
}
|
|
52
93
|
|
|
53
94
|
// ============================================================================
|
|
54
95
|
// TYPED HEALTH RESULT FACTORIES
|
|
@@ -69,28 +110,53 @@ type ChartMeta = Omit<HealthResultMeta, "x-jsonpath">;
|
|
|
69
110
|
* });
|
|
70
111
|
* ```
|
|
71
112
|
*/
|
|
72
|
-
export function healthResultString(
|
|
113
|
+
export function healthResultString(
|
|
114
|
+
meta: ChartMeta
|
|
115
|
+
): HealthResultField<z.ZodString> {
|
|
73
116
|
const schema = z.string();
|
|
74
117
|
schema.register(healthResultRegistry, meta);
|
|
75
|
-
return schema
|
|
118
|
+
return schema as HealthResultField<z.ZodString>;
|
|
76
119
|
}
|
|
77
120
|
|
|
78
121
|
/**
|
|
79
122
|
* Create a health result number field with typed chart metadata.
|
|
80
123
|
*/
|
|
81
|
-
export function healthResultNumber(
|
|
124
|
+
export function healthResultNumber(
|
|
125
|
+
meta: ChartMeta
|
|
126
|
+
): HealthResultField<z.ZodNumber> {
|
|
82
127
|
const schema = z.number();
|
|
83
128
|
schema.register(healthResultRegistry, meta);
|
|
84
|
-
return schema
|
|
129
|
+
return schema as HealthResultField<z.ZodNumber>;
|
|
85
130
|
}
|
|
86
131
|
|
|
87
132
|
/**
|
|
88
133
|
* Create a health result boolean field with typed chart metadata.
|
|
89
134
|
*/
|
|
90
|
-
export function healthResultBoolean(
|
|
135
|
+
export function healthResultBoolean(
|
|
136
|
+
meta: ChartMeta
|
|
137
|
+
): HealthResultField<z.ZodBoolean> {
|
|
91
138
|
const schema = z.boolean();
|
|
92
139
|
schema.register(healthResultRegistry, meta);
|
|
93
|
-
return schema
|
|
140
|
+
return schema as HealthResultField<z.ZodBoolean>;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Create a health result array field with typed chart metadata.
|
|
145
|
+
* For arrays of strings (e.g., DNS resolved values, list of hosts).
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```typescript
|
|
149
|
+
* const resultSchema = healthResultSchema({
|
|
150
|
+
* resolvedValues: healthResultArray({ "x-chart-type": "text", "x-chart-label": "Values" }),
|
|
151
|
+
* });
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
export function healthResultArray(
|
|
155
|
+
meta: ChartMeta
|
|
156
|
+
): HealthResultField<z.ZodArray<z.ZodString>> {
|
|
157
|
+
const schema = z.array(z.string());
|
|
158
|
+
schema.register(healthResultRegistry, meta);
|
|
159
|
+
return schema as HealthResultField<z.ZodArray<z.ZodString>>;
|
|
94
160
|
}
|
|
95
161
|
|
|
96
162
|
/**
|
|
@@ -106,10 +172,12 @@ export function healthResultBoolean(meta: ChartMeta) {
|
|
|
106
172
|
* });
|
|
107
173
|
* ```
|
|
108
174
|
*/
|
|
109
|
-
export function healthResultJSONPath(
|
|
175
|
+
export function healthResultJSONPath(
|
|
176
|
+
meta: ChartMeta
|
|
177
|
+
): HealthResultField<z.ZodString> {
|
|
110
178
|
const schema = z.string();
|
|
111
179
|
schema.register(healthResultRegistry, { ...meta, "x-jsonpath": true });
|
|
112
|
-
return schema
|
|
180
|
+
return schema as HealthResultField<z.ZodString>;
|
|
113
181
|
}
|
|
114
182
|
|
|
115
183
|
// ============================================================================
|
package/src/permissions.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { createPermission } from "@checkstack/common";
|
|
2
|
-
|
|
3
|
-
export const permissions = {
|
|
4
|
-
/**
|
|
5
|
-
* Status-only permission for viewing health check status.
|
|
6
|
-
* Enabled by default for anonymous and authenticated users.
|
|
7
|
-
*/
|
|
8
|
-
healthCheckStatusRead: createPermission(
|
|
9
|
-
"healthcheck.status",
|
|
10
|
-
"read",
|
|
11
|
-
"View Health Check Status",
|
|
12
|
-
{ isAuthenticatedDefault: true, isPublicDefault: true }
|
|
13
|
-
),
|
|
14
|
-
/**
|
|
15
|
-
* Configuration read permission for viewing health check configurations.
|
|
16
|
-
* Restricted to users with explicit grant.
|
|
17
|
-
*/
|
|
18
|
-
healthCheckRead: createPermission(
|
|
19
|
-
"healthcheck",
|
|
20
|
-
"read",
|
|
21
|
-
"Read Health Check Configurations"
|
|
22
|
-
),
|
|
23
|
-
/**
|
|
24
|
-
* Permission for viewing detailed health check run data including metadata.
|
|
25
|
-
* Allows access to extended visualizations without full management access.
|
|
26
|
-
*/
|
|
27
|
-
healthCheckDetailsRead: createPermission(
|
|
28
|
-
"healthcheck.details",
|
|
29
|
-
"read",
|
|
30
|
-
"View Detailed Health Check Run Data (Warning: This may expose sensitive data, depending on the health check strategy)"
|
|
31
|
-
),
|
|
32
|
-
healthCheckManage: createPermission(
|
|
33
|
-
"healthcheck",
|
|
34
|
-
"manage",
|
|
35
|
-
"Full management of Health Check Configurations"
|
|
36
|
-
),
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export const permissionList = Object.values(permissions);
|