inline-schema/src/csv-loader/loader.ts

95 lines
2.6 KiB
TypeScript
Raw Normal View History

2026-03-31 13:02:29 +08:00
import type { LoaderContext } from '@rspack/core';
import { parse } from 'csv-parse/sync';
import { parseSchema, createValidator, parseValue } from '../index.js';
export interface CsvLoaderOptions {
delimiter?: string;
quote?: string;
escape?: string;
2026-03-31 14:45:02 +08:00
bom?: boolean;
comment?: string | false;
trim?: boolean;
2026-03-31 13:02:29 +08:00
}
interface PropertyConfig {
name: string;
schema: any;
validator: (value: unknown) => boolean;
parser: (valueString: string) => unknown;
}
export default function csvLoader(
this: LoaderContext<CsvLoaderOptions>,
content: string
2026-03-31 14:25:38 +08:00
): string | Buffer {
2026-03-31 14:45:02 +08:00
const options = this.getOptions() as CsvLoaderOptions | undefined;
const delimiter = options?.delimiter ?? ',';
const quote = options?.quote ?? '"';
const escape = options?.escape ?? '\\';
const bom = options?.bom ?? true;
const comment = options?.comment === false ? undefined : (options?.comment ?? '#');
const trim = options?.trim ?? true;
2026-03-31 13:02:29 +08:00
const records = parse(content, {
delimiter,
quote,
escape,
2026-03-31 14:45:02 +08:00
bom,
comment,
trim,
2026-03-31 13:02:29 +08:00
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: PropertyConfig[] = headers.map((header: string, index: number) => {
const schemaString = schemas[index];
const schema = parseSchema(schemaString);
return {
name: header,
schema,
validator: createValidator(schema),
parser: (valueString: string) => parseValue(schema, valueString),
};
});
const dataRows = records.slice(2);
const objects = dataRows.map((row: string[], rowIndex: number) => {
const obj: Record<string, unknown> = {};
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};`;
}