@zintrust/trace 0.4.92 → 0.4.94

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.
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@zintrust/trace",
3
- "version": "0.4.92",
4
- "buildDate": "2026-04-10T08:41:38.521Z",
3
+ "version": "0.4.94",
4
+ "buildDate": "2026-04-11T09:24:47.024Z",
5
5
  "buildEnvironment": {
6
6
  "node": "v20.20.2",
7
7
  "platform": "linux",
8
8
  "arch": "x64"
9
9
  },
10
10
  "git": {
11
- "commit": "d195a996",
11
+ "commit": "785d0351",
12
12
  "branch": "master"
13
13
  },
14
14
  "package": {
@@ -70,12 +70,12 @@
70
70
  "sha256": "2903901d8c0c5076118aa691727daa79be7abe87fdb393c5b389a2b1a8fce170"
71
71
  },
72
72
  "index.d.ts": {
73
- "size": 2470,
74
- "sha256": "99c28d43f79dbb2b372bf6a8b611841c131f59f5066702b499915b874e9fa2b8"
73
+ "size": 2537,
74
+ "sha256": "1707d26322dbad17f6bf85938ae6fe2477e84c7fed3333760ce8d6eadfaaffd2"
75
75
  },
76
76
  "index.js": {
77
- "size": 3255,
78
- "sha256": "683151fa06b666ec62b150a4fda949c3d35221228b8538644ef740252b63587d"
77
+ "size": 3325,
78
+ "sha256": "0926788b00fc68f513ae20a508f2ef3c2f4e7e907fa32d70f146ce6e8b0ec812"
79
79
  },
80
80
  "migrations/20260331000001_create_zin_trace_entries_table.d.ts": {
81
81
  "size": 304,
@@ -130,8 +130,16 @@
130
130
  "sha256": "71d366165dd36f1675aa253a76262b226fb6c62e5ab632746b8aea61c0c625fc"
131
131
  },
132
132
  "register.js": {
133
- "size": 14225,
134
- "sha256": "07e1643982cc93149c609d44beb11dea168ecf1cac3f78d95d6d22fedcafbc23"
133
+ "size": 14327,
134
+ "sha256": "efc9bb131b9eef7e81a6f85ea3338fd8ac74257f794aa0fc7c76efed636f99d8"
135
+ },
136
+ "storage/TraceContentBudget.d.ts": {
137
+ "size": 159,
138
+ "sha256": "d899a615e6cf2a5eea51f6200347cb81fbaae11979b801859f57135083aaf85b"
139
+ },
140
+ "storage/TraceContentBudget.js": {
141
+ "size": 4022,
142
+ "sha256": "4b1d4f0ad7da15caeaa1f9fe8b4edcd3000b0000c5bdc270601cd6db2eedce2e"
135
143
  },
136
144
  "storage/TraceContentRedaction.d.ts": {
137
145
  "size": 207,
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@
8
8
  export { TraceConfig } from './config';
9
9
  export { TraceStorage } from './storage';
10
10
  export type { ITraceStorage } from './storage';
11
+ export { TraceContentBudget } from './storage/TraceContentBudget';
11
12
  export { TraceContentRedaction } from './storage/TraceContentRedaction';
12
13
  export { TraceContext } from './context';
13
14
  export { registerTraceDashboard, registerTraceRoutes } from './dashboard/routes';
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ export { TraceConfig } from './config.js';
14
14
  // Storage
15
15
  // ---------------------------------------------------------------------------
16
16
  export { TraceStorage } from './storage/index.js';
17
+ export { TraceContentBudget } from './storage/TraceContentBudget.js';
17
18
  export { TraceContentRedaction } from './storage/TraceContentRedaction.js';
18
19
  // ---------------------------------------------------------------------------
19
20
  // Context
package/dist/register.js CHANGED
@@ -22,6 +22,7 @@
22
22
  import { TraceConfig } from './config.js';
23
23
  import { TraceContext } from './context.js';
24
24
  import { TraceStorage } from './storage/index.js';
25
+ import { TraceContentBudget } from './storage/TraceContentBudget.js';
25
26
  import { TraceContentRedaction } from './storage/TraceContentRedaction.js';
26
27
  import { TraceEntryFiltering } from './storage/TraceEntryFiltering.js';
27
28
  import { TraceWriteDiagnostics } from './storage/TraceWriteDiagnostics.js';
@@ -256,7 +257,7 @@ if (!traceAlreadyInitialized && Env) {
256
257
  envKey: 'TRACE_QUERY_CONNECTION',
257
258
  });
258
259
  await assertTraceStorageReady(core, storageDb, resolvedConnectionName);
259
- const storage = TraceWriteDiagnostics.wrapStorage(TraceContentRedaction.wrapStorage(TraceEntryFiltering.wrapStorage(TraceStorage.resolveStorage(storageDb), config), config.redaction), {
260
+ const storage = TraceWriteDiagnostics.wrapStorage(TraceContentBudget.wrapStorage(TraceContentRedaction.wrapStorage(TraceEntryFiltering.wrapStorage(TraceStorage.resolveStorage(storageDb), config), config.redaction)), {
260
261
  connectionName: resolvedConnectionName,
261
262
  logger: core.Logger,
262
263
  });
@@ -0,0 +1,4 @@
1
+ import type { ITraceStorage } from '../types';
2
+ export declare const TraceContentBudget: Readonly<{
3
+ wrapStorage(storage: ITraceStorage): ITraceStorage;
4
+ }>;
@@ -0,0 +1,114 @@
1
+ const DEFAULT_MAX_ENTRY_BYTES = 64 * 1024;
2
+ const DEFAULT_MAX_STRING_BYTES = 16 * 1024;
3
+ const DEFAULT_MAX_ARRAY_ITEMS = 25;
4
+ const DEFAULT_MAX_OBJECT_ENTRIES = 40;
5
+ const DEFAULT_MAX_DEPTH = 6;
6
+ const DROPPED_FIELD_MESSAGE = '[trace] Value dropped because the field exceeded the trace storage size limit.';
7
+ const COMPACTED_CONTENT_MESSAGE = '[trace] Trace content was compacted because it exceeded the trace storage size limit.';
8
+ const encoder = new TextEncoder();
9
+ const serializedSize = (value) => {
10
+ try {
11
+ return encoder.encode(JSON.stringify(value)).length;
12
+ }
13
+ catch {
14
+ return Number.MAX_SAFE_INTEGER;
15
+ }
16
+ };
17
+ const describeValueType = (value) => {
18
+ if (Array.isArray(value))
19
+ return 'array';
20
+ if (value === null)
21
+ return 'null';
22
+ return typeof value;
23
+ };
24
+ const compactValue = (value, depth) => {
25
+ if (depth >= DEFAULT_MAX_DEPTH) {
26
+ return DROPPED_FIELD_MESSAGE;
27
+ }
28
+ if (typeof value === 'string') {
29
+ return serializedSize(value) > DEFAULT_MAX_STRING_BYTES ? DROPPED_FIELD_MESSAGE : value;
30
+ }
31
+ if (Array.isArray(value)) {
32
+ const next = value
33
+ .slice(0, DEFAULT_MAX_ARRAY_ITEMS)
34
+ .map((item) => compactValue(item, depth + 1));
35
+ if (value.length > DEFAULT_MAX_ARRAY_ITEMS) {
36
+ next.push(`[trace] ${String(value.length - DEFAULT_MAX_ARRAY_ITEMS)} additional items were dropped.`);
37
+ }
38
+ return next;
39
+ }
40
+ if (typeof value !== 'object' || value === null) {
41
+ return value;
42
+ }
43
+ const entries = Object.entries(value);
44
+ const compactedEntries = entries
45
+ .slice(0, DEFAULT_MAX_OBJECT_ENTRIES)
46
+ .map(([key, entryValue]) => [key, compactValue(entryValue, depth + 1)]);
47
+ if (entries.length > DEFAULT_MAX_OBJECT_ENTRIES) {
48
+ compactedEntries.push([
49
+ '__traceNotice',
50
+ `[trace] ${String(entries.length - DEFAULT_MAX_OBJECT_ENTRIES)} additional fields were dropped.`,
51
+ ]);
52
+ }
53
+ return Object.fromEntries(compactedEntries);
54
+ };
55
+ const compactTopLevelObjectToBudget = (value) => {
56
+ const compacted = {
57
+ ...value,
58
+ __traceNotice: COMPACTED_CONTENT_MESSAGE,
59
+ };
60
+ const keysByDescendingSize = Object.keys(compacted)
61
+ .filter((key) => key !== '__traceNotice')
62
+ .sort((left, right) => serializedSize(compacted[right]) - serializedSize(compacted[left]));
63
+ for (const key of keysByDescendingSize) {
64
+ if (serializedSize(compacted) <= DEFAULT_MAX_ENTRY_BYTES)
65
+ break;
66
+ compacted[key] = DROPPED_FIELD_MESSAGE;
67
+ }
68
+ return compacted;
69
+ };
70
+ const fitContentToBudget = (content) => {
71
+ if (serializedSize(content) <= DEFAULT_MAX_ENTRY_BYTES) {
72
+ return content;
73
+ }
74
+ const compacted = compactValue(content, 0);
75
+ if (serializedSize(compacted) <= DEFAULT_MAX_ENTRY_BYTES) {
76
+ return compacted;
77
+ }
78
+ if (typeof compacted === 'object' && compacted !== null && !Array.isArray(compacted)) {
79
+ const topLevelCompacted = compactTopLevelObjectToBudget(compacted);
80
+ if (serializedSize(topLevelCompacted) <= DEFAULT_MAX_ENTRY_BYTES) {
81
+ return topLevelCompacted;
82
+ }
83
+ }
84
+ return {
85
+ __traceNotice: COMPACTED_CONTENT_MESSAGE,
86
+ dropped: true,
87
+ valueType: describeValueType(content),
88
+ };
89
+ };
90
+ const fitEntryToBudget = (entry) => ({
91
+ ...entry,
92
+ content: fitContentToBudget(entry.content),
93
+ });
94
+ const fitPatchToBudget = (patch) => {
95
+ if (patch.content === undefined)
96
+ return patch;
97
+ return {
98
+ ...patch,
99
+ content: fitContentToBudget(patch.content),
100
+ };
101
+ };
102
+ export const TraceContentBudget = Object.freeze({
103
+ wrapStorage(storage) {
104
+ return Object.freeze({
105
+ ...storage,
106
+ writeEntry: async (entry) => {
107
+ await storage.writeEntry(fitEntryToBudget(entry));
108
+ },
109
+ updateEntry: async (uuid, patch) => {
110
+ await storage.updateEntry(uuid, fitPatchToBudget(patch));
111
+ },
112
+ });
113
+ },
114
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zintrust/trace",
3
- "version": "0.4.92",
3
+ "version": "0.4.94",
4
4
  "description": "Trace assistant for ZinTrust: logs requests, queries, exceptions, jobs, and more.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -40,7 +40,7 @@
40
40
  "node": ">=20.0.0"
41
41
  },
42
42
  "peerDependencies": {
43
- "@zintrust/core": "^0.4.91"
43
+ "@zintrust/core": "^0.4.93"
44
44
  },
45
45
  "publishConfig": {
46
46
  "access": "public"
package/src/index.ts CHANGED
@@ -18,6 +18,7 @@ export { TraceConfig } from './config';
18
18
  // ---------------------------------------------------------------------------
19
19
  export { TraceStorage } from './storage';
20
20
  export type { ITraceStorage } from './storage';
21
+ export { TraceContentBudget } from './storage/TraceContentBudget';
21
22
  export { TraceContentRedaction } from './storage/TraceContentRedaction';
22
23
 
23
24
  // ---------------------------------------------------------------------------
package/src/register.ts CHANGED
@@ -22,6 +22,7 @@
22
22
  import { TraceConfig } from './config';
23
23
  import { TraceContext } from './context';
24
24
  import { TraceStorage } from './storage';
25
+ import { TraceContentBudget } from './storage/TraceContentBudget';
25
26
  import { TraceContentRedaction } from './storage/TraceContentRedaction';
26
27
  import { TraceEntryFiltering } from './storage/TraceEntryFiltering';
27
28
  import { TraceWriteDiagnostics } from './storage/TraceWriteDiagnostics';
@@ -379,9 +380,11 @@ if (!traceAlreadyInitialized && Env) {
379
380
  await assertTraceStorageReady(core, storageDb, resolvedConnectionName);
380
381
 
381
382
  const storage = TraceWriteDiagnostics.wrapStorage(
382
- TraceContentRedaction.wrapStorage(
383
- TraceEntryFiltering.wrapStorage(TraceStorage.resolveStorage(storageDb), config),
384
- config.redaction
383
+ TraceContentBudget.wrapStorage(
384
+ TraceContentRedaction.wrapStorage(
385
+ TraceEntryFiltering.wrapStorage(TraceStorage.resolveStorage(storageDb), config),
386
+ config.redaction
387
+ )
385
388
  ),
386
389
  {
387
390
  connectionName: resolvedConnectionName,
@@ -0,0 +1,145 @@
1
+ import type { ITraceEntry, ITraceStorage } from '../types';
2
+
3
+ const DEFAULT_MAX_ENTRY_BYTES = 64 * 1024;
4
+ const DEFAULT_MAX_STRING_BYTES = 16 * 1024;
5
+ const DEFAULT_MAX_ARRAY_ITEMS = 25;
6
+ const DEFAULT_MAX_OBJECT_ENTRIES = 40;
7
+ const DEFAULT_MAX_DEPTH = 6;
8
+
9
+ const DROPPED_FIELD_MESSAGE =
10
+ '[trace] Value dropped because the field exceeded the trace storage size limit.';
11
+ const COMPACTED_CONTENT_MESSAGE =
12
+ '[trace] Trace content was compacted because it exceeded the trace storage size limit.';
13
+
14
+ const encoder = new TextEncoder();
15
+
16
+ const serializedSize = (value: unknown): number => {
17
+ try {
18
+ return encoder.encode(JSON.stringify(value)).length;
19
+ } catch {
20
+ return Number.MAX_SAFE_INTEGER;
21
+ }
22
+ };
23
+
24
+ const describeValueType = (value: unknown): string => {
25
+ if (Array.isArray(value)) return 'array';
26
+ if (value === null) return 'null';
27
+ return typeof value;
28
+ };
29
+
30
+ const compactValue = (value: unknown, depth: number): unknown => {
31
+ if (depth >= DEFAULT_MAX_DEPTH) {
32
+ return DROPPED_FIELD_MESSAGE;
33
+ }
34
+
35
+ if (typeof value === 'string') {
36
+ return serializedSize(value) > DEFAULT_MAX_STRING_BYTES ? DROPPED_FIELD_MESSAGE : value;
37
+ }
38
+
39
+ if (Array.isArray(value)) {
40
+ const next = value
41
+ .slice(0, DEFAULT_MAX_ARRAY_ITEMS)
42
+ .map((item) => compactValue(item, depth + 1));
43
+
44
+ if (value.length > DEFAULT_MAX_ARRAY_ITEMS) {
45
+ next.push(
46
+ `[trace] ${String(value.length - DEFAULT_MAX_ARRAY_ITEMS)} additional items were dropped.`
47
+ );
48
+ }
49
+
50
+ return next;
51
+ }
52
+
53
+ if (typeof value !== 'object' || value === null) {
54
+ return value;
55
+ }
56
+
57
+ const entries = Object.entries(value);
58
+ const compactedEntries = entries
59
+ .slice(0, DEFAULT_MAX_OBJECT_ENTRIES)
60
+ .map(([key, entryValue]) => [key, compactValue(entryValue, depth + 1)]);
61
+
62
+ if (entries.length > DEFAULT_MAX_OBJECT_ENTRIES) {
63
+ compactedEntries.push([
64
+ '__traceNotice',
65
+ `[trace] ${String(entries.length - DEFAULT_MAX_OBJECT_ENTRIES)} additional fields were dropped.`,
66
+ ]);
67
+ }
68
+
69
+ return Object.fromEntries(compactedEntries);
70
+ };
71
+
72
+ const compactTopLevelObjectToBudget = (value: Record<string, unknown>): Record<string, unknown> => {
73
+ const compacted: Record<string, unknown> = {
74
+ ...value,
75
+ __traceNotice: COMPACTED_CONTENT_MESSAGE,
76
+ };
77
+
78
+ const keysByDescendingSize = Object.keys(compacted)
79
+ .filter((key) => key !== '__traceNotice')
80
+ .sort((left, right) => serializedSize(compacted[right]) - serializedSize(compacted[left]));
81
+
82
+ for (const key of keysByDescendingSize) {
83
+ if (serializedSize(compacted) <= DEFAULT_MAX_ENTRY_BYTES) break;
84
+ compacted[key] = DROPPED_FIELD_MESSAGE;
85
+ }
86
+
87
+ return compacted;
88
+ };
89
+
90
+ const fitContentToBudget = (content: unknown): unknown => {
91
+ if (serializedSize(content) <= DEFAULT_MAX_ENTRY_BYTES) {
92
+ return content;
93
+ }
94
+
95
+ const compacted = compactValue(content, 0);
96
+ if (serializedSize(compacted) <= DEFAULT_MAX_ENTRY_BYTES) {
97
+ return compacted;
98
+ }
99
+
100
+ if (typeof compacted === 'object' && compacted !== null && !Array.isArray(compacted)) {
101
+ const topLevelCompacted = compactTopLevelObjectToBudget(compacted as Record<string, unknown>);
102
+ if (serializedSize(topLevelCompacted) <= DEFAULT_MAX_ENTRY_BYTES) {
103
+ return topLevelCompacted;
104
+ }
105
+ }
106
+
107
+ return {
108
+ __traceNotice: COMPACTED_CONTENT_MESSAGE,
109
+ dropped: true,
110
+ valueType: describeValueType(content),
111
+ };
112
+ };
113
+
114
+ const fitEntryToBudget = (entry: ITraceEntry): ITraceEntry => ({
115
+ ...entry,
116
+ content: fitContentToBudget(entry.content),
117
+ });
118
+
119
+ const fitPatchToBudget = (
120
+ patch: Partial<Pick<ITraceEntry, 'content' | 'isLatest'>>
121
+ ): Partial<Pick<ITraceEntry, 'content' | 'isLatest'>> => {
122
+ if (patch.content === undefined) return patch;
123
+
124
+ return {
125
+ ...patch,
126
+ content: fitContentToBudget(patch.content),
127
+ };
128
+ };
129
+
130
+ export const TraceContentBudget = Object.freeze({
131
+ wrapStorage(storage: ITraceStorage): ITraceStorage {
132
+ return Object.freeze({
133
+ ...storage,
134
+ writeEntry: async (entry: ITraceEntry): Promise<void> => {
135
+ await storage.writeEntry(fitEntryToBudget(entry));
136
+ },
137
+ updateEntry: async (
138
+ uuid: string,
139
+ patch: Partial<Pick<ITraceEntry, 'content' | 'isLatest'>>
140
+ ): Promise<void> => {
141
+ await storage.updateEntry(uuid, fitPatchToBudget(patch));
142
+ },
143
+ });
144
+ },
145
+ });