@flare-engine/storage

中文文档
0.4.12发布于 2026/11/13Apache-2.0TypeScriptESM + CJS零依赖构建来源已验证近 30 天下载 2379

Persistent key/value storage with typed schema, migrations, save slots, and JSON import/export

原始使用文档
文档目录4 节
  1. Install
  2. Usage
  3. Slot headers
  4. License

@flare-engine/storage

Typed, versioned key/value persistence for save games and app settings. Adapters: MMKV on native (sync, sub-millisecond), IndexedDB on web, in-memory for tests. Tiny Zod-ish schema validator (s.string(), s.object(), …) with Infer<S> type extraction. Forward-only Migration[] chain validated by defineMigrationChain — gaps throw at startup, not in production. Single-document versioned store (createDocumentStore) for settings-style docs. Lenient field-drop sanitizers (san.*) for migrating real persisted data.

Install

bun add @flare-engine/storage

Usage

import { createStorage, createMemoryAdapter, s, defineMigrationChain } from '@flare-engine/storage';

const SaveSchema = s.object({
  version: s.literal(2),
  zone: s.number(),
  inventory: s.array(s.string()),
});

const migrations = defineMigrationChain([
  {
    from: 1,
    to: 2,
    // Lift a v1 { zone, items } save into the v2 shape, preserving the player's data.
    migrate: (old: unknown) => {
      const v1 = old as { zone?: number; items?: string[] };
      return { version: 2, zone: v1.zone ?? 0, inventory: v1.items ?? [] };
    },
  },
]);

const storage = createStorage({
  adapter: createMemoryAdapter(), // in-memory: tests/SSR; swap for MMKV on native (below)
  schema: SaveSchema,
  version: 2,
  migrations,
});

await storage.saveSlot('slot1', { version: 2, zone: 4, inventory: ['key'] });
const data = await storage.loadSlot('slot1');

On native, swap in the persistent MMKV adapter. react-native-mmkv is a structural peer — you construct MMKV and pass it in; this package takes no dependency on it (createMmkvAdapter accepts the exported MMKVLike subset):

import { MMKV } from 'react-native-mmkv';
import { createStorage, createMmkvAdapter } from '@flare-engine/storage';

const storage = createStorage({
  adapter: createMmkvAdapter(new MMKV()),
  schema: SaveSchema,
  version: 2,
  migrations,
});

Single-document store with lenient parser for a settings doc:

import { createDocumentStore, createMemoryAdapter, san } from '@flare-engine/storage';

interface Settings { locale: string; volume: number; }

function parseSettings(raw: unknown): Settings {
  return san.object({ locale: san.string('en'), volume: san.clamped(1, 0, 1) })(raw);
}

const store = createDocumentStore<Settings>({
  adapter: createMemoryAdapter(),
  key: 'myapp:settings',
  version: 1,
  parse: parseSettings,
  onFirstRun: (doc) => ({ ...doc, locale: 'en' }),
});

const settings = await store.load();
await store.save({ ...settings, volume: 0.8 });

Slot headers

A slot menu needs one line per save ("Day 12 · Kaszuby · 6 h played") without paying for loadSlot on each: that runs every migration and the full schema parse per slot, and one corrupt save throws and takes the menu down. Pass a small, JSON-serialisable header as the third argument of saveSlot; listSlotHeaders() returns { name, version, header } for every slot from the envelope alone, with no migration and no schema. A slot saved without a header, or written before headers existed, lists with header: null; a slot whose envelope cannot be read lists with version: null and header: null. A bad slot never fails the others.

import { createStorage, createMemoryAdapter, s, san } from '@flare-engine/storage';

const SaveSchema = s.object({ day: s.number(), zone: s.string(), inventory: s.array(s.string()) });

// Lenient and never throwing: a header written before `playedSeconds` existed lists it as 0.
const parseHeader = san.object({
  day: san.number(0),
  zone: san.string('unknown'),
  playedSeconds: san.number(0),
});

const storage = createStorage({
  adapter: createMemoryAdapter(),
  schema: SaveSchema,
  version: 1,
  parseHeader,
});

await storage.saveSlot(
  'slot1',
  { day: 12, zone: 'Kaszuby', inventory: ['key'] },
  { day: 12, zone: 'Kaszuby', playedSeconds: 21_600 },
);

for (const slot of await storage.listSlotHeaders()) {
  const label = slot.header
    ? `Day ${slot.header.day} · ${slot.header.zone} · ${Math.round(slot.header.playedSeconds / 3600)} h played`
    : 'Unreadable save';
  console.log(slot.name, slot.version ?? '?', label);
}
  • Headers are not migrated. listSlotHeaders returns an old header as it was written, so a consumer that changes its header shape reads the old shape unless parseHeader repairs it. parseHeader is a lenient Sanitizer<H> like createDocumentStore's parse; it also types H, runs only in listSlotHeaders (saveSlot stores the header as given), and a header that makes it throw lists as null.
  • Cost. listSlotHeaders still reads and JSON-parses each slot's full string; it skips only the migrations and the schema. The saving is per-slot CPU and the no-throw contract, not I/O.
  • Overwrite drops the header. saveSlot(name, data) without a header removes the slot's previous header; pass it on every save.
  • A header-less saveSlot writes the same { version, data } envelope as before, and loadSlot ignores the header, so existing saves need no migration.

See API spec.

License

Apache-2.0

版本与发布记录

@flare-engine/storage 共发布 17 个版本,首次发布于 2026/06/10;过去 12 个月发布了 17 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.4.12latest2026/11/133 天前
0.4.112026/11/123 天前
0.4.102026/11/087 天前
0.4.82026/11/0610 天前
0.4.72026/11/0411 天前
0.4.62026/10/3115 天前

关于 @flare-engine/storage 的常见问题

@flare-engine/storage 是什么?

Persistent key/value storage with typed schema, migrations, save slots, and JSON import/export

如何安装 @flare-engine/storage?

在项目目录运行 npm install @flare-engine/storage,或使用 pnpm add / yarn add / bun add。

@flare-engine/storage 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@flare-engine/storage 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@flare-engine/storage 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@flare-engine/storage 可以商用吗?

@flare-engine/storage 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@flare-engine/storage 的下载量有多少?

根据 npm 官方下载统计,@flare-engine/storage 在 2026/10/15 – 2026/11/13 这 30 天内被下载 2,379 次,日均约 79 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@flare-engine/storage 还在维护吗?

最新版本 0.4.12 发布于 2026/11/13(3 天前);至今共发布 17 个版本,其中过去 12 个月发布了 17 个;首次发布于 2026/06/10。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。