@farm.js/core 0.1.0-beta.3 → 0.1.0-beta.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 (193) hide show
  1. package/dist/agent-runtime.d.mts +2 -1
  2. package/dist/agent-runtime.d.ts +2 -1
  3. package/dist/api/route-manager.cjs +313 -10
  4. package/dist/api/route-manager.cjs.map +1 -1
  5. package/dist/api/route-manager.d.mts +3 -2
  6. package/dist/api/route-manager.d.ts +3 -2
  7. package/dist/api/route-manager.mjs +6 -5
  8. package/dist/api/runtime.cjs +313 -10
  9. package/dist/api/runtime.cjs.map +1 -1
  10. package/dist/api/runtime.d.mts +2 -1
  11. package/dist/api/runtime.d.ts +2 -1
  12. package/dist/api/runtime.mjs +4 -3
  13. package/dist/api.cjs +642 -57
  14. package/dist/api.cjs.map +1 -1
  15. package/dist/api.d.mts +3 -2
  16. package/dist/api.d.ts +3 -2
  17. package/dist/api.mjs +29 -9
  18. package/dist/{app-DOKxo-LH.d.ts → app-BjN-da13.d.ts} +38 -3
  19. package/dist/{app-BB3Iu3xh.d.mts → app-CakAr8Be.d.mts} +38 -3
  20. package/dist/app-markdown.d.mts +2 -1
  21. package/dist/app-markdown.d.ts +2 -1
  22. package/dist/cache.cjs +395 -12
  23. package/dist/cache.cjs.map +1 -1
  24. package/dist/cache.d.mts +100 -6
  25. package/dist/cache.d.ts +100 -6
  26. package/dist/cache.mjs +8 -2
  27. package/dist/{chunk-XUBZ7JI4.mjs → chunk-2CUKEMVR.mjs} +187 -16
  28. package/dist/chunk-2CUKEMVR.mjs.map +1 -0
  29. package/dist/{chunk-OHC6VCDI.mjs → chunk-2NHCNRF6.mjs} +521 -42
  30. package/dist/chunk-2NHCNRF6.mjs.map +1 -0
  31. package/dist/{chunk-6WXJ54TC.mjs → chunk-65A7QIKI.mjs} +5 -5
  32. package/dist/{chunk-6WXJ54TC.mjs.map → chunk-65A7QIKI.mjs.map} +1 -1
  33. package/dist/{chunk-5QH4IKY3.mjs → chunk-7N62DNSO.mjs} +2 -2
  34. package/dist/{chunk-XJSHJN4M.mjs → chunk-A2LUXHD2.mjs} +593 -49
  35. package/dist/chunk-A2LUXHD2.mjs.map +1 -0
  36. package/dist/chunk-ALCLXXTY.mjs +187 -0
  37. package/dist/chunk-ALCLXXTY.mjs.map +1 -0
  38. package/dist/{chunk-CSU4KJJF.mjs → chunk-C2ROET2V.mjs} +2 -2
  39. package/dist/{chunk-JNYVN7YP.mjs → chunk-EEABSBKK.mjs} +2 -2
  40. package/dist/{chunk-JNYVN7YP.mjs.map → chunk-EEABSBKK.mjs.map} +1 -1
  41. package/dist/{chunk-BIOIOWXZ.mjs → chunk-HNIQFZKB.mjs} +2 -2
  42. package/dist/{chunk-MH6FKW2I.mjs → chunk-J5YWQVI5.mjs} +4 -4
  43. package/dist/chunk-J5YWQVI5.mjs.map +1 -0
  44. package/dist/{chunk-G5I34UA6.mjs → chunk-JDEOUK5V.mjs} +4 -4
  45. package/dist/{chunk-T543TMFY.mjs → chunk-NM3CRAAL.mjs} +3 -2
  46. package/dist/chunk-NM3CRAAL.mjs.map +1 -0
  47. package/dist/{chunk-LWMGIEJA.mjs → chunk-PFE6GMNE.mjs} +47 -37
  48. package/dist/chunk-PFE6GMNE.mjs.map +1 -0
  49. package/dist/{chunk-6VS6CKAV.mjs → chunk-QM746KUO.mjs} +8 -1
  50. package/dist/chunk-QM746KUO.mjs.map +1 -0
  51. package/dist/{chunk-5FW6PQ3J.mjs → chunk-QTZZ2ISQ.mjs} +41 -16
  52. package/dist/chunk-QTZZ2ISQ.mjs.map +1 -0
  53. package/dist/{chunk-SHRJG6OG.mjs → chunk-RBRLLZL5.mjs} +18 -3
  54. package/dist/chunk-RBRLLZL5.mjs.map +1 -0
  55. package/dist/{chunk-NRRFGX45.mjs → chunk-TQAEEJB4.mjs} +3 -3
  56. package/dist/{chunk-45LKHSHJ.mjs → chunk-UB5UOSHB.mjs} +24 -12
  57. package/dist/chunk-UB5UOSHB.mjs.map +1 -0
  58. package/dist/chunk-V3XURYJO.mjs +756 -0
  59. package/dist/chunk-V3XURYJO.mjs.map +1 -0
  60. package/dist/{chunk-HZYXGV4K.mjs → chunk-VY2UYN5R.mjs} +24 -4
  61. package/dist/chunk-VY2UYN5R.mjs.map +1 -0
  62. package/dist/{chunk-C33RALN7.mjs → chunk-W2CRQFF6.mjs} +79 -15
  63. package/dist/chunk-W2CRQFF6.mjs.map +1 -0
  64. package/dist/{chunk-LG3YINQN.mjs → chunk-Z5CQMP54.mjs} +3 -3
  65. package/dist/{client-DiViK9yK.d.mts → client-BT1mFQas.d.mts} +23 -4
  66. package/dist/{client-DpEBJbts.d.ts → client-Nn0q4eO4.d.ts} +23 -4
  67. package/dist/{client-plugins-CTnsZz_A.d.ts → client-plugins-Cw4qUGsw.d.ts} +6 -0
  68. package/dist/{client-plugins-Cnr0D3kk.d.mts → client-plugins-DQQ1adXM.d.mts} +6 -0
  69. package/dist/client-plugins.d.mts +2 -1
  70. package/dist/client-plugins.d.ts +2 -1
  71. package/dist/client.cjs +665 -82
  72. package/dist/client.cjs.map +1 -1
  73. package/dist/client.d.mts +71 -5
  74. package/dist/client.d.ts +71 -5
  75. package/dist/client.mjs +10 -7
  76. package/dist/config.d.mts +2 -1
  77. package/dist/config.d.ts +2 -1
  78. package/dist/{devtools-HE4FTUDN.mjs → devtools-LVC3VB5P.mjs} +4 -4
  79. package/dist/docs.cjs +11 -1
  80. package/dist/docs.cjs.map +1 -1
  81. package/dist/docs.mjs +4 -4
  82. package/dist/index.cjs +2113 -198
  83. package/dist/index.cjs.map +1 -1
  84. package/dist/index.d.mts +16 -16
  85. package/dist/index.d.ts +16 -16
  86. package/dist/index.mjs +49 -21
  87. package/dist/index.mjs.map +1 -1
  88. package/dist/integrations.d.mts +2 -1
  89. package/dist/integrations.d.ts +2 -1
  90. package/dist/internal/build-runtime.cjs +1455 -90
  91. package/dist/internal/build-runtime.cjs.map +1 -1
  92. package/dist/internal/build-runtime.d.mts +4 -3
  93. package/dist/internal/build-runtime.d.ts +4 -3
  94. package/dist/internal/build-runtime.mjs +15 -14
  95. package/dist/internal/config-runtime.cjs +12 -1
  96. package/dist/internal/config-runtime.cjs.map +1 -1
  97. package/dist/internal/config-runtime.d.mts +3 -2
  98. package/dist/internal/config-runtime.d.ts +3 -2
  99. package/dist/internal/config-runtime.mjs +3 -3
  100. package/dist/internal/production-runtime.cjs +282 -6
  101. package/dist/internal/production-runtime.cjs.map +1 -1
  102. package/dist/internal/production-runtime.d.mts +4 -4
  103. package/dist/internal/production-runtime.d.ts +4 -4
  104. package/dist/internal/production-runtime.mjs +4 -2
  105. package/dist/{metadata-eoVmHoyv.d.ts → metadata-BAPg_Nmo.d.ts} +1 -1
  106. package/dist/{metadata-Dp8AdvsB.d.mts → metadata-_6i9oFG3.d.mts} +1 -1
  107. package/dist/middleware.d.mts +4 -3
  108. package/dist/middleware.d.ts +4 -3
  109. package/dist/navigation.cjs +40 -15
  110. package/dist/navigation.cjs.map +1 -1
  111. package/dist/navigation.mjs +2 -2
  112. package/dist/plugin.cjs +913 -53
  113. package/dist/plugin.cjs.map +1 -1
  114. package/dist/plugin.d.mts +2 -1
  115. package/dist/plugin.d.ts +2 -1
  116. package/dist/plugin.mjs +11 -10
  117. package/dist/{production-runtime-BqTnUTai.d.ts → production-runtime-BoJDc5mc.d.ts} +1 -1
  118. package/dist/{production-runtime-DtDdG9fc.d.mts → production-runtime-DPfgMV9Y.d.mts} +1 -1
  119. package/dist/routes.cjs +276 -6
  120. package/dist/routes.cjs.map +1 -1
  121. package/dist/routes.d.mts +1 -1
  122. package/dist/routes.d.ts +1 -1
  123. package/dist/routes.mjs +3 -3
  124. package/dist/{runtime-C1B7pSGT.d.mts → runtime-9nQjVM7X.d.mts} +8 -9
  125. package/dist/{runtime-CdPHt0bv.d.ts → runtime-DgeS2skB.d.ts} +8 -9
  126. package/dist/server-action-security.cjs +28 -10
  127. package/dist/server-action-security.cjs.map +1 -1
  128. package/dist/server-action-security.d.mts +2 -1
  129. package/dist/server-action-security.d.ts +2 -1
  130. package/dist/server-action-security.mjs +2 -2
  131. package/dist/server-fn-client.d.mts +1 -0
  132. package/dist/server-fn-client.d.ts +1 -0
  133. package/dist/server-fn.cjs +785 -3
  134. package/dist/server-fn.cjs.map +1 -1
  135. package/dist/server-fn.d.mts +10 -1
  136. package/dist/server-fn.d.ts +10 -1
  137. package/dist/server-fn.mjs +6 -3
  138. package/dist/server-plugins.d.mts +3 -2
  139. package/dist/server-plugins.d.ts +3 -2
  140. package/dist/server-query-client.cjs +279 -8
  141. package/dist/server-query-client.cjs.map +1 -1
  142. package/dist/server-query-client.mjs +4 -4
  143. package/dist/server-query.cjs +381 -10
  144. package/dist/server-query.cjs.map +1 -1
  145. package/dist/server-query.mjs +5 -5
  146. package/dist/server.cjs +1086 -76
  147. package/dist/server.cjs.map +1 -1
  148. package/dist/server.d.mts +8 -7
  149. package/dist/server.d.ts +8 -7
  150. package/dist/server.mjs +31 -16
  151. package/dist/server.mjs.map +1 -1
  152. package/dist/testing.cjs +341 -20
  153. package/dist/testing.cjs.map +1 -1
  154. package/dist/testing.d.mts +3 -2
  155. package/dist/testing.d.ts +3 -2
  156. package/dist/testing.mjs +7 -6
  157. package/dist/testing.mjs.map +1 -1
  158. package/dist/transport-BmbGTXA5.d.mts +49 -0
  159. package/dist/transport-BmbGTXA5.d.ts +49 -0
  160. package/dist/{utils-8USa5BiY.d.mts → utils-CHXlMDkg.d.mts} +1 -1
  161. package/dist/{utils-DKJb6K6G.d.ts → utils-Drsyz1Vj.d.ts} +1 -1
  162. package/dist/version.cjs +1 -1
  163. package/dist/version.cjs.map +1 -1
  164. package/dist/version.mjs +1 -1
  165. package/dist/vite.cjs +914 -54
  166. package/dist/vite.cjs.map +1 -1
  167. package/dist/vite.d.mts +2 -1
  168. package/dist/vite.d.ts +2 -1
  169. package/dist/vite.mjs +11 -10
  170. package/package.json +1 -1
  171. package/types/api.d.ts +44 -0
  172. package/types/client.d.ts +85 -4
  173. package/dist/chunk-45LKHSHJ.mjs.map +0 -1
  174. package/dist/chunk-5FW6PQ3J.mjs.map +0 -1
  175. package/dist/chunk-6VS6CKAV.mjs.map +0 -1
  176. package/dist/chunk-C33RALN7.mjs.map +0 -1
  177. package/dist/chunk-GZWT6VI5.mjs +0 -382
  178. package/dist/chunk-GZWT6VI5.mjs.map +0 -1
  179. package/dist/chunk-HZYXGV4K.mjs.map +0 -1
  180. package/dist/chunk-LWMGIEJA.mjs.map +0 -1
  181. package/dist/chunk-MH6FKW2I.mjs.map +0 -1
  182. package/dist/chunk-OHC6VCDI.mjs.map +0 -1
  183. package/dist/chunk-SHRJG6OG.mjs.map +0 -1
  184. package/dist/chunk-T543TMFY.mjs.map +0 -1
  185. package/dist/chunk-XJSHJN4M.mjs.map +0 -1
  186. package/dist/chunk-XUBZ7JI4.mjs.map +0 -1
  187. /package/dist/{chunk-5QH4IKY3.mjs.map → chunk-7N62DNSO.mjs.map} +0 -0
  188. /package/dist/{chunk-CSU4KJJF.mjs.map → chunk-C2ROET2V.mjs.map} +0 -0
  189. /package/dist/{chunk-BIOIOWXZ.mjs.map → chunk-HNIQFZKB.mjs.map} +0 -0
  190. /package/dist/{chunk-G5I34UA6.mjs.map → chunk-JDEOUK5V.mjs.map} +0 -0
  191. /package/dist/{chunk-NRRFGX45.mjs.map → chunk-TQAEEJB4.mjs.map} +0 -0
  192. /package/dist/{chunk-LG3YINQN.mjs.map → chunk-Z5CQMP54.mjs.map} +0 -0
  193. /package/dist/{devtools-HE4FTUDN.mjs.map → devtools-LVC3VB5P.mjs.map} +0 -0
package/dist/plugin.cjs CHANGED
@@ -1958,7 +1958,8 @@ var init_route_rules = __esm({
1958
1958
  // src/cache-invalidation.ts
1959
1959
  function getFarmCacheInvalidationState() {
1960
1960
  return globalState[FARM_CACHE_INVALIDATION_STATE] ?? (globalState[FARM_CACHE_INVALIDATION_STATE] = {
1961
- listeners: /* @__PURE__ */ new Set()
1961
+ listeners: /* @__PURE__ */ new Set(),
1962
+ taskListeners: /* @__PURE__ */ new Set()
1962
1963
  });
1963
1964
  }
1964
1965
  function encodeFarmCacheInvalidations(keys) {
@@ -1984,6 +1985,11 @@ function subscribeFarmCacheInvalidation(listener) {
1984
1985
  state.listeners.add(listener);
1985
1986
  return () => state.listeners.delete(listener);
1986
1987
  }
1988
+ function subscribeFarmCacheTask(listener) {
1989
+ const state = getFarmCacheInvalidationState();
1990
+ state.taskListeners.add(listener);
1991
+ return () => state.taskListeners.delete(listener);
1992
+ }
1987
1993
  var FARM_CACHE_INVALIDATION_HEADER, FARM_CACHE_INVALIDATION_STATE, globalState;
1988
1994
  var init_cache_invalidation = __esm({
1989
1995
  "src/cache-invalidation.ts"() {
@@ -1995,6 +2001,7 @@ var init_cache_invalidation = __esm({
1995
2001
  __name(encodeFarmCacheInvalidations, "encodeFarmCacheInvalidations");
1996
2002
  __name(decodeFarmCacheInvalidations, "decodeFarmCacheInvalidations");
1997
2003
  __name(subscribeFarmCacheInvalidation, "subscribeFarmCacheInvalidation");
2004
+ __name(subscribeFarmCacheTask, "subscribeFarmCacheTask");
1998
2005
  }
1999
2006
  });
2000
2007
 
@@ -2098,6 +2105,9 @@ var init_server_action_security = __esm({
2098
2105
  subscribeFarmCacheInvalidation((key) => {
2099
2106
  getServerActionExecutionContext()?.invalidations.add(key);
2100
2107
  });
2108
+ subscribeFarmCacheTask((task) => {
2109
+ getServerActionExecutionContext()?.cacheTasks.add(task);
2110
+ });
2101
2111
  __name(getServerActionStorage, "getServerActionStorage");
2102
2112
  __name(parseBodySizeLimit, "parseBodySizeLimit");
2103
2113
  __name(normalizeAllowedOriginPattern, "normalizeAllowedOriginPattern");
@@ -11667,11 +11677,51 @@ __name(getActiveFarmI18nSnapshot, "getActiveFarmI18nSnapshot");
11667
11677
  // src/cache.ts
11668
11678
  init_cache_invalidation();
11669
11679
  var _FarmDataCache = class _FarmDataCache {
11670
- constructor() {
11680
+ constructor(config = {}) {
11671
11681
  this.entries = /* @__PURE__ */ new Map();
11672
11682
  this.inflight = /* @__PURE__ */ new Map();
11673
11683
  this.invalidatedTagVersions = /* @__PURE__ */ new Map();
11674
11684
  this.version = 0;
11685
+ this.namespace = "farm";
11686
+ this.local = true;
11687
+ this.lease = {
11688
+ enabled: true,
11689
+ ttlMs: 1e4,
11690
+ waitTimeoutMs: 1e4,
11691
+ pollIntervalMs: 25
11692
+ };
11693
+ this.configure(config);
11694
+ }
11695
+ configure(config = {}) {
11696
+ this.adapter = config.adapter;
11697
+ this.namespace = normalizeCacheNamespace(config.namespace || "farm");
11698
+ this.local = !config.adapter;
11699
+ this.lease = config.lease === false ? { ...this.lease, enabled: false } : {
11700
+ enabled: true,
11701
+ ttlMs: normalizePositiveDuration(config.lease?.ttlMs, 1e4, "cache.lease.ttlMs"),
11702
+ waitTimeoutMs: normalizePositiveDuration(
11703
+ config.lease?.waitTimeoutMs,
11704
+ 1e4,
11705
+ "cache.lease.waitTimeoutMs"
11706
+ ),
11707
+ pollIntervalMs: normalizePositiveDuration(
11708
+ config.lease?.pollIntervalMs,
11709
+ 25,
11710
+ "cache.lease.pollIntervalMs"
11711
+ )
11712
+ };
11713
+ if (!this.local) {
11714
+ this.entries.clear();
11715
+ this.inflight.clear();
11716
+ this.invalidatedTagVersions.clear();
11717
+ this.version = 0;
11718
+ }
11719
+ }
11720
+ get adapterName() {
11721
+ return this.adapter?.name || (this.adapter ? "custom" : "memory");
11722
+ }
11723
+ get hasAdapter() {
11724
+ return this.adapter !== void 0;
11675
11725
  }
11676
11726
  get size() {
11677
11727
  return this.entries.size;
@@ -11707,6 +11757,45 @@ var _FarmDataCache = class _FarmDataCache {
11707
11757
  });
11708
11758
  return this.toPublicEntry(entry);
11709
11759
  }
11760
+ async getEntryAsync(key, options = {}) {
11761
+ if (this.local) {
11762
+ const localEntry = this.getEntry(key, options);
11763
+ if (localEntry) return localEntry;
11764
+ }
11765
+ if (!this.adapter) {
11766
+ return this.local ? void 0 : this.getEntry(key, options);
11767
+ }
11768
+ const entry = await this.adapter.get(this.createAdapterKey(key));
11769
+ if (!entry) {
11770
+ emitFarmEvent({ type: "cache.miss", key });
11771
+ return void 0;
11772
+ }
11773
+ assertFarmCacheEntry(entry, key);
11774
+ const stale = await this.isAdapterEntryStale(entry, options.now);
11775
+ if (stale) {
11776
+ emitFarmEvent({
11777
+ type: "cache.stale",
11778
+ key,
11779
+ tags: [...entry.tags],
11780
+ revalidate: entry.revalidate
11781
+ });
11782
+ if (!options.allowStale) {
11783
+ emitFarmEvent({ type: "cache.miss", key, reason: "stale" });
11784
+ return void 0;
11785
+ }
11786
+ }
11787
+ emitFarmEvent({
11788
+ type: "cache.hit",
11789
+ key,
11790
+ tags: [...entry.tags],
11791
+ revalidate: entry.revalidate,
11792
+ stale
11793
+ });
11794
+ if (this.local && !stale) {
11795
+ this.hydrateLocalEntry(entry);
11796
+ }
11797
+ return { ...entry, key };
11798
+ }
11710
11799
  set(key, value, options = {}) {
11711
11800
  const tags = /* @__PURE__ */ new Set();
11712
11801
  for (const tag of options.tags ?? []) {
@@ -11719,6 +11808,7 @@ var _FarmDataCache = class _FarmDataCache {
11719
11808
  key,
11720
11809
  value,
11721
11810
  tags,
11811
+ tagVersions: void 0,
11722
11812
  createdAt: options.createdAt ?? Date.now(),
11723
11813
  createdVersion: ++this.version,
11724
11814
  revalidate: normalizeRevalidate(options.revalidate)
@@ -11732,11 +11822,45 @@ var _FarmDataCache = class _FarmDataCache {
11732
11822
  });
11733
11823
  return this.toPublicEntry(entry);
11734
11824
  }
11825
+ async setAsync(key, value, options = {}, tagVersions) {
11826
+ const tags = normalizeCacheOptionsTags(options);
11827
+ const capturedVersions = tagVersions ?? await this.getAdapterTagVersions(Array.from(tags.values()));
11828
+ const entry = {
11829
+ key,
11830
+ value,
11831
+ tags: Array.from(tags),
11832
+ tagVersions: capturedVersions,
11833
+ createdAt: options.createdAt ?? Date.now(),
11834
+ createdVersion: ++this.version,
11835
+ revalidate: normalizeRevalidate(options.revalidate)
11836
+ };
11837
+ if (this.local) {
11838
+ this.hydrateLocalEntry(entry);
11839
+ }
11840
+ if (this.adapter) {
11841
+ await this.adapter.set(this.createAdapterKey(key), entry);
11842
+ }
11843
+ emitFarmEvent({
11844
+ type: "cache.set",
11845
+ key,
11846
+ tags: [...entry.tags],
11847
+ revalidate: entry.revalidate
11848
+ });
11849
+ return entry;
11850
+ }
11735
11851
  delete(key) {
11736
11852
  const deleted = this.entries.delete(key);
11737
11853
  emitFarmEvent({ type: "cache.delete", key, deleted });
11738
11854
  return deleted;
11739
11855
  }
11856
+ async deleteAsync(key) {
11857
+ const deleted = this.entries.delete(key);
11858
+ if (this.adapter) {
11859
+ await this.adapter.delete(this.createAdapterKey(key));
11860
+ }
11861
+ emitFarmEvent({ type: "cache.delete", key, deleted: this.adapter ? true : deleted });
11862
+ return this.adapter ? true : deleted;
11863
+ }
11740
11864
  clear() {
11741
11865
  const count = this.entries.size;
11742
11866
  this.entries.clear();
@@ -11745,6 +11869,10 @@ var _FarmDataCache = class _FarmDataCache {
11745
11869
  this.version = 0;
11746
11870
  emitFarmEvent({ type: "cache.clear", count });
11747
11871
  }
11872
+ async clearAsync() {
11873
+ this.clear();
11874
+ await this.adapter?.clear?.();
11875
+ }
11748
11876
  isStale(entry, now = Date.now()) {
11749
11877
  if (typeof entry.revalidate === "number" && entry.revalidate > 0 && now - entry.createdAt >= entry.revalidate * 1e3) {
11750
11878
  return true;
@@ -11757,6 +11885,12 @@ var _FarmDataCache = class _FarmDataCache {
11757
11885
  }
11758
11886
  return false;
11759
11887
  }
11888
+ async isStaleAsync(entry, now = Date.now()) {
11889
+ if (this.adapter) {
11890
+ return this.isAdapterEntryStale(entry, now);
11891
+ }
11892
+ return this.isStale(entry, now);
11893
+ }
11760
11894
  revalidateTag(tag, options = {}) {
11761
11895
  const normalized = normalizeCacheTag(tag);
11762
11896
  const count = this.invalidateTag(normalized);
@@ -11765,6 +11899,15 @@ var _FarmDataCache = class _FarmDataCache {
11765
11899
  );
11766
11900
  return count;
11767
11901
  }
11902
+ async revalidateTagAsync(tag, options = {}) {
11903
+ const normalized = normalizeCacheTag(tag);
11904
+ const count = this.invalidateTag(normalized);
11905
+ await this.adapter?.invalidateTags?.([this.createAdapterTag(normalized)]);
11906
+ emitFarmEvent(
11907
+ options.source === "updateTag" ? { type: "cache.updateTag", tag: normalized, count } : { type: "cache.revalidateTag", tag: normalized, profile: options.profile, count }
11908
+ );
11909
+ return count;
11910
+ }
11768
11911
  revalidatePath(routePath) {
11769
11912
  const normalizedPath = normalizeRevalidatePath(routePath);
11770
11913
  const pathTag = createPathCacheTag(normalizedPath);
@@ -11781,8 +11924,25 @@ var _FarmDataCache = class _FarmDataCache {
11781
11924
  }
11782
11925
  return count;
11783
11926
  }
11927
+ async revalidatePathAsync(routePath) {
11928
+ const normalizedPath = normalizeRevalidatePath(routePath);
11929
+ const pathTag = createPathCacheTag(normalizedPath);
11930
+ const pprCount = this.countEntriesForTags([pathTag, "ppr"]);
11931
+ const count = this.invalidateTag(pathTag);
11932
+ await this.adapter?.invalidateTags?.([this.createAdapterTag(pathTag)]);
11933
+ emitFarmEvent({ type: "cache.revalidatePath", path: normalizedPath, count });
11934
+ if (pprCount > 0) {
11935
+ emitFarmEvent({
11936
+ type: "ppr.shell.invalidated",
11937
+ route: normalizedPath,
11938
+ reason: "revalidatePath",
11939
+ count: pprCount
11940
+ });
11941
+ }
11942
+ return count;
11943
+ }
11784
11944
  async getOrSet(key, producer, options = {}) {
11785
- const cached = this.getEntry(key);
11945
+ const cached = await this.getEntryAsync(key);
11786
11946
  if (cached) {
11787
11947
  return cached.value;
11788
11948
  }
@@ -11791,10 +11951,8 @@ var _FarmDataCache = class _FarmDataCache {
11791
11951
  emitFarmEvent({ type: "cache.dedupe", key });
11792
11952
  return inflight;
11793
11953
  }
11794
- const promise = Promise.resolve().then(producer).then((value) => {
11795
- this.set(key, value, options);
11796
- return value;
11797
- }).catch((error) => {
11954
+ const tags = Array.from(normalizeCacheOptionsTags(options));
11955
+ const promise = this.fillCacheEntry(key, producer, options, tags).catch((error) => {
11798
11956
  emitFarmEvent({ type: "cache.error", key, operation: "set", error });
11799
11957
  throw error;
11800
11958
  }).finally(() => {
@@ -11803,6 +11961,40 @@ var _FarmDataCache = class _FarmDataCache {
11803
11961
  this.inflight.set(key, promise);
11804
11962
  return promise;
11805
11963
  }
11964
+ async fillCacheEntry(key, producer, options, tags) {
11965
+ const leaseKey = `${this.namespace}:lease:${key}`;
11966
+ let leaseToken;
11967
+ if (this.adapter?.acquireLease && this.adapter.releaseLease && this.lease.enabled) {
11968
+ leaseToken = await this.adapter.acquireLease(leaseKey, this.lease.ttlMs);
11969
+ if (!leaseToken) {
11970
+ const shared = await this.waitForAdapterEntry(key);
11971
+ if (shared) {
11972
+ emitFarmEvent({ type: "cache.dedupe", key });
11973
+ return shared.value;
11974
+ }
11975
+ leaseToken = await this.adapter.acquireLease(leaseKey, this.lease.ttlMs);
11976
+ }
11977
+ }
11978
+ try {
11979
+ const initialTagVersions = await this.getAdapterTagVersions(tags);
11980
+ const value = await producer();
11981
+ await this.setAsync(key, value, options, initialTagVersions);
11982
+ return value;
11983
+ } finally {
11984
+ if (leaseToken && this.adapter?.releaseLease) {
11985
+ await this.adapter.releaseLease(leaseKey, leaseToken);
11986
+ }
11987
+ }
11988
+ }
11989
+ async waitForAdapterEntry(key) {
11990
+ const deadline = Date.now() + this.lease.waitTimeoutMs;
11991
+ while (Date.now() < deadline) {
11992
+ await delay(this.lease.pollIntervalMs);
11993
+ const entry = await this.getEntryAsync(key);
11994
+ if (entry) return entry;
11995
+ }
11996
+ return void 0;
11997
+ }
11806
11998
  countEntriesForTag(tag) {
11807
11999
  let count = 0;
11808
12000
  for (const entry of this.entries.values()) {
@@ -11830,11 +12022,53 @@ var _FarmDataCache = class _FarmDataCache {
11830
12022
  key: entry.key,
11831
12023
  value: entry.value,
11832
12024
  tags: Array.from(entry.tags),
12025
+ tagVersions: entry.tagVersions,
11833
12026
  createdAt: entry.createdAt,
11834
12027
  createdVersion: entry.createdVersion,
11835
12028
  revalidate: entry.revalidate
11836
12029
  };
11837
12030
  }
12031
+ hydrateLocalEntry(entry) {
12032
+ this.entries.set(entry.key, {
12033
+ key: entry.key,
12034
+ value: entry.value,
12035
+ tags: new Set(entry.tags.map(normalizeCacheTag)),
12036
+ tagVersions: entry.tagVersions,
12037
+ createdAt: entry.createdAt,
12038
+ createdVersion: entry.createdVersion ?? ++this.version,
12039
+ revalidate: normalizeRevalidate(entry.revalidate)
12040
+ });
12041
+ }
12042
+ createAdapterKey(key) {
12043
+ return `${this.namespace}:entry:${key}`;
12044
+ }
12045
+ createAdapterTag(tag) {
12046
+ return `${this.namespace}:tag:${tag}`;
12047
+ }
12048
+ async getAdapterTagVersions(tags) {
12049
+ if (!this.adapter?.getTagVersions || tags.length === 0) return {};
12050
+ const normalized = tags.map(normalizeCacheTag);
12051
+ const physicalTags = normalized.map((tag) => this.createAdapterTag(tag));
12052
+ const versions = await this.adapter.getTagVersions(physicalTags);
12053
+ return Object.fromEntries(
12054
+ normalized.map((tag, index) => [
12055
+ tag,
12056
+ normalizeAdapterVersion(versions[physicalTags[index]])
12057
+ ])
12058
+ );
12059
+ }
12060
+ async isAdapterEntryStale(entry, now = Date.now()) {
12061
+ if (typeof entry.revalidate === "number" && entry.revalidate > 0 && now - entry.createdAt >= entry.revalidate * 1e3) {
12062
+ return true;
12063
+ }
12064
+ const currentVersions = await this.getAdapterTagVersions(entry.tags);
12065
+ for (const tag of entry.tags) {
12066
+ if (normalizeAdapterVersion(currentVersions[tag]) > normalizeAdapterVersion(entry.tagVersions?.[tag])) {
12067
+ return true;
12068
+ }
12069
+ }
12070
+ return false;
12071
+ }
11838
12072
  };
11839
12073
  __name(_FarmDataCache, "FarmDataCache");
11840
12074
  var FarmDataCache = _FarmDataCache;
@@ -11847,6 +12081,10 @@ function getFarmDataCache() {
11847
12081
  return sharedFarmDataCache;
11848
12082
  }
11849
12083
  __name(getFarmDataCache, "getFarmDataCache");
12084
+ function configureFarmCache(config) {
12085
+ sharedFarmDataCache.configure(config);
12086
+ }
12087
+ __name(configureFarmCache, "configureFarmCache");
11850
12088
  function createPathCacheTag(routePath) {
11851
12089
  return `path:${normalizeRevalidatePath(routePath)}`;
11852
12090
  }
@@ -11907,6 +12145,52 @@ function normalizeCacheTag(tag) {
11907
12145
  return normalized;
11908
12146
  }
11909
12147
  __name(normalizeCacheTag, "normalizeCacheTag");
12148
+ function normalizeCacheNamespace(namespace) {
12149
+ if (typeof namespace !== "string") {
12150
+ throw new TypeError("Cache namespace must be a string.");
12151
+ }
12152
+ const normalized = namespace.trim().replace(/:+$/g, "");
12153
+ if (!normalized) {
12154
+ throw new Error("Cache namespace cannot be empty.");
12155
+ }
12156
+ return normalized;
12157
+ }
12158
+ __name(normalizeCacheNamespace, "normalizeCacheNamespace");
12159
+ function normalizeAdapterVersion(value) {
12160
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
12161
+ }
12162
+ __name(normalizeAdapterVersion, "normalizeAdapterVersion");
12163
+ function normalizePositiveDuration(value, fallback, name) {
12164
+ if (value === void 0) return fallback;
12165
+ if (!Number.isFinite(value) || value <= 0) {
12166
+ throw new TypeError(`${name} must be a positive number of milliseconds.`);
12167
+ }
12168
+ return Math.floor(value);
12169
+ }
12170
+ __name(normalizePositiveDuration, "normalizePositiveDuration");
12171
+ function delay(ms) {
12172
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
12173
+ }
12174
+ __name(delay, "delay");
12175
+ function normalizeCacheOptionsTags(options) {
12176
+ const tags = /* @__PURE__ */ new Set();
12177
+ for (const tag of options.tags ?? []) {
12178
+ tags.add(normalizeCacheTag(tag));
12179
+ }
12180
+ for (const routePath of options.paths ?? []) {
12181
+ tags.add(createPathCacheTag(routePath));
12182
+ }
12183
+ return tags;
12184
+ }
12185
+ __name(normalizeCacheOptionsTags, "normalizeCacheOptionsTags");
12186
+ function assertFarmCacheEntry(entry, expectedKey) {
12187
+ if (!entry || typeof entry !== "object" || entry.key !== expectedKey || !Array.isArray(entry.tags) || typeof entry.createdAt !== "number") {
12188
+ throw new TypeError(
12189
+ `Cache adapter returned an invalid entry for ${JSON.stringify(expectedKey)}.`
12190
+ );
12191
+ }
12192
+ }
12193
+ __name(assertFarmCacheEntry, "assertFarmCacheEntry");
11910
12194
  function getFunctionCacheIdentity(fn) {
11911
12195
  if (fn.name) {
11912
12196
  return `name:${fn.name}`;
@@ -13047,6 +13331,76 @@ function normalizePathname(pathname) {
13047
13331
  }
13048
13332
  __name(normalizePathname, "normalizePathname");
13049
13333
 
13334
+ // src/routing/route-slots.ts
13335
+ var SLOT_SEGMENT = /^@([A-Za-z][A-Za-z0-9_-]*)$/;
13336
+ function parseRouteSlotFile(filePath) {
13337
+ const normalized = filePath.replace(/\\/g, "/");
13338
+ const parts = normalized.split("/").filter(Boolean);
13339
+ const fileName = parts.at(-1) ?? "";
13340
+ const baseName = fileName.replace(/\.(tsx?|jsx?)$/, "");
13341
+ if (baseName !== "page" && baseName !== "default") return null;
13342
+ const slotIndex = parts.findIndex((part) => SLOT_SEGMENT.test(part));
13343
+ if (slotIndex < 0) return null;
13344
+ if (parts.slice(slotIndex + 1, -1).some((part) => SLOT_SEGMENT.test(part))) {
13345
+ throw new Error(`Nested route slots are not supported in "${filePath}"`);
13346
+ }
13347
+ const slotMatch = parts[slotIndex].match(SLOT_SEGMENT);
13348
+ const name = slotMatch?.[1];
13349
+ if (!name) return null;
13350
+ const ownerParts = parts.slice(0, slotIndex);
13351
+ const contentParts = parts.slice(slotIndex + 1, -1);
13352
+ const marker = parseInterceptionMarker(contentParts[0]);
13353
+ let targetBase = ownerParts;
13354
+ let targetParts = contentParts;
13355
+ if (marker) {
13356
+ targetBase = marker.root === true ? [] : ownerParts.slice(0, Math.max(0, ownerParts.length - marker.up));
13357
+ targetParts = [...marker.remainder ? [marker.remainder] : [], ...contentParts.slice(1)];
13358
+ }
13359
+ const ownerFile = [...ownerParts, "layout.tsx"].join("/");
13360
+ const routeFile = [
13361
+ ...baseName === "default" ? ownerParts : targetBase,
13362
+ ...baseName === "default" ? [] : targetParts,
13363
+ "page.tsx"
13364
+ ].join("/");
13365
+ return {
13366
+ name,
13367
+ ownerRoute: parseRoutePath(ownerFile || "layout.tsx"),
13368
+ route: parseRoutePath(routeFile || "page.tsx"),
13369
+ interception: marker !== null,
13370
+ fallback: baseName === "default"
13371
+ };
13372
+ }
13373
+ __name(parseRouteSlotFile, "parseRouteSlotFile");
13374
+ function createRouteSlotContainerId(name, ownerPattern) {
13375
+ const owner = ownerPattern === "/" ? "root" : ownerPattern.replace(/^\//, "").replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
13376
+ return `__farm_slot_${name}_${owner || "root"}__`;
13377
+ }
13378
+ __name(createRouteSlotContainerId, "createRouteSlotContainerId");
13379
+ function parseInterceptionMarker(segment) {
13380
+ if (!segment) return null;
13381
+ if (segment.startsWith("(...)")) {
13382
+ return {
13383
+ up: 0,
13384
+ root: true,
13385
+ remainder: segment.slice(5)
13386
+ };
13387
+ }
13388
+ if (segment.startsWith("(.)")) {
13389
+ return {
13390
+ up: 0,
13391
+ remainder: segment.slice(3)
13392
+ };
13393
+ }
13394
+ let remainder = segment;
13395
+ let up = 0;
13396
+ while (remainder.startsWith("(..)")) {
13397
+ up += 1;
13398
+ remainder = remainder.slice(4);
13399
+ }
13400
+ return up > 0 ? { up, remainder } : null;
13401
+ }
13402
+ __name(parseInterceptionMarker, "parseInterceptionMarker");
13403
+
13050
13404
  // src/routing/route-manager.ts
13051
13405
  function routeSpecificity(entry) {
13052
13406
  return entry.route.segments.reduce((score, segment) => {
@@ -13060,6 +13414,7 @@ var _RouteManager = class _RouteManager {
13060
13414
  constructor(config, viteServer) {
13061
13415
  this.routes = /* @__PURE__ */ new Map();
13062
13416
  this.layouts = /* @__PURE__ */ new Map();
13417
+ this.routeSlots = /* @__PURE__ */ new Map();
13063
13418
  this.loadings = /* @__PURE__ */ new Map();
13064
13419
  this.errors = /* @__PURE__ */ new Map();
13065
13420
  this.metadataImages = /* @__PURE__ */ new Map();
@@ -13075,6 +13430,7 @@ var _RouteManager = class _RouteManager {
13075
13430
  async discoverRoutes() {
13076
13431
  this.routes.clear();
13077
13432
  this.layouts.clear();
13433
+ this.routeSlots.clear();
13078
13434
  this.loadings.clear();
13079
13435
  this.errors.clear();
13080
13436
  this.metadataImages.clear();
@@ -13085,6 +13441,13 @@ var _RouteManager = class _RouteManager {
13085
13441
  await this.discoverFileRoutes(source);
13086
13442
  await this.discoverProgrammaticRoutes(source);
13087
13443
  }
13444
+ for (const slot of this.routeSlots.values()) {
13445
+ if (slot.interception && !this.routes.has(slot.pattern)) {
13446
+ throw new Error(
13447
+ `Intercepting route slot "${slot.name}" targets "${slot.pattern}", but no canonical page exists for that URL`
13448
+ );
13449
+ }
13450
+ }
13088
13451
  this.routes = new Map(
13089
13452
  Array.from(this.routes.entries()).sort(
13090
13453
  ([, left], [, right]) => routeSpecificity(right) - routeSpecificity(left)
@@ -13100,7 +13463,7 @@ var _RouteManager = class _RouteManager {
13100
13463
  /**
13101
13464
  * Find matching route for a given URL path
13102
13465
  */
13103
- matchRoute(pathname) {
13466
+ matchRoute(pathname, options = {}) {
13104
13467
  const routePathname = this.toRoutePathname(pathname);
13105
13468
  const normalizedPath = routePathname === "/" ? "/" : routePathname.replace(/\/$/, "");
13106
13469
  let matchedRoute = null;
@@ -13114,10 +13477,12 @@ var _RouteManager = class _RouteManager {
13114
13477
  }
13115
13478
  }
13116
13479
  const layouts = this.findMatchingLayouts(normalizedPath);
13480
+ const slots = this.findMatchingRouteSlots(normalizedPath, options.interceptFrom);
13117
13481
  return {
13118
13482
  route: matchedRoute,
13119
13483
  params,
13120
- layouts
13484
+ layouts,
13485
+ slots
13121
13486
  };
13122
13487
  }
13123
13488
  /**
@@ -13132,6 +13497,9 @@ var _RouteManager = class _RouteManager {
13132
13497
  getLayouts() {
13133
13498
  return new Map(this.layouts);
13134
13499
  }
13500
+ getRouteSlots() {
13501
+ return new Map(this.routeSlots);
13502
+ }
13135
13503
  /** Resolve route rules, inherited layouts, and the page export in precedence order. */
13136
13504
  async resolveRouteRuntimeConfig(pattern) {
13137
13505
  const routeEntry = this.routes.get(pattern);
@@ -13290,7 +13658,22 @@ var _RouteManager = class _RouteManager {
13290
13658
  pattern: entry.pattern,
13291
13659
  modulePath: toUrlPath(entry.modulePath)
13292
13660
  }));
13293
- return { routes, layouts };
13661
+ const slots = Array.from(this.routeSlots.values()).map((entry) => {
13662
+ const metadata = getClientModuleMetadata(entry.modulePath, projectRoot);
13663
+ return {
13664
+ name: entry.name,
13665
+ ownerPattern: entry.ownerPattern,
13666
+ pattern: entry.pattern,
13667
+ modulePath: toUrlPath(entry.modulePath),
13668
+ containerId: entry.containerId,
13669
+ interception: entry.interception,
13670
+ fallback: entry.fallback,
13671
+ shouldHydrate: metadata.shouldHydrate,
13672
+ isClientComponent: metadata.isClientComponent,
13673
+ segments: entry.route.segments
13674
+ };
13675
+ });
13676
+ return { routes, layouts, slots };
13294
13677
  }
13295
13678
  /**
13296
13679
  * Load a route module dynamically
@@ -13367,6 +13750,7 @@ var _RouteManager = class _RouteManager {
13367
13750
  async discoverFileRoutes(source) {
13368
13751
  const appDir = resolveAppPath(source.root, source.srcDir, "app");
13369
13752
  const pageFiles = await safeGlobFiles("**/page.{ts,tsx,js,jsx,md,mdx}", appDir);
13753
+ const slotDefaultFiles = await safeGlobFiles("**/@*/**/default.{ts,tsx,js,jsx}", appDir);
13370
13754
  const layoutFiles = await safeGlobFiles("**/layout.{ts,tsx,js,jsx}", appDir);
13371
13755
  const loadingFiles = await safeGlobFiles("**/loading.{ts,tsx,js,jsx}", appDir);
13372
13756
  const errorFiles = await safeGlobFiles("**/error.{ts,tsx,js,jsx}", appDir);
@@ -13375,7 +13759,7 @@ var _RouteManager = class _RouteManager {
13375
13759
  appDir
13376
13760
  );
13377
13761
  for (const [kind, files, target] of [
13378
- ["page", pageFiles, this.routes],
13762
+ ["page", pageFiles.filter((file) => parseRouteSlotFile(file) === null), this.routes],
13379
13763
  ["layout", layoutFiles, this.layouts],
13380
13764
  ["loading", loadingFiles, this.loadings],
13381
13765
  ["error", errorFiles, this.errors]
@@ -13399,6 +13783,32 @@ var _RouteManager = class _RouteManager {
13399
13783
  });
13400
13784
  }
13401
13785
  }
13786
+ for (const file of [...pageFiles, ...slotDefaultFiles]) {
13787
+ const slot = parseRouteSlotFile(file);
13788
+ if (!slot) continue;
13789
+ const modulePath = import_path5.default.join(appDir, file);
13790
+ const pattern = this.createRoutePattern(slot.route);
13791
+ const ownerPattern = this.createRoutePattern(slot.ownerRoute);
13792
+ const key = `${ownerPattern}:${slot.name}:${slot.interception ? "intercept" : "slot"}:${slot.fallback ? "default" : pattern}`;
13793
+ const existing = this.routeSlots.get(key);
13794
+ if (existing?.sourceRoot === source.root) {
13795
+ throw new Error(
13796
+ `Duplicate route slot "${slot.name}" for "${pattern}". Found both ${existing.modulePath} and ${modulePath}.`
13797
+ );
13798
+ }
13799
+ this.routeSlots.set(key, {
13800
+ route: slot.route,
13801
+ modulePath,
13802
+ pattern,
13803
+ source: "file",
13804
+ sourceRoot: source.root,
13805
+ name: slot.name,
13806
+ ownerPattern,
13807
+ interception: slot.interception,
13808
+ fallback: slot.fallback,
13809
+ containerId: createRouteSlotContainerId(slot.name, ownerPattern)
13810
+ });
13811
+ }
13402
13812
  for (const file of metadataImageFiles) {
13403
13813
  const fileBase = import_path5.default.basename(file, import_path5.default.extname(file));
13404
13814
  const kind = fileBase === "twitter-image" ? "twitter" : "opengraph";
@@ -13524,6 +13934,71 @@ var _RouteManager = class _RouteManager {
13524
13934
  }
13525
13935
  return matchingLayouts;
13526
13936
  }
13937
+ findMatchingRouteSlots(pathname, interceptFrom) {
13938
+ const groups = /* @__PURE__ */ new Map();
13939
+ for (const entry of this.routeSlots.values()) {
13940
+ if (!this.matchesRoutePrefix(pathname, entry.ownerPattern)) continue;
13941
+ const key = `${entry.ownerPattern}:${entry.name}`;
13942
+ const entries = groups.get(key) ?? [];
13943
+ entries.push(entry);
13944
+ groups.set(key, entries);
13945
+ }
13946
+ const normalizedFrom = interceptFrom ? this.toRoutePathname(new URL(interceptFrom, "http://farm.local").pathname) : void 0;
13947
+ const matches = [];
13948
+ for (const entries of groups.values()) {
13949
+ const candidates = entries.filter((entry) => !entry.fallback).filter(
13950
+ (entry) => !entry.interception || normalizedFrom !== void 0 && this.matchesRoutePrefix(normalizedFrom, entry.ownerPattern)
13951
+ ).map((entry) => ({
13952
+ entry,
13953
+ match: matchRoute(pathname, entry.route.segments)
13954
+ })).filter((candidate) => candidate.match.matches).sort((left, right) => {
13955
+ if (left.entry.interception !== right.entry.interception) {
13956
+ return left.entry.interception ? -1 : 1;
13957
+ }
13958
+ return routeSpecificity(right.entry) - routeSpecificity(left.entry);
13959
+ });
13960
+ const selected = candidates[0];
13961
+ if (selected) {
13962
+ matches.push({
13963
+ name: selected.entry.name,
13964
+ ownerPattern: selected.entry.ownerPattern,
13965
+ containerId: selected.entry.containerId,
13966
+ interception: selected.entry.interception,
13967
+ fallback: false,
13968
+ route: selected.entry,
13969
+ params: selected.match.params
13970
+ });
13971
+ continue;
13972
+ }
13973
+ const fallback = entries.find((entry) => entry.fallback);
13974
+ if (fallback) {
13975
+ matches.push({
13976
+ name: fallback.name,
13977
+ ownerPattern: fallback.ownerPattern,
13978
+ containerId: fallback.containerId,
13979
+ interception: false,
13980
+ fallback: true,
13981
+ route: fallback,
13982
+ params: {}
13983
+ });
13984
+ }
13985
+ }
13986
+ return matches.sort((left, right) => {
13987
+ const ownerDifference = left.route.route.segments.length - right.route.route.segments.length;
13988
+ return ownerDifference || left.name.localeCompare(right.name);
13989
+ });
13990
+ }
13991
+ matchesRoutePrefix(pathname, pattern) {
13992
+ if (pattern === "/") return true;
13993
+ const patternSegments = parseRoutePath(`${pattern}/page.tsx`).segments;
13994
+ const pathSegments = pathname.split("/").filter(Boolean);
13995
+ if (patternSegments.length > pathSegments.length) return false;
13996
+ for (let index = 0; index < patternSegments.length; index++) {
13997
+ const segment = patternSegments[index];
13998
+ if (!segment.isDynamic && segment.segment !== pathSegments[index]) return false;
13999
+ }
14000
+ return true;
14001
+ }
13527
14002
  findNearestBoundary(pathname, boundaries) {
13528
14003
  const normalizedPath = pathname === "/" ? "/" : pathname.replace(/\/$/, "");
13529
14004
  const pathSegments = normalizedPath.split("/").filter(Boolean);
@@ -15052,7 +15527,9 @@ var _ServerRenderer = class _ServerRenderer {
15052
15527
  async runWithRequestContext(request, fn) {
15053
15528
  return _runWithCurrentRequest(
15054
15529
  request,
15055
- () => this.i18nRuntime?.config.enabled ? _runWithFarmI18nRequest(this.i18nRuntime, request, fn, { redirect: false }) : fn()
15530
+ () => this.i18nRuntime?.config.enabled ? _runWithFarmI18nRequest(this.i18nRuntime, request, fn, {
15531
+ redirect: false
15532
+ }) : fn()
15056
15533
  );
15057
15534
  }
15058
15535
  async resolveRouteContext(input) {
@@ -15075,11 +15552,11 @@ var _ServerRenderer = class _ServerRenderer {
15075
15552
  /**
15076
15553
  * Check if a path should be served from SSG cache
15077
15554
  */
15078
- shouldServeSSG(pathname) {
15555
+ async shouldServeSSG(pathname) {
15079
15556
  const ssgPage = matchSSGPage(pathname, this.ssgManifest);
15080
15557
  if (!ssgPage) return null;
15081
- const cached = this.getCachedSSGPage(pathname);
15082
- if (cached && this.dataCache.isStale(cached)) {
15558
+ const cached = await this.getCachedSSGPage(pathname);
15559
+ if (cached && await this.dataCache.isStaleAsync(cached)) {
15083
15560
  this.regenerateSSGPage(ssgPage);
15084
15561
  }
15085
15562
  return ssgPage;
@@ -15091,12 +15568,12 @@ var _ServerRenderer = class _ServerRenderer {
15091
15568
  return createFarmCacheKey(["ppr", normalizeRevalidatePath(pathname), search]);
15092
15569
  }
15093
15570
  getCachedSSGPage(urlPath) {
15094
- return this.dataCache.getEntry(this.getSSGCacheKey(urlPath), {
15571
+ return this.dataCache.getEntryAsync(this.getSSGCacheKey(urlPath), {
15095
15572
  allowStale: true
15096
15573
  });
15097
15574
  }
15098
- cacheSSGPage(page2, html, options) {
15099
- this.dataCache.set(
15575
+ async cacheSSGPage(page2, html, options) {
15576
+ await this.dataCache.setAsync(
15100
15577
  this.getSSGCacheKey(page2.urlPath),
15101
15578
  { html, document: options.document },
15102
15579
  {
@@ -15108,11 +15585,11 @@ var _ServerRenderer = class _ServerRenderer {
15108
15585
  );
15109
15586
  }
15110
15587
  getCachedPPRShell(pathname, search) {
15111
- return this.dataCache.getEntry(this.getPPRCacheKey(pathname, search));
15588
+ return this.dataCache.getEntryAsync(this.getPPRCacheKey(pathname, search));
15112
15589
  }
15113
- cachePPRShell(options, html) {
15590
+ async cachePPRShell(options, html) {
15114
15591
  const key = this.getPPRCacheKey(options.pathname, options.search);
15115
- this.dataCache.set(
15592
+ await this.dataCache.setAsync(
15116
15593
  key,
15117
15594
  { html },
15118
15595
  {
@@ -15210,7 +15687,7 @@ var _ServerRenderer = class _ServerRenderer {
15210
15687
  }
15211
15688
  const { renderToString } = await import("react-dom/server");
15212
15689
  const html = renderToString(await this.wrapWithIntegrationProviders(pageElement));
15213
- this.cacheSSGPage(page2, html, { document: false });
15690
+ await this.cacheSSGPage(page2, html, { document: false });
15214
15691
  logger.info(`ISR: Regenerated ${page2.urlPath}`);
15215
15692
  } catch (error) {
15216
15693
  logger.error(`ISR regeneration failed for ${page2.urlPath}: ${error}`);
@@ -15224,7 +15701,7 @@ var _ServerRenderer = class _ServerRenderer {
15224
15701
  * Serve a pre-rendered SSG page
15225
15702
  */
15226
15703
  async serveSSGPage(req, res, page2) {
15227
- const cached = this.getCachedSSGPage(page2.urlPath);
15704
+ const cached = await this.getCachedSSGPage(page2.urlPath);
15228
15705
  if (cached) {
15229
15706
  res.setHeader("Content-Type", "text/html; charset=utf-8");
15230
15707
  res.setHeader("X-Farm-SSG", "cached");
@@ -15242,9 +15719,12 @@ var _ServerRenderer = class _ServerRenderer {
15242
15719
  if (fs2.existsSync(htmlPath)) {
15243
15720
  const stat = fs2.statSync(htmlPath);
15244
15721
  const html = fs2.readFileSync(htmlPath, "utf-8");
15245
- this.cacheSSGPage(page2, html, { document: true, createdAt: stat.mtimeMs });
15246
- const fileCacheEntry = this.getCachedSSGPage(page2.urlPath);
15247
- if (fileCacheEntry && this.dataCache.isStale(fileCacheEntry)) {
15722
+ await this.cacheSSGPage(page2, html, {
15723
+ document: true,
15724
+ createdAt: stat.mtimeMs
15725
+ });
15726
+ const fileCacheEntry = await this.getCachedSSGPage(page2.urlPath);
15727
+ if (fileCacheEntry && await this.dataCache.isStaleAsync(fileCacheEntry)) {
15248
15728
  this.regenerateSSGPage(page2);
15249
15729
  }
15250
15730
  res.setHeader("Content-Type", "text/html; charset=utf-8");
@@ -15290,6 +15770,7 @@ var _ServerRenderer = class _ServerRenderer {
15290
15770
  let pathname = "/";
15291
15771
  let params = {};
15292
15772
  let layouts = [];
15773
+ let routeSlots = [];
15293
15774
  let searchParamsObject = {};
15294
15775
  let middlewareMap = /* @__PURE__ */ new Map();
15295
15776
  let middlewareContext = /* @__PURE__ */ new Map();
@@ -15322,7 +15803,7 @@ var _ServerRenderer = class _ServerRenderer {
15322
15803
  }
15323
15804
  this.applyDeploymentHeaders(req, res);
15324
15805
  if (process.env.NODE_ENV === "production") {
15325
- const ssgPage = this.shouldServeSSG(pathname);
15806
+ const ssgPage = await this.shouldServeSSG(pathname);
15326
15807
  if (ssgPage) {
15327
15808
  const served = await this.serveSSGPage(req, res, ssgPage);
15328
15809
  if (served) {
@@ -15335,6 +15816,7 @@ var _ServerRenderer = class _ServerRenderer {
15335
15816
  const route = match.route;
15336
15817
  params = match.params;
15337
15818
  layouts = match.layouts;
15819
+ routeSlots = match.slots ?? [];
15338
15820
  if (!route) {
15339
15821
  emitFarmEvent({ type: "route.notFound", pathname });
15340
15822
  await this.render404(req, res);
@@ -15351,7 +15833,9 @@ var _ServerRenderer = class _ServerRenderer {
15351
15833
  errorBoundaryEntry = this.routeManager.getMatchingError(pathname);
15352
15834
  middlewareMap = toMiddlewareMap2(req.__FARM_MIDDLEWARE_DATA__);
15353
15835
  middlewareContext = toMiddlewareMap2(req.__FARM_MIDDLEWARE_CONTEXT__);
15354
- pluginExposedContext = getRequestContextSnapshot(req, { exposedOnly: true });
15836
+ pluginExposedContext = getRequestContextSnapshot(req, {
15837
+ exposedOnly: true
15838
+ });
15355
15839
  const currentRequest = createWebRequestFromFarmRequest(req);
15356
15840
  const routeContext = await this.resolveRouteContext({
15357
15841
  request: currentRequest,
@@ -15415,8 +15899,16 @@ var _ServerRenderer = class _ServerRenderer {
15415
15899
  revalidate: renderingConfig.revalidate
15416
15900
  } : void 0;
15417
15901
  if (renderingConfig.ppr && pprBypassReason) {
15418
- emitFarmEvent({ type: "ppr.shell.bypass", route: pathname, reason: pprBypassReason });
15419
- emitFarmEvent({ type: "cache.bypass", route: pathname, reason: pprBypassReason });
15902
+ emitFarmEvent({
15903
+ type: "ppr.shell.bypass",
15904
+ route: pathname,
15905
+ reason: pprBypassReason
15906
+ });
15907
+ emitFarmEvent({
15908
+ type: "cache.bypass",
15909
+ route: pathname,
15910
+ reason: pprBypassReason
15911
+ });
15420
15912
  if (pprBypassReason === "refresh") {
15421
15913
  pprRefreshRoute = pathname;
15422
15914
  emitFarmEvent({ type: "ppr.refresh.start", route: pathname });
@@ -15424,14 +15916,22 @@ var _ServerRenderer = class _ServerRenderer {
15424
15916
  }
15425
15917
  if (pprShellOptions) {
15426
15918
  const pprCacheKey = this.getPPRCacheKey(pathname, url.search);
15427
- const cachedPPRShell = this.getCachedPPRShell(pathname, url.search);
15919
+ const cachedPPRShell = await this.getCachedPPRShell(pathname, url.search);
15428
15920
  if (cachedPPRShell) {
15429
- emitFarmEvent({ type: "ppr.shell.hit", route: pathname, key: pprCacheKey });
15921
+ emitFarmEvent({
15922
+ type: "ppr.shell.hit",
15923
+ route: pathname,
15924
+ key: pprCacheKey
15925
+ });
15430
15926
  this.serveCachedPPRShell(res, cachedPPRShell.value, renderingConfig.revalidate);
15431
15927
  completeRender(res.statusCode || 200, pathname);
15432
15928
  return;
15433
15929
  }
15434
- emitFarmEvent({ type: "ppr.shell.miss", route: pathname, key: pprCacheKey });
15930
+ emitFarmEvent({
15931
+ type: "ppr.shell.miss",
15932
+ route: pathname,
15933
+ key: pprCacheKey
15934
+ });
15435
15935
  }
15436
15936
  let LoadingFallbackComponent = null;
15437
15937
  if (loadingBoundaryEntry) {
@@ -15455,14 +15955,77 @@ var _ServerRenderer = class _ServerRenderer {
15455
15955
  );
15456
15956
  const moduleMetadata = routeManifestEntry || getClientModuleMetadata(route.modulePath, this.config.root);
15457
15957
  const isClientComponent = moduleMetadata.isClientComponent;
15958
+ const renderedRouteSlots = await Promise.all(
15959
+ routeSlots.map(async (slot) => {
15960
+ const slotModule = await this.routeManager.loadRouteModule(slot.route.modulePath);
15961
+ if (!slotModule.default) {
15962
+ throw new Error(
15963
+ `Route slot "${slot.name}" module ${slot.route.modulePath} does not export a default component`
15964
+ );
15965
+ }
15966
+ const slotContext = await this.resolveRouteContext({
15967
+ request: currentRequest,
15968
+ rawRequest: req,
15969
+ params: slot.params,
15970
+ search: searchParamsObject,
15971
+ path: pathname
15972
+ });
15973
+ const rawSlotProps = withFarmRouteContext(
15974
+ {
15975
+ params: slot.params,
15976
+ searchParams: Promise.resolve(searchParamsObject),
15977
+ path: pathname,
15978
+ middleware: middlewareMap.size > 0 ? { data: middlewareMap } : void 0,
15979
+ context: pluginExposedContext.size > 0 ? { data: pluginExposedContext } : void 0
15980
+ },
15981
+ slotContext
15982
+ );
15983
+ const slotProps = await parseRouteModuleProps(slotModule, {
15984
+ props: rawSlotProps,
15985
+ search: searchParamsObject,
15986
+ routePath: slot.route.pattern
15987
+ });
15988
+ const metadata = getClientModuleMetadata(slot.route.modulePath, this.config.root);
15989
+ return {
15990
+ ...slot,
15991
+ module: slotModule,
15992
+ props: slotProps,
15993
+ isClientComponent: metadata.isClientComponent,
15994
+ shouldHydrate: metadata.shouldHydrate
15995
+ };
15996
+ })
15997
+ );
15458
15998
  const shouldHydrate = moduleMetadata.shouldHydrate;
15999
+ const hasHydratableRouteSlots = renderedRouteSlots.some(
16000
+ (slot) => slot.isClientComponent || slot.shouldHydrate
16001
+ );
15459
16002
  req.__FARM_PAGE_PATH__ = route.modulePath;
15460
16003
  req.__FARM_ROUTE__ = pathname;
15461
16004
  req.__FARM_IS_CLIENT_COMPONENT__ = isClientComponent;
15462
16005
  req.__FARM_SHOULD_HYDRATE__ = shouldHydrate;
16006
+ req.__FARM_HAS_HYDRATABLE_ROUTE_SLOTS__ = hasHydratableRouteSlots;
15463
16007
  req.__FARM_LOADING_MODULE_PATH__ = loadingBoundaryEntry?.modulePath ? loadingBoundaryEntry.modulePath.substring(
15464
16008
  loadingBoundaryEntry.modulePath.indexOf("/src/app/")
15465
16009
  ) : null;
16010
+ req.__FARM_ROUTE_SLOTS__ = renderedRouteSlots.map((slot) => ({
16011
+ name: slot.name,
16012
+ ownerPattern: slot.ownerPattern,
16013
+ containerId: slot.containerId,
16014
+ interception: slot.interception,
16015
+ fallback: slot.fallback,
16016
+ modulePath: slot.route.modulePath,
16017
+ isClientComponent: slot.isClientComponent,
16018
+ shouldHydrate: slot.shouldHydrate,
16019
+ props: {
16020
+ params: slot.props.params,
16021
+ search: slot.props.search,
16022
+ searchParams: slot.props.search,
16023
+ ..."data" in slot.props ? { data: slot.props.data } : {},
16024
+ ...slot.props.__farmCanonicalPath ? { __farmCanonicalPath: slot.props.__farmCanonicalPath } : {},
16025
+ ...slot.props.__farmRoutePropsResolved ? { __farmRoutePropsResolved: true } : {},
16026
+ path: pathname
16027
+ }
16028
+ }));
15466
16029
  req.__FARM_PROPS__ = {
15467
16030
  params: pageProps.params,
15468
16031
  search: pageProps.search,
@@ -15522,12 +16085,32 @@ var _ServerRenderer = class _ServerRenderer {
15522
16085
  let wrappedElement = pageElement;
15523
16086
  for (let i = layoutModules.length - 1; i >= 0; i--) {
15524
16087
  const layoutModule = layoutModules[i];
16088
+ const layoutEntry = layouts[i];
15525
16089
  const LayoutComponent = layoutModule.default;
16090
+ const slotProps = {};
16091
+ for (const slot of renderedRouteSlots) {
16092
+ if (slot.ownerPattern !== layoutEntry.pattern) continue;
16093
+ let slotElement = import_react4.default.createElement(
16094
+ slot.module.default,
16095
+ slot.props
16096
+ );
16097
+ slotElement = import_react4.default.createElement(
16098
+ "div",
16099
+ {
16100
+ id: slot.containerId,
16101
+ "data-farm-route-slot": slot.name,
16102
+ "data-farm-slot-owner": slot.ownerPattern
16103
+ },
16104
+ slotElement
16105
+ );
16106
+ slotProps[slot.name] = slotElement;
16107
+ }
15526
16108
  wrappedElement = import_react4.default.createElement(
15527
16109
  LayoutComponent,
15528
16110
  {
15529
16111
  children: wrappedElement,
15530
- params
16112
+ params,
16113
+ ...slotProps
15531
16114
  }
15532
16115
  );
15533
16116
  }
@@ -15564,7 +16147,10 @@ var _ServerRenderer = class _ServerRenderer {
15564
16147
  routeManifest,
15565
16148
  captureStaticShell: Boolean(pprShellOptions),
15566
16149
  observabilityRoute: pathname,
15567
- onSuspenseHoleDetected: pprShellOptions ? () => emitFarmEvent({ type: "ppr.suspense.holeDetected", route: pathname }) : void 0,
16150
+ onSuspenseHoleDetected: pprShellOptions ? () => emitFarmEvent({
16151
+ type: "ppr.suspense.holeDetected",
16152
+ route: pathname
16153
+ }) : void 0,
15568
16154
  onComplete: pprShellOptions && req.method !== "HEAD" ? (html) => this.cachePPRShell(pprShellOptions, html) : void 0
15569
16155
  }
15570
16156
  );
@@ -15612,7 +16198,11 @@ var _ServerRenderer = class _ServerRenderer {
15612
16198
  }
15613
16199
  emitFarmEvent({ type: "render.error", route: pathname, error });
15614
16200
  if (pprRefreshRoute) {
15615
- emitFarmEvent({ type: "ppr.refresh.error", route: pprRefreshRoute, error });
16201
+ emitFarmEvent({
16202
+ type: "ppr.refresh.error",
16203
+ route: pprRefreshRoute,
16204
+ error
16205
+ });
15616
16206
  }
15617
16207
  logger.error(`Error rendering page: ${error}`);
15618
16208
  if (res.headersSent || res.writableEnded) {
@@ -15665,7 +16255,9 @@ var _ServerRenderer = class _ServerRenderer {
15665
16255
  if (typeof layoutModule.generateMetadata === "function") {
15666
16256
  metadata = mergeMetadata(
15667
16257
  metadata,
15668
- await layoutModule.generateMetadata({ params: options.pageProps.params })
16258
+ await layoutModule.generateMetadata({
16259
+ params: options.pageProps.params
16260
+ })
15669
16261
  );
15670
16262
  }
15671
16263
  }
@@ -15896,7 +16488,13 @@ var _ServerRenderer = class _ServerRenderer {
15896
16488
  clientEntry: "/@farm/client.js",
15897
16489
  routes: {},
15898
16490
  layouts: {},
15899
- sharedAssets: [{ tag: "link", attrs: { rel: "stylesheet", href: "/src/app/globals.css" } }]
16491
+ slots: [],
16492
+ sharedAssets: [
16493
+ {
16494
+ tag: "link",
16495
+ attrs: { rel: "stylesheet", href: "/src/app/globals.css" }
16496
+ }
16497
+ ]
15900
16498
  };
15901
16499
  for (const routeEntry of manifest.routes) {
15902
16500
  clientManifest.routes[routeEntry.pattern] = {
@@ -15918,9 +16516,26 @@ var _ServerRenderer = class _ServerRenderer {
15918
16516
  assets: []
15919
16517
  };
15920
16518
  }
15921
- const deferredProps = prepareDeferredData(req.__FARM_PROPS__ || {});
16519
+ for (const slotEntry of manifest.slots ?? []) {
16520
+ clientManifest.slots.push({
16521
+ ...slotEntry,
16522
+ preloads: [slotEntry.modulePath],
16523
+ assets: []
16524
+ });
16525
+ }
16526
+ const routeSlotPayload = (req.__FARM_ROUTE_SLOTS__ || []).map(
16527
+ (slot) => ({
16528
+ ...slot,
16529
+ modulePath: typeof slot.modulePath === "string" && slot.modulePath.startsWith(this.config.root) ? slot.modulePath.slice(this.config.root.length) : slot.modulePath
16530
+ })
16531
+ );
16532
+ const deferredProps = prepareDeferredData({
16533
+ page: req.__FARM_PROPS__ || {},
16534
+ slots: routeSlotPayload
16535
+ });
15922
16536
  const propsScript = `<script>
15923
- window.__FARM_PROPS__ = ${serializeInlineValue(deferredProps.data)};
16537
+ window.__FARM_PROPS__ = ${serializeInlineValue(deferredProps.data.page)};
16538
+ window.__FARM_ROUTE_SLOTS__ = ${serializeInlineValue(deferredProps.data.slots)};
15924
16539
  window.__FARM_DEPLOYMENT_ID__ = ${serializeInlineValue(deploymentId)};
15925
16540
  window.__FARM_PATH__ = ${JSON.stringify(req.__FARM_ROUTE__ || req.url || "/")};
15926
16541
  window.__FARM_IS_CLIENT__ = ${JSON.stringify(isClientComponent)};
@@ -15933,7 +16548,7 @@ window.__FARM_MANIFEST__ = ${JSON.stringify(clientManifest)};
15933
16548
  window.__FARM_INTEGRATION_API_MANIFEST__ = ${JSON.stringify(getRegisteredIntegrationAPIManifest())};
15934
16549
  ${getFarmI18nClientSnapshot() ? `window.__FARM_I18N__ = ${serializeInlineValue(getFarmI18nClientSnapshot())};` : ""}
15935
16550
  </script>`;
15936
- const hydrationClickQueueScript = isClientComponent || req.__FARM_SHOULD_HYDRATE__ === true ? createPreHydrationClickQueueScript() : "";
16551
+ const hydrationClickQueueScript = isClientComponent || req.__FARM_SHOULD_HYDRATE__ === true || req.__FARM_HAS_HYDRATABLE_ROUTE_SLOTS__ === true ? createPreHydrationClickQueueScript() : "";
15937
16552
  const {
15938
16553
  title,
15939
16554
  tags: metaTags,
@@ -16044,7 +16659,11 @@ ${getFarmI18nClientSnapshot() ? `window.__FARM_I18N__ = ${serializeInlineValue(g
16044
16659
  didError = true;
16045
16660
  if (!isFarmRedirectError(error) && !isFarmNotFoundError(error)) {
16046
16661
  logger.error(`SSR shell error: ${error}`);
16047
- emitFarmEvent({ type: "render.error", route: observabilityRoute, error });
16662
+ emitFarmEvent({
16663
+ type: "render.error",
16664
+ route: observabilityRoute,
16665
+ error
16666
+ });
16048
16667
  }
16049
16668
  if (clearMiddlewareData) {
16050
16669
  clearMiddlewareData();
@@ -16055,7 +16674,11 @@ ${getFarmI18nClientSnapshot() ? `window.__FARM_I18N__ = ${serializeInlineValue(g
16055
16674
  didError = true;
16056
16675
  if (!isFarmRedirectError(error) && !isFarmNotFoundError(error)) {
16057
16676
  logger.error(`SSR streaming error: ${error}`);
16058
- emitFarmEvent({ type: "render.error", route: observabilityRoute, error });
16677
+ emitFarmEvent({
16678
+ type: "render.error",
16679
+ route: observabilityRoute,
16680
+ error
16681
+ });
16059
16682
  }
16060
16683
  }
16061
16684
  });
@@ -16231,6 +16854,7 @@ var _FarmApp = class _FarmApp {
16231
16854
  logger.info("Initializing Farm.js application...");
16232
16855
  }
16233
16856
  await initStorage(this.config.storage);
16857
+ await configureFarmCache(this.config.cache);
16234
16858
  await this.i18nRuntime.initialize();
16235
16859
  await this.verifyAppStructure();
16236
16860
  await this.routeManager.discoverRoutes();
@@ -16262,6 +16886,7 @@ var _FarmApp = class _FarmApp {
16262
16886
  preset: config.preset ?? "node-server",
16263
16887
  deploy: config.deploy || {},
16264
16888
  storage: config.storage || {},
16889
+ cache: config.cache || {},
16265
16890
  integrations: config.integrations || {},
16266
16891
  plugins: config.plugins || [],
16267
16892
  migrations: config.migrations || { commands: [] },
@@ -16736,7 +17361,7 @@ async function invokeAPIRouteEndpointInContext(endpoint, request, params) {
16736
17361
  });
16737
17362
  let body = void 0;
16738
17363
  if (request.method.toUpperCase() !== "GET" && request.method.toUpperCase() !== "HEAD") {
16739
- body = await readJSONBody(request);
17364
+ body = await readRequestBody(request);
16740
17365
  }
16741
17366
  const headers = Object.fromEntries(request.headers.entries());
16742
17367
  const types = endpoint.__types || {};
@@ -16840,17 +17465,50 @@ function isFarmContextHandler(endpoint) {
16840
17465
  return firstParameter === "ctx" || firstParameter === "context" || firstParameter.startsWith("{");
16841
17466
  }
16842
17467
  __name(isFarmContextHandler, "isFarmContextHandler");
16843
- async function readJSONBody(request) {
17468
+ async function readRequestBody(request) {
17469
+ const contentType = request.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase();
16844
17470
  try {
17471
+ if (contentType === "multipart/form-data") {
17472
+ return formDataToObject(await request.clone().formData());
17473
+ }
16845
17474
  const text2 = await request.clone().text();
16846
- if (text2) {
17475
+ if (!text2) return void 0;
17476
+ if (contentType === "application/x-www-form-urlencoded") {
17477
+ return searchParamsToObject2(new URLSearchParams(text2));
17478
+ }
17479
+ if (contentType === "application/json" || contentType?.endsWith("+json")) {
16847
17480
  return JSON.parse(text2);
16848
17481
  }
17482
+ return JSON.parse(text2);
16849
17483
  } catch {
16850
17484
  }
16851
17485
  return void 0;
16852
17486
  }
16853
- __name(readJSONBody, "readJSONBody");
17487
+ __name(readRequestBody, "readRequestBody");
17488
+ function formDataToObject(formData) {
17489
+ return entriesToObject(formData.entries());
17490
+ }
17491
+ __name(formDataToObject, "formDataToObject");
17492
+ function searchParamsToObject2(searchParams) {
17493
+ return entriesToObject(searchParams.entries());
17494
+ }
17495
+ __name(searchParamsToObject2, "searchParamsToObject");
17496
+ function entriesToObject(entries) {
17497
+ const output = /* @__PURE__ */ Object.create(null);
17498
+ for (const [key, value] of entries) {
17499
+ if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
17500
+ const current = output[key];
17501
+ if (current === void 0) {
17502
+ output[key] = value;
17503
+ } else if (Array.isArray(current)) {
17504
+ current.push(value);
17505
+ } else {
17506
+ output[key] = [current, value];
17507
+ }
17508
+ }
17509
+ return output;
17510
+ }
17511
+ __name(entriesToObject, "entriesToObject");
16854
17512
  function validateInput(schema, value, error) {
16855
17513
  if (!schema || typeof schema.parse !== "function") {
16856
17514
  return value;
@@ -18005,6 +18663,7 @@ async function generateRouteTypes(options) {
18005
18663
  absolute: false
18006
18664
  });
18007
18665
  for (const file of pageFiles) {
18666
+ if (parseRouteSlotFile(file)) continue;
18008
18667
  const route = parseRoutePath(file);
18009
18668
  if (route.type === "page") {
18010
18669
  const pattern = createRoutePattern(route);
@@ -19001,7 +19660,7 @@ function farmPlugin(options = {}, initialPluginManager) {
19001
19660
  }, "isProgrammaticRouteSourceFile");
19002
19661
  const isAppRuntimeFile = /* @__PURE__ */ __name((file) => {
19003
19662
  const normalized = file.replace(/\\/g, "/");
19004
- return appDirSlugs.some((appDir) => normalized.startsWith(`${appDir}/`)) && /\/(?:page|layout|loading|error|middleware|route)\.(?:ts|tsx|js|jsx|md|mdx)$|\/(?:opengraph-image|twitter-image)(?:\.(?:ts|tsx|js|jsx|png|jpg|jpeg|gif|webp)|\.alt\.txt)$/.test(
19663
+ return appDirSlugs.some((appDir) => normalized.startsWith(`${appDir}/`)) && /\/(?:page|default|layout|loading|error|middleware|route)\.(?:ts|tsx|js|jsx|md|mdx)$|\/(?:opengraph-image|twitter-image)(?:\.(?:ts|tsx|js|jsx|png|jpg|jpeg|gif|webp)|\.alt\.txt)$/.test(
19005
19664
  normalized
19006
19665
  );
19007
19666
  }, "isAppRuntimeFile");
@@ -19692,7 +20351,10 @@ function farmPlugin(options = {}, initialPluginManager) {
19692
20351
  method: req.method || "GET"
19693
20352
  });
19694
20353
  }
19695
- const match = routeManager3.matchRoute(targetPath);
20354
+ const interceptFromHeader = request.headers.get("x-farm-intercept-from") || void 0;
20355
+ const match = routeManager3.matchRoute(targetRequestUrl.pathname, {
20356
+ interceptFrom: interceptFromHeader
20357
+ });
19696
20358
  if (pm) {
19697
20359
  await pm.runHookParallel("afterRouteMatch", {
19698
20360
  pathname: targetPath,
@@ -19708,7 +20370,7 @@ function farmPlugin(options = {}, initialPluginManager) {
19708
20370
  res.end(JSON.stringify({ error: "Route not found" }));
19709
20371
  return;
19710
20372
  }
19711
- const { route, params, layouts } = match;
20373
+ const { route, params, layouts, slots } = match;
19712
20374
  if (!route) {
19713
20375
  res.statusCode = 404;
19714
20376
  res.setHeader("Content-Type", "application/json");
@@ -19763,6 +20425,54 @@ function farmPlugin(options = {}, initialPluginManager) {
19763
20425
  search: searchParams,
19764
20426
  routePath: route.pattern
19765
20427
  });
20428
+ const routeSlots = await Promise.all(
20429
+ slots.map(async (slot) => {
20430
+ const slotModule = await routeManager3.loadRouteModule(slot.route.modulePath);
20431
+ const slotMetadata = getClientModuleMetadata(
20432
+ slot.route.modulePath,
20433
+ server.config.root
20434
+ );
20435
+ const slotContext = await resolveFarmRouteContext(farmApp.getConfig(), {
20436
+ request,
20437
+ params: slot.params,
20438
+ search: searchParams,
20439
+ path: targetUrl.pathname
20440
+ });
20441
+ const slotProps = await parseRouteModuleProps2(slotModule, {
20442
+ props: withFarmRouteContext(
20443
+ {
20444
+ params: slot.params,
20445
+ searchParams: Promise.resolve(searchParams),
20446
+ path: targetUrl.pathname
20447
+ },
20448
+ slotContext
20449
+ ),
20450
+ search: searchParams,
20451
+ routePath: slot.route.pattern
20452
+ });
20453
+ return {
20454
+ name: slot.name,
20455
+ ownerPattern: slot.ownerPattern,
20456
+ containerId: slot.containerId,
20457
+ interception: slot.interception,
20458
+ fallback: slot.fallback,
20459
+ modulePath: slot.route.modulePath,
20460
+ isClientComponent: slotMetadata.isClientComponent,
20461
+ shouldHydrate: slotMetadata.shouldHydrate,
20462
+ props: {
20463
+ params: slotProps.params,
20464
+ search: slotProps.search,
20465
+ searchParams: slotProps.search,
20466
+ ..."data" in slotProps ? { data: slotProps.data } : {},
20467
+ ...slotProps.__farmCanonicalPath ? {
20468
+ __farmCanonicalPath: slotProps.__farmCanonicalPath
20469
+ } : {},
20470
+ ...slotProps.__farmRoutePropsResolved ? { __farmRoutePropsResolved: true } : {},
20471
+ path: targetUrl.pathname
20472
+ }
20473
+ };
20474
+ })
20475
+ );
19766
20476
  const projectRoot = server.config.root;
19767
20477
  const toUrlPath = /* @__PURE__ */ __name((absolutePath) => {
19768
20478
  if (absolutePath.startsWith(projectRoot)) {
@@ -19783,13 +20493,21 @@ function farmPlugin(options = {}, initialPluginManager) {
19783
20493
  },
19784
20494
  canonicalPath: routeProps.__farmCanonicalPath,
19785
20495
  modulePath: toUrlPath(route.modulePath),
19786
- isClientComponent,
19787
- shouldHydrate,
20496
+ isClientComponent: routeSlots.length > 0 ? false : isClientComponent,
20497
+ shouldHydrate: shouldHydrate || routeSlots.some((slot) => slot.isClientComponent || slot.shouldHydrate),
19788
20498
  metadata: {
19789
20499
  title: mergedMetadata.title,
19790
20500
  description: mergedMetadata.description
19791
20501
  },
19792
20502
  layoutModules: layouts.map((l) => toUrlPath(l.modulePath)),
20503
+ routeSlots: routeSlots.map((slot) => ({
20504
+ ...slot,
20505
+ modulePath: toUrlPath(slot.modulePath)
20506
+ })),
20507
+ interception: routeSlots.some((slot) => slot.interception) ? {
20508
+ from: interceptFromHeader,
20509
+ slots: routeSlots.filter((slot) => slot.interception).map((slot) => slot.name)
20510
+ } : void 0,
19793
20511
  i18n: getFarmI18nClientSnapshot()
19794
20512
  };
19795
20513
  await sendWebResponse(
@@ -20125,6 +20843,7 @@ function farmPlugin(options = {}, initialPluginManager) {
20125
20843
  export const getManifest = () => ({
20126
20844
  routes: {},
20127
20845
  layouts: {},
20846
+ slots: [],
20128
20847
  clientEntry: "/@farm/client.js",
20129
20848
  sharedAssets: []
20130
20849
  });
@@ -20135,6 +20854,7 @@ export const getManifest = () => ({
20135
20854
  clientEntry: "/@farm/client.js",
20136
20855
  routes: {},
20137
20856
  layouts: {},
20857
+ slots: [],
20138
20858
  sharedAssets: [
20139
20859
  {
20140
20860
  tag: "link",
@@ -20164,6 +20884,13 @@ export const getManifest = () => ({
20164
20884
  assets: []
20165
20885
  };
20166
20886
  }
20887
+ for (const slot of manifest.slots) {
20888
+ fullManifest.slots.push({
20889
+ ...slot,
20890
+ preloads: [slot.modulePath],
20891
+ assets: []
20892
+ });
20893
+ }
20167
20894
  return `
20168
20895
  // Auto-generated manifest for SPA navigation (TanStack Start pattern)
20169
20896
  // This manifest is inlined in the server bundle - no file on disk
@@ -20806,7 +21533,7 @@ window.__FARM_WRAP_PROVIDERS__ = wrapWithIntegrationProviders;
20806
21533
 
20807
21534
  // Get manifest from window (inlined by server in HTML)
20808
21535
  // Fallback to empty manifest if not available yet
20809
- const getManifest = () => window.__FARM_MANIFEST__ || { routes: {}, layouts: {}, clientEntry: '', sharedAssets: [] };
21536
+ const getManifest = () => window.__FARM_MANIFEST__ || { routes: {}, layouts: {}, slots: [], clientEntry: '', sharedAssets: [] };
20810
21537
 
20811
21538
  // ====== CLIENT-SIDE ROUTE MATCHING ======
20812
21539
  // Matches URL to route using manifest (no server request!)
@@ -21145,6 +21872,9 @@ let LayoutComponent = null;
21145
21872
 
21146
21873
  // Track if we've taken over rendering from SSR
21147
21874
  let hasClientTakenOver = false;
21875
+ const routeSlotRoots = new Map();
21876
+ const routeSlotDefinitions = new Map();
21877
+ let activeRouteInterception = null;
21148
21878
 
21149
21879
  function normalizeServerProps(rawProps) {
21150
21880
  const props = rawProps && typeof rawProps === 'object' ? { ...rawProps } : {};
@@ -21157,6 +21887,123 @@ function normalizeServerProps(rawProps) {
21157
21887
  return props;
21158
21888
  }
21159
21889
 
21890
+ function getRouteSlotKey(slot) {
21891
+ return slot.ownerPattern + ':' + slot.name;
21892
+ }
21893
+
21894
+ async function renderRouteSlot(slot, mode = 'render') {
21895
+ const container = document.getElementById(slot.containerId);
21896
+ if (!container || !slot.modulePath) return false;
21897
+ if (mode === 'intercept' && !slot.isClientComponent && !slot.shouldHydrate) {
21898
+ return false;
21899
+ }
21900
+
21901
+ let slotModule = pageModuleCache.get(slot.modulePath);
21902
+ if (!slotModule) {
21903
+ slotModule = await import(/* @vite-ignore */ slot.modulePath);
21904
+ pageModuleCache.set(slot.modulePath, slotModule);
21905
+ }
21906
+ const SlotComponent = slotModule?.default;
21907
+ if (!SlotComponent) return false;
21908
+
21909
+ const props = reviveDeferredData(
21910
+ normalizeServerProps(slot.props || {}),
21911
+ window.__FARM_DEFERRED_DATA__ || {},
21912
+ );
21913
+ const element = wrapWithIntegrationProviders(
21914
+ React.createElement(SlotComponent, props),
21915
+ );
21916
+ const key = getRouteSlotKey(slot);
21917
+ const existingRoot = routeSlotRoots.get(key);
21918
+
21919
+ if (mode === 'hydrate') {
21920
+ if (existingRoot) return true;
21921
+ const root = hydrateRoot(container, element);
21922
+ routeSlotRoots.set(key, root);
21923
+ } else {
21924
+ if (existingRoot) {
21925
+ try { existingRoot.unmount(); } catch (error) {}
21926
+ }
21927
+ const root = createRoot(container);
21928
+ root.render(element);
21929
+ routeSlotRoots.set(key, root);
21930
+ }
21931
+
21932
+ routeSlotDefinitions.set(key, slot);
21933
+ return true;
21934
+ }
21935
+
21936
+ async function hydrateInitialRouteSlots() {
21937
+ const slots = Array.isArray(window.__FARM_ROUTE_SLOTS__)
21938
+ ? window.__FARM_ROUTE_SLOTS__
21939
+ : [];
21940
+ let hydrated = false;
21941
+
21942
+ for (const slot of slots) {
21943
+ routeSlotDefinitions.set(getRouteSlotKey(slot), slot);
21944
+ if (!slot.isClientComponent && !slot.shouldHydrate) continue;
21945
+ try {
21946
+ hydrated = (await renderRouteSlot(slot, 'hydrate')) || hydrated;
21947
+ } catch (error) {
21948
+ console.warn('[Farm.js] Could not hydrate route slot:', slot.name, error);
21949
+ }
21950
+ }
21951
+
21952
+ return hydrated;
21953
+ }
21954
+
21955
+ async function renderRouteInterception(pageData) {
21956
+ const slots = Array.isArray(pageData.routeSlots)
21957
+ ? pageData.routeSlots.filter((slot) => slot.interception)
21958
+ : [];
21959
+ if (!pageData.interception || slots.length === 0) return false;
21960
+
21961
+ const previous = new Map();
21962
+ for (const slot of slots) {
21963
+ const key = getRouteSlotKey(slot);
21964
+ previous.set(key, routeSlotDefinitions.get(key) || null);
21965
+ if (!(await renderRouteSlot(slot, 'intercept'))) {
21966
+ return false;
21967
+ }
21968
+ }
21969
+
21970
+ activeRouteInterception = {
21971
+ from: pageData.interception.from,
21972
+ slots,
21973
+ previous,
21974
+ };
21975
+ return true;
21976
+ }
21977
+
21978
+ async function clearRouteInterception(destination) {
21979
+ if (!activeRouteInterception) return false;
21980
+ const active = activeRouteInterception;
21981
+ activeRouteInterception = null;
21982
+
21983
+ for (const slot of active.slots) {
21984
+ const key = getRouteSlotKey(slot);
21985
+ const root = routeSlotRoots.get(key);
21986
+ if (root) {
21987
+ try { root.unmount(); } catch (error) {}
21988
+ routeSlotRoots.delete(key);
21989
+ }
21990
+
21991
+ const previous = active.previous.get(key);
21992
+ if (previous && (previous.isClientComponent || previous.shouldHydrate)) {
21993
+ await renderRouteSlot(previous, 'render');
21994
+ } else {
21995
+ const container = document.getElementById(slot.containerId);
21996
+ if (container) container.replaceChildren();
21997
+ if (previous) routeSlotDefinitions.set(key, previous);
21998
+ else routeSlotDefinitions.delete(key);
21999
+ }
22000
+ }
22001
+
22002
+ if (!active.from) return false;
22003
+ const background = new URL(active.from, window.location.origin);
22004
+ return destination === background.pathname + background.search;
22005
+ }
22006
+
21160
22007
  function applyCanonicalPathFromProps(props) {
21161
22008
  const canonicalPath = props && typeof props.__farmCanonicalPath === 'string'
21162
22009
  ? props.__farmCanonicalPath
@@ -21391,6 +22238,17 @@ async function tryHydrateImportedPage(
21391
22238
  async function renderPage(pageData) {
21392
22239
  const container = document.getElementById('root');
21393
22240
  if (!container) return;
22241
+ const destination = window.location.pathname + window.location.search;
22242
+
22243
+ if (pageData.interception) {
22244
+ if (await renderRouteInterception(pageData)) return;
22245
+ window.location.assign(destination);
22246
+ return;
22247
+ }
22248
+
22249
+ if (await clearRouteInterception(destination)) {
22250
+ return;
22251
+ }
21394
22252
 
21395
22253
  const route = {
21396
22254
  modulePath: pageData.modulePath,
@@ -21619,7 +22477,9 @@ async function hydrate() {
21619
22477
  window.__FARM_SHOULD_HYDRATE__ === true ||
21620
22478
  isClientComponent ||
21621
22479
  await moduleLooksClient(modulePath);
22480
+ const hydratedSlots = await hydrateInitialRouteSlots();
21622
22481
  if (!shouldHydrate) {
22482
+ if (hydratedSlots) replayPreHydrationClicks();
21623
22483
  console.log('[Farm.js] Server component - SPA router ready')
21624
22484
  return
21625
22485
  }