tjs-lang 0.2.8 → 0.3.0
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/demo/docs.json +20 -14
- package/demo/src/examples.ts +23 -83
- package/demo/src/playground-shared.ts +666 -0
- package/demo/src/tjs-playground.ts +52 -528
- package/demo/src/ts-examples.ts +5 -4
- package/demo/src/ts-playground.ts +50 -414
- package/dist/index.js +58 -23
- package/dist/index.js.map +9 -9
- package/dist/src/lang/types.d.ts +1 -1
- package/dist/src/types/Type.d.ts +3 -1
- package/dist/tjs-full.js +58 -23
- package/dist/tjs-full.js.map +9 -9
- package/dist/tjs-transpiler.js +55 -20
- package/dist/tjs-transpiler.js.map +7 -7
- package/dist/tjs-vm.js +14 -14
- package/dist/tjs-vm.js.map +5 -5
- package/docs/index.js +740 -1010
- package/docs/index.js.map +9 -8
- package/editors/codemirror/ajs-language.ts +27 -1
- package/editors/codemirror/autocomplete.test.ts +3 -3
- package/package.json +1 -1
- package/src/lang/codegen.test.ts +11 -11
- package/src/lang/emitters/from-ts.ts +1 -1
- package/src/lang/emitters/js.ts +74 -0
- package/src/lang/inference.ts +40 -8
- package/src/lang/lang.test.ts +154 -16
- package/src/lang/runtime.ts +7 -0
- package/src/lang/types.ts +2 -0
- package/src/lang/typescript-syntax.test.ts +6 -4
- package/src/types/Type.test.ts +64 -0
- package/src/types/Type.ts +22 -1
- package/src/use-cases/transpiler-integration.test.ts +10 -10
- package/src/vm/atoms/batteries.ts +2 -0
|
@@ -42,7 +42,7 @@ describe('Basic Types', () => {
|
|
|
42
42
|
return x * 2
|
|
43
43
|
}
|
|
44
44
|
`)
|
|
45
|
-
expect(getFirstFunc(metadata).params.x.type.kind).toBe('
|
|
45
|
+
expect(getFirstFunc(metadata).params.x.type.kind).toBe('integer')
|
|
46
46
|
})
|
|
47
47
|
|
|
48
48
|
test('boolean parameter', () => {
|
|
@@ -76,7 +76,7 @@ describe('Basic Types', () => {
|
|
|
76
76
|
'string'
|
|
77
77
|
)
|
|
78
78
|
expect(getFirstFunc(metadata).params.user.type.shape?.age.kind).toBe(
|
|
79
|
-
'
|
|
79
|
+
'integer'
|
|
80
80
|
)
|
|
81
81
|
})
|
|
82
82
|
|
|
@@ -101,7 +101,9 @@ describe('Basic Types', () => {
|
|
|
101
101
|
}
|
|
102
102
|
`)
|
|
103
103
|
expect(getFirstFunc(metadata).params.nums.type.kind).toBe('array')
|
|
104
|
-
expect(getFirstFunc(metadata).params.nums.type.items?.kind).toBe(
|
|
104
|
+
expect(getFirstFunc(metadata).params.nums.type.items?.kind).toBe(
|
|
105
|
+
'integer'
|
|
106
|
+
)
|
|
105
107
|
})
|
|
106
108
|
|
|
107
109
|
test('array of objects', () => {
|
|
@@ -729,7 +731,7 @@ describe('Literal Types', () => {
|
|
|
729
731
|
return n
|
|
730
732
|
}
|
|
731
733
|
`)
|
|
732
|
-
expect(getFirstFunc(metadata).params.n.type.kind).toBe('
|
|
734
|
+
expect(getFirstFunc(metadata).params.n.type.kind).toBe('integer')
|
|
733
735
|
})
|
|
734
736
|
|
|
735
737
|
test('literal union type alias emits TJS Union', () => {
|
package/src/types/Type.test.ts
CHANGED
|
@@ -137,6 +137,70 @@ describe('Type()', () => {
|
|
|
137
137
|
expect(Email.check(Email.example)).toBe(true)
|
|
138
138
|
})
|
|
139
139
|
})
|
|
140
|
+
|
|
141
|
+
describe('Schema examples support', () => {
|
|
142
|
+
it('extracts examples from schema metadata', () => {
|
|
143
|
+
const Username = Type(
|
|
144
|
+
'username',
|
|
145
|
+
s.string.meta({ examples: ['alice', 'bob', 'charlie'] })
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
expect(Username.examples).toEqual(['alice', 'bob', 'charlie'])
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('sets example to first schema example when no explicit example given', () => {
|
|
152
|
+
const Username = Type(
|
|
153
|
+
'username',
|
|
154
|
+
s.string.meta({ examples: ['alice', 'bob'] })
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
expect(Username.example).toBe('alice')
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('preserves explicit example over schema examples', () => {
|
|
161
|
+
const Username = Type(
|
|
162
|
+
'username',
|
|
163
|
+
s.string.meta({ examples: ['alice', 'bob'] }),
|
|
164
|
+
'explicit_user'
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
expect(Username.example).toBe('explicit_user')
|
|
168
|
+
expect(Username.examples).toEqual(['alice', 'bob'])
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('has no examples when schema lacks metadata', () => {
|
|
172
|
+
const Name = Type('name', s.string)
|
|
173
|
+
|
|
174
|
+
expect(Name.examples).toBeUndefined()
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it('has no examples for predicate-based types', () => {
|
|
178
|
+
const Even = Type(
|
|
179
|
+
'even number',
|
|
180
|
+
(n) => typeof n === 'number' && n % 2 === 0
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
expect(Even.examples).toBeUndefined()
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it('has no examples for simple form', () => {
|
|
187
|
+
const Name = Type('name', 'Alice')
|
|
188
|
+
|
|
189
|
+
expect(Name.examples).toBeUndefined()
|
|
190
|
+
expect(Name.example).toBe('Alice')
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('validates using schema even with examples', () => {
|
|
194
|
+
const ShortString = Type(
|
|
195
|
+
'short string',
|
|
196
|
+
s.string.max(5).meta({ examples: ['hi', 'hey'] })
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
expect(ShortString.check('hi')).toBe(true)
|
|
200
|
+
expect(ShortString.check('toolong')).toBe(false)
|
|
201
|
+
expect(ShortString.examples).toEqual(['hi', 'hey'])
|
|
202
|
+
})
|
|
203
|
+
})
|
|
140
204
|
})
|
|
141
205
|
|
|
142
206
|
describe('Built-in Types', () => {
|
package/src/types/Type.ts
CHANGED
|
@@ -40,8 +40,10 @@ export interface RuntimeType<T = unknown> {
|
|
|
40
40
|
readonly schema?: Schema
|
|
41
41
|
/** The predicate function (if predicate-based) */
|
|
42
42
|
readonly predicate?: (value: unknown) => boolean
|
|
43
|
-
/** Example value (for documentation and
|
|
43
|
+
/** Example value (for documentation and signature testing) */
|
|
44
44
|
readonly example?: T
|
|
45
|
+
/** Multiple example values (from schema metadata, for autocomplete hints) */
|
|
46
|
+
readonly examples?: T[]
|
|
45
47
|
/** Default value (for instantiation) */
|
|
46
48
|
readonly default?: T
|
|
47
49
|
/** Brand for type identification */
|
|
@@ -150,6 +152,24 @@ export function Type<T = unknown>(
|
|
|
150
152
|
description = schemaToDescription(schema)
|
|
151
153
|
}
|
|
152
154
|
|
|
155
|
+
// Extract examples from schema metadata (if any)
|
|
156
|
+
let examples: T[] | undefined
|
|
157
|
+
if (schema) {
|
|
158
|
+
const jsonSchema = (schema as any)?.schema ?? schema
|
|
159
|
+
if (
|
|
160
|
+
jsonSchema &&
|
|
161
|
+
typeof jsonSchema === 'object' &&
|
|
162
|
+
Array.isArray((jsonSchema as any).examples)
|
|
163
|
+
) {
|
|
164
|
+
examples = (jsonSchema as any).examples as T[]
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// If no explicit example was provided, use first schema example for autocomplete
|
|
169
|
+
if (example === undefined && examples && examples.length > 0) {
|
|
170
|
+
example = examples[0]
|
|
171
|
+
}
|
|
172
|
+
|
|
153
173
|
// Build the check function
|
|
154
174
|
const check = (value: unknown): value is T => {
|
|
155
175
|
if (predicate) {
|
|
@@ -167,6 +187,7 @@ export function Type<T = unknown>(
|
|
|
167
187
|
schema,
|
|
168
188
|
predicate,
|
|
169
189
|
example,
|
|
190
|
+
examples,
|
|
170
191
|
default: defaultValue,
|
|
171
192
|
__runtimeType: true as const,
|
|
172
193
|
}
|
|
@@ -134,7 +134,7 @@ describe('Transpiler Integration', () => {
|
|
|
134
134
|
expect(signature.parameters.query.type.kind).toBe('string')
|
|
135
135
|
expect(signature.parameters.query.required).toBe(true)
|
|
136
136
|
expect(signature.parameters.query.description).toBe('Search terms')
|
|
137
|
-
expect(signature.parameters.limit.type.kind).toBe('
|
|
137
|
+
expect(signature.parameters.limit.type.kind).toBe('integer')
|
|
138
138
|
expect(signature.parameters.limit.required).toBe(false)
|
|
139
139
|
expect(signature.parameters.limit.default).toBe(10)
|
|
140
140
|
expect(signature.parameters.limit.description).toBe('Max results')
|
|
@@ -153,7 +153,7 @@ describe('Transpiler Integration', () => {
|
|
|
153
153
|
expect(signature.parameters.user.type.kind).toBe('object')
|
|
154
154
|
expect(signature.parameters.user.type.shape?.name.kind).toBe('string')
|
|
155
155
|
expect(signature.parameters.user.type.shape?.email.kind).toBe('string')
|
|
156
|
-
expect(signature.parameters.user.type.shape?.age.kind).toBe('
|
|
156
|
+
expect(signature.parameters.user.type.shape?.age.kind).toBe('integer')
|
|
157
157
|
expect(signature.parameters.user.required).toBe(true)
|
|
158
158
|
|
|
159
159
|
expect(signature.parameters.options.type.kind).toBe('object')
|
|
@@ -383,16 +383,16 @@ describe('Transpiler Integration', () => {
|
|
|
383
383
|
let answer = ''
|
|
384
384
|
let valid = false
|
|
385
385
|
let tries = 0
|
|
386
|
-
|
|
386
|
+
|
|
387
387
|
while (!valid && tries < 3) {
|
|
388
388
|
answer = llmPredict({ prompt: question })
|
|
389
389
|
tries = tries + 1
|
|
390
|
-
|
|
390
|
+
|
|
391
391
|
if (answer == 'A' || answer == 'B' || answer == 'C' || answer == 'D') {
|
|
392
392
|
valid = true
|
|
393
393
|
}
|
|
394
394
|
}
|
|
395
|
-
|
|
395
|
+
|
|
396
396
|
return { answer, tries, valid }
|
|
397
397
|
}
|
|
398
398
|
`)
|
|
@@ -772,9 +772,9 @@ describe('Transpiler Integration', () => {
|
|
|
772
772
|
const ast = ajs(`
|
|
773
773
|
function test() {
|
|
774
774
|
let d = Date('2024-06-15T10:30:00Z')
|
|
775
|
-
return {
|
|
776
|
-
year: d.year,
|
|
777
|
-
month: d.month,
|
|
775
|
+
return {
|
|
776
|
+
year: d.year,
|
|
777
|
+
month: d.month,
|
|
778
778
|
day: d.day,
|
|
779
779
|
hours: d.hours,
|
|
780
780
|
minutes: d.minutes
|
|
@@ -826,7 +826,7 @@ describe('Transpiler Integration', () => {
|
|
|
826
826
|
const ast = ajs(`
|
|
827
827
|
function test() {
|
|
828
828
|
let d = Date('2024-06-15T14:30:45Z')
|
|
829
|
-
return {
|
|
829
|
+
return {
|
|
830
830
|
iso: d.format('ISO'),
|
|
831
831
|
date: d.format('date'),
|
|
832
832
|
custom: d.format('YYYY-MM-DD')
|
|
@@ -847,7 +847,7 @@ describe('Transpiler Integration', () => {
|
|
|
847
847
|
function test() {
|
|
848
848
|
let a = Date('2024-01-15')
|
|
849
849
|
let b = Date('2024-01-20')
|
|
850
|
-
return {
|
|
850
|
+
return {
|
|
851
851
|
aBeforeB: a.isBefore(b),
|
|
852
852
|
aAfterB: a.isAfter(b)
|
|
853
853
|
}
|
|
@@ -145,6 +145,7 @@ export const llmPredictBattery = defineAtom(
|
|
|
145
145
|
responseFormat: s.any.optional,
|
|
146
146
|
}),
|
|
147
147
|
s.object({
|
|
148
|
+
role: s.string.optional,
|
|
148
149
|
content: s.string.optional,
|
|
149
150
|
tool_calls: s.array(s.any).optional,
|
|
150
151
|
}),
|
|
@@ -189,6 +190,7 @@ export const llmVision = defineAtom(
|
|
|
189
190
|
responseFormat: s.any.optional,
|
|
190
191
|
}),
|
|
191
192
|
s.object({
|
|
193
|
+
role: s.string.optional,
|
|
192
194
|
content: s.string.optional,
|
|
193
195
|
tool_calls: s.array(s.any).optional,
|
|
194
196
|
}),
|