prisma-generator-express 1.49.1 → 1.50.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 +216 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ Running `npx prisma generate` produces:
|
|
|
13
13
|
- Router generator with middleware support (before/after hooks per operation)
|
|
14
14
|
- POST read endpoints for all read operations (for complex queries exceeding URL length limits)
|
|
15
15
|
- Express-only progressive read streaming over Server-Sent Events (SSE), using manual stages or auto-include splitting for single-record relation reads
|
|
16
|
+
- Express-only standalone materialized view router for read-only access to registered PostgreSQL materialized views
|
|
16
17
|
- OpenAPI 3.1 spec (JSON and YAML endpoints registered automatically per router)
|
|
17
18
|
- Documentation helpers for contract view and Scalar UI (require manual mounting)
|
|
18
19
|
- Client-side query parameter encoder
|
|
@@ -34,6 +35,7 @@ Supports **Express**, **Fastify**, and **Hono** targets via the `target` configu
|
|
|
34
35
|
- [Request body format](#request-body-format)
|
|
35
36
|
- [Query encoding (client side)](#query-encoding-client-side)
|
|
36
37
|
- [POST read endpoints](#post-read-endpoints)
|
|
38
|
+
- [Materialized views router (Express)](#materialized-views-router-express)
|
|
37
39
|
- [Progressive Endpoint Composition (Express SSE)](#progressive-endpoint-composition-express-sse)
|
|
38
40
|
- [Response shaping: select, include, omit](#response-shaping-select-include-omit)
|
|
39
41
|
- [BigInt and Decimal handling](#bigint-and-decimal-handling)
|
|
@@ -1327,6 +1329,220 @@ app.use('/', UserRouter({
|
|
|
1327
1329
|
}))
|
|
1328
1330
|
```
|
|
1329
1331
|
|
|
1332
|
+
## Materialized views router (Express)
|
|
1333
|
+
|
|
1334
|
+
The Express target includes a standalone helper for read-only access to PostgreSQL materialized views.
|
|
1335
|
+
|
|
1336
|
+
Materialized views are not Prisma models and do not have Prisma delegates, so they are not generated as normal per-model routers. Instead, mount one standalone router and provide an explicit registry of allowed views.
|
|
1337
|
+
|
|
1338
|
+
This feature is **Express-only**.
|
|
1339
|
+
|
|
1340
|
+
### Usage
|
|
1341
|
+
|
|
1342
|
+
```ts
|
|
1343
|
+
import express from 'express'
|
|
1344
|
+
import { PrismaClient } from '@prisma/client'
|
|
1345
|
+
import { materializedViewsRouter } from './generated/materializedRouter'
|
|
1346
|
+
|
|
1347
|
+
const prisma = new PrismaClient()
|
|
1348
|
+
const app = express()
|
|
1349
|
+
|
|
1350
|
+
app.use('/api', materializedViewsRouter({
|
|
1351
|
+
prisma,
|
|
1352
|
+
basePath: '/materialized',
|
|
1353
|
+
views: {
|
|
1354
|
+
jobAdStats: {
|
|
1355
|
+
relation: 'mv_jobad_stats',
|
|
1356
|
+
orderBy: {
|
|
1357
|
+
field: 'updatedAt',
|
|
1358
|
+
direction: 'desc',
|
|
1359
|
+
},
|
|
1360
|
+
},
|
|
1361
|
+
companyStats: {
|
|
1362
|
+
relation: 'mv_company_stats',
|
|
1363
|
+
},
|
|
1364
|
+
},
|
|
1365
|
+
}))
|
|
1366
|
+
|
|
1367
|
+
app.listen(3000)
|
|
1368
|
+
```
|
|
1369
|
+
|
|
1370
|
+
This registers:
|
|
1371
|
+
|
|
1372
|
+
```http
|
|
1373
|
+
GET /api/materialized/jobAdStats?take=50&skip=0
|
|
1374
|
+
GET /api/materialized/companyStats?take=50
|
|
1375
|
+
```
|
|
1376
|
+
|
|
1377
|
+
### View registry
|
|
1378
|
+
|
|
1379
|
+
Each key in `views` is the public API name. The `relation` value is the actual database relation name.
|
|
1380
|
+
|
|
1381
|
+
```ts
|
|
1382
|
+
views: {
|
|
1383
|
+
jobAdStats: {
|
|
1384
|
+
relation: 'mv_jobad_stats',
|
|
1385
|
+
schema: 'public',
|
|
1386
|
+
defaultLimit: 50,
|
|
1387
|
+
maxLimit: 500,
|
|
1388
|
+
orderBy: {
|
|
1389
|
+
field: 'updatedAt',
|
|
1390
|
+
direction: 'desc',
|
|
1391
|
+
nulls: 'last',
|
|
1392
|
+
},
|
|
1393
|
+
},
|
|
1394
|
+
}
|
|
1395
|
+
```
|
|
1396
|
+
|
|
1397
|
+
Supported view options:
|
|
1398
|
+
|
|
1399
|
+
| Option | Type | Description |
|
|
1400
|
+
| ------ | ---- | ----------- |
|
|
1401
|
+
| `relation` | `string` | Database materialized view name |
|
|
1402
|
+
| `schema` | `string` | Optional schema name |
|
|
1403
|
+
| `defaultLimit` | `number` | Default page size for this view |
|
|
1404
|
+
| `maxLimit` | `number` | Maximum page size for this view |
|
|
1405
|
+
| `orderBy` | `string \| object` | Deterministic sort column |
|
|
1406
|
+
| `authorize` | `function` | Optional per-view authorization hook |
|
|
1407
|
+
|
|
1408
|
+
`orderBy` can be a string:
|
|
1409
|
+
|
|
1410
|
+
```ts
|
|
1411
|
+
orderBy: 'updatedAt'
|
|
1412
|
+
```
|
|
1413
|
+
|
|
1414
|
+
Or an object:
|
|
1415
|
+
|
|
1416
|
+
```ts
|
|
1417
|
+
orderBy: {
|
|
1418
|
+
field: 'updatedAt',
|
|
1419
|
+
direction: 'desc',
|
|
1420
|
+
nulls: 'last',
|
|
1421
|
+
}
|
|
1422
|
+
```
|
|
1423
|
+
|
|
1424
|
+
### Router options
|
|
1425
|
+
|
|
1426
|
+
```ts
|
|
1427
|
+
materializedViewsRouter({
|
|
1428
|
+
prisma,
|
|
1429
|
+
basePath: '/materialized',
|
|
1430
|
+
defaultLimit: 50,
|
|
1431
|
+
maxLimit: 1000,
|
|
1432
|
+
before: [authMiddleware],
|
|
1433
|
+
after: [auditMiddleware],
|
|
1434
|
+
views: {
|
|
1435
|
+
jobAdStats: { relation: 'mv_jobad_stats' },
|
|
1436
|
+
},
|
|
1437
|
+
})
|
|
1438
|
+
```
|
|
1439
|
+
|
|
1440
|
+
Supported router options:
|
|
1441
|
+
|
|
1442
|
+
| Option | Type | Description |
|
|
1443
|
+
| ------ | ---- | ----------- |
|
|
1444
|
+
| `prisma` | Prisma client-like object | Must expose `$queryRawUnsafe` |
|
|
1445
|
+
| `views` | `Record<string, ViewDef>` | Registry of allowed materialized views |
|
|
1446
|
+
| `basePath` | `string` | Path inside this router, default `''` |
|
|
1447
|
+
| `defaultLimit` | `number` | Global default page size, default `50` |
|
|
1448
|
+
| `maxLimit` | `number` | Global max page size, default `1000` |
|
|
1449
|
+
| `before` | `RequestHandler[]` | Express middleware before the query |
|
|
1450
|
+
| `after` | `RequestHandler[]` | Express middleware after the query |
|
|
1451
|
+
|
|
1452
|
+
### Authorization
|
|
1453
|
+
|
|
1454
|
+
Use `before` for shared middleware and `authorize` for per-view checks.
|
|
1455
|
+
|
|
1456
|
+
```ts
|
|
1457
|
+
const forbidden = (message: string) => Object.assign(new Error(message), { status: 403 })
|
|
1458
|
+
|
|
1459
|
+
app.use('/api', materializedViewsRouter({
|
|
1460
|
+
prisma,
|
|
1461
|
+
basePath: '/materialized',
|
|
1462
|
+
before: [requireAuth],
|
|
1463
|
+
views: {
|
|
1464
|
+
publicStats: {
|
|
1465
|
+
relation: 'mv_public_stats',
|
|
1466
|
+
},
|
|
1467
|
+
adminStats: {
|
|
1468
|
+
relation: 'mv_admin_stats',
|
|
1469
|
+
authorize: (req) => {
|
|
1470
|
+
if (req.user?.role !== 'admin') {
|
|
1471
|
+
throw forbidden('Forbidden')
|
|
1472
|
+
}
|
|
1473
|
+
},
|
|
1474
|
+
},
|
|
1475
|
+
},
|
|
1476
|
+
}))
|
|
1477
|
+
```
|
|
1478
|
+
|
|
1479
|
+
If a view is not in the registry, the router returns:
|
|
1480
|
+
|
|
1481
|
+
```json
|
|
1482
|
+
{ "message": "unknown view" }
|
|
1483
|
+
```
|
|
1484
|
+
|
|
1485
|
+
### Pagination
|
|
1486
|
+
|
|
1487
|
+
The router supports `take` and `skip` query parameters:
|
|
1488
|
+
|
|
1489
|
+
```http
|
|
1490
|
+
GET /materialized/jobAdStats?take=100&skip=200
|
|
1491
|
+
```
|
|
1492
|
+
|
|
1493
|
+
`take` is clamped to the configured max limit. `skip` is clamped to zero or greater.
|
|
1494
|
+
|
|
1495
|
+
When `skip > 0`, the view must define `orderBy`. This prevents unstable offset pagination.
|
|
1496
|
+
|
|
1497
|
+
```ts
|
|
1498
|
+
views: {
|
|
1499
|
+
jobAdStats: {
|
|
1500
|
+
relation: 'mv_jobad_stats',
|
|
1501
|
+
orderBy: {
|
|
1502
|
+
field: 'updatedAt',
|
|
1503
|
+
direction: 'desc',
|
|
1504
|
+
},
|
|
1505
|
+
},
|
|
1506
|
+
}
|
|
1507
|
+
```
|
|
1508
|
+
|
|
1509
|
+
### Identifier safety
|
|
1510
|
+
|
|
1511
|
+
Only registered view names can be queried. Database identifiers such as `schema`, `relation`, and `orderBy.field` must match this pattern:
|
|
1512
|
+
|
|
1513
|
+
```txt
|
|
1514
|
+
^[A-Za-z_][A-Za-z0-9_]*$
|
|
1515
|
+
```
|
|
1516
|
+
|
|
1517
|
+
Identifiers are double-quoted before being used in SQL.
|
|
1518
|
+
|
|
1519
|
+
This means camel-case database columns must exist as quoted identifiers. For example, `orderBy: 'updatedAt'` queries `"updatedAt"`. If the materialized view was created without a quoted alias, PostgreSQL stores the column as `updatedat`.
|
|
1520
|
+
|
|
1521
|
+
### Response serialization
|
|
1522
|
+
|
|
1523
|
+
Responses use the same serialization behavior as generated routers:
|
|
1524
|
+
|
|
1525
|
+
- `BigInt` values become strings
|
|
1526
|
+
- `Decimal` values become strings
|
|
1527
|
+
- `Buffer` and `Uint8Array` values become base64 strings
|
|
1528
|
+
- `DateTime` values become ISO 8601 strings
|
|
1529
|
+
|
|
1530
|
+
### Limitations
|
|
1531
|
+
|
|
1532
|
+
The materialized views router is intentionally small and read-only.
|
|
1533
|
+
|
|
1534
|
+
It does not support:
|
|
1535
|
+
|
|
1536
|
+
- Prisma `where`
|
|
1537
|
+
- Prisma `select`
|
|
1538
|
+
- Prisma `include`
|
|
1539
|
+
- Prisma guard shapes
|
|
1540
|
+
- OpenAPI generation
|
|
1541
|
+
- Fastify or Hono targets
|
|
1542
|
+
- refreshing materialized views
|
|
1543
|
+
|
|
1544
|
+
Use it for explicit read-only endpoints over known materialized views. For normal Prisma models, use the generated model routers.
|
|
1545
|
+
|
|
1330
1546
|
## Progressive Endpoint Composition (Express SSE)
|
|
1331
1547
|
|
|
1332
1548
|
Progressive Endpoint Composition lets an Express read endpoint stream partial response fields over Server-Sent Events while still ending with a final result event.
|
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.
|
|
4
|
+
"version": "1.50.0",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"license": "MIT",
|