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

meeglesdk

v0.1.4

Published

飞书项目 Open API TypeScript SDK

Readme

meeglesdk

TypeScript SDK for Feishu Project (Meego) Open API.

Install

npm install meeglesdk

Quick Start

import { MeegoClient, withUserKey } from 'meeglesdk';

const client = new MeegoClient({
  pluginId: 'YOUR_PLUGIN_ID',
  pluginSecret: 'YOUR_PLUGIN_SECRET',
  baseURL: 'https://project.feishu.cn',
});

const items = await client.workItem.query(
  'project_key',
  'story',
  { work_item_ids: [123456] },
  withUserKey('user_key')
);

Client Options

const client = new MeegoClient({
  pluginId: 'YOUR_PLUGIN_ID',
  pluginSecret: 'YOUR_PLUGIN_SECRET',
  baseURL: 'https://project.feishu.cn', // optional
  timeout: 30000,                       // optional, ms
  retry: {
    maxRetries: 2,
    retryDelay: 1000,                   // base delay, exponential backoff
    maxRetryDelay: 30000,               // optional cap for backoff delay
    retryableErrorCodes: [10429, 50006] // optional override
  },
  rateLimit: 'meego-openapi',           // preset: doc-based limits (15 QPS + endpoint overrides)
  // or customize:
  // rateLimit: {
  //   enabled: true,                    // optional, default true when provided
  //   qps: 15,                          // per token QPS
  //   burst: 15,                        // optional burst size
  //   scope: 'token+method+path',       // 'token' | 'token+path' | 'token+method+path'
  //   entryTtlMs: 600000,               // optional, idle bucket cleanup (default 10 min)
  //   rules: [                          // optional overrides
  //     { method: 'POST', path: '/open_api/work_item/actual_time/update', qps: 10 },
  //   ],
  // },
  tokenType: 0,                         // 0: plugin_access_token, 1: virtual_plugin_token
  onTokenRefresh: (token, type, userKey) => {
    // persist token if needed
  },
  logger: console,                      // optional
});

Auth Flows

Plugin Token (Server)

const token = await client.auth.getPluginToken();

User Token (Auth Code Exchange)

const { token, expire_time, refresh_token } = await client.auth.getUserToken(code);

Manually Set User Token

client.setUserToken({
  userKey,
  token,
  expireTime: expireTimeSeconds,
  refreshToken,
  refreshTokenExpireTime,
  saasTenantKey: tenantKey,
});

Request-Level Auth (Recommended for Multi-User)

Do not store user tokens on a shared client. Instead, pass auth per request:

await client.workItem.query(
  'project_key',
  'story',
  { work_item_ids: [123456] },
  { auth: { type: 'user', token: userToken } }
);

// Helper shortcuts (return request options)
await client.workItem.query(
  'project_key',
  'story',
  { work_item_ids: [123456] },
  withUserKey('user_key')
);

Rules:

  • type: 'user': use the provided user token; userKey is not needed.
  • type: 'plugin': use plugin token; userKey is required for user-scoped APIs.
  • type: 'auto' (default): if userKey is provided and a cached token exists for that user, it is used; otherwise plugin token is used.
  • authMode (optional): only applies when a plugin token is used; controls x-auth-mode for strict read permission checks.

x-auth-mode (Force Read Auth for Plugin Token)

When calling read APIs with plugin_access_token (or virtual_plugin_token), you can set x-auth-mode to force the server to respect the X-User-Key permissions. This header has no effect when a user token is used.

  • authMode: 1 -> strict mode (recommended when you must enforce the user's read permissions)
  • authMode: 0 or omitted -> compatibility mode
await client.workItem.query(
  'project_key',
  'story',
  { work_item_ids: [123456] },
  { auth: { type: 'plugin', userKey: 'user_key', authMode: 1 } }
);

Token Management Notes

  • TokenManager caches plugin_access_token and refreshes it automatically.
  • user_access_token is cached per userKey when you call auth.getUserToken or client.setUserToken.
  • user_access_token will be auto-refreshed per user when possible (requires refresh_token).
  • Use request-level auth for multi-user apps; avoid sharing a client-scoped user token across users.

Timeout & Retry

  • Default timeout: 30s.
  • Retries use exponential backoff with a base delay and are enabled by default.
  • Network errors, timeouts, and retryable API error codes are retried (default: 10429, 50006).
  • Per-request overrides are supported via options.retry, options.skipRetry, and options.timeout.

WBS View (Query + Body)

const wbs = await client.workItem.workflow.getWbsView(
  'project_key',
  'story',
  123456,
  {
    query: { need_union_deliverable: true, need_schedule_table_agg: true },
    body: {
      expand: {
        need_union_deliverable: true,
        need_wbs_relation_chain_entity: true,
        need_wbs_relation_chain_path: true,
      },
    },
  },
  { auth: { type: 'plugin', userKey: 'user_key' } }
);

Universal Search (Field Select)

const request = {
  data_sources: [
    {
      project_key: 'project_key',
      work_item_type_keys: 'story',
    },
  ],
  search_group: {
    conjunction: 'AND',
    search_params: [{ param_key: 'created_at', value: Date.now() - 86400000, operator: '>' }],
  },
  field_selected: ['work_item_id', 'name'],
  pagination: { page_size: 20 },
};

const result = await client.workItem.search.universalSearch(request, {
  auth: { type: 'plugin', userKey: 'user_key' },
});

// Next page
const next = await client.workItem.search.universalSearch(
  {
    ...request,
    pagination: { page_size: 20, search_after: result.pagination.search_after },
  },
  { auth: { type: 'plugin', userKey: 'user_key' } }
);

Services Overview

Top-level services:

  • auth
  • workItem
  • space
  • user
  • config
  • view
  • measure
  • tenant

workItem sub-services:

  • search, batch, comment, subtask, attachment, chat, workflow

Tests

Integration tests use real credentials in tests/config.ts.

bun test

Network access is required.

Unit Tests (Edge Cases)

Unit tests mock network calls and focus on request parsing, retries, and token caching.

bun test tests/unit

Performance Benchmarks (Local)

Quick micro-benchmarks for request parsing and big-int handling (no network).

bun run tests/perf/benchmark.ts