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

document-manager-sdk

v2.0.11

Published

TypeScript SDK for Lakehouse Document Manager – simple, typed, React-ready

Downloads

215

Readme

document-manager-sdk

TypeScript SDK for the Lakehouse Document Manager API. Designed to be simple, fully typed, and React-friendly. All internal API plumbing is hidden – you only call high-level methods.

Installation

npm install document-manager-sdk

Quick Start

import { DocumentManagerSDK } from "document-manager-sdk";

const sdk = new DocumentManagerSDK({
  domain: "https://api-dms.lakehouse.anybim.vn",
});

Configuration

type DocumentManagerConfig = {
  /** Base API URL (no trailing slash). */
  domain: string;
  /** Default bucket name used for all operations. */
  bucketName: string;
  /** Returns the access token (raw string or "Bearer …"). */
  getAccessToken?: () => Promise<string> | string;
  /** Request timeout in milliseconds (default: 30 000). */
  timeout?: number;
  /** Extra headers added to every backend request. */
  defaultHeaders?: Record<string, string>;

  // Auth config – required only when using auth methods
  /** Base URL of the identity server, e.g. "https://identity.anybim.vn". */
  identityDomain?: string;
  /** OAuth client ID. Default: "admin". */
  clientId?: string;
  /** OAuth client secret. Required for login() and refreshTokenWithOrganization(). */
  clientSecret?: string;
};

Authentication

The SDK includes a full authentication module so your frontend never needs to write auth boilerplate.

Setup (with auth)

import { DocumentManagerSDK } from "document-manager-sdk";

const sdk = new DocumentManagerSDK({
  domain:         "https://api-dms.lakehouse.anybim.vn",
  identityDomain: "https://identity.anybim.vn",
  clientId:       "admin",
  clientSecret:   process.env.CLIENT_SECRET, // never hardcode in frontend bundles
});

login(username, password)

Authenticate with the identity server using the OAuth 2.0 Resource Owner Password Credentials grant.

const auth = await sdk.login("[email protected]", "password");
// auth.accessToken, auth.refreshToken, auth.expiresIn, auth.tokenType

getOrganizations(accessToken)

Fetch all organizations the current user belongs to.

const orgs = await sdk.getOrganizations(auth.accessToken);
// orgs[0].guid, orgs[0].companyName, orgs[0].invitationStatus, …

getCurrentClientPreference(accessToken)

Read the user's stored client preferences, including their default organization GUID.

const pref = await sdk.getCurrentClientPreference(auth.accessToken);
// pref.defaultOrganization  – GUID or null

resolveOrganizationId(accessToken)

Automatically determine the best organization GUID for the user:

  1. Read defaultOrganization from preferences.
  2. Fall back to the first organization in the list.
  3. Return "" when no organization is found.
const orgId = await sdk.resolveOrganizationId(auth.accessToken);

refreshTokenWithOrganization({ refreshToken, orgId })

Exchange a refresh token for a new access token scoped to a specific organization.

const orgToken = await sdk.refreshTokenWithOrganization({
  refreshToken: auth.refreshToken,
  orgId,
  remember: true, // optional, default true
});
// orgToken.accessToken is now org-scoped

loginWithOrganization(username, password) ← recommended

High-level convenience method that runs the complete login + org flow in one call:

  1. Login with username/password → initial token.
  2. Fetch the user's organization list.
  3. Resolve the best orgId (default org, or first in list).
  4. Refresh the token with orgId → org-scoped access token.
const session = await sdk.loginWithOrganization("[email protected]", "password");

console.log(session.accessToken);    // org-scoped token, ready to use
console.log(session.orgId);          // resolved organization GUID
console.log(session.organizations);  // full list of user's organizations

Steps B–D are non-fatal — a session is always returned even if org resolution fails.

React example

const [session, setSession] = useState(null);

async function handleLogin(username: string, password: string) {
  const session = await sdk.loginWithOrganization(username, password);
  setSession(session);

  // Configure subsequent SDK calls to use the org-scoped token
  const scopedSdk = new DocumentManagerSDK({
    domain:         "https://api-dms.lakehouse.anybim.vn",
  });

  const result = await scopedSdk.uploadFile(file, {
    companyId: "1",
    projectId: "...",
    orgId:     session.orgId,
  });
}

Auth error types

| Error | When thrown | |---|---| | ValidationError | Missing username/password or config (identityDomain, clientSecret) | | AuthenticationError | Credentials rejected / 401 | | AuthorizationError | 403 from the identity server | | OrganizationError | Organization list response is not a valid array | | NetworkError | Network timeout or connectivity failure |


API Reference

uploadFile(file, options)

Uploads a browser File object. The SDK:

  1. Validates all inputs.
  2. Checks whether the file already exists in the project.
  3. Selects single upload (≤ 5 MB) or multipart upload (> 5 MB) automatically.
  4. Executes the full pipeline internally.
  5. Returns a normalised result.
const result = await sdk.uploadFile(file, {
  companyId: "1",
  projectId: "08dc133e-8168-42b8-8baf-5e5d72a0a00b",
  orgId: "08dc133e-6dd1-49eb-84e3-d98a2eb3a649",
  parentId: "6927bf543c3d0b51d8694a0b",   // optional
  isOrganization: false,                    // optional, default false
  displayName: "My Report.pdf",            // optional, falls back to file.name
});

console.log(result.fileId);     // UUID assigned by the backend
console.log(result.uploadType); // "single" | "multipart"
console.log(result.cloudLink);  // storage path

Return type:

type UploadResult = {
  success: boolean;
  fileId: string;
  documentId?: string;
  fileName: string;
  size: number;
  mimeType: string;
  bucketName: string;
  cloudLink?: string;
  version?: string;
  uploadType: "single" | "multipart";
  raw?: { checkExists; init; confirm; initMultipart; uploadedParts; completeMultipart; };
};

React example

const handleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
  const file = e.target.files?.[0];
  if (!file) return;

  try {
    const result = await sdk.uploadFile(file, {
      companyId: "1",
      projectId: "08dc133e-8168-42b8-8baf-5e5d72a0a00b",
      orgId: "08dc133e-6dd1-49eb-84e3-d98a2eb3a649",
      parentId: "6927bf543c3d0b51d8694a0b",
    });
    console.log("Uploaded:", result);
  } catch (err) {
    if (err instanceof FileAlreadyExistsError) {
      alert(`File already exists (id: ${err.existingFileId})`);
    } else {
      console.error(err);
    }
  }
};

getDocumentInfo(fileId)

Fetches full metadata for a document.

const info = await sdk.getDocumentInfo("eb5e587a-c8ef-4a60-b20f-3640852427dc");

console.log(info.displayName);    // "report.pdf"
console.log(info.owner?.email);   // "[email protected]"
console.log(info.path);           // breadcrumb path array

Return type:

type DocumentInfo = {
  id: string;
  fileId: string;
  name: string;
  displayName: string;
  mimeType: string;
  size: number;
  bucketName: string;
  cloudLink: string;
  version: string;
  parentId?: string;
  owner?: { id: string; name: string; email?: string; icon?: string | null; };
  createDate?: string;
  lastModified?: string;
  workspace?: string;
  path?: Array<{ id: string; name: string }>;
  raw?: unknown;
};

getFilesAndFolders(params)

List files and folders under a parent folder.

Calls GET /Label/files/{parentId}?page=&pageSize=. The SDK attaches the Authorization header, auto-refreshes the token, and normalizes the response into a typed structure that matches the search result shape so UI components can be reused across browse and search views.

const result = await sdk.getFilesAndFolders({
  parentId: "6927bf543c3d0b51d8694a0b",
  page:     1,
  pageSize: 20,
});

for (const item of result.items) {
  console.log(item.type, item.name);   // "folder" | "file"  +  display name
}
console.log(result.pagination.queryCount);  // total items
console.log(result.workspace);              // "Shared with me"

Return type:

type GetFilesAndFoldersResult = {        // alias → SearchFilesAndFoldersResult
  items:       DmsLabelItem[];           // alias → SearchResultItem[]
  pagination:  DmsPagination;            // alias → SearchPagination
  path:        Array<{ id?: string; name?: string }>;
  ownerRootId: string | null;
  workspace?:  string;
  raw?:        unknown;
};

type DmsLabelItem = {
  id: string; name: string; type: string; isDeleted: boolean;
  color?: string; createdDate?: string; lastModified?: string;
  children?:          Array<{ idChildren?: string; type?: string }>;
  createUser?:        DmsLabelUser;
  transferedUser?:    unknown;
  clientId?:          string | null;
  parentId?:          string | null;
  labelCode?:         string;
  isOrganization?:    boolean;
  isRootOrganization?: boolean;
  raw?:               unknown;
};

| Param | Required | Description | |---|---|---| | parentId | yes | MongoDB ObjectId of the parent folder | | page | no | Page number (1-based, default: 1) | | pageSize | no | Items per page (default: 20) |

Error types:

| Error | When thrown | |---|---| | ValidationError | parentId missing or pagination params ≤ 0 | | AuthenticationError | Token missing or rejected | | FileBrowserError | API call failed or response has unexpected structure |


getProjectRootFiles(params)

List files and folders at the root level of a project. The SDK resolves the root folder ID automatically — the caller only provides the projectId.

const result = await sdk.getProjectRootFiles({
  projectId: "08dc133e-8168-42b8-8baf-5e5d72a0a00b",
  page:     1,
  pageSize: 20,
});

console.log(result.items);      // DmsLabelItem[]
console.log(result.workspace);  // workspace string

Internal flow:

  1. Calls GET /Document/getFolderRootId/{projectId} → resolves parentId.
  2. Calls getFilesAndFolders({ parentId, page, pageSize }).

Error types:

| Error | When thrown | |---|---| | ValidationError | projectId missing | | FileBrowserError | Project has no root folder (call resolveUploadParentId() to create one) | | AuthenticationError | Token missing or rejected |


moveFilesToTrash(params) / moveFileToTrash(fileId)

Move one or more files to the trash (soft delete — reversible via restoreFiles).

Calls PATCH /Document/multi/delete with isDeleted: true.

// Batch
const results = await sdk.moveFilesToTrash({
  fileIds: ["f158bcc7-2a43-499e-b99e-98eb01fb6f78"],
});
console.log(results[0].success);   // true
console.log(results[0].isDeleted); // true

// Single-file convenience method
const result = await sdk.moveFileToTrash("f158bcc7-2a43-499e-b99e-98eb01fb6f78");

restoreFiles(params) / restoreFile(fileId)

Restore one or more files from the trash.

Calls PATCH /Document/multi/delete with isDeleted: false.

// Batch
const results = await sdk.restoreFiles({
  fileIds: ["f158bcc7-2a43-499e-b99e-98eb01fb6f78"],
});
console.log(results[0].isDeleted); // false

// Single-file convenience method
const result = await sdk.restoreFile("f158bcc7-2a43-499e-b99e-98eb01fb6f78");

forceDeleteFiles(params) / forceDeleteFile(fileId)

Permanently delete one or more files. This action cannot be undone.

Calls DELETE /Document/multi/force-delete?companyId=<resolved>. The SDK automatically resolves companyId from the current access token — the caller never provides it.

// Batch
const results = await sdk.forceDeleteFiles({
  fileIds: ["0d44ff92-a566-469e-bff8-85ab81c81ea1"],
});

// Single-file convenience method
const result = await sdk.forceDeleteFile("0d44ff92-a566-469e-bff8-85ab81c81ea1");

Return type (all three methods):

type DocumentDeleteResult = {
  success:    boolean;
  message?:   string;
  documentId?: string;   // MongoDB ObjectId of the affected document
  isDeleted?:  boolean;  // true = in trash, false = restored
  raw?:        unknown;
};

Error types

| Error | When thrown | |---|---| | ValidationError | fileIds missing, empty, or contains blank entries | | AuthenticationError | Token missing or rejected | | OrganizationError | companyId could not be resolved (force-delete only) | | TrashOperationError | moveFilesToTrash or restoreFiles API call failed | | ForceDeleteError | forceDeleteFiles API call failed |


searchFilesAndFolders(params)

Search for files and folders within a project by name and/or content.

The SDK automatically:

  1. Validates all inputs.
  2. Maps keywordfileNameKey + contentKey when only keyword is provided.
  3. Refreshes the access token if needed.
  4. Decodes OrgId from the token and resolves the numeric companyId.
  5. Builds the correct query string and calls GET /api/Search/search.
  6. Returns normalized, typed results with the same shape as getFilesAndFolders().

Search by keyword (shortcut)

const result = await sdk.searchFilesAndFolders({
  projectId: "08dc133e-8168-42b8-8baf-5e5d72a0a00b",
  keyword: "té",
});

console.log(result.items);       // SearchResultItem[]
console.log(result.pagination);  // { page, pageCount, pageSize, queryCount, ... }
console.log(result.workspace);   // "Shared with me"

keyword is a shortcut — it sets both fileNameKey and contentKey to the same value. The SDK builds the query fileNameKey=té&contentKey=té automatically.

Search by separate keys

const result = await sdk.searchFilesAndFolders({
  projectId:   "08dc133e-8168-42b8-8baf-5e5d72a0a00b",
  fileNameKey: "Converter",   // search by name
  contentKey:  "design",      // search by content
  page:     1,
  pageSize: 20,
});

Return type

type SearchFilesAndFoldersResult = {
  items: SearchResultItem[];
  pagination: {
    page:          number;
    pageCount:     number;
    pageSize:      number;
    queryCount:    number;
    firstRowIndex: number;
    lastRowIndex:  number;
  };
  path:        Array<{ id?: string; name?: string }>;
  ownerRootId: string | null;
  workspace?:  string;
  raw?:        unknown;
};

type SearchResultItem = {
  id:                 string;
  name:               string;
  type:               string;   // "file" | "folder"
  isDeleted:          boolean;
  color?:             string;
  createdDate?:       string;
  lastModified?:      string;
  children?:          Array<{ idChildren?: string; type?: string }>;
  createUser?:        { id: string; name: string; email?: string; icon?: string | null };
  transferedUser?:    unknown;
  clientId?:          string | null;
  parentId?:          string | null;
  labelCode?:         string;
  isOrganization?:    boolean;
  isRootOrganization?: boolean;
  raw?:               unknown;
};

Input parameters

| Param | Required | Description | |---|---|---| | projectId | yes | Project GUID to search within | | keyword | no | Shortcut — sets both fileNameKey and contentKey | | fileNameKey | no | Search by file / folder name | | contentKey | no | Search by document content | | page | no | Page number (1-based, default: 1) | | pageSize | no | Items per page (default: 20) |

At least one of keyword, fileNameKey, or contentKey is required.

Error types

| Error | When thrown | |---|---| | ValidationError | projectId missing, no search key, or page/pageSize ≤ 0 | | AuthenticationError | Token missing or could not be refreshed | | FileSearchError | companyId could not be resolved, or the API returned an error / unexpected structure |

React search box example

const [keyword, setKeyword]     = useState("");
const [results, setResults]     = useState<SearchFilesAndFoldersResult | null>(null);

async function handleSearch() {
  const result = await sdk.searchFilesAndFolders({
    projectId: "08dc133e-8168-42b8-8baf-5e5d72a0a00b",
    keyword,
  });
  setResults(result);
}

Error Handling

Import specific error classes to handle different failure modes:

import {
  FileAlreadyExistsError,
  ValidationError,
  AuthenticationError,
  AuthorizationError,
  NotFoundError,
  SingleUploadError,
  MultipartUploadError,
  UploadConfirmError,
  DocumentInfoError,
  NetworkError,
} from "document-manager-sdk";

| Error class | When thrown | |---|---| | ValidationError | Missing or invalid input parameters | | FileAlreadyExistsError | File already exists in the project (check before upload) | | AuthenticationError | Token invalid / 401 from backend | | AuthorizationError | Insufficient permissions / 403 | | NotFoundError | Resource not found / 404 | | OrganizationError | Organization list response is not a valid array | | SingleUploadError | Single upload init or binary PUT failed | | MultipartUploadError | Any multipart step failed | | UploadConfirmError | Confirm metadata API call failed | | DocumentInfoError | getDocumentInfo API call failed | | FileSearchError | searchFilesAndFolders API call failed or response has unexpected structure | | FileBrowserError | getFilesAndFolders or getProjectRootFiles failed | | TrashOperationError | moveFilesToTrash or restoreFiles API call failed | | ForceDeleteError | forceDeleteFiles permanent delete failed | | NetworkError | Network timeout or connectivity error |

All errors extend SDKError which provides .statusCode, .details, and .message.


Internal Architecture

DocumentManagerSDK                   ← public facade
├── HttpClient                       ← fetch wrapper (auth, retry, timeout, error mapping)
├── UploadService                    ← orchestrates the full upload pipeline
│   ├── SingleUploadService          ← init → PUT binary → confirm (≤ 5 MB)
│   └── MultipartUploadService       ← init → slice → part URLs → PUT parts → complete → confirm (> 5 MB)
├── DocumentService                  ← getDocumentInfo and future document operations
├── FileBrowserService               ← getFilesAndFolders / searchFilesAndFolders (shared parse logic)
├── TrashService                     ← moveFilesToTrash / restoreFiles / forceDeleteFiles
└── AuthService                      ← identity server integration
    ├── login                        ← ROPC grant
    ├── getCurrentClientPreference   ← read defaultOrganization
    ├── getOrganizations             ← fetch org list
    ├── resolveOrganizationId        ← defaultOrg → first org → ""
    ├── refreshTokenWithOrganization ← refresh_token grant + orgId
    └── loginWithOrganization        ← full flow (A→B→C→D)

Users of the SDK never interact with any of these internal classes.

Upload pipeline (single, ≤ 5 MB)

  1. GET /api/Upload/check-db – check file existence
  2. POST /api/Upload/single/init – obtain presigned PUT URL
  3. PUT <presigned-url> – upload binary (no auth header)
  4. POST /api/Upload/confirm – register document in DB

Upload pipeline (multipart, > 5 MB)

  1. GET /api/Upload/check-db – check file existence
  2. POST /api/upload/multipart/init – start multipart session
  3. File is sliced into 5 MB chunks
  4. For each part: GET /api/upload/multipart/presigned-urlPUT <presigned-url>
  5. POST /api/upload/multipart/complete – assemble parts
  6. POST /api/Upload/confirm – register document in DB

Development

npm install
npm run build           # build ESM + CJS + .d.ts into dist/
npm run build:check     # type-check without emitting
npm run clean           # delete dist/
npm test                # run Jest (52 tests)
npm run test:coverage   # with coverage report

Build output

dist/
├── index.js        ← CommonJS bundle
├── index.js.map    ← source map
├── index.mjs       ← ESM bundle
├── index.mjs.map   ← source map
├── index.d.ts      ← TypeScript declarations (CJS)
└── index.d.mts     ← TypeScript declarations (ESM)

The build is powered by tsup (esbuild under the hood) – fast and zero-config.


Publishing to npm

First-time setup

# 1. Create an npm account at https://www.npmjs.com
# 2. Login from the terminal
npm login

# 3. (Optional) If publishing as a scoped package, update name in package.json:
#    "@your-org/document-manager-sdk"

Change the package name

Edit package.json"name" field before the first publish:

{
  "name": "@your-org/document-manager-sdk"
}

Publish (manual)

# Runs: lint → test → build → publish in sequence
npm publish

prepublishOnly is configured to automatically run all checks before each publish.

Version bump + publish (one command)

npm run release:patch   # 2.0.0 → 2.0.1
npm run release:minor   # 2.0.0 → 2.1.0
npm run release:major   # 2.0.0 → 3.0.0

Each command runs npm version <bump> (creates a git tag + updates package.json) then npm publish.

Dry run (inspect what will be published)

npm pack --dry-run

Expected output: only dist/, README.md, CHANGELOG.md, LICENSE.

Verify the published package

After publishing, test the install:

mkdir /tmp/sdk-test && cd /tmp/sdk-test
npm init -y
npm install document-manager-sdk
node -e "const s = require('document-manager-sdk'); console.log(Object.keys(s))"

Requirements

  • Node.js ≥ 18 (for native fetch)
  • TypeScript ≥ 5