@primafuture/telemetry-structured-metadata 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -0
- package/dist/index.cjs +427 -0
- package/dist/index.d.cts +62 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.mjs +423 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# @primafuture/telemetry-structured-metadata
|
|
2
|
+
|
|
3
|
+
Shared structured metadata encoder for PrimaFuture telemetry libraries.
|
|
4
|
+
|
|
5
|
+
It converts nested JavaScript metadata into OpenTelemetry-safe flat attributes
|
|
6
|
+
and an optional JSON representation that can be decoded back later.
|
|
7
|
+
|
|
8
|
+
## Usage
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import {
|
|
12
|
+
encodeStructuredMetadata,
|
|
13
|
+
decodeStructuredMetadata,
|
|
14
|
+
} from '@primafuture/telemetry-structured-metadata';
|
|
15
|
+
|
|
16
|
+
const attributes = encodeStructuredMetadata({
|
|
17
|
+
request: {
|
|
18
|
+
id: 'abc',
|
|
19
|
+
deletedAt: null,
|
|
20
|
+
},
|
|
21
|
+
missing: undefined,
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Default output:
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
app.value.request.id = "abc"
|
|
29
|
+
app.type.request.deletedAt = "null"
|
|
30
|
+
app.type.missing = "undefined"
|
|
31
|
+
app.meta.json = "{\"request\":{\"id\":\"abc\",\"deletedAt\":null},\"missing\":{\"$pfType\":\"undefined\"}}"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`flatten` output is meant for queries. `json` output is meant for
|
|
35
|
+
reconstruction. `raw` output is disabled by default and only passes through
|
|
36
|
+
OpenTelemetry-safe scalar or homogeneous array values.
|
|
37
|
+
|
|
38
|
+
## Development
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install
|
|
42
|
+
npm run typecheck
|
|
43
|
+
npm test
|
|
44
|
+
npm run build
|
|
45
|
+
npm run prepublishOnly
|
|
46
|
+
```
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
var DEFAULT_NAMESPACE = "app";
|
|
5
|
+
var DEFAULT_VALUE_PREFIX = "value";
|
|
6
|
+
var DEFAULT_TYPE_PREFIX = "type";
|
|
7
|
+
var DEFAULT_JSON_KEY = "app.meta.json";
|
|
8
|
+
var DEFAULT_TYPE_KEY = "$pfType";
|
|
9
|
+
var OMIT_PLAIN_JSON_VALUE = /* @__PURE__ */ Symbol("omit-plain-json-value");
|
|
10
|
+
function normalizeStructuredMetadataOptions(options = {}) {
|
|
11
|
+
return {
|
|
12
|
+
flatten: {
|
|
13
|
+
enabled: options.flatten?.enabled ?? true,
|
|
14
|
+
namespace: options.flatten?.namespace ?? DEFAULT_NAMESPACE,
|
|
15
|
+
valuePrefix: options.flatten?.valuePrefix ?? DEFAULT_VALUE_PREFIX,
|
|
16
|
+
typePrefix: options.flatten?.typePrefix ?? DEFAULT_TYPE_PREFIX,
|
|
17
|
+
typeFields: options.flatten?.typeFields ?? "when-needed"
|
|
18
|
+
},
|
|
19
|
+
json: {
|
|
20
|
+
enabled: options.json?.enabled ?? true,
|
|
21
|
+
key: options.json?.key ?? DEFAULT_JSON_KEY,
|
|
22
|
+
valueEncoding: options.json?.valueEncoding ?? "typed",
|
|
23
|
+
typeKey: options.json?.typeKey ?? DEFAULT_TYPE_KEY
|
|
24
|
+
},
|
|
25
|
+
raw: {
|
|
26
|
+
enabled: options.raw?.enabled ?? false
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function encodeStructuredMetadata(data, options = {}) {
|
|
31
|
+
if (Object.keys(data).length === 0) {
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
const normalizedOptions = normalizeStructuredMetadataOptions(options);
|
|
35
|
+
const attributes = {};
|
|
36
|
+
if (normalizedOptions.raw.enabled) {
|
|
37
|
+
addRawAttributes(attributes, data);
|
|
38
|
+
}
|
|
39
|
+
if (normalizedOptions.flatten.enabled) {
|
|
40
|
+
addFlattenedAttributes(attributes, data, normalizedOptions.flatten);
|
|
41
|
+
}
|
|
42
|
+
if (normalizedOptions.json.enabled) {
|
|
43
|
+
const jsonValue = normalizedOptions.json.valueEncoding === "typed" ? encodeTypedJsonValue(data, normalizedOptions.json.typeKey, /* @__PURE__ */ new WeakSet()) : encodePlainJsonValue(data, /* @__PURE__ */ new WeakSet(), false);
|
|
44
|
+
if (jsonValue !== OMIT_PLAIN_JSON_VALUE) {
|
|
45
|
+
attributes[normalizedOptions.json.key] = JSON.stringify(jsonValue);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return attributes;
|
|
49
|
+
}
|
|
50
|
+
function decodeStructuredMetadata(attributes, options = {}) {
|
|
51
|
+
const normalizedOptions = normalizeStructuredMetadataOptions(options);
|
|
52
|
+
const encodedValue = readJsonAttribute(attributes, normalizedOptions.json.key);
|
|
53
|
+
if (typeof encodedValue !== "string") {
|
|
54
|
+
return void 0;
|
|
55
|
+
}
|
|
56
|
+
const parsedValue = JSON.parse(encodedValue);
|
|
57
|
+
if (normalizedOptions.json.valueEncoding === "typed") {
|
|
58
|
+
return decodeTypedJsonValue(parsedValue, normalizedOptions.json.typeKey);
|
|
59
|
+
}
|
|
60
|
+
return parsedValue;
|
|
61
|
+
}
|
|
62
|
+
function readJsonAttribute(attributes, key) {
|
|
63
|
+
if (Object.prototype.hasOwnProperty.call(attributes, key)) {
|
|
64
|
+
return attributes[key];
|
|
65
|
+
}
|
|
66
|
+
const lokiNormalizedKey = key.replaceAll(".", "_");
|
|
67
|
+
return attributes[lokiNormalizedKey];
|
|
68
|
+
}
|
|
69
|
+
function addRawAttributes(attributes, data) {
|
|
70
|
+
for (const [key, value] of Object.entries(data)) {
|
|
71
|
+
const rawValue = getRawAttributeValue(value);
|
|
72
|
+
if (rawValue !== void 0) {
|
|
73
|
+
attributes[key] = rawValue;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function getRawAttributeValue(value) {
|
|
78
|
+
if (isScalarAttributeValue(value)) {
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
if (Array.isArray(value)) {
|
|
82
|
+
return getHomogeneousScalarArray(value);
|
|
83
|
+
}
|
|
84
|
+
return void 0;
|
|
85
|
+
}
|
|
86
|
+
function isScalarAttributeValue(value) {
|
|
87
|
+
return typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value);
|
|
88
|
+
}
|
|
89
|
+
function getHomogeneousScalarArray(value) {
|
|
90
|
+
if (value.length === 0) {
|
|
91
|
+
return void 0;
|
|
92
|
+
}
|
|
93
|
+
if (value.every((item) => typeof item === "string")) {
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
if (value.every((item) => typeof item === "boolean")) {
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
if (value.every((item) => typeof item === "number" && Number.isFinite(item))) {
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
return void 0;
|
|
103
|
+
}
|
|
104
|
+
function addFlattenedAttributes(attributes, data, options) {
|
|
105
|
+
for (const [key, value] of Object.entries(data)) {
|
|
106
|
+
addFlattenedValue(attributes, [key], value, options, /* @__PURE__ */ new WeakSet());
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function addFlattenedValue(attributes, path, value, options, seenObjects) {
|
|
110
|
+
if (value === null) {
|
|
111
|
+
addTypeAttribute(attributes, path, "null", options, true);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (value === void 0) {
|
|
115
|
+
addTypeAttribute(attributes, path, "undefined", options, true);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (typeof value === "string" || typeof value === "boolean") {
|
|
119
|
+
addValueAttribute(attributes, path, value, options);
|
|
120
|
+
addTypeAttribute(attributes, path, typeof value, options, false);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (typeof value === "number") {
|
|
124
|
+
if (Number.isFinite(value)) {
|
|
125
|
+
addValueAttribute(attributes, path, value, options);
|
|
126
|
+
addTypeAttribute(attributes, path, "number", options, false);
|
|
127
|
+
} else {
|
|
128
|
+
addValueAttribute(attributes, path, String(value), options);
|
|
129
|
+
addTypeAttribute(attributes, path, "non-finite-number", options, true);
|
|
130
|
+
}
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (typeof value === "bigint") {
|
|
134
|
+
addValueAttribute(attributes, path, value.toString(), options);
|
|
135
|
+
addTypeAttribute(attributes, path, "bigint", options, true);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (typeof value === "function") {
|
|
139
|
+
addTypeAttribute(attributes, path, "function", options, true);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (typeof value === "symbol") {
|
|
143
|
+
addTypeAttribute(attributes, path, "symbol", options, true);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (value instanceof Date) {
|
|
147
|
+
addValueAttribute(attributes, path, value.toISOString(), options);
|
|
148
|
+
addTypeAttribute(attributes, path, "date", options, true);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (value instanceof Error) {
|
|
152
|
+
addFlattenedError(attributes, path, value, options);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (Array.isArray(value)) {
|
|
156
|
+
addFlattenedArray(attributes, path, value, options, seenObjects);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (isPlainObject(value)) {
|
|
160
|
+
addFlattenedObject(attributes, path, value, options, seenObjects);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
addValueAttribute(attributes, path, String(value), options);
|
|
164
|
+
addTypeAttribute(attributes, path, "unsupported-object", options, true);
|
|
165
|
+
}
|
|
166
|
+
function addFlattenedError(attributes, path, error, options) {
|
|
167
|
+
addTypeAttribute(attributes, path, "error", options, true);
|
|
168
|
+
addValueAttribute(attributes, [...path, "name"], error.name, options);
|
|
169
|
+
addValueAttribute(attributes, [...path, "message"], error.message, options);
|
|
170
|
+
if (error.stack !== void 0) {
|
|
171
|
+
addValueAttribute(attributes, [...path, "stack"], error.stack, options);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function addFlattenedArray(attributes, path, value, options, seenObjects) {
|
|
175
|
+
if (seenObjects.has(value)) {
|
|
176
|
+
addTypeAttribute(attributes, path, "circular-reference", options, true);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (value.length === 0) {
|
|
180
|
+
addTypeAttribute(attributes, path, "empty-array", options, true);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
addTypeAttribute(attributes, path, "array", options, false);
|
|
184
|
+
seenObjects.add(value);
|
|
185
|
+
for (const [index, item] of value.entries()) {
|
|
186
|
+
addFlattenedValue(attributes, [...path, String(index)], item, options, seenObjects);
|
|
187
|
+
}
|
|
188
|
+
seenObjects.delete(value);
|
|
189
|
+
}
|
|
190
|
+
function addFlattenedObject(attributes, path, value, options, seenObjects) {
|
|
191
|
+
if (seenObjects.has(value)) {
|
|
192
|
+
addTypeAttribute(attributes, path, "circular-reference", options, true);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const entries = Object.entries(value);
|
|
196
|
+
if (entries.length === 0) {
|
|
197
|
+
addTypeAttribute(attributes, path, "empty-object", options, true);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
addTypeAttribute(attributes, path, "object", options, false);
|
|
201
|
+
seenObjects.add(value);
|
|
202
|
+
for (const [key, nestedValue] of entries) {
|
|
203
|
+
addFlattenedValue(attributes, [...path, key], nestedValue, options, seenObjects);
|
|
204
|
+
}
|
|
205
|
+
seenObjects.delete(value);
|
|
206
|
+
}
|
|
207
|
+
function addValueAttribute(attributes, path, value, options) {
|
|
208
|
+
attributes[createFlattenedKey(options, options.valuePrefix, path)] = value;
|
|
209
|
+
}
|
|
210
|
+
function addTypeAttribute(attributes, path, typeName, options, needed) {
|
|
211
|
+
if (options.typeFields === "never") {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (options.typeFields === "when-needed" && !needed) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
attributes[createFlattenedKey(options, options.typePrefix, path)] = typeName;
|
|
218
|
+
}
|
|
219
|
+
function createFlattenedKey(options, prefix, path) {
|
|
220
|
+
return [
|
|
221
|
+
options.namespace,
|
|
222
|
+
prefix,
|
|
223
|
+
...path
|
|
224
|
+
].join(".");
|
|
225
|
+
}
|
|
226
|
+
function encodeTypedJsonValue(value, typeKey, seenObjects) {
|
|
227
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
228
|
+
return value;
|
|
229
|
+
}
|
|
230
|
+
if (typeof value === "number") {
|
|
231
|
+
return Number.isFinite(value) ? value : createTypedMarker(typeKey, "non-finite-number", { value: String(value) });
|
|
232
|
+
}
|
|
233
|
+
if (value === void 0) {
|
|
234
|
+
return createTypedMarker(typeKey, "undefined");
|
|
235
|
+
}
|
|
236
|
+
if (typeof value === "bigint") {
|
|
237
|
+
return createTypedMarker(typeKey, "bigint", { value: value.toString() });
|
|
238
|
+
}
|
|
239
|
+
if (typeof value === "function") {
|
|
240
|
+
return createTypedMarker(typeKey, "function", value.name ? { name: value.name } : void 0);
|
|
241
|
+
}
|
|
242
|
+
if (typeof value === "symbol") {
|
|
243
|
+
return createTypedMarker(typeKey, "symbol", { value: String(value) });
|
|
244
|
+
}
|
|
245
|
+
if (value instanceof Date) {
|
|
246
|
+
return createTypedMarker(typeKey, "date", { value: value.toISOString() });
|
|
247
|
+
}
|
|
248
|
+
if (value instanceof Error) {
|
|
249
|
+
return createTypedError(typeKey, value);
|
|
250
|
+
}
|
|
251
|
+
if (Array.isArray(value)) {
|
|
252
|
+
return encodeTypedJsonArray(value, typeKey, seenObjects);
|
|
253
|
+
}
|
|
254
|
+
if (isPlainObject(value)) {
|
|
255
|
+
return encodeTypedJsonObject(value, typeKey, seenObjects);
|
|
256
|
+
}
|
|
257
|
+
return createTypedMarker(typeKey, "unsupported-object", { value: String(value) });
|
|
258
|
+
}
|
|
259
|
+
function encodeTypedJsonArray(value, typeKey, seenObjects) {
|
|
260
|
+
if (seenObjects.has(value)) {
|
|
261
|
+
return createTypedMarker(typeKey, "circular-reference");
|
|
262
|
+
}
|
|
263
|
+
seenObjects.add(value);
|
|
264
|
+
const result = value.map((item) => encodeTypedJsonValue(item, typeKey, seenObjects));
|
|
265
|
+
seenObjects.delete(value);
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
268
|
+
function encodeTypedJsonObject(value, typeKey, seenObjects) {
|
|
269
|
+
if (seenObjects.has(value)) {
|
|
270
|
+
return createTypedMarker(typeKey, "circular-reference");
|
|
271
|
+
}
|
|
272
|
+
seenObjects.add(value);
|
|
273
|
+
const result = {};
|
|
274
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
275
|
+
result[key] = encodeTypedJsonValue(nestedValue, typeKey, seenObjects);
|
|
276
|
+
}
|
|
277
|
+
seenObjects.delete(value);
|
|
278
|
+
if (Object.prototype.hasOwnProperty.call(result, typeKey)) {
|
|
279
|
+
return createTypedMarker(typeKey, "object", {
|
|
280
|
+
value: result
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
return result;
|
|
284
|
+
}
|
|
285
|
+
function createTypedError(typeKey, error) {
|
|
286
|
+
const errorValue = {
|
|
287
|
+
name: error.name,
|
|
288
|
+
message: error.message
|
|
289
|
+
};
|
|
290
|
+
if (error.stack !== void 0) {
|
|
291
|
+
errorValue.stack = error.stack;
|
|
292
|
+
}
|
|
293
|
+
return createTypedMarker(typeKey, "error", errorValue);
|
|
294
|
+
}
|
|
295
|
+
function createTypedMarker(typeKey, typeName, extra) {
|
|
296
|
+
return {
|
|
297
|
+
[typeKey]: typeName,
|
|
298
|
+
...extra ?? {}
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
function encodePlainJsonValue(value, seenObjects, inArray) {
|
|
302
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
303
|
+
return value;
|
|
304
|
+
}
|
|
305
|
+
if (typeof value === "number") {
|
|
306
|
+
return Number.isFinite(value) ? value : null;
|
|
307
|
+
}
|
|
308
|
+
if (typeof value === "bigint") {
|
|
309
|
+
return value.toString();
|
|
310
|
+
}
|
|
311
|
+
if (value === void 0 || typeof value === "function" || typeof value === "symbol") {
|
|
312
|
+
return inArray ? null : OMIT_PLAIN_JSON_VALUE;
|
|
313
|
+
}
|
|
314
|
+
if (value instanceof Date) {
|
|
315
|
+
return value.toISOString();
|
|
316
|
+
}
|
|
317
|
+
if (value instanceof Error) {
|
|
318
|
+
return encodePlainJsonError(value);
|
|
319
|
+
}
|
|
320
|
+
if (Array.isArray(value)) {
|
|
321
|
+
return encodePlainJsonArray(value, seenObjects);
|
|
322
|
+
}
|
|
323
|
+
if (isPlainObject(value)) {
|
|
324
|
+
return encodePlainJsonObject(value, seenObjects);
|
|
325
|
+
}
|
|
326
|
+
return String(value);
|
|
327
|
+
}
|
|
328
|
+
function encodePlainJsonArray(value, seenObjects) {
|
|
329
|
+
if (seenObjects.has(value)) {
|
|
330
|
+
return "[Circular]";
|
|
331
|
+
}
|
|
332
|
+
seenObjects.add(value);
|
|
333
|
+
const result = value.map((item) => {
|
|
334
|
+
const encodedItem = encodePlainJsonValue(item, seenObjects, true);
|
|
335
|
+
return encodedItem === OMIT_PLAIN_JSON_VALUE ? null : encodedItem;
|
|
336
|
+
});
|
|
337
|
+
seenObjects.delete(value);
|
|
338
|
+
return result;
|
|
339
|
+
}
|
|
340
|
+
function encodePlainJsonObject(value, seenObjects) {
|
|
341
|
+
if (seenObjects.has(value)) {
|
|
342
|
+
return "[Circular]";
|
|
343
|
+
}
|
|
344
|
+
seenObjects.add(value);
|
|
345
|
+
const result = {};
|
|
346
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
347
|
+
const encodedValue = encodePlainJsonValue(nestedValue, seenObjects, false);
|
|
348
|
+
if (encodedValue !== OMIT_PLAIN_JSON_VALUE) {
|
|
349
|
+
result[key] = encodedValue;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
seenObjects.delete(value);
|
|
353
|
+
return result;
|
|
354
|
+
}
|
|
355
|
+
function encodePlainJsonError(error) {
|
|
356
|
+
const result = {
|
|
357
|
+
name: error.name,
|
|
358
|
+
message: error.message
|
|
359
|
+
};
|
|
360
|
+
if (error.stack !== void 0) {
|
|
361
|
+
result.stack = error.stack;
|
|
362
|
+
}
|
|
363
|
+
return result;
|
|
364
|
+
}
|
|
365
|
+
function decodeTypedJsonValue(value, typeKey) {
|
|
366
|
+
if (Array.isArray(value)) {
|
|
367
|
+
return value.map((item) => decodeTypedJsonValue(item, typeKey));
|
|
368
|
+
}
|
|
369
|
+
if (!isPlainObject(value)) {
|
|
370
|
+
return value;
|
|
371
|
+
}
|
|
372
|
+
const markerType = value[typeKey];
|
|
373
|
+
if (typeof markerType === "string") {
|
|
374
|
+
return decodeTypedMarker(markerType, value, typeKey);
|
|
375
|
+
}
|
|
376
|
+
const result = {};
|
|
377
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
378
|
+
result[key] = decodeTypedJsonValue(nestedValue, typeKey);
|
|
379
|
+
}
|
|
380
|
+
return result;
|
|
381
|
+
}
|
|
382
|
+
function decodeTypedMarker(typeName, value, typeKey) {
|
|
383
|
+
if (typeName === "undefined") {
|
|
384
|
+
return void 0;
|
|
385
|
+
}
|
|
386
|
+
if (typeName === "bigint" && typeof value.value === "string") {
|
|
387
|
+
return BigInt(value.value);
|
|
388
|
+
}
|
|
389
|
+
if (typeName === "date" && typeof value.value === "string") {
|
|
390
|
+
return new Date(value.value);
|
|
391
|
+
}
|
|
392
|
+
if (typeName === "non-finite-number" && typeof value.value === "string") {
|
|
393
|
+
return Number(value.value);
|
|
394
|
+
}
|
|
395
|
+
if (typeName === "error") {
|
|
396
|
+
return decodeTypedError(value);
|
|
397
|
+
}
|
|
398
|
+
if (typeName === "object" && isPlainObject(value.value)) {
|
|
399
|
+
return decodeTypedJsonValue(value.value, typeKey);
|
|
400
|
+
}
|
|
401
|
+
return {
|
|
402
|
+
...value,
|
|
403
|
+
[typeKey]: typeName
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
function decodeTypedError(value) {
|
|
407
|
+
const message = typeof value.message === "string" ? value.message : "";
|
|
408
|
+
const error = new Error(message);
|
|
409
|
+
if (typeof value.name === "string") {
|
|
410
|
+
error.name = value.name;
|
|
411
|
+
}
|
|
412
|
+
if (typeof value.stack === "string") {
|
|
413
|
+
error.stack = value.stack;
|
|
414
|
+
}
|
|
415
|
+
return error;
|
|
416
|
+
}
|
|
417
|
+
function isPlainObject(value) {
|
|
418
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
const prototype = Object.getPrototypeOf(value);
|
|
422
|
+
return prototype === Object.prototype || prototype === null;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
exports.decodeStructuredMetadata = decodeStructuredMetadata;
|
|
426
|
+
exports.encodeStructuredMetadata = encodeStructuredMetadata;
|
|
427
|
+
exports.normalizeStructuredMetadataOptions = normalizeStructuredMetadataOptions;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
type StructuredMetadataAttributeValue = string | number | boolean | string[] | number[] | boolean[];
|
|
2
|
+
type StructuredMetadataAttributes = Record<string, StructuredMetadataAttributeValue>;
|
|
3
|
+
type StructuredMetadataTypeFieldsMode = 'when-needed' | 'always' | 'never';
|
|
4
|
+
type StructuredMetadataJsonValueEncoding = 'typed' | 'plain-json';
|
|
5
|
+
interface StructuredMetadataFlattenOptions {
|
|
6
|
+
enabled?: boolean | undefined;
|
|
7
|
+
namespace?: string | undefined;
|
|
8
|
+
valuePrefix?: string | undefined;
|
|
9
|
+
typePrefix?: string | undefined;
|
|
10
|
+
typeFields?: StructuredMetadataTypeFieldsMode | undefined;
|
|
11
|
+
}
|
|
12
|
+
interface StructuredMetadataJsonOptions {
|
|
13
|
+
enabled?: boolean | undefined;
|
|
14
|
+
key?: string | undefined;
|
|
15
|
+
valueEncoding?: StructuredMetadataJsonValueEncoding | undefined;
|
|
16
|
+
typeKey?: string | undefined;
|
|
17
|
+
}
|
|
18
|
+
interface StructuredMetadataRawOptions {
|
|
19
|
+
enabled?: boolean | undefined;
|
|
20
|
+
}
|
|
21
|
+
interface StructuredMetadataOptions {
|
|
22
|
+
flatten?: StructuredMetadataFlattenOptions | undefined;
|
|
23
|
+
json?: StructuredMetadataJsonOptions | undefined;
|
|
24
|
+
raw?: StructuredMetadataRawOptions | undefined;
|
|
25
|
+
}
|
|
26
|
+
interface NormalizedFlattenOptions {
|
|
27
|
+
enabled: boolean;
|
|
28
|
+
namespace: string;
|
|
29
|
+
valuePrefix: string;
|
|
30
|
+
typePrefix: string;
|
|
31
|
+
typeFields: StructuredMetadataTypeFieldsMode;
|
|
32
|
+
}
|
|
33
|
+
interface NormalizedJsonOptions {
|
|
34
|
+
enabled: boolean;
|
|
35
|
+
key: string;
|
|
36
|
+
valueEncoding: StructuredMetadataJsonValueEncoding;
|
|
37
|
+
typeKey: string;
|
|
38
|
+
}
|
|
39
|
+
interface NormalizedStructuredMetadataOptions {
|
|
40
|
+
flatten: NormalizedFlattenOptions;
|
|
41
|
+
json: NormalizedJsonOptions;
|
|
42
|
+
raw: {
|
|
43
|
+
enabled: boolean;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Normalizuje volitelny config tak, aby zbytek encoderu nemusel vsude resit
|
|
48
|
+
* chybejici vetve. Defaulty jsou zamerne stejne pro logs, span attributes i events.
|
|
49
|
+
*/
|
|
50
|
+
declare function normalizeStructuredMetadataOptions(options?: StructuredMetadataOptions): NormalizedStructuredMetadataOptions;
|
|
51
|
+
/**
|
|
52
|
+
* Prevede metadata na ploche atributy vhodne pro OTel. Jednotlive vystupy
|
|
53
|
+
* (`raw`, `flatten`, `json`) jsou nezavisle a mohou byt zapnute soucasne.
|
|
54
|
+
*/
|
|
55
|
+
declare function encodeStructuredMetadata(data: Record<string, unknown>, options?: StructuredMetadataOptions): StructuredMetadataAttributes;
|
|
56
|
+
/**
|
|
57
|
+
* Dekoduje metadata z JSON atributu vytvoreneho `encodeStructuredMetadata`.
|
|
58
|
+
* Pri nacitani z Loki umi krom teckoveho klice zkusit i variantu s podtrzitky.
|
|
59
|
+
*/
|
|
60
|
+
declare function decodeStructuredMetadata(attributes: Record<string, unknown>, options?: StructuredMetadataOptions): unknown;
|
|
61
|
+
|
|
62
|
+
export { type StructuredMetadataAttributeValue, type StructuredMetadataAttributes, type StructuredMetadataFlattenOptions, type StructuredMetadataJsonOptions, type StructuredMetadataJsonValueEncoding, type StructuredMetadataOptions, type StructuredMetadataRawOptions, type StructuredMetadataTypeFieldsMode, decodeStructuredMetadata, encodeStructuredMetadata, normalizeStructuredMetadataOptions };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
type StructuredMetadataAttributeValue = string | number | boolean | string[] | number[] | boolean[];
|
|
2
|
+
type StructuredMetadataAttributes = Record<string, StructuredMetadataAttributeValue>;
|
|
3
|
+
type StructuredMetadataTypeFieldsMode = 'when-needed' | 'always' | 'never';
|
|
4
|
+
type StructuredMetadataJsonValueEncoding = 'typed' | 'plain-json';
|
|
5
|
+
interface StructuredMetadataFlattenOptions {
|
|
6
|
+
enabled?: boolean | undefined;
|
|
7
|
+
namespace?: string | undefined;
|
|
8
|
+
valuePrefix?: string | undefined;
|
|
9
|
+
typePrefix?: string | undefined;
|
|
10
|
+
typeFields?: StructuredMetadataTypeFieldsMode | undefined;
|
|
11
|
+
}
|
|
12
|
+
interface StructuredMetadataJsonOptions {
|
|
13
|
+
enabled?: boolean | undefined;
|
|
14
|
+
key?: string | undefined;
|
|
15
|
+
valueEncoding?: StructuredMetadataJsonValueEncoding | undefined;
|
|
16
|
+
typeKey?: string | undefined;
|
|
17
|
+
}
|
|
18
|
+
interface StructuredMetadataRawOptions {
|
|
19
|
+
enabled?: boolean | undefined;
|
|
20
|
+
}
|
|
21
|
+
interface StructuredMetadataOptions {
|
|
22
|
+
flatten?: StructuredMetadataFlattenOptions | undefined;
|
|
23
|
+
json?: StructuredMetadataJsonOptions | undefined;
|
|
24
|
+
raw?: StructuredMetadataRawOptions | undefined;
|
|
25
|
+
}
|
|
26
|
+
interface NormalizedFlattenOptions {
|
|
27
|
+
enabled: boolean;
|
|
28
|
+
namespace: string;
|
|
29
|
+
valuePrefix: string;
|
|
30
|
+
typePrefix: string;
|
|
31
|
+
typeFields: StructuredMetadataTypeFieldsMode;
|
|
32
|
+
}
|
|
33
|
+
interface NormalizedJsonOptions {
|
|
34
|
+
enabled: boolean;
|
|
35
|
+
key: string;
|
|
36
|
+
valueEncoding: StructuredMetadataJsonValueEncoding;
|
|
37
|
+
typeKey: string;
|
|
38
|
+
}
|
|
39
|
+
interface NormalizedStructuredMetadataOptions {
|
|
40
|
+
flatten: NormalizedFlattenOptions;
|
|
41
|
+
json: NormalizedJsonOptions;
|
|
42
|
+
raw: {
|
|
43
|
+
enabled: boolean;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Normalizuje volitelny config tak, aby zbytek encoderu nemusel vsude resit
|
|
48
|
+
* chybejici vetve. Defaulty jsou zamerne stejne pro logs, span attributes i events.
|
|
49
|
+
*/
|
|
50
|
+
declare function normalizeStructuredMetadataOptions(options?: StructuredMetadataOptions): NormalizedStructuredMetadataOptions;
|
|
51
|
+
/**
|
|
52
|
+
* Prevede metadata na ploche atributy vhodne pro OTel. Jednotlive vystupy
|
|
53
|
+
* (`raw`, `flatten`, `json`) jsou nezavisle a mohou byt zapnute soucasne.
|
|
54
|
+
*/
|
|
55
|
+
declare function encodeStructuredMetadata(data: Record<string, unknown>, options?: StructuredMetadataOptions): StructuredMetadataAttributes;
|
|
56
|
+
/**
|
|
57
|
+
* Dekoduje metadata z JSON atributu vytvoreneho `encodeStructuredMetadata`.
|
|
58
|
+
* Pri nacitani z Loki umi krom teckoveho klice zkusit i variantu s podtrzitky.
|
|
59
|
+
*/
|
|
60
|
+
declare function decodeStructuredMetadata(attributes: Record<string, unknown>, options?: StructuredMetadataOptions): unknown;
|
|
61
|
+
|
|
62
|
+
export { type StructuredMetadataAttributeValue, type StructuredMetadataAttributes, type StructuredMetadataFlattenOptions, type StructuredMetadataJsonOptions, type StructuredMetadataJsonValueEncoding, type StructuredMetadataOptions, type StructuredMetadataRawOptions, type StructuredMetadataTypeFieldsMode, decodeStructuredMetadata, encodeStructuredMetadata, normalizeStructuredMetadataOptions };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var DEFAULT_NAMESPACE = "app";
|
|
3
|
+
var DEFAULT_VALUE_PREFIX = "value";
|
|
4
|
+
var DEFAULT_TYPE_PREFIX = "type";
|
|
5
|
+
var DEFAULT_JSON_KEY = "app.meta.json";
|
|
6
|
+
var DEFAULT_TYPE_KEY = "$pfType";
|
|
7
|
+
var OMIT_PLAIN_JSON_VALUE = /* @__PURE__ */ Symbol("omit-plain-json-value");
|
|
8
|
+
function normalizeStructuredMetadataOptions(options = {}) {
|
|
9
|
+
return {
|
|
10
|
+
flatten: {
|
|
11
|
+
enabled: options.flatten?.enabled ?? true,
|
|
12
|
+
namespace: options.flatten?.namespace ?? DEFAULT_NAMESPACE,
|
|
13
|
+
valuePrefix: options.flatten?.valuePrefix ?? DEFAULT_VALUE_PREFIX,
|
|
14
|
+
typePrefix: options.flatten?.typePrefix ?? DEFAULT_TYPE_PREFIX,
|
|
15
|
+
typeFields: options.flatten?.typeFields ?? "when-needed"
|
|
16
|
+
},
|
|
17
|
+
json: {
|
|
18
|
+
enabled: options.json?.enabled ?? true,
|
|
19
|
+
key: options.json?.key ?? DEFAULT_JSON_KEY,
|
|
20
|
+
valueEncoding: options.json?.valueEncoding ?? "typed",
|
|
21
|
+
typeKey: options.json?.typeKey ?? DEFAULT_TYPE_KEY
|
|
22
|
+
},
|
|
23
|
+
raw: {
|
|
24
|
+
enabled: options.raw?.enabled ?? false
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function encodeStructuredMetadata(data, options = {}) {
|
|
29
|
+
if (Object.keys(data).length === 0) {
|
|
30
|
+
return {};
|
|
31
|
+
}
|
|
32
|
+
const normalizedOptions = normalizeStructuredMetadataOptions(options);
|
|
33
|
+
const attributes = {};
|
|
34
|
+
if (normalizedOptions.raw.enabled) {
|
|
35
|
+
addRawAttributes(attributes, data);
|
|
36
|
+
}
|
|
37
|
+
if (normalizedOptions.flatten.enabled) {
|
|
38
|
+
addFlattenedAttributes(attributes, data, normalizedOptions.flatten);
|
|
39
|
+
}
|
|
40
|
+
if (normalizedOptions.json.enabled) {
|
|
41
|
+
const jsonValue = normalizedOptions.json.valueEncoding === "typed" ? encodeTypedJsonValue(data, normalizedOptions.json.typeKey, /* @__PURE__ */ new WeakSet()) : encodePlainJsonValue(data, /* @__PURE__ */ new WeakSet(), false);
|
|
42
|
+
if (jsonValue !== OMIT_PLAIN_JSON_VALUE) {
|
|
43
|
+
attributes[normalizedOptions.json.key] = JSON.stringify(jsonValue);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return attributes;
|
|
47
|
+
}
|
|
48
|
+
function decodeStructuredMetadata(attributes, options = {}) {
|
|
49
|
+
const normalizedOptions = normalizeStructuredMetadataOptions(options);
|
|
50
|
+
const encodedValue = readJsonAttribute(attributes, normalizedOptions.json.key);
|
|
51
|
+
if (typeof encodedValue !== "string") {
|
|
52
|
+
return void 0;
|
|
53
|
+
}
|
|
54
|
+
const parsedValue = JSON.parse(encodedValue);
|
|
55
|
+
if (normalizedOptions.json.valueEncoding === "typed") {
|
|
56
|
+
return decodeTypedJsonValue(parsedValue, normalizedOptions.json.typeKey);
|
|
57
|
+
}
|
|
58
|
+
return parsedValue;
|
|
59
|
+
}
|
|
60
|
+
function readJsonAttribute(attributes, key) {
|
|
61
|
+
if (Object.prototype.hasOwnProperty.call(attributes, key)) {
|
|
62
|
+
return attributes[key];
|
|
63
|
+
}
|
|
64
|
+
const lokiNormalizedKey = key.replaceAll(".", "_");
|
|
65
|
+
return attributes[lokiNormalizedKey];
|
|
66
|
+
}
|
|
67
|
+
function addRawAttributes(attributes, data) {
|
|
68
|
+
for (const [key, value] of Object.entries(data)) {
|
|
69
|
+
const rawValue = getRawAttributeValue(value);
|
|
70
|
+
if (rawValue !== void 0) {
|
|
71
|
+
attributes[key] = rawValue;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function getRawAttributeValue(value) {
|
|
76
|
+
if (isScalarAttributeValue(value)) {
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
if (Array.isArray(value)) {
|
|
80
|
+
return getHomogeneousScalarArray(value);
|
|
81
|
+
}
|
|
82
|
+
return void 0;
|
|
83
|
+
}
|
|
84
|
+
function isScalarAttributeValue(value) {
|
|
85
|
+
return typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value);
|
|
86
|
+
}
|
|
87
|
+
function getHomogeneousScalarArray(value) {
|
|
88
|
+
if (value.length === 0) {
|
|
89
|
+
return void 0;
|
|
90
|
+
}
|
|
91
|
+
if (value.every((item) => typeof item === "string")) {
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
94
|
+
if (value.every((item) => typeof item === "boolean")) {
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
if (value.every((item) => typeof item === "number" && Number.isFinite(item))) {
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
return void 0;
|
|
101
|
+
}
|
|
102
|
+
function addFlattenedAttributes(attributes, data, options) {
|
|
103
|
+
for (const [key, value] of Object.entries(data)) {
|
|
104
|
+
addFlattenedValue(attributes, [key], value, options, /* @__PURE__ */ new WeakSet());
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function addFlattenedValue(attributes, path, value, options, seenObjects) {
|
|
108
|
+
if (value === null) {
|
|
109
|
+
addTypeAttribute(attributes, path, "null", options, true);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (value === void 0) {
|
|
113
|
+
addTypeAttribute(attributes, path, "undefined", options, true);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (typeof value === "string" || typeof value === "boolean") {
|
|
117
|
+
addValueAttribute(attributes, path, value, options);
|
|
118
|
+
addTypeAttribute(attributes, path, typeof value, options, false);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (typeof value === "number") {
|
|
122
|
+
if (Number.isFinite(value)) {
|
|
123
|
+
addValueAttribute(attributes, path, value, options);
|
|
124
|
+
addTypeAttribute(attributes, path, "number", options, false);
|
|
125
|
+
} else {
|
|
126
|
+
addValueAttribute(attributes, path, String(value), options);
|
|
127
|
+
addTypeAttribute(attributes, path, "non-finite-number", options, true);
|
|
128
|
+
}
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (typeof value === "bigint") {
|
|
132
|
+
addValueAttribute(attributes, path, value.toString(), options);
|
|
133
|
+
addTypeAttribute(attributes, path, "bigint", options, true);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (typeof value === "function") {
|
|
137
|
+
addTypeAttribute(attributes, path, "function", options, true);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (typeof value === "symbol") {
|
|
141
|
+
addTypeAttribute(attributes, path, "symbol", options, true);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (value instanceof Date) {
|
|
145
|
+
addValueAttribute(attributes, path, value.toISOString(), options);
|
|
146
|
+
addTypeAttribute(attributes, path, "date", options, true);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (value instanceof Error) {
|
|
150
|
+
addFlattenedError(attributes, path, value, options);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (Array.isArray(value)) {
|
|
154
|
+
addFlattenedArray(attributes, path, value, options, seenObjects);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (isPlainObject(value)) {
|
|
158
|
+
addFlattenedObject(attributes, path, value, options, seenObjects);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
addValueAttribute(attributes, path, String(value), options);
|
|
162
|
+
addTypeAttribute(attributes, path, "unsupported-object", options, true);
|
|
163
|
+
}
|
|
164
|
+
function addFlattenedError(attributes, path, error, options) {
|
|
165
|
+
addTypeAttribute(attributes, path, "error", options, true);
|
|
166
|
+
addValueAttribute(attributes, [...path, "name"], error.name, options);
|
|
167
|
+
addValueAttribute(attributes, [...path, "message"], error.message, options);
|
|
168
|
+
if (error.stack !== void 0) {
|
|
169
|
+
addValueAttribute(attributes, [...path, "stack"], error.stack, options);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function addFlattenedArray(attributes, path, value, options, seenObjects) {
|
|
173
|
+
if (seenObjects.has(value)) {
|
|
174
|
+
addTypeAttribute(attributes, path, "circular-reference", options, true);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (value.length === 0) {
|
|
178
|
+
addTypeAttribute(attributes, path, "empty-array", options, true);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
addTypeAttribute(attributes, path, "array", options, false);
|
|
182
|
+
seenObjects.add(value);
|
|
183
|
+
for (const [index, item] of value.entries()) {
|
|
184
|
+
addFlattenedValue(attributes, [...path, String(index)], item, options, seenObjects);
|
|
185
|
+
}
|
|
186
|
+
seenObjects.delete(value);
|
|
187
|
+
}
|
|
188
|
+
function addFlattenedObject(attributes, path, value, options, seenObjects) {
|
|
189
|
+
if (seenObjects.has(value)) {
|
|
190
|
+
addTypeAttribute(attributes, path, "circular-reference", options, true);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const entries = Object.entries(value);
|
|
194
|
+
if (entries.length === 0) {
|
|
195
|
+
addTypeAttribute(attributes, path, "empty-object", options, true);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
addTypeAttribute(attributes, path, "object", options, false);
|
|
199
|
+
seenObjects.add(value);
|
|
200
|
+
for (const [key, nestedValue] of entries) {
|
|
201
|
+
addFlattenedValue(attributes, [...path, key], nestedValue, options, seenObjects);
|
|
202
|
+
}
|
|
203
|
+
seenObjects.delete(value);
|
|
204
|
+
}
|
|
205
|
+
function addValueAttribute(attributes, path, value, options) {
|
|
206
|
+
attributes[createFlattenedKey(options, options.valuePrefix, path)] = value;
|
|
207
|
+
}
|
|
208
|
+
function addTypeAttribute(attributes, path, typeName, options, needed) {
|
|
209
|
+
if (options.typeFields === "never") {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (options.typeFields === "when-needed" && !needed) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
attributes[createFlattenedKey(options, options.typePrefix, path)] = typeName;
|
|
216
|
+
}
|
|
217
|
+
function createFlattenedKey(options, prefix, path) {
|
|
218
|
+
return [
|
|
219
|
+
options.namespace,
|
|
220
|
+
prefix,
|
|
221
|
+
...path
|
|
222
|
+
].join(".");
|
|
223
|
+
}
|
|
224
|
+
function encodeTypedJsonValue(value, typeKey, seenObjects) {
|
|
225
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
226
|
+
return value;
|
|
227
|
+
}
|
|
228
|
+
if (typeof value === "number") {
|
|
229
|
+
return Number.isFinite(value) ? value : createTypedMarker(typeKey, "non-finite-number", { value: String(value) });
|
|
230
|
+
}
|
|
231
|
+
if (value === void 0) {
|
|
232
|
+
return createTypedMarker(typeKey, "undefined");
|
|
233
|
+
}
|
|
234
|
+
if (typeof value === "bigint") {
|
|
235
|
+
return createTypedMarker(typeKey, "bigint", { value: value.toString() });
|
|
236
|
+
}
|
|
237
|
+
if (typeof value === "function") {
|
|
238
|
+
return createTypedMarker(typeKey, "function", value.name ? { name: value.name } : void 0);
|
|
239
|
+
}
|
|
240
|
+
if (typeof value === "symbol") {
|
|
241
|
+
return createTypedMarker(typeKey, "symbol", { value: String(value) });
|
|
242
|
+
}
|
|
243
|
+
if (value instanceof Date) {
|
|
244
|
+
return createTypedMarker(typeKey, "date", { value: value.toISOString() });
|
|
245
|
+
}
|
|
246
|
+
if (value instanceof Error) {
|
|
247
|
+
return createTypedError(typeKey, value);
|
|
248
|
+
}
|
|
249
|
+
if (Array.isArray(value)) {
|
|
250
|
+
return encodeTypedJsonArray(value, typeKey, seenObjects);
|
|
251
|
+
}
|
|
252
|
+
if (isPlainObject(value)) {
|
|
253
|
+
return encodeTypedJsonObject(value, typeKey, seenObjects);
|
|
254
|
+
}
|
|
255
|
+
return createTypedMarker(typeKey, "unsupported-object", { value: String(value) });
|
|
256
|
+
}
|
|
257
|
+
function encodeTypedJsonArray(value, typeKey, seenObjects) {
|
|
258
|
+
if (seenObjects.has(value)) {
|
|
259
|
+
return createTypedMarker(typeKey, "circular-reference");
|
|
260
|
+
}
|
|
261
|
+
seenObjects.add(value);
|
|
262
|
+
const result = value.map((item) => encodeTypedJsonValue(item, typeKey, seenObjects));
|
|
263
|
+
seenObjects.delete(value);
|
|
264
|
+
return result;
|
|
265
|
+
}
|
|
266
|
+
function encodeTypedJsonObject(value, typeKey, seenObjects) {
|
|
267
|
+
if (seenObjects.has(value)) {
|
|
268
|
+
return createTypedMarker(typeKey, "circular-reference");
|
|
269
|
+
}
|
|
270
|
+
seenObjects.add(value);
|
|
271
|
+
const result = {};
|
|
272
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
273
|
+
result[key] = encodeTypedJsonValue(nestedValue, typeKey, seenObjects);
|
|
274
|
+
}
|
|
275
|
+
seenObjects.delete(value);
|
|
276
|
+
if (Object.prototype.hasOwnProperty.call(result, typeKey)) {
|
|
277
|
+
return createTypedMarker(typeKey, "object", {
|
|
278
|
+
value: result
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
return result;
|
|
282
|
+
}
|
|
283
|
+
function createTypedError(typeKey, error) {
|
|
284
|
+
const errorValue = {
|
|
285
|
+
name: error.name,
|
|
286
|
+
message: error.message
|
|
287
|
+
};
|
|
288
|
+
if (error.stack !== void 0) {
|
|
289
|
+
errorValue.stack = error.stack;
|
|
290
|
+
}
|
|
291
|
+
return createTypedMarker(typeKey, "error", errorValue);
|
|
292
|
+
}
|
|
293
|
+
function createTypedMarker(typeKey, typeName, extra) {
|
|
294
|
+
return {
|
|
295
|
+
[typeKey]: typeName,
|
|
296
|
+
...extra ?? {}
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function encodePlainJsonValue(value, seenObjects, inArray) {
|
|
300
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
301
|
+
return value;
|
|
302
|
+
}
|
|
303
|
+
if (typeof value === "number") {
|
|
304
|
+
return Number.isFinite(value) ? value : null;
|
|
305
|
+
}
|
|
306
|
+
if (typeof value === "bigint") {
|
|
307
|
+
return value.toString();
|
|
308
|
+
}
|
|
309
|
+
if (value === void 0 || typeof value === "function" || typeof value === "symbol") {
|
|
310
|
+
return inArray ? null : OMIT_PLAIN_JSON_VALUE;
|
|
311
|
+
}
|
|
312
|
+
if (value instanceof Date) {
|
|
313
|
+
return value.toISOString();
|
|
314
|
+
}
|
|
315
|
+
if (value instanceof Error) {
|
|
316
|
+
return encodePlainJsonError(value);
|
|
317
|
+
}
|
|
318
|
+
if (Array.isArray(value)) {
|
|
319
|
+
return encodePlainJsonArray(value, seenObjects);
|
|
320
|
+
}
|
|
321
|
+
if (isPlainObject(value)) {
|
|
322
|
+
return encodePlainJsonObject(value, seenObjects);
|
|
323
|
+
}
|
|
324
|
+
return String(value);
|
|
325
|
+
}
|
|
326
|
+
function encodePlainJsonArray(value, seenObjects) {
|
|
327
|
+
if (seenObjects.has(value)) {
|
|
328
|
+
return "[Circular]";
|
|
329
|
+
}
|
|
330
|
+
seenObjects.add(value);
|
|
331
|
+
const result = value.map((item) => {
|
|
332
|
+
const encodedItem = encodePlainJsonValue(item, seenObjects, true);
|
|
333
|
+
return encodedItem === OMIT_PLAIN_JSON_VALUE ? null : encodedItem;
|
|
334
|
+
});
|
|
335
|
+
seenObjects.delete(value);
|
|
336
|
+
return result;
|
|
337
|
+
}
|
|
338
|
+
function encodePlainJsonObject(value, seenObjects) {
|
|
339
|
+
if (seenObjects.has(value)) {
|
|
340
|
+
return "[Circular]";
|
|
341
|
+
}
|
|
342
|
+
seenObjects.add(value);
|
|
343
|
+
const result = {};
|
|
344
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
345
|
+
const encodedValue = encodePlainJsonValue(nestedValue, seenObjects, false);
|
|
346
|
+
if (encodedValue !== OMIT_PLAIN_JSON_VALUE) {
|
|
347
|
+
result[key] = encodedValue;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
seenObjects.delete(value);
|
|
351
|
+
return result;
|
|
352
|
+
}
|
|
353
|
+
function encodePlainJsonError(error) {
|
|
354
|
+
const result = {
|
|
355
|
+
name: error.name,
|
|
356
|
+
message: error.message
|
|
357
|
+
};
|
|
358
|
+
if (error.stack !== void 0) {
|
|
359
|
+
result.stack = error.stack;
|
|
360
|
+
}
|
|
361
|
+
return result;
|
|
362
|
+
}
|
|
363
|
+
function decodeTypedJsonValue(value, typeKey) {
|
|
364
|
+
if (Array.isArray(value)) {
|
|
365
|
+
return value.map((item) => decodeTypedJsonValue(item, typeKey));
|
|
366
|
+
}
|
|
367
|
+
if (!isPlainObject(value)) {
|
|
368
|
+
return value;
|
|
369
|
+
}
|
|
370
|
+
const markerType = value[typeKey];
|
|
371
|
+
if (typeof markerType === "string") {
|
|
372
|
+
return decodeTypedMarker(markerType, value, typeKey);
|
|
373
|
+
}
|
|
374
|
+
const result = {};
|
|
375
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
376
|
+
result[key] = decodeTypedJsonValue(nestedValue, typeKey);
|
|
377
|
+
}
|
|
378
|
+
return result;
|
|
379
|
+
}
|
|
380
|
+
function decodeTypedMarker(typeName, value, typeKey) {
|
|
381
|
+
if (typeName === "undefined") {
|
|
382
|
+
return void 0;
|
|
383
|
+
}
|
|
384
|
+
if (typeName === "bigint" && typeof value.value === "string") {
|
|
385
|
+
return BigInt(value.value);
|
|
386
|
+
}
|
|
387
|
+
if (typeName === "date" && typeof value.value === "string") {
|
|
388
|
+
return new Date(value.value);
|
|
389
|
+
}
|
|
390
|
+
if (typeName === "non-finite-number" && typeof value.value === "string") {
|
|
391
|
+
return Number(value.value);
|
|
392
|
+
}
|
|
393
|
+
if (typeName === "error") {
|
|
394
|
+
return decodeTypedError(value);
|
|
395
|
+
}
|
|
396
|
+
if (typeName === "object" && isPlainObject(value.value)) {
|
|
397
|
+
return decodeTypedJsonValue(value.value, typeKey);
|
|
398
|
+
}
|
|
399
|
+
return {
|
|
400
|
+
...value,
|
|
401
|
+
[typeKey]: typeName
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
function decodeTypedError(value) {
|
|
405
|
+
const message = typeof value.message === "string" ? value.message : "";
|
|
406
|
+
const error = new Error(message);
|
|
407
|
+
if (typeof value.name === "string") {
|
|
408
|
+
error.name = value.name;
|
|
409
|
+
}
|
|
410
|
+
if (typeof value.stack === "string") {
|
|
411
|
+
error.stack = value.stack;
|
|
412
|
+
}
|
|
413
|
+
return error;
|
|
414
|
+
}
|
|
415
|
+
function isPlainObject(value) {
|
|
416
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
const prototype = Object.getPrototypeOf(value);
|
|
420
|
+
return prototype === Object.prototype || prototype === null;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export { decodeStructuredMetadata, encodeStructuredMetadata, normalizeStructuredMetadataOptions };
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@primafuture/telemetry-structured-metadata",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Shared structured metadata encoder for PrimaFuture telemetry libraries.",
|
|
6
|
+
"author": "PrimaFuture.cz s.r.o. <dev@primafuture.cz>",
|
|
7
|
+
"license": "ISC",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
12
|
+
"main": "./dist/index.cjs",
|
|
13
|
+
"module": "./dist/index.mjs",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"import": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/index.mjs"
|
|
20
|
+
},
|
|
21
|
+
"require": {
|
|
22
|
+
"types": "./dist/index.d.cts",
|
|
23
|
+
"default": "./dist/index.cjs"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"telemetry",
|
|
33
|
+
"opentelemetry",
|
|
34
|
+
"metadata",
|
|
35
|
+
"structured-logs"
|
|
36
|
+
],
|
|
37
|
+
"files": [
|
|
38
|
+
"dist"
|
|
39
|
+
],
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsup",
|
|
42
|
+
"test": "node --import tsx --test __tests__/*.test.ts",
|
|
43
|
+
"test:package": "node ./__tests__/package-smoke.mjs",
|
|
44
|
+
"test:audit": "npm audit --omit=dev",
|
|
45
|
+
"test:pack": "npm pack --dry-run",
|
|
46
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.dev.json --noEmit",
|
|
47
|
+
"prepublishOnly": "npm run typecheck && npm test && npm run build && npm run test:package && npm run test:audit && npm run test:pack"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^18.19.0",
|
|
51
|
+
"tsup": "^8.5.0",
|
|
52
|
+
"tsx": "^4.20.6",
|
|
53
|
+
"typescript": "^5.9.2"
|
|
54
|
+
}
|
|
55
|
+
}
|