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 +5 -5
- package/bin/db_codegen.mjs +16 -8
- package/dist/src/db/codegen_paths.js +6 -3
- package/dist/src/db/connection.d.ts +5 -1
- package/dist/src/db/connection.js +6 -2
- package/dist/src/db/context.d.ts +9 -6
- package/dist/src/db/context.js +26 -19
- package/dist/src/db/resolve_config_value.d.ts +7 -4
- package/dist/src/db/resolve_config_value.js +5 -2
- package/dist/src/db/resolve_mode.d.ts +29 -9
- package/dist/src/db/resolve_mode.js +22 -11
- package/dist/src/types/types.d.ts +19 -6
- package/package.json +2 -2
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` |
|
|
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
|
|
796
|
-
|
|
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/
|
|
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.
|
package/bin/db_codegen.mjs
CHANGED
|
@@ -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 {
|
|
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)
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
|
|
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
|
-
//
|
|
37
|
-
//
|
|
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(
|
|
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;
|
package/dist/src/db/context.d.ts
CHANGED
|
@@ -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
|
|
48
|
-
* normal use — the id then comes from
|
|
49
|
-
* from the signed-in Firebase user when
|
|
50
|
-
*
|
|
51
|
-
*
|
|
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
|
/**
|
package/dist/src/db/context.js
CHANGED
|
@@ -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
|
-
|
|
90
|
-
|
|
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
|
|
120
|
-
* normal use — the id then comes from
|
|
121
|
-
* from the signed-in Firebase user when
|
|
122
|
-
*
|
|
123
|
-
*
|
|
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
|
-
|
|
139
|
+
const resolved = await resolveDbMode(db);
|
|
140
|
+
if (resolved.mode === 'supabase') {
|
|
135
141
|
const token = await resolveAccessToken(config);
|
|
136
|
-
return fn(await supabaseDb(
|
|
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
|
-
|
|
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 =
|
|
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 {
|
|
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:
|
|
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
|
|
8
|
-
* block to an existing config never silently reroutes
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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
|
|
15
|
-
*
|
|
34
|
+
* @returns The resolved mode, plus whichever of `connectionString`/`supabase`
|
|
35
|
+
* that mode needs.
|
|
16
36
|
*/
|
|
17
|
-
export default function resolveDbMode(db: DbRoutingConfig):
|
|
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
|
|
5
|
-
* block to an existing config never silently reroutes
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
|
12
|
-
*
|
|
19
|
+
* @returns The resolved mode, plus whichever of `connectionString`/`supabase`
|
|
20
|
+
* that mode needs.
|
|
13
21
|
*/
|
|
14
|
-
export default function resolveDbMode(db) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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.
|
|
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",
|