@chidchanun/bcp 0.1.10 → 0.1.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/CHANGELOG.md +43 -2
- package/README.md +131 -9
- package/docs/form-actions.md +314 -0
- package/package.json +1 -1
- package/packages/bundler/src/server-production-actions.ts +523 -0
- package/packages/bundler/src/server-production-middleware.ts +17 -0
- package/packages/client/src/form.tsx +548 -0
- package/packages/client/src/index.tsx +10 -0
- package/packages/client/src/server.ts +6 -0
- package/packages/server/src/form-action-dev-proxy.ts +601 -0
- package/packages/server/src/form-action-transport.ts +271 -0
- package/packages/server/src/form-action.ts +493 -0
- package/packages/server/src/middleware-dev-server.ts +56 -2
- package/packages/server/src/standalone-production-runtime-v2-action.ts +765 -0
- package/packages/server/src/standalone-production-runtime-v3.ts +1 -1
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import {
|
|
2
|
+
copySetCookieHeaders,
|
|
3
|
+
} from "./navigation-response.js";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
applyResponseCookies,
|
|
7
|
+
} from "./request-context.js";
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
PageActionExecution,
|
|
11
|
+
} from "./form-action.js";
|
|
12
|
+
|
|
13
|
+
export interface CompletedPageActionData {
|
|
14
|
+
kind: "data";
|
|
15
|
+
data: unknown;
|
|
16
|
+
setCookies: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface CompletedPageActionResponse {
|
|
20
|
+
kind: "response";
|
|
21
|
+
response: Response;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type CompletedPageAction =
|
|
25
|
+
| CompletedPageActionData
|
|
26
|
+
| CompletedPageActionResponse;
|
|
27
|
+
|
|
28
|
+
export interface FormActionDataPayload {
|
|
29
|
+
kind: "data";
|
|
30
|
+
data: unknown;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface FormActionRedirectPayload {
|
|
34
|
+
kind: "redirect";
|
|
35
|
+
redirect: {
|
|
36
|
+
location: string;
|
|
37
|
+
status: number;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface FormActionErrorPayload {
|
|
42
|
+
kind: "error";
|
|
43
|
+
error: {
|
|
44
|
+
message: string;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function completePageActionExecution(
|
|
49
|
+
execution: PageActionExecution
|
|
50
|
+
): CompletedPageAction {
|
|
51
|
+
if (
|
|
52
|
+
execution.response
|
|
53
|
+
) {
|
|
54
|
+
return {
|
|
55
|
+
kind:
|
|
56
|
+
"response",
|
|
57
|
+
response:
|
|
58
|
+
applyResponseCookies(
|
|
59
|
+
execution.response
|
|
60
|
+
),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const carrier =
|
|
65
|
+
applyResponseCookies(
|
|
66
|
+
new Response(
|
|
67
|
+
null,
|
|
68
|
+
{
|
|
69
|
+
status: 204,
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
kind:
|
|
76
|
+
"data",
|
|
77
|
+
data:
|
|
78
|
+
execution.data,
|
|
79
|
+
setCookies:
|
|
80
|
+
carrier.headers
|
|
81
|
+
.getSetCookie(),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function createFormActionTransportResponse(
|
|
86
|
+
result: CompletedPageAction,
|
|
87
|
+
target: URL,
|
|
88
|
+
enhanced: boolean
|
|
89
|
+
): Response {
|
|
90
|
+
if (
|
|
91
|
+
result.kind ===
|
|
92
|
+
"response"
|
|
93
|
+
) {
|
|
94
|
+
if (enhanced) {
|
|
95
|
+
const redirect =
|
|
96
|
+
createRedirectPayloadResponse(
|
|
97
|
+
result.response
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
if (redirect) {
|
|
101
|
+
return redirect;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return result.response;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (enhanced) {
|
|
109
|
+
const headers =
|
|
110
|
+
new Headers();
|
|
111
|
+
|
|
112
|
+
for (
|
|
113
|
+
const cookie
|
|
114
|
+
of result.setCookies
|
|
115
|
+
) {
|
|
116
|
+
headers.append(
|
|
117
|
+
"Set-Cookie",
|
|
118
|
+
cookie
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return createFormActionJsonResponse(
|
|
123
|
+
{
|
|
124
|
+
kind:
|
|
125
|
+
"data",
|
|
126
|
+
data:
|
|
127
|
+
result.data,
|
|
128
|
+
} satisfies FormActionDataPayload,
|
|
129
|
+
{
|
|
130
|
+
status: 200,
|
|
131
|
+
headers,
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const headers =
|
|
137
|
+
new Headers({
|
|
138
|
+
Location:
|
|
139
|
+
`${target.pathname}${target.search}${target.hash}`,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
for (
|
|
143
|
+
const cookie
|
|
144
|
+
of result.setCookies
|
|
145
|
+
) {
|
|
146
|
+
headers.append(
|
|
147
|
+
"Set-Cookie",
|
|
148
|
+
cookie
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return new Response(
|
|
153
|
+
null,
|
|
154
|
+
{
|
|
155
|
+
status: 303,
|
|
156
|
+
headers,
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function createFormActionErrorResponse(
|
|
162
|
+
message: string,
|
|
163
|
+
status = 500
|
|
164
|
+
): Response {
|
|
165
|
+
return createFormActionJsonResponse(
|
|
166
|
+
{
|
|
167
|
+
kind:
|
|
168
|
+
"error",
|
|
169
|
+
error: {
|
|
170
|
+
message,
|
|
171
|
+
},
|
|
172
|
+
} satisfies FormActionErrorPayload,
|
|
173
|
+
{
|
|
174
|
+
status,
|
|
175
|
+
}
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function createFormActionJsonResponse(
|
|
180
|
+
value: unknown,
|
|
181
|
+
init: ResponseInit = {}
|
|
182
|
+
): Response {
|
|
183
|
+
const headers =
|
|
184
|
+
new Headers(
|
|
185
|
+
init.headers
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
headers.set(
|
|
189
|
+
"Cache-Control",
|
|
190
|
+
"no-store"
|
|
191
|
+
);
|
|
192
|
+
headers.set(
|
|
193
|
+
"X-Content-Type-Options",
|
|
194
|
+
"nosniff"
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
return Response.json(
|
|
198
|
+
value,
|
|
199
|
+
{
|
|
200
|
+
...init,
|
|
201
|
+
headers,
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function isEnhancedFormActionRequest(
|
|
207
|
+
headers: Headers
|
|
208
|
+
): boolean {
|
|
209
|
+
return (
|
|
210
|
+
headers.get(
|
|
211
|
+
"x-bcp-action"
|
|
212
|
+
) === "1"
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function createRedirectPayloadResponse(
|
|
217
|
+
response: Response
|
|
218
|
+
): Response | null {
|
|
219
|
+
if (
|
|
220
|
+
!isRedirectStatus(
|
|
221
|
+
response.status
|
|
222
|
+
)
|
|
223
|
+
) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const location =
|
|
228
|
+
response.headers.get(
|
|
229
|
+
"location"
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
if (!location) {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const headers =
|
|
237
|
+
new Headers();
|
|
238
|
+
|
|
239
|
+
copySetCookieHeaders(
|
|
240
|
+
response.headers,
|
|
241
|
+
headers
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
return createFormActionJsonResponse(
|
|
245
|
+
{
|
|
246
|
+
kind:
|
|
247
|
+
"redirect",
|
|
248
|
+
redirect: {
|
|
249
|
+
location,
|
|
250
|
+
status:
|
|
251
|
+
response.status,
|
|
252
|
+
},
|
|
253
|
+
} satisfies FormActionRedirectPayload,
|
|
254
|
+
{
|
|
255
|
+
status: 200,
|
|
256
|
+
headers,
|
|
257
|
+
}
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function isRedirectStatus(
|
|
262
|
+
status: number
|
|
263
|
+
): boolean {
|
|
264
|
+
return (
|
|
265
|
+
status === 301 ||
|
|
266
|
+
status === 302 ||
|
|
267
|
+
status === 303 ||
|
|
268
|
+
status === 307 ||
|
|
269
|
+
status === 308
|
|
270
|
+
);
|
|
271
|
+
}
|