@vantis/data 0.0.1

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.
@@ -0,0 +1,879 @@
1
+ /**
2
+ * Schema augmentation seam.
3
+ *
4
+ * This interface is intentionally empty here. You never edit this file.
5
+ *
6
+ * The `vantis data build` command (S2) emits a project-local declaration file
7
+ * that extends this interface with your table shapes:
8
+ *
9
+ * declare module '@vantis/data' {
10
+ * interface Schema {
11
+ * users: { id: string; email: string };
12
+ * orders: { id: string; user_id: string; total: string };
13
+ * }
14
+ * }
15
+ *
16
+ * TypeScript merges that declaration into this interface at compile time,
17
+ * making the full typed shape available throughout your project without any
18
+ * manual type registration. The same pattern is used by TanStack's `Register`
19
+ * interface (QueryClient, Router) and Hono's `Env` bindings.
20
+ *
21
+ * The Go-emitted `.vantis.d.ts` (from `vantis data build`, ADR-0048) augments this
22
+ * registry; the runtime client (`data.from()`, insert/upsert, embed, RPC, verbs)
23
+ * reads it to provide inferred row types and query results.
24
+ */
25
+ interface Schema {
26
+ }
27
+ /**
28
+ * RPC registry augmentation seam (VAN-832). Empty here; `vantis data build`
29
+ * emits a project-local declaration extending this with each typed function:
30
+ *
31
+ * declare module '@vantis/data' {
32
+ * interface Rpc {
33
+ * place_order: { Args: { qty: number }; Returns: { id: string }[] };
34
+ * }
35
+ * }
36
+ *
37
+ * `data.run.<name>(args)` is typed off this interface, exactly as `data.from`
38
+ * is typed off Schema.
39
+ */
40
+ interface Rpc {
41
+ }
42
+ /**
43
+ * Verb registry augmentation seam (VAN-866, ADR-0047). Empty here;
44
+ * `vantis data build` emits a project-local declaration extending this with
45
+ * per-table typed ops + match shapes:
46
+ *
47
+ * declare module '@vantis/data' {
48
+ * interface Verbs {
49
+ * orders: {
50
+ * ops: { total?: NumericVerb | SetOnInsertVerb; status?: TextVerb | SetOnInsertVerb };
51
+ * match: { id: string };
52
+ * guard: { id?: { eq?: string; ne?: string }; total?: { eq?: number; gt?: number } };
53
+ * };
54
+ * }
55
+ * }
56
+ *
57
+ * `.modify(ops, match, opts?)` is typed off this interface, exactly as
58
+ * `data.from()` is typed off Schema and `data.run.<name>()` off Rpc.
59
+ */
60
+ interface Verbs {
61
+ }
62
+ /**
63
+ * Strict insert payload seam (VAN-869, ADR-0048). Empty here;
64
+ * `vantis data build` emits a project-local declaration extending this with
65
+ * per-table insert shapes that enforce NOT-NULL-no-default columns as required:
66
+ *
67
+ * declare module '@vantis/data' {
68
+ * interface SchemaInsert {
69
+ * users: { email: string; id?: string; display_name?: string | null };
70
+ * }
71
+ * }
72
+ *
73
+ * `.insert()` and `.upsert()` read `SchemaInsert[T]` when T is registered
74
+ * here, catching missing required columns at compile time. Tables absent from
75
+ * SchemaInsert fall back to `Partial<Schema[T]>` (permissive v0 behaviour).
76
+ */
77
+ interface SchemaInsert {
78
+ }
79
+ /**
80
+ * FK embed cardinality seam (VAN-869, ADR-0048). Empty here;
81
+ * `vantis data build` emits a project-local declaration extending this with
82
+ * per-table FK cardinality entries — `'one'` for many-to-one (FK column is
83
+ * the referencing table's PK), `'many'` for one-to-many (all other FKs):
84
+ *
85
+ * declare module '@vantis/data' {
86
+ * interface Embeds {
87
+ * posts: { users: 'one' }; // posts.user_id FK → users (many-to-one)
88
+ * users: { posts: 'many' }; // users.id → posts (one-to-many inverse)
89
+ * }
90
+ * }
91
+ *
92
+ * `.embed(related)` reads `Embeds[T][Related]` to determine the result type:
93
+ * `'one'` → `Schema[Related]` (object), `'many'` → `Schema[Related][]` (array).
94
+ * Tables or relationships absent from Embeds default to the array form.
95
+ */
96
+ interface Embeds {
97
+ }
98
+
99
+ /**
100
+ * @vantis/data — typed PostgREST error.
101
+ *
102
+ * Maps the PostgREST error body shape `{ code, message, details, hint }`.
103
+ * The result discriminated union returned by every query builder:
104
+ * Success → { data: T; error: null; count: number|null; status: number; statusText: string }
105
+ * Failure → { data: null; error: DataError; count: null; status: number; statusText: string }
106
+ *
107
+ * Security (INV-01/INV-02): the token is NEVER included in any error field.
108
+ * Full PostgREST error detail is returned to the tenant's own pod (server-tier —
109
+ * acceptable per INV-10 / ADR-0039 §4). Public-edge redaction is VAN-844.
110
+ */
111
+ /**
112
+ * A typed error returned from the Data API.
113
+ *
114
+ * Mirrors the PostgREST error body: code (PostgreSQL error code, e.g. "42P01"),
115
+ * message (human-readable), and the optional details/hint fields.
116
+ *
117
+ * The `kind` field distinguishes config errors (missing env vars, browser
118
+ * environment) from wire errors returned by PostgREST.
119
+ */
120
+ interface DataError {
121
+ /** Discriminator: 'postgrest' for wire errors; 'config' for SDK config errors. */
122
+ readonly kind: 'postgrest' | 'config';
123
+ /** PostgreSQL error code (e.g. "42P01", "PGRST301") — or a short config key. */
124
+ readonly code: string;
125
+ /** Human-readable error message. NEVER contains the DATA_API_TOKEN. */
126
+ readonly message: string;
127
+ /** Additional error detail from PostgreSQL or PostgREST. May be null. */
128
+ readonly details: string | null;
129
+ /** Hint from PostgreSQL or PostgREST. May be null. */
130
+ readonly hint: string | null;
131
+ }
132
+ /**
133
+ * Discriminated result union for every Data API query.
134
+ *
135
+ * On success: `error` is null, `data` holds the typed payload.
136
+ * On failure: `data` is null, `error` holds the typed DataError.
137
+ * `count` carries the total row count (when requested) and is null on failure.
138
+ * `status`/`statusText` mirror the HTTP response.
139
+ */
140
+ type DataResult<T> = {
141
+ data: T;
142
+ error: null;
143
+ count: number | null;
144
+ status: number;
145
+ statusText: string;
146
+ } | {
147
+ data: null;
148
+ error: DataError;
149
+ count: null;
150
+ status: number;
151
+ statusText: string;
152
+ };
153
+
154
+ /**
155
+ * @vantis/data — zero-config typed query client.
156
+ *
157
+ * Exports the ambient `data` object. Entry point: `data.from(tableName)`.
158
+ *
159
+ * Security contracts:
160
+ * - INV-01/INV-02: DATA_API_TOKEN is NEVER logged, NEVER included in error
161
+ * messages, NEVER placed in the URL (it is an Authorization header only).
162
+ * - INV-11: env vars are read LAZILY on the FIRST QUERY (not at import/module
163
+ * eval) so a framework build (next build) that imports server modules before
164
+ * deploy-time injection does not throw.
165
+ * - Browser guard: if a browser global is present at query time, fail closed
166
+ * with a server-only error (secondary to the `exports` browser condition stub
167
+ * in browser.ts — the runtime guard is defense-in-depth).
168
+ * - ALL clients have explicit timeouts per CONVENTIONS.md.
169
+ */
170
+
171
+ /** The row shape for a given table (pulled from the augmented Schema). */
172
+ type Row<T extends keyof Schema> = Schema[T];
173
+ /**
174
+ * Strict insert payload for a Schema table (VAN-869, ADR-0048).
175
+ *
176
+ * When the Go emitter (`vantis data build`) has registered the table in the
177
+ * `SchemaInsert` interface, this resolves to `SchemaInsert[T]` — a shape
178
+ * where NOT-NULL-no-default columns are required and nullable/defaulted columns
179
+ * are optional. Tables absent from SchemaInsert (e.g. during initial authoring
180
+ * before the first `vantis data build`) fall back to `Partial<Row<T>>`.
181
+ *
182
+ * Used by `.insert()`, `.upsert()`, and the `upsert` branch of `ModifyOptions`.
183
+ */
184
+ type InsertPayload<T extends keyof Schema> = T extends keyof SchemaInsert ? SchemaInsert[T] : Partial<Row<T>>;
185
+ /**
186
+ * Partial update payload — always `Partial<Row<T>>`.
187
+ *
188
+ * `.update()` sends only the columns to change; requiring all NOT-NULL columns
189
+ * would be wrong for a partial update. This is separate from InsertPayload so
190
+ * insert/upsert can be strict while update remains permissive.
191
+ */
192
+ type UpdatePayload<T extends keyof Schema> = Partial<Row<T>>;
193
+ /**
194
+ * Resolves the embed result type for a related table.
195
+ *
196
+ * Reads the `Embeds` cardinality registry populated by `vantis data build`:
197
+ * - `'one'` (FK column is the referencing table's PK) → `Schema[TRelated]` (object)
198
+ * - `'many'` (all other FKs — one-to-many inverse) → `Schema[TRelated][]` (array)
199
+ * - `'one' | 'many'` (self-FK or ambiguous) → `Schema[TRelated] | Schema[TRelated][]` (union)
200
+ *
201
+ * Defaults to the array form when the table or relationship is absent from
202
+ * `Embeds` (safe: PostgREST returns an array for unregistered embed shapes).
203
+ *
204
+ * EmbedByCardinality uses a bare type parameter `C` so TypeScript distributes
205
+ * over union cardinalities: `EmbedByCardinality<R, 'one' | 'many'>` resolves
206
+ * to `Schema[R] | Schema[R][]` rather than collapsing to the array branch
207
+ * (the old `Embeds[TTable][TRelated] extends 'one'` pattern was NOT
208
+ * distributive because the checked type was a property access, not a bare
209
+ * parameter — VAN-869 fix D).
210
+ */
211
+ type EmbedByCardinality<R extends keyof Schema, C> = C extends 'one' ? Schema[R] : Schema[R][];
212
+ type EmbedResult<TTable extends keyof Schema, TRelated extends keyof Schema> = TTable extends keyof Embeds ? TRelated extends keyof Embeds[TTable] ? EmbedByCardinality<TRelated, Embeds[TTable][TRelated]> : Schema[TRelated][] : Schema[TRelated][];
213
+ /**
214
+ * The `ops` shape for `.modify()` on a given table — `VerbEnvelope` per
215
+ * modifiable (non-PK, non-uuid) column, all optional.
216
+ *
217
+ * Resolves to `never` for tables absent from the `Verbs` registry (i.e.
218
+ * tables with no `__vantis_modify_<t>` engine function — no PK, or all
219
+ * non-PK columns are non-modifiable types like uuid). Calling `.modify()`
220
+ * on such a table is a compile-time type error: the engine would 404 at
221
+ * runtime, so it must be caught by the type system instead.
222
+ *
223
+ * Populated by `vantis data build` emitting a per-project `.vantis.d.ts`
224
+ * that augments `interface Verbs { … }` — exactly as `Schema` is populated
225
+ * for `data.from()` and `Rpc` is populated for `data.run.<name>()`.
226
+ */
227
+ type VerbOpsFor<TTableName extends keyof Schema> = TTableName extends keyof Verbs ? 'ops' extends keyof Verbs[TTableName] ? Verbs[TTableName]['ops'] : never : never;
228
+ /**
229
+ * The `match` shape for `.modify()` on a given table — PK column values.
230
+ *
231
+ * Resolves to `never` for tables absent from the `Verbs` registry (same
232
+ * reasons as VerbOpsFor — engine-absent tables must fail at compile time).
233
+ */
234
+ type VerbMatchFor<TTableName extends keyof Schema> = TTableName extends keyof Verbs ? 'match' extends keyof Verbs[TTableName] ? Verbs[TTableName]['match'] : never : never;
235
+ /**
236
+ * The `guard` shape for `.modify()` on a given table — typed comparator
237
+ * objects per column. Falls back to `Record<string, Record<string, unknown>>`
238
+ * for tables absent from the `Verbs` registry.
239
+ */
240
+ type VerbGuardFor<TTableName extends keyof Schema> = TTableName extends keyof Verbs ? 'guard' extends keyof Verbs[TTableName] ? Verbs[TTableName]['guard'] : Record<string, Record<string, unknown>> : Record<string, Record<string, unknown>>;
241
+ /**
242
+ * Options for `.modify()`. Generic over the table name so `guard` is typed
243
+ * to the column + comparator shape generated by `vantis data build`.
244
+ *
245
+ * `guard` and `upsert` are mutually exclusive at the type level (XOR):
246
+ * providing both is a compile-time error. The engine enforces the same
247
+ * constraint at runtime (sending both raises INVALID_ARGUMENT).
248
+ *
249
+ * @property upsert - When present, activates the upsert branch: if the row
250
+ * identified by `match` does not exist, it is inserted with the values
251
+ * from this object. Use setOnInsert() in `ops` to set additional fields
252
+ * only on INSERT (Mongo $setOnInsert semantics).
253
+ *
254
+ * @property guard - Optional single-column optimistic-concurrency check.
255
+ * Shape: `{ "<col>": { "<comparator>": <value> } }`.
256
+ * Comparators: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`.
257
+ * If the guard condition is NOT met, the engine returns an empty array
258
+ * (not-applied signal) without modifying the row or raising an error.
259
+ * Check for `result.data?.length === 0` in your application logic.
260
+ */
261
+ type ModifyOptions<T extends keyof Schema = keyof Schema> = {
262
+ upsert?: InsertPayload<T>;
263
+ guard?: undefined;
264
+ } | {
265
+ upsert?: undefined;
266
+ guard?: VerbGuardFor<T>;
267
+ };
268
+ interface BuilderState {
269
+ table: string;
270
+ filters: string[];
271
+ selectCols: string;
272
+ orderParts: string[];
273
+ limitVal: number | null;
274
+ limitReferencedTable: string | null;
275
+ offsetVal: number | null;
276
+ countMode: 'exact' | 'planned' | 'estimated' | null;
277
+ headMode: boolean;
278
+ singleMode: boolean;
279
+ maybeSingleMode: boolean;
280
+ writeOp: 'insert' | 'update' | 'delete' | 'upsert' | null;
281
+ writeBody: unknown;
282
+ writeSelect: boolean;
283
+ writeSelectCols: string;
284
+ onConflict: string | null;
285
+ ignoreDuplicates: boolean;
286
+ embeds: string[];
287
+ searchQuery: string | null;
288
+ searchLimit: number | null;
289
+ }
290
+ /**
291
+ * Options for `.select()` when `head` is false or absent (normal query).
292
+ * - count: request a count total from PostgREST. Default none.
293
+ */
294
+ interface SelectOptions {
295
+ head?: false;
296
+ count?: 'exact' | 'planned' | 'estimated';
297
+ }
298
+ /**
299
+ * Options for `.select()` when `head` is true (HEAD request — count-only).
300
+ *
301
+ * @note `head: true` returns `data: null` (HEAD has no body). The data
302
+ * field will always be null regardless of the row type parameter.
303
+ */
304
+ interface SelectHeadOptions {
305
+ head: true;
306
+ count?: 'exact' | 'planned' | 'estimated';
307
+ }
308
+ /** Options for `.order()`. */
309
+ interface OrderOptions {
310
+ ascending?: boolean;
311
+ nullsFirst?: boolean;
312
+ referencedTable?: string;
313
+ }
314
+ /** Options for `.limit()`. */
315
+ interface LimitOptions {
316
+ referencedTable?: string;
317
+ }
318
+ /** Options for `.upsert()`. */
319
+ interface UpsertOptions {
320
+ onConflict?: string;
321
+ ignoreDuplicates?: boolean;
322
+ }
323
+ /**
324
+ * Options for `.search()`.
325
+ *
326
+ * The server pre-sorts results best-first (relevance-ordered). The `limit`
327
+ * option caps results client-side via the PostgREST `/rpc` limit parameter
328
+ * (also bounded by `PGRST_DB_MAX_ROWS=1000` on the server).
329
+ */
330
+ interface SearchOptions {
331
+ /** Maximum number of rows to return (passed as PostgREST `?limit=n`). */
332
+ limit?: number;
333
+ }
334
+ /**
335
+ * The QueryBuilder is a thenable — `await data.from('users')` resolves to
336
+ * a DataResult without requiring an explicit `.execute()` call.
337
+ *
338
+ * Generic parameters:
339
+ * TTableName — the table name (keyof Schema).
340
+ * TRow — the current row shape (modified by embed).
341
+ * TResult — the full DataResult type for this builder's current state.
342
+ *
343
+ * v0 notes:
344
+ * - `.select(columns)` types as the FULL row for any column string (column-
345
+ * subset Pick narrowing is a v1 follow-up).
346
+ * - `.insert(values)` payload is typed as `SchemaInsert[T]` when the Go emitter
347
+ * has registered the table (strict: required NOT-NULL-no-default columns
348
+ * enforced). Falls back to `Partial<Row[T]>` for unregistered tables.
349
+ * - `.embed(table)` cardinality is resolved from the `Embeds` registry:
350
+ * `'one'` → single object; `'many'` → array. Unregistered → array default.
351
+ */
352
+ declare class QueryBuilder<TTableName extends keyof Schema, TRow = Row<TTableName>, TResult extends DataResult<unknown> = DataResult<TRow[]>> implements PromiseLike<TResult> {
353
+ private readonly _state;
354
+ constructor(tableName: TTableName, state?: BuilderState);
355
+ /**
356
+ * Equality filter: column = value.
357
+ *
358
+ * The value is emitted raw (not quoted). URLSearchParams URL-encodes it, which
359
+ * is correct and sufficient for scalar operators — PostgREST takes the whole
360
+ * value after `eq.` and does NOT strip double-quotes for scalar ops (quoting
361
+ * would cause a literal string mismatch, e.g. `email=eq."a@b.com"` → 0 rows).
362
+ */
363
+ eq<K extends keyof TRow & string>(col: K, val: TRow[K]): this;
364
+ /**
365
+ * Inequality filter: column != value.
366
+ *
367
+ * Value emitted raw — see eq() for the scalar-quoting rationale.
368
+ */
369
+ neq<K extends keyof TRow & string>(col: K, val: TRow[K]): this;
370
+ /**
371
+ * Greater-than filter: column > value.
372
+ *
373
+ * Value emitted raw — see eq() for the scalar-quoting rationale.
374
+ */
375
+ gt<K extends keyof TRow & string>(col: K, val: TRow[K]): this;
376
+ /**
377
+ * Greater-than-or-equal filter: column >= value.
378
+ *
379
+ * Value emitted raw — see eq() for the scalar-quoting rationale.
380
+ */
381
+ gte<K extends keyof TRow & string>(col: K, val: TRow[K]): this;
382
+ /**
383
+ * Less-than filter: column < value.
384
+ *
385
+ * Value emitted raw — see eq() for the scalar-quoting rationale.
386
+ */
387
+ lt<K extends keyof TRow & string>(col: K, val: TRow[K]): this;
388
+ /**
389
+ * Less-than-or-equal filter: column <= value.
390
+ *
391
+ * Value emitted raw — see eq() for the scalar-quoting rationale.
392
+ */
393
+ lte<K extends keyof TRow & string>(col: K, val: TRow[K]): this;
394
+ /**
395
+ * LIKE filter. Pattern uses `%` (translated to PostgREST `*` on the wire)
396
+ * or `*` directly. Example: `.like('email', '%@example.com')`.
397
+ *
398
+ * The wildcard-translated pattern is emitted raw. URLSearchParams URL-encodes
399
+ * it, which is correct and sufficient — PostgREST takes the whole value after
400
+ * `like.` as the pattern. Do NOT quote like patterns: PostgREST does NOT strip
401
+ * double-quotes for scalar operators, so `like."a.b*"` would match a literal
402
+ * value containing `"` rather than performing `LIKE 'a.b%'`.
403
+ *
404
+ * NOTE: a LITERAL `*` in a user-supplied substring cannot be escaped —
405
+ * PostgREST has no like-escape mechanism and `*` is always the wildcard.
406
+ * Strip or reject literal `*` from user input at the call site if needed.
407
+ */
408
+ like<K extends keyof TRow & string>(col: K, pattern: string): this;
409
+ /**
410
+ * Case-insensitive LIKE filter. Pattern uses `%` or `*`.
411
+ *
412
+ * Pattern emitted raw after wildcard translation — see like() for the
413
+ * scalar-quoting rationale.
414
+ */
415
+ ilike<K extends keyof TRow & string>(col: K, pattern: string): this;
416
+ /**
417
+ * IN filter: column IN (values).
418
+ * Each element is quoted per the PostgREST URL grammar.
419
+ */
420
+ in<K extends keyof TRow & string>(col: K, values: TRow[K][]): this;
421
+ /**
422
+ * IS filter: column IS true/false/null.
423
+ * PostgREST wire: `?col=is.true` / `?col=is.false` / `?col=is.null`.
424
+ * Values are fixed keywords — NOT subject to quoteFilterValue.
425
+ */
426
+ is<K extends keyof TRow & string>(col: K, val: boolean | null): this;
427
+ /**
428
+ * Set the column selection and optional count/head options.
429
+ *
430
+ * v0 type-narrowing: column-subset narrowing (Pick) is not implemented for v0.
431
+ * `.select('id, name')` types as the full row (same as `.select('*')`).
432
+ *
433
+ * `head: true` returns `data: null` (HEAD has no body — it is a count-only
434
+ * request). The overload narrows the return to `DataResult<null>` when
435
+ * `head: true` is passed as an option.
436
+ *
437
+ * When chained after a write (.insert/.update/.delete/.upsert), sends
438
+ * `Prefer: return=representation` and emits `?select=<columns>` if a non-default
439
+ * column list is given (v0 fix: write-chained .select('id') correctly narrows
440
+ * the response columns, not silently over-returning all columns).
441
+ */
442
+ select(columns?: string, options?: SelectHeadOptions): QueryBuilder<TTableName, TRow, DataResult<null>>;
443
+ select(columns?: string, options?: SelectOptions): QueryBuilder<TTableName, TRow, DataResult<TRow[]>>;
444
+ /**
445
+ * Embed a related table by FK.
446
+ *
447
+ * Adds the related table to the PostgREST select: `?select=*,<relatedTable>(*)`.
448
+ * PostgREST resolves the FK automatically (requires a schema-cache reload after
449
+ * FK changes — https://postgrest.org/en/v14/references/api/resource_embedding.html).
450
+ *
451
+ * The result row type gains `{ <relatedTable>: EmbedResult<TTableName, TEmbedTable> }`.
452
+ * The cardinality is resolved from the `Embeds` registry populated by `vantis data build`:
453
+ * - `'one'` (FK column is the referencing table's PK) → single object `Schema[TEmbedTable]`.
454
+ * - `'many'` (all other FKs — one-to-many inverse) → array `Schema[TEmbedTable][]`.
455
+ * - Unregistered relationships default to the array form.
456
+ *
457
+ * @example
458
+ * ```ts
459
+ * // posts.user_id FK → users (many-to-one; Embeds says 'one')
460
+ * const result = await data.from('posts').embed('users');
461
+ * // result.data[0].users — Schema['users'] (object, not array)
462
+ *
463
+ * // users.id → posts (one-to-many; Embeds says 'many')
464
+ * const result2 = await data.from('users').embed('posts');
465
+ * // result2.data[0].posts — Schema['posts'][] (array)
466
+ * ```
467
+ */
468
+ embed<TEmbedTable extends keyof Schema>(relatedTable: TEmbedTable): QueryBuilder<TTableName, TRow & {
469
+ [K in TEmbedTable]: EmbedResult<TTableName, TEmbedTable>;
470
+ }, DataResult<(TRow & {
471
+ [K in TEmbedTable]: EmbedResult<TTableName, TEmbedTable>;
472
+ })[]>>;
473
+ /**
474
+ * Order results by a column.
475
+ * @param col Column name.
476
+ * @param options ascending (default true), nullsFirst (default undefined), referencedTable.
477
+ */
478
+ order<K extends keyof TRow & string>(col: K, options?: OrderOptions): this;
479
+ /**
480
+ * Limit the number of rows returned.
481
+ *
482
+ * When `referencedTable` is set, emits `?<referencedTable>.limit=n` to limit
483
+ * an embedded resource (PostgREST embedded-resource limiting). Without
484
+ * `referencedTable`, emits the top-level `?limit=n`.
485
+ */
486
+ limit(n: number, options?: LimitOptions): this;
487
+ /**
488
+ * Return a range of rows by offset.
489
+ * @param from Start offset (0-indexed).
490
+ * @param to End offset (inclusive).
491
+ */
492
+ range(from: number, to: number): this;
493
+ /**
494
+ * Insert one or more rows.
495
+ *
496
+ * By default returns `data: null` (PostgREST `return=minimal`).
497
+ * Chain `.select()` to get the inserted rows back (`return=representation`).
498
+ *
499
+ * When the Go emitter (`vantis data build`) has registered the table in
500
+ * `SchemaInsert`, the payload enforces required NOT-NULL-no-default columns
501
+ * at compile time. Tables not yet registered fall back to `Partial<Row>`.
502
+ */
503
+ insert(values: InsertPayload<TTableName> | InsertPayload<TTableName>[]): QueryBuilder<TTableName, TRow, DataResult<null>>;
504
+ /**
505
+ * Update rows matching the current filters.
506
+ *
507
+ * By default returns `data: null`. Chain `.select()` for the updated rows.
508
+ *
509
+ * `values` is always `Partial<Row>` — you send only the columns to change.
510
+ * For insert/upsert, see `.insert()` and `.upsert()` (strict SchemaInsert typing).
511
+ */
512
+ update(values: UpdatePayload<TTableName>): QueryBuilder<TTableName, TRow, DataResult<null>>;
513
+ /**
514
+ * Delete rows matching the current filters.
515
+ *
516
+ * By default returns `data: null`. Chain `.select()` for the deleted rows.
517
+ */
518
+ delete(): QueryBuilder<TTableName, TRow, DataResult<null>>;
519
+ /**
520
+ * Upsert one or more rows.
521
+ *
522
+ * Uses `POST + Prefer: resolution=merge-duplicates` (or `ignore-duplicates`).
523
+ * By default returns `data: null`. Chain `.select()` for the upserted rows.
524
+ */
525
+ upsert(values: InsertPayload<TTableName> | InsertPayload<TTableName>[], options?: UpsertOptions): QueryBuilder<TTableName, TRow, DataResult<null>>;
526
+ /**
527
+ * Expect exactly one row. Returns `data: TRow` (not an array).
528
+ * If zero or more than one row is returned, PostgREST returns a 406 and this
529
+ * resolves to an error.
530
+ *
531
+ * Sets `Accept: application/vnd.pgrst.object+json`.
532
+ */
533
+ single(): QueryBuilder<TTableName, TRow, DataResult<TRow>>;
534
+ /**
535
+ * Expect zero or one row. Returns `data: TRow | null`.
536
+ *
537
+ * Client-side normalization (mirrors postgrest-js):
538
+ * - 0 rows → `{ data: null, error: null }` (no error)
539
+ * - 1 row → `{ data: <row>, error: null }`
540
+ * - >1 rows → `{ data: null, error: { code: 'PGRST116', ... } }`
541
+ *
542
+ * Uses the normal array `Accept: application/json` — PostgREST returns the
543
+ * array, and normalization happens client-side. This avoids the 406 that
544
+ * `vnd.pgrst.object+json` would return for 0 rows.
545
+ */
546
+ maybeSingle(): QueryBuilder<TTableName, TRow, DataResult<TRow | null>>;
547
+ /**
548
+ * Full-text search via the engine-provisioned rank RPC.
549
+ *
550
+ * Routes to `GET /rpc/search_<table>?query=<text>` (the PostgREST `/rpc`
551
+ * path, not the table path). Results are pre-sorted most-relevant-first by
552
+ * the server (`ts_rank_cd` ordering inside the function). Each result row
553
+ * carries a `rank` field — an OPAQUE ordering-only relevance score.
554
+ *
555
+ * The four swap invariants (VAN-830 / ADR-0044):
556
+ * (i) One entry point — `.search(query, opts)`. Engine specifics never exposed.
557
+ * (ii) Text query in — engine-specific parsing is internal.
558
+ * (iii) Opaque `rank` — ordering-only, never normalized. A future BM25/rerank
559
+ * swap changes the scale, not the contract.
560
+ * (iv) Pluggable transport — currently PostgREST `/rpc`; semantic search goes
561
+ * to the AI gateway (opt-in, reserved).
562
+ *
563
+ * Security (INV-01/INV-02):
564
+ * - The query text is passed via URLSearchParams (URL-encoded by the browser
565
+ * URLSearchParams API) — NOT double-quoted (no PostgREST scalar-op quoting
566
+ * applies to RPC function arguments).
567
+ * - The DATA_API_TOKEN is in the Authorization header ONLY, never in the URL.
568
+ * - Filters/select/order from any previously chained methods are NOT emitted
569
+ * in search mode — the RPC returns the full row shape + rank, ordered by
570
+ * the server.
571
+ *
572
+ * Requires the table to have at least one `searchable: true` column in
573
+ * `vantis.schema.json` and a `vantis data apply` that provisions the search function.
574
+ *
575
+ * @example
576
+ * ```ts
577
+ * const result = await data.from('articles').search('hello world');
578
+ * if (result.error) { ... }
579
+ * for (const row of result.data) {
580
+ * console.log(row.title, row.rank); // rank: opaque ordering-only score
581
+ * }
582
+ * ```
583
+ *
584
+ * With a limit:
585
+ * ```ts
586
+ * const result = await data.from('articles').search('typescript', { limit: 10 });
587
+ * ```
588
+ */
589
+ search(query: string, opts?: SearchOptions): QueryBuilder<TTableName, TRow, DataResult<Array<TRow & {
590
+ rank: number;
591
+ }>>>;
592
+ /**
593
+ * Atomically modify a single row via the server-side verb RPC
594
+ * (`__vantis_modify_<table>`). Each value in `ops` must be a VerbEnvelope
595
+ * returned by one of the verb helpers (increment, decrement, multiply, divide,
596
+ * min, max, toggle, serverTimestamp, concat, merge, jsonSet, setOnInsert).
597
+ * Bare objects or plain values are rejected synchronously.
598
+ *
599
+ * Wire: `POST /rpc/__vantis_modify_<table>` `Content-Profile: api`
600
+ * ```json
601
+ * { "__vantis_match": {...}, "__vantis_ops": {...},
602
+ * "__vantis_row"?: {...}, "__vantis_guard"?: {...} }
603
+ * ```
604
+ *
605
+ * Returns:
606
+ * - `{ data: [row], error: null }` — applied (UPDATE path).
607
+ * - `{ data: [], error: null }` — guard miss or row absent (not-applied).
608
+ * Check `result.data?.length === 0` in your application logic.
609
+ * - `{ data: null, error }` — a DataError from PostgREST or the engine.
610
+ *
611
+ * Security: token in Authorization header only (INV-01/INV-02). Lazy env
612
+ * read (INV-11). Browser guard (secondary; browser.ts export condition is
613
+ * primary).
614
+ *
615
+ * @example
616
+ * ```ts
617
+ * const result = await data.from('orders').modify(
618
+ * { total: increment(10) },
619
+ * { id: orderId },
620
+ * { guard: { total: { gte: 0 } } },
621
+ * );
622
+ * if (!result.error && result.data?.length === 0) {
623
+ * // guard miss — row unchanged
624
+ * }
625
+ * ```
626
+ */
627
+ modify(ops: VerbOpsFor<TTableName>, match: VerbMatchFor<TTableName>, opts?: ModifyOptions<TTableName>): Promise<DataResult<TRow[]>>;
628
+ /**
629
+ * Guard: throws synchronously if `.search()` has already been called on this
630
+ * builder. `.search()` is terminal in v0 — no mutator may be chained after it.
631
+ *
632
+ * Without this guard, a trailing mutator (e.g. `.eq()`) would modify `_state`
633
+ * fields that `_buildRequest`'s search branch silently ignores, producing an
634
+ * unscoped query that returns rows the caller did not intend to receive.
635
+ *
636
+ * Called at the top of every state-mutating method.
637
+ */
638
+ private _assertNotSearch;
639
+ then<TFulfilled = TResult, TRejected = never>(onfulfilled?: ((value: TResult) => TFulfilled | PromiseLike<TFulfilled>) | null | undefined, onrejected?: ((reason: unknown) => TRejected | PromiseLike<TRejected>) | null | undefined): Promise<TFulfilled | TRejected>;
640
+ private _execute;
641
+ private _buildRequest;
642
+ private _parseResponse;
643
+ }
644
+ /** The typed shape of data.run — one method per Rpc registry entry. */
645
+ type RunProxy = {
646
+ [K in keyof Rpc]: (args: Rpc[K]['Args']) => Promise<DataResult<Rpc[K]['Returns']>>;
647
+ };
648
+ /**
649
+ * The typed data client. Import and use directly — no construction required.
650
+ *
651
+ * Reads `DATA_API_URL` and `DATA_API_TOKEN` from the environment on the first
652
+ * query (lazy — so framework builds that import server modules before env
653
+ * injection do not throw at import time).
654
+ *
655
+ * @example
656
+ * ```ts
657
+ * import { data } from '@vantis/data';
658
+ *
659
+ * const result = await data.from('users').eq('email', 'alice@example.com').single();
660
+ * if (result.error) { ... }
661
+ * const user = result.data; // typed as Schema['users']
662
+ * ```
663
+ */
664
+ declare const data: {
665
+ /**
666
+ * Start a query against the given table.
667
+ *
668
+ * `tableName` is typed as `keyof Schema` — the augmented Schema interface
669
+ * populated by `vantis data build`.
670
+ *
671
+ * Returns a `QueryBuilder` that is also PromiseLike — `await` it directly.
672
+ */
673
+ readonly from: <T extends keyof Schema>(tableName: T) => QueryBuilder<T>;
674
+ /**
675
+ * Call a typed RPC (VAN-832). `data.run.<name>(args)` POSTs to /rpc/<name>
676
+ * and returns the typed result. Names + arg/return types come from the
677
+ * generated Rpc registry (`vantis data build`).
678
+ *
679
+ * @example
680
+ * ```ts
681
+ * const r = await data.run.place_order({ product_id: id, qty: 2 });
682
+ * if (r.error) { ... }
683
+ * const orderId = r.data; // typed from the fn() declaration
684
+ * ```
685
+ */
686
+ readonly run: RunProxy;
687
+ };
688
+
689
+ /**
690
+ * @vantis/data — branded atomic modifier helpers (VAN-866, ADR-0047).
691
+ *
692
+ * Each helper returns a Symbol-branded VerbEnvelope that .modify() serializes
693
+ * into the __vantis_ops JSONB argument. JSON.stringify() ignores Symbol-keyed
694
+ * properties, so the brand never appears on the wire.
695
+ *
696
+ * Security: the server-side __vantis_modify_<t> function is the actual
697
+ * security boundary — it fully validates ops, match, and operand types.
698
+ * These helpers provide ergonomics + compile-time safety only.
699
+ *
700
+ * Wire contract (engine-generated function signature):
701
+ * api.__vantis_modify_<t>(
702
+ * __vantis_match jsonb, -- PK columns object
703
+ * __vantis_ops jsonb, -- { "<col>": { "op": "<opname>", "v"?: <operand>, "path"?: string[] } }
704
+ * __vantis_row jsonb, -- optional insert row (upsert branch)
705
+ * __vantis_guard jsonb -- optional single-column guard { "<col>": { "<cmp>": <value> } }
706
+ * ) RETURNS SETOF api.<t>
707
+ */
708
+ /** Brand symbol for verb envelopes. Symbol-keyed, ignored by JSON.stringify. */
709
+ declare const VERB_BRAND: unique symbol;
710
+ /**
711
+ * A Symbol-branded atomic modifier envelope returned by the verb helpers.
712
+ *
713
+ * The VERB_BRAND property is non-enumerable and symbol-keyed, so
714
+ * JSON.stringify() never includes it on the wire — the brand exists solely
715
+ * to distinguish a modifier from a plain object (preventing a bare
716
+ * `{op: "merge", v: {...}}` from being misread as a modifier envelope).
717
+ *
718
+ * String-keyed properties (op, v?, path?) are enumerable and are the
719
+ * wire payload. .modify() serializes them directly into __vantis_ops.
720
+ */
721
+ interface VerbEnvelope {
722
+ readonly [VERB_BRAND]: true;
723
+ /** The operation name sent to the engine (e.g. "increment", "toggle"). */
724
+ readonly op: string;
725
+ /** Operand value — absent for zero-operand ops (toggle, serverTimestamp). */
726
+ readonly v?: unknown;
727
+ /**
728
+ * JSON path for jsonSet — an array of path segments.
729
+ * Only present on envelopes returned by jsonSet().
730
+ * Sent as __vantis_ops->col->'path' alongside 'v'.
731
+ */
732
+ readonly path?: string[];
733
+ }
734
+ /**
735
+ * Phantom-branded subtype of VerbEnvelope for numeric column ops
736
+ * (increment / decrement / multiply / divide / min / max).
737
+ * The `__verb` property is optional and never-valued at runtime
738
+ * (TS-only phantom discriminant; JSON.stringify never emits it).
739
+ */
740
+ interface NumericVerb extends VerbEnvelope {
741
+ readonly __verb?: 'numeric';
742
+ }
743
+ /**
744
+ * Phantom-branded subtype of VerbEnvelope for boolean column ops (toggle).
745
+ */
746
+ interface BooleanVerb extends VerbEnvelope {
747
+ readonly __verb?: 'boolean';
748
+ }
749
+ /**
750
+ * Phantom-branded subtype of VerbEnvelope for timestamptz/date column ops
751
+ * (serverTimestamp).
752
+ */
753
+ interface TemporalVerb extends VerbEnvelope {
754
+ readonly __verb?: 'temporal';
755
+ }
756
+ /**
757
+ * Phantom-branded subtype of VerbEnvelope for text/varchar column ops (concat).
758
+ */
759
+ interface TextVerb extends VerbEnvelope {
760
+ readonly __verb?: 'text';
761
+ }
762
+ /**
763
+ * Phantom-branded subtype of VerbEnvelope for jsonb column ops
764
+ * (merge / jsonSet).
765
+ */
766
+ interface JsonbVerb extends VerbEnvelope {
767
+ readonly __verb?: 'jsonb';
768
+ }
769
+ /**
770
+ * Phantom-branded subtype of VerbEnvelope for the cross-type setOnInsert op.
771
+ * Valid on ANY non-PK column in the upsert branch.
772
+ */
773
+ interface SetOnInsertVerb extends VerbEnvelope {
774
+ readonly __verb?: 'setOnInsert';
775
+ }
776
+ /**
777
+ * Increment a numeric column by n (atomic server-side, no lost-update risk).
778
+ *
779
+ * @param n - The increment amount. bigint is serialized as a string to
780
+ * preserve precision; the server casts it to the column type.
781
+ */
782
+ declare function increment(n: number | string | bigint): NumericVerb;
783
+ /**
784
+ * Decrement a numeric column by n (atomic server-side).
785
+ *
786
+ * @param n - The decrement amount. bigint is serialized as a string.
787
+ */
788
+ declare function decrement(n: number | string | bigint): NumericVerb;
789
+ /**
790
+ * Multiply a numeric column by n (atomic server-side).
791
+ *
792
+ * @param n - The multiplier. bigint is serialized as a string.
793
+ */
794
+ declare function multiply(n: number | string | bigint): NumericVerb;
795
+ /**
796
+ * Divide a numeric column by n.
797
+ *
798
+ * Division by zero raises SQLSTATE 22012 and rolls the whole operation back
799
+ * atomically — surfaced as a typed DataError. Integer/numeric overflow raises
800
+ * 22003 and is also rolled back atomically.
801
+ *
802
+ * @param n - The divisor. bigint is serialized as a string.
803
+ */
804
+ declare function divide(n: number | string | bigint): NumericVerb;
805
+ /**
806
+ * Set a numeric field to v only if v < the current value — Mongo $min semantics
807
+ * (running minimum / lower-clamp via LEAST(current, v)).
808
+ * If the current value is NULL, the field is set to v.
809
+ *
810
+ * @param v - The candidate lower bound. bigint is serialized as a string.
811
+ */
812
+ declare function min(v: number | string | bigint): NumericVerb;
813
+ /**
814
+ * Set a numeric field to v only if v > the current value — Mongo $max semantics
815
+ * (high-water-mark / upper-clamp via GREATEST(current, v)).
816
+ * If the current value is NULL, the field is set to v.
817
+ *
818
+ * @param v - The candidate upper bound. bigint is serialized as a string.
819
+ */
820
+ declare function max(v: number | string | bigint): NumericVerb;
821
+ /**
822
+ * Flip a boolean column atomically (server reads the current value and negates it).
823
+ *
824
+ * Only valid on the UPDATE branch — toggle on the upsert INSERT seed raises a
825
+ * server-side error (there is no "current value" to flip on a new row). Use
826
+ * setOnInsert() to seed a boolean value on the upsert path.
827
+ */
828
+ declare function toggle(): BooleanVerb;
829
+ /**
830
+ * Set a timestamptz or date column to the server transaction clock
831
+ * (PostgreSQL transaction_timestamp() — consistent within the same transaction,
832
+ * independent of the client clock).
833
+ *
834
+ * Named `serverTimestamp` (not `now`) to avoid collision with the `now()`
835
+ * builtin default (a column `default` in `vantis.schema.json`). No operand — the
836
+ * server supplies the value.
837
+ */
838
+ declare function serverTimestamp(): TemporalVerb;
839
+ /**
840
+ * Append s to a text or varchar column atomically (server-side col || s).
841
+ *
842
+ * @param s - The string to append. Concatenated at the server, not the client.
843
+ */
844
+ declare function concat(s: string): TextVerb;
845
+ /**
846
+ * Atomically shallow-merge obj into a jsonb column
847
+ * (PostgreSQL `col || obj` — union of top-level keys, obj wins on duplicates).
848
+ * Does NOT recursively merge nested objects — for deep merges, use jsonSet().
849
+ *
850
+ * @param obj - The object to shallow-merge into the column.
851
+ */
852
+ declare function merge(obj: Record<string, unknown>): JsonbVerb;
853
+ /**
854
+ * Atomically set a path within a jsonb column to v
855
+ * (PostgreSQL `jsonb_set(col, path, v, create_if_missing := true)`).
856
+ *
857
+ * Creates the LEAF key at `path` if absent, but intermediate path segments
858
+ * must already exist — if an intermediate key is missing, PostgreSQL returns
859
+ * the original column value unchanged (it does not create nested structure).
860
+ * Use `merge()` first to ensure the intermediate container exists if needed.
861
+ *
862
+ * @param path - Array of path segments (string keys; integer indices as strings).
863
+ * @param v - The value to write at the path (any JSON-serializable value).
864
+ */
865
+ declare function jsonSet(path: string[], v: unknown): JsonbVerb;
866
+ /**
867
+ * On the upsert INSERT branch: set this column to v (Mongo $setOnInsert semantics).
868
+ * On the UPDATE branch: no-op — the column retains its current value.
869
+ *
870
+ * Only valid when `.modify()` is called with `opts.upsert` (the upsert branch);
871
+ * using setOnInsert without `opts.upsert` raises a server-side error.
872
+ *
873
+ * @param v - The value to set on INSERT. Must be a non-null JSON-serializable value.
874
+ * Passing `null` is a compile-time error; the engine rejects an explicit
875
+ * JSON null operand for setOnInsert at the server-side preamble.
876
+ */
877
+ declare function setOnInsert<T>(v: T extends null ? never : T): SetOnInsertVerb;
878
+
879
+ export { type BooleanVerb, type DataError, type DataResult, type Embeds, type JsonbVerb, type LimitOptions, type ModifyOptions, type NumericVerb, type OrderOptions, QueryBuilder, type Rpc, type Schema, type SchemaInsert, type SearchOptions, type SelectOptions, type SetOnInsertVerb, type TemporalVerb, type TextVerb, type UpsertOptions, type VerbEnvelope, type Verbs, concat, data, decrement, divide, increment, jsonSet, max, merge, min, multiply, serverTimestamp, setOnInsert, toggle };