@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.
@@ -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` + Prisma meta loaded (or `scalarFields` when meta unavailable) |
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` is an alias of `createRepository` in core. Note: `createPrismaRepository` is also an alias in core, but in `@prismakit/nestjs` it aliases `createInjectableRepository` instead — avoid using it to prevent confusion.
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
 
@@ -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. 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.
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`; `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.
204
204
 
205
- 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.
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 })` or Nest `telemetry` / `queryLog.slowThreshold`.
248
- - Optional: `@prismakit/opentelemetry` → `createPrismaKitTelemetry({ slowThreshold })`.
249
- - Events: `cache.hit` / `cache.miss` / `cache.bypass` / `cache.invalidate` / `cache.error`, `compose.*`, `lock.*`, `stampede.*`, `query.complete` / `query.slow`.
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.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
- Alias: `defineRepository`. (`createPrismaRepository` is also an alias in core but maps to `createInjectableRepository` in `@prismakit/nestjs` — avoid it to prevent confusion.)
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 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
 
@@ -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? }` for `getById`, `getThrowById`, `getFirst`, `getThrowFirst`, `getMany`, `getManyPaginate`, `getManyCursor`, `count`, `exists`, `aggregate`, `groupBy`. |
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
- | `compression` | | Hint for adapters (`'none' \| 'zstd' \| 'lz4'`). Redis adapter uses `'none' \| 'gzip'`. |
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` (`__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`.
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` (`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.
197
188
 
198
189
  ## Auto-compose
199
190
 
200
- `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.
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` / `'wallet'` / `'Wallet'` / `'wallets'` resolve via Prisma meta when available, else `buildLockConfigFromSchema`.
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 (forward slashes):
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 { Prisma, PrismaClient } from '@prisma/client';
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
- - [ ] `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
@@ -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 — 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.
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
- `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?`.
99
106
 
100
- Escape hatches (do not use as the app default):
107
+ Escape hatch (do not use as the app default):
101
108
 
102
- - `defineInjectableRepository` from `@prismakit/nestjs` (package alias `defineRepository`) phantoms + payload HKT when TypeMap is unavailable.
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
- 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`.
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. Omit `cacheModels` (fail-open). Pass an allowlist only if you want a second check.
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: true, onEvent }` or `createPrismaKitTelemetry()` from `@prismakit/opentelemetry` |
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, 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`);
@@ -1,6 +1,6 @@
1
1
  # PrismaKit NestJS reference
2
2
 
3
- API surface for `@prismakit/nestjs` 3.x. Repository methods, cache, compose, and locks are documented in skill `prismakit` (`reference.md` in that skill). This file covers the Nest adapter only.
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
- | `cacheModels` | no | Optional extra allowlist. Omit — repo `cache` is the source of truth. |
17
- | `compose` | no | `ComposeOptions`: `maxDepth` (default 10), `parallel` (default true), `setCache` (default true). `tx` is per-call only. |
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
- `queryLog.onSlowQuery` receives `{ model?, method?, durationMs, thresholdMs }` for `query.complete` events at/above the threshold.
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 local `defineRepo({ model, ... })` | **Default** for apps. Zero phantoms; `model` + `scalarFields` + `cache` / `lock`. |
84
- | `defineInjectableRepository({ model, select, create, update, where, orderBy, payload, ... })` | TypeMap unavailable. Package aliases: `defineRepository`. |
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`, `schemaPath`) and per-repo options: `model`, `scalarFields?` (optional since 3.1), `primaryKey?`, `cache?` (`true` inherits app defaults), `lock?`, `schemaPath?`.
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
- `createDefineRepo` / `RepositoryApiFromTypeMap` includes the full runtime surface: `create`, `createMany`, `createManyAndReturn`, `update`, `updateById`, `updateMany`, `updateManyAndReturn`, `upsert`, `upsertMany`, `delete`, `deleteById`, `deleteMany`, `getThrowFirst`, `count`, `exists`, `aggregate`, `groupBy`, `getManyCursor`, `queryRaw`, `executeRaw`, `lock` + `orderBy` on `getFirst`, `lock` on `getMany`, and composite-PK `id` on `*ById`. `primaryKey` is optional — composite `@@id` is read from schema meta.
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 table/client key) is set when any call uses `lock`
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("from '@prismakit/nestjs'");
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', () => {
@@ -44,7 +44,6 @@ export function findSkillsRoot(startDir: string): string | undefined {
44
44
  for (const candidate of [
45
45
  path.join(dir, 'skills'),
46
46
  dir,
47
- path.join(dir, 'templates', 'cursor-skills'),
48
47
  ]) {
49
48
  if (isSkillsRoot(candidate)) return candidate;
50
49
  }
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 { createInjectableRepository } from '@prismakit/nestjs';
46
+ `import { defineAppRepo } from 'src/infrastructure/prisma/define-app-repo';
54
47
 
55
- export class {{pascal}}Repository extends createInjectableRepository({
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
  `,