diff --git a/README.md b/README.md index 20d4ee6..211cabb 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,15 @@ -# @seneca/config +# @seneca/traverse -> _Seneca Config_ is a plugin for [Seneca](http://senecajs.org) +> _Seneca Traverse_ is a plugin for [Seneca](http://senecajs.org) -Live configuration plugin for the Seneca framework. +Data Traverse plugin for the Seneca framework. -Unlike static configuration, this plugin lets you store keyed -configuration in your deployed persistent storage so that you can -change it on the live system. This is useful for things like currency -exchange rates, feature flags, A/B testing etc. - - -[![npm version](https://img.shields.io/npm/v/@seneca/config.svg)](https://npmjs.com/package/@seneca/config) -[![build](https://github.com/senecajs/SenecaConfig/actions/workflows/build.yml/badge.svg)](https://github.com/senecajs/SenecaConfig/actions/workflows/build.yml) -[![Coverage Status](https://coveralls.io/repos/github/senecajs/SenecaConfig/badge.svg?branch=main)](https://coveralls.io/github/senecajs/SenecaConfig?branch=main) -[![Known Vulnerabilities](https://snyk.io/test/github/senecajs/SenecaConfig/badge.svg)](https://snyk.io/test/github/senecajs/SenecaConfig) +[![npm version](https://img.shields.io/npm/v/@seneca/traverse.svg)](https://npmjs.com/package/@seneca/traverse) +[![build](https://github.com/senecajs/SenecaTraverse/actions/workflows/build.yml/badge.svg)](https://github.com/senecajs/SenecaTraverse/actions/workflows/build.yml) +[![Coverage Status](https://coveralls.io/repos/github/senecajs/SenecaTraverse/badge.svg?branch=main)](https://coveralls.io/github/senecajs/SenecaTraverse?branch=main) +[![Known Vulnerabilities](https://snyk.io/test/github/senecajs/SenecaTraverse/badge.svg)](https://snyk.io/test/github/senecajs/SenecaTraverse) [![DeepScan grade](https://deepscan.io/api/teams/5016/projects/26547/branches/846930/badge/grade.svg)](https://deepscan.io/dashboard#view=project&tid=5016&pid=26547&bid=846930) -[![Maintainability](https://api.codeclimate.com/v1/badges/3e5e5c11a17dbfbdd894/maintainability)](https://codeclimate.com/github/senecajs/SenecaConfig/maintainability) +[![Maintainability](https://api.codeclimate.com/v1/badges/3e5e5c11a17dbfbdd894/maintainability)](https://codeclimate.com/github/senecajs/SenecaTraverse/maintainability) | ![Voxgig](https://www.voxgig.com/res/img/vgt01r.png) | This open source module is sponsored and supported by [Voxgig](https://www.voxgig.com). | | ---------------------------------------------------- | --------------------------------------------------------------------------------------- | @@ -23,125 +17,54 @@ exchange rates, feature flags, A/B testing etc. ## Install ```sh -$ npm install @seneca/Config +$ npm install @seneca/traverse ``` ## Quick Example ```js -seneca.use('Config', {}) - -const initRes = await seneca.post('sys:config,init:val,key:a,val:1') -// === { ok: true, key: 'a', val: 1, entry: { key: 'a', val: 1 } } - -const getRes = await seneca.post('sys:config,get:val,key:a') -// === { ok: true, key: 'a', val: 1, entry: { key: 'a', val: 1 } } - -const setRes = await seneca.post('sys:config,set:val,key:a,val:2') -// === { ok: true, key: 'a', val: 1, entry: { key: 'a', val: 2 } } +seneca.use('Traverse', {}) +const depsRes = await seneca.post('sys:traverse,find:deps') +// === { ok: true, deps: [['foo/bar0,foo/bar1'],...] } ``` ## More Examples -Review the [unit tests](test/Config.test.ts) for more examples. - - +Review the [unit tests](test/Traverse.test.ts) for more examples. - ## Options -* `debug` : boolean -* `numparts` : number -* `canon` : object -* `init$` : boolean - +- `debug` : boolean +- `rootEntity` : string +- `relations` : { parental: array } - ## Action Patterns -* [sys:config,get:val](#-sysconfiggetval-) -* [sys:config,init:val](#-sysconfiginitval-) -* [sys:config,list:val](#-sysconfiglistval-) -* [sys:config,map:val](#-sysconfigmapval-) -* [sys:config,set:val](#-sysconfigsetval-) - +- [sys:traverse,find:deps](#-systraversefinddeps-) - ## Action Descriptions -### « `sys:config,get:val` » - -Get a config value by key. - - -#### Parameters - - -* __key__ : _string_ - - ----------- -### « `sys:config,init:val` » - -Initialise a config value by key (must not exist). - - -#### Parameters - - -* __key__ : _string_ -* __existing__ : _boolean_ (optional, default: `false`) - - ----------- -### « `sys:config,list:val` » - -List config values by query. - - -#### Parameters - - -* __q__ : _object_ (optional, default: `{}`) - - ----------- -### « `sys:config,map:val` » - -Get a map of config values by key prefix (dot separated). - - -#### Parameters - - -* __prefix__ : _string_ - - ----------- -### « `sys:config,set:val` » - -Set a config value by key (must exist). +### « `sys:traverse,find:deps` » +Returns a sorted list of entity pairs starting from a given entity. #### Parameters +- **rootEntity** : _string_ (optional, default: 'sys/user') +- **relations** : _object_ (optional, default : { parental: [] }) -* __key__ : _string_ - - ----------- - +--- diff --git a/dist-test/Traverse.test.js b/dist-test/Traverse.test.js index f7720c3..b08f397 100644 --- a/dist-test/Traverse.test.js +++ b/dist-test/Traverse.test.js @@ -22,5 +22,996 @@ const __2 = __importDefault(require("..")); await seneca.ready(); (0, code_1.expect)(seneca.find_plugin('Traverse')).exist(); }); + (0, node_test_1.test)('find-deps', async () => { + const seneca = makeSeneca().use(__2.default, { + relations: { + parental: [ + ['foo/bar2', 'foo/bar3'], + ['foo/bar0', 'foo/bar1'], + ['foo/bar0', 'foo/bar2'], + ['foo/bar1', 'foo/bar4'], + ['foo/bar1', 'foo/bar5'], + ['foo/bar3', 'foo/bar6'], + ['foo/bar4', 'foo/bar7'], + ['foo/bar5', 'foo/bar8'], + ['foo/bar0', 'foo/zed0'], + ['foo/zed0', 'foo/zed1'], + ['foo/zed1', 'foo/zed2'], + ['bar/baz0', 'bar/baz1'], + ['qux/test', 'qux/prod'], + ['foo/bar2', 'foo/bar9'], + ['foo/bar6', 'foo/bar10'], + ['foo/bar7', 'foo/bar11'], + ], + }, + }); + await seneca.ready(); + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar0', + }); + // console.log('RES', res) + (0, code_1.expect)(res.deps).equal([ + // Level 0 + ['foo/bar0', 'foo/bar1'], + ['foo/bar0', 'foo/bar2'], + ['foo/bar0', 'foo/zed0'], + // Level 1 + ['foo/bar1', 'foo/bar4'], + ['foo/bar1', 'foo/bar5'], + ['foo/bar2', 'foo/bar3'], + ['foo/bar2', 'foo/bar9'], + ['foo/zed0', 'foo/zed1'], + // Level 2 + // Sort each level alphabetically. + // Thus, foo/bar3 should be listed first, + // although its parent is foo/bar2 + ['foo/bar3', 'foo/bar6'], + ['foo/bar4', 'foo/bar7'], + ['foo/bar5', 'foo/bar8'], + ['foo/zed1', 'foo/zed2'], + // Level 3 + ['foo/bar6', 'foo/bar10'], + ['foo/bar7', 'foo/bar11'], + ]); + }); + (0, node_test_1.test)('find-deps-empty-list', async () => { + const seneca = makeSeneca().use(__2.default, { + relations: { + parental: [], + }, + }); + await seneca.ready(); + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar0', + }); + // console.log('RES', res) + (0, code_1.expect)(res.deps).equal([]); + }); + (0, node_test_1.test)('find-deps-no-children', async () => { + const seneca = makeSeneca().use(__2.default, { + relations: { + parental: [ + ['foo/bar1', 'foo/bar2'], + ['foo/bar2', 'foo/bar3'], + ], + }, + }); + await seneca.ready(); + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar0', + }); + // console.log('RES', res) + (0, code_1.expect)(res.deps).equal([]); + }); + (0, node_test_1.test)('find-deps-cycle', async () => { + const seneca = makeSeneca().use(__2.default, { + relations: { + parental: [ + ['foo/bar0', 'foo/bar1'], + ['foo/bar1', 'foo/bar2'], + // Cycle back to root + ['foo/bar2', 'foo/bar0'], + ], + }, + }); + await seneca.ready(); + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar0', + }); + // console.log('RES', res) + // Should only traverse once, ignoring the cycle + (0, code_1.expect)(res.deps).equal([ + ['foo/bar0', 'foo/bar1'], + ['foo/bar1', 'foo/bar2'], + ]); + }); + (0, node_test_1.test)('find-deps-cycle-middle', async () => { + const seneca = makeSeneca().use(__2.default, { + relations: { + parental: [ + ['foo/bar0', 'foo/bar1'], + ['foo/bar1', 'foo/bar2'], + ['foo/bar2', 'foo/bar3'], + // Cycle bar1 -> bar2 -> bar3 -> bar1 + ['foo/bar3', 'foo/bar1'], + ['foo/bar2', 'foo/bar4'], + ], + }, + }); + await seneca.ready(); + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar0', + }); + // Each node visited only once despite cycle + (0, code_1.expect)(res.deps).equal([ + ['foo/bar0', 'foo/bar1'], + ['foo/bar1', 'foo/bar2'], + ['foo/bar2', 'foo/bar3'], + ['foo/bar2', 'foo/bar4'], + ]); + }); + (0, node_test_1.test)('find-deps-linear', async () => { + const seneca = makeSeneca().use(__2.default, { + relations: { + parental: [ + ['foo/bar0', 'foo/bar1'], + ['foo/bar1', 'foo/bar2'], + ['foo/bar2', 'foo/bar3'], + ['foo/bar3', 'foo/bar4'], + ], + }, + }); + await seneca.ready(); + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar0', + }); + // console.log('RES', res) + (0, code_1.expect)(res.deps).equal([ + ['foo/bar0', 'foo/bar1'], + ['foo/bar1', 'foo/bar2'], + ['foo/bar2', 'foo/bar3'], + ['foo/bar3', 'foo/bar4'], + ]); + }); + (0, node_test_1.test)('find-deps-duplicate', async () => { + const seneca = makeSeneca().use(__2.default, { + relations: { + parental: [ + ['foo/bar0', 'foo/bar1'], + // Duplicate + ['foo/bar0', 'foo/bar1'], + ['foo/bar1', 'foo/bar2'], + // Duplicate + ['foo/bar1', 'foo/bar2'], + ], + }, + }); + await seneca.ready(); + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar0', + }); + (0, code_1.expect)(res.deps).equal([ + ['foo/bar0', 'foo/bar1'], + ['foo/bar1', 'foo/bar2'], + ]); + }); + (0, node_test_1.test)('find-deps-convergent', async () => { + const seneca = makeSeneca().use(__2.default, { + relations: { + parental: [ + ['foo/bar0', 'foo/bar1'], + ['foo/bar0', 'foo/bar2'], + ['foo/bar1', 'foo/bar3'], + // bar3 reachable from two paths + ['foo/bar2', 'foo/bar3'], + ['foo/bar3', 'foo/bar4'], + ], + }, + }); + await seneca.ready(); + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar0', + }); + // console.log('RES', res) + // bar3 should only appear once (first path wins) + (0, code_1.expect)(res.deps).equal([ + ['foo/bar0', 'foo/bar1'], + ['foo/bar0', 'foo/bar2'], + ['foo/bar1', 'foo/bar3'], + ['foo/bar3', 'foo/bar4'], + ]); + }); + (0, node_test_1.test)('find-deps-two-convergent', async () => { + const seneca = makeSeneca().use(__2.default, { + relations: { + parental: [ + ['foo/bar0', 'foo/bar1'], + ['foo/bar0', 'foo/bar2'], + ['foo/bar0', 'foo/bar3'], + ['foo/bar1', 'foo/bar4'], + // bar4 from two parents + ['foo/bar2', 'foo/bar4'], + ['foo/bar3', 'foo/bar5'], + ['foo/bar4', 'foo/bar6'], + // bar6 from two parents at different levels + ['foo/bar5', 'foo/bar6'], + ], + }, + }); + await seneca.ready(); + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar0', + }); + (0, code_1.expect)(res.deps).equal([ + ['foo/bar0', 'foo/bar1'], + ['foo/bar0', 'foo/bar2'], + ['foo/bar0', 'foo/bar3'], + ['foo/bar1', 'foo/bar4'], + ['foo/bar3', 'foo/bar5'], + ['foo/bar4', 'foo/bar6'], + ]); + }); + (0, node_test_1.test)('find-deps-self-ref', async () => { + const seneca = makeSeneca().use(__2.default, { + relations: { + parental: [ + ['foo/bar0', 'foo/bar1'], + // Self loop + ['foo/bar1', 'foo/bar1'], + ['foo/bar1', 'foo/bar2'], + ], + }, + }); + await seneca.ready(); + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar0', + }); + // console.log('RES', res) + (0, code_1.expect)(res.deps).equal([ + ['foo/bar0', 'foo/bar1'], + ['foo/bar1', 'foo/bar2'], + ]); + }); + (0, node_test_1.test)('find-deps-all-default', async () => { + const seneca = makeSeneca().use(__2.default); + await seneca.ready(); + const res = await seneca.post('sys:traverse,find:deps'); + // console.log('RES', res) + (0, code_1.expect)(res.deps).equal([]); + }); + (0, node_test_1.test)('find-deps-empty-list', async () => { + const seneca = makeSeneca().use(__2.default, { + relations: { + parental: [], + }, + }); + await seneca.ready(); + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar0', + }); + // console.log('RES', res) + (0, code_1.expect)(res.deps).equal([]); + }); + (0, node_test_1.test)('find-deps-l1', async () => { + const seneca = makeSeneca().use(__2.default, { + relations: { + parental: [ + ['foo/bar2', 'foo/bar3'], + ['foo/bar0', 'foo/bar1'], + ['foo/bar0', 'foo/bar2'], + ['foo/bar1', 'foo/bar4'], + ['foo/bar1', 'foo/bar5'], + ['foo/bar3', 'foo/bar6'], + ['foo/bar4', 'foo/bar7'], + ['foo/bar5', 'foo/bar8'], + ['foo/bar0', 'foo/zed0'], + ['foo/zed0', 'foo/zed1'], + ['foo/zed1', 'foo/zed2'], + ['bar/baz0', 'bar/baz1'], + ['qux/test', 'qux/prod'], + ['foo/bar2', 'foo/bar9'], + ['foo/bar6', 'foo/bar10'], + ['foo/bar7', 'foo/bar11'], + ], + }, + }); + await seneca.ready(); + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar1', + }); + (0, code_1.expect)(res.deps).equal([ + // Level 0 + ['foo/bar1', 'foo/bar4'], + ['foo/bar1', 'foo/bar5'], + // Level 1 + ['foo/bar4', 'foo/bar7'], + ['foo/bar5', 'foo/bar8'], + // Level 2 + ['foo/bar7', 'foo/bar11'], + ]); + }); + (0, node_test_1.test)('find-deps-l1-convergent', async () => { + const seneca = makeSeneca().use(__2.default, { + relations: { + parental: [ + ['foo/bar2', 'foo/bar3'], + ['foo/bar0', 'foo/bar1'], + ['foo/bar0', 'foo/bar2'], + ['foo/bar1', 'foo/bar4'], + ['foo/bar1', 'foo/bar5'], + ['foo/bar3', 'foo/bar6'], + ['foo/bar4', 'foo/bar7'], + ['foo/bar5', 'foo/bar8'], + ['foo/bar0', 'foo/zed0'], + ['foo/zed0', 'foo/zed1'], + ['foo/zed1', 'foo/zed2'], + ['bar/baz0', 'bar/baz1'], + ['qux/test', 'qux/prod'], + ['foo/bar2', 'foo/bar9'], + ['foo/bar6', 'foo/bar10'], + ['foo/bar7', 'foo/bar11'], + ['foo/bar4', 'foo/bar12'], + // bar12 converges from bar4 and bar5 + ['foo/bar5', 'foo/bar12'], + ['foo/bar12', 'foo/bar13'], + ], + }, + }); + await seneca.ready(); + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar1', + }); + (0, code_1.expect)(res.deps).equal([ + // Level 0 + ['foo/bar1', 'foo/bar4'], + ['foo/bar1', 'foo/bar5'], + // Level 1 + ['foo/bar4', 'foo/bar7'], + ['foo/bar4', 'foo/bar12'], + ['foo/bar5', 'foo/bar8'], + // Level 2 + ['foo/bar7', 'foo/bar11'], + ['foo/bar12', 'foo/bar13'], + ]); + }); + (0, node_test_1.test)('find-deps-l1-cycle', async () => { + const seneca = makeSeneca().use(__2.default, { + relations: { + parental: [ + ['foo/bar2', 'foo/bar3'], + ['foo/bar0', 'foo/bar1'], + ['foo/bar0', 'foo/bar2'], + ['foo/bar1', 'foo/bar4'], + ['foo/bar1', 'foo/bar5'], + ['foo/bar3', 'foo/bar6'], + ['foo/bar4', 'foo/bar7'], + ['foo/bar5', 'foo/bar8'], + ['foo/bar0', 'foo/zed0'], + ['foo/zed0', 'foo/zed1'], + ['foo/zed1', 'foo/zed2'], + ['bar/baz0', 'bar/baz1'], + ['qux/test', 'qux/prod'], + ['foo/bar2', 'foo/bar9'], + ['foo/bar6', 'foo/bar10'], + ['foo/bar7', 'foo/bar11'], + // Cycle: bar1 -> bar4 -> bar7 -> bar1 + ['foo/bar7', 'foo/bar1'], + ['foo/bar8', 'foo/bar12'], + ], + }, + }); + await seneca.ready(); + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar1', + }); + (0, code_1.expect)(res.deps).equal([ + // Level 0 + ['foo/bar1', 'foo/bar4'], + ['foo/bar1', 'foo/bar5'], + // Level 1 + ['foo/bar4', 'foo/bar7'], + ['foo/bar5', 'foo/bar8'], + // Level 2 + ['foo/bar7', 'foo/bar11'], + ['foo/bar8', 'foo/bar12'], + ]); + }); + (0, node_test_1.test)('find-deps-l2', async () => { + const seneca = makeSeneca().use(__2.default, { + relations: { + parental: [ + ['foo/bar2', 'foo/bar3'], + ['foo/bar0', 'foo/bar1'], + ['foo/bar0', 'foo/bar2'], + ['foo/bar1', 'foo/bar4'], + ['foo/bar1', 'foo/bar5'], + ['foo/bar3', 'foo/bar6'], + ['foo/bar4', 'foo/bar7'], + ['foo/bar5', 'foo/bar8'], + ['foo/bar0', 'foo/zed0'], + ['foo/zed0', 'foo/zed1'], + ['foo/zed1', 'foo/zed2'], + ['bar/baz0', 'bar/baz1'], + ['qux/test', 'qux/prod'], + ['foo/bar2', 'foo/bar9'], + ['foo/bar6', 'foo/bar10'], + ['foo/bar7', 'foo/bar11'], + ], + }, + }); + await seneca.ready(); + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar3', + }); + // console.log('RES', res) + (0, code_1.expect)(res.deps).equal([ + // Level 0 + ['foo/bar3', 'foo/bar6'], + // Level 1 + ['foo/bar6', 'foo/bar10'], + ]); + }); + (0, node_test_1.test)('find-deps-l2-convergent', async () => { + const seneca = makeSeneca().use(__2.default, { + relations: { + parental: [ + ['foo/bar2', 'foo/bar3'], + ['foo/bar0', 'foo/bar1'], + ['foo/bar0', 'foo/bar2'], + ['foo/bar1', 'foo/bar4'], + ['foo/bar1', 'foo/bar5'], + ['foo/bar3', 'foo/bar6'], + ['foo/bar4', 'foo/bar7'], + ['foo/bar5', 'foo/bar8'], + ['foo/bar0', 'foo/zed0'], + ['foo/zed0', 'foo/zed1'], + ['foo/zed1', 'foo/zed2'], + ['bar/baz0', 'bar/baz1'], + ['qux/test', 'qux/prod'], + ['foo/bar2', 'foo/bar9'], + ['foo/bar6', 'foo/bar10'], + ['foo/bar7', 'foo/bar11'], + ['foo/bar3', 'foo/bar12'], + ['foo/bar6', 'foo/bar13'], + ['foo/bar10', 'foo/bar14'], + // bar14 converges from bar10 and bar12 + ['foo/bar12', 'foo/bar14'], + ['foo/bar14', 'foo/bar15'], + ], + }, + }); + await seneca.ready(); + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar3', + }); + (0, code_1.expect)(res.deps).equal([ + // Level 0 + ['foo/bar3', 'foo/bar6'], + ['foo/bar3', 'foo/bar12'], + // Level 1 + ['foo/bar6', 'foo/bar10'], + ['foo/bar6', 'foo/bar13'], + ['foo/bar12', 'foo/bar14'], + // Level 2 + ['foo/bar14', 'foo/bar15'], + ]); + }); + (0, node_test_1.test)('find-deps-l2-cycle', async () => { + const seneca = makeSeneca().use(__2.default, { + relations: { + parental: [ + ['foo/bar2', 'foo/bar3'], + ['foo/bar0', 'foo/bar1'], + ['foo/bar0', 'foo/bar2'], + ['foo/bar1', 'foo/bar4'], + ['foo/bar1', 'foo/bar5'], + ['foo/bar3', 'foo/bar6'], + ['foo/bar4', 'foo/bar7'], + ['foo/bar5', 'foo/bar8'], + ['foo/bar0', 'foo/zed0'], + ['foo/zed0', 'foo/zed1'], + ['foo/zed1', 'foo/zed2'], + ['bar/baz0', 'bar/baz1'], + ['qux/test', 'qux/prod'], + ['foo/bar2', 'foo/bar9'], + ['foo/bar6', 'foo/bar10'], + ['foo/bar7', 'foo/bar11'], + ['foo/bar10', 'foo/bar12'], + // Cycle: bar3 -> bar6 -> bar10 -> bar12 -> bar3 + ['foo/bar12', 'foo/bar3'], + ['foo/bar10', 'foo/bar13'], + ], + }, + }); + await seneca.ready(); + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar3', + }); + (0, code_1.expect)(res.deps).equal([ + // Level 0 + ['foo/bar3', 'foo/bar6'], + // Level 1 + ['foo/bar6', 'foo/bar10'], + // Level 2 + ['foo/bar10', 'foo/bar12'], + ['foo/bar10', 'foo/bar13'], + ]); + }); + (0, node_test_1.test)('find-deps-l2-multi-level-convergent', async () => { + const seneca = makeSeneca().use(__2.default, { + relations: { + parental: [ + ['foo/bar2', 'foo/bar3'], + ['foo/bar0', 'foo/bar1'], + ['foo/bar0', 'foo/bar2'], + ['foo/bar1', 'foo/bar4'], + ['foo/bar1', 'foo/bar5'], + ['foo/bar3', 'foo/bar6'], + ['foo/bar4', 'foo/bar7'], + ['foo/bar5', 'foo/bar8'], + ['foo/bar0', 'foo/zed0'], + ['foo/zed0', 'foo/zed1'], + ['foo/zed1', 'foo/zed2'], + ['bar/baz0', 'bar/baz1'], + ['qux/test', 'qux/prod'], + ['foo/bar2', 'foo/bar9'], + ['foo/bar6', 'foo/bar10'], + ['foo/bar7', 'foo/bar11'], + ['foo/bar3', 'foo/bar12'], + ['foo/bar3', 'foo/bar13'], + ['foo/bar12', 'foo/bar14'], + // Second path to bar14 + ['foo/bar13', 'foo/bar14'], + // Third path to bar14 + ['foo/bar6', 'foo/bar14'], + ['foo/bar14', 'foo/bar15'], + ], + }, + }); + await seneca.ready(); + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar3', + }); + (0, code_1.expect)(res.deps).equal([ + // Level 0 + ['foo/bar3', 'foo/bar6'], + ['foo/bar3', 'foo/bar12'], + ['foo/bar3', 'foo/bar13'], + // Level 1 + ['foo/bar6', 'foo/bar10'], + ['foo/bar12', 'foo/bar14'], + // Level 2 + ['foo/bar14', 'foo/bar15'], + ]); + }); + (0, node_test_1.test)('find-deps-single-cycle', async () => { + const seneca = makeSeneca().use(__2.default, { + relations: { + parental: [ + ['A', 'B'], + ['A', 'C'], + ['C', 'E'], + ['C', 'D'], + ['E', 'G'], + ['E', 'F'], + ['F', 'H'], + ['C', 'A'], + ['N', 'M'], + ], + }, + }); + await seneca.ready(); + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'C', + }); + (0, code_1.expect)(res.deps).equal([ + // Level 0 + ['C', 'A'], + ['C', 'D'], + ['C', 'E'], + // Level 1 + ['A', 'B'], + ['E', 'F'], + ['E', 'G'], + // Level 2 + ['F', 'H'], + ]); + }); + (0, node_test_1.test)('find-deps-single-missing-node', async () => { + const seneca = makeSeneca().use(__2.default, { + relations: { + parental: [ + ['A', 'B'], + ['B', 'C'], + ['D', 'E'], + ], + }, + }); + await seneca.ready(); + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'X', + }); + // X has no children + (0, code_1.expect)(res.deps).equal([]); + }); + (0, node_test_1.test)('find-deps-deep-linear-chain', async () => { + const seneca = makeSeneca().use(__2.default, { + relations: { + parental: [ + ['node0', 'node1'], + ['node1', 'node2'], + ['node2', 'node3'], + ['node3', 'node4'], + ['node4', 'node5'], + ['node5', 'node6'], + ['node6', 'node7'], + ['node7', 'node8'], + ['node8', 'node9'], + ['node9', 'node10'], + ], + }, + }); + await seneca.ready(); + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'node0', + }); + (0, code_1.expect)(res.deps).equal([ + ['node0', 'node1'], + ['node1', 'node2'], + ['node2', 'node3'], + ['node3', 'node4'], + ['node4', 'node5'], + ['node5', 'node6'], + ['node6', 'node7'], + ['node7', 'node8'], + ['node8', 'node9'], + ['node9', 'node10'], + ]); + }); + (0, node_test_1.test)('find-deps-binary-tree', async () => { + const seneca = makeSeneca().use(__2.default, { + relations: { + parental: [ + ['A', 'B'], + ['A', 'C'], + ['B', 'D'], + ['B', 'E'], + ['C', 'F'], + ['C', 'G'], + ['D', 'H'], + ['D', 'I'], + ['E', 'J'], + ['E', 'K'], + ], + }, + }); + await seneca.ready(); + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'A', + }); + (0, code_1.expect)(res.deps).equal([ + // Level 0 + ['A', 'B'], + ['A', 'C'], + // Level 1 + ['B', 'D'], + ['B', 'E'], + ['C', 'F'], + ['C', 'G'], + // Level 2 + ['D', 'H'], + ['D', 'I'], + ['E', 'J'], + ['E', 'K'], + ]); + }); + (0, node_test_1.test)('find-deps-diamond-pattern', async () => { + const seneca = makeSeneca().use(__2.default, { + relations: { + parental: [ + ['A', 'B'], + ['A', 'C'], + ['B', 'D'], + ['C', 'D'], + ['D', 'E'], + ], + }, + }); + await seneca.ready(); + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'A', + }); + // D is reached first from B (alphabetically first) + (0, code_1.expect)(res.deps).equal([ + // Level 0 + ['A', 'B'], + ['A', 'C'], + // Level 1 - D reached via B + ['B', 'D'], + // Level 2 + ['D', 'E'], + ]); + }); + (0, node_test_1.test)('find-deps-mixed-alph-sort', async () => { + const seneca = makeSeneca().use(__2.default, { + relations: { + parental: [ + ['root', 'node10'], + ['root', 'node2'], + ['root', 'node1'], + ['root', 'node20'], + ['node1', 'child2'], + ['node2', 'child1'], + ['node10', 'child10'], + ['node20', 'child20'], + ], + }, + }); + await seneca.ready(); + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'root', + }); + // Natural sorting: node1, node2, node10, node20 + (0, code_1.expect)(res.deps).equal([ + // Level 0 + ['root', 'node1'], + ['root', 'node2'], + ['root', 'node10'], + ['root', 'node20'], + // Level 1 + ['node1', 'child2'], + ['node2', 'child1'], + ['node10', 'child10'], + ['node20', 'child20'], + ]); + }); + (0, node_test_1.test)('find-deps-all-nodes-converge-to-one', async () => { + const seneca = makeSeneca().use(__2.default, { + relations: { + parental: [ + ['root', 'A'], + ['root', 'B'], + ['root', 'C'], + ['A', 'target'], + ['B', 'target'], + ['C', 'target'], + ['target', 'end'], + ], + }, + }); + await seneca.ready(); + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'root', + }); + // target reached first via A (alphabetically first) + (0, code_1.expect)(res.deps).equal([ + // Level 0 + ['root', 'A'], + ['root', 'B'], + ['root', 'C'], + // Level 1 - target via A + ['A', 'target'], + // Level 2 + ['target', 'end'], + ]); + }); + (0, node_test_1.test)('find-deps-deep-10-levels-complex', async () => { + const seneca = makeSeneca().use(__2.default, { + relations: { + parental: [ + // ========== LEVEL 0 → LEVEL 1 ========== + ['foo', 'bar'], + ['foo', 'zed'], + ['foo', 'qux'], + // ========== LEVEL 1 → LEVEL 2 ========== + ['bar', 'bar1'], + ['bar', 'bar2'], + ['zed', 'zed1'], + ['zed', 'zed2'], + ['qux', 'qux1'], + // ========== LEVEL 2 → LEVEL 3 ========== + ['bar1', 'bar3'], + ['bar1', 'bar4'], + ['bar2', 'bar5'], + ['zed1', 'zed3'], + ['zed1', 'zed4'], + ['zed2', 'zed5'], + ['qux1', 'qux2'], + // ========== LEVEL 3 → LEVEL 4 ========== + ['bar3', 'bar6'], + ['bar3', 'bar7'], + ['bar4', 'bar8'], + ['bar5', 'bar9'], + ['zed3', 'zed6'], + ['zed4', 'zed7'], + ['zed5', 'zed8'], + ['qux2', 'qux3'], + // ========== LEVEL 4 → LEVEL 5 ========== + ['bar6', 'bar10'], + ['bar7', 'bar11'], + ['bar8', 'bar12'], + ['bar9', 'bar13'], + ['zed6', 'zed9'], + ['zed7', 'zed10'], + ['zed8', 'zed11'], + ['qux3', 'qux4'], + // ========== LEVEL 5 → LEVEL 6 (switch to composed names) ========== + ['bar10', 'foo/bar1'], + ['bar10', 'foo/bar2'], + ['bar11', 'foo/bar3'], + ['bar12', 'foo/bar4'], + ['bar13', 'foo/bar5'], + ['zed9', 'foo/zed1'], + ['zed10', 'foo/zed2'], + ['zed11', 'foo/zed3'], + // Cycle: qux4 → foo (already visited at level 0) + ['qux4', 'foo'], + ['qux4', 'foo/qux1'], + // ========== LEVEL 6 → LEVEL 7 ========== + ['foo/bar1', 'foo/bar6'], + ['foo/bar1', 'foo/bar7'], + ['foo/bar2', 'foo/bar8'], + ['foo/bar3', 'foo/bar9'], + ['foo/bar4', 'foo/bar10'], + ['foo/bar5', 'foo/bar11'], + ['foo/zed1', 'foo/zed4'], + ['foo/zed2', 'foo/zed5'], + ['foo/zed3', 'foo/zed6'], + ['foo/qux1', 'foo/qux2'], + // ========== LEVEL 7 → LEVEL 8 ========== + ['foo/bar6', 'bar/foo1'], + ['foo/bar7', 'bar/foo2'], + ['foo/bar8', 'bar/foo3'], + ['foo/bar9', 'bar/foo4'], + ['foo/bar10', 'bar/foo5'], + ['foo/bar11', 'bar/foo6'], + ['foo/zed4', 'zed/foo1'], + ['foo/zed5', 'zed/foo2'], + // Convergent: foo/zed6 and foo/qux2 both point to zed/foo3 + ['foo/zed6', 'zed/foo3'], + ['foo/qux2', 'zed/foo3'], + // ========== LEVEL 8 → LEVEL 9 ========== + ['bar/foo1', 'bar/foo7'], + ['bar/foo2', 'bar/foo8'], + ['bar/foo3', 'bar/foo9'], + ['bar/foo4', 'bar/foo10'], + ['bar/foo5', 'bar/foo11'], + ['bar/foo6', 'bar/foo12'], + ['zed/foo1', 'zed/foo4'], + ['zed/foo2', 'zed/foo5'], + ['zed/foo3', 'zed/foo6'], + // ========== LEVEL 9 → LEVEL 10 ========== + ['bar/foo7', 'qux/bar1'], + ['bar/foo8', 'qux/bar2'], + ['bar/foo9', 'qux/bar3'], + ['bar/foo10', 'qux/bar4'], + ['bar/foo11', 'qux/bar5'], + ['bar/foo12', 'qux/bar6'], + ['zed/foo4', 'qux/zed1'], + ['zed/foo5', 'qux/zed2'], + ['zed/foo6', 'qux/zed3'], + // ========== LEVEL 10 → LEVEL 11 ========== + ['qux/bar1', 'qux/bar7'], + ['qux/bar2', 'qux/bar8'], + ['qux/bar3', 'qux/bar9'], + ['qux/bar4', 'qux/bar10'], + ['qux/bar5', 'qux/bar11'], + ['qux/bar6', 'qux/bar12'], + ['qux/zed1', 'qux/zed4'], + ['qux/zed2', 'qux/zed5'], + ['qux/zed3', 'qux/zed6'], + ], + }, + }); + await seneca.ready(); + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo', + }); + (0, code_1.expect)(res.deps).equal([ + // ========== LEVEL 0 ========== + ['foo', 'bar'], + ['foo', 'qux'], + ['foo', 'zed'], + // ========== LEVEL 1 ========== + ['bar', 'bar1'], + ['bar', 'bar2'], + ['qux', 'qux1'], + ['zed', 'zed1'], + ['zed', 'zed2'], + // ========== LEVEL 2 ========== + ['bar1', 'bar3'], + ['bar1', 'bar4'], + ['bar2', 'bar5'], + ['qux1', 'qux2'], + ['zed1', 'zed3'], + ['zed1', 'zed4'], + ['zed2', 'zed5'], + // ========== LEVEL 3 ========== + ['bar3', 'bar6'], + ['bar3', 'bar7'], + ['bar4', 'bar8'], + ['bar5', 'bar9'], + ['qux2', 'qux3'], + ['zed3', 'zed6'], + ['zed4', 'zed7'], + ['zed5', 'zed8'], + // ========== LEVEL 4 ========== + ['bar6', 'bar10'], + ['bar7', 'bar11'], + ['bar8', 'bar12'], + ['bar9', 'bar13'], + ['qux3', 'qux4'], + ['zed6', 'zed9'], + ['zed7', 'zed10'], + ['zed8', 'zed11'], + // ========== LEVEL 5 ========== + ['bar10', 'foo/bar1'], + ['bar10', 'foo/bar2'], + ['bar11', 'foo/bar3'], + ['bar12', 'foo/bar4'], + ['bar13', 'foo/bar5'], + // Note: qux4 → foo creates cycle (foo visited at level 0) + ['qux4', 'foo/qux1'], + ['zed9', 'foo/zed1'], + ['zed10', 'foo/zed2'], + ['zed11', 'foo/zed3'], + // ========== LEVEL 6 ========== + ['foo/bar1', 'foo/bar6'], + ['foo/bar1', 'foo/bar7'], + ['foo/bar2', 'foo/bar8'], + ['foo/bar3', 'foo/bar9'], + ['foo/bar4', 'foo/bar10'], + ['foo/bar5', 'foo/bar11'], + ['foo/qux1', 'foo/qux2'], + ['foo/zed1', 'foo/zed4'], + ['foo/zed2', 'foo/zed5'], + ['foo/zed3', 'foo/zed6'], + // ========== LEVEL 7 ========== + ['foo/bar6', 'bar/foo1'], + ['foo/bar7', 'bar/foo2'], + ['foo/bar8', 'bar/foo3'], + ['foo/bar9', 'bar/foo4'], + ['foo/bar10', 'bar/foo5'], + ['foo/bar11', 'bar/foo6'], + ['foo/qux2', 'zed/foo3'], + ['foo/zed4', 'zed/foo1'], + ['foo/zed5', 'zed/foo2'], + // Note: foo/zed6 → zed/foo3 is skipped because zed/foo3 was already visited via foo/qux2 + // ========== LEVEL 8 ========== + ['bar/foo1', 'bar/foo7'], + ['bar/foo2', 'bar/foo8'], + ['bar/foo3', 'bar/foo9'], + ['bar/foo4', 'bar/foo10'], + ['bar/foo5', 'bar/foo11'], + ['bar/foo6', 'bar/foo12'], + ['zed/foo1', 'zed/foo4'], + ['zed/foo2', 'zed/foo5'], + ['zed/foo3', 'zed/foo6'], + // ========== LEVEL 9 ========== + ['bar/foo7', 'qux/bar1'], + ['bar/foo8', 'qux/bar2'], + ['bar/foo9', 'qux/bar3'], + ['bar/foo10', 'qux/bar4'], + ['bar/foo11', 'qux/bar5'], + ['bar/foo12', 'qux/bar6'], + ['zed/foo4', 'qux/zed1'], + ['zed/foo5', 'qux/zed2'], + ['zed/foo6', 'qux/zed3'], + // ========== LEVEL 10 ========== + ['qux/bar1', 'qux/bar7'], + ['qux/bar2', 'qux/bar8'], + ['qux/bar3', 'qux/bar9'], + ['qux/bar4', 'qux/bar10'], + ['qux/bar5', 'qux/bar11'], + ['qux/bar6', 'qux/bar12'], + ['qux/zed1', 'qux/zed4'], + ['qux/zed2', 'qux/zed5'], + ['qux/zed3', 'qux/zed6'], + ]); + }); }); +function makeSeneca(opts = {}) { + const seneca = (0, seneca_1.default)({ legacy: false }).test().use('promisify').use('entity'); + return seneca; +} //# sourceMappingURL=Traverse.test.js.map \ No newline at end of file diff --git a/dist-test/Traverse.test.js.map b/dist-test/Traverse.test.js.map index d8ff3bc..a7f9b21 100644 --- a/dist-test/Traverse.test.js.map +++ b/dist-test/Traverse.test.js.map @@ -1 +1 @@ -{"version":3,"file":"Traverse.test.js","sourceRoot":"","sources":["../test/Traverse.test.ts"],"names":[],"mappings":";AAAA,gEAAgE;;;;;AAEhE,yCAA0C;AAC1C,qCAAmC;AAEnC,oDAA2B;AAC3B,8CAA8C;AAC9C,8CAA8C;AAE9C,2CAA4B;AAC5B,2CAAyB;AAEzB,IAAA,oBAAQ,EAAC,UAAU,EAAE,GAAG,EAAE;IACxB,IAAA,gBAAI,EAAC,aAAa,EAAE,KAAK,IAAI,EAAE;QAC7B,IAAA,aAAM,EAAC,WAAW,CAAC,CAAC,KAAK,EAAE,CAAA;QAE3B,MAAM,MAAM,GAAG,IAAA,gBAAM,EAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;aACrC,IAAI,EAAE;aACN,GAAG,CAAC,WAAW,CAAC;aAChB,GAAG,CAAC,QAAQ,CAAC;aACb,GAAG,CAAC,WAAQ,CAAC,CAAA;QAEhB,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QAEpB,IAAA,aAAM,EAAC,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,EAAE,CAAA;IAChD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"} \ No newline at end of file +{"version":3,"file":"Traverse.test.js","sourceRoot":"","sources":["../test/Traverse.test.ts"],"names":[],"mappings":";AAAA,gEAAgE;;;;;AAEhE,yCAA0C;AAC1C,qCAAmC;AAEnC,oDAA2B;AAC3B,8CAA8C;AAC9C,8CAA8C;AAE9C,2CAA4B;AAC5B,2CAAyB;AAEzB,IAAA,oBAAQ,EAAC,UAAU,EAAE,GAAG,EAAE;IACxB,IAAA,gBAAI,EAAC,aAAa,EAAE,KAAK,IAAI,EAAE;QAC7B,IAAA,aAAM,EAAC,WAAW,CAAC,CAAC,KAAK,EAAE,CAAA;QAE3B,MAAM,MAAM,GAAG,IAAA,gBAAM,EAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;aACrC,IAAI,EAAE;aACN,GAAG,CAAC,WAAW,CAAC;aAChB,GAAG,CAAC,QAAQ,CAAC;aACb,GAAG,CAAC,WAAQ,CAAC,CAAA;QAChB,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QAEpB,IAAA,aAAM,EAAC,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,EAAE,CAAA;IAChD,CAAC,CAAC,CAAA;IAEF,IAAA,gBAAI,EAAC,WAAW,EAAE,KAAK,IAAI,EAAE;QAC3B,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,GAAG,CAAC,WAAQ,EAAE;YACxC,SAAS,EAAE;gBACT,QAAQ,EAAE;oBACR,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,WAAW,CAAC;oBACzB,CAAC,UAAU,EAAE,WAAW,CAAC;iBAC1B;aACF;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QAEpB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACtD,UAAU,EAAE,UAAU;SACvB,CAAC,CAAA;QACF,0BAA0B;QAE1B,IAAA,aAAM,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;YACrB,UAAU;YACV,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YAExB,UAAU;YACV,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YAExB,UAAU;YACV,kCAAkC;YAClC,yCAAyC;YACzC,kCAAkC;YAClC,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YAExB,UAAU;YACV,CAAC,UAAU,EAAE,WAAW,CAAC;YACzB,CAAC,UAAU,EAAE,WAAW,CAAC;SAC1B,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,IAAA,gBAAI,EAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE;QACtC,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,GAAG,CAAC,WAAQ,EAAE;YACxC,SAAS,EAAE;gBACT,QAAQ,EAAE,EAAE;aACb;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QAEpB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACtD,UAAU,EAAE,UAAU;SACvB,CAAC,CAAA;QACF,0BAA0B;QAE1B,IAAA,aAAM,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;IAC5B,CAAC,CAAC,CAAA;IAEF,IAAA,gBAAI,EAAC,uBAAuB,EAAE,KAAK,IAAI,EAAE;QACvC,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,GAAG,CAAC,WAAQ,EAAE;YACxC,SAAS,EAAE;gBACT,QAAQ,EAAE;oBACR,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;iBACzB;aACF;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QAEpB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACtD,UAAU,EAAE,UAAU;SACvB,CAAC,CAAA;QACF,0BAA0B;QAE1B,IAAA,aAAM,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;IAC5B,CAAC,CAAC,CAAA;IAEF,IAAA,gBAAI,EAAC,iBAAiB,EAAE,KAAK,IAAI,EAAE;QACjC,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,GAAG,CAAC,WAAQ,EAAE;YACxC,SAAS,EAAE;gBACT,QAAQ,EAAE;oBACR,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,qBAAqB;oBACrB,CAAC,UAAU,EAAE,UAAU,CAAC;iBACzB;aACF;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QAEpB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACtD,UAAU,EAAE,UAAU;SACvB,CAAC,CAAA;QACF,0BAA0B;QAE1B,gDAAgD;QAChD,IAAA,aAAM,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;YACrB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;SACzB,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,IAAA,gBAAI,EAAC,wBAAwB,EAAE,KAAK,IAAI,EAAE;QACxC,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,GAAG,CAAC,WAAQ,EAAE;YACxC,SAAS,EAAE;gBACT,QAAQ,EAAE;oBACR,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,qCAAqC;oBACrC,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;iBACzB;aACF;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QAEpB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACtD,UAAU,EAAE,UAAU;SACvB,CAAC,CAAA;QAEF,4CAA4C;QAC5C,IAAA,aAAM,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;YACrB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;SACzB,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,IAAA,gBAAI,EAAC,kBAAkB,EAAE,KAAK,IAAI,EAAE;QAClC,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,GAAG,CAAC,WAAQ,EAAE;YACxC,SAAS,EAAE;gBACT,QAAQ,EAAE;oBACR,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;iBACzB;aACF;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QAEpB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACtD,UAAU,EAAE,UAAU;SACvB,CAAC,CAAA;QACF,0BAA0B;QAE1B,IAAA,aAAM,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;YACrB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;SACzB,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,IAAA,gBAAI,EAAC,qBAAqB,EAAE,KAAK,IAAI,EAAE;QACrC,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,GAAG,CAAC,WAAQ,EAAE;YACxC,SAAS,EAAE;gBACT,QAAQ,EAAE;oBACR,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,YAAY;oBACZ,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,YAAY;oBACZ,CAAC,UAAU,EAAE,UAAU,CAAC;iBACzB;aACF;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QAEpB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACtD,UAAU,EAAE,UAAU;SACvB,CAAC,CAAA;QAEF,IAAA,aAAM,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;YACrB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;SACzB,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,IAAA,gBAAI,EAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE;QACtC,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,GAAG,CAAC,WAAQ,EAAE;YACxC,SAAS,EAAE;gBACT,QAAQ,EAAE;oBACR,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,gCAAgC;oBAChC,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;iBACzB;aACF;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QAEpB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACtD,UAAU,EAAE,UAAU;SACvB,CAAC,CAAA;QACF,0BAA0B;QAE1B,iDAAiD;QACjD,IAAA,aAAM,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;YACrB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;SACzB,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,IAAA,gBAAI,EAAC,0BAA0B,EAAE,KAAK,IAAI,EAAE;QAC1C,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,GAAG,CAAC,WAAQ,EAAE;YACxC,SAAS,EAAE;gBACT,QAAQ,EAAE;oBACR,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,wBAAwB;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,4CAA4C;oBAC5C,CAAC,UAAU,EAAE,UAAU,CAAC;iBACzB;aACF;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QAEpB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACtD,UAAU,EAAE,UAAU;SACvB,CAAC,CAAA;QAEF,IAAA,aAAM,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;YACrB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;SACzB,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,IAAA,gBAAI,EAAC,oBAAoB,EAAE,KAAK,IAAI,EAAE;QACpC,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,GAAG,CAAC,WAAQ,EAAE;YACxC,SAAS,EAAE;gBACT,QAAQ,EAAE;oBACR,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,YAAY;oBACZ,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;iBACzB;aACF;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QAEpB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACtD,UAAU,EAAE,UAAU;SACvB,CAAC,CAAA;QACF,0BAA0B;QAE1B,IAAA,aAAM,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;YACrB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;SACzB,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,IAAA,gBAAI,EAAC,uBAAuB,EAAE,KAAK,IAAI,EAAE;QACvC,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,GAAG,CAAC,WAAQ,CAAC,CAAA;QACzC,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QAEpB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAA;QACvD,0BAA0B;QAE1B,IAAA,aAAM,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;IAC5B,CAAC,CAAC,CAAA;IAEF,IAAA,gBAAI,EAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE;QACtC,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,GAAG,CAAC,WAAQ,EAAE;YACxC,SAAS,EAAE;gBACT,QAAQ,EAAE,EAAE;aACb;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QAEpB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACtD,UAAU,EAAE,UAAU;SACvB,CAAC,CAAA;QACF,0BAA0B;QAE1B,IAAA,aAAM,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;IAC5B,CAAC,CAAC,CAAA;IAEF,IAAA,gBAAI,EAAC,cAAc,EAAE,KAAK,IAAI,EAAE;QAC9B,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,GAAG,CAAC,WAAQ,EAAE;YACxC,SAAS,EAAE;gBACT,QAAQ,EAAE;oBACR,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,WAAW,CAAC;oBACzB,CAAC,UAAU,EAAE,WAAW,CAAC;iBAC1B;aACF;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QAEpB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACtD,UAAU,EAAE,UAAU;SACvB,CAAC,CAAA;QAEF,IAAA,aAAM,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;YACrB,UAAU;YACV,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YAExB,UAAU;YACV,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YAExB,UAAU;YACV,CAAC,UAAU,EAAE,WAAW,CAAC;SAC1B,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,IAAA,gBAAI,EAAC,yBAAyB,EAAE,KAAK,IAAI,EAAE;QACzC,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,GAAG,CAAC,WAAQ,EAAE;YACxC,SAAS,EAAE;gBACT,QAAQ,EAAE;oBACR,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,WAAW,CAAC;oBACzB,CAAC,UAAU,EAAE,WAAW,CAAC;oBACzB,CAAC,UAAU,EAAE,WAAW,CAAC;oBACzB,qCAAqC;oBACrC,CAAC,UAAU,EAAE,WAAW,CAAC;oBACzB,CAAC,WAAW,EAAE,WAAW,CAAC;iBAC3B;aACF;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QAEpB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACtD,UAAU,EAAE,UAAU;SACvB,CAAC,CAAA;QAEF,IAAA,aAAM,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;YACrB,UAAU;YACV,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YAExB,UAAU;YACV,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,WAAW,CAAC;YACzB,CAAC,UAAU,EAAE,UAAU,CAAC;YAExB,UAAU;YACV,CAAC,UAAU,EAAE,WAAW,CAAC;YACzB,CAAC,WAAW,EAAE,WAAW,CAAC;SAC3B,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,IAAA,gBAAI,EAAC,oBAAoB,EAAE,KAAK,IAAI,EAAE;QACpC,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,GAAG,CAAC,WAAQ,EAAE;YACxC,SAAS,EAAE;gBACT,QAAQ,EAAE;oBACR,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,WAAW,CAAC;oBACzB,CAAC,UAAU,EAAE,WAAW,CAAC;oBACzB,sCAAsC;oBACtC,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,WAAW,CAAC;iBAC1B;aACF;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QAEpB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACtD,UAAU,EAAE,UAAU;SACvB,CAAC,CAAA;QAEF,IAAA,aAAM,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;YACrB,UAAU;YACV,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YAExB,UAAU;YACV,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YAExB,UAAU;YACV,CAAC,UAAU,EAAE,WAAW,CAAC;YACzB,CAAC,UAAU,EAAE,WAAW,CAAC;SAC1B,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,IAAA,gBAAI,EAAC,cAAc,EAAE,KAAK,IAAI,EAAE;QAC9B,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,GAAG,CAAC,WAAQ,EAAE;YACxC,SAAS,EAAE;gBACT,QAAQ,EAAE;oBACR,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,WAAW,CAAC;oBACzB,CAAC,UAAU,EAAE,WAAW,CAAC;iBAC1B;aACF;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QAEpB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACtD,UAAU,EAAE,UAAU;SACvB,CAAC,CAAA;QACF,0BAA0B;QAE1B,IAAA,aAAM,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;YACrB,UAAU;YACV,CAAC,UAAU,EAAE,UAAU,CAAC;YAExB,UAAU;YACV,CAAC,UAAU,EAAE,WAAW,CAAC;SAC1B,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,IAAA,gBAAI,EAAC,yBAAyB,EAAE,KAAK,IAAI,EAAE;QACzC,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,GAAG,CAAC,WAAQ,EAAE;YACxC,SAAS,EAAE;gBACT,QAAQ,EAAE;oBACR,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,WAAW,CAAC;oBACzB,CAAC,UAAU,EAAE,WAAW,CAAC;oBACzB,CAAC,UAAU,EAAE,WAAW,CAAC;oBACzB,CAAC,UAAU,EAAE,WAAW,CAAC;oBACzB,CAAC,WAAW,EAAE,WAAW,CAAC;oBAC1B,uCAAuC;oBACvC,CAAC,WAAW,EAAE,WAAW,CAAC;oBAC1B,CAAC,WAAW,EAAE,WAAW,CAAC;iBAC3B;aACF;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QAEpB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACtD,UAAU,EAAE,UAAU;SACvB,CAAC,CAAA;QAEF,IAAA,aAAM,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;YACrB,UAAU;YACV,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,WAAW,CAAC;YAEzB,UAAU;YACV,CAAC,UAAU,EAAE,WAAW,CAAC;YACzB,CAAC,UAAU,EAAE,WAAW,CAAC;YACzB,CAAC,WAAW,EAAE,WAAW,CAAC;YAE1B,UAAU;YACV,CAAC,WAAW,EAAE,WAAW,CAAC;SAC3B,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,IAAA,gBAAI,EAAC,oBAAoB,EAAE,KAAK,IAAI,EAAE;QACpC,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,GAAG,CAAC,WAAQ,EAAE;YACxC,SAAS,EAAE;gBACT,QAAQ,EAAE;oBACR,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,WAAW,CAAC;oBACzB,CAAC,UAAU,EAAE,WAAW,CAAC;oBACzB,CAAC,WAAW,EAAE,WAAW,CAAC;oBAC1B,gDAAgD;oBAChD,CAAC,WAAW,EAAE,UAAU,CAAC;oBACzB,CAAC,WAAW,EAAE,WAAW,CAAC;iBAC3B;aACF;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QAEpB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACtD,UAAU,EAAE,UAAU;SACvB,CAAC,CAAA;QAEF,IAAA,aAAM,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;YACrB,UAAU;YACV,CAAC,UAAU,EAAE,UAAU,CAAC;YAExB,UAAU;YACV,CAAC,UAAU,EAAE,WAAW,CAAC;YAEzB,UAAU;YACV,CAAC,WAAW,EAAE,WAAW,CAAC;YAC1B,CAAC,WAAW,EAAE,WAAW,CAAC;SAC3B,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,IAAA,gBAAI,EAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;QACrD,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,GAAG,CAAC,WAAQ,EAAE;YACxC,SAAS,EAAE;gBACT,QAAQ,EAAE;oBACR,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,WAAW,CAAC;oBACzB,CAAC,UAAU,EAAE,WAAW,CAAC;oBACzB,CAAC,UAAU,EAAE,WAAW,CAAC;oBACzB,CAAC,UAAU,EAAE,WAAW,CAAC;oBACzB,CAAC,WAAW,EAAE,WAAW,CAAC;oBAC1B,uBAAuB;oBACvB,CAAC,WAAW,EAAE,WAAW,CAAC;oBAC1B,sBAAsB;oBACtB,CAAC,UAAU,EAAE,WAAW,CAAC;oBACzB,CAAC,WAAW,EAAE,WAAW,CAAC;iBAC3B;aACF;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QAEpB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACtD,UAAU,EAAE,UAAU;SACvB,CAAC,CAAA;QAEF,IAAA,aAAM,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;YACrB,UAAU;YACV,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,WAAW,CAAC;YACzB,CAAC,UAAU,EAAE,WAAW,CAAC;YAEzB,UAAU;YACV,CAAC,UAAU,EAAE,WAAW,CAAC;YACzB,CAAC,WAAW,EAAE,WAAW,CAAC;YAE1B,UAAU;YACV,CAAC,WAAW,EAAE,WAAW,CAAC;SAC3B,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,IAAA,gBAAI,EAAC,wBAAwB,EAAE,KAAK,IAAI,EAAE;QACxC,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,GAAG,CAAC,WAAQ,EAAE;YACxC,SAAS,EAAE;gBACT,QAAQ,EAAE;oBACR,CAAC,GAAG,EAAE,GAAG,CAAC;oBACV,CAAC,GAAG,EAAE,GAAG,CAAC;oBACV,CAAC,GAAG,EAAE,GAAG,CAAC;oBACV,CAAC,GAAG,EAAE,GAAG,CAAC;oBACV,CAAC,GAAG,EAAE,GAAG,CAAC;oBACV,CAAC,GAAG,EAAE,GAAG,CAAC;oBACV,CAAC,GAAG,EAAE,GAAG,CAAC;oBACV,CAAC,GAAG,EAAE,GAAG,CAAC;oBACV,CAAC,GAAG,EAAE,GAAG,CAAC;iBACX;aACF;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QAEpB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACtD,UAAU,EAAE,GAAG;SAChB,CAAC,CAAA;QAEF,IAAA,aAAM,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;YACrB,UAAU;YACV,CAAC,GAAG,EAAE,GAAG,CAAC;YACV,CAAC,GAAG,EAAE,GAAG,CAAC;YACV,CAAC,GAAG,EAAE,GAAG,CAAC;YAEV,UAAU;YACV,CAAC,GAAG,EAAE,GAAG,CAAC;YACV,CAAC,GAAG,EAAE,GAAG,CAAC;YACV,CAAC,GAAG,EAAE,GAAG,CAAC;YAEV,UAAU;YACV,CAAC,GAAG,EAAE,GAAG,CAAC;SACX,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,IAAA,gBAAI,EAAC,+BAA+B,EAAE,KAAK,IAAI,EAAE;QAC/C,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,GAAG,CAAC,WAAQ,EAAE;YACxC,SAAS,EAAE;gBACT,QAAQ,EAAE;oBACR,CAAC,GAAG,EAAE,GAAG,CAAC;oBACV,CAAC,GAAG,EAAE,GAAG,CAAC;oBACV,CAAC,GAAG,EAAE,GAAG,CAAC;iBACX;aACF;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QAEpB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACtD,UAAU,EAAE,GAAG;SAChB,CAAC,CAAA;QAEF,oBAAoB;QACpB,IAAA,aAAM,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;IAC5B,CAAC,CAAC,CAAA;IAEF,IAAA,gBAAI,EAAC,6BAA6B,EAAE,KAAK,IAAI,EAAE;QAC7C,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,GAAG,CAAC,WAAQ,EAAE;YACxC,SAAS,EAAE;gBACT,QAAQ,EAAE;oBACR,CAAC,OAAO,EAAE,OAAO,CAAC;oBAClB,CAAC,OAAO,EAAE,OAAO,CAAC;oBAClB,CAAC,OAAO,EAAE,OAAO,CAAC;oBAClB,CAAC,OAAO,EAAE,OAAO,CAAC;oBAClB,CAAC,OAAO,EAAE,OAAO,CAAC;oBAClB,CAAC,OAAO,EAAE,OAAO,CAAC;oBAClB,CAAC,OAAO,EAAE,OAAO,CAAC;oBAClB,CAAC,OAAO,EAAE,OAAO,CAAC;oBAClB,CAAC,OAAO,EAAE,OAAO,CAAC;oBAClB,CAAC,OAAO,EAAE,QAAQ,CAAC;iBACpB;aACF;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QAEpB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACtD,UAAU,EAAE,OAAO;SACpB,CAAC,CAAA;QAEF,IAAA,aAAM,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;YACrB,CAAC,OAAO,EAAE,OAAO,CAAC;YAClB,CAAC,OAAO,EAAE,OAAO,CAAC;YAClB,CAAC,OAAO,EAAE,OAAO,CAAC;YAClB,CAAC,OAAO,EAAE,OAAO,CAAC;YAClB,CAAC,OAAO,EAAE,OAAO,CAAC;YAClB,CAAC,OAAO,EAAE,OAAO,CAAC;YAClB,CAAC,OAAO,EAAE,OAAO,CAAC;YAClB,CAAC,OAAO,EAAE,OAAO,CAAC;YAClB,CAAC,OAAO,EAAE,OAAO,CAAC;YAClB,CAAC,OAAO,EAAE,QAAQ,CAAC;SACpB,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,IAAA,gBAAI,EAAC,uBAAuB,EAAE,KAAK,IAAI,EAAE;QACvC,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,GAAG,CAAC,WAAQ,EAAE;YACxC,SAAS,EAAE;gBACT,QAAQ,EAAE;oBACR,CAAC,GAAG,EAAE,GAAG,CAAC;oBACV,CAAC,GAAG,EAAE,GAAG,CAAC;oBACV,CAAC,GAAG,EAAE,GAAG,CAAC;oBACV,CAAC,GAAG,EAAE,GAAG,CAAC;oBACV,CAAC,GAAG,EAAE,GAAG,CAAC;oBACV,CAAC,GAAG,EAAE,GAAG,CAAC;oBACV,CAAC,GAAG,EAAE,GAAG,CAAC;oBACV,CAAC,GAAG,EAAE,GAAG,CAAC;oBACV,CAAC,GAAG,EAAE,GAAG,CAAC;oBACV,CAAC,GAAG,EAAE,GAAG,CAAC;iBACX;aACF;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QAEpB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACtD,UAAU,EAAE,GAAG;SAChB,CAAC,CAAA;QAEF,IAAA,aAAM,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;YACrB,UAAU;YACV,CAAC,GAAG,EAAE,GAAG,CAAC;YACV,CAAC,GAAG,EAAE,GAAG,CAAC;YAEV,UAAU;YACV,CAAC,GAAG,EAAE,GAAG,CAAC;YACV,CAAC,GAAG,EAAE,GAAG,CAAC;YACV,CAAC,GAAG,EAAE,GAAG,CAAC;YACV,CAAC,GAAG,EAAE,GAAG,CAAC;YAEV,UAAU;YACV,CAAC,GAAG,EAAE,GAAG,CAAC;YACV,CAAC,GAAG,EAAE,GAAG,CAAC;YACV,CAAC,GAAG,EAAE,GAAG,CAAC;YACV,CAAC,GAAG,EAAE,GAAG,CAAC;SACX,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,IAAA,gBAAI,EAAC,2BAA2B,EAAE,KAAK,IAAI,EAAE;QAC3C,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,GAAG,CAAC,WAAQ,EAAE;YACxC,SAAS,EAAE;gBACT,QAAQ,EAAE;oBACR,CAAC,GAAG,EAAE,GAAG,CAAC;oBACV,CAAC,GAAG,EAAE,GAAG,CAAC;oBACV,CAAC,GAAG,EAAE,GAAG,CAAC;oBACV,CAAC,GAAG,EAAE,GAAG,CAAC;oBACV,CAAC,GAAG,EAAE,GAAG,CAAC;iBACX;aACF;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QAEpB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACtD,UAAU,EAAE,GAAG;SAChB,CAAC,CAAA;QAEF,mDAAmD;QACnD,IAAA,aAAM,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;YACrB,UAAU;YACV,CAAC,GAAG,EAAE,GAAG,CAAC;YACV,CAAC,GAAG,EAAE,GAAG,CAAC;YAEV,4BAA4B;YAC5B,CAAC,GAAG,EAAE,GAAG,CAAC;YAEV,UAAU;YACV,CAAC,GAAG,EAAE,GAAG,CAAC;SACX,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,IAAA,gBAAI,EAAC,2BAA2B,EAAE,KAAK,IAAI,EAAE;QAC3C,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,GAAG,CAAC,WAAQ,EAAE;YACxC,SAAS,EAAE;gBACT,QAAQ,EAAE;oBACR,CAAC,MAAM,EAAE,QAAQ,CAAC;oBAClB,CAAC,MAAM,EAAE,OAAO,CAAC;oBACjB,CAAC,MAAM,EAAE,OAAO,CAAC;oBACjB,CAAC,MAAM,EAAE,QAAQ,CAAC;oBAClB,CAAC,OAAO,EAAE,QAAQ,CAAC;oBACnB,CAAC,OAAO,EAAE,QAAQ,CAAC;oBACnB,CAAC,QAAQ,EAAE,SAAS,CAAC;oBACrB,CAAC,QAAQ,EAAE,SAAS,CAAC;iBACtB;aACF;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QAEpB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACtD,UAAU,EAAE,MAAM;SACnB,CAAC,CAAA;QAEF,gDAAgD;QAChD,IAAA,aAAM,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;YACrB,UAAU;YACV,CAAC,MAAM,EAAE,OAAO,CAAC;YACjB,CAAC,MAAM,EAAE,OAAO,CAAC;YACjB,CAAC,MAAM,EAAE,QAAQ,CAAC;YAClB,CAAC,MAAM,EAAE,QAAQ,CAAC;YAElB,UAAU;YACV,CAAC,OAAO,EAAE,QAAQ,CAAC;YACnB,CAAC,OAAO,EAAE,QAAQ,CAAC;YACnB,CAAC,QAAQ,EAAE,SAAS,CAAC;YACrB,CAAC,QAAQ,EAAE,SAAS,CAAC;SACtB,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,IAAA,gBAAI,EAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;QACrD,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,GAAG,CAAC,WAAQ,EAAE;YACxC,SAAS,EAAE;gBACT,QAAQ,EAAE;oBACR,CAAC,MAAM,EAAE,GAAG,CAAC;oBACb,CAAC,MAAM,EAAE,GAAG,CAAC;oBACb,CAAC,MAAM,EAAE,GAAG,CAAC;oBACb,CAAC,GAAG,EAAE,QAAQ,CAAC;oBACf,CAAC,GAAG,EAAE,QAAQ,CAAC;oBACf,CAAC,GAAG,EAAE,QAAQ,CAAC;oBACf,CAAC,QAAQ,EAAE,KAAK,CAAC;iBAClB;aACF;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QAEpB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACtD,UAAU,EAAE,MAAM;SACnB,CAAC,CAAA;QAEF,oDAAoD;QACpD,IAAA,aAAM,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;YACrB,UAAU;YACV,CAAC,MAAM,EAAE,GAAG,CAAC;YACb,CAAC,MAAM,EAAE,GAAG,CAAC;YACb,CAAC,MAAM,EAAE,GAAG,CAAC;YAEb,yBAAyB;YACzB,CAAC,GAAG,EAAE,QAAQ,CAAC;YAEf,UAAU;YACV,CAAC,QAAQ,EAAE,KAAK,CAAC;SAClB,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,IAAA,gBAAI,EAAC,kCAAkC,EAAE,KAAK,IAAI,EAAE;QAClD,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,GAAG,CAAC,WAAQ,EAAE;YACxC,SAAS,EAAE;gBACT,QAAQ,EAAE;oBACR,0CAA0C;oBAC1C,CAAC,KAAK,EAAE,KAAK,CAAC;oBACd,CAAC,KAAK,EAAE,KAAK,CAAC;oBACd,CAAC,KAAK,EAAE,KAAK,CAAC;oBAEd,0CAA0C;oBAC1C,CAAC,KAAK,EAAE,MAAM,CAAC;oBACf,CAAC,KAAK,EAAE,MAAM,CAAC;oBACf,CAAC,KAAK,EAAE,MAAM,CAAC;oBACf,CAAC,KAAK,EAAE,MAAM,CAAC;oBACf,CAAC,KAAK,EAAE,MAAM,CAAC;oBAEf,0CAA0C;oBAC1C,CAAC,MAAM,EAAE,MAAM,CAAC;oBAChB,CAAC,MAAM,EAAE,MAAM,CAAC;oBAChB,CAAC,MAAM,EAAE,MAAM,CAAC;oBAChB,CAAC,MAAM,EAAE,MAAM,CAAC;oBAChB,CAAC,MAAM,EAAE,MAAM,CAAC;oBAChB,CAAC,MAAM,EAAE,MAAM,CAAC;oBAChB,CAAC,MAAM,EAAE,MAAM,CAAC;oBAEhB,0CAA0C;oBAC1C,CAAC,MAAM,EAAE,MAAM,CAAC;oBAChB,CAAC,MAAM,EAAE,MAAM,CAAC;oBAChB,CAAC,MAAM,EAAE,MAAM,CAAC;oBAChB,CAAC,MAAM,EAAE,MAAM,CAAC;oBAChB,CAAC,MAAM,EAAE,MAAM,CAAC;oBAChB,CAAC,MAAM,EAAE,MAAM,CAAC;oBAChB,CAAC,MAAM,EAAE,MAAM,CAAC;oBAChB,CAAC,MAAM,EAAE,MAAM,CAAC;oBAEhB,0CAA0C;oBAC1C,CAAC,MAAM,EAAE,OAAO,CAAC;oBACjB,CAAC,MAAM,EAAE,OAAO,CAAC;oBACjB,CAAC,MAAM,EAAE,OAAO,CAAC;oBACjB,CAAC,MAAM,EAAE,OAAO,CAAC;oBACjB,CAAC,MAAM,EAAE,MAAM,CAAC;oBAChB,CAAC,MAAM,EAAE,OAAO,CAAC;oBACjB,CAAC,MAAM,EAAE,OAAO,CAAC;oBACjB,CAAC,MAAM,EAAE,MAAM,CAAC;oBAEhB,qEAAqE;oBACrE,CAAC,OAAO,EAAE,UAAU,CAAC;oBACrB,CAAC,OAAO,EAAE,UAAU,CAAC;oBACrB,CAAC,OAAO,EAAE,UAAU,CAAC;oBACrB,CAAC,OAAO,EAAE,UAAU,CAAC;oBACrB,CAAC,OAAO,EAAE,UAAU,CAAC;oBACrB,CAAC,MAAM,EAAE,UAAU,CAAC;oBACpB,CAAC,OAAO,EAAE,UAAU,CAAC;oBACrB,CAAC,OAAO,EAAE,UAAU,CAAC;oBACrB,iDAAiD;oBACjD,CAAC,MAAM,EAAE,KAAK,CAAC;oBACf,CAAC,MAAM,EAAE,UAAU,CAAC;oBAEpB,0CAA0C;oBAC1C,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,WAAW,CAAC;oBACzB,CAAC,UAAU,EAAE,WAAW,CAAC;oBACzB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBAExB,0CAA0C;oBAC1C,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,WAAW,EAAE,UAAU,CAAC;oBACzB,CAAC,WAAW,EAAE,UAAU,CAAC;oBACzB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,2DAA2D;oBAC3D,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBAExB,0CAA0C;oBAC1C,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,WAAW,CAAC;oBACzB,CAAC,UAAU,EAAE,WAAW,CAAC;oBACzB,CAAC,UAAU,EAAE,WAAW,CAAC;oBACzB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBAExB,2CAA2C;oBAC3C,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,WAAW,EAAE,UAAU,CAAC;oBACzB,CAAC,WAAW,EAAE,UAAU,CAAC;oBACzB,CAAC,WAAW,EAAE,UAAU,CAAC;oBACzB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBAExB,4CAA4C;oBAC5C,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,WAAW,CAAC;oBACzB,CAAC,UAAU,EAAE,WAAW,CAAC;oBACzB,CAAC,UAAU,EAAE,WAAW,CAAC;oBACzB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;oBACxB,CAAC,UAAU,EAAE,UAAU,CAAC;iBACzB;aACF;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QAEpB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACtD,UAAU,EAAE,KAAK;SAClB,CAAC,CAAA;QAEF,IAAA,aAAM,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;YACrB,gCAAgC;YAChC,CAAC,KAAK,EAAE,KAAK,CAAC;YACd,CAAC,KAAK,EAAE,KAAK,CAAC;YACd,CAAC,KAAK,EAAE,KAAK,CAAC;YAEd,gCAAgC;YAChC,CAAC,KAAK,EAAE,MAAM,CAAC;YACf,CAAC,KAAK,EAAE,MAAM,CAAC;YACf,CAAC,KAAK,EAAE,MAAM,CAAC;YACf,CAAC,KAAK,EAAE,MAAM,CAAC;YACf,CAAC,KAAK,EAAE,MAAM,CAAC;YAEf,gCAAgC;YAChC,CAAC,MAAM,EAAE,MAAM,CAAC;YAChB,CAAC,MAAM,EAAE,MAAM,CAAC;YAChB,CAAC,MAAM,EAAE,MAAM,CAAC;YAChB,CAAC,MAAM,EAAE,MAAM,CAAC;YAChB,CAAC,MAAM,EAAE,MAAM,CAAC;YAChB,CAAC,MAAM,EAAE,MAAM,CAAC;YAChB,CAAC,MAAM,EAAE,MAAM,CAAC;YAEhB,gCAAgC;YAChC,CAAC,MAAM,EAAE,MAAM,CAAC;YAChB,CAAC,MAAM,EAAE,MAAM,CAAC;YAChB,CAAC,MAAM,EAAE,MAAM,CAAC;YAChB,CAAC,MAAM,EAAE,MAAM,CAAC;YAChB,CAAC,MAAM,EAAE,MAAM,CAAC;YAChB,CAAC,MAAM,EAAE,MAAM,CAAC;YAChB,CAAC,MAAM,EAAE,MAAM,CAAC;YAChB,CAAC,MAAM,EAAE,MAAM,CAAC;YAEhB,gCAAgC;YAChC,CAAC,MAAM,EAAE,OAAO,CAAC;YACjB,CAAC,MAAM,EAAE,OAAO,CAAC;YACjB,CAAC,MAAM,EAAE,OAAO,CAAC;YACjB,CAAC,MAAM,EAAE,OAAO,CAAC;YACjB,CAAC,MAAM,EAAE,MAAM,CAAC;YAChB,CAAC,MAAM,EAAE,MAAM,CAAC;YAChB,CAAC,MAAM,EAAE,OAAO,CAAC;YACjB,CAAC,MAAM,EAAE,OAAO,CAAC;YAEjB,gCAAgC;YAChC,CAAC,OAAO,EAAE,UAAU,CAAC;YACrB,CAAC,OAAO,EAAE,UAAU,CAAC;YACrB,CAAC,OAAO,EAAE,UAAU,CAAC;YACrB,CAAC,OAAO,EAAE,UAAU,CAAC;YACrB,CAAC,OAAO,EAAE,UAAU,CAAC;YACrB,0DAA0D;YAC1D,CAAC,MAAM,EAAE,UAAU,CAAC;YACpB,CAAC,MAAM,EAAE,UAAU,CAAC;YACpB,CAAC,OAAO,EAAE,UAAU,CAAC;YACrB,CAAC,OAAO,EAAE,UAAU,CAAC;YAErB,gCAAgC;YAChC,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,WAAW,CAAC;YACzB,CAAC,UAAU,EAAE,WAAW,CAAC;YACzB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YAExB,gCAAgC;YAChC,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,WAAW,EAAE,UAAU,CAAC;YACzB,CAAC,WAAW,EAAE,UAAU,CAAC;YACzB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,yFAAyF;YAEzF,gCAAgC;YAChC,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,WAAW,CAAC;YACzB,CAAC,UAAU,EAAE,WAAW,CAAC;YACzB,CAAC,UAAU,EAAE,WAAW,CAAC;YACzB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YAExB,gCAAgC;YAChC,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,WAAW,EAAE,UAAU,CAAC;YACzB,CAAC,WAAW,EAAE,UAAU,CAAC;YACzB,CAAC,WAAW,EAAE,UAAU,CAAC;YACzB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YAExB,iCAAiC;YACjC,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,WAAW,CAAC;YACzB,CAAC,UAAU,EAAE,WAAW,CAAC;YACzB,CAAC,UAAU,EAAE,WAAW,CAAC;YACzB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;YACxB,CAAC,UAAU,EAAE,UAAU,CAAC;SACzB,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,SAAS,UAAU,CAAC,OAAY,EAAE;IAChC,MAAM,MAAM,GAAG,IAAA,gBAAM,EAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IAC9E,OAAO,MAAM,CAAA;AACf,CAAC"} \ No newline at end of file diff --git a/dist/Traverse.d.ts b/dist/Traverse.d.ts index 0a5227a..5ceb34f 100644 --- a/dist/Traverse.d.ts +++ b/dist/Traverse.d.ts @@ -1,5 +1,12 @@ +type EntityID = string; +type ParentChildRelation = [EntityID, EntityID]; +type Parental = ParentChildRelation[]; type TraverseOptionsFull = { debug: boolean; + rootEntity: EntityID; + relations: { + parental: Parental; + }; }; export type TraverseOptions = Partial; declare function Traverse(this: any, options: TraverseOptionsFull): void; diff --git a/dist/Traverse.d.ts.map b/dist/Traverse.d.ts.map index 348a22b..0e693ee 100644 --- a/dist/Traverse.d.ts.map +++ b/dist/Traverse.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"Traverse.d.ts","sourceRoot":"","sources":["../src/Traverse.ts"],"names":[],"mappings":"AAEA,KAAK,mBAAmB,GAAG;IACzB,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAED,MAAM,MAAM,eAAe,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAA;AAE1D,iBAAS,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,mBAAmB,QAOxD;AAUD,eAAe,QAAQ,CAAA"} \ No newline at end of file +{"version":3,"file":"Traverse.d.ts","sourceRoot":"","sources":["../src/Traverse.ts"],"names":[],"mappings":"AAIA,KAAK,QAAQ,GAAG,MAAM,CAAA;AAEtB,KAAK,mBAAmB,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;AAE/C,KAAK,QAAQ,GAAG,mBAAmB,EAAE,CAAA;AAErC,KAAK,mBAAmB,GAAG;IACzB,KAAK,EAAE,OAAO,CAAA;IACd,UAAU,EAAE,QAAQ,CAAA;IACpB,SAAS,EAAE;QACT,QAAQ,EAAE,QAAQ,CAAA;KACnB,CAAA;CACF,CAAA;AAED,MAAM,MAAM,eAAe,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAA;AAE1D,iBAAS,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,mBAAmB,QAiFxD;AAcD,eAAe,QAAQ,CAAA"} \ No newline at end of file diff --git a/dist/Traverse.js b/dist/Traverse.js index 092ba7a..0342d97 100644 --- a/dist/Traverse.js +++ b/dist/Traverse.js @@ -1,16 +1,68 @@ "use strict"; /* Copyright © 2025 Seneca Project Contributors, MIT License. */ Object.defineProperty(exports, "__esModule", { value: true }); +const gubu_1 = require("gubu"); function Traverse(options) { const seneca = this; - const { Default } = seneca.valid; - seneca.fix('sys:traverse'); - // .message('find:deps', msgFindDeps) + // const { Default } = seneca.valid + seneca.fix('sys:traverse').message('find:deps', { + rootEntity: (0, gubu_1.Optional)(String), + }, msgFindDeps); + // Returns a sorted list of entity pairs starting from a given entity. + // In breadth-first order, sorting first by level, then alphabetically in each level. + async function msgFindDeps(msg) { + // const seneca = this + const allRelations = options.relations.parental; + const rootEntity = msg.rootEntity || options.rootEntity; + const deps = []; + const parentChildrenMap = new Map(); + for (const [parent, child] of allRelations) { + if (!parentChildrenMap.has(parent)) { + parentChildrenMap.set(parent, []); + } + parentChildrenMap.get(parent).push(child); + } + for (const children of parentChildrenMap.values()) { + children.sort(); + } + const visitedEntitiesSet = new Set([rootEntity]); + let currentLevel = [rootEntity]; + while (currentLevel.length > 0) { + const nextLevel = []; + let levelDeps = []; + for (const parent of currentLevel) { + const children = parentChildrenMap.get(parent) || []; + for (const child of children) { + if (visitedEntitiesSet.has(child)) { + continue; + } + levelDeps.push([parent, child]); + visitedEntitiesSet.add(child); + nextLevel.push(child); + } + } + levelDeps = compareRelations(levelDeps); + deps.push(...levelDeps); + currentLevel = nextLevel; + } + return { + ok: true, + deps, + }; + } + function compareRelations(relations) { + return [...relations].sort((a, b) => a[0].localeCompare(b[0], undefined, { numeric: true }) || + a[1].localeCompare(b[1], undefined, { numeric: true })); + } } // Default options. const defaults = { // TODO: Enable debug logging debug: false, + rootEntity: 'sys/user', + relations: { + parental: [], + }, }; Object.assign(Traverse, { defaults }); exports.default = Traverse; diff --git a/dist/Traverse.js.map b/dist/Traverse.js.map index 3d3101d..c148f9a 100644 --- a/dist/Traverse.js.map +++ b/dist/Traverse.js.map @@ -1 +1 @@ -{"version":3,"file":"Traverse.js","sourceRoot":"","sources":["../src/Traverse.ts"],"names":[],"mappings":";AAAA,gEAAgE;;AAQhE,SAAS,QAAQ,CAAY,OAA4B;IACvD,MAAM,MAAM,GAAQ,IAAI,CAAA;IAExB,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC,KAAK,CAAA;IAEhC,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAA;IAC1B,qCAAqC;AACvC,CAAC;AAED,mBAAmB;AACnB,MAAM,QAAQ,GAAwB;IACpC,6BAA6B;IAC7B,KAAK,EAAE,KAAK;CACb,CAAA;AAED,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAA;AAErC,kBAAe,QAAQ,CAAA;AAEvB,IAAI,WAAW,KAAK,OAAO,MAAM,EAAE,CAAC;IAClC,MAAM,CAAC,OAAO,GAAG,QAAQ,CAAA;AAC3B,CAAC"} \ No newline at end of file +{"version":3,"file":"Traverse.js","sourceRoot":"","sources":["../src/Traverse.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/TraverseDoc.d.ts b/dist/TraverseDoc.d.ts index 8cf5a2b..82539bc 100644 --- a/dist/TraverseDoc.d.ts +++ b/dist/TraverseDoc.d.ts @@ -1,5 +1,9 @@ declare const docs: { - messages: {}; + messages: { + msgFindDeps: { + desc: string; + }; + }; }; export default docs; //# sourceMappingURL=TraverseDoc.d.ts.map \ No newline at end of file diff --git a/dist/TraverseDoc.d.ts.map b/dist/TraverseDoc.d.ts.map index 8953d88..02335d8 100644 --- a/dist/TraverseDoc.d.ts.map +++ b/dist/TraverseDoc.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"TraverseDoc.d.ts","sourceRoot":"","sources":["../src/TraverseDoc.ts"],"names":[],"mappings":"AAEA,QAAA,MAAM,IAAI;;CAMT,CAAA;AAED,eAAe,IAAI,CAAA"} \ No newline at end of file +{"version":3,"file":"TraverseDoc.d.ts","sourceRoot":"","sources":["../src/TraverseDoc.ts"],"names":[],"mappings":"AAEA,QAAA,MAAM,IAAI;;;;;;CAMT,CAAA;AAED,eAAe,IAAI,CAAA"} \ No newline at end of file diff --git a/dist/TraverseDoc.js b/dist/TraverseDoc.js index 9f90a6b..f8af759 100644 --- a/dist/TraverseDoc.js +++ b/dist/TraverseDoc.js @@ -3,9 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true }); const docs = { messages: { - // msgFindDeps: { - // desc: 'Return a sorted list of dependencies.', - // } + msgFindDeps: { + desc: 'Returns a sorted list of entity pairs starting from a given entity.', + }, }, }; exports.default = docs; diff --git a/dist/TraverseDoc.js.map b/dist/TraverseDoc.js.map index 46917e6..6eac21b 100644 --- a/dist/TraverseDoc.js.map +++ b/dist/TraverseDoc.js.map @@ -1 +1 @@ -{"version":3,"file":"TraverseDoc.js","sourceRoot":"","sources":["../src/TraverseDoc.ts"],"names":[],"mappings":";AAAA,gEAAgE;;AAEhE,MAAM,IAAI,GAAG;IACX,QAAQ,EAAE;IACR,iBAAiB;IACjB,mDAAmD;IACnD,IAAI;KACL;CACF,CAAA;AAED,kBAAe,IAAI,CAAA;AAEnB,IAAI,WAAW,KAAK,OAAO,MAAM,EAAE,CAAC;IAClC,MAAM,CAAC,OAAO,GAAG,IAAI,CAAA;AACvB,CAAC"} \ No newline at end of file +{"version":3,"file":"TraverseDoc.js","sourceRoot":"","sources":["../src/TraverseDoc.ts"],"names":[],"mappings":";AAAA,gEAAgE;;AAEhE,MAAM,IAAI,GAAG;IACX,QAAQ,EAAE;QACR,WAAW,EAAE;YACX,IAAI,EAAE,qEAAqE;SAC5E;KACF;CACF,CAAA;AAED,kBAAe,IAAI,CAAA;AAEnB,IAAI,WAAW,KAAK,OAAO,MAAM,EAAE,CAAC;IAClC,MAAM,CAAC,OAAO,GAAG,IAAI,CAAA;AACvB,CAAC"} \ No newline at end of file diff --git a/package.json b/package.json index f38e094..98507e0 100644 --- a/package.json +++ b/package.json @@ -42,12 +42,14 @@ "@seneca/doc": "^8.0.0", "@seneca/maintain": "^0.1.0", "@types/node": "^24.10.1", + "gubu": "^9.0.0", "prettier": "3.7.3", "seneca-msg-test": "^4.1.0", "typescript": "^5.9.3" }, "peerDependencies": { "@seneca/entity-util": ">=3", + "gubu": ">=9", "seneca": ">=3||>=4.0.0-rc2", "seneca-entity": ">=26", "seneca-promisify": ">=3" diff --git a/src/Traverse.ts b/src/Traverse.ts index 86adc15..fb6b2d6 100644 --- a/src/Traverse.ts +++ b/src/Traverse.ts @@ -1,7 +1,19 @@ /* Copyright © 2025 Seneca Project Contributors, MIT License. */ +import { Optional } from 'gubu' + +type EntityID = string + +type ParentChildRelation = [EntityID, EntityID] + +type Parental = ParentChildRelation[] + type TraverseOptionsFull = { debug: boolean + rootEntity: EntityID + relations: { + parental: Parental + } } export type TraverseOptions = Partial @@ -9,16 +21,94 @@ export type TraverseOptions = Partial function Traverse(this: any, options: TraverseOptionsFull) { const seneca: any = this - const { Default } = seneca.valid + // const { Default } = seneca.valid + + seneca.fix('sys:traverse').message( + 'find:deps', + { + rootEntity: Optional(String), + }, + msgFindDeps, + ) + + // Returns a sorted list of entity pairs starting from a given entity. + // In breadth-first order, sorting first by level, then alphabetically in each level. + async function msgFindDeps( + this: any, + msg: { + rootEntity?: EntityID + }, + ): Promise<{ ok: boolean; deps: ParentChildRelation[] }> { + // const seneca = this + const allRelations: Parental = options.relations.parental + const rootEntity = msg.rootEntity || options.rootEntity + const deps: ParentChildRelation[] = [] + + const parentChildrenMap: Map = new Map() + + for (const [parent, child] of allRelations) { + if (!parentChildrenMap.has(parent)) { + parentChildrenMap.set(parent, []) + } + + parentChildrenMap.get(parent)!.push(child) + } + + for (const children of parentChildrenMap.values()) { + children.sort() + } + + const visitedEntitiesSet: Set = new Set([rootEntity]) + let currentLevel: EntityID[] = [rootEntity] + + while (currentLevel.length > 0) { + const nextLevel: EntityID[] = [] + let levelDeps: ParentChildRelation[] = [] + + for (const parent of currentLevel) { + const children = parentChildrenMap.get(parent) || [] + + for (const child of children) { + if (visitedEntitiesSet.has(child)) { + continue + } + + levelDeps.push([parent, child]) + visitedEntitiesSet.add(child) + nextLevel.push(child) + } + } + + levelDeps = compareRelations(levelDeps) + deps.push(...levelDeps) + currentLevel = nextLevel + } + + return { + ok: true, + deps, + } + } - seneca.fix('sys:traverse') - // .message('find:deps', msgFindDeps) + function compareRelations( + relations: ParentChildRelation[], + ): ParentChildRelation[] { + return [...relations].sort( + (a, b) => + a[0].localeCompare(b[0], undefined, { numeric: true }) || + a[1].localeCompare(b[1], undefined, { numeric: true }), + ) + } } // Default options. const defaults: TraverseOptionsFull = { // TODO: Enable debug logging debug: false, + rootEntity: 'sys/user', + relations: { + parental: [], + }, } Object.assign(Traverse, { defaults }) diff --git a/src/TraverseDoc.ts b/src/TraverseDoc.ts index 500beda..9642bcb 100644 --- a/src/TraverseDoc.ts +++ b/src/TraverseDoc.ts @@ -2,9 +2,9 @@ const docs = { messages: { - // msgFindDeps: { - // desc: 'Return a sorted list of dependencies.', - // } + msgFindDeps: { + desc: 'Returns a sorted list of entity pairs starting from a given entity.', + }, }, } diff --git a/test/Traverse.test.ts b/test/Traverse.test.ts index 1fc2f5d..e237d3b 100644 --- a/test/Traverse.test.ts +++ b/test/Traverse.test.ts @@ -19,9 +19,1126 @@ describe('Traverse', () => { .use('promisify') .use('entity') .use(Traverse) - await seneca.ready() expect(seneca.find_plugin('Traverse')).exist() }) + + test('find-deps', async () => { + const seneca = makeSeneca().use(Traverse, { + relations: { + parental: [ + ['foo/bar2', 'foo/bar3'], + ['foo/bar0', 'foo/bar1'], + ['foo/bar0', 'foo/bar2'], + ['foo/bar1', 'foo/bar4'], + ['foo/bar1', 'foo/bar5'], + ['foo/bar3', 'foo/bar6'], + ['foo/bar4', 'foo/bar7'], + ['foo/bar5', 'foo/bar8'], + ['foo/bar0', 'foo/zed0'], + ['foo/zed0', 'foo/zed1'], + ['foo/zed1', 'foo/zed2'], + ['bar/baz0', 'bar/baz1'], + ['qux/test', 'qux/prod'], + ['foo/bar2', 'foo/bar9'], + ['foo/bar6', 'foo/bar10'], + ['foo/bar7', 'foo/bar11'], + ], + }, + }) + await seneca.ready() + + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar0', + }) + // console.log('RES', res) + + expect(res.deps).equal([ + // Level 0 + ['foo/bar0', 'foo/bar1'], + ['foo/bar0', 'foo/bar2'], + ['foo/bar0', 'foo/zed0'], + + // Level 1 + ['foo/bar1', 'foo/bar4'], + ['foo/bar1', 'foo/bar5'], + ['foo/bar2', 'foo/bar3'], + ['foo/bar2', 'foo/bar9'], + ['foo/zed0', 'foo/zed1'], + + // Level 2 + // Sort each level alphabetically. + // Thus, foo/bar3 should be listed first, + // although its parent is foo/bar2 + ['foo/bar3', 'foo/bar6'], + ['foo/bar4', 'foo/bar7'], + ['foo/bar5', 'foo/bar8'], + ['foo/zed1', 'foo/zed2'], + + // Level 3 + ['foo/bar6', 'foo/bar10'], + ['foo/bar7', 'foo/bar11'], + ]) + }) + + test('find-deps-empty-list', async () => { + const seneca = makeSeneca().use(Traverse, { + relations: { + parental: [], + }, + }) + await seneca.ready() + + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar0', + }) + // console.log('RES', res) + + expect(res.deps).equal([]) + }) + + test('find-deps-no-children', async () => { + const seneca = makeSeneca().use(Traverse, { + relations: { + parental: [ + ['foo/bar1', 'foo/bar2'], + ['foo/bar2', 'foo/bar3'], + ], + }, + }) + await seneca.ready() + + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar0', + }) + // console.log('RES', res) + + expect(res.deps).equal([]) + }) + + test('find-deps-cycle', async () => { + const seneca = makeSeneca().use(Traverse, { + relations: { + parental: [ + ['foo/bar0', 'foo/bar1'], + ['foo/bar1', 'foo/bar2'], + // Cycle back to root + ['foo/bar2', 'foo/bar0'], + ], + }, + }) + await seneca.ready() + + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar0', + }) + // console.log('RES', res) + + // Should only traverse once, ignoring the cycle + expect(res.deps).equal([ + ['foo/bar0', 'foo/bar1'], + ['foo/bar1', 'foo/bar2'], + ]) + }) + + test('find-deps-cycle-middle', async () => { + const seneca = makeSeneca().use(Traverse, { + relations: { + parental: [ + ['foo/bar0', 'foo/bar1'], + ['foo/bar1', 'foo/bar2'], + ['foo/bar2', 'foo/bar3'], + // Cycle bar1 -> bar2 -> bar3 -> bar1 + ['foo/bar3', 'foo/bar1'], + ['foo/bar2', 'foo/bar4'], + ], + }, + }) + await seneca.ready() + + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar0', + }) + + // Each node visited only once despite cycle + expect(res.deps).equal([ + ['foo/bar0', 'foo/bar1'], + ['foo/bar1', 'foo/bar2'], + ['foo/bar2', 'foo/bar3'], + ['foo/bar2', 'foo/bar4'], + ]) + }) + + test('find-deps-linear', async () => { + const seneca = makeSeneca().use(Traverse, { + relations: { + parental: [ + ['foo/bar0', 'foo/bar1'], + ['foo/bar1', 'foo/bar2'], + ['foo/bar2', 'foo/bar3'], + ['foo/bar3', 'foo/bar4'], + ], + }, + }) + await seneca.ready() + + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar0', + }) + // console.log('RES', res) + + expect(res.deps).equal([ + ['foo/bar0', 'foo/bar1'], + ['foo/bar1', 'foo/bar2'], + ['foo/bar2', 'foo/bar3'], + ['foo/bar3', 'foo/bar4'], + ]) + }) + + test('find-deps-duplicate', async () => { + const seneca = makeSeneca().use(Traverse, { + relations: { + parental: [ + ['foo/bar0', 'foo/bar1'], + // Duplicate + ['foo/bar0', 'foo/bar1'], + ['foo/bar1', 'foo/bar2'], + // Duplicate + ['foo/bar1', 'foo/bar2'], + ], + }, + }) + await seneca.ready() + + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar0', + }) + + expect(res.deps).equal([ + ['foo/bar0', 'foo/bar1'], + ['foo/bar1', 'foo/bar2'], + ]) + }) + + test('find-deps-convergent', async () => { + const seneca = makeSeneca().use(Traverse, { + relations: { + parental: [ + ['foo/bar0', 'foo/bar1'], + ['foo/bar0', 'foo/bar2'], + ['foo/bar1', 'foo/bar3'], + // bar3 reachable from two paths + ['foo/bar2', 'foo/bar3'], + ['foo/bar3', 'foo/bar4'], + ], + }, + }) + await seneca.ready() + + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar0', + }) + // console.log('RES', res) + + // bar3 should only appear once (first path wins) + expect(res.deps).equal([ + ['foo/bar0', 'foo/bar1'], + ['foo/bar0', 'foo/bar2'], + ['foo/bar1', 'foo/bar3'], + ['foo/bar3', 'foo/bar4'], + ]) + }) + + test('find-deps-two-convergent', async () => { + const seneca = makeSeneca().use(Traverse, { + relations: { + parental: [ + ['foo/bar0', 'foo/bar1'], + ['foo/bar0', 'foo/bar2'], + ['foo/bar0', 'foo/bar3'], + ['foo/bar1', 'foo/bar4'], + // bar4 from two parents + ['foo/bar2', 'foo/bar4'], + ['foo/bar3', 'foo/bar5'], + ['foo/bar4', 'foo/bar6'], + // bar6 from two parents at different levels + ['foo/bar5', 'foo/bar6'], + ], + }, + }) + await seneca.ready() + + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar0', + }) + + expect(res.deps).equal([ + ['foo/bar0', 'foo/bar1'], + ['foo/bar0', 'foo/bar2'], + ['foo/bar0', 'foo/bar3'], + ['foo/bar1', 'foo/bar4'], + ['foo/bar3', 'foo/bar5'], + ['foo/bar4', 'foo/bar6'], + ]) + }) + + test('find-deps-self-ref', async () => { + const seneca = makeSeneca().use(Traverse, { + relations: { + parental: [ + ['foo/bar0', 'foo/bar1'], + // Self loop + ['foo/bar1', 'foo/bar1'], + ['foo/bar1', 'foo/bar2'], + ], + }, + }) + await seneca.ready() + + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar0', + }) + // console.log('RES', res) + + expect(res.deps).equal([ + ['foo/bar0', 'foo/bar1'], + ['foo/bar1', 'foo/bar2'], + ]) + }) + + test('find-deps-all-default', async () => { + const seneca = makeSeneca().use(Traverse) + await seneca.ready() + + const res = await seneca.post('sys:traverse,find:deps') + // console.log('RES', res) + + expect(res.deps).equal([]) + }) + + test('find-deps-empty-list', async () => { + const seneca = makeSeneca().use(Traverse, { + relations: { + parental: [], + }, + }) + await seneca.ready() + + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar0', + }) + // console.log('RES', res) + + expect(res.deps).equal([]) + }) + + test('find-deps-l1', async () => { + const seneca = makeSeneca().use(Traverse, { + relations: { + parental: [ + ['foo/bar2', 'foo/bar3'], + ['foo/bar0', 'foo/bar1'], + ['foo/bar0', 'foo/bar2'], + ['foo/bar1', 'foo/bar4'], + ['foo/bar1', 'foo/bar5'], + ['foo/bar3', 'foo/bar6'], + ['foo/bar4', 'foo/bar7'], + ['foo/bar5', 'foo/bar8'], + ['foo/bar0', 'foo/zed0'], + ['foo/zed0', 'foo/zed1'], + ['foo/zed1', 'foo/zed2'], + ['bar/baz0', 'bar/baz1'], + ['qux/test', 'qux/prod'], + ['foo/bar2', 'foo/bar9'], + ['foo/bar6', 'foo/bar10'], + ['foo/bar7', 'foo/bar11'], + ], + }, + }) + await seneca.ready() + + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar1', + }) + + expect(res.deps).equal([ + // Level 0 + ['foo/bar1', 'foo/bar4'], + ['foo/bar1', 'foo/bar5'], + + // Level 1 + ['foo/bar4', 'foo/bar7'], + ['foo/bar5', 'foo/bar8'], + + // Level 2 + ['foo/bar7', 'foo/bar11'], + ]) + }) + + test('find-deps-l1-convergent', async () => { + const seneca = makeSeneca().use(Traverse, { + relations: { + parental: [ + ['foo/bar2', 'foo/bar3'], + ['foo/bar0', 'foo/bar1'], + ['foo/bar0', 'foo/bar2'], + ['foo/bar1', 'foo/bar4'], + ['foo/bar1', 'foo/bar5'], + ['foo/bar3', 'foo/bar6'], + ['foo/bar4', 'foo/bar7'], + ['foo/bar5', 'foo/bar8'], + ['foo/bar0', 'foo/zed0'], + ['foo/zed0', 'foo/zed1'], + ['foo/zed1', 'foo/zed2'], + ['bar/baz0', 'bar/baz1'], + ['qux/test', 'qux/prod'], + ['foo/bar2', 'foo/bar9'], + ['foo/bar6', 'foo/bar10'], + ['foo/bar7', 'foo/bar11'], + ['foo/bar4', 'foo/bar12'], + // bar12 converges from bar4 and bar5 + ['foo/bar5', 'foo/bar12'], + ['foo/bar12', 'foo/bar13'], + ], + }, + }) + await seneca.ready() + + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar1', + }) + + expect(res.deps).equal([ + // Level 0 + ['foo/bar1', 'foo/bar4'], + ['foo/bar1', 'foo/bar5'], + + // Level 1 + ['foo/bar4', 'foo/bar7'], + ['foo/bar4', 'foo/bar12'], + ['foo/bar5', 'foo/bar8'], + + // Level 2 + ['foo/bar7', 'foo/bar11'], + ['foo/bar12', 'foo/bar13'], + ]) + }) + + test('find-deps-l1-cycle', async () => { + const seneca = makeSeneca().use(Traverse, { + relations: { + parental: [ + ['foo/bar2', 'foo/bar3'], + ['foo/bar0', 'foo/bar1'], + ['foo/bar0', 'foo/bar2'], + ['foo/bar1', 'foo/bar4'], + ['foo/bar1', 'foo/bar5'], + ['foo/bar3', 'foo/bar6'], + ['foo/bar4', 'foo/bar7'], + ['foo/bar5', 'foo/bar8'], + ['foo/bar0', 'foo/zed0'], + ['foo/zed0', 'foo/zed1'], + ['foo/zed1', 'foo/zed2'], + ['bar/baz0', 'bar/baz1'], + ['qux/test', 'qux/prod'], + ['foo/bar2', 'foo/bar9'], + ['foo/bar6', 'foo/bar10'], + ['foo/bar7', 'foo/bar11'], + // Cycle: bar1 -> bar4 -> bar7 -> bar1 + ['foo/bar7', 'foo/bar1'], + ['foo/bar8', 'foo/bar12'], + ], + }, + }) + await seneca.ready() + + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar1', + }) + + expect(res.deps).equal([ + // Level 0 + ['foo/bar1', 'foo/bar4'], + ['foo/bar1', 'foo/bar5'], + + // Level 1 + ['foo/bar4', 'foo/bar7'], + ['foo/bar5', 'foo/bar8'], + + // Level 2 + ['foo/bar7', 'foo/bar11'], + ['foo/bar8', 'foo/bar12'], + ]) + }) + + test('find-deps-l2', async () => { + const seneca = makeSeneca().use(Traverse, { + relations: { + parental: [ + ['foo/bar2', 'foo/bar3'], + ['foo/bar0', 'foo/bar1'], + ['foo/bar0', 'foo/bar2'], + ['foo/bar1', 'foo/bar4'], + ['foo/bar1', 'foo/bar5'], + ['foo/bar3', 'foo/bar6'], + ['foo/bar4', 'foo/bar7'], + ['foo/bar5', 'foo/bar8'], + ['foo/bar0', 'foo/zed0'], + ['foo/zed0', 'foo/zed1'], + ['foo/zed1', 'foo/zed2'], + ['bar/baz0', 'bar/baz1'], + ['qux/test', 'qux/prod'], + ['foo/bar2', 'foo/bar9'], + ['foo/bar6', 'foo/bar10'], + ['foo/bar7', 'foo/bar11'], + ], + }, + }) + await seneca.ready() + + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar3', + }) + // console.log('RES', res) + + expect(res.deps).equal([ + // Level 0 + ['foo/bar3', 'foo/bar6'], + + // Level 1 + ['foo/bar6', 'foo/bar10'], + ]) + }) + + test('find-deps-l2-convergent', async () => { + const seneca = makeSeneca().use(Traverse, { + relations: { + parental: [ + ['foo/bar2', 'foo/bar3'], + ['foo/bar0', 'foo/bar1'], + ['foo/bar0', 'foo/bar2'], + ['foo/bar1', 'foo/bar4'], + ['foo/bar1', 'foo/bar5'], + ['foo/bar3', 'foo/bar6'], + ['foo/bar4', 'foo/bar7'], + ['foo/bar5', 'foo/bar8'], + ['foo/bar0', 'foo/zed0'], + ['foo/zed0', 'foo/zed1'], + ['foo/zed1', 'foo/zed2'], + ['bar/baz0', 'bar/baz1'], + ['qux/test', 'qux/prod'], + ['foo/bar2', 'foo/bar9'], + ['foo/bar6', 'foo/bar10'], + ['foo/bar7', 'foo/bar11'], + ['foo/bar3', 'foo/bar12'], + ['foo/bar6', 'foo/bar13'], + ['foo/bar10', 'foo/bar14'], + // bar14 converges from bar10 and bar12 + ['foo/bar12', 'foo/bar14'], + ['foo/bar14', 'foo/bar15'], + ], + }, + }) + await seneca.ready() + + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar3', + }) + + expect(res.deps).equal([ + // Level 0 + ['foo/bar3', 'foo/bar6'], + ['foo/bar3', 'foo/bar12'], + + // Level 1 + ['foo/bar6', 'foo/bar10'], + ['foo/bar6', 'foo/bar13'], + ['foo/bar12', 'foo/bar14'], + + // Level 2 + ['foo/bar14', 'foo/bar15'], + ]) + }) + + test('find-deps-l2-cycle', async () => { + const seneca = makeSeneca().use(Traverse, { + relations: { + parental: [ + ['foo/bar2', 'foo/bar3'], + ['foo/bar0', 'foo/bar1'], + ['foo/bar0', 'foo/bar2'], + ['foo/bar1', 'foo/bar4'], + ['foo/bar1', 'foo/bar5'], + ['foo/bar3', 'foo/bar6'], + ['foo/bar4', 'foo/bar7'], + ['foo/bar5', 'foo/bar8'], + ['foo/bar0', 'foo/zed0'], + ['foo/zed0', 'foo/zed1'], + ['foo/zed1', 'foo/zed2'], + ['bar/baz0', 'bar/baz1'], + ['qux/test', 'qux/prod'], + ['foo/bar2', 'foo/bar9'], + ['foo/bar6', 'foo/bar10'], + ['foo/bar7', 'foo/bar11'], + ['foo/bar10', 'foo/bar12'], + // Cycle: bar3 -> bar6 -> bar10 -> bar12 -> bar3 + ['foo/bar12', 'foo/bar3'], + ['foo/bar10', 'foo/bar13'], + ], + }, + }) + await seneca.ready() + + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar3', + }) + + expect(res.deps).equal([ + // Level 0 + ['foo/bar3', 'foo/bar6'], + + // Level 1 + ['foo/bar6', 'foo/bar10'], + + // Level 2 + ['foo/bar10', 'foo/bar12'], + ['foo/bar10', 'foo/bar13'], + ]) + }) + + test('find-deps-l2-multi-level-convergent', async () => { + const seneca = makeSeneca().use(Traverse, { + relations: { + parental: [ + ['foo/bar2', 'foo/bar3'], + ['foo/bar0', 'foo/bar1'], + ['foo/bar0', 'foo/bar2'], + ['foo/bar1', 'foo/bar4'], + ['foo/bar1', 'foo/bar5'], + ['foo/bar3', 'foo/bar6'], + ['foo/bar4', 'foo/bar7'], + ['foo/bar5', 'foo/bar8'], + ['foo/bar0', 'foo/zed0'], + ['foo/zed0', 'foo/zed1'], + ['foo/zed1', 'foo/zed2'], + ['bar/baz0', 'bar/baz1'], + ['qux/test', 'qux/prod'], + ['foo/bar2', 'foo/bar9'], + ['foo/bar6', 'foo/bar10'], + ['foo/bar7', 'foo/bar11'], + ['foo/bar3', 'foo/bar12'], + ['foo/bar3', 'foo/bar13'], + ['foo/bar12', 'foo/bar14'], + // Second path to bar14 + ['foo/bar13', 'foo/bar14'], + // Third path to bar14 + ['foo/bar6', 'foo/bar14'], + ['foo/bar14', 'foo/bar15'], + ], + }, + }) + await seneca.ready() + + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo/bar3', + }) + + expect(res.deps).equal([ + // Level 0 + ['foo/bar3', 'foo/bar6'], + ['foo/bar3', 'foo/bar12'], + ['foo/bar3', 'foo/bar13'], + + // Level 1 + ['foo/bar6', 'foo/bar10'], + ['foo/bar12', 'foo/bar14'], + + // Level 2 + ['foo/bar14', 'foo/bar15'], + ]) + }) + + test('find-deps-single-cycle', async () => { + const seneca = makeSeneca().use(Traverse, { + relations: { + parental: [ + ['A', 'B'], + ['A', 'C'], + ['C', 'E'], + ['C', 'D'], + ['E', 'G'], + ['E', 'F'], + ['F', 'H'], + ['C', 'A'], + ['N', 'M'], + ], + }, + }) + await seneca.ready() + + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'C', + }) + + expect(res.deps).equal([ + // Level 0 + ['C', 'A'], + ['C', 'D'], + ['C', 'E'], + + // Level 1 + ['A', 'B'], + ['E', 'F'], + ['E', 'G'], + + // Level 2 + ['F', 'H'], + ]) + }) + + test('find-deps-single-missing-node', async () => { + const seneca = makeSeneca().use(Traverse, { + relations: { + parental: [ + ['A', 'B'], + ['B', 'C'], + ['D', 'E'], + ], + }, + }) + await seneca.ready() + + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'X', + }) + + // X has no children + expect(res.deps).equal([]) + }) + + test('find-deps-deep-linear-chain', async () => { + const seneca = makeSeneca().use(Traverse, { + relations: { + parental: [ + ['node0', 'node1'], + ['node1', 'node2'], + ['node2', 'node3'], + ['node3', 'node4'], + ['node4', 'node5'], + ['node5', 'node6'], + ['node6', 'node7'], + ['node7', 'node8'], + ['node8', 'node9'], + ['node9', 'node10'], + ], + }, + }) + await seneca.ready() + + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'node0', + }) + + expect(res.deps).equal([ + ['node0', 'node1'], + ['node1', 'node2'], + ['node2', 'node3'], + ['node3', 'node4'], + ['node4', 'node5'], + ['node5', 'node6'], + ['node6', 'node7'], + ['node7', 'node8'], + ['node8', 'node9'], + ['node9', 'node10'], + ]) + }) + + test('find-deps-binary-tree', async () => { + const seneca = makeSeneca().use(Traverse, { + relations: { + parental: [ + ['A', 'B'], + ['A', 'C'], + ['B', 'D'], + ['B', 'E'], + ['C', 'F'], + ['C', 'G'], + ['D', 'H'], + ['D', 'I'], + ['E', 'J'], + ['E', 'K'], + ], + }, + }) + await seneca.ready() + + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'A', + }) + + expect(res.deps).equal([ + // Level 0 + ['A', 'B'], + ['A', 'C'], + + // Level 1 + ['B', 'D'], + ['B', 'E'], + ['C', 'F'], + ['C', 'G'], + + // Level 2 + ['D', 'H'], + ['D', 'I'], + ['E', 'J'], + ['E', 'K'], + ]) + }) + + test('find-deps-diamond-pattern', async () => { + const seneca = makeSeneca().use(Traverse, { + relations: { + parental: [ + ['A', 'B'], + ['A', 'C'], + ['B', 'D'], + ['C', 'D'], + ['D', 'E'], + ], + }, + }) + await seneca.ready() + + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'A', + }) + + // D is reached first from B (alphabetically first) + expect(res.deps).equal([ + // Level 0 + ['A', 'B'], + ['A', 'C'], + + // Level 1 - D reached via B + ['B', 'D'], + + // Level 2 + ['D', 'E'], + ]) + }) + + test('find-deps-mixed-alph-sort', async () => { + const seneca = makeSeneca().use(Traverse, { + relations: { + parental: [ + ['root', 'node10'], + ['root', 'node2'], + ['root', 'node1'], + ['root', 'node20'], + ['node1', 'child2'], + ['node2', 'child1'], + ['node10', 'child10'], + ['node20', 'child20'], + ], + }, + }) + await seneca.ready() + + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'root', + }) + + // Natural sorting: node1, node2, node10, node20 + expect(res.deps).equal([ + // Level 0 + ['root', 'node1'], + ['root', 'node2'], + ['root', 'node10'], + ['root', 'node20'], + + // Level 1 + ['node1', 'child2'], + ['node2', 'child1'], + ['node10', 'child10'], + ['node20', 'child20'], + ]) + }) + + test('find-deps-all-nodes-converge-to-one', async () => { + const seneca = makeSeneca().use(Traverse, { + relations: { + parental: [ + ['root', 'A'], + ['root', 'B'], + ['root', 'C'], + ['A', 'target'], + ['B', 'target'], + ['C', 'target'], + ['target', 'end'], + ], + }, + }) + await seneca.ready() + + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'root', + }) + + // target reached first via A (alphabetically first) + expect(res.deps).equal([ + // Level 0 + ['root', 'A'], + ['root', 'B'], + ['root', 'C'], + + // Level 1 - target via A + ['A', 'target'], + + // Level 2 + ['target', 'end'], + ]) + }) + + test('find-deps-deep-10-levels-complex', async () => { + const seneca = makeSeneca().use(Traverse, { + relations: { + parental: [ + // ========== LEVEL 0 → LEVEL 1 ========== + ['foo', 'bar'], + ['foo', 'zed'], + ['foo', 'qux'], + + // ========== LEVEL 1 → LEVEL 2 ========== + ['bar', 'bar1'], + ['bar', 'bar2'], + ['zed', 'zed1'], + ['zed', 'zed2'], + ['qux', 'qux1'], + + // ========== LEVEL 2 → LEVEL 3 ========== + ['bar1', 'bar3'], + ['bar1', 'bar4'], + ['bar2', 'bar5'], + ['zed1', 'zed3'], + ['zed1', 'zed4'], + ['zed2', 'zed5'], + ['qux1', 'qux2'], + + // ========== LEVEL 3 → LEVEL 4 ========== + ['bar3', 'bar6'], + ['bar3', 'bar7'], + ['bar4', 'bar8'], + ['bar5', 'bar9'], + ['zed3', 'zed6'], + ['zed4', 'zed7'], + ['zed5', 'zed8'], + ['qux2', 'qux3'], + + // ========== LEVEL 4 → LEVEL 5 ========== + ['bar6', 'bar10'], + ['bar7', 'bar11'], + ['bar8', 'bar12'], + ['bar9', 'bar13'], + ['zed6', 'zed9'], + ['zed7', 'zed10'], + ['zed8', 'zed11'], + ['qux3', 'qux4'], + + // ========== LEVEL 5 → LEVEL 6 (switch to composed names) ========== + ['bar10', 'foo/bar1'], + ['bar10', 'foo/bar2'], + ['bar11', 'foo/bar3'], + ['bar12', 'foo/bar4'], + ['bar13', 'foo/bar5'], + ['zed9', 'foo/zed1'], + ['zed10', 'foo/zed2'], + ['zed11', 'foo/zed3'], + // Cycle: qux4 → foo (already visited at level 0) + ['qux4', 'foo'], + ['qux4', 'foo/qux1'], + + // ========== LEVEL 6 → LEVEL 7 ========== + ['foo/bar1', 'foo/bar6'], + ['foo/bar1', 'foo/bar7'], + ['foo/bar2', 'foo/bar8'], + ['foo/bar3', 'foo/bar9'], + ['foo/bar4', 'foo/bar10'], + ['foo/bar5', 'foo/bar11'], + ['foo/zed1', 'foo/zed4'], + ['foo/zed2', 'foo/zed5'], + ['foo/zed3', 'foo/zed6'], + ['foo/qux1', 'foo/qux2'], + + // ========== LEVEL 7 → LEVEL 8 ========== + ['foo/bar6', 'bar/foo1'], + ['foo/bar7', 'bar/foo2'], + ['foo/bar8', 'bar/foo3'], + ['foo/bar9', 'bar/foo4'], + ['foo/bar10', 'bar/foo5'], + ['foo/bar11', 'bar/foo6'], + ['foo/zed4', 'zed/foo1'], + ['foo/zed5', 'zed/foo2'], + // Convergent: foo/zed6 and foo/qux2 both point to zed/foo3 + ['foo/zed6', 'zed/foo3'], + ['foo/qux2', 'zed/foo3'], + + // ========== LEVEL 8 → LEVEL 9 ========== + ['bar/foo1', 'bar/foo7'], + ['bar/foo2', 'bar/foo8'], + ['bar/foo3', 'bar/foo9'], + ['bar/foo4', 'bar/foo10'], + ['bar/foo5', 'bar/foo11'], + ['bar/foo6', 'bar/foo12'], + ['zed/foo1', 'zed/foo4'], + ['zed/foo2', 'zed/foo5'], + ['zed/foo3', 'zed/foo6'], + + // ========== LEVEL 9 → LEVEL 10 ========== + ['bar/foo7', 'qux/bar1'], + ['bar/foo8', 'qux/bar2'], + ['bar/foo9', 'qux/bar3'], + ['bar/foo10', 'qux/bar4'], + ['bar/foo11', 'qux/bar5'], + ['bar/foo12', 'qux/bar6'], + ['zed/foo4', 'qux/zed1'], + ['zed/foo5', 'qux/zed2'], + ['zed/foo6', 'qux/zed3'], + + // ========== LEVEL 10 → LEVEL 11 ========== + ['qux/bar1', 'qux/bar7'], + ['qux/bar2', 'qux/bar8'], + ['qux/bar3', 'qux/bar9'], + ['qux/bar4', 'qux/bar10'], + ['qux/bar5', 'qux/bar11'], + ['qux/bar6', 'qux/bar12'], + ['qux/zed1', 'qux/zed4'], + ['qux/zed2', 'qux/zed5'], + ['qux/zed3', 'qux/zed6'], + ], + }, + }) + await seneca.ready() + + const res = await seneca.post('sys:traverse,find:deps', { + rootEntity: 'foo', + }) + + expect(res.deps).equal([ + // ========== LEVEL 0 ========== + ['foo', 'bar'], + ['foo', 'qux'], + ['foo', 'zed'], + + // ========== LEVEL 1 ========== + ['bar', 'bar1'], + ['bar', 'bar2'], + ['qux', 'qux1'], + ['zed', 'zed1'], + ['zed', 'zed2'], + + // ========== LEVEL 2 ========== + ['bar1', 'bar3'], + ['bar1', 'bar4'], + ['bar2', 'bar5'], + ['qux1', 'qux2'], + ['zed1', 'zed3'], + ['zed1', 'zed4'], + ['zed2', 'zed5'], + + // ========== LEVEL 3 ========== + ['bar3', 'bar6'], + ['bar3', 'bar7'], + ['bar4', 'bar8'], + ['bar5', 'bar9'], + ['qux2', 'qux3'], + ['zed3', 'zed6'], + ['zed4', 'zed7'], + ['zed5', 'zed8'], + + // ========== LEVEL 4 ========== + ['bar6', 'bar10'], + ['bar7', 'bar11'], + ['bar8', 'bar12'], + ['bar9', 'bar13'], + ['qux3', 'qux4'], + ['zed6', 'zed9'], + ['zed7', 'zed10'], + ['zed8', 'zed11'], + + // ========== LEVEL 5 ========== + ['bar10', 'foo/bar1'], + ['bar10', 'foo/bar2'], + ['bar11', 'foo/bar3'], + ['bar12', 'foo/bar4'], + ['bar13', 'foo/bar5'], + // Note: qux4 → foo creates cycle (foo visited at level 0) + ['qux4', 'foo/qux1'], + ['zed9', 'foo/zed1'], + ['zed10', 'foo/zed2'], + ['zed11', 'foo/zed3'], + + // ========== LEVEL 6 ========== + ['foo/bar1', 'foo/bar6'], + ['foo/bar1', 'foo/bar7'], + ['foo/bar2', 'foo/bar8'], + ['foo/bar3', 'foo/bar9'], + ['foo/bar4', 'foo/bar10'], + ['foo/bar5', 'foo/bar11'], + ['foo/qux1', 'foo/qux2'], + ['foo/zed1', 'foo/zed4'], + ['foo/zed2', 'foo/zed5'], + ['foo/zed3', 'foo/zed6'], + + // ========== LEVEL 7 ========== + ['foo/bar6', 'bar/foo1'], + ['foo/bar7', 'bar/foo2'], + ['foo/bar8', 'bar/foo3'], + ['foo/bar9', 'bar/foo4'], + ['foo/bar10', 'bar/foo5'], + ['foo/bar11', 'bar/foo6'], + ['foo/qux2', 'zed/foo3'], + ['foo/zed4', 'zed/foo1'], + ['foo/zed5', 'zed/foo2'], + // Note: foo/zed6 → zed/foo3 is skipped because zed/foo3 was already visited via foo/qux2 + + // ========== LEVEL 8 ========== + ['bar/foo1', 'bar/foo7'], + ['bar/foo2', 'bar/foo8'], + ['bar/foo3', 'bar/foo9'], + ['bar/foo4', 'bar/foo10'], + ['bar/foo5', 'bar/foo11'], + ['bar/foo6', 'bar/foo12'], + ['zed/foo1', 'zed/foo4'], + ['zed/foo2', 'zed/foo5'], + ['zed/foo3', 'zed/foo6'], + + // ========== LEVEL 9 ========== + ['bar/foo7', 'qux/bar1'], + ['bar/foo8', 'qux/bar2'], + ['bar/foo9', 'qux/bar3'], + ['bar/foo10', 'qux/bar4'], + ['bar/foo11', 'qux/bar5'], + ['bar/foo12', 'qux/bar6'], + ['zed/foo4', 'qux/zed1'], + ['zed/foo5', 'qux/zed2'], + ['zed/foo6', 'qux/zed3'], + + // ========== LEVEL 10 ========== + ['qux/bar1', 'qux/bar7'], + ['qux/bar2', 'qux/bar8'], + ['qux/bar3', 'qux/bar9'], + ['qux/bar4', 'qux/bar10'], + ['qux/bar5', 'qux/bar11'], + ['qux/bar6', 'qux/bar12'], + ['qux/zed1', 'qux/zed4'], + ['qux/zed2', 'qux/zed5'], + ['qux/zed3', 'qux/zed6'], + ]) + }) }) + +function makeSeneca(opts: any = {}) { + const seneca = Seneca({ legacy: false }).test().use('promisify').use('entity') + return seneca +}