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-action-value

v0.0.0

Published

Renchan action value - module to handle the value of an action in ORT crm-kit

Readme

@openreachtech/mentsu-action-value

The shared record-action contract for Mentsu: the envelope a seeded action is stored in, the vocabulary its payload draws on, and BaseRecordActionHandler — the one class a frontend writes to declare a new action.

Zero runtime dependencies, pure ESM, browser-safe. The backend emits the envelope; the frontend fires it; neither can drift from the other on what an action value is.

The envelope

Exactly three properties. Every action, without exception, has these and nothing else at the top level:

{
  "actionType": "INVOKE_FORM_SEQUENCE",
  "originObjectCategoryId": 5,
  "payload": {}
}
  • actionType — the discriminator; selects the handler class. It is the action's master category name, supplied alongside the stored value. The stored value's own actionType, if a seed still carries one, is never read.
  • originObjectCategoryId — the origin object category the action is based on.
  • payload — an object. This package requires that it is one; what is inside belongs to the action.

That is the whole of the fixed part. An action whose actionType no handler serves simply yields no handler — the button does nothing rather than crashing the view.

The payload vocabulary

Every payload property whose value is not a plain literal is a two-key envelope, { sourceValueType, value }:

| sourceValueType | value | resolved from | | :-- | :-- | :-- | | FIXED_VALUE | the literal itself, any JSON type | — | | COLUMN_REFERENCE | { sourceOriginObjectColumnId?, columnKey? }, at least one | columns | | RECORD_UNIQUE_KEY | omitted | originObjectUniqueKey | | FORM_SEQUENCE | { formSequenceId } | submittedValues |

Not every property is a value source: plain scalars (hasAssociationSource: true) and nested objects (associationSourceRecord) stay as they are, and each is resolved by what its shape says it is. At most one form sequence per action.

{
  "actionType": "INVOKE_FORM_SEQUENCE",
  "originObjectCategoryId": 5,
  "payload": {
    "submittedValues": {
      "sourceValueType": "FORM_SEQUENCE",
      "value": { "formSequenceId": 40001 }
    },
    "hasAssociationSource": true,
    "associationSourceRecord": {
      "originObjectUniqueKey": { "sourceValueType": "RECORD_UNIQUE_KEY" }
    },
    "otherProperty": { "sourceValueType": "FIXED_VALUE", "value": 123 },
    "otherProperty3": {
      "sourceValueType": "COLUMN_REFERENCE",
      "value": {
        "sourceOriginObjectColumnId": 605101,
        "columnKey": "client_job_offer_industries_industry_major_category_id"
      }
    }
  }
}

Install

npm install @openreachtech/mentsu-action-value

Consuming repos that pin @openreachtech to GitHub Packages in .npmrc need a scope override for this one — it is published to npmjs.org.

Declaring an action

One class. No registration call, no shared list to edit.

import {
  BaseRecordActionHandler,
} from '@openreachtech/mentsu-action-value'

import CopyOriginObjectRecordMutationGraphqlLauncher from '~/app/graphql/client/mutations/copyOriginObjectRecord/CopyOriginObjectRecordMutationGraphqlLauncher'

export default class InvokeCopyRecordActionHandler extends BaseRecordActionHandler {
  /** @override */
  static get actionType () {
    return 'INVOKE_COPY_RECORD'
  }

  /** @override */
  static get MutationLauncherCtor () {
    return CopyOriginObjectRecordMutationGraphqlLauncher
  }

  /** @override */
  static get modalKey () {
    return null
  }

  /** @override */
  buildActionMutationPayload ({
    columns,
    originObjectUniqueKey,
    submittedValues,
  }) {
    const resolved = this.resolveFields({
      columns,
      originObjectUniqueKey,
      submittedValues,
    })

    return {
      originObjectCategoryId: this.originObjectCategoryId,
      originObjectUniqueKey: Number(resolved.originObjectUniqueKey),
    }
  }
}

How a host reads a handler

| modalKey | MutationLauncherCtor | behaviour | | :-- | :-- | :-- | | null | set | build the input, call the mutation | | set | set | open the modal with buildActionModalProps(), take the values it submits, build the input, call the mutation | | set | null | open the modal with buildActionModalProps(); the modal submits itself |

One action, one mutation: an action that needs a different mutation is a different action, with its own seeded row and its own handler class.

buildActionModalProps() defaults to the mutation's input built without submitted values, which is right whenever the modal opens on the same object the mutation takes; override it when the two shapes differ.

Registry

The glob must live in the app — a bundler rewrites it at build time and a package cannot glob its consumer's directory — but everything after it belongs to this package.

import {
  ActionHandlerRegistry,
} from '@openreachtech/mentsu-action-value'

const registry = ActionHandlerRegistry.fromModules(
  import.meta.glob('./handlers/*.js', { eager: true })
)

const handler = registry.createHandler({
  actionValue: action.actionValue,
  actionType: action.actionType,
  originObjectCategoryId: record.originObjectCategoryId,
})

Where the three fixed properties come from

actionValue may arrive as the stored JSON string — a backend that hands the column through untouched never has to read, parse or reshape it, and a malformed seed can never break its query. In that arrangement the other two properties are supplied alongside it: actionType from the action's own master category, originObjectCategoryId from the record the action fires on. The parser folds all three into one envelope.

Where the stored value already is an envelope and declares those properties too, originObjectCategoryId and payload keep what the column says — that is the action's configuration, and the column is where it is configured.

actionType is not taken from the column at all. It comes from the supplied master-category name, and from nowhere else. The category is what classifies an action, and that classification picks the handler and therefore the mutation — so the column an author edits must not be able to redirect a row to a different action than the one it is filed under. A seed that still carries an actionType of its own is carrying dead data: nothing reads it, and it does not reach the payload either.

A payload is trusted: it is seeded data, checked where it is authored. Nothing here validates it — an unreadable action value yields no handler, and an unreadable property resolves to null.

API

| export | purpose | | :-- | :-- | | BaseRecordActionHandler | the extension point: one subclass per action type | | ActionHandlerRegistry | actionType → handler class; fromModules() | | ActionValueParser | stored JSON (string or object) → canonical envelope | | ValueSourceResolver | the four source value types | | ACTION_FAMILY / SOURCE_VALUE_TYPE / LEGACY_SOURCE_VALUE_TYPE | the vocabulary | | MAX_PAYLOAD_DEPTH | how deep a payload's own object nesting may go. Past it a payload resolves to {} rather than raising — read it when authoring a seed, since nothing validates one |

Handler members

| member | purpose | | :-- | :-- | | hasFormSequence() | whether the payload contains a FORM_SEQUENCE value | | extractFormSequenceId() | that form sequence's id, or null | | extractFormSequenceDescriptor({ columns, originObjectUniqueKey }) | the resolved { formSequenceId, hasAssociationSource, associationSourceRecord } | | resolveFields({ columns, originObjectUniqueKey, submittedValues }) | the payload with every value source resolved | | buildActionMutationPayload({ … }) | the mutation's input — see the behaviour table | | buildActionModalProps({ columns, originObjectUniqueKey }) | the props the action's modal opens with |

The build parameters are the record the action fires from plus submittedValues — what the action's modal submitted — not one per action, so the signature only widens when this package adds a source value type. The action value itself is never a parameter: the instance holds it.

Legacy rows

A row stored before the envelope existed — a bare payload with no payload property — parses the same way, from the actionType supplied alongside it, so an un-migrated database keeps working:

parser.parse({
  actionValue: row.actionValue,   // '{"memoId":{"sourceValueType":"RECORD_UNIQUE_KEY"},…}'
  actionType: 'INVOKE_UPDATE_MEMO',
  originObjectCategoryId: 7,
})

On the way in, the parser

  • hoists a payload-level originObjectCategoryId onto the envelope,
  • rewrites FORM_FIELD_REFERENCE to COLUMN_REFERENCE,
  • folds a legacy { sourceValueType, fixedValue, formFieldReference } triple and a bare { columnKey } reference into the two-key envelope.

A legacy bare formSequenceId in the payload is still read by extractFormSequenceId(), so an un-migrated form-sequence action still opens its form.

A COLUMN_REFERENCE resolves to the column's value verbatim; a handler converts it to what its field means — Number(resolved.memoId) for an id, String(resolved.title ?? '') for text.

Scope

  • In: the record-action envelope, its payload vocabulary, the handler extension point.
  • Out: column edit-trigger tools (condition-shaped, served by @openreachtech/mentsu-validation-rules), search conditions (owned by @openreachtech/mentsu-search-condition), and resolving anything the host alone knows — which record is on screen, which mutation client to call.

Development

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.