@opensaas/stack-core 0.18.1 → 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.
@@ -0,0 +1,194 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { select } from './index.js';
3
+ describe('select field builder', () => {
4
+ describe('string type (default)', () => {
5
+ it('should throw when no options are provided', () => {
6
+ expect(() => select({ options: [] })).toThrow('Select field must have at least one option');
7
+ });
8
+ it('should return String prisma type for default select field', () => {
9
+ const field = select({
10
+ options: [
11
+ { label: 'Draft', value: 'draft' },
12
+ { label: 'Published', value: 'published' },
13
+ ],
14
+ });
15
+ const result = field.getPrismaType('status', 'sqlite', 'Post');
16
+ expect(result.type).toBe('String');
17
+ expect(result.enumValues).toBeUndefined();
18
+ });
19
+ it('should add ? modifier for optional string select', () => {
20
+ const field = select({
21
+ options: [{ label: 'Draft', value: 'draft' }],
22
+ });
23
+ const result = field.getPrismaType('status', 'sqlite', 'Post');
24
+ expect(result.modifiers).toBe('?');
25
+ });
26
+ it('should not add ? modifier for required string select', () => {
27
+ const field = select({
28
+ options: [{ label: 'Draft', value: 'draft' }],
29
+ validation: { isRequired: true },
30
+ });
31
+ const result = field.getPrismaType('status', 'sqlite', 'Post');
32
+ expect(result.modifiers).toBeUndefined();
33
+ });
34
+ it('should generate quoted default value for string select', () => {
35
+ const field = select({
36
+ options: [
37
+ { label: 'Draft', value: 'draft' },
38
+ { label: 'Published', value: 'published' },
39
+ ],
40
+ defaultValue: 'draft',
41
+ });
42
+ const result = field.getPrismaType('status', 'sqlite', 'Post');
43
+ expect(result.modifiers).toBe(' @default("draft")');
44
+ });
45
+ it('should generate union TypeScript type from options', () => {
46
+ const field = select({
47
+ options: [
48
+ { label: 'Draft', value: 'draft' },
49
+ { label: 'Published', value: 'published' },
50
+ ],
51
+ });
52
+ const result = field.getTypeScriptType();
53
+ expect(result.type).toBe("'draft' | 'published'");
54
+ expect(result.optional).toBe(true);
55
+ });
56
+ it('should mark TypeScript type as non-optional when required', () => {
57
+ const field = select({
58
+ options: [
59
+ { label: 'Draft', value: 'draft' },
60
+ { label: 'Published', value: 'published' },
61
+ ],
62
+ validation: { isRequired: true },
63
+ });
64
+ const result = field.getTypeScriptType();
65
+ expect(result.optional).toBe(false);
66
+ });
67
+ });
68
+ describe('enum type (db.type: enum)', () => {
69
+ it('should throw for values that are not valid Prisma identifiers (hyphens)', () => {
70
+ expect(() => select({
71
+ options: [{ label: 'In Progress', value: 'in-progress' }],
72
+ db: { type: 'enum' },
73
+ })).toThrow(/valid Prisma identifiers/);
74
+ });
75
+ it('should throw for values starting with a digit', () => {
76
+ expect(() => select({
77
+ options: [{ label: 'First', value: '1st' }],
78
+ db: { type: 'enum' },
79
+ })).toThrow(/valid Prisma identifiers/);
80
+ });
81
+ it('should throw for values with spaces', () => {
82
+ expect(() => select({
83
+ options: [{ label: 'In Progress', value: 'in progress' }],
84
+ db: { type: 'enum' },
85
+ })).toThrow(/valid Prisma identifiers/);
86
+ });
87
+ it('should accept values with underscores', () => {
88
+ expect(() => select({
89
+ options: [
90
+ { label: 'In Progress', value: 'in_progress' },
91
+ { label: 'Done', value: 'done' },
92
+ ],
93
+ db: { type: 'enum' },
94
+ })).not.toThrow();
95
+ });
96
+ it('should return derived enum name from listName + fieldName', () => {
97
+ const field = select({
98
+ options: [
99
+ { label: 'Draft', value: 'draft' },
100
+ { label: 'Published', value: 'published' },
101
+ ],
102
+ db: { type: 'enum' },
103
+ });
104
+ const result = field.getPrismaType('status', 'sqlite', 'Post');
105
+ expect(result.type).toBe('PostStatus');
106
+ });
107
+ it('should capitalize fieldName when deriving enum name', () => {
108
+ const field = select({
109
+ options: [
110
+ { label: 'Article', value: 'article' },
111
+ { label: 'Video', value: 'video' },
112
+ ],
113
+ db: { type: 'enum' },
114
+ });
115
+ const result = field.getPrismaType('contentType', 'sqlite', 'Post');
116
+ expect(result.type).toBe('PostContentType');
117
+ });
118
+ it('should fall back to capitalized fieldName when listName is not provided', () => {
119
+ const field = select({
120
+ options: [{ label: 'Draft', value: 'draft' }],
121
+ db: { type: 'enum' },
122
+ });
123
+ const result = field.getPrismaType('status');
124
+ expect(result.type).toBe('Status');
125
+ });
126
+ it('should return enumValues in getPrismaType result', () => {
127
+ const field = select({
128
+ options: [
129
+ { label: 'Draft', value: 'draft' },
130
+ { label: 'Published', value: 'published' },
131
+ ],
132
+ db: { type: 'enum' },
133
+ });
134
+ const result = field.getPrismaType('status', 'sqlite', 'Post');
135
+ expect(result.enumValues).toEqual(['draft', 'published']);
136
+ });
137
+ it('should add ? modifier for optional enum field', () => {
138
+ const field = select({
139
+ options: [{ label: 'Draft', value: 'draft' }],
140
+ db: { type: 'enum' },
141
+ });
142
+ const result = field.getPrismaType('status', 'sqlite', 'Post');
143
+ expect(result.modifiers).toBe('?');
144
+ });
145
+ it('should not add ? modifier for required enum field', () => {
146
+ const field = select({
147
+ options: [{ label: 'Draft', value: 'draft' }],
148
+ db: { type: 'enum' },
149
+ validation: { isRequired: true },
150
+ });
151
+ const result = field.getPrismaType('status', 'sqlite', 'Post');
152
+ expect(result.modifiers).toBeUndefined();
153
+ });
154
+ it('should generate unquoted default value for enum field', () => {
155
+ const field = select({
156
+ options: [
157
+ { label: 'Draft', value: 'draft' },
158
+ { label: 'Published', value: 'published' },
159
+ ],
160
+ db: { type: 'enum' },
161
+ defaultValue: 'draft',
162
+ });
163
+ const result = field.getPrismaType('status', 'sqlite', 'Post');
164
+ expect(result.modifiers).toBe(' @default(draft)');
165
+ // Explicitly check there are no quotes
166
+ expect(result.modifiers).not.toContain('"');
167
+ });
168
+ it('should include @map modifier for enum field with map option', () => {
169
+ const field = select({
170
+ options: [{ label: 'Draft', value: 'draft' }],
171
+ db: { type: 'enum', map: 'post_status' },
172
+ });
173
+ const result = field.getPrismaType('status', 'sqlite', 'Post');
174
+ expect(result.modifiers).toContain('@map("post_status")');
175
+ });
176
+ it('should generate same union TypeScript type as string select', () => {
177
+ const enumField = select({
178
+ options: [
179
+ { label: 'Draft', value: 'draft' },
180
+ { label: 'Published', value: 'published' },
181
+ ],
182
+ db: { type: 'enum' },
183
+ });
184
+ const stringField = select({
185
+ options: [
186
+ { label: 'Draft', value: 'draft' },
187
+ { label: 'Published', value: 'published' },
188
+ ],
189
+ });
190
+ expect(enumField.getTypeScriptType()).toEqual(stringField.getTypeScriptType());
191
+ });
192
+ });
193
+ });
194
+ //# sourceMappingURL=select.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select.test.js","sourceRoot":"","sources":["../../src/fields/select.test.ts"],"names":[],"mappings":"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"}
@@ -39,6 +39,20 @@ describe('Zod Schema Generation', () => {
39
39
  const schema = generateZodSchema(fields, 'create');
40
40
  expect(schema).toBeDefined();
41
41
  });
42
+ it('should generate schema for enum select field', () => {
43
+ const fields = {
44
+ status: select({
45
+ options: [
46
+ { label: 'Draft', value: 'draft' },
47
+ { label: 'Published', value: 'published' },
48
+ ],
49
+ db: { type: 'enum' },
50
+ validation: { isRequired: true },
51
+ }),
52
+ };
53
+ const schema = generateZodSchema(fields, 'create');
54
+ expect(schema).toBeDefined();
55
+ });
42
56
  it('should make fields optional in update mode', () => {
43
57
  const fields = {
44
58
  name: text({ validation: { isRequired: true } }),
@@ -123,6 +137,37 @@ describe('Zod Schema Generation', () => {
123
137
  expect(result.errors.status).toBeDefined();
124
138
  }
125
139
  });
140
+ it('should pass validation for valid enum select value', () => {
141
+ const fields = {
142
+ status: select({
143
+ options: [
144
+ { label: 'Draft', value: 'draft' },
145
+ { label: 'Published', value: 'published' },
146
+ ],
147
+ db: { type: 'enum' },
148
+ validation: { isRequired: true },
149
+ }),
150
+ };
151
+ const result = validateWithZod({ status: 'draft' }, fields, 'create');
152
+ expect(result.success).toBe(true);
153
+ });
154
+ it('should fail validation for invalid enum select value', () => {
155
+ const fields = {
156
+ status: select({
157
+ options: [
158
+ { label: 'Draft', value: 'draft' },
159
+ { label: 'Published', value: 'published' },
160
+ ],
161
+ db: { type: 'enum' },
162
+ validation: { isRequired: true },
163
+ }),
164
+ };
165
+ const result = validateWithZod({ status: 'archived' }, fields, 'create');
166
+ expect(result.success).toBe(false);
167
+ if (!result.success) {
168
+ expect(result.errors.status).toBeDefined();
169
+ }
170
+ });
126
171
  it('should skip system fields in validation', () => {
127
172
  const fields = {
128
173
  id: text(),
@@ -1 +1 @@
1
- {"version":3,"file":"schema.test.js","sourceRoot":"","sources":["../../src/validation/schema.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schema.test.js","sourceRoot":"","sources":["../../src/validation/schema.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opensaas/stack-core",
3
- "version": "0.18.1",
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.10.1",
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"
@@ -204,6 +204,14 @@ export async function buildIncludeWithAccessControl(
204
204
  return undefined
205
205
  }
206
206
 
207
+ // Skip auto-including relationships when inside a resolveOutput hook
208
+ // This prevents infinite loops when hooks make DB queries that include
209
+ // relationships back to the same entity (e.g., User virtual field queries Posts
210
+ // which includes author back to User, triggering the virtual field again)
211
+ if (args.context._resolveOutputCounter.depth > 0) {
212
+ return undefined
213
+ }
214
+
207
215
  type IncludeEntry = boolean | { where?: PrismaFilter; include?: Record<string, IncludeEntry> }
208
216
 
209
217
  const include: Record<string, IncludeEntry> = {}
@@ -347,17 +355,24 @@ export async function filterReadableFields<T extends Record<string, unknown>>(
347
355
  // Cast to runtime type for generic execution
348
356
  // At runtime, the hook will receive the correct value type for the field
349
357
  const hook = fieldConfig.hooks.resolveOutput as unknown as ResolveOutputHookRuntime
350
- // Use Promise.resolve() to handle both sync and async hooks
351
- filtered[fieldName] = await Promise.resolve(
352
- hook({
353
- value,
354
- operation: 'query',
355
- fieldName,
356
- listKey,
357
- item,
358
- context: args.context,
359
- }),
360
- )
358
+ // Increment depth counter to prevent infinite loops from hooks making DB queries
359
+ // that include relationships back to the same entity
360
+ args.context._resolveOutputCounter.depth++
361
+ try {
362
+ // Use Promise.resolve() to handle both sync and async hooks
363
+ filtered[fieldName] = await Promise.resolve(
364
+ hook({
365
+ value,
366
+ operation: 'query',
367
+ fieldName,
368
+ listKey,
369
+ item,
370
+ context: args.context,
371
+ }),
372
+ )
373
+ } finally {
374
+ args.context._resolveOutputCounter.depth--
375
+ }
361
376
  } else {
362
377
  filtered[fieldName] = value
363
378
  }
@@ -390,17 +405,24 @@ export async function filterReadableFields<T extends Record<string, unknown>>(
390
405
  // Virtual fields must have resolveOutput hook to compute their value
391
406
  if (fieldConfig.hooks?.resolveOutput && listKey) {
392
407
  const hook = fieldConfig.hooks.resolveOutput as unknown as ResolveOutputHookRuntime
393
- // Use Promise.resolve() to handle both sync and async hooks
394
- filtered[fieldName] = await Promise.resolve(
395
- hook({
396
- value: undefined, // Virtual fields don't have a database value
397
- operation: 'query',
398
- fieldName,
399
- listKey,
400
- item: filtered, // Pass filtered item so virtual field can access other fields
401
- context: args.context,
402
- }),
403
- )
408
+ // Increment depth counter to prevent infinite loops from hooks making DB queries
409
+ // that include relationships back to the same entity
410
+ args.context._resolveOutputCounter.depth++
411
+ try {
412
+ // Use Promise.resolve() to handle both sync and async hooks
413
+ filtered[fieldName] = await Promise.resolve(
414
+ hook({
415
+ value: undefined, // Virtual fields don't have a database value
416
+ operation: 'query',
417
+ fieldName,
418
+ listKey,
419
+ item: filtered, // Pass filtered item so virtual field can access other fields
420
+ context: args.context,
421
+ }),
422
+ )
423
+ } finally {
424
+ args.context._resolveOutputCounter.depth--
425
+ }
404
426
  }
405
427
  }
406
428
 
@@ -163,6 +163,13 @@ export interface AccessContext<TPrisma extends PrismaClientLike = PrismaClientLi
163
163
  storage: StorageUtils
164
164
  plugins: Record<string, unknown>
165
165
  _isSudo: boolean
166
+ /**
167
+ * Internal mutable counter to track resolveOutput hook depth.
168
+ * When depth > 0, we skip auto-including relationships to prevent infinite loops
169
+ * when hooks make database queries that include relationships back to the original entity.
170
+ * We use a mutable object so that spreading the context preserves the reference.
171
+ */
172
+ _resolveOutputCounter: { depth: number }
166
173
  }
167
174
 
168
175
  /**
@@ -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
- * @returns Prisma type string and optional modifiers
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
  }
@@ -38,6 +38,16 @@ async function executeFieldResolveInputHooks(
38
38
  item?: any,
39
39
  ): Promise<Record<string, unknown>> {
40
40
  let result = { ...resolvedData }
41
+ console.log(
42
+ 'Executing field resolveInput hooks for list:',
43
+ listKey,
44
+ 'operation:',
45
+ operation,
46
+ 'inputData:',
47
+ inputData,
48
+ 'resolvedData before field hooks:',
49
+ resolvedData,
50
+ )
41
51
 
42
52
  for (const [fieldKey, fieldConfig] of Object.entries(fields)) {
43
53
  // Skip if field not in data
@@ -430,6 +440,7 @@ export function getContext<
430
440
  },
431
441
  plugins: {}, // Will be populated with plugin runtime services
432
442
  _isSudo,
443
+ _resolveOutputCounter: { depth: 0 },
433
444
  }
434
445
 
435
446
  // Create access-controlled operations for each list
@@ -882,8 +893,10 @@ function createCreate<TPrisma extends PrismaClientLike>(
882
893
  // Access Prisma model dynamically - required because model names are generated at runtime
883
894
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
884
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
885
898
  const item = await model.create({
886
- data,
899
+ data: createData,
887
900
  })
888
901
 
889
902
  // 9. Execute list-level afterOperation hook