@vibeorm/runtime 2.6.0 → 3.0.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.
Files changed (97) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -1
  3. package/dist/adapter-kit/adapter-lifecycle.d.ts +79 -0
  4. package/dist/adapter-kit/deferred-control.d.ts +58 -0
  5. package/dist/adapter-kit/index.d.ts +10 -3
  6. package/dist/adapter-kit/index.js +317 -24
  7. package/dist/adapter-kit/index.js.map +10 -7
  8. package/dist/adapter-kit/nested-options.d.ts +16 -2
  9. package/dist/adapter-kit/row-changes.d.ts +15 -1
  10. package/dist/adapter-kit/savepoint-gate.d.ts +17 -3
  11. package/dist/adapter-kit/transaction-budget.d.ts +23 -13
  12. package/dist/adapter-kit/transaction-outcome.d.ts +167 -0
  13. package/dist/adapter.d.ts +103 -11
  14. package/dist/client.d.ts +7 -0
  15. package/dist/codecs.d.ts +35 -0
  16. package/dist/config.d.ts +15 -0
  17. package/dist/extensions.d.ts +10 -0
  18. package/dist/find-page.d.ts +9 -2
  19. package/dist/index.d.ts +8 -4
  20. package/dist/index.js +4086 -1295
  21. package/dist/index.js.map +40 -28
  22. package/dist/keyset-iterator.d.ts +12 -2
  23. package/dist/keyset.d.ts +65 -10
  24. package/dist/lateral-projection.d.ts +31 -1
  25. package/dist/model-meta.d.ts +12 -0
  26. package/dist/nested-fold.d.ts +98 -0
  27. package/dist/nested-update-data.d.ts +11 -0
  28. package/dist/nested-writes.d.ts +38 -1
  29. package/dist/query-builder.d.ts +78 -19
  30. package/dist/read-only.d.ts +5 -0
  31. package/dist/relation-key.d.ts +50 -0
  32. package/dist/relation-loader.d.ts +13 -1
  33. package/dist/relation-plan.d.ts +16 -0
  34. package/dist/strict-args.d.ts +1 -0
  35. package/dist/upsert-fold.d.ts +77 -0
  36. package/dist/write-scope.d.ts +9 -0
  37. package/package.json +7 -5
  38. package/dist/adapter-kit/index.d.ts.map +0 -1
  39. package/dist/adapter-kit/nested-options.d.ts.map +0 -1
  40. package/dist/adapter-kit/row-changes.d.ts.map +0 -1
  41. package/dist/adapter-kit/savepoint-gate.d.ts.map +0 -1
  42. package/dist/adapter-kit/savepoints.d.ts.map +0 -1
  43. package/dist/adapter-kit/session.d.ts.map +0 -1
  44. package/dist/adapter-kit/sqlite-session.d.ts.map +0 -1
  45. package/dist/adapter-kit/transaction-budget.d.ts.map +0 -1
  46. package/dist/adapter.d.ts.map +0 -1
  47. package/dist/advisory-key.d.ts.map +0 -1
  48. package/dist/advisory-lock.d.ts.map +0 -1
  49. package/dist/bulk-upsert.d.ts.map +0 -1
  50. package/dist/client-types.d.ts.map +0 -1
  51. package/dist/client.d.ts.map +0 -1
  52. package/dist/codecs.d.ts.map +0 -1
  53. package/dist/computed.d.ts.map +0 -1
  54. package/dist/database-module.d.ts.map +0 -1
  55. package/dist/db-now.d.ts.map +0 -1
  56. package/dist/diagnostics/index.d.ts.map +0 -1
  57. package/dist/diagnostics/insight.d.ts.map +0 -1
  58. package/dist/diagnostics/plan.d.ts.map +0 -1
  59. package/dist/diagnostics/preview.d.ts.map +0 -1
  60. package/dist/diagnostics/statement-diagnostics.d.ts.map +0 -1
  61. package/dist/diagnostics/types.d.ts.map +0 -1
  62. package/dist/diagnostics/workload.d.ts.map +0 -1
  63. package/dist/extensions.d.ts.map +0 -1
  64. package/dist/find-page.d.ts.map +0 -1
  65. package/dist/index.d.ts.map +0 -1
  66. package/dist/keyset-iterator.d.ts.map +0 -1
  67. package/dist/keyset-projection.d.ts.map +0 -1
  68. package/dist/keyset.d.ts.map +0 -1
  69. package/dist/lateral-projection.d.ts.map +0 -1
  70. package/dist/model-meta.d.ts.map +0 -1
  71. package/dist/module-context.d.ts.map +0 -1
  72. package/dist/nested-writes.d.ts.map +0 -1
  73. package/dist/policy-operation.d.ts.map +0 -1
  74. package/dist/policy.d.ts.map +0 -1
  75. package/dist/query-builder.d.ts.map +0 -1
  76. package/dist/read-only.d.ts.map +0 -1
  77. package/dist/relation-key.d.ts.map +0 -1
  78. package/dist/relation-loader.d.ts.map +0 -1
  79. package/dist/relation-plan.d.ts.map +0 -1
  80. package/dist/render-cache.d.ts.map +0 -1
  81. package/dist/rls-context.d.ts.map +0 -1
  82. package/dist/rls-readiness.d.ts.map +0 -1
  83. package/dist/scoped.d.ts.map +0 -1
  84. package/dist/sql-access.d.ts.map +0 -1
  85. package/dist/sql.d.ts.map +0 -1
  86. package/dist/strict-args.d.ts.map +0 -1
  87. package/dist/telemetry/collector.d.ts.map +0 -1
  88. package/dist/telemetry/config.d.ts.map +0 -1
  89. package/dist/telemetry/fingerprint.d.ts.map +0 -1
  90. package/dist/telemetry/index.d.ts.map +0 -1
  91. package/dist/telemetry/recorder.d.ts.map +0 -1
  92. package/dist/telemetry/statement.d.ts.map +0 -1
  93. package/dist/telemetry/types.d.ts.map +0 -1
  94. package/dist/transaction-row-changes.d.ts.map +0 -1
  95. package/dist/validators.d.ts.map +0 -1
  96. package/dist/views.d.ts.map +0 -1
  97. package/dist/write-scope.d.ts.map +0 -1
@@ -27,7 +27,12 @@
27
27
  import type { SqlDialect } from "@vibeorm/sql";
28
28
  import type { KeysetTimestampPrecision } from "./keyset.ts";
29
29
  import type { RuntimeMeta } from "./model-meta.ts";
30
- /** The one method this iterator needs — a model delegate satisfies it. */
30
+ /**
31
+ * The one method this iterator needs — a model delegate satisfies it. A
32
+ * wrapper that adds filters to `where` must return the delegate's result array
33
+ * itself: that array carries the query binding the engine checks, and a copy
34
+ * (`rows.map(…)`) loses it, so the next page is refused (`keyset-token-query`).
35
+ */
31
36
  export type KeysetPageSource = {
32
37
  readonly findMany: (args: Record<string, unknown>) => Promise<Record<string, unknown>[]>;
33
38
  };
@@ -43,11 +48,16 @@ export type KeysetIterateOptions = {
43
48
  readonly select?: Record<string, unknown>;
44
49
  readonly omit?: Record<string, unknown>;
45
50
  readonly include?: Record<string, unknown>;
46
- /** Resume from a checkpoint minted by a previous run over the SAME ordering. */
51
+ /**
52
+ * Resume from a checkpoint minted by a previous run over the SAME ordering,
53
+ * filters and scope — a checkpoint from another `where` or `scope` is refused.
54
+ */
47
55
  readonly after?: string;
48
56
  /** Consumer cancellation: stops future page fetches; ends iteration cleanly. */
49
57
  readonly signal?: AbortSignal;
50
58
  readonly timestampPrecision?: KeysetTimestampPrecision;
59
+ /** The caller's owner key (a user or tenant id) the checkpoints are bound to. */
60
+ readonly scope?: string;
51
61
  };
52
62
  export type KeysetIterator = AsyncIterableIterator<Record<string, unknown>> & {
53
63
  /** Always present: `break` and an exception both have to be able to release the traversal. */
package/dist/keyset.d.ts CHANGED
@@ -7,17 +7,23 @@
7
7
  * right place. That is the whole difference from the legacy `cursor`, which
8
8
  * addresses a row by a unique key and is left untouched by this module.
9
9
  *
10
- * Two rules this module exists to enforce:
10
+ * Three rules this module exists to enforce:
11
11
  * - a position is only meaningful against ONE effective ordering, so the
12
12
  * token carries that ordering's signature and a mismatch is refused loudly
13
13
  * rather than silently paginating the wrong sequence;
14
+ * - a position is only meaningful for the QUERY it came from, so the token
15
+ * also carries a digest of the normalized `where` (including the filters
16
+ * `$scoped` / `$withPolicy` add) and of the caller's optional
17
+ * `keyset.scope`, and a mismatch is refused the same way;
14
18
  * - a position's values are encoded LOSSLESSLY per scalar type — never
15
19
  * through a float64 detour and never from a rounded display value.
16
20
  *
17
- * The token is ENCODED, not encrypted: anyone holding it can read the ordering
18
- * values inside. It is also NOT an authorization credential — it selects a
19
- * position, never a row; every query consuming one still compiles the caller's
20
- * own `where` and keeps its `$scoped` / `$withPolicy` / RLS filters.
21
+ * The token is ENCODED, not encrypted: anyone holding it can read the model,
22
+ * the ordering and the ordering values inside, and can confirm a guessed
23
+ * filter or scope against the digest. It is also NOT an authorization
24
+ * credential — it selects a position, never a row; every query consuming one
25
+ * still compiles the caller's own `where` and keeps its `$scoped` /
26
+ * `$withPolicy` / RLS filters.
21
27
  */
22
28
  import type { SqlDialect } from "@vibeorm/sql";
23
29
  import type { FieldMeta, ModelMeta } from "./model-meta.ts";
@@ -28,6 +34,11 @@ export type KeysetNulls = "first" | "last";
28
34
  * millisecond-exact values. See {@link compileKeysetOrderPlan}.
29
35
  */
30
36
  export type KeysetTimestampPrecision = "milliseconds";
37
+ /**
38
+ * The query a token is bound to: a digest of the normalized `where` and the
39
+ * caller's optional `keyset.scope`. See {@link keysetQueryBinding}.
40
+ */
41
+ export type KeysetQueryBinding = string;
31
42
  /** One order term, already resolved by the query builder. */
32
43
  export type KeysetOrderInput = {
33
44
  readonly field: FieldMeta;
@@ -51,7 +62,11 @@ export type KeysetOrderPlan = {
51
62
  };
52
63
  /** Per-scalar lossless encoding tag stored in the token. */
53
64
  export type KeysetValueTag = "s" | "i" | "b" | "f" | "c" | "t" | "o" | "y";
54
- /** Token version. A token from another version is refused, never guessed at. */
65
+ /**
66
+ * Token version. A token from another version is refused, never guessed at —
67
+ * including version 1, minted before tokens were bound to their query: it
68
+ * cannot prove which filters it came from.
69
+ */
55
70
  export declare const KEYSET_TOKEN_VERSION: number;
56
71
  /** Token prefix — `vk` for "vibe keyset", then the version. */
57
72
  export declare const KEYSET_TOKEN_PREFIX: string;
@@ -97,8 +112,44 @@ export declare function keysetRowValues(params: {
97
112
  plan: KeysetOrderPlan;
98
113
  row: Readonly<Record<string, unknown>>;
99
114
  }): readonly unknown[];
115
+ /**
116
+ * The query a keyset token is bound to: the first 128 bits (hex) of a SHA-256
117
+ * over the canonical `where` and the caller's optional scope.
118
+ *
119
+ * `where` is the filter the ENGINE compiles — including the predicates
120
+ * `$scoped` and `$withPolicy` merge into it — so a token minted in one tenant's
121
+ * handle is refused in another's without any caller scope. `scope` is the
122
+ * caller's own owner key (a user or tenant id) for cases the filters do not
123
+ * show, e.g. native RLS context. A digest is not a secret: a holder can
124
+ * confirm a guessed filter value or scope against it.
125
+ *
126
+ * @example
127
+ * keysetQueryBinding({ where: { status: "OPEN" }, scope: userId })
128
+ */
129
+ export declare function keysetQueryBinding(params: {
130
+ where: unknown;
131
+ scope?: string;
132
+ }): KeysetQueryBinding;
133
+ /**
134
+ * Hidden channel on a `findMany` result that carried keyset arguments: the
135
+ * binding the ENGINE computed for it. The iterator mints its checkpoints with
136
+ * it, so a source that merges filters into `where` (`$scoped`, `$withPolicy`)
137
+ * yields checkpoints that source accepts. Non-enumerable, never serialized.
138
+ */
139
+ export declare const KEYSET_RESULT_BINDING: unique symbol;
140
+ /** Attach the engine's binding to a result array (see {@link KEYSET_RESULT_BINDING}). */
141
+ export declare function attachKeysetBinding<T extends object>(params: {
142
+ rows: T;
143
+ binding: KeysetQueryBinding;
144
+ }): T;
145
+ /** The engine's binding a result array carries, if any. */
146
+ export declare function keysetBindingOf(params: {
147
+ rows: unknown;
148
+ }): KeysetQueryBinding | undefined;
100
149
  /**
101
150
  * Mint a continuation token from a row that carries every ordering value.
151
+ * `binding` is the query it continues ({@link keysetQueryBinding}); omitted,
152
+ * it is the unfiltered, unscoped query.
102
153
  *
103
154
  * The row must contain each ordering field — a missing one is refused rather
104
155
  * than encoded as a null, because a silently null position paginates the wrong
@@ -108,15 +159,19 @@ export declare function keysetRowValues(params: {
108
159
  export declare function encodeKeysetToken(params: {
109
160
  plan: KeysetOrderPlan;
110
161
  row: Readonly<Record<string, unknown>>;
162
+ binding?: KeysetQueryBinding;
111
163
  }): string;
112
164
  /**
113
- * Read a token back into ordering values, validating version, model and the
114
- * effective order. A token minted under a different ordering (or for another
115
- * model, or by a future version) is refused with a precise, non-guessing
116
- * error — never used to paginate a sequence it does not describe.
165
+ * Read a token back into ordering values, validating version, model, the
166
+ * effective order and the query binding. A token minted under a different
167
+ * ordering, for different filters or another scope (or for another model, or
168
+ * by another version) is refused with a precise, non-guessing error — never
169
+ * used to paginate a sequence it does not describe. `binding` is the query
170
+ * presenting it; omitted, the unfiltered, unscoped query.
117
171
  */
118
172
  export declare function decodeKeysetToken(params: {
119
173
  plan: KeysetOrderPlan;
120
174
  token: string;
175
+ binding?: KeysetQueryBinding;
121
176
  }): readonly unknown[];
122
177
  //# sourceMappingURL=keyset.d.ts.map
@@ -1,5 +1,6 @@
1
1
  import type { ColRef } from "@vibeorm/sql";
2
- import type { RelationTreeNode } from "./relation-plan.ts";
2
+ import type { FieldMeta } from "./model-meta.ts";
3
+ import type { RelationSelection, RelationTreeNode } from "./relation-plan.ts";
3
4
  /** Child table qualifier, scoped independently inside each lateral. */
4
5
  export declare const LATERAL_CHILD_ALIAS: string;
5
6
  /** Shared SQL/decode descriptor. Its order is the resolved physical projection, never model order. */
@@ -11,6 +12,35 @@ export type LateralProjection = {
11
12
  export declare function lateralProjection(params: {
12
13
  node: RelationTreeNode;
13
14
  }): LateralProjection;
15
+ /**
16
+ * B3 (SQL review): a list column of BigInt/Decimal/Bytes has no faithful JSON
17
+ * wire form, so the lateral projection refuses it. ONE predicate, read by the
18
+ * refusal ({@link lateralProjection}) and by the R-02 nesting rule
19
+ * ({@link nestedLateralSelection}), so a level that would refuse never nests.
20
+ */
21
+ export declare function lateralRefusesField(params: {
22
+ field: FieldMeta;
23
+ }): boolean;
24
+ /**
25
+ * R-02 (round-trip campaign): the deeper level of a `"join"` relation node that
26
+ * rides INSIDE its lateral — each child carries one more positional slot per
27
+ * nested relation (`__rel_<name>`, its own nested lateral's JSON) — or `null`
28
+ * when that level keeps today's batched `"query"` loads. The builder (nesting
29
+ * the SQL) and the loader (decoding the slots) both read THIS function, so
30
+ * the payload and its decoder can never disagree. Cached per node.
31
+ *
32
+ * A level nests only in the shapes the campaign measured (EPIC 1 spike, EPIC 5
33
+ * A/B): foreign-key links (to-many FK, to-one either side), plain column
34
+ * orders, no `_count` at that level. Implicit many-to-many children and counts
35
+ * below level 1 keep the query strategy (with R-03a's derived counts) —
36
+ * unmeasured, so unchanged. (An include's orderBy is validated to scalars
37
+ * upstream; the order check here is defensive.) A level whose projection the
38
+ * lateral would refuse ({@link lateralRefusesField}, e.g. a `BigInt[]` column)
39
+ * keeps the query strategy too — before R-02 it loaded through it (RV4 B1).
40
+ */
41
+ export declare function nestedLateralSelection(params: {
42
+ node: RelationTreeNode;
43
+ }): RelationSelection | null;
14
44
  /**
15
45
  * PG-3 (SQL review round 2): order fields the lateral subselect must project
16
46
  * HIDDEN — the aggregate's explicit `ORDER BY "__sub".…` (N2) can only
@@ -152,6 +152,18 @@ export type RuntimeMeta = {
152
152
  */
153
153
  readonly extensionOperators?: ReadonlyMap<string, ExtensionOperatorFn>;
154
154
  };
155
+ /**
156
+ * The postgres type of a Json field's column (of its elements, for a list):
157
+ * `json` for `@db.Json` in any letter case — `.prisma` files say `@db.Json`,
158
+ * `db pull` writes `@db.json` and migrate accepts both — else `jsonb`. The
159
+ * ONE rule for every json/jsonb decision in the runtime: filters, list
160
+ * operands, order/distinct casts, write cells and the nested-write fold.
161
+ * Migrate normalizes the same annotation case-insensitively on its own
162
+ * (`normalizeNativeType`). Meaningful on postgres only.
163
+ */
164
+ export declare function jsonStorageOf(params: {
165
+ field: FieldMeta;
166
+ }): "json" | "jsonb";
155
167
  /**
156
168
  * Delegate key for a model name: the first character lowercased, nothing else
157
169
  * (the Prisma convention — `User` → `user`, `URLMap` → `uRLMap`). Keeping the
@@ -0,0 +1,98 @@
1
+ /**
2
+ * A nested write as ONE statement (round-trip campaign EPIC F, narrow scope).
3
+ *
4
+ * A top-level `create`, or an `update` with scalar data, whose ONLY nested
5
+ * operation is ONE homogeneous FK-side list of scalar children (`create: [...]`
6
+ * or `createMany`) runs today as parent statement + child statement inside a
7
+ * transaction (BEGIN · parent · children · COMMIT; a savepoint inside a
8
+ * caller's transaction). On a dialect with a statement-chain strategy
9
+ * (postgres: pg, bun, pglite) it is one statement instead:
10
+ *
11
+ * ```sql
12
+ * WITH "__vibe_parent" AS (INSERT … RETURNING … | UPDATE … WHERE <full where> RETURNING …),
13
+ * "__vibe_children" AS (INSERT INTO child (cols…, fk) SELECT src.cols…, "__vibe_parent".key
14
+ * FROM "__vibe_parent" CROSS JOIN (<typed VALUES rows>) AS src RETURNING …)
15
+ * SELECT parent row, CASE WHEN <a step returned nothing> THEN <fail> ELSE <rows changed> END
16
+ * ```
17
+ *
18
+ * Every child takes the parent key FROM the parent step, so an empty parent
19
+ * writes no child and a child BEFORE trigger sees the parent (review O-07).
20
+ * The in-statement veto (shape gate, `report-epic-f.md` §1) makes the
21
+ * STATEMENT fail when the parent step is empty (an UPDATE that matched
22
+ * nothing, a write a trigger suppressed) or, for a `create` list, when the
23
+ * child step returned fewer rows than items (EPIC W: one item → no row; N
24
+ * items → fewer than N, the `minRows` veto) — exactly where today's flow raises
25
+ * not-found. The database then undoes every effect, trigger writes included,
26
+ * so the statement needs no transaction at top level and no savepoint inside
27
+ * one (decision D1). The runtime turns the veto back into today's error.
28
+ *
29
+ * `createMany` carries no child veto: it never checks its row count (a
30
+ * trigger-suppressed child is silently skipped, like the top-level
31
+ * createMany's count).
32
+ *
33
+ * This module only BUILDS the statement (pure). Eligibility is a closed
34
+ * allowlist; anything else returns `null` and the whole call keeps today's
35
+ * path. Engine-level exclusions (native RLS, `@@policy` schemas) are the
36
+ * caller's.
37
+ */
38
+ import { VibeError } from "@vibeorm/schema";
39
+ import type { ChainStatement, SqlDialect, SqlStatement } from "@vibeorm/sql";
40
+ import type { ModelMeta, RuntimeMeta } from "./model-meta.ts";
41
+ import type { QueryMethod } from "./query-builder.ts";
42
+ /** Step names and the row-source alias. Models whose table starts with `__vibe_` never fold. */
43
+ export declare const NESTED_FOLD_NAMES: {
44
+ readonly parent: string;
45
+ readonly children: string;
46
+ readonly source: string;
47
+ };
48
+ /** The folded statement plus what the runtime needs to read its result or its veto. */
49
+ export type NestedFold = {
50
+ readonly statement: ChainStatement;
51
+ /** Model whose row the statement returns (the parent). */
52
+ readonly model: ModelMeta;
53
+ /** Veto token → today's error for that outcome. */
54
+ readonly vetoes: ReadonlyMap<string, () => VibeError>;
55
+ /** The per-call nonce every veto marker carries (a user value cannot know it). */
56
+ readonly nonce: string;
57
+ };
58
+ /**
59
+ * Fold the call into one statement, or return `null` (today's path). Builds
60
+ * the same plans today's flow builds (`plan`), so validation, defaults and
61
+ * codecs are unchanged; plan-time refusals raise today's errors (a child's now
62
+ * before any SQL, see below).
63
+ *
64
+ * Allowlist: statement-chain dialect with RETURNING; exactly ONE nested op, an
65
+ * FK-side to-many list (`toManyFk`) on a different table, not `__vibe_`
66
+ * prefixed; one verb — `create` (one or more items) or `createMany` without
67
+ * `skipDuplicates`; at least one item, none with nested writes, all defining
68
+ * the same fields; at most {@link RUNTIME_CONFIG}`.nestedFoldMaxChildren`
69
+ * items; no protected write scope on the verbs; an update must carry scalar
70
+ * data; one child statement (no parameter-budget chunks), no DEFAULT cell, no
71
+ * Json cell on a non-`jsonb` column, no column mixing `dbNow()` with values,
72
+ * `dbNow()` only on a timestamptz/timestamp/date column; no raw SQL fragment
73
+ * anywhere.
74
+ *
75
+ * The child plan is compiled BEFORE any SQL is sent (EPIC F decision 1): a
76
+ * child that validation refuses throws here, so it now outranks a parent
77
+ * database error (today's path compiled it after the parent statement ran).
78
+ */
79
+ export declare function buildNestedFold(params: {
80
+ dialect: SqlDialect;
81
+ meta: RuntimeMeta;
82
+ model: ModelMeta;
83
+ method: "create" | "update";
84
+ data: Record<string, unknown>;
85
+ where?: Record<string, unknown>;
86
+ skipUpdatedAt?: boolean;
87
+ /** Per-call random nonce bound into every veto marker (review RV3 MN-3). */
88
+ nonce: string;
89
+ plan: (params: {
90
+ model: string;
91
+ method: QueryMethod;
92
+ args: Record<string, unknown>;
93
+ }) => {
94
+ statement: SqlStatement;
95
+ batch?: readonly SqlStatement[];
96
+ };
97
+ }): NestedFold | null;
98
+ //# sourceMappingURL=nested-fold.d.ts.map
@@ -0,0 +1,11 @@
1
+ import type { ModelMeta } from "./model-meta.ts";
2
+ /** Resolve only a nested update's body, consistently across execution and argument walkers. */
3
+ export declare function resolveNestedUpdateData(params: {
4
+ model: ModelMeta;
5
+ isList: boolean;
6
+ operand: Record<string, unknown>;
7
+ }): {
8
+ readonly data: Record<string, unknown>;
9
+ readonly wrapped: boolean;
10
+ };
11
+ //# sourceMappingURL=nested-update-data.d.ts.map
@@ -1,6 +1,7 @@
1
1
  import type { SqlDialect, SqlStatement } from "@vibeorm/sql";
2
2
  import type { ModelMeta, RuntimeMeta } from "./model-meta.ts";
3
3
  import type { QueryMethod } from "./query-builder.ts";
4
+ import type { RelationLink } from "./relation-plan.ts";
4
5
  export type ClientRowLike = Record<string, unknown>;
5
6
  /**
6
7
  * Executes ORM statements against ONE adapter (plain or transactional).
@@ -14,6 +15,12 @@ export type StatementRunner = {
14
15
  method: QueryMethod;
15
16
  args: Record<string, unknown>;
16
17
  }) => Promise<ClientRowLike>;
18
+ /** `single`, but `null` when the where matched no row — the caller raises its own not-found. */
19
+ readonly singleOrNull: (params: {
20
+ model: string;
21
+ method: QueryMethod;
22
+ args: Record<string, unknown>;
23
+ }) => Promise<ClientRowLike | null>;
17
24
  readonly rows: (params: {
18
25
  model: string;
19
26
  method: QueryMethod;
@@ -36,7 +43,10 @@ export type WriteContext = {
36
43
  readonly dialect: SqlDialect;
37
44
  /** Runner on the client's current adapter (may already be transactional). */
38
45
  readonly run: StatementRunner;
39
- /** Runs `fn` with a runner bound to a (nested) transaction. */
46
+ /**
47
+ * Runs `fn` with a runner bound to a (nested) transaction — or, inside a
48
+ * transaction an array batch or the ORM call already owns, on that one.
49
+ */
40
50
  readonly transactional: <T>(fn: (run: StatementRunner) => Promise<T>) => Promise<T>;
41
51
  };
42
52
  /** Does this `data` object carry nested relation operations? */
@@ -44,6 +54,31 @@ export declare function hasNestedWrites(params: {
44
54
  model: ModelMeta;
45
55
  data: unknown;
46
56
  }): boolean;
57
+ /** One relation's nested-write verbs, as `partitionData` found them. */
58
+ export type NestedOp = {
59
+ readonly name: string;
60
+ readonly link: RelationLink;
61
+ readonly verbs: Record<string, unknown>;
62
+ };
63
+ /** Split write data into scalar columns and nested relation ops (validating verb names). */
64
+ export declare function partitionData(params: {
65
+ meta: RuntimeMeta;
66
+ model: ModelMeta;
67
+ data: Record<string, unknown>;
68
+ phase: "create" | "update";
69
+ }): {
70
+ scalars: Record<string, unknown>;
71
+ ops: NestedOp[];
72
+ };
73
+ /** A verb's operand as its list of object items (refusing an array on a to-one relation). */
74
+ export declare function toItems(params: {
75
+ op: NestedOp;
76
+ verb: string;
77
+ operand: unknown;
78
+ }): Record<string, unknown>[];
79
+ /** Every row defines the same key set — the batching precondition (see below). */
80
+ /** Whether every row defines the same keys (an undefined value counts as absent). */
81
+ export declare function sameDefinedKeys(rows: readonly Record<string, unknown>[]): boolean;
47
82
  /**
48
83
  * `create` with nested relation operations. Returns the parent row as the
49
84
  * INSERT produced it (relation loading/projection are the client's follow-up).
@@ -63,5 +98,7 @@ export declare function updateWithNested(params: {
63
98
  model: ModelMeta;
64
99
  where: Record<string, unknown>;
65
100
  data: Record<string, unknown>;
101
+ /** Suppress only the parent's automatic timestamp, never the child's. */
102
+ skipUpdatedAt?: boolean;
66
103
  }): Promise<ClientRowLike>;
67
104
  //# sourceMappingURL=nested-writes.d.ts.map
@@ -32,6 +32,17 @@ export type AggregateSpec = {
32
32
  };
33
33
  /** How included relations are fetched. */
34
34
  export type RelationStrategy = "query" | "join";
35
+ /**
36
+ * R-03b: one level-1 `_count` entry the root find statement projects as a
37
+ * correlated `COUNT(*)` column, so the loader reads it off the row instead of
38
+ * sending its grouped count statement.
39
+ */
40
+ export type EmbeddedCount = {
41
+ /** The counted relation (`posts`) — the `_count` key. */
42
+ readonly name: string;
43
+ /** The root row's column carrying the count (`__vibe_count_0`), deleted after reading. */
44
+ readonly column: string;
45
+ };
35
46
  /**
36
47
  * A built query plus everything the client needs after execution. The plan is
37
48
  * data only: the client decides how to run it.
@@ -65,7 +76,9 @@ export type QueryPlan = {
65
76
  /**
66
77
  * How relations load. With `"join"` on a find method the base statement IS
67
78
  * the lateral statement (level-1 relations ride along as JSON columns);
68
- * deeper levels always fall back to `"query"` batches (v1 semantics).
79
+ * deeper levels nest inside them where {@link nestedLateralSelection}
80
+ * admits (R-02, `nestedLaterals`) and otherwise fall back to `"query"`
81
+ * batches (v1 semantics).
69
82
  */
70
83
  readonly relationStrategy: RelationStrategy;
71
84
  /** True when `take` produced a LIMIT. */
@@ -102,6 +115,18 @@ export type QueryPlan = {
102
115
  * update 0, so its number is not a row count at all.
103
116
  */
104
117
  readonly upsertRowCount?: number;
118
+ /**
119
+ * find methods under the `"query"` strategy only (R-03b): the level-1
120
+ * `_count` entries this statement already projects. Absent when none.
121
+ */
122
+ readonly embeddedCounts?: readonly EmbeddedCount[];
123
+ /**
124
+ * find methods under `"join"` only (R-02): the lateral statement nests the
125
+ * deeper relation levels {@link nestedLateralSelection} admits inside the
126
+ * depth-1 laterals, so the loader decodes them from the payload instead of
127
+ * querying them. Absent when the statement nests nothing.
128
+ */
129
+ readonly nestedLaterals?: boolean;
105
130
  /** The caller's `select`, replayed on that re-select. */
106
131
  readonly selectArg: Readonly<Record<string, unknown>> | null;
107
132
  /** How to decode aggregate/groupBy/count-select rows; `null` elsewhere. */
@@ -110,26 +135,20 @@ export type QueryPlan = {
110
135
  readonly groupByFields: readonly string[] | null;
111
136
  /** Negative `take`: the SQL ordering was flipped; re-reverse rows after fetch. */
112
137
  readonly reverseRows: boolean;
113
- /**
114
- * `distinct` + an explicit user orderBy (N1, board #22): the arrangement
115
- * DISTINCT ON needs demotes the user's order behind the distinct fields, so
116
- * the client re-sorts the FINAL rows by these keys — Prisma's presentation
117
- * order, on every dialect. `nulls` is resolved at build time to the
118
- * dialect's own NULL placement so the re-sort mirrors what the SQL order
119
- * would have produced. `null` when no re-sort is needed.
120
- */
121
- readonly resortBy: readonly ResortKey[] | null;
122
- };
123
- /** One client-side re-sort key (see QueryPlan.resortBy). */
124
- export type ResortKey = {
125
- readonly field: string;
126
- readonly direction: "asc" | "desc";
127
- readonly nulls: "first" | "last";
128
- /** B4 mirror: Decimal ranks numerically — the SQL side ordered a number. */
129
- readonly decimal: boolean;
130
138
  };
131
139
  /** Column alias for COUNT(*) — postgres and sqlite/mysql name it differently otherwise. */
132
140
  export declare const COUNT_ALIAS: string;
141
+ /**
142
+ * A model's full row projection as SQL text, every column qualified by
143
+ * `qualify` and read in the form `findMany` reads it ({@link projectionRef}:
144
+ * postgres `Float[]`/`Json[]` as `::text`). For statements built outside the
145
+ * query builder — extension SQL — whose rows decode through the model's plan.
146
+ */
147
+ export declare function compileProjection(params: {
148
+ dialect: SqlDialect;
149
+ model: ModelMeta;
150
+ qualify: string;
151
+ }): string;
133
152
  /**
134
153
  * Compile a Prisma-shaped where object; `undefined` when it constrains nothing.
135
154
  *
@@ -167,6 +186,19 @@ export declare function compileSelect(params: {
167
186
  select: unknown;
168
187
  allowEmpty?: boolean;
169
188
  }): string[];
189
+ /**
190
+ * Expand the compound-key sugar the GENERATED `…WhereUniqueInput` emits:
191
+ * `{ projectId_userId: { projectId, userId } }` becomes
192
+ * `{ projectId: …, userId: … }`, so a composite `@@id`/`@@unique` compiles
193
+ * exactly like the flat form. Models without a composite key (the common case)
194
+ * return their where object untouched, allocation-free.
195
+ */
196
+ export declare function expandCompoundWhere(params: {
197
+ model: ModelMeta;
198
+ where: Record<string, unknown>;
199
+ /** Argument label for errors — `"where"` (default) or `"cursor"` (#4). */
200
+ what?: string;
201
+ }): Record<string, unknown>;
170
202
  /**
171
203
  * Does this (already compound-expanded) where address at most one row? The
172
204
  * predicate `assertUniqueWhere` enforces, exported so a batch that stands in
@@ -206,6 +238,22 @@ export declare function upsertUsesNativeStatement(params: {
206
238
  model: ModelMeta;
207
239
  args: Record<string, unknown>;
208
240
  }): boolean;
241
+ /**
242
+ * The rows split into the runs `createMany` inserts as one column group each,
243
+ * in INPUT order — or `null` when grouping would reorder them. A dialect with
244
+ * the per-row DEFAULT keyword keeps every row in one VALUES list (one run). On
245
+ * sqlite rows group by their COMPILED column set in first-appearance order
246
+ * ({@link compileCreateRows}), so item order survives only when every group is
247
+ * one contiguous run of items; otherwise autoincrement ids would be handed out
248
+ * of item order. Compiles each row exactly as the plan will (app defaults and
249
+ * `@updatedAt` included), so a row that cannot compile throws the plan's own
250
+ * error here.
251
+ */
252
+ export declare function createManyRowRuns<T>(params: {
253
+ dialect: SqlDialect;
254
+ model: ModelMeta;
255
+ data: readonly T[];
256
+ }): readonly (readonly T[])[] | null;
209
257
  /**
210
258
  * Compile a raw `orderBy` argument into keyset order inputs — the ONE parsing
211
259
  * path the iterator and the `after` bound share, so a token's signature can
@@ -217,12 +265,17 @@ export declare function compileKeysetOrderInputs(params: {
217
265
  model: ModelMeta;
218
266
  orderBy: unknown;
219
267
  }): KeysetOrderInput[];
220
- /** The `keyset: { … }` per-query options bag. */
268
+ /**
269
+ * The `keyset: { … }` per-query options bag: `timestampPrecision` (the
270
+ * millisecond assertion) and `scope` (the caller's owner key, mixed into the
271
+ * token's query binding).
272
+ */
221
273
  export declare function parseKeysetOptions(params: {
222
274
  model: ModelMeta;
223
275
  value: unknown;
224
276
  }): {
225
277
  timestampPrecision?: KeysetTimestampPrecision;
278
+ scope?: string;
226
279
  };
227
280
  /** The column a relation's JSON payload is exposed under in the outer row. */
228
281
  export declare function lateralColumnAlias(params: {
@@ -264,5 +317,11 @@ export declare function buildQuery(params: {
264
317
  args?: Record<string, unknown>;
265
318
  defaultRelationStrategy?: RelationStrategy;
266
319
  clientOptions?: BuilderClientOptions;
320
+ /**
321
+ * R-03b: `false` keeps level-1 counts out of the statement. A write's
322
+ * read-back (dialects without RETURNING) plans a find whose counts the
323
+ * write's own finalize loads with the grouped statement (RV4 m1).
324
+ */
325
+ embedCounts?: boolean;
267
326
  }): QueryPlan;
268
327
  //# sourceMappingURL=query-builder.d.ts.map
@@ -105,6 +105,11 @@ export type LazyOperationBridge = {
105
105
  reason: unknown;
106
106
  }) => void;
107
107
  };
108
+ /** @internal Inherit authentic classifier provenance without exposing a marker property. */
109
+ export declare function inheritReadOnlyReceiver(params: {
110
+ source: unknown;
111
+ target?: object;
112
+ }): boolean;
108
113
  /**
109
114
  * Attach `$readOnly` to a built client. Called once per client build; the
110
115
  * facade itself is built lazily on the first call, so it always sees the FINAL
@@ -1,3 +1,5 @@
1
+ import type { KeyTableMember, SqlDialect } from "@vibeorm/sql";
2
+ import type { FieldMeta } from "./model-meta.ts";
1
3
  /** Compare decoded relation keys by value, including DateTime milliseconds and binary bytes. */
2
4
  export declare function relationValueKey(params: {
3
5
  value: unknown;
@@ -20,4 +22,52 @@ export declare function relationValueKey(params: {
20
22
  export declare function relationTupleKey(params: {
21
23
  values: readonly unknown[];
22
24
  }): string | null;
25
+ /** EPIC R: alias of the key table (a `KeyTableJoin` from `@vibeorm/sql`). */
26
+ export declare const KEY_TABLE_ALIAS: string;
27
+ /** EPIC R: the key table's ordinal column — the index of the key row the database paired. */
28
+ export declare const KEY_ORDINAL_ALIAS: string;
29
+ /**
30
+ * Must the DATABASE decide which keys are equal?
31
+ *
32
+ * Comparing decoded keys in JavaScript is exact bytes. That is the database's
33
+ * own equality only where text compares bytes: postgres and sqlite
34
+ * (`conflictKeyEquality.textIsBinaryByDefault`). Mysql's default
35
+ * `utf8mb4_0900_ai_ci` equates `acme`/`Acme`/`ACME` and `cafe`/`Café`, and its
36
+ * PAD SPACE collations equate `x ` and `x` — the foreign key, the join table and
37
+ * every `IN` accept such a key, so an exact JavaScript comparison disagrees
38
+ * with the database (EPIC R for includes, release 3.0 D4 for nested writes).
39
+ *
40
+ * Any text (`String`) member switches the WHOLE tuple to the key table. A
41
+ * declared `@collation` does not opt out: it is the schema's claim, and
42
+ * migrate never alters an existing column's collation, so the physical column
43
+ * can still fold. Non-text keys (Int, BigInt, DateTime, Bytes, …) read back
44
+ * canonically on both sides and keep the exact comparison.
45
+ *
46
+ * Postgres (release 3.0 lane n, review "Fixes 2" and "Fixes 3"): a `citext`
47
+ * key member switches the tuple too — citext compares case-insensitively, and
48
+ * a citext foreign key or join column stores `acme` under the key `Acme`.
49
+ * Every other postgres key, and every sqlite key, compares by value, and its
50
+ * SQL is byte-identical to before.
51
+ */
52
+ export declare function associatesInDatabase(params: {
53
+ dialect: SqlDialect;
54
+ fields: readonly FieldMeta[];
55
+ }): boolean;
56
+ /**
57
+ * How a key member binds in the key table, on the dialect that runs it
58
+ * ({@link associatesInDatabase}): postgres in {@link postgresKeyTableMember},
59
+ * mysql below.
60
+ */
61
+ export declare function keyTableMember(params: {
62
+ dialect: SqlDialect;
63
+ field: FieldMeta;
64
+ }): KeyTableMember;
65
+ /**
66
+ * The key-table ordinal a row carries, or `null` when it is not one. The
67
+ * ordinals are integer literals: they arrive as numbers or, under mysql's
68
+ * `bigNumberStrings`, as digit strings.
69
+ */
70
+ export declare function keyOrdinalOf(params: {
71
+ raw: unknown;
72
+ }): number | null;
23
73
  //# sourceMappingURL=relation-key.d.ts.map