sloplog 0.0.3

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.
@@ -0,0 +1,500 @@
1
+ /**
2
+ * Code generation utilities for sloplog partials
3
+ *
4
+ * Generates TypeScript, Python, and JSON Schema from partial definitions
5
+ */
6
+ function getZodFieldInfo(zodType) {
7
+ let current = zodType;
8
+ let isOptional = false;
9
+ let isArray = false;
10
+ let description;
11
+ // Zod v4 uses _def.type instead of _def.typeName
12
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
+ const getDefType = (t) => t._def.type || t._def.typeName;
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ const getDescription = (t) => t._def.description;
16
+ description = getDescription(current);
17
+ // Unwrap optional
18
+ if (getDefType(current) === 'optional') {
19
+ isOptional = true;
20
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
+ current = current._def.innerType;
22
+ description = description ?? getDescription(current);
23
+ }
24
+ // Unwrap array
25
+ if (getDefType(current) === 'array') {
26
+ isArray = true;
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ current = current._def.element;
29
+ description = description ?? getDescription(current);
30
+ }
31
+ // Get base type
32
+ let baseType;
33
+ const defType = getDefType(current);
34
+ let enumValues;
35
+ switch (defType) {
36
+ case 'string':
37
+ baseType = 'string';
38
+ break;
39
+ case 'number':
40
+ baseType = 'number';
41
+ break;
42
+ case 'boolean':
43
+ baseType = 'boolean';
44
+ break;
45
+ case 'enum': {
46
+ baseType = 'string';
47
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
48
+ const def = current._def;
49
+ const values = def.values ?? def.options;
50
+ if (Array.isArray(values)) {
51
+ enumValues = values.filter((value) => typeof value === 'string');
52
+ }
53
+ else if (def.entries && typeof def.entries === 'object') {
54
+ enumValues = Object.values(def.entries).filter((value) => typeof value === 'string');
55
+ }
56
+ break;
57
+ }
58
+ case 'nativeEnum': {
59
+ baseType = 'string';
60
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
61
+ const values = current._def.values;
62
+ if (values && typeof values === 'object') {
63
+ enumValues = Object.values(values).filter((value) => typeof value === 'string');
64
+ }
65
+ break;
66
+ }
67
+ default:
68
+ throw new Error(`Unsupported Zod type: ${defType}`);
69
+ }
70
+ return { baseType, isArray, isOptional, enumValues, description };
71
+ }
72
+ /**
73
+ * Get the shape entries from a Zod schema, filtering out internal properties
74
+ */
75
+ function getSchemaEntries(schema) {
76
+ const shape = schema.shape;
77
+ return Object.entries(shape).filter(([_key, value]) => {
78
+ // Filter out non-Zod entries and internal properties
79
+ return value && typeof value === 'object' && '_def' in value;
80
+ });
81
+ }
82
+ function getPartialDescription(def) {
83
+ return def.options.description;
84
+ }
85
+ /**
86
+ * Convert a partial schema to JSON Schema
87
+ */
88
+ function partialToJsonSchema(def) {
89
+ const properties = {
90
+ type: { const: def.name },
91
+ };
92
+ const required = ['type'];
93
+ const description = getPartialDescription(def);
94
+ for (const [fieldName, zodType] of getSchemaEntries(def.schema)) {
95
+ const { baseType, isArray, isOptional, enumValues, description: fieldDescription, } = getZodFieldInfo(zodType);
96
+ const jsonType = baseType === 'number' ? 'number' : baseType;
97
+ if (isArray) {
98
+ const items = { type: jsonType };
99
+ if (enumValues && enumValues.length > 0) {
100
+ items.enum = enumValues;
101
+ }
102
+ properties[fieldName] = {
103
+ type: 'array',
104
+ items,
105
+ };
106
+ }
107
+ else {
108
+ const fieldSchema = { type: jsonType };
109
+ if (enumValues && enumValues.length > 0) {
110
+ fieldSchema.enum = enumValues;
111
+ }
112
+ properties[fieldName] = fieldSchema;
113
+ }
114
+ if (fieldDescription) {
115
+ properties[fieldName].description = fieldDescription;
116
+ }
117
+ if (!isOptional) {
118
+ required.push(fieldName);
119
+ }
120
+ }
121
+ return {
122
+ type: 'object',
123
+ properties,
124
+ required,
125
+ additionalProperties: false,
126
+ ...(description ? { description } : {}),
127
+ };
128
+ }
129
+ /**
130
+ * Generate full JSON Schema for a registry
131
+ */
132
+ export function generateJsonSchema(reg) {
133
+ const definitions = {};
134
+ const metadata = {};
135
+ for (const partial of reg.partials) {
136
+ definitions[partial.name] = partialToJsonSchema(partial);
137
+ metadata[partial.name] = {
138
+ repeatable: partial.options.repeatable ?? false,
139
+ alwaysSample: partial.options.alwaysSample ?? false,
140
+ };
141
+ }
142
+ // Generate registry schema showing the structure of a wide event log
143
+ const registryProperties = {};
144
+ for (const partial of reg.partials) {
145
+ const isRepeatable = partial.options.repeatable ?? false;
146
+ if (isRepeatable) {
147
+ registryProperties[partial.name] = {
148
+ type: 'array',
149
+ items: { $ref: `#/definitions/${partial.name}` },
150
+ };
151
+ }
152
+ else {
153
+ registryProperties[partial.name] = {
154
+ $ref: `#/definitions/${partial.name}`,
155
+ };
156
+ }
157
+ }
158
+ return {
159
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
160
+ $id: 'sloplog-partials',
161
+ definitions,
162
+ metadata,
163
+ registry: {
164
+ type: 'object',
165
+ description: 'Structure of partial fields in a wide event log',
166
+ properties: registryProperties,
167
+ additionalProperties: false,
168
+ },
169
+ };
170
+ }
171
+ /**
172
+ * Convert string to PascalCase
173
+ */
174
+ function pascalCase(str) {
175
+ return str
176
+ .split(/[-_]/)
177
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
178
+ .join('');
179
+ }
180
+ /**
181
+ * Convert camelCase to snake_case
182
+ */
183
+ function toSnakeCase(str) {
184
+ return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
185
+ }
186
+ /**
187
+ * Generate TypeScript types for a registry
188
+ */
189
+ export function generateTypeScript(reg) {
190
+ const lines = [
191
+ '// Auto-generated by sloplog codegen - DO NOT EDIT',
192
+ '// Source: sloplog registry',
193
+ '',
194
+ 'import type { EventPartial, PartialMetadata } from "../index"',
195
+ '',
196
+ ];
197
+ for (const partial of reg.partials) {
198
+ const interfaceName = pascalCase(partial.name) + 'Partial';
199
+ const partialDescription = getPartialDescription(partial);
200
+ if (partialDescription) {
201
+ lines.push(`/** ${partialDescription} */`);
202
+ }
203
+ lines.push(`export interface ${interfaceName} extends EventPartial<"${partial.name}"> {`);
204
+ lines.push(` type: "${partial.name}"`);
205
+ for (const [fieldName, zodType] of getSchemaEntries(partial.schema)) {
206
+ const { baseType, isArray, isOptional, enumValues, description: fieldDescription, } = getZodFieldInfo(zodType);
207
+ const enumType = enumValues?.length
208
+ ? enumValues.map((value) => JSON.stringify(value)).join(' | ')
209
+ : baseType;
210
+ const needsParens = isArray && enumValues && enumValues.length > 1;
211
+ const fullType = isArray ? (needsParens ? `(${enumType})[]` : `${enumType}[]`) : enumType;
212
+ const optionalMarker = isOptional ? '?' : '';
213
+ if (fieldDescription) {
214
+ lines.push(` /** ${fieldDescription} */`);
215
+ }
216
+ lines.push(` ${fieldName}${optionalMarker}: ${fullType}`);
217
+ }
218
+ lines.push('}');
219
+ lines.push('');
220
+ }
221
+ // Generate the registry type - repeatable partials become arrays
222
+ lines.push('// Registry type combining all partials');
223
+ lines.push('// Repeatable partials are typed as arrays');
224
+ lines.push('export type GeneratedRegistry = {');
225
+ for (const partial of reg.partials) {
226
+ const interfaceName = pascalCase(partial.name) + 'Partial';
227
+ const isRepeatable = partial.options.repeatable ?? false;
228
+ const registryType = isRepeatable ? `${interfaceName}[]` : interfaceName;
229
+ lines.push(` ${partial.name}: ${registryType}`);
230
+ }
231
+ lines.push('}');
232
+ lines.push('');
233
+ // Export partial names as a union
234
+ const partialNames = reg.partials.map((p) => `"${p.name}"`).join(' | ');
235
+ lines.push(`export type PartialName = ${partialNames || 'never'}`);
236
+ lines.push('');
237
+ // Export repeatable partial names
238
+ const repeatableNames = reg.partials
239
+ .filter((p) => p.options.repeatable)
240
+ .map((p) => `"${p.name}"`)
241
+ .join(' | ');
242
+ lines.push(`export type RepeatablePartialName = ${repeatableNames || 'never'}`);
243
+ lines.push('');
244
+ // Export alwaysSample partial names
245
+ const alwaysSampleNames = reg.partials
246
+ .filter((p) => p.options.alwaysSample)
247
+ .map((p) => `"${p.name}"`)
248
+ .join(' | ');
249
+ lines.push(`export type AlwaysSamplePartialName = ${alwaysSampleNames || 'never'}`);
250
+ lines.push('');
251
+ // Generate partial metadata constant for runtime use
252
+ lines.push('// Runtime metadata for partials');
253
+ lines.push('export const partialMetadata: Map<string, PartialMetadata> = new Map([');
254
+ for (const partial of reg.partials) {
255
+ const repeatable = partial.options.repeatable ?? false;
256
+ const alwaysSample = partial.options.alwaysSample ?? false;
257
+ lines.push(` ["${partial.name}", { repeatable: ${repeatable}, alwaysSample: ${alwaysSample} }],`);
258
+ }
259
+ lines.push(']);');
260
+ lines.push('');
261
+ return lines.join('\n');
262
+ }
263
+ /**
264
+ * Convert Zod type to Python type annotation
265
+ */
266
+ function zodFieldInfoToPython(info) {
267
+ const { baseType, isArray, enumValues } = info;
268
+ if (enumValues && enumValues.length > 0) {
269
+ const literalType = `Literal[${enumValues.map((v) => JSON.stringify(v)).join(', ')}]`;
270
+ return {
271
+ type: isArray ? `list[${literalType}]` : literalType,
272
+ needsLiteral: true,
273
+ };
274
+ }
275
+ let pyType;
276
+ switch (baseType) {
277
+ case 'string':
278
+ pyType = 'str';
279
+ break;
280
+ case 'number':
281
+ pyType = 'float';
282
+ break;
283
+ case 'boolean':
284
+ pyType = 'bool';
285
+ break;
286
+ }
287
+ return { type: isArray ? `list[${pyType}]` : pyType, needsLiteral: false };
288
+ }
289
+ /**
290
+ * Generate Python TypedDicts for a registry
291
+ */
292
+ export function generatePython(reg) {
293
+ const typingImports = ['TypedDict'];
294
+ const needsLiteral = reg.partials.some((partial) => getSchemaEntries(partial.schema).some(([, zodType]) => getZodFieldInfo(zodType).enumValues?.length));
295
+ if (needsLiteral) {
296
+ typingImports.push('Literal');
297
+ }
298
+ if (reg.partials.length > 0) {
299
+ typingImports.push('Union');
300
+ }
301
+ else {
302
+ typingImports.push('Never');
303
+ }
304
+ const lines = [
305
+ '# Auto-generated by sloplog codegen - DO NOT EDIT',
306
+ '# Source: sloplog registry',
307
+ '',
308
+ `from typing import ${typingImports.join(', ')}`,
309
+ '',
310
+ ];
311
+ for (const partial of reg.partials) {
312
+ const className = pascalCase(partial.name) + 'Partial';
313
+ const entries = getSchemaEntries(partial.schema);
314
+ const partialDescription = getPartialDescription(partial);
315
+ const hasOptional = entries.some(([_, zodType]) => getZodFieldInfo(zodType).isOptional);
316
+ if (hasOptional) {
317
+ const requiredFields = entries.filter(([_, zodType]) => !getZodFieldInfo(zodType).isOptional);
318
+ const optionalFields = entries.filter(([_, zodType]) => getZodFieldInfo(zodType).isOptional);
319
+ if (requiredFields.length > 0) {
320
+ lines.push(`class _${className}Required(TypedDict):`);
321
+ lines.push(` """Required fields for ${partial.name} partial"""`);
322
+ lines.push(` type: str # Literal["${partial.name}"]`);
323
+ for (const [fieldName, zodType] of requiredFields) {
324
+ const info = getZodFieldInfo(zodType);
325
+ const { type: pyType } = zodFieldInfoToPython(info);
326
+ if (info.description) {
327
+ lines.push(` # ${info.description}`);
328
+ }
329
+ lines.push(` ${toSnakeCase(fieldName)}: ${pyType}`);
330
+ }
331
+ lines.push('');
332
+ lines.push(`class ${className}(_${className}Required, total=False):`);
333
+ lines.push(` """${partialDescription ?? `${pascalCase(partial.name)} event partial`}"""`);
334
+ for (const [fieldName, zodType] of optionalFields) {
335
+ const info = getZodFieldInfo(zodType);
336
+ const { type: pyType } = zodFieldInfoToPython(info);
337
+ if (info.description) {
338
+ lines.push(` # ${info.description}`);
339
+ }
340
+ lines.push(` ${toSnakeCase(fieldName)}: ${pyType}`);
341
+ }
342
+ }
343
+ else {
344
+ lines.push(`class ${className}(TypedDict, total=False):`);
345
+ lines.push(` """${partialDescription ?? `${pascalCase(partial.name)} event partial`}"""`);
346
+ lines.push(` type: str # Literal["${partial.name}"] - required`);
347
+ for (const [fieldName, zodType] of optionalFields) {
348
+ const info = getZodFieldInfo(zodType);
349
+ const { type: pyType } = zodFieldInfoToPython(info);
350
+ if (info.description) {
351
+ lines.push(` # ${info.description}`);
352
+ }
353
+ lines.push(` ${toSnakeCase(fieldName)}: ${pyType}`);
354
+ }
355
+ }
356
+ }
357
+ else {
358
+ lines.push(`class ${className}(TypedDict):`);
359
+ lines.push(` """${partialDescription ?? `${pascalCase(partial.name)} event partial`}"""`);
360
+ lines.push(` type: str # Literal["${partial.name}"]`);
361
+ for (const [fieldName, zodType] of entries) {
362
+ const info = getZodFieldInfo(zodType);
363
+ const { type: pyType } = zodFieldInfoToPython(info);
364
+ if (info.description) {
365
+ lines.push(` # ${info.description}`);
366
+ }
367
+ lines.push(` ${toSnakeCase(fieldName)}: ${pyType}`);
368
+ }
369
+ }
370
+ lines.push('');
371
+ }
372
+ // Generate union type for all partials
373
+ const partialTypes = reg.partials.map((p) => pascalCase(p.name) + 'Partial');
374
+ lines.push('# Union of all partial types');
375
+ if (partialTypes.length > 0) {
376
+ lines.push(`GeneratedPartial = Union[${partialTypes.join(', ')}]`);
377
+ }
378
+ else {
379
+ lines.push('GeneratedPartial = Never');
380
+ }
381
+ lines.push('');
382
+ // Generate registry type - repeatable partials become lists
383
+ lines.push('# Registry mapping partial names to their types');
384
+ lines.push('# Repeatable partials are typed as lists');
385
+ lines.push('class GeneratedRegistry(TypedDict):');
386
+ lines.push(' """Type-safe registry of all event partials"""');
387
+ for (const partial of reg.partials) {
388
+ const className = pascalCase(partial.name) + 'Partial';
389
+ const isRepeatable = partial.options.repeatable ?? false;
390
+ const registryType = isRepeatable ? `list[${className}]` : className;
391
+ lines.push(` ${partial.name}: ${registryType}`);
392
+ }
393
+ lines.push('');
394
+ // Generate PartialMetadata TypedDict
395
+ lines.push('class PartialMetadata(TypedDict):');
396
+ lines.push(' """Metadata about a partial type"""');
397
+ lines.push(' repeatable: bool');
398
+ lines.push(' always_sample: bool');
399
+ lines.push('');
400
+ // Generate partial metadata dict for runtime use
401
+ lines.push('# Runtime metadata for partials');
402
+ lines.push('PARTIAL_METADATA: dict[str, PartialMetadata] = {');
403
+ for (const partial of reg.partials) {
404
+ const repeatable = partial.options.repeatable ?? false;
405
+ const alwaysSample = partial.options.alwaysSample ?? false;
406
+ lines.push(` "${partial.name}": {"repeatable": ${repeatable ? 'True' : 'False'}, "always_sample": ${alwaysSample ? 'True' : 'False'}},`);
407
+ }
408
+ lines.push('}');
409
+ lines.push('');
410
+ // Export list
411
+ lines.push('__all__ = [');
412
+ for (const partial of reg.partials) {
413
+ lines.push(` "${pascalCase(partial.name)}Partial",`);
414
+ }
415
+ lines.push(' "GeneratedPartial",');
416
+ lines.push(' "GeneratedRegistry",');
417
+ lines.push(' "PartialMetadata",');
418
+ lines.push(' "PARTIAL_METADATA",');
419
+ lines.push(']');
420
+ lines.push('');
421
+ return lines.join('\n');
422
+ }
423
+ function resolveOutputs(outputs) {
424
+ const defaults = { python: 'sloplog.py', jsonschema: 'sloplog.json' };
425
+ if (!outputs) {
426
+ return defaults;
427
+ }
428
+ if (Array.isArray(outputs)) {
429
+ return {
430
+ python: outputs.includes('python') ? defaults.python : undefined,
431
+ jsonschema: outputs.includes('jsonschema') ? defaults.jsonschema : undefined,
432
+ };
433
+ }
434
+ const resolved = {};
435
+ if (outputs.python !== undefined) {
436
+ resolved.python = outputs.python === false ? undefined : (outputs.python ?? defaults.python);
437
+ }
438
+ if (outputs.jsonschema !== undefined) {
439
+ resolved.jsonschema =
440
+ outputs.jsonschema === false ? undefined : (outputs.jsonschema ?? defaults.jsonschema);
441
+ }
442
+ return resolved;
443
+ }
444
+ function isRegistry(value) {
445
+ if (!value || typeof value !== 'object') {
446
+ return false;
447
+ }
448
+ return Array.isArray(value.partials);
449
+ }
450
+ async function loadRegistry(registry, resolvePath, pathToFileURL) {
451
+ if (typeof registry !== 'string') {
452
+ if (isRegistry(registry)) {
453
+ return registry;
454
+ }
455
+ throw new Error('sloplog registry must include a partials array');
456
+ }
457
+ const registryPath = resolvePath(registry);
458
+ const module = await import(pathToFileURL(registryPath).href);
459
+ const resolved = (module.default || module.registry);
460
+ if (!resolved || !isRegistry(resolved)) {
461
+ throw new Error('sloplog registry file must export a registry created with registry()');
462
+ }
463
+ return resolved;
464
+ }
465
+ /**
466
+ * Generate outputs from a sloplog registry (Python + JSON schema).
467
+ */
468
+ export async function config(config) {
469
+ const [{ mkdir, writeFile }, pathModule, urlModule] = await Promise.all([
470
+ import('node:fs/promises'),
471
+ import('node:path'),
472
+ import('node:url'),
473
+ ]);
474
+ const { resolve, dirname, isAbsolute } = pathModule;
475
+ const { pathToFileURL } = urlModule;
476
+ const registry = await loadRegistry(config.registry, resolve, pathToFileURL);
477
+ const outDir = resolve(config.outDir ?? './generated');
478
+ const outputs = resolveOutputs(config.outputs);
479
+ const result = {};
480
+ if (outputs.python) {
481
+ const code = generatePython(registry);
482
+ const outputPath = isAbsolute(outputs.python)
483
+ ? outputs.python
484
+ : resolve(outDir, outputs.python);
485
+ await mkdir(dirname(outputPath), { recursive: true });
486
+ await writeFile(outputPath, `${code}\n`, 'utf8');
487
+ result.python = { path: outputPath, code };
488
+ }
489
+ if (outputs.jsonschema) {
490
+ const schema = generateJsonSchema(registry);
491
+ const code = `${JSON.stringify(schema, null, 2)}\n`;
492
+ const outputPath = isAbsolute(outputs.jsonschema)
493
+ ? outputs.jsonschema
494
+ : resolve(outDir, outputs.jsonschema);
495
+ await mkdir(dirname(outputPath), { recursive: true });
496
+ await writeFile(outputPath, code, 'utf8');
497
+ result.jsonschema = { path: outputPath, code };
498
+ }
499
+ return result;
500
+ }
@@ -0,0 +1,47 @@
1
+ import type { WideEventBase, EventPartial } from '../index.js';
2
+ import type { LogCollectorClient, FlushOptions } from './index.js';
3
+ /** Type alias for partial values (singular or array) */
4
+ type PartialValue = EventPartial<string> | EventPartial<string>[];
5
+ /**
6
+ * Options for BetterStackCollector
7
+ */
8
+ export interface BetterStackCollectorOptions {
9
+ /** BetterStack source token */
10
+ sourceToken: string;
11
+ /** Ingesting host (default: "in.logs.betterstack.com") */
12
+ host?: string;
13
+ /** Number of events to buffer before flushing (default: 10) */
14
+ bufferSize?: number;
15
+ /** Maximum time in ms to wait before flushing buffer (default: 5000) */
16
+ flushIntervalMs?: number;
17
+ /** Custom fetch implementation (for testing or custom environments) */
18
+ fetch?: typeof fetch;
19
+ }
20
+ /**
21
+ * Collector that sends events to BetterStack Logs via HTTP API
22
+ * https://betterstack.com/docs/logs/ingesting-data/http/logs/
23
+ */
24
+ export declare class BetterStackCollector implements LogCollectorClient {
25
+ private buffer;
26
+ private bufferSize;
27
+ private flushIntervalMs;
28
+ private flushTimer;
29
+ private sourceToken;
30
+ private host;
31
+ private fetchFn;
32
+ constructor(options: BetterStackCollectorOptions);
33
+ flush(event: WideEventBase, partials: Map<string, PartialValue>, _options: FlushOptions): Promise<void>;
34
+ /**
35
+ * Flush the buffer to BetterStack
36
+ */
37
+ flushBuffer(): Promise<void>;
38
+ /**
39
+ * Force flush any remaining buffered events (call on shutdown)
40
+ */
41
+ close(): Promise<void>;
42
+ }
43
+ /**
44
+ * Create a collector that sends events to BetterStack Logs.
45
+ */
46
+ export declare function betterStackCollector(options: BetterStackCollectorOptions): BetterStackCollector;
47
+ export {};
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Collector that sends events to BetterStack Logs via HTTP API
3
+ * https://betterstack.com/docs/logs/ingesting-data/http/logs/
4
+ */
5
+ export class BetterStackCollector {
6
+ buffer = [];
7
+ bufferSize;
8
+ flushIntervalMs;
9
+ flushTimer = null;
10
+ sourceToken;
11
+ host;
12
+ fetchFn;
13
+ constructor(options) {
14
+ this.sourceToken = options.sourceToken;
15
+ this.host = options.host ?? 'in.logs.betterstack.com';
16
+ this.bufferSize = options.bufferSize ?? 10;
17
+ this.flushIntervalMs = options.flushIntervalMs ?? 5000;
18
+ this.fetchFn = options.fetch ?? fetch;
19
+ }
20
+ async flush(event, partials, _options) {
21
+ const partialsObj = {};
22
+ for (const [key, value] of partials) {
23
+ partialsObj[key] = value;
24
+ }
25
+ const logEntry = {
26
+ dt: new Date().toISOString(),
27
+ ...event,
28
+ ...partialsObj,
29
+ };
30
+ this.buffer.push(logEntry);
31
+ // Start flush timer if not already running
32
+ if (!this.flushTimer) {
33
+ this.flushTimer = setTimeout(() => this.flushBuffer(), this.flushIntervalMs);
34
+ }
35
+ // Flush immediately if buffer is full
36
+ if (this.buffer.length >= this.bufferSize) {
37
+ await this.flushBuffer();
38
+ }
39
+ }
40
+ /**
41
+ * Flush the buffer to BetterStack
42
+ */
43
+ async flushBuffer() {
44
+ if (this.flushTimer) {
45
+ clearTimeout(this.flushTimer);
46
+ this.flushTimer = null;
47
+ }
48
+ if (this.buffer.length === 0) {
49
+ return;
50
+ }
51
+ const batch = this.buffer;
52
+ this.buffer = [];
53
+ await this.fetchFn(`https://${this.host}`, {
54
+ method: 'POST',
55
+ headers: {
56
+ 'Content-Type': 'application/json',
57
+ Authorization: `Bearer ${this.sourceToken}`,
58
+ },
59
+ body: JSON.stringify(batch),
60
+ });
61
+ }
62
+ /**
63
+ * Force flush any remaining buffered events (call on shutdown)
64
+ */
65
+ async close() {
66
+ await this.flushBuffer();
67
+ }
68
+ }
69
+ /**
70
+ * Create a collector that sends events to BetterStack Logs.
71
+ */
72
+ export function betterStackCollector(options) {
73
+ return new BetterStackCollector(options);
74
+ }
@@ -0,0 +1 @@
1
+ export { CompositeCollector, compositeCollector } from './index.js';
@@ -0,0 +1 @@
1
+ export { CompositeCollector, compositeCollector } from './index.js';
@@ -0,0 +1 @@
1
+ export { FileCollector, fileCollector, type FileCollectorOptions, type FileSystem, } from './index.js';
@@ -0,0 +1 @@
1
+ export { FileCollector, fileCollector, } from './index.js';
@@ -0,0 +1 @@
1
+ export { FilteredCollector, filteredCollector, type EventFilter } from './index.js';
@@ -0,0 +1 @@
1
+ export { FilteredCollector, filteredCollector } from './index.js';