@spfn/core 0.1.0-alpha.7 → 0.1.0-alpha.74
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 +168 -195
- package/dist/auto-loader-JFaZ9gON.d.ts +80 -0
- package/dist/cache/index.d.ts +211 -0
- package/dist/cache/index.js +992 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/client/index.d.ts +92 -92
- package/dist/client/index.js +80 -85
- package/dist/client/index.js.map +1 -1
- package/dist/codegen/generators/index.d.ts +19 -0
- package/dist/codegen/generators/index.js +1500 -0
- package/dist/codegen/generators/index.js.map +1 -0
- package/dist/codegen/index.d.ts +76 -60
- package/dist/codegen/index.js +1486 -736
- package/dist/codegen/index.js.map +1 -1
- package/dist/database-errors-BNNmLTJE.d.ts +86 -0
- package/dist/db/index.d.ts +844 -44
- package/dist/db/index.js +1262 -1309
- package/dist/db/index.js.map +1 -1
- package/dist/env/index.d.ts +508 -0
- package/dist/env/index.js +1106 -0
- package/dist/env/index.js.map +1 -0
- package/dist/error-handler-wjLL3v-a.d.ts +44 -0
- package/dist/errors/index.d.ts +136 -0
- package/dist/errors/index.js +172 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index-DHiAqhKv.d.ts +101 -0
- package/dist/index.d.ts +3 -374
- package/dist/index.js +2394 -2176
- package/dist/index.js.map +1 -1
- package/dist/logger/index.d.ts +94 -0
- package/dist/logger/index.js +774 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/middleware/index.d.ts +33 -0
- package/dist/middleware/index.js +890 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/route/index.d.ts +21 -53
- package/dist/route/index.js +1234 -219
- package/dist/route/index.js.map +1 -1
- package/dist/server/index.d.ts +18 -0
- package/dist/server/index.js +2390 -2058
- package/dist/server/index.js.map +1 -1
- package/dist/types-Dzggq1Yb.d.ts +170 -0
- package/package.json +59 -15
- package/dist/auto-loader-C44TcLmM.d.ts +0 -125
- package/dist/bind-pssq1NRT.d.ts +0 -34
- package/dist/postgres-errors-CY_Es8EJ.d.ts +0 -1703
- package/dist/scripts/index.d.ts +0 -24
- package/dist/scripts/index.js +0 -1201
- package/dist/scripts/index.js.map +0 -1
- package/dist/scripts/templates/api-index.template.txt +0 -10
- package/dist/scripts/templates/api-tag.template.txt +0 -11
- package/dist/scripts/templates/contract.template.txt +0 -87
- package/dist/scripts/templates/entity-type.template.txt +0 -31
- package/dist/scripts/templates/entity.template.txt +0 -19
- package/dist/scripts/templates/index.template.txt +0 -10
- package/dist/scripts/templates/repository.template.txt +0 -37
- package/dist/scripts/templates/routes-id.template.txt +0 -59
- package/dist/scripts/templates/routes-index.template.txt +0 -44
- package/dist/types-SlzTr8ZO.d.ts +0 -143
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ export const getUsersContract = {
|
|
|
58
58
|
// src/server/routes/users/index.ts
|
|
59
59
|
import { createApp } from '@spfn/core/route';
|
|
60
60
|
import { getUsersContract } from './contract.js';
|
|
61
|
-
import {
|
|
61
|
+
import { findMany } from '@spfn/core/db';
|
|
62
62
|
import { users } from '../../entities/users.js';
|
|
63
63
|
|
|
64
64
|
const app = createApp();
|
|
@@ -66,14 +66,11 @@ const app = createApp();
|
|
|
66
66
|
app.bind(getUsersContract, async (c) => {
|
|
67
67
|
const { page = 1, limit = 10 } = c.query;
|
|
68
68
|
|
|
69
|
-
//
|
|
70
|
-
const
|
|
69
|
+
// Use helper function directly - no Repository needed
|
|
70
|
+
const offset = (page - 1) * limit;
|
|
71
|
+
const result = await findMany(users, { limit, offset });
|
|
71
72
|
|
|
72
|
-
|
|
73
|
-
pagination: { page, limit }
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
return c.json(result);
|
|
73
|
+
return c.json({ users: result, total: result.length });
|
|
77
74
|
});
|
|
78
75
|
|
|
79
76
|
export default app;
|
|
@@ -88,7 +85,7 @@ npm run spfn:dev
|
|
|
88
85
|
|
|
89
86
|
## Architecture Pattern
|
|
90
87
|
|
|
91
|
-
|
|
88
|
+
Superfunction follows a **layered architecture** that separates concerns and keeps code maintainable:
|
|
92
89
|
|
|
93
90
|
```
|
|
94
91
|
┌─────────────────────────────────────────┐
|
|
@@ -102,14 +99,14 @@ SPFN follows a **layered architecture** that separates concerns and keeps code m
|
|
|
102
99
|
│ Service Layer │ Business logic
|
|
103
100
|
│ - Orchestrate operations │
|
|
104
101
|
│ - Implement business rules │
|
|
105
|
-
│ - Use
|
|
102
|
+
│ - Use helper functions or custom logic │
|
|
106
103
|
└──────────────┬──────────────────────────┘
|
|
107
104
|
│
|
|
108
105
|
┌──────────────▼──────────────────────────┐
|
|
109
|
-
│
|
|
110
|
-
│ -
|
|
111
|
-
│ - Custom queries
|
|
112
|
-
│ -
|
|
106
|
+
│ Data Access Layer │ Database operations
|
|
107
|
+
│ - Use helper functions (findOne, etc) │
|
|
108
|
+
│ - Custom queries with Drizzle │
|
|
109
|
+
│ - Domain-specific wrappers │
|
|
113
110
|
└──────────────┬──────────────────────────┘
|
|
114
111
|
│
|
|
115
112
|
┌──────────────▼──────────────────────────┐
|
|
@@ -144,134 +141,35 @@ export type Post = typeof posts.$inferSelect;
|
|
|
144
141
|
export type NewPost = typeof posts.$inferInsert;
|
|
145
142
|
```
|
|
146
143
|
|
|
147
|
-
**2.
|
|
144
|
+
**2. Data Access Layer** - Helper functions with domain-specific wrappers
|
|
148
145
|
|
|
149
146
|
```typescript
|
|
150
147
|
// src/server/repositories/posts.repository.ts
|
|
151
|
-
import {
|
|
152
|
-
import {
|
|
153
|
-
import { posts } from '../entities';
|
|
154
|
-
import type { Post } from '../entities';
|
|
155
|
-
|
|
156
|
-
export class PostRepository extends Repository<typeof posts>
|
|
157
|
-
{
|
|
158
|
-
async findBySlug(slug: string): Promise<Post | null>
|
|
159
|
-
{
|
|
160
|
-
return this.findOne(eq(this.table.slug, slug));
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
async findPublished(): Promise<Post[]>
|
|
164
|
-
{
|
|
165
|
-
const results = await this.db
|
|
166
|
-
.select()
|
|
167
|
-
.from(this.table)
|
|
168
|
-
.where(eq(this.table.status, 'published'))
|
|
169
|
-
.orderBy(this.table.createdAt);
|
|
170
|
-
|
|
171
|
-
return results;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
**3. Service Layer** - Business logic (Function-based pattern)
|
|
148
|
+
import { findOne, findMany, create as createHelper } from '@spfn/core/db';
|
|
149
|
+
import { eq, desc } from 'drizzle-orm';
|
|
150
|
+
import { posts, type Post, type NewPost } from '../entities/posts';
|
|
177
151
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
import { ValidationError, DatabaseError, NotFoundError } from '@spfn/core';
|
|
182
|
-
import { posts } from '../entities';
|
|
183
|
-
import { PostRepository } from '../repositories/posts.repository';
|
|
184
|
-
import type { NewPost, Post } from '../entities';
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Create a new post
|
|
188
|
-
*/
|
|
189
|
-
export async function createPost(data: {
|
|
190
|
-
title: string;
|
|
191
|
-
content: string;
|
|
192
|
-
}): Promise<Post> {
|
|
193
|
-
try {
|
|
194
|
-
// Get repository singleton
|
|
195
|
-
const repo = getRepository(posts, PostRepository);
|
|
196
|
-
|
|
197
|
-
// Business logic: Generate slug from title
|
|
198
|
-
const slug = generateSlug(data.title);
|
|
199
|
-
|
|
200
|
-
// Validation: Check if slug already exists
|
|
201
|
-
const existing = await repo.findBySlug(slug);
|
|
202
|
-
if (existing) {
|
|
203
|
-
throw new ValidationError('Post with this title already exists', {
|
|
204
|
-
fields: [{
|
|
205
|
-
path: '/title',
|
|
206
|
-
message: 'A post with this title already exists',
|
|
207
|
-
value: data.title
|
|
208
|
-
}]
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Create post
|
|
213
|
-
return await repo.save({
|
|
214
|
-
...data,
|
|
215
|
-
slug,
|
|
216
|
-
status: 'draft',
|
|
217
|
-
});
|
|
218
|
-
} catch (error) {
|
|
219
|
-
// Re-throw ValidationError as-is
|
|
220
|
-
if (error instanceof ValidationError) {
|
|
221
|
-
throw error;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Wrap unexpected errors
|
|
225
|
-
throw new DatabaseError('Failed to create post', 500, {
|
|
226
|
-
originalError: error instanceof Error ? error.message : String(error)
|
|
227
|
-
});
|
|
228
|
-
}
|
|
152
|
+
// Domain-specific wrappers using helper functions
|
|
153
|
+
export async function findPostBySlug(slug: string): Promise<Post | null> {
|
|
154
|
+
return findOne(posts, { slug });
|
|
229
155
|
}
|
|
230
156
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
const repo = getRepository(posts, PostRepository);
|
|
237
|
-
const post = await repo.update(id, { status: 'published' });
|
|
238
|
-
|
|
239
|
-
if (!post) {
|
|
240
|
-
throw new NotFoundError('Post not found');
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
return post;
|
|
244
|
-
} catch (error) {
|
|
245
|
-
if (error instanceof NotFoundError) {
|
|
246
|
-
throw error;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
throw new DatabaseError('Failed to publish post', 500, {
|
|
250
|
-
originalError: error instanceof Error ? error.message : String(error)
|
|
251
|
-
});
|
|
252
|
-
}
|
|
157
|
+
export async function findPublishedPosts(): Promise<Post[]> {
|
|
158
|
+
return findMany(posts, {
|
|
159
|
+
where: { status: 'published' },
|
|
160
|
+
orderBy: desc(posts.createdAt)
|
|
161
|
+
});
|
|
253
162
|
}
|
|
254
163
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
*/
|
|
258
|
-
export async function getPublishedPosts(): Promise<Post[]> {
|
|
259
|
-
const repo = getRepository(posts, PostRepository);
|
|
260
|
-
return repo.findPublished();
|
|
164
|
+
export async function createPost(data: NewPost): Promise<Post> {
|
|
165
|
+
return createHelper(posts, data);
|
|
261
166
|
}
|
|
262
167
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
*/
|
|
266
|
-
function generateSlug(title: string): string {
|
|
267
|
-
return title
|
|
268
|
-
.toLowerCase()
|
|
269
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
270
|
-
.replace(/(^-|-$)/g, '');
|
|
271
|
-
}
|
|
168
|
+
// Or use helper functions directly in routes for simple cases
|
|
169
|
+
// const post = await findOne(posts, { id: 1 });
|
|
272
170
|
```
|
|
273
171
|
|
|
274
|
-
**
|
|
172
|
+
**3. Routes Layer** - HTTP API
|
|
275
173
|
|
|
276
174
|
```typescript
|
|
277
175
|
// src/server/routes/posts/contracts.ts
|
|
@@ -306,22 +204,40 @@ export const listPostsContract = {
|
|
|
306
204
|
// src/server/routes/posts/index.ts
|
|
307
205
|
import { createApp } from '@spfn/core/route';
|
|
308
206
|
import { Transactional } from '@spfn/core/db';
|
|
309
|
-
import { createPost,
|
|
207
|
+
import { findPostBySlug, createPost, findPublishedPosts } from '../../repositories/posts.repository';
|
|
310
208
|
import { createPostContract, listPostsContract } from './contracts';
|
|
311
209
|
|
|
312
210
|
const app = createApp();
|
|
313
211
|
|
|
314
212
|
// POST /posts - Create new post (with transaction)
|
|
315
|
-
app.bind(createPostContract, Transactional(), async (c) => {
|
|
213
|
+
app.bind(createPostContract, [Transactional()], async (c) => {
|
|
316
214
|
const body = await c.data();
|
|
317
|
-
|
|
215
|
+
|
|
216
|
+
// Generate slug from title
|
|
217
|
+
const slug = body.title.toLowerCase()
|
|
218
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
219
|
+
.replace(/(^-|-$)/g, '');
|
|
220
|
+
|
|
221
|
+
// Check if slug exists
|
|
222
|
+
const existing = await findPostBySlug(slug);
|
|
223
|
+
if (existing) {
|
|
224
|
+
return c.json({ error: 'Post with this title already exists' }, 409);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Create post
|
|
228
|
+
const post = await createPost({
|
|
229
|
+
...body,
|
|
230
|
+
slug,
|
|
231
|
+
status: 'draft'
|
|
232
|
+
});
|
|
233
|
+
|
|
318
234
|
// ✅ Auto-commit on success, auto-rollback on error
|
|
319
235
|
return c.json(post, 201);
|
|
320
236
|
});
|
|
321
237
|
|
|
322
238
|
// GET /posts - List published posts (no transaction needed)
|
|
323
239
|
app.bind(listPostsContract, async (c) => {
|
|
324
|
-
const posts = await
|
|
240
|
+
const posts = await findPublishedPosts();
|
|
325
241
|
return c.json(posts);
|
|
326
242
|
});
|
|
327
243
|
|
|
@@ -340,10 +256,10 @@ export default app;
|
|
|
340
256
|
|
|
341
257
|
**✅ Reusability**
|
|
342
258
|
- Services can be used by multiple routes
|
|
343
|
-
-
|
|
259
|
+
- Data access functions can be shared across services
|
|
344
260
|
|
|
345
261
|
**✅ Type Safety**
|
|
346
|
-
- Types flow from Entity →
|
|
262
|
+
- Types flow from Entity → Data Access → Service → Route
|
|
347
263
|
- Full IDE autocomplete and error checking
|
|
348
264
|
|
|
349
265
|
**✅ Maintainability**
|
|
@@ -355,7 +271,7 @@ export default app;
|
|
|
355
271
|
| Layer | Responsibility | Examples |
|
|
356
272
|
|-------|---------------|----------|
|
|
357
273
|
| **Entity** | Define data structure | Schema, types, constraints |
|
|
358
|
-
| **
|
|
274
|
+
| **Data Access** | Database operations | Helper functions, custom queries, joins |
|
|
359
275
|
| **Service** | Business logic | Validation, orchestration, rules |
|
|
360
276
|
| **Routes** | HTTP interface | Contracts, request handling |
|
|
361
277
|
|
|
@@ -366,21 +282,16 @@ export default app;
|
|
|
366
282
|
- ✅ Export inferred types: `Post`, `NewPost`
|
|
367
283
|
- ✅ Use TEXT with enum for status fields
|
|
368
284
|
|
|
369
|
-
**
|
|
370
|
-
- ✅
|
|
371
|
-
- ✅
|
|
372
|
-
- ✅
|
|
373
|
-
- ✅
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
- ✅ Use function-based pattern (export async functions)
|
|
377
|
-
- ✅ Get repositories via `getRepository()` (singleton)
|
|
378
|
-
- ✅ Implement business logic and validation
|
|
379
|
-
- ✅ Throw descriptive errors
|
|
380
|
-
- ✅ Keep functions focused and small
|
|
285
|
+
**Data Access Layer:**
|
|
286
|
+
- ✅ Use helper functions for simple CRUD: `findOne()`, `create()`, etc.
|
|
287
|
+
- ✅ Create domain-specific wrappers in `src/server/repositories/*.repository.ts`
|
|
288
|
+
- ✅ Export functions (not classes): `export async function findPostBySlug()`
|
|
289
|
+
- ✅ Use object-based where for simple queries: `{ id: 1 }`
|
|
290
|
+
- ✅ Use SQL-based where for complex queries: `and(eq(...), gt(...))`
|
|
291
|
+
- ✅ Full TypeScript type inference from table schemas
|
|
381
292
|
|
|
382
293
|
**Routes Layer:**
|
|
383
|
-
- ✅ Keep handlers thin (delegate to services)
|
|
294
|
+
- ✅ Keep handlers thin (delegate to services/data access)
|
|
384
295
|
- ✅ Define contracts with TypeBox
|
|
385
296
|
- ✅ Use `Transactional()` middleware for write operations
|
|
386
297
|
- ✅ Use `c.data()` for validated input
|
|
@@ -399,61 +310,75 @@ File-based routing with contract validation and type safety.
|
|
|
399
310
|
- Type-safe request/response handling
|
|
400
311
|
- Method-level middleware control (skip auth per HTTP method)
|
|
401
312
|
|
|
402
|
-
### 🗄️ Database
|
|
403
|
-
Drizzle ORM integration with
|
|
313
|
+
### 🗄️ Database
|
|
314
|
+
Drizzle ORM integration with type-safe helper functions and automatic transaction handling.
|
|
404
315
|
|
|
405
316
|
**[→ Read Database Documentation](./src/db/README.md)**
|
|
406
317
|
|
|
407
|
-
**
|
|
408
|
-
-
|
|
409
|
-
-
|
|
410
|
-
-
|
|
318
|
+
**Key Features:**
|
|
319
|
+
- Helper functions for type-safe CRUD operations
|
|
320
|
+
- Automatic transaction handling and read/write separation
|
|
321
|
+
- Schema helpers: `id()`, `timestamps()`, `foreignKey()`
|
|
322
|
+
- Hybrid where clause support (objects or SQL)
|
|
323
|
+
- **Function schema auto-discovery** (see below)
|
|
324
|
+
|
|
325
|
+
### 📦 Function Schema Discovery
|
|
326
|
+
Automatic discovery of database schemas from Superfunction ecosystem functions.
|
|
411
327
|
|
|
412
|
-
|
|
328
|
+
**[→ Read Database Manager Documentation](./src/db/manager/README.md)**
|
|
413
329
|
|
|
414
|
-
|
|
330
|
+
**Key Features:**
|
|
331
|
+
- Zero-config schema discovery from `@spfn/*` functions
|
|
332
|
+
- Functions declare schemas via `package.json`
|
|
333
|
+
- No hard dependencies between functions
|
|
334
|
+
- Efficient scanning (direct dependencies only)
|
|
335
|
+
- Function-specific migration support
|
|
415
336
|
|
|
416
|
-
**
|
|
417
|
-
```typescript
|
|
418
|
-
import { getRepository } from '@spfn/core/db';
|
|
337
|
+
**How it works:**
|
|
419
338
|
|
|
420
|
-
|
|
339
|
+
Functions declare their schemas in `package.json`:
|
|
340
|
+
```json
|
|
341
|
+
{
|
|
342
|
+
"name": "@spfn/cms",
|
|
343
|
+
"spfn": {
|
|
344
|
+
"schemas": ["./dist/entities/*.js"],
|
|
345
|
+
"setupMessage": "📚 Setup guide..."
|
|
346
|
+
}
|
|
347
|
+
}
|
|
421
348
|
```
|
|
422
349
|
|
|
423
|
-
|
|
424
|
-
- ✅ Maximum memory efficiency
|
|
425
|
-
- ⚠️ Requires manual `clearRepositoryCache()` in tests
|
|
426
|
-
- ⚠️ Global state across all requests
|
|
427
|
-
- 📝 **Use for:** Simple projects, prototypes, single-instance services
|
|
428
|
-
|
|
429
|
-
**Request-Scoped (`getScopedRepository` + `RepositoryScope()`)** ⭐ Recommended
|
|
350
|
+
Superfunction automatically discovers and merges these schemas during migration generation:
|
|
430
351
|
```typescript
|
|
431
|
-
import {
|
|
352
|
+
import { getDrizzleConfig } from '@spfn/core'
|
|
432
353
|
|
|
433
|
-
//
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
// Use in routes/services
|
|
437
|
-
const repo = getScopedRepository(users);
|
|
354
|
+
// Auto-discovers all function schemas
|
|
355
|
+
const config = getDrizzleConfig()
|
|
438
356
|
```
|
|
439
357
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
358
|
+
**Install functions with automatic DB setup:**
|
|
359
|
+
```bash
|
|
360
|
+
pnpm spfn add @spfn/cms
|
|
361
|
+
# ✅ Installs function
|
|
362
|
+
# ✅ Generates migrations
|
|
363
|
+
# ✅ Applies migrations
|
|
364
|
+
# ✅ Shows setup guide
|
|
365
|
+
```
|
|
445
366
|
|
|
446
|
-
**
|
|
367
|
+
**Create your own Superfunction packages:**
|
|
368
|
+
```typescript
|
|
369
|
+
// 1. Define entities
|
|
370
|
+
export const myTable = pgTable('my_table', { ... })
|
|
447
371
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
| Best for | Prototypes | Production |
|
|
372
|
+
// 2. Add to package.json
|
|
373
|
+
{
|
|
374
|
+
"spfn": {
|
|
375
|
+
"schemas": ["./dist/entities/*.js"]
|
|
376
|
+
}
|
|
377
|
+
}
|
|
455
378
|
|
|
456
|
-
|
|
379
|
+
// 3. Users install with one command
|
|
380
|
+
// pnpm spfn add @yourcompany/spfn-plugin
|
|
381
|
+
```
|
|
457
382
|
|
|
458
383
|
### 🔄 Transactions
|
|
459
384
|
Automatic transaction management with async context propagation.
|
|
@@ -485,6 +410,30 @@ Server configuration and lifecycle management.
|
|
|
485
410
|
|
|
486
411
|
**[→ Read Server Documentation](./src/server/README.md)**
|
|
487
412
|
|
|
413
|
+
### 📝 Logger
|
|
414
|
+
High-performance logging with multiple transports, sensitive data masking, and automatic validation.
|
|
415
|
+
|
|
416
|
+
**[→ Read Logger Documentation](./src/logger/README.md)**
|
|
417
|
+
|
|
418
|
+
**Key Features:**
|
|
419
|
+
- Adapter pattern (Pino for production, custom for full control)
|
|
420
|
+
- Sensitive data masking (passwords, tokens, API keys)
|
|
421
|
+
- File rotation (date and size-based) with automatic cleanup
|
|
422
|
+
- Configuration validation with clear error messages
|
|
423
|
+
- Multiple transports (Console, File, Slack, Email)
|
|
424
|
+
|
|
425
|
+
### ⚙️ Code Generation
|
|
426
|
+
Automatic code generation with pluggable generators and centralized file watching.
|
|
427
|
+
|
|
428
|
+
**[→ Read Codegen Documentation](./src/codegen/README.md)**
|
|
429
|
+
|
|
430
|
+
**Key Features:**
|
|
431
|
+
- Orchestrator pattern for managing multiple generators
|
|
432
|
+
- Built-in contract generator for type-safe API clients
|
|
433
|
+
- Configuration-based setup (`.spfnrc.json` or `package.json`)
|
|
434
|
+
- Watch mode integrated into `spfn dev`
|
|
435
|
+
- Extensible with custom generators
|
|
436
|
+
|
|
488
437
|
## Module Exports
|
|
489
438
|
|
|
490
439
|
### Main Export
|
|
@@ -501,11 +450,17 @@ import type { RouteContext, RouteContract } from '@spfn/core/route';
|
|
|
501
450
|
### Database
|
|
502
451
|
```typescript
|
|
503
452
|
import {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
453
|
+
getDatabase,
|
|
454
|
+
findOne,
|
|
455
|
+
findMany,
|
|
456
|
+
create,
|
|
457
|
+
createMany,
|
|
458
|
+
updateOne,
|
|
459
|
+
updateMany,
|
|
460
|
+
deleteOne,
|
|
461
|
+
deleteMany,
|
|
462
|
+
count
|
|
507
463
|
} from '@spfn/core/db';
|
|
508
|
-
import type { Pageable, Page } from '@spfn/core/db';
|
|
509
464
|
```
|
|
510
465
|
|
|
511
466
|
### Transactions
|
|
@@ -522,6 +477,11 @@ import {
|
|
|
522
477
|
import { initRedis, getRedis, getRedisRead } from '@spfn/core';
|
|
523
478
|
```
|
|
524
479
|
|
|
480
|
+
### Logger
|
|
481
|
+
```typescript
|
|
482
|
+
import { logger } from '@spfn/core';
|
|
483
|
+
```
|
|
484
|
+
|
|
525
485
|
### Client (for frontend)
|
|
526
486
|
```typescript
|
|
527
487
|
import { ContractClient, createClient } from '@spfn/core/client';
|
|
@@ -545,6 +505,17 @@ REDIS_READ_URL=redis://replica:6379
|
|
|
545
505
|
PORT=8790
|
|
546
506
|
HOST=localhost
|
|
547
507
|
NODE_ENV=development
|
|
508
|
+
|
|
509
|
+
# Server Timeouts (optional, in milliseconds)
|
|
510
|
+
SERVER_TIMEOUT=120000 # Request timeout (default: 120000)
|
|
511
|
+
SERVER_KEEPALIVE_TIMEOUT=65000 # Keep-alive timeout (default: 65000)
|
|
512
|
+
SERVER_HEADERS_TIMEOUT=60000 # Headers timeout (default: 60000)
|
|
513
|
+
SHUTDOWN_TIMEOUT=30000 # Graceful shutdown timeout (default: 30000)
|
|
514
|
+
|
|
515
|
+
# Logger (optional)
|
|
516
|
+
LOGGER_ADAPTER=pino # pino | custom (default: pino)
|
|
517
|
+
LOGGER_FILE_ENABLED=true # Enable file logging (production only)
|
|
518
|
+
LOG_DIR=/var/log/myapp # Log directory (required when file logging enabled)
|
|
548
519
|
```
|
|
549
520
|
|
|
550
521
|
## Requirements
|
|
@@ -568,12 +539,14 @@ npm test -- --coverage # With coverage
|
|
|
568
539
|
|
|
569
540
|
### Guides
|
|
570
541
|
- [File-based Routing](./src/route/README.md)
|
|
571
|
-
- [Database &
|
|
542
|
+
- [Database & Helper Functions](./src/db/README.md)
|
|
572
543
|
- [Transaction Management](./src/db/docs/transactions.md)
|
|
573
544
|
- [Redis Cache](./src/cache/README.md)
|
|
574
545
|
- [Error Handling](./src/errors/README.md)
|
|
575
546
|
- [Middleware](./src/middleware/README.md)
|
|
576
547
|
- [Server Configuration](./src/server/README.md)
|
|
548
|
+
- [Logger](./src/logger/README.md)
|
|
549
|
+
- [Code Generation](./src/codegen/README.md)
|
|
577
550
|
|
|
578
551
|
### API Reference
|
|
579
552
|
- See module-specific README files linked above
|
|
@@ -584,4 +557,4 @@ MIT
|
|
|
584
557
|
|
|
585
558
|
---
|
|
586
559
|
|
|
587
|
-
Part of the [
|
|
560
|
+
Part of the [Superfunction Framework](https://github.com/spfn/spfn)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { MiddlewareHandler, Hono } from 'hono';
|
|
2
|
+
|
|
3
|
+
declare module 'hono' {
|
|
4
|
+
interface ContextVariableMap {
|
|
5
|
+
_skipMiddlewares?: string[];
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* AutoRouteLoader: Simplified File-based Routing System
|
|
10
|
+
*
|
|
11
|
+
* Features:
|
|
12
|
+
* - Auto-discovery: Scans routes directory and auto-registers
|
|
13
|
+
* - Dynamic routes: [id] → :id, [...slug] → *
|
|
14
|
+
* - Statistics: Route registration stats for dashboard
|
|
15
|
+
* - Grouping: Natural grouping by directory structure
|
|
16
|
+
*/
|
|
17
|
+
type RouteInfo = {
|
|
18
|
+
path: string;
|
|
19
|
+
file: string;
|
|
20
|
+
meta?: {
|
|
21
|
+
description?: string;
|
|
22
|
+
tags?: string[];
|
|
23
|
+
auth?: boolean;
|
|
24
|
+
[key: string]: unknown;
|
|
25
|
+
};
|
|
26
|
+
priority: number;
|
|
27
|
+
};
|
|
28
|
+
type RouteStats = {
|
|
29
|
+
total: number;
|
|
30
|
+
byPriority: {
|
|
31
|
+
static: number;
|
|
32
|
+
dynamic: number;
|
|
33
|
+
catchAll: number;
|
|
34
|
+
};
|
|
35
|
+
byTag: Record<string, number>;
|
|
36
|
+
routes: RouteInfo[];
|
|
37
|
+
};
|
|
38
|
+
declare class AutoRouteLoader {
|
|
39
|
+
private routesDir;
|
|
40
|
+
private routes;
|
|
41
|
+
private readonly debug;
|
|
42
|
+
private readonly middlewares;
|
|
43
|
+
constructor(routesDir: string, debug?: boolean, middlewares?: Array<{
|
|
44
|
+
name: string;
|
|
45
|
+
handler: MiddlewareHandler;
|
|
46
|
+
}>);
|
|
47
|
+
load(app: Hono): Promise<RouteStats>;
|
|
48
|
+
/**
|
|
49
|
+
* Load routes from an external directory (e.g., from SPFN function packages)
|
|
50
|
+
* Reads package.json spfn.prefix and mounts routes under that prefix
|
|
51
|
+
*
|
|
52
|
+
* @param app - Hono app instance
|
|
53
|
+
* @param routesDir - Directory containing route handlers
|
|
54
|
+
* @param packageName - Name of the package (for logging)
|
|
55
|
+
* @param prefix - Optional prefix to mount routes under (from package.json spfn.prefix)
|
|
56
|
+
* @returns Route statistics
|
|
57
|
+
*/
|
|
58
|
+
loadExternalRoutes(app: Hono, routesDir: string, packageName: string, prefix?: string): Promise<RouteStats>;
|
|
59
|
+
getStats(): RouteStats;
|
|
60
|
+
private scanFiles;
|
|
61
|
+
private isValidRouteFile;
|
|
62
|
+
private loadRoute;
|
|
63
|
+
private extractContractPaths;
|
|
64
|
+
private calculateContractPriority;
|
|
65
|
+
private validateModule;
|
|
66
|
+
private registerContractBasedMiddlewares;
|
|
67
|
+
private categorizeAndLogError;
|
|
68
|
+
private logStats;
|
|
69
|
+
}
|
|
70
|
+
declare function loadRoutes(app: Hono, options?: {
|
|
71
|
+
routesDir?: string;
|
|
72
|
+
debug?: boolean;
|
|
73
|
+
middlewares?: Array<{
|
|
74
|
+
name: string;
|
|
75
|
+
handler: MiddlewareHandler;
|
|
76
|
+
}>;
|
|
77
|
+
includeFunctionRoutes?: boolean;
|
|
78
|
+
}): Promise<RouteStats>;
|
|
79
|
+
|
|
80
|
+
export { AutoRouteLoader as A, type RouteInfo as R, type RouteStats as a, loadRoutes as l };
|