prisma-generator-express 1.50.0 → 1.51.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "prisma-generator-express",
3
3
  "description": "Prisma generator for Express, Fastify, and Hono CRUD APIs with OpenAPI documentation",
4
- "version": "1.50.0",
4
+ "version": "1.51.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "license": "MIT",
@@ -21,6 +21,7 @@ type ViewDef = {
21
21
  defaultLimit?: number
22
22
  maxLimit?: number
23
23
  orderBy?: OrderByDef
24
+ allowedOrderBy?: string[]
24
25
  authorize?: (
25
26
  req: Request,
26
27
  viewName: string,
@@ -102,6 +103,47 @@ const buildOrderBy = (orderBy?: OrderByDef): string => {
102
103
  )
103
104
  }
104
105
 
106
+ const parseOrderByParam = (raw: unknown): OrderByDef | undefined => {
107
+ if (raw === undefined || raw === null || raw === '') return undefined
108
+ if (typeof raw !== 'string') return undefined
109
+ if (raw.startsWith('{') || raw.startsWith('[')) {
110
+ let parsed: unknown
111
+ try {
112
+ parsed = JSON.parse(raw)
113
+ } catch {
114
+ throw new HttpError(400, 'invalid orderBy JSON')
115
+ }
116
+ if (typeof parsed === 'string') return parsed
117
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
118
+ const keys = Object.keys(parsed as Record<string, unknown>)
119
+ if (keys.length === 0) return undefined
120
+ const field = keys[0]
121
+ const dirRaw = (parsed as Record<string, unknown>)[field]
122
+ if (dirRaw === 'asc' || dirRaw === 'desc')
123
+ return { field, direction: dirRaw }
124
+ if (dirRaw && typeof dirRaw === 'object') {
125
+ const sort = (dirRaw as { sort?: unknown }).sort
126
+ const nulls = (dirRaw as { nulls?: unknown }).nulls
127
+ const direction = sort === 'asc' || sort === 'desc' ? sort : undefined
128
+ const nullsOrder =
129
+ nulls === 'first' || nulls === 'last' ? nulls : undefined
130
+ return { field, direction, nulls: nullsOrder }
131
+ }
132
+ return { field }
133
+ }
134
+ throw new HttpError(400, 'invalid orderBy shape')
135
+ }
136
+ return raw
137
+ }
138
+
139
+ const enforceAllowlist = (orderBy: OrderByDef, allowed?: string[]): void => {
140
+ if (!allowed) return
141
+ const field = typeof orderBy === 'string' ? orderBy : orderBy.field
142
+ if (!allowed.includes(field)) {
143
+ throw new HttpError(400, 'orderBy field not allowed: ' + field)
144
+ }
145
+ }
146
+
105
147
  export const materializedViewsRouter = (
106
148
  opts: MaterializedRouterOptions,
107
149
  ): Router => {
@@ -130,7 +172,11 @@ export const materializedViewsRouter = (
130
172
  )
131
173
  const skip = clampInt(req.query.skip, 0, 0, Number.MAX_SAFE_INTEGER)
132
174
 
133
- if (skip > 0 && !def.orderBy) {
175
+ const queryOrderBy = parseOrderByParam(req.query.orderBy)
176
+ if (queryOrderBy) enforceAllowlist(queryOrderBy, def.allowedOrderBy)
177
+ const effectiveOrderBy = queryOrderBy ?? def.orderBy
178
+
179
+ if (skip > 0 && !effectiveOrderBy) {
134
180
  throw new HttpError(
135
181
  400,
136
182
  'skip requires orderBy for deterministic pagination',
@@ -140,7 +186,7 @@ export const materializedViewsRouter = (
140
186
  const sql =
141
187
  'SELECT * FROM ' +
142
188
  buildFqn(def) +
143
- buildOrderBy(def.orderBy) +
189
+ buildOrderBy(effectiveOrderBy) +
144
190
  ' LIMIT $1 OFFSET $2'
145
191
 
146
192
  const rows = await opts.prisma.$queryRawUnsafe<unknown[]>(