@volter/twin-planetscale 0.1.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 (128) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +473 -0
  3. package/api/src/fetch.ts +50 -0
  4. package/api/src/generated/surface.gen.json +1 -0
  5. package/api/src/generated/ui.gen.json +1 -0
  6. package/api/src/index.ts +19 -0
  7. package/api/src/manifest.ts +136 -0
  8. package/api/src/screens/deploy-request.tsx +111 -0
  9. package/api/src/screens/service-tokens.tsx +141 -0
  10. package/api/src/screens/session.tsx +117 -0
  11. package/api/src/semantics/audit.ts +82 -0
  12. package/api/src/semantics/backups.ts +258 -0
  13. package/api/src/semantics/branches.ts +201 -0
  14. package/api/src/semantics/deploy-requests.ts +493 -0
  15. package/api/src/semantics/index.ts +371 -0
  16. package/api/src/semantics/shared.ts +141 -0
  17. package/api/src/semantics/time.ts +77 -0
  18. package/api/src/token-gate.ts +96 -0
  19. package/dist/api/src/fetch.d.ts +8 -0
  20. package/dist/api/src/fetch.js +51 -0
  21. package/dist/api/src/fetch.ts +50 -0
  22. package/dist/api/src/generated/surface.gen.json +1 -0
  23. package/dist/api/src/generated/ui.gen.json +1 -0
  24. package/dist/api/src/index.ts +19 -0
  25. package/dist/api/src/manifest.d.ts +2 -0
  26. package/dist/api/src/manifest.js +113 -0
  27. package/dist/api/src/manifest.ts +136 -0
  28. package/dist/api/src/screens/deploy-request.d.ts +7 -0
  29. package/dist/api/src/screens/deploy-request.js +106 -0
  30. package/dist/api/src/screens/deploy-request.tsx +111 -0
  31. package/dist/api/src/screens/service-tokens.d.ts +3 -0
  32. package/dist/api/src/screens/service-tokens.js +134 -0
  33. package/dist/api/src/screens/service-tokens.tsx +141 -0
  34. package/dist/api/src/screens/session.d.ts +11 -0
  35. package/dist/api/src/screens/session.js +108 -0
  36. package/dist/api/src/screens/session.tsx +117 -0
  37. package/dist/api/src/semantics/audit.d.ts +31 -0
  38. package/dist/api/src/semantics/audit.js +80 -0
  39. package/dist/api/src/semantics/audit.ts +82 -0
  40. package/dist/api/src/semantics/backups.d.ts +37 -0
  41. package/dist/api/src/semantics/backups.js +264 -0
  42. package/dist/api/src/semantics/backups.ts +258 -0
  43. package/dist/api/src/semantics/branches.d.ts +53 -0
  44. package/dist/api/src/semantics/branches.js +197 -0
  45. package/dist/api/src/semantics/branches.ts +201 -0
  46. package/dist/api/src/semantics/deploy-requests.d.ts +47 -0
  47. package/dist/api/src/semantics/deploy-requests.js +491 -0
  48. package/dist/api/src/semantics/deploy-requests.ts +493 -0
  49. package/dist/api/src/semantics/index.d.ts +20 -0
  50. package/dist/api/src/semantics/index.js +381 -0
  51. package/dist/api/src/semantics/index.ts +371 -0
  52. package/dist/api/src/semantics/shared.d.ts +36 -0
  53. package/dist/api/src/semantics/shared.js +132 -0
  54. package/dist/api/src/semantics/shared.ts +141 -0
  55. package/dist/api/src/semantics/time.d.ts +2 -0
  56. package/dist/api/src/semantics/time.js +81 -0
  57. package/dist/api/src/semantics/time.ts +77 -0
  58. package/dist/api/src/token-gate.d.ts +9 -0
  59. package/dist/api/src/token-gate.js +97 -0
  60. package/dist/api/src/token-gate.ts +96 -0
  61. package/dist/src/cli.d.ts +2 -0
  62. package/dist/src/cli.js +61 -0
  63. package/dist/src/generated/surface.gen.json +1 -0
  64. package/dist/src/generated/ui.gen.json +1 -0
  65. package/dist/src/index.d.ts +26 -0
  66. package/dist/src/index.js +156 -0
  67. package/dist/src/manifest.d.ts +2 -0
  68. package/dist/src/manifest.js +41 -0
  69. package/dist/src/planetscale-budget.d.ts +78 -0
  70. package/dist/src/planetscale-budget.js +305 -0
  71. package/dist/src/planetscale-capabilities.d.ts +10 -0
  72. package/dist/src/planetscale-capabilities.js +3977 -0
  73. package/dist/src/planetscale-collation-weights.gen.d.ts +4 -0
  74. package/dist/src/planetscale-collation-weights.gen.js +12 -0
  75. package/dist/src/planetscale-collation.d.ts +70 -0
  76. package/dist/src/planetscale-collation.js +391 -0
  77. package/dist/src/planetscale-conformance.d.ts +8 -0
  78. package/dist/src/planetscale-conformance.js +213 -0
  79. package/dist/src/planetscale-connector.d.ts +150 -0
  80. package/dist/src/planetscale-connector.js +532 -0
  81. package/dist/src/planetscale-deploy.d.ts +26 -0
  82. package/dist/src/planetscale-deploy.js +235 -0
  83. package/dist/src/planetscale-information-schema.d.ts +32 -0
  84. package/dist/src/planetscale-information-schema.js +299 -0
  85. package/dist/src/planetscale-mysql.d.ts +33 -0
  86. package/dist/src/planetscale-mysql.js +547 -0
  87. package/dist/src/planetscale-roles.d.ts +11 -0
  88. package/dist/src/planetscale-roles.js +60 -0
  89. package/dist/src/planetscale-row.d.ts +12 -0
  90. package/dist/src/planetscale-row.js +39 -0
  91. package/dist/src/planetscale-server.d.ts +42 -0
  92. package/dist/src/planetscale-server.js +137 -0
  93. package/dist/src/planetscale-sql.d.ts +701 -0
  94. package/dist/src/planetscale-sql.js +7167 -0
  95. package/dist/src/planetscale-store.d.ts +126 -0
  96. package/dist/src/planetscale-store.js +827 -0
  97. package/dist/src/planetscale-twin.d.ts +48 -0
  98. package/dist/src/planetscale-twin.js +290 -0
  99. package/dist/src/planetscale-values.d.ts +139 -0
  100. package/dist/src/planetscale-values.js +719 -0
  101. package/dist/src/planetscale-wire.d.ts +110 -0
  102. package/dist/src/planetscale-wire.js +188 -0
  103. package/dist/src/semantics/psdb.d.ts +18 -0
  104. package/dist/src/semantics/psdb.js +30 -0
  105. package/package.json +58 -0
  106. package/src/cli.ts +58 -0
  107. package/src/generated/surface.gen.json +1 -0
  108. package/src/generated/ui.gen.json +1 -0
  109. package/src/index.ts +267 -0
  110. package/src/manifest.ts +60 -0
  111. package/src/planetscale-budget.ts +347 -0
  112. package/src/planetscale-capabilities.ts +3862 -0
  113. package/src/planetscale-collation-weights.gen.ts +13 -0
  114. package/src/planetscale-collation.ts +378 -0
  115. package/src/planetscale-conformance.ts +237 -0
  116. package/src/planetscale-connector.ts +571 -0
  117. package/src/planetscale-deploy.ts +197 -0
  118. package/src/planetscale-information-schema.ts +322 -0
  119. package/src/planetscale-mysql.ts +339 -0
  120. package/src/planetscale-roles.ts +71 -0
  121. package/src/planetscale-row.ts +43 -0
  122. package/src/planetscale-server.ts +162 -0
  123. package/src/planetscale-sql.ts +5957 -0
  124. package/src/planetscale-store.ts +869 -0
  125. package/src/planetscale-twin.ts +338 -0
  126. package/src/planetscale-values.ts +572 -0
  127. package/src/planetscale-wire.ts +274 -0
  128. package/src/semantics/psdb.ts +57 -0
@@ -0,0 +1,701 @@
1
+ import { type Collation, type DT } from './planetscale-collation.js';
2
+ /**
3
+ * One stored cell. ALWAYS the MySQL TEXT representation of the value, or null.
4
+ *
5
+ * This is not a shortcut — it is the wire's own model. psdb packs every column of every row as raw
6
+ * bytes with a per-column byte length (`row.lengths`/`row.values`), and the CLIENT casts those
7
+ * bytes by the field's declared type (dist/cast.js: INT32 → parseInt, FLOAT64 → parseFloat, JSON →
8
+ * JSON.parse, INT64 → left as a string, BLOB → Uint8Array). Storing text plus the column's type is
9
+ * therefore isomorphic to what the vendor stores as far as this protocol can ever observe.
10
+ */
11
+ export type Cell = string | null;
12
+ /** Vitess `query.Type` enum names, as protojson spells them — the values `Field.type` carries. */
13
+ export declare const VITESS_TYPES: readonly ["NULL", "INT8", "UINT8", "INT16", "UINT16", "INT24", "UINT24", "INT32", "UINT32", "INT64", "UINT64", "FLOAT32", "FLOAT64", "TIMESTAMP", "DATE", "TIME", "DATETIME", "YEAR", "DECIMAL", "TEXT", "BLOB", "VARCHAR", "VARBINARY", "CHAR", "BINARY", "BIT", "ENUM", "SET", "GEOMETRY", "JSON", "EXPRESSION"];
14
+ export type VitessType = (typeof VITESS_TYPES)[number];
15
+ /** MySQL's binary collation id. `@planetscale/database`'s `cast` treats charset 63 as BINARY and
16
+ * returns a `Uint8Array` for it (dist/cast.js `isBinary`), so getting this wrong changes the
17
+ * JavaScript type an integrator receives. utf8mb4_general_ci is 45, which is what a text column
18
+ * on a PlanetScale database reports. */
19
+ export declare const BINARY_CHARSET = 63;
20
+ export declare const UTF8MB4_CHARSET = 45;
21
+ /** MySQL column flags, as `Field.flags` reports them (mysql_com.h). Only the ones this engine
22
+ * actually sets are named; the rest are deliberately absent rather than guessed. */
23
+ export declare const FLAG_NOT_NULL = 1;
24
+ export declare const FLAG_PRI_KEY = 2;
25
+ export declare const FLAG_UNIQUE_KEY = 4;
26
+ export declare const FLAG_BLOB = 16;
27
+ export declare const FLAG_UNSIGNED = 32;
28
+ export declare const FLAG_BINARY = 128;
29
+ export declare const FLAG_AUTO_INCREMENT = 512;
30
+ /** The published PlanetScale system limits this engine ENFORCES, with the page they came from.
31
+ * Live-read 2026-08-31 from planetscale.com/docs/reference/planetscale-system-limits. */
32
+ export declare const PLANETSCALE_LIMITS: {
33
+ readonly source: "https://planetscale.com/docs/reference/planetscale-system-limits (read 2026-08-31)";
34
+ /** "Per-query rows returned, updated, or deleted: 100k". */
35
+ readonly maxRowsPerQuery: 100000;
36
+ /** "Tables per database schema (including views): 2048". */
37
+ readonly maxTablesPerSchema: 2048;
38
+ /** "Columns per table: 1017". */
39
+ readonly maxColumnsPerTable: 1017;
40
+ };
41
+ /** The version string both wires report (`VERSION()`, `@@version`, the native handshake). */
42
+ export declare const SERVER_VERSION = "8.0.0-volter-twin";
43
+ /** MySQL 8's default sql_mode (manual 7.1.11), which PlanetScale runs with and this engine
44
+ * enforces: strict writes, no zero dates, division by zero an error on write, full GROUP BY. */
45
+ export declare const SQL_MODE = "ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION";
46
+ /**
47
+ * The system variables a client can read (`SELECT @@name`, `@@session.name`), with the values this
48
+ * engine's behaviour actually has: `lower_case_table_names` is 2 because table names keep their
49
+ * declared lettercase and compare case-insensitively; `transaction_isolation` is READ-COMMITTED
50
+ * because every statement reads the latest committed rows (no REPEATABLE READ snapshot). Any other
51
+ * name is MySQL's errno 1193.
52
+ */
53
+ export declare const SYSTEM_VARIABLES: Record<string, string | number>;
54
+ /**
55
+ * A query failure, carrying BOTH halves of what PlanetScale actually returns: the vtrpc code that
56
+ * lands in `ExecuteResponse.error.code` and the MySQL errno/sqlstate that MySQL clients key on.
57
+ *
58
+ * WHAT IS DELIBERATELY NOT FABRICATED: a real PlanetScale error message is prefixed with the
59
+ * vttablet routing context — `target: <keyspace>.<shard>.<tablet_type>: vttablet: rpc error: code =
60
+ * … desc = ` — which encodes the keyspace, shard and tablet type of the deployment that answered.
61
+ * A local twin has no keyspace or shard, so inventing that prefix would be asserting a fact about a
62
+ * cluster that does not exist. The MySQL half (message text, `(errno N) (sqlstate S)`) IS real and
63
+ * is reproduced verbatim; `planetscale.errors.vttablet_prefix` files the omission as a todo.
64
+ */
65
+ export declare class SqlError extends Error {
66
+ /** the vtrpc code, protojson-spelled (`NOT_FOUND`, `INVALID_ARGUMENT`, `ALREADY_EXISTS`, …). */
67
+ readonly code: string;
68
+ readonly errno: number;
69
+ readonly sqlState: string;
70
+ constructor(message: string,
71
+ /** the vtrpc code, protojson-spelled (`NOT_FOUND`, `INVALID_ARGUMENT`, `ALREADY_EXISTS`, …). */
72
+ code: string, errno: number, sqlState: string);
73
+ }
74
+ export declare const parseError: (position: number, near: string) => SqlError;
75
+ /**
76
+ * An operation this twin does not model. Vitess's own refusal for a statement its planner cannot
77
+ * build — the message begins `unsupported: ` and the vtrpc code is UNIMPLEMENTED. A twin MUST take
78
+ * this path rather than answering an empty result set: a fabricated `[]` reads to the caller as
79
+ * "the query ran and matched nothing", which is the exact fake-success the bar forbids.
80
+ */
81
+ export declare const unsupported: (what: string) => SqlError;
82
+ export type ColumnDef = {
83
+ name: string;
84
+ /** the base type, uppercased and length-stripped: `VARCHAR`, `BIGINT`, `JSON`, … */
85
+ dataType: string;
86
+ /** the full MySQL rendering, as `Field.columnType` reports it: `varchar(255)`, `bigint unsigned`. */
87
+ columnType: string;
88
+ length: number | null;
89
+ unsigned: boolean;
90
+ nullable: boolean;
91
+ /** `undefined` = the column declared no DEFAULT (distinct from `DEFAULT NULL`, which is `null`). */
92
+ defaultValue?: Cell;
93
+ /** Was the DEFAULT written as a hex literal (`DEFAULT x'00ff'`)? Then it is already bytes and
94
+ * `execCreateTable` must not UTF-8-encode it. */
95
+ defaultIsBinaryLiteral?: boolean;
96
+ /** `DEFAULT CURRENT_TIMESTAMP[(fsp)]`: the fractional-seconds precision. The value is read from
97
+ * the World clock (`ExecOptions.now`) when a row is inserted, never from the wall clock. */
98
+ defaultCurrentTimestamp?: number;
99
+ /** An expression default, `DEFAULT (expr)` (MySQL 8.0.13+): its text as COLUMN_DEFAULT reports it,
100
+ * with the literal value it evaluates to held in `defaultValue`. */
101
+ defaultExpression?: string;
102
+ autoIncrement: boolean;
103
+ primaryKey: boolean;
104
+ unique: boolean;
105
+ /** A character column's character set and collation, resolved at CREATE TABLE from the column's
106
+ * own `CHARACTER SET`/`COLLATE`/`BINARY`, else the table's, else the database default. */
107
+ charset?: string;
108
+ collation?: string;
109
+ /** Parse-time only: the column's `BINARY` attribute (its charset's _bin collation). */
110
+ binaryAttribute?: boolean;
111
+ /** Parse-time only: how the DEFAULT was written — a number literal, a string literal, or a
112
+ * parenthesised expression — which decides how CREATE TABLE converts it to the column's type. */
113
+ defaultLiteral?: 'number' | 'string' | 'expression';
114
+ };
115
+ /** One column of an index: its name, an optional prefix length and its direction. */
116
+ export type IndexPart = {
117
+ name: string;
118
+ length?: number;
119
+ desc?: boolean;
120
+ };
121
+ /** A non-unique secondary index. It changes access paths, not results — this engine has one access
122
+ * path (a scan) — but it is part of the catalog INFORMATION_SCHEMA.STATISTICS reports. */
123
+ export type IndexDef = {
124
+ name: string;
125
+ columns: IndexPart[];
126
+ type: 'BTREE' | 'FULLTEXT';
127
+ };
128
+ export type TableDef = {
129
+ name: string;
130
+ columns: ColumnDef[];
131
+ /** column names forming the PRIMARY KEY, in declaration order. Empty = no primary key. */
132
+ primaryKey: string[];
133
+ /** each UNIQUE constraint as `{ name, columns }` — the name is what errno 1062 reports. `parts`
134
+ * keeps each column's prefix length and direction for the catalog. */
135
+ uniques: Array<{
136
+ name: string;
137
+ columns: string[];
138
+ parts?: IndexPart[];
139
+ }>;
140
+ /** non-unique secondary and FULLTEXT indexes, in declaration order. */
141
+ indexes?: IndexDef[];
142
+ /** the table's declared default character set and collation (`DEFAULT CHARSET=… COLLATE=…`). */
143
+ charset?: string;
144
+ collation?: string;
145
+ /** the persisted AUTO_INCREMENT watermark. InnoDB 8.0 persists this across restarts, and so does
146
+ * this engine — which is also what stops a delete-then-insert from reusing a retired id. */
147
+ autoIncrement: number;
148
+ };
149
+ export type RowRec = {
150
+ /** the engine's internal row identity — never a column, never exposed on the wire. Minted from
151
+ * the MAX rowid present in the table (see `nextRowId`), never from a row COUNT: a count-mint
152
+ * collides the instant a row above the count exists, which is what silently clobbers a
153
+ * connector-pulled row. */
154
+ rowid: number;
155
+ cells: Record<string, Cell>;
156
+ };
157
+ /** The in-memory image one statement executes against. */
158
+ export type Database = {
159
+ /** the schema/database name — what `Field.database` reports and what errno 1146 names. */
160
+ name: string;
161
+ tables: Map<string, TableDef>;
162
+ rows: Map<string, RowRec[]>;
163
+ };
164
+ export declare const emptyDatabase: (name: string) => Database;
165
+ /** MySQL identifiers are case-insensitive on the platforms PlanetScale runs (lower_case_table_names
166
+ * = 1), so tables are keyed lowercase while the DECLARED spelling is preserved for display. */
167
+ export declare const tableKey: (name: string) => string;
168
+ export declare function findTable(db: Database, name: string): TableDef;
169
+ export declare function tableRows(db: Database, name: string): RowRec[];
170
+ /**
171
+ * Row ids at or above this belong to the CONNECTOR: a pulled row's id is derived from the real
172
+ * row's own identity (see `planetscale-connector.ts`, `pulledRowId`), not from its position in a
173
+ * `SELECT`, so a re-pull re-derives the same subject and a new real row gets a new one.
174
+ *
175
+ * The band exists because the two minting rules must never meet. §9 round one reproduced the
176
+ * collision end-to-end when they shared a namespace: pull 1 → rowids 1,2; a local INSERT →
177
+ * `nextRowId` = 3; pull 2, now returning a third real row, positionally assigned it rowid 3 as
178
+ * well — so it landed on the LOCAL row's subject, the real row was silently dropped, and `syncPull`
179
+ * reported deltas that changed nothing. Disjoint bands make that unrepresentable rather than
180
+ * unlikely.
181
+ */
182
+ export declare const PULLED_ROWID_BASE: number;
183
+ /**
184
+ * The next LOCAL row id: MAX(existing local) + 1 — entropy-free, and collision-free in both
185
+ * directions. Pulled ids are skipped (they live in their own band, above), so a local mint can
186
+ * never land on a pulled row and a pull can never land on a local one.
187
+ *
188
+ * Never a row COUNT: a count-mint collides the moment a row above the count exists.
189
+ */
190
+ export declare function nextRowId(rows: RowRec[]): number;
191
+ /**
192
+ * The mapping the whole protocol rests on. `@planetscale/database`'s `cast` (dist/cast.js) branches
193
+ * ONLY on `field.type` and `field.charset`, so this table decides what JavaScript type an
194
+ * integrator receives: INT32 → `number`, INT64 → `string` (bigint-safe, deliberately NOT cast),
195
+ * FLOAT64 → `number`, JSON → a parsed object, DATETIME/DATE/TIME → the raw string, anything with
196
+ * charset 63 → `Uint8Array`, everything else → a decoded UTF-8 string.
197
+ */
198
+ export declare function vitessTypeFor(column: {
199
+ dataType: string;
200
+ unsigned: boolean;
201
+ }): VitessType;
202
+ export declare const isNumericVitessType: (t: VitessType) => boolean;
203
+ /**
204
+ * Does a column of this vitess type report charset 63?
205
+ *
206
+ * TWO families do, and conflating them was a §9 round-one defect: the NUMERIC types report binary
207
+ * because MySQL has no collation for a number, and the BLOB/BINARY/VARBINARY family reports it
208
+ * because the value genuinely IS bytes. `@planetscale/database`'s `cast` branches on
209
+ * `field.charset === 63` alone (dist/cast.js, `isBinary`), so a VARBINARY expression served with
210
+ * charset 45 comes back to the caller as a mojibake STRING instead of a `Uint8Array` — and its
211
+ * payload is UTF-8-expanded, so the byte lengths are wrong too.
212
+ */
213
+ export declare const isBinaryCharsetType: (t: VitessType) => boolean;
214
+ /**
215
+ * UTF-8-encode a string into this engine's byte-string form (one character per byte).
216
+ *
217
+ * A BINARY column's cell is always held this way, because that is what the wire packs verbatim and
218
+ * what `LENGTH()` counts. Text arriving for such a column is encoded HERE, at write time.
219
+ */
220
+ export declare function toByteString(value: string): string;
221
+ /** The wire-shape of one result column. Mirrors `@planetscale/database`'s exported `Field`. */
222
+ export type Field = {
223
+ name: string;
224
+ type: VitessType;
225
+ table?: string;
226
+ orgTable?: string;
227
+ database?: string;
228
+ orgName?: string;
229
+ columnLength?: number;
230
+ charset?: number;
231
+ flags?: number;
232
+ columnType?: string;
233
+ /** Engine-internal, never on the wire: the collation a character result column compares under
234
+ * (a derived table's column or an IN subquery's operand reads it back). */
235
+ collation?: string;
236
+ };
237
+ export declare function fieldForColumn(db: Database, table: TableDef, column: ColumnDef, alias?: string): Field;
238
+ type TokKind = 'ident' | 'quoted-ident' | 'string' | 'number' | 'hex' | 'punct' | 'eof';
239
+ type Token = {
240
+ kind: TokKind;
241
+ value: string;
242
+ pos: number;
243
+ };
244
+ /** `noBackslashEscapes`: the session's NO_BACKSLASH_ESCAPES SQL mode ("Disable the use of the backslash character (\\) as
245
+ * an escape character within strings … backslash becomes an ordinary character like any other", manual 7.1.11). */
246
+ export declare function tokenize(sql: string, noBackslashEscapes?: boolean): Token[];
247
+ export type Expr =
248
+ /** `collation` is an introducer's (`_utf8mb4'x'`); a plain string literal takes the connection's. */
249
+ {
250
+ kind: 'literal';
251
+ value: Cell;
252
+ hint: VitessType;
253
+ collation?: string;
254
+ hex?: true;
255
+ }
256
+ /** `expr COLLATE name`: an EXPLICIT collation for the comparison it takes part in. */
257
+ | {
258
+ kind: 'collate';
259
+ arg: Expr;
260
+ collation: string;
261
+ } | {
262
+ kind: 'column';
263
+ table?: string;
264
+ name: string;
265
+ } | {
266
+ kind: 'star';
267
+ table?: string;
268
+ } | {
269
+ kind: 'unary';
270
+ op: 'NOT' | '-' | 'BINARY' | '~';
271
+ arg: Expr;
272
+ }
273
+ /** `x IS [NOT] TRUE | FALSE | UNKNOWN`: a truth test that is never NULL (manual 14.4.2). */
274
+ | {
275
+ kind: 'is';
276
+ arg: Expr;
277
+ value: 'TRUE' | 'FALSE' | 'UNKNOWN';
278
+ negated: boolean;
279
+ }
280
+ /** `x MEMBER OF (json_array)` (8.0.17+). */
281
+ | {
282
+ kind: 'member';
283
+ arg: Expr;
284
+ doc: Expr;
285
+ }
286
+ /** `CAST(x AS type)` / `CONVERT(x, type)` / `CONVERT(x USING charset)`: `to` is the target type. */
287
+ | {
288
+ kind: 'cast';
289
+ arg: Expr;
290
+ to: ColumnDef;
291
+ atTimeZone?: string;
292
+ } | {
293
+ kind: 'binary';
294
+ op: string;
295
+ left: Expr;
296
+ right: Expr;
297
+ } | {
298
+ kind: 'isnull';
299
+ arg: Expr;
300
+ negated: boolean;
301
+ }
302
+ /** `x IN (a, b)`, or `x IN (SELECT …)` when `subquery` is set (then `list` is empty). */
303
+ | {
304
+ kind: 'in';
305
+ arg: Expr;
306
+ list: Expr[];
307
+ negated: boolean;
308
+ subquery?: SelectStatement;
309
+ run?: SubqueryRun;
310
+ } | {
311
+ kind: 'like';
312
+ arg: Expr;
313
+ pattern: Expr;
314
+ negated: boolean;
315
+ escape?: Expr;
316
+ } | {
317
+ kind: 'between';
318
+ arg: Expr;
319
+ low: Expr;
320
+ high: Expr;
321
+ negated: boolean;
322
+ } | {
323
+ kind: 'call';
324
+ name: string;
325
+ args: Expr[];
326
+ star: boolean;
327
+ distinct?: boolean;
328
+ order?: OrderTerm[];
329
+ separator?: string;
330
+ }
331
+ /** A row constructor, `(a, b)`, compared element-wise (`=`, `<`, `IN`). */
332
+ | {
333
+ kind: 'row';
334
+ items: Expr[];
335
+ }
336
+ /** A scalar subquery, `(SELECT …)`. `run` is attached when the statement is bound for execution. */
337
+ | {
338
+ kind: 'subquery';
339
+ select: SelectStatement;
340
+ run?: SubqueryRun;
341
+ } | {
342
+ kind: 'exists';
343
+ select: SelectStatement;
344
+ run?: SubqueryRun;
345
+ } | {
346
+ kind: 'case';
347
+ operand?: Expr;
348
+ whens: Array<{
349
+ when: Expr;
350
+ then: Expr;
351
+ }>;
352
+ otherwise?: Expr;
353
+ }
354
+ /** A column BOUND to one source of the executing statement: `src` indexes the row tuple and
355
+ * `def` carries the column's declared type. Produced by binding, never by the parser. */
356
+ | {
357
+ kind: 'slot';
358
+ src: number;
359
+ col: string;
360
+ def: ColumnDef;
361
+ };
362
+ /** A bound subquery's evaluator: its result set, given the (outer) row it is evaluated for. */
363
+ export type SubqueryRun = ((row: EvalRow) => {
364
+ fields: Field[];
365
+ rows: Cell[][];
366
+ }) & {
367
+ /** the result columns, typed statically: known before (and without) running it. */
368
+ fields: () => Field[];
369
+ };
370
+ export type SelectItem = {
371
+ expr: Expr;
372
+ alias?: string;
373
+ text: string;
374
+ };
375
+ export type OrderTerm = {
376
+ expr: Expr;
377
+ desc: boolean;
378
+ };
379
+ /** One table in FROM: a stored table (or INFORMATION_SCHEMA view), or a DERIVED TABLE, a
380
+ * parenthesised subquery MySQL requires an alias for (errno 1248), materialised once and then read
381
+ * exactly as a table is. */
382
+ export type TableFactor = {
383
+ table: string;
384
+ schema?: string;
385
+ alias?: string;
386
+ } | {
387
+ derived: SelectStatement;
388
+ alias: string;
389
+ };
390
+ /** A joined table: `[INNER|CROSS] JOIN t [ON …]` or `LEFT [OUTER] JOIN t ON …`. */
391
+ export type JoinClause = {
392
+ kind: 'inner' | 'left';
393
+ factor: TableFactor;
394
+ on?: Expr;
395
+ };
396
+ /** One comma-separated FROM item: a table factor and the joins chained onto it. */
397
+ export type FromItem = {
398
+ factor: TableFactor;
399
+ joins: JoinClause[];
400
+ };
401
+ export type SelectStatement = {
402
+ kind: 'select';
403
+ items: SelectItem[];
404
+ distinct?: boolean;
405
+ from?: FromItem[];
406
+ where?: Expr;
407
+ groupBy?: Expr[];
408
+ having?: Expr;
409
+ order: OrderTerm[];
410
+ limit?: number;
411
+ offset?: number;
412
+ /** `FOR UPDATE` / `FOR SHARE` / `LOCK IN SHARE MODE`: a locking read. */
413
+ lock?: 'update' | 'share';
414
+ };
415
+ export type IsolationLevel = 'READ UNCOMMITTED' | 'READ COMMITTED' | 'REPEATABLE READ' | 'SERIALIZABLE';
416
+ export type Statement = SelectStatement
417
+ /** `alias`/`aliasColumns`: the new row's name in ON DUPLICATE KEY UPDATE (`VALUES … AS new [(a, b)]`, 8.0.19+). */
418
+ | {
419
+ kind: 'insert';
420
+ table: string;
421
+ columns: string[];
422
+ rows: Expr[][];
423
+ ignore: boolean;
424
+ onDuplicate?: Array<{
425
+ column: string;
426
+ value: Expr;
427
+ }>;
428
+ alias?: string;
429
+ aliasColumns?: string[];
430
+ } | {
431
+ kind: 'update';
432
+ table: string;
433
+ alias?: string;
434
+ set: Array<{
435
+ table?: string;
436
+ column: string;
437
+ value: Expr;
438
+ }>;
439
+ where?: Expr;
440
+ order: OrderTerm[];
441
+ limit?: number;
442
+ }
443
+ /** The multiple-table form: `UPDATE table_references SET [t.]col = expr, … [WHERE …]` (execUpdateMulti). */
444
+ | {
445
+ kind: 'update-multi';
446
+ from: FromItem[];
447
+ set: Array<{
448
+ table?: string;
449
+ column: string;
450
+ value: Expr;
451
+ }>;
452
+ where?: Expr;
453
+ } | {
454
+ kind: 'delete';
455
+ table: string;
456
+ where?: Expr;
457
+ order: OrderTerm[];
458
+ limit?: number;
459
+ } | {
460
+ kind: 'create-table';
461
+ table: string;
462
+ ifNotExists: boolean;
463
+ columns: ColumnDef[];
464
+ primaryKey: string[];
465
+ uniques: TableDef['uniques'];
466
+ indexes: IndexDef[];
467
+ charset?: string;
468
+ collation?: string;
469
+ } | {
470
+ kind: 'drop-table';
471
+ table: string;
472
+ ifExists: boolean;
473
+ } | {
474
+ kind: 'create-table-select';
475
+ table: string;
476
+ ifNotExists: boolean;
477
+ select: SelectStatement;
478
+ }
479
+ /** `ALTER TABLE t ADD [COLUMN] col_def [FIRST | AFTER c], …`: the one alteration this engine models. */
480
+ | {
481
+ kind: 'alter-add-columns';
482
+ table: string;
483
+ adds: Array<{
484
+ column: ColumnDef;
485
+ first?: boolean;
486
+ after?: string;
487
+ }>;
488
+ }
489
+ /** `ALTER TABLE t DROP [COLUMN] c, …`: a statement of drops only. */
490
+ | {
491
+ kind: 'alter-drop-columns';
492
+ table: string;
493
+ drops: string[];
494
+ } | {
495
+ kind: 'truncate';
496
+ table: string;
497
+ } | {
498
+ kind: 'show-tables';
499
+ } | {
500
+ kind: 'show-warnings';
501
+ } | {
502
+ kind: 'show-create-table';
503
+ table: string;
504
+ } | {
505
+ kind: 'describe';
506
+ table: string;
507
+ }
508
+ /** `SET [SESSION] TRANSACTION ISOLATION LEVEL …` / `SET [SESSION] TRANSACTION READ ONLY|WRITE`. */
509
+ | {
510
+ kind: 'set-transaction';
511
+ isolation?: IsolationLevel;
512
+ access?: 'READ WRITE' | 'READ ONLY';
513
+ scope: 'next' | 'session';
514
+ }
515
+ /** `SET @var := expr, …` and `SET [SESSION] sql_mode = expr`: the session's user variables and its SQL mode. */
516
+ | {
517
+ kind: 'set-vars';
518
+ user: Array<{
519
+ name: string;
520
+ value: Expr;
521
+ }>;
522
+ sqlMode?: Expr;
523
+ timeZone?: Expr;
524
+ names?: {
525
+ charset: string;
526
+ collation?: string;
527
+ };
528
+ } | {
529
+ kind: 'begin';
530
+ access?: 'READ WRITE' | 'READ ONLY';
531
+ } | {
532
+ kind: 'commit';
533
+ } | {
534
+ kind: 'rollback';
535
+ };
536
+ /** A stored table's character columns with their collations filled in from the table's (for a
537
+ * catalog written before columns carried their own). Lenient: an unrecognised stored name leaves
538
+ * the column to the database default rather than failing the whole image. */
539
+ export declare function withColumnCollations(table: TableDef): TableDef;
540
+ export declare function parseStatement(sql: string, sqlMode?: string): Statement;
541
+ /**
542
+ * Is this expression's RESULT binary — i.e. bytes rather than text?
543
+ *
544
+ * MySQL propagates binariness through an expression: `CONCAT(binary_col, …)` is binary, `UPPER` on
545
+ * a binary value is a no-op that stays binary, and a hex literal is binary. This ONE predicate
546
+ * governs storage (a source already in bytes is not re-encoded: `UPDATE b SET p = p` must not
547
+ * double the value), the result type and charset of an expression (charset 63, so the client
548
+ * returns a `Uint8Array`), and LENGTH / CHAR_LENGTH (bytes are counted, not re-encoded).
549
+ *
550
+ * `table` may be undefined (the constant-row `SELECT … FROM dual` path), where no column exists.
551
+ */
552
+ export declare function staticBinary(e: Expr | undefined, table: TableDef | undefined): boolean;
553
+ /** A row under evaluation: the table's cells, plus the schema needed to type a comparison. A
554
+ * statement bound over several sources (joins, subqueries) reads its `slot` columns from `tuple`,
555
+ * one cell record per source, where a missing record is a LEFT JOIN's null extension. */
556
+ export type EvalRow = {
557
+ cells: Record<string, Cell>;
558
+ table?: TableDef;
559
+ database?: string;
560
+ tuple?: Tuple;
561
+ /** Evaluating a value an INSERT or UPDATE stores: under strict mode with ERROR_FOR_DIVISION_BY_ZERO
562
+ * (MySQL 8's default sql_mode) a division by zero there is errno 1365, not NULL. */
563
+ write?: boolean;
564
+ /** ON DUPLICATE KEY UPDATE: the row the INSERT proposed, read by `VALUES(col)` and through the
565
+ * row alias (`AS new` → `new.col`, or its column aliases, `AS new (m, n)` → `m`). */
566
+ values?: Record<string, Cell>;
567
+ valuesAlias?: string;
568
+ /** lowercased column alias → the table column it names (only with `AS new (m, n)`). */
569
+ valuesNames?: Map<string, string>;
570
+ /** The World clock's instant for the statement, which the clock functions read (INSERT values). */
571
+ now?: string;
572
+ };
573
+ export type Tuple = Array<Record<string, Cell> | undefined>;
574
+ /** MySQL `LIKE`: `%` = any sequence, `_` = any single character, `\` escapes both, every other
575
+ * character matched per character under `collation` (default: the connection's, utf8mb4_0900_ai_ci). */
576
+ export declare function likeMatch(value: string, pattern: string, collation?: Collation): boolean;
577
+ type Cls = 'int' | 'decimal' | 'double' | 'string' | 'binary' | 'temporal' | 'json' | 'null';
578
+ /** An operand of a comparison: its type, the class it compares in, and (for strings) the
579
+ * collation and derivation it brings. */
580
+ type Operand = {
581
+ cls: Cls;
582
+ dt: DT;
583
+ type: ColumnDef | null;
584
+ };
585
+ /** The key a UNIQUE/PRIMARY KEY value of `column` is enforced (and locked) under: two values
586
+ * collide exactly when the column's collation calls them equal (MySQL errno 1062). */
587
+ export declare function uniqueKeyPart(column: ColumnDef | undefined, value: string): string;
588
+ export declare function evalExpr(e: Expr, row: EvalRow): Cell;
589
+ /** The context of a value being stored: MySQL's messages name the column and the row. */
590
+ export type StoreContext = {
591
+ /** strict mode (MySQL 8's default STRICT_TRANS_TABLES): an invalid or out-of-range value is an
592
+ * error; without it (INSERT IGNORE) the value is adjusted to the nearest valid one. */
593
+ strict: boolean;
594
+ /** 1-based row number within the statement, as the messages print it. */
595
+ row: number;
596
+ table: string;
597
+ /** the World clock's instant (a TIME stored into a DATETIME takes today's date). */
598
+ now?: string;
599
+ };
600
+ /** The value a NOT NULL column takes when a non-strict write (INSERT IGNORE) supplies NULL: its
601
+ * type's implicit default (manual 13.6 "Data Type Default Values"). */
602
+ export declare function implicitDefault(col: ColumnDef): string;
603
+ /**
604
+ * Convert a value to `col`'s type for storage, as MySQL 8 does under its default sql_mode
605
+ * (STRICT_TRANS_TABLES, NO_ZERO_IN_DATE, NO_ZERO_DATE; manual 7.1.11 "Server SQL Modes", 13
606
+ * "Data Types"): integers rounded and range-checked (1264), strings that are not numbers refused
607
+ * (1366, or 1265 when only a prefix is numeric), DECIMAL rounded to its scale and padded, DOUBLE
608
+ * and FLOAT stored at their precision, temporals read from any accepted spelling and stored
609
+ * canonically at the column's fsp (1292 for an unreadable or zero date), JSON validated and stored
610
+ * normalized (3140), strings and binary strings checked against their length (1406), ENUM/SET
611
+ * checked against their members (1265). Without strict mode the value is adjusted instead.
612
+ */
613
+ export declare function storeValue(col: ColumnDef, value: Cell, src: Operand, c: StoreContext): Cell;
614
+ /** What one statement produced, in engine terms. `planetscale-wire.ts` turns this into protojson. */
615
+ export type QueryOutcome = {
616
+ fields: Field[];
617
+ rows: Cell[][];
618
+ rowsAffected: number;
619
+ insertId: number;
620
+ /** subject writes the caller must persist. Empty for a pure read. */
621
+ writes: Write[];
622
+ /** the transaction verb this statement is, if any — the store owns the session bookkeeping. */
623
+ transaction?: 'begin' | 'commit' | 'rollback';
624
+ /** START TRANSACTION's access mode, or SET TRANSACTION's characteristics, for the store to hold. */
625
+ access?: 'READ WRITE' | 'READ ONLY';
626
+ setTransaction?: {
627
+ scope: 'next' | 'session';
628
+ access?: 'READ WRITE' | 'READ ONLY';
629
+ isolation?: IsolationLevel;
630
+ };
631
+ /** the stored rows a locking read (`FOR UPDATE` / `FOR SHARE`) returned; the store locks them. */
632
+ locked?: Array<{
633
+ table: string;
634
+ rowid: number;
635
+ }>;
636
+ /** the session's variables after a SET of them, for the caller to carry in the session it answers */
637
+ vars?: SessionVars;
638
+ };
639
+ export type Write = {
640
+ kind: 'table';
641
+ table: TableDef;
642
+ } | {
643
+ kind: 'drop-table';
644
+ table: string;
645
+ } | {
646
+ kind: 'row';
647
+ table: string;
648
+ row: RowRec;
649
+ } | {
650
+ kind: 'delete-row';
651
+ table: string;
652
+ rowid: number;
653
+ };
654
+ export type ExecOptions = {
655
+ /** `readOnly` twins refuse every write with MySQL's own errno 1290 — never a silent no-op. */
656
+ readOnly?: boolean;
657
+ /** The World clock's instant for this statement (ISO-8601). `DEFAULT CURRENT_TIMESTAMP` reads
658
+ * it; without it such a default is refused rather than taken from the wall clock. */
659
+ now?: string;
660
+ /** The statement runs in a READ ONLY transaction: a write is MySQL's errno 1792. */
661
+ transactionReadOnly?: boolean;
662
+ /** The session's user variables and SQL mode, as the client's session carries them (SessionVars). */
663
+ vars?: SessionVars;
664
+ };
665
+ /** A user variable's value and the type it holds (manual 11.4: "User variables can be assigned a value from a limited set
666
+ * of data types: integer, decimal, floating-point, binary or nonbinary string, or NULL value"), as a Vitess BindVariable
667
+ * types it. */
668
+ export type UserVariable = {
669
+ value: Cell;
670
+ type: VitessType;
671
+ };
672
+ /** What a session holds between statements: its user variables (by lowercased name; manual 11.4: "User variable names are
673
+ * not case-sensitive") and its sql_mode, when it set one. */
674
+ export type SessionVars = {
675
+ user: Record<string, UserVariable>;
676
+ sqlMode?: string;
677
+ collationConnection?: string;
678
+ timeZone?: string;
679
+ warnings?: Array<{
680
+ level: string;
681
+ code: number;
682
+ message: string;
683
+ }>;
684
+ };
685
+ export declare function execStatement(db: Database, stmt: Statement, opts?: ExecOptions): QueryOutcome;
686
+ export type SelectPlan = {
687
+ correlated: boolean;
688
+ /** the result columns, typed statically from the select list (no row is read to type them). */
689
+ fields: () => Field[];
690
+ execute: (outer: Tuple | undefined) => {
691
+ fields: Field[];
692
+ rows: Cell[][];
693
+ locked: Array<{
694
+ table: string;
695
+ rowid: number;
696
+ }>;
697
+ };
698
+ };
699
+ /** Parse and execute in one step — the entry point the store and every test use. */
700
+ export declare function runSql(db: Database, sql: string, opts?: ExecOptions): QueryOutcome;
701
+ export {};