ts-procedures 10.1.0 → 10.1.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/CHANGELOG.md +29 -3
- package/build/schema/typebox.d.ts +1 -1
- package/build/schema/typebox.js +20 -2
- package/build/schema/typebox.js.map +1 -1
- package/build/schema/typebox.test.js +10 -0
- package/build/schema/typebox.test.js.map +1 -1
- package/docs/{doc-envelope-spec.md → doc-envelope-spec-v1.md} +8 -3
- package/docs/{doc-envelope.schema.json → doc-envelope-v1.schema.json} +2 -2
- package/docs/http-integrations.md +1 -1
- package/package.json +1 -1
- package/src/schema/typebox.test.ts +12 -0
- package/src/schema/typebox.ts +21 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,31 @@ All notable changes to **ts-procedures** are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## 10.1.1 — 2026-06-24
|
|
8
|
+
|
|
9
|
+
Fixes TypeBox schema detection for `Type.Unsafe(...)` on typebox ≥ 1.2.0. No
|
|
10
|
+
change to generated output, docs, or validation behavior.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **Top-level `Type.Unsafe(...)` channel schemas are recognized again.** TypeBox
|
|
15
|
+
1.2.0 tags `Type.Unsafe(...)` with a `~unsafe` marker *instead of* `~kind`, so
|
|
16
|
+
the built-in TypeBox adapter — which only looked for `~kind` — failed to detect
|
|
17
|
+
a bare top-level `Type.Unsafe(...)` used as a channel schema (e.g.
|
|
18
|
+
`schema: { params: Type.Unsafe(...) }`), throwing a `ProcedureRegistrationError`
|
|
19
|
+
at registration. Detection now keys off TypeBox v1's `~`-prefix marker
|
|
20
|
+
*convention* (via `Reflect.ownKeys`, since the markers are non-enumerable)
|
|
21
|
+
rather than a fixed list of names, so current and future `~*` markers are
|
|
22
|
+
recognized without further changes. `Type.Unsafe` nested inside a
|
|
23
|
+
`Type.Object(...)` was never affected.
|
|
24
|
+
|
|
25
|
+
### Notes
|
|
26
|
+
|
|
27
|
+
- Serialized JSON Schema was never affected: TypeBox's `~` markers are
|
|
28
|
+
non-enumerable, so they never reached `JSON.stringify` (DocEnvelope, codegen)
|
|
29
|
+
or AJV. The frozen codegen goldens and DocEnvelope parity fixtures are
|
|
30
|
+
unchanged. App devs only need to upgrade; no code changes required.
|
|
31
|
+
|
|
7
32
|
## 10.1.0 — 2026-06-24
|
|
8
33
|
|
|
9
34
|
Adds explicit versioning to the DocEnvelope so downstream consumers (docs
|
|
@@ -24,9 +49,10 @@ compatible. Purely additive — existing envelopes and consumers are unaffected.
|
|
|
24
49
|
parse-compatibility signal. Exported as `DOC_ENVELOPE_GENERATOR_VERSION` from
|
|
25
50
|
`ts-procedures/server`. Strippable via the `transform` hook for servers that
|
|
26
51
|
don't want to advertise the version on a public docs endpoint.
|
|
27
|
-
- **DocEnvelope wire-contract docs** — `docs/doc-envelope-spec.md` plus a
|
|
28
|
-
validatable JSON Schema at `docs/doc-envelope.schema.json
|
|
29
|
-
third-party tools against the
|
|
52
|
+
- **DocEnvelope wire-contract docs** — `docs/doc-envelope-spec-v1.md` plus a
|
|
53
|
+
validatable JSON Schema at `docs/doc-envelope-v1.schema.json` (filenames pinned
|
|
54
|
+
to the envelope `specVersion`), for building third-party tools against the
|
|
55
|
+
served envelope.
|
|
30
56
|
|
|
31
57
|
### Internal
|
|
32
58
|
|
|
@@ -2,7 +2,7 @@ import type { SchemaAdapter } from './adapter.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Built-in adapter for TypeBox (`import { Type } from 'typebox'`).
|
|
4
4
|
*
|
|
5
|
-
* Detection covers both typebox v1 (
|
|
5
|
+
* Detection covers both typebox v1 (any `~`-prefixed marker) and legacy
|
|
6
6
|
* `@sinclair/typebox` 0.3x (`Symbol.for('TypeBox.Kind')`). TypeBox schemas
|
|
7
7
|
* already ARE JSON Schema, so conversion is the identity.
|
|
8
8
|
*/
|
package/build/schema/typebox.js
CHANGED
|
@@ -1,8 +1,26 @@
|
|
|
1
1
|
const TYPEBOX_KIND = Symbol.for('TypeBox.Kind');
|
|
2
|
+
/** TypeBox v1 prefixes every internal marker key with `~`. */
|
|
3
|
+
const TILDE = '~'.charCodeAt(0);
|
|
4
|
+
/**
|
|
5
|
+
* TypeBox v1 namespaces all of its internal markers under a `~` prefix
|
|
6
|
+
* (`~kind`, `~unsafe`, `~optional`, ...) — non-enumerable own properties that
|
|
7
|
+
* stay out of the serialized JSON Schema. Detecting the *convention* rather
|
|
8
|
+
* than specific marker names means new markers (e.g. `~unsafe`, added in
|
|
9
|
+
* typebox 1.2.0 and used by `Type.Unsafe(...)` *instead of* `~kind`) are
|
|
10
|
+
* recognized without a code change. Reflect.ownKeys is required because these
|
|
11
|
+
* markers are non-enumerable.
|
|
12
|
+
*/
|
|
13
|
+
function hasTypeBoxV1Marker(schema) {
|
|
14
|
+
for (const key of Reflect.ownKeys(schema)) {
|
|
15
|
+
if (typeof key === 'string' && key.charCodeAt(0) === TILDE)
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
2
20
|
/**
|
|
3
21
|
* Built-in adapter for TypeBox (`import { Type } from 'typebox'`).
|
|
4
22
|
*
|
|
5
|
-
* Detection covers both typebox v1 (
|
|
23
|
+
* Detection covers both typebox v1 (any `~`-prefixed marker) and legacy
|
|
6
24
|
* `@sinclair/typebox` 0.3x (`Symbol.for('TypeBox.Kind')`). TypeBox schemas
|
|
7
25
|
* already ARE JSON Schema, so conversion is the identity.
|
|
8
26
|
*/
|
|
@@ -11,7 +29,7 @@ export const typeboxAdapter = {
|
|
|
11
29
|
detect(schema) {
|
|
12
30
|
if (typeof schema !== 'object' || schema === null)
|
|
13
31
|
return false;
|
|
14
|
-
return
|
|
32
|
+
return hasTypeBoxV1Marker(schema) || TYPEBOX_KIND in schema;
|
|
15
33
|
},
|
|
16
34
|
toJsonSchema(schema) {
|
|
17
35
|
return schema;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typebox.js","sourceRoot":"","sources":["../../src/schema/typebox.ts"],"names":[],"mappings":"AAGA,MAAM,YAAY,GAAG,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAA;AAE/C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,cAAc,GAAkB;IAC3C,IAAI,EAAE,SAAS;IACf,MAAM,CAAC,MAAe;QACpB,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,KAAK,CAAA;QAC/D,OAAO,
|
|
1
|
+
{"version":3,"file":"typebox.js","sourceRoot":"","sources":["../../src/schema/typebox.ts"],"names":[],"mappings":"AAGA,MAAM,YAAY,GAAG,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAA;AAE/C,8DAA8D;AAC9D,MAAM,KAAK,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;AAE/B;;;;;;;;GAQG;AACH,SAAS,kBAAkB,CAAC,MAAc;IACxC,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1C,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,KAAK;YAAE,OAAO,IAAI,CAAA;IACzE,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,cAAc,GAAkB;IAC3C,IAAI,EAAE,SAAS;IACf,MAAM,CAAC,MAAe;QACpB,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,KAAK,CAAA;QAC/D,OAAO,kBAAkB,CAAC,MAAM,CAAC,IAAI,YAAY,IAAI,MAAM,CAAA;IAC7D,CAAC;IACD,YAAY,CAAC,MAAe;QAC1B,OAAO,MAAqB,CAAA;IAC9B,CAAC;CACF,CAAA;AAED,4DAA4D;AAC5D,MAAM,UAAU,eAAe,CAAC,MAAe;IAC7C,OAAO,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;AACtC,CAAC"}
|
|
@@ -6,6 +6,16 @@ describe('typebox schema detection', () => {
|
|
|
6
6
|
test('it recognizes TypeBox schema', () => {
|
|
7
7
|
expect(isTypeboxSchema(typebox)).toBe(true);
|
|
8
8
|
});
|
|
9
|
+
test('it recognizes a top-level Type.Unsafe schema (~unsafe marker, no ~kind)', () => {
|
|
10
|
+
// typebox >= 1.2.0: Type.Unsafe carries `~unsafe` INSTEAD of `~kind`.
|
|
11
|
+
expect(isTypeboxSchema(Type.Unsafe({ type: 'string' }))).toBe(true);
|
|
12
|
+
});
|
|
13
|
+
test('it recognizes any future ~-prefixed marker (convention, not a name list)', () => {
|
|
14
|
+
// Detection keys off TypeBox's `~` namespace convention, so a marker that
|
|
15
|
+
// doesn't exist yet is still recognized without a code change.
|
|
16
|
+
const futureMarker = { '~somethingNew': true, type: 'object' };
|
|
17
|
+
expect(isTypeboxSchema(futureMarker)).toBe(true);
|
|
18
|
+
});
|
|
9
19
|
test('it rejects non-TypeBox values', () => {
|
|
10
20
|
expect(isTypeboxSchema({ type: 'object' })).toBe(false);
|
|
11
21
|
expect(isTypeboxSchema('string value')).toBe(false);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typebox.test.js","sourceRoot":"","sources":["../../src/schema/typebox.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAC/C,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAC9B,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAE9D,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;IACxC,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IAEpD,IAAI,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACxC,MAAM,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC7C,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACzC,MAAM,CAAC,eAAe,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACvD,MAAM,CAAC,eAAe,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACnD,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACxC,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACzC,MAAM,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAChD,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,kEAAkE,EAAE,GAAG,EAAE;QAC5E,MAAM,MAAM,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAA;QACzE,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC5C,CAAC,CAAC,CAAA;IAEF,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;QAC9B,IAAI,CAAC,yDAAyD,EAAE,GAAG,EAAE;YACnE,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QAC7C,CAAC,CAAC,CAAA;QAEF,IAAI,CAAC,gCAAgC,EAAE,GAAG,EAAE;YAC1C,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACjD,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YAC7D,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACjD,CAAC,CAAC,CAAA;QAEF,IAAI,CAAC,gEAAgE,EAAE,GAAG,EAAE;YAC1E,MAAM,CAAC,cAAc,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAC5D,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
|
1
|
+
{"version":3,"file":"typebox.test.js","sourceRoot":"","sources":["../../src/schema/typebox.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
# ts-procedures DocEnvelope — Consumer Spec
|
|
1
|
+
# ts-procedures DocEnvelope — Consumer Spec (`specVersion: 1`)
|
|
2
|
+
|
|
3
|
+
> **This document describes DocEnvelope `specVersion: 1`.** The filename is
|
|
4
|
+
> pinned to the spec version (`-v1`) so future format changes land as a sibling
|
|
5
|
+
> (`doc-envelope-spec-v2.md`) and the differences stay diffable. Check the
|
|
6
|
+
> envelope's `specVersion` field (absent ⇒ `1`) to know which spec applies.
|
|
2
7
|
|
|
3
8
|
A spec for a GUI that fetches a `DocEnvelope` JSON document from a ts-procedures
|
|
4
9
|
server URL and renders it. This is the **stable wire contract** between a
|
|
@@ -6,7 +11,7 @@ ts-procedures server and any documentation/codegen consumer. It is a *frozen*
|
|
|
6
11
|
contract in the framework (changing field names/nesting breaks codegen goldens),
|
|
7
12
|
so you can build against it confidently.
|
|
8
13
|
|
|
9
|
-
Companion file: `doc-envelope.schema.json` (JSON Schema Draft-07) — validate
|
|
14
|
+
Companion file: `doc-envelope-v1.schema.json` (JSON Schema Draft-07) — validate
|
|
10
15
|
fetched documents against it.
|
|
11
16
|
|
|
12
17
|
---
|
|
@@ -26,7 +31,7 @@ So your website's "paste a URL" flow should:
|
|
|
26
31
|
what the official codegen uses as its "is this an envelope?" gate. If
|
|
27
32
|
`routes` is missing/not-an-array, show a clear "not a ts-procedures docs
|
|
28
33
|
document" error.
|
|
29
|
-
4. Optionally validate fully against `doc-envelope.schema.json`.
|
|
34
|
+
4. Optionally validate fully against `doc-envelope-v1.schema.json`.
|
|
30
35
|
|
|
31
36
|
CORS: the server must allow your origin, or you'll need a proxy. Worth surfacing
|
|
32
37
|
as a distinct error from "bad JSON".
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
-
"$id": "https://ts-procedures.dev/schemas/doc-envelope.schema.json",
|
|
4
|
-
"title": "ts-procedures DocEnvelope",
|
|
3
|
+
"$id": "https://ts-procedures.dev/schemas/doc-envelope-v1.schema.json",
|
|
4
|
+
"title": "ts-procedures DocEnvelope (specVersion 1)",
|
|
5
5
|
"description": "The JSON document a ts-procedures server exposes (via builder.toDocEnvelope() / DocRegistry.toJSON()). Describes every procedure/route the server serves, the global error catalog, and global headers. Payload schemas embedded under each route's `jsonSchema` are arbitrary JSON Schema (Draft-07) objects and are intentionally left unconstrained here.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": ["basePath", "headers", "errors", "routes"],
|
|
@@ -337,7 +337,7 @@ app.get('/docs', (c) => c.json(
|
|
|
337
337
|
))
|
|
338
338
|
```
|
|
339
339
|
|
|
340
|
-
The `DocRegistry` output is the input for [Client Code Generation](./client-and-codegen.md). Building a third-party docs viewer or other tool that consumes the served envelope? The full wire contract — every field, per-kind shapes, and a validatable JSON Schema — is in [DocEnvelope Spec](./doc-envelope-spec.md) (`doc-envelope.schema.json`).
|
|
340
|
+
The `DocRegistry` output is the input for [Client Code Generation](./client-and-codegen.md). Building a third-party docs viewer or other tool that consumes the served envelope? The full wire contract — every field, per-kind shapes, and a validatable JSON Schema — is in [DocEnvelope Spec](./doc-envelope-spec-v1.md) (`doc-envelope-v1.schema.json`).
|
|
341
341
|
|
|
342
342
|
## Type Exports
|
|
343
343
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-procedures",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.1",
|
|
4
4
|
"description": "A TypeScript RPC framework that creates type-safe, schema-validated procedure calls with a single function definition. Define your procedures once and get full type inference, runtime validation, and framework integration hooks.",
|
|
5
5
|
"main": "build/exports.js",
|
|
6
6
|
"types": "build/exports.d.ts",
|
|
@@ -9,6 +9,18 @@ describe('typebox schema detection', () => {
|
|
|
9
9
|
expect(isTypeboxSchema(typebox)).toBe(true)
|
|
10
10
|
})
|
|
11
11
|
|
|
12
|
+
test('it recognizes a top-level Type.Unsafe schema (~unsafe marker, no ~kind)', () => {
|
|
13
|
+
// typebox >= 1.2.0: Type.Unsafe carries `~unsafe` INSTEAD of `~kind`.
|
|
14
|
+
expect(isTypeboxSchema(Type.Unsafe({ type: 'string' }))).toBe(true)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('it recognizes any future ~-prefixed marker (convention, not a name list)', () => {
|
|
18
|
+
// Detection keys off TypeBox's `~` namespace convention, so a marker that
|
|
19
|
+
// doesn't exist yet is still recognized without a code change.
|
|
20
|
+
const futureMarker = { '~somethingNew': true, type: 'object' }
|
|
21
|
+
expect(isTypeboxSchema(futureMarker)).toBe(true)
|
|
22
|
+
})
|
|
23
|
+
|
|
12
24
|
test('it rejects non-TypeBox values', () => {
|
|
13
25
|
expect(isTypeboxSchema({ type: 'object' })).toBe(false)
|
|
14
26
|
expect(isTypeboxSchema('string value')).toBe(false)
|
package/src/schema/typebox.ts
CHANGED
|
@@ -3,10 +3,29 @@ import type { TJSONSchema } from './json-schema.js'
|
|
|
3
3
|
|
|
4
4
|
const TYPEBOX_KIND = Symbol.for('TypeBox.Kind')
|
|
5
5
|
|
|
6
|
+
/** TypeBox v1 prefixes every internal marker key with `~`. */
|
|
7
|
+
const TILDE = '~'.charCodeAt(0)
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* TypeBox v1 namespaces all of its internal markers under a `~` prefix
|
|
11
|
+
* (`~kind`, `~unsafe`, `~optional`, ...) — non-enumerable own properties that
|
|
12
|
+
* stay out of the serialized JSON Schema. Detecting the *convention* rather
|
|
13
|
+
* than specific marker names means new markers (e.g. `~unsafe`, added in
|
|
14
|
+
* typebox 1.2.0 and used by `Type.Unsafe(...)` *instead of* `~kind`) are
|
|
15
|
+
* recognized without a code change. Reflect.ownKeys is required because these
|
|
16
|
+
* markers are non-enumerable.
|
|
17
|
+
*/
|
|
18
|
+
function hasTypeBoxV1Marker(schema: object): boolean {
|
|
19
|
+
for (const key of Reflect.ownKeys(schema)) {
|
|
20
|
+
if (typeof key === 'string' && key.charCodeAt(0) === TILDE) return true
|
|
21
|
+
}
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
24
|
+
|
|
6
25
|
/**
|
|
7
26
|
* Built-in adapter for TypeBox (`import { Type } from 'typebox'`).
|
|
8
27
|
*
|
|
9
|
-
* Detection covers both typebox v1 (
|
|
28
|
+
* Detection covers both typebox v1 (any `~`-prefixed marker) and legacy
|
|
10
29
|
* `@sinclair/typebox` 0.3x (`Symbol.for('TypeBox.Kind')`). TypeBox schemas
|
|
11
30
|
* already ARE JSON Schema, so conversion is the identity.
|
|
12
31
|
*/
|
|
@@ -14,7 +33,7 @@ export const typeboxAdapter: SchemaAdapter = {
|
|
|
14
33
|
name: 'typebox',
|
|
15
34
|
detect(schema: unknown): boolean {
|
|
16
35
|
if (typeof schema !== 'object' || schema === null) return false
|
|
17
|
-
return
|
|
36
|
+
return hasTypeBoxV1Marker(schema) || TYPEBOX_KIND in schema
|
|
18
37
|
},
|
|
19
38
|
toJsonSchema(schema: unknown): TJSONSchema {
|
|
20
39
|
return schema as TJSONSchema
|