tempest-express-sdk 0.1.0 → 0.3.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 +16 -6
- package/dist/chunk-6ZKN2ELQ.js +6 -0
- package/dist/{chunk-2NB7ZA7G.js.map → chunk-6ZKN2ELQ.js.map} +1 -1
- package/dist/cli.cjs +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.cjs +1322 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1125 -10
- package/dist/index.d.ts +1125 -10
- package/dist/index.js +1284 -8
- package/dist/index.js.map +1 -1
- package/package.json +26 -2
- package/dist/chunk-2NB7ZA7G.js +0 -6
package/dist/index.cjs
CHANGED
|
@@ -5,6 +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 os = require('os');
|
|
9
|
+
var promises = require('fs/promises');
|
|
10
|
+
var path = require('path');
|
|
8
11
|
var express2 = require('express');
|
|
9
12
|
var swaggerUiDist = require('swagger-ui-dist');
|
|
10
13
|
|
|
@@ -6753,6 +6756,1279 @@ var AttemptThrottle = class {
|
|
|
6753
6756
|
}
|
|
6754
6757
|
};
|
|
6755
6758
|
|
|
6759
|
+
// src/utils/clientIp.ts
|
|
6760
|
+
var UNKNOWN = "unknown";
|
|
6761
|
+
function getClientIp(req, options = {}) {
|
|
6762
|
+
if (options.trustedHeader) {
|
|
6763
|
+
const value = req.header(options.trustedHeader);
|
|
6764
|
+
if (value) return value.trim();
|
|
6765
|
+
}
|
|
6766
|
+
return req.socket?.remoteAddress ?? req.ip ?? UNKNOWN;
|
|
6767
|
+
}
|
|
6768
|
+
var BASE32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
6769
|
+
function base32Encode(bytes) {
|
|
6770
|
+
let bits = 0;
|
|
6771
|
+
let value = 0;
|
|
6772
|
+
let out = "";
|
|
6773
|
+
for (const byte of bytes) {
|
|
6774
|
+
value = value << 8 | byte;
|
|
6775
|
+
bits += 8;
|
|
6776
|
+
while (bits >= 5) {
|
|
6777
|
+
out += BASE32_ALPHABET[value >>> bits - 5 & 31];
|
|
6778
|
+
bits -= 5;
|
|
6779
|
+
}
|
|
6780
|
+
}
|
|
6781
|
+
if (bits > 0) out += BASE32_ALPHABET[value << 5 - bits & 31];
|
|
6782
|
+
return out;
|
|
6783
|
+
}
|
|
6784
|
+
function base32Decode(secret) {
|
|
6785
|
+
const clean = secret.toUpperCase().replace(/=+$/, "").replace(/\s/g, "");
|
|
6786
|
+
let bits = 0;
|
|
6787
|
+
let value = 0;
|
|
6788
|
+
const out = [];
|
|
6789
|
+
for (const char of clean) {
|
|
6790
|
+
const index = BASE32_ALPHABET.indexOf(char);
|
|
6791
|
+
if (index === -1) continue;
|
|
6792
|
+
value = value << 5 | index;
|
|
6793
|
+
bits += 5;
|
|
6794
|
+
if (bits >= 8) {
|
|
6795
|
+
out.push(value >>> bits - 8 & 255);
|
|
6796
|
+
bits -= 8;
|
|
6797
|
+
}
|
|
6798
|
+
}
|
|
6799
|
+
return Buffer.from(out);
|
|
6800
|
+
}
|
|
6801
|
+
function hotp(secret, counter, digits) {
|
|
6802
|
+
const buffer = Buffer.alloc(8);
|
|
6803
|
+
buffer.writeUInt32BE(Math.floor(counter / 2 ** 32), 0);
|
|
6804
|
+
buffer.writeUInt32BE(counter >>> 0, 4);
|
|
6805
|
+
const digest = crypto.createHmac("sha1", secret).update(buffer).digest();
|
|
6806
|
+
const offset = digest[digest.length - 1] & 15;
|
|
6807
|
+
const binary = (digest[offset] & 127) << 24 | (digest[offset + 1] & 255) << 16 | (digest[offset + 2] & 255) << 8 | digest[offset + 3] & 255;
|
|
6808
|
+
return (binary % 10 ** digits).toString().padStart(digits, "0");
|
|
6809
|
+
}
|
|
6810
|
+
var TOTPHelper = class {
|
|
6811
|
+
issuer;
|
|
6812
|
+
step;
|
|
6813
|
+
digits;
|
|
6814
|
+
/**
|
|
6815
|
+
* @param options - Issuer label, time step and digit count.
|
|
6816
|
+
*/
|
|
6817
|
+
constructor(options) {
|
|
6818
|
+
this.issuer = options.issuer;
|
|
6819
|
+
this.step = options.step ?? 30;
|
|
6820
|
+
this.digits = options.digits ?? 6;
|
|
6821
|
+
}
|
|
6822
|
+
/**
|
|
6823
|
+
* Generate a fresh base32 secret (80 bits).
|
|
6824
|
+
*
|
|
6825
|
+
* @returns A base32-encoded TOTP secret to persist on the user row.
|
|
6826
|
+
*/
|
|
6827
|
+
generateSecret() {
|
|
6828
|
+
return base32Encode(crypto.randomBytes(10));
|
|
6829
|
+
}
|
|
6830
|
+
/**
|
|
6831
|
+
* Build the `otpauth://` provisioning URI (render as a QR code).
|
|
6832
|
+
*
|
|
6833
|
+
* @param secret - The base32 secret.
|
|
6834
|
+
* @param accountName - Identifier shown next to the issuer (e.g. the email).
|
|
6835
|
+
* @returns The `otpauth://totp/...` URI.
|
|
6836
|
+
*/
|
|
6837
|
+
provisioningUri(secret, accountName) {
|
|
6838
|
+
const label = encodeURIComponent(`${this.issuer}:${accountName}`);
|
|
6839
|
+
const params = new URLSearchParams({
|
|
6840
|
+
secret,
|
|
6841
|
+
issuer: this.issuer,
|
|
6842
|
+
algorithm: "SHA1",
|
|
6843
|
+
digits: String(this.digits),
|
|
6844
|
+
period: String(this.step)
|
|
6845
|
+
});
|
|
6846
|
+
return `otpauth://totp/${label}?${params.toString()}`;
|
|
6847
|
+
}
|
|
6848
|
+
/**
|
|
6849
|
+
* Verify a code against the secret for the current time window.
|
|
6850
|
+
*
|
|
6851
|
+
* @param secret - The base32 secret.
|
|
6852
|
+
* @param code - The submitted code.
|
|
6853
|
+
* @param window - Tolerance in steps (±). Default 1 (previous/current/next).
|
|
6854
|
+
* @returns `true` when the code matches within the window.
|
|
6855
|
+
*/
|
|
6856
|
+
verify(secret, code, window = 1) {
|
|
6857
|
+
const cleaned = code.trim().replace(/[\s-]/g, "");
|
|
6858
|
+
if (!/^\d+$/.test(cleaned) || cleaned.length !== this.digits) return false;
|
|
6859
|
+
const key = base32Decode(secret);
|
|
6860
|
+
const counter = Math.floor(Date.now() / 1e3 / this.step);
|
|
6861
|
+
for (let offset = -window; offset <= window; offset++) {
|
|
6862
|
+
if (hotp(key, counter + offset, this.digits) === cleaned) return true;
|
|
6863
|
+
}
|
|
6864
|
+
return false;
|
|
6865
|
+
}
|
|
6866
|
+
};
|
|
6867
|
+
|
|
6868
|
+
// src/utils/httpClient.ts
|
|
6869
|
+
var CircuitOpenError = class extends Error {
|
|
6870
|
+
constructor(host) {
|
|
6871
|
+
super(`Circuit breaker is open for host ${host}`);
|
|
6872
|
+
this.host = host;
|
|
6873
|
+
this.name = "CircuitOpenError";
|
|
6874
|
+
}
|
|
6875
|
+
host;
|
|
6876
|
+
};
|
|
6877
|
+
var RetryPolicy = class {
|
|
6878
|
+
/**
|
|
6879
|
+
* @param maxRetries - Additional attempts after the first. Default 2.
|
|
6880
|
+
* @param baseDelayMs - Base backoff in ms (doubles each attempt). Default 100.
|
|
6881
|
+
* @param retryOn - HTTP status codes that trigger a retry. Default 5xx + 429.
|
|
6882
|
+
*/
|
|
6883
|
+
constructor(maxRetries = 2, baseDelayMs = 100, retryOn = [429, 500, 502, 503, 504]) {
|
|
6884
|
+
this.maxRetries = maxRetries;
|
|
6885
|
+
this.baseDelayMs = baseDelayMs;
|
|
6886
|
+
this.retryOn = retryOn;
|
|
6887
|
+
}
|
|
6888
|
+
maxRetries;
|
|
6889
|
+
baseDelayMs;
|
|
6890
|
+
retryOn;
|
|
6891
|
+
/** Backoff delay in ms before `attempt` (0-indexed). */
|
|
6892
|
+
sleepFor(attempt) {
|
|
6893
|
+
return this.baseDelayMs * 2 ** attempt;
|
|
6894
|
+
}
|
|
6895
|
+
};
|
|
6896
|
+
var HTTPClient = class {
|
|
6897
|
+
baseUrl;
|
|
6898
|
+
defaultHeaders;
|
|
6899
|
+
timeoutMs;
|
|
6900
|
+
retryPolicy;
|
|
6901
|
+
breakerThreshold;
|
|
6902
|
+
breakerCooldownMs;
|
|
6903
|
+
breakers = /* @__PURE__ */ new Map();
|
|
6904
|
+
/**
|
|
6905
|
+
* @param options - Base URL, headers, timeout, retry and breaker settings.
|
|
6906
|
+
*/
|
|
6907
|
+
constructor(options = {}) {
|
|
6908
|
+
this.baseUrl = options.baseUrl ?? "";
|
|
6909
|
+
this.defaultHeaders = options.defaultHeaders ?? {};
|
|
6910
|
+
this.timeoutMs = options.timeoutMs ?? 3e4;
|
|
6911
|
+
this.retryPolicy = options.retryPolicy ?? new RetryPolicy();
|
|
6912
|
+
this.breakerThreshold = options.breakerThreshold ?? 5;
|
|
6913
|
+
this.breakerCooldownMs = options.breakerCooldownMs ?? 3e4;
|
|
6914
|
+
}
|
|
6915
|
+
resolve(url) {
|
|
6916
|
+
return this.baseUrl && !/^https?:\/\//.test(url) ? `${this.baseUrl}${url}` : url;
|
|
6917
|
+
}
|
|
6918
|
+
hostOf(url) {
|
|
6919
|
+
try {
|
|
6920
|
+
return new URL(url).host;
|
|
6921
|
+
} catch {
|
|
6922
|
+
return url;
|
|
6923
|
+
}
|
|
6924
|
+
}
|
|
6925
|
+
breakerCheck(host) {
|
|
6926
|
+
const state = this.breakers.get(host);
|
|
6927
|
+
if (state && state.openUntil > Date.now()) throw new CircuitOpenError(host);
|
|
6928
|
+
}
|
|
6929
|
+
breakerRecord(host, failed) {
|
|
6930
|
+
const state = this.breakers.get(host) ?? { failures: 0, openUntil: 0 };
|
|
6931
|
+
if (failed) {
|
|
6932
|
+
state.failures += 1;
|
|
6933
|
+
if (state.failures >= this.breakerThreshold) {
|
|
6934
|
+
state.openUntil = Date.now() + this.breakerCooldownMs;
|
|
6935
|
+
state.failures = 0;
|
|
6936
|
+
}
|
|
6937
|
+
} else {
|
|
6938
|
+
state.failures = 0;
|
|
6939
|
+
state.openUntil = 0;
|
|
6940
|
+
}
|
|
6941
|
+
this.breakers.set(host, state);
|
|
6942
|
+
}
|
|
6943
|
+
/**
|
|
6944
|
+
* Perform a request with retries and breaker protection.
|
|
6945
|
+
*
|
|
6946
|
+
* @param method - HTTP method.
|
|
6947
|
+
* @param url - Absolute URL or a path resolved against `baseUrl`.
|
|
6948
|
+
* @param init - Extra `fetch` init (headers, body, …).
|
|
6949
|
+
* @returns The `Response`.
|
|
6950
|
+
* @throws {CircuitOpenError} When the per-host breaker is open.
|
|
6951
|
+
*/
|
|
6952
|
+
async request(method, url, init = {}) {
|
|
6953
|
+
const target = this.resolve(url);
|
|
6954
|
+
const host = this.hostOf(target);
|
|
6955
|
+
this.breakerCheck(host);
|
|
6956
|
+
let lastError;
|
|
6957
|
+
for (let attempt = 0; attempt <= this.retryPolicy.maxRetries; attempt++) {
|
|
6958
|
+
const controller = new AbortController();
|
|
6959
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
6960
|
+
try {
|
|
6961
|
+
const response = await fetch(target, {
|
|
6962
|
+
...init,
|
|
6963
|
+
method,
|
|
6964
|
+
headers: { ...this.defaultHeaders, ...init.headers ?? {} },
|
|
6965
|
+
signal: controller.signal
|
|
6966
|
+
});
|
|
6967
|
+
clearTimeout(timer);
|
|
6968
|
+
if (this.retryPolicy.retryOn.includes(response.status)) {
|
|
6969
|
+
this.breakerRecord(host, true);
|
|
6970
|
+
if (attempt < this.retryPolicy.maxRetries) {
|
|
6971
|
+
await this.sleep(this.retryPolicy.sleepFor(attempt));
|
|
6972
|
+
continue;
|
|
6973
|
+
}
|
|
6974
|
+
return response;
|
|
6975
|
+
}
|
|
6976
|
+
this.breakerRecord(host, false);
|
|
6977
|
+
return response;
|
|
6978
|
+
} catch (error) {
|
|
6979
|
+
clearTimeout(timer);
|
|
6980
|
+
lastError = error;
|
|
6981
|
+
this.breakerRecord(host, true);
|
|
6982
|
+
if (attempt < this.retryPolicy.maxRetries) {
|
|
6983
|
+
await this.sleep(this.retryPolicy.sleepFor(attempt));
|
|
6984
|
+
continue;
|
|
6985
|
+
}
|
|
6986
|
+
}
|
|
6987
|
+
}
|
|
6988
|
+
throw lastError instanceof Error ? lastError : new Error("Request failed");
|
|
6989
|
+
}
|
|
6990
|
+
sleep(ms) {
|
|
6991
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6992
|
+
}
|
|
6993
|
+
/** GET request. */
|
|
6994
|
+
get(url, init) {
|
|
6995
|
+
return this.request("GET", url, init);
|
|
6996
|
+
}
|
|
6997
|
+
/** POST request. */
|
|
6998
|
+
post(url, init) {
|
|
6999
|
+
return this.request("POST", url, init);
|
|
7000
|
+
}
|
|
7001
|
+
/** PUT request. */
|
|
7002
|
+
put(url, init) {
|
|
7003
|
+
return this.request("PUT", url, init);
|
|
7004
|
+
}
|
|
7005
|
+
/** PATCH request. */
|
|
7006
|
+
patch(url, init) {
|
|
7007
|
+
return this.request("PATCH", url, init);
|
|
7008
|
+
}
|
|
7009
|
+
/** DELETE request. */
|
|
7010
|
+
delete(url, init) {
|
|
7011
|
+
return this.request("DELETE", url, init);
|
|
7012
|
+
}
|
|
7013
|
+
};
|
|
7014
|
+
function readCpu() {
|
|
7015
|
+
const cores = os.cpus().length;
|
|
7016
|
+
const load1 = os.loadavg()[0] ?? 0;
|
|
7017
|
+
return {
|
|
7018
|
+
cores,
|
|
7019
|
+
load1,
|
|
7020
|
+
loadPercent: cores > 0 ? load1 / cores * 100 : 0
|
|
7021
|
+
};
|
|
7022
|
+
}
|
|
7023
|
+
function readMemory() {
|
|
7024
|
+
const total = os.totalmem();
|
|
7025
|
+
const free = os.freemem();
|
|
7026
|
+
const used = total - free;
|
|
7027
|
+
return {
|
|
7028
|
+
total,
|
|
7029
|
+
free,
|
|
7030
|
+
used,
|
|
7031
|
+
usedPercent: total > 0 ? used / total * 100 : 0,
|
|
7032
|
+
processRss: process.memoryUsage().rss
|
|
7033
|
+
};
|
|
7034
|
+
}
|
|
7035
|
+
function readSystem() {
|
|
7036
|
+
return {
|
|
7037
|
+
cpu: readCpu(),
|
|
7038
|
+
memory: readMemory(),
|
|
7039
|
+
uptimeSeconds: process.uptime()
|
|
7040
|
+
};
|
|
7041
|
+
}
|
|
7042
|
+
function toPrometheus(snapshot = readSystem()) {
|
|
7043
|
+
const lines = [
|
|
7044
|
+
"# HELP process_cpu_load_percent 1-minute load average as percent of cores",
|
|
7045
|
+
"# TYPE process_cpu_load_percent gauge",
|
|
7046
|
+
`process_cpu_load_percent ${snapshot.cpu.loadPercent}`,
|
|
7047
|
+
"# HELP process_memory_used_percent System memory used percent",
|
|
7048
|
+
"# TYPE process_memory_used_percent gauge",
|
|
7049
|
+
`process_memory_used_percent ${snapshot.memory.usedPercent}`,
|
|
7050
|
+
"# HELP process_memory_rss_bytes Resident set size of the process",
|
|
7051
|
+
"# TYPE process_memory_rss_bytes gauge",
|
|
7052
|
+
`process_memory_rss_bytes ${snapshot.memory.processRss}`,
|
|
7053
|
+
"# HELP process_uptime_seconds Process uptime in seconds",
|
|
7054
|
+
"# TYPE process_uptime_seconds counter",
|
|
7055
|
+
`process_uptime_seconds ${snapshot.uptimeSeconds}`
|
|
7056
|
+
];
|
|
7057
|
+
return `${lines.join("\n")}
|
|
7058
|
+
`;
|
|
7059
|
+
}
|
|
7060
|
+
var MetricsUtils = {
|
|
7061
|
+
cpu: readCpu,
|
|
7062
|
+
memory: readMemory,
|
|
7063
|
+
system: readSystem,
|
|
7064
|
+
toPrometheus
|
|
7065
|
+
};
|
|
7066
|
+
|
|
7067
|
+
// src/utils/email.ts
|
|
7068
|
+
var EmailUtils = class {
|
|
7069
|
+
/**
|
|
7070
|
+
* @param options - SMTP connection and default sender.
|
|
7071
|
+
*/
|
|
7072
|
+
constructor(options) {
|
|
7073
|
+
this.options = options;
|
|
7074
|
+
}
|
|
7075
|
+
options;
|
|
7076
|
+
transport = null;
|
|
7077
|
+
async ready() {
|
|
7078
|
+
if (this.transport) return this.transport;
|
|
7079
|
+
let nodemailer;
|
|
7080
|
+
try {
|
|
7081
|
+
const mod = await import('nodemailer');
|
|
7082
|
+
nodemailer = mod.default ?? mod;
|
|
7083
|
+
} catch (cause) {
|
|
7084
|
+
throw new Error(
|
|
7085
|
+
"EmailUtils requires the 'nodemailer' peer dependency. Install with `npm i nodemailer`.",
|
|
7086
|
+
{ cause }
|
|
7087
|
+
);
|
|
7088
|
+
}
|
|
7089
|
+
this.transport = nodemailer.createTransport({
|
|
7090
|
+
host: this.options.host,
|
|
7091
|
+
port: this.options.port ?? 587,
|
|
7092
|
+
secure: this.options.secure ?? false,
|
|
7093
|
+
...this.options.user ? { auth: { user: this.options.user, pass: this.options.password ?? "" } } : {}
|
|
7094
|
+
});
|
|
7095
|
+
return this.transport;
|
|
7096
|
+
}
|
|
7097
|
+
/**
|
|
7098
|
+
* Send an email message.
|
|
7099
|
+
*
|
|
7100
|
+
* @param message - The message (recipients, subject, body).
|
|
7101
|
+
*/
|
|
7102
|
+
async send(message) {
|
|
7103
|
+
const transport = await this.ready();
|
|
7104
|
+
await transport.sendMail({
|
|
7105
|
+
from: message.from ?? this.options.from,
|
|
7106
|
+
to: message.to,
|
|
7107
|
+
subject: message.subject,
|
|
7108
|
+
...message.text !== void 0 ? { text: message.text } : {},
|
|
7109
|
+
...message.html !== void 0 ? { html: message.html } : {}
|
|
7110
|
+
});
|
|
7111
|
+
}
|
|
7112
|
+
};
|
|
7113
|
+
|
|
7114
|
+
// src/cache/manager.ts
|
|
7115
|
+
var MemoryCacheManager = class {
|
|
7116
|
+
store = /* @__PURE__ */ new Map();
|
|
7117
|
+
live(key) {
|
|
7118
|
+
const entry = this.store.get(key);
|
|
7119
|
+
if (!entry) return null;
|
|
7120
|
+
if (entry.expiresAt !== null && entry.expiresAt <= Date.now()) {
|
|
7121
|
+
this.store.delete(key);
|
|
7122
|
+
return null;
|
|
7123
|
+
}
|
|
7124
|
+
return entry;
|
|
7125
|
+
}
|
|
7126
|
+
async get(key) {
|
|
7127
|
+
const entry = this.live(key);
|
|
7128
|
+
return entry ? JSON.parse(entry.value) : null;
|
|
7129
|
+
}
|
|
7130
|
+
async set(key, value, ttlSeconds) {
|
|
7131
|
+
this.store.set(key, {
|
|
7132
|
+
value: JSON.stringify(value),
|
|
7133
|
+
expiresAt: ttlSeconds !== void 0 ? Date.now() + ttlSeconds * 1e3 : null
|
|
7134
|
+
});
|
|
7135
|
+
}
|
|
7136
|
+
async delete(key) {
|
|
7137
|
+
this.store.delete(key);
|
|
7138
|
+
}
|
|
7139
|
+
async has(key) {
|
|
7140
|
+
return this.live(key) !== null;
|
|
7141
|
+
}
|
|
7142
|
+
async clear() {
|
|
7143
|
+
this.store.clear();
|
|
7144
|
+
}
|
|
7145
|
+
};
|
|
7146
|
+
var RedisCacheManager = class {
|
|
7147
|
+
/**
|
|
7148
|
+
* @param client - A connected `redis` (node-redis v4) client or compatible.
|
|
7149
|
+
* @param prefix - Optional key prefix applied to every operation.
|
|
7150
|
+
*/
|
|
7151
|
+
constructor(client, prefix = "") {
|
|
7152
|
+
this.client = client;
|
|
7153
|
+
this.prefix = prefix;
|
|
7154
|
+
}
|
|
7155
|
+
client;
|
|
7156
|
+
prefix;
|
|
7157
|
+
key(key) {
|
|
7158
|
+
return this.prefix ? `${this.prefix}${key}` : key;
|
|
7159
|
+
}
|
|
7160
|
+
async get(key) {
|
|
7161
|
+
const raw = await this.client.get(this.key(key));
|
|
7162
|
+
return raw === null ? null : JSON.parse(raw);
|
|
7163
|
+
}
|
|
7164
|
+
async set(key, value, ttlSeconds) {
|
|
7165
|
+
const payload = JSON.stringify(value);
|
|
7166
|
+
await this.client.set(
|
|
7167
|
+
this.key(key),
|
|
7168
|
+
payload,
|
|
7169
|
+
ttlSeconds !== void 0 ? { EX: ttlSeconds } : void 0
|
|
7170
|
+
);
|
|
7171
|
+
}
|
|
7172
|
+
async delete(key) {
|
|
7173
|
+
await this.client.del(this.key(key));
|
|
7174
|
+
}
|
|
7175
|
+
async has(key) {
|
|
7176
|
+
return await this.client.exists(this.key(key)) > 0;
|
|
7177
|
+
}
|
|
7178
|
+
async clear() {
|
|
7179
|
+
await this.client.flushDb();
|
|
7180
|
+
}
|
|
7181
|
+
};
|
|
7182
|
+
|
|
7183
|
+
// src/cache/cached.ts
|
|
7184
|
+
function cached3(fn, options) {
|
|
7185
|
+
return async (...args) => {
|
|
7186
|
+
const key = options.key(...args);
|
|
7187
|
+
const hit = await options.manager.get(key);
|
|
7188
|
+
if (hit !== null) return hit;
|
|
7189
|
+
const result = await fn(...args);
|
|
7190
|
+
await options.manager.set(key, result, options.ttlSeconds);
|
|
7191
|
+
return result;
|
|
7192
|
+
};
|
|
7193
|
+
}
|
|
7194
|
+
|
|
7195
|
+
// src/sessions/store.ts
|
|
7196
|
+
var MemorySessionStore = class {
|
|
7197
|
+
byHash = /* @__PURE__ */ new Map();
|
|
7198
|
+
live(session, idHash) {
|
|
7199
|
+
if (!session) return null;
|
|
7200
|
+
if (session.expiresAt <= Date.now()) {
|
|
7201
|
+
this.byHash.delete(idHash);
|
|
7202
|
+
return null;
|
|
7203
|
+
}
|
|
7204
|
+
return session;
|
|
7205
|
+
}
|
|
7206
|
+
async get(idHash) {
|
|
7207
|
+
return this.live(this.byHash.get(idHash), idHash);
|
|
7208
|
+
}
|
|
7209
|
+
async set(session) {
|
|
7210
|
+
this.byHash.set(session.idHash, session);
|
|
7211
|
+
}
|
|
7212
|
+
async delete(idHash) {
|
|
7213
|
+
this.byHash.delete(idHash);
|
|
7214
|
+
}
|
|
7215
|
+
async deleteByUser(userId) {
|
|
7216
|
+
let count = 0;
|
|
7217
|
+
for (const [hash, session] of this.byHash) {
|
|
7218
|
+
if (session.userId === userId) {
|
|
7219
|
+
this.byHash.delete(hash);
|
|
7220
|
+
count += 1;
|
|
7221
|
+
}
|
|
7222
|
+
}
|
|
7223
|
+
return count;
|
|
7224
|
+
}
|
|
7225
|
+
async listByUser(userId) {
|
|
7226
|
+
const now = Date.now();
|
|
7227
|
+
return [...this.byHash.values()].filter((s) => s.userId === userId && s.expiresAt > now).sort((a, b) => a.createdAt - b.createdAt);
|
|
7228
|
+
}
|
|
7229
|
+
};
|
|
7230
|
+
|
|
7231
|
+
// src/sessions/service.ts
|
|
7232
|
+
var SessionService = class {
|
|
7233
|
+
store;
|
|
7234
|
+
ttlSeconds;
|
|
7235
|
+
/**
|
|
7236
|
+
* @param options - Store and default TTL.
|
|
7237
|
+
*/
|
|
7238
|
+
constructor(options) {
|
|
7239
|
+
this.store = options.store;
|
|
7240
|
+
this.ttlSeconds = options.ttlSeconds ?? 60 * 60 * 24 * 7;
|
|
7241
|
+
}
|
|
7242
|
+
/**
|
|
7243
|
+
* Create a session for `userId` and return its one-time cookie value.
|
|
7244
|
+
*
|
|
7245
|
+
* @param userId - The owning user id.
|
|
7246
|
+
* @param data - Arbitrary session payload.
|
|
7247
|
+
* @param ttlSeconds - Override the default lifetime.
|
|
7248
|
+
* @returns The plaintext token (set as a cookie) and the stored session.
|
|
7249
|
+
*/
|
|
7250
|
+
async create(userId, data = {}, ttlSeconds) {
|
|
7251
|
+
const { plaintext, tokenHash } = generateOpaqueToken();
|
|
7252
|
+
const now = Date.now();
|
|
7253
|
+
const session = {
|
|
7254
|
+
idHash: tokenHash,
|
|
7255
|
+
userId,
|
|
7256
|
+
data,
|
|
7257
|
+
createdAt: now,
|
|
7258
|
+
expiresAt: now + (ttlSeconds ?? this.ttlSeconds) * 1e3
|
|
7259
|
+
};
|
|
7260
|
+
await this.store.set(session);
|
|
7261
|
+
return { token: plaintext, session };
|
|
7262
|
+
}
|
|
7263
|
+
/**
|
|
7264
|
+
* Resolve an opaque cookie value to its live session.
|
|
7265
|
+
*
|
|
7266
|
+
* @param token - The plaintext cookie value.
|
|
7267
|
+
* @returns The session, or `null` when missing/expired.
|
|
7268
|
+
*/
|
|
7269
|
+
async resolve(token) {
|
|
7270
|
+
return this.store.get(hashOpaqueToken(token));
|
|
7271
|
+
}
|
|
7272
|
+
/**
|
|
7273
|
+
* Revoke a single session by its cookie value.
|
|
7274
|
+
*
|
|
7275
|
+
* @param token - The plaintext cookie value.
|
|
7276
|
+
*/
|
|
7277
|
+
async destroy(token) {
|
|
7278
|
+
await this.store.delete(hashOpaqueToken(token));
|
|
7279
|
+
}
|
|
7280
|
+
/**
|
|
7281
|
+
* Revoke every session a user owns (global logout).
|
|
7282
|
+
*
|
|
7283
|
+
* @param userId - The user id.
|
|
7284
|
+
* @returns The number of sessions removed.
|
|
7285
|
+
*/
|
|
7286
|
+
async destroyByUser(userId) {
|
|
7287
|
+
return this.store.deleteByUser(userId);
|
|
7288
|
+
}
|
|
7289
|
+
/**
|
|
7290
|
+
* List a user's live sessions (e.g. an "active devices" view).
|
|
7291
|
+
*
|
|
7292
|
+
* @param userId - The user id.
|
|
7293
|
+
* @returns The user's sessions, oldest first.
|
|
7294
|
+
*/
|
|
7295
|
+
async listByUser(userId) {
|
|
7296
|
+
return this.store.listByUser(userId);
|
|
7297
|
+
}
|
|
7298
|
+
};
|
|
7299
|
+
|
|
7300
|
+
// src/sessions/middleware.ts
|
|
7301
|
+
function parseCookies(header) {
|
|
7302
|
+
const out = {};
|
|
7303
|
+
if (!header) return out;
|
|
7304
|
+
for (const part of header.split(";")) {
|
|
7305
|
+
const index = part.indexOf("=");
|
|
7306
|
+
if (index < 0) continue;
|
|
7307
|
+
const name = part.slice(0, index).trim();
|
|
7308
|
+
const value = part.slice(index + 1).trim();
|
|
7309
|
+
if (name) out[name] = decodeURIComponent(value);
|
|
7310
|
+
}
|
|
7311
|
+
return out;
|
|
7312
|
+
}
|
|
7313
|
+
function sessionCookie(req, cookieName) {
|
|
7314
|
+
return parseCookies(req.header("cookie") ?? void 0)[cookieName] ?? null;
|
|
7315
|
+
}
|
|
7316
|
+
function makeSessionMiddleware(service, options = {}) {
|
|
7317
|
+
const cookieName = options.cookieName ?? "sid";
|
|
7318
|
+
return (req, _res, next) => {
|
|
7319
|
+
const token = sessionCookie(req, cookieName);
|
|
7320
|
+
if (!token) {
|
|
7321
|
+
req.session = null;
|
|
7322
|
+
next();
|
|
7323
|
+
return;
|
|
7324
|
+
}
|
|
7325
|
+
service.resolve(token).then((session) => {
|
|
7326
|
+
req.session = session;
|
|
7327
|
+
next();
|
|
7328
|
+
}).catch(next);
|
|
7329
|
+
};
|
|
7330
|
+
}
|
|
7331
|
+
|
|
7332
|
+
// src/sse/eventStream.ts
|
|
7333
|
+
var ServerSentEvent = class {
|
|
7334
|
+
constructor(init) {
|
|
7335
|
+
this.init = init;
|
|
7336
|
+
}
|
|
7337
|
+
init;
|
|
7338
|
+
/**
|
|
7339
|
+
* Encode to the SSE wire format (terminated by a blank line).
|
|
7340
|
+
*
|
|
7341
|
+
* @returns The encoded event block.
|
|
7342
|
+
*/
|
|
7343
|
+
encode() {
|
|
7344
|
+
const lines = [];
|
|
7345
|
+
if (this.init.event) lines.push(`event: ${this.init.event}`);
|
|
7346
|
+
if (this.init.id) lines.push(`id: ${this.init.id}`);
|
|
7347
|
+
if (this.init.retry !== void 0) lines.push(`retry: ${this.init.retry}`);
|
|
7348
|
+
for (const line of this.init.data.split("\n")) lines.push(`data: ${line}`);
|
|
7349
|
+
return `${lines.join("\n")}
|
|
7350
|
+
|
|
7351
|
+
`;
|
|
7352
|
+
}
|
|
7353
|
+
};
|
|
7354
|
+
function deferred() {
|
|
7355
|
+
let resolve;
|
|
7356
|
+
const promise = new Promise((r) => {
|
|
7357
|
+
resolve = r;
|
|
7358
|
+
});
|
|
7359
|
+
return { promise, resolve };
|
|
7360
|
+
}
|
|
7361
|
+
var EventStream = class {
|
|
7362
|
+
queue = [];
|
|
7363
|
+
waiter = null;
|
|
7364
|
+
closed = false;
|
|
7365
|
+
heartbeatSeconds;
|
|
7366
|
+
/**
|
|
7367
|
+
* @param options - Heartbeat configuration.
|
|
7368
|
+
*/
|
|
7369
|
+
constructor(options = {}) {
|
|
7370
|
+
this.heartbeatSeconds = options.heartbeatSeconds ?? 15;
|
|
7371
|
+
}
|
|
7372
|
+
/** Enqueue raw SSE-encoded text and wake the iterator. */
|
|
7373
|
+
push(raw) {
|
|
7374
|
+
if (this.closed) return;
|
|
7375
|
+
this.queue.push(raw);
|
|
7376
|
+
this.waiter?.resolve();
|
|
7377
|
+
this.waiter = null;
|
|
7378
|
+
}
|
|
7379
|
+
/**
|
|
7380
|
+
* Publish a data payload as an SSE event.
|
|
7381
|
+
*
|
|
7382
|
+
* @param data - The payload; objects are JSON-encoded.
|
|
7383
|
+
* @param event - Optional event name.
|
|
7384
|
+
*/
|
|
7385
|
+
publish(data, event) {
|
|
7386
|
+
const payload = typeof data === "string" ? data : JSON.stringify(data);
|
|
7387
|
+
this.push(
|
|
7388
|
+
new ServerSentEvent({ data: payload, ...event ? { event } : {} }).encode()
|
|
7389
|
+
);
|
|
7390
|
+
}
|
|
7391
|
+
/** Publish a pre-built {@link ServerSentEvent}. */
|
|
7392
|
+
publishEvent(event) {
|
|
7393
|
+
this.push(event.encode());
|
|
7394
|
+
}
|
|
7395
|
+
/** Close the stream; the iterator finishes after draining. */
|
|
7396
|
+
close() {
|
|
7397
|
+
this.closed = true;
|
|
7398
|
+
this.waiter?.resolve();
|
|
7399
|
+
this.waiter = null;
|
|
7400
|
+
}
|
|
7401
|
+
/**
|
|
7402
|
+
* Async iterator yielding encoded SSE chunks, with periodic heartbeats.
|
|
7403
|
+
*
|
|
7404
|
+
* @returns An async iterator of encoded event strings.
|
|
7405
|
+
*/
|
|
7406
|
+
async *stream() {
|
|
7407
|
+
const heartbeatMs = this.heartbeatSeconds !== null ? this.heartbeatSeconds * 1e3 : null;
|
|
7408
|
+
while (!this.closed || this.queue.length > 0) {
|
|
7409
|
+
if (this.queue.length > 0) {
|
|
7410
|
+
yield this.queue.shift();
|
|
7411
|
+
continue;
|
|
7412
|
+
}
|
|
7413
|
+
if (this.closed) break;
|
|
7414
|
+
this.waiter = deferred();
|
|
7415
|
+
if (heartbeatMs === null) {
|
|
7416
|
+
await this.waiter.promise;
|
|
7417
|
+
} else {
|
|
7418
|
+
let timer;
|
|
7419
|
+
const heartbeat = new Promise((resolve) => {
|
|
7420
|
+
timer = setTimeout(resolve, heartbeatMs);
|
|
7421
|
+
});
|
|
7422
|
+
await Promise.race([this.waiter.promise, heartbeat]);
|
|
7423
|
+
if (timer) clearTimeout(timer);
|
|
7424
|
+
if (this.queue.length === 0 && !this.closed) yield ": ping\n\n";
|
|
7425
|
+
}
|
|
7426
|
+
}
|
|
7427
|
+
}
|
|
7428
|
+
};
|
|
7429
|
+
async function sseResponse(req, res, stream) {
|
|
7430
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
7431
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
7432
|
+
res.setHeader("Connection", "keep-alive");
|
|
7433
|
+
res.flushHeaders?.();
|
|
7434
|
+
req.on("close", () => stream.close());
|
|
7435
|
+
for await (const chunk of stream.stream()) {
|
|
7436
|
+
if (res.writableEnded) break;
|
|
7437
|
+
res.write(chunk);
|
|
7438
|
+
}
|
|
7439
|
+
res.end();
|
|
7440
|
+
}
|
|
7441
|
+
|
|
7442
|
+
// src/sse/broker.ts
|
|
7443
|
+
var SSEBroker = class {
|
|
7444
|
+
/**
|
|
7445
|
+
* @param streamOptions - Options applied to every {@link EventStream} created
|
|
7446
|
+
* by {@link register} (e.g. heartbeat interval).
|
|
7447
|
+
*/
|
|
7448
|
+
constructor(streamOptions = {}) {
|
|
7449
|
+
this.streamOptions = streamOptions;
|
|
7450
|
+
}
|
|
7451
|
+
streamOptions;
|
|
7452
|
+
channels = /* @__PURE__ */ new Map();
|
|
7453
|
+
/**
|
|
7454
|
+
* Register a new subscriber stream on `channel`.
|
|
7455
|
+
*
|
|
7456
|
+
* @param channel - The channel name.
|
|
7457
|
+
* @returns A fresh {@link EventStream} to serve to the subscriber.
|
|
7458
|
+
*/
|
|
7459
|
+
register(channel) {
|
|
7460
|
+
const stream = new EventStream(this.streamOptions);
|
|
7461
|
+
const set = this.channels.get(channel) ?? /* @__PURE__ */ new Set();
|
|
7462
|
+
set.add(stream);
|
|
7463
|
+
this.channels.set(channel, set);
|
|
7464
|
+
return stream;
|
|
7465
|
+
}
|
|
7466
|
+
/**
|
|
7467
|
+
* Remove a subscriber stream from `channel` and close it.
|
|
7468
|
+
*
|
|
7469
|
+
* @param channel - The channel name.
|
|
7470
|
+
* @param stream - The stream to remove.
|
|
7471
|
+
*/
|
|
7472
|
+
unregister(channel, stream) {
|
|
7473
|
+
const set = this.channels.get(channel);
|
|
7474
|
+
if (!set) return;
|
|
7475
|
+
set.delete(stream);
|
|
7476
|
+
stream.close();
|
|
7477
|
+
if (set.size === 0) this.channels.delete(channel);
|
|
7478
|
+
}
|
|
7479
|
+
/**
|
|
7480
|
+
* Number of live subscribers on `channel`.
|
|
7481
|
+
*
|
|
7482
|
+
* @param channel - The channel name.
|
|
7483
|
+
* @returns The subscriber count.
|
|
7484
|
+
*/
|
|
7485
|
+
localSubscribers(channel) {
|
|
7486
|
+
return this.channels.get(channel)?.size ?? 0;
|
|
7487
|
+
}
|
|
7488
|
+
/**
|
|
7489
|
+
* Publish an event to every subscriber on `channel`.
|
|
7490
|
+
*
|
|
7491
|
+
* @param channel - The channel name.
|
|
7492
|
+
* @param data - The payload (objects are JSON-encoded).
|
|
7493
|
+
* @param event - Optional event name.
|
|
7494
|
+
* @returns The number of subscribers the event was delivered to.
|
|
7495
|
+
*/
|
|
7496
|
+
publish(channel, data, event) {
|
|
7497
|
+
const set = this.channels.get(channel);
|
|
7498
|
+
if (!set) return 0;
|
|
7499
|
+
for (const stream of set) stream.publish(data, event);
|
|
7500
|
+
return set.size;
|
|
7501
|
+
}
|
|
7502
|
+
};
|
|
7503
|
+
|
|
7504
|
+
// src/websockets/schemas.ts
|
|
7505
|
+
var wsEnvelopeSchema = zod.z.object({
|
|
7506
|
+
type: zod.z.string().openapi({ description: "Message type discriminator." }),
|
|
7507
|
+
data: zod.z.unknown().optional().openapi({ description: "Arbitrary payload." })
|
|
7508
|
+
}).openapi("WSEnvelope");
|
|
7509
|
+
var WebSocketHub = class {
|
|
7510
|
+
byId = /* @__PURE__ */ new Map();
|
|
7511
|
+
byUser = /* @__PURE__ */ new Map();
|
|
7512
|
+
maxPerUser;
|
|
7513
|
+
/**
|
|
7514
|
+
* @param options - Per-user connection cap.
|
|
7515
|
+
*/
|
|
7516
|
+
constructor(options = {}) {
|
|
7517
|
+
this.maxPerUser = options.maxPerUser ?? 5;
|
|
7518
|
+
}
|
|
7519
|
+
/**
|
|
7520
|
+
* Register a new connection for `userId`, evicting the oldest if over cap.
|
|
7521
|
+
*
|
|
7522
|
+
* @param userId - The owning user id.
|
|
7523
|
+
* @param ws - The socket to register.
|
|
7524
|
+
* @returns The created connection record.
|
|
7525
|
+
*/
|
|
7526
|
+
register(userId, ws) {
|
|
7527
|
+
const connection = {
|
|
7528
|
+
id: crypto.randomUUID(),
|
|
7529
|
+
userId,
|
|
7530
|
+
ws,
|
|
7531
|
+
topics: /* @__PURE__ */ new Set()
|
|
7532
|
+
};
|
|
7533
|
+
this.byId.set(connection.id, connection);
|
|
7534
|
+
const ids = this.byUser.get(userId) ?? /* @__PURE__ */ new Set();
|
|
7535
|
+
ids.add(connection.id);
|
|
7536
|
+
this.byUser.set(userId, ids);
|
|
7537
|
+
if (ids.size > this.maxPerUser) {
|
|
7538
|
+
const oldest = ids.values().next().value;
|
|
7539
|
+
if (oldest) this.unregister(oldest, 1008);
|
|
7540
|
+
}
|
|
7541
|
+
return connection;
|
|
7542
|
+
}
|
|
7543
|
+
/**
|
|
7544
|
+
* Remove a connection and close its socket.
|
|
7545
|
+
*
|
|
7546
|
+
* @param connectionId - The connection id.
|
|
7547
|
+
* @param code - Optional WebSocket close code.
|
|
7548
|
+
*/
|
|
7549
|
+
unregister(connectionId, code) {
|
|
7550
|
+
const connection = this.byId.get(connectionId);
|
|
7551
|
+
if (!connection) return;
|
|
7552
|
+
this.byId.delete(connectionId);
|
|
7553
|
+
const ids = this.byUser.get(connection.userId);
|
|
7554
|
+
if (ids) {
|
|
7555
|
+
ids.delete(connectionId);
|
|
7556
|
+
if (ids.size === 0) this.byUser.delete(connection.userId);
|
|
7557
|
+
}
|
|
7558
|
+
try {
|
|
7559
|
+
connection.ws.close(code);
|
|
7560
|
+
} catch {
|
|
7561
|
+
}
|
|
7562
|
+
}
|
|
7563
|
+
/** Subscribe a connection to a topic. */
|
|
7564
|
+
subscribe(connectionId, topic) {
|
|
7565
|
+
this.byId.get(connectionId)?.topics.add(topic);
|
|
7566
|
+
}
|
|
7567
|
+
/** Unsubscribe a connection from a topic. */
|
|
7568
|
+
unsubscribe(connectionId, topic) {
|
|
7569
|
+
this.byId.get(connectionId)?.topics.delete(topic);
|
|
7570
|
+
}
|
|
7571
|
+
/** Serialize and send an envelope to a single connection. */
|
|
7572
|
+
deliver(connection, payload) {
|
|
7573
|
+
try {
|
|
7574
|
+
connection.ws.send(payload);
|
|
7575
|
+
return true;
|
|
7576
|
+
} catch {
|
|
7577
|
+
this.unregister(connection.id);
|
|
7578
|
+
return false;
|
|
7579
|
+
}
|
|
7580
|
+
}
|
|
7581
|
+
/**
|
|
7582
|
+
* Send an envelope to every connection of `userId`.
|
|
7583
|
+
*
|
|
7584
|
+
* @param userId - The target user.
|
|
7585
|
+
* @param envelope - The message envelope.
|
|
7586
|
+
* @returns The number of connections delivered to.
|
|
7587
|
+
*/
|
|
7588
|
+
sendTo(userId, envelope) {
|
|
7589
|
+
const ids = this.byUser.get(userId);
|
|
7590
|
+
if (!ids) return 0;
|
|
7591
|
+
const payload = JSON.stringify(envelope);
|
|
7592
|
+
let count = 0;
|
|
7593
|
+
for (const id of [...ids]) {
|
|
7594
|
+
const connection = this.byId.get(id);
|
|
7595
|
+
if (connection && this.deliver(connection, payload)) count += 1;
|
|
7596
|
+
}
|
|
7597
|
+
return count;
|
|
7598
|
+
}
|
|
7599
|
+
/**
|
|
7600
|
+
* Broadcast an envelope to all connections, or only a topic's subscribers.
|
|
7601
|
+
*
|
|
7602
|
+
* @param envelope - The message envelope.
|
|
7603
|
+
* @param topic - Optional topic to scope the broadcast.
|
|
7604
|
+
* @returns The number of connections delivered to.
|
|
7605
|
+
*/
|
|
7606
|
+
broadcast(envelope, topic) {
|
|
7607
|
+
const payload = JSON.stringify(envelope);
|
|
7608
|
+
let count = 0;
|
|
7609
|
+
for (const connection of [...this.byId.values()]) {
|
|
7610
|
+
if (topic && !connection.topics.has(topic)) continue;
|
|
7611
|
+
if (this.deliver(connection, payload)) count += 1;
|
|
7612
|
+
}
|
|
7613
|
+
return count;
|
|
7614
|
+
}
|
|
7615
|
+
/** The set of users with at least one live connection. */
|
|
7616
|
+
onlineUsers() {
|
|
7617
|
+
return new Set(this.byUser.keys());
|
|
7618
|
+
}
|
|
7619
|
+
/** Total live connection count. */
|
|
7620
|
+
connectionCount() {
|
|
7621
|
+
return this.byId.size;
|
|
7622
|
+
}
|
|
7623
|
+
/** Number of connections subscribed to `topic`. */
|
|
7624
|
+
topicCount(topic) {
|
|
7625
|
+
let count = 0;
|
|
7626
|
+
for (const connection of this.byId.values()) {
|
|
7627
|
+
if (connection.topics.has(topic)) count += 1;
|
|
7628
|
+
}
|
|
7629
|
+
return count;
|
|
7630
|
+
}
|
|
7631
|
+
};
|
|
7632
|
+
|
|
7633
|
+
// src/websockets/attach.ts
|
|
7634
|
+
function tokenFromUrl(url) {
|
|
7635
|
+
const query = url.includes("?") ? url.slice(url.indexOf("?") + 1) : "";
|
|
7636
|
+
return new URLSearchParams(query).get("token");
|
|
7637
|
+
}
|
|
7638
|
+
async function attachWebSocketHub(server, hub, options = {}) {
|
|
7639
|
+
let ws;
|
|
7640
|
+
try {
|
|
7641
|
+
ws = await import('ws');
|
|
7642
|
+
} catch (cause) {
|
|
7643
|
+
throw new Error(
|
|
7644
|
+
"attachWebSocketHub requires the 'ws' peer dependency. Install with `npm i ws`.",
|
|
7645
|
+
{ cause }
|
|
7646
|
+
);
|
|
7647
|
+
}
|
|
7648
|
+
const path = options.path ?? "/ws";
|
|
7649
|
+
const heartbeatMs = (options.heartbeatSeconds ?? 30) * 1e3;
|
|
7650
|
+
const authenticate = options.authenticate ?? (() => "anonymous");
|
|
7651
|
+
const wss = new ws.WebSocketServer({ server, path });
|
|
7652
|
+
wss.on("connection", (socket, req) => {
|
|
7653
|
+
void (async () => {
|
|
7654
|
+
const userId = await authenticate({
|
|
7655
|
+
url: req.url ?? "",
|
|
7656
|
+
headers: req.headers
|
|
7657
|
+
});
|
|
7658
|
+
if (userId === null) {
|
|
7659
|
+
socket.close(1008);
|
|
7660
|
+
return;
|
|
7661
|
+
}
|
|
7662
|
+
const connection = hub.register(userId, socket);
|
|
7663
|
+
let alive = true;
|
|
7664
|
+
socket.on("pong", () => {
|
|
7665
|
+
alive = true;
|
|
7666
|
+
});
|
|
7667
|
+
socket.on("message", (data) => {
|
|
7668
|
+
options.onMessage?.(connection, String(data));
|
|
7669
|
+
});
|
|
7670
|
+
socket.on("close", () => {
|
|
7671
|
+
alive = false;
|
|
7672
|
+
hub.unregister(connection.id);
|
|
7673
|
+
});
|
|
7674
|
+
if (heartbeatMs > 0) {
|
|
7675
|
+
const timer = setInterval(() => {
|
|
7676
|
+
if (!alive) {
|
|
7677
|
+
clearInterval(timer);
|
|
7678
|
+
hub.unregister(connection.id);
|
|
7679
|
+
return;
|
|
7680
|
+
}
|
|
7681
|
+
alive = false;
|
|
7682
|
+
socket.ping();
|
|
7683
|
+
}, heartbeatMs);
|
|
7684
|
+
socket.on("close", () => clearInterval(timer));
|
|
7685
|
+
}
|
|
7686
|
+
})();
|
|
7687
|
+
});
|
|
7688
|
+
return wss;
|
|
7689
|
+
}
|
|
7690
|
+
|
|
7691
|
+
// src/queue/broker.ts
|
|
7692
|
+
var MemoryBroker = class {
|
|
7693
|
+
handlers = /* @__PURE__ */ new Map();
|
|
7694
|
+
async publish(queue, message) {
|
|
7695
|
+
const set = this.handlers.get(queue);
|
|
7696
|
+
if (!set) return;
|
|
7697
|
+
const payload = JSON.parse(JSON.stringify(message));
|
|
7698
|
+
for (const handler of [...set]) await handler(payload);
|
|
7699
|
+
}
|
|
7700
|
+
async subscribe(queue, handler) {
|
|
7701
|
+
const set = this.handlers.get(queue) ?? /* @__PURE__ */ new Set();
|
|
7702
|
+
set.add(handler);
|
|
7703
|
+
this.handlers.set(queue, set);
|
|
7704
|
+
return async () => {
|
|
7705
|
+
set.delete(handler);
|
|
7706
|
+
if (set.size === 0) this.handlers.delete(queue);
|
|
7707
|
+
};
|
|
7708
|
+
}
|
|
7709
|
+
async close() {
|
|
7710
|
+
this.handlers.clear();
|
|
7711
|
+
}
|
|
7712
|
+
};
|
|
7713
|
+
var RabbitBroker = class {
|
|
7714
|
+
/**
|
|
7715
|
+
* @param options - Connection URL and queue durability.
|
|
7716
|
+
*/
|
|
7717
|
+
constructor(options) {
|
|
7718
|
+
this.options = options;
|
|
7719
|
+
this.durable = options.durable ?? true;
|
|
7720
|
+
}
|
|
7721
|
+
options;
|
|
7722
|
+
connection = null;
|
|
7723
|
+
channel = null;
|
|
7724
|
+
durable;
|
|
7725
|
+
/** Lazily connect and open a channel. */
|
|
7726
|
+
async ready() {
|
|
7727
|
+
if (this.channel) return this.channel;
|
|
7728
|
+
let amqp;
|
|
7729
|
+
try {
|
|
7730
|
+
amqp = await import('amqplib');
|
|
7731
|
+
} catch (cause) {
|
|
7732
|
+
throw new Error(
|
|
7733
|
+
"RabbitBroker requires the 'amqplib' peer dependency. Install with `npm i amqplib`.",
|
|
7734
|
+
{ cause }
|
|
7735
|
+
);
|
|
7736
|
+
}
|
|
7737
|
+
this.connection = await amqp.connect(this.options.url);
|
|
7738
|
+
this.channel = await this.connection.createChannel();
|
|
7739
|
+
return this.channel;
|
|
7740
|
+
}
|
|
7741
|
+
async publish(queue, message) {
|
|
7742
|
+
const channel = await this.ready();
|
|
7743
|
+
await channel.assertQueue(queue, { durable: this.durable });
|
|
7744
|
+
channel.sendToQueue(queue, Buffer.from(JSON.stringify(message)), {
|
|
7745
|
+
persistent: this.durable
|
|
7746
|
+
});
|
|
7747
|
+
}
|
|
7748
|
+
async subscribe(queue, handler) {
|
|
7749
|
+
const channel = await this.ready();
|
|
7750
|
+
await channel.assertQueue(queue, { durable: this.durable });
|
|
7751
|
+
const { consumerTag } = await channel.consume(queue, (msg) => {
|
|
7752
|
+
if (!msg) return;
|
|
7753
|
+
void Promise.resolve(handler(JSON.parse(msg.content.toString()))).then(() => channel.ack(msg)).catch(() => channel.nack(msg, false, false));
|
|
7754
|
+
});
|
|
7755
|
+
return async () => {
|
|
7756
|
+
await channel.cancel(consumerTag);
|
|
7757
|
+
};
|
|
7758
|
+
}
|
|
7759
|
+
async close() {
|
|
7760
|
+
await this.channel?.close();
|
|
7761
|
+
await this.connection?.close();
|
|
7762
|
+
this.channel = null;
|
|
7763
|
+
this.connection = null;
|
|
7764
|
+
}
|
|
7765
|
+
};
|
|
7766
|
+
|
|
7767
|
+
// src/tasks/manager.ts
|
|
7768
|
+
var logger = new JSONLogger("tempest_express_sdk.tasks");
|
|
7769
|
+
var TaskManager = class {
|
|
7770
|
+
broker;
|
|
7771
|
+
queue;
|
|
7772
|
+
handlers = /* @__PURE__ */ new Map();
|
|
7773
|
+
unsubscribe = null;
|
|
7774
|
+
/**
|
|
7775
|
+
* @param options - Broker and queue name.
|
|
7776
|
+
*/
|
|
7777
|
+
constructor(options = {}) {
|
|
7778
|
+
this.broker = options.broker ?? new MemoryBroker();
|
|
7779
|
+
this.queue = options.queue ?? "tasks";
|
|
7780
|
+
}
|
|
7781
|
+
/**
|
|
7782
|
+
* Register a handler for a named task.
|
|
7783
|
+
*
|
|
7784
|
+
* @param name - The task name.
|
|
7785
|
+
* @param handler - The handler invoked with the task payload.
|
|
7786
|
+
*/
|
|
7787
|
+
register(name, handler) {
|
|
7788
|
+
this.handlers.set(name, handler);
|
|
7789
|
+
}
|
|
7790
|
+
/**
|
|
7791
|
+
* Enqueue a task by name.
|
|
7792
|
+
*
|
|
7793
|
+
* @param name - The registered task name.
|
|
7794
|
+
* @param payload - The JSON-serializable payload.
|
|
7795
|
+
*/
|
|
7796
|
+
async enqueue(name, payload = {}) {
|
|
7797
|
+
const envelope = { name, payload };
|
|
7798
|
+
await this.broker.publish(this.queue, envelope);
|
|
7799
|
+
}
|
|
7800
|
+
/**
|
|
7801
|
+
* Start the worker: subscribe to the task queue and dispatch to handlers.
|
|
7802
|
+
* A task with no registered handler is logged and skipped.
|
|
7803
|
+
*/
|
|
7804
|
+
async start() {
|
|
7805
|
+
if (this.unsubscribe) return;
|
|
7806
|
+
this.unsubscribe = await this.broker.subscribe(this.queue, async (message) => {
|
|
7807
|
+
const { name, payload } = message;
|
|
7808
|
+
const handler = this.handlers.get(name);
|
|
7809
|
+
if (!handler) {
|
|
7810
|
+
logger.warning("No handler for task", { task: name });
|
|
7811
|
+
return;
|
|
7812
|
+
}
|
|
7813
|
+
await handler(payload);
|
|
7814
|
+
});
|
|
7815
|
+
}
|
|
7816
|
+
/** Stop the worker (stops consuming; does not close the broker). */
|
|
7817
|
+
async stop() {
|
|
7818
|
+
await this.unsubscribe?.();
|
|
7819
|
+
this.unsubscribe = null;
|
|
7820
|
+
}
|
|
7821
|
+
};
|
|
7822
|
+
|
|
7823
|
+
// src/flags/backends.ts
|
|
7824
|
+
function coerceFlag(value) {
|
|
7825
|
+
if (typeof value === "boolean") return value;
|
|
7826
|
+
if (typeof value === "number") return value !== 0;
|
|
7827
|
+
if (typeof value === "string") {
|
|
7828
|
+
return ["1", "true", "on", "yes", "enabled"].includes(value.trim().toLowerCase());
|
|
7829
|
+
}
|
|
7830
|
+
return false;
|
|
7831
|
+
}
|
|
7832
|
+
var MemoryFeatureFlagBackend = class {
|
|
7833
|
+
flags = /* @__PURE__ */ new Map();
|
|
7834
|
+
/**
|
|
7835
|
+
* @param initial - Initial flag → enabled map.
|
|
7836
|
+
*/
|
|
7837
|
+
constructor(initial = {}) {
|
|
7838
|
+
for (const [flag, enabled] of Object.entries(initial)) this.flags.set(flag, enabled);
|
|
7839
|
+
}
|
|
7840
|
+
/** Set or override a flag. */
|
|
7841
|
+
set(flag, enabled) {
|
|
7842
|
+
this.flags.set(flag, enabled);
|
|
7843
|
+
}
|
|
7844
|
+
resolve(flag) {
|
|
7845
|
+
return this.flags.has(flag) ? this.flags.get(flag) : null;
|
|
7846
|
+
}
|
|
7847
|
+
};
|
|
7848
|
+
var EnvFeatureFlagBackend = class {
|
|
7849
|
+
/**
|
|
7850
|
+
* @param env - Environment source (defaults to `process.env`).
|
|
7851
|
+
* @param prefix - Env var prefix. Default `FLAG_`.
|
|
7852
|
+
*/
|
|
7853
|
+
constructor(env = process.env, prefix = "FLAG_") {
|
|
7854
|
+
this.env = env;
|
|
7855
|
+
this.prefix = prefix;
|
|
7856
|
+
}
|
|
7857
|
+
env;
|
|
7858
|
+
prefix;
|
|
7859
|
+
key(flag) {
|
|
7860
|
+
return this.prefix + flag.toUpperCase().replace(/[^A-Z0-9]+/g, "_");
|
|
7861
|
+
}
|
|
7862
|
+
resolve(flag) {
|
|
7863
|
+
const raw = this.env[this.key(flag)];
|
|
7864
|
+
return raw === void 0 ? null : coerceFlag(raw);
|
|
7865
|
+
}
|
|
7866
|
+
};
|
|
7867
|
+
var CompositeFeatureFlagBackend = class {
|
|
7868
|
+
/**
|
|
7869
|
+
* @param backends - Backends in priority order.
|
|
7870
|
+
*/
|
|
7871
|
+
constructor(backends) {
|
|
7872
|
+
this.backends = backends;
|
|
7873
|
+
}
|
|
7874
|
+
backends;
|
|
7875
|
+
async resolve(flag, context) {
|
|
7876
|
+
for (const backend of this.backends) {
|
|
7877
|
+
const answer = await backend.resolve(flag, context);
|
|
7878
|
+
if (answer !== null) return answer;
|
|
7879
|
+
}
|
|
7880
|
+
return null;
|
|
7881
|
+
}
|
|
7882
|
+
};
|
|
7883
|
+
|
|
7884
|
+
// src/flags/service.ts
|
|
7885
|
+
var FeatureFlags = class {
|
|
7886
|
+
/**
|
|
7887
|
+
* @param backend - The resolving backend.
|
|
7888
|
+
* @param defaultEnabled - Value used when the backend returns `null`.
|
|
7889
|
+
*/
|
|
7890
|
+
constructor(backend, defaultEnabled = false) {
|
|
7891
|
+
this.backend = backend;
|
|
7892
|
+
this.defaultEnabled = defaultEnabled;
|
|
7893
|
+
}
|
|
7894
|
+
backend;
|
|
7895
|
+
defaultEnabled;
|
|
7896
|
+
/**
|
|
7897
|
+
* Whether `flag` is enabled for the given context.
|
|
7898
|
+
*
|
|
7899
|
+
* @param flag - The flag name.
|
|
7900
|
+
* @param context - Optional evaluation context.
|
|
7901
|
+
* @returns `true` when enabled (or default when the backend is undecided).
|
|
7902
|
+
*/
|
|
7903
|
+
async isEnabled(flag, context) {
|
|
7904
|
+
const answer = await this.backend.resolve(flag, context);
|
|
7905
|
+
return answer ?? this.defaultEnabled;
|
|
7906
|
+
}
|
|
7907
|
+
};
|
|
7908
|
+
function makeFlagGuard(flags, flag) {
|
|
7909
|
+
return (_req, _res, next) => {
|
|
7910
|
+
flags.isEnabled(flag).then((enabled) => {
|
|
7911
|
+
if (enabled) next();
|
|
7912
|
+
else next(new NotFoundException({ message: "Not found", details: { flag } }));
|
|
7913
|
+
}).catch(next);
|
|
7914
|
+
};
|
|
7915
|
+
}
|
|
7916
|
+
var LocalUploadStorage = class {
|
|
7917
|
+
root;
|
|
7918
|
+
baseUrl;
|
|
7919
|
+
/**
|
|
7920
|
+
* @param options - Filesystem root and public base URL.
|
|
7921
|
+
*/
|
|
7922
|
+
constructor(options) {
|
|
7923
|
+
this.root = options.root;
|
|
7924
|
+
this.baseUrl = options.baseUrl ?? "";
|
|
7925
|
+
}
|
|
7926
|
+
async save(key, data, options = {}) {
|
|
7927
|
+
const target = path.join(this.root, key);
|
|
7928
|
+
await promises.mkdir(path.dirname(target), { recursive: true });
|
|
7929
|
+
await promises.writeFile(target, data);
|
|
7930
|
+
return {
|
|
7931
|
+
key,
|
|
7932
|
+
url: this.url(key),
|
|
7933
|
+
size: data.byteLength,
|
|
7934
|
+
...options.contentType !== void 0 ? { contentType: options.contentType } : {}
|
|
7935
|
+
};
|
|
7936
|
+
}
|
|
7937
|
+
async read(key) {
|
|
7938
|
+
return promises.readFile(path.join(this.root, key));
|
|
7939
|
+
}
|
|
7940
|
+
async delete(key) {
|
|
7941
|
+
await promises.rm(path.join(this.root, key), { force: true });
|
|
7942
|
+
}
|
|
7943
|
+
url(key) {
|
|
7944
|
+
const base = this.baseUrl.replace(/\/$/, "");
|
|
7945
|
+
return base ? `${base}/${key}` : `/${key}`;
|
|
7946
|
+
}
|
|
7947
|
+
};
|
|
7948
|
+
function buildContentDisposition(filename, inline = false) {
|
|
7949
|
+
const disposition = inline ? "inline" : "attachment";
|
|
7950
|
+
const encoded = encodeURIComponent(filename);
|
|
7951
|
+
return `${disposition}; filename*=UTF-8''${encoded}`;
|
|
7952
|
+
}
|
|
7953
|
+
|
|
7954
|
+
// src/webpush/schemas.ts
|
|
7955
|
+
var webPushKeysSchema = zod.z.object({
|
|
7956
|
+
p256dh: zod.z.string().openapi({ description: "Client public key (base64url)." }),
|
|
7957
|
+
auth: zod.z.string().openapi({ description: "Client auth secret (base64url)." })
|
|
7958
|
+
}).openapi("WebPushKeys");
|
|
7959
|
+
var webPushSubscriptionSchema = zod.z.object({
|
|
7960
|
+
endpoint: zod.z.string().url().openapi({ description: "Push service endpoint URL." }),
|
|
7961
|
+
keys: webPushKeysSchema
|
|
7962
|
+
}).openapi("WebPushSubscription");
|
|
7963
|
+
var webPushPayloadSchema = zod.z.object({
|
|
7964
|
+
title: zod.z.string().openapi({ description: "Notification title." }),
|
|
7965
|
+
body: zod.z.string().optional().openapi({ description: "Notification body." }),
|
|
7966
|
+
url: zod.z.string().optional().openapi({ description: "URL opened on click." }),
|
|
7967
|
+
data: zod.z.record(zod.z.unknown()).optional().openapi({ description: "Extra data." })
|
|
7968
|
+
}).openapi("WebPushPayload");
|
|
7969
|
+
|
|
7970
|
+
// src/webpush/dispatcher.ts
|
|
7971
|
+
var WebPushError = class extends Error {
|
|
7972
|
+
constructor(message, statusCode) {
|
|
7973
|
+
super(message);
|
|
7974
|
+
this.statusCode = statusCode;
|
|
7975
|
+
this.name = "WebPushError";
|
|
7976
|
+
}
|
|
7977
|
+
statusCode;
|
|
7978
|
+
};
|
|
7979
|
+
var WebPushGoneError = class extends WebPushError {
|
|
7980
|
+
constructor(statusCode) {
|
|
7981
|
+
super("Push subscription is gone", statusCode);
|
|
7982
|
+
this.name = "WebPushGoneError";
|
|
7983
|
+
}
|
|
7984
|
+
};
|
|
7985
|
+
var cached4 = null;
|
|
7986
|
+
async function loadWebPush() {
|
|
7987
|
+
if (cached4) return cached4;
|
|
7988
|
+
try {
|
|
7989
|
+
const mod = await import('web-push');
|
|
7990
|
+
cached4 = mod.default ?? mod;
|
|
7991
|
+
} catch (cause) {
|
|
7992
|
+
throw new Error(
|
|
7993
|
+
"WebPushDispatcher requires the 'web-push' peer dependency. Install with `npm i web-push`.",
|
|
7994
|
+
{ cause }
|
|
7995
|
+
);
|
|
7996
|
+
}
|
|
7997
|
+
return cached4;
|
|
7998
|
+
}
|
|
7999
|
+
var WebPushDispatcher = class {
|
|
8000
|
+
/**
|
|
8001
|
+
* @param options - VAPID keys and subject.
|
|
8002
|
+
*/
|
|
8003
|
+
constructor(options) {
|
|
8004
|
+
this.options = options;
|
|
8005
|
+
}
|
|
8006
|
+
options;
|
|
8007
|
+
/**
|
|
8008
|
+
* Send a payload to a single subscription.
|
|
8009
|
+
*
|
|
8010
|
+
* @param subscription - The browser push subscription.
|
|
8011
|
+
* @param payload - The notification payload.
|
|
8012
|
+
* @throws {WebPushGoneError} When the subscription is expired (410/404).
|
|
8013
|
+
* @throws {WebPushError} On any other delivery failure.
|
|
8014
|
+
*/
|
|
8015
|
+
async send(subscription, payload) {
|
|
8016
|
+
const webpush = await loadWebPush();
|
|
8017
|
+
webpush.setVapidDetails(
|
|
8018
|
+
this.options.subject,
|
|
8019
|
+
this.options.publicKey,
|
|
8020
|
+
this.options.privateKey
|
|
8021
|
+
);
|
|
8022
|
+
try {
|
|
8023
|
+
await webpush.sendNotification(subscription, JSON.stringify(payload));
|
|
8024
|
+
} catch (error) {
|
|
8025
|
+
const status = error.statusCode;
|
|
8026
|
+
if (status === 404 || status === 410) throw new WebPushGoneError(status);
|
|
8027
|
+
throw new WebPushError(error.message, status);
|
|
8028
|
+
}
|
|
8029
|
+
}
|
|
8030
|
+
};
|
|
8031
|
+
|
|
6756
8032
|
// src/auth/schemas.ts
|
|
6757
8033
|
var signupSchema = zod.z.object({
|
|
6758
8034
|
email: zod.z.string().email().openapi({ description: "Login identifier (email)." }),
|
|
@@ -6999,7 +8275,7 @@ function makeAuthRouter(options) {
|
|
|
6999
8275
|
});
|
|
7000
8276
|
return router;
|
|
7001
8277
|
}
|
|
7002
|
-
var
|
|
8278
|
+
var logger2 = new JSONLogger("tempest_express_sdk.api.handlers");
|
|
7003
8279
|
var REQUEST_ID_HEADER = "X-Request-ID";
|
|
7004
8280
|
function requestIdMiddleware() {
|
|
7005
8281
|
return (req, res, next) => {
|
|
@@ -7040,7 +8316,7 @@ function makeAppExceptionHandler(options = {}) {
|
|
|
7040
8316
|
return;
|
|
7041
8317
|
}
|
|
7042
8318
|
const isServerError = exc.statusCode >= 500;
|
|
7043
|
-
|
|
8319
|
+
logger2.log(isServerError ? serverErrorLevel : "info", "AppException handled", {
|
|
7044
8320
|
path: req.path,
|
|
7045
8321
|
method: req.method,
|
|
7046
8322
|
statusCode: exc.statusCode,
|
|
@@ -7064,7 +8340,7 @@ function makeUnhandledExceptionHandler(options = {}) {
|
|
|
7064
8340
|
const { includeStack = false, logLevel = "error" } = options;
|
|
7065
8341
|
return (err, req, res, _next) => {
|
|
7066
8342
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
7067
|
-
|
|
8343
|
+
logger2.log(logLevel, "Unhandled exception", {
|
|
7068
8344
|
path: req.path,
|
|
7069
8345
|
method: req.method,
|
|
7070
8346
|
[HTTP_500_MARKER]: true,
|
|
@@ -7199,7 +8475,7 @@ function makeHealthRouter(options = {}) {
|
|
|
7199
8475
|
});
|
|
7200
8476
|
return router;
|
|
7201
8477
|
}
|
|
7202
|
-
var
|
|
8478
|
+
var logger3 = new JSONLogger("tempest_express_sdk.api.server");
|
|
7203
8479
|
function corsMiddleware(origins) {
|
|
7204
8480
|
const allowAll = origins === "*";
|
|
7205
8481
|
const allowList = new Set(Array.isArray(origins) ? origins : [origins]);
|
|
@@ -7258,14 +8534,14 @@ function runServer(app, options = {}) {
|
|
|
7258
8534
|
const port = options.port ?? 8e3;
|
|
7259
8535
|
return new Promise((resolve) => {
|
|
7260
8536
|
const server = app.listen(port, host, () => {
|
|
7261
|
-
|
|
8537
|
+
logger3.info("Server listening", { host, port });
|
|
7262
8538
|
resolve(server);
|
|
7263
8539
|
});
|
|
7264
8540
|
});
|
|
7265
8541
|
}
|
|
7266
8542
|
|
|
7267
8543
|
// src/version.ts
|
|
7268
|
-
var VERSION = "0.
|
|
8544
|
+
var VERSION = "0.3.0";
|
|
7269
8545
|
|
|
7270
8546
|
Object.defineProperty(exports, "OpenAPIRegistry", {
|
|
7271
8547
|
enumerable: true,
|
|
@@ -7427,35 +8703,64 @@ exports.BaseService = BaseService;
|
|
|
7427
8703
|
exports.CEP_PATTERN = CEP_PATTERN;
|
|
7428
8704
|
exports.CNPJ_PATTERN = CNPJ_PATTERN;
|
|
7429
8705
|
exports.CPF_PATTERN = CPF_PATTERN;
|
|
8706
|
+
exports.CircuitOpenError = CircuitOpenError;
|
|
8707
|
+
exports.CompositeFeatureFlagBackend = CompositeFeatureFlagBackend;
|
|
7430
8708
|
exports.ConflictException = ConflictException;
|
|
7431
8709
|
exports.DEFAULT_LOCALE = DEFAULT_LOCALE;
|
|
8710
|
+
exports.EmailUtils = EmailUtils;
|
|
8711
|
+
exports.EnvFeatureFlagBackend = EnvFeatureFlagBackend;
|
|
8712
|
+
exports.EventStream = EventStream;
|
|
7432
8713
|
exports.ExpiredTokenException = ExpiredTokenException;
|
|
8714
|
+
exports.FeatureFlags = FeatureFlags;
|
|
7433
8715
|
exports.ForbiddenException = ForbiddenException;
|
|
8716
|
+
exports.HTTPClient = HTTPClient;
|
|
7434
8717
|
exports.HTTP_500_MARKER = HTTP_500_MARKER;
|
|
7435
8718
|
exports.InvalidTokenException = InvalidTokenException;
|
|
7436
8719
|
exports.JSONLogger = JSONLogger;
|
|
7437
8720
|
exports.JWTUtils = JWTUtils;
|
|
8721
|
+
exports.LocalUploadStorage = LocalUploadStorage;
|
|
8722
|
+
exports.MemoryBroker = MemoryBroker;
|
|
8723
|
+
exports.MemoryCacheManager = MemoryCacheManager;
|
|
8724
|
+
exports.MemoryFeatureFlagBackend = MemoryFeatureFlagBackend;
|
|
8725
|
+
exports.MemorySessionStore = MemorySessionStore;
|
|
7438
8726
|
exports.MemoryThrottleBackend = MemoryThrottleBackend;
|
|
7439
8727
|
exports.MessageCatalog = MessageCatalog;
|
|
8728
|
+
exports.MetricsUtils = MetricsUtils;
|
|
7440
8729
|
exports.NotFoundException = NotFoundException;
|
|
7441
8730
|
exports.PHONE_BR_PATTERN = PHONE_BR_PATTERN;
|
|
7442
8731
|
exports.PasswordUtils = PasswordUtils;
|
|
7443
8732
|
exports.REQUEST_ID_HEADER = REQUEST_ID_HEADER;
|
|
8733
|
+
exports.RabbitBroker = RabbitBroker;
|
|
8734
|
+
exports.RedisCacheManager = RedisCacheManager;
|
|
7444
8735
|
exports.Region = Region;
|
|
8736
|
+
exports.RetryPolicy = RetryPolicy;
|
|
8737
|
+
exports.SSEBroker = SSEBroker;
|
|
8738
|
+
exports.ServerSentEvent = ServerSentEvent;
|
|
8739
|
+
exports.SessionService = SessionService;
|
|
8740
|
+
exports.TOTPHelper = TOTPHelper;
|
|
8741
|
+
exports.TaskManager = TaskManager;
|
|
7445
8742
|
exports.TooManyRequestsException = TooManyRequestsException;
|
|
7446
8743
|
exports.UF = UF;
|
|
7447
8744
|
exports.UnauthorizedException = UnauthorizedException;
|
|
7448
8745
|
exports.UserAuthService = UserAuthService;
|
|
7449
8746
|
exports.VERSION = VERSION;
|
|
7450
8747
|
exports.ValidationException = ValidationException;
|
|
8748
|
+
exports.WebPushDispatcher = WebPushDispatcher;
|
|
8749
|
+
exports.WebPushError = WebPushError;
|
|
8750
|
+
exports.WebPushGoneError = WebPushGoneError;
|
|
8751
|
+
exports.WebSocketHub = WebSocketHub;
|
|
8752
|
+
exports.attachWebSocketHub = attachWebSocketHub;
|
|
7451
8753
|
exports.authResponseSchema = authResponseSchema;
|
|
7452
8754
|
exports.baseAppSettingsSchema = baseAppSettingsSchema;
|
|
7453
8755
|
exports.baseAppSettingsShape = baseAppSettingsShape;
|
|
7454
8756
|
exports.baseResponseSchema = baseResponseSchema;
|
|
7455
8757
|
exports.bearerToken = bearerToken;
|
|
8758
|
+
exports.buildContentDisposition = buildContentDisposition;
|
|
8759
|
+
exports.cached = cached3;
|
|
7456
8760
|
exports.cepField = cepField;
|
|
7457
8761
|
exports.citiesByUf = citiesByUf;
|
|
7458
8762
|
exports.cnpjField = cnpjField;
|
|
8763
|
+
exports.coerceFlag = coerceFlag;
|
|
7459
8764
|
exports.configureLogging = configureLogging;
|
|
7460
8765
|
exports.corsSettingsShape = corsSettingsShape;
|
|
7461
8766
|
exports.cpfField = cpfField;
|
|
@@ -7474,6 +8779,7 @@ exports.encodeCursor = encodeCursor;
|
|
|
7474
8779
|
exports.generateOpaqueToken = generateOpaqueToken;
|
|
7475
8780
|
exports.generateOpenApiDocument = generateOpenApiDocument;
|
|
7476
8781
|
exports.getAuth = getAuth;
|
|
8782
|
+
exports.getClientIp = getClientIp;
|
|
7477
8783
|
exports.getConditions = getConditions;
|
|
7478
8784
|
exports.getPaginationConditions = getPaginationConditions;
|
|
7479
8785
|
exports.getRequestId = getRequestId;
|
|
@@ -7491,8 +8797,10 @@ exports.loadSettings = loadSettings;
|
|
|
7491
8797
|
exports.loginSchema = loginSchema;
|
|
7492
8798
|
exports.makeAppExceptionHandler = makeAppExceptionHandler;
|
|
7493
8799
|
exports.makeAuthRouter = makeAuthRouter;
|
|
8800
|
+
exports.makeFlagGuard = makeFlagGuard;
|
|
7494
8801
|
exports.makeHealthRouter = makeHealthRouter;
|
|
7495
8802
|
exports.makeJwtAuthMiddleware = makeJwtAuthMiddleware;
|
|
8803
|
+
exports.makeSessionMiddleware = makeSessionMiddleware;
|
|
7496
8804
|
exports.makeUnhandledExceptionHandler = makeUnhandledExceptionHandler;
|
|
7497
8805
|
exports.modifyDict = modifyDict;
|
|
7498
8806
|
exports.mountOpenApiJson = mountOpenApiJson;
|
|
@@ -7509,6 +8817,7 @@ exports.onlyDigits = onlyDigits;
|
|
|
7509
8817
|
exports.paginationFilterSchema = paginationFilterSchema;
|
|
7510
8818
|
exports.paginationSchema = paginationSchema;
|
|
7511
8819
|
exports.parseAcceptLanguage = parseAcceptLanguage;
|
|
8820
|
+
exports.parseCookies = parseCookies;
|
|
7512
8821
|
exports.phoneBrField = phoneBrField;
|
|
7513
8822
|
exports.refreshSchema = refreshSchema;
|
|
7514
8823
|
exports.registerExceptionHandlers = registerExceptionHandlers;
|
|
@@ -7517,17 +8826,24 @@ exports.requireRoles = requireRoles;
|
|
|
7517
8826
|
exports.runServer = runServer;
|
|
7518
8827
|
exports.runWithRequestContext = runWithRequestContext;
|
|
7519
8828
|
exports.serverSettingsShape = serverSettingsShape;
|
|
8829
|
+
exports.sessionCookie = sessionCookie;
|
|
7520
8830
|
exports.setRequestId = setRequestId;
|
|
7521
8831
|
exports.signupSchema = signupSchema;
|
|
8832
|
+
exports.sseResponse = sseResponse;
|
|
7522
8833
|
exports.statesByRegion = statesByRegion;
|
|
7523
8834
|
exports.tableNameFor = tableNameFor;
|
|
7524
8835
|
exports.toDict = toDict;
|
|
7525
8836
|
exports.toUtc = toUtc;
|
|
8837
|
+
exports.tokenFromUrl = tokenFromUrl;
|
|
7526
8838
|
exports.tokenPairSchema = tokenPairSchema;
|
|
7527
8839
|
exports.ufField = ufField;
|
|
7528
8840
|
exports.updatedByColumn = updatedByColumn;
|
|
7529
8841
|
exports.userPublicSchema = userPublicSchema;
|
|
7530
8842
|
exports.utcnow = utcnow;
|
|
7531
8843
|
exports.verifyOpaqueToken = verifyOpaqueToken;
|
|
8844
|
+
exports.webPushKeysSchema = webPushKeysSchema;
|
|
8845
|
+
exports.webPushPayloadSchema = webPushPayloadSchema;
|
|
8846
|
+
exports.webPushSubscriptionSchema = webPushSubscriptionSchema;
|
|
8847
|
+
exports.wsEnvelopeSchema = wsEnvelopeSchema;
|
|
7532
8848
|
//# sourceMappingURL=index.cjs.map
|
|
7533
8849
|
//# sourceMappingURL=index.cjs.map
|