@metamask-previews/tooling-insight 1.0.1-preview-898fae5

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.
Files changed (108) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/LICENSE +21 -0
  3. package/README.md +134 -0
  4. package/dist/daily-anonymizer.cjs +8 -0
  5. package/dist/daily-anonymizer.cjs.map +1 -0
  6. package/dist/daily-anonymizer.d.cts +3 -0
  7. package/dist/daily-anonymizer.d.cts.map +1 -0
  8. package/dist/daily-anonymizer.d.mts +3 -0
  9. package/dist/daily-anonymizer.d.mts.map +1 -0
  10. package/dist/daily-anonymizer.mjs +6 -0
  11. package/dist/daily-anonymizer.mjs.map +1 -0
  12. package/dist/index.cjs +6 -0
  13. package/dist/index.cjs.map +1 -0
  14. package/dist/index.d.cts +4 -0
  15. package/dist/index.d.cts.map +1 -0
  16. package/dist/index.d.mts +4 -0
  17. package/dist/index.d.mts.map +1 -0
  18. package/dist/index.mjs +2 -0
  19. package/dist/index.mjs.map +1 -0
  20. package/dist/lib/allowlist.cjs +159 -0
  21. package/dist/lib/allowlist.cjs.map +1 -0
  22. package/dist/lib/allowlist.d.cts +31 -0
  23. package/dist/lib/allowlist.d.cts.map +1 -0
  24. package/dist/lib/allowlist.d.mts +31 -0
  25. package/dist/lib/allowlist.d.mts.map +1 -0
  26. package/dist/lib/allowlist.mjs +155 -0
  27. package/dist/lib/allowlist.mjs.map +1 -0
  28. package/dist/lib/csv.cjs +152 -0
  29. package/dist/lib/csv.cjs.map +1 -0
  30. package/dist/lib/csv.d.cts +16 -0
  31. package/dist/lib/csv.d.cts.map +1 -0
  32. package/dist/lib/csv.d.mts +16 -0
  33. package/dist/lib/csv.d.mts.map +1 -0
  34. package/dist/lib/csv.mjs +149 -0
  35. package/dist/lib/csv.mjs.map +1 -0
  36. package/dist/lib/exposition.cjs +102 -0
  37. package/dist/lib/exposition.cjs.map +1 -0
  38. package/dist/lib/exposition.d.cts +9 -0
  39. package/dist/lib/exposition.d.cts.map +1 -0
  40. package/dist/lib/exposition.d.mts +9 -0
  41. package/dist/lib/exposition.d.mts.map +1 -0
  42. package/dist/lib/exposition.mjs +99 -0
  43. package/dist/lib/exposition.mjs.map +1 -0
  44. package/dist/lib/fold.cjs +294 -0
  45. package/dist/lib/fold.cjs.map +1 -0
  46. package/dist/lib/fold.d.cts +32 -0
  47. package/dist/lib/fold.d.cts.map +1 -0
  48. package/dist/lib/fold.d.mts +32 -0
  49. package/dist/lib/fold.d.mts.map +1 -0
  50. package/dist/lib/fold.mjs +288 -0
  51. package/dist/lib/fold.mjs.map +1 -0
  52. package/dist/lib/log.cjs +116 -0
  53. package/dist/lib/log.cjs.map +1 -0
  54. package/dist/lib/log.d.cts +32 -0
  55. package/dist/lib/log.d.cts.map +1 -0
  56. package/dist/lib/log.d.mts +32 -0
  57. package/dist/lib/log.d.mts.map +1 -0
  58. package/dist/lib/log.mjs +113 -0
  59. package/dist/lib/log.mjs.map +1 -0
  60. package/dist/lib/paths.cjs +91 -0
  61. package/dist/lib/paths.cjs.map +1 -0
  62. package/dist/lib/paths.d.cts +45 -0
  63. package/dist/lib/paths.d.cts.map +1 -0
  64. package/dist/lib/paths.d.mts +45 -0
  65. package/dist/lib/paths.d.mts.map +1 -0
  66. package/dist/lib/paths.mjs +82 -0
  67. package/dist/lib/paths.mjs.map +1 -0
  68. package/dist/lib/push.cjs +122 -0
  69. package/dist/lib/push.cjs.map +1 -0
  70. package/dist/lib/push.d.cts +58 -0
  71. package/dist/lib/push.d.cts.map +1 -0
  72. package/dist/lib/push.d.mts +58 -0
  73. package/dist/lib/push.d.mts.map +1 -0
  74. package/dist/lib/push.mjs +116 -0
  75. package/dist/lib/push.mjs.map +1 -0
  76. package/dist/lib/remoteWrite.cjs +177 -0
  77. package/dist/lib/remoteWrite.cjs.map +1 -0
  78. package/dist/lib/remoteWrite.d.cts +24 -0
  79. package/dist/lib/remoteWrite.d.cts.map +1 -0
  80. package/dist/lib/remoteWrite.d.mts +24 -0
  81. package/dist/lib/remoteWrite.d.mts.map +1 -0
  82. package/dist/lib/remoteWrite.mjs +172 -0
  83. package/dist/lib/remoteWrite.mjs.map +1 -0
  84. package/dist/lib/state.cjs +100 -0
  85. package/dist/lib/state.cjs.map +1 -0
  86. package/dist/lib/state.d.cts +28 -0
  87. package/dist/lib/state.d.cts.map +1 -0
  88. package/dist/lib/state.d.mts +28 -0
  89. package/dist/lib/state.d.mts.map +1 -0
  90. package/dist/lib/state.mjs +95 -0
  91. package/dist/lib/state.mjs.map +1 -0
  92. package/dist/lib/types.cjs +3 -0
  93. package/dist/lib/types.cjs.map +1 -0
  94. package/dist/lib/types.d.cts +82 -0
  95. package/dist/lib/types.d.cts.map +1 -0
  96. package/dist/lib/types.d.mts +82 -0
  97. package/dist/lib/types.d.mts.map +1 -0
  98. package/dist/lib/types.mjs +2 -0
  99. package/dist/lib/types.mjs.map +1 -0
  100. package/dist/run.cjs +137 -0
  101. package/dist/run.cjs.map +1 -0
  102. package/dist/run.d.cts +20 -0
  103. package/dist/run.d.cts.map +1 -0
  104. package/dist/run.d.mts +20 -0
  105. package/dist/run.d.mts.map +1 -0
  106. package/dist/run.mjs +134 -0
  107. package/dist/run.mjs.map +1 -0
  108. package/package.json +100 -0
@@ -0,0 +1,58 @@
1
+ /** Prometheus remote-write endpoint coordinates. */
2
+ export type PushConfig = {
3
+ /** Full URL of the remote-write endpoint. */
4
+ url: string;
5
+ /** HTTP Basic auth username. Omit for unauthenticated endpoints (e.g. prod via Cloudflare WARP). */
6
+ username?: string;
7
+ /** HTTP Basic auth password. Omit for unauthenticated endpoints (e.g. prod via Cloudflare WARP). */
8
+ password?: string;
9
+ };
10
+ /** Minimal logger interface used by `pushBatch`. */
11
+ export type Logger = {
12
+ /** Log an informational message. */
13
+ info: (message: string, extra?: Record<string, unknown>) => void;
14
+ /** Log an error message. */
15
+ error: (message: string, extra?: Record<string, unknown>) => void;
16
+ };
17
+ /** Thrown when the remote-write endpoint returns a non-2xx response. */
18
+ export declare class PushError extends Error {
19
+ readonly status: number;
20
+ readonly responseBody: string;
21
+ /**
22
+ * Create a `PushError`.
23
+ *
24
+ * @param status - HTTP status code returned by the endpoint.
25
+ * @param responseBody - Response body text returned by the endpoint.
26
+ */
27
+ constructor(status: number, responseBody: string);
28
+ }
29
+ /**
30
+ * Load push credentials from process env, falling back to the repo's `.js.env`.
31
+ *
32
+ * @param repoRoot - Absolute path to the repo root where `.js.env` may live.
33
+ * @returns The resolved push configuration.
34
+ */
35
+ export declare function loadPushConfig(repoRoot: string): PushConfig;
36
+ /** Options for `pushBatch`. */
37
+ export type PushBatchOptions = {
38
+ /** Full URL of the remote-write endpoint. */
39
+ url: string;
40
+ /** HTTP Basic auth username. Omit for unauthenticated endpoints (e.g. prod via Cloudflare WARP). */
41
+ username?: string;
42
+ /** HTTP Basic auth password. Omit for unauthenticated endpoints (e.g. prod via Cloudflare WARP). */
43
+ password?: string;
44
+ /** When true, logs the body but does not POST. */
45
+ dryRun: boolean;
46
+ /** Logger instance for success and error messages. */
47
+ logger: Logger;
48
+ /** Fetch implementation; defaults to the global `fetch`. */
49
+ fetchImpl?: typeof fetch;
50
+ };
51
+ /**
52
+ * POST a remote-write body to the configured Prometheus remote-write endpoint.
53
+ *
54
+ * @param body - Snappy-compressed protobuf `WriteRequest` bytes to push.
55
+ * @param options - Push configuration including URL, credentials, and optional fetch override.
56
+ */
57
+ export declare function pushBatch(body: Uint8Array, options: PushBatchOptions): Promise<void>;
58
+ //# sourceMappingURL=push.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push.d.mts","sourceRoot":"","sources":["../../src/lib/push.ts"],"names":[],"mappings":"AAKA,oDAAoD;AACpD,MAAM,MAAM,UAAU,GAAG;IACvB,6CAA6C;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,oGAAoG;IACpG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oGAAoG;IACpG,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,oDAAoD;AACpD,MAAM,MAAM,MAAM,GAAG;IACnB,oCAAoC;IACpC,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACjE,4BAA4B;IAC5B,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;CACnE,CAAC;AAEF,wEAAwE;AACxE,qBAAa,SAAU,SAAQ,KAAK;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAE9B;;;;;OAKG;gBACS,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM;CAMjD;AAqCD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAgB3D;AAED,+BAA+B;AAC/B,MAAM,MAAM,gBAAgB,GAAG;IAC7B,6CAA6C;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,oGAAoG;IACpG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oGAAoG;IACpG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,MAAM,EAAE,OAAO,CAAC;IAChB,sDAAsD;IACtD,MAAM,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B,CAAC;AAEF;;;;;GAKG;AACH,wBAAsB,SAAS,CAC7B,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,IAAI,CAAC,CA0Cf"}
@@ -0,0 +1,116 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { ENV_MIMIR_URL, ENV_MIMIR_PASSWORD, ENV_MIMIR_USERNAME } from "./paths.mjs";
4
+ /** Thrown when the remote-write endpoint returns a non-2xx response. */
5
+ export class PushError extends Error {
6
+ /**
7
+ * Create a `PushError`.
8
+ *
9
+ * @param status - HTTP status code returned by the endpoint.
10
+ * @param responseBody - Response body text returned by the endpoint.
11
+ */
12
+ constructor(status, responseBody) {
13
+ super(`Push failed with status ${status}`);
14
+ this.name = 'PushError';
15
+ this.status = status;
16
+ this.responseBody = responseBody;
17
+ }
18
+ }
19
+ /**
20
+ * Parse the repo's `.js.env` (shell-sourceable: `export KEY="value"`, `#` comments).
21
+ * Handles the optional `export ` prefix and surrounding single/double quotes.
22
+ *
23
+ * @param contents - Shell-sourceable `.js.env` file contents.
24
+ * @returns A map of environment variable names to their string values.
25
+ */
26
+ function parseEnvFile(contents) {
27
+ const entries = {};
28
+ for (const rawLine of contents.split('\n')) {
29
+ let line = rawLine.trim();
30
+ if (!line || line.startsWith('#')) {
31
+ continue;
32
+ }
33
+ if (line.startsWith('export ')) {
34
+ line = line.slice('export '.length).trim();
35
+ }
36
+ const eq = line.indexOf('=');
37
+ if (eq === -1) {
38
+ continue;
39
+ }
40
+ const key = line.slice(0, eq).trim();
41
+ let value = line.slice(eq + 1).trim();
42
+ if (value.length >= 2 &&
43
+ ((value.startsWith('"') && value.endsWith('"')) ||
44
+ (value.startsWith("'") && value.endsWith("'")))) {
45
+ value = value.slice(1, -1);
46
+ }
47
+ entries[key] = value;
48
+ }
49
+ return entries;
50
+ }
51
+ /**
52
+ * Load push credentials from process env, falling back to the repo's `.js.env`.
53
+ *
54
+ * @param repoRoot - Absolute path to the repo root where `.js.env` may live.
55
+ * @returns The resolved push configuration.
56
+ */
57
+ export function loadPushConfig(repoRoot) {
58
+ // File values are the fallback; env takes precedence for every key.
59
+ const envPath = join(repoRoot, '.js.env');
60
+ const file = existsSync(envPath)
61
+ ? parseEnvFile(readFileSync(envPath, 'utf8'))
62
+ : {};
63
+ const url = process.env[ENV_MIMIR_URL] ?? file[ENV_MIMIR_URL] ?? '';
64
+ const username = process.env[ENV_MIMIR_USERNAME] ?? file[ENV_MIMIR_USERNAME];
65
+ const password = process.env[ENV_MIMIR_PASSWORD] ?? file[ENV_MIMIR_PASSWORD];
66
+ return {
67
+ url,
68
+ ...(username !== undefined && { username }),
69
+ ...(password !== undefined && { password }),
70
+ };
71
+ }
72
+ /**
73
+ * POST a remote-write body to the configured Prometheus remote-write endpoint.
74
+ *
75
+ * @param body - Snappy-compressed protobuf `WriteRequest` bytes to push.
76
+ * @param options - Push configuration including URL, credentials, and optional fetch override.
77
+ */
78
+ export async function pushBatch(body, options) {
79
+ if (options.dryRun) {
80
+ options.logger.info('dry-run push body', { byteLength: body.byteLength });
81
+ return;
82
+ }
83
+ if (!options.url) {
84
+ throw new Error('Prometheus push URL is required');
85
+ }
86
+ // Basic auth is only needed for authenticated endpoints (e.g. dev). Prod uses Cloudflare WARP.
87
+ const authHeader = options.username && options.password
88
+ ? {
89
+ Authorization: `Basic ${Buffer.from(`${options.username}:${options.password}`).toString('base64')}`,
90
+ }
91
+ : {};
92
+ const fetchImpl = options.fetchImpl ?? fetch;
93
+ const response = await fetchImpl(options.url, {
94
+ method: 'POST',
95
+ headers: {
96
+ ...authHeader,
97
+ 'Content-Type': 'application/x-protobuf',
98
+ 'X-Prometheus-Remote-Write-Version': '0.1.0',
99
+ },
100
+ body,
101
+ });
102
+ const responseBody = await response.text();
103
+ if (!response.ok) {
104
+ options.logger.error('push failed', {
105
+ status: response.status,
106
+ body: responseBody,
107
+ });
108
+ throw new PushError(response.status, responseBody);
109
+ }
110
+ // Mimir returns HTTP 200 even for partial rejections; warnings appear in the body.
111
+ options.logger.info('push complete', {
112
+ status: response.status,
113
+ ...(responseBody.trim() && { mimirWarning: responseBody.trim() }),
114
+ });
115
+ }
116
+ //# sourceMappingURL=push.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push.mjs","sourceRoot":"","sources":["../../src/lib/push.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { ENV_MIMIR_URL, ENV_MIMIR_PASSWORD, ENV_MIMIR_USERNAME } from './paths';\n\n/** Prometheus remote-write endpoint coordinates. */\nexport type PushConfig = {\n /** Full URL of the remote-write endpoint. */\n url: string;\n /** HTTP Basic auth username. Omit for unauthenticated endpoints (e.g. prod via Cloudflare WARP). */\n username?: string;\n /** HTTP Basic auth password. Omit for unauthenticated endpoints (e.g. prod via Cloudflare WARP). */\n password?: string;\n};\n\n/** Minimal logger interface used by `pushBatch`. */\nexport type Logger = {\n /** Log an informational message. */\n info: (message: string, extra?: Record<string, unknown>) => void;\n /** Log an error message. */\n error: (message: string, extra?: Record<string, unknown>) => void;\n};\n\n/** Thrown when the remote-write endpoint returns a non-2xx response. */\nexport class PushError extends Error {\n readonly status: number;\n\n readonly responseBody: string;\n\n /**\n * Create a `PushError`.\n *\n * @param status - HTTP status code returned by the endpoint.\n * @param responseBody - Response body text returned by the endpoint.\n */\n constructor(status: number, responseBody: string) {\n super(`Push failed with status ${status}`);\n this.name = 'PushError';\n this.status = status;\n this.responseBody = responseBody;\n }\n}\n\n/**\n * Parse the repo's `.js.env` (shell-sourceable: `export KEY=\"value\"`, `#` comments).\n * Handles the optional `export ` prefix and surrounding single/double quotes.\n *\n * @param contents - Shell-sourceable `.js.env` file contents.\n * @returns A map of environment variable names to their string values.\n */\nfunction parseEnvFile(contents: string): Record<string, string> {\n const entries: Record<string, string> = {};\n for (const rawLine of contents.split('\\n')) {\n let line = rawLine.trim();\n if (!line || line.startsWith('#')) {\n continue;\n }\n if (line.startsWith('export ')) {\n line = line.slice('export '.length).trim();\n }\n const eq = line.indexOf('=');\n if (eq === -1) {\n continue;\n }\n const key = line.slice(0, eq).trim();\n let value = line.slice(eq + 1).trim();\n if (\n value.length >= 2 &&\n ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\")))\n ) {\n value = value.slice(1, -1);\n }\n entries[key] = value;\n }\n return entries;\n}\n\n/**\n * Load push credentials from process env, falling back to the repo's `.js.env`.\n *\n * @param repoRoot - Absolute path to the repo root where `.js.env` may live.\n * @returns The resolved push configuration.\n */\nexport function loadPushConfig(repoRoot: string): PushConfig {\n // File values are the fallback; env takes precedence for every key.\n const envPath = join(repoRoot, '.js.env');\n const file = existsSync(envPath)\n ? parseEnvFile(readFileSync(envPath, 'utf8'))\n : {};\n\n const url = process.env[ENV_MIMIR_URL] ?? file[ENV_MIMIR_URL] ?? '';\n const username = process.env[ENV_MIMIR_USERNAME] ?? file[ENV_MIMIR_USERNAME];\n const password = process.env[ENV_MIMIR_PASSWORD] ?? file[ENV_MIMIR_PASSWORD];\n\n return {\n url,\n ...(username !== undefined && { username }),\n ...(password !== undefined && { password }),\n };\n}\n\n/** Options for `pushBatch`. */\nexport type PushBatchOptions = {\n /** Full URL of the remote-write endpoint. */\n url: string;\n /** HTTP Basic auth username. Omit for unauthenticated endpoints (e.g. prod via Cloudflare WARP). */\n username?: string;\n /** HTTP Basic auth password. Omit for unauthenticated endpoints (e.g. prod via Cloudflare WARP). */\n password?: string;\n /** When true, logs the body but does not POST. */\n dryRun: boolean;\n /** Logger instance for success and error messages. */\n logger: Logger;\n /** Fetch implementation; defaults to the global `fetch`. */\n fetchImpl?: typeof fetch;\n};\n\n/**\n * POST a remote-write body to the configured Prometheus remote-write endpoint.\n *\n * @param body - Snappy-compressed protobuf `WriteRequest` bytes to push.\n * @param options - Push configuration including URL, credentials, and optional fetch override.\n */\nexport async function pushBatch(\n body: Uint8Array,\n options: PushBatchOptions,\n): Promise<void> {\n if (options.dryRun) {\n options.logger.info('dry-run push body', { byteLength: body.byteLength });\n return;\n }\n\n if (!options.url) {\n throw new Error('Prometheus push URL is required');\n }\n\n // Basic auth is only needed for authenticated endpoints (e.g. dev). Prod uses Cloudflare WARP.\n const authHeader =\n options.username && options.password\n ? {\n Authorization: `Basic ${Buffer.from(`${options.username}:${options.password}`).toString('base64')}`,\n }\n : {};\n\n const fetchImpl = options.fetchImpl ?? fetch;\n const response = await fetchImpl(options.url, {\n method: 'POST',\n headers: {\n ...authHeader,\n 'Content-Type': 'application/x-protobuf',\n 'X-Prometheus-Remote-Write-Version': '0.1.0',\n },\n body,\n });\n\n const responseBody = await response.text();\n if (!response.ok) {\n options.logger.error('push failed', {\n status: response.status,\n body: responseBody,\n });\n throw new PushError(response.status, responseBody);\n }\n // Mimir returns HTTP 200 even for partial rejections; warnings appear in the body.\n options.logger.info('push complete', {\n status: response.status,\n ...(responseBody.trim() && { mimirWarning: responseBody.trim() }),\n });\n}\n"]}
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ // Prometheus remote_write encoder for the daily anonymizer (MCWP-644 / INFRA-3677).
3
+ //
4
+ // The Mimir remote_write endpoint accepts only snappy-compressed protobuf
5
+ // (`WriteRequest`) with per-sample timestamps. Both the protobuf and the snappy
6
+ // block are hand-rolled here so the tool stays dependency-free:
7
+ // - protobuf: the WriteRequest schema is four tiny messages, trivially encoded.
8
+ // - snappy: a valid block can be a single all-literals run (no back-references),
9
+ // which any snappy decoder accepts. We skip real compression — payloads are
10
+ // KB-scale, so the ratio is irrelevant — and avoid pulling a snappy library.
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.encodeWriteRequest = encodeWriteRequest;
13
+ exports.snappyBlock = snappyBlock;
14
+ exports.encodeRemoteWrite = encodeRemoteWrite;
15
+ const textEncoder = new TextEncoder();
16
+ /**
17
+ * Concatenate multiple `Uint8Array` chunks into a single array.
18
+ *
19
+ * @param chunks - Arrays of bytes to concatenate, in order.
20
+ * @returns A single `Uint8Array` containing all chunks concatenated.
21
+ */
22
+ function concat(chunks) {
23
+ const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
24
+ const out = new Uint8Array(total);
25
+ let offset = 0;
26
+ for (const chunk of chunks) {
27
+ out.set(chunk, offset);
28
+ offset += chunk.length;
29
+ }
30
+ return out;
31
+ }
32
+ /**
33
+ * Base-128 varint (protobuf + snappy length preamble). BigInt-safe for int64.
34
+ *
35
+ * @param value - The integer to encode as a varint.
36
+ * @returns Base-128 varint encoding as a `Uint8Array`.
37
+ */
38
+ function varint(value) {
39
+ let remaining = typeof value === 'bigint' ? value : BigInt(value);
40
+ const bytes = [];
41
+ do {
42
+ let byte = Number(remaining & 0x7fn);
43
+ remaining >>= 7n;
44
+ if (remaining > 0n) {
45
+ byte |= 0x80;
46
+ }
47
+ bytes.push(byte);
48
+ } while (remaining > 0n);
49
+ return Uint8Array.from(bytes);
50
+ }
51
+ /**
52
+ * A length-delimited (wire type 2) field: tag byte, length varint, then the payload.
53
+ *
54
+ * @param fieldNumber - The protobuf field number (1-based).
55
+ * @param payload - Raw bytes of the field value.
56
+ * @returns Wire-encoded length-delimited field as a `Uint8Array`.
57
+ */
58
+ function lengthDelimitedField(fieldNumber, payload) {
59
+ const tag = (fieldNumber << 3) | 2;
60
+ return concat([varint(tag), varint(payload.length), payload]);
61
+ }
62
+ // Label { name = 1; value = 2; }
63
+ /**
64
+ * Encode a Prometheus label name/value pair as a protobuf `Label` message.
65
+ *
66
+ * @param name - Label name.
67
+ * @param value - Label value.
68
+ * @returns Encoded `Label` message as a `Uint8Array`.
69
+ */
70
+ function encodeLabel(name, value) {
71
+ return concat([
72
+ lengthDelimitedField(1, textEncoder.encode(name)),
73
+ lengthDelimitedField(2, textEncoder.encode(value)),
74
+ ]);
75
+ }
76
+ // Sample { double value = 1; int64 timestamp = 2; }
77
+ /**
78
+ * Encode a Prometheus sample value and timestamp as a protobuf `Sample` message.
79
+ *
80
+ * @param value - Metric value (float64).
81
+ * @param timestampMs - Timestamp in milliseconds since epoch.
82
+ * @returns Encoded `Sample` message as a `Uint8Array`.
83
+ */
84
+ function encodeSample(value, timestampMs) {
85
+ const valueBytes = new Uint8Array(8);
86
+ new DataView(valueBytes.buffer).setFloat64(0, value, true);
87
+ const valueField = concat([varint((1 << 3) | 1), valueBytes]);
88
+ const timestampField = concat([
89
+ varint((2 << 3) | 0),
90
+ varint(BigInt(timestampMs)),
91
+ ]);
92
+ return concat([valueField, timestampField]);
93
+ }
94
+ // TimeSeries { repeated Label labels = 1; repeated Sample samples = 2; }
95
+ /**
96
+ * Encode a single time series (one set of labels with one sample) as a protobuf `TimeSeries`.
97
+ *
98
+ * @param labels - Array of `[name, value]` label pairs.
99
+ * @param value - Metric value for the single sample.
100
+ * @param timestampMs - Timestamp in milliseconds since epoch.
101
+ * @returns Encoded `TimeSeries` message as a `Uint8Array`.
102
+ */
103
+ function encodeTimeSeries(labels, value, timestampMs) {
104
+ const labelFields = labels.map(([name, labelValue]) => lengthDelimitedField(1, encodeLabel(name, labelValue)));
105
+ const sampleField = lengthDelimitedField(2, encodeSample(value, timestampMs));
106
+ return concat([...labelFields, sampleField]);
107
+ }
108
+ /**
109
+ * Build the sorted label pairs for a sample point, ready for the protobuf encoder.
110
+ *
111
+ * @param sample - The sample point whose labels to extract.
112
+ * @returns Label pairs sorted lexicographically by name (required by Mimir).
113
+ */
114
+ function sampleLabels(sample) {
115
+ // Mimir requires label names sorted lexicographically; `__name__` (ASCII `_` = 95)
116
+ // precedes all lowercase keys, so the array below is already in order.
117
+ // `le` sorts between `instance` and `repo` and is only present on _bucket samples.
118
+ const labels = [
119
+ ['__name__', sample.metric],
120
+ ['agent_vendor', sample.labels.agent_vendor],
121
+ ['event_type', sample.labels.event_type],
122
+ ['instance', sample.labels.instance],
123
+ ];
124
+ if (sample.le !== undefined) {
125
+ labels.push(['le', sample.le]);
126
+ }
127
+ labels.push(['repo', sample.labels.repo], ['tool_name', sample.labels.tool_name], ['tool_type', sample.labels.tool_type]);
128
+ return labels;
129
+ }
130
+ /**
131
+ * Encode a batch of samples as an (uncompressed) `WriteRequest` protobuf.
132
+ *
133
+ * @param samples - Sample points to encode into `TimeSeries` messages.
134
+ * @returns Raw protobuf `WriteRequest` bytes.
135
+ */
136
+ function encodeWriteRequest(samples) {
137
+ // WriteRequest { repeated TimeSeries timeseries = 1; }
138
+ const series = samples.map((sample) => lengthDelimitedField(1, encodeTimeSeries(sampleLabels(sample), sample.value, sample.timestampMs)));
139
+ return concat(series);
140
+ }
141
+ /**
142
+ * Wrap raw bytes in a snappy block as a single all-literals run.
143
+ * Format: varint(uncompressedLength) + literal element(s) + raw bytes.
144
+ *
145
+ * @param data - The raw bytes to wrap.
146
+ * @returns A snappy-framed byte array accepted by any snappy decoder.
147
+ */
148
+ function snappyBlock(data) {
149
+ if (data.length === 0) {
150
+ return varint(0); // valid snappy block: uncompressed length 0, no literals
151
+ }
152
+ const preamble = varint(data.length);
153
+ const literalLengthMinusOne = data.length - 1;
154
+ if (literalLengthMinusOne < 60) {
155
+ const tag = literalLengthMinusOne << 2; // literal tag, wire type 0b00
156
+ return concat([preamble, Uint8Array.from([tag]), data]);
157
+ }
158
+ // Length needs 1-4 extra little-endian bytes; the 6-bit field holds 59 + count.
159
+ const extraBytes = [];
160
+ let remaining = literalLengthMinusOne;
161
+ while (remaining > 0) {
162
+ extraBytes.push(remaining & 0xff);
163
+ remaining >>>= 8;
164
+ }
165
+ const tag = (59 + extraBytes.length) << 2;
166
+ return concat([preamble, Uint8Array.from([tag, ...extraBytes]), data]);
167
+ }
168
+ /**
169
+ * Encode samples into the exact body posted to the remote_write endpoint.
170
+ *
171
+ * @param samples - The sample points to encode.
172
+ * @returns A snappy-wrapped protobuf `WriteRequest`.
173
+ */
174
+ function encodeRemoteWrite(samples) {
175
+ return snappyBlock(encodeWriteRequest(samples));
176
+ }
177
+ //# sourceMappingURL=remoteWrite.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remoteWrite.cjs","sourceRoot":"","sources":["../../src/lib/remoteWrite.ts"],"names":[],"mappings":"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","sourcesContent":["// Prometheus remote_write encoder for the daily anonymizer (MCWP-644 / INFRA-3677).\n//\n// The Mimir remote_write endpoint accepts only snappy-compressed protobuf\n// (`WriteRequest`) with per-sample timestamps. Both the protobuf and the snappy\n// block are hand-rolled here so the tool stays dependency-free:\n// - protobuf: the WriteRequest schema is four tiny messages, trivially encoded.\n// - snappy: a valid block can be a single all-literals run (no back-references),\n// which any snappy decoder accepts. We skip real compression — payloads are\n// KB-scale, so the ratio is irrelevant — and avoid pulling a snappy library.\n\nimport type { SamplePoint } from './types';\n\nconst textEncoder = new TextEncoder();\n\n/**\n * Concatenate multiple `Uint8Array` chunks into a single array.\n *\n * @param chunks - Arrays of bytes to concatenate, in order.\n * @returns A single `Uint8Array` containing all chunks concatenated.\n */\nfunction concat(chunks: Uint8Array[]): Uint8Array {\n const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);\n const out = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n out.set(chunk, offset);\n offset += chunk.length;\n }\n return out;\n}\n\n/**\n * Base-128 varint (protobuf + snappy length preamble). BigInt-safe for int64.\n *\n * @param value - The integer to encode as a varint.\n * @returns Base-128 varint encoding as a `Uint8Array`.\n */\nfunction varint(value: number | bigint): Uint8Array {\n let remaining = typeof value === 'bigint' ? value : BigInt(value);\n const bytes: number[] = [];\n do {\n let byte = Number(remaining & 0x7fn);\n remaining >>= 7n;\n if (remaining > 0n) {\n byte |= 0x80;\n }\n bytes.push(byte);\n } while (remaining > 0n);\n return Uint8Array.from(bytes);\n}\n\n/**\n * A length-delimited (wire type 2) field: tag byte, length varint, then the payload.\n *\n * @param fieldNumber - The protobuf field number (1-based).\n * @param payload - Raw bytes of the field value.\n * @returns Wire-encoded length-delimited field as a `Uint8Array`.\n */\nfunction lengthDelimitedField(\n fieldNumber: number,\n payload: Uint8Array,\n): Uint8Array {\n const tag = (fieldNumber << 3) | 2;\n return concat([varint(tag), varint(payload.length), payload]);\n}\n\n// Label { name = 1; value = 2; }\n/**\n * Encode a Prometheus label name/value pair as a protobuf `Label` message.\n *\n * @param name - Label name.\n * @param value - Label value.\n * @returns Encoded `Label` message as a `Uint8Array`.\n */\nfunction encodeLabel(name: string, value: string): Uint8Array {\n return concat([\n lengthDelimitedField(1, textEncoder.encode(name)),\n lengthDelimitedField(2, textEncoder.encode(value)),\n ]);\n}\n\n// Sample { double value = 1; int64 timestamp = 2; }\n/**\n * Encode a Prometheus sample value and timestamp as a protobuf `Sample` message.\n *\n * @param value - Metric value (float64).\n * @param timestampMs - Timestamp in milliseconds since epoch.\n * @returns Encoded `Sample` message as a `Uint8Array`.\n */\nfunction encodeSample(value: number, timestampMs: number): Uint8Array {\n const valueBytes = new Uint8Array(8);\n new DataView(valueBytes.buffer).setFloat64(0, value, true);\n const valueField = concat([varint((1 << 3) | 1), valueBytes]);\n const timestampField = concat([\n varint((2 << 3) | 0),\n varint(BigInt(timestampMs)),\n ]);\n return concat([valueField, timestampField]);\n}\n\n// TimeSeries { repeated Label labels = 1; repeated Sample samples = 2; }\n/**\n * Encode a single time series (one set of labels with one sample) as a protobuf `TimeSeries`.\n *\n * @param labels - Array of `[name, value]` label pairs.\n * @param value - Metric value for the single sample.\n * @param timestampMs - Timestamp in milliseconds since epoch.\n * @returns Encoded `TimeSeries` message as a `Uint8Array`.\n */\nfunction encodeTimeSeries(\n labels: readonly (readonly [string, string])[],\n value: number,\n timestampMs: number,\n): Uint8Array {\n const labelFields = labels.map(([name, labelValue]) =>\n lengthDelimitedField(1, encodeLabel(name, labelValue)),\n );\n const sampleField = lengthDelimitedField(2, encodeSample(value, timestampMs));\n return concat([...labelFields, sampleField]);\n}\n\n/**\n * Build the sorted label pairs for a sample point, ready for the protobuf encoder.\n *\n * @param sample - The sample point whose labels to extract.\n * @returns Label pairs sorted lexicographically by name (required by Mimir).\n */\nfunction sampleLabels(sample: SamplePoint): [string, string][] {\n // Mimir requires label names sorted lexicographically; `__name__` (ASCII `_` = 95)\n // precedes all lowercase keys, so the array below is already in order.\n // `le` sorts between `instance` and `repo` and is only present on _bucket samples.\n const labels: [string, string][] = [\n ['__name__', sample.metric],\n ['agent_vendor', sample.labels.agent_vendor],\n ['event_type', sample.labels.event_type],\n ['instance', sample.labels.instance],\n ];\n if (sample.le !== undefined) {\n labels.push(['le', sample.le]);\n }\n labels.push(\n ['repo', sample.labels.repo],\n ['tool_name', sample.labels.tool_name],\n ['tool_type', sample.labels.tool_type],\n );\n return labels;\n}\n\n/**\n * Encode a batch of samples as an (uncompressed) `WriteRequest` protobuf.\n *\n * @param samples - Sample points to encode into `TimeSeries` messages.\n * @returns Raw protobuf `WriteRequest` bytes.\n */\nexport function encodeWriteRequest(samples: SamplePoint[]): Uint8Array {\n // WriteRequest { repeated TimeSeries timeseries = 1; }\n const series = samples.map((sample) =>\n lengthDelimitedField(\n 1,\n encodeTimeSeries(sampleLabels(sample), sample.value, sample.timestampMs),\n ),\n );\n return concat(series);\n}\n\n/**\n * Wrap raw bytes in a snappy block as a single all-literals run.\n * Format: varint(uncompressedLength) + literal element(s) + raw bytes.\n *\n * @param data - The raw bytes to wrap.\n * @returns A snappy-framed byte array accepted by any snappy decoder.\n */\nexport function snappyBlock(data: Uint8Array): Uint8Array {\n if (data.length === 0) {\n return varint(0); // valid snappy block: uncompressed length 0, no literals\n }\n\n const preamble = varint(data.length);\n const literalLengthMinusOne = data.length - 1;\n\n if (literalLengthMinusOne < 60) {\n const tag = literalLengthMinusOne << 2; // literal tag, wire type 0b00\n return concat([preamble, Uint8Array.from([tag]), data]);\n }\n\n // Length needs 1-4 extra little-endian bytes; the 6-bit field holds 59 + count.\n const extraBytes: number[] = [];\n let remaining = literalLengthMinusOne;\n while (remaining > 0) {\n extraBytes.push(remaining & 0xff);\n remaining >>>= 8;\n }\n const tag = (59 + extraBytes.length) << 2;\n return concat([preamble, Uint8Array.from([tag, ...extraBytes]), data]);\n}\n\n/**\n * Encode samples into the exact body posted to the remote_write endpoint.\n *\n * @param samples - The sample points to encode.\n * @returns A snappy-wrapped protobuf `WriteRequest`.\n */\nexport function encodeRemoteWrite(samples: SamplePoint[]): Uint8Array {\n return snappyBlock(encodeWriteRequest(samples));\n}\n"]}
@@ -0,0 +1,24 @@
1
+ import type { SamplePoint } from "./types.cjs";
2
+ /**
3
+ * Encode a batch of samples as an (uncompressed) `WriteRequest` protobuf.
4
+ *
5
+ * @param samples - Sample points to encode into `TimeSeries` messages.
6
+ * @returns Raw protobuf `WriteRequest` bytes.
7
+ */
8
+ export declare function encodeWriteRequest(samples: SamplePoint[]): Uint8Array;
9
+ /**
10
+ * Wrap raw bytes in a snappy block as a single all-literals run.
11
+ * Format: varint(uncompressedLength) + literal element(s) + raw bytes.
12
+ *
13
+ * @param data - The raw bytes to wrap.
14
+ * @returns A snappy-framed byte array accepted by any snappy decoder.
15
+ */
16
+ export declare function snappyBlock(data: Uint8Array): Uint8Array;
17
+ /**
18
+ * Encode samples into the exact body posted to the remote_write endpoint.
19
+ *
20
+ * @param samples - The sample points to encode.
21
+ * @returns A snappy-wrapped protobuf `WriteRequest`.
22
+ */
23
+ export declare function encodeRemoteWrite(samples: SamplePoint[]): Uint8Array;
24
+ //# sourceMappingURL=remoteWrite.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remoteWrite.d.cts","sourceRoot":"","sources":["../../src/lib/remoteWrite.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,oBAAgB;AA0I3C;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,UAAU,CASrE;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU,CAsBxD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,UAAU,CAEpE"}
@@ -0,0 +1,24 @@
1
+ import type { SamplePoint } from "./types.mjs";
2
+ /**
3
+ * Encode a batch of samples as an (uncompressed) `WriteRequest` protobuf.
4
+ *
5
+ * @param samples - Sample points to encode into `TimeSeries` messages.
6
+ * @returns Raw protobuf `WriteRequest` bytes.
7
+ */
8
+ export declare function encodeWriteRequest(samples: SamplePoint[]): Uint8Array;
9
+ /**
10
+ * Wrap raw bytes in a snappy block as a single all-literals run.
11
+ * Format: varint(uncompressedLength) + literal element(s) + raw bytes.
12
+ *
13
+ * @param data - The raw bytes to wrap.
14
+ * @returns A snappy-framed byte array accepted by any snappy decoder.
15
+ */
16
+ export declare function snappyBlock(data: Uint8Array): Uint8Array;
17
+ /**
18
+ * Encode samples into the exact body posted to the remote_write endpoint.
19
+ *
20
+ * @param samples - The sample points to encode.
21
+ * @returns A snappy-wrapped protobuf `WriteRequest`.
22
+ */
23
+ export declare function encodeRemoteWrite(samples: SamplePoint[]): Uint8Array;
24
+ //# sourceMappingURL=remoteWrite.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remoteWrite.d.mts","sourceRoot":"","sources":["../../src/lib/remoteWrite.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,oBAAgB;AA0I3C;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,UAAU,CASrE;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU,CAsBxD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,UAAU,CAEpE"}
@@ -0,0 +1,172 @@
1
+ // Prometheus remote_write encoder for the daily anonymizer (MCWP-644 / INFRA-3677).
2
+ //
3
+ // The Mimir remote_write endpoint accepts only snappy-compressed protobuf
4
+ // (`WriteRequest`) with per-sample timestamps. Both the protobuf and the snappy
5
+ // block are hand-rolled here so the tool stays dependency-free:
6
+ // - protobuf: the WriteRequest schema is four tiny messages, trivially encoded.
7
+ // - snappy: a valid block can be a single all-literals run (no back-references),
8
+ // which any snappy decoder accepts. We skip real compression — payloads are
9
+ // KB-scale, so the ratio is irrelevant — and avoid pulling a snappy library.
10
+ const textEncoder = new TextEncoder();
11
+ /**
12
+ * Concatenate multiple `Uint8Array` chunks into a single array.
13
+ *
14
+ * @param chunks - Arrays of bytes to concatenate, in order.
15
+ * @returns A single `Uint8Array` containing all chunks concatenated.
16
+ */
17
+ function concat(chunks) {
18
+ const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
19
+ const out = new Uint8Array(total);
20
+ let offset = 0;
21
+ for (const chunk of chunks) {
22
+ out.set(chunk, offset);
23
+ offset += chunk.length;
24
+ }
25
+ return out;
26
+ }
27
+ /**
28
+ * Base-128 varint (protobuf + snappy length preamble). BigInt-safe for int64.
29
+ *
30
+ * @param value - The integer to encode as a varint.
31
+ * @returns Base-128 varint encoding as a `Uint8Array`.
32
+ */
33
+ function varint(value) {
34
+ let remaining = typeof value === 'bigint' ? value : BigInt(value);
35
+ const bytes = [];
36
+ do {
37
+ let byte = Number(remaining & 0x7fn);
38
+ remaining >>= 7n;
39
+ if (remaining > 0n) {
40
+ byte |= 0x80;
41
+ }
42
+ bytes.push(byte);
43
+ } while (remaining > 0n);
44
+ return Uint8Array.from(bytes);
45
+ }
46
+ /**
47
+ * A length-delimited (wire type 2) field: tag byte, length varint, then the payload.
48
+ *
49
+ * @param fieldNumber - The protobuf field number (1-based).
50
+ * @param payload - Raw bytes of the field value.
51
+ * @returns Wire-encoded length-delimited field as a `Uint8Array`.
52
+ */
53
+ function lengthDelimitedField(fieldNumber, payload) {
54
+ const tag = (fieldNumber << 3) | 2;
55
+ return concat([varint(tag), varint(payload.length), payload]);
56
+ }
57
+ // Label { name = 1; value = 2; }
58
+ /**
59
+ * Encode a Prometheus label name/value pair as a protobuf `Label` message.
60
+ *
61
+ * @param name - Label name.
62
+ * @param value - Label value.
63
+ * @returns Encoded `Label` message as a `Uint8Array`.
64
+ */
65
+ function encodeLabel(name, value) {
66
+ return concat([
67
+ lengthDelimitedField(1, textEncoder.encode(name)),
68
+ lengthDelimitedField(2, textEncoder.encode(value)),
69
+ ]);
70
+ }
71
+ // Sample { double value = 1; int64 timestamp = 2; }
72
+ /**
73
+ * Encode a Prometheus sample value and timestamp as a protobuf `Sample` message.
74
+ *
75
+ * @param value - Metric value (float64).
76
+ * @param timestampMs - Timestamp in milliseconds since epoch.
77
+ * @returns Encoded `Sample` message as a `Uint8Array`.
78
+ */
79
+ function encodeSample(value, timestampMs) {
80
+ const valueBytes = new Uint8Array(8);
81
+ new DataView(valueBytes.buffer).setFloat64(0, value, true);
82
+ const valueField = concat([varint((1 << 3) | 1), valueBytes]);
83
+ const timestampField = concat([
84
+ varint((2 << 3) | 0),
85
+ varint(BigInt(timestampMs)),
86
+ ]);
87
+ return concat([valueField, timestampField]);
88
+ }
89
+ // TimeSeries { repeated Label labels = 1; repeated Sample samples = 2; }
90
+ /**
91
+ * Encode a single time series (one set of labels with one sample) as a protobuf `TimeSeries`.
92
+ *
93
+ * @param labels - Array of `[name, value]` label pairs.
94
+ * @param value - Metric value for the single sample.
95
+ * @param timestampMs - Timestamp in milliseconds since epoch.
96
+ * @returns Encoded `TimeSeries` message as a `Uint8Array`.
97
+ */
98
+ function encodeTimeSeries(labels, value, timestampMs) {
99
+ const labelFields = labels.map(([name, labelValue]) => lengthDelimitedField(1, encodeLabel(name, labelValue)));
100
+ const sampleField = lengthDelimitedField(2, encodeSample(value, timestampMs));
101
+ return concat([...labelFields, sampleField]);
102
+ }
103
+ /**
104
+ * Build the sorted label pairs for a sample point, ready for the protobuf encoder.
105
+ *
106
+ * @param sample - The sample point whose labels to extract.
107
+ * @returns Label pairs sorted lexicographically by name (required by Mimir).
108
+ */
109
+ function sampleLabels(sample) {
110
+ // Mimir requires label names sorted lexicographically; `__name__` (ASCII `_` = 95)
111
+ // precedes all lowercase keys, so the array below is already in order.
112
+ // `le` sorts between `instance` and `repo` and is only present on _bucket samples.
113
+ const labels = [
114
+ ['__name__', sample.metric],
115
+ ['agent_vendor', sample.labels.agent_vendor],
116
+ ['event_type', sample.labels.event_type],
117
+ ['instance', sample.labels.instance],
118
+ ];
119
+ if (sample.le !== undefined) {
120
+ labels.push(['le', sample.le]);
121
+ }
122
+ labels.push(['repo', sample.labels.repo], ['tool_name', sample.labels.tool_name], ['tool_type', sample.labels.tool_type]);
123
+ return labels;
124
+ }
125
+ /**
126
+ * Encode a batch of samples as an (uncompressed) `WriteRequest` protobuf.
127
+ *
128
+ * @param samples - Sample points to encode into `TimeSeries` messages.
129
+ * @returns Raw protobuf `WriteRequest` bytes.
130
+ */
131
+ export function encodeWriteRequest(samples) {
132
+ // WriteRequest { repeated TimeSeries timeseries = 1; }
133
+ const series = samples.map((sample) => lengthDelimitedField(1, encodeTimeSeries(sampleLabels(sample), sample.value, sample.timestampMs)));
134
+ return concat(series);
135
+ }
136
+ /**
137
+ * Wrap raw bytes in a snappy block as a single all-literals run.
138
+ * Format: varint(uncompressedLength) + literal element(s) + raw bytes.
139
+ *
140
+ * @param data - The raw bytes to wrap.
141
+ * @returns A snappy-framed byte array accepted by any snappy decoder.
142
+ */
143
+ export function snappyBlock(data) {
144
+ if (data.length === 0) {
145
+ return varint(0); // valid snappy block: uncompressed length 0, no literals
146
+ }
147
+ const preamble = varint(data.length);
148
+ const literalLengthMinusOne = data.length - 1;
149
+ if (literalLengthMinusOne < 60) {
150
+ const tag = literalLengthMinusOne << 2; // literal tag, wire type 0b00
151
+ return concat([preamble, Uint8Array.from([tag]), data]);
152
+ }
153
+ // Length needs 1-4 extra little-endian bytes; the 6-bit field holds 59 + count.
154
+ const extraBytes = [];
155
+ let remaining = literalLengthMinusOne;
156
+ while (remaining > 0) {
157
+ extraBytes.push(remaining & 0xff);
158
+ remaining >>>= 8;
159
+ }
160
+ const tag = (59 + extraBytes.length) << 2;
161
+ return concat([preamble, Uint8Array.from([tag, ...extraBytes]), data]);
162
+ }
163
+ /**
164
+ * Encode samples into the exact body posted to the remote_write endpoint.
165
+ *
166
+ * @param samples - The sample points to encode.
167
+ * @returns A snappy-wrapped protobuf `WriteRequest`.
168
+ */
169
+ export function encodeRemoteWrite(samples) {
170
+ return snappyBlock(encodeWriteRequest(samples));
171
+ }
172
+ //# sourceMappingURL=remoteWrite.mjs.map