@getstrata/core 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 +8 -0
- package/dist/core/database/acquisitionMetrics.d.ts +23 -0
- package/dist/core/database/postgresReadOnlyCollector.d.ts +7 -0
- package/dist/core/database/transaction.d.ts +5 -0
- package/dist/core/queue/failedJobMetrics.d.ts +17 -0
- package/dist/core/queue/queueMetrics.d.ts +4 -0
- package/dist/core/tracing/health.d.ts +27 -0
- package/dist/core/tracing/otel.d.ts +2 -0
- package/dist/core/tracing/tracingMiddleware.d.ts +2 -0
- package/dist/entries/audit/exportAuditLogs.js +61 -3
- package/dist/entries/auth/scimAuthMiddleware.js +61 -3
- package/dist/entries/http/response.js +3 -0
- package/dist/entries/jobs/exportAuditLogsJob.js +61 -3
- package/dist/entries/queue/queueMetrics.js +1018 -7
- package/dist/entries/tenant/databaseTenantContext.js +61 -3
- package/dist/entries/tenant/tenantDatabaseScope.js +61 -3
- package/dist/entries/tracing/tracingMiddleware.js +188 -5
- package/dist/framework/public-api.d.ts +6 -0
- package/dist/index.js +455 -99
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @getstrata/core 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
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
declare const outcomes: readonly ["acquired", "failed", "aborted"];
|
|
2
|
+
type AcquisitionOutcome = (typeof outcomes)[number];
|
|
3
|
+
interface AcquisitionHistogram {
|
|
4
|
+
outcome: AcquisitionOutcome;
|
|
5
|
+
count: number;
|
|
6
|
+
sumSeconds: number;
|
|
7
|
+
buckets: readonly number[];
|
|
8
|
+
}
|
|
9
|
+
interface TransactionAcquisitionSnapshot {
|
|
10
|
+
inflight: number;
|
|
11
|
+
outcomes: readonly AcquisitionHistogram[];
|
|
12
|
+
}
|
|
13
|
+
declare const metricsBrand: unique symbol;
|
|
14
|
+
interface TransactionAcquisitionMetrics {
|
|
15
|
+
readonly [metricsBrand]: true;
|
|
16
|
+
snapshot(): TransactionAcquisitionSnapshot;
|
|
17
|
+
}
|
|
18
|
+
declare function createTransactionAcquisitionMetrics(): TransactionAcquisitionMetrics;
|
|
19
|
+
/** Internal native-reservation boundary, never a business/BEGIN/savepoint timer. */
|
|
20
|
+
declare function observeTransactionAcquisition<T>(metrics: TransactionAcquisitionMetrics, acquire: () => Promise<T>, signal?: AbortSignal): Promise<T>;
|
|
21
|
+
declare function renderTransactionAcquisitionMetrics(snapshot: TransactionAcquisitionSnapshot): string;
|
|
22
|
+
export type { TransactionAcquisitionMetrics, TransactionAcquisitionSnapshot };
|
|
23
|
+
export { createTransactionAcquisitionMetrics, observeTransactionAcquisition, renderTransactionAcquisitionMetrics, };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
interface ReadOnlyCollector<T> {
|
|
2
|
+
collect(): Promise<T>;
|
|
3
|
+
close(): Promise<void>;
|
|
4
|
+
}
|
|
5
|
+
/** Internal shared observation lifecycle. No bypass, payload or tenant enumeration. */
|
|
6
|
+
declare function createPostgresReadOnlyCollector<T>(name: string, timeoutMs: number, collect: (remaining: () => number) => Promise<T>): ReadOnlyCollector<T>;
|
|
7
|
+
export { createPostgresReadOnlyCollector };
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { type TransactionAcquisitionMetrics } from "./acquisitionMetrics.ts";
|
|
1
2
|
import type { DatabaseConnection } from "./baseRepository.ts";
|
|
2
3
|
import { hasActiveTransaction, requestTransactionRollback } from "./transactionControl.ts";
|
|
3
4
|
interface TransactionOptions {
|
|
4
5
|
/** Cancel admission/pool checkout only. Does not cancel SQL or an admitted business callback. */
|
|
5
6
|
acquisitionSignal?: AbortSignal;
|
|
7
|
+
/** Observe native checkout only; requires a reserve-capable adapter. */
|
|
8
|
+
acquisitionMetrics?: TransactionAcquisitionMetrics;
|
|
6
9
|
}
|
|
7
10
|
declare function runInTransaction<TValue>(operation: (connection: DatabaseConnection) => Promise<TValue>, options?: TransactionOptions): Promise<TValue>;
|
|
11
|
+
export type { TransactionAcquisitionMetrics, TransactionAcquisitionSnapshot, } from "./acquisitionMetrics.ts";
|
|
12
|
+
export { createTransactionAcquisitionMetrics, renderTransactionAcquisitionMetrics, } from "./acquisitionMetrics.ts";
|
|
8
13
|
export type { TransactionOptions };
|
|
9
14
|
export { hasActiveTransaction, requestTransactionRollback, runInTransaction };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
interface FailedJobMetricsSnapshot {
|
|
2
|
+
sampleLimit: number;
|
|
3
|
+
count: number;
|
|
4
|
+
capped: boolean;
|
|
5
|
+
}
|
|
6
|
+
interface FailedJobMetricsOptions {
|
|
7
|
+
timeoutMs?: number;
|
|
8
|
+
sampleLimit?: number;
|
|
9
|
+
}
|
|
10
|
+
interface FailedJobMetricsCollector {
|
|
11
|
+
collect(): Promise<FailedJobMetricsSnapshot>;
|
|
12
|
+
close(): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
declare function createFailedJobMetricsCollector(options?: FailedJobMetricsOptions): FailedJobMetricsCollector;
|
|
15
|
+
declare function renderFailedJobMetrics(snapshot: FailedJobMetricsSnapshot): string;
|
|
16
|
+
export type { FailedJobMetricsCollector, FailedJobMetricsOptions, FailedJobMetricsSnapshot };
|
|
17
|
+
export { createFailedJobMetricsCollector, renderFailedJobMetrics };
|
|
@@ -26,6 +26,8 @@ interface QueuePrioritySnapshot {
|
|
|
26
26
|
inflightCapped: boolean;
|
|
27
27
|
retryDue: number;
|
|
28
28
|
retryWaiting: number;
|
|
29
|
+
/** Oldest positive due-score lateness; unavailable for lists or cancellation sentinels. */
|
|
30
|
+
retryActionableLatenessSeconds?: number | null;
|
|
29
31
|
quarantined: number;
|
|
30
32
|
/** Residence age of the oldest current stream entry, not original job age across retries. */
|
|
31
33
|
oldestStreamEntryAgeSeconds: number | null;
|
|
@@ -39,5 +41,7 @@ declare const INFLIGHT_SAMPLE_LIMIT = 500;
|
|
|
39
41
|
declare function readRedisQueueSnapshot(redisUrl: string, options?: RedisQueueSnapshotOptions): Promise<RedisQueueSnapshot>;
|
|
40
42
|
/** Fixed metric/label vocabulary; no job IDs, tenant IDs, payloads or credentials. */
|
|
41
43
|
declare function renderRedisQueueMetrics(snapshot: RedisQueueSnapshot): string;
|
|
44
|
+
export type { FailedJobMetricsCollector, FailedJobMetricsOptions, FailedJobMetricsSnapshot, } from "./failedJobMetrics";
|
|
45
|
+
export { createFailedJobMetricsCollector, renderFailedJobMetrics } from "./failedJobMetrics";
|
|
42
46
|
export type { QueuePrioritySnapshot, RedisQueueSnapshot, RedisQueueSnapshotOptions };
|
|
43
47
|
export { INFLIGHT_SAMPLE_LIMIT, readRedisQueueSnapshot, renderRedisQueueMetrics };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type MeterProvider } from "@opentelemetry/api";
|
|
2
|
+
import type { SpanExporter } from "@opentelemetry/sdk-trace-base";
|
|
3
|
+
interface TracingMetricsSnapshot {
|
|
4
|
+
enabled: boolean;
|
|
5
|
+
exporterConfigured: boolean;
|
|
6
|
+
active: boolean;
|
|
7
|
+
queueSize: number | null;
|
|
8
|
+
queueCapacity: number | null;
|
|
9
|
+
droppedSpans: number;
|
|
10
|
+
exportedBatches: number;
|
|
11
|
+
failedBatches: number;
|
|
12
|
+
exportedSpans: number;
|
|
13
|
+
failedSpans: number;
|
|
14
|
+
exportDeadlineExceededBatches: number;
|
|
15
|
+
flushFailures: number;
|
|
16
|
+
shutdownFailures: number;
|
|
17
|
+
}
|
|
18
|
+
/** A fixed projection of SDK self-observations, not a general metric registry. */
|
|
19
|
+
declare function createTracingHealth(enabled: boolean, exporterConfigured: boolean, timeoutMillis: number): {
|
|
20
|
+
state: TracingMetricsSnapshot;
|
|
21
|
+
meterProvider: MeterProvider;
|
|
22
|
+
snapshot(): TracingMetricsSnapshot;
|
|
23
|
+
wrap(exporter: SpanExporter): SpanExporter;
|
|
24
|
+
};
|
|
25
|
+
declare function renderTracingMetrics(snapshot: TracingMetricsSnapshot): string;
|
|
26
|
+
export type { TracingMetricsSnapshot };
|
|
27
|
+
export { createTracingHealth, renderTracingMetrics };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type Context, type Tracer } from "@opentelemetry/api";
|
|
2
2
|
import { W3CTraceContextPropagator } from "@opentelemetry/core";
|
|
3
3
|
import { type SpanExporter } from "@opentelemetry/sdk-trace-base";
|
|
4
|
+
import { type TracingMetricsSnapshot } from "./health";
|
|
4
5
|
type TracingOptions = {
|
|
5
6
|
exporter?: SpanExporter;
|
|
6
7
|
endpoint?: string;
|
|
@@ -15,6 +16,7 @@ type TracingRuntime = {
|
|
|
15
16
|
tracer: Tracer;
|
|
16
17
|
propagator: W3CTraceContextPropagator;
|
|
17
18
|
run<T>(parent: Context, callback: () => T): T;
|
|
19
|
+
metrics(): TracingMetricsSnapshot;
|
|
18
20
|
forceFlush(): Promise<void>;
|
|
19
21
|
shutdown(): Promise<void>;
|
|
20
22
|
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Middleware } from "@getstrata/core/http/middleware";
|
|
2
2
|
import { type TracingRuntime } from "./otel";
|
|
3
3
|
declare function createTracingMiddleware(runtime?: TracingRuntime): Middleware;
|
|
4
|
+
export type { TracingMetricsSnapshot } from "./health";
|
|
5
|
+
export { renderTracingMetrics } from "./health";
|
|
4
6
|
export type { TracingOptions, TracingRuntime } from "./otel";
|
|
5
7
|
export { acquireTracingRuntime, createTracingRuntime, getTracingRuntime } from "./otel";
|
|
6
8
|
export { createTracingMiddleware };
|
|
@@ -129,6 +129,9 @@ function unwrapTransactionRollback(error, depth = 0) {
|
|
|
129
129
|
}
|
|
130
130
|
return unwrapTransactionRollback(error.cause, depth + 1);
|
|
131
131
|
}
|
|
132
|
+
function hasActiveTransaction() {
|
|
133
|
+
return hasActiveDatabaseConnection() && (transactionScopes.getStore()?.rollback.length ?? 0) > 0;
|
|
134
|
+
}
|
|
132
135
|
function requestTransactionRollback() {
|
|
133
136
|
const state = transactionScopes.getStore();
|
|
134
137
|
const index = state ? state.rollback.length - 1 : -1;
|
|
@@ -344,6 +347,55 @@ var init_deferredModelEvents = __esm(() => {
|
|
|
344
347
|
deferredModelEvents = createAsyncContextStore("@getstrata/deferredModelEvents");
|
|
345
348
|
});
|
|
346
349
|
|
|
350
|
+
// ../../src/core/database/acquisitionMetrics.ts
|
|
351
|
+
async function observeTransactionAcquisition(metrics, acquire, signal) {
|
|
352
|
+
const state = states.get(metrics);
|
|
353
|
+
if (!state)
|
|
354
|
+
throw new TypeError("Transaction acquisition metrics must be created by the framework factory.");
|
|
355
|
+
const started = performance.now();
|
|
356
|
+
state.inflight++;
|
|
357
|
+
let outcome = "acquired";
|
|
358
|
+
try {
|
|
359
|
+
return await acquire();
|
|
360
|
+
} catch (error) {
|
|
361
|
+
outcome = signal?.aborted ? "aborted" : "failed";
|
|
362
|
+
throw error;
|
|
363
|
+
} finally {
|
|
364
|
+
state.inflight--;
|
|
365
|
+
const elapsed = Math.max(0, performance.now() - started) / 1000;
|
|
366
|
+
const histogram = state.outcomes[outcomes.indexOf(outcome)];
|
|
367
|
+
if (histogram) {
|
|
368
|
+
histogram.count++;
|
|
369
|
+
histogram.sumSeconds += elapsed;
|
|
370
|
+
for (let index = 0;index < ACQUISITION_BUCKETS_SECONDS.length; index++) {
|
|
371
|
+
const bound = ACQUISITION_BUCKETS_SECONDS[index];
|
|
372
|
+
if (bound !== undefined && elapsed <= bound)
|
|
373
|
+
histogram.buckets[index] = (histogram.buckets[index] ?? 0) + 1;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
var ACQUISITION_BUCKETS_SECONDS, outcomes, metricsBrand, states;
|
|
379
|
+
var init_acquisitionMetrics = __esm(() => {
|
|
380
|
+
ACQUISITION_BUCKETS_SECONDS = [
|
|
381
|
+
0.001,
|
|
382
|
+
0.005,
|
|
383
|
+
0.01,
|
|
384
|
+
0.025,
|
|
385
|
+
0.05,
|
|
386
|
+
0.1,
|
|
387
|
+
0.25,
|
|
388
|
+
0.5,
|
|
389
|
+
1,
|
|
390
|
+
2.5,
|
|
391
|
+
5,
|
|
392
|
+
10
|
|
393
|
+
];
|
|
394
|
+
outcomes = ["acquired", "failed", "aborted"];
|
|
395
|
+
metricsBrand = Symbol("transaction acquisition metrics");
|
|
396
|
+
states = new WeakMap;
|
|
397
|
+
});
|
|
398
|
+
|
|
347
399
|
// ../../src/core/database/connection.ts
|
|
348
400
|
function createDatabaseConnection(source) {
|
|
349
401
|
return {
|
|
@@ -712,9 +764,13 @@ async function runInTransaction(operation, options = {}) {
|
|
|
712
764
|
throw new Error("Active database connection does not support transactions. Bind a client with begin() via bindDatabaseConnection.");
|
|
713
765
|
}
|
|
714
766
|
return await settleTransaction(() => runWithDeferredModelEvents(async () => {
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
767
|
+
let reserved;
|
|
768
|
+
if (signal || options.acquisitionMetrics) {
|
|
769
|
+
if (!pool.reserve)
|
|
770
|
+
throw new Error(signal ? "Active database connection does not support cancellable transaction acquisition." : "Active database connection does not support observable transaction acquisition.");
|
|
771
|
+
const reserve = pool.reserve.bind(pool);
|
|
772
|
+
reserved = options.acquisitionMetrics ? await observeTransactionAcquisition(options.acquisitionMetrics, () => reserve({ signal }), signal) : await reserve({ signal });
|
|
773
|
+
}
|
|
718
774
|
const connection = reserved ?? pool;
|
|
719
775
|
try {
|
|
720
776
|
signal?.throwIfAborted();
|
|
@@ -732,10 +788,12 @@ var transactionFrame, outerFrames;
|
|
|
732
788
|
var init_transaction = __esm(() => {
|
|
733
789
|
init_deferredModelEvents();
|
|
734
790
|
init_asyncContextStore();
|
|
791
|
+
init_acquisitionMetrics();
|
|
735
792
|
init_connection();
|
|
736
793
|
init_connectionContext();
|
|
737
794
|
init_repositoryConnection();
|
|
738
795
|
init_transactionControl();
|
|
796
|
+
init_acquisitionMetrics();
|
|
739
797
|
transactionFrame = createAsyncContextStore("@getstrata/transactionFrame");
|
|
740
798
|
outerFrames = new WeakMap;
|
|
741
799
|
});
|
|
@@ -74,6 +74,9 @@ function unwrapTransactionRollback(error, depth = 0) {
|
|
|
74
74
|
}
|
|
75
75
|
return unwrapTransactionRollback(error.cause, depth + 1);
|
|
76
76
|
}
|
|
77
|
+
function hasActiveTransaction() {
|
|
78
|
+
return hasActiveDatabaseConnection() && (transactionScopes.getStore()?.rollback.length ?? 0) > 0;
|
|
79
|
+
}
|
|
77
80
|
function requestTransactionRollback() {
|
|
78
81
|
const state = transactionScopes.getStore();
|
|
79
82
|
const index = state ? state.rollback.length - 1 : -1;
|
|
@@ -300,6 +303,55 @@ var init_deferredModelEvents = __esm(() => {
|
|
|
300
303
|
deferredModelEvents = createAsyncContextStore("@getstrata/deferredModelEvents");
|
|
301
304
|
});
|
|
302
305
|
|
|
306
|
+
// ../../src/core/database/acquisitionMetrics.ts
|
|
307
|
+
async function observeTransactionAcquisition(metrics, acquire, signal) {
|
|
308
|
+
const state = states.get(metrics);
|
|
309
|
+
if (!state)
|
|
310
|
+
throw new TypeError("Transaction acquisition metrics must be created by the framework factory.");
|
|
311
|
+
const started = performance.now();
|
|
312
|
+
state.inflight++;
|
|
313
|
+
let outcome = "acquired";
|
|
314
|
+
try {
|
|
315
|
+
return await acquire();
|
|
316
|
+
} catch (error) {
|
|
317
|
+
outcome = signal?.aborted ? "aborted" : "failed";
|
|
318
|
+
throw error;
|
|
319
|
+
} finally {
|
|
320
|
+
state.inflight--;
|
|
321
|
+
const elapsed = Math.max(0, performance.now() - started) / 1000;
|
|
322
|
+
const histogram = state.outcomes[outcomes.indexOf(outcome)];
|
|
323
|
+
if (histogram) {
|
|
324
|
+
histogram.count++;
|
|
325
|
+
histogram.sumSeconds += elapsed;
|
|
326
|
+
for (let index = 0;index < ACQUISITION_BUCKETS_SECONDS.length; index++) {
|
|
327
|
+
const bound = ACQUISITION_BUCKETS_SECONDS[index];
|
|
328
|
+
if (bound !== undefined && elapsed <= bound)
|
|
329
|
+
histogram.buckets[index] = (histogram.buckets[index] ?? 0) + 1;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
var ACQUISITION_BUCKETS_SECONDS, outcomes, metricsBrand, states;
|
|
335
|
+
var init_acquisitionMetrics = __esm(() => {
|
|
336
|
+
ACQUISITION_BUCKETS_SECONDS = [
|
|
337
|
+
0.001,
|
|
338
|
+
0.005,
|
|
339
|
+
0.01,
|
|
340
|
+
0.025,
|
|
341
|
+
0.05,
|
|
342
|
+
0.1,
|
|
343
|
+
0.25,
|
|
344
|
+
0.5,
|
|
345
|
+
1,
|
|
346
|
+
2.5,
|
|
347
|
+
5,
|
|
348
|
+
10
|
|
349
|
+
];
|
|
350
|
+
outcomes = ["acquired", "failed", "aborted"];
|
|
351
|
+
metricsBrand = Symbol("transaction acquisition metrics");
|
|
352
|
+
states = new WeakMap;
|
|
353
|
+
});
|
|
354
|
+
|
|
303
355
|
// ../../src/core/database/connection.ts
|
|
304
356
|
function createDatabaseConnection(source) {
|
|
305
357
|
return {
|
|
@@ -668,9 +720,13 @@ async function runInTransaction(operation, options = {}) {
|
|
|
668
720
|
throw new Error("Active database connection does not support transactions. Bind a client with begin() via bindDatabaseConnection.");
|
|
669
721
|
}
|
|
670
722
|
return await settleTransaction(() => runWithDeferredModelEvents(async () => {
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
723
|
+
let reserved;
|
|
724
|
+
if (signal || options.acquisitionMetrics) {
|
|
725
|
+
if (!pool.reserve)
|
|
726
|
+
throw new Error(signal ? "Active database connection does not support cancellable transaction acquisition." : "Active database connection does not support observable transaction acquisition.");
|
|
727
|
+
const reserve = pool.reserve.bind(pool);
|
|
728
|
+
reserved = options.acquisitionMetrics ? await observeTransactionAcquisition(options.acquisitionMetrics, () => reserve({ signal }), signal) : await reserve({ signal });
|
|
729
|
+
}
|
|
674
730
|
const connection = reserved ?? pool;
|
|
675
731
|
try {
|
|
676
732
|
signal?.throwIfAborted();
|
|
@@ -688,10 +744,12 @@ var transactionFrame, outerFrames;
|
|
|
688
744
|
var init_transaction = __esm(() => {
|
|
689
745
|
init_deferredModelEvents();
|
|
690
746
|
init_asyncContextStore();
|
|
747
|
+
init_acquisitionMetrics();
|
|
691
748
|
init_connection();
|
|
692
749
|
init_connectionContext();
|
|
693
750
|
init_repositoryConnection();
|
|
694
751
|
init_transactionControl();
|
|
752
|
+
init_acquisitionMetrics();
|
|
695
753
|
transactionFrame = createAsyncContextStore("@getstrata/transactionFrame");
|
|
696
754
|
outerFrames = new WeakMap;
|
|
697
755
|
});
|
|
@@ -52,6 +52,9 @@ function unwrapTransactionRollback(error, depth = 0) {
|
|
|
52
52
|
}
|
|
53
53
|
return unwrapTransactionRollback(error.cause, depth + 1);
|
|
54
54
|
}
|
|
55
|
+
function hasActiveTransaction() {
|
|
56
|
+
return hasActiveDatabaseConnection() && (transactionScopes.getStore()?.rollback.length ?? 0) > 0;
|
|
57
|
+
}
|
|
55
58
|
function requestTransactionRollback() {
|
|
56
59
|
const state = transactionScopes.getStore();
|
|
57
60
|
const index = state ? state.rollback.length - 1 : -1;
|
|
@@ -129,6 +129,9 @@ function unwrapTransactionRollback(error, depth = 0) {
|
|
|
129
129
|
}
|
|
130
130
|
return unwrapTransactionRollback(error.cause, depth + 1);
|
|
131
131
|
}
|
|
132
|
+
function hasActiveTransaction() {
|
|
133
|
+
return hasActiveDatabaseConnection() && (transactionScopes.getStore()?.rollback.length ?? 0) > 0;
|
|
134
|
+
}
|
|
132
135
|
function requestTransactionRollback() {
|
|
133
136
|
const state = transactionScopes.getStore();
|
|
134
137
|
const index = state ? state.rollback.length - 1 : -1;
|
|
@@ -344,6 +347,55 @@ var init_deferredModelEvents = __esm(() => {
|
|
|
344
347
|
deferredModelEvents = createAsyncContextStore("@getstrata/deferredModelEvents");
|
|
345
348
|
});
|
|
346
349
|
|
|
350
|
+
// ../../src/core/database/acquisitionMetrics.ts
|
|
351
|
+
async function observeTransactionAcquisition(metrics, acquire, signal) {
|
|
352
|
+
const state = states.get(metrics);
|
|
353
|
+
if (!state)
|
|
354
|
+
throw new TypeError("Transaction acquisition metrics must be created by the framework factory.");
|
|
355
|
+
const started = performance.now();
|
|
356
|
+
state.inflight++;
|
|
357
|
+
let outcome = "acquired";
|
|
358
|
+
try {
|
|
359
|
+
return await acquire();
|
|
360
|
+
} catch (error) {
|
|
361
|
+
outcome = signal?.aborted ? "aborted" : "failed";
|
|
362
|
+
throw error;
|
|
363
|
+
} finally {
|
|
364
|
+
state.inflight--;
|
|
365
|
+
const elapsed = Math.max(0, performance.now() - started) / 1000;
|
|
366
|
+
const histogram = state.outcomes[outcomes.indexOf(outcome)];
|
|
367
|
+
if (histogram) {
|
|
368
|
+
histogram.count++;
|
|
369
|
+
histogram.sumSeconds += elapsed;
|
|
370
|
+
for (let index = 0;index < ACQUISITION_BUCKETS_SECONDS.length; index++) {
|
|
371
|
+
const bound = ACQUISITION_BUCKETS_SECONDS[index];
|
|
372
|
+
if (bound !== undefined && elapsed <= bound)
|
|
373
|
+
histogram.buckets[index] = (histogram.buckets[index] ?? 0) + 1;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
var ACQUISITION_BUCKETS_SECONDS, outcomes, metricsBrand, states;
|
|
379
|
+
var init_acquisitionMetrics = __esm(() => {
|
|
380
|
+
ACQUISITION_BUCKETS_SECONDS = [
|
|
381
|
+
0.001,
|
|
382
|
+
0.005,
|
|
383
|
+
0.01,
|
|
384
|
+
0.025,
|
|
385
|
+
0.05,
|
|
386
|
+
0.1,
|
|
387
|
+
0.25,
|
|
388
|
+
0.5,
|
|
389
|
+
1,
|
|
390
|
+
2.5,
|
|
391
|
+
5,
|
|
392
|
+
10
|
|
393
|
+
];
|
|
394
|
+
outcomes = ["acquired", "failed", "aborted"];
|
|
395
|
+
metricsBrand = Symbol("transaction acquisition metrics");
|
|
396
|
+
states = new WeakMap;
|
|
397
|
+
});
|
|
398
|
+
|
|
347
399
|
// ../../src/core/database/connection.ts
|
|
348
400
|
function createDatabaseConnection(source) {
|
|
349
401
|
return {
|
|
@@ -712,9 +764,13 @@ async function runInTransaction(operation, options = {}) {
|
|
|
712
764
|
throw new Error("Active database connection does not support transactions. Bind a client with begin() via bindDatabaseConnection.");
|
|
713
765
|
}
|
|
714
766
|
return await settleTransaction(() => runWithDeferredModelEvents(async () => {
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
767
|
+
let reserved;
|
|
768
|
+
if (signal || options.acquisitionMetrics) {
|
|
769
|
+
if (!pool.reserve)
|
|
770
|
+
throw new Error(signal ? "Active database connection does not support cancellable transaction acquisition." : "Active database connection does not support observable transaction acquisition.");
|
|
771
|
+
const reserve = pool.reserve.bind(pool);
|
|
772
|
+
reserved = options.acquisitionMetrics ? await observeTransactionAcquisition(options.acquisitionMetrics, () => reserve({ signal }), signal) : await reserve({ signal });
|
|
773
|
+
}
|
|
718
774
|
const connection = reserved ?? pool;
|
|
719
775
|
try {
|
|
720
776
|
signal?.throwIfAborted();
|
|
@@ -732,10 +788,12 @@ var transactionFrame, outerFrames;
|
|
|
732
788
|
var init_transaction = __esm(() => {
|
|
733
789
|
init_deferredModelEvents();
|
|
734
790
|
init_asyncContextStore();
|
|
791
|
+
init_acquisitionMetrics();
|
|
735
792
|
init_connection();
|
|
736
793
|
init_connectionContext();
|
|
737
794
|
init_repositoryConnection();
|
|
738
795
|
init_transactionControl();
|
|
796
|
+
init_acquisitionMetrics();
|
|
739
797
|
transactionFrame = createAsyncContextStore("@getstrata/transactionFrame");
|
|
740
798
|
outerFrames = new WeakMap;
|
|
741
799
|
});
|