npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@openreachtech/mentsu-crmkit-edit-tool-search-option

v0.0.0

Published

The shared contract for an edit tool's search option: reading the stored value, resolving its payloads, and building the arguments of one edit search query

Readme

@openreachtech/mentsu-crmkit-edit-tool-search-option

The shared contract for an edit tool's search option: reading the stored value, resolving its payloads, and building the arguments of one edit search query.

Concept

A searchable field offers a list of options to pick from. A search option is the stored configuration saying which query produces that list, and what arguments to pass it.

Three parties handle the same stored value:

  • a server reads it out of a column and sends it to a screen
  • a seeder writes it into that column
  • a screen turns it into the arguments of the query that fetches the options

Each used to hold its own copy of the rules. The copies agreed by coincidence, and could be edited apart with every test still passing. This package holds the contract once, so a rule tightened on one side cannot be left alone on another.

It reaches no database, no transport and no screen, and it carries no runtime dependency — so a browser bundle can take it whole.

Installation

Requires Node.js 20.x (the version the CI builds against).

npm install @openreachtech/mentsu-crmkit-edit-tool-search-option

It is an ES module ("type": "module"); import it with ESM import syntax.

Usage

Reading a stored search option

SearchOptionReader takes the text a column holds and answers with the whole search option. A host injects a resolver for values the record carries; a fixed value needs none.

import { SearchOptionReader } from '@openreachtech/mentsu-crmkit-edit-tool-search-option'

const alphaReader = SearchOptionReader.create({
  fieldPathResolver: ({ value }) =>
    fieldPathValueExtractor.extractFieldPathValue({ fieldPath: value }),
})

const alphaOption = alphaReader.readSearchOption({
  searchOptionValue: columnEntity.searchOptionValue,
  editSearchQueryId: editSearchQuery.id,
  hasTextSearch: editSearchQuery.hasTextSearch,
})

It throws nothing of its own accord. A value that is absent, empty or unparseable yields no payloads, and so does one whose payload list is missing or is not a list. A payload whose payloadKey is not a filled string is dropped, since that key names a query argument. A field whose stored option is broken therefore offers no options, instead of failing the query that carries it.

Writing one

SearchOptionWriter builds the stored value, so that a seeder never assembles the two-level envelope by hand.

import { SearchOptionWriter } from '@openreachtech/mentsu-crmkit-edit-tool-search-option'

const alphaWriter = SearchOptionWriter.create()

const alphaStoredValue = alphaWriter.generateStoredValue({
  searchPayloads: [
    {
      payloadKey: 'companyId',
      sourceValueType: 'FORM_FIELD_REFERENCE',
      formFieldReference: {
        sourceOriginObjectColumnId: 1010401,
        columnKey: 'works_company_id',
      },
    },
  ],
})

It answers null rather than emitting what the check refuses — a fixed value JSON cannot hold included. Whatever it admits, the reader reads back to the same payloads.

Checking one before it is written

SearchOptionValidator answers with issue codes rather than throwing, so a caller can show every refusal at once.

import {
  SearchOptionValidator,
  VALIDATION_ISSUE_CODE,
} from '@openreachtech/mentsu-crmkit-edit-tool-search-option'

const alphaValidator = SearchOptionValidator.create({
  acceptedPayloadKeys: ['companyId'],
})

const alphaIssues = alphaValidator.buildIssues({ searchPayloads })
// [{ issueCode: 'UNACCEPTED_PAYLOAD_KEY', payloadKey: 'companyid' }]

Building a query's arguments

SearchArgumentBuilder fills each payload from the form being filled in right now, through the accessor the screen supplies, and names the columns the screen must watch so that changing one refetches the options.

import { SearchArgumentBuilder } from '@openreachtech/mentsu-crmkit-edit-tool-search-option'

const alphaBuilder = SearchArgumentBuilder.create({
  searchPayloads: alphaOption.searchPayloads,
  formValueAccessor: ({ sourceOriginObjectColumnId, columnKey }) =>
    formValues[columnKey],
})

const alphaArguments = alphaBuilder.buildSearchArguments()
// [{ payloadKey: 'companyId', value: '1010401' }]

const alphaWatchedKeys = alphaBuilder.extractWatchedColumnKeys()
// ['works_company_id']

It throws nothing of its own accord either. A field the user has not filled in sends an empty argument, so the option list is narrowed rather than the request failing.

API

Class members are written with the following notation.

| notation | members | | :-- | :-- | | #instanceProperty | instance property | | #instanceMethod() | instance method | | #get:instanceGetter | instance getter | | #set:instanceSetter | instance setter | | .staticProperty | static property | | .staticMethod() | static method | | .get:staticGetter | static getter | | .set:staticSetter | static setter |

SearchOptionReader

| member | description | | :-- | :-- | | .create() | Creates a reader, optionally given a fieldPathResolver. | | #readSearchOption() | Reads one stored value into { editSearchQueryId, hasTextSearch, searchPayloads }. |

SearchOptionWriter

| member | description | | :-- | :-- | | .create() | Creates a writer, optionally given a searchOptionValidator. | | #generateStoredValue() | Builds the stored JSON text, or null when the payloads are refused. | | #buildStoredOption() | Builds the stored structure before it is serialized, or null. |

SearchOptionValidator

| member | description | | :-- | :-- | | .create() | Creates a check, optionally given acceptedPayloadKeys. | | #buildIssues() | Answers every issue across a list of payloads. | | #buildPayloadIssues() | Answers the issues of one payload. |

SearchArgumentBuilder

| member | description | | :-- | :-- | | .create() | Creates a builder, given searchPayloads and optionally a formValueAccessor. | | #buildSearchArguments() | Builds one argument per payload. Never throws. | | #extractWatchedColumnKeys() | Names the columns a change to which refetches the options. Never throws. |

SearchPayload

| member | description | | :-- | :-- | | .create() | Creates one payload with all four of its fields present. |

ValueSourceResolver

| member | description | | :-- | :-- | | .create() | Creates a resolver, optionally given a fieldPathResolver. | | #resolveValue() | Resolves one { sourceValueType, value } envelope. |

Also exported

| member | description | | :-- | :-- | | SOURCE_VALUE_TYPE | The wider value-source grammar this product's stored JSON speaks. | | EDIT_SEARCH_OPTION_SOURCE_VALUE_TYPE | The source kinds one search payload may declare. | | VALIDATION_ISSUE_CODE | The rules a refusal can name. | | acceptsSearchPayloadKey() | Whether a payload key can name a query argument. | | PROHIBITED_COLUMN_KEY | The column keys a form may not be read by. |

How the two shared rules work

Two rules are asked by three classes each, at three moments in one payload's life — before it is written, as it is read back, and as it becomes a query argument.

acceptsSearchPayloadKey() decides whether a key can name a query argument. That key is the one position a parameterized query does not protect, so anything that is not a filled string names no argument and drops the whole payload with it.

PROHIBITED_COLUMN_KEY is derived from Object.getOwnPropertyNames(Object.prototype) rather than transcribed, so a member a later engine adds is refused without anyone editing a list. A column key reaches a host as the property key of its form values, and a natural accessor — ({ columnKey }) => formValues[columnKey] — would answer those names off Object.prototype rather than off the form. A host should read its form values with Object.hasOwn() or hold them in a null-prototype map all the same.

Contribution

Bug reports, feature requests, and code contributions are welcome.

Feel free to contact us through GitHub Issues.

git clone https://github.com/openreachtech/mentsu-crmkit-edit-tool-search-option.git
cd mentsu-crmkit-edit-tool-search-option
npm install
npm run lint
npm test

License

This project is released under the Apache License 2.0.

For more details, please see in the LICENSE file.

Developer

Open Reach Tech Inc.

Copyright

© 2026 Open Reach Tech Inc.