@orpc/json-schema 2.0.0-beta.47 → 2.0.0-beta.48
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 +2 -3
- package/dist/coercer.d.mts +29 -0
- package/dist/coercer.mjs +465 -0
- package/dist/composition-utils.d.mts +41 -0
- package/dist/composition-utils.mjs +357 -0
- package/dist/constants.d.mts +4 -0
- package/dist/constants.mjs +61 -0
- package/dist/convert.d.mts +29 -0
- package/dist/convert.mjs +14 -0
- package/dist/index.d.mts +10 -297
- package/dist/index.mjs +9 -1117
- package/dist/ref-utils.d.mts +44 -0
- package/dist/ref-utils.mjs +117 -0
- package/dist/smart-coercion-handler-plugin.d.mts +22 -0
- package/dist/smart-coercion-handler-plugin.mjs +42 -0
- package/dist/smart-coercion-link-plugin.d.mts +28 -0
- package/dist/smart-coercion-link-plugin.mjs +61 -0
- package/dist/standard-json-schema-converter.d.mts +16 -0
- package/dist/standard-json-schema-converter.mjs +30 -0
- package/dist/types.d.mts +51 -0
- package/dist/types.mjs +40 -0
- package/dist/utils.d.mts +40 -0
- package/dist/utils.mjs +50 -0
- package/package.json +6 -6
- package/dist/index.d.ts +0 -297
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* These utilities assume the schema has only one root-level `$defs` object
|
|
3
|
+
* and exclusively use absolute JSON pointers for `$ref` values.
|
|
4
|
+
*/
|
|
5
|
+
import type { JsonSchema } from "./types.mjs";
|
|
6
|
+
/**
|
|
7
|
+
* Encodes a JSON Pointer segment according to RFC 6901.
|
|
8
|
+
*
|
|
9
|
+
* https://datatracker.ietf.org/doc/html/rfc6901
|
|
10
|
+
*/
|
|
11
|
+
export declare function encodeJsonPointerSegment(segment: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Decodes a JSON Pointer segment according to RFC 6901.
|
|
14
|
+
*
|
|
15
|
+
* https://datatracker.ietf.org/doc/html/rfc6901
|
|
16
|
+
*/
|
|
17
|
+
export declare function decodeJsonPointerSegment(segment: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Visits every `$ref` in a schema, traversing the same keywords as {@link mapJsonSchemaRefs}.
|
|
20
|
+
* Shared or cyclic object instances are visited once.
|
|
21
|
+
*/
|
|
22
|
+
export declare function visitJsonSchemaRefs(value: JsonSchema, visit: (ref: string) => void, schemaLevel?: boolean, seen?: Set<object>): void;
|
|
23
|
+
export declare function mapJsonSchemaRefs(value: JsonSchema, map: (ref: string, path: Array<string | number>) => string, schemaLevel?: boolean, path?: Array<string | number>): JsonSchema;
|
|
24
|
+
/**
|
|
25
|
+
* Rewrites recursive root `#` refs by moving the schema body into `$defs`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function hoistRecursiveRefToDef(schema: JsonSchema): JsonSchema;
|
|
28
|
+
/**
|
|
29
|
+
* Resolves a local `$ref` at the **root level** of the given schema, if present.
|
|
30
|
+
*
|
|
31
|
+
* Only handles refs of the form `#/$defs/<name>` pointing into the provided
|
|
32
|
+
* (or schema-embedded) `$defs` map. Nested `$ref`s inside sub-schemas are
|
|
33
|
+
* intentionally left untouched.
|
|
34
|
+
*
|
|
35
|
+
* If the ref cannot be resolved (missing `$defs`, unknown key, etc.) the
|
|
36
|
+
* schema is returned as-is. Chained refs are followed until one repeats,
|
|
37
|
+
* which is kept.
|
|
38
|
+
*
|
|
39
|
+
* @param schema - The schema whose root-level `$ref` should be resolved.
|
|
40
|
+
* @param $defs - Definition map to resolve against. If omitted, falls back to
|
|
41
|
+
* `schema.$defs`. When provided, takes precedence over any `$defs` embedded
|
|
42
|
+
* in the schema.
|
|
43
|
+
*/
|
|
44
|
+
export declare function resolveJsonSchemaRootLocalRef(schema: JsonSchema, $defs?: Exclude<JsonSchema, boolean>["$defs"]): JsonSchema;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { get, setOwn } from "@orpc/shared";
|
|
2
|
+
import { JSON_SCHEMA_LOGIC_KEYWORDS, JSON_SCHEMA_RECORD_KEYWORDS } from "./constants.mjs";
|
|
3
|
+
|
|
4
|
+
export function encodeJsonPointerSegment(segment) {
|
|
5
|
+
return segment.replaceAll("~", "~0").replaceAll("/", "~1");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function decodeJsonPointerSegment(segment) {
|
|
9
|
+
return segment.replaceAll("~1", "/").replaceAll("~0", "~");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function visitJsonSchemaRefs(value, visit, schemaLevel = true, seen = new Set()) {
|
|
13
|
+
if (!value || typeof value !== "object" || seen.has(value)) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
seen.add(value);
|
|
17
|
+
if (Array.isArray(value)) {
|
|
18
|
+
for (const item of value) {
|
|
19
|
+
visitJsonSchemaRefs(item, visit, schemaLevel, seen);
|
|
20
|
+
}
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
for (const key of Object.keys(value)) {
|
|
24
|
+
const val = value[key];
|
|
25
|
+
if (key === "$ref" && typeof val === "string") {
|
|
26
|
+
visit(val);
|
|
27
|
+
} else if (!schemaLevel) {
|
|
28
|
+
visitJsonSchemaRefs(val, visit, true, seen);
|
|
29
|
+
} else if (JSON_SCHEMA_LOGIC_KEYWORDS.has(key) || JSON_SCHEMA_RECORD_KEYWORDS.has(key)) {
|
|
30
|
+
visitJsonSchemaRefs(val, visit, !JSON_SCHEMA_RECORD_KEYWORDS.has(key), seen);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function mapJsonSchemaRefs(value, map, schemaLevel = true, path = []) {
|
|
35
|
+
if (!value || typeof value !== "object") {
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
if (Array.isArray(value)) {
|
|
39
|
+
return value.map((item, index) => mapJsonSchemaRefs(item, map, schemaLevel, [...path, index]));
|
|
40
|
+
}
|
|
41
|
+
const result = {};
|
|
42
|
+
for (const key of Object.keys(value)) {
|
|
43
|
+
const val = value[key];
|
|
44
|
+
if (key === "$ref" && typeof val === "string") {
|
|
45
|
+
setOwn(result, key, map(val, [...path, key]));
|
|
46
|
+
} else if (!schemaLevel) {
|
|
47
|
+
setOwn(result, key, mapJsonSchemaRefs(val, map, true, [...path, key]));
|
|
48
|
+
} else if (JSON_SCHEMA_LOGIC_KEYWORDS.has(key) || JSON_SCHEMA_RECORD_KEYWORDS.has(key)) {
|
|
49
|
+
setOwn(result, key, mapJsonSchemaRefs(val, map, !JSON_SCHEMA_RECORD_KEYWORDS.has(key), [...path, key]));
|
|
50
|
+
} else {
|
|
51
|
+
setOwn(result, key, val);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function hoistRecursiveRefToDef(schema) {
|
|
58
|
+
if (typeof schema !== "object") {
|
|
59
|
+
return schema;
|
|
60
|
+
}
|
|
61
|
+
const isRootRef = (ref) => ref === "#" || ref.startsWith("#/") && !ref.startsWith("#/$defs/") && get(schema, ref.slice(2).split("/").map(decodeJsonPointerSegment)) !== undefined;
|
|
62
|
+
|
|
63
|
+
let hasRootRef = false;
|
|
64
|
+
visitJsonSchemaRefs(schema, (ref) => {
|
|
65
|
+
hasRootRef ||= isRootRef(ref);
|
|
66
|
+
});
|
|
67
|
+
if (!hasRootRef) {
|
|
68
|
+
return schema;
|
|
69
|
+
}
|
|
70
|
+
const defName = findRecursiveJsonSchemaDefName(schema.$defs);
|
|
71
|
+
const defRef = `#/$defs/${encodeJsonPointerSegment(defName)}`;
|
|
72
|
+
const { $defs, ...rest } = mapJsonSchemaRefs(schema, (ref) => isRootRef(ref) ? `${defRef}${ref.slice(1)}` : ref);
|
|
73
|
+
return {
|
|
74
|
+
$ref: defRef,
|
|
75
|
+
$defs: {
|
|
76
|
+
...$defs,
|
|
77
|
+
[defName]: rest
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function resolveJsonSchemaRootLocalRef(schema, $defs) {
|
|
83
|
+
if (typeof schema === "boolean") {
|
|
84
|
+
return schema;
|
|
85
|
+
}
|
|
86
|
+
if (arguments.length === 1) {
|
|
87
|
+
$defs = schema.$defs;
|
|
88
|
+
}
|
|
89
|
+
if (!$defs) {
|
|
90
|
+
return schema;
|
|
91
|
+
}
|
|
92
|
+
const followedRefs = new Set();
|
|
93
|
+
let current = schema;
|
|
94
|
+
while (typeof current.$ref === "string" && current.$ref.startsWith("#/$defs/") && !followedRefs.has(current.$ref)) {
|
|
95
|
+
followedRefs.add(current.$ref);
|
|
96
|
+
const resolved = get($defs, current.$ref.slice("#/$defs/".length).split("/").map(decodeJsonPointerSegment));
|
|
97
|
+
if (resolved === undefined) {
|
|
98
|
+
return current;
|
|
99
|
+
}
|
|
100
|
+
if (typeof resolved !== "object") {
|
|
101
|
+
return resolved;
|
|
102
|
+
}
|
|
103
|
+
const { $ref: _ref, ...rest } = current;
|
|
104
|
+
current = {
|
|
105
|
+
...rest,
|
|
106
|
+
...resolved
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return current;
|
|
110
|
+
}
|
|
111
|
+
function findRecursiveJsonSchemaDefName(defs) {
|
|
112
|
+
let index = 0;
|
|
113
|
+
while (defs?.[`__schema${index}`] !== undefined) {
|
|
114
|
+
index++;
|
|
115
|
+
}
|
|
116
|
+
return `__schema${index}`;
|
|
117
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Context } from "@orpc/server";
|
|
2
|
+
import type { StandardHandlerOptions, StandardHandlerPlugin } from "@orpc/server/standard";
|
|
3
|
+
import type { JsonSchemaCoercerOptions } from "./coercer.mjs";
|
|
4
|
+
import type { JsonSchemaConverter } from "./convert.mjs";
|
|
5
|
+
export interface SmartCoercionHandlerPluginOptions extends JsonSchemaCoercerOptions {
|
|
6
|
+
converters?: JsonSchemaConverter[] | undefined;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Coerces incoming request data to match the expected `.input` schema types before validation,
|
|
10
|
+
* based on JSON schemas produced by the configured converters.
|
|
11
|
+
*
|
|
12
|
+
* @see {@link https://orpc.dev/docs/plugins/smart-coercion | Smart Coercion Plugin}
|
|
13
|
+
*/
|
|
14
|
+
export declare class SmartCoercionHandlerPlugin<T extends Context> implements StandardHandlerPlugin<T> {
|
|
15
|
+
name: string;
|
|
16
|
+
private readonly converter;
|
|
17
|
+
private readonly coercer;
|
|
18
|
+
private readonly cache;
|
|
19
|
+
constructor(options?: SmartCoercionHandlerPluginOptions);
|
|
20
|
+
init(options: StandardHandlerOptions<T>): StandardHandlerOptions<T>;
|
|
21
|
+
private coerceValue;
|
|
22
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { toArray } from "@orpc/shared";
|
|
2
|
+
import { JsonSchemaCoercer } from "./coercer.mjs";
|
|
3
|
+
import { DelegatingJsonSchemaConverter } from "./convert.mjs";
|
|
4
|
+
import { StandardJsonSchemaConverter } from "./standard-json-schema-converter.mjs";
|
|
5
|
+
|
|
6
|
+
export class SmartCoercionHandlerPlugin {
|
|
7
|
+
name = "~smart-coercion";
|
|
8
|
+
converter;
|
|
9
|
+
coercer;
|
|
10
|
+
cache = new WeakMap();
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
this.converter = new DelegatingJsonSchemaConverter([...toArray(options.converters), new StandardJsonSchemaConverter()]);
|
|
13
|
+
this.coercer = new JsonSchemaCoercer(options);
|
|
14
|
+
}
|
|
15
|
+
init(options) {
|
|
16
|
+
return {
|
|
17
|
+
...options,
|
|
18
|
+
clientInterceptors: [({ next, input, ...interceptorOptions }) => {
|
|
19
|
+
const inputSchemas = interceptorOptions.procedure["~orpc"].inputSchemas;
|
|
20
|
+
if (!inputSchemas) {
|
|
21
|
+
return next();
|
|
22
|
+
}
|
|
23
|
+
const coercedInput = this.coerceValue(inputSchemas, input);
|
|
24
|
+
return next({
|
|
25
|
+
...interceptorOptions,
|
|
26
|
+
input: coercedInput
|
|
27
|
+
});
|
|
28
|
+
}, ...toArray(options.clientInterceptors)]
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
coerceValue(schemas, value) {
|
|
32
|
+
for (const schema of schemas) {
|
|
33
|
+
let converted = this.cache.get(schema);
|
|
34
|
+
if (!converted) {
|
|
35
|
+
converted = this.converter.convert(schema, "input");
|
|
36
|
+
this.cache.set(schema, converted);
|
|
37
|
+
}
|
|
38
|
+
value = this.coercer.coerce(converted, value);
|
|
39
|
+
}
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ClientContext } from "@orpc/client";
|
|
2
|
+
import type { StandardLinkOptions, StandardLinkPlugin } from "@orpc/client/standard";
|
|
3
|
+
import type { RouterContract } from "@orpc/contract";
|
|
4
|
+
import type { JsonSchemaCoercerOptions } from "./coercer.mjs";
|
|
5
|
+
import type { JsonSchemaConverter } from "./convert.mjs";
|
|
6
|
+
export interface SmartCoercionLinkPluginOptions extends JsonSchemaCoercerOptions {
|
|
7
|
+
converters?: JsonSchemaConverter[] | undefined;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Coerces server responses to match the expected `.output` or `.errors` schema types before validation,
|
|
11
|
+
* based on JSON schemas produced by the configured converters.
|
|
12
|
+
*
|
|
13
|
+
* @see {@link https://orpc.dev/docs/plugins/smart-coercion | Smart Coercion Plugin}
|
|
14
|
+
*/
|
|
15
|
+
export declare class SmartCoercionLinkPlugin<T extends ClientContext> implements StandardLinkPlugin<T> {
|
|
16
|
+
private readonly contract;
|
|
17
|
+
name: string;
|
|
18
|
+
/**
|
|
19
|
+
* Output and error values should be coerced before validation.
|
|
20
|
+
*/
|
|
21
|
+
after: string[];
|
|
22
|
+
private readonly converter;
|
|
23
|
+
private readonly coercer;
|
|
24
|
+
private readonly cache;
|
|
25
|
+
constructor(contract: RouterContract, options?: SmartCoercionLinkPluginOptions);
|
|
26
|
+
init(options: StandardLinkOptions<T>): StandardLinkOptions<T>;
|
|
27
|
+
private coerceValue;
|
|
28
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { cloneORPCError, ORPCError } from "@orpc/client";
|
|
2
|
+
import { getProcedureContractOrThrow } from "@orpc/contract";
|
|
3
|
+
import { getOwn, toArray } from "@orpc/shared";
|
|
4
|
+
import { JsonSchemaCoercer } from "./coercer.mjs";
|
|
5
|
+
import { DelegatingJsonSchemaConverter } from "./convert.mjs";
|
|
6
|
+
import { StandardJsonSchemaConverter } from "./standard-json-schema-converter.mjs";
|
|
7
|
+
|
|
8
|
+
export class SmartCoercionLinkPlugin {
|
|
9
|
+
contract;
|
|
10
|
+
name = "~smart-coercion";
|
|
11
|
+
|
|
12
|
+
after = ["~response-validation"];
|
|
13
|
+
converter;
|
|
14
|
+
coercer;
|
|
15
|
+
cache = new WeakMap();
|
|
16
|
+
constructor(contract, options = {}) {
|
|
17
|
+
this.contract = contract;
|
|
18
|
+
this.converter = new DelegatingJsonSchemaConverter([...toArray(options.converters), new StandardJsonSchemaConverter()]);
|
|
19
|
+
this.coercer = new JsonSchemaCoercer(options);
|
|
20
|
+
}
|
|
21
|
+
init(options) {
|
|
22
|
+
return {
|
|
23
|
+
...options,
|
|
24
|
+
interceptors: [...toArray(options.interceptors), async ({ next, path }) => {
|
|
25
|
+
const procedure = getProcedureContractOrThrow(this.contract, path);
|
|
26
|
+
try {
|
|
27
|
+
const output = await next();
|
|
28
|
+
const outputSchemas = procedure["~orpc"].outputSchemas;
|
|
29
|
+
if (!outputSchemas) {
|
|
30
|
+
return output;
|
|
31
|
+
}
|
|
32
|
+
const coercedOutput = this.coerceValue(outputSchemas, output);
|
|
33
|
+
return coercedOutput;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (!(error instanceof ORPCError) || !error.defined) {
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
const errorMap = procedure["~orpc"].errorMap;
|
|
39
|
+
const dataSchema = getOwn(errorMap, error.code)?.data;
|
|
40
|
+
if (!dataSchema) {
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
const cloned = cloneORPCError(error);
|
|
44
|
+
cloned.data = this.coerceValue([dataSchema], cloned.data);
|
|
45
|
+
throw cloned;
|
|
46
|
+
}
|
|
47
|
+
}]
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
coerceValue(schemas, value) {
|
|
51
|
+
for (const schema of schemas) {
|
|
52
|
+
let converted = this.cache.get(schema);
|
|
53
|
+
if (!converted) {
|
|
54
|
+
converted = this.converter.convert(schema, "output");
|
|
55
|
+
this.cache.set(schema, converted);
|
|
56
|
+
}
|
|
57
|
+
value = this.coercer.coerce(converted, value);
|
|
58
|
+
}
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AnySchema } from "@orpc/contract";
|
|
2
|
+
import type { StandardJSONSchemaV1 } from "@standard-schema/spec";
|
|
3
|
+
import type { JsonSchemaConverter, JsonSchemaConverterDirection } from "./convert.mjs";
|
|
4
|
+
import type { JsonSchema } from "./types.mjs";
|
|
5
|
+
/**
|
|
6
|
+
* Returns true when the schema accepts `undefined` in the given direction,
|
|
7
|
+
* found by validating `undefined` against it. For `output`, the validated value must also be `undefined`.
|
|
8
|
+
*
|
|
9
|
+
* Returns false when validation throws or is async. An async result is never left as an unhandled rejection.
|
|
10
|
+
*/
|
|
11
|
+
export declare function isStandardSchemaOptional(schema: AnySchema, direction: JsonSchemaConverterDirection): boolean;
|
|
12
|
+
export declare class StandardJsonSchemaConverter implements JsonSchemaConverter {
|
|
13
|
+
condition(schema: AnySchema | undefined, _direction: JsonSchemaConverterDirection): boolean;
|
|
14
|
+
convert(schema: AnySchema | undefined, direction: JsonSchemaConverterDirection): [jsonSchema: JsonSchema, optional: boolean];
|
|
15
|
+
convertInternal(schema: StandardJSONSchemaV1 & AnySchema, direction: JsonSchemaConverterDirection): [jsonSchema: JsonSchema, optional: boolean];
|
|
16
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { isTypescriptObject } from "@orpc/shared";
|
|
2
|
+
|
|
3
|
+
export function isStandardSchemaOptional(schema, direction) {
|
|
4
|
+
try {
|
|
5
|
+
const result = schema["~standard"].validate(undefined);
|
|
6
|
+
if (result instanceof Promise) {
|
|
7
|
+
result.catch(() => {});
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
return !result.issues && (direction === "input" || result.value === undefined);
|
|
11
|
+
} catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export class StandardJsonSchemaConverter {
|
|
16
|
+
condition(schema, _direction) {
|
|
17
|
+
return Boolean(schema && "jsonSchema" in schema["~standard"] && isTypescriptObject(schema["~standard"].jsonSchema) && "input" in schema["~standard"].jsonSchema && typeof schema["~standard"].jsonSchema.input === "function" && "output" in schema["~standard"].jsonSchema && typeof schema["~standard"].jsonSchema.output === "function");
|
|
18
|
+
}
|
|
19
|
+
convert(schema, direction) {
|
|
20
|
+
return this.convertInternal(schema, direction);
|
|
21
|
+
}
|
|
22
|
+
convertInternal(schema, direction) {
|
|
23
|
+
try {
|
|
24
|
+
const jsonSchema = direction === "input" ? schema["~standard"].jsonSchema.input({ target: "draft-2020-12" }) : schema["~standard"].jsonSchema.output({ target: "draft-2020-12" });
|
|
25
|
+
return [jsonSchema, isStandardSchemaOptional(schema, direction)];
|
|
26
|
+
} catch {
|
|
27
|
+
return [{}, true];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
package/dist/types.d.mts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type * as OpenAPIV3_2 from "@openapi-spec/types/v3.2";
|
|
2
|
+
/**
|
|
3
|
+
* A JSON Schema (draft 2020-12) representation used across oRPC's JSON schema tooling.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* It is also the OpenAPI 3.2 Schema Object, so converted schemas embed into OpenAPI documents as-is.
|
|
7
|
+
*
|
|
8
|
+
* @see {@link https://orpc.dev/docs/integrations/standard-schema | Standard Schema Integration}
|
|
9
|
+
*/
|
|
10
|
+
export type JsonSchema<Value = any> = OpenAPIV3_2.SchemaObject<Value>;
|
|
11
|
+
/**
|
|
12
|
+
* The declared JSON Schema keywords, the index signature is filtered out.
|
|
13
|
+
*/
|
|
14
|
+
export type JsonSchemaKeywords = keyof { [K in keyof OpenAPIV3_2.SchemaObjectFields as string extends K ? never : K]: unknown };
|
|
15
|
+
export declare enum JsonSchemaType {
|
|
16
|
+
Array = "array",
|
|
17
|
+
Boolean = "boolean",
|
|
18
|
+
Integer = "integer",
|
|
19
|
+
Null = "null",
|
|
20
|
+
Number = "number",
|
|
21
|
+
Object = "object",
|
|
22
|
+
String = "string"
|
|
23
|
+
}
|
|
24
|
+
export declare enum JsonSchemaFormat {
|
|
25
|
+
Date = "date",
|
|
26
|
+
DateTime = "date-time",
|
|
27
|
+
Duration = "duration",
|
|
28
|
+
Email = "email",
|
|
29
|
+
Hostname = "hostname",
|
|
30
|
+
IDNEmail = "idn-email",
|
|
31
|
+
IDNHostname = "idn-hostname",
|
|
32
|
+
IPv4 = "ipv4",
|
|
33
|
+
IPv6 = "ipv6",
|
|
34
|
+
IRI = "iri",
|
|
35
|
+
IRIReference = "iri-reference",
|
|
36
|
+
JSONPointer = "json-pointer",
|
|
37
|
+
RegEx = "regex",
|
|
38
|
+
RelativeJSONPointer = "relative-json-pointer",
|
|
39
|
+
Time = "time",
|
|
40
|
+
URI = "uri",
|
|
41
|
+
URIReference = "uri-reference",
|
|
42
|
+
URITemplate = "uri-template",
|
|
43
|
+
UUID = "uuid"
|
|
44
|
+
}
|
|
45
|
+
export declare enum JsonSchemaXNativeType {
|
|
46
|
+
BigInt = "bigint",
|
|
47
|
+
Date = "date",
|
|
48
|
+
Url = "url",
|
|
49
|
+
Set = "set",
|
|
50
|
+
Map = "map"
|
|
51
|
+
}
|
package/dist/types.mjs
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export let JsonSchemaType = /* @__PURE__ */ function(JsonSchemaType) {
|
|
2
|
+
JsonSchemaType["Array"] = "array";
|
|
3
|
+
JsonSchemaType["Boolean"] = "boolean";
|
|
4
|
+
JsonSchemaType["Integer"] = "integer";
|
|
5
|
+
JsonSchemaType["Null"] = "null";
|
|
6
|
+
JsonSchemaType["Number"] = "number";
|
|
7
|
+
JsonSchemaType["Object"] = "object";
|
|
8
|
+
JsonSchemaType["String"] = "string";
|
|
9
|
+
return JsonSchemaType;
|
|
10
|
+
}({});
|
|
11
|
+
export let JsonSchemaFormat = /* @__PURE__ */ function(JsonSchemaFormat) {
|
|
12
|
+
JsonSchemaFormat["Date"] = "date";
|
|
13
|
+
JsonSchemaFormat["DateTime"] = "date-time";
|
|
14
|
+
JsonSchemaFormat["Duration"] = "duration";
|
|
15
|
+
JsonSchemaFormat["Email"] = "email";
|
|
16
|
+
JsonSchemaFormat["Hostname"] = "hostname";
|
|
17
|
+
JsonSchemaFormat["IDNEmail"] = "idn-email";
|
|
18
|
+
JsonSchemaFormat["IDNHostname"] = "idn-hostname";
|
|
19
|
+
JsonSchemaFormat["IPv4"] = "ipv4";
|
|
20
|
+
JsonSchemaFormat["IPv6"] = "ipv6";
|
|
21
|
+
JsonSchemaFormat["IRI"] = "iri";
|
|
22
|
+
JsonSchemaFormat["IRIReference"] = "iri-reference";
|
|
23
|
+
JsonSchemaFormat["JSONPointer"] = "json-pointer";
|
|
24
|
+
JsonSchemaFormat["RegEx"] = "regex";
|
|
25
|
+
JsonSchemaFormat["RelativeJSONPointer"] = "relative-json-pointer";
|
|
26
|
+
JsonSchemaFormat["Time"] = "time";
|
|
27
|
+
JsonSchemaFormat["URI"] = "uri";
|
|
28
|
+
JsonSchemaFormat["URIReference"] = "uri-reference";
|
|
29
|
+
JsonSchemaFormat["URITemplate"] = "uri-template";
|
|
30
|
+
JsonSchemaFormat["UUID"] = "uuid";
|
|
31
|
+
return JsonSchemaFormat;
|
|
32
|
+
}({});
|
|
33
|
+
export let JsonSchemaXNativeType = /* @__PURE__ */ function(JsonSchemaXNativeType) {
|
|
34
|
+
JsonSchemaXNativeType["BigInt"] = "bigint";
|
|
35
|
+
JsonSchemaXNativeType["Date"] = "date";
|
|
36
|
+
JsonSchemaXNativeType["Url"] = "url";
|
|
37
|
+
JsonSchemaXNativeType["Set"] = "set";
|
|
38
|
+
JsonSchemaXNativeType["Map"] = "map";
|
|
39
|
+
return JsonSchemaXNativeType;
|
|
40
|
+
}({});
|
package/dist/utils.d.mts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* These utilities assume the schema has only one root-level `$defs` object
|
|
3
|
+
* and exclusively use absolute JSON pointers for `$ref` values.
|
|
4
|
+
*/
|
|
5
|
+
import type { JsonSchema } from "./types.mjs";
|
|
6
|
+
export declare function isJsonPrimitiveSchema(schema: JsonSchema): schema is JsonSchema & object;
|
|
7
|
+
export type JsonFileSchema = JsonSchema & object & {
|
|
8
|
+
type: "string";
|
|
9
|
+
contentMediaType?: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Returns true when the schema is a file-like string schema.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isJsonFileSchema(schema: JsonSchema): schema is JsonFileSchema;
|
|
15
|
+
/**
|
|
16
|
+
* Returns true when a file schema appears anywhere in the schema, such as in a property, a composition, or a def.
|
|
17
|
+
*/
|
|
18
|
+
export declare function containsJsonFileSchema(schema: JsonSchema): boolean;
|
|
19
|
+
export type JsonObjectSchema = JsonSchema & object & {
|
|
20
|
+
type: "object";
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Returns true when the schema is an object schema.
|
|
24
|
+
*/
|
|
25
|
+
export declare function isJsonObjectSchema(schema: JsonSchema): schema is JsonObjectSchema;
|
|
26
|
+
export type JsonArraySchema = JsonSchema & object & {
|
|
27
|
+
type: "array";
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Returns true when the schema is an array schema.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isJsonArraySchema(schema: JsonSchema): schema is JsonArraySchema;
|
|
33
|
+
/**
|
|
34
|
+
* Returns true when the schema does not apply any recognized constraints.
|
|
35
|
+
*/
|
|
36
|
+
export declare function isUnconstrainedSchema(schema: JsonSchema): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Ensures a JSON Schema is represented as an object schema document.
|
|
39
|
+
*/
|
|
40
|
+
export declare function ensureJsonSchemaObject(schema: JsonSchema): Exclude<JsonSchema, boolean>;
|
package/dist/utils.mjs
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { findDeepMatches, isPlainObject } from "@orpc/shared";
|
|
2
|
+
import { JSON_SCHEMA_LOGIC_KEYWORDS, JSON_SCHEMA_PRIMITIVE_TYPES } from "./constants.mjs";
|
|
3
|
+
export function isJsonPrimitiveSchema(schema) {
|
|
4
|
+
if (typeof schema === "boolean") {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
if (typeof schema.type === "string" && JSON_SCHEMA_PRIMITIVE_TYPES.has(schema.type)) {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
if (schema.const !== undefined) {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
if (schema.enum !== undefined) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function isJsonFileSchema(schema) {
|
|
20
|
+
return typeof schema !== "boolean" && schema.type === "string" && (typeof schema.contentMediaType === "string" || schema.format === "binary" || schema.contentEncoding === "binary");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function containsJsonFileSchema(schema) {
|
|
24
|
+
return findDeepMatches((v) => isPlainObject(v) && isJsonFileSchema(v), schema).values.length > 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function isJsonObjectSchema(schema) {
|
|
28
|
+
return typeof schema !== "boolean" && schema.type === "object";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function isJsonArraySchema(schema) {
|
|
32
|
+
return typeof schema !== "boolean" && schema.type === "array";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function isUnconstrainedSchema(schema) {
|
|
36
|
+
if (typeof schema === "boolean") {
|
|
37
|
+
return schema;
|
|
38
|
+
}
|
|
39
|
+
if (Object.keys(schema).every((k) => !JSON_SCHEMA_LOGIC_KEYWORDS.has(k))) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function ensureJsonSchemaObject(schema) {
|
|
46
|
+
if (typeof schema === "boolean") {
|
|
47
|
+
return schema ? {} : { not: {} };
|
|
48
|
+
}
|
|
49
|
+
return schema;
|
|
50
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orpc/json-schema",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.0.0-beta.
|
|
4
|
+
"version": "2.0.0-beta.48",
|
|
5
5
|
"description": "JSON Schema conversion and smart request coercion for oRPC and OpenAPI",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"funding": [
|
|
@@ -35,11 +35,11 @@
|
|
|
35
35
|
"dist"
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@openapi-spec/types": "~0.
|
|
39
|
-
"@orpc/client": "2.0.0-beta.
|
|
40
|
-
"@orpc/contract": "2.0.0-beta.
|
|
41
|
-
"@orpc/server": "2.0.0-beta.
|
|
42
|
-
"@orpc/shared": "2.0.0-beta.
|
|
38
|
+
"@openapi-spec/types": "~0.6.0",
|
|
39
|
+
"@orpc/client": "2.0.0-beta.48",
|
|
40
|
+
"@orpc/contract": "2.0.0-beta.48",
|
|
41
|
+
"@orpc/server": "2.0.0-beta.48",
|
|
42
|
+
"@orpc/shared": "2.0.0-beta.48",
|
|
43
43
|
"@standard-schema/spec": "^1.1.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|