@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
Keywords
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-optionIt 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 testLicense
This project is released under the Apache License 2.0.
For more details, please see in the LICENSE file.
Developer
Copyright
© 2026 Open Reach Tech Inc.
