privileged
v2.0.0
Published
Modern privileged method pattern with inheritable protected state
Maintainers
Readme
privileged
Modern privileged method pattern with inheritable protected state.
Installation
pnpm add privilegedProblem
JavaScript prototype methods cannot access constructor-scoped variables:
class Counter {
constructor(initial) {
// initial is private, but inaccessible from methods
}
increment() {
initial++ // ReferenceError: initial is not defined
}
get_count() {
return initial // ReferenceError: initial is not defined
}
}Solution
create_protected creates a protected bridge between constructor scope and prototype methods, enabling privileged access:
import create_protected from 'privileged'
const { protect, access: _ } = create_protected()
class Counter {
constructor(initial) {
protect(this, {
count: { get: _=> initial, set: v=> initial = v }
})
}
increment() {
_(this).count++
}
get_count() {
return _(this).count
}
}
const counter = new Counter(0)
counter.increment()
counter.get_count() // 1Tip: Use access: _ for a more concise syntax, similar to class #private:
const { protect, access: _ } = create_protected()
// In prototype methods:
class Counter {
get_count() {
return _(this).count // vs this.#count
}
}Usage
Use this to access public members — privileged and prototype members
Use _(this) to access protected methods, getter/setter and non-callable objects
Use protect() to define protected methods, getter/setter and non-callable objects — privileged methods with protected visibility. No primitive values!
Use privilege() to define privileged methods, getter/setter
Basic Class Pattern
// observable.js
import create_protected, { privilege } from 'privileged'
// export protected state for Child to inherit
export const { protect, access: _ } = create_protected()
export default class Observable {
constructor() {
// --- Private state: Private variables, intermediate values
const events = {}
// Instance API: protected members
protect(this, {
event(event_name) {
if (!events[event_name]) events[event_name] = []
return events[event_name]
},
emit(event_name) {
const lsnrs = _(this).event(event_name)
for (let i = lsnrs.length - 1; i >= 0; i--)
lsnrs[i]()
}
})
// Instance API: privileged members
privilege(this, {
events_length: { get: _=> events.length }
})
// Initialization
}
// Prototype methods
on(event_name, cb) {
_(this).event(event_name).push(cb)
return _=> this.off(event_name, cb)
}
off(event_name, cb) {
const lsnrs = _(this).event(event_name)
for (let i = 0, l = lsnrs.length; i < l; i++) {
if (lsnrs[i] === cb) {
lsnrs.splice(i, 1)
break
}
}
}
}// todostore.js
import Observable, { protect, _ } from './observable.js'
import { privilege } from 'privileged'
export default class TodoStore extends Observable {
constructor() {
super()
// Protected members
protect(this, {
items: []
})
// Privileged members
// Privileged method can access private variables directly
privilege(this, {
// privileged method is bound to `this`, so you can use `onChange`
// as a function.
onChange(cb) {
return this.on('change', cb)
},
todos() {
return _(this).items
}
})
}
// prototype methods
add(title) {
const { items, emit } = _(this)
items.push({ id: Date.now(), title, completed: false })
emit('change')
}
}
API
protect(instance, protected_state)
Defines protected members for an instance.
Parameters:
instance- The instance to define protected members on (usuallythis)protected_state- Protected member definitions
Members allowed:
Protected methods: Method shorthand.
calc() {...} bound to
thisautomatically
Getter/Setter in Property descriptors:
fullname: { get: _=> {...}, set: v=> {...} }
Non-callable objects: objects, arrays, etc.
let count = 0
protect(this, {
items: [], // non-callable object
count: {get: _=> count, set: v=> count = v}, // descriptor
compute() {} // protected method
})Members NOT allowed
Note: object literal getter/setter is not allowed
protect(this, {
get count() { return 0} // throw Error
})Primitive values
Since protected state members are NOT allowed to be re-assigned, use getter/setter to manage primitive values as follows:
let count = 3
protect(this, {
// getter/setter for primitive values
count: { get: _=> count, set: v=> count = v }
})access(instance)
Returns the protected members.
_(this).count // Read
_(this).count = 5 // Write
_(this).compute() // Call method
_(this).items // Access reference
const { count, compute, items, cache } = _(this) // use destructuring to get allprivilege(instance, privileged_state)
Defines privileged members for an instance. Functions are auto-bound to the instance.
Parameters:
instance- The instance to define privileged members on (usuallythis)privileged_state- Privileged member definitions
Works like Object.assign(instance, privileged_state) but auto-binds this.
import { privilege } from 'privileged'
class Counter {
constructor() {
privilege(this, {
count: 0, // primitive values are allowed in privilege, because `this` is `public` visibility
increment() { this.count++ },
get_count() { return this.count }
})
}
}
const counter = new Counter()
const { increment, get_count } = counter // this stays bound!
increment()
increment()
get_count() // 2Members allowed
Same as protect(), but also allows primitive values
Primitive values allowed in privilege(), but also allowed to be re-assigned
privilege(this, {
count: 0
})Create only readable property in privileged getter descriptor
let isReady = false
privilege(this, {
isReady: { get: _=> isReady }
})Member NOT allowed
Note: object literal getter/setter is not allowed
privilege(this, {
get count() { return 0} // throw Error
})Reflect change
Define privileged methods in constructor, prototype methods on prototype:
import create_protected, { privilege } from 'privileged'
const { protect, access: _ } = create_protected()
class BankAccount {
constructor(initial = 100) {
let balance = initial
protect(this, {
balance: { get: _=> balance }
})
// Privileged method - unique per instance
privilege(this, {
deposit(amount) {
if (amount > 0) balance += amount // update the private variable directly
}
})
}
// Class method - on prototype
show() {
return _(this).balance
}
}
const acct = new BankAccount()
console.log(acct.show()) // 100
acct.deposit(100)
console.log(acct.show()) // 200: reflect the changeSharing Reference Types
Reference types are stored by reference, not copied:
const { protect, access: _ } = create_protected()
class Cache {
constructor() {
const store = new Map()
protect(this, {
store // Stored by reference, not copied
})
}
set(key, value) {
_(this).store.set(key, value)
}
get(key) {
return _(this).store.get(key)
}
}Module Isolation
Each create_protected() call creates isolated access:
// module-a.js
import create_protected from 'privileged'
export const { protect, access } = create_protected()
// module-b.js
import create_protected from 'privileged'
export const { protect, access } = create_protected()
// These cannot access each other's protected stateErrors
Primitive Values in protect()
Primitives must use property descriptors in protect() but allowed in privilege:
protect(this, {
count: 0 // Error: "count" is primitive, use { get, set } descriptor instead
})
// Correct:
protect(this, {
count: { get: _=> count, set: v=> count = v }
})
Object Literal Getter/Setter is NOT allowed
Object literal getter/setter requires return and multiple lines. Descriptor pattern is more concise:
protect(this, {
get config() {
return config
}, // Error: getter syntax not supported
set config(v) {
config = v
} // Error: setter syntax not supported
})
// Correct:
let config = {}
protect(this, {
config: { get: _=> config, set: v=> config = v }
})Accessing Protected State from Other Modules
Using mismatched protect/access pairs throws:
// module-a.js
import create_protected from 'privileged'
const { protect } = create_protected()
export default class Foo {
constructor() {
let value = 1
protect(this, {
value: { get: _=> value }
})
}
}// module-b.js
import create_protected from 'privileged'
import Foo from './module-a.js'
const { access: _ } = create_protected() // different pair!
class Bar extends Foo {
get() { return _(this).value } // Error: Illegal Access Exception
}Inheritance
privileged supports inheritance. The behavior depends on whether the Child uses the same protect/access as the Parent.
Controlling Visibility (Java Analogy)
Visibility is controlled by export and import decisions:
| Parent exports protect/access? | Child imports them? | Result |
|----------------------------------|---------------------|--------|
| No | - | Private - Child cannot access Parent's protected state |
| Yes | No (creates own) | Isolated - Parent and Child have separate protected states |
| Yes | Yes | Shared - Parent and Child share the same protected state |
This differs from Java where protected is always visible to subclasses. Here, both sides must agree to share.
1. Shared Protected State
If the Parent exports its protect and access functions, and the Child imports/uses them, they share the same protected state.
- Behavior: Child can read/write Parent's protected state, and can also add its own. Child can override Parent's protected members if using the same name.
- Access:
_(this)returns all members (Parent's + Child's).
// Observable.js
import create_protected from 'privileged'
export const { protect, access: _ } = create_protected()
export default class Observable {
constructor() {
const listeners = {}
protect(this, {
emit(event_name) {
for (const listener of (listeners[event_name] || [])) {
listener()
}
}
})
}
}// Admin.js
import Observable, { protect, _ } from './Observable.js'
export class Admin extends Observable {
constructor() {
super()
protect(this, {
log(msg) { console.log(`[Admin]: ${msg}`) }
})
}
triggerCustomEvent() {
const { emit, log } = _(this)
emit('custom_event') // access protected member in Observable
log('Broadcasting...') // access protected member in Admin
}
}2. Override Parent's Protected Members
When sharing protected state, the Child can override Parent's protected members by using the same name.
// animal.js
import create_protected from 'privileged'
export const { protect, access: _ } = create_protected()
export default class Animal {
constructor(name) {
protect(this, {
speak() {
return `${name} makes a sound`
}
})
}
talk() {
return _(this).speak()
}
}// dog.js
import Animal, { protect } from './animal.js'
export class Dog extends Animal {
constructor(name) {
super(name)
// Override parent's speak()
protect(this, {
speak() {
return `${name} barks: Woof!`
}
})
}
}
const dog = new Dog('Buddy')
console.log(dog.talk()) // "Buddy barks: Woof!"3. Isolated Protected State
If the Child creates its own create_protected(), the protected states remain strictly isolated in layers.
- Behavior: Parent and Child maintain separate protected state.
- Access:
- Parent's
_(this)only sees Parent's protected members. - Child's
_(this)only sees Child's protected members. - Cross-access throws
Property Access Exception.
- Parent's
// Component.js (Parent)
import create_protected from 'privileged'
const { protect, access: _ } = create_protected()
export class Component {
constructor(id) {
protect(this, {
id: { get: _=> id }
})
}
get_id() { return _(this).id }
}// Widget.js (Child)
import create_protected from 'privileged'
import { Component } from './Component.js'
// Child creates its OWN protect/access
const { protect, access: _ } = create_protected()
export class Widget extends Component {
constructor(id, width) {
super(id)
protect(this, {
width: { get: _=> width }
})
}
render() {
// _(this).id <-- Error: Property Access Exception (wrong pair)
// Correct: use public API for parent's protected state
const id = this.get_id()
const { width } = _(this)
return `<Widget id="${id}" width="${width}" />`
}
}License
MIT
