@prismakit/cli 3.2.2 → 4.0.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.
- 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 +18 -44
- package/skills/prismakit/review-checklist.md +2 -3
- package/skills/prismakit-nestjs/SKILL.md +21 -14
- 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`. Pass `decimalFactory: (s) => new Prisma.Decimal(s)` on `RedisCacheAdapter` (and/or Nest `PrismaKitModule.forRoot`) so compose clone and cache hits return native Prisma scalars — `JSON.stringify` otherwise turns `Date`/`Decimal` into strings via `toJSON`.
|
|
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`):
|
|
@@ -184,6 +175,7 @@ new RedisCacheAdapter({
|
|
|
184
175
|
prefix: 'myapp', // default 'prismakit'
|
|
185
176
|
compression: 'gzip', // 'none' | 'gzip'
|
|
186
177
|
compressionThresholdBytes: 1024,
|
|
178
|
+
decimalFactory: (s) => new Prisma.Decimal(s),
|
|
187
179
|
});
|
|
188
180
|
```
|
|
189
181
|
|
|
@@ -193,11 +185,11 @@ new RedisCacheAdapter({
|
|
|
193
185
|
new MemoryCacheAdapter({ prefix: 'test', maxSize: 1000, defaultTtl: 300 });
|
|
194
186
|
```
|
|
195
187
|
|
|
196
|
-
Custom: implement `CacheAdapter
|
|
188
|
+
Custom: implement `CacheAdapter`. Prefer fail-open `safe*` semantics.
|
|
197
189
|
|
|
198
190
|
## Auto-compose
|
|
199
191
|
|
|
200
|
-
`splitSelect` keeps scalars (+ FK fields from
|
|
192
|
+
`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
193
|
|
|
202
194
|
Load meta once at bootstrap:
|
|
203
195
|
|
|
@@ -208,8 +200,6 @@ loadPrismaMetaFromDmmf(Prisma.dmmf); // Prisma 5/6
|
|
|
208
200
|
loadPrismaMetaFromSchema('prisma/schema.prisma'); // Prisma 7 (no Prisma.dmmf)
|
|
209
201
|
```
|
|
210
202
|
|
|
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
203
|
Validate: `npx prismakit validate --auto-register` or `assertSelectComposeValid`.
|
|
214
204
|
|
|
215
205
|
### `ComposeOptions` (global via `setComposeOptions`)
|
|
@@ -219,7 +209,6 @@ Validate: `npx prismakit validate --auto-register` or `assertSelectComposeValid`
|
|
|
219
209
|
| `maxDepth` | `10` | Max relation nesting. |
|
|
220
210
|
| `parallel` | `true` | Same-level relations via `Promise.all`. |
|
|
221
211
|
| `setCache` | `true` | Nested fetches pass `setCache: true` unless parent has `tx` / `setCache: false`. |
|
|
222
|
-
| `tx` | — | Per-call only; forwarded to nested `getMany`. |
|
|
223
212
|
|
|
224
213
|
Related repos must be registered on `RepositoryRegistry`.
|
|
225
214
|
|
|
@@ -235,7 +224,7 @@ Related repos must be registered on `RepositoryRegistry`.
|
|
|
235
224
|
| `nowait` | Fail immediately if locked. |
|
|
236
225
|
| `skipLocked` | Skip locked rows. **Cannot** combine with `nowait`. |
|
|
237
226
|
|
|
238
|
-
`lock: true`
|
|
227
|
+
`lock: true` resolves via Prisma meta. Explicit: `{ tableName, columns? }`.
|
|
239
228
|
|
|
240
229
|
## Telemetry
|
|
241
230
|
|
|
@@ -265,10 +254,6 @@ npx prismakit validate [--schema <path>] [--auto-register] [--no-assert]
|
|
|
265
254
|
npx prismakit help
|
|
266
255
|
```
|
|
267
256
|
|
|
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
257
|
## ESLint
|
|
273
258
|
|
|
274
259
|
```js
|
|
@@ -276,8 +261,6 @@ import prismakit from '@prismakit/eslint-plugin';
|
|
|
276
261
|
export default [prismakit.configs.recommended];
|
|
277
262
|
```
|
|
278
263
|
|
|
279
|
-
`recommended` turns all rules on at **error**.
|
|
280
|
-
|
|
281
264
|
| Rule | Forbids |
|
|
282
265
|
|------|---------|
|
|
283
266
|
| `prismakit/no-prisma-service-outside-repos` | Inject/reference `PrismaService` / `PrismaClient` outside allowlist |
|
|
@@ -285,23 +268,15 @@ export default [prismakit.configs.recommended];
|
|
|
285
268
|
| `prismakit/require-transaction-service` | `.$transaction` in feature code |
|
|
286
269
|
| `prismakit/require-cached-repo-provider` | Cached repo class missing from Nest `providers` |
|
|
287
270
|
|
|
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.
|
|
271
|
+
Allowed path patterns: `**/repositories/**`, `**/infrastructure/prisma/**`.
|
|
296
272
|
|
|
297
273
|
## Bootstrap (plain Node)
|
|
298
274
|
|
|
299
275
|
```typescript
|
|
300
|
-
import {
|
|
276
|
+
import { PrismaClient } from '@prisma/client';
|
|
301
277
|
import {
|
|
302
278
|
createRepository,
|
|
303
279
|
loadPrismaMetaFromSchema,
|
|
304
|
-
setRegisteredCacheModels,
|
|
305
280
|
setComposeOptions,
|
|
306
281
|
setTelemetry,
|
|
307
282
|
RepositoryRegistry,
|
|
@@ -311,7 +286,6 @@ import { RedisCacheAdapter } from '@prismakit/redis';
|
|
|
311
286
|
|
|
312
287
|
const prisma = new PrismaClient();
|
|
313
288
|
loadPrismaMetaFromSchema('prisma/schema.prisma');
|
|
314
|
-
setRegisteredCacheModels(['user', 'post']);
|
|
315
289
|
setComposeOptions({ maxDepth: 6, parallel: true, setCache: true });
|
|
316
290
|
setTelemetry({ enabled: true, onEvent: (e) => console.debug('[pk]', e.type) });
|
|
317
291
|
|
|
@@ -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
|
|
@@ -28,10 +30,18 @@ import { RedisCacheAdapter } from '@prismakit/redis';
|
|
|
28
30
|
imports: [
|
|
29
31
|
PrismaKitModule.forRoot({
|
|
30
32
|
prisma: prismaClient,
|
|
31
|
-
cache: new RedisCacheAdapter({
|
|
33
|
+
cache: new RedisCacheAdapter({
|
|
34
|
+
prefix: 'myapp',
|
|
35
|
+
decimalFactory: (s) => new Prisma.Decimal(s),
|
|
36
|
+
}),
|
|
32
37
|
schemaPath: 'prisma/schema.prisma', // default; Prisma 5/6: dmmf: Prisma.dmmf
|
|
33
38
|
validateCompose: true,
|
|
34
39
|
compose: { maxDepth: 6, parallel: true, setCache: true },
|
|
40
|
+
telemetry: {
|
|
41
|
+
enabled: true,
|
|
42
|
+
slowThreshold: 500,
|
|
43
|
+
onEvent: (e) => console.debug(e.type),
|
|
44
|
+
},
|
|
35
45
|
}),
|
|
36
46
|
],
|
|
37
47
|
})
|
|
@@ -40,7 +50,7 @@ export class AppModule {}
|
|
|
40
50
|
|
|
41
51
|
`forRootAsync` when cache/URL come from `ConfigService` — see [examples.md](examples.md).
|
|
42
52
|
|
|
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
|
|
53
|
+
`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
54
|
|
|
45
55
|
## Factory (one default)
|
|
46
56
|
|
|
@@ -95,14 +105,13 @@ export class AuditLogRepository extends defineAppRepo({
|
|
|
95
105
|
}) {}
|
|
96
106
|
```
|
|
97
107
|
|
|
98
|
-
|
|
108
|
+
Per-repo options: `model` (required), `cache?`, `lock?: true | RepositoryLockConfig`, `toPayload?`.
|
|
99
109
|
|
|
100
|
-
Escape
|
|
110
|
+
Escape hatch (do not use as the app default):
|
|
101
111
|
|
|
102
|
-
- `
|
|
103
|
-
- `createInjectableRepository` without a types bag — thin, results are `unknown`. Alias: `createPrismaRepository`.
|
|
112
|
+
- `createInjectableRepository` — thin Nest wrapper when TypeMap binding is unavailable (results are thinly typed).
|
|
104
113
|
|
|
105
|
-
|
|
114
|
+
Removed in 4.0: `defineInjectableRepository`, `defineRepo`, `defineRepository`, `createPrismaRepository`, Nest `cacheModels`, Nest `queryLog`.
|
|
106
115
|
|
|
107
116
|
## Register and inject
|
|
108
117
|
|
|
@@ -200,7 +209,7 @@ await this.tx.execTx(
|
|
|
200
209
|
|
|
201
210
|
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
211
|
|
|
203
|
-
Repository `cache` is the source of truth.
|
|
212
|
+
Repository `cache` is the source of truth.
|
|
204
213
|
|
|
205
214
|
`cache.defaultSetCache: true` makes user-facing reads cache by default; still pass `setCache: false` on auth/uniqueness.
|
|
206
215
|
|
|
@@ -219,12 +228,10 @@ Repository `cache` is the source of truth. Omit `cacheModels` (fail-open). Pass
|
|
|
219
228
|
|
|
220
229
|
| Option | Use |
|
|
221
230
|
|--------|-----|
|
|
222
|
-
| `cacheModels` | Optional extra allowlist (omit — repo `cache` is enough) |
|
|
223
231
|
| `validateCompose: true` | Assert compose-safe selects on boot |
|
|
224
232
|
| `strictCachedRepos` | Fail boot if a `cache` repo class is not in Nest `providers` (default `true`) |
|
|
225
233
|
| `compose` | `{ maxDepth, parallel, setCache }` |
|
|
226
|
-
| `telemetry` | `{ enabled
|
|
227
|
-
| `queryLog` | `{ slowThreshold, onSlowQuery }` — enables telemetry / `query.slow` |
|
|
234
|
+
| `telemetry` | `{ enabled, slowThreshold, onSlowQuery, onEvent }` or `createPrismaKitTelemetry()` |
|
|
228
235
|
| `autoRegisterModels` | `true` or `string[]` — stub repos for compose-only models |
|
|
229
236
|
|
|
230
237
|
## 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
|
`,
|