@checkstack/backend-api 0.0.2 → 0.1.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,44 @@
1
1
  # @checkstack/backend-api
2
2
 
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - f5b1f49: Added collector registry lifecycle cleanup during plugin unloading.
8
+
9
+ - Added `unregisterByOwner(pluginId)` to remove collectors owned by unloading plugins
10
+ - Added `unregisterByMissingStrategies(loadedPluginIds)` for dependency-based pruning
11
+ - Integrated registry cleanup into `PluginManager.deregisterPlugin()`
12
+ - Updated `registerCoreServices` to return global registries for lifecycle management
13
+
14
+ ### Patch Changes
15
+
16
+ - f5b1f49: Added JSONPath assertions for response body validation and fully qualified strategy IDs.
17
+
18
+ **JSONPath Assertions:**
19
+
20
+ - Added `healthResultJSONPath()` factory in healthcheck-common for fields supporting JSONPath queries
21
+ - Extended AssertionBuilder with jsonpath field type showing path input (e.g., `$.data.status`)
22
+ - Added `jsonPath` field to `CollectorAssertionSchema` for persistence
23
+ - HTTP Request collector body field now supports JSONPath assertions
24
+
25
+ **Fully Qualified Strategy IDs:**
26
+
27
+ - HealthCheckRegistry now uses scoped factories like CollectorRegistry
28
+ - Strategies are stored with `pluginId.strategyId` format
29
+ - Added `getStrategiesWithMeta()` method to HealthCheckRegistry interface
30
+ - Router returns qualified IDs so frontend can correctly fetch collectors
31
+
32
+ **UI Improvements:**
33
+
34
+ - Save button disabled when collector configs have invalid required fields
35
+ - Fixed nested button warning in CollectorList accordion
36
+
37
+ - Updated dependencies [f5b1f49]
38
+ - @checkstack/common@0.0.3
39
+ - @checkstack/queue-api@0.0.3
40
+ - @checkstack/signal-common@0.0.3
41
+
3
42
  ## 0.0.2
4
43
 
5
44
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/backend-api",
3
- "version": "0.0.2",
3
+ "version": "0.1.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "scripts": {
@@ -0,0 +1,42 @@
1
+ import type { PluginMetadata } from "@checkstack/common";
2
+ import type { CollectorStrategy } from "./collector-strategy";
3
+ import type { TransportClient } from "./transport-client";
4
+
5
+ /**
6
+ * A registered collector with its owning plugin metadata.
7
+ */
8
+ export interface RegisteredCollector {
9
+ /** The collector strategy */
10
+ collector: CollectorStrategy<TransportClient<unknown, unknown>>;
11
+ /** The plugin that registered this collector */
12
+ ownerPlugin: PluginMetadata;
13
+ }
14
+
15
+ /**
16
+ * Scoped collector registry interface.
17
+ * The owning plugin metadata is automatically injected via factory.
18
+ */
19
+ export interface CollectorRegistry {
20
+ /**
21
+ * Register a collector strategy.
22
+ * The owning plugin metadata is automatically captured from the scoped context.
23
+ */
24
+ register(
25
+ collector: CollectorStrategy<TransportClient<unknown, unknown>>
26
+ ): void;
27
+
28
+ /**
29
+ * Get a collector by ID.
30
+ */
31
+ getCollector(id: string): RegisteredCollector | undefined;
32
+
33
+ /**
34
+ * Get all collectors that support a specific transport plugin.
35
+ */
36
+ getCollectorsForPlugin(pluginMetadata: PluginMetadata): RegisteredCollector[];
37
+
38
+ /**
39
+ * Get all registered collectors.
40
+ */
41
+ getCollectors(): RegisteredCollector[];
42
+ }
@@ -0,0 +1,83 @@
1
+ import type { PluginMetadata } from "@checkstack/common";
2
+ import type { TransportClient } from "./transport-client";
3
+ import type { Versioned } from "./config-versioning";
4
+ import type { HealthCheckRunForAggregation } from "./health-check";
5
+
6
+ /**
7
+ * Result from a collector execution.
8
+ */
9
+ export interface CollectorResult<TResult> {
10
+ /** Collector-specific result data */
11
+ result: TResult;
12
+ /** Optional error message if collection partially failed */
13
+ error?: string;
14
+ }
15
+
16
+ /**
17
+ * Generic collector strategy interface.
18
+ *
19
+ * Collectors extend health check strategies by providing additional metrics
20
+ * collection capabilities. They receive a connected transport client and
21
+ * produce typed results with chart metadata.
22
+ *
23
+ * @template TClient - Transport client type (e.g., SshTransportClient)
24
+ * @template TConfig - Collector configuration schema
25
+ * @template TResult - Per-execution result type
26
+ * @template TAggregated - Aggregated result for buckets
27
+ */
28
+ export interface CollectorStrategy<
29
+ TClient extends TransportClient<unknown, unknown>,
30
+ TConfig = unknown,
31
+ TResult = Record<string, unknown>,
32
+ TAggregated = Record<string, unknown>
33
+ > {
34
+ /** Unique identifier for this collector */
35
+ id: string;
36
+
37
+ /** Human-readable name */
38
+ displayName: string;
39
+
40
+ /** Optional description */
41
+ description?: string;
42
+
43
+ /**
44
+ * PluginMetadata of transport strategies this collector supports.
45
+ * The registry uses this to match collectors to compatible strategies.
46
+ */
47
+ supportedPlugins: PluginMetadata[];
48
+
49
+ /**
50
+ * Whether multiple instances of this collector can be added to one config.
51
+ * Default: false (one instance per collector type)
52
+ */
53
+ allowMultiple?: boolean;
54
+
55
+ /** Collector configuration schema with versioning */
56
+ config: Versioned<TConfig>;
57
+
58
+ /** Per-execution result schema (with x-chart-* metadata) */
59
+ result: Versioned<TResult>;
60
+
61
+ /** Aggregated result schema for bucket storage */
62
+ aggregatedResult: Versioned<TAggregated>;
63
+
64
+ /**
65
+ * Execute the collector using the provided transport client.
66
+ *
67
+ * @param params.config - Validated collector configuration
68
+ * @param params.client - Connected transport client
69
+ * @param params.pluginId - ID of the transport strategy invoking this collector
70
+ * @returns Collector result with typed metadata
71
+ */
72
+ execute(params: {
73
+ config: TConfig;
74
+ client: TClient;
75
+ pluginId: string;
76
+ }): Promise<CollectorResult<TResult>>;
77
+
78
+ /**
79
+ * Aggregate results from multiple runs into a summary.
80
+ * Called during retention processing.
81
+ */
82
+ aggregateResult(runs: HealthCheckRunForAggregation<TResult>[]): TAggregated;
83
+ }
@@ -1,10 +1,8 @@
1
1
  import { createServiceRef } from "./service-ref";
2
2
  import type { RpcService } from "./rpc";
3
3
  import type { HealthCheckRegistry } from "./health-check";
4
- import type {
5
- QueuePluginRegistry,
6
- QueueManager,
7
- } from "@checkstack/queue-api";
4
+ import type { CollectorRegistry } from "./collector-registry";
5
+ import type { QueuePluginRegistry, QueueManager } from "@checkstack/queue-api";
8
6
  import type { ConfigService } from "./config-service";
9
7
  import type { SignalService } from "@checkstack/signal-common";
10
8
  import { NodePgDatabase } from "drizzle-orm/node-postgres";
@@ -32,6 +30,9 @@ export const coreServices = {
32
30
  healthCheckRegistry: createServiceRef<HealthCheckRegistry>(
33
31
  "core.healthCheckRegistry"
34
32
  ),
33
+ collectorRegistry: createServiceRef<CollectorRegistry>(
34
+ "core.collectorRegistry"
35
+ ),
35
36
  pluginInstaller: createServiceRef<PluginInstaller>("core.pluginInstaller"),
36
37
  rpc: createServiceRef<RpcService>("core.rpc"),
37
38
  rpcClient: createServiceRef<RpcClient>("core.rpcClient"),
@@ -1,4 +1,5 @@
1
1
  import { Versioned } from "./config-versioning";
2
+ import type { TransportClient } from "./transport-client";
2
3
 
3
4
  /**
4
5
  * Health check result with typed metadata.
@@ -23,13 +24,35 @@ export interface HealthCheckRunForAggregation<
23
24
  }
24
25
 
25
26
  /**
26
- * Health check strategy definition with typed config and result.
27
+ * Connected transport client with cleanup capability.
28
+ */
29
+ export interface ConnectedClient<
30
+ TClient extends TransportClient<unknown, unknown>
31
+ > {
32
+ /** The connected transport client */
33
+ client: TClient;
34
+ /** Close the connection and release resources */
35
+ close(): void;
36
+ }
37
+
38
+ /**
39
+ * Health check strategy definition with typed config and transport client.
40
+ *
41
+ * Strategies provide a `createClient` function that establishes a connection
42
+ * and returns a transport client. The platform executor handles running
43
+ * collectors and basic health check logic (connectivity test, latency measurement).
44
+ *
27
45
  * @template TConfig - Configuration type for this strategy
28
- * @template TResult - Per-run result type
46
+ * @template TClient - Transport client type (e.g., SshTransportClient)
47
+ * @template TResult - Per-run result type (for aggregation)
29
48
  * @template TAggregatedResult - Aggregated result type for buckets
30
49
  */
31
50
  export interface HealthCheckStrategy<
32
51
  TConfig = unknown,
52
+ TClient extends TransportClient<unknown, unknown> = TransportClient<
53
+ unknown,
54
+ unknown
55
+ >,
33
56
  TResult = Record<string, unknown>,
34
57
  TAggregatedResult = Record<string, unknown>
35
58
  > {
@@ -46,7 +69,15 @@ export interface HealthCheckStrategy<
46
69
  /** Aggregated result schema for long-term bucket storage */
47
70
  aggregatedResult: Versioned<TAggregatedResult>;
48
71
 
49
- execute(config: TConfig): Promise<HealthCheckResult<TResult>>;
72
+ /**
73
+ * Create a connected transport client from the configuration.
74
+ * The platform will use this client to execute collectors.
75
+ *
76
+ * @param config - Validated strategy configuration
77
+ * @returns Connected client wrapper with close() method
78
+ * @throws Error if connection fails (will be caught by executor)
79
+ */
80
+ createClient(config: TConfig): Promise<ConnectedClient<TClient>>;
50
81
 
51
82
  /**
52
83
  * Aggregate results from multiple runs into a summary for bucket storage.
@@ -59,10 +90,47 @@ export interface HealthCheckStrategy<
59
90
  ): TAggregatedResult;
60
91
  }
61
92
 
93
+ /**
94
+ * A registered strategy with its owning plugin metadata and qualified ID.
95
+ */
96
+ export interface RegisteredStrategy {
97
+ strategy: HealthCheckStrategy<
98
+ unknown,
99
+ TransportClient<unknown, unknown>,
100
+ unknown,
101
+ unknown
102
+ >;
103
+ ownerPluginId: string;
104
+ qualifiedId: string;
105
+ }
106
+
62
107
  export interface HealthCheckRegistry {
63
- register(strategy: HealthCheckStrategy<unknown, unknown, unknown>): void;
108
+ register(
109
+ strategy: HealthCheckStrategy<
110
+ unknown,
111
+ TransportClient<unknown, unknown>,
112
+ unknown,
113
+ unknown
114
+ >
115
+ ): void;
64
116
  getStrategy(
65
117
  id: string
66
- ): HealthCheckStrategy<unknown, unknown, unknown> | undefined;
67
- getStrategies(): HealthCheckStrategy<unknown, unknown, unknown>[];
118
+ ):
119
+ | HealthCheckStrategy<
120
+ unknown,
121
+ TransportClient<unknown, unknown>,
122
+ unknown,
123
+ unknown
124
+ >
125
+ | undefined;
126
+ getStrategies(): HealthCheckStrategy<
127
+ unknown,
128
+ TransportClient<unknown, unknown>,
129
+ unknown,
130
+ unknown
131
+ >[];
132
+ /**
133
+ * Get all registered strategies with their metadata (qualified ID, owner plugin).
134
+ */
135
+ getStrategiesWithMeta(): RegisteredStrategy[];
68
136
  }
package/src/index.ts CHANGED
@@ -21,3 +21,6 @@ export * from "./markdown";
21
21
  export * from "./email-layout";
22
22
  export * from "./assertions";
23
23
  export * from "./chart-metadata";
24
+ export * from "./transport-client";
25
+ export * from "./collector-strategy";
26
+ export * from "./collector-registry";
package/src/rpc.ts CHANGED
@@ -1,14 +1,9 @@
1
1
  import { os as baseOs, ORPCError, Router } from "@orpc/server";
2
2
  import { AnyContractRouter } from "@orpc/contract";
3
3
  import { HealthCheckRegistry } from "./health-check";
4
- import {
5
- QueuePluginRegistry,
6
- QueueManager,
7
- } from "@checkstack/queue-api";
8
- import {
9
- ProcedureMetadata,
10
- qualifyPermissionId,
11
- } from "@checkstack/common";
4
+ import { CollectorRegistry } from "./collector-registry";
5
+ import { QueuePluginRegistry, QueueManager } from "@checkstack/queue-api";
6
+ import { ProcedureMetadata, qualifyPermissionId } from "@checkstack/common";
12
7
  import { NodePgDatabase } from "drizzle-orm/node-postgres";
13
8
  import {
14
9
  Logger,
@@ -43,6 +38,7 @@ export interface RpcContext {
43
38
  auth: AuthService;
44
39
  user?: AuthUser;
45
40
  healthCheckRegistry: HealthCheckRegistry;
41
+ collectorRegistry: CollectorRegistry;
46
42
  queuePluginRegistry: QueuePluginRegistry;
47
43
  queueManager: QueueManager;
48
44
  /** Emit a hook event for cross-plugin communication */
package/src/test-utils.ts CHANGED
@@ -2,10 +2,8 @@ import { mock } from "bun:test";
2
2
  import { RpcContext, EmitHookFn } from "./rpc";
3
3
  import { NodePgDatabase } from "drizzle-orm/node-postgres";
4
4
  import { HealthCheckRegistry } from "./health-check";
5
- import {
6
- QueuePluginRegistry,
7
- QueueManager,
8
- } from "@checkstack/queue-api";
5
+ import { CollectorRegistry } from "./collector-registry";
6
+ import { QueuePluginRegistry, QueueManager } from "@checkstack/queue-api";
9
7
 
10
8
  /**
11
9
  * Creates a mocked oRPC context for testing.
@@ -39,10 +37,17 @@ export function createMockRpcContext(
39
37
  getAnonymousPermissions: mock().mockResolvedValue([]),
40
38
  },
41
39
  healthCheckRegistry: {
42
- registerStrategy: mock(),
40
+ register: mock(),
43
41
  getStrategies: mock().mockReturnValue([]),
44
42
  getStrategy: mock(),
43
+ getStrategiesWithMeta: mock().mockReturnValue([]),
45
44
  } as unknown as HealthCheckRegistry,
45
+ collectorRegistry: {
46
+ register: mock(),
47
+ getCollector: mock(),
48
+ getCollectors: mock().mockReturnValue([]),
49
+ getCollectorsForPlugin: mock().mockReturnValue([]),
50
+ } as unknown as CollectorRegistry,
46
51
  queuePluginRegistry: {
47
52
  register: mock(),
48
53
  getPlugin: mock(),
@@ -0,0 +1,2 @@
1
+ // Re-export TransportClient from common for convenience
2
+ export type { TransportClient } from "@checkstack/common";