fix: make sequenceDB typesafe

This commit is contained in:
Aakansha Doshi 2024-03-29 13:29:31 +05:30
parent 8e95c4db55
commit 3567308a22
1 changed files with 168 additions and 71 deletions

View File

@ -12,7 +12,60 @@ import {
} from '../common/commonDb.js';
import { ImperativeState } from '../../utils/imperativeState.js';
const state = new ImperativeState(() => ({
type Box = {
name: string;
wrap: boolean;
fill: string;
actorKeys: string[];
};
type Actor = {
box?: Box;
name: string;
description: string;
wrap: boolean;
prevActor?: string;
nextActor?: string;
links: Record<string, unknown>;
properties: Record<string, unknown>;
actorCnt: number | null;
rectData: unknown;
type: string;
};
type Message = {
from?: { actor: string };
to?: { actor: string };
message?:
| string
| {
start: number;
step: number;
visible: boolean;
};
wrap: boolean;
answer?: unknown;
type?: number;
activate?: boolean;
placement?: string;
};
type State = {
prevActor?: string;
actors: Record<string, Actor>;
createdActors: Record<string, number>;
destroyedActors: Record<string, number>;
boxes: Box[];
messages: Message[];
notes: unknown[];
sequenceNumbersEnabled: boolean;
wrapEnabled?: boolean;
currentBox?: Box;
lastCreated?: Actor;
lastDestroyed?: Actor;
};
const state = new ImperativeState<State>(() => ({
prevActor: undefined,
actors: {},
createdActors: {},
@ -27,7 +80,7 @@ const state = new ImperativeState(() => ({
lastDestroyed: undefined,
}));
export const addBox = function (data) {
export const addBox = function (data: { text: string; color: string; wrap: boolean }) {
state.records.boxes.push({
name: data.text,
wrap: (data.wrap === undefined && autoWrap()) || !!data.wrap,
@ -37,20 +90,19 @@ export const addBox = function (data) {
state.records.currentBox = state.records.boxes.slice(-1)[0];
};
export const addActor = function (id, name, description, type) {
export const addActor = function (
id: string,
name: string,
description: { text: string; wrap?: boolean | null; type: string },
type: string
) {
let assignedBox = state.records.currentBox;
const old = state.records.actors[id];
if (old) {
// If already set and trying to set to a new one throw error
if (state.records.currentBox && old.box && state.records.currentBox !== old.box) {
throw new Error(
'A same participant should only be defined in one Box: ' +
old.name +
" can't be in '" +
old.box.name +
"' and in '" +
state.records.currentBox.name +
"' at the same time."
`A same participant should only be defined in one Box: ${old.name} can't be in '${old.box.name}' and in '${state.records.currentBox.name}' at the same time.`
);
}
@ -94,19 +146,19 @@ export const addActor = function (id, name, description, type) {
state.records.prevActor = id;
};
const activationCount = (part) => {
const activationCount = (part?: string) => {
let i;
let count = 0;
for (i = 0; i < state.records.messages.length; i++) {
if (
state.records.messages[i].type === LINETYPE.ACTIVE_START &&
state.records.messages[i].from.actor === part
state.records.messages[i].from?.actor === part
) {
count++;
}
if (
state.records.messages[i].type === LINETYPE.ACTIVE_END &&
state.records.messages[i].from.actor === part
state.records.messages[i].from?.actor === part
) {
count--;
}
@ -114,7 +166,12 @@ const activationCount = (part) => {
return count;
};
export const addMessage = function (idFrom, idTo, message, answer) {
export const addMessage = function (
idFrom: Message['from'],
idTo: Message['to'],
message: { text: string; wrap?: boolean },
answer: Message['answer']
) {
state.records.messages.push({
from: idFrom,
to: idTo,
@ -125,17 +182,21 @@ export const addMessage = function (idFrom, idTo, message, answer) {
};
export const addSignal = function (
idFrom,
idTo,
message = { text: undefined, wrap: undefined },
messageType,
activate = false
idFrom?: Message['from'],
idTo?: Message['to'],
message?: { text: string; wrap?: boolean },
messageType?: number,
activate: boolean = false
) {
if (messageType === LINETYPE.ACTIVE_END) {
const cnt = activationCount(idFrom.actor);
const cnt = activationCount(idFrom?.actor);
if (cnt < 1) {
// Bail out as there is an activation signal from an inactive participant
let error = new Error('Trying to inactivate an inactive participant (' + idFrom.actor + ')');
const error = new Error(
'Trying to inactivate an inactive participant (' + idFrom?.actor + ')'
);
// @ts-ignore: we are passing hash param to the error object, however we should define our own custom error class to make it type safe
error.hash = {
text: '->>-',
token: '->>-',
@ -149,8 +210,8 @@ export const addSignal = function (
state.records.messages.push({
from: idFrom,
to: idTo,
message: message.text,
wrap: (message.wrap === undefined && autoWrap()) || !!message.wrap,
message: message?.text,
wrap: (message?.wrap === undefined && autoWrap()) || !!message?.wrap,
type: messageType,
activate,
});
@ -181,7 +242,7 @@ export const getCreatedActors = function () {
export const getDestroyedActors = function () {
return state.records.destroyedActors;
};
export const getActor = function (id) {
export const getActor = function (id: string) {
return state.records.actors[id];
};
export const getActorKeys = function () {
@ -195,7 +256,7 @@ export const disableSequenceNumbers = function () {
};
export const showSequenceNumbers = () => state.records.sequenceNumbersEnabled;
export const setWrap = function (wrapSetting) {
export const setWrap = function (wrapSetting: boolean) {
state.records.wrapEnabled = wrapSetting;
};
@ -205,7 +266,7 @@ export const autoWrap = () => {
if (state.records.wrapEnabled !== undefined) {
return state.records.wrapEnabled;
}
return getConfig().sequence.wrap;
return getConfig()?.sequence?.wrap;
};
export const clear = function () {
@ -213,25 +274,25 @@ export const clear = function () {
commonClear();
};
export const parseMessage = function (str) {
const _str = str.trim();
export const parseMessage = function (str: string) {
const trimmedStr = str.trim();
const message = {
text: _str.replace(/^:?(?:no)?wrap:/, '').trim(),
text: trimmedStr.replace(/^:?(?:no)?wrap:/, '').trim(),
wrap:
_str.match(/^:?wrap:/) !== null
trimmedStr.match(/^:?wrap:/) !== null
? true
: _str.match(/^:?nowrap:/) !== null
: trimmedStr.match(/^:?nowrap:/) !== null
? false
: undefined,
};
log.debug('parseMessage:', message);
log.debug(`parseMessage: ${message}`);
return message;
};
// We expect the box statement to be color first then description
// The color can be rgb,rgba,hsl,hsla, or css code names #hex codes are not supported for now because of the way the char # is handled
// We extract first segment as color, the rest of the line is considered as text
export const parseBoxData = function (str) {
export const parseBoxData = function (str: string) {
const match = str.match(/^((?:rgba?|hsla?)\s*\(.*\)|\w*)(.*)$/);
let color = match != null && match[1] ? match[1].trim() : 'transparent';
let title = match != null && match[2] ? match[2].trim() : undefined;
@ -312,7 +373,11 @@ export const PLACEMENT = {
OVER: 2,
};
export const addNote = function (actor, placement, message) {
export const addNote = function (
actor: { actor: string },
placement: Message['placement'],
message: { text: string; wrap?: boolean }
) {
const note = {
actor: actor,
placement: placement,
@ -322,7 +387,7 @@ export const addNote = function (actor, placement, message) {
// Coerce actor into a [to, from, ...] array
// eslint-disable-next-line unicorn/prefer-spread
const actors = [].concat(actor, actor);
const actors = [actor, actor];
state.records.notes.push(note);
state.records.messages.push({
@ -335,7 +400,7 @@ export const addNote = function (actor, placement, message) {
});
};
export const addLinks = function (actorId, text) {
export const addLinks = function (actorId: string, text: { text: string }) {
// find the actor
const actor = getActor(actorId);
// JSON.parse the text
@ -351,17 +416,17 @@ export const addLinks = function (actorId, text) {
}
};
export const addALink = function (actorId, text) {
export const addALink = function (actorId: string, text: { text: string }) {
// find the actor
const actor = getActor(actorId);
try {
const links = {};
const links: { [key: string]: string } = {};
let sanitizedText = sanitizeText(text.text, getConfig());
var sep = sanitizedText.indexOf('@');
const sep = sanitizedText.indexOf('@');
sanitizedText = sanitizedText.replace(/&amp;/g, '&');
sanitizedText = sanitizedText.replace(/&equals;/g, '=');
var label = sanitizedText.slice(0, sep - 1).trim();
var link = sanitizedText.slice(sep + 1).trim();
const label = sanitizedText.slice(0, sep - 1).trim();
const link = sanitizedText.slice(sep + 1).trim();
links[label] = link;
// add the deserialized text to the actor's links field.
@ -372,26 +437,26 @@ export const addALink = function (actorId, text) {
};
/**
* @param {any} actor
* @param {any} links
* @param actor - the actor to add the links to
* @param links - the links to add to the actor
*/
function insertLinks(actor, links) {
function insertLinks(actor: Actor, links: Record<string, string>) {
if (actor.links == null) {
actor.links = links;
} else {
for (let key in links) {
for (const key in links) {
actor.links[key] = links[key];
}
}
}
export const addProperties = function (actorId, text) {
export const addProperties = function (actorId: string, text: { text: string }) {
// find the actor
const actor = getActor(actorId);
// JSON.parse the text
try {
let sanitizedText = sanitizeText(text.text, getConfig());
const properties = JSON.parse(sanitizedText);
const sanitizedText = sanitizeText(text.text, getConfig());
const properties: Record<string, unknown> = JSON.parse(sanitizedText);
// add the deserialized text to the actor's property field.
insertProperties(actor, properties);
} catch (e) {
@ -400,30 +465,27 @@ export const addProperties = function (actorId, text) {
};
/**
* @param {any} actor
* @param {any} properties
* @param actor - the actor to add the properties to
* @param properties - the properties to add to the actor's properties
*/
function insertProperties(actor, properties) {
function insertProperties(actor: Actor, properties: Record<string, unknown>) {
if (actor.properties == null) {
actor.properties = properties;
} else {
for (let key in properties) {
for (const key in properties) {
actor.properties[key] = properties[key];
}
}
}
/**
*
*/
function boxEnd() {
state.records.currentBox = undefined;
}
export const addDetails = function (actorId, text) {
export const addDetails = function (actorId: string, text: { text: string }) {
// find the actor
const actor = getActor(actorId);
const elem = document.getElementById(text.text);
const elem = document.getElementById(text.text)!;
// JSON.parse the text
try {
@ -442,7 +504,7 @@ export const addDetails = function (actorId, text) {
}
};
export const getActorProperty = function (actor, key) {
export const getActorProperty = function (actor: Actor, key: string) {
if (actor !== undefined && actor.properties !== undefined) {
return actor.properties[key];
}
@ -450,20 +512,55 @@ export const getActorProperty = function (actor, key) {
return undefined;
};
/**
* @typedef {object} AddMessageParams A message from one actor to another.
* @property {string} from - The id of the actor sending the message.
* @property {string} to - The id of the actor receiving the message.
* @property {string} msg - The message text.
* @property {number} signalType - The type of signal.
* @property {"addMessage"} type - Set to `"addMessage"` if this is an `AddMessageParams`.
* @property {boolean} [activate] - If `true`, this signal starts an activation.
*/
type AddMessageParams = {
from: string;
to: string;
msg: string;
signalType: number;
type:
| 'addMessage'
| 'sequenceIndex'
| 'addParticipant'
| 'createParticipant'
| 'destroyParticipant'
| 'activeStart'
| 'activeEnd'
| 'addNote'
| 'addLinks'
| 'addALink'
| 'addProperties'
| 'addDetails'
| 'boxStart'
| 'boxEnd'
| 'loopStart'
| 'loopEnd'
| 'rectStart'
| 'rectEnd'
| 'optStart'
| 'optEnd'
| 'altStart'
| 'else'
| 'altEnd'
| 'setAccTitle'
| 'parStart'
| 'parAnd'
| 'parEnd'
| 'and'
| 'criticalStart'
| 'criticalOption'
| 'option'
| 'criticalEnd'
| 'breakStart'
| 'breakEnd'
| 'parOverStart'
| 'parOverEnd'
| 'parOverAnd'
| 'parOverEnd';
/**
* @param {object | object[] | AddMessageParams} param - Object of parameters.
*/
export const apply = function (param) {
activate: boolean;
};
export const apply = function (param: any | AddMessageParams | AddMessageParams[]) {
if (Array.isArray(param)) {
param.forEach(function (item) {
apply(item);