PredicateCore.js

  1. /* eslint no-unused-vars: "off" */
  2. /**
  3. * PredicateCore
  4. * @module core
  5. * @namespace core
  6. * @since 1.0.0
  7. * @note rules are 100% tested from PredicateCore.test.js
  8. */
  9. const merge = require('ramda/src/merge');
  10. const find = require('ramda/src/find');
  11. const curry = require('ramda/src/curry');
  12. const pipe = require('ramda/src/pipe');
  13. const filter = require('ramda/src/filter');
  14. const map = require('ramda/src/map');
  15. const takeLast = require('ramda/src/takeLast');
  16. const insert = require('ramda/src/insert');
  17. const option = require('option');
  18. const { EventEmitter } = require('events');
  19. function head(list) {
  20. return option.fromNullable(list[0]).value();
  21. }
  22. module.exports = function({ dataclasses, invariants, errors, rules, UITypes }) {
  23. const { CompoundPredicate, ComparisonPredicate, Predicate } = dataclasses;
  24. /**
  25. * Get a type by its type_id
  26. * @param {array} types types
  27. * @param {string} type_id type id name
  28. * @return {?dataclasses.Type} a Type
  29. * @private
  30. * @since 1.0.0
  31. */
  32. const _getTypeById = (types, type_id) =>
  33. option.fromNullable(find(type => type.type_id === type_id, types));
  34. /**
  35. * Get a target by its target_id
  36. * @param {Array<dataclasses.Target>} targets targets
  37. * @param {string} target_id target id name
  38. * @return {Promise<dataclasses.Target, errors.Target_idMustReferToADefinedTarget>} resolved promise will yield a Target, rejected promise will yield `Target_idMustReferToADefinedTarget` if target_id was not found in `targets`
  39. * @private
  40. * @since 1.0.0
  41. */
  42. const _getTargetById = (targets, target_id) =>
  43. invariants.Target_idMustReferToADefinedTarget(
  44. find(target => target.target_id === target_id, targets)
  45. );
  46. /**
  47. * Get a logical type by its logicalType_id
  48. * @param {Array<dataclasses.LogicalType>} logicalTypes logicalTypes
  49. * @param {string} logicalType_id logicalType id name
  50. * @return {Promise<dataclasses.LogicalType, errors.LogicalType_idMustReferToADefinedLogicalType>} resolved promise will yield a LogicalType, rejected promise will yield `LogicalType_idMustReferToADefinedLogicalType` if logicalType was not found in `logicalTypes`.
  51. * @private
  52. * @since 1.0.0
  53. */
  54. const _getLogicalTypeById = (logicalTypes, logicalType_id) =>
  55. invariants.LogicalType_idMustReferToADefinedLogicalType(
  56. find(
  57. logicalType => logicalType.logicalType_id === logicalType_id,
  58. logicalTypes
  59. )
  60. );
  61. /**
  62. * Get an operator by its operator_id
  63. * @param {Array<dataclasses.Operator>} operators operators
  64. * @param {string[]} operator_id operator_id
  65. * @return {Promise<dataclasses.Operator, errors.Operator_idMustReferToADefinedOperator>} resolved promise will yield an Operator, rejected promise will yield `Operator_idMustReferToADefinedOperator` if operator was not found in `operators`.
  66. * @private
  67. * @since 1.0.0
  68. */
  69. const _getOperatorById = (operators, operator_id) =>
  70. invariants.Operator_idMustReferToADefinedOperator(
  71. find(operator => operator.operator_id === operator_id, operators)
  72. );
  73. /**
  74. * _getOperatorsByIds
  75. * @param {Object} operators operators
  76. * @param {string[]} operator_ids operator_ids
  77. * @return {Array<dataclasses.operator>}
  78. * @private
  79. * @since 1.0.0
  80. */
  81. const _getOperatorsByIds = curry((operators, operator_ids) =>
  82. pipe(filter(({ operator_id }) => operator_ids.includes(operator_id)))(
  83. operators
  84. )
  85. );
  86. const _set$operatorsToType = curry((columns, type) => {
  87. type.$operators = _getOperatorsByIds(columns.operators, type.operator_ids);
  88. return type;
  89. });
  90. const _set$typeToTarget = curry((columns, target) => {
  91. const typeOption = _getTypeById(columns.types, target.type_id);
  92. return invariants
  93. .TargetMustReferToADefinedType(typeOption, target)
  94. .then(type => {
  95. target.$type = type;
  96. return target;
  97. });
  98. });
  99. /**
  100. * Tap for Promise
  101. * @param {Function} f function to call
  102. * @return {Function} function that accept a promise and will call `f`
  103. * @private
  104. */
  105. const _tapPromise = f => {
  106. return function(promise) {
  107. return promise.then(result => {
  108. f();
  109. return result;
  110. });
  111. };
  112. };
  113. /**
  114. * Run `fAfter()` (without any arguments) after `fBefore`, it will yield the promise yield from fBefore
  115. * @param {Function} fBefore fBefore
  116. * @param {Function} fAfter fAfter
  117. * @return {Promise} promise from fBefore
  118. * @private
  119. */
  120. const _afterPromise = (fBefore, fAfter) => pipe(fBefore, _tapPromise(fAfter));
  121. // columns => Promise[columns]
  122. const _initializeColumns = columns => {
  123. // at first I used lenses, but the code was way harder to read so it's better that way :)
  124. // wrap operators
  125. columns.operators = map(dataclasses.Operator, columns.operators);
  126. // wrap logicalTypes
  127. columns.logicalTypes = map(dataclasses.LogicalType, columns.logicalTypes);
  128. // wrap argumentTypes (allow argumentTypes to be undefined)
  129. columns.argumentTypes = map(
  130. dataclasses.ArgumentType,
  131. columns.argumentTypes || []
  132. );
  133. // wrap types and set `$operators` attribute on each type
  134. const wrapType = pipe(dataclasses.Type, _set$operatorsToType(columns));
  135. columns.types = map(wrapType, columns.types);
  136. // wrap targets and set `$type` attribut on each target
  137. const wrapTarget = pipe(dataclasses.Target, _set$typeToTarget(columns));
  138. return Promise.all(map(wrapTarget, columns.targets)).then(targets => {
  139. columns.targets = targets;
  140. return columns;
  141. });
  142. };
  143. /**
  144. * Create a new PredicateCore
  145. * @param {Object} args Predicate Core parameters
  146. * @param {?dataclasses.CompoundPredicate} [args.data=core.defaults.options.getDefaultData] data
  147. * @param {Object} [args.columns] columns definition
  148. * @example <caption>Example of columns definition</caption>
  149. * // don't forget to take a look at the Storybook https://ui-predicate.fgribreau.com/ui-predicate-vue/latest#/examples
  150. * {
  151. * // first define the operator list available along with what type of argument they need
  152. * operators: [
  153. * {
  154. * operator_id: 'is',
  155. * label: 'is',
  156. * argumentType_id: 'smallString',
  157. * },
  158. * {
  159. * operator_id: 'contains',
  160. * label: 'Contains',
  161. * argumentType_id: 'smallString',
  162. * },
  163. * {
  164. * operator_id: 'isLowerThan',
  165. * label: '<',
  166. * argumentType_id: 'number',
  167. * },
  168. * {
  169. * operator_id: 'isEqualTo',
  170. * label: '=',
  171. * argumentType_id: 'number',
  172. * },
  173. * {
  174. * operator_id: 'isHigherThan',
  175. * label: '>',
  176. * argumentType_id: 'number',
  177. * },
  178. * {
  179. * operator_id: 'is_date',
  180. * label: 'is',
  181. * argumentType_id: 'datepicker',
  182. * },
  183. * {
  184. * operator_id: 'isBetween_date',
  185. * label: 'is between',
  186. * argumentType_id: 'daterangepicker',
  187. * },
  188. * ],
  189. * // then define the type, think of them as aggregate of operators so you can attach them to targets
  190. * types: [
  191. * {
  192. * type_id: 'int',
  193. * operator_ids: ['isLowerThan', 'isEqualTo', 'isHigherThan'],
  194. * },
  195. * {
  196. * type_id: 'string',
  197. * operator_ids: ['is', 'contains'],
  198. * },
  199. * {
  200. * type_id: 'datetime',
  201. * operator_ids: ['is', 'isBetween'],
  202. * },
  203. * ],
  204. * // finally define targets, don't forget to specify their associated `type_id`
  205. * targets: [
  206. * {
  207. * target_id: 'title',
  208. * label: 'Title',
  209. * type_id: 'string',
  210. * },
  211. * {
  212. * target_id: 'videoCount',
  213. * label: 'Video count',
  214. * type_id: 'int',
  215. * },
  216. * {
  217. * target_id: 'publishedAt',
  218. * label: 'Created at',
  219. * type_id: 'datetime',
  220. * },
  221. * ],
  222. * // define supported logical type
  223. * logicalTypes: [
  224. * {
  225. * logicalType_id: 'any',
  226. * label: 'Any',
  227. * },
  228. * {
  229. * logicalType_id: 'all',
  230. * label: 'All',
  231. * },
  232. * {
  233. * logicalType_id: 'none',
  234. * label: 'None',
  235. * },
  236. * ],
  237. * // (optional) finally define how to display each argumentType_id
  238. * argumentTypes: [
  239. * // here we don't define `component` because it depends on the UI Framework you are using (e.g. Vue, React, Angular, ...)
  240. * // since we are in ui-predicate-core here we don't know the UI Framework library that will be used
  241. * // read your UI Framework adapter (e.g. ui-predicate-vue) on how to set the component.
  242. * // if no argumentType is defined for argumentType_id, then UIPredicateCore will fallback on the default UI component (thanks to getDefaultArgumentComponent)
  243. * // { argumentType_id: 'datepicker', component: ? },
  244. * // { argumentType_id: 'daterangepicker', component: ? },
  245. * // { argumentType_id: 'smallString', component: ? },
  246. * // { argumentType_id: 'number', component: ? },
  247. * ]}
  248. * @param {Object} args.columns.operators operators
  249. * @param {Object} args.columns.types types
  250. * @param {Object} args.columns.targets targets
  251. * @param {Object} args.columns.logicalTypes logicalTypes
  252. * @param {?Object} args.columns.argumentTypes argumentTypes
  253. * @param {Object} [args.ui] overriden core ui-predicate component (see UITypes and examples)
  254. * @param {Object} [args.options=core.defaults.options] options
  255. * @return {Promise<core.PredicateCoreAPI, errors<*>>} resolved promise yield Predicate Core public API, rejected promise yield an error {@link errors}
  256. * @memberof core
  257. */
  258. function PredicateCore(args) {
  259. const { data, columns, ui, options } = args;
  260. const _ui = ui || {};
  261. return new Promise((resolve, reject) => {
  262. try {
  263. dataclasses._requireProps(
  264. columns,
  265. 'operators,logicalTypes,types,targets'
  266. );
  267. } catch (err) {
  268. reject(err);
  269. return;
  270. }
  271. resolve();
  272. })
  273. .then(() => _initializeColumns(columns))
  274. .then(_columns => {
  275. let _root;
  276. let _api;
  277. const _events = new EventEmitter();
  278. const _options = merge(PredicateCore.defaults.options, options);
  279. /**
  280. * Loop through the predicate tree and update flags (e.g. $canBeRemoved)
  281. * @return {undefined} nothing.
  282. * @private
  283. */
  284. function _apply$flags() {
  285. const canRemoveAnyPredicate = !rules.predicateToRemoveIsTheLastComparisonPredicate(
  286. _root,
  287. CompoundPredicate,
  288. ComparisonPredicate
  289. );
  290. CompoundPredicate.forEach(_root, predicate => {
  291. predicate.$canBeRemoved =
  292. canRemoveAnyPredicate &&
  293. !rules.predicateToRemoveIsRootPredicate(_root, predicate);
  294. });
  295. }
  296. function _emitChangedEvent() {
  297. _events.emit('changed', _api);
  298. }
  299. const _afterWrite = pipe(_apply$flags, _emitChangedEvent);
  300. /**
  301. * Set PredicateCore data
  302. * @param {dataclasses.CompoundPredicate} root CompoundPredicate
  303. * @return {Promise<undefined, errors.RootPredicateMustBeACompoundPredicate>} resolved promise yield nothing, rejected promise yield RootPredicateMustBeACompoundPredicate error
  304. * @since 1.0.0
  305. * @memberof core.api
  306. */
  307. function setData(root) {
  308. return invariants
  309. .RootPredicateMustBeACompoundPredicate(root, CompoundPredicate)
  310. .then(() => {
  311. _root = root;
  312. });
  313. }
  314. /**
  315. * @param {object} json user defined JSON
  316. * @return {Promise<Predicate, errors<*>>} resolved promise yield the root CompoundPredicate, rejected promise yield an errors
  317. */
  318. function _fromJSON(json) {
  319. return Predicate.fromJSON(json, {
  320. getTargetById: target_id =>
  321. _getTargetById(_columns.targets, target_id),
  322. getLogicalTypeById: logicalType_id =>
  323. _getLogicalTypeById(_columns.logicalTypes, logicalType_id),
  324. getOperatorById: operator_id =>
  325. _getOperatorById(_columns.operators, operator_id),
  326. });
  327. }
  328. /**
  329. * Add a ComparisonPredicate or CompoundPredicate
  330. * @param {Object} option option object
  331. * @param {string} options.type what type of Predicate to add
  332. * @param {string} [options.how=after] should we insert it before, after or instead of? (currently only after is supported)
  333. * @param {dataclasses.Predicate} options.where current element
  334. * @return {Promise<dataclasses.Predicate>} inserted predicate
  335. * @since 1.0.0
  336. * @memberof core.api
  337. */
  338. function add({ where, how = 'after', type }) {
  339. // currently only after is supported
  340. return (
  341. Promise.resolve()
  342. .then(() => invariants.AddOnlySupportsAfter(how))
  343. .then(() =>
  344. invariants.PredicateTypeMustBeValid(type, Predicate.Types)
  345. )
  346. // generate the Predicates
  347. .then(() => _options[`getDefault${type}`](_columns, _options))
  348. // then add it
  349. .then(predicate => {
  350. const isComparisonPredicate = ComparisonPredicate.is(where);
  351. if (isComparisonPredicate || CompoundPredicate.is(where)) {
  352. if (isComparisonPredicate) {
  353. // it's a comparisonpredicate
  354. // first find predicates array that contains the element
  355. const path = _find(where);
  356. // we are starting from a ComparisonPredicate that always live inside a CompoundPredicate.predicates array
  357. const [compoundpredicate, [_, index]] = takeLast(2, path);
  358. compoundpredicate.predicates = insert(
  359. index + 1,
  360. predicate,
  361. compoundpredicate.predicates
  362. );
  363. } else {
  364. // it's a compoundpredicate
  365. // we want to add a CompoundPredicate after a compound predicate
  366. // so we need to add it as its first .predicates entry
  367. where.predicates.unshift(predicate);
  368. }
  369. return predicate;
  370. }
  371. return Promise.reject(
  372. new errors.CannotAddSomethingElseThanACompoundPredicateOrAComparisonPredicate()
  373. );
  374. })
  375. );
  376. }
  377. /**
  378. * Remove a ComparisonPredicate or CompoundPredicate
  379. * @param {(dataclasses.ComparisonPredicate|dataclasses.CompoundPredicate)} predicate predicate
  380. * @return {Promise<dataclasses.Predicate>} yield the removed predicate, will reject the promise if remove was called with the root CompoundPredicate or the last ComparisonPredicate of the root CompoundPredicate
  381. * @since 1.0.0
  382. * @memberof core.api
  383. */
  384. function remove(predicate) {
  385. return Promise.resolve()
  386. .then(() =>
  387. invariants.RemovePredicateMustDifferFromRootPredicate(
  388. _root,
  389. predicate
  390. )
  391. )
  392. .then(() =>
  393. invariants.RemovePredicateCannotBeTheLastComparisonPredicate(
  394. _root,
  395. predicate,
  396. CompoundPredicate,
  397. ComparisonPredicate
  398. )
  399. )
  400. .then(() => {
  401. if (
  402. CompoundPredicate.is(predicate) ||
  403. ComparisonPredicate.is(predicate)
  404. ) {
  405. const path = _find(predicate);
  406. // we are starting from a ComparisonPredicate that always live
  407. // inside a CompoundPredicate.predicates array
  408. const [parentCompoundpredicate, [_, index]] = takeLast(2, path);
  409. parentCompoundpredicate.predicates.splice(index, 1);
  410. if (parentCompoundpredicate.predicates.length === 0) {
  411. // if there are not any more predicates
  412. // inside the parentCompoundpredicate, we should also remove it
  413. return remove(parentCompoundpredicate);
  414. }
  415. return predicate;
  416. }
  417. return Promise.reject(
  418. new errors.CannotRemoveSomethingElseThanACompoundPredicateOrAComparisonPredicate()
  419. );
  420. });
  421. }
  422. /**
  423. * Change a CompoundPredicate logical
  424. * @param {dataclasses.CompoundPredicate} predicate predicate
  425. * @param {string} newLogicalType_id newLogicalType_id
  426. * @return {Promise<undefined, errors.PredicateMustBeACompoundPredicate>} yield nothing if everything went right, otherwise yield a reject promise with the PredicateMustBeACompoundPredicate error
  427. * @since 1.0.0
  428. * @memberof core.api
  429. */
  430. function setPredicateLogicalType_id(predicate, newLogicalType_id) {
  431. return invariants
  432. .PredicateMustBeACompoundPredicate(predicate, CompoundPredicate)
  433. .then(() => {
  434. // first change the logical type
  435. return _getLogicalTypeById(
  436. _columns.logicalTypes,
  437. newLogicalType_id
  438. );
  439. })
  440. .then(logicalType => {
  441. predicate.logic = logicalType;
  442. });
  443. }
  444. /**
  445. * Change a predicate's target
  446. * @param {dataclasses.ComparisonPredicate} predicate predicate
  447. * @param {string} newTarget_id newTarget_id
  448. * @return {Promise<undefined, errors.PredicateMustBeAComparisonPredicate>} yield nothing if everything went right, otherwise yield a reject promise with the PredicateMustBeAComparisonPredicate error
  449. * @since 1.0.0
  450. * @memberof core.api
  451. */
  452. function setPredicateTarget_id(predicate, newTarget_id) {
  453. return (
  454. invariants
  455. .PredicateMustBeAComparisonPredicate(
  456. predicate,
  457. ComparisonPredicate
  458. )
  459. // first change the target
  460. .then(() => _getTargetById(_columns.targets, newTarget_id))
  461. .then(target => {
  462. predicate.target = target;
  463. // then change the operator to the first operator for this target
  464. return setPredicateOperator_id(
  465. predicate,
  466. head(predicate.target.$type.$operators).operator_id
  467. );
  468. })
  469. );
  470. }
  471. /**
  472. * Change a predicate's operator
  473. * @param {dataclasses.ComparisonPredicate} predicate predicate
  474. * @param {string} newOperator_id newOperator_id
  475. * @return {Promise<undefined, errors.Operator_idMustReferToADefinedOperator>} yield nothing if everything went right, otherwise yield a reject promise with the PredicateMustBeAComparisonPredicate error
  476. * @since 1.0.0
  477. * @memberof core.api
  478. */
  479. function setPredicateOperator_id(predicate, newOperator_id) {
  480. return (
  481. Promise.resolve()
  482. // find operator
  483. .then(() =>
  484. invariants.Operator_idMustReferToADefinedOperator(
  485. predicate.target.$type.$operators.find(
  486. operator => operator.operator_id === newOperator_id
  487. )
  488. )
  489. )
  490. // change the operator
  491. .then(operator => {
  492. predicate.operator = operator;
  493. // then reset arguments
  494. predicate.argument = null;
  495. })
  496. );
  497. }
  498. /**
  499. * Change a predicate's operator value
  500. * @param {dataclasses.ComparisonPredicate} predicate predicate
  501. * @param {*} newValue newValue
  502. * @return {Promise<undefined>} yield nothing if everything went right, currently everything always go right ;)
  503. * @since 1.0.0
  504. * @memberof core.api
  505. */
  506. function setArgumentValue(predicate, newValue) {
  507. return Promise.resolve().then(() => {
  508. predicate.argument = newValue;
  509. });
  510. }
  511. /**
  512. * Get a UI Component (e.g. Vue Component) based on the argumentType_id
  513. * @param {string} argumentType_id the argumentType id to find
  514. * @return {*} it will either yield the argumentType id associated component or fallback on {@link core.defaults.getArgumentTypeComponentById} to yield the default component
  515. * @since 1.0.0
  516. * @memberof core.api
  517. */
  518. function getArgumentTypeComponentById(argumentType_id) {
  519. return option
  520. .fromNullable(
  521. _columns.argumentTypes.find(
  522. argumentType => argumentType.argumentType_id === argumentType_id
  523. )
  524. )
  525. .map(argumentType => argumentType.component)
  526. .valueOrElse(() =>
  527. _options.getDefaultArgumentComponent(_columns, _options, _ui)
  528. );
  529. }
  530. /**
  531. * Get default or overrided ui component
  532. * @param {ui} name the UIType key to get the right component
  533. * @return {any} component
  534. * @since 1.0.0
  535. * @memberof core.api
  536. */
  537. function getUIComponent(name) {
  538. return ui[name];
  539. }
  540. /**
  541. * Compute the JSON pointer path the element
  542. * @param {Object} element (http://jsonpatch.com/)
  543. * @return {?Array} null if not found
  544. * @readonly
  545. * @since 1.0.0
  546. */
  547. function _find(element) {
  548. return CompoundPredicate.reduce(
  549. _root,
  550. (acc, predicate, parents) => {
  551. return element === predicate ? parents : acc;
  552. },
  553. null
  554. );
  555. }
  556. /**
  557. * Yield a serializable object without internal flags ($xxx properties)
  558. * @example console.log(JSON.stringify(ctrl, null, 2)); // will call ctrl.toJSON() underneath
  559. * @return {Object} a serializable object
  560. * @since 1.0.0
  561. * @memberof core.api
  562. */
  563. function toJSON() {
  564. return Predicate.toJSON(_root);
  565. }
  566. /**
  567. * Adds the `listener` function to the end of the listeners array for the event named `eventName`.
  568. * No checks are made to see if the `listener` has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.
  569. * @param {string} eventName available event names are : (`changed`, api)
  570. * @param {function} listener listener
  571. * @return {undefined}
  572. * @since 1.0.0
  573. * @memberof core.api
  574. */
  575. function on(eventName, listener) {
  576. _events.on(eventName, listener);
  577. }
  578. /**
  579. * Adds a *one-time* `listener` function for the event named `eventName`. The next time `eventName` is triggered, this `listener` is removed and then invoked.
  580. * @param {string} eventName see {@link core.api.on} for available event names
  581. * @param {function} listener listener
  582. * @return {undefined}
  583. * @see core.api.on
  584. * @since 1.0.0
  585. * @memberof core.api
  586. */
  587. function once(eventName, listener) {
  588. _events.once(eventName, listener);
  589. }
  590. /**
  591. * Remove listener(s)
  592. * If off() will remove every listeners
  593. * If off(eventName) will only remove listeners to this specific eventName
  594. * If off(eventName, listener) will remove the `listener` to the `eventName`
  595. * @param {?string} eventName see {@link core.api.on} for available event names
  596. * @param {?function} listener listener
  597. * @return {undefined}
  598. * @since 1.0.0
  599. * @memberof core.api
  600. */
  601. function off(eventName, listener) {
  602. if (!eventName) {
  603. // because removeAllListeners treat "undefined" as an event name :facepalm:
  604. _events.removeAllListeners();
  605. } else if (!listener) {
  606. _events.removeAllListeners(eventName);
  607. } else {
  608. _events.removeListener(eventName, listener);
  609. }
  610. }
  611. // get data for initialization
  612. return (
  613. (data ? _fromJSON(data) : _options.getDefaultData(_columns, _options))
  614. // setup PredicateCore data
  615. .then(_afterPromise(setData, _afterWrite))
  616. // expose public API
  617. .then(() => {
  618. /**
  619. * ui-predicate core public API
  620. * @typedef {object} PredicateCoreAPI
  621. * @namespace core.api
  622. */
  623. _api = {
  624. on,
  625. once,
  626. off,
  627. setData: _afterPromise(setData, _afterWrite),
  628. add: _afterPromise(add, _afterWrite),
  629. remove: _afterPromise(remove, _afterWrite),
  630. setPredicateTarget_id: _afterPromise(
  631. setPredicateTarget_id,
  632. _afterWrite
  633. ),
  634. setPredicateOperator_id: _afterPromise(
  635. setPredicateOperator_id,
  636. _afterWrite
  637. ),
  638. setPredicateLogicalType_id: _afterPromise(
  639. setPredicateLogicalType_id,
  640. _afterWrite
  641. ),
  642. setArgumentValue: _afterPromise(setArgumentValue, _afterWrite),
  643. getArgumentTypeComponentById,
  644. /**
  645. * Enumeration of overridable core ui-predicate component
  646. * @enum {String}
  647. */
  648. UITypes,
  649. /**
  650. * Get core UI component (e.g. target selector)
  651. * @param {core.ui} ui component name
  652. * @return {Object} component
  653. * @memberof core.api
  654. */
  655. getUIComponent,
  656. toJSON,
  657. /**
  658. * Get root CompoundPredicate
  659. * @return {dataclasses.CompoundPredicate} root CompoundPredicate
  660. * @memberof core.api
  661. */
  662. get root() {
  663. return _root;
  664. },
  665. // used for testing
  666. get columns() {
  667. return _columns;
  668. },
  669. // used for testing
  670. get options() {
  671. return _options;
  672. },
  673. };
  674. return _api;
  675. })
  676. );
  677. });
  678. }
  679. /**
  680. * Defaults configuration of PredicateCore
  681. * @type {Object}
  682. * @namespace core.defaults
  683. */
  684. PredicateCore.defaults = {
  685. /**
  686. * Defaults options of PredicateCore
  687. * @type {Object}
  688. * @namespace core.defaults.options
  689. */
  690. options: {
  691. /**
  692. * When data is not set at construction time PredicateCore default behavior will be to use the first target and its first operator with empty argument
  693. * @param {Object} columns every necessary data class
  694. * @param {Object} options PredicateCore available options
  695. * @return {Promise<dataclasses.CompoundPredicate>} root CompoundPredicate
  696. * @since 1.0.0
  697. * @memberof core.defaults.options
  698. */
  699. getDefaultData(columns, options) {
  700. return options
  701. .getDefaultComparisonPredicate(columns, options)
  702. .then(comparisonPredicate => {
  703. return options.getDefaultCompoundPredicate(columns, options, [
  704. comparisonPredicate,
  705. ]);
  706. });
  707. },
  708. /**
  709. * Default compount predicate to use
  710. *
  711. * This function is called whenever a new CompoundPredicate is added to the UIPredicate
  712. * @param {Object} columns specified columns
  713. * @param {Object} options PredicateCore available options
  714. * @param {Array<dataclasses.Predicate>} predicates array of predicates to include into the CompoundPredicate
  715. * @return {Promise<dataclasses.CompoundPredicate>} a CompoundPredicate
  716. * @since 1.0.0
  717. * @memberof core.defaults.options
  718. */
  719. getDefaultCompoundPredicate(columns, options, predicates) {
  720. return (!Array.isArray(predicates) || predicates.length === 0
  721. ? options
  722. .getDefaultComparisonPredicate(columns, options)
  723. .then(comparisonPredicate => [comparisonPredicate])
  724. : Promise.resolve(predicates)
  725. ).then(predicates =>
  726. options
  727. .getDefaultLogicalType(predicates, columns, options)
  728. .then(logicalType => CompoundPredicate(logicalType, predicates))
  729. );
  730. },
  731. /**
  732. * Default comparison predicate to use
  733. *
  734. * This function is called whenever a new ComparisonPredicate is added to the UIPredicate
  735. * @param {Object} columns specified columns
  736. * @param {Object} [options=PredicateCore.defaults.options] PredicateCore available options
  737. * @return {Promise<dataclasses.ComparisonPredicate>} a Comparison
  738. * @since 1.0.0
  739. * @memberof core.defaults.options
  740. */
  741. getDefaultComparisonPredicate(columns, options) {
  742. const firstTarget = head(columns.targets);
  743. return ComparisonPredicate(
  744. firstTarget,
  745. head(firstTarget.$type.$operators)
  746. );
  747. },
  748. /**
  749. * Default logical type to use when a new comparison predicate is created
  750. *
  751. * This function is called whenever a new ComparisonPredicate is added to the UIPredicate
  752. * @param {Array<dataclasses.Predicate>} predicates specified columns
  753. * @param {Object} columns specified columns
  754. * @param {Object} [options=PredicateCore.defaults.options] PredicateCore available options
  755. * @return {Promise<dataclasses.LogicalType>} a logical type
  756. * @since 1.0.0
  757. * @memberof core.defaults.options
  758. */
  759. getDefaultLogicalType(predicates, columns, options) {
  760. return Promise.resolve(head(columns.logicalTypes));
  761. },
  762. /**
  763. * Get the default UI component for any argument. Also used if no registered UI component match `argumentType_id`
  764. * @param {Object} columns specified columns
  765. * @param {Object} [options=PredicateCore.defaults.options] PredicateCore available options
  766. * @return {*} yield a UI Component (depending on the UI Framework used)
  767. * @throws if the UI Framework adapter did not override this function. Each UI Framework adapter (e.g. ui-predicate-vue, ui-predicate-react, ...) must implement this and let user override it
  768. * @memberof core.defaults.options
  769. */
  770. getDefaultArgumentComponent(columns, options) {
  771. throw new errors.UIFrameworkMustImplementgetDefaultArgumentComponent(
  772. 'UIFrameworkMustImplementgetDefaultArgumentComponent'
  773. );
  774. },
  775. },
  776. };
  777. return PredicateCore;
  778. };