narrative-studio-sdk
v0.4.31
Published
Narrative SDK for building apps on the Narrative Studio
Downloads
2,152
Readme
Narrative SDK
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 Studioname
- 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 aretext
,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:
- Hand-craft the Scheme object manually.
- 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,
}),
});
}
});