# Do we have other granular levels for visibility?

**URL:** <https://es.discourse.group/t/do-we-have-other-granular-levels-for-visibility/2308>\
**Category:** I have questions\
**Created:** [February 17, 2025, 3:15pm UTC](https://es.discourse.group/t/do-we-have-other-granular-levels-for-visibility/2308 "2025-02-17T15:15:06Z")\
**Posts on this page:** 15\
**Page:** 1

<div class="post-metadata">

**Author:** ![Nefcanto](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/nefcanto/32/2276_2.png) [@Nefcanto](https://es.discourse.group/u/Nefcanto)\
**Post date:** [February 17, 2025, 3:15pm UTC](https://es.discourse.group/t/do-we-have-other-granular-levels-for-visibility/2308/1 "2025-02-17T15:15:06Z")

</div>

In ESM, a function is either limited to the module (file) or is globally available.  
Once a function or anything else is exported, anyone can import it.

Do we have other granular levels?

Why do I ask for this? We tend to take code cleanness to extremes. We break a big coherent file into smaller files and wrap them inside a directory.

For example, instead of having a big file called `Database.js` with a thousand lines and 20 functions, we create a directory called `Database` and then break that file into 20 files each with 50 lines or so. Then we create a file inside it called `Database.js` and import functions from those 20 files, and at last you can consider this:

```javascript
import { connect } from "./Connection.js"
import { get } from " ./Get.js"
import { run } from "./Run.js"
import { bulkInsert } from "./BulkInsert.js"
// other imports here

export const db = {
    connect,
    get,
    run,
    bulkInsert,
}

```

The only problem with this method is that each of those functions is available independently.

Is there a way to limit their visibility to the enclosing directory only?

---

<div class="post-metadata">

**Author:** ![Nefcanto](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/nefcanto/32/2276_2.png) [@Nefcanto](https://es.discourse.group/u/Nefcanto)\
**Post date:** [February 17, 2025, 3:18pm UTC](https://es.discourse.group/t/do-we-have-other-granular-levels-for-visibility/2308/2 "2025-02-17T15:18:32Z")

</div>

We don't want developers to do this:

```javascript
import { get } from "./path-to-database-get.js"

const result = await get("some query")

```

Rather, we want them to do this:

```javascript
import { db } from "./path-to-database.js"

const result = await db.get("some query")

```

We can create static code analysis for that of course. But if we could physically prevent it, that would have made much more value.

---

<div class="post-metadata">

**Author:** ![Nefcanto](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/nefcanto/32/2276_2.png) [@Nefcanto](https://es.discourse.group/u/Nefcanto)\
**Post date:** [February 17, 2025, 3:20pm UTC](https://es.discourse.group/t/do-we-have-other-granular-levels-for-visibility/2308/3 "2025-02-17T15:20:15Z")

</div>

For example, consider this imaginary export:

```javascript
exportDir const get = async query => {
}

```

And this `get` method would be visible only for sibling files inside the same directory.

---

<div class="post-metadata">

**Author:** ![ljharb](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/ljharb/32/8_2.png) [@ljharb](https://es.discourse.group/u/ljharb)\
**Post date:** [February 17, 2025, 3:37pm UTC](https://es.discourse.group/t/do-we-have-other-granular-levels-for-visibility/2308/4 "2025-02-17T15:37:23Z")

</div>

Every scope is a granular level of visibility/reachability.

For your specific question, in node you’d do that by putting the database stuff in a package, with the exports field defined. In a browser, you’d probably want to use a bundler and not expose the individual modules at all.

---

<div class="post-metadata">

**Author:** ![theScottyJam](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/thescottyjam/32/616_2.png) [@theScottyJam](https://es.discourse.group/u/theScottyJam)\
**Post date:** [February 17, 2025, 7:12pm UTC](https://es.discourse.group/t/do-we-have-other-granular-levels-for-visibility/2308/5 "2025-02-17T19:12:33Z")

</div>

I do feel like static analysis can be a useful way to solve this problem as well. I've been fond of [dependency cruiser](https://www.npmjs.com/package/dependency-cruiser/v/5.1.0) which lets you described arbitrary rules you would like enforced, such as "you can not import from a file starting with an underscore unless you are either in the directory containing that file, or one of the sub-directories of that parent one", or something like that.

It would be nice if there was native support for this sort of thing as well, but it gets tricky, because how does JavaScript know if it is allowed to import a particular file or not, without, for example, making a bunch of extra requests to look for config files or something telling it what it can and can not import.

---

<div class="post-metadata">

**Author:** ![Nefcanto](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/nefcanto/32/2276_2.png) [@Nefcanto](https://es.discourse.group/u/Nefcanto)\
**Post date:** [February 18, 2025, 5:12am UTC](https://es.discourse.group/t/do-we-have-other-granular-levels-for-visibility/2308/6 "2025-02-18T05:12:51Z")

</div>

@theScottyJam, that's an interesting npm package.  
The point is there would be no config file and no need for extra requests.

I asked ChatGPT for a table of access modifiers in different languages:

| Language | Modifier | Scope Description |
| --- | --- | --- |
| **C#** | `public` | Accessible from anywhere |
| | `internal` | Accessible within the same assembly |
| | `protected` | Accessible within the class and derived classes |
| | `private` | Accessible only within the class |
| | `protected internal` | Accessible within the same assembly and derived classes |
| | `private protected` | Accessible within the same class or derived classes in the same assembly |
| **Java** | `public` | Accessible from anywhere |
| | `protected` | Accessible within the class, package, and subclasses |
| | _(default)_ | Accessible within the same package (no keyword needed) |
| | `private` | Accessible only within the class |
| **C++** | `public` | Accessible from anywhere |
| | `protected` | Accessible within the class and derived classes |
| | `private` | Accessible only within the class |
| **Python** | _(default)_ | Public by default (no strict access control) |
| | `_protected` | Convention: intended for internal use but still accessible |
| | `__private` | Name-mangled to prevent accidental access |
| **JavaScript** | _(default)_ | Public by default (no strict access control) |
| | `#private` | Accessible only within the class (ES6 private fields) |
| **TypeScript** | `public` | Accessible from anywhere |
| | `protected` | Accessible within the class and derived classes |
| | `private` | Accessible only within the class |
| **Swift** | `public` | Accessible from anywhere |
| | `internal` | Accessible within the same module |
| | `fileprivate` | Accessible within the same file |
| | `private` | Accessible only within the enclosing declaration |
| **Kotlin** | `public` | Accessible from anywhere |
| | `internal` | Accessible within the same module |
| | `protected` | Accessible within the class and derived classes |
| | `private` | Accessible only within the class |
| **PHP** | `public` | Accessible from anywhere |
| | `protected` | Accessible within the class and derived classes |
| | `private` | Accessible only within the class |
| **Go** | _(default)_ | Public if identifier starts with an uppercase letter |
| | _(lowercase)_ | Private if identifier starts with a lowercase letter |
| **Rust** | `pub` | Accessible from anywhere |
| | `pub(crate)` | Accessible within the same crate |
| | `pub(super)` | Accessible within the parent module |
| | `pub(in path)` | Accessible within a specific module |

I think C#'s `internal` would be a perfect candidate here. Since in JS we don't have assemblies (other than Wasm) and usually, packages are directories and whatever is inside them or their subdirectories, I think this can be helpful:

```javascript
export internal const someFunction = () => {

```

This function would only be importable by sibling files or from subdirectories. But not from the parent directories. And it should be easy to check.

---

<div class="post-metadata">

**Author:** ![aclaymore](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/aclaymore/32/501_2.png) [@aclaymore](https://es.discourse.group/u/aclaymore)\
**Post date:** [February 18, 2025, 3:28pm UTC](https://es.discourse.group/t/do-we-have-other-granular-levels-for-visibility/2308/7 "2025-02-18T15:28:02Z")

</div>

> [@Nefcanto](#):
>
> sibling files or from subdirectories

The JavaScript specification has no notion of file locations or directories. Where the code comes from is up to the "host" environment.

---

<div class="post-metadata">

**Author:** ![aclaymore](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/aclaymore/32/501_2.png) [@aclaymore](https://es.discourse.group/u/aclaymore)\
**Post date:** [February 18, 2025, 4:31pm UTC](https://es.discourse.group/t/do-we-have-other-granular-levels-for-visibility/2308/8 "2025-02-18T16:31:50Z")

</div>

Similar discussion here: [Package Scope Proposal](https://es.discourse.group/t/package-scope-proposal/1860)

---

<div class="post-metadata">

**Author:** ![Nefcanto](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/nefcanto/32/2276_2.png) [@Nefcanto](https://es.discourse.group/u/Nefcanto)\
**Post date:** [February 26, 2025, 7:39am UTC](https://es.discourse.group/t/do-we-have-other-granular-levels-for-visibility/2308/9 "2025-02-26T07:39:15Z")

</div>

Does this mean that the Node.js or the browser manages loading code from "directories and files"?

Or in other words, does this mean that in theory I can create a host that loads code from database?

---

<div class="post-metadata">

**Author:** ![aclaymore](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/aclaymore/32/501_2.png) [@aclaymore](https://es.discourse.group/u/aclaymore)\
**Post date:** [February 26, 2025, 9:20am UTC](https://es.discourse.group/t/do-we-have-other-granular-levels-for-visibility/2308/10 "2025-02-26T09:20:11Z")

</div>

Exactly. The host determines what it means to import a particular string, if it's a filesystem path or a http url, or could be from an in-memory database. The core language does not specify.

---

<div class="post-metadata">

**Author:** ![Nefcanto](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/nefcanto/32/2276_2.png) [@Nefcanto](https://es.discourse.group/u/Nefcanto)\
**Post date:** [February 26, 2025, 10:00am UTC](https://es.discourse.group/t/do-we-have-other-granular-levels-for-visibility/2308/11 "2025-02-26T10:00:06Z")

</div>

That's an interesting architecture. However, there still needs to be some keyword to specify the scope of visibility. It's an abstract idea. Let's say I want to create a host that loads strings from database.

My host sees this code:

```javascript
import { someModule } form "somePath"

```

I will search my database for something like this:

```javascript
export { someModule }

```

And if I find it, I load that record. Otherwise, I know it's not exported thus I won't load it.

This is 0 or 1 visibility. Either something is not visible to the outside world (other records, other files, other urls) or it's visible to the entire world.

What do you think? How can I limit the visibility? How can we get gray between black and white?

---

<div class="post-metadata">

**Author:** ![Josh-Cena](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/josh-cena/32/1408_2.png) [@Josh-Cena](https://es.discourse.group/u/Josh-Cena)\
**Post date:** [February 26, 2025, 4:52pm UTC](https://es.discourse.group/t/do-we-have-other-granular-levels-for-visibility/2308/12 "2025-02-26T16:52:17Z")

</div>

A host implements a `HostLoadImportedModule` hook, which knows what the current module is and what the requested module's path is. You could say make it fail to resolve a module if the requested module's path is not within the current module's visibiilty. In Node, you could easily do this with a custom module resolver/loader.

---

<div class="post-metadata">

**Author:** ![Nefcanto](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/nefcanto/32/2276_2.png) [@Nefcanto](https://es.discourse.group/u/Nefcanto)\
**Post date:** [March 2, 2025, 7:23am UTC](https://es.discourse.group/t/do-we-have-other-granular-levels-for-visibility/2308/13 "2025-03-02T07:23:00Z")

</div>

I have not created a resolver/loader for Node. I do not have any experience in that field. Does my custom resolver/loader replaces the Node's default resolver?

Because if it does, then that's not a good solution. I should reinvent a big wheel, just to add a small functionality.

But if my custom resolver can be added to a pipe, like the middleware architecture, then that's fantastic. I can add a small code to check that the importing module belongs to the family and otherwise throw error and break the build with a clear message.

So, do you know which one is the case?

---

<div class="post-metadata">

**Author:** ![aclaymore](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/aclaymore/32/501_2.png) [@aclaymore](https://es.discourse.group/u/aclaymore)\
**Post date:** [March 2, 2025, 7:30am UTC](https://es.discourse.group/t/do-we-have-other-granular-levels-for-visibility/2308/14 "2025-03-02T07:30:22Z")

</div>

It's a middleware

> **[Modules: node:module API | Node.js v23.9.0 Documentation](https://nodejs.org/docs/latest-v22.x/api/module.html#resolvespecifier-context-nextresolve)**

---

<div class="post-metadata">

**Author:** ![ljharb](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/ljharb/32/8_2.png) [@ljharb](https://es.discourse.group/u/ljharb)\
**Post date:** [March 2, 2025, 5:06pm UTC](https://es.discourse.group/t/do-we-have-other-granular-levels-for-visibility/2308/15 "2025-03-02T17:06:20Z")

</div>

It’s indeed a chain; you don’t have to reinvent anything.
