launchbase 1.1.6 → 1.1.8

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.
package/bin/launchbase.js CHANGED
@@ -7,7 +7,7 @@ const crypto = require('crypto');
7
7
  const fs = require('fs-extra');
8
8
  const { execSync, spawn } = require('child_process');
9
9
 
10
- const VERSION = '1.1.6';
10
+ const VERSION = '1.1.8';
11
11
  const program = new Command();
12
12
 
13
13
  function findAvailablePort(startPort = 5432, maxAttempts = 100) {
@@ -357,15 +357,13 @@ program
357
357
  .command('new')
358
358
  .description('šŸš€ Create new project and start development (one command experience)')
359
359
  .argument('<appName>', 'Project name')
360
- .option('--template', 'Include frontend React template')
360
+ .option('--no-template', 'Skip frontend React template')
361
361
  .option('--sdk', 'Include TypeScript SDK')
362
362
  .option('--no-docker', 'Skip Docker/database setup')
363
363
  .option('--no-cicd', 'Skip CI/CD workflow')
364
364
  .action(async (appName, options, command) => {
365
365
  console.log('\nšŸš€ LaunchBase CLI v' + VERSION + '\n');
366
366
  console.log('šŸ“ Creating project:', appName);
367
- console.log('šŸ” DEBUG options:', JSON.stringify(options));
368
- console.log('šŸ” DEBUG template flag:', options.template);
369
367
 
370
368
  // Find available ports first
371
369
  console.log('šŸ” Finding available ports...');
@@ -384,7 +382,7 @@ program
384
382
 
385
383
  // Copy template files with filtering
386
384
  console.log('šŸ“‚ Copying template files...');
387
- console.log(` Include frontend: ${options.template ? 'yes' : 'no'}`);
385
+ console.log(` Include frontend: ${options.template === false ? 'no' : 'yes'}`);
388
386
 
389
387
  await fs.copy(templateDir, targetDir, {
390
388
  filter: (src) => {
@@ -396,8 +394,8 @@ program
396
394
  return false;
397
395
  }
398
396
 
399
- // Skip frontend if not requested (check with trailing slash to avoid partial matches)
400
- if ((relativePath.startsWith('frontend/') || relativePath === 'frontend') && !options.template) {
397
+ // Skip frontend if --no-template flag is used
398
+ if ((relativePath.startsWith('frontend/') || relativePath === 'frontend') && options.template === false) {
401
399
  return false;
402
400
  }
403
401
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "launchbase",
3
- "version": "1.1.6",
3
+ "version": "1.1.8",
4
4
  "description": "Generate production-ready NestJS backends with authentication, multi-tenancy, billing, and deployment in minutes",
5
5
  "author": "LaunchBase",
6
6
  "keywords": [
@@ -166,3 +166,96 @@ export const filesApi = {
166
166
  await api.delete(`/api/files/${id}`)
167
167
  },
168
168
  }
169
+
170
+ // Edge Functions API
171
+ export const edgeFunctionsApi = {
172
+ list: async (projectId: string) => {
173
+ const res = await api.get(`/api/projects/${projectId}/functions`)
174
+ return res.data
175
+ },
176
+
177
+ create: async (projectId: string, data: { name: string; code: string; description?: string }) => {
178
+ const res = await api.post(`/api/projects/${projectId}/functions`, data)
179
+ return res.data
180
+ },
181
+
182
+ get: async (projectId: string, functionId: string) => {
183
+ const res = await api.get(`/api/projects/${projectId}/functions/${functionId}`)
184
+ return res.data
185
+ },
186
+
187
+ update: async (projectId: string, functionId: string, data: Partial<{ name: string; code: string; description: string }>) => {
188
+ const res = await api.put(`/api/projects/${projectId}/functions/${functionId}`, data)
189
+ return res.data
190
+ },
191
+
192
+ delete: async (projectId: string, functionId: string) => {
193
+ await api.delete(`/api/projects/${projectId}/functions/${functionId}`)
194
+ },
195
+
196
+ deploy: async (projectId: string, functionId: string) => {
197
+ const res = await api.post(`/api/projects/${projectId}/functions/${functionId}/deploy`)
198
+ return res.data
199
+ },
200
+
201
+ execute: async (projectId: string, functionId: string, payload: any) => {
202
+ const res = await api.post(`/api/projects/${projectId}/functions/${functionId}/execute`, { payload })
203
+ return res.data
204
+ },
205
+
206
+ getLogs: async (projectId: string, functionId: string) => {
207
+ const res = await api.get(`/api/projects/${projectId}/functions/${functionId}/logs`)
208
+ return res.data
209
+ },
210
+ }
211
+
212
+ // Vector Collections API
213
+ export const vectorApi = {
214
+ listCollections: async (projectId: string) => {
215
+ const res = await api.get(`/api/projects/${projectId}/vector/collections`)
216
+ return res.data
217
+ },
218
+
219
+ createCollection: async (projectId: string, data: { name: string; description?: string }) => {
220
+ const res = await api.post(`/api/projects/${projectId}/vector/collections`, data)
221
+ return res.data
222
+ },
223
+
224
+ createEmbedding: async (projectId: string, data: { content: string; collectionId: string; metadata?: any }) => {
225
+ const res = await api.post(`/api/projects/${projectId}/vector/embeddings`, data)
226
+ return res.data
227
+ },
228
+
229
+ search: async (projectId: string, data: { query: string; collectionId: string; limit?: number }) => {
230
+ const res = await api.post(`/api/projects/${projectId}/vector/search`, data)
231
+ return res.data
232
+ },
233
+
234
+ query: async (projectId: string, query: string, collection?: string, limit?: number) => {
235
+ const res = await api.post(`/api/projects/${projectId}/vector/query`, { query, collection, limit })
236
+ return res.data
237
+ },
238
+ }
239
+
240
+ // Deployments API
241
+ export const deploymentsApi = {
242
+ list: async (projectId: string) => {
243
+ const res = await api.get(`/api/projects/${projectId}/deployments`)
244
+ return res.data
245
+ },
246
+
247
+ create: async (projectId: string, data: { version: string; description?: string }) => {
248
+ const res = await api.post(`/api/projects/${projectId}/deployments`, data)
249
+ return res.data
250
+ },
251
+
252
+ get: async (projectId: string, deploymentId: string) => {
253
+ const res = await api.get(`/api/projects/${projectId}/deployments/${deploymentId}`)
254
+ return res.data
255
+ },
256
+
257
+ retry: async (projectId: string, deploymentId: string) => {
258
+ const res = await api.post(`/api/projects/${projectId}/deployments/${deploymentId}/retry`)
259
+ return res.data
260
+ },
261
+ }
@@ -37,7 +37,11 @@ const RUNTIMES = [
37
37
  { value: 'go121', label: 'Go 1.21' },
38
38
  ]
39
39
 
40
- export function EdgeFunctions() {
40
+ interface EdgeFunctionsProps {
41
+ projectId: string
42
+ }
43
+
44
+ export function EdgeFunctions({ projectId }: EdgeFunctionsProps) {
41
45
  const queryClient = useQueryClient()
42
46
  const toast = useToast()
43
47
 
@@ -60,21 +64,21 @@ export function EdgeFunctions() {
60
64
 
61
65
  // Fetch functions
62
66
  const { data: functionsData, isLoading } = useQuery({
63
- queryKey: ['edge-functions'],
67
+ queryKey: ['edge-functions', projectId],
64
68
  queryFn: async () => {
65
- const res = await fetch('/api/functions')
69
+ const res = await fetch(`/api/projects/${projectId}/functions`)
66
70
  if (!res.ok) throw new Error('Failed to fetch functions')
67
71
  return res.json()
68
72
  }
69
73
  })
70
74
 
71
- const functions = functionsData?.functions || []
75
+ const functions = functionsData?.functions || functionsData || []
72
76
  const stats = functionsData?.stats || { total: 0, deployed: 0, draft: 0, invocations: 0 }
73
77
 
74
78
  // Create function mutation
75
79
  const createMutation = useMutation({
76
80
  mutationFn: async () => {
77
- const res = await fetch('/api/functions', {
81
+ const res = await fetch(`/api/projects/${projectId}/functions`, {
78
82
  method: 'POST',
79
83
  headers: { 'Content-Type': 'application/json' },
80
84
  body: JSON.stringify(newFunction)
@@ -86,7 +90,7 @@ export function EdgeFunctions() {
86
90
  return res.json()
87
91
  },
88
92
  onSuccess: () => {
89
- queryClient.invalidateQueries({ queryKey: ['edge-functions'] })
93
+ queryClient.invalidateQueries({ queryKey: ['edge-functions', projectId] })
90
94
  setShowCreateModal(false)
91
95
  setNewFunction({
92
96
  name: '',
@@ -107,7 +111,7 @@ export function EdgeFunctions() {
107
111
  // Deploy function mutation
108
112
  const deployMutation = useMutation({
109
113
  mutationFn: async (functionId: string) => {
110
- const res = await fetch(`/api/functions/${functionId}/deploy`, {
114
+ const res = await fetch(`/api/projects/${projectId}/functions/${functionId}/deploy`, {
111
115
  method: 'POST'
112
116
  })
113
117
  if (!res.ok) {
@@ -117,7 +121,7 @@ export function EdgeFunctions() {
117
121
  return res.json()
118
122
  },
119
123
  onSuccess: () => {
120
- queryClient.invalidateQueries({ queryKey: ['edge-functions'] })
124
+ queryClient.invalidateQueries({ queryKey: ['edge-functions', projectId] })
121
125
  toast.success('Function deployed successfully')
122
126
  },
123
127
  onError: (error: Error) => {
@@ -128,7 +132,7 @@ export function EdgeFunctions() {
128
132
  // Delete function mutation
129
133
  const deleteMutation = useMutation({
130
134
  mutationFn: async (functionId: string) => {
131
- const res = await fetch(`/api/functions/${functionId}`, {
135
+ const res = await fetch(`/api/projects/${projectId}/functions/${functionId}`, {
132
136
  method: 'DELETE'
133
137
  })
134
138
  if (!res.ok) {
@@ -138,7 +142,7 @@ export function EdgeFunctions() {
138
142
  return res.json()
139
143
  },
140
144
  onSuccess: () => {
141
- queryClient.invalidateQueries({ queryKey: ['edge-functions'] })
145
+ queryClient.invalidateQueries({ queryKey: ['edge-functions', projectId] })
142
146
  toast.success('Function deleted successfully')
143
147
  },
144
148
  onError: (error: Error) => {
@@ -149,10 +153,10 @@ export function EdgeFunctions() {
149
153
  // Fetch logs
150
154
  const fetchLogs = async (functionId: string) => {
151
155
  try {
152
- const res = await fetch(`/api/functions/${functionId}/logs`)
156
+ const res = await fetch(`/api/projects/${projectId}/functions/${functionId}/logs`)
153
157
  if (res.ok) {
154
158
  const data = await res.json()
155
- setFunctionLogs(data.logs || [])
159
+ setFunctionLogs(data.logs || data || [])
156
160
  }
157
161
  } catch (error) {
158
162
  console.error('Failed to fetch logs:', error)
@@ -15,6 +15,9 @@ import { WebsocketModule } from '../websocket/websocket.module';
15
15
  import { HealthModule } from '../health/health.module';
16
16
  import { DatabaseModule } from '../database/database.module';
17
17
  import { AiModule } from '../ai/ai.module';
18
+ import { EdgeFunctionsModule } from '../edge-functions/edge-functions.module';
19
+ import { VectorModule } from '../vector/vector.module';
20
+ import { DeploymentsModule } from '../deployments/deployments.module';
18
21
 
19
22
  // QueueModule is optional - only loaded when Redis is configured
20
23
  let QueueModule: any = null;
@@ -47,7 +50,10 @@ try {
47
50
  DatabaseModule,
48
51
  // Only include QueueModule if Redis is configured
49
52
  ...(process.env.REDIS_HOST && QueueModule ? [QueueModule] : []),
50
- AiModule
53
+ AiModule,
54
+ EdgeFunctionsModule,
55
+ VectorModule,
56
+ DeploymentsModule,
51
57
  ],
52
58
  providers: [
53
59
  {
@@ -0,0 +1,47 @@
1
+ import { Controller, Post, Get, Body, Param, UseGuards } from '@nestjs/common';
2
+ import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
3
+ import { JwtAuthGuard } from '../common/jwt-auth.guard';
4
+ import { TenantGuard } from '../common/tenant.guard';
5
+ import { DeploymentsService } from './deployments.service';
6
+ import { CreateDeploymentDto } from './dto/create-deployment.dto';
7
+
8
+ @ApiTags('deployments')
9
+ @ApiBearerAuth()
10
+ @UseGuards(JwtAuthGuard, TenantGuard)
11
+ @Controller('projects/:projectId/deployments')
12
+ export class DeploymentsController {
13
+ constructor(private readonly deploymentsService: DeploymentsService) {}
14
+
15
+ @Post()
16
+ @ApiOperation({ summary: 'Create a new deployment' })
17
+ async create(
18
+ @Param('projectId') projectId: string,
19
+ @Body() dto: CreateDeploymentDto,
20
+ ) {
21
+ return this.deploymentsService.create(projectId, dto);
22
+ }
23
+
24
+ @Get()
25
+ @ApiOperation({ summary: 'List all deployments' })
26
+ async findAll(@Param('projectId') projectId: string) {
27
+ return this.deploymentsService.findAll(projectId);
28
+ }
29
+
30
+ @Get(':deploymentId')
31
+ @ApiOperation({ summary: 'Get deployment details' })
32
+ async findOne(
33
+ @Param('projectId') projectId: string,
34
+ @Param('deploymentId') deploymentId: string,
35
+ ) {
36
+ return this.deploymentsService.findOne(projectId, deploymentId);
37
+ }
38
+
39
+ @Post(':deploymentId/retry')
40
+ @ApiOperation({ summary: 'Retry a failed deployment' })
41
+ async retry(
42
+ @Param('projectId') projectId: string,
43
+ @Param('deploymentId') deploymentId: string,
44
+ ) {
45
+ return this.deploymentsService.retry(projectId, deploymentId);
46
+ }
47
+ }
@@ -0,0 +1,12 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { DeploymentsController } from './deployments.controller';
3
+ import { DeploymentsService } from './deployments.service';
4
+ import { PrismaModule } from '../prisma/prisma.module';
5
+
6
+ @Module({
7
+ imports: [PrismaModule],
8
+ controllers: [DeploymentsController],
9
+ providers: [DeploymentsService],
10
+ exports: [DeploymentsService],
11
+ })
12
+ export class DeploymentsModule {}
@@ -0,0 +1,88 @@
1
+ import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
2
+ import { PrismaService } from '../prisma/prisma.service';
3
+ import { CreateDeploymentDto } from './dto/create-deployment.dto';
4
+
5
+ @Injectable()
6
+ export class DeploymentsService {
7
+ constructor(private prisma: PrismaService) {}
8
+
9
+ async create(projectId: string, dto: CreateDeploymentDto) {
10
+ // Get project to verify it exists
11
+ const project = await this.prisma.project.findUnique({
12
+ where: { id: projectId },
13
+ });
14
+
15
+ if (!project) {
16
+ throw new NotFoundException('Project not found');
17
+ }
18
+
19
+ const deployment = await this.prisma.deployment.create({
20
+ data: {
21
+ version: dto.version,
22
+ description: dto.description,
23
+ projectId,
24
+ status: 'pending',
25
+ },
26
+ });
27
+
28
+ // Simulate deployment process (in real app, this would trigger CI/CD)
29
+ this.simulateDeployment(deployment.id);
30
+
31
+ return deployment;
32
+ }
33
+
34
+ async findAll(projectId: string) {
35
+ return this.prisma.deployment.findMany({
36
+ where: { projectId },
37
+ orderBy: { createdAt: 'desc' },
38
+ });
39
+ }
40
+
41
+ async findOne(projectId: string, deploymentId: string) {
42
+ const deployment = await this.prisma.deployment.findFirst({
43
+ where: { id: deploymentId, projectId },
44
+ });
45
+
46
+ if (!deployment) {
47
+ throw new NotFoundException('Deployment not found');
48
+ }
49
+
50
+ return deployment;
51
+ }
52
+
53
+ async retry(projectId: string, deploymentId: string) {
54
+ const deployment = await this.findOne(projectId, deploymentId);
55
+
56
+ if (deployment.status !== 'failed') {
57
+ throw new BadRequestException('Only failed deployments can be retried');
58
+ }
59
+
60
+ const updated = await this.prisma.deployment.update({
61
+ where: { id: deploymentId },
62
+ data: { status: 'pending', error: null },
63
+ });
64
+
65
+ // Simulate deployment process
66
+ this.simulateDeployment(deploymentId);
67
+
68
+ return updated;
69
+ }
70
+
71
+ private simulateDeployment(deploymentId: string) {
72
+ // In a real implementation, this would trigger actual deployment
73
+ // For now, we just update the status after a delay
74
+ setTimeout(async () => {
75
+ try {
76
+ await this.prisma.deployment.update({
77
+ where: { id: deploymentId },
78
+ data: {
79
+ status: 'success',
80
+ deployedAt: new Date(),
81
+ },
82
+ });
83
+ } catch {
84
+ // Deployment might have been deleted
85
+ }
86
+ }, 5000);
87
+ }
88
+ }
@@ -0,0 +1,12 @@
1
+ import { IsString, IsOptional, MaxLength } from 'class-validator';
2
+
3
+ export class CreateDeploymentDto {
4
+ @IsString()
5
+ @MaxLength(50)
6
+ version: string;
7
+
8
+ @IsString()
9
+ @IsOptional()
10
+ @MaxLength(500)
11
+ description?: string;
12
+ }
@@ -8,80 +8,80 @@ import { CreateEdgeFunctionDto, UpdateEdgeFunctionDto, ExecuteEdgeFunctionDto }
8
8
  @ApiTags('edge-functions')
9
9
  @ApiBearerAuth()
10
10
  @UseGuards(JwtAuthGuard, TenantGuard)
11
- @Controller('organizations/:orgId/edge-functions')
11
+ @Controller('projects/:projectId/functions')
12
12
  export class EdgeFunctionsController {
13
13
  constructor(private readonly edgeFunctionsService: EdgeFunctionsService) {}
14
14
 
15
15
  @Post()
16
16
  @ApiOperation({ summary: 'Create a new edge function' })
17
17
  async create(
18
- @Param('orgId') orgId: string,
18
+ @Param('projectId') projectId: string,
19
19
  @Body() dto: CreateEdgeFunctionDto,
20
20
  @Request() req: any,
21
21
  ) {
22
- return this.edgeFunctionsService.create(orgId, req.user.id, dto);
22
+ return this.edgeFunctionsService.create(projectId, req.user.id, dto);
23
23
  }
24
24
 
25
25
  @Get()
26
26
  @ApiOperation({ summary: 'List all edge functions' })
27
- async findAll(@Param('orgId') orgId: string) {
28
- return this.edgeFunctionsService.findAll(orgId);
27
+ async findAll(@Param('projectId') projectId: string) {
28
+ return this.edgeFunctionsService.findAll(projectId);
29
29
  }
30
30
 
31
31
  @Get(':functionId')
32
32
  @ApiOperation({ summary: 'Get edge function details' })
33
33
  async findOne(
34
- @Param('orgId') orgId: string,
34
+ @Param('projectId') projectId: string,
35
35
  @Param('functionId') functionId: string,
36
36
  ) {
37
- return this.edgeFunctionsService.findOne(orgId, functionId);
37
+ return this.edgeFunctionsService.findOne(projectId, functionId);
38
38
  }
39
39
 
40
40
  @Put(':functionId')
41
41
  @ApiOperation({ summary: 'Update edge function' })
42
42
  async update(
43
- @Param('orgId') orgId: string,
43
+ @Param('projectId') projectId: string,
44
44
  @Param('functionId') functionId: string,
45
45
  @Body() dto: UpdateEdgeFunctionDto,
46
46
  ) {
47
- return this.edgeFunctionsService.update(orgId, functionId, dto);
47
+ return this.edgeFunctionsService.update(projectId, functionId, dto);
48
48
  }
49
49
 
50
50
  @Delete(':functionId')
51
51
  @ApiOperation({ summary: 'Delete edge function' })
52
52
  async remove(
53
- @Param('orgId') orgId: string,
53
+ @Param('projectId') projectId: string,
54
54
  @Param('functionId') functionId: string,
55
55
  ) {
56
- return this.edgeFunctionsService.remove(orgId, functionId);
56
+ return this.edgeFunctionsService.remove(projectId, functionId);
57
57
  }
58
58
 
59
59
  @Post(':functionId/execute')
60
60
  @ApiOperation({ summary: 'Execute edge function' })
61
61
  async execute(
62
- @Param('orgId') orgId: string,
62
+ @Param('projectId') projectId: string,
63
63
  @Param('functionId') functionId: string,
64
64
  @Body() dto: ExecuteEdgeFunctionDto,
65
65
  @Request() req: any,
66
66
  ) {
67
- return this.edgeFunctionsService.execute(orgId, functionId, dto, req.user);
67
+ return this.edgeFunctionsService.execute(projectId, functionId, dto, req.user);
68
68
  }
69
69
 
70
70
  @Post(':functionId/deploy')
71
71
  @ApiOperation({ summary: 'Deploy edge function to edge network' })
72
72
  async deploy(
73
- @Param('orgId') orgId: string,
73
+ @Param('projectId') projectId: string,
74
74
  @Param('functionId') functionId: string,
75
75
  ) {
76
- return this.edgeFunctionsService.deploy(orgId, functionId);
76
+ return this.edgeFunctionsService.deploy(projectId, functionId);
77
77
  }
78
78
 
79
79
  @Get(':functionId/logs')
80
80
  @ApiOperation({ summary: 'Get edge function execution logs' })
81
81
  async getLogs(
82
- @Param('orgId') orgId: string,
82
+ @Param('projectId') projectId: string,
83
83
  @Param('functionId') functionId: string,
84
84
  ) {
85
- return this.edgeFunctionsService.getLogs(orgId, functionId);
85
+ return this.edgeFunctionsService.getLogs(projectId, functionId);
86
86
  }
87
87
  }
@@ -8,7 +8,7 @@ import * as crypto from 'crypto';
8
8
  export class EdgeFunctionsService {
9
9
  constructor(private prisma: PrismaService) {}
10
10
 
11
- async create(orgId: string, userId: string, dto: CreateEdgeFunctionDto) {
11
+ async create(projectId: string, userId: string, dto: CreateEdgeFunctionDto) {
12
12
  // Validate the function code
13
13
  this.validateCode(dto.code);
14
14
 
@@ -22,7 +22,7 @@ export class EdgeFunctionsService {
22
22
  memory: dto.memory || 128,
23
23
  environment: dto.environment || {},
24
24
  triggers: dto.triggers || [],
25
- organizationId: orgId,
25
+ projectId,
26
26
  createdBy: userId,
27
27
  },
28
28
  });
@@ -30,16 +30,16 @@ export class EdgeFunctionsService {
30
30
  return func;
31
31
  }
32
32
 
33
- async findAll(orgId: string) {
33
+ async findAll(projectId: string) {
34
34
  return this.prisma.edgeFunction.findMany({
35
- where: { organizationId: orgId },
35
+ where: { projectId },
36
36
  orderBy: { createdAt: 'desc' },
37
37
  });
38
38
  }
39
39
 
40
- async findOne(orgId: string, functionId: string) {
40
+ async findOne(projectId: string, functionId: string) {
41
41
  const func = await this.prisma.edgeFunction.findFirst({
42
- where: { id: functionId, organizationId: orgId },
42
+ where: { id: functionId, projectId },
43
43
  });
44
44
 
45
45
  if (!func) {
@@ -49,8 +49,8 @@ export class EdgeFunctionsService {
49
49
  return func;
50
50
  }
51
51
 
52
- async update(orgId: string, functionId: string, dto: UpdateEdgeFunctionDto) {
53
- await this.findOne(orgId, functionId);
52
+ async update(projectId: string, functionId: string, dto: UpdateEdgeFunctionDto) {
53
+ await this.findOne(projectId, functionId);
54
54
 
55
55
  if (dto.code) {
56
56
  this.validateCode(dto.code);
@@ -66,8 +66,8 @@ export class EdgeFunctionsService {
66
66
  });
67
67
  }
68
68
 
69
- async remove(orgId: string, functionId: string) {
70
- await this.findOne(orgId, functionId);
69
+ async remove(projectId: string, functionId: string) {
70
+ await this.findOne(projectId, functionId);
71
71
 
72
72
  await this.prisma.edgeFunction.delete({
73
73
  where: { id: functionId },
@@ -76,8 +76,8 @@ export class EdgeFunctionsService {
76
76
  return { success: true };
77
77
  }
78
78
 
79
- async execute(orgId: string, functionId: string, dto: ExecuteEdgeFunctionDto, user: any) {
80
- const func = await this.findOne(orgId, functionId);
79
+ async execute(projectId: string, functionId: string, dto: ExecuteEdgeFunctionDto, user: any) {
80
+ const func = await this.findOne(projectId, functionId);
81
81
 
82
82
  if (func.status !== 'deployed') {
83
83
  throw new BadRequestException('Function must be deployed before execution');
@@ -93,7 +93,7 @@ export class EdgeFunctionsService {
93
93
  result = await this.runInSandbox(func.code, {
94
94
  ...dto.payload,
95
95
  user: { id: user.id, email: user.email },
96
- organizationId: orgId,
96
+ projectId,
97
97
  env: func.environment as Record<string, string>,
98
98
  });
99
99
  } catch (err: any) {
@@ -123,8 +123,8 @@ export class EdgeFunctionsService {
123
123
  return result;
124
124
  }
125
125
 
126
- async deploy(orgId: string, functionId: string) {
127
- const func = await this.findOne(orgId, functionId);
126
+ async deploy(projectId: string, functionId: string) {
127
+ const func = await this.findOne(projectId, functionId);
128
128
 
129
129
  // Generate deployment info
130
130
  const deploymentId = crypto.randomUUID();
@@ -147,8 +147,8 @@ export class EdgeFunctionsService {
147
147
  };
148
148
  }
149
149
 
150
- async getLogs(orgId: string, functionId: string) {
151
- await this.findOne(orgId, functionId);
150
+ async getLogs(projectId: string, functionId: string) {
151
+ await this.findOne(projectId, functionId);
152
152
 
153
153
  return this.prisma.edgeFunctionLog.findMany({
154
154
  where: { functionId },
@@ -7,27 +7,30 @@ import { UpdateProjectDto } from './dto/update-project.dto';
7
7
  export class ProjectsService {
8
8
  constructor(private readonly prisma: PrismaService) {}
9
9
 
10
- async list(orgId: string) {
10
+ async list(organizationId: string) {
11
11
  const projects = await this.prisma.project.findMany({
12
- where: { orgId },
12
+ where: { organizationId },
13
13
  orderBy: { createdAt: 'desc' }
14
14
  });
15
15
 
16
16
  return { projects };
17
17
  }
18
18
 
19
- async create(orgId: string, dto: CreateProjectDto) {
19
+ async create(organizationId: string, dto: CreateProjectDto) {
20
+ // Generate slug from project name
21
+ const slug = dto.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
22
+
20
23
  const project = await this.prisma.project.create({
21
- data: { orgId, name: dto.name }
24
+ data: { organizationId, name: dto.name, slug }
22
25
  });
23
26
 
24
27
  return { project };
25
28
  }
26
29
 
27
- async update(orgId: string, id: string, dto: UpdateProjectDto) {
30
+ async update(organizationId: string, id: string, dto: UpdateProjectDto) {
28
31
  const project = await this.prisma.project.findUnique({ where: { id } });
29
32
  if (!project) throw new NotFoundException('Project not found');
30
- if (project.orgId !== orgId) throw new ForbiddenException('Forbidden');
33
+ if (project.organizationId !== organizationId) throw new ForbiddenException('Forbidden');
31
34
 
32
35
  const updated = await this.prisma.project.update({
33
36
  where: { id },
@@ -37,10 +40,10 @@ export class ProjectsService {
37
40
  return { project: updated };
38
41
  }
39
42
 
40
- async remove(orgId: string, id: string) {
43
+ async remove(organizationId: string, id: string) {
41
44
  const project = await this.prisma.project.findUnique({ where: { id } });
42
45
  if (!project) throw new NotFoundException('Project not found');
43
- if (project.orgId !== orgId) throw new ForbiddenException('Forbidden');
46
+ if (project.organizationId !== organizationId) throw new ForbiddenException('Forbidden');
44
47
 
45
48
  await this.prisma.project.delete({ where: { id } });
46
49
  return { deleted: true };
@@ -8,88 +8,88 @@ import { CreateEmbeddingDto, SearchSimilarDto, CreateCollectionDto } from './dto
8
8
  @ApiTags('vector')
9
9
  @ApiBearerAuth()
10
10
  @UseGuards(JwtAuthGuard, TenantGuard)
11
- @Controller('organizations/:orgId/vector')
11
+ @Controller('projects/:projectId/vector')
12
12
  export class VectorController {
13
13
  constructor(private readonly vectorService: VectorService) {}
14
14
 
15
15
  @Post('collections')
16
16
  @ApiOperation({ summary: 'Create a vector collection' })
17
17
  async createCollection(
18
- @Param('orgId') orgId: string,
18
+ @Param('projectId') projectId: string,
19
19
  @Body() dto: CreateCollectionDto,
20
20
  ) {
21
- return this.vectorService.createCollection(orgId, dto);
21
+ return this.vectorService.createCollection(projectId, dto);
22
22
  }
23
23
 
24
24
  @Get('collections')
25
25
  @ApiOperation({ summary: 'List all collections' })
26
- async listCollections(@Param('orgId') orgId: string) {
27
- return this.vectorService.listCollections(orgId);
26
+ async listCollections(@Param('projectId') projectId: string) {
27
+ return this.vectorService.listCollections(projectId);
28
28
  }
29
29
 
30
30
  @Post('embeddings')
31
31
  @ApiOperation({ summary: 'Create embedding from text' })
32
32
  async createEmbedding(
33
- @Param('orgId') orgId: string,
33
+ @Param('projectId') projectId: string,
34
34
  @Body() dto: CreateEmbeddingDto,
35
35
  @Request() req: any,
36
36
  ) {
37
- return this.vectorService.createEmbedding(orgId, req.user.id, dto);
37
+ return this.vectorService.createEmbedding(projectId, req.user.id, dto);
38
38
  }
39
39
 
40
40
  @Post('embeddings/batch')
41
41
  @ApiOperation({ summary: 'Create multiple embeddings' })
42
42
  async createEmbeddingsBatch(
43
- @Param('orgId') orgId: string,
43
+ @Param('projectId') projectId: string,
44
44
  @Body() items: CreateEmbeddingDto[],
45
45
  @Request() req: any,
46
46
  ) {
47
- return this.vectorService.createEmbeddingsBatch(orgId, req.user.id, items);
47
+ return this.vectorService.createEmbeddingsBatch(projectId, req.user.id, items);
48
48
  }
49
49
 
50
50
  @Post('search')
51
51
  @ApiOperation({ summary: 'Search similar vectors' })
52
52
  async searchSimilar(
53
- @Param('orgId') orgId: string,
53
+ @Param('projectId') projectId: string,
54
54
  @Body() dto: SearchSimilarDto,
55
55
  ) {
56
- return this.vectorService.searchSimilar(orgId, dto);
56
+ return this.vectorService.searchSimilar(projectId, dto);
57
57
  }
58
58
 
59
59
  @Get('embeddings/:id')
60
60
  @ApiOperation({ summary: 'Get embedding by ID' })
61
61
  async getEmbedding(
62
- @Param('orgId') orgId: string,
62
+ @Param('projectId') projectId: string,
63
63
  @Param('id') id: string,
64
64
  ) {
65
- return this.vectorService.getEmbedding(orgId, id);
65
+ return this.vectorService.getEmbedding(projectId, id);
66
66
  }
67
67
 
68
68
  @Post('embeddings/:id')
69
69
  @ApiOperation({ summary: 'Update embedding metadata' })
70
70
  async updateEmbedding(
71
- @Param('orgId') orgId: string,
71
+ @Param('projectId') projectId: string,
72
72
  @Param('id') id: string,
73
73
  @Body() metadata: Record<string, any>,
74
74
  ) {
75
- return this.vectorService.updateEmbedding(orgId, id, metadata);
75
+ return this.vectorService.updateEmbedding(projectId, id, metadata);
76
76
  }
77
77
 
78
78
  @Post('embeddings/:id/delete')
79
79
  @ApiOperation({ summary: 'Delete embedding' })
80
80
  async deleteEmbedding(
81
- @Param('orgId') orgId: string,
81
+ @Param('projectId') projectId: string,
82
82
  @Param('id') id: string,
83
83
  ) {
84
- return this.vectorService.deleteEmbedding(orgId, id);
84
+ return this.vectorService.deleteEmbedding(projectId, id);
85
85
  }
86
86
 
87
87
  @Post('query')
88
88
  @ApiOperation({ summary: 'Natural language query with RAG' })
89
89
  async query(
90
- @Param('orgId') orgId: string,
90
+ @Param('projectId') projectId: string,
91
91
  @Body() body: { query: string; collection?: string; limit?: number },
92
92
  ) {
93
- return this.vectorService.query(orgId, body.query, body.collection, body.limit);
93
+ return this.vectorService.query(projectId, body.query, body.collection, body.limit);
94
94
  }
95
95
  }
@@ -14,7 +14,7 @@ export class VectorService {
14
14
  this.openaiApiKey = this.config.get('OPENAI_API_KEY');
15
15
  }
16
16
 
17
- async createCollection(orgId: string, dto: CreateCollectionDto) {
17
+ async createCollection(projectId: string, dto: CreateCollectionDto) {
18
18
  // Create the collection table with pgvector extension
19
19
  await this.prisma.$executeRaw`
20
20
  CREATE TABLE IF NOT EXISTS vector_embeddings_${dto.name} (
@@ -22,7 +22,7 @@ export class VectorService {
22
22
  content TEXT NOT NULL,
23
23
  embedding vector(1536),
24
24
  metadata JSONB DEFAULT '{}',
25
- "organizationId" UUID NOT NULL,
25
+ "projectId" UUID NOT NULL,
26
26
  "createdBy" UUID NOT NULL,
27
27
  "createdAt" TIMESTAMP DEFAULT NOW(),
28
28
  "updatedAt" TIMESTAMP DEFAULT NOW()
@@ -42,26 +42,26 @@ export class VectorService {
42
42
  description: dto.description,
43
43
  dimension: dto.dimension || 1536,
44
44
  embeddingModel: dto.embeddingModel || 'text-embedding-3-small',
45
- organizationId: orgId,
45
+ projectId,
46
46
  },
47
47
  });
48
48
 
49
49
  return collection;
50
50
  }
51
51
 
52
- async listCollections(orgId: string) {
52
+ async listCollections(projectId: string) {
53
53
  return this.prisma.vectorCollection.findMany({
54
- where: { organizationId: orgId },
54
+ where: { projectId },
55
55
  orderBy: { createdAt: 'desc' },
56
56
  });
57
57
  }
58
58
 
59
- async createEmbedding(orgId: string, userId: string, dto: CreateEmbeddingDto) {
59
+ async createEmbedding(projectId: string, userId: string, dto: CreateEmbeddingDto) {
60
60
  // Generate embedding using OpenAI
61
61
  const embedding = await this.generateEmbedding(dto.content);
62
62
 
63
63
  const collection = await this.prisma.vectorCollection.findFirst({
64
- where: { id: dto.collectionId, organizationId: orgId },
64
+ where: { id: dto.collectionId, projectId },
65
65
  });
66
66
 
67
67
  if (!collection) {
@@ -70,28 +70,28 @@ export class VectorService {
70
70
 
71
71
  // Insert into collection table
72
72
  const result = await this.prisma.$queryRaw`
73
- INSERT INTO vector_embeddings_${collection.name} (content, embedding, metadata, "organizationId", "createdBy")
74
- VALUES (${dto.content}, ${`[${embedding.join(',')}]`}::vector, ${dto.metadata || '{}'}::jsonb, ${orgId}::uuid, ${userId}::uuid)
73
+ INSERT INTO vector_embeddings_${collection.name} (content, embedding, metadata, "projectId", "createdBy")
74
+ VALUES (${dto.content}, ${`[${embedding.join(',')}]`}::vector, ${dto.metadata || '{}'}::jsonb, ${projectId}::uuid, ${userId}::uuid)
75
75
  RETURNING id, content, metadata, "createdAt";
76
76
  `;
77
77
 
78
78
  return (result as { id: string; content: string; metadata: any; createdAt: Date }[])[0];
79
79
  }
80
80
 
81
- async createEmbeddingsBatch(orgId: string, userId: string, items: CreateEmbeddingDto[]) {
81
+ async createEmbeddingsBatch(projectId: string, userId: string, items: CreateEmbeddingDto[]) {
82
82
  const results: any[] = [];
83
83
  for (const item of items) {
84
- const result = await this.createEmbedding(orgId, userId, item);
84
+ const result = await this.createEmbedding(projectId, userId, item);
85
85
  results.push(result as any);
86
86
  }
87
87
  return results;
88
88
  }
89
89
 
90
- async searchSimilar(orgId: string, dto: SearchSimilarDto) {
90
+ async searchSimilar(projectId: string, dto: SearchSimilarDto) {
91
91
  const queryEmbedding = await this.generateEmbedding(dto.query);
92
92
 
93
93
  const collection = await this.prisma.vectorCollection.findFirst({
94
- where: { id: dto.collectionId, organizationId: orgId },
94
+ where: { id: dto.collectionId, projectId },
95
95
  });
96
96
 
97
97
  if (!collection) {
@@ -110,7 +110,7 @@ export class VectorService {
110
110
  "createdAt",
111
111
  1 - (embedding <=> ${`[${queryEmbedding.join(',')}]`}::vector) as similarity
112
112
  FROM vector_embeddings_${collection.name}
113
- WHERE "organizationId" = ${orgId}::uuid
113
+ WHERE "projectId" = ${projectId}::uuid
114
114
  AND 1 - (embedding <=> ${`[${queryEmbedding.join(',')}]`}::vector) > ${threshold}
115
115
  ORDER BY embedding <=> ${`[${queryEmbedding.join(',')}]`}::vector
116
116
  LIMIT ${limit};
@@ -119,15 +119,15 @@ export class VectorService {
119
119
  return results;
120
120
  }
121
121
 
122
- async getEmbedding(orgId: string, id: string) {
123
- const collections = await this.listCollections(orgId);
122
+ async getEmbedding(projectId: string, id: string) {
123
+ const collections = await this.listCollections(projectId);
124
124
 
125
125
  for (const collection of collections) {
126
126
  try {
127
127
  const result = await this.prisma.$queryRaw`
128
128
  SELECT id, content, metadata, "createdAt", "updatedAt"
129
129
  FROM vector_embeddings_${collection.name}
130
- WHERE id = ${id}::uuid AND "organizationId" = ${orgId}::uuid
130
+ WHERE id = ${id}::uuid AND "projectId" = ${projectId}::uuid
131
131
  `;
132
132
  if ((result as any[]).length > 0) {
133
133
  return { ...(result as any[])[0], collection: collection.name };
@@ -140,32 +140,32 @@ export class VectorService {
140
140
  throw new NotFoundException('Embedding not found');
141
141
  }
142
142
 
143
- async updateEmbedding(orgId: string, id: string, metadata: Record<string, any>) {
144
- const embedding = await this.getEmbedding(orgId, id);
143
+ async updateEmbedding(projectId: string, id: string, metadata: Record<string, any>) {
144
+ const embedding = await this.getEmbedding(projectId, id);
145
145
 
146
146
  await this.prisma.$executeRaw`
147
147
  UPDATE vector_embeddings_${embedding.collection}
148
148
  SET metadata = ${metadata}::jsonb, "updatedAt" = NOW()
149
- WHERE id = ${id}::uuid AND "organizationId" = ${orgId}::uuid
149
+ WHERE id = ${id}::uuid AND "projectId" = ${projectId}::uuid
150
150
  `;
151
151
 
152
- return this.getEmbedding(orgId, id);
152
+ return this.getEmbedding(projectId, id);
153
153
  }
154
154
 
155
- async deleteEmbedding(orgId: string, id: string) {
156
- const embedding = await this.getEmbedding(orgId, id);
155
+ async deleteEmbedding(projectId: string, id: string) {
156
+ const embedding = await this.getEmbedding(projectId, id);
157
157
 
158
158
  await this.prisma.$executeRaw`
159
159
  DELETE FROM vector_embeddings_${embedding.collection}
160
- WHERE id = ${id}::uuid AND "organizationId" = ${orgId}::uuid
160
+ WHERE id = ${id}::uuid AND "projectId" = ${projectId}::uuid
161
161
  `;
162
162
 
163
163
  return { success: true };
164
164
  }
165
165
 
166
- async query(orgId: string, query: string, collectionName?: string, limit: number = 5) {
166
+ async query(projectId: string, query: string, collectionName?: string, limit: number = 5) {
167
167
  // RAG-style query: search similar and return context
168
- let collections = await this.listCollections(orgId);
168
+ let collections = await this.listCollections(projectId);
169
169
 
170
170
  if (collectionName) {
171
171
  collections = collections.filter(c => c.name === collectionName);
@@ -174,7 +174,7 @@ export class VectorService {
174
174
  const contexts: any[] = [];
175
175
 
176
176
  for (const collection of collections) {
177
- const results = await this.searchSimilar(orgId, {
177
+ const results = await this.searchSimilar(projectId, {
178
178
  query,
179
179
  collectionId: collection.id,
180
180
  limit,