@datasworn-community/build-tools 0.2.1 → 0.2.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.
@@ -0,0 +1,1138 @@
1
+ /* eslint-disable object-shorthand */
2
+ /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
3
+ /* eslint-disable @typescript-eslint/prefer-reduce-type-parameter */
4
+ /* eslint-disable @typescript-eslint/lines-between-class-members */
5
+ /* eslint-disable @typescript-eslint/no-array-constructor */
6
+ /* eslint-disable @typescript-eslint/dot-notation */
7
+ /* eslint-disable @typescript-eslint/consistent-indexed-object-style */
8
+ /* eslint-disable @typescript-eslint/no-namespace */
9
+ /* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */
10
+
11
+ // adapted from https://github.com/sinclairzx81/typebox/blob/master/examples/typedef/typedef.ts
12
+
13
+ import * as Types from '@sinclair/typebox'
14
+ import {
15
+ DefaultErrorFunction,
16
+ SetErrorFunction
17
+ } from '@sinclair/typebox/errors'
18
+ import type { SchemaFormEmpty, SchemaFormEnum, SchemaFormType } from 'jtd'
19
+
20
+ // --------------------------------------------------------------------------
21
+ // Utility Types
22
+ // --------------------------------------------------------------------------
23
+ export type Assert<T, U> = T extends U ? T : never
24
+ export type Base = { m: string; t: string }
25
+ export type Base16 = {
26
+ m: 'F'
27
+ t: '01'
28
+ '0': '1'
29
+ '1': '2'
30
+ '2': '3'
31
+ '3': '4'
32
+ '4': '5'
33
+ '5': '6'
34
+ '6': '7'
35
+ '7': '8'
36
+ '8': '9'
37
+ '9': 'A'
38
+ A: 'B'
39
+ B: 'C'
40
+ C: 'D'
41
+ D: 'E'
42
+ E: 'F'
43
+ F: '0'
44
+ }
45
+ export type Base10 = {
46
+ m: '9'
47
+ t: '01'
48
+ '0': '1'
49
+ '1': '2'
50
+ '2': '3'
51
+ '3': '4'
52
+ '4': '5'
53
+ '5': '6'
54
+ '6': '7'
55
+ '7': '8'
56
+ '8': '9'
57
+ '9': '0'
58
+ }
59
+ export type Reverse<T extends string> = T extends `${infer L}${infer R}`
60
+ ? `${Reverse<R>}${L}`
61
+ : T
62
+ export type Tick<T extends string, B extends Base> = T extends keyof B
63
+ ? B[T]
64
+ : never
65
+ export type Next<T extends string, B extends Base> = T extends Assert<
66
+ B,
67
+ Base
68
+ >['m']
69
+ ? Assert<B, Base>['t']
70
+ : T extends `${infer L}${infer R}`
71
+ ? L extends Assert<B, Base>['m']
72
+ ? `${Assert<Tick<L, B>, string>}${Next<R, B>}`
73
+ : `${Assert<Tick<L, B>, string>}${R}`
74
+ : never
75
+ export type Increment<T extends string, B extends Base = Base10> = Reverse<
76
+ Next<Reverse<T>, B>
77
+ >
78
+ // --------------------------------------------------------------------------
79
+ // SchemaOptions
80
+ // --------------------------------------------------------------------------
81
+
82
+ export type Metadata = {
83
+ /**
84
+ * `description` customizes the documentation comment to put on a type or property in generated code.
85
+ */
86
+ description?: string
87
+ /** Override for code generation, used as-is. */
88
+ typescriptType?: string
89
+ /** Override for code generation, used as-is. */
90
+ csharpSystemTextType?: string
91
+ /** Override for code generation, used as-is. */
92
+ goType?: string
93
+ /** Override for code generation, used as-is. */
94
+ javaJacksonType?: string
95
+ /** Override for code generation, used as-is. */
96
+ pythonType?: string
97
+ /** Override for code generation, used as-is. */
98
+ rubyType?: string
99
+ /** Override for code generation, used as-is. */
100
+ rustType?: string
101
+ } & Record<string, unknown>
102
+
103
+ export type EnumMetadata = Metadata & {
104
+ /**
105
+ * `enumDescription` is like `description`, but for the members of an `enum`. The keys of `enumDescription` should correspond to the values in the schema’s enum, and the values should be descriptions for those values.
106
+ */
107
+ enumDescription?: Record<string, string>
108
+ }
109
+
110
+ // --------------------------------------------------------------------------
111
+ // TAny
112
+ // --------------------------------------------------------------------------
113
+
114
+ export interface TAny extends Types.TSchema {
115
+ [Types.Kind]: 'TypeDef:Any'
116
+ static: any
117
+ }
118
+
119
+ // --------------------------------------------------------------------------
120
+ // TArray
121
+ // --------------------------------------------------------------------------
122
+ export interface TArray<T extends Types.TSchema = Types.TSchema>
123
+ extends Types.TSchema {
124
+ [Types.Kind]: 'TypeDef:Array'
125
+ static: Types.Static<T, this['params']>[]
126
+ elements: T
127
+ }
128
+ // --------------------------------------------------------------------------
129
+ // TBoolean
130
+ // --------------------------------------------------------------------------
131
+ export interface TBoolean extends Types.TSchema {
132
+ [Types.Kind]: 'TypeDef:Boolean'
133
+ static: 'boolean'
134
+ type: 'boolean'
135
+ }
136
+ // --------------------------------------------------------------------------
137
+ // TUnion
138
+ // --------------------------------------------------------------------------
139
+ type InferUnion<
140
+ T extends TStruct[],
141
+ D extends string,
142
+ Index = string
143
+ > = T extends [infer L, ...infer R]
144
+ ?
145
+ | Types.Evaluate<{ [_ in D]: Index } & Types.Static<Types.AssertType<L>>>
146
+ | InferUnion<
147
+ Types.AssertRest<R, TStruct[]>,
148
+ D,
149
+ Increment<Types.Assert<Index, string>>
150
+ >
151
+ : never
152
+
153
+ export interface TUnion<
154
+ T extends TStruct[] = TStruct[],
155
+ D extends string = string
156
+ > extends Types.TSchema {
157
+ [Types.Kind]: 'TypeDef:Union'
158
+ static: InferUnion<T, D, '0'>
159
+ discriminator: D
160
+ mapping: T
161
+ }
162
+ // --------------------------------------------------------------------------
163
+ // TEnum
164
+ // --------------------------------------------------------------------------
165
+ export interface TEnum<T extends string[] = string[]>
166
+ extends Types.TSchema,
167
+ SchemaFormEnum {
168
+ [Types.Kind]: 'TypeDef:Enum'
169
+ static: T[number]
170
+ enum: [...T]
171
+ metadata: EnumMetadata
172
+ }
173
+ // --------------------------------------------------------------------------
174
+ // TFloat32
175
+ // --------------------------------------------------------------------------
176
+ export interface TFloat32 extends Types.TSchema, SchemaFormType {
177
+ [Types.Kind]: 'TypeDef:Float32'
178
+ type: 'float32'
179
+ static: number
180
+ }
181
+ // --------------------------------------------------------------------------
182
+ // TFloat64
183
+ // --------------------------------------------------------------------------
184
+ export interface TFloat64 extends Types.TSchema, SchemaFormType {
185
+ [Types.Kind]: 'TypeDef:Float64'
186
+ type: 'float64'
187
+ static: number
188
+ }
189
+ // --------------------------------------------------------------------------
190
+ // TInt8
191
+ // --------------------------------------------------------------------------
192
+ export interface TInt8 extends Types.TSchema, SchemaFormType {
193
+ [Types.Kind]: 'TypeDef:Int8'
194
+ type: 'int8'
195
+ static: number
196
+ }
197
+ // --------------------------------------------------------------------------
198
+ // TInt16
199
+ // --------------------------------------------------------------------------
200
+ export interface TInt16 extends Types.TSchema, SchemaFormType {
201
+ [Types.Kind]: 'TypeDef:Int16'
202
+ type: 'int16'
203
+ static: number
204
+ }
205
+ // --------------------------------------------------------------------------
206
+ // TInt32
207
+ // --------------------------------------------------------------------------
208
+ export interface TInt32 extends Types.TSchema, SchemaFormType {
209
+ [Types.Kind]: 'TypeDef:Int32'
210
+ type: 'int32'
211
+ static: number
212
+ }
213
+ // --------------------------------------------------------------------------
214
+ // TUint8
215
+ // --------------------------------------------------------------------------
216
+ export interface TUint8 extends Types.TSchema, SchemaFormType {
217
+ [Types.Kind]: 'TypeDef:Uint8'
218
+ type: 'uint8'
219
+ static: number
220
+ }
221
+ // --------------------------------------------------------------------------
222
+ // TUint16
223
+ // --------------------------------------------------------------------------
224
+ export interface TUint16 extends Types.TSchema, SchemaFormType {
225
+ [Types.Kind]: 'TypeDef:Uint16'
226
+ type: 'uint16'
227
+ static: number
228
+ }
229
+ // --------------------------------------------------------------------------
230
+ // TUint32
231
+ // --------------------------------------------------------------------------
232
+ export interface TUint32 extends Types.TSchema, SchemaFormType {
233
+ [Types.Kind]: 'TypeDef:Uint32'
234
+ type: 'uint32'
235
+ static: number
236
+ }
237
+ // --------------------------------------------------------------------------
238
+ // TProperties
239
+ // --------------------------------------------------------------------------
240
+ export type TFields = Record<string, Types.TSchema>
241
+ // --------------------------------------------------------------------------
242
+ // TRecord
243
+ // --------------------------------------------------------------------------
244
+ export interface TRecord<T extends Types.TSchema = Types.TSchema>
245
+ extends Types.TSchema {
246
+ [Types.Kind]: 'TypeDef:Record'
247
+ static: Record<string, Types.Static<T, this['params']>>
248
+ values: T
249
+ }
250
+ // --------------------------------------------------------------------------
251
+ // TString
252
+ // --------------------------------------------------------------------------
253
+ export interface TString extends Types.TSchema {
254
+ [Types.Kind]: 'TypeDef:String'
255
+ type: 'string'
256
+ static: string
257
+ }
258
+
259
+ // --------------------------------------------------------------------------
260
+ // TStruct
261
+ // --------------------------------------------------------------------------
262
+ // used for structural type inference
263
+ type OptionalKeys<T extends TFields> = {
264
+ [K in keyof T]: T[K] extends Types.TOptional<T[K]> ? T[K] : never
265
+ }
266
+ type RequiredKeys<T extends TFields> = {
267
+ [K in keyof T]: T[K] extends Types.TOptional<T[K]> ? never : T[K]
268
+ }
269
+ // static inference
270
+ type ReadonlyOptionalPropertyKeys<T extends TFields> = {
271
+ [K in keyof T]: T[K] extends Types.TReadonly<Types.TSchema>
272
+ ? T[K] extends Types.TOptional<T[K]>
273
+ ? K
274
+ : never
275
+ : never
276
+ }[keyof T]
277
+ type ReadonlyPropertyKeys<T extends TFields> = {
278
+ [K in keyof T]: T[K] extends Types.TReadonly<Types.TSchema>
279
+ ? T[K] extends Types.TOptional<T[K]>
280
+ ? never
281
+ : K
282
+ : never
283
+ }[keyof T]
284
+ type OptionalPropertyKeys<T extends TFields> = {
285
+ [K in keyof T]: T[K] extends Types.TOptional<Types.TSchema>
286
+ ? T[K] extends Types.TReadonly<T[K]>
287
+ ? never
288
+ : K
289
+ : never
290
+ }[keyof T]
291
+ type RequiredPropertyKeys<T extends TFields> = keyof Omit<
292
+ T,
293
+ | ReadonlyOptionalPropertyKeys<T>
294
+ | ReadonlyPropertyKeys<T>
295
+ | OptionalPropertyKeys<T>
296
+ >
297
+ // prettier-ignore
298
+ type StructStaticProperties<
299
+ T extends TFields,
300
+ R extends Record<keyof any, unknown>
301
+ > = Types.Evaluate<
302
+ Readonly<Partial<Pick<R, ReadonlyOptionalPropertyKeys<T>>>> &
303
+ Readonly<Pick<R, ReadonlyPropertyKeys<T>>> &
304
+ Partial<Pick<R, OptionalPropertyKeys<T>>> &
305
+ Required<Pick<R, RequiredPropertyKeys<T>>>
306
+ >
307
+ // prettier-ignore
308
+ export type StructStatic<
309
+ T extends TFields,
310
+ P extends unknown[]
311
+ > = StructStaticProperties<
312
+ T,
313
+ {
314
+ [K in keyof T]: Static<T[K], P>
315
+ }
316
+ >
317
+
318
+ export interface StructMetadata extends Metadata {
319
+ additionalProperties?: boolean
320
+ }
321
+ export interface TStruct<T extends TFields = TFields>
322
+ extends Types.TSchema,
323
+ StructMetadata {
324
+ [Types.Kind]: 'TypeDef:Struct'
325
+ static: StructStatic<T, this['params']>
326
+ optionalProperties: { [K in Types.Assert<OptionalKeys<T>, keyof T>]: T[K] }
327
+ properties: { [K in Types.Assert<RequiredKeys<T>, keyof T>]: T[K] }
328
+ }
329
+ // --------------------------------------------------------------------------
330
+ // TTimestamp
331
+ // --------------------------------------------------------------------------
332
+ export interface TTimestamp extends Types.TSchema {
333
+ [Types.Kind]: 'TypeDef:Timestamp'
334
+ type: 'timestamp'
335
+ static: string
336
+ }
337
+
338
+ export interface TRef<T extends Types.TSchema = Types.TSchema>
339
+ extends Types.TSchema {
340
+ [Types.Kind]: 'TypeDef:Ref'
341
+ ref: string
342
+ static: Static<T>
343
+ }
344
+
345
+ export type TNullable<T extends Types.TSchema> = Omit<T, 'static'> & {
346
+ [Types.Kind]: T[typeof Types.Kind]
347
+ params: T['params']
348
+ nullable: true
349
+ static: T['static'] | null
350
+ }
351
+
352
+ export type TNonNullable<T extends Types.TSchema> = (T extends TNullable<
353
+ infer U
354
+ >
355
+ ? U
356
+ : T) & { nullable?: false | undefined }
357
+
358
+ // --------------------------------------------------------------------------
359
+ // Static
360
+ // --------------------------------------------------------------------------
361
+ export type Static<
362
+ T extends Types.TSchema,
363
+ P extends unknown[] = []
364
+ > = Types.Static<T, P>
365
+
366
+ // --------------------------------------------------------------------------
367
+ // TimestampFormat
368
+ // --------------------------------------------------------------------------
369
+ // biome-ignore lint/style/noNamespace: groups related timestamp validation logic with private helpers
370
+ export namespace TimestampFormat {
371
+ const DATE_TIME_SEPARATOR = /t|\s/i
372
+ const TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i
373
+ const DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/
374
+ const DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
375
+ function IsLeapYear(year: number): boolean {
376
+ return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)
377
+ }
378
+ function IsDate(str: string): boolean {
379
+ const matches: string[] | null = DATE.exec(str)
380
+ if (!matches) return false
381
+ const year: number = +matches[1]
382
+ const month: number = +matches[2]
383
+ const day: number = +matches[3]
384
+ return (
385
+ month >= 1 &&
386
+ month <= 12 &&
387
+ day >= 1 &&
388
+ day <= (month === 2 && IsLeapYear(year) ? 29 : DAYS[month])
389
+ )
390
+ }
391
+ function IsTime(str: string, strictTimeZone?: boolean): boolean {
392
+ const matches: string[] | null = TIME.exec(str)
393
+ if (!matches) return false
394
+ const hr: number = +matches[1]
395
+ const min: number = +matches[2]
396
+ const sec: number = +matches[3]
397
+ const tz: string | undefined = matches[4]
398
+ const tzSign: number = matches[5] === '-' ? -1 : 1
399
+ const tzH: number = +(matches[6] || 0)
400
+ const tzM: number = +(matches[7] || 0)
401
+ if (tzH > 23 || tzM > 59 || (strictTimeZone && !tz)) return false
402
+ if (hr <= 23 && min <= 59 && sec < 60) return true
403
+ const utcMin = min - tzM * tzSign
404
+ const utcHr = hr - tzH * tzSign - (utcMin < 0 ? 1 : 0)
405
+ return (
406
+ (utcHr === 23 || utcHr === -1) &&
407
+ (utcMin === 59 || utcMin === -1) &&
408
+ sec < 61
409
+ )
410
+ }
411
+ function IsDateTime(value: string, strictTimeZone?: boolean): boolean {
412
+ const dateTime: string[] = value.split(DATE_TIME_SEPARATOR)
413
+ return (
414
+ dateTime.length === 2 &&
415
+ IsDate(dateTime[0]) &&
416
+ IsTime(dateTime[1], strictTimeZone)
417
+ )
418
+ }
419
+ export function Check(value: string): boolean {
420
+ return IsDateTime(value)
421
+ }
422
+ }
423
+ // --------------------------------------------------------------------------
424
+ // ValueCheck
425
+ // --------------------------------------------------------------------------
426
+ export class ValueCheckError extends Types.TypeBoxError {
427
+ constructor(public readonly schema: Types.TSchema) {
428
+ super('Unknown type')
429
+ }
430
+ }
431
+ // biome-ignore lint/style/noNamespace: groups JTD type-checking functions with private helpers
432
+ export namespace ValueCheck {
433
+ // ------------------------------------------------------------------------
434
+ // Guards
435
+ // ------------------------------------------------------------------------
436
+ function IsObject(value: unknown): value is Record<keyof any, any> {
437
+ return (
438
+ typeof value === 'object' &&
439
+ value !== null &&
440
+ !globalThis.Array.isArray(value)
441
+ )
442
+ }
443
+ function IsArray(value: unknown): value is unknown[] {
444
+ return globalThis.Array.isArray(value)
445
+ }
446
+ function IsString(value: unknown): value is string {
447
+ return typeof value === 'string'
448
+ }
449
+ function IsInt(value: unknown, min: number, max: number): value is number {
450
+ return (
451
+ typeof value === 'number' &&
452
+ globalThis.Number.isInteger(value) &&
453
+ value >= min &&
454
+ value < max
455
+ )
456
+ }
457
+ // ------------------------------------------------------------------------
458
+ // Types
459
+ // ------------------------------------------------------------------------
460
+ // biome-ignore lint/suspicious/noShadowRestrictedNames: named after JTD type
461
+ function Array(schema: TArray, value: unknown): boolean {
462
+ return (
463
+ IsArray(value) && value.every((value) => Visit(schema.elements, value))
464
+ )
465
+ }
466
+ // biome-ignore lint/suspicious/noShadowRestrictedNames: named after JTD type
467
+ function Boolean(_schema: TBoolean, value: unknown): boolean {
468
+ return typeof value === 'boolean'
469
+ }
470
+ function Enum(schema: TEnum, value: unknown): boolean {
471
+ return typeof value === 'string' && schema.enum.includes(value)
472
+ }
473
+ function Float32(_schema: TFloat32, value: unknown): boolean {
474
+ return typeof value === 'number'
475
+ }
476
+ function Float64(_schema: TFloat64, value: unknown): boolean {
477
+ return typeof value === 'number'
478
+ }
479
+ function Int8(_schema: TInt8, value: unknown): boolean {
480
+ return IsInt(value, -128, 127)
481
+ }
482
+ function Int16(_schema: TInt16, value: unknown): boolean {
483
+ return IsInt(value, -32_768, 32_767)
484
+ }
485
+ function Int32(_schema: TInt32, value: unknown): boolean {
486
+ return IsInt(value, -2_147_483_648, 2_147_483_647)
487
+ }
488
+ function Uint8(_schema: TUint8, value: unknown): boolean {
489
+ return IsInt(value, 0, 255)
490
+ }
491
+ function Uint16(_schema: TUint16, value: unknown): boolean {
492
+ return IsInt(value, 0, 65535)
493
+ }
494
+ function Uint32(_schema: TUint32, value: unknown): boolean {
495
+ return IsInt(value, 0, 4_294_967_295)
496
+ }
497
+ function Record(schema: TRecord, value: unknown): boolean {
498
+ return (
499
+ IsObject(value) &&
500
+ globalThis.Object.getOwnPropertyNames(value).every((key) =>
501
+ Visit(schema.values, value[key])
502
+ )
503
+ )
504
+ }
505
+ // biome-ignore lint/suspicious/noShadowRestrictedNames: named after JTD type
506
+ function String(_schema: TString, value: unknown): boolean {
507
+ return typeof value === 'string'
508
+ }
509
+ function Struct(
510
+ schema: TStruct,
511
+ value: unknown,
512
+ descriminator?: string
513
+ ): boolean {
514
+ if (!IsObject(value)) return false
515
+ const optionalKeys =
516
+ schema.optionalProperties === undefined
517
+ ? []
518
+ : globalThis.Object.getOwnPropertyNames(schema.optionalProperties)
519
+ const requiredKeys =
520
+ schema.properties === undefined
521
+ ? []
522
+ : globalThis.Object.getOwnPropertyNames(schema.properties)
523
+ const unknownKeys = globalThis.Object.getOwnPropertyNames(value)
524
+ for (const requiredKey of requiredKeys) {
525
+ if (!(requiredKey in value)) return false
526
+ const requiredProperty = value[requiredKey]
527
+ const requiredSchema = (schema as any).properties[requiredKey]
528
+ if (!Visit(requiredSchema, requiredProperty)) return false
529
+ }
530
+ for (const optionalKey of optionalKeys) {
531
+ if (!(optionalKey in value)) continue
532
+ const optionalProperty = value[optionalKey]
533
+ const optionalSchema = (schema as any).properties[optionalKey]
534
+ if (!Visit(optionalSchema, optionalProperty)) return false
535
+ }
536
+ if (schema.additionalProperties === true) return true
537
+ const knownKeys = [...optionalKeys, ...requiredKeys]
538
+ for (const unknownKey of unknownKeys)
539
+ if (
540
+ !knownKeys.includes(unknownKey) &&
541
+ descriminator !== undefined &&
542
+ unknownKey !== descriminator
543
+ )
544
+ return false
545
+ for (const knownKey of knownKeys)
546
+ if (!unknownKeys.includes(knownKey)) return false
547
+ return true
548
+ }
549
+ function Timestamp(_schema: TString, value: unknown): boolean {
550
+ return IsString(value) && TimestampFormat.Check(value)
551
+ }
552
+ function Union(schema: TUnion, value: unknown): boolean {
553
+ if (!IsObject(value)) return false
554
+ if (!(schema.discriminator in value)) return false
555
+ if (!IsString(value[schema.discriminator])) return false
556
+ if (!(value[schema.discriminator] in schema.mapping)) return false
557
+ const struct = schema.mapping[value[schema.discriminator]] as TStruct
558
+ return Struct(struct, value, schema.discriminator)
559
+ }
560
+ function Visit(schema: Types.TSchema, value: unknown): boolean {
561
+ const anySchema = schema as any
562
+ switch (anySchema[Types.Kind]) {
563
+ case 'TypeDef:Array':
564
+ return Array(anySchema, value)
565
+ case 'TypeDef:Boolean':
566
+ return Boolean(anySchema, value)
567
+ case 'TypeDef:Union':
568
+ return Union(anySchema, value)
569
+ case 'TypeDef:Enum':
570
+ return Enum(anySchema, value)
571
+ case 'TypeDef:Float32':
572
+ return Float32(anySchema, value)
573
+ case 'TypeDef:Float64':
574
+ return Float64(anySchema, value)
575
+ case 'TypeDef:Int8':
576
+ return Int8(anySchema, value)
577
+ case 'TypeDef:Int16':
578
+ return Int16(anySchema, value)
579
+ case 'TypeDef:Int32':
580
+ return Int32(anySchema, value)
581
+ case 'TypeDef:Uint8':
582
+ return Uint8(anySchema, value)
583
+ case 'TypeDef:Uint16':
584
+ return Uint16(anySchema, value)
585
+ case 'TypeDef:Uint32':
586
+ return Uint32(anySchema, value)
587
+ case 'TypeDef:Record':
588
+ return Record(anySchema, value)
589
+ case 'TypeDef:String':
590
+ return String(anySchema, value)
591
+ case 'TypeDef:Struct':
592
+ return Struct(anySchema, value)
593
+ case 'TypeDef:Timestamp':
594
+ return Timestamp(anySchema, value)
595
+ default:
596
+ throw new ValueCheckError(anySchema)
597
+ }
598
+ }
599
+ export function Check<T extends Types.TSchema>(
600
+ schema: T,
601
+ value: unknown
602
+ ): value is Types.Static<T> {
603
+ return Visit(schema, value)
604
+ }
605
+ }
606
+ // --------------------------------------------------------------------------
607
+ // TypeGuard
608
+ // --------------------------------------------------------------------------
609
+ // biome-ignore lint/style/noNamespace: groups JTD type guard functions with private helpers
610
+ export namespace TypeGuard {
611
+ // ------------------------------------------------------------------------
612
+ // Guards
613
+ // ------------------------------------------------------------------------
614
+ function IsObject(value: unknown): value is Record<keyof any, unknown> {
615
+ return typeof value === 'object'
616
+ }
617
+ function IsArray(value: unknown): value is unknown[] {
618
+ return globalThis.Array.isArray(value)
619
+ }
620
+ function IsOptionalBoolean(value: unknown): value is boolean | undefined {
621
+ return IsBoolean(value) || value === undefined
622
+ }
623
+ function IsBoolean(value: unknown): value is boolean {
624
+ return typeof value === 'boolean'
625
+ }
626
+ function IsString(value: unknown): value is string {
627
+ return typeof value === 'string'
628
+ }
629
+ // ------------------------------------------------------------------------
630
+ // Types
631
+ // ------------------------------------------------------------------------
632
+ export function TNullable(
633
+ schema: unknown
634
+ ): schema is TNullable<Types.TSchema> {
635
+ return IsObject(schema) && 'nullable' in schema && schema.nullable === true
636
+ }
637
+
638
+ export function TNonNullable(
639
+ schema: unknown
640
+ ): schema is TNonNullable<Types.TSchema> {
641
+ return IsObject(schema) && !TNullable(schema)
642
+ }
643
+
644
+ export function TArray(schema: unknown): schema is TArray {
645
+ return (
646
+ IsObject(schema) &&
647
+ schema[Types.Kind] === 'TypeDef:Array' &&
648
+ TSchema(schema.elements)
649
+ )
650
+ }
651
+ export function TBoolean(schema: unknown): schema is TBoolean {
652
+ return (
653
+ IsObject(schema) &&
654
+ schema[Types.Kind] === 'TypeDef:Boolean' &&
655
+ schema.type === 'boolean'
656
+ )
657
+ }
658
+ export function TUnion(schema: unknown): schema is TUnion {
659
+ if (
660
+ !(
661
+ IsObject(schema) &&
662
+ schema[Types.Kind] === 'TypeDef:Union' &&
663
+ IsString(schema.discriminator) &&
664
+ IsObject(schema.mapping)
665
+ )
666
+ )
667
+ return false
668
+ return globalThis.Object.getOwnPropertyNames(schema.mapping).every((key) =>
669
+ TNonNullable(TStruct((schema.mapping as any)[key]))
670
+ )
671
+ }
672
+ export function TEnum(schema: unknown): schema is TEnum {
673
+ return (
674
+ IsObject(schema) &&
675
+ schema[Types.Kind] === 'TypeDef:Enum' &&
676
+ IsArray(schema.enum) &&
677
+ schema.enum.every((item) => IsString(item))
678
+ )
679
+ }
680
+ export function TFloat32(schema: unknown): schema is TFloat32 {
681
+ return (
682
+ IsObject(schema) &&
683
+ schema[Types.Kind] === 'TypeDef:Float32' &&
684
+ schema.type === 'float32'
685
+ )
686
+ }
687
+ export function TFloat64(schema: unknown): schema is TFloat64 {
688
+ return (
689
+ IsObject(schema) &&
690
+ schema[Types.Kind] === 'TypeDef:Float64' &&
691
+ schema.type === 'float64'
692
+ )
693
+ }
694
+ export function TInt8(schema: unknown): schema is TInt8 {
695
+ return (
696
+ IsObject(schema) &&
697
+ schema[Types.Kind] === 'TypeDef:Int8' &&
698
+ schema.type === 'int8'
699
+ )
700
+ }
701
+ export function TInt16(schema: unknown): schema is TInt16 {
702
+ return (
703
+ IsObject(schema) &&
704
+ schema[Types.Kind] === 'TypeDef:Int16' &&
705
+ schema.type === 'int16'
706
+ )
707
+ }
708
+ export function TInt32(schema: unknown): schema is TInt32 {
709
+ return (
710
+ IsObject(schema) &&
711
+ schema[Types.Kind] === 'TypeDef:Int32' &&
712
+ schema.type === 'int32'
713
+ )
714
+ }
715
+ export function TUint8(schema: unknown): schema is TUint8 {
716
+ return (
717
+ IsObject(schema) &&
718
+ schema[Types.Kind] === 'TypeDef:Uint8' &&
719
+ schema.type === 'uint8'
720
+ )
721
+ }
722
+ export function TUint16(schema: unknown): schema is TUint16 {
723
+ return (
724
+ IsObject(schema) &&
725
+ schema[Types.Kind] === 'TypeDef:Uint16' &&
726
+ schema.type === 'uint16'
727
+ )
728
+ }
729
+ export function TUint32(schema: unknown): schema is TUint32 {
730
+ return (
731
+ IsObject(schema) &&
732
+ schema[Types.Kind] === 'TypeDef:Uint32' &&
733
+ schema.type === 'uint32'
734
+ )
735
+ }
736
+ export function TRecord(schema: unknown): schema is TRecord {
737
+ return (
738
+ IsObject(schema) &&
739
+ schema[Types.Kind] === 'TypeDef:Record' &&
740
+ TSchema(schema.values)
741
+ )
742
+ }
743
+ export function TString(schema: unknown): schema is TString {
744
+ return (
745
+ IsObject(schema) &&
746
+ schema[Types.Kind] === 'TypeDef:String' &&
747
+ schema.type === 'string'
748
+ )
749
+ }
750
+ export function TAny(schema: unknown): schema is TAny {
751
+ return !(IsObject(schema) && schema[Types.Kind] === 'TypeDef:Any')
752
+ }
753
+
754
+ export function TStruct(schema: unknown): schema is TStruct {
755
+ if (
756
+ !(
757
+ IsObject(schema) &&
758
+ schema[Types.Kind] === 'TypeDef:Struct' &&
759
+ IsOptionalBoolean(schema.additionalProperties)
760
+ )
761
+ )
762
+ return false
763
+ const optionalProperties = schema.optionalProperties
764
+ const requiredProperties = schema.properties
765
+ const optionalCheck =
766
+ optionalProperties === undefined ||
767
+ (IsObject(optionalProperties) &&
768
+ globalThis.Object.getOwnPropertyNames(optionalProperties).every((key) =>
769
+ TSchema(optionalProperties[key])
770
+ ))
771
+ const requiredCheck =
772
+ requiredProperties === undefined ||
773
+ (IsObject(requiredProperties) &&
774
+ globalThis.Object.getOwnPropertyNames(requiredProperties).every((key) =>
775
+ TSchema(requiredProperties[key])
776
+ ))
777
+ return optionalCheck && requiredCheck
778
+ }
779
+ export function TTimestamp(schema: unknown): schema is TTimestamp {
780
+ return (
781
+ IsObject(schema) &&
782
+ schema[Types.Kind] === 'TypeDef:Timestamp' &&
783
+ schema.type === 'timestamp'
784
+ )
785
+ }
786
+
787
+ export function TRef(schema: unknown): schema is TRef {
788
+ return (
789
+ IsObject(schema) &&
790
+ schema[Types.Kind] === 'TypeDef:Ref' &&
791
+ typeof schema.ref === 'string'
792
+ )
793
+ }
794
+
795
+ export function TKind(schema: unknown): schema is Types.TKind {
796
+ return (
797
+ IsObject(schema) &&
798
+ Types.Kind in schema &&
799
+ typeof (schema as any)[Types.Kind] === 'string'
800
+ ) // TS 4.1.5: any required for symbol indexer
801
+ }
802
+ export function TSchema(schema: unknown): schema is Types.TSchema {
803
+ // prettier-ignore
804
+ return (
805
+ TArray(schema) ||
806
+ TBoolean(schema) ||
807
+ TUnion(schema) ||
808
+ TEnum(schema) ||
809
+ TFloat32(schema) ||
810
+ TFloat64(schema) ||
811
+ TInt8(schema) ||
812
+ TInt16(schema) ||
813
+ TInt32(schema) ||
814
+ TUint8(schema) ||
815
+ TUint16(schema) ||
816
+ TUint32(schema) ||
817
+ TRecord(schema) ||
818
+ TString(schema) ||
819
+ TStruct(schema) ||
820
+ TTimestamp(schema) ||
821
+ TRef(schema) ||
822
+ (TKind(schema) && Types.TypeRegistry.Has(schema[Types.Kind]))
823
+ )
824
+ }
825
+ }
826
+ // --------------------------------------------------------------------------
827
+ // TypeRegistry
828
+ // --------------------------------------------------------------------------
829
+ Types.TypeRegistry.Set<TArray>('TypeDef:Array', (schema, value) =>
830
+ ValueCheck.Check(schema, value)
831
+ )
832
+ Types.TypeRegistry.Set<TBoolean>('TypeDef:Boolean', (schema, value) =>
833
+ ValueCheck.Check(schema, value)
834
+ )
835
+ Types.TypeRegistry.Set<TUnion>('TypeDef:Union', (schema, value) =>
836
+ ValueCheck.Check(schema, value)
837
+ )
838
+ Types.TypeRegistry.Set<TInt8>('TypeDef:Int8', (schema, value) =>
839
+ ValueCheck.Check(schema, value)
840
+ )
841
+ Types.TypeRegistry.Set<TInt16>('TypeDef:Int16', (schema, value) =>
842
+ ValueCheck.Check(schema, value)
843
+ )
844
+ Types.TypeRegistry.Set<TInt32>('TypeDef:Int32', (schema, value) =>
845
+ ValueCheck.Check(schema, value)
846
+ )
847
+ Types.TypeRegistry.Set<TUint8>('TypeDef:Uint8', (schema, value) =>
848
+ ValueCheck.Check(schema, value)
849
+ )
850
+ Types.TypeRegistry.Set<TUint16>('TypeDef:Uint16', (schema, value) =>
851
+ ValueCheck.Check(schema, value)
852
+ )
853
+ Types.TypeRegistry.Set<TUint32>('TypeDef:Uint32', (schema, value) =>
854
+ ValueCheck.Check(schema, value)
855
+ )
856
+ Types.TypeRegistry.Set<TRecord>('TypeDef:Record', (schema, value) =>
857
+ ValueCheck.Check(schema, value)
858
+ )
859
+ Types.TypeRegistry.Set<TString>('TypeDef:String', (schema, value) =>
860
+ ValueCheck.Check(schema, value)
861
+ )
862
+ Types.TypeRegistry.Set<TStruct>('TypeDef:Struct', (schema, value) =>
863
+ ValueCheck.Check(schema, value)
864
+ )
865
+ Types.TypeRegistry.Set<TTimestamp>('TypeDef:Timestamp', (schema, value) =>
866
+ ValueCheck.Check(schema, value)
867
+ )
868
+ Types.TypeRegistry.Set<TRef>('TypeDef:Ref', (schema, value) =>
869
+ ValueCheck.Check(schema, value)
870
+ )
871
+ Types.TypeRegistry.Set<TAny>('TypeDef:Any', (schema, value) =>
872
+ ValueCheck.Check(schema, value)
873
+ )
874
+
875
+ // --------------------------------------------------------------------------
876
+ // TypeSystemErrorFunction
877
+ // --------------------------------------------------------------------------
878
+ SetErrorFunction((error) => {
879
+ switch (error.schema[Types.Kind]) {
880
+ case 'TypeDef:Array':
881
+ return 'Expected Array'
882
+ case 'TypeDef:Boolean':
883
+ return 'Expected Boolean'
884
+ case 'TypeDef:Union':
885
+ return 'Expected Union'
886
+ case 'TypeDef:Int8':
887
+ return 'Expected Int8'
888
+ case 'TypeDef:Int16':
889
+ return 'Expected Int16'
890
+ case 'TypeDef:Int32':
891
+ return 'Expected Int32'
892
+ case 'TypeDef:Uint8':
893
+ return 'Expected Uint8'
894
+ case 'TypeDef:Uint16':
895
+ return 'Expected Uint16'
896
+ case 'TypeDef:Uint32':
897
+ return 'Expected Uint32'
898
+ case 'TypeDef:Record':
899
+ return 'Expected Record'
900
+ case 'TypeDef:String':
901
+ return 'Expected String'
902
+ case 'TypeDef:Struct':
903
+ return 'Expected Struct'
904
+ case 'TypeDef:Timestamp':
905
+ return 'Expected Timestamp'
906
+ case 'TypeDef:Ref':
907
+ return 'Expected Ref'
908
+ case 'TypeDef:Any':
909
+ return 'Expected Any'
910
+ }
911
+ return DefaultErrorFunction(error)
912
+ })
913
+ // --------------------------------------------------------------------------
914
+ // TypeDefBuilder
915
+ // --------------------------------------------------------------------------
916
+ export class TypeDefBuilder {
917
+ // ------------------------------------------------------------------------
918
+ // Core
919
+ // ------------------------------------------------------------------------
920
+ protected Create(schema: Record<PropertyKey, any>, metadata: Metadata): any {
921
+ const keys = globalThis.Object.getOwnPropertyNames(metadata)
922
+ return keys.length > 0
923
+ ? { ...schema, metadata: { ...metadata } }
924
+ : { ...schema }
925
+ }
926
+ /** [Standard] Removes compositing symbols from this schema */
927
+ public Strict<T extends Types.TSchema>(schema: T): T {
928
+ return JSON.parse(JSON.stringify(schema)) as T
929
+ }
930
+ // ------------------------------------------------------------------------
931
+ // Modifiers
932
+ // ------------------------------------------------------------------------
933
+ /** `[Standard]` Creates an Optional property */
934
+ public Optional<T extends Types.TSchema>(schema: T): Types.TOptional<T> {
935
+ // return this.Optional(schema)
936
+ return JtdType.Optional(schema)
937
+ }
938
+ /** `[Standard]` Creates a Readonly property */
939
+ public Readonly<T extends Types.TSchema>(schema: T): Types.TReadonly<T> {
940
+ // return this.Readonly(schema)
941
+ return JtdType.Readonly(schema)
942
+ }
943
+ /** `[Standard]` Creates a Nullable schema */
944
+ public Nullable<T extends Types.TSchema = Types.TSchema>(schema: T) {
945
+ const newType = { ...Types.CloneType(schema), nullable: true }
946
+ return newType as TNullable<T>
947
+ }
948
+ // ------------------------------------------------------------------------
949
+ // Types
950
+ // ------------------------------------------------------------------------
951
+ public Any(metadata: Metadata = {}): SchemaFormEmpty {
952
+ return this.Create({ [Types.Kind]: 'TypeDef:Any' }, metadata)
953
+ }
954
+
955
+ /** [Standard] Creates a Array type */
956
+ public Array<T extends Types.TSchema>(
957
+ elements: T,
958
+ metadata: Metadata = {}
959
+ ): TArray<T> {
960
+ return this.Create({ [Types.Kind]: 'TypeDef:Array', elements }, metadata)
961
+ }
962
+ /** [Standard] Creates a Boolean type */
963
+ public Boolean(metadata: Metadata = {}): TBoolean {
964
+ return this.Create(
965
+ { [Types.Kind]: 'TypeDef:Boolean', type: 'boolean' },
966
+ metadata
967
+ )
968
+ }
969
+ /** [Standard] Creates a Enum type */
970
+ public Enum<T extends string[]>(
971
+ values: [...T],
972
+ metadata: EnumMetadata = {}
973
+ ): TEnum<T> {
974
+ return this.Create(
975
+ { [Types.Kind]: 'TypeDef:Enum', enum: Array.from(new Set(values)) },
976
+ metadata
977
+ )
978
+ }
979
+ /** [Standard] Creates a Float32 type */
980
+ public Float32(metadata: Metadata = {}): TFloat32 {
981
+ return this.Create(
982
+ { [Types.Kind]: 'TypeDef:Float32', type: 'float32' },
983
+ metadata
984
+ )
985
+ }
986
+ /** [Standard] Creates a Float64 type */
987
+ public Float64(metadata: Metadata = {}): TFloat64 {
988
+ return this.Create(
989
+ { [Types.Kind]: 'TypeDef:Float64', type: 'float64' },
990
+ metadata
991
+ )
992
+ }
993
+ /** [Standard] Creates a Int8 type */
994
+ public Int8(metadata: Metadata = {}): TInt8 {
995
+ return this.Create({ [Types.Kind]: 'TypeDef:Int8', type: 'int8' }, metadata)
996
+ }
997
+ /** [Standard] Creates a Int16 type */
998
+ public Int16(metadata: Metadata = {}): TInt16 {
999
+ return this.Create(
1000
+ { [Types.Kind]: 'TypeDef:Int16', type: 'int16' },
1001
+ metadata
1002
+ )
1003
+ }
1004
+ /** [Standard] Creates a Int32 type */
1005
+ public Int32(metadata: Metadata = {}): TInt32 {
1006
+ return this.Create(
1007
+ { [Types.Kind]: 'TypeDef:Int32', type: 'int32' },
1008
+ metadata
1009
+ )
1010
+ }
1011
+ /** [Standard] Creates a Uint8 type */
1012
+ public Uint8(metadata: Metadata = {}): TUint8 {
1013
+ return this.Create(
1014
+ { [Types.Kind]: 'TypeDef:Uint8', type: 'uint8' },
1015
+ metadata
1016
+ )
1017
+ }
1018
+ /** [Standard] Creates a Uint16 type */
1019
+ public Uint16(metadata: Metadata = {}): TUint16 {
1020
+ return this.Create(
1021
+ { [Types.Kind]: 'TypeDef:Uint16', type: 'uint16' },
1022
+ metadata
1023
+ )
1024
+ }
1025
+ /** [Standard] Creates a Uint32 type */
1026
+ public Uint32(metadata: Metadata = {}): TUint32 {
1027
+ return this.Create(
1028
+ { [Types.Kind]: 'TypeDef:Uint32', type: 'uint32' },
1029
+ metadata
1030
+ )
1031
+ }
1032
+ /** [Standard] Creates a Record type */
1033
+ public Record<T extends Types.TSchema>(
1034
+ values: T,
1035
+ metadata: Metadata = {}
1036
+ ): TRecord<T> {
1037
+ return this.Create({ [Types.Kind]: 'TypeDef:Record', values }, metadata)
1038
+ }
1039
+ /** [Standard] Creates a String type */
1040
+ public String(metadata: Metadata = {}): TString {
1041
+ return this.Create(
1042
+ { [Types.Kind]: 'TypeDef:String', type: 'string' },
1043
+ metadata
1044
+ )
1045
+ }
1046
+ /** [Standard] Creates a Struct type */
1047
+ public Struct<T extends TFields>(
1048
+ fields: T,
1049
+ metadata: StructMetadata = {}
1050
+ ): TStruct<T> {
1051
+ const optionalProperties: TFields = {} as TFields
1052
+ const properties: TFields = {} as TFields
1053
+ for (const key of globalThis.Object.getOwnPropertyNames(fields)) {
1054
+ if (Types.TypeGuard.IsOptional(fields[key])) {
1055
+ optionalProperties[key] = fields[key]
1056
+ } else {
1057
+ properties[key] = fields[key]
1058
+ }
1059
+ }
1060
+ const optionalObject =
1061
+ globalThis.Object.getOwnPropertyNames(optionalProperties).length > 0
1062
+ ? { optionalProperties: optionalProperties }
1063
+ : {}
1064
+ const requiredObject =
1065
+ globalThis.Object.getOwnPropertyNames(properties).length === 0
1066
+ ? {}
1067
+ : { properties: properties }
1068
+ return this.Create(
1069
+ {
1070
+ [Types.Kind]: 'TypeDef:Struct',
1071
+ ...requiredObject,
1072
+ ...optionalObject
1073
+ },
1074
+ metadata
1075
+ )
1076
+ }
1077
+ /** [Standard] Creates a Union type */
1078
+ // public Union<T extends TStruct<TFields>[], D extends string = 'type'>(
1079
+ // structs: [...T],
1080
+ // discriminator?: D
1081
+ // ): TUnion<T, D> {
1082
+ // discriminator = (discriminator || 'type') as D
1083
+ // if (structs.length === 0)
1084
+ // throw new Error(
1085
+ // 'TypeDefBuilder: Union types must contain at least one struct'
1086
+ // )
1087
+ // const mapping = structs.reduce(
1088
+ // (acc, current, index) => ({ ...acc, [index.toString()]: current }),
1089
+ // {}
1090
+ // )
1091
+ // return this.Create(
1092
+ // { [Types.Kind]: 'TypeDef:Union', discriminator, mapping },
1093
+ // {}
1094
+ // )
1095
+ // }
1096
+ public Union<T extends Record<string, TStruct<TFields>>, D extends string>(
1097
+ mapping: T,
1098
+ discriminator: D
1099
+ ): TUnion<T[keyof T][], D> {
1100
+ if (Object.values(mapping).length === 0)
1101
+ throw new Error(
1102
+ 'TypeDefBuilder: Union types must contain at least one struct'
1103
+ )
1104
+
1105
+ const result = this.Create(
1106
+ { [Types.Kind]: 'TypeDef:Union', discriminator, mapping },
1107
+ {}
1108
+ )
1109
+
1110
+ // console.log(JSON.stringify(result, undefined, '\t'))
1111
+
1112
+ return result
1113
+ }
1114
+ /** [Standard] Creates a Timestamp type */
1115
+ public Timestamp(metadata: Metadata = {}): TTimestamp {
1116
+ return this.Create(
1117
+ { [Types.Kind]: 'TypeDef:Timestamp', type: 'timestamp' },
1118
+ metadata
1119
+ )
1120
+ }
1121
+
1122
+ public Ref<T extends Types.TSchema = Types.TSchema>(
1123
+ ref: string,
1124
+ metadata: Metadata = {}
1125
+ ): TRef<T> {
1126
+ return this.Create(
1127
+ {
1128
+ [Types.Kind]: 'TypeDef:Ref',
1129
+ ref
1130
+ },
1131
+ metadata
1132
+ )
1133
+ }
1134
+ }
1135
+
1136
+ /** JSON Type Definition Type Builder */
1137
+ const JtdType = new TypeDefBuilder()
1138
+ export default JtdType