autotel
Version:
Write Once, Observe Anywhere
337 lines (335 loc) • 9.31 kB
JavaScript
import { propagation, context } from '@opentelemetry/api';
// src/business-baggage.ts
var DEFAULT_MAX_KEY_LENGTH = 64;
var DEFAULT_MAX_VALUE_LENGTH = 256;
var DEFAULT_MAX_TOTAL_SIZE = 8192;
var PII_PATTERNS = [
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/,
// Email
/\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/,
// Phone (US)
/\b\d{3}[-]?\d{2}[-]?\d{4}\b/,
// SSN
/\b\d{16}\b/
// Credit card (basic)
];
var HIGH_CARDINALITY_PATTERNS = [
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
// UUID
/^\d{13,}$/,
// Timestamps
/^[A-Za-z0-9+/]{20,}={0,2}$/
// Base64
];
function createSafeBaggageSchema(schema, options = {}) {
const {
maxKeyLength = DEFAULT_MAX_KEY_LENGTH,
maxValueLength = DEFAULT_MAX_VALUE_LENGTH,
maxTotalSize = DEFAULT_MAX_TOTAL_SIZE,
prefix = "",
hashHighCardinality = false,
redactPII = false,
allowedKeys,
onError
} = options;
const schemaKeys = new Set(Object.keys(schema));
if (allowedKeys) {
for (const key of schemaKeys) {
if (!allowedKeys.includes(key)) {
throw new Error(`Key "${key}" not in allowedKeys whitelist`);
}
}
}
const prefixKey = (key) => prefix ? `${prefix}.${key}` : key;
const hashValue = (value) => {
let hash = 2166136261;
for (let i = 0; i < value.length; i++) {
hash ^= value.codePointAt(i) ?? 0;
hash = hash * 16777619 >>> 0;
}
return `h_${hash.toString(16)}`;
};
const containsPII = (value) => {
return PII_PATTERNS.some((pattern) => pattern.test(value));
};
const isHighCardinality = (value) => {
return HIGH_CARDINALITY_PATTERNS.some((pattern) => pattern.test(value));
};
const validateAndTransform = (key, value, fieldDef) => {
const fullKey = prefixKey(key);
if (fullKey.length > maxKeyLength) {
onError?.({
type: "key_length",
key,
message: `Key "${key}" exceeds max length ${maxKeyLength}`
});
return null;
}
if (value === void 0 || value === null) {
if (fieldDef.required) {
onError?.({
type: "validation",
key,
message: `Required field "${key}" is missing`
});
return null;
}
if (fieldDef.defaultValue === void 0) {
return null;
} else {
value = fieldDef.defaultValue;
}
}
let stringValue;
switch (fieldDef.type) {
case "string": {
if (typeof value !== "string") {
onError?.({
type: "validation",
key,
message: `Field "${key}" expected string, got ${typeof value}`,
value
});
return null;
}
stringValue = value;
break;
}
case "number": {
if (typeof value !== "number" || Number.isNaN(value)) {
onError?.({
type: "validation",
key,
message: `Field "${key}" expected number, got ${typeof value}`,
value
});
return null;
}
stringValue = String(value);
break;
}
case "boolean": {
if (typeof value !== "boolean") {
onError?.({
type: "validation",
key,
message: `Field "${key}" expected boolean, got ${typeof value}`,
value
});
return null;
}
stringValue = String(value);
break;
}
case "enum": {
if (!fieldDef.values?.includes(String(value))) {
onError?.({
type: "validation",
key,
message: `Field "${key}" value "${value}" not in allowed values: ${fieldDef.values?.join(", ")}`,
value
});
return null;
}
stringValue = String(value);
break;
}
default: {
stringValue = String(value);
}
}
if (fieldDef.validate && !fieldDef.validate(value)) {
onError?.({
type: "validation",
key,
message: `Field "${key}" failed custom validation`,
value
});
return null;
}
if (redactPII && containsPII(stringValue)) {
onError?.({
type: "pii",
key,
message: `Field "${key}" contains PII pattern`,
value: "[REDACTED]"
});
stringValue = hashValue(stringValue);
}
if (fieldDef.hash || hashHighCardinality && isHighCardinality(stringValue)) {
stringValue = hashValue(stringValue);
}
const maxLen = fieldDef.maxLength ?? maxValueLength;
if (stringValue.length > maxLen) {
onError?.({
type: "value_length",
key,
message: `Field "${key}" value exceeds max length ${maxLen}`,
value: stringValue
});
stringValue = stringValue.slice(0, maxLen);
}
return stringValue;
};
const parseValue = (key, stringValue, fieldDef) => {
switch (fieldDef.type) {
case "number": {
return Number.parseFloat(stringValue);
}
case "boolean": {
return stringValue === "true";
}
default: {
return stringValue;
}
}
};
return {
get() {
const baggage = propagation.getBaggage(context.active());
if (!baggage) {
return {};
}
const result = {};
for (const [key, fieldDef] of Object.entries(schema)) {
const fullKey = prefixKey(key);
const entry = baggage.getEntry(fullKey);
if (entry) {
result[key] = parseValue(key, entry.value, fieldDef);
} else if (fieldDef.defaultValue !== void 0) {
result[key] = fieldDef.defaultValue;
}
}
return result;
},
set(ctx, values) {
let baggage = propagation.getBaggage(context.active()) ?? propagation.createBaggage();
let totalSize = 0;
for (const [key, entry] of baggage.getAllEntries()) {
totalSize += key.length + entry.value.length;
}
for (const [key, value] of Object.entries(values)) {
const fieldDef = schema[key];
if (!fieldDef) continue;
const fullKey = prefixKey(key);
const stringValue = validateAndTransform(key, value, fieldDef);
if (stringValue !== null) {
const entrySize = fullKey.length + stringValue.length;
if (totalSize + entrySize > maxTotalSize) {
onError?.({
type: "size",
key,
message: `Adding "${key}" would exceed max baggage size ${maxTotalSize}`,
value
});
continue;
}
baggage = baggage.setEntry(fullKey, { value: stringValue });
totalSize += entrySize;
}
}
const newContext = propagation.setBaggage(context.active(), baggage);
propagation.setBaggage(newContext, baggage);
},
getValue(key) {
const baggage = propagation.getBaggage(context.active());
if (!baggage) return void 0;
const fullKey = prefixKey(String(key));
const entry = baggage.getEntry(fullKey);
const fieldDef = schema[String(key)];
if (!entry) {
return fieldDef?.defaultValue;
}
if (!fieldDef) {
return void 0;
}
return parseValue(
String(key),
entry.value,
fieldDef
);
},
setValue(key, value, ctx) {
this.set(ctx, { [key]: value });
},
clear() {
let baggage = propagation.getBaggage(context.active());
if (!baggage) return;
for (const key of Object.keys(schema)) {
const fullKey = prefixKey(key);
baggage = baggage.removeEntry(fullKey);
}
propagation.setBaggage(context.active(), baggage);
},
toHeaders() {
const headers = {};
propagation.inject(context.active(), headers);
return headers;
},
fromHeaders(headers, ctx) {
const extractedContext = propagation.extract(context.active(), headers);
const baggage = propagation.getBaggage(extractedContext);
if (baggage) {
const values = {};
for (const [key, fieldDef] of Object.entries(schema)) {
const fullKey = prefixKey(key);
const entry = baggage.getEntry(fullKey);
if (entry) {
values[key] = parseValue(key, entry.value, fieldDef);
}
}
this.set(ctx, values);
}
}
};
}
var BusinessBaggage = createSafeBaggageSchema(
{
tenantId: {
type: "string",
maxLength: 64
},
userId: {
type: "string",
hash: true,
// Auto-hash for privacy
maxLength: 64
},
correlationId: {
type: "string",
maxLength: 128
},
workflowId: {
type: "string",
maxLength: 128
},
priority: {
type: "enum",
values: ["low", "normal", "high", "critical"],
defaultValue: "normal"
},
region: {
type: "string",
maxLength: 32
},
channel: {
type: "enum",
values: [
"web",
"mobile",
"api",
"internal",
"webhook",
"scheduled"
]
}
},
{
prefix: "biz",
redactPII: true,
hashHighCardinality: true
}
);
export { BusinessBaggage, createSafeBaggageSchema };
//# sourceMappingURL=chunk-4IFSYQVX.js.map
//# sourceMappingURL=chunk-4IFSYQVX.js.map