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

@linn-cloud/domain-erasure

v1.0.0

Published

The erasure contract for Linn's cloud services: message types, the JSON Schema validator, the channel constants, the shared address-key helper and the response sender. Published to support Linn's own consumers; intended for internal use, unsupported, and

Readme

@linn-cloud/domain-erasure

The erasure contract between Linn's account service and the stores that hold customer data. When a customer's data must be erased, the account service publishes one message on an erasure topic; every store receives it, does its work, and answers on one response queue. This package is what both sides share: the message types, a validator, the channel's timing constants, the address helper every store compares addresses with, and a sender for the answers.

The .NET twin is the NuGet package Linn.Cloud.Common.Domain.Messaging.Erasure. Both validate against the same JSON Schema (schema/erasure-contract.schema.json in the linn-cloud-common repository) and run the same fixture set, so a message one accepts the other accepts.

Messages

Every message carries its type in the body, uses camelCase fields and ISO-8601 UTC times ending in Z, and sets no SNS message attribute. No field beyond those listed is allowed.

| type | Sent by | Fields | |---|---|---| | erasure/ERASE | account service | requestId, sendNo, expiresAt, keyKind, key | | erasure/REFILE | account service | requestId, sendNo, expiresAt, keyKind (ADDRESS), from, to, accountId | | erasure/CLASSIFY | account service | requestId, expiresAt, keyKind, key | | erasure/CHECK | account service | requestId, expiresAt, keyKind, key | | erasure/ACK | store, answering ERASE | requestId, sendNo, subscriber, outcome: DONE or NOT_APPLICABLE | | erasure/REFILE_ACK | store, answering REFILE | requestId, sendNo, subscriber, outcome: MOVED, NONE_OWNED, NOT_MOVED or NOT_APPLICABLE | | erasure/HOLDINGS | store, answering CHECK or CLASSIFY | requestId, subscriber, keyKind, key, answer: HOLDS_DATA, HOLDS_NONE, CANNOT_ANSWER or NOT_APPLICABLE |

  • keyKind is ACCOUNT (the key is an account id) or ADDRESS (the key is an email address, already lowercased by addressKey: the validator rejects one containing A to Z).
  • sendNo identifies one broadcast of a request; an answer echoes it.
  • subscriber is the store's own name (ErasureSubscriber), taken from its deployed configuration, never from the message it is answering.

Handling a request in a store

import {
  ErasureResponseSender,
  parseErasureRequest,
} from "@linn-cloud/domain-erasure";

const request = parseErasureRequest(record.body); // throws on anything invalid, expired or not
  1. Validate first. parseErasureRequest throws ErasureContractError (code NOT_JSON, INVALID_MESSAGE or NOT_A_REQUEST) on a bad body. Let it throw: SQS retries the message and then moves it to the store's dead-letter queue, so nothing is dropped silently. The error's message is its code, never the body.
  2. Then check the expiry with the store's own clock. An expired valid message is consumed with no answer at all.
  3. Then dispatch on type and keyKind. A pair the store does not implement is answered at once with NOT_APPLICABLE; an implemented one does its work and answers.
  4. Answer with ErasureResponseSender. It validates the answer against the schema, sends it, and on failure logs only an error code and a count of failed sends, never the body, a key or a requestId, then throws ErasureResponseSendError so the handler fails and SQS retries.
const sender = new ErasureResponseSender(sqsClient, responsesQueueUrl, log);
await sender.send({ type: "erasure/ACK", requestId: request.requestId, sendNo: 1, subscriber: "space", outcome: "DONE" });

The handler reads its pass-two delay and batch timeout from the environment variables named in ErasureHandlerEnvironment (ERASURE_PASS_TWO_DELAY_SECONDS, ERASURE_BATCH_TIMEOUT_SECONDS); each must stay within its cap below.

What this package cannot check

A store that answers NOT_APPLICABLE is saying it holds nothing of that kind. Nothing here can verify that claim. Each store's own capability test, built from erasureCapabilityFixtures, and its ErasureNotApplicable metric are the safeguards.

Addresses

  • addressKey(raw) lowercases A to Z and leaves every other character as typed. Every store compares addresses through it (or its .NET twin), never through toLowerCase, because Unicode lowercasing differs between JavaScript, .NET and Postgres. A Kelvin sign (U+212A) therefore stays a Kelvin sign, and Kelvin@x and kelvin@x are two different keys. Rows some stores wrote earlier hold the fully lowercased form instead, so an erasure by address in those stores also matches that form; the second match is each store's own, not part of this contract.
  • quarantineAddress(holdId) returns quarantine+<holdId>@erasure.invalid: the address a deleted customer's address-keyed rows are moved to while an erasure is pending. .invalid is a reserved top-level domain, so it can never be a real mailbox. holdId must be 1 to 64 of a-z, 0-9 and -, or the function throws RangeError.
  • isQuarantineAddress(address) says whether an address is one of those.

A store treats a quarantine address as an ordinary address in every message. It only never sends mail to it.

The channel constants

One set for every message type, declared once in schema/erasure-channel-constants.json and read by both packages. A store may configure less than a cap, never more. Every value is a limit chosen with generous margin, not a measurement, so every store and the sender can rely on the same bounds.

| Name | Value | Meaning, and why | |---|---|---| | handlerTimeoutCapSeconds (T) | 60 | The longest a store's erasure handler may run. Chosen far inside the Lambda limit. | | visibilityTimeoutMultiplier | 6 | A store's queue visibility timeout is exactly 6 × its handler timeout, AWS's guidance for an SQS event source. | | visibilityTimeoutCapSeconds (V) | 360 | 6 × T. Derived. | | maxReceiveCount (N) | 5 | Receives before a message moves to the dead-letter queue. | | passTwoDelayCapSeconds (X) | 300 | The longest delay before a store's second pass. Chosen inside SQS's 900 s delay limit. | | batchTimeoutCapSeconds (b) | 60 | The client-side limit on one destructive call; at most T. | | clockSkewAllowanceSeconds (S) | 60 | Allowance for clock difference between publisher and store. Chosen generously because no documented bound on a Lambda's clock was found. | | expiryPeriodSeconds (R) | 3,300 | A request expires R after it is published. It must cover both passes' redeliveries: 2·(N − 1)·V + 2·T + X = 3,300 s at the caps. | | roundPublishBoundSeconds (P) | 600 | A CHECK or CLASSIFY round must be published within this of its creation: above the publisher's 5-minute tick so a failed first attempt gets one retry. | | queueRetentionSeconds | 86,400 | 24 hours, for every queue and dead-letter queue on the channel. R must stay inside it. | | duplicateAfterCompletionWindowSeconds | 3,480 | R + S + T + b: how long after an erasure completes a late duplicate could still act. Derived. |

The package's tests fail if R falls below 2·(N − 1)·V + 2·T + X or above the retention, so a constant cannot change without the arithmetic holding.

Capability fixtures

erasureCapabilityFixtures.pairs holds one valid request for each (type, key kind) pair a store can implement, with the response type and the outcomes a store gives when it implements the pair and when it does not. Its expiresAt is a placeholder in 2099: restamp it to now + R before sending. It is data only; the package ships no responder code.