metal-orm 1.0.56 → 1.0.58

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 (82) hide show
  1. package/README.md +41 -33
  2. package/dist/index.cjs +1461 -195
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +541 -114
  5. package/dist/index.d.ts +541 -114
  6. package/dist/index.js +1424 -195
  7. package/dist/index.js.map +1 -1
  8. package/package.json +69 -69
  9. package/src/codegen/naming-strategy.ts +3 -1
  10. package/src/codegen/typescript.ts +20 -10
  11. package/src/core/ast/aggregate-functions.ts +14 -0
  12. package/src/core/ast/builders.ts +38 -20
  13. package/src/core/ast/expression-builders.ts +70 -2
  14. package/src/core/ast/expression-nodes.ts +305 -274
  15. package/src/core/ast/expression-visitor.ts +11 -1
  16. package/src/core/ast/expression.ts +4 -0
  17. package/src/core/ast/query.ts +3 -0
  18. package/src/core/ddl/introspect/catalogs/mysql.ts +5 -0
  19. package/src/core/ddl/introspect/catalogs/sqlite.ts +3 -0
  20. package/src/core/ddl/introspect/functions/mssql.ts +13 -0
  21. package/src/core/ddl/introspect/mssql.ts +4 -0
  22. package/src/core/ddl/introspect/mysql.ts +4 -0
  23. package/src/core/ddl/introspect/sqlite.ts +4 -0
  24. package/src/core/dialect/abstract.ts +552 -531
  25. package/src/core/dialect/base/function-table-formatter.ts +9 -30
  26. package/src/core/dialect/base/sql-dialect.ts +24 -0
  27. package/src/core/dialect/mssql/functions.ts +40 -2
  28. package/src/core/dialect/mysql/functions.ts +16 -2
  29. package/src/core/dialect/postgres/functions.ts +66 -2
  30. package/src/core/dialect/postgres/index.ts +17 -4
  31. package/src/core/dialect/postgres/table-functions.ts +27 -0
  32. package/src/core/dialect/sqlite/functions.ts +34 -0
  33. package/src/core/dialect/sqlite/index.ts +17 -1
  34. package/src/core/driver/database-driver.ts +9 -1
  35. package/src/core/driver/mssql-driver.ts +3 -0
  36. package/src/core/driver/mysql-driver.ts +3 -0
  37. package/src/core/driver/postgres-driver.ts +3 -0
  38. package/src/core/driver/sqlite-driver.ts +3 -0
  39. package/src/core/execution/executors/mssql-executor.ts +5 -0
  40. package/src/core/execution/executors/mysql-executor.ts +5 -0
  41. package/src/core/execution/executors/postgres-executor.ts +5 -0
  42. package/src/core/execution/executors/sqlite-executor.ts +5 -0
  43. package/src/core/functions/array.ts +26 -0
  44. package/src/core/functions/control-flow.ts +69 -0
  45. package/src/core/functions/datetime.ts +50 -0
  46. package/src/core/functions/definitions/aggregate.ts +16 -0
  47. package/src/core/functions/definitions/control-flow.ts +24 -0
  48. package/src/core/functions/definitions/datetime.ts +36 -0
  49. package/src/core/functions/definitions/helpers.ts +29 -0
  50. package/src/core/functions/definitions/json.ts +49 -0
  51. package/src/core/functions/definitions/numeric.ts +55 -0
  52. package/src/core/functions/definitions/string.ts +43 -0
  53. package/src/core/functions/function-registry.ts +48 -0
  54. package/src/core/functions/group-concat-helpers.ts +57 -0
  55. package/src/core/functions/json.ts +38 -0
  56. package/src/core/functions/numeric.ts +14 -0
  57. package/src/core/functions/standard-strategy.ts +86 -115
  58. package/src/core/functions/standard-table-strategy.ts +13 -0
  59. package/src/core/functions/table-types.ts +15 -0
  60. package/src/core/functions/text.ts +57 -0
  61. package/src/core/sql/sql.ts +59 -38
  62. package/src/decorators/bootstrap.ts +41 -4
  63. package/src/index.ts +18 -11
  64. package/src/orm/entity-meta.ts +6 -3
  65. package/src/orm/entity.ts +81 -14
  66. package/src/orm/execute.ts +87 -20
  67. package/src/orm/hydration-context.ts +10 -0
  68. package/src/orm/identity-map.ts +19 -0
  69. package/src/orm/interceptor-pipeline.ts +4 -0
  70. package/src/orm/lazy-batch.ts +237 -54
  71. package/src/orm/relations/belongs-to.ts +19 -2
  72. package/src/orm/relations/has-many.ts +23 -9
  73. package/src/orm/relations/has-one.ts +19 -2
  74. package/src/orm/relations/many-to-many.ts +59 -4
  75. package/src/orm/save-graph-types.ts +2 -2
  76. package/src/orm/save-graph.ts +18 -18
  77. package/src/query-builder/relation-conditions.ts +80 -59
  78. package/src/query-builder/relation-service.ts +399 -95
  79. package/src/query-builder/relation-types.ts +2 -2
  80. package/src/query-builder/select.ts +124 -106
  81. package/src/schema/table-guards.ts +6 -0
  82. package/src/schema/types.ts +109 -85
package/src/orm/entity.ts CHANGED
@@ -9,6 +9,7 @@ import { DefaultManyToManyCollection } from './relations/many-to-many.js';
9
9
  import { HasManyRelation, HasOneRelation, BelongsToRelation, BelongsToManyRelation, RelationKinds } from '../schema/relation.js';
10
10
  import { loadHasManyRelation, loadHasOneRelation, loadBelongsToRelation, loadBelongsToManyRelation } from './lazy-batch.js';
11
11
  import { findPrimaryKey } from '../query-builder/hydration-planner.js';
12
+ import { RelationIncludeOptions } from '../query-builder/relation-types.js';
12
13
 
13
14
  /**
14
15
  * Type representing an array of database rows.
@@ -23,7 +24,7 @@ type Rows = Record<string, unknown>[];
23
24
  * @param factory - The factory function to create the cache
24
25
  * @returns Promise with the cached relation data
25
26
  */
26
- const relationLoaderCache = <T extends Map<string, unknown>>(
27
+ export const relationLoaderCache = <T extends Map<string, unknown>>(
27
28
  meta: EntityMeta<TableDef>,
28
29
  relationName: string,
29
30
  factory: () => Promise<T>
@@ -69,13 +70,15 @@ export const createEntityProxy = <
69
70
  ctx: EntityContext,
70
71
  table: TTable,
71
72
  row: Record<string, unknown>,
72
- lazyRelations: TLazy[] = [] as TLazy[]
73
+ lazyRelations: TLazy[] = [] as TLazy[],
74
+ lazyRelationOptions: Map<string, RelationIncludeOptions> = new Map()
73
75
  ): EntityInstance<TTable> => {
74
76
  const target: Record<string, unknown> = { ...row };
75
77
  const meta: EntityMeta<TTable> = {
76
78
  ctx,
77
79
  table,
78
80
  lazyRelations: [...lazyRelations],
81
+ lazyRelationOptions: new Map(lazyRelationOptions),
79
82
  relationCache: new Map(),
80
83
  relationHydration: new Map(),
81
84
  relationWrappers: new Map()
@@ -141,7 +144,8 @@ export const createEntityFromRow = <
141
144
  ctx: EntityContext,
142
145
  table: TTable,
143
146
  row: Record<string, unknown>,
144
- lazyRelations: (keyof RelationMap<TTable>)[] = []
147
+ lazyRelations: (keyof RelationMap<TTable>)[] = [],
148
+ lazyRelationOptions: Map<string, RelationIncludeOptions> = new Map()
145
149
  ): TResult => {
146
150
  const pkName = findPrimaryKey(table);
147
151
  const pkValue = row[pkName];
@@ -150,7 +154,7 @@ export const createEntityFromRow = <
150
154
  if (tracked) return tracked as TResult;
151
155
  }
152
156
 
153
- const entity = createEntityProxy(ctx, table, row, lazyRelations);
157
+ const entity = createEntityProxy(ctx, table, row, lazyRelations, lazyRelationOptions);
154
158
  if (pkValue !== undefined && pkValue !== null) {
155
159
  ctx.trackManaged(table, pkValue, entity);
156
160
  } else {
@@ -223,6 +227,68 @@ const populateHydrationCache = <TTable extends TableDef>(
223
227
  }
224
228
  };
225
229
 
230
+ const proxifyRelationWrapper = <T extends object>(wrapper: T): T => {
231
+ return new Proxy(wrapper, {
232
+ get(target, prop, receiver) {
233
+ if (typeof prop === 'symbol') {
234
+ return Reflect.get(target, prop, receiver);
235
+ }
236
+
237
+ if (prop in target) {
238
+ return Reflect.get(target, prop, receiver);
239
+ }
240
+
241
+ const getItems = (target as { getItems?: () => unknown }).getItems;
242
+ if (typeof getItems === 'function') {
243
+ const items = getItems.call(target);
244
+ if (items && prop in (items as object)) {
245
+ const propName = prop as string;
246
+ const value = (items as Record<string, unknown>)[propName];
247
+ return typeof value === 'function' ? value.bind(items) : value;
248
+ }
249
+ }
250
+
251
+ const getRef = (target as { get?: () => unknown }).get;
252
+ if (typeof getRef === 'function') {
253
+ const current = getRef.call(target);
254
+ if (current && prop in (current as object)) {
255
+ const propName = prop as string;
256
+ const value = (current as Record<string, unknown>)[propName];
257
+ return typeof value === 'function' ? value.bind(current) : value;
258
+ }
259
+ }
260
+
261
+ return undefined;
262
+ },
263
+
264
+ set(target, prop, value, receiver) {
265
+ if (typeof prop === 'symbol') {
266
+ return Reflect.set(target, prop, value, receiver);
267
+ }
268
+
269
+ if (prop in target) {
270
+ return Reflect.set(target, prop, value, receiver);
271
+ }
272
+
273
+ const getRef = (target as { get?: () => unknown }).get;
274
+ if (typeof getRef === 'function') {
275
+ const current = getRef.call(target);
276
+ if (current && typeof current === 'object') {
277
+ return Reflect.set(current as object, prop, value);
278
+ }
279
+ }
280
+
281
+ const getItems = (target as { getItems?: () => unknown }).getItems;
282
+ if (typeof getItems === 'function') {
283
+ const items = getItems.call(target);
284
+ return Reflect.set(items as object, prop, value);
285
+ }
286
+
287
+ return Reflect.set(target, prop, value, receiver);
288
+ }
289
+ });
290
+ };
291
+
226
292
  /**
227
293
  * Gets a relation wrapper for an entity.
228
294
  * @param meta - The entity metadata
@@ -234,7 +300,7 @@ const getRelationWrapper = (
234
300
  meta: EntityMeta<TableDef>,
235
301
  relationName: string,
236
302
  owner: unknown
237
- ): HasManyCollection<unknown> | HasOneReference<unknown> | BelongsToReference<unknown> | ManyToManyCollection<unknown> | undefined => {
303
+ ): HasManyCollection<unknown> | HasOneReference<object> | BelongsToReference<object> | ManyToManyCollection<unknown> | undefined => {
238
304
  if (meta.relationWrappers.has(relationName)) {
239
305
  return meta.relationWrappers.get(relationName) as HasManyCollection<unknown>;
240
306
  }
@@ -243,11 +309,11 @@ const getRelationWrapper = (
243
309
  if (!relation) return undefined;
244
310
 
245
311
  const wrapper = instantiateWrapper(meta, relationName, relation, owner);
246
- if (wrapper) {
247
- meta.relationWrappers.set(relationName, wrapper);
248
- }
312
+ if (!wrapper) return undefined;
249
313
 
250
- return wrapper;
314
+ const proxied = proxifyRelationWrapper(wrapper as object);
315
+ meta.relationWrappers.set(relationName, proxied);
316
+ return proxied as HasManyCollection<unknown>;
251
317
  };
252
318
 
253
319
  /**
@@ -263,13 +329,14 @@ const instantiateWrapper = (
263
329
  relationName: string,
264
330
  relation: HasManyRelation | HasOneRelation | BelongsToRelation | BelongsToManyRelation,
265
331
  owner: unknown
266
- ): HasManyCollection<unknown> | HasOneReference<unknown> | BelongsToReference<unknown> | ManyToManyCollection<unknown> | undefined => {
332
+ ): HasManyCollection<unknown> | HasOneReference<object> | BelongsToReference<object> | ManyToManyCollection<unknown> | undefined => {
333
+ const lazyOptions = meta.lazyRelationOptions.get(relationName);
267
334
  switch (relation.type) {
268
335
  case RelationKinds.HasOne: {
269
336
  const hasOne = relation as HasOneRelation;
270
337
  const localKey = hasOne.localKey || findPrimaryKey(meta.table);
271
338
  const loader = () => relationLoaderCache(meta, relationName, () =>
272
- loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne)
339
+ loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne, lazyOptions)
273
340
  );
274
341
  return new DefaultHasOneReference(
275
342
  meta.ctx,
@@ -287,7 +354,7 @@ const instantiateWrapper = (
287
354
  const hasMany = relation as HasManyRelation;
288
355
  const localKey = hasMany.localKey || findPrimaryKey(meta.table);
289
356
  const loader = () => relationLoaderCache(meta, relationName, () =>
290
- loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany)
357
+ loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany, lazyOptions)
291
358
  );
292
359
  return new DefaultHasManyCollection(
293
360
  meta.ctx,
@@ -305,7 +372,7 @@ const instantiateWrapper = (
305
372
  const belongsTo = relation as BelongsToRelation;
306
373
  const targetKey = belongsTo.localKey || findPrimaryKey(belongsTo.target);
307
374
  const loader = () => relationLoaderCache(meta, relationName, () =>
308
- loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo)
375
+ loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo, lazyOptions)
309
376
  );
310
377
  return new DefaultBelongsToReference(
311
378
  meta.ctx,
@@ -323,7 +390,7 @@ const instantiateWrapper = (
323
390
  const many = relation as BelongsToManyRelation;
324
391
  const localKey = many.localKey || findPrimaryKey(meta.table);
325
392
  const loader = () => relationLoaderCache(meta, relationName, () =>
326
- loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many)
393
+ loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many, lazyOptions)
327
394
  );
328
395
  return new DefaultManyToManyCollection(
329
396
  meta.ctx,
@@ -1,16 +1,29 @@
1
- import { TableDef } from '../schema/table.js';
2
- import { EntityInstance } from '../schema/types.js';
3
- import { hydrateRows } from './hydration.js';
4
- import { OrmSession } from './orm-session.ts';
5
- import { SelectQueryBuilder } from '../query-builder/select.js';
6
- import { createEntityProxy, createEntityFromRow } from './entity.js';
1
+ import { TableDef } from '../schema/table.js';
2
+ import { EntityInstance, RelationMap } from '../schema/types.js';
3
+ import { RelationKinds } from '../schema/relation.js';
4
+ import { hydrateRows } from './hydration.js';
5
+ import { OrmSession } from './orm-session.ts';
6
+ import { SelectQueryBuilder } from '../query-builder/select.js';
7
+ import {
8
+ createEntityProxy,
9
+ createEntityFromRow,
10
+ relationLoaderCache
11
+ } from './entity.js';
7
12
  import { EntityContext } from './entity-context.js';
8
13
  import { ExecutionContext } from './execution-context.js';
9
14
  import { HydrationContext } from './hydration-context.js';
15
+ import { RelationIncludeOptions } from '../query-builder/relation-types.js';
16
+ import { getEntityMeta } from './entity-meta.js';
17
+ import {
18
+ loadHasManyRelation,
19
+ loadHasOneRelation,
20
+ loadBelongsToRelation,
21
+ loadBelongsToManyRelation
22
+ } from './lazy-batch.js';
10
23
 
11
24
  type Row = Record<string, unknown>;
12
25
 
13
- const flattenResults = (results: { columns: string[]; values: unknown[][] }[]): Row[] => {
26
+ const flattenResults = (results: { columns: string[]; values: unknown[][] }[]): Row[] => {
14
27
  const rows: Row[] = [];
15
28
  for (const result of results) {
16
29
  const { columns, values } = result;
@@ -23,8 +36,8 @@ const flattenResults = (results: { columns: string[]; values: unknown[][] }[]):
23
36
  }
24
37
  }
25
38
  return rows;
26
- };
27
-
39
+ };
40
+
28
41
  const executeWithContexts = async <TTable extends TableDef>(
29
42
  execCtx: ExecutionContext,
30
43
  entityCtx: EntityContext,
@@ -34,14 +47,20 @@ const executeWithContexts = async <TTable extends TableDef>(
34
47
  const compiled = execCtx.dialect.compileSelect(ast);
35
48
  const executed = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
36
49
  const rows = flattenResults(executed);
50
+ const lazyRelations = qb.getLazyRelations();
51
+ const lazyRelationOptions = qb.getLazyRelationOptions();
37
52
 
38
53
  if (ast.setOps && ast.setOps.length > 0) {
39
- return rows.map(row => createEntityProxy(entityCtx, qb.getTable(), row, qb.getLazyRelations()));
54
+ const proxies = rows.map(row => createEntityProxy(entityCtx, qb.getTable(), row, lazyRelations, lazyRelationOptions));
55
+ await loadLazyRelationsForTable(entityCtx, qb.getTable(), lazyRelations, lazyRelationOptions);
56
+ return proxies;
40
57
  }
41
-
42
- const hydrated = hydrateRows(rows, qb.getHydrationPlan());
43
- return hydrated.map(row => createEntityFromRow(entityCtx, qb.getTable(), row, qb.getLazyRelations()));
44
- };
58
+
59
+ const hydrated = hydrateRows(rows, qb.getHydrationPlan());
60
+ const entities = hydrated.map(row => createEntityFromRow(entityCtx, qb.getTable(), row, lazyRelations, lazyRelationOptions));
61
+ await loadLazyRelationsForTable(entityCtx, qb.getTable(), lazyRelations, lazyRelationOptions);
62
+ return entities;
63
+ };
45
64
 
46
65
  /**
47
66
  * Executes a hydrated query using the ORM session.
@@ -50,12 +69,12 @@ const executeWithContexts = async <TTable extends TableDef>(
50
69
  * @param qb - The select query builder
51
70
  * @returns Promise resolving to array of entity instances
52
71
  */
53
- export async function executeHydrated<TTable extends TableDef>(
54
- session: OrmSession,
55
- qb: SelectQueryBuilder<unknown, TTable>
56
- ): Promise<EntityInstance<TTable>[]> {
57
- return executeWithContexts(session.getExecutionContext(), session, qb);
58
- }
72
+ export async function executeHydrated<TTable extends TableDef>(
73
+ session: OrmSession,
74
+ qb: SelectQueryBuilder<unknown, TTable>
75
+ ): Promise<EntityInstance<TTable>[]> {
76
+ return executeWithContexts(session.getExecutionContext(), session, qb);
77
+ }
59
78
 
60
79
  /**
61
80
  * Executes a hydrated query using execution and hydration contexts.
@@ -76,3 +95,51 @@ export async function executeHydratedWithContexts<TTable extends TableDef>(
76
95
  }
77
96
  return executeWithContexts(execCtx, entityCtx, qb);
78
97
  }
98
+
99
+ const loadLazyRelationsForTable = async <TTable extends TableDef>(
100
+ ctx: EntityContext,
101
+ table: TTable,
102
+ lazyRelations: (keyof RelationMap<TTable>)[],
103
+ lazyRelationOptions: Map<string, RelationIncludeOptions>
104
+ ): Promise<void> => {
105
+ if (!lazyRelations.length) return;
106
+
107
+ const tracked = ctx.getEntitiesForTable(table);
108
+ if (!tracked.length) return;
109
+
110
+ const meta = getEntityMeta(tracked[0].entity);
111
+ if (!meta) return;
112
+
113
+ for (const relationName of lazyRelations) {
114
+ const relation = table.relations[relationName as string];
115
+ if (!relation) continue;
116
+ const key = relationName as string;
117
+ const options = lazyRelationOptions.get(key);
118
+ if (!options) {
119
+ continue;
120
+ }
121
+
122
+ switch (relation.type) {
123
+ case RelationKinds.HasOne:
124
+ await relationLoaderCache(meta, key, () =>
125
+ loadHasOneRelation(ctx, table, key, relation, options)
126
+ );
127
+ break;
128
+ case RelationKinds.HasMany:
129
+ await relationLoaderCache(meta, key, () =>
130
+ loadHasManyRelation(ctx, table, key, relation, options)
131
+ );
132
+ break;
133
+ case RelationKinds.BelongsTo:
134
+ await relationLoaderCache(meta, key, () =>
135
+ loadBelongsToRelation(ctx, table, key, relation, options)
136
+ );
137
+ break;
138
+ case RelationKinds.BelongsToMany:
139
+ await relationLoaderCache(meta, key, () =>
140
+ loadBelongsToManyRelation(ctx, table, key, relation, options)
141
+ );
142
+ break;
143
+ }
144
+ }
145
+ };
@@ -6,11 +6,21 @@ import type { EntityContext } from './entity-context.js';
6
6
  import type { AnyDomainEvent, DomainEvent } from './runtime-types.js';
7
7
  import type { OrmSession } from './orm-session.js';
8
8
 
9
+ /**
10
+ * Context used during the hydration of entities from database results.
11
+ * It carries necessary services and processors to handle identity management,
12
+ * unit of work registration, and relation changes.
13
+ */
9
14
  export interface HydrationContext<E extends DomainEvent = AnyDomainEvent> {
15
+ /** The identity map used to track and reuse entity instances. */
10
16
  identityMap: IdentityMap;
17
+ /** The unit of work used to track changes in hydrated entities. */
11
18
  unitOfWork: UnitOfWork;
19
+ /** The bus used to dispatch domain events during or after hydration. */
12
20
  domainEvents: DomainEventBus<E, OrmSession<E>>;
21
+ /** Processor for handling changes in entity relations during hydration. */
13
22
  relationChanges: RelationChangeProcessor;
23
+ /** Context providing access to entity-specific metadata and services. */
14
24
  entityContext: EntityContext;
15
25
  // maybe mapping registry, converters, etc.
16
26
  }
@@ -1,6 +1,10 @@
1
1
  import type { TableDef } from '../schema/table.js';
2
2
  import type { TrackedEntity } from './runtime-types.js';
3
3
 
4
+ /**
5
+ * Simple identity map for tracking entities within a session.
6
+ * Ensures that the same database record is represented by a single entity instance.
7
+ */
4
8
  export class IdentityMap {
5
9
  private readonly buckets = new Map<string, Map<string, TrackedEntity>>();
6
10
 
@@ -8,11 +12,21 @@ export class IdentityMap {
8
12
  return this.buckets;
9
13
  }
10
14
 
15
+ /**
16
+ * Retrieves an entity from the identity map if it exists.
17
+ * @param table The table definition of the entity.
18
+ * @param pk The primary key value.
19
+ * @returns The entity instance if found, undefined otherwise.
20
+ */
11
21
  getEntity(table: TableDef, pk: string | number): unknown | undefined {
12
22
  const bucket = this.buckets.get(table.name);
13
23
  return bucket?.get(this.toIdentityKey(pk))?.entity;
14
24
  }
15
25
 
26
+ /**
27
+ * Registers a tracked entity in the identity map.
28
+ * @param tracked The tracked entity metadata and instance.
29
+ */
16
30
  register(tracked: TrackedEntity): void {
17
31
  if (tracked.pk == null) return;
18
32
  const bucket = this.buckets.get(tracked.table.name) ?? new Map<string, TrackedEntity>();
@@ -26,6 +40,11 @@ export class IdentityMap {
26
40
  bucket?.delete(this.toIdentityKey(tracked.pk));
27
41
  }
28
42
 
43
+ /**
44
+ * Returns all tracked entities for a specific table.
45
+ * @param table The table definition.
46
+ * @returns Array of tracked entities.
47
+ */
29
48
  getEntitiesForTable(table: TableDef): TrackedEntity[] {
30
49
  const bucket = this.buckets.get(table.name);
31
50
  return bucket ? Array.from(bucket.values()) : [];
@@ -8,6 +8,10 @@ export interface QueryContext {
8
8
 
9
9
  export type QueryInterceptor = (ctx: QueryContext, next: () => Promise<QueryResult[]>) => Promise<QueryResult[]>;
10
10
 
11
+ /**
12
+ * Pipeline for query interceptors.
13
+ * Interceptors can wrap query execution to add logging, tracing, caching, etc.
14
+ */
11
15
  export class InterceptorPipeline {
12
16
  private interceptors: QueryInterceptor[] = [];
13
17