Kinesis

Lightweight, dependency-free fake of Amazon Kinesis Data Streams that speaks the real Kinesis AWS JSON 1.1 wire protocol, so application code using @aws-sdk/client-kinesis can run against it with zero cost and zero side effects.

KeyValue
Port4576
ProtocolKinesis AWS JSON 1.1 over HTTP/2 cleartext (h2c, prior knowledge)
Compatible client@aws-sdk/client-kinesis (v3)
Imageparlel/kinesis:0.1
Size~95 KB
Startup< 100ms
StateIn-memory, ephemeral, resettable

Why HTTP/2? The real @aws-sdk/client-kinesis ships a NodeHttp2Handler, so the client talks HTTP/2 cleartext (h2c) with prior knowledge — it does not speak HTTP/1.1. The parlel fake fronts the port with a tiny TCP listener that sniffs the connection preface: h2c connections are routed to an HTTP/2 server, while plain HTTP/1.1 requests (e.g. curl/fetch against the internal /_parlel/* endpoints) are routed to an HTTP/1.1 server. Both are served on the same port.

Quick Start

Start the server:

import { KinesisServer } from "./services/kinesis/src/server.js";

const server = new KinesisServer(4576);
await server.start();
// ... use it ...
await server.stop();

Connect with the real AWS SDK client:

import {
  KinesisClient,
  CreateStreamCommand,
  PutRecordCommand,
  GetShardIteratorCommand,
  GetRecordsCommand,
  ListShardsCommand,
} from "@aws-sdk/client-kinesis";

const kinesis = new KinesisClient({
  region: "us-east-1",
  endpoint: "http://127.0.0.1:4576",
  credentials: { accessKeyId: "parlel", secretAccessKey: "parlel" },
});

// Create a stream with one shard.
await kinesis.send(new CreateStreamCommand({ StreamName: "events", ShardCount: 1 }));

// Write a record.
await kinesis.send(
  new PutRecordCommand({
    StreamName: "events",
    PartitionKey: "user-123",
    Data: new TextEncoder().encode("hello-kinesis"),
  }),
);

// Read it back.
const { Shards } = await kinesis.send(new ListShardsCommand({ StreamName: "events" }));
const { ShardIterator } = await kinesis.send(
  new GetShardIteratorCommand({
    StreamName: "events",
    ShardId: Shards[0].ShardId,
    ShardIteratorType: "TRIM_HORIZON",
  }),
);
const { Records } = await kinesis.send(new GetRecordsCommand({ ShardIterator }));
console.log(new TextDecoder().decode(Records[0].Data)); // "hello-kinesis"

Implemented Operations

All 39 operations exposed by @aws-sdk/client-kinesis are dispatched. 38 are fully functional; SubscribeToShard is intentionally a no-op error (see below).

Stream lifecycle

Retention

Shards

Records

Tags

Enhanced monitoring

Encryption

Consumers (enhanced fan-out)

Resource policies

Streaming (unsupported)

Addressing: StreamName vs StreamARN

Every stream-scoped operation accepts either StreamName or StreamARN. ARNs follow the form:

arn:aws:kinesis:us-east-1:000000000000:stream/<streamName>

Consumer ARNs follow:

arn:aws:kinesis:us-east-1:000000000000:stream/<streamName>/consumer/<consumerName>:<creationEpochSeconds>

Records, shards, and iterators

Surface coverage

This emulator faithfully replicates the API surface most application code and agents exercise. Anything below the supported lines is either an intentional design choice for a fast, zero-cost local emulator (✓ By design) or a candidate for a future release (⟳ Roadmap) — never a silent inaccuracy.

Legend: ✅ fully supported · ◐ accepted (stored, not strictly enforced) · ✓ by design · ⟳ on the roadmap.

FeatureStatusNotes
Stream create/delete/describe/list✅ SupportedPROVISIONED + ON_DEMAND
PutRecord / PutRecords / GetRecords✅ Supportedbyte-accurate Data, partial-batch failures
Shard iterators (all 5 types)✅ Supportedincl. AT_TIMESTAMP
Split / merge / UpdateShardCount✅ Supportedparent/child lineage tracked
Retention increase/decrease✅ Supportedbounds enforced (24–8760h)
Tags (stream + generic resource)✅ Supportedpagination on ListTagsForStream
Enhanced monitoring✅ Supportedmetric names + ALL
Stream encryption (KMS)✅ Supportedmetadata only; no actual crypto
Consumers (register/describe/list)✅ Supportedenhanced fan-out registration
Resource policies✅ Supportedstored verbatim, no policy evaluation
Account settings / max record size✅ Supportedin-memory toggles
Addressing by StreamName or StreamARN✅ Supportedboth accepted everywhere
SubscribeToShard⛔ Unsupportedrequires a long-lived HTTP/2 event stream; returns InvalidArgumentException directing you to GetRecords
Actual KMS encryption of payloads⛔ Unsupportedencryption is metadata-only
Time-based record expiry / trimming⛔ Unsupportedrecords persist until reset() or process exit
IAM authorization / signature checks⛔ Unsupportedany credentials accepted
Cross-region / real throughput limits⛔ Unsupportedsingle in-memory region, no throttling

Error codes and shapes

Errors are returned as a non-2xx response with the JSON-RPC error body plus the x-amzn-errortype header. The SDK reads the error code from the body's __type field first, then the header.

{
  "__type": "ResourceNotFoundException",
  "message": "Stream ghost under account 000000000000 not found."
}

Common error codes:

CodeHTTPWhen
ResourceNotFoundException400Stream/shard/consumer/policy does not exist
ResourceInUseException400Stream or consumer name already exists
InvalidArgumentException400Missing/invalid parameters, bad iterator, unsupported op
ValidationException400Out-of-range values (e.g. max record size)
LimitExceededException400Too many consumers on a stream
ExpiredNextTokenException400Malformed pagination token
InternalFailureException500Unexpected server error

Internal endpoints (not part of Kinesis)

These are served over plain HTTP/1.1 for convenience:

State can also be reset in-process via server.reset().

Environment variables

The manifest exports these so application code auto-discovers the endpoint:

AWS_ACCESS_KEY_ID=parlel
AWS_SECRET_ACCESS_KEY=parlel
AWS_REGION=us-east-1
AWS_ENDPOINT_URL_KINESIS=http://127.0.0.1:4576
AWS_ENDPOINT_URL=http://127.0.0.1:4576
<!-- parlel:testenv:start -->

Configuration — test.env

Copy these into your test.env (used by the bridge sidecar flow). Tokens are Parlel's seeded test credentials — any non-empty value is accepted by the emulator, so you rarely need to change them. Swap in real credentials only when pointing at the live service in prod.env.

AWS_ACCESS_KEY_ID=parlel
AWS_SECRET_ACCESS_KEY=parlel
AWS_REGION=us-east-1
AWS_ENDPOINT_URL_KINESIS=http://parlel-bridge:4576
AWS_ENDPOINT_URL=http://parlel-bridge:4576
<!-- parlel:testenv:end -->