@nifrajs/web 4.0.2 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (384) hide show
  1. package/README.md +11 -0
  2. package/dist/build-vite.d.ts +19 -0
  3. package/dist/build-vite.d.ts.map +1 -1
  4. package/dist/build-vite.js +31 -10
  5. package/dist/build-vite.js.map +1 -1
  6. package/dist/build.d.ts +8 -10
  7. package/dist/build.d.ts.map +1 -1
  8. package/dist/build.js +90 -31
  9. package/dist/build.js.map +1 -1
  10. package/dist/cdn.d.ts +8 -0
  11. package/dist/cdn.d.ts.map +1 -1
  12. package/dist/cdn.js +14 -15
  13. package/dist/cdn.js.map +1 -1
  14. package/dist/client.d.ts +1 -1
  15. package/dist/client.d.ts.map +1 -1
  16. package/dist/client.js +25 -7
  17. package/dist/client.js.map +1 -1
  18. package/dist/deferred.d.ts +2 -2
  19. package/dist/deferred.d.ts.map +1 -1
  20. package/dist/deferred.js +28 -7
  21. package/dist/deferred.js.map +1 -1
  22. package/dist/dev-feed.d.ts.map +1 -1
  23. package/dist/dev-feed.js +24 -5
  24. package/dist/dev-feed.js.map +1 -1
  25. package/dist/dev-session.d.ts.map +1 -1
  26. package/dist/dev-session.js +4 -14
  27. package/dist/dev-session.js.map +1 -1
  28. package/dist/dev.d.ts +7 -17
  29. package/dist/dev.d.ts.map +1 -1
  30. package/dist/dev.js +13 -1
  31. package/dist/dev.js.map +1 -1
  32. package/dist/draft.d.ts +8 -5
  33. package/dist/draft.d.ts.map +1 -1
  34. package/dist/draft.js +30 -13
  35. package/dist/draft.js.map +1 -1
  36. package/dist/fonts-google.d.ts.map +1 -1
  37. package/dist/fonts-google.js +7 -0
  38. package/dist/fonts-google.js.map +1 -1
  39. package/dist/index.d.ts +2 -2
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +2 -2
  42. package/dist/index.js.map +1 -1
  43. package/dist/internal/codegen.d.ts +1 -0
  44. package/dist/internal/codegen.d.ts.map +1 -1
  45. package/dist/internal/codegen.js +56 -29
  46. package/dist/internal/codegen.js.map +1 -1
  47. package/dist/internal/disposable-dir.d.ts +8 -0
  48. package/dist/internal/disposable-dir.d.ts.map +1 -0
  49. package/dist/internal/disposable-dir.js +25 -0
  50. package/dist/internal/disposable-dir.js.map +1 -0
  51. package/dist/internal/entry-chunks.d.ts +9 -0
  52. package/dist/internal/entry-chunks.d.ts.map +1 -0
  53. package/dist/internal/entry-chunks.js +15 -0
  54. package/dist/internal/entry-chunks.js.map +1 -0
  55. package/dist/internal/evidence-traversal.d.ts +12 -0
  56. package/dist/internal/evidence-traversal.d.ts.map +1 -0
  57. package/dist/internal/evidence-traversal.js +48 -0
  58. package/dist/internal/evidence-traversal.js.map +1 -0
  59. package/dist/internal/head-merge.d.ts +6 -0
  60. package/dist/internal/head-merge.d.ts.map +1 -1
  61. package/dist/internal/head-merge.js +9 -0
  62. package/dist/internal/head-merge.js.map +1 -1
  63. package/dist/internal/js-string.d.ts +2 -0
  64. package/dist/internal/js-string.d.ts.map +1 -1
  65. package/dist/internal/js-string.js +5 -1
  66. package/dist/internal/js-string.js.map +1 -1
  67. package/dist/internal/mount-shadow.js +1 -1
  68. package/dist/internal/mount-shadow.js.map +1 -1
  69. package/dist/internal/output-guard.d.ts +7 -0
  70. package/dist/internal/output-guard.d.ts.map +1 -1
  71. package/dist/internal/output-guard.js +80 -8
  72. package/dist/internal/output-guard.js.map +1 -1
  73. package/dist/internal/page-execution.d.ts +6 -0
  74. package/dist/internal/page-execution.d.ts.map +1 -1
  75. package/dist/internal/page-execution.js +377 -108
  76. package/dist/internal/page-execution.js.map +1 -1
  77. package/dist/internal/progressive-cookies.d.ts +14 -0
  78. package/dist/internal/progressive-cookies.d.ts.map +1 -0
  79. package/dist/internal/progressive-cookies.js +43 -0
  80. package/dist/internal/progressive-cookies.js.map +1 -0
  81. package/dist/internal/public-env.d.ts +2 -0
  82. package/dist/internal/public-env.d.ts.map +1 -0
  83. package/dist/internal/public-env.js +13 -0
  84. package/dist/internal/public-env.js.map +1 -0
  85. package/dist/internal/render-document.d.ts +1 -0
  86. package/dist/internal/render-document.d.ts.map +1 -1
  87. package/dist/internal/render-document.js +28 -5
  88. package/dist/internal/render-document.js.map +1 -1
  89. package/dist/internal/response-controls.d.ts +2 -0
  90. package/dist/internal/response-controls.d.ts.map +1 -1
  91. package/dist/internal/response-controls.js +26 -16
  92. package/dist/internal/response-controls.js.map +1 -1
  93. package/dist/internal/revive-dates.d.ts +16 -0
  94. package/dist/internal/revive-dates.d.ts.map +1 -0
  95. package/dist/internal/revive-dates.js +32 -0
  96. package/dist/internal/revive-dates.js.map +1 -0
  97. package/dist/internal/route-path.d.ts +8 -0
  98. package/dist/internal/route-path.d.ts.map +1 -0
  99. package/dist/internal/route-path.js +8 -0
  100. package/dist/internal/route-path.js.map +1 -0
  101. package/dist/internal/secret-scan.d.ts.map +1 -1
  102. package/dist/internal/secret-scan.js +88 -20
  103. package/dist/internal/secret-scan.js.map +1 -1
  104. package/dist/internal/server-boundary.d.ts.map +1 -1
  105. package/dist/internal/server-boundary.js +33 -5
  106. package/dist/internal/server-boundary.js.map +1 -1
  107. package/dist/internal/web-app.d.ts +21 -16
  108. package/dist/internal/web-app.d.ts.map +1 -1
  109. package/dist/internal/web-app.js +73 -21
  110. package/dist/internal/web-app.js.map +1 -1
  111. package/dist/internal/wire-date.d.ts +3 -0
  112. package/dist/internal/wire-date.d.ts.map +1 -0
  113. package/dist/internal/wire-date.js +14 -0
  114. package/dist/internal/wire-date.js.map +1 -0
  115. package/dist/internal/zone-graph.d.ts +4 -0
  116. package/dist/internal/zone-graph.d.ts.map +1 -1
  117. package/dist/internal/zone-graph.js +54 -14
  118. package/dist/internal/zone-graph.js.map +1 -1
  119. package/dist/isr.d.ts +58 -2
  120. package/dist/isr.d.ts.map +1 -1
  121. package/dist/isr.js +296 -39
  122. package/dist/isr.js.map +1 -1
  123. package/dist/llms-txt.d.ts +2 -0
  124. package/dist/llms-txt.d.ts.map +1 -1
  125. package/dist/llms-txt.js +29 -3
  126. package/dist/llms-txt.js.map +1 -1
  127. package/dist/manifest.d.ts +28 -4
  128. package/dist/manifest.d.ts.map +1 -1
  129. package/dist/manifest.js +3 -0
  130. package/dist/manifest.js.map +1 -1
  131. package/dist/open-resolved.d.ts +18 -0
  132. package/dist/open-resolved.d.ts.map +1 -0
  133. package/dist/open-resolved.js +62 -0
  134. package/dist/open-resolved.js.map +1 -0
  135. package/dist/plugins/kit.d.ts +1 -0
  136. package/dist/plugins/kit.d.ts.map +1 -1
  137. package/dist/plugins/kit.js +26 -11
  138. package/dist/plugins/kit.js.map +1 -1
  139. package/dist/plugins/vite-leak-guard.d.ts +3 -1
  140. package/dist/plugins/vite-leak-guard.d.ts.map +1 -1
  141. package/dist/plugins/vite-leak-guard.js +4 -0
  142. package/dist/plugins/vite-leak-guard.js.map +1 -1
  143. package/dist/public-dir.d.ts +8 -1
  144. package/dist/public-dir.d.ts.map +1 -1
  145. package/dist/public-dir.js +17 -6
  146. package/dist/public-dir.js.map +1 -1
  147. package/dist/query.d.ts +13 -4
  148. package/dist/query.d.ts.map +1 -1
  149. package/dist/query.js +89 -26
  150. package/dist/query.js.map +1 -1
  151. package/dist/render-seam.d.ts +2 -0
  152. package/dist/render-seam.d.ts.map +1 -1
  153. package/dist/render-seam.js +1 -0
  154. package/dist/render-seam.js.map +1 -1
  155. package/dist/route-types.d.ts.map +1 -1
  156. package/dist/route-types.js +20 -9
  157. package/dist/route-types.js.map +1 -1
  158. package/dist/router.d.ts +9 -2
  159. package/dist/router.d.ts.map +1 -1
  160. package/dist/router.js +39 -46
  161. package/dist/router.js.map +1 -1
  162. package/dist/server-components/build-info.d.ts +3 -0
  163. package/dist/server-components/build-info.d.ts.map +1 -0
  164. package/dist/server-components/build-info.js +7 -0
  165. package/dist/server-components/build-info.js.map +1 -0
  166. package/dist/server-components/build-target.d.ts +12 -0
  167. package/dist/server-components/build-target.d.ts.map +1 -0
  168. package/dist/server-components/build-target.js +153 -0
  169. package/dist/server-components/build-target.js.map +1 -0
  170. package/dist/server-components/build.d.ts +24 -0
  171. package/dist/server-components/build.d.ts.map +1 -0
  172. package/dist/server-components/build.js +366 -0
  173. package/dist/server-components/build.js.map +1 -0
  174. package/dist/server-components/bundle-proof.d.ts +3 -0
  175. package/dist/server-components/bundle-proof.d.ts.map +1 -0
  176. package/dist/server-components/bundle-proof.js +23 -0
  177. package/dist/server-components/bundle-proof.js.map +1 -0
  178. package/dist/server-components/client.d.ts +46 -0
  179. package/dist/server-components/client.d.ts.map +1 -0
  180. package/dist/server-components/client.js +594 -0
  181. package/dist/server-components/client.js.map +1 -0
  182. package/dist/server-components/definition.d.ts +11 -0
  183. package/dist/server-components/definition.d.ts.map +1 -0
  184. package/dist/server-components/definition.js +94 -0
  185. package/dist/server-components/definition.js.map +1 -0
  186. package/dist/server-components/dev-http.d.ts +5 -0
  187. package/dist/server-components/dev-http.d.ts.map +1 -0
  188. package/dist/server-components/dev-http.js +71 -0
  189. package/dist/server-components/dev-http.js.map +1 -0
  190. package/dist/server-components/error.d.ts +9 -0
  191. package/dist/server-components/error.d.ts.map +1 -0
  192. package/dist/server-components/error.js +24 -0
  193. package/dist/server-components/error.js.map +1 -0
  194. package/dist/server-components/frames.d.ts +7 -0
  195. package/dist/server-components/frames.d.ts.map +1 -0
  196. package/dist/server-components/frames.js +169 -0
  197. package/dist/server-components/frames.js.map +1 -0
  198. package/dist/server-components/html-policy.d.ts +6 -0
  199. package/dist/server-components/html-policy.d.ts.map +1 -0
  200. package/dist/server-components/html-policy.js +75 -0
  201. package/dist/server-components/html-policy.js.map +1 -0
  202. package/dist/server-components/html.d.ts +4 -0
  203. package/dist/server-components/html.d.ts.map +1 -0
  204. package/dist/server-components/html.js +67 -0
  205. package/dist/server-components/html.js.map +1 -0
  206. package/dist/server-components/islands.d.ts +44 -0
  207. package/dist/server-components/islands.d.ts.map +1 -0
  208. package/dist/server-components/islands.js +42 -0
  209. package/dist/server-components/islands.js.map +1 -0
  210. package/dist/server-components/native.d.ts +4 -0
  211. package/dist/server-components/native.d.ts.map +1 -0
  212. package/dist/server-components/native.js +81 -0
  213. package/dist/server-components/native.js.map +1 -0
  214. package/dist/server-components/portal.d.ts +6 -0
  215. package/dist/server-components/portal.d.ts.map +1 -0
  216. package/dist/server-components/portal.js +35 -0
  217. package/dist/server-components/portal.js.map +1 -0
  218. package/dist/server-components/props.d.ts +3 -0
  219. package/dist/server-components/props.d.ts.map +1 -0
  220. package/dist/server-components/props.js +14 -0
  221. package/dist/server-components/props.js.map +1 -0
  222. package/dist/server-components/protocol.d.ts +44 -0
  223. package/dist/server-components/protocol.d.ts.map +1 -0
  224. package/dist/server-components/protocol.js +139 -0
  225. package/dist/server-components/protocol.js.map +1 -0
  226. package/dist/server-components/range.d.ts +24 -0
  227. package/dist/server-components/range.d.ts.map +1 -0
  228. package/dist/server-components/range.js +263 -0
  229. package/dist/server-components/range.js.map +1 -0
  230. package/dist/server-components/registry.d.ts +6 -0
  231. package/dist/server-components/registry.d.ts.map +1 -0
  232. package/dist/server-components/registry.js +30 -0
  233. package/dist/server-components/registry.js.map +1 -0
  234. package/dist/server-components/response.d.ts +4 -0
  235. package/dist/server-components/response.d.ts.map +1 -0
  236. package/dist/server-components/response.js +32 -0
  237. package/dist/server-components/response.js.map +1 -0
  238. package/dist/server-components/router-integration.d.ts +9 -0
  239. package/dist/server-components/router-integration.d.ts.map +1 -0
  240. package/dist/server-components/router-integration.js +29 -0
  241. package/dist/server-components/router-integration.js.map +1 -0
  242. package/dist/server-components/runtime.d.ts +14 -0
  243. package/dist/server-components/runtime.d.ts.map +1 -0
  244. package/dist/server-components/runtime.js +276 -0
  245. package/dist/server-components/runtime.js.map +1 -0
  246. package/dist/server-components/scheduler.d.ts +14 -0
  247. package/dist/server-components/scheduler.d.ts.map +1 -0
  248. package/dist/server-components/scheduler.js +74 -0
  249. package/dist/server-components/scheduler.js.map +1 -0
  250. package/dist/server-components/schema.d.ts +4 -0
  251. package/dist/server-components/schema.d.ts.map +1 -0
  252. package/dist/server-components/schema.js +77 -0
  253. package/dist/server-components/schema.js.map +1 -0
  254. package/dist/server-components/server-entry.d.ts +12 -0
  255. package/dist/server-components/server-entry.d.ts.map +1 -0
  256. package/dist/server-components/server-entry.js +90 -0
  257. package/dist/server-components/server-entry.js.map +1 -0
  258. package/dist/server-components/source.d.ts +15 -0
  259. package/dist/server-components/source.d.ts.map +1 -0
  260. package/dist/server-components/source.js +269 -0
  261. package/dist/server-components/source.js.map +1 -0
  262. package/dist/server-components/state.d.ts +4 -0
  263. package/dist/server-components/state.d.ts.map +1 -0
  264. package/dist/server-components/state.js +123 -0
  265. package/dist/server-components/state.js.map +1 -0
  266. package/dist/server-components/stream.d.ts +6 -0
  267. package/dist/server-components/stream.d.ts.map +1 -0
  268. package/dist/server-components/stream.js +70 -0
  269. package/dist/server-components/stream.js.map +1 -0
  270. package/dist/server-components/trusted-html.d.ts +3 -0
  271. package/dist/server-components/trusted-html.d.ts.map +1 -0
  272. package/dist/server-components/trusted-html.js +14 -0
  273. package/dist/server-components/trusted-html.js.map +1 -0
  274. package/dist/server-components/types.d.ts +107 -0
  275. package/dist/server-components/types.d.ts.map +1 -0
  276. package/dist/server-components/types.js +2 -0
  277. package/dist/server-components/types.js.map +1 -0
  278. package/dist/server-components/typescript.d.ts +11 -0
  279. package/dist/server-components/typescript.d.ts.map +1 -0
  280. package/dist/server-components/typescript.js +28 -0
  281. package/dist/server-components/typescript.js.map +1 -0
  282. package/dist/server-components/value.d.ts +5 -0
  283. package/dist/server-components/value.d.ts.map +1 -0
  284. package/dist/server-components/value.js +77 -0
  285. package/dist/server-components/value.js.map +1 -0
  286. package/dist/server-components/vite.d.ts +12 -0
  287. package/dist/server-components/vite.d.ts.map +1 -0
  288. package/dist/server-components/vite.js +197 -0
  289. package/dist/server-components/vite.js.map +1 -0
  290. package/dist/server-components.d.ts +5 -0
  291. package/dist/server-components.d.ts.map +1 -0
  292. package/dist/server-components.js +4 -0
  293. package/dist/server-components.js.map +1 -0
  294. package/dist/service-worker.d.ts +8 -1
  295. package/dist/service-worker.d.ts.map +1 -1
  296. package/dist/service-worker.js +20 -6
  297. package/dist/service-worker.js.map +1 -1
  298. package/dist/vite.d.ts +12 -0
  299. package/dist/vite.d.ts.map +1 -1
  300. package/dist/vite.js +7 -1
  301. package/dist/vite.js.map +1 -1
  302. package/dist/zones.d.ts +40 -0
  303. package/dist/zones.d.ts.map +1 -1
  304. package/dist/zones.js +170 -6
  305. package/dist/zones.js.map +1 -1
  306. package/package.json +97 -4
  307. package/src/build-vite.ts +31 -9
  308. package/src/build.ts +109 -31
  309. package/src/cdn.ts +18 -12
  310. package/src/client.ts +25 -6
  311. package/src/deferred.ts +27 -9
  312. package/src/dev-feed.ts +24 -5
  313. package/src/dev-session.ts +4 -13
  314. package/src/dev.ts +29 -1
  315. package/src/draft.ts +35 -14
  316. package/src/fonts-google.ts +10 -0
  317. package/src/index.ts +3 -2
  318. package/src/internal/codegen.ts +60 -29
  319. package/src/internal/disposable-dir.ts +24 -0
  320. package/src/internal/entry-chunks.ts +18 -0
  321. package/src/internal/evidence-traversal.ts +57 -0
  322. package/src/internal/head-merge.ts +16 -0
  323. package/src/internal/js-string.ts +6 -1
  324. package/src/internal/mount-shadow.ts +1 -1
  325. package/src/internal/output-guard.ts +88 -7
  326. package/src/internal/page-execution.ts +506 -108
  327. package/src/internal/progressive-cookies.ts +60 -0
  328. package/src/internal/public-env.ts +14 -0
  329. package/src/internal/render-document.ts +26 -11
  330. package/src/internal/response-controls.ts +31 -14
  331. package/src/internal/revive-dates.ts +35 -0
  332. package/src/internal/route-path.ts +8 -0
  333. package/src/internal/secret-scan.ts +90 -18
  334. package/src/internal/server-boundary.ts +29 -5
  335. package/src/internal/web-app.ts +116 -37
  336. package/src/internal/wire-date.ts +16 -0
  337. package/src/internal/zone-graph.ts +59 -19
  338. package/src/isr.ts +351 -43
  339. package/src/llms-txt.ts +32 -3
  340. package/src/manifest.ts +28 -4
  341. package/src/open-resolved.ts +64 -0
  342. package/src/plugins/kit.ts +31 -11
  343. package/src/plugins/vite-leak-guard.ts +5 -1
  344. package/src/public-dir.ts +27 -8
  345. package/src/query.ts +107 -26
  346. package/src/render-seam.ts +2 -0
  347. package/src/route-types.ts +34 -7
  348. package/src/router.ts +58 -55
  349. package/src/server-components/build-info.ts +11 -0
  350. package/src/server-components/build-target.ts +194 -0
  351. package/src/server-components/build.ts +419 -0
  352. package/src/server-components/bundle-proof.ts +33 -0
  353. package/src/server-components/client.ts +663 -0
  354. package/src/server-components/definition.ts +125 -0
  355. package/src/server-components/dev-http.ts +75 -0
  356. package/src/server-components/error.ts +24 -0
  357. package/src/server-components/frames.ts +181 -0
  358. package/src/server-components/html-policy.ts +94 -0
  359. package/src/server-components/html.ts +81 -0
  360. package/src/server-components/islands.ts +111 -0
  361. package/src/server-components/native.ts +101 -0
  362. package/src/server-components/portal.ts +42 -0
  363. package/src/server-components/props.ts +19 -0
  364. package/src/server-components/protocol.ts +167 -0
  365. package/src/server-components/range.ts +313 -0
  366. package/src/server-components/registry.ts +34 -0
  367. package/src/server-components/response.ts +44 -0
  368. package/src/server-components/router-integration.ts +40 -0
  369. package/src/server-components/runtime.ts +376 -0
  370. package/src/server-components/scheduler.ts +84 -0
  371. package/src/server-components/schema.ts +69 -0
  372. package/src/server-components/server-entry.ts +98 -0
  373. package/src/server-components/source.ts +324 -0
  374. package/src/server-components/state.ts +143 -0
  375. package/src/server-components/stream.ts +71 -0
  376. package/src/server-components/trusted-html.ts +21 -0
  377. package/src/server-components/types.ts +138 -0
  378. package/src/server-components/typescript.ts +39 -0
  379. package/src/server-components/value.ts +82 -0
  380. package/src/server-components/vite.ts +242 -0
  381. package/src/server-components.ts +32 -0
  382. package/src/service-worker.ts +24 -7
  383. package/src/vite.ts +8 -1
  384. package/src/zones.ts +208 -7
package/src/zones.ts CHANGED
@@ -23,6 +23,8 @@ export type Zone =
23
23
  | "shared"
24
24
  /** A server-function module (`*.fn.ts`); the browser only ever receives its generated RPC stub. */
25
25
  | "fn"
26
+ /** A compiler-owned server UI module; only opaque reference stubs may reach a browser. */
27
+ | "component"
26
28
  /** Framework output (the client entry, the server composition, generated types). */
27
29
  | "generated"
28
30
  /** A third-party package, or a workspace package declaring `"nifra": { "environment": "library" }`. */
@@ -38,7 +40,12 @@ export type Classification =
38
40
  /** A third-party file's path inside `node_modules`: `drizzle-orm/node-postgres/index.js`. */
39
41
  readonly modulePath?: string
40
42
  }
41
- | { readonly zone: "error"; readonly reason: string }
43
+ | {
44
+ readonly zone: "error"
45
+ readonly reason: string
46
+ /** The undeclared package this error is about: every file in it shares the one fix. */
47
+ readonly package?: string
48
+ }
42
49
 
43
50
  export type PackageEnvironment = "frontend" | "backend" | "shared" | "library"
44
51
  const PACKAGE_ENVIRONMENTS: ReadonlySet<string> = new Set([
@@ -113,6 +120,7 @@ const SOURCE = "(?:[cm]?[jt]sx?|svelte|vue|mdx)"
113
120
  export const ROUTE_BACKEND_FILE = /\.backend\.(?:[cm]?[jt]s)$/
114
121
  const ZONE_SUFFIX = new RegExp(`\\.(frontend|backend|shared|fn)\\.${SOURCE}$`)
115
122
  const LEGACY_SERVER_SUFFIX = new RegExp(`\\.server\\.${SOURCE}$`)
123
+ export const SERVER_COMPONENT_FILE = /\.component\.server\.[cm]?[jt]sx?$/
116
124
 
117
125
  const ZONE_FOLDERS: Readonly<Record<string, Zone>> = {
118
126
  frontend: "frontend",
@@ -128,8 +136,169 @@ export interface ZoneClassifierOptions {
128
136
  readonly routesDir?: string
129
137
  /** Extra absolute files treated as `generated` - a build's own entry modules. */
130
138
  readonly generatedFiles?: readonly string[]
139
+ /** The app's zone settings. Default: `"nifra": { "zones": ... }` in the app's package.json. */
140
+ readonly zones?: ZoneConfig
141
+ }
142
+
143
+ /**
144
+ * App-side zone settings, read from `"nifra": { "zones": ... }` in the app's package.json so every
145
+ * build, dev server, `nifra check` and the editor plugin see the same ones without running app code.
146
+ */
147
+ export interface ZoneConfig {
148
+ /**
149
+ * The side of a package that does not declare one in its own package.json, by name: exact
150
+ * (`"@shop/db"`) or a trailing-`*` prefix (`"@shop/*"`). The exact name wins over a prefix, the
151
+ * longest prefix over a shorter one, and a package's own declaration over all of them.
152
+ */
153
+ readonly packages?: Readonly<Record<string, PackageEnvironment>>
154
+ /** App-relative folders holding backend code, beside `backend/` (`"migrations"`). */
155
+ readonly backend?: readonly string[]
156
+ /** App-relative folders holding frontend code, beside `frontend/`. */
157
+ readonly frontend?: readonly string[]
158
+ /** App-relative folders holding code both sides import, beside `shared/`. */
159
+ readonly shared?: readonly string[]
131
160
  }
132
161
 
162
+ /** A `"nifra": { "zones": ... }` setting that cannot be applied. Thrown when a classifier is created. */
163
+ export class ZoneConfigError extends Error {
164
+ override readonly name = "ZoneConfigError"
165
+ /** The file holding the setting, as the caller named it. */
166
+ readonly source: string
167
+ constructor(source: string, problem: string) {
168
+ super(`[nifra] ${source} "nifra.zones": ${problem}`)
169
+ this.source = source
170
+ }
171
+ }
172
+
173
+ const FOLDER_SIDES = ["backend", "frontend", "shared"] as const
174
+ const FOLDER_SIDE_NAMES: ReadonlySet<string> = new Set(FOLDER_SIDES)
175
+ const isPackageEnvironment = (value: unknown): value is PackageEnvironment =>
176
+ typeof value === "string" && PACKAGE_ENVIRONMENTS.has(value)
177
+ const RESERVED_FOLDERS: ReadonlySet<string> = new Set([
178
+ "routes",
179
+ "frontend",
180
+ "backend",
181
+ "shared",
182
+ "public",
183
+ "node_modules",
184
+ ])
185
+
186
+ const recordOf = (value: unknown): Readonly<Record<string, unknown>> | undefined =>
187
+ typeof value === "object" && value !== null && !Array.isArray(value)
188
+ ? Object.fromEntries(Object.entries(value))
189
+ : undefined
190
+
191
+ /**
192
+ * Validate a `zones` setting. `source` names where it came from in the error. Folders are plain
193
+ * app-relative paths (a trailing `/**` is accepted and dropped); any other glob is refused, so a
194
+ * pattern can never match less than its author meant without saying so.
195
+ */
196
+ export function parseZoneConfig(value: unknown, source: string): ZoneConfig {
197
+ const fail = (message: string): never => {
198
+ throw new ZoneConfigError(source, message)
199
+ }
200
+ const record = recordOf(value) ?? fail("must be an object")
201
+ const config: {
202
+ packages?: Record<string, PackageEnvironment>
203
+ backend?: string[]
204
+ frontend?: string[]
205
+ shared?: string[]
206
+ } = {}
207
+ for (const key of Object.keys(record)) {
208
+ if (key !== "packages" && !FOLDER_SIDE_NAMES.has(key))
209
+ fail(`unknown key "${key}" (expected packages, backend, frontend or shared)`)
210
+ }
211
+ if (record.packages !== undefined) {
212
+ const packages = recordOf(record.packages) ?? fail('"packages" must be an object')
213
+ const parsed: Record<string, PackageEnvironment> = {}
214
+ for (const [pattern, side] of Object.entries(packages)) {
215
+ if (pattern === "" || pattern.indexOf("*") !== pattern.lastIndexOf("*"))
216
+ fail(`package pattern "${pattern}" must be a name, or a name prefix ending in one "*"`)
217
+ if (pattern.includes("*") && !pattern.endsWith("*"))
218
+ fail(`package pattern "${pattern}" may use "*" only at its end`)
219
+ parsed[pattern] = isPackageEnvironment(side)
220
+ ? side
221
+ : fail(`packages["${pattern}"] must be one of frontend, backend, shared, library`)
222
+ }
223
+ config.packages = parsed
224
+ }
225
+ const owner = new Map<string, string>()
226
+ for (const side of FOLDER_SIDES) {
227
+ const raw = record[side]
228
+ if (raw === undefined) continue
229
+ const entries: readonly unknown[] = Array.isArray(raw)
230
+ ? raw
231
+ : fail(`"${side}" must be an array of app-relative folders`)
232
+ const folders: string[] = []
233
+ for (const entry of entries) {
234
+ const folder = (typeof entry === "string" ? entry : fail(`"${side}" must hold strings`))
235
+ .replaceAll("\\", "/")
236
+ .replace(/^\.\//, "")
237
+ .replace(/\/\*\*$/, "")
238
+ .replace(/\/+$/, "")
239
+ const top = folder.split("/")[0] ?? ""
240
+ if (folder === "" || folder.startsWith("/") || /^[a-z]:/i.test(folder))
241
+ fail(`"${side}" entry "${entry}" must be a folder relative to the app`)
242
+ if (folder.split("/").includes("..")) fail(`"${side}" entry "${entry}" may not contain ".."`)
243
+ if (/[*?[\]{}!]/.test(folder))
244
+ fail(`"${side}" entry "${entry}" must be a plain folder path (a trailing "/**" is allowed)`)
245
+ if (RESERVED_FOLDERS.has(top) || top.startsWith(".nifra"))
246
+ fail(`"${side}" entry "${entry}" is inside ${top}/, which already has its own rules`)
247
+ for (const [other, otherSide] of owner) {
248
+ const nested =
249
+ other === folder || other.startsWith(`${folder}/`) || folder.startsWith(`${other}/`)
250
+ if (nested && otherSide !== side)
251
+ fail(`"${folder}" (${side}) overlaps "${other}" (${otherSide}); a folder has one side`)
252
+ }
253
+ owner.set(folder, side)
254
+ folders.push(folder)
255
+ }
256
+ config[side] = folders
257
+ }
258
+ return config
259
+ }
260
+
261
+ /** The `nifra.zones` of the nearest package.json at or above `appRoot`, validated. */
262
+ function appZoneConfig(appRoot: string): ZoneConfig {
263
+ let dir = appRoot
264
+ for (;;) {
265
+ const manifest = join(dir, "package.json")
266
+ if (existsSync(manifest)) {
267
+ let parsed: unknown
268
+ try {
269
+ parsed = JSON.parse(readFileSync(manifest, "utf8"))
270
+ } catch {
271
+ return {}
272
+ }
273
+ const zones = recordOf(recordOf(parsed)?.nifra)?.zones
274
+ return zones === undefined ? {} : parseZoneConfig(zones, toPosix(manifest))
275
+ }
276
+ const parent = dirname(dir)
277
+ if (parent === dir) return {}
278
+ dir = parent
279
+ }
280
+ }
281
+
282
+ /** The side `packages` gives `name`: the exact entry, else the longest matching prefix. */
283
+ function mappedSide(
284
+ packages: Readonly<Record<string, PackageEnvironment>> | undefined,
285
+ name: string | undefined,
286
+ ): PackageEnvironment | undefined {
287
+ if (packages === undefined || name === undefined) return undefined
288
+ const exact = packages[name]
289
+ if (exact !== undefined && Object.hasOwn(packages, name)) return exact
290
+ let best: string | undefined
291
+ for (const pattern of Object.keys(packages)) {
292
+ if (!pattern.endsWith("*") || !name.startsWith(pattern.slice(0, -1))) continue
293
+ if (best === undefined || pattern.length > best.length) best = pattern
294
+ }
295
+ return best === undefined ? undefined : packages[best]
296
+ }
297
+
298
+ /** How the `packages` setting would name `name`: its scope wildcard when it has a scope. */
299
+ const suggestedPattern = (name: string): string =>
300
+ name.startsWith("@") && name.includes("/") ? `${name.slice(0, name.indexOf("/"))}/*` : name
301
+
133
302
  export interface ZoneClassifier {
134
303
  readonly appRoot: string
135
304
  /** Classify a file by its absolute (or appRoot-relative) path. Results are cached per path. */
@@ -198,6 +367,18 @@ export function createZoneClassifier(options: ZoneClassifierOptions): ZoneClassi
198
367
  const appRoot = realFile(resolve(options.appRoot))
199
368
  const routesDir = realFile(resolve(options.routesDir ?? join(options.appRoot, "routes")))
200
369
  const generated = new Set((options.generatedFiles ?? []).map((file) => realFile(resolve(file))))
370
+ const zones =
371
+ options.zones === undefined
372
+ ? appZoneConfig(appRoot)
373
+ : parseZoneConfig(options.zones, "ZoneClassifierOptions")
374
+ const configuredFolders: Array<readonly [string, "backend" | "frontend" | "shared"]> = []
375
+ for (const side of FOLDER_SIDES)
376
+ for (const folder of zones[side] ?? []) configuredFolders.push([folder, side])
377
+ const folderSide = (rel: string): "backend" | "frontend" | "shared" | undefined => {
378
+ for (const [folder, side] of configuredFolders)
379
+ if (rel === folder || rel.startsWith(`${folder}/`)) return side
380
+ return undefined
381
+ }
201
382
  const cache = new Map<string, Classification>()
202
383
  const packages = new Map<string, PackageInfo | null>()
203
384
 
@@ -246,7 +427,7 @@ export function createZoneClassifier(options: ZoneClassifierOptions): ZoneClassi
246
427
  const declared =
247
428
  info?.environment !== undefined && PACKAGE_ENVIRONMENTS.has(info.environment)
248
429
  ? (info.environment as PackageEnvironment)
249
- : undefined
430
+ : mappedSide(zones.packages, thirdParty)
250
431
  return {
251
432
  zone: "library",
252
433
  packageName: thirdParty,
@@ -259,6 +440,8 @@ export function createZoneClassifier(options: ZoneClassifierOptions): ZoneClassi
259
440
  if (SERVER_MANIFEST_FILE.test(base) && isGeneratedServerManifest(file)) {
260
441
  return { zone: "generated" }
261
442
  }
443
+ if (SERVER_COMPONENT_FILE.test(base)) return { zone: "component" }
444
+ if (/\.island\.[cm]?[jt]sx?$/.test(base)) return { zone: "frontend" }
262
445
  if (LEGACY_SERVER_SUFFIX.test(base)) {
263
446
  return {
264
447
  zone: "error",
@@ -289,7 +472,7 @@ export function createZoneClassifier(options: ZoneClassifierOptions): ZoneClassi
289
472
  if (suffix === "shared") return { zone: "shared" }
290
473
  return { zone: "route-frontend" }
291
474
  }
292
- const folder = ZONE_FOLDERS[top]
475
+ const folder = ZONE_FOLDERS[top] ?? folderSide(rel)
293
476
  if (folder !== undefined) {
294
477
  if (suffix === undefined || suffix === folder) return { zone: folder }
295
478
  if (suffix === "fn" && folder === "backend") return { zone: "fn" }
@@ -303,8 +486,11 @@ export function createZoneClassifier(options: ZoneClassifierOptions): ZoneClassi
303
486
  }
304
487
  // First-party code outside the app: a workspace package, or a relative import out of the app.
305
488
  const info = packageFor(toPosix(dirname(file)))
306
- const environment = info?.environment
307
- if (environment !== undefined && PACKAGE_ENVIRONMENTS.has(environment)) {
489
+ const environment =
490
+ info?.environment !== undefined && PACKAGE_ENVIRONMENTS.has(info.environment)
491
+ ? info.environment
492
+ : mappedSide(zones.packages, info?.name)
493
+ if (environment !== undefined) {
308
494
  return environment === "library"
309
495
  ? {
310
496
  zone: "library",
@@ -313,10 +499,18 @@ export function createZoneClassifier(options: ZoneClassifierOptions): ZoneClassi
313
499
  }
314
500
  : { zone: environment as Zone }
315
501
  }
316
- const where = info?.name ?? info?.dir ?? dirname(file)
502
+ const declare = `"nifra": { "environment": "frontend" | "backend" | "shared" | "library" }`
503
+ if (info?.name === undefined) {
504
+ return {
505
+ zone: "error",
506
+ reason: `"${file}" is first-party code outside the app and declares no side. Add ${declare} to ${info === undefined ? "a package.json above it" : `${info.dir}/package.json`}`,
507
+ }
508
+ }
509
+ // Named per package, not per file, so a build that reaches 50 files of it reports one fact once.
317
510
  return {
318
511
  zone: "error",
319
- reason: `"${file}" is first-party code outside the app (${where}) and declares no side. Add "nifra": { "environment": "frontend" | "backend" | "shared" | "library" } to ${info === undefined ? "its package.json" : `${info.dir}/package.json`}`,
512
+ package: info.name,
513
+ reason: `package "${info.name}" is first-party code outside the app and declares no side. Add ${declare} to ${info.dir}/package.json, or give it a side from the app's package.json: "nifra": { "zones": { "packages": { "${suggestedPattern(info.name)}": "backend" } } } (or "library" if the browser may load it)`,
320
514
  }
321
515
  }
322
516
 
@@ -388,8 +582,15 @@ export function browserDenial(
388
582
  return undefined
389
583
  }
390
584
 
585
+ /** Whether a bare specifier names one of {@link SERVER_PACKAGES} or a database driver entry. */
586
+ export function isServerPackageSpecifier(specifier: string): boolean {
587
+ const name = specifierPackage(specifier)
588
+ return (name !== undefined && SERVER_PACKAGES.includes(name)) || SERVER_SUBPATHS.test(specifier)
589
+ }
590
+
391
591
  /** Whether a module in `from` may import (with a value import) a module in `to`. */
392
592
  export function importAllowed(from: Zone, to: Zone): boolean {
593
+ if (from === "component") return true
393
594
  if (from === "generated" || from === "library") return true
394
595
  if (to === "generated" || to === "library") return true
395
596
  if (FRONTEND_ZONES.has(from)) return FRONTEND_ZONES.has(to) || to === "shared" || to === "fn"