@apisr/drizzle-model 2.0.0 → 2.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/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ # Change log
2
+
3
+ ## 2.0.1 | 01-03-2026
4
+ - mark `pg` as peerDep
5
+ - add `CHANGELOG.md`
6
+ - add `relations.test.ts` to test relations
7
+ - add `SimplifyDeep<>` from `type-fest` in queries, for better DX of using relations
8
+ - fix relations `include()` function
9
+
10
+ ## 2.0.0 | 28-02-2026
11
+ - add `REAMDE.md`
12
+ - add `count()` function
13
+ - add `Simplify<>` to all queries
14
+ - add `returnFirst()` and make `return()` to return array of rows
15
+ - add `omit()` as transformator after `return()/returnFirst()`
16
+ - remake entire core, better JSDoc, OOP over functional, and much more...
17
+ - remake all tests
18
+ - add `safe()` function
19
+ ```ts
20
+ const { data: user, error } = userModel.findFirst().safe();
21
+ ```
22
+ - and in overall just make better DX
package/README.md ADDED
@@ -0,0 +1,433 @@
1
+ # @apisr/drizzle-model
2
+
3
+ > **⚠️ This package is on high development stage! May have bugs**
4
+
5
+ > **⚠️ Requires `drizzle-orm@beta`**
6
+ > This package is built for Drizzle ORM beta versions (`^1.0.0-beta.2-86f844e`).
7
+
8
+ Type-safe, chainable model runtime for **Drizzle ORM**.
9
+
10
+ Build reusable models for tables and relations with a progressive flow:
11
+
12
+ 1. **Intent Stage** — declare what you want (`where`, `insert`, `update`, ...)
13
+ 2. **Execution Stage** — choose execution (`findMany`, `findFirst`, `return`, `returnFirst`)
14
+ 3. **Refinement Stage** — shape the SQL query (`select`, `exclude`, `with`)
15
+ 4. **Programmatic Polishing** — post-process the result (`omit`, `raw`, `safe`)
16
+
17
+ ---
18
+
19
+ ## Learning Path (easy → advanced)
20
+
21
+ 1. [Install and create your first model](#install-and-first-model)
22
+ 2. [Basic reads](#basic-reads)
23
+ 3. [Basic writes](#basic-writes)
24
+ 4. [Result refinement](#result-refinement)
25
+ 5. [Error-safe execution](#error-safe-execution)
26
+ 6. [Advanced: model options and extension](#advanced-model-options-and-extension)
27
+ 7. [Full API reference](#full-api-reference)
28
+
29
+ ---
30
+
31
+ ## Install and first model
32
+
33
+ ```bash
34
+ bun add @apisr/drizzle-model drizzle-orm@beta
35
+ ```
36
+
37
+ ```ts
38
+ import { modelBuilder, esc } from "@apisr/drizzle-model";
39
+ import { drizzle } from "drizzle-orm/node-postgres";
40
+ import * as schema from "./schema";
41
+ import { relations } from "./relations";
42
+
43
+ const db = drizzle(process.env.DATABASE_URL!, { schema, relations });
44
+
45
+ const model = modelBuilder({
46
+ db,
47
+ schema,
48
+ // requires DrizzleORM relations v2. See: https://orm.drizzle.team/docs/relations-v1-v2
49
+ relations,
50
+ dialect: "PostgreSQL",
51
+ });
52
+
53
+ const userModel = model("user", {});
54
+ ```
55
+
56
+ > `drizzle-orm` is a peer dependency.
57
+
58
+ ---
59
+
60
+ ## Basic reads
61
+
62
+ ### Find one
63
+
64
+ ```ts
65
+ const user = await userModel.findFirst();
66
+ ```
67
+
68
+ ### Find many with filter
69
+
70
+ ```ts
71
+ const users = await userModel
72
+ .where({ name: esc("Alex") })
73
+ .findMany();
74
+ ```
75
+
76
+ ### Count
77
+
78
+ ```ts
79
+ const total = await userModel.count();
80
+ const verified = await userModel.where({ isVerified: esc(true) }).count();
81
+ ```
82
+
83
+ ---
84
+
85
+ ## Basic writes
86
+
87
+ ### Insert
88
+
89
+ ```ts
90
+ await userModel.insert({
91
+ name: "New User",
92
+ email: "new@example.com",
93
+ age: 18,
94
+ });
95
+ ```
96
+
97
+ ### Update
98
+
99
+ ```ts
100
+ const updated = await userModel
101
+ .where({ id: esc(1) })
102
+ .update({ name: "Updated" })
103
+ .returnFirst();
104
+ ```
105
+
106
+ ### Delete
107
+
108
+ ```ts
109
+ await userModel.where({ id: esc(2) }).delete();
110
+ ```
111
+
112
+ ### Upsert
113
+
114
+ ```ts
115
+ const row = await userModel
116
+ .upsert({
117
+ insert: { name: "Alex", email: "alex@ex.com", age: 20 },
118
+ update: { name: "Alex Updated" },
119
+ target: schema.user.email,
120
+ })
121
+ .returnFirst();
122
+ ```
123
+
124
+ ---
125
+
126
+ ## Result refinement
127
+
128
+ ### Query-side refinement (`findMany` / `findFirst` result)
129
+
130
+ #### Loading relations with `.with()`
131
+
132
+ ```ts
133
+ // Load related posts for each user
134
+ const users = await userModel
135
+ .findMany()
136
+ .with({ posts: true });
137
+
138
+ // Nested relations
139
+ const users = await userModel
140
+ .findMany()
141
+ .with({
142
+ posts: {
143
+ comments: true,
144
+ },
145
+ });
146
+
147
+ // Multiple relations
148
+ const users = await userModel
149
+ .findMany()
150
+ .with({
151
+ posts: true,
152
+ invitee: true,
153
+ });
154
+
155
+ // Query `where` relations
156
+ const users = await userModel
157
+ .findMany()
158
+ .with({
159
+ posts: postModel.where({
160
+ title: {
161
+ like: "New%"
162
+ }
163
+ }),
164
+ });
165
+ ```
166
+
167
+ #### Using `.include()` for type-safe relation values
168
+
169
+ `.include()` is a helper that returns the relation value as-is, used for type-level relation selection:
170
+
171
+ ```ts
172
+ // Pass to .with()
173
+ const users = await userModel.findMany().with({
174
+ posts: postModel.where({
175
+ title: {
176
+ like: "New%"
177
+ }
178
+ }).include({
179
+ comments: true
180
+ })
181
+ });
182
+ ```
183
+
184
+ #### SQL column selection with `.select()` and `.exclude()`
185
+
186
+ `.select()` and `.exclude()` control which columns appear in the SQL `SELECT` clause — they affect the query itself, not just the result.
187
+
188
+ ```ts
189
+ // Only fetch id and name columns
190
+ const users = await userModel
191
+ .findMany()
192
+ .select({ id: true, name: true });
193
+
194
+ // Fetch all columns except email
195
+ const users = await userModel
196
+ .findMany()
197
+ .exclude({ email: true });
198
+
199
+ // Combine: start with a whitelist, then drop a field
200
+ const users = await userModel
201
+ .findMany()
202
+ .select({ id: true, name: true, email: true })
203
+ .exclude({ email: true });
204
+ ```
205
+
206
+ This is equivalent to:
207
+
208
+ ```ts
209
+ db.select({ id: schema.user.id, name: schema.user.name }).from(schema.user);
210
+ ```
211
+
212
+ #### Combining query refiners
213
+
214
+ ```ts
215
+ const users = await userModel
216
+ .findMany()
217
+ .with({ posts: true })
218
+ .select({ id: true, name: true });
219
+ ```
220
+
221
+ Available query refiners:
222
+
223
+ - `.select(fields)` — SQL SELECT whitelist
224
+ - `.exclude(fields)` — SQL SELECT blacklist
225
+ - `.with(relations)` — load related entities via JOINs
226
+ - `.raw()` — skip format function
227
+ - `.safe()` — wrap in `{ data, error }`
228
+ - `.debug()` — inspect query state
229
+
230
+ ### Mutation-side refinement (`insert` / `update` / `delete` / `upsert` result)
231
+
232
+ ```ts
233
+ const rows = await userModel
234
+ .insert({ email: "a@b.com", name: "Alex", age: 20 })
235
+ .return();
236
+
237
+ const first = await userModel
238
+ .insert({ email: "b@b.com", name: "Anna", age: 21 })
239
+ .returnFirst();
240
+
241
+ // .omit() removes fields from the result AFTER the query runs (programmatic, not SQL)
242
+ const sanitized = await userModel
243
+ .where({ id: esc(1) })
244
+ .update({ secretField: 999 })
245
+ .returnFirst()
246
+ .omit({ secretField: true });
247
+ ```
248
+
249
+ Available mutation refiners:
250
+
251
+ - `.return(fields?)` — return all rows
252
+ - `.returnFirst(fields?)` — return first row
253
+ - `.omit(fields)` — remove fields from result after query (programmatic, not SQL)
254
+ - `.safe()` — wrap in `{ data, error }`
255
+
256
+ ---
257
+
258
+ ## Error-safe execution
259
+
260
+ Use `.safe()` when you prefer a result object instead of throw/reject behavior.
261
+
262
+ ```ts
263
+ const result = await userModel.findMany().safe();
264
+
265
+ if (result.error) {
266
+ console.error(result.error);
267
+ } else {
268
+ console.log(result.data);
269
+ }
270
+ ```
271
+
272
+ Shape:
273
+
274
+ ```ts
275
+ type SafeResult<T> =
276
+ | { data: T; error: undefined }
277
+ | { data: undefined; error: unknown };
278
+ ```
279
+
280
+ ---
281
+
282
+ ## Advanced: model options and extension
283
+
284
+ ### `format`
285
+
286
+ ```ts
287
+ const userModel = model("user", {
288
+ format(row) {
289
+ const { secretField, ...rest } = row;
290
+ return {
291
+ ...rest,
292
+ isVerified: Boolean(rest.isVerified),
293
+ };
294
+ },
295
+ });
296
+ ```
297
+
298
+ Use `.raw()` to bypass format.
299
+
300
+ ### Default `where`
301
+
302
+ ```ts
303
+ const activeUsers = model("user", {
304
+ where: { isVerified: esc(true) },
305
+ });
306
+ ```
307
+
308
+ ### Custom `methods`
309
+
310
+ ```ts
311
+ const userModel = model("user", {
312
+ methods: {
313
+ async byEmail(email: string) {
314
+ return await userModel.where({ email: esc(email) }).findFirst();
315
+ },
316
+ },
317
+ });
318
+ ```
319
+
320
+ ### `extend()` and `db()`
321
+
322
+ ```ts
323
+ const extended = userModel.extend({
324
+ methods: {
325
+ async adults() {
326
+ return await userModel.where({ age: { gte: esc(18) } }).findMany();
327
+ },
328
+ },
329
+ });
330
+
331
+ const txUserModel = userModel.db(db);
332
+ ```
333
+
334
+ Note: when method names conflict during `extend`, existing runtime methods take precedence over newly passed ones.
335
+
336
+ ---
337
+
338
+ ## Full API reference
339
+
340
+ ### Model-level methods
341
+
342
+ - Query/lifecycle:
343
+ - `where(value)`
344
+ - `findMany()`
345
+ - `findFirst()`
346
+ - `count()`
347
+ - `include(value)`
348
+ - `extend(options)`
349
+ - `db(dbInstance)`
350
+ - Mutations:
351
+ - `insert(value)`
352
+ - `update(value)`
353
+ - `delete()`
354
+ - `upsert(value)`
355
+
356
+ ### Query result methods
357
+
358
+ - `.with(...)`
359
+ - `.select(...)`
360
+ - `.exclude(...)`
361
+ - `.raw()`
362
+ - `.safe()`
363
+ - `.debug()`
364
+
365
+ ### Mutation result methods
366
+
367
+ - `.return(...)`
368
+ - `.returnFirst(...)`
369
+ - `.omit(...)`
370
+ - `.safe()`
371
+
372
+ ---
373
+
374
+ ## Dialect notes
375
+
376
+ - Dialects with native `.returning()` use it for mutation return pipelines.
377
+ - Dialects with ID-only return paths may use dialect-specific fallback behavior.
378
+ - Upsert uses `onConflictDoUpdate` when supported.
379
+
380
+ ---
381
+
382
+ ## Type safety notes
383
+
384
+ - Prefer `esc(...)` for explicit where value/operator expressions.
385
+ - `.select()` and `.exclude()` control SQL SELECT columns and refine result types.
386
+ - `.omit()` removes fields from the result programmatically after the query.
387
+ - `.safe()` wraps result types into `{ data, error }`.
388
+ - `.return()` returns array shape; `.returnFirst()` returns single-row shape.
389
+
390
+ ---
391
+
392
+ ## Testing
393
+
394
+ Comprehensive tests are available in `tests/base`:
395
+
396
+ - `find.test.ts`
397
+ - `insert.test.ts`
398
+ - `update.test.ts`
399
+ - `delete.test.ts`
400
+ - `upsert.test.ts`
401
+ - `count.test.ts`
402
+ - `safe.test.ts`
403
+ - `relations.test.ts`
404
+
405
+ Run all base tests:
406
+
407
+ ```bash
408
+ bun test base
409
+ ```
410
+
411
+ ---
412
+
413
+ ## Troubleshooting
414
+
415
+ ### `safe()` returns `{ data: undefined, error }`
416
+
417
+ The underlying operation throws. Re-run without `.safe()` to inspect the raw stack.
418
+
419
+ ### `.return()` result shape surprises
420
+
421
+ - `.return()` => array
422
+ - `.returnFirst()` => single object
423
+ - no return chain => dialect/default execution behavior
424
+
425
+ ### Relation loading with `.with(...)`
426
+
427
+ Ensure relation metadata is defined with Drizzle `defineRelations` and passed to `modelBuilder({ relations })`.
428
+
429
+ ---
430
+
431
+ ## License
432
+
433
+ MIT (follow repository root license if different).
package/TODO.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # TODO:
2
2
 
3
- - DONE TYPES => `count()` function to count rows
3
+ - DONE => `count()` function to count rows
4
4
  - DONE => Fix types. On insert, and add Simplify<> = for more readable queries.
5
- - DONE TYPES => add `returnFirst()` to return first of `return()`
6
- - DONE TYPES => add `omit()` as progammic `exclude()`. The main difference is `omit()` is applied after query. `exclude()` is applied on the query.
7
- - JUST remake a entire core. Manually write code with a few AI changes.
8
- - DONE TYPES => Add `safe()`:
5
+ - DONE => add `returnFirst()` to return first of `return()`
6
+ - DONE => add `omit()` as progammic `exclude()`. The main difference is `omit()` is applied after query. `exclude()` is applied on the query.
7
+ - DONE JUST remake a entire core. Manually write code with a few AI changes.
8
+ - DONE => Add `safe()`:
9
9
  ```ts
10
10
  const { data: user, error } = userModel.findFirst().safe();
11
11
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apisr/drizzle-model",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "lint": "eslint . --max-warnings 0",
@@ -8,7 +8,8 @@
8
8
  "check-types": "tsc --noEmit"
9
9
  },
10
10
  "peerDependencies": {
11
- "drizzle-orm": "^1.0.0-beta.2-86f844e"
11
+ "drizzle-orm": "^1.0.0-beta.2-86f844e",
12
+ "pg": "^8.16.3"
12
13
  },
13
14
  "devDependencies": {
14
15
  "@repo/eslint-config": "*",
@@ -31,6 +32,6 @@
31
32
  },
32
33
  "type": "module",
33
34
  "dependencies": {
34
- "pg": "^8.16.3"
35
+ "type-fest": "^5.4.4"
35
36
  }
36
37
  }
@@ -1,6 +1,7 @@
1
1
  import { and, eq } from "drizzle-orm";
2
2
  import type { DialectHelper } from "../dialect.ts";
3
3
  import { ProjectionBuilder } from "./projection.ts";
4
+ import { WhereCompiler } from "./where.ts";
4
5
 
5
6
  /** Generic record type used throughout the join executor. */
6
7
  type AnyRecord = Record<string, unknown>;
@@ -45,6 +46,8 @@ export interface JoinNode {
45
46
  targetTable: AnyRecord;
46
47
  /** Name of the target (child) table. */
47
48
  targetTableName: string;
49
+ /** Optional compiled WHERE filter for this relation (added to JOIN ON). */
50
+ whereFilter?: unknown;
48
51
  }
49
52
 
50
53
  // ---------------------------------------------------------------------------
@@ -59,12 +62,16 @@ export interface JoinExecutorConfig {
59
62
  baseTableName: string;
60
63
  /** The Drizzle database instance. */
61
64
  db: unknown;
65
+ /** SQL SELECT blacklist for base table columns. */
66
+ exclude?: AnyRecord;
62
67
  /** When `true`, only the first result is returned. */
63
68
  limitOne?: boolean;
64
69
  /** The relations metadata map from Drizzle. */
65
70
  relations: Record<string, AnyRecord>;
66
71
  /** The full schema map (`{ tableName: drizzleTable }`). */
67
72
  schema: Record<string, AnyRecord>;
73
+ /** SQL SELECT whitelist for base table columns. */
74
+ select?: AnyRecord;
68
75
  /** An optional compiled SQL where clause. */
69
76
  whereSql?: unknown;
70
77
  /** The user-supplied `.with()` value describing which relations to load. */
@@ -91,10 +98,12 @@ export interface JoinExecutorConfig {
91
98
  export class JoinExecutor {
92
99
  private readonly dialect: DialectHelper;
93
100
  private readonly projection: ProjectionBuilder;
101
+ private readonly whereCompiler: WhereCompiler;
94
102
 
95
103
  constructor(dialect: DialectHelper) {
96
104
  this.dialect = dialect;
97
105
  this.projection = new ProjectionBuilder();
106
+ this.whereCompiler = new WhereCompiler();
98
107
  }
99
108
 
100
109
  /**
@@ -126,6 +135,7 @@ export class JoinExecutor {
126
135
  */
127
136
  private async buildJoinTree(config: JoinExecutorConfig): Promise<JoinNode> {
128
137
  const usedAliasKeys = new Set<string>();
138
+ usedAliasKeys.add(`table:${config.baseTableName}`);
129
139
 
130
140
  const root: JoinNode = {
131
141
  path: [],
@@ -180,6 +190,9 @@ export class JoinExecutor {
180
190
  value: unknown,
181
191
  path: string[]
182
192
  ): Promise<JoinNode> {
193
+ const { whereValue, nestedWith } =
194
+ this.extractRelationDescriptor(value);
195
+
183
196
  const relMeta = this.getRelationMeta(
184
197
  config.relations,
185
198
  currentTableName,
@@ -200,6 +213,13 @@ export class JoinExecutor {
200
213
  ? await this.dialect.createTableAlias(targetTable, aliasKey)
201
214
  : targetTable;
202
215
 
216
+ const whereFilter = whereValue
217
+ ? this.whereCompiler.compile(
218
+ targetAliasTable as AnyRecord,
219
+ whereValue
220
+ )
221
+ : undefined;
222
+
203
223
  const node: JoinNode = {
204
224
  path: [...path, key],
205
225
  key,
@@ -215,10 +235,13 @@ export class JoinExecutor {
215
235
  pkField: this.getPrimaryKeyField(targetAliasTable),
216
236
  parent,
217
237
  children: [],
238
+ whereFilter,
218
239
  };
219
240
 
220
- if (value && typeof value === "object" && value !== (true as unknown)) {
221
- for (const [childKey, childVal] of Object.entries(value as AnyRecord)) {
241
+ if (nestedWith && typeof nestedWith === "object") {
242
+ for (const [childKey, childVal] of Object.entries(
243
+ nestedWith as AnyRecord
244
+ )) {
222
245
  if (
223
246
  childVal !== true &&
224
247
  (typeof childVal !== "object" || childVal == null)
@@ -258,8 +281,14 @@ export class JoinExecutor {
258
281
  root: JoinNode,
259
282
  nodes: JoinNode[]
260
283
  ): Promise<AnyRecord[]> {
284
+ const baseColumns = this.projection.build(
285
+ root.targetAliasTable,
286
+ config.select,
287
+ config.exclude
288
+ ).selectMap;
289
+
261
290
  const selectMap: AnyRecord = {
262
- base: this.projection.extractColumns(root.targetAliasTable),
291
+ base: baseColumns,
263
292
  };
264
293
 
265
294
  for (const node of nodes) {
@@ -387,6 +416,10 @@ export class JoinExecutor {
387
416
  return eq(tgtCol as never, src as never);
388
417
  });
389
418
 
419
+ if (node.whereFilter) {
420
+ parts.push(node.whereFilter as never);
421
+ }
422
+
390
423
  return parts.length === 1 ? parts[0] : and(...parts);
391
424
  }
392
425
 
@@ -496,6 +529,40 @@ export class JoinExecutor {
496
529
  );
497
530
  }
498
531
 
532
+ /**
533
+ * Extracts relation where clause and nested relations from a `.with()` value.
534
+ *
535
+ * Handles three cases:
536
+ * - `true` → no filter, no nested relations.
537
+ * - A ModelRuntime (has `$model === "model"`) → extract `$where`, no nested.
538
+ * - A model descriptor (`__modelRelation: true`) → extract `whereValue` and `with`.
539
+ * - A plain object → treat as nested relation map.
540
+ */
541
+ private extractRelationDescriptor(value: unknown): {
542
+ whereValue: unknown;
543
+ nestedWith: unknown;
544
+ } {
545
+ if (value === true || value == null) {
546
+ return { whereValue: undefined, nestedWith: undefined };
547
+ }
548
+
549
+ if (typeof value !== "object") {
550
+ return { whereValue: undefined, nestedWith: undefined };
551
+ }
552
+
553
+ const rec = value as AnyRecord;
554
+
555
+ if (rec.$model === "model") {
556
+ return { whereValue: rec.$where, nestedWith: undefined };
557
+ }
558
+
559
+ if (rec.__modelRelation === true) {
560
+ return { whereValue: rec.whereValue, nestedWith: rec.with };
561
+ }
562
+
563
+ return { whereValue: undefined, nestedWith: value };
564
+ }
565
+
499
566
  // ---------------------------------------------------------------------------
500
567
  // Helpers: result grouping internals
501
568
  // ---------------------------------------------------------------------------
@@ -22,13 +22,13 @@ export type SafeResult<T> =
22
22
 
23
23
  /** Accumulated state for a query operation (findMany / findFirst). */
24
24
  export interface QueryState {
25
- /** Column blacklist for `.exclude()`. */
25
+ /** SQL SELECT blacklist columns to omit from the query. */
26
26
  exclude?: AnyRecord;
27
27
  /** When `true`, formatting is skipped. */
28
28
  raw?: boolean;
29
29
  /** When `true`, result is wrapped in `{ data, error }`. */
30
30
  safe?: boolean;
31
- /** Column whitelist for `.select()`. */
31
+ /** SQL SELECT whitelist columns to include in the query. */
32
32
  select?: AnyRecord;
33
33
  /** Compiled where clause. */
34
34
  where?: unknown;
@@ -154,9 +154,12 @@ export class QueryResult<T> extends ThenableResult<T> {
154
154
  }
155
155
 
156
156
  /**
157
- * Whitelists specific fields in the result.
157
+ * Controls which columns appear in the SQL SELECT clause (whitelist).
158
158
  *
159
- * @param value - A map of `{ fieldName: true }`.
159
+ * This affects the query itself, not just the result.
160
+ * Equivalent to `db.select({ col: table.col }).from(table)`.
161
+ *
162
+ * @param value - A map of `{ columnName: true }`.
160
163
  * @returns A new `QueryResult` with the `.select()` state applied.
161
164
  */
162
165
  select(value: AnyRecord): QueryResult<T> {
@@ -164,9 +167,12 @@ export class QueryResult<T> extends ThenableResult<T> {
164
167
  }
165
168
 
166
169
  /**
167
- * Blacklists specific fields from the result.
170
+ * Controls which columns are excluded from the SQL SELECT clause (blacklist).
171
+ *
172
+ * This affects the query itself, not just the result.
173
+ * All columns except the listed ones will be fetched.
168
174
  *
169
- * @param value - A map of `{ fieldName: true }`.
175
+ * @param value - A map of `{ columnName: true }`.
170
176
  * @returns A new `QueryResult` with the `.exclude()` state applied.
171
177
  */
172
178
  exclude(value: AnyRecord): QueryResult<T> {