@chidchanun/bcp 0.1.9 → 0.1.10
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 +41 -0
- package/README.md +141 -8
- package/docs/releasing.md +34 -19
- package/docs/route-guards.md +240 -0
- package/docs/updating.md +130 -0
- package/package.json +1 -1
- package/packages/bundler/src/server-production-guards.ts +497 -0
- package/packages/bundler/src/server-production-middleware.ts +33 -8
- package/packages/cli/src/args.ts +58 -0
- package/packages/cli/src/index.ts +41 -13
- package/packages/cli/src/update.ts +860 -0
- package/packages/client/src/index.tsx +1 -0
- package/packages/client/src/loader-data.tsx +171 -41
- package/packages/server/src/page-guard.ts +529 -0
- package/packages/server/src/page-loader.ts +317 -20
- package/packages/server/src/standalone-production-runtime-v2-guard.ts +815 -0
- package/packages/server/src/standalone-production-runtime-v3.ts +1 -1
|
@@ -4,12 +4,29 @@ import {
|
|
|
4
4
|
pathToFileURL,
|
|
5
5
|
} from "node:url";
|
|
6
6
|
|
|
7
|
+
import {
|
|
8
|
+
executePageGuards,
|
|
9
|
+
type PageGuardData,
|
|
10
|
+
} from "./page-guard.js";
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
headers,
|
|
14
|
+
} from "./request-context.js";
|
|
15
|
+
|
|
7
16
|
export const LOADER_DATA_PARAM =
|
|
8
17
|
"__bcp_loader_data";
|
|
18
|
+
export const GUARD_DATA_PARAM =
|
|
19
|
+
"__bcp_guard_data";
|
|
20
|
+
export const GUARD_DATA_HEADER =
|
|
21
|
+
"x-bcp-guard-data";
|
|
22
|
+
|
|
23
|
+
const SERVER_DATA_MARKER =
|
|
24
|
+
"__bcp_server_data";
|
|
9
25
|
|
|
10
26
|
export interface PageLoaderContext {
|
|
11
27
|
params: Record<string, any>;
|
|
12
28
|
searchParams: URLSearchParams;
|
|
29
|
+
guardData: Readonly<PageGuardData>;
|
|
13
30
|
}
|
|
14
31
|
|
|
15
32
|
export type PageLoader = (
|
|
@@ -18,9 +35,26 @@ export type PageLoader = (
|
|
|
18
35
|
| unknown
|
|
19
36
|
| Promise<unknown>;
|
|
20
37
|
|
|
38
|
+
export interface PageServerDataEnvelope {
|
|
39
|
+
__bcp_server_data: true;
|
|
40
|
+
hasPageLoader: boolean;
|
|
41
|
+
hasGuard: boolean;
|
|
42
|
+
loaderData: unknown;
|
|
43
|
+
guardData: PageGuardData;
|
|
44
|
+
}
|
|
45
|
+
|
|
21
46
|
export interface PageLoaderExecution {
|
|
47
|
+
/*
|
|
48
|
+
* hasLoader remains the SSR-provider switch for backward compatibility.
|
|
49
|
+
* It is true when either loader or guard data must be made available to
|
|
50
|
+
* React during server rendering.
|
|
51
|
+
*/
|
|
22
52
|
hasLoader: boolean;
|
|
53
|
+
hasPageLoader?: boolean;
|
|
54
|
+
hasGuard?: boolean;
|
|
23
55
|
data: unknown;
|
|
56
|
+
loaderData?: unknown;
|
|
57
|
+
guardData?: PageGuardData;
|
|
24
58
|
response: Response | null;
|
|
25
59
|
}
|
|
26
60
|
|
|
@@ -81,20 +115,54 @@ export async function executePageLoader(
|
|
|
81
115
|
requestUrl: URL,
|
|
82
116
|
moduleVersion = 0
|
|
83
117
|
): Promise<PageLoaderExecution> {
|
|
118
|
+
const appDirectory =
|
|
119
|
+
findAppDirectory(
|
|
120
|
+
pageFilePath
|
|
121
|
+
);
|
|
122
|
+
const guardExecution =
|
|
123
|
+
await executePageGuards(
|
|
124
|
+
appDirectory,
|
|
125
|
+
pageFilePath,
|
|
126
|
+
params,
|
|
127
|
+
requestUrl,
|
|
128
|
+
moduleVersion
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
if (
|
|
132
|
+
guardExecution.response
|
|
133
|
+
) {
|
|
134
|
+
return createExecution({
|
|
135
|
+
hasPageLoader:
|
|
136
|
+
false,
|
|
137
|
+
hasGuard:
|
|
138
|
+
guardExecution.hasGuard,
|
|
139
|
+
loaderData:
|
|
140
|
+
undefined,
|
|
141
|
+
guardData:
|
|
142
|
+
guardExecution.data,
|
|
143
|
+
response:
|
|
144
|
+
guardExecution.response,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
84
148
|
const loaderFile =
|
|
85
149
|
findPageLoaderFile(
|
|
86
150
|
pageFilePath
|
|
87
151
|
);
|
|
88
152
|
|
|
89
153
|
if (!loaderFile) {
|
|
90
|
-
return {
|
|
91
|
-
|
|
154
|
+
return createExecution({
|
|
155
|
+
hasPageLoader:
|
|
92
156
|
false,
|
|
93
|
-
|
|
157
|
+
hasGuard:
|
|
158
|
+
guardExecution.hasGuard,
|
|
159
|
+
loaderData:
|
|
94
160
|
undefined,
|
|
161
|
+
guardData:
|
|
162
|
+
guardExecution.data,
|
|
95
163
|
response:
|
|
96
164
|
null,
|
|
97
|
-
};
|
|
165
|
+
});
|
|
98
166
|
}
|
|
99
167
|
|
|
100
168
|
const moduleUrl =
|
|
@@ -130,7 +198,9 @@ export async function executePageLoader(
|
|
|
130
198
|
PageLoader,
|
|
131
199
|
params,
|
|
132
200
|
requestUrl,
|
|
133
|
-
loaderFile
|
|
201
|
+
loaderFile,
|
|
202
|
+
guardExecution.data,
|
|
203
|
+
guardExecution.hasGuard
|
|
134
204
|
);
|
|
135
205
|
}
|
|
136
206
|
|
|
@@ -138,8 +208,24 @@ export async function executePageLoaderFunction(
|
|
|
138
208
|
loader: PageLoader,
|
|
139
209
|
params: Record<string, any>,
|
|
140
210
|
requestUrl: URL,
|
|
141
|
-
label = "loader"
|
|
211
|
+
label = "loader",
|
|
212
|
+
explicitGuardData?: PageGuardData,
|
|
213
|
+
explicitHasGuard?: boolean
|
|
142
214
|
): Promise<PageLoaderExecution> {
|
|
215
|
+
const headerGuardData =
|
|
216
|
+
explicitGuardData ===
|
|
217
|
+
undefined
|
|
218
|
+
? await readGuardDataHeader()
|
|
219
|
+
: null;
|
|
220
|
+
const guardData =
|
|
221
|
+
explicitGuardData ??
|
|
222
|
+
headerGuardData?.data ??
|
|
223
|
+
{};
|
|
224
|
+
const hasGuard =
|
|
225
|
+
explicitHasGuard ??
|
|
226
|
+
headerGuardData?.hasGuard ??
|
|
227
|
+
false;
|
|
228
|
+
|
|
143
229
|
const result =
|
|
144
230
|
await loader({
|
|
145
231
|
params: {
|
|
@@ -149,20 +235,25 @@ export async function executePageLoaderFunction(
|
|
|
149
235
|
new URLSearchParams(
|
|
150
236
|
requestUrl.searchParams
|
|
151
237
|
),
|
|
238
|
+
guardData: {
|
|
239
|
+
...guardData,
|
|
240
|
+
},
|
|
152
241
|
});
|
|
153
242
|
|
|
154
243
|
if (
|
|
155
244
|
result instanceof
|
|
156
245
|
Response
|
|
157
246
|
) {
|
|
158
|
-
return {
|
|
159
|
-
|
|
247
|
+
return createExecution({
|
|
248
|
+
hasPageLoader:
|
|
160
249
|
true,
|
|
161
|
-
|
|
250
|
+
hasGuard,
|
|
251
|
+
loaderData:
|
|
162
252
|
undefined,
|
|
253
|
+
guardData,
|
|
163
254
|
response:
|
|
164
255
|
result,
|
|
165
|
-
};
|
|
256
|
+
});
|
|
166
257
|
}
|
|
167
258
|
|
|
168
259
|
assertLoaderData(
|
|
@@ -170,33 +261,239 @@ export async function executePageLoaderFunction(
|
|
|
170
261
|
label
|
|
171
262
|
);
|
|
172
263
|
|
|
173
|
-
return {
|
|
174
|
-
|
|
264
|
+
return createExecution({
|
|
265
|
+
hasPageLoader:
|
|
175
266
|
true,
|
|
176
|
-
|
|
267
|
+
hasGuard,
|
|
268
|
+
loaderData:
|
|
177
269
|
result,
|
|
270
|
+
guardData,
|
|
178
271
|
response:
|
|
179
272
|
null,
|
|
180
|
-
};
|
|
273
|
+
});
|
|
181
274
|
}
|
|
182
275
|
|
|
183
276
|
export function attachLoaderDataToParams(
|
|
184
277
|
params: Record<string, any>,
|
|
185
278
|
execution: PageLoaderExecution
|
|
186
279
|
): Record<string, any> {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
280
|
+
let result = {
|
|
281
|
+
...params,
|
|
282
|
+
};
|
|
283
|
+
const hasGuard =
|
|
284
|
+
execution.hasGuard ===
|
|
285
|
+
true;
|
|
286
|
+
const guardData =
|
|
287
|
+
execution.guardData ??
|
|
288
|
+
{};
|
|
289
|
+
const hasPageLoader =
|
|
290
|
+
execution.hasPageLoader ??
|
|
291
|
+
execution.hasLoader;
|
|
292
|
+
const loaderData =
|
|
293
|
+
execution.hasPageLoader ===
|
|
294
|
+
undefined
|
|
295
|
+
? execution.data
|
|
296
|
+
: execution.loaderData;
|
|
297
|
+
|
|
298
|
+
if (hasGuard) {
|
|
299
|
+
result = {
|
|
300
|
+
...result,
|
|
301
|
+
[GUARD_DATA_PARAM]:
|
|
302
|
+
guardData,
|
|
190
303
|
};
|
|
191
304
|
}
|
|
192
305
|
|
|
306
|
+
if (hasPageLoader) {
|
|
307
|
+
result = {
|
|
308
|
+
...result,
|
|
309
|
+
[LOADER_DATA_PARAM]:
|
|
310
|
+
loaderData,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return result;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function isPageServerDataEnvelope(
|
|
318
|
+
value: unknown
|
|
319
|
+
): value is PageServerDataEnvelope {
|
|
320
|
+
return (
|
|
321
|
+
value !== null &&
|
|
322
|
+
typeof value ===
|
|
323
|
+
"object" &&
|
|
324
|
+
(
|
|
325
|
+
value as
|
|
326
|
+
Record<string, unknown>
|
|
327
|
+
)[SERVER_DATA_MARKER] ===
|
|
328
|
+
true
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function createExecution(
|
|
333
|
+
input: {
|
|
334
|
+
hasPageLoader: boolean;
|
|
335
|
+
hasGuard: boolean;
|
|
336
|
+
loaderData: unknown;
|
|
337
|
+
guardData: PageGuardData;
|
|
338
|
+
response: Response | null;
|
|
339
|
+
}
|
|
340
|
+
): PageLoaderExecution {
|
|
341
|
+
const data:
|
|
342
|
+
PageServerDataEnvelope = {
|
|
343
|
+
__bcp_server_data:
|
|
344
|
+
true,
|
|
345
|
+
hasPageLoader:
|
|
346
|
+
input.hasPageLoader,
|
|
347
|
+
hasGuard:
|
|
348
|
+
input.hasGuard,
|
|
349
|
+
loaderData:
|
|
350
|
+
input.loaderData,
|
|
351
|
+
guardData: {
|
|
352
|
+
...input.guardData,
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
|
|
193
356
|
return {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
357
|
+
hasLoader:
|
|
358
|
+
input.hasPageLoader ||
|
|
359
|
+
input.hasGuard,
|
|
360
|
+
hasPageLoader:
|
|
361
|
+
input.hasPageLoader,
|
|
362
|
+
hasGuard:
|
|
363
|
+
input.hasGuard,
|
|
364
|
+
data,
|
|
365
|
+
loaderData:
|
|
366
|
+
input.loaderData,
|
|
367
|
+
guardData:
|
|
368
|
+
data.guardData,
|
|
369
|
+
response:
|
|
370
|
+
input.response,
|
|
197
371
|
};
|
|
198
372
|
}
|
|
199
373
|
|
|
374
|
+
async function readGuardDataHeader():
|
|
375
|
+
Promise<{
|
|
376
|
+
hasGuard: true;
|
|
377
|
+
data: PageGuardData;
|
|
378
|
+
} | null> {
|
|
379
|
+
let requestHeaders:
|
|
380
|
+
Headers;
|
|
381
|
+
|
|
382
|
+
try {
|
|
383
|
+
requestHeaders =
|
|
384
|
+
await headers();
|
|
385
|
+
} catch {
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const encoded =
|
|
390
|
+
requestHeaders.get(
|
|
391
|
+
GUARD_DATA_HEADER
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
if (!encoded) {
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
let parsed:
|
|
399
|
+
unknown;
|
|
400
|
+
|
|
401
|
+
try {
|
|
402
|
+
parsed =
|
|
403
|
+
JSON.parse(
|
|
404
|
+
Buffer.from(
|
|
405
|
+
encoded,
|
|
406
|
+
"base64url"
|
|
407
|
+
).toString(
|
|
408
|
+
"utf8"
|
|
409
|
+
)
|
|
410
|
+
);
|
|
411
|
+
} catch {
|
|
412
|
+
throw new Error(
|
|
413
|
+
"BCP Framework: invalid internal guard data header."
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
assertGuardHeaderData(
|
|
418
|
+
parsed
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
return {
|
|
422
|
+
hasGuard:
|
|
423
|
+
true,
|
|
424
|
+
data:
|
|
425
|
+
parsed,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function assertGuardHeaderData(
|
|
430
|
+
value: unknown
|
|
431
|
+
): asserts value is PageGuardData {
|
|
432
|
+
if (
|
|
433
|
+
value === null ||
|
|
434
|
+
Array.isArray(
|
|
435
|
+
value
|
|
436
|
+
) ||
|
|
437
|
+
typeof value !==
|
|
438
|
+
"object"
|
|
439
|
+
) {
|
|
440
|
+
throw new Error(
|
|
441
|
+
"BCP Framework: invalid internal guard data header."
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const prototype =
|
|
446
|
+
Object.getPrototypeOf(
|
|
447
|
+
value
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
if (
|
|
451
|
+
prototype !==
|
|
452
|
+
Object.prototype &&
|
|
453
|
+
prototype !==
|
|
454
|
+
null
|
|
455
|
+
) {
|
|
456
|
+
throw new Error(
|
|
457
|
+
"BCP Framework: invalid internal guard data header."
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function findAppDirectory(
|
|
463
|
+
pageFilePath: string
|
|
464
|
+
): string {
|
|
465
|
+
let current =
|
|
466
|
+
path.dirname(
|
|
467
|
+
path.resolve(
|
|
468
|
+
pageFilePath
|
|
469
|
+
)
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
while (true) {
|
|
473
|
+
if (
|
|
474
|
+
path.basename(
|
|
475
|
+
current
|
|
476
|
+
) === "app"
|
|
477
|
+
) {
|
|
478
|
+
return current;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const parent =
|
|
482
|
+
path.dirname(
|
|
483
|
+
current
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
if (parent === current) {
|
|
487
|
+
throw new Error(
|
|
488
|
+
`BCP Framework: could not resolve app directory for page "${pageFilePath}".`
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
current =
|
|
493
|
+
parent;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
200
497
|
function assertLoaderData(
|
|
201
498
|
value: unknown,
|
|
202
499
|
loaderLabel: string
|