feat: csv-loader?

This commit is contained in:
hypercross 2026-03-31 13:02:29 +08:00
parent 4296c2bdcd
commit fe2e323d19
6 changed files with 229 additions and 11 deletions

View File

@ -146,6 +146,24 @@ Creates a validation function for the given schema.
- Special characters can be escaped with backslash: `\;`, `\[`, `\]`, `\\` - Special characters can be escaped with backslash: `\;`, `\[`, `\]`, `\\`
- Empty arrays/tuples are not allowed - Empty arrays/tuples are not allowed
## CSV Loader
For loading CSV files with schema validation in rspack, see [csv-loader.md](./csv-loader.md).
```javascript
// rspack.config.js
module.exports = {
module: {
rules: [
{
test: /\.schema\.csv$/,
use: 'inline-schema/csv-loader',
},
],
},
};
```
## License ## License
ISC ISC

82
csv-loader.md Normal file
View File

@ -0,0 +1,82 @@
# inline-schema/csv-loader
A rspack loader for CSV files that uses inline-schema for type validation.
## Installation
```bash
npm install inline-schema
```
## Usage
The loader expects:
- **First row**: Property names (headers)
- **Second row**: Inline-schema definitions for each property
- **Remaining rows**: Data values
### Example CSV
```csv
name,age,active,scores
string,number,boolean,number[]
Alice,30,true,[90; 85; 95]
Bob,25,false,[75; 80; 70]
```
### rspack.config.js
```javascript
module.exports = {
module: {
rules: [
{
test: /\.schema\.csv$/,
use: {
loader: 'inline-schema/csv-loader',
options: {
delimiter: ',',
quote: '"',
escape: '\\',
},
},
},
],
},
};
```
### Importing in TypeScript
```typescript
import data from './data.schema.csv';
// data = [
// { name: "Alice", age: 30, active: true, scores: [90, 85, 95] },
// { name: "Bob", age: 25, active: false, scores: [75, 80, 70] }
// ]
```
## Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `delimiter` | string | `,` | Column delimiter |
| `quote` | string | `"` | Quote character |
| `escape` | string | `\` | Escape character |
## Schema Syntax
Uses [inline-schema](https://github.com/your-repo/inline-schema) syntax:
| Type | Schema | Example |
|------|--------|---------|
| String | `string` | `hello` |
| Number | `number` | `42` |
| Boolean | `boolean` | `true` |
| Array | `string[]` or `[string][]` | `[a; b; c]` |
| Tuple | `[string; number]` | `[hello; 42]` |
## License
ISC

View File

@ -9,26 +9,41 @@
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
"import": "./dist/index.mjs", "import": "./dist/index.mjs",
"require": "./dist/index.js" "require": "./dist/index.js"
},
"./csv-loader": {
"types": "./dist/csv-loader/loader.d.ts",
"import": "./dist/csv-loader/loader.mjs",
"require": "./dist/csv-loader/loader.js"
} }
}, },
"scripts": { "scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts", "build": "tsup",
"dev": "tsup src/index.ts --format cjs,esm --dts --watch", "dev": "tsup --watch",
"test": "tsx src/test.ts" "test": "tsx src/test.ts"
}, },
"keywords": [ "keywords": [
"schema", "schema",
"parser", "parser",
"validator", "validator",
"typescript" "typescript",
"rspack",
"loader",
"csv"
], ],
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"description": "A TypeScript library for parsing and validating inline schemas", "description": "A TypeScript library for parsing and validating inline schemas",
"dependencies": {
"csv-parse": "^5.5.6"
},
"devDependencies": { "devDependencies": {
"@rspack/core": "^1.1.6",
"@types/node": "^25.5.0", "@types/node": "^25.5.0",
"tsup": "^8.5.1", "tsup": "^8.5.1",
"tsx": "^4.21.0", "tsx": "^4.21.0",
"typescript": "^6.0.2" "typescript": "^6.0.2"
},
"peerDependencies": {
"@rspack/core": "^1.x"
} }
} }

85
src/csv-loader/loader.ts Normal file
View File

@ -0,0 +1,85 @@
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;
}
interface PropertyConfig {
name: string;
schema: any;
validator: (value: unknown) => boolean;
parser: (valueString: string) => unknown;
}
export default function csvLoader(
this: LoaderContext<CsvLoaderOptions>,
content: string
): string {
const options = this.getOptions() || {};
const delimiter = options.delimiter ?? ',';
const quote = options.quote ?? '"';
const escape = options.escape ?? '\\';
const records = parse(content, {
delimiter,
quote,
escape,
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};`;
}

View File

@ -3,18 +3,17 @@
"target": "ES2020", "target": "ES2020",
"module": "ESNext", "module": "ESNext",
"lib": ["ES2020"], "lib": ["ES2020"],
"types": ["node"], "moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"declaration": true, "declaration": true,
"declarationMap": true, "declarationMap": true,
"sourceMap": true, "sourceMap": true,
"outDir": "./dist", "outDir": "./dist",
"strict": true, "rootDir": "./src"
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "bundler",
"ignoreDeprecations": "6.0"
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"exclude": ["node_modules", "dist"] "exclude": ["node_modules", "dist", "src/csv-loader"]
} }

19
tsup.config.ts Normal file
View File

@ -0,0 +1,19 @@
import { defineConfig } from 'tsup';
export default defineConfig([
{
entry: ['src/index.ts'],
format: ['cjs', 'esm'],
dts: true,
outDir: 'dist',
clean: true,
},
{
entry: ['src/csv-loader/loader.ts'],
format: ['cjs', 'esm'],
dts: true,
outDir: 'dist/csv-loader',
external: ['@rspack/core', 'csv-parse'],
clean: false,
},
]);