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: `\;`, `\[`, `\]`, `\\`
|
||||
- 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
|
||||
|
||||
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",
|
||||
"import": "./dist/index.mjs",
|
||||
"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": {
|
||||
"build": "tsup src/index.ts --format cjs,esm --dts",
|
||||
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"test": "tsx src/test.ts"
|
||||
},
|
||||
"keywords": [
|
||||
"schema",
|
||||
"parser",
|
||||
"validator",
|
||||
"typescript"
|
||||
"typescript",
|
||||
"rspack",
|
||||
"loader",
|
||||
"csv"
|
||||
],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "A TypeScript library for parsing and validating inline schemas",
|
||||
"dependencies": {
|
||||
"csv-parse": "^5.5.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rspack/core": "^1.1.6",
|
||||
"@types/node": "^25.5.0",
|
||||
"tsup": "^8.5.1",
|
||||
"tsx": "^4.21.0",
|
||||
"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",
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2020"],
|
||||
"types": ["node"],
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"moduleResolution": "bundler",
|
||||
"ignoreDeprecations": "6.0"
|
||||
"rootDir": "./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