@farm.js/core 0.1.0-beta.3 → 0.1.0-beta.4

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 (192) 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-5QH4IKY3.mjs → chunk-3U3ZUQAQ.mjs} +2 -2
  30. package/dist/{chunk-6WXJ54TC.mjs → chunk-65A7QIKI.mjs} +5 -5
  31. package/dist/{chunk-6WXJ54TC.mjs.map → chunk-65A7QIKI.mjs.map} +1 -1
  32. package/dist/{chunk-OHC6VCDI.mjs → chunk-AJWRO5WE.mjs} +521 -42
  33. package/dist/chunk-AJWRO5WE.mjs.map +1 -0
  34. package/dist/chunk-ALCLXXTY.mjs +187 -0
  35. package/dist/chunk-ALCLXXTY.mjs.map +1 -0
  36. package/dist/{chunk-XJSHJN4M.mjs → chunk-AOBXKNS5.mjs} +593 -49
  37. package/dist/chunk-AOBXKNS5.mjs.map +1 -0
  38. package/dist/{chunk-CSU4KJJF.mjs → chunk-C2ROET2V.mjs} +2 -2
  39. package/dist/{chunk-BIOIOWXZ.mjs → chunk-HNIQFZKB.mjs} +2 -2
  40. package/dist/{chunk-G5I34UA6.mjs → chunk-JDEOUK5V.mjs} +4 -4
  41. package/dist/{chunk-T543TMFY.mjs → chunk-NM3CRAAL.mjs} +3 -2
  42. package/dist/chunk-NM3CRAAL.mjs.map +1 -0
  43. package/dist/{chunk-LWMGIEJA.mjs → chunk-PFE6GMNE.mjs} +47 -37
  44. package/dist/chunk-PFE6GMNE.mjs.map +1 -0
  45. package/dist/{chunk-6VS6CKAV.mjs → chunk-QM746KUO.mjs} +8 -1
  46. package/dist/chunk-QM746KUO.mjs.map +1 -0
  47. package/dist/{chunk-SHRJG6OG.mjs → chunk-RBRLLZL5.mjs} +18 -3
  48. package/dist/chunk-RBRLLZL5.mjs.map +1 -0
  49. package/dist/{chunk-MH6FKW2I.mjs → chunk-SADGIRS2.mjs} +3 -3
  50. package/dist/{chunk-NRRFGX45.mjs → chunk-TQAEEJB4.mjs} +3 -3
  51. package/dist/{chunk-45LKHSHJ.mjs → chunk-UB5UOSHB.mjs} +24 -12
  52. package/dist/chunk-UB5UOSHB.mjs.map +1 -0
  53. package/dist/chunk-V3XURYJO.mjs +756 -0
  54. package/dist/chunk-V3XURYJO.mjs.map +1 -0
  55. package/dist/{chunk-5FW6PQ3J.mjs → chunk-VVKWFZAH.mjs} +41 -16
  56. package/dist/chunk-VVKWFZAH.mjs.map +1 -0
  57. package/dist/{chunk-HZYXGV4K.mjs → chunk-VY2UYN5R.mjs} +24 -4
  58. package/dist/chunk-VY2UYN5R.mjs.map +1 -0
  59. package/dist/{chunk-C33RALN7.mjs → chunk-W2CRQFF6.mjs} +79 -15
  60. package/dist/chunk-W2CRQFF6.mjs.map +1 -0
  61. package/dist/{chunk-JNYVN7YP.mjs → chunk-W4UIVMVI.mjs} +2 -2
  62. package/dist/{chunk-JNYVN7YP.mjs.map → chunk-W4UIVMVI.mjs.map} +1 -1
  63. package/dist/{chunk-LG3YINQN.mjs → chunk-Z5CQMP54.mjs} +3 -3
  64. package/dist/{client-DiViK9yK.d.mts → client-BT1mFQas.d.mts} +23 -4
  65. package/dist/{client-DpEBJbts.d.ts → client-Nn0q4eO4.d.ts} +23 -4
  66. package/dist/{client-plugins-CTnsZz_A.d.ts → client-plugins-Cw4qUGsw.d.ts} +6 -0
  67. package/dist/{client-plugins-Cnr0D3kk.d.mts → client-plugins-DQQ1adXM.d.mts} +6 -0
  68. package/dist/client-plugins.d.mts +2 -1
  69. package/dist/client-plugins.d.ts +2 -1
  70. package/dist/client.cjs +665 -82
  71. package/dist/client.cjs.map +1 -1
  72. package/dist/client.d.mts +70 -4
  73. package/dist/client.d.ts +70 -4
  74. package/dist/client.mjs +10 -7
  75. package/dist/config.d.mts +2 -1
  76. package/dist/config.d.ts +2 -1
  77. package/dist/{devtools-HE4FTUDN.mjs → devtools-LVC3VB5P.mjs} +4 -4
  78. package/dist/docs.cjs +11 -1
  79. package/dist/docs.cjs.map +1 -1
  80. package/dist/docs.mjs +4 -4
  81. package/dist/index.cjs +2112 -197
  82. package/dist/index.cjs.map +1 -1
  83. package/dist/index.d.mts +12 -11
  84. package/dist/index.d.ts +12 -11
  85. package/dist/index.mjs +49 -21
  86. package/dist/index.mjs.map +1 -1
  87. package/dist/integrations.d.mts +2 -1
  88. package/dist/integrations.d.ts +2 -1
  89. package/dist/internal/build-runtime.cjs +1455 -90
  90. package/dist/internal/build-runtime.cjs.map +1 -1
  91. package/dist/internal/build-runtime.d.mts +4 -3
  92. package/dist/internal/build-runtime.d.ts +4 -3
  93. package/dist/internal/build-runtime.mjs +15 -14
  94. package/dist/internal/config-runtime.cjs +12 -1
  95. package/dist/internal/config-runtime.cjs.map +1 -1
  96. package/dist/internal/config-runtime.d.mts +3 -2
  97. package/dist/internal/config-runtime.d.ts +3 -2
  98. package/dist/internal/config-runtime.mjs +3 -3
  99. package/dist/internal/production-runtime.cjs +282 -6
  100. package/dist/internal/production-runtime.cjs.map +1 -1
  101. package/dist/internal/production-runtime.d.mts +4 -4
  102. package/dist/internal/production-runtime.d.ts +4 -4
  103. package/dist/internal/production-runtime.mjs +4 -2
  104. package/dist/{metadata-eoVmHoyv.d.ts → metadata-BAPg_Nmo.d.ts} +1 -1
  105. package/dist/{metadata-Dp8AdvsB.d.mts → metadata-_6i9oFG3.d.mts} +1 -1
  106. package/dist/middleware.d.mts +4 -3
  107. package/dist/middleware.d.ts +4 -3
  108. package/dist/navigation.cjs +40 -15
  109. package/dist/navigation.cjs.map +1 -1
  110. package/dist/navigation.mjs +2 -2
  111. package/dist/plugin.cjs +913 -53
  112. package/dist/plugin.cjs.map +1 -1
  113. package/dist/plugin.d.mts +2 -1
  114. package/dist/plugin.d.ts +2 -1
  115. package/dist/plugin.mjs +11 -10
  116. package/dist/{production-runtime-BqTnUTai.d.ts → production-runtime-BoJDc5mc.d.ts} +1 -1
  117. package/dist/{production-runtime-DtDdG9fc.d.mts → production-runtime-DPfgMV9Y.d.mts} +1 -1
  118. package/dist/routes.cjs +276 -6
  119. package/dist/routes.cjs.map +1 -1
  120. package/dist/routes.d.mts +1 -1
  121. package/dist/routes.d.ts +1 -1
  122. package/dist/routes.mjs +3 -3
  123. package/dist/{runtime-C1B7pSGT.d.mts → runtime-9nQjVM7X.d.mts} +8 -9
  124. package/dist/{runtime-CdPHt0bv.d.ts → runtime-DgeS2skB.d.ts} +8 -9
  125. package/dist/server-action-security.cjs +28 -10
  126. package/dist/server-action-security.cjs.map +1 -1
  127. package/dist/server-action-security.d.mts +2 -1
  128. package/dist/server-action-security.d.ts +2 -1
  129. package/dist/server-action-security.mjs +2 -2
  130. package/dist/server-fn-client.d.mts +1 -0
  131. package/dist/server-fn-client.d.ts +1 -0
  132. package/dist/server-fn.cjs +785 -3
  133. package/dist/server-fn.cjs.map +1 -1
  134. package/dist/server-fn.d.mts +10 -1
  135. package/dist/server-fn.d.ts +10 -1
  136. package/dist/server-fn.mjs +6 -3
  137. package/dist/server-plugins.d.mts +3 -2
  138. package/dist/server-plugins.d.ts +3 -2
  139. package/dist/server-query-client.cjs +278 -7
  140. package/dist/server-query-client.cjs.map +1 -1
  141. package/dist/server-query-client.mjs +4 -4
  142. package/dist/server-query.cjs +381 -10
  143. package/dist/server-query.cjs.map +1 -1
  144. package/dist/server-query.mjs +5 -5
  145. package/dist/server.cjs +1086 -76
  146. package/dist/server.cjs.map +1 -1
  147. package/dist/server.d.mts +8 -7
  148. package/dist/server.d.ts +8 -7
  149. package/dist/server.mjs +31 -16
  150. package/dist/server.mjs.map +1 -1
  151. package/dist/testing.cjs +341 -20
  152. package/dist/testing.cjs.map +1 -1
  153. package/dist/testing.d.mts +3 -2
  154. package/dist/testing.d.ts +3 -2
  155. package/dist/testing.mjs +7 -6
  156. package/dist/testing.mjs.map +1 -1
  157. package/dist/transport-BmbGTXA5.d.mts +49 -0
  158. package/dist/transport-BmbGTXA5.d.ts +49 -0
  159. package/dist/{utils-8USa5BiY.d.mts → utils-CHXlMDkg.d.mts} +1 -1
  160. package/dist/{utils-DKJb6K6G.d.ts → utils-Drsyz1Vj.d.ts} +1 -1
  161. package/dist/version.cjs +1 -1
  162. package/dist/version.cjs.map +1 -1
  163. package/dist/version.mjs +1 -1
  164. package/dist/vite.cjs +914 -54
  165. package/dist/vite.cjs.map +1 -1
  166. package/dist/vite.d.mts +2 -1
  167. package/dist/vite.d.ts +2 -1
  168. package/dist/vite.mjs +11 -10
  169. package/package.json +1 -1
  170. package/types/api.d.ts +44 -0
  171. package/types/client.d.ts +85 -4
  172. package/dist/chunk-45LKHSHJ.mjs.map +0 -1
  173. package/dist/chunk-5FW6PQ3J.mjs.map +0 -1
  174. package/dist/chunk-6VS6CKAV.mjs.map +0 -1
  175. package/dist/chunk-C33RALN7.mjs.map +0 -1
  176. package/dist/chunk-GZWT6VI5.mjs +0 -382
  177. package/dist/chunk-GZWT6VI5.mjs.map +0 -1
  178. package/dist/chunk-HZYXGV4K.mjs.map +0 -1
  179. package/dist/chunk-LWMGIEJA.mjs.map +0 -1
  180. package/dist/chunk-OHC6VCDI.mjs.map +0 -1
  181. package/dist/chunk-SHRJG6OG.mjs.map +0 -1
  182. package/dist/chunk-T543TMFY.mjs.map +0 -1
  183. package/dist/chunk-XJSHJN4M.mjs.map +0 -1
  184. package/dist/chunk-XUBZ7JI4.mjs.map +0 -1
  185. /package/dist/{chunk-5QH4IKY3.mjs.map → chunk-3U3ZUQAQ.mjs.map} +0 -0
  186. /package/dist/{chunk-CSU4KJJF.mjs.map → chunk-C2ROET2V.mjs.map} +0 -0
  187. /package/dist/{chunk-BIOIOWXZ.mjs.map → chunk-HNIQFZKB.mjs.map} +0 -0
  188. /package/dist/{chunk-G5I34UA6.mjs.map → chunk-JDEOUK5V.mjs.map} +0 -0
  189. /package/dist/{chunk-MH6FKW2I.mjs.map → chunk-SADGIRS2.mjs.map} +0 -0
  190. /package/dist/{chunk-NRRFGX45.mjs.map → chunk-TQAEEJB4.mjs.map} +0 -0
  191. /package/dist/{chunk-LG3YINQN.mjs.map → chunk-Z5CQMP54.mjs.map} +0 -0
  192. /package/dist/{devtools-HE4FTUDN.mjs.map → devtools-LVC3VB5P.mjs.map} +0 -0
package/dist/vite.cjs CHANGED
@@ -1767,7 +1767,8 @@ var init_route_rules = __esm({
1767
1767
  // src/cache-invalidation.ts
1768
1768
  function getFarmCacheInvalidationState() {
1769
1769
  return globalState[FARM_CACHE_INVALIDATION_STATE] ?? (globalState[FARM_CACHE_INVALIDATION_STATE] = {
1770
- listeners: /* @__PURE__ */ new Set()
1770
+ listeners: /* @__PURE__ */ new Set(),
1771
+ taskListeners: /* @__PURE__ */ new Set()
1771
1772
  });
1772
1773
  }
1773
1774
  function encodeFarmCacheInvalidations(keys) {
@@ -1793,6 +1794,11 @@ function subscribeFarmCacheInvalidation(listener) {
1793
1794
  state.listeners.add(listener);
1794
1795
  return () => state.listeners.delete(listener);
1795
1796
  }
1797
+ function subscribeFarmCacheTask(listener) {
1798
+ const state = getFarmCacheInvalidationState();
1799
+ state.taskListeners.add(listener);
1800
+ return () => state.taskListeners.delete(listener);
1801
+ }
1796
1802
  var FARM_CACHE_INVALIDATION_HEADER, FARM_CACHE_INVALIDATION_STATE, globalState;
1797
1803
  var init_cache_invalidation = __esm({
1798
1804
  "src/cache-invalidation.ts"() {
@@ -1804,6 +1810,7 @@ var init_cache_invalidation = __esm({
1804
1810
  __name(encodeFarmCacheInvalidations, "encodeFarmCacheInvalidations");
1805
1811
  __name(decodeFarmCacheInvalidations, "decodeFarmCacheInvalidations");
1806
1812
  __name(subscribeFarmCacheInvalidation, "subscribeFarmCacheInvalidation");
1813
+ __name(subscribeFarmCacheTask, "subscribeFarmCacheTask");
1807
1814
  }
1808
1815
  });
1809
1816
 
@@ -1907,6 +1914,9 @@ var init_server_action_security = __esm({
1907
1914
  subscribeFarmCacheInvalidation((key) => {
1908
1915
  getServerActionExecutionContext()?.invalidations.add(key);
1909
1916
  });
1917
+ subscribeFarmCacheTask((task) => {
1918
+ getServerActionExecutionContext()?.cacheTasks.add(task);
1919
+ });
1910
1920
  __name(getServerActionStorage, "getServerActionStorage");
1911
1921
  __name(parseBodySizeLimit, "parseBodySizeLimit");
1912
1922
  __name(normalizeAllowedOriginPattern, "normalizeAllowedOriginPattern");
@@ -12075,11 +12085,51 @@ __name(getActiveFarmI18nSnapshot, "getActiveFarmI18nSnapshot");
12075
12085
  // src/cache.ts
12076
12086
  init_cache_invalidation();
12077
12087
  var _FarmDataCache = class _FarmDataCache {
12078
- constructor() {
12088
+ constructor(config = {}) {
12079
12089
  this.entries = /* @__PURE__ */ new Map();
12080
12090
  this.inflight = /* @__PURE__ */ new Map();
12081
12091
  this.invalidatedTagVersions = /* @__PURE__ */ new Map();
12082
12092
  this.version = 0;
12093
+ this.namespace = "farm";
12094
+ this.local = true;
12095
+ this.lease = {
12096
+ enabled: true,
12097
+ ttlMs: 1e4,
12098
+ waitTimeoutMs: 1e4,
12099
+ pollIntervalMs: 25
12100
+ };
12101
+ this.configure(config);
12102
+ }
12103
+ configure(config = {}) {
12104
+ this.adapter = config.adapter;
12105
+ this.namespace = normalizeCacheNamespace(config.namespace || "farm");
12106
+ this.local = !config.adapter;
12107
+ this.lease = config.lease === false ? { ...this.lease, enabled: false } : {
12108
+ enabled: true,
12109
+ ttlMs: normalizePositiveDuration(config.lease?.ttlMs, 1e4, "cache.lease.ttlMs"),
12110
+ waitTimeoutMs: normalizePositiveDuration(
12111
+ config.lease?.waitTimeoutMs,
12112
+ 1e4,
12113
+ "cache.lease.waitTimeoutMs"
12114
+ ),
12115
+ pollIntervalMs: normalizePositiveDuration(
12116
+ config.lease?.pollIntervalMs,
12117
+ 25,
12118
+ "cache.lease.pollIntervalMs"
12119
+ )
12120
+ };
12121
+ if (!this.local) {
12122
+ this.entries.clear();
12123
+ this.inflight.clear();
12124
+ this.invalidatedTagVersions.clear();
12125
+ this.version = 0;
12126
+ }
12127
+ }
12128
+ get adapterName() {
12129
+ return this.adapter?.name || (this.adapter ? "custom" : "memory");
12130
+ }
12131
+ get hasAdapter() {
12132
+ return this.adapter !== void 0;
12083
12133
  }
12084
12134
  get size() {
12085
12135
  return this.entries.size;
@@ -12115,6 +12165,45 @@ var _FarmDataCache = class _FarmDataCache {
12115
12165
  });
12116
12166
  return this.toPublicEntry(entry);
12117
12167
  }
12168
+ async getEntryAsync(key, options = {}) {
12169
+ if (this.local) {
12170
+ const localEntry = this.getEntry(key, options);
12171
+ if (localEntry) return localEntry;
12172
+ }
12173
+ if (!this.adapter) {
12174
+ return this.local ? void 0 : this.getEntry(key, options);
12175
+ }
12176
+ const entry = await this.adapter.get(this.createAdapterKey(key));
12177
+ if (!entry) {
12178
+ emitFarmEvent({ type: "cache.miss", key });
12179
+ return void 0;
12180
+ }
12181
+ assertFarmCacheEntry(entry, key);
12182
+ const stale = await this.isAdapterEntryStale(entry, options.now);
12183
+ if (stale) {
12184
+ emitFarmEvent({
12185
+ type: "cache.stale",
12186
+ key,
12187
+ tags: [...entry.tags],
12188
+ revalidate: entry.revalidate
12189
+ });
12190
+ if (!options.allowStale) {
12191
+ emitFarmEvent({ type: "cache.miss", key, reason: "stale" });
12192
+ return void 0;
12193
+ }
12194
+ }
12195
+ emitFarmEvent({
12196
+ type: "cache.hit",
12197
+ key,
12198
+ tags: [...entry.tags],
12199
+ revalidate: entry.revalidate,
12200
+ stale
12201
+ });
12202
+ if (this.local && !stale) {
12203
+ this.hydrateLocalEntry(entry);
12204
+ }
12205
+ return { ...entry, key };
12206
+ }
12118
12207
  set(key, value, options = {}) {
12119
12208
  const tags = /* @__PURE__ */ new Set();
12120
12209
  for (const tag of options.tags ?? []) {
@@ -12127,6 +12216,7 @@ var _FarmDataCache = class _FarmDataCache {
12127
12216
  key,
12128
12217
  value,
12129
12218
  tags,
12219
+ tagVersions: void 0,
12130
12220
  createdAt: options.createdAt ?? Date.now(),
12131
12221
  createdVersion: ++this.version,
12132
12222
  revalidate: normalizeRevalidate(options.revalidate)
@@ -12140,11 +12230,45 @@ var _FarmDataCache = class _FarmDataCache {
12140
12230
  });
12141
12231
  return this.toPublicEntry(entry);
12142
12232
  }
12233
+ async setAsync(key, value, options = {}, tagVersions) {
12234
+ const tags = normalizeCacheOptionsTags(options);
12235
+ const capturedVersions = tagVersions ?? await this.getAdapterTagVersions(Array.from(tags.values()));
12236
+ const entry = {
12237
+ key,
12238
+ value,
12239
+ tags: Array.from(tags),
12240
+ tagVersions: capturedVersions,
12241
+ createdAt: options.createdAt ?? Date.now(),
12242
+ createdVersion: ++this.version,
12243
+ revalidate: normalizeRevalidate(options.revalidate)
12244
+ };
12245
+ if (this.local) {
12246
+ this.hydrateLocalEntry(entry);
12247
+ }
12248
+ if (this.adapter) {
12249
+ await this.adapter.set(this.createAdapterKey(key), entry);
12250
+ }
12251
+ emitFarmEvent({
12252
+ type: "cache.set",
12253
+ key,
12254
+ tags: [...entry.tags],
12255
+ revalidate: entry.revalidate
12256
+ });
12257
+ return entry;
12258
+ }
12143
12259
  delete(key) {
12144
12260
  const deleted = this.entries.delete(key);
12145
12261
  emitFarmEvent({ type: "cache.delete", key, deleted });
12146
12262
  return deleted;
12147
12263
  }
12264
+ async deleteAsync(key) {
12265
+ const deleted = this.entries.delete(key);
12266
+ if (this.adapter) {
12267
+ await this.adapter.delete(this.createAdapterKey(key));
12268
+ }
12269
+ emitFarmEvent({ type: "cache.delete", key, deleted: this.adapter ? true : deleted });
12270
+ return this.adapter ? true : deleted;
12271
+ }
12148
12272
  clear() {
12149
12273
  const count = this.entries.size;
12150
12274
  this.entries.clear();
@@ -12153,6 +12277,10 @@ var _FarmDataCache = class _FarmDataCache {
12153
12277
  this.version = 0;
12154
12278
  emitFarmEvent({ type: "cache.clear", count });
12155
12279
  }
12280
+ async clearAsync() {
12281
+ this.clear();
12282
+ await this.adapter?.clear?.();
12283
+ }
12156
12284
  isStale(entry, now = Date.now()) {
12157
12285
  if (typeof entry.revalidate === "number" && entry.revalidate > 0 && now - entry.createdAt >= entry.revalidate * 1e3) {
12158
12286
  return true;
@@ -12165,6 +12293,12 @@ var _FarmDataCache = class _FarmDataCache {
12165
12293
  }
12166
12294
  return false;
12167
12295
  }
12296
+ async isStaleAsync(entry, now = Date.now()) {
12297
+ if (this.adapter) {
12298
+ return this.isAdapterEntryStale(entry, now);
12299
+ }
12300
+ return this.isStale(entry, now);
12301
+ }
12168
12302
  revalidateTag(tag, options = {}) {
12169
12303
  const normalized = normalizeCacheTag(tag);
12170
12304
  const count = this.invalidateTag(normalized);
@@ -12173,6 +12307,15 @@ var _FarmDataCache = class _FarmDataCache {
12173
12307
  );
12174
12308
  return count;
12175
12309
  }
12310
+ async revalidateTagAsync(tag, options = {}) {
12311
+ const normalized = normalizeCacheTag(tag);
12312
+ const count = this.invalidateTag(normalized);
12313
+ await this.adapter?.invalidateTags?.([this.createAdapterTag(normalized)]);
12314
+ emitFarmEvent(
12315
+ options.source === "updateTag" ? { type: "cache.updateTag", tag: normalized, count } : { type: "cache.revalidateTag", tag: normalized, profile: options.profile, count }
12316
+ );
12317
+ return count;
12318
+ }
12176
12319
  revalidatePath(routePath) {
12177
12320
  const normalizedPath = normalizeRevalidatePath(routePath);
12178
12321
  const pathTag = createPathCacheTag(normalizedPath);
@@ -12189,8 +12332,25 @@ var _FarmDataCache = class _FarmDataCache {
12189
12332
  }
12190
12333
  return count;
12191
12334
  }
12335
+ async revalidatePathAsync(routePath) {
12336
+ const normalizedPath = normalizeRevalidatePath(routePath);
12337
+ const pathTag = createPathCacheTag(normalizedPath);
12338
+ const pprCount = this.countEntriesForTags([pathTag, "ppr"]);
12339
+ const count = this.invalidateTag(pathTag);
12340
+ await this.adapter?.invalidateTags?.([this.createAdapterTag(pathTag)]);
12341
+ emitFarmEvent({ type: "cache.revalidatePath", path: normalizedPath, count });
12342
+ if (pprCount > 0) {
12343
+ emitFarmEvent({
12344
+ type: "ppr.shell.invalidated",
12345
+ route: normalizedPath,
12346
+ reason: "revalidatePath",
12347
+ count: pprCount
12348
+ });
12349
+ }
12350
+ return count;
12351
+ }
12192
12352
  async getOrSet(key, producer, options = {}) {
12193
- const cached = this.getEntry(key);
12353
+ const cached = await this.getEntryAsync(key);
12194
12354
  if (cached) {
12195
12355
  return cached.value;
12196
12356
  }
@@ -12199,10 +12359,8 @@ var _FarmDataCache = class _FarmDataCache {
12199
12359
  emitFarmEvent({ type: "cache.dedupe", key });
12200
12360
  return inflight;
12201
12361
  }
12202
- const promise = Promise.resolve().then(producer).then((value) => {
12203
- this.set(key, value, options);
12204
- return value;
12205
- }).catch((error) => {
12362
+ const tags = Array.from(normalizeCacheOptionsTags(options));
12363
+ const promise = this.fillCacheEntry(key, producer, options, tags).catch((error) => {
12206
12364
  emitFarmEvent({ type: "cache.error", key, operation: "set", error });
12207
12365
  throw error;
12208
12366
  }).finally(() => {
@@ -12211,6 +12369,40 @@ var _FarmDataCache = class _FarmDataCache {
12211
12369
  this.inflight.set(key, promise);
12212
12370
  return promise;
12213
12371
  }
12372
+ async fillCacheEntry(key, producer, options, tags) {
12373
+ const leaseKey = `${this.namespace}:lease:${key}`;
12374
+ let leaseToken;
12375
+ if (this.adapter?.acquireLease && this.adapter.releaseLease && this.lease.enabled) {
12376
+ leaseToken = await this.adapter.acquireLease(leaseKey, this.lease.ttlMs);
12377
+ if (!leaseToken) {
12378
+ const shared = await this.waitForAdapterEntry(key);
12379
+ if (shared) {
12380
+ emitFarmEvent({ type: "cache.dedupe", key });
12381
+ return shared.value;
12382
+ }
12383
+ leaseToken = await this.adapter.acquireLease(leaseKey, this.lease.ttlMs);
12384
+ }
12385
+ }
12386
+ try {
12387
+ const initialTagVersions = await this.getAdapterTagVersions(tags);
12388
+ const value = await producer();
12389
+ await this.setAsync(key, value, options, initialTagVersions);
12390
+ return value;
12391
+ } finally {
12392
+ if (leaseToken && this.adapter?.releaseLease) {
12393
+ await this.adapter.releaseLease(leaseKey, leaseToken);
12394
+ }
12395
+ }
12396
+ }
12397
+ async waitForAdapterEntry(key) {
12398
+ const deadline = Date.now() + this.lease.waitTimeoutMs;
12399
+ while (Date.now() < deadline) {
12400
+ await delay(this.lease.pollIntervalMs);
12401
+ const entry = await this.getEntryAsync(key);
12402
+ if (entry) return entry;
12403
+ }
12404
+ return void 0;
12405
+ }
12214
12406
  countEntriesForTag(tag) {
12215
12407
  let count = 0;
12216
12408
  for (const entry of this.entries.values()) {
@@ -12238,11 +12430,53 @@ var _FarmDataCache = class _FarmDataCache {
12238
12430
  key: entry.key,
12239
12431
  value: entry.value,
12240
12432
  tags: Array.from(entry.tags),
12433
+ tagVersions: entry.tagVersions,
12241
12434
  createdAt: entry.createdAt,
12242
12435
  createdVersion: entry.createdVersion,
12243
12436
  revalidate: entry.revalidate
12244
12437
  };
12245
12438
  }
12439
+ hydrateLocalEntry(entry) {
12440
+ this.entries.set(entry.key, {
12441
+ key: entry.key,
12442
+ value: entry.value,
12443
+ tags: new Set(entry.tags.map(normalizeCacheTag)),
12444
+ tagVersions: entry.tagVersions,
12445
+ createdAt: entry.createdAt,
12446
+ createdVersion: entry.createdVersion ?? ++this.version,
12447
+ revalidate: normalizeRevalidate(entry.revalidate)
12448
+ });
12449
+ }
12450
+ createAdapterKey(key) {
12451
+ return `${this.namespace}:entry:${key}`;
12452
+ }
12453
+ createAdapterTag(tag) {
12454
+ return `${this.namespace}:tag:${tag}`;
12455
+ }
12456
+ async getAdapterTagVersions(tags) {
12457
+ if (!this.adapter?.getTagVersions || tags.length === 0) return {};
12458
+ const normalized = tags.map(normalizeCacheTag);
12459
+ const physicalTags = normalized.map((tag) => this.createAdapterTag(tag));
12460
+ const versions = await this.adapter.getTagVersions(physicalTags);
12461
+ return Object.fromEntries(
12462
+ normalized.map((tag, index) => [
12463
+ tag,
12464
+ normalizeAdapterVersion(versions[physicalTags[index]])
12465
+ ])
12466
+ );
12467
+ }
12468
+ async isAdapterEntryStale(entry, now = Date.now()) {
12469
+ if (typeof entry.revalidate === "number" && entry.revalidate > 0 && now - entry.createdAt >= entry.revalidate * 1e3) {
12470
+ return true;
12471
+ }
12472
+ const currentVersions = await this.getAdapterTagVersions(entry.tags);
12473
+ for (const tag of entry.tags) {
12474
+ if (normalizeAdapterVersion(currentVersions[tag]) > normalizeAdapterVersion(entry.tagVersions?.[tag])) {
12475
+ return true;
12476
+ }
12477
+ }
12478
+ return false;
12479
+ }
12246
12480
  };
12247
12481
  __name(_FarmDataCache, "FarmDataCache");
12248
12482
  var FarmDataCache = _FarmDataCache;
@@ -12255,6 +12489,10 @@ function getFarmDataCache() {
12255
12489
  return sharedFarmDataCache;
12256
12490
  }
12257
12491
  __name(getFarmDataCache, "getFarmDataCache");
12492
+ function configureFarmCache(config) {
12493
+ sharedFarmDataCache.configure(config);
12494
+ }
12495
+ __name(configureFarmCache, "configureFarmCache");
12258
12496
  function createPathCacheTag(routePath) {
12259
12497
  return `path:${normalizeRevalidatePath(routePath)}`;
12260
12498
  }
@@ -12315,6 +12553,52 @@ function normalizeCacheTag(tag) {
12315
12553
  return normalized;
12316
12554
  }
12317
12555
  __name(normalizeCacheTag, "normalizeCacheTag");
12556
+ function normalizeCacheNamespace(namespace) {
12557
+ if (typeof namespace !== "string") {
12558
+ throw new TypeError("Cache namespace must be a string.");
12559
+ }
12560
+ const normalized = namespace.trim().replace(/:+$/g, "");
12561
+ if (!normalized) {
12562
+ throw new Error("Cache namespace cannot be empty.");
12563
+ }
12564
+ return normalized;
12565
+ }
12566
+ __name(normalizeCacheNamespace, "normalizeCacheNamespace");
12567
+ function normalizeAdapterVersion(value) {
12568
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
12569
+ }
12570
+ __name(normalizeAdapterVersion, "normalizeAdapterVersion");
12571
+ function normalizePositiveDuration(value, fallback, name) {
12572
+ if (value === void 0) return fallback;
12573
+ if (!Number.isFinite(value) || value <= 0) {
12574
+ throw new TypeError(`${name} must be a positive number of milliseconds.`);
12575
+ }
12576
+ return Math.floor(value);
12577
+ }
12578
+ __name(normalizePositiveDuration, "normalizePositiveDuration");
12579
+ function delay(ms) {
12580
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
12581
+ }
12582
+ __name(delay, "delay");
12583
+ function normalizeCacheOptionsTags(options) {
12584
+ const tags = /* @__PURE__ */ new Set();
12585
+ for (const tag of options.tags ?? []) {
12586
+ tags.add(normalizeCacheTag(tag));
12587
+ }
12588
+ for (const routePath of options.paths ?? []) {
12589
+ tags.add(createPathCacheTag(routePath));
12590
+ }
12591
+ return tags;
12592
+ }
12593
+ __name(normalizeCacheOptionsTags, "normalizeCacheOptionsTags");
12594
+ function assertFarmCacheEntry(entry, expectedKey) {
12595
+ if (!entry || typeof entry !== "object" || entry.key !== expectedKey || !Array.isArray(entry.tags) || typeof entry.createdAt !== "number") {
12596
+ throw new TypeError(
12597
+ `Cache adapter returned an invalid entry for ${JSON.stringify(expectedKey)}.`
12598
+ );
12599
+ }
12600
+ }
12601
+ __name(assertFarmCacheEntry, "assertFarmCacheEntry");
12318
12602
  function getFunctionCacheIdentity(fn) {
12319
12603
  if (fn.name) {
12320
12604
  return `name:${fn.name}`;
@@ -13455,6 +13739,76 @@ function normalizePathname(pathname) {
13455
13739
  }
13456
13740
  __name(normalizePathname, "normalizePathname");
13457
13741
 
13742
+ // src/routing/route-slots.ts
13743
+ var SLOT_SEGMENT = /^@([A-Za-z][A-Za-z0-9_-]*)$/;
13744
+ function parseRouteSlotFile(filePath) {
13745
+ const normalized = filePath.replace(/\\/g, "/");
13746
+ const parts = normalized.split("/").filter(Boolean);
13747
+ const fileName = parts.at(-1) ?? "";
13748
+ const baseName = fileName.replace(/\.(tsx?|jsx?)$/, "");
13749
+ if (baseName !== "page" && baseName !== "default") return null;
13750
+ const slotIndex = parts.findIndex((part) => SLOT_SEGMENT.test(part));
13751
+ if (slotIndex < 0) return null;
13752
+ if (parts.slice(slotIndex + 1, -1).some((part) => SLOT_SEGMENT.test(part))) {
13753
+ throw new Error(`Nested route slots are not supported in "${filePath}"`);
13754
+ }
13755
+ const slotMatch = parts[slotIndex].match(SLOT_SEGMENT);
13756
+ const name = slotMatch?.[1];
13757
+ if (!name) return null;
13758
+ const ownerParts = parts.slice(0, slotIndex);
13759
+ const contentParts = parts.slice(slotIndex + 1, -1);
13760
+ const marker = parseInterceptionMarker(contentParts[0]);
13761
+ let targetBase = ownerParts;
13762
+ let targetParts = contentParts;
13763
+ if (marker) {
13764
+ targetBase = marker.root === true ? [] : ownerParts.slice(0, Math.max(0, ownerParts.length - marker.up));
13765
+ targetParts = [...marker.remainder ? [marker.remainder] : [], ...contentParts.slice(1)];
13766
+ }
13767
+ const ownerFile = [...ownerParts, "layout.tsx"].join("/");
13768
+ const routeFile = [
13769
+ ...baseName === "default" ? ownerParts : targetBase,
13770
+ ...baseName === "default" ? [] : targetParts,
13771
+ "page.tsx"
13772
+ ].join("/");
13773
+ return {
13774
+ name,
13775
+ ownerRoute: parseRoutePath(ownerFile || "layout.tsx"),
13776
+ route: parseRoutePath(routeFile || "page.tsx"),
13777
+ interception: marker !== null,
13778
+ fallback: baseName === "default"
13779
+ };
13780
+ }
13781
+ __name(parseRouteSlotFile, "parseRouteSlotFile");
13782
+ function createRouteSlotContainerId(name, ownerPattern) {
13783
+ const owner = ownerPattern === "/" ? "root" : ownerPattern.replace(/^\//, "").replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
13784
+ return `__farm_slot_${name}_${owner || "root"}__`;
13785
+ }
13786
+ __name(createRouteSlotContainerId, "createRouteSlotContainerId");
13787
+ function parseInterceptionMarker(segment) {
13788
+ if (!segment) return null;
13789
+ if (segment.startsWith("(...)")) {
13790
+ return {
13791
+ up: 0,
13792
+ root: true,
13793
+ remainder: segment.slice(5)
13794
+ };
13795
+ }
13796
+ if (segment.startsWith("(.)")) {
13797
+ return {
13798
+ up: 0,
13799
+ remainder: segment.slice(3)
13800
+ };
13801
+ }
13802
+ let remainder = segment;
13803
+ let up = 0;
13804
+ while (remainder.startsWith("(..)")) {
13805
+ up += 1;
13806
+ remainder = remainder.slice(4);
13807
+ }
13808
+ return up > 0 ? { up, remainder } : null;
13809
+ }
13810
+ __name(parseInterceptionMarker, "parseInterceptionMarker");
13811
+
13458
13812
  // src/routing/route-manager.ts
13459
13813
  function routeSpecificity(entry) {
13460
13814
  return entry.route.segments.reduce((score, segment) => {
@@ -13468,6 +13822,7 @@ var _RouteManager = class _RouteManager {
13468
13822
  constructor(config, viteServer) {
13469
13823
  this.routes = /* @__PURE__ */ new Map();
13470
13824
  this.layouts = /* @__PURE__ */ new Map();
13825
+ this.routeSlots = /* @__PURE__ */ new Map();
13471
13826
  this.loadings = /* @__PURE__ */ new Map();
13472
13827
  this.errors = /* @__PURE__ */ new Map();
13473
13828
  this.metadataImages = /* @__PURE__ */ new Map();
@@ -13483,6 +13838,7 @@ var _RouteManager = class _RouteManager {
13483
13838
  async discoverRoutes() {
13484
13839
  this.routes.clear();
13485
13840
  this.layouts.clear();
13841
+ this.routeSlots.clear();
13486
13842
  this.loadings.clear();
13487
13843
  this.errors.clear();
13488
13844
  this.metadataImages.clear();
@@ -13493,6 +13849,13 @@ var _RouteManager = class _RouteManager {
13493
13849
  await this.discoverFileRoutes(source);
13494
13850
  await this.discoverProgrammaticRoutes(source);
13495
13851
  }
13852
+ for (const slot of this.routeSlots.values()) {
13853
+ if (slot.interception && !this.routes.has(slot.pattern)) {
13854
+ throw new Error(
13855
+ `Intercepting route slot "${slot.name}" targets "${slot.pattern}", but no canonical page exists for that URL`
13856
+ );
13857
+ }
13858
+ }
13496
13859
  this.routes = new Map(
13497
13860
  Array.from(this.routes.entries()).sort(
13498
13861
  ([, left], [, right]) => routeSpecificity(right) - routeSpecificity(left)
@@ -13508,7 +13871,7 @@ var _RouteManager = class _RouteManager {
13508
13871
  /**
13509
13872
  * Find matching route for a given URL path
13510
13873
  */
13511
- matchRoute(pathname) {
13874
+ matchRoute(pathname, options = {}) {
13512
13875
  const routePathname = this.toRoutePathname(pathname);
13513
13876
  const normalizedPath = routePathname === "/" ? "/" : routePathname.replace(/\/$/, "");
13514
13877
  let matchedRoute = null;
@@ -13522,10 +13885,12 @@ var _RouteManager = class _RouteManager {
13522
13885
  }
13523
13886
  }
13524
13887
  const layouts = this.findMatchingLayouts(normalizedPath);
13888
+ const slots = this.findMatchingRouteSlots(normalizedPath, options.interceptFrom);
13525
13889
  return {
13526
13890
  route: matchedRoute,
13527
13891
  params,
13528
- layouts
13892
+ layouts,
13893
+ slots
13529
13894
  };
13530
13895
  }
13531
13896
  /**
@@ -13540,6 +13905,9 @@ var _RouteManager = class _RouteManager {
13540
13905
  getLayouts() {
13541
13906
  return new Map(this.layouts);
13542
13907
  }
13908
+ getRouteSlots() {
13909
+ return new Map(this.routeSlots);
13910
+ }
13543
13911
  /** Resolve route rules, inherited layouts, and the page export in precedence order. */
13544
13912
  async resolveRouteRuntimeConfig(pattern) {
13545
13913
  const routeEntry = this.routes.get(pattern);
@@ -13698,7 +14066,22 @@ var _RouteManager = class _RouteManager {
13698
14066
  pattern: entry.pattern,
13699
14067
  modulePath: toUrlPath(entry.modulePath)
13700
14068
  }));
13701
- return { routes, layouts };
14069
+ const slots = Array.from(this.routeSlots.values()).map((entry) => {
14070
+ const metadata = getClientModuleMetadata(entry.modulePath, projectRoot);
14071
+ return {
14072
+ name: entry.name,
14073
+ ownerPattern: entry.ownerPattern,
14074
+ pattern: entry.pattern,
14075
+ modulePath: toUrlPath(entry.modulePath),
14076
+ containerId: entry.containerId,
14077
+ interception: entry.interception,
14078
+ fallback: entry.fallback,
14079
+ shouldHydrate: metadata.shouldHydrate,
14080
+ isClientComponent: metadata.isClientComponent,
14081
+ segments: entry.route.segments
14082
+ };
14083
+ });
14084
+ return { routes, layouts, slots };
13702
14085
  }
13703
14086
  /**
13704
14087
  * Load a route module dynamically
@@ -13775,6 +14158,7 @@ var _RouteManager = class _RouteManager {
13775
14158
  async discoverFileRoutes(source) {
13776
14159
  const appDir = resolveAppPath(source.root, source.srcDir, "app");
13777
14160
  const pageFiles = await safeGlobFiles("**/page.{ts,tsx,js,jsx,md,mdx}", appDir);
14161
+ const slotDefaultFiles = await safeGlobFiles("**/@*/**/default.{ts,tsx,js,jsx}", appDir);
13778
14162
  const layoutFiles = await safeGlobFiles("**/layout.{ts,tsx,js,jsx}", appDir);
13779
14163
  const loadingFiles = await safeGlobFiles("**/loading.{ts,tsx,js,jsx}", appDir);
13780
14164
  const errorFiles = await safeGlobFiles("**/error.{ts,tsx,js,jsx}", appDir);
@@ -13783,7 +14167,7 @@ var _RouteManager = class _RouteManager {
13783
14167
  appDir
13784
14168
  );
13785
14169
  for (const [kind, files, target] of [
13786
- ["page", pageFiles, this.routes],
14170
+ ["page", pageFiles.filter((file) => parseRouteSlotFile(file) === null), this.routes],
13787
14171
  ["layout", layoutFiles, this.layouts],
13788
14172
  ["loading", loadingFiles, this.loadings],
13789
14173
  ["error", errorFiles, this.errors]
@@ -13807,6 +14191,32 @@ var _RouteManager = class _RouteManager {
13807
14191
  });
13808
14192
  }
13809
14193
  }
14194
+ for (const file of [...pageFiles, ...slotDefaultFiles]) {
14195
+ const slot = parseRouteSlotFile(file);
14196
+ if (!slot) continue;
14197
+ const modulePath = import_path5.default.join(appDir, file);
14198
+ const pattern = this.createRoutePattern(slot.route);
14199
+ const ownerPattern = this.createRoutePattern(slot.ownerRoute);
14200
+ const key = `${ownerPattern}:${slot.name}:${slot.interception ? "intercept" : "slot"}:${slot.fallback ? "default" : pattern}`;
14201
+ const existing = this.routeSlots.get(key);
14202
+ if (existing?.sourceRoot === source.root) {
14203
+ throw new Error(
14204
+ `Duplicate route slot "${slot.name}" for "${pattern}". Found both ${existing.modulePath} and ${modulePath}.`
14205
+ );
14206
+ }
14207
+ this.routeSlots.set(key, {
14208
+ route: slot.route,
14209
+ modulePath,
14210
+ pattern,
14211
+ source: "file",
14212
+ sourceRoot: source.root,
14213
+ name: slot.name,
14214
+ ownerPattern,
14215
+ interception: slot.interception,
14216
+ fallback: slot.fallback,
14217
+ containerId: createRouteSlotContainerId(slot.name, ownerPattern)
14218
+ });
14219
+ }
13810
14220
  for (const file of metadataImageFiles) {
13811
14221
  const fileBase = import_path5.default.basename(file, import_path5.default.extname(file));
13812
14222
  const kind = fileBase === "twitter-image" ? "twitter" : "opengraph";
@@ -13932,6 +14342,71 @@ var _RouteManager = class _RouteManager {
13932
14342
  }
13933
14343
  return matchingLayouts;
13934
14344
  }
14345
+ findMatchingRouteSlots(pathname, interceptFrom) {
14346
+ const groups = /* @__PURE__ */ new Map();
14347
+ for (const entry of this.routeSlots.values()) {
14348
+ if (!this.matchesRoutePrefix(pathname, entry.ownerPattern)) continue;
14349
+ const key = `${entry.ownerPattern}:${entry.name}`;
14350
+ const entries = groups.get(key) ?? [];
14351
+ entries.push(entry);
14352
+ groups.set(key, entries);
14353
+ }
14354
+ const normalizedFrom = interceptFrom ? this.toRoutePathname(new URL(interceptFrom, "http://farm.local").pathname) : void 0;
14355
+ const matches = [];
14356
+ for (const entries of groups.values()) {
14357
+ const candidates = entries.filter((entry) => !entry.fallback).filter(
14358
+ (entry) => !entry.interception || normalizedFrom !== void 0 && this.matchesRoutePrefix(normalizedFrom, entry.ownerPattern)
14359
+ ).map((entry) => ({
14360
+ entry,
14361
+ match: matchRoute(pathname, entry.route.segments)
14362
+ })).filter((candidate) => candidate.match.matches).sort((left, right) => {
14363
+ if (left.entry.interception !== right.entry.interception) {
14364
+ return left.entry.interception ? -1 : 1;
14365
+ }
14366
+ return routeSpecificity(right.entry) - routeSpecificity(left.entry);
14367
+ });
14368
+ const selected = candidates[0];
14369
+ if (selected) {
14370
+ matches.push({
14371
+ name: selected.entry.name,
14372
+ ownerPattern: selected.entry.ownerPattern,
14373
+ containerId: selected.entry.containerId,
14374
+ interception: selected.entry.interception,
14375
+ fallback: false,
14376
+ route: selected.entry,
14377
+ params: selected.match.params
14378
+ });
14379
+ continue;
14380
+ }
14381
+ const fallback = entries.find((entry) => entry.fallback);
14382
+ if (fallback) {
14383
+ matches.push({
14384
+ name: fallback.name,
14385
+ ownerPattern: fallback.ownerPattern,
14386
+ containerId: fallback.containerId,
14387
+ interception: false,
14388
+ fallback: true,
14389
+ route: fallback,
14390
+ params: {}
14391
+ });
14392
+ }
14393
+ }
14394
+ return matches.sort((left, right) => {
14395
+ const ownerDifference = left.route.route.segments.length - right.route.route.segments.length;
14396
+ return ownerDifference || left.name.localeCompare(right.name);
14397
+ });
14398
+ }
14399
+ matchesRoutePrefix(pathname, pattern) {
14400
+ if (pattern === "/") return true;
14401
+ const patternSegments = parseRoutePath(`${pattern}/page.tsx`).segments;
14402
+ const pathSegments = pathname.split("/").filter(Boolean);
14403
+ if (patternSegments.length > pathSegments.length) return false;
14404
+ for (let index = 0; index < patternSegments.length; index++) {
14405
+ const segment = patternSegments[index];
14406
+ if (!segment.isDynamic && segment.segment !== pathSegments[index]) return false;
14407
+ }
14408
+ return true;
14409
+ }
13935
14410
  findNearestBoundary(pathname, boundaries) {
13936
14411
  const normalizedPath = pathname === "/" ? "/" : pathname.replace(/\/$/, "");
13937
14412
  const pathSegments = normalizedPath.split("/").filter(Boolean);
@@ -15460,7 +15935,9 @@ var _ServerRenderer = class _ServerRenderer {
15460
15935
  async runWithRequestContext(request, fn) {
15461
15936
  return _runWithCurrentRequest(
15462
15937
  request,
15463
- () => this.i18nRuntime?.config.enabled ? _runWithFarmI18nRequest(this.i18nRuntime, request, fn, { redirect: false }) : fn()
15938
+ () => this.i18nRuntime?.config.enabled ? _runWithFarmI18nRequest(this.i18nRuntime, request, fn, {
15939
+ redirect: false
15940
+ }) : fn()
15464
15941
  );
15465
15942
  }
15466
15943
  async resolveRouteContext(input) {
@@ -15483,11 +15960,11 @@ var _ServerRenderer = class _ServerRenderer {
15483
15960
  /**
15484
15961
  * Check if a path should be served from SSG cache
15485
15962
  */
15486
- shouldServeSSG(pathname) {
15963
+ async shouldServeSSG(pathname) {
15487
15964
  const ssgPage = matchSSGPage(pathname, this.ssgManifest);
15488
15965
  if (!ssgPage) return null;
15489
- const cached = this.getCachedSSGPage(pathname);
15490
- if (cached && this.dataCache.isStale(cached)) {
15966
+ const cached = await this.getCachedSSGPage(pathname);
15967
+ if (cached && await this.dataCache.isStaleAsync(cached)) {
15491
15968
  this.regenerateSSGPage(ssgPage);
15492
15969
  }
15493
15970
  return ssgPage;
@@ -15499,12 +15976,12 @@ var _ServerRenderer = class _ServerRenderer {
15499
15976
  return createFarmCacheKey(["ppr", normalizeRevalidatePath(pathname), search]);
15500
15977
  }
15501
15978
  getCachedSSGPage(urlPath) {
15502
- return this.dataCache.getEntry(this.getSSGCacheKey(urlPath), {
15979
+ return this.dataCache.getEntryAsync(this.getSSGCacheKey(urlPath), {
15503
15980
  allowStale: true
15504
15981
  });
15505
15982
  }
15506
- cacheSSGPage(page2, html, options) {
15507
- this.dataCache.set(
15983
+ async cacheSSGPage(page2, html, options) {
15984
+ await this.dataCache.setAsync(
15508
15985
  this.getSSGCacheKey(page2.urlPath),
15509
15986
  { html, document: options.document },
15510
15987
  {
@@ -15516,11 +15993,11 @@ var _ServerRenderer = class _ServerRenderer {
15516
15993
  );
15517
15994
  }
15518
15995
  getCachedPPRShell(pathname, search) {
15519
- return this.dataCache.getEntry(this.getPPRCacheKey(pathname, search));
15996
+ return this.dataCache.getEntryAsync(this.getPPRCacheKey(pathname, search));
15520
15997
  }
15521
- cachePPRShell(options, html) {
15998
+ async cachePPRShell(options, html) {
15522
15999
  const key = this.getPPRCacheKey(options.pathname, options.search);
15523
- this.dataCache.set(
16000
+ await this.dataCache.setAsync(
15524
16001
  key,
15525
16002
  { html },
15526
16003
  {
@@ -15618,7 +16095,7 @@ var _ServerRenderer = class _ServerRenderer {
15618
16095
  }
15619
16096
  const { renderToString } = await import("react-dom/server");
15620
16097
  const html = renderToString(await this.wrapWithIntegrationProviders(pageElement));
15621
- this.cacheSSGPage(page2, html, { document: false });
16098
+ await this.cacheSSGPage(page2, html, { document: false });
15622
16099
  logger.info(`ISR: Regenerated ${page2.urlPath}`);
15623
16100
  } catch (error) {
15624
16101
  logger.error(`ISR regeneration failed for ${page2.urlPath}: ${error}`);
@@ -15632,7 +16109,7 @@ var _ServerRenderer = class _ServerRenderer {
15632
16109
  * Serve a pre-rendered SSG page
15633
16110
  */
15634
16111
  async serveSSGPage(req, res, page2) {
15635
- const cached = this.getCachedSSGPage(page2.urlPath);
16112
+ const cached = await this.getCachedSSGPage(page2.urlPath);
15636
16113
  if (cached) {
15637
16114
  res.setHeader("Content-Type", "text/html; charset=utf-8");
15638
16115
  res.setHeader("X-Farm-SSG", "cached");
@@ -15650,9 +16127,12 @@ var _ServerRenderer = class _ServerRenderer {
15650
16127
  if (fs2.existsSync(htmlPath)) {
15651
16128
  const stat = fs2.statSync(htmlPath);
15652
16129
  const html = fs2.readFileSync(htmlPath, "utf-8");
15653
- this.cacheSSGPage(page2, html, { document: true, createdAt: stat.mtimeMs });
15654
- const fileCacheEntry = this.getCachedSSGPage(page2.urlPath);
15655
- if (fileCacheEntry && this.dataCache.isStale(fileCacheEntry)) {
16130
+ await this.cacheSSGPage(page2, html, {
16131
+ document: true,
16132
+ createdAt: stat.mtimeMs
16133
+ });
16134
+ const fileCacheEntry = await this.getCachedSSGPage(page2.urlPath);
16135
+ if (fileCacheEntry && await this.dataCache.isStaleAsync(fileCacheEntry)) {
15656
16136
  this.regenerateSSGPage(page2);
15657
16137
  }
15658
16138
  res.setHeader("Content-Type", "text/html; charset=utf-8");
@@ -15698,6 +16178,7 @@ var _ServerRenderer = class _ServerRenderer {
15698
16178
  let pathname = "/";
15699
16179
  let params = {};
15700
16180
  let layouts = [];
16181
+ let routeSlots = [];
15701
16182
  let searchParamsObject = {};
15702
16183
  let middlewareMap = /* @__PURE__ */ new Map();
15703
16184
  let middlewareContext = /* @__PURE__ */ new Map();
@@ -15730,7 +16211,7 @@ var _ServerRenderer = class _ServerRenderer {
15730
16211
  }
15731
16212
  this.applyDeploymentHeaders(req, res);
15732
16213
  if (process.env.NODE_ENV === "production") {
15733
- const ssgPage = this.shouldServeSSG(pathname);
16214
+ const ssgPage = await this.shouldServeSSG(pathname);
15734
16215
  if (ssgPage) {
15735
16216
  const served = await this.serveSSGPage(req, res, ssgPage);
15736
16217
  if (served) {
@@ -15743,6 +16224,7 @@ var _ServerRenderer = class _ServerRenderer {
15743
16224
  const route = match.route;
15744
16225
  params = match.params;
15745
16226
  layouts = match.layouts;
16227
+ routeSlots = match.slots ?? [];
15746
16228
  if (!route) {
15747
16229
  emitFarmEvent({ type: "route.notFound", pathname });
15748
16230
  await this.render404(req, res);
@@ -15759,7 +16241,9 @@ var _ServerRenderer = class _ServerRenderer {
15759
16241
  errorBoundaryEntry = this.routeManager.getMatchingError(pathname);
15760
16242
  middlewareMap = toMiddlewareMap2(req.__FARM_MIDDLEWARE_DATA__);
15761
16243
  middlewareContext = toMiddlewareMap2(req.__FARM_MIDDLEWARE_CONTEXT__);
15762
- pluginExposedContext = getRequestContextSnapshot(req, { exposedOnly: true });
16244
+ pluginExposedContext = getRequestContextSnapshot(req, {
16245
+ exposedOnly: true
16246
+ });
15763
16247
  const currentRequest = createWebRequestFromFarmRequest(req);
15764
16248
  const routeContext = await this.resolveRouteContext({
15765
16249
  request: currentRequest,
@@ -15823,8 +16307,16 @@ var _ServerRenderer = class _ServerRenderer {
15823
16307
  revalidate: renderingConfig.revalidate
15824
16308
  } : void 0;
15825
16309
  if (renderingConfig.ppr && pprBypassReason) {
15826
- emitFarmEvent({ type: "ppr.shell.bypass", route: pathname, reason: pprBypassReason });
15827
- emitFarmEvent({ type: "cache.bypass", route: pathname, reason: pprBypassReason });
16310
+ emitFarmEvent({
16311
+ type: "ppr.shell.bypass",
16312
+ route: pathname,
16313
+ reason: pprBypassReason
16314
+ });
16315
+ emitFarmEvent({
16316
+ type: "cache.bypass",
16317
+ route: pathname,
16318
+ reason: pprBypassReason
16319
+ });
15828
16320
  if (pprBypassReason === "refresh") {
15829
16321
  pprRefreshRoute = pathname;
15830
16322
  emitFarmEvent({ type: "ppr.refresh.start", route: pathname });
@@ -15832,14 +16324,22 @@ var _ServerRenderer = class _ServerRenderer {
15832
16324
  }
15833
16325
  if (pprShellOptions) {
15834
16326
  const pprCacheKey = this.getPPRCacheKey(pathname, url.search);
15835
- const cachedPPRShell = this.getCachedPPRShell(pathname, url.search);
16327
+ const cachedPPRShell = await this.getCachedPPRShell(pathname, url.search);
15836
16328
  if (cachedPPRShell) {
15837
- emitFarmEvent({ type: "ppr.shell.hit", route: pathname, key: pprCacheKey });
16329
+ emitFarmEvent({
16330
+ type: "ppr.shell.hit",
16331
+ route: pathname,
16332
+ key: pprCacheKey
16333
+ });
15838
16334
  this.serveCachedPPRShell(res, cachedPPRShell.value, renderingConfig.revalidate);
15839
16335
  completeRender(res.statusCode || 200, pathname);
15840
16336
  return;
15841
16337
  }
15842
- emitFarmEvent({ type: "ppr.shell.miss", route: pathname, key: pprCacheKey });
16338
+ emitFarmEvent({
16339
+ type: "ppr.shell.miss",
16340
+ route: pathname,
16341
+ key: pprCacheKey
16342
+ });
15843
16343
  }
15844
16344
  let LoadingFallbackComponent = null;
15845
16345
  if (loadingBoundaryEntry) {
@@ -15863,14 +16363,77 @@ var _ServerRenderer = class _ServerRenderer {
15863
16363
  );
15864
16364
  const moduleMetadata = routeManifestEntry || getClientModuleMetadata(route.modulePath, this.config.root);
15865
16365
  const isClientComponent = moduleMetadata.isClientComponent;
16366
+ const renderedRouteSlots = await Promise.all(
16367
+ routeSlots.map(async (slot) => {
16368
+ const slotModule = await this.routeManager.loadRouteModule(slot.route.modulePath);
16369
+ if (!slotModule.default) {
16370
+ throw new Error(
16371
+ `Route slot "${slot.name}" module ${slot.route.modulePath} does not export a default component`
16372
+ );
16373
+ }
16374
+ const slotContext = await this.resolveRouteContext({
16375
+ request: currentRequest,
16376
+ rawRequest: req,
16377
+ params: slot.params,
16378
+ search: searchParamsObject,
16379
+ path: pathname
16380
+ });
16381
+ const rawSlotProps = withFarmRouteContext(
16382
+ {
16383
+ params: slot.params,
16384
+ searchParams: Promise.resolve(searchParamsObject),
16385
+ path: pathname,
16386
+ middleware: middlewareMap.size > 0 ? { data: middlewareMap } : void 0,
16387
+ context: pluginExposedContext.size > 0 ? { data: pluginExposedContext } : void 0
16388
+ },
16389
+ slotContext
16390
+ );
16391
+ const slotProps = await parseRouteModuleProps(slotModule, {
16392
+ props: rawSlotProps,
16393
+ search: searchParamsObject,
16394
+ routePath: slot.route.pattern
16395
+ });
16396
+ const metadata = getClientModuleMetadata(slot.route.modulePath, this.config.root);
16397
+ return {
16398
+ ...slot,
16399
+ module: slotModule,
16400
+ props: slotProps,
16401
+ isClientComponent: metadata.isClientComponent,
16402
+ shouldHydrate: metadata.shouldHydrate
16403
+ };
16404
+ })
16405
+ );
15866
16406
  const shouldHydrate = moduleMetadata.shouldHydrate;
16407
+ const hasHydratableRouteSlots = renderedRouteSlots.some(
16408
+ (slot) => slot.isClientComponent || slot.shouldHydrate
16409
+ );
15867
16410
  req.__FARM_PAGE_PATH__ = route.modulePath;
15868
16411
  req.__FARM_ROUTE__ = pathname;
15869
16412
  req.__FARM_IS_CLIENT_COMPONENT__ = isClientComponent;
15870
16413
  req.__FARM_SHOULD_HYDRATE__ = shouldHydrate;
16414
+ req.__FARM_HAS_HYDRATABLE_ROUTE_SLOTS__ = hasHydratableRouteSlots;
15871
16415
  req.__FARM_LOADING_MODULE_PATH__ = loadingBoundaryEntry?.modulePath ? loadingBoundaryEntry.modulePath.substring(
15872
16416
  loadingBoundaryEntry.modulePath.indexOf("/src/app/")
15873
16417
  ) : null;
16418
+ req.__FARM_ROUTE_SLOTS__ = renderedRouteSlots.map((slot) => ({
16419
+ name: slot.name,
16420
+ ownerPattern: slot.ownerPattern,
16421
+ containerId: slot.containerId,
16422
+ interception: slot.interception,
16423
+ fallback: slot.fallback,
16424
+ modulePath: slot.route.modulePath,
16425
+ isClientComponent: slot.isClientComponent,
16426
+ shouldHydrate: slot.shouldHydrate,
16427
+ props: {
16428
+ params: slot.props.params,
16429
+ search: slot.props.search,
16430
+ searchParams: slot.props.search,
16431
+ ..."data" in slot.props ? { data: slot.props.data } : {},
16432
+ ...slot.props.__farmCanonicalPath ? { __farmCanonicalPath: slot.props.__farmCanonicalPath } : {},
16433
+ ...slot.props.__farmRoutePropsResolved ? { __farmRoutePropsResolved: true } : {},
16434
+ path: pathname
16435
+ }
16436
+ }));
15874
16437
  req.__FARM_PROPS__ = {
15875
16438
  params: pageProps.params,
15876
16439
  search: pageProps.search,
@@ -15930,12 +16493,32 @@ var _ServerRenderer = class _ServerRenderer {
15930
16493
  let wrappedElement = pageElement;
15931
16494
  for (let i = layoutModules.length - 1; i >= 0; i--) {
15932
16495
  const layoutModule = layoutModules[i];
16496
+ const layoutEntry = layouts[i];
15933
16497
  const LayoutComponent = layoutModule.default;
16498
+ const slotProps = {};
16499
+ for (const slot of renderedRouteSlots) {
16500
+ if (slot.ownerPattern !== layoutEntry.pattern) continue;
16501
+ let slotElement = import_react4.default.createElement(
16502
+ slot.module.default,
16503
+ slot.props
16504
+ );
16505
+ slotElement = import_react4.default.createElement(
16506
+ "div",
16507
+ {
16508
+ id: slot.containerId,
16509
+ "data-farm-route-slot": slot.name,
16510
+ "data-farm-slot-owner": slot.ownerPattern
16511
+ },
16512
+ slotElement
16513
+ );
16514
+ slotProps[slot.name] = slotElement;
16515
+ }
15934
16516
  wrappedElement = import_react4.default.createElement(
15935
16517
  LayoutComponent,
15936
16518
  {
15937
16519
  children: wrappedElement,
15938
- params
16520
+ params,
16521
+ ...slotProps
15939
16522
  }
15940
16523
  );
15941
16524
  }
@@ -15972,7 +16555,10 @@ var _ServerRenderer = class _ServerRenderer {
15972
16555
  routeManifest,
15973
16556
  captureStaticShell: Boolean(pprShellOptions),
15974
16557
  observabilityRoute: pathname,
15975
- onSuspenseHoleDetected: pprShellOptions ? () => emitFarmEvent({ type: "ppr.suspense.holeDetected", route: pathname }) : void 0,
16558
+ onSuspenseHoleDetected: pprShellOptions ? () => emitFarmEvent({
16559
+ type: "ppr.suspense.holeDetected",
16560
+ route: pathname
16561
+ }) : void 0,
15976
16562
  onComplete: pprShellOptions && req.method !== "HEAD" ? (html) => this.cachePPRShell(pprShellOptions, html) : void 0
15977
16563
  }
15978
16564
  );
@@ -16020,7 +16606,11 @@ var _ServerRenderer = class _ServerRenderer {
16020
16606
  }
16021
16607
  emitFarmEvent({ type: "render.error", route: pathname, error });
16022
16608
  if (pprRefreshRoute) {
16023
- emitFarmEvent({ type: "ppr.refresh.error", route: pprRefreshRoute, error });
16609
+ emitFarmEvent({
16610
+ type: "ppr.refresh.error",
16611
+ route: pprRefreshRoute,
16612
+ error
16613
+ });
16024
16614
  }
16025
16615
  logger.error(`Error rendering page: ${error}`);
16026
16616
  if (res.headersSent || res.writableEnded) {
@@ -16073,7 +16663,9 @@ var _ServerRenderer = class _ServerRenderer {
16073
16663
  if (typeof layoutModule.generateMetadata === "function") {
16074
16664
  metadata = mergeMetadata(
16075
16665
  metadata,
16076
- await layoutModule.generateMetadata({ params: options.pageProps.params })
16666
+ await layoutModule.generateMetadata({
16667
+ params: options.pageProps.params
16668
+ })
16077
16669
  );
16078
16670
  }
16079
16671
  }
@@ -16304,7 +16896,13 @@ var _ServerRenderer = class _ServerRenderer {
16304
16896
  clientEntry: "/@farm/client.js",
16305
16897
  routes: {},
16306
16898
  layouts: {},
16307
- sharedAssets: [{ tag: "link", attrs: { rel: "stylesheet", href: "/src/app/globals.css" } }]
16899
+ slots: [],
16900
+ sharedAssets: [
16901
+ {
16902
+ tag: "link",
16903
+ attrs: { rel: "stylesheet", href: "/src/app/globals.css" }
16904
+ }
16905
+ ]
16308
16906
  };
16309
16907
  for (const routeEntry of manifest.routes) {
16310
16908
  clientManifest.routes[routeEntry.pattern] = {
@@ -16326,9 +16924,26 @@ var _ServerRenderer = class _ServerRenderer {
16326
16924
  assets: []
16327
16925
  };
16328
16926
  }
16329
- const deferredProps = prepareDeferredData(req.__FARM_PROPS__ || {});
16927
+ for (const slotEntry of manifest.slots ?? []) {
16928
+ clientManifest.slots.push({
16929
+ ...slotEntry,
16930
+ preloads: [slotEntry.modulePath],
16931
+ assets: []
16932
+ });
16933
+ }
16934
+ const routeSlotPayload = (req.__FARM_ROUTE_SLOTS__ || []).map(
16935
+ (slot) => ({
16936
+ ...slot,
16937
+ modulePath: typeof slot.modulePath === "string" && slot.modulePath.startsWith(this.config.root) ? slot.modulePath.slice(this.config.root.length) : slot.modulePath
16938
+ })
16939
+ );
16940
+ const deferredProps = prepareDeferredData({
16941
+ page: req.__FARM_PROPS__ || {},
16942
+ slots: routeSlotPayload
16943
+ });
16330
16944
  const propsScript = `<script>
16331
- window.__FARM_PROPS__ = ${serializeInlineValue(deferredProps.data)};
16945
+ window.__FARM_PROPS__ = ${serializeInlineValue(deferredProps.data.page)};
16946
+ window.__FARM_ROUTE_SLOTS__ = ${serializeInlineValue(deferredProps.data.slots)};
16332
16947
  window.__FARM_DEPLOYMENT_ID__ = ${serializeInlineValue(deploymentId)};
16333
16948
  window.__FARM_PATH__ = ${JSON.stringify(req.__FARM_ROUTE__ || req.url || "/")};
16334
16949
  window.__FARM_IS_CLIENT__ = ${JSON.stringify(isClientComponent)};
@@ -16341,7 +16956,7 @@ window.__FARM_MANIFEST__ = ${JSON.stringify(clientManifest)};
16341
16956
  window.__FARM_INTEGRATION_API_MANIFEST__ = ${JSON.stringify(getRegisteredIntegrationAPIManifest())};
16342
16957
  ${getFarmI18nClientSnapshot() ? `window.__FARM_I18N__ = ${serializeInlineValue(getFarmI18nClientSnapshot())};` : ""}
16343
16958
  </script>`;
16344
- const hydrationClickQueueScript = isClientComponent || req.__FARM_SHOULD_HYDRATE__ === true ? createPreHydrationClickQueueScript() : "";
16959
+ const hydrationClickQueueScript = isClientComponent || req.__FARM_SHOULD_HYDRATE__ === true || req.__FARM_HAS_HYDRATABLE_ROUTE_SLOTS__ === true ? createPreHydrationClickQueueScript() : "";
16345
16960
  const {
16346
16961
  title,
16347
16962
  tags: metaTags,
@@ -16452,7 +17067,11 @@ ${getFarmI18nClientSnapshot() ? `window.__FARM_I18N__ = ${serializeInlineValue(g
16452
17067
  didError = true;
16453
17068
  if (!isFarmRedirectError(error) && !isFarmNotFoundError(error)) {
16454
17069
  logger.error(`SSR shell error: ${error}`);
16455
- emitFarmEvent({ type: "render.error", route: observabilityRoute, error });
17070
+ emitFarmEvent({
17071
+ type: "render.error",
17072
+ route: observabilityRoute,
17073
+ error
17074
+ });
16456
17075
  }
16457
17076
  if (clearMiddlewareData) {
16458
17077
  clearMiddlewareData();
@@ -16463,7 +17082,11 @@ ${getFarmI18nClientSnapshot() ? `window.__FARM_I18N__ = ${serializeInlineValue(g
16463
17082
  didError = true;
16464
17083
  if (!isFarmRedirectError(error) && !isFarmNotFoundError(error)) {
16465
17084
  logger.error(`SSR streaming error: ${error}`);
16466
- emitFarmEvent({ type: "render.error", route: observabilityRoute, error });
17085
+ emitFarmEvent({
17086
+ type: "render.error",
17087
+ route: observabilityRoute,
17088
+ error
17089
+ });
16467
17090
  }
16468
17091
  }
16469
17092
  });
@@ -16639,6 +17262,7 @@ var _FarmApp = class _FarmApp {
16639
17262
  logger.info("Initializing Farm.js application...");
16640
17263
  }
16641
17264
  await initStorage(this.config.storage);
17265
+ await configureFarmCache(this.config.cache);
16642
17266
  await this.i18nRuntime.initialize();
16643
17267
  await this.verifyAppStructure();
16644
17268
  await this.routeManager.discoverRoutes();
@@ -16670,6 +17294,7 @@ var _FarmApp = class _FarmApp {
16670
17294
  preset: config.preset ?? "node-server",
16671
17295
  deploy: config.deploy || {},
16672
17296
  storage: config.storage || {},
17297
+ cache: config.cache || {},
16673
17298
  integrations: config.integrations || {},
16674
17299
  plugins: config.plugins || [],
16675
17300
  migrations: config.migrations || { commands: [] },
@@ -17144,7 +17769,7 @@ async function invokeAPIRouteEndpointInContext(endpoint, request, params) {
17144
17769
  });
17145
17770
  let body = void 0;
17146
17771
  if (request.method.toUpperCase() !== "GET" && request.method.toUpperCase() !== "HEAD") {
17147
- body = await readJSONBody(request);
17772
+ body = await readRequestBody(request);
17148
17773
  }
17149
17774
  const headers = Object.fromEntries(request.headers.entries());
17150
17775
  const types = endpoint.__types || {};
@@ -17248,17 +17873,50 @@ function isFarmContextHandler(endpoint) {
17248
17873
  return firstParameter === "ctx" || firstParameter === "context" || firstParameter.startsWith("{");
17249
17874
  }
17250
17875
  __name(isFarmContextHandler, "isFarmContextHandler");
17251
- async function readJSONBody(request) {
17876
+ async function readRequestBody(request) {
17877
+ const contentType = request.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase();
17252
17878
  try {
17879
+ if (contentType === "multipart/form-data") {
17880
+ return formDataToObject(await request.clone().formData());
17881
+ }
17253
17882
  const text2 = await request.clone().text();
17254
- if (text2) {
17883
+ if (!text2) return void 0;
17884
+ if (contentType === "application/x-www-form-urlencoded") {
17885
+ return searchParamsToObject2(new URLSearchParams(text2));
17886
+ }
17887
+ if (contentType === "application/json" || contentType?.endsWith("+json")) {
17255
17888
  return JSON.parse(text2);
17256
17889
  }
17890
+ return JSON.parse(text2);
17257
17891
  } catch {
17258
17892
  }
17259
17893
  return void 0;
17260
17894
  }
17261
- __name(readJSONBody, "readJSONBody");
17895
+ __name(readRequestBody, "readRequestBody");
17896
+ function formDataToObject(formData) {
17897
+ return entriesToObject(formData.entries());
17898
+ }
17899
+ __name(formDataToObject, "formDataToObject");
17900
+ function searchParamsToObject2(searchParams) {
17901
+ return entriesToObject(searchParams.entries());
17902
+ }
17903
+ __name(searchParamsToObject2, "searchParamsToObject");
17904
+ function entriesToObject(entries) {
17905
+ const output = /* @__PURE__ */ Object.create(null);
17906
+ for (const [key, value] of entries) {
17907
+ if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
17908
+ const current = output[key];
17909
+ if (current === void 0) {
17910
+ output[key] = value;
17911
+ } else if (Array.isArray(current)) {
17912
+ current.push(value);
17913
+ } else {
17914
+ output[key] = [current, value];
17915
+ }
17916
+ }
17917
+ return output;
17918
+ }
17919
+ __name(entriesToObject, "entriesToObject");
17262
17920
  function validateInput(schema, value, error) {
17263
17921
  if (!schema || typeof schema.parse !== "function") {
17264
17922
  return value;
@@ -18413,6 +19071,7 @@ async function generateRouteTypes(options) {
18413
19071
  absolute: false
18414
19072
  });
18415
19073
  for (const file of pageFiles) {
19074
+ if (parseRouteSlotFile(file)) continue;
18416
19075
  const route = parseRoutePath(file);
18417
19076
  if (route.type === "page") {
18418
19077
  const pattern = createRoutePattern(route);
@@ -19148,7 +19807,7 @@ function transformError(id, message) {
19148
19807
  __name(transformError, "transformError");
19149
19808
 
19150
19809
  // package.json
19151
- var version = "0.1.0-beta.3";
19810
+ var version = "0.1.0-beta.4";
19152
19811
 
19153
19812
  // src/version.ts
19154
19813
  var FARM_VERSION = version;
@@ -19776,7 +20435,7 @@ function farmPlugin(options = {}, initialPluginManager) {
19776
20435
  }, "isProgrammaticRouteSourceFile");
19777
20436
  const isAppRuntimeFile = /* @__PURE__ */ __name((file) => {
19778
20437
  const normalized = file.replace(/\\/g, "/");
19779
- 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(
20438
+ 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(
19780
20439
  normalized
19781
20440
  );
19782
20441
  }, "isAppRuntimeFile");
@@ -20467,7 +21126,10 @@ function farmPlugin(options = {}, initialPluginManager) {
20467
21126
  method: req.method || "GET"
20468
21127
  });
20469
21128
  }
20470
- const match = routeManager3.matchRoute(targetPath);
21129
+ const interceptFromHeader = request.headers.get("x-farm-intercept-from") || void 0;
21130
+ const match = routeManager3.matchRoute(targetRequestUrl.pathname, {
21131
+ interceptFrom: interceptFromHeader
21132
+ });
20471
21133
  if (pm) {
20472
21134
  await pm.runHookParallel("afterRouteMatch", {
20473
21135
  pathname: targetPath,
@@ -20483,7 +21145,7 @@ function farmPlugin(options = {}, initialPluginManager) {
20483
21145
  res.end(JSON.stringify({ error: "Route not found" }));
20484
21146
  return;
20485
21147
  }
20486
- const { route, params, layouts } = match;
21148
+ const { route, params, layouts, slots } = match;
20487
21149
  if (!route) {
20488
21150
  res.statusCode = 404;
20489
21151
  res.setHeader("Content-Type", "application/json");
@@ -20538,6 +21200,54 @@ function farmPlugin(options = {}, initialPluginManager) {
20538
21200
  search: searchParams,
20539
21201
  routePath: route.pattern
20540
21202
  });
21203
+ const routeSlots = await Promise.all(
21204
+ slots.map(async (slot) => {
21205
+ const slotModule = await routeManager3.loadRouteModule(slot.route.modulePath);
21206
+ const slotMetadata = getClientModuleMetadata(
21207
+ slot.route.modulePath,
21208
+ server.config.root
21209
+ );
21210
+ const slotContext = await resolveFarmRouteContext(farmApp.getConfig(), {
21211
+ request,
21212
+ params: slot.params,
21213
+ search: searchParams,
21214
+ path: targetUrl.pathname
21215
+ });
21216
+ const slotProps = await parseRouteModuleProps2(slotModule, {
21217
+ props: withFarmRouteContext(
21218
+ {
21219
+ params: slot.params,
21220
+ searchParams: Promise.resolve(searchParams),
21221
+ path: targetUrl.pathname
21222
+ },
21223
+ slotContext
21224
+ ),
21225
+ search: searchParams,
21226
+ routePath: slot.route.pattern
21227
+ });
21228
+ return {
21229
+ name: slot.name,
21230
+ ownerPattern: slot.ownerPattern,
21231
+ containerId: slot.containerId,
21232
+ interception: slot.interception,
21233
+ fallback: slot.fallback,
21234
+ modulePath: slot.route.modulePath,
21235
+ isClientComponent: slotMetadata.isClientComponent,
21236
+ shouldHydrate: slotMetadata.shouldHydrate,
21237
+ props: {
21238
+ params: slotProps.params,
21239
+ search: slotProps.search,
21240
+ searchParams: slotProps.search,
21241
+ ..."data" in slotProps ? { data: slotProps.data } : {},
21242
+ ...slotProps.__farmCanonicalPath ? {
21243
+ __farmCanonicalPath: slotProps.__farmCanonicalPath
21244
+ } : {},
21245
+ ...slotProps.__farmRoutePropsResolved ? { __farmRoutePropsResolved: true } : {},
21246
+ path: targetUrl.pathname
21247
+ }
21248
+ };
21249
+ })
21250
+ );
20541
21251
  const projectRoot = server.config.root;
20542
21252
  const toUrlPath = /* @__PURE__ */ __name((absolutePath) => {
20543
21253
  if (absolutePath.startsWith(projectRoot)) {
@@ -20558,13 +21268,21 @@ function farmPlugin(options = {}, initialPluginManager) {
20558
21268
  },
20559
21269
  canonicalPath: routeProps.__farmCanonicalPath,
20560
21270
  modulePath: toUrlPath(route.modulePath),
20561
- isClientComponent,
20562
- shouldHydrate,
21271
+ isClientComponent: routeSlots.length > 0 ? false : isClientComponent,
21272
+ shouldHydrate: shouldHydrate || routeSlots.some((slot) => slot.isClientComponent || slot.shouldHydrate),
20563
21273
  metadata: {
20564
21274
  title: mergedMetadata.title,
20565
21275
  description: mergedMetadata.description
20566
21276
  },
20567
21277
  layoutModules: layouts.map((l) => toUrlPath(l.modulePath)),
21278
+ routeSlots: routeSlots.map((slot) => ({
21279
+ ...slot,
21280
+ modulePath: toUrlPath(slot.modulePath)
21281
+ })),
21282
+ interception: routeSlots.some((slot) => slot.interception) ? {
21283
+ from: interceptFromHeader,
21284
+ slots: routeSlots.filter((slot) => slot.interception).map((slot) => slot.name)
21285
+ } : void 0,
20568
21286
  i18n: getFarmI18nClientSnapshot()
20569
21287
  };
20570
21288
  await sendWebResponse(
@@ -20900,6 +21618,7 @@ function farmPlugin(options = {}, initialPluginManager) {
20900
21618
  export const getManifest = () => ({
20901
21619
  routes: {},
20902
21620
  layouts: {},
21621
+ slots: [],
20903
21622
  clientEntry: "/@farm/client.js",
20904
21623
  sharedAssets: []
20905
21624
  });
@@ -20910,6 +21629,7 @@ export const getManifest = () => ({
20910
21629
  clientEntry: "/@farm/client.js",
20911
21630
  routes: {},
20912
21631
  layouts: {},
21632
+ slots: [],
20913
21633
  sharedAssets: [
20914
21634
  {
20915
21635
  tag: "link",
@@ -20939,6 +21659,13 @@ export const getManifest = () => ({
20939
21659
  assets: []
20940
21660
  };
20941
21661
  }
21662
+ for (const slot of manifest.slots) {
21663
+ fullManifest.slots.push({
21664
+ ...slot,
21665
+ preloads: [slot.modulePath],
21666
+ assets: []
21667
+ });
21668
+ }
20942
21669
  return `
20943
21670
  // Auto-generated manifest for SPA navigation (TanStack Start pattern)
20944
21671
  // This manifest is inlined in the server bundle - no file on disk
@@ -21581,7 +22308,7 @@ window.__FARM_WRAP_PROVIDERS__ = wrapWithIntegrationProviders;
21581
22308
 
21582
22309
  // Get manifest from window (inlined by server in HTML)
21583
22310
  // Fallback to empty manifest if not available yet
21584
- const getManifest = () => window.__FARM_MANIFEST__ || { routes: {}, layouts: {}, clientEntry: '', sharedAssets: [] };
22311
+ const getManifest = () => window.__FARM_MANIFEST__ || { routes: {}, layouts: {}, slots: [], clientEntry: '', sharedAssets: [] };
21585
22312
 
21586
22313
  // ====== CLIENT-SIDE ROUTE MATCHING ======
21587
22314
  // Matches URL to route using manifest (no server request!)
@@ -21920,6 +22647,9 @@ let LayoutComponent = null;
21920
22647
 
21921
22648
  // Track if we've taken over rendering from SSR
21922
22649
  let hasClientTakenOver = false;
22650
+ const routeSlotRoots = new Map();
22651
+ const routeSlotDefinitions = new Map();
22652
+ let activeRouteInterception = null;
21923
22653
 
21924
22654
  function normalizeServerProps(rawProps) {
21925
22655
  const props = rawProps && typeof rawProps === 'object' ? { ...rawProps } : {};
@@ -21932,6 +22662,123 @@ function normalizeServerProps(rawProps) {
21932
22662
  return props;
21933
22663
  }
21934
22664
 
22665
+ function getRouteSlotKey(slot) {
22666
+ return slot.ownerPattern + ':' + slot.name;
22667
+ }
22668
+
22669
+ async function renderRouteSlot(slot, mode = 'render') {
22670
+ const container = document.getElementById(slot.containerId);
22671
+ if (!container || !slot.modulePath) return false;
22672
+ if (mode === 'intercept' && !slot.isClientComponent && !slot.shouldHydrate) {
22673
+ return false;
22674
+ }
22675
+
22676
+ let slotModule = pageModuleCache.get(slot.modulePath);
22677
+ if (!slotModule) {
22678
+ slotModule = await import(/* @vite-ignore */ slot.modulePath);
22679
+ pageModuleCache.set(slot.modulePath, slotModule);
22680
+ }
22681
+ const SlotComponent = slotModule?.default;
22682
+ if (!SlotComponent) return false;
22683
+
22684
+ const props = reviveDeferredData(
22685
+ normalizeServerProps(slot.props || {}),
22686
+ window.__FARM_DEFERRED_DATA__ || {},
22687
+ );
22688
+ const element = wrapWithIntegrationProviders(
22689
+ React.createElement(SlotComponent, props),
22690
+ );
22691
+ const key = getRouteSlotKey(slot);
22692
+ const existingRoot = routeSlotRoots.get(key);
22693
+
22694
+ if (mode === 'hydrate') {
22695
+ if (existingRoot) return true;
22696
+ const root = hydrateRoot(container, element);
22697
+ routeSlotRoots.set(key, root);
22698
+ } else {
22699
+ if (existingRoot) {
22700
+ try { existingRoot.unmount(); } catch (error) {}
22701
+ }
22702
+ const root = createRoot(container);
22703
+ root.render(element);
22704
+ routeSlotRoots.set(key, root);
22705
+ }
22706
+
22707
+ routeSlotDefinitions.set(key, slot);
22708
+ return true;
22709
+ }
22710
+
22711
+ async function hydrateInitialRouteSlots() {
22712
+ const slots = Array.isArray(window.__FARM_ROUTE_SLOTS__)
22713
+ ? window.__FARM_ROUTE_SLOTS__
22714
+ : [];
22715
+ let hydrated = false;
22716
+
22717
+ for (const slot of slots) {
22718
+ routeSlotDefinitions.set(getRouteSlotKey(slot), slot);
22719
+ if (!slot.isClientComponent && !slot.shouldHydrate) continue;
22720
+ try {
22721
+ hydrated = (await renderRouteSlot(slot, 'hydrate')) || hydrated;
22722
+ } catch (error) {
22723
+ console.warn('[Farm.js] Could not hydrate route slot:', slot.name, error);
22724
+ }
22725
+ }
22726
+
22727
+ return hydrated;
22728
+ }
22729
+
22730
+ async function renderRouteInterception(pageData) {
22731
+ const slots = Array.isArray(pageData.routeSlots)
22732
+ ? pageData.routeSlots.filter((slot) => slot.interception)
22733
+ : [];
22734
+ if (!pageData.interception || slots.length === 0) return false;
22735
+
22736
+ const previous = new Map();
22737
+ for (const slot of slots) {
22738
+ const key = getRouteSlotKey(slot);
22739
+ previous.set(key, routeSlotDefinitions.get(key) || null);
22740
+ if (!(await renderRouteSlot(slot, 'intercept'))) {
22741
+ return false;
22742
+ }
22743
+ }
22744
+
22745
+ activeRouteInterception = {
22746
+ from: pageData.interception.from,
22747
+ slots,
22748
+ previous,
22749
+ };
22750
+ return true;
22751
+ }
22752
+
22753
+ async function clearRouteInterception(destination) {
22754
+ if (!activeRouteInterception) return false;
22755
+ const active = activeRouteInterception;
22756
+ activeRouteInterception = null;
22757
+
22758
+ for (const slot of active.slots) {
22759
+ const key = getRouteSlotKey(slot);
22760
+ const root = routeSlotRoots.get(key);
22761
+ if (root) {
22762
+ try { root.unmount(); } catch (error) {}
22763
+ routeSlotRoots.delete(key);
22764
+ }
22765
+
22766
+ const previous = active.previous.get(key);
22767
+ if (previous && (previous.isClientComponent || previous.shouldHydrate)) {
22768
+ await renderRouteSlot(previous, 'render');
22769
+ } else {
22770
+ const container = document.getElementById(slot.containerId);
22771
+ if (container) container.replaceChildren();
22772
+ if (previous) routeSlotDefinitions.set(key, previous);
22773
+ else routeSlotDefinitions.delete(key);
22774
+ }
22775
+ }
22776
+
22777
+ if (!active.from) return false;
22778
+ const background = new URL(active.from, window.location.origin);
22779
+ return destination === background.pathname + background.search;
22780
+ }
22781
+
21935
22782
  function applyCanonicalPathFromProps(props) {
21936
22783
  const canonicalPath = props && typeof props.__farmCanonicalPath === 'string'
21937
22784
  ? props.__farmCanonicalPath
@@ -22166,6 +23013,17 @@ async function tryHydrateImportedPage(
22166
23013
  async function renderPage(pageData) {
22167
23014
  const container = document.getElementById('root');
22168
23015
  if (!container) return;
23016
+ const destination = window.location.pathname + window.location.search;
23017
+
23018
+ if (pageData.interception) {
23019
+ if (await renderRouteInterception(pageData)) return;
23020
+ window.location.assign(destination);
23021
+ return;
23022
+ }
23023
+
23024
+ if (await clearRouteInterception(destination)) {
23025
+ return;
23026
+ }
22169
23027
 
22170
23028
  const route = {
22171
23029
  modulePath: pageData.modulePath,
@@ -22394,7 +23252,9 @@ async function hydrate() {
22394
23252
  window.__FARM_SHOULD_HYDRATE__ === true ||
22395
23253
  isClientComponent ||
22396
23254
  await moduleLooksClient(modulePath);
23255
+ const hydratedSlots = await hydrateInitialRouteSlots();
22397
23256
  if (!shouldHydrate) {
23257
+ if (hydratedSlots) replayPreHydrationClicks();
22398
23258
  console.log('[Farm.js] Server component - SPA router ready')
22399
23259
  return
22400
23260
  }