cloudflare-next-intl 0.8.3 → 0.8.5
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 +44 -13
- package/bin/db_codegen.mjs +14 -7
- package/dist/src/db/codegen_paths.d.ts +6 -0
- package/dist/src/db/codegen_paths.js +19 -1
- package/dist/src/db/connection.d.ts +1 -1
- package/dist/src/db/connection.js +8 -17
- package/dist/src/db/helpers.d.ts +16 -0
- package/dist/src/db/helpers.js +18 -0
- package/dist/src/db/index.d.ts +3 -4
- package/dist/src/db/index.js +3 -4
- package/dist/src/db/require_config.js +1 -1
- package/dist/src/db/resolve_mode.d.ts +2 -2
- package/dist/src/db/resolve_mode.js +3 -3
- package/dist/src/types/types.d.ts +6 -12
- package/llms.txt +7 -2
- package/package.json +1 -1
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 (
|
|
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
|
|
394
|
-
(
|
|
393
|
+
Thin Postgres/Drizzle data-access layer over a Postgres connection string
|
|
394
|
+
(which may come from a Cloudflare Hyperdrive binding). `pg` and `drizzle-orm` ship as dependencies of
|
|
395
395
|
this package, so there is nothing extra to install. They are loaded through
|
|
396
396
|
dynamic `import()` inside the `db` exports, so an app that never calls a `db`
|
|
397
397
|
export never pulls them into its bundle. Enable it by setting `db` on your
|
|
@@ -405,21 +405,20 @@ export default setIntlConfig({
|
|
|
405
405
|
locales: ["en", "uk"] as const,
|
|
406
406
|
defaultLocale: "en",
|
|
407
407
|
generate: { getCloudflareContext },
|
|
408
|
-
db: {
|
|
408
|
+
db: {
|
|
409
|
+
connectionString: async () =>
|
|
410
|
+
(await getCloudflareContext({ async: true })).env.HYPERDRIVE
|
|
411
|
+
.connectionString,
|
|
412
|
+
},
|
|
409
413
|
});
|
|
410
414
|
```
|
|
411
415
|
|
|
412
416
|
`db` fields (all optional):
|
|
413
417
|
|
|
414
418
|
- `connectionString` — a Postgres connection string, or a sync/async function
|
|
415
|
-
returning one (resolved on each connect
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
always wins over the binding, which is what makes local dev / build-time
|
|
419
|
-
evaluation work.
|
|
420
|
-
- `hyperdriveBinding` — name of the Hyperdrive binding on `env` to read a
|
|
421
|
-
connection string from when `connectionString` is not set. Defaults to
|
|
422
|
-
`'HYPERDRIVE'`. Requires `generate.getCloudflareContext` to be configured.
|
|
419
|
+
returning one (resolved on each connect). The function form is how you reach
|
|
420
|
+
a value that isn't available at module scope — a Cloudflare Hyperdrive
|
|
421
|
+
binding, or a secret store — as in the example above.
|
|
423
422
|
- `disconnectAfterRequest` — whether the pooled client is closed once the
|
|
424
423
|
last in-flight `withPublicDb`/`withUserDb` call of the request
|
|
425
424
|
finishes. Defaults to `true` (one connection per request, released to
|
|
@@ -443,7 +442,7 @@ query code is identical either way — switching is a config change only.
|
|
|
443
442
|
|
|
444
443
|
| Config | Transport | Use when |
|
|
445
444
|
|---|---|---|
|
|
446
|
-
| `connectionString`
|
|
445
|
+
| `connectionString` | Direct Postgres via `pg` | You have a Postgres connection string, or a Hyperdrive binding to read one from. |
|
|
447
446
|
| `supabase` | Supabase Data API (PostgREST) | You only have `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_ANON_KEY`. |
|
|
448
447
|
|
|
449
448
|
A direct connection always wins if both are configured, so adding a `supabase`
|
|
@@ -559,6 +558,38 @@ re-export of `drizzle-orm`'s common query-building primitives (`eq`, `and`,
|
|
|
559
558
|
`drizzle-orm` directly — this package re-exports the query-operator surface
|
|
560
559
|
only, not the whole library.
|
|
561
560
|
|
|
561
|
+
#### Schema codegen (`cfni-db-codegen`)
|
|
562
|
+
|
|
563
|
+
The package ships a `cfni-db-codegen` binary that regenerates Drizzle models by
|
|
564
|
+
introspecting a live Postgres with `drizzle-kit pull`, patches drizzle-kit's
|
|
565
|
+
bare function-call defaults into raw-SQL-wrapped ones, and writes a
|
|
566
|
+
`manifest.json` next to the schema so `--check` can fail CI when the DDL
|
|
567
|
+
changed without regenerating.
|
|
568
|
+
|
|
569
|
+
```bash
|
|
570
|
+
npx cfni-db-codegen
|
|
571
|
+
npx cfni-db-codegen --check
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
| Flag | Env | Default |
|
|
575
|
+
| --- | --- | --- |
|
|
576
|
+
| `--ddl-dir=` | `CFNI_DB_DDL_DIR` | `supabase/data-base` |
|
|
577
|
+
| `--out-dir=` | `CFNI_DB_OUT_DIR` | `src/shared/db/generated` |
|
|
578
|
+
| `--out-file=` | `CFNI_DB_OUT_FILE` | `schema.ts` |
|
|
579
|
+
| `--db-url=` | `CODEGEN_DATABASE_URL` | `postgresql://postgres:postgres@127.0.0.1:54322/postgres` |
|
|
580
|
+
| `--drizzle-config=` | `CFNI_DB_DRIZZLE_CONFIG` | none |
|
|
581
|
+
| `--check` | — | off |
|
|
582
|
+
|
|
583
|
+
`--out-dir` may be repeated, or given a comma-separated list, to generate the
|
|
584
|
+
same schema into several projects in one run (`CFNI_DB_OUT_DIR` accepts a
|
|
585
|
+
comma-separated list too). The database is introspected once and the identical
|
|
586
|
+
schema plus manifest is written to every target; `--check` verifies all of
|
|
587
|
+
them and fails naming the first one that is stale.
|
|
588
|
+
|
|
589
|
+
```bash
|
|
590
|
+
npx cfni-db-codegen --out-dir=src/shared/db/generated --out-dir=../other-app/src/db/generated
|
|
591
|
+
```
|
|
592
|
+
|
|
562
593
|
#### Testing code that calls `withPublicDb`/`withUserDb`
|
|
563
594
|
|
|
564
595
|
`cloudflare-next-intl/dbTesting` exports a fake `DrizzleDb` so repository/unit
|
package/bin/db_codegen.mjs
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
// Regenerates Drizzle models by introspecting a live Postgres with drizzle-kit.
|
|
3
3
|
// Usage: cfni-db-codegen [--check] [--ddl-dir=…] [--out-dir=…] [--out-file=…] [--db-url=…] [--drizzle-config=…]
|
|
4
4
|
//
|
|
5
|
+
// --out-dir may be repeated, or given a comma-separated list, to generate the
|
|
6
|
+
// same schema into several projects at once (CFNI_DB_OUT_DIR accepts a
|
|
7
|
+
// comma-separated list too).
|
|
8
|
+
//
|
|
5
9
|
// Needs a reachable Postgres to introspect — any Postgres, not specifically
|
|
6
10
|
// a Docker one. Set CODEGEN_DATABASE_URL to point at whichever you have:
|
|
7
11
|
// local Supabase (./supabase/scripts/db_start.sh --reset, needs Docker), a
|
|
@@ -55,9 +59,10 @@ function ddlHash() {
|
|
|
55
59
|
const hash = ddlHash();
|
|
56
60
|
|
|
57
61
|
if (paths.check) {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
for (const target of paths.targets) {
|
|
63
|
+
const previous = existsSync(target.manifest) ? JSON.parse(readFileSync(target.manifest, "utf8")).ddlHash : null;
|
|
64
|
+
if (previous === hash) continue;
|
|
65
|
+
console.error(`❌ ${relative(process.cwd(), paths.ddlDir)} changed without regenerating models in ${relative(process.cwd(), target.outDir)}. Run: npm run db:codegen`);
|
|
61
66
|
process.exit(1);
|
|
62
67
|
}
|
|
63
68
|
console.log(`✅ Drizzle models are in sync with ${relative(process.cwd(), paths.ddlDir)}`);
|
|
@@ -69,7 +74,6 @@ await assertReachable(paths.dbUrl);
|
|
|
69
74
|
rmSync(paths.pullDir, { recursive: true, force: true });
|
|
70
75
|
execFileSync('npx', ['drizzle-kit', 'pull', ...(paths.drizzleConfig ? [`--config=${paths.drizzleConfig}`] : [])], { stdio: 'inherit' });
|
|
71
76
|
|
|
72
|
-
mkdirSync(paths.outDir, { recursive: true });
|
|
73
77
|
const pulled = join(paths.pullDir, "schema.ts");
|
|
74
78
|
if (!existsSync(pulled)) {
|
|
75
79
|
console.error(`❌ drizzle-kit pull produced no schema at ${pulled}`);
|
|
@@ -104,7 +108,10 @@ function patchBareFunctionCallDefaults(source) {
|
|
|
104
108
|
|
|
105
109
|
const banner = `// GENERATED by cfni-db-codegen from ${relative(process.cwd(), paths.ddlDir)} — do not edit.\n`;
|
|
106
110
|
const pulledSource = patchBareFunctionCallDefaults(readFileSync(pulled, "utf8"));
|
|
107
|
-
writeFileSync(paths.outFile, banner + pulledSource);
|
|
108
111
|
rmSync(paths.pullDir, { recursive: true, force: true });
|
|
109
|
-
|
|
110
|
-
|
|
112
|
+
for (const target of paths.targets) {
|
|
113
|
+
mkdirSync(target.outDir, { recursive: true });
|
|
114
|
+
writeFileSync(target.outFile, banner + pulledSource);
|
|
115
|
+
writeFileSync(target.manifest, `${JSON.stringify({ ddlHash: hash }, null, 2)}\n`);
|
|
116
|
+
console.log(`✅ Generated ${relative(process.cwd(), target.outFile)}`);
|
|
117
|
+
}
|
|
@@ -4,6 +4,13 @@ const DEFAULT_OUT_DIR = 'src/shared/db/generated';
|
|
|
4
4
|
const DEFAULT_OUT_FILE = 'schema.ts';
|
|
5
5
|
const DEFAULT_DB_URL = 'postgresql://postgres:postgres@127.0.0.1:54322/postgres';
|
|
6
6
|
const DEFAULT_TIMEOUT_MS = 5000;
|
|
7
|
+
function flags(argv, name) {
|
|
8
|
+
const prefix = `--${name}=`;
|
|
9
|
+
return argv.filter((arg) => arg.startsWith(prefix)).map((arg) => arg.slice(prefix.length));
|
|
10
|
+
}
|
|
11
|
+
function list(value) {
|
|
12
|
+
return value.split(',').map((part) => part.trim()).filter(Boolean);
|
|
13
|
+
}
|
|
7
14
|
function flag(argv, name) {
|
|
8
15
|
const prefix = `--${name}=`;
|
|
9
16
|
const hit = argv.find((arg) => arg.startsWith(prefix));
|
|
@@ -15,11 +22,22 @@ function abs(cwd, value) {
|
|
|
15
22
|
/** Resolves every codegen path from flags, then env, then the documented defaults. */
|
|
16
23
|
export default function resolveCodegenPaths(argv, env, cwd) {
|
|
17
24
|
const ddlDir = abs(cwd, flag(argv, 'ddl-dir') ?? env.CFNI_DB_DDL_DIR ?? DEFAULT_DDL_DIR);
|
|
18
|
-
const
|
|
25
|
+
const outDirArgs = flags(argv, 'out-dir').flatMap(list);
|
|
26
|
+
const outDirs = (outDirArgs.length > 0
|
|
27
|
+
? outDirArgs
|
|
28
|
+
: list(env.CFNI_DB_OUT_DIR ?? '')).map((dir) => abs(cwd, dir));
|
|
29
|
+
if (outDirs.length === 0)
|
|
30
|
+
outDirs.push(abs(cwd, DEFAULT_OUT_DIR));
|
|
31
|
+
const outDir = outDirs[0];
|
|
19
32
|
const outFileName = flag(argv, 'out-file') ?? env.CFNI_DB_OUT_FILE ?? DEFAULT_OUT_FILE;
|
|
20
33
|
const drizzleConfig = flag(argv, 'drizzle-config') ?? env.CFNI_DB_DRIZZLE_CONFIG ?? null;
|
|
21
34
|
return {
|
|
22
35
|
ddlDir,
|
|
36
|
+
targets: outDirs.map((dir) => ({
|
|
37
|
+
outDir: dir,
|
|
38
|
+
outFile: join(dir, outFileName),
|
|
39
|
+
manifest: join(dir, 'manifest.json'),
|
|
40
|
+
})),
|
|
23
41
|
outDir,
|
|
24
42
|
outFile: join(outDir, outFileName),
|
|
25
43
|
pullDir: resolve(outDir, '..', '.drizzle-pull'),
|
|
@@ -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
|
|
26
|
+
* `db.connectionString`.
|
|
27
27
|
*/
|
|
28
28
|
export default function connectToPostgres(config: DbConfig): Promise<Client>;
|
|
29
29
|
/**
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import reportError from '../error_handling/report_error';
|
|
2
2
|
import requireDbConfig from './require_config';
|
|
3
3
|
import resolveConfigValue from './resolve_config_value';
|
|
4
|
-
const DEFAULT_BINDING = 'HYPERDRIVE';
|
|
5
4
|
const DEFAULT_DISCONNECT_TIMEOUT_MS = 2000;
|
|
6
5
|
let connectionString = null;
|
|
7
6
|
let client = null;
|
|
@@ -27,24 +26,16 @@ export function resetConnectionState() {
|
|
|
27
26
|
activeUsers = 0;
|
|
28
27
|
}
|
|
29
28
|
/**
|
|
30
|
-
* Resolves the connection string from `db.connectionString
|
|
31
|
-
*
|
|
29
|
+
* Resolves the connection string from `db.connectionString`, awaiting it when
|
|
30
|
+
* it was given as a function.
|
|
32
31
|
*/
|
|
33
|
-
async function resolveConnectionString(
|
|
32
|
+
async function resolveConnectionString(db) {
|
|
34
33
|
const configured = await resolveConfigValue(db.connectionString);
|
|
35
34
|
if (configured)
|
|
36
35
|
return configured;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const env = context?.env;
|
|
41
|
-
const binding = env?.[db.hyperdriveBinding ?? DEFAULT_BINDING];
|
|
42
|
-
if (binding?.connectionString)
|
|
43
|
-
return binding.connectionString;
|
|
44
|
-
}
|
|
45
|
-
throw new Error('db: could not resolve a Postgres connection string. Set `db.connectionString`, or ' +
|
|
46
|
-
`configure \`generate.getCloudflareContext\` and a \`${db.hyperdriveBinding ?? DEFAULT_BINDING}\` ` +
|
|
47
|
-
'Hyperdrive binding on your Worker.');
|
|
36
|
+
throw new Error('db: could not resolve a Postgres connection string. Set `db.connectionString` ' +
|
|
37
|
+
'to a connection string, or to a function returning one (e.g. reading a ' +
|
|
38
|
+
'Hyperdrive binding off `getCloudflareContext().env`).');
|
|
48
39
|
}
|
|
49
40
|
// A single `pg.Client` is not safe for concurrent queries; Next.js fires many
|
|
50
41
|
// in parallel per request. Serializing every `query` through one promise chain
|
|
@@ -75,7 +66,7 @@ function serializeQueries(raw) {
|
|
|
75
66
|
* @param config Your routing config; `config.db` must be set.
|
|
76
67
|
* @returns The connected, shared client.
|
|
77
68
|
* @throws If `db` is not set, or no connection string can be resolved from
|
|
78
|
-
* `db.connectionString
|
|
69
|
+
* `db.connectionString`.
|
|
79
70
|
*/
|
|
80
71
|
export default async function connectToPostgres(config) {
|
|
81
72
|
const db = config.db;
|
|
@@ -84,7 +75,7 @@ export default async function connectToPostgres(config) {
|
|
|
84
75
|
if (connectingPromise === null) {
|
|
85
76
|
connectingPromise = (async () => {
|
|
86
77
|
try {
|
|
87
|
-
connectionString ?? (connectionString = await resolveConnectionString(
|
|
78
|
+
connectionString ?? (connectionString = await resolveConnectionString(db));
|
|
88
79
|
const { Client } = await import('pg');
|
|
89
80
|
const created = serializeQueries(new Client({ connectionString }));
|
|
90
81
|
client = created;
|
package/dist/src/db/helpers.d.ts
CHANGED
|
@@ -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>;
|
package/dist/src/db/helpers.js
CHANGED
|
@@ -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)})`;
|
package/dist/src/db/index.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
13
|
-
*
|
|
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
|
*
|
package/dist/src/db/index.js
CHANGED
|
@@ -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
|
-
*
|
|
13
|
-
*
|
|
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
|
|
17
|
+
'(connectionString or supabase) to the config passed to ' +
|
|
18
18
|
'`setIntlConfig` before using any db export.');
|
|
19
19
|
}
|
|
20
20
|
}
|
|
@@ -8,10 +8,10 @@ export type DbMode = 'postgres' | 'supabase';
|
|
|
8
8
|
* block to an existing config never silently reroutes live traffic. With
|
|
9
9
|
* neither set the result is still `'postgres'`, which lets
|
|
10
10
|
* `connectToPostgres` raise its existing, more specific error about the
|
|
11
|
-
* missing
|
|
11
|
+
* missing connection string.
|
|
12
12
|
*
|
|
13
13
|
* @param db The `db` field off your routing config.
|
|
14
|
-
* @returns `'postgres'` for connection-string
|
|
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
|
|
8
|
+
* missing connection string.
|
|
9
9
|
*
|
|
10
10
|
* @param db The `db` field off your routing config.
|
|
11
|
-
* @returns `'postgres'` for connection-string
|
|
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
|
|
15
|
+
if (db.connectionString)
|
|
16
16
|
return 'postgres';
|
|
17
17
|
return db.supabase ? 'supabase' : 'postgres';
|
|
18
18
|
}
|
|
@@ -846,19 +846,13 @@ export interface SupabaseDbConfig {
|
|
|
846
846
|
}
|
|
847
847
|
export interface DbRoutingConfig {
|
|
848
848
|
/**
|
|
849
|
-
* Postgres connection string
|
|
850
|
-
*
|
|
851
|
-
*
|
|
852
|
-
*
|
|
853
|
-
*
|
|
849
|
+
* Postgres connection string, or a function (sync or async) returning one,
|
|
850
|
+
* resolved on each connect. The function form is how you reach a value that
|
|
851
|
+
* isn't available at module scope — e.g. a Cloudflare Hyperdrive binding:
|
|
852
|
+
* `connectionString: async () => (await getCloudflareContext({ async: true
|
|
853
|
+
* })).env.HYPERDRIVE.connectionString`.
|
|
854
854
|
*/
|
|
855
855
|
connectionString?: ConfigValue<string | undefined>;
|
|
856
|
-
/**
|
|
857
|
-
* Name of the Hyperdrive binding on `env` whose `connectionString` is used
|
|
858
|
-
* when `connectionString` is not set. Defaults to `'HYPERDRIVE'`. Requires
|
|
859
|
-
* `generate.getCloudflareContext` to be configured.
|
|
860
|
-
*/
|
|
861
|
-
hyperdriveBinding?: string;
|
|
862
856
|
/**
|
|
863
857
|
* Whether the pooled client is closed once the last in-flight
|
|
864
858
|
* `withPublicDb`/`withUserDb` call of the request finishes.
|
|
@@ -889,7 +883,7 @@ export interface DbRoutingConfig {
|
|
|
889
883
|
* `withUserDb` behave the same either way, so switching is a config change
|
|
890
884
|
* with no app-code change.
|
|
891
885
|
*
|
|
892
|
-
* Ignored when `connectionString`
|
|
886
|
+
* Ignored when `connectionString` is set: a direct
|
|
893
887
|
* connection always wins, so adding this block cannot silently reroute
|
|
894
888
|
* live traffic. Requires the `cfni_exec` function from
|
|
895
889
|
* `supabase/cfni_exec.sql` to be installed in your database.
|
package/llms.txt
CHANGED
|
@@ -50,7 +50,7 @@ other subpath can be used.
|
|
|
50
50
|
|
|
51
51
|
Two transports, picked by which `db` fields are set — `pg`/`drizzle-orm`/`@supabase/supabase-js` ship as dependencies and load via dynamic `import()`, so nothing bundles unless a `db` export is called.
|
|
52
52
|
|
|
53
|
-
- Direct Postgres (wins if configured): `db.connectionString` — Postgres connection string or a sync/async function returning one (resolved on each connect)
|
|
53
|
+
- Direct Postgres (wins if configured): `db.connectionString` — Postgres connection string, or a sync/async function returning one (resolved on each connect). The function form is the way to read a value unavailable at module scope, e.g. a Cloudflare Hyperdrive binding: `connectionString: async () => (await getCloudflareContext({ async: true })).env.HYPERDRIVE.connectionString`. There is no separate `hyperdriveBinding` option.
|
|
54
54
|
- Supabase Data API (used only when neither of the above is set): `db.supabase` — `{ url?, anonKey?, execFunction? }` where `url`/`anonKey` each accept a string or a sync/async function returning one, defaulting `url`/`anonKey` to `NEXT_PUBLIC_SUPABASE_URL`/`NEXT_PUBLIC_SUPABASE_ANON_KEY`. Requires installing `supabase/cfni_exec.sql` (a `security invoker` SQL-exec function) in your database. No multi-statement transactions — each statement in a `withUserDb` callback is its own round-trip.
|
|
55
55
|
- `db.disconnectAfterRequest` — direct-Postgres mode only: closes the pooled client once the last in-flight `withPublicDb`/`withUserDb` call of the request finishes. Defaults to `true`; set `false` to keep the connection open for the isolate's lifetime (holds a Hyperdrive slot between requests).
|
|
56
56
|
- `db.authenticatedRole` — direct-Postgres mode only: Postgres role assumed inside `withUserDb`'s transaction. Defaults to `'authenticated'` (Supabase RLS convention).
|
|
@@ -60,6 +60,7 @@ Two transports, picked by which `db` fields are set — `pg`/`drizzle-orm`/`@sup
|
|
|
60
60
|
- `withPublicDb(fn)` — anonymous role. Direct-Postgres mode: the request's pooled connection, no transaction, no role switch. Supabase mode: the anon key as the PostgREST bearer token. Either way, no user id is attached — RLS keyed on `auth.jwt()` denies access.
|
|
61
61
|
- `withUserDb(fn, uid?)` — signed-in-user role. Direct-Postgres mode: a transaction with `set_config('request.jwt.claims', ...)` + `set local role`, `uid` resolution order explicit arg → `db.getUserId()` → Firebase auth uid → throws. Supabase mode: identity rides on the JWT from `db.getAccessToken`/Firebase instead (`uid` param is ignored), no transaction wraps the call.
|
|
62
62
|
- `./dbHelpers` functions are plain Drizzle `sql`-building utilities with no config dependency — usable standalone.
|
|
63
|
+
- `cfni-db-codegen` binary — regenerates Drizzle models via `drizzle-kit pull`. Flags/env: `--ddl-dir`/`CFNI_DB_DDL_DIR`, `--out-dir`/`CFNI_DB_OUT_DIR`, `--out-file`/`CFNI_DB_OUT_FILE`, `--db-url`/`CODEGEN_DATABASE_URL`, `--drizzle-config`/`CFNI_DB_DRIZZLE_CONFIG`, `--check`. `--out-dir` is repeatable and accepts a comma-separated list, so one run generates the same schema (and `manifest.json`) into several projects; `--check` verifies every target.
|
|
63
64
|
|
|
64
65
|
```typescript
|
|
65
66
|
// src/i18n/intl_config.ts
|
|
@@ -69,7 +70,11 @@ export default setIntlConfig({
|
|
|
69
70
|
locales: ["en", "uk"] as const,
|
|
70
71
|
defaultLocale: "en",
|
|
71
72
|
generate: { getCloudflareContext },
|
|
72
|
-
db: {
|
|
73
|
+
db: {
|
|
74
|
+
connectionString: async () =>
|
|
75
|
+
(await getCloudflareContext({ async: true })).env.HYPERDRIVE
|
|
76
|
+
.connectionString,
|
|
77
|
+
},
|
|
73
78
|
});
|
|
74
79
|
|
|
75
80
|
// anywhere on the server
|