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

@narrative.io/marketplace-sdk

v0.3.0

Published

Generated TypeScript SDK for the Narrative Marketplace API

Readme

@narrative.io/marketplace-sdk

npm types license

A TypeScript client for the Narrative platform API, generated from the platform's OpenAPI spec with Hey API. It covers the derivation rules and the three identity endpoints.

  • Typed end to end. Request and response types come from the spec, so a field that changes upstream changes the types here.
  • One dependency. The fetch client. Zod, Pinia Colada, and MSW are optional peers, pulled in only if you import their subpath.
  • Batteries on separate shelves. Validation schemas, Vue query factories, MSW handlers, and test fixtures each live behind their own import.
  • ESM only. Works in Node 18+, Bun, Deno, and browsers — anywhere with a global fetch.

Install

npm install @narrative.io/marketplace-sdk
pnpm add @narrative.io/marketplace-sdk
yarn add @narrative.io/marketplace-sdk
bun add @narrative.io/marketplace-sdk

Quick start

Configure the default client once at startup, then call the SDK functions:

import {
	configureNarrativeClient,
	getDerivations,
} from "@narrative.io/marketplace-sdk";

configureNarrativeClient({ token: process.env.NARRATIVE_TOKEN! });

const { data } = await getDerivations({
	query: { source_attribute_id: [123] },
});

To reach more than one environment or company from the same process, build a client per target and pass it to each call:

import {
	createNarrativeClient,
	getDerivations,
} from "@narrative.io/marketplace-sdk";

const dev = createNarrativeClient({
	token: process.env.NARRATIVE_DEV_TOKEN!,
	environment: "dev",
});

const { data } = await getDerivations({ client: dev });

Authentication

Both factories take a token, either a string or a function returning one. The Bearer prefix is added for you, so pass the token on its own. A function is re-read per request, which is what to use for a token that rotates:

configureNarrativeClient({ token: () => vault.currentNarrativeToken() });

Environments

environment selects the base URL and defaults to prod:

| environment | Base URL | | ------------- | ------------------------------ | | "prod" | https://api.narrative.io | | "dev" | https://api-dev.narrative.io |

Tokens are environment-specific — a prod token is not accepted by dev, so switch the token alongside the environment.

configureNarrativeClient({
	token: process.env.NARRATIVE_DEV_TOKEN!,
	environment: "dev",
});

NARRATIVE_BASE_URLS exports the mapping. For a local API or a data plane, pass baseUrl instead; it takes precedence over environment.

Errors

Calls reject on any non-2xx response. To handle the failure as a value instead, pass throwOnError: false on the call and read error from the result:

const { data, error, response } = await getDerivations({
	throwOnError: false,
});
if (error) console.error(response?.status, error);

Pass it per call rather than on the client. createNarrativeClient({ token, throwOnError: false }) does stop the rejection at runtime, but TypeScript still types the result as the throwing variant — error is missing from the type and data looks present while being undefined. The option at the call site is what changes the type.

response is optional on that result, because an error can come from building the request or from the network, before there is any response.

API

Every operation is an exported function taking one options object and returning { data, error, response }.

| Function | Request | | -------------------------------------------- | --------------------------------------------------- | | getAttributes | GET /attributes | | postAttributes | POST /attributes | | deleteAttributesByAttributeNameOrId | DELETE /attributes/{attribute_name_or_id} | | getAttributesByAttributeNameOrId | GET /attributes/{attribute_name_or_id} | | putAttributesByAttributeNameOrId | PUT /attributes/{attribute_name_or_id} | | getAttributesByAttributeNameOrIdReferences | GET /attributes/{attribute_name_or_id}/references | | getDerivations | GET /derivations | | postDerivations | POST /derivations | | deleteDerivationsByDerivationId | DELETE /derivations/{derivation_id} | | getDerivationsByDerivationId | GET /derivations/{derivation_id} | | patchDerivationsByDerivationId | PATCH /derivations/{derivation_id} | | getWhoami | GET /whoami | | getCompanyInfoWhoami | GET /company-info/whoami | | getInstallationsWhoami | GET /installations/whoami | | getWebhooks | GET /webhooks | | postWebhooks | POST /webhooks | | deleteWebhooksByWebhookSubscriptionId | DELETE /webhooks/{webhook_subscription_id} | | getWebhooksByWebhookSubscriptionId | GET /webhooks/{webhook_subscription_id} | | getAccessTokensTokens | GET /access-tokens/tokens | | postAccessTokensTokens | POST /access-tokens/tokens | | deleteAccessTokensTokensByAccessTokenId | DELETE /access-tokens/tokens/{access_token_id} | | getAccessTokensTokensByAccessTokenId | GET /access-tokens/tokens/{access_token_id} | | putAccessTokensTokensByAccessTokenId | PUT /access-tokens/tokens/{access_token_id} |

The names come from the method and path, because the spec's operations carry no operationId.

Identity

Three calls answer "who is this token", and they answer different questions.

getWhoami is the one to reach for. It returns the user behind the token — id, name, email, role, the companies they can reach, the company the token is currently scoped to, their permissions, and when the token expires.

const { data: me } = await getWhoami();
console.log(me.email, me.role, me.current_company_scope?.name);

getCompanyInfoWhoami returns only the company id, plus the user id when asked:

const { data } = await getCompanyInfoWhoami({ query: { with_user_id: true } });

getInstallationsWhoami is for app tokens. It reports which installation the token operates under, and 403s on a token that is not installation-bound.

Two things about the response types are easy to trip on. getWhoami returns WhoamiResponse and getCompanyInfoWhoami returns WhoAmIResponse; the names differ only in capitalization, because the spec names the schemas that way. And every field on WhoamiResponse is optional — the spec marks none of them required, so me.email types as string | undefined even though the API always sends it.

Entry points

| Import | What it gives you | Needs | | -------------------------------------------- | -------------------------------------- | ----------------- | | @narrative.io/marketplace-sdk | The client, the operations, the types | — | | @narrative.io/marketplace-sdk/zod | A Zod schema per type in the spec | zod | | @narrative.io/marketplace-sdk/colada | Pinia Colada query and mutation factories | @pinia/colada | | @narrative.io/marketplace-sdk/msw | An MSW request handler per operation | msw | | @narrative.io/marketplace-sdk/fixtures | A fixture factory per object schema | — |

zod, @pinia/colada, and msw are optional peer dependencies, so installing this package pulls in none of them. Add whichever subpath you import:

npm install zod             # for /zod
npm install @pinia/colada   # for /colada
npm install --save-dev msw  # for /msw

Importing a subpath without its peer installed fails at resolution, not silently at runtime.

Zod schemas

@narrative.io/marketplace-sdk/zod exports a schema per type in the spec — zDerivationResponse, zDerivationCreateRequest, zWhoamiResponse, zInstallationInfo, and so on. They carry the spec's constraints rather than just its shapes, so a cost of 0 fails against minimum: 1.

import { zDerivationsResponse } from "@narrative.io/marketplace-sdk/zod";

const parsed = zDerivationsResponse.safeParse(payload);
if (!parsed.success) console.error(parsed.error.issues);

SDK calls do not validate on their own. Validating a response is something you opt into, at the point you care about it.

Pinia Colada

@narrative.io/marketplace-sdk/colada exports a query factory per read and a mutation factory per write, for use in Vue components.

import { useQuery } from "@pinia/colada";
import { getDerivationsQuery } from "@narrative.io/marketplace-sdk/colada";

const { data, isLoading } = useQuery(getDerivationsQuery({
	query: { source_attribute_id: [123] },
}));

The factory names follow the SDK function names: a read gets a Query factory and a QueryKey factory, and a write gets a Mutation. The QueryKey factories build the cache keys, for invalidating a query after a mutation.

| Factory | Operation | | ---------------------------------------------------- | --------------------------------------------------- | | getAttributesQuery | GET /attributes | | getAttributesQueryKey | GET /attributes | | postAttributesMutation | POST /attributes | | deleteAttributesByAttributeNameOrIdMutation | DELETE /attributes/{attribute_name_or_id} | | getAttributesByAttributeNameOrIdQuery | GET /attributes/{attribute_name_or_id} | | getAttributesByAttributeNameOrIdQueryKey | GET /attributes/{attribute_name_or_id} | | putAttributesByAttributeNameOrIdMutation | PUT /attributes/{attribute_name_or_id} | | getAttributesByAttributeNameOrIdReferencesQuery | GET /attributes/{attribute_name_or_id}/references | | getAttributesByAttributeNameOrIdReferencesQueryKey | GET /attributes/{attribute_name_or_id}/references | | getDerivationsQuery | GET /derivations | | getDerivationsQueryKey | GET /derivations | | postDerivationsMutation | POST /derivations | | deleteDerivationsByDerivationIdMutation | DELETE /derivations/{derivation_id} | | getDerivationsByDerivationIdQuery | GET /derivations/{derivation_id} | | getDerivationsByDerivationIdQueryKey | GET /derivations/{derivation_id} | | patchDerivationsByDerivationIdMutation | PATCH /derivations/{derivation_id} | | getWhoamiQuery | GET /whoami | | getWhoamiQueryKey | GET /whoami | | getCompanyInfoWhoamiQuery | GET /company-info/whoami | | getCompanyInfoWhoamiQueryKey | GET /company-info/whoami | | getInstallationsWhoamiQuery | GET /installations/whoami | | getInstallationsWhoamiQueryKey | GET /installations/whoami | | getWebhooksQuery | GET /webhooks | | getWebhooksQueryKey | GET /webhooks | | postWebhooksMutation | POST /webhooks | | deleteWebhooksByWebhookSubscriptionIdMutation | DELETE /webhooks/{webhook_subscription_id} | | getWebhooksByWebhookSubscriptionIdQuery | GET /webhooks/{webhook_subscription_id} | | getWebhooksByWebhookSubscriptionIdQueryKey | GET /webhooks/{webhook_subscription_id} | | getAccessTokensTokensQuery | GET /access-tokens/tokens | | getAccessTokensTokensQueryKey | GET /access-tokens/tokens | | postAccessTokensTokensMutation | POST /access-tokens/tokens | | deleteAccessTokensTokensByAccessTokenIdMutation | DELETE /access-tokens/tokens/{access_token_id} | | getAccessTokensTokensByAccessTokenIdQuery | GET /access-tokens/tokens/{access_token_id} | | getAccessTokensTokensByAccessTokenIdQueryKey | GET /access-tokens/tokens/{access_token_id} | | putAccessTokensTokensByAccessTokenIdMutation | PUT /access-tokens/tokens/{access_token_id} |

Testing

MSW handlers

@narrative.io/marketplace-sdk/msw exports a request handler per operation, so tests can serve responses typed against the spec instead of hand-written fixtures.

import { setupServer } from "msw/node";
import { handleGetDerivations } from "@narrative.io/marketplace-sdk/msw";

const server = setupServer(
	handleGetDerivations({ status: 200, body: { records: [rule] } }),
);

createMswHandlers() returns handlers for every operation at once. Passing a resolver function instead of a response object gives access to the intercepted request.

| Handler | Operation | | -------------------------------------------------- | --------------------------------------------------- | | handleGetAttributes | GET /attributes | | handlePostAttributes | POST /attributes | | handleDeleteAttributesByAttributeNameOrId | DELETE /attributes/{attribute_name_or_id} | | handleGetAttributesByAttributeNameOrId | GET /attributes/{attribute_name_or_id} | | handlePutAttributesByAttributeNameOrId | PUT /attributes/{attribute_name_or_id} | | handleGetAttributesByAttributeNameOrIdReferences | GET /attributes/{attribute_name_or_id}/references | | handleGetDerivations | GET /derivations | | handlePostDerivations | POST /derivations | | handleDeleteDerivationsByDerivationId | DELETE /derivations/{derivation_id} | | handleGetDerivationsByDerivationId | GET /derivations/{derivation_id} | | handlePatchDerivationsByDerivationId | PATCH /derivations/{derivation_id} | | handleGetWhoami | GET /whoami | | handleGetCompanyInfoWhoami | GET /company-info/whoami | | handleGetInstallationsWhoami | GET /installations/whoami | | handleGetWebhooks | GET /webhooks | | handlePostWebhooks | POST /webhooks | | handleDeleteWebhooksByWebhookSubscriptionId | DELETE /webhooks/{webhook_subscription_id} | | handleGetWebhooksByWebhookSubscriptionId | GET /webhooks/{webhook_subscription_id} | | handleGetAccessTokensTokens | GET /access-tokens/tokens | | handlePostAccessTokensTokens | POST /access-tokens/tokens | | handleDeleteAccessTokensTokensByAccessTokenId | DELETE /access-tokens/tokens/{access_token_id} | | handleGetAccessTokensTokensByAccessTokenId | GET /access-tokens/tokens/{access_token_id} | | handlePutAccessTokensTokensByAccessTokenId | PUT /access-tokens/tokens/{access_token_id} |

Fixture factories

@narrative.io/marketplace-sdk/fixtures exports a factory per object schema in the spec, for building a response body without writing the object out:

import { createWhoamiResponse } from "@narrative.io/marketplace-sdk/fixtures";
import { handleGetWhoami } from "@narrative.io/marketplace-sdk/msw";

server.use(
	handleGetWhoami({ body: createWhoamiResponse({ role: "global_admin" }) }),
);

Each takes a Partial of its type and spreads it last, so a caller overrides only what its assertion is about. The return type is the annotation on the function, which is what keeps role a literal union rather than string.

Three properties are worth relying on:

  • The values are the same on every call and after every regeneration. They come from the spec's examples and, where the spec has none, from the schema itself. Nothing is random, so a test can seed an id and assert on it.
  • Optional fields are present rather than absent. A nullable field the spec gives no example for is an explicit null. A fixture that omits a key lets a test assert toBeUndefined(), pass, and be wrong about an API that sends the key with a null in it.
  • Nested objects come from the factory for their own schema. So createWhoamiResponse().current_company_scope equals createPublicCompanyResponse(), and a test can seed from one and assert against the other rather than restating a literal.

Two limits are worth knowing before reaching for them:

  • There is one factory per schema, not per semantic variant. A per-schema generator has no way to know which values make a variant, so the variant is an override at the call site: createDerivationCollaborators({ use: { type: "all" } }). Annotate the override when it is a const, so a required field added to the schema later fails at the override rather than quietly producing a value the API never sends.
  • Only object schemas get one. Partial<T> and a spread mean nothing for Instant, which is a string, and a union like DerivationMapping has no single shape to spread into. Both still appear, as the value of a property on a schema that does get a factory.

| Factory | Schema | | -------------------------------------- | -------------------------------- | | createPermission | Permission | | createAccessTokenMetadata | AccessTokenMetadata | | createUpdateAccessTokenRequest | UpdateAccessTokenRequest | | createGetAccessTokenResponse | GetAccessTokenResponse | | createCreatedAccessToken | CreatedAccessToken | | createCreateAccessTokenRequest | CreateAccessTokenRequest | | createListAccessTokensResponse | ListAccessTokensResponse | | createJobTypeFilter | JobTypeFilter | | createJobStateFilter | JobStateFilter | | createJobIdFilter | JobIdFilter | | createWebhookSubscriptionResponse | WebhookSubscriptionResponse | | createAppWebhookSubscriptionRequest | AppWebhookSubscriptionRequest | | createJobsWebhookSubscriptionRequest | JobsWebhookSubscriptionRequest | | createError | Error | | createInstallationInfo | InstallationInfo | | createWhoAmIResponse | WhoAmIResponse | | createResourcePermissions | ResourcePermissions | | createPublicCompanyResponse | PublicCompanyResponse | | createWhoamiSchemasAccessToken | WhoamiSchemasAccessToken | | createWhoamiResponse | WhoamiResponse | | createRawValueMapping | RawValueMapping | | createRawObjectMapping | RawObjectMapping | | createDerivationCollaborators | DerivationCollaborators | | createDerivationUpdateRequest | DerivationUpdateRequest | | createDerivationWarning | DerivationWarning | | createDerivationResponse | DerivationResponse | | createDerivationCreateRequest | DerivationCreateRequest | | createDerivationsResponse | DerivationsResponse | | createVariantType | VariantType | | createTimestampTzType | TimestampTzType | | createStringType | StringType | | createRefType | RefType | | createLongType | LongType | | createDoubleType | DoubleType | | createBooleanType | BooleanType | | createBinaryType | BinaryType | | createAttributeReferencesResponse | AttributeReferencesResponse | | createObjectType | ObjectType | | createArrayType | ArrayType | | createAttributeMetadata | AttributeMetadata | | createAttributeCooccurrence | AttributeCooccurrence | | createAttributesResponse | AttributesResponse |

Versioning

Semver, with the caveat that this client's surface is generated from a spec that moves upstream. A removed or renamed operation, a narrowed type, or a new required parameter is a major; a new operation or optional field is a minor. Every release is described in CHANGELOG.md, which ships in the package.

Support

Platform documentation is at docs.narrative.io. For anything else — a bug in this client, an endpoint you need covered, a question about a response — email [email protected].

License

ISC © Narrative I/O, Inc.