turbine-orm 0.52.0 → 0.53.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.
@@ -37,7 +37,15 @@
37
37
  * These cannot be faithfully translated and are not attempted; each throws or is
38
38
  * documented rather than silently returning wrong data:
39
39
  *
40
- * - `$extends` / client extensions, `$use` with Prisma's middleware param shape.
40
+ * - **`$extends` beyond `client` + `model`.** Client extensions ARE supported for
41
+ * those two components (plus the `Prisma.defineExtension` callback form), and
42
+ * return a new client whose delegates, `$transaction` and raw surface all
43
+ * survive. The `query` (interception) and `result` (computed fields)
44
+ * components, and any component this adapter does not recognize, throw an
45
+ * {@link UnsupportedFeatureError} naming the component AT `$extends` TIME
46
+ * rather than being accepted and quietly not applied.
47
+ * - `$use` with Prisma's middleware param shape (Turbine's own `client.$use` is
48
+ * the supported interception seam).
41
49
  * - `instanceof PrismaClientKnownRequestError`, `.meta`/message byte parity
42
50
  * (opt into `prismaErrorCodes` for a `.code` like `P2002`, without pretending
43
51
  * `instanceof` identity).
@@ -149,6 +157,23 @@ export const Prisma = {
149
157
  },
150
158
  /** An empty fragment. */
151
159
  empty: makeSql([''], []),
160
+ /**
161
+ * The extension context of `this` inside a client / model extension method.
162
+ * Turbine binds extension members directly onto the client and delegate
163
+ * objects, so the context IS `this`; the identity function exists so migrated
164
+ * `Prisma.getExtensionContext(this).$name` call sites keep working.
165
+ */
166
+ getExtensionContext(that) {
167
+ return that;
168
+ },
169
+ /**
170
+ * Type-preserving passthrough for `Prisma.defineExtension(ext)`. Prisma uses
171
+ * it purely for inference; the value is returned unchanged, so both the object
172
+ * and the callback form reach `$extends` intact.
173
+ */
174
+ defineExtension(ext) {
175
+ return ext;
176
+ },
152
177
  };
153
178
  /** Turbine error code → nearest Prisma `PXXXX` code. */
154
179
  const PRISMA_ERROR_CODE = {
@@ -1361,6 +1386,7 @@ function makeRawSurface(exec, ph) {
1361
1386
  */
1362
1387
  const CLIENT_RESERVED_KEY_MAP = {
1363
1388
  $transaction: true,
1389
+ $extends: true,
1364
1390
  $queryRaw: true,
1365
1391
  $queryRawUnsafe: true,
1366
1392
  $executeRaw: true,
@@ -1433,6 +1459,109 @@ function junctionModels(ctx, map, tableToModel) {
1433
1459
  }
1434
1460
  return out;
1435
1461
  }
1462
+ // ---------------------------------------------------------------------------
1463
+ // $extends, client extensions
1464
+ // ---------------------------------------------------------------------------
1465
+ /** The `model` key whose members apply to every delegate. */
1466
+ const ALL_MODELS = '$allModels';
1467
+ /**
1468
+ * Why each unsupported extension component is refused, and what to do instead.
1469
+ * Both are refused AT `$extends` TIME, not at the first query, so the failure is
1470
+ * a full message at boot rather than a surprise mid-request.
1471
+ */
1472
+ const UNSUPPORTED_COMPONENTS = {
1473
+ query: {
1474
+ feature: '$extends `query` (query interception)',
1475
+ hint: [
1476
+ 'Use the core middleware seam instead: `client.$use((params, next) => ...)` sees every query, or',
1477
+ 'wrap the call site; `$allOperations` hooks have no equivalent.',
1478
+ 'The `client` and `model` components ARE supported.',
1479
+ ].join(' '),
1480
+ },
1481
+ result: {
1482
+ feature: '$extends `result` (computed fields)',
1483
+ hint: [
1484
+ 'Prisma implements it by rewriting the projection to satisfy `needs` and stripping the injected',
1485
+ 'columns back out at every nesting level, which cannot be done safely on top of the PII projection',
1486
+ 'rules (a `needs` field on a pii-tagged column would arrive undefined and the computed value would',
1487
+ 'be silently wrong). Compute the field in application code, or add a generated column so it comes',
1488
+ 'back as a real column. The `client` and `model` components ARE supported.',
1489
+ ].join(' '),
1490
+ },
1491
+ };
1492
+ const EMPTY_EXTENSIONS = { client: {}, model: new Map(), allModels: {} };
1493
+ /**
1494
+ * Validate one extension against the client's real shape and fold it into a NEW
1495
+ * {@link ExtensionState} (the previous one is never mutated, so the client
1496
+ * `$extends` was called on keeps working unchanged).
1497
+ *
1498
+ * Everything this adapter cannot honour throws here: an unsupported component
1499
+ * ({@link UNSUPPORTED_COMPONENTS}), a component name we do not recognize at all
1500
+ * (`@prisma/extension-accelerate`, Pulse, read replicas, ...), a `client` member
1501
+ * that would shadow a delegate or a client-level method, or a `model` key that
1502
+ * names no model in the map. Extension-over-extension overrides are allowed and
1503
+ * last-wins, as in Prisma.
1504
+ *
1505
+ * @param modelKeys - every accepted `model` key spelling -> canonical model name.
1506
+ * @param clientKeys - names already taken on the client (delegates + reserved).
1507
+ */
1508
+ function applyExtension(prev, ext, modelKeys, clientKeys) {
1509
+ if (typeof ext !== 'object' || ext === null) {
1510
+ throw new ValidationError('[turbine] prisma-compat: $extends expects an extension object or a function ' +
1511
+ `(Prisma.defineExtension callback form), received ${ext === null ? 'null' : typeof ext}.`);
1512
+ }
1513
+ for (const [component, value] of Object.entries(ext)) {
1514
+ if (component === 'name' || component === 'client' || component === 'model')
1515
+ continue;
1516
+ // An explicitly-undefined component asked for nothing (a spread of a partial
1517
+ // extension object), so there is nothing to refuse.
1518
+ if (value === undefined)
1519
+ continue;
1520
+ const known = UNSUPPORTED_COMPONENTS[component];
1521
+ throw new UnsupportedFeatureError(known?.feature ?? `$extends extension component "${component}"`, 'prisma-compat', known?.hint ??
1522
+ 'Only the `client` and `model` components are supported (Accelerate / Pulse / read-replica ' +
1523
+ 'extensions are not).');
1524
+ }
1525
+ const client = { ...prev.client };
1526
+ for (const [name, member] of Object.entries(ext.client ?? {})) {
1527
+ if (clientKeys.has(name)) {
1528
+ throw new ValidationError(`[turbine] prisma-compat: $extends \`client\` member "${name}" would shadow an existing client ` +
1529
+ 'member (a model delegate or a client-level method). Rename it.');
1530
+ }
1531
+ client[name] = member;
1532
+ }
1533
+ const model = new Map(prev.model);
1534
+ const allModels = { ...prev.allModels };
1535
+ for (const [key, members] of Object.entries(ext.model ?? {})) {
1536
+ if (key === ALL_MODELS) {
1537
+ Object.assign(allModels, members);
1538
+ continue;
1539
+ }
1540
+ const canonical = modelKeys.get(key);
1541
+ if (!canonical) {
1542
+ throw new ValidationError(`[turbine] prisma-compat: $extends \`model\` key "${key}" is not a model on this client. ` +
1543
+ `Known models: ${[...new Set(modelKeys.values())].sort().join(', ') || '(none)'}.`);
1544
+ }
1545
+ model.set(canonical, { ...(model.get(canonical) ?? {}), ...members });
1546
+ }
1547
+ return { client, model, allModels };
1548
+ }
1549
+ /**
1550
+ * Overlay an extension's members on one delegate. Returns the delegate itself
1551
+ * when the extension contributes nothing to it, so an unextended client and an
1552
+ * extended one that only adds `client` members share the exact same delegates.
1553
+ *
1554
+ * The members are copied onto a shallow copy, so `this` inside an extension
1555
+ * method is the extended delegate (what `Prisma.getExtensionContext(this)`
1556
+ * returns), and `$name` carries the Prisma model name as Prisma's model context
1557
+ * does.
1558
+ */
1559
+ function extendDelegate(delegate, prismaModel, exts) {
1560
+ const own = exts.model.get(prismaModel);
1561
+ if (!own && Object.keys(exts.allModels).length === 0)
1562
+ return delegate;
1563
+ return Object.assign({ $name: prismaModel }, delegate, exts.allModels, own ?? {});
1564
+ }
1436
1565
  /**
1437
1566
  * Create a PrismaClient-surface adapter over a {@link TurbineClient}, driven by a
1438
1567
  * {@link PrismaCompatMap} (the `prisma-map.ts` that `turbine
@@ -1476,10 +1605,23 @@ export function createPrismaCompatClient(client, map, options = {}) {
1476
1605
  ...Object.entries(map.models),
1477
1606
  ...junctionModels(ctx, map, tableToModel),
1478
1607
  ];
1608
+ // Every spelling a `$extends` `model` key may use -> the canonical model name.
1609
+ // Canonical names are registered first so a model can never lose its own key
1610
+ // to another model's lowercased alias.
1611
+ const modelKeys = new Map();
1612
+ for (const [prismaModel] of delegateModels)
1613
+ modelKeys.set(prismaModel, prismaModel);
1614
+ for (const [prismaModel] of delegateModels) {
1615
+ const alias = prismaPropertyAlias(prismaModel);
1616
+ if (alias && !modelKeys.has(alias))
1617
+ modelKeys.set(alias, prismaModel);
1618
+ }
1619
+ // Names a `$extends` `client` member must not shadow.
1620
+ const clientKeys = new Set([...CLIENT_RESERVED_KEYS, ...modelKeys.keys()]);
1479
1621
  // Delegates bound to the base client (each call reads db.table(...) lazily).
1480
- const delegates = new Map();
1622
+ const baseDelegates = new Map();
1481
1623
  for (const [prismaModel, mm] of delegateModels) {
1482
- delegates.set(prismaModel, makeDelegate(ctx, mm, () => db.table(mm.table), (fn) => db.$transaction((tx) => fn((n) => tx.table(n)))));
1624
+ baseDelegates.set(prismaModel, makeDelegate(ctx, mm, () => db.table(mm.table), (fn) => db.$transaction((tx) => fn((n) => tx.table(n)))));
1483
1625
  }
1484
1626
  const ph = placeholderOf(db);
1485
1627
  const runRaw = async (text, params) => {
@@ -1511,79 +1653,112 @@ export function createPrismaCompatClient(client, map, options = {}) {
1511
1653
  throw decorate(wrapPgError(err), ctx.options.prismaErrorCodes);
1512
1654
  }
1513
1655
  };
1514
- const base = {
1515
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1516
- $transaction: ((arg, txOptions) => {
1517
- // Array (lazy batch) form. Wrapped so validation/build errors REJECT the
1518
- // returned promise (Prisma's $transaction is always thenable) rather than
1519
- // throwing synchronously.
1520
- if (Array.isArray(arg)) {
1521
- return (async () => {
1522
- try {
1523
- const batchables = arg.map((p, i) => {
1524
- const b = batchableOf(p);
1525
- if (!b) {
1526
- throw new ValidationError(`[turbine] prisma-compat: $transaction([...]) item ${i} is not a lazy model call. Pass un-awaited delegate calls (e.g. prisma.User.create(...)).`);
1656
+ /**
1657
+ * Assemble one client for a set of already-validated extensions. `$extends`
1658
+ * calls this again with a folded {@link ExtensionState}, so an extended client
1659
+ * is a genuinely NEW object built by the SAME path: delegates (extended),
1660
+ * `$transaction` (whose tx-scoped delegates get the same `model` members, the
1661
+ * one place a naive implementation would silently diverge), the raw surface,
1662
+ * and `$extends` itself, which is why extending stays chainable.
1663
+ *
1664
+ * Client-level `client` members are deliberately NOT copied onto the
1665
+ * transaction client: such a member usually closes over the base client, so
1666
+ * reaching it through `tx` would silently run its queries OUTSIDE the
1667
+ * transaction. Absent, it is a TypeError at the call site instead.
1668
+ */
1669
+ const build = (exts) => {
1670
+ // Delegates bound to the base client, extended where the extension has
1671
+ // members for them. `baseDelegates` is built ONCE (outside), so a client
1672
+ // whose extension only adds `client` members shares the very same delegate
1673
+ // objects: extending costs nothing on the query path.
1674
+ const delegates = new Map();
1675
+ for (const [prismaModel, delegate] of baseDelegates) {
1676
+ delegates.set(prismaModel, extendDelegate(delegate, prismaModel, exts));
1677
+ }
1678
+ const base = {
1679
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1680
+ $transaction: ((arg, txOptions) => {
1681
+ // Array (lazy batch) form. Wrapped so validation/build errors REJECT the
1682
+ // returned promise (Prisma's $transaction is always thenable) rather than
1683
+ // throwing synchronously.
1684
+ if (Array.isArray(arg)) {
1685
+ return (async () => {
1686
+ try {
1687
+ const batchables = arg.map((p, i) => {
1688
+ const b = batchableOf(p);
1689
+ if (!b) {
1690
+ throw new ValidationError(`[turbine] prisma-compat: $transaction([...]) item ${i} is not a lazy model call. Pass un-awaited delegate calls (e.g. prisma.User.create(...)).`);
1691
+ }
1692
+ return b;
1693
+ });
1694
+ // Nested write data (or a lookup-first upsert) cannot run as a
1695
+ // single deferred statement. Prisma's array form still supports
1696
+ // those, so fall back to running the WHOLE array sequentially
1697
+ // inside one transaction; ordering and atomicity are preserved.
1698
+ if (batchables.some((b) => b.nested())) {
1699
+ return await db.$transaction(async (tx) => {
1700
+ const out = [];
1701
+ for (const b of batchables)
1702
+ out.push(await b.execInTx((n) => tx.table(n)));
1703
+ return out;
1704
+ }, txOptions);
1527
1705
  }
1528
- return b;
1529
- });
1530
- // Nested write data (or a lookup-first upsert) cannot run as a
1531
- // single deferred statement. Prisma's array form still supports
1532
- // those, so fall back to running the WHOLE array sequentially
1533
- // inside one transaction; ordering and atomicity are preserved.
1534
- if (batchables.some((b) => b.nested())) {
1535
- return await db.$transaction(async (tx) => {
1536
- const out = [];
1537
- for (const b of batchables)
1538
- out.push(await b.execInTx((n) => tx.table(n)));
1539
- return out;
1540
- }, txOptions);
1706
+ const deferreds = batchables.map((b) => b.build());
1707
+ const results = (await db.$transaction(deferreds));
1708
+ return results.map((raw, i) => batchables[i].reshape(raw));
1541
1709
  }
1542
- const deferreds = batchables.map((b) => b.build());
1543
- const results = (await db.$transaction(deferreds));
1544
- return results.map((raw, i) => batchables[i].reshape(raw));
1545
- }
1546
- catch (err) {
1547
- throw decorate(err, ctx.options.prismaErrorCodes);
1548
- }
1549
- })();
1550
- }
1551
- // Callback form: hand the user a compat client bound to the tx connection.
1552
- const fn = arg;
1553
- return db.$transaction((tx) => {
1554
- const txDelegates = {};
1555
- for (const [prismaModel, mm] of delegateModels) {
1556
- txDelegates[prismaModel] = makeDelegate(ctx, mm, () => tx.table(mm.table), (fn) => fn((n) => tx.table(n)));
1557
- const alias = prismaPropertyAlias(prismaModel);
1558
- if (alias && !(alias in map.models) && !(alias in txDelegates)) {
1559
- txDelegates[alias] = txDelegates[prismaModel];
1560
- }
1710
+ catch (err) {
1711
+ throw decorate(err, ctx.options.prismaErrorCodes);
1712
+ }
1713
+ })();
1561
1714
  }
1562
- // Raw SQL on the transaction's own connection. Prisma's tx client
1563
- // carries these four, and code that mixes `$transaction` with raw SQL is
1564
- // the common case in a migrated codebase. No model can shadow them: a
1565
- // Prisma model name cannot start with `$`, and junction accessors skip
1566
- // every CLIENT_RESERVED_KEYS name.
1567
- const txClient = { ...txDelegates, ...makeRawSurface(txRunRaw(tx), ph) };
1568
- return fn(txClient);
1569
- }, txOptions);
1570
- }),
1571
- ...makeRawSurface(runRaw, ph),
1572
- $connect: async () => { },
1573
- $disconnect: async () => { },
1715
+ // Callback form: hand the user a compat client bound to the tx connection.
1716
+ const fn = arg;
1717
+ return db.$transaction((tx) => {
1718
+ const txDelegates = {};
1719
+ for (const [prismaModel, mm] of delegateModels) {
1720
+ txDelegates[prismaModel] = extendDelegate(makeDelegate(ctx, mm, () => tx.table(mm.table), (fn) => fn((n) => tx.table(n))), prismaModel, exts);
1721
+ const alias = prismaPropertyAlias(prismaModel);
1722
+ if (alias && !(alias in map.models) && !(alias in txDelegates)) {
1723
+ txDelegates[alias] = txDelegates[prismaModel];
1724
+ }
1725
+ }
1726
+ // Raw SQL on the transaction's own connection. Prisma's tx client
1727
+ // carries these four, and code that mixes `$transaction` with raw SQL is
1728
+ // the common case in a migrated codebase. No model can shadow them: a
1729
+ // Prisma model name cannot start with `$`, and junction accessors skip
1730
+ // every CLIENT_RESERVED_KEYS name.
1731
+ const txClient = { ...txDelegates, ...makeRawSurface(txRunRaw(tx), ph) };
1732
+ return fn(txClient);
1733
+ }, txOptions);
1734
+ }),
1735
+ $extends: ((extension) => {
1736
+ // Prisma's callback form: `client.$extends(fn)` IS `fn(client)`.
1737
+ if (typeof extension === 'function')
1738
+ return extension(result);
1739
+ return build(applyExtension(exts, extension, modelKeys, clientKeys));
1740
+ }),
1741
+ ...makeRawSurface(runRaw, ph),
1742
+ $connect: async () => { },
1743
+ $disconnect: async () => { },
1744
+ };
1745
+ // Assemble the result: model delegates keyed by Prisma model name, plus the
1746
+ // client-level base methods. A plain object suffices, every model is a known
1747
+ // key from the map, so no dynamic-access proxy is needed.
1748
+ const result = { ...base };
1749
+ for (const [prismaModel, delegate] of delegates)
1750
+ result[prismaModel] = delegate;
1751
+ // Prisma-spelling aliases (`prisma.user` for `model User`). Skipped when the
1752
+ // lowercased name is itself a model or already taken (never shadow a real key).
1753
+ for (const [prismaModel, delegate] of delegates) {
1754
+ const alias = prismaPropertyAlias(prismaModel);
1755
+ if (alias && !(alias in result))
1756
+ result[alias] = delegate;
1757
+ }
1758
+ // Extension `client` members last: every name was checked against the real
1759
+ // client keys in applyExtension, so this can never overwrite a delegate.
1760
+ Object.assign(result, exts.client);
1761
+ return result;
1574
1762
  };
1575
- // Assemble the result: model delegates keyed by Prisma model name, plus the
1576
- // client-level base methods. A plain object suffices, every model is a known
1577
- // key from the map, so no dynamic-access proxy is needed.
1578
- const result = { ...base };
1579
- for (const [prismaModel, delegate] of delegates)
1580
- result[prismaModel] = delegate;
1581
- // Prisma-spelling aliases (`prisma.user` for `model User`). Skipped when the
1582
- // lowercased name is itself a model or already taken (never shadow a real key).
1583
- for (const [prismaModel, delegate] of delegates) {
1584
- const alias = prismaPropertyAlias(prismaModel);
1585
- if (alias && !(alias in result))
1586
- result[alias] = delegate;
1587
- }
1588
- return result;
1763
+ return build(EMPTY_EXTENSIONS);
1589
1764
  }
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import type { TableMetadata } from '../schema.js';
12
12
  import type { DeferredQuery } from './deferred.js';
13
- import type { AggregateArgs, AggregateResult, GroupByArgs, GroupByOrderBy, HavingClause, HavingFilter } from './types.js';
13
+ import type { AggregateArgs, AggregateResult, GroupByArgs, GroupByOrderBy, HavingClause } from './types.js';
14
14
  import type { BuilderCtx } from './where.js';
15
15
  /**
16
16
  * Enforce the PII contract on the aggregate surface. A PII-tagged
@@ -65,28 +65,52 @@ export declare function resolveJsonPathTarget(qi: BuilderCtx, context: string, f
65
65
  * JSON paths push their text[] param here, after the WHERE params.
66
66
  */
67
67
  export declare function buildDistinctOnSource<T extends object>(qi: BuilderCtx, distinctOn: NonNullable<GroupByArgs<T>['distinctOn']>, whereSql: string, params: unknown[]): string;
68
+ /**
69
+ * How one groupBy group key is addressed from a `having` SCALAR filter: a
70
+ * plain `by` column (compiled by the shared WHERE machinery, by field name) or
71
+ * a JSON-path group key (its SELECT/GROUP BY extract expression, re-emitted
72
+ * verbatim with its already-bound path placeholder).
73
+ */
74
+ export type HavingGroupKey = {
75
+ kind: 'column';
76
+ field: string;
77
+ } | {
78
+ kind: 'expr';
79
+ expr: string;
80
+ label: string;
81
+ };
68
82
  /**
69
83
  * Build the SQL fragments for a {@link HavingClause}.
70
84
  *
85
+ * A field entry carries an AGGREGATE filter (`{ _sum: { gt: 100 } }`), a
86
+ * SCALAR filter on the grouped value itself (`{ not: null }`, `{ in: [...] }`,
87
+ * or a bare value as equality shorthand), or both in one object (ANDed,
88
+ * scalar first). `AND` / `OR` / `NOT` combine predicates at any depth.
89
+ *
71
90
  * Each aggregate expression (`COUNT(*)`, `SUM("col")`, etc.) is constructed
72
91
  * from a **schema-validated, quoted** column identifier: `qi.toColumn()`
73
92
  * throws {@link ValidationError} for unknown fields and `qi.q()` quotes via
74
93
  * the dialect, so no unvalidated identifier ever reaches the SQL string. Every
75
94
  * comparison value is pushed onto the shared `params` array and referenced by
76
- * a `$N` placeholder via {@link buildHavingNumericClauses}, there is no string
77
- * interpolation of user values.
95
+ * a `$N` placeholder via {@link buildHavingNumericClauses} (aggregates) or the
96
+ * shared WHERE compiler (scalars), there is no string interpolation of user
97
+ * values.
78
98
  *
79
99
  * `jsonAggExprs` (from {@link buildGroupBy}) maps `alias:aggKey` to the
80
100
  * exact aggregate expression a JSON-path aggregate emitted in SELECT
81
101
  * (including its already-bound path placeholder), so HAVING on a JSON-path
82
102
  * aggregate alias reuses the same expression instead of resolving the alias
83
- * as a column.
103
+ * as a column. `groupKeys` is the resolved `by` key set (see
104
+ * {@link HavingGroupKey}): a scalar filter is legal ONLY on a group key,
105
+ * because a non-grouped column cannot be referenced in HAVING at all.
84
106
  */
85
- export declare function buildHavingClauses<T extends object>(qi: BuilderCtx, having: HavingClause<T>, params: unknown[], jsonAggExprs?: Map<string, string>): string[];
107
+ export declare function buildHavingClauses<T extends object>(qi: BuilderCtx, having: HavingClause<T>, params: unknown[], jsonAggExprs?: Map<string, string>, groupKeys?: Map<string, HavingGroupKey>): string[];
86
108
  /**
87
- * Convert a single having filter into one or more parameterized SQL
88
- * comparisons against the given aggregate expression. A bare number is
89
- * shorthand for equality. Unknown operator keys throw {@link ValidationError}.
109
+ * Convert a single having aggregate filter into one or more parameterized SQL
110
+ * comparisons against the given aggregate expression. A bare value is
111
+ * shorthand for equality. Operands are not numeric-only: `_min` / `_max`
112
+ * return a stored cell, so `MIN("title") > 'm'` is as valid as
113
+ * `SUM("views") > 10`. Unknown operator keys throw {@link ValidationError}.
90
114
  */
91
- export declare function buildHavingNumericClauses(qi: BuilderCtx, expr: string, filter: HavingFilter, params: unknown[]): string[];
115
+ export declare function buildHavingNumericClauses(qi: BuilderCtx, expr: string, filter: unknown, params: unknown[]): string[];
92
116
  export declare function buildAggregate<T extends object>(qi: BuilderCtx, args: AggregateArgs<T>): DeferredQuery<AggregateResult<T>>;