Repository files navigation

caliper-ts

The Caliper Analytics® Specification provides a structured approach to describing, collecting and exchanging learning activity data at scale. Caliper also defines an application programming interface (the Sensor API™) for marshalling and transmitting event data from instrumented applications to target endpoints for storage, analysis and use.

caliper-ts is a reference implementation of the Sensor API™ written in TypeScript, based on the caliper-js library.

NOTE: See this page for the different RAD Tailpipe service receiver endpoints https://weldnorthed.atlassian.net/wiki/spaces/TECH/pages/1242202248/RAD+Tailpipe+Endpoints

NOTE: See this page for the different Deadletter service receiver endpoints https://weldnorthed.atlassian.net/wiki/spaces/TECH/pages/131270774660/RAD+Pipeline+Service+Endpoints

NOTE: See this page for the official Caliper Specification from IMS Global https://www.imsglobal.org/spec/caliper/v1p2

Installation

The caliper-ts package is available on GitHub Package Registry. To install it, you will need to configure your project by adding a .npmrc file to the project root with the following content:

@imaginelearning:registry=https://npm.pkg.github.com

You can then install it using npm or yarn.

npm install @imaginelearning/caliper-ts

Or

yarn add @imaginelearning/caliper-ts

Caliper vocabulary

The Caliper Analytics® Specification defines a set of concepts, relationships and rules for describing learning activities. Each activity domain modeled is described in a profile. Each profile is composed of one or more Event types (e.g., AssessmentEvent, NavigationEvent). Each Event type is associated with a set of actions undertaken by learners, instructors, and others. Various Entity types representing people, groups, and resources are provided in order to better describe both the relationships established between participating entities and the contextual elements relevant to the interaction (e.g., Assessment, Attempt, CourseSection, Person).

Usage

caliper-ts provides a number of classes and factory functions to facilitate working with the Sensor API in a consistent way. Below is a basic example of configuring a sensor and sending an event, as well as more in-depth documentation of the various classes, factories, and utility functions.

Basic example

// Set application URI if using DLQCaliper.settings.applicationUri='https://example.org';// Initialize Caliper sensorconstsensor=newSensor('http://example.org/sensors/1');// Initialize and register clientconstclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w','https://dlq.rad.dev.edgenuityapp.com/api/DeadletterMessage');sensor.registerClient(client);// Set Event property values// Note: only actor and object property assignments shownconstactor=createPerson({id: 'https://example.edu/users/554433'});constobject=createAssessment({id: 'https://example.edu/terms/201801/courses/7/sections/1/assess/1',dateToStartOn: getFormattedDateTime('2018-08-16T05:00:00.000Z'),dateToSubmit: getFormattedDateTime('2018-09-28T11:59:59.000Z'),maxAttempts: 1,maxScore: 25.0,// ... add additional optional property assignments});// ... Use the entity factories to mint additional entity values.constmembership=createMembership({// ...});constsession=createSession({// ...});// Create Eventconstevent=sensor.createEvent(createAssessmentEvent,{
actor,action: Action.Started,
object,
membership,
session,});// ... Create additional events and/or entity describes.// Create envelope with data payloadconstenvelope=sensor.createEnvelope({data: [event,// ... add additional events and/or entity describes],});// Delegate transmission responsibilities to clientsensor.sendToClient(client,envelope);

Sensor class

The Sensor class manages clients for interacting with a Sensor API, as well as providing a helper function for creating properly formatted Envelope objects for transmitting Caliper events.

Constructor: new Sensor(id: string, config?: SensorConfig)

Creates a new instance of a Sensor with the specified ID. Optionally takes a SensorConfig object which can provide the SoftwareApplication to include in events, a flag to enable/disable event validation, and a Record of objects that implement the Client interface, as an alternative to using the Sensor.registerClient function.

// Set application URI if using DLQCaliper.settings.applicationUri='https://example.org';constsensor1=newSensor('http://example.org/sensors/1');// With SensorConfigconstsensor2=newSensor('http://example.org/sensors/2',{edApp: createSoftwareApplication({id: 'https://example.org'}),validationEnabled: true,});// With SensorConfig including HttpClientsconstclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w','https://dlq.rad.dev.edgenuityapp.com/api/DeadletterMessage');constsensor3=newSensor('http://example.org/sensors/3',{edApp: createSoftwareApplication({id: 'https://example.org'}),validationEnabled: true,clients: {[client.getId()]: client,},});

Sensor.createEnvelope<T>(opts: EnvelopeOptions<T>): Envelope<T>

Creates a new Envelope object with the specified options, where the data field is an array of type T.

EnvelopeOptions<T> contains the following properties:

  • sensor: string: ID of the sensor
  • sendTime?: string: ISO 8601 formatted date with time (defaults to current date and time)
  • dataVersion?: string: Version of the Caliper context being used (defaults to http://purl.imsglobal.org/ctx/caliper/v1p1)
  • data?: T | T[]: Object(s) to be transmitted in the envelope, typically an Event, Entity, or combination.
constdata=sensor.createEvent(createSessionEvent,{// See documentation on creating events});constenvelope=sensor.createEnvelope<SessionEvent>({ data });console.log(envelope);/* => { sensor: 'http://example.org/sensors/1', sendTime: '2020-09-09T21:47:01.959Z', dataVersion: 'http://purl.imsglobal.org/ctx/caliper/v1p1', data: [ { "@context": "http://purl.imsglobal.org/ctx/caliper/v1p1", "id": "urn:uuid:fcd495d0-3740-4298-9bec-1154571dc211", "type": "SessionEvent", ... } ]}*/

`Sensor.createEvent<TEvent extends Event, TParams>(eventFactory: (params: TParams, edApp?: SoftwareApplication) => TEvent, params: TParams): TEvent

Creates a new event of type TEvent using the provided factory function and the SoftwareApplication object from the Sensor instance.

constclient=httpClient('http://example.org/sensors/1/clients/1','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w','https://dlq.rad.dev.edgenuityapp.com/api/DeadletterMessage');constsensor=newSensor('http://example.org/sensors/1',{edApp: createSoftwareApplication({id: 'https://example.org'}),validationEnabled: true,clients: {[client.getId()]: client,},});constevent=sensor.createEvent(createAssessmentEvent,{// ... data for AssessmentEventParams});console.log(event);/* => {	type: 'AssessmentEvent',	'@context': ['http://purl.imsglobal.org/ctx/caliper/v1p2'],	edApp: { id: 'https://example.org, type: 'SoftwareApplication'	}	...}*/

Sensor.getClient(id: string): Client

Returns the Client instance registered under the specified ID.

Sensor.getClients(): Client[]

Returns an array containing all registered Client instances.

Sensor.getId(): string

Returns the ID of the current Sensor instance.

Sensor.registerClient(client: Client): void

Adds the specified Client to the Sensor instance's collection of registered clients.

sensor.registerClient(httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint'));

Sensor.sendToClient<TEnvelope, TResponse>(client: Client | string, envelope: Envelope<T>): Promise<TResponse>

Sends the specified Envelope via the specified Client. Returns Promise<TResponse> that resolves when the HTTP request has completed.

// Register HttpClient with Sensorconstclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint');sensor.registerClient(client);// Create Envelopeconstenvelope=sensor.createEnvelope<SessionEvent>({ data });// Send via client by referencesensor.sendToClient<SessionEvent,{success: boolean}>(client,envelope).then((response)=>{console.log(response);// => { success: true }});// Or send via client by IDsensor.sendToClient<SessionEvent,{success: boolean}>('http://example.org/sensors/1/clients/2',envelope).then((response)=>{console.log(response);// => { success: true }});

Sensor.sendToClients<TEnvelope, TResponse>(envelope: Envelope<TEnvelope>): Promise<TResponse[]>

Sends the specified Envelope via all registered HttpClient instances. Returns Promise<TResponse[]> that resolves when all HTTP requests have completed.

// Register clientsconstclient1=httpClient('http://example.org/sensors/1/clients/1','https://example.edu/caliper/target/endpoint1');sensor.registerClient(client1);constclient2=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint2');sensor.registerClient(client2);// Create Envelopeconstenvelope=sensor.createEnvelope<SessionEvent>({ data });// Sends posts envelope to both endpointssensor.sendToClients<SessionEvent,{success: boolean}>(envelope).then((response)=>{console.log(response);// => [{ success: true }, { success: true }]});

Sensor.unregisterClient(id: string): void

Removes the Client instance with the specified ID from the Sensor instance's collection of registered clients.

Client interface

The Client interface defines the required functionality for posting HTTP requests to a Sensor API. Any object that implements the Client interface can be registered with the Sensor as a client. For convenience, caliper-ts includes an HttpClient class which implements the Client interface using the Fetch API. However, using the Client interface you can implement your own client using your preferred method for making HTTP requests.

The Client interface requires the following functions in the implementing class:

  • getId(): string: Returns the ID of the client.
  • send<TEnvelope, TResponse>(envelope: Envelope<TEnvelope>): Promise<TResponse>: Makes a POST request to a Sensor API endpoint with the specified Envelope as the payload. Returns a promise that resolves with the response from the endpoint. This function should also ensure that the appropriate authorization header is included with the request.

HttpClient class

The HttpClient is a complete implementation of the Client interface using the Fetch API. Depending on what browsers you need to support for your application, you may need to include an appropriate polyfill, such as whatwg-fetch. Each HttpClient is configured for a single endpoint, but multiple clients can be registered with a single sensor.

httpClient(id: string, uri: string, token?: string): HttpClient

This factory function returns a new instance of the HttpClient class, configured with the specified ID, URI, and optional access token.

// Create HttpClient that will post to https://example.edu/caliper/target/endpoint// and include the header `Authorization: Bearer 40dI6P62Q_qrWxpTk95z8w`constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w');

HttpClient.bearer(token?: string): HttpClient

Returns a new instance of HttpClient configured to include the specified bearer token in the Authorization header for any request sent with the send function.

// Create HttpClient that will post to https://example.edu/caliper/target/endpoint// and configure to include the header `Authorization: Bearer 40dI6P62Q_qrWxpTk95z8w`constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint').bearer('40dI6P62Q_qrWxpTk95z8w');

HttpClient.getId(): string

Returns the ID of the client.

constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint');constid=client.getId();console.log(id);// => "http://example.org/sensors/1/clients/2"

HttpClient.send<TEnvelope, TResponse>(envelope: TEnvelope): Promise<TResponse>

Makes a POST request to the configured Sensor API endpoint with the specified Envelope as the payload. It includes the Authorization header in the request if the client has been configured with a bearer token. Returns a promise that resolves with the parsed JSON response.

constenvelope=sensor.createEnvelope<SessionEvent>({ data });constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint2');client.send<Envelope<SessionEvent>,{success: boolean}>(envelope).then((result)=>{console.log(result);// => { "success": true }});

Note: The send function is called by the Sensor via the sendToClient and sendToClients functions. You would not invoke the send function directly in a typical application.

Entity factory functions

Caliper entities can be created through factory functions provided by the caliper-ts-models library. Each factory function takes a single parameters: a delegate, which is an object defining values for properties to be set in the entity (see the Entity Subtypes section of the Caliper Spec).

constassessment=createAssessment({dateCreated: '2016-08-01T06:00:00.000Z',dateModified: '2016-09-02T11:30:00.000Z',datePublished: '2016-08-15T09:30:00.000Z',dateToActivate: '2016-08-16T05:00:00.000Z',dateToShow: '2016-08-16T05:00:00.000Z',dateToStartOn: '2016-08-16T05:00:00.000Z',dateToSubmit: '2016-09-28T11:59:59.000Z',id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1',items: [AssessmentItem({id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/1',}),AssessmentItem({id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/2',}),AssessmentItem({id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/3',}),],maxAttempts: 2,maxScore: 15,maxSubmits: 2,name: 'Quiz One',version: '1.0',});console.log(assessment);/* => { "@context": "http://purl.imsglobal.org/ctx/caliper/v1p1", "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1", "type": "Assessment", "name": "Quiz One", "items": [ { "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/1", "type": "AssessmentItem" }, { "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/2", "type": "AssessmentItem" }, { "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/3", "type": "AssessmentItem" } ], "dateCreated": "2016-08-01T06:00:00.000Z", "dateModified": "2016-09-02T11:30:00.000Z", "datePublished": "2016-08-15T09:30:00.000Z", "dateToActivate": "2016-08-16T05:00:00.000Z", "dateToShow": "2016-08-16T05:00:00.000Z", "dateToStartOn": "2016-08-16T05:00:00.000Z", "dateToSubmit": "2016-09-28T11:59:59.000Z", "maxAttempts": 2, "maxScore": 15.0, "maxSubmits": 2, "version": "1.0"}*/

Event factory functions

Caliper events can be created through factory functions. Each factory function takes two parameters: 1) a delegate, which is an object defining values for properties to be set in the event (see the Event Subtypes section of the Caliper Spec), and 2) an optional SoftwareApplication object to use for populating the edApp property in the event.

The recommended way to create events is to use the createEvent function on the Sensor object. This function takes the factory function and delegate object as parameters, and automatically passes the SoftwareApplication object from the Sensor instance to the factory function.

constsessionEvent=sensor.createEvent(createSessionEvent,{action: Action.LoggedIn,actor: createPerson({id: 'https://example.edu/users/554433'}),object: createSoftwareApplication({id: 'https://example.edu',version: 'v2'}),session: createSession({dateCreated: '2016-11-15T10:00:00.000Z',id: 'https://example.edu/sessions/1f6442a482de72ea6ad134943812bff564a76259',startedAtTime: '2016-11-15T10:00:00.000Z',user: 'https://example.edu/users/554433',}),});console.log(sessionEvent);/* => { "@context": "http://purl.imsglobal.org/ctx/caliper/v1p1", "id": "urn:uuid:fcd495d0-3740-4298-9bec-1154571dc211", "type": "SessionEvent", "actor": { "id": "https://example.edu/users/554433", "type": "Person" }, "action": "LoggedIn", "object": { "id": "https://example.edu", "type": "SoftwareApplication", "version": "v2" }, "eventTime": "2016-11-15T10:15:00.000Z", "edApp": { "id": "https://example.edu", "type": "SoftwareApplication" }, "session": { "id": "https://example.edu/sessions/1f6442a482de72ea6ad134943812bff564a76259", "type": "Session", "user": "https://example.edu/users/554433", "dateCreated": "2016-11-15T10:00:00.000Z", "startedAtTime": "2016-11-15T10:00:00.000Z" }}*/

Utility functions

There are a handful of utility functions provided for convenience in properly formatting dates and IDs.

getFormattedDateTime(date?: Date | number | string): string

Takes an optional Date object, number (Unix timestamp), or string and returns a properly formatted ISO-8601 date and time string. If no parameter is specified, it uses the current date and time.

constdate=getFormattedDateTime('9/2/2020, 6:00:00 AM');console.log(date);// => "2020-09-02T12:00:00.000Z"

getFormattedDuration(startedAtTime: Date | string, endedAtTime: Date | string): string

Takes start and end Date objects or strings, calculates the duration between the specified dates, and returns a properly formatted ISO-8601 duration string.

constduration=getFormattedDuration('1969-07-20T02:56:00+0000','1969-07-21T17:54:00+0000');console.log(duration);// => "P0Y0M1DT14H58M0S"

getFormattedUrn(urn: URN): string

Takes a URN object which consists of a namespace ID (nid) and namespace-specific string (nss) and formats it as a URN string.

consturn=getFormattedUrn({nid: 'WNE',nss: 'GUID_OF_AWESOMENESS'});console.log(urn);// => "urn:wne:guid_of_awesomeness"

getFormattedUrnUuid(uuid?: string): string

Takes an optional UUID and formats it as a URN according to RFC-4122. If no UUID is provided, a v4 UUID will be generated with uuid.

consturn=getFormattedUrnUuid('ff9ec22a-fc59-4ae1-ae8d-2c9463ee2f8f');console.log(urn);// => "urn:uuid:ff9ec22a-fc59-4ae1-ae8d-2c9463ee2f8f"

Development

Dependencies

Dependencies in this project are managed with Yarn.You can install dependencies by running the following command in the project's root directory:

yarn

Commands

yarn build

Builds the caliper-ts library.

yarn test

Runs Jest with the --watch flag.

yarn test:ci

Runs Jest in CI mode.

yarn lint

Runs ESLint in the project.

Configuration

Code quality is set up for you with eslint using the using the @imaginelearning/eslint-config/base configuration, prettier using the @imaginelearning/prettier-config configuration, husky, and lint-staged.

Rollup

TSDX uses Rollup as a bundler and generates multiple rollup configs for various module formats and build settings. See Optimizations for details.

TypeScript

tsconfig.json is set up to interpret dom and esnext types, as well as react for jsx. Adjust according to your needs.

Optimizations

Please see the main tsdxoptimizations docs. In particular, know that you can take advantage of development-only optimizations:

// ./types/index.d.tsdeclarevar __DEV__: boolean;// inside your code...if(__DEV__){console.log('foo');}

You can also choose to install and use invariant and warning functions.

Module formats

CJS, ESModules, and UMD module formats are supported.

The appropriate paths are configured in package.json and dist/index.js accordingly. Please report if any issues are found.

Named exports

Per Palmer Group guidelines, always use named exports. Code split inside your app instead of your library.

Code generation

This repository contains events and entities that are generated with the caliper-code-generator using caliper-net as the source of truth.

About

TypeScript implementation of the IMSGlobal/caliper-js library

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

caliper-ts

The Caliper Analytics® Specification provides a structured approach to describing, collecting and exchanging learning activity data at scale. Caliper also defines an application programming interface (the Sensor API™) for marshalling and transmitting event data from instrumented applications to target endpoints for storage, analysis and use.

caliper-ts is a reference implementation of the Sensor API™ written in TypeScript, based on the caliper-js library.

NOTE: See this page for the different RAD Tailpipe service receiver endpoints https://weldnorthed.atlassian.net/wiki/spaces/TECH/pages/1242202248/RAD+Tailpipe+Endpoints

NOTE: See this page for the different Deadletter service receiver endpoints https://weldnorthed.atlassian.net/wiki/spaces/TECH/pages/131270774660/RAD+Pipeline+Service+Endpoints

NOTE: See this page for the official Caliper Specification from IMS Global https://www.imsglobal.org/spec/caliper/v1p2

Installation

The caliper-ts package is available on GitHub Package Registry. To install it, you will need to configure your project by adding a .npmrc file to the project root with the following content:

@imaginelearning:registry=https://npm.pkg.github.com

You can then install it using npm or yarn.

npm install @imaginelearning/caliper-ts

Or

yarn add @imaginelearning/caliper-ts

Caliper vocabulary

The Caliper Analytics® Specification defines a set of concepts, relationships and rules for describing learning activities. Each activity domain modeled is described in a profile. Each profile is composed of one or more Event types (e.g., AssessmentEvent, NavigationEvent). Each Event type is associated with a set of actions undertaken by learners, instructors, and others. Various Entity types representing people, groups, and resources are provided in order to better describe both the relationships established between participating entities and the contextual elements relevant to the interaction (e.g., Assessment, Attempt, CourseSection, Person).

Usage

caliper-ts provides a number of classes and factory functions to facilitate working with the Sensor API in a consistent way. Below is a basic example of configuring a sensor and sending an event, as well as more in-depth documentation of the various classes, factories, and utility functions.

Basic example

// Set application URI if using DLQCaliper.settings.applicationUri='https://example.org';// Initialize Caliper sensorconstsensor=newSensor('http://example.org/sensors/1');// Initialize and register clientconstclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w','https://dlq.rad.dev.edgenuityapp.com/api/DeadletterMessage');sensor.registerClient(client);// Set Event property values// Note: only actor and object property assignments shownconstactor=createPerson({id: 'https://example.edu/users/554433'});constobject=createAssessment({id: 'https://example.edu/terms/201801/courses/7/sections/1/assess/1',dateToStartOn: getFormattedDateTime('2018-08-16T05:00:00.000Z'),dateToSubmit: getFormattedDateTime('2018-09-28T11:59:59.000Z'),maxAttempts: 1,maxScore: 25.0,// ... add additional optional property assignments});// ... Use the entity factories to mint additional entity values.constmembership=createMembership({// ...});constsession=createSession({// ...});// Create Eventconstevent=sensor.createEvent(createAssessmentEvent,{
actor,action: Action.Started,
object,
membership,
session,});// ... Create additional events and/or entity describes.// Create envelope with data payloadconstenvelope=sensor.createEnvelope({data: [event,// ... add additional events and/or entity describes],});// Delegate transmission responsibilities to clientsensor.sendToClient(client,envelope);

Sensor class

The Sensor class manages clients for interacting with a Sensor API, as well as providing a helper function for creating properly formatted Envelope objects for transmitting Caliper events.

Constructor: new Sensor(id: string, config?: SensorConfig)

Creates a new instance of a Sensor with the specified ID. Optionally takes a SensorConfig object which can provide the SoftwareApplication to include in events, a flag to enable/disable event validation, and a Record of objects that implement the Client interface, as an alternative to using the Sensor.registerClient function.

// Set application URI if using DLQCaliper.settings.applicationUri='https://example.org';constsensor1=newSensor('http://example.org/sensors/1');// With SensorConfigconstsensor2=newSensor('http://example.org/sensors/2',{edApp: createSoftwareApplication({id: 'https://example.org'}),validationEnabled: true,});// With SensorConfig including HttpClientsconstclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w','https://dlq.rad.dev.edgenuityapp.com/api/DeadletterMessage');constsensor3=newSensor('http://example.org/sensors/3',{edApp: createSoftwareApplication({id: 'https://example.org'}),validationEnabled: true,clients: {[client.getId()]: client,},});

Sensor.createEnvelope<T>(opts: EnvelopeOptions<T>): Envelope<T>

Creates a new Envelope object with the specified options, where the data field is an array of type T.

EnvelopeOptions<T> contains the following properties:

  • sensor: string: ID of the sensor
  • sendTime?: string: ISO 8601 formatted date with time (defaults to current date and time)
  • dataVersion?: string: Version of the Caliper context being used (defaults to http://purl.imsglobal.org/ctx/caliper/v1p1)
  • data?: T | T[]: Object(s) to be transmitted in the envelope, typically an Event, Entity, or combination.
constdata=sensor.createEvent(createSessionEvent,{// See documentation on creating events});constenvelope=sensor.createEnvelope<SessionEvent>({ data });console.log(envelope);/* => { sensor: 'http://example.org/sensors/1', sendTime: '2020-09-09T21:47:01.959Z', dataVersion: 'http://purl.imsglobal.org/ctx/caliper/v1p1', data: [ { "@context": "http://purl.imsglobal.org/ctx/caliper/v1p1", "id": "urn:uuid:fcd495d0-3740-4298-9bec-1154571dc211", "type": "SessionEvent", ... } ]}*/

`Sensor.createEvent<TEvent extends Event, TParams>(eventFactory: (params: TParams, edApp?: SoftwareApplication) => TEvent, params: TParams): TEvent

Creates a new event of type TEvent using the provided factory function and the SoftwareApplication object from the Sensor instance.

constclient=httpClient('http://example.org/sensors/1/clients/1','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w','https://dlq.rad.dev.edgenuityapp.com/api/DeadletterMessage');constsensor=newSensor('http://example.org/sensors/1',{edApp: createSoftwareApplication({id: 'https://example.org'}),validationEnabled: true,clients: {[client.getId()]: client,},});constevent=sensor.createEvent(createAssessmentEvent,{// ... data for AssessmentEventParams});console.log(event);/* => {	type: 'AssessmentEvent',	'@context': ['http://purl.imsglobal.org/ctx/caliper/v1p2'],	edApp: { id: 'https://example.org, type: 'SoftwareApplication'	}	...}*/

Sensor.getClient(id: string): Client

Returns the Client instance registered under the specified ID.

Sensor.getClients(): Client[]

Returns an array containing all registered Client instances.

Sensor.getId(): string

Returns the ID of the current Sensor instance.

Sensor.registerClient(client: Client): void

Adds the specified Client to the Sensor instance's collection of registered clients.

sensor.registerClient(httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint'));

Sensor.sendToClient<TEnvelope, TResponse>(client: Client | string, envelope: Envelope<T>): Promise<TResponse>

Sends the specified Envelope via the specified Client. Returns Promise<TResponse> that resolves when the HTTP request has completed.

// Register HttpClient with Sensorconstclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint');sensor.registerClient(client);// Create Envelopeconstenvelope=sensor.createEnvelope<SessionEvent>({ data });// Send via client by referencesensor.sendToClient<SessionEvent,{success: boolean}>(client,envelope).then((response)=>{console.log(response);// => { success: true }});// Or send via client by IDsensor.sendToClient<SessionEvent,{success: boolean}>('http://example.org/sensors/1/clients/2',envelope).then((response)=>{console.log(response);// => { success: true }});

Sensor.sendToClients<TEnvelope, TResponse>(envelope: Envelope<TEnvelope>): Promise<TResponse[]>

Sends the specified Envelope via all registered HttpClient instances. Returns Promise<TResponse[]> that resolves when all HTTP requests have completed.

// Register clientsconstclient1=httpClient('http://example.org/sensors/1/clients/1','https://example.edu/caliper/target/endpoint1');sensor.registerClient(client1);constclient2=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint2');sensor.registerClient(client2);// Create Envelopeconstenvelope=sensor.createEnvelope<SessionEvent>({ data });// Sends posts envelope to both endpointssensor.sendToClients<SessionEvent,{success: boolean}>(envelope).then((response)=>{console.log(response);// => [{ success: true }, { success: true }]});

Sensor.unregisterClient(id: string): void

Removes the Client instance with the specified ID from the Sensor instance's collection of registered clients.

Client interface

The Client interface defines the required functionality for posting HTTP requests to a Sensor API. Any object that implements the Client interface can be registered with the Sensor as a client. For convenience, caliper-ts includes an HttpClient class which implements the Client interface using the Fetch API. However, using the Client interface you can implement your own client using your preferred method for making HTTP requests.

The Client interface requires the following functions in the implementing class:

  • getId(): string: Returns the ID of the client.
  • send<TEnvelope, TResponse>(envelope: Envelope<TEnvelope>): Promise<TResponse>: Makes a POST request to a Sensor API endpoint with the specified Envelope as the payload. Returns a promise that resolves with the response from the endpoint. This function should also ensure that the appropriate authorization header is included with the request.

HttpClient class

The HttpClient is a complete implementation of the Client interface using the Fetch API. Depending on what browsers you need to support for your application, you may need to include an appropriate polyfill, such as whatwg-fetch. Each HttpClient is configured for a single endpoint, but multiple clients can be registered with a single sensor.

httpClient(id: string, uri: string, token?: string): HttpClient

This factory function returns a new instance of the HttpClient class, configured with the specified ID, URI, and optional access token.

// Create HttpClient that will post to https://example.edu/caliper/target/endpoint// and include the header `Authorization: Bearer 40dI6P62Q_qrWxpTk95z8w`constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w');

HttpClient.bearer(token?: string): HttpClient

Returns a new instance of HttpClient configured to include the specified bearer token in the Authorization header for any request sent with the send function.

// Create HttpClient that will post to https://example.edu/caliper/target/endpoint// and configure to include the header `Authorization: Bearer 40dI6P62Q_qrWxpTk95z8w`constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint').bearer('40dI6P62Q_qrWxpTk95z8w');

HttpClient.getId(): string

Returns the ID of the client.

constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint');constid=client.getId();console.log(id);// => "http://example.org/sensors/1/clients/2"

HttpClient.send<TEnvelope, TResponse>(envelope: TEnvelope): Promise<TResponse>

Makes a POST request to the configured Sensor API endpoint with the specified Envelope as the payload. It includes the Authorization header in the request if the client has been configured with a bearer token. Returns a promise that resolves with the parsed JSON response.

constenvelope=sensor.createEnvelope<SessionEvent>({ data });constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint2');client.send<Envelope<SessionEvent>,{success: boolean}>(envelope).then((result)=>{console.log(result);// => { "success": true }});

Note: The send function is called by the Sensor via the sendToClient and sendToClients functions. You would not invoke the send function directly in a typical application.

Entity factory functions

Caliper entities can be created through factory functions provided by the caliper-ts-models library. Each factory function takes a single parameters: a delegate, which is an object defining values for properties to be set in the entity (see the Entity Subtypes section of the Caliper Spec).

constassessment=createAssessment({dateCreated: '2016-08-01T06:00:00.000Z',dateModified: '2016-09-02T11:30:00.000Z',datePublished: '2016-08-15T09:30:00.000Z',dateToActivate: '2016-08-16T05:00:00.000Z',dateToShow: '2016-08-16T05:00:00.000Z',dateToStartOn: '2016-08-16T05:00:00.000Z',dateToSubmit: '2016-09-28T11:59:59.000Z',id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1',items: [AssessmentItem({id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/1',}),AssessmentItem({id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/2',}),AssessmentItem({id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/3',}),],maxAttempts: 2,maxScore: 15,maxSubmits: 2,name: 'Quiz One',version: '1.0',});console.log(assessment);/* => { "@context": "http://purl.imsglobal.org/ctx/caliper/v1p1", "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1", "type": "Assessment", "name": "Quiz One", "items": [ { "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/1", "type": "AssessmentItem" }, { "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/2", "type": "AssessmentItem" }, { "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/3", "type": "AssessmentItem" } ], "dateCreated": "2016-08-01T06:00:00.000Z", "dateModified": "2016-09-02T11:30:00.000Z", "datePublished": "2016-08-15T09:30:00.000Z", "dateToActivate": "2016-08-16T05:00:00.000Z", "dateToShow": "2016-08-16T05:00:00.000Z", "dateToStartOn": "2016-08-16T05:00:00.000Z", "dateToSubmit": "2016-09-28T11:59:59.000Z", "maxAttempts": 2, "maxScore": 15.0, "maxSubmits": 2, "version": "1.0"}*/

Event factory functions

Caliper events can be created through factory functions. Each factory function takes two parameters: 1) a delegate, which is an object defining values for properties to be set in the event (see the Event Subtypes section of the Caliper Spec), and 2) an optional SoftwareApplication object to use for populating the edApp property in the event.

The recommended way to create events is to use the createEvent function on the Sensor object. This function takes the factory function and delegate object as parameters, and automatically passes the SoftwareApplication object from the Sensor instance to the factory function.

constsessionEvent=sensor.createEvent(createSessionEvent,{action: Action.LoggedIn,actor: createPerson({id: 'https://example.edu/users/554433'}),object: createSoftwareApplication({id: 'https://example.edu',version: 'v2'}),session: createSession({dateCreated: '2016-11-15T10:00:00.000Z',id: 'https://example.edu/sessions/1f6442a482de72ea6ad134943812bff564a76259',startedAtTime: '2016-11-15T10:00:00.000Z',user: 'https://example.edu/users/554433',}),});console.log(sessionEvent);/* => { "@context": "http://purl.imsglobal.org/ctx/caliper/v1p1", "id": "urn:uuid:fcd495d0-3740-4298-9bec-1154571dc211", "type": "SessionEvent", "actor": { "id": "https://example.edu/users/554433", "type": "Person" }, "action": "LoggedIn", "object": { "id": "https://example.edu", "type": "SoftwareApplication", "version": "v2" }, "eventTime": "2016-11-15T10:15:00.000Z", "edApp": { "id": "https://example.edu", "type": "SoftwareApplication" }, "session": { "id": "https://example.edu/sessions/1f6442a482de72ea6ad134943812bff564a76259", "type": "Session", "user": "https://example.edu/users/554433", "dateCreated": "2016-11-15T10:00:00.000Z", "startedAtTime": "2016-11-15T10:00:00.000Z" }}*/

Utility functions

There are a handful of utility functions provided for convenience in properly formatting dates and IDs.

getFormattedDateTime(date?: Date | number | string): string

Takes an optional Date object, number (Unix timestamp), or string and returns a properly formatted ISO-8601 date and time string. If no parameter is specified, it uses the current date and time.

constdate=getFormattedDateTime('9/2/2020, 6:00:00 AM');console.log(date);// => "2020-09-02T12:00:00.000Z"

getFormattedDuration(startedAtTime: Date | string, endedAtTime: Date | string): string

Takes start and end Date objects or strings, calculates the duration between the specified dates, and returns a properly formatted ISO-8601 duration string.

constduration=getFormattedDuration('1969-07-20T02:56:00+0000','1969-07-21T17:54:00+0000');console.log(duration);// => "P0Y0M1DT14H58M0S"

getFormattedUrn(urn: URN): string

Takes a URN object which consists of a namespace ID (nid) and namespace-specific string (nss) and formats it as a URN string.

consturn=getFormattedUrn({nid: 'WNE',nss: 'GUID_OF_AWESOMENESS'});console.log(urn);// => "urn:wne:guid_of_awesomeness"

getFormattedUrnUuid(uuid?: string): string

Takes an optional UUID and formats it as a URN according to RFC-4122. If no UUID is provided, a v4 UUID will be generated with uuid.

consturn=getFormattedUrnUuid('ff9ec22a-fc59-4ae1-ae8d-2c9463ee2f8f');console.log(urn);// => "urn:uuid:ff9ec22a-fc59-4ae1-ae8d-2c9463ee2f8f"

Development

Dependencies

Dependencies in this project are managed with Yarn.You can install dependencies by running the following command in the project's root directory:

yarn

Commands

yarn build

Builds the caliper-ts library.

yarn test

Runs Jest with the --watch flag.

yarn test:ci

Runs Jest in CI mode.

yarn lint

Runs ESLint in the project.

Configuration

Code quality is set up for you with eslint using the using the @imaginelearning/eslint-config/base configuration, prettier using the @imaginelearning/prettier-config configuration, husky, and lint-staged.

Rollup

TSDX uses Rollup as a bundler and generates multiple rollup configs for various module formats and build settings. See Optimizations for details.

TypeScript

tsconfig.json is set up to interpret dom and esnext types, as well as react for jsx. Adjust according to your needs.

Optimizations

Please see the main tsdxoptimizations docs. In particular, know that you can take advantage of development-only optimizations:

// ./types/index.d.tsdeclarevar __DEV__: boolean;// inside your code...if(__DEV__){console.log('foo');}

You can also choose to install and use invariant and warning functions.

Module formats

CJS, ESModules, and UMD module formats are supported.

The appropriate paths are configured in package.json and dist/index.js accordingly. Please report if any issues are found.

Named exports

Per Palmer Group guidelines, always use named exports. Code split inside your app instead of your library.

Code generation

This repository contains events and entities that are generated with the caliper-code-generator using caliper-net as the source of truth.

About

TypeScript implementation of the IMSGlobal/caliper-js library

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

caliper-ts

The Caliper Analytics® Specification provides a structured approach to describing, collecting and exchanging learning activity data at scale. Caliper also defines an application programming interface (the Sensor API™) for marshalling and transmitting event data from instrumented applications to target endpoints for storage, analysis and use.

caliper-ts is a reference implementation of the Sensor API™ written in TypeScript, based on the caliper-js library.

NOTE: See this page for the different RAD Tailpipe service receiver endpoints https://weldnorthed.atlassian.net/wiki/spaces/TECH/pages/1242202248/RAD+Tailpipe+Endpoints

NOTE: See this page for the different Deadletter service receiver endpoints https://weldnorthed.atlassian.net/wiki/spaces/TECH/pages/131270774660/RAD+Pipeline+Service+Endpoints

NOTE: See this page for the official Caliper Specification from IMS Global https://www.imsglobal.org/spec/caliper/v1p2

Installation

The caliper-ts package is available on GitHub Package Registry. To install it, you will need to configure your project by adding a .npmrc file to the project root with the following content:

@imaginelearning:registry=https://npm.pkg.github.com

You can then install it using npm or yarn.

npm install @imaginelearning/caliper-ts

Or

yarn add @imaginelearning/caliper-ts

Caliper vocabulary

The Caliper Analytics® Specification defines a set of concepts, relationships and rules for describing learning activities. Each activity domain modeled is described in a profile. Each profile is composed of one or more Event types (e.g., AssessmentEvent, NavigationEvent). Each Event type is associated with a set of actions undertaken by learners, instructors, and others. Various Entity types representing people, groups, and resources are provided in order to better describe both the relationships established between participating entities and the contextual elements relevant to the interaction (e.g., Assessment, Attempt, CourseSection, Person).

Usage

caliper-ts provides a number of classes and factory functions to facilitate working with the Sensor API in a consistent way. Below is a basic example of configuring a sensor and sending an event, as well as more in-depth documentation of the various classes, factories, and utility functions.

Basic example

// Set application URI if using DLQCaliper.settings.applicationUri='https://example.org';// Initialize Caliper sensorconstsensor=newSensor('http://example.org/sensors/1');// Initialize and register clientconstclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w','https://dlq.rad.dev.edgenuityapp.com/api/DeadletterMessage');sensor.registerClient(client);// Set Event property values// Note: only actor and object property assignments shownconstactor=createPerson({id: 'https://example.edu/users/554433'});constobject=createAssessment({id: 'https://example.edu/terms/201801/courses/7/sections/1/assess/1',dateToStartOn: getFormattedDateTime('2018-08-16T05:00:00.000Z'),dateToSubmit: getFormattedDateTime('2018-09-28T11:59:59.000Z'),maxAttempts: 1,maxScore: 25.0,// ... add additional optional property assignments});// ... Use the entity factories to mint additional entity values.constmembership=createMembership({// ...});constsession=createSession({// ...});// Create Eventconstevent=sensor.createEvent(createAssessmentEvent,{
actor,action: Action.Started,
object,
membership,
session,});// ... Create additional events and/or entity describes.// Create envelope with data payloadconstenvelope=sensor.createEnvelope({data: [event,// ... add additional events and/or entity describes],});// Delegate transmission responsibilities to clientsensor.sendToClient(client,envelope);

Sensor class

The Sensor class manages clients for interacting with a Sensor API, as well as providing a helper function for creating properly formatted Envelope objects for transmitting Caliper events.

Constructor: new Sensor(id: string, config?: SensorConfig)

Creates a new instance of a Sensor with the specified ID. Optionally takes a SensorConfig object which can provide the SoftwareApplication to include in events, a flag to enable/disable event validation, and a Record of objects that implement the Client interface, as an alternative to using the Sensor.registerClient function.

// Set application URI if using DLQCaliper.settings.applicationUri='https://example.org';constsensor1=newSensor('http://example.org/sensors/1');// With SensorConfigconstsensor2=newSensor('http://example.org/sensors/2',{edApp: createSoftwareApplication({id: 'https://example.org'}),validationEnabled: true,});// With SensorConfig including HttpClientsconstclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w','https://dlq.rad.dev.edgenuityapp.com/api/DeadletterMessage');constsensor3=newSensor('http://example.org/sensors/3',{edApp: createSoftwareApplication({id: 'https://example.org'}),validationEnabled: true,clients: {[client.getId()]: client,},});

Sensor.createEnvelope<T>(opts: EnvelopeOptions<T>): Envelope<T>

Creates a new Envelope object with the specified options, where the data field is an array of type T.

EnvelopeOptions<T> contains the following properties:

  • sensor: string: ID of the sensor
  • sendTime?: string: ISO 8601 formatted date with time (defaults to current date and time)
  • dataVersion?: string: Version of the Caliper context being used (defaults to http://purl.imsglobal.org/ctx/caliper/v1p1)
  • data?: T | T[]: Object(s) to be transmitted in the envelope, typically an Event, Entity, or combination.
constdata=sensor.createEvent(createSessionEvent,{// See documentation on creating events});constenvelope=sensor.createEnvelope<SessionEvent>({ data });console.log(envelope);/* => { sensor: 'http://example.org/sensors/1', sendTime: '2020-09-09T21:47:01.959Z', dataVersion: 'http://purl.imsglobal.org/ctx/caliper/v1p1', data: [ { "@context": "http://purl.imsglobal.org/ctx/caliper/v1p1", "id": "urn:uuid:fcd495d0-3740-4298-9bec-1154571dc211", "type": "SessionEvent", ... } ]}*/

`Sensor.createEvent<TEvent extends Event, TParams>(eventFactory: (params: TParams, edApp?: SoftwareApplication) => TEvent, params: TParams): TEvent

Creates a new event of type TEvent using the provided factory function and the SoftwareApplication object from the Sensor instance.

constclient=httpClient('http://example.org/sensors/1/clients/1','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w','https://dlq.rad.dev.edgenuityapp.com/api/DeadletterMessage');constsensor=newSensor('http://example.org/sensors/1',{edApp: createSoftwareApplication({id: 'https://example.org'}),validationEnabled: true,clients: {[client.getId()]: client,},});constevent=sensor.createEvent(createAssessmentEvent,{// ... data for AssessmentEventParams});console.log(event);/* => {	type: 'AssessmentEvent',	'@context': ['http://purl.imsglobal.org/ctx/caliper/v1p2'],	edApp: { id: 'https://example.org, type: 'SoftwareApplication'	}	...}*/

Sensor.getClient(id: string): Client

Returns the Client instance registered under the specified ID.

Sensor.getClients(): Client[]

Returns an array containing all registered Client instances.

Sensor.getId(): string

Returns the ID of the current Sensor instance.

Sensor.registerClient(client: Client): void

Adds the specified Client to the Sensor instance's collection of registered clients.

sensor.registerClient(httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint'));

Sensor.sendToClient<TEnvelope, TResponse>(client: Client | string, envelope: Envelope<T>): Promise<TResponse>

Sends the specified Envelope via the specified Client. Returns Promise<TResponse> that resolves when the HTTP request has completed.

// Register HttpClient with Sensorconstclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint');sensor.registerClient(client);// Create Envelopeconstenvelope=sensor.createEnvelope<SessionEvent>({ data });// Send via client by referencesensor.sendToClient<SessionEvent,{success: boolean}>(client,envelope).then((response)=>{console.log(response);// => { success: true }});// Or send via client by IDsensor.sendToClient<SessionEvent,{success: boolean}>('http://example.org/sensors/1/clients/2',envelope).then((response)=>{console.log(response);// => { success: true }});

Sensor.sendToClients<TEnvelope, TResponse>(envelope: Envelope<TEnvelope>): Promise<TResponse[]>

Sends the specified Envelope via all registered HttpClient instances. Returns Promise<TResponse[]> that resolves when all HTTP requests have completed.

// Register clientsconstclient1=httpClient('http://example.org/sensors/1/clients/1','https://example.edu/caliper/target/endpoint1');sensor.registerClient(client1);constclient2=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint2');sensor.registerClient(client2);// Create Envelopeconstenvelope=sensor.createEnvelope<SessionEvent>({ data });// Sends posts envelope to both endpointssensor.sendToClients<SessionEvent,{success: boolean}>(envelope).then((response)=>{console.log(response);// => [{ success: true }, { success: true }]});

Sensor.unregisterClient(id: string): void

Removes the Client instance with the specified ID from the Sensor instance's collection of registered clients.

Client interface

The Client interface defines the required functionality for posting HTTP requests to a Sensor API. Any object that implements the Client interface can be registered with the Sensor as a client. For convenience, caliper-ts includes an HttpClient class which implements the Client interface using the Fetch API. However, using the Client interface you can implement your own client using your preferred method for making HTTP requests.

The Client interface requires the following functions in the implementing class:

  • getId(): string: Returns the ID of the client.
  • send<TEnvelope, TResponse>(envelope: Envelope<TEnvelope>): Promise<TResponse>: Makes a POST request to a Sensor API endpoint with the specified Envelope as the payload. Returns a promise that resolves with the response from the endpoint. This function should also ensure that the appropriate authorization header is included with the request.

HttpClient class

The HttpClient is a complete implementation of the Client interface using the Fetch API. Depending on what browsers you need to support for your application, you may need to include an appropriate polyfill, such as whatwg-fetch. Each HttpClient is configured for a single endpoint, but multiple clients can be registered with a single sensor.

httpClient(id: string, uri: string, token?: string): HttpClient

This factory function returns a new instance of the HttpClient class, configured with the specified ID, URI, and optional access token.

// Create HttpClient that will post to https://example.edu/caliper/target/endpoint// and include the header `Authorization: Bearer 40dI6P62Q_qrWxpTk95z8w`constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w');

HttpClient.bearer(token?: string): HttpClient

Returns a new instance of HttpClient configured to include the specified bearer token in the Authorization header for any request sent with the send function.

// Create HttpClient that will post to https://example.edu/caliper/target/endpoint// and configure to include the header `Authorization: Bearer 40dI6P62Q_qrWxpTk95z8w`constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint').bearer('40dI6P62Q_qrWxpTk95z8w');

HttpClient.getId(): string

Returns the ID of the client.

constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint');constid=client.getId();console.log(id);// => "http://example.org/sensors/1/clients/2"

HttpClient.send<TEnvelope, TResponse>(envelope: TEnvelope): Promise<TResponse>

Makes a POST request to the configured Sensor API endpoint with the specified Envelope as the payload. It includes the Authorization header in the request if the client has been configured with a bearer token. Returns a promise that resolves with the parsed JSON response.

constenvelope=sensor.createEnvelope<SessionEvent>({ data });constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint2');client.send<Envelope<SessionEvent>,{success: boolean}>(envelope).then((result)=>{console.log(result);// => { "success": true }});

Note: The send function is called by the Sensor via the sendToClient and sendToClients functions. You would not invoke the send function directly in a typical application.

Entity factory functions

Caliper entities can be created through factory functions provided by the caliper-ts-models library. Each factory function takes a single parameters: a delegate, which is an object defining values for properties to be set in the entity (see the Entity Subtypes section of the Caliper Spec).

constassessment=createAssessment({dateCreated: '2016-08-01T06:00:00.000Z',dateModified: '2016-09-02T11:30:00.000Z',datePublished: '2016-08-15T09:30:00.000Z',dateToActivate: '2016-08-16T05:00:00.000Z',dateToShow: '2016-08-16T05:00:00.000Z',dateToStartOn: '2016-08-16T05:00:00.000Z',dateToSubmit: '2016-09-28T11:59:59.000Z',id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1',items: [AssessmentItem({id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/1',}),AssessmentItem({id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/2',}),AssessmentItem({id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/3',}),],maxAttempts: 2,maxScore: 15,maxSubmits: 2,name: 'Quiz One',version: '1.0',});console.log(assessment);/* => { "@context": "http://purl.imsglobal.org/ctx/caliper/v1p1", "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1", "type": "Assessment", "name": "Quiz One", "items": [ { "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/1", "type": "AssessmentItem" }, { "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/2", "type": "AssessmentItem" }, { "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/3", "type": "AssessmentItem" } ], "dateCreated": "2016-08-01T06:00:00.000Z", "dateModified": "2016-09-02T11:30:00.000Z", "datePublished": "2016-08-15T09:30:00.000Z", "dateToActivate": "2016-08-16T05:00:00.000Z", "dateToShow": "2016-08-16T05:00:00.000Z", "dateToStartOn": "2016-08-16T05:00:00.000Z", "dateToSubmit": "2016-09-28T11:59:59.000Z", "maxAttempts": 2, "maxScore": 15.0, "maxSubmits": 2, "version": "1.0"}*/

Event factory functions

Caliper events can be created through factory functions. Each factory function takes two parameters: 1) a delegate, which is an object defining values for properties to be set in the event (see the Event Subtypes section of the Caliper Spec), and 2) an optional SoftwareApplication object to use for populating the edApp property in the event.

The recommended way to create events is to use the createEvent function on the Sensor object. This function takes the factory function and delegate object as parameters, and automatically passes the SoftwareApplication object from the Sensor instance to the factory function.

constsessionEvent=sensor.createEvent(createSessionEvent,{action: Action.LoggedIn,actor: createPerson({id: 'https://example.edu/users/554433'}),object: createSoftwareApplication({id: 'https://example.edu',version: 'v2'}),session: createSession({dateCreated: '2016-11-15T10:00:00.000Z',id: 'https://example.edu/sessions/1f6442a482de72ea6ad134943812bff564a76259',startedAtTime: '2016-11-15T10:00:00.000Z',user: 'https://example.edu/users/554433',}),});console.log(sessionEvent);/* => { "@context": "http://purl.imsglobal.org/ctx/caliper/v1p1", "id": "urn:uuid:fcd495d0-3740-4298-9bec-1154571dc211", "type": "SessionEvent", "actor": { "id": "https://example.edu/users/554433", "type": "Person" }, "action": "LoggedIn", "object": { "id": "https://example.edu", "type": "SoftwareApplication", "version": "v2" }, "eventTime": "2016-11-15T10:15:00.000Z", "edApp": { "id": "https://example.edu", "type": "SoftwareApplication" }, "session": { "id": "https://example.edu/sessions/1f6442a482de72ea6ad134943812bff564a76259", "type": "Session", "user": "https://example.edu/users/554433", "dateCreated": "2016-11-15T10:00:00.000Z", "startedAtTime": "2016-11-15T10:00:00.000Z" }}*/

Utility functions

There are a handful of utility functions provided for convenience in properly formatting dates and IDs.

getFormattedDateTime(date?: Date | number | string): string

Takes an optional Date object, number (Unix timestamp), or string and returns a properly formatted ISO-8601 date and time string. If no parameter is specified, it uses the current date and time.

constdate=getFormattedDateTime('9/2/2020, 6:00:00 AM');console.log(date);// => "2020-09-02T12:00:00.000Z"

getFormattedDuration(startedAtTime: Date | string, endedAtTime: Date | string): string

Takes start and end Date objects or strings, calculates the duration between the specified dates, and returns a properly formatted ISO-8601 duration string.

constduration=getFormattedDuration('1969-07-20T02:56:00+0000','1969-07-21T17:54:00+0000');console.log(duration);// => "P0Y0M1DT14H58M0S"

getFormattedUrn(urn: URN): string

Takes a URN object which consists of a namespace ID (nid) and namespace-specific string (nss) and formats it as a URN string.

consturn=getFormattedUrn({nid: 'WNE',nss: 'GUID_OF_AWESOMENESS'});console.log(urn);// => "urn:wne:guid_of_awesomeness"

getFormattedUrnUuid(uuid?: string): string

Takes an optional UUID and formats it as a URN according to RFC-4122. If no UUID is provided, a v4 UUID will be generated with uuid.

consturn=getFormattedUrnUuid('ff9ec22a-fc59-4ae1-ae8d-2c9463ee2f8f');console.log(urn);// => "urn:uuid:ff9ec22a-fc59-4ae1-ae8d-2c9463ee2f8f"

Development

Dependencies

Dependencies in this project are managed with Yarn.You can install dependencies by running the following command in the project's root directory:

yarn

Commands

yarn build

Builds the caliper-ts library.

yarn test

Runs Jest with the --watch flag.

yarn test:ci

Runs Jest in CI mode.

yarn lint

Runs ESLint in the project.

Configuration

Code quality is set up for you with eslint using the using the @imaginelearning/eslint-config/base configuration, prettier using the @imaginelearning/prettier-config configuration, husky, and lint-staged.

Rollup

TSDX uses Rollup as a bundler and generates multiple rollup configs for various module formats and build settings. See Optimizations for details.

TypeScript

tsconfig.json is set up to interpret dom and esnext types, as well as react for jsx. Adjust according to your needs.

Optimizations

Please see the main tsdxoptimizations docs. In particular, know that you can take advantage of development-only optimizations:

// ./types/index.d.tsdeclarevar __DEV__: boolean;// inside your code...if(__DEV__){console.log('foo');}

You can also choose to install and use invariant and warning functions.

Module formats

CJS, ESModules, and UMD module formats are supported.

The appropriate paths are configured in package.json and dist/index.js accordingly. Please report if any issues are found.

Named exports

Per Palmer Group guidelines, always use named exports. Code split inside your app instead of your library.

Code generation

This repository contains events and entities that are generated with the caliper-code-generator using caliper-net as the source of truth.

About

TypeScript implementation of the IMSGlobal/caliper-js library

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

caliper-ts

The Caliper Analytics® Specification provides a structured approach to describing, collecting and exchanging learning activity data at scale. Caliper also defines an application programming interface (the Sensor API™) for marshalling and transmitting event data from instrumented applications to target endpoints for storage, analysis and use.

caliper-ts is a reference implementation of the Sensor API™ written in TypeScript, based on the caliper-js library.

NOTE: See this page for the different RAD Tailpipe service receiver endpoints https://weldnorthed.atlassian.net/wiki/spaces/TECH/pages/1242202248/RAD+Tailpipe+Endpoints

NOTE: See this page for the different Deadletter service receiver endpoints https://weldnorthed.atlassian.net/wiki/spaces/TECH/pages/131270774660/RAD+Pipeline+Service+Endpoints

NOTE: See this page for the official Caliper Specification from IMS Global https://www.imsglobal.org/spec/caliper/v1p2

Installation

The caliper-ts package is available on GitHub Package Registry. To install it, you will need to configure your project by adding a .npmrc file to the project root with the following content:

@imaginelearning:registry=https://npm.pkg.github.com

You can then install it using npm or yarn.

npm install @imaginelearning/caliper-ts

Or

yarn add @imaginelearning/caliper-ts

Caliper vocabulary

The Caliper Analytics® Specification defines a set of concepts, relationships and rules for describing learning activities. Each activity domain modeled is described in a profile. Each profile is composed of one or more Event types (e.g., AssessmentEvent, NavigationEvent). Each Event type is associated with a set of actions undertaken by learners, instructors, and others. Various Entity types representing people, groups, and resources are provided in order to better describe both the relationships established between participating entities and the contextual elements relevant to the interaction (e.g., Assessment, Attempt, CourseSection, Person).

Usage

caliper-ts provides a number of classes and factory functions to facilitate working with the Sensor API in a consistent way. Below is a basic example of configuring a sensor and sending an event, as well as more in-depth documentation of the various classes, factories, and utility functions.

Basic example

// Set application URI if using DLQCaliper.settings.applicationUri='https://example.org';// Initialize Caliper sensorconstsensor=newSensor('http://example.org/sensors/1');// Initialize and register clientconstclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w','https://dlq.rad.dev.edgenuityapp.com/api/DeadletterMessage');sensor.registerClient(client);// Set Event property values// Note: only actor and object property assignments shownconstactor=createPerson({id: 'https://example.edu/users/554433'});constobject=createAssessment({id: 'https://example.edu/terms/201801/courses/7/sections/1/assess/1',dateToStartOn: getFormattedDateTime('2018-08-16T05:00:00.000Z'),dateToSubmit: getFormattedDateTime('2018-09-28T11:59:59.000Z'),maxAttempts: 1,maxScore: 25.0,// ... add additional optional property assignments});// ... Use the entity factories to mint additional entity values.constmembership=createMembership({// ...});constsession=createSession({// ...});// Create Eventconstevent=sensor.createEvent(createAssessmentEvent,{
actor,action: Action.Started,
object,
membership,
session,});// ... Create additional events and/or entity describes.// Create envelope with data payloadconstenvelope=sensor.createEnvelope({data: [event,// ... add additional events and/or entity describes],});// Delegate transmission responsibilities to clientsensor.sendToClient(client,envelope);

Sensor class

The Sensor class manages clients for interacting with a Sensor API, as well as providing a helper function for creating properly formatted Envelope objects for transmitting Caliper events.

Constructor: new Sensor(id: string, config?: SensorConfig)

Creates a new instance of a Sensor with the specified ID. Optionally takes a SensorConfig object which can provide the SoftwareApplication to include in events, a flag to enable/disable event validation, and a Record of objects that implement the Client interface, as an alternative to using the Sensor.registerClient function.

// Set application URI if using DLQCaliper.settings.applicationUri='https://example.org';constsensor1=newSensor('http://example.org/sensors/1');// With SensorConfigconstsensor2=newSensor('http://example.org/sensors/2',{edApp: createSoftwareApplication({id: 'https://example.org'}),validationEnabled: true,});// With SensorConfig including HttpClientsconstclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w','https://dlq.rad.dev.edgenuityapp.com/api/DeadletterMessage');constsensor3=newSensor('http://example.org/sensors/3',{edApp: createSoftwareApplication({id: 'https://example.org'}),validationEnabled: true,clients: {[client.getId()]: client,},});

Sensor.createEnvelope<T>(opts: EnvelopeOptions<T>): Envelope<T>

Creates a new Envelope object with the specified options, where the data field is an array of type T.

EnvelopeOptions<T> contains the following properties:

  • sensor: string: ID of the sensor
  • sendTime?: string: ISO 8601 formatted date with time (defaults to current date and time)
  • dataVersion?: string: Version of the Caliper context being used (defaults to http://purl.imsglobal.org/ctx/caliper/v1p1)
  • data?: T | T[]: Object(s) to be transmitted in the envelope, typically an Event, Entity, or combination.
constdata=sensor.createEvent(createSessionEvent,{// See documentation on creating events});constenvelope=sensor.createEnvelope<SessionEvent>({ data });console.log(envelope);/* => { sensor: 'http://example.org/sensors/1', sendTime: '2020-09-09T21:47:01.959Z', dataVersion: 'http://purl.imsglobal.org/ctx/caliper/v1p1', data: [ { "@context": "http://purl.imsglobal.org/ctx/caliper/v1p1", "id": "urn:uuid:fcd495d0-3740-4298-9bec-1154571dc211", "type": "SessionEvent", ... } ]}*/

`Sensor.createEvent<TEvent extends Event, TParams>(eventFactory: (params: TParams, edApp?: SoftwareApplication) => TEvent, params: TParams): TEvent

Creates a new event of type TEvent using the provided factory function and the SoftwareApplication object from the Sensor instance.

constclient=httpClient('http://example.org/sensors/1/clients/1','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w','https://dlq.rad.dev.edgenuityapp.com/api/DeadletterMessage');constsensor=newSensor('http://example.org/sensors/1',{edApp: createSoftwareApplication({id: 'https://example.org'}),validationEnabled: true,clients: {[client.getId()]: client,},});constevent=sensor.createEvent(createAssessmentEvent,{// ... data for AssessmentEventParams});console.log(event);/* => {	type: 'AssessmentEvent',	'@context': ['http://purl.imsglobal.org/ctx/caliper/v1p2'],	edApp: { id: 'https://example.org, type: 'SoftwareApplication'	}	...}*/

Sensor.getClient(id: string): Client

Returns the Client instance registered under the specified ID.

Sensor.getClients(): Client[]

Returns an array containing all registered Client instances.

Sensor.getId(): string

Returns the ID of the current Sensor instance.

Sensor.registerClient(client: Client): void

Adds the specified Client to the Sensor instance's collection of registered clients.

sensor.registerClient(httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint'));

Sensor.sendToClient<TEnvelope, TResponse>(client: Client | string, envelope: Envelope<T>): Promise<TResponse>

Sends the specified Envelope via the specified Client. Returns Promise<TResponse> that resolves when the HTTP request has completed.

// Register HttpClient with Sensorconstclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint');sensor.registerClient(client);// Create Envelopeconstenvelope=sensor.createEnvelope<SessionEvent>({ data });// Send via client by referencesensor.sendToClient<SessionEvent,{success: boolean}>(client,envelope).then((response)=>{console.log(response);// => { success: true }});// Or send via client by IDsensor.sendToClient<SessionEvent,{success: boolean}>('http://example.org/sensors/1/clients/2',envelope).then((response)=>{console.log(response);// => { success: true }});

Sensor.sendToClients<TEnvelope, TResponse>(envelope: Envelope<TEnvelope>): Promise<TResponse[]>

Sends the specified Envelope via all registered HttpClient instances. Returns Promise<TResponse[]> that resolves when all HTTP requests have completed.

// Register clientsconstclient1=httpClient('http://example.org/sensors/1/clients/1','https://example.edu/caliper/target/endpoint1');sensor.registerClient(client1);constclient2=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint2');sensor.registerClient(client2);// Create Envelopeconstenvelope=sensor.createEnvelope<SessionEvent>({ data });// Sends posts envelope to both endpointssensor.sendToClients<SessionEvent,{success: boolean}>(envelope).then((response)=>{console.log(response);// => [{ success: true }, { success: true }]});

Sensor.unregisterClient(id: string): void

Removes the Client instance with the specified ID from the Sensor instance's collection of registered clients.

Client interface

The Client interface defines the required functionality for posting HTTP requests to a Sensor API. Any object that implements the Client interface can be registered with the Sensor as a client. For convenience, caliper-ts includes an HttpClient class which implements the Client interface using the Fetch API. However, using the Client interface you can implement your own client using your preferred method for making HTTP requests.

The Client interface requires the following functions in the implementing class:

  • getId(): string: Returns the ID of the client.
  • send<TEnvelope, TResponse>(envelope: Envelope<TEnvelope>): Promise<TResponse>: Makes a POST request to a Sensor API endpoint with the specified Envelope as the payload. Returns a promise that resolves with the response from the endpoint. This function should also ensure that the appropriate authorization header is included with the request.

HttpClient class

The HttpClient is a complete implementation of the Client interface using the Fetch API. Depending on what browsers you need to support for your application, you may need to include an appropriate polyfill, such as whatwg-fetch. Each HttpClient is configured for a single endpoint, but multiple clients can be registered with a single sensor.

httpClient(id: string, uri: string, token?: string): HttpClient

This factory function returns a new instance of the HttpClient class, configured with the specified ID, URI, and optional access token.

// Create HttpClient that will post to https://example.edu/caliper/target/endpoint// and include the header `Authorization: Bearer 40dI6P62Q_qrWxpTk95z8w`constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w');

HttpClient.bearer(token?: string): HttpClient

Returns a new instance of HttpClient configured to include the specified bearer token in the Authorization header for any request sent with the send function.

// Create HttpClient that will post to https://example.edu/caliper/target/endpoint// and configure to include the header `Authorization: Bearer 40dI6P62Q_qrWxpTk95z8w`constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint').bearer('40dI6P62Q_qrWxpTk95z8w');

HttpClient.getId(): string

Returns the ID of the client.

constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint');constid=client.getId();console.log(id);// => "http://example.org/sensors/1/clients/2"

HttpClient.send<TEnvelope, TResponse>(envelope: TEnvelope): Promise<TResponse>

Makes a POST request to the configured Sensor API endpoint with the specified Envelope as the payload. It includes the Authorization header in the request if the client has been configured with a bearer token. Returns a promise that resolves with the parsed JSON response.

constenvelope=sensor.createEnvelope<SessionEvent>({ data });constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint2');client.send<Envelope<SessionEvent>,{success: boolean}>(envelope).then((result)=>{console.log(result);// => { "success": true }});

Note: The send function is called by the Sensor via the sendToClient and sendToClients functions. You would not invoke the send function directly in a typical application.

Entity factory functions

Caliper entities can be created through factory functions provided by the caliper-ts-models library. Each factory function takes a single parameters: a delegate, which is an object defining values for properties to be set in the entity (see the Entity Subtypes section of the Caliper Spec).

constassessment=createAssessment({dateCreated: '2016-08-01T06:00:00.000Z',dateModified: '2016-09-02T11:30:00.000Z',datePublished: '2016-08-15T09:30:00.000Z',dateToActivate: '2016-08-16T05:00:00.000Z',dateToShow: '2016-08-16T05:00:00.000Z',dateToStartOn: '2016-08-16T05:00:00.000Z',dateToSubmit: '2016-09-28T11:59:59.000Z',id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1',items: [AssessmentItem({id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/1',}),AssessmentItem({id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/2',}),AssessmentItem({id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/3',}),],maxAttempts: 2,maxScore: 15,maxSubmits: 2,name: 'Quiz One',version: '1.0',});console.log(assessment);/* => { "@context": "http://purl.imsglobal.org/ctx/caliper/v1p1", "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1", "type": "Assessment", "name": "Quiz One", "items": [ { "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/1", "type": "AssessmentItem" }, { "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/2", "type": "AssessmentItem" }, { "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/3", "type": "AssessmentItem" } ], "dateCreated": "2016-08-01T06:00:00.000Z", "dateModified": "2016-09-02T11:30:00.000Z", "datePublished": "2016-08-15T09:30:00.000Z", "dateToActivate": "2016-08-16T05:00:00.000Z", "dateToShow": "2016-08-16T05:00:00.000Z", "dateToStartOn": "2016-08-16T05:00:00.000Z", "dateToSubmit": "2016-09-28T11:59:59.000Z", "maxAttempts": 2, "maxScore": 15.0, "maxSubmits": 2, "version": "1.0"}*/

Event factory functions

Caliper events can be created through factory functions. Each factory function takes two parameters: 1) a delegate, which is an object defining values for properties to be set in the event (see the Event Subtypes section of the Caliper Spec), and 2) an optional SoftwareApplication object to use for populating the edApp property in the event.

The recommended way to create events is to use the createEvent function on the Sensor object. This function takes the factory function and delegate object as parameters, and automatically passes the SoftwareApplication object from the Sensor instance to the factory function.

constsessionEvent=sensor.createEvent(createSessionEvent,{action: Action.LoggedIn,actor: createPerson({id: 'https://example.edu/users/554433'}),object: createSoftwareApplication({id: 'https://example.edu',version: 'v2'}),session: createSession({dateCreated: '2016-11-15T10:00:00.000Z',id: 'https://example.edu/sessions/1f6442a482de72ea6ad134943812bff564a76259',startedAtTime: '2016-11-15T10:00:00.000Z',user: 'https://example.edu/users/554433',}),});console.log(sessionEvent);/* => { "@context": "http://purl.imsglobal.org/ctx/caliper/v1p1", "id": "urn:uuid:fcd495d0-3740-4298-9bec-1154571dc211", "type": "SessionEvent", "actor": { "id": "https://example.edu/users/554433", "type": "Person" }, "action": "LoggedIn", "object": { "id": "https://example.edu", "type": "SoftwareApplication", "version": "v2" }, "eventTime": "2016-11-15T10:15:00.000Z", "edApp": { "id": "https://example.edu", "type": "SoftwareApplication" }, "session": { "id": "https://example.edu/sessions/1f6442a482de72ea6ad134943812bff564a76259", "type": "Session", "user": "https://example.edu/users/554433", "dateCreated": "2016-11-15T10:00:00.000Z", "startedAtTime": "2016-11-15T10:00:00.000Z" }}*/

Utility functions

There are a handful of utility functions provided for convenience in properly formatting dates and IDs.

getFormattedDateTime(date?: Date | number | string): string

Takes an optional Date object, number (Unix timestamp), or string and returns a properly formatted ISO-8601 date and time string. If no parameter is specified, it uses the current date and time.

constdate=getFormattedDateTime('9/2/2020, 6:00:00 AM');console.log(date);// => "2020-09-02T12:00:00.000Z"

getFormattedDuration(startedAtTime: Date | string, endedAtTime: Date | string): string

Takes start and end Date objects or strings, calculates the duration between the specified dates, and returns a properly formatted ISO-8601 duration string.

constduration=getFormattedDuration('1969-07-20T02:56:00+0000','1969-07-21T17:54:00+0000');console.log(duration);// => "P0Y0M1DT14H58M0S"

getFormattedUrn(urn: URN): string

Takes a URN object which consists of a namespace ID (nid) and namespace-specific string (nss) and formats it as a URN string.

consturn=getFormattedUrn({nid: 'WNE',nss: 'GUID_OF_AWESOMENESS'});console.log(urn);// => "urn:wne:guid_of_awesomeness"

getFormattedUrnUuid(uuid?: string): string

Takes an optional UUID and formats it as a URN according to RFC-4122. If no UUID is provided, a v4 UUID will be generated with uuid.

consturn=getFormattedUrnUuid('ff9ec22a-fc59-4ae1-ae8d-2c9463ee2f8f');console.log(urn);// => "urn:uuid:ff9ec22a-fc59-4ae1-ae8d-2c9463ee2f8f"

Development

Dependencies

Dependencies in this project are managed with Yarn.You can install dependencies by running the following command in the project's root directory:

yarn

Commands

yarn build

Builds the caliper-ts library.

yarn test

Runs Jest with the --watch flag.

yarn test:ci

Runs Jest in CI mode.

yarn lint

Runs ESLint in the project.

Configuration

Code quality is set up for you with eslint using the using the @imaginelearning/eslint-config/base configuration, prettier using the @imaginelearning/prettier-config configuration, husky, and lint-staged.

Rollup

TSDX uses Rollup as a bundler and generates multiple rollup configs for various module formats and build settings. See Optimizations for details.

TypeScript

tsconfig.json is set up to interpret dom and esnext types, as well as react for jsx. Adjust according to your needs.

Optimizations

Please see the main tsdxoptimizations docs. In particular, know that you can take advantage of development-only optimizations:

// ./types/index.d.tsdeclarevar __DEV__: boolean;// inside your code...if(__DEV__){console.log('foo');}

You can also choose to install and use invariant and warning functions.

Module formats

CJS, ESModules, and UMD module formats are supported.

The appropriate paths are configured in package.json and dist/index.js accordingly. Please report if any issues are found.

Named exports

Per Palmer Group guidelines, always use named exports. Code split inside your app instead of your library.

Code generation

This repository contains events and entities that are generated with the caliper-code-generator using caliper-net as the source of truth.

About

TypeScript implementation of the IMSGlobal/caliper-js library

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

caliper-ts

The Caliper Analytics® Specification provides a structured approach to describing, collecting and exchanging learning activity data at scale. Caliper also defines an application programming interface (the Sensor API™) for marshalling and transmitting event data from instrumented applications to target endpoints for storage, analysis and use.

caliper-ts is a reference implementation of the Sensor API™ written in TypeScript, based on the caliper-js library.

NOTE: See this page for the different RAD Tailpipe service receiver endpoints https://weldnorthed.atlassian.net/wiki/spaces/TECH/pages/1242202248/RAD+Tailpipe+Endpoints

NOTE: See this page for the different Deadletter service receiver endpoints https://weldnorthed.atlassian.net/wiki/spaces/TECH/pages/131270774660/RAD+Pipeline+Service+Endpoints

NOTE: See this page for the official Caliper Specification from IMS Global https://www.imsglobal.org/spec/caliper/v1p2

Installation

The caliper-ts package is available on GitHub Package Registry. To install it, you will need to configure your project by adding a .npmrc file to the project root with the following content:

@imaginelearning:registry=https://npm.pkg.github.com

You can then install it using npm or yarn.

npm install @imaginelearning/caliper-ts

Or

yarn add @imaginelearning/caliper-ts

Caliper vocabulary

The Caliper Analytics® Specification defines a set of concepts, relationships and rules for describing learning activities. Each activity domain modeled is described in a profile. Each profile is composed of one or more Event types (e.g., AssessmentEvent, NavigationEvent). Each Event type is associated with a set of actions undertaken by learners, instructors, and others. Various Entity types representing people, groups, and resources are provided in order to better describe both the relationships established between participating entities and the contextual elements relevant to the interaction (e.g., Assessment, Attempt, CourseSection, Person).

Usage

caliper-ts provides a number of classes and factory functions to facilitate working with the Sensor API in a consistent way. Below is a basic example of configuring a sensor and sending an event, as well as more in-depth documentation of the various classes, factories, and utility functions.

Basic example

// Set application URI if using DLQCaliper.settings.applicationUri='https://example.org';// Initialize Caliper sensorconstsensor=newSensor('http://example.org/sensors/1');// Initialize and register clientconstclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w','https://dlq.rad.dev.edgenuityapp.com/api/DeadletterMessage');sensor.registerClient(client);// Set Event property values// Note: only actor and object property assignments shownconstactor=createPerson({id: 'https://example.edu/users/554433'});constobject=createAssessment({id: 'https://example.edu/terms/201801/courses/7/sections/1/assess/1',dateToStartOn: getFormattedDateTime('2018-08-16T05:00:00.000Z'),dateToSubmit: getFormattedDateTime('2018-09-28T11:59:59.000Z'),maxAttempts: 1,maxScore: 25.0,// ... add additional optional property assignments});// ... Use the entity factories to mint additional entity values.constmembership=createMembership({// ...});constsession=createSession({// ...});// Create Eventconstevent=sensor.createEvent(createAssessmentEvent,{
actor,action: Action.Started,
object,
membership,
session,});// ... Create additional events and/or entity describes.// Create envelope with data payloadconstenvelope=sensor.createEnvelope({data: [event,// ... add additional events and/or entity describes],});// Delegate transmission responsibilities to clientsensor.sendToClient(client,envelope);

Sensor class

The Sensor class manages clients for interacting with a Sensor API, as well as providing a helper function for creating properly formatted Envelope objects for transmitting Caliper events.

Constructor: new Sensor(id: string, config?: SensorConfig)

Creates a new instance of a Sensor with the specified ID. Optionally takes a SensorConfig object which can provide the SoftwareApplication to include in events, a flag to enable/disable event validation, and a Record of objects that implement the Client interface, as an alternative to using the Sensor.registerClient function.

// Set application URI if using DLQCaliper.settings.applicationUri='https://example.org';constsensor1=newSensor('http://example.org/sensors/1');// With SensorConfigconstsensor2=newSensor('http://example.org/sensors/2',{edApp: createSoftwareApplication({id: 'https://example.org'}),validationEnabled: true,});// With SensorConfig including HttpClientsconstclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w','https://dlq.rad.dev.edgenuityapp.com/api/DeadletterMessage');constsensor3=newSensor('http://example.org/sensors/3',{edApp: createSoftwareApplication({id: 'https://example.org'}),validationEnabled: true,clients: {[client.getId()]: client,},});

Sensor.createEnvelope<T>(opts: EnvelopeOptions<T>): Envelope<T>

Creates a new Envelope object with the specified options, where the data field is an array of type T.

EnvelopeOptions<T> contains the following properties:

  • sensor: string: ID of the sensor
  • sendTime?: string: ISO 8601 formatted date with time (defaults to current date and time)
  • dataVersion?: string: Version of the Caliper context being used (defaults to http://purl.imsglobal.org/ctx/caliper/v1p1)
  • data?: T | T[]: Object(s) to be transmitted in the envelope, typically an Event, Entity, or combination.
constdata=sensor.createEvent(createSessionEvent,{// See documentation on creating events});constenvelope=sensor.createEnvelope<SessionEvent>({ data });console.log(envelope);/* => { sensor: 'http://example.org/sensors/1', sendTime: '2020-09-09T21:47:01.959Z', dataVersion: 'http://purl.imsglobal.org/ctx/caliper/v1p1', data: [ { "@context": "http://purl.imsglobal.org/ctx/caliper/v1p1", "id": "urn:uuid:fcd495d0-3740-4298-9bec-1154571dc211", "type": "SessionEvent", ... } ]}*/

`Sensor.createEvent<TEvent extends Event, TParams>(eventFactory: (params: TParams, edApp?: SoftwareApplication) => TEvent, params: TParams): TEvent

Creates a new event of type TEvent using the provided factory function and the SoftwareApplication object from the Sensor instance.

constclient=httpClient('http://example.org/sensors/1/clients/1','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w','https://dlq.rad.dev.edgenuityapp.com/api/DeadletterMessage');constsensor=newSensor('http://example.org/sensors/1',{edApp: createSoftwareApplication({id: 'https://example.org'}),validationEnabled: true,clients: {[client.getId()]: client,},});constevent=sensor.createEvent(createAssessmentEvent,{// ... data for AssessmentEventParams});console.log(event);/* => {	type: 'AssessmentEvent',	'@context': ['http://purl.imsglobal.org/ctx/caliper/v1p2'],	edApp: { id: 'https://example.org, type: 'SoftwareApplication'	}	...}*/

Sensor.getClient(id: string): Client

Returns the Client instance registered under the specified ID.

Sensor.getClients(): Client[]

Returns an array containing all registered Client instances.

Sensor.getId(): string

Returns the ID of the current Sensor instance.

Sensor.registerClient(client: Client): void

Adds the specified Client to the Sensor instance's collection of registered clients.

sensor.registerClient(httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint'));

Sensor.sendToClient<TEnvelope, TResponse>(client: Client | string, envelope: Envelope<T>): Promise<TResponse>

Sends the specified Envelope via the specified Client. Returns Promise<TResponse> that resolves when the HTTP request has completed.

// Register HttpClient with Sensorconstclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint');sensor.registerClient(client);// Create Envelopeconstenvelope=sensor.createEnvelope<SessionEvent>({ data });// Send via client by referencesensor.sendToClient<SessionEvent,{success: boolean}>(client,envelope).then((response)=>{console.log(response);// => { success: true }});// Or send via client by IDsensor.sendToClient<SessionEvent,{success: boolean}>('http://example.org/sensors/1/clients/2',envelope).then((response)=>{console.log(response);// => { success: true }});

Sensor.sendToClients<TEnvelope, TResponse>(envelope: Envelope<TEnvelope>): Promise<TResponse[]>

Sends the specified Envelope via all registered HttpClient instances. Returns Promise<TResponse[]> that resolves when all HTTP requests have completed.

// Register clientsconstclient1=httpClient('http://example.org/sensors/1/clients/1','https://example.edu/caliper/target/endpoint1');sensor.registerClient(client1);constclient2=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint2');sensor.registerClient(client2);// Create Envelopeconstenvelope=sensor.createEnvelope<SessionEvent>({ data });// Sends posts envelope to both endpointssensor.sendToClients<SessionEvent,{success: boolean}>(envelope).then((response)=>{console.log(response);// => [{ success: true }, { success: true }]});

Sensor.unregisterClient(id: string): void

Removes the Client instance with the specified ID from the Sensor instance's collection of registered clients.

Client interface

The Client interface defines the required functionality for posting HTTP requests to a Sensor API. Any object that implements the Client interface can be registered with the Sensor as a client. For convenience, caliper-ts includes an HttpClient class which implements the Client interface using the Fetch API. However, using the Client interface you can implement your own client using your preferred method for making HTTP requests.

The Client interface requires the following functions in the implementing class:

  • getId(): string: Returns the ID of the client.
  • send<TEnvelope, TResponse>(envelope: Envelope<TEnvelope>): Promise<TResponse>: Makes a POST request to a Sensor API endpoint with the specified Envelope as the payload. Returns a promise that resolves with the response from the endpoint. This function should also ensure that the appropriate authorization header is included with the request.

HttpClient class

The HttpClient is a complete implementation of the Client interface using the Fetch API. Depending on what browsers you need to support for your application, you may need to include an appropriate polyfill, such as whatwg-fetch. Each HttpClient is configured for a single endpoint, but multiple clients can be registered with a single sensor.

httpClient(id: string, uri: string, token?: string): HttpClient

This factory function returns a new instance of the HttpClient class, configured with the specified ID, URI, and optional access token.

// Create HttpClient that will post to https://example.edu/caliper/target/endpoint// and include the header `Authorization: Bearer 40dI6P62Q_qrWxpTk95z8w`constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w');

HttpClient.bearer(token?: string): HttpClient

Returns a new instance of HttpClient configured to include the specified bearer token in the Authorization header for any request sent with the send function.

// Create HttpClient that will post to https://example.edu/caliper/target/endpoint// and configure to include the header `Authorization: Bearer 40dI6P62Q_qrWxpTk95z8w`constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint').bearer('40dI6P62Q_qrWxpTk95z8w');

HttpClient.getId(): string

Returns the ID of the client.

constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint');constid=client.getId();console.log(id);// => "http://example.org/sensors/1/clients/2"

HttpClient.send<TEnvelope, TResponse>(envelope: TEnvelope): Promise<TResponse>

Makes a POST request to the configured Sensor API endpoint with the specified Envelope as the payload. It includes the Authorization header in the request if the client has been configured with a bearer token. Returns a promise that resolves with the parsed JSON response.

constenvelope=sensor.createEnvelope<SessionEvent>({ data });constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint2');client.send<Envelope<SessionEvent>,{success: boolean}>(envelope).then((result)=>{console.log(result);// => { "success": true }});

Note: The send function is called by the Sensor via the sendToClient and sendToClients functions. You would not invoke the send function directly in a typical application.

Entity factory functions

Caliper entities can be created through factory functions provided by the caliper-ts-models library. Each factory function takes a single parameters: a delegate, which is an object defining values for properties to be set in the entity (see the Entity Subtypes section of the Caliper Spec).

constassessment=createAssessment({dateCreated: '2016-08-01T06:00:00.000Z',dateModified: '2016-09-02T11:30:00.000Z',datePublished: '2016-08-15T09:30:00.000Z',dateToActivate: '2016-08-16T05:00:00.000Z',dateToShow: '2016-08-16T05:00:00.000Z',dateToStartOn: '2016-08-16T05:00:00.000Z',dateToSubmit: '2016-09-28T11:59:59.000Z',id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1',items: [AssessmentItem({id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/1',}),AssessmentItem({id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/2',}),AssessmentItem({id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/3',}),],maxAttempts: 2,maxScore: 15,maxSubmits: 2,name: 'Quiz One',version: '1.0',});console.log(assessment);/* => { "@context": "http://purl.imsglobal.org/ctx/caliper/v1p1", "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1", "type": "Assessment", "name": "Quiz One", "items": [ { "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/1", "type": "AssessmentItem" }, { "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/2", "type": "AssessmentItem" }, { "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/3", "type": "AssessmentItem" } ], "dateCreated": "2016-08-01T06:00:00.000Z", "dateModified": "2016-09-02T11:30:00.000Z", "datePublished": "2016-08-15T09:30:00.000Z", "dateToActivate": "2016-08-16T05:00:00.000Z", "dateToShow": "2016-08-16T05:00:00.000Z", "dateToStartOn": "2016-08-16T05:00:00.000Z", "dateToSubmit": "2016-09-28T11:59:59.000Z", "maxAttempts": 2, "maxScore": 15.0, "maxSubmits": 2, "version": "1.0"}*/

Event factory functions

Caliper events can be created through factory functions. Each factory function takes two parameters: 1) a delegate, which is an object defining values for properties to be set in the event (see the Event Subtypes section of the Caliper Spec), and 2) an optional SoftwareApplication object to use for populating the edApp property in the event.

The recommended way to create events is to use the createEvent function on the Sensor object. This function takes the factory function and delegate object as parameters, and automatically passes the SoftwareApplication object from the Sensor instance to the factory function.

constsessionEvent=sensor.createEvent(createSessionEvent,{action: Action.LoggedIn,actor: createPerson({id: 'https://example.edu/users/554433'}),object: createSoftwareApplication({id: 'https://example.edu',version: 'v2'}),session: createSession({dateCreated: '2016-11-15T10:00:00.000Z',id: 'https://example.edu/sessions/1f6442a482de72ea6ad134943812bff564a76259',startedAtTime: '2016-11-15T10:00:00.000Z',user: 'https://example.edu/users/554433',}),});console.log(sessionEvent);/* => { "@context": "http://purl.imsglobal.org/ctx/caliper/v1p1", "id": "urn:uuid:fcd495d0-3740-4298-9bec-1154571dc211", "type": "SessionEvent", "actor": { "id": "https://example.edu/users/554433", "type": "Person" }, "action": "LoggedIn", "object": { "id": "https://example.edu", "type": "SoftwareApplication", "version": "v2" }, "eventTime": "2016-11-15T10:15:00.000Z", "edApp": { "id": "https://example.edu", "type": "SoftwareApplication" }, "session": { "id": "https://example.edu/sessions/1f6442a482de72ea6ad134943812bff564a76259", "type": "Session", "user": "https://example.edu/users/554433", "dateCreated": "2016-11-15T10:00:00.000Z", "startedAtTime": "2016-11-15T10:00:00.000Z" }}*/

Utility functions

There are a handful of utility functions provided for convenience in properly formatting dates and IDs.

getFormattedDateTime(date?: Date | number | string): string

Takes an optional Date object, number (Unix timestamp), or string and returns a properly formatted ISO-8601 date and time string. If no parameter is specified, it uses the current date and time.

constdate=getFormattedDateTime('9/2/2020, 6:00:00 AM');console.log(date);// => "2020-09-02T12:00:00.000Z"

getFormattedDuration(startedAtTime: Date | string, endedAtTime: Date | string): string

Takes start and end Date objects or strings, calculates the duration between the specified dates, and returns a properly formatted ISO-8601 duration string.

constduration=getFormattedDuration('1969-07-20T02:56:00+0000','1969-07-21T17:54:00+0000');console.log(duration);// => "P0Y0M1DT14H58M0S"

getFormattedUrn(urn: URN): string

Takes a URN object which consists of a namespace ID (nid) and namespace-specific string (nss) and formats it as a URN string.

consturn=getFormattedUrn({nid: 'WNE',nss: 'GUID_OF_AWESOMENESS'});console.log(urn);// => "urn:wne:guid_of_awesomeness"

getFormattedUrnUuid(uuid?: string): string

Takes an optional UUID and formats it as a URN according to RFC-4122. If no UUID is provided, a v4 UUID will be generated with uuid.

consturn=getFormattedUrnUuid('ff9ec22a-fc59-4ae1-ae8d-2c9463ee2f8f');console.log(urn);// => "urn:uuid:ff9ec22a-fc59-4ae1-ae8d-2c9463ee2f8f"

Development

Dependencies

Dependencies in this project are managed with Yarn.You can install dependencies by running the following command in the project's root directory:

yarn

Commands

yarn build

Builds the caliper-ts library.

yarn test

Runs Jest with the --watch flag.

yarn test:ci

Runs Jest in CI mode.

yarn lint

Runs ESLint in the project.

Configuration

Code quality is set up for you with eslint using the using the @imaginelearning/eslint-config/base configuration, prettier using the @imaginelearning/prettier-config configuration, husky, and lint-staged.

Rollup

TSDX uses Rollup as a bundler and generates multiple rollup configs for various module formats and build settings. See Optimizations for details.

TypeScript

tsconfig.json is set up to interpret dom and esnext types, as well as react for jsx. Adjust according to your needs.

Optimizations

Please see the main tsdxoptimizations docs. In particular, know that you can take advantage of development-only optimizations:

// ./types/index.d.tsdeclarevar __DEV__: boolean;// inside your code...if(__DEV__){console.log('foo');}

You can also choose to install and use invariant and warning functions.

Module formats

CJS, ESModules, and UMD module formats are supported.

The appropriate paths are configured in package.json and dist/index.js accordingly. Please report if any issues are found.

Named exports

Per Palmer Group guidelines, always use named exports. Code split inside your app instead of your library.

Code generation

This repository contains events and entities that are generated with the caliper-code-generator using caliper-net as the source of truth.

About

TypeScript implementation of the IMSGlobal/caliper-js library

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

caliper-ts

The Caliper Analytics® Specification provides a structured approach to describing, collecting and exchanging learning activity data at scale. Caliper also defines an application programming interface (the Sensor API™) for marshalling and transmitting event data from instrumented applications to target endpoints for storage, analysis and use.

caliper-ts is a reference implementation of the Sensor API™ written in TypeScript, based on the caliper-js library.

NOTE: See this page for the different RAD Tailpipe service receiver endpoints https://weldnorthed.atlassian.net/wiki/spaces/TECH/pages/1242202248/RAD+Tailpipe+Endpoints

NOTE: See this page for the different Deadletter service receiver endpoints https://weldnorthed.atlassian.net/wiki/spaces/TECH/pages/131270774660/RAD+Pipeline+Service+Endpoints

NOTE: See this page for the official Caliper Specification from IMS Global https://www.imsglobal.org/spec/caliper/v1p2

Installation

The caliper-ts package is available on GitHub Package Registry. To install it, you will need to configure your project by adding a .npmrc file to the project root with the following content:

@imaginelearning:registry=https://npm.pkg.github.com

You can then install it using npm or yarn.

npm install @imaginelearning/caliper-ts

Or

yarn add @imaginelearning/caliper-ts

Caliper vocabulary

The Caliper Analytics® Specification defines a set of concepts, relationships and rules for describing learning activities. Each activity domain modeled is described in a profile. Each profile is composed of one or more Event types (e.g., AssessmentEvent, NavigationEvent). Each Event type is associated with a set of actions undertaken by learners, instructors, and others. Various Entity types representing people, groups, and resources are provided in order to better describe both the relationships established between participating entities and the contextual elements relevant to the interaction (e.g., Assessment, Attempt, CourseSection, Person).

Usage

caliper-ts provides a number of classes and factory functions to facilitate working with the Sensor API in a consistent way. Below is a basic example of configuring a sensor and sending an event, as well as more in-depth documentation of the various classes, factories, and utility functions.

Basic example

// Set application URI if using DLQCaliper.settings.applicationUri='https://example.org';// Initialize Caliper sensorconstsensor=newSensor('http://example.org/sensors/1');// Initialize and register clientconstclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w','https://dlq.rad.dev.edgenuityapp.com/api/DeadletterMessage');sensor.registerClient(client);// Set Event property values// Note: only actor and object property assignments shownconstactor=createPerson({id: 'https://example.edu/users/554433'});constobject=createAssessment({id: 'https://example.edu/terms/201801/courses/7/sections/1/assess/1',dateToStartOn: getFormattedDateTime('2018-08-16T05:00:00.000Z'),dateToSubmit: getFormattedDateTime('2018-09-28T11:59:59.000Z'),maxAttempts: 1,maxScore: 25.0,// ... add additional optional property assignments});// ... Use the entity factories to mint additional entity values.constmembership=createMembership({// ...});constsession=createSession({// ...});// Create Eventconstevent=sensor.createEvent(createAssessmentEvent,{
actor,action: Action.Started,
object,
membership,
session,});// ... Create additional events and/or entity describes.// Create envelope with data payloadconstenvelope=sensor.createEnvelope({data: [event,// ... add additional events and/or entity describes],});// Delegate transmission responsibilities to clientsensor.sendToClient(client,envelope);

Sensor class

The Sensor class manages clients for interacting with a Sensor API, as well as providing a helper function for creating properly formatted Envelope objects for transmitting Caliper events.

Constructor: new Sensor(id: string, config?: SensorConfig)

Creates a new instance of a Sensor with the specified ID. Optionally takes a SensorConfig object which can provide the SoftwareApplication to include in events, a flag to enable/disable event validation, and a Record of objects that implement the Client interface, as an alternative to using the Sensor.registerClient function.

// Set application URI if using DLQCaliper.settings.applicationUri='https://example.org';constsensor1=newSensor('http://example.org/sensors/1');// With SensorConfigconstsensor2=newSensor('http://example.org/sensors/2',{edApp: createSoftwareApplication({id: 'https://example.org'}),validationEnabled: true,});// With SensorConfig including HttpClientsconstclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w','https://dlq.rad.dev.edgenuityapp.com/api/DeadletterMessage');constsensor3=newSensor('http://example.org/sensors/3',{edApp: createSoftwareApplication({id: 'https://example.org'}),validationEnabled: true,clients: {[client.getId()]: client,},});

Sensor.createEnvelope<T>(opts: EnvelopeOptions<T>): Envelope<T>

Creates a new Envelope object with the specified options, where the data field is an array of type T.

EnvelopeOptions<T> contains the following properties:

  • sensor: string: ID of the sensor
  • sendTime?: string: ISO 8601 formatted date with time (defaults to current date and time)
  • dataVersion?: string: Version of the Caliper context being used (defaults to http://purl.imsglobal.org/ctx/caliper/v1p1)
  • data?: T | T[]: Object(s) to be transmitted in the envelope, typically an Event, Entity, or combination.
constdata=sensor.createEvent(createSessionEvent,{// See documentation on creating events});constenvelope=sensor.createEnvelope<SessionEvent>({ data });console.log(envelope);/* => { sensor: 'http://example.org/sensors/1', sendTime: '2020-09-09T21:47:01.959Z', dataVersion: 'http://purl.imsglobal.org/ctx/caliper/v1p1', data: [ { "@context": "http://purl.imsglobal.org/ctx/caliper/v1p1", "id": "urn:uuid:fcd495d0-3740-4298-9bec-1154571dc211", "type": "SessionEvent", ... } ]}*/

`Sensor.createEvent<TEvent extends Event, TParams>(eventFactory: (params: TParams, edApp?: SoftwareApplication) => TEvent, params: TParams): TEvent

Creates a new event of type TEvent using the provided factory function and the SoftwareApplication object from the Sensor instance.

constclient=httpClient('http://example.org/sensors/1/clients/1','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w','https://dlq.rad.dev.edgenuityapp.com/api/DeadletterMessage');constsensor=newSensor('http://example.org/sensors/1',{edApp: createSoftwareApplication({id: 'https://example.org'}),validationEnabled: true,clients: {[client.getId()]: client,},});constevent=sensor.createEvent(createAssessmentEvent,{// ... data for AssessmentEventParams});console.log(event);/* => {	type: 'AssessmentEvent',	'@context': ['http://purl.imsglobal.org/ctx/caliper/v1p2'],	edApp: { id: 'https://example.org, type: 'SoftwareApplication'	}	...}*/

Sensor.getClient(id: string): Client

Returns the Client instance registered under the specified ID.

Sensor.getClients(): Client[]

Returns an array containing all registered Client instances.

Sensor.getId(): string

Returns the ID of the current Sensor instance.

Sensor.registerClient(client: Client): void

Adds the specified Client to the Sensor instance's collection of registered clients.

sensor.registerClient(httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint'));

Sensor.sendToClient<TEnvelope, TResponse>(client: Client | string, envelope: Envelope<T>): Promise<TResponse>

Sends the specified Envelope via the specified Client. Returns Promise<TResponse> that resolves when the HTTP request has completed.

// Register HttpClient with Sensorconstclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint');sensor.registerClient(client);// Create Envelopeconstenvelope=sensor.createEnvelope<SessionEvent>({ data });// Send via client by referencesensor.sendToClient<SessionEvent,{success: boolean}>(client,envelope).then((response)=>{console.log(response);// => { success: true }});// Or send via client by IDsensor.sendToClient<SessionEvent,{success: boolean}>('http://example.org/sensors/1/clients/2',envelope).then((response)=>{console.log(response);// => { success: true }});

Sensor.sendToClients<TEnvelope, TResponse>(envelope: Envelope<TEnvelope>): Promise<TResponse[]>

Sends the specified Envelope via all registered HttpClient instances. Returns Promise<TResponse[]> that resolves when all HTTP requests have completed.

// Register clientsconstclient1=httpClient('http://example.org/sensors/1/clients/1','https://example.edu/caliper/target/endpoint1');sensor.registerClient(client1);constclient2=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint2');sensor.registerClient(client2);// Create Envelopeconstenvelope=sensor.createEnvelope<SessionEvent>({ data });// Sends posts envelope to both endpointssensor.sendToClients<SessionEvent,{success: boolean}>(envelope).then((response)=>{console.log(response);// => [{ success: true }, { success: true }]});

Sensor.unregisterClient(id: string): void

Removes the Client instance with the specified ID from the Sensor instance's collection of registered clients.

Client interface

The Client interface defines the required functionality for posting HTTP requests to a Sensor API. Any object that implements the Client interface can be registered with the Sensor as a client. For convenience, caliper-ts includes an HttpClient class which implements the Client interface using the Fetch API. However, using the Client interface you can implement your own client using your preferred method for making HTTP requests.

The Client interface requires the following functions in the implementing class:

  • getId(): string: Returns the ID of the client.
  • send<TEnvelope, TResponse>(envelope: Envelope<TEnvelope>): Promise<TResponse>: Makes a POST request to a Sensor API endpoint with the specified Envelope as the payload. Returns a promise that resolves with the response from the endpoint. This function should also ensure that the appropriate authorization header is included with the request.

HttpClient class

The HttpClient is a complete implementation of the Client interface using the Fetch API. Depending on what browsers you need to support for your application, you may need to include an appropriate polyfill, such as whatwg-fetch. Each HttpClient is configured for a single endpoint, but multiple clients can be registered with a single sensor.

httpClient(id: string, uri: string, token?: string): HttpClient

This factory function returns a new instance of the HttpClient class, configured with the specified ID, URI, and optional access token.

// Create HttpClient that will post to https://example.edu/caliper/target/endpoint// and include the header `Authorization: Bearer 40dI6P62Q_qrWxpTk95z8w`constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w');

HttpClient.bearer(token?: string): HttpClient

Returns a new instance of HttpClient configured to include the specified bearer token in the Authorization header for any request sent with the send function.

// Create HttpClient that will post to https://example.edu/caliper/target/endpoint// and configure to include the header `Authorization: Bearer 40dI6P62Q_qrWxpTk95z8w`constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint').bearer('40dI6P62Q_qrWxpTk95z8w');

HttpClient.getId(): string

Returns the ID of the client.

constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint');constid=client.getId();console.log(id);// => "http://example.org/sensors/1/clients/2"

HttpClient.send<TEnvelope, TResponse>(envelope: TEnvelope): Promise<TResponse>

Makes a POST request to the configured Sensor API endpoint with the specified Envelope as the payload. It includes the Authorization header in the request if the client has been configured with a bearer token. Returns a promise that resolves with the parsed JSON response.

constenvelope=sensor.createEnvelope<SessionEvent>({ data });constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint2');client.send<Envelope<SessionEvent>,{success: boolean}>(envelope).then((result)=>{console.log(result);// => { "success": true }});

Note: The send function is called by the Sensor via the sendToClient and sendToClients functions. You would not invoke the send function directly in a typical application.

Entity factory functions

Caliper entities can be created through factory functions provided by the caliper-ts-models library. Each factory function takes a single parameters: a delegate, which is an object defining values for properties to be set in the entity (see the Entity Subtypes section of the Caliper Spec).

constassessment=createAssessment({dateCreated: '2016-08-01T06:00:00.000Z',dateModified: '2016-09-02T11:30:00.000Z',datePublished: '2016-08-15T09:30:00.000Z',dateToActivate: '2016-08-16T05:00:00.000Z',dateToShow: '2016-08-16T05:00:00.000Z',dateToStartOn: '2016-08-16T05:00:00.000Z',dateToSubmit: '2016-09-28T11:59:59.000Z',id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1',items: [AssessmentItem({id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/1',}),AssessmentItem({id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/2',}),AssessmentItem({id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/3',}),],maxAttempts: 2,maxScore: 15,maxSubmits: 2,name: 'Quiz One',version: '1.0',});console.log(assessment);/* => { "@context": "http://purl.imsglobal.org/ctx/caliper/v1p1", "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1", "type": "Assessment", "name": "Quiz One", "items": [ { "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/1", "type": "AssessmentItem" }, { "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/2", "type": "AssessmentItem" }, { "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/3", "type": "AssessmentItem" } ], "dateCreated": "2016-08-01T06:00:00.000Z", "dateModified": "2016-09-02T11:30:00.000Z", "datePublished": "2016-08-15T09:30:00.000Z", "dateToActivate": "2016-08-16T05:00:00.000Z", "dateToShow": "2016-08-16T05:00:00.000Z", "dateToStartOn": "2016-08-16T05:00:00.000Z", "dateToSubmit": "2016-09-28T11:59:59.000Z", "maxAttempts": 2, "maxScore": 15.0, "maxSubmits": 2, "version": "1.0"}*/

Event factory functions

Caliper events can be created through factory functions. Each factory function takes two parameters: 1) a delegate, which is an object defining values for properties to be set in the event (see the Event Subtypes section of the Caliper Spec), and 2) an optional SoftwareApplication object to use for populating the edApp property in the event.

The recommended way to create events is to use the createEvent function on the Sensor object. This function takes the factory function and delegate object as parameters, and automatically passes the SoftwareApplication object from the Sensor instance to the factory function.

constsessionEvent=sensor.createEvent(createSessionEvent,{action: Action.LoggedIn,actor: createPerson({id: 'https://example.edu/users/554433'}),object: createSoftwareApplication({id: 'https://example.edu',version: 'v2'}),session: createSession({dateCreated: '2016-11-15T10:00:00.000Z',id: 'https://example.edu/sessions/1f6442a482de72ea6ad134943812bff564a76259',startedAtTime: '2016-11-15T10:00:00.000Z',user: 'https://example.edu/users/554433',}),});console.log(sessionEvent);/* => { "@context": "http://purl.imsglobal.org/ctx/caliper/v1p1", "id": "urn:uuid:fcd495d0-3740-4298-9bec-1154571dc211", "type": "SessionEvent", "actor": { "id": "https://example.edu/users/554433", "type": "Person" }, "action": "LoggedIn", "object": { "id": "https://example.edu", "type": "SoftwareApplication", "version": "v2" }, "eventTime": "2016-11-15T10:15:00.000Z", "edApp": { "id": "https://example.edu", "type": "SoftwareApplication" }, "session": { "id": "https://example.edu/sessions/1f6442a482de72ea6ad134943812bff564a76259", "type": "Session", "user": "https://example.edu/users/554433", "dateCreated": "2016-11-15T10:00:00.000Z", "startedAtTime": "2016-11-15T10:00:00.000Z" }}*/

Utility functions

There are a handful of utility functions provided for convenience in properly formatting dates and IDs.

getFormattedDateTime(date?: Date | number | string): string

Takes an optional Date object, number (Unix timestamp), or string and returns a properly formatted ISO-8601 date and time string. If no parameter is specified, it uses the current date and time.

constdate=getFormattedDateTime('9/2/2020, 6:00:00 AM');console.log(date);// => "2020-09-02T12:00:00.000Z"

getFormattedDuration(startedAtTime: Date | string, endedAtTime: Date | string): string

Takes start and end Date objects or strings, calculates the duration between the specified dates, and returns a properly formatted ISO-8601 duration string.

constduration=getFormattedDuration('1969-07-20T02:56:00+0000','1969-07-21T17:54:00+0000');console.log(duration);// => "P0Y0M1DT14H58M0S"

getFormattedUrn(urn: URN): string

Takes a URN object which consists of a namespace ID (nid) and namespace-specific string (nss) and formats it as a URN string.

consturn=getFormattedUrn({nid: 'WNE',nss: 'GUID_OF_AWESOMENESS'});console.log(urn);// => "urn:wne:guid_of_awesomeness"

getFormattedUrnUuid(uuid?: string): string

Takes an optional UUID and formats it as a URN according to RFC-4122. If no UUID is provided, a v4 UUID will be generated with uuid.

consturn=getFormattedUrnUuid('ff9ec22a-fc59-4ae1-ae8d-2c9463ee2f8f');console.log(urn);// => "urn:uuid:ff9ec22a-fc59-4ae1-ae8d-2c9463ee2f8f"

Development

Dependencies

Dependencies in this project are managed with Yarn.You can install dependencies by running the following command in the project's root directory:

yarn

Commands

yarn build

Builds the caliper-ts library.

yarn test

Runs Jest with the --watch flag.

yarn test:ci

Runs Jest in CI mode.

yarn lint

Runs ESLint in the project.

Configuration

Code quality is set up for you with eslint using the using the @imaginelearning/eslint-config/base configuration, prettier using the @imaginelearning/prettier-config configuration, husky, and lint-staged.

Rollup

TSDX uses Rollup as a bundler and generates multiple rollup configs for various module formats and build settings. See Optimizations for details.

TypeScript

tsconfig.json is set up to interpret dom and esnext types, as well as react for jsx. Adjust according to your needs.

Optimizations

Please see the main tsdxoptimizations docs. In particular, know that you can take advantage of development-only optimizations:

// ./types/index.d.tsdeclarevar __DEV__: boolean;// inside your code...if(__DEV__){console.log('foo');}

You can also choose to install and use invariant and warning functions.

Module formats

CJS, ESModules, and UMD module formats are supported.

The appropriate paths are configured in package.json and dist/index.js accordingly. Please report if any issues are found.

Named exports

Per Palmer Group guidelines, always use named exports. Code split inside your app instead of your library.

Code generation

This repository contains events and entities that are generated with the caliper-code-generator using caliper-net as the source of truth.

About

TypeScript implementation of the IMSGlobal/caliper-js library

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

caliper-ts

The Caliper Analytics® Specification provides a structured approach to describing, collecting and exchanging learning activity data at scale. Caliper also defines an application programming interface (the Sensor API™) for marshalling and transmitting event data from instrumented applications to target endpoints for storage, analysis and use.

caliper-ts is a reference implementation of the Sensor API™ written in TypeScript, based on the caliper-js library.

NOTE: See this page for the different RAD Tailpipe service receiver endpoints https://weldnorthed.atlassian.net/wiki/spaces/TECH/pages/1242202248/RAD+Tailpipe+Endpoints

NOTE: See this page for the different Deadletter service receiver endpoints https://weldnorthed.atlassian.net/wiki/spaces/TECH/pages/131270774660/RAD+Pipeline+Service+Endpoints

NOTE: See this page for the official Caliper Specification from IMS Global https://www.imsglobal.org/spec/caliper/v1p2

Installation

The caliper-ts package is available on GitHub Package Registry. To install it, you will need to configure your project by adding a .npmrc file to the project root with the following content:

@imaginelearning:registry=https://npm.pkg.github.com

You can then install it using npm or yarn.

npm install @imaginelearning/caliper-ts

Or

yarn add @imaginelearning/caliper-ts

Caliper vocabulary

The Caliper Analytics® Specification defines a set of concepts, relationships and rules for describing learning activities. Each activity domain modeled is described in a profile. Each profile is composed of one or more Event types (e.g., AssessmentEvent, NavigationEvent). Each Event type is associated with a set of actions undertaken by learners, instructors, and others. Various Entity types representing people, groups, and resources are provided in order to better describe both the relationships established between participating entities and the contextual elements relevant to the interaction (e.g., Assessment, Attempt, CourseSection, Person).

Usage

caliper-ts provides a number of classes and factory functions to facilitate working with the Sensor API in a consistent way. Below is a basic example of configuring a sensor and sending an event, as well as more in-depth documentation of the various classes, factories, and utility functions.

Basic example

// Set application URI if using DLQCaliper.settings.applicationUri='https://example.org';// Initialize Caliper sensorconstsensor=newSensor('http://example.org/sensors/1');// Initialize and register clientconstclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w','https://dlq.rad.dev.edgenuityapp.com/api/DeadletterMessage');sensor.registerClient(client);// Set Event property values// Note: only actor and object property assignments shownconstactor=createPerson({id: 'https://example.edu/users/554433'});constobject=createAssessment({id: 'https://example.edu/terms/201801/courses/7/sections/1/assess/1',dateToStartOn: getFormattedDateTime('2018-08-16T05:00:00.000Z'),dateToSubmit: getFormattedDateTime('2018-09-28T11:59:59.000Z'),maxAttempts: 1,maxScore: 25.0,// ... add additional optional property assignments});// ... Use the entity factories to mint additional entity values.constmembership=createMembership({// ...});constsession=createSession({// ...});// Create Eventconstevent=sensor.createEvent(createAssessmentEvent,{
actor,action: Action.Started,
object,
membership,
session,});// ... Create additional events and/or entity describes.// Create envelope with data payloadconstenvelope=sensor.createEnvelope({data: [event,// ... add additional events and/or entity describes],});// Delegate transmission responsibilities to clientsensor.sendToClient(client,envelope);

Sensor class

The Sensor class manages clients for interacting with a Sensor API, as well as providing a helper function for creating properly formatted Envelope objects for transmitting Caliper events.

Constructor: new Sensor(id: string, config?: SensorConfig)

Creates a new instance of a Sensor with the specified ID. Optionally takes a SensorConfig object which can provide the SoftwareApplication to include in events, a flag to enable/disable event validation, and a Record of objects that implement the Client interface, as an alternative to using the Sensor.registerClient function.

// Set application URI if using DLQCaliper.settings.applicationUri='https://example.org';constsensor1=newSensor('http://example.org/sensors/1');// With SensorConfigconstsensor2=newSensor('http://example.org/sensors/2',{edApp: createSoftwareApplication({id: 'https://example.org'}),validationEnabled: true,});// With SensorConfig including HttpClientsconstclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w','https://dlq.rad.dev.edgenuityapp.com/api/DeadletterMessage');constsensor3=newSensor('http://example.org/sensors/3',{edApp: createSoftwareApplication({id: 'https://example.org'}),validationEnabled: true,clients: {[client.getId()]: client,},});

Sensor.createEnvelope<T>(opts: EnvelopeOptions<T>): Envelope<T>

Creates a new Envelope object with the specified options, where the data field is an array of type T.

EnvelopeOptions<T> contains the following properties:

  • sensor: string: ID of the sensor
  • sendTime?: string: ISO 8601 formatted date with time (defaults to current date and time)
  • dataVersion?: string: Version of the Caliper context being used (defaults to http://purl.imsglobal.org/ctx/caliper/v1p1)
  • data?: T | T[]: Object(s) to be transmitted in the envelope, typically an Event, Entity, or combination.
constdata=sensor.createEvent(createSessionEvent,{// See documentation on creating events});constenvelope=sensor.createEnvelope<SessionEvent>({ data });console.log(envelope);/* => { sensor: 'http://example.org/sensors/1', sendTime: '2020-09-09T21:47:01.959Z', dataVersion: 'http://purl.imsglobal.org/ctx/caliper/v1p1', data: [ { "@context": "http://purl.imsglobal.org/ctx/caliper/v1p1", "id": "urn:uuid:fcd495d0-3740-4298-9bec-1154571dc211", "type": "SessionEvent", ... } ]}*/

`Sensor.createEvent<TEvent extends Event, TParams>(eventFactory: (params: TParams, edApp?: SoftwareApplication) => TEvent, params: TParams): TEvent

Creates a new event of type TEvent using the provided factory function and the SoftwareApplication object from the Sensor instance.

constclient=httpClient('http://example.org/sensors/1/clients/1','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w','https://dlq.rad.dev.edgenuityapp.com/api/DeadletterMessage');constsensor=newSensor('http://example.org/sensors/1',{edApp: createSoftwareApplication({id: 'https://example.org'}),validationEnabled: true,clients: {[client.getId()]: client,},});constevent=sensor.createEvent(createAssessmentEvent,{// ... data for AssessmentEventParams});console.log(event);/* => {	type: 'AssessmentEvent',	'@context': ['http://purl.imsglobal.org/ctx/caliper/v1p2'],	edApp: { id: 'https://example.org, type: 'SoftwareApplication'	}	...}*/

Sensor.getClient(id: string): Client

Returns the Client instance registered under the specified ID.

Sensor.getClients(): Client[]

Returns an array containing all registered Client instances.

Sensor.getId(): string

Returns the ID of the current Sensor instance.

Sensor.registerClient(client: Client): void

Adds the specified Client to the Sensor instance's collection of registered clients.

sensor.registerClient(httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint'));

Sensor.sendToClient<TEnvelope, TResponse>(client: Client | string, envelope: Envelope<T>): Promise<TResponse>

Sends the specified Envelope via the specified Client. Returns Promise<TResponse> that resolves when the HTTP request has completed.

// Register HttpClient with Sensorconstclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint');sensor.registerClient(client);// Create Envelopeconstenvelope=sensor.createEnvelope<SessionEvent>({ data });// Send via client by referencesensor.sendToClient<SessionEvent,{success: boolean}>(client,envelope).then((response)=>{console.log(response);// => { success: true }});// Or send via client by IDsensor.sendToClient<SessionEvent,{success: boolean}>('http://example.org/sensors/1/clients/2',envelope).then((response)=>{console.log(response);// => { success: true }});

Sensor.sendToClients<TEnvelope, TResponse>(envelope: Envelope<TEnvelope>): Promise<TResponse[]>

Sends the specified Envelope via all registered HttpClient instances. Returns Promise<TResponse[]> that resolves when all HTTP requests have completed.

// Register clientsconstclient1=httpClient('http://example.org/sensors/1/clients/1','https://example.edu/caliper/target/endpoint1');sensor.registerClient(client1);constclient2=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint2');sensor.registerClient(client2);// Create Envelopeconstenvelope=sensor.createEnvelope<SessionEvent>({ data });// Sends posts envelope to both endpointssensor.sendToClients<SessionEvent,{success: boolean}>(envelope).then((response)=>{console.log(response);// => [{ success: true }, { success: true }]});

Sensor.unregisterClient(id: string): void

Removes the Client instance with the specified ID from the Sensor instance's collection of registered clients.

Client interface

The Client interface defines the required functionality for posting HTTP requests to a Sensor API. Any object that implements the Client interface can be registered with the Sensor as a client. For convenience, caliper-ts includes an HttpClient class which implements the Client interface using the Fetch API. However, using the Client interface you can implement your own client using your preferred method for making HTTP requests.

The Client interface requires the following functions in the implementing class:

  • getId(): string: Returns the ID of the client.
  • send<TEnvelope, TResponse>(envelope: Envelope<TEnvelope>): Promise<TResponse>: Makes a POST request to a Sensor API endpoint with the specified Envelope as the payload. Returns a promise that resolves with the response from the endpoint. This function should also ensure that the appropriate authorization header is included with the request.

HttpClient class

The HttpClient is a complete implementation of the Client interface using the Fetch API. Depending on what browsers you need to support for your application, you may need to include an appropriate polyfill, such as whatwg-fetch. Each HttpClient is configured for a single endpoint, but multiple clients can be registered with a single sensor.

httpClient(id: string, uri: string, token?: string): HttpClient

This factory function returns a new instance of the HttpClient class, configured with the specified ID, URI, and optional access token.

// Create HttpClient that will post to https://example.edu/caliper/target/endpoint// and include the header `Authorization: Bearer 40dI6P62Q_qrWxpTk95z8w`constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w');

HttpClient.bearer(token?: string): HttpClient

Returns a new instance of HttpClient configured to include the specified bearer token in the Authorization header for any request sent with the send function.

// Create HttpClient that will post to https://example.edu/caliper/target/endpoint// and configure to include the header `Authorization: Bearer 40dI6P62Q_qrWxpTk95z8w`constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint').bearer('40dI6P62Q_qrWxpTk95z8w');

HttpClient.getId(): string

Returns the ID of the client.

constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint');constid=client.getId();console.log(id);// => "http://example.org/sensors/1/clients/2"

HttpClient.send<TEnvelope, TResponse>(envelope: TEnvelope): Promise<TResponse>

Makes a POST request to the configured Sensor API endpoint with the specified Envelope as the payload. It includes the Authorization header in the request if the client has been configured with a bearer token. Returns a promise that resolves with the parsed JSON response.

constenvelope=sensor.createEnvelope<SessionEvent>({ data });constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint2');client.send<Envelope<SessionEvent>,{success: boolean}>(envelope).then((result)=>{console.log(result);// => { "success": true }});

Note: The send function is called by the Sensor via the sendToClient and sendToClients functions. You would not invoke the send function directly in a typical application.

Entity factory functions

Caliper entities can be created through factory functions provided by the caliper-ts-models library. Each factory function takes a single parameters: a delegate, which is an object defining values for properties to be set in the entity (see the Entity Subtypes section of the Caliper Spec).

constassessment=createAssessment({dateCreated: '2016-08-01T06:00:00.000Z',dateModified: '2016-09-02T11:30:00.000Z',datePublished: '2016-08-15T09:30:00.000Z',dateToActivate: '2016-08-16T05:00:00.000Z',dateToShow: '2016-08-16T05:00:00.000Z',dateToStartOn: '2016-08-16T05:00:00.000Z',dateToSubmit: '2016-09-28T11:59:59.000Z',id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1',items: [AssessmentItem({id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/1',}),AssessmentItem({id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/2',}),AssessmentItem({id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/3',}),],maxAttempts: 2,maxScore: 15,maxSubmits: 2,name: 'Quiz One',version: '1.0',});console.log(assessment);/* => { "@context": "http://purl.imsglobal.org/ctx/caliper/v1p1", "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1", "type": "Assessment", "name": "Quiz One", "items": [ { "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/1", "type": "AssessmentItem" }, { "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/2", "type": "AssessmentItem" }, { "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/3", "type": "AssessmentItem" } ], "dateCreated": "2016-08-01T06:00:00.000Z", "dateModified": "2016-09-02T11:30:00.000Z", "datePublished": "2016-08-15T09:30:00.000Z", "dateToActivate": "2016-08-16T05:00:00.000Z", "dateToShow": "2016-08-16T05:00:00.000Z", "dateToStartOn": "2016-08-16T05:00:00.000Z", "dateToSubmit": "2016-09-28T11:59:59.000Z", "maxAttempts": 2, "maxScore": 15.0, "maxSubmits": 2, "version": "1.0"}*/

Event factory functions

Caliper events can be created through factory functions. Each factory function takes two parameters: 1) a delegate, which is an object defining values for properties to be set in the event (see the Event Subtypes section of the Caliper Spec), and 2) an optional SoftwareApplication object to use for populating the edApp property in the event.

The recommended way to create events is to use the createEvent function on the Sensor object. This function takes the factory function and delegate object as parameters, and automatically passes the SoftwareApplication object from the Sensor instance to the factory function.

constsessionEvent=sensor.createEvent(createSessionEvent,{action: Action.LoggedIn,actor: createPerson({id: 'https://example.edu/users/554433'}),object: createSoftwareApplication({id: 'https://example.edu',version: 'v2'}),session: createSession({dateCreated: '2016-11-15T10:00:00.000Z',id: 'https://example.edu/sessions/1f6442a482de72ea6ad134943812bff564a76259',startedAtTime: '2016-11-15T10:00:00.000Z',user: 'https://example.edu/users/554433',}),});console.log(sessionEvent);/* => { "@context": "http://purl.imsglobal.org/ctx/caliper/v1p1", "id": "urn:uuid:fcd495d0-3740-4298-9bec-1154571dc211", "type": "SessionEvent", "actor": { "id": "https://example.edu/users/554433", "type": "Person" }, "action": "LoggedIn", "object": { "id": "https://example.edu", "type": "SoftwareApplication", "version": "v2" }, "eventTime": "2016-11-15T10:15:00.000Z", "edApp": { "id": "https://example.edu", "type": "SoftwareApplication" }, "session": { "id": "https://example.edu/sessions/1f6442a482de72ea6ad134943812bff564a76259", "type": "Session", "user": "https://example.edu/users/554433", "dateCreated": "2016-11-15T10:00:00.000Z", "startedAtTime": "2016-11-15T10:00:00.000Z" }}*/

Utility functions

There are a handful of utility functions provided for convenience in properly formatting dates and IDs.

getFormattedDateTime(date?: Date | number | string): string

Takes an optional Date object, number (Unix timestamp), or string and returns a properly formatted ISO-8601 date and time string. If no parameter is specified, it uses the current date and time.

constdate=getFormattedDateTime('9/2/2020, 6:00:00 AM');console.log(date);// => "2020-09-02T12:00:00.000Z"

getFormattedDuration(startedAtTime: Date | string, endedAtTime: Date | string): string

Takes start and end Date objects or strings, calculates the duration between the specified dates, and returns a properly formatted ISO-8601 duration string.

constduration=getFormattedDuration('1969-07-20T02:56:00+0000','1969-07-21T17:54:00+0000');console.log(duration);// => "P0Y0M1DT14H58M0S"

getFormattedUrn(urn: URN): string

Takes a URN object which consists of a namespace ID (nid) and namespace-specific string (nss) and formats it as a URN string.

consturn=getFormattedUrn({nid: 'WNE',nss: 'GUID_OF_AWESOMENESS'});console.log(urn);// => "urn:wne:guid_of_awesomeness"

getFormattedUrnUuid(uuid?: string): string

Takes an optional UUID and formats it as a URN according to RFC-4122. If no UUID is provided, a v4 UUID will be generated with uuid.

consturn=getFormattedUrnUuid('ff9ec22a-fc59-4ae1-ae8d-2c9463ee2f8f');console.log(urn);// => "urn:uuid:ff9ec22a-fc59-4ae1-ae8d-2c9463ee2f8f"

Development

Dependencies

Dependencies in this project are managed with Yarn.You can install dependencies by running the following command in the project's root directory:

yarn

Commands

yarn build

Builds the caliper-ts library.

yarn test

Runs Jest with the --watch flag.

yarn test:ci

Runs Jest in CI mode.

yarn lint

Runs ESLint in the project.

Configuration

Code quality is set up for you with eslint using the using the @imaginelearning/eslint-config/base configuration, prettier using the @imaginelearning/prettier-config configuration, husky, and lint-staged.

Rollup

TSDX uses Rollup as a bundler and generates multiple rollup configs for various module formats and build settings. See Optimizations for details.

TypeScript

tsconfig.json is set up to interpret dom and esnext types, as well as react for jsx. Adjust according to your needs.

Optimizations

Please see the main tsdxoptimizations docs. In particular, know that you can take advantage of development-only optimizations:

// ./types/index.d.tsdeclarevar __DEV__: boolean;// inside your code...if(__DEV__){console.log('foo');}

You can also choose to install and use invariant and warning functions.

Module formats

CJS, ESModules, and UMD module formats are supported.

The appropriate paths are configured in package.json and dist/index.js accordingly. Please report if any issues are found.

Named exports

Per Palmer Group guidelines, always use named exports. Code split inside your app instead of your library.

Code generation

This repository contains events and entities that are generated with the caliper-code-generator using caliper-net as the source of truth.

About

TypeScript implementation of the IMSGlobal/caliper-js library

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

caliper-ts

The Caliper Analytics® Specification provides a structured approach to describing, collecting and exchanging learning activity data at scale. Caliper also defines an application programming interface (the Sensor API™) for marshalling and transmitting event data from instrumented applications to target endpoints for storage, analysis and use.

caliper-ts is a reference implementation of the Sensor API™ written in TypeScript, based on the caliper-js library.

NOTE: See this page for the different RAD Tailpipe service receiver endpoints https://weldnorthed.atlassian.net/wiki/spaces/TECH/pages/1242202248/RAD+Tailpipe+Endpoints

NOTE: See this page for the different Deadletter service receiver endpoints https://weldnorthed.atlassian.net/wiki/spaces/TECH/pages/131270774660/RAD+Pipeline+Service+Endpoints

NOTE: See this page for the official Caliper Specification from IMS Global https://www.imsglobal.org/spec/caliper/v1p2

Installation

The caliper-ts package is available on GitHub Package Registry. To install it, you will need to configure your project by adding a .npmrc file to the project root with the following content:

@imaginelearning:registry=https://npm.pkg.github.com

You can then install it using npm or yarn.

npm install @imaginelearning/caliper-ts

Or

yarn add @imaginelearning/caliper-ts

Caliper vocabulary

The Caliper Analytics® Specification defines a set of concepts, relationships and rules for describing learning activities. Each activity domain modeled is described in a profile. Each profile is composed of one or more Event types (e.g., AssessmentEvent, NavigationEvent). Each Event type is associated with a set of actions undertaken by learners, instructors, and others. Various Entity types representing people, groups, and resources are provided in order to better describe both the relationships established between participating entities and the contextual elements relevant to the interaction (e.g., Assessment, Attempt, CourseSection, Person).

Usage

caliper-ts provides a number of classes and factory functions to facilitate working with the Sensor API in a consistent way. Below is a basic example of configuring a sensor and sending an event, as well as more in-depth documentation of the various classes, factories, and utility functions.

Basic example

// Set application URI if using DLQCaliper.settings.applicationUri='https://example.org';// Initialize Caliper sensorconstsensor=newSensor('http://example.org/sensors/1');// Initialize and register clientconstclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w','https://dlq.rad.dev.edgenuityapp.com/api/DeadletterMessage');sensor.registerClient(client);// Set Event property values// Note: only actor and object property assignments shownconstactor=createPerson({id: 'https://example.edu/users/554433'});constobject=createAssessment({id: 'https://example.edu/terms/201801/courses/7/sections/1/assess/1',dateToStartOn: getFormattedDateTime('2018-08-16T05:00:00.000Z'),dateToSubmit: getFormattedDateTime('2018-09-28T11:59:59.000Z'),maxAttempts: 1,maxScore: 25.0,// ... add additional optional property assignments});// ... Use the entity factories to mint additional entity values.constmembership=createMembership({// ...});constsession=createSession({// ...});// Create Eventconstevent=sensor.createEvent(createAssessmentEvent,{
actor,action: Action.Started,
object,
membership,
session,});// ... Create additional events and/or entity describes.// Create envelope with data payloadconstenvelope=sensor.createEnvelope({data: [event,// ... add additional events and/or entity describes],});// Delegate transmission responsibilities to clientsensor.sendToClient(client,envelope);

Sensor class

The Sensor class manages clients for interacting with a Sensor API, as well as providing a helper function for creating properly formatted Envelope objects for transmitting Caliper events.

Constructor: new Sensor(id: string, config?: SensorConfig)

Creates a new instance of a Sensor with the specified ID. Optionally takes a SensorConfig object which can provide the SoftwareApplication to include in events, a flag to enable/disable event validation, and a Record of objects that implement the Client interface, as an alternative to using the Sensor.registerClient function.

// Set application URI if using DLQCaliper.settings.applicationUri='https://example.org';constsensor1=newSensor('http://example.org/sensors/1');// With SensorConfigconstsensor2=newSensor('http://example.org/sensors/2',{edApp: createSoftwareApplication({id: 'https://example.org'}),validationEnabled: true,});// With SensorConfig including HttpClientsconstclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w','https://dlq.rad.dev.edgenuityapp.com/api/DeadletterMessage');constsensor3=newSensor('http://example.org/sensors/3',{edApp: createSoftwareApplication({id: 'https://example.org'}),validationEnabled: true,clients: {[client.getId()]: client,},});

Sensor.createEnvelope<T>(opts: EnvelopeOptions<T>): Envelope<T>

Creates a new Envelope object with the specified options, where the data field is an array of type T.

EnvelopeOptions<T> contains the following properties:

  • sensor: string: ID of the sensor
  • sendTime?: string: ISO 8601 formatted date with time (defaults to current date and time)
  • dataVersion?: string: Version of the Caliper context being used (defaults to http://purl.imsglobal.org/ctx/caliper/v1p1)
  • data?: T | T[]: Object(s) to be transmitted in the envelope, typically an Event, Entity, or combination.
constdata=sensor.createEvent(createSessionEvent,{// See documentation on creating events});constenvelope=sensor.createEnvelope<SessionEvent>({ data });console.log(envelope);/* => { sensor: 'http://example.org/sensors/1', sendTime: '2020-09-09T21:47:01.959Z', dataVersion: 'http://purl.imsglobal.org/ctx/caliper/v1p1', data: [ { "@context": "http://purl.imsglobal.org/ctx/caliper/v1p1", "id": "urn:uuid:fcd495d0-3740-4298-9bec-1154571dc211", "type": "SessionEvent", ... } ]}*/

`Sensor.createEvent<TEvent extends Event, TParams>(eventFactory: (params: TParams, edApp?: SoftwareApplication) => TEvent, params: TParams): TEvent

Creates a new event of type TEvent using the provided factory function and the SoftwareApplication object from the Sensor instance.

constclient=httpClient('http://example.org/sensors/1/clients/1','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w','https://dlq.rad.dev.edgenuityapp.com/api/DeadletterMessage');constsensor=newSensor('http://example.org/sensors/1',{edApp: createSoftwareApplication({id: 'https://example.org'}),validationEnabled: true,clients: {[client.getId()]: client,},});constevent=sensor.createEvent(createAssessmentEvent,{// ... data for AssessmentEventParams});console.log(event);/* => {	type: 'AssessmentEvent',	'@context': ['http://purl.imsglobal.org/ctx/caliper/v1p2'],	edApp: { id: 'https://example.org, type: 'SoftwareApplication'	}	...}*/

Sensor.getClient(id: string): Client

Returns the Client instance registered under the specified ID.

Sensor.getClients(): Client[]

Returns an array containing all registered Client instances.

Sensor.getId(): string

Returns the ID of the current Sensor instance.

Sensor.registerClient(client: Client): void

Adds the specified Client to the Sensor instance's collection of registered clients.

sensor.registerClient(httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint'));

Sensor.sendToClient<TEnvelope, TResponse>(client: Client | string, envelope: Envelope<T>): Promise<TResponse>

Sends the specified Envelope via the specified Client. Returns Promise<TResponse> that resolves when the HTTP request has completed.

// Register HttpClient with Sensorconstclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint');sensor.registerClient(client);// Create Envelopeconstenvelope=sensor.createEnvelope<SessionEvent>({ data });// Send via client by referencesensor.sendToClient<SessionEvent,{success: boolean}>(client,envelope).then((response)=>{console.log(response);// => { success: true }});// Or send via client by IDsensor.sendToClient<SessionEvent,{success: boolean}>('http://example.org/sensors/1/clients/2',envelope).then((response)=>{console.log(response);// => { success: true }});

Sensor.sendToClients<TEnvelope, TResponse>(envelope: Envelope<TEnvelope>): Promise<TResponse[]>

Sends the specified Envelope via all registered HttpClient instances. Returns Promise<TResponse[]> that resolves when all HTTP requests have completed.

// Register clientsconstclient1=httpClient('http://example.org/sensors/1/clients/1','https://example.edu/caliper/target/endpoint1');sensor.registerClient(client1);constclient2=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint2');sensor.registerClient(client2);// Create Envelopeconstenvelope=sensor.createEnvelope<SessionEvent>({ data });// Sends posts envelope to both endpointssensor.sendToClients<SessionEvent,{success: boolean}>(envelope).then((response)=>{console.log(response);// => [{ success: true }, { success: true }]});

Sensor.unregisterClient(id: string): void

Removes the Client instance with the specified ID from the Sensor instance's collection of registered clients.

Client interface

The Client interface defines the required functionality for posting HTTP requests to a Sensor API. Any object that implements the Client interface can be registered with the Sensor as a client. For convenience, caliper-ts includes an HttpClient class which implements the Client interface using the Fetch API. However, using the Client interface you can implement your own client using your preferred method for making HTTP requests.

The Client interface requires the following functions in the implementing class:

  • getId(): string: Returns the ID of the client.
  • send<TEnvelope, TResponse>(envelope: Envelope<TEnvelope>): Promise<TResponse>: Makes a POST request to a Sensor API endpoint with the specified Envelope as the payload. Returns a promise that resolves with the response from the endpoint. This function should also ensure that the appropriate authorization header is included with the request.

HttpClient class

The HttpClient is a complete implementation of the Client interface using the Fetch API. Depending on what browsers you need to support for your application, you may need to include an appropriate polyfill, such as whatwg-fetch. Each HttpClient is configured for a single endpoint, but multiple clients can be registered with a single sensor.

httpClient(id: string, uri: string, token?: string): HttpClient

This factory function returns a new instance of the HttpClient class, configured with the specified ID, URI, and optional access token.

// Create HttpClient that will post to https://example.edu/caliper/target/endpoint// and include the header `Authorization: Bearer 40dI6P62Q_qrWxpTk95z8w`constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint','40dI6P62Q_qrWxpTk95z8w');

HttpClient.bearer(token?: string): HttpClient

Returns a new instance of HttpClient configured to include the specified bearer token in the Authorization header for any request sent with the send function.

// Create HttpClient that will post to https://example.edu/caliper/target/endpoint// and configure to include the header `Authorization: Bearer 40dI6P62Q_qrWxpTk95z8w`constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint').bearer('40dI6P62Q_qrWxpTk95z8w');

HttpClient.getId(): string

Returns the ID of the client.

constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint');constid=client.getId();console.log(id);// => "http://example.org/sensors/1/clients/2"

HttpClient.send<TEnvelope, TResponse>(envelope: TEnvelope): Promise<TResponse>

Makes a POST request to the configured Sensor API endpoint with the specified Envelope as the payload. It includes the Authorization header in the request if the client has been configured with a bearer token. Returns a promise that resolves with the parsed JSON response.

constenvelope=sensor.createEnvelope<SessionEvent>({ data });constclient=httpClient('http://example.org/sensors/1/clients/2','https://example.edu/caliper/target/endpoint2');client.send<Envelope<SessionEvent>,{success: boolean}>(envelope).then((result)=>{console.log(result);// => { "success": true }});

Note: The send function is called by the Sensor via the sendToClient and sendToClients functions. You would not invoke the send function directly in a typical application.

Entity factory functions

Caliper entities can be created through factory functions provided by the caliper-ts-models library. Each factory function takes a single parameters: a delegate, which is an object defining values for properties to be set in the entity (see the Entity Subtypes section of the Caliper Spec).

constassessment=createAssessment({dateCreated: '2016-08-01T06:00:00.000Z',dateModified: '2016-09-02T11:30:00.000Z',datePublished: '2016-08-15T09:30:00.000Z',dateToActivate: '2016-08-16T05:00:00.000Z',dateToShow: '2016-08-16T05:00:00.000Z',dateToStartOn: '2016-08-16T05:00:00.000Z',dateToSubmit: '2016-09-28T11:59:59.000Z',id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1',items: [AssessmentItem({id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/1',}),AssessmentItem({id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/2',}),AssessmentItem({id: 'https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/3',}),],maxAttempts: 2,maxScore: 15,maxSubmits: 2,name: 'Quiz One',version: '1.0',});console.log(assessment);/* => { "@context": "http://purl.imsglobal.org/ctx/caliper/v1p1", "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1", "type": "Assessment", "name": "Quiz One", "items": [ { "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/1", "type": "AssessmentItem" }, { "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/2", "type": "AssessmentItem" }, { "id": "https://example.edu/terms/201601/courses/7/sections/1/assess/1/items/3", "type": "AssessmentItem" } ], "dateCreated": "2016-08-01T06:00:00.000Z", "dateModified": "2016-09-02T11:30:00.000Z", "datePublished": "2016-08-15T09:30:00.000Z", "dateToActivate": "2016-08-16T05:00:00.000Z", "dateToShow": "2016-08-16T05:00:00.000Z", "dateToStartOn": "2016-08-16T05:00:00.000Z", "dateToSubmit": "2016-09-28T11:59:59.000Z", "maxAttempts": 2, "maxScore": 15.0, "maxSubmits": 2, "version": "1.0"}*/

Event factory functions

Caliper events can be created through factory functions. Each factory function takes two parameters: 1) a delegate, which is an object defining values for properties to be set in the event (see the Event Subtypes section of the Caliper Spec), and 2) an optional SoftwareApplication object to use for populating the edApp property in the event.

The recommended way to create events is to use the createEvent function on the Sensor object. This function takes the factory function and delegate object as parameters, and automatically passes the SoftwareApplication object from the Sensor instance to the factory function.

constsessionEvent=sensor.createEvent(createSessionEvent,{action: Action.LoggedIn,actor: createPerson({id: 'https://example.edu/users/554433'}),object: createSoftwareApplication({id: 'https://example.edu',version: 'v2'}),session: createSession({dateCreated: '2016-11-15T10:00:00.000Z',id: 'https://example.edu/sessions/1f6442a482de72ea6ad134943812bff564a76259',startedAtTime: '2016-11-15T10:00:00.000Z',user: 'https://example.edu/users/554433',}),});console.log(sessionEvent);/* => { "@context": "http://purl.imsglobal.org/ctx/caliper/v1p1", "id": "urn:uuid:fcd495d0-3740-4298-9bec-1154571dc211", "type": "SessionEvent", "actor": { "id": "https://example.edu/users/554433", "type": "Person" }, "action": "LoggedIn", "object": { "id": "https://example.edu", "type": "SoftwareApplication", "version": "v2" }, "eventTime": "2016-11-15T10:15:00.000Z", "edApp": { "id": "https://example.edu", "type": "SoftwareApplication" }, "session": { "id": "https://example.edu/sessions/1f6442a482de72ea6ad134943812bff564a76259", "type": "Session", "user": "https://example.edu/users/554433", "dateCreated": "2016-11-15T10:00:00.000Z", "startedAtTime": "2016-11-15T10:00:00.000Z" }}*/

Utility functions

There are a handful of utility functions provided for convenience in properly formatting dates and IDs.

getFormattedDateTime(date?: Date | number | string): string

Takes an optional Date object, number (Unix timestamp), or string and returns a properly formatted ISO-8601 date and time string. If no parameter is specified, it uses the current date and time.

constdate=getFormattedDateTime('9/2/2020, 6:00:00 AM');console.log(date);// => "2020-09-02T12:00:00.000Z"

getFormattedDuration(startedAtTime: Date | string, endedAtTime: Date | string): string

Takes start and end Date objects or strings, calculates the duration between the specified dates, and returns a properly formatted ISO-8601 duration string.

constduration=getFormattedDuration('1969-07-20T02:56:00+0000','1969-07-21T17:54:00+0000');console.log(duration);// => "P0Y0M1DT14H58M0S"

getFormattedUrn(urn: URN): string

Takes a URN object which consists of a namespace ID (nid) and namespace-specific string (nss) and formats it as a URN string.

consturn=getFormattedUrn({nid: 'WNE',nss: 'GUID_OF_AWESOMENESS'});console.log(urn);// => "urn:wne:guid_of_awesomeness"

getFormattedUrnUuid(uuid?: string): string

Takes an optional UUID and formats it as a URN according to RFC-4122. If no UUID is provided, a v4 UUID will be generated with uuid.

consturn=getFormattedUrnUuid('ff9ec22a-fc59-4ae1-ae8d-2c9463ee2f8f');console.log(urn);// => "urn:uuid:ff9ec22a-fc59-4ae1-ae8d-2c9463ee2f8f"

Development

Dependencies

Dependencies in this project are managed with Yarn.You can install dependencies by running the following command in the project's root directory:

yarn

Commands

yarn build

Builds the caliper-ts library.

yarn test

Runs Jest with the --watch flag.

yarn test:ci

Runs Jest in CI mode.

yarn lint

Runs ESLint in the project.

Configuration

Code quality is set up for you with eslint using the using the @imaginelearning/eslint-config/base configuration, prettier using the @imaginelearning/prettier-config configuration, husky, and lint-staged.

Rollup

TSDX uses Rollup as a bundler and generates multiple rollup configs for various module formats and build settings. See Optimizations for details.

TypeScript

tsconfig.json is set up to interpret dom and esnext types, as well as react for jsx. Adjust according to your needs.

Optimizations

Please see the main tsdxoptimizations docs. In particular, know that you can take advantage of development-only optimizations:

// ./types/index.d.tsdeclarevar __DEV__: boolean;// inside your code...if(__DEV__){console.log('foo');}

You can also choose to install and use invariant and warning functions.

Module formats

CJS, ESModules, and UMD module formats are supported.

The appropriate paths are configured in package.json and dist/index.js accordingly. Please report if any issues are found.

Named exports

Per Palmer Group guidelines, always use named exports. Code split inside your app instead of your library.

Code generation

This repository contains events and entities that are generated with the caliper-code-generator using caliper-net as the source of truth.

About

TypeScript implementation of the IMSGlobal/caliper-js library

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages