@outray/runtime-shared 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +4 -2
- package/dist/buffered-logger.cjs +102 -0
- package/dist/buffered-logger.cjs.map +1 -0
- package/dist/buffered-logger.d.cts +26 -0
- package/dist/buffered-logger.d.ts +26 -0
- package/dist/buffered-logger.js +77 -0
- package/dist/buffered-logger.js.map +1 -0
- package/dist/chunk-7Z55KL36.js +1111 -0
- package/dist/chunk-7Z55KL36.js.map +1 -0
- package/dist/chunk-EBXUVSOO.js +37 -0
- package/dist/chunk-EBXUVSOO.js.map +1 -0
- package/dist/chunk-XAMVXW6B.js +19 -0
- package/dist/chunk-XAMVXW6B.js.map +1 -0
- package/dist/durable-queue.cjs +316 -0
- package/dist/durable-queue.cjs.map +1 -0
- package/dist/durable-queue.d.cts +41 -0
- package/dist/durable-queue.d.ts +41 -0
- package/dist/durable-queue.js +290 -0
- package/dist/durable-queue.js.map +1 -0
- package/dist/helpers.cjs +1167 -0
- package/dist/helpers.cjs.map +1 -0
- package/dist/helpers.d.cts +6 -0
- package/dist/helpers.d.ts +6 -0
- package/dist/helpers.js +14 -0
- package/dist/helpers.js.map +1 -0
- package/dist/postgres-ssl.cjs +37 -0
- package/dist/postgres-ssl.cjs.map +1 -0
- package/dist/postgres-ssl.d.cts +9 -0
- package/dist/postgres-ssl.d.ts +9 -0
- package/dist/postgres-ssl.js +12 -0
- package/dist/postgres-ssl.js.map +1 -0
- package/dist/protocol.cjs +38 -0
- package/dist/protocol.cjs.map +1 -0
- package/dist/protocol.d.cts +8 -0
- package/dist/protocol.d.ts +8 -0
- package/dist/protocol.js +13 -0
- package/dist/protocol.js.map +1 -0
- package/dist/public-hosts.cjs +116 -0
- package/dist/public-hosts.cjs.map +1 -0
- package/dist/public-hosts.d.cts +29 -0
- package/dist/public-hosts.d.ts +29 -0
- package/dist/public-hosts.js +82 -0
- package/dist/public-hosts.js.map +1 -0
- package/dist/tinybird-records.cjs +160 -0
- package/dist/tinybird-records.cjs.map +1 -0
- package/dist/tinybird-records.d.cts +62 -0
- package/dist/tinybird-records.d.ts +62 -0
- package/dist/tinybird-records.js +129 -0
- package/dist/tinybird-records.js.map +1 -0
- package/dist/tunnel-accounting.cjs +71 -0
- package/dist/tunnel-accounting.cjs.map +1 -0
- package/dist/tunnel-accounting.d.cts +12 -0
- package/dist/tunnel-accounting.d.ts +12 -0
- package/dist/tunnel-accounting.js +32 -0
- package/dist/tunnel-accounting.js.map +1 -0
- package/dist/tunnel-product-usage.cjs +107 -0
- package/dist/tunnel-product-usage.cjs.map +1 -0
- package/dist/tunnel-product-usage.d.cts +26 -0
- package/dist/tunnel-product-usage.d.ts +26 -0
- package/dist/tunnel-product-usage.js +81 -0
- package/dist/tunnel-product-usage.js.map +1 -0
- package/dist/types.cjs +19 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +105 -0
- package/dist/types.d.ts +105 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/dist/usage-writer.cjs +43 -0
- package/dist/usage-writer.cjs.map +1 -0
- package/dist/usage-writer.d.cts +14 -0
- package/dist/usage-writer.d.ts +14 -0
- package/dist/usage-writer.js +7 -0
- package/dist/usage-writer.js.map +1 -0
- package/dist/utils.cjs +1171 -0
- package/dist/utils.cjs.map +1 -0
- package/dist/utils.d.cts +2 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +19 -0
- package/dist/utils.js.map +1 -0
- package/dist/words.cjs +1136 -0
- package/dist/words.cjs.map +1 -0
- package/dist/words.d.cts +4 -0
- package/dist/words.d.ts +4 -0
- package/dist/words.js +9 -0
- package/dist/words.js.map +1 -0
- package/package.json +99 -4
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/tinybird-records.ts
|
|
21
|
+
var tinybird_records_exports = {};
|
|
22
|
+
__export(tinybird_records_exports, {
|
|
23
|
+
TunnelTinybirdClient: () => TunnelTinybirdClient,
|
|
24
|
+
captureRecordIsCurrent: () => captureRecordIsCurrent,
|
|
25
|
+
normalizeRetentionDays: () => normalizeRetentionDays,
|
|
26
|
+
protocolEventRecord: () => protocolEventRecord,
|
|
27
|
+
requestCaptureRecord: () => requestCaptureRecord,
|
|
28
|
+
tinybirdTimestamp: () => tinybirdTimestamp,
|
|
29
|
+
tunnelEventRecord: () => tunnelEventRecord
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(tinybird_records_exports);
|
|
32
|
+
var import_node_crypto = require("crypto");
|
|
33
|
+
function normalizeRetentionDays(retentionDays) {
|
|
34
|
+
if (!Number.isFinite(retentionDays)) return 3;
|
|
35
|
+
return Math.min(90, Math.max(1, Math.trunc(retentionDays)));
|
|
36
|
+
}
|
|
37
|
+
function tinybirdTimestamp(timestamp) {
|
|
38
|
+
return new Date(timestamp).toISOString().replace("T", " ").replace("Z", "");
|
|
39
|
+
}
|
|
40
|
+
function tunnelEventRecord(event) {
|
|
41
|
+
return {
|
|
42
|
+
...event,
|
|
43
|
+
request_id: event.request_id || "",
|
|
44
|
+
event_id: event.request_id || (0, import_node_crypto.randomUUID)(),
|
|
45
|
+
timestamp: tinybirdTimestamp(event.timestamp),
|
|
46
|
+
ingested_at: tinybirdTimestamp(Date.now()),
|
|
47
|
+
retention_days: normalizeRetentionDays(event.retention_days)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function requestCaptureRecord(capture) {
|
|
51
|
+
return {
|
|
52
|
+
...capture,
|
|
53
|
+
timestamp: tinybirdTimestamp(capture.timestamp),
|
|
54
|
+
ingested_at: tinybirdTimestamp(Date.now()),
|
|
55
|
+
retention_days: normalizeRetentionDays(capture.retention_days),
|
|
56
|
+
request_headers: JSON.stringify(capture.request_headers),
|
|
57
|
+
response_headers: JSON.stringify(capture.response_headers)
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function protocolEventRecord(event) {
|
|
61
|
+
return {
|
|
62
|
+
...event,
|
|
63
|
+
event_id: (0, import_node_crypto.randomUUID)(),
|
|
64
|
+
timestamp: tinybirdTimestamp(event.timestamp),
|
|
65
|
+
ingested_at: tinybirdTimestamp(Date.now()),
|
|
66
|
+
retention_days: normalizeRetentionDays(event.retention_days)
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
var TunnelTinybirdClient = class {
|
|
70
|
+
constructor(host, token) {
|
|
71
|
+
this.host = host;
|
|
72
|
+
this.token = token;
|
|
73
|
+
}
|
|
74
|
+
host;
|
|
75
|
+
token;
|
|
76
|
+
get configured() {
|
|
77
|
+
if (!this.host || !this.token) return false;
|
|
78
|
+
try {
|
|
79
|
+
const url = new URL(this.host);
|
|
80
|
+
return (url.protocol === "https:" || url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)) && !url.username && !url.password && !url.search && !url.hash && url.pathname === "/";
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async append(datasource, records) {
|
|
86
|
+
if (datasource === "tunnel_request_captures") {
|
|
87
|
+
const oversized = records.filter((record) => Buffer.byteLength(JSON.stringify(record)) > 3 * 1024 * 1024).length;
|
|
88
|
+
if (oversized) console.error(`Tinybird capture delivery rejected ${oversized} oversized records; other captures will continue`);
|
|
89
|
+
records = records.filter((record) => captureRecordIsCurrent(record) && Buffer.byteLength(JSON.stringify(record)) <= 3 * 1024 * 1024);
|
|
90
|
+
}
|
|
91
|
+
if (!records.length) return;
|
|
92
|
+
if (!this.configured) throw new Error("Tinybird tunnel ingestion is not configured");
|
|
93
|
+
const maxBytes = 4 * 1024 * 1024;
|
|
94
|
+
let lines = [];
|
|
95
|
+
let bytes = 0;
|
|
96
|
+
for (const record of records) {
|
|
97
|
+
const line = JSON.stringify(record);
|
|
98
|
+
const lineBytes = Buffer.byteLength(line) + 1;
|
|
99
|
+
if (lineBytes > maxBytes) {
|
|
100
|
+
throw new Error(`Tinybird ${datasource} record exceeds the ingestion size limit`);
|
|
101
|
+
}
|
|
102
|
+
if (bytes + lineBytes > maxBytes && lines.length) {
|
|
103
|
+
await this.appendBatch(datasource, lines);
|
|
104
|
+
lines = [];
|
|
105
|
+
bytes = 0;
|
|
106
|
+
}
|
|
107
|
+
lines.push(line);
|
|
108
|
+
bytes += lineBytes;
|
|
109
|
+
}
|
|
110
|
+
if (lines.length) await this.appendBatch(datasource, lines);
|
|
111
|
+
}
|
|
112
|
+
async appendBatch(datasource, lines) {
|
|
113
|
+
const response = await fetch(
|
|
114
|
+
`${this.host.replace(/\/$/, "")}/v0/events?name=${encodeURIComponent(datasource)}&wait=true`,
|
|
115
|
+
{
|
|
116
|
+
method: "POST",
|
|
117
|
+
headers: {
|
|
118
|
+
Authorization: `Bearer ${this.token}`,
|
|
119
|
+
"Content-Type": "application/x-ndjson"
|
|
120
|
+
},
|
|
121
|
+
body: lines.join("\n"),
|
|
122
|
+
signal: AbortSignal.timeout(15e3)
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
if (!response.ok) {
|
|
126
|
+
await response.body?.cancel();
|
|
127
|
+
throw new Error(`Tinybird ${datasource} ingestion failed (${response.status})`);
|
|
128
|
+
}
|
|
129
|
+
let result;
|
|
130
|
+
try {
|
|
131
|
+
const candidate = await response.json();
|
|
132
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
|
|
133
|
+
throw new Error("invalid acknowledgement");
|
|
134
|
+
}
|
|
135
|
+
result = candidate;
|
|
136
|
+
} catch {
|
|
137
|
+
throw new Error(`Tinybird ${datasource} ingestion returned an invalid acknowledgement`);
|
|
138
|
+
}
|
|
139
|
+
if (Number(result.quarantined_rows) > 0 || Number(result.successful_rows) !== lines.length) {
|
|
140
|
+
throw new Error(`Tinybird ${datasource} ingestion did not acknowledge the complete batch`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
function captureRecordIsCurrent(record, now = Date.now()) {
|
|
145
|
+
if (typeof record.timestamp !== "string") return false;
|
|
146
|
+
const timestamp = Date.parse(`${record.timestamp.replace(" ", "T").replace(/Z$/, "")}Z`);
|
|
147
|
+
const days = normalizeRetentionDays(Number(record.retention_days));
|
|
148
|
+
return Number.isFinite(timestamp) && timestamp + days * 24 * 60 * 60 * 1e3 > now;
|
|
149
|
+
}
|
|
150
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
151
|
+
0 && (module.exports = {
|
|
152
|
+
TunnelTinybirdClient,
|
|
153
|
+
captureRecordIsCurrent,
|
|
154
|
+
normalizeRetentionDays,
|
|
155
|
+
protocolEventRecord,
|
|
156
|
+
requestCaptureRecord,
|
|
157
|
+
tinybirdTimestamp,
|
|
158
|
+
tunnelEventRecord
|
|
159
|
+
});
|
|
160
|
+
//# sourceMappingURL=tinybird-records.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tinybird-records.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\n\nexport interface TunnelEvent {\n request_id?: string;\n timestamp: number;\n tunnel_id: string;\n organization_id: string;\n retention_days: number;\n host: string;\n method: string;\n path: string;\n status_code: number;\n request_duration_ms: number;\n bytes_in: number;\n bytes_out: number;\n client_ip: string;\n user_agent: string;\n}\n\nexport interface RequestCapture {\n id: string;\n timestamp: number;\n tunnel_id: string;\n organization_id: string;\n retention_days: number;\n request_headers: Record<string, string | string[]>;\n request_body: string | null;\n request_body_size: number;\n response_headers: Record<string, string | string[]>;\n response_body: string | null;\n response_body_size: number;\n}\n\nexport interface ProtocolEvent {\n direction?: \"in\" | \"out\";\n timestamp: number;\n tunnel_id: string;\n organization_id: string;\n retention_days: number;\n protocol: \"tcp\" | \"udp\";\n event_type: \"connection\" | \"data\" | \"close\" | \"packet\";\n connection_id: string;\n client_ip: string;\n client_port: number;\n bytes_in: number;\n bytes_out: number;\n duration_ms: number;\n}\n\nexport type TinybirdTunnelRecord = Record<string, string | number | null>;\n\nexport function normalizeRetentionDays(retentionDays: number): number {\n if (!Number.isFinite(retentionDays)) return 3;\n return Math.min(90, Math.max(1, Math.trunc(retentionDays)));\n}\n\nexport function tinybirdTimestamp(timestamp: number | Date): string {\n return new Date(timestamp).toISOString().replace(\"T\", \" \").replace(\"Z\", \"\");\n}\n\nexport function tunnelEventRecord(event: TunnelEvent): TinybirdTunnelRecord {\n return {\n ...event,\n request_id: event.request_id || \"\",\n event_id: event.request_id || randomUUID(),\n timestamp: tinybirdTimestamp(event.timestamp),\n ingested_at: tinybirdTimestamp(Date.now()),\n retention_days: normalizeRetentionDays(event.retention_days),\n };\n}\n\nexport function requestCaptureRecord(capture: RequestCapture): TinybirdTunnelRecord {\n return {\n ...capture,\n timestamp: tinybirdTimestamp(capture.timestamp),\n ingested_at: tinybirdTimestamp(Date.now()),\n retention_days: normalizeRetentionDays(capture.retention_days),\n request_headers: JSON.stringify(capture.request_headers),\n response_headers: JSON.stringify(capture.response_headers),\n };\n}\n\nexport function protocolEventRecord(event: ProtocolEvent): TinybirdTunnelRecord {\n return {\n ...event,\n event_id: randomUUID(),\n timestamp: tinybirdTimestamp(event.timestamp),\n ingested_at: tinybirdTimestamp(Date.now()),\n retention_days: normalizeRetentionDays(event.retention_days),\n };\n}\n\nexport class TunnelTinybirdClient {\n constructor(private readonly host: string, private readonly token: string) {}\n\n get configured(): boolean {\n if (!this.host || !this.token) return false;\n try {\n const url = new URL(this.host);\n return (url.protocol === \"https:\" ||\n (url.protocol === \"http:\" && [\"localhost\", \"127.0.0.1\", \"[::1]\"].includes(url.hostname))) &&\n !url.username && !url.password && !url.search && !url.hash && url.pathname === \"/\";\n } catch {\n return false;\n }\n }\n\n async append(datasource: string, records: TinybirdTunnelRecord[]): Promise<void> {\n if (datasource === \"tunnel_request_captures\") {\n const oversized = records.filter((record) => Buffer.byteLength(JSON.stringify(record)) > 3 * 1024 * 1024).length;\n if (oversized) console.error(`Tinybird capture delivery rejected ${oversized} oversized records; other captures will continue`);\n records = records.filter((record) => captureRecordIsCurrent(record) && Buffer.byteLength(JSON.stringify(record)) <= 3 * 1024 * 1024);\n }\n if (!records.length) return;\n if (!this.configured) throw new Error(\"Tinybird tunnel ingestion is not configured\");\n // Keep requests below the Events API payload ceiling, even when captures\n // contain large base64 request/response bodies. Stable ids make retrying an\n // already accepted prefix safe when a later request fails.\n const maxBytes = 4 * 1024 * 1024;\n let lines: string[] = [];\n let bytes = 0;\n for (const record of records) {\n const line = JSON.stringify(record);\n const lineBytes = Buffer.byteLength(line) + 1;\n if (lineBytes > maxBytes) {\n throw new Error(`Tinybird ${datasource} record exceeds the ingestion size limit`);\n }\n if (bytes + lineBytes > maxBytes && lines.length) {\n await this.appendBatch(datasource, lines);\n lines = [];\n bytes = 0;\n }\n lines.push(line);\n bytes += lineBytes;\n }\n if (lines.length) await this.appendBatch(datasource, lines);\n }\n\n private async appendBatch(datasource: string, lines: string[]): Promise<void> {\n const response = await fetch(\n `${this.host.replace(/\\/$/, \"\")}/v0/events?name=${encodeURIComponent(datasource)}&wait=true`,\n {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.token}`,\n \"Content-Type\": \"application/x-ndjson\",\n },\n body: lines.join(\"\\n\"),\n signal: AbortSignal.timeout(15_000),\n },\n );\n // Error bodies can echo capture values. Never include them in logs/errors.\n if (!response.ok) {\n await response.body?.cancel();\n throw new Error(`Tinybird ${datasource} ingestion failed (${response.status})`);\n }\n let result: { successful_rows?: number; quarantined_rows?: number };\n try {\n const candidate: unknown = await response.json();\n if (!candidate || typeof candidate !== \"object\" || Array.isArray(candidate)) {\n throw new Error(\"invalid acknowledgement\");\n }\n result = candidate as typeof result;\n } catch {\n throw new Error(`Tinybird ${datasource} ingestion returned an invalid acknowledgement`);\n }\n if (Number(result.quarantined_rows) > 0 || Number(result.successful_rows) !== lines.length) {\n throw new Error(`Tinybird ${datasource} ingestion did not acknowledge the complete batch`);\n }\n }\n}\n\n/** Backlogs must never republish captures past their individual plan expiry. */\nexport function captureRecordIsCurrent(record: TinybirdTunnelRecord, now = Date.now()): boolean {\n if (typeof record.timestamp !== \"string\") return false;\n const timestamp = Date.parse(`${record.timestamp.replace(\" \", \"T\").replace(/Z$/, \"\")}Z`);\n const days = normalizeRetentionDays(Number(record.retention_days));\n return Number.isFinite(timestamp) && timestamp + days * 24 * 60 * 60 * 1000 > now;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
interface TunnelEvent {
|
|
2
|
+
request_id?: string;
|
|
3
|
+
timestamp: number;
|
|
4
|
+
tunnel_id: string;
|
|
5
|
+
organization_id: string;
|
|
6
|
+
retention_days: number;
|
|
7
|
+
host: string;
|
|
8
|
+
method: string;
|
|
9
|
+
path: string;
|
|
10
|
+
status_code: number;
|
|
11
|
+
request_duration_ms: number;
|
|
12
|
+
bytes_in: number;
|
|
13
|
+
bytes_out: number;
|
|
14
|
+
client_ip: string;
|
|
15
|
+
user_agent: string;
|
|
16
|
+
}
|
|
17
|
+
interface RequestCapture {
|
|
18
|
+
id: string;
|
|
19
|
+
timestamp: number;
|
|
20
|
+
tunnel_id: string;
|
|
21
|
+
organization_id: string;
|
|
22
|
+
retention_days: number;
|
|
23
|
+
request_headers: Record<string, string | string[]>;
|
|
24
|
+
request_body: string | null;
|
|
25
|
+
request_body_size: number;
|
|
26
|
+
response_headers: Record<string, string | string[]>;
|
|
27
|
+
response_body: string | null;
|
|
28
|
+
response_body_size: number;
|
|
29
|
+
}
|
|
30
|
+
interface ProtocolEvent {
|
|
31
|
+
direction?: "in" | "out";
|
|
32
|
+
timestamp: number;
|
|
33
|
+
tunnel_id: string;
|
|
34
|
+
organization_id: string;
|
|
35
|
+
retention_days: number;
|
|
36
|
+
protocol: "tcp" | "udp";
|
|
37
|
+
event_type: "connection" | "data" | "close" | "packet";
|
|
38
|
+
connection_id: string;
|
|
39
|
+
client_ip: string;
|
|
40
|
+
client_port: number;
|
|
41
|
+
bytes_in: number;
|
|
42
|
+
bytes_out: number;
|
|
43
|
+
duration_ms: number;
|
|
44
|
+
}
|
|
45
|
+
type TinybirdTunnelRecord = Record<string, string | number | null>;
|
|
46
|
+
declare function normalizeRetentionDays(retentionDays: number): number;
|
|
47
|
+
declare function tinybirdTimestamp(timestamp: number | Date): string;
|
|
48
|
+
declare function tunnelEventRecord(event: TunnelEvent): TinybirdTunnelRecord;
|
|
49
|
+
declare function requestCaptureRecord(capture: RequestCapture): TinybirdTunnelRecord;
|
|
50
|
+
declare function protocolEventRecord(event: ProtocolEvent): TinybirdTunnelRecord;
|
|
51
|
+
declare class TunnelTinybirdClient {
|
|
52
|
+
private readonly host;
|
|
53
|
+
private readonly token;
|
|
54
|
+
constructor(host: string, token: string);
|
|
55
|
+
get configured(): boolean;
|
|
56
|
+
append(datasource: string, records: TinybirdTunnelRecord[]): Promise<void>;
|
|
57
|
+
private appendBatch;
|
|
58
|
+
}
|
|
59
|
+
/** Backlogs must never republish captures past their individual plan expiry. */
|
|
60
|
+
declare function captureRecordIsCurrent(record: TinybirdTunnelRecord, now?: number): boolean;
|
|
61
|
+
|
|
62
|
+
export { type ProtocolEvent, type RequestCapture, type TinybirdTunnelRecord, type TunnelEvent, TunnelTinybirdClient, captureRecordIsCurrent, normalizeRetentionDays, protocolEventRecord, requestCaptureRecord, tinybirdTimestamp, tunnelEventRecord };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
interface TunnelEvent {
|
|
2
|
+
request_id?: string;
|
|
3
|
+
timestamp: number;
|
|
4
|
+
tunnel_id: string;
|
|
5
|
+
organization_id: string;
|
|
6
|
+
retention_days: number;
|
|
7
|
+
host: string;
|
|
8
|
+
method: string;
|
|
9
|
+
path: string;
|
|
10
|
+
status_code: number;
|
|
11
|
+
request_duration_ms: number;
|
|
12
|
+
bytes_in: number;
|
|
13
|
+
bytes_out: number;
|
|
14
|
+
client_ip: string;
|
|
15
|
+
user_agent: string;
|
|
16
|
+
}
|
|
17
|
+
interface RequestCapture {
|
|
18
|
+
id: string;
|
|
19
|
+
timestamp: number;
|
|
20
|
+
tunnel_id: string;
|
|
21
|
+
organization_id: string;
|
|
22
|
+
retention_days: number;
|
|
23
|
+
request_headers: Record<string, string | string[]>;
|
|
24
|
+
request_body: string | null;
|
|
25
|
+
request_body_size: number;
|
|
26
|
+
response_headers: Record<string, string | string[]>;
|
|
27
|
+
response_body: string | null;
|
|
28
|
+
response_body_size: number;
|
|
29
|
+
}
|
|
30
|
+
interface ProtocolEvent {
|
|
31
|
+
direction?: "in" | "out";
|
|
32
|
+
timestamp: number;
|
|
33
|
+
tunnel_id: string;
|
|
34
|
+
organization_id: string;
|
|
35
|
+
retention_days: number;
|
|
36
|
+
protocol: "tcp" | "udp";
|
|
37
|
+
event_type: "connection" | "data" | "close" | "packet";
|
|
38
|
+
connection_id: string;
|
|
39
|
+
client_ip: string;
|
|
40
|
+
client_port: number;
|
|
41
|
+
bytes_in: number;
|
|
42
|
+
bytes_out: number;
|
|
43
|
+
duration_ms: number;
|
|
44
|
+
}
|
|
45
|
+
type TinybirdTunnelRecord = Record<string, string | number | null>;
|
|
46
|
+
declare function normalizeRetentionDays(retentionDays: number): number;
|
|
47
|
+
declare function tinybirdTimestamp(timestamp: number | Date): string;
|
|
48
|
+
declare function tunnelEventRecord(event: TunnelEvent): TinybirdTunnelRecord;
|
|
49
|
+
declare function requestCaptureRecord(capture: RequestCapture): TinybirdTunnelRecord;
|
|
50
|
+
declare function protocolEventRecord(event: ProtocolEvent): TinybirdTunnelRecord;
|
|
51
|
+
declare class TunnelTinybirdClient {
|
|
52
|
+
private readonly host;
|
|
53
|
+
private readonly token;
|
|
54
|
+
constructor(host: string, token: string);
|
|
55
|
+
get configured(): boolean;
|
|
56
|
+
append(datasource: string, records: TinybirdTunnelRecord[]): Promise<void>;
|
|
57
|
+
private appendBatch;
|
|
58
|
+
}
|
|
59
|
+
/** Backlogs must never republish captures past their individual plan expiry. */
|
|
60
|
+
declare function captureRecordIsCurrent(record: TinybirdTunnelRecord, now?: number): boolean;
|
|
61
|
+
|
|
62
|
+
export { type ProtocolEvent, type RequestCapture, type TinybirdTunnelRecord, type TunnelEvent, TunnelTinybirdClient, captureRecordIsCurrent, normalizeRetentionDays, protocolEventRecord, requestCaptureRecord, tinybirdTimestamp, tunnelEventRecord };
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// src/tinybird-records.ts
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
3
|
+
function normalizeRetentionDays(retentionDays) {
|
|
4
|
+
if (!Number.isFinite(retentionDays)) return 3;
|
|
5
|
+
return Math.min(90, Math.max(1, Math.trunc(retentionDays)));
|
|
6
|
+
}
|
|
7
|
+
function tinybirdTimestamp(timestamp) {
|
|
8
|
+
return new Date(timestamp).toISOString().replace("T", " ").replace("Z", "");
|
|
9
|
+
}
|
|
10
|
+
function tunnelEventRecord(event) {
|
|
11
|
+
return {
|
|
12
|
+
...event,
|
|
13
|
+
request_id: event.request_id || "",
|
|
14
|
+
event_id: event.request_id || randomUUID(),
|
|
15
|
+
timestamp: tinybirdTimestamp(event.timestamp),
|
|
16
|
+
ingested_at: tinybirdTimestamp(Date.now()),
|
|
17
|
+
retention_days: normalizeRetentionDays(event.retention_days)
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function requestCaptureRecord(capture) {
|
|
21
|
+
return {
|
|
22
|
+
...capture,
|
|
23
|
+
timestamp: tinybirdTimestamp(capture.timestamp),
|
|
24
|
+
ingested_at: tinybirdTimestamp(Date.now()),
|
|
25
|
+
retention_days: normalizeRetentionDays(capture.retention_days),
|
|
26
|
+
request_headers: JSON.stringify(capture.request_headers),
|
|
27
|
+
response_headers: JSON.stringify(capture.response_headers)
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function protocolEventRecord(event) {
|
|
31
|
+
return {
|
|
32
|
+
...event,
|
|
33
|
+
event_id: randomUUID(),
|
|
34
|
+
timestamp: tinybirdTimestamp(event.timestamp),
|
|
35
|
+
ingested_at: tinybirdTimestamp(Date.now()),
|
|
36
|
+
retention_days: normalizeRetentionDays(event.retention_days)
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
var TunnelTinybirdClient = class {
|
|
40
|
+
constructor(host, token) {
|
|
41
|
+
this.host = host;
|
|
42
|
+
this.token = token;
|
|
43
|
+
}
|
|
44
|
+
host;
|
|
45
|
+
token;
|
|
46
|
+
get configured() {
|
|
47
|
+
if (!this.host || !this.token) return false;
|
|
48
|
+
try {
|
|
49
|
+
const url = new URL(this.host);
|
|
50
|
+
return (url.protocol === "https:" || url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)) && !url.username && !url.password && !url.search && !url.hash && url.pathname === "/";
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async append(datasource, records) {
|
|
56
|
+
if (datasource === "tunnel_request_captures") {
|
|
57
|
+
const oversized = records.filter((record) => Buffer.byteLength(JSON.stringify(record)) > 3 * 1024 * 1024).length;
|
|
58
|
+
if (oversized) console.error(`Tinybird capture delivery rejected ${oversized} oversized records; other captures will continue`);
|
|
59
|
+
records = records.filter((record) => captureRecordIsCurrent(record) && Buffer.byteLength(JSON.stringify(record)) <= 3 * 1024 * 1024);
|
|
60
|
+
}
|
|
61
|
+
if (!records.length) return;
|
|
62
|
+
if (!this.configured) throw new Error("Tinybird tunnel ingestion is not configured");
|
|
63
|
+
const maxBytes = 4 * 1024 * 1024;
|
|
64
|
+
let lines = [];
|
|
65
|
+
let bytes = 0;
|
|
66
|
+
for (const record of records) {
|
|
67
|
+
const line = JSON.stringify(record);
|
|
68
|
+
const lineBytes = Buffer.byteLength(line) + 1;
|
|
69
|
+
if (lineBytes > maxBytes) {
|
|
70
|
+
throw new Error(`Tinybird ${datasource} record exceeds the ingestion size limit`);
|
|
71
|
+
}
|
|
72
|
+
if (bytes + lineBytes > maxBytes && lines.length) {
|
|
73
|
+
await this.appendBatch(datasource, lines);
|
|
74
|
+
lines = [];
|
|
75
|
+
bytes = 0;
|
|
76
|
+
}
|
|
77
|
+
lines.push(line);
|
|
78
|
+
bytes += lineBytes;
|
|
79
|
+
}
|
|
80
|
+
if (lines.length) await this.appendBatch(datasource, lines);
|
|
81
|
+
}
|
|
82
|
+
async appendBatch(datasource, lines) {
|
|
83
|
+
const response = await fetch(
|
|
84
|
+
`${this.host.replace(/\/$/, "")}/v0/events?name=${encodeURIComponent(datasource)}&wait=true`,
|
|
85
|
+
{
|
|
86
|
+
method: "POST",
|
|
87
|
+
headers: {
|
|
88
|
+
Authorization: `Bearer ${this.token}`,
|
|
89
|
+
"Content-Type": "application/x-ndjson"
|
|
90
|
+
},
|
|
91
|
+
body: lines.join("\n"),
|
|
92
|
+
signal: AbortSignal.timeout(15e3)
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
if (!response.ok) {
|
|
96
|
+
await response.body?.cancel();
|
|
97
|
+
throw new Error(`Tinybird ${datasource} ingestion failed (${response.status})`);
|
|
98
|
+
}
|
|
99
|
+
let result;
|
|
100
|
+
try {
|
|
101
|
+
const candidate = await response.json();
|
|
102
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
|
|
103
|
+
throw new Error("invalid acknowledgement");
|
|
104
|
+
}
|
|
105
|
+
result = candidate;
|
|
106
|
+
} catch {
|
|
107
|
+
throw new Error(`Tinybird ${datasource} ingestion returned an invalid acknowledgement`);
|
|
108
|
+
}
|
|
109
|
+
if (Number(result.quarantined_rows) > 0 || Number(result.successful_rows) !== lines.length) {
|
|
110
|
+
throw new Error(`Tinybird ${datasource} ingestion did not acknowledge the complete batch`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
function captureRecordIsCurrent(record, now = Date.now()) {
|
|
115
|
+
if (typeof record.timestamp !== "string") return false;
|
|
116
|
+
const timestamp = Date.parse(`${record.timestamp.replace(" ", "T").replace(/Z$/, "")}Z`);
|
|
117
|
+
const days = normalizeRetentionDays(Number(record.retention_days));
|
|
118
|
+
return Number.isFinite(timestamp) && timestamp + days * 24 * 60 * 60 * 1e3 > now;
|
|
119
|
+
}
|
|
120
|
+
export {
|
|
121
|
+
TunnelTinybirdClient,
|
|
122
|
+
captureRecordIsCurrent,
|
|
123
|
+
normalizeRetentionDays,
|
|
124
|
+
protocolEventRecord,
|
|
125
|
+
requestCaptureRecord,
|
|
126
|
+
tinybirdTimestamp,
|
|
127
|
+
tunnelEventRecord
|
|
128
|
+
};
|
|
129
|
+
//# sourceMappingURL=tinybird-records.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tinybird-records.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\n\nexport interface TunnelEvent {\n request_id?: string;\n timestamp: number;\n tunnel_id: string;\n organization_id: string;\n retention_days: number;\n host: string;\n method: string;\n path: string;\n status_code: number;\n request_duration_ms: number;\n bytes_in: number;\n bytes_out: number;\n client_ip: string;\n user_agent: string;\n}\n\nexport interface RequestCapture {\n id: string;\n timestamp: number;\n tunnel_id: string;\n organization_id: string;\n retention_days: number;\n request_headers: Record<string, string | string[]>;\n request_body: string | null;\n request_body_size: number;\n response_headers: Record<string, string | string[]>;\n response_body: string | null;\n response_body_size: number;\n}\n\nexport interface ProtocolEvent {\n direction?: \"in\" | \"out\";\n timestamp: number;\n tunnel_id: string;\n organization_id: string;\n retention_days: number;\n protocol: \"tcp\" | \"udp\";\n event_type: \"connection\" | \"data\" | \"close\" | \"packet\";\n connection_id: string;\n client_ip: string;\n client_port: number;\n bytes_in: number;\n bytes_out: number;\n duration_ms: number;\n}\n\nexport type TinybirdTunnelRecord = Record<string, string | number | null>;\n\nexport function normalizeRetentionDays(retentionDays: number): number {\n if (!Number.isFinite(retentionDays)) return 3;\n return Math.min(90, Math.max(1, Math.trunc(retentionDays)));\n}\n\nexport function tinybirdTimestamp(timestamp: number | Date): string {\n return new Date(timestamp).toISOString().replace(\"T\", \" \").replace(\"Z\", \"\");\n}\n\nexport function tunnelEventRecord(event: TunnelEvent): TinybirdTunnelRecord {\n return {\n ...event,\n request_id: event.request_id || \"\",\n event_id: event.request_id || randomUUID(),\n timestamp: tinybirdTimestamp(event.timestamp),\n ingested_at: tinybirdTimestamp(Date.now()),\n retention_days: normalizeRetentionDays(event.retention_days),\n };\n}\n\nexport function requestCaptureRecord(capture: RequestCapture): TinybirdTunnelRecord {\n return {\n ...capture,\n timestamp: tinybirdTimestamp(capture.timestamp),\n ingested_at: tinybirdTimestamp(Date.now()),\n retention_days: normalizeRetentionDays(capture.retention_days),\n request_headers: JSON.stringify(capture.request_headers),\n response_headers: JSON.stringify(capture.response_headers),\n };\n}\n\nexport function protocolEventRecord(event: ProtocolEvent): TinybirdTunnelRecord {\n return {\n ...event,\n event_id: randomUUID(),\n timestamp: tinybirdTimestamp(event.timestamp),\n ingested_at: tinybirdTimestamp(Date.now()),\n retention_days: normalizeRetentionDays(event.retention_days),\n };\n}\n\nexport class TunnelTinybirdClient {\n constructor(private readonly host: string, private readonly token: string) {}\n\n get configured(): boolean {\n if (!this.host || !this.token) return false;\n try {\n const url = new URL(this.host);\n return (url.protocol === \"https:\" ||\n (url.protocol === \"http:\" && [\"localhost\", \"127.0.0.1\", \"[::1]\"].includes(url.hostname))) &&\n !url.username && !url.password && !url.search && !url.hash && url.pathname === \"/\";\n } catch {\n return false;\n }\n }\n\n async append(datasource: string, records: TinybirdTunnelRecord[]): Promise<void> {\n if (datasource === \"tunnel_request_captures\") {\n const oversized = records.filter((record) => Buffer.byteLength(JSON.stringify(record)) > 3 * 1024 * 1024).length;\n if (oversized) console.error(`Tinybird capture delivery rejected ${oversized} oversized records; other captures will continue`);\n records = records.filter((record) => captureRecordIsCurrent(record) && Buffer.byteLength(JSON.stringify(record)) <= 3 * 1024 * 1024);\n }\n if (!records.length) return;\n if (!this.configured) throw new Error(\"Tinybird tunnel ingestion is not configured\");\n // Keep requests below the Events API payload ceiling, even when captures\n // contain large base64 request/response bodies. Stable ids make retrying an\n // already accepted prefix safe when a later request fails.\n const maxBytes = 4 * 1024 * 1024;\n let lines: string[] = [];\n let bytes = 0;\n for (const record of records) {\n const line = JSON.stringify(record);\n const lineBytes = Buffer.byteLength(line) + 1;\n if (lineBytes > maxBytes) {\n throw new Error(`Tinybird ${datasource} record exceeds the ingestion size limit`);\n }\n if (bytes + lineBytes > maxBytes && lines.length) {\n await this.appendBatch(datasource, lines);\n lines = [];\n bytes = 0;\n }\n lines.push(line);\n bytes += lineBytes;\n }\n if (lines.length) await this.appendBatch(datasource, lines);\n }\n\n private async appendBatch(datasource: string, lines: string[]): Promise<void> {\n const response = await fetch(\n `${this.host.replace(/\\/$/, \"\")}/v0/events?name=${encodeURIComponent(datasource)}&wait=true`,\n {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.token}`,\n \"Content-Type\": \"application/x-ndjson\",\n },\n body: lines.join(\"\\n\"),\n signal: AbortSignal.timeout(15_000),\n },\n );\n // Error bodies can echo capture values. Never include them in logs/errors.\n if (!response.ok) {\n await response.body?.cancel();\n throw new Error(`Tinybird ${datasource} ingestion failed (${response.status})`);\n }\n let result: { successful_rows?: number; quarantined_rows?: number };\n try {\n const candidate: unknown = await response.json();\n if (!candidate || typeof candidate !== \"object\" || Array.isArray(candidate)) {\n throw new Error(\"invalid acknowledgement\");\n }\n result = candidate as typeof result;\n } catch {\n throw new Error(`Tinybird ${datasource} ingestion returned an invalid acknowledgement`);\n }\n if (Number(result.quarantined_rows) > 0 || Number(result.successful_rows) !== lines.length) {\n throw new Error(`Tinybird ${datasource} ingestion did not acknowledge the complete batch`);\n }\n }\n}\n\n/** Backlogs must never republish captures past their individual plan expiry. */\nexport function captureRecordIsCurrent(record: TinybirdTunnelRecord, now = Date.now()): boolean {\n if (typeof record.timestamp !== \"string\") return false;\n const timestamp = Date.parse(`${record.timestamp.replace(\" \", \"T\").replace(/Z$/, \"\")}Z`);\n const days = normalizeRetentionDays(Number(record.retention_days));\n return Number.isFinite(timestamp) && timestamp + days * 24 * 60 * 60 * 1000 > now;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/tunnel-accounting.ts
|
|
21
|
+
var tunnel_accounting_exports = {};
|
|
22
|
+
__export(tunnel_accounting_exports, {
|
|
23
|
+
default: () => tunnel_accounting_default,
|
|
24
|
+
recordProductUsage: () => recordProductUsage,
|
|
25
|
+
tunnelUsageMetrics: () => tunnelUsageMetrics
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(tunnel_accounting_exports);
|
|
28
|
+
|
|
29
|
+
// src/usage-writer.ts
|
|
30
|
+
async function recordUsageCounters(database, event, allowedMetrics) {
|
|
31
|
+
if (!event.organizationId || event.organizationId.length > 128 || !/^[a-z][a-z0-9-]{0,63}$/.test(event.source) || !event.id || event.id.length > 256 || !Number.isFinite(Date.parse(event.timestamp))) throw new Error("Invalid usage event");
|
|
32
|
+
const deltas = {};
|
|
33
|
+
for (const [key, value] of Object.entries(event.deltas).sort(([a], [b]) => a.localeCompare(b))) {
|
|
34
|
+
if (!allowedMetrics.has(key) || typeof value === "number" && !Number.isSafeInteger(value) || !/^\d+$/.test(String(value))) throw new Error("Invalid usage delta");
|
|
35
|
+
const count = BigInt(value);
|
|
36
|
+
if (count > 9223372036854775807n) throw new Error("Usage delta overflow");
|
|
37
|
+
if (count > 0n) deltas[key] = String(count);
|
|
38
|
+
}
|
|
39
|
+
if (!Object.keys(deltas).length) return false;
|
|
40
|
+
const result = await database.query("SELECT record_product_usage($1,$2,$3,$4::timestamptz,$5::jsonb) AS recorded", [event.organizationId, event.source, event.id, event.timestamp, JSON.stringify(deltas)]);
|
|
41
|
+
return result.rows[0]?.recorded === true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/tunnel-accounting.ts
|
|
45
|
+
var tunnelUsageMetrics = [
|
|
46
|
+
"tunnels.opens.http",
|
|
47
|
+
"tunnels.opens.tcp",
|
|
48
|
+
"tunnels.opens.udp",
|
|
49
|
+
"tunnels.registrations.http",
|
|
50
|
+
"tunnels.registrations.tcp",
|
|
51
|
+
"tunnels.registrations.udp",
|
|
52
|
+
"tunnels.http_requests",
|
|
53
|
+
"tunnels.tcp_connections",
|
|
54
|
+
"tunnels.udp_datagrams.in",
|
|
55
|
+
"tunnels.udp_datagrams.out",
|
|
56
|
+
"tunnels.bytes.http.in",
|
|
57
|
+
"tunnels.bytes.http.out",
|
|
58
|
+
"tunnels.bytes.tcp.in",
|
|
59
|
+
"tunnels.bytes.tcp.out",
|
|
60
|
+
"tunnels.bytes.udp.in",
|
|
61
|
+
"tunnels.bytes.udp.out"
|
|
62
|
+
];
|
|
63
|
+
var known = new Set(tunnelUsageMetrics);
|
|
64
|
+
var recordProductUsage = (database, event) => recordUsageCounters(database, event, known);
|
|
65
|
+
var tunnel_accounting_default = { recordProductUsage };
|
|
66
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
67
|
+
0 && (module.exports = {
|
|
68
|
+
recordProductUsage,
|
|
69
|
+
tunnelUsageMetrics
|
|
70
|
+
});
|
|
71
|
+
//# sourceMappingURL=tunnel-accounting.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tunnel-accounting.ts","../src/usage-writer.ts"],"sourcesContent":["import { recordUsageCounters, type CounterEvent, type UsageDatabase } from \"./usage-writer\";\nexport const tunnelUsageMetrics = [\n \"tunnels.opens.http\", \"tunnels.opens.tcp\", \"tunnels.opens.udp\",\n \"tunnels.registrations.http\", \"tunnels.registrations.tcp\", \"tunnels.registrations.udp\",\n \"tunnels.http_requests\", \"tunnels.tcp_connections\", \"tunnels.udp_datagrams.in\", \"tunnels.udp_datagrams.out\",\n \"tunnels.bytes.http.in\", \"tunnels.bytes.http.out\", \"tunnels.bytes.tcp.in\", \"tunnels.bytes.tcp.out\",\n \"tunnels.bytes.udp.in\", \"tunnels.bytes.udp.out\",\n] as const;\nexport type UsageMetric = typeof tunnelUsageMetrics[number];\nexport type UsageEvent = CounterEvent<UsageMetric>;\nexport type { UsageDatabase };\nconst known = new Set<string>(tunnelUsageMetrics);\nexport const recordProductUsage = (database: UsageDatabase, event: UsageEvent) => recordUsageCounters(database, event, known);\nexport default { recordProductUsage };\n","export interface UsageDatabase { query(text: string, values?: any[]): Promise<any> }\nexport interface CounterEvent<Metric extends string = string> {\n organizationId: string; source: string; id: string; timestamp: string;\n deltas: Partial<Record<Metric, string | number>>;\n}\n/** Transport and validation only; callers supply their product's metric catalog. */\nexport async function recordUsageCounters<Metric extends string>(database: UsageDatabase, event: CounterEvent<Metric>, allowedMetrics: ReadonlySet<string>): Promise<boolean> {\n if (!event.organizationId || event.organizationId.length > 128 || !/^[a-z][a-z0-9-]{0,63}$/.test(event.source) || !event.id || event.id.length > 256 || !Number.isFinite(Date.parse(event.timestamp))) throw new Error(\"Invalid usage event\");\n const deltas: Record<string, string> = {};\n for (const [key, value] of Object.entries(event.deltas).sort(([a], [b]) => a.localeCompare(b))) {\n if (!allowedMetrics.has(key) || (typeof value === \"number\" && !Number.isSafeInteger(value)) || !/^\\d+$/.test(String(value))) throw new Error(\"Invalid usage delta\");\n const count = BigInt(value as string | number);\n if (count > 9223372036854775807n) throw new Error(\"Usage delta overflow\");\n if (count > 0n) deltas[key] = String(count);\n }\n if (!Object.keys(deltas).length) return false;\n const result = await database.query(\"SELECT record_product_usage($1,$2,$3,$4::timestamptz,$5::jsonb) AS recorded\", [event.organizationId, event.source, event.id, event.timestamp, JSON.stringify(deltas)]);\n return result.rows[0]?.recorded === true;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACMA,eAAsB,oBAA2C,UAAyB,OAA6B,gBAAuD;AAC5K,MAAI,CAAC,MAAM,kBAAkB,MAAM,eAAe,SAAS,OAAO,CAAC,yBAAyB,KAAK,MAAM,MAAM,KAAK,CAAC,MAAM,MAAM,MAAM,GAAG,SAAS,OAAO,CAAC,OAAO,SAAS,KAAK,MAAM,MAAM,SAAS,CAAC,EAAG,OAAM,IAAI,MAAM,qBAAqB;AAC5O,QAAM,SAAiC,CAAC;AACxC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,GAAG;AAC9F,QAAI,CAAC,eAAe,IAAI,GAAG,KAAM,OAAO,UAAU,YAAY,CAAC,OAAO,cAAc,KAAK,KAAM,CAAC,QAAQ,KAAK,OAAO,KAAK,CAAC,EAAG,OAAM,IAAI,MAAM,qBAAqB;AAClK,UAAM,QAAQ,OAAO,KAAwB;AAC7C,QAAI,QAAQ,qBAAsB,OAAM,IAAI,MAAM,sBAAsB;AACxE,QAAI,QAAQ,GAAI,QAAO,GAAG,IAAI,OAAO,KAAK;AAAA,EAC5C;AACA,MAAI,CAAC,OAAO,KAAK,MAAM,EAAE,OAAQ,QAAO;AACxC,QAAM,SAAS,MAAM,SAAS,MAAM,+EAA+E,CAAC,MAAM,gBAAgB,MAAM,QAAQ,MAAM,IAAI,MAAM,WAAW,KAAK,UAAU,MAAM,CAAC,CAAC;AAC1M,SAAO,OAAO,KAAK,CAAC,GAAG,aAAa;AACtC;;;ADjBO,IAAM,qBAAqB;AAAA,EAChC;AAAA,EAAsB;AAAA,EAAqB;AAAA,EAC3C;AAAA,EAA8B;AAAA,EAA6B;AAAA,EAC3D;AAAA,EAAyB;AAAA,EAA2B;AAAA,EAA4B;AAAA,EAChF;AAAA,EAAyB;AAAA,EAA0B;AAAA,EAAwB;AAAA,EAC3E;AAAA,EAAwB;AAC1B;AAIA,IAAM,QAAQ,IAAI,IAAY,kBAAkB;AACzC,IAAM,qBAAqB,CAAC,UAAyB,UAAsB,oBAAoB,UAAU,OAAO,KAAK;AAC5H,IAAO,4BAAQ,EAAE,mBAAmB;","names":[]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { CounterEvent, UsageDatabase } from './usage-writer.cjs';
|
|
2
|
+
|
|
3
|
+
declare const tunnelUsageMetrics: readonly ["tunnels.opens.http", "tunnels.opens.tcp", "tunnels.opens.udp", "tunnels.registrations.http", "tunnels.registrations.tcp", "tunnels.registrations.udp", "tunnels.http_requests", "tunnels.tcp_connections", "tunnels.udp_datagrams.in", "tunnels.udp_datagrams.out", "tunnels.bytes.http.in", "tunnels.bytes.http.out", "tunnels.bytes.tcp.in", "tunnels.bytes.tcp.out", "tunnels.bytes.udp.in", "tunnels.bytes.udp.out"];
|
|
4
|
+
type UsageMetric = typeof tunnelUsageMetrics[number];
|
|
5
|
+
type UsageEvent = CounterEvent<UsageMetric>;
|
|
6
|
+
|
|
7
|
+
declare const recordProductUsage: (database: UsageDatabase, event: UsageEvent) => Promise<boolean>;
|
|
8
|
+
declare const _default: {
|
|
9
|
+
recordProductUsage: (database: UsageDatabase, event: UsageEvent) => Promise<boolean>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export { UsageDatabase, type UsageEvent, type UsageMetric, _default as default, recordProductUsage, tunnelUsageMetrics };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { CounterEvent, UsageDatabase } from './usage-writer.js';
|
|
2
|
+
|
|
3
|
+
declare const tunnelUsageMetrics: readonly ["tunnels.opens.http", "tunnels.opens.tcp", "tunnels.opens.udp", "tunnels.registrations.http", "tunnels.registrations.tcp", "tunnels.registrations.udp", "tunnels.http_requests", "tunnels.tcp_connections", "tunnels.udp_datagrams.in", "tunnels.udp_datagrams.out", "tunnels.bytes.http.in", "tunnels.bytes.http.out", "tunnels.bytes.tcp.in", "tunnels.bytes.tcp.out", "tunnels.bytes.udp.in", "tunnels.bytes.udp.out"];
|
|
4
|
+
type UsageMetric = typeof tunnelUsageMetrics[number];
|
|
5
|
+
type UsageEvent = CounterEvent<UsageMetric>;
|
|
6
|
+
|
|
7
|
+
declare const recordProductUsage: (database: UsageDatabase, event: UsageEvent) => Promise<boolean>;
|
|
8
|
+
declare const _default: {
|
|
9
|
+
recordProductUsage: (database: UsageDatabase, event: UsageEvent) => Promise<boolean>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export { UsageDatabase, type UsageEvent, type UsageMetric, _default as default, recordProductUsage, tunnelUsageMetrics };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
recordUsageCounters
|
|
3
|
+
} from "./chunk-XAMVXW6B.js";
|
|
4
|
+
|
|
5
|
+
// src/tunnel-accounting.ts
|
|
6
|
+
var tunnelUsageMetrics = [
|
|
7
|
+
"tunnels.opens.http",
|
|
8
|
+
"tunnels.opens.tcp",
|
|
9
|
+
"tunnels.opens.udp",
|
|
10
|
+
"tunnels.registrations.http",
|
|
11
|
+
"tunnels.registrations.tcp",
|
|
12
|
+
"tunnels.registrations.udp",
|
|
13
|
+
"tunnels.http_requests",
|
|
14
|
+
"tunnels.tcp_connections",
|
|
15
|
+
"tunnels.udp_datagrams.in",
|
|
16
|
+
"tunnels.udp_datagrams.out",
|
|
17
|
+
"tunnels.bytes.http.in",
|
|
18
|
+
"tunnels.bytes.http.out",
|
|
19
|
+
"tunnels.bytes.tcp.in",
|
|
20
|
+
"tunnels.bytes.tcp.out",
|
|
21
|
+
"tunnels.bytes.udp.in",
|
|
22
|
+
"tunnels.bytes.udp.out"
|
|
23
|
+
];
|
|
24
|
+
var known = new Set(tunnelUsageMetrics);
|
|
25
|
+
var recordProductUsage = (database, event) => recordUsageCounters(database, event, known);
|
|
26
|
+
var tunnel_accounting_default = { recordProductUsage };
|
|
27
|
+
export {
|
|
28
|
+
tunnel_accounting_default as default,
|
|
29
|
+
recordProductUsage,
|
|
30
|
+
tunnelUsageMetrics
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=tunnel-accounting.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tunnel-accounting.ts"],"sourcesContent":["import { recordUsageCounters, type CounterEvent, type UsageDatabase } from \"./usage-writer\";\nexport const tunnelUsageMetrics = [\n \"tunnels.opens.http\", \"tunnels.opens.tcp\", \"tunnels.opens.udp\",\n \"tunnels.registrations.http\", \"tunnels.registrations.tcp\", \"tunnels.registrations.udp\",\n \"tunnels.http_requests\", \"tunnels.tcp_connections\", \"tunnels.udp_datagrams.in\", \"tunnels.udp_datagrams.out\",\n \"tunnels.bytes.http.in\", \"tunnels.bytes.http.out\", \"tunnels.bytes.tcp.in\", \"tunnels.bytes.tcp.out\",\n \"tunnels.bytes.udp.in\", \"tunnels.bytes.udp.out\",\n] as const;\nexport type UsageMetric = typeof tunnelUsageMetrics[number];\nexport type UsageEvent = CounterEvent<UsageMetric>;\nexport type { UsageDatabase };\nconst known = new Set<string>(tunnelUsageMetrics);\nexport const recordProductUsage = (database: UsageDatabase, event: UsageEvent) => recordUsageCounters(database, event, known);\nexport default { recordProductUsage };\n"],"mappings":";;;;;AACO,IAAM,qBAAqB;AAAA,EAChC;AAAA,EAAsB;AAAA,EAAqB;AAAA,EAC3C;AAAA,EAA8B;AAAA,EAA6B;AAAA,EAC3D;AAAA,EAAyB;AAAA,EAA2B;AAAA,EAA4B;AAAA,EAChF;AAAA,EAAyB;AAAA,EAA0B;AAAA,EAAwB;AAAA,EAC3E;AAAA,EAAwB;AAC1B;AAIA,IAAM,QAAQ,IAAI,IAAY,kBAAkB;AACzC,IAAM,qBAAqB,CAAC,UAAyB,UAAsB,oBAAoB,UAAU,OAAO,KAAK;AAC5H,IAAO,4BAAQ,EAAE,mBAAmB;","names":[]}
|