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

@8lines/gauntlet-typescript-core

v0.1.8

Published

Framework-neutral registries, validation, execution coordination, run management, and capability catalog for TypeScript Gauntlet adapters.

Readme

Gauntlet TypeScript Core

Framework-neutral registries and runtime for an application's explicitly registered Gauntlet features, operations, data sources, runs, and optional capabilities. This package does not expose HTTP routes or a dashboard and does not discover arbitrary application handlers, commands, URLs, or database queries.

Use it only in selected non-production environments. A Node or Next transport can expose the resulting AdapterCatalog at /_gauntlet/v1.

Install

Release consumers install matching exact versions from the public npm registry (no registry configuration or token is needed; see installing packages):

pnpm add @8lines/[email protected] \
  @8lines/[email protected]

Inside this repository, workspace packages use workspace:* dependencies and run pnpm install from the repository root:

{
  "dependencies": {
    "@8lines/gauntlet-protocol": "workspace:*",
    "@8lines/gauntlet-typescript-core": "workspace:*"
  }
}

For offline release verification, build and pack both packages:

mkdir -p /tmp/gauntlet-packages
pnpm --filter @8lines/gauntlet-protocol build
pnpm --filter @8lines/gauntlet-typescript-core build
pnpm --filter @8lines/gauntlet-protocol pack --pack-destination /tmp/gauntlet-packages
pnpm --filter @8lines/gauntlet-typescript-core pack --pack-destination /tmp/gauntlet-packages

Reference both tarballs from the consumer's package.json:

{
  "dependencies": {
    "@8lines/gauntlet-protocol": "file:/tmp/gauntlet-packages/8lines-gauntlet-protocol-0.1.8.tgz",
    "@8lines/gauntlet-typescript-core": "file:/tmp/gauntlet-packages/8lines-gauntlet-typescript-core-0.1.8.tgz"
  }
}

Pin Core's transitive protocol dependency to the same tarball in an offline consumer's pnpm-workspace.yaml:

overrides:
  '@8lines/gauntlet-protocol': 'file:/tmp/gauntlet-packages/8lines-gauntlet-protocol-0.1.8.tgz'

Then run:

pnpm install

Add @8lines/gauntlet-typescript-node for a native Node host or @8lines/gauntlet-next-adapter for an App Router bridge.

Build a catalog

The complete, compile-checked reference is catalog-example.ts. It demonstrates the whole application-side composition in one file:

  1. OperationRegistry owns feature groups and operations created only with defineOperation().
  2. DataSourceRegistry owns bounded query/resolve providers used by rich UI controls.
  3. RunManager validates requests and persists immutable Run snapshots.
  4. CapabilityRegistry advertises only endpoint SPIs that the application actually installs.
  5. createAdapterCatalog() exposes the validated, framework-neutral API that an HTTP adapter consumes.

The important shape is:

const operations = new OperationRegistry();
operations.registerFeature({ id: "applications", label: "Applications" });
operations.register(defineOperation({
  id: "applications.change-state",
  label: "Change application state",
  featureId: "applications",
  order: 10,
  tags: ["applications", "workflow"],
  requirements: {
    profiles: ["tc-schema-core@1", "tc-rich-forms@1", "tc-rich-results@1"],
    capabilities: ["tc-run-cancellation@1"],
  },
  inputSchema: {
    $schema: "https://json-schema.org/draft/2020-12/schema",
    type: "object",
    required: ["applicationId", "workflowState", "effectiveAt", "notify"],
    properties: {
      applicationId: { type: "string", minLength: 1 },
      workflowState: { type: "string", enum: ["pending", "approved", "blocked"] },
      effectiveAt: { type: "string", format: "date-time" },
      notify: { type: "boolean", default: false },
      reason: { type: "string", minLength: 3, maxLength: 500 },
    },
    additionalProperties: false,
  },
  uiSchema: {
    profile: "tc-rich-forms@1",
    root: {
      type: "group",
      label: "Workflow change",
      children: [
        {
          type: "columns",
          children: [
            {
              type: "field",
              pointer: "/applicationId",
              widget: "autocomplete",
              dataSourceId: "pending-applications",
            },
            { type: "field", pointer: "/workflowState", widget: "select" },
          ],
        },
        { type: "field", pointer: "/effectiveAt", widget: "date-time" },
        { type: "field", pointer: "/notify", widget: "toggle" },
        {
          type: "field",
          pointer: "/reason",
          widget: "textarea",
          visibleWhen: {
            op: "in",
            pointer: "/workflowState",
            values: ["approved", "blocked"],
          },
        },
      ],
    },
  },
  dataSources: [{
    id: "pending-applications",
    inputPointer: "/applicationId",
    dependencyPointers: ["/workflowState"],
    required: true,
  }],
  presets: [{
    id: "return-to-pending",
    label: "Return to pending",
    input: { workflowState: "pending", notify: false },
    lockedPointers: ["/workflowState"],
  }],
  execution: {
    impact: "write",
    confirmationRequired: true,
    dryRunSupported: false,
    idempotency: "required",
    cancellationSupported: true,
    timeoutSeconds: 120,
    concurrency: "queue",
  },
  output: {
    schema: {
      $schema: "https://json-schema.org/draft/2020-12/schema",
      type: "object",
    },
    presentation: { profile: "tc-rich-results@1", defaultView: "summary" },
  },
}, async (input, context) => {
  context.report({ phase: "changing-state", current: 0, total: 1 });
  // Call one explicit application service here.
  return { output: input };
}));

The linked source supplies the complete typed handler and output schema. It also registers pending-applications with search, cursor limits, ordered resolve behavior, and a dependency schema keyed by the JSON Pointer /workflowState. The dashboard can therefore render and refresh the autocomplete without knowing application-specific APIs.

Do not make a generic operation that accepts a class, command, route, SQL, or URL name. Each operation handler and data source is an explicit allow-listed application binding.

Run storage and idempotency

Construct the manager with the same validator used by the catalog:

const schemaValidator = new AjvSchemaValidator();
const runs = new RunManager(operations, sharedRunStore, {
  validateSchema: ({ schema, value }) => schemaValidator.validate(schema, value),
  validateFileReference: () => [], // replace when file input rules are registered
  idempotencySecret,
});

idempotencySecret must contain at least 32 high-entropy bytes. Configure the same stable value for every process, worker, pod, and restart that shares a store. There is no generated or random fallback: construction fails when the secret is missing, has the wrong runtime type, or is shorter than 32 bytes. Load it from deployment-owned secret storage, decode it into a Uint8Array, and discard the source string after construction. Never log the source string, decoded bytes, or raw idempotency keys. RunManager defensively copies the bytes and HMAC-fingerprints raw idempotency keys before calling the store; raw keys must never be persisted or logged.

InMemoryRunStore is suitable only for tests and a genuinely single-process development host. Multiple workers or pods require an application-owned database/Redis implementation of RunStore shared by every replica. Its create(run, idempotencyFingerprint) operation must atomically reserve both the queued Run and the operation-scoped fingerprint, throwing DuplicateIdempotencyKeyError on a uniqueness race. get, findByIdempotencyKey, and update must return/persist complete canonical Run snapshots. update(next, expectedPreviousSequence) is a required atomic compare-and-set: throw RunStoreConflictError when the stored sequence differs and never mutate the winner. Do not deploy replicas with independent in-memory stores.

Execution policy and coordination

RunManager enforces each operation's execution policy. Missing concurrency and "allow" admit handlers independently. "forbid" returns the fixed 409 operation-busy Problem while another run of that operation is queued or running; a concurrent request with the same idempotency key still replays its original run. "queue" admits runs in FIFO order and starts one handler at a time. timeoutSeconds starts when the run enters running, aborts RunContext.signal, and persists one timed_out terminal snapshot.

Cancellation and timeout close the context immediately, so late progress, artifacts, actions, logs, warnings, and results are ignored. JavaScript cannot forcibly stop arbitrary application code: a handler that ignores its AbortSignal keeps the queue/forbid execution lease until its Promise actually settles. Handlers should observe context.signal or call context.throwIfCancelled() around application-service boundaries.

The optional schedule hook receives a process-local, revocable, one-shot function. It may retain that function only for local deferred execution; it must never serialize it, place it on a durable/general-purpose queue, or report acceptance and then drop it. Invoke it at least once after acceptance; repeated or concurrent calls are safe no-ops. The runtime releases captured input, secret guards, and invocation context when the task starts or is revoked by a failed/cancelled reservation, even if the scheduler keeps its function object. Throw synchronously from schedule when ownership cannot be accepted.

The default InMemoryExecutionCoordinator coordinates only one JavaScript process. It is suitable for tests and a single-process development host. A multi-worker or multi-pod adapter must inject one shared ExecutionCoordinator that provides distributed admission/FIFO leases and cancellation publication (for example through database locks plus pub/sub):

const runs = new RunManager(operations, sharedRunStore, {
  validateSchema: ({ schema, value }) => schemaValidator.validate(schema, value),
  validateFileReference: validateUploadReference,
  idempotencySecret,
  executionCoordinator: sharedExecutionCoordinator,
});

The coordinator's commitAdmission() boundary follows the durable RunStore.create; admission conflicts wait for that decision before returning busy or replaying an idempotent run. Coordinator cancellation must reach the lease's signal in every replica. This SPI is deliberately separate from RunStore, so a deployment can use the same database for both without making the in-memory default look globally safe. A local timeout closes and terminalizes its run without waiting for cancellation publication; coordinator publication is best-effort for notifying other replicas and must not be used as the local timeout acknowledgement.

Optional capability SPIs

Uploads, SSE, and session launch are advertised only when their typed endpoint is registered. A real RunManager automatically advertises managed tc-run-cancellation@1; an optional cancellation endpoint remains a fallback for run IDs owned outside that manager:

const capabilities = new CapabilityRegistry();
capabilities.registerCancellation(externalCancellationFallback); // optional
capabilities.registerEvents(runEventsEndpoint);
capabilities.registerUploads(uploadEndpoint);
capabilities.registerSessionLaunch(sessionLaunchEndpoint);

The matching capabilities are tc-run-cancellation@1, tc-run-sse@1, tc-uploads@1, and tc-session-launch@1. An absent optional SPI stays unadvertised and the transport returns the protocol's fixed 501 response. Endpoint results are treated as untrusted and validated before crossing the adapter boundary. registerProvider() may advertise a non-core capability requirement, but it does not create a custom HTTP executor.

Expose the catalog

Pass the completed catalog to exactly one transport. The native Node example preserves the raw request target; the Next bridge requires a trusted ingress because Next normalizes the request before the route handler sees it.

Verify

From the repository root:

pnpm --filter @8lines/gauntlet-typescript-core typecheck
pnpm --filter @8lines/gauntlet-typescript-core test
pnpm --filter @8lines/gauntlet-typescript-core build
pnpm --filter @8lines/gauntlet-typescript-core test:package
pnpm --filter @8lines/gauntlet-typescript-fixture typecheck

Documentation index