@opensaas/stack-cli 0.1.6 → 0.3.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 (94) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +215 -0
  3. package/CLAUDE.md +60 -12
  4. package/dist/commands/generate.d.ts.map +1 -1
  5. package/dist/commands/generate.js +10 -1
  6. package/dist/commands/generate.js.map +1 -1
  7. package/dist/commands/mcp.d.ts +6 -0
  8. package/dist/commands/mcp.d.ts.map +1 -0
  9. package/dist/commands/mcp.js +116 -0
  10. package/dist/commands/mcp.js.map +1 -0
  11. package/dist/generator/context.d.ts.map +1 -1
  12. package/dist/generator/context.js +21 -3
  13. package/dist/generator/context.js.map +1 -1
  14. package/dist/generator/index.d.ts +3 -0
  15. package/dist/generator/index.d.ts.map +1 -1
  16. package/dist/generator/index.js +3 -0
  17. package/dist/generator/index.js.map +1 -1
  18. package/dist/generator/lists.d.ts +31 -0
  19. package/dist/generator/lists.d.ts.map +1 -0
  20. package/dist/generator/lists.js +91 -0
  21. package/dist/generator/lists.js.map +1 -0
  22. package/dist/generator/plugin-types.d.ts +10 -0
  23. package/dist/generator/plugin-types.d.ts.map +1 -0
  24. package/dist/generator/plugin-types.js +122 -0
  25. package/dist/generator/plugin-types.js.map +1 -0
  26. package/dist/generator/prisma-config.d.ts +17 -0
  27. package/dist/generator/prisma-config.d.ts.map +1 -0
  28. package/dist/generator/prisma-config.js +40 -0
  29. package/dist/generator/prisma-config.js.map +1 -0
  30. package/dist/generator/prisma.d.ts.map +1 -1
  31. package/dist/generator/prisma.js +1 -2
  32. package/dist/generator/prisma.js.map +1 -1
  33. package/dist/generator/types.d.ts.map +1 -1
  34. package/dist/generator/types.js +53 -1
  35. package/dist/generator/types.js.map +1 -1
  36. package/dist/index.js +3 -0
  37. package/dist/index.js.map +1 -1
  38. package/dist/mcp/lib/documentation-provider.d.ts +43 -0
  39. package/dist/mcp/lib/documentation-provider.d.ts.map +1 -0
  40. package/dist/mcp/lib/documentation-provider.js +163 -0
  41. package/dist/mcp/lib/documentation-provider.js.map +1 -0
  42. package/dist/mcp/lib/features/catalog.d.ts +26 -0
  43. package/dist/mcp/lib/features/catalog.d.ts.map +1 -0
  44. package/dist/mcp/lib/features/catalog.js +291 -0
  45. package/dist/mcp/lib/features/catalog.js.map +1 -0
  46. package/dist/mcp/lib/generators/feature-generator.d.ts +35 -0
  47. package/dist/mcp/lib/generators/feature-generator.d.ts.map +1 -0
  48. package/dist/mcp/lib/generators/feature-generator.js +546 -0
  49. package/dist/mcp/lib/generators/feature-generator.js.map +1 -0
  50. package/dist/mcp/lib/types.d.ts +80 -0
  51. package/dist/mcp/lib/types.d.ts.map +1 -0
  52. package/dist/mcp/lib/types.js +5 -0
  53. package/dist/mcp/lib/types.js.map +1 -0
  54. package/dist/mcp/lib/wizards/wizard-engine.d.ts +71 -0
  55. package/dist/mcp/lib/wizards/wizard-engine.d.ts.map +1 -0
  56. package/dist/mcp/lib/wizards/wizard-engine.js +356 -0
  57. package/dist/mcp/lib/wizards/wizard-engine.js.map +1 -0
  58. package/dist/mcp/server/index.d.ts +8 -0
  59. package/dist/mcp/server/index.d.ts.map +1 -0
  60. package/dist/mcp/server/index.js +202 -0
  61. package/dist/mcp/server/index.js.map +1 -0
  62. package/dist/mcp/server/stack-mcp-server.d.ts +92 -0
  63. package/dist/mcp/server/stack-mcp-server.d.ts.map +1 -0
  64. package/dist/mcp/server/stack-mcp-server.js +265 -0
  65. package/dist/mcp/server/stack-mcp-server.js.map +1 -0
  66. package/package.json +9 -7
  67. package/src/commands/__snapshots__/generate.test.ts.snap +61 -21
  68. package/src/commands/dev.test.ts +0 -1
  69. package/src/commands/generate.test.ts +18 -8
  70. package/src/commands/generate.ts +12 -0
  71. package/src/commands/mcp.ts +135 -0
  72. package/src/generator/__snapshots__/context.test.ts.snap +8 -8
  73. package/src/generator/__snapshots__/prisma.test.ts.snap +8 -16
  74. package/src/generator/__snapshots__/types.test.ts.snap +605 -9
  75. package/src/generator/context.test.ts +13 -8
  76. package/src/generator/context.ts +21 -3
  77. package/src/generator/index.ts +3 -0
  78. package/src/generator/lists.test.ts +335 -0
  79. package/src/generator/lists.ts +102 -0
  80. package/src/generator/plugin-types.ts +147 -0
  81. package/src/generator/prisma-config.ts +46 -0
  82. package/src/generator/prisma.test.ts +0 -10
  83. package/src/generator/prisma.ts +1 -2
  84. package/src/generator/types.test.ts +0 -12
  85. package/src/generator/types.ts +56 -1
  86. package/src/index.ts +4 -0
  87. package/src/mcp/lib/documentation-provider.ts +203 -0
  88. package/src/mcp/lib/features/catalog.ts +301 -0
  89. package/src/mcp/lib/generators/feature-generator.ts +598 -0
  90. package/src/mcp/lib/types.ts +89 -0
  91. package/src/mcp/lib/wizards/wizard-engine.ts +427 -0
  92. package/src/mcp/server/index.ts +240 -0
  93. package/src/mcp/server/stack-mcp-server.ts +301 -0
  94. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,92 @@
1
+ /**
2
+ * OpenSaaS Stack MCP Server - Business logic for MCP tools
3
+ */
4
+ export declare class StackMCPServer {
5
+ private wizardEngine;
6
+ private docsProvider;
7
+ constructor();
8
+ /**
9
+ * Implement a feature - starts the wizard flow
10
+ */
11
+ implementFeature({ feature, description }: {
12
+ feature: string;
13
+ description?: string;
14
+ }): Promise<{
15
+ content: Array<{
16
+ type: string;
17
+ text: string;
18
+ }>;
19
+ }>;
20
+ /**
21
+ * Answer a wizard question
22
+ */
23
+ answerFeatureQuestion({ sessionId, answer, }: {
24
+ sessionId: string;
25
+ answer: string | boolean | string[];
26
+ }): Promise<{
27
+ content: Array<{
28
+ type: string;
29
+ text: string;
30
+ }>;
31
+ }>;
32
+ /**
33
+ * Answer a follow-up question
34
+ */
35
+ answerFollowUpQuestion({ sessionId, answer }: {
36
+ sessionId: string;
37
+ answer: string;
38
+ }): Promise<{
39
+ content: Array<{
40
+ type: string;
41
+ text: string;
42
+ }>;
43
+ }>;
44
+ /**
45
+ * Search documentation
46
+ */
47
+ searchFeatureDocs({ topic }: {
48
+ topic: string;
49
+ }): Promise<{
50
+ content: {
51
+ type: "text";
52
+ text: string;
53
+ }[];
54
+ }>;
55
+ /**
56
+ * List all available features
57
+ */
58
+ listFeatures(): Promise<{
59
+ content: {
60
+ type: "text";
61
+ text: string;
62
+ }[];
63
+ }>;
64
+ /**
65
+ * Suggest complementary features based on described features
66
+ */
67
+ suggestFeatures({ currentFeatures }: {
68
+ currentFeatures?: string[];
69
+ }): Promise<{
70
+ content: {
71
+ type: "text";
72
+ text: string;
73
+ }[];
74
+ }>;
75
+ /**
76
+ * Validate a feature implementation
77
+ */
78
+ validateFeature({ feature }: {
79
+ feature: string;
80
+ configPath?: string;
81
+ }): Promise<{
82
+ content: {
83
+ type: "text";
84
+ text: string;
85
+ }[];
86
+ }>;
87
+ /**
88
+ * Cleanup - clear wizard sessions and caches
89
+ */
90
+ cleanup(): void;
91
+ }
92
+ //# sourceMappingURL=stack-mcp-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stack-mcp-server.d.ts","sourceRoot":"","sources":["../../../src/mcp/server/stack-mcp-server.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,qBAAa,cAAc;IACzB,OAAO,CAAC,YAAY,CAAc;IAClC,OAAO,CAAC,YAAY,CAA+B;;IAOnD;;OAEG;IACG,gBAAgB,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE;;;;;;IAsC1F;;OAEG;IACG,qBAAqB,CAAC,EAC1B,SAAS,EACT,MAAM,GACP,EAAE;QACD,SAAS,EAAE,MAAM,CAAA;QACjB,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,CAAA;KACpC;;;;;;IAID;;OAEG;IACG,sBAAsB,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;;;;;;IAIzF;;OAEG;IACG,iBAAiB,CAAC,EAAE,KAAK,EAAE,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE;;;;;;IAuBpD;;OAEG;IACG,YAAY;;;;;;IAiElB;;OAEG;IACG,eAAe,CAAC,EAAE,eAAe,EAAE,EAAE;QAAE,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE;;;;;;IAqEzE;;OAEG;IACG,eAAe,CAAC,EAAE,OAAO,EAAE,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE;;;;;;IA8C3E;;OAEG;IACH,OAAO;CAIR"}
@@ -0,0 +1,265 @@
1
+ /**
2
+ * OpenSaaS Stack MCP Server - Business logic for MCP tools
3
+ */
4
+ import { WizardEngine } from '../lib/wizards/wizard-engine.js';
5
+ import { OpenSaasDocumentationProvider } from '../lib/documentation-provider.js';
6
+ import { getAllFeatures, getFeature } from '../lib/features/catalog.js';
7
+ export class StackMCPServer {
8
+ wizardEngine;
9
+ docsProvider;
10
+ constructor() {
11
+ this.wizardEngine = new WizardEngine();
12
+ this.docsProvider = new OpenSaasDocumentationProvider();
13
+ }
14
+ /**
15
+ * Implement a feature - starts the wizard flow
16
+ */
17
+ async implementFeature({ feature, description }) {
18
+ if (feature === 'custom' && !description) {
19
+ return {
20
+ content: [
21
+ {
22
+ type: 'text',
23
+ text: '❌ For custom features, please provide a description of what you want to build.',
24
+ },
25
+ ],
26
+ };
27
+ }
28
+ if (feature === 'custom') {
29
+ return {
30
+ content: [
31
+ {
32
+ type: 'text',
33
+ text: `🔧 **Custom Feature**: ${description}
34
+
35
+ I'll help you build this custom feature. Let me search the docs for relevant patterns...
36
+
37
+ ${await this.searchFeatureDocs({ topic: description })}
38
+
39
+ Based on your description, consider using these OpenSaaS patterns:
40
+ - Define your data model with \`list()\` and field types
41
+ - Add access control with \`access.operation\` and \`access.field\`
42
+ - Use hooks for data transformation and side effects
43
+ - Consider if any plugins would help (auth, storage, RAG)
44
+
45
+ Would you like me to help you design the config for this feature?`,
46
+ },
47
+ ],
48
+ };
49
+ }
50
+ return this.wizardEngine.startFeature(feature);
51
+ }
52
+ /**
53
+ * Answer a wizard question
54
+ */
55
+ async answerFeatureQuestion({ sessionId, answer, }) {
56
+ return this.wizardEngine.answerQuestion(sessionId, answer);
57
+ }
58
+ /**
59
+ * Answer a follow-up question
60
+ */
61
+ async answerFollowUpQuestion({ sessionId, answer }) {
62
+ return this.wizardEngine.answerFollowUp(sessionId, answer);
63
+ }
64
+ /**
65
+ * Search documentation
66
+ */
67
+ async searchFeatureDocs({ topic }) {
68
+ const docs = await this.docsProvider.getTopicDocs(topic);
69
+ return {
70
+ content: [
71
+ {
72
+ type: 'text',
73
+ text: `# ${docs.topic}
74
+
75
+ ${docs.content}
76
+
77
+ ${docs.codeExamples.length > 0 ? `\n## Code Examples\n\n${docs.codeExamples.join('\n\n')}` : ''}
78
+
79
+ ${docs.relatedTopics.length > 0 ? `\n## Related Topics\n\n${docs.relatedTopics.map((t) => `- ${t}`).join('\n')}` : ''}
80
+
81
+ ---
82
+
83
+ 📚 **Full documentation**: ${docs.url}`,
84
+ },
85
+ ],
86
+ };
87
+ }
88
+ /**
89
+ * List all available features
90
+ */
91
+ async listFeatures() {
92
+ const features = getAllFeatures();
93
+ const categories = {
94
+ authentication: [],
95
+ content: [],
96
+ storage: [],
97
+ search: [],
98
+ custom: [],
99
+ };
100
+ features.forEach((feature) => {
101
+ categories[feature.category].push(feature);
102
+ });
103
+ const formatFeatureList = (featureList) => featureList
104
+ .map((f) => `### ${f.name}\n**ID**: \`${f.id}\`\n${f.description}\n\n**Includes**:\n${f.includes.map((i) => `- ${i}`).join('\n')}\n${f.dependsOn && f.dependsOn.length > 0 ? `\n**Requires**: ${f.dependsOn.join(', ')}` : ''}`)
105
+ .join('\n\n');
106
+ return {
107
+ content: [
108
+ {
109
+ type: 'text',
110
+ text: `# Available OpenSaaS Stack Features
111
+
112
+ Use \`opensaas_implement_feature\` to start implementing any of these:
113
+
114
+ ## 🔐 Authentication
115
+
116
+ ${formatFeatureList(categories.authentication)}
117
+
118
+ ## 📝 Content Management
119
+
120
+ ${formatFeatureList(categories.content)}
121
+
122
+ ## 📦 Storage
123
+
124
+ ${formatFeatureList(categories.storage)}
125
+
126
+ ## 🔍 Search
127
+
128
+ ${formatFeatureList(categories.search)}
129
+
130
+ ---
131
+
132
+ **To implement a feature**, call:
133
+ \`\`\`
134
+ opensaas_implement_feature({ feature: "authentication" })
135
+ \`\`\`
136
+
137
+ **For custom features**, call:
138
+ \`\`\`
139
+ opensaas_implement_feature({
140
+ feature: "custom",
141
+ description: "what you want to build"
142
+ })
143
+ \`\`\``,
144
+ },
145
+ ],
146
+ };
147
+ }
148
+ /**
149
+ * Suggest complementary features based on described features
150
+ */
151
+ async suggestFeatures({ currentFeatures }) {
152
+ const allFeatures = getAllFeatures();
153
+ const implemented = new Set(currentFeatures || []);
154
+ const suggestions = allFeatures
155
+ .filter((f) => !implemented.has(f.id))
156
+ .map((f) => {
157
+ let reasoning = '';
158
+ // Add context-aware suggestions
159
+ if (f.id === 'authentication' && !implemented.has('authentication')) {
160
+ reasoning = 'Essential for user management and access control';
161
+ }
162
+ else if (f.id === 'blog' && implemented.has('authentication')) {
163
+ reasoning = 'You have auth - add content creation capabilities';
164
+ }
165
+ else if (f.id === 'comments' &&
166
+ (implemented.has('blog') || implemented.has('authentication'))) {
167
+ reasoning = 'Enhance engagement with user comments';
168
+ }
169
+ else if (f.id === 'file-upload' &&
170
+ (implemented.has('blog') || implemented.has('authentication'))) {
171
+ reasoning = 'Add media support for richer content';
172
+ }
173
+ else if (f.id === 'semantic-search' && implemented.has('blog')) {
174
+ reasoning = 'Make your content more discoverable';
175
+ }
176
+ return { feature: f, reasoning };
177
+ })
178
+ .filter((s) => s.reasoning);
179
+ if (suggestions.length === 0) {
180
+ return {
181
+ content: [
182
+ {
183
+ type: 'text',
184
+ text: "🎉 You've implemented all our built-in features!\n\nConsider building custom features tailored to your specific needs using `opensaas_implement_feature({ feature: 'custom', description: '...' })`",
185
+ },
186
+ ],
187
+ };
188
+ }
189
+ return {
190
+ content: [
191
+ {
192
+ type: 'text',
193
+ text: `# Feature Suggestions
194
+
195
+ Based on ${currentFeatures && currentFeatures.length > 0 ? `your current features (${currentFeatures.join(', ')})` : 'common patterns'}, consider adding:
196
+
197
+ ${suggestions
198
+ .map((s, i) => `## ${i + 1}. ${s.feature.name}
199
+
200
+ ${s.reasoning}
201
+
202
+ **What you'll get**:
203
+ ${s.feature.includes.map((inc) => `- ${inc}`).join('\n')}
204
+
205
+ **To implement**: \`opensaas_implement_feature({ feature: "${s.feature.id}" })\`
206
+ `)
207
+ .join('\n---\n\n')}`,
208
+ },
209
+ ],
210
+ };
211
+ }
212
+ /**
213
+ * Validate a feature implementation
214
+ */
215
+ async validateFeature({ feature }) {
216
+ const featureDefinition = getFeature(feature);
217
+ if (!featureDefinition) {
218
+ return {
219
+ content: [
220
+ {
221
+ type: 'text',
222
+ text: `❌ Unknown feature: ${feature}\n\nUse \`opensaas_list_features\` to see available features.`,
223
+ },
224
+ ],
225
+ };
226
+ }
227
+ // TODO: Implement actual validation by reading the config file
228
+ // For now, return validation checklist
229
+ return {
230
+ content: [
231
+ {
232
+ type: 'text',
233
+ text: `# ${featureDefinition.name} Validation
234
+
235
+ Checking your implementation...
236
+
237
+ ## Checklist
238
+
239
+ ${featureDefinition.includes.map((item) => `- [ ] ${item}`).join('\n')}
240
+
241
+ ${featureDefinition.dependsOn && featureDefinition.dependsOn.length > 0 ? `\n## Dependencies\n\nEnsure these features are implemented:\n${featureDefinition.dependsOn.map((dep) => `- [ ] ${dep}`).join('\n')}\n` : ''}
242
+
243
+ ---
244
+
245
+ **Manual validation steps**:
246
+
247
+ 1. Check that all required lists are defined in your config
248
+ 2. Verify access control is properly configured
249
+ 3. Test the feature in your development environment
250
+ 4. Run \`pnpm generate\` and \`pnpm db:push\` successfully
251
+
252
+ **Note**: Full automatic validation coming soon! For now, use this checklist to verify your implementation.`,
253
+ },
254
+ ],
255
+ };
256
+ }
257
+ /**
258
+ * Cleanup - clear wizard sessions and caches
259
+ */
260
+ cleanup() {
261
+ this.wizardEngine.clearCompletedSessions();
262
+ this.docsProvider.clearExpiredCache();
263
+ }
264
+ }
265
+ //# sourceMappingURL=stack-mcp-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stack-mcp-server.js","sourceRoot":"","sources":["../../../src/mcp/server/stack-mcp-server.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opensaas/stack-cli",
3
- "version": "0.1.6",
3
+ "version": "0.3.0",
4
4
  "description": "CLI tools for OpenSaas Stack",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -36,20 +36,22 @@
36
36
  "url": "https://github.com/OpenSaasAU/stack/issues"
37
37
  },
38
38
  "dependencies": {
39
- "commander": "^14.0.1",
39
+ "@modelcontextprotocol/sdk": "^1.0.4",
40
40
  "chalk": "^5.6.2",
41
- "ora": "^9.0.0",
42
- "prompts": "^2.4.2",
43
41
  "chokidar": "^4.0.3",
42
+ "commander": "^14.0.1",
44
43
  "jiti": "^2.6.1",
45
- "@opensaas/stack-core": "0.1.6"
44
+ "ora": "^9.0.0",
45
+ "prompts": "^2.4.2",
46
+ "zod": "^4.1.12",
47
+ "@opensaas/stack-core": "0.3.0"
46
48
  },
47
49
  "devDependencies": {
48
50
  "@types/node": "^24.7.2",
49
51
  "@types/prompts": "^2.4.9",
50
- "@vitest/coverage-v8": "^4.0.3",
52
+ "@vitest/coverage-v8": "^4.0.4",
51
53
  "typescript": "^5.9.3",
52
- "vitest": "^4.0.3"
54
+ "vitest": "^4.0.0"
53
55
  },
54
56
  "scripts": {
55
57
  "build": "tsc",
@@ -12,7 +12,7 @@ exports[`Generate Command Integration > Generator Integration > should generate
12
12
 
13
13
  import { getContext as getOpensaasContext } from '@opensaas/stack-core'
14
14
  import type { Session as OpensaasSession, OpenSaasConfig } from '@opensaas/stack-core'
15
- import { PrismaClient } from './prisma-client'
15
+ import { PrismaClient } from './prisma-client/client'
16
16
  import type { Context } from './types'
17
17
  import configOrPromise from '../opensaas.config'
18
18
 
@@ -21,7 +21,7 @@ const configPromise = Promise.resolve(configOrPromise)
21
21
  let resolvedConfig: OpenSaasConfig | null = null
22
22
 
23
23
  // Internal Prisma singleton - managed automatically
24
- const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined }
24
+ const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | null }
25
25
  let prisma: PrismaClient | null = null
26
26
 
27
27
  async function getPrisma() {
@@ -29,7 +29,7 @@ async function getPrisma() {
29
29
  if (!resolvedConfig) {
30
30
  resolvedConfig = await configPromise
31
31
  }
32
- prisma = globalForPrisma.prisma ?? new PrismaClient()
32
+ prisma = globalForPrisma.prisma ?? resolvedConfig.db.prismaClientConstructor!(PrismaClient)
33
33
  if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
34
34
  }
35
35
  return prisma
@@ -107,13 +107,12 @@ export const config = getConfig()
107
107
 
108
108
  exports[`Generate Command Integration > Generator Integration > should generate all files for a basic config > prisma-schema 1`] = `
109
109
  "generator client {
110
- provider = "prisma-client-js"
110
+ provider = "prisma-client"
111
111
  output = "../.opensaas/prisma-client"
112
112
  }
113
113
 
114
114
  datasource db {
115
115
  provider = "sqlite"
116
- url = env("DATABASE_URL")
117
116
  }
118
117
 
119
118
  model User {
@@ -133,7 +132,8 @@ exports[`Generate Command Integration > Generator Integration > should generate
133
132
  */
134
133
 
135
134
  import type { Session as OpensaasSession, StorageUtils, ServerActionProps, AccessControlledDB } from '@opensaas/stack-core'
136
- import type { PrismaClient } from './prisma-client'
135
+ import type { PrismaClient, Prisma } from './prisma-client/client'
136
+ import type { PluginServices } from './plugin-types'
137
137
 
138
138
  export type User = {
139
139
  id: string
@@ -162,24 +162,66 @@ export type UserWhereInput = {
162
162
  email?: { equals?: string, not?: string }
163
163
  }
164
164
 
165
+ /**
166
+ * Hook types for User list
167
+ * Properly typed to use Prisma's generated input types
168
+ */
169
+ export type UserHooks = {
170
+ resolveInput?: (args:
171
+ | {
172
+ operation: 'create'
173
+ resolvedData: Prisma.UserCreateInput
174
+ item: undefined
175
+ context: import('@opensaas/stack-core').AccessContext
176
+ }
177
+ | {
178
+ operation: 'update'
179
+ resolvedData: Prisma.UserUpdateInput
180
+ item: User
181
+ context: import('@opensaas/stack-core').AccessContext
182
+ }
183
+ ) => Promise<Prisma.UserCreateInput | Prisma.UserUpdateInput>
184
+ validateInput?: (args: {
185
+ operation: 'create' | 'update'
186
+ resolvedData: Prisma.UserCreateInput | Prisma.UserUpdateInput
187
+ item?: User
188
+ context: import('@opensaas/stack-core').AccessContext
189
+ addValidationError: (msg: string) => void
190
+ }) => Promise<void>
191
+ beforeOperation?: (args: {
192
+ operation: 'create' | 'update' | 'delete'
193
+ resolvedData?: Prisma.UserCreateInput | Prisma.UserUpdateInput
194
+ item?: User
195
+ context: import('@opensaas/stack-core').AccessContext
196
+ }) => Promise<void>
197
+ afterOperation?: (args: {
198
+ operation: 'create' | 'update' | 'delete'
199
+ resolvedData?: Prisma.UserCreateInput | Prisma.UserUpdateInput
200
+ item?: User
201
+ context: import('@opensaas/stack-core').AccessContext
202
+ }) => Promise<void>
203
+ }
204
+
165
205
  export type Context<TSession extends OpensaasSession = OpensaasSession> = {
166
206
  db: AccessControlledDB<PrismaClient>
167
207
  session: TSession
168
208
  prisma: PrismaClient
169
209
  storage: StorageUtils
210
+ plugins: PluginServices
170
211
  serverAction: (props: ServerActionProps) => Promise<unknown>
212
+ sudo: () => Context<TSession>
213
+ _isSudo: boolean
171
214
  }"
172
215
  `;
173
216
 
174
217
  exports[`Generate Command Integration > Generator Integration > should generate consistent output across multiple runs > consistent-output 1`] = `
175
218
  "generator client {
176
- provider = "prisma-client-js"
219
+ provider = "prisma-client"
177
220
  output = "../.opensaas/prisma-client"
178
221
  }
179
222
 
180
223
  datasource db {
181
224
  provider = "sqlite"
182
- url = env("DATABASE_URL")
183
225
  }
184
226
 
185
227
  model User {
@@ -193,52 +235,48 @@ model User {
193
235
 
194
236
  exports[`Generate Command Integration > Generator Integration > should handle different database providers > mysql-provider 1`] = `
195
237
  "generator client {
196
- provider = "prisma-client-js"
238
+ provider = "prisma-client"
197
239
  output = "../.opensaas/prisma-client"
198
240
  }
199
241
 
200
242
  datasource db {
201
243
  provider = "mysql"
202
- url = env("DATABASE_URL")
203
244
  }
204
245
  "
205
246
  `;
206
247
 
207
248
  exports[`Generate Command Integration > Generator Integration > should handle different database providers > postgresql-provider 1`] = `
208
249
  "generator client {
209
- provider = "prisma-client-js"
250
+ provider = "prisma-client"
210
251
  output = "../.opensaas/prisma-client"
211
252
  }
212
253
 
213
254
  datasource db {
214
255
  provider = "postgresql"
215
- url = env("DATABASE_URL")
216
256
  }
217
257
  "
218
258
  `;
219
259
 
220
260
  exports[`Generate Command Integration > Generator Integration > should handle different database providers > sqlite-provider 1`] = `
221
261
  "generator client {
222
- provider = "prisma-client-js"
262
+ provider = "prisma-client"
223
263
  output = "../.opensaas/prisma-client"
224
264
  }
225
265
 
226
266
  datasource db {
227
267
  provider = "sqlite"
228
- url = env("DATABASE_URL")
229
268
  }
230
269
  "
231
270
  `;
232
271
 
233
272
  exports[`Generate Command Integration > Generator Integration > should handle empty lists config > empty-lists-schema 1`] = `
234
273
  "generator client {
235
- provider = "prisma-client-js"
274
+ provider = "prisma-client"
236
275
  output = "../.opensaas/prisma-client"
237
276
  }
238
277
 
239
278
  datasource db {
240
279
  provider = "sqlite"
241
- url = env("DATABASE_URL")
242
280
  }
243
281
  "
244
282
  `;
@@ -250,26 +288,29 @@ exports[`Generate Command Integration > Generator Integration > should handle em
250
288
  */
251
289
 
252
290
  import type { Session as OpensaasSession, StorageUtils, ServerActionProps, AccessControlledDB } from '@opensaas/stack-core'
253
- import type { PrismaClient } from './prisma-client'
291
+ import type { PrismaClient, Prisma } from './prisma-client/client'
292
+ import type { PluginServices } from './plugin-types'
254
293
 
255
294
  export type Context<TSession extends OpensaasSession = OpensaasSession> = {
256
295
  db: AccessControlledDB<PrismaClient>
257
296
  session: TSession
258
297
  prisma: PrismaClient
259
298
  storage: StorageUtils
299
+ plugins: PluginServices
260
300
  serverAction: (props: ServerActionProps) => Promise<unknown>
301
+ sudo: () => Context<TSession>
302
+ _isSudo: boolean
261
303
  }"
262
304
  `;
263
305
 
264
306
  exports[`Generate Command Integration > Generator Integration > should overwrite existing files > overwrite-after 1`] = `
265
307
  "generator client {
266
- provider = "prisma-client-js"
308
+ provider = "prisma-client"
267
309
  output = "../.opensaas/prisma-client"
268
310
  }
269
311
 
270
312
  datasource db {
271
313
  provider = "sqlite"
272
- url = env("DATABASE_URL")
273
314
  }
274
315
 
275
316
  model Post {
@@ -283,13 +324,12 @@ model Post {
283
324
 
284
325
  exports[`Generate Command Integration > Generator Integration > should overwrite existing files > overwrite-before 1`] = `
285
326
  "generator client {
286
- provider = "prisma-client-js"
327
+ provider = "prisma-client"
287
328
  output = "../.opensaas/prisma-client"
288
329
  }
289
330
 
290
331
  datasource db {
291
332
  provider = "sqlite"
292
- url = env("DATABASE_URL")
293
333
  }
294
334
 
295
335
  model User {
@@ -73,7 +73,6 @@ describe('Dev Command', () => {
73
73
  `
74
74
  import { config } from '@opensaas/stack-core'
75
75
  export default config({
76
- db: { provider: 'sqlite', url: 'file:./dev.db' },
77
76
  lists: {}
78
77
  })
79
78
  `,