@vibeorm/runtime 1.3.0 → 2.0.0-alpha.10
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/README.md +50 -107
- package/dist/adapter.d.ts +250 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/bulk-upsert.d.ts +282 -0
- package/dist/bulk-upsert.d.ts.map +1 -0
- package/dist/client.d.ts +200 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/codecs.d.ts +170 -0
- package/dist/codecs.d.ts.map +1 -0
- package/dist/computed.d.ts +43 -0
- package/dist/computed.d.ts.map +1 -0
- package/dist/db-now.d.ts +41 -0
- package/dist/db-now.d.ts.map +1 -0
- package/dist/diagnostics/index.d.ts +12 -0
- package/dist/diagnostics/index.d.ts.map +1 -0
- package/dist/diagnostics/insight.d.ts +63 -0
- package/dist/diagnostics/insight.d.ts.map +1 -0
- package/dist/diagnostics/plan.d.ts +88 -0
- package/dist/diagnostics/plan.d.ts.map +1 -0
- package/dist/diagnostics/preview.d.ts +43 -0
- package/dist/diagnostics/preview.d.ts.map +1 -0
- package/dist/diagnostics/types.d.ts +223 -0
- package/dist/diagnostics/types.d.ts.map +1 -0
- package/dist/diagnostics/workload.d.ts +32 -0
- package/dist/diagnostics/workload.d.ts.map +1 -0
- package/dist/extensions.d.ts +102 -0
- package/dist/extensions.d.ts.map +1 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13070 -0
- package/dist/index.js.map +43 -0
- package/dist/keyset-iterator.d.ts +73 -0
- package/dist/keyset-iterator.d.ts.map +1 -0
- package/dist/keyset.d.ts +121 -0
- package/dist/keyset.d.ts.map +1 -0
- package/dist/model-meta.d.ts +200 -0
- package/dist/model-meta.d.ts.map +1 -0
- package/dist/nested-writes.d.ts +67 -0
- package/dist/nested-writes.d.ts.map +1 -0
- package/dist/policy-operation.d.ts +14 -0
- package/dist/policy-operation.d.ts.map +1 -0
- package/dist/policy.d.ts +17 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/query-builder.d.ts +271 -0
- package/dist/query-builder.d.ts.map +1 -0
- package/dist/relation-key.d.ts +23 -0
- package/dist/relation-key.d.ts.map +1 -0
- package/dist/relation-loader.d.ts +46 -0
- package/dist/relation-loader.d.ts.map +1 -0
- package/dist/relation-plan.d.ts +141 -0
- package/dist/relation-plan.d.ts.map +1 -0
- package/dist/render-cache.d.ts +48 -0
- package/dist/render-cache.d.ts.map +1 -0
- package/dist/rls-context.d.ts +14 -0
- package/dist/rls-context.d.ts.map +1 -0
- package/dist/rls-readiness.d.ts +114 -0
- package/dist/rls-readiness.d.ts.map +1 -0
- package/dist/scoped.d.ts +104 -0
- package/dist/scoped.d.ts.map +1 -0
- package/dist/strict-args.d.ts +47 -0
- package/dist/strict-args.d.ts.map +1 -0
- package/dist/telemetry/collector.d.ts +53 -0
- package/dist/telemetry/collector.d.ts.map +1 -0
- package/dist/telemetry/config.d.ts +53 -0
- package/dist/telemetry/config.d.ts.map +1 -0
- package/dist/telemetry/fingerprint.d.ts +38 -0
- package/dist/telemetry/fingerprint.d.ts.map +1 -0
- package/dist/telemetry/index.d.ts +18 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/recorder.d.ts +93 -0
- package/dist/telemetry/recorder.d.ts.map +1 -0
- package/dist/telemetry/statement.d.ts +53 -0
- package/dist/telemetry/statement.d.ts.map +1 -0
- package/dist/telemetry/types.d.ts +265 -0
- package/dist/telemetry/types.d.ts.map +1 -0
- package/dist/validators.d.ts +61 -0
- package/dist/validators.d.ts.map +1 -0
- package/dist/views.d.ts +97 -0
- package/dist/views.d.ts.map +1 -0
- package/dist/write-scope.d.ts +14 -0
- package/dist/write-scope.d.ts.map +1 -0
- package/package.json +33 -26
- package/src/adapter.ts +0 -146
- package/src/client.ts +0 -2172
- package/src/coerce.ts +0 -184
- package/src/count-loader.ts +0 -152
- package/src/errors.ts +0 -492
- package/src/id-generators.ts +0 -151
- package/src/index.ts +0 -55
- package/src/lateral-join-builder.ts +0 -1053
- package/src/query-builder.ts +0 -1832
- package/src/relation-loader.ts +0 -534
- package/src/retry.ts +0 -183
- package/src/types.ts +0 -317
- package/src/view.ts +0 -629
- package/src/where-builder.ts +0 -772
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EPIC 10 — batched upsert for ingestion (`upsertMany`).
|
|
3
|
+
*
|
|
4
|
+
* The whole logical input is validated HERE, before the query builder assembles
|
|
5
|
+
* a single statement, so a malformed feed never reaches the database as a
|
|
6
|
+
* partial write. Everything in this module is a pure function over the runtime
|
|
7
|
+
* metadata and the already-compiled rows: the rows themselves are compiled by
|
|
8
|
+
* the query builder's existing `compileCreateRows`, so validators, string-enum
|
|
9
|
+
* membership checks, app-side defaults and `@updatedAt` stamping all come from
|
|
10
|
+
* the one write path (constitution rule 6 — no second coercion path).
|
|
11
|
+
*
|
|
12
|
+
* The two decisions this file exists to keep honest:
|
|
13
|
+
*
|
|
14
|
+
* 1. **Duplicate conflict keys are refused, not resolved — and "duplicate"
|
|
15
|
+
* means what the DATABASE means.** A batch is accepted only when its
|
|
16
|
+
* conflict keys are provably distinct under the database's own equality.
|
|
17
|
+
* Two routes get there, and {@link assertConflictKeysAreComparable} picks
|
|
18
|
+
* one per call:
|
|
19
|
+
*
|
|
20
|
+
* - **Route 1 — the ORM's own check, which spans parameter chunks.**
|
|
21
|
+
* {@link assertNoDuplicateConflictKeys} compares the encoded values of
|
|
22
|
+
* every row in the batch, in input order, before it is split into
|
|
23
|
+
* statements. That answer is the database's answer only where the
|
|
24
|
+
* column's equality IS byte equality, so route 1 is available only when
|
|
25
|
+
* {@link conflictKeyComparisonOf} says so for every target column: no
|
|
26
|
+
* folding collation, no folding scalar, and no `@db.*` native type
|
|
27
|
+
* outside the measured {@link BYTE_EXACT_NATIVE_TYPES}.
|
|
28
|
+
* - **Route 2 — the engine's own comparison, inside ONE statement.**
|
|
29
|
+
* Postgres refuses a doubled key with SQLSTATE 21000 under the real
|
|
30
|
+
* column, which catches every fold the ORM cannot see: a `char(n)` pad,
|
|
31
|
+
* a `uuid` case difference, `numeric` scale, `timestamp(0)` and `date`
|
|
32
|
+
* truncation, `real` narrowing, `citext`, a nondeterministic collation.
|
|
33
|
+
* It ends at the statement boundary. Rows in two statements are never
|
|
34
|
+
* compared, and the second statement silently updates what the first
|
|
35
|
+
* inserted.
|
|
36
|
+
*
|
|
37
|
+
* Neither route available means the batch is refused before a statement is
|
|
38
|
+
* built. Mysql's default `utf8mb4_0900_ai_ci` and a sqlite `COLLATE NOCASE`
|
|
39
|
+
* column both fold keys and both keep the LAST row without an error, so
|
|
40
|
+
* those calls now refuse instead of guessing.
|
|
41
|
+
*
|
|
42
|
+
* **The strength of that guarantee, stated exactly.** Route 2 is the
|
|
43
|
+
* database's own answer and carries no condition. Route 1 is only as true
|
|
44
|
+
* as the schema: it reads `@collation` and `@db.*` and never asks the live
|
|
45
|
+
* database what the column really is. Where the physical column disagrees
|
|
46
|
+
* with what the schema declares — a table or database default, an ALTER
|
|
47
|
+
* applied outside migrations, a restored dump, a column created before the
|
|
48
|
+
* annotation — route 1 can accept a batch the database then folds. Nothing
|
|
49
|
+
* in this module detects that, and nothing here claims to.
|
|
50
|
+
* 2. **The affected count is not the driver's number.** Mysql's ON DUPLICATE
|
|
51
|
+
* KEY convention counts an insert 1, a changed update 2 and an unchanged
|
|
52
|
+
* update 0 — measured 3 for a four-row batch on 8.4.11. That is not a row
|
|
53
|
+
* count and is never presented as one.
|
|
54
|
+
*/
|
|
55
|
+
import type { SqlDialect } from "@vibeorm/sql";
|
|
56
|
+
import type { FieldMeta, ModelMeta } from "./model-meta.ts";
|
|
57
|
+
/** What `upsertMany` resolves to. */
|
|
58
|
+
export type UpsertManyResult = {
|
|
59
|
+
/**
|
|
60
|
+
* Rows the database was asked to insert-or-update: one per input row, after
|
|
61
|
+
* duplicate-conflict-key validation. Every one of them was either inserted or
|
|
62
|
+
* updated — the statement carries no DO NOTHING arm and the whole operation
|
|
63
|
+
* is atomic. NOT a count of rows whose values changed, and it does not
|
|
64
|
+
* distinguish inserts from updates.
|
|
65
|
+
*/
|
|
66
|
+
readonly count: number;
|
|
67
|
+
/**
|
|
68
|
+
* The driver's raw affected-row number, and only where that number IS a row
|
|
69
|
+
* count ({@link affectedRowsAreRowCounts}). `null` on mysql.
|
|
70
|
+
*/
|
|
71
|
+
readonly affectedRows: number | null;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Does this dialect's affected-row number mean "rows written"?
|
|
75
|
+
*
|
|
76
|
+
* Postgres/pglite/sqlite: yes — a four-row `ON CONFLICT DO UPDATE` batch of
|
|
77
|
+
* 2 unchanged + 1 changed + 1 insert reports 4. Mysql: no — the same batch
|
|
78
|
+
* reports 3, because ON DUPLICATE KEY scores an insert 1, a changed update 2
|
|
79
|
+
* and an unchanged update 0. Both measured, see notes-epic10.md.
|
|
80
|
+
*/
|
|
81
|
+
export declare function affectedRowsAreRowCounts(params: {
|
|
82
|
+
dialect: SqlDialect;
|
|
83
|
+
}): boolean;
|
|
84
|
+
/** The arbiter an `upsertMany` call selected. */
|
|
85
|
+
export type UpsertConflictTarget = {
|
|
86
|
+
readonly kind: "columns";
|
|
87
|
+
readonly fields: readonly FieldMeta[];
|
|
88
|
+
} | {
|
|
89
|
+
readonly kind: "constraint";
|
|
90
|
+
readonly constraint: string;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Every distinct way this model can address one row, as a column-set signature.
|
|
94
|
+
* Used to check that a conflict target names a REAL unique group, and to decide
|
|
95
|
+
* whether mysql's untargetable ON DUPLICATE KEY could fire on something else.
|
|
96
|
+
*/
|
|
97
|
+
export declare function uniqueGroupSignatures(params: {
|
|
98
|
+
model: ModelMeta;
|
|
99
|
+
}): ReadonlySet<string>;
|
|
100
|
+
/**
|
|
101
|
+
* `conflictTarget` → the arbiter. A field-name array must name a DECLARED
|
|
102
|
+
* unique group: a target the database has no unique index for cannot arbitrate
|
|
103
|
+
* anything, and postgres would refuse it at execution time anyway ("there is no
|
|
104
|
+
* unique or exclusion constraint matching the ON CONFLICT specification") —
|
|
105
|
+
* refusing at build time is the same answer, earlier and typed.
|
|
106
|
+
*/
|
|
107
|
+
export declare function resolveUpsertConflictTarget(params: {
|
|
108
|
+
model: ModelMeta;
|
|
109
|
+
target: unknown;
|
|
110
|
+
}): UpsertConflictTarget;
|
|
111
|
+
/**
|
|
112
|
+
* Mysql's `ON DUPLICATE KEY UPDATE` fires on ANY unique index, so a selected
|
|
113
|
+
* conflict target is not enforceable there. With exactly one unique group there
|
|
114
|
+
* is nothing else it could fire on and the contract holds; with more, the call
|
|
115
|
+
* is refused unless the caller acknowledges the widening. This EPIC never
|
|
116
|
+
* claims mysql enforces the target.
|
|
117
|
+
*/
|
|
118
|
+
export declare function assertConflictTargetIsEnforceable(params: {
|
|
119
|
+
dialect: SqlDialect;
|
|
120
|
+
model: ModelMeta;
|
|
121
|
+
allowAnyUniqueConflict: boolean;
|
|
122
|
+
}): void;
|
|
123
|
+
/**
|
|
124
|
+
* `update` → the fields the conflict arm overwrites from the incoming row.
|
|
125
|
+
*
|
|
126
|
+
* Refused: a conflict-target column (the arbiter is not a mutable field) and
|
|
127
|
+
* the `@@policy` tenant field (tenant identity is not a mutable field). The
|
|
128
|
+
* list is explicit by design — a "everything else" default would rewrite
|
|
129
|
+
* columns a caller never mentioned.
|
|
130
|
+
*/
|
|
131
|
+
export declare function resolveUpsertUpdateFields(params: {
|
|
132
|
+
model: ModelMeta;
|
|
133
|
+
update: unknown;
|
|
134
|
+
targetColumns: ReadonlySet<string>;
|
|
135
|
+
skipUpdatedAt: boolean;
|
|
136
|
+
}): readonly FieldMeta[];
|
|
137
|
+
/**
|
|
138
|
+
* Every row must BIND every conflict-target column and every updated column.
|
|
139
|
+
*
|
|
140
|
+
* The target columns are the B1 rule batched: a row that does not bind the
|
|
141
|
+
* arbiter can never conflict on the row the caller meant. The update columns
|
|
142
|
+
* are the destructive case — `SET col = <incoming row>.col` on a row that
|
|
143
|
+
* omitted the column would write the column DEFAULT (or NULL) over a live
|
|
144
|
+
* value. Columns that are neither may still be omitted per row: they take the
|
|
145
|
+
* column default on insert and keep their stored value on conflict.
|
|
146
|
+
*/
|
|
147
|
+
export declare function assertUpsertRowsBindColumns(params: {
|
|
148
|
+
model: ModelMeta;
|
|
149
|
+
compiled: readonly Readonly<Record<string, unknown>>[];
|
|
150
|
+
targetColumns: readonly string[];
|
|
151
|
+
updateFields: readonly FieldMeta[];
|
|
152
|
+
}): void;
|
|
153
|
+
/**
|
|
154
|
+
* Refuse a batch that proposes the same conflict key twice, BYTE-wise.
|
|
155
|
+
*
|
|
156
|
+
* Not a `firstWins`/`lastWins` resolution, because the engines disagree about
|
|
157
|
+
* what such a batch even means (postgres: SQLSTATE 21000; mysql and sqlite:
|
|
158
|
+
* silent last-wins). Byte equality is a SUBSET of every collation's equality —
|
|
159
|
+
* no collation can call two identical byte strings different — so this check
|
|
160
|
+
* never refuses a batch the database would have accepted, and it covers the
|
|
161
|
+
* whole batch, parameter chunks included. Where it is not the WHOLE answer,
|
|
162
|
+
* {@link assertConflictKeysAreComparable} refuses rather than guessing.
|
|
163
|
+
*/
|
|
164
|
+
export declare function assertNoDuplicateConflictKeys(params: {
|
|
165
|
+
model: ModelMeta;
|
|
166
|
+
compiled: readonly Readonly<Record<string, unknown>>[];
|
|
167
|
+
targetColumns: readonly string[];
|
|
168
|
+
}): void;
|
|
169
|
+
/**
|
|
170
|
+
* Refuse a batch that doubles a unique group the arbiter does NOT name, on a
|
|
171
|
+
* dialect whose upsert has no arbiter at all.
|
|
172
|
+
*
|
|
173
|
+
* `ON DUPLICATE KEY UPDATE` resolves through whichever unique index the
|
|
174
|
+
* incoming row happens to match, so two rows carrying one `email` — distinct
|
|
175
|
+
* `id`s, an untouched conflict target — are not two rows: the second UPDATES
|
|
176
|
+
* the first and one of them silently disappears, with `ROW_COUNT()` reporting
|
|
177
|
+
* a number that hides it. Postgres and sqlite arbitrate on the named target
|
|
178
|
+
* only, so the second row violates the other index and the whole batch fails
|
|
179
|
+
* loudly; nothing is refused for them here.
|
|
180
|
+
*
|
|
181
|
+
* `allowAnyUniqueConflict` does NOT authorise this. That flag acknowledges
|
|
182
|
+
* that a SINGLE incoming row may be resolved through an index other than the
|
|
183
|
+
* one named — a widening of which stored row is replaced. It was never an
|
|
184
|
+
* acceptance of two submitted rows collapsing into one, which is a row the
|
|
185
|
+
* caller sent and the database does not have.
|
|
186
|
+
*
|
|
187
|
+
* Comparison is byte equality, deliberately: byte-identical values are equal
|
|
188
|
+
* under every collation and every native type, so this can only refuse a batch
|
|
189
|
+
* the database would itself have folded. Values a collation might fold while
|
|
190
|
+
* their bytes differ are the opaque case, and
|
|
191
|
+
* {@link assertConflictKeysAreComparable} already governs it.
|
|
192
|
+
*
|
|
193
|
+
* Rows that leave any of the group's columns unbound or NULL are skipped: an
|
|
194
|
+
* unbound column takes a default this layer cannot know, and mysql's unique
|
|
195
|
+
* indexes never collide on NULL.
|
|
196
|
+
*/
|
|
197
|
+
export declare function assertNoDuplicateSecondaryUniques(params: {
|
|
198
|
+
dialect: SqlDialect;
|
|
199
|
+
model: ModelMeta;
|
|
200
|
+
compiled: readonly Readonly<Record<string, unknown>>[];
|
|
201
|
+
targetColumns: readonly string[];
|
|
202
|
+
}): void;
|
|
203
|
+
/** How a single conflict-target column's equality can be established. */
|
|
204
|
+
export type ConflictKeyComparison =
|
|
205
|
+
/** The encoded JavaScript values compare exactly as the column does. */
|
|
206
|
+
"byte"
|
|
207
|
+
/** Only the database knows: a collation, a scale or a precision decides. */
|
|
208
|
+
| "database";
|
|
209
|
+
/**
|
|
210
|
+
* Which comparison decides whether two rows address the same stored row
|
|
211
|
+
* through this column.
|
|
212
|
+
*
|
|
213
|
+
* A declared `@db.*` native type is read FIRST and outranks everything the
|
|
214
|
+
* scalar implies, because the physical column pads, rounds, narrows or
|
|
215
|
+
* case-folds before a collation is ever consulted — and no annotation
|
|
216
|
+
* elsewhere can undo that. Only the measured types in
|
|
217
|
+
* {@link BYTE_EXACT_NATIVE_TYPES} pass; anything else is opaque, including a
|
|
218
|
+
* type this project has never run.
|
|
219
|
+
*
|
|
220
|
+
* Text (and enum members, which live in a text column on sqlite and mysql)
|
|
221
|
+
* then follows the column's collation: a declared `@collation` is believed on
|
|
222
|
+
* every dialect, because it is the schema's own statement about the column,
|
|
223
|
+
* and an undeclared one falls back to the dialect's default. Other scalars
|
|
224
|
+
* follow the dialect's folding list — `Decimal` is there on postgres and mysql
|
|
225
|
+
* because `numeric`/`DECIMAL` equality ignores trailing zeros while the
|
|
226
|
+
* encoded value is the string the caller wrote.
|
|
227
|
+
*
|
|
228
|
+
* Everything here reads the SCHEMA. It is a claim about what the column was
|
|
229
|
+
* declared to be, never a reading of what it is.
|
|
230
|
+
*/
|
|
231
|
+
export declare function conflictKeyComparisonOf(params: {
|
|
232
|
+
dialect: SqlDialect;
|
|
233
|
+
field: FieldMeta;
|
|
234
|
+
}): ConflictKeyComparison;
|
|
235
|
+
/**
|
|
236
|
+
* Refuse a batch whose conflict keys neither side can compare.
|
|
237
|
+
*
|
|
238
|
+
* Runs after {@link assertNoDuplicateConflictKeys} and before any statement is
|
|
239
|
+
* executed. Two things make a batch safe, and one of them has to hold:
|
|
240
|
+
*
|
|
241
|
+
* 1. **Every target column compares BYTES.** Then the whole-batch JavaScript
|
|
242
|
+
* check already saw every pair, across parameter chunks, and is the same
|
|
243
|
+
* answer the database would give.
|
|
244
|
+
* 2. **The engine compares the pairs itself, in ONE statement.** Postgres does
|
|
245
|
+
* (SQLSTATE 21000) and it does so under the real collation, scale and
|
|
246
|
+
* precision — including the ones the ORM cannot see, such as a `@db.Char`
|
|
247
|
+
* pad or a `uuid` column's case folding. A SECOND statement breaks it: rows
|
|
248
|
+
* in different statements are never compared, and the second one quietly
|
|
249
|
+
* updates what the first inserted.
|
|
250
|
+
*
|
|
251
|
+
* A named-constraint arbiter hides its column list, so route 1 is unavailable
|
|
252
|
+
* by construction and route 2 is the only one left.
|
|
253
|
+
*
|
|
254
|
+
* **What route 1 does NOT establish.** It reads the SCHEMA. `@collation` and
|
|
255
|
+
* `@db.*` are what the schema intends the column to be, and nothing here reads
|
|
256
|
+
* the live database to confirm either. A table or database default, an ALTER
|
|
257
|
+
* applied outside migrations, a restored dump, or a column created before the
|
|
258
|
+
* annotation was added all leave the physical column carrying something else.
|
|
259
|
+
* On mysql that gap is the normal case rather than the exception for a
|
|
260
|
+
* collation: the differ compares a column's type, nullability and default,
|
|
261
|
+
* never its collation, so adding `@collation` to an existing column generates
|
|
262
|
+
* no migration step at all. Where the schema and the column disagree, route
|
|
263
|
+
* 1's guarantee is the schema's, not the database's.
|
|
264
|
+
*
|
|
265
|
+
* **What route 1 now also refuses.** A conflict-target column carrying a
|
|
266
|
+
* `@db.*` native type is opaque unless that exact type was measured to leave
|
|
267
|
+
* equality alone ({@link BYTE_EXACT_NATIVE_TYPES}). This closes the case where
|
|
268
|
+
* a `@db.Char(10)` or `@db.Uuid` key classified as byte-comparable, took route
|
|
269
|
+
* 1, and let a chunked batch through: measured on postgres, `'pad'` and
|
|
270
|
+
* `'pad '` submitted as two statements against a `char(10)` key stored ONE row
|
|
271
|
+
* and kept the last, with no error, while the same pair inside one statement
|
|
272
|
+
* raised 21000.
|
|
273
|
+
*/
|
|
274
|
+
export declare function assertConflictKeysAreComparable(params: {
|
|
275
|
+
dialect: SqlDialect;
|
|
276
|
+
model: ModelMeta;
|
|
277
|
+
target: UpsertConflictTarget;
|
|
278
|
+
/** Statements this batch will run; more than one means uncompared pairs. */
|
|
279
|
+
statementCount: number;
|
|
280
|
+
compiled: readonly Readonly<Record<string, unknown>>[];
|
|
281
|
+
}): void;
|
|
282
|
+
//# sourceMappingURL=bulk-upsert.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bulk-upsert.d.ts","sourceRoot":"","sources":["../src/bulk-upsert.ts"],"names":[],"mappings":"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"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import type { SchemaIR } from "@vibeorm/schema";
|
|
2
|
+
import type { DatabaseAdapter, TransactionOptions } from "./adapter.ts";
|
|
3
|
+
import type { RelationStrategy } from "./query-builder.ts";
|
|
4
|
+
import type { ClientComputedOptions } from "./computed.ts";
|
|
5
|
+
import type { ClientExtensionsOptions } from "./extensions.ts";
|
|
6
|
+
import type { DefineViewFn } from "./views.ts";
|
|
7
|
+
import type { WriteValidators } from "./validators.ts";
|
|
8
|
+
import type { PolicyContextValue } from "./policy.ts";
|
|
9
|
+
import type { ScopedClientConfig } from "./scoped.ts";
|
|
10
|
+
import type { UpsertManyResult } from "./bulk-upsert.ts";
|
|
11
|
+
import type { TelemetryOptions } from "./telemetry/types.ts";
|
|
12
|
+
/** One executed statement, reported to `ClientOptions.onQuery`. */
|
|
13
|
+
export type QueryEvent = {
|
|
14
|
+
/** Model name, or `null` for raw queries. */
|
|
15
|
+
readonly model: string | null;
|
|
16
|
+
/** ORM method, or `"$queryRaw"` / `"$executeRaw"`. */
|
|
17
|
+
readonly method: string;
|
|
18
|
+
readonly text: string;
|
|
19
|
+
readonly values: readonly unknown[];
|
|
20
|
+
readonly durationMs: number;
|
|
21
|
+
};
|
|
22
|
+
export type ClientOptions = {
|
|
23
|
+
/** Trusted infrastructure configuration. PGlite requires a pre-existing restricted role;
|
|
24
|
+
* embedded role binding is not a security boundary against the host process.
|
|
25
|
+
* Desired policy deparse is probed once per backend generation (bounded cache);
|
|
26
|
+
* live expressions/privileges are still checked every transaction. Privileged
|
|
27
|
+
* dependency changes retaining identical deparse need a fresh client/doctor check. */
|
|
28
|
+
readonly nativeRls?: {
|
|
29
|
+
readonly runtimeRole?: string;
|
|
30
|
+
};
|
|
31
|
+
/** Called after every statement — logging, tracing, test assertions. */
|
|
32
|
+
readonly onQuery?: (event: QueryEvent) => void;
|
|
33
|
+
/**
|
|
34
|
+
* Default relation-loading strategy: `"query"` (batched WHERE-IN, portable,
|
|
35
|
+
* the default) or `"join"` (LATERAL + JSON aggregation, postgres/pglite
|
|
36
|
+
* only). A per-query `relationStrategy` argument overrides it.
|
|
37
|
+
*/
|
|
38
|
+
readonly relationStrategy?: RelationStrategy;
|
|
39
|
+
/**
|
|
40
|
+
* Append the primary key to ORDER BY when a find has no explicit orderBy
|
|
41
|
+
* (default false) — deterministic row order at the cost of a sort. v1 parity.
|
|
42
|
+
*/
|
|
43
|
+
readonly defaultOrderByPk?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Append the primary key as a tie-breaker to `distinct` ordering (default
|
|
46
|
+
* TRUE) — without it, WHICH row represents each distinct key is unspecified.
|
|
47
|
+
* v1 parity.
|
|
48
|
+
*/
|
|
49
|
+
readonly distinctOrderByPk?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Opt-in write-boundary validators (Standard Schema, per model × field):
|
|
52
|
+
* `{ User: { settings: zodSchema } }`. Run on every write path (nested
|
|
53
|
+
* creates included) before encoding; failure throws VIBE_VALIDATION with
|
|
54
|
+
* the validator's issues in `meta`. Reads are never taxed.
|
|
55
|
+
*/
|
|
56
|
+
readonly validate?: WriteValidators;
|
|
57
|
+
/**
|
|
58
|
+
* Opt-in operation tracing. One event per public ORM call, with the
|
|
59
|
+
* statements it ran; parameter values are redacted and raw SQL text is not
|
|
60
|
+
* captured unless separately asked for. The exporter is the host's — a throw
|
|
61
|
+
* from it can never change a database outcome, and `$disconnect()` flushes it
|
|
62
|
+
* but never shuts it down.
|
|
63
|
+
*/
|
|
64
|
+
readonly telemetry?: TelemetryOptions;
|
|
65
|
+
/**
|
|
66
|
+
* Strict argument handling (EPIC 2), **on by default**: an explicit
|
|
67
|
+
* `undefined` anywhere in ORM argument syntax is refused BEFORE a statement
|
|
68
|
+
* is built, instead of silently dropping the property (which widens a filter
|
|
69
|
+
* or loses a write). `SKIP` (`@vibeorm/runtime`) omits a property
|
|
70
|
+
* deliberately.
|
|
71
|
+
*
|
|
72
|
+
* Set `false` to opt OUT and restore legacy/Prisma semantics, where an
|
|
73
|
+
* explicit `undefined` member simply vanishes. Only an explicit `false` does
|
|
74
|
+
* that: an options object that does not mention this stays strict.
|
|
75
|
+
*/
|
|
76
|
+
readonly strictArguments?: boolean;
|
|
77
|
+
};
|
|
78
|
+
/** Rows are plain objects keyed by field name; the generated client narrows them. */
|
|
79
|
+
export type ClientRow = Record<string, unknown>;
|
|
80
|
+
/** Result of the count-returning batch methods. */
|
|
81
|
+
export type BatchResult = {
|
|
82
|
+
readonly count: number;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* A model's methods. Arguments and results are intentionally broad here — the
|
|
86
|
+
* generated `.d.ts` replaces this surface with precise per-model types, and
|
|
87
|
+
* keeping the runtime free of inference is what holds the type-cost budget
|
|
88
|
+
* (constitution rule 2).
|
|
89
|
+
*/
|
|
90
|
+
export type ModelDelegate = {
|
|
91
|
+
readonly findMany: (args?: ClientRow) => Promise<ClientRow[]>;
|
|
92
|
+
readonly findFirst: (args?: ClientRow) => Promise<ClientRow | null>;
|
|
93
|
+
readonly findFirstOrThrow: (args?: ClientRow) => Promise<ClientRow>;
|
|
94
|
+
readonly findUnique: (args: ClientRow) => Promise<ClientRow | null>;
|
|
95
|
+
readonly findUniqueOrThrow: (args: ClientRow) => Promise<ClientRow>;
|
|
96
|
+
readonly create: (args: ClientRow) => Promise<ClientRow | null>;
|
|
97
|
+
readonly createMany: (args: ClientRow) => Promise<BatchResult>;
|
|
98
|
+
readonly createManyAndReturn: (args: ClientRow) => Promise<ClientRow[]>;
|
|
99
|
+
readonly update: (args: ClientRow) => Promise<ClientRow>;
|
|
100
|
+
readonly updateMany: (args: ClientRow) => Promise<BatchResult>;
|
|
101
|
+
/**
|
|
102
|
+
* Bulk update that returns the rows it changed (dialects with RETURNING).
|
|
103
|
+
*
|
|
104
|
+
* OPTIONAL on purpose: the base client always provides it, but the rewriting
|
|
105
|
+
* handles (`$scoped`, `$withPolicy`) build explicit delegate literals and do
|
|
106
|
+
* not implement it yet, and a required member would make the type claim a
|
|
107
|
+
* method those handles do not have. See notes-epic2.md `## Hand-off`.
|
|
108
|
+
*/
|
|
109
|
+
readonly updateManyAndReturn: (args: ClientRow) => Promise<ClientRow[]>;
|
|
110
|
+
readonly upsertMany: (args: ClientRow) => Promise<UpsertManyResult>;
|
|
111
|
+
readonly upsert: (args: ClientRow) => Promise<ClientRow>;
|
|
112
|
+
readonly delete: (args: ClientRow) => Promise<ClientRow>;
|
|
113
|
+
readonly deleteMany: (args?: ClientRow) => Promise<BatchResult>;
|
|
114
|
+
/** Plain form returns a number; `{ select: { _all, field } }` returns per-key counts. */
|
|
115
|
+
readonly count: (args?: ClientRow) => Promise<number | Record<string, number>>;
|
|
116
|
+
readonly aggregate: (args: ClientRow) => Promise<ClientRow>;
|
|
117
|
+
readonly groupBy: (args: ClientRow) => Promise<ClientRow[]>;
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* The client. Delegates are also attached as own properties under their
|
|
121
|
+
* camelCase names (`client.user.findMany()`); the type surfaces them through
|
|
122
|
+
* `$models` because a plain index signature would erase the `$`-methods.
|
|
123
|
+
* Model names can never collide with those: every reserved client name is
|
|
124
|
+
* `$`-prefixed (see `RESERVED_CLIENT_NAMES` in @vibeorm/schema).
|
|
125
|
+
*/
|
|
126
|
+
export type DynamicClient = {
|
|
127
|
+
/** Copy and freeze the required native-RLS identity without acquiring a connection. */
|
|
128
|
+
readonly $withContext?: (context: Readonly<Record<string, unknown>>) => DynamicClient;
|
|
129
|
+
/** Delegates keyed by BOTH camelCase client name and model name. */
|
|
130
|
+
readonly $models: Readonly<Record<string, ModelDelegate>>;
|
|
131
|
+
/**
|
|
132
|
+
* Subscribe to client events (`"query"` fires after every statement, with
|
|
133
|
+
* the same payload as `ClientOptions.onQuery` — both fire). Returns an
|
|
134
|
+
* unsubscribe function. Transaction clients share the parent's listeners.
|
|
135
|
+
*/
|
|
136
|
+
readonly $on: (event: "query", listener: (event: QueryEvent) => void) => () => void;
|
|
137
|
+
/**
|
|
138
|
+
* Run work in ONE transaction. Callback form: `fn` receives a transaction
|
|
139
|
+
* client; everything it runs rides the same BEGIN/COMMIT and a throw rolls
|
|
140
|
+
* back. Array form (Prisma parity): UN-AWAITED delegate calls — delegate
|
|
141
|
+
* methods return LAZY query promises that only dispatch on await — run
|
|
142
|
+
* SEQUENTIALLY inside one transaction, results returned positionally.
|
|
143
|
+
*/
|
|
144
|
+
readonly $transaction: {
|
|
145
|
+
<T>(fn: (tx: DynamicClient) => Promise<T>, options?: TransactionOptions): Promise<T>;
|
|
146
|
+
<T extends readonly unknown[]>(operations: readonly [...T], options?: TransactionOptions): Promise<{
|
|
147
|
+
-readonly [K in keyof T]: Awaited<T[K]>;
|
|
148
|
+
}>;
|
|
149
|
+
};
|
|
150
|
+
/** Tagged template — interpolations become bound parameters, never text. */
|
|
151
|
+
readonly $queryRaw: (strings: TemplateStringsArray, ...values: unknown[]) => Promise<ClientRow[]>;
|
|
152
|
+
/** Tagged template returning the affected-row count. */
|
|
153
|
+
readonly $executeRaw: (strings: TemplateStringsArray, ...values: unknown[]) => Promise<number>;
|
|
154
|
+
readonly $queryRawUnsafe: (text: string, ...values: unknown[]) => Promise<ClientRow[]>;
|
|
155
|
+
readonly $executeRawUnsafe: (text: string, ...values: unknown[]) => Promise<number>;
|
|
156
|
+
readonly $connect: () => Promise<void>;
|
|
157
|
+
readonly $disconnect: () => Promise<void>;
|
|
158
|
+
/**
|
|
159
|
+
* Field-masking views (v1 `defineView` parity, board #11) — a read-only
|
|
160
|
+
* scoped client restricted to the definition's models and fields. Attached
|
|
161
|
+
* by `attachDefineView` after construction (hence optional in the type);
|
|
162
|
+
* it is always present on a built client, transaction clients included.
|
|
163
|
+
*/
|
|
164
|
+
readonly $defineView?: DefineViewFn;
|
|
165
|
+
/**
|
|
166
|
+
* Row scoping (@@policy): bind a per-request context — the returned client
|
|
167
|
+
* AND-s the policy predicate into every query on policied models and forces
|
|
168
|
+
* it into creates. Attached only when the schema declares @@policy.
|
|
169
|
+
*/
|
|
170
|
+
readonly $withPolicy?: (context: Readonly<Record<string, PolicyContextValue>>) => DynamicClient;
|
|
171
|
+
/** Deliberate, greppable full-access escape from policy scoping. */
|
|
172
|
+
readonly $bypassPolicy?: () => DynamicClient;
|
|
173
|
+
/**
|
|
174
|
+
* Tenant scoping (framework handoff): base client + total per-model tenancy
|
|
175
|
+
* map + scope value → a NEW handle whose every call is confined to that
|
|
176
|
+
* tenant (raw SQL refused). Always attached; refuses on @@policy schemas.
|
|
177
|
+
*/
|
|
178
|
+
readonly $scoped?: (config: ScopedClientConfig) => DynamicClient;
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* Build a client for a schema over an adapter.
|
|
182
|
+
*
|
|
183
|
+
* The adapter chooses the dialect: the same IR runs on postgres, sqlite or
|
|
184
|
+
* mysql, and every dialect difference is resolved by `@vibeorm/sql` and the
|
|
185
|
+
* codec table rather than by branching here.
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* const db = createClient({ schema, adapter });
|
|
189
|
+
* const users = await db.$models.user.findMany({ where: { active: true } });
|
|
190
|
+
*/
|
|
191
|
+
export declare function createClient(params: {
|
|
192
|
+
schema: SchemaIR;
|
|
193
|
+
adapter: DatabaseAdapter;
|
|
194
|
+
options?: ClientOptions;
|
|
195
|
+
/** Extension wiring — baked mounts + manifest from the generated client, live instances from the caller. */
|
|
196
|
+
extensions?: ClientExtensionsOptions;
|
|
197
|
+
/** Computed-field wiring — baked manifest from the generated client, live specs from the caller. */
|
|
198
|
+
computed?: ClientComputedOptions;
|
|
199
|
+
}): DynamicClient;
|
|
200
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/codecs.d.ts
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The codec entry points — constitution rule 6.
|
|
3
|
+
*
|
|
4
|
+
* The PURE per-dialect tables (dialect × scalar type) live in @vibeorm/sql
|
|
5
|
+
* (`codecs.ts` there) and are re-exported here so consumers keep one import
|
|
6
|
+
* site. This module owns the FieldMeta-aware entry points: list handling,
|
|
7
|
+
* enum-to-String routing and row materialization.
|
|
8
|
+
*
|
|
9
|
+
* The two rules that make it work:
|
|
10
|
+
* - EVERY parameter bound by the query builder passes through `encodeValue`.
|
|
11
|
+
* - EVERY row materialized by the client passes through `decodeRow`/
|
|
12
|
+
* `decodeRows` — including every relation-loaded child row, at every
|
|
13
|
+
* materialization point (the v1 lesson: BigInt-as-string and enum-array
|
|
14
|
+
* bugs came from children skipping coercion).
|
|
15
|
+
*/
|
|
16
|
+
import type { Dialect, FieldType } from "@vibeorm/schema";
|
|
17
|
+
import type { ScalarCodec, WireFidelity } from "@vibeorm/sql";
|
|
18
|
+
import type { FieldMeta, ModelMeta } from "./model-meta.ts";
|
|
19
|
+
export { DIALECT_CODECS, MYSQL_CODECS, POSTGRES_CODECS, SQLITE_CODECS, decodeIsNoop, } from "@vibeorm/sql";
|
|
20
|
+
export type { CodecTable, ScalarCodec, WireFidelity } from "@vibeorm/sql";
|
|
21
|
+
/** The codec for a field type on a dialect. Enum references use the String codec. */
|
|
22
|
+
export declare function getCodec(params: {
|
|
23
|
+
dialect: Dialect;
|
|
24
|
+
type: FieldType;
|
|
25
|
+
}): ScalarCodec;
|
|
26
|
+
/**
|
|
27
|
+
* Encode one value for parameter binding. `null`/`undefined` pass through
|
|
28
|
+
* untouched (SQL NULL). List fields encode element-wise; the adapter's
|
|
29
|
+
* `formatArrayParam` then owns the array's wire representation — except for
|
|
30
|
+
* postgres ENUM arrays, which become an array literal here (no driver can
|
|
31
|
+
* serialize them, see {@link isPostgresEnumList}).
|
|
32
|
+
*/
|
|
33
|
+
export declare function encodeValue(params: {
|
|
34
|
+
dialect: Dialect;
|
|
35
|
+
field: FieldMeta;
|
|
36
|
+
value: unknown;
|
|
37
|
+
}): unknown;
|
|
38
|
+
/**
|
|
39
|
+
* Decode one driver value. List fields decode element-wise.
|
|
40
|
+
*
|
|
41
|
+
* postgres returns ENUM arrays as a raw array literal string, because no driver
|
|
42
|
+
* knows a user-defined enum's array OID (LEARNINGS.md) — that literal is parsed
|
|
43
|
+
* here, next to the other coercion rules.
|
|
44
|
+
*/
|
|
45
|
+
export declare function decodeValue(params: {
|
|
46
|
+
dialect: Dialect;
|
|
47
|
+
field: FieldMeta;
|
|
48
|
+
value: unknown;
|
|
49
|
+
}): unknown;
|
|
50
|
+
/**
|
|
51
|
+
* Encode one COMPARISON operand (WHERE / cursor bounds). Identical to
|
|
52
|
+
* {@link encodeValue} everywhere except sqlite Decimal (SQL review B4):
|
|
53
|
+
* Decimal STORES as exact TEXT there, but text comparison is lexicographic —
|
|
54
|
+
* so every compare site casts the column (`CAST(col AS REAL)`) and binds the
|
|
55
|
+
* operand as a NUMBER. Comparison precision is float64-bounded on sqlite
|
|
56
|
+
* (docs/dialects.md); storage and read-back stay exact.
|
|
57
|
+
*/
|
|
58
|
+
export declare function encodeFilterValue(params: {
|
|
59
|
+
dialect: Dialect;
|
|
60
|
+
field: FieldMeta;
|
|
61
|
+
value: unknown;
|
|
62
|
+
}): unknown;
|
|
63
|
+
/**
|
|
64
|
+
* Encode one AGGREGATE-predicate operand (HAVING). On sqlite an aggregate
|
|
65
|
+
* expression has NO column affinity, so a TEXT operand never converts and the
|
|
66
|
+
* predicate is constant-false (SQL review M5, live-proven) — operands bind in
|
|
67
|
+
* raw numeric form there: Decimal as a number (the aggregate is over
|
|
68
|
+
* `CAST(col AS REAL)`), BigInt as a bigint (bun:sqlite binds int64 natively).
|
|
69
|
+
* Other dialects keep the field codec's wire form.
|
|
70
|
+
*/
|
|
71
|
+
export declare function encodeAggregateOperand(params: {
|
|
72
|
+
dialect: Dialect;
|
|
73
|
+
field: FieldMeta;
|
|
74
|
+
value: unknown;
|
|
75
|
+
}): unknown;
|
|
76
|
+
/** Decode one non-null driver value (list handling prebound). */
|
|
77
|
+
type RowDecoder = (value: unknown) => unknown;
|
|
78
|
+
/**
|
|
79
|
+
* The prebound encoder for one field on one dialect, or `null` when its codec
|
|
80
|
+
* encode is the identity. Mirrors `encodeValue` exactly (see `fieldDecoder`).
|
|
81
|
+
*/
|
|
82
|
+
export declare function fieldEncoder(params: {
|
|
83
|
+
dialect: Dialect;
|
|
84
|
+
field: FieldMeta;
|
|
85
|
+
}): RowDecoder | null;
|
|
86
|
+
/**
|
|
87
|
+
* The prebound decoder for one field on one dialect, or `null` when its codec
|
|
88
|
+
* decode is the identity (nothing to do). Mirrors `decodeValue` exactly:
|
|
89
|
+
* elements of a list decode element-wise (null elements pass), a non-array
|
|
90
|
+
* value on a list field goes through the element decoder as-is.
|
|
91
|
+
*
|
|
92
|
+
* With a `wire` declaration (board #30 — the ADAPTER's promise about what its
|
|
93
|
+
* driver delivers), decoders that are provably no-ops for that wire are pruned
|
|
94
|
+
* too ({@link decodeIsNoop}): non-list DateTime/Json/Int/Float columns on
|
|
95
|
+
* drivers that already deliver native forms. Without a declaration the full
|
|
96
|
+
* idempotency-guarded decoder set applies — exactly the pre-#30 behaviour.
|
|
97
|
+
*/
|
|
98
|
+
export declare function fieldDecoder(params: {
|
|
99
|
+
dialect: Dialect;
|
|
100
|
+
field: FieldMeta;
|
|
101
|
+
wire?: WireFidelity;
|
|
102
|
+
}): RowDecoder | null;
|
|
103
|
+
/**
|
|
104
|
+
* Materialize one driver row: column names become field names, values pass
|
|
105
|
+
* through their codecs. Columns the model does not know (aggregate aliases, raw
|
|
106
|
+
* extras) are carried over verbatim.
|
|
107
|
+
*/
|
|
108
|
+
export declare function decodeRow(params: {
|
|
109
|
+
dialect: Dialect;
|
|
110
|
+
model: ModelMeta;
|
|
111
|
+
row: Record<string, unknown>;
|
|
112
|
+
/** The executing adapter's wire declaration — prunes provably-no-op decoders (board #30). */
|
|
113
|
+
wire?: WireFidelity;
|
|
114
|
+
}): Record<string, unknown>;
|
|
115
|
+
/** `decodeRow` over a result set. */
|
|
116
|
+
export declare function decodeRows(params: {
|
|
117
|
+
dialect: Dialect;
|
|
118
|
+
model: ModelMeta;
|
|
119
|
+
rows: readonly Record<string, unknown>[];
|
|
120
|
+
/** The executing adapter's wire declaration — prunes provably-no-op decoders (board #30). */
|
|
121
|
+
wire?: WireFidelity;
|
|
122
|
+
}): Record<string, unknown>[];
|
|
123
|
+
/**
|
|
124
|
+
* `decodeRows` for rows the caller OWNS (fresh driver output): when no column
|
|
125
|
+
* is renamed the rows are decoded in place — only columns whose codec does
|
|
126
|
+
* something are touched — and the same array returns. With renames it falls
|
|
127
|
+
* back to rebuilding rows. Every decoder is idempotent (each checks its input
|
|
128
|
+
* type), so re-materializing an already-decoded row is harmless.
|
|
129
|
+
*
|
|
130
|
+
* Internal to the runtime (client + relation loader) — the exported
|
|
131
|
+
* `decodeRow`/`decodeRows` keep their copy semantics.
|
|
132
|
+
*/
|
|
133
|
+
export declare function materializeRows(params: {
|
|
134
|
+
dialect: Dialect;
|
|
135
|
+
model: ModelMeta;
|
|
136
|
+
rows: Record<string, unknown>[];
|
|
137
|
+
/** The executing adapter's wire declaration — prunes provably-no-op decoders (board #30). */
|
|
138
|
+
wire?: WireFidelity;
|
|
139
|
+
}): Record<string, unknown>[];
|
|
140
|
+
/**
|
|
141
|
+
* `materializeRows` for JSON-transport rows — the join strategy's child rows
|
|
142
|
+
* after `JSON.parse`. Postgres-only by construction (lateral joins are).
|
|
143
|
+
*/
|
|
144
|
+
export declare function materializeJsonRows(params: {
|
|
145
|
+
model: ModelMeta;
|
|
146
|
+
rows: Record<string, unknown>[];
|
|
147
|
+
}): Record<string, unknown>[];
|
|
148
|
+
/**
|
|
149
|
+
* Normalize one aggregate value — the contract, tested live on PGlite:
|
|
150
|
+
*
|
|
151
|
+
* | aggregate | field type | JS result |
|
|
152
|
+
* |-----------|-----------------|-------------------------------|
|
|
153
|
+
* | `_count` | any / `_all` | `number` (0 over an empty set)|
|
|
154
|
+
* | `_avg` | Int/BigInt/Float| `number` (pg numeric → Number)|
|
|
155
|
+
* | `_avg` | Decimal | `string` (no float rounding) |
|
|
156
|
+
* | `_sum` | Int / Float | `number` |
|
|
157
|
+
* | `_sum` | BigInt | `bigint` |
|
|
158
|
+
* | `_sum` | Decimal | `string` |
|
|
159
|
+
* | `_min/_max`| any | the field's own codec type |
|
|
160
|
+
*
|
|
161
|
+
* Every non-`_count` aggregate over an empty set is `null` (SQL semantics,
|
|
162
|
+
* kept deliberately — 0 would be a lie for MIN/AVG).
|
|
163
|
+
*/
|
|
164
|
+
export declare function decodeAggregateValue(params: {
|
|
165
|
+
dialect: Dialect;
|
|
166
|
+
key: "_count" | "_avg" | "_sum" | "_min" | "_max";
|
|
167
|
+
field: FieldMeta | null;
|
|
168
|
+
value: unknown;
|
|
169
|
+
}): unknown;
|
|
170
|
+
//# sourceMappingURL=codecs.d.ts.map
|