create-nexwire 4.0.0 → 4.1.0
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 +5 -4
- package/express/.env +1 -1
- package/express/README.md +13 -7
- package/express/package.json +2 -2
- package/express/src/config/session.ts +1 -1
- package/express/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -1
- package/express/src/database/migrations/sqlite/meta/_journal.json +1 -1
- package/express/src/database/schema.ts +3 -3
- package/express/src/framework/cache/cache.ts +82 -81
- package/express/src/framework/database/seed.ts +1 -1
- package/express/src/framework/facade.ts +1 -24
- package/express/src/framework/http/app.ts +5 -5
- package/express/src/framework/http/openapi.ts +1 -1
- package/express/src/framework/http/router.ts +3 -18
- package/express/src/framework/http/types.ts +19 -0
- package/express/src/framework/http/validation.ts +18 -18
- package/express/src/framework/maker-cli/middleware/core.mjs +41 -47
- package/express/src/framework/maker-cli/module/core.mjs +920 -684
- package/express/src/framework/maker-cli/module/index.mjs +202 -140
- package/express/src/framework/maker-cli/runtime/core.mjs +1 -2
- package/express/src/framework/maker-cli/stubs/controller/openapi.ts.stub +40 -14
- package/express/src/framework/maker-cli/stubs/controller/openapi.with-model.ts.stub +40 -15
- package/express/src/framework/maker-cli/stubs/controller/plain.ts.stub +42 -16
- package/express/src/framework/maker-cli/stubs/controller/standalone.ts.stub +47 -0
- package/express/src/framework/maker-cli/stubs/facade/name.ts.stub +12 -0
- package/express/src/framework/maker-cli/stubs/helper/name.ts.stub +14 -0
- package/express/src/framework/maker-cli/stubs/middleware/name.local.ts.stub +15 -0
- package/express/src/framework/maker-cli/stubs/model/name.mysql.ts.stub +9 -8
- package/express/src/framework/maker-cli/stubs/model/name.postgresql.ts.stub +9 -8
- package/express/src/framework/maker-cli/stubs/model/name.sqlite.ts.stub +9 -8
- package/express/src/framework/maker-cli/stubs/notification/route.api.ts.stub +50 -50
- package/express/src/framework/maker-cli/stubs/notification/route.plain.ts.stub +11 -11
- package/express/src/framework/maker-cli/stubs/route/api.ts.stub +93 -79
- package/express/src/framework/maker-cli/stubs/route/plain.ts.stub +23 -10
- package/express/src/framework/maker-cli/stubs/route/standalone.api.ts.stub +113 -0
- package/express/src/framework/maker-cli/stubs/route/standalone.plain.ts.stub +37 -0
- package/express/src/framework/maker-cli/stubs/{controller/schema.plain.ts.stub → schema/name.plain.ts.stub} +13 -13
- package/express/src/framework/maker-cli/stubs/{controller/schema.ts.stub → schema/name.ts.stub} +32 -32
- package/express/src/framework/maker-cli/stubs/service/name.plain.ts.stub +67 -0
- package/express/src/framework/maker-cli/stubs/service/name.ts.stub +65 -0
- package/express/src/framework/maker-cli/stubs/type/name.ts.stub +22 -0
- package/express/src/framework/maker-cli/utils/naming.mjs +56 -13
- package/express/src/framework/modules/discover.ts +57 -54
- package/express/src/framework/notification/index.ts +1 -1
- package/express/src/framework/realtime/broadcast.ts +46 -46
- package/express/src/framework/server.ts +2 -1
- package/express/src/framework/session/session.ts +102 -98
- package/express/src/framework/support/mail.ts +48 -48
- package/express/src/framework/support/status-codes.ts +75 -0
- package/express/src/middlewares/auth-middleware.ts +1 -1
- package/express/src/modules/auth/__tests__/forgetpass.test.ts +200 -0
- package/express/src/modules/auth/__tests__/login.test.ts +171 -0
- package/express/src/modules/auth/__tests__/logout.test.ts +60 -0
- package/express/src/modules/auth/__tests__/logoutalldevices.test.ts +49 -0
- package/express/src/modules/auth/__tests__/me.test.ts +91 -0
- package/express/src/modules/auth/__tests__/refreshtoken.test.ts +305 -0
- package/express/src/modules/auth/__tests__/register.test.ts +291 -0
- package/express/src/modules/auth/__tests__/resetpass.test.ts +206 -0
- package/express/src/modules/auth/__tests__/verifyemail.test.ts +183 -0
- package/express/src/modules/auth/controllers/auth.ts +254 -0
- package/express/src/modules/auth/controllers/{role.controller.ts → role.ts} +25 -15
- package/{hono/src/modules/auth/database/models/notifications.ts → express/src/modules/auth/database/models/notification.ts} +1 -1
- package/express/src/modules/auth/database/models/role.ts +1 -1
- package/express/src/modules/auth/database/models/user.ts +1 -1
- package/express/src/modules/auth/facade.ts +12 -0
- package/express/src/modules/auth/{controllers/auth.helpers.ts → helpers/auth.ts} +3 -4
- package/express/src/modules/auth/jobs/registeruser.ts +23 -23
- package/express/src/modules/auth/routes/{api.ts → index.ts} +150 -150
- package/express/src/modules/auth/routes/role.ts +33 -33
- package/{hono/src/modules/auth/controllers/auth.schema.ts → express/src/modules/auth/schemas/auth.ts} +102 -102
- package/express/src/modules/auth/services/forgetpassword.ts +66 -0
- package/express/src/modules/auth/services/login.ts +63 -0
- package/express/src/modules/auth/services/logout.ts +25 -0
- package/express/src/modules/auth/services/logoutalldevices.ts +19 -0
- package/express/src/modules/auth/services/me.ts +31 -0
- package/express/src/modules/auth/services/refreshtoken.ts +110 -0
- package/express/src/modules/auth/services/register.ts +141 -0
- package/express/src/modules/auth/services/resetpassword.ts +69 -0
- package/express/src/modules/auth/services/verifyemail.ts +77 -0
- package/express/src/modules/auth/types/auth.ts +31 -0
- package/express/src/resources/src/assets/images/logo.png +0 -0
- package/express/src/resources/src/pages/auth/forgetPassword.vue +1 -0
- package/express/src/resources/src/pages/auth/login.vue +1 -1
- package/express/src/storage/database/nexwire.sqlite +0 -0
- package/hono/.env +1 -1
- package/hono/README.md +13 -7
- package/hono/package.json +1 -1
- package/hono/src/config/session.ts +1 -1
- package/hono/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -1
- package/hono/src/database/migrations/sqlite/meta/_journal.json +1 -1
- package/hono/src/database/schema.ts +3 -3
- package/hono/src/framework/cache/cache.ts +82 -81
- package/hono/src/framework/database/seed.ts +1 -1
- package/hono/src/framework/http/app.ts +5 -5
- package/hono/src/framework/http/validation.ts +21 -21
- package/hono/src/framework/maker-cli/middleware/core.mjs +40 -46
- package/hono/src/framework/maker-cli/module/core.mjs +401 -168
- package/hono/src/framework/maker-cli/module/index.mjs +73 -11
- package/hono/src/framework/maker-cli/stubs/controller/openapi.ts.stub +28 -16
- package/hono/src/framework/maker-cli/stubs/controller/openapi.with-model.ts.stub +28 -17
- package/hono/src/framework/maker-cli/stubs/controller/plain.ts.stub +28 -16
- package/hono/src/framework/maker-cli/stubs/controller/standalone.ts.stub +56 -0
- package/hono/src/framework/maker-cli/stubs/facade/name.ts.stub +12 -0
- package/hono/src/framework/maker-cli/stubs/helper/name.ts.stub +14 -0
- package/hono/src/framework/maker-cli/stubs/middleware/name.local.ts.stub +15 -0
- package/hono/src/framework/maker-cli/stubs/model/name.mysql.ts.stub +9 -8
- package/hono/src/framework/maker-cli/stubs/model/name.postgresql.ts.stub +9 -8
- package/hono/src/framework/maker-cli/stubs/model/name.sqlite.ts.stub +9 -8
- package/hono/src/framework/maker-cli/stubs/notification/route.api.ts.stub +49 -49
- package/hono/src/framework/maker-cli/stubs/notification/route.plain.ts.stub +11 -11
- package/hono/src/framework/maker-cli/stubs/route/api.ts.stub +93 -79
- package/hono/src/framework/maker-cli/stubs/route/plain.ts.stub +23 -10
- package/hono/src/framework/maker-cli/stubs/route/standalone.api.ts.stub +112 -0
- package/hono/src/framework/maker-cli/stubs/route/standalone.plain.ts.stub +37 -0
- package/hono/src/framework/maker-cli/stubs/{controller/schema.plain.ts.stub → schema/name.plain.ts.stub} +13 -13
- package/hono/src/framework/maker-cli/stubs/{controller/schema.ts.stub → schema/name.ts.stub} +32 -32
- package/hono/src/framework/maker-cli/stubs/service/name.plain.ts.stub +67 -0
- package/hono/src/framework/maker-cli/stubs/service/name.ts.stub +65 -0
- package/hono/src/framework/maker-cli/stubs/type/name.ts.stub +22 -0
- package/hono/src/framework/maker-cli/utils/naming.mjs +56 -13
- package/hono/src/framework/modules/discover.ts +57 -54
- package/hono/src/framework/notification/index.ts +1 -1
- package/hono/src/framework/realtime/broadcast.ts +46 -46
- package/hono/src/framework/server.ts +1 -1
- package/hono/src/framework/session/session.ts +121 -116
- package/hono/src/middlewares/auth-middleware.ts +1 -1
- package/hono/src/modules/auth/__tests__/forgetpass.test.ts +170 -0
- package/hono/src/modules/auth/__tests__/login.test.ts +154 -0
- package/hono/src/modules/auth/__tests__/logout.test.ts +53 -0
- package/hono/src/modules/auth/__tests__/logoutalldevices.test.ts +34 -0
- package/hono/src/modules/auth/__tests__/me.test.ts +87 -0
- package/hono/src/modules/auth/__tests__/refreshtoken.test.ts +305 -0
- package/hono/src/modules/auth/__tests__/register.test.ts +258 -0
- package/hono/src/modules/auth/__tests__/resetpass.test.ts +214 -0
- package/hono/src/modules/auth/__tests__/verifyemail.test.ts +189 -0
- package/hono/src/modules/auth/controllers/auth.ts +238 -0
- package/hono/src/modules/auth/controllers/{role.controller.ts → role.ts} +25 -25
- package/{express/src/modules/auth/database/models/notifications.ts → hono/src/modules/auth/database/models/notification.ts} +1 -1
- package/hono/src/modules/auth/database/models/role.ts +1 -1
- package/hono/src/modules/auth/database/models/user.ts +1 -1
- package/hono/src/modules/auth/facade.ts +12 -0
- package/hono/src/modules/auth/{controllers/auth.helpers.ts → helpers/auth.ts} +5 -6
- package/hono/src/modules/auth/jobs/registeruser.ts +23 -23
- package/hono/src/modules/auth/routes/{api.ts → index.ts} +150 -150
- package/hono/src/modules/auth/routes/role.ts +33 -33
- package/{express/src/modules/auth/controllers/auth.schema.ts → hono/src/modules/auth/schemas/auth.ts} +102 -102
- package/hono/src/modules/auth/services/forgetpassword.ts +58 -0
- package/hono/src/modules/auth/services/login.ts +61 -0
- package/hono/src/modules/auth/services/logout.ts +24 -0
- package/hono/src/modules/auth/services/logoutalldevices.ts +19 -0
- package/hono/src/modules/auth/services/me.ts +31 -0
- package/hono/src/modules/auth/services/refreshtoken.ts +110 -0
- package/hono/src/modules/auth/services/register.ts +132 -0
- package/hono/src/modules/auth/services/resetpassword.ts +69 -0
- package/hono/src/modules/auth/services/verifyemail.ts +77 -0
- package/hono/src/modules/auth/types/auth.ts +31 -0
- package/hono/src/resources/src/assets/images/logo.png +0 -0
- package/hono/src/storage/database/nexwire.sqlite +0 -0
- package/package.json +1 -1
- package/express/src/framework/maker-cli/stubs/example/console.ts.stub +0 -34
- package/express/src/framework/maker-cli/stubs/example/controller.ts.stub +0 -418
- package/express/src/framework/maker-cli/stubs/example/job.ts.stub +0 -75
- package/express/src/framework/maker-cli/stubs/example/route.api.ts.stub +0 -207
- package/express/src/framework/maker-cli/stubs/example/route.plain.ts.stub +0 -18
- package/express/src/framework/maker-cli/stubs/example/schema.plain.ts.stub +0 -42
- package/express/src/framework/maker-cli/stubs/example/schema.ts.stub +0 -42
- package/express/src/modules/auth/controllers/auth.controller.ts +0 -358
- package/hono/src/framework/maker-cli/stubs/example/console.ts.stub +0 -34
- package/hono/src/framework/maker-cli/stubs/example/controller.ts.stub +0 -505
- package/hono/src/framework/maker-cli/stubs/example/job.ts.stub +0 -75
- package/hono/src/framework/maker-cli/stubs/example/route.api.ts.stub +0 -207
- package/hono/src/framework/maker-cli/stubs/example/route.plain.ts.stub +0 -18
- package/hono/src/framework/maker-cli/stubs/example/schema.plain.ts.stub +0 -42
- package/hono/src/framework/maker-cli/stubs/example/schema.ts.stub +0 -42
- package/hono/src/modules/auth/controllers/auth.controller.ts +0 -413
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { and, eq, gt } from "drizzle-orm";
|
|
2
|
+
import { db, password } from "@/framework/facade.js";
|
|
3
|
+
import { passwordResetTokens, refreshTokens, users } from "@/modules/auth/database/models/user.js";
|
|
4
|
+
import { hashResetToken } from "@/modules/auth/helpers/auth.js";
|
|
5
|
+
import type { ResetPasswordInput } from "@/modules/auth/types/auth.js";
|
|
6
|
+
|
|
7
|
+
export type ResetPasswordResult = { kind: "invalid_token"; message: string } | { kind: "reset"; message: string };
|
|
8
|
+
|
|
9
|
+
export const resetPasswordService = {
|
|
10
|
+
resetPassword: async (body: ResetPasswordInput): Promise<ResetPasswordResult> => {
|
|
11
|
+
const user = await db.query.users.findFirst({
|
|
12
|
+
where: eq(users.email, body.email)
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
if (!user) {
|
|
16
|
+
return {
|
|
17
|
+
kind: "invalid_token",
|
|
18
|
+
message: "Invalid or expired reset token"
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const hashedPassword = await password.hashPassword(body.password);
|
|
23
|
+
const hashedToken = hashResetToken(body.token);
|
|
24
|
+
const now = new Date();
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const consumed = await db.transaction(async (tx) => {
|
|
28
|
+
// Consume the token atomically inside the transaction.
|
|
29
|
+
// If anything below fails, the delete rolls back and the
|
|
30
|
+
// user can retry with the same token.
|
|
31
|
+
const [row] = await tx
|
|
32
|
+
.delete(passwordResetTokens)
|
|
33
|
+
.where(
|
|
34
|
+
and(
|
|
35
|
+
eq(passwordResetTokens.email, body.email),
|
|
36
|
+
eq(passwordResetTokens.token, hashedToken),
|
|
37
|
+
gt(passwordResetTokens.expiresAt, now)
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
.returning();
|
|
41
|
+
|
|
42
|
+
if (!row) return false;
|
|
43
|
+
|
|
44
|
+
await tx.update(users).set({ password: hashedPassword, updatedAt: now }).where(eq(users.id, user.id));
|
|
45
|
+
|
|
46
|
+
await tx.update(refreshTokens).set({ revoked: 1 }).where(eq(refreshTokens.userId, user.id));
|
|
47
|
+
|
|
48
|
+
return true;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
if (!consumed) {
|
|
52
|
+
return {
|
|
53
|
+
kind: "invalid_token",
|
|
54
|
+
message: "Invalid or expired reset token"
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { kind: "reset", message: "Password reset successfully" };
|
|
59
|
+
} catch (err) {
|
|
60
|
+
// Token was NOT consumed — the transaction rolled back, so the
|
|
61
|
+
// user can retry with the same token. Log so failures aren't silent.
|
|
62
|
+
// logger.error({ err, email: body.email }, "password reset failed");
|
|
63
|
+
return {
|
|
64
|
+
kind: "invalid_token",
|
|
65
|
+
message: "Invalid or expired reset token"
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { and, eq, gt } from "drizzle-orm";
|
|
2
|
+
import { authConfig } from "@/config/index.js";
|
|
3
|
+
import { db } from "@/framework/facade.js";
|
|
4
|
+
import { emailVerificationTokens, users } from "@/modules/auth/database/models/user.js";
|
|
5
|
+
import { hashEmailVerificationToken } from "@/modules/auth/helpers/auth.js";
|
|
6
|
+
import type { VerifyEmailInput } from "@/modules/auth/types/auth.js";
|
|
7
|
+
|
|
8
|
+
export type VerifyEmailResult =
|
|
9
|
+
| { kind: "not_required"; message: string }
|
|
10
|
+
| { kind: "invalid_token"; message: string }
|
|
11
|
+
| { kind: "verified"; message: string };
|
|
12
|
+
|
|
13
|
+
export const verifyEmailService = {
|
|
14
|
+
verifyEmail: async (body: VerifyEmailInput): Promise<VerifyEmailResult> => {
|
|
15
|
+
if (!authConfig.requireEmailVerification) {
|
|
16
|
+
return {
|
|
17
|
+
kind: "not_required",
|
|
18
|
+
message: "Email verification is not required"
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const user = await db.query.users.findFirst({
|
|
23
|
+
where: eq(users.email, body.email)
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (!user) {
|
|
27
|
+
return {
|
|
28
|
+
kind: "invalid_token",
|
|
29
|
+
message: "Invalid or expired verification token"
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const hashedToken = hashEmailVerificationToken(body.token);
|
|
34
|
+
const now = new Date();
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const verified = await db.transaction(async (tx) => {
|
|
38
|
+
// Consume the token atomically INSIDE the transaction.
|
|
39
|
+
// If the update below fails, the delete rolls back and the
|
|
40
|
+
// user can retry with the same token.
|
|
41
|
+
const [consumed] = await tx
|
|
42
|
+
.delete(emailVerificationTokens)
|
|
43
|
+
.where(
|
|
44
|
+
and(
|
|
45
|
+
eq(emailVerificationTokens.email, body.email),
|
|
46
|
+
eq(emailVerificationTokens.token, hashedToken),
|
|
47
|
+
gt(emailVerificationTokens.expiresAt, now)
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
.returning();
|
|
51
|
+
|
|
52
|
+
if (!consumed) return false;
|
|
53
|
+
|
|
54
|
+
await tx.update(users).set({ emailVerifiedAt: now, updatedAt: now }).where(eq(users.id, user.id));
|
|
55
|
+
|
|
56
|
+
return true;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (!verified) {
|
|
60
|
+
return {
|
|
61
|
+
kind: "invalid_token",
|
|
62
|
+
message: "Invalid or expired verification token"
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return { kind: "verified", message: "Email verified successfully" };
|
|
67
|
+
} catch (err) {
|
|
68
|
+
// Token was NOT consumed — the transaction rolled back, so the
|
|
69
|
+
// user can retry with the same token. Log so failures aren't silent.
|
|
70
|
+
// logger.error({ err, email: body.email }, "email verification failed");
|
|
71
|
+
return {
|
|
72
|
+
kind: "invalid_token",
|
|
73
|
+
message: "Invalid or expired verification token"
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type RegisterInput = {
|
|
2
|
+
name: string;
|
|
3
|
+
email: string;
|
|
4
|
+
password: string;
|
|
5
|
+
remember?: boolean;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type LoginInput = {
|
|
9
|
+
email: string;
|
|
10
|
+
password: string;
|
|
11
|
+
remember?: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type ForgotPasswordInput = {
|
|
15
|
+
email: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type ResetPasswordInput = {
|
|
19
|
+
email: string;
|
|
20
|
+
token: string;
|
|
21
|
+
password: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type VerifyEmailInput = {
|
|
25
|
+
email: string;
|
|
26
|
+
token: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type RefreshTokenInput = {
|
|
30
|
+
refresh_token: string;
|
|
31
|
+
};
|
|
Binary file
|
|
@@ -50,10 +50,10 @@
|
|
|
50
50
|
import { useHead } from "@vueuse/head";
|
|
51
51
|
import { ref } from "vue";
|
|
52
52
|
import { useRouter } from "vue-router";
|
|
53
|
+
import Button from "@/components/Button.vue";
|
|
53
54
|
import Checkbox from "@/components/Checkbox.vue";
|
|
54
55
|
import Input from "@/components/Input.vue";
|
|
55
56
|
import InputPasswordToggle from "@/components/InputPasswordToggle.vue";
|
|
56
|
-
import Button from "@/components/Button.vue";
|
|
57
57
|
import { useGumForm } from "@/plugins/gum";
|
|
58
58
|
import { useAuthStore } from "@/stores/auth";
|
|
59
59
|
|
|
Binary file
|
package/hono/.env
CHANGED
|
@@ -26,7 +26,7 @@ MAIL_PASSWORD=
|
|
|
26
26
|
|
|
27
27
|
# ── Redis ──────────────────────────────────────────
|
|
28
28
|
# Set true to enable cache, session, queue, events, and Queue Dashboard
|
|
29
|
-
REDIS=
|
|
29
|
+
REDIS=true
|
|
30
30
|
# Shared key prefix for cache, session, queue, events, and dashboards
|
|
31
31
|
REDIS_URL=redis://127.0.0.1:6379
|
|
32
32
|
REDIS_PREFIX=nexwire
|
package/hono/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<h3 align="center">Full-stack TypeScript framework for modern web applications</h3>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
<a href="https://niyamulahsan.github.io/nexwire"><img src="https://img.shields.io/badge/docs-3b8eed" alt="Documentation"></a>
|
|
10
|
+
<a href="https://niyamulahsan.github.io/nexwire"><img src="https://img.shields.io/badge/docs%20%7C%20nexwire-3b8eed" alt="Documentation"></a>
|
|
11
11
|
<a href="https://www.npmjs.com/package/create-nexwire"><img src="https://img.shields.io/npm/v/create-nexwire" alt="npm version"></a>
|
|
12
12
|
<a href="https://www.npmjs.com/package/create-nexwire"><img src="https://img.shields.io/npm/dt/create-nexwire" alt="npm downloads"></a>
|
|
13
13
|
<a href="https://github.com/niyamulahsan/nexwire/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="MIT License"></a>
|
|
@@ -59,8 +59,8 @@ Works with any package manager: `pnpm`, `yarn`, or `bun`.
|
|
|
59
59
|
|
|
60
60
|
All examples use `npm` as the default. nexwire works with any major package manager:
|
|
61
61
|
|
|
62
|
-
| Manager | Create project
|
|
63
|
-
| -------- |
|
|
62
|
+
| Manager | Create project | Run commands |
|
|
63
|
+
| -------- | ----------------------------------- | --------------------- |
|
|
64
64
|
| **npm** | `npm create nexwire@latest my-app` | `npm run maker <cmd>` |
|
|
65
65
|
| **pnpm** | `pnpm create nexwire@latest my-app` | `pnpm maker <cmd>` |
|
|
66
66
|
| **yarn** | `yarn create nexwire@latest my-app` | `yarn maker <cmd>` |
|
|
@@ -73,7 +73,7 @@ nexwire runs on **Node.js** or **Bun** — pick whichever fits your deployment:
|
|
|
73
73
|
|
|
74
74
|
| Runtime | Minimum version | Notes |
|
|
75
75
|
| ----------- | --------------- | --------------------------------------------------------------------- |
|
|
76
|
-
| **Node.js** | `>= 22.12`
|
|
76
|
+
| **Node.js** | `>= 22.12` | Default. Uses `node` in Dockerfile. |
|
|
77
77
|
| **Bun** | `>= 1.3` | Pass `--runtime=bun` to `deploy:init`. Uses `oven/bun` in Dockerfile. |
|
|
78
78
|
|
|
79
79
|
## Features
|
|
@@ -114,14 +114,20 @@ Every feature is a self-contained module under `src/modules/<name>/`:
|
|
|
114
114
|
|
|
115
115
|
```
|
|
116
116
|
src/modules/posts/
|
|
117
|
+
├── __tests__/ # Unit tests
|
|
117
118
|
├── console/ # CLI commands & scheduled tasks
|
|
118
|
-
├── controllers/ # Request handlers
|
|
119
|
+
├── controllers/ # Request handlers
|
|
119
120
|
├── database/
|
|
120
121
|
│ ├── models/ # Drizzle table definitions
|
|
121
|
-
│ └── seeders/ # Test data
|
|
122
|
+
│ └── seeders/ # Test data
|
|
123
|
+
├── helpers/ # Pure functions — no I/O
|
|
122
124
|
├── jobs/ # BullMQ queue handlers
|
|
125
|
+
├── middlewares/ # Module-local middleware
|
|
123
126
|
├── routes/ # HTTP route definitions (auto-discovered)
|
|
124
|
-
|
|
127
|
+
├── schemas/ # Zod validation schemas
|
|
128
|
+
├── services/ # Business logic — DB, files, network
|
|
129
|
+
├── types/ # Shared TypeScript types
|
|
130
|
+
└── facade.ts # Public API for other modules
|
|
125
131
|
```
|
|
126
132
|
|
|
127
133
|
Modules are **auto-discovered** — no manual registration. Create one with:
|
package/hono/package.json
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export * from "../modules/auth/database/models/
|
|
2
|
-
export * from "../modules/auth/database/models/role.js";
|
|
3
|
-
export * from "../modules/auth/database/models/user.js";
|
|
1
|
+
export * from "../modules/auth/database/models/notification.js";
|
|
2
|
+
export * from "../modules/auth/database/models/role.js";
|
|
3
|
+
export * from "../modules/auth/database/models/user.js";
|
|
@@ -1,81 +1,82 @@
|
|
|
1
|
-
import { cacheConfig, redisConfig } from "@/config/index.js";
|
|
2
|
-
import { redisClientIfReady } from "@/framework/redis/client.js";
|
|
3
|
-
|
|
4
|
-
function cacheKey(key: string) {
|
|
5
|
-
return `${cacheConfig.keyPrefix}:${key}`;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Why: Provides Redis-backed cache helpers with graceful fallback behavior.
|
|
10
|
-
* When: Controllers/jobs need temporary computed data caching.
|
|
11
|
-
* Where: Application module code via facade.
|
|
12
|
-
* How: Uses namespaced Redis keys and JSON serialization.
|
|
13
|
-
*/
|
|
14
|
-
export const cache = {
|
|
15
|
-
/**
|
|
16
|
-
* Why: Reads cached JSON value by key.
|
|
17
|
-
* When: A feature wants fast lookup before recomputing.
|
|
18
|
-
* Where: Controllers/jobs/services using shared cache.
|
|
19
|
-
* How: Fetches namespaced Redis key and parses JSON.
|
|
20
|
-
*/
|
|
21
|
-
async get<T>(key: string, fallback: T | null = null) {
|
|
22
|
-
const client = redisClientIfReady();
|
|
23
|
-
if (!client) return fallback;
|
|
24
|
-
|
|
25
|
-
const value = await client.get(cacheKey(key));
|
|
26
|
-
return value ? (JSON.parse(value) as T) : fallback;
|
|
27
|
-
},
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
await
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
1
|
+
import { cacheConfig, redisConfig } from "@/config/index.js";
|
|
2
|
+
import { redisClientIfReady } from "@/framework/redis/client.js";
|
|
3
|
+
|
|
4
|
+
function cacheKey(key: string) {
|
|
5
|
+
return `${cacheConfig.keyPrefix}:${key}`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Why: Provides Redis-backed cache helpers with graceful fallback behavior.
|
|
10
|
+
* When: Controllers/jobs need temporary computed data caching.
|
|
11
|
+
* Where: Application module code via facade.
|
|
12
|
+
* How: Uses namespaced Redis keys and JSON serialization.
|
|
13
|
+
*/
|
|
14
|
+
export const cache = {
|
|
15
|
+
/**
|
|
16
|
+
* Why: Reads cached JSON value by key.
|
|
17
|
+
* When: A feature wants fast lookup before recomputing.
|
|
18
|
+
* Where: Controllers/jobs/services using shared cache.
|
|
19
|
+
* How: Fetches namespaced Redis key and parses JSON.
|
|
20
|
+
*/
|
|
21
|
+
async get<T>(key: string, fallback: T | null = null) {
|
|
22
|
+
const client = redisClientIfReady();
|
|
23
|
+
if (!client) return fallback;
|
|
24
|
+
|
|
25
|
+
const value = await client.get(cacheKey(key));
|
|
26
|
+
return value ? (JSON.parse(value) as T) : fallback;
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
async set(key: string, value: unknown, ttl = cacheConfig.ttlSeconds) {
|
|
30
|
+
const client = redisClientIfReady();
|
|
31
|
+
if (!client) return false;
|
|
32
|
+
|
|
33
|
+
await client.set(cacheKey(key), JSON.stringify(value), "EX", ttl);
|
|
34
|
+
return true;
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @deprecated Use set() instead.
|
|
39
|
+
*/
|
|
40
|
+
async put(key: string, value: unknown, ttl = cacheConfig.ttlSeconds) {
|
|
41
|
+
return this.set(key, value, ttl);
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Why: Deletes a cached value by key.
|
|
46
|
+
* When: Cached data becomes stale or must be invalidated.
|
|
47
|
+
* Where: Write/update flows that change source data.
|
|
48
|
+
* How: Removes namespaced Redis cache key.
|
|
49
|
+
*/
|
|
50
|
+
async forget(key: string) {
|
|
51
|
+
const client = redisClientIfReady();
|
|
52
|
+
if (!client) return false;
|
|
53
|
+
|
|
54
|
+
await client.del(cacheKey(key));
|
|
55
|
+
return true;
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Why: Returns cached value or computes/stores a fresh one.
|
|
60
|
+
* When: A feature needs cache-aside behavior.
|
|
61
|
+
* Where: Read paths with potentially expensive callbacks.
|
|
62
|
+
* How: Attempts get first, then executes callback and put.
|
|
63
|
+
*/
|
|
64
|
+
async remember<T>(key: string, ttl: number, callback: () => Promise<T>) {
|
|
65
|
+
const cached = await cache.get<T>(key);
|
|
66
|
+
if (cached !== null) return cached;
|
|
67
|
+
|
|
68
|
+
const fresh = await callback();
|
|
69
|
+
await this.put(key, fresh, ttl);
|
|
70
|
+
return fresh;
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Why: Indicates whether Redis cache backend is currently usable.
|
|
75
|
+
* When: Runtime checks need to branch on cache availability.
|
|
76
|
+
* Where: Health/status endpoints and conditional flows.
|
|
77
|
+
* How: Confirms REDIS flag and ready Redis client instance.
|
|
78
|
+
*/
|
|
79
|
+
isAvailable() {
|
|
80
|
+
return redisConfig.enabled && redisClientIfReady() !== null;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
1
2
|
import { closeDatabase, initDatabase } from "@/framework/database/connection.js";
|
|
2
3
|
import { discoverModuleFiles, importFile } from "@/framework/modules/discover.js";
|
|
3
|
-
import { readFile } from "node:fs/promises";
|
|
4
4
|
|
|
5
5
|
const NAME_SYMBOL = Symbol.for("drizzle:Name");
|
|
6
6
|
const FK_SYMBOLS = [
|
|
@@ -58,20 +58,20 @@ export function createHttpApp() {
|
|
|
58
58
|
let allOk = true;
|
|
59
59
|
try {
|
|
60
60
|
database();
|
|
61
|
-
checks
|
|
61
|
+
checks.database = "ok";
|
|
62
62
|
} catch {
|
|
63
|
-
checks
|
|
63
|
+
checks.database = "error";
|
|
64
64
|
allOk = false;
|
|
65
65
|
}
|
|
66
66
|
if (redisConfig.enabled) {
|
|
67
67
|
if (redisClientIfReady()) {
|
|
68
|
-
checks
|
|
68
|
+
checks.redis = "ok";
|
|
69
69
|
} else {
|
|
70
|
-
checks
|
|
70
|
+
checks.redis = "error";
|
|
71
71
|
allOk = false;
|
|
72
72
|
}
|
|
73
73
|
} else {
|
|
74
|
-
checks
|
|
74
|
+
checks.redis = "disabled";
|
|
75
75
|
}
|
|
76
76
|
const status = allOk ? 200 : 503;
|
|
77
77
|
return c.json({ status: allOk ? "ready" : "not_ready", checks }, status);
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import type { z } from "zod";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Why: Centralizes schema validation with consistent error shape.
|
|
5
|
-
* When: Non-route code needs explicit Zod validation.
|
|
6
|
-
* Where: Controllers/helpers/services.
|
|
7
|
-
* How: Runs safeParseAsync and throws 422 payload on failure.
|
|
8
|
-
*/
|
|
9
|
-
export async function validate<T extends z.
|
|
10
|
-
const result = await schema.safeParseAsync(data);
|
|
11
|
-
|
|
12
|
-
if (!result.success) {
|
|
13
|
-
throw {
|
|
14
|
-
status: 422,
|
|
15
|
-
message: "Validation failed",
|
|
16
|
-
errors: result.error.flatten()
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return result.data;
|
|
21
|
-
}
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Why: Centralizes schema validation with consistent error shape.
|
|
5
|
+
* When: Non-route code needs explicit Zod validation.
|
|
6
|
+
* Where: Controllers/helpers/services.
|
|
7
|
+
* How: Runs safeParseAsync and throws 422 payload on failure.
|
|
8
|
+
*/
|
|
9
|
+
export async function validate<T extends z.ZodType>(schema: T, data: unknown): Promise<z.infer<T>> {
|
|
10
|
+
const result = await schema.safeParseAsync(data);
|
|
11
|
+
|
|
12
|
+
if (!result.success) {
|
|
13
|
+
throw {
|
|
14
|
+
status: 422,
|
|
15
|
+
message: "Validation failed",
|
|
16
|
+
errors: result.error.flatten()
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return result.data;
|
|
21
|
+
}
|
|
@@ -1,47 +1,41 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { hasFlag } from "../utils/flags.mjs";
|
|
4
|
-
import { assertName,
|
|
5
|
-
|
|
6
|
-
const stubsRoot = path.resolve(import.meta.dirname, "../stubs");
|
|
7
|
-
|
|
8
|
-
/** Read a stub file and replace {{key}} placeholders, removing unfilled ones. */
|
|
9
|
-
async function stub(name, values = {}) {
|
|
10
|
-
let content = await fs.readFile(path.join(stubsRoot, name), "utf8");
|
|
11
|
-
for (const [key, value] of Object.entries(values)) {
|
|
12
|
-
content = content.replaceAll(`{{${key}}}`, String(value));
|
|
13
|
-
}
|
|
14
|
-
return content.replace(/\{\{[A-Z0-9_]+\}\}/g, "");
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
function
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const content = await stub("middleware/name.ts.stub", { name, camel });
|
|
43
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
44
|
-
await fs.writeFile(filePath, content);
|
|
45
|
-
console.log(`Middleware ready: ${path.relative(process.cwd(), filePath)}`);
|
|
46
|
-
console.log(`Apply it to a route group: group(${camel}Middleware)`);
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { hasFlag } from "../utils/flags.mjs";
|
|
4
|
+
import { assertName, camelCase } from "../utils/naming.mjs";
|
|
5
|
+
|
|
6
|
+
const stubsRoot = path.resolve(import.meta.dirname, "../stubs");
|
|
7
|
+
|
|
8
|
+
/** Read a stub file and replace {{key}} placeholders, removing unfilled ones. */
|
|
9
|
+
async function stub(name, values = {}) {
|
|
10
|
+
let content = await fs.readFile(path.join(stubsRoot, name), "utf8");
|
|
11
|
+
for (const [key, value] of Object.entries(values)) {
|
|
12
|
+
content = content.replaceAll(`{{${key}}}`, String(value));
|
|
13
|
+
}
|
|
14
|
+
return content.replace(/\{\{[A-Z0-9_]+\}\}/g, "");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Generate a middleware file in src/middlewares. */
|
|
18
|
+
export async function makeMiddleware(rawName, flags = []) {
|
|
19
|
+
const name = assertName(rawName, "Middleware name");
|
|
20
|
+
const camel = camelCase(name);
|
|
21
|
+
const dryRun = hasFlag(flags, "--dry-run");
|
|
22
|
+
const force = hasFlag(flags, "--force") || hasFlag(flags, "--yes");
|
|
23
|
+
|
|
24
|
+
const filePath = path.resolve(process.cwd(), "src/middlewares", `${name}-middleware.ts`);
|
|
25
|
+
if (dryRun) return;
|
|
26
|
+
|
|
27
|
+
let exists = false;
|
|
28
|
+
try {
|
|
29
|
+
await fs.access(filePath);
|
|
30
|
+
exists = true;
|
|
31
|
+
} catch {}
|
|
32
|
+
if (exists && !force) {
|
|
33
|
+
throw new Error(`Middleware already exists: ${path.relative(process.cwd(), filePath)}. Re-run with --force to overwrite.`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const content = await stub("middleware/name.ts.stub", { name, camel });
|
|
37
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
38
|
+
await fs.writeFile(filePath, content);
|
|
39
|
+
console.log(`Middleware ready: ${path.relative(process.cwd(), filePath)}`);
|
|
40
|
+
console.log(`Apply it to a route group: group(${camel}Middleware)`);
|
|
47
41
|
}
|