cloudflare-next-intl 0.8.12 → 0.8.14

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
@@ -760,7 +760,7 @@ npx cfni-db-codegen --check
760
760
  | `--out-file=` | `CFNI_DB_OUT_FILE` | `schema.ts` |
761
761
  | `--db-url=` | `CODEGEN_DATABASE_URL` | `postgresql://postgres:postgres@127.0.0.1:54322/postgres` |
762
762
  | `--drizzle-config=` | `CFNI_DB_DRIZZLE_CONFIG` | none |
763
- | `--rpc-dir=` | `CFNI_DB_RPC_DIR` | sibling of `--ddl-dir`, e.g. `supabase/rpc` |
763
+ | `--rpc-dir=` | `CFNI_DB_RPC_DIR` | inside `--ddl-dir`, e.g. `supabase/data-base/rpcs` |
764
764
  | `--rpc-file-name=` | `CFNI_DB_RPC_FILE_NAME` | `cfni_exec.sql` |
765
765
  | `--tests-dir=` | `CFNI_DB_TESTS_DIR` | sibling of `--ddl-dir`, e.g. `supabase/tests` |
766
766
  | `--tests-file-name=` | `CFNI_DB_TESTS_FILE_NAME` | `cfni_exec.sql` |
@@ -792,9 +792,9 @@ fallback entirely and fails loudly if that target is unreachable.
792
792
  After a successful (non-`--check`) run, `cfni-db-codegen` also copies
793
793
  `supabase/cfni_exec.sql` and its pgTAP test file (see
794
794
  [Testing `cfni_exec.sql` itself](#testing-cfni_execsql-itself) below) into
795
- your project — `--rpc-dir`/`--tests-dir` (defaulting to sibling folders of
796
- `--ddl-dir`, so `rpc`/`tests` next to `data-base`), each named `cfni_exec.sql`
797
- by default. This keeps a project that enables Supabase mode's raw-SQL path
795
+ your project — `--rpc-dir` (defaulting inside `--ddl-dir`, so `rpcs` under
796
+ `data-base`) and `--tests-dir` (defaulting to a sibling of `--ddl-dir`, so
797
+ `tests` next to `data-base`), each named `cfni_exec.sql` by default. This keeps a project that enables Supabase mode's raw-SQL path
798
798
  always holding the current version of the function, without a manual
799
799
  copy-paste step. Since the file now ships both `cfni_exec` and
800
800
  `cfni_exec_batch` (see
@@ -808,7 +808,7 @@ This step is gated on `db.supabase.rawSql` (see
808
808
  codegen reads your `next.config.*`'s `@intl-config` alias, opens the intl
809
809
  config file it points at, and looks for a literal `rawSql: true`/`rawSql:
810
810
  false`. If it's explicitly `false`, the copy is skipped entirely — no
811
- `supabase/rpc`/`supabase/tests` folders are created. If it can't be
811
+ `supabase/data-base/rpcs`/`supabase/tests` folders are created. If it can't be
812
812
  determined (no `next.config.*` found, no alias, or `rawSql` isn't a plain
813
813
  `true`/`false` literal in the source), a warning is printed and codegen
814
814
  assumes `true`, matching `withPublicDb`/`withUserDb`'s own default.
@@ -33,14 +33,14 @@
33
33
  import { createHash } from 'node:crypto';
34
34
  import { execFileSync } from 'node:child_process';
35
35
  import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
36
- import { join, relative } from 'node:path';
36
+ import { createRequire } from 'node:module';
37
+ import { dirname, join, relative } from 'node:path';
37
38
  import { Client } from 'pg';
38
39
  import resolveCodegenPaths from '../dist/src/db/codegen_paths.js';
39
40
  import { runInstallExecStep } from './install_exec_step.mjs';
40
41
  import { startEphemeralPostgres } from './ephemeral_pg.mjs';
41
42
  import { orderedSqlFiles } from './ddl_order.mjs';
42
43
 
43
- const PACKAGE_ROOT = new URL('..', import.meta.url).pathname;
44
44
  const paths = resolveCodegenPaths(process.argv.slice(2), process.env, process.cwd());
45
45
 
46
46
  async function isReachable(url) {
@@ -128,13 +128,21 @@ try {
128
128
  // drizzle-kit itself requires `drizzle-orm` at runtime. This package
129
129
  // depends on drizzle-orm, but npm doesn't guarantee that dependency gets
130
130
  // hoisted to the consuming project's own node_modules (it commonly stays
131
- // nested under node_modules/cloudflare-next-intl/node_modules) — so
132
- // running from *this* package's own directory, where it's guaranteed
133
- // resolvable, avoids "please install required packages: drizzle-orm"
134
- // when a consumer doesn't happen to have it hoisted.
135
- execFileSync('npx', ['drizzle-kit', 'pull', `--config=${configPath}`], {
131
+ // nested under node_modules/cloudflare-next-intl/node_modules). `npx
132
+ // drizzle-kit` resolves the binary through its own lookup rather than
133
+ // Node's normal upward node_modules walk from this file, which — even
134
+ // pinned to this package's own directory as cwd — resolved drizzle-orm
135
+ // inconsistently across runs. Resolving and invoking drizzle-kit's own
136
+ // installed binary directly sidesteps npx's resolution entirely: Node's
137
+ // ordinary require() from that file's real location always finds
138
+ // drizzle-orm right next to it in this package's own node_modules.
139
+ // `drizzle-kit/bin.cjs` isn't in the package's `exports` map, so it can't
140
+ // be require.resolve()'d directly — but its main entry point can, and
141
+ // `bin.cjs` always sits next to it (declared via `bin` in package.json).
142
+ const drizzleKitEntry = createRequire(import.meta.url).resolve('drizzle-kit');
143
+ const drizzleKitBin = join(dirname(drizzleKitEntry), 'bin.cjs');
144
+ execFileSync(process.execPath, [drizzleKitBin, 'pull', `--config=${configPath}`], {
136
145
  stdio: 'inherit',
137
- cwd: PACKAGE_ROOT,
138
146
  env: { ...process.env, CODEGEN_DATABASE_URL: effectiveDbUrl },
139
147
  });
140
148
  } finally {
@@ -33,10 +33,13 @@ export default function resolveCodegenPaths(argv, env, cwd) {
33
33
  const outDir = outDirs[0];
34
34
  const outFileName = flag(argv, 'out-file') ?? env.CFNI_DB_OUT_FILE ?? DEFAULT_OUT_FILE;
35
35
  const drizzleConfig = flag(argv, 'drizzle-config') ?? env.CFNI_DB_DRIZZLE_CONFIG ?? null;
36
- // Sibling of ddlDir (default `supabase/data-base` → `supabase`), matching
37
- // where `cfni_exec.sql` ships in this package's own `supabase/` folder.
36
+ // rpcDir defaults inside ddlDir itself (default `supabase/data-base` →
37
+ // `supabase/data-base/rpcs`), matching where a project's DDL walk (and
38
+ // this package's own `supabase/data-base/rpcs/`) actually keeps RPC
39
+ // definitions. testsDir stays a sibling of ddlDir (`supabase/tests`) —
40
+ // pgTAP tests aren't part of the DDL a project applies to its database.
38
41
  const supabaseRoot = dirname(ddlDir);
39
- const rpcDir = abs(cwd, flag(argv, 'rpc-dir') ?? env.CFNI_DB_RPC_DIR ?? join(supabaseRoot, 'rpc'));
42
+ const rpcDir = abs(cwd, flag(argv, 'rpc-dir') ?? env.CFNI_DB_RPC_DIR ?? join(ddlDir, 'rpcs'));
40
43
  const testsDir = abs(cwd, flag(argv, 'tests-dir') ?? env.CFNI_DB_TESTS_DIR ?? join(supabaseRoot, 'tests'));
41
44
  const rpcFileName = flag(argv, 'rpc-file-name') ?? env.CFNI_DB_RPC_FILE_NAME ?? DEFAULT_RPC_FILE_NAME;
42
45
  const testsFileName = flag(argv, 'tests-file-name') ?? env.CFNI_DB_TESTS_FILE_NAME ?? DEFAULT_TESTS_FILE_NAME;
@@ -21,11 +21,15 @@ export declare function resetConnectionState(): void;
21
21
  * `disconnectPostgres` call, or the connection is never released.
22
22
  *
23
23
  * @param config Your routing config; `config.db` must be set.
24
+ * @param resolved A connection string already resolved by the caller (e.g.
25
+ * `resolveDbMode`, which has to call `db.connectionString` itself to decide
26
+ * the transport) — pass it to skip resolving `db.connectionString` a second
27
+ * time. Omit it to have this function resolve it itself, as before.
24
28
  * @returns The connected, shared client.
25
29
  * @throws If `db` is not set, or no connection string can be resolved from
26
30
  * `db.connectionString`.
27
31
  */
28
- export default function connectToPostgres(config: DbConfig): Promise<Client>;
32
+ export default function connectToPostgres(config: DbConfig, resolved?: string | undefined): Promise<Client>;
29
33
  /**
30
34
  * Releases one caller's hold on the shared client, closing it once the last
31
35
  * holder of the request is done. Call it exactly once per
@@ -64,18 +64,22 @@ function serializeQueries(raw) {
64
64
  * `disconnectPostgres` call, or the connection is never released.
65
65
  *
66
66
  * @param config Your routing config; `config.db` must be set.
67
+ * @param resolved A connection string already resolved by the caller (e.g.
68
+ * `resolveDbMode`, which has to call `db.connectionString` itself to decide
69
+ * the transport) — pass it to skip resolving `db.connectionString` a second
70
+ * time. Omit it to have this function resolve it itself, as before.
67
71
  * @returns The connected, shared client.
68
72
  * @throws If `db` is not set, or no connection string can be resolved from
69
73
  * `db.connectionString`.
70
74
  */
71
- export default async function connectToPostgres(config) {
75
+ export default async function connectToPostgres(config, resolved) {
72
76
  const db = config.db;
73
77
  requireDbConfig(db);
74
78
  await disconnectionPromise;
75
79
  if (connectingPromise === null) {
76
80
  connectingPromise = (async () => {
77
81
  try {
78
- connectionString ?? (connectionString = await resolveConnectionString(db));
82
+ connectionString ?? (connectionString = resolved ?? await resolveConnectionString(db));
79
83
  const { Client } = await import('pg');
80
84
  const created = serializeQueries(new Client({ connectionString }));
81
85
  client = created;
@@ -44,11 +44,14 @@ export declare function withPublicDb<T>(fn: (db: DrizzleDb) => Promise<T>): Prom
44
44
  * @param fn Receives the Drizzle handle. In connection-string mode it is
45
45
  * bound to a transaction; in Supabase mode it is not — do not rely on
46
46
  * multi-statement atomicity there.
47
- * @param uid Connection-string mode only: overrides the user id. Omit it in
48
- * normal use — the id then comes from `db.getUserId()` when set, otherwise
49
- * from the signed-in Firebase user when `firebaseAuth` is configured.
50
- * Ignored in Supabase mode, which resolves identity via `db.getAccessToken`/
51
- * Firebase instead see {@link resolveAccessToken}.
47
+ * @param uid Connection-string mode only: overrides the user id. Omit it, or
48
+ * pass `null`, in normal use — either way the id then comes from
49
+ * `db.getUserId()` when set, otherwise from the signed-in Firebase user when
50
+ * `firebaseAuth` is configured. `null` is accepted alongside `undefined` so a
51
+ * caller's own lookup (which may itself come back empty) can be passed
52
+ * straight through without an extra check. Ignored in Supabase mode, which
53
+ * resolves identity via `db.getAccessToken`/Firebase instead — see
54
+ * {@link resolveAccessToken}.
52
55
  * @returns Whatever `fn` resolves to.
53
56
  * @throws If `db` is not set on your `RoutingConfig`, if no user id/access
54
57
  * token can be resolved, or the connection fails.
@@ -56,7 +59,7 @@ export declare function withPublicDb<T>(fn: (db: DrizzleDb) => Promise<T>): Prom
56
59
  * @example
57
60
  * const mine = await withUserDb((db) => db.select().from(orders));
58
61
  */
59
- export declare function withUserDb<T>(fn: (db: DrizzleDb) => Promise<T>, uid?: string): Promise<T>;
62
+ export declare function withUserDb<T>(fn: (db: DrizzleDb) => Promise<T>, uid?: string | null): Promise<T>;
60
63
  /** One statement's `{rows, rowCount}` result from a `withUserTransaction`/`withPublicTransaction` batch. */
61
64
  export type { ExecResult as TransactionResult } from './supabase_transport';
62
65
  /**
@@ -9,7 +9,9 @@ import runTransactionBatch from './transaction_batch';
9
9
  const DEFAULT_ROLE = 'authenticated';
10
10
  /**
11
11
  * Resolves the user id for `withUserDb`, trying, in order: the explicit `uid`
12
- * argument, `db.getUserId()`, then the signed-in Firebase user.
12
+ * argument, `db.getUserId()`, then the signed-in Firebase user. `uid` may be
13
+ * `null` (as well as omitted) to mean "skip this source, try the next one" —
14
+ * useful when the caller's own uid lookup can itself come back empty.
13
15
  */
14
16
  async function resolveUserId(uid) {
15
17
  if (uid)
@@ -86,12 +88,12 @@ function buildOnlyDb() {
86
88
  export async function withPublicDb(fn) {
87
89
  const db = config.db;
88
90
  requireDbConfig(db);
89
- if (resolveDbMode(db) === 'supabase') {
90
- const supabase = db.supabase;
91
- const { anonKey } = await resolveSupabaseEndpoint(supabase);
92
- return fn(await supabaseDb(supabase, anonKey));
91
+ const resolved = await resolveDbMode(db);
92
+ if (resolved.mode === 'supabase') {
93
+ const { anonKey } = await resolveSupabaseEndpoint(resolved.supabase);
94
+ return fn(await supabaseDb(resolved.supabase, anonKey));
93
95
  }
94
- const client = await connectToPostgres(config);
96
+ const client = await connectToPostgres(config, resolved.connectionString);
95
97
  try {
96
98
  const { drizzle } = await import('drizzle-orm/node-postgres');
97
99
  return await fn(drizzle(client));
@@ -116,11 +118,14 @@ export async function withPublicDb(fn) {
116
118
  * @param fn Receives the Drizzle handle. In connection-string mode it is
117
119
  * bound to a transaction; in Supabase mode it is not — do not rely on
118
120
  * multi-statement atomicity there.
119
- * @param uid Connection-string mode only: overrides the user id. Omit it in
120
- * normal use — the id then comes from `db.getUserId()` when set, otherwise
121
- * from the signed-in Firebase user when `firebaseAuth` is configured.
122
- * Ignored in Supabase mode, which resolves identity via `db.getAccessToken`/
123
- * Firebase instead see {@link resolveAccessToken}.
121
+ * @param uid Connection-string mode only: overrides the user id. Omit it, or
122
+ * pass `null`, in normal use — either way the id then comes from
123
+ * `db.getUserId()` when set, otherwise from the signed-in Firebase user when
124
+ * `firebaseAuth` is configured. `null` is accepted alongside `undefined` so a
125
+ * caller's own lookup (which may itself come back empty) can be passed
126
+ * straight through without an extra check. Ignored in Supabase mode, which
127
+ * resolves identity via `db.getAccessToken`/Firebase instead — see
128
+ * {@link resolveAccessToken}.
124
129
  * @returns Whatever `fn` resolves to.
125
130
  * @throws If `db` is not set on your `RoutingConfig`, if no user id/access
126
131
  * token can be resolved, or the connection fails.
@@ -131,12 +136,13 @@ export async function withPublicDb(fn) {
131
136
  export async function withUserDb(fn, uid) {
132
137
  const db = config.db;
133
138
  requireDbConfig(db);
134
- if (resolveDbMode(db) === 'supabase') {
139
+ const resolved = await resolveDbMode(db);
140
+ if (resolved.mode === 'supabase') {
135
141
  const token = await resolveAccessToken(config);
136
- return fn(await supabaseDb(db.supabase, token));
142
+ return fn(await supabaseDb(resolved.supabase, token));
137
143
  }
138
144
  const userId = await resolveUserId(uid);
139
- const client = await connectToPostgres(config);
145
+ const client = await connectToPostgres(config, resolved.connectionString);
140
146
  const role = db.authenticatedRole ?? DEFAULT_ROLE;
141
147
  try {
142
148
  const { drizzle } = await import('drizzle-orm/node-postgres');
@@ -171,13 +177,14 @@ export async function withUserDb(fn, uid) {
171
177
  * @param build Returns the queries to run, via `.toSQL()` — never executes them directly.
172
178
  * @returns One result per query, in the same order as `build`'s array.
173
179
  */
174
- function requireSupabaseTransactionMode(db) {
175
- if (resolveDbMode(db) !== 'supabase') {
180
+ async function requireSupabaseTransactionMode(db) {
181
+ const resolved = await resolveDbMode(db);
182
+ if (resolved.mode !== 'supabase') {
176
183
  throw new Error('db: withUserTransaction/withPublicTransaction only run their Supabase-mode batch path ' +
177
184
  'right now. In connection-string mode, use withUserDb/withPublicDb\'s own `.transaction()` ' +
178
185
  '— it already provides real atomicity there.');
179
186
  }
180
- const supabase = db.supabase;
187
+ const supabase = resolved.supabase;
181
188
  if (supabase.rawSql === false) {
182
189
  throw new Error('db: withUserTransaction/withPublicTransaction need `cfni_exec_batch`, which runs through ' +
183
190
  '`cfni_exec` — both are unavailable while `db.supabase.rawSql` is `false`. Install ' +
@@ -211,7 +218,7 @@ async function runTransaction(supabase, bearerToken, build) {
211
218
  export async function withPublicTransaction(build) {
212
219
  const db = config.db;
213
220
  requireDbConfig(db);
214
- const supabase = requireSupabaseTransactionMode(db);
221
+ const supabase = await requireSupabaseTransactionMode(db);
215
222
  const { anonKey } = await resolveSupabaseEndpoint(supabase);
216
223
  return runTransaction(supabase, anonKey, build);
217
224
  }
@@ -239,7 +246,7 @@ export async function withPublicTransaction(build) {
239
246
  export async function withUserTransaction(build) {
240
247
  const db = config.db;
241
248
  requireDbConfig(db);
242
- const supabase = requireSupabaseTransactionMode(db);
249
+ const supabase = await requireSupabaseTransactionMode(db);
243
250
  const token = await resolveAccessToken(config);
244
251
  return runTransaction(supabase, token, build);
245
252
  }
@@ -1,9 +1,12 @@
1
- import type { ConfigValue } from '../types/types';
1
+ import type { FallibleConfigValue } from '../types/types';
2
2
  /**
3
3
  * Resolves a config value that may have been supplied directly or as a
4
- * sync/async function.
4
+ * sync/async function. A resolver may return `null` to mean "nothing here —
5
+ * fall through to whatever the caller checks next" — callers already treat
6
+ * that the same as `undefined` wherever they use `??`/truthiness on the
7
+ * result, so this passes it through rather than coercing it away.
5
8
  *
6
9
  * @param value The configured value or resolver.
7
- * @returns The resolved value, or `undefined` when nothing was configured.
10
+ * @returns The resolved value, or `null`/`undefined` when nothing was configured.
8
11
  */
9
- export default function resolveConfigValue<T>(value: ConfigValue<T | undefined> | undefined): Promise<T | undefined>;
12
+ export default function resolveConfigValue<T>(value: FallibleConfigValue<T> | undefined): Promise<T | null | undefined>;
@@ -1,9 +1,12 @@
1
1
  /**
2
2
  * Resolves a config value that may have been supplied directly or as a
3
- * sync/async function.
3
+ * sync/async function. A resolver may return `null` to mean "nothing here —
4
+ * fall through to whatever the caller checks next" — callers already treat
5
+ * that the same as `undefined` wherever they use `??`/truthiness on the
6
+ * result, so this passes it through rather than coercing it away.
4
7
  *
5
8
  * @param value The configured value or resolver.
6
- * @returns The resolved value, or `undefined` when nothing was configured.
9
+ * @returns The resolved value, or `null`/`undefined` when nothing was configured.
7
10
  */
8
11
  export default async function resolveConfigValue(value) {
9
12
  return typeof value === 'function' ? value() : value;
@@ -1,17 +1,37 @@
1
- import type { DbRoutingConfig } from '../types/types';
1
+ import type { DbRoutingConfig, SupabaseDbConfig } from '../types/types';
2
2
  /** Which transport the `db` exports use for a given config. */
3
3
  export type DbMode = 'postgres' | 'supabase';
4
+ /**
5
+ * The resolved transport for a `db` config, carrying whatever
6
+ * {@link resolveDbMode} already resolved to decide it — so a caller that
7
+ * picks `'postgres'` already has the connection string in hand and never
8
+ * needs to call a function-based `db.connectionString` a second time.
9
+ */
10
+ export type ResolvedDbMode = {
11
+ mode: 'postgres';
12
+ connectionString: string | undefined;
13
+ } | {
14
+ mode: 'supabase';
15
+ supabase: SupabaseDbConfig;
16
+ };
4
17
  /**
5
18
  * Decides how to reach the database from the shape of the `db` config.
6
19
  *
7
- * Direct Postgres wins whenever it is configured, so adding a `supabase`
8
- * block to an existing config never silently reroutes live traffic. With
9
- * neither set the result is still `'postgres'`, which lets
10
- * `connectToPostgres` raise its existing, more specific error about the
11
- * missing connection string.
20
+ * Direct Postgres wins whenever it resolves to a real connection string, so
21
+ * adding a `supabase` block to an existing config never silently reroutes
22
+ * live traffic. `connectionString` given as a function is actually called
23
+ * (and awaited) here not just checked for presence — so a resolver that
24
+ * comes back `null`/`undefined` (its own source having nothing right now)
25
+ * falls through to `supabase` instead of locking in `'postgres'` mode and
26
+ * failing later in `connectToPostgres` with no `supabase` fallback tried.
27
+ * With neither resolving to anything, the result is still `'postgres'`
28
+ * (with an `undefined` connection string), which lets `connectToPostgres`
29
+ * raise its existing, more specific error about the missing connection
30
+ * string — it is never asked to resolve `db.connectionString` itself, so a
31
+ * resolver given as a function runs exactly once per call here, not twice.
12
32
  *
13
33
  * @param db The `db` field off your routing config.
14
- * @returns `'postgres'` for direct connection-string access, `'supabase'`
15
- * for PostgREST access.
34
+ * @returns The resolved mode, plus whichever of `connectionString`/`supabase`
35
+ * that mode needs.
16
36
  */
17
- export default function resolveDbMode(db: DbRoutingConfig): DbMode;
37
+ export default function resolveDbMode(db: DbRoutingConfig): Promise<ResolvedDbMode>;
@@ -1,18 +1,29 @@
1
+ import resolveConfigValue from './resolve_config_value';
1
2
  /**
2
3
  * Decides how to reach the database from the shape of the `db` config.
3
4
  *
4
- * Direct Postgres wins whenever it is configured, so adding a `supabase`
5
- * block to an existing config never silently reroutes live traffic. With
6
- * neither set the result is still `'postgres'`, which lets
7
- * `connectToPostgres` raise its existing, more specific error about the
8
- * missing connection string.
5
+ * Direct Postgres wins whenever it resolves to a real connection string, so
6
+ * adding a `supabase` block to an existing config never silently reroutes
7
+ * live traffic. `connectionString` given as a function is actually called
8
+ * (and awaited) here not just checked for presence — so a resolver that
9
+ * comes back `null`/`undefined` (its own source having nothing right now)
10
+ * falls through to `supabase` instead of locking in `'postgres'` mode and
11
+ * failing later in `connectToPostgres` with no `supabase` fallback tried.
12
+ * With neither resolving to anything, the result is still `'postgres'`
13
+ * (with an `undefined` connection string), which lets `connectToPostgres`
14
+ * raise its existing, more specific error about the missing connection
15
+ * string — it is never asked to resolve `db.connectionString` itself, so a
16
+ * resolver given as a function runs exactly once per call here, not twice.
9
17
  *
10
18
  * @param db The `db` field off your routing config.
11
- * @returns `'postgres'` for direct connection-string access, `'supabase'`
12
- * for PostgREST access.
19
+ * @returns The resolved mode, plus whichever of `connectionString`/`supabase`
20
+ * that mode needs.
13
21
  */
14
- export default function resolveDbMode(db) {
15
- if (db.connectionString)
16
- return 'postgres';
17
- return db.supabase ? 'supabase' : 'postgres';
22
+ export default async function resolveDbMode(db) {
23
+ const connectionString = await resolveConfigValue(db.connectionString);
24
+ if (connectionString)
25
+ return { mode: 'postgres', connectionString };
26
+ if (db.supabase)
27
+ return { mode: 'supabase', supabase: db.supabase };
28
+ return { mode: 'postgres', connectionString: undefined };
18
29
  }
@@ -824,20 +824,31 @@ export interface IntlSitemap {
824
824
  * the config object is first created.
825
825
  */
826
826
  export type ConfigValue<T> = T | (() => T | Promise<T>);
827
+ /**
828
+ * A {@link ConfigValue} whose function form may also return `null` to mean
829
+ * "this source has nothing — fall through to the next one" (an env var
830
+ * default, or another resolver), the same way `getUserId`/`getAccessToken`
831
+ * already do. `undefined` means the same thing; both are treated
832
+ * identically by every resolver that reads one of these.
833
+ */
834
+ export type FallibleConfigValue<T> = ConfigValue<T | null | undefined>;
827
835
  export interface SupabaseDbConfig {
828
836
  /**
829
837
  * Supabase project URL, e.g. `https://abc.supabase.co`. Defaults to
830
838
  * `process.env.NEXT_PUBLIC_SUPABASE_URL`. May be a function (sync or
831
- * async) resolved on each use.
839
+ * async) resolved on each use — return `null`/`undefined` from it to
840
+ * fall through to the environment variable instead of erroring.
832
841
  */
833
- url?: ConfigValue<string | undefined>;
842
+ url?: FallibleConfigValue<string>;
834
843
  /**
835
844
  * Supabase anon (publishable) key. Defaults to
836
845
  * `process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY`. This is the only key the
837
846
  * `db` module ever needs — never put a service-role key here. May be a
838
- * function (sync or async) resolved on each use.
847
+ * function (sync or async) resolved on each use — return
848
+ * `null`/`undefined` from it to fall through to the environment variable
849
+ * instead of erroring.
839
850
  */
840
- anonKey?: ConfigValue<string | undefined>;
851
+ anonKey?: FallibleConfigValue<string>;
841
852
  /**
842
853
  * Name of the Postgres function that runs the generated SQL. Defaults to
843
854
  * `'cfni_exec'` — the function shipped in `supabase/cfni_exec.sql`.
@@ -865,9 +876,11 @@ export interface DbRoutingConfig {
865
876
  * resolved on each connect. The function form is how you reach a value that
866
877
  * isn't available at module scope — e.g. a Cloudflare Hyperdrive binding:
867
878
  * `connectionString: async () => (await getCloudflareContext({ async: true
868
- * })).env.HYPERDRIVE.connectionString`.
879
+ * })).env.HYPERDRIVE.connectionString`. Return `null`/`undefined` from it
880
+ * when there is nothing to give — both surface the same "could not
881
+ * resolve a Postgres connection string" error as leaving this unset.
869
882
  */
870
- connectionString?: ConfigValue<string | undefined>;
883
+ connectionString?: FallibleConfigValue<string>;
871
884
  /**
872
885
  * Whether the pooled client is closed once the last in-flight
873
886
  * `withPublicDb`/`withUserDb` call of the request finishes.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare-next-intl",
3
- "version": "0.8.12",
3
+ "version": "0.8.14",
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",
@@ -242,6 +242,7 @@
242
242
  "dependencies": {
243
243
  "@microsoft/clarity": "^1.0.2",
244
244
  "@supabase/supabase-js": "^2.112.3",
245
+ "drizzle-kit": "^0.31.10",
245
246
  "drizzle-orm": "^0.45.2",
246
247
  "embedded-postgres": "^18.4.0-beta.17",
247
248
  "firebase": "^12.17.0",
@@ -269,7 +270,6 @@
269
270
  "@types/react": "^19.0.0",
270
271
  "@types/react-dom": "^19.0.0",
271
272
  "@vitest/coverage-v8": "^3.2.7",
272
- "drizzle-kit": "^0.31.10",
273
273
  "eslint": "^9.28.0",
274
274
  "eslint-config-next": "15.3.3",
275
275
  "eslint-config-prettier": "^10.1.2",