tjs-lang 0.7.3 → 0.7.5
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 +29 -29
- package/dist/index.js +175 -176
- package/dist/index.js.map +5 -44
- package/dist/scripts/build.d.ts +8 -4
- package/dist/src/lang/runtime.d.ts +8 -4
- package/dist/src/types/Type.d.ts +5 -5
- package/dist/tjs-batteries.js +3 -4
- package/dist/tjs-batteries.js.map +5 -13
- package/dist/tjs-eval.js +47 -0
- package/dist/tjs-eval.js.map +7 -0
- package/dist/tjs-from-ts.js +58 -0
- package/dist/tjs-from-ts.js.map +7 -0
- package/dist/tjs-lang.js +349 -0
- package/dist/tjs-lang.js.map +7 -0
- package/dist/tjs-vm.js +51 -52
- package/dist/tjs-vm.js.map +4 -19
- package/package.json +17 -11
- package/src/lang/emitters/js.ts +4 -4
- package/src/lang/function-predicate.test.ts +8 -6
- package/src/lang/runtime.test.ts +58 -0
- package/src/lang/runtime.ts +27 -13
- package/src/types/Type.test.ts +68 -19
- package/src/types/Type.ts +80 -54
- package/dist/tjs-full.js +0 -437
- package/dist/tjs-full.js.map +0 -46
- package/dist/tjs-transpiler.js +0 -3
- package/dist/tjs-transpiler.js.map +0 -11
package/src/types/Type.ts
CHANGED
|
@@ -35,12 +35,12 @@ type Schema = Base<any> | JSONSchema
|
|
|
35
35
|
export interface RuntimeType<T = unknown> {
|
|
36
36
|
/** Human-readable description of the type */
|
|
37
37
|
readonly description: string
|
|
38
|
-
/** Check if a value matches this type */
|
|
39
|
-
check(value: unknown):
|
|
38
|
+
/** Check if a value matches this type. Returns true on pass, false on fail, or a reason string on fail. */
|
|
39
|
+
check(value: unknown): boolean | string
|
|
40
40
|
/** The underlying schema (if schema-based) */
|
|
41
41
|
readonly schema?: Schema
|
|
42
|
-
/** The predicate function (if predicate-based) */
|
|
43
|
-
readonly predicate?: (value: unknown) => boolean
|
|
42
|
+
/** The predicate function (if predicate-based). May return a reason string on failure. */
|
|
43
|
+
readonly predicate?: (value: unknown) => boolean | string
|
|
44
44
|
/** Example value (for documentation and signature testing) */
|
|
45
45
|
readonly example?: T
|
|
46
46
|
/** Multiple example values (from schema metadata, for autocomplete hints) */
|
|
@@ -99,7 +99,7 @@ function isJSONSchema(value: unknown): value is JSONSchema {
|
|
|
99
99
|
export function Type<T = unknown>(
|
|
100
100
|
descriptionOrSchema: string | Schema,
|
|
101
101
|
predicateOrSchemaOrExample?:
|
|
102
|
-
| ((value: unknown) => boolean)
|
|
102
|
+
| ((value: unknown) => boolean | string)
|
|
103
103
|
| Schema
|
|
104
104
|
| T
|
|
105
105
|
| undefined,
|
|
@@ -108,7 +108,7 @@ export function Type<T = unknown>(
|
|
|
108
108
|
): RuntimeType<T> {
|
|
109
109
|
// Parse arguments
|
|
110
110
|
let description: string
|
|
111
|
-
let predicate: ((value: unknown) => boolean) | undefined
|
|
111
|
+
let predicate: ((value: unknown) => boolean | string) | undefined
|
|
112
112
|
let schema: Schema | undefined
|
|
113
113
|
let example: T | undefined = exampleArg
|
|
114
114
|
let defaultValue: T | undefined = defaultArg
|
|
@@ -119,7 +119,9 @@ export function Type<T = unknown>(
|
|
|
119
119
|
|
|
120
120
|
if (typeof predicateOrSchemaOrExample === 'function') {
|
|
121
121
|
// Type(description, predicate, example?, default?)
|
|
122
|
-
predicate = predicateOrSchemaOrExample as (
|
|
122
|
+
predicate = predicateOrSchemaOrExample as (
|
|
123
|
+
value: unknown
|
|
124
|
+
) => boolean | string
|
|
123
125
|
// If we have example, infer schema from it for the type guard in predicate
|
|
124
126
|
if (example !== undefined) {
|
|
125
127
|
schema = s.infer(example)
|
|
@@ -176,7 +178,8 @@ export function Type<T = unknown>(
|
|
|
176
178
|
}
|
|
177
179
|
|
|
178
180
|
// Build the check function
|
|
179
|
-
|
|
181
|
+
// Returns true on pass, false on fail, or a reason string on fail
|
|
182
|
+
const check = (value: unknown): boolean | string => {
|
|
180
183
|
if (predicate) {
|
|
181
184
|
return predicate(value)
|
|
182
185
|
}
|
|
@@ -264,46 +267,59 @@ function schemaToDescription(schema: Schema): string {
|
|
|
264
267
|
// ============================================================================
|
|
265
268
|
|
|
266
269
|
/** String type */
|
|
267
|
-
export const TString = Type<string>(
|
|
268
|
-
'string'
|
|
269
|
-
|
|
270
|
-
)
|
|
270
|
+
export const TString = Type<string>('string', (v: unknown) => {
|
|
271
|
+
if (typeof v === 'string') return true
|
|
272
|
+
return `expected string, got ${v === null ? 'null' : typeof v}`
|
|
273
|
+
})
|
|
271
274
|
|
|
272
275
|
/** Number type */
|
|
273
|
-
export const TNumber = Type<number>(
|
|
274
|
-
'number'
|
|
275
|
-
|
|
276
|
-
)
|
|
276
|
+
export const TNumber = Type<number>('number', (v: unknown) => {
|
|
277
|
+
if (typeof v === 'number') return true
|
|
278
|
+
return `expected number, got ${v === null ? 'null' : typeof v}`
|
|
279
|
+
})
|
|
277
280
|
|
|
278
281
|
/** Boolean type */
|
|
279
|
-
export const TBoolean = Type<boolean>(
|
|
280
|
-
'boolean'
|
|
281
|
-
|
|
282
|
-
)
|
|
282
|
+
export const TBoolean = Type<boolean>('boolean', (v: unknown) => {
|
|
283
|
+
if (typeof v === 'boolean') return true
|
|
284
|
+
return `expected boolean, got ${v === null ? 'null' : typeof v}`
|
|
285
|
+
})
|
|
283
286
|
|
|
284
287
|
/** Integer type */
|
|
285
|
-
export const TInteger = Type<number>(
|
|
286
|
-
'
|
|
287
|
-
|
|
288
|
-
)
|
|
288
|
+
export const TInteger = Type<number>('integer', (v: unknown) => {
|
|
289
|
+
if (typeof v !== 'number')
|
|
290
|
+
return `expected integer, got ${v === null ? 'null' : typeof v}`
|
|
291
|
+
if (!Number.isInteger(v)) return `${v} is not an integer`
|
|
292
|
+
return true
|
|
293
|
+
})
|
|
289
294
|
|
|
290
295
|
/** Positive integer type */
|
|
291
|
-
export const TPositiveInt = Type<number>(
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
)
|
|
296
|
+
export const TPositiveInt = Type<number>('positive integer', (v: unknown) => {
|
|
297
|
+
if (typeof v !== 'number')
|
|
298
|
+
return `expected positive integer, got ${v === null ? 'null' : typeof v}`
|
|
299
|
+
if (!Number.isInteger(v)) return `${v} is not an integer`
|
|
300
|
+
if (v <= 0) return `${v} is not positive`
|
|
301
|
+
return true
|
|
302
|
+
})
|
|
295
303
|
|
|
296
304
|
/** Non-empty string type */
|
|
297
305
|
export const TNonEmptyString = Type<string>(
|
|
298
306
|
'non-empty string',
|
|
299
|
-
(v: unknown) =>
|
|
307
|
+
(v: unknown) => {
|
|
308
|
+
if (typeof v !== 'string')
|
|
309
|
+
return `expected string, got ${v === null ? 'null' : typeof v}`
|
|
310
|
+
if (v.length === 0) return 'string is empty'
|
|
311
|
+
return true
|
|
312
|
+
}
|
|
300
313
|
)
|
|
301
314
|
|
|
302
315
|
/** Email type (basic validation) */
|
|
303
|
-
export const TEmail = Type<string>(
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
)
|
|
316
|
+
export const TEmail = Type<string>('email address', (v: unknown) => {
|
|
317
|
+
if (typeof v !== 'string')
|
|
318
|
+
return `expected string, got ${v === null ? 'null' : typeof v}`
|
|
319
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v))
|
|
320
|
+
return `"${v}" is not a valid email`
|
|
321
|
+
return true
|
|
322
|
+
})
|
|
307
323
|
|
|
308
324
|
/**
|
|
309
325
|
* Check if a string is a valid URL (portable helper for predicates)
|
|
@@ -319,18 +335,23 @@ export const isValidUrl = (v: string): boolean => {
|
|
|
319
335
|
}
|
|
320
336
|
|
|
321
337
|
/** URL type */
|
|
322
|
-
export const TUrl = Type<string>(
|
|
323
|
-
'
|
|
324
|
-
|
|
325
|
-
)
|
|
338
|
+
export const TUrl = Type<string>('URL', (v: unknown) => {
|
|
339
|
+
if (typeof v !== 'string')
|
|
340
|
+
return `expected string, got ${v === null ? 'null' : typeof v}`
|
|
341
|
+
if (!isValidUrl(v)) return `"${v}" is not a valid URL`
|
|
342
|
+
return true
|
|
343
|
+
})
|
|
326
344
|
|
|
327
345
|
/** UUID type */
|
|
328
|
-
export const TUuid = Type<string>(
|
|
329
|
-
'
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
)
|
|
346
|
+
export const TUuid = Type<string>('UUID', (v: unknown) => {
|
|
347
|
+
if (typeof v !== 'string')
|
|
348
|
+
return `expected string, got ${v === null ? 'null' : typeof v}`
|
|
349
|
+
if (
|
|
350
|
+
!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v)
|
|
351
|
+
)
|
|
352
|
+
return `"${v}" is not a valid UUID`
|
|
353
|
+
return true
|
|
354
|
+
})
|
|
334
355
|
|
|
335
356
|
/**
|
|
336
357
|
* Check if a string is a valid ISO 8601 timestamp (portable helper for predicates)
|
|
@@ -371,7 +392,7 @@ export const LegalDate = Type<string>(
|
|
|
371
392
|
export function Nullable<T>(type: RuntimeType<T>): RuntimeType<T | null> {
|
|
372
393
|
return Type<T | null>(
|
|
373
394
|
`${type.description} or null`,
|
|
374
|
-
(v: unknown) => v === null || type.check(v)
|
|
395
|
+
(v: unknown) => v === null || type.check(v) === true
|
|
375
396
|
)
|
|
376
397
|
}
|
|
377
398
|
|
|
@@ -381,7 +402,7 @@ export function Optional<T>(
|
|
|
381
402
|
): RuntimeType<T | null | undefined> {
|
|
382
403
|
return Type<T | null | undefined>(
|
|
383
404
|
`${type.description} (optional)`,
|
|
384
|
-
(v: unknown) => v === null || v === undefined || type.check(v)
|
|
405
|
+
(v: unknown) => v === null || v === undefined || type.check(v) === true
|
|
385
406
|
)
|
|
386
407
|
}
|
|
387
408
|
|
|
@@ -434,14 +455,17 @@ export function Union<T extends unknown[]>(
|
|
|
434
455
|
types.push(...restTypes)
|
|
435
456
|
|
|
436
457
|
const description = types.map((t) => t.description).join(' | ')
|
|
437
|
-
return Type(description, (v: unknown) =>
|
|
458
|
+
return Type(description, (v: unknown) =>
|
|
459
|
+
types.some((t) => t.check(v) === true)
|
|
460
|
+
)
|
|
438
461
|
}
|
|
439
462
|
|
|
440
463
|
/** Create an array type */
|
|
441
464
|
export function TArray<T>(itemType: RuntimeType<T>): RuntimeType<T[]> {
|
|
442
465
|
return Type<T[]>(
|
|
443
466
|
`array of ${itemType.description}`,
|
|
444
|
-
(v: unknown) =>
|
|
467
|
+
(v: unknown) =>
|
|
468
|
+
Array.isArray(v) && v.every((item) => itemType.check(item) === true)
|
|
445
469
|
)
|
|
446
470
|
}
|
|
447
471
|
|
|
@@ -467,7 +491,7 @@ export interface GenericType<TParams extends string[] = string[]> {
|
|
|
467
491
|
*/
|
|
468
492
|
function typeParamToCheck(param: TypeParam): (value: unknown) => boolean {
|
|
469
493
|
if (isRuntimeType(param)) {
|
|
470
|
-
return (v) => param.check(v)
|
|
494
|
+
return (v) => param.check(v) === true
|
|
471
495
|
}
|
|
472
496
|
// Check if it's a schema builder (has .schema property)
|
|
473
497
|
if (param && typeof param === 'object' && 'schema' in param) {
|
|
@@ -820,21 +844,23 @@ function _createFunctionPredicate(
|
|
|
820
844
|
returnContract,
|
|
821
845
|
toJSONSchema: () => ({ description: name, type: 'function' as any }),
|
|
822
846
|
strip: (value: unknown) => value,
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
847
|
+
check: (value: unknown): boolean | string => {
|
|
848
|
+
if (typeof value !== 'function')
|
|
849
|
+
return `expected function, got ${
|
|
850
|
+
value === null ? 'null' : typeof value
|
|
851
|
+
}`
|
|
826
852
|
|
|
827
853
|
// Structural validation: check arity and __tjs metadata
|
|
828
854
|
const expectedArity = Object.keys(params).length
|
|
829
855
|
if (expectedArity > 0) {
|
|
830
|
-
// Check function.length (number of params before first default)
|
|
831
856
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
832
857
|
const fn = value as Function
|
|
833
858
|
const meta = (fn as any).__tjs
|
|
834
859
|
if (meta?.params) {
|
|
835
860
|
// Has TJS metadata — check param count matches
|
|
836
861
|
const metaParamCount = Object.keys(meta.params).length
|
|
837
|
-
if (metaParamCount !== expectedArity)
|
|
862
|
+
if (metaParamCount !== expectedArity)
|
|
863
|
+
return `expected ${expectedArity} params, got ${metaParamCount}`
|
|
838
864
|
|
|
839
865
|
// Check param type kinds match where both sides have type info
|
|
840
866
|
const expectedKeys = Object.keys(params)
|
|
@@ -849,7 +875,7 @@ function _createFunctionPredicate(
|
|
|
849
875
|
metaInfo.type.kind !== expectedKind &&
|
|
850
876
|
metaInfo.type.kind !== 'any'
|
|
851
877
|
)
|
|
852
|
-
return
|
|
878
|
+
return `param '${expectedKeys[i]}' expected ${expectedKind}, got ${metaInfo.type.kind}`
|
|
853
879
|
}
|
|
854
880
|
}
|
|
855
881
|
}
|