cloudflare-next-intl 0.8.16 → 0.8.17
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 +9 -30
- package/dist/src/db/context.js +38 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -584,17 +584,9 @@ below for the `.transaction()` API that does.
|
|
|
584
584
|
|
|
585
585
|
Call `.transaction(...)` on the handle `withUserDb`/`withPublicDb` hand your
|
|
586
586
|
callback whenever a write needs more than one statement to succeed or fail
|
|
587
|
-
together — same method name in both transport modes.
|
|
587
|
+
together — same method name and same signature in both transport modes.
|
|
588
588
|
|
|
589
|
-
|
|
590
|
-
db.transaction(async (tx) => { await tx.insert(...); await tx.update(...); })`,
|
|
591
|
-
and a later statement may use an earlier one's result, exactly like plain
|
|
592
|
-
Drizzle usage.
|
|
593
|
-
|
|
594
|
-
In Supabase mode there is no shared session for `.transaction()` to open, so
|
|
595
|
-
its callback instead **builds** queries rather than executing them — call
|
|
596
|
-
`.toSQL()` on each Drizzle query and return the array, rather than `await`ing
|
|
597
|
-
the query directly:
|
|
589
|
+
To achieve mode-transparency between Postgres/connection-string mode and Supabase/REST mode, the callback **builds** queries rather than executing them directly: call `.toSQL()` on each Drizzle query and return the array, rather than `await`ing the query directly.
|
|
598
590
|
|
|
599
591
|
```typescript
|
|
600
592
|
import { withUserDb } from "cloudflare-next-intl/db";
|
|
@@ -608,26 +600,13 @@ const [invitationResult, grantResult] = await withUserDb((db) =>
|
|
|
608
600
|
);
|
|
609
601
|
```
|
|
610
602
|
|
|
611
|
-
Every query in the array is
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
whenever `cfni_exec` is: there is no separate config flag, and
|
|
619
|
-
`db.supabase.rawSql: false` turns off both.
|
|
620
|
-
|
|
621
|
-
Each result is the same `{ rows, rowCount }` shape a single `cfni_exec` call
|
|
622
|
-
returns — decode rows the same way you would from `db.execute(sql\`...\`)`.
|
|
623
|
-
|
|
624
|
-
`await`ing a query directly inside the Supabase-mode `.transaction()`
|
|
625
|
-
callback (instead of calling `.toSQL()`) throws immediately, naming the
|
|
626
|
-
mistake, rather than hanging or silently running that one statement outside
|
|
627
|
-
the batch with no atomicity. This also means, unlike connection-string mode,
|
|
628
|
-
a later statement in a Supabase-mode `.transaction()` callback cannot read an
|
|
629
|
-
earlier one's result — build every statement from arguments/closures you
|
|
630
|
-
already have.
|
|
603
|
+
Every query in the array is executed sequentially in a single transaction blocks/batch:
|
|
604
|
+
- In **Supabase mode**, they are sent in one round-trip to `cfni_exec_batch` which runs them inside a single `plpgsql` block.
|
|
605
|
+
- In **connection-string mode**, they are run sequentially over the Postgres client inside a standard Drizzle transaction.
|
|
606
|
+
|
|
607
|
+
Either way, a failure on any statement rolls back every statement that ran before it in the transaction.
|
|
608
|
+
|
|
609
|
+
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 inside `.transaction()` throws immediately to prevent running queries outside the transaction boundary.
|
|
631
610
|
|
|
632
611
|
#### Supabase mode and REST translation
|
|
633
612
|
|
package/dist/src/db/context.js
CHANGED
|
@@ -6,6 +6,7 @@ import resolveSupabaseEndpoint from './supabase_config';
|
|
|
6
6
|
import createSupabaseTransport from './supabase_transport';
|
|
7
7
|
import resolveAccessToken from './access_token';
|
|
8
8
|
import runTransactionBatch from './transaction_batch';
|
|
9
|
+
import inlineParams from './inline_params';
|
|
9
10
|
const DEFAULT_ROLE = 'authenticated';
|
|
10
11
|
/**
|
|
11
12
|
* Resolves the user id for `withUserDb`, trying, in order: the explicit `uid`
|
|
@@ -51,6 +52,36 @@ async function supabaseDb(supabase, bearerToken) {
|
|
|
51
52
|
},
|
|
52
53
|
});
|
|
53
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Wraps a live Drizzle postgres transaction handle with a `.transaction()`
|
|
57
|
+
* override that mirrors the Supabase-mode batch API: `build` receives a
|
|
58
|
+
* build-only proxy, must return an array of `.toSQL()` objects (same shape),
|
|
59
|
+
* and this function executes each one sequentially on the real pg session,
|
|
60
|
+
* collecting `ExecResult[]`. Both modes therefore share the identical
|
|
61
|
+
* callback shape — callers never need to detect the transport themselves.
|
|
62
|
+
*/
|
|
63
|
+
async function postgresDb(drizzleHandle, rawClient) {
|
|
64
|
+
return Object.assign(drizzleHandle, {
|
|
65
|
+
async transaction(build) {
|
|
66
|
+
return runPostgresTransaction(rawClient, build);
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Postgres-mode equivalent of `runTransaction`: calls `build` with a
|
|
72
|
+
* build-only handle, then executes each returned query on the raw pg client
|
|
73
|
+
* via an inline-parameterised `query()` call and returns `ExecResult[]`.
|
|
74
|
+
*/
|
|
75
|
+
async function runPostgresTransaction(rawClient, build) {
|
|
76
|
+
const queries = await build(buildOnlyDb());
|
|
77
|
+
const results = [];
|
|
78
|
+
for (const q of queries) {
|
|
79
|
+
const statement = inlineParams(q.sql, q.params);
|
|
80
|
+
const res = await rawClient.query(statement);
|
|
81
|
+
results.push({ rows: res.rows ?? [], rowCount: res.rowCount ?? null });
|
|
82
|
+
}
|
|
83
|
+
return results;
|
|
84
|
+
}
|
|
54
85
|
/**
|
|
55
86
|
* Builds a Drizzle handle with no working transport, for Supabase-mode
|
|
56
87
|
* `db.transaction(...)` callbacks. Query builders' `.toSQL()` never touches
|
|
@@ -101,7 +132,9 @@ export async function withPublicDb(fn) {
|
|
|
101
132
|
const client = await connectToPostgres(config, resolved.connectionString);
|
|
102
133
|
try {
|
|
103
134
|
const { drizzle } = await import('drizzle-orm/node-postgres');
|
|
104
|
-
|
|
135
|
+
const drizzleHandle = drizzle(client);
|
|
136
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
137
|
+
return await fn(await postgresDb(drizzleHandle, client));
|
|
105
138
|
}
|
|
106
139
|
finally {
|
|
107
140
|
disconnectPostgres(config);
|
|
@@ -158,7 +191,10 @@ export async function withUserDb(fn, uid) {
|
|
|
158
191
|
return await drizzle(client).transaction(async (transaction) => {
|
|
159
192
|
await transaction.execute(sql `select set_config('request.jwt.claims', ${JSON.stringify({ sub: userId })}, true)`);
|
|
160
193
|
await transaction.execute(sql `set local role ${sql.raw(role)}`);
|
|
161
|
-
|
|
194
|
+
// The transaction handle's session.client is the live pg socket — use it directly.
|
|
195
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
196
|
+
const txClient = transaction.session?.client ?? client;
|
|
197
|
+
return fn(await postgresDb(transaction, txClient));
|
|
162
198
|
});
|
|
163
199
|
}
|
|
164
200
|
finally {
|