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.
@@ -38,7 +38,15 @@
38
38
  * These cannot be faithfully translated and are not attempted; each throws or is
39
39
  * documented rather than silently returning wrong data:
40
40
  *
41
- * - `$extends` / client extensions, `$use` with Prisma's middleware param shape.
41
+ * - **`$extends` beyond `client` + `model`.** Client extensions ARE supported for
42
+ * those two components (plus the `Prisma.defineExtension` callback form), and
43
+ * return a new client whose delegates, `$transaction` and raw surface all
44
+ * survive. The `query` (interception) and `result` (computed fields)
45
+ * components, and any component this adapter does not recognize, throw an
46
+ * {@link UnsupportedFeatureError} naming the component AT `$extends` TIME
47
+ * rather than being accepted and quietly not applied.
48
+ * - `$use` with Prisma's middleware param shape (Turbine's own `client.$use` is
49
+ * the supported interception seam).
42
50
  * - `instanceof PrismaClientKnownRequestError`, `.meta`/message byte parity
43
51
  * (opt into `prismaErrorCodes` for a `.code` like `P2002`, without pretending
44
52
  * `instanceof` identity).
@@ -153,6 +161,23 @@ exports.Prisma = {
153
161
  },
154
162
  /** An empty fragment. */
155
163
  empty: makeSql([''], []),
164
+ /**
165
+ * The extension context of `this` inside a client / model extension method.
166
+ * Turbine binds extension members directly onto the client and delegate
167
+ * objects, so the context IS `this`; the identity function exists so migrated
168
+ * `Prisma.getExtensionContext(this).$name` call sites keep working.
169
+ */
170
+ getExtensionContext(that) {
171
+ return that;
172
+ },
173
+ /**
174
+ * Type-preserving passthrough for `Prisma.defineExtension(ext)`. Prisma uses
175
+ * it purely for inference; the value is returned unchanged, so both the object
176
+ * and the callback form reach `$extends` intact.
177
+ */
178
+ defineExtension(ext) {
179
+ return ext;
180
+ },
156
181
  };
157
182
  /** Turbine error code → nearest Prisma `PXXXX` code. */
158
183
  const PRISMA_ERROR_CODE = {
@@ -1365,6 +1390,7 @@ function makeRawSurface(exec, ph) {
1365
1390
  */
1366
1391
  const CLIENT_RESERVED_KEY_MAP = {
1367
1392
  $transaction: true,
1393
+ $extends: true,
1368
1394
  $queryRaw: true,
1369
1395
  $queryRawUnsafe: true,
1370
1396
  $executeRaw: true,
@@ -1437,6 +1463,109 @@ function junctionModels(ctx, map, tableToModel) {
1437
1463
  }
1438
1464
  return out;
1439
1465
  }
1466
+ // ---------------------------------------------------------------------------
1467
+ // $extends, client extensions
1468
+ // ---------------------------------------------------------------------------
1469
+ /** The `model` key whose members apply to every delegate. */
1470
+ const ALL_MODELS = '$allModels';
1471
+ /**
1472
+ * Why each unsupported extension component is refused, and what to do instead.
1473
+ * Both are refused AT `$extends` TIME, not at the first query, so the failure is
1474
+ * a full message at boot rather than a surprise mid-request.
1475
+ */
1476
+ const UNSUPPORTED_COMPONENTS = {
1477
+ query: {
1478
+ feature: '$extends `query` (query interception)',
1479
+ hint: [
1480
+ 'Use the core middleware seam instead: `client.$use((params, next) => ...)` sees every query, or',
1481
+ 'wrap the call site; `$allOperations` hooks have no equivalent.',
1482
+ 'The `client` and `model` components ARE supported.',
1483
+ ].join(' '),
1484
+ },
1485
+ result: {
1486
+ feature: '$extends `result` (computed fields)',
1487
+ hint: [
1488
+ 'Prisma implements it by rewriting the projection to satisfy `needs` and stripping the injected',
1489
+ 'columns back out at every nesting level, which cannot be done safely on top of the PII projection',
1490
+ 'rules (a `needs` field on a pii-tagged column would arrive undefined and the computed value would',
1491
+ 'be silently wrong). Compute the field in application code, or add a generated column so it comes',
1492
+ 'back as a real column. The `client` and `model` components ARE supported.',
1493
+ ].join(' '),
1494
+ },
1495
+ };
1496
+ const EMPTY_EXTENSIONS = { client: {}, model: new Map(), allModels: {} };
1497
+ /**
1498
+ * Validate one extension against the client's real shape and fold it into a NEW
1499
+ * {@link ExtensionState} (the previous one is never mutated, so the client
1500
+ * `$extends` was called on keeps working unchanged).
1501
+ *
1502
+ * Everything this adapter cannot honour throws here: an unsupported component
1503
+ * ({@link UNSUPPORTED_COMPONENTS}), a component name we do not recognize at all
1504
+ * (`@prisma/extension-accelerate`, Pulse, read replicas, ...), a `client` member
1505
+ * that would shadow a delegate or a client-level method, or a `model` key that
1506
+ * names no model in the map. Extension-over-extension overrides are allowed and
1507
+ * last-wins, as in Prisma.
1508
+ *
1509
+ * @param modelKeys - every accepted `model` key spelling -> canonical model name.
1510
+ * @param clientKeys - names already taken on the client (delegates + reserved).
1511
+ */
1512
+ function applyExtension(prev, ext, modelKeys, clientKeys) {
1513
+ if (typeof ext !== 'object' || ext === null) {
1514
+ throw new errors_js_1.ValidationError('[turbine] prisma-compat: $extends expects an extension object or a function ' +
1515
+ `(Prisma.defineExtension callback form), received ${ext === null ? 'null' : typeof ext}.`);
1516
+ }
1517
+ for (const [component, value] of Object.entries(ext)) {
1518
+ if (component === 'name' || component === 'client' || component === 'model')
1519
+ continue;
1520
+ // An explicitly-undefined component asked for nothing (a spread of a partial
1521
+ // extension object), so there is nothing to refuse.
1522
+ if (value === undefined)
1523
+ continue;
1524
+ const known = UNSUPPORTED_COMPONENTS[component];
1525
+ throw new errors_js_1.UnsupportedFeatureError(known?.feature ?? `$extends extension component "${component}"`, 'prisma-compat', known?.hint ??
1526
+ 'Only the `client` and `model` components are supported (Accelerate / Pulse / read-replica ' +
1527
+ 'extensions are not).');
1528
+ }
1529
+ const client = { ...prev.client };
1530
+ for (const [name, member] of Object.entries(ext.client ?? {})) {
1531
+ if (clientKeys.has(name)) {
1532
+ throw new errors_js_1.ValidationError(`[turbine] prisma-compat: $extends \`client\` member "${name}" would shadow an existing client ` +
1533
+ 'member (a model delegate or a client-level method). Rename it.');
1534
+ }
1535
+ client[name] = member;
1536
+ }
1537
+ const model = new Map(prev.model);
1538
+ const allModels = { ...prev.allModels };
1539
+ for (const [key, members] of Object.entries(ext.model ?? {})) {
1540
+ if (key === ALL_MODELS) {
1541
+ Object.assign(allModels, members);
1542
+ continue;
1543
+ }
1544
+ const canonical = modelKeys.get(key);
1545
+ if (!canonical) {
1546
+ throw new errors_js_1.ValidationError(`[turbine] prisma-compat: $extends \`model\` key "${key}" is not a model on this client. ` +
1547
+ `Known models: ${[...new Set(modelKeys.values())].sort().join(', ') || '(none)'}.`);
1548
+ }
1549
+ model.set(canonical, { ...(model.get(canonical) ?? {}), ...members });
1550
+ }
1551
+ return { client, model, allModels };
1552
+ }
1553
+ /**
1554
+ * Overlay an extension's members on one delegate. Returns the delegate itself
1555
+ * when the extension contributes nothing to it, so an unextended client and an
1556
+ * extended one that only adds `client` members share the exact same delegates.
1557
+ *
1558
+ * The members are copied onto a shallow copy, so `this` inside an extension
1559
+ * method is the extended delegate (what `Prisma.getExtensionContext(this)`
1560
+ * returns), and `$name` carries the Prisma model name as Prisma's model context
1561
+ * does.
1562
+ */
1563
+ function extendDelegate(delegate, prismaModel, exts) {
1564
+ const own = exts.model.get(prismaModel);
1565
+ if (!own && Object.keys(exts.allModels).length === 0)
1566
+ return delegate;
1567
+ return Object.assign({ $name: prismaModel }, delegate, exts.allModels, own ?? {});
1568
+ }
1440
1569
  /**
1441
1570
  * Create a PrismaClient-surface adapter over a {@link TurbineClient}, driven by a
1442
1571
  * {@link PrismaCompatMap} (the `prisma-map.ts` that `turbine
@@ -1480,10 +1609,23 @@ function createPrismaCompatClient(client, map, options = {}) {
1480
1609
  ...Object.entries(map.models),
1481
1610
  ...junctionModels(ctx, map, tableToModel),
1482
1611
  ];
1612
+ // Every spelling a `$extends` `model` key may use -> the canonical model name.
1613
+ // Canonical names are registered first so a model can never lose its own key
1614
+ // to another model's lowercased alias.
1615
+ const modelKeys = new Map();
1616
+ for (const [prismaModel] of delegateModels)
1617
+ modelKeys.set(prismaModel, prismaModel);
1618
+ for (const [prismaModel] of delegateModels) {
1619
+ const alias = prismaPropertyAlias(prismaModel);
1620
+ if (alias && !modelKeys.has(alias))
1621
+ modelKeys.set(alias, prismaModel);
1622
+ }
1623
+ // Names a `$extends` `client` member must not shadow.
1624
+ const clientKeys = new Set([...exports.CLIENT_RESERVED_KEYS, ...modelKeys.keys()]);
1483
1625
  // Delegates bound to the base client (each call reads db.table(...) lazily).
1484
- const delegates = new Map();
1626
+ const baseDelegates = new Map();
1485
1627
  for (const [prismaModel, mm] of delegateModels) {
1486
- delegates.set(prismaModel, makeDelegate(ctx, mm, () => db.table(mm.table), (fn) => db.$transaction((tx) => fn((n) => tx.table(n)))));
1628
+ baseDelegates.set(prismaModel, makeDelegate(ctx, mm, () => db.table(mm.table), (fn) => db.$transaction((tx) => fn((n) => tx.table(n)))));
1487
1629
  }
1488
1630
  const ph = placeholderOf(db);
1489
1631
  const runRaw = async (text, params) => {
@@ -1515,79 +1657,112 @@ function createPrismaCompatClient(client, map, options = {}) {
1515
1657
  throw decorate((0, errors_js_1.wrapPgError)(err), ctx.options.prismaErrorCodes);
1516
1658
  }
1517
1659
  };
1518
- const base = {
1519
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1520
- $transaction: ((arg, txOptions) => {
1521
- // Array (lazy batch) form. Wrapped so validation/build errors REJECT the
1522
- // returned promise (Prisma's $transaction is always thenable) rather than
1523
- // throwing synchronously.
1524
- if (Array.isArray(arg)) {
1525
- return (async () => {
1526
- try {
1527
- const batchables = arg.map((p, i) => {
1528
- const b = batchableOf(p);
1529
- if (!b) {
1530
- throw new errors_js_1.ValidationError(`[turbine] prisma-compat: $transaction([...]) item ${i} is not a lazy model call. Pass un-awaited delegate calls (e.g. prisma.User.create(...)).`);
1660
+ /**
1661
+ * Assemble one client for a set of already-validated extensions. `$extends`
1662
+ * calls this again with a folded {@link ExtensionState}, so an extended client
1663
+ * is a genuinely NEW object built by the SAME path: delegates (extended),
1664
+ * `$transaction` (whose tx-scoped delegates get the same `model` members, the
1665
+ * one place a naive implementation would silently diverge), the raw surface,
1666
+ * and `$extends` itself, which is why extending stays chainable.
1667
+ *
1668
+ * Client-level `client` members are deliberately NOT copied onto the
1669
+ * transaction client: such a member usually closes over the base client, so
1670
+ * reaching it through `tx` would silently run its queries OUTSIDE the
1671
+ * transaction. Absent, it is a TypeError at the call site instead.
1672
+ */
1673
+ const build = (exts) => {
1674
+ // Delegates bound to the base client, extended where the extension has
1675
+ // members for them. `baseDelegates` is built ONCE (outside), so a client
1676
+ // whose extension only adds `client` members shares the very same delegate
1677
+ // objects: extending costs nothing on the query path.
1678
+ const delegates = new Map();
1679
+ for (const [prismaModel, delegate] of baseDelegates) {
1680
+ delegates.set(prismaModel, extendDelegate(delegate, prismaModel, exts));
1681
+ }
1682
+ const base = {
1683
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1684
+ $transaction: ((arg, txOptions) => {
1685
+ // Array (lazy batch) form. Wrapped so validation/build errors REJECT the
1686
+ // returned promise (Prisma's $transaction is always thenable) rather than
1687
+ // throwing synchronously.
1688
+ if (Array.isArray(arg)) {
1689
+ return (async () => {
1690
+ try {
1691
+ const batchables = arg.map((p, i) => {
1692
+ const b = batchableOf(p);
1693
+ if (!b) {
1694
+ throw new errors_js_1.ValidationError(`[turbine] prisma-compat: $transaction([...]) item ${i} is not a lazy model call. Pass un-awaited delegate calls (e.g. prisma.User.create(...)).`);
1695
+ }
1696
+ return b;
1697
+ });
1698
+ // Nested write data (or a lookup-first upsert) cannot run as a
1699
+ // single deferred statement. Prisma's array form still supports
1700
+ // those, so fall back to running the WHOLE array sequentially
1701
+ // inside one transaction; ordering and atomicity are preserved.
1702
+ if (batchables.some((b) => b.nested())) {
1703
+ return await db.$transaction(async (tx) => {
1704
+ const out = [];
1705
+ for (const b of batchables)
1706
+ out.push(await b.execInTx((n) => tx.table(n)));
1707
+ return out;
1708
+ }, txOptions);
1531
1709
  }
1532
- return b;
1533
- });
1534
- // Nested write data (or a lookup-first upsert) cannot run as a
1535
- // single deferred statement. Prisma's array form still supports
1536
- // those, so fall back to running the WHOLE array sequentially
1537
- // inside one transaction; ordering and atomicity are preserved.
1538
- if (batchables.some((b) => b.nested())) {
1539
- return await db.$transaction(async (tx) => {
1540
- const out = [];
1541
- for (const b of batchables)
1542
- out.push(await b.execInTx((n) => tx.table(n)));
1543
- return out;
1544
- }, txOptions);
1710
+ const deferreds = batchables.map((b) => b.build());
1711
+ const results = (await db.$transaction(deferreds));
1712
+ return results.map((raw, i) => batchables[i].reshape(raw));
1545
1713
  }
1546
- const deferreds = batchables.map((b) => b.build());
1547
- const results = (await db.$transaction(deferreds));
1548
- return results.map((raw, i) => batchables[i].reshape(raw));
1549
- }
1550
- catch (err) {
1551
- throw decorate(err, ctx.options.prismaErrorCodes);
1552
- }
1553
- })();
1554
- }
1555
- // Callback form: hand the user a compat client bound to the tx connection.
1556
- const fn = arg;
1557
- return db.$transaction((tx) => {
1558
- const txDelegates = {};
1559
- for (const [prismaModel, mm] of delegateModels) {
1560
- txDelegates[prismaModel] = makeDelegate(ctx, mm, () => tx.table(mm.table), (fn) => fn((n) => tx.table(n)));
1561
- const alias = prismaPropertyAlias(prismaModel);
1562
- if (alias && !(alias in map.models) && !(alias in txDelegates)) {
1563
- txDelegates[alias] = txDelegates[prismaModel];
1564
- }
1714
+ catch (err) {
1715
+ throw decorate(err, ctx.options.prismaErrorCodes);
1716
+ }
1717
+ })();
1565
1718
  }
1566
- // Raw SQL on the transaction's own connection. Prisma's tx client
1567
- // carries these four, and code that mixes `$transaction` with raw SQL is
1568
- // the common case in a migrated codebase. No model can shadow them: a
1569
- // Prisma model name cannot start with `$`, and junction accessors skip
1570
- // every CLIENT_RESERVED_KEYS name.
1571
- const txClient = { ...txDelegates, ...makeRawSurface(txRunRaw(tx), ph) };
1572
- return fn(txClient);
1573
- }, txOptions);
1574
- }),
1575
- ...makeRawSurface(runRaw, ph),
1576
- $connect: async () => { },
1577
- $disconnect: async () => { },
1719
+ // Callback form: hand the user a compat client bound to the tx connection.
1720
+ const fn = arg;
1721
+ return db.$transaction((tx) => {
1722
+ const txDelegates = {};
1723
+ for (const [prismaModel, mm] of delegateModels) {
1724
+ txDelegates[prismaModel] = extendDelegate(makeDelegate(ctx, mm, () => tx.table(mm.table), (fn) => fn((n) => tx.table(n))), prismaModel, exts);
1725
+ const alias = prismaPropertyAlias(prismaModel);
1726
+ if (alias && !(alias in map.models) && !(alias in txDelegates)) {
1727
+ txDelegates[alias] = txDelegates[prismaModel];
1728
+ }
1729
+ }
1730
+ // Raw SQL on the transaction's own connection. Prisma's tx client
1731
+ // carries these four, and code that mixes `$transaction` with raw SQL is
1732
+ // the common case in a migrated codebase. No model can shadow them: a
1733
+ // Prisma model name cannot start with `$`, and junction accessors skip
1734
+ // every CLIENT_RESERVED_KEYS name.
1735
+ const txClient = { ...txDelegates, ...makeRawSurface(txRunRaw(tx), ph) };
1736
+ return fn(txClient);
1737
+ }, txOptions);
1738
+ }),
1739
+ $extends: ((extension) => {
1740
+ // Prisma's callback form: `client.$extends(fn)` IS `fn(client)`.
1741
+ if (typeof extension === 'function')
1742
+ return extension(result);
1743
+ return build(applyExtension(exts, extension, modelKeys, clientKeys));
1744
+ }),
1745
+ ...makeRawSurface(runRaw, ph),
1746
+ $connect: async () => { },
1747
+ $disconnect: async () => { },
1748
+ };
1749
+ // Assemble the result: model delegates keyed by Prisma model name, plus the
1750
+ // client-level base methods. A plain object suffices, every model is a known
1751
+ // key from the map, so no dynamic-access proxy is needed.
1752
+ const result = { ...base };
1753
+ for (const [prismaModel, delegate] of delegates)
1754
+ result[prismaModel] = delegate;
1755
+ // Prisma-spelling aliases (`prisma.user` for `model User`). Skipped when the
1756
+ // lowercased name is itself a model or already taken (never shadow a real key).
1757
+ for (const [prismaModel, delegate] of delegates) {
1758
+ const alias = prismaPropertyAlias(prismaModel);
1759
+ if (alias && !(alias in result))
1760
+ result[alias] = delegate;
1761
+ }
1762
+ // Extension `client` members last: every name was checked against the real
1763
+ // client keys in applyExtension, so this can never overwrite a delegate.
1764
+ Object.assign(result, exts.client);
1765
+ return result;
1578
1766
  };
1579
- // Assemble the result: model delegates keyed by Prisma model name, plus the
1580
- // client-level base methods. A plain object suffices, every model is a known
1581
- // key from the map, so no dynamic-access proxy is needed.
1582
- const result = { ...base };
1583
- for (const [prismaModel, delegate] of delegates)
1584
- result[prismaModel] = delegate;
1585
- // Prisma-spelling aliases (`prisma.user` for `model User`). Skipped when the
1586
- // lowercased name is itself a model or already taken (never shadow a real key).
1587
- for (const [prismaModel, delegate] of delegates) {
1588
- const alias = prismaPropertyAlias(prismaModel);
1589
- if (alias && !(alias in result))
1590
- result[alias] = delegate;
1591
- }
1592
- return result;
1767
+ return build(EMPTY_EXTENSIONS);
1593
1768
  }
@@ -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>>;