@getstrata/bootstrap 2.2.3 → 2.2.5

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,13 @@
1
1
  # @getstrata/bootstrap changelog
2
2
 
3
+ ## 2.2.5
4
+
5
+ - fix(starter): keep container readiness independent of API admission
6
+
7
+ ## 2.2.4
8
+
9
+ - perf(database): seek Postgres composite keyset bounds
10
+
3
11
  ## 2.2.3
4
12
 
5
13
  - feat(database): add scoped composite keyset pagination
@@ -1,13 +1,17 @@
1
1
  import type { AppDependencies } from "./contracts";
2
2
  interface CreateHealthRoutesOptions {
3
3
  pingOnHealth?: boolean;
4
+ /** Optional non-mutating schema check; applies to dependency-checked /health only. */
5
+ schemaCheck?: () => boolean | Promise<boolean>;
6
+ /** Text mode emits only ok/degraded for container readiness probes. */
7
+ healthFormat?: "json" | "text";
4
8
  extra?: Record<string, unknown> | (() => Record<string, unknown> | Promise<Record<string, unknown>>);
5
9
  }
6
10
  declare function checkDatabase(): Promise<boolean>;
7
11
  declare function pingDatabase(): Promise<boolean>;
8
12
  declare function checkRedis(redisUrl: string): Promise<boolean>;
9
13
  declare function createHealthRoutes(dependencies: AppDependencies, options?: CreateHealthRoutesOptions): {
10
- "/health": () => Promise<Response>;
14
+ "/health": (request?: Request) => Promise<Response>;
11
15
  "/ready": () => Promise<Response>;
12
16
  };
13
17
  export type { CreateHealthRoutesOptions };
@@ -75,12 +75,19 @@ async function pingDatabase() {
75
75
  return await checkDatabase();
76
76
  }
77
77
  async function checkRedis(redisUrl) {
78
+ let client;
78
79
  try {
79
- const client = new RedisClient(redisUrl);
80
+ client = new RedisClient(redisUrl, {
81
+ connectionTimeout: 1000,
82
+ idleTimeout: 1000,
83
+ autoReconnect: false
84
+ });
80
85
  const response = await client.ping();
81
86
  return response === "PONG";
82
87
  } catch {
83
88
  return false;
89
+ } finally {
90
+ client?.close();
84
91
  }
85
92
  }
86
93
  async function resolveExtraFields(extra) {
@@ -104,17 +111,40 @@ async function collectDependencyChecks(dependencies) {
104
111
  }
105
112
  function createHealthRoutes(dependencies, options = {}) {
106
113
  return {
107
- "/health": async () => {
108
- const extra = await resolveExtraFields(options.extra);
114
+ "/health": async (request) => {
115
+ if (request && request.method !== "GET" && request.method !== "HEAD") {
116
+ return new Response(null, { status: 405, headers: { allow: "GET, HEAD" } });
117
+ }
118
+ const headers = { "cache-control": "no-store" };
119
+ const extra = options.healthFormat === "text" ? {} : await resolveExtraFields(options.extra);
109
120
  if (!options.pingOnHealth) {
110
- return jsonResponse({ status: "ok", ...extra });
121
+ return options.healthFormat === "text" ? new Response("ok", {
122
+ headers: { ...headers, "content-type": "text/plain; charset=utf-8" }
123
+ }) : jsonResponse({ status: "ok", ...extra }, { headers });
124
+ }
125
+ const { checks, ready: dependenciesReady } = await collectDependencyChecks(dependencies);
126
+ let schemaReady = true;
127
+ if (options.schemaCheck) {
128
+ try {
129
+ schemaReady = await options.schemaCheck();
130
+ } catch {
131
+ schemaReady = false;
132
+ }
133
+ }
134
+ if (options.schemaCheck)
135
+ checks.schema = schemaReady ? "ok" : "error";
136
+ const ready = dependenciesReady && schemaReady;
137
+ if (options.healthFormat === "text") {
138
+ return new Response(ready ? "ok" : "degraded", {
139
+ status: ready ? 200 : 503,
140
+ headers: { ...headers, "content-type": "text/plain; charset=utf-8" }
141
+ });
111
142
  }
112
- const { checks, ready } = await collectDependencyChecks(dependencies);
113
143
  return jsonResponse({
114
144
  status: ready ? "ok" : "error",
115
145
  checks,
116
146
  ...extra
117
- }, { status: ready ? 200 : 503 });
147
+ }, { status: ready ? 200 : 503, headers });
118
148
  },
119
149
  "/ready": async () => {
120
150
  const extra = await resolveExtraFields(options.extra);
package/dist/index.js CHANGED
@@ -1559,12 +1559,19 @@ async function pingDatabase() {
1559
1559
  return await checkDatabase();
1560
1560
  }
1561
1561
  async function checkRedis(redisUrl) {
1562
+ let client;
1562
1563
  try {
1563
- const client = new RedisClient(redisUrl);
1564
+ client = new RedisClient(redisUrl, {
1565
+ connectionTimeout: 1000,
1566
+ idleTimeout: 1000,
1567
+ autoReconnect: false
1568
+ });
1564
1569
  const response = await client.ping();
1565
1570
  return response === "PONG";
1566
1571
  } catch {
1567
1572
  return false;
1573
+ } finally {
1574
+ client?.close();
1568
1575
  }
1569
1576
  }
1570
1577
  async function resolveExtraFields(extra) {
@@ -1588,17 +1595,40 @@ async function collectDependencyChecks(dependencies) {
1588
1595
  }
1589
1596
  function createHealthRoutes(dependencies, options = {}) {
1590
1597
  return {
1591
- "/health": async () => {
1592
- const extra = await resolveExtraFields(options.extra);
1598
+ "/health": async (request) => {
1599
+ if (request && request.method !== "GET" && request.method !== "HEAD") {
1600
+ return new Response(null, { status: 405, headers: { allow: "GET, HEAD" } });
1601
+ }
1602
+ const headers = { "cache-control": "no-store" };
1603
+ const extra = options.healthFormat === "text" ? {} : await resolveExtraFields(options.extra);
1593
1604
  if (!options.pingOnHealth) {
1594
- return jsonResponse({ status: "ok", ...extra });
1605
+ return options.healthFormat === "text" ? new Response("ok", {
1606
+ headers: { ...headers, "content-type": "text/plain; charset=utf-8" }
1607
+ }) : jsonResponse({ status: "ok", ...extra }, { headers });
1608
+ }
1609
+ const { checks, ready: dependenciesReady } = await collectDependencyChecks(dependencies);
1610
+ let schemaReady = true;
1611
+ if (options.schemaCheck) {
1612
+ try {
1613
+ schemaReady = await options.schemaCheck();
1614
+ } catch {
1615
+ schemaReady = false;
1616
+ }
1617
+ }
1618
+ if (options.schemaCheck)
1619
+ checks.schema = schemaReady ? "ok" : "error";
1620
+ const ready = dependenciesReady && schemaReady;
1621
+ if (options.healthFormat === "text") {
1622
+ return new Response(ready ? "ok" : "degraded", {
1623
+ status: ready ? 200 : 503,
1624
+ headers: { ...headers, "content-type": "text/plain; charset=utf-8" }
1625
+ });
1595
1626
  }
1596
- const { checks, ready } = await collectDependencyChecks(dependencies);
1597
1627
  return jsonResponse({
1598
1628
  status: ready ? "ok" : "error",
1599
1629
  checks,
1600
1630
  ...extra
1601
- }, { status: ready ? 200 : 503 });
1631
+ }, { status: ready ? 200 : 503, headers });
1602
1632
  },
1603
1633
  "/ready": async () => {
1604
1634
  const extra = await resolveExtraFields(options.extra);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getstrata/bootstrap",
3
- "version": "2.2.3",
3
+ "version": "2.2.5",
4
4
  "description": "Strata application bootstrap: HttpKernel, DI, web session helpers",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -190,7 +190,7 @@
190
190
  "access": "public"
191
191
  },
192
192
  "peerDependencies": {
193
- "@getstrata/core": "^2.2.3",
193
+ "@getstrata/core": "^2.2.5",
194
194
  "typescript": "^5.9.0 || ^6.0.0 || ^7.0.0"
195
195
  },
196
196
  "engines": {