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 🙏

© 2025 – Pkg Stats / Ryan Hefner

narrative-studio-sdk

v0.4.31

Published

Narrative SDK for building apps on the Narrative Studio

Downloads

2,152

Readme

Narrative SDK

npm version

The Narrative SDK provides an interface for interacting with Narrative Studio. It enables you to manage entities, handle events, and maintain a responsive read model, leveraging CQRS (Command Query Responsibility Segregation) for better scalability and performance.

Installation

Install the SDK using npm or yarn:

npm install narrative-studio-sdk

# or

yarn add narrative-studio-sdk

App Manifest

The App Manifest defines your app, its entities, and settings. It must be hosted on a publicly accessible URL to register the app with Narrative Studio.

Manifest Example

{
  "name": "My Workflow App",
  "description": "Single source model for workflow management",
  "icon": "https://example.com/icon.png",
  "version": "1.0.0",
  "appUrl": "https://example.com/app.js",
  "vendor": {
    "name": "VendorName",
    "website": "https://vendor.com",
    "supportUrl": "https://vendor.com/support"
  },
  "settings": {
    "fields": [
      {
        "name": "apiKey",
        "label": "API Key",
        "type": "text",
        "description": "Your personal API key for integration",
        "required": true
      },
      {
        "name": "projectId",
        "label": "Default Project ID",
        "type": "number",
        "description": "The ID of your default project",
        "required": false
      },
      {
        "name": "enableNotifications",
        "label": "Enable Notifications",
        "type": "boolean",
        "description": "Turn on/off notifications",
        "required": false,
        "default": true
      }
    ]
  }
}

Fields Description

  • name - App display name in Narrative Studio.
  • description - Description shown in Narrative Studio.
  • icon - URL for the app icon.
  • version - App version.
  • appUrl - Public URL of the app’s JavaScript file.
  • vendor -
    • name - Organization’s name.
    • website - Organization’s website URL.
    • supportUrl - URL for app support.
  • settings (optional) - The settings that the app will make available to users in the Narrative Studio. The settings saved by the user are available to the app via the SDK.
  • fields - The fields that the app will make available to users in the Narrative Studio
    • name - A unique name for the field.
    • label - The label of the field as it will appear in the Narrative Studio.
    • type - The type of the field. Supported types are text, number, boolean.
    • description - The description of the field as it will appear in the Narrative Studio.
    • required - Whether the field is required or not.
    • default (optional) - The default value of the field.

Usage

The Narrative SDK leverages CQRS (Command Query Responsibility Segregation) to manage application state efficiently, separating the command (write) and query (read) sides for better scalability and performance. Here’s how to use its core features:

1. Initialize the SDK

Start by importing and initializing the SDK. This gives you access to event handling, command sending, and data querying.

import {Narrative} from 'narrative-studio-sdk';

2. Create Scheme

The createScheme method in the Narrative SDK allows you to define and register a custom Scheme with Narrative Studio. A Scheme represents the structure of your model, consisting of Categories, Assets, Constructs, Scripts, and more.

You can either:

  1. Hand-craft the Scheme object manually.
  2. Use the optional SchemeBuilder to construct it programmatically.

Both approaches are supported, but the SchemeBuilder simplifies the process for complex Schemes.

Example: Modeling an E-Commerce System

import {
  SchemeBuilder,
  ConstructShape,
  PermissionAction,
  Narrative,
} from 'narrative-studio-sdk';

// Define entities
const ProductEntity = {
  label: 'Product Entity',
  type: 'product',
  description: 'Represents a product in the system.',
  backgroundColor: '#FFFAE0',
  textColor: '#000000',
  shape: ConstructShape.RECTANGLE,
};

const OrderEntity = {
  label: 'Order Entity',
  type: 'order',
  description: 'Represents a customer order.',
  backgroundColor: '#E8F5E9',
  textColor: '#1B5E20',
  shape: ConstructShape.SQUARE,
};

const UserEntity = {
  label: 'User Entity',
  type: 'user',
  description: 'Represents a user or customer in the system.',
  backgroundColor: '#E3F2FD',
  textColor: '#0D47A1',
  shape: ConstructShape.RECTANGLE,
};

// Create a Scheme using the builder
const ecommerceScheme = SchemeBuilder.create('E-Commerce System')
        .addCategory('Products')
        .addAsset({
          label: 'Product Catalog',
          type: 'catalog',
          description: 'The catalog of all available products.',
          icon: 'https://example.com/catalog-icon.png',
          dataSource: 'products',
        })
        .addConstruct(ProductEntity) // Add Product Entity to the constructs
        .addScript({
          label: 'Inventory Workflow',
          type: 'script',
          description: 'Manages product inventory updates.',
          icon: 'https://example.com/script-icon.png',
          style: {
            backgroundColor: '#F3E5F5',
            borderColor: '#AB47BC',
            borderWidth: 1,
          },
        })
        .addFrameGroup({
          label: { text: 'Inventory Management' },
          permissions: { actions: [PermissionAction.ADD, PermissionAction.REMOVE] },
          frameGroupLimits: { min: 1, max: Infinity },
          frameLimits: { min: 1, max: Infinity },
        })
        .addFrame({
          label: {
              text: 'Stock Check',
              icon: 'https://example.com/frame-icon.png',
          },
          allowedEntities: [ProductEntity], // Reuse Product Entity here
          style: {
            backgroundColor: '#FFF9C4',
          },
        })
        .addLaneGroup({
          permissions: { actions: [PermissionAction.ADD, PermissionAction.REORDER] },
          laneGroupLimits: { min: 1, max: 1 },
          laneLimits: { min: 1, max: 1 },
        })
        .addLane({
          label: {'text': 'Stock Levels'},
          icon: 'https://example.com/lane-icon.png',
          allowedEntities: [ProductEntity], // Reuse Product Entity here
          entityLimits: { min: 1, max: 1 },
          style: { backgroundColor: '#C8E6C9' },
        })
        .addCategory('Orders')
        .addConstruct(OrderEntity) // Add Order Entity to the constructs
        .addCategory('Users')
        .addConstruct(UserEntity) // Add User Entity to the constructs
        .build();

// Send the Scheme to Narrative Studio
const response = await Narrative.createScheme(ecommerceScheme);

2. Send Commands

Commands are used to perform actions that modify the system’s state. They are the primary way to make changes, such as adding, updating, or deleting entities.

Example: Adding a New Entity

import { Narrative, AddEntityCommand } from 'narrative-studio-sdk';

const sendCommandResponse = await Narrative.sendCommand(AddEntityCommand, {
  id: 'order1',
  type: 'order',
  position: { x: 200, y: 150 },
  name: 'Order #1',
});

2. Subscribe to events

Events inform your app of changes or interactions occurring within the Narrative Studio. You can subscribe to these events to react to system changes.

Example: Listening for Changes

Narrative.subscribeToEvents([ChangesSavedEvent], (event: ChangesSavedEvent) => {
    console.log('Changes detected:', event.changes);
});

3. Update the Read Model

The Read Model provides a simplified and optimized view of your data, making it easy to bind to UI components. It allows you to maintain a reactive and up-to-date interface.

You can use the updateReadModel method to add, update, or delete entities in your Read Model.

Example: Updating the Read Model Based on Events

Narrative.subscribeToEvents([ChangesSavedEvent], (event: ChangesSavedEvent) => {
    // Handle added entities
    event.changes.added
        .filter((entity) => entity.type === 'CustomType') // Filter by type
        .forEach((entity) => {
            narrative.updateReadModel([
                {
                    id: entity.id,
                    type: entity.type,
                    data: entity,
                },
            ]);
        });

    // Handle updated entities
    event.changes.updated
        .filter((update) => update.entity.type === 'CustomType') // Filter by type
        .forEach((update) => {
            narrative.updateReadModel([
                {
                    id: update.entity.id,
                    type: update.entity.type,
                    data: update.entity,
                },
            ]);
        });

    // Handle deleted entities
    event.changes.deleted
        .filter((deleted) => deleted.type === 'CustomType') // Filter by type
        .forEach((deleted) => {
            narrative.updateReadModel((currentState) =>
                currentState.filter((item) => item.id !== deleted.id)
            );
        });
});

4. Integrate with Your Backend

To create a seamless user experience, you can integrate Narrative SDK with your backend services. This allows you to:

  • Store data for persistence or auditing.
  • Enrich the read model with additional backend data.
  • Trigger backend workflows based on Narrative Studio events.

Example: Sending Custom Entity Changes to Your Backend

Narrative.subscribeToEvents([ChangesSavedEvent], async (event: ChangesSavedEvent) => {
  const customAddedEntities = event.changes.added.filter(
    (entity) => entity.type === 'CustomType'
  );

  const customUpdatedEntities = event.changes.updated.filter(
    (change) => change.entity.type === 'CustomType'
  );

  const customDeletedEntities = event.changes.deleted.filter(
    (deleted) => deleted.type === 'CustomType'
  );

  if (customAddedEntities.length > 0 || customUpdatedEntities.length > 0 || customDeletedEntities.length > 0) {
    await fetch('https://your-backend-api.com/entity-changes', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        added: customAddedEntities,
        updated: customUpdatedEntities,
        deleted: customDeletedEntities,
      }),
    });
  }
});