@opensaas/stack-core 0.18.2 → 0.19.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +145 -0
- package/dist/config/types.d.ts +69 -41
- package/dist/config/types.d.ts.map +1 -1
- package/dist/context/index.js +3 -1
- package/dist/context/index.js.map +1 -1
- package/dist/fields/index.d.ts.map +1 -1
- package/dist/fields/index.js +105 -30
- package/dist/fields/index.js.map +1 -1
- package/dist/fields/select.test.d.ts +2 -0
- package/dist/fields/select.test.d.ts.map +1 -0
- package/dist/fields/select.test.js +194 -0
- package/dist/fields/select.test.js.map +1 -0
- package/dist/validation/schema.test.js +45 -0
- package/dist/validation/schema.test.js.map +1 -1
- package/package.json +2 -2
- package/src/config/types.ts +69 -40
- package/src/context/index.ts +3 -1
- package/src/fields/index.ts +125 -31
- package/src/fields/select.test.ts +237 -0
- package/src/validation/schema.test.ts +51 -0
- package/tests/field-types.test.ts +187 -3
- package/tests/singleton.test.ts +14 -13
- package/tsconfig.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opensaas/stack-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Core stack for OpenSaas - schema definition, access control, and runtime utilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@prisma/client": "^7.1.0",
|
|
52
|
-
"@types/node": "^24.
|
|
52
|
+
"@types/node": "^24.11.0",
|
|
53
53
|
"@vitest/coverage-v8": "^4.0.15",
|
|
54
54
|
"typescript": "^5.9.3",
|
|
55
55
|
"vitest": "^4.0.15"
|
package/src/config/types.ts
CHANGED
|
@@ -374,6 +374,52 @@ export type BaseFieldConfig<TTypeInfo extends TypeInfo> = {
|
|
|
374
374
|
* ```
|
|
375
375
|
*/
|
|
376
376
|
map?: string
|
|
377
|
+
/**
|
|
378
|
+
* Controls DB-level nullability independently of validation.isRequired.
|
|
379
|
+
* When specified, overrides the default behavior where nullability is inferred
|
|
380
|
+
* from validation.isRequired (required = non-nullable, optional = nullable).
|
|
381
|
+
*
|
|
382
|
+
* This allows you to:
|
|
383
|
+
* - Make a field non-nullable at the DB level without making it API-required
|
|
384
|
+
* - Explicitly mark a field as nullable even when it has isRequired validation
|
|
385
|
+
*
|
|
386
|
+
* @example
|
|
387
|
+
* ```typescript
|
|
388
|
+
* // DB non-nullable, but API optional (relies on a default value or hook)
|
|
389
|
+
* fields: {
|
|
390
|
+
* phoneNumber: text({
|
|
391
|
+
* db: { isNullable: false }
|
|
392
|
+
* })
|
|
393
|
+
* // Generates: phoneNumber String (non-nullable)
|
|
394
|
+
*
|
|
395
|
+
* // DB nullable (explicit), regardless of validation
|
|
396
|
+
* lastMessagePreview: text({
|
|
397
|
+
* db: { isNullable: true }
|
|
398
|
+
* })
|
|
399
|
+
* // Generates: lastMessagePreview String? (nullable)
|
|
400
|
+
* }
|
|
401
|
+
* ```
|
|
402
|
+
*/
|
|
403
|
+
isNullable?: boolean
|
|
404
|
+
/**
|
|
405
|
+
* Override the native database type for the column.
|
|
406
|
+
* Generates a @db.<nativeType> attribute in the Prisma schema.
|
|
407
|
+
* The available types depend on your database provider.
|
|
408
|
+
*
|
|
409
|
+
* @example
|
|
410
|
+
* ```typescript
|
|
411
|
+
* // PostgreSQL: use TEXT instead of VARCHAR
|
|
412
|
+
* fields: {
|
|
413
|
+
* description: text({ db: { nativeType: 'Text' } })
|
|
414
|
+
* // Generates: description String? @db.Text
|
|
415
|
+
*
|
|
416
|
+
* // PostgreSQL: use SMALLINT instead of INT
|
|
417
|
+
* count: integer({ db: { nativeType: 'SmallInt' } })
|
|
418
|
+
* // Generates: count Int? @db.SmallInt
|
|
419
|
+
* }
|
|
420
|
+
* ```
|
|
421
|
+
*/
|
|
422
|
+
nativeType?: string
|
|
377
423
|
}
|
|
378
424
|
ui?: {
|
|
379
425
|
/**
|
|
@@ -415,14 +461,21 @@ export type BaseFieldConfig<TTypeInfo extends TypeInfo> = {
|
|
|
415
461
|
* Get Prisma type and modifiers for schema generation
|
|
416
462
|
* @param fieldName - The name of the field (for generating modifiers)
|
|
417
463
|
* @param provider - Optional database provider ('sqlite', 'postgresql', 'mysql', etc.)
|
|
418
|
-
* @
|
|
464
|
+
* @param listName - Optional list name (used for generating enum type names)
|
|
465
|
+
* @returns Prisma type string, optional modifiers, and optional enum values
|
|
419
466
|
*/
|
|
420
467
|
getPrismaType?: (
|
|
421
468
|
fieldName: string,
|
|
422
469
|
provider?: string,
|
|
470
|
+
listName?: string,
|
|
423
471
|
) => {
|
|
424
472
|
type: string
|
|
425
473
|
modifiers?: string
|
|
474
|
+
/**
|
|
475
|
+
* If set, this field requires a Prisma enum definition with these values.
|
|
476
|
+
* The enum name is the value of `type`.
|
|
477
|
+
*/
|
|
478
|
+
enumValues?: string[]
|
|
426
479
|
}
|
|
427
480
|
/**
|
|
428
481
|
* Get TypeScript type information for type generation
|
|
@@ -465,37 +518,6 @@ export type TextField<TTypeInfo extends TypeInfo = TypeInfo> = BaseFieldConfig<T
|
|
|
465
518
|
}
|
|
466
519
|
}
|
|
467
520
|
isIndexed?: boolean | 'unique'
|
|
468
|
-
db?: {
|
|
469
|
-
map?: string
|
|
470
|
-
/**
|
|
471
|
-
* Prisma native database type attribute
|
|
472
|
-
* Allows overriding the default String type for the database provider
|
|
473
|
-
* @example
|
|
474
|
-
* ```typescript
|
|
475
|
-
* // PostgreSQL/MySQL
|
|
476
|
-
* fields: {
|
|
477
|
-
* description: text({ db: { nativeType: 'Text' } })
|
|
478
|
-
* // Generates: description String @db.Text
|
|
479
|
-
* }
|
|
480
|
-
* ```
|
|
481
|
-
*/
|
|
482
|
-
nativeType?: string
|
|
483
|
-
/**
|
|
484
|
-
* Controls nullability in the database schema
|
|
485
|
-
* When specified, overrides the default behavior (isRequired determines nullability)
|
|
486
|
-
* @example
|
|
487
|
-
* ```typescript
|
|
488
|
-
* fields: {
|
|
489
|
-
* description: text({
|
|
490
|
-
* validation: { isRequired: true },
|
|
491
|
-
* db: { isNullable: false }
|
|
492
|
-
* })
|
|
493
|
-
* // Generates: description String (non-nullable)
|
|
494
|
-
* }
|
|
495
|
-
* ```
|
|
496
|
-
*/
|
|
497
|
-
isNullable?: boolean
|
|
498
|
-
}
|
|
499
521
|
ui?: {
|
|
500
522
|
displayMode?: 'input' | 'textarea'
|
|
501
523
|
}
|
|
@@ -515,10 +537,6 @@ export type DecimalField<TTypeInfo extends TypeInfo = TypeInfo> = BaseFieldConfi
|
|
|
515
537
|
defaultValue?: string
|
|
516
538
|
precision?: number
|
|
517
539
|
scale?: number
|
|
518
|
-
db?: {
|
|
519
|
-
map?: string
|
|
520
|
-
isNullable?: boolean
|
|
521
|
-
}
|
|
522
540
|
validation?: {
|
|
523
541
|
isRequired?: boolean
|
|
524
542
|
min?: string
|
|
@@ -539,10 +557,6 @@ export type TimestampField<TTypeInfo extends TypeInfo = TypeInfo> = BaseFieldCon
|
|
|
539
557
|
export type CalendarDayField<TTypeInfo extends TypeInfo = TypeInfo> = BaseFieldConfig<TTypeInfo> & {
|
|
540
558
|
type: 'calendarDay'
|
|
541
559
|
defaultValue?: string
|
|
542
|
-
db?: {
|
|
543
|
-
map?: string
|
|
544
|
-
isNullable?: boolean
|
|
545
|
-
}
|
|
546
560
|
validation?: {
|
|
547
561
|
isRequired?: boolean
|
|
548
562
|
}
|
|
@@ -559,6 +573,21 @@ export type PasswordField<TTypeInfo extends TypeInfo = TypeInfo> = BaseFieldConf
|
|
|
559
573
|
export type SelectField<TTypeInfo extends TypeInfo = TypeInfo> = BaseFieldConfig<TTypeInfo> & {
|
|
560
574
|
type: 'select'
|
|
561
575
|
options: Array<{ label: string; value: string }>
|
|
576
|
+
defaultValue?: string
|
|
577
|
+
db?: {
|
|
578
|
+
/**
|
|
579
|
+
* Whether to store as a native database enum type.
|
|
580
|
+
* - 'string' (default): stores as a plain string/varchar column
|
|
581
|
+
* - 'enum': stores as a Prisma enum, generating a native enum type in the schema
|
|
582
|
+
*
|
|
583
|
+
* Note: enum values must be valid Prisma identifiers (letters, numbers, underscores,
|
|
584
|
+
* starting with a letter) when using 'enum' type.
|
|
585
|
+
*
|
|
586
|
+
* @default 'string'
|
|
587
|
+
*/
|
|
588
|
+
type?: 'string' | 'enum'
|
|
589
|
+
map?: string
|
|
590
|
+
}
|
|
562
591
|
validation?: {
|
|
563
592
|
isRequired?: boolean
|
|
564
593
|
}
|
package/src/context/index.ts
CHANGED
|
@@ -893,8 +893,10 @@ function createCreate<TPrisma extends PrismaClientLike>(
|
|
|
893
893
|
// Access Prisma model dynamically - required because model names are generated at runtime
|
|
894
894
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
895
895
|
const model = (prisma as any)[getDbKey(listName)]
|
|
896
|
+
// Singleton lists use Int @id with value always 1 (matching Keystone 6 behaviour)
|
|
897
|
+
const createData = isSingletonList(listConfig) ? { id: 1, ...data } : data
|
|
896
898
|
const item = await model.create({
|
|
897
|
-
data,
|
|
899
|
+
data: createData,
|
|
898
900
|
})
|
|
899
901
|
|
|
900
902
|
// 9. Execute list-level afterOperation hook
|
package/src/fields/index.ts
CHANGED
|
@@ -97,7 +97,7 @@ export function text<
|
|
|
97
97
|
|
|
98
98
|
return {
|
|
99
99
|
type: 'String',
|
|
100
|
-
modifiers: modifiers || undefined,
|
|
100
|
+
modifiers: modifiers.trimStart() || undefined,
|
|
101
101
|
}
|
|
102
102
|
},
|
|
103
103
|
getTypeScriptType: () => {
|
|
@@ -145,22 +145,30 @@ export function integer<
|
|
|
145
145
|
: withMax
|
|
146
146
|
},
|
|
147
147
|
getPrismaType: (_fieldName: string) => {
|
|
148
|
-
const
|
|
148
|
+
const validation = options?.validation
|
|
149
|
+
const db = options?.db
|
|
150
|
+
const isRequired = validation?.isRequired
|
|
151
|
+
const isNullable = db?.isNullable ?? !isRequired
|
|
149
152
|
let modifiers = ''
|
|
150
153
|
|
|
151
154
|
// Optional modifier
|
|
152
|
-
if (
|
|
155
|
+
if (isNullable) {
|
|
153
156
|
modifiers += '?'
|
|
154
157
|
}
|
|
155
158
|
|
|
159
|
+
// Native type modifier (e.g., @db.SmallInt, @db.BigInt)
|
|
160
|
+
if (db?.nativeType) {
|
|
161
|
+
modifiers += ` @db.${db.nativeType}`
|
|
162
|
+
}
|
|
163
|
+
|
|
156
164
|
// Map modifier
|
|
157
|
-
if (
|
|
158
|
-
modifiers += ` @map("${
|
|
165
|
+
if (db?.map) {
|
|
166
|
+
modifiers += ` @map("${db.map}")`
|
|
159
167
|
}
|
|
160
168
|
|
|
161
169
|
return {
|
|
162
170
|
type: 'Int',
|
|
163
|
-
modifiers: modifiers || undefined,
|
|
171
|
+
modifiers: modifiers.trimStart() || undefined,
|
|
164
172
|
}
|
|
165
173
|
},
|
|
166
174
|
getTypeScriptType: () => {
|
|
@@ -353,21 +361,28 @@ export function checkbox<
|
|
|
353
361
|
return z.boolean().optional().nullable()
|
|
354
362
|
},
|
|
355
363
|
getPrismaType: (_fieldName: string) => {
|
|
364
|
+
const db = options?.db
|
|
356
365
|
const hasDefault = options?.defaultValue !== undefined
|
|
357
366
|
let modifiers = ''
|
|
358
367
|
|
|
368
|
+
// Nullable modifier - checkbox fields are non-nullable by default (must be true or false)
|
|
369
|
+
// Use db.isNullable: true to allow NULL values in the database
|
|
370
|
+
if (db?.isNullable === true) {
|
|
371
|
+
modifiers += '?'
|
|
372
|
+
}
|
|
373
|
+
|
|
359
374
|
if (hasDefault) {
|
|
360
|
-
modifiers
|
|
375
|
+
modifiers += ` @default(${options.defaultValue})`
|
|
361
376
|
}
|
|
362
377
|
|
|
363
378
|
// Map modifier
|
|
364
|
-
if (
|
|
365
|
-
modifiers += ` @map("${
|
|
379
|
+
if (db?.map) {
|
|
380
|
+
modifiers += ` @map("${db.map}")`
|
|
366
381
|
}
|
|
367
382
|
|
|
368
383
|
return {
|
|
369
384
|
type: 'Boolean',
|
|
370
|
-
modifiers: modifiers || undefined,
|
|
385
|
+
modifiers: modifiers.trimStart() || undefined,
|
|
371
386
|
}
|
|
372
387
|
},
|
|
373
388
|
getTypeScriptType: () => {
|
|
@@ -392,26 +407,41 @@ export function timestamp<
|
|
|
392
407
|
return z.union([z.date(), z.iso.datetime()]).optional().nullable()
|
|
393
408
|
},
|
|
394
409
|
getPrismaType: (_fieldName: string) => {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
// Check for default value
|
|
398
|
-
if (
|
|
410
|
+
const db = options?.db
|
|
411
|
+
const hasDefaultNow =
|
|
399
412
|
options?.defaultValue &&
|
|
400
413
|
typeof options.defaultValue === 'object' &&
|
|
401
414
|
'kind' in options.defaultValue &&
|
|
402
415
|
options.defaultValue.kind === 'now'
|
|
403
|
-
|
|
404
|
-
|
|
416
|
+
|
|
417
|
+
// Nullability: explicit db.isNullable overrides the default (nullable unless @default(now()))
|
|
418
|
+
const isNullable = db?.isNullable ?? !hasDefaultNow
|
|
419
|
+
|
|
420
|
+
let modifiers = ''
|
|
421
|
+
|
|
422
|
+
// Optional modifier
|
|
423
|
+
if (isNullable) {
|
|
424
|
+
modifiers += '?'
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Default value
|
|
428
|
+
if (hasDefaultNow) {
|
|
429
|
+
modifiers += ' @default(now())'
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Native type modifier (e.g., @db.Timestamptz for PostgreSQL)
|
|
433
|
+
if (db?.nativeType) {
|
|
434
|
+
modifiers += ` @db.${db.nativeType}`
|
|
405
435
|
}
|
|
406
436
|
|
|
407
437
|
// Map modifier
|
|
408
|
-
if (
|
|
409
|
-
modifiers += ` @map("${
|
|
438
|
+
if (db?.map) {
|
|
439
|
+
modifiers += ` @map("${db.map}")`
|
|
410
440
|
}
|
|
411
441
|
|
|
412
442
|
return {
|
|
413
443
|
type: 'DateTime',
|
|
414
|
-
modifiers,
|
|
444
|
+
modifiers: modifiers.trimStart() || undefined,
|
|
415
445
|
}
|
|
416
446
|
},
|
|
417
447
|
getTypeScriptType: () => {
|
|
@@ -689,22 +719,30 @@ export function password<TTypeInfo extends import('../config/types.js').TypeInfo
|
|
|
689
719
|
}
|
|
690
720
|
},
|
|
691
721
|
getPrismaType: (_fieldName: string) => {
|
|
692
|
-
const
|
|
722
|
+
const validation = options?.validation
|
|
723
|
+
const db = options?.db
|
|
724
|
+
const isRequired = validation?.isRequired
|
|
725
|
+
const isNullable = db?.isNullable ?? !isRequired
|
|
693
726
|
let modifiers = ''
|
|
694
727
|
|
|
695
728
|
// Optional modifier
|
|
696
|
-
if (
|
|
729
|
+
if (isNullable) {
|
|
697
730
|
modifiers += '?'
|
|
698
731
|
}
|
|
699
732
|
|
|
733
|
+
// Native type modifier (e.g., @db.Text)
|
|
734
|
+
if (db?.nativeType) {
|
|
735
|
+
modifiers += ` @db.${db.nativeType}`
|
|
736
|
+
}
|
|
737
|
+
|
|
700
738
|
// Map modifier
|
|
701
|
-
if (
|
|
702
|
-
modifiers += ` @map("${
|
|
739
|
+
if (db?.map) {
|
|
740
|
+
modifiers += ` @map("${db.map}")`
|
|
703
741
|
}
|
|
704
742
|
|
|
705
743
|
return {
|
|
706
744
|
type: 'String',
|
|
707
|
-
modifiers: modifiers || undefined,
|
|
745
|
+
modifiers: modifiers.trimStart() || undefined,
|
|
708
746
|
}
|
|
709
747
|
},
|
|
710
748
|
getTypeScriptType: () => {
|
|
@@ -718,6 +756,11 @@ export function password<TTypeInfo extends import('../config/types.js').TypeInfo
|
|
|
718
756
|
}
|
|
719
757
|
}
|
|
720
758
|
|
|
759
|
+
/**
|
|
760
|
+
* Valid Prisma enum value pattern: starts with a letter, followed by letters, digits, or underscores
|
|
761
|
+
*/
|
|
762
|
+
const PRISMA_ENUM_VALUE_PATTERN = /^[A-Za-z][A-Za-z0-9_]*$/
|
|
763
|
+
|
|
721
764
|
/**
|
|
722
765
|
* Select field (enum-like)
|
|
723
766
|
*/
|
|
@@ -728,6 +771,20 @@ export function select<
|
|
|
728
771
|
throw new Error('Select field must have at least one option')
|
|
729
772
|
}
|
|
730
773
|
|
|
774
|
+
const isNativeEnum = options.db?.type === 'enum'
|
|
775
|
+
|
|
776
|
+
if (isNativeEnum) {
|
|
777
|
+
const invalidValues = options.options
|
|
778
|
+
.map((opt) => opt.value)
|
|
779
|
+
.filter((value) => !PRISMA_ENUM_VALUE_PATTERN.test(value))
|
|
780
|
+
|
|
781
|
+
if (invalidValues.length > 0) {
|
|
782
|
+
throw new Error(
|
|
783
|
+
`Enum select field values must be valid Prisma identifiers (letters, numbers, and underscores, starting with a letter). Invalid values: ${invalidValues.join(', ')}`,
|
|
784
|
+
)
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
731
788
|
return {
|
|
732
789
|
type: 'select',
|
|
733
790
|
...options,
|
|
@@ -743,10 +800,39 @@ export function select<
|
|
|
743
800
|
|
|
744
801
|
return schema
|
|
745
802
|
},
|
|
746
|
-
getPrismaType: (
|
|
803
|
+
getPrismaType: (fieldName: string, _provider?: string, listName?: string) => {
|
|
747
804
|
const isRequired = options.validation?.isRequired
|
|
748
805
|
let modifiers = ''
|
|
749
806
|
|
|
807
|
+
if (isNativeEnum) {
|
|
808
|
+
// Derive enum name from list name + field name in PascalCase
|
|
809
|
+
const capitalizedField = fieldName.charAt(0).toUpperCase() + fieldName.slice(1)
|
|
810
|
+
const enumName = listName ? `${listName}${capitalizedField}` : capitalizedField
|
|
811
|
+
|
|
812
|
+
// Required fields don't get the ? modifier
|
|
813
|
+
if (!isRequired) {
|
|
814
|
+
modifiers = '?'
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// Add default value if provided (no quotes for enum values)
|
|
818
|
+
if (options.defaultValue !== undefined) {
|
|
819
|
+
modifiers = ` @default(${options.defaultValue})`
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// Map modifier
|
|
823
|
+
if (options.db?.map) {
|
|
824
|
+
modifiers += ` @map("${options.db.map}")`
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
return {
|
|
828
|
+
type: enumName,
|
|
829
|
+
modifiers: modifiers || undefined,
|
|
830
|
+
enumValues: options.options.map((opt) => opt.value),
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// String type (default)
|
|
835
|
+
|
|
750
836
|
// Required fields don't get the ? modifier
|
|
751
837
|
if (!isRequired) {
|
|
752
838
|
modifiers = '?'
|
|
@@ -768,7 +854,7 @@ export function select<
|
|
|
768
854
|
}
|
|
769
855
|
},
|
|
770
856
|
getTypeScriptType: () => {
|
|
771
|
-
// Generate union type from options
|
|
857
|
+
// Generate union type from options (same for both string and enum db types)
|
|
772
858
|
const unionType = options.options.map((opt) => `'${opt.value}'`).join(' | ')
|
|
773
859
|
|
|
774
860
|
return {
|
|
@@ -890,22 +976,30 @@ export function json<
|
|
|
890
976
|
}
|
|
891
977
|
},
|
|
892
978
|
getPrismaType: (_fieldName: string) => {
|
|
893
|
-
const
|
|
979
|
+
const validation = options?.validation
|
|
980
|
+
const db = options?.db
|
|
981
|
+
const isRequired = validation?.isRequired
|
|
982
|
+
const isNullable = db?.isNullable ?? !isRequired
|
|
894
983
|
let modifiers = ''
|
|
895
984
|
|
|
896
985
|
// Optional modifier
|
|
897
|
-
if (
|
|
986
|
+
if (isNullable) {
|
|
898
987
|
modifiers += '?'
|
|
899
988
|
}
|
|
900
989
|
|
|
990
|
+
// Native type modifier
|
|
991
|
+
if (db?.nativeType) {
|
|
992
|
+
modifiers += ` @db.${db.nativeType}`
|
|
993
|
+
}
|
|
994
|
+
|
|
901
995
|
// Map modifier
|
|
902
|
-
if (
|
|
903
|
-
modifiers += ` @map("${
|
|
996
|
+
if (db?.map) {
|
|
997
|
+
modifiers += ` @map("${db.map}")`
|
|
904
998
|
}
|
|
905
999
|
|
|
906
1000
|
return {
|
|
907
1001
|
type: 'Json',
|
|
908
|
-
modifiers: modifiers || undefined,
|
|
1002
|
+
modifiers: modifiers.trimStart() || undefined,
|
|
909
1003
|
}
|
|
910
1004
|
},
|
|
911
1005
|
getTypeScriptType: () => {
|