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

@rfjs/data-schema

v0.1.0

Published

Data resource metadata contract: field metadata, request pagination/sort protocol, response envelope paths, with infer/build/extract helpers

Downloads

93

Readme

@rfjs/data-schema

English → README.md

資料資源中繼資料契約(data resource metadata contract):一個小巧、以 zod 驗證的形狀, 描述一個表格型資料資源 —— 它的欄位(key/label/dataType/format/options/sortable)、 如何請求其中一頁(分頁策略 + 排序編碼),以及如何從回應信封(response envelope)中 讀出一頁資料(rows/total/cursor 路徑)。它沒有 UI,也不做任何 fetch 呼叫 —— 它是 @rfjs/table-builder 及其下游消費者共同編譯所依據的共享詞彙。


安裝

npm i @rfjs/data-schema

快速開始

import {
  inferFieldsFromRows,
  buildRequestParams,
  extractRows,
  extractTotal,
  extractCursor,
  type DataResourceMeta,
} from '@rfjs/data-schema';

// 1. 定義(或從樣本資料列推斷)欄位中繼資料
const sampleRows = [
  { id: 1, name: 'Widget', price: 9.99, createdAt: '2024-03-15' },
  { id: 2, name: 'Gadget', price: 19.5, createdAt: '2024-03-16' },
];
const fields = inferFieldsFromRows(sampleRows);
// → [{ key: 'id', label: 'id', dataType: 'numeric' }, { key: 'name', label: 'name', dataType: 'string' }, ...]

const meta: DataResourceMeta = {
  fields,
  request: {
    endpoint: '/api/products',
    pagination: { strategy: 'page', pageParam: 'page', pageSizeParam: 'pageSize' },
    sort: { style: 'split', fieldParam: 'sortBy', dirParam: 'order' },
  },
  response: {
    rowsPath: 'data.items',
    totalPath: 'data.total',
  },
};

// 2. 依指定的頁碼 + 排序狀態建構請求參數
const built = buildRequestParams(meta.request!, {
  pageSize: 20,
  page: 1,
  sort: { key: 'price', direction: 'desc' },
});
// → { endpoint: '/api/products', method: 'GET', params: { page: '1', pageSize: '20', sortBy: 'price', order: 'desc' } }

const res = await fetch(`${built.endpoint}?${new URLSearchParams(built.params)}`, { method: built.method });
const payload = await res.json();
// payload = { data: { items: [{ id: 1, name: 'Widget', ... }], total: 87 } }

// 3. 從回應信封中讀出這一頁
const rows = extractRows(payload, meta.response!); // → payload.data.items
const total = extractTotal(payload, meta.response!); // → 87
const cursor = extractCursor(payload, meta.response!); // → undefined(未設定 cursorPath)

inferFieldsFromRows 只讀取純物件(plain object)的資料列:巢狀物件會被遞迴走訪 (以 dot path 表示,例如 'author.name'),陣列與 null/undefined 葉節點會被跳過; 若同一個 key 在不同資料列間推斷出不同型別,會回退為 'string'

驗證不受信任的中繼資料

來自設定檔、資料庫或使用者輸入的中繼資料,應該透過 zod schema 解析,而不是直接當作 DataResourceMeta 信任:

import { parseDataResourceMeta } from '@rfjs/data-schema';

const meta = parseDataResourceMeta(rawJson); // 形狀或 format 不符時拋出 ZodError

format 會與 dataType 交叉檢查(例如 'currency' 只在 dataType: 'numeric' 時合法, 'datetime' 只在 dataType: 'date' 時合法),透過 superRefine 實作 —— 因此不一致的組合 會在驗證階段就失敗,而不是靜默地渲染錯誤結果。

API

Infer / build / extract

| 匯出 | 簽章 | 說明 | | --- | --- | --- | | inferFieldsFromRows(rows) | (unknown) => DataFieldMeta[] | 從樣本資料列推斷每個 key 的 dataType;若 rows 不是純物件陣列則拋出例外 | | buildRequestParams(request, state) | (RequestMeta, PageState) => BuiltRequest | 將分頁(offset/page/cursor)+ 選用的排序編碼進查詢參數 | | extractRows(payload, response) | (unknown, ResponseMeta) => unknown[] | 若 rowsPath 處的值不是陣列則拋出例外 | | extractTotal(payload, response) | (unknown, ResponseMeta) => number \| undefined | 當 totalPath 未設定或解析出非數字時回傳 undefined | | extractCursor(payload, response) | (unknown, ResponseMeta) => string \| undefined | 當 cursorPath 未設定或解析出非字串時回傳 undefined | | getByPath(obj, path) | (unknown, string) => unknown | dot path 取值器;'' 會回傳 obj 本身 | | resolveLabel(label, locale, fallbackLocale?) | (LocalizedLabel, string, string?) => string | 字串直接回傳,或依 locale 對照表查找(附 fallback locale / 第一個值的保底) |

驗證(zod)

| 匯出 | 驗證對象 | | --- | --- | | parseDataResourceMeta(input) | DataResourceMeta;形狀不合法時拋出例外 | | dataResourceMetaSchema | 完整契約(fields + 選用的 request/response) | | dataFieldMetaSchema | 單一欄位;交叉檢查 formatdataType | | paginationMetaSchema | 以 strategy 區分的 discriminated union | | sortMetaSchema | 以 style 區分的 discriminated union | | requestMetaSchemaresponseMetaSchema | 請求 / 回應中繼資料 | | fieldOptionSchemafieldFormatSchemalocalizedLabelSchema | 葉節點 schema |

核心型別

| 型別 | 形狀 | | --- | --- | | DataFieldMeta | { key, label, dataType, format?, options?, sortable?, filterable? } | | PaginationMeta | { strategy: 'offset', limitParam, offsetParam } | { strategy: 'page', pageParam, pageSizeParam, firstPage? } | { strategy: 'cursor', cursorParam, limitParam } | | SortMeta | { style: 'single', param, encoding: 'colon' \| 'signed' } | { style: 'split', fieldParam, dirParam } | | RequestMeta | { endpoint, method?, pagination, sort? } | | ResponseMeta | { rowsPath, totalPath?, cursorPath? } | | DataResourceMeta | { fields, request?, response? } | | SortState | { key, direction: 'asc' \| 'desc' } —— 消費端的排序狀態 | | PageState | { pageSize, offset?, page?, cursor?, sort? } —— 消費端的分頁狀態 | | BuiltRequest | { endpoint, method, params } —— buildRequestParams 的結果 | | ScalarType | 'string' \| 'numeric' \| 'date' \| 'boolean' | | FieldFormat | 'integer' \| 'decimal' \| 'percent' \| 'currency'(僅 numeric)| 'date' \| 'datetime' \| 'time'(僅 date) | | LocalizedLabel | string \| Record<string, string> |

家族關係

 @rfjs/data-schema           ← 你在這裡
 欄位 / 請求 / 回應中繼資料契約 + infer/build/extract 輔助函式
              ▼
 @rfjs/table-builder
 deriveTableConfig(meta) 將 DataResourceMeta 編譯為 TableConfig,
 並提供 sortRows / formatCell / paginate 等純函式
              ▼
 @rfjs/table-builder-ui              @rfjs/form-builder(result item)
 TableConfig 的 React 渲染器            mode: 'table' 內嵌一個 TableConfig

data-schema 是最底層的契約:它不認識 React、表格或表單。任何下游若需要描述 「某個資源、帶有這些欄位的一頁資料」,都應該建立在這個套件之上,而不是自行發明形狀。

授權

ISC