@buenojs/bueno 0.8.3 → 0.8.5

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 (218) hide show
  1. package/README.md +136 -16
  2. package/dist/cli/{index.js → bin.js} +3036 -1421
  3. package/dist/container/index.js +250 -0
  4. package/dist/context/index.js +219 -0
  5. package/dist/database/index.js +493 -0
  6. package/dist/frontend/index.js +7697 -0
  7. package/dist/health/index.js +364 -0
  8. package/dist/i18n/index.js +345 -0
  9. package/dist/index.js +11043 -6482
  10. package/dist/jobs/index.js +819 -0
  11. package/dist/lock/index.js +367 -0
  12. package/dist/logger/index.js +281 -0
  13. package/dist/metrics/index.js +289 -0
  14. package/dist/middleware/index.js +77 -0
  15. package/dist/migrations/index.js +571 -0
  16. package/dist/modules/index.js +3346 -0
  17. package/dist/notification/index.js +484 -0
  18. package/dist/observability/index.js +331 -0
  19. package/dist/openapi/index.js +776 -0
  20. package/dist/orm/index.js +1356 -0
  21. package/dist/router/index.js +886 -0
  22. package/dist/rpc/index.js +691 -0
  23. package/dist/schema/index.js +400 -0
  24. package/dist/telemetry/index.js +595 -0
  25. package/dist/template/index.js +640 -0
  26. package/dist/templates/index.js +640 -0
  27. package/dist/testing/index.js +1111 -0
  28. package/dist/types/index.js +60 -0
  29. package/package.json +121 -27
  30. package/src/cache/index.ts +2 -1
  31. package/src/cli/bin.ts +2 -2
  32. package/src/cli/commands/build.ts +183 -165
  33. package/src/cli/commands/dev.ts +96 -89
  34. package/src/cli/commands/generate.ts +142 -111
  35. package/src/cli/commands/help.ts +20 -16
  36. package/src/cli/commands/index.ts +3 -6
  37. package/src/cli/commands/migration.ts +124 -105
  38. package/src/cli/commands/new.ts +392 -438
  39. package/src/cli/commands/start.ts +81 -79
  40. package/src/cli/core/args.ts +68 -50
  41. package/src/cli/core/console.ts +89 -95
  42. package/src/cli/core/index.ts +4 -4
  43. package/src/cli/core/prompt.ts +65 -62
  44. package/src/cli/core/spinner.ts +23 -20
  45. package/src/cli/index.ts +46 -38
  46. package/src/cli/templates/database/index.ts +61 -0
  47. package/src/cli/templates/database/mysql.ts +14 -0
  48. package/src/cli/templates/database/none.ts +16 -0
  49. package/src/cli/templates/database/postgresql.ts +14 -0
  50. package/src/cli/templates/database/sqlite.ts +14 -0
  51. package/src/cli/templates/deploy.ts +29 -26
  52. package/src/cli/templates/docker.ts +41 -30
  53. package/src/cli/templates/frontend/index.ts +63 -0
  54. package/src/cli/templates/frontend/none.ts +17 -0
  55. package/src/cli/templates/frontend/react.ts +140 -0
  56. package/src/cli/templates/frontend/solid.ts +134 -0
  57. package/src/cli/templates/frontend/svelte.ts +131 -0
  58. package/src/cli/templates/frontend/vue.ts +130 -0
  59. package/src/cli/templates/generators/index.ts +339 -0
  60. package/src/cli/templates/generators/types.ts +56 -0
  61. package/src/cli/templates/index.ts +35 -2
  62. package/src/cli/templates/project/api.ts +81 -0
  63. package/src/cli/templates/project/default.ts +140 -0
  64. package/src/cli/templates/project/fullstack.ts +111 -0
  65. package/src/cli/templates/project/index.ts +95 -0
  66. package/src/cli/templates/project/minimal.ts +45 -0
  67. package/src/cli/templates/project/types.ts +94 -0
  68. package/src/cli/templates/project/website.ts +263 -0
  69. package/src/cli/utils/fs.ts +55 -41
  70. package/src/cli/utils/index.ts +3 -2
  71. package/src/cli/utils/strings.ts +47 -33
  72. package/src/cli/utils/version.ts +47 -0
  73. package/src/config/env-validation.ts +100 -0
  74. package/src/config/env.ts +169 -41
  75. package/src/config/index.ts +28 -20
  76. package/src/config/loader.ts +25 -16
  77. package/src/config/merge.ts +21 -10
  78. package/src/config/types.ts +545 -25
  79. package/src/config/validation.ts +215 -7
  80. package/src/container/forward-ref.ts +22 -22
  81. package/src/container/index.ts +34 -12
  82. package/src/context/index.ts +11 -1
  83. package/src/database/index.ts +7 -190
  84. package/src/database/orm/builder.ts +457 -0
  85. package/src/database/orm/casts/index.ts +130 -0
  86. package/src/database/orm/casts/types.ts +25 -0
  87. package/src/database/orm/compiler.ts +304 -0
  88. package/src/database/orm/hooks/index.ts +114 -0
  89. package/src/database/orm/index.ts +61 -0
  90. package/src/database/orm/model-registry.ts +59 -0
  91. package/src/database/orm/model.ts +821 -0
  92. package/src/database/orm/relationships/base.ts +146 -0
  93. package/src/database/orm/relationships/belongs-to-many.ts +179 -0
  94. package/src/database/orm/relationships/belongs-to.ts +56 -0
  95. package/src/database/orm/relationships/has-many.ts +45 -0
  96. package/src/database/orm/relationships/has-one.ts +41 -0
  97. package/src/database/orm/relationships/index.ts +11 -0
  98. package/src/database/orm/scopes/index.ts +55 -0
  99. package/src/events/__tests__/event-system.test.ts +235 -0
  100. package/src/events/config.ts +238 -0
  101. package/src/events/example-usage.ts +185 -0
  102. package/src/events/index.ts +278 -0
  103. package/src/events/manager.ts +385 -0
  104. package/src/events/registry.ts +182 -0
  105. package/src/events/types.ts +124 -0
  106. package/src/frontend/api-routes.ts +65 -23
  107. package/src/frontend/bundler.ts +76 -34
  108. package/src/frontend/console-client.ts +2 -2
  109. package/src/frontend/console-stream.ts +94 -38
  110. package/src/frontend/dev-server.ts +94 -46
  111. package/src/frontend/file-router.ts +61 -19
  112. package/src/frontend/frameworks/index.ts +37 -10
  113. package/src/frontend/frameworks/react.ts +10 -8
  114. package/src/frontend/frameworks/solid.ts +11 -9
  115. package/src/frontend/frameworks/svelte.ts +15 -9
  116. package/src/frontend/frameworks/vue.ts +13 -11
  117. package/src/frontend/hmr-client.ts +12 -10
  118. package/src/frontend/hmr.ts +146 -103
  119. package/src/frontend/index.ts +14 -5
  120. package/src/frontend/islands.ts +41 -22
  121. package/src/frontend/isr.ts +59 -37
  122. package/src/frontend/layout.ts +36 -21
  123. package/src/frontend/ssr/react.ts +74 -27
  124. package/src/frontend/ssr/solid.ts +54 -20
  125. package/src/frontend/ssr/svelte.ts +48 -14
  126. package/src/frontend/ssr/vue.ts +50 -18
  127. package/src/frontend/ssr.ts +83 -39
  128. package/src/frontend/types.ts +91 -56
  129. package/src/health/index.ts +21 -9
  130. package/src/i18n/engine.ts +305 -0
  131. package/src/i18n/index.ts +38 -0
  132. package/src/i18n/loader.ts +218 -0
  133. package/src/i18n/middleware.ts +164 -0
  134. package/src/i18n/negotiator.ts +162 -0
  135. package/src/i18n/types.ts +158 -0
  136. package/src/index.ts +179 -27
  137. package/src/jobs/drivers/memory.ts +315 -0
  138. package/src/jobs/drivers/redis.ts +459 -0
  139. package/src/jobs/index.ts +30 -0
  140. package/src/jobs/queue.ts +281 -0
  141. package/src/jobs/types.ts +295 -0
  142. package/src/jobs/worker.ts +380 -0
  143. package/src/logger/index.ts +1 -3
  144. package/src/logger/transports/index.ts +62 -22
  145. package/src/metrics/index.ts +25 -16
  146. package/src/migrations/index.ts +9 -0
  147. package/src/modules/filters.ts +13 -17
  148. package/src/modules/guards.ts +49 -26
  149. package/src/modules/index.ts +409 -298
  150. package/src/modules/interceptors.ts +58 -20
  151. package/src/modules/lazy.ts +11 -19
  152. package/src/modules/lifecycle.ts +15 -7
  153. package/src/modules/metadata.ts +15 -5
  154. package/src/modules/pipes.ts +94 -72
  155. package/src/notification/channels/base.ts +68 -0
  156. package/src/notification/channels/email.ts +105 -0
  157. package/src/notification/channels/push.ts +104 -0
  158. package/src/notification/channels/sms.ts +105 -0
  159. package/src/notification/channels/whatsapp.ts +104 -0
  160. package/src/notification/index.ts +48 -0
  161. package/src/notification/service.ts +354 -0
  162. package/src/notification/types.ts +344 -0
  163. package/src/observability/__tests__/observability.test.ts +483 -0
  164. package/src/observability/breadcrumbs.ts +114 -0
  165. package/src/observability/index.ts +136 -0
  166. package/src/observability/interceptor.ts +85 -0
  167. package/src/observability/service.ts +303 -0
  168. package/src/observability/trace.ts +37 -0
  169. package/src/observability/types.ts +196 -0
  170. package/src/openapi/__tests__/decorators.test.ts +335 -0
  171. package/src/openapi/__tests__/document-builder.test.ts +285 -0
  172. package/src/openapi/__tests__/route-scanner.test.ts +334 -0
  173. package/src/openapi/__tests__/schema-generator.test.ts +275 -0
  174. package/src/openapi/decorators.ts +328 -0
  175. package/src/openapi/document-builder.ts +274 -0
  176. package/src/openapi/index.ts +112 -0
  177. package/src/openapi/metadata.ts +112 -0
  178. package/src/openapi/route-scanner.ts +289 -0
  179. package/src/openapi/schema-generator.ts +256 -0
  180. package/src/openapi/swagger-module.ts +166 -0
  181. package/src/openapi/types.ts +398 -0
  182. package/src/orm/index.ts +10 -0
  183. package/src/rpc/index.ts +3 -1
  184. package/src/schema/index.ts +9 -0
  185. package/src/security/index.ts +15 -6
  186. package/src/ssg/index.ts +9 -8
  187. package/src/telemetry/index.ts +76 -22
  188. package/src/template/index.ts +7 -0
  189. package/src/templates/engine.ts +224 -0
  190. package/src/templates/index.ts +9 -0
  191. package/src/templates/loader.ts +331 -0
  192. package/src/templates/renderers/markdown.ts +212 -0
  193. package/src/templates/renderers/simple.ts +269 -0
  194. package/src/templates/types.ts +154 -0
  195. package/src/testing/index.ts +100 -27
  196. package/src/types/optional-deps.d.ts +347 -187
  197. package/src/validation/index.ts +92 -2
  198. package/src/validation/schemas.ts +536 -0
  199. package/tests/integration/fullstack.test.ts +4 -4
  200. package/tests/unit/database.test.ts +2 -72
  201. package/tests/unit/env-validation.test.ts +166 -0
  202. package/tests/unit/events.test.ts +910 -0
  203. package/tests/unit/i18n.test.ts +455 -0
  204. package/tests/unit/jobs.test.ts +493 -0
  205. package/tests/unit/notification.test.ts +988 -0
  206. package/tests/unit/observability.test.ts +453 -0
  207. package/tests/unit/orm/builder.test.ts +323 -0
  208. package/tests/unit/orm/casts.test.ts +179 -0
  209. package/tests/unit/orm/compiler.test.ts +220 -0
  210. package/tests/unit/orm/eager-loading.test.ts +285 -0
  211. package/tests/unit/orm/hooks.test.ts +191 -0
  212. package/tests/unit/orm/model.test.ts +373 -0
  213. package/tests/unit/orm/relationships.test.ts +303 -0
  214. package/tests/unit/orm/scopes.test.ts +74 -0
  215. package/tests/unit/templates-simple.test.ts +53 -0
  216. package/tests/unit/templates.test.ts +454 -0
  217. package/tests/unit/validation.test.ts +18 -24
  218. package/tsconfig.json +11 -3
@@ -8,16 +8,16 @@
8
8
  * - State serialization for islands
9
9
  */
10
10
 
11
- import { createLogger, type Logger } from "../logger/index.js";
11
+ import { type Logger, createLogger } from "../logger/index.js";
12
12
  import type {
13
+ FrontendFramework,
13
14
  IslandConfig,
14
15
  IslandDefinition,
16
+ IslandHydrationScript,
15
17
  IslandHydrationStrategy,
16
18
  IslandRegistry,
17
19
  IslandRenderResult,
18
20
  IslandState,
19
- IslandHydrationScript,
20
- FrontendFramework,
21
21
  SSRElement,
22
22
  } from "./types.js";
23
23
 
@@ -74,7 +74,7 @@ export class IslandManager {
74
74
  * Register multiple islands
75
75
  */
76
76
  registerAll(definitions: IslandDefinition[]): string[] {
77
- return definitions.map(def => this.register(def));
77
+ return definitions.map((def) => this.register(def));
78
78
  }
79
79
 
80
80
  /**
@@ -104,7 +104,7 @@ export class IslandManager {
104
104
  renderIsland(
105
105
  componentName: string,
106
106
  props: Record<string, unknown> = {},
107
- options: Partial<IslandConfig> = {}
107
+ options: Partial<IslandConfig> = {},
108
108
  ): IslandRenderResult {
109
109
  const id = options.id || `island-${++this.islandCounter}`;
110
110
  const strategy = options.strategy || "lazy";
@@ -139,7 +139,8 @@ export class IslandManager {
139
139
  .join(" ");
140
140
 
141
141
  // Generate placeholder or SSR content
142
- const placeholder = options.placeholder || this.generatePlaceholder(componentName, props);
142
+ const placeholder =
143
+ options.placeholder || this.generatePlaceholder(componentName, props);
143
144
 
144
145
  const html = `<div ${attrString}>${placeholder}</div>`;
145
146
 
@@ -160,7 +161,7 @@ export class IslandManager {
160
161
  componentName: string,
161
162
  ssrContent: string,
162
163
  props: Record<string, unknown> = {},
163
- options: Partial<IslandConfig> = {}
164
+ options: Partial<IslandConfig> = {},
164
165
  ): IslandRenderResult {
165
166
  const id = options.id || `island-${++this.islandCounter}`;
166
167
  const strategy = options.strategy || "lazy";
@@ -196,7 +197,9 @@ export class IslandManager {
196
197
  /**
197
198
  * Find island by component name
198
199
  */
199
- private findIslandByComponent(componentName: string): IslandDefinition | undefined {
200
+ private findIslandByComponent(
201
+ componentName: string,
202
+ ): IslandDefinition | undefined {
200
203
  for (const island of this.registry.values()) {
201
204
  if (island.component === componentName) {
202
205
  return island;
@@ -210,7 +213,7 @@ export class IslandManager {
210
213
  */
211
214
  private generatePlaceholder(
212
215
  componentName: string,
213
- props: Record<string, unknown>
216
+ props: Record<string, unknown>,
214
217
  ): string {
215
218
  // Generate a simple placeholder based on component type
216
219
  if (props.children && typeof props.children === "string") {
@@ -229,7 +232,7 @@ export class IslandManager {
229
232
  .replace(/&/g, "&")
230
233
  .replace(/</g, "<")
231
234
  .replace(/>/g, ">")
232
- .replace(/"/g, "\"")
235
+ .replace(/"/g, '"')
233
236
  .replace(/'/g, "'");
234
237
  }
235
238
 
@@ -240,7 +243,7 @@ export class IslandManager {
240
243
  const islands = this.getAllIslands();
241
244
  const framework = this.framework;
242
245
 
243
- const islandData = islands.map(island => ({
246
+ const islandData = islands.map((island) => ({
244
247
  id: island.id,
245
248
  component: island.component,
246
249
  entry: island.entry,
@@ -417,7 +420,9 @@ hydrator.init();
417
420
  /**
418
421
  * Create an island manager
419
422
  */
420
- export function createIslandManager(framework: FrontendFramework): IslandManager {
423
+ export function createIslandManager(
424
+ framework: FrontendFramework,
425
+ ): IslandManager {
421
426
  return new IslandManager(framework);
422
427
  }
423
428
 
@@ -429,7 +434,7 @@ export function createIslandManager(framework: FrontendFramework): IslandManager
429
434
  export function defineIsland(
430
435
  component: string,
431
436
  entry: string,
432
- options: Partial<IslandDefinition> = {}
437
+ options: Partial<IslandDefinition> = {},
433
438
  ): IslandDefinition {
434
439
  return {
435
440
  id: options.id || `island-${component}`,
@@ -467,7 +472,9 @@ export function getIslandData(element: ElementLike): IslandState | null {
467
472
  id: element.getAttribute(ISLAND_ID) || "",
468
473
  component: element.getAttribute(ISLAND_COMPONENT) || "",
469
474
  props: JSON.parse(element.getAttribute(ISLAND_PROPS) || "{}"),
470
- strategy: (element.getAttribute(ISLAND_STRATEGY) as IslandHydrationStrategy) || "lazy",
475
+ strategy:
476
+ (element.getAttribute(ISLAND_STRATEGY) as IslandHydrationStrategy) ||
477
+ "lazy",
471
478
  hydrated: false,
472
479
  };
473
480
  }
@@ -479,7 +486,7 @@ export function getIslandAttributes(
479
486
  id: string,
480
487
  component: string,
481
488
  props: Record<string, unknown>,
482
- strategy: IslandHydrationStrategy = "lazy"
489
+ strategy: IslandHydrationStrategy = "lazy",
483
490
  ): Record<string, string> {
484
491
  return {
485
492
  [ISLAND_MARKER]: "true",
@@ -498,7 +505,7 @@ export function createIslandElement(
498
505
  component: string,
499
506
  props: Record<string, unknown>,
500
507
  strategy: IslandHydrationStrategy = "lazy",
501
- children?: SSRElement[]
508
+ children?: SSRElement[],
502
509
  ): SSRElement {
503
510
  return {
504
511
  tag: "div",
@@ -512,14 +519,21 @@ export function createIslandElement(
512
519
  */
513
520
  export function parseIslandsFromHTML(html: string): IslandState[] {
514
521
  const islands: IslandState[] = [];
515
- const regex = /data-island-id="([^"]+)"[^>]*data-island-component="([^"]+)"[^>]*data-island-props="([^"]+)"[^>]*data-island-strategy="([^"]+)"/g;
522
+ const regex =
523
+ /data-island-id="([^"]+)"[^>]*data-island-component="([^"]+)"[^>]*data-island-props="([^"]+)"[^>]*data-island-strategy="([^"]+)"/g;
516
524
 
517
525
  let match;
518
526
  while ((match = regex.exec(html)) !== null) {
519
527
  islands.push({
520
528
  id: match[1],
521
529
  component: match[2],
522
- props: JSON.parse(match[3].replace(/"/g, '"').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')),
530
+ props: JSON.parse(
531
+ match[3]
532
+ .replace(/"/g, '"')
533
+ .replace(/&/g, "&")
534
+ .replace(/</g, "<")
535
+ .replace(/>/g, ">"),
536
+ ),
523
537
  strategy: match[4] as IslandHydrationStrategy,
524
538
  hydrated: false,
525
539
  });
@@ -531,7 +545,9 @@ export function parseIslandsFromHTML(html: string): IslandState[] {
531
545
  /**
532
546
  * Get hydration priority
533
547
  */
534
- export function getHydrationPriority(strategy: IslandHydrationStrategy): number {
548
+ export function getHydrationPriority(
549
+ strategy: IslandHydrationStrategy,
550
+ ): number {
535
551
  const priorities: Record<IslandHydrationStrategy, number> = {
536
552
  eager: 1,
537
553
  visible: 2,
@@ -545,8 +561,11 @@ export function getHydrationPriority(strategy: IslandHydrationStrategy): number
545
561
  /**
546
562
  * Sort islands by hydration priority
547
563
  */
548
- export function sortIslandsByPriority(islands: IslandDefinition[]): IslandDefinition[] {
564
+ export function sortIslandsByPriority(
565
+ islands: IslandDefinition[],
566
+ ): IslandDefinition[] {
549
567
  return [...islands].sort(
550
- (a, b) => getHydrationPriority(a.strategy) - getHydrationPriority(b.strategy)
568
+ (a, b) =>
569
+ getHydrationPriority(a.strategy) - getHydrationPriority(b.strategy),
551
570
  );
552
- }
571
+ }
@@ -8,18 +8,18 @@
8
8
  * - Distributed cache support via Bun.redis
9
9
  */
10
10
 
11
- import { createLogger, type Logger } from "../logger/index.js";
11
+ import { type Logger, createLogger } from "../logger/index.js";
12
+ import { type SSRRenderer, createSSRContext } from "./ssr.js";
12
13
  import type {
13
- ISRConfig,
14
- PartialISRConfig,
15
14
  ISRCacheEntry,
15
+ ISRConfig,
16
16
  ISRPageConfig,
17
17
  ISRRevalidationResult,
18
18
  ISRStats,
19
+ PartialISRConfig,
19
20
  SSRRenderOptions,
20
21
  } from "./types.js";
21
- import { SSRRenderer, createSSRContext } from "./ssr.js";
22
- import type { SSRContext, RenderResult } from "./types.js";
22
+ import type { RenderResult, SSRContext } from "./types.js";
23
23
 
24
24
  // ============= Constants =============
25
25
 
@@ -31,7 +31,7 @@ const DEFAULT_STALE_WHILE_REVALIDATE = 60; // 1 minute
31
31
 
32
32
  /**
33
33
  * ISR Manager handles incremental static regeneration
34
- *
34
+ *
35
35
  * Features:
36
36
  * - Time-based revalidation with configurable TTL
37
37
  * - Stale-while-revalidate for instant responses
@@ -42,7 +42,8 @@ export class ISRManager {
42
42
  private config: ISRConfig;
43
43
  private logger: Logger;
44
44
  private cache: Map<string, ISRCacheEntry> = new Map();
45
- private pendingRegenerations: Map<string, Promise<ISRRevalidationResult>> = new Map();
45
+ private pendingRegenerations: Map<string, Promise<ISRRevalidationResult>> =
46
+ new Map();
46
47
  private ssrRenderer: SSRRenderer | null = null;
47
48
  private stats = {
48
49
  hits: 0,
@@ -67,7 +68,8 @@ export class ISRManager {
67
68
  return {
68
69
  cacheDir: config.cacheDir ?? DEFAULT_CACHE_DIR,
69
70
  defaultRevalidate: config.defaultRevalidate ?? DEFAULT_REVALIDATE,
70
- staleWhileRevalidate: config.staleWhileRevalidate ?? DEFAULT_STALE_WHILE_REVALIDATE,
71
+ staleWhileRevalidate:
72
+ config.staleWhileRevalidate ?? DEFAULT_STALE_WHILE_REVALIDATE,
71
73
  maxCacheSize: config.maxCacheSize ?? 1000,
72
74
  redis: config.redis,
73
75
  redisKeyPrefix: config.redisKeyPrefix ?? "bueno:isr:",
@@ -88,7 +90,7 @@ export class ISRManager {
88
90
  async getPage(
89
91
  url: string,
90
92
  request: Request,
91
- pageConfig?: ISRPageConfig
93
+ pageConfig?: ISRPageConfig,
92
94
  ): Promise<RenderResult> {
93
95
  if (!this.config.enabled) {
94
96
  return this.renderPage(url, request);
@@ -100,8 +102,10 @@ export class ISRManager {
100
102
  if (entry) {
101
103
  const now = Date.now();
102
104
  const age = (now - entry.timestamp) / 1000;
103
- const revalidate = pageConfig?.revalidate ?? this.config.defaultRevalidate;
104
- const staleWhileRevalidate = pageConfig?.staleWhileRevalidate ?? this.config.staleWhileRevalidate;
105
+ const revalidate =
106
+ pageConfig?.revalidate ?? this.config.defaultRevalidate;
107
+ const staleWhileRevalidate =
108
+ pageConfig?.staleWhileRevalidate ?? this.config.staleWhileRevalidate;
105
109
 
106
110
  // Cache hit - check if stale
107
111
  if (age < revalidate) {
@@ -114,7 +118,9 @@ export class ISRManager {
114
118
  // Stale but within stale-while-revalidate window
115
119
  if (age < revalidate + staleWhileRevalidate) {
116
120
  this.stats.staleHits++;
117
- this.logger.debug(`Cache hit (stale): ${url}, revalidating in background`);
121
+ this.logger.debug(
122
+ `Cache hit (stale): ${url}, revalidating in background`,
123
+ );
118
124
 
119
125
  // Trigger background revalidation
120
126
  this.triggerBackgroundRevalidation(url, request, pageConfig);
@@ -137,7 +143,10 @@ export class ISRManager {
137
143
  /**
138
144
  * Render a page using SSR
139
145
  */
140
- private async renderPage(url: string, request: Request): Promise<RenderResult> {
146
+ private async renderPage(
147
+ url: string,
148
+ request: Request,
149
+ ): Promise<RenderResult> {
141
150
  if (!this.ssrRenderer) {
142
151
  throw new Error("SSR renderer not configured");
143
152
  }
@@ -159,7 +168,7 @@ export class ISRManager {
159
168
  private triggerBackgroundRevalidation(
160
169
  url: string,
161
170
  request: Request,
162
- pageConfig?: ISRPageConfig
171
+ pageConfig?: ISRPageConfig,
163
172
  ): void {
164
173
  const cacheKey = this.getCacheKey(url);
165
174
 
@@ -168,10 +177,11 @@ export class ISRManager {
168
177
  return;
169
178
  }
170
179
 
171
- const promise = this.revalidatePage(url, request, pageConfig)
172
- .finally(() => {
180
+ const promise = this.revalidatePage(url, request, pageConfig).finally(
181
+ () => {
173
182
  this.pendingRegenerations.delete(cacheKey);
174
- });
183
+ },
184
+ );
175
185
 
176
186
  this.pendingRegenerations.set(cacheKey, promise);
177
187
  }
@@ -182,7 +192,7 @@ export class ISRManager {
182
192
  async revalidatePage(
183
193
  url: string,
184
194
  request: Request,
185
- pageConfig?: ISRPageConfig
195
+ pageConfig?: ISRPageConfig,
186
196
  ): Promise<ISRRevalidationResult> {
187
197
  const cacheKey = this.getCacheKey(url);
188
198
  const startTime = Date.now();
@@ -205,7 +215,8 @@ export class ISRManager {
205
215
  };
206
216
  } catch (error) {
207
217
  const duration = Date.now() - startTime;
208
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
218
+ const errorMessage =
219
+ error instanceof Error ? error.message : "Unknown error";
209
220
  this.logger.error(`Revalidation failed: ${url}`, error);
210
221
 
211
222
  return {
@@ -258,7 +269,9 @@ export class ISRManager {
258
269
  // Invalidate Redis cache if available
259
270
  if (this.config.redis) {
260
271
  try {
261
- const keys = await this.config.redis.keys(`${this.config.redisKeyPrefix}*`);
272
+ const keys = await this.config.redis.keys(
273
+ `${this.config.redisKeyPrefix}*`,
274
+ );
262
275
  for (const key of keys) {
263
276
  const cacheKey = key.replace(this.config.redisKeyPrefix, "");
264
277
  if (regex.test(cacheKey)) {
@@ -284,7 +297,9 @@ export class ISRManager {
284
297
 
285
298
  if (this.config.redis) {
286
299
  try {
287
- const keys = await this.config.redis.keys(`${this.config.redisKeyPrefix}*`);
300
+ const keys = await this.config.redis.keys(
301
+ `${this.config.redisKeyPrefix}*`,
302
+ );
288
303
  if (keys.length > 0) {
289
304
  await this.config.redis.del(...keys);
290
305
  }
@@ -309,7 +324,9 @@ export class ISRManager {
309
324
  // Check Redis if available
310
325
  if (this.config.redis) {
311
326
  try {
312
- const data = await this.config.redis.get(`${this.config.redisKeyPrefix}${key}`);
327
+ const data = await this.config.redis.get(
328
+ `${this.config.redisKeyPrefix}${key}`,
329
+ );
313
330
  if (data) {
314
331
  const entry = JSON.parse(data) as ISRCacheEntry;
315
332
  // Cache locally for faster access
@@ -330,7 +347,7 @@ export class ISRManager {
330
347
  private async setCacheEntry(
331
348
  key: string,
332
349
  result: RenderResult,
333
- pageConfig?: ISRPageConfig
350
+ pageConfig?: ISRPageConfig,
334
351
  ): Promise<void> {
335
352
  const entry: ISRCacheEntry = {
336
353
  result,
@@ -354,7 +371,7 @@ export class ISRManager {
354
371
  await this.config.redis.set(
355
372
  `${this.config.redisKeyPrefix}${key}`,
356
373
  JSON.stringify(entry),
357
- { EX: ttl }
374
+ { EX: ttl },
358
375
  );
359
376
  } catch (error) {
360
377
  this.logger.error("Failed to set Redis cache entry", error);
@@ -366,10 +383,14 @@ export class ISRManager {
366
383
  * Evict oldest entries when cache is full
367
384
  */
368
385
  private evictOldestEntries(): void {
369
- const entries = Array.from(this.cache.entries())
370
- .sort((a, b) => a[1].timestamp - b[1].timestamp);
371
-
372
- const toEvict = entries.slice(0, Math.floor(this.config.maxCacheSize * 0.1));
386
+ const entries = Array.from(this.cache.entries()).sort(
387
+ (a, b) => a[1].timestamp - b[1].timestamp,
388
+ );
389
+
390
+ const toEvict = entries.slice(
391
+ 0,
392
+ Math.floor(this.config.maxCacheSize * 0.1),
393
+ );
373
394
  for (const [key] of toEvict) {
374
395
  this.cache.delete(key);
375
396
  }
@@ -398,9 +419,10 @@ export class ISRManager {
398
419
  ...this.stats,
399
420
  cacheSize: this.cache.size,
400
421
  pendingRevalidations: this.pendingRegenerations.size,
401
- hitRate: this.stats.hits + this.stats.misses > 0
402
- ? this.stats.hits / (this.stats.hits + this.stats.misses)
403
- : 0,
422
+ hitRate:
423
+ this.stats.hits + this.stats.misses > 0
424
+ ? this.stats.hits / (this.stats.hits + this.stats.misses)
425
+ : 0,
404
426
  };
405
427
  }
406
428
 
@@ -517,15 +539,15 @@ export function parseRevalidateHeader(header: string): {
517
539
  revalidate: number;
518
540
  staleWhileRevalidate: number;
519
541
  } {
520
- const parts = header.split(",").map(p => p.trim());
542
+ const parts = header.split(",").map((p) => p.trim());
521
543
  let revalidate = DEFAULT_REVALIDATE;
522
544
  let staleWhileRevalidate = DEFAULT_STALE_WHILE_REVALIDATE;
523
545
 
524
546
  for (const part of parts) {
525
547
  if (part.includes("stale-while-revalidate=")) {
526
- staleWhileRevalidate = parseInt(part.split("=")[1], 10);
548
+ staleWhileRevalidate = Number.parseInt(part.split("=")[1], 10);
527
549
  } else {
528
- revalidate = parseInt(part, 10);
550
+ revalidate = Number.parseInt(part, 10);
529
551
  }
530
552
  }
531
553
 
@@ -537,7 +559,7 @@ export function parseRevalidateHeader(header: string): {
537
559
  */
538
560
  export function generateCacheControlHeader(
539
561
  revalidate: number,
540
- staleWhileRevalidate: number
562
+ staleWhileRevalidate: number,
541
563
  ): string {
542
564
  return `public, max-age=0, s-maxage=${revalidate}, stale-while-revalidate=${staleWhileRevalidate}`;
543
565
  }
@@ -548,8 +570,8 @@ export function generateCacheControlHeader(
548
570
  export function shouldRegenerate(
549
571
  entry: ISRCacheEntry,
550
572
  revalidate: number,
551
- staleWhileRevalidate: number
573
+ staleWhileRevalidate: number,
552
574
  ): boolean {
553
575
  const age = (Date.now() - entry.timestamp) / 1000;
554
576
  return age > revalidate && age <= revalidate + staleWhileRevalidate;
555
- }
577
+ }
@@ -8,20 +8,20 @@
8
8
  * - Per-segment layouts
9
9
  */
10
10
 
11
- import { createLogger, type Logger } from "../logger/index.js";
11
+ import { type Logger, createLogger } from "../logger/index.js";
12
12
  import type {
13
+ LayoutConfig,
13
14
  LayoutDefinition,
15
+ LayoutMiddleware,
14
16
  LayoutNode,
15
- LayoutTree,
16
17
  LayoutProps,
17
- LayoutRenderer,
18
- LayoutMiddleware,
19
- LayoutConfig,
20
- PartialLayoutConfig,
21
18
  LayoutRenderResult,
19
+ LayoutRenderer,
22
20
  LayoutSegment,
21
+ LayoutTree,
22
+ PartialLayoutConfig,
23
23
  } from "./types.js";
24
- import type { SSRContext, SSRElement, RenderResult } from "./types.js";
24
+ import type { RenderResult, SSRContext, SSRElement } from "./types.js";
25
25
 
26
26
  // ============= Constants =============
27
27
 
@@ -70,7 +70,9 @@ export class LayoutManager {
70
70
  * Initialize the layout manager by scanning for layout files
71
71
  */
72
72
  async init(): Promise<void> {
73
- this.logger.info(`Initializing layout manager from: ${this.config.pagesDir}`);
73
+ this.logger.info(
74
+ `Initializing layout manager from: ${this.config.pagesDir}`,
75
+ );
74
76
  await this.scanLayoutFiles();
75
77
  this.buildLayoutTree();
76
78
  this.logger.info(`Loaded ${this.layouts.size} layouts`);
@@ -81,7 +83,9 @@ export class LayoutManager {
81
83
  */
82
84
  private async scanLayoutFiles(): Promise<void> {
83
85
  const pagesPath = this.config.pagesDir;
84
- const glob = new Bun.Glob(`**/${LAYOUT_FILE}{${this.config.extensions.join(",")}}`);
86
+ const glob = new Bun.Glob(
87
+ `**/${LAYOUT_FILE}{${this.config.extensions.join(",")}}`,
88
+ );
85
89
 
86
90
  try {
87
91
  for await (const file of glob.scan(pagesPath)) {
@@ -95,7 +99,10 @@ export class LayoutManager {
95
99
  /**
96
100
  * Process a single layout file
97
101
  */
98
- private async processLayoutFile(filePath: string, basePath: string): Promise<void> {
102
+ private async processLayoutFile(
103
+ filePath: string,
104
+ basePath: string,
105
+ ): Promise<void> {
99
106
  const fullPath = `${basePath}/${filePath}`;
100
107
  const segment = this.getLayoutSegment(filePath);
101
108
 
@@ -115,7 +122,10 @@ export class LayoutManager {
115
122
  */
116
123
  private getLayoutSegment(filePath: string): string {
117
124
  // Remove _layout.tsx from path
118
- const segment = filePath.replace(new RegExp(`/${LAYOUT_FILE}\\.(tsx?|jsx?)$`), "");
125
+ const segment = filePath.replace(
126
+ new RegExp(`/${LAYOUT_FILE}\\.(tsx?|jsx?)$`),
127
+ "",
128
+ );
119
129
  return segment === "" ? "/" : `/${segment}`;
120
130
  }
121
131
 
@@ -153,7 +163,10 @@ export class LayoutManager {
153
163
  /**
154
164
  * Build a layout tree node
155
165
  */
156
- private buildTreeNode(layout: LayoutDefinition, parent: LayoutNode | null): LayoutNode {
166
+ private buildTreeNode(
167
+ layout: LayoutDefinition,
168
+ parent: LayoutNode | null,
169
+ ): LayoutNode {
157
170
  const node: LayoutNode = {
158
171
  layout,
159
172
  parent,
@@ -224,7 +237,9 @@ export class LayoutManager {
224
237
  /**
225
238
  * Load layout module
226
239
  */
227
- private async loadLayoutModule(filePath: string): Promise<LayoutRenderer | null> {
240
+ private async loadLayoutModule(
241
+ filePath: string,
242
+ ): Promise<LayoutRenderer | null> {
228
243
  try {
229
244
  const module = await import(filePath);
230
245
  return module.default || module;
@@ -240,7 +255,7 @@ export class LayoutManager {
240
255
  async renderLayouts(
241
256
  routePath: string,
242
257
  content: string,
243
- context: SSRContext
258
+ context: SSRContext,
244
259
  ): Promise<LayoutRenderResult> {
245
260
  const chain = this.getLayoutChain(routePath);
246
261
 
@@ -361,7 +376,9 @@ export class LayoutManager {
361
376
  /**
362
377
  * Create a layout manager
363
378
  */
364
- export function createLayoutManager(config: PartialLayoutConfig = {}): LayoutManager {
379
+ export function createLayoutManager(
380
+ config: PartialLayoutConfig = {},
381
+ ): LayoutManager {
365
382
  return new LayoutManager(config);
366
383
  }
367
384
 
@@ -394,7 +411,7 @@ export function getLayoutSegmentFromPath(filePath: string): string {
394
411
  */
395
412
  export function buildLayoutProps(
396
413
  children: string,
397
- context: SSRContext
414
+ context: SSRContext,
398
415
  ): LayoutProps {
399
416
  return {
400
417
  children,
@@ -409,7 +426,7 @@ export function buildLayoutProps(
409
426
  */
410
427
  export function createLayoutSegment(
411
428
  path: string,
412
- params: Record<string, string> = {}
429
+ params: Record<string, string> = {},
413
430
  ): LayoutSegment {
414
431
  return {
415
432
  path,
@@ -421,9 +438,7 @@ export function createLayoutSegment(
421
438
  /**
422
439
  * Merge layout head elements
423
440
  */
424
- export function mergeLayoutHead(
425
- ...heads: SSRElement[][]
426
- ): SSRElement[] {
441
+ export function mergeLayoutHead(...heads: SSRElement[][]): SSRElement[] {
427
442
  const merged: SSRElement[] = [];
428
443
  const seen = new Set<string>();
429
444
 
@@ -472,4 +487,4 @@ export function layoutTreeToString(node: LayoutNode, indent = 0): string {
472
487
  }
473
488
 
474
489
  return result;
475
- }
490
+ }