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

@embeddables/protocols

v0.2.0

Published

Protocol navigation and eligibility client for Embeddables medical intake flows.

Readme

@embeddables/protocols

Protocol navigation and eligibility for Embeddables medical intake flows. Composes on @embeddables/core for initialization parity and reads answers from initialized @embeddables/forms FormInstances — no form-data arguments on protocol instance methods.

Install

npm install @embeddables/protocols @embeddables/core @embeddables/forms

For React apps, also install react (peer dependency) and wrap your tree in EmbeddablesProvider from @embeddables/core/react. Add protocols() to modules, and pass your protocols through the provider's protocols prop.

React

Import hooks from @embeddables/protocols/react:

Example:

import { EmbeddablesProvider } from '@embeddables/core/react'
import { forms } from '@embeddables/forms/react'
import { protocols, useProtocol } from '@embeddables/protocols/react'

const schemas = {
  'pre-intake': {
    id: 'pre-intake',
    fields: [
      { key: 'bmi', label: 'BMI', type: 'text', protocolFieldId: 'bmi' },
    ],
  },
  intake: {
    id: 'intake',
    fields: [
      {
        key: 'conditions',
        label: 'Conditions',
        type: 'multiselect',
        protocolFieldId: 'exclusionary_conditions',
      },
    ],
  },
} as const

const myProtocol = {
  id: 'my-protocol',
  name: 'My Protocol',
  version: '1.0.0',
  ehr: 'openloop_health',
  medicalIntakeQuestions: {
    getFirstMedicalIntakeQuestionId: () => 'exclusionary_conditions',
    questions: [
      {
        id: 'exclusionary_conditions',
        type: 'multiselect',
        text: 'Do any apply?',
        shouldShowThisQuestion: () => true,
        getContinueStatus: (formsData) =>
          formsData.getValueByProtocolFieldId('exclusionary_conditions')
            ? { status: 'can_continue' }
            : { status: 'no_reply' },
        getNextQuestionId: () => null,
        getPrevQuestionId: () => null,
      },
    ],
  },
  canUserStartMedicalIntake: (formsData) =>
    formsData.getValueByProtocolFieldId('bmi') !== undefined,
  isUserEligible: () => ({ status: 'qualified', modality: 'async', statusReasons: [] }),
  getQuestionById(id) {
    return this.medicalIntakeQuestions.questions.find((question) => question.id === id)
  },
} as const

function IntakeQuestion() {
  const { question, continueStatus, goToNextQuestion } = useProtocol({
    protocolId: 'my-protocol',
  })

  return (
    <div>
      <p>{question?.text}</p>
      <button
        type="button"
        disabled={continueStatus?.status !== 'can_continue'}
        onClick={() => goToNextQuestion()}
      >
        Continue
      </button>
    </div>
  )
}

export function App() {
  return (
    <EmbeddablesProvider
      modules={[forms(), protocols()]}
      protocols={{ formIds: ['pre-intake', 'intake'], protocols: [myProtocol] }}
    >
      <IntakeQuestion />
    </EmbeddablesProvider>
  )
}
  • formIds lists the forms your protocols may read. They are resolved once and shared by every protocol in protocols, so all of them see the same answers.
  • Protocols read those answers only through protocolFieldId, never through a form's field keys — see getValueByProtocolFieldId in the example above.
  • Configuring protocols requires forms() in modules. The order of modules does not matter; the provider initializes Forms first on its own.
  • useProtocol({ protocolId }) returns { instance, currentQuestionId, question, shouldShowQuestion, continueStatus, canStartMedicalIntake, eligibility, goToNextQuestion, goToPreviousQuestion, goToQuestion }. It throws if protocols() was not registered or protocolId is unknown.
  • useProtocol also returns instance: ProtocolInstance for direct access to any method (arbitrary question ids, id/name/version, etc.) beyond the current-question convenience fields.

Quick start

import { initEmbeddables } from '@embeddables/core'
import { initForms, type FormSchema } from '@embeddables/forms'
import { initProtocols, type Protocol } from '@embeddables/protocols'

const preIntakeSchema = {
  id: 'pre-intake',
  fields: [
    { key: 'bmi', label: 'BMI', type: 'number', protocolFieldId: 'bmi' },
    {
      key: 'sex',
      label: 'Sex assigned at birth',
      type: 'select',
      protocolFieldId: 'sex_assigned_at_birth',
    },
  ],
} as const satisfies FormSchema

const intakeSchema = {
  id: 'intake',
  fields: [
    {
      key: 'conditions',
      label: 'Conditions',
      type: 'multiselect',
      protocolFieldId: 'exclusionary_conditions',
    },
  ],
} as const satisfies FormSchema

const core = initEmbeddables({
  projectId: 'proj_…',
  forms: [],
  experiments: [],
})

const { getForm } = initForms({
  core,
  schemas: { 'pre-intake': preIntakeSchema, intake: intakeSchema },
})
const preIntakeForm = getForm({ formId: 'pre-intake' })
const intakeForm = getForm({ formId: 'intake' })

const protocol = {
  id: 'my-protocol',
  name: 'My Protocol',
  version: '1.0.0',
  ehr: 'openloop_health',
  medicalIntakeQuestions: {
    getFirstMedicalIntakeQuestionId: () => 'exclusionary_conditions',
    questions: [
      {
        id: 'exclusionary_conditions',
        type: 'multiselect',
        text: 'Do any apply?',
        shouldShowThisQuestion: () => true,
        getContinueStatus: (formsData) =>
          formsData.getValueByProtocolFieldId('exclusionary_conditions')
            ? { status: 'can_continue' }
            : { status: 'no_reply' },
        getNextQuestionId: () => null,
        getPrevQuestionId: () => null,
      },
    ],
  },
  canUserStartMedicalIntake: (formsData) =>
    formsData.getValueByProtocolFieldId('bmi') !== undefined,
  isUserEligible: () => ({ status: 'qualified', modality: 'async', statusReasons: [] }),
  getQuestionById(id) {
    return this.medicalIntakeQuestions.questions.find((question) => question.id === id)
  },
} satisfies Protocol

const instance = initProtocols({ core }).initProtocol({
  protocol,
  forms: [preIntakeForm, intakeForm],
})

const firstId = instance.getFirstQuestionId()
const question = instance.getQuestionById(firstId)
const visible = instance.shouldShowQuestion(firstId)
const continueStatus = instance.getContinueStatus(firstId)
const nextId = instance.getNextQuestionId(firstId)
const prevId = instance.getPrevQuestionId(firstId)
const canStart = instance.canStartMedicalIntake()
const eligibility = instance.isEligible()

void question
void visible
void continueStatus
void nextId
void prevId
void canStart
void eligibility

Declare protocolFieldId on form fields that protocol logic reads. Pass every initialized form to initProtocol({ protocol, forms }) once — instance methods never take a form-data argument.

Protocol contract

The Protocol interface and related section/question/result types ship from this package. A vendored per-project implementation can satisfies Protocol against those types.

QuestionType

"text" | "email" | "number" | "boolean" | "select" | "multiselect" | "file"

ProtocolFieldValue

string | readonly string[] | number | boolean | FormFileRef | null | undefined

The bridge returns compatible values unchanged. Numbers must be finite, and file references must have status: "done". null is an explicit skip; undefined is unanswered. json form fields cannot declare protocolFieldId.

Variations

A variation is a Variation object exported from a vendored embeddables/_protocols/<id>/<version>/variations/<variationId>/schema.ts. Its id must match the folder name. name and description are structured fields on the object for author ergonomics; a sibling README.md carries additional prose documentation of the delta vs the base protocol.

Multiple variations compose in the order listed under variations: in embeddables/config.yaml. The CLI inlines the base protocol and each configured variation into one self-contained embeddables/_dist/protocols/<id>/<version>/schema.ts via nested .transform calls. There is no runtime chain-runner function to call — import the generated schema export only.

Helpers for vendored protocols

getQuestionById, canUserStartMedicalIntake, getContinueStatusForRequiredAlwaysContinueQuestions, getContinueStatusForRequiredDisqualifyingQuestions, getContinueStatusForNonRequiredAlwaysContinueQuestions, getAlwaysShowQuestion, getConditionalShowQuestionOrContent, getNextVisibleQuestionId, and getPrevVisibleQuestionId are protocol-agnostic — they only depend on the Protocol contract shape, never on a specific protocol's questions or rules. A vendored embeddables/_protocols/{protocolId}/{version}/schema.ts imports and composes them directly instead of reimplementing this logic per protocol:

import {
  getContinueStatusForRequiredAlwaysContinueQuestions,
  getNextVisibleQuestionId,
  type Protocol,
} from '@embeddables/protocols'

export const myProtocol: Protocol = {
  // …
  medicalIntakeQuestions: {
    questions: [
      {
        id: 'some_question',
        getContinueStatus: (formsData) =>
          getContinueStatusForRequiredAlwaysContinueQuestions({
            fieldId: 'some_question',
            formsData,
          }),
        getNextQuestionId: (formsData) =>
          getNextVisibleQuestionId({
            protocol: myProtocol,
            currentId: 'some_question',
            formsData,
          }),
        // …
      },
    ],
  },
}

Options

initProtocols

| Option | Purpose | | ------ | ------- | | core | Initialized @embeddables/core instance (required) |

initProtocol

| Option | Purpose | | ------ | ------- | | protocol | Protocol definition (required) | | forms | Initialized @embeddables/forms instances whose fields declare protocolFieldId (required) |

When two forms declare the same protocolFieldId, the later form in the forms array wins.

Errors

| When | Error | | ---- | ----- | | Bad core at init | ProtocolsError (thrown) | | Bad protocol at init | ProtocolSchemaError (thrown) | | Unknown question id on navigation/eligibility helpers | UnknownQuestionError (thrown; branch on .questionId) |

All SDK errors extend ProtocolsError.