cloudflare-next-intl 0.8.13 → 0.8.15

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.
@@ -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
  }
@@ -37,8 +37,12 @@ export declare function excluded<T extends Table>(table: T): {
37
37
  */
38
38
  export declare function onConflictSet<T extends Table, K extends keyof T["_"]["columns"]>(table: T, fields: K[]): Record<string, SQL>;
39
39
  export type TimeUnit = "days" | "hours" | "minutes" | "months" | "years" | "weeks";
40
+ /** General SQL helper returning `now()`. */
41
+ export declare function now(): SQL;
40
42
  /** General SQL helper generating a timestamp expression relative to now (`now() - (N unit)::interval`). */
41
43
  export declare function ago(amount: number, unit: TimeUnit): SQL;
44
+ /** General SQL helper generating a timestamp expression ahead of now (`now() + (N unit)::interval`). */
45
+ export declare function fromNow(amount: number, unit: TimeUnit): SQL;
42
46
  /** General SQL helper returning `current_date`. */
43
47
  export declare function currentDate(): SQL;
44
48
  /** General SQL helper for window function `count(*) over ()`. */
@@ -66,10 +66,18 @@ export function onConflictSet(table, fields) {
66
66
  }
67
67
  return setObj;
68
68
  }
69
+ /** General SQL helper returning `now()`. */
70
+ export function now() {
71
+ return sql `now()`;
72
+ }
69
73
  /** General SQL helper generating a timestamp expression relative to now (`now() - (N unit)::interval`). */
70
74
  export function ago(amount, unit) {
71
75
  return sql `now() - (${amount} || ' ' || ${unit})::interval`;
72
76
  }
77
+ /** General SQL helper generating a timestamp expression ahead of now (`now() + (N unit)::interval`). */
78
+ export function fromNow(amount, unit) {
79
+ return sql `now() + (${amount} || ' ' || ${unit})::interval`;
80
+ }
73
81
  /** General SQL helper returning `current_date`. */
74
82
  export function currentDate() {
75
83
  return sql `current_date`;
@@ -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.13",
3
+ "version": "0.8.15",
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",