toolcraft-schema 0.0.42 → 0.0.43

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/dist/index.d.ts CHANGED
@@ -16,6 +16,7 @@ type JsonSchemaEnumValue = EnumValue | null;
16
16
  type NumberJsonType = "number" | "integer";
17
17
  type NonEmptyReadonlyArray<T> = readonly [T, ...T[]];
18
18
  type ObjectShape = Record<string, AnySchema>;
19
+ type EmptyOptions = Record<never, never>;
19
20
  type SchemaScope = "cli" | "mcp" | "sdk";
20
21
  type StringMetadata = {
21
22
  format?: string;
@@ -56,6 +57,13 @@ type SchemaOptions<TDefault> = {
56
57
  scope?: readonly SchemaScope[];
57
58
  global?: boolean;
58
59
  };
60
+ type WithNullable<TSchema extends AnySchema, TOptions extends {
61
+ nullable?: boolean;
62
+ }> = TOptions extends {
63
+ readonly nullable: true;
64
+ } ? TSchema & {
65
+ readonly nullable: true;
66
+ } : TSchema;
59
67
  export interface SchemaBase<TKind extends SchemaKind, TStatic> {
60
68
  readonly kind: TKind;
61
69
  readonly description?: string;
@@ -116,14 +124,16 @@ export interface OptionalSchema<TInner extends AnySchema> extends SchemaBase<"op
116
124
  readonly inner: TInner;
117
125
  }
118
126
  export type AnySchema = StringSchema | NumberSchema | BooleanSchema | EnumSchema<NonEmptyReadonlyArray<EnumValue>> | ArraySchema<AnySchema> | ObjectSchema<ObjectShape> | OptionalSchema<AnySchema> | OneOfSchema<Record<string, ObjectSchema<any>>, string> | UnionSchema<readonly ObjectSchema<any>[]> | RecordSchema<AnySchema> | JsonValueSchema;
119
- export type Static<TSchema extends AnySchema> = TSchema extends SchemaBase<any, infer TStatic> ? TStatic : never;
127
+ export type Static<TSchema extends AnySchema> = TSchema extends {
128
+ readonly nullable: true;
129
+ } ? TSchema extends SchemaBase<any, infer TStatic> ? TStatic | null : never : TSchema extends SchemaBase<any, infer TStatic> ? TStatic : never;
120
130
  export declare const S: {
121
- readonly String: (options?: SchemaOptions<string> & StringMetadata) => StringSchema;
122
- readonly Number: (options?: SchemaOptions<number> & NumberMetadata & {
131
+ readonly String: <const TOptions extends SchemaOptions<string> & StringMetadata = EmptyOptions>(options?: TOptions) => WithNullable<StringSchema, TOptions>;
132
+ readonly Number: <const TOptions extends SchemaOptions<number> & NumberMetadata & {
123
133
  jsonType?: NumberJsonType;
124
- }) => NumberSchema;
125
- readonly Boolean: (options?: SchemaOptions<boolean>) => BooleanSchema;
126
- readonly Enum: <const TValues extends NonEmptyReadonlyArray<EnumValue>>(values: TValues, options?: SchemaOptions<TValues[number]> & {
134
+ } = EmptyOptions>(options?: TOptions) => WithNullable<NumberSchema, TOptions>;
135
+ readonly Boolean: <const TOptions extends SchemaOptions<boolean> = EmptyOptions>(options?: TOptions) => WithNullable<BooleanSchema, TOptions>;
136
+ readonly Enum: <const TValues extends NonEmptyReadonlyArray<EnumValue>, const TOptions extends SchemaOptions<TValues[number]> & {
127
137
  jsonType?: "integer";
128
138
  labels?: Partial<Record<string, string>>;
129
139
  loadOptions?: (() => Array<{
@@ -133,9 +143,9 @@ export declare const S: {
133
143
  label: string;
134
144
  value: string;
135
145
  }>>);
136
- }) => EnumSchema<TValues>;
137
- readonly Array: <TItem extends AnySchema>(item: TItem, options?: SchemaOptions<Array<Static<TItem>>> & ArrayMetadata) => ArraySchema<TItem>;
138
- readonly Object: <const TShape extends ObjectShape>(shape: TShape, options?: SchemaOptions<InferObject<TShape>> & ObjectMetadata) => ObjectSchema<TShape>;
146
+ } = EmptyOptions>(values: TValues, options?: TOptions) => WithNullable<EnumSchema<TValues>, TOptions>;
147
+ readonly Array: <TItem extends AnySchema, const TOptions extends SchemaOptions<Array<Static<TItem>>> & ArrayMetadata = EmptyOptions>(item: TItem, options?: TOptions) => WithNullable<ArraySchema<TItem>, TOptions>;
148
+ readonly Object: <const TShape extends ObjectShape, const TOptions extends SchemaOptions<InferObject<TShape>> & ObjectMetadata = EmptyOptions>(shape: TShape, options?: TOptions) => WithNullable<ObjectSchema<TShape>, TOptions>;
139
149
  readonly Optional: <TInner extends AnySchema>(inner: TInner) => OptionalSchema<TInner>;
140
150
  readonly OneOf: typeof OneOf;
141
151
  readonly Union: typeof Union;
package/dist/index.js CHANGED
@@ -50,9 +50,7 @@ function withArrayMetadata(schema, jsonSchema) {
50
50
  return withMetadata(schema, jsonSchema);
51
51
  }
52
52
  function withObjectMetadata(schema, jsonSchema) {
53
- if (schema.additionalProperties !== undefined) {
54
- jsonSchema.additionalProperties = schema.additionalProperties;
55
- }
53
+ jsonSchema.additionalProperties = schema.additionalProperties ?? false;
56
54
  return withMetadata(schema, jsonSchema);
57
55
  }
58
56
  function getEnumJsonType(values) {
@@ -95,6 +93,20 @@ function assertFiniteNumber(value, name) {
95
93
  throw new Error(`${name} must be finite`);
96
94
  }
97
95
  }
96
+ function assertMinMaxOrder(minimum, maximum, minimumName, maximumName) {
97
+ if (minimum !== undefined && maximum !== undefined && minimum > maximum) {
98
+ throw new Error(`${minimumName} must be less than or equal to ${maximumName}`);
99
+ }
100
+ }
101
+ function assertValidDefault(schema) {
102
+ if (schema.default === undefined) {
103
+ return;
104
+ }
105
+ const result = validate(schema, schema.default);
106
+ if (!result.ok) {
107
+ throw new Error(`default must satisfy schema: ${result.issues[0]?.message ?? "invalid default"}`);
108
+ }
109
+ }
98
110
  function assertPattern(pattern) {
99
111
  if (pattern === undefined) {
100
112
  return;
@@ -133,31 +145,39 @@ export const S = {
133
145
  String(options = {}) {
134
146
  assertNonNegativeInteger(options.minLength, "minLength");
135
147
  assertNonNegativeInteger(options.maxLength, "maxLength");
148
+ assertMinMaxOrder(options.minLength, options.maxLength, "minLength", "maxLength");
136
149
  assertPattern(options.pattern);
137
- return {
150
+ const schema = {
138
151
  kind: "string",
139
152
  ...options
140
153
  };
154
+ assertValidDefault(schema);
155
+ return schema;
141
156
  },
142
157
  Number(options = {}) {
143
158
  assertFiniteNumber(options.minimum, "minimum");
144
159
  assertFiniteNumber(options.maximum, "maximum");
160
+ assertMinMaxOrder(options.minimum, options.maximum, "minimum", "maximum");
145
161
  assertFiniteNumber(options.default, "default");
146
162
  if (options.jsonType === "integer" &&
147
163
  options.default !== undefined &&
148
164
  !Number.isInteger(options.default)) {
149
165
  throw new Error("default must be an integer");
150
166
  }
151
- return {
167
+ const schema = {
152
168
  kind: "number",
153
169
  ...options
154
170
  };
171
+ assertValidDefault(schema);
172
+ return schema;
155
173
  },
156
174
  Boolean(options = {}) {
157
- return {
175
+ const schema = {
158
176
  kind: "boolean",
159
177
  ...options
160
178
  };
179
+ assertValidDefault(schema);
180
+ return schema;
161
181
  },
162
182
  Enum(values, options = {}) {
163
183
  assertValidEnumValues(values);
@@ -165,27 +185,34 @@ export const S = {
165
185
  values.some((value) => typeof value !== "number" || !Number.isInteger(value))) {
166
186
  throw new Error("Integer enum values must be integers");
167
187
  }
168
- return {
188
+ const schema = {
169
189
  kind: "enum",
170
190
  values,
171
191
  ...options
172
192
  };
193
+ assertValidDefault(schema);
194
+ return schema;
173
195
  },
174
196
  Array(item, options = {}) {
175
197
  assertNonNegativeInteger(options.minItems, "minItems");
176
198
  assertNonNegativeInteger(options.maxItems, "maxItems");
177
- return {
199
+ assertMinMaxOrder(options.minItems, options.maxItems, "minItems", "maxItems");
200
+ const schema = {
178
201
  kind: "array",
179
202
  item,
180
203
  ...options
181
204
  };
205
+ assertValidDefault(schema);
206
+ return schema;
182
207
  },
183
208
  Object(shape, options = {}) {
184
- return {
209
+ const schema = {
185
210
  kind: "object",
186
211
  shape,
187
212
  ...options
188
213
  };
214
+ assertValidDefault(schema);
215
+ return schema;
189
216
  },
190
217
  Optional(inner) {
191
218
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft-schema",
3
- "version": "0.0.42",
3
+ "version": "0.0.43",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",