toolcraft-schema 0.0.2 → 0.0.4

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
@@ -1,5 +1,13 @@
1
+ import { Json } from "./json.js";
2
+ import { OneOf } from "./oneof.js";
3
+ import { Record as RecordBuilder } from "./record.js";
4
+ import { Union } from "./union.js";
5
+ import type { JsonValue, JsonValueSchema } from "./json.js";
6
+ import type { OneOfSchema } from "./oneof.js";
7
+ import type { RecordSchema } from "./record.js";
8
+ import type { UnionSchema } from "./union.js";
1
9
  type JsonSchemaType = "string" | "number" | "integer" | "boolean" | "array" | "object";
2
- type SchemaKind = "string" | "number" | "boolean" | "enum" | "array" | "object" | "optional";
10
+ type SchemaKind = "string" | "number" | "boolean" | "enum" | "array" | "object" | "optional" | "oneOf" | "union" | "record" | "json";
3
11
  type EnumValue = string | number | boolean;
4
12
  type JsonSchemaEnumValue = EnumValue | null;
5
13
  type NumberJsonType = "number" | "integer";
@@ -41,7 +49,7 @@ type SchemaOptions<TDefault> = {
41
49
  short?: string;
42
50
  scope?: readonly SchemaScope[];
43
51
  };
44
- interface SchemaBase<TKind extends SchemaKind, TStatic> {
52
+ export interface SchemaBase<TKind extends SchemaKind, TStatic> {
45
53
  readonly kind: TKind;
46
54
  readonly description?: string;
47
55
  readonly default?: TStatic;
@@ -52,7 +60,7 @@ interface SchemaBase<TKind extends SchemaKind, TStatic> {
52
60
  readonly __static?: TStatic;
53
61
  }
54
62
  export interface JsonSchema {
55
- additionalProperties?: boolean;
63
+ additionalProperties?: boolean | JsonSchema;
56
64
  type?: JsonSchemaType;
57
65
  description?: string;
58
66
  default?: unknown;
@@ -66,6 +74,7 @@ export interface JsonSchema {
66
74
  minimum?: number;
67
75
  minLength?: number;
68
76
  nullable?: boolean;
77
+ oneOf?: JsonSchema[];
69
78
  pattern?: string;
70
79
  properties?: Record<string, JsonSchema>;
71
80
  required?: string[];
@@ -97,7 +106,7 @@ export interface ObjectSchema<TShape extends ObjectShape> extends SchemaBase<"ob
97
106
  export interface OptionalSchema<TInner extends AnySchema> extends SchemaBase<"optional", Static<TInner> | undefined> {
98
107
  readonly inner: TInner;
99
108
  }
100
- export type AnySchema = StringSchema | NumberSchema | BooleanSchema | EnumSchema<NonEmptyReadonlyArray<EnumValue>> | ArraySchema<AnySchema> | ObjectSchema<ObjectShape> | OptionalSchema<AnySchema>;
109
+ 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;
101
110
  export type Static<TSchema extends AnySchema> = TSchema extends SchemaBase<any, infer TStatic> ? TStatic : never;
102
111
  export declare const S: {
103
112
  readonly String: (options?: SchemaOptions<string> & StringMetadata) => StringSchema;
@@ -119,6 +128,11 @@ export declare const S: {
119
128
  readonly Array: <TItem extends AnySchema>(item: TItem, options?: SchemaOptions<Array<Static<TItem>>> & ArrayMetadata) => ArraySchema<TItem>;
120
129
  readonly Object: <const TShape extends ObjectShape>(shape: TShape, options?: SchemaOptions<InferObject<TShape>> & ObjectMetadata) => ObjectSchema<TShape>;
121
130
  readonly Optional: <TInner extends AnySchema>(inner: TInner) => OptionalSchema<TInner>;
131
+ readonly OneOf: typeof OneOf;
132
+ readonly Union: typeof Union;
133
+ readonly Record: typeof RecordBuilder;
134
+ readonly Json: typeof Json;
122
135
  };
123
136
  export declare function toJsonSchema(schema: AnySchema): JsonSchema;
124
- export {};
137
+ export { Json, OneOf, RecordBuilder as Record, Union };
138
+ export type { JsonValue, JsonValueSchema, OneOfSchema, RecordSchema, UnionSchema };
package/dist/index.js CHANGED
@@ -1,3 +1,7 @@
1
+ import { Json } from "./json.js";
2
+ import { OneOf } from "./oneof.js";
3
+ import { Record as RecordBuilder } from "./record.js";
4
+ import { Union } from "./union.js";
1
5
  function withMetadata(schema, jsonSchema) {
2
6
  if (schema.description !== undefined) {
3
7
  jsonSchema.description = schema.description;
@@ -82,6 +86,23 @@ function unwrapOptional(schema) {
82
86
  }
83
87
  return schema;
84
88
  }
89
+ function withInjectedDiscriminator(schema, discriminator, branchName) {
90
+ const branchJsonSchema = toJsonSchema(schema);
91
+ const properties = {
92
+ ...(branchJsonSchema.properties ?? {}),
93
+ [discriminator]: {
94
+ type: "string",
95
+ enum: [branchName],
96
+ },
97
+ };
98
+ const required = [...new Set([...(branchJsonSchema.required ?? []), discriminator])];
99
+ return {
100
+ ...branchJsonSchema,
101
+ type: "object",
102
+ properties,
103
+ required,
104
+ };
105
+ }
85
106
  export const S = {
86
107
  String(options = {}) {
87
108
  return {
@@ -129,6 +150,10 @@ export const S = {
129
150
  inner,
130
151
  };
131
152
  },
153
+ OneOf,
154
+ Union,
155
+ Record: RecordBuilder,
156
+ Json,
132
157
  };
133
158
  export function toJsonSchema(schema) {
134
159
  const unwrappedSchema = unwrapOptional(schema);
@@ -171,5 +196,21 @@ export function toJsonSchema(schema) {
171
196
  required,
172
197
  });
173
198
  }
199
+ case "oneOf":
200
+ return withMetadata(unwrappedSchema, {
201
+ oneOf: Object.entries(unwrappedSchema.branches).map(([branchName, branchSchema]) => withInjectedDiscriminator(branchSchema, unwrappedSchema.discriminator, branchName)),
202
+ });
203
+ case "union":
204
+ return withMetadata(unwrappedSchema, {
205
+ oneOf: unwrappedSchema.branches.map((branchSchema) => toJsonSchema(branchSchema)),
206
+ });
207
+ case "record":
208
+ return withMetadata(unwrappedSchema, {
209
+ type: "object",
210
+ additionalProperties: toJsonSchema(unwrappedSchema.value),
211
+ });
212
+ case "json":
213
+ return withMetadata(unwrappedSchema, {});
174
214
  }
175
215
  }
216
+ export { Json, OneOf, RecordBuilder as Record, Union };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import { S } from "./index.js";
2
+ const ignoredSchema = S.Json();
package/dist/json.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import type { SchemaBase } from "./index.js";
2
+ type JsonPrimitive = string | number | boolean | null;
3
+ export type JsonValue = JsonPrimitive | {
4
+ [key: string]: JsonValue;
5
+ } | JsonValue[];
6
+ export interface JsonValueSchema extends SchemaBase<"json", JsonValue> {
7
+ readonly kind: "json";
8
+ }
9
+ export declare function Json(): JsonValueSchema;
10
+ export {};
package/dist/json.js ADDED
@@ -0,0 +1,5 @@
1
+ export function Json() {
2
+ return {
3
+ kind: "json",
4
+ };
5
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,12 @@
1
+ import { S } from "./index.js";
2
+ const ignoredSchema = S.OneOf({
3
+ discriminator: "kind",
4
+ branches: {
5
+ text: S.Object({
6
+ value: S.String(),
7
+ }),
8
+ count: S.Object({
9
+ value: S.Number(),
10
+ }),
11
+ },
12
+ });
@@ -0,0 +1,15 @@
1
+ import type { ObjectSchema, SchemaBase, Static } from "./index.js";
2
+ type OneOfStatic<TBranches extends Record<string, ObjectSchema<any>>, TDiscriminator extends string> = {
3
+ [TBranchName in keyof TBranches & string]: Omit<Static<TBranches[TBranchName]>, TDiscriminator> & {
4
+ [TFieldName in TDiscriminator]: TBranchName;
5
+ };
6
+ }[keyof TBranches & string];
7
+ export interface OneOfSchema<TBranches extends Record<string, ObjectSchema<any>>, TDiscriminator extends string = string> extends SchemaBase<"oneOf", OneOfStatic<TBranches, TDiscriminator>> {
8
+ readonly discriminator: TDiscriminator;
9
+ readonly branches: TBranches;
10
+ }
11
+ export declare function OneOf<TDiscriminator extends string, TBranches extends Record<string, ObjectSchema<any>>>(config: {
12
+ discriminator: TDiscriminator;
13
+ branches: TBranches;
14
+ }): OneOfSchema<TBranches, TDiscriminator>;
15
+ export {};
package/dist/oneof.js ADDED
@@ -0,0 +1,13 @@
1
+ function assertValidBranches(branches) {
2
+ if (Object.keys(branches).length === 0) {
3
+ throw new Error("OneOf schema requires at least one branch");
4
+ }
5
+ }
6
+ export function OneOf(config) {
7
+ assertValidBranches(config.branches);
8
+ return {
9
+ kind: "oneOf",
10
+ discriminator: config.discriminator,
11
+ branches: config.branches,
12
+ };
13
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import { S } from "./index.js";
2
+ const ignoredSchema = S.Record(S.String());
@@ -0,0 +1,5 @@
1
+ import type { AnySchema, SchemaBase, Static } from "./index.js";
2
+ export interface RecordSchema<TValue extends AnySchema> extends SchemaBase<"record", Record<string, Static<TValue>>> {
3
+ readonly value: TValue;
4
+ }
5
+ export declare function Record<TValue extends AnySchema>(value: TValue): RecordSchema<TValue>;
package/dist/record.js ADDED
@@ -0,0 +1,6 @@
1
+ export function Record(value) {
2
+ return {
3
+ kind: "record",
4
+ value,
5
+ };
6
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import { S } from "./index.js";
2
+ const ignoredSchema = S.Union([
3
+ S.Object({
4
+ email: S.String(),
5
+ }),
6
+ S.Object({
7
+ phone: S.String(),
8
+ }),
9
+ ]);
@@ -0,0 +1,7 @@
1
+ import type { ObjectSchema, SchemaBase, Static } from "./index.js";
2
+ type UnionStatic<TBranches extends readonly ObjectSchema<any>[]> = Static<TBranches[number]>;
3
+ export interface UnionSchema<TBranches extends readonly ObjectSchema<any>[]> extends SchemaBase<"union", UnionStatic<TBranches>> {
4
+ readonly branches: TBranches;
5
+ }
6
+ export declare function Union<const TBranches extends readonly ObjectSchema<any>[]>(branches: TBranches): UnionSchema<TBranches>;
7
+ export {};
package/dist/union.js ADDED
@@ -0,0 +1,29 @@
1
+ function isOptionalSchema(schema) {
2
+ return schema.kind === "optional";
3
+ }
4
+ function getRequiredKeyFingerprint(schema) {
5
+ const requiredKeys = Object.keys(schema.shape)
6
+ .filter((key) => !isOptionalSchema(schema.shape[key]))
7
+ .sort();
8
+ return JSON.stringify(requiredKeys);
9
+ }
10
+ function assertValidBranches(branches) {
11
+ if (branches.length === 0) {
12
+ throw new Error("Union schema requires at least one branch");
13
+ }
14
+ const fingerprints = new Set();
15
+ for (const branch of branches) {
16
+ const fingerprint = getRequiredKeyFingerprint(branch);
17
+ if (fingerprints.has(fingerprint)) {
18
+ throw new Error("Union schema branches must have unique required-key fingerprints");
19
+ }
20
+ fingerprints.add(fingerprint);
21
+ }
22
+ }
23
+ export function Union(branches) {
24
+ assertValidBranches(branches);
25
+ return {
26
+ kind: "union",
27
+ branches,
28
+ };
29
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft-schema",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",