cloudflare-next-intl 0.8.2 → 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,19 +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
- - `connectionString` — a Postgres connection string. Omit to resolve it from
415
- the Hyperdrive binding named by `hyperdriveBinding` instead (the normal
416
- production setup); a value here always wins over the binding, which is
417
- what makes local dev / build-time evaluation work.
418
- - `hyperdriveBinding` — name of the Hyperdrive binding on `env` to read a
419
- connection string from when `connectionString` is not set. Defaults to
420
- `'HYPERDRIVE'`. Requires `generate.getCloudflareContext` to be configured.
418
+ - `connectionString` — a Postgres connection string, or a sync/async function
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.
421
422
  - `disconnectAfterRequest` — whether the pooled client is closed once the
422
423
  last in-flight `withPublicDb`/`withUserDb` call of the request
423
424
  finishes. Defaults to `true` (one connection per request, released to
@@ -441,7 +442,7 @@ query code is identical either way — switching is a config change only.
441
442
 
442
443
  | Config | Transport | Use when |
443
444
  |---|---|---|
444
- | `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. |
445
446
  | `supabase` | Supabase Data API (PostgREST) | You only have `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_ANON_KEY`. |
446
447
 
447
448
  A direct connection always wins if both are configured, so adding a `supabase`
@@ -472,11 +473,35 @@ bundles any of them.
472
473
 
473
474
  `db.supabase` fields (all optional):
474
475
 
475
- - `url` — project URL. Defaults to `NEXT_PUBLIC_SUPABASE_URL`.
476
- - `anonKey` — anon/publishable key. Defaults to `NEXT_PUBLIC_SUPABASE_ANON_KEY`.
477
- Never put a service-role key here.
476
+ - `url` — project URL, or a sync/async function returning one. Defaults to
477
+ `NEXT_PUBLIC_SUPABASE_URL`.
478
+ - `anonKey` anon/publishable key, or a sync/async function returning one.
479
+ Defaults to `NEXT_PUBLIC_SUPABASE_ANON_KEY`. Never put a service-role key
480
+ here.
478
481
  - `execFunction` — name of the exec function. Defaults to `'cfni_exec'`.
479
482
 
483
+ Any of `connectionString`, `supabase.url`, and `supabase.anonKey` may be a
484
+ function instead of a string, resolved (and awaited) at use time rather than
485
+ when the config object is built — useful when the value lives in a secret
486
+ store or a Cloudflare binding rather than an environment variable:
487
+
488
+ ```typescript
489
+ export default setIntlConfig({
490
+ locales: ["en", "uk"] as const,
491
+ defaultLocale: "en",
492
+ generate: { getCloudflareContext },
493
+ db: {
494
+ supabase: {
495
+ url: "https://abc.supabase.co",
496
+ anonKey: async () => {
497
+ const { env } = await getCloudflareContext({ async: true });
498
+ return env.SUPABASE_ANON_KEY.get();
499
+ },
500
+ },
501
+ },
502
+ });
503
+ ```
504
+
480
505
  `db.getAccessToken` resolves the JWT `withUserDb` sends as
481
506
  `Authorization: Bearer`, which is what makes PostgREST resolve the caller as
482
507
  `authenticated`. Omit it when `firebaseAuth` is configured — the signed-in
@@ -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,6 +1,6 @@
1
1
  import reportError from '../error_handling/report_error';
2
2
  import requireDbConfig from './require_config';
3
- const DEFAULT_BINDING = 'HYPERDRIVE';
3
+ import resolveConfigValue from './resolve_config_value';
4
4
  const DEFAULT_DISCONNECT_TIMEOUT_MS = 2000;
5
5
  let connectionString = null;
6
6
  let client = null;
@@ -26,23 +26,16 @@ export function resetConnectionState() {
26
26
  activeUsers = 0;
27
27
  }
28
28
  /**
29
- * Resolves the connection string from `db.connectionString` first, then the
30
- * 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.
31
31
  */
32
- async function resolveConnectionString(config, db) {
33
- if (db.connectionString)
34
- return db.connectionString;
35
- const getContext = config.generate?.getCloudflareContext;
36
- if (getContext) {
37
- const context = await getContext({ async: true });
38
- const env = context?.env;
39
- const binding = env?.[db.hyperdriveBinding ?? DEFAULT_BINDING];
40
- if (binding?.connectionString)
41
- return binding.connectionString;
42
- }
43
- throw new Error('db: could not resolve a Postgres connection string. Set `db.connectionString`, or ' +
44
- `configure \`generate.getCloudflareContext\` and a \`${db.hyperdriveBinding ?? DEFAULT_BINDING}\` ` +
45
- 'Hyperdrive binding on your Worker.');
32
+ async function resolveConnectionString(db) {
33
+ const configured = await resolveConfigValue(db.connectionString);
34
+ if (configured)
35
+ return configured;
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`).');
46
39
  }
47
40
  // A single `pg.Client` is not safe for concurrent queries; Next.js fires many
48
41
  // in parallel per request. Serializing every `query` through one promise chain
@@ -73,7 +66,7 @@ function serializeQueries(raw) {
73
66
  * @param config Your routing config; `config.db` must be set.
74
67
  * @returns The connected, shared client.
75
68
  * @throws If `db` is not set, or no connection string can be resolved from
76
- * `db.connectionString` or the Hyperdrive binding.
69
+ * `db.connectionString`.
77
70
  */
78
71
  export default async function connectToPostgres(config) {
79
72
  const db = config.db;
@@ -82,7 +75,7 @@ export default async function connectToPostgres(config) {
82
75
  if (connectingPromise === null) {
83
76
  connectingPromise = (async () => {
84
77
  try {
85
- connectionString ?? (connectionString = await resolveConnectionString(config, db));
78
+ connectionString ?? (connectionString = await resolveConnectionString(db));
86
79
  const { Client } = await import('pg');
87
80
  const created = serializeQueries(new Client({ connectionString }));
88
81
  client = created;
@@ -60,7 +60,7 @@ export async function withPublicDb(fn) {
60
60
  requireDbConfig(db);
61
61
  if (resolveDbMode(db) === 'supabase') {
62
62
  const supabase = db.supabase;
63
- const { anonKey } = resolveSupabaseEndpoint(supabase);
63
+ const { anonKey } = await resolveSupabaseEndpoint(supabase);
64
64
  return fn(await supabaseDb(supabase, anonKey));
65
65
  }
66
66
  const client = await connectToPostgres(config);
@@ -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
  }
@@ -0,0 +1,9 @@
1
+ import type { ConfigValue } from '../types/types';
2
+ /**
3
+ * Resolves a config value that may have been supplied directly or as a
4
+ * sync/async function.
5
+ *
6
+ * @param value The configured value or resolver.
7
+ * @returns The resolved value, or `undefined` when nothing was configured.
8
+ */
9
+ export default function resolveConfigValue<T>(value: ConfigValue<T | undefined> | undefined): Promise<T | undefined>;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Resolves a config value that may have been supplied directly or as a
3
+ * sync/async function.
4
+ *
5
+ * @param value The configured value or resolver.
6
+ * @returns The resolved value, or `undefined` when nothing was configured.
7
+ */
8
+ export default async function resolveConfigValue(value) {
9
+ return typeof value === 'function' ? value() : value;
10
+ }
@@ -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
  }
@@ -13,6 +13,7 @@ export interface ResolvedSupabaseEndpoint {
13
13
  *
14
14
  * @param supabase The `db.supabase` config block.
15
15
  * @returns The project URL and anon key to build a Supabase client from.
16
+ * Values given as functions are resolved here.
16
17
  * @throws If neither config nor environment supplies a URL or an anon key.
17
18
  */
18
- export default function resolveSupabaseEndpoint(supabase: SupabaseDbConfig): ResolvedSupabaseEndpoint;
19
+ export default function resolveSupabaseEndpoint(supabase: SupabaseDbConfig): Promise<ResolvedSupabaseEndpoint>;
@@ -1,3 +1,4 @@
1
+ import resolveConfigValue from './resolve_config_value';
1
2
  /**
2
3
  * Resolves the Supabase project URL and anon key, preferring explicit config
3
4
  * over the `NEXT_PUBLIC_SUPABASE_URL`/`NEXT_PUBLIC_SUPABASE_ANON_KEY`
@@ -5,15 +6,16 @@
5
6
  *
6
7
  * @param supabase The `db.supabase` config block.
7
8
  * @returns The project URL and anon key to build a Supabase client from.
9
+ * Values given as functions are resolved here.
8
10
  * @throws If neither config nor environment supplies a URL or an anon key.
9
11
  */
10
- export default function resolveSupabaseEndpoint(supabase) {
11
- const url = supabase.url ?? process.env.NEXT_PUBLIC_SUPABASE_URL;
12
+ export default async function resolveSupabaseEndpoint(supabase) {
13
+ const url = (await resolveConfigValue(supabase.url)) ?? process.env.NEXT_PUBLIC_SUPABASE_URL;
12
14
  if (!url) {
13
15
  throw new Error('db: could not resolve a Supabase project URL. Set `db.supabase.url` ' +
14
16
  'or the NEXT_PUBLIC_SUPABASE_URL environment variable.');
15
17
  }
16
- const anonKey = supabase.anonKey ?? process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
18
+ const anonKey = (await resolveConfigValue(supabase.anonKey)) ?? process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
17
19
  if (!anonKey) {
18
20
  throw new Error('db: could not resolve a Supabase anon key. Set `db.supabase.anonKey` ' +
19
21
  'or the NEXT_PUBLIC_SUPABASE_ANON_KEY environment variable.');
@@ -21,11 +21,11 @@ const DEFAULT_EXEC_FUNCTION = 'cfni_exec';
21
21
  * @returns A callback suitable for `drizzle-orm/pg-proxy`'s `drizzle()`.
22
22
  */
23
23
  export default function createSupabaseTransport(supabase, bearerToken) {
24
- const { url, anonKey } = resolveSupabaseEndpoint(supabase);
25
24
  const execFunction = supabase.execFunction ?? DEFAULT_EXEC_FUNCTION;
26
25
  let clientPromise = null;
27
26
  async function getClient() {
28
27
  clientPromise ?? (clientPromise = (async () => {
28
+ const { url, anonKey } = await resolveSupabaseEndpoint(supabase);
29
29
  const { createClient } = await import('@supabase/supabase-js');
30
30
  return createClient(url, anonKey, { accessToken: async () => bearerToken });
31
31
  })());
@@ -1 +1 @@
1
- export type { CookieAttributes, LocalePrefixMode, Locales, ReturnType, RoutingConfig, TranslationEntry, TranslationObject, TranslatorReturnType, Alternates, changeFrequency, IntlSitemap, CookieConsentRoutingConfig, CookieConsentAnalyticsConfig, CookieConsentCloudflareContext, CookieConsentGetCloudflareContext, DbRoutingConfig, SupabaseDbConfig, } from './types';
1
+ export type { CookieAttributes, LocalePrefixMode, Locales, ReturnType, RoutingConfig, TranslationEntry, TranslationObject, TranslatorReturnType, Alternates, changeFrequency, IntlSitemap, CookieConsentRoutingConfig, CookieConsentAnalyticsConfig, CookieConsentCloudflareContext, CookieConsentGetCloudflareContext, DbRoutingConfig, SupabaseDbConfig, ConfigValue, } from './types';
@@ -817,18 +817,27 @@ export interface IntlSitemap {
817
817
  lastModified: Date | string | undefined;
818
818
  videos?: Videos[] | undefined;
819
819
  }
820
+ /**
821
+ * A config value that may be given directly, or as a sync/async function
822
+ * resolved at use time. The function form lets a value come from a secret
823
+ * store, a Cloudflare binding, or any other source that isn't available when
824
+ * the config object is first created.
825
+ */
826
+ export type ConfigValue<T> = T | (() => T | Promise<T>);
820
827
  export interface SupabaseDbConfig {
821
828
  /**
822
829
  * Supabase project URL, e.g. `https://abc.supabase.co`. Defaults to
823
- * `process.env.NEXT_PUBLIC_SUPABASE_URL`.
830
+ * `process.env.NEXT_PUBLIC_SUPABASE_URL`. May be a function (sync or
831
+ * async) resolved on each use.
824
832
  */
825
- url?: string;
833
+ url?: ConfigValue<string | undefined>;
826
834
  /**
827
835
  * Supabase anon (publishable) key. Defaults to
828
836
  * `process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY`. This is the only key the
829
- * `db` module ever needs — never put a service-role key here.
837
+ * `db` module ever needs — never put a service-role key here. May be a
838
+ * function (sync or async) resolved on each use.
830
839
  */
831
- anonKey?: string;
840
+ anonKey?: ConfigValue<string | undefined>;
832
841
  /**
833
842
  * Name of the Postgres function that runs the generated SQL. Defaults to
834
843
  * `'cfni_exec'` — the function shipped in `supabase/cfni_exec.sql`.
@@ -837,18 +846,13 @@ export interface SupabaseDbConfig {
837
846
  }
838
847
  export interface DbRoutingConfig {
839
848
  /**
840
- * Postgres connection string. Omit to resolve it from the Cloudflare
841
- * Hyperdrive binding named by `hyperdriveBinding` instead (the normal
842
- * production setup); a value here always wins over the binding, which is
843
- * what makes local dev / build-time evaluation work.
844
- */
845
- connectionString?: string;
846
- /**
847
- * Name of the Hyperdrive binding on `env` whose `connectionString` is used
848
- * when `connectionString` is not set. Defaults to `'HYPERDRIVE'`. Requires
849
- * `generate.getCloudflareContext` to be configured.
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`.
850
854
  */
851
- hyperdriveBinding?: string;
855
+ connectionString?: ConfigValue<string | undefined>;
852
856
  /**
853
857
  * Whether the pooled client is closed once the last in-flight
854
858
  * `withPublicDb`/`withUserDb` call of the request finishes.
@@ -879,7 +883,7 @@ export interface DbRoutingConfig {
879
883
  * `withUserDb` behave the same either way, so switching is a config change
880
884
  * with no app-code change.
881
885
  *
882
- * Ignored when `connectionString` or `hyperdriveBinding` is set: a direct
886
+ * Ignored when `connectionString` is set: a direct
883
887
  * connection always wins, so adding this block cannot silently reroute
884
888
  * live traffic. Requires the `cfni_exec` function from
885
889
  * `supabase/cfni_exec.sql` to be installed in your database.
package/llms.txt CHANGED
@@ -50,8 +50,8 @@ 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; 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).
54
- - Supabase Data API (used only when neither of the above is set): `db.supabase` — `{ url?, anonKey?, execFunction? }`, 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.
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
+ - 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).
57
57
  - `db.getUserId` — direct-Postgres mode only: resolves the user id injected as `request.jwt.claims->>'sub'` in `withUserDb`. Omit when `firebaseAuth` is configured — the uid is then taken automatically from the signed-in Firebase user via this package's own `getAuthUser()`.
@@ -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.2",
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",