cloudflare-next-intl 0.8.32 → 0.8.34
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 +1 -1
- package/dist/src/db/context.js +23 -6
- package/dist/src/firebase_auth/client/components/auto_firebase_performance_events.d.ts +3 -4
- package/dist/src/firebase_auth/client/components/auto_firebase_performance_events.js +3 -71
- package/dist/src/firebase_auth/middleware/update_session.js +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -609,7 +609,7 @@ Every query in the array is executed sequentially in a single transaction block/
|
|
|
609
609
|
|
|
610
610
|
Either way, a failure on any statement rolls back every statement that ran before it in the transaction.
|
|
611
611
|
|
|
612
|
-
Each result is the `{ rows, rowCount }` shape. Because the callback only builds queries, a later statement in a `.transaction()` callback cannot read an earlier one's result — build every statement from arguments/closures you already have. `await`ing a query directly
|
|
612
|
+
Each result is the `{ rows, rowCount }` shape. Because the callback only builds queries, a later statement in a `.transaction()` callback cannot read an earlier one's result — build every statement from arguments/closures you already have. The handle passed into the callback is a fully working Drizzle query builder — chain `.insert()`/`.select()`/`.where()`/etc. freely — but it has no real connection behind it: `await`ing a query directly (instead of calling `.toSQL()` and returning it) throws once execution is actually attempted, not on the property access or chaining itself, to prevent running queries outside the transaction boundary.
|
|
613
613
|
|
|
614
614
|
#### Supabase mode and REST translation
|
|
615
615
|
|
package/dist/src/db/context.js
CHANGED
|
@@ -101,7 +101,7 @@ async function postgresDb(drizzleHandle, rawClient) {
|
|
|
101
101
|
* interleave statements into this transaction.
|
|
102
102
|
*/
|
|
103
103
|
async function runPostgresTransaction(rawClient, build) {
|
|
104
|
-
const queries = await build
|
|
104
|
+
const queries = await callBuild(build);
|
|
105
105
|
await rawClient.query('begin');
|
|
106
106
|
try {
|
|
107
107
|
const results = [];
|
|
@@ -126,14 +126,31 @@ async function runPostgresTransaction(rawClient, build) {
|
|
|
126
126
|
* output) throws immediately here instead of hanging or silently running
|
|
127
127
|
* outside the batch.
|
|
128
128
|
*/
|
|
129
|
-
function buildOnlyDb() {
|
|
130
|
-
const
|
|
129
|
+
async function buildOnlyDb() {
|
|
130
|
+
const { drizzle } = await import('drizzle-orm/pg-proxy');
|
|
131
|
+
return drizzle(() => {
|
|
131
132
|
throw new Error('db: this Drizzle handle is for building statements only — call `.toSQL()` on each ' +
|
|
132
133
|
'query and return the array, do not `await`/execute it directly. Awaiting a query ' +
|
|
133
134
|
'inside a Supabase-mode db.transaction() callback runs it outside the batch, with no ' +
|
|
134
135
|
'atomicity, which is exactly what `.transaction()` exists to prevent.');
|
|
135
|
-
};
|
|
136
|
-
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Runs `build` against a fresh build-only handle, unwrapping pg-proxy's
|
|
140
|
+
* `Failed query: ...` wrapper (with the real message on `.cause`) so a
|
|
141
|
+
* caller who awaits a query instead of collecting `.toSQL()` sees the
|
|
142
|
+
* build-only guidance directly, not the wrapper's generic text.
|
|
143
|
+
*/
|
|
144
|
+
async function callBuild(build) {
|
|
145
|
+
try {
|
|
146
|
+
return await build(await buildOnlyDb());
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
const cause = error?.cause;
|
|
150
|
+
if (error instanceof Error && cause instanceof Error)
|
|
151
|
+
throw cause;
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
137
154
|
}
|
|
138
155
|
/**
|
|
139
156
|
* Runs a query as the **anonymous** role: no transaction, no role switch, no
|
|
@@ -299,7 +316,7 @@ async function runTransaction(supabase, bearerToken, build) {
|
|
|
299
316
|
'unavailable while `db.supabase.rawSql` is `false`. Install cfni_exec.sql and drop ' +
|
|
300
317
|
'`rawSql: false`, or use `db.connectionString` for a direct Postgres connection instead.');
|
|
301
318
|
}
|
|
302
|
-
const queries = await build
|
|
319
|
+
const queries = await callBuild(build);
|
|
303
320
|
const batchQueries = queries.map((query) => ({ sql: query.sql, params: query.params }));
|
|
304
321
|
return runTransactionBatch(supabase, bearerToken, batchQueries);
|
|
305
322
|
}
|
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
* Auto-rendered alongside `FirebaseAuthClientProvider` when
|
|
3
3
|
* `firebaseAuth.performance` isn't `false` — records Firebase Performance
|
|
4
4
|
* custom traces for Web Vitals metrics (`web_cls`, `web_fcp`, `web_fid`,
|
|
5
|
-
* `web_lcp`, `web_ttfb`, `web_inp`)
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* never resolves an instance).
|
|
5
|
+
* `web_lcp`, `web_ttfb`, `web_inp`), alongside Firebase Performance's own
|
|
6
|
+
* automatic page-load/network traces. No-ops if `firebaseAuth.performance`
|
|
7
|
+
* is disabled (`getFirebasePerformanceSync` never resolves an instance).
|
|
9
8
|
*/
|
|
10
9
|
export default function AutoFirebasePerformanceEvents(): null;
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { usePathname } from 'next/navigation';
|
|
3
2
|
import { useReportWebVitals } from 'next/web-vitals';
|
|
4
|
-
import { useEffect, useRef } from 'react';
|
|
5
3
|
import reportError from '../../../error_handling/report_error';
|
|
6
4
|
import { getFirebaseAuthClient, getFirebasePerformanceSync } from '../firebase_client';
|
|
7
5
|
/**
|
|
@@ -28,84 +26,18 @@ async function recordFirebaseTrace(name, durationMs, attributes, metrics) {
|
|
|
28
26
|
});
|
|
29
27
|
}
|
|
30
28
|
}
|
|
31
|
-
const SLOW_RESOURCE_THRESHOLD_MS = 1000;
|
|
32
29
|
/**
|
|
33
30
|
* Auto-rendered alongside `FirebaseAuthClientProvider` when
|
|
34
31
|
* `firebaseAuth.performance` isn't `false` — records Firebase Performance
|
|
35
32
|
* custom traces for Web Vitals metrics (`web_cls`, `web_fcp`, `web_fid`,
|
|
36
|
-
* `web_lcp`, `web_ttfb`, `web_inp`)
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* never resolves an instance).
|
|
33
|
+
* `web_lcp`, `web_ttfb`, `web_inp`), alongside Firebase Performance's own
|
|
34
|
+
* automatic page-load/network traces. No-ops if `firebaseAuth.performance`
|
|
35
|
+
* is disabled (`getFirebasePerformanceSync` never resolves an instance).
|
|
40
36
|
*/
|
|
41
37
|
export default function AutoFirebasePerformanceEvents() {
|
|
42
38
|
useReportWebVitals((metric) => {
|
|
43
39
|
const value = Math.round(metric.name === 'CLS' ? metric.value * 1000 : metric.value);
|
|
44
40
|
void recordFirebaseTrace(`web_${metric.name.toLowerCase()}`, value, { rating: metric.rating });
|
|
45
41
|
});
|
|
46
|
-
const path = usePathname();
|
|
47
|
-
const isFirstRoute = useRef(true);
|
|
48
|
-
const lastRouteChangeRef = useRef(Date.now());
|
|
49
|
-
const longTaskStats = useRef({ count: 0, totalDuration: 0 });
|
|
50
|
-
// Mount-only: registers a PerformanceObserver for long tasks (main-thread
|
|
51
|
-
// blocks >= 50ms). Not all browsers support the 'longtask' entry type, so
|
|
52
|
-
// this is a best-effort signal. The final route's tail of accumulated
|
|
53
|
-
// long tasks between the last route change and unmount is intentionally
|
|
54
|
-
// dropped rather than flushed here — this is a monitoring signal, not a
|
|
55
|
-
// billing metric, and an occasional dropped tail sample is acceptable.
|
|
56
|
-
useEffect(() => {
|
|
57
|
-
if (typeof PerformanceObserver === 'undefined' || !PerformanceObserver.supportedEntryTypes?.includes('longtask'))
|
|
58
|
-
return;
|
|
59
|
-
const observer = new PerformanceObserver((list) => {
|
|
60
|
-
for (const entry of list.getEntries()) {
|
|
61
|
-
longTaskStats.current.count += 1;
|
|
62
|
-
longTaskStats.current.totalDuration += entry.duration;
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
observer.observe({ type: 'longtask', buffered: true });
|
|
66
|
-
return () => observer.disconnect();
|
|
67
|
-
}, []);
|
|
68
|
-
// Mount-only: registers a PerformanceObserver for slow non-fetch/XHR
|
|
69
|
-
// resources (scripts, images, stylesheets, fonts, etc). Firebase
|
|
70
|
-
// Performance's own automatic network monitoring already instruments
|
|
71
|
-
// fetch/XHR, so those are skipped here to avoid duplicate signals.
|
|
72
|
-
// `buffered: true` also catches resources that loaded before this
|
|
73
|
-
// component mounted (it only mounts after `AuthUserProvider`).
|
|
74
|
-
useEffect(() => {
|
|
75
|
-
if (typeof PerformanceObserver === 'undefined' || !PerformanceObserver.supportedEntryTypes?.includes('resource'))
|
|
76
|
-
return;
|
|
77
|
-
const observer = new PerformanceObserver((list) => {
|
|
78
|
-
for (const entry of list.getEntries()) {
|
|
79
|
-
if (entry.initiatorType === 'fetch' || entry.initiatorType === 'xmlhttprequest')
|
|
80
|
-
continue;
|
|
81
|
-
if (entry.duration < SLOW_RESOURCE_THRESHOLD_MS)
|
|
82
|
-
continue;
|
|
83
|
-
void recordFirebaseTrace('slow_resource', entry.duration, {
|
|
84
|
-
initiator_type: entry.initiatorType,
|
|
85
|
-
resource: entry.name.slice(-100),
|
|
86
|
-
}, { transfer_size_bytes: Math.round(entry.transferSize ?? 0) });
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
observer.observe({ type: 'resource', buffered: true });
|
|
90
|
-
return () => observer.disconnect();
|
|
91
|
-
}, []);
|
|
92
|
-
useEffect(() => {
|
|
93
|
-
const now = Date.now();
|
|
94
|
-
const duration = now - lastRouteChangeRef.current;
|
|
95
|
-
lastRouteChangeRef.current = now;
|
|
96
|
-
if (isFirstRoute.current) {
|
|
97
|
-
isFirstRoute.current = false;
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
// Approximates navigation duration as time between path-change commits —
|
|
101
|
-
// App Router exposes no public "navigation start" event this package
|
|
102
|
-
// can hook into, so this is not a precise navigation timing.
|
|
103
|
-
void recordFirebaseTrace('route_change', duration, { path: path.slice(-100) });
|
|
104
|
-
const { count, totalDuration } = longTaskStats.current;
|
|
105
|
-
longTaskStats.current = { count: 0, totalDuration: 0 };
|
|
106
|
-
if (count > 0) {
|
|
107
|
-
void recordFirebaseTrace('route_long_tasks', totalDuration, { path: path.slice(-100) }, { long_task_count: count });
|
|
108
|
-
}
|
|
109
|
-
}, [path]);
|
|
110
42
|
return null;
|
|
111
43
|
}
|
|
@@ -160,6 +160,7 @@ export async function refreshIdToken(apiKey, refreshToken, options) {
|
|
|
160
160
|
method: 'POST',
|
|
161
161
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
162
162
|
body: `grant_type=refresh_token&refresh_token=${encodeURIComponent(refreshToken)}`,
|
|
163
|
+
cache: 'no-store',
|
|
163
164
|
});
|
|
164
165
|
if (!res.ok) {
|
|
165
166
|
if (res.status === 400) {
|
|
@@ -499,5 +500,7 @@ function buildRedirect(baseResponse, url) {
|
|
|
499
500
|
const redirectResponse = NextResponse.redirect(url);
|
|
500
501
|
baseResponse.cookies.getAll().forEach((cookie) => redirectResponse.cookies.set(cookie));
|
|
501
502
|
baseResponse.headers.forEach((value, key) => redirectResponse.headers.set(key, value));
|
|
503
|
+
// Explicitly prevent OpenNext/Cloudflare from caching these auth redirects.
|
|
504
|
+
redirectResponse.headers.set('Cache-Control', 'private, no-cache, no-store, max-age=0, must-revalidate');
|
|
502
505
|
return redirectResponse;
|
|
503
506
|
}
|