Modern privileged method pattern with inheritable protected state
npm install privilegedModern privileged method pattern with inheritable protected state.
``bash`
pnpm add privileged
JavaScript prototype methods cannot access constructor-scoped variables:
`javascript
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
}
}
`
create_protected creates a privileged protected bridge between constructor scope and prototype methods:
`javascript
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() // 1
`
Tip: Use access: _ for a more concise syntax, similar to class #private:
`javascript
const { protect, access: _ } = create_protected()
// In prototype methods:
class Counter {
get_count() {
return _(this).count // vs this.#count
}
}
`
- Use this to access public members - privileged, prototype members.
- Use _(this) to access protected members.
- Use protect() to define protected members - privileged methods with protected visibility
- Use privilege() to define privileged members
`javascript
// 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 variables & intermediate values
const events = {}
// 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]()
}
})
}
// 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
}
}
}
}
`
`js
// 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')
}
}
`
Factory function that returns { protect, access }. Each call creates an isolated pair - only matching protect/access pairs can communicate.
Options:
- bind (default: true) - Auto-bind functions to instance. When true, regular functions in protect() have this bound to the instance.
`javascript
// Default: bind = true (regular functions work)
const { protect, access: _ } = create_protected()
protect(this, {
emit() { _(this).listeners.forEach(fn=> fn()) }
})
// Disable auto-bind (must use arrow functions for this)
const { protect, access: _ } = create_protected({ bind: false })
protect(this, {
emit: _=> { _(this).listeners.forEach(fn=> fn()) } // arrow captures this
})
`
Defines protected members for an instance.
Parameters:
- instance - The instance to define protected members on (usually this)protected_state
- - Protected member definitions
Member values:
- Property descriptors: { get, set } - for primitives
- Reference types: functions, objects, arrays, etc. - bound directly
Note: object literal getter/setter is not allowed
`javascript
protect(this, {
// Property descriptor for primitive
count: { get: _=> count, set: v=> count = v },
// Method
compute() {
return count * 2
},
// Reference types bound directly
items: [],
cache: new Map()
})
`
Returns the protected members.
`javascript
_(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 all
`
Defines privileged members for an instance. Functions are auto-bound to the instance.
Parameters:
- instance - The instance to define privileged members on (usually this)privileged_state
- - Privileged member definitions
Works like Object.assign(instance, privileged_state) but auto-binds functions.
`javascript
import { privilege } from 'privileged'
class Counter {
constructor() {
privilege(this, {
count: 0,
increment() { this.count++ },
get_count() { return this.count }
})
}
}
const counter = new Counter()
const { increment, get_count } = counter // this stays bound!
increment()
increment()
get_count() // 2
`
Define privileged methods in constructor, prototype methods on prototype:
`javascript
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 change
`
Reference types are bound directly and shared:
`javascript
const { protect, access: _ } = create_protected()
class Cache {
constructor() {
const store = new Map()
protect(this, {
store // Bound directly, not copied
})
}
set(key, value) {
_(this).store.set(key, value)
}
get(key) {
return _(this).store.get(key)
}
}
`
Each create_protected() call creates isolated access:
`javascript
// 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 state
`
Primitives must use property descriptors:
`javascript
protect(this, {
count: 0 // Error: "count" is primitive, use { get, set } descriptor instead
})
// Correct:
protect(this, {
count: { get: _=> count, set: v=> count = v }
})
`
Rejected primitives: null, undefined, number, string, boolean, symbol, bigint
Object literal getter/setter syntax is not supported because it cannot reflect changes:
`javascript
protect(this, {
get config() {
return config
}, // Error: getter syntax not supported
set config(v) {
config = v
} // Error: setter syntax not supported
})
// Correct:
protect(this, {
config: { get: _=> config, set: v=> config = v }
})
`
Using mismatched protect/access pairs throws:
`javascript
// 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 }
})
}
}
`
`javascript
// 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
}
`
privileged supports inheritance. The behavior depends on whether the Child uses the same protect/access as the Parent.
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.
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).
`javascript
// 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()
}
}
})
}
}
`
`javascript
// 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
}
}
`
When sharing protected state, the Child can override Parent's protected members by using the same name.
`javascript
// 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()
}
}
`
`javascript
// 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!"
`
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._(this)
* Child's only sees Child's protected members.Property Access Exception
* Cross-access throws .
`javascript
// 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 }
}
`
`javascript
// 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 ``
}
}
MIT