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 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 (Cloudflare Hyperdrive or a connection string) or through the
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 Cloudflare Hyperdrive binding
394
- (or a plain connection string). `pg` and `drizzle-orm` ship as dependencies of
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: { hyperdriveBinding: "HYPERDRIVE" },
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, so the value can come from a secret
416
- store). Omit to resolve it from the Hyperdrive binding named by
417
- `hyperdriveBinding` instead (the normal production setup); a value here
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` or `hyperdriveBinding` | Direct Postgres via `pg` | You have a Postgres password or a Hyperdrive binding. |
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` or the Hyperdrive binding.
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` first, then the
31
- * Cloudflare Hyperdrive binding named by `db.hyperdriveBinding`.
29
+ * Resolves the connection string from `db.connectionString`, awaiting it when
30
+ * it was given as a function.
32
31
  */
33
- async function resolveConnectionString(config, db) {
32
+ async function resolveConnectionString(db) {
34
33
  const configured = await resolveConfigValue(db.connectionString);
35
34
  if (configured)
36
35
  return configured;
37
- const getContext = config.generate?.getCloudflareContext;
38
- if (getContext) {
39
- const context = await getContext({ async: true });
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` or the Hyperdrive binding.
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(config, db));
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;
@@ -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>;
@@ -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)})`;
@@ -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
- * `hyperdriveBinding` for a direct connection (wins if both are configured),
13
- * or `supabase` for the Supabase Data API when only a project URL and anon
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
  *
@@ -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
- * `hyperdriveBinding` for a direct connection (wins if both are configured),
13
- * or `supabase` for the Supabase Data API when only a project URL and anon
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 hyperdriveBinding) to the config passed to ' +
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 Hyperdrive binding.
11
+ * missing connection string.
12
12
  *
13
13
  * @param db The `db` field off your routing config.
14
- * @returns `'postgres'` for connection-string/Hyperdrive access, `'supabase'`
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 Hyperdrive binding.
8
+ * missing connection string.
9
9
  *
10
10
  * @param db The `db` field off your routing config.
11
- * @returns `'postgres'` for connection-string/Hyperdrive access, `'supabase'`
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 || db.hyperdriveBinding)
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. Omit to resolve it from the Cloudflare
850
- * Hyperdrive binding named by `hyperdriveBinding` instead (the normal
851
- * production setup); a value here always wins over the binding, which is
852
- * what makes local dev / build-time evaluation work. May be a function
853
- * (sync or async) resolved on each connect.
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` or `hyperdriveBinding` is set: a direct
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); when omitted, resolved from the Hyperdrive binding named by `db.hyperdriveBinding` (default `'HYPERDRIVE'`) via `generate.getCloudflareContext`. An explicit `connectionString` always wins (enables local dev / build-time evaluation).
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: { hyperdriveBinding: "HYPERDRIVE" },
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare-next-intl",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "description": "Optimized Next Intl Package Special for App Router and Cloudflare",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",