{"_id":"@dogsvr/cfg-luban","_rev":"2-581a49219a00cd87e7bf24fb0cee5b8d","name":"@dogsvr/cfg-luban","dist-tags":{"latest":"0.4.1"},"versions":{"0.4.0":{"name":"@dogsvr/cfg-luban","version":"0.4.0","keywords":["dogsvr","game-config","luban","flatbuffers","lmdb"],"author":{"name":"rowanzhu"},"license":"MIT","_id":"@dogsvr/cfg-luban@0.4.0","maintainers":[{"name":"rowanzhu","email":"rowanzhu@gmail.com"}],"homepage":"https://github.com/dogsvr/cfg-luban#readme","bugs":{"url":"https://github.com/dogsvr/cfg-luban/issues"},"dist":{"shasum":"5df2cd2ddebd4668963f81a389602a15a93258c9","tarball":"https://registry.npmjs.org/@dogsvr/cfg-luban/-/cfg-luban-0.4.0.tgz","fileCount":9,"integrity":"sha512-eAnJPlFzfOfp5suD/hccrm04jO5DHSZOFBRvaD9OVCEGj8ZdS7v0bYmoF7HbjMrPpDt7fYT42tLihvMDYh9o0w==","signatures":[{"sig":"MEUCIEAFj2Zwmg8GpLGOYnplumBR+ghzbmbj0xSV6olQOTgxAiEA2kKHP3ScCy3Fc7RFfUi0tQagZGB89LKVTD6neoeTNyw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":30658},"main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./package.json":"./package.json"},"gitHead":"2027c070a71f44e0e1a5848143180a37cccfed93","scripts":{"build":"rm -rf dist && tsc"},"_npmUser":{"name":"rowanzhu","email":"rowanzhu@gmail.com"},"repository":{"url":"git+https://github.com/dogsvr/cfg-luban.git","type":"git","directory":"cfg-luban"},"_npmVersion":"11.6.2","description":"Luban + FlatBuffers + LMDB game config module for dogsvr.","directories":{},"_nodeVersion":"24.13.0","dependencies":{"lmdb":"^3.5.4","flatbuffers":"^25.9.23"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^6.0.3","@types/node":"^24.12.2"},"_npmOperationalInternal":{"tmp":"tmp/cfg-luban_0.4.0_1777813601861_0.5606114698841291","host":"s3://npm-registry-packages-npm-production"}},"0.4.1":{"name":"@dogsvr/cfg-luban","version":"0.4.1","description":"Luban + FlatBuffers + LMDB game config module for dogsvr.","keywords":["dogsvr","game-config","luban","flatbuffers","lmdb"],"main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./package.json":"./package.json"},"scripts":{"build":"rm -rf dist && tsc"},"repository":{"type":"git","url":"git+https://github.com/dogsvr/cfg-luban.git","directory":"cfg-luban"},"author":{"name":"rowanzhu"},"license":"MIT","bugs":{"url":"https://github.com/dogsvr/cfg-luban/issues"},"homepage":"https://github.com/dogsvr/cfg-luban#readme","dependencies":{"flatbuffers":"^25.9.23","lmdb":"^3.5.4"},"devDependencies":{"@types/node":"^24.12.2","typescript":"^6.0.3"},"gitHead":"b9a6c4c783553f463f6fa52833e56978356f0c90","_id":"@dogsvr/cfg-luban@0.4.1","_nodeVersion":"24.13.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-OzNhlttSWydLSMuIJrfQNc+xdDhtuyssuXQow1peTvypBMzd/1HwaNz7G1EEDIQ8sFVp5DBEAFbXzDf+WYfMyw==","shasum":"bca99575c93b7b9827a43ddd2546a005a828f8a1","tarball":"https://registry.npmjs.org/@dogsvr/cfg-luban/-/cfg-luban-0.4.1.tgz","fileCount":9,"unpackedSize":30675,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIH4vs86BeVFVSOw0rnNcyKeDbGUf/UwqAFuKQfFvGnbSAiEAtl7j3eEzplyeFSdMowKvzI/5zwM7+NIYIIAxdsOWY7Y="}]},"_npmUser":{"name":"rowanzhu","email":"rowanzhu@gmail.com"},"directories":{},"maintainers":[{"name":"rowanzhu","email":"rowanzhu@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/cfg-luban_0.4.1_1785066397821_0.3297500925068664"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-03T13:06:41.678Z","modified":"2026-07-26T11:46:38.113Z","0.4.0":"2026-05-03T13:06:42.004Z","0.4.1":"2026-07-26T11:46:37.960Z"},"bugs":{"url":"https://github.com/dogsvr/cfg-luban/issues"},"author":{"name":"rowanzhu"},"license":"MIT","homepage":"https://github.com/dogsvr/cfg-luban#readme","keywords":["dogsvr","game-config","luban","flatbuffers","lmdb"],"repository":{"type":"git","url":"git+https://github.com/dogsvr/cfg-luban.git","directory":"cfg-luban"},"description":"Luban + FlatBuffers + LMDB game config module for dogsvr.","maintainers":[{"name":"rowanzhu","email":"rowanzhu@gmail.com"}],"readme":"# @dogsvr/cfg-luban\n\nRuntime library for reading Luban-generated game config.\n\nData generated by [`@dogsvr/cfg-luban-cli`](../cfg-luban-cli/README.md) is stored in **LMDB** and encoded as **FlatBuffers**. At runtime the library mmap's the database and reads rows via FlatBuffers' offset-based random access. Practical consequences:\n\n- **Out of the V8 heap**. Config bytes live in the OS pagecache, not in Node's managed heap, so they don't count against `--max-old-space-size` and don't pay for full-heap GC scans.\n- **Shared across workers and processes**. All worker threads in one Node process map the same backing pages — `workerThreadNum: N` doesn't multiply the config footprint by N. Multiple Node processes on the same host (e.g. pm2-managed dir/zone/battle servers) all hit the same pagecache pages too; the kernel deduplicates by inode + offset.\n- **No upfront parse**. A cold start doesn't pay to deserialize every table — rows are decoded only when queried, and only the pages actually read get paged into physical RAM.\n- **O(log n) primary-key lookup**. `cfg-luban-cli` sorts every table by its primary key at build time, so the runtime does a binary search over the FlatBuffers vector directly; no hash map build-up at startup.\n\n\"Zero-copy\" is often claimed for this kind of setup but is not strictly true here — by default `getCfgRow` unpacks the FlatBuffers accessor into a plain JS object (one copy). Use `getCfgRowUnsafe` when you want to skip that copy and read fields directly off the accessor (see [Unsafe accessor](#unsafe-accessor-skip-unpack) below for the lifetime caveat).\n\nFor the config generation pipeline (Excel → LMDB), see the sibling package [`@dogsvr/cfg-luban-cli`](../cfg-luban-cli/README.md). For this repo's overall layout and dev workflow, see the [repo README](../README.md). For how this fits into the wider framework, see [`@dogsvr/dogsvr`](https://github.com/dogsvr/dogsvr).\n\n## Install\n\n```sh\nnpm install @dogsvr/cfg-luban\n```\n\n**Node.js**: tested on **v24.13.0 on Linux (x86-64)**; other maintained LTS lines are expected to work but are not routinely exercised. File an issue if something breaks on your runtime.\n\n## API\n\n| Interface | Purpose |\n|-----------|---------|\n| `openCfgDb(options)` | Open the LMDB database and load `table_keys.json`. If `options.cfgModule` is passed (the flatc barrel), every table is auto-registered. |\n| `closeCfgDb()` | Close the DB and clear all registered tables |\n| `registerCfgTable(name, rootFn)` | Manually register a single table's FlatBuffers root accessor. Primary use: per-worker selective loading of large cfg, or tables that don't follow the `getRootAs<fullName>` convention. |\n| `getCfgRow<T>(table, keys)` | Primary-key lookup; returns a plain object. O(log n) |\n| `getCfgRowList<T>(table, keysList)` | Batch lookup on the same table (1 memcpy + N binary searches). O(N log n) |\n| `getCfgRowUnsafe(table, keys)` | Primary-key lookup; returns the raw FlatBuffers accessor (no unpack). O(log n) |\n| `forEachCfgRow<T>(table, cb)` | Iterate the entire table; return `false` from `cb` to stop early |\n\nThe `table` argument is always the Luban `full_name` form (e.g. `'TbItem'`), matching `table_keys.json`.\n\n## Usage\n\n### Worker initialization\n\nConfig paths should come from the worker thread config — don't hardcode them, so that a single build artifact can serve multiple environments. cfg-luban supports two wiring styles. Pick based on table count + per-worker coverage.\n\n#### Style A — barrel module (recommended for small-to-medium cfg)\n\nMinimal boilerplate. Eager-loads every flatc class in the barrel; bundler tree-shaking is defeated by the dynamic `cfgModule[fullName]` access. Fine up to ~1000 tables; beyond that each worker pays for classes it never touches.\n\n```ts\nimport * as dogsvr from '@dogsvr/dogsvr/worker_thread';\nimport { openCfgDb } from '@dogsvr/cfg-luban';\n// The barrel file `ts/<topModule>.ts` is produced by cfg-luban-cli.\n// Adjust the relative path to match your project layout.\nimport * as cfgModule from '<path-to-generated>/ts/cfg';\n\ninterface MyCfg { cfgDbPath: string; tableKeysPath: string; }\n\ndogsvr.workerReady(async () => {\n    dogsvr.loadWorkerThreadConfig();\n    const cfg = dogsvr.getThreadConfig<MyCfg>();\n\n    openCfgDb({\n        dbPath: cfg.cfgDbPath,\n        tableKeysPath: cfg.tableKeysPath,\n        cfgModule,\n    });\n    // done — no registerCfgTable calls\n});\n```\n\n#### Style B — per-table imports + manual `registerCfgTable` (recommended for large cfg with per-worker subsets)\n\nNode only loads the imported table modules + their element-type dependencies. For cfg with thousands of tables where each worker role uses a clear subset, resident memory can drop 5–10× vs. Style A. The tradeoff is N lines of boilerplate and the need to keep the per-worker import list in sync with business code.\n\n```ts\nimport * as dogsvr from '@dogsvr/dogsvr/worker_thread';\nimport { openCfgDb, registerCfgTable } from '@dogsvr/cfg-luban';\n// Import only the tables this worker actually queries.\nimport { TbReward } from '<path-to-generated>/ts/tb-reward';\nimport { TbSkill }  from '<path-to-generated>/ts/tb-skill';\nimport { TbItem }   from '<path-to-generated>/ts/tb-item';\n\ndogsvr.workerReady(async () => {\n    dogsvr.loadWorkerThreadConfig();\n    const cfg = dogsvr.getThreadConfig<{ cfgDbPath: string; tableKeysPath: string }>();\n\n    openCfgDb({ dbPath: cfg.cfgDbPath, tableKeysPath: cfg.tableKeysPath });\n    registerCfgTable('TbReward', TbReward.getRootAsTbReward);\n    registerCfgTable('TbSkill',  TbSkill.getRootAsTbSkill);\n    registerCfgTable('TbItem',   TbItem.getRootAsTbItem);\n});\n```\n\nThe `dbPath` and `tableKeysPath` values come from whatever `worker_thread_config.json` the worker is launched with (see [`@dogsvr/dogsvr`](../../dogsvr/README.md) for how thread config loading works).\n\n### Primary-key lookup\n\n```ts\nimport { getCfgRow } from '@dogsvr/cfg-luban';\n\nconst reward = getCfgRow<RewardT>('TbReward', 1001);            // single key\nconst skill  = getCfgRow<SkillT>('TbSkill', [1001, 5]);         // composite key\nconst text   = getCfgRow<I18nT>('TbI18n', 'LOGIN_TITLE');       // string key\n```\n\n### Batch lookup (performance)\n\n```ts\nimport { getCfgRowList } from '@dogsvr/cfg-luban';\n\n// Same table, many keys: 1 memcpy + N binary searches (not N memcpys)\nconst rewards = getCfgRowList<RewardT>('TbReward', [1001, 1002, 1003]);\n```\n\n### Unsafe accessor (skip unpack)\n\n```ts\nimport { getCfgRowUnsafe } from '@dogsvr/cfg-luban';\n\n// Returns a FlatBuffers accessor; fields are read via method calls.\n// ⚠️ The caller must finish using it within synchronous code — the accessor\n//    becomes invalid after the next getBinaryFast.\nconst item = getCfgRowUnsafe('TbItem', 2001);\nconst damage = item?.damage();\nconst name   = item?.name();\n```\n\n### Iteration\n\n```ts\nimport { forEachCfgRow } from '@dogsvr/cfg-luban';\n\n// Find the first match\nlet found: RewardT | null = null;\nforEachCfgRow<RewardT>('TbReward', (row) => {\n    if (row.count > 1000) { found = row; return false; }\n});\n\n// Filter\nconst weapons: ItemT[] = [];\nforEachCfgRow<ItemT>('TbItem', (row) => {\n    if (row.type === 3) weapons.push(row);\n});\n```\n\n## Migration note\n\nLMDB keys and the `tableName` argument now use `TbXxx` (Luban `full_name`) rather than the lowercase filename stem `tbxxx`. After upgrading cfg-luban + cfg-luban-cli, rerun `npm run build` on your config package once so the regenerated LMDB matches the new keys. `registerCfgTable('tbitem', ...)` emits a one-shot warning to help catch stragglers.\n","readmeFilename":"README.md"}