From 5e93bb5a1d81f858ae91f43ea14ceb40f514f9c0 Mon Sep 17 00:00:00 2001 From: hyper Date: Tue, 31 Mar 2026 14:55:46 +0800 Subject: [PATCH] chore: add dist files and .npmignore for npm distribution - Include dist/ in git for npm package distribution - Add .npmignore to exclude source files from npm package - This ensures npm install from git works without requiring build Co-authored-by: Qwen-Coder --- .gitignore | 6 +- .npmignore | 47 ++++ dist/csv-loader/loader.d.mts | 13 ++ dist/csv-loader/loader.d.ts | 13 ++ dist/csv-loader/loader.js | 420 +++++++++++++++++++++++++++++++++++ dist/csv-loader/loader.mjs | 399 +++++++++++++++++++++++++++++++++ dist/index.d.mts | 31 +++ dist/index.d.ts | 31 +++ dist/index.js | 376 +++++++++++++++++++++++++++++++ dist/index.mjs | 345 ++++++++++++++++++++++++++++ 10 files changed, 1678 insertions(+), 3 deletions(-) create mode 100644 .npmignore create mode 100644 dist/csv-loader/loader.d.mts create mode 100644 dist/csv-loader/loader.d.ts create mode 100644 dist/csv-loader/loader.js create mode 100644 dist/csv-loader/loader.mjs create mode 100644 dist/index.d.mts create mode 100644 dist/index.d.ts create mode 100644 dist/index.js create mode 100644 dist/index.mjs diff --git a/.gitignore b/.gitignore index 8774eaa..d785c1d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,10 +4,10 @@ package-lock.json pnpm-lock.yaml yarn.lock -# Build output -dist/ +# Build output (kept in git for npm distribution) +# dist/ -# TypeScript cache +# TypeScript Cache *.tsbuildinfo # IDE diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..0e35d34 --- /dev/null +++ b/.npmignore @@ -0,0 +1,47 @@ +# Source files +src/ + +# Config files +tsconfig.json +tsup.config.ts + +# Dev dependencies config +.eslintrc* +.prettierrc* +jest.config.* +vitest.config.* + +# Test files +*.test.ts +*.spec.ts +__tests__/ +test/ +tests/ + +# Docs +*.md +!README.md + +# Git +.git/ +.gitignore + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log +npm-debug.log* + +# TypeScript cache +*.tsbuildinfo + +# Local env +.env* diff --git a/dist/csv-loader/loader.d.mts b/dist/csv-loader/loader.d.mts new file mode 100644 index 0000000..65bbc8e --- /dev/null +++ b/dist/csv-loader/loader.d.mts @@ -0,0 +1,13 @@ +import { LoaderContext } from '@rspack/core'; + +interface CsvLoaderOptions { + delimiter?: string; + quote?: string; + escape?: string; + bom?: boolean; + comment?: string | false; + trim?: boolean; +} +declare function csvLoader(this: LoaderContext, content: string): string | Buffer; + +export { type CsvLoaderOptions, csvLoader as default }; diff --git a/dist/csv-loader/loader.d.ts b/dist/csv-loader/loader.d.ts new file mode 100644 index 0000000..65bbc8e --- /dev/null +++ b/dist/csv-loader/loader.d.ts @@ -0,0 +1,13 @@ +import { LoaderContext } from '@rspack/core'; + +interface CsvLoaderOptions { + delimiter?: string; + quote?: string; + escape?: string; + bom?: boolean; + comment?: string | false; + trim?: boolean; +} +declare function csvLoader(this: LoaderContext, content: string): string | Buffer; + +export { type CsvLoaderOptions, csvLoader as default }; diff --git a/dist/csv-loader/loader.js b/dist/csv-loader/loader.js new file mode 100644 index 0000000..e24759f --- /dev/null +++ b/dist/csv-loader/loader.js @@ -0,0 +1,420 @@ +"use strict"; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// src/csv-loader/loader.ts +var loader_exports = {}; +__export(loader_exports, { + default: () => csvLoader +}); +module.exports = __toCommonJS(loader_exports); +var import_sync = require("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 = (0, import_sync.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};`; +} diff --git a/dist/csv-loader/loader.mjs b/dist/csv-loader/loader.mjs new file mode 100644 index 0000000..cab3491 --- /dev/null +++ b/dist/csv-loader/loader.mjs @@ -0,0 +1,399 @@ +// 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 +}; diff --git a/dist/index.d.mts b/dist/index.d.mts new file mode 100644 index 0000000..5dc9b94 --- /dev/null +++ b/dist/index.d.mts @@ -0,0 +1,31 @@ +type SchemaType = 'string' | 'number' | 'boolean'; +interface PrimitiveSchema { + type: SchemaType; +} +interface TupleSchema { + type: 'tuple'; + elements: Schema[]; +} +interface ArraySchema { + type: 'array'; + element: Schema; +} +type Schema = PrimitiveSchema | TupleSchema | ArraySchema; +interface ParsedSchema { + schema: Schema; + validator: (value: unknown) => boolean; + parse: (valueString: string) => unknown; +} + +declare class ParseError extends Error { + position?: number | undefined; + constructor(message: string, position?: number | undefined); +} +declare function parseSchema(schemaString: string): Schema; + +declare function parseValue(schema: Schema, valueString: string): unknown; +declare function createValidator(schema: Schema): (value: unknown) => boolean; + +declare function defineSchema(schemaString: string): ParsedSchema; + +export { type ArraySchema, ParseError, type ParsedSchema, type PrimitiveSchema, type Schema, type TupleSchema, createValidator, defineSchema, parseSchema, parseValue }; diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000..5dc9b94 --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,31 @@ +type SchemaType = 'string' | 'number' | 'boolean'; +interface PrimitiveSchema { + type: SchemaType; +} +interface TupleSchema { + type: 'tuple'; + elements: Schema[]; +} +interface ArraySchema { + type: 'array'; + element: Schema; +} +type Schema = PrimitiveSchema | TupleSchema | ArraySchema; +interface ParsedSchema { + schema: Schema; + validator: (value: unknown) => boolean; + parse: (valueString: string) => unknown; +} + +declare class ParseError extends Error { + position?: number | undefined; + constructor(message: string, position?: number | undefined); +} +declare function parseSchema(schemaString: string): Schema; + +declare function parseValue(schema: Schema, valueString: string): unknown; +declare function createValidator(schema: Schema): (value: unknown) => boolean; + +declare function defineSchema(schemaString: string): ParsedSchema; + +export { type ArraySchema, ParseError, type ParsedSchema, type PrimitiveSchema, type Schema, type TupleSchema, createValidator, defineSchema, parseSchema, parseValue }; diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..e945fd5 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,376 @@ +"use strict"; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// src/index.ts +var index_exports = {}; +__export(index_exports, { + ParseError: () => ParseError, + createValidator: () => createValidator, + defineSchema: () => defineSchema, + parseSchema: () => parseSchema, + parseValue: () => parseValue +}); +module.exports = __toCommonJS(index_exports); + +// 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/index.ts +function defineSchema(schemaString) { + const schema = parseSchema(schemaString); + const validator = createValidator(schema); + return { + schema, + validator, + parse: (valueString) => parseValue(schema, valueString) + }; +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + ParseError, + createValidator, + defineSchema, + parseSchema, + parseValue +}); diff --git a/dist/index.mjs b/dist/index.mjs new file mode 100644 index 0000000..df2c2a7 --- /dev/null +++ b/dist/index.mjs @@ -0,0 +1,345 @@ +// 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/index.ts +function defineSchema(schemaString) { + const schema = parseSchema(schemaString); + const validator = createValidator(schema); + return { + schema, + validator, + parse: (valueString) => parseValue(schema, valueString) + }; +} +export { + ParseError, + createValidator, + defineSchema, + parseSchema, + parseValue +};