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

@danor-lib/poseidon

v10.0.5

Published

Type-based config library

Downloads

728

Readme

[!TIP] 本文档由 AI 阅读代码后生成。中文版本已经过本人初步审校。英文版本基于中文版本由 AI 翻译生成。如有问题或更好的文档,欢迎提交 Issue。
This document is generated by AI after reading the code. The Chinese version has been preliminarily reviewed by the author. The English version is AI-translated based on the Chinese version. If you have any issues or a better version of the documentation, please feel free to submit an Issue.

简体中文 | English

@danor-lib/poseidon

Version License

一个基于分类(type)的数据文件管理库,适用于 Node.js 环境。
A type-grouped data file management library for Node.js.

Poseidon 将同一目录下的多个分类数据文件,合并组织成一个只读的大 Object,以取代传统的通过函数获取配置/数据的方式,大幅减少代码量和心智消耗。所有加载的数据会被深度冻结(只读),并自动将路径标记字段解析为绝对路径。

注意:文档中使用 config 作为文件名前缀仅是一种预设。实际上 Poseidon 已完全解耦,通过 prefix 选项可面向任意类型的数据文件(如 statedatasetting 等)。

设计理念

传统的配置管理通常需要通过读取函数访问不同分类的数据文件。Poseidon 的设计目标是:

  • 一次构造,随处访问 — 通过 Proxy 将目录下所有分类数据文件映射为一个大 Object 的属性。
  • 懒加载 — 构造时不读取任何文件,仅在首次访问属性时才加载并缓存。
  • 不可变数据 — 所有加载的数据默认深度冻结,杜绝意外修改。
  • 可扩展前缀config 只是默认前缀,你可以用它管理 statedata 等任意分类的数据文件。

基础示例

import { Poseidon } from '@danor-lib/poseidon';

// --- 创建 Poseidon 实例 ---
const P = new Poseidon({ dirn: './config' });

// --- 直接访问分类数据文件 ---
// config.apple.json 的数据 → P.apple(支持任意 JSON 类型)
console.log(P.apple);  // { color: 'red', price: 5 }

// config.boy.json 的数据 → P.boy
console.log(P.boy);    // { name: 'Tom', age: 12 }

// --- 默认分类 config.json 的字段会展开到顶层 ---
// 若 config.json = { cat: 'lihua' },则可以直接访问:
console.log(P.cat);    // 'lihua'

// --- 通过 `$` 访问管理方法 ---
console.log(P.$.selectExistTypes());

// --- 修改数据并保存 ---
P.$.edit('apple', (data) => {
  data.price = 6;
  return data;
});

// --- 找不到文件时返回 undefined(默认不抛错) ---
console.log(P.unknown);  // undefined

配置文件结构

config/
├── config.json               ← 默认分类(type = `_`),必须是 Object
├── config.apple.json         ← 普通分类 `apple`,可以是任意 JSON 类型
├── config.boy.json           ← 普通分类 `boy`
├── .config.secret.json       ← 隐藏文件(Linux 风格,以 `.` 开头),优先级高于普通文件
└── config.apple.backup1.json ← 备份文件(`selectExistTypes()` 会归入 backups)
  • 文件名格式:{prefix}.{type}.{ext},其中 prefix 默认为 configtype 为分类名,ext 为扩展名。
  • 默认分类文件名为 {prefix}.{ext}(无 type 段),内部使用 _ 表示。
  • 支持的扩展名由 exts 选项控制,默认 ['.json']

核心概念

分类(Type)与文件映射

| 文件名 | 分类(type) | 访问方式 | 说明 | | :------------------- | :----------- | :-------- | :---------------------------------------------------------------- | | config.json | _ | P.cat | 默认分类,必须是 Object,其字段展开到 Proxy 顶层 | | config.apple.json | apple | P.apple | 普通分类,支持任意 JSON 类型(Object / Array / string / number…) | | .config.apple.json | apple | P.apple | 隐藏文件,优先级高于 config.apple.json |

保留类型

以下类型名称被 Poseidon 内部占用,不能作为数据分类使用

| 保留类型 | 用途 | 说明 | | :------------ | :-------------------- | :--------------------------------------------------------------------------------------------------------- | | $ | 访问管理方法 | P.$ 返回 Poseidon 实例自身,用于调用 read / load / save / edit 等方法 | | _ | 默认分类标识 | 在 P.$.read('_') 等管理方法中表示默认分类。P._ 虽然可以返回 config.json 的数据,但不建议直接使用 | | constructor | JavaScript 原型链属性 | 由于 JS Class 的设计限制,访问 P.constructor 不会触发 Proxy 的 getter,因此无法用于数据分类 |

这意味着 config.$.jsonconfig._.jsonconfig.constructor.json 等文件不会被加载

默认分类与字段展开

默认分类(_,即 config.json)必须是一个 Object。它的顶层字段会展开到 Proxy 上,可以像访问普通属性一样直接读取:

// config.json
{ "cat": "lihua", "host": "localhost" }
const P = new Poseidon({ dirn: './config' });
console.log(P.cat);   // 'lihua'
console.log(P.host);  // 'localhost'

非默认分类的数据则通过分类名整体访问,不做展开。

分类冲突与 preferDefault

当默认分类 config.json 的某个字段名与另一个分类文件名同名时,会产生冲突:

config/
├── config.json       ← 内容: { "apple": "red" }
└── config.apple.json ← 内容: { "color": "green" }

此时访问 P.apple,应该返回 config.json 中的 "red",还是 config.apple.json 中的 { color: "green" }

这由 preferDefault 选项决定:

| preferDefault | 行为 | | :-------------- | :----------------------------------------------------------- | | true(默认) | 优先返回默认分类中的字段值。P.apple"red" | | false | 优先返回独立分类文件的数据。P.apple{ color: "green" } |

PoseidonBox 包装类

@danor-lib/poseidon 导入的 Poseidon 实际上是 PoseidonBox 类。它是一个包装类,构造函数直接返回 Poseidon 实例(Proxy),但其原型链指向 PoseidonBox

这样设计有两个目的:

  1. 干净的智能提示PoseidonBox 只有 $constructor 两个属性,编辑器不会将管理方法(如 readsave)混入数据属性的提示中。
  2. 便于 TypeScript 类型扩展 — 使用者可以通过 declare module 为特定分类声明类型:
declare module '@danor-lib/poseidon' {
  interface Poseidon {
    readonly 'apple': { color: string; price: number; };
    readonly 'boy': { name: string; age: number; };
  }
}

export { }; // 将文件标记为模块

这样在编辑器中访问 P.apple 时就能获得完整的类型提示。

如果需要直接使用核心类(如子类化),可以导入 Poseidon_

import { Poseidon_ } from '@danor-lib/poseidon';

构造函数

new Poseidon(options?)

创建一个 Poseidon 实例,返回 Proxy 对象。

  • 参数 options {Object} — 可选。所有属性均为可选。

| 选项 | 类型 | 默认值 | 说明 | | :-------------- | :----------------------- | :--------------- | :----------------------------------------------------- | | prefix | string | 'config' | 数据文件名前缀。设为 'state' 则读取 state.xxx.json | | dirn | string | process.cwd() | 数据文件所在目录 | | preloads | string \| string[] | [] | 需要预加载的分类。字符串以 , 分隔,如 '_,apple' | | exts | string[] | ['.json'] | 支持的扩展名列表,靠前的优先级更高 | | parser | (buffer, self) => data | JSON.parse | 自定义解析函数 | | packer | (data, self) => string | JSON.stringify | 自定义序列化函数 | | preferDefault | boolean | true | 默认分类与独立分类冲突时,是否优先返回默认分类的值 | | willFreeze | boolean | true | 加载后是否深度冻结数据 | | absolutize | Object | 见下方 | 路径绝对化配置 | | howAssign | 'throw' \| 'ignore' | 'throw' | 对 Proxy 赋值时的行为 |

absolutize 子选项:

| 子选项 | 类型 | 默认值 | 说明 | | :---------- | :-------- | :----- | :------------------------------- | | enable | boolean | true | 是否启用路径绝对化 | | prefix | string | '_' | 需要绝对化的 key 前缀 | | overwrite | boolean | true | 是否覆盖已有同名属性 | | path | string | '' | 数据目录与相对路径之间的中间路径 |

// 使用默认选项
const P = new Poseidon();

// 自定义目录和前缀
const P = new Poseidon({ dirn: './data', prefix: 'state' });

// 预加载多个分类
const P = new Poseidon({ preloads: '_,apple,boy' });
// 等价于
const P = new Poseidon({ preloads: ['_', 'apple', 'boy'] });

// 自定义解析器(如 JSONC 支持)
import JSONC from 'comment-json';

const P = new Poseidon({
  exts: ['.jsonc', '.json'],
  parser: (buffer) => JSONC.parse(buffer.toString()),
  packer: (data) => JSONC.stringify(data, null, '\t'),
});

// 关闭冻结和路径绝对化
const P = new Poseidon({ willFreeze: false, absolutize: { enable: false } });

实例属性 / 方法

通过 P.$ 获取 Poseidon 实例,进行底层操作。

实例属性

| 属性 | 类型 | 说明 | | :------------------------ | :------------------------------ | :------------------------------------------- | | $.dirnData | string | 数据文件目录的绝对路径 | | $.prefixFile | string | 文件名前缀,默认为 'config' | | $.datas$type | Record<string, any> | 已加载的数据缓存(key 为分类名) | | $.buffers$type | Record<string, Buffer> | 已加载的原始文件 Buffer 缓存(key 为分类名) | | $.extensions | string[] | 当前使用的扩展名列表 | | $.infos$type | Record<string, {hidden, ext}> | 各分类对应文件的元信息 | | $.preferDefault | boolean | 冲突时是否优先默认分类 | | $.willFreeze | boolean | 是否启用深度冻结 | | $.parser | Function | 当前使用的解析函数 | | $.packer | Function | 当前使用的序列化函数 | | $.optionsPathAbsolutize | Object | 路径绝对化选项 | | $.howAssign | string | 赋值行为('throw' / 'ignore') | | $.proxy | Proxy | 关联的 Proxy 实例 |

$.read(type, willParse?)

读取数据文件的原始内容,不进行冻结或路径转换。

  • 参数
    • type {string} — 分类名。
    • willParse {boolean} — 默认 truetrue 时返回解析后的数据;false 时返回原始 Buffer。
  • 返回值:解析后的数据 或 Buffer。
const data = P.$.read('apple');          // 返回解析后的数据
const buffer = P.$.read('apple', false); // 返回原始 Buffer

$.load(type, willThrow?)

加载一个数据文件。数据会被深度冻结(若启用),路径标记字段会被转换为绝对路径(若启用)。可重复加载以覆盖旧缓存。

  • 参数
    • type {string} — 分类名。
    • willThrow {boolean} — 默认 falsetrue 时文件不存在会抛出错误;false 时返回 undefined
  • 返回值:冻结后的数据,或 undefined
P.$.load('apple');            // 加载并缓存
P.$.load('apple');            // 重复加载,覆盖旧缓存
P.$.load('unknown', true);    // 文件不存在则抛出错误

$.save(type, data, options?)

将数据写入文件。支持写入前自动备份。

  • 参数
    • type {string} — 分类名。
    • data {any} — 要保存的数据。
    • options {Object} — 可选。
      • willBackup {boolean} — 默认 falsetrue 时先将现有文件备份再写入。
      • dirnBackup {string} — 备份/写入目录,默认为实例的 dirnData
      • padBackup {number} — 备份ID的最小长度,不足则填充0,默认为 1
      • prefix {string} — 文件名前缀,默认使用实例的 prefixFile
      • ext {string} — 扩展名,默认使用实例的 extensions[0]
  • 返回值Poseidon 实例,支持链式调用。
// 普通保存
P.$.save('apple', { color: 'red', price: 6 });

// 带备份保存
P.$.save('apple', newData, { willBackup: true });
// 备份文件: config.apple.backup1.json → config.apple.backup2.json ...

// 保存备份到其他目录
P.$.save('apple', data, { willBackup: true, dirnBackup: './backup' });

$.edit(type, handle)

修改、保存并重新加载数据的便捷方法。支持同步和异步回调。

  • 参数
    • type {string} — 分类名。
    • handle {(data, dataRaw, type, self) => newData} — 修改回调。接收当前解析后的数据、原始 Buffer、分类名和 Poseidon 实例自身,返回修改后的数据。若返回 Promise 则自动等待。
  • 返回值Poseidon 实例(异步时返回 Promise<Poseidon>)。
// 同步修改
P.$.edit('apple', (data) => {
  data.price = 10;
  return data;
});

// 异步修改
await P.$.edit('apple', async (data, _raw, _type, self) => {
  const remote = await fetchRemoteData();
  return { ...data, ...remote };
});

$.selectExistTypes()

扫描数据目录,列出所有分类及其文件和备份信息。

  • 返回值Record<string, { files: FileInfo[], backups: BackupInfo[] }> — 以分类名为 key,每个分类包含 files(普通文件列表)和 backups(备份文件列表)。
const info = P.$.selectExistTypes();

info结构参考

{
  _: {
    files: [{ hidden: false, type: '_', backup: false, ext: '.json', file: 'config.json' }],
    backups: []
  },
  apple: {
    files: [
      { hidden: false, type: 'apple', backup: false, ext: '.json', file: 'config.apple.json' },
      { hidden: true,  type: 'apple', backup: false, ext: '.json', file: '.config.apple.json' }
    ],
    backups: [
      { hidden: false, type: 'apple', backup: 1, ext: '.json', file: 'config.apple.backup1.json' }
    ]
  }
}
// 获取所有可用分类名
const types = Object.keys(P.$.selectExistTypes());
console.log(types); // ['_', 'apple', 'boy']

高级特性

1. Proxy 代理访问

new Poseidon(...) 返回一个 Proxy 对象,访问行为如下:

  1. 保留属性优先$ 返回 Poseidon 实例;constructor 返回类构造函数。
  2. 分类匹配 — 若 key 已在缓存中,直接返回对应分类数据。
  3. 默认分类展开 — 若默认分类(_)是 Object,尝试从其字段中查找(preferDefault 控制优先级)。
  4. 懒加载 — 以上均未命中时,自动调用 load(key) 安全加载(文件不存在返回 undefined)。
  5. 禁止赋值 — Proxy 的 setter 在 howAssign='throw' 时抛出 forbidden-set 错误(默认行为)。
const P = new Poseidon({ dirn: './config' });

// config.json = { host: 'localhost' }
P.host;   // 命中规则 3:从默认分类展开

// 存在 config.apple.json
P.apple;  // 命中规则 4:自动懒加载

// 保留属性
P.$;           // 命中规则 1:Poseidon 实例
P.constructor; // 命中规则 1:PoseidonBox 构造函数

// 赋值行为
P.host = 'new';  // 抛出 forbidden-set 错误(默认)

2. 路径绝对化

数据对象中所有以 _ 开头的 key(可通过 absolutize.prefix 自定义),其字符串值会被自动解析为绝对路径(相对于 dirnData + absolutize.path):

// config.apple.json
{
  "_pathIcon": "./icons/apple.png",
  "_pathData": "../data/apple.dat",
  "nested": {
    "_cacheDir": "./cache"
  },
  "normalKey": "保持不变"
}

加载后:

const P = new Poseidon({ dirn: '/app/config' });

console.log(P.apple.pathIcon);          // '/app/config/icons/apple.png'
console.log(P.apple._pathIcon);         // './icons/apple.png'(原始值保留)
console.log(P.apple.pathData);          // '/app/data/apple.dat'
console.log(P.apple.nested.cacheDir);   // '/app/config/cache'
console.log(P.apple.normalKey);         // '保持不变'

规则说明:

  • 仅处理字符串值;嵌套 Object 会递归处理。
  • 绝对化后的 key 会去掉前缀 _。如果目标 key 已存在且 absolutize.overwritefalse,则跳过。
  • 原始带前缀的 key 保持不变。

3. 深度冻结

所有通过 load() 加载的数据默认会被递归深度冻结(Object.freeze),任何修改尝试都会在严格模式下抛出错误。如需修改数据,必须使用 edit()save() 方法。

可通过 willFreeze: false 选项关闭冻结。

4. 备份机制

save() 方法支持 willBackup 选项。启用后,在覆盖写入前会先将现有文件复制为备份。

备份文件命名规则:{prefix}.{type}.backup{n}.{ext}{n} 为自增序号:

config/
├── config.apple.json            ← 当前数据
├── config.apple.backup1.json    ← 第 1 次备份
├── config.apple.backup2.json    ← 第 2 次备份
└── config.apple.backup3.json    ← 第 3 次备份

selectExistTypes() 会将备份文件归入 backups 数组,与普通文件区分开。

5. 隐藏文件

Poseidon 支持读取 Linux 风格的隐藏文件(文件名以 . 开头)。当同时存在 .config.apple.jsonconfig.apple.json 时,隐藏文件具有更高优先级(在 read() 中优先尝试)。

6. 自定义解析器与扩展名

通过 extsparserpacker 选项,可以支持任意文件格式:

// JSONC 示例
import JSONC from 'comment-json';

const P = new Poseidon({
  exts: ['.jsonc', '.json'],
  parser: (buffer) => JSONC.parse(buffer.toString()),
  packer: (data) => JSONC.stringify(data, null, '\t'),
});

// YAML 示例(需安装 yaml 包)
import { parse as parseYAML, stringify as stringifyYAML } from 'yaml';

const P = new Poseidon({
  exts: ['.yaml', '.yml', '.json'],
  parser: (buffer) => parseYAML(buffer.toString()),
  packer: (data) => stringifyYAML(data),
});

7. 自定义前缀

通过 prefix 选项可以改变文件名前缀,使 Poseidon 管理不同类型的数据文件:

// 管理 state 文件: state.apple.json, state.boy.json, state.json ...
const state = new Poseidon({ dirn: './data', prefix: 'state' });

// 管理 data 文件: data.apple.json, data.boy.json, data.json ...
const store = new Poseidon({ dirn: './data', prefix: 'data' });

8. 懒加载与预加载

默认情况下,构造 Poseidon 时不会读取任何文件。数据仅在第一次通过 Proxy 访问时才会加载并缓存到内存中。

如果需要在构造时就加载某些分类,使用 preloads 选项:

// 构造时立即加载 config.json 和 config.apple.json
const P = new Poseidon({ preloads: '_,apple' });

错误代码

| 代码 | 位置 | 描述 | 上下文数据 | | ----------------------------------- | ----------------------------------------------------------- | ----------------------------------------------------- | ---------------------- | | invalid-options | poseidon/Poseidon#constructor(options) | options 参数不是对象 | { options } | | invalid-prefix-file | poseidon/Poseidon#constructor(options.prefix) | prefix 选项值类型无效(必须为字符串) | { prefix } | | invalid-dirn-data | poseidon/Poseidon#constructor(options.dirn) | dirn 选项值类型无效(必须为字符串) | { dirn } | | invalid-preload | poseidon/Poseidon#constructor(options.preload) | preloads 选项值类型无效(必须为字符串或数组) | { types } | | invalid-preload-type | poseidon/Poseidon#constructor(options.preload) | preloads 中某个元素类型无效(必须为字符串或数字) | { type, types, index } | | invalid-parser | poseidon/Poseidon#constructor(options.parser) | parser 选项值类型无效(必须为函数) | { parser } | | invalid-packer | poseidon/Poseidon#constructor(options.packer) | packer 选项值类型无效(必须为函数) | { packer } | | invalid-extensions | poseidon/Poseidon#constructor(options.exts) | exts 选项值类型无效(必须为数组) | { extensions } | | invalid-prefer-default | poseidon/Poseidon#constructor(options.preferDefault) | preferDefault 选项值类型无效(必须为布尔值) | { preferDefault } | | invalid-will-freeze | poseidon/Poseidon#constructor(options.willFreeze) | willFreeze 选项值类型无效(必须为布尔值) | { willFreeze } | | invalid-absolutize-option | poseidon/Poseidon#constructor(options.absolutize) | absolutize 选项值类型无效(必须为对象) | { absolutize } | | invalid-absolutize-option-enable | poseidon/Poseidon#constructor(options.absolutize.enable) | absolutize.enable 选项值类型无效(必须为布尔值) | { enable } | | invalid-absolutize-option-prefix | poseidon/Poseidon#constructor(options.absolutize.prefix) | absolutize.prefix 选项值类型无效(必须为字符串) | { prefix } | | invalid-absolutize-option-overwrite | poseidon/Poseidon#constructor(options.absolutize.overwrite) | absolutize.overwrite 选项值类型无效(必须为布尔值) | { overwrite } | | invalid-absolutize-option-path | poseidon/Poseidon#constructor(options.absolutize.path) | absolutize.path 选项值类型无效(必须为字符串) | { path } | | invalid-how-assign | poseidon/Poseidon#constructor(options.howAssign) | howAssign 选项值无效,仅允许 'ignore' 'throw' | { howAssign } | | invalid-type | poseidon/Poseidon#read(1:type) | type 参数类型无效(必须为字符串) | { type } | | invalid-type | poseidon/Poseidon#load(type) | type 参数类型无效(必须为字符串) | { type } | | invalid-type | poseidon/Poseidon#save(1:type) | type 参数类型无效(必须为字符串) | { type } | | invalid-options | poseidon/Poseidon#save(3:options) | save()options 参数不是对象 | { options } | | invalid-options-willBackup | poseidon/Poseidon#save(3:options.willBackup) | willBackup 选项值类型无效(必须为布尔值) | { willBackup } | | invalid-options-dirn-backup | poseidon/Poseidon#save(3:options.dirnBackup) | dirnBackup 选项值类型无效(必须为字符串) | { dirnBackup } | | invalid-options-pad-start-backup | poseidon/Poseidon#save(3:options.padBackup) | padBackup 选项值类型无效(必须为字符串) | { padBackup } | | invalid-options-prefix | poseidon/Poseidon#save(3:options.prefix) | prefix 选项值类型无效(必须为字符串) | { prefix } | | invalid-options-ext | poseidon/Poseidon#save(3:options.ext) | ext 选项值类型无效(必须为字符串) | { extension } | | forbidden-set | poseidon/Poseidon#proxy.set | 尝试对 Proxy 进行赋值操作(howAssign'throw') | { key, value } |