orez 0.2.20 → 0.2.25

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 (58) hide show
  1. package/dist/browser.d.ts +5 -0
  2. package/dist/browser.d.ts.map +1 -1
  3. package/dist/browser.js +1 -0
  4. package/dist/browser.js.map +1 -1
  5. package/dist/cf-do/test-protocol.d.ts +11 -0
  6. package/dist/cf-do/test-protocol.d.ts.map +1 -0
  7. package/dist/cf-do/test-protocol.js +137 -0
  8. package/dist/cf-do/test-protocol.js.map +1 -0
  9. package/dist/cf-do/worker.d.ts +65 -0
  10. package/dist/cf-do/worker.d.ts.map +1 -0
  11. package/dist/cf-do/worker.js +440 -0
  12. package/dist/cf-do/worker.js.map +1 -0
  13. package/dist/config.d.ts +4 -0
  14. package/dist/config.d.ts.map +1 -1
  15. package/dist/config.js +1 -0
  16. package/dist/config.js.map +1 -1
  17. package/dist/index.d.ts +2 -3
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +60 -28
  20. package/dist/index.js.map +1 -1
  21. package/dist/pg-proxy-do-backend.d.ts +49 -0
  22. package/dist/pg-proxy-do-backend.d.ts.map +1 -0
  23. package/dist/pg-proxy-do-backend.js +713 -0
  24. package/dist/pg-proxy-do-backend.js.map +1 -0
  25. package/dist/pglite-ipc.d.ts +3 -0
  26. package/dist/pglite-ipc.d.ts.map +1 -1
  27. package/dist/pglite-ipc.js +34 -12
  28. package/dist/pglite-ipc.js.map +1 -1
  29. package/dist/pglite-web-proxy.d.ts +3 -0
  30. package/dist/pglite-web-proxy.d.ts.map +1 -1
  31. package/dist/pglite-web-proxy.js +50 -7
  32. package/dist/pglite-web-proxy.js.map +1 -1
  33. package/dist/query-rewrites.d.ts +2 -0
  34. package/dist/query-rewrites.d.ts.map +1 -0
  35. package/dist/query-rewrites.js +140 -0
  36. package/dist/query-rewrites.js.map +1 -0
  37. package/dist/worker/browser-admin.d.ts +13 -0
  38. package/dist/worker/browser-admin.d.ts.map +1 -0
  39. package/dist/worker/browser-admin.js +33 -0
  40. package/dist/worker/browser-admin.js.map +1 -0
  41. package/dist/worker/browser-embed.d.ts +12 -12
  42. package/dist/worker/browser-embed.d.ts.map +1 -1
  43. package/dist/worker/browser-embed.js +7 -0
  44. package/dist/worker/browser-embed.js.map +1 -1
  45. package/package.json +2 -2
  46. package/src/browser.ts +7 -0
  47. package/src/config.ts +5 -0
  48. package/src/index.ts +66 -33
  49. package/src/pg-proxy-do-backend.ts +840 -0
  50. package/src/pglite-ipc.test.ts +17 -0
  51. package/src/pglite-ipc.ts +31 -12
  52. package/src/pglite-web-proxy.test.ts +57 -0
  53. package/src/pglite-web-proxy.ts +48 -7
  54. package/src/query-rewrites.test.ts +30 -0
  55. package/src/query-rewrites.ts +152 -0
  56. package/src/worker/browser-admin.ts +52 -0
  57. package/src/worker/browser-embed-admin.test.ts +75 -0
  58. package/src/worker/browser-embed.ts +21 -12
@@ -0,0 +1,713 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * DoBackend: a PGlite-compatible adapter that forwards SQL to Cloudflare Durable Objects.
4
+ *
5
+ * Translates PG wire protocol messages → SQL → DO HTTP API → PG wire protocol responses.
6
+ *
7
+ * Handles PG transactions transparently: BEGIN/COMMIT/ROLLBACK are intercepted
8
+ * and managed with in-memory write buffering. Writes are flushed to the DO
9
+ * atomically via ctx.storage.transaction() on COMMIT.
10
+ */
11
+ const textEncoder = new TextEncoder();
12
+ const textDecoder = new TextDecoder();
13
+ // ── PG wire protocol constants ────────────────────────────────────────────
14
+ const FT_QUERY = 0x51;
15
+ const FT_PARSE = 0x50;
16
+ const FT_BIND = 0x42;
17
+ const FT_DESCRIBE = 0x44;
18
+ const FT_EXECUTE = 0x45;
19
+ const FT_SYNC = 0x53;
20
+ const FT_CLOSE = 0x43;
21
+ const FT_TERMINATE = 0x58;
22
+ const FT_FLUSH = 0x48;
23
+ const STATUS_IDLE = 0x49;
24
+ const PG_TYPE_TEXT = 25;
25
+ const PG_TYPE_INT4 = 23;
26
+ const PG_TYPE_INT8 = 20;
27
+ const PG_TYPE_BOOL = 16;
28
+ const PG_TYPE_FLOAT8 = 701;
29
+ const PG_TYPE_VARCHAR = 1043;
30
+ const PG_TYPE_JSON = 114;
31
+ const PG_TYPE_NUMERIC = 1700;
32
+ const PG_TYPE_TIMESTAMP = 1114;
33
+ const PG_TYPE_BYTEA = 17;
34
+ const PG_TYPE_INT2 = 21;
35
+ // ── Utilities ─────────────────────────────────────────────────────────────
36
+ function concat(...parts) {
37
+ let total = 0;
38
+ for (const p of parts)
39
+ total += p.length;
40
+ const result = new Uint8Array(total);
41
+ let offset = 0;
42
+ for (const p of parts) {
43
+ result.set(p, offset);
44
+ offset += p.length;
45
+ }
46
+ return result;
47
+ }
48
+ function i16(v, buf = new ArrayBuffer(2)) {
49
+ new DataView(buf).setInt16(0, v);
50
+ return new Uint8Array(buf);
51
+ }
52
+ function i32(v, buf = new ArrayBuffer(4)) {
53
+ new DataView(buf).setInt32(0, v);
54
+ return new Uint8Array(buf);
55
+ }
56
+ function int4(v, buf = new ArrayBuffer(4)) {
57
+ new DataView(buf).setInt32(0, v);
58
+ return new Uint8Array(buf);
59
+ }
60
+ function uint4(v, buf = new ArrayBuffer(4)) {
61
+ new DataView(buf).setUint32(0, v);
62
+ return new Uint8Array(buf);
63
+ }
64
+ function cstr(s) {
65
+ const encoded = textEncoder.encode(s);
66
+ const result = new Uint8Array(encoded.length + 1);
67
+ result.set(encoded);
68
+ return result;
69
+ }
70
+ function msg(ty, payload) {
71
+ const out = new Uint8Array(1 + 4 + payload.length);
72
+ out[0] = ty;
73
+ new DataView(out.buffer, 1, 4).setUint32(0, 4 + payload.length);
74
+ out.set(payload, 5);
75
+ return out;
76
+ }
77
+ const zero4 = new Uint8Array(4);
78
+ const zero2 = new Uint8Array(2);
79
+ // ── PG response builders ─────────────────────────────────────────────────
80
+ function buildRowDescription(fields) {
81
+ if (fields.length === 0)
82
+ return buildNoData();
83
+ const colParts = [];
84
+ for (const f of fields) {
85
+ colParts.push(cstr(f.name));
86
+ const col = new Uint8Array(18);
87
+ const v = new DataView(col.buffer);
88
+ v.setUint32(0, 0);
89
+ v.setInt16(4, 0);
90
+ v.setUint32(6, f.oid ?? PG_TYPE_TEXT);
91
+ v.setInt16(10, -1);
92
+ v.setInt32(12, -1);
93
+ v.setInt16(16, 0);
94
+ colParts.push(col);
95
+ }
96
+ return msg(0x54, concat(i16(fields.length), ...colParts));
97
+ }
98
+ function buildDataRow(row, fields) {
99
+ const colParts = [];
100
+ for (const name of fields) {
101
+ const val = row[name];
102
+ if (val === null || val === undefined) {
103
+ colParts.push(int4(-1));
104
+ }
105
+ else {
106
+ const str = typeof val === 'object' ? JSON.stringify(val) : String(val);
107
+ const encoded = textEncoder.encode(str);
108
+ colParts.push(concat(uint4(encoded.length), encoded));
109
+ }
110
+ }
111
+ return msg(0x44, concat(i16(fields.length), ...colParts));
112
+ }
113
+ function buildCommandComplete(tag) {
114
+ return msg(0x43, cstr(tag));
115
+ }
116
+ function buildReadyForQuery(status = STATUS_IDLE) {
117
+ return msg(0x5a, new Uint8Array([status]));
118
+ }
119
+ function buildErrorResponse(message) {
120
+ return msg(0x45, concat(cstr('S'), cstr('ERROR'), cstr('C'), cstr('XX000'), cstr('M'), cstr(message), new Uint8Array([0])));
121
+ }
122
+ function buildParseComplete() {
123
+ return msg(0x31, zero4);
124
+ }
125
+ function buildBindComplete() {
126
+ return msg(0x32, zero4);
127
+ }
128
+ function buildCloseComplete() {
129
+ return msg(0x33, zero4);
130
+ }
131
+ function buildNoData() {
132
+ return msg(0x6e, zero4);
133
+ }
134
+ function buildParameterDescription(oids) {
135
+ return msg(0x74, concat(i16(oids.length), ...oids.map(uint4)));
136
+ }
137
+ function buildParameterStatus(name, value) {
138
+ return msg(0x53, concat(cstr(name), cstr(value)));
139
+ }
140
+ function buildNotificationResponse(pid, channel, payload) {
141
+ return msg(0x41, concat(uint4(pid), cstr(channel), cstr(payload)));
142
+ }
143
+ // ── PG message parsers ────────────────────────────────────────────────────
144
+ function extractQueryText(data) {
145
+ if (data[0] !== 0x51)
146
+ return null;
147
+ const len = new DataView(data.buffer, data.byteOffset, 4).getInt32(1);
148
+ return textDecoder.decode(data.subarray(5, 1 + len - 1)).replace(/\0$/, '');
149
+ }
150
+ function extractParseQuery(data) {
151
+ if (data[0] !== 0x50)
152
+ return null;
153
+ let offset = 5;
154
+ while (offset < data.length && data[offset] !== 0)
155
+ offset++;
156
+ offset++;
157
+ const qStart = offset;
158
+ while (offset < data.length && data[offset] !== 0)
159
+ offset++;
160
+ return textDecoder.decode(data.subarray(qStart, offset));
161
+ }
162
+ function extractParseStatementName(data) {
163
+ let offset = 5;
164
+ const start = offset;
165
+ while (offset < data.length && data[offset] !== 0)
166
+ offset++;
167
+ return textDecoder.decode(data.subarray(start, offset));
168
+ }
169
+ function extractBindStatementName(data) {
170
+ let offset = 5;
171
+ while (offset < data.length && data[offset] !== 0)
172
+ offset++;
173
+ offset++;
174
+ const start = offset;
175
+ while (offset < data.length && data[offset] !== 0)
176
+ offset++;
177
+ return textDecoder.decode(data.subarray(start, offset));
178
+ }
179
+ function extractBindParams(data) {
180
+ const params = [];
181
+ let offset = 5;
182
+ while (offset < data.length && data[offset] !== 0)
183
+ offset++;
184
+ offset++;
185
+ while (offset < data.length && data[offset] !== 0)
186
+ offset++;
187
+ offset++;
188
+ if (offset + 2 > data.length)
189
+ return params;
190
+ const nfc = new DataView(data.buffer, data.byteOffset + offset, 2).getInt16(0);
191
+ offset += 2 + nfc * 2;
192
+ if (offset + 2 > data.length)
193
+ return params;
194
+ const np = new DataView(data.buffer, data.byteOffset + offset, 2).getInt16(0);
195
+ offset += 2;
196
+ for (let i = 0; i < np; i++) {
197
+ if (offset + 4 > data.length)
198
+ break;
199
+ const plen = new DataView(data.buffer, data.byteOffset + offset, 4).getInt32(0);
200
+ offset += 4;
201
+ if (plen === -1) {
202
+ params.push(null);
203
+ continue;
204
+ }
205
+ const str = textDecoder.decode(data.subarray(offset, offset + plen));
206
+ offset += plen;
207
+ params.push(/^-?\d+(\.\d+)?$/.test(str)
208
+ ? str.includes('.')
209
+ ? parseFloat(str)
210
+ : Number(str)
211
+ : str);
212
+ }
213
+ return params;
214
+ }
215
+ function extractDescribeType(data) {
216
+ return data[5] === 0x53 ? 'S' : 'P';
217
+ }
218
+ function extractDescribeName(data) {
219
+ const start = 6;
220
+ let off = start;
221
+ while (off < data.length && data[off] !== 0)
222
+ off++;
223
+ return textDecoder.decode(data.subarray(start, off));
224
+ }
225
+ // ── Catalog query interception ────────────────────────────────────────────
226
+ function isCatalogQuery(sql) {
227
+ const n = sql.replace(/\s+/g, ' ').trim().toLowerCase();
228
+ if (n.includes('current_setting('))
229
+ return true;
230
+ if (n.includes('pg_advisory_xact_lock') || n.includes('pg_advisory_lock'))
231
+ return true;
232
+ if (n.startsWith('select') &&
233
+ (n.includes('information_schema.') ||
234
+ n.includes('pg_catalog.') ||
235
+ n.includes('pg_tables') ||
236
+ n.includes('pg_namespace') ||
237
+ n.includes('pg_type') ||
238
+ n.includes('pg_class') ||
239
+ n.includes('pg_attribute') ||
240
+ n.includes('pg_stat_') ||
241
+ n.includes('pg_index') ||
242
+ n.includes('pg_depend') ||
243
+ n.includes('pg_constraint') ||
244
+ n.includes('pg_inherits') ||
245
+ n.includes('pg_cast') ||
246
+ n.includes('pg_opfamily') ||
247
+ n.includes('pg_am ') ||
248
+ n.includes('pg_operator') ||
249
+ n.includes('pg_aggregate') ||
250
+ n.includes('pg_language') ||
251
+ n.includes('pg_extension') ||
252
+ n.includes('pg_foreign_data') ||
253
+ n.includes('pg_foreign_server') ||
254
+ n.includes('pg_range') ||
255
+ n.includes('pg_enum') ||
256
+ n.includes('pg_rewrite') ||
257
+ n.includes('pg_proc') ||
258
+ n.includes('pg_roles') ||
259
+ n.includes('pg_user ') ||
260
+ n.includes('pg_authid') ||
261
+ n.includes('pg_settings') ||
262
+ n.includes('pg_collation') ||
263
+ n.includes('pg_trigger') ||
264
+ n.includes('pg_get_expr') ||
265
+ n.includes('pg_get_functiondef') ||
266
+ n.includes('pg_get_constraintdef') ||
267
+ n.includes('pg_describe_object') ||
268
+ n.includes('has_') ||
269
+ n.includes('obj_description') ||
270
+ n.includes('format_type')))
271
+ return true;
272
+ if (n.includes('information_schema.') &&
273
+ (n.includes('schemata') ||
274
+ n.includes('views') ||
275
+ n.includes('view_') ||
276
+ n.includes('_pg_') ||
277
+ n.includes('table_privileges') ||
278
+ n.includes('column_udt_usage') ||
279
+ n.includes('routine_') ||
280
+ n.includes('parameters') ||
281
+ n.includes('check_constraints') ||
282
+ n.includes('referential_constraints') ||
283
+ n.includes('key_column_usage')))
284
+ return true;
285
+ return false;
286
+ }
287
+ // ── SQL rewriting ─────────────────────────────────────────────────────────
288
+ function rewriteSQL(sql) {
289
+ let result = sql.trim();
290
+ // Strip PG type casts
291
+ result = result.replace(/::\w+(\[\])?\b/g, '');
292
+ // Schema-qualified names: "schema"."table" or schema.table → flat
293
+ result = result.replace(/"(\w+)"\s*\.\s*"(\w+)"/g, '"$1_$2"');
294
+ result = result.replace(/_orez\._zero_changes\b/g, '_zero_changes');
295
+ result = result.replace(/_orez\._zero_replication_slots\b/g, '_orez__zero_replication_slots');
296
+ result = result.replace(/(\b)_orez\.(\w+)/g, '$1_orez__$2');
297
+ // nextval → 1
298
+ result = result.replace(/nextval\s*\([^)]*\)/gi, '1');
299
+ // CREATE SEQUENCE → CREATE TABLE IF NOT EXISTS
300
+ if (/^\s*create\s+sequence\s+/i.test(result)) {
301
+ const m = /create\s+sequence\s+(\S+)/i.exec(result);
302
+ if (m)
303
+ result = `CREATE TABLE IF NOT EXISTS _${m[1].replace(/"/g, '')}_seq (val INTEGER DEFAULT 1, dummy INTEGER PRIMARY KEY DEFAULT 1)`;
304
+ }
305
+ // Skipped PG features
306
+ if (/^\s*create\s+(or\s+replace\s+)?(function|trigger)\s+/i.test(result))
307
+ return '';
308
+ if (/^\s*cluster\s+/i.test(result))
309
+ return '';
310
+ if (/^\s*(grant|revoke)\s+/i.test(result))
311
+ return '';
312
+ if (/^\s*alter\s+default\s+privileges/i.test(result))
313
+ return '';
314
+ if (/^\s*comment\s+on\s+/i.test(result))
315
+ return '';
316
+ if (/^\s*(create|alter|drop)\s+publication\s+/i.test(result))
317
+ return '';
318
+ if (/^\s*alter\s+table\s+.+replica\s+identity/i.test(result))
319
+ return '';
320
+ // CLOSE cursor — pg-specific
321
+ if (/^\s*close\s+/i.test(result))
322
+ return '';
323
+ // DDL schema flattening
324
+ if (/^\s*(create|alter|drop)\s+(table|index|view|schema|sequence)\s+/i.test(result)) {
325
+ result = result.replace(/(\w+)\.(\w+)/g, '$1_$2');
326
+ }
327
+ // DEALLOCATE / DISCARD / RESET
328
+ if (/^(deallocate|discard|reset\s+all)/i.test(result))
329
+ return '';
330
+ // LISTEN / UNLISTEN
331
+ if (/^(listen|unlisten)/i.test(result))
332
+ return '';
333
+ // SHOW
334
+ if (/^show\s+/i.test(result))
335
+ return '';
336
+ return result;
337
+ }
338
+ // ── DoBackend class ───────────────────────────────────────────────────────
339
+ export class DoBackend {
340
+ waitReady;
341
+ ready = false;
342
+ closed = false;
343
+ doUrl;
344
+ dbName;
345
+ httpClient;
346
+ preparedStatements = new Map();
347
+ sqlToExecute = null;
348
+ // Transaction state
349
+ inTransaction = false;
350
+ txnBuffer = [];
351
+ txnReadOnly = false;
352
+ constructor(doUrl, dbName = 'postgres') {
353
+ this.doUrl = doUrl.replace(/\/+$/, '');
354
+ this.dbName = dbName;
355
+ this.httpClient = new HttpClient();
356
+ this.waitReady = this.init();
357
+ }
358
+ async init() {
359
+ try {
360
+ await this.httpClient.post(`${this.doUrl}/exec?db=${encodeURIComponent(this.dbName)}`, JSON.stringify({ sql: 'SELECT 1' }));
361
+ }
362
+ catch { }
363
+ this.ready = true;
364
+ }
365
+ async close() {
366
+ this.closed = true;
367
+ }
368
+ async execProtocolRaw(message, options) {
369
+ const msgType = message[0];
370
+ try {
371
+ switch (msgType) {
372
+ case FT_QUERY:
373
+ return await this.handleSimpleQuery(message);
374
+ case FT_PARSE:
375
+ return this.handleParse(message);
376
+ case FT_BIND:
377
+ return this.handleBind(message);
378
+ case FT_DESCRIBE:
379
+ return this.handleDescribe(message);
380
+ case FT_EXECUTE:
381
+ return await this.handleExecute(message);
382
+ case FT_SYNC:
383
+ return this.handleSync();
384
+ case FT_CLOSE:
385
+ return buildCloseComplete();
386
+ case FT_FLUSH:
387
+ return new Uint8Array(0);
388
+ case FT_TERMINATE:
389
+ return new Uint8Array(0);
390
+ default:
391
+ return new Uint8Array(0);
392
+ }
393
+ }
394
+ catch (err) {
395
+ if (options?.throwOnError !== false)
396
+ throw err;
397
+ return buildErrorResponse(err.message || String(err));
398
+ }
399
+ }
400
+ // ── Transaction-aware query handling ──────────────────────────────────────
401
+ async handleSimpleQuery(data) {
402
+ const sql = extractQueryText(data);
403
+ if (!sql)
404
+ return concat(buildCommandComplete('OK'), buildReadyForQuery());
405
+ const normalized = sql.trimStart().toLowerCase();
406
+ // BEGIN / START TRANSACTION
407
+ if (normalized === 'begin' ||
408
+ normalized === 'begin;' ||
409
+ normalized === 'begin work' ||
410
+ normalized === 'begin transaction' ||
411
+ normalized === 'start transaction') {
412
+ this.inTransaction = true;
413
+ this.txnBuffer = [];
414
+ this.txnReadOnly = false;
415
+ return concat(buildCommandComplete('BEGIN'), buildReadyForQuery());
416
+ }
417
+ // COMMIT / END
418
+ if (normalized === 'commit' ||
419
+ normalized === 'commit;' ||
420
+ normalized === 'commit work' ||
421
+ normalized === 'end' ||
422
+ normalized === 'end;') {
423
+ if (this.inTransaction && this.txnBuffer.length > 0) {
424
+ await this.flushTransactionBuffer();
425
+ }
426
+ this.inTransaction = false;
427
+ this.txnBuffer = [];
428
+ return concat(buildCommandComplete('COMMIT'), buildReadyForQuery());
429
+ }
430
+ // ROLLBACK / ABORT
431
+ if (normalized === 'rollback' ||
432
+ normalized === 'rollback;' ||
433
+ normalized === 'rollback work' ||
434
+ normalized === 'abort' ||
435
+ normalized === 'abort;') {
436
+ this.inTransaction = false;
437
+ this.txnBuffer = [];
438
+ return concat(buildCommandComplete('ROLLBACK'), buildReadyForQuery());
439
+ }
440
+ // SET (local) — skip
441
+ if (normalized.startsWith('set '))
442
+ return concat(buildCommandComplete('SET'), buildReadyForQuery());
443
+ if (normalized.startsWith('show '))
444
+ return concat(buildCommandComplete('SHOW'), buildReadyForQuery());
445
+ if (normalized === 'show' || normalized === 'show;')
446
+ return concat(buildCommandComplete('SHOW'), buildReadyForQuery());
447
+ // SAVEPOINT — skip
448
+ if (normalized.startsWith('savepoint ') ||
449
+ normalized.startsWith('release savepoint') ||
450
+ normalized.startsWith('release ') ||
451
+ normalized.startsWith('rollback to savepoint') ||
452
+ normalized.startsWith('rollback to ')) {
453
+ return concat(buildCommandComplete('SAVEPOINT'), buildReadyForQuery());
454
+ }
455
+ // DEALLOCATE, DISCARD, RESET → skip
456
+ if (/^(deallocate|discard|reset)\b/.test(normalized)) {
457
+ return concat(buildCommandComplete('OK'), buildReadyForQuery());
458
+ }
459
+ // LOCK TABLE → skip
460
+ if (normalized.startsWith('lock table') || normalized.startsWith('lock ')) {
461
+ return concat(buildCommandComplete('LOCK TABLE'), buildReadyForQuery());
462
+ }
463
+ // Prepare query
464
+ const rewritten = rewriteSQL(sql);
465
+ if (rewritten === '' || rewritten.startsWith('--'))
466
+ return concat(buildCommandComplete('OK'), buildReadyForQuery());
467
+ // Catalog queries — check before forwarding
468
+ if (isCatalogQuery(rewritten)) {
469
+ const result = this.handleCatalogQuery(rewritten);
470
+ return this.buildSelectResponse(result.rows, result.fields);
471
+ }
472
+ // SELECT reads — execute immediately even in transaction
473
+ const isWrite = this.isWriteQuery(rewritten);
474
+ const isDDL = this.isDDLQuery(rewritten);
475
+ if (this.inTransaction && (isWrite || isDDL)) {
476
+ // Buffer writes until COMMIT
477
+ this.txnBuffer.push(rewritten);
478
+ if (isDDL)
479
+ return concat(buildCommandComplete('CREATE TABLE'), buildReadyForQuery());
480
+ const isInsert = /^\s*insert\b/i.test(rewritten);
481
+ const isUpdate = /^\s*update\b/i.test(rewritten);
482
+ const isDelete = /^\s*delete\b/i.test(rewritten);
483
+ const tag = isInsert
484
+ ? 'INSERT 0 1'
485
+ : isUpdate
486
+ ? 'UPDATE 1'
487
+ : isDelete
488
+ ? 'DELETE 1'
489
+ : 'OK';
490
+ return concat(buildCommandComplete(tag), buildReadyForQuery());
491
+ }
492
+ // Execute SQL
493
+ try {
494
+ const rows = await this.doExec(rewritten);
495
+ return this.buildSQLResponse(rewritten, rows);
496
+ }
497
+ catch (err) {
498
+ return concat(buildErrorResponse(err.message), buildReadyForQuery());
499
+ }
500
+ }
501
+ async flushTransactionBuffer() {
502
+ if (this.txnBuffer.length === 0)
503
+ return;
504
+ await this.doBatchExec(this.txnBuffer);
505
+ this.txnBuffer = [];
506
+ }
507
+ isWriteQuery(sql) {
508
+ return /^\s*(insert|update|delete|upsert|merge|truncate|copy)\b/i.test(sql);
509
+ }
510
+ isDDLQuery(sql) {
511
+ return /^\s*(create|alter|drop|grant|revoke)\s+(table|index|view|schema|sequence|function|trigger|publication)/i.test(sql);
512
+ }
513
+ // ── Extended protocol handlers ──────────────────────────────────────────
514
+ handleParse(data) {
515
+ const sql = extractParseQuery(data);
516
+ const stmtName = extractParseStatementName(data);
517
+ if (sql) {
518
+ const rewritten = rewriteSQL(sql);
519
+ if (rewritten && !rewritten.startsWith('--') && !isCatalogQuery(rewritten)) {
520
+ this.preparedStatements.set(stmtName, { sql: rewritten });
521
+ }
522
+ }
523
+ return buildParseComplete();
524
+ }
525
+ handleBind(data) {
526
+ const stmtName = extractBindStatementName(data);
527
+ const params = extractBindParams(data);
528
+ const stmt = this.preparedStatements.get(stmtName);
529
+ if (stmt)
530
+ stmt._params = params;
531
+ return buildBindComplete();
532
+ }
533
+ async handleExecute(_data) {
534
+ let stmt;
535
+ for (const [, s] of this.preparedStatements) {
536
+ if (s._params !== undefined) {
537
+ stmt = s;
538
+ break;
539
+ }
540
+ }
541
+ if (!stmt || !stmt.sql?.trim())
542
+ return new Uint8Array(0);
543
+ const params = stmt._params || [];
544
+ delete stmt._params;
545
+ const sql = this.inlineParams(stmt.sql, params);
546
+ const normalized = sql.trimStart().toLowerCase();
547
+ // Handle transaction markers in extended protocol
548
+ if (normalized === 'begin' || normalized.startsWith('begin ')) {
549
+ this.inTransaction = true;
550
+ this.txnBuffer = [];
551
+ this.txnReadOnly = false;
552
+ return new Uint8Array(0);
553
+ }
554
+ if (normalized === 'commit' || normalized.startsWith('commit ')) {
555
+ if (this.inTransaction && this.txnBuffer.length > 0)
556
+ await this.flushTransactionBuffer();
557
+ this.inTransaction = false;
558
+ this.txnBuffer = [];
559
+ return buildCommandComplete('COMMIT');
560
+ }
561
+ if (normalized === 'rollback' ||
562
+ normalized.startsWith('rollback ') ||
563
+ normalized === 'abort') {
564
+ this.inTransaction = false;
565
+ this.txnBuffer = [];
566
+ return buildCommandComplete('ROLLBACK');
567
+ }
568
+ this.sqlToExecute = { sql, params };
569
+ try {
570
+ const rows = await this.doExec(sql);
571
+ if (rows.length > 0) {
572
+ const fns = Object.keys(rows[0]);
573
+ return concat(buildRowDescription(fns.map((n) => ({ name: n }))), ...rows.map((r) => buildDataRow(r, fns)), buildCommandComplete(`SELECT ${rows.length}`));
574
+ }
575
+ const isSelect = /^\s*select\b/i.test(sql) || /^\s*with\b/i.test(sql);
576
+ return buildCommandComplete(isSelect ? 'SELECT 0' : 'OK');
577
+ }
578
+ catch (err) {
579
+ return buildErrorResponse(err.message);
580
+ }
581
+ }
582
+ handleSync() {
583
+ this.sqlToExecute = null;
584
+ return buildReadyForQuery();
585
+ }
586
+ handleDescribe(data) {
587
+ const stmt = this.preparedStatements.get(extractDescribeName(data));
588
+ if (stmt && stmt.paramOIDs?.length)
589
+ return buildParameterDescription(stmt.paramOIDs);
590
+ return buildNoData();
591
+ }
592
+ // ── High-level API ──────────────────────────────────────────────────────
593
+ async exec(sql) {
594
+ const rewritten = rewriteSQL(sql);
595
+ if (!rewritten)
596
+ return [];
597
+ if (isCatalogQuery(rewritten))
598
+ return [];
599
+ return this.doExec(rewritten);
600
+ }
601
+ async query(sql, _params) {
602
+ const rewritten = rewriteSQL(sql);
603
+ if (!rewritten)
604
+ return { rows: [] };
605
+ if (isCatalogQuery(rewritten))
606
+ return { rows: [] };
607
+ const rows = await this.doExec(rewritten);
608
+ return { rows: rows };
609
+ }
610
+ // ── Internal helpers ─────────────────────────────────────────────────────
611
+ async doExec(sql) {
612
+ if (!sql.trim())
613
+ return [];
614
+ for (let attempt = 0; attempt < 2; attempt++) {
615
+ try {
616
+ const resp = await this.httpClient.post(`${this.doUrl}/exec?db=${encodeURIComponent(this.dbName)}`, JSON.stringify({ sql }), { 'Content-Type': 'application/json' });
617
+ const result = JSON.parse(resp);
618
+ return result.rows ?? result ?? [];
619
+ }
620
+ catch { }
621
+ }
622
+ return [];
623
+ }
624
+ async doBatchExec(statements) {
625
+ await this.httpClient.post(`${this.doUrl}/batch?db=${encodeURIComponent(this.dbName)}`, JSON.stringify({ statements }), { 'Content-Type': 'application/json' });
626
+ }
627
+ inlineParams(sql, params) {
628
+ let result = sql;
629
+ for (let i = params.length; i >= 1; i--) {
630
+ const val = params[i - 1];
631
+ const esc = val === null
632
+ ? 'NULL'
633
+ : typeof val === 'string'
634
+ ? `'${val.replace(/'/g, "''")}'`
635
+ : String(val);
636
+ result = result.replace(new RegExp(`\\$${i}\\b`, 'g'), esc);
637
+ }
638
+ return result;
639
+ }
640
+ buildSQLResponse(originalSql, rows) {
641
+ const isSelect = /^\s*select\b/i.test(originalSql) || /^\s*with\b/i.test(originalSql);
642
+ if (rows.length > 0) {
643
+ const fns = Object.keys(rows[0]);
644
+ const tag = isSelect
645
+ ? `SELECT ${rows.length}`
646
+ : /^\s*insert\b/i.test(originalSql)
647
+ ? 'INSERT 0 1'
648
+ : /^\s*update\b/i.test(originalSql)
649
+ ? 'UPDATE 1'
650
+ : /^\s*delete\b/i.test(originalSql)
651
+ ? 'DELETE 1'
652
+ : 'OK';
653
+ return concat(buildRowDescription(fns.map((n) => ({ name: n }))), ...rows.map((r) => buildDataRow(r, fns)), buildCommandComplete(tag), buildReadyForQuery());
654
+ }
655
+ const tag = isSelect
656
+ ? 'SELECT 0'
657
+ : /^\s*insert\b/i.test(originalSql)
658
+ ? 'INSERT 0 0'
659
+ : /^\s*update\b/i.test(originalSql)
660
+ ? 'UPDATE 0'
661
+ : /^\s*delete\b/i.test(originalSql)
662
+ ? 'DELETE 0'
663
+ : 'OK';
664
+ return concat(buildCommandComplete(tag), buildReadyForQuery());
665
+ }
666
+ buildSelectResponse(rows, fields) {
667
+ const fns = fields.map((f) => f.name);
668
+ if (rows.length === 0)
669
+ return concat(buildRowDescription(fields), buildCommandComplete('SELECT 0'), buildReadyForQuery());
670
+ return concat(buildRowDescription(fields), ...rows.map((r) => buildDataRow(r, fns)), buildCommandComplete(`SELECT ${rows.length}`), buildReadyForQuery());
671
+ }
672
+ handleCatalogQuery(sql) {
673
+ const n = sql.replace(/\s+/g, ' ').trim().toLowerCase();
674
+ // current_setting('server_version') etc.
675
+ const csMatch = /current_setting\s*\(\s*['"]([^'"]+)['"]\s*\)/.exec(n);
676
+ if (csMatch) {
677
+ const vals = {
678
+ server_version: '16.0',
679
+ server_encoding: 'UTF8',
680
+ client_encoding: 'UTF8',
681
+ standard_conforming_strings: 'on',
682
+ TimeZone: 'UTC',
683
+ integer_datetimes: 'on',
684
+ IntervalStyle: 'postgres',
685
+ DateStyle: 'ISO, MDY',
686
+ lc_messages: 'en_US.UTF-8',
687
+ lc_monetary: 'en_US.UTF-8',
688
+ lc_numeric: 'en_US.UTF-8',
689
+ lc_time: 'en_US.UTF-8',
690
+ };
691
+ return {
692
+ rows: [{ current_setting: vals[csMatch[1]] ?? '' }],
693
+ fields: [{ name: 'current_setting' }],
694
+ };
695
+ }
696
+ return { rows: [], fields: [] };
697
+ }
698
+ }
699
+ class HttpClient {
700
+ async post(url, body, headers) {
701
+ const resp = await fetch(url, {
702
+ method: 'POST',
703
+ headers: headers ?? { 'Content-Type': 'application/json' },
704
+ body,
705
+ });
706
+ if (!resp.ok) {
707
+ const text = await resp.text().catch(() => '');
708
+ throw new Error(`HTTP ${resp.status}: ${text.slice(0, 200)}`);
709
+ }
710
+ return resp.text();
711
+ }
712
+ }
713
+ //# sourceMappingURL=pg-proxy-do-backend.js.map