toolcraft-schema 0.0.1

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 ADDED
@@ -0,0 +1,69 @@
1
+ # toolcraft-schema
2
+
3
+ tools for agents and humans
4
+
5
+ Zero-dependency schema builder for typed command inputs and JSON Schema generation.
6
+
7
+ ## Features
8
+
9
+ - Zero runtime dependencies
10
+ - Typed schema descriptors
11
+ - `Static<typeof schema>` type inference
12
+ - JSON Schema serialization via `toJsonSchema()`
13
+
14
+ ## Usage
15
+
16
+ ```ts
17
+ import { S, toJsonSchema } from "agent-kit-schema";
18
+ import type { Static } from "agent-kit-schema";
19
+
20
+ const schema = S.Object({
21
+ name: S.String({ description: "User name" }),
22
+ retries: S.Optional(S.Number({ default: 3 })),
23
+ mode: S.Enum(["fast", "safe"] as const, { default: "safe" }),
24
+ tags: S.Array(S.String(), { default: [] }),
25
+ });
26
+
27
+ type Input = Static<typeof schema>;
28
+ // {
29
+ // name: string;
30
+ // retries?: number;
31
+ // mode: "fast" | "safe";
32
+ // tags: string[];
33
+ // }
34
+
35
+ const jsonSchema = toJsonSchema(schema);
36
+ ```
37
+
38
+ ## API
39
+
40
+ ### Builders
41
+
42
+ - `S.String({ description?, default? })`
43
+ - `S.Number({ description?, default? })`
44
+ - `S.Boolean({ description?, default? })`
45
+ - `S.Enum(values, { description?, default? })`
46
+ - `S.Array(itemSchema, { description?, default? })`
47
+ - `S.Object({ [key]: schema })`
48
+ - `S.Optional(schema)`
49
+
50
+ ### Type helpers
51
+
52
+ - `Static<typeof schema>` infers the runtime TypeScript shape for a schema descriptor.
53
+ - Object properties wrapped in `S.Optional(...)` become optional properties in `Static`.
54
+
55
+ ### JSON Schema generation
56
+
57
+ - `toJsonSchema(schema)` converts any schema descriptor to standard JSON Schema.
58
+ - Object properties not wrapped in `S.Optional(...)` are emitted in `required`.
59
+ - Defaults provided to schema builders are emitted as JSON Schema `default`.
60
+ - Nested `S.Object(...)` schemas produce nested JSON Schema objects.
61
+ - `S.Enum(...)` rejects empty or duplicate values at runtime for JavaScript callers.
62
+
63
+ ## Environment variables
64
+
65
+ This package exposes no environment variables.
66
+
67
+ ## Configuration
68
+
69
+ This package currently exposes no package-level configuration options.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,12 @@
1
+ import { S } from "./index.js";
2
+ const ignoredStringSchema = S.String({ description: "Name", default: "guest" });
3
+ const ignoredNumberSchema = S.Number({ description: "Count", default: 1 });
4
+ const ignoredBooleanSchema = S.Boolean({ description: "Enabled", default: false });
5
+ const ignoredEnumSchema = S.Enum(["admin", "user"], { default: "admin" });
6
+ const ignoredIntegerEnumSchema = S.Enum([1, 2], { jsonType: "integer" });
7
+ const ignoredArraySchema = S.Array(S.String(), { default: ["a"] });
8
+ const ignoredObjectSchema = S.Object({
9
+ name: S.String(),
10
+ retries: S.Optional(S.Number()),
11
+ });
12
+ const ignoredOptionalSchema = S.Optional(S.Boolean());
@@ -0,0 +1,124 @@
1
+ type JsonSchemaType = "string" | "number" | "integer" | "boolean" | "array" | "object";
2
+ type SchemaKind = "string" | "number" | "boolean" | "enum" | "array" | "object" | "optional";
3
+ type EnumValue = string | number | boolean;
4
+ type JsonSchemaEnumValue = EnumValue | null;
5
+ type NumberJsonType = "number" | "integer";
6
+ type NonEmptyReadonlyArray<T> = readonly [T, ...T[]];
7
+ type ObjectShape = Record<string, AnySchema>;
8
+ type SchemaScope = "cli" | "mcp" | "sdk";
9
+ type StringMetadata = {
10
+ format?: string;
11
+ maxLength?: number;
12
+ minLength?: number;
13
+ pattern?: string;
14
+ };
15
+ type NumberMetadata = {
16
+ maximum?: number;
17
+ minimum?: number;
18
+ };
19
+ type ArrayMetadata = {
20
+ maxItems?: number;
21
+ minItems?: number;
22
+ };
23
+ type ObjectMetadata = {
24
+ additionalProperties?: boolean;
25
+ };
26
+ type OptionalKeys<TShape extends ObjectShape> = {
27
+ [TKey in keyof TShape]: TShape[TKey] extends OptionalSchema<any> ? TKey : never;
28
+ }[keyof TShape];
29
+ type RequiredKeys<TShape extends ObjectShape> = Exclude<keyof TShape, OptionalKeys<TShape>>;
30
+ type PropertyStatic<TSchema extends AnySchema> = TSchema extends OptionalSchema<infer TInner> ? Static<TInner> : Static<TSchema>;
31
+ type InferObject<TShape extends ObjectShape> = {
32
+ [TKey in RequiredKeys<TShape>]: PropertyStatic<TShape[TKey]>;
33
+ } & {
34
+ [TKey in OptionalKeys<TShape>]?: PropertyStatic<TShape[TKey]>;
35
+ };
36
+ type SchemaOptions<TDefault> = {
37
+ description?: string;
38
+ default?: TDefault;
39
+ nullable?: boolean;
40
+ requiredScopes?: readonly SchemaScope[];
41
+ short?: string;
42
+ scope?: readonly SchemaScope[];
43
+ };
44
+ interface SchemaBase<TKind extends SchemaKind, TStatic> {
45
+ readonly kind: TKind;
46
+ readonly description?: string;
47
+ readonly default?: TStatic;
48
+ readonly nullable?: boolean;
49
+ readonly requiredScopes?: readonly SchemaScope[];
50
+ readonly short?: string;
51
+ readonly scope?: readonly SchemaScope[];
52
+ readonly __static?: TStatic;
53
+ }
54
+ export interface JsonSchema {
55
+ additionalProperties?: boolean;
56
+ type?: JsonSchemaType;
57
+ description?: string;
58
+ default?: unknown;
59
+ enum?: ReadonlyArray<JsonSchemaEnumValue>;
60
+ format?: string;
61
+ items?: JsonSchema;
62
+ maxItems?: number;
63
+ maximum?: number;
64
+ maxLength?: number;
65
+ minItems?: number;
66
+ minimum?: number;
67
+ minLength?: number;
68
+ nullable?: boolean;
69
+ pattern?: string;
70
+ properties?: Record<string, JsonSchema>;
71
+ required?: string[];
72
+ }
73
+ export interface StringSchema extends SchemaBase<"string", string>, StringMetadata {
74
+ }
75
+ export interface NumberSchema extends SchemaBase<"number", number>, NumberMetadata {
76
+ readonly jsonType?: NumberJsonType;
77
+ }
78
+ export type BooleanSchema = SchemaBase<"boolean", boolean>;
79
+ export interface EnumSchema<TValues extends NonEmptyReadonlyArray<EnumValue>> extends SchemaBase<"enum", TValues[number]> {
80
+ readonly values: TValues;
81
+ readonly jsonType?: "integer";
82
+ readonly labels?: Partial<Record<string, string>>;
83
+ readonly loadOptions?: (() => Array<{
84
+ label: string;
85
+ value: string;
86
+ }>) | (() => Promise<Array<{
87
+ label: string;
88
+ value: string;
89
+ }>>);
90
+ }
91
+ export interface ArraySchema<TItem extends AnySchema> extends SchemaBase<"array", Array<Static<TItem>>>, ArrayMetadata {
92
+ readonly item: TItem;
93
+ }
94
+ export interface ObjectSchema<TShape extends ObjectShape> extends SchemaBase<"object", InferObject<TShape>>, ObjectMetadata {
95
+ readonly shape: TShape;
96
+ }
97
+ export interface OptionalSchema<TInner extends AnySchema> extends SchemaBase<"optional", Static<TInner> | undefined> {
98
+ readonly inner: TInner;
99
+ }
100
+ export type AnySchema = StringSchema | NumberSchema | BooleanSchema | EnumSchema<NonEmptyReadonlyArray<EnumValue>> | ArraySchema<AnySchema> | ObjectSchema<ObjectShape> | OptionalSchema<AnySchema>;
101
+ export type Static<TSchema extends AnySchema> = TSchema extends SchemaBase<any, infer TStatic> ? TStatic : never;
102
+ export declare const S: {
103
+ readonly String: (options?: SchemaOptions<string> & StringMetadata) => StringSchema;
104
+ readonly Number: (options?: SchemaOptions<number> & NumberMetadata & {
105
+ jsonType?: NumberJsonType;
106
+ }) => NumberSchema;
107
+ readonly Boolean: (options?: SchemaOptions<boolean>) => BooleanSchema;
108
+ readonly Enum: <const TValues extends NonEmptyReadonlyArray<EnumValue>>(values: TValues, options?: SchemaOptions<TValues[number]> & {
109
+ jsonType?: "integer";
110
+ labels?: Partial<Record<string, string>>;
111
+ loadOptions?: (() => Array<{
112
+ label: string;
113
+ value: string;
114
+ }>) | (() => Promise<Array<{
115
+ label: string;
116
+ value: string;
117
+ }>>);
118
+ }) => EnumSchema<TValues>;
119
+ readonly Array: <TItem extends AnySchema>(item: TItem, options?: SchemaOptions<Array<Static<TItem>>> & ArrayMetadata) => ArraySchema<TItem>;
120
+ readonly Object: <const TShape extends ObjectShape>(shape: TShape, options?: SchemaOptions<InferObject<TShape>> & ObjectMetadata) => ObjectSchema<TShape>;
121
+ readonly Optional: <TInner extends AnySchema>(inner: TInner) => OptionalSchema<TInner>;
122
+ };
123
+ export declare function toJsonSchema(schema: AnySchema): JsonSchema;
124
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,175 @@
1
+ function withMetadata(schema, jsonSchema) {
2
+ if (schema.description !== undefined) {
3
+ jsonSchema.description = schema.description;
4
+ }
5
+ if (schema.default !== undefined) {
6
+ jsonSchema.default = schema.default;
7
+ }
8
+ if (schema.nullable === true) {
9
+ jsonSchema.nullable = true;
10
+ }
11
+ return jsonSchema;
12
+ }
13
+ function withStringMetadata(schema, jsonSchema) {
14
+ if (schema.minLength !== undefined) {
15
+ jsonSchema.minLength = schema.minLength;
16
+ }
17
+ if (schema.maxLength !== undefined) {
18
+ jsonSchema.maxLength = schema.maxLength;
19
+ }
20
+ if (schema.pattern !== undefined) {
21
+ jsonSchema.pattern = schema.pattern;
22
+ }
23
+ if (schema.format !== undefined) {
24
+ jsonSchema.format = schema.format;
25
+ }
26
+ return withMetadata(schema, jsonSchema);
27
+ }
28
+ function withNumberMetadata(schema, jsonSchema) {
29
+ if (schema.minimum !== undefined) {
30
+ jsonSchema.minimum = schema.minimum;
31
+ }
32
+ if (schema.maximum !== undefined) {
33
+ jsonSchema.maximum = schema.maximum;
34
+ }
35
+ return withMetadata(schema, jsonSchema);
36
+ }
37
+ function withArrayMetadata(schema, jsonSchema) {
38
+ if (schema.minItems !== undefined) {
39
+ jsonSchema.minItems = schema.minItems;
40
+ }
41
+ if (schema.maxItems !== undefined) {
42
+ jsonSchema.maxItems = schema.maxItems;
43
+ }
44
+ return withMetadata(schema, jsonSchema);
45
+ }
46
+ function withObjectMetadata(schema, jsonSchema) {
47
+ if (schema.additionalProperties !== undefined) {
48
+ jsonSchema.additionalProperties = schema.additionalProperties;
49
+ }
50
+ return withMetadata(schema, jsonSchema);
51
+ }
52
+ function getEnumJsonType(values) {
53
+ const [firstValue] = values;
54
+ if (firstValue === undefined) {
55
+ return undefined;
56
+ }
57
+ const firstType = typeof firstValue;
58
+ const isSinglePrimitiveType = values.every((value) => typeof value === firstType);
59
+ if (!isSinglePrimitiveType) {
60
+ return undefined;
61
+ }
62
+ if (firstType === "string" || firstType === "number" || firstType === "boolean") {
63
+ return firstType;
64
+ }
65
+ return undefined;
66
+ }
67
+ function isOptionalSchema(schema) {
68
+ return schema.kind === "optional";
69
+ }
70
+ function assertValidEnumValues(values) {
71
+ if (values.length === 0) {
72
+ throw new Error("Enum schema requires at least one value");
73
+ }
74
+ const uniqueValues = new Set(values);
75
+ if (uniqueValues.size !== values.length) {
76
+ throw new Error("Enum schema values must be unique");
77
+ }
78
+ }
79
+ function unwrapOptional(schema) {
80
+ if (isOptionalSchema(schema)) {
81
+ return unwrapOptional(schema.inner);
82
+ }
83
+ return schema;
84
+ }
85
+ export const S = {
86
+ String(options = {}) {
87
+ return {
88
+ kind: "string",
89
+ ...options,
90
+ };
91
+ },
92
+ Number(options = {}) {
93
+ return {
94
+ kind: "number",
95
+ ...options,
96
+ };
97
+ },
98
+ Boolean(options = {}) {
99
+ return {
100
+ kind: "boolean",
101
+ ...options,
102
+ };
103
+ },
104
+ Enum(values, options = {}) {
105
+ assertValidEnumValues(values);
106
+ return {
107
+ kind: "enum",
108
+ values,
109
+ ...options,
110
+ };
111
+ },
112
+ Array(item, options = {}) {
113
+ return {
114
+ kind: "array",
115
+ item,
116
+ ...options,
117
+ };
118
+ },
119
+ Object(shape, options = {}) {
120
+ return {
121
+ kind: "object",
122
+ shape,
123
+ ...options,
124
+ };
125
+ },
126
+ Optional(inner) {
127
+ return {
128
+ kind: "optional",
129
+ inner,
130
+ };
131
+ },
132
+ };
133
+ export function toJsonSchema(schema) {
134
+ const unwrappedSchema = unwrapOptional(schema);
135
+ switch (unwrappedSchema.kind) {
136
+ case "string":
137
+ return withStringMetadata(unwrappedSchema, { type: "string" });
138
+ case "number":
139
+ return withNumberMetadata(unwrappedSchema, { type: unwrappedSchema.jsonType ?? "number" });
140
+ case "boolean":
141
+ return withMetadata(unwrappedSchema, { type: "boolean" });
142
+ case "enum": {
143
+ const jsonSchema = {
144
+ enum: unwrappedSchema.nullable === true
145
+ ? [...unwrappedSchema.values, null]
146
+ : [...unwrappedSchema.values],
147
+ };
148
+ const enumType = unwrappedSchema.jsonType ?? getEnumJsonType(unwrappedSchema.values);
149
+ if (enumType !== undefined) {
150
+ jsonSchema.type = enumType;
151
+ }
152
+ return withMetadata(unwrappedSchema, jsonSchema);
153
+ }
154
+ case "array":
155
+ return withArrayMetadata(unwrappedSchema, {
156
+ type: "array",
157
+ items: toJsonSchema(unwrappedSchema.item),
158
+ });
159
+ case "object": {
160
+ const properties = {};
161
+ const required = [];
162
+ for (const [key, propertySchema] of Object.entries(unwrappedSchema.shape)) {
163
+ properties[key] = toJsonSchema(propertySchema);
164
+ if (!isOptionalSchema(propertySchema)) {
165
+ required.push(key);
166
+ }
167
+ }
168
+ return withObjectMetadata(unwrappedSchema, {
169
+ type: "object",
170
+ properties,
171
+ required,
172
+ });
173
+ }
174
+ }
175
+ }
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "toolcraft-schema",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ }
12
+ },
13
+ "scripts": {
14
+ "build": "rm -rf dist && tsc",
15
+ "test": "cd ../.. && vitest run packages/agent-kit-schema/src/index.test.ts",
16
+ "test:unit": "cd ../.. && vitest run packages/agent-kit-schema/src/index.test.ts",
17
+ "lint": "cd ../.. && eslint packages/agent-kit-schema/src --ext ts && tsc -p packages/agent-kit-schema/tsconfig.json --noEmit"
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "engines": {
23
+ "node": ">=20"
24
+ },
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/poe-platform/poe-code.git",
28
+ "directory": "packages/agent-kit-schema"
29
+ }
30
+ }