cloudflare-next-intl 0.8.6 → 0.8.8
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/README.md +101 -71
- package/dist/src/db/context.js +0 -8
- package/dist/src/db/eslint_config.d.ts +21 -0
- package/dist/src/db/eslint_config.js +29 -0
- package/dist/src/db/index.d.ts +5 -8
- package/dist/src/db/index.js +5 -7
- package/dist/src/db/parse_statement.d.ts +76 -0
- package/dist/src/db/parse_statement.js +388 -0
- package/dist/src/db/parse_where.d.ts +59 -0
- package/dist/src/db/parse_where.js +235 -0
- package/dist/src/db/rest_client.d.ts +58 -0
- package/dist/src/db/rest_client.js +26 -0
- package/dist/src/db/rest_execute.d.ts +25 -0
- package/dist/src/db/rest_execute.js +113 -0
- package/dist/src/db/rest_filters.d.ts +59 -0
- package/dist/src/db/rest_filters.js +130 -0
- package/dist/src/db/sql_tokens.d.ts +33 -0
- package/dist/src/db/sql_tokens.js +96 -0
- package/dist/src/db/supabase_transport.d.ts +5 -10
- package/dist/src/db/supabase_transport.js +32 -25
- package/dist/src/db/unsupported_sql.d.ts +16 -0
- package/dist/src/db/unsupported_sql.js +18 -0
- package/dist/src/types/types.d.ts +6 -10
- package/llms.txt +25 -3
- package/package.json +5 -1
- package/supabase/cfni_exec.sql +2 -0
package/README.md
CHANGED
|
@@ -74,6 +74,48 @@ export default nextConfig;
|
|
|
74
74
|
If this alias is missing, any import from `cloudflare-next-intl` throws an
|
|
75
75
|
error naming the missing `@intl-config` alias at startup.
|
|
76
76
|
|
|
77
|
+
### 2b. Deploying to Cloudflare Workers: alias it in `wrangler.toml` too
|
|
78
|
+
|
|
79
|
+
`wrangler deploy`/`wrangler dev` bundle the Worker with their own esbuild
|
|
80
|
+
pass, separate from Next's webpack/Turbopack build — the `next.config`
|
|
81
|
+
alias above doesn't apply to it. Without this, `wrangler deploy` fails with
|
|
82
|
+
`Could not resolve "@intl-config"`. Path is relative to `wrangler.toml`.
|
|
83
|
+
|
|
84
|
+
```toml
|
|
85
|
+
# wrangler.toml
|
|
86
|
+
[alias]
|
|
87
|
+
"@intl-config" = "./src/i18n/intl_config.ts"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Put `[alias]` after all top-level scalar keys (`name`, `main`,
|
|
91
|
+
`compatibility_date`, `compatibility_flags`, etc.) and before any other
|
|
92
|
+
`[table]`/`[[array_of_tables]]` header — TOML assigns every key that follows
|
|
93
|
+
a table header to that table until the next header, so an `[alias]` placed
|
|
94
|
+
earlier silently swallows the keys meant for the top level.
|
|
95
|
+
|
|
96
|
+
**In a monorepo, give every Worker its own alias target — never point two
|
|
97
|
+
Workers' `[alias]` at the same shared config file.** Declaring `[alias]`
|
|
98
|
+
makes Wrangler eagerly resolve and bundle whatever it points to, even if
|
|
99
|
+
nothing in that Worker's own code imports `@intl-config`. If a second,
|
|
100
|
+
smaller Worker (e.g. a cron/background worker) points its alias at the main
|
|
101
|
+
app's config file, it inherits that file's whole import graph — every
|
|
102
|
+
package the main app's config touches (Firebase, a DB client, `zod`, etc.)
|
|
103
|
+
must then be a dependency of the small Worker too, or the build fails with
|
|
104
|
+
`Could not resolve "<package>"` for packages that Worker never actually
|
|
105
|
+
uses. Give it its own minimal file instead:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
// backend/src/i18n/intl_config.ts — deliberately minimal, not the main
|
|
109
|
+
// app's config: aliasing it there would drag in every dependency that
|
|
110
|
+
// config touches, even ones this Worker never imports.
|
|
111
|
+
import { setIntlConfig } from "cloudflare-next-intl";
|
|
112
|
+
|
|
113
|
+
export default setIntlConfig({
|
|
114
|
+
locales: ["en"],
|
|
115
|
+
defaultLocale: "en",
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
77
119
|
### 3. Wire up the middleware
|
|
78
120
|
|
|
79
121
|
```typescript
|
|
@@ -535,82 +577,60 @@ so arrays/`numeric`/timestamps/`bytea` decode the same way they do in
|
|
|
535
577
|
connection-string mode. Not supported: multi-statement transactions (each
|
|
536
578
|
call is its own PostgREST round-trip — see above).
|
|
537
579
|
|
|
538
|
-
#### Supabase mode
|
|
580
|
+
#### Supabase mode and REST translation
|
|
539
581
|
|
|
540
|
-
|
|
541
|
-
false`. `withPublicDb`/`withUserDb` then throw immediately in Supabase mode
|
|
542
|
-
instead of failing later against PostgREST, and you reach Postgres instead
|
|
543
|
-
through a set of helpers that call `@supabase/supabase-js`'s `.from()`/`.rpc()`
|
|
544
|
-
API directly — the same REST surface the Supabase client itself uses, no raw
|
|
545
|
-
SQL involved:
|
|
582
|
+
You write the exact same Drizzle code with `withPublicDb`/`withUserDb` in Supabase mode as in connection-string mode. Behind the scenes, the transport translates each statement into `@supabase/supabase-js` `.from()` PostgREST calls whenever possible:
|
|
546
583
|
|
|
547
584
|
```typescript
|
|
548
|
-
import {
|
|
585
|
+
import { withPublicDb, withUserDb } from "cloudflare-next-intl/db";
|
|
586
|
+
import { eq, gte, desc } from "cloudflare-next-intl/dbHelpers";
|
|
587
|
+
|
|
588
|
+
// Automatically translated to PostgREST REST calls:
|
|
589
|
+
const rows = await withPublicDb((db) =>
|
|
590
|
+
db.select({ id: bonds.id, name: bonds.name, yield: bonds.yield })
|
|
591
|
+
.from(bonds)
|
|
592
|
+
.where(gte(bonds.yield, 5))
|
|
593
|
+
.orderBy(desc(bonds.yield))
|
|
594
|
+
.limit(10)
|
|
595
|
+
);
|
|
596
|
+
|
|
597
|
+
await withPublicDb((db) => db.insert(bonds).values({ name: "10Y", yield: 4.2 }));
|
|
598
|
+
await withPublicDb((db) =>
|
|
599
|
+
db.insert(bonds)
|
|
600
|
+
.values({ id: 1, name: "10Y", yield: 4.5 })
|
|
601
|
+
.onConflictDoUpdate({ target: bonds.id, set: { yield: 4.5 } })
|
|
602
|
+
);
|
|
603
|
+
await withPublicDb((db) => db.update(bonds).set({ yield: 4.3 }).where(eq(bonds.id, 1)));
|
|
604
|
+
await withPublicDb((db) => db.delete(bonds).where(eq(bonds.id, 1)));
|
|
605
|
+
```
|
|
549
606
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
607
|
+
**Transport routing:**
|
|
608
|
+
1. **REST translation first:** Single-table `SELECT`, `INSERT`, `UPDATE`, `DELETE`, `on conflict`, and `returning` are mapped to `@supabase/supabase-js` `.from()` calls.
|
|
609
|
+
2. **`cfni_exec` fallback:** Statements PostgREST cannot express (joins, CTEs, complex subqueries) fall back to the `cfni_exec` SQL-exec function.
|
|
610
|
+
3. **`rawSql: false` mode:** If `db.supabase.rawSql: false` is configured (when `cfni_exec` is not installed), any statement requiring raw SQL throws an error explaining the limitation and how to resolve it.
|
|
611
|
+
|
|
612
|
+
| Operation | REST API | `cfni_exec` fallback |
|
|
613
|
+
| --- | --- | --- |
|
|
614
|
+
| Single-table `SELECT` (projections, `count(*)`, `WHERE`, `ORDER BY`, `LIMIT`, `OFFSET`) | Supported | Used if untranslatable |
|
|
615
|
+
| Single-table `INSERT` / `UPDATE` / `DELETE` | Supported | Used if untranslatable |
|
|
616
|
+
| `ON CONFLICT DO NOTHING / UPDATE` | Supported | Supported |
|
|
617
|
+
| `RETURNING` clauses | Supported | Supported |
|
|
618
|
+
| Operators: `=`, `<>`, `!=`, `>`, `>=`, `<`, `<=`, `like`, `ilike`, `is [not] null`, `[not] in`, `is [not] distinct from`, `~`, `~*`, `@>`, `<@`, `&&`, `>>`, `<<`, `&>`, `&<`, `-|-`, `@@` | Supported | Supported |
|
|
619
|
+
| Multi-table joins, CTEs, non-count aggregates, `GROUP BY`, `UNION`, `DISTINCT`, raw SQL | Not supported by REST | Supported |
|
|
620
|
+
| Multi-statement transactions | Not supported | Not supported (Postgres connection only) |
|
|
621
|
+
|
|
622
|
+
#### Enforcing single API via ESLint (`cloudflare-next-intl/dbEslint`)
|
|
623
|
+
|
|
624
|
+
To prevent application code from bypassing the single `db` API with direct driver imports (`@supabase/supabase-js`, `pg`, `postgres`, or deep `dist/` paths), spread the shipped flat-config fragment in your `eslint.config.js`:
|
|
625
|
+
|
|
626
|
+
```typescript
|
|
627
|
+
import dbEslint from "cloudflare-next-intl/dbEslint";
|
|
555
628
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
// Anything a plain select/insert/update/delete/upsert can't express (joins,
|
|
562
|
-
// aggregates, custom logic) — write a Postgres function, `grant execute` to
|
|
563
|
-
// `anon`/`authenticated`, and call it by name. No cfni_exec needed for this
|
|
564
|
-
// either, since PostgREST always supports calling a named function you own.
|
|
565
|
-
const total = await supabaseRpc("total_yield", { bond_ids: [1, 2, 3] });
|
|
566
|
-
```
|
|
567
|
-
|
|
568
|
-
Each has a `*AsUser` counterpart (`supabaseSelectAsUser`,
|
|
569
|
-
`supabaseInsertAsUser`, `supabaseUpsertAsUser`, `supabaseUpdateAsUser`,
|
|
570
|
-
`supabaseDeleteAsUser`, `supabaseRpcAsUser`) that authenticates as the
|
|
571
|
-
signed-in user instead of anon — same token resolution as `withUserDb`
|
|
572
|
-
(`db.getAccessToken`, or the signed-in Firebase user's ID token), so RLS
|
|
573
|
-
applies the same way.
|
|
574
|
-
|
|
575
|
-
`supabaseSelect`/`supabaseSelectAsUser` accept:
|
|
576
|
-
|
|
577
|
-
- `columns` — PostgREST column-list syntax. Defaults to `'*'`. Supports an
|
|
578
|
-
embedded resource for a foreign-key join in one round-trip, e.g.
|
|
579
|
-
`'*, author(name)'`.
|
|
580
|
-
- `where` — filters, ANDed together. A bare value (`{ id: 5 }`) is shorthand
|
|
581
|
-
for `eq`; use `{ column: [operator, value] }` for any other operator —
|
|
582
|
-
`neq`, `gt`, `gte`, `lt`, `lte`, `like`, `likeAllOf`, `likeAnyOf`, `ilike`,
|
|
583
|
-
`ilikeAllOf`, `ilikeAnyOf`, `regexMatch`, `regexIMatch`, `is`,
|
|
584
|
-
`isDistinct`, `in`, `contains`, `containedBy`, `overlaps`, `rangeGt`,
|
|
585
|
-
`rangeGte`, `rangeLt`, `rangeLte`, or `rangeAdjacent` — or
|
|
586
|
-
`{ column: ['not', operator, value] }` to negate any of them. This is the
|
|
587
|
-
complete operator set `@supabase/supabase-js`'s `.from()` builder exposes
|
|
588
|
-
(it *is* a `postgrest-js` builder — there's no separate, larger "Supabase
|
|
589
|
-
client" query surface on top of it).
|
|
590
|
-
- `match` — shorthand for several `eq` filters at once (`{ status: 'active',
|
|
591
|
-
archived: false }`). ANDed with `where`.
|
|
592
|
-
- `or` — a raw PostgREST `or()` filter string (e.g.
|
|
593
|
-
`'age.gt.18,status.eq.active'`) for filters spanning multiple columns in
|
|
594
|
-
one clause; ANDed with `where`/`match`.
|
|
595
|
-
- `textSearch` — `{ column, query, type?, config? }` for full-text search via
|
|
596
|
-
PostgREST's `@@` operators.
|
|
597
|
-
- `orderBy` — one `{ column, ascending?, nullsFirst? }`, or an array for
|
|
598
|
-
multiple `order by` clauses.
|
|
599
|
-
- `limit` / `range` — row limit, or a `[from, to]` PostgREST pagination range.
|
|
600
|
-
- `single` / `maybeSingle` — resolve to one row (erroring if there isn't
|
|
601
|
-
exactly one), or one row or `null` (erroring if there's more than one).
|
|
602
|
-
- `count` — also return the total matching row count (`'exact'`, `'planned'`,
|
|
603
|
-
or `'estimated'`).
|
|
604
|
-
|
|
605
|
-
`supabaseUpdate`/`supabaseDelete` (and their `*AsUser` forms) accept `where`/
|
|
606
|
-
`match`/`or` the same way, and require at least one of them — an unfiltered
|
|
607
|
-
update/delete would otherwise touch every row.
|
|
608
|
-
|
|
609
|
-
This mode trades capability for not needing `cfni_exec`: everything the
|
|
610
|
-
Supabase JS client itself can express against a single table (including an
|
|
611
|
-
embedded-resource join) works; multi-table joins beyond that, aggregates,
|
|
612
|
-
window functions, and raw SQL of any kind do not — those need `cfni_exec`, or
|
|
613
|
-
a Postgres function called via `supabaseRpc`.
|
|
629
|
+
export default [
|
|
630
|
+
...dbEslint,
|
|
631
|
+
// your other configs...
|
|
632
|
+
];
|
|
633
|
+
```
|
|
614
634
|
|
|
615
635
|
Two query wrappers, both from `cloudflare-next-intl/db`. Choose by who is
|
|
616
636
|
allowed to see the rows:
|
|
@@ -786,6 +806,16 @@ untested:
|
|
|
786
806
|
55432:5432 postgres:15`), so it never runs — and never needs a database —
|
|
787
807
|
during a normal `npm test`.
|
|
788
808
|
|
|
809
|
+
## AI Agent Setup & Conventions
|
|
810
|
+
|
|
811
|
+
When using AI coding assistants (Claude Code, Cursor, Copilot, Antigravity) with `cloudflare-next-intl`:
|
|
812
|
+
|
|
813
|
+
- **One Database API**: Always use `withPublicDb` or `withUserDb` from `cloudflare-next-intl/db`. Never import `@supabase/supabase-js`, `pg`, or `postgres` in application code.
|
|
814
|
+
- **Drizzle Schema & Helpers**: Always import schema definitions from `cloudflare-next-intl/dbSchema` (`pgTable`, `text`, `timestamp`, `uuid`, etc.) and query operators from `cloudflare-next-intl/dbHelpers` (`eq`, `and`, `or`, `inArray`, `count`, etc.).
|
|
815
|
+
- **Lint Enforcement**: Spread `...dbEslint` from `cloudflare-next-intl/dbEslint` in `eslint.config.js` to catch accidental driver imports.
|
|
816
|
+
- **Reference Document**: Direct AI tools to `llms.txt` in this package for concise rules and subpath exports.
|
|
817
|
+
|
|
789
818
|
## License
|
|
790
819
|
|
|
791
820
|
MIT
|
|
821
|
+
|
package/dist/src/db/context.js
CHANGED
|
@@ -27,19 +27,11 @@ async function resolveUserId(uid) {
|
|
|
27
27
|
throw new Error('db: withUserDb could not resolve a user id. Pass one explicitly, set ' +
|
|
28
28
|
'`db.getUserId`, or configure `firebaseAuth` so the signed-in Firebase uid is used.');
|
|
29
29
|
}
|
|
30
|
-
function requireRawSql(supabase) {
|
|
31
|
-
if (supabase.rawSql === false) {
|
|
32
|
-
throw new Error('db: withPublicDb/withUserDb need `cfni_exec` to run SQL in Supabase mode, but ' +
|
|
33
|
-
'`db.supabase.rawSql` is set to `false`. Use `supabaseSelect`/`supabaseInsert`/' +
|
|
34
|
-
'`supabaseUpdate`/`supabaseDelete` instead, which call the Supabase REST API directly.');
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
30
|
/**
|
|
38
31
|
* Builds a Drizzle handle backed by PostgREST. `bearerToken` decides the role
|
|
39
32
|
* Postgres sees: the anon key for public access, a user JWT for `withUserDb`.
|
|
40
33
|
*/
|
|
41
34
|
async function supabaseDb(supabase, bearerToken) {
|
|
42
|
-
requireRawSql(supabase);
|
|
43
35
|
const { drizzle } = await import('drizzle-orm/pg-proxy');
|
|
44
36
|
const db = drizzle(createSupabaseTransport(supabase, bearerToken));
|
|
45
37
|
return Object.assign(db, {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A flat-config fragment consumers can spread into their `eslint.config.*` to
|
|
3
|
+
* keep application code on the single `db` API.
|
|
4
|
+
*
|
|
5
|
+
* The runtime already refuses to give out a raw client, but an import of
|
|
6
|
+
* `@supabase/supabase-js` or a deep `dist/` path is how that guarantee gets
|
|
7
|
+
* bypassed in practice, so it is worth failing at lint time where the fix is
|
|
8
|
+
* cheap.
|
|
9
|
+
*/
|
|
10
|
+
declare const dbEslintConfig: {
|
|
11
|
+
rules: {
|
|
12
|
+
'no-restricted-imports': (string | {
|
|
13
|
+
paths: {
|
|
14
|
+
name: string;
|
|
15
|
+
message: string;
|
|
16
|
+
}[];
|
|
17
|
+
patterns: string[];
|
|
18
|
+
})[];
|
|
19
|
+
};
|
|
20
|
+
}[];
|
|
21
|
+
export default dbEslintConfig;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const MESSAGE = 'Query the database through `withPublicDb`/`withUserDb` from cloudflare-next-intl/db. ' +
|
|
2
|
+
'The package picks the transport (direct Postgres, cfni_exec, or PostgREST) for you.';
|
|
3
|
+
/**
|
|
4
|
+
* A flat-config fragment consumers can spread into their `eslint.config.*` to
|
|
5
|
+
* keep application code on the single `db` API.
|
|
6
|
+
*
|
|
7
|
+
* The runtime already refuses to give out a raw client, but an import of
|
|
8
|
+
* `@supabase/supabase-js` or a deep `dist/` path is how that guarantee gets
|
|
9
|
+
* bypassed in practice, so it is worth failing at lint time where the fix is
|
|
10
|
+
* cheap.
|
|
11
|
+
*/
|
|
12
|
+
const dbEslintConfig = [
|
|
13
|
+
{
|
|
14
|
+
rules: {
|
|
15
|
+
'no-restricted-imports': [
|
|
16
|
+
'error',
|
|
17
|
+
{
|
|
18
|
+
paths: [
|
|
19
|
+
{ name: '@supabase/supabase-js', message: MESSAGE },
|
|
20
|
+
{ name: 'pg', message: MESSAGE },
|
|
21
|
+
{ name: 'postgres', message: MESSAGE },
|
|
22
|
+
],
|
|
23
|
+
patterns: ['cloudflare-next-intl/dist/*'],
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
export default dbEslintConfig;
|
package/dist/src/db/index.d.ts
CHANGED
|
@@ -14,12 +14,11 @@
|
|
|
14
14
|
* load through dynamic `import()` inside these functions, so an app that
|
|
15
15
|
* never calls a `db` export never bundles any of them.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* `
|
|
20
|
-
* `
|
|
21
|
-
*
|
|
22
|
-
* `cfni_exec`, no raw SQL, only what PostgREST's REST API itself supports.
|
|
17
|
+
* You write the same Drizzle code either way. In Supabase mode each generated
|
|
18
|
+
* statement is first translated into `@supabase/supabase-js` `.from()` calls;
|
|
19
|
+
* anything PostgREST cannot express falls back to `cfni_exec`, and if
|
|
20
|
+
* `db.supabase.rawSql` is `false` the call throws naming the construct that
|
|
21
|
+
* needs raw SQL. `.transaction()` is never available in Supabase mode.
|
|
23
22
|
*
|
|
24
23
|
* Generic Drizzle SQL helpers (`excluded`, `onConflictSet`, `ago`, …) live in
|
|
25
24
|
* the separate `cloudflare-next-intl/dbHelpers` entry point.
|
|
@@ -28,5 +27,3 @@ export { withPublicDb, withUserDb } from './context';
|
|
|
28
27
|
export type { DrizzleDb } from './context';
|
|
29
28
|
export { default as connectToPostgres, disconnectPostgres, resetConnectionState } from './connection';
|
|
30
29
|
export type { DbRoutingConfig } from '../types/types';
|
|
31
|
-
export { supabaseSelect, supabaseSelectAsUser, supabaseInsert, supabaseInsertAsUser, supabaseUpsert, supabaseUpsertAsUser, supabaseUpdate, supabaseUpdateAsUser, supabaseDelete, supabaseDeleteAsUser, supabaseRpc, supabaseRpcAsUser, } from './supabase_rest';
|
|
32
|
-
export type { SupabaseWhere, SupabaseFilterOperator, SupabaseFilterValue, SupabaseOrderBy, SupabaseTextSearch, SupabaseSelectOptions, SupabaseMutationOptions, SupabaseUpsertOptions, SupabaseResult, } from './supabase_rest';
|
package/dist/src/db/index.js
CHANGED
|
@@ -14,16 +14,14 @@
|
|
|
14
14
|
* load through dynamic `import()` inside these functions, so an app that
|
|
15
15
|
* never calls a `db` export never bundles any of them.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* `
|
|
20
|
-
* `
|
|
21
|
-
*
|
|
22
|
-
* `cfni_exec`, no raw SQL, only what PostgREST's REST API itself supports.
|
|
17
|
+
* You write the same Drizzle code either way. In Supabase mode each generated
|
|
18
|
+
* statement is first translated into `@supabase/supabase-js` `.from()` calls;
|
|
19
|
+
* anything PostgREST cannot express falls back to `cfni_exec`, and if
|
|
20
|
+
* `db.supabase.rawSql` is `false` the call throws naming the construct that
|
|
21
|
+
* needs raw SQL. `.transaction()` is never available in Supabase mode.
|
|
23
22
|
*
|
|
24
23
|
* Generic Drizzle SQL helpers (`excluded`, `onConflictSet`, `ago`, …) live in
|
|
25
24
|
* the separate `cloudflare-next-intl/dbHelpers` entry point.
|
|
26
25
|
*/
|
|
27
26
|
export { withPublicDb, withUserDb } from './context';
|
|
28
27
|
export { default as connectToPostgres, disconnectPostgres, resetConnectionState } from './connection';
|
|
29
|
-
export { supabaseSelect, supabaseSelectAsUser, supabaseInsert, supabaseInsertAsUser, supabaseUpsert, supabaseUpsertAsUser, supabaseUpdate, supabaseUpdateAsUser, supabaseDelete, supabaseDeleteAsUser, supabaseRpc, supabaseRpcAsUser, } from './supabase_rest';
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { type SqlValue, type WhereNode } from './parse_where';
|
|
2
|
+
/** One selected/returned column, with its output name when aliased. */
|
|
3
|
+
export interface Projection {
|
|
4
|
+
column: string;
|
|
5
|
+
alias?: string;
|
|
6
|
+
}
|
|
7
|
+
/** One `order by` term. */
|
|
8
|
+
export interface OrderBy {
|
|
9
|
+
column: string;
|
|
10
|
+
ascending: boolean;
|
|
11
|
+
nullsFirst?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/** A single-table `select` reduced to what PostgREST can express. */
|
|
14
|
+
export interface ParsedSelect {
|
|
15
|
+
kind: 'select';
|
|
16
|
+
table: string;
|
|
17
|
+
projection: Projection[] | 'all' | 'count';
|
|
18
|
+
where?: WhereNode;
|
|
19
|
+
orderBy: OrderBy[];
|
|
20
|
+
limit?: SqlValue;
|
|
21
|
+
offset?: SqlValue;
|
|
22
|
+
}
|
|
23
|
+
/** The `excluded.<column>` reference an upsert's `do update set` may use. */
|
|
24
|
+
export interface ExcludedRef {
|
|
25
|
+
kind: 'excluded';
|
|
26
|
+
column: string;
|
|
27
|
+
}
|
|
28
|
+
/** A parsed `on conflict` clause. */
|
|
29
|
+
export type OnConflict = {
|
|
30
|
+
columns: string[];
|
|
31
|
+
action: 'nothing';
|
|
32
|
+
} | {
|
|
33
|
+
columns: string[];
|
|
34
|
+
action: 'update';
|
|
35
|
+
set: Record<string, SqlValue | ExcludedRef>;
|
|
36
|
+
};
|
|
37
|
+
/** A single-table `insert`, optionally an upsert, optionally `returning`. */
|
|
38
|
+
export interface ParsedInsert {
|
|
39
|
+
kind: 'insert';
|
|
40
|
+
table: string;
|
|
41
|
+
columns: string[];
|
|
42
|
+
rows: SqlValue[][];
|
|
43
|
+
onConflict?: OnConflict;
|
|
44
|
+
returning?: Projection[] | 'all';
|
|
45
|
+
}
|
|
46
|
+
/** A single-table `update`. */
|
|
47
|
+
export interface ParsedUpdate {
|
|
48
|
+
kind: 'update';
|
|
49
|
+
table: string;
|
|
50
|
+
set: Record<string, SqlValue>;
|
|
51
|
+
where?: WhereNode;
|
|
52
|
+
returning?: Projection[] | 'all';
|
|
53
|
+
}
|
|
54
|
+
/** A single-table `delete`. */
|
|
55
|
+
export interface ParsedDelete {
|
|
56
|
+
kind: 'delete';
|
|
57
|
+
table: string;
|
|
58
|
+
where?: WhereNode;
|
|
59
|
+
returning?: Projection[] | 'all';
|
|
60
|
+
}
|
|
61
|
+
/** Any statement the REST executor knows how to run. */
|
|
62
|
+
export type ParsedStatement = ParsedSelect | ParsedInsert | ParsedUpdate | ParsedDelete;
|
|
63
|
+
/**
|
|
64
|
+
* Parses a generated statement into the smallest description the REST
|
|
65
|
+
* executor needs, rejecting anything PostgREST's single-table API cannot do.
|
|
66
|
+
*
|
|
67
|
+
* The parser is deliberately strict: a statement it does not fully understand
|
|
68
|
+
* must raise rather than translate approximately, because the transport reads
|
|
69
|
+
* a raise as "send this to `cfni_exec` instead" and a wrong translation would
|
|
70
|
+
* silently return wrong rows.
|
|
71
|
+
*
|
|
72
|
+
* @param sql The generated statement text, `$n` placeholders included.
|
|
73
|
+
* @returns The parsed statement.
|
|
74
|
+
* @throws {UnsupportedSqlError} If the statement is outside the supported subset.
|
|
75
|
+
*/
|
|
76
|
+
export default function parseStatement(sql: string): ParsedStatement;
|