@prismakit/cli 3.2.1 → 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.
@@ -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 factories |
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` + `scalarFields` or loaded Prisma meta |
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 `defineRepo` — see `prismakit-nestjs` |
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 resolved from Prisma schema meta
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
- `defineRepository` and `createPrismaRepository` are aliases of `createRepository`.
87
+ Public factory: **`createRepository` only** (no `defineRepository` / `createPrismaRepository` aliases).
85
88
 
86
- **NestJS apps:** use `createDefineRepo` / `defineAppRepo` with app-wide cache defaults instead — see skill `prismakit-nestjs`.
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'`). Needed for cache + compose. |
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` / client key / `@@map` table / `{ tableName, columns }`. |
94
- | `primaryKey` | Override only. Defaults to schema `@id` / `@@id` (composite `string[]`) or `id`. |
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
 
@@ -123,7 +123,8 @@ await users.getFirst({
123
123
  | `getMany` | array (`take` / `skip` / `orderBy`) |
124
124
  | `getManyPaginate` | `{ data, meta: { page, pageSize, totalItems, totalPages } }` |
125
125
  | `getManyCursor` | `{ data, nextCursor, hasMore }` |
126
- | `count` / `exists` | `{ count }` / `{ exists }` |
126
+ | `getThrowFirst` | first match; throws if missing |
127
+ | `count` / `exists` | `number` / `boolean` |
127
128
  | `aggregate` / `groupBy` | Prisma delegate results |
128
129
 
129
130
  `id` is `string` or `Record<string, string>` for composite PKs (object form for `@@id([a,b])`).
@@ -134,8 +135,11 @@ await users.getFirst({
134
135
 
135
136
  | Method | Default `invalidate` |
136
137
  |--------|----------------------|
137
- | `create` / `createMany` | `queries` |
138
- | `updateById` / `updateMany` / `upsert` / `deleteById` / `deleteMany` | `all` |
138
+ | `create` / `createMany` / `createManyAndReturn` | `queries` |
139
+ | `updateById` / `update` / `updateMany` / `updateManyAndReturn` | `all` |
140
+ | `upsert` / `upsertMany` | `all` |
141
+ | `deleteById` / `delete` / `deleteMany` | `all` |
142
+ | `queryRaw` / `executeRaw` | none (raw SQL — no cache) |
139
143
 
140
144
  ```typescript
141
145
  await users.updateById({
@@ -163,7 +167,7 @@ If Redis is down, `RedisCacheAdapter` **fails open** — queries still hit Prism
163
167
  | Auth, uniqueness, JWT lookup | omit / `false` |
164
168
  | Inside `tx` | ignored |
165
169
 
166
- Repository `cache` is the source of truth. Omit `cacheModels` (fail-open). An optional allowlist throws if a cached repo's model is missing from the list.
170
+ Repository `cache` is the source of truth. There is no Nest `cacheModels` allowlist in 4.0.
167
171
 
168
172
  `setCache` / `cacheTags` / `invalidate` / `invalidateCache` exist on the type **only** when the repo has `cache` config. Do not force them on uncached repos.
169
173
 
@@ -196,9 +200,9 @@ await posts.getThrowById({
196
200
  });
197
201
  ```
198
202
 
199
- Requirements: source repo has `model`; `scalarFields` **or** Prisma meta loaded (`loadPrismaMetaFromDmmf(Prisma.dmmf)` on Prisma 5/6, `loadPrismaMetaFromSchema('prisma/schema.prisma')` on Prisma 7); related model repos are registered.
203
+ Requirements: source repo has `model`; Prisma meta loaded; related model repos are registered.
200
204
 
201
- AutoComposer injects the target primary key into nested selects even if omitted. Relation field names resolve from schema / DMMF meta (`schemaPath` defaults to `prisma/schema.prisma`).
205
+ AutoComposer injects the target primary key into nested selects even if omitted. Relation field names resolve from schema / DMMF meta.
202
206
 
203
207
  ## Row locks
204
208
 
@@ -240,8 +244,10 @@ Allowed Prisma usage: `**/repositories/**`, `**/infrastructure/prisma/**`. Rules
240
244
 
241
245
  ## Observability
242
246
 
243
- - Core: `setTelemetry({ enabled, onEvent })` or Nest `telemetry` / `queryLog.slowThreshold`.
244
- - Optional: `@prismakit/opentelemetry` → `createPrismaKitTelemetry({ slowThreshold })`.
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`
245
251
 
246
252
  ## Clean code
247
253
 
@@ -6,7 +6,7 @@ Copy-paste patterns for `@prismakit/core`. Contract: [SKILL.md](SKILL.md).
6
6
 
7
7
  ```typescript
8
8
  // src/modules/users/repositories/user.repository.ts
9
- import { Prisma } from '@prisma/client';
9
+ import { Prisma } from '@prisma/client'; // or generated client path for Prisma 7
10
10
  import { createRepository } from '@prismakit/core';
11
11
 
12
12
  const DAY = 86_400;
@@ -23,7 +23,6 @@ export const userSelectPresets = {
23
23
 
24
24
  export const UserRepoClass = createRepository({
25
25
  model: 'user',
26
- scalarFields: Prisma.UserScalarFieldEnum,
27
26
  cache: {
28
27
  ttl: DAY,
29
28
  nullTtl: 60,
@@ -40,7 +39,7 @@ export type UserRepository = InstanceType<typeof UserRepoClass>;
40
39
 
41
40
  ```typescript
42
41
  // src/infrastructure/prisma/repos.ts
43
- import { PrismaClient } from '@prisma/client';
42
+ import { PrismaClient } from '@prisma/client'; // or generated client for Prisma 7
44
43
  import { RedisCacheAdapter } from '@prismakit/redis';
45
44
  import { UserRepoClass } from '../../modules/users/repositories/user.repository';
46
45
 
@@ -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.x. Read [SKILL.md](SKILL.md) first.
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. Line **3.2.x**. Install:
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
- Aliases: `defineRepository`, `createPrismaRepository`.
32
+ Public factory: **`createRepository` only**.
33
33
 
34
34
  `RepositoryDeps`: `{ prisma, cache?, registry?, autoCompose? }`.
35
35
 
@@ -37,14 +37,12 @@ Aliases: `defineRepository`, `createPrismaRepository`.
37
37
 
38
38
  | Field | Type | Notes |
39
39
  |-------|------|-------|
40
- | `model` | `string` | Client key. Required for cache + compose. |
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 \| string \| RepositoryLockConfig` | `true` / client key / Pascal name / `@@map` table. |
44
- | `schemaPath` | `string` | Lock/schema helpers when global meta is missing. |
45
- | `primaryKey` | `string \| string[]` | `*ById` + row locks. Default: meta PK or `id`. Composite → Prisma `a_b: { a, b }` where. |
46
- | `getDelegate` | `(client) => delegate` | Default `(c) => c[model]`. |
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
 
@@ -58,12 +56,13 @@ All methods accept optional `tx`. Cached repos also accept cache fields (see bel
58
56
  |--------|------------|---------|
59
57
  | `getById` | `id`, `select?`, `lock?`, `setCache?` | `T \| null` |
60
58
  | `getThrowById` | same | `T` (throws if missing) |
61
- | `getFirst` | `where?`, `select?`, `lock?`, `setCache?`, `cacheTags?` | `T \| null` |
59
+ | `getFirst` | `where?`, `select?`, `lock?`, `setCache?`, `cacheTags?`, `orderBy?` | `T \| null` |
60
+ | `getThrowFirst` | same as `getFirst` | `T` (throws if missing) |
62
61
  | `getMany` | `where?`, `select?`, `orderBy?`, `take?`, `skip?`, `lock?`, `setCache?`, `cacheTags?` | `T[]` |
63
62
  | `getManyPaginate` | `where?`, `select?`, `orderBy?`, `page?`, `pageSize?`, `setCache?`, `cacheTags?` | `PaginatedResult<T>` |
64
63
  | `getManyCursor` | `where?`, `select?`, `orderBy?`, `cursor?`, `take?`, `skip?`, `setCache?`, `cacheTags?` | `CursorPage<T>`; with `cursor`, default `skip: 1` |
65
- | `count` | `where?`, `select?`, `setCache?`, `cacheTags?` | `{ count: number }` |
66
- | `exists` | `where?`, `setCache?`, `cacheTags?` | `{ exists: boolean }` |
64
+ | `count` | `where?`, `setCache?`, `cacheTags?` | `number` |
65
+ | `exists` | `where?`, `setCache?`, `cacheTags?` | `boolean` |
67
66
  | `aggregate` | Prisma aggregate args + `setCache?`, `cacheTags?` | delegate result |
68
67
  | `groupBy` | Prisma groupBy args + `setCache?`, `cacheTags?` | delegate result |
69
68
 
@@ -86,6 +85,13 @@ All methods accept optional `tx`. Cached repos also accept cache fields (see bel
86
85
  | `upsert` | `where`, `create`, `update`, `select?`, `invalidate?`, `tags?` | `all` | `T` |
87
86
  | `deleteById` | `id`, `select?`, `invalidate?`, `tags?` | `all` | `T` |
88
87
  | `deleteMany` | `where`, `invalidate?`, `tags?` | `all` | `{ count }` |
88
+ | `update` | `where`, `data`, `select?`, `invalidate?`, `tags?` | `all` | `T` |
89
+ | `delete` | `where`, `select?`, `invalidate?`, `tags?` | `all` | `T` |
90
+ | `createManyAndReturn` | `data[]`, `select?`, `skipDuplicates?`, `invalidate?`, `tags?` | `queries` | `T[]` |
91
+ | `updateManyAndReturn` | `where`, `data`, `select?`, `invalidate?`, `tags?` | `all` | `T[]` |
92
+ | `upsertMany` | `data[]`, `skipDuplicates?`, `invalidate?`, `tags?` | `all` | `{ count }` |
93
+ | `queryRaw` | `sql`, `...params` | — | raw result |
94
+ | `executeRaw` | `sql`, `...params` | — | `number` (affected rows) |
89
95
 
90
96
  Mutation `tags`: `string[] | null | undefined | ((result) => string[] | null | undefined)`.
91
97
 
@@ -112,10 +118,10 @@ await repo.invalidateCache({ id?: string; tags?: string[] });
112
118
  | `ttl` | `86400` | Entity TTL (seconds). |
113
119
  | `nullTtl` | — | Negative cache for null results. |
114
120
  | `sensitiveFields` | `['password']` | Selects containing these never cache. |
115
- | `methods` | — | Per-method `{ enabled?, ttl? }` for `getById`, `getThrowById`, `getFirst`, `getMany`, `getManyPaginate`. |
121
+ | `methods` | — | Per-method `{ enabled?, ttl? }`. |
116
122
  | `defaultSetCache` | `false` | Reads cache unless caller passes `setCache: false`. |
117
123
  | `stampede` | see below | Per-repo stampede overrides. |
118
- | `compression` | | Hint for adapters (`'none' \| 'zstd' \| 'lz4'`). Redis adapter uses `'none' \| 'gzip'`. |
124
+ | `strictInvalidation` | `false` | When true, invalidation failures rethrow. |
119
125
 
120
126
  ### `InvalidateMode`
121
127
 
@@ -125,11 +131,6 @@ await repo.invalidateCache({ id?: string; tags?: string[] });
125
131
  | `entity` | Entity keys only |
126
132
  | `queries` | Query index only |
127
133
  | `none` | Skip — **required inside transactions** |
128
- | `stale` | Core treats like entity+queries (adapter may layer SWR) |
129
-
130
- ### Allowlist
131
-
132
- `setRegisteredCacheModels(['user', 'product'])`. Empty/unset = fail-open. When set, a repo with `cache` whose `model` is missing throws at init.
133
134
 
134
135
  ### Key schema
135
136
 
@@ -142,7 +143,7 @@ await repo.invalidateCache({ id?: string; tags?: string[] });
142
143
  {prefix}:v2:repo:{model}:t:{tag}:__idx
143
144
  ```
144
145
 
145
- Redis payloads use tagged JSON for `Date`, `BigInt`, `Bytes`, and `Decimal` (`__date`, `__bigint`, …). Bump to v2 keys causes a one-time miss after upgrade.
146
+ Redis payloads use tagged JSON for `Date`, `BigInt`, `Bytes`, and `Decimal`. Custom revive: `createRedisJsonReviver` from `@prismakit/redis`.
146
147
 
147
148
  ### Debug
148
149
 
@@ -162,8 +163,6 @@ Hits/misses/bypasses via `cacheDebugStorage` from `@prismakit/core`.
162
163
  | `backoff` | `'exponential'` (`'fixed'` also valid) |
163
164
  | `totalTimeoutMs` | `3000` |
164
165
 
165
- In-process `singleflight` is also used. If Redis is down, fail open.
166
-
167
166
  ### Adapters
168
167
 
169
168
  **Redis** (`@prismakit/redis`):
@@ -185,11 +184,11 @@ new RedisCacheAdapter({
185
184
  new MemoryCacheAdapter({ prefix: 'test', maxSize: 1000, defaultTtl: 300 });
186
185
  ```
187
186
 
188
- Custom: implement `CacheAdapter` (`get`/`set`/`del`/`setNx`/`setWithIndex`/`invalidateByIndex`/`saddAndExpire`/`smembers`/`isReady`/`getPrefix` + `safe*` wrappers). `get`/`safeGet` **must** return a fresh copy (structuredClone / JSON). Prefer fail-open `safe*` semantics.
187
+ Custom: implement `CacheAdapter`. Prefer fail-open `safe*` semantics.
189
188
 
190
189
  ## Auto-compose
191
190
 
192
- `splitSelect` keeps scalars (+ FK fields from DMMF) for the Prisma query. Relation keys load via the target repository. Target PK is always injected into the nested select.
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.
193
192
 
194
193
  Load meta once at bootstrap:
195
194
 
@@ -200,8 +199,6 @@ loadPrismaMetaFromDmmf(Prisma.dmmf); // Prisma 5/6
200
199
  loadPrismaMetaFromSchema('prisma/schema.prisma'); // Prisma 7 (no Prisma.dmmf)
201
200
  ```
202
201
 
203
- 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).
204
-
205
202
  Validate: `npx prismakit validate --auto-register` or `assertSelectComposeValid`.
206
203
 
207
204
  ### `ComposeOptions` (global via `setComposeOptions`)
@@ -211,7 +208,6 @@ Validate: `npx prismakit validate --auto-register` or `assertSelectComposeValid`
211
208
  | `maxDepth` | `10` | Max relation nesting. |
212
209
  | `parallel` | `true` | Same-level relations via `Promise.all`. |
213
210
  | `setCache` | `true` | Nested fetches pass `setCache: true` unless parent has `tx` / `setCache: false`. |
214
- | `tx` | — | Per-call only; forwarded to nested `getMany`. |
215
211
 
216
212
  Related repos must be registered on `RepositoryRegistry`.
217
213
 
@@ -227,7 +223,7 @@ Related repos must be registered on `RepositoryRegistry`.
227
223
  | `nowait` | Fail immediately if locked. |
228
224
  | `skipLocked` | Skip locked rows. **Cannot** combine with `nowait`. |
229
225
 
230
- `lock: true` / `'wallet'` / `'Wallet'` / `'wallets'` resolve via Prisma meta when available, else `buildLockConfigFromSchema`.
226
+ `lock: true` resolves via Prisma meta. Explicit: `{ tableName, columns? }`.
231
227
 
232
228
  ## Telemetry
233
229
 
@@ -237,12 +233,13 @@ import { setTelemetry } from '@prismakit/core';
237
233
  setTelemetry({
238
234
  enabled: true,
239
235
  onEvent: (event) => { /* metrics */ },
236
+ slowThreshold: 500, // emits query.slow for queries ≥ this ms
240
237
  });
241
238
  ```
242
239
 
243
240
  | Type | When |
244
241
  |------|------|
245
- | `cache.hit` / `cache.miss` / `cache.bypass` / `cache.invalidate` | Cache-aside path |
242
+ | `cache.hit` / `cache.miss` / `cache.bypass` / `cache.invalidate` / `cache.error` | Cache-aside path |
246
243
  | `compose.start` / `compose.complete` | Auto-compose (`queryCount`, `durationMs`) |
247
244
  | `lock.acquired` / `lock.waited` / `lock.timeout` | Row locks |
248
245
  | `stampede.locked` / `stampede.waited` / `stampede.fallthrough` | Stampede protection |
@@ -256,10 +253,6 @@ npx prismakit validate [--schema <path>] [--auto-register] [--no-assert]
256
253
  npx prismakit help
257
254
  ```
258
255
 
259
- Default generate output: `src/modules/<kebab>/repositories/<kebab>.repository.ts`.
260
-
261
- `--full` emits Nest module + controller + service + select/where types. Register `*Module` in `app.module.ts` afterwards.
262
-
263
256
  ## ESLint
264
257
 
265
258
  ```js
@@ -267,8 +260,6 @@ import prismakit from '@prismakit/eslint-plugin';
267
260
  export default [prismakit.configs.recommended];
268
261
  ```
269
262
 
270
- `recommended` turns all rules on at **error**.
271
-
272
263
  | Rule | Forbids |
273
264
  |------|---------|
274
265
  | `prismakit/no-prisma-service-outside-repos` | Inject/reference `PrismaService` / `PrismaClient` outside allowlist |
@@ -276,23 +267,15 @@ export default [prismakit.configs.recommended];
276
267
  | `prismakit/require-transaction-service` | `.$transaction` in feature code |
277
268
  | `prismakit/require-cached-repo-provider` | Cached repo class missing from Nest `providers` |
278
269
 
279
- Allowed path patterns (forward slashes):
280
-
281
- - `**/repositories/**`
282
- - `**/infrastructure/prisma/**`
283
- - `**/node_modules/**`
284
- - `packages/(core|nestjs|redis)/**` (PrismaKit monorepo)
285
-
286
- Mirror this layout rather than weakening the plugin.
270
+ Allowed path patterns: `**/repositories/**`, `**/infrastructure/prisma/**`.
287
271
 
288
272
  ## Bootstrap (plain Node)
289
273
 
290
274
  ```typescript
291
- import { Prisma, PrismaClient } from '@prisma/client';
275
+ import { PrismaClient } from '@prisma/client';
292
276
  import {
293
277
  createRepository,
294
278
  loadPrismaMetaFromSchema,
295
- setRegisteredCacheModels,
296
279
  setComposeOptions,
297
280
  setTelemetry,
298
281
  RepositoryRegistry,
@@ -302,7 +285,6 @@ import { RedisCacheAdapter } from '@prismakit/redis';
302
285
 
303
286
  const prisma = new PrismaClient();
304
287
  loadPrismaMetaFromSchema('prisma/schema.prisma');
305
- setRegisteredCacheModels(['user', 'post']);
306
288
  setComposeOptions({ maxDepth: 6, parallel: true, setCache: true });
307
289
  setTelemetry({ enabled: true, onEvent: (e) => console.debug('[pk]', e.type) });
308
290
 
@@ -312,7 +294,6 @@ const autoCompose = new AutoComposer(registry);
312
294
 
313
295
  const UserRepo = createRepository({
314
296
  model: 'user',
315
- scalarFields: Prisma.UserScalarFieldEnum,
316
297
  cache: { ttl: 86_400 },
317
298
  });
318
299
  const users = new UserRepo({ prisma, cache, registry, autoCompose });
@@ -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
- - [ ] `scalarFields` is set **or** Prisma meta is loaded at bootstrap (`dmmf` / `schemaPath`)
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`, client key, table name, or config) before any call passes `lock`
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/defineInjectableRepository, TransactionService.execTx with afterCommit invalidation,
6
- and PRISMAKIT_* DI tokens. Use when working with @prismakit/nestjs, Nest feature modules that need
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
@@ -23,7 +25,6 @@ pnpm add -D @prismakit/eslint-plugin @prismakit/cli
23
25
  import { Module } from '@nestjs/common';
24
26
  import { PrismaKitModule } from '@prismakit/nestjs';
25
27
  import { RedisCacheAdapter } from '@prismakit/redis';
26
- import { Prisma } from '@prisma/client';
27
28
 
28
29
  @Module({
29
30
  imports: [
@@ -33,6 +34,11 @@ import { Prisma } from '@prisma/client';
33
34
  schemaPath: 'prisma/schema.prisma', // default; Prisma 5/6: dmmf: Prisma.dmmf
34
35
  validateCompose: true,
35
36
  compose: { maxDepth: 6, parallel: true, setCache: true },
37
+ telemetry: {
38
+ enabled: true,
39
+ slowThreshold: 500,
40
+ onEvent: (e) => console.debug(e.type),
41
+ },
36
42
  }),
37
43
  ],
38
44
  })
@@ -41,7 +47,7 @@ export class AppModule {}
41
47
 
42
48
  `forRootAsync` when cache/URL come from `ConfigService` — see [examples.md](examples.md).
43
49
 
44
- `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 — no relation-alias map.
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.
45
51
 
46
52
  ## Factory (one default)
47
53
 
@@ -96,14 +102,13 @@ export class AuditLogRepository extends defineAppRepo({
96
102
  }) {}
97
103
  ```
98
104
 
99
- `scalarFields` is **optional** when `schemaPath` or DMMF meta is loaded (default since 3.1). Omit it in new repos.
105
+ Per-repo options: `model` (required), `cache?`, `lock?: true | RepositoryLockConfig`, `toPayload?`.
100
106
 
101
- Escape hatches (do not use as the app default):
107
+ Escape hatch (do not use as the app default):
102
108
 
103
- - `defineInjectableRepository` from `@prismakit/nestjs` (package alias `defineRepository`) phantoms + payload HKT when TypeMap is unavailable.
104
- - `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).
105
110
 
106
- Do not import `defineRepo` from `@prismakit/nestjs` in apps that already bind `createDefineRepo` as `defineAppRepo`.
111
+ Removed in 4.0: `defineInjectableRepository`, `defineRepo`, `defineRepository`, `createPrismaRepository`, Nest `cacheModels`, Nest `queryLog`.
107
112
 
108
113
  ## Register and inject
109
114
 
@@ -201,7 +206,7 @@ await this.tx.execTx(
201
206
 
202
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.
203
208
 
204
- Repository `cache` is the source of truth. Omit `cacheModels` (fail-open). Pass an allowlist only if you want a second check.
209
+ Repository `cache` is the source of truth.
205
210
 
206
211
  `cache.defaultSetCache: true` makes user-facing reads cache by default; still pass `setCache: false` on auth/uniqueness.
207
212
 
@@ -220,12 +225,10 @@ Repository `cache` is the source of truth. Omit `cacheModels` (fail-open). Pass
220
225
 
221
226
  | Option | Use |
222
227
  |--------|-----|
223
- | `cacheModels` | Optional extra allowlist (omit — repo `cache` is enough) |
224
228
  | `validateCompose: true` | Assert compose-safe selects on boot |
225
229
  | `strictCachedRepos` | Fail boot if a `cache` repo class is not in Nest `providers` (default `true`) |
226
230
  | `compose` | `{ maxDepth, parallel, setCache }` |
227
- | `telemetry` | `{ enabled: true, onEvent }` or `createPrismaKitTelemetry()` from `@prismakit/opentelemetry` |
228
- | `queryLog` | `{ slowThreshold, onSlowQuery }` — enables telemetry / `query.slow` |
231
+ | `telemetry` | `{ enabled, slowThreshold, onSlowQuery, onEvent }` or `createPrismaKitTelemetry()` |
229
232
  | `autoRegisterModels` | `true` or `string[]` — stub repos for compose-only models |
230
233
 
231
234
  ## Scaffolding
@@ -244,7 +247,7 @@ Reference app: [starter-prismakit-nestjs](https://github.com/fikiap23/starter-pr
244
247
 
245
248
  ## Clean code (Nest)
246
249
 
247
- - One `defineRepo` binder under `src/infrastructure/prisma/`. Do not call `createDefineRepo` per feature.
250
+ - One `defineAppRepo` binder under `src/infrastructure/prisma/`. Do not call `createDefineRepo` per feature.
248
251
  - Feature modules own their repository providers; do not make every repo global.
249
252
  - Controllers stay HTTP-only: map DTO → service method. No repository calls in controllers.
250
253
  - Select presets live next to the repository (`minimal` / `general` / `withPassword`).
@@ -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, allowlist, slow-query log)
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
- queryLog: {
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`);
@@ -47,24 +48,30 @@ export class AppModule {}
47
48
 
48
49
  Prisma 5/6: pass `dmmf: Prisma.dmmf` instead of (or in addition to skipping) `schemaPath`. Prisma 7: `schemaPath` only.
49
50
 
50
- ## 2. TypeMap binder
51
+ ## 2. TypeMap binder with app-wide cache defaults
51
52
 
52
53
  ```typescript
53
- // src/infrastructure/prisma/define-repo.ts
54
+ // src/infrastructure/prisma/define-app-repo.ts
54
55
  import { createDefineRepo } from '@prismakit/nestjs';
55
- import type { Prisma } from '@prisma/client';
56
+ import type { Prisma } from 'src/infrastructure/prisma/prisma-client';
56
57
 
57
- export const defineRepo = createDefineRepo<Prisma.TypeMap>();
58
+ const DAY = 86_400;
59
+
60
+ export const defineAppRepo = createDefineRepo<Prisma.TypeMap>({
61
+ cache: {
62
+ ttl: DAY,
63
+ nullTtl: 60,
64
+ defaultSetCache: true,
65
+ },
66
+ });
58
67
  ```
59
68
 
60
69
  ## 3. Feature repository + select presets
61
70
 
62
71
  ```typescript
63
72
  // src/modules/users/repositories/user.repository.ts
64
- import { Prisma } from '@prisma/client';
65
- import { defineRepo } from '../../../infrastructure/prisma/define-repo';
66
-
67
- const DAY = 86_400;
73
+ import { Prisma } from 'src/infrastructure/prisma/prisma-client';
74
+ import { defineAppRepo } from 'src/infrastructure/prisma/define-app-repo';
68
75
 
69
76
  export const userSelectPresets = {
70
77
  minimal: { id: true } satisfies Prisma.UserSelect,
@@ -80,18 +87,24 @@ export const userSelectPresets = {
80
87
  } satisfies Prisma.UserSelect,
81
88
  };
82
89
 
83
- export const UserRepository = defineRepo({
90
+ export class UserRepository extends defineAppRepo({
84
91
  model: 'user',
85
- scalarFields: Prisma.UserScalarFieldEnum,
86
92
  cache: {
87
- ttl: DAY,
88
- nullTtl: 60,
93
+ defaultSetCache: false, // auth lookups pass setCache explicitly
89
94
  sensitiveFields: ['password'],
90
95
  methods: { getFirst: { enabled: false } },
91
96
  },
92
- lock: true,
93
- });
94
- export interface UserRepository extends InstanceType<typeof UserRepository> {}
97
+ }) {}
98
+ ```
99
+
100
+ ```typescript
101
+ // Simple cached repo — inherits app-wide defaults
102
+ import { defineAppRepo } from 'src/infrastructure/prisma/define-app-repo';
103
+
104
+ export class CategoryRepository extends defineAppRepo({
105
+ model: 'category',
106
+ cache: true,
107
+ }) {}
95
108
  ```
96
109
 
97
110
  ## 4. Feature module + thin controller + service