@opensaas/stack-cli 0.1.7 → 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 +207 -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 +51 -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 +57 -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 +587 -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 +54 -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.7",
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.7"
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,11 +162,52 @@ 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>
171
212
  sudo: () => Context<TSession>
172
213
  _isSudo: boolean
@@ -175,13 +216,12 @@ export type Context<TSession extends OpensaasSession = OpensaasSession> = {
175
216
 
176
217
  exports[`Generate Command Integration > Generator Integration > should generate consistent output across multiple runs > consistent-output 1`] = `
177
218
  "generator client {
178
- provider = "prisma-client-js"
219
+ provider = "prisma-client"
179
220
  output = "../.opensaas/prisma-client"
180
221
  }
181
222
 
182
223
  datasource db {
183
224
  provider = "sqlite"
184
- url = env("DATABASE_URL")
185
225
  }
186
226
 
187
227
  model User {
@@ -195,52 +235,48 @@ model User {
195
235
 
196
236
  exports[`Generate Command Integration > Generator Integration > should handle different database providers > mysql-provider 1`] = `
197
237
  "generator client {
198
- provider = "prisma-client-js"
238
+ provider = "prisma-client"
199
239
  output = "../.opensaas/prisma-client"
200
240
  }
201
241
 
202
242
  datasource db {
203
243
  provider = "mysql"
204
- url = env("DATABASE_URL")
205
244
  }
206
245
  "
207
246
  `;
208
247
 
209
248
  exports[`Generate Command Integration > Generator Integration > should handle different database providers > postgresql-provider 1`] = `
210
249
  "generator client {
211
- provider = "prisma-client-js"
250
+ provider = "prisma-client"
212
251
  output = "../.opensaas/prisma-client"
213
252
  }
214
253
 
215
254
  datasource db {
216
255
  provider = "postgresql"
217
- url = env("DATABASE_URL")
218
256
  }
219
257
  "
220
258
  `;
221
259
 
222
260
  exports[`Generate Command Integration > Generator Integration > should handle different database providers > sqlite-provider 1`] = `
223
261
  "generator client {
224
- provider = "prisma-client-js"
262
+ provider = "prisma-client"
225
263
  output = "../.opensaas/prisma-client"
226
264
  }
227
265
 
228
266
  datasource db {
229
267
  provider = "sqlite"
230
- url = env("DATABASE_URL")
231
268
  }
232
269
  "
233
270
  `;
234
271
 
235
272
  exports[`Generate Command Integration > Generator Integration > should handle empty lists config > empty-lists-schema 1`] = `
236
273
  "generator client {
237
- provider = "prisma-client-js"
274
+ provider = "prisma-client"
238
275
  output = "../.opensaas/prisma-client"
239
276
  }
240
277
 
241
278
  datasource db {
242
279
  provider = "sqlite"
243
- url = env("DATABASE_URL")
244
280
  }
245
281
  "
246
282
  `;
@@ -252,13 +288,15 @@ exports[`Generate Command Integration > Generator Integration > should handle em
252
288
  */
253
289
 
254
290
  import type { Session as OpensaasSession, StorageUtils, ServerActionProps, AccessControlledDB } from '@opensaas/stack-core'
255
- import type { PrismaClient } from './prisma-client'
291
+ import type { PrismaClient, Prisma } from './prisma-client/client'
292
+ import type { PluginServices } from './plugin-types'
256
293
 
257
294
  export type Context<TSession extends OpensaasSession = OpensaasSession> = {
258
295
  db: AccessControlledDB<PrismaClient>
259
296
  session: TSession
260
297
  prisma: PrismaClient
261
298
  storage: StorageUtils
299
+ plugins: PluginServices
262
300
  serverAction: (props: ServerActionProps) => Promise<unknown>
263
301
  sudo: () => Context<TSession>
264
302
  _isSudo: boolean
@@ -267,13 +305,12 @@ export type Context<TSession extends OpensaasSession = OpensaasSession> = {
267
305
 
268
306
  exports[`Generate Command Integration > Generator Integration > should overwrite existing files > overwrite-after 1`] = `
269
307
  "generator client {
270
- provider = "prisma-client-js"
308
+ provider = "prisma-client"
271
309
  output = "../.opensaas/prisma-client"
272
310
  }
273
311
 
274
312
  datasource db {
275
313
  provider = "sqlite"
276
- url = env("DATABASE_URL")
277
314
  }
278
315
 
279
316
  model Post {
@@ -287,13 +324,12 @@ model Post {
287
324
 
288
325
  exports[`Generate Command Integration > Generator Integration > should overwrite existing files > overwrite-before 1`] = `
289
326
  "generator client {
290
- provider = "prisma-client-js"
327
+ provider = "prisma-client"
291
328
  output = "../.opensaas/prisma-client"
292
329
  }
293
330
 
294
331
  datasource db {
295
332
  provider = "sqlite"
296
- url = env("DATABASE_URL")
297
333
  }
298
334
 
299
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
  `,