Compare commits

...

2 Commits

Author SHA1 Message Date
hyper d9a91ae8be Merge branch 'master' of https://gitea.ayi-games.online/hypercross/inline-schema 2026-04-02 21:04:16 +08:00
hyper da1e3b2dac refactor: remove dist/ from git 2026-04-02 21:03:57 +08:00
9 changed files with 2 additions and 2025 deletions

4
.gitignore vendored
View File

@ -4,8 +4,8 @@ package-lock.json
pnpm-lock.yaml pnpm-lock.yaml
yarn.lock yarn.lock
# Build output (kept in git for npm distribution) # Build output
# dist/ dist/
# TypeScript Cache # TypeScript Cache
*.tsbuildinfo *.tsbuildinfo

View File

@ -1,54 +0,0 @@
import { LoaderContext } from '@rspack/core';
interface CsvLoaderOptions {
delimiter?: string;
quote?: string;
escape?: string;
bom?: boolean;
comment?: string | false;
trim?: boolean;
/** Generate TypeScript declaration file (.d.ts) */
emitTypes?: boolean;
/** Output directory for generated type files (relative to output path) */
typesOutputDir?: string;
/** Write .d.ts files to disk (useful for dev server) */
writeToDisk?: boolean;
}
interface CsvParseResult {
/** Parsed CSV data as array of objects */
data: Record<string, unknown>[];
/** Generated TypeScript type definition string (if emitTypes is true) */
typeDefinition?: string;
/** Property configurations for the CSV columns */
propertyConfigs: PropertyConfig[];
}
interface PropertyConfig {
name: string;
schema: any;
validator: (value: unknown) => boolean;
parser: (valueString: string) => unknown;
}
/**
* Parse CSV content string into structured data with schema validation.
* This is a standalone function that doesn't depend on webpack/rspack LoaderContext.
*
* @param content - CSV content string (must have at least headers + schema row + 1 data row)
* @param options - Parsing options
* @returns CsvParseResult containing parsed data and optional type definitions
*/
declare function parseCsv(content: string, options?: CsvLoaderOptions): CsvParseResult;
/**
* Generate JavaScript module code from CSV content.
* Returns a string that can be used as a module export.
*
* @param content - CSV content string
* @param options - Parsing options
* @returns JavaScript module code string
*/
declare function csvToModule(content: string, options?: CsvLoaderOptions): {
js: string;
dts?: string;
};
declare function csvLoader(this: LoaderContext<CsvLoaderOptions>, content: string): string | Buffer;
export { type CsvLoaderOptions, type CsvParseResult, csvToModule, csvLoader as default, parseCsv };

View File

@ -1,54 +0,0 @@
import { LoaderContext } from '@rspack/core';
interface CsvLoaderOptions {
delimiter?: string;
quote?: string;
escape?: string;
bom?: boolean;
comment?: string | false;
trim?: boolean;
/** Generate TypeScript declaration file (.d.ts) */
emitTypes?: boolean;
/** Output directory for generated type files (relative to output path) */
typesOutputDir?: string;
/** Write .d.ts files to disk (useful for dev server) */
writeToDisk?: boolean;
}
interface CsvParseResult {
/** Parsed CSV data as array of objects */
data: Record<string, unknown>[];
/** Generated TypeScript type definition string (if emitTypes is true) */
typeDefinition?: string;
/** Property configurations for the CSV columns */
propertyConfigs: PropertyConfig[];
}
interface PropertyConfig {
name: string;
schema: any;
validator: (value: unknown) => boolean;
parser: (valueString: string) => unknown;
}
/**
* Parse CSV content string into structured data with schema validation.
* This is a standalone function that doesn't depend on webpack/rspack LoaderContext.
*
* @param content - CSV content string (must have at least headers + schema row + 1 data row)
* @param options - Parsing options
* @returns CsvParseResult containing parsed data and optional type definitions
*/
declare function parseCsv(content: string, options?: CsvLoaderOptions): CsvParseResult;
/**
* Generate JavaScript module code from CSV content.
* Returns a string that can be used as a module export.
*
* @param content - CSV content string
* @param options - Parsing options
* @returns JavaScript module code string
*/
declare function csvToModule(content: string, options?: CsvLoaderOptions): {
js: string;
dts?: string;
};
declare function csvLoader(this: LoaderContext<CsvLoaderOptions>, content: string): string | Buffer;
export { type CsvLoaderOptions, type CsvParseResult, csvToModule, csvLoader as default, parseCsv };

View File

@ -1,548 +0,0 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
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 __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/csv-loader/loader.ts
var loader_exports = {};
__export(loader_exports, {
csvToModule: () => csvToModule,
default: () => csvLoader,
parseCsv: () => parseCsv
});
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.parseNamedSchema());
this.skipWhitespace();
if (this.consumeStr(";")) {
const remainingElements = [];
while (true) {
this.skipWhitespace();
remainingElements.push(this.parseNamedSchema());
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 && !elements[0].name) {
return { type: "array", element: elements[0].schema };
}
return { type: "array", element: { type: "tuple", elements } };
}
if (elements.length === 1 && !elements[0].name) {
return { type: "array", element: elements[0].schema };
}
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);
}
parseNamedSchema() {
this.skipWhitespace();
const startpos = this.pos;
let identifier = "";
while (this.pos < this.input.length && /[a-zA-Z0-9\-_]/.test(this.peek())) {
identifier += this.consume();
}
if (identifier.length === 0) {
throw new ParseError("Expected schema or named schema", this.pos);
}
this.skipWhitespace();
if (this.consumeStr(":")) {
this.skipWhitespace();
const name = identifier;
const schema = this.parseSchema();
return { name, schema };
} else {
this.pos = startpos;
const schema = this.parseSchema();
return { schema };
}
}
};
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();
const elementSchema = schema.elements[i];
if (elementSchema.name) {
this.skipWhitespace();
const savedPos = this.pos;
if (this.consumeStr(`${elementSchema.name}:`)) {
this.skipWhitespace();
} else {
this.pos = savedPos;
}
}
result.push(this.parseValue(elementSchema.schema, 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.schema)(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
var path = __toESM(require("path"));
var fs = __toESM(require("fs"));
function schemaToTypeString(schema) {
switch (schema.type) {
case "string":
return "string";
case "number":
return "number";
case "boolean":
return "boolean";
case "array":
if (schema.element.type === "tuple") {
const tupleElements2 = schema.element.elements.map((el) => {
const typeStr = schemaToTypeString(el.schema);
return el.name ? `${el.name}: ${typeStr}` : typeStr;
});
return `[${tupleElements2.join(", ")}]`;
}
return `${schemaToTypeString(schema.element)}[]`;
case "tuple":
const tupleElements = schema.elements.map((el) => {
const typeStr = schemaToTypeString(el.schema);
return el.name ? `${el.name}: ${typeStr}` : typeStr;
});
return `[${tupleElements.join(", ")}]`;
default:
return "unknown";
}
}
function generateTypeDefinition(resourceName, propertyConfigs) {
const properties = propertyConfigs.map((config) => ` ${config.name}: ${schemaToTypeString(config.schema)};`).join("\n");
return `type Table = {
${properties}
}[];
declare const data: Table;
export default data;
`;
}
function parseCsv(content, options = {}) {
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 emitTypes = options.emitTypes ?? 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 result = {
data: objects,
propertyConfigs
};
if (emitTypes) {
result.typeDefinition = generateTypeDefinition("", propertyConfigs);
}
return result;
}
function csvToModule(content, options = {}) {
const result = parseCsv(content, options);
const json = JSON.stringify(result.data, null, 2);
const js = `export default ${json};`;
return {
js,
dts: result.typeDefinition
};
}
function csvLoader(content) {
const options = this.getOptions();
const emitTypes = options?.emitTypes ?? true;
const typesOutputDir = options?.typesOutputDir ?? "";
const writeToDisk = options?.writeToDisk ?? false;
const result = parseCsv(content, options);
if (emitTypes && result.typeDefinition) {
const context = this.context || "";
let relativePath = this.resourcePath.replace(context, "");
if (relativePath.startsWith("\\") || relativePath.startsWith("/")) {
relativePath = relativePath.substring(1);
}
relativePath = relativePath.replace(/\\/g, "/");
const dtsFileName = `${relativePath}.d.ts`;
const outputPath = typesOutputDir ? path.join(typesOutputDir, dtsFileName) : dtsFileName;
if (writeToDisk) {
const absolutePath = path.join(this.context || process.cwd(), typesOutputDir || "", dtsFileName);
fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
fs.writeFileSync(absolutePath, result.typeDefinition);
} else {
this.emitFile?.(outputPath, result.typeDefinition);
}
}
return `export default ${JSON.stringify(result.data, null, 2)};`;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
csvToModule,
parseCsv
});

View File

@ -1,512 +0,0 @@
// 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.parseNamedSchema());
this.skipWhitespace();
if (this.consumeStr(";")) {
const remainingElements = [];
while (true) {
this.skipWhitespace();
remainingElements.push(this.parseNamedSchema());
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 && !elements[0].name) {
return { type: "array", element: elements[0].schema };
}
return { type: "array", element: { type: "tuple", elements } };
}
if (elements.length === 1 && !elements[0].name) {
return { type: "array", element: elements[0].schema };
}
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);
}
parseNamedSchema() {
this.skipWhitespace();
const startpos = this.pos;
let identifier = "";
while (this.pos < this.input.length && /[a-zA-Z0-9\-_]/.test(this.peek())) {
identifier += this.consume();
}
if (identifier.length === 0) {
throw new ParseError("Expected schema or named schema", this.pos);
}
this.skipWhitespace();
if (this.consumeStr(":")) {
this.skipWhitespace();
const name = identifier;
const schema = this.parseSchema();
return { name, schema };
} else {
this.pos = startpos;
const schema = this.parseSchema();
return { schema };
}
}
};
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();
const elementSchema = schema.elements[i];
if (elementSchema.name) {
this.skipWhitespace();
const savedPos = this.pos;
if (this.consumeStr(`${elementSchema.name}:`)) {
this.skipWhitespace();
} else {
this.pos = savedPos;
}
}
result.push(this.parseValue(elementSchema.schema, 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.schema)(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
import * as path from "path";
import * as fs from "fs";
function schemaToTypeString(schema) {
switch (schema.type) {
case "string":
return "string";
case "number":
return "number";
case "boolean":
return "boolean";
case "array":
if (schema.element.type === "tuple") {
const tupleElements2 = schema.element.elements.map((el) => {
const typeStr = schemaToTypeString(el.schema);
return el.name ? `${el.name}: ${typeStr}` : typeStr;
});
return `[${tupleElements2.join(", ")}]`;
}
return `${schemaToTypeString(schema.element)}[]`;
case "tuple":
const tupleElements = schema.elements.map((el) => {
const typeStr = schemaToTypeString(el.schema);
return el.name ? `${el.name}: ${typeStr}` : typeStr;
});
return `[${tupleElements.join(", ")}]`;
default:
return "unknown";
}
}
function generateTypeDefinition(resourceName, propertyConfigs) {
const properties = propertyConfigs.map((config) => ` ${config.name}: ${schemaToTypeString(config.schema)};`).join("\n");
return `type Table = {
${properties}
}[];
declare const data: Table;
export default data;
`;
}
function parseCsv(content, options = {}) {
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 emitTypes = options.emitTypes ?? 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 result = {
data: objects,
propertyConfigs
};
if (emitTypes) {
result.typeDefinition = generateTypeDefinition("", propertyConfigs);
}
return result;
}
function csvToModule(content, options = {}) {
const result = parseCsv(content, options);
const json = JSON.stringify(result.data, null, 2);
const js = `export default ${json};`;
return {
js,
dts: result.typeDefinition
};
}
function csvLoader(content) {
const options = this.getOptions();
const emitTypes = options?.emitTypes ?? true;
const typesOutputDir = options?.typesOutputDir ?? "";
const writeToDisk = options?.writeToDisk ?? false;
const result = parseCsv(content, options);
if (emitTypes && result.typeDefinition) {
const context = this.context || "";
let relativePath = this.resourcePath.replace(context, "");
if (relativePath.startsWith("\\") || relativePath.startsWith("/")) {
relativePath = relativePath.substring(1);
}
relativePath = relativePath.replace(/\\/g, "/");
const dtsFileName = `${relativePath}.d.ts`;
const outputPath = typesOutputDir ? path.join(typesOutputDir, dtsFileName) : dtsFileName;
if (writeToDisk) {
const absolutePath = path.join(this.context || process.cwd(), typesOutputDir || "", dtsFileName);
fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
fs.writeFileSync(absolutePath, result.typeDefinition);
} else {
this.emitFile?.(outputPath, result.typeDefinition);
}
}
return `export default ${JSON.stringify(result.data, null, 2)};`;
}
export {
csvToModule,
csvLoader as default,
parseCsv
};

35
dist/index.d.mts vendored
View File

@ -1,35 +0,0 @@
type SchemaType = 'string' | 'number' | 'boolean';
interface PrimitiveSchema {
type: SchemaType;
}
interface NamedSchema {
name?: string;
schema: Schema;
}
interface TupleSchema {
type: 'tuple';
elements: NamedSchema[];
}
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 };

35
dist/index.d.ts vendored
View File

@ -1,35 +0,0 @@
type SchemaType = 'string' | 'number' | 'boolean';
interface PrimitiveSchema {
type: SchemaType;
}
interface NamedSchema {
name?: string;
schema: Schema;
}
interface TupleSchema {
type: 'tuple';
elements: NamedSchema[];
}
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 };

408
dist/index.js vendored
View File

@ -1,408 +0,0 @@
"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.parseNamedSchema());
this.skipWhitespace();
if (this.consumeStr(";")) {
const remainingElements = [];
while (true) {
this.skipWhitespace();
remainingElements.push(this.parseNamedSchema());
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 && !elements[0].name) {
return { type: "array", element: elements[0].schema };
}
return { type: "array", element: { type: "tuple", elements } };
}
if (elements.length === 1 && !elements[0].name) {
return { type: "array", element: elements[0].schema };
}
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);
}
parseNamedSchema() {
this.skipWhitespace();
const startpos = this.pos;
let identifier = "";
while (this.pos < this.input.length && /[a-zA-Z0-9\-_]/.test(this.peek())) {
identifier += this.consume();
}
if (identifier.length === 0) {
throw new ParseError("Expected schema or named schema", this.pos);
}
this.skipWhitespace();
if (this.consumeStr(":")) {
this.skipWhitespace();
const name = identifier;
const schema = this.parseSchema();
return { name, schema };
} else {
this.pos = startpos;
const schema = this.parseSchema();
return { schema };
}
}
};
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();
const elementSchema = schema.elements[i];
if (elementSchema.name) {
this.skipWhitespace();
const savedPos = this.pos;
if (this.consumeStr(`${elementSchema.name}:`)) {
this.skipWhitespace();
} else {
this.pos = savedPos;
}
}
result.push(this.parseValue(elementSchema.schema, 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.schema)(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
});

377
dist/index.mjs vendored
View File

@ -1,377 +0,0 @@
// 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.parseNamedSchema());
this.skipWhitespace();
if (this.consumeStr(";")) {
const remainingElements = [];
while (true) {
this.skipWhitespace();
remainingElements.push(this.parseNamedSchema());
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 && !elements[0].name) {
return { type: "array", element: elements[0].schema };
}
return { type: "array", element: { type: "tuple", elements } };
}
if (elements.length === 1 && !elements[0].name) {
return { type: "array", element: elements[0].schema };
}
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);
}
parseNamedSchema() {
this.skipWhitespace();
const startpos = this.pos;
let identifier = "";
while (this.pos < this.input.length && /[a-zA-Z0-9\-_]/.test(this.peek())) {
identifier += this.consume();
}
if (identifier.length === 0) {
throw new ParseError("Expected schema or named schema", this.pos);
}
this.skipWhitespace();
if (this.consumeStr(":")) {
this.skipWhitespace();
const name = identifier;
const schema = this.parseSchema();
return { name, schema };
} else {
this.pos = startpos;
const schema = this.parseSchema();
return { schema };
}
}
};
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();
const elementSchema = schema.elements[i];
if (elementSchema.name) {
this.skipWhitespace();
const savedPos = this.pos;
if (this.consumeStr(`${elementSchema.name}:`)) {
this.skipWhitespace();
} else {
this.pos = savedPos;
}
}
result.push(this.parseValue(elementSchema.schema, 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.schema)(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
};