cloudflare-next-intl 0.8.3 → 0.8.4
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 +12 -13
- package/dist/src/db/connection.d.ts +1 -1
- package/dist/src/db/connection.js +8 -17
- package/dist/src/db/helpers.d.ts +16 -0
- package/dist/src/db/helpers.js +18 -0
- package/dist/src/db/index.d.ts +3 -4
- package/dist/src/db/index.js +3 -4
- package/dist/src/db/require_config.js +1 -1
- package/dist/src/db/resolve_mode.d.ts +2 -2
- package/dist/src/db/resolve_mode.js +3 -3
- package/dist/src/types/types.d.ts +6 -12
- package/llms.txt +6 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ and Cloudflare environment.
|
|
|
14
14
|
- **Error handling**: shared, opt-in `console.error` override and
|
|
15
15
|
`reportError`/`withErrorHandling` helpers, GDPR-aware (consent-gated).
|
|
16
16
|
- **Database**: optional Postgres/Drizzle data-access layer, reachable either
|
|
17
|
-
directly (
|
|
17
|
+
directly (a connection string, e.g. from Cloudflare Hyperdrive) or through the
|
|
18
18
|
Supabase Data API (project URL + anon key only), with request-scoped
|
|
19
19
|
public/user contexts and RLS wiring in both modes.
|
|
20
20
|
|
|
@@ -390,8 +390,8 @@ third party without consent can itself be GDPR-relevant.
|
|
|
390
390
|
|
|
391
391
|
### Database (`db`)
|
|
392
392
|
|
|
393
|
-
Thin Postgres/Drizzle data-access layer over a
|
|
394
|
-
(
|
|
393
|
+
Thin Postgres/Drizzle data-access layer over a Postgres connection string
|
|
394
|
+
(which may come from a Cloudflare Hyperdrive binding). `pg` and `drizzle-orm` ship as dependencies of
|
|
395
395
|
this package, so there is nothing extra to install. They are loaded through
|
|
396
396
|
dynamic `import()` inside the `db` exports, so an app that never calls a `db`
|
|
397
397
|
export never pulls them into its bundle. Enable it by setting `db` on your
|
|
@@ -405,21 +405,20 @@ export default setIntlConfig({
|
|
|
405
405
|
locales: ["en", "uk"] as const,
|
|
406
406
|
defaultLocale: "en",
|
|
407
407
|
generate: { getCloudflareContext },
|
|
408
|
-
db: {
|
|
408
|
+
db: {
|
|
409
|
+
connectionString: async () =>
|
|
410
|
+
(await getCloudflareContext({ async: true })).env.HYPERDRIVE
|
|
411
|
+
.connectionString,
|
|
412
|
+
},
|
|
409
413
|
});
|
|
410
414
|
```
|
|
411
415
|
|
|
412
416
|
`db` fields (all optional):
|
|
413
417
|
|
|
414
418
|
- `connectionString` — a Postgres connection string, or a sync/async function
|
|
415
|
-
returning one (resolved on each connect
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
always wins over the binding, which is what makes local dev / build-time
|
|
419
|
-
evaluation work.
|
|
420
|
-
- `hyperdriveBinding` — name of the Hyperdrive binding on `env` to read a
|
|
421
|
-
connection string from when `connectionString` is not set. Defaults to
|
|
422
|
-
`'HYPERDRIVE'`. Requires `generate.getCloudflareContext` to be configured.
|
|
419
|
+
returning one (resolved on each connect). The function form is how you reach
|
|
420
|
+
a value that isn't available at module scope — a Cloudflare Hyperdrive
|
|
421
|
+
binding, or a secret store — as in the example above.
|
|
423
422
|
- `disconnectAfterRequest` — whether the pooled client is closed once the
|
|
424
423
|
last in-flight `withPublicDb`/`withUserDb` call of the request
|
|
425
424
|
finishes. Defaults to `true` (one connection per request, released to
|
|
@@ -443,7 +442,7 @@ query code is identical either way — switching is a config change only.
|
|
|
443
442
|
|
|
444
443
|
| Config | Transport | Use when |
|
|
445
444
|
|---|---|---|
|
|
446
|
-
| `connectionString`
|
|
445
|
+
| `connectionString` | Direct Postgres via `pg` | You have a Postgres connection string, or a Hyperdrive binding to read one from. |
|
|
447
446
|
| `supabase` | Supabase Data API (PostgREST) | You only have `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_ANON_KEY`. |
|
|
448
447
|
|
|
449
448
|
A direct connection always wins if both are configured, so adding a `supabase`
|
|
@@ -23,7 +23,7 @@ export declare function resetConnectionState(): void;
|
|
|
23
23
|
* @param config Your routing config; `config.db` must be set.
|
|
24
24
|
* @returns The connected, shared client.
|
|
25
25
|
* @throws If `db` is not set, or no connection string can be resolved from
|
|
26
|
-
* `db.connectionString
|
|
26
|
+
* `db.connectionString`.
|
|
27
27
|
*/
|
|
28
28
|
export default function connectToPostgres(config: DbConfig): Promise<Client>;
|
|
29
29
|
/**
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import reportError from '../error_handling/report_error';
|
|
2
2
|
import requireDbConfig from './require_config';
|
|
3
3
|
import resolveConfigValue from './resolve_config_value';
|
|
4
|
-
const DEFAULT_BINDING = 'HYPERDRIVE';
|
|
5
4
|
const DEFAULT_DISCONNECT_TIMEOUT_MS = 2000;
|
|
6
5
|
let connectionString = null;
|
|
7
6
|
let client = null;
|
|
@@ -27,24 +26,16 @@ export function resetConnectionState() {
|
|
|
27
26
|
activeUsers = 0;
|
|
28
27
|
}
|
|
29
28
|
/**
|
|
30
|
-
* Resolves the connection string from `db.connectionString
|
|
31
|
-
*
|
|
29
|
+
* Resolves the connection string from `db.connectionString`, awaiting it when
|
|
30
|
+
* it was given as a function.
|
|
32
31
|
*/
|
|
33
|
-
async function resolveConnectionString(
|
|
32
|
+
async function resolveConnectionString(db) {
|
|
34
33
|
const configured = await resolveConfigValue(db.connectionString);
|
|
35
34
|
if (configured)
|
|
36
35
|
return configured;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const env = context?.env;
|
|
41
|
-
const binding = env?.[db.hyperdriveBinding ?? DEFAULT_BINDING];
|
|
42
|
-
if (binding?.connectionString)
|
|
43
|
-
return binding.connectionString;
|
|
44
|
-
}
|
|
45
|
-
throw new Error('db: could not resolve a Postgres connection string. Set `db.connectionString`, or ' +
|
|
46
|
-
`configure \`generate.getCloudflareContext\` and a \`${db.hyperdriveBinding ?? DEFAULT_BINDING}\` ` +
|
|
47
|
-
'Hyperdrive binding on your Worker.');
|
|
36
|
+
throw new Error('db: could not resolve a Postgres connection string. Set `db.connectionString` ' +
|
|
37
|
+
'to a connection string, or to a function returning one (e.g. reading a ' +
|
|
38
|
+
'Hyperdrive binding off `getCloudflareContext().env`).');
|
|
48
39
|
}
|
|
49
40
|
// A single `pg.Client` is not safe for concurrent queries; Next.js fires many
|
|
50
41
|
// in parallel per request. Serializing every `query` through one promise chain
|
|
@@ -75,7 +66,7 @@ function serializeQueries(raw) {
|
|
|
75
66
|
* @param config Your routing config; `config.db` must be set.
|
|
76
67
|
* @returns The connected, shared client.
|
|
77
68
|
* @throws If `db` is not set, or no connection string can be resolved from
|
|
78
|
-
* `db.connectionString
|
|
69
|
+
* `db.connectionString`.
|
|
79
70
|
*/
|
|
80
71
|
export default async function connectToPostgres(config) {
|
|
81
72
|
const db = config.db;
|
|
@@ -84,7 +75,7 @@ export default async function connectToPostgres(config) {
|
|
|
84
75
|
if (connectingPromise === null) {
|
|
85
76
|
connectingPromise = (async () => {
|
|
86
77
|
try {
|
|
87
|
-
connectionString ?? (connectionString = await resolveConnectionString(
|
|
78
|
+
connectionString ?? (connectionString = await resolveConnectionString(db));
|
|
88
79
|
const { Client } = await import('pg');
|
|
89
80
|
const created = serializeQueries(new Client({ connectionString }));
|
|
90
81
|
client = created;
|
package/dist/src/db/helpers.d.ts
CHANGED
|
@@ -60,5 +60,21 @@ export declare function maxOf<T = unknown>(expression: unknown): SQL<T>;
|
|
|
60
60
|
export declare function roundReal(expression: unknown, digits: number): SQL<number>;
|
|
61
61
|
/** Multiplies an expression by a factor. */
|
|
62
62
|
export declare function multiply(expression: unknown, factor: number): SQL<number>;
|
|
63
|
+
/**
|
|
64
|
+
* Wraps an aggregate over a subquery/CTE as a scalar subquery:
|
|
65
|
+
* `(select <expr> from <source>)`.
|
|
66
|
+
*
|
|
67
|
+
* Unlike {@link scalarFromCte} this takes the Drizzle CTE/subquery object
|
|
68
|
+
* rather than its name, so the source is referenced through the query builder
|
|
69
|
+
* and its columns stay typed.
|
|
70
|
+
*
|
|
71
|
+
* @param source A Drizzle CTE or subquery to read from.
|
|
72
|
+
* @param expression The aggregate expression to select.
|
|
73
|
+
* @returns A scalar subquery usable directly in a `select({...})`.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* select({ lowest: scalarFrom(filtered, minOf(filtered.price)) })
|
|
77
|
+
*/
|
|
78
|
+
export declare function scalarFrom<T = unknown>(source: unknown, expression: unknown): SQL<T>;
|
|
63
79
|
/** Wraps an expression as a scalar subquery over a named CTE: `(select <expr> from <cte>)`. */
|
|
64
80
|
export declare function scalarFromCte<T = unknown>(cte: string, expression: unknown): SQL<T>;
|
package/dist/src/db/helpers.js
CHANGED
|
@@ -113,6 +113,24 @@ export function roundReal(expression, digits) {
|
|
|
113
113
|
export function multiply(expression, factor) {
|
|
114
114
|
return sql `${expression} * ${factor}`;
|
|
115
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Wraps an aggregate over a subquery/CTE as a scalar subquery:
|
|
118
|
+
* `(select <expr> from <source>)`.
|
|
119
|
+
*
|
|
120
|
+
* Unlike {@link scalarFromCte} this takes the Drizzle CTE/subquery object
|
|
121
|
+
* rather than its name, so the source is referenced through the query builder
|
|
122
|
+
* and its columns stay typed.
|
|
123
|
+
*
|
|
124
|
+
* @param source A Drizzle CTE or subquery to read from.
|
|
125
|
+
* @param expression The aggregate expression to select.
|
|
126
|
+
* @returns A scalar subquery usable directly in a `select({...})`.
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* select({ lowest: scalarFrom(filtered, minOf(filtered.price)) })
|
|
130
|
+
*/
|
|
131
|
+
export function scalarFrom(source, expression) {
|
|
132
|
+
return sql `(select ${expression} from ${source})`;
|
|
133
|
+
}
|
|
116
134
|
/** Wraps an expression as a scalar subquery over a named CTE: `(select <expr> from <cte>)`. */
|
|
117
135
|
export function scalarFromCte(cte, expression) {
|
|
118
136
|
return sql `(select ${expression} from ${sql.raw(cte)})`;
|
package/dist/src/db/index.d.ts
CHANGED
|
@@ -8,10 +8,9 @@
|
|
|
8
8
|
* - {@link withUserDb} — the signed-in user, with RLS applied to their id.
|
|
9
9
|
*
|
|
10
10
|
* Two transports reach Postgres behind that same Drizzle query API, chosen by
|
|
11
|
-
* `resolveDbMode` from which `db` config fields are set: `connectionString
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* key are available. `pg`, `drizzle-orm`, and `@supabase/supabase-js` all
|
|
11
|
+
* `resolveDbMode` from which `db` config fields are set: `connectionString`
|
|
12
|
+
* for a direct connection (wins if both are configured), or `supabase` for
|
|
13
|
+
* the Supabase Data API when only a project URL and anon key are available. `pg`, `drizzle-orm`, and `@supabase/supabase-js` all
|
|
15
14
|
* load through dynamic `import()` inside these functions, so an app that
|
|
16
15
|
* never calls a `db` export never bundles any of them.
|
|
17
16
|
*
|
package/dist/src/db/index.js
CHANGED
|
@@ -8,10 +8,9 @@
|
|
|
8
8
|
* - {@link withUserDb} — the signed-in user, with RLS applied to their id.
|
|
9
9
|
*
|
|
10
10
|
* Two transports reach Postgres behind that same Drizzle query API, chosen by
|
|
11
|
-
* `resolveDbMode` from which `db` config fields are set: `connectionString
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* key are available. `pg`, `drizzle-orm`, and `@supabase/supabase-js` all
|
|
11
|
+
* `resolveDbMode` from which `db` config fields are set: `connectionString`
|
|
12
|
+
* for a direct connection (wins if both are configured), or `supabase` for
|
|
13
|
+
* the Supabase Data API when only a project URL and anon key are available. `pg`, `drizzle-orm`, and `@supabase/supabase-js` all
|
|
15
14
|
* load through dynamic `import()` inside these functions, so an app that
|
|
16
15
|
* never calls a `db` export never bundles any of them.
|
|
17
16
|
*
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
export default function requireDbConfig(db) {
|
|
15
15
|
if (!db) {
|
|
16
16
|
throw new Error('db: `db` is not set on your RoutingConfig. Add a `db` object ' +
|
|
17
|
-
'(connectionString or
|
|
17
|
+
'(connectionString or supabase) to the config passed to ' +
|
|
18
18
|
'`setIntlConfig` before using any db export.');
|
|
19
19
|
}
|
|
20
20
|
}
|
|
@@ -8,10 +8,10 @@ export type DbMode = 'postgres' | 'supabase';
|
|
|
8
8
|
* block to an existing config never silently reroutes live traffic. With
|
|
9
9
|
* neither set the result is still `'postgres'`, which lets
|
|
10
10
|
* `connectToPostgres` raise its existing, more specific error about the
|
|
11
|
-
* missing
|
|
11
|
+
* missing connection string.
|
|
12
12
|
*
|
|
13
13
|
* @param db The `db` field off your routing config.
|
|
14
|
-
* @returns `'postgres'` for connection-string
|
|
14
|
+
* @returns `'postgres'` for direct connection-string access, `'supabase'`
|
|
15
15
|
* for PostgREST access.
|
|
16
16
|
*/
|
|
17
17
|
export default function resolveDbMode(db: DbRoutingConfig): DbMode;
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
* block to an existing config never silently reroutes live traffic. With
|
|
6
6
|
* neither set the result is still `'postgres'`, which lets
|
|
7
7
|
* `connectToPostgres` raise its existing, more specific error about the
|
|
8
|
-
* missing
|
|
8
|
+
* missing connection string.
|
|
9
9
|
*
|
|
10
10
|
* @param db The `db` field off your routing config.
|
|
11
|
-
* @returns `'postgres'` for connection-string
|
|
11
|
+
* @returns `'postgres'` for direct connection-string access, `'supabase'`
|
|
12
12
|
* for PostgREST access.
|
|
13
13
|
*/
|
|
14
14
|
export default function resolveDbMode(db) {
|
|
15
|
-
if (db.connectionString
|
|
15
|
+
if (db.connectionString)
|
|
16
16
|
return 'postgres';
|
|
17
17
|
return db.supabase ? 'supabase' : 'postgres';
|
|
18
18
|
}
|
|
@@ -846,19 +846,13 @@ export interface SupabaseDbConfig {
|
|
|
846
846
|
}
|
|
847
847
|
export interface DbRoutingConfig {
|
|
848
848
|
/**
|
|
849
|
-
* Postgres connection string
|
|
850
|
-
*
|
|
851
|
-
*
|
|
852
|
-
*
|
|
853
|
-
*
|
|
849
|
+
* Postgres connection string, or a function (sync or async) returning one,
|
|
850
|
+
* resolved on each connect. The function form is how you reach a value that
|
|
851
|
+
* isn't available at module scope — e.g. a Cloudflare Hyperdrive binding:
|
|
852
|
+
* `connectionString: async () => (await getCloudflareContext({ async: true
|
|
853
|
+
* })).env.HYPERDRIVE.connectionString`.
|
|
854
854
|
*/
|
|
855
855
|
connectionString?: ConfigValue<string | undefined>;
|
|
856
|
-
/**
|
|
857
|
-
* Name of the Hyperdrive binding on `env` whose `connectionString` is used
|
|
858
|
-
* when `connectionString` is not set. Defaults to `'HYPERDRIVE'`. Requires
|
|
859
|
-
* `generate.getCloudflareContext` to be configured.
|
|
860
|
-
*/
|
|
861
|
-
hyperdriveBinding?: string;
|
|
862
856
|
/**
|
|
863
857
|
* Whether the pooled client is closed once the last in-flight
|
|
864
858
|
* `withPublicDb`/`withUserDb` call of the request finishes.
|
|
@@ -889,7 +883,7 @@ export interface DbRoutingConfig {
|
|
|
889
883
|
* `withUserDb` behave the same either way, so switching is a config change
|
|
890
884
|
* with no app-code change.
|
|
891
885
|
*
|
|
892
|
-
* Ignored when `connectionString`
|
|
886
|
+
* Ignored when `connectionString` is set: a direct
|
|
893
887
|
* connection always wins, so adding this block cannot silently reroute
|
|
894
888
|
* live traffic. Requires the `cfni_exec` function from
|
|
895
889
|
* `supabase/cfni_exec.sql` to be installed in your database.
|
package/llms.txt
CHANGED
|
@@ -50,7 +50,7 @@ other subpath can be used.
|
|
|
50
50
|
|
|
51
51
|
Two transports, picked by which `db` fields are set — `pg`/`drizzle-orm`/`@supabase/supabase-js` ship as dependencies and load via dynamic `import()`, so nothing bundles unless a `db` export is called.
|
|
52
52
|
|
|
53
|
-
- Direct Postgres (wins if configured): `db.connectionString` — Postgres connection string or a sync/async function returning one (resolved on each connect)
|
|
53
|
+
- Direct Postgres (wins if configured): `db.connectionString` — Postgres connection string, or a sync/async function returning one (resolved on each connect). The function form is the way to read a value unavailable at module scope, e.g. a Cloudflare Hyperdrive binding: `connectionString: async () => (await getCloudflareContext({ async: true })).env.HYPERDRIVE.connectionString`. There is no separate `hyperdriveBinding` option.
|
|
54
54
|
- Supabase Data API (used only when neither of the above is set): `db.supabase` — `{ url?, anonKey?, execFunction? }` where `url`/`anonKey` each accept a string or a sync/async function returning one, defaulting `url`/`anonKey` to `NEXT_PUBLIC_SUPABASE_URL`/`NEXT_PUBLIC_SUPABASE_ANON_KEY`. Requires installing `supabase/cfni_exec.sql` (a `security invoker` SQL-exec function) in your database. No multi-statement transactions — each statement in a `withUserDb` callback is its own round-trip.
|
|
55
55
|
- `db.disconnectAfterRequest` — direct-Postgres mode only: closes the pooled client once the last in-flight `withPublicDb`/`withUserDb` call of the request finishes. Defaults to `true`; set `false` to keep the connection open for the isolate's lifetime (holds a Hyperdrive slot between requests).
|
|
56
56
|
- `db.authenticatedRole` — direct-Postgres mode only: Postgres role assumed inside `withUserDb`'s transaction. Defaults to `'authenticated'` (Supabase RLS convention).
|
|
@@ -69,7 +69,11 @@ export default setIntlConfig({
|
|
|
69
69
|
locales: ["en", "uk"] as const,
|
|
70
70
|
defaultLocale: "en",
|
|
71
71
|
generate: { getCloudflareContext },
|
|
72
|
-
db: {
|
|
72
|
+
db: {
|
|
73
|
+
connectionString: async () =>
|
|
74
|
+
(await getCloudflareContext({ async: true })).env.HYPERDRIVE
|
|
75
|
+
.connectionString,
|
|
76
|
+
},
|
|
73
77
|
});
|
|
74
78
|
|
|
75
79
|
// anywhere on the server
|