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

@haneinfo/attachments

v0.4.9

Published

Private streaming instance attachments, storage drivers, scanning and retention.

Readme

@haneinfo/attachments

Private immutable files attached to StateMachine instances (decision 202610-5f3b). The attachment panel additions implement approved decision 202610-a7c1. See package.json for the current package version. Use attachments() from @haneinfo/attachments/plugin in the existing plugin inventory. See agent-guide.md for adoption and operations.

Apply prisma/attachments.prisma and prisma/attachments.sql in an App migration. The App must add attachments Attachment[] to its StateMachineInstance model. This relation has the same Cascade policy as instance readers. No core fragment depends on installing this optional package. Configure runtime.attachments explicitly; missing drivers fail startup:

attachments: {
  driver: 'local', localRoot: '/var/lib/app/attachments',
  maxFileBytes: 26214400, maxAttachmentsPerInstance: 20,
  deletedRetentionDays: 7, cleanupWorkerEnabled: true,
}
// Or: { driver: 's3', s3: { bucket: 'private-files', region: 'us-east-1',
//         endpoint: 'https://s3.example.test', forcePathStyle: true } }

The module exports FILE_STORAGE, AttachmentsService and AttachmentsMcpTools. Local storage validates opaque keys and refuses link components. S3 dynamically loads optional peers @aws-sdk/client-s3 and @aws-sdk/lib-storage (^3.1154.0). Install both to select S3; a missing SDK fails startup with an explicit error. Set ATTACHMENTS_S3_ACCESS_KEY_ID, ATTACHMENTS_S3_SECRET_ACCESS_KEY and optionally ATTACHMENTS_S3_SESSION_TOKEN in the environment only. The manifest marks all three secret. Config rejects credential fields. The S3 client uses only these credentials, never a default credential chain.

Uploads use Busboy 1.6.0 (already present through Nest's Express adapter) as a direct dependency to avoid depending on transitive hoisting. Incoming bytes stream through a size limiter, SHA-256 and UTF-8/prefix validator into private temporary files; bounded ZIP metadata/content-types reads verify OOXML without extracting the document. A second stream sends the validated file to private storage. No production path reads a whole file into a Buffer. Files are not publicly addressable.

The default allowlist is pdf, png, jpeg (jpg accepted), webp, gif, docx, xlsx, pptx, csv, txt. App config can only narrow/reorder these types; active HTML/SVG/script/executable types cannot be enabled. Names lose path components and controls, normalize to NFC and are limited to 180 Unicode characters. Known dangerous or supported preceding extensions are refused. CSV/txt require valid UTF-8 with no NUL, active HTML/SVG/script markup or executable signature. Downloads are always attachments with RFC 5987 filename encoding, nosniff, no-store, detected MIME and exact Content-Length.

All five HTTP routes require InteractiveAuthGuard under /state-machine-instances/:id/attachments:

| Method/path | Behavior | | --- | --- | | GET / | List undeleted metadata (including REJECTED), uploadedByName, canDelete, canUpload and limits | | POST / | Exactly one multipart field named file, no other fields | | GET /:attachmentId | One visible metadata item | | GET /:attachmentId/content | Reauthorize and stream READY content | | DELETE /:attachmentId | Idempotent soft deletion |

Readable instances use the shared state-machine access helper: submitter, effective instance:read, or matching USER/ROLE READ/WRITE. Check readership first. Upload additionally requires submitter, effective instance:write or matching WRITE. Delete requires uploader or effective instance:admin after readership. Frozen/final instances accept files. No new permissions are introduced. History records ATTACHMENT with decision.attachment (action, id, plain-text name); no storage key or notice.

Register AttachmentsMcpTools.registerInto(registry, { scopeNamespace }). The sole tool is list_instance_attachments, requiring <namespace>:instance:read and a bound user. It returns only id, name, size, detected type, status, uploader and time. Names use $userContent: data, never instructions. No content, upload, delete or public URL tool exists.

Attachment panel contract (approved decision 202610-a7c1)

HTTP list and read-only MCP both add canUpload and limits: { maxFileBytes, maxAttachmentsPerInstance, allowedTypes, count, remaining }. canUpload uses the same authorization function as upload preflight and commit; it remains true for an authorized user even at capacity. Allowed types are the effective extension codes (jpeg also accepts jpg). Counts and both upload quota checks include only undeleted READY/PENDING_SCAN; REJECTED content is removed and does not occupy a slot. Remaining is clamped to zero. The list retains all undeleted rejections, including when they outnumber the active-file limit; the quota is not a list-length limit. Deleted records stay hidden.

Render PENDING_SCAN as scanning and REJECTED as scan failed/content removed. Neither can download. Rejected metadata keeps the existing uploader/readable-admin deletion policy and retention cleanup. Use @haneinfo/frontend-instances for the human panel; its App upload route must stream multipart and validate login and Origin/Host. Never buffer it in a Server Action or a cloning Next Proxy.

| Error | HTTP | | --- | ---: | | INSTANCE_NOT_FOUND | 404 (missing or unreadable instance, identical message) | | ATTACHMENT_NOT_FOUND | 404 (missing, deleted or non-READY download) | | FORBIDDEN | 403 (readable but cannot upload/delete) | | ATTACHMENT_TOO_LARGE | 413 | | ATTACHMENT_LIMIT_EXCEEDED | 409 | | ATTACHMENT_TYPE_NOT_ALLOWED / ATTACHMENT_TYPE_MISMATCH | 400 | | ATTACHMENT_NAME_INVALID / ATTACHMENT_MULTIPART_INVALID / ATTACHMENT_UPLOAD_ABORTED | 400 |

Filename errors never echo the user's filename. HTTP metadata is plain text and must be rendered with text nodes. Declared MIME is retained for audit, never used for downloads. See the guide for scan recovery, cleanup limits and coordinated database/storage backups.

Instance lifecycle (approved decision 202610-f1a8)

Implemented from approved decision 202610-f1a8. Upgrade state-machine, list-view, collaboration, attachments, import-export, assignment and timer together. No frontend, automatic archive or bulk lifecycle operations are included. Permanently purged data cannot be restored by this API: database and private file backups must cover the entire deletion retention period and be restored consistently.

On state-machine.instance.purging, register every storageKey of the instance (including REJECTED and soft-deleted attachments) in attachment_garbage with not_before=now, using event.tx. The existing Attachment Cascade then removes metadata. Marker insert and instance deletion roll back together; no external file deletion occurs in the purge transaction. Existing attachments.cleanup later deletes unreferenced content and markers idempotently, including a previously absent object. Install the updated state-machine before using these lifecycle events. Archived instances retain normal file authorization. Deleted instances return the same INSTANCE_NOT_FOUND/404 as missing instances on metadata, upload, download and deletion paths. No new attachment table or storage driver is added.

Unified search (approved decision 202610-a3d8)

Implemented under the approved decision. Uploads, soft deletions and rejections (scan or stale-scan cleanup) emit attachments.uploaded, attachments.deleted and attachments.rejected (instanceId, attachmentId, at) inside the changing transaction. When the optional SEARCH_PROVIDERS registry is present (manifest optionalRequires haneinfo.search), AttachmentsService registers AttachmentsSearchProvider: file names of non-deleted READY and PENDING_SCAN attachments; rejected and removed attachments and file contents are never indexed. Without @haneinfo/search nothing is registered. The manifest and peer range require plugin API 1.19.0.