@opensaas/stack-core 0.11.0 → 0.12.1

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.
@@ -5,6 +5,7 @@ import type {
5
5
  DecimalField,
6
6
  CheckboxField,
7
7
  TimestampField,
8
+ CalendarDayField,
8
9
  PasswordField,
9
10
  SelectField,
10
11
  RelationshipField,
@@ -67,14 +68,21 @@ export function text<
67
68
  },
68
69
  getPrismaType: (_fieldName: string) => {
69
70
  const validation = options?.validation
71
+ const db = options?.db
70
72
  const isRequired = validation?.isRequired
73
+ const isNullable = db?.isNullable ?? !isRequired
71
74
  let modifiers = ''
72
75
 
73
76
  // Optional modifier
74
- if (!isRequired) {
77
+ if (isNullable) {
75
78
  modifiers += '?'
76
79
  }
77
80
 
81
+ // Native type modifier (e.g., @db.Text)
82
+ if (db?.nativeType) {
83
+ modifiers += ` @db.${db.nativeType}`
84
+ }
85
+
78
86
  // Unique/index modifiers
79
87
  if (options?.isIndexed === 'unique') {
80
88
  modifiers += ' @unique'
@@ -83,8 +91,8 @@ export function text<
83
91
  }
84
92
 
85
93
  // Map modifier
86
- if (options?.db?.map) {
87
- modifiers += ` @map("${options.db.map}")`
94
+ if (db?.map) {
95
+ modifiers += ` @map("${db.map}")`
88
96
  }
89
97
 
90
98
  return {
@@ -421,6 +429,131 @@ export function timestamp<
421
429
  }
422
430
  }
423
431
 
432
+ /**
433
+ * Calendar Day field - date only (no time) in ISO8601 format
434
+ *
435
+ * **Features:**
436
+ * - Stores date values only (no time component)
437
+ * - PostgreSQL/MySQL: Uses native DATE type via @db.Date
438
+ * - SQLite: Uses String representation
439
+ * - Accepts ISO8601 date strings (YYYY-MM-DD format)
440
+ * - Optional validation for required fields
441
+ * - Database column mapping and nullability control
442
+ * - Index support (boolean or 'unique')
443
+ *
444
+ * **Usage Example:**
445
+ * ```typescript
446
+ * // In opensaas.config.ts
447
+ * fields: {
448
+ * birthDate: calendarDay({
449
+ * validation: { isRequired: true }
450
+ * }),
451
+ * startDate: calendarDay({
452
+ * defaultValue: '2025-01-01',
453
+ * db: { map: 'start_date' }
454
+ * }),
455
+ * endDate: calendarDay({
456
+ * isIndexed: true
457
+ * })
458
+ * }
459
+ *
460
+ * // Creating with date values
461
+ * const event = await context.db.event.create({
462
+ * data: {
463
+ * startDate: '2025-01-15',
464
+ * endDate: '2025-01-20'
465
+ * }
466
+ * })
467
+ * ```
468
+ *
469
+ * @param options - Field configuration options
470
+ * @returns Calendar Day field configuration
471
+ */
472
+ export function calendarDay<
473
+ TTypeInfo extends import('../config/types.js').TypeInfo = import('../config/types.js').TypeInfo,
474
+ >(options?: Omit<CalendarDayField<TTypeInfo>, 'type'>): CalendarDayField<TTypeInfo> {
475
+ return {
476
+ type: 'calendarDay',
477
+ ...options,
478
+ getZodSchema: (fieldName: string, operation: 'create' | 'update') => {
479
+ const validation = options?.validation
480
+ const isRequired = validation?.isRequired
481
+
482
+ // Accept ISO8601 date strings (YYYY-MM-DD)
483
+ const baseSchema = z.string({
484
+ message: `${formatFieldName(fieldName)} must be a valid date in ISO8601 format (YYYY-MM-DD)`,
485
+ })
486
+
487
+ // Validate ISO8601 date format (YYYY-MM-DD)
488
+ const dateSchema = baseSchema.regex(/^\d{4}-\d{2}-\d{2}$/, {
489
+ message: `${formatFieldName(fieldName)} must be in YYYY-MM-DD format`,
490
+ })
491
+
492
+ if (isRequired && operation === 'create') {
493
+ return dateSchema
494
+ } else if (isRequired && operation === 'update') {
495
+ // Required in update mode: can be undefined for partial updates
496
+ return z.union([dateSchema, z.undefined()])
497
+ } else {
498
+ return dateSchema.optional().nullable()
499
+ }
500
+ },
501
+ getPrismaType: (_fieldName: string, provider?: string) => {
502
+ const validation = options?.validation
503
+ const db = options?.db
504
+ const isRequired = validation?.isRequired
505
+ const isNullable = db?.isNullable ?? !isRequired
506
+
507
+ let modifiers = ''
508
+
509
+ // Optional modifier
510
+ if (isNullable) {
511
+ modifiers += '?'
512
+ }
513
+
514
+ // Add @db.Date attribute for date-only storage
515
+ // Only for PostgreSQL/MySQL - SQLite doesn't support native DATE type
516
+ // SQLite will use TEXT for DateTime fields
517
+ if (provider && provider.toLowerCase() !== 'sqlite') {
518
+ modifiers += ' @db.Date'
519
+ }
520
+
521
+ // Default value if provided
522
+ if (options?.defaultValue !== undefined) {
523
+ modifiers += ` @default("${options.defaultValue}")`
524
+ }
525
+
526
+ // Database mapping
527
+ if (db?.map) {
528
+ modifiers += ` @map("${db.map}")`
529
+ }
530
+
531
+ // Unique/index modifiers
532
+ if (options?.isIndexed === 'unique') {
533
+ modifiers += ' @unique'
534
+ } else if (options?.isIndexed === true) {
535
+ modifiers += ' @index'
536
+ }
537
+
538
+ return {
539
+ type: 'DateTime',
540
+ modifiers: modifiers.trimStart() || undefined,
541
+ }
542
+ },
543
+ getTypeScriptType: () => {
544
+ const validation = options?.validation
545
+ const db = options?.db
546
+ const isRequired = validation?.isRequired
547
+ const isNullable = db?.isNullable ?? !isRequired
548
+
549
+ return {
550
+ type: 'Date',
551
+ optional: isNullable,
552
+ }
553
+ },
554
+ }
555
+ }
556
+
424
557
  /**
425
558
  * Password field (automatically hashed using bcrypt)
426
559
  *