@synode/core 1.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 +35 -0
- package/dist/engine-CgLY6SKJ.cjs +593 -0
- package/dist/engine-CgLY6SKJ.cjs.map +1 -0
- package/dist/engine-SRByMZvP.mjs +515 -0
- package/dist/engine-SRByMZvP.mjs.map +1 -0
- package/dist/execution/worker.cjs +125 -0
- package/dist/execution/worker.cjs.map +1 -0
- package/dist/execution/worker.d.cts +1 -0
- package/dist/execution/worker.d.mts +1 -0
- package/dist/execution/worker.mjs +125 -0
- package/dist/execution/worker.mjs.map +1 -0
- package/dist/index.cjs +1163 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1142 -0
- package/dist/index.d.mts +1142 -0
- package/dist/index.mjs +1093 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["payload: Record<string, unknown>","result: SerializedDataset[]","errors: Error[]","workers: Worker[]","reason: unknown","mode: EventValidationMode","context: SynodeContext","adapter: OutputAdapter","lanePromises: Promise<void>[]","allEvents: Event[]","callback: (event: Event) => void | Promise<void>","csvLines: string[]","rows: DatasetRow[]","row: DatasetRow","lines: string[]","result: string[]"],"sources":["../src/generators/builder.ts","../src/generators/fields.ts","../src/execution/pool.ts","../src/adapters/console.ts","../src/monitoring/telemetry.ts","../src/monitoring/event-validation.ts","../src/execution/runner.ts","../src/monitoring/validation.ts","../src/adapters/memory.ts","../src/adapters/callback.ts","../src/dataset-io.ts"],"sourcesContent":["import { Journey, Adventure, Action, ActionDefinition, Context, Event } from '../types.js';\n\n/**\n * Defines a new journey.\n * @param config The journey configuration.\n * @returns The configured journey object.\n * @see {@link Journey}\n */\nexport function defineJourney(config: Journey): Journey {\n return config;\n}\n\n/**\n * Defines a new adventure.\n * @param config The adventure configuration.\n * @returns The configured adventure object.\n * @see {@link Adventure}\n */\nexport function defineAdventure(config: Adventure): Adventure {\n return config;\n}\n\n/**\n * Defines a new action.\n * @param config The action configuration.\n * @returns The configured action object.\n * @see {@link Action}\n * @see {@link ActionDefinition}\n */\nexport function defineAction(config: ActionDefinition): Action {\n // If a custom handler is provided, use it directly\n if (config.handler) {\n return {\n id: config.id,\n name: config.name,\n handler: config.handler,\n timeSpan: config.timeSpan,\n bounceChance: config.bounceChance,\n };\n }\n\n // Otherwise, generate a handler from the fields definition\n // This creates a single event from the fields\n return {\n id: config.id,\n name: config.name,\n timeSpan: config.timeSpan,\n bounceChance: config.bounceChance,\n handler: async (context: Context): Promise<Event[]> => {\n const payload: Record<string, unknown> = {};\n\n if (config.fields) {\n for (const [key, generator] of Object.entries(config.fields)) {\n if (typeof generator === 'function') {\n // Execute the generator function with context and current payload\n const genFn = generator as (\n context: Context,\n payload: Record<string, unknown>,\n ) => unknown;\n payload[key] = await genFn(context, payload);\n } else {\n // Use static value\n payload[key] = generator;\n }\n }\n }\n\n // Return as array with single event\n return [\n {\n id: context.generateId('event'),\n userId: context.userId,\n sessionId: context.sessionId,\n name: config.name,\n timestamp: context.now(),\n payload,\n },\n ];\n },\n };\n}\n","import { Faker } from '@faker-js/faker';\nimport { FieldGenerator } from '../types.js';\n\n/**\n * Returns a value generated by Faker.js using the context's locale.\n * @param generator Function that takes a Faker instance and returns a value.\n */\nexport function fake<T>(generator: (faker: Faker) => T): FieldGenerator<T> {\n return (context) => {\n return generator(context.faker);\n };\n}\n\n/**\n * Returns one of the provided options randomly.\n */\nexport function oneOf<T>(options: T[]): FieldGenerator<T> {\n return () => {\n const index = Math.floor(Math.random() * options.length);\n return options[index];\n };\n}\n\n/**\n * Returns true with the given probability (0-1).\n */\nexport function chance(probability: number): FieldGenerator<boolean> {\n return () => {\n return Math.random() < probability;\n };\n}\n\n/**\n * Returns a value based on weighted probabilities.\n * @param options Map of value to weight (weights should sum to 1, but will be normalized if not)\n */\nexport function weighted<T extends string>(options: Record<T, number>): FieldGenerator<T> {\n return () => {\n const entries = Object.entries(options) as [T, number][];\n const totalWeight = entries.reduce((sum, [, weight]) => sum + weight, 0);\n let random = Math.random() * totalWeight;\n\n for (const [value, weight] of entries) {\n random -= weight;\n if (random <= 0) {\n return value;\n }\n }\n\n // Fallback to last item (should rarely happen due to floating point)\n return entries[entries.length - 1][0];\n };\n}\n","import { Worker } from 'node:worker_threads';\nimport { join, extname } from 'node:path';\nimport { access } from 'node:fs/promises';\nimport { accessSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport type { Dataset } from '../types.js';\nimport type { OutputAdapter } from '../types.js';\nimport type { TelemetryCollector } from '../monitoring/telemetry.js';\nimport type { WorkerInit, WorkerMessage, SerializedDataset } from './worker-types.js';\n\nconst __dirname = fileURLToPath(new URL('.', import.meta.url));\n\n/**\n * Configuration options for the worker pool.\n */\nexport interface WorkerPoolOptions {\n /** Absolute path to the user's journey module (TS or JS). */\n workerModule: string;\n /** Total number of users to simulate across all workers. */\n userCount: number;\n /** Number of worker threads to spawn. */\n workerCount: number;\n /** Output adapter to write events to. */\n adapter: OutputAdapter;\n /** Optional telemetry collector for recording progress. */\n telemetry?: TelemetryCollector | null;\n /** ISO 8601 start date for randomizing user start times. */\n startDate?: string;\n /** ISO 8601 end date for randomizing user start times. */\n endDate?: string;\n /** Pre-generated datasets to transfer to workers. */\n preGeneratedDatasets?: Map<string, Dataset>;\n}\n\n/**\n * Resolves the worker script path. Uses the compiled `.mjs` in dist when available,\n * falling back to the TypeScript source for development with tsx.\n *\n * Checks two locations for the compiled worker:\n * 1. Same directory as pool (works when running from dist/)\n * 2. Equivalent dist/ path (works when running from src/ during dev/test)\n *\n * @returns Absolute path to the worker script\n */\nfunction resolveWorkerScript(): string {\n // Check same directory first (running from dist/)\n const localMjs = join(__dirname, 'worker.mjs');\n try {\n accessSync(localMjs);\n return localMjs;\n } catch {\n // noop\n }\n\n // Check dist/ equivalent when running from src/ (dev/test with vitest)\n const distMjs = __dirname.replace(/src[\\\\/]/, 'dist/') + 'worker.mjs';\n try {\n accessSync(distMjs);\n return distMjs;\n } catch {\n // noop\n }\n\n // Fall back to TypeScript source (requires tsx --import)\n return join(__dirname, 'worker.ts');\n}\n\n/**\n * Serializes a Map of datasets into plain objects for structured clone transfer.\n *\n * @param datasets - Map of dataset ID to Dataset\n * @returns Array of serialized datasets\n */\nfunction serializeDatasets(datasets?: Map<string, Dataset>): SerializedDataset[] {\n if (!datasets || datasets.size === 0) {\n return [];\n }\n const result: SerializedDataset[] = [];\n for (const dataset of datasets.values()) {\n result.push({\n id: dataset.id,\n name: dataset.name,\n rows: dataset.rows,\n });\n }\n return result;\n}\n\n/**\n * Rehydrates a Date value that may have been stringified during structured clone.\n * Structured clone preserves Dates natively, but this provides a safety net\n * for edge cases where timestamps arrive as ISO strings.\n *\n * @param value - A Date object or ISO string timestamp\n * @returns A proper Date instance\n */\nfunction rehydrateDate(value: Date | string): Date {\n if (value instanceof Date) {\n return value;\n }\n return new Date(value);\n}\n\n/**\n * Manages a pool of worker threads for parallel user generation.\n * Distributes users evenly across workers, collects events via message passing,\n * and writes them through the configured output adapter.\n */\nexport class WorkerPool {\n private readonly options: WorkerPoolOptions;\n\n /**\n * @param options - Pool configuration including module path, user count, and adapter\n */\n constructor(options: WorkerPoolOptions) {\n this.options = options;\n }\n\n /**\n * Validates the worker module path exists on disk.\n *\n * @throws Error if the module path does not exist\n */\n async validateModule(): Promise<void> {\n try {\n await access(this.options.workerModule);\n } catch {\n throw new Error(`Worker module not found: ${this.options.workerModule}`);\n }\n }\n\n /**\n * Spawns all worker threads, distributes user ranges, collects events,\n * and waits for completion.\n *\n * @throws Error if any worker fails with an unrecoverable error\n */\n async run(): Promise<void> {\n await this.validateModule();\n\n const { workerModule, userCount, workerCount, adapter, telemetry, startDate, endDate } =\n this.options;\n\n const serializedDatasets = serializeDatasets(this.options.preGeneratedDatasets);\n const usersPerWorker = Math.ceil(userCount / workerCount);\n const workerScript = resolveWorkerScript();\n const isTypeScript = extname(workerScript) === '.ts';\n\n const errors: Error[] = [];\n const workers: Worker[] = [];\n\n const workerPromises = Array.from({ length: workerCount }, (_, i) => {\n const userStart = i * usersPerWorker;\n const userEnd = Math.min(userStart + usersPerWorker, userCount);\n\n // Skip empty ranges when users don't divide evenly\n if (userStart >= userCount) {\n return Promise.resolve();\n }\n\n const workerInit: WorkerInit = {\n workerModule,\n userStart,\n userEnd,\n startDate,\n endDate,\n serializedDatasets,\n };\n\n const execArgv = isTypeScript\n ? ['--import', 'tsx']\n : extname(workerModule) === '.ts'\n ? ['--experimental-strip-types']\n : [];\n\n const worker = new Worker(workerScript, {\n workerData: workerInit,\n execArgv,\n });\n workers.push(worker);\n\n return this.listenToWorker(worker, adapter, telemetry ?? null, errors);\n });\n\n const results = await Promise.allSettled(workerPromises);\n\n // Collect rejections from the allSettled results\n for (const result of results) {\n if (result.status === 'rejected') {\n const reason: unknown = result.reason;\n errors.push(reason instanceof Error ? reason : new Error(String(reason)));\n }\n }\n\n if (errors.length > 0) {\n const combined = errors.map((e) => e.message).join('\\n');\n throw new Error(`Worker pool failed with ${String(errors.length)} error(s):\\n${combined}`);\n }\n }\n\n /**\n * Listens to a single worker's messages and routes them to the adapter/telemetry.\n *\n * @param worker - The Worker thread to listen to\n * @param adapter - Output adapter for writing events\n * @param telemetry - Optional telemetry collector\n * @param errors - Mutable array to collect errors from failed workers\n * @returns Promise that resolves when the worker sends 'done' or rejects on error\n */\n private listenToWorker(\n worker: Worker,\n adapter: OutputAdapter,\n telemetry: TelemetryCollector | null,\n errors: Error[],\n ): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n worker.on('message', (msg: WorkerMessage) => {\n switch (msg.type) {\n case 'events':\n // Write each event through the adapter, rehydrating dates if needed\n for (const event of msg.events) {\n event.timestamp = rehydrateDate(event.timestamp);\n // Fire-and-forget: adapter.write may be sync or async\n void Promise.resolve(adapter.write(event)).catch((err: unknown) => {\n const writeError = new Error(\n `Adapter write failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n errors.push(writeError);\n });\n }\n if (telemetry) {\n let remaining = msg.events.length;\n while (remaining-- > 0) {\n telemetry.recordEvent();\n }\n }\n break;\n\n case 'user-started':\n if (telemetry) {\n telemetry.recordUserStarted();\n }\n break;\n\n case 'user-completed':\n if (telemetry) {\n telemetry.recordUserCompleted();\n }\n break;\n\n case 'error': {\n const workerError = new Error(msg.message);\n if (msg.stack) {\n workerError.stack = msg.stack;\n }\n errors.push(workerError);\n void worker.terminate();\n reject(workerError);\n break;\n }\n\n case 'done':\n resolve();\n break;\n }\n });\n\n worker.on('error', (err: Error) => {\n errors.push(err);\n reject(err);\n });\n\n worker.on('exit', (code: number) => {\n if (code !== 0) {\n const exitError = new Error(`Worker exited with code ${String(code)}`);\n errors.push(exitError);\n reject(exitError);\n }\n });\n });\n }\n}\n","import { Event, OutputAdapter } from '../types.js';\n\n/**\n * Adapter that writes events to the console as pretty-printed JSON.\n *\n * @example\n * ```ts\n * await generate(journey, { users: 10, adapter: new ConsoleAdapter() });\n * ```\n */\nexport class ConsoleAdapter implements OutputAdapter {\n /** @inheritdoc */\n write(event: Event): void {\n // eslint-disable-next-line no-console\n console.log(JSON.stringify(event, null, 2));\n }\n}\n","import { writeFile } from 'node:fs/promises';\n\n/**\n * Telemetry data point captured every second during generation.\n */\nexport interface TelemetrySnapshot {\n /**\n * Timestamp of this snapshot in ISO format.\n */\n timestamp: string;\n /**\n * Elapsed time since generation started in milliseconds.\n */\n elapsedMs: number;\n /**\n * Number of events generated in this second.\n */\n eventsPerSecond: number;\n /**\n * Total events generated so far.\n */\n totalEvents: number;\n /**\n * Number of users currently being processed across all lanes.\n */\n activeUsers: number;\n /**\n * Number of users completed so far.\n */\n completedUsers: number;\n /**\n * Number of parallel lanes in use.\n */\n lanes: number;\n}\n\n/**\n * A single validation error captured during event generation.\n */\nexport interface TelemetryValidationError {\n eventName: string;\n path: string;\n message: string;\n}\n\n/**\n * Complete telemetry report for a generation run.\n */\nexport interface TelemetryReport {\n /**\n * When the generation started.\n */\n startTime: string;\n /**\n * When the generation ended.\n */\n endTime: string;\n /**\n * Total duration in milliseconds.\n */\n durationMs: number;\n /**\n * Total number of users processed.\n */\n totalUsers: number;\n /**\n * Total number of events generated.\n */\n totalEvents: number;\n /**\n * Number of parallel lanes used.\n */\n lanes: number;\n /**\n * Average events per second across the entire run.\n */\n averageEventsPerSecond: number;\n /**\n * Number of users currently active (at report time).\n */\n activeUsers: number;\n /**\n * Number of users completed (at report time).\n */\n completedUsers: number;\n /**\n * Total number of events that were validated.\n */\n eventsValidated: number;\n /**\n * Total number of events that passed validation.\n */\n eventsValid: number;\n /**\n * Total number of events that failed validation.\n */\n eventsInvalid: number;\n /**\n * Validation errors collected during the run (capped at 50).\n */\n validationErrors: TelemetryValidationError[];\n /**\n * Per-second telemetry snapshots.\n */\n snapshots: TelemetrySnapshot[];\n}\n\n/**\n * Collects telemetry data during generation runs.\n */\nexport class TelemetryCollector {\n private startTime: Date;\n private snapshots: TelemetrySnapshot[] = [];\n private intervalHandle: NodeJS.Timeout | null = null;\n private currentSecondEvents = 0;\n private totalEvents = 0;\n private activeUsers = 0;\n private completedUsers = 0;\n private lanes: number;\n private eventsValidated = 0;\n private eventsValid = 0;\n private eventsInvalid = 0;\n private validationErrors: TelemetryValidationError[] = [];\n\n constructor(lanes: number) {\n this.startTime = new Date();\n this.lanes = lanes;\n }\n\n /**\n * Start collecting telemetry data every second.\n */\n start(): void {\n this.intervalHandle = setInterval(() => {\n this.captureSnapshot();\n }, 1000);\n }\n\n /**\n * Stop collecting telemetry data.\n */\n stop(): void {\n if (this.intervalHandle) {\n clearInterval(this.intervalHandle);\n this.intervalHandle = null;\n }\n // Capture final snapshot\n this.captureSnapshot();\n }\n\n /**\n * Record that an event was generated.\n */\n recordEvent(): void {\n this.currentSecondEvents++;\n this.totalEvents++;\n }\n\n /**\n * Record that a user started processing.\n */\n recordUserStarted(): void {\n this.activeUsers++;\n }\n\n /**\n * Record that a user completed processing.\n */\n recordUserCompleted(): void {\n this.activeUsers--;\n this.completedUsers++;\n }\n\n /**\n * Merge a validation summary from a lane or journey into the collector totals.\n *\n * @param summary - Aggregated validation counts and errors to record.\n */\n recordValidationSummary(summary: {\n eventsValidated: number;\n eventsValid: number;\n eventsInvalid: number;\n validationErrors: { eventName: string; path: string; message: string }[];\n }): void {\n this.eventsValidated += summary.eventsValidated;\n this.eventsValid += summary.eventsValid;\n this.eventsInvalid += summary.eventsInvalid;\n for (const err of summary.validationErrors) {\n if (this.validationErrors.length < 50) {\n this.validationErrors.push(err);\n }\n }\n }\n\n /**\n * Generate the final telemetry report.\n */\n getReport(): TelemetryReport {\n const endTime = new Date();\n const durationMs = endTime.getTime() - this.startTime.getTime();\n const averageEventsPerSecond =\n durationMs > 0 ? (this.totalEvents / durationMs) * 1000 : this.totalEvents;\n\n return {\n startTime: this.startTime.toISOString(),\n endTime: endTime.toISOString(),\n durationMs,\n totalUsers: this.completedUsers,\n totalEvents: this.totalEvents,\n lanes: this.lanes,\n averageEventsPerSecond: Number(averageEventsPerSecond.toFixed(2)),\n snapshots: this.snapshots,\n activeUsers: this.activeUsers,\n completedUsers: this.completedUsers,\n eventsValidated: this.eventsValidated,\n eventsValid: this.eventsValid,\n eventsInvalid: this.eventsInvalid,\n validationErrors: this.validationErrors,\n };\n }\n\n /**\n * Save the telemetry report to a JSON file.\n */\n async saveReport(filePath: string): Promise<void> {\n const report = this.getReport();\n await writeFile(filePath, JSON.stringify(report, null, 2), 'utf-8');\n }\n\n private captureSnapshot(): void {\n const now = new Date();\n const elapsedMs = now.getTime() - this.startTime.getTime();\n\n this.snapshots.push({\n timestamp: now.toISOString(),\n elapsedMs,\n eventsPerSecond: this.currentSecondEvents,\n totalEvents: this.totalEvents,\n activeUsers: this.activeUsers,\n completedUsers: this.completedUsers,\n lanes: this.lanes,\n });\n\n // Reset the per-second counter\n this.currentSecondEvents = 0;\n }\n}\n","import { z } from 'zod';\nimport type { Event, EventSchemaConfig, EventValidationMode } from '../types.js';\n\nconst MAX_STORED_ERRORS = 50;\n\n/**\n * A single validation issue from a failed schema check.\n */\nexport interface ValidationIssue {\n path: (string | number)[];\n message: string;\n code: string;\n}\n\n/**\n * Options for constructing a SynodeValidationError.\n */\nexport interface SynodeValidationErrorOptions {\n message: string;\n event: Event;\n issues: ValidationIssue[];\n}\n\n/**\n * Error thrown when an event fails schema validation in strict mode.\n */\nexport class SynodeValidationError extends Error {\n readonly event: Event;\n readonly issues: ValidationIssue[];\n\n constructor(options: SynodeValidationErrorOptions) {\n super(options.message);\n this.name = 'SynodeValidationError';\n this.event = options.event;\n this.issues = options.issues;\n }\n}\n\n/**\n * Aggregate summary of event validation results.\n */\nexport interface ValidationSummary {\n eventsValidated: number;\n eventsValid: number;\n eventsInvalid: number;\n validationErrors: { eventName: string; path: string; message: string }[];\n}\n\n/**\n * Wraps `z.object()` for defining event payload schemas.\n *\n * @param shape - Zod raw shape describing the expected payload fields\n * @returns A ZodObject schema\n *\n * @example\n * ```typescript\n * const pageViewSchema = defineEventSchema({\n * url: z.string().url(),\n * referrer: z.string().optional(),\n * });\n * ```\n */\nexport function defineEventSchema<T extends z.ZodRawShape>(shape: T): z.ZodObject<T> {\n return z.object(shape);\n}\n\n/**\n * Creates a fresh zeroed validation summary.\n *\n * @returns An empty ValidationSummary ready for accumulation\n */\nexport function createValidationSummary(): ValidationSummary {\n return {\n eventsValidated: 0,\n eventsValid: 0,\n eventsInvalid: 0,\n validationErrors: [],\n };\n}\n\n/**\n * Converts Zod v4 issues to ValidationIssue instances.\n */\nfunction toValidationIssues(zodIssues: z.core.$ZodIssue[]): ValidationIssue[] {\n return zodIssues.map((issue) => ({\n path: issue.path.map((segment) => (typeof segment === 'symbol' ? String(segment) : segment)),\n message: issue.message,\n code: issue.code,\n }));\n}\n\n/**\n * Resolves the correct schema for an event based on the config.\n * Returns undefined if no schema applies to this event.\n */\nfunction resolveSchema(event: Event, config: EventSchemaConfig): z.ZodType | undefined {\n if (config.schema instanceof z.ZodType) {\n return config.schema;\n }\n\n return config.schema[event.name];\n}\n\n/**\n * Records a validation failure in the summary, respecting the MAX_STORED_ERRORS cap.\n */\nfunction recordFailure(summary: ValidationSummary, event: Event, issues: ValidationIssue[]): void {\n summary.eventsInvalid++;\n for (const issue of issues) {\n if (summary.validationErrors.length < MAX_STORED_ERRORS) {\n summary.validationErrors.push({\n eventName: event.name,\n path: issue.path.join('.'),\n message: issue.message,\n });\n }\n }\n}\n\n/**\n * Validates an event against its configured schema.\n *\n * Behavior depends on the configured mode:\n * - `strict` (default): throws {@link SynodeValidationError} on first failure\n * - `warn`: returns the event but records failure in the summary\n * - `skip`: returns `undefined` (event is dropped) and records failure in the summary\n *\n * Events with no matching schema in a per-name map are passed through without validation.\n *\n * @param event - The event to validate\n * @param config - Schema and mode configuration\n * @param summary - Mutable summary accumulating validation statistics\n * @returns The event if it passes or is kept (warn mode), or undefined if skipped\n * @throws SynodeValidationError in strict mode when validation fails\n */\nexport function validateEvent(\n event: Event,\n config: EventSchemaConfig,\n summary: ValidationSummary,\n): Event | undefined {\n const schema = resolveSchema(event, config);\n\n if (!schema) {\n return event;\n }\n\n summary.eventsValidated++;\n\n const result = schema.safeParse(event.payload);\n\n if (result.success) {\n summary.eventsValid++;\n return event;\n }\n\n const issues = toValidationIssues(result.error.issues);\n const mode: EventValidationMode = config.mode ?? 'strict';\n\n if (mode === 'strict') {\n recordFailure(summary, event, issues);\n throw new SynodeValidationError({\n message: `Event '${event.name}' failed schema validation: ${issues.map((i) => i.message).join('; ')}`,\n event,\n issues,\n });\n }\n\n if (mode === 'warn') {\n recordFailure(summary, event, issues);\n return event;\n }\n\n // skip mode\n recordFailure(summary, event, issues);\n return undefined;\n}\n","import { cpus } from 'node:os';\nimport { Journey, Dataset, DatasetDefinition } from '../types.js';\nimport type { EventSchemaConfig } from '../types.js';\nimport { Engine } from './engine.js';\nimport { WorkerPool } from './pool.js';\nimport { OutputAdapter } from '../types.js';\nimport { ConsoleAdapter } from '../adapters/console.js';\nimport { PersonaDefinition, generatePersona } from '../generators/persona.js';\nimport { generateDataset } from '../generators/dataset.js';\nimport { SynodeContext } from '../state/context.js';\nimport { TelemetryCollector } from '../monitoring/telemetry.js';\nimport {\n validateEvent,\n createValidationSummary,\n type ValidationSummary,\n} from '../monitoring/event-validation.js';\nimport { SynodeError } from '../errors.js';\n\nexport interface RunOptions {\n /**\n * Total number of users to simulate.\n */\n users: number;\n\n /**\n * Persona definition to use for generating users.\n * If provided, context will be initialized with persona attributes.\n */\n persona?: PersonaDefinition;\n\n /**\n * Optional dataset definitions to pre-generate before journey execution.\n * Datasets can be referenced within journey actions using ctx.dataset('id').\n */\n datasets?: DatasetDefinition[];\n\n /**\n * Number of parallel lanes to use for generation.\n * Lanes process users concurrently using async execution.\n * @default 1\n */\n lanes?: number;\n\n /**\n * Output adapter to write events to.\n * @default ConsoleAdapter\n */\n adapter?: OutputAdapter;\n\n /**\n * Enable debug mode with telemetry collection.\n * When enabled, collects detailed metrics about the generation run.\n * @default false\n */\n debug?: boolean;\n\n /**\n * File path to save telemetry data when debug mode is enabled.\n * @default './telemetry-report.json'\n */\n telemetryPath?: string;\n\n /**\n * Pre-loaded datasets to register with each user's context.\n * Unlike `datasets` (which generates from definitions), these are already populated.\n * Useful for datasets imported from external sources like BigQuery.\n */\n preloadedDatasets?: Dataset[];\n\n /**\n * Start of the date range for user start times.\n * Must be provided together with endDate.\n */\n startDate?: Date;\n\n /**\n * End of the date range for user start times.\n * Must be provided together with startDate.\n */\n endDate?: Date;\n\n /**\n * Event schema validation configuration.\n * When provided, each event is validated against the schema before adapter.write().\n */\n eventSchema?: EventSchemaConfig;\n\n /**\n * Path to a module that exports journey definitions for worker thread parallelism.\n * When provided, generation runs in worker threads instead of Promise.all lanes.\n * The module must export: { journeys: Journey[], persona?, datasets?, preloadedDatasets? }\n */\n workerModule?: string;\n\n /**\n * Number of worker threads to spawn. Default: number of CPU cores.\n * Only used when workerModule is set.\n */\n workers?: number;\n}\n\n/**\n * Returns a random Date between start (inclusive) and end (inclusive).\n */\nfunction randomDateInRange(start: Date, end: Date): Date {\n const startMs = start.getTime();\n const endMs = end.getTime();\n return new Date(startMs + Math.random() * (endMs - startMs));\n}\n\n/**\n * Pre-generates datasets from definitions and merges with preloaded datasets.\n *\n * @param datasets - Optional dataset definitions to generate\n * @param preloaded - Optional pre-populated datasets to include\n * @returns Map of dataset ID to generated/preloaded Dataset\n */\nasync function prepareDatasets(\n datasets?: DatasetDefinition[],\n preloaded?: Dataset[],\n): Promise<Map<string, Dataset>> {\n const result = new Map<string, Dataset>();\n\n if (datasets && datasets.length > 0) {\n const tempContext = new SynodeContext();\n for (const datasetDef of datasets) {\n const dataset = await generateDataset(datasetDef, tempContext);\n result.set(dataset.id, dataset);\n }\n }\n\n if (preloaded) {\n for (const dataset of preloaded) {\n result.set(dataset.id, dataset);\n }\n }\n\n return result;\n}\n\n/**\n * Creates a SynodeContext for a single user, hydrating persona attributes\n * and registering pre-generated datasets.\n *\n * @param persona - Optional persona definition for generating user attributes\n * @param preGeneratedDatasets - Pre-generated datasets to register with the context\n * @param startDate - Optional start of date range for random start time\n * @param endDate - Optional end of date range for random start time\n * @returns A fully initialized SynodeContext\n */\nasync function createUserContext(\n persona: PersonaDefinition | undefined,\n preGeneratedDatasets: Map<string, Dataset>,\n startDate?: Date,\n endDate?: Date,\n): Promise<SynodeContext> {\n const userStartTime = startDate && endDate ? randomDateInRange(startDate, endDate) : new Date();\n\n let context: SynodeContext;\n\n if (persona) {\n const tempContext = new SynodeContext();\n const personaData = await generatePersona(persona, tempContext);\n const locale =\n typeof personaData.attributes.locale === 'string' ? personaData.attributes.locale : 'en';\n context = new SynodeContext(userStartTime, undefined, locale);\n\n for (const [key, value] of Object.entries(personaData.attributes)) {\n context.set(key, value);\n }\n } else {\n context = new SynodeContext(userStartTime);\n }\n\n for (const dataset of preGeneratedDatasets.values()) {\n context.registerDataset(dataset);\n }\n\n return context;\n}\n\n/**\n * Runs all journeys on a context, writing events to the adapter.\n * Wraps adapter.write() failures in SynodeError with code ADAPTER_WRITE_ERROR.\n *\n * @param journeys - Journeys to execute\n * @param context - The user's execution context\n * @param adapter - Output adapter to write events to\n * @param telemetry - Optional telemetry collector for recording events\n * @param eventSchema - Optional event schema validation configuration\n * @param summary - Optional mutable validation summary for accumulating results\n */\nasync function processUser(\n journeys: Journey[],\n context: SynodeContext,\n adapter: OutputAdapter,\n telemetry: TelemetryCollector | null,\n eventSchema?: EventSchemaConfig,\n summary?: ValidationSummary,\n): Promise<void> {\n for (const journey of journeys) {\n const engine = new Engine(journey);\n for await (const event of engine.run(context)) {\n if (eventSchema && summary) {\n const validated = validateEvent(event, eventSchema, summary);\n if (!validated) continue; // skip mode dropped the event\n }\n try {\n await adapter.write(event);\n } catch (error) {\n throw new SynodeError({\n code: 'ADAPTER_WRITE_ERROR',\n message: `Adapter write failed for event '${event.name}': ${error instanceof Error ? error.message : String(error)}`,\n path: [journey.id],\n suggestion: 'Check the output adapter for write errors or capacity issues',\n cause: error,\n });\n }\n if (telemetry) {\n telemetry.recordEvent();\n }\n }\n }\n}\n\n/**\n * Generates synthetic data based on the provided journey configuration.\n */\nexport async function generate(journey: Journey | Journey[], options: RunOptions): Promise<void> {\n const journeys = Array.isArray(journey) ? journey : [journey];\n // Validate date range options\n if (options.startDate && !options.endDate) {\n throw new Error('startDate requires endDate to be provided');\n }\n if (options.endDate && !options.startDate) {\n throw new Error('endDate requires startDate to be provided');\n }\n if (\n options.startDate &&\n options.endDate &&\n options.startDate.getTime() >= options.endDate.getTime()\n ) {\n throw new Error('startDate must be before endDate');\n }\n\n if (!Number.isFinite(options.users) || options.users < 0 || options.users > 10_000_000) {\n throw new Error('users must be a finite number between 0 and 10,000,000');\n }\n if (\n options.lanes !== undefined &&\n (!Number.isFinite(options.lanes) || options.lanes < 1 || options.lanes > 1000)\n ) {\n throw new Error('lanes must be a finite number between 1 and 1,000');\n }\n if (options.workers !== undefined && !options.workerModule) {\n throw new Error('workers option requires workerModule to be set');\n }\n if (\n options.workers !== undefined &&\n (!Number.isFinite(options.workers) || options.workers < 1 || options.workers > 1024)\n ) {\n throw new Error('workers must be a finite number between 1 and 1,024');\n }\n\n const adapter: OutputAdapter = options.adapter ?? new ConsoleAdapter();\n const userCount = options.users;\n const lanes = options.lanes ?? 1;\n const debug = options.debug ?? false;\n const telemetryPath = options.telemetryPath ?? './telemetry-report.json';\n\n // Initialize telemetry collector if debug mode is enabled\n const telemetry = debug ? new TelemetryCollector(lanes) : null;\n if (telemetry) {\n telemetry.start();\n }\n\n // Pre-generate datasets once for all users and lanes\n const preGeneratedDatasets = await prepareDatasets(options.datasets, options.preloadedDatasets);\n\n // Initialize validation summary if event schema is configured\n const summary = options.eventSchema ? createValidationSummary() : null;\n\n // Use worker threads when workerModule is provided\n if (options.workerModule) {\n const pool = new WorkerPool({\n workerModule: options.workerModule,\n userCount,\n workerCount: options.workers ?? cpus().length,\n adapter,\n telemetry,\n startDate: options.startDate?.toISOString(),\n endDate: options.endDate?.toISOString(),\n preGeneratedDatasets,\n });\n await pool.run();\n } else if (lanes > 1) {\n await runParallel(\n journeys,\n userCount,\n lanes,\n options.persona,\n preGeneratedDatasets,\n adapter,\n telemetry,\n options.startDate,\n options.endDate,\n options.eventSchema,\n summary,\n );\n } else {\n await runSequential(\n journeys,\n userCount,\n options.persona,\n preGeneratedDatasets,\n adapter,\n telemetry,\n options.startDate,\n options.endDate,\n options.eventSchema,\n summary,\n );\n }\n\n if (summary && options.eventSchema?.mode === 'warn' && summary.eventsInvalid > 0) {\n console.error(\n `[synode] Validation: ${String(summary.eventsValid)} passed, ${String(summary.eventsInvalid)} failed out of ${String(summary.eventsValidated)} checked`,\n );\n }\n if (telemetry && summary) {\n telemetry.recordValidationSummary(summary);\n }\n\n if (adapter.close) {\n await adapter.close();\n }\n\n // Stop telemetry and save report\n if (telemetry) {\n telemetry.stop();\n await telemetry.saveReport(telemetryPath);\n }\n}\n\n/**\n * Run generation sequentially in the main thread.\n */\nasync function runSequential(\n journeys: Journey[],\n userCount: number,\n persona: PersonaDefinition | undefined,\n preGeneratedDatasets: Map<string, Dataset>,\n adapter: OutputAdapter,\n telemetry: TelemetryCollector | null,\n startDate?: Date,\n endDate?: Date,\n eventSchema?: EventSchemaConfig,\n summary?: ValidationSummary | null,\n): Promise<void> {\n for (let i = 0; i < userCount; i++) {\n if (telemetry) {\n telemetry.recordUserStarted();\n }\n\n const context = await createUserContext(persona, preGeneratedDatasets, startDate, endDate);\n await processUser(journeys, context, adapter, telemetry, eventSchema, summary ?? undefined);\n\n if (telemetry) {\n telemetry.recordUserCompleted();\n }\n }\n}\n\n/**\n * Run generation in parallel using concurrent async execution.\n * Note: This uses Promise.all for concurrent execution in the main thread.\n * For true multi-core parallelism, worker threads would require serializable\n * journey definitions (e.g., loaded from file paths rather than in-memory objects).\n */\nasync function runParallel(\n journeys: Journey[],\n userCount: number,\n lanes: number,\n persona: PersonaDefinition | undefined,\n preGeneratedDatasets: Map<string, Dataset>,\n adapter: OutputAdapter,\n telemetry: TelemetryCollector | null,\n startDate?: Date,\n endDate?: Date,\n eventSchema?: EventSchemaConfig,\n summary?: ValidationSummary | null,\n): Promise<void> {\n const usersPerLane = Math.ceil(userCount / lanes);\n const lanePromises: Promise<void>[] = [];\n\n // Create a promise for each lane\n for (let laneIndex = 0; laneIndex < lanes; laneIndex++) {\n const userStart = laneIndex * usersPerLane;\n const userEnd = Math.min(userStart + usersPerLane, userCount);\n\n if (userStart >= userCount) {\n break;\n }\n\n // Each lane processes its assigned users concurrently\n const lanePromise = (async (): Promise<void> => {\n for (let i = userStart; i < userEnd; i++) {\n if (telemetry) {\n telemetry.recordUserStarted();\n }\n\n const context = await createUserContext(persona, preGeneratedDatasets, startDate, endDate);\n await processUser(journeys, context, adapter, telemetry, eventSchema, summary ?? undefined);\n\n if (telemetry) {\n telemetry.recordUserCompleted();\n }\n }\n })();\n\n lanePromises.push(lanePromise);\n }\n\n // Wait for all lanes to complete\n await Promise.all(lanePromises);\n}\n","import { z } from 'zod';\nimport { Journey, Event, TimeSpan, SuppressionPeriod } from '../types.js';\nimport { Engine } from '../execution/engine.js';\nimport { SynodeError, buildNotFoundSuggestion } from '../errors.js';\n\n// Zod Schemas\nexport const ActionSchema = z.object({\n id: z.string().min(1).describe('Unique identifier for this action'),\n name: z\n .string()\n .min(1)\n .describe('Event name this action generates (e.g., \"product_viewed\", \"checkout_completed\")'),\n handler: z\n .function()\n .describe('Function returning Event[] or Promise<Event[]>. Receives Context as argument.'),\n});\n\nexport const AdventureSchema = z.object({\n id: z.string().min(1).describe('Unique identifier for this adventure within its journey'),\n name: z.string().min(1).describe('Human-readable adventure name (e.g., \"Browsing Session\")'),\n actions: z\n .array(ActionSchema)\n .describe('Ordered list of actions to execute in this adventure. Actions run sequentially.'),\n});\n\nexport const JourneySchema = z.object({\n id: z\n .string()\n .min(1)\n .describe('Unique identifier for this journey. Referenced by other journeys via \"requires\".'),\n name: z.string().min(1).describe('Human-readable journey name (e.g., \"First Purchase Flow\")'),\n adventures: z\n .array(AdventureSchema)\n .describe(\n 'Ordered list of adventures in this journey. Adventures run sequentially; each may bounce.',\n ),\n});\n\n/**\n * Validates that a bounce chance value is between 0 and 1 (inclusive).\n *\n * @param value - The bounce chance value to validate\n * @param path - Hierarchical path for error reporting\n * @throws SynodeError with INVALID_BOUNCE_CHANCE code if out of range\n */\nexport function validateBounceChance(value: number | undefined, path: string[]): void {\n if (value === undefined) return;\n\n if (value < 0 || value > 1) {\n throw new SynodeError({\n code: 'INVALID_BOUNCE_CHANCE',\n message: `Bounce chance must be between 0 and 1, got ${String(value)}`,\n path,\n expected: '0 <= bounceChance <= 1',\n received: String(value),\n });\n }\n}\n\n/**\n * Validates that a time span has min <= max.\n *\n * @param timeSpan - The time span to validate\n * @param path - Hierarchical path for error reporting\n * @throws SynodeError with INVALID_TIME_SPAN code if min > max\n */\nexport function validateTimeSpan(timeSpan: TimeSpan | undefined, path: string[]): void {\n if (timeSpan === undefined) return;\n\n if (timeSpan.min > timeSpan.max) {\n throw new SynodeError({\n code: 'INVALID_TIME_SPAN',\n message: `TimeSpan min (${String(timeSpan.min)}) must not exceed max (${String(timeSpan.max)})`,\n path,\n expected: 'min <= max',\n received: `min=${String(timeSpan.min)}, max=${String(timeSpan.max)}`,\n });\n }\n}\n\n/**\n * Validates that a suppression period has min <= max.\n *\n * @param period - The suppression period to validate\n * @param path - Hierarchical path for error reporting\n * @throws SynodeError with INVALID_SUPPRESSION_PERIOD code if min > max\n */\nfunction validateSuppressionPeriod(period: SuppressionPeriod | undefined, path: string[]): void {\n if (period === undefined) return;\n\n if (period.min > period.max) {\n throw new SynodeError({\n code: 'INVALID_SUPPRESSION_PERIOD',\n message: `Suppression period min (${String(period.min)}) must not exceed max (${String(period.max)})`,\n path,\n expected: 'min <= max',\n received: `min=${String(period.min)}, max=${String(period.max)}`,\n });\n }\n}\n\n/**\n * Detects circular dependencies starting from a journey using depth-first search.\n *\n * @param journeyId - The journey ID to start the search from\n * @param allJourneys - All journeys to check against\n * @throws SynodeError with CIRCULAR_DEPENDENCY code if a cycle is detected\n */\nfunction detectCircularDeps(journeyId: string, allJourneys: Journey[]): void {\n const journeyMap = new Map<string, Journey>();\n for (const j of allJourneys) {\n journeyMap.set(j.id, j);\n }\n\n const visiting = new Set<string>();\n const visited = new Set<string>();\n\n function dfs(currentId: string, chain: string[]): void {\n if (visiting.has(currentId)) {\n throw new SynodeError({\n code: 'CIRCULAR_DEPENDENCY',\n message: `Circular dependency detected: ${[...chain, currentId].join(' -> ')}`,\n path: [journeyId],\n });\n }\n\n if (visited.has(currentId)) return;\n\n visiting.add(currentId);\n const journey = journeyMap.get(currentId);\n\n if (journey?.requires) {\n for (const reqId of journey.requires) {\n dfs(reqId, [...chain, currentId]);\n }\n }\n\n visiting.delete(currentId);\n visited.add(currentId);\n }\n\n dfs(journeyId, []);\n}\n\n/**\n * Validates the journey configuration including structural checks for bounce chances,\n * time spans, suppression periods, duplicate IDs, unknown references, and circular dependencies.\n *\n * @param config - The journey configuration to validate\n * @param allJourneys - Optional list of all journeys for cross-journey validation\n * @throws ZodError if basic schema validation fails\n * @throws SynodeError for structural validation failures\n */\nexport function validateConfig(config: Journey, allJourneys?: Journey[]): void {\n JourneySchema.parse(config);\n\n const journeyPath = [config.id];\n\n // Validate journey-level bounceChance\n validateBounceChance(config.bounceChance, journeyPath);\n\n // Validate journey-level suppressionPeriod\n validateSuppressionPeriod(config.suppressionPeriod, journeyPath);\n\n // Check for duplicate Adventure IDs\n const adventureIds = new Set<string>();\n for (const adventure of config.adventures) {\n if (adventureIds.has(adventure.id)) {\n throw new SynodeError({\n code: 'DUPLICATE_ID',\n message: `Duplicate Adventure ID found: ${adventure.id}`,\n path: [...journeyPath, adventure.id],\n });\n }\n adventureIds.add(adventure.id);\n\n const adventurePath = [...journeyPath, adventure.id];\n\n // Validate adventure-level bounceChance\n validateBounceChance(adventure.bounceChance, adventurePath);\n\n // Validate adventure-level timeSpan\n validateTimeSpan(adventure.timeSpan, adventurePath);\n\n // Check for duplicate Action IDs within an adventure\n const actionIds = new Set<string>();\n for (const action of adventure.actions) {\n if (actionIds.has(action.id)) {\n throw new SynodeError({\n code: 'DUPLICATE_ID',\n message: `Duplicate Action ID found in adventure '${adventure.id}': ${action.id}`,\n path: [...adventurePath, action.id],\n });\n }\n actionIds.add(action.id);\n\n const actionPath = [...adventurePath, action.id];\n\n // Validate action-level bounceChance\n validateBounceChance(action.bounceChance, actionPath);\n\n // Validate action-level timeSpan\n validateTimeSpan(action.timeSpan, actionPath);\n }\n }\n\n // Cross-journey validation (only when allJourneys provided)\n if (allJourneys && config.requires) {\n const availableIds = allJourneys.map((j) => j.id);\n\n for (const reqId of config.requires) {\n if (!availableIds.includes(reqId)) {\n const suggestion = buildNotFoundSuggestion('journey', reqId, availableIds);\n throw new SynodeError({\n code: 'UNKNOWN_JOURNEY_REF',\n message: `Unknown journey reference '${reqId}'`,\n path: journeyPath,\n suggestion,\n });\n }\n }\n\n // Circular dependency detection\n detectCircularDeps(config.id, allJourneys);\n }\n}\n\n/**\n * Performs a dry run of the journey for a specified number of users.\n * Returns all generated events in memory.\n */\nexport async function dryRun(journey: Journey, userCount = 1): Promise<Event[]> {\n validateConfig(journey);\n\n const allEvents: Event[] = [];\n\n for (let i = 0; i < userCount; i++) {\n const engine = new Engine(journey);\n for await (const event of engine.run()) {\n allEvents.push(event);\n }\n }\n\n return allEvents;\n}\n","import { Event, OutputAdapter } from '../types.js';\n\n/**\n * Adapter that stores events in memory.\n * Useful for testing and dry runs.\n *\n * @example\n * ```ts\n * const adapter = new InMemoryAdapter();\n * await generate(journey, { users: 10, adapter });\n * console.log(adapter.events.length);\n * ```\n */\nexport class InMemoryAdapter implements OutputAdapter {\n public readonly events: Event[] = [];\n\n /** @inheritdoc */\n write(event: Event): void {\n this.events.push(event);\n }\n\n /**\n * Clears all stored events.\n */\n clear(): void {\n this.events.length = 0;\n }\n}\n","import { Event, OutputAdapter } from '../types.js';\n\n/**\n * Adapter that forwards events to a user-supplied callback function.\n * Supports both synchronous and asynchronous callbacks.\n *\n * @example\n * ```ts\n * const events: Event[] = [];\n * const adapter = new CallbackAdapter((event) => events.push(event));\n * await generate(journey, { users: 10, adapter });\n * ```\n */\nexport class CallbackAdapter implements OutputAdapter {\n constructor(private callback: (event: Event) => void | Promise<void>) {}\n\n /** @inheritdoc */\n async write(event: Event): Promise<void> {\n await this.callback(event);\n }\n}\n","import { Dataset, DatasetRow } from './types.js';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\n\n/**\n * Export format options.\n */\nexport type ExportFormat = 'csv' | 'json' | 'jsonl';\n\n/**\n * Exports a dataset to a string in the specified format.\n */\nexport function exportDatasetToString(dataset: Dataset, format: ExportFormat): string {\n switch (format) {\n case 'csv':\n return generateCSV(dataset);\n case 'json':\n return JSON.stringify(dataset.rows, null, 2);\n case 'jsonl':\n return generateJSONL(dataset);\n }\n}\n\n/**\n * Imports a dataset from a string in the specified format.\n */\nexport function importDatasetFromString(\n content: string,\n format: ExportFormat,\n id = 'imported',\n name = 'Imported Dataset',\n): Dataset {\n switch (format) {\n case 'csv':\n return parseCSVContent(id, name, content);\n case 'json':\n return parseJSONContent(id, name, content);\n case 'jsonl':\n return parseJSONLContent(id, name, content);\n }\n}\n\n/**\n * Validates that a file path does not escape the given base directory.\n * @throws Error if path traversal is detected.\n */\nexport function validateFilePath(filePath: string, basePath: string = process.cwd()): string {\n const resolved = path.resolve(basePath, filePath);\n const baseResolved = path.resolve(basePath);\n if (!resolved.startsWith(baseResolved + path.sep) && resolved !== baseResolved) {\n throw new Error(`Path traversal detected: '${filePath}' escapes base directory`);\n }\n return resolved;\n}\n\n/**\n * Exports a dataset to a file in the specified format.\n */\nexport async function exportDataset(\n dataset: Dataset,\n filePath: string,\n format: ExportFormat,\n): Promise<void> {\n const safePath = validateFilePath(filePath);\n const content = exportDatasetToString(dataset, format);\n await fs.writeFile(safePath, content, 'utf-8');\n}\n\n/**\n * Imports a dataset from a file in the specified format.\n */\nexport async function importDataset(\n id: string,\n name: string,\n filePath: string,\n format: ExportFormat,\n): Promise<Dataset> {\n switch (format) {\n case 'csv':\n return importFromCSV(id, name, filePath);\n case 'json':\n return importFromJSON(id, name, filePath);\n case 'jsonl':\n return importFromJSONL(id, name, filePath);\n }\n}\n\nfunction generateCSV(dataset: Dataset): string {\n if (dataset.rows.length === 0) {\n return '';\n }\n\n // Extract headers from first row\n const headers = Object.keys(dataset.rows[0]);\n const csvLines: string[] = [];\n\n // Add header row\n csvLines.push(headers.map((h) => escapeCSVValue(h)).join(','));\n\n // Add data rows\n for (const row of dataset.rows) {\n const values = headers.map((header) => {\n const value = row[header];\n if (value === null || value === undefined) {\n return escapeCSVValue('');\n }\n if (typeof value === 'object') {\n return escapeCSVValue(JSON.stringify(value));\n }\n // At this point, value is guaranteed to be string | number | boolean | bigint | symbol\n return escapeCSVValue(\n typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean'\n ? String(value)\n : JSON.stringify(value),\n );\n });\n csvLines.push(values.join(','));\n }\n\n return csvLines.join('\\n');\n}\n\nfunction generateJSONL(dataset: Dataset): string {\n const lines = dataset.rows.map((row) => JSON.stringify(row));\n return lines.join('\\n');\n}\n\nfunction parseJSONContent(id: string, name: string, content: string): Dataset {\n let rows: DatasetRow[];\n try {\n rows = JSON.parse(content) as DatasetRow[];\n } catch {\n throw new Error(`Failed to parse JSON dataset '${id}': invalid JSON`);\n }\n if (!Array.isArray(rows)) {\n throw new Error(`Failed to parse JSON dataset '${id}': expected an array of rows`);\n }\n return { id, name, rows };\n}\n\nfunction parseJSONLContent(id: string, name: string, content: string): Dataset {\n const lines = content.split('\\n').filter((line) => line.trim());\n const rows = lines.map((line, index) => {\n try {\n return JSON.parse(line) as DatasetRow;\n } catch {\n throw new Error(\n `Failed to parse JSONL dataset '${id}' at line ${String(index + 1)}: invalid JSON`,\n );\n }\n });\n return { id, name, rows };\n}\n\nfunction parseCSVContent(id: string, name: string, content: string): Dataset {\n const lines = splitCSVLines(content);\n if (lines.length === 0) {\n return { id, name, rows: [] };\n }\n\n const headers = parseCSVLine(lines[0]);\n const rows: DatasetRow[] = [];\n\n for (let i = 1; i < lines.length; i++) {\n const values = parseCSVLine(lines[i]);\n const row: DatasetRow = {};\n for (let j = 0; j < headers.length; j++) {\n row[headers[j]] = values[j] ?? '';\n }\n rows.push(row);\n }\n\n return { id, name, rows };\n}\n\n/**\n * Split CSV content into lines, respecting quoted values that contain newlines.\n */\nfunction splitCSVLines(content: string): string[] {\n const lines: string[] = [];\n let currentLine = '';\n let inQuotes = false;\n\n for (let i = 0; i < content.length; i++) {\n const char = content[i];\n const nextChar = content[i + 1];\n\n if (char === '\"') {\n currentLine += char;\n if (inQuotes && nextChar === '\"') {\n currentLine += nextChar;\n i++; // Skip next quote\n } else {\n inQuotes = !inQuotes;\n }\n } else if (char === '\\n' && !inQuotes) {\n if (currentLine.trim()) {\n lines.push(currentLine);\n }\n currentLine = '';\n } else {\n currentLine += char;\n }\n }\n\n // Add the last line if there's content\n if (currentLine.trim()) {\n lines.push(currentLine);\n }\n\n return lines;\n}\n\nasync function importFromCSV(id: string, name: string, filePath: string): Promise<Dataset> {\n const safePath = validateFilePath(filePath);\n const content = await fs.readFile(safePath, 'utf-8');\n return parseCSVContent(id, name, content);\n}\n\nasync function importFromJSON(id: string, name: string, filePath: string): Promise<Dataset> {\n const safePath = validateFilePath(filePath);\n const content = await fs.readFile(safePath, 'utf-8');\n return parseJSONContent(id, name, content);\n}\n\nasync function importFromJSONL(id: string, name: string, filePath: string): Promise<Dataset> {\n const safePath = validateFilePath(filePath);\n const content = await fs.readFile(safePath, 'utf-8');\n return parseJSONLContent(id, name, content);\n}\n\nfunction escapeCSVValue(value: string): string {\n // Prevent CSV formula injection by prefixing dangerous characters with a single quote\n if (/^[=+\\-@\\t\\r]/.test(value)) {\n value = \"'\" + value;\n }\n if (value.includes(',') || value.includes('\"') || value.includes('\\n')) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n }\n return value;\n}\n\nfunction parseCSVLine(line: string): string[] {\n const result: string[] = [];\n let current = '';\n let inQuotes = false;\n\n for (let i = 0; i < line.length; i++) {\n const char = line[i];\n const nextChar = line[i + 1];\n\n if (char === '\"') {\n if (inQuotes && nextChar === '\"') {\n current += '\"';\n i++; // Skip next quote\n } else {\n inQuotes = !inQuotes;\n }\n } else if (char === ',' && !inQuotes) {\n result.push(current);\n current = '';\n } else {\n current += char;\n }\n }\n\n result.push(current);\n return result;\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@synode/core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Core engine for synthetic data generation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.cjs",
|
|
7
|
+
"module": "dist/index.mjs",
|
|
8
|
+
"types": "dist/index.d.mts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.mts",
|
|
12
|
+
"import": "./dist/index.mjs",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./worker": {
|
|
16
|
+
"import": "./dist/execution/worker.mjs",
|
|
17
|
+
"require": "./dist/execution/worker.cjs"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"keywords": [
|
|
24
|
+
"synthetic-data",
|
|
25
|
+
"cdp",
|
|
26
|
+
"generator",
|
|
27
|
+
"journey",
|
|
28
|
+
"user-simulation"
|
|
29
|
+
],
|
|
30
|
+
"author": "Digitl Cloud GmbH",
|
|
31
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://github.com/digitl-cloud/synode",
|
|
35
|
+
"directory": "packages/core"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=18.0.0"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@faker-js/faker": "^10.4.0",
|
|
42
|
+
"zod": "^4.3.6"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^24.12.0",
|
|
46
|
+
"eslint": "^9.39.4",
|
|
47
|
+
"eslint-config-prettier": "^10.1.8",
|
|
48
|
+
"eslint-plugin-prettier": "^5.5.5",
|
|
49
|
+
"typescript-eslint": "^8.58.0",
|
|
50
|
+
"tsdown": "^0.16.8",
|
|
51
|
+
"tsx": "^4.21.0",
|
|
52
|
+
"typescript": "^5.9.3",
|
|
53
|
+
"vitest": "^4.1.2"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsdown",
|
|
57
|
+
"test": "vitest run",
|
|
58
|
+
"lint": "eslint src tests"
|
|
59
|
+
}
|
|
60
|
+
}
|