feat: csv-loader?
This commit is contained in:
parent
4296c2bdcd
commit
fe2e323d19
18
README.md
18
README.md
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
21
package.json
21
package.json
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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};`;
|
||||||
|
}
|
||||||
|
|
@ -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"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
},
|
||||||
|
]);
|
||||||
Loading…
Reference in New Issue