create-nexwire 4.1.2 → 4.2.1

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/gitignore-stub +31 -24
  2. package/express/package.json +2 -2
  3. package/express/src/database/migrations/sqlite/meta/0000_snapshot.json +411 -411
  4. package/express/src/database/migrations/sqlite/meta/_journal.json +12 -12
  5. package/express/src/framework/http/router.ts +16 -2
  6. package/express/src/framework/maker-cli/db/index.mjs +1 -1
  7. package/express/src/framework/maker-cli/deploy/core.mjs +0 -1
  8. package/express/src/framework/maker-cli/index.mjs +13 -3
  9. package/express/src/framework/maker-cli/module/core.mjs +121 -43
  10. package/express/src/framework/maker-cli/runtime/core.mjs +26 -7
  11. package/express/src/framework/maker-cli/runtime/index.mjs +120 -104
  12. package/express/src/framework/maker-cli/stubs/controller/openapi.ts.stub +6 -6
  13. package/express/src/framework/maker-cli/stubs/controller/plain.ts.stub +6 -6
  14. package/express/src/framework/maker-cli/stubs/helper/name.ts.stub +1 -1
  15. package/express/src/framework/maker-cli/stubs/seeder/name.ts.stub +17 -19
  16. package/express/src/framework/maker-cli/stubs/seeder/standalone.ts.stub +22 -0
  17. package/express/src/framework/maker-cli/stubs/service/name.plain.ts.stub +2 -2
  18. package/express/src/framework/maker-cli/stubs/service/name.ts.stub +1 -1
  19. package/express/src/framework/maker-cli/utils/help.mjs +374 -50
  20. package/express/src/framework/maker-cli/utils/naming.mjs +83 -20
  21. package/express/src/framework/maker-cli/utils/project-commands.mjs +140 -0
  22. package/express/src/framework/queue/queue.ts +402 -342
  23. package/express/src/modules/auth/database/seeders/role.ts +18 -18
  24. package/express/src/modules/auth/database/seeders/user.ts +35 -35
  25. package/express/vitest.config.ts +38 -23
  26. package/hono/gitignore-stub +31 -24
  27. package/hono/package.json +2 -2
  28. package/hono/src/database/migrations/sqlite/meta/0000_snapshot.json +411 -411
  29. package/hono/src/database/migrations/sqlite/meta/_journal.json +12 -12
  30. package/hono/src/framework/database/paginate.ts +253 -253
  31. package/hono/src/framework/http/router.ts +35 -14
  32. package/hono/src/framework/maker-cli/db/index.mjs +1 -1
  33. package/hono/src/framework/maker-cli/deploy/core.mjs +0 -1
  34. package/hono/src/framework/maker-cli/index.mjs +13 -3
  35. package/hono/src/framework/maker-cli/module/core.mjs +116 -38
  36. package/hono/src/framework/maker-cli/runtime/core.mjs +24 -6
  37. package/hono/src/framework/maker-cli/runtime/index.mjs +120 -104
  38. package/hono/src/framework/maker-cli/stubs/controller/openapi.ts.stub +6 -6
  39. package/hono/src/framework/maker-cli/stubs/controller/plain.ts.stub +6 -6
  40. package/hono/src/framework/maker-cli/stubs/helper/name.ts.stub +1 -1
  41. package/hono/src/framework/maker-cli/stubs/seeder/name.ts.stub +17 -19
  42. package/hono/src/framework/maker-cli/stubs/seeder/standalone.ts.stub +22 -0
  43. package/hono/src/framework/maker-cli/stubs/service/name.plain.ts.stub +2 -2
  44. package/hono/src/framework/maker-cli/stubs/service/name.ts.stub +1 -1
  45. package/hono/src/framework/maker-cli/utils/help.mjs +374 -50
  46. package/hono/src/framework/maker-cli/utils/naming.mjs +83 -20
  47. package/hono/src/framework/maker-cli/utils/project-commands.mjs +140 -0
  48. package/hono/src/framework/queue/queue.ts +402 -342
  49. package/hono/src/modules/auth/database/seeders/role.ts +18 -18
  50. package/hono/src/modules/auth/database/seeders/user.ts +35 -35
  51. package/hono/vitest.config.ts +38 -23
  52. package/package.json +1 -1
  53. package/express/src/framework/maker-cli/stubs/controller/openapi.with-model.ts.stub +0 -73
  54. package/hono/src/framework/maker-cli/stubs/controller/openapi.with-model.ts.stub +0 -69
@@ -1,13 +1,13 @@
1
- {
2
- "version": "7",
3
- "dialect": "sqlite",
4
- "entries": [
5
- {
6
- "idx": 0,
7
- "version": "6",
8
- "when": 1791141088797,
9
- "tag": "0000_init",
10
- "breakpoints": true
11
- }
12
- ]
1
+ {
2
+ "version": "7",
3
+ "dialect": "sqlite",
4
+ "entries": [
5
+ {
6
+ "idx": 0,
7
+ "version": "6",
8
+ "when": 1791141088797,
9
+ "tag": "0000_init",
10
+ "breakpoints": true
11
+ }
12
+ ]
13
13
  }
@@ -1,253 +1,253 @@
1
- import { count, getTableColumns, type SQL, sql } from "drizzle-orm";
2
- import { db } from "@/framework/database/connection.js";
3
-
4
- type PaginateOptions = {
5
- page?: number;
6
- perPage?: number;
7
- maxPerPage?: number;
8
- path?: string;
9
- where?: SQL<unknown>;
10
- orderBy?: unknown[];
11
- };
12
-
13
- type RequestLike = {
14
- req: {
15
- query: (key: string) => string | undefined;
16
- path: string;
17
- url?: string;
18
- };
19
- };
20
-
21
- type PaginateQueryOptions<T> = {
22
- page?: number;
23
- perPage?: number;
24
- maxPerPage?: number;
25
- path?: string;
26
- total: () => Promise<number>;
27
- data: (limit: number, offset: number) => Promise<T[]>;
28
- };
29
-
30
- type PaginateModelOptions<T = any> = {
31
- table?: any;
32
- query?: { findMany: (args: Record<string, any>) => Promise<T[]> };
33
- where?: SQL<unknown>;
34
- with?: Record<string, any>;
35
- columns?: Record<string, any>;
36
- extras?: Record<string, any>;
37
- orderBy?: unknown;
38
- page?: number;
39
- perPage?: number;
40
- maxPerPage?: number;
41
- path?: string;
42
- total?: () => Promise<number>;
43
- data?: (params: { limit: number; offset: number }) => Promise<T[]>;
44
- };
45
-
46
- export type PaginatedResult<T> = {
47
- current_page: number;
48
- data: T[];
49
- first_page_url: string | null;
50
- from: number | null;
51
- last_page: number;
52
- last_page_url: string | null;
53
- links: Array<{ url: string | null; label: string; page: number | null; active: boolean }>;
54
- next_page_url: string | null;
55
- path: string;
56
- per_page: number;
57
- prev_page_url: string | null;
58
- to: number | null;
59
- total: number;
60
- };
61
-
62
- function toPositiveInt(value: unknown, fallback: number) {
63
- const parsed = Number(value);
64
- if (!Number.isFinite(parsed) || parsed < 1) return fallback;
65
- return Math.floor(parsed);
66
- }
67
-
68
- function pageUrl(path: string, page: number, perPage: number) {
69
- if (!path) return null;
70
- const delimiter = path.includes("?") ? "&" : "?";
71
- return `${path}${delimiter}page=${page}&per_page=${perPage}`;
72
- }
73
-
74
- function resolvePath(c: RequestLike, path?: string) {
75
- if (path) return path;
76
- const fallbackPath = c.req.path;
77
- if (!c.req.url) return fallbackPath;
78
- try {
79
- const parsed = new URL(c.req.url);
80
- return `${parsed.origin}${parsed.pathname}`;
81
- } catch {
82
- return fallbackPath;
83
- }
84
- }
85
-
86
- /**
87
- * Why: Generic paginator for custom total/data callbacks.
88
- * When: You need pagination outside direct table helpers.
89
- * Where: Controllers/services composing complex queries.
90
- * How: Normalizes page params, computes URLs/meta, and executes callbacks.
91
- */
92
- export async function paginateQuery<T = any>(options: PaginateQueryOptions<T>): Promise<PaginatedResult<T>> {
93
- const maxPerPage = toPositiveInt(options.maxPerPage, 100);
94
- const perPage = Math.min(toPositiveInt(options.perPage, 15), maxPerPage);
95
- const page = toPositiveInt(options.page, 1);
96
-
97
- const total = Number(await options.total());
98
- const lastPage = Math.max(1, Math.ceil(total / perPage));
99
- const currentPage = Math.min(page, lastPage);
100
- const safeOffset = (currentPage - 1) * perPage;
101
- const data = await options.data(perPage, safeOffset);
102
-
103
- const from = total === 0 ? null : safeOffset + 1;
104
- const to = total === 0 ? null : safeOffset + data.length;
105
- const path = options.path || "";
106
- const firstPageUrl = pageUrl(path, 1, perPage);
107
- const lastPageUrl = pageUrl(path, lastPage, perPage);
108
- const prevPageUrl = currentPage > 1 ? pageUrl(path, currentPage - 1, perPage) : null;
109
- const nextPageUrl = currentPage < lastPage ? pageUrl(path, currentPage + 1, perPage) : null;
110
-
111
- const links: Array<{ url: string | null; label: string; page: number | null; active: boolean }> = [
112
- {
113
- url: prevPageUrl,
114
- label: "&laquo; Previous",
115
- page: currentPage > 1 ? currentPage - 1 : null,
116
- active: false
117
- }
118
- ];
119
-
120
- for (let p = 1; p <= lastPage; p += 1) {
121
- links.push({
122
- url: pageUrl(path, p, perPage),
123
- label: String(p),
124
- page: p,
125
- active: p === currentPage
126
- });
127
- }
128
-
129
- links.push({
130
- url: nextPageUrl,
131
- label: "Next &raquo;",
132
- page: currentPage < lastPage ? currentPage + 1 : null,
133
- active: false
134
- });
135
-
136
- return {
137
- current_page: currentPage,
138
- data,
139
- first_page_url: firstPageUrl,
140
- from,
141
- last_page: lastPage,
142
- last_page_url: lastPageUrl,
143
- links,
144
- next_page_url: nextPageUrl,
145
- path,
146
- per_page: perPage,
147
- prev_page_url: prevPageUrl,
148
- to,
149
- total
150
- };
151
- }
152
-
153
- /**
154
- * Why: Paginates a Drizzle query using request query params.
155
- * When: Route handlers receive `page/per_page` from client.
156
- * Where: API controllers returning list resources.
157
- * How: Builds count subquery and applies limit/offset to source query.
158
- */
159
- export async function paginate<T = any>(
160
- c: RequestLike,
161
- query: any,
162
- perPage = 15,
163
- options: { maxPerPage?: number; path?: string } = {}
164
- ): Promise<PaginatedResult<T>> {
165
- const page = Number(c.req.query("page") || 1);
166
- const size = c.req.query("size");
167
- const perPageQuery = c.req.query("per_page");
168
- const perPageValue = Number(size || perPageQuery || perPage);
169
- const resolvedPath = resolvePath(c, options.path);
170
-
171
- return paginateQuery<T>({
172
- page,
173
- perPage: perPageValue,
174
- maxPerPage: options.maxPerPage,
175
- path: resolvedPath,
176
- total: async () => {
177
- const inner = db.select({ val: sql`1` }).from(query.as("_inner"));
178
- const [row] = await db.select({ total: count() }).from(inner);
179
- return Number(row?.total ?? 0);
180
- },
181
- data: (limit, offset) => query.limit(limit).offset(offset)
182
- });
183
- }
184
-
185
- /**
186
- * Why: Laravel-style pagination for Drizzle relational queries.
187
- * When: Controllers need eager loading via `db.query.table.findMany({ with })`.
188
- * Where: Use beside `paginate`; this targets model/relational API, not select builders.
189
- * How: Counts the base table separately, then runs `findMany` with limit/offset.
190
- */
191
- export async function paginateModel<T = any>(c: RequestLike, options: PaginateModelOptions<T>): Promise<PaginatedResult<T>> {
192
- const page = Number(c.req.query("page") || options.page || 1);
193
- const size = c.req.query("size");
194
- const perPageQuery = c.req.query("per_page");
195
- const perPageValue = Number(size || perPageQuery || options.perPage || 15);
196
- const resolvedPath = resolvePath(c, options.path);
197
-
198
- return paginateQuery<T>({
199
- page,
200
- perPage: perPageValue,
201
- maxPerPage: options.maxPerPage,
202
- path: resolvedPath,
203
- total: async () => {
204
- if (options.total) return Number(await options.total());
205
- if (!options.table) throw new Error("paginateModel requires either table or total callback");
206
-
207
- let totalQuery = db.select({ total: count() }).from(options.table);
208
- if (options.where) totalQuery = totalQuery.where(options.where);
209
- const [row] = await totalQuery;
210
- return Number(row?.total ?? 0);
211
- },
212
- data: async (limit, offset) => {
213
- if (options.data) return options.data({ limit, offset });
214
- if (!options.query) throw new Error("paginateModel requires either query or data callback");
215
-
216
- const args: Record<string, any> = { limit, offset };
217
- if (options.where) args.where = options.where;
218
- if (options.with) args.with = options.with;
219
- if (options.columns) args.columns = options.columns;
220
- if (options.extras) args.extras = options.extras;
221
- if (options.orderBy) args.orderBy = options.orderBy;
222
-
223
- return options.query.findMany(args);
224
- }
225
- });
226
- }
227
-
228
- /**
229
- * Why: Paginates a full table with optional filters/sorting.
230
- * When: CRUD list endpoints need simple table pagination.
231
- * Where: Service/controller code using table helpers.
232
- * How: Runs total + paged select with where/order options.
233
- */
234
- export async function paginateTable<T = any>(db: any, table: any, options: PaginateOptions = {}): Promise<PaginatedResult<T>> {
235
- return paginateQuery<T>({
236
- page: options.page,
237
- perPage: options.perPage,
238
- maxPerPage: options.maxPerPage,
239
- path: options.path,
240
- total: async () => {
241
- let totalQuery = db.select({ total: count() }).from(table);
242
- if (options.where) totalQuery = totalQuery.where(options.where);
243
- const totalRow = await totalQuery;
244
- return Number(totalRow[0]?.total ?? 0);
245
- },
246
- data: async (limit, offset) => {
247
- let dataQuery = db.select(getTableColumns(table)).from(table);
248
- if (options.where) dataQuery = dataQuery.where(options.where);
249
- if (options.orderBy?.length) dataQuery = dataQuery.orderBy(...options.orderBy);
250
- return dataQuery.limit(limit).offset(offset);
251
- }
252
- });
253
- }
1
+ import { count, getTableColumns, type SQL, sql } from "drizzle-orm";
2
+ import { db } from "@/framework/database/connection.js";
3
+
4
+ type PaginateOptions = {
5
+ page?: number;
6
+ perPage?: number;
7
+ maxPerPage?: number;
8
+ path?: string;
9
+ where?: SQL<unknown>;
10
+ orderBy?: unknown[];
11
+ };
12
+
13
+ type RequestLike = {
14
+ req: {
15
+ query: (key: string) => string | undefined;
16
+ path: string;
17
+ url?: string;
18
+ };
19
+ };
20
+
21
+ type PaginateQueryOptions<T> = {
22
+ page?: number;
23
+ perPage?: number;
24
+ maxPerPage?: number;
25
+ path?: string;
26
+ total: () => Promise<number>;
27
+ data: (limit: number, offset: number) => Promise<T[]>;
28
+ };
29
+
30
+ type PaginateModelOptions<T = any> = {
31
+ table?: any;
32
+ query?: { findMany: (args: Record<string, any>) => Promise<T[]> };
33
+ where?: SQL<unknown>;
34
+ with?: Record<string, any>;
35
+ columns?: Record<string, any>;
36
+ extras?: Record<string, any>;
37
+ orderBy?: unknown;
38
+ page?: number;
39
+ perPage?: number;
40
+ maxPerPage?: number;
41
+ path?: string;
42
+ total?: () => Promise<number>;
43
+ data?: (params: { limit: number; offset: number }) => Promise<T[]>;
44
+ };
45
+
46
+ export type PaginatedResult<T> = {
47
+ current_page: number;
48
+ data: T[];
49
+ first_page_url: string | null;
50
+ from: number | null;
51
+ last_page: number;
52
+ last_page_url: string | null;
53
+ links: Array<{ url: string | null; label: string; page: number | null; active: boolean }>;
54
+ next_page_url: string | null;
55
+ path: string;
56
+ per_page: number;
57
+ prev_page_url: string | null;
58
+ to: number | null;
59
+ total: number;
60
+ };
61
+
62
+ function toPositiveInt(value: unknown, fallback: number) {
63
+ const parsed = Number(value);
64
+ if (!Number.isFinite(parsed) || parsed < 1) return fallback;
65
+ return Math.floor(parsed);
66
+ }
67
+
68
+ function pageUrl(path: string, page: number, perPage: number) {
69
+ if (!path) return null;
70
+ const delimiter = path.includes("?") ? "&" : "?";
71
+ return `${path}${delimiter}page=${page}&per_page=${perPage}`;
72
+ }
73
+
74
+ function resolvePath(c: RequestLike, path?: string) {
75
+ if (path) return path;
76
+ const fallbackPath = c.req.path;
77
+ if (!c.req.url) return fallbackPath;
78
+ try {
79
+ const parsed = new URL(c.req.url);
80
+ return `${parsed.origin}${parsed.pathname}`;
81
+ } catch {
82
+ return fallbackPath;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Why: Generic paginator for custom total/data callbacks.
88
+ * When: You need pagination outside direct table helpers.
89
+ * Where: Controllers/services composing complex queries.
90
+ * How: Normalizes page params, computes URLs/meta, and executes callbacks.
91
+ */
92
+ export async function paginateQuery<T = any>(options: PaginateQueryOptions<T>): Promise<PaginatedResult<T>> {
93
+ const maxPerPage = toPositiveInt(options.maxPerPage, 100);
94
+ const perPage = Math.min(toPositiveInt(options.perPage, 15), maxPerPage);
95
+ const page = toPositiveInt(options.page, 1);
96
+
97
+ const total = Number(await options.total());
98
+ const lastPage = Math.max(1, Math.ceil(total / perPage));
99
+ const currentPage = Math.min(page, lastPage);
100
+ const safeOffset = (currentPage - 1) * perPage;
101
+ const data = await options.data(perPage, safeOffset);
102
+
103
+ const from = total === 0 ? null : safeOffset + 1;
104
+ const to = total === 0 ? null : safeOffset + data.length;
105
+ const path = options.path || "";
106
+ const firstPageUrl = pageUrl(path, 1, perPage);
107
+ const lastPageUrl = pageUrl(path, lastPage, perPage);
108
+ const prevPageUrl = currentPage > 1 ? pageUrl(path, currentPage - 1, perPage) : null;
109
+ const nextPageUrl = currentPage < lastPage ? pageUrl(path, currentPage + 1, perPage) : null;
110
+
111
+ const links: Array<{ url: string | null; label: string; page: number | null; active: boolean }> = [
112
+ {
113
+ url: prevPageUrl,
114
+ label: "&laquo; Previous",
115
+ page: currentPage > 1 ? currentPage - 1 : null,
116
+ active: false
117
+ }
118
+ ];
119
+
120
+ for (let p = 1; p <= lastPage; p += 1) {
121
+ links.push({
122
+ url: pageUrl(path, p, perPage),
123
+ label: String(p),
124
+ page: p,
125
+ active: p === currentPage
126
+ });
127
+ }
128
+
129
+ links.push({
130
+ url: nextPageUrl,
131
+ label: "Next &raquo;",
132
+ page: currentPage < lastPage ? currentPage + 1 : null,
133
+ active: false
134
+ });
135
+
136
+ return {
137
+ current_page: currentPage,
138
+ data,
139
+ first_page_url: firstPageUrl,
140
+ from,
141
+ last_page: lastPage,
142
+ last_page_url: lastPageUrl,
143
+ links,
144
+ next_page_url: nextPageUrl,
145
+ path,
146
+ per_page: perPage,
147
+ prev_page_url: prevPageUrl,
148
+ to,
149
+ total
150
+ };
151
+ }
152
+
153
+ /**
154
+ * Why: Paginates a Drizzle query using request query params.
155
+ * When: Route handlers receive `page/per_page` from client.
156
+ * Where: API controllers returning list resources.
157
+ * How: Builds count subquery and applies limit/offset to source query.
158
+ */
159
+ export async function paginate<T = any>(
160
+ c: RequestLike,
161
+ query: any,
162
+ perPage = 15,
163
+ options: { maxPerPage?: number; path?: string } = {}
164
+ ): Promise<PaginatedResult<T>> {
165
+ const page = Number(c.req.query("page") || 1);
166
+ const size = c.req.query("size");
167
+ const perPageQuery = c.req.query("per_page");
168
+ const perPageValue = Number(size || perPageQuery || perPage);
169
+ const resolvedPath = resolvePath(c, options.path);
170
+
171
+ return paginateQuery<T>({
172
+ page,
173
+ perPage: perPageValue,
174
+ maxPerPage: options.maxPerPage,
175
+ path: resolvedPath,
176
+ total: async () => {
177
+ const inner = db.select({ val: sql`1` }).from(query.as("_inner"));
178
+ const [row] = await db.select({ total: count() }).from(inner);
179
+ return Number(row?.total ?? 0);
180
+ },
181
+ data: (limit, offset) => query.limit(limit).offset(offset)
182
+ });
183
+ }
184
+
185
+ /**
186
+ * Why: Pagination for Drizzle relational queries.
187
+ * When: Controllers need eager loading via `db.query.table.findMany({ with })`.
188
+ * Where: Use beside `paginate`; this targets model/relational API, not select builders.
189
+ * How: Counts the base table separately, then runs `findMany` with limit/offset.
190
+ */
191
+ export async function paginateModel<T = any>(c: RequestLike, options: PaginateModelOptions<T>): Promise<PaginatedResult<T>> {
192
+ const page = Number(c.req.query("page") || options.page || 1);
193
+ const size = c.req.query("size");
194
+ const perPageQuery = c.req.query("per_page");
195
+ const perPageValue = Number(size || perPageQuery || options.perPage || 15);
196
+ const resolvedPath = resolvePath(c, options.path);
197
+
198
+ return paginateQuery<T>({
199
+ page,
200
+ perPage: perPageValue,
201
+ maxPerPage: options.maxPerPage,
202
+ path: resolvedPath,
203
+ total: async () => {
204
+ if (options.total) return Number(await options.total());
205
+ if (!options.table) throw new Error("paginateModel requires either table or total callback");
206
+
207
+ let totalQuery = db.select({ total: count() }).from(options.table);
208
+ if (options.where) totalQuery = totalQuery.where(options.where);
209
+ const [row] = await totalQuery;
210
+ return Number(row?.total ?? 0);
211
+ },
212
+ data: async (limit, offset) => {
213
+ if (options.data) return options.data({ limit, offset });
214
+ if (!options.query) throw new Error("paginateModel requires either query or data callback");
215
+
216
+ const args: Record<string, any> = { limit, offset };
217
+ if (options.where) args.where = options.where;
218
+ if (options.with) args.with = options.with;
219
+ if (options.columns) args.columns = options.columns;
220
+ if (options.extras) args.extras = options.extras;
221
+ if (options.orderBy) args.orderBy = options.orderBy;
222
+
223
+ return options.query.findMany(args);
224
+ }
225
+ });
226
+ }
227
+
228
+ /**
229
+ * Why: Paginates a full table with optional filters/sorting.
230
+ * When: CRUD list endpoints need simple table pagination.
231
+ * Where: Service/controller code using table helpers.
232
+ * How: Runs total + paged select with where/order options.
233
+ */
234
+ export async function paginateTable<T = any>(db: any, table: any, options: PaginateOptions = {}): Promise<PaginatedResult<T>> {
235
+ return paginateQuery<T>({
236
+ page: options.page,
237
+ perPage: options.perPage,
238
+ maxPerPage: options.maxPerPage,
239
+ path: options.path,
240
+ total: async () => {
241
+ let totalQuery = db.select({ total: count() }).from(table);
242
+ if (options.where) totalQuery = totalQuery.where(options.where);
243
+ const totalRow = await totalQuery;
244
+ return Number(totalRow[0]?.total ?? 0);
245
+ },
246
+ data: async (limit, offset) => {
247
+ let dataQuery = db.select(getTableColumns(table)).from(table);
248
+ if (options.where) dataQuery = dataQuery.where(options.where);
249
+ if (options.orderBy?.length) dataQuery = dataQuery.orderBy(...options.orderBy);
250
+ return dataQuery.limit(limit).offset(offset);
251
+ }
252
+ });
253
+ }
@@ -11,47 +11,71 @@ export type NexwireRouter = OpenAPIHono & {
11
11
  * When: Building root app and module route files.
12
12
  * Where: HTTP app bootstrap and route stubs.
13
13
  * How: Wraps OpenAPIHono and adds group/api convenience APIs.
14
+ *
15
+ * group() records its middlewares here instead of calling this.use("*", mw).
16
+ * A use("*") is a route entry: mounting the router copies it and "*" rewrites
17
+ * to the mount prefix, so a limiter declared on one sub-router also ran for its
18
+ * siblings and for every route added after the mount. Recorded middlewares are
19
+ * merged into each .api() call instead, so they travel with the routes they
20
+ * were declared on and reach nothing else.
14
21
  */
15
22
  export function createRouter(): NexwireRouter {
16
23
  const router = new OpenAPIHono({ strict: false, defaultHook }) as NexwireRouter;
17
24
  const baseOpenapi = router.openapi.bind(router);
18
25
 
26
+ // Store group middlewares on the instance
27
+ const groupMiddlewares: any[] = [];
28
+
19
29
  router.group = function (...middlewares) {
30
+ // Accumulate group middlewares (supports multiple .group() calls)
20
31
  for (const middleware of middlewares) {
21
- this.use("*", middleware);
32
+ groupMiddlewares.push(middleware);
22
33
  }
23
-
24
34
  return this;
25
35
  };
26
36
 
27
37
  router.api = function (route: any, handlerOrMiddlewares: any, handler?: any) {
38
+ // Normalize arguments:
39
+ // .api(route, handler) → [groupMiddlewares], handler
40
+ // .api(route, [mw1, mw2], handler) → [groupMiddlewares, mw1, mw2], handler
41
+ let routeMiddlewares: any[] = [];
42
+ let finalHandler: any;
43
+
28
44
  if (!Array.isArray(handlerOrMiddlewares)) {
29
- baseOpenapi(route, handlerOrMiddlewares);
30
- return this;
45
+ finalHandler = handlerOrMiddlewares;
46
+ } else {
47
+ routeMiddlewares = handlerOrMiddlewares;
48
+ finalHandler = handler;
31
49
  }
32
50
 
33
- const middlewares = handlerOrMiddlewares;
34
- const finalHandler = handler;
51
+ // Combine: group middleware runs first, then route middleware
52
+ const allMiddlewares = [...groupMiddlewares, ...routeMiddlewares];
35
53
 
36
54
  if (typeof finalHandler !== "function") {
37
55
  throw new Error("api(route, middlewares, handler) requires a handler function");
38
56
  }
39
57
 
58
+ // No middleware at all → simple pass-through
59
+ if (allMiddlewares.length === 0) {
60
+ baseOpenapi(route, finalHandler);
61
+ return this;
62
+ }
63
+
64
+ // Wrap middleware chain into the handler — travels with the route,
65
+ // no matter where the router is mounted.
40
66
  const wrappedHandler = async (c: any) => {
41
67
  let index = 0;
42
68
 
43
69
  const next = async (): Promise<any> => {
44
- if (index < middlewares.length) {
45
- const middleware = middlewares[index++];
70
+ if (index < allMiddlewares.length) {
71
+ const middleware = allMiddlewares[index++];
46
72
  let downstream: any;
47
73
  const result = await middleware(c, async () => {
48
74
  downstream = await next();
49
75
  return downstream;
50
76
  });
51
-
52
77
  return result === undefined ? downstream : result;
53
78
  }
54
-
55
79
  return await finalHandler(c);
56
80
  };
57
81
 
@@ -66,10 +90,7 @@ export function createRouter(): NexwireRouter {
66
90
  }
67
91
 
68
92
  /**
69
- * Why: Shorthand for creating grouped router middleware chain.
70
- * When: Route files need shared middleware composition.
71
- * Where: Module route declarations.
72
- * How: Creates a router instance and applies all provided middlewares.
93
+ * Shorthand for creating a router with group middleware pre-applied.
73
94
  */
74
95
  export function group(...middlewares: any[]) {
75
96
  return createRouter().group(...middlewares);
@@ -42,7 +42,7 @@ function assertName(value, label) {
42
42
 
43
43
  function unsupported() {
44
44
  console.error(
45
- 'Drizzle Kit does not support Laravel-style rollback/refresh migrations. Use "npm run maker db:fresh -- --seed" (or "bun maker db:fresh --seed") for a SQLite development reset, or manage rollback SQL manually for production databases.'
45
+ 'Drizzle Kit does not support rollback/refresh migrations. Use "npm run maker db:fresh -- --seed" (or "bun maker db:fresh --seed") for a SQLite development reset, or manage rollback SQL manually for production databases.'
46
46
  );
47
47
  process.exit(1);
48
48
  }
@@ -69,7 +69,6 @@ const STUBS = {
69
69
  },
70
70
  controller: {
71
71
  openapi: "controller/openapi.ts.stub",
72
- openapiWithModel: "controller/openapi.with-model.ts.stub",
73
72
  plain: "controller/plain.ts.stub",
74
73
  schema: {
75
74
  openapi: "controller/schema.ts.stub",