autotel-edge 3.0.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/LICENSE +21 -0
- package/README.md +333 -0
- package/dist/chunk-F32WSLNX.js +309 -0
- package/dist/chunk-F32WSLNX.js.map +1 -0
- package/dist/events.d.ts +86 -0
- package/dist/events.js +157 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +326 -0
- package/dist/index.js +921 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +89 -0
- package/dist/logger.js +81 -0
- package/dist/logger.js.map +1 -0
- package/dist/sampling.d.ts +166 -0
- package/dist/sampling.js +108 -0
- package/dist/sampling.js.map +1 -0
- package/dist/testing.d.ts +2 -0
- package/dist/testing.js +3 -0
- package/dist/testing.js.map +1 -0
- package/dist/types-Dj85cPUj.d.ts +182 -0
- package/package.json +88 -0
- package/src/api/logger.test.ts +367 -0
- package/src/api/logger.ts +197 -0
- package/src/compose.ts +243 -0
- package/src/core/buffer.ts +16 -0
- package/src/core/config.test.ts +388 -0
- package/src/core/config.ts +167 -0
- package/src/core/context.ts +224 -0
- package/src/core/exporter.ts +99 -0
- package/src/core/provider.ts +45 -0
- package/src/core/span.ts +222 -0
- package/src/core/spanprocessor.test.ts +521 -0
- package/src/core/spanprocessor.ts +232 -0
- package/src/core/trace-context.ts +66 -0
- package/src/core/tracer.test.ts +123 -0
- package/src/core/tracer.ts +216 -0
- package/src/events/index.test.ts +242 -0
- package/src/events/index.ts +338 -0
- package/src/events.ts +6 -0
- package/src/functional.test.ts +702 -0
- package/src/functional.ts +846 -0
- package/src/index.ts +81 -0
- package/src/logger.ts +13 -0
- package/src/sampling/index.test.ts +297 -0
- package/src/sampling/index.ts +276 -0
- package/src/sampling.ts +6 -0
- package/src/testing/index.ts +9 -0
- package/src/testing.ts +6 -0
- package/src/types.ts +267 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-dependency structured logger for edge environments
|
|
3
|
+
*
|
|
4
|
+
* This logger is ~100 LOC and provides:
|
|
5
|
+
* - Structured JSON logging
|
|
6
|
+
* - Auto trace context injection (traceId, spanId)
|
|
7
|
+
* - Dynamic log level control (per-request via context)
|
|
8
|
+
* - Level support (info, error, warn, debug)
|
|
9
|
+
* - Zero dependencies (console-based)
|
|
10
|
+
*
|
|
11
|
+
* Unlike Pino/Winston (~500KB), this is <1KB minified!
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { trace, context as api_context, createContextKey } from '@opentelemetry/api';
|
|
15
|
+
|
|
16
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'none';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Context key for storing active log level (enables per-request log levels)
|
|
20
|
+
*/
|
|
21
|
+
const LOG_LEVEL_KEY = createContextKey('autotel-edge-log-level');
|
|
22
|
+
|
|
23
|
+
export interface EdgeLogger {
|
|
24
|
+
info(msg: string, attrs?: Record<string, any>): void;
|
|
25
|
+
error(msg: string, error?: Error | unknown, attrs?: Record<string, any>): void;
|
|
26
|
+
warn(msg: string, attrs?: Record<string, any>): void;
|
|
27
|
+
debug(msg: string, attrs?: Record<string, any>): void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get the active log level from context (if set)
|
|
32
|
+
* Falls back to undefined if no log level is set in context
|
|
33
|
+
*/
|
|
34
|
+
export function getActiveLogLevel(): LogLevel | undefined {
|
|
35
|
+
return api_context.active().getValue(LOG_LEVEL_KEY) as LogLevel | undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Run a function with a specific log level
|
|
40
|
+
* The log level is stored in OpenTelemetry context and applies to all logger calls within the callback
|
|
41
|
+
*
|
|
42
|
+
* This works in edge runtimes (uses OTel context, not Node.js AsyncLocalStorage)
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* // Enable debug logging for a specific request
|
|
47
|
+
* runWithLogLevel('debug', () => {
|
|
48
|
+
* log.debug('This will be logged')
|
|
49
|
+
* processRequest()
|
|
50
|
+
* })
|
|
51
|
+
*
|
|
52
|
+
* // Disable logging temporarily
|
|
53
|
+
* runWithLogLevel('none', () => {
|
|
54
|
+
* log.info('This will NOT be logged')
|
|
55
|
+
* })
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export function runWithLogLevel<T>(level: LogLevel, callback: () => T): T {
|
|
59
|
+
const ctx = api_context.active().setValue(LOG_LEVEL_KEY, level);
|
|
60
|
+
return api_context.with(ctx, callback);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get current trace context from active span
|
|
65
|
+
*/
|
|
66
|
+
function getTraceContext():
|
|
67
|
+
| { traceId: string; spanId: string; correlationId: string }
|
|
68
|
+
| null {
|
|
69
|
+
const span = trace.getActiveSpan();
|
|
70
|
+
if (!span) return null;
|
|
71
|
+
|
|
72
|
+
const ctx = span.spanContext();
|
|
73
|
+
return {
|
|
74
|
+
traceId: ctx.traceId,
|
|
75
|
+
spanId: ctx.spanId,
|
|
76
|
+
correlationId: ctx.traceId.slice(0, 16), // First 16 chars for grouping
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Create a lightweight structured logger
|
|
82
|
+
*
|
|
83
|
+
* @param service - Service name for logging
|
|
84
|
+
* @param options - Optional configuration
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```typescript
|
|
88
|
+
* const log = createEdgeLogger('user-service')
|
|
89
|
+
*
|
|
90
|
+
* log.info('Creating user', { email: 'test@example.com' })
|
|
91
|
+
* // Output: {"level":"info","service":"user-service","msg":"Creating user",
|
|
92
|
+
* // "email":"test@example.com","traceId":"...","spanId":"..."}
|
|
93
|
+
*
|
|
94
|
+
* // Dynamic log level control per-request
|
|
95
|
+
* runWithLogLevel('debug', () => {
|
|
96
|
+
* log.debug('This will be logged even if logger was created with level: "info"')
|
|
97
|
+
* })
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export function createEdgeLogger(
|
|
101
|
+
service: string,
|
|
102
|
+
options?: {
|
|
103
|
+
level?: LogLevel;
|
|
104
|
+
pretty?: boolean; // For development
|
|
105
|
+
},
|
|
106
|
+
): EdgeLogger {
|
|
107
|
+
const defaultLevel = options?.level || 'info';
|
|
108
|
+
const pretty = options?.pretty || false;
|
|
109
|
+
|
|
110
|
+
const levelPriority: Record<LogLevel, number> = {
|
|
111
|
+
none: -1,
|
|
112
|
+
debug: 0,
|
|
113
|
+
info: 1,
|
|
114
|
+
warn: 2,
|
|
115
|
+
error: 3,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const shouldLog = (level: LogLevel): boolean => {
|
|
119
|
+
// Priority: context level > options level > 'info' default
|
|
120
|
+
const activeLevel = getActiveLogLevel() ?? defaultLevel;
|
|
121
|
+
|
|
122
|
+
// 'none' means suppress all logging
|
|
123
|
+
if (activeLevel === 'none') return false;
|
|
124
|
+
|
|
125
|
+
return levelPriority[level] >= levelPriority[activeLevel];
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const log = (
|
|
129
|
+
level: 'info' | 'error' | 'warn' | 'debug',
|
|
130
|
+
msg: string,
|
|
131
|
+
attrs?: Record<string, any>,
|
|
132
|
+
) => {
|
|
133
|
+
if (!shouldLog(level)) return;
|
|
134
|
+
|
|
135
|
+
const ctx = getTraceContext();
|
|
136
|
+
const logEntry: Record<string, any> = {
|
|
137
|
+
level,
|
|
138
|
+
service,
|
|
139
|
+
msg,
|
|
140
|
+
...attrs,
|
|
141
|
+
...ctx, // Auto-inject traceId, spanId, correlationId
|
|
142
|
+
timestamp: new Date().toISOString(),
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
if (pretty) {
|
|
146
|
+
// Pretty print for development
|
|
147
|
+
const traceInfo = ctx
|
|
148
|
+
? ` [${ctx.traceId.slice(0, 8)}.../${ctx.spanId.slice(0, 8)}...]`
|
|
149
|
+
: '';
|
|
150
|
+
console.log(
|
|
151
|
+
`[${level.toUpperCase()}]${traceInfo} ${service}: ${msg}`,
|
|
152
|
+
attrs || '',
|
|
153
|
+
);
|
|
154
|
+
} else {
|
|
155
|
+
// Structured JSON for production
|
|
156
|
+
console.log(JSON.stringify(logEntry));
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
info: (msg: string, attrs?: Record<string, any>) => log('info', msg, attrs),
|
|
162
|
+
|
|
163
|
+
error: (msg: string, error?: Error | unknown, attrs?: Record<string, any>) => {
|
|
164
|
+
const errorAttrs = error instanceof Error
|
|
165
|
+
? {
|
|
166
|
+
error: error.message,
|
|
167
|
+
stack: error.stack,
|
|
168
|
+
name: error.name,
|
|
169
|
+
...attrs,
|
|
170
|
+
}
|
|
171
|
+
: { error: String(error), ...attrs };
|
|
172
|
+
|
|
173
|
+
log('error', msg, errorAttrs);
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
warn: (msg: string, attrs?: Record<string, any>) => log('warn', msg, attrs),
|
|
177
|
+
|
|
178
|
+
debug: (msg: string, attrs?: Record<string, any>) => log('debug', msg, attrs),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Helper to get trace context (useful for BYOL - Bring Your Own Logger)
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```typescript
|
|
187
|
+
* import bunyan from 'bunyan'
|
|
188
|
+
* import { getEdgeTraceContext } from 'autotel-edge/api/logger'
|
|
189
|
+
*
|
|
190
|
+
* const bunyanLogger = bunyan.createLogger({ name: 'myapp' })
|
|
191
|
+
* const ctx = getEdgeTraceContext()
|
|
192
|
+
* bunyanLogger.info({ ...ctx, email: 'test@example.com' }, 'Creating user')
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
195
|
+
export function getEdgeTraceContext() {
|
|
196
|
+
return getTraceContext();
|
|
197
|
+
}
|
package/src/compose.ts
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composition utilities for autotel-edge
|
|
3
|
+
*
|
|
4
|
+
* Helper functions for composing instrumentation and middleware.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { compose } from 'autotel-edge/api/compose'
|
|
9
|
+
* import { instrumentGlobalFetch, instrumentGlobalCache } from 'autotel-edge/instrumentation'
|
|
10
|
+
*
|
|
11
|
+
* const setupInstrumentation = compose(
|
|
12
|
+
* instrumentGlobalFetch,
|
|
13
|
+
* instrumentGlobalCache
|
|
14
|
+
* )
|
|
15
|
+
*
|
|
16
|
+
* setupInstrumentation({ enabled: true })
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Compose multiple setup functions into one
|
|
22
|
+
*
|
|
23
|
+
* Takes multiple instrumentation functions and returns a single function
|
|
24
|
+
* that calls them all in order with the same config.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const setup = compose(
|
|
29
|
+
* instrumentGlobalFetch,
|
|
30
|
+
* instrumentGlobalCache
|
|
31
|
+
* )
|
|
32
|
+
*
|
|
33
|
+
* setup({ enabled: true })
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export function compose<TConfig = unknown>(
|
|
37
|
+
...fns: Array<(config?: TConfig) => void>
|
|
38
|
+
): (config?: TConfig) => void {
|
|
39
|
+
return (config?: TConfig) => {
|
|
40
|
+
for (const fn of fns) {
|
|
41
|
+
fn(config);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Compose multiple async setup functions into one
|
|
48
|
+
*
|
|
49
|
+
* Like `compose` but for async functions.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* const setup = composeAsync(
|
|
54
|
+
* async (config) => { await initTracing(config) },
|
|
55
|
+
* async (config) => { await initMetrics(config) }
|
|
56
|
+
* )
|
|
57
|
+
*
|
|
58
|
+
* await setup({ endpoint: 'https://...' })
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export function composeAsync<TConfig = unknown>(
|
|
62
|
+
...fns: Array<(config?: TConfig) => Promise<void>>
|
|
63
|
+
): (config?: TConfig) => Promise<void> {
|
|
64
|
+
return async (config?: TConfig) => {
|
|
65
|
+
for (const fn of fns) {
|
|
66
|
+
await fn(config);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Pipe - compose functions from left to right
|
|
73
|
+
*
|
|
74
|
+
* Unlike `compose` which is right-to-left, pipe is left-to-right
|
|
75
|
+
* which matches the execution order visually.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* const setup = pipe(
|
|
80
|
+
* (config) => ({ ...config, tracing: true }),
|
|
81
|
+
* (config) => ({ ...config, metrics: true }),
|
|
82
|
+
* (config) => initObservability(config)
|
|
83
|
+
* )
|
|
84
|
+
*
|
|
85
|
+
* setup({ service: 'my-worker' })
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export function pipe<TInput, TOutput>(
|
|
89
|
+
...fns: Array<(input: any) => any>
|
|
90
|
+
): (input: TInput) => TOutput {
|
|
91
|
+
return (input: TInput) => {
|
|
92
|
+
return fns.reduce((acc, fn) => fn(acc), input as any) as TOutput;
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Create a conditional instrumentation function
|
|
98
|
+
*
|
|
99
|
+
* Only runs the instrumentation if the predicate returns true.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* const setupFetch = when(
|
|
104
|
+
* (env) => env.ENABLE_FETCH_TRACING === 'true',
|
|
105
|
+
* instrumentGlobalFetch
|
|
106
|
+
* )
|
|
107
|
+
*
|
|
108
|
+
* setupFetch(env)
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
export function when<TConfig = unknown>(
|
|
112
|
+
predicate: (config?: TConfig) => boolean,
|
|
113
|
+
fn: (config?: TConfig) => void
|
|
114
|
+
): (config?: TConfig) => void {
|
|
115
|
+
return (config?: TConfig) => {
|
|
116
|
+
if (predicate(config)) {
|
|
117
|
+
fn(config);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Create a conditional async instrumentation function
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```typescript
|
|
127
|
+
* const setupCache = whenAsync(
|
|
128
|
+
* async (env) => await featureEnabled('cache-tracing'),
|
|
129
|
+
* instrumentGlobalCache
|
|
130
|
+
* )
|
|
131
|
+
*
|
|
132
|
+
* await setupCache(env)
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
export function whenAsync<TConfig = unknown>(
|
|
136
|
+
predicate: (config?: TConfig) => Promise<boolean> | boolean,
|
|
137
|
+
fn: (config?: TConfig) => Promise<void>
|
|
138
|
+
): (config?: TConfig) => Promise<void> {
|
|
139
|
+
return async (config?: TConfig) => {
|
|
140
|
+
if (await predicate(config)) {
|
|
141
|
+
await fn(config);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Tap - run a side effect and return the original value
|
|
148
|
+
*
|
|
149
|
+
* Useful for logging or debugging in a pipe.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```typescript
|
|
153
|
+
* const setup = pipe(
|
|
154
|
+
* tap((config) => console.log('Initial config:', config)),
|
|
155
|
+
* (config) => ({ ...config, tracing: true }),
|
|
156
|
+
* tap((config) => console.log('After tracing:', config)),
|
|
157
|
+
* (config) => initObservability(config)
|
|
158
|
+
* )
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
export function tap<T>(fn: (value: T) => void): (value: T) => T {
|
|
162
|
+
return (value: T) => {
|
|
163
|
+
fn(value);
|
|
164
|
+
return value;
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Memoize - cache the result of a function
|
|
170
|
+
*
|
|
171
|
+
* Useful for expensive initialization functions that should only run once.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```typescript
|
|
175
|
+
* const setup = memoize(() => {
|
|
176
|
+
* console.log('Setting up (expensive)...')
|
|
177
|
+
* instrumentGlobalFetch()
|
|
178
|
+
* instrumentGlobalCache()
|
|
179
|
+
* })
|
|
180
|
+
*
|
|
181
|
+
* setup() // Logs "Setting up (expensive)..."
|
|
182
|
+
* setup() // Does nothing (cached)
|
|
183
|
+
* setup() // Does nothing (cached)
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
export function memoize<TArgs extends any[], TReturn>(
|
|
187
|
+
fn: (...args: TArgs) => TReturn
|
|
188
|
+
): (...args: TArgs) => TReturn {
|
|
189
|
+
const cached: { hasValue: boolean; value: TReturn } = { hasValue: false, value: undefined as any };
|
|
190
|
+
|
|
191
|
+
return (...args: TArgs) => {
|
|
192
|
+
if (!cached.hasValue) {
|
|
193
|
+
cached.value = fn(...args);
|
|
194
|
+
cached.hasValue = true;
|
|
195
|
+
}
|
|
196
|
+
return cached.value;
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Retry - retry a function on failure
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* ```typescript
|
|
205
|
+
* const setupWithRetry = retry(
|
|
206
|
+
* async () => {
|
|
207
|
+
* await fetch('https://api.example.com/init')
|
|
208
|
+
* },
|
|
209
|
+
* { maxAttempts: 3, delayMs: 1000 }
|
|
210
|
+
* )
|
|
211
|
+
*
|
|
212
|
+
* await setupWithRetry()
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
export function retry<TArgs extends any[], TReturn>(
|
|
216
|
+
fn: (...args: TArgs) => Promise<TReturn>,
|
|
217
|
+
options: {
|
|
218
|
+
maxAttempts?: number;
|
|
219
|
+
delayMs?: number;
|
|
220
|
+
onRetry?: (attempt: number, error: Error) => void;
|
|
221
|
+
} = {}
|
|
222
|
+
): (...args: TArgs) => Promise<TReturn> {
|
|
223
|
+
const { maxAttempts = 3, delayMs = 1000, onRetry } = options;
|
|
224
|
+
|
|
225
|
+
return async (...args: TArgs) => {
|
|
226
|
+
let lastError: Error | undefined;
|
|
227
|
+
|
|
228
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
229
|
+
try {
|
|
230
|
+
return await fn(...args);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
233
|
+
|
|
234
|
+
if (attempt < maxAttempts) {
|
|
235
|
+
onRetry?.(attempt, lastError);
|
|
236
|
+
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
throw lastError;
|
|
242
|
+
};
|
|
243
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Buffer polyfill for edge environments
|
|
3
|
+
*
|
|
4
|
+
* Cloudflare Workers and other edge runtimes need the Buffer global
|
|
5
|
+
* for OpenTelemetry OTLP serialization.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
//@ts-ignore - node:buffer available in CF Workers with nodejs_compat
|
|
9
|
+
import { Buffer } from 'node:buffer';
|
|
10
|
+
|
|
11
|
+
//@ts-ignore
|
|
12
|
+
globalThis.Buffer = Buffer;
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
export {Buffer} from 'node:buffer';
|