rwsdk 1.2.10 → 1.2.12
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/dist/runtime/client/navigation.js +15 -0
- package/dist/runtime/client/navigation.test.js +24 -2
- package/dist/runtime/lib/db/DOSqliteIntrospector.d.ts +8 -0
- package/dist/runtime/lib/db/DOSqliteIntrospector.js +129 -0
- package/dist/runtime/lib/db/DOWorkerDialect.d.ts +3 -2
- package/dist/runtime/lib/db/DOWorkerDialect.js +3 -2
- package/dist/runtime/lib/db/SqliteDurableObject.js +10 -1
- package/dist/vite/transformServerFunctions.mjs +22 -9
- package/dist/vite/transformServerFunctions.test.mjs +11 -0
- package/package.json +1 -1
|
@@ -34,6 +34,13 @@ export function validateClickEvent(event, target) {
|
|
|
34
34
|
}
|
|
35
35
|
let IS_CLIENT_NAVIGATION = false;
|
|
36
36
|
let scrollRestoration = null;
|
|
37
|
+
let currentPathKey = null;
|
|
38
|
+
function getLocationPathKey() {
|
|
39
|
+
return `${window.location.pathname ?? ""}${window.location.search ?? ""}`;
|
|
40
|
+
}
|
|
41
|
+
function getUrlPathKey(url) {
|
|
42
|
+
return `${url.pathname ?? ""}${url.search ?? ""}` || getLocationPathKey();
|
|
43
|
+
}
|
|
37
44
|
export async function navigate(href, options = { history: "push" }) {
|
|
38
45
|
if (!IS_CLIENT_NAVIGATION) {
|
|
39
46
|
window.location.href = href;
|
|
@@ -52,6 +59,7 @@ export async function navigate(href, options = { history: "push" }) {
|
|
|
52
59
|
else {
|
|
53
60
|
scrollRestoration?.replaceEntry(href, url, nextScrollPosition);
|
|
54
61
|
}
|
|
62
|
+
currentPathKey = getUrlPathKey(url);
|
|
55
63
|
if (scrollToTop) {
|
|
56
64
|
scrollRestoration?.setPendingScroll({
|
|
57
65
|
...nextScrollPosition,
|
|
@@ -109,6 +117,7 @@ export function initClientNavigation(opts = {}) {
|
|
|
109
117
|
IS_CLIENT_NAVIGATION = true;
|
|
110
118
|
scrollRestoration = createScrollRestoration();
|
|
111
119
|
scrollRestoration.initialize();
|
|
120
|
+
currentPathKey = getLocationPathKey();
|
|
112
121
|
document.addEventListener("click", async function handleClickEvent(event) {
|
|
113
122
|
if (!validateClickEvent(event, event.target)) {
|
|
114
123
|
return;
|
|
@@ -120,6 +129,12 @@ export function initClientNavigation(opts = {}) {
|
|
|
120
129
|
await navigate(href, { history: "push", onNavigate: opts.onNavigate });
|
|
121
130
|
}, true);
|
|
122
131
|
window.addEventListener("popstate", async function handlePopState() {
|
|
132
|
+
const nextPathKey = getLocationPathKey();
|
|
133
|
+
const isHashOnlyChange = nextPathKey === currentPathKey;
|
|
134
|
+
currentPathKey = nextPathKey;
|
|
135
|
+
if (isHashOnlyChange) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
123
138
|
scrollRestoration?.restorePopStateScroll();
|
|
124
139
|
await opts.onNavigate?.();
|
|
125
140
|
await globalThis.__rsc_callServer(null, null, "navigation");
|
|
@@ -103,7 +103,7 @@ describe("onNavigate callback (issue #1123 regression)", () => {
|
|
|
103
103
|
}),
|
|
104
104
|
});
|
|
105
105
|
vi.stubGlobal("window", {
|
|
106
|
-
location: { href: "http://localhost/" },
|
|
106
|
+
location: { href: "http://localhost/", pathname: "/", search: "" },
|
|
107
107
|
addEventListener: vi.fn((event, handler) => {
|
|
108
108
|
if (event === "popstate")
|
|
109
109
|
capturedPopstateHandler = handler;
|
|
@@ -167,6 +167,7 @@ describe("onNavigate callback (issue #1123 regression)", () => {
|
|
|
167
167
|
const onNavigate = vi.fn();
|
|
168
168
|
initClientNavigation({ onNavigate });
|
|
169
169
|
expect(capturedPopstateHandler).not.toBeNull();
|
|
170
|
+
window.location.pathname = "/about";
|
|
170
171
|
await capturedPopstateHandler();
|
|
171
172
|
expect(onNavigate).toHaveBeenCalled();
|
|
172
173
|
});
|
|
@@ -208,11 +209,13 @@ describe("initClientNavigation", () => {
|
|
|
208
209
|
let capturedScrollHandler = null;
|
|
209
210
|
let capturedPagehideHandler = null;
|
|
210
211
|
let capturedVisibilityChangeHandler = null;
|
|
212
|
+
let capturedPopstateHandler = null;
|
|
211
213
|
beforeEach(() => {
|
|
212
214
|
historyState = {};
|
|
213
215
|
capturedScrollHandler = null;
|
|
214
216
|
capturedPagehideHandler = null;
|
|
215
217
|
capturedVisibilityChangeHandler = null;
|
|
218
|
+
capturedPopstateHandler = null;
|
|
216
219
|
vi.clearAllMocks();
|
|
217
220
|
const mockHistory = {
|
|
218
221
|
scrollRestoration: "auto",
|
|
@@ -236,7 +239,7 @@ describe("initClientNavigation", () => {
|
|
|
236
239
|
}),
|
|
237
240
|
});
|
|
238
241
|
vi.stubGlobal("window", {
|
|
239
|
-
location: { href: "http://localhost/" },
|
|
242
|
+
location: { href: "http://localhost/", pathname: "/", search: "" },
|
|
240
243
|
addEventListener: vi.fn((event, handler) => {
|
|
241
244
|
if (event === "scroll") {
|
|
242
245
|
capturedScrollHandler = handler;
|
|
@@ -244,6 +247,9 @@ describe("initClientNavigation", () => {
|
|
|
244
247
|
if (event === "pagehide") {
|
|
245
248
|
capturedPagehideHandler = handler;
|
|
246
249
|
}
|
|
250
|
+
if (event === "popstate") {
|
|
251
|
+
capturedPopstateHandler = handler;
|
|
252
|
+
}
|
|
247
253
|
}),
|
|
248
254
|
history: mockHistory,
|
|
249
255
|
fetch: vi.fn(),
|
|
@@ -279,6 +285,22 @@ describe("initClientNavigation", () => {
|
|
|
279
285
|
initClientNavigation();
|
|
280
286
|
expect(history.scrollRestoration).toBe("manual");
|
|
281
287
|
});
|
|
288
|
+
it("ignores hash-only popstate events so anchor links keep their native scroll", async () => {
|
|
289
|
+
const onNavigate = vi.fn();
|
|
290
|
+
globalThis.__rsc_callServer = vi.fn().mockResolvedValue(undefined);
|
|
291
|
+
const { onHydrated } = initClientNavigation({ onNavigate });
|
|
292
|
+
expect(capturedPopstateHandler).not.toBeNull();
|
|
293
|
+
window.location.hash =
|
|
294
|
+
"#heading";
|
|
295
|
+
window.location.href =
|
|
296
|
+
"http://localhost/#heading";
|
|
297
|
+
window.scrollY = 500;
|
|
298
|
+
await capturedPopstateHandler();
|
|
299
|
+
onHydrated();
|
|
300
|
+
expect(onNavigate).not.toHaveBeenCalled();
|
|
301
|
+
expect(globalThis.__rsc_callServer).not.toHaveBeenCalled();
|
|
302
|
+
expect(window.scrollTo).not.toHaveBeenCalled();
|
|
303
|
+
});
|
|
282
304
|
it("does not write to history state on scroll", () => {
|
|
283
305
|
initClientNavigation();
|
|
284
306
|
expect(capturedScrollHandler).not.toBeNull();
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type DatabaseMetadata, type DatabaseMetadataOptions, type Kysely, type SchemaMetadata, type TableMetadata } from "kysely";
|
|
2
|
+
export declare class DOSqliteIntrospector {
|
|
3
|
+
#private;
|
|
4
|
+
constructor(db: Kysely<any>);
|
|
5
|
+
getSchemas(): Promise<SchemaMetadata[]>;
|
|
6
|
+
getTables(options?: DatabaseMetadataOptions): Promise<TableMetadata[]>;
|
|
7
|
+
getMetadata(options: DatabaseMetadataOptions): Promise<DatabaseMetadata>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// NOTE(justinvdm, 9 Jun 2026): This file copies Kysely's SqliteIntrospector.
|
|
2
|
+
// Before trying to simplify it, read this comment.
|
|
3
|
+
//
|
|
4
|
+
// Problem: Cloudflare's DO SQLite adds internal tables (_cf_KV, _cf_METADATA)
|
|
5
|
+
// when storage.put/setAlarm is used. Kysely's SqliteIntrospector discovers
|
|
6
|
+
// these via sqlite_master, then runs PRAGMA table_info on each. Cloudflare's
|
|
7
|
+
// authorizer rejects PRAGMA on _cf_* tables with SQLITE_AUTH, breaking
|
|
8
|
+
// migrations and rendering the DO unusable.
|
|
9
|
+
//
|
|
10
|
+
// Why we copied instead of composed:
|
|
11
|
+
// - Kysely's SqliteIntrospector uses JS private fields (#db, #tablesQuery,
|
|
12
|
+
// #getTableMetadata). Private fields are truly private — subclasses cannot
|
|
13
|
+
// override them, and wrappers cannot intercept internal calls.
|
|
14
|
+
// - We considered a lighter approach: query sqlite_master with Kysely's
|
|
15
|
+
// builder, then run PRAGMA table_info per-table. This is lighter but
|
|
16
|
+
// requires inlining the table name into raw SQL. Even with escaping,
|
|
17
|
+
// any injection risk is unacceptable. The CTE approach below uses
|
|
18
|
+
// `pragma_table_info(tl.name)` where `tl.name` is a column reference,
|
|
19
|
+
// not an inlined value — zero injection surface.
|
|
20
|
+
// - We also considered Kysely plugins (AST rewriting, SQL string rewriting)
|
|
21
|
+
// but these are fragile: they depend on Kysely's internal AST shape and
|
|
22
|
+
// SQL formatting, both of which can change between releases.
|
|
23
|
+
//
|
|
24
|
+
// This is a copy of Kysely's SqliteIntrospector (MIT licensed) with one
|
|
25
|
+
// change: `.where('name', 'not like', '_cf_%')` to exclude Cloudflare tables.
|
|
26
|
+
//
|
|
27
|
+
// See: https://github.com/redwoodjs/sdk/issues/1219
|
|
28
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
29
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
30
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
31
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
32
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
33
|
+
};
|
|
34
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
35
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
36
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
37
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
38
|
+
};
|
|
39
|
+
var _DOSqliteIntrospector_instances, _DOSqliteIntrospector_db, _DOSqliteIntrospector_tablesQuery, _DOSqliteIntrospector_getTableMetadata;
|
|
40
|
+
import { DEFAULT_MIGRATION_LOCK_TABLE, DEFAULT_MIGRATION_TABLE, sql, } from "kysely";
|
|
41
|
+
export class DOSqliteIntrospector {
|
|
42
|
+
constructor(db) {
|
|
43
|
+
_DOSqliteIntrospector_instances.add(this);
|
|
44
|
+
_DOSqliteIntrospector_db.set(this, void 0);
|
|
45
|
+
__classPrivateFieldSet(this, _DOSqliteIntrospector_db, db, "f");
|
|
46
|
+
}
|
|
47
|
+
async getSchemas() {
|
|
48
|
+
// Sqlite doesn't support schemas.
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
async getTables(options = { withInternalKyselyTables: false }) {
|
|
52
|
+
return await __classPrivateFieldGet(this, _DOSqliteIntrospector_instances, "m", _DOSqliteIntrospector_getTableMetadata).call(this, options);
|
|
53
|
+
}
|
|
54
|
+
async getMetadata(options) {
|
|
55
|
+
return {
|
|
56
|
+
tables: await this.getTables(options),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
_DOSqliteIntrospector_db = new WeakMap(), _DOSqliteIntrospector_instances = new WeakSet(), _DOSqliteIntrospector_tablesQuery = function _DOSqliteIntrospector_tablesQuery(qb, options) {
|
|
61
|
+
let tablesQuery = qb
|
|
62
|
+
.selectFrom("sqlite_master")
|
|
63
|
+
.where("type", "in", ["table", "view"])
|
|
64
|
+
.where("name", "not like", "sqlite_%")
|
|
65
|
+
// context(justinvdm, 9 Jun 2026): Exclude Cloudflare internal tables.
|
|
66
|
+
// These are added by the DO runtime and cannot be introspected.
|
|
67
|
+
.where("name", "not like", "_cf_%")
|
|
68
|
+
.select(["name", "sql", "type"])
|
|
69
|
+
.orderBy("name");
|
|
70
|
+
if (!options.withInternalKyselyTables) {
|
|
71
|
+
tablesQuery = tablesQuery
|
|
72
|
+
.where("name", "!=", DEFAULT_MIGRATION_TABLE)
|
|
73
|
+
.where("name", "!=", DEFAULT_MIGRATION_LOCK_TABLE);
|
|
74
|
+
}
|
|
75
|
+
return tablesQuery;
|
|
76
|
+
}, _DOSqliteIntrospector_getTableMetadata = async function _DOSqliteIntrospector_getTableMetadata(options) {
|
|
77
|
+
const tablesResult = await __classPrivateFieldGet(this, _DOSqliteIntrospector_instances, "m", _DOSqliteIntrospector_tablesQuery).call(this, __classPrivateFieldGet(this, _DOSqliteIntrospector_db, "f"), options).execute();
|
|
78
|
+
const tableMetadata = await __classPrivateFieldGet(this, _DOSqliteIntrospector_db, "f")
|
|
79
|
+
.with("table_list", (qb) => __classPrivateFieldGet(this, _DOSqliteIntrospector_instances, "m", _DOSqliteIntrospector_tablesQuery).call(this, qb, options))
|
|
80
|
+
.selectFrom([
|
|
81
|
+
"table_list as tl",
|
|
82
|
+
sql `pragma_table_info(tl.name)`.as("p"),
|
|
83
|
+
])
|
|
84
|
+
.select([
|
|
85
|
+
"tl.name as table",
|
|
86
|
+
"p.cid",
|
|
87
|
+
"p.name",
|
|
88
|
+
"p.type",
|
|
89
|
+
"p.notnull",
|
|
90
|
+
"p.dflt_value",
|
|
91
|
+
"p.pk",
|
|
92
|
+
])
|
|
93
|
+
.orderBy("tl.name")
|
|
94
|
+
.orderBy("p.cid")
|
|
95
|
+
.execute();
|
|
96
|
+
const columnsByTable = {};
|
|
97
|
+
for (const row of tableMetadata) {
|
|
98
|
+
columnsByTable[row.table] ??= [];
|
|
99
|
+
columnsByTable[row.table].push(row);
|
|
100
|
+
}
|
|
101
|
+
return tablesResult.map(({ name, sql: tableSql, type }) => {
|
|
102
|
+
let autoIncrementCol = tableSql
|
|
103
|
+
?.split(/[\(\),]/)
|
|
104
|
+
?.find((it) => it.toLowerCase().includes("autoincrement"))
|
|
105
|
+
?.trimStart()
|
|
106
|
+
?.split(/\s+/)?.[0]
|
|
107
|
+
?.replace(/["`]/g, "");
|
|
108
|
+
const columns = columnsByTable[name] ?? [];
|
|
109
|
+
if (!autoIncrementCol) {
|
|
110
|
+
const pkCols = columns.filter((r) => r.pk > 0);
|
|
111
|
+
if (pkCols.length === 1 &&
|
|
112
|
+
pkCols[0].type.toLowerCase() === "integer") {
|
|
113
|
+
autoIncrementCol = pkCols[0].name;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
name: name,
|
|
118
|
+
isView: type === "view",
|
|
119
|
+
columns: columns.map((col) => ({
|
|
120
|
+
name: col.name,
|
|
121
|
+
dataType: col.type,
|
|
122
|
+
isNullable: !col.notnull,
|
|
123
|
+
isAutoIncrementing: col.name === autoIncrementCol,
|
|
124
|
+
hasDefaultValue: col.dflt_value != null,
|
|
125
|
+
comment: undefined,
|
|
126
|
+
})),
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { DatabaseConnection, Driver, QueryResult, SqliteAdapter,
|
|
1
|
+
import { DatabaseConnection, Driver, QueryResult, SqliteAdapter, SqliteQueryCompiler } from "kysely";
|
|
2
|
+
import { DOSqliteIntrospector } from "./DOSqliteIntrospector.js";
|
|
2
3
|
type DOWorkerDialectConfig = {
|
|
3
4
|
kyselyExecuteQuery: (compiledQuery: {
|
|
4
5
|
sql: string;
|
|
@@ -11,7 +12,7 @@ export declare class DOWorkerDialect {
|
|
|
11
12
|
createAdapter(): SqliteAdapter;
|
|
12
13
|
createDriver(): DOWorkerDriver;
|
|
13
14
|
createQueryCompiler(): SqliteQueryCompiler;
|
|
14
|
-
createIntrospector(db: any):
|
|
15
|
+
createIntrospector(db: any): DOSqliteIntrospector;
|
|
15
16
|
}
|
|
16
17
|
declare class DOWorkerDriver implements Driver {
|
|
17
18
|
config: DOWorkerDialectConfig;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { SqliteAdapter,
|
|
1
|
+
import { SqliteAdapter, SqliteQueryCompiler, } from "kysely";
|
|
2
2
|
import debug from "../debug";
|
|
3
|
+
import { DOSqliteIntrospector } from "./DOSqliteIntrospector.js";
|
|
3
4
|
const log = debug("sdk:db:do-worker-dialect");
|
|
4
5
|
export class DOWorkerDialect {
|
|
5
6
|
constructor(config) {
|
|
@@ -15,7 +16,7 @@ export class DOWorkerDialect {
|
|
|
15
16
|
return new SqliteQueryCompiler();
|
|
16
17
|
}
|
|
17
18
|
createIntrospector(db) {
|
|
18
|
-
return new
|
|
19
|
+
return new DOSqliteIntrospector(db);
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
class DOWorkerDriver {
|
|
@@ -2,7 +2,16 @@ import { DurableObject } from "cloudflare:workers";
|
|
|
2
2
|
import { DODialect } from "kysely-do";
|
|
3
3
|
import { Kysely, ParseJSONResultsPlugin, } from "kysely";
|
|
4
4
|
import debug from "../debug.js";
|
|
5
|
+
import { DOSqliteIntrospector } from "./DOSqliteIntrospector.js";
|
|
5
6
|
import { createMigrator } from "./index.js";
|
|
7
|
+
// context(justinvdm, 9 Jun 2026): Wrapper around kysely-do's DODialect that
|
|
8
|
+
// uses our custom introspector to avoid SQLITE_AUTH on Cloudflare internal
|
|
9
|
+
// _cf_* tables. See DOSqliteIntrospector for details.
|
|
10
|
+
class DODialectWithCustomIntrospector extends DODialect {
|
|
11
|
+
createIntrospector(db) {
|
|
12
|
+
return new DOSqliteIntrospector(db);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
6
15
|
const log = debug("sdk:do-db");
|
|
7
16
|
// Base class for Durable Objects that need Kysely database access
|
|
8
17
|
export class SqliteDurableObject extends DurableObject {
|
|
@@ -12,7 +21,7 @@ export class SqliteDurableObject extends DurableObject {
|
|
|
12
21
|
this.migrations = migrations;
|
|
13
22
|
this.migrationTableName = migrationTableName;
|
|
14
23
|
this.kysely = new Kysely({
|
|
15
|
-
dialect: new
|
|
24
|
+
dialect: new DODialectWithCustomIntrospector({ ctx }),
|
|
16
25
|
plugins: plugins,
|
|
17
26
|
});
|
|
18
27
|
}
|
|
@@ -94,10 +94,8 @@ export const transformServerFunctions = (code, normalizedId, environment, server
|
|
|
94
94
|
process.env.VERBOSE &&
|
|
95
95
|
log(`Transforming for ${environment} environment: normalizedId=%s`, normalizedId);
|
|
96
96
|
const exportInfo = findExportInfo(code, normalizedId);
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
...exportInfo.reExports.map((r) => r.localName),
|
|
100
|
-
]);
|
|
97
|
+
const reExportNames = new Set(exportInfo.reExports.map((r) => r.localName));
|
|
98
|
+
const allExports = new Set(Array.from(exportInfo.localFunctions).filter((name) => !reExportNames.has(name)));
|
|
101
99
|
// Check for default function exports that should also be named exports
|
|
102
100
|
const defaultFunctionName = findDefaultFunctionName(code, normalizedId);
|
|
103
101
|
if (defaultFunctionName) {
|
|
@@ -105,11 +103,26 @@ export const transformServerFunctions = (code, normalizedId, environment, server
|
|
|
105
103
|
}
|
|
106
104
|
// Generate completely new code for SSR
|
|
107
105
|
const s = new MagicString("");
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
const hasDefExport = hasDefaultExport(code, normalizedId);
|
|
107
|
+
if (allExports.size > 0 || hasDefExport) {
|
|
108
|
+
if (environment === "ssr") {
|
|
109
|
+
s.append('import { createServerReference } from "rwsdk/__ssr";\n\n');
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
s.append('import { createServerReference } from "rwsdk/client";\n\n');
|
|
113
|
+
}
|
|
110
114
|
}
|
|
111
|
-
|
|
112
|
-
|
|
115
|
+
for (const reExport of exportInfo.reExports) {
|
|
116
|
+
const reExportStatement = reExport.originalName === "default"
|
|
117
|
+
? `export { default as ${reExport.localName} } from ${JSON.stringify(reExport.moduleSpecifier)};\n`
|
|
118
|
+
: reExport.originalName === reExport.localName
|
|
119
|
+
? `export { ${reExport.originalName} } from ${JSON.stringify(reExport.moduleSpecifier)};\n`
|
|
120
|
+
: `export { ${reExport.originalName} as ${reExport.localName} } from ${JSON.stringify(reExport.moduleSpecifier)};\n`;
|
|
121
|
+
s.append(reExportStatement);
|
|
122
|
+
log(`Preserved ${environment} re-export for function: %s (original: %s) from %s in normalizedId=%s`, reExport.localName, reExport.originalName, reExport.moduleSpecifier, normalizedId);
|
|
123
|
+
}
|
|
124
|
+
if (exportInfo.reExports.length > 0 && allExports.size > 0) {
|
|
125
|
+
s.append("\n");
|
|
113
126
|
}
|
|
114
127
|
const ext = path.extname(normalizedId).toLowerCase();
|
|
115
128
|
const lang = ext === ".tsx" || ext === ".jsx" ? Lang.Tsx : SgLang.TypeScript;
|
|
@@ -151,7 +164,7 @@ export const transformServerFunctions = (code, normalizedId, environment, server
|
|
|
151
164
|
}
|
|
152
165
|
}
|
|
153
166
|
// Check for default export in the actual module (not re-exports)
|
|
154
|
-
if (
|
|
167
|
+
if (hasDefExport) {
|
|
155
168
|
let method;
|
|
156
169
|
let source = "action";
|
|
157
170
|
const patterns = [
|
|
@@ -169,6 +169,17 @@ export const getProject = serverQuery([
|
|
|
169
169
|
SERVER_QUERY_ARRAY_POST_CODE,
|
|
170
170
|
};
|
|
171
171
|
describe("TRANSFORMS", () => {
|
|
172
|
+
it("preserves client re-exports so serverQuery metadata comes from the defining module", () => {
|
|
173
|
+
const barrelResult = transformServerFunctions(`
|
|
174
|
+
"use server";
|
|
175
|
+
|
|
176
|
+
export { getProject } from "./queries";
|
|
177
|
+
`, "/actions.ts", "client", new Set());
|
|
178
|
+
expect(barrelResult?.code).toContain(`export { getProject } from "./queries";`);
|
|
179
|
+
expect(barrelResult?.code).not.toContain(`createServerReference("/actions.ts", "getProject")`);
|
|
180
|
+
const queryResult = transformServerFunctions(SERVER_QUERY_GET_CODE, "/queries.ts", "client", new Set());
|
|
181
|
+
expect(queryResult?.code).toContain(`createServerReference("/queries.ts", "getProject", "GET", "query")`);
|
|
182
|
+
});
|
|
172
183
|
for (const [key, CODE] of Object.entries(TEST_CASES)) {
|
|
173
184
|
describe(key, () => {
|
|
174
185
|
it(`CLIENT`, () => {
|