postgrest-parser 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 PostgREST Parser Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,1082 @@
1
+ # PostgREST Parser for Rust
2
+
3
+ [![Crates.io](https://img.shields.io/crates/v/postgrest-parser.svg)](https://crates.io/crates/postgrest-parser)
4
+ [![Documentation](https://docs.rs/postgrest-parser/badge.svg)](https://docs.rs/postgrest-parser)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ A high-performance Rust implementation of the PostgREST URL-to-SQL parser, supporting both native and WASM targets.
8
+
9
+ ## Features
10
+
11
+ - ✅ **Complete PostgREST API**: All 22+ filter operators fully implemented
12
+ - ✅ **Logic Operators**: AND, OR, NOT with arbitrary nesting depth
13
+ - ✅ **Select Parsing**: Fields, relations, spreads, aliases, JSON paths, type casting
14
+ - ✅ **Full-Text Search**: Multiple FTS operators with language support
15
+ - ✅ **Array/Range Operations**: PostgreSQL array and range type support
16
+ - ✅ **Quantifiers**: `any` and `all` for array comparisons
17
+ - ✅ **Order Parsing**: Multi-column ordering with nulls handling
18
+ - ✅ **Parameterized SQL**: Safe SQL generation with $1, $2, etc. placeholders
19
+ - ✅ **Zero Regex**: Uses nom parser combinators for better performance
20
+ - ✅ **Type Safe**: Comprehensive error handling with thiserror
21
+ - ✅ **WASM Support**: Full TypeScript/JavaScript bindings for browser and Deno (optional feature)
22
+ - ✅ **TypeScript Client**: Type-safe API with zero `any` types, object-based APIs, and IntelliSense
23
+ - ✅ **171 Tests**: Comprehensive test coverage (148 Rust + 23 WASM integration tests)
24
+
25
+ ## Installation
26
+
27
+ ### Rust
28
+
29
+ Add to your `Cargo.toml`:
30
+
31
+ ```toml
32
+ [dependencies]
33
+ postgrest-parser = "0.1.0"
34
+ ```
35
+
36
+ ### TypeScript/JavaScript (WASM)
37
+
38
+ The parser is available as a WebAssembly module with full TypeScript support for use in browsers, Node.js, Deno, and edge runtimes.
39
+
40
+ #### Quick Start for TypeScript Projects
41
+
42
+ **1. Build the WASM package:**
43
+
44
+ ```bash
45
+ # Install wasm-pack if you haven't already
46
+ cargo install wasm-pack
47
+
48
+ # Build for web (browsers, Deno, Cloudflare Workers, etc.)
49
+ wasm-pack build --target web --features wasm
50
+
51
+ # Or for Node.js
52
+ wasm-pack build --target nodejs --features wasm
53
+
54
+ # Development build (faster compilation, larger file)
55
+ wasm-pack build --dev --target web --features wasm
56
+ ```
57
+
58
+ **2. Copy to your TypeScript project:**
59
+
60
+ ```bash
61
+ # Copy the entire pkg/ directory to your project
62
+ cp -r pkg/ /path/to/your/project/postgrest-parser/
63
+
64
+ # Or publish to npm (requires package.json configuration)
65
+ cd pkg && npm publish
66
+ ```
67
+
68
+ **3. Import and use:**
69
+
70
+ > **🎉 NEW: Type-Safe Client API**
71
+ >
72
+ > We now provide a fully type-safe TypeScript client with zero `any` types, object-based APIs, and better IntelliSense support. See [TYPESCRIPT_GUIDE.md](docs/TYPESCRIPT_GUIDE.md) for details.
73
+ >
74
+ > ```typescript
75
+ > // Recommended: Type-safe client (client.ts)
76
+ > import { createClient } from './postgrest-parser/client.js';
77
+ > const client = createClient();
78
+ >
79
+ > const result = client.select("users", {
80
+ > filters: { age: "gte.18", status: "eq.active" },
81
+ > order: ["name.asc"],
82
+ > limit: 10
83
+ > });
84
+ > // Full IntelliSense, no 'any' types, native objects
85
+ > ```
86
+ >
87
+ > ```typescript
88
+ > // Alternative: Low-level WASM API (postgrest_parser.js)
89
+ > import init, { parseRequest } from './postgrest-parser/postgrest_parser.js';
90
+ > await init();
91
+ >
92
+ > const result = parseRequest("GET", "users", "age=gte.18&limit=10", null, null);
93
+ > // Direct WASM bindings, requires manual query string construction
94
+ > ```
95
+
96
+ #### TypeScript Integration Guide
97
+
98
+ ##### 1. HTTP Method Routing (Recommended Approach)
99
+
100
+ The `parseRequest()` function is the **primary entry point** - it automatically routes HTTP methods to SQL operations following PostgREST conventions:
101
+
102
+ ```typescript
103
+ import init, { parseRequest } from './postgrest-parser/postgrest_parser.js';
104
+
105
+ await init();
106
+
107
+ // GET → SELECT
108
+ const getUsers = parseRequest(
109
+ "GET",
110
+ "users",
111
+ "age=gte.18&status=eq.active&order=name.asc&limit=10",
112
+ null,
113
+ null
114
+ );
115
+ // Generates: SELECT * FROM "users" WHERE "age" >= $1 AND "status" = $2 ORDER BY "name" ASC LIMIT $3
116
+
117
+ // POST → INSERT
118
+ const createUser = parseRequest(
119
+ "POST",
120
+ "users",
121
+ "returning=id,name,email",
122
+ JSON.stringify({ name: "Alice", email: "alice@example.com" }),
123
+ JSON.stringify({ Prefer: "return=representation" })
124
+ );
125
+ // Generates: INSERT INTO "users" ("name", "email") VALUES ($1, $2) RETURNING "id", "name", "email"
126
+
127
+ // PUT → UPSERT (auto ON CONFLICT from query filters)
128
+ const upsertUser = parseRequest(
129
+ "PUT",
130
+ "users",
131
+ "email=eq.alice@example.com&returning=*",
132
+ JSON.stringify({ email: "alice@example.com", name: "Alice Updated" }),
133
+ null
134
+ );
135
+ // Generates: INSERT ... ON CONFLICT ("email") DO UPDATE SET ... RETURNING *
136
+
137
+ // PATCH → UPDATE
138
+ const updateUser = parseRequest(
139
+ "PATCH",
140
+ "users",
141
+ "id=eq.123&returning=id,status",
142
+ JSON.stringify({ status: "verified" }),
143
+ null
144
+ );
145
+ // Generates: UPDATE "users" SET "status" = $1 WHERE "id" = $2 RETURNING "id", "status"
146
+
147
+ // DELETE → DELETE
148
+ const deleteUser = parseRequest(
149
+ "DELETE",
150
+ "users",
151
+ "id=eq.123&returning=id",
152
+ null,
153
+ null
154
+ );
155
+ // Generates: DELETE FROM "users" WHERE "id" = $1 RETURNING "id"
156
+
157
+ // RPC → Function call
158
+ const rpcResult = parseRequest(
159
+ "POST",
160
+ "rpc/calculate_total",
161
+ "select=total,tax",
162
+ JSON.stringify({ order_id: 123, tax_rate: 0.08 }),
163
+ null
164
+ );
165
+ // Generates: SELECT * FROM calculate_total($1, $2)
166
+ ```
167
+
168
+ ##### 2. Express.js Integration
169
+
170
+ ```typescript
171
+ import express from 'express';
172
+ import init, { parseRequest } from './postgrest-parser/postgrest_parser.js';
173
+ import pg from 'pg';
174
+
175
+ const app = express();
176
+ const db = new pg.Pool({ connectionString: process.env.DATABASE_URL });
177
+
178
+ // Initialize WASM once at startup
179
+ await init();
180
+
181
+ app.use(express.json());
182
+
183
+ // Universal PostgREST-compatible endpoint
184
+ app.all('/api/:table', async (req, res) => {
185
+ try {
186
+ const result = parseRequest(
187
+ req.method,
188
+ req.params.table,
189
+ new URLSearchParams(req.query).toString(),
190
+ req.body ? JSON.stringify(req.body) : null,
191
+ JSON.stringify(req.headers)
192
+ );
193
+
194
+ const { rows } = await db.query(result.query, result.params);
195
+ res.json(rows);
196
+ } catch (error) {
197
+ res.status(400).json({ error: error.message });
198
+ }
199
+ });
200
+
201
+ app.listen(3000);
202
+ ```
203
+
204
+ Now your API supports:
205
+ ```bash
206
+ GET /api/users?age=gte.18&select=id,name
207
+ POST /api/users + body { "name": "Alice" }
208
+ PUT /api/users?id=eq.123 + body { "id": 123, "name": "Alice" }
209
+ PATCH /api/users?id=eq.123 + body { "status": "active" }
210
+ DELETE /api/users?id=eq.123
211
+ ```
212
+
213
+ ##### 3. Next.js API Route
214
+
215
+ ```typescript
216
+ // pages/api/[table].ts
217
+ import type { NextApiRequest, NextApiResponse } from 'next';
218
+ import init, { parseRequest } from '@/lib/postgrest-parser/postgrest_parser.js';
219
+ import { query } from '@/lib/db';
220
+
221
+ let initialized = false;
222
+
223
+ export default async function handler(req: NextApiRequest, res: NextApiResponse) {
224
+ if (!initialized) {
225
+ await init();
226
+ initialized = true;
227
+ }
228
+
229
+ const { table } = req.query;
230
+ const queryString = new URLSearchParams(req.query as Record<string, string>).toString();
231
+
232
+ try {
233
+ const result = parseRequest(
234
+ req.method!,
235
+ table as string,
236
+ queryString,
237
+ req.body ? JSON.stringify(req.body) : null,
238
+ JSON.stringify(req.headers)
239
+ );
240
+
241
+ const rows = await query(result.query, result.params);
242
+ res.status(200).json(rows);
243
+ } catch (error) {
244
+ res.status(400).json({ error: (error as Error).message });
245
+ }
246
+ }
247
+ ```
248
+
249
+ ##### 4. Deno Edge Function
250
+
251
+ ```typescript
252
+ // supabase/functions/postgrest-proxy/index.ts
253
+ import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
254
+ import init, { parseRequest } from './postgrest_parser.js';
255
+ import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
256
+
257
+ await init();
258
+
259
+ const supabase = createClient(
260
+ Deno.env.get('SUPABASE_URL')!,
261
+ Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
262
+ );
263
+
264
+ serve(async (req) => {
265
+ const url = new URL(req.url);
266
+ const path = url.pathname.slice(1);
267
+ const query = url.search.slice(1);
268
+
269
+ let body = null;
270
+ if (req.method !== 'GET' && req.method !== 'DELETE') {
271
+ body = await req.text();
272
+ }
273
+
274
+ try {
275
+ const result = parseRequest(
276
+ req.method,
277
+ path,
278
+ query,
279
+ body,
280
+ JSON.stringify(Object.fromEntries(req.headers))
281
+ );
282
+
283
+ const { data, error } = await supabase.rpc('execute_sql', {
284
+ query: result.query,
285
+ params: result.params
286
+ });
287
+
288
+ if (error) throw error;
289
+
290
+ return new Response(JSON.stringify(data), {
291
+ headers: { 'Content-Type': 'application/json' }
292
+ });
293
+ } catch (error) {
294
+ return new Response(JSON.stringify({ error: error.message }), {
295
+ status: 400,
296
+ headers: { 'Content-Type': 'application/json' }
297
+ });
298
+ }
299
+ });
300
+ ```
301
+
302
+ ##### 5. Type-Safe Wrapper
303
+
304
+ ```typescript
305
+ // lib/postgrest.ts
306
+ import init, { parseRequest, WasmQueryResult } from './postgrest-parser/postgrest_parser.js';
307
+
308
+ let initialized = false;
309
+
310
+ async function ensureInit() {
311
+ if (!initialized) {
312
+ await init();
313
+ initialized = true;
314
+ }
315
+ }
316
+
317
+ export interface QueryOptions {
318
+ select?: string;
319
+ filters?: Record<string, string>;
320
+ order?: string;
321
+ limit?: number;
322
+ offset?: number;
323
+ }
324
+
325
+ export class PostgRESTClient {
326
+ constructor(private executeQuery: (sql: string, params: any[]) => Promise<any[]>) {}
327
+
328
+ async select(table: string, options: QueryOptions = {}): Promise<any[]> {
329
+ await ensureInit();
330
+
331
+ const params = new URLSearchParams();
332
+ if (options.select) params.set('select', options.select);
333
+ if (options.filters) Object.entries(options.filters).forEach(([k, v]) => params.set(k, v));
334
+ if (options.order) params.set('order', options.order);
335
+ if (options.limit) params.set('limit', String(options.limit));
336
+ if (options.offset) params.set('offset', String(options.offset));
337
+
338
+ const result = parseRequest('GET', table, params.toString(), null, null);
339
+ return this.executeQuery(result.query, result.params);
340
+ }
341
+
342
+ async insert(table: string, data: any | any[], returning = '*'): Promise<any[]> {
343
+ await ensureInit();
344
+
345
+ const result = parseRequest(
346
+ 'POST',
347
+ table,
348
+ `returning=${returning}`,
349
+ JSON.stringify(data),
350
+ JSON.stringify({ Prefer: 'return=representation' })
351
+ );
352
+ return this.executeQuery(result.query, result.params);
353
+ }
354
+
355
+ async upsert(
356
+ table: string,
357
+ data: any,
358
+ conflictColumns: string[],
359
+ returning = '*'
360
+ ): Promise<any[]> {
361
+ await ensureInit();
362
+
363
+ const filters = conflictColumns.map(col => `${col}=eq.${data[col]}`).join('&');
364
+ const result = parseRequest(
365
+ 'PUT',
366
+ table,
367
+ `${filters}&returning=${returning}`,
368
+ JSON.stringify(data),
369
+ null
370
+ );
371
+ return this.executeQuery(result.query, result.params);
372
+ }
373
+
374
+ async update(
375
+ table: string,
376
+ data: any,
377
+ filters: Record<string, string>,
378
+ returning = '*'
379
+ ): Promise<any[]> {
380
+ await ensureInit();
381
+
382
+ const params = new URLSearchParams(filters);
383
+ params.set('returning', returning);
384
+
385
+ const result = parseRequest('PATCH', table, params.toString(), JSON.stringify(data), null);
386
+ return this.executeQuery(result.query, result.params);
387
+ }
388
+
389
+ async delete(table: string, filters: Record<string, string>, returning = 'id'): Promise<any[]> {
390
+ await ensureInit();
391
+
392
+ const params = new URLSearchParams(filters);
393
+ params.set('returning', returning);
394
+
395
+ const result = parseRequest('DELETE', table, params.toString(), null, null);
396
+ return this.executeQuery(result.query, result.params);
397
+ }
398
+
399
+ async rpc(functionName: string, args: any = {}, returning?: string): Promise<any[]> {
400
+ await ensureInit();
401
+
402
+ const queryString = returning ? `returning=${returning}` : '';
403
+ const result = parseRequest(
404
+ 'POST',
405
+ `rpc/${functionName}`,
406
+ queryString,
407
+ Object.keys(args).length > 0 ? JSON.stringify(args) : null,
408
+ null
409
+ );
410
+ return this.executeQuery(result.query, result.params);
411
+ }
412
+ }
413
+
414
+ // Usage:
415
+ // const client = new PostgRESTClient(async (sql, params) => {
416
+ // const { rows } = await db.query(sql, params);
417
+ // return rows;
418
+ // });
419
+ //
420
+ // const users = await client.select('users', {
421
+ // select: 'id,name,email',
422
+ // filters: { 'age': 'gte.18', 'status': 'eq.active' },
423
+ // order: 'name.asc',
424
+ // limit: 10
425
+ // });
426
+ ```
427
+
428
+ ##### 6. Basic Usage (Browser/Deno)
429
+
430
+ ```typescript
431
+ import init, { parseQueryString } from './postgrest_parser.js';
432
+
433
+ // Initialize WASM module (call once)
434
+ await init();
435
+
436
+ // Parse a PostgREST query string
437
+ const result = parseQueryString(
438
+ "users",
439
+ "age=gte.18&status=in.(active,pending)&order=created_at.desc&limit=10"
440
+ );
441
+
442
+ console.log('SQL:', result.query);
443
+ // SELECT * FROM "users" WHERE "age" >= $1 AND "status" = ANY($2) ORDER BY "created_at" DESC LIMIT $3
444
+
445
+ console.log('Params:', result.params);
446
+ // ["18", ["active", "pending"], 10]
447
+
448
+ console.log('Tables:', result.tables);
449
+ // ["users"]
450
+ ```
451
+
452
+ ##### Parse Only (Without SQL Generation)
453
+
454
+ ```typescript
455
+ import init, { parseOnly } from './postgrest_parser.js';
456
+
457
+ await init();
458
+
459
+ // Parse query structure without generating SQL
460
+ const parsed = parseOnly("select=id,name&age=gte.18&limit=10");
461
+
462
+ console.log(parsed);
463
+ // {
464
+ // select: [{ field: "id" }, { field: "name" }],
465
+ // filters: [...],
466
+ // limit: 10,
467
+ // offset: null,
468
+ // order: []
469
+ // }
470
+ ```
471
+
472
+ ##### Using with Deno
473
+
474
+ ```typescript
475
+ // run_parser.ts
476
+ import init, { parseQueryString } from "./pkg/postgrest_parser.js";
477
+
478
+ await init();
479
+
480
+ const result = parseQueryString(
481
+ "posts",
482
+ "select=id,title,author(name)&status=eq.published&created_at=gte.2024-01-01"
483
+ );
484
+
485
+ console.log("Generated SQL:", result.query);
486
+ console.log("Parameters:", result.params);
487
+ ```
488
+
489
+ Run with:
490
+ ```bash
491
+ deno run --allow-read run_parser.ts
492
+ ```
493
+
494
+ ##### Real-World Example: API Endpoint
495
+
496
+ ```typescript
497
+ // api/posts.ts
498
+ import init, { parseQueryString } from '../postgrest_parser.js';
499
+
500
+ // Initialize once at startup
501
+ await init();
502
+
503
+ export async function handlePostsRequest(request: Request) {
504
+ const url = new URL(request.url);
505
+ const queryString = url.search.slice(1); // Remove leading '?'
506
+
507
+ try {
508
+ const result = parseQueryString("posts", queryString);
509
+
510
+ // Execute query with your database client
511
+ const rows = await db.query(result.query, result.params);
512
+
513
+ return new Response(JSON.stringify(rows), {
514
+ headers: { 'Content-Type': 'application/json' }
515
+ });
516
+ } catch (error) {
517
+ return new Response(
518
+ JSON.stringify({ error: error.message }),
519
+ { status: 400 }
520
+ );
521
+ }
522
+ }
523
+ ```
524
+
525
+ ##### JSON Serialization
526
+
527
+ ```typescript
528
+ const result = parseQueryString("users", "age=gte.18");
529
+
530
+ // Convert to plain JSON object
531
+ const json = result.toJSON();
532
+ console.log(JSON.stringify(json, null, 2));
533
+ // {
534
+ // "query": "SELECT * FROM \"users\" WHERE \"age\" >= $1",
535
+ // "params": ["18"],
536
+ // "tables": ["users"]
537
+ // }
538
+ ```
539
+
540
+ #### Complete WASM API Reference
541
+
542
+ The WASM module provides comprehensive TypeScript/JavaScript bindings for all PostgREST operations.
543
+
544
+ **📚 Full Documentation:** See [WASM_API.md](docs/WASM_API.md) for complete API reference with 40+ examples.
545
+
546
+ **Core Functions:**
547
+
548
+ | Function | Purpose | HTTP Method Equivalent |
549
+ |----------|---------|----------------------|
550
+ | `parseRequest(method, path, qs, body?, headers?)` | **Main entry point** - Routes HTTP methods to SQL | All methods |
551
+ | `parseQueryString(table, queryString)` | Direct SELECT generation | GET |
552
+ | `parseInsert(table, body, qs?, headers?)` | Direct INSERT generation | POST |
553
+ | `parseUpdate(table, body, qs, headers?)` | Direct UPDATE generation | PATCH |
554
+ | `parseDelete(table, qs, headers?)` | Direct DELETE generation | DELETE |
555
+ | `parseRpc(function, body?, qs?, headers?)` | Direct RPC call | POST to rpc/* |
556
+ | `parseOnly(queryString)` | Parse without SQL generation | N/A |
557
+ | `buildFilterClause(filters)` | Build WHERE clause from filters | N/A |
558
+
559
+ **Return Type:**
560
+
561
+ ```typescript
562
+ interface WasmQueryResult {
563
+ query: string; // Parameterized SQL with $1, $2, ... placeholders
564
+ params: any[]; // Parameter values (strings, numbers, arrays, etc.)
565
+ tables: string[]; // Referenced table names
566
+ }
567
+ ```
568
+
569
+ **HTTP Method Routing:**
570
+
571
+ ```typescript
572
+ parseRequest("GET", path, qs) // → SELECT
573
+ parseRequest("POST", path, qs) // → INSERT (or RPC if path starts with "rpc/")
574
+ parseRequest("PUT", path, qs) // → UPSERT (auto ON CONFLICT from filters)
575
+ parseRequest("PATCH", path, qs) // → UPDATE
576
+ parseRequest("DELETE", path, qs) // → DELETE
577
+ ```
578
+
579
+ **Examples:** See [examples/wasm_mutations_example.ts](examples/wasm_mutations_example.ts) for 21 comprehensive examples covering all operations.
580
+
581
+ #### Running Examples and Tests
582
+
583
+ **Run comprehensive examples:**
584
+
585
+ ```bash
586
+ # Build WASM
587
+ wasm-pack build --target web --features wasm
588
+
589
+ # Run SELECT examples (20 examples)
590
+ deno run --allow-read examples/wasm_example.ts
591
+
592
+ # Run mutation examples (21 examples: INSERT, UPDATE, DELETE, RPC, HTTP routing)
593
+ deno run --allow-read examples/wasm_mutations_example.ts
594
+ ```
595
+
596
+ **Run integration tests:**
597
+
598
+ ```bash
599
+ # Install Deno (if not already installed)
600
+ curl -fsSL https://deno.land/install.sh | sh
601
+
602
+ # Run WASM integration tests
603
+ deno test --allow-read tests/integration/wasm_test.ts
604
+
605
+ # Or use the Deno task
606
+ deno task test:wasm
607
+ ```
608
+
609
+ See [tests/integration/README.md](tests/integration/README.md) for detailed test documentation.
610
+
611
+ **TypeScript Type Definitions:**
612
+
613
+ The WASM package includes full TypeScript definitions in `pkg/postgrest_parser.d.ts`. Your IDE will automatically provide:
614
+ - IntelliSense/autocomplete for all functions
615
+ - Type checking for parameters and return values
616
+ - JSDoc documentation on hover
617
+
618
+ ```typescript
619
+ import init, { parseRequest, WasmQueryResult } from './postgrest-parser/postgrest_parser.js';
620
+
621
+ // TypeScript knows the exact shape of WasmQueryResult
622
+ const result: WasmQueryResult = parseRequest("GET", "users", "age=gte.18", null, null);
623
+ // ^-- Type: { query: string; params: any[]; tables: string[] }
624
+ ```
625
+
626
+ #### Performance
627
+
628
+ WASM performance benchmarks (from integration tests):
629
+
630
+ - **Average parse time:** ~0.01ms per query
631
+ - **100 queries:** ~1ms total
632
+ - **Throughput:** ~100,000 queries/second in browser
633
+
634
+ The WASM build maintains near-native performance while running in JavaScript environments.
635
+
636
+ #### Browser Compatibility
637
+
638
+ Tested and working in:
639
+ - ✅ Chrome 90+
640
+ - ✅ Firefox 89+
641
+ - ✅ Safari 15+
642
+ - ✅ Edge 90+
643
+ - ✅ Deno 1.x+
644
+ - ✅ Node.js 16+ (with `--target nodejs`)
645
+
646
+ #### Troubleshooting
647
+
648
+ **Module not found:**
649
+ ```typescript
650
+ // Make sure to use the correct path to pkg/
651
+ import init from './pkg/postgrest_parser.js'; // ✅
652
+ import init from './postgrest_parser.js'; // ❌
653
+ ```
654
+
655
+ **WASM initialization:**
656
+ ```typescript
657
+ // Always call init() before using other functions
658
+ await init(); // ✅
659
+ parseQueryString(...); // ✅
660
+
661
+ parseQueryString(...); // ❌ Will fail - init() not called
662
+ ```
663
+
664
+ **Type errors in TypeScript:**
665
+ ```typescript
666
+ // Use generated .d.ts files
667
+ import init, { parseQueryString } from './pkg/postgrest_parser.js';
668
+ // Type definitions are in ./pkg/postgrest_parser.d.ts
669
+ ```
670
+
671
+ ## Usage
672
+
673
+ ### Rust: Basic Query Parsing
674
+
675
+ ```rust
676
+ use postgrest_parser::*;
677
+
678
+ // Parse a query string
679
+ let params = parse_query_string("select=id,name&id=eq.1&order=id.desc&limit=10")?;
680
+ assert!(params.has_select());
681
+ assert!(params.has_filters());
682
+
683
+ // Generate SQL
684
+ let result = to_sql("users", &params)?;
685
+ println!("Query: {}", result.query);
686
+ println!("Params: {:?}", result.params);
687
+ // Output:
688
+ // Query: SELECT "id", "name" FROM "users" WHERE "id" = $1 ORDER BY "id" DESC LIMIT $2
689
+ // Params: [String("1"), Number(10)]
690
+ ```
691
+
692
+ ### Filter Operators
693
+
694
+ #### Comparison Operators
695
+
696
+ ```rust
697
+ // Equality
698
+ let params = parse_query_string("id=eq.1")?; // WHERE "id" = $1
699
+ let params = parse_query_string("status=neq.deleted")?; // WHERE "status" <> $1
700
+
701
+ // Comparison
702
+ let params = parse_query_string("age=gt.18")?; // WHERE "age" > $1
703
+ let params = parse_query_string("age=gte.18")?; // WHERE "age" >= $1
704
+ let params = parse_query_string("age=lt.65")?; // WHERE "age" < $1
705
+ let params = parse_query_string("age=lte.65")?; // WHERE "age" <= $1
706
+
707
+ // Negation works with all operators
708
+ let params = parse_query_string("age=not.gt.18")?; // WHERE "age" <= $1
709
+ ```
710
+
711
+ #### Pattern Matching
712
+
713
+ ```rust
714
+ // SQL LIKE operators
715
+ let params = parse_query_string("name=like.*Smith%")?; // WHERE "name" LIKE $1
716
+ let params = parse_query_string("name=ilike.*smith%")?; // WHERE "name" ILIKE $1 (case-insensitive)
717
+
718
+ // POSIX regex
719
+ let params = parse_query_string("name=match.^John")?; // WHERE "name" ~ $1
720
+ let params = parse_query_string("name=imatch.^john")?; // WHERE "name" ~* $1 (case-insensitive)
721
+ ```
722
+
723
+ #### List and Array Operators
724
+
725
+ ```rust
726
+ // IN operator
727
+ let params = parse_query_string("status=in.(active,pending)")?; // WHERE "status" = ANY($1)
728
+
729
+ // Array contains
730
+ let params = parse_query_string("tags=cs.{rust}")?; // WHERE "tags" @> $1
731
+ let params = parse_query_string("tags=cd.{rust,elixir}")?; // WHERE "tags" <@ $1
732
+
733
+ // Array overlap
734
+ let params = parse_query_string("tags=ov.(rust,elixir)")?; // WHERE "tags" && $1
735
+ ```
736
+
737
+ #### Full-Text Search
738
+
739
+ ```rust
740
+ // Basic FTS (uses plainto_tsquery)
741
+ let params = parse_query_string("content=fts.search term")?;
742
+ // WHERE to_tsvector('english', "content") @@ plainto_tsquery('english', $1)
743
+
744
+ // With custom language
745
+ let params = parse_query_string("content=fts(french).terme")?;
746
+ // WHERE to_tsvector('french', "content") @@ plainto_tsquery('french', $1)
747
+
748
+ // Phrase search
749
+ let params = parse_query_string("content=phfts.exact phrase")?;
750
+ // WHERE to_tsvector('english', "content") @@ phraseto_tsquery('english', $1)
751
+
752
+ // Websearch (most lenient)
753
+ let params = parse_query_string("content=wfts.search query")?;
754
+ // WHERE to_tsvector('english', "content") @@ websearch_to_tsquery('english', $1)
755
+ ```
756
+
757
+ #### Range Operators (PostgreSQL ranges)
758
+
759
+ ```rust
760
+ let params = parse_query_string("range=sl.[1,10)")?; // WHERE "range" << $1 (strictly left)
761
+ let params = parse_query_string("range=sr.[1,10)")?; // WHERE "range" >> $1 (strictly right)
762
+ let params = parse_query_string("range=nxl.[1,10)")?; // WHERE "range" &< $1
763
+ let params = parse_query_string("range=nxr.[1,10)")?; // WHERE "range" &> $1
764
+ let params = parse_query_string("range=adj.[1,10)")?; // WHERE "range" -|- $1 (adjacent)
765
+ ```
766
+
767
+ #### Special Operators
768
+
769
+ ```rust
770
+ // IS operator
771
+ let params = parse_query_string("deleted_at=is.null")?; // WHERE "deleted_at" IS NULL
772
+ let params = parse_query_string("deleted_at=is.not_null")?; // WHERE "deleted_at" IS NOT NULL
773
+ let params = parse_query_string("active=is.true")?; // WHERE "active" IS TRUE
774
+ let params = parse_query_string("active=is.false")?; // WHERE "active" IS FALSE
775
+ ```
776
+
777
+ #### Quantifiers
778
+
779
+ ```rust
780
+ // ANY quantifier
781
+ let params = parse_query_string("tags=eq(any).{rust,elixir}")?; // WHERE "tags" = ANY($1)
782
+
783
+ // ALL quantifier
784
+ let params = parse_query_string("tags=eq(all).{rust}")?; // WHERE "tags" = ALL($1)
785
+ ```
786
+
787
+ #### JSON Path Navigation
788
+
789
+ ```rust
790
+ // Arrow operator (returns JSON)
791
+ let params = parse_query_string("data->name=eq.test")?;
792
+ // WHERE "data"->'name' = $1
793
+
794
+ // Double arrow operator (returns text)
795
+ let params = parse_query_string("data->>email=like.*@example.com")?;
796
+ // WHERE "data"->>'email' LIKE $1
797
+
798
+ // Nested paths
799
+ let params = parse_query_string("data->user->name=eq.John")?;
800
+ // WHERE "data"->'user'->'name' = $1
801
+ ```
802
+
803
+ #### Type Casting
804
+
805
+ ```rust
806
+ let params = parse_query_string("price::numeric=gt.100")?;
807
+ // WHERE "price"::numeric > $1
808
+
809
+ let params = parse_query_string("data->age::int=gte.18")?;
810
+ // WHERE ("data"->'age')::int >= $1
811
+ ```
812
+
813
+ ### Logic Trees
814
+
815
+ ```rust
816
+ // AND conditions
817
+ let params = parse_query_string("and=(id.eq.1,name.eq.john)")?;
818
+
819
+ // OR conditions
820
+ let params = parse_query_string("or=(status.eq.pending,status.eq.processing)")?;
821
+
822
+ // Nested logic
823
+ let params = parse_query_string("and=(id.eq.1,or(status.eq.active,status.eq.pending))")?;
824
+
825
+ // Negated logic
826
+ let params = parse_query_string("not.and=(id.eq.1,name.eq.john)")?;
827
+ ```
828
+
829
+ ### Select with Relations
830
+
831
+ ```rust
832
+ let params = parse_query_string("select=id,client(id,name),posts(title)")?;
833
+ assert!(params.select.is_some());
834
+ ```
835
+
836
+ ### Ordering
837
+
838
+ ```rust
839
+ // Single column
840
+ let params = parse_query_string("order=id.desc")?;
841
+
842
+ // Multiple columns
843
+ let params = parse_query_string("order=id.desc,name.asc")?;
844
+
845
+ // With nulls handling
846
+ let params = parse_query_string("order=id.desc.nullslast")?;
847
+ ```
848
+
849
+ ## Development
850
+
851
+ ### Building
852
+
853
+ ```bash
854
+ # Native
855
+ cargo build --release
856
+
857
+ # WASM
858
+ cargo build --release --target wasm32-unknown-unknown
859
+ wasm-pack build --target web
860
+ ```
861
+
862
+ ### Testing
863
+
864
+ ```bash
865
+ # Run all tests
866
+ cargo test
867
+
868
+ # Run specific test
869
+ cargo test test_parse_query_string
870
+
871
+ # Run with output
872
+ cargo test -- --nocapture
873
+ ```
874
+
875
+ ### Benchmarks
876
+
877
+ ```bash
878
+ # Run all benchmarks
879
+ cargo bench
880
+
881
+ # Run specific benchmark group
882
+ cargo bench simple_parsing
883
+ cargo bench realistic_workloads
884
+
885
+ # See docs/BENCHMARKS.md for detailed results and analysis
886
+ ```
887
+
888
+ #### Latest Benchmark Results
889
+
890
+ Benchmarked on Darwin 24.6.0 (macOS) with release optimizations.
891
+
892
+ **Simple Parsing Performance:**
893
+
894
+ | Operation | Time (median) | Throughput |
895
+ |-----------|---------------|------------|
896
+ | `select=id,name,email` | 1.14 µs | 880K ops/s |
897
+ | `age=gte.18` | 781 ns | 1.28M ops/s |
898
+ | `order=created_at.desc` | 1.27 µs | 790K ops/s |
899
+ | `limit=10&offset=20` | 520 ns | 1.92M ops/s |
900
+
901
+ **Realistic Workload Performance:**
902
+
903
+ | Workload | Time (median) | Throughput | Description |
904
+ |----------|---------------|------------|-------------|
905
+ | User Search | 7.19 µs | 139K ops/s | SELECT + 2 filters + ORDER + LIMIT |
906
+ | Paginated List | 6.84 µs | 146K ops/s | SELECT + relation + filter + ORDER + pagination |
907
+ | Filtered Report | 9.92 µs | 101K ops/s | SELECT + relation + 4 filters + ORDER |
908
+ | Complex Search | 10.66 µs | 94K ops/s | SELECT + FTS + array ops + 3 filters + ORDER |
909
+ | Dashboard Aggregation | 7.84 µs | 128K ops/s | Complex logic tree + date range + ORDER |
910
+
911
+ **Operator Performance:**
912
+
913
+ | Operator Category | Example | Time (median) |
914
+ |-------------------|---------|---------------|
915
+ | Comparison (`eq`, `gte`) | `id=eq.1` | ~750-783 ns |
916
+ | Pattern Match | `name=like.*Smith*` | ~783-803 ns |
917
+ | List Operations | `status=in.(a,b,c)` | ~1.07 µs |
918
+ | Full-Text Search | `content=fts.term` | ~941 ns |
919
+ | FTS with Language | `content=fts(french).terme` | ~974 ns |
920
+ | Array Operations | `tags=cs.{rust,elixir}` | ~787-1.06 µs |
921
+ | Range Operations | `range=sl.[1,10)` | ~1.01 µs |
922
+ | JSON Path | `data->name=eq.test` | ~934-1.13 µs |
923
+ | Type Casting | `price::numeric=gt.100` | ~1.10 µs |
924
+ | Quantifiers | `tags=eq(any).{a,b}` | ~907-1.04 µs |
925
+
926
+ **SQL Generation (End-to-End):**
927
+
928
+ | Query Type | Time (median) | Throughput |
929
+ |------------|---------------|------------|
930
+ | Simple SELECT | 2.21 µs | 452K ops/s |
931
+ | With Filters | 3.03 µs | 330K ops/s |
932
+ | With ORDER | 3.75 µs | 267K ops/s |
933
+ | Complex Query | 7.57 µs | 132K ops/s |
934
+
935
+ **Query Scaling:**
936
+
937
+ - 1 filter: 1.97 µs
938
+ - 3 filters: 4.09 µs (2.1x)
939
+ - 5 filters: 6.79 µs (3.4x)
940
+ - 10 filters: 13.40 µs (6.8x)
941
+
942
+ **Performance vs Reference Implementation:**
943
+ - Simple queries: **1.3-6x faster**
944
+ - Complex queries: **1.3x faster**
945
+ - All operations under 15 µs
946
+
947
+ See [BENCHMARKS.md](docs/BENCHMARKS.md) for complete performance analysis.
948
+
949
+ ## Complete Operator Reference
950
+
951
+ | Operator | PostgREST | SQL | Example |
952
+ |----------|-----------|-----|---------|
953
+ | `eq` | Equal | `=` | `id=eq.1` |
954
+ | `neq` | Not equal | `<>` | `status=neq.deleted` |
955
+ | `gt` | Greater than | `>` | `age=gt.18` |
956
+ | `gte` | Greater than or equal | `>=` | `age=gte.18` |
957
+ | `lt` | Less than | `<` | `age=lt.65` |
958
+ | `lte` | Less than or equal | `<=` | `age=lte.65` |
959
+ | `like` | LIKE pattern | `LIKE` | `name=like.*Smith*` |
960
+ | `ilike` | Case-insensitive LIKE | `ILIKE` | `name=ilike.*smith*` |
961
+ | `match` | POSIX regex | `~` | `name=match.^John` |
962
+ | `imatch` | Case-insensitive regex | `~*` | `name=imatch.^john` |
963
+ | `in` | In list | `= ANY($1)` | `status=in.(active,pending)` |
964
+ | `is` | IS check | `IS` | `deleted=is.null` |
965
+ | `fts` | Full-text search | `@@` | `content=fts.search` |
966
+ | `plfts` | Plain FTS | `@@` | `content=plfts.search` |
967
+ | `phfts` | Phrase FTS | `@@` | `content=phfts.exact phrase` |
968
+ | `wfts` | Websearch FTS | `@@` | `content=wfts.query` |
969
+ | `cs` | Contains | `@>` | `tags=cs.{rust}` |
970
+ | `cd` | Contained in | `<@` | `tags=cd.{rust,elixir}` |
971
+ | `ov` | Overlaps | `&&` | `tags=ov.(rust,elixir)` |
972
+ | `sl` | Strictly left | `<<` | `range=sl.[1,10)` |
973
+ | `sr` | Strictly right | `>>` | `range=sr.[1,10)` |
974
+ | `nxl` | Not extends right | `&<` | `range=nxl.[1,10)` |
975
+ | `nxr` | Not extends left | `&>` | `range=nxr.[1,10)` |
976
+ | `adj` | Adjacent | `-|-` | `range=adj.[1,10)` |
977
+
978
+ ## Performance
979
+
980
+ - **Zero-copy parsing** where possible with nom combinators
981
+ - **No regex usage** - all parsing done with efficient pattern matching
982
+ - **148 passing tests** with comprehensive coverage
983
+ - **Optimized for Rust** - leverages Rust's zero-cost abstractions
984
+
985
+ ## Architecture
986
+
987
+ - **AST** ([src/ast/](src/ast/)): Typed intermediate representation of parsed queries
988
+ - **Parser** ([src/parser/](src/parser/)): nom-based combinator parsers (no regex)
989
+ - `common.rs` - Shared parsing utilities (identifiers, lists, JSON paths)
990
+ - `filter.rs` - Filter/operator parsing
991
+ - `logic.rs` - Logic tree parsing (AND/OR/NOT)
992
+ - `order.rs` - ORDER BY clause parsing
993
+ - `select.rs` - SELECT clause parsing with relations
994
+ - **SQL Builder** ([src/sql/](src/sql/)): Parameterized PostgreSQL SQL generation
995
+ - **Error Handling** ([src/error/](src/error/)): Typed errors using thiserror
996
+
997
+ ## Development
998
+
999
+ ### Building
1000
+
1001
+ ```bash
1002
+ # Native
1003
+ cargo build --release
1004
+
1005
+ # With all features
1006
+ cargo build --release --features full
1007
+
1008
+ # WASM (if you need browser support)
1009
+ cargo build --release --target wasm32-unknown-unknown --features wasm
1010
+ wasm-pack build --target web
1011
+ ```
1012
+
1013
+ ### Testing
1014
+
1015
+ ```bash
1016
+ # Run all Rust tests (148 tests)
1017
+ cargo test
1018
+
1019
+ # Run specific test
1020
+ cargo test test_parse_query_string
1021
+
1022
+ # Run with output
1023
+ cargo test -- --nocapture
1024
+
1025
+ # Check code quality
1026
+ cargo clippy -- -D warnings
1027
+
1028
+ # Run WASM integration tests (23 tests)
1029
+ # Requires Deno: https://deno.land
1030
+ wasm-pack build --target web --features wasm
1031
+ deno test --allow-read tests/integration/wasm_test.ts
1032
+
1033
+ # Or use Deno task
1034
+ deno task test:wasm
1035
+ ```
1036
+
1037
+ ### Code Quality
1038
+
1039
+ ```bash
1040
+ # Format code
1041
+ cargo fmt
1042
+
1043
+ # Run linter
1044
+ cargo clippy
1045
+
1046
+ # Check for security vulnerabilities
1047
+ cargo audit
1048
+ ```
1049
+
1050
+ ## Roadmap
1051
+
1052
+ - [x] Complete PostgREST filter operator support (22+ operators)
1053
+ - [x] Logic trees with arbitrary nesting
1054
+ - [x] Full-text search with language support
1055
+ - [x] Array and range operators
1056
+ - [x] Quantifiers (any/all)
1057
+ - [x] Comprehensive test coverage (148 tests)
1058
+ - [x] WASM bindings for TypeScript/JavaScript with Deno integration tests
1059
+ - [x] Benchmark suite comparing to reference implementation
1060
+ - [ ] Count aggregation support
1061
+ - [ ] `on_conflict` parameter support
1062
+ - [ ] Relation column filtering
1063
+
1064
+ ## Contributing
1065
+
1066
+ Contributions are welcome! Areas of interest:
1067
+
1068
+ - Additional test cases and edge cases
1069
+ - Performance optimizations
1070
+ - WASM/JavaScript bindings
1071
+ - Documentation improvements
1072
+ - Bug reports and fixes
1073
+
1074
+ ## License
1075
+
1076
+ MIT
1077
+
1078
+ ## Acknowledgments
1079
+
1080
+ - Inspired by the [PostgREST](https://postgrest.org/) project
1081
+ - Parser built with [nom](https://github.com/rust-bakery/nom)
1082
+ - Reference implementation: [postgrest_parser (Elixir)](https://github.com/supabase/postgrest_parser)
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "postgrest-parser",
3
+ "version": "0.1.0",
4
+ "description": "PostgREST URL-to-SQL parser with WASM bindings for TypeScript/JavaScript",
5
+ "main": "pkg/postgrest_parser.js",
6
+ "types": "postgrest_parser.d.ts",
7
+ "type": "module",
8
+ "scripts": {
9
+ "build": "wasm-pack build --target web --out-dir pkg -- --features wasm && cd pkg && npm install && npm run build",
10
+ "build:wasm": "wasm-pack build --target web --out-dir pkg -- --features wasm",
11
+ "build:node": "wasm-pack build --target nodejs --out-dir pkg-node -- --features wasm",
12
+ "build:bundler": "wasm-pack build --target bundler --out-dir pkg-bundler -- --features wasm",
13
+ "test:wasm": "wasm-pack test --headless --firefox -- --features wasm",
14
+ "example": "tsx example.ts"
15
+ },
16
+ "keywords": [
17
+ "postgrest",
18
+ "sql",
19
+ "parser",
20
+ "postgresql",
21
+ "wasm",
22
+ "typescript",
23
+ "query-builder"
24
+ ],
25
+ "author": "Your Name",
26
+ "license": "MIT",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/your-org/postgrest-parser-rust"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^20.0.0",
33
+ "tsx": "^4.0.0"
34
+ },
35
+ "files": [
36
+ "pkg/**/*",
37
+ "postgrest_parser.d.ts",
38
+ "README.md",
39
+ "LICENSE"
40
+ ],
41
+ "engines": {
42
+ "node": ">=18.0.0"
43
+ }
44
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * TypeScript definitions for postgrest-parser WASM bindings
3
+ *
4
+ * @module postgrest-parser
5
+ */
6
+
7
+ /**
8
+ * Query result containing SQL, parameters, and affected tables
9
+ */
10
+ export interface QueryResult {
11
+ /**
12
+ * The generated PostgreSQL SELECT query with parameter placeholders ($1, $2, etc.)
13
+ */
14
+ readonly query: string;
15
+
16
+ /**
17
+ * Array of parameter values corresponding to placeholders in the query
18
+ */
19
+ readonly params: any[];
20
+
21
+ /**
22
+ * List of table names referenced in the query
23
+ */
24
+ readonly tables: string[];
25
+
26
+ /**
27
+ * Convert the result to a plain JSON object
28
+ */
29
+ toJSON(): QueryResult;
30
+ }
31
+
32
+ /**
33
+ * Filter clause result containing WHERE clause and parameters
34
+ */
35
+ export interface FilterClauseResult {
36
+ /**
37
+ * The WHERE clause SQL fragment (without the "WHERE" keyword)
38
+ */
39
+ clause: string;
40
+
41
+ /**
42
+ * Parameter values referenced in the clause
43
+ */
44
+ params: any[];
45
+ }
46
+
47
+ /**
48
+ * Parse a PostgREST query string and convert it to SQL.
49
+ *
50
+ * @param table - The table name to query
51
+ * @param queryString - The PostgREST query string (e.g., "select=id,name&age=gte.18")
52
+ * @returns Query result with SQL, params, and tables
53
+ * @throws Error if parsing or SQL generation fails
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * const result = parseQueryString("users", "age=gte.18&status=eq.active&limit=10");
58
+ * console.log(result.query); // SELECT * FROM "users" WHERE "age" >= $1 AND "status" = $2 LIMIT $3
59
+ * console.log(result.params); // ["18", "active", 10]
60
+ * console.log(result.tables); // ["users"]
61
+ * ```
62
+ */
63
+ export function parseQueryString(table: string, queryString: string): QueryResult;
64
+
65
+ /**
66
+ * Parse a query string without generating SQL.
67
+ *
68
+ * Useful for inspecting the parsed structure or validating queries.
69
+ *
70
+ * @param queryString - The PostgREST query string
71
+ * @returns Parsed parameters as a JSON object
72
+ * @throws Error if parsing fails
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * const parsed = parseOnly("age=gte.18&order=name.asc");
77
+ * console.log(parsed.filters); // Array of filter conditions
78
+ * console.log(parsed.order); // Array of order terms
79
+ * ```
80
+ */
81
+ export function parseOnly(queryString: string): any;
82
+
83
+ /**
84
+ * Build a WHERE clause from filter conditions.
85
+ *
86
+ * @param filters - JSON array of filter conditions
87
+ * @returns Object with clause (SQL string) and params (array of values)
88
+ * @throws Error if building the clause fails
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * const filters = [{
93
+ * Filter: {
94
+ * field: { name: "age", json_path: [], cast: null },
95
+ * operator: "Gte",
96
+ * value: { Single: "18" },
97
+ * quantifier: null,
98
+ * language: null,
99
+ * negated: false
100
+ * }
101
+ * }];
102
+ *
103
+ * const result = buildFilterClause(filters);
104
+ * console.log(result.clause); // "age" >= $1
105
+ * console.log(result.params); // ["18"]
106
+ * ```
107
+ */
108
+ export function buildFilterClause(filters: any[]): FilterClauseResult;
109
+
110
+ /**
111
+ * Initialize the WASM module. Must be called before using any functions.
112
+ *
113
+ * @example
114
+ * ```typescript
115
+ * import init, { parseQueryString } from './postgrest_parser.js';
116
+ *
117
+ * await init();
118
+ * const result = parseQueryString("users", "id=eq.1");
119
+ * ```
120
+ */
121
+ export default function init(input?: RequestInfo | URL): Promise<void>;