spfn 0.2.0-beta.46 → 0.2.0-beta.48
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/README.md +309 -211
- package/dist/index.js +421 -1666
- package/dist/templates/.dockerignore +1 -0
- package/dist/templates/Dockerfile +1 -1
- package/dist/templates/server/config/env.config.ts +9 -74
- package/dist/templates/server/routes/examples.ts +1 -11
- package/dist/templates/server/routes/root.ts +0 -1
- package/package.json +2 -2
- 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
|
@@ -36,88 +36,23 @@ export const envSchema = defineEnvSchema({
|
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
|
-
* Type-safe Environment
|
|
39
|
+
* Type-safe Environment Variables
|
|
40
40
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
41
|
+
* Variables are lazily validated on access — if a required variable is
|
|
42
|
+
* missing or invalid, an error is thrown at the first access point.
|
|
43
|
+
* This is timing-safe with dotenv loading.
|
|
43
44
|
*
|
|
44
45
|
* @example
|
|
45
46
|
* ```typescript
|
|
46
47
|
* import env from '@/server/config/env.config';
|
|
47
48
|
*
|
|
48
|
-
*
|
|
49
|
-
* const
|
|
50
|
-
*
|
|
51
|
-
* // Get optional variable (returns undefined if missing)
|
|
52
|
-
* const redisUrl = env.get('REDIS_URL');
|
|
53
|
-
*
|
|
54
|
-
* // Get with default value
|
|
55
|
-
* const port = env.get('PORT') ?? 8790;
|
|
49
|
+
* const dbUrl = env.DATABASE_URL; // throws if required and missing
|
|
50
|
+
* const port = env.PORT ?? 8790; // optional with fallback
|
|
56
51
|
* ```
|
|
57
|
-
*/
|
|
58
|
-
export const env = createEnvRegistry(envSchema);
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Validate Environment Variables
|
|
62
52
|
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* or if there are configuration errors.
|
|
53
|
+
* For explicit upfront validation (e.g. in a CLI script), call
|
|
54
|
+
* `createEnvRegistry(envSchema).validateAll()` to get `{ errors, warnings }`.
|
|
66
55
|
*/
|
|
67
|
-
|
|
68
|
-
{
|
|
69
|
-
const validation = env.validate();
|
|
70
|
-
|
|
71
|
-
// Critical errors (missing required variables)
|
|
72
|
-
if (!validation.valid)
|
|
73
|
-
{
|
|
74
|
-
console.error('\n❌ Environment Validation Failed\n');
|
|
75
|
-
console.error('The following required environment variables are missing or invalid:\n');
|
|
76
|
-
|
|
77
|
-
validation.errors.forEach((error) =>
|
|
78
|
-
{
|
|
79
|
-
console.error(` • ${error.key}`);
|
|
80
|
-
console.error(` ${error.message}`);
|
|
81
|
-
|
|
82
|
-
if (error.suggestion)
|
|
83
|
-
{
|
|
84
|
-
console.error(` 💡 ${error.suggestion}`);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
console.error('');
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
console.error('Please check your .env files and ensure all required variables are set.');
|
|
91
|
-
console.error('See .env.example for reference.\n');
|
|
92
|
-
|
|
93
|
-
process.exit(1);
|
|
94
|
-
}
|
|
56
|
+
export const env = createEnvRegistry(envSchema).validate();
|
|
95
57
|
|
|
96
|
-
// Non-critical warnings
|
|
97
|
-
if (validation.warnings.length > 0)
|
|
98
|
-
{
|
|
99
|
-
console.warn('\n⚠️ Environment Warnings:\n');
|
|
100
|
-
|
|
101
|
-
validation.warnings.forEach((warning) =>
|
|
102
|
-
{
|
|
103
|
-
console.warn(` • ${warning.key}: ${warning.message}`);
|
|
104
|
-
|
|
105
|
-
if (warning.suggestion)
|
|
106
|
-
{
|
|
107
|
-
console.warn(` 💡 ${warning.suggestion}`);
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
console.warn('');
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Export the registry as default
|
|
117
|
-
*
|
|
118
|
-
* Import this in your application to access environment variables:
|
|
119
|
-
* ```typescript
|
|
120
|
-
* import env from '@/server/config/env.config';
|
|
121
|
-
* ```
|
|
122
|
-
*/
|
|
123
58
|
export default env;
|
|
@@ -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
|
|
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.48",
|
|
4
4
|
"description": "Superfunction CLI - Add SPFN to your Next.js project",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -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.49"
|
|
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
|
-
});
|