Custom Library (pseudo-jQuery)
You have a fn which creates the internal object. Maybe call it something more precise, like Collection or MakeCollection. Since the argument to the fn will be an array or an NodeList, you can concisely assign each property of the argument to the instance with Object.assign, no need for manual iteration.

When possible, consider using dot notation instead of bracket notation - dot notation is shorter, easier to read, and works better with minifiers. See the eslint rule.

Right now, you're creating new methods for the instance every time the library is called. It would be more efficient to put them on the prototype instead, eg:

_o.fn.prototype.eq = function(num) {
  // ...


But since it's 2020, it would make sense to at least write with modern ES2015 syntax, and use a class:

class Collection {
  constructor() {
    // ...
  }
  eq(num) {
    // ...
  }


Using modern syntax often makes code more concise and easier to read, which means less surface area for bugs, as well as more methods available for use. If you need compatibility for obsolete 6+ year old browsers, use Babel to transpile it into ES5 syntax automatically for production (but keep your source code modern!).

In your eq method, something else that keeps code easy to read is when variables aren't reassigned unnecessarily. For example, if you see a variable created at line 5, and you see it used on line 25, if you aren't sure if you're reassigned it, you'll have to look closely to each line 6-24 to be absolutely certain of its value at 25. But even with that, I'm quite doubtful that the numeric verification in the eq provides any benefits - not even jQuery does such a thing. I think you may as well just return the num property on the instance. Worst-case, if the user provides an invalid argument, they'll get undefined in return, which is probably fine as-is.

eq(num) {
  return this[num];
}


Though, if you wanted to emulate jQuery, eq should return a new collection, including the wrapper, not the underlying element:

eq(num) {
  return new Collection(this[num]);
}


If you want the library to be similar to jQuery, something that would be helpful would be if it doesn't throw when the collection is empty. If the elms turn out to be empty, currently, you return null, which means that to be safe, a consumer of the library would often enough have to do something like:

const $elms = $('.foo');
if ($elms) {
  $elms.display('inline-block');
}


instead of unconditionally calling $('.foo').display('inline-block'), which looks much cleaner. The first method has the same issue, though if this is deliberate, that's fine.

Your first method is doing something markedly different from jQuery's - yours takes a selector and searches for a descendant matching the selector, while jQuery's doesn't (jQuery's just extracts the first element in the collection). For those already experienced with jQuery, this has good potential to cause confusion. To find a selector-matching descendant of the first element in the collection, consider renaming your method, maybe to something like firstDescendant.

In each of your class methods, rather than assigning to f_condition if it doesn't exist on the first line:

addClass(className, f_condition) {
  f_condition = f_condition || function() {
    return true;
  };


Consider using default arguments instead:

addClass(className, f_condition = () =&gt; true) {


An option to make your addClass, removeClass, toggleClass, and display loops less repetitive would be to have a helper function which iterates over all elements in the collection, and executes a callback if a test provided by a different callback is passed:

_iterate(test, callback) {
  for (let i = 0; i &lt; this.length; i++) {
    if (test(this[i], i, this)) {
      callback(this[i]);
    }
  }
}
addClass(className, f_condition = () =&gt; true) {
  this._iterate(f_condition, elm =&gt; elm.classList.add(className));
  return this;
}


For the display method, argument overloading is a bit messy in Javascript. You can do it if you want, but I think the code's logic would be easier to follow and it would make more intuitive sense to consumers if an object with multiple optional properties was passed instead:

display({ newDisplay = 'block', test = () =&gt; true } = {}) {
  this._iterate(test, elm =&gt; elm.style.display = newDisplay)
  return this;
}


Another benefit to this is that you don't need to reassign anything.

Put it all together:



;(function() {
  window._o = function(selector) {
    if (!selector) {
      throw new Error("Cannot create Object from " + selector);
    }
    return new _o.Collection(
      selector instanceof HTMLElement
        ? [selector]
        : document.querySelectorAll(selector)
    );
  }
  _o.Collection = class Collection {
    constructor(elems) {
      Object.assign(this, elems);
      this.length = elems?.length || 0;
    }
    *[Symbol.iterator]() {
      for (let i = 0; i &lt; this.length; i++) {
        yield this[i];
      }
    }
    eq(num) {
      return new Collection(this[num]);
    }
    first() {
      return new Collection(this[0]);
    }
    firstDescendant(selector) {
      // don't forget to check if elms[0] exists first:
      return new Collection(this[0]?.querySelector(selector));
    }
    
    _iterate(test, callback) {
      for (let i = 0; i &lt; this.length; i++) {
        if (test(this[i], i, this)) {
          callback(this[i]);
        }
      }
    }
    /***/
    /* className features */
    addClass(className, f_condition = () =&gt; true) {
      this._iterate(f_condition, elm =&gt; elm.classList.add(className));
      return this;
    }
    removeClass(className, f_condition = () =&gt; true) {
      this._iterate(f_condition, elm =&gt; elm.classList.remove(className));
      return this;
    }
    toggleClass(className, f_condition = () =&gt; true) {
      this._iterate(f_condition, elm =&gt; elm.classList.toggle(className));
      return this;
    }

    /***/
    /* show - hide */
    display({ newDisplay = 'block', test = () =&gt; true } = {}) {
      this._iterate(test, elm =&gt; elm.style.display = newDisplay)
      return this;
    }
  };
})();
.demo {
  width: 50px;
  height: 50px;
  margin: 5px;
  background: orange;
}

.blue {
  background: #045acf;
}
&lt;div class="demo"&gt;&lt;/div&gt;
&lt;div class="demo"&gt;&lt;/div&gt;
&lt;div class="demo"&gt;&lt;/div&gt;
&lt;div class="demo"&gt;&lt;/div&gt;
&lt;div class="demo"&gt;&lt;/div&gt;
&lt;div class="demo"&gt;&lt;/div&gt;
&lt;div class="demo"&gt;&lt;/div&gt;

&lt;script&gt;
  setTimeout(function() {
    // Test to make sure selecting non-existent elements doesn't throw:
    _o('foo')
      .first()
      .eq(0)
      .firstDescendant('bar')
      .addClass('blue');
    _o('.demo').addClass('blue', (e, i) =&gt; i % 2).display({ newDisplay: 'inline-block' });
  });
&lt;/script&gt;





  write a separate mini-library before the main code?


You can if you want a bit more experience figuring out how such a library would be made, but for anything you expect to maintain (or that others will maintain), I'd recommend using one of the common libraries that are already tried-and-tested and have thorough documentation, Q+A, and support (like jQuery). Otherwise, any reader of the code will first have to internalize your library before actually getting to any of the meat of what the main script does. It's a good amount of overhead without any real benefit. Not that you can't create helper functions (or an object of helper functions) for yourself, but it's not the best idea to reinvent the wheel with serious code.