@appsemble/lang-sdk
Version:
Language SDK for Appsemble
798 lines (797 loc) • 25 kB
TypeScript
import { type RequireExactlyOne } from 'type-fest';
type AppMemberInfoKey = 'demo' | 'email_verified' | 'email' | 'locale' | 'name' | 'phoneNumber' | 'picture' | 'properties' | 'role' | 'sub' | 'zoneinfo';
interface BaseICSRemapper {
/**
* The start of the icalendar event.
*/
start: Remapper;
/**
* The title of the event.
*/
title: Remapper;
/**
* An optional description of the event.
*/
description?: Remapper;
/**
* An optional link to attach to the event.
*/
url?: Remapper;
/**
* An optional location description to attach to the event.
*/
location?: Remapper;
/**
* An optional geolocation description to attach to the event.
*
* This must be an object with the properties `lat` or `latitude`, and `lon`, `lng` or
* `longitude`.
*/
coordinates?: Remapper;
}
interface DurationICSRemapper extends BaseICSRemapper {
/**
* The duration of the event.
*
* @example '1w 3d 10h 30m'
*/
duration: Remapper;
}
interface EndTimeICSRemapper extends BaseICSRemapper {
/**
* The end time of the event as a date or a date string.
*/
end: Remapper;
}
export interface SubstringCaseType {
/**
* Whether to match the case of the substring.
*/
strict?: boolean;
/**
* Substring to match.
*/
substring: string;
}
type FilterParams = Record<string, {
type: 'Boolean' | 'Date' | 'Guid' | 'Number' | 'String';
value: Remapper;
comparator: 'eq' | 'ge' | 'gt' | 'le' | 'lt' | 'ne';
}>;
type OrderParams = Record<string, 'asc' | 'desc'>;
export interface Remappers {
/**
* Get app metadata.
*
* Supported properties:
*
* - `id`: Get the app id.
* - `locale`: Get the current locale of the app.
* - `url`: Get the base URL of the app.
*/
app: 'id' | 'locale' | 'url';
/**
* Get group metadata.
* Supported properties:
*
* - `id`: Get the id of the selected group.
* - `role`: Role of the current app member in the group.
* - `name`: Get the name of the selected group.
*/
group: 'id' | 'name' | 'role';
/**
* Get property of the AppMember object.
*
* Supported properties:
*
* - `sub`: Get the id of the app member.
* - `name`: Get the name of the app member.
* - `email`: Get the email of the app member.
* - `email_verified`: Whether the email of the app member is verified.
* - `picture`: Get the picture of the app member.
* - `locale`: Get the locale of the app member.
* - `zoneinfo`: Get the zoneinfo of the app member.
* - `role`: Get the role of the app member.
* - `properties`: Get the custom properties of the app member.
*/
'app.member': AppMemberInfoKey;
/**
* Get a predefined app variable by name.
*/
variable: string;
/**
* Get page metadata.
*
* Supported properties:
*
* - `data`: Get the current page data.
* - `url`: Get the URL of the current page.
* - `name`: Get the translated name of the current page.
*/
page: 'data' | 'name' | 'url';
/**
* Get a property from the context.
*/
context: string;
/**
* Get the title of current page.
*/
'tab.name': string;
/**
* Convert a string to a number.
*/
'number.parse': Remapper;
/**
* Convert a string to a date using a given format.
*/
'date.parse': string;
/**
* Returns the current date.
*/
'date.now': unknown;
/**
* Adds to a date.
*/
'date.add': string;
/**
* Formats a date to an iso8601 / rfc3339 compatible string.
*
* An argument can also be specified to use a different output format.
*
* Please refer to https://date-fns.org/docs/format for the supported patterns.
*/
'date.format'?: string;
/**
* Takes a date and returns the start of the specified unit.
*
* Supported units:
* - `year`: First day of the year
* - `quarter`: First day of the quarter
* - `month`: First day of the month
* - `week`: First day of the week (Monday)
* - `weekSun`: First day of the week (Sunday)
*/
'date.startOf': 'month' | 'quarter' | 'week' | 'weekSun' | 'year';
/**
* Takes a date and returns the end of the specified unit.
*
* Supported units:
* - `year`: Last moment of the year
* - `quarter`: Last moment of the quarter
* - `month`: Last moment of the month
* - `week`: Last moment of the week (ends Sunday if week starts Monday)
* - `weekSun`: Last moment of the week (ends Saturday if week starts Sunday)
*/
'date.endOf': 'month' | 'quarter' | 'week' | 'weekSun' | 'year';
/**
* Sets parts of a date.
*
* Only the given parts are changed.
*
* Month is 1-indexed (1 = January, 12 = December).
*/
'date.set': {
year?: Remapper;
month?: Remapper;
day?: Remapper;
};
/**
* Compare all computed remapper values against each other.
*
* Returns `true` if all entries are equal, otherwise `false`.
*/
equals: Remapper[];
/**
* Compare all computed remapper values against the first.
*
* Returns `false` if all entries are equal to the first entry, otherwise `true`.
*
* If only one remapper or none is passed, the remapper value gets computed and then inverted.
*/
not: Remapper[];
/**
* Compare all computed remapper values against each other.
*
* Returns `true` if all entries are true, otherwise `false`.
*
* If only one remapper is passed, the remapper is returned.
*/
and: Remapper[];
/**
* Compare all computed remapper values against each other.
*
* Returns `false` if all entries are false, otherwise `true`.
*
* If only one remapper is passed, the remapper is returned.
*/
or: Remapper[];
/**
* Get data stored at the current flow page step
*/
step: string;
/**
* Compares the first computed remapper value with the second computed remapper value.
*
* Returns `true` of the first entry is greater than the second entry.
*/
gt: [Remapper, Remapper];
/**
* Compares the first computed remapper value with the second computed remapper value.
*
* Returns `true` of the first entry is greater than or equal to the second entry.
*/
gte: [Remapper, Remapper];
/**
* Compares the first computed remapper value with the second computed remapper value.
*
* Returns `true` of the first entry is less than the second entry.
*/
lt: [Remapper, Remapper];
/**
* Compares the first computed remapper value with the second computed remapper value.
*
* Returns `true` of the first entry is less than or equal to the second entry.
*/
lte: [Remapper, Remapper];
/**
* Logs its input data (returns it) and its context.
*
* The value to set is the log level.
*/
log: 'error' | 'info' | 'warn';
/**
* Get input object type.
*/
type: null;
/**
* Builds an array based on the given data and remappers.
*
* The remappers gets applied to each item in the array.
*
* Always returns an array, can be empty if supplied data isn’t an array.
*/
'array.map': Remapper;
/**
* Creates an array of numbers from 0 to N-1.
*
* @example
* { "array.range": 5 } // returns [0, 1, 2, 3, 4]
*/
'array.range': Remapper;
/**
* Filters out unique entries from an array.
*
* The value Remapper is applied to each entry in the array,
* using its result to determine uniqueness.
*
* If the value Remapper result in `undefined` or `null`, the entire entry is used for uniqueness.
*
* If the input is not an array, the input is returned without any modifications.
*/
'array.unique': Remapper;
/**
* Flattens an array.
*
* The value of the remapper is used for the flattening depth.
*
* If the value Remapper result in `undefined` or `null`, the array will be flattened until
* the last layer.
*
* If the input is not an array, the input is returned without any modifications.
*/
'array.flatten': Remapper;
/**
* Create an icalendar event.
*/
ics: DurationICSRemapper | EndTimeICSRemapper;
/**
* Checks if condition results in a truthy value.
*
* Returns value of then if condition is truthy, otherwise it returns the value of else.
*/
if: {
condition: Remapper;
then: Remapper;
else: Remapper;
};
/**
* Check if any case results in a truthy value.
*
* Returns the value of the first case where the condition equals true, otherwise returns null.
*/
match: {
case: Remapper;
value: Remapper;
}[];
/**
* Get the current array.map’s index or length.
*
* Returns nothing if array.map’s context isn’t set.
*/
array: 'index' | 'item' | 'length' | 'nextItem' | 'prevItem';
/**
*
* Returns an array containing the items matching the specified conditions.
*/
'array.filter': Remapper;
/**
* Returns an object based on the specified condition
*/
'array.find': Remapper;
/**
* Create a new array with an array of predefined remappers.
*/
'array.from': Remapper[];
/**
* Append new values to the end of an array.
*
* If the input is not an array an empty array is returned.
*/
'array.append': Remapper[];
/**
* Remove item(s) from an array given a predefined array of remappable indices.
*
* Only the remapped values that are turned into numbers are applied.
*
* If the input is not an array an empty array is returned.
*/
'array.omit': Remapper[];
/**
* Create a new object given some predefined mapper keys.
*/
'object.from': Record<string, Remapper>;
/**
* Assign properties to an existing object given some predefined mapper keys.
*/
'object.assign': Record<string, Remapper>;
/**
* Remove properties from an existing object based on the given the object keys.
*
* Nested properties can be removed using arrays of keys.
*
* @example
* ```yaml
* object.omit:
* - foo # Removes the property foo
* - - bar # Removes the property baz inside of bar
* - baz
* ```
*/
'object.omit': (string[] | string)[];
/**
* Compare two objects to each other and get an array of differences
*
* Nested object keys are returned as a path array.
*
* @example
* ```yaml
* object.compare:
* - object.from:
* name: Alice
* age: 25
* address:
* object.from:
* city: Paris
* zip: 7500
* - object.from:
* name: Alice
* age: 26
* address:
* object.from:
* city: Lyon
* country: France
* ```
*
* Returns:
* ```javascript
* [
* { path: ['age'], type: 'changed', from: 25, to: 26 },
* { path: ['address', 'city'], type: 'changed', from: 'Paris', to: 'Lyon' },
* { path: ['address', 'zip'], type: 'removed', value: 7500 },
* { path: ['address', 'country'], type: 'added', value: 'France' }
* ]
* ```
*/
'object.compare': [Remapper, Remapper];
/**
* Takes an object with an array property and transforms it into an array of objects.
*
* Each object in the resulting array contains all the entries of the original object
* plus all the entries of the corresponding array item from the array property.
*
* > **Note**
* > If one of the items in the array contains a key, which exists in the original object
* > it will overwrite the original key
*
* > **Note**
* > Nested arrays or objects are not exploded
*
* @example
* Input:
* ```javascript
* {
* ownerName: 'John',
* country: 'USA',
* pets: [
* { name: 'Milka' },
* { name: 'Sven', country: 'Sweden' },
* { name: 'Tom', likes: ['mice', 'fish'] },
* { name: 'Jerry', looks: { color: 'brown' } }
* ]
* }
* ```
*
* Remapper:
* ```yaml
* object.explode: pets
* ```
*
* Returns:
* ```javascript
* [
* { ownerName: 'John', name: 'Milka', country: 'USA' },
* { ownerName: 'John', name: 'Sven', country: 'Sweden' },
* { ownerName: 'John', name: 'Tom', country: 'USA', likes: ['mice', 'fish'] },
* { ownerName: 'John', name: 'Jerry', country: 'USA', looks: { color: 'brown' } }
* ]
* ```
*/
'object.explode': string;
/**
* Use a static value.
*/
static: any;
/**
* Get a property from an object.
*
* If the prop is an array, nested properties will be retrieved in sequence.
*/
prop: number[] | Remapper | string[];
/**
* Recursively strip all nullish values from an object or array.
*/
'null.strip': {
depth: number;
} | null;
/**
* Pick and return a random entry from an array.
*
* If the input is not an array, the input is returned as-is.
*/
'random.choice': null;
/**
* Pick and return a random entry from an array.
*
* If the input is not an array, the input is returned as-is.
*/
'random.integer': [number, number];
/**
* Pick and return a random entry from an array.
*
* If the input is not an array, the input is returned as-is.
*/
'random.float': [number, number];
/**
* Pick and return a random entry from an array.
*
* If the input is not an array, the input is returned as-is.
*/
'random.string': {
choice: string;
length: number;
};
/**
* This remapper return true if the provided item is in the input array.
*
*/
'array.contains': Remapper;
/**
* Join the items of an array using the input separator,
* If No separator is provided, ',' is used.
*
*/
'array.join': string | null;
/**
* Groups an array of objects by a common property value.
*
* Returns an array of group objects, each containing a `key` (the grouped property value)
* and `items` (array of objects with that property value).
*
* If the input is not an array, returns an empty array.
*
* @example
* // Input: [{ name: "Alice", dept: "Eng" }, { name: "Bob", dept: "Sales" }, { name: "Charlie", dept: "Eng" }]
* { "array.groupBy": "dept" }
* // Result: [
* // { key: "Eng", items: [{ name: "Alice", dept: "Eng" }, { name: "Charlie", dept: "Eng" }] },
* // { key: "Sales", items: [{ name: "Bob", dept: "Sales" }] }
* // ]
*/
'array.groupBy': string;
/**
* Converts an array of objects into a single object using specified key and value remappers.
*
* For each item in the array, the `key` remapper determines the property name and the `value`
* remapper determines the property value in the resulting object.
*
* If the input is not an array, returns an empty object.
* If multiple items produce the same key, later items overwrite earlier ones.
*
* @example
* // Input: [{ key: "Eng", items: [...] }, { key: "Sales", items: [...] }]
* { "array.toObject": { key: { prop: "key" }, value: { prop: "items" } } }
* // Result: { "Eng": [...], "Sales": [...] }
*/
'array.toObject': {
key: Remapper;
value: Remapper;
};
/**
* Sorts an array of items.
*
* Can sort by a property (for arrays of objects) or by the items themselves (for primitive arrays).
* Supports ascending and descending order, and handles numbers, strings, and dates.
*
* When a string is provided, it's used as the property name to sort by (ascending).
*
* **Sorting strategy (`strategy` option):**
*
* - `infer` (default): Determines comparison type from the first non-null value.
* Numbers use numeric comparison, Dates use timestamp comparison, everything else
* uses lexicographic (string) comparison. Mixed types fall back to lexicographic.
* - `numeric`: Coerces values to numbers. Non-numeric values (NaN) are pushed to the end.
* - `lexicographic`: Converts values to strings and uses `localeCompare()`.
* - `date`: Parses values as ISO dates and compares timestamps. Invalid dates are pushed to the end.
*
* Nullish values (null/undefined) are always pushed to the end regardless of strategy.
*
* @example
* // Input: [3, 1, 2]
* { "array.sort": null }
* // Result: [1, 2, 3]
* @example
* // Input: [{ name: "Charlie" }, { name: "Alice" }, { name: "Bob" }]
* { "array.sort": "name" }
* // Result: [{ name: "Alice" }, { name: "Bob" }, { name: "Charlie" }]
* @example
* // Input: [{ name: "Charlie" }, { name: "Alice" }, { name: "Bob" }]
* { "array.sort": { by: "name", descending: true } }
* // Result: [{ name: "Charlie" }, { name: "Bob" }, { name: "Alice" }]
* @example
* // Input: ["10", "2", "1"]
* { "array.sort": { strategy: "numeric" } }
* // Result: ["1", "2", "10"]
*/
'array.sort': string | {
/**
* The property name to sort by. If not provided, sorts by the items themselves.
*/
by?: string;
/**
* Whether to sort in descending order. Defaults to false (ascending).
*/
descending?: boolean;
/**
* The comparison strategy to use.
*
* - `infer` (default): Auto-detect from first non-null value. Numbers use numeric,
* Dates use date, others use lexicographic. Mixed types fall back to lexicographic.
* - `numeric`: Coerce to numbers. NaN values pushed to end.
* - `lexicographic`: Convert to strings, use localeCompare().
* - `date`: Parse as ISO dates, compare timestamps. Invalid dates pushed to end.
*/
strategy?: 'date' | 'infer' | 'lexicographic' | 'numeric';
} | null;
/**
* This remapper return true if the provided string is a substring of the input string.
*
*/
'string.contains': string;
/**
* This remapper returns the length of the input array or a string, this remapper
* doesn't require array to be in the context unlike `{ array: length }` remapper.
*/
len: null;
/**
* Get the input data as it was initially passed to the remap function.
*/
root: null;
/**
* Get the data at a certain index from the history stack prior to an action.
*
* 0 is the index of the first item in the history stack.
*/
history: number;
/**
* Create a new object with properties from the history stack at a certain index.
*/
'from.history': {
/**
* The index of the history stack item to apply.
*
* 0 is the index of the first item in the history stack.
*/
index: number;
/**
* Predefined mapper keys to choose what properties to apply.
*/
props: Record<string, Remapper>;
};
/**
* Assign properties from the history stack at a certain index to an existing object.
*/
'assign.history': {
/**
* The index of the history stack item to assign.
*
* 0 is the index of the first item in the history stack.
*/
index: number;
/**
* Predefined mapper keys to choose what properties to assign.
*/
props: Record<string, Remapper>;
};
/**
* Assign properties from the history stack at a certain index and exclude the unwanted.
*/
'omit.history': {
/**
* The index of the history stack item to assign.
*
* 0 is the index of the first item in the history stack.
*/
index: number;
/**
* Exclude properties from the history stack item, based on the given object keys.
*
* Nested properties can be excluded using arrays of keys.
*
* @example
* ```yaml
* omit.history:
* index: 0
* keys:
* - foo # Excludes the property foo
* - - bar # Excludes the property baz inside of bar
* - baz
* ```
*/
keys: (string[] | string)[];
};
/**
* Convert an input to lower or upper case.
*/
'string.case': 'lower' | 'upper';
/**
* Check if the initial characters of the string matches with the input string.
*/
'string.startsWith': SubstringCaseType | string;
/**
* Check if the last characters of the string matches with the input string.
*/
'string.endsWith': SubstringCaseType | string;
/**
* Extract a section of the string or an array.
*/
slice: number | [number, number];
/**
* Format a string using remapped input variables.
*/
'string.format': {
/**
* The message id pointing to the template string to format.
*/
messageId?: Remapper;
/**
* The template default string to format.
*/
template?: string;
/**
* A set of remappers to convert the input to usable values.
*/
values?: Record<string, Remapper>;
};
/**
* Match the content with the regex in the key, and replace it with its value.
*/
'string.replace': Record<string, string>;
/**
* Translate using a messageID.
*
* This does not support parameters, for more nuanced translations use `string.format`.
*/
translate: Remapper;
container: string;
/**
* Construct an OData $filter
*/
'filter.from': FilterParams;
/**
* Construct an OData $orderby
*/
'order.from': OrderParams;
/**
* Parse an xml string to a JavaScript object
*/
'xml.parse': Remapper;
/**
* Check if the value is defined
*
* @example
* "" -> true
* 0 -> true
* null -> false
* undefined -> false
*/
defined: Remapper;
/**
* Perform the specified mathematical operation on the two numbers.
*
* Where the position matters, `a` is the first input.
*
* If one of the inputs is not a number, or the operation is invalid, `-1` is returned.
*/
maths: {
a: Remapper;
b: Remapper;
operation: 'add' | 'divide' | 'mod' | 'multiply' | 'subtract';
};
/**
* Executes a remapper chain with a temporary root context.
*
* This is useful for solving nested context problems, like performing a
* filter on one list that depends on a value from an outer loop.
*
* @example
*
* // Get all customers, and for each customer, find the orders belonging to that customer.
* // Assumes that `history: 1` is an array of all customers,
* // and `history: 2` is an array of all orders.
* {
* 'array.map': {
* focus: {
* on: {
* 'object.from': {
* currentCustomer: { array: 'item' },
* allOrders: { history: 1 },
* },
* },
* do: {
* 'object.from': {
* id: { prop: 'id' },
* name: { prop: 'name' },
* associatedOrders: [
* { root: null },
* { prop: 'allOrders' },
* {
* 'array.filter': {
* equals: [
* { prop: 'customerId' },
* [{ root: null }, { prop: 'currentCustomer' }, { prop: 'id' }],
* ],
* },
* },
* ],
* },
* },
* },
* },
* },
*/
focus: {
/**
* A remapper that resolves to the object that will become the new `root`
* for the `do` remapper chain.
*/
on: Remapper;
/**
* The remapper or chain of remappers to execute with the new focused `root`.
*/
do: Remapper;
};
}
export type ObjectRemapper = RequireExactlyOne<Remappers>;
export type ArrayRemapper = (ArrayRemapper | ObjectRemapper)[];
export type Remapper = ArrayRemapper | ObjectRemapper | boolean | number | string | null;
export {};