@chidchanun/bcp 0.1.0

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.
Files changed (66) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/LICENSE +21 -0
  3. package/README.md +241 -0
  4. package/docs/caching.md +76 -0
  5. package/docs/configuration.md +97 -0
  6. package/docs/deployment.md +74 -0
  7. package/docs/getting-started.md +82 -0
  8. package/docs/middleware.md +58 -0
  9. package/docs/releasing.md +299 -0
  10. package/docs/routing.md +103 -0
  11. package/docs/security.md +57 -0
  12. package/package.json +68 -0
  13. package/packages/bundler/src/client-islands.ts +1457 -0
  14. package/packages/bundler/src/incremental-context.ts +206 -0
  15. package/packages/bundler/src/index.ts +1991 -0
  16. package/packages/bundler/src/module-graph.ts +317 -0
  17. package/packages/bundler/src/partial-hydration.ts +414 -0
  18. package/packages/bundler/src/production.ts +974 -0
  19. package/packages/bundler/src/server-production-middleware.ts +447 -0
  20. package/packages/bundler/src/server-production.ts +1193 -0
  21. package/packages/bundler/src/special-files.ts +131 -0
  22. package/packages/cache/src/index.ts +761 -0
  23. package/packages/cli/bin/bcp.mjs +93 -0
  24. package/packages/cli/src/args.ts +305 -0
  25. package/packages/cli/src/bootstrap.ts +514 -0
  26. package/packages/cli/src/index.ts +504 -0
  27. package/packages/cli/src/version.ts +45 -0
  28. package/packages/client/src/cache.ts +11 -0
  29. package/packages/client/src/config.ts +18 -0
  30. package/packages/client/src/error-boundary.tsx +149 -0
  31. package/packages/client/src/hydration.ts +3 -0
  32. package/packages/client/src/index.tsx +57 -0
  33. package/packages/client/src/islands.tsx +315 -0
  34. package/packages/client/src/metadata.ts +281 -0
  35. package/packages/client/src/navigation-loading.ts +52 -0
  36. package/packages/client/src/navigation-state.ts +80 -0
  37. package/packages/client/src/not-found.ts +34 -0
  38. package/packages/client/src/persistent-layout-runtime.ts +273 -0
  39. package/packages/client/src/router-v2.tsx +969 -0
  40. package/packages/client/src/router.tsx +1 -0
  41. package/packages/config/src/index.ts +1038 -0
  42. package/packages/env/src/index.ts +593 -0
  43. package/packages/router/src/advanced-router.ts +1032 -0
  44. package/packages/router/src/index.ts +1 -0
  45. package/packages/server/src/compression.ts +249 -0
  46. package/packages/server/src/dev-document-metadata.ts +154 -0
  47. package/packages/server/src/dev-hmr.ts +225 -0
  48. package/packages/server/src/index.ts +2265 -0
  49. package/packages/server/src/metadata.ts +478 -0
  50. package/packages/server/src/middleware-dev-server.ts +260 -0
  51. package/packages/server/src/middleware-loader.ts +140 -0
  52. package/packages/server/src/middleware-proxy.ts +516 -0
  53. package/packages/server/src/middleware.ts +704 -0
  54. package/packages/server/src/navigation-payload.ts +471 -0
  55. package/packages/server/src/production-server.ts +1746 -0
  56. package/packages/server/src/response-cache-proxy.ts +828 -0
  57. package/packages/server/src/security-proxy.ts +406 -0
  58. package/packages/server/src/security.ts +451 -0
  59. package/packages/server/src/standalone-production-runtime-v2.ts +2047 -0
  60. package/packages/server/src/standalone-production-runtime-v3.ts +250 -0
  61. package/packages/server/src/standalone-production-runtime-v4.ts +289 -0
  62. package/packages/server/src/standalone-production-runtime-v5.ts +289 -0
  63. package/packages/server/src/standalone-production-runtime.ts +1951 -0
  64. package/packages/server/src/standalone-production-server.ts +6 -0
  65. package/packages/server/src/static-assets.ts +262 -0
  66. package/packages/server/src/static-dev-server.ts +854 -0
@@ -0,0 +1,704 @@
1
+ import type * as http from "node:http";
2
+
3
+ import {
4
+ assertSafeMiddlewareTarget,
5
+ } from "./security.js";
6
+
7
+ export type MiddlewareMatcher =
8
+ | string
9
+ | RegExp;
10
+
11
+ export interface MiddlewareConfig {
12
+ matcher?:
13
+ | MiddlewareMatcher
14
+ | MiddlewareMatcher[];
15
+ }
16
+
17
+ export interface MiddlewareCookieStore {
18
+ get(name: string): string | null;
19
+ has(name: string): boolean;
20
+ }
21
+
22
+ export interface MiddlewareRequest {
23
+ readonly url: string;
24
+ readonly method: string;
25
+ readonly headers: Headers;
26
+ readonly nextUrl: URL;
27
+ readonly cookies: MiddlewareCookieStore;
28
+ }
29
+
30
+ export interface MiddlewareHeaderOptions {
31
+ request?: {
32
+ headers?: HeadersInit;
33
+ };
34
+ response?: {
35
+ headers?: HeadersInit;
36
+ };
37
+ }
38
+
39
+ export interface MiddlewareRedirectOptions {
40
+ status?: 301 | 302 | 303 | 307 | 308;
41
+ headers?: HeadersInit;
42
+ }
43
+
44
+ export interface MiddlewareNextResult {
45
+ readonly type: "next";
46
+ readonly requestHeaders: Headers;
47
+ readonly responseHeaders: Headers;
48
+ }
49
+
50
+ export interface MiddlewareRewriteResult {
51
+ readonly type: "rewrite";
52
+ readonly url: string;
53
+ readonly requestHeaders: Headers;
54
+ readonly responseHeaders: Headers;
55
+ }
56
+
57
+ export interface MiddlewareRedirectResult {
58
+ readonly type: "redirect";
59
+ readonly url: string;
60
+ readonly status: 301 | 302 | 303 | 307 | 308;
61
+ readonly headers: Headers;
62
+ }
63
+
64
+ export type MiddlewareResult =
65
+ | MiddlewareNextResult
66
+ | MiddlewareRewriteResult
67
+ | MiddlewareRedirectResult;
68
+
69
+ export type MiddlewareHandler = (
70
+ request: MiddlewareRequest
71
+ ) =>
72
+ | MiddlewareResult
73
+ | Response
74
+ | void
75
+ | Promise<
76
+ MiddlewareResult |
77
+ Response |
78
+ void
79
+ >;
80
+
81
+ export interface MiddlewareModule {
82
+ middleware?: MiddlewareHandler;
83
+ default?: MiddlewareHandler;
84
+ config?: MiddlewareConfig;
85
+ }
86
+
87
+ export interface MiddlewareExecutionInput {
88
+ url: string | URL;
89
+ method?: string;
90
+ headers?: HeadersInit;
91
+ }
92
+
93
+ export type MiddlewareExecutionResult =
94
+ | {
95
+ type: "next";
96
+ url: URL;
97
+ requestHeaders: Headers;
98
+ responseHeaders: Headers;
99
+ }
100
+ | {
101
+ type: "rewrite";
102
+ url: URL;
103
+ requestHeaders: Headers;
104
+ responseHeaders: Headers;
105
+ }
106
+ | {
107
+ type: "redirect";
108
+ url: URL;
109
+ status: 301 | 302 | 303 | 307 | 308;
110
+ headers: Headers;
111
+ }
112
+ | {
113
+ type: "response";
114
+ response: Response;
115
+ };
116
+
117
+ export function next(
118
+ options: MiddlewareHeaderOptions = {}
119
+ ): MiddlewareNextResult {
120
+ return {
121
+ type: "next",
122
+ requestHeaders:
123
+ new Headers(
124
+ options.request?.headers
125
+ ),
126
+ responseHeaders:
127
+ new Headers(
128
+ options.response?.headers
129
+ ),
130
+ };
131
+ }
132
+
133
+ export function rewrite(
134
+ url: string | URL,
135
+ options: MiddlewareHeaderOptions = {}
136
+ ): MiddlewareRewriteResult {
137
+ return {
138
+ type: "rewrite",
139
+ url:
140
+ url.toString(),
141
+ requestHeaders:
142
+ new Headers(
143
+ options.request?.headers
144
+ ),
145
+ responseHeaders:
146
+ new Headers(
147
+ options.response?.headers
148
+ ),
149
+ };
150
+ }
151
+
152
+ export function redirect(
153
+ url: string | URL,
154
+ options: MiddlewareRedirectOptions = {}
155
+ ): MiddlewareRedirectResult {
156
+ return {
157
+ type: "redirect",
158
+ url:
159
+ url.toString(),
160
+ status:
161
+ options.status ?? 307,
162
+ headers:
163
+ new Headers(
164
+ options.headers
165
+ ),
166
+ };
167
+ }
168
+
169
+ export async function executeMiddleware(
170
+ module: MiddlewareModule | null | undefined,
171
+ input: MiddlewareExecutionInput
172
+ ): Promise<MiddlewareExecutionResult> {
173
+ const originalUrl =
174
+ input.url instanceof URL
175
+ ? new URL(
176
+ input.url.href
177
+ )
178
+ : new URL(
179
+ input.url
180
+ );
181
+
182
+ const baseHeaders =
183
+ new Headers(
184
+ input.headers
185
+ );
186
+
187
+ const passthrough =
188
+ (): MiddlewareExecutionResult => ({
189
+ type: "next",
190
+ url:
191
+ originalUrl,
192
+ requestHeaders:
193
+ baseHeaders,
194
+ responseHeaders:
195
+ new Headers(),
196
+ });
197
+
198
+ if (
199
+ !module ||
200
+ isFrameworkInternalPath(
201
+ originalUrl.pathname
202
+ )
203
+ ) {
204
+ return passthrough();
205
+ }
206
+
207
+ const handler =
208
+ module.middleware ??
209
+ module.default;
210
+
211
+ if (!handler) {
212
+ return passthrough();
213
+ }
214
+
215
+ if (
216
+ !matchesMiddleware(
217
+ originalUrl.pathname,
218
+ module.config?.matcher
219
+ )
220
+ ) {
221
+ return passthrough();
222
+ }
223
+
224
+ const request:
225
+ MiddlewareRequest = {
226
+ url:
227
+ originalUrl.href,
228
+ method:
229
+ input.method ??
230
+ "GET",
231
+ headers:
232
+ baseHeaders,
233
+ nextUrl:
234
+ originalUrl,
235
+ cookies:
236
+ createCookieStore(
237
+ baseHeaders.get(
238
+ "cookie"
239
+ )
240
+ ),
241
+ };
242
+
243
+ const result =
244
+ await handler(
245
+ request
246
+ );
247
+
248
+ if (
249
+ result === undefined
250
+ ) {
251
+ return passthrough();
252
+ }
253
+
254
+ if (
255
+ result instanceof Response
256
+ ) {
257
+ return {
258
+ type: "response",
259
+ response:
260
+ result,
261
+ };
262
+ }
263
+
264
+ if (
265
+ result.type === "next"
266
+ ) {
267
+ return {
268
+ type: "next",
269
+ url:
270
+ originalUrl,
271
+ requestHeaders:
272
+ mergeHeaders(
273
+ baseHeaders,
274
+ result.requestHeaders
275
+ ),
276
+ responseHeaders:
277
+ result.responseHeaders,
278
+ };
279
+ }
280
+
281
+ if (
282
+ result.type === "rewrite"
283
+ ) {
284
+ const target =
285
+ new URL(
286
+ result.url,
287
+ originalUrl
288
+ );
289
+
290
+ assertSafeMiddlewareTarget(
291
+ target,
292
+ originalUrl,
293
+ "rewrite"
294
+ );
295
+
296
+ return {
297
+ type: "rewrite",
298
+ url:
299
+ target,
300
+ requestHeaders:
301
+ mergeHeaders(
302
+ baseHeaders,
303
+ result.requestHeaders
304
+ ),
305
+ responseHeaders:
306
+ result.responseHeaders,
307
+ };
308
+ }
309
+
310
+ if (
311
+ result.type === "redirect"
312
+ ) {
313
+ const target =
314
+ new URL(
315
+ result.url,
316
+ originalUrl
317
+ );
318
+
319
+ assertSafeMiddlewareTarget(
320
+ target,
321
+ originalUrl,
322
+ "redirect"
323
+ );
324
+
325
+ return {
326
+ type: "redirect",
327
+ url:
328
+ target,
329
+ status:
330
+ result.status,
331
+ headers:
332
+ result.headers,
333
+ };
334
+ }
335
+
336
+ throw new Error(
337
+ "BCP Framework: middleware returned an unsupported result."
338
+ );
339
+ }
340
+
341
+ export function matchesMiddleware(
342
+ pathname: string,
343
+ matcher:
344
+ | MiddlewareConfig["matcher"]
345
+ | undefined
346
+ ): boolean {
347
+ if (
348
+ matcher === undefined
349
+ ) {
350
+ return true;
351
+ }
352
+
353
+ const matchers =
354
+ Array.isArray(
355
+ matcher
356
+ )
357
+ ? matcher
358
+ : [
359
+ matcher,
360
+ ];
361
+
362
+ if (
363
+ matchers.length === 0
364
+ ) {
365
+ return false;
366
+ }
367
+
368
+ return matchers.some(
369
+ (value) => {
370
+ if (
371
+ value instanceof RegExp
372
+ ) {
373
+ value.lastIndex = 0;
374
+ return value.test(
375
+ pathname
376
+ );
377
+ }
378
+
379
+ return matchPathPattern(
380
+ pathname,
381
+ value
382
+ );
383
+ }
384
+ );
385
+ }
386
+
387
+ export function applyMiddlewareRequestHeaders(
388
+ req: http.IncomingMessage,
389
+ headers: Headers
390
+ ): void {
391
+ const nextHeaders:
392
+ http.IncomingHttpHeaders = {};
393
+
394
+ headers.forEach(
395
+ (value, key) => {
396
+ nextHeaders[
397
+ key.toLowerCase()
398
+ ] =
399
+ value;
400
+ }
401
+ );
402
+
403
+ req.headers =
404
+ nextHeaders;
405
+ }
406
+
407
+ export function applyMiddlewareResponseHeaders(
408
+ res: http.ServerResponse,
409
+ headers: Headers
410
+ ): void {
411
+ headers.forEach(
412
+ (value, key) => {
413
+ res.setHeader(
414
+ key,
415
+ value
416
+ );
417
+ }
418
+ );
419
+ }
420
+
421
+ export function sendMiddlewareRedirect(
422
+ req: http.IncomingMessage,
423
+ res: http.ServerResponse,
424
+ result: Extract<
425
+ MiddlewareExecutionResult,
426
+ {
427
+ type: "redirect";
428
+ }
429
+ >
430
+ ): void {
431
+ applyMiddlewareResponseHeaders(
432
+ res,
433
+ result.headers
434
+ );
435
+
436
+ res.statusCode =
437
+ result.status;
438
+ res.setHeader(
439
+ "Location",
440
+ result.url.href
441
+ );
442
+ res.setHeader(
443
+ "Cache-Control",
444
+ "no-store"
445
+ );
446
+
447
+ if (
448
+ req.method === "HEAD"
449
+ ) {
450
+ res.end();
451
+ return;
452
+ }
453
+
454
+ res.end();
455
+ }
456
+
457
+ export async function sendMiddlewareResponse(
458
+ req: http.IncomingMessage,
459
+ res: http.ServerResponse,
460
+ response: Response
461
+ ): Promise<void> {
462
+ res.statusCode =
463
+ response.status;
464
+
465
+ response.headers.forEach(
466
+ (value, key) => {
467
+ res.setHeader(
468
+ key,
469
+ value
470
+ );
471
+ }
472
+ );
473
+
474
+ const getSetCookie =
475
+ (
476
+ response.headers as Headers & {
477
+ getSetCookie?: () => string[];
478
+ }
479
+ ).getSetCookie;
480
+
481
+ if (
482
+ typeof getSetCookie ===
483
+ "function"
484
+ ) {
485
+ const cookies =
486
+ getSetCookie.call(
487
+ response.headers
488
+ );
489
+
490
+ if (
491
+ cookies.length > 0
492
+ ) {
493
+ res.setHeader(
494
+ "Set-Cookie",
495
+ cookies
496
+ );
497
+ }
498
+ }
499
+
500
+ if (
501
+ req.method === "HEAD" ||
502
+ response.body === null
503
+ ) {
504
+ res.end();
505
+ return;
506
+ }
507
+
508
+ const body =
509
+ Buffer.from(
510
+ await response.arrayBuffer()
511
+ );
512
+
513
+ if (
514
+ !res.hasHeader(
515
+ "Content-Length"
516
+ )
517
+ ) {
518
+ res.setHeader(
519
+ "Content-Length",
520
+ body.length
521
+ );
522
+ }
523
+
524
+ res.end(
525
+ body
526
+ );
527
+ }
528
+
529
+ function matchPathPattern(
530
+ pathname: string,
531
+ pattern: string
532
+ ): boolean {
533
+ if (
534
+ pattern === "*" ||
535
+ pattern === "/:path*"
536
+ ) {
537
+ return true;
538
+ }
539
+
540
+ if (
541
+ !pattern.startsWith(
542
+ "/"
543
+ )
544
+ ) {
545
+ throw new Error(
546
+ `BCP Framework: middleware matcher "${pattern}" must start with "/".`
547
+ );
548
+ }
549
+
550
+ const escaped =
551
+ pattern
552
+ .split("/")
553
+ .filter(
554
+ Boolean
555
+ )
556
+ .map(
557
+ (segment) => {
558
+ if (
559
+ segment === "*" ||
560
+ /^:[A-Za-z_][A-Za-z0-9_]*\*$/.test(
561
+ segment
562
+ )
563
+ ) {
564
+ return "(?:.*)?";
565
+ }
566
+
567
+ if (
568
+ /^:[A-Za-z_][A-Za-z0-9_]*$/.test(
569
+ segment
570
+ )
571
+ ) {
572
+ return "[^/]+";
573
+ }
574
+
575
+ return escapeRegExp(
576
+ segment
577
+ );
578
+ }
579
+ );
580
+
581
+ if (
582
+ escaped.length === 0
583
+ ) {
584
+ return pathname === "/";
585
+ }
586
+
587
+ const source =
588
+ `^/${escaped.join("/")}/?$`;
589
+
590
+ return new RegExp(
591
+ source
592
+ ).test(
593
+ pathname
594
+ );
595
+ }
596
+
597
+ function createCookieStore(
598
+ cookieHeader: string | null
599
+ ): MiddlewareCookieStore {
600
+ const values =
601
+ new Map<string, string>();
602
+
603
+ if (cookieHeader) {
604
+ for (
605
+ const part
606
+ of cookieHeader.split(";")
607
+ ) {
608
+ const separator =
609
+ part.indexOf("=");
610
+
611
+ if (
612
+ separator < 0
613
+ ) {
614
+ continue;
615
+ }
616
+
617
+ const name =
618
+ part
619
+ .slice(
620
+ 0,
621
+ separator
622
+ )
623
+ .trim();
624
+ const rawValue =
625
+ part
626
+ .slice(
627
+ separator + 1
628
+ )
629
+ .trim();
630
+
631
+ if (!name) {
632
+ continue;
633
+ }
634
+
635
+ try {
636
+ values.set(
637
+ name,
638
+ decodeURIComponent(
639
+ rawValue
640
+ )
641
+ );
642
+ } catch {
643
+ values.set(
644
+ name,
645
+ rawValue
646
+ );
647
+ }
648
+ }
649
+ }
650
+
651
+ return {
652
+ get(name) {
653
+ return values.get(
654
+ name
655
+ ) ?? null;
656
+ },
657
+ has(name) {
658
+ return values.has(
659
+ name
660
+ );
661
+ },
662
+ };
663
+ }
664
+
665
+ function mergeHeaders(
666
+ base: Headers,
667
+ additions: Headers
668
+ ): Headers {
669
+ const result =
670
+ new Headers(
671
+ base
672
+ );
673
+
674
+ additions.forEach(
675
+ (value, key) => {
676
+ result.set(
677
+ key,
678
+ value
679
+ );
680
+ }
681
+ );
682
+
683
+ return result;
684
+ }
685
+
686
+ function isFrameworkInternalPath(
687
+ pathname: string
688
+ ): boolean {
689
+ return (
690
+ pathname === "/_bcp" ||
691
+ pathname.startsWith(
692
+ "/_bcp/"
693
+ )
694
+ );
695
+ }
696
+
697
+ function escapeRegExp(
698
+ value: string
699
+ ): string {
700
+ return value.replace(
701
+ /[.*+?^${}()|[\]\\]/g,
702
+ "\\$&"
703
+ );
704
+ }