@timber-js/app 0.2.0-alpha.94 → 0.2.0-alpha.95
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.
|
@@ -44,15 +44,24 @@ export declare function isActionRequest(req: Request): boolean;
|
|
|
44
44
|
/**
|
|
45
45
|
* Signal from handleFormAction to re-render the page with flash data instead of redirecting.
|
|
46
46
|
*
|
|
47
|
-
* Carries
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
47
|
+
* Carries two cookie-related snapshots taken before the action's ALS scope
|
|
48
|
+
* exits, so the rerender pipeline (which establishes its own fresh cookie
|
|
49
|
+
* jar from the original request) doesn't lose the action's mutations:
|
|
50
|
+
*
|
|
51
|
+
* - `setCookieHeaders`: serialized Set-Cookie headers to append to the
|
|
52
|
+
* final HTML response. Without this, cookies set inside the action are
|
|
53
|
+
* silently dropped from the response. See TIM-836 (LOCAL-740).
|
|
54
|
+
*
|
|
55
|
+
* - `cookieHeader`: the post-action read-your-own-writes view of the
|
|
56
|
+
* cookie jar, serialized as a `Cookie:` request header. The rerender
|
|
57
|
+
* pipeline uses this to clone the request before re-rendering, so the
|
|
58
|
+
* page server components see the action's cookie writes immediately
|
|
59
|
+
* instead of the stale pre-action state. See TIM-837.
|
|
52
60
|
*/
|
|
53
61
|
export interface FormRerender {
|
|
54
62
|
rerender: FormFlashData;
|
|
55
63
|
setCookieHeaders: string[];
|
|
64
|
+
cookieHeader: string;
|
|
56
65
|
}
|
|
57
66
|
/**
|
|
58
67
|
* Handle a server action request.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action-handler.d.ts","sourceRoot":"","sources":["../../src/server/action-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AASH,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAiB,KAAK,kBAAkB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"action-handler.d.ts","sourceRoot":"","sources":["../../src/server/action-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AASH,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAiB,KAAK,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAQtE,OAAO,EAAwC,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAE/F,OAAO,EAIL,KAAK,qBAAqB,EAC3B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAMrD,4CAA4C;AAC5C,MAAM,WAAW,oBAAoB;IACnC,0BAA0B;IAC1B,IAAI,EAAE,UAAU,CAAC;IACjB,kEAAkE;IAClE,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,gDAAgD;IAChD,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B;;;;OAIG;IACH,eAAe,CAAC,EAAE,qBAAqB,CAAC;CACzC;AAQD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAarD;AAuBD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,aAAa,CAAC;IACxB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,QAAQ,GAAG,YAAY,GAAG,IAAI,CAAC,CAmEzC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/index.ts"],"names":[],"mappings":"AAuBA,OAAO,uCAAuC,CAAC;AA0C/C,OAAO,EAKL,KAAK,mBAAmB,EACzB,MAAM,cAAc,CAAC;AAoCtB;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,mBAAmB,EAAE,KAAK,IAAI,GACtF,IAAI,CAEN;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI,CAE5E;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/index.ts"],"names":[],"mappings":"AAuBA,OAAO,uCAAuC,CAAC;AA0C/C,OAAO,EAKL,KAAK,mBAAmB,EACzB,MAAM,cAAc,CAAC;AAoCtB;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,mBAAmB,EAAE,KAAK,IAAI,GACtF,IAAI,CAEN;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI,CAE5E;AAklBD,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAInE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;8BAzUrC,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;AA2UhD,wBAAiE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timber-js/app",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.95",
|
|
4
4
|
"description": "Vite-native React framework built for Servers and Serverless Platforms — correct HTTP semantics, real status codes, pages that work without JavaScript",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare-workers",
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
runWithRequestContext,
|
|
27
27
|
setMutableCookieContext,
|
|
28
28
|
getSetCookieHeaders,
|
|
29
|
+
getCookiesForSsr,
|
|
29
30
|
} from './request-context.js';
|
|
30
31
|
import { handleActionError } from './action-client.js';
|
|
31
32
|
import { enforceBodyLimits, enforceFieldLimit, type BodyLimitsConfig } from './body-limits.js';
|
|
@@ -89,18 +90,46 @@ export function isActionRequest(req: Request): boolean {
|
|
|
89
90
|
|
|
90
91
|
// ─── Handler ──────────────────────────────────────────────────────────────
|
|
91
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Serialize a `Map<string, string>` of cookie name → value as a `Cookie:`
|
|
95
|
+
* request header value. Format: `name1=value1; name2=value2`. Matches the
|
|
96
|
+
* format `parseCookieHeader` in `request-context.ts` reads with, so the
|
|
97
|
+
* rerender pipeline can parse it back into the same RYW state.
|
|
98
|
+
*
|
|
99
|
+
* Used to thread the post-action cookie state from the action's ALS scope
|
|
100
|
+
* into the rerender pipeline's fresh ALS scope. Cookies marked for deletion
|
|
101
|
+
* are already absent from `getCookiesForSsr()`'s map, so they naturally drop
|
|
102
|
+
* out of the synthesized header. See TIM-837.
|
|
103
|
+
*/
|
|
104
|
+
function serializeCookieMapAsHeader(cookies: Map<string, string>): string {
|
|
105
|
+
const parts: string[] = [];
|
|
106
|
+
for (const [name, value] of cookies) {
|
|
107
|
+
parts.push(`${name}=${value}`);
|
|
108
|
+
}
|
|
109
|
+
return parts.join('; ');
|
|
110
|
+
}
|
|
111
|
+
|
|
92
112
|
/**
|
|
93
113
|
* Signal from handleFormAction to re-render the page with flash data instead of redirecting.
|
|
94
114
|
*
|
|
95
|
-
* Carries
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
115
|
+
* Carries two cookie-related snapshots taken before the action's ALS scope
|
|
116
|
+
* exits, so the rerender pipeline (which establishes its own fresh cookie
|
|
117
|
+
* jar from the original request) doesn't lose the action's mutations:
|
|
118
|
+
*
|
|
119
|
+
* - `setCookieHeaders`: serialized Set-Cookie headers to append to the
|
|
120
|
+
* final HTML response. Without this, cookies set inside the action are
|
|
121
|
+
* silently dropped from the response. See TIM-836 (LOCAL-740).
|
|
122
|
+
*
|
|
123
|
+
* - `cookieHeader`: the post-action read-your-own-writes view of the
|
|
124
|
+
* cookie jar, serialized as a `Cookie:` request header. The rerender
|
|
125
|
+
* pipeline uses this to clone the request before re-rendering, so the
|
|
126
|
+
* page server components see the action's cookie writes immediately
|
|
127
|
+
* instead of the stale pre-action state. See TIM-837.
|
|
100
128
|
*/
|
|
101
129
|
export interface FormRerender {
|
|
102
130
|
rerender: FormFlashData;
|
|
103
131
|
setCookieHeaders: string[];
|
|
132
|
+
cookieHeader: string;
|
|
104
133
|
}
|
|
105
134
|
|
|
106
135
|
/**
|
|
@@ -173,6 +202,9 @@ export async function handleActionRequest(
|
|
|
173
202
|
}
|
|
174
203
|
} else if (result && 'rerender' in result) {
|
|
175
204
|
result.setCookieHeaders = getSetCookieHeaders();
|
|
205
|
+
// Snapshot the post-action RYW cookie state so the rerender pipeline
|
|
206
|
+
// can re-parse it into a fresh ALS scope. See TIM-837.
|
|
207
|
+
result.cookieHeader = serializeCookieMapAsHeader(getCookiesForSsr());
|
|
176
208
|
}
|
|
177
209
|
return result;
|
|
178
210
|
});
|
|
@@ -359,6 +391,7 @@ async function handleFormAction(
|
|
|
359
391
|
},
|
|
360
392
|
// Filled in by handleActionRequest before the ALS scope exits.
|
|
361
393
|
setCookieHeaders: [],
|
|
394
|
+
cookieHeader: '',
|
|
362
395
|
};
|
|
363
396
|
}
|
|
364
397
|
|
|
@@ -388,6 +421,7 @@ async function handleFormAction(
|
|
|
388
421
|
);
|
|
389
422
|
}
|
|
390
423
|
|
|
391
|
-
// setCookieHeaders
|
|
392
|
-
|
|
424
|
+
// setCookieHeaders + cookieHeader are filled in by handleActionRequest
|
|
425
|
+
// before the ALS scope exits.
|
|
426
|
+
return { rerender: actionResult, setCookieHeaders: [], cookieHeader: '' };
|
|
393
427
|
}
|
|
@@ -446,11 +446,33 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
|
|
|
446
446
|
// Re-render the page with the action result as flash data.
|
|
447
447
|
// Server components read it via getFormFlash() and pass it to
|
|
448
448
|
// client form components as the initial useActionState value.
|
|
449
|
-
|
|
449
|
+
//
|
|
450
|
+
// Build a synthetic GET request for the rerender pipeline:
|
|
451
|
+
// - Same URL (so route matching lands on the same page)
|
|
452
|
+
// - Cookie header replaced with the post-action RYW snapshot
|
|
453
|
+
// so server components see the action's writes (TIM-837)
|
|
454
|
+
// - Method GET because the rerender is conceptually a page
|
|
455
|
+
// render, not a re-POST. The pipeline doesn't branch on
|
|
456
|
+
// method for page rendering, and constructing a POST without
|
|
457
|
+
// a body is awkward across Request implementations.
|
|
458
|
+
const rerenderHeaders = new Headers(req.headers);
|
|
459
|
+
if (formRerender.cookieHeader) {
|
|
460
|
+
rerenderHeaders.set('cookie', formRerender.cookieHeader);
|
|
461
|
+
} else {
|
|
462
|
+
rerenderHeaders.delete('cookie');
|
|
463
|
+
}
|
|
464
|
+
const rerenderReq = new Request(req.url, {
|
|
465
|
+
method: 'GET',
|
|
466
|
+
headers: rerenderHeaders,
|
|
467
|
+
});
|
|
468
|
+
const response = await runWithFormFlash(formRerender.rerender, () =>
|
|
469
|
+
pipeline(rerenderReq)
|
|
470
|
+
);
|
|
450
471
|
// Apply Set-Cookie headers snapshotted from the action's ALS scope.
|
|
451
472
|
// The pipeline above runs in its own request context with a fresh
|
|
452
473
|
// cookie jar, so cookies set inside the action would otherwise be
|
|
453
|
-
// silently dropped on the no-JS rerender path. See
|
|
474
|
+
// silently dropped on the no-JS rerender path. See TIM-836
|
|
475
|
+
// (LOCAL-740).
|
|
454
476
|
for (const value of formRerender.setCookieHeaders) {
|
|
455
477
|
response.headers.append('Set-Cookie', value);
|
|
456
478
|
}
|