npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

proxy-views

v0.0.4

Published

Some wrapper views for Plain Data Object with ES6 Proxy. Make Data Object more semantic and easier to use.

Readme

proxy-views.js


🇨🇳 中文文档 {#中文文档}

📖 项目背景与设计特点

proxy-views 用 ES6 Proxy 为普通 JavaScript 对象提供"视图包装(View Wrapping)",把日常反复手写的 Proxy 模板沉淀成几个稳定的小工具。

什么是"视图包装"

借用数据库视图的思路:不复制、不修改原始对象,而是套一层 Proxy,让同一个底层对象以不同的访问语义被使用。视图是活的——底层对象变了,视图立刻反映;通过视图的写入也会透传回底层对象。

与常见手段的区别

| 手段 | 是否复制 | 是否侵入原对象 | 是否保持 live | |---|---|---|---| | Object.assign / 展开语法 | 是(快照) | 否 | 否(源变了副本不变) | | Object.defineProperty getter | 否 | 是(往原对象加属性) | 是 | | 原型链 Object.setPrototypeOf | 否 | 是(改原对象 proto) | 是 | | dot-prop / lodash.get | 否(一次性读取) | 否 | n/a(不是可传递的对象) | | proxy-views | 否 | 否 | 是 |

即:内置手段要么复制(丢掉 live),要么污染原对象(丢掉非侵入)。proxy-views 的定位是同时保持「零拷贝 + 非侵入 + live + 双向透传」——这是它相对内置手段唯一但真实的差异点。

四个视图各管一种访问语义

  • StrictView:只允许访问已定义属性,访问拼写错误抛具名错误,且递归包裹嵌套对象
  • AliasView:为深嵌套路径挂上活的短别名,读写双向透传,不污染原对象
  • ChainView:多个字典的只读链式视图,源字典后续变更能反映,不做合并拷贝
  • MissingView:缺失属性的 get/set 走自定义回调,是最小可定制的底座

适用与不适用

  • ✅ 适合:需要对一个共享对象反复以不同"面孔"访问、又不能改动它(如配置层、API 响应映射、多团队共享只读视图)
  • ❌ 不适合:只想一次性按路径取值(用 dot-prop/lodash 更直接)、需要不可变更新(用 immer)、需要编译期类型安全(用 TypeScript)

✨ 核心特性

  • 🔒 StrictView: 严格模式,只允许访问已定义的属性
  • 🔗 AliasView: 为复杂嵌套属性创建简洁别名
  • ⛓️ ChainView: 将多个字典链接成单一只读视图
  • 🛡️ MissingView: 基础包装,自定义缺失属性处理
  • 📦 零依赖: 纯 JavaScript 实现,无外部依赖
  • 🎯 非侵入: 不修改原始对象,只提供包装视图

📦 安装

npm install proxy-views

🚀 快速开始

1. ChainView - 链式字典视图

将多个字典链接成一个只读视图,支持链式查找和唯一遍历。

const { ChainView } = require('proxy-views');

const defaults = { theme: 'light', lang: 'en', debug: false };
const userPrefs = { theme: 'dark', lang: 'zh' };
const session = { debug: true, user: 'alice' };

const config = new ChainView(userPrefs, defaults, session);

// 链式查找:从第一个字典开始找,找不到继续向后找
console.log(config.theme); // 'dark' (来自 userPrefs)
console.log(config.debug); // true (来自 session)
console.log(config.port); // undefined (不存在)

// 只读访问:不能修改
config.newProp = 'value'; // Error: Cannot set property 'newProp' on ChainView (read-only)

// 遍历所有唯一键
for (const key of config) {
  console.log(key, config[key]);
}
// 输出: theme dark, lang zh, debug true, user alice

// 获取所有键
console.log(Object.keys(config)); // ['theme', 'lang', 'debug', 'user']
console.log(config.length); // 4

💡 length 与 size 返回所有字典的唯一键数量,是字典视图的便捷属性,非数组语义。

2. StrictView - 严格模式

防止拼写错误或访问不存在的属性,让你的代码更安全。

const { StrictView } = require('proxy-views');

const user = {
  name: '张三',
  age: 25,
  address: { city: '北京', street: '中关村大街' }
};

const safeUser = new StrictView(user);

// ✅ 正常访问
console.log(safeUser.name); // '张三'
console.log(safeUser.address.city); // '北京'

// ❌ 错误访问会抛出明确的错误
console.log(safeUser.nmae); // Error: Property "nmae" is not defined

⚠️ StrictView 只允许访问对象的自有属性,因此仅适用于纯数据对象。数组方法(forEach/map/for...of)和类实例的原型方法不在自有属性上,访问会被视为未定义而抛错。需要迭代数组时请先解包,或改用 AliasView。

2. AliasView - 属性别名

为深层嵌套属性创建简洁的别名,告别冗长的属性链。

const { AliasView } = require('proxy-views');

const config = {
  server: {
    database: {
      host: 'localhost',
      port: 5432,
      credentials: { username: 'admin', password: 'secret' }
    }
  }
};

const view = new AliasView(config);

// 创建别名
view.alias('server.database.host', 'dbHost');
view.alias('server.database.port', 'dbPort');

// 🎉 简洁访问
console.log(view.dbHost); // 'localhost'
console.log(view.dbPort); // 5432

// 修改会同步更新原始对象
view.dbHost = 'prod.example.com';
console.log(config.server.database.host); // 'prod.example.com'

批量创建别名

const view = new AliasView(data);

// 多种方式
view.alias('user.name', ['displayName', 'nickname']);
view.alias('user.email', 'email', 'mail', 'userEmail');
view.alias('settings.theme', ['theme', 'colorScheme'], 'uiTheme');

数据导出

const view = new AliasView({ name: '王五', age: 30 });
view.alias('name', 'username');

// 完整导出
console.log(view.toJSON());
// { name: '王五', age: 30, username: '王五' }

// 只导出别名
console.log(view.toJSON({ target: false, alias: true }));
// { username: '王五' }

4. MissingView - 缺失属性处理

最基础的包装:访问或设置未定义属性时,交由自定义回调处理,原对象保持不变。

const { MissingView } = require('proxy-views');

const user = { name: '张三', age: 25 };

const view = new MissingView(user, (op, target, prop, value) => {
  if (op === 'get') {
    return '默认值'; // 访问未定义属性时返回默认值
  }
  if (op === 'set') {
    // 设置未定义属性时的自定义行为;这里选择忽略,不污染原对象
    console.log(`忽略设置 ${String(prop)} = ${value}`);
  }
});

console.log(view.name); // '张三'(已定义属性原样返回)
console.log(view.email); // '默认值'(未定义属性走回调)

view.email = '[email protected]'; // 触发 set 回调,原对象不被污染
console.log(user.email); // undefined

💡 MissingView 是其他视图的可定制底座:__missing__(op, target, prop, value) 在 get 时返回你给的默认值,在 set 时让你自行决定如何处理未定义属性的写入。

🎯 实际应用场景

1. 配置管理

const config = new AliasView(appConfig);
config.alias('database.connection.host', 'dbHost');
const connStr = `${config.dbHost}:${config.dbPort}`;

2. API 响应简化

const user = new AliasView(apiResponse);
user.alias('user.profile.display_name', 'name');
setUserInfo({ name: user.name });

3. 表单数据映射

const form = new AliasView(apiData);
form.alias('customer.billing_address.street', 'billingStreet');

📋 API 参考

StrictView

  • new StrictView(object) - 创建严格视图
  • 只能访问原始对象中自有的属性;仅适用于纯数据对象,数组/类实例方法不在自有属性上会抛错

AliasView

  • new AliasView(object) - 创建别名视图
  • view.alias(originPath, ...aliasNames) - 创建别名
  • view.toJSON(options) - 导出数据

ChainView

  • new ChainView(dict1, dict2, ...) - 创建链式字典视图
  • 只读访问,支持链式查找
  • 支持 Object.keys(), for...of, Object.entries() 等遍历方法
  • length / size - 返回所有字典的唯一键数量(非数组语义)

MissingView

  • new MissingView(object, __missing__) - 创建缺失属性处理视图
  • __missing__(op, target, prop, value) - 访问/设置未定义属性时的回调;op 为 'get' 或 'set'
  • 已定义属性原样读写,未定义属性走回调,原对象不被污染

🇺🇸 English Documentation {#english-documentation}

📖 Background & Design

proxy-views uses ES6 Proxy to provide "view wrapping" for plain JavaScript objects — crystallizing the Proxy boilerplate you keep rewriting by hand into a few stable small tools.

What is "View Wrapping"?

Borrowing the idea of database views: don't copy, don't mutate the original object — instead wrap it in a Proxy so the same underlying object can be accessed with different semantics. A view is live — changes to the underlying object show up immediately, and writes through the view propagate back to the original.

How it differs from common approaches

| Approach | Copies? | Invasive to original? | Stays live? | |---|---|---|---| | Object.assign / spread | Yes (snapshot) | No | No (source changes don't reach copy) | | Object.defineProperty getter | No | Yes (adds prop on original) | Yes | | Object.setPrototypeOf | No | Yes (mutates original's proto) | Yes | | dot-prop / lodash.get | No (one-shot read) | No | n/a (not a passable object) | | proxy-views | No | No | Yes |

In other words: built-in approaches either copy (losing liveness) or pollute the original (losing non-invasiveness). proxy-views is positioned to keep all of "zero-copy + non-invasive + live + bidirectional propagation" at once — a small but real differentiator.

One view per access semantic

  • StrictView: only allow access to defined properties; typos throw a named error; recursively wraps nested objects
  • AliasView: attach live short aliases to deeply nested paths, read/write propagate bidirectionally, without polluting the original
  • ChainView: read-only chain view over multiple dicts; later changes to source dicts are reflected, no merge-copy
  • MissingView: route missing-property get/set to a custom callback — the minimal customizable base

When to use / not to use

  • ✅ Good for: accessing one shared object repeatedly under different "faces" without mutating it (config layers, API response mapping, cross-team read-only views)
  • ❌ Not for: one-shot path reads (use dot-prop/lodash directly), immutable updates (use immer), compile-time type safety (use TypeScript)

✨ Core Features

  • 🔒 StrictView: Strict mode, only allow access to defined properties
  • 🔗 AliasView: Create simple aliases for complex nested properties
  • ⛓️ ChainView: Chain multiple dictionaries into a single read-only view
  • 🛡️ MissingView: Basic wrapper with custom missing property handling
  • 📦 Zero Dependencies: Pure JavaScript implementation, no external deps
  • 🎯 Non-invasive: Doesn't modify original object, only provides wrapper view

📦 Installation

npm install proxy-views

🚀 Quick Start

1. ChainView - Chain Dictionary View

Chain multiple dictionaries into a single read-only view with chain lookup and unique iteration.

const { ChainView } = require('proxy-views');

const defaults = { theme: 'light', lang: 'en', debug: false };
const userPrefs = { theme: 'dark', lang: 'zh' };
const session = { debug: true, user: 'alice' };

const config = new ChainView(userPrefs, defaults, session);

// Chain lookup: search from first dict, continue to next if not found
console.log(config.theme); // 'dark' (from userPrefs)
console.log(config.debug); // true (from session)
console.log(config.port); // undefined (not found)

// Read-only: cannot modify
config.newProp = 'value'; // Error: Cannot set property 'newProp' on ChainView (read-only)

// Iterate all unique keys
for (const key of config) {
  console.log(key, config[key]);
}
// Output: theme dark, lang zh, debug true, user alice

// Get all keys
console.log(Object.keys(config)); // ['theme', 'lang', 'debug', 'user']
console.log(config.length); // 4

💡 length and size return the number of unique keys across all dicts — a convenience for the dict view, not array semantics.

2. StrictView - Strict Mode

Prevent typos or accessing non-existent properties, making your code safer.

const { StrictView } = require('proxy-views');

const user = {
  name: 'John',
  age: 25,
  address: { city: 'Beijing', street: 'Zhongguancun Street' }
};

const safeUser = new StrictView(user);

// ✅ Normal access
console.log(safeUser.name); // 'John'
console.log(safeUser.address.city); // 'Beijing'

// ❌ Error access throws clear error
console.log(safeUser.nmae); // Error: Property "nmae" is not defined

⚠️ StrictView only allows access to an object's own properties, so it works for plain data objects only. Array methods (forEach/map/for...of) and class instance methods live on the prototype, not as own properties, so accessing them is treated as undefined and throws. To iterate an array, unwrap it first or use AliasView instead.

2. AliasView - Property Aliases

Create simple aliases for deeply nested properties, eliminating long property chains.

const { AliasView } = require('proxy-views');

const config = {
  server: {
    database: {
      host: 'localhost',
      port: 5432,
      credentials: { username: 'admin', password: 'secret' }
    }
  }
};

const view = new AliasView(config);

// Create aliases
view.alias('server.database.host', 'dbHost');
view.alias('server.database.port', 'dbPort');

// 🎉 Simple access
console.log(view.dbHost); // 'localhost'
console.log(view.dbPort); // 5432

// Changes sync to original object
view.dbHost = 'prod.example.com';
console.log(config.server.database.host); // 'prod.example.com'

Batch Alias Creation

const view = new AliasView(data);

// Multiple ways
view.alias('user.name', ['displayName', 'nickname']);
view.alias('user.email', 'email', 'mail', 'userEmail');
view.alias('settings.theme', ['theme', 'colorScheme'], 'uiTheme');

Data Export

const view = new AliasView({ name: 'John', age: 30 });
view.alias('name', 'username');

// Full export
console.log(view.toJSON());
// { name: 'John', age: 30, username: 'John' }

// Only aliases
console.log(view.toJSON({ target: false, alias: true }));
// { username: 'John' }

4. MissingView - Missing Property Handling

The most basic wrapper: accessing or setting an undefined property is routed to a custom callback, leaving the original object untouched.

const { MissingView } = require('proxy-views');

const user = { name: 'John', age: 25 };

const view = new MissingView(user, (op, target, prop, value) => {
  if (op === 'get') {
    return 'default value'; // returned for undefined property access
  }
  if (op === 'set') {
    // custom behavior for setting an undefined property; here we ignore it
    console.log(`ignored setting ${String(prop)} = ${value}`);
  }
});

console.log(view.name); // 'John' (defined properties pass through)
console.log(view.email); // 'default value' (undefined property hits the callback)

view.email = '[email protected]'; // triggers the set callback; the original is not polluted
console.log(user.email); // undefined

💡 MissingView is the customizable base behind the other views: __missing__(op, target, prop, value) returns your default on get and lets you decide how to handle writes to undefined properties on set.

🎯 Real-world Use Cases

1. Configuration Management

const config = new AliasView(appConfig);
config.alias('database.connection.host', 'dbHost');
const connStr = `${config.dbHost}:${config.dbPort}`;

2. API Response Simplification

const user = new AliasView(apiResponse);
user.alias('user.profile.display_name', 'name');
setUserInfo({ name: user.name });

3. Form Data Mapping

const form = new AliasView(apiData);
form.alias('customer.billing_address.street', 'billingStreet');

📋 API Reference

StrictView

  • new StrictView(object) - Create strict view
  • Only access own properties of the original object; plain data objects only — array/class methods are not own properties and will throw

AliasView

  • new AliasView(object) - Create alias view
  • view.alias(originPath, ...aliasNames) - Create aliases
  • view.toJSON(options) - Export data

ChainView

  • new ChainView(dict1, dict2, ...) - Create chain dictionary view
  • Read-only access with chain lookup
  • Supports Object.keys(), for...of, Object.entries() traversal methods
  • length / size - number of unique keys across all dicts (not array semantics)

MissingView

  • new MissingView(object, __missing__) - Create a missing-property-handling view
  • __missing__(op, target, prop, value) - callback for accessing/setting undefined properties; op is 'get' or 'set'
  • Defined properties pass through; undefined properties hit the callback; the original object is not polluted

🤝 Contributing

欢迎提交 Issue 和 Pull Request! / Contributions are welcome!

📄 License

MIT License - see LICENSE file

📊 Changelog

0.0.2

  • Added ChainView: Chain dictionary view for read-only linking of multiple dictionaries
  • Supports chain lookup and unique key iteration
  • Fully compatible with standard iteration methods (Object.keys, for...of, Object.entries, etc.)

0.0.1

  • Initial release with MissingView, StrictView and AliasView base implementations