@volter/supercode-teams 0.2.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +119 -0
- package/attention.mjs +191 -0
- package/bin/fleet.mjs +151 -0
- package/bin/teams.mjs +291 -0
- package/bin/workspace/args.mjs +66 -0
- package/bin/workspace/files.mjs +110 -0
- package/bin/workspace/lifecycle.mjs +113 -0
- package/bin/workspace/volter-device.mjs +42 -0
- package/bin/workspace/volter-login.mjs +185 -0
- package/bin/workspace.mjs +726 -0
- package/browser-attach.mjs +90 -0
- package/client.mjs +212 -0
- package/connector/effects.mjs +95 -0
- package/connector/index.d.ts +69 -0
- package/connector/index.mjs +174 -0
- package/connector/native-continuation.d.ts +8 -0
- package/connector/native-continuation.mjs +50 -0
- package/connector/native-effects.mjs +164 -0
- package/context.mjs +34 -0
- package/contracts/adapter-map.json +16 -0
- package/contracts/index.d.ts +299 -0
- package/contracts/index.mjs +37 -0
- package/execution.mjs +206 -0
- package/fleet.mjs +78 -0
- package/index.mjs +12 -0
- package/machine-door.mjs +36 -0
- package/machine.mjs +1398 -0
- package/package.json +80 -0
- package/porthole-hook.cjs +137 -0
- package/remote/job-exec.py +146 -0
- package/remote/wire.py +23 -0
- package/server/arc2-transactions.mjs +125 -0
- package/server/attachment-relay.mjs +149 -0
- package/server/catalog.mjs +7 -0
- package/server/continuations.mjs +56 -0
- package/server/correlation.d.ts +4 -0
- package/server/correlation.mjs +17 -0
- package/server/effects-transactions.mjs +119 -0
- package/server/effects.mjs +97 -0
- package/server/event-routes.mjs +208 -0
- package/server/exports.mjs +46 -0
- package/server/harness-routes.mjs +248 -0
- package/server/index.d.ts +31 -0
- package/server/index.mjs +52 -0
- package/server/ingest.mjs +24 -0
- package/server/node-upgrade.mjs +39 -0
- package/server/otlp/collector-config.d.ts +2 -0
- package/server/otlp/collector-config.mjs +26 -0
- package/server/otlp/decoders/index.d.ts +63 -0
- package/server/otlp/decoders/index.mjs +311 -0
- package/server/otlp/index.d.ts +4 -0
- package/server/otlp/index.mjs +4 -0
- package/server/otlp/projection.d.ts +5 -0
- package/server/otlp/projection.mjs +17 -0
- package/server/otlp/receiver.d.ts +3 -0
- package/server/otlp/receiver.mjs +36 -0
- package/server/otlp/schema/google/protobuf/any.proto +162 -0
- package/server/otlp/schema/google/rpc/status.proto +48 -0
- package/server/otlp/schema/manifest.json +22 -0
- package/server/otlp/schema/opentelemetry/proto/collector/logs/v1/logs_service.proto +77 -0
- package/server/otlp/schema/opentelemetry/proto/collector/metrics/v1/metrics_service.proto +77 -0
- package/server/otlp/schema/opentelemetry/proto/collector/trace/v1/trace_service.proto +77 -0
- package/server/otlp/schema/opentelemetry/proto/common/v1/common.proto +154 -0
- package/server/otlp/schema/opentelemetry/proto/logs/v1/logs.proto +226 -0
- package/server/otlp/schema/opentelemetry/proto/metrics/v1/metrics.proto +735 -0
- package/server/otlp/schema/opentelemetry/proto/resource/v1/resource.proto +45 -0
- package/server/otlp/schema/opentelemetry/proto/trace/v1/trace.proto +359 -0
- package/server/otlp/transport.d.ts +10 -0
- package/server/otlp/transport.mjs +424 -0
- package/server/otlp/wire-codec.mjs +153 -0
- package/server/store-core.mjs +139 -0
- package/server/store-worker.mjs +23 -0
- package/worker/fetch-adapter.mjs +49 -0
- package/worker/index.mjs +127 -0
- package/worker/websocket-duplex.mjs +29 -0
- package/worker/wrangler.toml +38 -0
- package/workspace-client.d.ts +75 -0
- package/workspace-client.mjs +171 -0
- package/workspace-move.mjs +38 -0
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import { gunzipSync } from "node:zlib";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import protobuf from "protobufjs";
|
|
5
|
+
import { decodeToObject, encodeFromObject } from "./wire-codec.mjs";
|
|
6
|
+
|
|
7
|
+
export const MAX_UNCOMPRESSED_BODY_BYTES = 16 * 1024 * 1024;
|
|
8
|
+
|
|
9
|
+
const SIGNALS = {
|
|
10
|
+
logs: {
|
|
11
|
+
request: "opentelemetry.proto.collector.logs.v1.ExportLogsServiceRequest",
|
|
12
|
+
response: "opentelemetry.proto.collector.logs.v1.ExportLogsServiceResponse",
|
|
13
|
+
roots: "resourceLogs", scopes: "scopeLogs", records: "logRecords",
|
|
14
|
+
rejected: "rejectedLogRecords"
|
|
15
|
+
},
|
|
16
|
+
traces: {
|
|
17
|
+
request: "opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest",
|
|
18
|
+
response: "opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse",
|
|
19
|
+
roots: "resourceSpans", scopes: "scopeSpans", records: "spans",
|
|
20
|
+
rejected: "rejectedSpans"
|
|
21
|
+
},
|
|
22
|
+
metrics: {
|
|
23
|
+
request: "opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest",
|
|
24
|
+
response: "opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceResponse",
|
|
25
|
+
roots: "resourceMetrics", scopes: "scopeMetrics", records: "metrics",
|
|
26
|
+
rejected: "rejectedDataPoints"
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
let schemaPromise;
|
|
31
|
+
let schemaSources = null;
|
|
32
|
+
const SCHEMA_ENTRIES = [
|
|
33
|
+
"opentelemetry/proto/collector/logs/v1/logs_service.proto",
|
|
34
|
+
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
35
|
+
"opentelemetry/proto/collector/metrics/v1/metrics_service.proto",
|
|
36
|
+
"google/rpc/status.proto"
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Supply the pinned .proto files as text, keyed by their path under schema/ (e.g.
|
|
41
|
+
* "google/rpc/status.proto"), for a host with no filesystem. Without it they are read from disk.
|
|
42
|
+
*/
|
|
43
|
+
export function setOtlpSchemaSources(sources) {
|
|
44
|
+
schemaSources = sources;
|
|
45
|
+
schemaPromise = undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function schema() {
|
|
49
|
+
if (schemaSources) {
|
|
50
|
+
schemaPromise ??= Promise.resolve().then(() => {
|
|
51
|
+
const root = new protobuf.Root();
|
|
52
|
+
const loaded = new Set();
|
|
53
|
+
const load = (name) => {
|
|
54
|
+
if (loaded.has(name)) return;
|
|
55
|
+
loaded.add(name);
|
|
56
|
+
const text = schemaSources[name];
|
|
57
|
+
if (typeof text !== "string") throw new Error(`OTLP schema source missing: ${name}`);
|
|
58
|
+
const parsed = protobuf.parse(text, root, { keepCase: false });
|
|
59
|
+
for (const dependency of [...(parsed.imports ?? []), ...(parsed.weakImports ?? [])]) load(dependency);
|
|
60
|
+
};
|
|
61
|
+
for (const entry of SCHEMA_ENTRIES) load(entry);
|
|
62
|
+
return root.resolveAll();
|
|
63
|
+
});
|
|
64
|
+
return schemaPromise;
|
|
65
|
+
}
|
|
66
|
+
const base = path.join(path.dirname(fileURLToPath(import.meta.url)), "schema");
|
|
67
|
+
const root = new protobuf.Root();
|
|
68
|
+
root.resolvePath = (_origin, target) => path.isAbsolute(target) ? target : path.join(base, target);
|
|
69
|
+
schemaPromise ??= root.load(SCHEMA_ENTRIES.map((name) => path.join(base, name)));
|
|
70
|
+
return schemaPromise;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function config(signal) {
|
|
74
|
+
const value = SIGNALS[signal];
|
|
75
|
+
if (!value) throw new TypeError(`unsupported OTLP signal: ${signal}`);
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function representation(value) {
|
|
80
|
+
if (value === "json" || value === "application/json") return "json";
|
|
81
|
+
if (value === "protobuf" || value === "application/x-protobuf") return "protobuf";
|
|
82
|
+
throw new TypeError(`unsupported OTLP representation: ${value}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function asBytes(input) {
|
|
86
|
+
if (input instanceof Uint8Array) return input;
|
|
87
|
+
if (typeof input === "string") return Buffer.from(input);
|
|
88
|
+
throw new TypeError("OTLP body must be a string or Uint8Array");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function ownArray(value, key, where) {
|
|
92
|
+
if (value[key] === undefined) return [];
|
|
93
|
+
if (!Array.isArray(value[key])) throw new TypeError(`${where}.${key} must be an array`);
|
|
94
|
+
return value[key];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const UINT64_MAX = (1n << 64n) - 1n;
|
|
98
|
+
const INT64_MIN = -(1n << 63n), INT64_MAX = (1n << 63n) - 1n;
|
|
99
|
+
const ID_LENGTHS = new Map([["traceId", 32], ["spanId", 16], ["parentSpanId", 16]]);
|
|
100
|
+
function canonicalInteger(value, signed, key) {
|
|
101
|
+
if (typeof value !== "string" || !/^-?(0|[1-9][0-9]*)$/.test(value) || value.length > 21) throw new TypeError(`${key} is not a decimal 64-bit integer`);
|
|
102
|
+
const integer = BigInt(value);
|
|
103
|
+
if (signed ? integer < INT64_MIN || integer > INT64_MAX : integer < 0n || integer > UINT64_MAX) throw new TypeError(`${key} is outside its 64-bit range`);
|
|
104
|
+
return integer.toString();
|
|
105
|
+
}
|
|
106
|
+
function validUint64(value) {
|
|
107
|
+
if (value === undefined || value === null || value === "") return true;
|
|
108
|
+
if (typeof value === "number") return Number.isSafeInteger(value) && value >= 0;
|
|
109
|
+
if (typeof value !== "string" || !/^(0|[1-9][0-9]*)$/.test(value) || value.length > 20) return false;
|
|
110
|
+
return BigInt(value) <= UINT64_MAX;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
class RawJsonNumber { constructor(raw) { this.raw = raw; } }
|
|
114
|
+
function setData(object, key, value) { Object.defineProperty(object, key, { value, enumerable: true, configurable: true, writable: true }); }
|
|
115
|
+
function parseLosslessJson(text) {
|
|
116
|
+
let index = 0;
|
|
117
|
+
const whitespace = () => { while (/\s/.test(text[index] ?? "")) index++; };
|
|
118
|
+
const value = () => {
|
|
119
|
+
whitespace(); const char = text[index];
|
|
120
|
+
if (char === '"') {
|
|
121
|
+
const start = index++;
|
|
122
|
+
while (index < text.length) { if (text[index] === "\\") index += 2; else if (text[index++] === '"') return JSON.parse(text.slice(start, index)); }
|
|
123
|
+
throw new SyntaxError("unterminated string");
|
|
124
|
+
}
|
|
125
|
+
if (char === "{") {
|
|
126
|
+
index++; const result = {}; whitespace(); if (text[index] === "}") { index++; return result; }
|
|
127
|
+
while (true) { const key = value(); if (typeof key !== "string") throw new SyntaxError("object key must be a string"); whitespace(); if (text[index++] !== ":") throw new SyntaxError("expected colon"); setData(result,key,value()); whitespace(); const next = text[index++]; if (next === "}") return result; if (next !== ",") throw new SyntaxError("expected comma"); }
|
|
128
|
+
}
|
|
129
|
+
if (char === "[") {
|
|
130
|
+
index++; const result = []; whitespace(); if (text[index] === "]") { index++; return result; }
|
|
131
|
+
while (true) { result.push(value()); whitespace(); const next = text[index++]; if (next === "]") return result; if (next !== ",") throw new SyntaxError("expected comma"); }
|
|
132
|
+
}
|
|
133
|
+
for (const [literal, result] of [["true", true], ["false", false], ["null", null]]) if (text.startsWith(literal, index)) { index += literal.length; return result; }
|
|
134
|
+
const number = /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/.exec(text.slice(index));
|
|
135
|
+
if (number) { index += number[0].length; return new RawJsonNumber(number[0]); }
|
|
136
|
+
throw new SyntaxError("invalid JSON value");
|
|
137
|
+
};
|
|
138
|
+
const result = value(); whitespace(); if (index !== text.length) throw new SyntaxError("trailing JSON content"); return result;
|
|
139
|
+
}
|
|
140
|
+
function rawInteger(value, signed, key) {
|
|
141
|
+
if (typeof value === "string") return canonicalInteger(value, signed, key);
|
|
142
|
+
if (!(value instanceof RawJsonNumber)) throw new TypeError(`${key} is not a 64-bit integer`);
|
|
143
|
+
const match = /^(-?)([0-9]+)(?:\.([0-9]+))?(?:[eE]([+-]?[0-9]+))?$/.exec(value.raw);
|
|
144
|
+
if (!match) throw new TypeError(`${key} is not an integer number`);
|
|
145
|
+
const fraction = match[3] ?? ""; const exponent = Number(match[4] ?? 0);
|
|
146
|
+
if (!Number.isSafeInteger(exponent) || Math.abs(exponent) > 100) throw new TypeError(`${key} exponent is outside the receiver limit`);
|
|
147
|
+
let digits = `${match[2]}${fraction}`, scale = exponent - fraction.length;
|
|
148
|
+
if (scale < 0) { const cut = digits.length + scale; if (cut < 0 || !/^0*$/.test(digits.slice(Math.max(0, cut)))) throw new TypeError(`${key} is not an integer number`); digits = cut <= 0 ? "0" : digits.slice(0, cut); }
|
|
149
|
+
else digits += "0".repeat(scale);
|
|
150
|
+
digits = digits.replace(/^0+(?=\d)/, ""); return canonicalInteger(`${match[1]}${digits}`, signed, key);
|
|
151
|
+
}
|
|
152
|
+
function unknownJson(value) {
|
|
153
|
+
if (value instanceof RawJsonNumber) return Number(value.raw);
|
|
154
|
+
if (Array.isArray(value)) return value.map(unknownJson);
|
|
155
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, unknownJson(child)]));
|
|
156
|
+
return value;
|
|
157
|
+
}
|
|
158
|
+
const SIGNED_PROTO64 = new Set(["int64", "sint64", "sfixed64"]), UNSIGNED_PROTO64 = new Set(["uint64", "fixed64"]);
|
|
159
|
+
function normalizeJsonMessage(type, input, where = type.name) {
|
|
160
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) throw new TypeError(`${where} must be an object`);
|
|
161
|
+
const result = {};
|
|
162
|
+
for (const [key, raw] of Object.entries(input)) {
|
|
163
|
+
const field = Object.hasOwn(type.fields,key) ? type.fields[key] : null;
|
|
164
|
+
if (!field) { setData(result,key,unknownJson(raw)); continue; }
|
|
165
|
+
if (raw === null) continue;
|
|
166
|
+
const normalizeOne = (item) => {
|
|
167
|
+
if (SIGNED_PROTO64.has(field.type)) return rawInteger(item, true, key);
|
|
168
|
+
if (UNSIGNED_PROTO64.has(field.type)) return rawInteger(item, false, key);
|
|
169
|
+
if (field.resolvedType instanceof protobuf.Enum) {
|
|
170
|
+
const number = item instanceof RawJsonNumber ? Number(item.raw) : item;
|
|
171
|
+
if (!Number.isInteger(number)) throw new TypeError(`${key} enum must be encoded as an integer`); return number;
|
|
172
|
+
}
|
|
173
|
+
if (field.resolvedType instanceof protobuf.Type) return normalizeJsonMessage(field.resolvedType, item, `${where}.${key}`);
|
|
174
|
+
if (field.type === "bytes" && ID_LENGTHS.has(key)) {
|
|
175
|
+
if (typeof item !== "string" || (item !== "" && (item.length !== ID_LENGTHS.get(key) || !/^[0-9a-f]+$/i.test(item)))) throw new TypeError(`${key} is not a ${ID_LENGTHS.get(key) / 2}-byte hexadecimal ID`);
|
|
176
|
+
return item.toLowerCase();
|
|
177
|
+
}
|
|
178
|
+
if (field.type === "string") { if (typeof item !== "string") throw new TypeError(`${where}.${key} must be a string`); return item; }
|
|
179
|
+
if (field.type === "bool") { if (typeof item !== "boolean") throw new TypeError(`${where}.${key} must be a boolean`); return item; }
|
|
180
|
+
if (field.type === "bytes") {
|
|
181
|
+
if (typeof item !== "string" || !/^(?:[A-Za-z0-9+/_-]{4})*(?:[A-Za-z0-9+/_-]{2}==?|[A-Za-z0-9+/_-]{3}=?)?$/.test(item)) throw new TypeError(`${where}.${key} must be base64 bytes`);
|
|
182
|
+
return item;
|
|
183
|
+
}
|
|
184
|
+
if (["double", "float"].includes(field.type)) {
|
|
185
|
+
if (typeof item === "string" && ["NaN", "Infinity", "-Infinity"].includes(item)) return item;
|
|
186
|
+
if (!(item instanceof RawJsonNumber)) throw new TypeError(`${where}.${key} must be a number`);
|
|
187
|
+
const number = Number(item.raw); if (!Number.isFinite(number)) throw new TypeError(`${where}.${key} is outside the numeric range`); return number;
|
|
188
|
+
}
|
|
189
|
+
if (["int32", "sint32", "sfixed32", "uint32", "fixed32"].includes(field.type)) {
|
|
190
|
+
if (!(item instanceof RawJsonNumber) || /[.eE]/.test(item.raw)) throw new TypeError(`${where}.${key} must be an integer number`);
|
|
191
|
+
const number = Number(item.raw), unsigned = field.type.startsWith("u") || field.type.startsWith("fixed");
|
|
192
|
+
if (!Number.isInteger(number) || number < (unsigned ? 0 : -2147483648) || number > (unsigned ? 4294967295 : 2147483647)) throw new TypeError(`${where}.${key} is outside its 32-bit range`);
|
|
193
|
+
return number;
|
|
194
|
+
}
|
|
195
|
+
if (item instanceof RawJsonNumber) return Number(item.raw);
|
|
196
|
+
return unknownJson(item);
|
|
197
|
+
};
|
|
198
|
+
if (field.repeated) { if (!Array.isArray(raw)) throw new TypeError(`${where}.${key} must be an array`); setData(result,key,raw.map(normalizeOne)); }
|
|
199
|
+
else setData(result,key,normalizeOne(raw));
|
|
200
|
+
}
|
|
201
|
+
for (const oneof of Object.values(type.oneofs ?? {})) {
|
|
202
|
+
const populated = oneof.oneof.filter((key) => result[key] !== undefined);
|
|
203
|
+
if (populated.length > 1) throw new TypeError(`${where} has conflicting oneof fields: ${populated.join(", ")}`);
|
|
204
|
+
}
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
function normalizeProtobufValue(value, key = "") {
|
|
208
|
+
if (ID_LENGTHS.has(key) && typeof value === "string") return value === "" ? "" : Buffer.from(value, "base64").toString("hex");
|
|
209
|
+
if (Array.isArray(value)) return value.map((item) => normalizeProtobufValue(item));
|
|
210
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([childKey, child]) => [childKey, normalizeProtobufValue(child, childKey)]));
|
|
211
|
+
return value;
|
|
212
|
+
}
|
|
213
|
+
const NORMALIZATION_ERROR = Symbol("otlpNormalizationError");
|
|
214
|
+
function without(input, keys) { return Object.fromEntries(Object.entries(input).filter(([key]) => !keys.includes(key))); }
|
|
215
|
+
function markError(value, error) { if (value && typeof value === "object") Object.defineProperty(value, NORMALIZATION_ERROR, { value: error.message }); return value; }
|
|
216
|
+
function normalizeJsonEnvelope(type, c, input) {
|
|
217
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) throw new TypeError("OTLP request must be an object");
|
|
218
|
+
const rootField = type.fields[c.roots];
|
|
219
|
+
const output = normalizeJsonMessage(type, without(input, [c.roots]));
|
|
220
|
+
const roots = input[c.roots] ?? []; if (!Array.isArray(roots)) throw new TypeError(`request.${c.roots} must be an array`);
|
|
221
|
+
output[c.roots] = roots.map((group, resourceIndex) => {
|
|
222
|
+
if (!group || typeof group !== "object" || Array.isArray(group)) throw new TypeError(`${c.roots}[${resourceIndex}] must be an object`);
|
|
223
|
+
const groupType = rootField.resolvedType, groupOutput = normalizeJsonMessage(groupType, without(group, [c.scopes]));
|
|
224
|
+
const scopeField = groupType.fields[c.scopes], scopes = group[c.scopes] ?? []; if (!Array.isArray(scopes)) throw new TypeError(`${c.roots}[${resourceIndex}].${c.scopes} must be an array`);
|
|
225
|
+
groupOutput[c.scopes] = scopes.map((scopeGroup, scopeIndex) => {
|
|
226
|
+
if (!scopeGroup || typeof scopeGroup !== "object" || Array.isArray(scopeGroup)) throw new TypeError(`${c.scopes}[${scopeIndex}] must be an object`);
|
|
227
|
+
const scopeType = scopeField.resolvedType, scopeOutput = normalizeJsonMessage(scopeType, without(scopeGroup, [c.records]));
|
|
228
|
+
const recordField = scopeType.fields[c.records], items = scopeGroup[c.records] ?? []; if (!Array.isArray(items)) throw new TypeError(`${c.scopes}[${scopeIndex}].${c.records} must be an array`);
|
|
229
|
+
scopeOutput[c.records] = items.map((item) => {
|
|
230
|
+
if (c.records !== "metrics") { try { return normalizeJsonMessage(recordField.resolvedType, item); } catch (error) { return markError(unknownJson(item), error); } }
|
|
231
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return item;
|
|
232
|
+
const metricType = recordField.resolvedType, dataKeys = METRIC_DATA_KEYS.filter((key) => item[key] !== undefined && item[key] !== null);
|
|
233
|
+
if (dataKeys.length > 1) throw new TypeError("metric must contain at most one data field");
|
|
234
|
+
const metricOutput = normalizeJsonMessage(metricType, without(item, METRIC_DATA_KEYS));
|
|
235
|
+
if (!dataKeys.length) return metricOutput;
|
|
236
|
+
const dataKey = dataKeys[0], data = item[dataKey], dataField = metricType.fields[dataKey];
|
|
237
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) throw new TypeError(`${dataKey} must be an object`);
|
|
238
|
+
const dataOutput = normalizeJsonMessage(dataField.resolvedType, without(data, ["dataPoints"]));
|
|
239
|
+
const pointField = dataField.resolvedType.fields.dataPoints, points = data.dataPoints ?? [];
|
|
240
|
+
if (!Array.isArray(points)) throw new TypeError(`${dataKey}.dataPoints must be an array`);
|
|
241
|
+
dataOutput.dataPoints = points.map((point) => { try { return normalizeJsonMessage(pointField.resolvedType, point); } catch (error) { return markError(unknownJson(point), error); } });
|
|
242
|
+
metricOutput[dataKey] = dataOutput; return metricOutput;
|
|
243
|
+
});
|
|
244
|
+
return scopeOutput;
|
|
245
|
+
});
|
|
246
|
+
return groupOutput;
|
|
247
|
+
});
|
|
248
|
+
return output;
|
|
249
|
+
}
|
|
250
|
+
function protobufInputValue(value, key = "") {
|
|
251
|
+
if (ID_LENGTHS.has(key) && typeof value === "string") return Buffer.from(value, "hex");
|
|
252
|
+
if (Array.isArray(value)) return value.map((item) => protobufInputValue(item));
|
|
253
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([childKey, child]) => [childKey, protobufInputValue(child, childKey)]));
|
|
254
|
+
return value;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function validBytes(value, length) {
|
|
258
|
+
if (value === undefined || value === null || value === "") return true;
|
|
259
|
+
return typeof value === "string" && value.length === length * 2 && /^[0-9a-f]+$/i.test(value);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function recordErrors(signal, record) {
|
|
263
|
+
if (!record || typeof record !== "object" || Array.isArray(record)) return ["record must be an object"];
|
|
264
|
+
const errors = [];
|
|
265
|
+
if (record.attributes !== undefined && !Array.isArray(record.attributes)) errors.push("attributes must be an array");
|
|
266
|
+
if (signal === "logs" && record.body !== undefined && (!record.body || typeof record.body !== "object" || Array.isArray(record.body))) errors.push("body must be an AnyValue object");
|
|
267
|
+
if (signal === "traces") {
|
|
268
|
+
for (const key of ["events", "links"]) if (record[key] !== undefined && !Array.isArray(record[key])) errors.push(`${key} must be an array`);
|
|
269
|
+
if (record.status !== undefined && (!record.status || typeof record.status !== "object" || Array.isArray(record.status))) errors.push("status must be an object");
|
|
270
|
+
for (const [index, event] of (Array.isArray(record.events) ? record.events : []).entries()) if (!event || typeof event !== "object" || (event.attributes !== undefined && !Array.isArray(event.attributes))) errors.push(`events[${index}] is malformed`);
|
|
271
|
+
for (const [index, link] of (Array.isArray(record.links) ? record.links : []).entries()) if (!link || typeof link !== "object" || (link.attributes !== undefined && !Array.isArray(link.attributes))) errors.push(`links[${index}] is malformed`);
|
|
272
|
+
}
|
|
273
|
+
const times = signal === "logs" ? ["timeUnixNano", "observedTimeUnixNano"] : signal === "traces" ? ["startTimeUnixNano", "endTimeUnixNano"] : [];
|
|
274
|
+
for (const key of times) if (!validUint64(record[key])) errors.push(`${key} is not a uint64`);
|
|
275
|
+
if ((signal === "logs" || signal === "traces") && !validBytes(record.traceId, 16)) errors.push("traceId is not 16 bytes");
|
|
276
|
+
if ((signal === "logs" || signal === "traces") && !validBytes(record.spanId, 8)) errors.push("spanId is not 8 bytes");
|
|
277
|
+
return errors;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const METRIC_DATA_KEYS = ["gauge", "sum", "histogram", "exponentialHistogram", "summary"];
|
|
281
|
+
function metricEvidence(metric, context) {
|
|
282
|
+
if (!metric || typeof metric !== "object" || Array.isArray(metric)) throw new TypeError("metric must be an object");
|
|
283
|
+
const dataType = METRIC_DATA_KEYS.find((key) => metric[key] !== undefined) ?? null;
|
|
284
|
+
if (METRIC_DATA_KEYS.filter((key) => metric[key] !== undefined).length > 1) throw new TypeError("metric must contain at most one data field");
|
|
285
|
+
const { gauge, sum, histogram, exponentialHistogram, summary, ...identity } = metric;
|
|
286
|
+
if (!dataType) return { records: [{ ...context, evidenceKind: "metric_metadata", record: metric, metric: { ...identity, dataType: null, data: null } }], rejected: [] };
|
|
287
|
+
const data = metric[dataType];
|
|
288
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) throw new TypeError(`${dataType} must be an object`);
|
|
289
|
+
const { dataPoints, ...container } = data;
|
|
290
|
+
if (dataPoints === undefined || (Array.isArray(dataPoints) && dataPoints.length === 0)) {
|
|
291
|
+
return { records: [{ ...context, evidenceKind: "metric_metadata", record: metric, metric: { ...identity, dataType, data: container } }], rejected: [] };
|
|
292
|
+
}
|
|
293
|
+
if (!Array.isArray(dataPoints)) throw new TypeError(`${dataType}.dataPoints must be an array`);
|
|
294
|
+
const records = [], rejected = [];
|
|
295
|
+
dataPoints.forEach((rawPoint, pointIndex) => {
|
|
296
|
+
const point = rawPoint, normalizationError = rawPoint?.[NORMALIZATION_ERROR];
|
|
297
|
+
const evidence = { ...context, evidenceKind: "metric_data_point", record: point, metric: { ...identity, dataType, data: container }, position: { ...context.position, pointIndex } };
|
|
298
|
+
const errors = [];
|
|
299
|
+
if (normalizationError) errors.push(normalizationError);
|
|
300
|
+
if (!point || typeof point !== "object" || Array.isArray(point)) errors.push("data point must be an object");
|
|
301
|
+
else {
|
|
302
|
+
for (const key of ["timeUnixNano", "startTimeUnixNano"]) if (!validUint64(point[key])) errors.push(`${key} is not a uint64`);
|
|
303
|
+
for (const key of ["attributes", "exemplars", "bucketCounts", "explicitBounds", "quantileValues"]) if (point[key] !== undefined && !Array.isArray(point[key])) errors.push(`${key} must be an array`);
|
|
304
|
+
}
|
|
305
|
+
if (errors.length) rejected.push({ evidence, error: errors.join("; ") }); else records.push(evidence);
|
|
306
|
+
});
|
|
307
|
+
return { records, rejected };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function flatten(signal, envelope, receivedAt) {
|
|
311
|
+
const c = config(signal);
|
|
312
|
+
const records = [];
|
|
313
|
+
const rejected = [];
|
|
314
|
+
const roots = ownArray(envelope, c.roots, "request");
|
|
315
|
+
roots.forEach((resourceGroup, resourceIndex) => {
|
|
316
|
+
if (!resourceGroup || typeof resourceGroup !== "object" || Array.isArray(resourceGroup)) throw new TypeError(`${c.roots}[${resourceIndex}] must be an object`);
|
|
317
|
+
if (resourceGroup.resource !== undefined && (!resourceGroup.resource || typeof resourceGroup.resource !== "object" || Array.isArray(resourceGroup.resource))) throw new TypeError(`${c.roots}[${resourceIndex}].resource must be an object`);
|
|
318
|
+
if (resourceGroup.resource?.attributes !== undefined && !Array.isArray(resourceGroup.resource.attributes)) throw new TypeError(`${c.roots}[${resourceIndex}].resource.attributes must be an array`);
|
|
319
|
+
const resource = resourceGroup.resource ?? null;
|
|
320
|
+
const scopes = ownArray(resourceGroup, c.scopes, `${c.roots}[${resourceIndex}]`);
|
|
321
|
+
scopes.forEach((scopeGroup, scopeIndex) => {
|
|
322
|
+
if (!scopeGroup || typeof scopeGroup !== "object" || Array.isArray(scopeGroup)) throw new TypeError(`${c.scopes}[${scopeIndex}] must be an object`);
|
|
323
|
+
if (scopeGroup.scope !== undefined && (!scopeGroup.scope || typeof scopeGroup.scope !== "object" || Array.isArray(scopeGroup.scope))) throw new TypeError(`${c.scopes}[${scopeIndex}].scope must be an object`);
|
|
324
|
+
if (scopeGroup.scope?.attributes !== undefined && !Array.isArray(scopeGroup.scope.attributes)) throw new TypeError(`${c.scopes}[${scopeIndex}].scope.attributes must be an array`);
|
|
325
|
+
const scope = scopeGroup.scope ?? null;
|
|
326
|
+
const items = ownArray(scopeGroup, c.records, `${c.scopes}[${scopeIndex}]`);
|
|
327
|
+
items.forEach((inputRecord, recordIndex) => {
|
|
328
|
+
const context = {
|
|
329
|
+
signal,
|
|
330
|
+
resource,
|
|
331
|
+
resourceSchemaUrl: resourceGroup.schemaUrl ?? null,
|
|
332
|
+
scope,
|
|
333
|
+
scopeSchemaUrl: scopeGroup.schemaUrl ?? null,
|
|
334
|
+
receivedAt,
|
|
335
|
+
position: { resourceIndex, scopeIndex, recordIndex }
|
|
336
|
+
};
|
|
337
|
+
if (signal === "metrics") {
|
|
338
|
+
const result = metricEvidence(inputRecord, context);
|
|
339
|
+
records.push(...result.records); rejected.push(...result.rejected); return;
|
|
340
|
+
}
|
|
341
|
+
const rawRecord = inputRecord, normalizationError = inputRecord?.[NORMALIZATION_ERROR];
|
|
342
|
+
const errors = recordErrors(signal, rawRecord);
|
|
343
|
+
if (normalizationError) errors.unshift(normalizationError);
|
|
344
|
+
const evidence = { ...context, evidenceKind: signal === "logs" ? "log_record" : "span", record: rawRecord };
|
|
345
|
+
if (errors.length) rejected.push({ evidence, error: errors.join("; ") }); else records.push(evidence);
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
return { records, rejected };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export async function decodeOtlpRequest({ signal, representation: encoding, bytes, receivedAt }) {
|
|
353
|
+
const c = config(signal);
|
|
354
|
+
const kind = representation(encoding);
|
|
355
|
+
const input = asBytes(bytes);
|
|
356
|
+
let envelope;
|
|
357
|
+
if (kind === "json") {
|
|
358
|
+
const type = await schema().then((root) => root.lookupType(c.request));
|
|
359
|
+
try { envelope = normalizeJsonEnvelope(type, c, parseLosslessJson(Buffer.from(input).toString("utf8"))); }
|
|
360
|
+
catch (error) { throw new TypeError(`malformed OTLP JSON: ${error.message}`); }
|
|
361
|
+
if (!envelope || typeof envelope !== "object" || Array.isArray(envelope)) throw new TypeError("OTLP request must be an object");
|
|
362
|
+
} else {
|
|
363
|
+
const type = (await schema()).lookupType(c.request);
|
|
364
|
+
try {
|
|
365
|
+
envelope = normalizeProtobufValue(decodeToObject(type, input));
|
|
366
|
+
} catch (error) { throw new TypeError(`malformed OTLP protobuf: ${error.message}`); }
|
|
367
|
+
}
|
|
368
|
+
return { envelope, ...flatten(signal, envelope, receivedAt ?? null) };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export async function encodeOtlpRequest({ signal, representation: encoding, envelope }) {
|
|
372
|
+
const c = config(signal); const kind = representation(encoding);
|
|
373
|
+
if (kind === "json") return Buffer.from(JSON.stringify(envelope));
|
|
374
|
+
const type = (await schema()).lookupType(c.request);
|
|
375
|
+
try { return Buffer.from(encodeFromObject(type, protobufInputValue(envelope))); }
|
|
376
|
+
catch (error) { throw new TypeError(`invalid OTLP request: ${error.message}`); }
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export async function encodeOtlpResponse({ signal, representation: encoding, rejectedCount = 0, errorMessage = "" }) {
|
|
380
|
+
const c = config(signal); const kind = representation(encoding);
|
|
381
|
+
const partialSuccess = rejectedCount > 0 || errorMessage ? { [c.rejected]: String(rejectedCount), errorMessage } : undefined;
|
|
382
|
+
const value = partialSuccess ? { partialSuccess } : {};
|
|
383
|
+
if (kind === "json") return Buffer.from(JSON.stringify(value));
|
|
384
|
+
const type = (await schema()).lookupType(c.response);
|
|
385
|
+
return Buffer.from(encodeFromObject(type, value));
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export async function encodeOtlpError({ representation: encoding, code, message }) {
|
|
389
|
+
const kind = representation(encoding); const value = { code, message };
|
|
390
|
+
if (kind === "json") return Buffer.from(JSON.stringify(value));
|
|
391
|
+
const type = (await schema()).lookupType("google.rpc.Status");
|
|
392
|
+
return Buffer.from(encodeFromObject(type, value));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export function decodeContentEncoding(bytes, contentEncoding, maxBytes = MAX_UNCOMPRESSED_BODY_BYTES) {
|
|
396
|
+
const input = asBytes(bytes);
|
|
397
|
+
let result;
|
|
398
|
+
if (!contentEncoding || contentEncoding === "identity") result = input;
|
|
399
|
+
else if (contentEncoding.toLowerCase() === "gzip") {
|
|
400
|
+
try { result = gunzipSync(input, { maxOutputLength: maxBytes + 1 }); }
|
|
401
|
+
catch (error) {
|
|
402
|
+
if (error?.code === "ERR_BUFFER_TOO_LARGE") throw Object.assign(new RangeError("uncompressed OTLP body exceeds limit"), { code: "BODY_TOO_LARGE" });
|
|
403
|
+
throw new TypeError(`invalid gzip OTLP body: ${error.message}`);
|
|
404
|
+
}
|
|
405
|
+
} else throw Object.assign(new TypeError(`unsupported content-encoding: ${contentEncoding}`), { code: "UNSUPPORTED_ENCODING" });
|
|
406
|
+
if (result.byteLength > maxBytes) throw Object.assign(new RangeError("uncompressed OTLP body exceeds limit"), { code: "BODY_TOO_LARGE" });
|
|
407
|
+
return result;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
export async function readBoundedBody(body, maxBytes = MAX_UNCOMPRESSED_BODY_BYTES) {
|
|
411
|
+
if (body instanceof Uint8Array || typeof body === "string") {
|
|
412
|
+
const bytes = asBytes(body);
|
|
413
|
+
if (bytes.byteLength > maxBytes) throw Object.assign(new RangeError("compressed OTLP body exceeds limit"), { code: "BODY_TOO_LARGE" });
|
|
414
|
+
return bytes;
|
|
415
|
+
}
|
|
416
|
+
if (!body || typeof body[Symbol.asyncIterator] !== "function") throw new TypeError("OTLP body must be bytes or an async byte stream");
|
|
417
|
+
const chunks = []; let length = 0;
|
|
418
|
+
for await (const chunk of body) {
|
|
419
|
+
const bytes = asBytes(chunk); length += bytes.byteLength;
|
|
420
|
+
if (length > maxBytes) throw Object.assign(new RangeError("compressed OTLP body exceeds limit"), { code: "BODY_TOO_LARGE" });
|
|
421
|
+
chunks.push(bytes);
|
|
422
|
+
}
|
|
423
|
+
return Buffer.concat(chunks, length);
|
|
424
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// Protobuf wire encoding and decoding driven by protobufjs reflection, with no
|
|
2
|
+
// generated code. protobufjs compiles its encoders and decoders with `new
|
|
3
|
+
// Function`, which a Cloudflare Worker refuses; parsing a schema into reflection
|
|
4
|
+
// objects needs none, so this walks the parsed types directly.
|
|
5
|
+
//
|
|
6
|
+
// `decodeToObject` answers what `type.toObject(type.decode(bytes), { longs: String,
|
|
7
|
+
// bytes: String, enums: Number, defaults: false, oneofs: false, json: true })`
|
|
8
|
+
// answers: field names as parsed (camelCase), 64-bit integers as decimal strings,
|
|
9
|
+
// bytes as base64, enums as numbers, only fields present on the wire.
|
|
10
|
+
// `encodeFromObject` writes such an object back (a message's fields in field order).
|
|
11
|
+
import protobuf from 'protobufjs';
|
|
12
|
+
|
|
13
|
+
const VARINT = 0, FIXED64 = 1, BYTES = 2, FIXED32 = 5;
|
|
14
|
+
const PACKABLE = new Set(['double', 'float', 'int32', 'uint32', 'sint32', 'fixed32', 'sfixed32', 'int64', 'uint64', 'sint64', 'fixed64', 'sfixed64', 'bool']);
|
|
15
|
+
const utf8 = new TextDecoder('utf-8', { fatal: true });
|
|
16
|
+
const utf8Encoder = new TextEncoder();
|
|
17
|
+
|
|
18
|
+
class Reader {
|
|
19
|
+
constructor(bytes) { this.buf = bytes; this.pos = 0; this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); }
|
|
20
|
+
varint() {
|
|
21
|
+
let result = 0n, shift = 0n;
|
|
22
|
+
for (;;) {
|
|
23
|
+
if (this.pos >= this.buf.length) throw new RangeError('protobuf varint runs past the end');
|
|
24
|
+
const byte = this.buf[this.pos++];
|
|
25
|
+
result |= BigInt(byte & 0x7f) << shift;
|
|
26
|
+
if (!(byte & 0x80)) return result;
|
|
27
|
+
shift += 7n;
|
|
28
|
+
if (shift > 63n) throw new RangeError('protobuf varint is longer than 10 bytes');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
take(length) {
|
|
32
|
+
if (length < 0 || this.pos + length > this.buf.length) throw new RangeError('protobuf field runs past the end');
|
|
33
|
+
const out = this.buf.subarray(this.pos, this.pos + length); this.pos += length; return out;
|
|
34
|
+
}
|
|
35
|
+
fixed32() { const at = this.pos; this.take(4); return this.view.getUint32(at, true); }
|
|
36
|
+
fixed64() { const at = this.pos; this.take(8); return this.view.getBigUint64(at, true); }
|
|
37
|
+
skip(wire) {
|
|
38
|
+
if (wire === VARINT) this.varint();
|
|
39
|
+
else if (wire === FIXED64) this.take(8);
|
|
40
|
+
else if (wire === BYTES) this.take(Number(this.varint()));
|
|
41
|
+
else if (wire === FIXED32) this.take(4);
|
|
42
|
+
else throw new RangeError(`unsupported protobuf wire type ${wire}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const signed64 = (value) => BigInt.asIntN(64, value);
|
|
47
|
+
const zigzag64 = (value) => (value >> 1n) ^ -(value & 1n);
|
|
48
|
+
|
|
49
|
+
function scalar(type, wire, reader) {
|
|
50
|
+
switch (type) {
|
|
51
|
+
case 'string': return utf8.decode(reader.take(Number(reader.varint())));
|
|
52
|
+
case 'bytes': return Buffer.from(reader.take(Number(reader.varint()))).toString('base64');
|
|
53
|
+
case 'bool': return reader.varint() !== 0n;
|
|
54
|
+
case 'int32': return Number(BigInt.asIntN(32, reader.varint()));
|
|
55
|
+
case 'uint32': return Number(BigInt.asUintN(32, reader.varint()));
|
|
56
|
+
case 'sint32': return Number(zigzag64(reader.varint()));
|
|
57
|
+
case 'int64': return signed64(reader.varint()).toString();
|
|
58
|
+
case 'uint64': return BigInt.asUintN(64, reader.varint()).toString();
|
|
59
|
+
case 'sint64': return zigzag64(reader.varint()).toString();
|
|
60
|
+
case 'fixed32': return reader.fixed32();
|
|
61
|
+
case 'sfixed32': return reader.fixed32() | 0;
|
|
62
|
+
case 'float': { const at = reader.pos; reader.take(4); return reader.view.getFloat32(at, true); }
|
|
63
|
+
case 'fixed64': return reader.fixed64().toString();
|
|
64
|
+
case 'sfixed64': return signed64(reader.fixed64()).toString();
|
|
65
|
+
case 'double': { const at = reader.pos; reader.take(8); return reader.view.getFloat64(at, true); }
|
|
66
|
+
default: throw new TypeError(`unsupported protobuf scalar ${type} (wire ${wire})`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function decodeMessage(type, reader, end) {
|
|
71
|
+
const out = {};
|
|
72
|
+
while (reader.pos < end) {
|
|
73
|
+
const tag = Number(reader.varint()), id = tag >>> 3, wire = tag & 7;
|
|
74
|
+
const field = type.fieldsById[id];
|
|
75
|
+
if (!field) { reader.skip(wire); continue; }
|
|
76
|
+
field.resolve();
|
|
77
|
+
const value = () => {
|
|
78
|
+
if (field.resolvedType instanceof protobuf.Type) { const length = Number(reader.varint()); return decodeMessage(field.resolvedType, reader, reader.pos + length); }
|
|
79
|
+
if (field.resolvedType instanceof protobuf.Enum) return Number(BigInt.asIntN(32, reader.varint()));
|
|
80
|
+
return scalar(field.type, wire, reader);
|
|
81
|
+
};
|
|
82
|
+
if (field.map) throw new TypeError(`protobuf map field ${field.fullName} is not supported`);
|
|
83
|
+
if (field.repeated) {
|
|
84
|
+
const list = out[field.name] ??= [];
|
|
85
|
+
if (wire === BYTES && (PACKABLE.has(field.type) || field.resolvedType instanceof protobuf.Enum)) {
|
|
86
|
+
const stop = reader.pos + Number(reader.varint());
|
|
87
|
+
while (reader.pos < stop) list.push(field.resolvedType instanceof protobuf.Enum ? Number(BigInt.asIntN(32, reader.varint())) : scalar(field.type, wire, reader));
|
|
88
|
+
} else list.push(value());
|
|
89
|
+
} else out[field.name] = value();
|
|
90
|
+
}
|
|
91
|
+
if (reader.pos !== end) throw new RangeError('protobuf message length disagrees with its fields');
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function decodeToObject(type, bytes) {
|
|
96
|
+
const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
|
97
|
+
return decodeMessage(type, new Reader(view), view.length);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
class Writer {
|
|
101
|
+
constructor() { this.parts = []; this.length = 0; }
|
|
102
|
+
push(bytes) { this.parts.push(bytes); this.length += bytes.length; }
|
|
103
|
+
varint(value) {
|
|
104
|
+
let v = BigInt.asUintN(64, BigInt(value)); const out = [];
|
|
105
|
+
do { let byte = Number(v & 0x7fn); v >>= 7n; if (v) byte |= 0x80; out.push(byte); } while (v);
|
|
106
|
+
this.push(Uint8Array.from(out));
|
|
107
|
+
}
|
|
108
|
+
fixed(bytes, set) { const buffer = new Uint8Array(bytes); set(new DataView(buffer.buffer)); this.push(buffer); }
|
|
109
|
+
tag(id, wire) { this.varint((id << 3) | wire); }
|
|
110
|
+
finish() { const out = new Uint8Array(this.length); let at = 0; for (const part of this.parts) { out.set(part, at); at += part.length; } return out; }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const toBig = (value) => (typeof value === 'bigint' ? value : BigInt(typeof value === 'number' ? Math.trunc(value) : String(value)));
|
|
114
|
+
|
|
115
|
+
function writeScalar(writer, type, value) {
|
|
116
|
+
switch (type) {
|
|
117
|
+
case 'string': { const bytes = utf8Encoder.encode(String(value)); writer.varint(bytes.length); writer.push(bytes); return; }
|
|
118
|
+
case 'bytes': { const bytes = typeof value === 'string' ? Buffer.from(value, 'base64') : Buffer.from(value); writer.varint(bytes.length); writer.push(bytes); return; }
|
|
119
|
+
case 'bool': writer.varint(value ? 1 : 0); return;
|
|
120
|
+
case 'int32': case 'uint32': case 'int64': case 'uint64': writer.varint(toBig(value)); return;
|
|
121
|
+
case 'sint32': case 'sint64': { const v = toBig(value); writer.varint((v << 1n) ^ (v >> 63n)); return; }
|
|
122
|
+
case 'fixed32': writer.fixed(4, (view) => view.setUint32(0, Number(value), true)); return;
|
|
123
|
+
case 'sfixed32': writer.fixed(4, (view) => view.setInt32(0, Number(value), true)); return;
|
|
124
|
+
case 'float': writer.fixed(4, (view) => view.setFloat32(0, Number(value), true)); return;
|
|
125
|
+
case 'fixed64': writer.fixed(8, (view) => view.setBigUint64(0, BigInt.asUintN(64, toBig(value)), true)); return;
|
|
126
|
+
case 'sfixed64': writer.fixed(8, (view) => view.setBigInt64(0, BigInt.asIntN(64, toBig(value)), true)); return;
|
|
127
|
+
case 'double': writer.fixed(8, (view) => view.setFloat64(0, Number(value), true)); return;
|
|
128
|
+
default: throw new TypeError(`unsupported protobuf scalar ${type}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const wireOf = (type) => (type === 'string' || type === 'bytes' ? BYTES : ['fixed64', 'sfixed64', 'double'].includes(type) ? FIXED64 : ['fixed32', 'sfixed32', 'float'].includes(type) ? FIXED32 : VARINT);
|
|
133
|
+
|
|
134
|
+
function encodeMessage(type, value, writer) {
|
|
135
|
+
for (const field of type.fieldsArray) {
|
|
136
|
+
const item = value?.[field.name] ?? value?.[field.name.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`)];
|
|
137
|
+
if (item === undefined || item === null) continue;
|
|
138
|
+
field.resolve();
|
|
139
|
+
const one = (entry) => {
|
|
140
|
+
if (field.resolvedType instanceof protobuf.Type) { const inner = new Writer(); encodeMessage(field.resolvedType, entry, inner); const bytes = inner.finish(); writer.tag(field.id, BYTES); writer.varint(bytes.length); writer.push(bytes); return; }
|
|
141
|
+
if (field.resolvedType instanceof protobuf.Enum) { writer.tag(field.id, VARINT); writer.varint(typeof entry === 'string' ? field.resolvedType.values[entry] : entry); return; }
|
|
142
|
+
writer.tag(field.id, wireOf(field.type)); writeScalar(writer, field.type, entry);
|
|
143
|
+
};
|
|
144
|
+
if (field.repeated) { if (!Array.isArray(item)) throw new TypeError(`${field.fullName} must be an array`); for (const entry of item) one(entry); }
|
|
145
|
+
else one(item);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function encodeFromObject(type, value) {
|
|
150
|
+
const writer = new Writer();
|
|
151
|
+
encodeMessage(type, value, writer);
|
|
152
|
+
return writer.finish();
|
|
153
|
+
}
|