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

@sdkwork/im-sdk

v0.1.1

Published

Unified IM SDK package with embedded OpenAPI-generated transport and handwritten realtime/business modules

Readme

SDKWork IM TypeScript SDK

Official package: @sdkwork/im-sdk

This package is the single consumer-facing TypeScript SDK in the IM SDK family. It targets browser and Node.js and follows one package rule:

  • semantic SDK surface lives at the package root
  • generated OpenAPI transport is assembled under src/generated/** inside the same package
  • application code should start from ImSdkClient
  • route-aligned transport control stays available on the same client through sdk.session, sdk.presence, sdk.realtime, sdk.device, sdk.inbox, and sdk.stream

Authoring boundaries inside this workspace remain explicit:

  • generated/server-openapi Generator-owned transport authoring output before root-package assembly. This workspace carries the private internal package identity @sdkwork-internal/im-sdk-generated and is not a second public consumer package.
  • composed Manual-owned semantic authoring layer used before the single-package root is assembled.
  • root package The published @sdkwork/im-sdk consumer package that assembles generated transport and handwritten runtime modules together.

Install

npm install @sdkwork/im-sdk
pnpm add @sdkwork/im-sdk

Quick Start

Browser

import { ImSdkClient } from '@sdkwork/im-sdk';

const sdk = new ImSdkClient({
  baseUrl: import.meta.env.VITE_CRAW_CHAT_BASE_URL,
  authToken: window.localStorage.getItem('craw-chat-token') ?? undefined,
});

Node.js

import { ImSdkClient } from '@sdkwork/im-sdk';

const sdk = new ImSdkClient({
  baseUrl: process.env.CRAW_CHAT_BASE_URL!,
  authToken: process.env.CRAW_CHAT_TOKEN,
});

When HTTP API traffic and realtime WebSocket traffic do not share the same origin:

const sdk = new ImSdkClient({
  apiBaseUrl: 'https://api.example.com',
  websocketBaseUrl: 'wss://realtime.example.com',
  authToken: process.env.CRAW_CHAT_TOKEN,
});

The constructor is synchronous. Network traffic starts only when you call an explicit method such as sdk.auth.login(), sdk.send(), sdk.connect(), or sdk.sync.catchUp().

Chat CLI

The TypeScript workspace ships a standard chat smoke CLI for SDK integration testing. It stays on top of ImSdkClient and does not bypass the SDK with handwritten transport calls.

Workspace entrypoints:

node ./bin/sdk-chat.mjs --help
./bin/sdk-chat.sh --help
.\bin\sdk-chat.ps1 --help

Root workspace standard entrypoints:

node ../bin/sdk-chat.mjs --language typescript --help
../bin/sdk-chat.sh --language typescript --help
powershell -ExecutionPolicy Bypass -File ..\bin\sdk-chat.ps1 -Language typescript --help

The local sdk-chat.ps1 and sdk-chat.sh forward back to the root workspace dispatcher so every language workspace converges on the same sdk-chat standard as more language implementations are added.

Published package entrypoint after install:

npx sdkwork-im-chat --help

Standard environment variables:

export SDKWORK_IM_BASE_URL="https://chat.example.test"
export SDKWORK_IM_WEBSOCKET_BASE_URL="wss://chat.example.test"
export SDKWORK_IM_TENANT_ID="tenant-demo"
export SDKWORK_IM_LOGIN="ops-demo"
export SDKWORK_IM_PASSWORD="secret-demo"
export SDKWORK_IM_CONVERSATION_ID="conversation-demo"

Login-first interactive chat:

node ./bin/sdk-chat.mjs \
  --base-url https://chat.example.test \
  --tenant-id tenant-demo \
  --login ops-demo \
  --password secret-demo \
  --conversation-id conversation-demo

Token-first interactive chat:

node ./bin/sdk-chat.mjs \
  --base-url https://chat.example.test \
  --token "<access-token>" \
  --conversation-id conversation-demo

Behavior:

  • receive-mode=auto prefers realtime live receive when Node.js ws is available
  • receive-mode=auto falls back to durable catch-up when live receive is unavailable
  • /catchup triggers one immediate catch-up batch
  • /help prints CLI help
  • /exit or /quit closes the session

Authentication

Authentication is exposed through sdk.auth.

const login = await sdk.auth.login({
  tenantId: 'tenant-acme',
  login: 'ops_lead',
  password: '***',
  clientKind: 'portal_operator',
});

await sdk.auth.me();

sdk.auth.useToken(login.accessToken);
sdk.auth.clearToken();

Behavior:

  • sdk.auth.login() automatically applies accessToken when the service returns one
  • sdk.auth.useToken(token) updates the underlying generated client
  • sdk.auth.clearToken() clears the bearer token used by HTTP and realtime helpers

Portal

Portal snapshot reads are exposed directly on sdk.portal.

const home = await sdk.portal.getHome();
const workspace = await sdk.portal.getWorkspace();
const dashboard = await sdk.portal.getDashboard();

console.log(home.hero?.title, workspace.name, dashboard.hero?.title);

Available portal helpers:

  • sdk.portal.getHome()
  • sdk.portal.getAuth()
  • sdk.portal.getWorkspace()
  • sdk.portal.getDashboard()
  • sdk.portal.getConversations()
  • sdk.portal.getRealtime()
  • sdk.portal.getMedia()
  • sdk.portal.getAutomation()
  • sdk.portal.getGovernance()

Conversations

Use sdk.conversations when you want route-aligned access for conversation lifecycle, handoff, membership, read-state, and direct message posting.

| Task | Method | | --- | --- | | Create a conversation | sdk.conversations.create(...) | | Create a one-to-one agent dialog | sdk.conversations.createAgentDialog(...) | | Create a handoff conversation | sdk.conversations.createAgentHandoff(...) | | Create a system channel | sdk.conversations.createSystemChannel(...) | | Read the conversation summary | sdk.conversations.get(...) | | Read handoff state | sdk.conversations.getAgentHandoffState(...) | | Accept / resolve / close handoff | sdk.conversations.acceptAgentHandoff(...), sdk.conversations.resolveAgentHandoff(...), sdk.conversations.closeAgentHandoff(...) | | Manage members | sdk.conversations.listMembers(...), sdk.conversations.addMember(...), sdk.conversations.removeMember(...), sdk.conversations.transferOwner(...), sdk.conversations.changeMemberRole(...), sdk.conversations.leave(...) | | Manage read state | sdk.conversations.getReadCursor(...), sdk.conversations.updateReadCursor(...) | | Read and post route-level messages | sdk.conversations.listMessages(...), sdk.conversations.postMessage(...), sdk.conversations.postText(...), sdk.conversations.publishSystemMessage(...), sdk.conversations.publishSystemText(...) |

const conversation = await sdk.conversations.create({
  conversationId: 'conversation-order-1',
  conversationType: 'group',
});

await sdk.conversations.postText(conversation.conversationId, 'hello world', {
  summary: 'Greeting',
});

await sdk.conversations.publishSystemText(
  conversation.conversationId,
  'Maintenance starts in 5 minutes',
  {
    summary: 'Maintenance window',
  },
);

const inbox = await sdk.inbox.getInbox();
console.log(conversation.conversationId, inbox.items.length);

Messages

The outbound model is message-first at the client root:

  1. create a message with sdk.createXxxMessage(...)
  2. send it with sdk.send(message)

The same builders remain available on sdk.messages when you want a namespaced module surface. The primary message entrypoints are sdk.createTextMessage(...), sdk.send(...), sdk.upload(...), sdk.uploadAndSendMessage(...), and sdk.decodeMessage(...).

Text

const message = sdk.createTextMessage({
  conversationId: 'conversation-1',
  text: 'hello world',
  summary: 'Greeting',
  renderHints: { tone: 'friendly' },
});

await sdk.send(message);

Media

const image = sdk.createImageMessage({
  conversationId: 'conversation-1',
  mediaAssetId: 'asset-image-1',
  text: 'Latest storefront concept',
  summary: 'Storefront concept',
});

await sdk.send(image);

Upload And Send

const uploaded = await sdk.uploadAndSendMessage({
  upload: {
    mediaAssetId: 'asset-image-1',
    bucket: 'tenant-media',
    objectKey: 'conversation-1/storefront.png',
    resource: {
      type: 'image',
      name: 'storefront.png',
      mimeType: 'image/png',
      size: file.size,
    },
    body: file,
  },
  createMessage: (preparedUpload) =>
    sdk.createImageMessage({
      conversationId: 'conversation-1',
      mediaAssetId: preparedUpload.mediaAssetId,
      text: 'Uploaded storefront image',
      summary: 'Storefront image',
    }),
});

console.log(uploaded.mediaAssetId, uploaded.url, uploaded.delivery.messageId);

When you only need the presigned upload flow without sending a message immediately, use sdk.upload(...). It returns a ImUploadedMediaAsset with the completed asset metadata, upload session details, and the resolved CDN or download URL when the service provides one.

const uploadedAsset = await sdk.upload({
  mediaAssetId: 'asset-image-2',
  bucket: 'tenant-media',
  objectKey: 'conversation-1/manual-upload.png',
  resource: {
    type: 'image',
    name: 'manual-upload.png',
    mimeType: 'image/png',
    size: file.size,
  },
  body: file,
});

console.log(
  uploadedAsset.mediaAssetId,
  uploadedAsset.asset.status,
  uploadedAsset.session.uploadUrl,
);

Rich Standard Messages

The semantic layer includes common IM and AI-era message families:

  • createLocationMessage
  • createLinkMessage
  • createCardMessage
  • createMusicMessage
  • createContactMessage
  • createStickerMessage
  • createVoiceMessage
  • createCustomMessage
  • createAiTextMessage
  • createAiImageGenerationMessage
  • createAiVideoGenerationMessage
  • createAgentMessage
  • createAgentStateMessage
  • createAgentHandoffMessage
  • createToolResultMessage
  • createWorkflowEventMessage
  • createDataMessage
  • createSignalMessage
  • createStreamReferenceMessage

Examples:

const location = sdk.createLocationMessage({
  conversationId: 'conversation-1',
  text: 'Meet me here',
  latitude: 31.2304,
  longitude: 121.4737,
  name: 'The Bund',
  address: 'Shanghai',
});

const aiText = sdk.createAiTextMessage({
  conversationId: 'conversation-1',
  text: 'Assistant answer',
  prompt: 'summarize the last order',
  model: 'gpt-5.4',
  status: 'completed',
});

const aiImage = sdk.createAiImageGenerationMessage({
  conversationId: 'conversation-1',
  text: 'Generated hero illustration',
  prompt: 'cinematic storefront at sunrise',
  model: 'gpt-image-1',
  mediaAssetId: 'asset-generated-hero-1',
  resource: {
    type: 'image',
    name: 'storefront-hero.png',
    mimeType: 'image/png',
    url: 'https://cdn.example.com/generated/storefront-hero.png',
  },
  status: 'completed',
});

const agent = sdk.createAgentMessage({
  conversationId: 'conversation-1',
  text: 'Primary support agent joined',
  agentId: 'assistant-1',
  agentName: 'Assistant',
  stage: 'active',
  status: 'online',
  capabilities: ['summarize', 'route'],
});

const handoff = sdk.createAgentHandoffMessage({
  conversationId: 'conversation-1',
  text: 'Escalating to billing specialist',
  fromAgentId: 'router',
  toAgentId: 'billing-specialist',
  reason: 'invoice_exception',
  status: 'pending',
});

await sdk.send(location);
await sdk.send(aiText);
await sdk.send(aiImage);
await sdk.send(agent);
await sdk.send(handoff);

System Channel

Set channel: 'system' to publish into the system channel. sdk.send(...) will route to the correct HTTP endpoint automatically.

const notice = sdk.createTextMessage({
  conversationId: 'conversation-system-1',
  channel: 'system',
  text: 'Deployment starts in 5 minutes',
  summary: 'Maintenance window',
});

await sdk.send(notice);

Decode

If you already own a raw MessageBody or PostMessageRequest, decode it through the semantic message codec:

const decoded = sdk.decodeMessage(aiText.body);
console.log(decoded.type, decoded.summary);

Media

Use sdk.media for explicit upload lifecycle control.

  • sdk.media.createUpload(...)
  • sdk.media.createUploadSession(...)
  • sdk.media.upload(...)
  • sdk.media.completeUpload(...)
  • sdk.media.uploadAndComplete(...)
  • sdk.media.getDownloadUrl(...)
  • sdk.media.get(...)
  • sdk.media.attach(...)
  • sdk.media.attachText(...)
const prepared = await sdk.media.uploadAndComplete({
  mediaAssetId: 'asset-file-1',
  bucket: 'tenant-media',
  objectKey: 'conversation-1/brief.pdf',
  resource: {
    type: 'file',
    name: 'brief.pdf',
    mimeType: 'application/pdf',
    size: file.size,
  },
  body: file,
});

await sdk.media.attachText(prepared.mediaAssetId, {
  conversationId: 'conversation-1',
  text: 'Uploaded project brief',
  summary: 'Project brief',
});

sdk.upload(...) is the preferred root helper for standard application uploads. sdk.media.upload and sdk.media.uploadAndComplete(...) expose the same presigned S3-style client upload flow from the namespaced media module. Use sdk.media.createUploadSession(...) plus sdk.media.completeUpload(...) only when you need to control the upload transaction manually.

Realtime

The SDK exposes two receive models on purpose:

  • sdk.connect(...) Live push over WebSocket
  • sdk.sync.catchUp(...) Durable HTTP catch-up and replay window acknowledgement

Live Push

const live = await sdk.connect({
  deviceId: 'web-chrome-01',
  subscriptions: {
    conversations: ['conversation-1'],
    rtcSessions: ['rtc-1'],
  },
});

live.messages.on((message, context) => {
  console.log(message.type, message.summary, context.sequence);
  void context.ack();
});

live.messages.onConversation('conversation-1', (message, context) => {
  console.log(context.conversationId, message.type);
});

live.data.on((data, context) => {
  console.log(data.schemaRef, data.payload, context.sequence);
});

live.signals.on((signal, context) => {
  console.log(signal.signalType, signal.payload, context.scopeId);
});

live.signals.onRtcSession('rtc-1', (signal, context) => {
  console.log(signal.signalType, context.scopeId);
});

live.events.on((context) => {
  console.log(context.kind, context.sequence, context.source);
});

live.lifecycle.onStateChange((state) => {
  console.log(state.status);
});

live.lifecycle.onError((context) => {
  console.log(context.code, context.error);
});

console.log(live.lifecycle.getState().status);

The recommended live callback shape is payload-first by domain stream:

  • message, data, or signal as the first callback argument
  • context as the second callback argument
  • context.sequence
  • context.receivedAt
  • context.sender
  • context.source
  • context.rawEvent
  • context.ack()

The live runtime surface is:

  • live.messages.on(...) for the primary inbound message stream
  • live.messages.onConversation(...) for conversation-scoped inbound message handling
  • live.data.on(...) for non-message structured data delivery
  • live.signals.on(...) for generic signaling delivery
  • live.signals.onRtcSession(...) for RTC-session-scoped signaling
  • live.events.on(...) for the normalized receive context before your app-specific routing
  • live.lifecycle.onStateChange(...) for connected, error, and closed transitions
  • live.lifecycle.onError(...) for realtime protocol and socket-level failures
  • live.lifecycle.getState() for the latest connection snapshot

Durable Catch-Up

const batch = await sdk.sync.catchUp({ limit: 50 });

for (const item of batch.items) {
  if (item.kind === 'message') {
    console.log(item.sequence, item.message.type, item.message.summary);
    await item.ack();
  }
}

await sdk.sync.ack(batch);

Use sdk.sync when your app needs resume, replay, background processing, or explicit ACK control. context.ack() acknowledges through the current receive context, while sdk.sync.ack(...) lets you commit the highest durable replay position explicitly.

Browser And Node WebSocket Factories

If the realtime gateway requires a custom upgrade strategy, provide webSocketFactory.

Important: the default global WebSocket constructor cannot attach Authorization headers. In plain browser environments, authenticated realtime should use a browser-safe gateway strategy:

  • ImWebSocketAuthOptions.automatic() is the standard default
  • automatic auth resolves to query bearer for the default browser WebSocket path
  • automatic auth resolves to header bearer when you provide webSocketFactory
  • prefer exchanging the main access token for a short-lived realtime ticket or query credential
  • pass that credential through a gateway-owned wss:// URL or cookie-based upgrade path
  • use connect({ url }) when the browser runtime must join through a fully pre-signed realtime URL

Node.js and custom runtimes can provide header-based upgrades through webSocketFactory.

import { ImSdkClient, ImWebSocketAuthOptions } from '@sdkwork/im-sdk';

const sdk = new ImSdkClient({
  baseUrl: 'https://api.example.com',
  authToken: window.localStorage.getItem('craw-chat-token') ?? undefined,
  webSocketAuth: ImWebSocketAuthOptions.queryBearer({
    queryParameterName: 'rt',
    credentialProvider: async ({ authToken }) =>
      issueRealtimeTicket(authToken),
  }),
});

const live = await sdk.connect({
  subscriptions: {
    conversations: ['conversation-1'],
  },
});
const realtimeTicket = await issueRealtimeTicket();

const live = await sdk.connect({
  url: `wss://realtime.example.com/api/v1/realtime/ws?rt=${encodeURIComponent(realtimeTicket)}`,
  subscriptions: {
    conversations: ['conversation-1'],
  },
});
import WebSocket from 'ws';

const sdk = new ImSdkClient({
  baseUrl: process.env.CRAW_CHAT_BASE_URL!,
  authToken: process.env.CRAW_CHAT_TOKEN,
  webSocketAuth: ImWebSocketAuthOptions.headerBearer(),
  webSocketFactory: ({ url, protocols, headers }) =>
    new WebSocket(url, protocols, { headers }),
});

RTC

RTC lifecycle operations are available from sdk.rtc.

const session = await sdk.rtc.create({
  rtcSessionId: 'rtc-1',
  conversationId: 'conversation-1',
  rtcMode: 'group_call',
});

await sdk.rtc.invite(session.rtcSessionId, {
  signalingStreamId: 'rtc-signal-1',
});

await sdk.rtc.postJsonSignal(session.rtcSessionId, 'offer', {
  signalingStreamId: 'rtc-signal-1',
  payload: {
    sdp: 'v=0...',
  },
});

const credential = await sdk.rtc.issueParticipantCredential(session.rtcSessionId, {
  participantId: 'user-1',
});

const recording = await sdk.rtc.getRecordingArtifact(session.rtcSessionId);

live.signals.onRtcSession(session.rtcSessionId, (signal, context) => {
  console.log(signal.signalType, signal.payload, context.scopeId);
});

Realtime RTC signals are received through live.signals.onRtcSession(...). Use sdk.rtc.postJsonSignal(...) for common JSON signaling, sdk.rtc.issueParticipantCredential(...) for provider join credentials, and sdk.rtc.getRecordingArtifact(...) for recording metadata.

Route-Aligned Transport Modules

Some route groups stay exposed directly on ImSdkClient because they already match the public OpenAPI contract cleanly:

const sdk = new ImSdkClient({
  baseUrl: 'http://127.0.0.1:18090',
  authToken: 'token',
});

await sdk.session.resume({ deviceId: 'web-chrome-01' });
await sdk.presence.getPresenceMe();
await sdk.realtime.listRealtimeEvents({ limit: 20 });
await sdk.device.register({ deviceId: 'web-chrome-01' });
await sdk.inbox.getInbox();
await sdk.stream.open({
  streamId: 'stream-demo-1',
  streamType: 'custom.delta.text',
  scopeKind: 'conversation',
  scopeId: 'conversation-1',
  durabilityClass: 'durableSession',
  schemaRef: 'custom.delta.text.v1',
});

Guidance:

  • start from ImSdkClient for application code
  • use the root transport modules when you need exact route-group control such as sdk.session.resume(...), sdk.presence.getPresenceMe(), sdk.realtime.listRealtimeEvents(...), sdk.device.register(...), sdk.inbox.getInbox(), and sdk.stream.open(...)
  • keep generated transport authoring inside generated/server-openapi and src/generated/**

Verification

Local workspace checks:

node ./bin/package-task.mjs typecheck
node ./bin/package-task.mjs build
node ../bin/verify-typescript-live-contract.mjs --package root
node ./bin/package-task.mjs smoke
node ../bin/verify-typescript-single-package-publishability.mjs
node ../bin/verify-typescript-single-package-install-safety.mjs

From the SDK workspace root, the default cross-language verification entrypoint is sdk-verify via ./bin/verify-sdk.sh or .\bin\verify-sdk.ps1.

Release-oriented verification also checks npm pack --dry-run, verify-typescript-live-contract.mjs, verify-typescript-single-package-install-safety.mjs, verify-typescript-generated-build-determinism.mjs, runtime root exports, and cleanup of dead auth scaffolding plus stray src/index.js and src/index.d.ts residue before the TypeScript package is considered publishable. On Windows hosts, release-oriented verification also includes verify-typescript-generated-build-concurrency.mjs and verify-typescript-workspace-concurrency.mjs so both stable generated-package builds and full workspace verifications stay race-free under overlapping runs.

Release Snapshot Boundary

This workspace inherits the current SDK release snapshot from artifacts/releases/wave-d-2026-04-08/sdk-release-catalog.json.

  • state = generated_pending_publication
  • generationStatus = generated
  • releaseStatus = not_published
  • plannedVersion = null
  • versionStatus = version_unassigned_pending_freeze
  • versionDecisionSourcePath = null