@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/dist/index.js
CHANGED
|
@@ -687,6 +687,111 @@ var init_deferredModelEvents = __esm(() => {
|
|
|
687
687
|
deferredModelEvents = createAsyncContextStore("@getstrata/deferredModelEvents");
|
|
688
688
|
});
|
|
689
689
|
|
|
690
|
+
// ../../src/core/database/acquisitionMetrics.ts
|
|
691
|
+
function createTransactionAcquisitionMetrics() {
|
|
692
|
+
const state = {
|
|
693
|
+
inflight: 0,
|
|
694
|
+
outcomes: outcomes.map((outcome) => ({
|
|
695
|
+
outcome,
|
|
696
|
+
count: 0,
|
|
697
|
+
sumSeconds: 0,
|
|
698
|
+
buckets: ACQUISITION_BUCKETS_SECONDS.map(() => 0)
|
|
699
|
+
}))
|
|
700
|
+
};
|
|
701
|
+
const metrics = {
|
|
702
|
+
[metricsBrand]: true,
|
|
703
|
+
snapshot: () => ({
|
|
704
|
+
inflight: state.inflight,
|
|
705
|
+
outcomes: state.outcomes.map((histogram) => ({
|
|
706
|
+
...histogram,
|
|
707
|
+
buckets: [...histogram.buckets]
|
|
708
|
+
}))
|
|
709
|
+
})
|
|
710
|
+
};
|
|
711
|
+
states.set(metrics, state);
|
|
712
|
+
return metrics;
|
|
713
|
+
}
|
|
714
|
+
async function observeTransactionAcquisition(metrics, acquire, signal) {
|
|
715
|
+
const state = states.get(metrics);
|
|
716
|
+
if (!state)
|
|
717
|
+
throw new TypeError("Transaction acquisition metrics must be created by the framework factory.");
|
|
718
|
+
const started = performance.now();
|
|
719
|
+
state.inflight++;
|
|
720
|
+
let outcome = "acquired";
|
|
721
|
+
try {
|
|
722
|
+
return await acquire();
|
|
723
|
+
} catch (error) {
|
|
724
|
+
outcome = signal?.aborted ? "aborted" : "failed";
|
|
725
|
+
throw error;
|
|
726
|
+
} finally {
|
|
727
|
+
state.inflight--;
|
|
728
|
+
const elapsed = Math.max(0, performance.now() - started) / 1000;
|
|
729
|
+
const histogram = state.outcomes[outcomes.indexOf(outcome)];
|
|
730
|
+
if (histogram) {
|
|
731
|
+
histogram.count++;
|
|
732
|
+
histogram.sumSeconds += elapsed;
|
|
733
|
+
for (let index = 0;index < ACQUISITION_BUCKETS_SECONDS.length; index++) {
|
|
734
|
+
const bound = ACQUISITION_BUCKETS_SECONDS[index];
|
|
735
|
+
if (bound !== undefined && elapsed <= bound)
|
|
736
|
+
histogram.buckets[index] = (histogram.buckets[index] ?? 0) + 1;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
function renderTransactionAcquisitionMetrics(snapshot) {
|
|
742
|
+
const invalid = () => new TypeError("Invalid transaction acquisition metrics.");
|
|
743
|
+
if (!Number.isSafeInteger(snapshot.inflight) || snapshot.inflight < 0 || snapshot.outcomes.length !== outcomes.length)
|
|
744
|
+
throw invalid();
|
|
745
|
+
const name = "strata_database_transaction_acquisition_duration_seconds";
|
|
746
|
+
const lines = [
|
|
747
|
+
"# HELP strata_database_acquisition_collector_success Whether transaction acquisition observation succeeded.",
|
|
748
|
+
"# TYPE strata_database_acquisition_collector_success gauge",
|
|
749
|
+
"strata_database_acquisition_collector_success 1",
|
|
750
|
+
"# HELP strata_database_transaction_acquisition_inflight Native transaction checkouts awaiting settlement in this process.",
|
|
751
|
+
"# TYPE strata_database_transaction_acquisition_inflight gauge",
|
|
752
|
+
`strata_database_transaction_acquisition_inflight ${snapshot.inflight}`,
|
|
753
|
+
`# HELP ${name} Native checkout duration, including pool wait and connection establishment, for explicitly observed transactions.`,
|
|
754
|
+
`# TYPE ${name} histogram`
|
|
755
|
+
];
|
|
756
|
+
for (let i = 0;i < outcomes.length; i++) {
|
|
757
|
+
const histogram = snapshot.outcomes[i];
|
|
758
|
+
if (!histogram || histogram.outcome !== outcomes[i] || !Number.isSafeInteger(histogram.count) || histogram.count < 0 || !Number.isFinite(histogram.sumSeconds) || histogram.sumSeconds < 0 || histogram.buckets.length !== ACQUISITION_BUCKETS_SECONDS.length)
|
|
759
|
+
throw invalid();
|
|
760
|
+
let previous = 0;
|
|
761
|
+
for (let index = 0;index < ACQUISITION_BUCKETS_SECONDS.length; index++) {
|
|
762
|
+
const value = histogram.buckets[index];
|
|
763
|
+
if (value === undefined || !Number.isSafeInteger(value) || value < previous || value > histogram.count)
|
|
764
|
+
throw invalid();
|
|
765
|
+
previous = value;
|
|
766
|
+
lines.push(`${name}_bucket{outcome="${histogram.outcome}",le="${ACQUISITION_BUCKETS_SECONDS[index]}"} ${value}`);
|
|
767
|
+
}
|
|
768
|
+
lines.push(`${name}_bucket{outcome="${histogram.outcome}",le="+Inf"} ${histogram.count}`, `${name}_count{outcome="${histogram.outcome}"} ${histogram.count}`, `${name}_sum{outcome="${histogram.outcome}"} ${histogram.sumSeconds}`);
|
|
769
|
+
}
|
|
770
|
+
return `${lines.join(`
|
|
771
|
+
`)}
|
|
772
|
+
`;
|
|
773
|
+
}
|
|
774
|
+
var ACQUISITION_BUCKETS_SECONDS, outcomes, metricsBrand, states;
|
|
775
|
+
var init_acquisitionMetrics = __esm(() => {
|
|
776
|
+
ACQUISITION_BUCKETS_SECONDS = [
|
|
777
|
+
0.001,
|
|
778
|
+
0.005,
|
|
779
|
+
0.01,
|
|
780
|
+
0.025,
|
|
781
|
+
0.05,
|
|
782
|
+
0.1,
|
|
783
|
+
0.25,
|
|
784
|
+
0.5,
|
|
785
|
+
1,
|
|
786
|
+
2.5,
|
|
787
|
+
5,
|
|
788
|
+
10
|
|
789
|
+
];
|
|
790
|
+
outcomes = ["acquired", "failed", "aborted"];
|
|
791
|
+
metricsBrand = Symbol("transaction acquisition metrics");
|
|
792
|
+
states = new WeakMap;
|
|
793
|
+
});
|
|
794
|
+
|
|
690
795
|
// ../../src/core/database/connection.ts
|
|
691
796
|
function createDatabaseConnection(source) {
|
|
692
797
|
return {
|
|
@@ -745,9 +850,13 @@ async function runInTransaction(operation, options = {}) {
|
|
|
745
850
|
throw new Error("Active database connection does not support transactions. Bind a client with begin() via bindDatabaseConnection.");
|
|
746
851
|
}
|
|
747
852
|
return await settleTransaction(() => runWithDeferredModelEvents(async () => {
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
853
|
+
let reserved;
|
|
854
|
+
if (signal || options.acquisitionMetrics) {
|
|
855
|
+
if (!pool.reserve)
|
|
856
|
+
throw new Error(signal ? "Active database connection does not support cancellable transaction acquisition." : "Active database connection does not support observable transaction acquisition.");
|
|
857
|
+
const reserve = pool.reserve.bind(pool);
|
|
858
|
+
reserved = options.acquisitionMetrics ? await observeTransactionAcquisition(options.acquisitionMetrics, () => reserve({ signal }), signal) : await reserve({ signal });
|
|
859
|
+
}
|
|
751
860
|
const connection = reserved ?? pool;
|
|
752
861
|
try {
|
|
753
862
|
signal?.throwIfAborted();
|
|
@@ -765,10 +874,12 @@ var transactionFrame, outerFrames;
|
|
|
765
874
|
var init_transaction = __esm(() => {
|
|
766
875
|
init_deferredModelEvents();
|
|
767
876
|
init_asyncContextStore();
|
|
877
|
+
init_acquisitionMetrics();
|
|
768
878
|
init_connection();
|
|
769
879
|
init_connectionContext();
|
|
770
880
|
init_repositoryConnection();
|
|
771
881
|
init_transactionControl();
|
|
882
|
+
init_acquisitionMetrics();
|
|
772
883
|
transactionFrame = createAsyncContextStore("@getstrata/transactionFrame");
|
|
773
884
|
outerFrames = new WeakMap;
|
|
774
885
|
});
|
|
@@ -4123,6 +4234,10 @@ var CACHE_TAGS = {
|
|
|
4123
4234
|
careers: "careers",
|
|
4124
4235
|
offers: "offers"
|
|
4125
4236
|
};
|
|
4237
|
+
|
|
4238
|
+
// ../../src/framework/public-api.ts
|
|
4239
|
+
init_acquisitionMetrics();
|
|
4240
|
+
|
|
4126
4241
|
// ../../src/core/database/baseRepository.ts
|
|
4127
4242
|
init_eventBus();
|
|
4128
4243
|
|
|
@@ -9181,10 +9296,75 @@ init_databaseTenantContext();
|
|
|
9181
9296
|
init_tenantContext();
|
|
9182
9297
|
init_tenantDatabaseScope();
|
|
9183
9298
|
|
|
9184
|
-
// ../../src/core/
|
|
9299
|
+
// ../../src/core/database/postgresReadOnlyCollector.ts
|
|
9300
|
+
init_connectionContext();
|
|
9185
9301
|
init_dialect();
|
|
9186
9302
|
init_repositoryConnection();
|
|
9187
9303
|
init_transaction();
|
|
9304
|
+
function createPostgresReadOnlyCollector(name, timeoutMs, collect) {
|
|
9305
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 5000)
|
|
9306
|
+
throw new TypeError(`${name} timeout must be between 1 and 5000 milliseconds.`);
|
|
9307
|
+
let active;
|
|
9308
|
+
let closed = false;
|
|
9309
|
+
return {
|
|
9310
|
+
async collect() {
|
|
9311
|
+
if (closed)
|
|
9312
|
+
throw new Error(`${name} collector is closed.`);
|
|
9313
|
+
if (hasActiveTransaction() || hasActiveDatabaseConnection())
|
|
9314
|
+
throw new Error(`${name} require an independent transaction.`);
|
|
9315
|
+
if (currentSqlDialect().driver !== "pgsql")
|
|
9316
|
+
throw new Error(`${name} currently require Postgres.`);
|
|
9317
|
+
if (active)
|
|
9318
|
+
throw new Error(`${name} collection is still settling.`);
|
|
9319
|
+
const deadline = performance.now() + timeoutMs;
|
|
9320
|
+
let expired = false;
|
|
9321
|
+
const acquisition = new AbortController;
|
|
9322
|
+
let timer;
|
|
9323
|
+
const remaining = () => {
|
|
9324
|
+
const value = Math.floor(deadline - performance.now());
|
|
9325
|
+
if (expired || value < 1)
|
|
9326
|
+
throw new Error(`${name} collection timed out.`);
|
|
9327
|
+
return value;
|
|
9328
|
+
};
|
|
9329
|
+
const work = runInTransaction(async () => {
|
|
9330
|
+
await repositoryConnection.unsafe("SELECT set_config('statement_timeout', $1, true)", [`${remaining()}ms`]);
|
|
9331
|
+
await repositoryConnection.unsafe("SET TRANSACTION READ ONLY");
|
|
9332
|
+
remaining();
|
|
9333
|
+
const result = await collect(remaining);
|
|
9334
|
+
remaining();
|
|
9335
|
+
return result;
|
|
9336
|
+
}, { acquisitionSignal: acquisition.signal });
|
|
9337
|
+
active = work;
|
|
9338
|
+
work.finally(() => {
|
|
9339
|
+
if (active === work)
|
|
9340
|
+
active = undefined;
|
|
9341
|
+
}).catch(() => {});
|
|
9342
|
+
try {
|
|
9343
|
+
return await Promise.race([
|
|
9344
|
+
work,
|
|
9345
|
+
new Promise((_, reject) => {
|
|
9346
|
+
timer = setTimeout(() => {
|
|
9347
|
+
expired = true;
|
|
9348
|
+
const error = new Error(`${name} collection timed out.`);
|
|
9349
|
+
acquisition.abort(error);
|
|
9350
|
+
reject(error);
|
|
9351
|
+
}, timeoutMs);
|
|
9352
|
+
})
|
|
9353
|
+
]);
|
|
9354
|
+
} finally {
|
|
9355
|
+
if (timer !== undefined)
|
|
9356
|
+
clearTimeout(timer);
|
|
9357
|
+
}
|
|
9358
|
+
},
|
|
9359
|
+
async close() {
|
|
9360
|
+
closed = true;
|
|
9361
|
+
await active?.catch(() => {});
|
|
9362
|
+
}
|
|
9363
|
+
};
|
|
9364
|
+
}
|
|
9365
|
+
|
|
9366
|
+
// ../../src/core/events/outbox/metrics.ts
|
|
9367
|
+
init_repositoryConnection();
|
|
9188
9368
|
init_databaseTenantContext();
|
|
9189
9369
|
var specifications = [
|
|
9190
9370
|
{ state: "pending_due", status: "pending", column: "available_at", predicate: "<=", age: true },
|
|
@@ -9234,84 +9414,29 @@ UNION ALL
|
|
|
9234
9414
|
function createOutboxMetricsCollector(options = {}) {
|
|
9235
9415
|
const timeoutMs = options.timeoutMs ?? 1000;
|
|
9236
9416
|
const sampleLimit = options.sampleLimit ?? 500;
|
|
9237
|
-
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 5000)
|
|
9238
|
-
throw new TypeError("Outbox metrics timeout must be between 1 and 5000 milliseconds.");
|
|
9239
9417
|
if (!Number.isSafeInteger(sampleLimit) || sampleLimit < 1 || sampleLimit > 1000)
|
|
9240
9418
|
throw new TypeError("Outbox metrics sample limit must be between 1 and 1000.");
|
|
9241
|
-
|
|
9242
|
-
|
|
9243
|
-
|
|
9244
|
-
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
|
|
9249
|
-
|
|
9250
|
-
|
|
9251
|
-
if (
|
|
9252
|
-
throw new Error("
|
|
9253
|
-
|
|
9254
|
-
|
|
9255
|
-
|
|
9256
|
-
|
|
9257
|
-
|
|
9258
|
-
const value = Math.floor(deadline - performance.now());
|
|
9259
|
-
if (expired || value < 1)
|
|
9260
|
-
throw new Error("Outbox metrics collection timed out.");
|
|
9261
|
-
return value;
|
|
9419
|
+
return createPostgresReadOnlyCollector("Outbox metrics", timeoutMs, (remaining) => runWithMigrationBypass(async () => {
|
|
9420
|
+
remaining();
|
|
9421
|
+
const rows = await repositoryConnection.unsafe(snapshotQuery, [sampleLimit + 1]);
|
|
9422
|
+
remaining();
|
|
9423
|
+
if (rows.length !== specifications.length)
|
|
9424
|
+
throw new Error("Invalid outbox metrics response.");
|
|
9425
|
+
const states = specifications.map((spec) => {
|
|
9426
|
+
const row = rows.find((value) => value.state === spec.state);
|
|
9427
|
+
const count = Number(row?.count);
|
|
9428
|
+
const lateness = row?.lateness_ms == null ? null : Number(row.lateness_ms);
|
|
9429
|
+
if (!Number.isSafeInteger(count) || count < 0 || count > sampleLimit + 1 || spec.age && (lateness === null || !Number.isFinite(lateness) || lateness < 0))
|
|
9430
|
+
throw new Error("Invalid outbox metrics values.");
|
|
9431
|
+
return {
|
|
9432
|
+
state: spec.state,
|
|
9433
|
+
count: Math.min(count, sampleLimit),
|
|
9434
|
+
capped: count > sampleLimit,
|
|
9435
|
+
actionableLatenessSeconds: lateness === null ? null : lateness / 1000
|
|
9262
9436
|
};
|
|
9263
|
-
|
|
9264
|
-
|
|
9265
|
-
|
|
9266
|
-
return runWithMigrationBypass(async () => {
|
|
9267
|
-
remaining();
|
|
9268
|
-
const rows = await repositoryConnection.unsafe(snapshotQuery, [sampleLimit + 1]);
|
|
9269
|
-
remaining();
|
|
9270
|
-
if (rows.length !== specifications.length)
|
|
9271
|
-
throw new Error("Invalid outbox metrics response.");
|
|
9272
|
-
const states = specifications.map((spec) => {
|
|
9273
|
-
const row = rows.find((value) => value.state === spec.state);
|
|
9274
|
-
const count = Number(row?.count);
|
|
9275
|
-
const lateness = row?.lateness_ms == null ? null : Number(row.lateness_ms);
|
|
9276
|
-
if (!Number.isSafeInteger(count) || count < 0 || count > sampleLimit + 1 || spec.age && (lateness === null || !Number.isFinite(lateness) || lateness < 0))
|
|
9277
|
-
throw new Error("Invalid outbox metrics values.");
|
|
9278
|
-
return {
|
|
9279
|
-
state: spec.state,
|
|
9280
|
-
count: Math.min(count, sampleLimit),
|
|
9281
|
-
capped: count > sampleLimit,
|
|
9282
|
-
actionableLatenessSeconds: lateness === null ? null : lateness / 1000
|
|
9283
|
-
};
|
|
9284
|
-
});
|
|
9285
|
-
return { sampleLimit, states };
|
|
9286
|
-
});
|
|
9287
|
-
}, { acquisitionSignal: acquisition.signal });
|
|
9288
|
-
active = work;
|
|
9289
|
-
work.finally(() => {
|
|
9290
|
-
if (active === work)
|
|
9291
|
-
active = undefined;
|
|
9292
|
-
}).catch(() => {});
|
|
9293
|
-
try {
|
|
9294
|
-
return await Promise.race([
|
|
9295
|
-
work,
|
|
9296
|
-
new Promise((_, reject) => {
|
|
9297
|
-
timer = setTimeout(() => {
|
|
9298
|
-
expired = true;
|
|
9299
|
-
const error = new Error("Outbox metrics collection timed out.");
|
|
9300
|
-
acquisition.abort(error);
|
|
9301
|
-
reject(error);
|
|
9302
|
-
}, timeoutMs);
|
|
9303
|
-
})
|
|
9304
|
-
]);
|
|
9305
|
-
} finally {
|
|
9306
|
-
if (timer !== undefined)
|
|
9307
|
-
clearTimeout(timer);
|
|
9308
|
-
}
|
|
9309
|
-
},
|
|
9310
|
-
async close() {
|
|
9311
|
-
closed = true;
|
|
9312
|
-
await active?.catch(() => {});
|
|
9313
|
-
}
|
|
9314
|
-
};
|
|
9437
|
+
});
|
|
9438
|
+
return { sampleLimit, states };
|
|
9439
|
+
}));
|
|
9315
9440
|
}
|
|
9316
9441
|
function renderOutboxMetrics(snapshot) {
|
|
9317
9442
|
if (!Number.isSafeInteger(snapshot.sampleLimit) || snapshot.sampleLimit < 1 || snapshot.sampleLimit > 1000 || snapshot.states.length !== specifications.length)
|
|
@@ -12876,6 +13001,67 @@ class Notification {
|
|
|
12876
13001
|
return null;
|
|
12877
13002
|
}
|
|
12878
13003
|
}
|
|
13004
|
+
// ../../src/core/queue/failedJobMetrics.ts
|
|
13005
|
+
init_repositoryConnection();
|
|
13006
|
+
|
|
13007
|
+
// ../../src/core/queue/failedJobTable.ts
|
|
13008
|
+
var failedJobTable = defineTable({
|
|
13009
|
+
name: "failed_job",
|
|
13010
|
+
primaryKey: "id",
|
|
13011
|
+
columns: ["id", "job_name", "job_id", "payload", "exception", "failed_at"],
|
|
13012
|
+
defaultOrderBy: { column: "failed_at", direction: "DESC" }
|
|
13013
|
+
});
|
|
13014
|
+
|
|
13015
|
+
// ../../src/core/queue/failedJobRepository.ts
|
|
13016
|
+
class FailedJobRepository extends BaseRepository {
|
|
13017
|
+
constructor() {
|
|
13018
|
+
super(failedJobTable);
|
|
13019
|
+
}
|
|
13020
|
+
}
|
|
13021
|
+
var failedJobRepository_default = FailedJobRepository;
|
|
13022
|
+
|
|
13023
|
+
// ../../src/core/queue/failedJobMetrics.ts
|
|
13024
|
+
var schemaQuery = `SELECT c.relkind = 'r' AND c.relpersistence = 'p' AND NOT c.relrowsecurity AND EXISTS (
|
|
13025
|
+
SELECT 1 FROM pg_index i JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = i.indkey[0]
|
|
13026
|
+
WHERE i.indrelid = c.oid AND i.indisprimary AND i.indisvalid AND i.indisready
|
|
13027
|
+
AND i.indnkeyatts = 1 AND a.attname = 'id' AND NOT a.attisdropped
|
|
13028
|
+
AND a.atttypid IN ('int2'::regtype, 'int4'::regtype, 'int8'::regtype)
|
|
13029
|
+
) AS supported FROM pg_class c WHERE c.oid = 'failed_job'::regclass`;
|
|
13030
|
+
function createFailedJobMetricsCollector(options = {}) {
|
|
13031
|
+
const sampleLimit = options.sampleLimit ?? 500;
|
|
13032
|
+
if (!Number.isSafeInteger(sampleLimit) || sampleLimit < 1 || sampleLimit > 1000)
|
|
13033
|
+
throw new TypeError("Failed-job metrics sample limit must be between 1 and 1000.");
|
|
13034
|
+
const repository = new failedJobRepository_default;
|
|
13035
|
+
return createPostgresReadOnlyCollector("Failed-job metrics", options.timeoutMs ?? 1000, async (remaining) => {
|
|
13036
|
+
await repositoryConnection.unsafe('LOCK TABLE "failed_job" IN ACCESS SHARE MODE');
|
|
13037
|
+
const schema = await repositoryConnection.unsafe(schemaQuery);
|
|
13038
|
+
if (schema.length !== 1 || schema[0]?.supported !== true)
|
|
13039
|
+
throw new Error("Failed-job metrics require the standard global table with an ID primary key and no RLS.");
|
|
13040
|
+
remaining();
|
|
13041
|
+
const rows = await repository.query().orderBy({ column: "id", direction: "ASC" }).project(["id"], sampleLimit + 1);
|
|
13042
|
+
if (rows.length > sampleLimit + 1 || rows.some((row) => row.id == null))
|
|
13043
|
+
throw new Error("Invalid failed-job metrics response.");
|
|
13044
|
+
return {
|
|
13045
|
+
sampleLimit,
|
|
13046
|
+
count: Math.min(rows.length, sampleLimit),
|
|
13047
|
+
capped: rows.length > sampleLimit
|
|
13048
|
+
};
|
|
13049
|
+
});
|
|
13050
|
+
}
|
|
13051
|
+
function renderFailedJobMetrics(snapshot) {
|
|
13052
|
+
if (!Number.isSafeInteger(snapshot.sampleLimit) || snapshot.sampleLimit < 1 || snapshot.sampleLimit > 1000 || !Number.isSafeInteger(snapshot.count) || snapshot.count < 0 || snapshot.count > snapshot.sampleLimit || typeof snapshot.capped !== "boolean" || snapshot.capped && snapshot.count !== snapshot.sampleLimit)
|
|
13053
|
+
throw new TypeError("Invalid failed-job metrics snapshot.");
|
|
13054
|
+
return `# HELP strata_queue_failed_job_collector_success Whether failed-job collection succeeded.
|
|
13055
|
+
# TYPE strata_queue_failed_job_collector_success gauge
|
|
13056
|
+
strata_queue_failed_job_collector_success 1
|
|
13057
|
+
# HELP strata_queue_failed_jobs_sample Capped retained failed-job count; a lower bound when capped.
|
|
13058
|
+
# TYPE strata_queue_failed_jobs_sample gauge
|
|
13059
|
+
strata_queue_failed_jobs_sample ${snapshot.count}
|
|
13060
|
+
# HELP strata_queue_failed_jobs_sample_capped Whether the failed-job sample is truncated.
|
|
13061
|
+
# TYPE strata_queue_failed_jobs_sample_capped gauge
|
|
13062
|
+
strata_queue_failed_jobs_sample_capped ${Number(snapshot.capped)}
|
|
13063
|
+
`;
|
|
13064
|
+
}
|
|
12879
13065
|
// ../../src/core/queue/jobRegistry.ts
|
|
12880
13066
|
class JobRegistry {
|
|
12881
13067
|
factories = new Map;
|
|
@@ -12970,22 +13156,6 @@ class AsyncQueue {
|
|
|
12970
13156
|
function createQueue(driver) {
|
|
12971
13157
|
return driver === "async" ? new AsyncQueue : new SyncQueue;
|
|
12972
13158
|
}
|
|
12973
|
-
// ../../src/core/queue/failedJobTable.ts
|
|
12974
|
-
var failedJobTable = defineTable({
|
|
12975
|
-
name: "failed_job",
|
|
12976
|
-
primaryKey: "id",
|
|
12977
|
-
columns: ["id", "job_name", "job_id", "payload", "exception", "failed_at"],
|
|
12978
|
-
defaultOrderBy: { column: "failed_at", direction: "DESC" }
|
|
12979
|
-
});
|
|
12980
|
-
|
|
12981
|
-
// ../../src/core/queue/failedJobRepository.ts
|
|
12982
|
-
class FailedJobRepository extends BaseRepository {
|
|
12983
|
-
constructor() {
|
|
12984
|
-
super(failedJobTable);
|
|
12985
|
-
}
|
|
12986
|
-
}
|
|
12987
|
-
var failedJobRepository_default = FailedJobRepository;
|
|
12988
|
-
|
|
12989
13159
|
// ../../src/core/queue/failedJobService.ts
|
|
12990
13160
|
class FailedJobService {
|
|
12991
13161
|
repository;
|
|
@@ -14688,6 +14858,171 @@ function createTenantMiddleware(options = {}) {
|
|
|
14688
14858
|
}
|
|
14689
14859
|
};
|
|
14690
14860
|
}
|
|
14861
|
+
// ../../src/core/tracing/health.ts
|
|
14862
|
+
import {
|
|
14863
|
+
createNoopMeter
|
|
14864
|
+
} from "@opentelemetry/api";
|
|
14865
|
+
import { ExportResultCode } from "@opentelemetry/core";
|
|
14866
|
+
function observeExportDeadline(state, timeoutMillis) {
|
|
14867
|
+
const deadline = setTimeout(() => {
|
|
14868
|
+
state.exportDeadlineExceededBatches++;
|
|
14869
|
+
}, timeoutMillis);
|
|
14870
|
+
deadline.unref();
|
|
14871
|
+
return deadline;
|
|
14872
|
+
}
|
|
14873
|
+
function createTracingHealth(enabled, exporterConfigured, timeoutMillis) {
|
|
14874
|
+
const state = {
|
|
14875
|
+
enabled,
|
|
14876
|
+
exporterConfigured,
|
|
14877
|
+
active: true,
|
|
14878
|
+
queueSize: null,
|
|
14879
|
+
queueCapacity: null,
|
|
14880
|
+
droppedSpans: 0,
|
|
14881
|
+
exportedBatches: 0,
|
|
14882
|
+
failedBatches: 0,
|
|
14883
|
+
exportedSpans: 0,
|
|
14884
|
+
failedSpans: 0,
|
|
14885
|
+
exportDeadlineExceededBatches: 0,
|
|
14886
|
+
flushFailures: 0,
|
|
14887
|
+
shutdownFailures: 0
|
|
14888
|
+
};
|
|
14889
|
+
let exportQueueFullSpans = 0;
|
|
14890
|
+
let queueCallback;
|
|
14891
|
+
const noop = createNoopMeter();
|
|
14892
|
+
const meter = {
|
|
14893
|
+
createGauge: noop.createGauge.bind(noop),
|
|
14894
|
+
createHistogram: noop.createHistogram.bind(noop),
|
|
14895
|
+
createObservableGauge: noop.createObservableGauge.bind(noop),
|
|
14896
|
+
createObservableCounter: noop.createObservableCounter.bind(noop),
|
|
14897
|
+
addBatchObservableCallback: noop.addBatchObservableCallback.bind(noop),
|
|
14898
|
+
removeBatchObservableCallback: noop.removeBatchObservableCallback.bind(noop),
|
|
14899
|
+
createCounter(name, options) {
|
|
14900
|
+
if (name !== "otel.sdk.processor.span.processed")
|
|
14901
|
+
return noop.createCounter(name, options);
|
|
14902
|
+
return {
|
|
14903
|
+
add(value, attributes) {
|
|
14904
|
+
if (attributes?.["error.type"] === "queue_full")
|
|
14905
|
+
state.droppedSpans += value;
|
|
14906
|
+
}
|
|
14907
|
+
};
|
|
14908
|
+
},
|
|
14909
|
+
createUpDownCounter(name, options) {
|
|
14910
|
+
if (name !== "otel.sdk.processor.span.queue.capacity")
|
|
14911
|
+
return noop.createUpDownCounter(name, options);
|
|
14912
|
+
return {
|
|
14913
|
+
add(value) {
|
|
14914
|
+
state.queueCapacity = (state.queueCapacity ?? 0) + value;
|
|
14915
|
+
}
|
|
14916
|
+
};
|
|
14917
|
+
},
|
|
14918
|
+
createObservableUpDownCounter(name, options) {
|
|
14919
|
+
if (name !== "otel.sdk.processor.span.queue.size")
|
|
14920
|
+
return noop.createObservableUpDownCounter(name, options);
|
|
14921
|
+
return {
|
|
14922
|
+
addCallback(callback) {
|
|
14923
|
+
queueCallback = callback;
|
|
14924
|
+
},
|
|
14925
|
+
removeCallback(callback) {
|
|
14926
|
+
if (queueCallback === callback)
|
|
14927
|
+
queueCallback = undefined;
|
|
14928
|
+
}
|
|
14929
|
+
};
|
|
14930
|
+
}
|
|
14931
|
+
};
|
|
14932
|
+
const meterProvider = { getMeter: () => meter };
|
|
14933
|
+
return {
|
|
14934
|
+
state,
|
|
14935
|
+
meterProvider,
|
|
14936
|
+
snapshot() {
|
|
14937
|
+
let queueSize = null;
|
|
14938
|
+
queueCallback?.({
|
|
14939
|
+
observe(value) {
|
|
14940
|
+
queueSize = value;
|
|
14941
|
+
}
|
|
14942
|
+
});
|
|
14943
|
+
return {
|
|
14944
|
+
...state,
|
|
14945
|
+
queueSize,
|
|
14946
|
+
droppedSpans: Math.max(0, state.droppedSpans - exportQueueFullSpans)
|
|
14947
|
+
};
|
|
14948
|
+
},
|
|
14949
|
+
wrap(exporter) {
|
|
14950
|
+
return {
|
|
14951
|
+
export(spans, callback) {
|
|
14952
|
+
const count = spans.length;
|
|
14953
|
+
let completed = false;
|
|
14954
|
+
const deadline = observeExportDeadline(state, timeoutMillis);
|
|
14955
|
+
const finish = (success) => {
|
|
14956
|
+
completed = true;
|
|
14957
|
+
clearTimeout(deadline);
|
|
14958
|
+
if (success) {
|
|
14959
|
+
state.exportedBatches++;
|
|
14960
|
+
state.exportedSpans += count;
|
|
14961
|
+
} else {
|
|
14962
|
+
state.failedBatches++;
|
|
14963
|
+
state.failedSpans += count;
|
|
14964
|
+
}
|
|
14965
|
+
};
|
|
14966
|
+
try {
|
|
14967
|
+
exporter.export(spans, (result) => {
|
|
14968
|
+
if (completed)
|
|
14969
|
+
return;
|
|
14970
|
+
finish(result.code === ExportResultCode.SUCCESS);
|
|
14971
|
+
if (result.error?.name === "queue_full")
|
|
14972
|
+
exportQueueFullSpans += count;
|
|
14973
|
+
callback(result);
|
|
14974
|
+
});
|
|
14975
|
+
} catch (error) {
|
|
14976
|
+
if (!completed)
|
|
14977
|
+
finish(false);
|
|
14978
|
+
throw error;
|
|
14979
|
+
}
|
|
14980
|
+
},
|
|
14981
|
+
shutdown: () => exporter.shutdown(),
|
|
14982
|
+
...exporter.forceFlush ? { forceFlush: exporter.forceFlush.bind(exporter) } : {}
|
|
14983
|
+
};
|
|
14984
|
+
}
|
|
14985
|
+
};
|
|
14986
|
+
}
|
|
14987
|
+
function renderTracingMetrics(snapshot) {
|
|
14988
|
+
for (const key of ["enabled", "exporterConfigured", "active"])
|
|
14989
|
+
if (typeof snapshot[key] !== "boolean")
|
|
14990
|
+
throw new TypeError("Invalid tracing metrics.");
|
|
14991
|
+
const values = {
|
|
14992
|
+
strata_tracing_enabled: Number(snapshot.enabled),
|
|
14993
|
+
strata_tracing_exporter_configured: Number(snapshot.exporterConfigured),
|
|
14994
|
+
strata_tracing_active: Number(snapshot.active),
|
|
14995
|
+
strata_tracing_queue_size: snapshot.queueSize,
|
|
14996
|
+
strata_tracing_queue_capacity: snapshot.queueCapacity,
|
|
14997
|
+
strata_tracing_dropped_spans_total: snapshot.droppedSpans,
|
|
14998
|
+
strata_tracing_exported_batches_total: snapshot.exportedBatches,
|
|
14999
|
+
strata_tracing_failed_batches_total: snapshot.failedBatches,
|
|
15000
|
+
strata_tracing_exported_spans_total: snapshot.exportedSpans,
|
|
15001
|
+
strata_tracing_failed_spans_total: snapshot.failedSpans,
|
|
15002
|
+
strata_tracing_export_deadline_exceeded_batches_total: snapshot.exportDeadlineExceededBatches,
|
|
15003
|
+
strata_tracing_flush_failures_total: snapshot.flushFailures,
|
|
15004
|
+
strata_tracing_shutdown_failures_total: snapshot.shutdownFailures
|
|
15005
|
+
};
|
|
15006
|
+
let output = `# HELP strata_tracing_collector_success Whether tracing observation succeeded.
|
|
15007
|
+
# TYPE strata_tracing_collector_success gauge
|
|
15008
|
+
strata_tracing_collector_success 1
|
|
15009
|
+
`;
|
|
15010
|
+
for (const [name, value] of Object.entries(values)) {
|
|
15011
|
+
if (value === null) {
|
|
15012
|
+
if (name === "strata_tracing_queue_size" || name === "strata_tracing_queue_capacity")
|
|
15013
|
+
continue;
|
|
15014
|
+
throw new TypeError("Invalid tracing metrics.");
|
|
15015
|
+
}
|
|
15016
|
+
if (!Number.isSafeInteger(value) || value < 0)
|
|
15017
|
+
throw new TypeError("Invalid tracing metrics.");
|
|
15018
|
+
const type = name.endsWith("_total") ? "counter" : "gauge";
|
|
15019
|
+
output += `# HELP ${name} Process-local tracing ${name.slice(15).replaceAll("_", " ")}.
|
|
15020
|
+
# TYPE ${name} ${type}
|
|
15021
|
+
${name} ${value}
|
|
15022
|
+
`;
|
|
15023
|
+
}
|
|
15024
|
+
return output;
|
|
15025
|
+
}
|
|
14691
15026
|
// ../../src/core/tracing/traceContext.ts
|
|
14692
15027
|
init_asyncContextStore();
|
|
14693
15028
|
var traceContextStorage = createAsyncContextStore("@getstrata/traceContext");
|
|
@@ -14706,10 +15041,10 @@ import { AsyncLocalStorageContextManager } from "@opentelemetry/context-async-ho
|
|
|
14706
15041
|
import { W3CTraceContextPropagator } from "@opentelemetry/core";
|
|
14707
15042
|
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
14708
15043
|
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
15044
|
+
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace";
|
|
14709
15045
|
import {
|
|
14710
15046
|
AlwaysOffSampler,
|
|
14711
15047
|
AlwaysOnSampler,
|
|
14712
|
-
BatchSpanProcessor,
|
|
14713
15048
|
ParentBasedSampler,
|
|
14714
15049
|
TraceIdRatioBasedSampler
|
|
14715
15050
|
} from "@opentelemetry/sdk-trace-base";
|
|
@@ -14763,11 +15098,14 @@ function createTracingRuntime(options = {}) {
|
|
|
14763
15098
|
throw new TypeError("Invalid tracing collector endpoint.");
|
|
14764
15099
|
}
|
|
14765
15100
|
const exporter = disabled ? undefined : options.exporter ?? (endpoint ? new OTLPTraceExporter({ url: endpoint, timeoutMillis: timeout, concurrencyLimit: 1 }) : undefined);
|
|
15101
|
+
const health = createTracingHealth(!disabled, exporter !== undefined, timeout);
|
|
14766
15102
|
const provider = new NodeTracerProvider({
|
|
14767
15103
|
resource: resourceFromAttributes({ "service.name": options.serviceName ?? otelServiceName() }),
|
|
14768
15104
|
sampler,
|
|
14769
15105
|
spanProcessors: exporter ? [
|
|
14770
|
-
new BatchSpanProcessor(
|
|
15106
|
+
new BatchSpanProcessor({
|
|
15107
|
+
exporter: health.wrap(exporter),
|
|
15108
|
+
selfObsMeterProvider: health.meterProvider,
|
|
14771
15109
|
maxQueueSize: queueSize,
|
|
14772
15110
|
maxExportBatchSize: batchSize,
|
|
14773
15111
|
scheduledDelayMillis: delay,
|
|
@@ -14787,8 +15125,21 @@ function createTracingRuntime(options = {}) {
|
|
|
14787
15125
|
tracer: provider.getTracer("@getstrata/http"),
|
|
14788
15126
|
propagator: new W3CTraceContextPropagator,
|
|
14789
15127
|
run: (parent, callback) => context.with(parent, callback),
|
|
14790
|
-
|
|
14791
|
-
|
|
15128
|
+
metrics: () => health.snapshot(),
|
|
15129
|
+
async forceFlush() {
|
|
15130
|
+
try {
|
|
15131
|
+
await provider.forceFlush();
|
|
15132
|
+
} catch (error) {
|
|
15133
|
+
health.state.flushFailures++;
|
|
15134
|
+
throw error;
|
|
15135
|
+
}
|
|
15136
|
+
},
|
|
15137
|
+
shutdown: () => shutdown ??= provider.shutdown().catch((error) => {
|
|
15138
|
+
health.state.shutdownFailures++;
|
|
15139
|
+
throw error;
|
|
15140
|
+
}).finally(() => {
|
|
15141
|
+
health.state.active = false;
|
|
15142
|
+
})
|
|
14792
15143
|
};
|
|
14793
15144
|
}
|
|
14794
15145
|
var runtimeKey = Symbol.for("@getstrata/otel-runtime");
|
|
@@ -15408,6 +15759,7 @@ export {
|
|
|
15408
15759
|
createCsrfTokenCookie,
|
|
15409
15760
|
createDatabaseConnection,
|
|
15410
15761
|
createDatabaseQueryProxy,
|
|
15762
|
+
createFailedJobMetricsCollector,
|
|
15411
15763
|
createFailedJobService,
|
|
15412
15764
|
createFlashMiddleware,
|
|
15413
15765
|
createLoginThrottleMiddleware,
|
|
@@ -15444,6 +15796,7 @@ export {
|
|
|
15444
15796
|
createTracingMiddleware,
|
|
15445
15797
|
createTracingRuntime,
|
|
15446
15798
|
createTrackedJob,
|
|
15799
|
+
createTransactionAcquisitionMetrics,
|
|
15447
15800
|
createValidateSignatureMiddleware,
|
|
15448
15801
|
createdResponse,
|
|
15449
15802
|
currentAuthUser,
|
|
@@ -15592,9 +15945,12 @@ export {
|
|
|
15592
15945
|
registerModelRepository,
|
|
15593
15946
|
registerNamedConnection,
|
|
15594
15947
|
registerShutdownHandler,
|
|
15948
|
+
renderFailedJobMetrics,
|
|
15595
15949
|
renderKernelErrorChrome,
|
|
15596
15950
|
renderMarkdownMail,
|
|
15597
15951
|
renderOutboxMetrics,
|
|
15952
|
+
renderTracingMetrics,
|
|
15953
|
+
renderTransactionAcquisitionMetrics,
|
|
15598
15954
|
renderWebErrorHtml,
|
|
15599
15955
|
repositoryConnection,
|
|
15600
15956
|
requestIdMiddleware,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getstrata/core",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.10",
|
|
4
4
|
"description": "Strata Bun framework public API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -970,6 +970,7 @@
|
|
|
970
970
|
"@opentelemetry/core": "2.12.0",
|
|
971
971
|
"@opentelemetry/exporter-trace-otlp-http": "0.223.0",
|
|
972
972
|
"@opentelemetry/resources": "2.12.0",
|
|
973
|
+
"@opentelemetry/sdk-trace": "2.12.0",
|
|
973
974
|
"@opentelemetry/sdk-trace-base": "2.12.0",
|
|
974
975
|
"@opentelemetry/sdk-trace-node": "2.12.0"
|
|
975
976
|
}
|