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

@openparachute/surface-server

v0.1.2

Published

Server kit for backed Parachute surfaces — actor resolution (hub JWT / capability links / anon), audience store, vault-native grants with live SSE cache, deny-by-default routing, and a public conformance suite. A library surface backends import inside cre

Readme

@openparachute/surface-server

The server kit for backed surfaces — a library a surface backend imports inside createBackend(ctx). Never a host object: the host (@openparachute/surface) injects the SurfaceHostContext; this kit builds the trust machinery on top of it.

Part of the Surface Runtime design (R4: P7–P9; R6 foundation: P10). Companion pattern: parachute-patterns/patterns/backed-surface.md.

Bun required. This package publishes raw TypeScript source (no compiled dist/) — it runs where backed surfaces run: inside the Bun-native surface host. Node consumers would need their own TS loader; that path is untested and unsupported.

What's in the box

P7 — createSurfaceAuth

resolveActor(req) with exactly three branches:

  • operatorAuthorization: Bearer <hub JWT>, validated against the hub's JWKS via @openparachute/scope-guard. v1 pins aud=vault.<name> + scope vault:<name>:write (the owner branch from the design's open questions; a per-surface audience is an issuance evolution).
  • audienceAuthorization: Capability <token> (programmatic) or the path-scoped session cookie set by the entry route GET ${mount}/api/a/<token> (verify → link-session → httpOnly SameSite=Lax cookie scoped to ${mount}/ → 302 to a clean URL — the raw token never lingers in history or logs).
  • anon — neither presented. Presented-but-INVALID credentials are a 401 refusal, never a silent downgrade to anon.

Plus: the AudienceStore (subjects / capabilities / sessions in the per-surface state store; passwordHash is nullable v2 schema room only), capability + single-use personal-link minting (email delivery is OPTIONAL operator config per module-credential-ownership — links always render inline for copy-paste without it), an Origin-check middleware (default-on for cookie-authenticated mutations), and a fail-closed rate limiter keyed off the hub-stamped ctx.clientIp (null IP on the public layer shares one collective bucket — limited, never unlimited).

P8 — SurfaceAuthz

can(actor, note, action) with the level→action table (view < comment < suggest < edit; manage_grants / manage_tags / manage_path are operator-only — tags are the sharing scope, so writing them is privilege escalation). Grants are vault-native: notes tagged surface-acl/<surface> with indexed metadata (subject_type, subject, resource_type, resource, level, expires_at), enforced from an in-memory cache fed by the vault's live-query SSE. Fail-closed on stream loss: while degraded the store revalidates with a single-flight one-shot query or denies — stale-allow never happens. Revocation = delete the grant note.

createSurfaceRouter composes both into a deny-by-default gateway: every route declares access (public / audience / operator / note+action); undeclared paths 404; denied note reads are indistinguishable from missing notes (no existence oracle).

P9 — projections (one definition → REST + MCP)

Declare a domain query once; the kit derives both consumer faces:

const upcomingMeetings = defineProjection({
  name: "upcomingMeetings",
  params: { from: "date?" },
  query: (p) => ({
    tag: "meeting",
    metadata: { date: { gte: p.from ?? new Date().toISOString().slice(0, 10) } },
    includeContent: true, // vault list results omit content by default
  }),
  shape: (note) => ({
    title: note.metadata?.title,
    date: note.metadata?.date,
  }),
  describe: "Upcoming public meetings, soonest first.",
  access: "public", // default is "audience" — public is an explicit opt-in
});
  • REST: GET ${mount}/api/upcoming-meetings?from=2026-06-10 — emitted as a SurfaceRoute, so it rides the same gateway as everything else. Returns { projection, count, items: notes.map(shape) }. Bad params → 400 with per-param issues, never a 500.

  • MCP: a tool named upcoming-meetings on the per-surface Streamable-HTTP endpoint POST ${mount}/api/mcp (stateless — no initialize handshake required, restarts never strand a client). describe is the tool description and the params declaration compiles to the tool's inputSchema, so the two faces cannot drift. Connect a Claude session with:

    claude mcp add --transport http my-surface <origin>/surface/<name>/api/mcp

The MCP endpoint rides the same actor resolution: tools/list shows only the projections the caller's access clears (anon sees exactly the public slice), and calling a denied tool returns the identical error as a nonexistent one — no existence oracle. Browsers and AI clients both get domain vocabulary; raw tags/notes/links never ride out — the only data that leaves a projection is what shape returns.

Param specs are 'string' | 'number' | 'boolean' | 'date' with a ? suffix for optional (date values stay ISO strings). Validation is strict both ways: unknown params refuse, dates must actually parse.

P10 — createVaultReconciler (+ the SurfaceStateStore substrate)

The corrected reconciliation machine (design §9) between a surface's live Y.Docs and their backing vault notes — the collaborative-editing foundation. The host's per-surface SurfaceStateStore (ctx.store, SQLite, deleted on surface removal) is the persistence substrate; the machine's internals (state layout, queues, debounce, version tracking) stay private. Surface authors see exactly two hooks and the conflict events:

import { createVaultReconciler } from "@openparachute/surface-server";
import { docToMarkdown, markdownToDocJSON, schema } from "@openparachute/doc-schema";
import { prosemirrorJSONToYDoc, yDocToProsemirrorJSON } from "y-prosemirror";

const reconciler = createVaultReconciler(ctx, {
  tag: "doc", // the surface's working tag — also the SSE watch scope
  hooks: {
    seed(doc, note) {
      /* REPLACE the doc's content from note.content (markdown).
         ALWAYS doc-schema's exported schema — node/mark names persist
         inside Y.Docs, so an ad-hoc schema corrupts every doc it touches. */
    },
    serialize(doc) {
      /* derive canonical markdown — doc-schema's docToMarkdown, never
         an ad-hoc serializer (schema + codec version together). */
    },
  },
});
await reconciler.start(); // resolves on the first SSE snapshot
reconciler.on((ev) => {
  /* "external-edit" | "writeback-conflict" | "note-removed" | … */
});
// documentName = note id (e.g. Hocuspocus onLoadDocument):
const doc = await reconciler.load(noteId, engineDoc);
// shutdown(): await reconciler.stop()  — flushes + persists everything

The rules it enforces (Prism's load-bearing rules kept, both bug paths replaced — see the module doc for the full contract and the documented failure windows):

  • Vault-as-source-of-truth, external-edit-WINS — the external-edit signal is the vault's live-query SSE on the working tag, not load-time comparison.
  • Writebacks send if_updated_at with the tracked updatedAt string VERBATIM — versions are opaque strings, equality is the only operation, and no force flag ever rides a reconciler writeback (test-pinned).
  • 409 → fetch the winner → re-seed into the live Y.Doc in ONE transaction — connected clients observe a single atomic swap, never a torn intermediate state.
  • Populated re-seed guard — a doc that already carries CRDT state is never seeded over on load (the classic double-seed bug).
  • Fail-closed on stream loss — while degraded the machine revalidates before the next writeback instead of assuming no external edits; it never writes blind.

One operational warning for collab engines: Hocuspocus's onDisconnect fires twice when the departing client had awareness state (upstream bug, recorded in the design appendix) — any disconnect-driven cleanup around this machine (presence counters, unload() calls) must be idempotent, deduped by socketId. Version anchor: the Hocuspocus-under-Bun spike was verified on Bun 1.3.13 + @hocuspocus/server 4.1.1 — on a Bun (or Hocuspocus) upgrade, re-verify the manual-pumping contract and that double-onDisconnect behavior before trusting disconnect-driven cleanup.

Conformance suite (public export)

import { test } from "bun:test";
import { gatewayConformanceCases } from "@openparachute/surface-server/conformance";

for (const c of gatewayConformanceCases({ fetch: backend.fetch, mount, ... })) {
  test(c.name, () => c.run());
}

Pins anon-sees-nothing, deny-by-default, leak conditions, path/tag locks, entry-redirect hygiene, and the cookie-mutation origin check — against YOUR routes. The kit runs the same suite against its own example wiring.

SECURITY.md template (spec §13)

Every backed surface should ship a SECURITY.md. The kit packs a scaffold — SECURITY.template.md (in the published tarball at the package root) — covering the one-rule statement, threat-model summary, credential posture, audience plane, working-scope statement, an actor table that cites your conformance-suite case names as evidence, a secrets table, residual risks, and the report channel. Copy it to your surface package root, fill the placeholders with your real answers. The docs-editor's SECURITY.md is the filled reference.

From createBackend(ctx) to a gated, projected backend

The whole journey in one file. A surface package declares a server block in its .parachute/meta.json; the host calls the default export once per mount and forwards ${mount}/api/* (+ ${mount}/ws) to the returned fetch.

import type { SurfaceBackend, SurfaceHostContext } from "@openparachute/surface";
import {
  createSurfaceAuth,
  createSurfaceAuthz,
  createSurfaceProjections,
  createSurfaceRouter,
  defineProjection,
  GrantStore,
} from "@openparachute/surface-server";

export default async function createBackend(ctx: SurfaceHostContext): Promise<SurfaceBackend> {
  // 1. AUTH — who is calling? (hub JWT / capability link / anon)
  const auth = createSurfaceAuth(ctx);

  // 2. AUTHZ — what may they touch? (vault-native grants, live SSE cache)
  const grants = new GrantStore(ctx);
  await grants.start(); // resolves on the first snapshot — authz is ready
  const authz = createSurfaceAuthz(grants);

  // 3. PROJECTIONS — the domain vocabulary, declared once.
  const projections = createSurfaceProjections(ctx, {
    projections: [
      defineProjection({
        name: "upcomingMeetings",
        params: { from: "date?" },
        query: (p) => ({ tag: "meeting", metadata: { date: { gte: p.from ?? "2026-01-01" } } }),
        shape: (note) => ({ title: note.metadata?.title, date: note.metadata?.date }),
        describe: "Upcoming public meetings, soonest first.",
        access: "public",
      }),
    ],
  });

  // 4. THE GATEWAY — deny-by-default; every route declares its access.
  const router = createSurfaceRouter(ctx, auth, authz, {
    routes: [
      ...projections.routes, // REST faces + the MCP endpoint

      // A note-gated read: 404s identically for denied and missing.
      {
        method: "GET",
        path: "/api/doc/:id",
        access: { kind: "note", action: "read" },
        handler: (_req, { note }) => Response.json({ id: note?.id, content: note?.content }),
      },

      // An operator-only share flow: mint a capability link + its grant.
      {
        method: "POST",
        path: "/api/share",
        access: { kind: "operator" },
        handler: async (req) => {
          const { noteId, level } = (await req.json()) as { noteId: string; level: "view" };
          const cap = auth.mintCapability();
          await grants.createGrant({
            subject: `cap:${cap.id}`,
            resourceType: "note",
            resource: noteId,
            level,
          });
          return Response.json({ url: cap.entryPath }); // hand out ONCE
        },
      },
    ],
  });

  return { fetch: router.fetch, shutdown: async () => grants.stop() };
}

Then pin the trust architecture in your own test suite:

import { test } from "bun:test";
import { gatewayConformanceCases } from "@openparachute/surface-server/conformance";

for (const c of gatewayConformanceCases({
  fetch: (req) => backend.fetch(req),
  mount: "/surface/my-surface",
  protectedProbes: [{ path: "/api/doc/n-1", mustNotContain: ["a distinctive phrase"] }],
})) {
  test(c.name, () => c.run());
}

Notes for surface authors

  • Entry + MCP paths live under /api/. The host forwards exactly ${mount}/api/* and ${mount}/ws to a backend — so the kit emits ${mount}/api/a/<token> and serves ${mount}/api/mcp. /api/mcp is the CANONICAL (and only) MCP route (#104 — the spec was amended to name it; the bare ${mount}/mcp route was dropped as dead code). The short entry form ${mount}/a/<token> is still accepted when parsing entry URLs.
  • Credential scope: the surface's working-tag credential must include surface-acl/<surface> so the GrantStore can read/write grant notes — declare it in the surface's required_schema / tag scope at install time.
  • manage_tags / manage_path never reach the audience. Tags are the sharing scope; granting tag writes would be privilege escalation. The kit denies them for every non-operator actor.
  • Trust signals come from the substrate. Use ctx.layer(req) / ctx.clientIp(req), never raw headers; the kit ships no isLocal().