@checkstack/healthcheck-postgres-backend 0.4.3 → 0.5.1

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,85 @@
1
1
  # @checkstack/healthcheck-postgres-backend
2
2
 
3
+ ## 0.5.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [bd41130]
8
+ - @checkstack/backend-api@0.32.0
9
+ - @checkstack/healthcheck-common@1.16.1
10
+
11
+ ## 0.5.0
12
+
13
+ ### Minor Changes
14
+
15
+ - 43e4484: Extend `{{ … }}` environment templating across every built-in health-check type
16
+ and add editor UX for it, so one check config can cover N environments (mirrors
17
+ the existing HTTP `url` pattern).
18
+
19
+ Templatable connection/target fields now marked `x-templatable`:
20
+
21
+ - TLS: `host`, `servername`; TCP: `host`; Ping: `host`; gRPC: `host`, `service`.
22
+ - MySQL / Postgres: `host`, `database`, `user`, `query`.
23
+ - SSH: `host`, `username`, `command`; Redis: `host`, `args`; RCON: `host`,
24
+ `command`.
25
+ - DNS: `hostname`, `nameserver`; Jenkins: `url` (`baseUrl`), `jobName`;
26
+ Container: `endpoint`, `container`.
27
+ - SNMP: `host` (strategy), `oid` (collector).
28
+ - Script (shell): `cwd` (working directory).
29
+
30
+ This closes the last gaps so the coverage is now truly every built-in
31
+ health-check type. The Script collectors' `script` bodies are deliberately NOT
32
+ templatable: rendering `{{ … }}` into shell/TypeScript source would splice env
33
+ values into executed code. Per-environment data reaches those scripts safely via
34
+ the reserved `CHECKSTACK_ENV_*` shell vars (shell collector) and
35
+ `globalThis.context.environment` (inline collector) instead.
36
+
37
+ Because templating strips `{{ }}` and renders an undefined variable to an empty
38
+ string, every REQUIRED templatable field now has a post-render config-error
39
+ guard so an empty/invalid render is treated as a transport failure instead of a
40
+ silent "healthy" empty probe. Strategy connection fields (host, database, user,
41
+ endpoint, container, Jenkins base URL, SNMP host) throw from `createClient`;
42
+ collector target fields (query, command, hostname, jobName, SNMP oid) return a
43
+ `CollectorResult` with an `error`. Jenkins `baseUrl` moves its `.url()` validation to post-render.
44
+ Secret fields (passwords/tokens/keys) are never templatable; optional fields
45
+ (SNI `servername`, gRPC `service`, DNS `nameserver`, Redis `args`, Script `cwd`)
46
+ are templatable but not non-empty-guarded, since an empty render is a legitimate
47
+ "unset". SSRF/egress guards continue to run on the rendered host (rendering
48
+ happens before `createClient`).
49
+
50
+ Editor UX (`@checkstack/ui` + `@checkstack/healthcheck-frontend`):
51
+
52
+ - The environment "Preview as" picker + live preview line now also apply to the
53
+ strategy (connection) form, not just collector forms, so host/port templates
54
+ preview too.
55
+ - A single-line templatable field shows a small "Templating" badge next to its
56
+ label and, when a completion provider is supplied, renders a
57
+ `TemplateValueInput` with `{{ … }}` autocomplete. The health-check editor
58
+ seeds the provider with the fixed `environment.* / check.* / system.*`
59
+ namespace (`createReferenceCompletionProvider`, new `@checkstack/ui` export),
60
+ and `DynamicForm` gains a `templatableFieldsOnly` prop so only `x-templatable`
61
+ fields become template inputs (automation keeps templating every string field).
62
+
63
+ BREAKING CHANGE: none. Existing non-templatable configs and stored values are
64
+ unaffected; only fields explicitly marked `x-templatable` change behavior.
65
+
66
+ The `@checkstack/ai-backend` bump reflects the regenerated docs index for the
67
+ updated health-check collector and config-schema templating documentation.
68
+
69
+ Thanks to [@stuajnht](https://github.com/stuajnht) for the valuable feedback.
70
+
71
+ ### Patch Changes
72
+
73
+ - Updated dependencies [43e4484]
74
+ - Updated dependencies [43e4484]
75
+ - Updated dependencies [43e4484]
76
+ - Updated dependencies [43e4484]
77
+ - Updated dependencies [43e4484]
78
+ - Updated dependencies [43e4484]
79
+ - Updated dependencies [43e4484]
80
+ - @checkstack/healthcheck-common@1.16.0
81
+ - @checkstack/backend-api@0.31.1
82
+
3
83
  ## 0.4.3
4
84
 
5
85
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/healthcheck-postgres-backend",
3
- "version": "0.4.3",
3
+ "version": "0.5.1",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "checkstack": {
@@ -14,9 +14,9 @@
14
14
  "pack": "bunx @checkstack/scripts plugin-pack"
15
15
  },
16
16
  "dependencies": {
17
- "@checkstack/backend-api": "0.31.0",
17
+ "@checkstack/backend-api": "0.32.0",
18
18
  "@checkstack/common": "0.22.0",
19
- "@checkstack/healthcheck-common": "1.15.0",
19
+ "@checkstack/healthcheck-common": "1.16.1",
20
20
  "pg": "^8.11.0"
21
21
  },
22
22
  "devDependencies": {
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  Versioned,
3
3
  z,
4
+ configString,
4
5
  type HealthCheckRunForAggregation,
5
6
  type CollectorResult,
6
7
  type CollectorStrategy,
@@ -24,11 +25,28 @@ import type { PostgresTransportClient } from "./transport-client";
24
25
  // ============================================================================
25
26
 
26
27
  const queryConfigSchema = z.object({
27
- query: z.string().min(1).default("SELECT 1").describe("SQL query to execute"),
28
+ // Templatable: supports `{{ environment.query }}` so one config covers N
29
+ // environments. `.min(1)` still guards the STORED value (a `{{ }}` template is
30
+ // non-empty); the CONCRETE rendered query is re-checked POST-RENDER in
31
+ // `execute` because an empty render must not run as a successful query.
32
+ query: configString({ "x-templatable": true })
33
+ .min(1)
34
+ .default("SELECT 1")
35
+ .describe(
36
+ "SQL query to execute. Supports templating, e.g. {{ environment.query }}",
37
+ ),
28
38
  });
29
39
 
30
40
  export type QueryConfig = z.infer<typeof queryConfigSchema>;
31
41
 
42
+ /**
43
+ * Post-render validator for the rendered `query`. An empty render (e.g. an
44
+ * env-less run resolving `{{ environment.query }}` to "") is a config error
45
+ * that prevents the probe - transport-failure semantics - not a healthy empty
46
+ * query.
47
+ */
48
+ const renderedQuerySchema = z.string().trim().min(1);
49
+
32
50
  // ============================================================================
33
51
  // RESULT SCHEMAS
34
52
  // ============================================================================
@@ -143,7 +161,24 @@ export class QueryCollector implements CollectorStrategy<
143
161
  }): Promise<CollectorResult<QueryResult>> {
144
162
  const startTime = Date.now();
145
163
 
146
- const response = await client.exec({ query: config.query });
164
+ // Post-render guard: `query` is a templatable string, so the concrete value
165
+ // is re-validated here after the executor rendered `{{ environment.* }}`.
166
+ // An empty render is a config error - fail as a transport failure rather
167
+ // than running (and "succeeding" at) an empty query.
168
+ const query = renderedQuerySchema.safeParse(config.query);
169
+ if (!query.success) {
170
+ return {
171
+ result: {
172
+ rowCount: 0,
173
+ executionTimeMs: Date.now() - startTime,
174
+ success: false,
175
+ },
176
+ error: `Rendered query is empty: ${JSON.stringify(config.query)}. ` +
177
+ `Check the {{ environment.* }} templating for this environment.`,
178
+ };
179
+ }
180
+
181
+ const response = await client.exec({ query: query.data });
147
182
  const executionTimeMs = Date.now() - startTime;
148
183
 
149
184
  return {
package/src/strategy.ts CHANGED
@@ -44,15 +44,24 @@ import { extractErrorMessage } from "@checkstack/common";
44
44
  * Configuration schema for PostgreSQL health checks.
45
45
  */
46
46
  export const postgresConfigSchema = baseStrategyConfigSchema.extend({
47
- host: configString({}).describe("PostgreSQL server hostname"),
47
+ // Templatable connection fields: support `{{ environment.host }}` etc. so one
48
+ // config covers N environments. Presence is enforced POST-RENDER in
49
+ // `createClient`. `password` stays a secret (never templatable).
50
+ host: configString({ "x-templatable": true }).describe(
51
+ "PostgreSQL server hostname. Supports templating, e.g. {{ environment.host }}",
52
+ ),
48
53
  port: configNumber({})
49
54
  .int()
50
55
  .min(1)
51
56
  .max(65_535)
52
57
  .default(5432)
53
58
  .describe("PostgreSQL port"),
54
- database: configString({}).describe("Database name"),
55
- user: configString({}).describe("Database user"),
59
+ database: configString({ "x-templatable": true }).describe(
60
+ "Database name. Supports templating, e.g. {{ environment.database }}",
61
+ ),
62
+ user: configString({ "x-templatable": true }).describe(
63
+ "Database user. Supports templating, e.g. {{ environment.user }}",
64
+ ),
56
65
  password: configSecret({ id: "password" }).describe("Database password"),
57
66
  ssl: configBoolean({}).default(false).describe("Use SSL connection"),
58
67
  });
@@ -60,6 +69,15 @@ export const postgresConfigSchema = baseStrategyConfigSchema.extend({
60
69
  export type PostgresConfig = z.infer<typeof postgresConfigSchema>;
61
70
  export type PostgresConfigInput = z.input<typeof postgresConfigSchema>;
62
71
 
72
+ /**
73
+ * Post-render validator for required connection fields. The stored values are
74
+ * plain templatable strings, so presence cannot be checked at store time; the
75
+ * executor renders `{{ environment.* }}` per environment, then this rejects a
76
+ * render that collapsed to empty/whitespace. An empty host/database/user is a
77
+ * config error that prevents the probe - transport-failure semantics.
78
+ */
79
+ const renderedRequiredSchema = z.string().trim().min(1);
80
+
63
81
  /**
64
82
  * Per-run result metadata.
65
83
  */
@@ -263,12 +281,35 @@ export class PostgresHealthCheckStrategy implements HealthCheckStrategy<
263
281
  ): Promise<ConnectedClient<PostgresTransportClient>> {
264
282
  const validatedConfig = this.config.validate(config);
265
283
 
284
+ // Post-render guard: the connection fields are templatable strings, so their
285
+ // presence cannot be checked at store time. The executor has already
286
+ // rendered `{{ environment.* }}`; reject a render that collapsed to empty so
287
+ // the run fails clearly instead of attempting an empty connection.
288
+ const rendered = z
289
+ .object({
290
+ host: renderedRequiredSchema,
291
+ database: renderedRequiredSchema,
292
+ user: renderedRequiredSchema,
293
+ })
294
+ .safeParse({
295
+ host: validatedConfig.host,
296
+ database: validatedConfig.database,
297
+ user: validatedConfig.user,
298
+ });
299
+ if (!rendered.success) {
300
+ throw new Error(
301
+ `Rendered PostgreSQL connection fields are empty ` +
302
+ `(host/database/user). Check the {{ environment.* }} templating ` +
303
+ `for this environment.`,
304
+ );
305
+ }
306
+
266
307
  const connectStart = performance.now();
267
308
  const connection = await this.dbClient.connect({
268
- host: validatedConfig.host,
309
+ host: rendered.data.host,
269
310
  port: validatedConfig.port,
270
- database: validatedConfig.database,
271
- user: validatedConfig.user,
311
+ database: rendered.data.database,
312
+ user: rendered.data.user,
272
313
  password: validatedConfig.password,
273
314
  ssl: validatedConfig.ssl ? { rejectUnauthorized: false } : undefined,
274
315
  connectionTimeoutMillis: validatedConfig.timeout,