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

@mrjacket/geto

v1.1.5

Published

Resource lifecycle gateway

Readme



geto is an extensible resource consumption and entity lifecycle gateway for Node.js.

You define what consuming a resource means.
geto handles everything that comes after.

Buffer. File. Stream. Process. HTTP resource. Custom object.
If you can write an adapter for it, geto can manage it.


How it works

Resource
  │
  ▼
Adapter          ← you define this
  │
  ▼
GetoGateway      ← geto owns this
  ├── identity
  ├── storage
  ├── lifecycle
  └── metadata
  │
  ▼
Entity

The gateway does not know what a file is. It does not know what a stream is.
It knows how to manage what your adapter gives it.


Installation

npm install @mrjacket/geto

Requirements: Node.js >= 22.12.0


Documentation

Explore the full documentation portal in the docs/ directory:


Quick start

import { GetoGateway, MemoryStorage, BufferAdapter } from '@mrjacket/geto';

const gateway = new GetoGateway({ storage: new MemoryStorage() });
const adapter = new BufferAdapter();

// Consume a resource — geto stores it and gives you an entity
const entity = await gateway.consume(Buffer.from('Hello, geto'), adapter);

console.log(entity.id);        // "3f2504e0-4f89-..."
console.log(entity.adapterId); // "buffer"
console.log(entity.state);     // "stored"

// Restore the original resource from storage
const buffer = await gateway.restore(entity, adapter);
console.log(buffer.toString()); // "Hello, geto"

Gateway API

consume(resource, adapter, options?)

Consumes a resource through the adapter and stores its representation.
Returns an IEntity descriptor. The entity is the managed handle — not the data itself.

const entity = await gateway.consume(resource, adapter);
const entity = await gateway.consume(resource, adapter, {
  metadata: { label: 'my-resource' }
});

get(id)

Retrieves the entity descriptor by ID. Throws EntityNotFoundError if not found,
EntityStateError if the entity has been deleted.

const entity = await gateway.get('3f2504e0-...');

restore(entity, adapter)

Loads the stored representation and returns the original resource type.
Does not change the entity's state — an entity can be restored multiple times.

const resource = await gateway.restore(entity, adapter);

release(entity, adapter)

Calls adapter.release() on the resource if the adapter defines it.
Marks the entity as RELEASED. The entity remains in storage.

await gateway.release(entity, adapter);

release ≠ delete
Release is a resource lifecycle operation (close a stream, detach a process).
Delete is a storage operation (remove the entity entirely).

delete(id)

Removes the entity's representation from storage and marks it DELETED.
DELETED is a terminal state — no further operations are allowed.

await gateway.delete(entity.id);

exists(id)

Returns true if the entity exists and has not been deleted.

const alive = await gateway.exists(entity.id);

inspect(id)

Returns the entity's metadata.

const metadata = await gateway.inspect(entity.id);
// { id, adapterId, createdAt, updatedAt, custom: { ... } }

Entity lifecycle

consume()
    │
    ▼
 STORED ──────────────────────── restore() [no state change, repeatable]
    │
    ├── release() ──▶ RELEASED ── restore() [adapter-defined]
    │
    └── delete()  ──▶ DELETED  ── [terminal — all further operations throw]

Adapters

Adapters define the consumption semantic. They know how to turn a resource into
a storable representation, and how to turn it back.

interface IGetoAdapter<T, R> {
  readonly adapterId: string;
  readonly semantic: EConsumptionSemantic | EConsumptionSemantic[];
  consume(resource: T): Promise<R>;
  restore(data: R): Promise<T>;
  release?(resource: T): Promise<void>;
}

Built-in adapters

| Adapter | Semantic | Description | |---|---|---| | BufferAdapter | COPY | Copies a Buffer into an independent representation | | JsonAdapter | SERIALIZE | Serializes arbitrary JavaScript objects/values to JSON strings | | StreamAdapter | CAPTURE | Drains a readable stream and restores fresh, replayable streams | | ProcessAdapter | WRAP | Wraps active ChildProcess instances with supervised termination on release | | HttpReferenceAdapter | REGISTER | Registers remote URL resource references and lazily resolves them on restore |

Custom adapters

import { IGetoAdapter, EConsumptionSemantic } from '@mrjacket/geto';

class MyAdapter implements IGetoAdapter<MyResource, MyStoredForm> {
  readonly adapterId = 'my-resource';
  readonly semantic  = EConsumptionSemantic.SERIALIZE;

  async consume(resource: MyResource): Promise<MyStoredForm> {
    return { data: resource.serialize() };
  }

  async restore(stored: MyStoredForm): Promise<MyResource> {
    return MyResource.from(stored.data);
  }
}

const entity = await gateway.consume(resource, new MyAdapter());

geto does not need to know anything about MyResource.


Storage

Storage backends persist the representations. Swapping storage does not change
any other part of your code.

interface IGetoStorage {
  save(id: string, data: unknown): Promise<void>;
  load(id: string): Promise<unknown>;
  delete(id: string): Promise<void>;
  exists(id: string): Promise<boolean>;
}

Built-in storage

| Storage | Description | |---|---| | MemoryStorage | In-memory transient storage using JavaScript Map. No persistence across restarts. | | FileStorage | Persistent local filesystem storage in a designated directory. |

Custom storage

import { IGetoStorage } from '@mrjacket/geto';

class RedisStorage implements IGetoStorage {
  async save(id: string, data: unknown): Promise<void> { /* ... */ }
  async load(id: string): Promise<unknown>              { /* ... */ }
  async delete(id: string): Promise<void>               { /* ... */ }
  async exists(id: string): Promise<boolean>            { /* ... */ }
}

const gateway = new GetoGateway({ storage: new RedisStorage() });

Five consumption semantics

Every adapter declares which consumption semantic it implements via adapter.semantic.
This is observable metadata — the gateway does not behave differently based on it.

| Semantic | Meaning | Built-in Adapter | |---|---|---| | COPY | Create an independent copy of the resource | BufferAdapter | | SERIALIZE | Transform a resource into a persistable representation | JsonAdapter | | CAPTURE | Take ownership/control of a single-use resource | StreamAdapter | | WRAP | Manage an active live resource without serializing it | ProcessAdapter | | REGISTER | Store a reference locator; lazily resolve on restore | HttpReferenceAdapter |

1. COPY — Independent Data Duplication

const adapter = new BufferAdapter();
const entity = await gateway.consume(Buffer.from('hello'), adapter);
const copy = await gateway.restore(entity, adapter);

2. SERIALIZE — Transforming Objects into Persistent State

const adapter = new JsonAdapter<{ id: number; name: string }>();
const entity = await gateway.consume({ id: 1, name: 'Item' }, adapter);
const parsed = await gateway.restore(entity, adapter);

3. CAPTURE — Replaying Single-Use Resources

import { Readable } from 'stream';
// Optional maxBytes guards against unbounded stream memory exhaustion (DoS)
const adapter = new StreamAdapter({ maxBytes: 50 * 1024 * 1024 });

// Drains a single-use Node.js stream and stores it
const entity = await gateway.consume(Readable.from(['Chunk 1', 'Chunk 2']), adapter);

// Restore can be called multiple times, generating fresh Readable streams each time!
const stream1 = await gateway.restore(entity, adapter);
const stream2 = await gateway.restore(entity, adapter);

4. WRAP — Supervising Live Running Resources

import { spawn } from 'child_process';
const adapter = new ProcessAdapter();

// Wraps an active live process
const proc = spawn('node', ['worker.js']);
const entity = await gateway.consume(proc, adapter);

// Releasing the entity terminates the underlying process cleanly (SIGTERM)
await gateway.release(entity, adapter);

5. REGISTER — Deferred / Lazy Network Resolution

const adapter = new HttpReferenceAdapter({
  allowedOrigins: ['https://api.github.com']
});

// Consumes the URL immediately — 0 network requests made
const entity = await gateway.consume('https://api.github.com/zen', adapter);

// Network fetch is triggered only when restore() is explicitly invoked
const body = await gateway.restore(entity, adapter);

Errors

All errors extend GetoError and preserve the original cause where available.

| Error | When | |---|---| | GetoError | Base class | | EntityNotFoundError | Entity ID does not exist | | EntityStateError | Operation is invalid for the entity's current state | | AdapterError | Adapter threw during consume / restore / release | | StorageError | Storage threw during save / load / delete / exists |

import { EntityNotFoundError, EntityStateError } from '@mrjacket/geto';

try {
  await gateway.restore(entity, adapter);
} catch (err) {
  if (err instanceof EntityStateError) {
    console.error('Entity is in an invalid state:', err.message);
  }
}

Support & Sponsoring

If you liked this library or want to support my work, I'd be eternally grateful for a warm coffee! ☕ <3


License

This project is licensed under the GPL-3.0 License. See the LICENSE file for details.

Credits

Author: Mr Jacket / Felix Manrique / x-name15 (we are all the same person)