create-velox-app 0.4.13 → 0.6.23
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 +2 -43
- package/dist/cli.js +23 -13
- package/dist/cli.js.map +1 -1
- package/dist/index.js +26 -8
- package/dist/index.js.map +1 -1
- package/dist/templates/auth.d.ts.map +1 -1
- package/dist/templates/auth.js +24 -0
- package/dist/templates/auth.js.map +1 -1
- package/dist/templates/fullstack.d.ts +15 -0
- package/dist/templates/fullstack.d.ts.map +1 -0
- package/dist/templates/fullstack.js +110 -0
- package/dist/templates/fullstack.js.map +1 -0
- package/dist/templates/index.d.ts +1 -1
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/index.js +27 -5
- package/dist/templates/index.js.map +1 -1
- package/dist/templates/placeholders.d.ts +6 -1
- package/dist/templates/placeholders.d.ts.map +1 -1
- package/dist/templates/placeholders.js +15 -5
- package/dist/templates/placeholders.js.map +1 -1
- package/dist/templates/rsc.d.ts +15 -0
- package/dist/templates/rsc.d.ts.map +1 -0
- package/dist/templates/rsc.js +192 -0
- package/dist/templates/rsc.js.map +1 -0
- package/dist/templates/shared/root.d.ts +1 -0
- package/dist/templates/shared/root.d.ts.map +1 -1
- package/dist/templates/shared/root.js +4 -0
- package/dist/templates/shared/root.js.map +1 -1
- package/dist/templates/spa.d.ts +12 -0
- package/dist/templates/spa.d.ts.map +1 -0
- package/dist/templates/spa.js +101 -0
- package/dist/templates/spa.js.map +1 -0
- package/dist/templates/trpc.d.ts.map +1 -1
- package/dist/templates/trpc.js +16 -0
- package/dist/templates/trpc.js.map +1 -1
- package/dist/templates/types.d.ts +14 -1
- package/dist/templates/types.d.ts.map +1 -1
- package/dist/templates/types.js +35 -10
- package/dist/templates/types.js.map +1 -1
- package/package.json +3 -3
- package/src/templates/source/api/config/auth.ts +2 -2
- package/src/templates/source/api/config/database.ts +44 -10
- package/src/templates/source/api/index.auth.ts +10 -16
- package/src/templates/source/api/index.default.ts +10 -9
- package/src/templates/source/api/index.trpc.ts +9 -28
- package/src/templates/source/api/package.auth.json +7 -6
- package/src/templates/source/api/package.default.json +5 -4
- package/src/templates/source/api/prisma/schema.auth.prisma +3 -2
- package/src/templates/source/api/prisma/schema.default.prisma +3 -2
- package/src/templates/source/api/prisma.config.ts +7 -1
- package/src/templates/source/api/procedures/auth.ts +38 -66
- package/src/templates/source/api/procedures/health.ts +4 -9
- package/src/templates/source/api/procedures/users.auth.ts +7 -11
- package/src/templates/source/api/procedures/users.default.ts +7 -11
- package/src/templates/source/api/router.auth.ts +31 -0
- package/src/templates/source/api/router.default.ts +29 -0
- package/src/templates/source/api/router.trpc.ts +37 -0
- package/src/templates/source/api/router.types.auth.ts +88 -0
- package/src/templates/source/api/router.types.default.ts +73 -0
- package/src/templates/source/api/router.types.trpc.ts +73 -0
- package/src/templates/source/api/routes.auth.ts +66 -0
- package/src/templates/source/api/routes.default.ts +53 -0
- package/src/templates/source/api/schemas/auth.ts +79 -0
- package/src/templates/source/api/schemas/health.ts +21 -0
- package/src/templates/source/api/schemas/user.ts +62 -12
- package/src/templates/source/api/utils/auth.ts +157 -0
- package/src/templates/source/root/.cursorrules +187 -0
- package/src/templates/source/root/CLAUDE.auth.md +264 -0
- package/src/templates/source/root/CLAUDE.default.md +185 -0
- package/src/templates/source/root/package.json +7 -1
- package/src/templates/source/rsc/CLAUDE.md +104 -0
- package/src/templates/source/rsc/app/actions/posts.ts +93 -0
- package/src/templates/source/rsc/app/actions/users.ts +83 -0
- package/src/templates/source/rsc/app/layouts/dashboard.tsx +127 -0
- package/src/templates/source/rsc/app/layouts/marketing.tsx +25 -0
- package/src/templates/source/rsc/app/layouts/minimal.tsx +30 -0
- package/src/templates/source/rsc/app/layouts/root.tsx +241 -0
- package/src/templates/source/rsc/app/pages/(dashboard)/profile.tsx +71 -0
- package/src/templates/source/rsc/app/pages/(dashboard)/settings.tsx +104 -0
- package/src/templates/source/rsc/app/pages/(marketing)/about.tsx +52 -0
- package/src/templates/source/rsc/app/pages/_not-found.tsx +149 -0
- package/src/templates/source/rsc/app/pages/docs/[...slug].tsx +211 -0
- package/src/templates/source/rsc/app/pages/index.tsx +50 -0
- package/src/templates/source/rsc/app/pages/print.tsx +80 -0
- package/src/templates/source/rsc/app/pages/users/[id]/posts/[postId].tsx +89 -0
- package/src/templates/source/rsc/app/pages/users/[id]/posts/index.tsx +76 -0
- package/src/templates/source/rsc/app/pages/users/[id]/posts/new.tsx +79 -0
- package/src/templates/source/rsc/app/pages/users/[id].tsx +64 -0
- package/src/templates/source/rsc/app/pages/users/_layout.tsx +104 -0
- package/src/templates/source/rsc/app/pages/users.tsx +44 -0
- package/src/templates/source/rsc/app.config.ts +12 -0
- package/src/templates/source/rsc/env.example +6 -0
- package/src/templates/source/rsc/gitignore +34 -0
- package/src/templates/source/rsc/package.json +41 -0
- package/src/templates/source/rsc/prisma/schema.prisma +34 -0
- package/src/templates/source/rsc/prisma.config.ts +22 -0
- package/src/templates/source/rsc/public/favicon.svg +4 -0
- package/src/templates/source/rsc/src/api/database.ts +72 -0
- package/src/templates/source/rsc/src/api/handler.ts +53 -0
- package/src/templates/source/rsc/src/api/procedures/health.ts +48 -0
- package/src/templates/source/rsc/src/api/procedures/posts.ts +151 -0
- package/src/templates/source/rsc/src/api/procedures/users.ts +87 -0
- package/src/templates/source/rsc/src/api/schemas/post.ts +53 -0
- package/src/templates/source/rsc/src/api/schemas/user.ts +38 -0
- package/src/templates/source/rsc/src/entry.client.tsx +28 -0
- package/src/templates/source/rsc/src/entry.server.tsx +304 -0
- package/src/templates/source/rsc/tsconfig.json +24 -0
- package/src/templates/source/web/App.module.css +1 -1
- package/src/templates/source/web/api.ts +8 -1
- package/src/templates/source/web/main.tsx +4 -4
- package/src/templates/source/web/package.json +6 -6
- package/src/templates/source/web/routes/__root.tsx +2 -2
- package/src/templates/source/web/routes/index.auth.tsx +3 -0
- package/src/templates/source/web/routes/users.tsx +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/templates/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/templates/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAgBH;;GAEG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAiC;IAC5D,OAAO,EAAE,KAAK;IACd,SAAS,EAAE,KAAK;CACjB,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAAgB;IACnD,IAAI,QAAQ,IAAI,gBAAgB,EAAE,CAAC;QACjC,OAAO,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACpC,CAAC;IACD,IAAI,eAAe,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC9B,OAAO,QAAwB,CAAC;IAClC,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAiBD;;GAEG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAA2C;IACvE,MAAM,EAAE;QACN,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,QAAQ;QACf,IAAI,EAAE,sDAAsD;KAC7D;IACD,UAAU,EAAE;QACV,IAAI,EAAE,YAAY;QAClB,KAAK,EAAE,YAAY;QACnB,IAAI,EAAE,kDAAkD;QACxD,QAAQ,EAAE,IAAI;KACf;IACD,KAAK,EAAE;QACL,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,OAAO;QACd,IAAI,EAAE,6CAA6C;QACnD,QAAQ,EAAE,IAAI;KACf;CACF,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,qBAAqB;IACnC,OAAO,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;AAC1C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,OAAO,QAAQ,IAAI,iBAAiB,CAAC;AACvC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,QAAsB;IACxD,OAAO,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC;AAC/C,CAAC;AA8CD,+EAA+E;AAC/E,oBAAoB;AACpB,+EAA+E;AAE/E;;GAEG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAA2C;IACvE,GAAG,EAAE;QACH,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,WAAW;QAClB,WAAW,EAAE,iDAAiD;QAC9D,IAAI,EAAE,2DAA2D;KAClE;IACD,IAAI,EAAE;QACJ,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,YAAY;QACnB,WAAW,EAAE,yDAAyD;QACtE,IAAI,EAAE,0DAA0D;KACjE;IACD,IAAI,EAAE;QACJ,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,YAAY;QACnB,WAAW,EAAE,gDAAgD;QAC7D,IAAI,EAAE,2DAA2D;KAClE;IACD,GAAG,EAAE;QACH,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,gBAAgB;QACvB,WAAW,EAAE,uDAAuD;QACpE,IAAI,EAAE,6DAA6D;KACpE;CACF,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,qBAAqB;IACnC,OAAO,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;AAC1C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,OAAO,QAAQ,IAAI,iBAAiB,CAAC;AACvC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-velox-app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.23",
|
|
4
4
|
"description": "Project scaffolder for VeloxTS framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@clack/prompts": "0.11.0",
|
|
24
|
-
"ora": "
|
|
24
|
+
"ora": "9.0.0",
|
|
25
25
|
"picocolors": "1.1.1"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@types/node": "25.0.
|
|
28
|
+
"@types/node": "25.0.3",
|
|
29
29
|
"typescript": "5.9.3"
|
|
30
30
|
},
|
|
31
31
|
"keywords": [
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import type { AuthPluginOptions } from '@veloxts/velox';
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import { db } from './database.js';
|
|
13
13
|
|
|
14
14
|
// ============================================================================
|
|
15
15
|
// Environment Variable Validation
|
|
@@ -167,7 +167,7 @@ export function parseUserRoles(rolesJson: string | null): string[] {
|
|
|
167
167
|
// ============================================================================
|
|
168
168
|
|
|
169
169
|
async function userLoader(userId: string) {
|
|
170
|
-
const user = await
|
|
170
|
+
const user = await db.user.findUnique({
|
|
171
171
|
where: { id: userId },
|
|
172
172
|
});
|
|
173
173
|
|
|
@@ -2,21 +2,55 @@
|
|
|
2
2
|
* Database Client (Prisma 7.x)
|
|
3
3
|
*
|
|
4
4
|
* Prisma 7 requires:
|
|
5
|
-
* - Generated client from custom output path
|
|
6
5
|
* - Driver adapter for database connections
|
|
6
|
+
* - Uses standard @prisma/client import path
|
|
7
|
+
*
|
|
8
|
+
* Uses Laravel-style `db` export for consistency.
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
// Runtime imports using createRequire for Node.js v24+ CJS interop
|
|
12
|
+
import { createRequire } from 'node:module';
|
|
13
|
+
|
|
14
|
+
// Type imports (erased at runtime, safe for ESM)
|
|
15
|
+
import type { PrismaBetterSqlite3 as PrismaBetterSqlite3Type } from '@prisma/adapter-better-sqlite3';
|
|
16
|
+
import type { PrismaClient as PrismaClientType } from '@prisma/client';
|
|
10
17
|
|
|
11
|
-
|
|
18
|
+
const require = createRequire(import.meta.url);
|
|
19
|
+
const { PrismaBetterSqlite3 } = require('@prisma/adapter-better-sqlite3') as {
|
|
20
|
+
PrismaBetterSqlite3: typeof PrismaBetterSqlite3Type;
|
|
21
|
+
};
|
|
22
|
+
const { PrismaClient } = require('@prisma/client') as {
|
|
23
|
+
PrismaClient: typeof PrismaClientType;
|
|
24
|
+
};
|
|
12
25
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
26
|
+
declare global {
|
|
27
|
+
// Allow global `var` declarations for hot reload in development
|
|
28
|
+
// eslint-disable-next-line no-var
|
|
29
|
+
var __db: PrismaClient | undefined;
|
|
16
30
|
}
|
|
17
31
|
|
|
18
|
-
|
|
19
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Create a Prisma client instance using the SQLite adapter.
|
|
34
|
+
* Validates that DATABASE_URL is set before creating the client.
|
|
35
|
+
*/
|
|
36
|
+
function createPrismaClient(): PrismaClient {
|
|
37
|
+
const databaseUrl = process.env.DATABASE_URL;
|
|
38
|
+
|
|
39
|
+
if (!databaseUrl) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
'[VeloxTS] DATABASE_URL environment variable is not set. ' +
|
|
42
|
+
'Ensure .env file exists with DATABASE_URL defined.'
|
|
43
|
+
);
|
|
44
|
+
}
|
|
20
45
|
|
|
21
|
-
//
|
|
22
|
-
|
|
46
|
+
// Prisma 7 requires driver adapters for direct connections
|
|
47
|
+
const adapter = new PrismaBetterSqlite3({ url: databaseUrl });
|
|
48
|
+
return new PrismaClient({ adapter });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Use global singleton for hot reload in development
|
|
52
|
+
export const db = globalThis.__db ?? createPrismaClient();
|
|
53
|
+
|
|
54
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
55
|
+
globalThis.__db = db;
|
|
56
|
+
}
|
|
@@ -4,24 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
import 'dotenv/config';
|
|
6
6
|
|
|
7
|
-
import { authPlugin, databasePlugin,
|
|
7
|
+
import { authPlugin, databasePlugin, rest, veloxApp } from '@veloxts/velox';
|
|
8
8
|
|
|
9
9
|
import { config } from './config/app.js';
|
|
10
10
|
import { authConfig } from './config/auth.js';
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
import {
|
|
14
|
-
import { userProcedures } from './procedures/users.js';
|
|
11
|
+
import { db } from './config/database.js';
|
|
12
|
+
// Import router definition (type-only safe for frontend imports)
|
|
13
|
+
import { collections } from './router.js';
|
|
15
14
|
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const router = { auth: authProcedures, health: healthProcedures, users: userProcedures };
|
|
21
|
-
export type AppRouter = typeof router;
|
|
22
|
-
|
|
23
|
-
// Route mappings for frontend client - imported directly, no manual duplication needed
|
|
24
|
-
export const routes = extractRoutes(collections);
|
|
15
|
+
// Re-export AppRouter and routes for backward compatibility
|
|
16
|
+
// Frontend should import from ./router.js directly for type safety
|
|
17
|
+
export type { AppRouter } from './router.js';
|
|
18
|
+
export { routes } from './router.js';
|
|
25
19
|
|
|
26
20
|
const app = await veloxApp({
|
|
27
21
|
port: config.port,
|
|
@@ -29,7 +23,7 @@ const app = await veloxApp({
|
|
|
29
23
|
logger: config.logger,
|
|
30
24
|
});
|
|
31
25
|
|
|
32
|
-
await app.register(databasePlugin({ client:
|
|
26
|
+
await app.register(databasePlugin({ client: db }));
|
|
33
27
|
await app.register(authPlugin(authConfig));
|
|
34
28
|
|
|
35
29
|
app.routes(
|
|
@@ -54,7 +48,7 @@ const shutdown = async () => {
|
|
|
54
48
|
isShuttingDown = true;
|
|
55
49
|
|
|
56
50
|
try {
|
|
57
|
-
await
|
|
51
|
+
await db.$disconnect();
|
|
58
52
|
} catch {
|
|
59
53
|
// Ignore disconnect errors during shutdown
|
|
60
54
|
}
|
|
@@ -7,13 +7,14 @@ import 'dotenv/config';
|
|
|
7
7
|
import { databasePlugin, rest, veloxApp } from '@veloxts/velox';
|
|
8
8
|
|
|
9
9
|
import { config } from './config/app.js';
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
import {
|
|
10
|
+
import { db } from './config/database.js';
|
|
11
|
+
// Import router definition (type-only safe for frontend imports)
|
|
12
|
+
import { collections } from './router.js';
|
|
13
13
|
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
export type AppRouter
|
|
14
|
+
// Re-export AppRouter and routes for backward compatibility
|
|
15
|
+
// Frontend should import from ./router.js directly for type safety
|
|
16
|
+
export type { AppRouter } from './router.js';
|
|
17
|
+
export { routes } from './router.js';
|
|
17
18
|
|
|
18
19
|
const app = await veloxApp({
|
|
19
20
|
port: config.port,
|
|
@@ -21,10 +22,10 @@ const app = await veloxApp({
|
|
|
21
22
|
logger: config.logger,
|
|
22
23
|
});
|
|
23
24
|
|
|
24
|
-
await app.register(databasePlugin({ client:
|
|
25
|
+
await app.register(databasePlugin({ client: db }));
|
|
25
26
|
|
|
26
27
|
app.routes(
|
|
27
|
-
rest(
|
|
28
|
+
rest(collections, {
|
|
28
29
|
prefix: config.apiPrefix,
|
|
29
30
|
})
|
|
30
31
|
);
|
|
@@ -45,7 +46,7 @@ const shutdown = async () => {
|
|
|
45
46
|
isShuttingDown = true;
|
|
46
47
|
|
|
47
48
|
try {
|
|
48
|
-
await
|
|
49
|
+
await db.$disconnect();
|
|
49
50
|
} catch {
|
|
50
51
|
// Ignore disconnect errors during shutdown
|
|
51
52
|
}
|
|
@@ -13,32 +13,13 @@ import 'dotenv/config';
|
|
|
13
13
|
import { databasePlugin, serve, veloxApp } from '@veloxts/velox';
|
|
14
14
|
|
|
15
15
|
import { config } from './config/app.js';
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
import {
|
|
16
|
+
import { db } from './config/database.js';
|
|
17
|
+
// Import router definition (type-only safe for frontend imports)
|
|
18
|
+
import { collections } from './router.js';
|
|
19
19
|
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* AppRouter type for frontend type safety
|
|
26
|
-
*
|
|
27
|
-
* Constructed from procedure collections to preserve full type information.
|
|
28
|
-
* This enables type-safe API calls with full autocomplete.
|
|
29
|
-
*
|
|
30
|
-
* @example
|
|
31
|
-
* ```typescript
|
|
32
|
-
* import type { AppRouter } from '../../api/src';
|
|
33
|
-
* import { createVeloxHooks } from '@veloxts/client/react';
|
|
34
|
-
*
|
|
35
|
-
* export const api = createVeloxHooks<AppRouter>();
|
|
36
|
-
* ```
|
|
37
|
-
*/
|
|
38
|
-
export type AppRouter = {
|
|
39
|
-
health: typeof healthProcedures;
|
|
40
|
-
users: typeof userProcedures;
|
|
41
|
-
};
|
|
20
|
+
// Re-export AppRouter for backward compatibility
|
|
21
|
+
// Frontend should import from ./router.js directly for type safety
|
|
22
|
+
export type { AppRouter } from './router.js';
|
|
42
23
|
|
|
43
24
|
// ============================================================================
|
|
44
25
|
// Application Bootstrap
|
|
@@ -50,7 +31,7 @@ const app = await veloxApp({
|
|
|
50
31
|
logger: config.logger,
|
|
51
32
|
});
|
|
52
33
|
|
|
53
|
-
await app.register(databasePlugin({ client:
|
|
34
|
+
await app.register(databasePlugin({ client: db }));
|
|
54
35
|
|
|
55
36
|
// ============================================================================
|
|
56
37
|
// API Registration
|
|
@@ -62,7 +43,7 @@ await app.register(databasePlugin({ client: prisma }));
|
|
|
62
43
|
* - REST: /api/users, /api/health
|
|
63
44
|
* - tRPC: /trpc/users.getUser, /trpc/health.getHealth
|
|
64
45
|
*/
|
|
65
|
-
await serve(app,
|
|
46
|
+
await serve(app, collections, {
|
|
66
47
|
api: config.apiPrefix,
|
|
67
48
|
rpc: '/trpc',
|
|
68
49
|
});
|
|
@@ -83,7 +64,7 @@ const shutdown = async () => {
|
|
|
83
64
|
isShuttingDown = true;
|
|
84
65
|
|
|
85
66
|
try {
|
|
86
|
-
await
|
|
67
|
+
await db.$disconnect();
|
|
87
68
|
} catch {
|
|
88
69
|
// Ignore disconnect errors during shutdown
|
|
89
70
|
}
|
|
@@ -17,21 +17,22 @@
|
|
|
17
17
|
"postinstall": "prisma generate"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@prisma/adapter-better-sqlite3": "7.
|
|
21
|
-
"@prisma/client": "7.
|
|
20
|
+
"@prisma/adapter-better-sqlite3": "7.2.0",
|
|
21
|
+
"@prisma/client": "7.2.0",
|
|
22
|
+
"@prisma/client-runtime-utils": "7.2.0",
|
|
22
23
|
"@veloxts/velox": "__VELOXTS_VERSION__",
|
|
23
|
-
"bcrypt": "
|
|
24
|
+
"bcrypt": "6.0.0",
|
|
24
25
|
"better-sqlite3": "12.5.0",
|
|
25
26
|
"dotenv": "17.2.3",
|
|
26
27
|
"file-uri-to-path": "2.0.0",
|
|
27
|
-
"zod": "3.
|
|
28
|
+
"zod": "3.25.76"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
|
-
"@types/bcrypt": "
|
|
31
|
+
"@types/bcrypt": "6.0.0",
|
|
31
32
|
"@types/node": "25.0.2",
|
|
32
33
|
"@veloxts/cli": "__VELOXTS_VERSION__",
|
|
33
34
|
"hot-hook": "0.4.0",
|
|
34
|
-
"prisma": "7.
|
|
35
|
+
"prisma": "7.2.0",
|
|
35
36
|
"tsup": "8.5.1",
|
|
36
37
|
"tsx": "4.21.0",
|
|
37
38
|
"typescript": "5.9.3"
|
|
@@ -17,19 +17,20 @@
|
|
|
17
17
|
"postinstall": "prisma generate"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@prisma/adapter-better-sqlite3": "7.
|
|
21
|
-
"@prisma/client": "7.
|
|
20
|
+
"@prisma/adapter-better-sqlite3": "7.2.0",
|
|
21
|
+
"@prisma/client": "7.2.0",
|
|
22
|
+
"@prisma/client-runtime-utils": "7.2.0",
|
|
22
23
|
"@veloxts/velox": "__VELOXTS_VERSION__",
|
|
23
24
|
"better-sqlite3": "12.5.0",
|
|
24
25
|
"dotenv": "17.2.3",
|
|
25
26
|
"file-uri-to-path": "2.0.0",
|
|
26
|
-
"zod": "3.
|
|
27
|
+
"zod": "3.25.76"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
29
30
|
"@types/node": "25.0.2",
|
|
30
31
|
"@veloxts/cli": "__VELOXTS_VERSION__",
|
|
31
32
|
"hot-hook": "0.4.0",
|
|
32
|
-
"prisma": "7.
|
|
33
|
+
"prisma": "7.2.0",
|
|
33
34
|
"tsup": "8.5.1",
|
|
34
35
|
"tsx": "4.21.0",
|
|
35
36
|
"typescript": "5.9.3"
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
// Using SQLite for simplicity - easily swap to PostgreSQL for production.
|
|
5
5
|
|
|
6
6
|
generator client {
|
|
7
|
-
provider = "prisma-client"
|
|
8
|
-
|
|
7
|
+
provider = "prisma-client-js"
|
|
8
|
+
// Generate to root node_modules for workspace compatibility
|
|
9
|
+
output = "../../../node_modules/.prisma/client"
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
datasource db {
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
// Using SQLite for simplicity - easily swap to PostgreSQL for production.
|
|
5
5
|
|
|
6
6
|
generator client {
|
|
7
|
-
provider = "prisma-client"
|
|
8
|
-
|
|
7
|
+
provider = "prisma-client-js"
|
|
8
|
+
// Generate to root node_modules for workspace compatibility
|
|
9
|
+
output = "../../../node_modules/.prisma/client"
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
datasource db {
|
|
@@ -8,9 +8,15 @@ import 'dotenv/config';
|
|
|
8
8
|
|
|
9
9
|
import { defineConfig } from 'prisma/config';
|
|
10
10
|
|
|
11
|
+
const databaseUrl = process.env.DATABASE_URL;
|
|
12
|
+
|
|
13
|
+
if (!databaseUrl) {
|
|
14
|
+
throw new Error('DATABASE_URL environment variable is required');
|
|
15
|
+
}
|
|
16
|
+
|
|
11
17
|
export default defineConfig({
|
|
12
18
|
schema: './prisma/schema.prisma',
|
|
13
19
|
datasource: {
|
|
14
|
-
url:
|
|
20
|
+
url: databaseUrl,
|
|
15
21
|
},
|
|
16
22
|
});
|
|
@@ -20,11 +20,17 @@ import {
|
|
|
20
20
|
jwtManager,
|
|
21
21
|
procedure,
|
|
22
22
|
verifyPassword,
|
|
23
|
-
z,
|
|
24
23
|
} from '@veloxts/velox';
|
|
25
24
|
|
|
26
|
-
import {
|
|
27
|
-
|
|
25
|
+
import {
|
|
26
|
+
LoginInput,
|
|
27
|
+
LogoutResponse,
|
|
28
|
+
RefreshInput,
|
|
29
|
+
RegisterInput,
|
|
30
|
+
TokenResponse,
|
|
31
|
+
UserResponse,
|
|
32
|
+
} from '../schemas/auth.js';
|
|
33
|
+
import { getJwtSecrets, parseUserRoles, tokenStore } from '../utils/auth.js';
|
|
28
34
|
|
|
29
35
|
// ============================================================================
|
|
30
36
|
// Rate Limiter
|
|
@@ -50,7 +56,7 @@ const rateLimiter = createAuthRateLimiter({
|
|
|
50
56
|
});
|
|
51
57
|
|
|
52
58
|
// ============================================================================
|
|
53
|
-
// Password Blacklist
|
|
59
|
+
// Password Blacklist (runtime-only, not in type chain)
|
|
54
60
|
// ============================================================================
|
|
55
61
|
|
|
56
62
|
const COMMON_PASSWORDS = new Set([
|
|
@@ -64,66 +70,32 @@ const COMMON_PASSWORDS = new Set([
|
|
|
64
70
|
'admin123',
|
|
65
71
|
]);
|
|
66
72
|
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
.refine((pwd) => /[0-9]/.test(pwd), 'Password must contain at least one number')
|
|
78
|
-
.refine(
|
|
79
|
-
(pwd) => !COMMON_PASSWORDS.has(pwd.toLowerCase()),
|
|
80
|
-
'Password is too common. Please choose a stronger password.'
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
const EmailSchema = z
|
|
84
|
-
.string()
|
|
85
|
-
.email('Invalid email address')
|
|
86
|
-
.transform((email) => email.toLowerCase().trim());
|
|
87
|
-
|
|
88
|
-
const RegisterInput = z.object({
|
|
89
|
-
name: z.string().min(2).max(100).trim(),
|
|
90
|
-
email: EmailSchema,
|
|
91
|
-
password: PasswordSchema,
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
const LoginInput = z.object({
|
|
95
|
-
email: EmailSchema,
|
|
96
|
-
password: z.string().min(1),
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
const RefreshInput = z.object({
|
|
100
|
-
refreshToken: z.string(),
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
const TokenResponse = z.object({
|
|
104
|
-
accessToken: z.string(),
|
|
105
|
-
refreshToken: z.string(),
|
|
106
|
-
expiresIn: z.number(),
|
|
107
|
-
tokenType: z.literal('Bearer'),
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
const UserResponse = z.object({
|
|
111
|
-
id: z.string(),
|
|
112
|
-
name: z.string(),
|
|
113
|
-
email: z.string(),
|
|
114
|
-
roles: z.array(z.string()),
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
const LogoutResponse = z.object({
|
|
118
|
-
success: z.boolean(),
|
|
119
|
-
message: z.string(),
|
|
73
|
+
// Enhanced password validation with common password check
|
|
74
|
+
const EnhancedRegisterInput = RegisterInput.extend({
|
|
75
|
+
password: RegisterInput.shape.password
|
|
76
|
+
.refine((pwd) => /[a-z]/.test(pwd), 'Password must contain at least one lowercase letter')
|
|
77
|
+
.refine((pwd) => /[A-Z]/.test(pwd), 'Password must contain at least one uppercase letter')
|
|
78
|
+
.refine((pwd) => /[0-9]/.test(pwd), 'Password must contain at least one number')
|
|
79
|
+
.refine(
|
|
80
|
+
(pwd) => !COMMON_PASSWORDS.has(pwd.toLowerCase()),
|
|
81
|
+
'Password is too common. Please choose a stronger password.'
|
|
82
|
+
),
|
|
120
83
|
});
|
|
121
84
|
|
|
122
85
|
// ============================================================================
|
|
123
86
|
// JWT Manager
|
|
124
87
|
// ============================================================================
|
|
125
88
|
|
|
126
|
-
const
|
|
89
|
+
const { jwtSecret, refreshSecret } = getJwtSecrets();
|
|
90
|
+
|
|
91
|
+
const jwt = jwtManager({
|
|
92
|
+
secret: jwtSecret,
|
|
93
|
+
refreshSecret: refreshSecret,
|
|
94
|
+
accessTokenExpiry: '15m',
|
|
95
|
+
refreshTokenExpiry: '7d',
|
|
96
|
+
issuer: 'velox-app',
|
|
97
|
+
audience: 'velox-app-client',
|
|
98
|
+
});
|
|
127
99
|
|
|
128
100
|
// Dummy hash for timing attack prevention
|
|
129
101
|
const DUMMY_HASH = '$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/X4.uy7dPSSXB5G6Uy';
|
|
@@ -136,12 +108,12 @@ export const authProcedures = defineProcedures('auth', {
|
|
|
136
108
|
createAccount: procedure()
|
|
137
109
|
.rest({ method: 'POST', path: '/auth/register' })
|
|
138
110
|
.use(rateLimiter.register())
|
|
139
|
-
.input(
|
|
111
|
+
.input(EnhancedRegisterInput)
|
|
140
112
|
.output(TokenResponse)
|
|
141
|
-
.mutation(async ({ input }) => {
|
|
113
|
+
.mutation(async ({ input, ctx }) => {
|
|
142
114
|
const normalizedEmail = input.email.toLowerCase().trim();
|
|
143
115
|
|
|
144
|
-
const existing = await
|
|
116
|
+
const existing = await ctx.db.user.findUnique({
|
|
145
117
|
where: { email: normalizedEmail },
|
|
146
118
|
});
|
|
147
119
|
|
|
@@ -155,7 +127,7 @@ export const authProcedures = defineProcedures('auth', {
|
|
|
155
127
|
|
|
156
128
|
const hashedPassword = await hashPassword(input.password);
|
|
157
129
|
|
|
158
|
-
const user = await
|
|
130
|
+
const user = await ctx.db.user.create({
|
|
159
131
|
data: {
|
|
160
132
|
name: input.name.trim(),
|
|
161
133
|
email: normalizedEmail,
|
|
@@ -181,10 +153,10 @@ export const authProcedures = defineProcedures('auth', {
|
|
|
181
153
|
)
|
|
182
154
|
.input(LoginInput)
|
|
183
155
|
.output(TokenResponse)
|
|
184
|
-
.mutation(async ({ input }) => {
|
|
156
|
+
.mutation(async ({ input, ctx }) => {
|
|
185
157
|
const normalizedEmail = input.email.toLowerCase().trim();
|
|
186
158
|
|
|
187
|
-
const user = await
|
|
159
|
+
const user = await ctx.db.user.findUnique({
|
|
188
160
|
where: { email: normalizedEmail },
|
|
189
161
|
});
|
|
190
162
|
|
|
@@ -209,7 +181,7 @@ export const authProcedures = defineProcedures('auth', {
|
|
|
209
181
|
.use(rateLimiter.refresh())
|
|
210
182
|
.input(RefreshInput)
|
|
211
183
|
.output(TokenResponse)
|
|
212
|
-
.mutation(async ({ input }) => {
|
|
184
|
+
.mutation(async ({ input, ctx }) => {
|
|
213
185
|
try {
|
|
214
186
|
const payload = jwt.verifyToken(input.refreshToken);
|
|
215
187
|
|
|
@@ -234,7 +206,7 @@ export const authProcedures = defineProcedures('auth', {
|
|
|
234
206
|
tokenStore.markRefreshTokenUsed(payload.jti, payload.sub);
|
|
235
207
|
}
|
|
236
208
|
|
|
237
|
-
const user = await
|
|
209
|
+
const user = await ctx.db.user.findUnique({
|
|
238
210
|
where: { id: payload.sub },
|
|
239
211
|
});
|
|
240
212
|
|
|
@@ -2,19 +2,14 @@
|
|
|
2
2
|
* Health Check Procedures
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { defineProcedures, procedure, VELOX_VERSION
|
|
5
|
+
import { defineProcedures, procedure, VELOX_VERSION } from '@veloxts/velox';
|
|
6
|
+
|
|
7
|
+
import { HealthResponse } from '../schemas/health.js';
|
|
6
8
|
|
|
7
9
|
export const healthProcedures = defineProcedures('health', {
|
|
8
10
|
getHealth: procedure()
|
|
9
11
|
.rest({ method: 'GET', path: '/health' })
|
|
10
|
-
.output(
|
|
11
|
-
z.object({
|
|
12
|
-
status: z.literal('ok'),
|
|
13
|
-
version: z.string(),
|
|
14
|
-
timestamp: z.string().datetime(),
|
|
15
|
-
uptime: z.number(),
|
|
16
|
-
})
|
|
17
|
-
)
|
|
12
|
+
.output(HealthResponse)
|
|
18
13
|
.query(async () => ({
|
|
19
14
|
status: 'ok' as const,
|
|
20
15
|
version: VELOX_VERSION,
|
|
@@ -20,7 +20,12 @@ import {
|
|
|
20
20
|
z,
|
|
21
21
|
} from '@veloxts/velox';
|
|
22
22
|
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
CreateUserInput,
|
|
25
|
+
ListUsersResponse,
|
|
26
|
+
UpdateUserInput,
|
|
27
|
+
UserSchema,
|
|
28
|
+
} from '../schemas/user.js';
|
|
24
29
|
|
|
25
30
|
// ============================================================================
|
|
26
31
|
// User Procedures
|
|
@@ -41,16 +46,7 @@ export const userProcedures = defineProcedures('users', {
|
|
|
41
46
|
|
|
42
47
|
listUsers: procedure()
|
|
43
48
|
.input(paginationInputSchema.optional())
|
|
44
|
-
.output(
|
|
45
|
-
z.object({
|
|
46
|
-
data: z.array(UserSchema),
|
|
47
|
-
meta: z.object({
|
|
48
|
-
page: z.number(),
|
|
49
|
-
limit: z.number(),
|
|
50
|
-
total: z.number(),
|
|
51
|
-
}),
|
|
52
|
-
})
|
|
53
|
-
)
|
|
49
|
+
.output(ListUsersResponse)
|
|
54
50
|
.query(async ({ input, ctx }) => {
|
|
55
51
|
const page = input?.page ?? 1;
|
|
56
52
|
const limit = input?.limit ?? 10;
|
|
@@ -15,7 +15,12 @@ import {
|
|
|
15
15
|
z,
|
|
16
16
|
} from '@veloxts/velox';
|
|
17
17
|
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
CreateUserInput,
|
|
20
|
+
ListUsersResponse,
|
|
21
|
+
UpdateUserInput,
|
|
22
|
+
UserSchema,
|
|
23
|
+
} from '../schemas/user.js';
|
|
19
24
|
|
|
20
25
|
export const userProcedures = defineProcedures('users', {
|
|
21
26
|
getUser: procedure()
|
|
@@ -32,16 +37,7 @@ export const userProcedures = defineProcedures('users', {
|
|
|
32
37
|
|
|
33
38
|
listUsers: procedure()
|
|
34
39
|
.input(paginationInputSchema.optional())
|
|
35
|
-
.output(
|
|
36
|
-
z.object({
|
|
37
|
-
data: z.array(UserSchema),
|
|
38
|
-
meta: z.object({
|
|
39
|
-
page: z.number(),
|
|
40
|
-
limit: z.number(),
|
|
41
|
-
total: z.number(),
|
|
42
|
-
}),
|
|
43
|
-
})
|
|
44
|
-
)
|
|
40
|
+
.output(ListUsersResponse)
|
|
45
41
|
.query(async ({ input, ctx }) => {
|
|
46
42
|
const page = input?.page ?? 1;
|
|
47
43
|
const limit = input?.limit ?? 10;
|