@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/healthcheck-common",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
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
@@ -1,4 +1,4 @@
1
- export * from "./permissions";
1
+ export * from "./access";
2
2
  export * from "./schemas";
3
3
  export * from "./zod-health-result";
4
4
 
@@ -1,12 +1,7 @@
1
- import { oc } from "@orpc/contract";
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 { permissions } from "./permissions";
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 permission)
44
+ // STRATEGY MANAGEMENT (userType: "authenticated" with read access)
55
45
  // ==========================================================================
56
46
 
57
- getStrategies: _base
58
- .meta({
59
- userType: "authenticated",
60
- permissions: [permissions.healthCheckRead.id],
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
- * Get available collectors for a specific strategy.
66
- * Returns collectors that support the given strategy's transport.
67
- */
68
- getCollectors: _base
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: _base
81
- .meta({
82
- userType: "authenticated",
83
- permissions: [permissions.healthCheckRead.id],
84
- resourceAccess: [configListAccess],
85
- })
86
- .output(
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: _base
91
- .meta({
92
- userType: "authenticated",
93
- permissions: [permissions.healthCheckManage.id],
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: _base
99
- .meta({
100
- userType: "authenticated",
101
- permissions: [permissions.healthCheckManage.id],
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: _base
112
- .meta({
113
- userType: "authenticated",
114
- permissions: [permissions.healthCheckManage.id],
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: _base
124
- .meta({
125
- userType: "authenticated",
126
- permissions: [permissions.healthCheckRead.id],
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
- * Get system associations with their threshold configurations.
133
- * Returns full association data including enabled state and thresholds.
134
- */
135
- getSystemAssociations: _base
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: _base
153
- .meta({
154
- userType: "authenticated",
155
- permissions: [permissions.healthCheckManage.id],
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: _base
166
- .meta({
167
- userType: "authenticated",
168
- permissions: [permissions.healthCheckManage.id],
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 permission)
158
+ // RETENTION CONFIGURATION (userType: "authenticated" with manage access)
180
159
  // ==========================================================================
181
160
 
182
- getRetentionConfig: _base
183
- .meta({
184
- userType: "authenticated",
185
- permissions: [permissions.healthCheckRead.id],
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: _base
200
- .meta({
201
- userType: "authenticated",
202
- permissions: [permissions.healthCheckManage.id],
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: "user" with read permission)
193
+ // HISTORY & STATUS (userType: "public" with read access)
215
194
  // ==========================================================================
216
195
 
217
- getHistory: _base
218
- .meta({
219
- userType: "public",
220
- permissions: [permissions.healthCheckStatusRead.id],
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
- * Get detailed health check run history with full result data.
241
- * Requires permission to view detailed run data including metadata.
242
- */
243
- getDetailedHistory: _base
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
- * Get aggregated health check history for long-term analysis.
267
- * Returns pre-computed buckets with core metrics only (no strategy-specific data).
268
- * For strategy-specific aggregated results, use getDetailedAggregatedHistory.
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
- * Get detailed aggregated health check history including strategy-specific data.
292
- * Returns buckets with core metrics AND aggregatedResult from strategy.
293
- * Requires healthCheckDetailsRead permission.
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
- * Get evaluateted health status for a system based on configured thresholds.
317
- * Aggregates all health check statuses for the system.
318
- */
319
- getSystemHealthStatus: _base
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
- * Get comprehensive health overview for a system.
329
- * Returns all health checks with their last 25 runs for sparkline visualization.
330
- */
331
- getSystemHealthOverview: _base
332
- .meta({
333
- userType: "public",
334
- permissions: [permissions.healthCheckStatusRead.id],
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 permission).
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 permission).
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(),
@@ -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
- * Chart types for auto-generated health check visualizations.
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 type ChartType =
23
- | "line"
24
- | "bar"
25
- | "pie"
26
- | "counter"
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
- * Metadata type for health check result schemas.
34
- * Provides autocompletion for `.meta()` calls on result fields.
19
+ * Unique symbol for branding health result fields.
20
+ * This enables compile-time enforcement that developers use factory functions.
35
21
  */
36
- export interface HealthResultMeta {
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
- * Registry for health result schema metadata.
49
- * Used by auto-chart components for visualization inference.
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 const healthResultRegistry = z.registry<HealthResultMeta>();
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(meta: ChartMeta) {
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(meta: ChartMeta) {
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(meta: ChartMeta) {
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(meta: ChartMeta) {
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
  // ============================================================================
@@ -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);