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.
- package/README.md +204 -0
- package/dist/codegen.d.ts +43 -0
- package/dist/codegen.js +500 -0
- package/dist/collectors/betterstack.d.ts +47 -0
- package/dist/collectors/betterstack.js +74 -0
- package/dist/collectors/composite.d.ts +1 -0
- package/dist/collectors/composite.js +1 -0
- package/dist/collectors/file.d.ts +1 -0
- package/dist/collectors/file.js +1 -0
- package/dist/collectors/filtered.d.ts +1 -0
- package/dist/collectors/filtered.js +1 -0
- package/dist/collectors/index.d.ts +102 -0
- package/dist/collectors/index.js +127 -0
- package/dist/collectors/sentry.d.ts +46 -0
- package/dist/collectors/sentry.js +45 -0
- package/dist/collectors/stdio.d.ts +1 -0
- package/dist/collectors/stdio.js +1 -0
- package/dist/generated/partials.d.ts +40 -0
- package/dist/generated/partials.js +3 -0
- package/dist/index.d.ts +199 -0
- package/dist/index.js +354 -0
- package/dist/originator/index.d.ts +150 -0
- package/dist/originator/index.js +217 -0
- package/dist/partials.d.ts +154 -0
- package/dist/partials.js +52 -0
- package/dist/registry.d.ts +89 -0
- package/dist/registry.js +44 -0
- package/dist/wevt-0.0.1-py3-none-any.whl +0 -0
- package/dist/wevt-0.0.1.tar.gz +0 -0
- package/dist/wevt-0.0.2-py3-none-any.whl +0 -0
- package/dist/wevt-0.0.2.tar.gz +0 -0
- package/package.json +101 -0
package/dist/codegen.js
ADDED
|
@@ -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';
|