@prismakit/cli 3.2.2 → 4.0.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/dist/bin.cjs +8 -9
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-MY5VVL5C.js → chunk-MCTLXSRF.js} +9 -10
- package/dist/chunk-MCTLXSRF.js.map +1 -0
- package/dist/index.cjs +8 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +12 -12
- package/rules/data-access.mdc +1 -1
- package/skills/prismakit/SKILL.md +21 -20
- package/skills/prismakit/examples.md +0 -2
- package/skills/prismakit/reference.md +17 -44
- package/skills/prismakit/review-checklist.md +2 -3
- package/skills/prismakit-nestjs/SKILL.md +17 -13
- package/skills/prismakit-nestjs/examples.md +3 -2
- package/skills/prismakit-nestjs/reference.md +12 -52
- package/skills/prismakit-nestjs/review-checklist.md +1 -2
- package/src/__tests__/naming.spec.ts +5 -2
- package/src/commands/skills.ts +0 -1
- package/src/templates.ts +7 -10
- package/dist/chunk-MY5VVL5C.js.map +0 -1
|
@@ -11,7 +11,7 @@ description: >-
|
|
|
11
11
|
|
|
12
12
|
Framework-agnostic Prisma repository kit. Not a Prisma fork. Only repositories talk to Prisma.
|
|
13
13
|
|
|
14
|
-
NestJS apps: also load skill `prismakit-nestjs` after this contract.
|
|
14
|
+
**Line:** 4.0 (pre-stable). NestJS apps: also load skill `prismakit-nestjs` after this contract.
|
|
15
15
|
|
|
16
16
|
## Non-negotiables
|
|
17
17
|
|
|
@@ -29,10 +29,10 @@ Violations are bugs. Enforce with `@prismakit/eslint-plugin` + this skill.
|
|
|
29
29
|
|
|
30
30
|
| Required | How |
|
|
31
31
|
|----------|-----|
|
|
32
|
-
| Reads/writes | `*Repository` from `createRepository` / Nest
|
|
32
|
+
| Reads/writes | `*Repository` from `createRepository` / Nest `createDefineRepo` |
|
|
33
33
|
| Tx writes | `invalidate: 'none'` then `invalidateCache` after commit |
|
|
34
34
|
| User-facing reads | `setCache: true` when the repo has cache config |
|
|
35
|
-
| Relations in `select` | `model` + Prisma meta loaded (or `
|
|
35
|
+
| Relations in `select` | `model` + Prisma meta loaded (`loadPrismaMetaFromSchema` / `loadPrismaMetaFromDmmf` or Nest `schemaPath` / `dmmf`) |
|
|
36
36
|
| ESLint | `prismakit.configs.recommended` |
|
|
37
37
|
|
|
38
38
|
## Layers
|
|
@@ -47,7 +47,7 @@ Helpers may inject repositories — never the Prisma client.
|
|
|
47
47
|
|
|
48
48
|
| Task | Use |
|
|
49
49
|
|------|-----|
|
|
50
|
-
| New repository | `createRepository` (core) or Nest `
|
|
50
|
+
| New repository | `createRepository` (core) or Nest `defineAppRepo` — see `prismakit-nestjs` |
|
|
51
51
|
| User-facing get by id | `getThrowById` / `getById` + `setCache: true` |
|
|
52
52
|
| Existence / uniqueness / auth | `getFirst` — **no** `setCache` |
|
|
53
53
|
| List | `getMany` + `setCache: true` + optional `cacheTags` |
|
|
@@ -65,15 +65,18 @@ Helpers may inject repositories — never the Prisma client.
|
|
|
65
65
|
|
|
66
66
|
```typescript
|
|
67
67
|
import { PrismaClient } from '@prisma/client';
|
|
68
|
-
import { createRepository } from '@prismakit/core';
|
|
68
|
+
import { createRepository, loadPrismaMetaFromSchema } from '@prismakit/core';
|
|
69
69
|
import { RedisCacheAdapter } from '@prismakit/redis';
|
|
70
70
|
|
|
71
|
+
loadPrismaMetaFromSchema('prisma/schema.prisma');
|
|
72
|
+
// Prisma 5/6: loadPrismaMetaFromDmmf(Prisma.dmmf)
|
|
73
|
+
|
|
71
74
|
const DAY = 86_400;
|
|
72
75
|
|
|
73
76
|
const UserRepoClass = createRepository({
|
|
74
77
|
model: 'user',
|
|
75
78
|
cache: { ttl: DAY, nullTtl: 60, sensitiveFields: ['password'] },
|
|
76
|
-
lock: true, // table + columns
|
|
79
|
+
lock: true, // table + columns from Prisma meta
|
|
77
80
|
});
|
|
78
81
|
|
|
79
82
|
const prisma = new PrismaClient();
|
|
@@ -81,19 +84,16 @@ const cache = new RedisCacheAdapter({ prefix: 'myapp' });
|
|
|
81
84
|
export const users = new UserRepoClass({ prisma, cache });
|
|
82
85
|
```
|
|
83
86
|
|
|
84
|
-
|
|
87
|
+
Public factory: **`createRepository` only** (no `defineRepository` / `createPrismaRepository` aliases).
|
|
85
88
|
|
|
86
|
-
**NestJS apps:** use `createDefineRepo` / `defineAppRepo`
|
|
89
|
+
**NestJS apps:** use `createDefineRepo` / `defineAppRepo` — see skill `prismakit-nestjs`.
|
|
87
90
|
|
|
88
91
|
| Option | Description |
|
|
89
92
|
|--------|-------------|
|
|
90
|
-
| `model` | Prisma client key (`prisma.user` → `'user'`).
|
|
91
|
-
| `scalarFields` | Usually `Prisma.XScalarFieldEnum`. **Optional** when `schemaPath` / DMMF meta is loaded (default since 3.1). |
|
|
93
|
+
| `model` | Prisma client key (`prisma.user` → `'user'`). **Required.** |
|
|
92
94
|
| `cache` | `CacheOptions` or `true` (uses app defaults when bound via `createDefineRepo`). |
|
|
93
|
-
| `lock` | `true`
|
|
94
|
-
| `
|
|
95
|
-
| `schemaPath` | Path to `schema.prisma` when meta is not loaded globally. Default: `prisma/schema.prisma`. |
|
|
96
|
-
| `getDelegate` | Optional. Defaults to `(c) => c[model]`. |
|
|
95
|
+
| `lock` | `true` (from meta) or `{ tableName, columns? }`. |
|
|
96
|
+
| `toPayload` | Optional payload mapper (default identity). |
|
|
97
97
|
|
|
98
98
|
Put files under `**/repositories/**`. Keep Prisma client construction under `**/infrastructure/prisma/**`.
|
|
99
99
|
|
|
@@ -167,7 +167,7 @@ If Redis is down, `RedisCacheAdapter` **fails open** — queries still hit Prism
|
|
|
167
167
|
| Auth, uniqueness, JWT lookup | omit / `false` |
|
|
168
168
|
| Inside `tx` | ignored |
|
|
169
169
|
|
|
170
|
-
Repository `cache` is the source of truth.
|
|
170
|
+
Repository `cache` is the source of truth. There is no Nest `cacheModels` allowlist in 4.0.
|
|
171
171
|
|
|
172
172
|
`setCache` / `cacheTags` / `invalidate` / `invalidateCache` exist on the type **only** when the repo has `cache` config. Do not force them on uncached repos.
|
|
173
173
|
|
|
@@ -200,9 +200,9 @@ await posts.getThrowById({
|
|
|
200
200
|
});
|
|
201
201
|
```
|
|
202
202
|
|
|
203
|
-
Requirements: source repo has `model`;
|
|
203
|
+
Requirements: source repo has `model`; Prisma meta loaded; related model repos are registered.
|
|
204
204
|
|
|
205
|
-
AutoComposer injects the target primary key into nested selects even if omitted. Relation field names resolve from schema / DMMF meta
|
|
205
|
+
AutoComposer injects the target primary key into nested selects even if omitted. Relation field names resolve from schema / DMMF meta.
|
|
206
206
|
|
|
207
207
|
## Row locks
|
|
208
208
|
|
|
@@ -244,9 +244,10 @@ Allowed Prisma usage: `**/repositories/**`, `**/infrastructure/prisma/**`. Rules
|
|
|
244
244
|
|
|
245
245
|
## Observability
|
|
246
246
|
|
|
247
|
-
- Core: `setTelemetry({ enabled, onEvent, slowThreshold })`
|
|
248
|
-
-
|
|
249
|
-
-
|
|
247
|
+
- Core: `setTelemetry({ enabled, onEvent, slowThreshold })`
|
|
248
|
+
- Nest: `telemetry: { enabled, slowThreshold, onSlowQuery, onEvent }`
|
|
249
|
+
- Optional: `@prismakit/opentelemetry` → `createPrismaKitTelemetry({ slowThreshold })`
|
|
250
|
+
- Events: `cache.hit` / `cache.miss` / `cache.bypass` / `cache.invalidate` / `cache.error`, `compose.*`, `lock.*`, `stampede.*`, `query.complete` / `query.slow`
|
|
250
251
|
|
|
251
252
|
## Clean code
|
|
252
253
|
|
|
@@ -23,7 +23,6 @@ export const userSelectPresets = {
|
|
|
23
23
|
|
|
24
24
|
export const UserRepoClass = createRepository({
|
|
25
25
|
model: 'user',
|
|
26
|
-
// scalarFields: optional when schemaPath/DMMF meta is loaded (default since 3.1)
|
|
27
26
|
cache: {
|
|
28
27
|
ttl: DAY,
|
|
29
28
|
nullTtl: 60,
|
|
@@ -202,7 +201,6 @@ import { MemoryCacheAdapter } from '@prismakit/memory';
|
|
|
202
201
|
|
|
203
202
|
const UserRepo = createRepository({
|
|
204
203
|
model: 'user',
|
|
205
|
-
scalarFields: { id: 'id', email: 'email', name: 'name' },
|
|
206
204
|
cache: { ttl: 60 },
|
|
207
205
|
});
|
|
208
206
|
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# PrismaKit core reference
|
|
2
2
|
|
|
3
|
-
API surface for `@prismakit/core`
|
|
3
|
+
API surface for `@prismakit/core` **4.0** (pre-stable). Read [SKILL.md](SKILL.md) first.
|
|
4
4
|
|
|
5
5
|
## Packages
|
|
6
6
|
|
|
7
7
|
| Package | Role |
|
|
8
8
|
|---------|------|
|
|
9
9
|
| `@prismakit/core` | `createRepository`, AutoComposer, locks, pagination, `CacheAdapter` |
|
|
10
|
-
| `@prismakit/redis` | `RedisCacheAdapter` |
|
|
10
|
+
| `@prismakit/redis` | `RedisCacheAdapter`, `createRedisJsonReviver` |
|
|
11
11
|
| `@prismakit/memory` | `MemoryCacheAdapter` (tests / local) |
|
|
12
12
|
| `@prismakit/opentelemetry` | Map telemetry → OTel metrics/spans |
|
|
13
13
|
| `@prismakit/cli` | `prismakit generate / validate / skills` |
|
|
14
14
|
| `@prismakit/eslint-plugin` | Repository-only data-access rules |
|
|
15
15
|
|
|
16
|
-
Node ≥ 20.
|
|
16
|
+
Node ≥ 20. Install:
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
19
|
pnpm add @prismakit/core
|
|
@@ -29,7 +29,7 @@ pnpm add -D @prismakit/eslint-plugin @prismakit/cli
|
|
|
29
29
|
createRepository(options) → new RepoClass(deps: RepositoryDeps)
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
Public factory: **`createRepository` only**.
|
|
33
33
|
|
|
34
34
|
`RepositoryDeps`: `{ prisma, cache?, registry?, autoCompose? }`.
|
|
35
35
|
|
|
@@ -37,14 +37,12 @@ Alias: `defineRepository`. (`createPrismaRepository` is also an alias in core bu
|
|
|
37
37
|
|
|
38
38
|
| Field | Type | Notes |
|
|
39
39
|
|-------|------|-------|
|
|
40
|
-
| `model` | `string` | Client key. Required
|
|
41
|
-
| `scalarFields` | `Record<string, string>` | `Prisma.XScalarFieldEnum`; inferred from meta if omitted. |
|
|
40
|
+
| `model` | `string` | Client key. **Required.** |
|
|
42
41
|
| `cache` | `CacheOptions \| true` | `true` → `{ ttl: 86400, sensitiveFields: ['password'] }`. |
|
|
43
|
-
| `lock` | `true \|
|
|
44
|
-
| `
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
| `toPayload` | `(data) => payload` | Default identity. Prefer typed factories over this. |
|
|
42
|
+
| `lock` | `true \| RepositoryLockConfig` | `true` resolves table/columns from Prisma meta. |
|
|
43
|
+
| `toPayload` | `(data) => payload` | Default identity. |
|
|
44
|
+
|
|
45
|
+
Scalars, primary key, and relations come from Prisma meta (`loadPrismaMetaFromSchema` / `loadPrismaMetaFromDmmf`).
|
|
48
46
|
|
|
49
47
|
`RepositoryLockConfig`: `{ tableName: string; columns?: Record<string, string> }`.
|
|
50
48
|
|
|
@@ -120,10 +118,10 @@ await repo.invalidateCache({ id?: string; tags?: string[] });
|
|
|
120
118
|
| `ttl` | `86400` | Entity TTL (seconds). |
|
|
121
119
|
| `nullTtl` | — | Negative cache for null results. |
|
|
122
120
|
| `sensitiveFields` | `['password']` | Selects containing these never cache. |
|
|
123
|
-
| `methods` | — | Per-method `{ enabled?, ttl? }
|
|
121
|
+
| `methods` | — | Per-method `{ enabled?, ttl? }`. |
|
|
124
122
|
| `defaultSetCache` | `false` | Reads cache unless caller passes `setCache: false`. |
|
|
125
123
|
| `stampede` | see below | Per-repo stampede overrides. |
|
|
126
|
-
| `
|
|
124
|
+
| `strictInvalidation` | `false` | When true, invalidation failures rethrow. |
|
|
127
125
|
|
|
128
126
|
### `InvalidateMode`
|
|
129
127
|
|
|
@@ -133,11 +131,6 @@ await repo.invalidateCache({ id?: string; tags?: string[] });
|
|
|
133
131
|
| `entity` | Entity keys only |
|
|
134
132
|
| `queries` | Query index only |
|
|
135
133
|
| `none` | Skip — **required inside transactions** |
|
|
136
|
-
| `stale` | Core treats like entity+queries (adapter may layer SWR) |
|
|
137
|
-
|
|
138
|
-
### Allowlist
|
|
139
|
-
|
|
140
|
-
`setRegisteredCacheModels(['user', 'product'])`. Empty/unset = fail-open. When set, a repo with `cache` whose `model` is missing throws at init.
|
|
141
134
|
|
|
142
135
|
### Key schema
|
|
143
136
|
|
|
@@ -150,7 +143,7 @@ await repo.invalidateCache({ id?: string; tags?: string[] });
|
|
|
150
143
|
{prefix}:v2:repo:{model}:t:{tag}:__idx
|
|
151
144
|
```
|
|
152
145
|
|
|
153
|
-
Redis payloads use tagged JSON for `Date`, `BigInt`, `Bytes`, and `Decimal
|
|
146
|
+
Redis payloads use tagged JSON for `Date`, `BigInt`, `Bytes`, and `Decimal`. Custom revive: `createRedisJsonReviver` from `@prismakit/redis`.
|
|
154
147
|
|
|
155
148
|
### Debug
|
|
156
149
|
|
|
@@ -170,8 +163,6 @@ Hits/misses/bypasses via `cacheDebugStorage` from `@prismakit/core`.
|
|
|
170
163
|
| `backoff` | `'exponential'` (`'fixed'` also valid) |
|
|
171
164
|
| `totalTimeoutMs` | `3000` |
|
|
172
165
|
|
|
173
|
-
In-process `singleflight` is also used. If Redis is down, fail open.
|
|
174
|
-
|
|
175
166
|
### Adapters
|
|
176
167
|
|
|
177
168
|
**Redis** (`@prismakit/redis`):
|
|
@@ -193,11 +184,11 @@ new RedisCacheAdapter({
|
|
|
193
184
|
new MemoryCacheAdapter({ prefix: 'test', maxSize: 1000, defaultTtl: 300 });
|
|
194
185
|
```
|
|
195
186
|
|
|
196
|
-
Custom: implement `CacheAdapter
|
|
187
|
+
Custom: implement `CacheAdapter`. Prefer fail-open `safe*` semantics.
|
|
197
188
|
|
|
198
189
|
## Auto-compose
|
|
199
190
|
|
|
200
|
-
`splitSelect` keeps scalars (+ FK fields from
|
|
191
|
+
`splitSelect` keeps scalars (+ FK fields from meta) for the Prisma query. Relation keys load via the target repository. Target PK is always injected into the nested select.
|
|
201
192
|
|
|
202
193
|
Load meta once at bootstrap:
|
|
203
194
|
|
|
@@ -208,8 +199,6 @@ loadPrismaMetaFromDmmf(Prisma.dmmf); // Prisma 5/6
|
|
|
208
199
|
loadPrismaMetaFromSchema('prisma/schema.prisma'); // Prisma 7 (no Prisma.dmmf)
|
|
209
200
|
```
|
|
210
201
|
|
|
211
|
-
Without meta, AutoComposer cannot resolve renamed relations; pass `schemaPath` or `dmmf`. Relation field → registry key is source-scoped (`Category.products` vs `Tag.products` can target different models).
|
|
212
|
-
|
|
213
202
|
Validate: `npx prismakit validate --auto-register` or `assertSelectComposeValid`.
|
|
214
203
|
|
|
215
204
|
### `ComposeOptions` (global via `setComposeOptions`)
|
|
@@ -219,7 +208,6 @@ Validate: `npx prismakit validate --auto-register` or `assertSelectComposeValid`
|
|
|
219
208
|
| `maxDepth` | `10` | Max relation nesting. |
|
|
220
209
|
| `parallel` | `true` | Same-level relations via `Promise.all`. |
|
|
221
210
|
| `setCache` | `true` | Nested fetches pass `setCache: true` unless parent has `tx` / `setCache: false`. |
|
|
222
|
-
| `tx` | — | Per-call only; forwarded to nested `getMany`. |
|
|
223
211
|
|
|
224
212
|
Related repos must be registered on `RepositoryRegistry`.
|
|
225
213
|
|
|
@@ -235,7 +223,7 @@ Related repos must be registered on `RepositoryRegistry`.
|
|
|
235
223
|
| `nowait` | Fail immediately if locked. |
|
|
236
224
|
| `skipLocked` | Skip locked rows. **Cannot** combine with `nowait`. |
|
|
237
225
|
|
|
238
|
-
`lock: true`
|
|
226
|
+
`lock: true` resolves via Prisma meta. Explicit: `{ tableName, columns? }`.
|
|
239
227
|
|
|
240
228
|
## Telemetry
|
|
241
229
|
|
|
@@ -265,10 +253,6 @@ npx prismakit validate [--schema <path>] [--auto-register] [--no-assert]
|
|
|
265
253
|
npx prismakit help
|
|
266
254
|
```
|
|
267
255
|
|
|
268
|
-
Default generate output: `src/modules/<kebab>/repositories/<kebab>.repository.ts`.
|
|
269
|
-
|
|
270
|
-
`--full` emits Nest module + controller + service + select/where types. Register `*Module` in `app.module.ts` afterwards.
|
|
271
|
-
|
|
272
256
|
## ESLint
|
|
273
257
|
|
|
274
258
|
```js
|
|
@@ -276,8 +260,6 @@ import prismakit from '@prismakit/eslint-plugin';
|
|
|
276
260
|
export default [prismakit.configs.recommended];
|
|
277
261
|
```
|
|
278
262
|
|
|
279
|
-
`recommended` turns all rules on at **error**.
|
|
280
|
-
|
|
281
263
|
| Rule | Forbids |
|
|
282
264
|
|------|---------|
|
|
283
265
|
| `prismakit/no-prisma-service-outside-repos` | Inject/reference `PrismaService` / `PrismaClient` outside allowlist |
|
|
@@ -285,23 +267,15 @@ export default [prismakit.configs.recommended];
|
|
|
285
267
|
| `prismakit/require-transaction-service` | `.$transaction` in feature code |
|
|
286
268
|
| `prismakit/require-cached-repo-provider` | Cached repo class missing from Nest `providers` |
|
|
287
269
|
|
|
288
|
-
Allowed path patterns
|
|
289
|
-
|
|
290
|
-
- `**/repositories/**`
|
|
291
|
-
- `**/infrastructure/prisma/**`
|
|
292
|
-
- `**/node_modules/**`
|
|
293
|
-
- `packages/(core|nestjs|redis)/**` (PrismaKit monorepo)
|
|
294
|
-
|
|
295
|
-
Mirror this layout rather than weakening the plugin.
|
|
270
|
+
Allowed path patterns: `**/repositories/**`, `**/infrastructure/prisma/**`.
|
|
296
271
|
|
|
297
272
|
## Bootstrap (plain Node)
|
|
298
273
|
|
|
299
274
|
```typescript
|
|
300
|
-
import {
|
|
275
|
+
import { PrismaClient } from '@prisma/client';
|
|
301
276
|
import {
|
|
302
277
|
createRepository,
|
|
303
278
|
loadPrismaMetaFromSchema,
|
|
304
|
-
setRegisteredCacheModels,
|
|
305
279
|
setComposeOptions,
|
|
306
280
|
setTelemetry,
|
|
307
281
|
RepositoryRegistry,
|
|
@@ -311,7 +285,6 @@ import { RedisCacheAdapter } from '@prismakit/redis';
|
|
|
311
285
|
|
|
312
286
|
const prisma = new PrismaClient();
|
|
313
287
|
loadPrismaMetaFromSchema('prisma/schema.prisma');
|
|
314
|
-
setRegisteredCacheModels(['user', 'post']);
|
|
315
288
|
setComposeOptions({ maxDepth: 6, parallel: true, setCache: true });
|
|
316
289
|
setTelemetry({ enabled: true, onEvent: (e) => console.debug('[pk]', e.type) });
|
|
317
290
|
|
|
@@ -26,8 +26,7 @@ Task Progress:
|
|
|
26
26
|
- [ ] New model access goes through `createRepository` (core) or the app's Nest `defineRepo` binder — not a one-off Prisma call
|
|
27
27
|
- [ ] File lives under `**/repositories/**` as `*.repository.ts`
|
|
28
28
|
- [ ] `model` is the Prisma client key (`'user'`, not `'User'`)
|
|
29
|
-
- [ ]
|
|
30
|
-
- [ ] `cacheModels` is omitted unless the app wants an extra allowlist
|
|
29
|
+
- [ ] Prisma meta is loaded at bootstrap (`dmmf` / `schemaPath` / `loadPrismaMetaFrom*`)
|
|
31
30
|
|
|
32
31
|
## Selects
|
|
33
32
|
|
|
@@ -54,7 +53,7 @@ Task Progress:
|
|
|
54
53
|
|
|
55
54
|
## Locks
|
|
56
55
|
|
|
57
|
-
- [ ] Repo declares `lock` (`true
|
|
56
|
+
- [ ] Repo declares `lock` (`true` or `{ tableName, columns }`) before any call passes `lock`
|
|
58
57
|
- [ ] Every `lock: { mode }` call also passes `tx`
|
|
59
58
|
- [ ] `skipLocked` is not combined with `nowait`
|
|
60
59
|
- [ ] Locked transactions stay short
|
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
name: prismakit-nestjs
|
|
3
3
|
description: >-
|
|
4
4
|
PrismaKit NestJS adapter: PrismaKitModule.forRoot/forRootAsync wiring, injectable repositories via
|
|
5
|
-
createDefineRepo/
|
|
6
|
-
and PRISMAKIT_* DI tokens. Use when working with @prismakit/nestjs,
|
|
7
|
-
Prisma data access, or Nest transactions and cache invalidation.
|
|
5
|
+
createDefineRepo (default) / createInjectableRepository (escape hatch), TransactionService.execTx
|
|
6
|
+
with afterCommit invalidation, and PRISMAKIT_* DI tokens. Use when working with @prismakit/nestjs,
|
|
7
|
+
Nest feature modules that need Prisma data access, or Nest transactions and cache invalidation.
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# PrismaKit NestJS
|
|
11
11
|
|
|
12
12
|
Adapter for NestJS. The data-access contract (layering, cache, compose, locks, ESLint) lives in skill `prismakit` — follow it. This skill covers module wiring, injectable repos, and `TransactionService`.
|
|
13
13
|
|
|
14
|
+
**Line:** 4.0 (pre-stable). Default factory: **`createDefineRepo` / app `defineAppRepo`**.
|
|
15
|
+
|
|
14
16
|
## Bootstrap
|
|
15
17
|
|
|
16
18
|
```bash
|
|
@@ -32,6 +34,11 @@ import { RedisCacheAdapter } from '@prismakit/redis';
|
|
|
32
34
|
schemaPath: 'prisma/schema.prisma', // default; Prisma 5/6: dmmf: Prisma.dmmf
|
|
33
35
|
validateCompose: true,
|
|
34
36
|
compose: { maxDepth: 6, parallel: true, setCache: true },
|
|
37
|
+
telemetry: {
|
|
38
|
+
enabled: true,
|
|
39
|
+
slowThreshold: 500,
|
|
40
|
+
onEvent: (e) => console.debug(e.type),
|
|
41
|
+
},
|
|
35
42
|
}),
|
|
36
43
|
],
|
|
37
44
|
})
|
|
@@ -40,7 +47,7 @@ export class AppModule {}
|
|
|
40
47
|
|
|
41
48
|
`forRootAsync` when cache/URL come from `ConfigService` — see [examples.md](examples.md).
|
|
42
49
|
|
|
43
|
-
`schemaPath` defaults to `prisma/schema.prisma`. Always load Prisma meta (`dmmf` or `schemaPath`) so auto-compose and `lock: true` resolve FKs/`@@map` from the schema
|
|
50
|
+
`schemaPath` defaults to `prisma/schema.prisma`. Always load Prisma meta (`dmmf` or `schemaPath`) so auto-compose and `lock: true` resolve FKs/`@@map` from the schema.
|
|
44
51
|
|
|
45
52
|
## Factory (one default)
|
|
46
53
|
|
|
@@ -95,14 +102,13 @@ export class AuditLogRepository extends defineAppRepo({
|
|
|
95
102
|
}) {}
|
|
96
103
|
```
|
|
97
104
|
|
|
98
|
-
|
|
105
|
+
Per-repo options: `model` (required), `cache?`, `lock?: true | RepositoryLockConfig`, `toPayload?`.
|
|
99
106
|
|
|
100
|
-
Escape
|
|
107
|
+
Escape hatch (do not use as the app default):
|
|
101
108
|
|
|
102
|
-
- `
|
|
103
|
-
- `createInjectableRepository` without a types bag — thin, results are `unknown`. Alias: `createPrismaRepository`.
|
|
109
|
+
- `createInjectableRepository` — thin Nest wrapper when TypeMap binding is unavailable (results are thinly typed).
|
|
104
110
|
|
|
105
|
-
|
|
111
|
+
Removed in 4.0: `defineInjectableRepository`, `defineRepo`, `defineRepository`, `createPrismaRepository`, Nest `cacheModels`, Nest `queryLog`.
|
|
106
112
|
|
|
107
113
|
## Register and inject
|
|
108
114
|
|
|
@@ -200,7 +206,7 @@ await this.tx.execTx(
|
|
|
200
206
|
|
|
201
207
|
When the repo options include `cache`, TypeScript exposes `setCache`, `cacheTags`, mutation `invalidate`/`tags`, and `invalidateCache`. Without `cache`, those fields are omitted — do not pass them.
|
|
202
208
|
|
|
203
|
-
Repository `cache` is the source of truth.
|
|
209
|
+
Repository `cache` is the source of truth.
|
|
204
210
|
|
|
205
211
|
`cache.defaultSetCache: true` makes user-facing reads cache by default; still pass `setCache: false` on auth/uniqueness.
|
|
206
212
|
|
|
@@ -219,12 +225,10 @@ Repository `cache` is the source of truth. Omit `cacheModels` (fail-open). Pass
|
|
|
219
225
|
|
|
220
226
|
| Option | Use |
|
|
221
227
|
|--------|-----|
|
|
222
|
-
| `cacheModels` | Optional extra allowlist (omit — repo `cache` is enough) |
|
|
223
228
|
| `validateCompose: true` | Assert compose-safe selects on boot |
|
|
224
229
|
| `strictCachedRepos` | Fail boot if a `cache` repo class is not in Nest `providers` (default `true`) |
|
|
225
230
|
| `compose` | `{ maxDepth, parallel, setCache }` |
|
|
226
|
-
| `telemetry` | `{ enabled
|
|
227
|
-
| `queryLog` | `{ slowThreshold, onSlowQuery }` — enables telemetry / `query.slow` |
|
|
231
|
+
| `telemetry` | `{ enabled, slowThreshold, onSlowQuery, onEvent }` or `createPrismaKitTelemetry()` |
|
|
228
232
|
| `autoRegisterModels` | `true` or `string[]` — stub repos for compose-only models |
|
|
229
233
|
|
|
230
234
|
## Scaffolding
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Production-shaped snippets. Contract: skill `prismakit` + [SKILL.md](SKILL.md).
|
|
4
4
|
|
|
5
|
-
## 1. App module (Redis,
|
|
5
|
+
## 1. App module (Redis, telemetry)
|
|
6
6
|
|
|
7
7
|
```typescript
|
|
8
8
|
// src/app.module.ts
|
|
@@ -31,7 +31,8 @@ import { UserModule } from './modules/users/user.module';
|
|
|
31
31
|
validateCompose: true,
|
|
32
32
|
compose: { maxDepth: 6, parallel: true, setCache: true },
|
|
33
33
|
autoRegisterModels: true,
|
|
34
|
-
|
|
34
|
+
telemetry: {
|
|
35
|
+
enabled: true,
|
|
35
36
|
slowThreshold: 500,
|
|
36
37
|
onSlowQuery: (e) => {
|
|
37
38
|
console.warn(`Slow ${e.model}.${e.method}: ${e.durationMs}ms`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# PrismaKit NestJS reference
|
|
2
2
|
|
|
3
|
-
API surface for `@prismakit/nestjs`
|
|
3
|
+
API surface for `@prismakit/nestjs` **4.0** (pre-stable). Repository methods, cache, compose, and locks are documented in skill `prismakit` (`reference.md` in that skill). This file covers the Nest adapter only.
|
|
4
4
|
|
|
5
5
|
## `PrismaKitModuleOptions`
|
|
6
6
|
|
|
@@ -13,13 +13,11 @@ API surface for `@prismakit/nestjs` 3.x. Repository methods, cache, compose, and
|
|
|
13
13
|
| `validateCompose` | no | When `true`, `assertSelectComposeValid` on module init. |
|
|
14
14
|
| `strictCachedRepos` | no | Fail boot when a cached repo is missing from `providers`, or listed in two modules. Default `true`. |
|
|
15
15
|
| `modulesRoot` | no | Directory scanned by `strictCachedRepos`. Default `src/modules`. |
|
|
16
|
-
| `
|
|
17
|
-
| `
|
|
18
|
-
| `telemetry` | no | `{ enabled?: boolean; onEvent?: (event) => void }`. |
|
|
19
|
-
| `queryLog` | no | `{ slowThreshold?: number; onSlowQuery?: (e) => void }`. Default threshold 500ms. Setting this enables telemetry. |
|
|
16
|
+
| `compose` | no | `ComposeOptions`: `maxDepth` (default 10), `parallel` (default true), `setCache` (default true). |
|
|
17
|
+
| `telemetry` | no | `{ enabled?, slowThreshold?, onSlowQuery?, onEvent? }`. |
|
|
20
18
|
| `autoRegisterModels` | no | `true` = stub repos for all schema/DMMF models; `string[]` = those client keys only. |
|
|
21
19
|
|
|
22
|
-
`
|
|
20
|
+
`onSlowQuery` receives `{ model?, method?, durationMs, thresholdMs }` for slow queries. Setting `slowThreshold` / `onSlowQuery` enables telemetry unless `enabled: false`.
|
|
23
21
|
|
|
24
22
|
## Async config
|
|
25
23
|
|
|
@@ -42,6 +40,10 @@ PrismaKitModule.forRootAsync({
|
|
|
42
40
|
prefix: config.get('CACHE_PREFIX') ?? 'myapp',
|
|
43
41
|
}),
|
|
44
42
|
schemaPath: 'prisma/schema.prisma',
|
|
43
|
+
telemetry: {
|
|
44
|
+
enabled: true,
|
|
45
|
+
slowThreshold: 500,
|
|
46
|
+
},
|
|
45
47
|
}),
|
|
46
48
|
});
|
|
47
49
|
```
|
|
@@ -68,67 +70,25 @@ execTx<T, TClient = unknown>(
|
|
|
68
70
|
|
|
69
71
|
`TransactionOptions`: `{ maxWait?: number; timeout?: number; isolationLevel?: 'ReadUncommitted' | 'ReadCommitted' | 'RepeatableRead' | 'Serializable' | string }`.
|
|
70
72
|
|
|
71
|
-
Type the client when useful:
|
|
72
|
-
|
|
73
|
-
```typescript
|
|
74
|
-
await this.tx.execTx<User, Prisma.TransactionClient>(async (tx) => { /* ... */ });
|
|
75
|
-
```
|
|
76
|
-
|
|
77
73
|
`afterCommit` runs only after `prisma.$transaction` resolves successfully.
|
|
78
74
|
|
|
79
75
|
## Repository factories
|
|
80
76
|
|
|
81
77
|
| Factory | When |
|
|
82
78
|
|---------|------|
|
|
83
|
-
| `createDefineRepo<Prisma.TypeMap>()` then
|
|
84
|
-
| `
|
|
85
|
-
| `createInjectableRepository({ model, ... })` | Thin / untyped. Results `unknown` unless `toPayload` is supplied. Alias: `createPrismaRepository`. |
|
|
79
|
+
| `createDefineRepo<Prisma.TypeMap>()` then app `defineAppRepo({ model, ... })` | **Default** for apps. Zero phantoms; `model` + `cache` / `lock` / `toPayload`. |
|
|
80
|
+
| `createInjectableRepository({ model, ... })` | Low-level escape hatch. Results thinly typed unless `toPayload` is supplied. |
|
|
86
81
|
|
|
87
|
-
`createDefineRepo` accepts app-wide defaults (`cache
|
|
82
|
+
`createDefineRepo` accepts app-wide defaults (`cache`) and per-repo options: `model`, `cache?` (`true` inherits app defaults), `lock?`, `toPayload?`.
|
|
88
83
|
|
|
89
84
|
When `cache` is set, the returned API includes `setCache` / `cacheTags` / `invalidate` / `tags` / `invalidateCache`. Otherwise those fields are omitted from the type (`HasCacheFromOptions`).
|
|
90
85
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
Export the instance type with interface merging so `cache` on options gates `setCache` (a same-name `type` alias collapses to `any`):
|
|
86
|
+
Export the instance type with interface merging so `cache` on options gates `setCache`:
|
|
94
87
|
|
|
95
88
|
```typescript
|
|
96
89
|
export interface UserRepository extends InstanceType<typeof UserRepository> {}
|
|
97
90
|
```
|
|
98
91
|
|
|
99
|
-
## `defineInjectableRepository` shape (escape hatch)
|
|
100
|
-
|
|
101
|
-
```typescript
|
|
102
|
-
import { Prisma } from '@prisma/client';
|
|
103
|
-
import { defineInjectableRepository } from '@prismakit/nestjs';
|
|
104
|
-
|
|
105
|
-
type Of<S> = S extends Prisma.UserSelect
|
|
106
|
-
? Prisma.UserGetPayload<{ select: S }>
|
|
107
|
-
: never;
|
|
108
|
-
|
|
109
|
-
export const UserRepository = defineInjectableRepository({
|
|
110
|
-
model: 'user',
|
|
111
|
-
scalarFields: Prisma.UserScalarFieldEnum, // required for this escape hatch (meta not typed)
|
|
112
|
-
select: null! as Prisma.UserSelect,
|
|
113
|
-
create: null! as Prisma.UserCreateInput,
|
|
114
|
-
update: null! as Prisma.UserUpdateInput,
|
|
115
|
-
where: null! as Prisma.UserWhereInput,
|
|
116
|
-
orderBy: null! as Prisma.UserOrderByWithRelationInput,
|
|
117
|
-
payload: class {
|
|
118
|
-
declare readonly _select: unknown;
|
|
119
|
-
declare type: () => Of<this['_select']>;
|
|
120
|
-
},
|
|
121
|
-
cache: { ttl: 86_400, sensitiveFields: ['password'] },
|
|
122
|
-
lock: true,
|
|
123
|
-
});
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
## Re-exports from core
|
|
127
|
-
|
|
128
|
-
`@prismakit/nestjs` re-exports: `AutoComposer`, `RepositoryRegistry`, `CacheAdapter`, repository option/instance types, `RepoPayloadHKT`, `ComposeOptions`, `TelemetryOptions`, `TelemetryEvent`, `loadPrismaMetaFromDmmf`, `loadPrismaMetaFromSchema`, `setComposeOptions`, `setTelemetry`.
|
|
129
|
-
|
|
130
|
-
Prefer importing Nest-only APIs from `@prismakit/nestjs` and core-only helpers from `@prismakit/core`.
|
|
131
|
-
|
|
132
92
|
## Layout
|
|
133
93
|
|
|
134
94
|
```
|
|
@@ -22,7 +22,6 @@ Task Progress:
|
|
|
22
22
|
- [ ] `prisma` is the shared client instance
|
|
23
23
|
- [ ] Prisma meta is loaded: `dmmf: Prisma.dmmf` (Prisma 5/6) or `schemaPath: 'prisma/schema.prisma'` (Prisma 7)
|
|
24
24
|
- [ ] Production cache uses `RedisCacheAdapter` with a stable `prefix`
|
|
25
|
-
- [ ] `cacheModels` is omitted (repo `cache` is source of truth), or lists every cached model if an allowlist is used
|
|
26
25
|
- [ ] `validateCompose: true` is on for apps that nest relations in `select`
|
|
27
26
|
- [ ] Cached repo classes are in feature `providers` (`strictCachedRepos` fails boot otherwise)
|
|
28
27
|
|
|
@@ -32,7 +31,7 @@ Task Progress:
|
|
|
32
31
|
- [ ] Feature repos call that binder — not `createInjectableRepository` unless types are intentionally thin
|
|
33
32
|
- [ ] `export interface XRepository extends InstanceType<typeof XRepository> {}` (infers cache from options; do not use a same-name `type` alias)
|
|
34
33
|
- [ ] Repo class is in `providers` and `exports` of the feature module
|
|
35
|
-
- [ ] `lock: true` (or
|
|
34
|
+
- [ ] `lock: true` (or `{ tableName, columns }`) is set when any call uses `lock`
|
|
36
35
|
|
|
37
36
|
## DI
|
|
38
37
|
|
|
@@ -20,10 +20,13 @@ describe('cli generate templates', () => {
|
|
|
20
20
|
expect(files).toHaveLength(1);
|
|
21
21
|
expect(files[0].relativePath).toContain('repositories/product.repository.ts');
|
|
22
22
|
expect(files[0].content).toContain("model: 'product'");
|
|
23
|
-
expect(files[0].content).toContain('cache:
|
|
23
|
+
expect(files[0].content).toContain('cache: true');
|
|
24
24
|
expect(files[0].content).not.toContain('getDelegate');
|
|
25
25
|
expect(files[0].content).not.toContain('scalarFields');
|
|
26
|
-
expect(files[0].content).toContain(
|
|
26
|
+
expect(files[0].content).toContain('defineAppRepo');
|
|
27
|
+
expect(files[0].content).toContain(
|
|
28
|
+
"from 'src/infrastructure/prisma/define-app-repo'",
|
|
29
|
+
);
|
|
27
30
|
});
|
|
28
31
|
|
|
29
32
|
it('emits full Nest module when full: true', () => {
|
package/src/commands/skills.ts
CHANGED
package/src/templates.ts
CHANGED
|
@@ -40,19 +40,16 @@ function renderRepository(
|
|
|
40
40
|
prismaImport: string,
|
|
41
41
|
base: string,
|
|
42
42
|
): GeneratedFile {
|
|
43
|
-
const cacheBlock = cacheEnabled
|
|
44
|
-
? ` cache: {
|
|
45
|
-
ttl: 86400,
|
|
46
|
-
sensitiveFields: ['password'],
|
|
47
|
-
defaultSetCache: true,
|
|
48
|
-
},
|
|
49
|
-
`
|
|
50
|
-
: '';
|
|
43
|
+
const cacheBlock = cacheEnabled ? ` cache: true,\n` : '';
|
|
51
44
|
|
|
52
45
|
const content = apply(
|
|
53
|
-
`import {
|
|
46
|
+
`import { defineAppRepo } from 'src/infrastructure/prisma/define-app-repo';
|
|
54
47
|
|
|
55
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Bind once in infrastructure:
|
|
50
|
+
* export const defineAppRepo = createDefineRepo<Prisma.TypeMap>({ cache: { ... } });
|
|
51
|
+
*/
|
|
52
|
+
export class {{pascal}}Repository extends defineAppRepo({
|
|
56
53
|
model: '{{repoModel}}',
|
|
57
54
|
{{cacheBlock}}}) {}
|
|
58
55
|
`,
|