@nubase/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -0
- package/dist/index.d.mts +93 -0
- package/dist/index.d.ts +93 -0
- package/dist/index.js +178 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +145 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +47 -0
- package/schema/base-schema.ts +46 -0
- package/schema/entity-schema.ts +19 -0
- package/schema/index.ts +3 -0
- package/schema/nu.test.ts +325 -0
- package/schema/nu.ts +36 -0
- package/schema/toZod.ts +74 -0
- package/schema/types.ts +149 -0
package/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# @nubase/core
|
|
2
|
+
|
|
3
|
+
Core schema and types for the nubase ecosystem.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @nubase/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { BaseSchema, nu } from '@nubase/core';
|
|
15
|
+
|
|
16
|
+
// Example usage of base schema
|
|
17
|
+
const mySchema = new BaseSchema({
|
|
18
|
+
fields: {
|
|
19
|
+
name: { type: 'string', required: true },
|
|
20
|
+
age: { type: 'number', required: false }
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Example usage of nu types
|
|
25
|
+
const result = nu.string().parse('hello world');
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
- Type-safe schema definitions
|
|
31
|
+
- Zod-based validation
|
|
32
|
+
- TypeScript-first design
|
|
33
|
+
- Extensible base schema system
|
|
34
|
+
|
|
35
|
+
## API Reference
|
|
36
|
+
|
|
37
|
+
### BaseSchema
|
|
38
|
+
|
|
39
|
+
Base class for creating type-safe schemas with metadata.
|
|
40
|
+
|
|
41
|
+
### nu
|
|
42
|
+
|
|
43
|
+
Utility functions and types for schema validation.
|
|
44
|
+
|
|
45
|
+
## Contributing
|
|
46
|
+
|
|
47
|
+
Please see the main repository for contributing guidelines.
|
|
48
|
+
|
|
49
|
+
## License
|
|
50
|
+
|
|
51
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
interface SchemaMetadata<Output = any> {
|
|
2
|
+
label?: string;
|
|
3
|
+
description?: string;
|
|
4
|
+
defaultValue?: Output;
|
|
5
|
+
}
|
|
6
|
+
declare abstract class BaseSchema<Output = any> {
|
|
7
|
+
/**
|
|
8
|
+
* Phantom property used for TypeScript type inference.
|
|
9
|
+
* Does not exist at runtime.
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
readonly _outputType: Output;
|
|
13
|
+
_meta: SchemaMetadata<Output>;
|
|
14
|
+
/**
|
|
15
|
+
* Replace the schema metadata with a new object.
|
|
16
|
+
* @param meta The new metadata object.
|
|
17
|
+
* @returns The schema instance for chaining.
|
|
18
|
+
*/
|
|
19
|
+
meta(meta: SchemaMetadata<Output>): this;
|
|
20
|
+
/**
|
|
21
|
+
* Parses and validates the input data against the schema.
|
|
22
|
+
* Should throw an error if validation fails.
|
|
23
|
+
* @param data The data to parse.
|
|
24
|
+
* @returns The parsed data (potentially transformed).
|
|
25
|
+
*/
|
|
26
|
+
abstract parse(data: any): Output;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
declare class BooleanSchema extends BaseSchema<boolean> {
|
|
30
|
+
parse(data: any): boolean;
|
|
31
|
+
}
|
|
32
|
+
declare class StringSchema extends BaseSchema<string> {
|
|
33
|
+
parse(data: any): string;
|
|
34
|
+
}
|
|
35
|
+
declare class NumberSchema extends BaseSchema<number> {
|
|
36
|
+
parse(data: any): number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Type representing the shape of an object schema (key to schema mapping).
|
|
40
|
+
*/
|
|
41
|
+
type ObjectShape = {
|
|
42
|
+
[key: string]: BaseSchema<any>;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Infers the TypeScript output type from an ObjectShape.
|
|
46
|
+
* Use mapped types to get the output type of each property schema.
|
|
47
|
+
*/
|
|
48
|
+
type ObjectOutput<TShape extends ObjectShape> = {
|
|
49
|
+
[K in keyof TShape]: TShape[K]['_outputType'];
|
|
50
|
+
};
|
|
51
|
+
declare class ObjectSchema<TShape extends ObjectShape> extends BaseSchema<ObjectOutput<TShape>> {
|
|
52
|
+
_shape: TShape;
|
|
53
|
+
constructor(shape: TShape);
|
|
54
|
+
parse(data: any): ObjectOutput<TShape>;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Infers the TypeScript output type from an element schema for an array.
|
|
58
|
+
*/
|
|
59
|
+
type ArrayOutput<TElementSchema extends BaseSchema<any>> = Array<TElementSchema['_outputType']>;
|
|
60
|
+
declare class ArraySchema<TElementSchema extends BaseSchema<any>> extends BaseSchema<ArrayOutput<TElementSchema>> {
|
|
61
|
+
_element: TElementSchema;
|
|
62
|
+
constructor(elementSchema: TElementSchema);
|
|
63
|
+
parse(data: any): ArrayOutput<TElementSchema>;
|
|
64
|
+
}
|
|
65
|
+
type NuSchema = BooleanSchema | StringSchema | NumberSchema | ObjectSchema<any> | ArraySchema<any>;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The main nubase schema instance.
|
|
69
|
+
* Provides factory methods to create different schema types.
|
|
70
|
+
*/
|
|
71
|
+
declare const nu: {
|
|
72
|
+
boolean: () => BooleanSchema;
|
|
73
|
+
/**
|
|
74
|
+
* Creates a string schema.
|
|
75
|
+
*/
|
|
76
|
+
string: () => StringSchema;
|
|
77
|
+
/**
|
|
78
|
+
* Creates a number schema.
|
|
79
|
+
*/
|
|
80
|
+
number: () => NumberSchema;
|
|
81
|
+
/**
|
|
82
|
+
* Creates an object schema with a defined shape.
|
|
83
|
+
* @param shape An object mapping keys to schemas.
|
|
84
|
+
*/
|
|
85
|
+
object: <TShape extends ObjectShape>(shape: TShape) => ObjectSchema<TShape>;
|
|
86
|
+
/**
|
|
87
|
+
* Creates an array schema with a defined element schema.
|
|
88
|
+
* @param elementSchema The schema for elements within the array.
|
|
89
|
+
*/
|
|
90
|
+
array: <TElementSchema extends BaseSchema<any>>(elementSchema: TElementSchema) => ArraySchema<TElementSchema>;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export { type ArrayOutput, ArraySchema, BaseSchema, BooleanSchema, type NuSchema, NumberSchema, type ObjectOutput, ObjectSchema, type ObjectShape, type SchemaMetadata, StringSchema, nu };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
interface SchemaMetadata<Output = any> {
|
|
2
|
+
label?: string;
|
|
3
|
+
description?: string;
|
|
4
|
+
defaultValue?: Output;
|
|
5
|
+
}
|
|
6
|
+
declare abstract class BaseSchema<Output = any> {
|
|
7
|
+
/**
|
|
8
|
+
* Phantom property used for TypeScript type inference.
|
|
9
|
+
* Does not exist at runtime.
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
readonly _outputType: Output;
|
|
13
|
+
_meta: SchemaMetadata<Output>;
|
|
14
|
+
/**
|
|
15
|
+
* Replace the schema metadata with a new object.
|
|
16
|
+
* @param meta The new metadata object.
|
|
17
|
+
* @returns The schema instance for chaining.
|
|
18
|
+
*/
|
|
19
|
+
meta(meta: SchemaMetadata<Output>): this;
|
|
20
|
+
/**
|
|
21
|
+
* Parses and validates the input data against the schema.
|
|
22
|
+
* Should throw an error if validation fails.
|
|
23
|
+
* @param data The data to parse.
|
|
24
|
+
* @returns The parsed data (potentially transformed).
|
|
25
|
+
*/
|
|
26
|
+
abstract parse(data: any): Output;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
declare class BooleanSchema extends BaseSchema<boolean> {
|
|
30
|
+
parse(data: any): boolean;
|
|
31
|
+
}
|
|
32
|
+
declare class StringSchema extends BaseSchema<string> {
|
|
33
|
+
parse(data: any): string;
|
|
34
|
+
}
|
|
35
|
+
declare class NumberSchema extends BaseSchema<number> {
|
|
36
|
+
parse(data: any): number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Type representing the shape of an object schema (key to schema mapping).
|
|
40
|
+
*/
|
|
41
|
+
type ObjectShape = {
|
|
42
|
+
[key: string]: BaseSchema<any>;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Infers the TypeScript output type from an ObjectShape.
|
|
46
|
+
* Use mapped types to get the output type of each property schema.
|
|
47
|
+
*/
|
|
48
|
+
type ObjectOutput<TShape extends ObjectShape> = {
|
|
49
|
+
[K in keyof TShape]: TShape[K]['_outputType'];
|
|
50
|
+
};
|
|
51
|
+
declare class ObjectSchema<TShape extends ObjectShape> extends BaseSchema<ObjectOutput<TShape>> {
|
|
52
|
+
_shape: TShape;
|
|
53
|
+
constructor(shape: TShape);
|
|
54
|
+
parse(data: any): ObjectOutput<TShape>;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Infers the TypeScript output type from an element schema for an array.
|
|
58
|
+
*/
|
|
59
|
+
type ArrayOutput<TElementSchema extends BaseSchema<any>> = Array<TElementSchema['_outputType']>;
|
|
60
|
+
declare class ArraySchema<TElementSchema extends BaseSchema<any>> extends BaseSchema<ArrayOutput<TElementSchema>> {
|
|
61
|
+
_element: TElementSchema;
|
|
62
|
+
constructor(elementSchema: TElementSchema);
|
|
63
|
+
parse(data: any): ArrayOutput<TElementSchema>;
|
|
64
|
+
}
|
|
65
|
+
type NuSchema = BooleanSchema | StringSchema | NumberSchema | ObjectSchema<any> | ArraySchema<any>;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The main nubase schema instance.
|
|
69
|
+
* Provides factory methods to create different schema types.
|
|
70
|
+
*/
|
|
71
|
+
declare const nu: {
|
|
72
|
+
boolean: () => BooleanSchema;
|
|
73
|
+
/**
|
|
74
|
+
* Creates a string schema.
|
|
75
|
+
*/
|
|
76
|
+
string: () => StringSchema;
|
|
77
|
+
/**
|
|
78
|
+
* Creates a number schema.
|
|
79
|
+
*/
|
|
80
|
+
number: () => NumberSchema;
|
|
81
|
+
/**
|
|
82
|
+
* Creates an object schema with a defined shape.
|
|
83
|
+
* @param shape An object mapping keys to schemas.
|
|
84
|
+
*/
|
|
85
|
+
object: <TShape extends ObjectShape>(shape: TShape) => ObjectSchema<TShape>;
|
|
86
|
+
/**
|
|
87
|
+
* Creates an array schema with a defined element schema.
|
|
88
|
+
* @param elementSchema The schema for elements within the array.
|
|
89
|
+
*/
|
|
90
|
+
array: <TElementSchema extends BaseSchema<any>>(elementSchema: TElementSchema) => ArraySchema<TElementSchema>;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export { type ArrayOutput, ArraySchema, BaseSchema, BooleanSchema, type NuSchema, NumberSchema, type ObjectOutput, ObjectSchema, type ObjectShape, type SchemaMetadata, StringSchema, nu };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// schema/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
ArraySchema: () => ArraySchema,
|
|
24
|
+
BaseSchema: () => BaseSchema,
|
|
25
|
+
BooleanSchema: () => BooleanSchema,
|
|
26
|
+
NumberSchema: () => NumberSchema,
|
|
27
|
+
ObjectSchema: () => ObjectSchema,
|
|
28
|
+
StringSchema: () => StringSchema,
|
|
29
|
+
nu: () => nu
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(index_exports);
|
|
32
|
+
|
|
33
|
+
// schema/base-schema.ts
|
|
34
|
+
var BaseSchema = class {
|
|
35
|
+
/**
|
|
36
|
+
* Phantom property used for TypeScript type inference.
|
|
37
|
+
* Does not exist at runtime.
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
_outputType;
|
|
41
|
+
_meta = {};
|
|
42
|
+
/**
|
|
43
|
+
* Replace the schema metadata with a new object.
|
|
44
|
+
* @param meta The new metadata object.
|
|
45
|
+
* @returns The schema instance for chaining.
|
|
46
|
+
*/
|
|
47
|
+
meta(meta) {
|
|
48
|
+
this._meta = meta;
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
// We could also add a safeParse method like Zod if needed
|
|
52
|
+
// safeParse(data: any): { success: true; data: Output } | { success: false; error: SchemaError };
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// schema/types.ts
|
|
56
|
+
var BooleanSchema = class extends BaseSchema {
|
|
57
|
+
parse(data) {
|
|
58
|
+
if (typeof data !== "boolean") {
|
|
59
|
+
throw new Error(`Expected boolean, received ${typeof data}`);
|
|
60
|
+
}
|
|
61
|
+
return data;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
var StringSchema = class extends BaseSchema {
|
|
65
|
+
parse(data) {
|
|
66
|
+
if (typeof data !== "string") {
|
|
67
|
+
throw new Error(`Expected string, received ${typeof data}`);
|
|
68
|
+
}
|
|
69
|
+
return data;
|
|
70
|
+
}
|
|
71
|
+
// Add string-specific validation methods here (e.g., minLength, pattern)
|
|
72
|
+
};
|
|
73
|
+
var NumberSchema = class extends BaseSchema {
|
|
74
|
+
parse(data) {
|
|
75
|
+
if (typeof data !== "number" || !Number.isFinite(data)) {
|
|
76
|
+
throw new Error(`Expected number, received ${typeof data}`);
|
|
77
|
+
}
|
|
78
|
+
return data;
|
|
79
|
+
}
|
|
80
|
+
// Add number-specific validation methods here (e.g., min, max)
|
|
81
|
+
};
|
|
82
|
+
var ObjectSchema = class extends BaseSchema {
|
|
83
|
+
_shape;
|
|
84
|
+
constructor(shape) {
|
|
85
|
+
super();
|
|
86
|
+
this._shape = shape;
|
|
87
|
+
}
|
|
88
|
+
parse(data) {
|
|
89
|
+
if (typeof data !== "object" || data === null) {
|
|
90
|
+
throw new Error(`Expected object, received ${typeof data}`);
|
|
91
|
+
}
|
|
92
|
+
const result = {};
|
|
93
|
+
const errors = [];
|
|
94
|
+
for (const key in this._shape) {
|
|
95
|
+
if (Object.prototype.hasOwnProperty.call(this._shape, key)) {
|
|
96
|
+
const schema = this._shape[key];
|
|
97
|
+
const value = data[key];
|
|
98
|
+
try {
|
|
99
|
+
if (schema) {
|
|
100
|
+
result[key] = schema.parse(value);
|
|
101
|
+
}
|
|
102
|
+
} catch (e) {
|
|
103
|
+
errors.push(`Property "${key}": ${e.message}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
for (const key in data) {
|
|
108
|
+
if (Object.prototype.hasOwnProperty.call(data, key) && !Object.prototype.hasOwnProperty.call(this._shape, key)) {
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (errors.length > 0) {
|
|
112
|
+
throw new Error(`Object validation failed:
|
|
113
|
+
${errors.join("\n")}`);
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
var ArraySchema = class extends BaseSchema {
|
|
119
|
+
_element;
|
|
120
|
+
constructor(elementSchema) {
|
|
121
|
+
super();
|
|
122
|
+
this._element = elementSchema;
|
|
123
|
+
}
|
|
124
|
+
parse(data) {
|
|
125
|
+
if (!Array.isArray(data)) {
|
|
126
|
+
throw new Error(`Expected array, received ${typeof data}`);
|
|
127
|
+
}
|
|
128
|
+
const result = [];
|
|
129
|
+
const errors = [];
|
|
130
|
+
for (let i = 0; i < data.length; i++) {
|
|
131
|
+
try {
|
|
132
|
+
result[i] = this._element.parse(data[i]);
|
|
133
|
+
} catch (e) {
|
|
134
|
+
errors.push(`Element at index ${i}: ${e.message}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (errors.length > 0) {
|
|
138
|
+
throw new Error(`Array validation failed:
|
|
139
|
+
${errors.join("\n")}`);
|
|
140
|
+
}
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// schema/nu.ts
|
|
146
|
+
var nu = {
|
|
147
|
+
boolean: () => new BooleanSchema(),
|
|
148
|
+
/**
|
|
149
|
+
* Creates a string schema.
|
|
150
|
+
*/
|
|
151
|
+
string: () => new StringSchema(),
|
|
152
|
+
/**
|
|
153
|
+
* Creates a number schema.
|
|
154
|
+
*/
|
|
155
|
+
number: () => new NumberSchema(),
|
|
156
|
+
/**
|
|
157
|
+
* Creates an object schema with a defined shape.
|
|
158
|
+
* @param shape An object mapping keys to schemas.
|
|
159
|
+
*/
|
|
160
|
+
object: (shape) => new ObjectSchema(shape),
|
|
161
|
+
/**
|
|
162
|
+
* Creates an array schema with a defined element schema.
|
|
163
|
+
* @param elementSchema The schema for elements within the array.
|
|
164
|
+
*/
|
|
165
|
+
array: (elementSchema) => new ArraySchema(elementSchema)
|
|
166
|
+
// Add more factory methods here (boolean, union, literal, etc.)
|
|
167
|
+
};
|
|
168
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
169
|
+
0 && (module.exports = {
|
|
170
|
+
ArraySchema,
|
|
171
|
+
BaseSchema,
|
|
172
|
+
BooleanSchema,
|
|
173
|
+
NumberSchema,
|
|
174
|
+
ObjectSchema,
|
|
175
|
+
StringSchema,
|
|
176
|
+
nu
|
|
177
|
+
});
|
|
178
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../schema/index.ts","../schema/base-schema.ts","../schema/types.ts","../schema/nu.ts"],"sourcesContent":["export * from './types';\nexport { BaseSchema, type SchemaMetadata } from './base-schema';\nexport * from './nu';\n","// src/schema/BaseSchema.ts\n\nimport { z } from 'zod'; // We'll need Zod later for toZod conversion\n\n// Define metadata types\nexport interface SchemaMetadata<Output = any> {\n label?: string;\n description?: string;\n defaultValue?: Output;\n}\n\n// Base schema class\nexport abstract class BaseSchema<Output = any> {\n /**\n * Phantom property used for TypeScript type inference.\n * Does not exist at runtime.\n * @internal\n */\n readonly _outputType!: Output;\n\n _meta: SchemaMetadata<Output> = {};\n\n /**\n * Replace the schema metadata with a new object.\n * @param meta The new metadata object.\n * @returns The schema instance for chaining.\n */\n meta(meta: SchemaMetadata<Output>): this {\n this._meta = meta;\n return this;\n }\n\n /**\n * Parses and validates the input data against the schema.\n * Should throw an error if validation fails.\n * @param data The data to parse.\n * @returns The parsed data (potentially transformed).\n */\n abstract parse(data: any): Output;\n\n // We could also add a safeParse method like Zod if needed\n // safeParse(data: any): { success: true; data: Output } | { success: false; error: SchemaError };\n}\n\n// Define a union type of all concrete schema types for easier handling\nexport type NuSchema = BaseSchema<any>; // This will be refined later","// src/schema/types.ts\n\nimport { BaseSchema } from './base-schema';\n\n// --- Primitive Schemas ---\n\nexport class BooleanSchema extends BaseSchema<boolean> {\n parse(data: any): boolean {\n if (typeof data !== 'boolean') {\n throw new Error(`Expected boolean, received ${typeof data}`);\n }\n return data;\n }\n}\n\nexport class StringSchema extends BaseSchema<string> {\n parse(data: any): string {\n if (typeof data !== 'string') {\n throw new Error(`Expected string, received ${typeof data}`);\n }\n return data;\n }\n // Add string-specific validation methods here (e.g., minLength, pattern)\n}\n\nexport class NumberSchema extends BaseSchema<number> {\n parse(data: any): number {\n if (typeof data !== 'number' || !Number.isFinite(data)) {\n throw new Error(`Expected number, received ${typeof data}`);\n }\n return data;\n }\n // Add number-specific validation methods here (e.g., min, max)\n}\n\n// Add more primitives like BooleanSchema, NullableSchema, OptionalSchema etc.\n\n// --- Complex Schemas ---\n\n/**\n * Type representing the shape of an object schema (key to schema mapping).\n */\nexport type ObjectShape = {\n [key: string]: BaseSchema<any>;\n};\n\n/**\n * Infers the TypeScript output type from an ObjectShape.\n * Use mapped types to get the output type of each property schema.\n */\nexport type ObjectOutput<TShape extends ObjectShape> = {\n [K in keyof TShape]: TShape[K]['_outputType'];\n};\n\nexport class ObjectSchema<TShape extends ObjectShape> extends BaseSchema<ObjectOutput<TShape>> {\n _shape: TShape;\n\n constructor(shape: TShape) {\n super();\n this._shape = shape;\n }\n\n parse(data: any): ObjectOutput<TShape> {\n if (typeof data !== 'object' || data === null) {\n throw new Error(`Expected object, received ${typeof data}`);\n }\n\n const result: any = {};\n const errors: string[] = [];\n\n // Validate defined properties\n for (const key in this._shape) {\n if (Object.prototype.hasOwnProperty.call(this._shape, key)) {\n const schema = this._shape[key];\n const value = (data as any)[key]; // Get value from input data\n try {\n if (schema) {\n result[key] = schema.parse(value); // Recursively parse property\n }\n } catch (e: any) {\n errors.push(`Property \"${key}\": ${e.message}`);\n }\n }\n }\n\n // Basic handling for extra keys (can be made configurable)\n for (const key in data) {\n if (Object.prototype.hasOwnProperty.call(data, key) && !Object.prototype.hasOwnProperty.call(this._shape, key)) {\n // For now, ignore extra keys. Could throw an error or strip them.\n // errors.push(`Unknown property \"${key}\"`);\n // console.warn(`Warning: Unknown property \"${key}\" in object data.`);\n }\n }\n\n\n if (errors.length > 0) {\n throw new Error(`Object validation failed:\\n${errors.join('\\n')}`);\n }\n\n return result as ObjectOutput<TShape>;\n }\n}\n\n/**\n * Infers the TypeScript output type from an element schema for an array.\n */\nexport type ArrayOutput<TElementSchema extends BaseSchema<any>> = Array<TElementSchema['_outputType']>;\n\n\nexport class ArraySchema<TElementSchema extends BaseSchema<any>> extends BaseSchema<ArrayOutput<TElementSchema>> {\n _element: TElementSchema;\n\n constructor(elementSchema: TElementSchema) {\n super();\n this._element = elementSchema;\n }\n\n parse(data: any): ArrayOutput<TElementSchema> {\n if (!Array.isArray(data)) {\n throw new Error(`Expected array, received ${typeof data}`);\n }\n\n const result: any[] = [];\n const errors: string[] = [];\n\n for (let i = 0; i < data.length; i++) {\n try {\n result[i] = this._element.parse(data[i]); // Recursively parse each element\n } catch (e: any) {\n errors.push(`Element at index ${i}: ${e.message}`);\n }\n }\n\n if (errors.length > 0) {\n throw new Error(`Array validation failed:\\n${errors.join('\\n')}`);\n }\n\n return result as ArrayOutput<TElementSchema>;\n }\n}\n\n// Refine NuSchema union after defining concrete types\nexport type NuSchema =\n | BooleanSchema\n | StringSchema\n | NumberSchema\n // Add others like BooleanSchema\n | ObjectSchema<any>\n | ArraySchema<any>;","// src/nu.ts\n\nimport { BaseSchema } from \"./base-schema\";\nimport { StringSchema, NumberSchema, ObjectShape, ObjectSchema, ArraySchema, BooleanSchema } from \"./types\";\n\n\n/**\n * The main nubase schema instance.\n * Provides factory methods to create different schema types.\n */\nexport const nu = {\n boolean : () => new BooleanSchema(),\n /**\n * Creates a string schema.\n */\n string: () => new StringSchema(),\n\n /**\n * Creates a number schema.\n */\n number: () => new NumberSchema(),\n\n /**\n * Creates an object schema with a defined shape.\n * @param shape An object mapping keys to schemas.\n */\n object: <TShape extends ObjectShape>(shape: TShape) => new ObjectSchema(shape),\n\n /**\n * Creates an array schema with a defined element schema.\n * @param elementSchema The schema for elements within the array.\n */\n array: <TElementSchema extends BaseSchema<any>>(elementSchema: TElementSchema) => new ArraySchema(elementSchema),\n\n // Add more factory methods here (boolean, union, literal, etc.)\n};"],"mappings":"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","names":[]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// schema/base-schema.ts
|
|
2
|
+
var BaseSchema = class {
|
|
3
|
+
/**
|
|
4
|
+
* Phantom property used for TypeScript type inference.
|
|
5
|
+
* Does not exist at runtime.
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
_outputType;
|
|
9
|
+
_meta = {};
|
|
10
|
+
/**
|
|
11
|
+
* Replace the schema metadata with a new object.
|
|
12
|
+
* @param meta The new metadata object.
|
|
13
|
+
* @returns The schema instance for chaining.
|
|
14
|
+
*/
|
|
15
|
+
meta(meta) {
|
|
16
|
+
this._meta = meta;
|
|
17
|
+
return this;
|
|
18
|
+
}
|
|
19
|
+
// We could also add a safeParse method like Zod if needed
|
|
20
|
+
// safeParse(data: any): { success: true; data: Output } | { success: false; error: SchemaError };
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// schema/types.ts
|
|
24
|
+
var BooleanSchema = class extends BaseSchema {
|
|
25
|
+
parse(data) {
|
|
26
|
+
if (typeof data !== "boolean") {
|
|
27
|
+
throw new Error(`Expected boolean, received ${typeof data}`);
|
|
28
|
+
}
|
|
29
|
+
return data;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var StringSchema = class extends BaseSchema {
|
|
33
|
+
parse(data) {
|
|
34
|
+
if (typeof data !== "string") {
|
|
35
|
+
throw new Error(`Expected string, received ${typeof data}`);
|
|
36
|
+
}
|
|
37
|
+
return data;
|
|
38
|
+
}
|
|
39
|
+
// Add string-specific validation methods here (e.g., minLength, pattern)
|
|
40
|
+
};
|
|
41
|
+
var NumberSchema = class extends BaseSchema {
|
|
42
|
+
parse(data) {
|
|
43
|
+
if (typeof data !== "number" || !Number.isFinite(data)) {
|
|
44
|
+
throw new Error(`Expected number, received ${typeof data}`);
|
|
45
|
+
}
|
|
46
|
+
return data;
|
|
47
|
+
}
|
|
48
|
+
// Add number-specific validation methods here (e.g., min, max)
|
|
49
|
+
};
|
|
50
|
+
var ObjectSchema = class extends BaseSchema {
|
|
51
|
+
_shape;
|
|
52
|
+
constructor(shape) {
|
|
53
|
+
super();
|
|
54
|
+
this._shape = shape;
|
|
55
|
+
}
|
|
56
|
+
parse(data) {
|
|
57
|
+
if (typeof data !== "object" || data === null) {
|
|
58
|
+
throw new Error(`Expected object, received ${typeof data}`);
|
|
59
|
+
}
|
|
60
|
+
const result = {};
|
|
61
|
+
const errors = [];
|
|
62
|
+
for (const key in this._shape) {
|
|
63
|
+
if (Object.prototype.hasOwnProperty.call(this._shape, key)) {
|
|
64
|
+
const schema = this._shape[key];
|
|
65
|
+
const value = data[key];
|
|
66
|
+
try {
|
|
67
|
+
if (schema) {
|
|
68
|
+
result[key] = schema.parse(value);
|
|
69
|
+
}
|
|
70
|
+
} catch (e) {
|
|
71
|
+
errors.push(`Property "${key}": ${e.message}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
for (const key in data) {
|
|
76
|
+
if (Object.prototype.hasOwnProperty.call(data, key) && !Object.prototype.hasOwnProperty.call(this._shape, key)) {
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (errors.length > 0) {
|
|
80
|
+
throw new Error(`Object validation failed:
|
|
81
|
+
${errors.join("\n")}`);
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
var ArraySchema = class extends BaseSchema {
|
|
87
|
+
_element;
|
|
88
|
+
constructor(elementSchema) {
|
|
89
|
+
super();
|
|
90
|
+
this._element = elementSchema;
|
|
91
|
+
}
|
|
92
|
+
parse(data) {
|
|
93
|
+
if (!Array.isArray(data)) {
|
|
94
|
+
throw new Error(`Expected array, received ${typeof data}`);
|
|
95
|
+
}
|
|
96
|
+
const result = [];
|
|
97
|
+
const errors = [];
|
|
98
|
+
for (let i = 0; i < data.length; i++) {
|
|
99
|
+
try {
|
|
100
|
+
result[i] = this._element.parse(data[i]);
|
|
101
|
+
} catch (e) {
|
|
102
|
+
errors.push(`Element at index ${i}: ${e.message}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (errors.length > 0) {
|
|
106
|
+
throw new Error(`Array validation failed:
|
|
107
|
+
${errors.join("\n")}`);
|
|
108
|
+
}
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// schema/nu.ts
|
|
114
|
+
var nu = {
|
|
115
|
+
boolean: () => new BooleanSchema(),
|
|
116
|
+
/**
|
|
117
|
+
* Creates a string schema.
|
|
118
|
+
*/
|
|
119
|
+
string: () => new StringSchema(),
|
|
120
|
+
/**
|
|
121
|
+
* Creates a number schema.
|
|
122
|
+
*/
|
|
123
|
+
number: () => new NumberSchema(),
|
|
124
|
+
/**
|
|
125
|
+
* Creates an object schema with a defined shape.
|
|
126
|
+
* @param shape An object mapping keys to schemas.
|
|
127
|
+
*/
|
|
128
|
+
object: (shape) => new ObjectSchema(shape),
|
|
129
|
+
/**
|
|
130
|
+
* Creates an array schema with a defined element schema.
|
|
131
|
+
* @param elementSchema The schema for elements within the array.
|
|
132
|
+
*/
|
|
133
|
+
array: (elementSchema) => new ArraySchema(elementSchema)
|
|
134
|
+
// Add more factory methods here (boolean, union, literal, etc.)
|
|
135
|
+
};
|
|
136
|
+
export {
|
|
137
|
+
ArraySchema,
|
|
138
|
+
BaseSchema,
|
|
139
|
+
BooleanSchema,
|
|
140
|
+
NumberSchema,
|
|
141
|
+
ObjectSchema,
|
|
142
|
+
StringSchema,
|
|
143
|
+
nu
|
|
144
|
+
};
|
|
145
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../schema/base-schema.ts","../schema/types.ts","../schema/nu.ts"],"sourcesContent":["// src/schema/BaseSchema.ts\n\nimport { z } from 'zod'; // We'll need Zod later for toZod conversion\n\n// Define metadata types\nexport interface SchemaMetadata<Output = any> {\n label?: string;\n description?: string;\n defaultValue?: Output;\n}\n\n// Base schema class\nexport abstract class BaseSchema<Output = any> {\n /**\n * Phantom property used for TypeScript type inference.\n * Does not exist at runtime.\n * @internal\n */\n readonly _outputType!: Output;\n\n _meta: SchemaMetadata<Output> = {};\n\n /**\n * Replace the schema metadata with a new object.\n * @param meta The new metadata object.\n * @returns The schema instance for chaining.\n */\n meta(meta: SchemaMetadata<Output>): this {\n this._meta = meta;\n return this;\n }\n\n /**\n * Parses and validates the input data against the schema.\n * Should throw an error if validation fails.\n * @param data The data to parse.\n * @returns The parsed data (potentially transformed).\n */\n abstract parse(data: any): Output;\n\n // We could also add a safeParse method like Zod if needed\n // safeParse(data: any): { success: true; data: Output } | { success: false; error: SchemaError };\n}\n\n// Define a union type of all concrete schema types for easier handling\nexport type NuSchema = BaseSchema<any>; // This will be refined later","// src/schema/types.ts\n\nimport { BaseSchema } from './base-schema';\n\n// --- Primitive Schemas ---\n\nexport class BooleanSchema extends BaseSchema<boolean> {\n parse(data: any): boolean {\n if (typeof data !== 'boolean') {\n throw new Error(`Expected boolean, received ${typeof data}`);\n }\n return data;\n }\n}\n\nexport class StringSchema extends BaseSchema<string> {\n parse(data: any): string {\n if (typeof data !== 'string') {\n throw new Error(`Expected string, received ${typeof data}`);\n }\n return data;\n }\n // Add string-specific validation methods here (e.g., minLength, pattern)\n}\n\nexport class NumberSchema extends BaseSchema<number> {\n parse(data: any): number {\n if (typeof data !== 'number' || !Number.isFinite(data)) {\n throw new Error(`Expected number, received ${typeof data}`);\n }\n return data;\n }\n // Add number-specific validation methods here (e.g., min, max)\n}\n\n// Add more primitives like BooleanSchema, NullableSchema, OptionalSchema etc.\n\n// --- Complex Schemas ---\n\n/**\n * Type representing the shape of an object schema (key to schema mapping).\n */\nexport type ObjectShape = {\n [key: string]: BaseSchema<any>;\n};\n\n/**\n * Infers the TypeScript output type from an ObjectShape.\n * Use mapped types to get the output type of each property schema.\n */\nexport type ObjectOutput<TShape extends ObjectShape> = {\n [K in keyof TShape]: TShape[K]['_outputType'];\n};\n\nexport class ObjectSchema<TShape extends ObjectShape> extends BaseSchema<ObjectOutput<TShape>> {\n _shape: TShape;\n\n constructor(shape: TShape) {\n super();\n this._shape = shape;\n }\n\n parse(data: any): ObjectOutput<TShape> {\n if (typeof data !== 'object' || data === null) {\n throw new Error(`Expected object, received ${typeof data}`);\n }\n\n const result: any = {};\n const errors: string[] = [];\n\n // Validate defined properties\n for (const key in this._shape) {\n if (Object.prototype.hasOwnProperty.call(this._shape, key)) {\n const schema = this._shape[key];\n const value = (data as any)[key]; // Get value from input data\n try {\n if (schema) {\n result[key] = schema.parse(value); // Recursively parse property\n }\n } catch (e: any) {\n errors.push(`Property \"${key}\": ${e.message}`);\n }\n }\n }\n\n // Basic handling for extra keys (can be made configurable)\n for (const key in data) {\n if (Object.prototype.hasOwnProperty.call(data, key) && !Object.prototype.hasOwnProperty.call(this._shape, key)) {\n // For now, ignore extra keys. Could throw an error or strip them.\n // errors.push(`Unknown property \"${key}\"`);\n // console.warn(`Warning: Unknown property \"${key}\" in object data.`);\n }\n }\n\n\n if (errors.length > 0) {\n throw new Error(`Object validation failed:\\n${errors.join('\\n')}`);\n }\n\n return result as ObjectOutput<TShape>;\n }\n}\n\n/**\n * Infers the TypeScript output type from an element schema for an array.\n */\nexport type ArrayOutput<TElementSchema extends BaseSchema<any>> = Array<TElementSchema['_outputType']>;\n\n\nexport class ArraySchema<TElementSchema extends BaseSchema<any>> extends BaseSchema<ArrayOutput<TElementSchema>> {\n _element: TElementSchema;\n\n constructor(elementSchema: TElementSchema) {\n super();\n this._element = elementSchema;\n }\n\n parse(data: any): ArrayOutput<TElementSchema> {\n if (!Array.isArray(data)) {\n throw new Error(`Expected array, received ${typeof data}`);\n }\n\n const result: any[] = [];\n const errors: string[] = [];\n\n for (let i = 0; i < data.length; i++) {\n try {\n result[i] = this._element.parse(data[i]); // Recursively parse each element\n } catch (e: any) {\n errors.push(`Element at index ${i}: ${e.message}`);\n }\n }\n\n if (errors.length > 0) {\n throw new Error(`Array validation failed:\\n${errors.join('\\n')}`);\n }\n\n return result as ArrayOutput<TElementSchema>;\n }\n}\n\n// Refine NuSchema union after defining concrete types\nexport type NuSchema =\n | BooleanSchema\n | StringSchema\n | NumberSchema\n // Add others like BooleanSchema\n | ObjectSchema<any>\n | ArraySchema<any>;","// src/nu.ts\n\nimport { BaseSchema } from \"./base-schema\";\nimport { StringSchema, NumberSchema, ObjectShape, ObjectSchema, ArraySchema, BooleanSchema } from \"./types\";\n\n\n/**\n * The main nubase schema instance.\n * Provides factory methods to create different schema types.\n */\nexport const nu = {\n boolean : () => new BooleanSchema(),\n /**\n * Creates a string schema.\n */\n string: () => new StringSchema(),\n\n /**\n * Creates a number schema.\n */\n number: () => new NumberSchema(),\n\n /**\n * Creates an object schema with a defined shape.\n * @param shape An object mapping keys to schemas.\n */\n object: <TShape extends ObjectShape>(shape: TShape) => new ObjectSchema(shape),\n\n /**\n * Creates an array schema with a defined element schema.\n * @param elementSchema The schema for elements within the array.\n */\n array: <TElementSchema extends BaseSchema<any>>(elementSchema: TElementSchema) => new ArraySchema(elementSchema),\n\n // Add more factory methods here (boolean, union, literal, etc.)\n};"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nubase/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Core schema and types for nubase",
|
|
5
|
+
"keywords": ["schema", "types", "validation", "zod"],
|
|
6
|
+
"sideEffects": [
|
|
7
|
+
"**/*.css"
|
|
8
|
+
],
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"schema"
|
|
12
|
+
],
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"module": "./dist/index.mjs",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.mjs",
|
|
20
|
+
"require": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./schema": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"import": "./dist/index.mjs",
|
|
25
|
+
"require": "./dist/index.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsup",
|
|
34
|
+
"test": "vitest",
|
|
35
|
+
"typecheck": "tsc --noEmit",
|
|
36
|
+
"prepublishOnly": "npm run build"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"tsup": "^8.0.0",
|
|
40
|
+
"typescript": "^5.8.3",
|
|
41
|
+
"vite": "^6.3.5",
|
|
42
|
+
"vitest": "^3.1.4"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"zod": "^3.25.20"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/schema/BaseSchema.ts
|
|
2
|
+
|
|
3
|
+
import { z } from 'zod'; // We'll need Zod later for toZod conversion
|
|
4
|
+
|
|
5
|
+
// Define metadata types
|
|
6
|
+
export interface SchemaMetadata<Output = any> {
|
|
7
|
+
label?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
defaultValue?: Output;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Base schema class
|
|
13
|
+
export abstract class BaseSchema<Output = any> {
|
|
14
|
+
/**
|
|
15
|
+
* Phantom property used for TypeScript type inference.
|
|
16
|
+
* Does not exist at runtime.
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
readonly _outputType!: Output;
|
|
20
|
+
|
|
21
|
+
_meta: SchemaMetadata<Output> = {};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Replace the schema metadata with a new object.
|
|
25
|
+
* @param meta The new metadata object.
|
|
26
|
+
* @returns The schema instance for chaining.
|
|
27
|
+
*/
|
|
28
|
+
meta(meta: SchemaMetadata<Output>): this {
|
|
29
|
+
this._meta = meta;
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Parses and validates the input data against the schema.
|
|
35
|
+
* Should throw an error if validation fails.
|
|
36
|
+
* @param data The data to parse.
|
|
37
|
+
* @returns The parsed data (potentially transformed).
|
|
38
|
+
*/
|
|
39
|
+
abstract parse(data: any): Output;
|
|
40
|
+
|
|
41
|
+
// We could also add a safeParse method like Zod if needed
|
|
42
|
+
// safeParse(data: any): { success: true; data: Output } | { success: false; error: SchemaError };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Define a union type of all concrete schema types for easier handling
|
|
46
|
+
export type NuSchema = BaseSchema<any>; // This will be refined later
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { BaseSchema } from "./base-schema"
|
|
2
|
+
|
|
3
|
+
export interface ResourceDescriptor {
|
|
4
|
+
search: ResourceOperationDescriptor
|
|
5
|
+
view: ResourceOperationDescriptor
|
|
6
|
+
edit: ResourceOperationDescriptor
|
|
7
|
+
create: ResourceOperationDescriptor
|
|
8
|
+
delete: ResourceOperationDescriptor
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type ResourceOperationDescriptor = {
|
|
12
|
+
// /r/contacts
|
|
13
|
+
resourceName: string,
|
|
14
|
+
endpoint: string
|
|
15
|
+
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
|
|
16
|
+
querySchema?: BaseSchema,
|
|
17
|
+
bodySchema?: BaseSchema,
|
|
18
|
+
responseSchema?: BaseSchema
|
|
19
|
+
}
|
package/schema/index.ts
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
// src/tests/nu.test.ts
|
|
2
|
+
|
|
3
|
+
import { describe, it, expect, expectTypeOf } from 'vitest';
|
|
4
|
+
import { nu } from './nu';
|
|
5
|
+
import { toZod } from './toZod';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
|
|
8
|
+
describe('nubase Schema Library (nu)', () => {
|
|
9
|
+
|
|
10
|
+
// --- Basic Schema Creation and Metadata ---
|
|
11
|
+
it('should create a string schema with metadata', () => {
|
|
12
|
+
const stringSchema = nu.string().meta({
|
|
13
|
+
label: 'Username',
|
|
14
|
+
description: 'The user\'s login name',
|
|
15
|
+
})
|
|
16
|
+
expect(stringSchema).toBeDefined();
|
|
17
|
+
expect(stringSchema._meta.label).toBe('Username');
|
|
18
|
+
expect(stringSchema._meta.description).toBe('The user\'s login name');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should create a number schema with metadata', () => {
|
|
22
|
+
const numberSchema = nu.number().meta({
|
|
23
|
+
label: 'Age',
|
|
24
|
+
});
|
|
25
|
+
expect(numberSchema).toBeDefined();
|
|
26
|
+
expect(numberSchema._meta.label).toBe('Age');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should create an object schema with nested schemas and metadata', () => {
|
|
30
|
+
const objectSchema = nu.object({
|
|
31
|
+
id: nu.number(),
|
|
32
|
+
name: nu.string().meta({
|
|
33
|
+
label: 'Full Name',
|
|
34
|
+
}),
|
|
35
|
+
address: nu.object({
|
|
36
|
+
street: nu.string(),
|
|
37
|
+
city: nu.string(),
|
|
38
|
+
zip: nu.string(),
|
|
39
|
+
}).meta({
|
|
40
|
+
label: 'Address',
|
|
41
|
+
description: 'User\'s mailing address',
|
|
42
|
+
}),
|
|
43
|
+
}).meta({
|
|
44
|
+
description: 'User Profile',
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
expect(objectSchema).toBeDefined();
|
|
48
|
+
expect(objectSchema._shape.id).toBeDefined();
|
|
49
|
+
expect(objectSchema._shape.name).toBeDefined();
|
|
50
|
+
expect(objectSchema._shape.name._meta.label).toBe('Full Name');
|
|
51
|
+
expect(objectSchema._meta.description).toBe('User Profile');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should create an array schema with an element schema', () => {
|
|
55
|
+
const arraySchema = nu.array(nu.string().meta({ label: 'Item' })).meta({ label: 'List of Items' });
|
|
56
|
+
expect(arraySchema).toBeDefined();
|
|
57
|
+
expect(arraySchema._element).toBeDefined();
|
|
58
|
+
expect(arraySchema._element._meta.label).toBe('Item');
|
|
59
|
+
expect(arraySchema._meta.label).toBe('List of Items');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// --- Parse/Validation Tests ---
|
|
63
|
+
it('should parse valid string data', () => {
|
|
64
|
+
const stringSchema = nu.string();
|
|
65
|
+
expect(stringSchema.parse('hello')).toBe('hello');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should throw error for invalid string data', () => {
|
|
69
|
+
const stringSchema = nu.string();
|
|
70
|
+
expect(() => stringSchema.parse(123)).toThrow('Expected string, received number');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should parse valid number data', () => {
|
|
74
|
+
const numberSchema = nu.number();
|
|
75
|
+
expect(numberSchema.parse(123)).toBe(123);
|
|
76
|
+
expect(numberSchema.parse(123.45)).toBe(123.45);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should throw error for invalid number data', () => {
|
|
80
|
+
const numberSchema = nu.number();
|
|
81
|
+
expect(() => numberSchema.parse('abc')).toThrow('Expected number, received string');
|
|
82
|
+
expect(() => numberSchema.parse(NaN)).toThrow('Expected number, received number'); // NaN is typeof number but not finite
|
|
83
|
+
expect(() => numberSchema.parse(Infinity)).toThrow('Expected number, received number');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should parse valid object data', () => {
|
|
87
|
+
const userSchema = nu.object({
|
|
88
|
+
id: nu.number(),
|
|
89
|
+
name: nu.string(),
|
|
90
|
+
isActive: nu.boolean().meta({ label: 'Active', defaultValue: true }), // Only supported keys
|
|
91
|
+
});
|
|
92
|
+
// Note: Literal/boolean schemas aren't implemented, using parse here as a placeholder for demo.
|
|
93
|
+
// A real implementation would have nu.boolean() and potentially nu.literal()
|
|
94
|
+
|
|
95
|
+
const validData = { id: 1, name: 'Alice', isActive: true };
|
|
96
|
+
expect(userSchema.parse(validData)).toEqual(validData);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
it('should throw error for invalid object data (wrong type)', () => {
|
|
101
|
+
const userSchema = nu.object({ id: nu.number(), name: nu.string() });
|
|
102
|
+
expect(() => userSchema.parse(123)).toThrow('Expected object, received number');
|
|
103
|
+
expect(() => userSchema.parse(null)).toThrow('Expected object, received object'); // typeof null is 'object'
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should throw error for invalid object data (invalid property)', () => {
|
|
107
|
+
const userSchema = nu.object({ id: nu.number(), name: nu.string() });
|
|
108
|
+
const invalidData = { id: 'one', name: 'Alice' };
|
|
109
|
+
expect(() => userSchema.parse(invalidData)).toThrow(/Object validation failed:\nProperty "id": Expected number, received string/);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Note: Current ObjectSchema parse ignores extra keys. Add a test if you change that.
|
|
113
|
+
it('should ignore extra keys in object data by default', () => {
|
|
114
|
+
const userSchema = nu.object({ id: nu.number(), name: nu.string() });
|
|
115
|
+
const dataWithExtra = { id: 1, name: 'Alice', age: 30 };
|
|
116
|
+
const parsedData = userSchema.parse(dataWithExtra);
|
|
117
|
+
expect(parsedData).toEqual({ id: 1, name: 'Alice' }); // Extra 'age' is ignored
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
it('should parse valid array data', () => {
|
|
122
|
+
const stringArraySchema = nu.array(nu.string());
|
|
123
|
+
const validData = ['a', 'b', 'c'];
|
|
124
|
+
expect(stringArraySchema.parse(validData)).toEqual(validData);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should throw error for invalid array data (wrong type)', () => {
|
|
128
|
+
const stringArraySchema = nu.array(nu.string());
|
|
129
|
+
expect(() => stringArraySchema.parse('not an array')).toThrow('Expected array, received string');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should throw error for invalid array data (invalid element)', () => {
|
|
133
|
+
const stringArraySchema = nu.array(nu.string());
|
|
134
|
+
const invalidData = ['a', 123, 'c'];
|
|
135
|
+
expect(() => stringArraySchema.parse(invalidData)).toThrow(/Array validation failed:\nElement at index 1: Expected string, received number/);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// --- toZod Conversion Tests ---
|
|
139
|
+
|
|
140
|
+
it('should convert a string schema to a Zod string schema', () => {
|
|
141
|
+
const nuString = nu.string();
|
|
142
|
+
const zodString = toZod(nuString);
|
|
143
|
+
expect(zodString instanceof z.ZodString).toBe(true);
|
|
144
|
+
// Test Zod validation
|
|
145
|
+
expect(zodString.parse('test')).toBe('test');
|
|
146
|
+
expect(() => zodString.parse(123)).toThrow();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should convert a number schema to a Zod number schema', () => {
|
|
150
|
+
const nuNumber = nu.number();
|
|
151
|
+
const zodNumber = toZod(nuNumber);
|
|
152
|
+
expect(zodNumber instanceof z.ZodNumber).toBe(true);
|
|
153
|
+
// Test Zod validation
|
|
154
|
+
expect(zodNumber.parse(123)).toBe(123);
|
|
155
|
+
expect(() => zodNumber.parse('test')).toThrow();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should convert an object schema to a Zod object schema', () => {
|
|
159
|
+
const nuObject = nu.object({
|
|
160
|
+
name: nu.string(),
|
|
161
|
+
age: nu.number().meta({ label: 'User Age' })
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const zodObject = toZod(nuObject);
|
|
165
|
+
|
|
166
|
+
expect(zodObject instanceof z.ZodObject).toBe(true);
|
|
167
|
+
|
|
168
|
+
// Check the structure of the converted Zod schema
|
|
169
|
+
expect(zodObject.shape.name instanceof z.ZodString).toBe(true);
|
|
170
|
+
expect(zodObject.shape.age instanceof z.ZodNumber).toBe(true);
|
|
171
|
+
|
|
172
|
+
// Test Zod validation
|
|
173
|
+
const validData = { name: 'Bob', age: 42 };
|
|
174
|
+
expect(zodObject.parse(validData)).toEqual(validData);
|
|
175
|
+
expect(() => zodObject.parse({ name: 'Bob', age: 'old' })).toThrow();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('should convert an array schema to a Zod array schema', () => {
|
|
179
|
+
const nuArray = nu.array(nu.number());
|
|
180
|
+
const zodArray = toZod(nuArray);
|
|
181
|
+
expect(zodArray instanceof z.ZodArray).toBe(true);
|
|
182
|
+
expect(zodArray.element instanceof z.ZodNumber).toBe(true);
|
|
183
|
+
|
|
184
|
+
// Test Zod validation
|
|
185
|
+
const validData = [1, 2, 3];
|
|
186
|
+
expect(zodArray.parse(validData)).toEqual(validData);
|
|
187
|
+
expect(() => zodArray.parse([1, 'two', 3])).toThrow();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should handle nested structures in toZod conversion', () => {
|
|
191
|
+
const nestedNuSchema = nu.object({
|
|
192
|
+
user: nu.object({
|
|
193
|
+
name: nu.string(),
|
|
194
|
+
address: nu.object({
|
|
195
|
+
street: nu.string(),
|
|
196
|
+
zip: nu.string()
|
|
197
|
+
})
|
|
198
|
+
}),
|
|
199
|
+
tags: nu.array(nu.string())
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const nestedZodSchema = toZod(nestedNuSchema);
|
|
203
|
+
|
|
204
|
+
// Check types in the converted Zod schema structure
|
|
205
|
+
expect(nestedZodSchema instanceof z.ZodObject).toBe(true);
|
|
206
|
+
expect(nestedZodSchema.shape.user instanceof z.ZodObject).toBe(true);
|
|
207
|
+
expect((nestedZodSchema.shape.user as z.ZodObject<any>).shape.name instanceof z.ZodString).toBe(true);
|
|
208
|
+
expect((nestedZodSchema.shape.user as z.ZodObject<any>).shape.address instanceof z.ZodObject).toBe(true);
|
|
209
|
+
expect(((nestedZodSchema.shape.user as z.ZodObject<any>).shape.address as z.ZodObject<any>).shape.street instanceof z.ZodString).toBe(true);
|
|
210
|
+
expect(nestedZodSchema.shape.tags instanceof z.ZodArray).toBe(true);
|
|
211
|
+
expect((nestedZodSchema.shape.tags as z.ZodArray<any>).element instanceof z.ZodString).toBe(true);
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
// Test Zod validation with nested structure
|
|
215
|
+
const validData = {
|
|
216
|
+
user: {
|
|
217
|
+
name: 'Charlie',
|
|
218
|
+
address: { street: 'Main St', zip: '12345' }
|
|
219
|
+
},
|
|
220
|
+
tags: ['a', 'b']
|
|
221
|
+
};
|
|
222
|
+
expect(nestedZodSchema.parse(validData)).toEqual(validData);
|
|
223
|
+
|
|
224
|
+
const invalidData = {
|
|
225
|
+
user: {
|
|
226
|
+
name: 'Charlie',
|
|
227
|
+
address: { street: 123, zip: '12345' } // invalid street type
|
|
228
|
+
},
|
|
229
|
+
tags: ['a', 'b']
|
|
230
|
+
};
|
|
231
|
+
expect(() => nestedZodSchema.parse(invalidData)).toThrow();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
// --- TypeScript Type Inference Tests ---
|
|
236
|
+
|
|
237
|
+
it('should infer correct type for primitive schemas', () => {
|
|
238
|
+
const s = nu.string();
|
|
239
|
+
const n = nu.number();
|
|
240
|
+
|
|
241
|
+
// Check inferred output type
|
|
242
|
+
expectTypeOf(s).toHaveProperty('_outputType').toBeString();
|
|
243
|
+
expectTypeOf(n).toHaveProperty('_outputType').toBeNumber();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('should infer correct type for object schema', () => {
|
|
247
|
+
const userSchema = nu.object({
|
|
248
|
+
id: nu.number().meta({ label: 'User ID' }),
|
|
249
|
+
name: nu.string(),
|
|
250
|
+
settings: nu.object({
|
|
251
|
+
theme: nu.string(),
|
|
252
|
+
darkMode: nu.boolean().meta({ label: 'Switch' }) // Only supported keys
|
|
253
|
+
})
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Check inferred output type structure
|
|
257
|
+
expectTypeOf(userSchema).toHaveProperty('_outputType').toMatchTypeOf<{
|
|
258
|
+
id: number;
|
|
259
|
+
name: string;
|
|
260
|
+
settings: {
|
|
261
|
+
theme: string;
|
|
262
|
+
darkMode: any; // Placeholder output type from .parse(true)
|
|
263
|
+
}
|
|
264
|
+
}>();
|
|
265
|
+
|
|
266
|
+
// Test that assigning parsed data is type-safe
|
|
267
|
+
const userData = { id: 1, name: 'Test', settings: { theme: 'dark', darkMode: false } };
|
|
268
|
+
const parsedUser = userSchema.parse(userData);
|
|
269
|
+
expectTypeOf(parsedUser).toMatchTypeOf<{ id: number; name: string; settings: { theme: string; darkMode: any; } }>();
|
|
270
|
+
|
|
271
|
+
// This would be a TS error if types mismatched:
|
|
272
|
+
// const badParsedUser: string = userSchema.parse(userData); // TS error expected
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('should infer correct type for array schema', () => {
|
|
276
|
+
const numberArraySchema = nu.array(nu.number()); // Removed unsupported minValue metadata
|
|
277
|
+
const stringArraySchema = nu.array(nu.string().meta({ label: 'Entry' }));
|
|
278
|
+
const arrayOfObjectsSchema = nu.array(nu.object({ id: nu.number(), value: nu.string() }));
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
// Check inferred output types
|
|
282
|
+
expectTypeOf(numberArraySchema).toHaveProperty('_outputType').toBeArray();
|
|
283
|
+
expectTypeOf(stringArraySchema).toHaveProperty('_outputType').toBeArray();
|
|
284
|
+
expectTypeOf(arrayOfObjectsSchema).toHaveProperty('_outputType').toBeArray();
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
// Test that assigning parsed data is type-safe
|
|
288
|
+
const numberArrayData = [1, 2, 3];
|
|
289
|
+
const parsedNumberArray = numberArraySchema.parse(numberArrayData);
|
|
290
|
+
expectTypeOf(parsedNumberArray).toBeArray();
|
|
291
|
+
// This would be a TS error:
|
|
292
|
+
// const badParsedNumberArray: string[] = parsedNumberArray; // TS error expected
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
it('should infer correct Zod schema type after toZod conversion', () => {
|
|
297
|
+
const nuString = nu.string().meta({ label: 'ID' });
|
|
298
|
+
const zodString = toZod(nuString);
|
|
299
|
+
expectTypeOf(zodString).toMatchTypeOf<z.ZodString>(); // Should be a ZodString
|
|
300
|
+
expectTypeOf(zodString._output).toBeString(); // Should infer string output
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
const nuObject = nu.object({
|
|
304
|
+
name: nu.string(),
|
|
305
|
+
age: nu.number()
|
|
306
|
+
});
|
|
307
|
+
const zodObject = toZod(nuObject);
|
|
308
|
+
expectTypeOf(zodObject).toMatchTypeOf<z.ZodObject<any>>(); // Should be a ZodObject
|
|
309
|
+
expectTypeOf(zodObject._output).toMatchTypeOf<{ name: string, age: number }>(); // Should infer the object type
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
const nuArray = nu.array(nu.string());
|
|
313
|
+
const zodArray = toZod(nuArray);
|
|
314
|
+
expectTypeOf(zodArray).toMatchTypeOf<z.ZodArray<z.ZodString>>(); // Should be ZodArray of ZodString
|
|
315
|
+
|
|
316
|
+
const nuNested = nu.object({
|
|
317
|
+
items: nu.array(nu.object({
|
|
318
|
+
id: nu.number()
|
|
319
|
+
}))
|
|
320
|
+
});
|
|
321
|
+
const zodNested = toZod(nuNested);
|
|
322
|
+
expectTypeOf(zodNested._output).toMatchTypeOf<{ items: Array<{ id: number }> }>(); // Should infer nested type
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
});
|
package/schema/nu.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// src/nu.ts
|
|
2
|
+
|
|
3
|
+
import { BaseSchema } from "./base-schema";
|
|
4
|
+
import { StringSchema, NumberSchema, ObjectShape, ObjectSchema, ArraySchema, BooleanSchema } from "./types";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The main nubase schema instance.
|
|
9
|
+
* Provides factory methods to create different schema types.
|
|
10
|
+
*/
|
|
11
|
+
export const nu = {
|
|
12
|
+
boolean : () => new BooleanSchema(),
|
|
13
|
+
/**
|
|
14
|
+
* Creates a string schema.
|
|
15
|
+
*/
|
|
16
|
+
string: () => new StringSchema(),
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Creates a number schema.
|
|
20
|
+
*/
|
|
21
|
+
number: () => new NumberSchema(),
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Creates an object schema with a defined shape.
|
|
25
|
+
* @param shape An object mapping keys to schemas.
|
|
26
|
+
*/
|
|
27
|
+
object: <TShape extends ObjectShape>(shape: TShape) => new ObjectSchema(shape),
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Creates an array schema with a defined element schema.
|
|
31
|
+
* @param elementSchema The schema for elements within the array.
|
|
32
|
+
*/
|
|
33
|
+
array: <TElementSchema extends BaseSchema<any>>(elementSchema: TElementSchema) => new ArraySchema(elementSchema),
|
|
34
|
+
|
|
35
|
+
// Add more factory methods here (boolean, union, literal, etc.)
|
|
36
|
+
};
|
package/schema/toZod.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// src/converters/toZod.ts
|
|
2
|
+
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import {
|
|
5
|
+
StringSchema,
|
|
6
|
+
NumberSchema,
|
|
7
|
+
ObjectSchema,
|
|
8
|
+
ArraySchema,
|
|
9
|
+
NuSchema, // The union type of all nu schemas
|
|
10
|
+
ObjectOutput // Type utility to get object output type
|
|
11
|
+
} from '../schema/types';
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Infers the Zod schema type corresponding to a given NuSchema type.
|
|
16
|
+
* This is a conditional type that maps NuSchema types to their Zod equivalents
|
|
17
|
+
* and preserves the output type.
|
|
18
|
+
*/
|
|
19
|
+
export type NuSchemaToZodSchema<S extends NuSchema> =
|
|
20
|
+
S extends StringSchema ? z.ZodString :
|
|
21
|
+
S extends NumberSchema ? z.ZodNumber :
|
|
22
|
+
S extends ObjectSchema<infer TShape> ? z.ZodObject<{
|
|
23
|
+
[K in keyof TShape]: NuSchemaToZodSchema<TShape[K]>;
|
|
24
|
+
}, any, z.ZodTypeAny, ObjectOutput<TShape>> : // Recursive mapping for object shape
|
|
25
|
+
S extends ArraySchema<infer TElementSchema> ? z.ZodArray<NuSchemaToZodSchema<TElementSchema>> : // Recursive mapping for array element
|
|
26
|
+
z.ZodSchema<any>; // Fallback for unknown types
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Converts a nubase schema to a Zod schema.
|
|
31
|
+
* Preserves the TypeScript output type.
|
|
32
|
+
* Metadata (label, description etc.) is NOT translated to Zod schema properties
|
|
33
|
+
* as Zod doesn't have direct equivalents in its core schema structure.
|
|
34
|
+
* It only translates the validation structure and output type.
|
|
35
|
+
*
|
|
36
|
+
* @param schema The nubase schema to convert.
|
|
37
|
+
* @returns The equivalent Zod schema.
|
|
38
|
+
*/
|
|
39
|
+
export function toZod<S extends NuSchema>(schema: S): NuSchemaToZodSchema<S> {
|
|
40
|
+
if (schema instanceof StringSchema) {
|
|
41
|
+
// Add Zod string validations based on schema._meta or specific properties if they exist
|
|
42
|
+
// e.g., if (schema._meta.minLength) zodSchema = zodSchema.min(schema._meta.minLength);
|
|
43
|
+
// For now, just the base type:
|
|
44
|
+
return z.string() as NuSchemaToZodSchema<S>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (schema instanceof NumberSchema) {
|
|
48
|
+
// Add Zod number validations similarly
|
|
49
|
+
return z.number() as NuSchemaToZodSchema<S>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (schema instanceof ObjectSchema) {
|
|
53
|
+
const zodShape: Record<string, z.ZodTypeAny> = {};
|
|
54
|
+
// Recursively convert each schema in the shape
|
|
55
|
+
for (const key in schema._shape) {
|
|
56
|
+
if (Object.prototype.hasOwnProperty.call(schema._shape, key)) {
|
|
57
|
+
zodShape[key] = toZod(schema._shape[key]); // Recursive call
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Zod object constructor needs the shape object
|
|
61
|
+
return z.object(zodShape) as NuSchemaToZodSchema<S>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (schema instanceof ArraySchema) {
|
|
65
|
+
// Recursively convert the element schema
|
|
66
|
+
const zodElementSchema = toZod(schema._element);
|
|
67
|
+
// Zod array constructor needs the element schema
|
|
68
|
+
return z.array(zodElementSchema) as NuSchemaToZodSchema<S>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Handle other schema types here...
|
|
72
|
+
// For now, throw an error for unsupported types
|
|
73
|
+
throw new Error(`Unsupported schema type for Zod conversion: ${schema.constructor.name}`);
|
|
74
|
+
}
|
package/schema/types.ts
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// src/schema/types.ts
|
|
2
|
+
|
|
3
|
+
import { BaseSchema } from './base-schema';
|
|
4
|
+
|
|
5
|
+
// --- Primitive Schemas ---
|
|
6
|
+
|
|
7
|
+
export class BooleanSchema extends BaseSchema<boolean> {
|
|
8
|
+
parse(data: any): boolean {
|
|
9
|
+
if (typeof data !== 'boolean') {
|
|
10
|
+
throw new Error(`Expected boolean, received ${typeof data}`);
|
|
11
|
+
}
|
|
12
|
+
return data;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class StringSchema extends BaseSchema<string> {
|
|
17
|
+
parse(data: any): string {
|
|
18
|
+
if (typeof data !== 'string') {
|
|
19
|
+
throw new Error(`Expected string, received ${typeof data}`);
|
|
20
|
+
}
|
|
21
|
+
return data;
|
|
22
|
+
}
|
|
23
|
+
// Add string-specific validation methods here (e.g., minLength, pattern)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class NumberSchema extends BaseSchema<number> {
|
|
27
|
+
parse(data: any): number {
|
|
28
|
+
if (typeof data !== 'number' || !Number.isFinite(data)) {
|
|
29
|
+
throw new Error(`Expected number, received ${typeof data}`);
|
|
30
|
+
}
|
|
31
|
+
return data;
|
|
32
|
+
}
|
|
33
|
+
// Add number-specific validation methods here (e.g., min, max)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Add more primitives like BooleanSchema, NullableSchema, OptionalSchema etc.
|
|
37
|
+
|
|
38
|
+
// --- Complex Schemas ---
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Type representing the shape of an object schema (key to schema mapping).
|
|
42
|
+
*/
|
|
43
|
+
export type ObjectShape = {
|
|
44
|
+
[key: string]: BaseSchema<any>;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Infers the TypeScript output type from an ObjectShape.
|
|
49
|
+
* Use mapped types to get the output type of each property schema.
|
|
50
|
+
*/
|
|
51
|
+
export type ObjectOutput<TShape extends ObjectShape> = {
|
|
52
|
+
[K in keyof TShape]: TShape[K]['_outputType'];
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export class ObjectSchema<TShape extends ObjectShape> extends BaseSchema<ObjectOutput<TShape>> {
|
|
56
|
+
_shape: TShape;
|
|
57
|
+
|
|
58
|
+
constructor(shape: TShape) {
|
|
59
|
+
super();
|
|
60
|
+
this._shape = shape;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
parse(data: any): ObjectOutput<TShape> {
|
|
64
|
+
if (typeof data !== 'object' || data === null) {
|
|
65
|
+
throw new Error(`Expected object, received ${typeof data}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const result: any = {};
|
|
69
|
+
const errors: string[] = [];
|
|
70
|
+
|
|
71
|
+
// Validate defined properties
|
|
72
|
+
for (const key in this._shape) {
|
|
73
|
+
if (Object.prototype.hasOwnProperty.call(this._shape, key)) {
|
|
74
|
+
const schema = this._shape[key];
|
|
75
|
+
const value = (data as any)[key]; // Get value from input data
|
|
76
|
+
try {
|
|
77
|
+
if (schema) {
|
|
78
|
+
result[key] = schema.parse(value); // Recursively parse property
|
|
79
|
+
}
|
|
80
|
+
} catch (e: any) {
|
|
81
|
+
errors.push(`Property "${key}": ${e.message}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Basic handling for extra keys (can be made configurable)
|
|
87
|
+
for (const key in data) {
|
|
88
|
+
if (Object.prototype.hasOwnProperty.call(data, key) && !Object.prototype.hasOwnProperty.call(this._shape, key)) {
|
|
89
|
+
// For now, ignore extra keys. Could throw an error or strip them.
|
|
90
|
+
// errors.push(`Unknown property "${key}"`);
|
|
91
|
+
// console.warn(`Warning: Unknown property "${key}" in object data.`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
if (errors.length > 0) {
|
|
97
|
+
throw new Error(`Object validation failed:\n${errors.join('\n')}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return result as ObjectOutput<TShape>;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Infers the TypeScript output type from an element schema for an array.
|
|
106
|
+
*/
|
|
107
|
+
export type ArrayOutput<TElementSchema extends BaseSchema<any>> = Array<TElementSchema['_outputType']>;
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
export class ArraySchema<TElementSchema extends BaseSchema<any>> extends BaseSchema<ArrayOutput<TElementSchema>> {
|
|
111
|
+
_element: TElementSchema;
|
|
112
|
+
|
|
113
|
+
constructor(elementSchema: TElementSchema) {
|
|
114
|
+
super();
|
|
115
|
+
this._element = elementSchema;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
parse(data: any): ArrayOutput<TElementSchema> {
|
|
119
|
+
if (!Array.isArray(data)) {
|
|
120
|
+
throw new Error(`Expected array, received ${typeof data}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const result: any[] = [];
|
|
124
|
+
const errors: string[] = [];
|
|
125
|
+
|
|
126
|
+
for (let i = 0; i < data.length; i++) {
|
|
127
|
+
try {
|
|
128
|
+
result[i] = this._element.parse(data[i]); // Recursively parse each element
|
|
129
|
+
} catch (e: any) {
|
|
130
|
+
errors.push(`Element at index ${i}: ${e.message}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (errors.length > 0) {
|
|
135
|
+
throw new Error(`Array validation failed:\n${errors.join('\n')}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return result as ArrayOutput<TElementSchema>;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Refine NuSchema union after defining concrete types
|
|
143
|
+
export type NuSchema =
|
|
144
|
+
| BooleanSchema
|
|
145
|
+
| StringSchema
|
|
146
|
+
| NumberSchema
|
|
147
|
+
// Add others like BooleanSchema
|
|
148
|
+
| ObjectSchema<any>
|
|
149
|
+
| ArraySchema<any>;
|