spfn 0.2.0-beta.47 → 0.2.0-beta.49
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/LICENSE +1 -1
- package/README.md +309 -211
- package/dist/index.js +442 -1678
- package/dist/templates/.dockerignore +1 -0
- package/dist/templates/Dockerfile +1 -1
- package/dist/templates/lib/api-client.ts +1 -1
- package/dist/templates/server/config/env.config.ts +1 -1
- package/dist/templates/server/entities/config.ts +1 -1
- package/dist/templates/server/entities/example.entity.ts +1 -1
- package/dist/templates/server/repositories/example.repository.ts +1 -1
- package/dist/templates/server/router.ts +2 -3
- package/dist/templates/server/routes/examples.ts +2 -12
- package/dist/templates/server/routes/health.ts +1 -1
- package/dist/templates/server/routes/root.ts +1 -2
- package/dist/templates/server/server.config.ts +2 -2
- package/package.json +3 -3
- package/dist/commands/generate/templates/contract.template +0 -58
- package/dist/commands/generate/templates/entity.template +0 -28
- package/dist/commands/generate/templates/init-migration.template +0 -3
- package/dist/commands/generate/templates/repository.template +0 -56
- package/dist/commands/generate/templates/route.template +0 -65
- package/dist/commands/generate/templates/schema.template +0 -13
- package/dist/templates/Dockerfile.optimized +0 -67
- package/dist/templates/config/index.ts +0 -22
- package/dist/templates/config/schema.ts +0 -42
|
@@ -1 +1 @@
|
|
|
1
|
-
export { examples } from './example.entity';
|
|
1
|
+
export { examples } from './example.entity';
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
/**
|
|
3
2
|
* Application Router
|
|
4
3
|
*
|
|
@@ -20,10 +19,10 @@ export const appRouter = defineRouter({
|
|
|
20
19
|
getExample,
|
|
21
20
|
createExample,
|
|
22
21
|
updateExample,
|
|
23
|
-
deleteExample
|
|
22
|
+
deleteExample,
|
|
24
23
|
});
|
|
25
24
|
|
|
26
25
|
/**
|
|
27
26
|
* Router type for client usage
|
|
28
27
|
*/
|
|
29
|
-
export type AppRouter = typeof appRouter;
|
|
28
|
+
export type AppRouter = typeof appRouter;
|
|
@@ -39,26 +39,16 @@ export const listExamples = route.get('/examples')
|
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* GET /examples/:id - Get single example
|
|
42
|
-
*
|
|
43
|
-
* This route requires authorization header for testing header validation
|
|
44
42
|
*/
|
|
45
43
|
export const getExample = route.get('/examples/:id')
|
|
46
44
|
.input({
|
|
47
|
-
headers: Type.Object({
|
|
48
|
-
test: Type.String({
|
|
49
|
-
description: 'Bearer token for authentication'
|
|
50
|
-
})
|
|
51
|
-
}),
|
|
52
45
|
params: Type.Object({
|
|
53
46
|
id: Type.String(),
|
|
54
47
|
}),
|
|
55
48
|
})
|
|
56
49
|
.handler(async (c) =>
|
|
57
50
|
{
|
|
58
|
-
const { params
|
|
59
|
-
|
|
60
|
-
// Log authorization header for testing
|
|
61
|
-
console.log('Authorization:', headers.test);
|
|
51
|
+
const { params } = await c.data();
|
|
62
52
|
|
|
63
53
|
const example = await exampleRepo.findById(params.id);
|
|
64
54
|
|
|
@@ -137,4 +127,4 @@ export const deleteExample = route.delete('/examples/:id')
|
|
|
137
127
|
success: true,
|
|
138
128
|
id: params.id,
|
|
139
129
|
};
|
|
140
|
-
});
|
|
130
|
+
});
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* Server Configuration
|
|
3
3
|
*/
|
|
4
4
|
import { defineServerConfig } from '@spfn/core/server';
|
|
5
|
-
import { appRouter } from
|
|
5
|
+
import { appRouter } from '@/server/router';
|
|
6
6
|
|
|
7
7
|
export default defineServerConfig()
|
|
8
8
|
.port(8790)
|
|
9
9
|
.host('0.0.0.0')
|
|
10
10
|
.routes(appRouter)
|
|
11
|
-
.build();
|
|
11
|
+
.build();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spfn",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.49",
|
|
4
4
|
"description": "Superfunction CLI - Add SPFN to your Next.js project",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"typescript",
|
|
28
28
|
"type-safe"
|
|
29
29
|
],
|
|
30
|
-
"author": "Ray Im <rayim@
|
|
30
|
+
"author": "Ray Im <rayim@fxy.global>",
|
|
31
31
|
"license": "MIT",
|
|
32
32
|
"repository": {
|
|
33
33
|
"type": "git",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"concurrently": "^9.2.1",
|
|
79
79
|
"tsx": "^4.20.6",
|
|
80
80
|
"typescript": "^5.3.3",
|
|
81
|
-
"@spfn/core": "0.2.0-beta.
|
|
81
|
+
"@spfn/core": "0.2.0-beta.50"
|
|
82
82
|
},
|
|
83
83
|
"scripts": {
|
|
84
84
|
"build": "tsup && node scripts/copy-templates.js",
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* {{PASCAL_NAME}} Contracts
|
|
3
|
-
*
|
|
4
|
-
* Auto-generated by SPFN CLI
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { Type } from '@sinclair/typebox';
|
|
8
|
-
import { createContract } from '@spfn/core/route';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* GET /{{ENTITY_NAME}}
|
|
12
|
-
* List {{PASCAL_NAME}} items
|
|
13
|
-
*/
|
|
14
|
-
export const list{{PASCAL_NAME}}Contract = createContract({
|
|
15
|
-
method: 'GET',
|
|
16
|
-
path: '/{{ENTITY_NAME}}',
|
|
17
|
-
query: Type.Object({
|
|
18
|
-
limit: Type.Optional(Type.Number({ minimum: 1, maximum: 100 })),
|
|
19
|
-
offset: Type.Optional(Type.Number({ minimum: 0 })),
|
|
20
|
-
}),
|
|
21
|
-
responses: {
|
|
22
|
-
200: Type.Object({
|
|
23
|
-
items: Type.Array(
|
|
24
|
-
Type.Object({
|
|
25
|
-
id: Type.Number(),
|
|
26
|
-
name: Type.String(),
|
|
27
|
-
description: Type.Union([Type.String(), Type.Null()]),
|
|
28
|
-
createdAt: Type.String(),
|
|
29
|
-
updatedAt: Type.String(),
|
|
30
|
-
})
|
|
31
|
-
),
|
|
32
|
-
limit: Type.Number(),
|
|
33
|
-
offset: Type.Number(),
|
|
34
|
-
}),
|
|
35
|
-
},
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* POST /{{ENTITY_NAME}}
|
|
40
|
-
* Create new {{PASCAL_NAME}}
|
|
41
|
-
*/
|
|
42
|
-
export const create{{PASCAL_NAME}}Contract = createContract({
|
|
43
|
-
method: 'POST',
|
|
44
|
-
path: '/{{ENTITY_NAME}}',
|
|
45
|
-
body: Type.Object({
|
|
46
|
-
name: Type.String({ minLength: 1 }),
|
|
47
|
-
description: Type.Optional(Type.String()),
|
|
48
|
-
}),
|
|
49
|
-
responses: {
|
|
50
|
-
201: Type.Object({
|
|
51
|
-
id: Type.Number(),
|
|
52
|
-
name: Type.String(),
|
|
53
|
-
description: Type.Union([Type.String(), Type.Null()]),
|
|
54
|
-
createdAt: Type.String(),
|
|
55
|
-
updatedAt: Type.String(),
|
|
56
|
-
}),
|
|
57
|
-
},
|
|
58
|
-
});
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* {{PASCAL_NAME}} Entity
|
|
3
|
-
*
|
|
4
|
-
* Auto-generated by SPFN CLI
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { index, serial, text, timestamp } from 'drizzle-orm/pg-core';
|
|
8
|
-
import { {{SCHEMA_VAR_NAME}} } from './{{SCHEMA_FILE_NAME}}';
|
|
9
|
-
|
|
10
|
-
export const {{ENTITY_NAME}} = {{SCHEMA_VAR_NAME}}.table('{{TABLE_NAME}}', {
|
|
11
|
-
// Primary Key
|
|
12
|
-
id: serial('id').primaryKey(),
|
|
13
|
-
|
|
14
|
-
// Fields
|
|
15
|
-
name: text('name').notNull(),
|
|
16
|
-
description: text('description'),
|
|
17
|
-
|
|
18
|
-
// Timestamps
|
|
19
|
-
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
|
20
|
-
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
|
21
|
-
}, (table) => [
|
|
22
|
-
// Indexes
|
|
23
|
-
index('{{SCHEMA_NAME}}_{{TABLE_NAME}}_name_idx').on(table.name),
|
|
24
|
-
]);
|
|
25
|
-
|
|
26
|
-
// Type inference
|
|
27
|
-
export type {{PASCAL_NAME}} = typeof {{ENTITY_NAME}}.$inferSelect;
|
|
28
|
-
export type New{{PASCAL_NAME}} = typeof {{ENTITY_NAME}}.$inferInsert;
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* {{PASCAL_NAME}} Repository
|
|
3
|
-
*
|
|
4
|
-
* Auto-generated by SPFN CLI
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { findOne, findMany, create, update, deleteById } from '@spfn/core/db';
|
|
8
|
-
import { eq } from 'drizzle-orm';
|
|
9
|
-
import { {{ENTITY_NAME}} } from '@/entities';
|
|
10
|
-
import type { {{PASCAL_NAME}}, New{{PASCAL_NAME}} } from '@/entities';
|
|
11
|
-
|
|
12
|
-
export const {{REPO_NAME}} = {
|
|
13
|
-
/**
|
|
14
|
-
* Find {{PASCAL_NAME}} by ID
|
|
15
|
-
*/
|
|
16
|
-
findById: async (id: number): Promise<{{PASCAL_NAME}} | null> =>
|
|
17
|
-
{
|
|
18
|
-
return findOne({{ENTITY_NAME}}, eq({{ENTITY_NAME}}.id, id));
|
|
19
|
-
},
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Find all {{PASCAL_NAME}} items
|
|
23
|
-
*/
|
|
24
|
-
findMany: async (options?: { limit?: number; offset?: number }): Promise<{{PASCAL_NAME}}[]> =>
|
|
25
|
-
{
|
|
26
|
-
const { limit = 20, offset = 0 } = options || {};
|
|
27
|
-
return findMany({{ENTITY_NAME}}, {
|
|
28
|
-
limit,
|
|
29
|
-
offset,
|
|
30
|
-
});
|
|
31
|
-
},
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Create new {{PASCAL_NAME}}
|
|
35
|
-
*/
|
|
36
|
-
create: async (data: New{{PASCAL_NAME}}): Promise<{{PASCAL_NAME}}> =>
|
|
37
|
-
{
|
|
38
|
-
return create({{ENTITY_NAME}}, data);
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Update {{PASCAL_NAME}} by ID
|
|
43
|
-
*/
|
|
44
|
-
updateById: async (id: number, data: Partial<New{{PASCAL_NAME}}>): Promise<{{PASCAL_NAME}} | null> =>
|
|
45
|
-
{
|
|
46
|
-
return update({{ENTITY_NAME}}, eq({{ENTITY_NAME}}.id, id), data);
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Delete {{PASCAL_NAME}} by ID
|
|
51
|
-
*/
|
|
52
|
-
deleteById: async (id: number): Promise<void> =>
|
|
53
|
-
{
|
|
54
|
-
await deleteById({{ENTITY_NAME}}, id);
|
|
55
|
-
},
|
|
56
|
-
};
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* {{PASCAL_NAME}} Routes
|
|
3
|
-
*
|
|
4
|
-
* Auto-generated by SPFN CLI
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { createApp } from '@spfn/core/route';
|
|
8
|
-
import { Transactional } from '@spfn/core/db';
|
|
9
|
-
import { {{REPO_NAME}} } from '@/repositories/{{KEBAB_NAME}}.repository.js';
|
|
10
|
-
import {
|
|
11
|
-
list{{PASCAL_NAME}}Contract,
|
|
12
|
-
create{{PASCAL_NAME}}Contract,
|
|
13
|
-
} from '@/contracts/{{KEBAB_NAME}}.js';
|
|
14
|
-
|
|
15
|
-
const app = createApp();
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* GET /{{ENTITY_NAME}}
|
|
19
|
-
* List {{PASCAL_NAME}} items
|
|
20
|
-
*/
|
|
21
|
-
app.bind(list{{PASCAL_NAME}}Contract, async (c) =>
|
|
22
|
-
{
|
|
23
|
-
const { limit = 20, offset = 0 } = c.query;
|
|
24
|
-
|
|
25
|
-
const items = await {{REPO_NAME}}.findMany({ limit, offset });
|
|
26
|
-
|
|
27
|
-
return c.json({
|
|
28
|
-
items: items.map((item) => ({
|
|
29
|
-
id: item.id,
|
|
30
|
-
name: item.name,
|
|
31
|
-
description: item.description,
|
|
32
|
-
createdAt: item.createdAt.toISOString(),
|
|
33
|
-
updatedAt: item.updatedAt.toISOString(),
|
|
34
|
-
})),
|
|
35
|
-
limit,
|
|
36
|
-
offset,
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* POST /{{ENTITY_NAME}}
|
|
42
|
-
* Create new {{PASCAL_NAME}}
|
|
43
|
-
*/
|
|
44
|
-
app.bind(create{{PASCAL_NAME}}Contract, [Transactional()], async (c) =>
|
|
45
|
-
{
|
|
46
|
-
const body = await c.data();
|
|
47
|
-
|
|
48
|
-
const item = await {{REPO_NAME}}.create({
|
|
49
|
-
name: body.name,
|
|
50
|
-
description: body.description,
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
return c.json(
|
|
54
|
-
{
|
|
55
|
-
id: item.id,
|
|
56
|
-
name: item.name,
|
|
57
|
-
description: item.description,
|
|
58
|
-
createdAt: item.createdAt.toISOString(),
|
|
59
|
-
updatedAt: item.updatedAt.toISOString(),
|
|
60
|
-
},
|
|
61
|
-
201
|
|
62
|
-
);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
export default app;
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* {{FN_NAME_PASCAL}} Schema
|
|
3
|
-
*
|
|
4
|
-
* Creates isolated PostgreSQL schema for {{PACKAGE_NAME}} tables.
|
|
5
|
-
* IMPORTANT: Export this schema so drizzle-kit can generate CREATE SCHEMA statement.
|
|
6
|
-
*
|
|
7
|
-
* Auto-generated by SPFN CLI
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { createFunctionSchema } from '@spfn/core/db';
|
|
11
|
-
|
|
12
|
-
// ✅ Export schema to auto-generate CREATE SCHEMA in migrations
|
|
13
|
-
export const {{SCHEMA_VAR_NAME}} = createFunctionSchema('{{PACKAGE_NAME}}');
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
# Optimized Production Dockerfile for SPFN
|
|
2
|
-
# Multi-stage build for smaller image size
|
|
3
|
-
|
|
4
|
-
# Stage 1: Dependencies
|
|
5
|
-
FROM node:20-alpine AS deps
|
|
6
|
-
WORKDIR /app
|
|
7
|
-
|
|
8
|
-
# Install pnpm
|
|
9
|
-
RUN corepack enable pnpm
|
|
10
|
-
|
|
11
|
-
# Copy dependency files
|
|
12
|
-
COPY package.json pnpm-lock.yaml* ./
|
|
13
|
-
|
|
14
|
-
# Install ALL dependencies (needed for build)
|
|
15
|
-
RUN pnpm install --frozen-lockfile
|
|
16
|
-
|
|
17
|
-
# Stage 2: Builder
|
|
18
|
-
FROM node:20-alpine AS builder
|
|
19
|
-
WORKDIR /app
|
|
20
|
-
|
|
21
|
-
RUN corepack enable pnpm
|
|
22
|
-
|
|
23
|
-
# Copy dependencies from deps stage
|
|
24
|
-
COPY --from=deps /app/node_modules ./node_modules
|
|
25
|
-
COPY . .
|
|
26
|
-
|
|
27
|
-
# Build both Next.js and SPFN server
|
|
28
|
-
RUN pnpm run spfn:build
|
|
29
|
-
|
|
30
|
-
# Stage 3: Runner (Production)
|
|
31
|
-
FROM node:20-alpine AS runner
|
|
32
|
-
WORKDIR /app
|
|
33
|
-
|
|
34
|
-
ENV NODE_ENV=production
|
|
35
|
-
|
|
36
|
-
RUN corepack enable pnpm
|
|
37
|
-
|
|
38
|
-
# Copy necessary files from builder
|
|
39
|
-
COPY --from=builder /app/package.json ./package.json
|
|
40
|
-
COPY --from=builder /app/pnpm-lock.yaml ./pnpm-lock.yaml
|
|
41
|
-
COPY --from=builder /app/.next ./.next
|
|
42
|
-
COPY --from=builder /app/.spfn ./.spfn
|
|
43
|
-
COPY --from=builder /app/public ./public
|
|
44
|
-
COPY --from=builder /app/next.config.* ./
|
|
45
|
-
COPY --from=builder /app/.env.local.example ./.env.local.example
|
|
46
|
-
|
|
47
|
-
# Install PRODUCTION dependencies only
|
|
48
|
-
RUN pnpm install --frozen-lockfile --prod
|
|
49
|
-
|
|
50
|
-
# Create non-root user for security
|
|
51
|
-
RUN addgroup --system --gid 1001 nodejs
|
|
52
|
-
RUN adduser --system --uid 1001 nextjs
|
|
53
|
-
|
|
54
|
-
# Change ownership
|
|
55
|
-
RUN chown -R nextjs:nodejs /app
|
|
56
|
-
|
|
57
|
-
USER nextjs
|
|
58
|
-
|
|
59
|
-
# Expose both ports
|
|
60
|
-
EXPOSE 3790 8790
|
|
61
|
-
|
|
62
|
-
# Health check for SPFN API
|
|
63
|
-
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
|
64
|
-
CMD node -e "require('http').get('http://localhost:8790/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
|
|
65
|
-
|
|
66
|
-
# Start both Next.js and SPFN server
|
|
67
|
-
CMD ["pnpm", "run", "spfn:start"]
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Core Package Configuration
|
|
3
|
-
*
|
|
4
|
-
* @example
|
|
5
|
-
* ```typescript
|
|
6
|
-
* import { registry } from '@spfn/core/config';
|
|
7
|
-
*
|
|
8
|
-
* const env = registry.validate();
|
|
9
|
-
* console.log(env.DB_POOL_MAX);
|
|
10
|
-
* ```
|
|
11
|
-
*
|
|
12
|
-
* @module config
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { createEnvRegistry } from '@spfn/core/env';
|
|
16
|
-
import { envSchema } from './schema';
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Environment registry
|
|
20
|
-
*/
|
|
21
|
-
const registry = createEnvRegistry(envSchema);
|
|
22
|
-
export const env = registry.validate();
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Environment Variable Schema
|
|
3
|
-
*
|
|
4
|
-
* Centralized schema definition for all environment variables.
|
|
5
|
-
* This provides type safety, validation, and documentation for your application configuration.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```typescript
|
|
9
|
-
* import { envSchema } from './config/schema';
|
|
10
|
-
*
|
|
11
|
-
* // Access validated environment variables
|
|
12
|
-
* console.log(envSchema.DATABASE_URL.description);
|
|
13
|
-
* console.log(envSchema.PORT.default);
|
|
14
|
-
* ```
|
|
15
|
-
*
|
|
16
|
-
* @module config/schema
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import {
|
|
20
|
-
defineEnvSchema,
|
|
21
|
-
envString,
|
|
22
|
-
envNumber,
|
|
23
|
-
} from '@spfn/core/env';
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Application environment variable schema
|
|
27
|
-
*
|
|
28
|
-
* Add your environment variables here with proper types and validation.
|
|
29
|
-
*/
|
|
30
|
-
export const envSchema = defineEnvSchema({
|
|
31
|
-
// Example: API configuration
|
|
32
|
-
API_KEY: envString({
|
|
33
|
-
description: 'Third-party API key',
|
|
34
|
-
required: true,
|
|
35
|
-
}),
|
|
36
|
-
|
|
37
|
-
// Example: Server port
|
|
38
|
-
PORT: envNumber({
|
|
39
|
-
description: 'Server port number',
|
|
40
|
-
default: 8790,
|
|
41
|
-
}),
|
|
42
|
-
});
|