tempest-express-sdk 0.7.0 → 0.9.0
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/README.md +5 -4
- package/dist/chunk-4QZZGHGV.js +6 -0
- package/dist/{chunk-PKUUVK7K.js.map → chunk-4QZZGHGV.js.map} +1 -1
- package/dist/cli.cjs +2 -2
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +218 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +161 -7
- package/dist/index.d.ts +161 -7
- package/dist/index.js +216 -3
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-PKUUVK7K.js +0 -6
package/dist/index.cjs
CHANGED
|
@@ -5,7 +5,9 @@ var zodToOpenapi = require('@asteasolutions/zod-to-openapi');
|
|
|
5
5
|
var zod = require('zod');
|
|
6
6
|
var tempestDbJs = require('tempest-db-js');
|
|
7
7
|
var crypto = require('crypto');
|
|
8
|
+
var child_process = require('child_process');
|
|
8
9
|
var os = require('os');
|
|
10
|
+
var util = require('util');
|
|
9
11
|
var promises = require('fs/promises');
|
|
10
12
|
var path = require('path');
|
|
11
13
|
var express2 = require('express');
|
|
@@ -7011,6 +7013,7 @@ var HTTPClient = class {
|
|
|
7011
7013
|
return this.request("DELETE", url, init);
|
|
7012
7014
|
}
|
|
7013
7015
|
};
|
|
7016
|
+
var execFileAsync = util.promisify(child_process.execFile);
|
|
7014
7017
|
function readCpu() {
|
|
7015
7018
|
const cores = os.cpus().length;
|
|
7016
7019
|
const load1 = os.loadavg()[0] ?? 0;
|
|
@@ -7039,7 +7042,27 @@ function readSystem() {
|
|
|
7039
7042
|
uptimeSeconds: process.uptime()
|
|
7040
7043
|
};
|
|
7041
7044
|
}
|
|
7042
|
-
function
|
|
7045
|
+
async function readGpus() {
|
|
7046
|
+
try {
|
|
7047
|
+
const { stdout } = await execFileAsync("nvidia-smi", [
|
|
7048
|
+
"--query-gpu=index,utilization.gpu,memory.used,memory.total,temperature.gpu",
|
|
7049
|
+
"--format=csv,noheader,nounits"
|
|
7050
|
+
]);
|
|
7051
|
+
return stdout.trim().split("\n").filter((line) => line.trim().length > 0).map((line) => {
|
|
7052
|
+
const [index, util, used, total, temp] = line.split(",").map((v) => Number(v.trim()));
|
|
7053
|
+
return {
|
|
7054
|
+
index: index ?? 0,
|
|
7055
|
+
utilizationPercent: util ?? 0,
|
|
7056
|
+
memoryUsedMb: used ?? 0,
|
|
7057
|
+
memoryTotalMb: total ?? 0,
|
|
7058
|
+
temperatureC: temp ?? 0
|
|
7059
|
+
};
|
|
7060
|
+
});
|
|
7061
|
+
} catch {
|
|
7062
|
+
return [];
|
|
7063
|
+
}
|
|
7064
|
+
}
|
|
7065
|
+
function toPrometheus(snapshot = readSystem(), gpus = []) {
|
|
7043
7066
|
const lines = [
|
|
7044
7067
|
"# HELP process_cpu_load_percent 1-minute load average as percent of cores",
|
|
7045
7068
|
"# TYPE process_cpu_load_percent gauge",
|
|
@@ -7054,6 +7077,29 @@ function toPrometheus(snapshot = readSystem()) {
|
|
|
7054
7077
|
"# TYPE process_uptime_seconds counter",
|
|
7055
7078
|
`process_uptime_seconds ${snapshot.uptimeSeconds}`
|
|
7056
7079
|
];
|
|
7080
|
+
if (gpus.length > 0) {
|
|
7081
|
+
lines.push(
|
|
7082
|
+
"# HELP gpu_utilization_percent GPU utilization percent",
|
|
7083
|
+
"# TYPE gpu_utilization_percent gauge"
|
|
7084
|
+
);
|
|
7085
|
+
for (const gpu of gpus) {
|
|
7086
|
+
lines.push(`gpu_utilization_percent{gpu="${gpu.index}"} ${gpu.utilizationPercent}`);
|
|
7087
|
+
}
|
|
7088
|
+
lines.push(
|
|
7089
|
+
"# HELP gpu_memory_used_mb GPU memory used in MiB",
|
|
7090
|
+
"# TYPE gpu_memory_used_mb gauge"
|
|
7091
|
+
);
|
|
7092
|
+
for (const gpu of gpus) {
|
|
7093
|
+
lines.push(`gpu_memory_used_mb{gpu="${gpu.index}"} ${gpu.memoryUsedMb}`);
|
|
7094
|
+
}
|
|
7095
|
+
lines.push(
|
|
7096
|
+
"# HELP gpu_temperature_celsius GPU core temperature",
|
|
7097
|
+
"# TYPE gpu_temperature_celsius gauge"
|
|
7098
|
+
);
|
|
7099
|
+
for (const gpu of gpus) {
|
|
7100
|
+
lines.push(`gpu_temperature_celsius{gpu="${gpu.index}"} ${gpu.temperatureC}`);
|
|
7101
|
+
}
|
|
7102
|
+
}
|
|
7057
7103
|
return `${lines.join("\n")}
|
|
7058
7104
|
`;
|
|
7059
7105
|
}
|
|
@@ -7061,6 +7107,7 @@ var MetricsUtils = {
|
|
|
7061
7107
|
cpu: readCpu,
|
|
7062
7108
|
memory: readMemory,
|
|
7063
7109
|
system: readSystem,
|
|
7110
|
+
gpus: readGpus,
|
|
7064
7111
|
toPrometheus
|
|
7065
7112
|
};
|
|
7066
7113
|
|
|
@@ -7329,6 +7376,76 @@ function makeSessionMiddleware(service, options = {}) {
|
|
|
7329
7376
|
};
|
|
7330
7377
|
}
|
|
7331
7378
|
|
|
7379
|
+
// src/sessions/redisStore.ts
|
|
7380
|
+
var RedisSessionStore = class {
|
|
7381
|
+
/**
|
|
7382
|
+
* @param client - A connected node-redis v4 (or compatible) client.
|
|
7383
|
+
* @param prefix - Key prefix. Default `sess:`.
|
|
7384
|
+
*/
|
|
7385
|
+
constructor(client, prefix = "sess:") {
|
|
7386
|
+
this.client = client;
|
|
7387
|
+
this.prefix = prefix;
|
|
7388
|
+
}
|
|
7389
|
+
client;
|
|
7390
|
+
prefix;
|
|
7391
|
+
key(idHash) {
|
|
7392
|
+
return `${this.prefix}${idHash}`;
|
|
7393
|
+
}
|
|
7394
|
+
userKey(userId) {
|
|
7395
|
+
return `${this.prefix}user:${userId}`;
|
|
7396
|
+
}
|
|
7397
|
+
async get(idHash) {
|
|
7398
|
+
const raw = await this.client.get(this.key(idHash));
|
|
7399
|
+
if (raw === null) return null;
|
|
7400
|
+
const session = JSON.parse(raw);
|
|
7401
|
+
if (session.expiresAt <= Date.now()) {
|
|
7402
|
+
await this.delete(idHash);
|
|
7403
|
+
return null;
|
|
7404
|
+
}
|
|
7405
|
+
return session;
|
|
7406
|
+
}
|
|
7407
|
+
async set(session) {
|
|
7408
|
+
const ttlSeconds = Math.max(1, Math.ceil((session.expiresAt - Date.now()) / 1e3));
|
|
7409
|
+
await this.client.set(this.key(session.idHash), JSON.stringify(session), {
|
|
7410
|
+
EX: ttlSeconds
|
|
7411
|
+
});
|
|
7412
|
+
await this.client.sAdd(this.userKey(session.userId), session.idHash);
|
|
7413
|
+
}
|
|
7414
|
+
async delete(idHash) {
|
|
7415
|
+
const raw = await this.client.get(this.key(idHash));
|
|
7416
|
+
await this.client.del(this.key(idHash));
|
|
7417
|
+
if (raw) {
|
|
7418
|
+
const session = JSON.parse(raw);
|
|
7419
|
+
await this.client.sRem(this.userKey(session.userId), idHash);
|
|
7420
|
+
}
|
|
7421
|
+
}
|
|
7422
|
+
async deleteByUser(userId) {
|
|
7423
|
+
const ids = await this.client.sMembers(this.userKey(userId));
|
|
7424
|
+
let count = 0;
|
|
7425
|
+
for (const idHash of ids) {
|
|
7426
|
+
await this.client.del(this.key(idHash));
|
|
7427
|
+
await this.client.sRem(this.userKey(userId), idHash);
|
|
7428
|
+
count += 1;
|
|
7429
|
+
}
|
|
7430
|
+
return count;
|
|
7431
|
+
}
|
|
7432
|
+
async listByUser(userId) {
|
|
7433
|
+
const ids = await this.client.sMembers(this.userKey(userId));
|
|
7434
|
+
const sessions = [];
|
|
7435
|
+
const now = Date.now();
|
|
7436
|
+
for (const idHash of ids) {
|
|
7437
|
+
const raw = await this.client.get(this.key(idHash));
|
|
7438
|
+
if (raw === null) {
|
|
7439
|
+
await this.client.sRem(this.userKey(userId), idHash);
|
|
7440
|
+
continue;
|
|
7441
|
+
}
|
|
7442
|
+
const session = JSON.parse(raw);
|
|
7443
|
+
if (session.expiresAt > now) sessions.push(session);
|
|
7444
|
+
}
|
|
7445
|
+
return sessions.sort((a, b) => a.createdAt - b.createdAt);
|
|
7446
|
+
}
|
|
7447
|
+
};
|
|
7448
|
+
|
|
7332
7449
|
// src/sse/eventStream.ts
|
|
7333
7450
|
var ServerSentEvent = class {
|
|
7334
7451
|
constructor(init) {
|
|
@@ -7501,6 +7618,92 @@ var SSEBroker = class {
|
|
|
7501
7618
|
}
|
|
7502
7619
|
};
|
|
7503
7620
|
|
|
7621
|
+
// src/sse/redisBroker.ts
|
|
7622
|
+
var RedisSSEBroker = class {
|
|
7623
|
+
/**
|
|
7624
|
+
* @param publisher - The main Redis client (used to `publish`).
|
|
7625
|
+
* @param subscriber - A dedicated subscriber connection (`client.duplicate()`).
|
|
7626
|
+
* @param options - Channel prefix + per-stream options.
|
|
7627
|
+
*/
|
|
7628
|
+
constructor(publisher, subscriber, options = {}) {
|
|
7629
|
+
this.publisher = publisher;
|
|
7630
|
+
this.subscriber = subscriber;
|
|
7631
|
+
this.prefix = options.prefix ?? "sse:";
|
|
7632
|
+
const { prefix: _p, ...streamOptions } = options;
|
|
7633
|
+
this.streamOptions = streamOptions;
|
|
7634
|
+
}
|
|
7635
|
+
publisher;
|
|
7636
|
+
subscriber;
|
|
7637
|
+
local = /* @__PURE__ */ new Map();
|
|
7638
|
+
prefix;
|
|
7639
|
+
streamOptions;
|
|
7640
|
+
channelKey(channel) {
|
|
7641
|
+
return `${this.prefix}${channel}`;
|
|
7642
|
+
}
|
|
7643
|
+
/** Emit a decoded payload to every local stream on a channel. */
|
|
7644
|
+
emitLocal(channel, data, event) {
|
|
7645
|
+
const set = this.local.get(channel);
|
|
7646
|
+
if (!set) return;
|
|
7647
|
+
for (const stream of set) stream.publish(data, event);
|
|
7648
|
+
}
|
|
7649
|
+
/**
|
|
7650
|
+
* Register a subscriber stream, subscribing to the Redis channel on first use.
|
|
7651
|
+
*
|
|
7652
|
+
* @param channel - The channel name.
|
|
7653
|
+
* @returns A fresh {@link EventStream} to serve to the client.
|
|
7654
|
+
*/
|
|
7655
|
+
async register(channel) {
|
|
7656
|
+
const stream = new EventStream(this.streamOptions);
|
|
7657
|
+
let set = this.local.get(channel);
|
|
7658
|
+
if (!set) {
|
|
7659
|
+
set = /* @__PURE__ */ new Set();
|
|
7660
|
+
this.local.set(channel, set);
|
|
7661
|
+
await this.subscriber.subscribe(this.channelKey(channel), (raw) => {
|
|
7662
|
+
try {
|
|
7663
|
+
const { data, event } = JSON.parse(raw);
|
|
7664
|
+
this.emitLocal(channel, data, event);
|
|
7665
|
+
} catch {
|
|
7666
|
+
}
|
|
7667
|
+
});
|
|
7668
|
+
}
|
|
7669
|
+
set.add(stream);
|
|
7670
|
+
return stream;
|
|
7671
|
+
}
|
|
7672
|
+
/**
|
|
7673
|
+
* Remove a subscriber stream; unsubscribe from Redis when the last leaves.
|
|
7674
|
+
*
|
|
7675
|
+
* @param channel - The channel name.
|
|
7676
|
+
* @param stream - The stream to remove.
|
|
7677
|
+
*/
|
|
7678
|
+
async unregister(channel, stream) {
|
|
7679
|
+
const set = this.local.get(channel);
|
|
7680
|
+
if (!set) return;
|
|
7681
|
+
set.delete(stream);
|
|
7682
|
+
stream.close();
|
|
7683
|
+
if (set.size === 0) {
|
|
7684
|
+
this.local.delete(channel);
|
|
7685
|
+
await this.subscriber.unsubscribe(this.channelKey(channel));
|
|
7686
|
+
}
|
|
7687
|
+
}
|
|
7688
|
+
/** Local subscriber count on `channel` (this replica only). */
|
|
7689
|
+
localSubscribers(channel) {
|
|
7690
|
+
return this.local.get(channel)?.size ?? 0;
|
|
7691
|
+
}
|
|
7692
|
+
/**
|
|
7693
|
+
* Publish to every subscriber across all replicas.
|
|
7694
|
+
*
|
|
7695
|
+
* @param channel - The channel name.
|
|
7696
|
+
* @param data - The payload (JSON-encoded).
|
|
7697
|
+
* @param event - Optional event name.
|
|
7698
|
+
*/
|
|
7699
|
+
async publish(channel, data, event) {
|
|
7700
|
+
await this.publisher.publish(
|
|
7701
|
+
this.channelKey(channel),
|
|
7702
|
+
JSON.stringify({ data, ...event ? { event } : {} })
|
|
7703
|
+
);
|
|
7704
|
+
}
|
|
7705
|
+
};
|
|
7706
|
+
|
|
7504
7707
|
// src/websockets/schemas.ts
|
|
7505
7708
|
var wsEnvelopeSchema = zod.z.object({
|
|
7506
7709
|
type: zod.z.string().openapi({ description: "Message type discriminator." }),
|
|
@@ -9161,6 +9364,16 @@ function makeHealthRouter(options = {}) {
|
|
|
9161
9364
|
});
|
|
9162
9365
|
return router;
|
|
9163
9366
|
}
|
|
9367
|
+
function makeMetricsRouter(options = {}) {
|
|
9368
|
+
const path = options.path ?? "/metrics";
|
|
9369
|
+
const router = express2.Router();
|
|
9370
|
+
if (options.guard) router.use(path, options.guard);
|
|
9371
|
+
router.get(path, async (_req, res) => {
|
|
9372
|
+
const gpus = options.includeGpu ? await MetricsUtils.gpus() : [];
|
|
9373
|
+
res.type("text/plain").send(MetricsUtils.toPrometheus(MetricsUtils.system(), gpus));
|
|
9374
|
+
});
|
|
9375
|
+
return router;
|
|
9376
|
+
}
|
|
9164
9377
|
var logger3 = new JSONLogger("tempest_express_sdk.api.server");
|
|
9165
9378
|
function corsMiddleware(origins) {
|
|
9166
9379
|
const allowAll = origins === "*";
|
|
@@ -9227,7 +9440,7 @@ function runServer(app, options = {}) {
|
|
|
9227
9440
|
}
|
|
9228
9441
|
|
|
9229
9442
|
// src/version.ts
|
|
9230
|
-
var VERSION = "0.
|
|
9443
|
+
var VERSION = "0.9.0";
|
|
9231
9444
|
|
|
9232
9445
|
Object.defineProperty(exports, "OpenAPIRegistry", {
|
|
9233
9446
|
enumerable: true,
|
|
@@ -9422,6 +9635,8 @@ exports.PasswordUtils = PasswordUtils;
|
|
|
9422
9635
|
exports.REQUEST_ID_HEADER = REQUEST_ID_HEADER;
|
|
9423
9636
|
exports.RabbitBroker = RabbitBroker;
|
|
9424
9637
|
exports.RedisCacheManager = RedisCacheManager;
|
|
9638
|
+
exports.RedisSSEBroker = RedisSSEBroker;
|
|
9639
|
+
exports.RedisSessionStore = RedisSessionStore;
|
|
9425
9640
|
exports.Region = Region;
|
|
9426
9641
|
exports.RetryPolicy = RetryPolicy;
|
|
9427
9642
|
exports.SSEBroker = SSEBroker;
|
|
@@ -9496,6 +9711,7 @@ exports.makeAuthRouter = makeAuthRouter;
|
|
|
9496
9711
|
exports.makeFlagGuard = makeFlagGuard;
|
|
9497
9712
|
exports.makeHealthRouter = makeHealthRouter;
|
|
9498
9713
|
exports.makeJwtAuthMiddleware = makeJwtAuthMiddleware;
|
|
9714
|
+
exports.makeMetricsRouter = makeMetricsRouter;
|
|
9499
9715
|
exports.makeSessionMiddleware = makeSessionMiddleware;
|
|
9500
9716
|
exports.makeTwilioWebhookRouter = makeTwilioWebhookRouter;
|
|
9501
9717
|
exports.makeUnhandledExceptionHandler = makeUnhandledExceptionHandler;
|