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

@incorta/sdk

v1.10.0

Published

Read Incorta schemas, tables, views, and columns on behalf of the signed-in user — OAuth 2.0 sessions from @incorta/auth, no personal access tokens.

Readme

@incorta/sdk

npm version license

Read Incorta schemas, tables, views, and columns from Node.js, as the signed-in user. Sessions come from @incorta/auth — OAuth 2.0 against the authorization server built into Incorta — so this client has no identity of its own and no personal access token to store.

The Python twin is incorta-sdk; the two speak the same API, model the same distinctions, and share the same INCORTA_* configuration.

npm install @incorta/sdk

@incorta/auth comes with it as a dependency, pinned to the same version — the two are always released together, so @incorta/[email protected] runs on @incorta/[email protected].

Why it is scoped to a user

Every call carries a user's own Incorta access token, so Incorta filters the results: two people hitting the same endpoint of your app see two different catalogs. That is a property of the design, not a setting — there is no app-level identity to over-share from, and nothing to revoke separately when someone leaves.

It also means the scoped client, not the top-level one, is what you hold:

IncortaClient            configuration + OAuth  (build once, at startup)
  └── forRequest()  →  IncortaUserClient        (build per request)
        ├── schemas
        └── tables

Configuration

createIncortaClient() takes every @incorta/auth option, each falling back to its environment variable:

| Option | Environment variable | Meaning | | --- | --- | --- | | incortaUrl | INCORTA_URL | Environment root including any context path (often /incorta), without /api/v2 | | tenant | INCORTA_TENANT | Tenant name, e.g. default | | clientId | INCORTA_CLIENT_ID | OAuth client id (see incorta-auth register) | | clientSecret | INCORTA_CLIENT_SECRET | OAuth client secret | | secret | INCORTA_AUTH_SECRET | Session-cookie encryption key (≥ 32 chars) | | internalIncortaUrl | INCORTA_INTERNAL_URL | Optional split-horizon address for server-to-server calls |

Plus two of its own: timeoutMs (default 30000) and maxRetries (default 3, for 429/5xx and network errors only — client errors are never retried).

When the app already builds an IncortaAuth — the usual case, since it needs one to serve logins — pass it in rather than letting this package create a second:

import { createIncortaAuth } from "@incorta/auth";
import { createIncortaClient } from "@incorta/sdk";

const auth = createIncortaAuth({ appAccess: "catalog" });
const client = createIncortaClient({ auth });

Usage

Express

import express from "express";
import { incortaAuth, requireAuth } from "@incorta/auth/express";
import { createIncortaClient } from "@incorta/sdk";

const client = createIncortaClient();          // once, at startup
const app = express();

// The client owns an auth instance — this serves /auth/* and attaches the
// session to req.incortaAuth.
app.use(incortaAuth(client.auth));

app.get("/api/schemas", requireAuth(client.auth), async (req, res) => {
  const incorta = client.forSession(req.incortaAuth!);   // per request, as this user
  res.json(await incorta.schemas.physical());
});

app.get("/api/tables/:schema/:table", requireAuth(client.auth), async (req, res) => {
  const incorta = client.forSession(req.incortaAuth!);
  const object = await incorta.tables.get(req.params.schema, req.params.table);
  res.json({
    name: object.qualifiedName,
    columns: object.columns.map((c) => ({ name: c.name, type: c.dataType })),
  });
});

TanStack Start / anything with web-standard Request

export const ServerRoute = createServerFileRoute().methods({
  GET: async ({ request }) => {
    const incorta = await client.forRequest(request);
    return Response.json(await incorta.schemas.list());
  },
});

Given a session you already read (from a gate or a middleware), use client.forSession(session) instead.

Surface

createIncortaClient(config) → IncortaClient

| Member | Purpose | | --- | --- | | auth | The underlying IncortaAuth — mount auth.handler / auth.gate | | forRequest(request, opts?) | Scoped client for the user behind a Request | | forSession(session, opts?) | Scoped client from a session you already read | | forAccessToken(token, opts?) | Scoped client from a bare access token | | info | baseUrl, tenant, timeoutMs, maxRetries — no secrets |

IncortaUserClient.schemas

| Method | Returns | | --- | --- | | list({ type, limit, offset, sortBy }) | SchemaInfo[] | | listPage(...) | Page<SchemaInfo> — adds the server-side total | | iterAll({ type, pageSize }) | AsyncGenerator, one page fetched at a time | | physical() / business() | Shorthands for the type filter | | get(name) | PhysicalSchema \| BusinessSchema, with contents | | exists(name) | boolean |

IncortaUserClient.tables

| Method | Returns | | --- | --- | | get(schema, name) | Table \| View, columns populated | | list(schema) / names(schema) | Every object, or just their names | | columns(schema, name) | Column[] | | tablesOnly(schema) / viewsOnly(schema) | Filtered by kind | | exists(schema, name) | boolean |

Names are matched case-insensitively.

IncortaUserClient.data

Reads rows out of business views. Fields are addressed by their fully qualified name, SCHEMA.VIEW.COLUMN.

| Method | Returns | | --- | --- | | query(measures, options?) | Promise<QueryResult> | | iterRows(measures, options?) | Async generator of rows, one page at a time | | csv(measures, options?) | Promise<string> — the CSV Incorta rendered | | raw(body) | The decoded response for a body sent verbatim | | buildBody(measures, options?) | The request body, without sending it |

const result = await incorta.data.query(
  [{ field: "HR_BS.Employee_BS.SALARY", aggregation: "sum", label: "payroll" }],
  {
    rows: ["HR_BS.Employee_BS.JOB_TITLE"],
    aggregate: true,
    filters: [
      {
        type: "fieldKey",
        fieldKey: "HR_BS.Employee_BS.JOB_TITLE",
        op: "IN_LIST",
        values: ["Accountant"],
      },
    ],
    sorting: [{ field: "HR_BS.Employee_BS.JOB_TITLE", dir: "desc" }],
  },
);
result.headers;     // ["JOB_TITLE", "payroll"]
result.records();   // [{ JOB_TITLE: "Accountant", payroll: "39600.0" }]
result.totalRows;   // rows matching beyond this page

A bare string is shorthand for { field } in both measures and the dimension lists. Cells always come back as strings — Incorta renders every value as text.

aggregate: false gives a flat extract; aggregate: true folds each measure with its aggregation and groups by rows and columns.

Models

Plain data — no classes — so results survive structuredClone, JSON.stringify, and a trip through a worker or an HTTP response. Where Python uses isinstance, these carry a kind discriminant that narrows in TypeScript and survives serialisation:

const schema = await incorta.schemas.get("OnlineStore");
if (schema.kind === "physical") {
  for (const table of schema.tables) console.log(table.rowsCount);
} else {
  for (const view of schema.views) console.log(view.sources);
}

Every model keeps the untouched API record in .raw, so a field this package does not model is still reachable.

Errors

Everything derives from IncortaError:

IncortaError
├── IncortaConfigError            a setting is missing or malformed
├── IncortaAuthRequiredError      no signed-in user on this request
├── IncortaSessionExpiredError    the captured token aged out (thrown locally)
├── IncortaConnectionError        environment unreachable
│   └── IncortaTimeoutError
├── IncortaApiError               non-2xx, carrying .statusCode and .code
│   ├── AuthenticationError       401 — Incorta refused the token
│   ├── PermissionDeniedError     403 — this user lacks access
│   ├── NotFoundError             404
│   │   └── SchemaNotFoundError
│   └── IncortaServerError        5xx
└── TableNotFoundError            detected client-side, lists what does exist

Token lifetime

forRequest refreshes the access token as it reads the session, so a client built per request always starts fresh. A scoped client held past its token's expiry throws IncortaSessionExpiredError before making a request, rather than letting Incorta answer 401 — build one per request and the case never arises.

Behaviour both SDKs share

These are the API quirks the SDKs exist to absorb, handled identically in TypeScript and Python.

  • schemaType fails silently. ?schemaType=TYPO returns HTTP 200 with business schemas, and so does omitting the parameter. A typo would hand you plausible but wrong data, so both clients validate the value locally and always send it explicitly.
  • Physical and business schemas return disjoint keys. A physical schema carries tablesDetails; a business schema carries viewsDetails. The other key is absent entirely, not empty. Both clients return a different type for each rather than one half-null shape.
  • The API misspells its own value as BUSSINESS_VIEW (three S's). Both clients round-trip that spelling and accept the corrected one, so nothing breaks whichever way Incorta resolves it.
  • There is no per-table endpoint. Fetching one table means fetching its whole schema, so prefer schemas.get(name) once over N tables.get calls.
  • aggregate defaults to true when omitted. A flat extract written without it returns zero rows with HTTP 200, reporting string columns as double. Both clients always send the flag explicitly.
  • Aggregate queries ignore the top-level sorting list. Sorting is read only from inside a dimension. Both clients route each sort onto the dimension it names, and reject a sort matching none rather than letting it vanish.
  • format: "csv" cannot be unstringified. Asking for both returns the header line alone, with HTTP 200. Both clients pick the encoding themselves.
  • nullValueAs: "DASH" is documented but rejected with HTTP 400. Both clients omit it from the accepted values and say why.
  • The query endpoint uses a different error envelope, {"errorMessages": [{"message": "INC_..."}]}, and answers some 400s in plain text rather than JSON. Both clients parse all three shapes onto the same error object.
  • Errors carry a stable INC_ code inside {"message": "INC_09030108: ..."}. Both clients parse it onto the error object separately from the prose.
  • Tokens never appear in logs, serialised output, or a client's public surface.

Development

From the repository root (a pnpm workspace — this package resolves @incorta/auth from packages/auth via workspace:^, so it always builds against the auth code in this commit):

pnpm install
pnpm build && pnpm typecheck && pnpm test

Released off the same v{version} tag as @incorta/auth, at the same version; the publish pipeline rewrites the workspace:^ dependency to that exact version. See the root README.