1use std::sync::Arc;
17
18use rquickjs::loader::{BuiltinResolver, ImportAttributes, Loader, Resolver};
19use rquickjs::module::ModuleDef;
20use rquickjs::{Ctx, Module, Object};
21
22pub type DeclareFn = Arc<dyn for<'js> Fn(Ctx<'js>, Vec<u8>) -> rquickjs::Result<Module<'js>> + Send + Sync>;
24
25pub type NamespaceFn = Arc<dyn for<'js> Fn(&Ctx<'js>) -> rquickjs::Result<Object<'js>> + Send + Sync>;
27
28#[derive(Clone)]
30pub struct NativeModule {
31 pub specifiers: Vec<String>,
35 pub declare: DeclareFn,
36 pub namespace: NamespaceFn,
37}
38
39impl std::fmt::Debug for NativeModule {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 f.debug_struct("NativeModule")
42 .field("specifiers", &self.specifiers)
43 .finish_non_exhaustive()
44 }
45}
46
47impl NativeModule {
48 pub fn new<D, N>(specifiers: impl IntoIterator<Item = impl Into<String>>, namespace: N) -> Self
50 where
51 D: ModuleDef,
52 N: for<'js> Fn(&Ctx<'js>) -> rquickjs::Result<Object<'js>> + Send + Sync + 'static,
53 {
54 Self {
55 specifiers: specifiers.into_iter().map(Into::into).collect(),
56 declare: Arc::new(|ctx, name| Module::declare_def::<D, _>(ctx, name)),
57 namespace: Arc::new(namespace),
58 }
59 }
60
61 pub fn from_def<D: ModuleDef>(specifiers: impl IntoIterator<Item = impl Into<String>>) -> Self {
65 let specifiers: Vec<String> = specifiers.into_iter().map(Into::into).collect();
66 let canonical = specifiers.first().cloned().unwrap_or_default();
67 Self {
68 specifiers,
69 declare: Arc::new(|ctx, name| Module::declare_def::<D, _>(ctx, name)),
70 namespace: Arc::new(move |ctx| module_default_object::<D>(ctx, &canonical)),
71 }
72 }
73
74 #[must_use]
75 pub fn canonical(&self) -> &str {
76 self.specifiers.first().map_or("", String::as_str)
77 }
78
79 #[must_use]
80 pub fn answers_to(&self, specifier: &str) -> bool {
81 self.specifiers.iter().any(|s| s == specifier)
82 }
83}
84
85impl From<ferrijs_std::modules::NodeModule> for NativeModule {
86 fn from(m: ferrijs_std::modules::NodeModule) -> Self {
87 Self {
88 specifiers: m.specifiers.iter().map(|s| (*s).to_string()).collect(),
89 declare: Arc::new(m.declare),
90 namespace: Arc::new(m.namespace),
91 }
92 }
93}
94
95fn module_default_object<'js, D: ModuleDef>(ctx: &Ctx<'js>, name: &str) -> rquickjs::Result<Object<'js>> {
98 let (module, _promise) = Module::evaluate_def::<D, _>(ctx.clone(), name)?;
99 let namespace = module.namespace()?;
100 if let Ok(default) = namespace.get::<_, Object<'js>>("default") {
101 return Ok(default);
102 }
103 Ok(namespace)
104}
105
106#[derive(Clone, Default)]
113pub struct ModuleRegistry {
114 modules: Vec<NativeModule>,
115 aliases: Vec<(String, String)>,
117 reserved_prefixes: Vec<String>,
120 reserved_names: Vec<String>,
121}
122
123impl std::fmt::Debug for ModuleRegistry {
124 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125 f.debug_struct("ModuleRegistry")
126 .field("modules", &self.names())
127 .field("aliases", &self.aliases)
128 .finish_non_exhaustive()
129 }
130}
131
132impl ModuleRegistry {
133 #[must_use]
135 pub fn new() -> Self {
136 Self::default()
137 }
138
139 #[must_use]
142 pub fn with_std() -> Self {
143 let mut registry = Self::new();
144 for module in ferrijs_std::modules::modules() {
145 registry.modules.push(module.into());
146 }
147 registry.reserved_prefixes.push("node:".to_string());
148 registry
149 }
150
151 pub fn register(&mut self, module: NativeModule) -> Result<(), String> {
159 for specifier in &module.specifiers {
160 if self.serves(specifier) {
161 return Err(format!("module `{specifier}` is already served by this runtime"));
162 }
163 }
164 self.modules.push(module);
165 Ok(())
166 }
167
168 #[must_use]
175 pub fn with(mut self, module: NativeModule) -> Self {
176 if let Err(e) = self.register(module) {
177 panic!("{e}");
178 }
179 self
180 }
181
182 pub fn alias(&mut self, from: impl Into<String>, to: impl Into<String>) -> Result<(), String> {
189 let (from, to) = (from.into(), to.into());
190 if self.modules.iter().any(|m| m.answers_to(&from)) {
191 return Err(format!(
192 "module alias `{from}`: cannot alias a specifier the runtime already serves natively"
193 ));
194 }
195 if !self.modules.iter().any(|m| m.answers_to(&to)) {
196 return Err(format!(
197 "module alias `{from}` -> `{to}`: `{to}` is not a native module (expected one of {})",
198 self.names().join(", ")
199 ));
200 }
201 match self.aliases.iter_mut().find(|(f, _)| *f == from) {
202 Some(entry) => entry.1 = to,
203 None => self.aliases.push((from, to)),
204 }
205 Ok(())
206 }
207
208 pub fn retain(&mut self, keep: impl Fn(&str) -> bool) {
211 self.modules.retain(|m| keep(m.canonical()));
212 let served: Vec<String> = self.modules.iter().flat_map(|m| m.specifiers.clone()).collect();
213 self.aliases.retain(|(_, to)| served.contains(to));
214 }
215
216 pub fn reserve_prefix(&mut self, prefix: impl Into<String>) {
219 self.reserved_prefixes.push(prefix.into());
220 }
221
222 pub fn reserve_name(&mut self, name: impl Into<String>) {
224 self.reserved_names.push(name.into());
225 }
226
227 #[must_use]
229 pub fn names(&self) -> Vec<String> {
230 let mut names: Vec<String> = self.modules.iter().flat_map(|m| m.specifiers.clone()).collect();
231 names.extend(self.aliases.iter().map(|(from, _)| from.clone()));
232 names
233 }
234
235 #[must_use]
236 pub fn modules(&self) -> &[NativeModule] {
237 &self.modules
238 }
239
240 #[must_use]
241 pub fn aliases(&self) -> &[(String, String)] {
242 &self.aliases
243 }
244
245 #[must_use]
247 pub fn serves(&self, specifier: &str) -> bool {
248 self.canonical(specifier).is_some()
249 }
250
251 #[must_use]
255 pub fn canonical(&self, specifier: &str) -> Option<String> {
256 let target = self
257 .aliases
258 .iter()
259 .find(|(from, _)| from == specifier)
260 .map_or(specifier, |(_, to)| to.as_str());
261 self
262 .modules
263 .iter()
264 .find(|m| m.answers_to(target))
265 .map(|m| m.canonical().to_string())
266 }
267
268 #[must_use]
273 pub fn is_reserved(&self, specifier: &str) -> bool {
274 if self.serves(specifier) || self.reserved_names.iter().any(|n| n == specifier) {
275 return true;
276 }
277 if self.reserved_prefixes.iter().any(|p| specifier.starts_with(p)) {
278 return true;
279 }
280 self
281 .names()
282 .iter()
283 .any(|name| name.strip_prefix("node:") == Some(specifier))
284 }
285
286 #[must_use]
291 pub fn fingerprint(&self) -> u64 {
292 use std::hash::{Hash, Hasher};
293 let mut names = self.names();
294 names.sort();
295 let mut aliases = self.aliases.clone();
296 aliases.sort();
297 let mut h = std::collections::hash_map::DefaultHasher::new();
298 names.hash(&mut h);
299 aliases.hash(&mut h);
300 h.finish()
301 }
302
303 fn module_for(&self, specifier: &str) -> Option<&NativeModule> {
304 let canonical = self.canonical(specifier)?;
305 self.modules.iter().find(|m| m.canonical() == canonical)
306 }
307
308 pub fn namespace<'js>(&self, ctx: &Ctx<'js>, specifier: &str) -> rquickjs::Result<Option<Object<'js>>> {
315 match self.module_for(specifier) {
316 Some(module) => (module.namespace)(ctx).map(Some),
317 None => Ok(None),
318 }
319 }
320
321 #[must_use]
324 pub fn loader(self: &Arc<Self>) -> (NativeResolver, NativeLoader) {
325 let mut builtin = BuiltinResolver::default();
326 for name in self.names() {
327 builtin.add_module(name);
328 }
329 (
330 NativeResolver {
331 builtin,
332 registry: Arc::clone(self),
333 },
334 NativeLoader {
335 registry: Arc::clone(self),
336 },
337 )
338 }
339}
340
341pub struct NativeResolver {
343 builtin: BuiltinResolver,
344 registry: Arc<ModuleRegistry>,
345}
346
347impl Resolver for NativeResolver {
348 fn resolve<'js>(
349 &mut self,
350 ctx: &Ctx<'js>,
351 base: &str,
352 name: &str,
353 attributes: Option<ImportAttributes<'js>>,
354 ) -> rquickjs::Result<String> {
355 let _ = &self.registry;
361 self.builtin.resolve(ctx, base, name, attributes)
362 }
363}
364
365pub struct NativeLoader {
370 registry: Arc<ModuleRegistry>,
371}
372
373impl Loader for NativeLoader {
374 fn load<'js>(
375 &mut self,
376 ctx: &Ctx<'js>,
377 path: &str,
378 _attributes: Option<ImportAttributes<'js>>,
379 ) -> rquickjs::Result<Module<'js>> {
380 let module = self
381 .registry
382 .module_for(path)
383 .ok_or_else(|| rquickjs::Error::new_loading(path))?;
384 (module.declare)(ctx.clone(), Vec::from(path))
385 }
386}
387
388#[cfg(test)]
389mod tests {
390 use super::*;
391
392 struct Dummy;
393 impl ModuleDef for Dummy {
394 fn declare(decl: &rquickjs::module::Declarations<'_>) -> rquickjs::Result<()> {
395 decl.declare("x")?;
396 Ok(())
397 }
398 fn evaluate<'js>(_ctx: &Ctx<'js>, exports: &rquickjs::module::Exports<'js>) -> rquickjs::Result<()> {
399 exports.export("x", 1)?;
400 Ok(())
401 }
402 }
403
404 #[test]
405 fn std_table_serves_node_modules_under_both_spellings() {
406 let r = ModuleRegistry::with_std();
407 assert!(r.serves("fs"));
408 assert!(r.serves("node:fs"));
409 assert_eq!(r.canonical("node:fs").as_deref(), Some("fs"));
410 assert!(r.is_reserved("node:anything"));
411 assert!(!r.serves("lodash"));
412 }
413
414 #[test]
415 fn register_refuses_a_clash_and_alias_refuses_a_redirect() {
416 let mut r = ModuleRegistry::with_std();
417 assert!(r.register(NativeModule::from_def::<Dummy>(["fs"])).is_err());
418 r.register(NativeModule::from_def::<Dummy>(["acme"])).unwrap();
419 assert!(r.alias("fs", "acme").is_err());
420 assert!(r.alias("acme2", "nope").is_err());
421 r.alias("acme2", "acme").unwrap();
422 assert_eq!(r.canonical("acme2").as_deref(), Some("acme"));
423 assert!(r.is_reserved("acme2"));
424 }
425
426 #[test]
427 fn fingerprint_ignores_order() {
428 let mut a = ModuleRegistry::new();
429 a.register(NativeModule::from_def::<Dummy>(["one"])).unwrap();
430 a.register(NativeModule::from_def::<Dummy>(["two"])).unwrap();
431 let mut b = ModuleRegistry::new();
432 b.register(NativeModule::from_def::<Dummy>(["two"])).unwrap();
433 b.register(NativeModule::from_def::<Dummy>(["one"])).unwrap();
434 assert_eq!(a.fingerprint(), b.fingerprint());
435 b.alias("three", "one").unwrap();
436 assert_ne!(a.fingerprint(), b.fingerprint());
437 }
438}