tjs-lang 0.6.44 → 0.7.3
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/CLAUDE.md +85 -422
- package/README.md +15 -82
- package/bin/benchmarks.ts +7 -7
- package/bin/dev.ts +2 -1
- package/demo/autocomplete.test.ts +1 -1
- package/demo/docs.json +744 -48
- package/demo/src/demo-nav.ts +5 -5
- package/demo/src/index.ts +28 -36
- package/demo/src/module-sw.ts +1 -1
- package/demo/src/playground-shared.ts +17 -17
- package/demo/src/playground.ts +13 -1
- package/demo/src/style.ts +4 -1
- package/demo/src/tjs-playground.ts +5 -5
- package/demo/src/user-store.ts +2 -1
- package/demo/static/favicon.svg +17 -24
- package/demo/static/tosi-platform.json +9304 -0
- package/dist/index.js +158 -156
- package/dist/index.js.map +14 -13
- package/dist/scripts/compat-effect.d.ts +16 -0
- package/dist/scripts/compat-kysely.d.ts +13 -0
- package/dist/scripts/compat-radash.d.ts +13 -0
- package/dist/scripts/compat-superstruct.d.ts +13 -0
- package/dist/scripts/compat-ts-pattern.d.ts +13 -0
- package/dist/scripts/compat-zod.d.ts +12 -0
- package/dist/src/lang/emitters/from-ts.d.ts +1 -1
- package/dist/src/lang/emitters/js-tests.d.ts +4 -0
- package/dist/src/lang/emitters/js.d.ts +2 -2
- package/dist/src/lang/index.d.ts +1 -0
- package/dist/src/lang/json-schema.d.ts +40 -0
- package/dist/src/lang/parser-transforms.d.ts +14 -0
- package/dist/src/lang/runtime.d.ts +39 -6
- package/dist/src/types/Type.d.ts +5 -0
- package/dist/tjs-full.js +158 -156
- package/dist/tjs-full.js.map +14 -13
- package/dist/tjs-vm.js +44 -43
- package/dist/tjs-vm.js.map +5 -5
- package/docs/README.md +21 -20
- package/docs/WASM-QUICKSTART.md +283 -0
- package/docs/diagrams/architecture-shift.svg +117 -0
- package/docs/diagrams/compile-runtime.svg +130 -0
- package/docs/diagrams/icon-riff-1.svg +55 -0
- package/docs/diagrams/icon-riff-2.svg +62 -0
- package/docs/diagrams/icon-riff-3.svg +61 -0
- package/docs/diagrams/platform-overview.svg +114 -0
- package/docs/diagrams/safe-eval.svg +147 -0
- package/docs/eval-v4/arch-comparison.svg +277 -0
- package/docs/eval-v4/bundler-tree.svg +250 -0
- package/docs/eval-v4/http-lifecycle.svg +148 -0
- package/docs/function-predicate-design.md +8 -8
- package/docs/native-engine-integration.md +2 -2
- package/editors/codemirror/autocomplete.test.ts +29 -29
- package/package.json +10 -4
- package/src/cli/commands/convert.test.ts +11 -8
- package/src/cli/tjs.ts +1 -1
- package/src/lang/codegen.test.ts +117 -112
- package/src/lang/docs.test.ts +22 -22
- package/src/lang/docs.ts +5 -8
- package/src/lang/emitters/dts.test.ts +13 -13
- package/src/lang/emitters/from-ts.ts +36 -9
- package/src/lang/emitters/js-tests.ts +143 -28
- package/src/lang/emitters/js.ts +49 -28
- package/src/lang/features.test.ts +259 -43
- package/src/lang/from-ts.test.ts +3 -3
- package/src/lang/function-predicate.test.ts +1 -1
- package/src/lang/index.ts +8 -47
- package/src/lang/json-schema.test.ts +261 -0
- package/src/lang/json-schema.ts +167 -0
- package/src/lang/parser-params.ts +28 -44
- package/src/lang/parser-transforms.ts +255 -0
- package/src/lang/parser.test.ts +32 -13
- package/src/lang/parser.ts +49 -11
- package/src/lang/perf.test.ts +11 -11
- package/src/lang/roundtrip.test.ts +3 -3
- package/src/lang/runtime.test.ts +167 -0
- package/src/lang/runtime.ts +234 -46
- package/src/lang/transpiler.test.ts +21 -21
- package/src/lang/typescript-syntax.test.ts +11 -9
- package/src/types/Type.ts +38 -1
- package/src/use-cases/bootstrap.test.ts +7 -7
- package/src/use-cases/client-server.test.ts +1 -1
- package/src/use-cases/malicious-actor.test.ts +1 -1
- package/src/use-cases/rag-processor.test.ts +1 -1
- package/src/use-cases/sophisticated-agents.test.ts +2 -2
- package/src/use-cases/transpiler-llm.test.ts +1 -1
- package/src/use-cases/unbundled-imports.test.ts +9 -9
- package/tjs-lang.svg +17 -25
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { describe, it, expect } from 'bun:test'
|
|
2
|
+
import {
|
|
3
|
+
typeDescriptorToJSONSchema,
|
|
4
|
+
exampleToJSONSchema,
|
|
5
|
+
functionMetaToJSONSchema,
|
|
6
|
+
} from './json-schema'
|
|
7
|
+
// Also used directly in tests below
|
|
8
|
+
import type { TypeDescriptor } from './types'
|
|
9
|
+
import { Type, TString, TInteger } from '../types/Type'
|
|
10
|
+
import { tjs } from './index'
|
|
11
|
+
import { createRuntime, installRuntime } from './runtime'
|
|
12
|
+
|
|
13
|
+
describe('json-schema', () => {
|
|
14
|
+
describe('typeDescriptorToJSONSchema', () => {
|
|
15
|
+
it('handles primitives', () => {
|
|
16
|
+
expect(typeDescriptorToJSONSchema({ kind: 'string' })).toEqual({
|
|
17
|
+
type: 'string',
|
|
18
|
+
})
|
|
19
|
+
expect(typeDescriptorToJSONSchema({ kind: 'number' })).toEqual({
|
|
20
|
+
type: 'number',
|
|
21
|
+
})
|
|
22
|
+
expect(typeDescriptorToJSONSchema({ kind: 'integer' })).toEqual({
|
|
23
|
+
type: 'integer',
|
|
24
|
+
})
|
|
25
|
+
expect(typeDescriptorToJSONSchema({ kind: 'boolean' })).toEqual({
|
|
26
|
+
type: 'boolean',
|
|
27
|
+
})
|
|
28
|
+
expect(typeDescriptorToJSONSchema({ kind: 'null' })).toEqual({
|
|
29
|
+
type: 'null',
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('handles non-negative-integer', () => {
|
|
34
|
+
expect(
|
|
35
|
+
typeDescriptorToJSONSchema({ kind: 'non-negative-integer' })
|
|
36
|
+
).toEqual({ type: 'integer', minimum: 0 })
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('handles any and undefined', () => {
|
|
40
|
+
expect(typeDescriptorToJSONSchema({ kind: 'any' })).toEqual({})
|
|
41
|
+
expect(typeDescriptorToJSONSchema({ kind: 'undefined' })).toEqual({})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('handles arrays', () => {
|
|
45
|
+
expect(
|
|
46
|
+
typeDescriptorToJSONSchema({
|
|
47
|
+
kind: 'array',
|
|
48
|
+
items: { kind: 'string' },
|
|
49
|
+
})
|
|
50
|
+
).toEqual({ type: 'array', items: { type: 'string' } })
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('handles objects', () => {
|
|
54
|
+
const td: TypeDescriptor = {
|
|
55
|
+
kind: 'object',
|
|
56
|
+
shape: {
|
|
57
|
+
name: { kind: 'string' },
|
|
58
|
+
age: { kind: 'integer' },
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
expect(typeDescriptorToJSONSchema(td)).toEqual({
|
|
62
|
+
type: 'object',
|
|
63
|
+
properties: {
|
|
64
|
+
name: { type: 'string' },
|
|
65
|
+
age: { type: 'integer' },
|
|
66
|
+
},
|
|
67
|
+
required: ['name', 'age'],
|
|
68
|
+
additionalProperties: false,
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('handles nullable', () => {
|
|
73
|
+
expect(
|
|
74
|
+
typeDescriptorToJSONSchema({ kind: 'string', nullable: true })
|
|
75
|
+
).toEqual({ anyOf: [{ type: 'string' }, { type: 'null' }] })
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('handles unions', () => {
|
|
79
|
+
expect(
|
|
80
|
+
typeDescriptorToJSONSchema({
|
|
81
|
+
kind: 'union',
|
|
82
|
+
members: [{ kind: 'string' }, { kind: 'integer' }],
|
|
83
|
+
})
|
|
84
|
+
).toEqual({ anyOf: [{ type: 'string' }, { type: 'integer' }] })
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('handles nested objects', () => {
|
|
88
|
+
const td: TypeDescriptor = {
|
|
89
|
+
kind: 'object',
|
|
90
|
+
shape: {
|
|
91
|
+
user: {
|
|
92
|
+
kind: 'object',
|
|
93
|
+
shape: { name: { kind: 'string' } },
|
|
94
|
+
},
|
|
95
|
+
tags: {
|
|
96
|
+
kind: 'array',
|
|
97
|
+
items: { kind: 'string' },
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
const schema = typeDescriptorToJSONSchema(td)
|
|
102
|
+
expect(schema.properties?.user).toEqual({
|
|
103
|
+
type: 'object',
|
|
104
|
+
properties: { name: { type: 'string' } },
|
|
105
|
+
required: ['name'],
|
|
106
|
+
additionalProperties: false,
|
|
107
|
+
})
|
|
108
|
+
expect(schema.properties?.tags).toEqual({
|
|
109
|
+
type: 'array',
|
|
110
|
+
items: { type: 'string' },
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
describe('exampleToJSONSchema', () => {
|
|
116
|
+
it('infers from primitive examples', () => {
|
|
117
|
+
expect(exampleToJSONSchema('hello')).toEqual({ type: 'string' })
|
|
118
|
+
expect(exampleToJSONSchema(42)).toEqual({ type: 'integer' })
|
|
119
|
+
expect(exampleToJSONSchema(3.14)).toEqual({ type: 'number' })
|
|
120
|
+
expect(exampleToJSONSchema(true)).toEqual({ type: 'boolean' })
|
|
121
|
+
expect(exampleToJSONSchema(null)).toEqual({ type: 'null' })
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('infers from object examples', () => {
|
|
125
|
+
expect(exampleToJSONSchema({ name: '', age: 0 })).toEqual({
|
|
126
|
+
type: 'object',
|
|
127
|
+
properties: {
|
|
128
|
+
name: { type: 'string' },
|
|
129
|
+
age: { type: 'integer' },
|
|
130
|
+
},
|
|
131
|
+
required: ['name', 'age'],
|
|
132
|
+
additionalProperties: false,
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('infers from array examples', () => {
|
|
137
|
+
expect(exampleToJSONSchema(['hello'])).toEqual({
|
|
138
|
+
type: 'array',
|
|
139
|
+
items: { type: 'string' },
|
|
140
|
+
})
|
|
141
|
+
expect(exampleToJSONSchema([])).toEqual({ type: 'array' })
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
describe('RuntimeType.toJSONSchema', () => {
|
|
146
|
+
it('generates schema from example-based types', () => {
|
|
147
|
+
const User = Type('user', { name: '', age: 0 })
|
|
148
|
+
const schema = User.toJSONSchema()
|
|
149
|
+
expect(schema.type).toBe('object')
|
|
150
|
+
expect(schema.properties?.name).toEqual({ type: 'string' })
|
|
151
|
+
expect(schema.properties?.age).toEqual({ type: 'integer' })
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('generates schema for predicate-only types', () => {
|
|
155
|
+
const schema = TString.toJSONSchema()
|
|
156
|
+
// Predicate-only types have no example or schema, just description
|
|
157
|
+
expect(schema.description).toBe('string')
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('generates schema from simple types', () => {
|
|
161
|
+
const Name = Type('name', 'Alice')
|
|
162
|
+
const schema = Name.toJSONSchema()
|
|
163
|
+
expect(schema.type).toBe('string')
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
describe('RuntimeType.strip', () => {
|
|
168
|
+
it('strips extra fields from objects', () => {
|
|
169
|
+
const User = Type('user', { name: '', age: 0 })
|
|
170
|
+
const input = { name: 'Alice', age: 30, secret: 'password' }
|
|
171
|
+
const stripped = User.strip(input) as any
|
|
172
|
+
expect(stripped.name).toBe('Alice')
|
|
173
|
+
expect(stripped.age).toBe(30)
|
|
174
|
+
expect(stripped.secret).toBeUndefined()
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it('returns value as-is for predicate-only types', () => {
|
|
178
|
+
const result = TString.strip('hello')
|
|
179
|
+
expect(result).toBe('hello')
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
describe('functionMetaToJSONSchema', () => {
|
|
184
|
+
it('generates input/output schema from function metadata', () => {
|
|
185
|
+
const meta = {
|
|
186
|
+
params: {
|
|
187
|
+
name: {
|
|
188
|
+
type: { kind: 'string' as const },
|
|
189
|
+
required: true,
|
|
190
|
+
example: 'Alice',
|
|
191
|
+
},
|
|
192
|
+
age: {
|
|
193
|
+
type: { kind: 'integer' as const },
|
|
194
|
+
required: true,
|
|
195
|
+
example: 0,
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
returns: {
|
|
199
|
+
type: {
|
|
200
|
+
kind: 'object' as const,
|
|
201
|
+
shape: { id: { kind: 'integer' as const } },
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const { input, output } = functionMetaToJSONSchema(meta)
|
|
207
|
+
expect(input.type).toBe('object')
|
|
208
|
+
expect(input.properties?.name).toEqual({
|
|
209
|
+
type: 'string',
|
|
210
|
+
examples: ['Alice'],
|
|
211
|
+
})
|
|
212
|
+
expect(input.required).toContain('name')
|
|
213
|
+
expect(input.required).toContain('age')
|
|
214
|
+
expect(output?.type).toBe('object')
|
|
215
|
+
})
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
describe('fn.__tjs.schema() on transpiled functions', () => {
|
|
219
|
+
const savedTjs = globalThis.__tjs
|
|
220
|
+
|
|
221
|
+
it('provides schema via functionMetaToJSONSchema on emitted metadata', () => {
|
|
222
|
+
const runtime = createRuntime()
|
|
223
|
+
try {
|
|
224
|
+
globalThis.__tjs = runtime
|
|
225
|
+
const result = tjs(`function greet(name: 'World'): 'Hello, World' {
|
|
226
|
+
return 'Hello, ' + name
|
|
227
|
+
}`)
|
|
228
|
+
const fn = new Function(result.code + '\nreturn greet')()
|
|
229
|
+
expect(fn.__tjs).toBeDefined()
|
|
230
|
+
// Use standalone function on the metadata
|
|
231
|
+
const { input, output } = functionMetaToJSONSchema(fn.__tjs)
|
|
232
|
+
expect(input.type).toBe('object')
|
|
233
|
+
expect(input.properties?.name?.type).toBe('string')
|
|
234
|
+
expect(output?.type).toBe('string')
|
|
235
|
+
} finally {
|
|
236
|
+
globalThis.__tjs = savedTjs
|
|
237
|
+
}
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
it('provides schema via .schema() when wrap() is used', () => {
|
|
241
|
+
const runtime = createRuntime()
|
|
242
|
+
try {
|
|
243
|
+
globalThis.__tjs = runtime
|
|
244
|
+
installRuntime()
|
|
245
|
+
const result = tjs(`function greet(name: 'World'): 'Hello, World' {
|
|
246
|
+
return 'Hello, ' + name
|
|
247
|
+
}`)
|
|
248
|
+
const fn = new Function(result.code + '\nreturn greet')()
|
|
249
|
+
// After installRuntime(), wrap() should have attached .schema()
|
|
250
|
+
if (typeof fn.__tjs.schema === 'function') {
|
|
251
|
+
const { input, output } = fn.__tjs.schema()
|
|
252
|
+
expect(input.type).toBe('object')
|
|
253
|
+
expect(input.properties?.name?.type).toBe('string')
|
|
254
|
+
expect(output?.type).toBe('string')
|
|
255
|
+
}
|
|
256
|
+
} finally {
|
|
257
|
+
globalThis.__tjs = savedTjs
|
|
258
|
+
}
|
|
259
|
+
})
|
|
260
|
+
})
|
|
261
|
+
})
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Schema generation from TJS TypeDescriptors and example values
|
|
3
|
+
*
|
|
4
|
+
* Converts TJS type information into standard JSON Schema (draft 2020-12).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { TypeDescriptor } from './types'
|
|
8
|
+
|
|
9
|
+
export interface JSONSchemaObject {
|
|
10
|
+
type?: string | string[]
|
|
11
|
+
properties?: Record<string, JSONSchemaObject>
|
|
12
|
+
items?: JSONSchemaObject | JSONSchemaObject[]
|
|
13
|
+
required?: string[]
|
|
14
|
+
additionalProperties?: boolean
|
|
15
|
+
anyOf?: JSONSchemaObject[]
|
|
16
|
+
minimum?: number
|
|
17
|
+
examples?: unknown[]
|
|
18
|
+
description?: string
|
|
19
|
+
[key: string]: unknown
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Convert a TypeDescriptor to JSON Schema
|
|
24
|
+
*/
|
|
25
|
+
export function typeDescriptorToJSONSchema(
|
|
26
|
+
td: TypeDescriptor
|
|
27
|
+
): JSONSchemaObject {
|
|
28
|
+
if (td.nullable) {
|
|
29
|
+
const base = typeDescriptorToJSONSchema({ ...td, nullable: false })
|
|
30
|
+
return { anyOf: [base, { type: 'null' }] }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
switch (td.kind) {
|
|
34
|
+
case 'string':
|
|
35
|
+
return { type: 'string' }
|
|
36
|
+
case 'number':
|
|
37
|
+
return { type: 'number' }
|
|
38
|
+
case 'integer':
|
|
39
|
+
return { type: 'integer' }
|
|
40
|
+
case 'non-negative-integer':
|
|
41
|
+
return { type: 'integer', minimum: 0 }
|
|
42
|
+
case 'boolean':
|
|
43
|
+
return { type: 'boolean' }
|
|
44
|
+
case 'null':
|
|
45
|
+
return { type: 'null' }
|
|
46
|
+
case 'undefined':
|
|
47
|
+
return {}
|
|
48
|
+
case 'any':
|
|
49
|
+
return {}
|
|
50
|
+
case 'array':
|
|
51
|
+
if (td.items) {
|
|
52
|
+
return { type: 'array', items: typeDescriptorToJSONSchema(td.items) }
|
|
53
|
+
}
|
|
54
|
+
return { type: 'array' }
|
|
55
|
+
case 'object':
|
|
56
|
+
if (td.shape) {
|
|
57
|
+
const properties: Record<string, JSONSchemaObject> = {}
|
|
58
|
+
const required: string[] = []
|
|
59
|
+
for (const [key, fieldTd] of Object.entries(td.shape)) {
|
|
60
|
+
properties[key] = typeDescriptorToJSONSchema(fieldTd)
|
|
61
|
+
required.push(key)
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
type: 'object',
|
|
65
|
+
properties,
|
|
66
|
+
required,
|
|
67
|
+
additionalProperties: false,
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { type: 'object' }
|
|
71
|
+
case 'union':
|
|
72
|
+
if (td.members) {
|
|
73
|
+
return { anyOf: td.members.map(typeDescriptorToJSONSchema) }
|
|
74
|
+
}
|
|
75
|
+
return {}
|
|
76
|
+
default:
|
|
77
|
+
return {}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Infer a JSON Schema from an example value (without going through TypeDescriptor)
|
|
83
|
+
*/
|
|
84
|
+
export function exampleToJSONSchema(value: unknown): JSONSchemaObject {
|
|
85
|
+
if (value === null) return { type: 'null' }
|
|
86
|
+
if (value === undefined) return {}
|
|
87
|
+
|
|
88
|
+
switch (typeof value) {
|
|
89
|
+
case 'string':
|
|
90
|
+
return { type: 'string' }
|
|
91
|
+
case 'number':
|
|
92
|
+
return Number.isInteger(value) ? { type: 'integer' } : { type: 'number' }
|
|
93
|
+
case 'boolean':
|
|
94
|
+
return { type: 'boolean' }
|
|
95
|
+
case 'object': {
|
|
96
|
+
if (Array.isArray(value)) {
|
|
97
|
+
if (value.length === 0) return { type: 'array' }
|
|
98
|
+
// Infer item type from first element
|
|
99
|
+
return { type: 'array', items: exampleToJSONSchema(value[0]) }
|
|
100
|
+
}
|
|
101
|
+
// Object
|
|
102
|
+
const properties: Record<string, JSONSchemaObject> = {}
|
|
103
|
+
const required: string[] = []
|
|
104
|
+
for (const [key, val] of Object.entries(
|
|
105
|
+
value as Record<string, unknown>
|
|
106
|
+
)) {
|
|
107
|
+
properties[key] = exampleToJSONSchema(val)
|
|
108
|
+
required.push(key)
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
type: 'object',
|
|
112
|
+
properties,
|
|
113
|
+
required,
|
|
114
|
+
additionalProperties: false,
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
default:
|
|
118
|
+
return {}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Generate JSON Schema for function parameters (as an object schema)
|
|
124
|
+
* and return type from __tjs metadata
|
|
125
|
+
*/
|
|
126
|
+
export function functionMetaToJSONSchema(meta: {
|
|
127
|
+
params: Record<string, any>
|
|
128
|
+
returns?: { type: any; example?: any }
|
|
129
|
+
}): { input: JSONSchemaObject; output?: JSONSchemaObject } {
|
|
130
|
+
const properties: Record<string, JSONSchemaObject> = {}
|
|
131
|
+
const required: string[] = []
|
|
132
|
+
|
|
133
|
+
for (const [name, paramInfo] of Object.entries(meta.params)) {
|
|
134
|
+
if (paramInfo?.type?.kind) {
|
|
135
|
+
// Has TypeDescriptor
|
|
136
|
+
properties[name] = typeDescriptorToJSONSchema(paramInfo.type)
|
|
137
|
+
} else if (paramInfo?.example !== undefined) {
|
|
138
|
+
// Has example value
|
|
139
|
+
properties[name] = exampleToJSONSchema(paramInfo.example)
|
|
140
|
+
} else {
|
|
141
|
+
properties[name] = {}
|
|
142
|
+
}
|
|
143
|
+
if (paramInfo?.required !== false) {
|
|
144
|
+
required.push(name)
|
|
145
|
+
}
|
|
146
|
+
if (paramInfo?.example !== undefined) {
|
|
147
|
+
properties[name].examples = [paramInfo.example]
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const input: JSONSchemaObject = {
|
|
152
|
+
type: 'object',
|
|
153
|
+
properties,
|
|
154
|
+
required,
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let output: JSONSchemaObject | undefined
|
|
158
|
+
if (meta.returns) {
|
|
159
|
+
if (meta.returns.type?.kind) {
|
|
160
|
+
output = typeDescriptorToJSONSchema(meta.returns.type)
|
|
161
|
+
} else if (meta.returns.example !== undefined) {
|
|
162
|
+
output = exampleToJSONSchema(meta.returns.example)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return { input, output }
|
|
167
|
+
}
|
|
@@ -307,34 +307,28 @@ export function transformParenExpressions(
|
|
|
307
307
|
const processedParams = processParamString(params, ctx, true)
|
|
308
308
|
result += processedParams + ')'
|
|
309
309
|
|
|
310
|
-
// Check what follows the closing paren:
|
|
310
|
+
// Check what follows the closing paren: return type annotation (:, :?, :!)
|
|
311
311
|
let j = i
|
|
312
312
|
while (j < source.length && /\s/.test(source[j])) j++
|
|
313
313
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
314
|
+
if (source[j] === ':') {
|
|
315
|
+
const colonMarker = source.slice(j, j + 2)
|
|
316
|
+
let safety: 'safe' | 'unsafe' | undefined
|
|
317
|
+
if (colonMarker === ':?' || colonMarker === ':!') {
|
|
318
|
+
j += 2
|
|
319
|
+
safety = colonMarker === ':?' ? 'safe' : 'unsafe'
|
|
320
|
+
} else {
|
|
321
|
+
j += 1
|
|
322
|
+
}
|
|
323
323
|
while (j < source.length && /\s/.test(source[j])) j++
|
|
324
324
|
|
|
325
325
|
const typeResult = extractReturnTypeValue(source, j)
|
|
326
326
|
if (typeResult) {
|
|
327
|
-
const { type, endPos: typeEnd } = typeResult
|
|
328
|
-
// Record first return type for metadata
|
|
329
327
|
if (firstReturnType === undefined) {
|
|
330
|
-
firstReturnType = type
|
|
331
|
-
if (
|
|
332
|
-
firstReturnSafety = 'safe'
|
|
333
|
-
} else if (returnArrow === '-!') {
|
|
334
|
-
firstReturnSafety = 'unsafe'
|
|
335
|
-
}
|
|
328
|
+
firstReturnType = typeResult.type
|
|
329
|
+
if (safety) firstReturnSafety = safety
|
|
336
330
|
}
|
|
337
|
-
i =
|
|
331
|
+
i = typeResult.endPos
|
|
338
332
|
}
|
|
339
333
|
}
|
|
340
334
|
continue
|
|
@@ -398,27 +392,17 @@ export function transformParenExpressions(
|
|
|
398
392
|
const processedParams = processParamString(params, ctx, true)
|
|
399
393
|
result += processedParams + ')'
|
|
400
394
|
|
|
401
|
-
// Check for return type annotation: )
|
|
395
|
+
// Check for return type annotation: ): type, ):! type, ):? type
|
|
402
396
|
let j = i
|
|
403
397
|
while (j < source.length && /\s/.test(source[j])) j++
|
|
404
398
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
) {
|
|
412
|
-
j += 2
|
|
413
|
-
while (j < source.length && /\s/.test(source[j])) j++
|
|
414
|
-
const typeResult = extractReturnTypeValue(source, j)
|
|
415
|
-
if (typeResult) {
|
|
416
|
-
i = typeResult.endPos
|
|
399
|
+
if (source[j] === ':') {
|
|
400
|
+
const colonMarker = source.slice(j, j + 2)
|
|
401
|
+
if (colonMarker === ':?' || colonMarker === ':!') {
|
|
402
|
+
j += 2
|
|
403
|
+
} else {
|
|
404
|
+
j++
|
|
417
405
|
}
|
|
418
|
-
}
|
|
419
|
-
// Handle : return type (TS style) - just strip it
|
|
420
|
-
else if (source[j] === ':') {
|
|
421
|
-
j++
|
|
422
406
|
while (j < source.length && /\s/.test(source[j])) j++
|
|
423
407
|
const typeResult = extractReturnTypeValue(source, j)
|
|
424
408
|
if (typeResult) {
|
|
@@ -448,15 +432,15 @@ export function transformParenExpressions(
|
|
|
448
432
|
let j = endPos
|
|
449
433
|
while (j < source.length && /\s/.test(source[j])) j++
|
|
450
434
|
|
|
451
|
-
// Check for return type annotation on arrow function: )
|
|
435
|
+
// Check for return type annotation on arrow function: ): type =>
|
|
452
436
|
let arrowReturnType: string | undefined
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
437
|
+
if (source[j] === ':') {
|
|
438
|
+
const colonMarker = source.slice(j, j + 2)
|
|
439
|
+
if (colonMarker === ':?' || colonMarker === ':!') {
|
|
440
|
+
j += 2
|
|
441
|
+
} else {
|
|
442
|
+
j++
|
|
443
|
+
}
|
|
460
444
|
while (j < source.length && /\s/.test(source[j])) j++
|
|
461
445
|
const typeResult = extractReturnTypeValue(source, j)
|
|
462
446
|
if (typeResult) {
|