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.
- package/dist/cjs/client.js +152 -0
- package/dist/cjs/nested-write.js +51 -5
- package/dist/cjs/prisma-compat.d.ts +80 -2
- package/dist/cjs/prisma-compat.js +249 -74
- package/dist/cjs/query/aggregates.d.ts +33 -9
- package/dist/cjs/query/aggregates.js +180 -40
- package/dist/cjs/query/builder.d.ts +23 -2
- package/dist/cjs/query/builder.js +72 -45
- package/dist/cjs/query/types.d.ts +49 -24
- package/dist/cjs/query/utils.d.ts +30 -0
- package/dist/cjs/query/utils.js +35 -0
- package/dist/cjs/query/warn-registry.d.ts +7 -0
- package/dist/cjs/query/warn-registry.js +7 -0
- package/dist/cjs/query/writes.d.ts +5 -1
- package/dist/cjs/query/writes.js +31 -6
- package/dist/client.js +153 -1
- package/dist/nested-write.js +51 -5
- package/dist/prisma-compat.d.ts +80 -2
- package/dist/prisma-compat.js +249 -74
- package/dist/query/aggregates.d.ts +33 -9
- package/dist/query/aggregates.js +181 -41
- package/dist/query/builder.d.ts +23 -2
- package/dist/query/builder.js +74 -47
- package/dist/query/types.d.ts +49 -24
- package/dist/query/utils.d.ts +30 -0
- package/dist/query/utils.js +35 -1
- package/dist/query/warn-registry.d.ts +7 -0
- package/dist/query/warn-registry.js +7 -0
- package/dist/query/writes.d.ts +5 -1
- package/dist/query/writes.js +32 -7
- package/package.json +1 -1
|
@@ -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
|
-
* -
|
|
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
|
|
1626
|
+
const baseDelegates = new Map();
|
|
1485
1627
|
for (const [prismaModel, mm] of delegateModels) {
|
|
1486
|
-
|
|
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
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
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
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
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
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
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
|
-
//
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
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
|
-
|
|
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
|
|
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}
|
|
77
|
-
* interpolation of user
|
|
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
|
|
89
|
-
* shorthand for equality.
|
|
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:
|
|
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>>;
|