@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.
Files changed (96) hide show
  1. package/README.md +50 -107
  2. package/dist/adapter.d.ts +250 -0
  3. package/dist/adapter.d.ts.map +1 -0
  4. package/dist/bulk-upsert.d.ts +282 -0
  5. package/dist/bulk-upsert.d.ts.map +1 -0
  6. package/dist/client.d.ts +200 -0
  7. package/dist/client.d.ts.map +1 -0
  8. package/dist/codecs.d.ts +170 -0
  9. package/dist/codecs.d.ts.map +1 -0
  10. package/dist/computed.d.ts +43 -0
  11. package/dist/computed.d.ts.map +1 -0
  12. package/dist/db-now.d.ts +41 -0
  13. package/dist/db-now.d.ts.map +1 -0
  14. package/dist/diagnostics/index.d.ts +12 -0
  15. package/dist/diagnostics/index.d.ts.map +1 -0
  16. package/dist/diagnostics/insight.d.ts +63 -0
  17. package/dist/diagnostics/insight.d.ts.map +1 -0
  18. package/dist/diagnostics/plan.d.ts +88 -0
  19. package/dist/diagnostics/plan.d.ts.map +1 -0
  20. package/dist/diagnostics/preview.d.ts +43 -0
  21. package/dist/diagnostics/preview.d.ts.map +1 -0
  22. package/dist/diagnostics/types.d.ts +223 -0
  23. package/dist/diagnostics/types.d.ts.map +1 -0
  24. package/dist/diagnostics/workload.d.ts +32 -0
  25. package/dist/diagnostics/workload.d.ts.map +1 -0
  26. package/dist/extensions.d.ts +102 -0
  27. package/dist/extensions.d.ts.map +1 -0
  28. package/dist/index.d.ts +59 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +13070 -0
  31. package/dist/index.js.map +43 -0
  32. package/dist/keyset-iterator.d.ts +73 -0
  33. package/dist/keyset-iterator.d.ts.map +1 -0
  34. package/dist/keyset.d.ts +121 -0
  35. package/dist/keyset.d.ts.map +1 -0
  36. package/dist/model-meta.d.ts +200 -0
  37. package/dist/model-meta.d.ts.map +1 -0
  38. package/dist/nested-writes.d.ts +67 -0
  39. package/dist/nested-writes.d.ts.map +1 -0
  40. package/dist/policy-operation.d.ts +14 -0
  41. package/dist/policy-operation.d.ts.map +1 -0
  42. package/dist/policy.d.ts +17 -0
  43. package/dist/policy.d.ts.map +1 -0
  44. package/dist/query-builder.d.ts +271 -0
  45. package/dist/query-builder.d.ts.map +1 -0
  46. package/dist/relation-key.d.ts +23 -0
  47. package/dist/relation-key.d.ts.map +1 -0
  48. package/dist/relation-loader.d.ts +46 -0
  49. package/dist/relation-loader.d.ts.map +1 -0
  50. package/dist/relation-plan.d.ts +141 -0
  51. package/dist/relation-plan.d.ts.map +1 -0
  52. package/dist/render-cache.d.ts +48 -0
  53. package/dist/render-cache.d.ts.map +1 -0
  54. package/dist/rls-context.d.ts +14 -0
  55. package/dist/rls-context.d.ts.map +1 -0
  56. package/dist/rls-readiness.d.ts +114 -0
  57. package/dist/rls-readiness.d.ts.map +1 -0
  58. package/dist/scoped.d.ts +104 -0
  59. package/dist/scoped.d.ts.map +1 -0
  60. package/dist/strict-args.d.ts +47 -0
  61. package/dist/strict-args.d.ts.map +1 -0
  62. package/dist/telemetry/collector.d.ts +53 -0
  63. package/dist/telemetry/collector.d.ts.map +1 -0
  64. package/dist/telemetry/config.d.ts +53 -0
  65. package/dist/telemetry/config.d.ts.map +1 -0
  66. package/dist/telemetry/fingerprint.d.ts +38 -0
  67. package/dist/telemetry/fingerprint.d.ts.map +1 -0
  68. package/dist/telemetry/index.d.ts +18 -0
  69. package/dist/telemetry/index.d.ts.map +1 -0
  70. package/dist/telemetry/recorder.d.ts +93 -0
  71. package/dist/telemetry/recorder.d.ts.map +1 -0
  72. package/dist/telemetry/statement.d.ts +53 -0
  73. package/dist/telemetry/statement.d.ts.map +1 -0
  74. package/dist/telemetry/types.d.ts +265 -0
  75. package/dist/telemetry/types.d.ts.map +1 -0
  76. package/dist/validators.d.ts +61 -0
  77. package/dist/validators.d.ts.map +1 -0
  78. package/dist/views.d.ts +97 -0
  79. package/dist/views.d.ts.map +1 -0
  80. package/dist/write-scope.d.ts +14 -0
  81. package/dist/write-scope.d.ts.map +1 -0
  82. package/package.json +33 -26
  83. package/src/adapter.ts +0 -146
  84. package/src/client.ts +0 -2172
  85. package/src/coerce.ts +0 -184
  86. package/src/count-loader.ts +0 -152
  87. package/src/errors.ts +0 -492
  88. package/src/id-generators.ts +0 -151
  89. package/src/index.ts +0 -55
  90. package/src/lateral-join-builder.ts +0 -1053
  91. package/src/query-builder.ts +0 -1832
  92. package/src/relation-loader.ts +0 -534
  93. package/src/retry.ts +0 -183
  94. package/src/types.ts +0 -317
  95. package/src/view.ts +0 -629
  96. 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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"}
@@ -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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"}
@@ -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