cloudflare-next-intl 0.8.2 → 0.8.3

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
@@ -411,10 +411,12 @@ export default setIntlConfig({
411
411
 
412
412
  `db` fields (all optional):
413
413
 
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.
414
+ - `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.
418
420
  - `hyperdriveBinding` — name of the Hyperdrive binding on `env` to read a
419
421
  connection string from when `connectionString` is not set. Defaults to
420
422
  `'HYPERDRIVE'`. Requires `generate.getCloudflareContext` to be configured.
@@ -472,11 +474,35 @@ bundles any of them.
472
474
 
473
475
  `db.supabase` fields (all optional):
474
476
 
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.
477
+ - `url` — project URL, or a sync/async function returning one. Defaults to
478
+ `NEXT_PUBLIC_SUPABASE_URL`.
479
+ - `anonKey` anon/publishable key, or a sync/async function returning one.
480
+ Defaults to `NEXT_PUBLIC_SUPABASE_ANON_KEY`. Never put a service-role key
481
+ here.
478
482
  - `execFunction` — name of the exec function. Defaults to `'cfni_exec'`.
479
483
 
484
+ Any of `connectionString`, `supabase.url`, and `supabase.anonKey` may be a
485
+ function instead of a string, resolved (and awaited) at use time rather than
486
+ when the config object is built — useful when the value lives in a secret
487
+ store or a Cloudflare binding rather than an environment variable:
488
+
489
+ ```typescript
490
+ export default setIntlConfig({
491
+ locales: ["en", "uk"] as const,
492
+ defaultLocale: "en",
493
+ generate: { getCloudflareContext },
494
+ db: {
495
+ supabase: {
496
+ url: "https://abc.supabase.co",
497
+ anonKey: async () => {
498
+ const { env } = await getCloudflareContext({ async: true });
499
+ return env.SUPABASE_ANON_KEY.get();
500
+ },
501
+ },
502
+ },
503
+ });
504
+ ```
505
+
480
506
  `db.getAccessToken` resolves the JWT `withUserDb` sends as
481
507
  `Authorization: Bearer`, which is what makes PostgREST resolve the caller as
482
508
  `authenticated`. Omit it when `firebaseAuth` is configured — the signed-in
@@ -1,5 +1,6 @@
1
1
  import reportError from '../error_handling/report_error';
2
2
  import requireDbConfig from './require_config';
3
+ import resolveConfigValue from './resolve_config_value';
3
4
  const DEFAULT_BINDING = 'HYPERDRIVE';
4
5
  const DEFAULT_DISCONNECT_TIMEOUT_MS = 2000;
5
6
  let connectionString = null;
@@ -30,8 +31,9 @@ export function resetConnectionState() {
30
31
  * Cloudflare Hyperdrive binding named by `db.hyperdriveBinding`.
31
32
  */
32
33
  async function resolveConnectionString(config, db) {
33
- if (db.connectionString)
34
- return db.connectionString;
34
+ const configured = await resolveConfigValue(db.connectionString);
35
+ if (configured)
36
+ return configured;
35
37
  const getContext = config.generate?.getCloudflareContext;
36
38
  if (getContext) {
37
39
  const context = await getContext({ async: true });
@@ -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);
@@ -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
+ }
@@ -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`.
@@ -840,9 +849,10 @@ export interface DbRoutingConfig {
840
849
  * Postgres connection string. Omit to resolve it from the Cloudflare
841
850
  * Hyperdrive binding named by `hyperdriveBinding` instead (the normal
842
851
  * production setup); a value here always wins over the binding, which is
843
- * what makes local dev / build-time evaluation work.
852
+ * what makes local dev / build-time evaluation work. May be a function
853
+ * (sync or async) resolved on each connect.
844
854
  */
845
- connectionString?: string;
855
+ connectionString?: ConfigValue<string | undefined>;
846
856
  /**
847
857
  * Name of the Hyperdrive binding on `env` whose `connectionString` is used
848
858
  * when `connectionString` is not set. Defaults to `'HYPERDRIVE'`. Requires
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); 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? }` 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()`.
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.3",
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",