@opensaas/stack-cli 0.3.0 → 0.5.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.
Files changed (105) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +193 -0
  3. package/dist/commands/generate.d.ts.map +1 -1
  4. package/dist/commands/generate.js +4 -13
  5. package/dist/commands/generate.js.map +1 -1
  6. package/dist/commands/migrate.d.ts +9 -0
  7. package/dist/commands/migrate.d.ts.map +1 -0
  8. package/dist/commands/migrate.js +473 -0
  9. package/dist/commands/migrate.js.map +1 -0
  10. package/dist/generator/context.d.ts.map +1 -1
  11. package/dist/generator/context.js +20 -5
  12. package/dist/generator/context.js.map +1 -1
  13. package/dist/generator/index.d.ts +1 -1
  14. package/dist/generator/index.d.ts.map +1 -1
  15. package/dist/generator/index.js +1 -1
  16. package/dist/generator/index.js.map +1 -1
  17. package/dist/generator/lists.d.ts.map +1 -1
  18. package/dist/generator/lists.js +33 -1
  19. package/dist/generator/lists.js.map +1 -1
  20. package/dist/generator/prisma-extensions.d.ts +11 -0
  21. package/dist/generator/prisma-extensions.d.ts.map +1 -0
  22. package/dist/generator/prisma-extensions.js +134 -0
  23. package/dist/generator/prisma-extensions.js.map +1 -0
  24. package/dist/generator/prisma.d.ts.map +1 -1
  25. package/dist/generator/prisma.js +4 -0
  26. package/dist/generator/prisma.js.map +1 -1
  27. package/dist/generator/types.d.ts.map +1 -1
  28. package/dist/generator/types.js +151 -17
  29. package/dist/generator/types.js.map +1 -1
  30. package/dist/index.js +3 -0
  31. package/dist/index.js.map +1 -1
  32. package/dist/mcp/lib/documentation-provider.d.ts +23 -0
  33. package/dist/mcp/lib/documentation-provider.d.ts.map +1 -1
  34. package/dist/mcp/lib/documentation-provider.js +471 -0
  35. package/dist/mcp/lib/documentation-provider.js.map +1 -1
  36. package/dist/mcp/lib/wizards/migration-wizard.d.ts +80 -0
  37. package/dist/mcp/lib/wizards/migration-wizard.d.ts.map +1 -0
  38. package/dist/mcp/lib/wizards/migration-wizard.js +499 -0
  39. package/dist/mcp/lib/wizards/migration-wizard.js.map +1 -0
  40. package/dist/mcp/server/index.d.ts.map +1 -1
  41. package/dist/mcp/server/index.js +103 -0
  42. package/dist/mcp/server/index.js.map +1 -1
  43. package/dist/mcp/server/stack-mcp-server.d.ts +85 -0
  44. package/dist/mcp/server/stack-mcp-server.d.ts.map +1 -1
  45. package/dist/mcp/server/stack-mcp-server.js +219 -0
  46. package/dist/mcp/server/stack-mcp-server.js.map +1 -1
  47. package/dist/migration/generators/migration-generator.d.ts +60 -0
  48. package/dist/migration/generators/migration-generator.d.ts.map +1 -0
  49. package/dist/migration/generators/migration-generator.js +510 -0
  50. package/dist/migration/generators/migration-generator.js.map +1 -0
  51. package/dist/migration/introspectors/index.d.ts +12 -0
  52. package/dist/migration/introspectors/index.d.ts.map +1 -0
  53. package/dist/migration/introspectors/index.js +10 -0
  54. package/dist/migration/introspectors/index.js.map +1 -0
  55. package/dist/migration/introspectors/keystone-introspector.d.ts +59 -0
  56. package/dist/migration/introspectors/keystone-introspector.d.ts.map +1 -0
  57. package/dist/migration/introspectors/keystone-introspector.js +229 -0
  58. package/dist/migration/introspectors/keystone-introspector.js.map +1 -0
  59. package/dist/migration/introspectors/nextjs-introspector.d.ts +59 -0
  60. package/dist/migration/introspectors/nextjs-introspector.d.ts.map +1 -0
  61. package/dist/migration/introspectors/nextjs-introspector.js +159 -0
  62. package/dist/migration/introspectors/nextjs-introspector.js.map +1 -0
  63. package/dist/migration/introspectors/prisma-introspector.d.ts +45 -0
  64. package/dist/migration/introspectors/prisma-introspector.d.ts.map +1 -0
  65. package/dist/migration/introspectors/prisma-introspector.js +190 -0
  66. package/dist/migration/introspectors/prisma-introspector.js.map +1 -0
  67. package/dist/migration/types.d.ts +86 -0
  68. package/dist/migration/types.d.ts.map +1 -0
  69. package/dist/migration/types.js +5 -0
  70. package/dist/migration/types.js.map +1 -0
  71. package/package.json +12 -9
  72. package/src/commands/__snapshots__/generate.test.ts.snap +92 -21
  73. package/src/commands/generate.ts +8 -19
  74. package/src/commands/migrate.ts +534 -0
  75. package/src/generator/__snapshots__/context.test.ts.snap +60 -15
  76. package/src/generator/__snapshots__/types.test.ts.snap +689 -95
  77. package/src/generator/context.test.ts +3 -1
  78. package/src/generator/context.ts +20 -5
  79. package/src/generator/index.ts +1 -1
  80. package/src/generator/lists.ts +39 -1
  81. package/src/generator/prisma-extensions.ts +159 -0
  82. package/src/generator/prisma.ts +5 -0
  83. package/src/generator/types.ts +204 -17
  84. package/src/index.ts +4 -0
  85. package/src/mcp/lib/documentation-provider.ts +507 -0
  86. package/src/mcp/lib/wizards/migration-wizard.ts +584 -0
  87. package/src/mcp/server/index.ts +121 -0
  88. package/src/mcp/server/stack-mcp-server.ts +243 -0
  89. package/src/migration/generators/migration-generator.ts +675 -0
  90. package/src/migration/introspectors/index.ts +12 -0
  91. package/src/migration/introspectors/keystone-introspector.ts +296 -0
  92. package/src/migration/introspectors/nextjs-introspector.ts +209 -0
  93. package/src/migration/introspectors/prisma-introspector.ts +233 -0
  94. package/src/migration/types.ts +92 -0
  95. package/tests/introspectors/keystone-introspector.test.ts +255 -0
  96. package/tests/introspectors/nextjs-introspector.test.ts +302 -0
  97. package/tests/introspectors/prisma-introspector.test.ts +268 -0
  98. package/tests/migration-generator.test.ts +592 -0
  99. package/tests/migration-wizard.test.ts +442 -0
  100. package/tsconfig.tsbuildinfo +1 -1
  101. package/dist/generator/type-patcher.d.ts +0 -13
  102. package/dist/generator/type-patcher.d.ts.map +0 -1
  103. package/dist/generator/type-patcher.js +0 -68
  104. package/dist/generator/type-patcher.js.map +0 -1
  105. package/src/generator/type-patcher.ts +0 -93
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Prisma Schema Introspector
3
+ *
4
+ * Parses prisma/schema.prisma and extracts structured information
5
+ * about models, fields, relationships, and enums.
6
+ */
7
+ import fs from 'fs-extra';
8
+ import path from 'path';
9
+ export class PrismaIntrospector {
10
+ /**
11
+ * Introspect a Prisma schema file
12
+ */
13
+ async introspect(cwd, schemaPath = 'prisma/schema.prisma') {
14
+ const fullPath = path.isAbsolute(schemaPath) ? schemaPath : path.join(cwd, schemaPath);
15
+ if (!(await fs.pathExists(fullPath))) {
16
+ throw new Error(`Schema file not found: ${fullPath}`);
17
+ }
18
+ const schema = await fs.readFile(fullPath, 'utf-8');
19
+ return {
20
+ provider: this.extractProvider(schema),
21
+ models: this.extractModels(schema),
22
+ enums: this.extractEnums(schema),
23
+ };
24
+ }
25
+ /**
26
+ * Extract database provider from datasource block
27
+ */
28
+ extractProvider(schema) {
29
+ const match = schema.match(/datasource\s+\w+\s*\{[^}]*provider\s*=\s*"(\w+)"/);
30
+ return match ? match[1] : 'unknown';
31
+ }
32
+ /**
33
+ * Extract all model definitions
34
+ */
35
+ extractModels(schema) {
36
+ const models = [];
37
+ // Match model blocks
38
+ const modelRegex = /model\s+(\w+)\s*\{([^}]+)\}/g;
39
+ let match;
40
+ while ((match = modelRegex.exec(schema)) !== null) {
41
+ const name = match[1];
42
+ const body = match[2];
43
+ const fields = this.extractFields(body);
44
+ const primaryKey = fields.find((f) => f.isId)?.name || 'id';
45
+ models.push({
46
+ name,
47
+ fields,
48
+ hasRelations: fields.some((f) => f.relation !== undefined),
49
+ primaryKey,
50
+ });
51
+ }
52
+ return models;
53
+ }
54
+ /**
55
+ * Extract fields from a model body
56
+ */
57
+ extractFields(body) {
58
+ const fields = [];
59
+ const lines = body.split('\n');
60
+ for (const line of lines) {
61
+ const trimmed = line.trim();
62
+ // Skip empty lines, comments, and model-level attributes
63
+ if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('@@')) {
64
+ continue;
65
+ }
66
+ const field = this.parseFieldLine(trimmed);
67
+ if (field) {
68
+ fields.push(field);
69
+ }
70
+ }
71
+ return fields;
72
+ }
73
+ /**
74
+ * Parse a single field line
75
+ */
76
+ parseFieldLine(line) {
77
+ // Basic field pattern: name Type modifiers attributes
78
+ // Examples:
79
+ // id String @id @default(cuid())
80
+ // title String
81
+ // isActive Boolean? @default(true)
82
+ // posts Post[]
83
+ // author User @relation(fields: [authorId], references: [id])
84
+ // Remove comments
85
+ const withoutComment = line.split('//')[0].trim();
86
+ // Match field name and type
87
+ const fieldMatch = withoutComment.match(/^(\w+)\s+(\w+)(\?)?(\[\])?(.*)$/);
88
+ if (!fieldMatch)
89
+ return null;
90
+ const [, name, rawType, optional, isList, rest] = fieldMatch;
91
+ // Skip if this looks like an index or other non-field line
92
+ if (['@@', 'index', 'unique'].some((kw) => name.startsWith(kw))) {
93
+ return null;
94
+ }
95
+ const field = {
96
+ name,
97
+ type: rawType,
98
+ isRequired: !optional,
99
+ isUnique: rest.includes('@unique'),
100
+ isId: rest.includes('@id'),
101
+ isList: !!isList,
102
+ };
103
+ // Extract default value (handle nested parentheses)
104
+ const defaultMatch = rest.match(/@default\(/);
105
+ if (defaultMatch) {
106
+ const startIdx = rest.indexOf('@default(') + '@default('.length;
107
+ let depth = 1;
108
+ let endIdx = startIdx;
109
+ while (depth > 0 && endIdx < rest.length) {
110
+ if (rest[endIdx] === '(')
111
+ depth++;
112
+ else if (rest[endIdx] === ')')
113
+ depth--;
114
+ if (depth > 0)
115
+ endIdx++;
116
+ }
117
+ field.defaultValue = rest.substring(startIdx, endIdx);
118
+ }
119
+ // Extract relation
120
+ const relationMatch = rest.match(/@relation\(([^)]+)\)/);
121
+ if (relationMatch) {
122
+ const relationBody = relationMatch[1];
123
+ // Parse relation parts
124
+ const fieldsMatch = relationBody.match(/fields:\s*\[([^\]]+)\]/);
125
+ const referencesMatch = relationBody.match(/references:\s*\[([^\]]+)\]/);
126
+ const nameMatch = relationBody.match(/name:\s*"([^"]+)"/) || relationBody.match(/"([^"]+)"/);
127
+ field.relation = {
128
+ name: nameMatch ? nameMatch[1] : '',
129
+ model: rawType,
130
+ fields: fieldsMatch ? fieldsMatch[1].split(',').map((f) => f.trim()) : [],
131
+ references: referencesMatch ? referencesMatch[1].split(',').map((r) => r.trim()) : [],
132
+ };
133
+ }
134
+ return field;
135
+ }
136
+ /**
137
+ * Extract enum definitions
138
+ */
139
+ extractEnums(schema) {
140
+ const enums = [];
141
+ // Match enum blocks
142
+ const enumRegex = /enum\s+(\w+)\s*\{([^}]+)\}/g;
143
+ let match;
144
+ while ((match = enumRegex.exec(schema)) !== null) {
145
+ const name = match[1];
146
+ const body = match[2];
147
+ const values = body
148
+ .split('\n')
149
+ .map((line) => line.trim())
150
+ .filter((line) => line && !line.startsWith('//'));
151
+ enums.push({ name, values });
152
+ }
153
+ return enums;
154
+ }
155
+ /**
156
+ * Map Prisma type to OpenSaaS field type
157
+ */
158
+ mapPrismaTypeToOpenSaas(prismaType) {
159
+ const mappings = {
160
+ String: { type: 'text', import: 'text' },
161
+ Int: { type: 'integer', import: 'integer' },
162
+ Float: { type: 'float', import: 'float' },
163
+ Boolean: { type: 'checkbox', import: 'checkbox' },
164
+ DateTime: { type: 'timestamp', import: 'timestamp' },
165
+ Json: { type: 'json', import: 'json' },
166
+ BigInt: { type: 'text', import: 'text' }, // No native support
167
+ Decimal: { type: 'text', import: 'text' }, // No native support
168
+ Bytes: { type: 'text', import: 'text' }, // No native support
169
+ };
170
+ return mappings[prismaType] || { type: 'text', import: 'text' };
171
+ }
172
+ /**
173
+ * Get warnings for unsupported features
174
+ */
175
+ getWarnings(schema) {
176
+ const warnings = [];
177
+ // Check for unsupported types
178
+ for (const model of schema.models) {
179
+ for (const field of model.fields) {
180
+ if (['BigInt', 'Decimal', 'Bytes'].includes(field.type)) {
181
+ warnings.push(`Field "${model.name}.${field.name}" uses unsupported type "${field.type}" - will be mapped to text()`);
182
+ }
183
+ }
184
+ }
185
+ // Check for composite IDs
186
+ // This would require checking for @@id in the original schema
187
+ return warnings;
188
+ }
189
+ }
190
+ //# sourceMappingURL=prisma-introspector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prisma-introspector.js","sourceRoot":"","sources":["../../../src/migration/introspectors/prisma-introspector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Migration types - Shared types for the migration system
3
+ */
4
+ export type ProjectType = 'prisma' | 'nextjs' | 'keystone';
5
+ export interface ModelInfo {
6
+ name: string;
7
+ fieldCount: number;
8
+ }
9
+ export interface ProjectAnalysis {
10
+ projectTypes: ProjectType[];
11
+ cwd: string;
12
+ models?: ModelInfo[];
13
+ provider?: string;
14
+ hasAuth?: boolean;
15
+ authLibrary?: string;
16
+ }
17
+ export interface FieldMapping {
18
+ prismaType: string;
19
+ opensaasType: string;
20
+ opensaasImport: string;
21
+ }
22
+ export interface MigrationQuestion {
23
+ id: string;
24
+ text: string;
25
+ type: 'text' | 'select' | 'boolean' | 'multiselect';
26
+ options?: string[];
27
+ defaultValue?: string | boolean | string[];
28
+ required?: boolean;
29
+ dependsOn?: {
30
+ questionId: string;
31
+ value: string | boolean;
32
+ };
33
+ }
34
+ export interface MigrationSession {
35
+ id: string;
36
+ projectType: ProjectType;
37
+ analysis: ProjectAnalysis;
38
+ currentQuestionIndex: number;
39
+ answers: Record<string, string | boolean | string[]>;
40
+ generatedConfig?: string;
41
+ isComplete: boolean;
42
+ createdAt: Date;
43
+ updatedAt: Date;
44
+ }
45
+ export interface MigrationOutput {
46
+ configContent: string;
47
+ dependencies: string[];
48
+ files: Array<{
49
+ path: string;
50
+ content: string;
51
+ language: string;
52
+ description: string;
53
+ }>;
54
+ steps: string[];
55
+ warnings: string[];
56
+ }
57
+ export interface IntrospectedModel {
58
+ name: string;
59
+ fields: IntrospectedField[];
60
+ hasRelations: boolean;
61
+ primaryKey: string;
62
+ }
63
+ export interface IntrospectedField {
64
+ name: string;
65
+ type: string;
66
+ isRequired: boolean;
67
+ isUnique: boolean;
68
+ isId: boolean;
69
+ isList: boolean;
70
+ defaultValue?: string;
71
+ relation?: {
72
+ name: string;
73
+ model: string;
74
+ fields: string[];
75
+ references: string[];
76
+ };
77
+ }
78
+ export interface IntrospectedSchema {
79
+ provider: string;
80
+ models: IntrospectedModel[];
81
+ enums: Array<{
82
+ name: string;
83
+ values: string[];
84
+ }>;
85
+ }
86
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/migration/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Migration types - Shared types for the migration system
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/migration/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opensaas/stack-cli",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "CLI tools for OpenSaas Stack",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -36,22 +36,25 @@
36
36
  "url": "https://github.com/OpenSaasAU/stack/issues"
37
37
  },
38
38
  "dependencies": {
39
- "@modelcontextprotocol/sdk": "^1.0.4",
39
+ "@modelcontextprotocol/sdk": "^1.24.3",
40
40
  "chalk": "^5.6.2",
41
- "chokidar": "^4.0.3",
42
- "commander": "^14.0.1",
41
+ "chokidar": "^5.0.0",
42
+ "commander": "^14.0.2",
43
+ "fs-extra": "^11.3.2",
44
+ "glob": "^13.0.0",
43
45
  "jiti": "^2.6.1",
44
46
  "ora": "^9.0.0",
45
47
  "prompts": "^2.4.2",
46
- "zod": "^4.1.12",
47
- "@opensaas/stack-core": "0.3.0"
48
+ "zod": "^4.1.13",
49
+ "@opensaas/stack-core": "0.5.0"
48
50
  },
49
51
  "devDependencies": {
50
- "@types/node": "^24.7.2",
52
+ "@types/fs-extra": "^11.0.4",
53
+ "@types/node": "^24.10.1",
51
54
  "@types/prompts": "^2.4.9",
52
- "@vitest/coverage-v8": "^4.0.4",
55
+ "@vitest/coverage-v8": "^4.0.15",
53
56
  "typescript": "^5.9.3",
54
- "vitest": "^4.0.0"
57
+ "vitest": "^4.0.15"
55
58
  },
56
59
  "scripts": {
57
60
  "build": "tsc",
@@ -14,6 +14,7 @@ import { getContext as getOpensaasContext } from '@opensaas/stack-core'
14
14
  import type { Session as OpensaasSession, OpenSaasConfig } from '@opensaas/stack-core'
15
15
  import { PrismaClient } from './prisma-client/client'
16
16
  import type { Context } from './types'
17
+ import { prismaExtensions } from './prisma-extensions'
17
18
  import configOrPromise from '../opensaas.config'
18
19
 
19
20
  // Resolve config if it's a Promise (when plugins are present)
@@ -21,15 +22,29 @@ const configPromise = Promise.resolve(configOrPromise)
21
22
  let resolvedConfig: OpenSaasConfig | null = null
22
23
 
23
24
  // Internal Prisma singleton - managed automatically
24
- const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | null }
25
- let prisma: PrismaClient | null = null
25
+ const globalForPrisma = globalThis as unknown as { prisma: ReturnType<typeof createExtendedPrisma> | null }
26
+ let prisma: ReturnType<typeof createExtendedPrisma> | null = null
27
+
28
+ /**
29
+ * Create Prisma client with result extensions
30
+ */
31
+ function createExtendedPrisma(basePrisma: PrismaClient) {
32
+ // Check if there are any extensions to apply
33
+ if (Object.keys(prismaExtensions).length === 0) {
34
+ return basePrisma
35
+ }
36
+ // Apply result extensions
37
+ return basePrisma.$extends(prismaExtensions)
38
+ }
26
39
 
27
40
  async function getPrisma() {
28
41
  if (!prisma) {
29
42
  if (!resolvedConfig) {
30
43
  resolvedConfig = await configPromise
31
44
  }
32
- prisma = globalForPrisma.prisma ?? resolvedConfig.db.prismaClientConstructor!(PrismaClient)
45
+ const basePrisma = resolvedConfig.db.prismaClientConstructor!(PrismaClient)
46
+ const extendedPrisma = createExtendedPrisma(basePrisma)
47
+ prisma = globalForPrisma.prisma ?? extendedPrisma
33
48
  if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
34
49
  }
35
50
  return prisma
@@ -84,7 +99,7 @@ const storage = {
84
99
  export async function getContext<TSession extends OpensaasSession = OpensaasSession>(session?: TSession): Promise<Context<TSession>> {
85
100
  const config = await getConfig()
86
101
  const prismaClient = await getPrisma()
87
- return getOpensaasContext(config, prismaClient, session ?? null, storage) as Context<TSession>
102
+ return getOpensaasContext(config, prismaClient, session ?? null, storage) as unknown as Context<TSession>
88
103
  }
89
104
 
90
105
  /**
@@ -94,7 +109,7 @@ export async function getContext<TSession extends OpensaasSession = OpensaasSess
94
109
  export const rawOpensaasContext = (async () => {
95
110
  const config = await getConfig()
96
111
  const prismaClient = await getPrisma()
97
- return getOpensaasContext(config, prismaClient, null, storage)
112
+ return getOpensaasContext(config, prismaClient, null, storage) as unknown as Context
98
113
  })()
99
114
 
100
115
  /**
@@ -131,17 +146,35 @@ exports[`Generate Command Integration > Generator Integration > should generate
131
146
  * DO NOT EDIT - This file is automatically generated
132
147
  */
133
148
 
134
- import type { Session as OpensaasSession, StorageUtils, ServerActionProps, AccessControlledDB } from '@opensaas/stack-core'
149
+ import type { Session as OpensaasSession, StorageUtils, ServerActionProps, AccessControlledDB, AccessContext } from '@opensaas/stack-core'
135
150
  import type { PrismaClient, Prisma } from './prisma-client/client'
136
151
  import type { PluginServices } from './plugin-types'
137
152
 
138
- export type User = {
153
+ /**
154
+ * Virtual fields for User - computed fields not in database
155
+ * These are added to query results via resolveOutput hooks
156
+ */
157
+ export type UserVirtualFields = {
158
+ // No virtual fields defined
159
+ }
160
+
161
+ /**
162
+ * Transformed fields for User - fields with resultExtension transformations
163
+ * These override Prisma's base types with transformed types via result extensions
164
+ */
165
+ export type UserTransformedFields = {
166
+ // No transformed fields defined
167
+ }
168
+
169
+ export type UserOutput = {
139
170
  id: string
140
171
  name: string
141
172
  email: string
142
173
  createdAt: Date
143
174
  updatedAt: Date
144
- }
175
+ } & UserVirtualFields
176
+
177
+ export type User = UserOutput
145
178
 
146
179
  export type UserCreateInput = {
147
180
  name: string
@@ -202,15 +235,43 @@ export type UserHooks = {
202
235
  }) => Promise<void>
203
236
  }
204
237
 
205
- export type Context<TSession extends OpensaasSession = OpensaasSession> = {
206
- db: AccessControlledDB<PrismaClient>
238
+ /**
239
+ * Custom DB type that uses Prisma's conditional types with virtual and transformed field support
240
+ * Types change based on select/include - relationships only present when explicitly included
241
+ * Virtual fields and transformed fields are added to the base model type
242
+ */
243
+ export type CustomDB = Omit<AccessControlledDB<PrismaClient>,
244
+ 'user'
245
+ > & {
246
+ user: {
247
+ findUnique: <T extends Prisma.UserFindUniqueArgs>(
248
+ args: Prisma.SelectSubset<T, Prisma.UserFindUniqueArgs>
249
+ ) => Promise<(Omit<Prisma.UserGetPayload<T>, keyof UserTransformedFields> & UserTransformedFields & UserVirtualFields) | null>
250
+ findMany: <T extends Prisma.UserFindManyArgs>(
251
+ args?: Prisma.SelectSubset<T, Prisma.UserFindManyArgs>
252
+ ) => Promise<Array<Omit<Prisma.UserGetPayload<T>, keyof UserTransformedFields> & UserTransformedFields & UserVirtualFields>>
253
+ create: <T extends Prisma.UserCreateArgs>(
254
+ args: Prisma.SelectSubset<T, Prisma.UserCreateArgs>
255
+ ) => Promise<Omit<Prisma.UserGetPayload<T>, keyof UserTransformedFields> & UserTransformedFields & UserVirtualFields>
256
+ update: <T extends Prisma.UserUpdateArgs>(
257
+ args: Prisma.SelectSubset<T, Prisma.UserUpdateArgs>
258
+ ) => Promise<(Omit<Prisma.UserGetPayload<T>, keyof UserTransformedFields> & UserTransformedFields & UserVirtualFields) | null>
259
+ delete: <T extends Prisma.UserDeleteArgs>(
260
+ args: Prisma.SelectSubset<T, Prisma.UserDeleteArgs>
261
+ ) => Promise<(Omit<Prisma.UserGetPayload<T>, keyof UserTransformedFields> & UserTransformedFields & UserVirtualFields) | null>
262
+ count: (args?: Prisma.UserCountArgs) => Promise<number>
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Context type compatible with AccessContext but with CustomDB for virtual field typing
268
+ * Extends AccessContext and overrides db property to include virtual fields in output types
269
+ */
270
+ export type Context<TSession extends OpensaasSession = OpensaasSession> = Omit<AccessContext<PrismaClient>, 'db' | 'session'> & {
271
+ db: CustomDB
207
272
  session: TSession
208
- prisma: PrismaClient
209
- storage: StorageUtils
210
- plugins: PluginServices
211
273
  serverAction: (props: ServerActionProps) => Promise<unknown>
212
274
  sudo: () => Context<TSession>
213
- _isSudo: boolean
214
275
  }"
215
276
  `;
216
277
 
@@ -287,19 +348,29 @@ exports[`Generate Command Integration > Generator Integration > should handle em
287
348
  * DO NOT EDIT - This file is automatically generated
288
349
  */
289
350
 
290
- import type { Session as OpensaasSession, StorageUtils, ServerActionProps, AccessControlledDB } from '@opensaas/stack-core'
351
+ import type { Session as OpensaasSession, StorageUtils, ServerActionProps, AccessControlledDB, AccessContext } from '@opensaas/stack-core'
291
352
  import type { PrismaClient, Prisma } from './prisma-client/client'
292
353
  import type { PluginServices } from './plugin-types'
293
354
 
294
- export type Context<TSession extends OpensaasSession = OpensaasSession> = {
295
- db: AccessControlledDB<PrismaClient>
355
+ /**
356
+ * Custom DB type that uses Prisma's conditional types with virtual and transformed field support
357
+ * Types change based on select/include - relationships only present when explicitly included
358
+ * Virtual fields and transformed fields are added to the base model type
359
+ */
360
+ export type CustomDB = Omit<AccessControlledDB<PrismaClient>,
361
+
362
+ > & {
363
+ }
364
+
365
+ /**
366
+ * Context type compatible with AccessContext but with CustomDB for virtual field typing
367
+ * Extends AccessContext and overrides db property to include virtual fields in output types
368
+ */
369
+ export type Context<TSession extends OpensaasSession = OpensaasSession> = Omit<AccessContext<PrismaClient>, 'db' | 'session'> & {
370
+ db: CustomDB
296
371
  session: TSession
297
- prisma: PrismaClient
298
- storage: StorageUtils
299
- plugins: PluginServices
300
372
  serverAction: (props: ServerActionProps) => Promise<unknown>
301
373
  sudo: () => Context<TSession>
302
- _isSudo: boolean
303
374
  }"
304
375
  `;
305
376
 
@@ -11,7 +11,7 @@ import {
11
11
  writeLists,
12
12
  writeContext,
13
13
  writePluginTypes,
14
- patchPrismaTypes,
14
+ writePrismaExtensions,
15
15
  } from '../generator/index.js'
16
16
  import { OpenSaasConfig } from '@opensaas/stack-core'
17
17
 
@@ -58,9 +58,8 @@ export async function generateCommand() {
58
58
 
59
59
  try {
60
60
  // Import plugin engine (avoid circular dependency)
61
- const { executeBeforeGenerateHooks } = await import(
62
- '@opensaas/stack-core/config/plugin-engine'
63
- )
61
+ const { executeBeforeGenerateHooks } =
62
+ await import('@opensaas/stack-core/config/plugin-engine')
64
63
  config = await executeBeforeGenerateHooks(config)
65
64
  pluginSpinner.succeed(chalk.green('Plugin beforeGenerate hooks complete'))
66
65
  } catch (err) {
@@ -78,6 +77,7 @@ export async function generateCommand() {
78
77
  const listsPath = path.join(cwd, '.opensaas', 'lists.ts')
79
78
  const contextPath = path.join(cwd, '.opensaas', 'context.ts')
80
79
  const pluginTypesPath = path.join(cwd, '.opensaas', 'plugin-types.ts')
80
+ const prismaExtensionsPath = path.join(cwd, '.opensaas', 'prisma-extensions.ts')
81
81
 
82
82
  writePrismaSchema(config, prismaSchemaPath)
83
83
  writePrismaConfig(config, prismaConfigPath)
@@ -85,6 +85,7 @@ export async function generateCommand() {
85
85
  writeLists(config, listsPath)
86
86
  writeContext(config, contextPath)
87
87
  writePluginTypes(config, pluginTypesPath)
88
+ writePrismaExtensions(config, prismaExtensionsPath)
88
89
 
89
90
  generatorSpinner.succeed(chalk.green('Schema generation complete'))
90
91
  console.log(chalk.green('✅ Prisma schema generated'))
@@ -93,6 +94,7 @@ export async function generateCommand() {
93
94
  console.log(chalk.green('✅ Lists namespace generated'))
94
95
  console.log(chalk.green('✅ Context factory generated'))
95
96
  console.log(chalk.green('✅ Plugin types generated'))
97
+ console.log(chalk.green('✅ Prisma extensions generated'))
96
98
 
97
99
  // Execute afterGenerate hooks if plugins are present
98
100
  if (config.plugins && config.plugins.length > 0) {
@@ -107,9 +109,8 @@ export async function generateCommand() {
107
109
  }
108
110
 
109
111
  // Execute afterGenerate hooks
110
- const { executeAfterGenerateHooks } = await import(
111
- '@opensaas/stack-core/config/plugin-engine'
112
- )
112
+ const { executeAfterGenerateHooks } =
113
+ await import('@opensaas/stack-core/config/plugin-engine')
113
114
  const modifiedFiles = await executeAfterGenerateHooks(config, generatedFiles)
114
115
 
115
116
  // Write back modified files
@@ -165,18 +166,6 @@ export async function generateCommand() {
165
166
  process.exit(1)
166
167
  }
167
168
 
168
- // Patch Prisma types with field transformations
169
- const patchSpinner = ora('Patching Prisma types...').start()
170
- try {
171
- patchPrismaTypes(config, cwd)
172
- patchSpinner.succeed(chalk.green('Type patching complete'))
173
- } catch (err) {
174
- patchSpinner.fail(chalk.red('Failed to patch types'))
175
- const message = err instanceof Error ? err.message : String(err)
176
- console.error(chalk.red('\n❌ Error:'), message)
177
- process.exit(1)
178
- }
179
-
180
169
  console.log(chalk.bold('\n✨ Generation complete!\n'))
181
170
  console.log(chalk.gray('Next steps:'))
182
171
  console.log(chalk.gray(' 1. Run: npx prisma db push'))