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.
Files changed (2) hide show
  1. package/README.md +216 -0
  2. 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.49.1",
4
+ "version": "1.50.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "license": "MIT",