// src/csv-loader/loader.ts import { parse } from "csv-parse/sync"; // src/parser.ts var ParseError = class extends Error { constructor(message, position) { super(position !== void 0 ? `${message} at position ${position}` : message); this.position = position; this.name = "ParseError"; } }; var Parser = class { constructor(input) { this.pos = 0; this.input = input; } peek() { return this.input[this.pos] || ""; } consume() { return this.input[this.pos++] || ""; } skipWhitespace() { while (this.pos < this.input.length && /\s/.test(this.input[this.pos])) { this.pos++; } } match(str) { return this.input.slice(this.pos, this.pos + str.length) === str; } consumeStr(str) { if (this.match(str)) { this.pos += str.length; return true; } return false; } getPosition() { return this.pos; } getInputLength() { return this.input.length; } parseSchema() { this.skipWhitespace(); if (this.consumeStr("string")) { if (this.consumeStr("[")) { this.skipWhitespace(); if (!this.consumeStr("]")) { throw new ParseError("Expected ]", this.pos); } return { type: "array", element: { type: "string" } }; } return { type: "string" }; } if (this.consumeStr("number")) { if (this.consumeStr("[")) { this.skipWhitespace(); if (!this.consumeStr("]")) { throw new ParseError("Expected ]", this.pos); } return { type: "array", element: { type: "number" } }; } return { type: "number" }; } if (this.consumeStr("boolean")) { if (this.consumeStr("[")) { this.skipWhitespace(); if (!this.consumeStr("]")) { throw new ParseError("Expected ]", this.pos); } return { type: "array", element: { type: "boolean" } }; } return { type: "boolean" }; } if (this.consumeStr("[")) { const elements = []; this.skipWhitespace(); if (this.peek() === "]") { this.consume(); throw new ParseError("Empty array/tuple not allowed", this.pos); } elements.push(this.parseSchema()); this.skipWhitespace(); if (this.consumeStr(";")) { const remainingElements = []; while (true) { this.skipWhitespace(); remainingElements.push(this.parseSchema()); this.skipWhitespace(); if (!this.consumeStr(";")) { break; } } elements.push(...remainingElements); } this.skipWhitespace(); if (!this.consumeStr("]")) { throw new ParseError("Expected ]", this.pos); } if (this.consumeStr("[")) { this.skipWhitespace(); if (!this.consumeStr("]")) { throw new ParseError("Expected ]", this.pos); } if (elements.length === 1) { return { type: "array", element: elements[0] }; } return { type: "array", element: { type: "tuple", elements } }; } if (elements.length === 1) { return { type: "array", element: elements[0] }; } return { type: "tuple", elements }; } let identifier = ""; while (this.pos < this.input.length && /[a-zA-Z0-9\-_]/.test(this.peek())) { identifier += this.consume(); } if (identifier.length > 0) { if (this.consumeStr("[")) { this.skipWhitespace(); if (!this.consumeStr("]")) { throw new ParseError("Expected ]", this.pos); } return { type: "array", element: { type: "string" } }; } return { type: "string" }; } throw new ParseError(`Unexpected character: ${this.peek()}`, this.pos); } }; function parseSchema(schemaString) { const parser = new Parser(schemaString.trim()); const schema = parser.parseSchema(); if (parser.getPosition() < parser.getInputLength()) { throw new ParseError("Unexpected input after schema", parser.getPosition()); } return schema; } // src/validator.ts var ValueParser = class { constructor(input) { this.pos = 0; this.input = input; } peek() { return this.input[this.pos] || ""; } consume() { return this.input[this.pos++] || ""; } skipWhitespace() { while (this.pos < this.input.length && /\s/.test(this.input[this.pos])) { this.pos++; } } consumeStr(str) { if (this.input.slice(this.pos, this.pos + str.length) === str) { this.pos += str.length; return true; } return false; } parseValue(schema, allowOmitBrackets = false) { this.skipWhitespace(); switch (schema.type) { case "string": return this.parseStringValue(); case "number": return this.parseNumberValue(); case "boolean": return this.parseBooleanValue(); case "tuple": return this.parseTupleValue(schema, allowOmitBrackets); case "array": return this.parseArrayValue(schema, allowOmitBrackets); default: throw new ParseError(`Unknown schema type: ${schema.type}`, this.pos); } } parseStringValue() { let result = ""; while (this.pos < this.input.length) { const char = this.peek(); if (char === "\\") { this.consume(); const nextChar = this.consume(); if (nextChar === ";" || nextChar === "[" || nextChar === "]" || nextChar === "\\") { result += nextChar; } else { result += "\\" + nextChar; } } else if (char === ";" || char === "]") { break; } else { result += this.consume(); } } return result.trim(); } parseNumberValue() { let numStr = ""; while (this.pos < this.input.length && /[\d.\-+eE]/.test(this.peek())) { numStr += this.consume(); } const num = parseFloat(numStr); if (isNaN(num)) { throw new ParseError("Invalid number", this.pos - numStr.length); } return num; } parseBooleanValue() { if (this.consumeStr("true")) { return true; } if (this.consumeStr("false")) { return false; } throw new ParseError("Expected true or false", this.pos); } parseTupleValue(schema, allowOmitBrackets) { let hasOpenBracket = false; if (this.peek() === "[") { this.consume(); hasOpenBracket = true; } else if (!allowOmitBrackets) { throw new ParseError("Expected [", this.pos); } this.skipWhitespace(); if (this.peek() === "]" && hasOpenBracket) { this.consume(); return []; } const result = []; for (let i = 0; i < schema.elements.length; i++) { this.skipWhitespace(); result.push(this.parseValue(schema.elements[i], false)); this.skipWhitespace(); if (i < schema.elements.length - 1) { if (!this.consumeStr(";")) { throw new ParseError("Expected ;", this.pos); } } } this.skipWhitespace(); if (hasOpenBracket) { if (!this.consumeStr("]")) { throw new ParseError("Expected ]", this.pos); } } return result; } parseArrayValue(schema, allowOmitBrackets) { let hasOpenBracket = false; const elementIsTupleOrArray = schema.element.type === "tuple" || schema.element.type === "array"; if (this.peek() === "[") { if (!elementIsTupleOrArray) { this.consume(); hasOpenBracket = true; } else if (this.input[this.pos + 1] === "[") { this.consume(); hasOpenBracket = true; } } if (!hasOpenBracket && !allowOmitBrackets && !elementIsTupleOrArray) { throw new ParseError("Expected [", this.pos); } this.skipWhitespace(); if (this.peek() === "]" && hasOpenBracket) { this.consume(); return []; } const result = []; while (true) { this.skipWhitespace(); result.push(this.parseValue(schema.element, false)); this.skipWhitespace(); if (!this.consumeStr(";")) { break; } } this.skipWhitespace(); if (hasOpenBracket) { if (!this.consumeStr("]")) { throw new ParseError("Expected ]", this.pos); } } return result; } getPosition() { return this.pos; } getInputLength() { return this.input.length; } }; function parseValue(schema, valueString) { const parser = new ValueParser(valueString.trim()); const allowOmitBrackets = schema.type === "tuple" || schema.type === "array"; const value = parser.parseValue(schema, allowOmitBrackets); if (parser.getPosition() < parser.getInputLength()) { throw new ParseError("Unexpected input after value", parser.getPosition()); } return value; } function createValidator(schema) { return function validate(value) { switch (schema.type) { case "string": return typeof value === "string"; case "number": return typeof value === "number" && !isNaN(value); case "boolean": return typeof value === "boolean"; case "tuple": if (!Array.isArray(value)) return false; if (value.length !== schema.elements.length) return false; return schema.elements.every( (elementSchema, index) => createValidator(elementSchema)(value[index]) ); case "array": if (!Array.isArray(value)) return false; return value.every((item) => createValidator(schema.element)(item)); default: return false; } }; } // src/csv-loader/loader.ts function csvLoader(content) { const options = this.getOptions(); const delimiter = options?.delimiter ?? ","; const quote = options?.quote ?? '"'; const escape = options?.escape ?? "\\"; const bom = options?.bom ?? true; const comment = options?.comment === false ? void 0 : options?.comment ?? "#"; const trim = options?.trim ?? true; const records = parse(content, { delimiter, quote, escape, bom, comment, trim, relax_column_count: true }); if (records.length < 2) { throw new Error("CSV must have at least 2 rows: headers and schemas"); } const headers = records[0]; const schemas = records[1]; if (headers.length !== schemas.length) { throw new Error( `Header count (${headers.length}) does not match schema count (${schemas.length})` ); } const propertyConfigs = headers.map((header, index) => { const schemaString = schemas[index]; const schema = parseSchema(schemaString); return { name: header, schema, validator: createValidator(schema), parser: (valueString) => parseValue(schema, valueString) }; }); const dataRows = records.slice(2); const objects = dataRows.map((row, rowIndex) => { const obj = {}; propertyConfigs.forEach((config, colIndex) => { const rawValue = row[colIndex] ?? ""; try { const parsed = config.parser(rawValue); if (!config.validator(parsed)) { throw new Error( `Validation failed for property "${config.name}" at row ${rowIndex + 3}: ${rawValue}` ); } obj[config.name] = parsed; } catch (error) { if (error instanceof Error) { throw new Error( `Failed to parse property "${config.name}" at row ${rowIndex + 3}, column ${colIndex + 1}: ${error.message}` ); } throw error; } }); return obj; }); const json = JSON.stringify(objects, null, 2); return `export default ${json};`; } export { csvLoader as default };