@unito/integration-api
Version:
The Unito Integration API
803 lines (797 loc) • 29.5 kB
JavaScript
;
/**
* A FieldValueType determines the type of an item's value.
* The type represents a unique scalar value such as a string or an integer.
* For more complex types, use an Item instead.
*/
const FieldValueTypes = {
BLOB: 'blob',
BOOLEAN: 'boolean',
DATE: 'date',
DATE_RANGE: 'dateRange',
DATETIME: 'datetime',
DATETIME_RANGE: 'datetimeRange',
DURATION: 'duration',
EMAIL: 'email',
RICH_TEXT_HTML: 'html',
INTEGER: 'integer',
RICH_TEXT_MARKDOWN: 'markdown',
NUMBER: 'number',
OBJECT: 'object',
REFERENCE: 'reference',
STRING: 'string',
URL: 'url',
};
/**
* A Semantic gives meaning to an Item or a Field in the Unito platform.
* An object with a specified semantic is expected to hold a certain type of value that can later be used
* by the consumers of the spec to better understand what that value means.
*/
const Semantics = {
CREATED_AT: 'createdAt',
DESCRIPTION: 'description',
DISPLAY_NAME: 'displayName',
PROVIDER_URL: 'providerUrl',
UPDATED_AT: 'updatedAt',
USER: 'user',
PARENT: 'parent',
};
/**
* A Relation Semantic gives meaning to a Relation in the Unito platform. A relation with a
* specified semantic is expected to hold a certain type of item that can later be used by
* the consumers of the spec for specific usage.
*/
const RelationSemantics = {
COMMENTS: 'comments',
SUBTASKS: 'subtasks',
USERS: 'users',
ATTACHMENTS: 'attachments',
};
/**
* An OperatorType represents an operator used in a filter.
*/
const OperatorTypes = {
EQUAL: '=',
NOT_EQUAL: '!=',
GREATER_THAN: '>',
GREATER_THAN_OR_EQUAL: '>=',
LESSER_THAN: '<',
LESSER_THAN_OR_EQUAL: '<=',
INCLUDE: '*=',
EXCLUDE: '!*=',
START_WITH: '^=',
END_WITH: '$=',
IS_NULL: '!!',
IS_NOT_NULL: '!',
};
/**
* HTTP code returned by the integration
*/
const StatusCodes = {
OK: 200,
CREATED: 201,
ACCEPTED: 202,
NO_CONTENT: 204,
BAD_REQUEST: 400,
UNAUTHORIZED: 401,
FORBIDDEN: 403,
NOT_FOUND: 404,
NOT_ACCEPTABLE: 406,
REQUEST_TIMEOUT: 408,
UNPROCESSABLE_CONTENT: 422,
LOCKED: 423,
TOO_MANY_REQUESTS: 429,
INTERNAL_SERVER_ERROR: 500,
};
/**
* Checks if the input is a record<unknown, unknown>
* @param potentialObject - The value to check.
* @returns True if the value is a record, false otherwise.
*/
function isObject(potentialObject) {
return typeof potentialObject === 'object' && potentialObject !== null && !Array.isArray(potentialObject);
}
/**
* Checks if the input is a string.
* @param potentialString - The value to check.
* @returns True if the value is a string, false otherwise.
*/
function isString(potentialString) {
return typeof potentialString === 'string';
}
/**
* Checks if the input is undefined.
* @param potentialUndefined - The value to check.
* @returns True if the value is undefined, false otherwise.
*/
function isUndefined(potentialUndefined) {
return potentialUndefined === undefined;
}
/**
* Checks if the input is an Api.ItemSummary object.
* @param potentialItemSummary - The value to check.
* @returns True if the value is an ItemSummary, false otherwise.
*/
function isItemSummary(potentialItemSummary) {
return (isObject(potentialItemSummary) &&
isString(potentialItemSummary['path']) &&
(isUndefined(potentialItemSummary['fields']) || isObject(potentialItemSummary['fields'])) &&
(isUndefined(potentialItemSummary['relations']) ||
(Array.isArray(potentialItemSummary['relations']) && potentialItemSummary['relations'].every(isString))));
}
/**
* Checks if the input is an Api.Item object.
* @param potentialItem - The value to check.
* @returns True if the value is an Item, false otherwise.
*/
function isItem(potentialItem) {
return (isObject(potentialItem) &&
isObject(potentialItem['fields']) &&
Array.isArray(potentialItem['relations']) &&
potentialItem['relations'].every(isRelation));
}
/**
* Checks if the input is an Api.ReferenceRelation object.
* @param potentialReferenceRelation - The value to check.
* @returns True if the value is a ReferenceRelation, false otherwise.
*/
function isReferenceRelation(potentialReferenceRelation) {
return (isObject(potentialReferenceRelation) &&
isString(potentialReferenceRelation['path']) &&
isString(potentialReferenceRelation['label']) &&
isRelationSchemaOrSelf(potentialReferenceRelation['schema']));
}
/**
* Checks if the input is an Api.RelationSummary object.
* @param potentialRelationSummary - The value to check.
* @returns True if the value is a RelationSummary, false otherwise.
*/
function isRelationSummary(potentialRelationSummary) {
return (isObject(potentialRelationSummary) &&
isString(potentialRelationSummary['name']) &&
isString(potentialRelationSummary['label']) &&
isRelationSchemaOrSelf(potentialRelationSummary['schema']));
}
/**
* Checks if the input is an Api.Relation object.
* @param potentialRelation - The value to check.
* @returns True if the value is a Relation, false otherwise.
*/
function isRelation(potentialRelation) {
return (isObject(potentialRelation) &&
isString(potentialRelation['name']) &&
isString(potentialRelation['path']) &&
isString(potentialRelation['label']) &&
isRelationSchema(potentialRelation['schema']));
}
/**
* Checks if the input is an Api.RelationSchema object.
* @param potentialRelationSchema - The value to check.
* @returns True if the value is a RelationSchema, false otherwise.
*/
function isRelationSchema(potentialRelationSchema) {
return (isObject(potentialRelationSchema) &&
isString(potentialRelationSchema['label']) &&
Array.isArray(potentialRelationSchema['fields']) &&
potentialRelationSchema['fields'].every(isFieldSchema) &&
(isUndefined(potentialRelationSchema['relations']) ||
(Array.isArray(potentialRelationSchema['relations']) &&
potentialRelationSchema['relations'].every(isRelationSummary))));
}
/**
* Checks if the input is an Api.RelationSchema object or the string '__self'.
* @param potentialRelationSchema - The value to check.
* @returns True if the value is a RelationSchema or '__self', false otherwise.
*/
function isRelationSchemaOrSelf(potentialRelationSchemaOrSelf) {
return isRelationSchema(potentialRelationSchemaOrSelf) || potentialRelationSchemaOrSelf === '__self';
}
/**
* Checks if the input is an Api.FieldValueType.
* @param potentialFieldValueType - The value to check.
* @returns True if the value is a FieldValueType, false otherwise.
*/
function isFieldValueType(potentialFieldValueType) {
return Object.values(FieldValueTypes).includes(potentialFieldValueType);
}
/**
* Checks if the input is an Api.Semantic value.
* @param potentialSemantic - The value to check.
* @returns True if the value is a Semantic, false otherwise.
*/
function isSemantic(potentialSemantic) {
return Object.values(Semantics).includes(potentialSemantic);
}
/**
* Checks if the input is an Api.FieldSchema object.
* @param potentialFieldSchema - The value to check.
* @returns True if the value is a FieldSchema, false otherwise.
*/
function isFieldSchema(potentialFieldSchema) {
return (isObject(potentialFieldSchema) &&
isString(potentialFieldSchema['name']) &&
isString(potentialFieldSchema['label']) &&
isString(potentialFieldSchema['type']) &&
isFieldValueType(potentialFieldSchema['type']) &&
(isUndefined(potentialFieldSchema['semantic']) || isSemantic(potentialFieldSchema['semantic'])));
}
const referenceToStringLikeConfiguration = {
field: {
type: 'sourceField',
defaults: [{ value: 'semantic:displayName' }],
description: 'The field of the referenced entry to use for the string value.',
required: true,
},
};
const stringLikeToReferenceConfiguration = {
field: {
type: 'targetField',
defaults: [{ value: 'semantic:displayName' }],
description: 'The field against which to match the string value.',
required: true,
},
};
const dateToStringlikeConfiguration = {
locale: {
type: 'enum',
defaults: [{ value: 'en-CA' }],
values: ['en-CA'],
description: 'The locale to use for formatting the date.',
},
timezone: {
type: 'enum',
defaults: [{ value: 'UTC' }],
values: ['UTC'],
description: 'The timezone to use for formatting the date.',
},
};
const htmlToRichTextConfiguration = {
decoratorTemplate: {
type: 'stringWithSourceItemFields',
description: 'Template for the decorator. Use {author} and {date} placeholders.',
defaults: [
{
sourceRelationSemantic: RelationSemantics.COMMENTS,
sourceFieldSemantic: Semantics.DESCRIPTION,
targetRelationSemantic: RelationSemantics.COMMENTS,
targetFieldSemantic: Semantics.DESCRIPTION,
value: 'On <strong>{semantic:createdAt}</strong>, <em>{semantic:user.fields.semantic:displayName}</em> commented:',
},
],
hidden: true,
},
decoratorPrefix: {
type: 'string',
description: `Prefix for the decorator. Defaults to "➤".`,
defaults: [
{
sourceRelationSemantic: RelationSemantics.COMMENTS,
sourceFieldSemantic: Semantics.DESCRIPTION,
targetRelationSemantic: RelationSemantics.COMMENTS,
targetFieldSemantic: Semantics.DESCRIPTION,
value: '➤',
},
],
hidden: true,
},
};
const markdownToRichTextConfiguration = {
decoratorTemplate: {
type: 'stringWithSourceItemFields',
description: 'Template for the decorator. Use {author} and {date} placeholders.',
defaults: [
{
sourceRelationSemantic: RelationSemantics.COMMENTS,
sourceFieldSemantic: Semantics.DESCRIPTION,
targetRelationSemantic: RelationSemantics.COMMENTS,
targetFieldSemantic: Semantics.DESCRIPTION,
value: 'On **{semantic:createdAt}**, __{semantic:user.fields.semantic:displayName}__ commented:',
},
],
hidden: true,
},
decoratorPrefix: {
type: 'string',
description: `Prefix for the decorator. Defaults to "➤".`,
defaults: [
{
sourceRelationSemantic: RelationSemantics.COMMENTS,
sourceFieldSemantic: Semantics.DESCRIPTION,
targetRelationSemantic: RelationSemantics.COMMENTS,
targetFieldSemantic: Semantics.DESCRIPTION,
value: '➤',
},
],
hidden: true,
},
};
const fieldTypeCompatibilityMatrix = {
[FieldValueTypes.BLOB]: {
[FieldValueTypes.BLOB]: {},
[FieldValueTypes.BOOLEAN]: null,
[FieldValueTypes.DATE]: null,
[FieldValueTypes.DATE_RANGE]: null,
[FieldValueTypes.DATETIME]: null,
[FieldValueTypes.DATETIME_RANGE]: null,
[FieldValueTypes.DURATION]: null,
[FieldValueTypes.EMAIL]: null,
[FieldValueTypes.INTEGER]: null,
[FieldValueTypes.NUMBER]: null,
[FieldValueTypes.OBJECT]: null,
[FieldValueTypes.REFERENCE]: null,
[FieldValueTypes.RICH_TEXT_HTML]: null,
[FieldValueTypes.RICH_TEXT_MARKDOWN]: null,
[FieldValueTypes.STRING]: null,
[FieldValueTypes.URL]: null,
},
[FieldValueTypes.BOOLEAN]: {
[FieldValueTypes.BLOB]: null,
[FieldValueTypes.BOOLEAN]: {},
[FieldValueTypes.DATE]: null,
[FieldValueTypes.DATE_RANGE]: null,
[FieldValueTypes.DATETIME]: null,
[FieldValueTypes.DATETIME_RANGE]: null,
[FieldValueTypes.DURATION]: null,
[FieldValueTypes.EMAIL]: null,
[FieldValueTypes.INTEGER]: {},
[FieldValueTypes.NUMBER]: {},
[FieldValueTypes.OBJECT]: null,
[FieldValueTypes.REFERENCE]: null,
[FieldValueTypes.RICH_TEXT_HTML]: {},
[FieldValueTypes.RICH_TEXT_MARKDOWN]: {},
[FieldValueTypes.STRING]: {},
[FieldValueTypes.URL]: null,
},
[FieldValueTypes.DATE]: {
[FieldValueTypes.BLOB]: null,
[FieldValueTypes.BOOLEAN]: null,
[FieldValueTypes.DATE]: {},
[FieldValueTypes.DATE_RANGE]: null,
[FieldValueTypes.DATETIME]: {},
[FieldValueTypes.DATETIME_RANGE]: null,
[FieldValueTypes.DURATION]: null,
[FieldValueTypes.EMAIL]: null,
[FieldValueTypes.INTEGER]: {},
[FieldValueTypes.NUMBER]: {},
[FieldValueTypes.OBJECT]: null,
[FieldValueTypes.REFERENCE]: null,
[FieldValueTypes.RICH_TEXT_HTML]: dateToStringlikeConfiguration,
[FieldValueTypes.RICH_TEXT_MARKDOWN]: dateToStringlikeConfiguration,
[FieldValueTypes.STRING]: dateToStringlikeConfiguration,
[FieldValueTypes.URL]: null,
},
[FieldValueTypes.DATE_RANGE]: {
[FieldValueTypes.BLOB]: null,
[FieldValueTypes.BOOLEAN]: null,
[FieldValueTypes.DATE]: null,
[FieldValueTypes.DATE_RANGE]: null,
[FieldValueTypes.DATETIME]: null,
[FieldValueTypes.DATETIME_RANGE]: null,
[FieldValueTypes.DURATION]: null,
[FieldValueTypes.EMAIL]: null,
[FieldValueTypes.INTEGER]: null,
[FieldValueTypes.NUMBER]: null,
[FieldValueTypes.OBJECT]: null,
[FieldValueTypes.REFERENCE]: null,
[FieldValueTypes.RICH_TEXT_HTML]: null,
[FieldValueTypes.RICH_TEXT_MARKDOWN]: null,
[FieldValueTypes.STRING]: null,
[FieldValueTypes.URL]: null,
},
[FieldValueTypes.DATETIME]: {
[FieldValueTypes.BLOB]: null,
[FieldValueTypes.BOOLEAN]: null,
[FieldValueTypes.DATE]: {},
[FieldValueTypes.DATE_RANGE]: null,
[FieldValueTypes.DATETIME]: {},
[FieldValueTypes.DATETIME_RANGE]: null,
[FieldValueTypes.DURATION]: null,
[FieldValueTypes.EMAIL]: null,
[FieldValueTypes.INTEGER]: {},
[FieldValueTypes.NUMBER]: {},
[FieldValueTypes.OBJECT]: null,
[FieldValueTypes.REFERENCE]: null,
[FieldValueTypes.RICH_TEXT_HTML]: dateToStringlikeConfiguration,
[FieldValueTypes.RICH_TEXT_MARKDOWN]: dateToStringlikeConfiguration,
[FieldValueTypes.STRING]: dateToStringlikeConfiguration,
[FieldValueTypes.URL]: null,
},
[FieldValueTypes.DATETIME_RANGE]: {
[FieldValueTypes.BLOB]: null,
[FieldValueTypes.BOOLEAN]: null,
[FieldValueTypes.DATE]: null,
[FieldValueTypes.DATE_RANGE]: null,
[FieldValueTypes.DATETIME]: null,
[FieldValueTypes.DATETIME_RANGE]: null,
[FieldValueTypes.DURATION]: null,
[FieldValueTypes.EMAIL]: null,
[FieldValueTypes.INTEGER]: null,
[FieldValueTypes.NUMBER]: null,
[FieldValueTypes.OBJECT]: null,
[FieldValueTypes.REFERENCE]: null,
[FieldValueTypes.RICH_TEXT_HTML]: null,
[FieldValueTypes.RICH_TEXT_MARKDOWN]: null,
[FieldValueTypes.STRING]: null,
[FieldValueTypes.URL]: null,
},
[FieldValueTypes.DURATION]: {
[FieldValueTypes.BLOB]: null,
[FieldValueTypes.BOOLEAN]: null,
[FieldValueTypes.DATE]: null,
[FieldValueTypes.DATE_RANGE]: null,
[FieldValueTypes.DATETIME]: null,
[FieldValueTypes.DATETIME_RANGE]: null,
[FieldValueTypes.DURATION]: {},
[FieldValueTypes.EMAIL]: null,
[FieldValueTypes.INTEGER]: {},
[FieldValueTypes.NUMBER]: {},
[FieldValueTypes.OBJECT]: null,
[FieldValueTypes.REFERENCE]: null,
[FieldValueTypes.RICH_TEXT_HTML]: {},
[FieldValueTypes.RICH_TEXT_MARKDOWN]: {},
[FieldValueTypes.STRING]: {},
[FieldValueTypes.URL]: null,
},
[FieldValueTypes.EMAIL]: {
[FieldValueTypes.BLOB]: null,
[FieldValueTypes.BOOLEAN]: null,
[FieldValueTypes.DATE]: null,
[FieldValueTypes.DATE_RANGE]: null,
[FieldValueTypes.DATETIME]: null,
[FieldValueTypes.DATETIME_RANGE]: null,
[FieldValueTypes.DURATION]: null,
[FieldValueTypes.EMAIL]: {},
[FieldValueTypes.INTEGER]: null,
[FieldValueTypes.NUMBER]: null,
[FieldValueTypes.OBJECT]: null,
[FieldValueTypes.REFERENCE]: stringLikeToReferenceConfiguration,
[FieldValueTypes.RICH_TEXT_HTML]: {},
[FieldValueTypes.RICH_TEXT_MARKDOWN]: {},
[FieldValueTypes.STRING]: {},
[FieldValueTypes.URL]: null,
},
[FieldValueTypes.INTEGER]: {
[FieldValueTypes.BLOB]: null,
[FieldValueTypes.BOOLEAN]: {},
[FieldValueTypes.DATE]: {},
[FieldValueTypes.DATE_RANGE]: null,
[FieldValueTypes.DATETIME]: {},
[FieldValueTypes.DATETIME_RANGE]: null,
[FieldValueTypes.DURATION]: null,
[FieldValueTypes.EMAIL]: null,
[FieldValueTypes.INTEGER]: {},
[FieldValueTypes.NUMBER]: {},
[FieldValueTypes.OBJECT]: null,
[FieldValueTypes.REFERENCE]: null,
[FieldValueTypes.RICH_TEXT_HTML]: {},
[FieldValueTypes.RICH_TEXT_MARKDOWN]: {},
[FieldValueTypes.STRING]: {},
[FieldValueTypes.URL]: null,
},
[FieldValueTypes.NUMBER]: {
[FieldValueTypes.BLOB]: null,
[FieldValueTypes.BOOLEAN]: {},
[FieldValueTypes.DATE]: {},
[FieldValueTypes.DATE_RANGE]: null,
[FieldValueTypes.DATETIME]: {},
[FieldValueTypes.DATETIME_RANGE]: null,
[FieldValueTypes.DURATION]: null,
[FieldValueTypes.EMAIL]: null,
[FieldValueTypes.INTEGER]: {},
[FieldValueTypes.NUMBER]: {},
[FieldValueTypes.OBJECT]: null,
[FieldValueTypes.REFERENCE]: null,
[FieldValueTypes.RICH_TEXT_HTML]: {},
[FieldValueTypes.RICH_TEXT_MARKDOWN]: {},
[FieldValueTypes.STRING]: {},
[FieldValueTypes.URL]: null,
},
[FieldValueTypes.OBJECT]: {
[FieldValueTypes.BLOB]: null,
[FieldValueTypes.BOOLEAN]: null,
[FieldValueTypes.DATE]: null,
[FieldValueTypes.DATE_RANGE]: null,
[FieldValueTypes.DATETIME]: null,
[FieldValueTypes.DATETIME_RANGE]: null,
[FieldValueTypes.DURATION]: null,
[FieldValueTypes.EMAIL]: null,
[FieldValueTypes.INTEGER]: null,
[FieldValueTypes.NUMBER]: null,
[FieldValueTypes.OBJECT]: null,
[FieldValueTypes.REFERENCE]: null,
[FieldValueTypes.RICH_TEXT_HTML]: null,
[FieldValueTypes.RICH_TEXT_MARKDOWN]: null,
[FieldValueTypes.STRING]: null,
[FieldValueTypes.URL]: null,
},
[FieldValueTypes.REFERENCE]: {
[FieldValueTypes.BLOB]: null,
[FieldValueTypes.BOOLEAN]: null,
[FieldValueTypes.DATE]: null,
[FieldValueTypes.DATE_RANGE]: null,
[FieldValueTypes.DATETIME]: null,
[FieldValueTypes.DATETIME_RANGE]: null,
[FieldValueTypes.DURATION]: null,
[FieldValueTypes.EMAIL]: referenceToStringLikeConfiguration,
[FieldValueTypes.INTEGER]: null,
[FieldValueTypes.NUMBER]: null,
[FieldValueTypes.OBJECT]: null,
[FieldValueTypes.REFERENCE]: {
sideIds: {
type: 'sideIdOfSameCollection',
description: 'If specified, only the references kept in sync in the specified link sides will be evaluated. An empty array means no side will be evaluated.',
isArray: true,
defaults: [{ value: [] }],
},
type: {
type: 'enum',
description: 'The type of mapping, as selected by the customer.',
hidden: true,
isArray: false,
values: ['manualOnly', 'referenceOnly'],
required: true,
},
},
[FieldValueTypes.RICH_TEXT_HTML]: referenceToStringLikeConfiguration,
[FieldValueTypes.RICH_TEXT_MARKDOWN]: referenceToStringLikeConfiguration,
[FieldValueTypes.STRING]: referenceToStringLikeConfiguration,
[FieldValueTypes.URL]: null,
},
[FieldValueTypes.RICH_TEXT_HTML]: {
[FieldValueTypes.BLOB]: null,
[FieldValueTypes.BOOLEAN]: null,
[FieldValueTypes.DATE]: null,
[FieldValueTypes.DATE_RANGE]: null,
[FieldValueTypes.DATETIME]: null,
[FieldValueTypes.DATETIME_RANGE]: null,
[FieldValueTypes.DURATION]: null,
[FieldValueTypes.EMAIL]: null,
[FieldValueTypes.INTEGER]: null,
[FieldValueTypes.NUMBER]: null,
[FieldValueTypes.OBJECT]: null,
[FieldValueTypes.REFERENCE]: null,
[FieldValueTypes.RICH_TEXT_HTML]: htmlToRichTextConfiguration,
[FieldValueTypes.RICH_TEXT_MARKDOWN]: htmlToRichTextConfiguration,
[FieldValueTypes.STRING]: {},
[FieldValueTypes.URL]: null,
},
[FieldValueTypes.RICH_TEXT_MARKDOWN]: {
[FieldValueTypes.BLOB]: null,
[FieldValueTypes.BOOLEAN]: null,
[FieldValueTypes.DATE]: null,
[FieldValueTypes.DATE_RANGE]: null,
[FieldValueTypes.DATETIME]: null,
[FieldValueTypes.DATETIME_RANGE]: null,
[FieldValueTypes.DURATION]: null,
[FieldValueTypes.EMAIL]: null,
[FieldValueTypes.INTEGER]: null,
[FieldValueTypes.NUMBER]: null,
[FieldValueTypes.OBJECT]: null,
[FieldValueTypes.REFERENCE]: null,
[FieldValueTypes.RICH_TEXT_HTML]: markdownToRichTextConfiguration,
[FieldValueTypes.RICH_TEXT_MARKDOWN]: markdownToRichTextConfiguration,
[FieldValueTypes.STRING]: {},
[FieldValueTypes.URL]: null,
},
[FieldValueTypes.STRING]: {
[FieldValueTypes.BLOB]: null,
[FieldValueTypes.BOOLEAN]: {},
[FieldValueTypes.DATE]: {},
[FieldValueTypes.DATE_RANGE]: null,
[FieldValueTypes.DATETIME]: {},
[FieldValueTypes.DATETIME_RANGE]: null,
[FieldValueTypes.DURATION]: null,
[FieldValueTypes.EMAIL]: null,
[FieldValueTypes.INTEGER]: {},
[FieldValueTypes.NUMBER]: {},
[FieldValueTypes.OBJECT]: null,
[FieldValueTypes.REFERENCE]: stringLikeToReferenceConfiguration,
[FieldValueTypes.RICH_TEXT_HTML]: {},
[FieldValueTypes.RICH_TEXT_MARKDOWN]: {},
[FieldValueTypes.STRING]: {},
[FieldValueTypes.URL]: null,
},
[FieldValueTypes.URL]: {
[FieldValueTypes.BLOB]: null,
[FieldValueTypes.BOOLEAN]: null,
[FieldValueTypes.DATE]: null,
[FieldValueTypes.DATE_RANGE]: null,
[FieldValueTypes.DATETIME]: null,
[FieldValueTypes.DATETIME_RANGE]: null,
[FieldValueTypes.DURATION]: null,
[FieldValueTypes.EMAIL]: null,
[FieldValueTypes.INTEGER]: null,
[FieldValueTypes.NUMBER]: null,
[FieldValueTypes.OBJECT]: null,
[FieldValueTypes.REFERENCE]: null,
[FieldValueTypes.RICH_TEXT_HTML]: {},
[FieldValueTypes.RICH_TEXT_MARKDOWN]: {},
[FieldValueTypes.STRING]: {},
[FieldValueTypes.URL]: {},
},
};
/**
* JSONPath parser that returns a relation that is guaranteed to have its schema populated.
*/
function findRelationByJSONPath(item, query) {
const tokens = parseJSONPath(query);
const schemas = [];
let current = item;
for (const token of tokens) {
if (current === '__self') {
const previousSchema = schemas[schemas.length - 1];
if (!previousSchema) {
throw new Error(`Invalid use of __self`);
}
current = previousSchema;
}
const result = applyToken(current, token);
if (isObject(result) && isRelationSchema(result['schema'])) {
schemas.push(result['schema']);
}
current = result;
}
if (isRelation(current)) {
return current;
}
if (isReferenceRelation(current) || isRelationSummary(current)) {
const latestSchema = schemas[schemas.length - 1];
if (latestSchema === undefined) {
throw new Error(`No schema found for relation ${current.label}`);
}
return { ...current, schema: latestSchema };
}
return undefined;
}
/**
* Parse JSONPath expression into tokens
*/
function parseJSONPath(query) {
const tokens = [];
let remaining = query;
// Remove root $ if present
if (remaining.startsWith('$')) {
remaining = remaining.substring(1);
}
while (remaining.length > 0) {
// Skip leading dots
if (remaining.startsWith('.')) {
remaining = remaining.substring(1);
continue;
}
// Parse bracket notation [...]
if (remaining.startsWith('[')) {
const bracketMatch = remaining.match(/^\[([^\]]*)\]/);
if (!bracketMatch) {
throw new Error(`Unclosed bracket in JSONPath: ${query}`);
}
remaining = remaining.substring(bracketMatch[0].length);
tokens.push(parseBracketExpression(String(bracketMatch[1])));
continue;
}
// Parse property name (until . or [ or end)
const propertyMatch = remaining.match(/^([^.[]+)/);
if (propertyMatch) {
const propertyName = String(propertyMatch[1]);
remaining = remaining.substring(propertyName.length);
tokens.push({ type: 'property', name: propertyName });
}
}
return tokens;
}
/**
* Parse bracket expression into a token
*/
function parseBracketExpression(content) {
// Filter expression: ?(@.property == 'value')
if (content.startsWith('?(')) {
const filterExpr = content.substring(2, content.length - 1);
return { type: 'filter', expression: parseFilterExpression(filterExpr) };
}
// Array index: 0, 1, 2, etc.
const index = parseInt(content, 10);
if (!isNaN(index)) {
return { type: 'index', value: index };
}
throw new Error(`Unsupported bracket expression: ${content}`);
}
/**
* Parse filter expression like @.name == 'value'
*/
function parseFilterExpression(expr) {
const opIndex = expr.indexOf('==');
if (opIndex === -1) {
throw new Error(`Filter expression must use == operator: ${expr}`);
}
const left = expr.substring(0, opIndex).trim();
const right = expr.substring(opIndex + 2).trim();
// Parse left side (should be @.property)
if (!left.startsWith('@.')) {
throw new Error(`Filter expression must start with @.: ${expr}`);
}
const property = left.substring(2);
// Parse right side (value) using regex to extract quoted strings
const quotedMatch = right.match(/^(?<quote>['"])(?<content>.*?)\k<quote>$/);
if (!quotedMatch) {
throw new Error(`Filter expression value must be a quoted string: ${expr}`);
}
return { property, value: quotedMatch.groups['content'] };
}
/**
* Apply a single token to the current value
*/
function applyToken(current, token) {
switch (token.type) {
case 'property':
return applyProperty(current, token.name);
case 'index':
return applyIndex(current, token.value);
case 'filter':
return applyFilter(current, token.expression);
default:
throw new Error(`Unsupported token type: ${token.type}`);
}
}
/**
* Apply property access
*/
function applyProperty(current, property) {
if (!isObject(current) || !(property in current)) {
return undefined;
}
return current[property];
}
/**
* Apply array index access
*/
function applyIndex(current, index) {
if (!Array.isArray(current) || index < 0 || index >= current.length) {
return undefined;
}
return current[index];
}
/**
* Apply filter expression
*
* This function returns the first item that matches the filter expression.
*/
function applyFilter(current, filter) {
if (!Array.isArray(current)) {
return undefined;
}
for (const item of current) {
if (isObject(item) && matchesFilter(item, filter)) {
return item;
}
}
return undefined;
}
/**
* Check if an item matches a filter expression
*/
function matchesFilter(item, filter) {
if (!(filter.property in item)) {
return false;
}
return item[filter.property] === filter.value;
}
exports.FieldValueTypes = FieldValueTypes;
exports.OperatorTypes = OperatorTypes;
exports.RelationSemantics = RelationSemantics;
exports.Semantics = Semantics;
exports.StatusCodes = StatusCodes;
exports.fieldTypeCompatibilityMatrix = fieldTypeCompatibilityMatrix;
exports.findRelationByJSONPath = findRelationByJSONPath;
exports.isFieldSchema = isFieldSchema;
exports.isFieldValueType = isFieldValueType;
exports.isItem = isItem;
exports.isItemSummary = isItemSummary;
exports.isObject = isObject;
exports.isReferenceRelation = isReferenceRelation;
exports.isRelation = isRelation;
exports.isRelationSchema = isRelationSchema;
exports.isRelationSchemaOrSelf = isRelationSchemaOrSelf;
exports.isRelationSummary = isRelationSummary;
exports.isSemantic = isSemantic;
exports.isString = isString;
exports.isUndefined = isUndefined;