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.
@@ -14,6 +14,7 @@ dist
14
14
  .env
15
15
  .env.local
16
16
  .env*.local
17
+ .env.server
17
18
 
18
19
  # Git
19
20
  .git
@@ -1,5 +1,5 @@
1
1
  # Production Dockerfile for SPFN
2
- FROM node:20-alpine
2
+ FROM node:22-alpine
3
3
 
4
4
  WORKDIR /app
5
5
 
@@ -77,4 +77,4 @@ export const api = createApi<AppRouter>({
77
77
  * @default false
78
78
  */
79
79
  debug: process.env.NODE_ENV === 'development',
80
- });
80
+ });
@@ -55,4 +55,4 @@ export const envSchema = defineEnvSchema({
55
55
  */
56
56
  export const env = createEnvRegistry(envSchema).validate();
57
57
 
58
- export default env;
58
+ export default env;
@@ -1 +1 @@
1
- export { examples } from './example.entity';
1
+ export { examples } from './example.entity';
@@ -9,4 +9,4 @@ export const examples = pgTable('examples', {
9
9
  });
10
10
 
11
11
  export type Example = typeof examples.$inferSelect;
12
- export type NewExample = typeof examples.$inferInsert;
12
+ export type NewExample = typeof examples.$inferInsert;
@@ -37,4 +37,4 @@ export class ExampleRepository extends BaseRepository
37
37
  {
38
38
  return await this._count(examples);
39
39
  }
40
- }
40
+ }
@@ -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, headers } = await c.data();
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
+ });
@@ -15,4 +15,4 @@ export const getHealth = route.get('/health')
15
15
  timestamp: Date.now(),
16
16
  uptime: process.uptime(),
17
17
  };
18
- });
18
+ });
@@ -16,8 +16,7 @@ export const getRoot = route.get('/')
16
16
  endpoints: {
17
17
  health: '/health',
18
18
  examples: '/examples',
19
- teams: '/teams',
20
19
  },
21
20
  message: 'Welcome to SPFN! Visit /examples for usage examples.',
22
21
  };
23
- });
22
+ });
@@ -2,10 +2,10 @@
2
2
  * Server Configuration
3
3
  */
4
4
  import { defineServerConfig } from '@spfn/core/server';
5
- import { appRouter } from "@/server/router";
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.47",
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@inflike.com>",
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.48"
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,3 +0,0 @@
1
- -- Initial migration for @spfn/{{FN_NAME}}
2
- -- Create schema for {{PASCAL_NAME}} package
3
- CREATE SCHEMA IF NOT EXISTS spfn_{{FN_NAME}};
@@ -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
- });