@getstrata/bootstrap 2.2.9 → 2.2.10
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.10
|
|
4
|
+
|
|
5
|
+
- Add bounded OpenTelemetry SDK health observations and selected native transaction-acquisition histograms through explicit metrics options.
|
|
6
|
+
- Add bounded Postgres failed-job counts using ORM ID projections, with schema guards, native acquisition deadlines and read-only collector draining.
|
|
7
|
+
- Observe Streams retry promotion lateness using Redis time; cancellation cleanup sentinels omit unavailable age instead of reporting age since epoch.
|
|
8
|
+
- Generate typed runtimeMetrics opt-in and provider-owned SQL collector draining, including startup cleanup; HTTP-only defaults and borrowed tracing ownership remain intact.
|
|
9
|
+
- Keep additional SQL observation dialects, sustained operational qualification and encryption rotation explicit follow-up work.
|
|
10
|
+
|
|
3
11
|
## 2.2.9
|
|
4
12
|
|
|
5
13
|
- Support native cancellable transaction acquisition
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { type TransactionAcquisitionMetrics } from "@getstrata/core/database/transaction";
|
|
1
2
|
import { type OutboxMetricsCollector } from "@getstrata/core/events/outbox";
|
|
2
|
-
import { type RedisQueueSnapshotOptions } from "@getstrata/core/queue/queueMetrics";
|
|
3
|
+
import { type FailedJobMetricsCollector, type RedisQueueSnapshotOptions } from "@getstrata/core/queue/queueMetrics";
|
|
4
|
+
import { type TracingRuntime } from "@getstrata/core/tracing/tracingMiddleware";
|
|
3
5
|
interface MetricsRoutesOptions {
|
|
4
6
|
/** Opt in to shared Redis queue observations; no database or failed-job scan. */
|
|
5
7
|
queue?: RedisQueueSnapshotOptions & {
|
|
@@ -7,9 +9,17 @@ interface MetricsRoutesOptions {
|
|
|
7
9
|
};
|
|
8
10
|
/** Explicit platform outbox observation; caller owns collector shutdown. */
|
|
9
11
|
outbox?: Pick<OutboxMetricsCollector, "collect">;
|
|
12
|
+
/** Read an existing runtime; scraping never initializes tracing or flushes spans. */
|
|
13
|
+
tracing?: Pick<TracingRuntime, "metrics">;
|
|
14
|
+
/** Fixed process-local observations; scrape does not reserve a database connection. */
|
|
15
|
+
transactionAcquisition?: Pick<TransactionAcquisitionMetrics, "snapshot">;
|
|
16
|
+
/** Explicit global failed-job observation; caller owns collector shutdown. */
|
|
17
|
+
failedJobs?: Pick<FailedJobMetricsCollector, "collect">;
|
|
10
18
|
}
|
|
11
19
|
declare function createMetricsRoutes(options?: MetricsRoutesOptions): {
|
|
12
20
|
"/metrics": (request: Request) => Promise<Response>;
|
|
13
21
|
};
|
|
22
|
+
export type { MetricsRuntime, MetricsRuntimeOptions } from "./metricsRuntime";
|
|
23
|
+
export { createMetricsRuntime } from "./metricsRuntime";
|
|
14
24
|
export type { MetricsRoutesOptions };
|
|
15
25
|
export { createMetricsRoutes };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type OutboxMetricsOptions } from "@getstrata/core/events/outbox";
|
|
2
|
+
import { type FailedJobMetricsOptions } from "@getstrata/core/queue/queueMetrics";
|
|
3
|
+
import type { MetricsRoutesOptions } from "./metricsRoutes";
|
|
4
|
+
interface MetricsRuntimeOptions {
|
|
5
|
+
queue?: MetricsRoutesOptions["queue"];
|
|
6
|
+
outbox?: OutboxMetricsOptions;
|
|
7
|
+
failedJobs?: FailedJobMetricsOptions;
|
|
8
|
+
/** Borrowed: the existing tracing provider retains flush/shutdown ownership. */
|
|
9
|
+
tracing?: MetricsRoutesOptions["tracing"];
|
|
10
|
+
/** Borrowed: callers choose which transactions to instrument. */
|
|
11
|
+
transactionAcquisition?: MetricsRoutesOptions["transactionAcquisition"];
|
|
12
|
+
}
|
|
13
|
+
interface MetricsRuntime {
|
|
14
|
+
readonly options: Readonly<MetricsRoutesOptions>;
|
|
15
|
+
/** Drain owned SQL observations after HTTP admission stops and requests drain, before pool closure. */
|
|
16
|
+
close(): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
/** Lazy, explicit observations only: no schema provisioning, worker or tracing initialization. */
|
|
19
|
+
declare function createMetricsRuntime(options?: MetricsRuntimeOptions): MetricsRuntime;
|
|
20
|
+
export type { MetricsRuntime, MetricsRuntimeOptions };
|
|
21
|
+
export { createMetricsRuntime };
|
|
@@ -28,6 +28,8 @@ export { checkDatabase, checkRedis, createHealthRoutes, pingDatabase } from "./h
|
|
|
28
28
|
export { type RouteModelAuthorization, securedBindRouteModel, securedBindRouteModelByKey, } from "./http/securedRouteModelBinding.ts";
|
|
29
29
|
export { createHttpKernel, type HttpKernel, type MiddlewareGroupName } from "./httpKernel.ts";
|
|
30
30
|
export { resolveMembershipService } from "./membershipService.ts";
|
|
31
|
+
export type { MetricsRoutesOptions, MetricsRuntime, MetricsRuntimeOptions, } from "./metricsRoutes.ts";
|
|
32
|
+
export { createMetricsRoutes, createMetricsRuntime } from "./metricsRoutes.ts";
|
|
31
33
|
export { prefixRouteMap } from "./prefixRouteMap.ts";
|
|
32
34
|
export { coreProviders } from "./providers/index.ts";
|
|
33
35
|
export { default as tracingProvider } from "./providers/tracing.ts";
|
|
@@ -1,13 +1,50 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// ../../src/bootstrap/metricsRoutes.ts
|
|
3
3
|
import { timingSafeEqual } from "crypto";
|
|
4
|
+
import {
|
|
5
|
+
renderTransactionAcquisitionMetrics
|
|
6
|
+
} from "@getstrata/core/database/transaction";
|
|
4
7
|
import { renderOutboxMetrics } from "@getstrata/core/events/outbox";
|
|
5
8
|
import { prometheusRegistry } from "@getstrata/core/metrics/prometheus";
|
|
6
9
|
import {
|
|
7
10
|
readRedisQueueSnapshot,
|
|
11
|
+
renderFailedJobMetrics,
|
|
8
12
|
renderRedisQueueMetrics
|
|
9
13
|
} from "@getstrata/core/queue/queueMetrics";
|
|
10
14
|
import { isProductionEnv } from "@getstrata/core/runtime/appEnv";
|
|
15
|
+
import {
|
|
16
|
+
renderTracingMetrics
|
|
17
|
+
} from "@getstrata/core/tracing/tracingMiddleware";
|
|
18
|
+
|
|
19
|
+
// ../../src/bootstrap/metricsRuntime.ts
|
|
20
|
+
import {
|
|
21
|
+
createOutboxMetricsCollector
|
|
22
|
+
} from "@getstrata/core/events/outbox";
|
|
23
|
+
import {
|
|
24
|
+
createFailedJobMetricsCollector
|
|
25
|
+
} from "@getstrata/core/queue/queueMetrics";
|
|
26
|
+
function createMetricsRuntime(options = {}) {
|
|
27
|
+
const outbox = options.outbox ? createOutboxMetricsCollector(options.outbox) : undefined;
|
|
28
|
+
const failedJobs = options.failedJobs ? createFailedJobMetricsCollector(options.failedJobs) : undefined;
|
|
29
|
+
let closing;
|
|
30
|
+
return {
|
|
31
|
+
options: Object.freeze({
|
|
32
|
+
queue: options.queue ? { ...options.queue } : undefined,
|
|
33
|
+
outbox,
|
|
34
|
+
failedJobs,
|
|
35
|
+
tracing: options.tracing,
|
|
36
|
+
transactionAcquisition: options.transactionAcquisition
|
|
37
|
+
}),
|
|
38
|
+
close() {
|
|
39
|
+
closing ??= Promise.all([outbox?.close(), failedJobs?.close()]).then(() => {
|
|
40
|
+
return;
|
|
41
|
+
});
|
|
42
|
+
return closing;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ../../src/bootstrap/metricsRoutes.ts
|
|
11
48
|
function tokensMatch(left, right) {
|
|
12
49
|
const leftBuffer = Buffer.from(left);
|
|
13
50
|
const rightBuffer = Buffer.from(right);
|
|
@@ -31,7 +68,7 @@ function authorizeMetrics(request) {
|
|
|
31
68
|
function createMetricsRoutes(options = {}) {
|
|
32
69
|
let collection;
|
|
33
70
|
const collect = () => {
|
|
34
|
-
if (!options.queue && !options.outbox)
|
|
71
|
+
if (!options.queue && !options.outbox && !options.tracing && !options.transactionAcquisition && !options.failedJobs)
|
|
35
72
|
return Promise.resolve("");
|
|
36
73
|
const failure = (name) => `# HELP strata_${name}_collector_success Whether the configured ${name} collection succeeded.
|
|
37
74
|
# TYPE strata_${name}_collector_success gauge
|
|
@@ -39,7 +76,10 @@ strata_${name}_collector_success 0
|
|
|
39
76
|
`;
|
|
40
77
|
collection ??= Promise.all([
|
|
41
78
|
options.queue ? readRedisQueueSnapshot(options.queue.redisUrl, options.queue).then(renderRedisQueueMetrics).catch(() => failure("queue")) : Promise.resolve(""),
|
|
42
|
-
options.outbox ? Promise.resolve().then(() => options.outbox?.collect()).then((snapshot) => snapshot ? renderOutboxMetrics(snapshot) : "").catch(() => failure("outbox")) : Promise.resolve("")
|
|
79
|
+
options.outbox ? Promise.resolve().then(() => options.outbox?.collect()).then((snapshot) => snapshot ? renderOutboxMetrics(snapshot) : "").catch(() => failure("outbox")) : Promise.resolve(""),
|
|
80
|
+
options.tracing ? Promise.resolve().then(() => options.tracing ? renderTracingMetrics(options.tracing.metrics()) : "").catch(() => failure("tracing")) : Promise.resolve(""),
|
|
81
|
+
options.transactionAcquisition ? Promise.resolve().then(() => options.transactionAcquisition ? renderTransactionAcquisitionMetrics(options.transactionAcquisition.snapshot()) : "").catch(() => failure("database_acquisition")) : Promise.resolve(""),
|
|
82
|
+
options.failedJobs ? Promise.resolve().then(() => options.failedJobs?.collect()).then((snapshot) => snapshot ? renderFailedJobMetrics(snapshot) : "").catch(() => failure("queue_failed_job")) : Promise.resolve("")
|
|
43
83
|
]).then((parts) => parts.join("")).finally(() => {
|
|
44
84
|
collection = undefined;
|
|
45
85
|
});
|
|
@@ -61,5 +101,6 @@ strata_${name}_collector_success 0
|
|
|
61
101
|
};
|
|
62
102
|
}
|
|
63
103
|
export {
|
|
64
|
-
createMetricsRoutes
|
|
104
|
+
createMetricsRoutes,
|
|
105
|
+
createMetricsRuntime
|
|
65
106
|
};
|
package/dist/index.js
CHANGED
|
@@ -1648,9 +1648,110 @@ import {
|
|
|
1648
1648
|
} from "@getstrata/core/http/securedRouteModelBinding";
|
|
1649
1649
|
// ../../src/bootstrap/membershipService.ts
|
|
1650
1650
|
import { resolveMembershipService } from "@getstrata/core/auth/membershipService";
|
|
1651
|
+
// ../../src/bootstrap/metricsRoutes.ts
|
|
1652
|
+
import { timingSafeEqual } from "crypto";
|
|
1653
|
+
import {
|
|
1654
|
+
renderTransactionAcquisitionMetrics
|
|
1655
|
+
} from "@getstrata/core/database/transaction";
|
|
1656
|
+
import { renderOutboxMetrics } from "@getstrata/core/events/outbox";
|
|
1657
|
+
import { prometheusRegistry } from "@getstrata/core/metrics/prometheus";
|
|
1658
|
+
import {
|
|
1659
|
+
readRedisQueueSnapshot,
|
|
1660
|
+
renderFailedJobMetrics,
|
|
1661
|
+
renderRedisQueueMetrics
|
|
1662
|
+
} from "@getstrata/core/queue/queueMetrics";
|
|
1663
|
+
import { isProductionEnv } from "@getstrata/core/runtime/appEnv";
|
|
1664
|
+
import {
|
|
1665
|
+
renderTracingMetrics
|
|
1666
|
+
} from "@getstrata/core/tracing/tracingMiddleware";
|
|
1667
|
+
|
|
1668
|
+
// ../../src/bootstrap/metricsRuntime.ts
|
|
1669
|
+
import {
|
|
1670
|
+
createOutboxMetricsCollector
|
|
1671
|
+
} from "@getstrata/core/events/outbox";
|
|
1672
|
+
import {
|
|
1673
|
+
createFailedJobMetricsCollector
|
|
1674
|
+
} from "@getstrata/core/queue/queueMetrics";
|
|
1675
|
+
function createMetricsRuntime(options = {}) {
|
|
1676
|
+
const outbox = options.outbox ? createOutboxMetricsCollector(options.outbox) : undefined;
|
|
1677
|
+
const failedJobs = options.failedJobs ? createFailedJobMetricsCollector(options.failedJobs) : undefined;
|
|
1678
|
+
let closing;
|
|
1679
|
+
return {
|
|
1680
|
+
options: Object.freeze({
|
|
1681
|
+
queue: options.queue ? { ...options.queue } : undefined,
|
|
1682
|
+
outbox,
|
|
1683
|
+
failedJobs,
|
|
1684
|
+
tracing: options.tracing,
|
|
1685
|
+
transactionAcquisition: options.transactionAcquisition
|
|
1686
|
+
}),
|
|
1687
|
+
close() {
|
|
1688
|
+
closing ??= Promise.all([outbox?.close(), failedJobs?.close()]).then(() => {
|
|
1689
|
+
return;
|
|
1690
|
+
});
|
|
1691
|
+
return closing;
|
|
1692
|
+
}
|
|
1693
|
+
};
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
// ../../src/bootstrap/metricsRoutes.ts
|
|
1697
|
+
function tokensMatch(left, right) {
|
|
1698
|
+
const leftBuffer = Buffer.from(left);
|
|
1699
|
+
const rightBuffer = Buffer.from(right);
|
|
1700
|
+
if (leftBuffer.length !== rightBuffer.length) {
|
|
1701
|
+
return false;
|
|
1702
|
+
}
|
|
1703
|
+
return timingSafeEqual(leftBuffer, rightBuffer);
|
|
1704
|
+
}
|
|
1705
|
+
function authorizeMetrics(request) {
|
|
1706
|
+
const expected = process.env.METRICS_TOKEN?.trim();
|
|
1707
|
+
const authorization = request.headers.get("authorization") ?? "";
|
|
1708
|
+
const presented = authorization.startsWith("Bearer ") ? authorization.slice("Bearer ".length) : "";
|
|
1709
|
+
if (expected) {
|
|
1710
|
+
return presented.length > 0 && tokensMatch(presented, expected);
|
|
1711
|
+
}
|
|
1712
|
+
if (isProductionEnv()) {
|
|
1713
|
+
return false;
|
|
1714
|
+
}
|
|
1715
|
+
return true;
|
|
1716
|
+
}
|
|
1717
|
+
function createMetricsRoutes(options = {}) {
|
|
1718
|
+
let collection;
|
|
1719
|
+
const collect = () => {
|
|
1720
|
+
if (!options.queue && !options.outbox && !options.tracing && !options.transactionAcquisition && !options.failedJobs)
|
|
1721
|
+
return Promise.resolve("");
|
|
1722
|
+
const failure = (name) => `# HELP strata_${name}_collector_success Whether the configured ${name} collection succeeded.
|
|
1723
|
+
# TYPE strata_${name}_collector_success gauge
|
|
1724
|
+
strata_${name}_collector_success 0
|
|
1725
|
+
`;
|
|
1726
|
+
collection ??= Promise.all([
|
|
1727
|
+
options.queue ? readRedisQueueSnapshot(options.queue.redisUrl, options.queue).then(renderRedisQueueMetrics).catch(() => failure("queue")) : Promise.resolve(""),
|
|
1728
|
+
options.outbox ? Promise.resolve().then(() => options.outbox?.collect()).then((snapshot) => snapshot ? renderOutboxMetrics(snapshot) : "").catch(() => failure("outbox")) : Promise.resolve(""),
|
|
1729
|
+
options.tracing ? Promise.resolve().then(() => options.tracing ? renderTracingMetrics(options.tracing.metrics()) : "").catch(() => failure("tracing")) : Promise.resolve(""),
|
|
1730
|
+
options.transactionAcquisition ? Promise.resolve().then(() => options.transactionAcquisition ? renderTransactionAcquisitionMetrics(options.transactionAcquisition.snapshot()) : "").catch(() => failure("database_acquisition")) : Promise.resolve(""),
|
|
1731
|
+
options.failedJobs ? Promise.resolve().then(() => options.failedJobs?.collect()).then((snapshot) => snapshot ? renderFailedJobMetrics(snapshot) : "").catch(() => failure("queue_failed_job")) : Promise.resolve("")
|
|
1732
|
+
]).then((parts) => parts.join("")).finally(() => {
|
|
1733
|
+
collection = undefined;
|
|
1734
|
+
});
|
|
1735
|
+
return collection;
|
|
1736
|
+
};
|
|
1737
|
+
return {
|
|
1738
|
+
"/metrics": async (request) => {
|
|
1739
|
+
if (!authorizeMetrics(request)) {
|
|
1740
|
+
return new Response("Not Found", { status: 404 });
|
|
1741
|
+
}
|
|
1742
|
+
return new Response(prometheusRegistry.renderMetrics() + await collect(), {
|
|
1743
|
+
status: 200,
|
|
1744
|
+
headers: {
|
|
1745
|
+
"content-type": "text/plain; version=0.0.4; charset=utf-8",
|
|
1746
|
+
"cache-control": "no-store"
|
|
1747
|
+
}
|
|
1748
|
+
});
|
|
1749
|
+
}
|
|
1750
|
+
};
|
|
1751
|
+
}
|
|
1651
1752
|
// ../../src/bootstrap/secretsGuard.ts
|
|
1652
1753
|
import { getDefaultDatabasePool as getDefaultDatabasePool2 } from "@getstrata/core/database/defaultConnection";
|
|
1653
|
-
import { envFlagEnabled as envFlagEnabled3, isProductionEnv } from "@getstrata/core/runtime/appEnv";
|
|
1754
|
+
import { envFlagEnabled as envFlagEnabled3, isProductionEnv as isProductionEnv2 } from "@getstrata/core/runtime/appEnv";
|
|
1654
1755
|
import { isViewsMode, parseFrontendMode } from "@getstrata/core/runtime/frontendMode";
|
|
1655
1756
|
import {
|
|
1656
1757
|
assertPostgresRoleCannotBypassRls,
|
|
@@ -1829,7 +1930,7 @@ function assertPublicAppUrl(env) {
|
|
|
1829
1930
|
}
|
|
1830
1931
|
}
|
|
1831
1932
|
function assertProductionSecrets(env = process.env) {
|
|
1832
|
-
if (!
|
|
1933
|
+
if (!isProductionEnv2(env)) {
|
|
1833
1934
|
return;
|
|
1834
1935
|
}
|
|
1835
1936
|
assertNoPlaceholderSecrets(env);
|
|
@@ -2087,7 +2188,7 @@ import {
|
|
|
2087
2188
|
import { currentSqlDialect, sqlTimestamp } from "@getstrata/core/database/dialect";
|
|
2088
2189
|
import { readRequestCookie } from "@getstrata/core/http/cookies";
|
|
2089
2190
|
import { currentRequestMeta as currentRequestMeta3 } from "@getstrata/core/http/requestMetaContext";
|
|
2090
|
-
import { isProductionEnv as
|
|
2191
|
+
import { isProductionEnv as isProductionEnv3 } from "@getstrata/core/runtime/appEnv";
|
|
2091
2192
|
import { timingSafeCompareString } from "@getstrata/core/security/timingSafeCompare";
|
|
2092
2193
|
import { runWithMigrationBypassForIdentifier } from "@getstrata/core/tenant/databaseTenantContext";
|
|
2093
2194
|
function sqlPlaceholder(index) {
|
|
@@ -2217,7 +2318,7 @@ class CookieSessionStore {
|
|
|
2217
2318
|
return sessionId;
|
|
2218
2319
|
}
|
|
2219
2320
|
withSecureFlag(header) {
|
|
2220
|
-
if (!
|
|
2321
|
+
if (!isProductionEnv3()) {
|
|
2221
2322
|
return header;
|
|
2222
2323
|
}
|
|
2223
2324
|
return header.includes("Secure") ? header : `${header}; Secure`;
|
|
@@ -2371,6 +2472,8 @@ export {
|
|
|
2371
2472
|
createCsrfProtection,
|
|
2372
2473
|
createHealthRoutes,
|
|
2373
2474
|
createHttpKernel,
|
|
2475
|
+
createMetricsRoutes,
|
|
2476
|
+
createMetricsRuntime,
|
|
2374
2477
|
createRouteKernel,
|
|
2375
2478
|
createWebRoutes,
|
|
2376
2479
|
createWebServer,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getstrata/bootstrap",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.10",
|
|
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.
|
|
193
|
+
"@getstrata/core": "^2.2.10",
|
|
194
194
|
"typescript": "^5.9.0 || ^6.0.0 || ^7.0.0"
|
|
195
195
|
},
|
|
196
196
|
"engines": {
|