create-nexwire 4.2.1 → 4.2.3

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.
Files changed (54) hide show
  1. package/express/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -1
  2. package/express/src/database/migrations/sqlite/meta/_journal.json +1 -1
  3. package/express/src/framework/database/app-schema.ts +37 -0
  4. package/express/src/framework/database/connection.ts +198 -196
  5. package/express/src/framework/database/seed.ts +35 -22
  6. package/express/src/framework/facade.ts +24 -5
  7. package/express/src/framework/kernel.ts +54 -52
  8. package/express/src/framework/maker-cli/index.mjs +2 -0
  9. package/express/src/framework/maker-cli/module/core.mjs +30 -0
  10. package/express/src/framework/maker-cli/module/index.mjs +10 -1
  11. package/express/src/framework/maker-cli/provider/core.mjs +49 -0
  12. package/express/src/framework/maker-cli/provider/index.mjs +12 -0
  13. package/express/src/framework/maker-cli/stubs/notification/controller.ts.stub +77 -77
  14. package/express/src/framework/maker-cli/stubs/provider/module-barrel.ts.stub +19 -0
  15. package/express/src/framework/maker-cli/stubs/provider/module.ts.stub +21 -0
  16. package/express/src/framework/maker-cli/stubs/provider/name.ts.stub +21 -0
  17. package/express/src/framework/maker-cli/utils/help.mjs +6 -1
  18. package/express/src/framework/maker-cli/utils/project-commands.mjs +73 -12
  19. package/express/src/framework/notification/index.ts +63 -7
  20. package/express/src/framework/providers/adapter.ts +125 -0
  21. package/express/src/framework/providers/contracts.ts +56 -0
  22. package/express/src/framework/providers/loader.ts +105 -0
  23. package/express/src/framework/providers/registry.ts +62 -0
  24. package/express/src/middlewares/auth-middleware.ts +65 -38
  25. package/express/src/providers/README.md +107 -0
  26. package/express/src/storage/database/nexwire.sqlite +0 -0
  27. package/hono/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -1
  28. package/hono/src/database/migrations/sqlite/meta/_journal.json +1 -1
  29. package/hono/src/framework/database/app-schema.ts +37 -0
  30. package/hono/src/framework/database/connection.ts +198 -196
  31. package/hono/src/framework/database/seed.ts +35 -22
  32. package/hono/src/framework/facade.ts +48 -29
  33. package/hono/src/framework/kernel.ts +50 -48
  34. package/hono/src/framework/maker-cli/index.mjs +2 -0
  35. package/hono/src/framework/maker-cli/module/core.mjs +30 -0
  36. package/hono/src/framework/maker-cli/module/index.mjs +10 -1
  37. package/hono/src/framework/maker-cli/provider/core.mjs +49 -0
  38. package/hono/src/framework/maker-cli/provider/index.mjs +12 -0
  39. package/hono/src/framework/maker-cli/stubs/notification/controller.ts.stub +85 -85
  40. package/hono/src/framework/maker-cli/stubs/provider/module-barrel.ts.stub +19 -0
  41. package/hono/src/framework/maker-cli/stubs/provider/module.ts.stub +21 -0
  42. package/hono/src/framework/maker-cli/stubs/provider/name.ts.stub +21 -0
  43. package/hono/src/framework/maker-cli/utils/help.mjs +6 -1
  44. package/hono/src/framework/maker-cli/utils/project-commands.mjs +73 -12
  45. package/hono/src/framework/notification/index.ts +63 -7
  46. package/hono/src/framework/providers/adapter.ts +125 -0
  47. package/hono/src/framework/providers/contracts.ts +56 -0
  48. package/hono/src/framework/providers/loader.ts +105 -0
  49. package/hono/src/framework/providers/registry.ts +62 -0
  50. package/hono/src/middlewares/auth-middleware.ts +65 -38
  51. package/hono/src/providers/README.md +107 -0
  52. package/hono/src/storage/database/nexwire.sqlite +0 -0
  53. package/package.json +2 -2
  54. package/src/index.js +14 -6
@@ -1,19 +1,23 @@
1
- import { eq } from "drizzle-orm";
2
1
  import type { Context, Next } from "hono";
3
- import { cookie, db, HttpStatusCodes, jwt } from "@/framework/facade.js";
4
- import { refreshTokens, users } from "@/modules/auth/database/models/user.js";
2
+ import { cookie, HttpStatusCodes, jwt } from "@/framework/facade.js";
3
+ import { resolveAuthProvider } from "@/framework/providers/adapter.js";
5
4
 
6
5
  export async function authMiddleware(c: Context, next: Next) {
6
+ const provider = await resolveAuthProvider();
7
+ if (!provider) {
8
+ console.warn(
9
+ "[auth-middleware] auth provider not available; skipping auth. Register one in src/providers/ or restore src/modules/auth."
10
+ );
11
+ return await next();
12
+ }
13
+
7
14
  const accessToken = await cookie.getAuth(c);
8
15
 
9
16
  if (accessToken) {
10
- const accessPayload = await jwt.verifyToken(accessToken, "access");
17
+ const accessPayload: any = await jwt.verifyToken(accessToken, "access");
11
18
 
12
19
  if (accessPayload) {
13
- const user = await db.query.users.findFirst({
14
- where: eq(users.id, accessPayload.id as number),
15
- with: { role: true }
16
- });
20
+ const user = await provider.getUserById((accessPayload as any)?.id);
17
21
 
18
22
  if (!user) {
19
23
  cookie.deleteAuth(c);
@@ -21,13 +25,20 @@ export async function authMiddleware(c: Context, next: Next) {
21
25
  return c.json({ message: "Unauthorized" }, HttpStatusCodes.UNAUTHORIZED);
22
26
  }
23
27
 
24
- c.set("auth", {
25
- ...accessPayload,
26
- id: user.id,
27
- email: user.email,
28
- roleId: user.role?.id ?? null,
29
- role: user.role?.name ?? null
30
- });
28
+ let authCtx: any;
29
+ if (provider.getAuthContext) {
30
+ authCtx = await provider.getAuthContext(user, accessPayload);
31
+ } else {
32
+ authCtx = {
33
+ ...(accessPayload || {}),
34
+ id: (user as any).id,
35
+ email: (user as any).email,
36
+ roleId: (user as any).role?.id ?? (user as any).roleId ?? null,
37
+ role: (user as any).role?.name ?? (user as any).role ?? null,
38
+ ...(user as any)
39
+ };
40
+ }
41
+ c.set("auth", authCtx);
31
42
  return await next();
32
43
  }
33
44
  }
@@ -40,7 +51,7 @@ export async function authMiddleware(c: Context, next: Next) {
40
51
  return c.json({ message: "Unauthorized" }, HttpStatusCodes.UNAUTHORIZED);
41
52
  }
42
53
 
43
- const refreshPayload = await jwt.verifyToken(refreshToken, "refresh");
54
+ const refreshPayload: any = await jwt.verifyToken(refreshToken, "refresh");
44
55
 
45
56
  if (!refreshPayload?.jti) {
46
57
  cookie.deleteAuth(c);
@@ -48,27 +59,27 @@ export async function authMiddleware(c: Context, next: Next) {
48
59
  return c.json({ message: "Invalid token" }, HttpStatusCodes.UNAUTHORIZED);
49
60
  }
50
61
 
51
- const storedToken = await db.query.refreshTokens.findFirst({
52
- where: eq(refreshTokens.jti, refreshPayload.jti as string)
53
- });
62
+ const storedToken = await provider.findRefreshToken((refreshPayload as any)?.jti);
54
63
 
55
- if (!storedToken || storedToken.revoked) {
64
+ if (!storedToken || (storedToken as any).revoked) {
56
65
  cookie.deleteAuth(c);
57
66
  cookie.deleteRefresh(c);
58
67
  return c.json({ message: "Invalid token" }, HttpStatusCodes.UNAUTHORIZED);
59
68
  }
60
69
 
61
- if (storedToken.expiresAt.getTime() < Date.now()) {
62
- await db.delete(refreshTokens).where(eq(refreshTokens.id, storedToken.id));
63
- cookie.deleteAuth(c);
64
- cookie.deleteRefresh(c);
65
- return c.json({ message: "Invalid token" }, HttpStatusCodes.UNAUTHORIZED);
70
+ const expiresAt = (storedToken as any).expiresAt;
71
+ if (expiresAt) {
72
+ const exp = expiresAt instanceof Date ? expiresAt.getTime() : new Date(expiresAt).getTime();
73
+ if (!Number.isNaN(exp) && exp < Date.now()) {
74
+ if (provider.deleteRefreshToken) await provider.deleteRefreshToken((storedToken as any).id);
75
+ else if (provider.revokeRefreshToken) await provider.revokeRefreshToken((storedToken as any).id);
76
+ cookie.deleteAuth(c);
77
+ cookie.deleteRefresh(c);
78
+ return c.json({ message: "Invalid token" }, HttpStatusCodes.UNAUTHORIZED);
79
+ }
66
80
  }
67
81
 
68
- const user = await db.query.users.findFirst({
69
- where: eq(users.id, refreshPayload.id as number),
70
- with: { role: true }
71
- });
82
+ const user = await provider.getUserById((refreshPayload as any)?.id);
72
83
 
73
84
  if (!user) {
74
85
  cookie.deleteAuth(c);
@@ -76,23 +87,39 @@ export async function authMiddleware(c: Context, next: Next) {
76
87
  return c.json({ message: "Unauthorized" }, HttpStatusCodes.UNAUTHORIZED);
77
88
  }
78
89
 
90
+ let baseCtx: any;
91
+ if (provider.getAuthContext) {
92
+ baseCtx = await provider.getAuthContext(user, refreshPayload);
93
+ } else {
94
+ baseCtx = {
95
+ ...(refreshPayload || {}),
96
+ id: (user as any).id,
97
+ email: (user as any).email,
98
+ roleId: (user as any).role?.id ?? (user as any).roleId ?? null,
99
+ role: (user as any).role?.name ?? (user as any).role ?? null,
100
+ ...(user as any)
101
+ };
102
+ }
103
+
79
104
  const newAccessToken = await jwt.generateToken(
80
105
  {
81
- id: user.id,
82
- email: user.email,
83
- roleId: user.role?.id ?? null,
84
- role: user.role?.name ?? null
106
+ id: (user as any).id,
107
+ email: (user as any).email,
108
+ roleId: baseCtx?.roleId ?? (user as any).role?.id ?? (user as any).roleId ?? null,
109
+ role: baseCtx?.role ?? (user as any).role?.name ?? (user as any).role ?? null
85
110
  },
86
111
  "access"
87
112
  );
88
113
 
89
114
  await cookie.setAuth(c, newAccessToken.token);
90
115
  c.set("auth", {
91
- id: user.id,
92
- email: user.email,
93
- roleId: user.role?.id ?? null,
94
- role: user.role?.name ?? null,
95
- type: "access"
116
+ ...(baseCtx || {}),
117
+ id: (user as any).id,
118
+ email: (user as any).email,
119
+ roleId: baseCtx?.roleId ?? (user as any).role?.id ?? (user as any).roleId ?? null,
120
+ role: baseCtx?.role ?? (user as any).role?.name ?? (user as any).role ?? null,
121
+ type: "access",
122
+ ...(user as any)
96
123
  });
97
124
  return await next();
98
125
  }
@@ -0,0 +1,107 @@
1
+ # Providers
2
+
3
+ Provider files in this folder are imported automatically when the application
4
+ boots. Use them for anything the **whole app** needs — a replacement auth
5
+ implementation, a payment gateway, an SMS driver, a storage backend.
6
+
7
+ There is no wiring step: drop a file in here and it runs.
8
+
9
+ ## How it works
10
+
11
+ 1. Every `src/providers/**/*.{ts,js,mjs}` file is imported at boot.
12
+ 2. Every `src/modules/<module>/providers.ts` file is imported at boot too, so a
13
+ module can plug itself into the app without the app knowing about it.
14
+ 3. Every `src/modules/<module>/providers/*.ts` file is imported at boot as well,
15
+ for a module that offers more than one implementation.
16
+ 4. A provider file claims a contract by registering an implementation.
17
+
18
+ ```ts
19
+ // src/providers/payment.ts
20
+ import { registerProvider } from "@/framework/facade.js";
21
+
22
+ registerProvider("payment", {
23
+ async charge(order) {
24
+ return { id: "ch_123", status: "paid" };
25
+ }
26
+ });
27
+ ```
28
+
29
+ Read it back the same way:
30
+
31
+ ```ts
32
+ import { getProvider } from "@/framework/facade.js";
33
+
34
+ const payment = getProvider<any>("payment");
35
+ ```
36
+
37
+ ## Generating one
38
+
39
+ ```bash
40
+ npm run maker provider:make payment
41
+ npm run maker module:make-provider billing stripe
42
+ ```
43
+
44
+ `provider:make` writes `src/providers/<name>.ts` for the whole app.
45
+ `module:make-provider` writes `src/modules/<module>/providers/<name>.ts` for a
46
+ single module. Both files register themselves when they are imported.
47
+
48
+ ## Replacing the built-in auth
49
+
50
+ The framework ships with an auth module under `src/modules/auth`. To replace
51
+ it, delete that folder and register your own auth provider:
52
+
53
+ ```ts
54
+ // src/providers/auth.ts
55
+ import { registerAuthProvider } from "@/framework/facade.js";
56
+ import type { AuthProvider } from "@/framework/facade.js";
57
+
58
+ const authProvider: AuthProvider = {
59
+ async getUserById(id) {
60
+ // Return any user shape you like. Extra fields (tenantId, plan, status, ...)
61
+ // are passed straight through to `c.get("auth")` / `res.locals.auth`.
62
+ return { id, email: "user@example.com", tenantId: 42, plan: "pro" };
63
+ },
64
+ async findRefreshToken(jti) {
65
+ // Return the stored refresh token, or null when it is unknown.
66
+ return { id: 1, jti, revoked: false, expiresAt: new Date() };
67
+ },
68
+ async revokeRefreshToken(tokenId) {
69
+ // Optional. Called when a refresh token is expired or revoked.
70
+ },
71
+ async getAuthContext(user, payload) {
72
+ // Optional. Shapes what handlers receive as the auth context.
73
+ return { id: user.id, email: user.email, tenantId: user.tenantId };
74
+ },
75
+ async getUserContact(userId) {
76
+ // Optional. Used by `notify()` to find the email address of a user.
77
+ return { email: "user@example.com", name: "User" };
78
+ }
79
+ };
80
+
81
+ registerAuthProvider(authProvider);
82
+ ```
83
+
84
+ Import `registerAuthProvider` and the `AuthProvider` type from
85
+ `@/framework/facade.js` — never from `@/framework/...` internals. The facade is
86
+ the public API of the framework.
87
+
88
+ ## Module-to-module
89
+
90
+ Modules do not import each other. They talk through:
91
+
92
+ - `src/modules/<module>/facade.ts` — the public API of a module.
93
+ - `dispatchEvent` / `dispatchCommand` — for cross-module notifications.
94
+ - `src/providers/` — for app-wide implementations.
95
+
96
+ ## Contract reference
97
+
98
+ | Method | Required | Purpose |
99
+ | ----------------------------------- | -------- | -------------------------------------------------------------- |
100
+ | `getUserById(id)` | Yes | Loads a user. Any extra fields are passed through to the auth context. |
101
+ | `findRefreshToken(jti)` | Yes | Loads a stored refresh token by its JWT id. |
102
+ | `revokeRefreshToken(id)` | No | Revokes a refresh token. `deleteRefreshToken(id)` also works. |
103
+ | `getAuthContext(user, payload)` | No | Shapes the object handlers receive as `auth`. |
104
+ | `getUserContact(userId)` | No | Finds the email/name of a user, used by notifications. |
105
+
106
+ When no provider is registered and `src/modules/auth` was deleted, the auth
107
+ middleware logs a warning and lets the request through instead of crashing.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nexwire",
3
- "version": "4.2.1",
3
+ "version": "4.2.3",
4
4
  "type": "module",
5
5
  "description": "Scaffold a new nexwire project",
6
6
  "bin": {
@@ -235,4 +235,4 @@
235
235
  "optional": true
236
236
  }
237
237
  }
238
- }
238
+ }
package/src/index.js CHANGED
@@ -130,22 +130,30 @@ async function main() {
130
130
  console.log(`create-nexwire v${version}`);
131
131
  console.log();
132
132
  console.log("Usage:");
133
- console.log(" npm create nexwire@latest <project-name> [--engine=<hono|express>]");
134
- console.log(" pnpm create nexwire@latest <project-name> [--engine=<hono|express>]");
135
- console.log(" bun create nexwire@latest <project-name> [--engine=<hono|express>]");
136
- console.log(" npx create-nexwire@latest <project-name> [--engine=<hono|express>]");
133
+ console.log(" npm create nexwire@latest <project-name|.> [--engine=<hono|express>]");
134
+ console.log(" pnpm create nexwire@latest <project-name|.> [--engine=<hono|express>]");
135
+ console.log(" bun create nexwire@latest <project-name|.> [--engine=<hono|express>]");
136
+ console.log(" npx create-nexwire@latest <project-name|.> [--engine=<hono|express>]");
137
137
  console.log();
138
138
  console.log("Engines:");
139
139
  console.log(" hono (default) Hono-based HTTP layer and OpenAPI");
140
140
  console.log(" express Express-based HTTP layer and OpenAPI");
141
+ console.log();
142
+ console.log("Tip:");
143
+ console.log(" Use '.' to scaffold into the current directory (must be empty).");
141
144
  process.exit(0);
142
145
  }
143
146
 
144
147
  if (!projectName) {
145
- projectName = await ask("Project name: ");
148
+ projectName = await ask("Project name or '.' for the current directory: ");
149
+ }
150
+
151
+ if (!projectName) {
152
+ console.error("Error: Please provide a project name or '.' to scaffold into the current directory.");
153
+ process.exit(1);
146
154
  }
147
155
 
148
- if (!projectName || (projectName !== "." && !isValidProjectName(projectName))) {
156
+ if (projectName !== "." && !isValidProjectName(projectName)) {
149
157
  console.error("Error: Invalid project name. Use alphanumeric, dashes, or underscores, or '.' for the current directory.");
150
158
  process.exit(1);
151
159
  }