@zeroxsolutions/db 9.5.0 → 10.1.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +57 -22
  2. package/README.md +149 -180
  3. package/dist/base/d1/entity.d.ts.map +1 -1
  4. package/dist/base/d1/outbox.d.ts.map +1 -1
  5. package/dist/base/d1/outbox.js +1 -3
  6. package/dist/base/d1/repository.d.ts +76 -66
  7. package/dist/base/d1/repository.d.ts.map +1 -1
  8. package/dist/base/d1/repository.js +134 -122
  9. package/dist/base/d1/unit-of-work.d.ts.map +1 -1
  10. package/dist/base/postgres/entity.d.ts.map +1 -1
  11. package/dist/base/postgres/outbox.d.ts.map +1 -1
  12. package/dist/base/postgres/outbox.js +1 -3
  13. package/dist/base/postgres/repository.d.ts +109 -86
  14. package/dist/base/postgres/repository.d.ts.map +1 -1
  15. package/dist/base/postgres/repository.js +182 -143
  16. package/dist/base/postgres/unit-of-work.d.ts.map +1 -1
  17. package/dist/base/read-engine.d.ts +48 -0
  18. package/dist/base/read-engine.d.ts.map +1 -0
  19. package/dist/base/read-engine.js +94 -0
  20. package/dist/utils/filter.d.ts +44 -59
  21. package/dist/utils/filter.d.ts.map +1 -1
  22. package/dist/utils/filter.js +51 -85
  23. package/dist/utils/index.d.ts +3 -5
  24. package/dist/utils/index.d.ts.map +1 -1
  25. package/dist/utils/index.js +3 -5
  26. package/dist/utils/read.d.ts +88 -0
  27. package/dist/utils/read.d.ts.map +1 -0
  28. package/dist/utils/read.js +124 -0
  29. package/dist/utils/reads.d.ts +30 -0
  30. package/dist/utils/reads.d.ts.map +1 -0
  31. package/dist/utils/reads.js +26 -0
  32. package/dist/utils/relationships.d.ts +9 -6
  33. package/dist/utils/relationships.d.ts.map +1 -1
  34. package/dist/utils/relationships.js +26 -8
  35. package/package.json +7 -2
  36. package/dist/utils/assert-queryable-fields.d.ts +0 -27
  37. package/dist/utils/assert-queryable-fields.d.ts.map +0 -1
  38. package/dist/utils/assert-queryable-fields.js +0 -47
  39. package/dist/utils/constants.d.ts +0 -7
  40. package/dist/utils/constants.d.ts.map +0 -1
  41. package/dist/utils/constants.js +0 -6
  42. package/dist/utils/include.d.ts +0 -80
  43. package/dist/utils/include.d.ts.map +0 -1
  44. package/dist/utils/include.js +0 -132
  45. package/dist/utils/table-query.d.ts +0 -41
  46. package/dist/utils/table-query.d.ts.map +0 -1
  47. package/dist/utils/table-query.js +0 -1
  48. package/dist/utils/total-order.d.ts +0 -15
  49. package/dist/utils/total-order.d.ts.map +0 -1
  50. package/dist/utils/total-order.js +0 -35
@@ -1,17 +1,22 @@
1
- import { and, count, eq, getTableColumns, getViewSelectedFields, isNull, sql, } from 'drizzle-orm';
2
- import { resolveRelationalRead, selectedColumns } from '../../utils/index.js';
3
- import { buildOrderBy, DEFAULT_OFFSET, DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE } from '../../utils/index.js';
4
- import { totalOrder } from '../../utils/total-order.js';
5
- /** A Postgres repository over `TTable` with CRUD, pagination, soft-delete, and relation loading. */
1
+ import { and, count, eq, getTableColumns, getViewSelectedFields, inArray, sql, } from 'drizzle-orm';
2
+ import { orderOf, whereOf } from '../../utils/filter.js';
3
+ import { plainColumns } from '../../utils/read.js';
4
+ import { conditionOf, liveCondition, loadsRelations, orderClausesOf, primaryKeyFields, queryDelegateOf, relationalReadOf, } from '../read-engine.js';
5
+ /** A Postgres repository over `TTable`: reads that name what they load, soft delete, and writes keyed by id. */
6
6
  export class BaseRepository {
7
7
  db;
8
8
  table;
9
9
  queryKey;
10
- /** @param queryKey - omitted: `findAll`/`findOne` throw if called with `with` or `include`, and a fieldset names no relation. */
11
- constructor(db, table, queryKey) {
10
+ reads;
11
+ /**
12
+ * @param queryKey - the table's EXPORT name in the schema; omitted, a read that loads a relation throws
13
+ * @param reads - the context's `READS`: each table's order as a relation, and its `virtual` names
14
+ */
15
+ constructor(db, table, queryKey, reads = {}) {
12
16
  this.db = db;
13
17
  this.table = table;
14
18
  this.queryKey = queryKey;
19
+ this.reads = reads;
15
20
  }
16
21
  getExecutor(tx) {
17
22
  const executor = tx ?? this.db;
@@ -19,82 +24,111 @@ export class BaseRepository {
19
24
  throw new Error('Database executor is not available');
20
25
  return executor;
21
26
  }
22
- async findAll(where, options) {
23
- const columns = getTableColumns(this.table);
24
- const limit = Math.min(options?.limit ?? DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE);
25
- const offset = options?.offset ?? DEFAULT_OFFSET;
26
- let _where = where;
27
- if (!options?.softDeleted && columns.deletedAt)
28
- _where = and(where, isNull(columns.deletedAt));
29
- const executor = this.getExecutor(options?.tx);
30
- const read = resolveRelationalRead(executor, this.queryKey, this.table, options);
31
- // The key after the caller's sort: rows that tie on it have no order of their own, and two pages
32
- // cut from two orders of the same rows repeat one and skip another.
33
- const orderClauses = buildOrderBy(this.table, totalOrder(this.table, options?.sort));
34
- if (read.with) {
35
- if (!this.queryKey)
36
- throw new Error('queryKey must be provided to use the "with" option.');
37
- const queries = executor.query;
38
- const safeQueryKey = String(this.queryKey);
39
- const queryDelegate = queries[safeQueryKey];
40
- if (!queryDelegate)
41
- throw new Error(`Invalid queryKey: ${safeQueryKey}`);
42
- return await queryDelegate.findMany({
43
- where: _where,
44
- limit,
45
- offset,
46
- columns: read.columns,
47
- with: read.with,
48
- orderBy: orderClauses,
27
+ get source() {
28
+ return {
29
+ table: this.table,
30
+ queryKey: this.queryKey,
31
+ reads: this.reads,
32
+ caseInsensitiveOperator: true,
33
+ };
34
+ }
35
+ idColumn(action) {
36
+ const column = getTableColumns(this.table)['id'];
37
+ if (!column)
38
+ throw new Error(`${action} by id is not supported for this table`);
39
+ return column;
40
+ }
41
+ async findOneById(id, options) {
42
+ return this.findOneWhere(eq(this.idColumn('Find one'), id), options);
43
+ }
44
+ async findAllById(ids, options) {
45
+ const column = this.idColumn('Find all');
46
+ if (ids.length === 0)
47
+ return [];
48
+ return this.findAllWhere(inArray(column, [...ids]), options);
49
+ }
50
+ async existsById(id, options) {
51
+ return this.existsWhere(eq(this.idColumn('Exists'), id), options);
52
+ }
53
+ async findAll(options) {
54
+ return this.findAllWhere(undefined, options);
55
+ }
56
+ async findAllAndCount(options) {
57
+ return this.findAllAndCountWhere(undefined, options);
58
+ }
59
+ async count(options) {
60
+ return this.countWhere(undefined, options);
61
+ }
62
+ /** The rows matching `where` and the options, in their order and page window. */
63
+ async findAllWhere(where, options) {
64
+ const executor = this.getExecutor();
65
+ const condition = conditionOf(this.source, executor, where, options);
66
+ const order = orderClausesOf(this.source, executor, options);
67
+ if (loadsRelations(options?.read)) {
68
+ const config = relationalReadOf(this.source, executor, options?.read);
69
+ return queryDelegateOf(executor, String(this.queryKey)).findMany({
70
+ ...config,
71
+ where: condition,
72
+ orderBy: order,
73
+ limit: options?.limit,
74
+ offset: options?.offset,
49
75
  });
50
76
  }
51
- const rows = await executor
52
- .select(selectedColumns(columns, read.columns))
77
+ let query = executor
78
+ .select(this.plainSelection(options?.read))
53
79
  .from(this.table)
54
- .limit(limit)
55
- .offset(offset)
56
- .where(_where)
57
- .orderBy(...orderClauses);
58
- return rows;
80
+ .where(condition)
81
+ .orderBy(...order)
82
+ .$dynamic();
83
+ if (options?.limit !== undefined)
84
+ query = query.limit(options.limit);
85
+ if (options?.offset !== undefined)
86
+ query = query.offset(options.offset);
87
+ return (await query);
59
88
  }
60
- async findAllAndCount(where, options) {
61
- return await Promise.all([this.findAll(where, options), this.count(where, options)]);
89
+ /** {@link findAllWhere} and {@link countWhere} over the same `where` and filter, so the total matches the page. */
90
+ async findAllAndCountWhere(where, options) {
91
+ return await Promise.all([this.findAllWhere(where, options), this.countWhere(where, options)]);
62
92
  }
63
- async findOne(where, options) {
64
- const columns = getTableColumns(this.table);
65
- let _where = where;
66
- if (!options?.softDeleted && columns.deletedAt)
67
- _where = and(where, isNull(columns.deletedAt));
68
- const executor = this.getExecutor(options?.tx);
69
- const read = resolveRelationalRead(executor, this.queryKey, this.table, options);
70
- if (read.with) {
71
- if (!this.queryKey)
72
- throw new Error('queryKey must be provided to use the "with" option.');
73
- const queries = executor.query;
74
- const safeQueryKey = String(this.queryKey);
75
- const queryDelegate = queries[safeQueryKey];
76
- if (!queryDelegate)
77
- throw new Error(`Invalid queryKey: ${safeQueryKey}`);
78
- const result = await queryDelegate.findFirst({
79
- where: _where,
80
- columns: read.columns,
81
- with: read.with,
93
+ /** The first row matching `where`, loaded as the options' `read` names. */
94
+ async findOneWhere(where, options) {
95
+ const executor = this.getExecutor();
96
+ const condition = and(where, liveCondition(this.table, options?.softDeleted));
97
+ if (loadsRelations(options?.read)) {
98
+ const config = relationalReadOf(this.source, executor, options?.read);
99
+ const row = await queryDelegateOf(executor, String(this.queryKey)).findFirst({
100
+ ...config,
101
+ where: condition,
82
102
  });
83
- return result ?? null;
103
+ return row ?? null;
84
104
  }
85
105
  const rows = await executor
86
- .select(selectedColumns(columns, read.columns))
106
+ .select(this.plainSelection(options?.read))
87
107
  .from(this.table)
88
- .where(_where)
108
+ .where(condition)
89
109
  .limit(1);
90
110
  return rows[0] ?? null;
91
111
  }
92
- async findOneById(id, options) {
112
+ /** How many rows match `where` and the options' filter. */
113
+ async countWhere(where, options) {
114
+ const result = await this.getExecutor()
115
+ .select({ count: count() })
116
+ .from(this.table)
117
+ .where(conditionOf(this.source, this.getExecutor(), where, options));
118
+ return result[0]?.count ?? 0;
119
+ }
120
+ /** Whether any row matches `where` and the options' filter. */
121
+ async existsWhere(where, options) {
122
+ const result = await this.getExecutor()
123
+ .select({ value: sql `1` })
124
+ .from(this.table)
125
+ .where(conditionOf(this.source, this.getExecutor(), where, options))
126
+ .limit(1);
127
+ return result.length > 0;
128
+ }
129
+ plainSelection(read) {
93
130
  const columns = getTableColumns(this.table);
94
- if (!columns.id) {
95
- throw new Error('Find one by id is not supported for this table');
96
- }
97
- return this.findOne(eq(columns.id, id), options);
131
+ return plainColumns(columns, primaryKeyFields(this.table), read, String(this.queryKey ?? 'table'));
98
132
  }
99
133
  async create(data, options) {
100
134
  const rows = await this.getExecutor(options?.tx).insert(this.table).values(data).returning();
@@ -104,53 +138,38 @@ export class BaseRepository {
104
138
  const rows = await this.getExecutor(options?.tx).insert(this.table).values(data).returning();
105
139
  return rows;
106
140
  }
107
- async update(where, data, options) {
108
- const rows = await this.getExecutor(options?.tx).update(this.table).set(data).where(where).returning();
109
- return rows[0];
110
- }
111
141
  async updateById(id, data, options) {
112
- const columns = getTableColumns(this.table);
113
- if (!columns.id) {
114
- throw new Error('Update by id is not supported for this table');
115
- }
116
- return await this.update(eq(columns.id, id), data, options);
117
- }
118
- async delete(where, options) {
119
- await this.getExecutor(options?.tx).delete(this.table).where(where);
142
+ return await this.updateWhere(eq(this.idColumn('Update'), id), data, options);
120
143
  }
121
144
  async deleteById(id, options) {
122
- const columns = getTableColumns(this.table);
123
- if (!columns.id) {
124
- throw new Error('Delete by id is not supported for this table');
125
- }
126
- await this.delete(eq(columns.id, id), options);
145
+ await this.deleteWhere(eq(this.idColumn('Delete'), id), options);
127
146
  }
128
- async count(where, options) {
129
- const columns = getTableColumns(this.table);
130
- const executor = this.getExecutor(options?.tx);
131
- let _where = where;
132
- if (!options?.softDeleted && columns.deletedAt)
133
- _where = and(where, isNull(columns.deletedAt));
134
- const result = await executor
135
- .select({ count: count() })
136
- .from(this.table)
137
- .where(_where);
138
- return result[0]?.count ?? 0;
147
+ async deleteAllById(ids, options) {
148
+ const column = this.idColumn('Delete all');
149
+ if (ids.length === 0)
150
+ return;
151
+ await this.deleteWhere(inArray(column, [...ids]), options);
139
152
  }
140
- async exists(where, options) {
141
- const columns = getTableColumns(this.table);
142
- const executor = this.getExecutor(options?.tx);
143
- let _where = where;
144
- if (!options?.softDeleted && columns.deletedAt)
145
- _where = and(where, isNull(columns.deletedAt));
146
- const result = await executor
147
- .select({ value: sql `1` })
148
- .from(this.table)
149
- .where(_where)
150
- .limit(1);
151
- return result.length > 0;
153
+ async softDeleteById(id, options) {
154
+ await this.softDeleteWhere(eq(this.idColumn('Soft delete'), id), options);
155
+ }
156
+ async softDeleteAllById(ids, options) {
157
+ const column = this.idColumn('Soft delete all');
158
+ if (ids.length === 0)
159
+ return;
160
+ await this.softDeleteWhere(inArray(column, [...ids]), options);
152
161
  }
153
- async softDelete(where, options) {
162
+ /** Updates the rows matching `where`, answering the first. */
163
+ async updateWhere(where, data, options) {
164
+ const rows = await this.getExecutor(options?.tx).update(this.table).set(data).where(where).returning();
165
+ return rows[0] ?? null;
166
+ }
167
+ /** Removes the rows matching `where`. */
168
+ async deleteWhere(where, options) {
169
+ await this.getExecutor(options?.tx).delete(this.table).where(where);
170
+ }
171
+ /** Marks the rows matching `where` deleted. */
172
+ async softDeleteWhere(where, options) {
154
173
  const columns = getTableColumns(this.table);
155
174
  if (!columns.deletedAt) {
156
175
  throw new Error('Soft delete is not supported for this table');
@@ -160,13 +179,7 @@ export class BaseRepository {
160
179
  .set({ deletedAt: new Date() })
161
180
  .where(where);
162
181
  }
163
- async softDeleteById(id, options) {
164
- const columns = getTableColumns(this.table);
165
- if (!columns.id || !columns.deletedAt) {
166
- throw new Error('Soft delete by id is not supported for this table');
167
- }
168
- await this.softDelete(eq(columns.id, id), options);
169
- }
182
+ /** Sets `field` to null on every row whose `field` holds `value`. */
170
183
  async nullifyBy(field, value, options) {
171
184
  const columns = getTableColumns(this.table);
172
185
  const column = columns[field];
@@ -180,15 +193,18 @@ export class BaseRepository {
180
193
  }
181
194
  }
182
195
  /**
183
- * A read-only repository over a Postgres materialized view, with paginated reads and a refresh. A view has no
184
- * `deletedAt` and no relational config, and its order is the one its own definition states.
196
+ * A read-only repository over a Postgres materialized view. A view has no `deletedAt` and no relational config, and it
197
+ * is read back with no order of its own, so every order ends on the columns of its unique index.
185
198
  */
186
199
  export class BaseViewRepository {
187
200
  db;
188
201
  viewTable;
189
- constructor(db, viewTable) {
202
+ key;
203
+ /** @param key - the columns of the view's unique index, appended to every order as a table's primary key is */
204
+ constructor(db, viewTable, key) {
190
205
  this.db = db;
191
206
  this.viewTable = viewTable;
207
+ this.key = key;
192
208
  }
193
209
  getExecutor(tx) {
194
210
  const executor = tx ?? this.db;
@@ -196,40 +212,63 @@ export class BaseViewRepository {
196
212
  throw new Error('Database executor is not available');
197
213
  return executor;
198
214
  }
199
- /** The view's selected fields, narrowed to the fieldset `options` gives it. */
200
- selectedFields(options) {
201
- const read = resolveRelationalRead(undefined, undefined, this.viewTable, { fields: options?.fields });
215
+ /** The view's selected fields, which a filter and an order resolve against. */
216
+ get selectable() {
202
217
  // drizzle types a view's selection as `ColumnsSelection`, values `unknown`; it is the object its own `select()` reads.
203
- return selectedColumns(getViewSelectedFields(this.viewTable), read.columns);
218
+ return getViewSelectedFields(this.viewTable);
204
219
  }
205
- async findAll(where, options) {
206
- const limit = Math.min(options?.limit ?? DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE);
207
- const executor = this.getExecutor(options?.tx);
208
- return (await executor
209
- .select(this.selectedFields(options))
220
+ get fields() {
221
+ return {
222
+ columns: this.selectable,
223
+ relationKeys: {},
224
+ virtual: {},
225
+ at: new Date(),
226
+ context: undefined,
227
+ caseInsensitiveOperator: true,
228
+ };
229
+ }
230
+ async findAll(options) {
231
+ return this.findAllWhere(undefined, options);
232
+ }
233
+ async findAllAndCount(options) {
234
+ return this.findAllAndCountWhere(undefined, options);
235
+ }
236
+ async count(options) {
237
+ return this.countWhere(undefined, options);
238
+ }
239
+ /** The view rows matching `where` and the options, in their order and page window. */
240
+ async findAllWhere(where, options) {
241
+ let query = this.getExecutor()
242
+ .select(plainColumns(this.selectable, this.key, options?.read, 'view'))
210
243
  .from(this.viewTable)
211
- .limit(limit)
212
- .offset(options?.offset ?? DEFAULT_OFFSET)
213
- .where(where));
244
+ .where(and(where, whereOf(this.fields, options?.filter)))
245
+ .orderBy(...orderOf(this.fields, options?.orderBy, this.key))
246
+ .$dynamic();
247
+ if (options?.limit !== undefined)
248
+ query = query.limit(options.limit);
249
+ if (options?.offset !== undefined)
250
+ query = query.offset(options.offset);
251
+ return (await query);
214
252
  }
215
- async findOne(where, options) {
216
- const executor = this.getExecutor(options?.tx);
217
- const rows = await executor
218
- .select(this.selectedFields(options))
253
+ /** The first view row matching `where`. */
254
+ async findOneWhere(where, options) {
255
+ const rows = await this.getExecutor()
256
+ .select(plainColumns(this.selectable, this.key, options?.read, 'view'))
219
257
  .from(this.viewTable)
220
258
  .where(where)
221
259
  .limit(1);
222
260
  return rows[0] ?? null;
223
261
  }
224
- async findAllAndCount(where, options) {
225
- return await Promise.all([this.findAll(where, options), this.count(where, options)]);
262
+ /** {@link findAllWhere} and {@link countWhere} over the same `where` and filter. */
263
+ async findAllAndCountWhere(where, options) {
264
+ return await Promise.all([this.findAllWhere(where, options), this.countWhere(where, options)]);
226
265
  }
227
- async count(where, options) {
228
- const executor = this.getExecutor(options?.tx);
229
- const result = await executor
266
+ /** How many view rows match `where` and the options' filter. */
267
+ async countWhere(where, options) {
268
+ const result = await this.getExecutor()
230
269
  .select({ count: count() })
231
270
  .from(this.viewTable)
232
- .where(where);
271
+ .where(and(where, whereOf(this.fields, options?.filter)));
233
272
  return result[0]?.count ?? 0;
234
273
  }
235
274
  async refresh(options) {
@@ -1 +1 @@
1
- {"version":3,"file":"unit-of-work.d.ts","sourceRoot":"","sources":["../../../src/base/postgres/unit-of-work.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD;;;;GAIG;AACH,qBAAa,iBAAiB,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,CACnF,YAAW,WAAW,CAAC,aAAa,CAAC;IAGnC,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,gBAAgB;gBADhB,EAAE,EAAE,kBAAkB,CAAC,OAAO,CAAC,EAC/B,gBAAgB,EAAE,CAAC,EAAE,EAAE,WAAW,CAAC,OAAO,CAAC,KAAK,aAAa;IAGhF,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,YAAY,EAAE,aAAa,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAGtE"}
1
+ {"version":3,"file":"unit-of-work.d.ts","sourceRoot":"","sources":["../../../src/base/postgres/unit-of-work.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD;;;;GAIG;AACH,qBAAa,iBAAiB,CAC5B,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACvC,aAAa,CACb,YAAW,WAAW,CAAC,aAAa,CAAC;IAEnC,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,gBAAgB;gBADhB,EAAE,EAAE,kBAAkB,CAAC,OAAO,CAAC,EAC/B,gBAAgB,EAAE,CAAC,EAAE,EAAE,WAAW,CAAC,OAAO,CAAC,KAAK,aAAa;IAGhF,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,YAAY,EAAE,aAAa,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAGtE"}
@@ -0,0 +1,48 @@
1
+ import { type SQL, type Table } from 'drizzle-orm';
2
+ import { type FieldContext } from '../utils/filter.js';
3
+ import { type MatchOptions, type Read, type RelationalConfig, type RelationalMetadata } from '../utils/read.js';
4
+ import type { TableReads } from '../utils/reads.js';
5
+ /**
6
+ * What every base repository reads with, whichever engine runs it: the table, its export name in the schema, and the
7
+ * context's `READS`.
8
+ */
9
+ export interface ReadSource {
10
+ table: Table;
11
+ queryKey: string | undefined;
12
+ reads: Readonly<Record<string, TableReads | undefined>>;
13
+ /** Postgres matches text with `ILIKE`; SQLite has none. */
14
+ caseInsensitiveOperator: boolean;
15
+ }
16
+ /** The relational config a drizzle client was built with, or `undefined` for one built with no schema. */
17
+ export declare function metadataOf(executor: unknown): RelationalMetadata | undefined;
18
+ /**
19
+ * The property keys of the table's primary key columns, in the order the key declares them.
20
+ *
21
+ * A composite key sets no column's `primary` flag, so it is read off the table config instead, and matched back by SQL
22
+ * name: the key holds its own copies of the columns, so its column objects are not the table's (drizzle-orm@0.45.1).
23
+ */
24
+ export declare function primaryKeyFields(table: Table): string[];
25
+ /** What resolving a filter or an order name of this table needs, decided at `at` against `context`. */
26
+ export declare function fieldContextOf(source: ReadSource, executor: unknown, options: Pick<MatchOptions, 'at' | 'context'> | undefined): FieldContext;
27
+ /** The root table's soft-delete condition, unless the read asks for marked rows or the table marks none. */
28
+ export declare function liveCondition(table: Table, softDeleted: boolean | undefined): SQL | undefined;
29
+ /** A finder's own `where`, the caller's filter and the soft-delete condition, joined. */
30
+ export declare function conditionOf(source: ReadSource, executor: unknown, where: SQL | undefined, options: MatchOptions | undefined): SQL | undefined;
31
+ /** The order a read of many rows runs in, the primary key appended. */
32
+ export declare function orderClausesOf(source: ReadSource, executor: unknown, options: ({
33
+ orderBy?: readonly {
34
+ field: string;
35
+ direction: 'asc' | 'desc';
36
+ }[];
37
+ } & Pick<MatchOptions, 'at' | 'context'>) | undefined): SQL[];
38
+ /** Whether a read loads any relation, which only drizzle's relational query can. */
39
+ export declare function loadsRelations(read: Read | undefined): boolean;
40
+ /**
41
+ * The relational config a read resolves into, for drizzle's `findFirst` / `findMany`.
42
+ *
43
+ * @throws Error when the repository has no `queryKey` or its client no schema, which a relation needs
44
+ */
45
+ export declare function relationalReadOf(source: ReadSource, executor: unknown, read: Read | undefined): Pick<RelationalConfig, 'columns' | 'with'>;
46
+ /** The relational query delegate drizzle builds for the table exported as `queryKey`. */
47
+ export declare function queryDelegateOf<TDelegate>(executor: unknown, queryKey: string): TDelegate;
48
+ //# sourceMappingURL=read-engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-engine.d.ts","sourceRoot":"","sources":["../../src/base/read-engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsD,KAAK,GAAG,EAAE,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AAGvG,OAAO,EAAoB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,IAAI,EACT,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EAExB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,SAAS,CAAC,CAAC,CAAC;IACxD,2DAA2D;IAC3D,uBAAuB,EAAE,OAAO,CAAC;CAClC;AAED,0GAA0G;AAC1G,wBAAgB,UAAU,CAAC,QAAQ,EAAE,OAAO,GAAG,kBAAkB,GAAG,SAAS,CAM5E;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,EAAE,CAOvD;AAiBD,uGAAuG;AACvG,wBAAgB,cAAc,CAC5B,MAAM,EAAE,UAAU,EAClB,QAAQ,EAAE,OAAO,EACjB,OAAO,EAAE,IAAI,CAAC,YAAY,EAAE,IAAI,GAAG,SAAS,CAAC,GAAG,SAAS,GACxD,YAAY,CAUd;AAED,4GAA4G;AAC5G,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,GAAG,SAAS,GAAG,GAAG,GAAG,SAAS,CAG7F;AAED,yFAAyF;AACzF,wBAAgB,WAAW,CACzB,MAAM,EAAE,UAAU,EAClB,QAAQ,EAAE,OAAO,EACjB,KAAK,EAAE,GAAG,GAAG,SAAS,EACtB,OAAO,EAAE,YAAY,GAAG,SAAS,GAChC,GAAG,GAAG,SAAS,CAGjB;AAED,uEAAuE;AACvE,wBAAgB,cAAc,CAC5B,MAAM,EAAE,UAAU,EAClB,QAAQ,EAAE,OAAO,EACjB,OAAO,EACH,CAAC;IAAE,OAAO,CAAC,EAAE,SAAS;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,KAAK,GAAG,MAAM,CAAA;KAAE,EAAE,CAAA;CAAE,GAAG,IAAI,CAAC,YAAY,EAAE,IAAI,GAAG,SAAS,CAAC,CAAC,GAC9G,SAAS,GACZ,GAAG,EAAE,CAEP;AAED,oFAAoF;AACpF,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,GAAG,OAAO,CAE9D;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,UAAU,EAClB,QAAQ,EAAE,OAAO,EACjB,IAAI,EAAE,IAAI,GAAG,SAAS,GACrB,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,MAAM,CAAC,CAS5C;AAED,yFAAyF;AACzF,wBAAgB,eAAe,CAAC,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,CAKzF"}
@@ -0,0 +1,94 @@
1
+ import { and, getTableColumns, is, isNull, One } from 'drizzle-orm';
2
+ import { getTableConfig as getPgTableConfig, PgTable } from 'drizzle-orm/pg-core';
3
+ import { getTableConfig as getSQLiteTableConfig } from 'drizzle-orm/sqlite-core';
4
+ import { orderOf, whereOf } from '../utils/filter.js';
5
+ import { relationalConfig, } from '../utils/read.js';
6
+ /** The relational config a drizzle client was built with, or `undefined` for one built with no schema. */
7
+ export function metadataOf(executor) {
8
+ const internals = executor._;
9
+ if (!internals?.schema || !internals.tableNamesMap)
10
+ return undefined;
11
+ return { tables: internals.schema, tableNamesMap: internals.tableNamesMap };
12
+ }
13
+ /**
14
+ * The property keys of the table's primary key columns, in the order the key declares them.
15
+ *
16
+ * A composite key sets no column's `primary` flag, so it is read off the table config instead, and matched back by SQL
17
+ * name: the key holds its own copies of the columns, so its column objects are not the table's (drizzle-orm@0.45.1).
18
+ */
19
+ export function primaryKeyFields(table) {
20
+ const columns = getTableColumns(table);
21
+ const fieldOf = new Map(Object.entries(columns).map(([field, column]) => [column.name, field]));
22
+ const declared = Object.values(columns).filter((column) => column.primary);
23
+ if (declared.length > 0)
24
+ return declared.flatMap((column) => fieldOf.get(column.name) ?? []);
25
+ const config = is(table, PgTable) ? getPgTableConfig(table) : getSQLiteTableConfig(table);
26
+ return (config.primaryKeys[0]?.columns ?? []).flatMap((column) => fieldOf.get(column.name) ?? []);
27
+ }
28
+ /**
29
+ * Each relation of the table that reaches one row through a column the table itself holds, mapped to that column: a
30
+ * relationship filter compares the related row's id, which is that column's value.
31
+ */
32
+ function relationKeysOf(source, metadata) {
33
+ if (!metadata || source.queryKey === undefined)
34
+ return {};
35
+ const relations = metadata.tables[source.queryKey]?.relations ?? {};
36
+ const keys = {};
37
+ for (const [name, relation] of Object.entries(relations)) {
38
+ const fields = is(relation, One) ? relation.config?.fields : undefined;
39
+ if (fields?.length === 1 && fields[0])
40
+ keys[name] = fields[0];
41
+ }
42
+ return keys;
43
+ }
44
+ /** What resolving a filter or an order name of this table needs, decided at `at` against `context`. */
45
+ export function fieldContextOf(source, executor, options) {
46
+ const metadata = metadataOf(executor);
47
+ return {
48
+ columns: getTableColumns(source.table),
49
+ relationKeys: relationKeysOf(source, metadata),
50
+ virtual: (source.queryKey !== undefined ? source.reads[source.queryKey]?.virtual : undefined) ?? {},
51
+ at: options?.at ?? new Date(),
52
+ context: options?.context,
53
+ caseInsensitiveOperator: source.caseInsensitiveOperator,
54
+ };
55
+ }
56
+ /** The root table's soft-delete condition, unless the read asks for marked rows or the table marks none. */
57
+ export function liveCondition(table, softDeleted) {
58
+ const columns = getTableColumns(table);
59
+ return !softDeleted && columns['deletedAt'] ? isNull(columns['deletedAt']) : undefined;
60
+ }
61
+ /** A finder's own `where`, the caller's filter and the soft-delete condition, joined. */
62
+ export function conditionOf(source, executor, where, options) {
63
+ const context = fieldContextOf(source, executor, options);
64
+ return and(where, whereOf(context, options?.filter), liveCondition(source.table, options?.softDeleted));
65
+ }
66
+ /** The order a read of many rows runs in, the primary key appended. */
67
+ export function orderClausesOf(source, executor, options) {
68
+ return orderOf(fieldContextOf(source, executor, options), options?.orderBy, primaryKeyFields(source.table));
69
+ }
70
+ /** Whether a read loads any relation, which only drizzle's relational query can. */
71
+ export function loadsRelations(read) {
72
+ return read?.relations !== undefined && Object.keys(read.relations).length > 0;
73
+ }
74
+ /**
75
+ * The relational config a read resolves into, for drizzle's `findFirst` / `findMany`.
76
+ *
77
+ * @throws Error when the repository has no `queryKey` or its client no schema, which a relation needs
78
+ */
79
+ export function relationalReadOf(source, executor, read) {
80
+ const metadata = metadataOf(executor);
81
+ if (source.queryKey === undefined || !metadata) {
82
+ throw new Error('A read loading a relation needs a queryKey and a client built with its schema.');
83
+ }
84
+ const orders = Object.fromEntries(Object.entries(source.reads).map(([key, declared]) => [key, declared?.orderBy]));
85
+ return relationalConfig(metadata, orders, source.queryKey, read);
86
+ }
87
+ /** The relational query delegate drizzle builds for the table exported as `queryKey`. */
88
+ export function queryDelegateOf(executor, queryKey) {
89
+ const queries = executor.query;
90
+ const delegate = Object.hasOwn(queries, queryKey) ? queries[queryKey] : undefined;
91
+ if (!delegate)
92
+ throw new Error(`Invalid queryKey: ${queryKey}`);
93
+ return delegate;
94
+ }