Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Whether you're building a startup or scaling an enterprise service, these librar
Our philosophy is simple: **production code should be safe, testable, and easy to reason about.**

Applications should:

- **Respect system signals** for shutdown/interrupts
- **Be easy to start up** and log lifecycle events
- **Be safe to run** in different environments without accidentally affecting production
Expand Down Expand Up @@ -52,7 +53,7 @@ This library is the result of years of building production services that handle
- [CSV Processing](packages/common-server/src/csv/README.md) - Type-safe CSV reading and writing
- [Encryption](packages/common-server/src/encryption/README.md) - AES-256-GCM and RSA utilities
- [Environment](packages/common-server/src/env/README.md) - Environment detection and management
- [Extensions](packages/common-server/src/extensions/README.md) - Stream and iterator utilities
- [Extensions](packages/common/src/extensions/README.md) - Stream and iterator utilities
- [Hashing](packages/common-server/src/hash/README.md) - Consistent hashing for feature flags
- [HTTP Client](packages/common-server/src/http/README.md) - Axios wrappers with logging and proxies
- [Locking](packages/common-server/src/locking/README.md) - Distributed lock interface
Expand Down Expand Up @@ -103,7 +104,7 @@ class Example extends ServiceBase {
}
}

await app(new Example())
await app(new Example());
```

### Configuration
Expand All @@ -117,12 +118,12 @@ For example, we can create a configuration that uses the concept of a `Provided
Imagine we load `ProvidedConfig` from [our example](packages/common-server/src/config/loader.test.ts). How do we get the actual value of `/path/to/ssm`? We can resolve each value in parallel:

```typescript
const resolveConfig = async (resolver: ValueProvider, config: ProvidedConfig): Promise<Config> => {
const resolveConfig = async (resolver: ValueProvider, config: ProvidedConfig): Promise<Config> => {
return autoResolve({
host: config.host,
dynamic: async () => resolver.getValue(config.dynamic)
})
}
dynamic: async () => resolver.getValue(config.dynamic),
});
};
```

`autoResolve` will recursively go through the object and automagically resolve any lambda-based promises in parallel. This way you can have throughput limitation on SSM/etc and dynamically resolve your configuration with minimal friction.
Expand All @@ -140,7 +141,7 @@ You may wonder how you generate a trace. To create a new one you can easily wrap
```typescript
await withNewTrace(async () => {
// ...
})
});
```

If you have a trace already provided (for example via a library like [hapi](https://hapi.dev/tutorials/logging/?lang=en_US)) you can provide a trace ID with:
Expand Down Expand Up @@ -206,7 +207,7 @@ To see how to easily create a connection to in-memory databases (sqlite) or mysq
Other than the trace logging we've mentioned above, our logger supports context-sensitive log wrapping. For example:

```typescript
const envBasedLogger = log.with({ env: currentEnvironment() })
const envBasedLogger = log.with({ env: currentEnvironment() });

envBasedLogger.info('Booting up!');
envBasedLogger.info('Welcome');
Expand Down Expand Up @@ -307,7 +308,7 @@ Need to roll out features gradually or run A/B tests? Our consistent hashing uti

```typescript
// Roll out to 10% of users
if (consistentChance(userId, 'new-feature', 0.10)) {
if (consistentChance(userId, 'new-feature', 0.1)) {
// User is in the 10%
}

Expand Down
2 changes: 1 addition & 1 deletion packages/common-aws/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@paradoxical-io/common-aws",
"version": "2.5.1",
"version": "2.5.2",
"description": "Common aws for paradox services",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Streams } from '@paradoxical-io/common-server';
import { Streams } from '@paradoxical-io/common';
import { CompoundKey, notNullOrUndefined } from '@paradoxical-io/types';

import { KeyValueList, KeyValueTable } from '../keyTable';
Expand Down
2 changes: 1 addition & 1 deletion packages/common-hapi/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@paradoxical-io/common-hapi",
"version": "2.5.1",
"version": "2.5.2",
"description": "Common hapi code for paradoxical services",
"files": [
"dist/**/*.js",
Expand Down
2 changes: 1 addition & 1 deletion packages/common-server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@paradoxical-io/common-server",
"version": "2.5.1",
"version": "2.5.2",
"description": "Common code for paradox services",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand Down
3 changes: 1 addition & 2 deletions packages/common-server/src/csv/streamableCsv.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { TypedReadable, TypedTransformable } from '@paradoxical-io/common';
import { Brand } from '@paradoxical-io/types';
import { stringify } from 'csv';
import stream from 'stream';

import { TypedReadable, TypedTransformable } from '../extensions';

export interface StreamableOptions {
dateToISO?: boolean;
}
Expand Down
1 change: 0 additions & 1 deletion packages/common-server/src/extensions/index.ts

This file was deleted.

1 change: 0 additions & 1 deletion packages/common-server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ export * from './contracts';
export * from './csv';
export * from './encryption';
export * from './env';
export * from './extensions';
export * from './hash';
export * from './http';
export * from './locking';
Expand Down
2 changes: 1 addition & 1 deletion packages/common-server/src/sftp/sftp.itest.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Streams } from '@paradoxical-io/common';
import { safeExpect } from '@paradoxical-io/common-test';
import { Readable } from 'stream';

import { CsvStreamWriter } from '../csv';
import { Streams } from '../extensions';
import { newSftpDocker } from './docker';
import { Sftp } from './sftp';

Expand Down
2 changes: 1 addition & 1 deletion packages/common-sql/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@paradoxical-io/common-sql",
"version": "2.5.1",
"version": "2.5.2",
"files": [
"dist/**/*.js",
"dist/**/*.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/common-test/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@paradoxical-io/common-test",
"version": "2.5.1",
"version": "2.5.2",
"description": "Common test-code for jest",
"files": [
"dist/**/*.js",
Expand Down
1 change: 0 additions & 1 deletion packages/common/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ module.exports = {
'readline',
'repl',
'smalloc',
'stream',
'string_decoder',
'sys',
'timers',
Expand Down
2 changes: 1 addition & 1 deletion packages/common/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@paradoxical-io/common",
"version": "2.5.1",
"version": "2.5.2",
"description": "Common code for all paradox projects",
"files": [
"dist/**/*.js",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ A comprehensive utility library for working with Node.js streams, async iterator
Convert Node.js streams into more manageable data structures:

```typescript
import { Streams } from '@paradoxical-io/common-server/extensions';
import { Streams } from '@paradoxical-io/common/extensions';
import fs from 'fs';

// Convert a byte stream to a Buffer
Expand All @@ -35,7 +35,7 @@ const users = await Streams.toArray(objectStream);
Transform any paginated API into a streamable async iterator:

```typescript
import { Streams } from '@paradoxical-io/common-server/extensions';
import { Streams } from '@paradoxical-io/common/extensions';

// Example: Paginate through database results
interface DbResponse {
Expand All @@ -45,8 +45,8 @@ interface DbResponse {

const userStream = Streams.pagingAsyncIterator(
undefined, // start page/token
async (token) => await db.query({ nextToken: token }),
(response) => {
async token => await db.query({ nextToken: token }),
response => {
if (!response.items.length) return undefined;
return [response.nextToken, response.items];
}
Expand All @@ -63,7 +63,7 @@ for await (const user of userStream) {
Process async iterators with functional operations:

```typescript
import { Streams } from '@paradoxical-io/common-server/extensions';
import { Streams } from '@paradoxical-io/common/extensions';

// Take only first N items
const firstTen = Streams.takeAsync(dataStream, 10);
Expand All @@ -75,10 +75,7 @@ for await (const batch of batched) {
}

// Transform stream data
const transformed = Streams.mapAsync(
userStream,
async (user) => await enrichUserData(user)
);
const transformed = Streams.mapAsync(userStream, async user => await enrichUserData(user));

// Collect async iterator to array
const allUsers = await Streams.from(userStream);
Expand All @@ -89,7 +86,7 @@ const allUsers = await Streams.from(userStream);
Work with sync generators using familiar functional patterns:

```typescript
import { Streams } from '@paradoxical-io/common-server/extensions';
import { Streams } from '@paradoxical-io/common/extensions';

function* generateNumbers() {
let i = 0;
Expand All @@ -101,12 +98,12 @@ const firstTen = [...Streams.take(generateNumbers(), 10)];
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

// Drop items while predicate is true
const afterFive = Streams.dropWhile(generateNumbers(), (n) => n < 5);
const afterFive = Streams.dropWhile(generateNumbers(), n => n < 5);
const next5 = [...Streams.take(afterFive, 5)];
// [5, 6, 7, 8, 9]

// Take items while predicate is true
const lessThanTen = Streams.takeWhile(generateNumbers(), (n) => n < 10);
const lessThanTen = Streams.takeWhile(generateNumbers(), n => n < 10);
const result = [...lessThanTen];
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
```
Expand All @@ -118,64 +115,77 @@ const result = [...lessThanTen];
#### Static Methods

**`toBuffer(readStream: Readable): Promise<Buffer>`**

- Reads a byte stream into a single Buffer
- Handles backpressure automatically
- Throws on stream errors

**`toArray<T>(readStream: TypedReadable<T>): Promise<T[]>`**

- Reads a typed object stream into an array
- Useful for collecting stream results
- Handles both 'end' and 'close' events

**`pagingAsyncIterator<Page, Result, Response>(start, next, extract): AsyncGenerator<Result>`**

- Converts paginated API calls into async iterator
- `start`: Initial page/token value
- `next`: Function to fetch next page
- `extract`: Function to extract [nextPage, results] from response

**`grouped<T>(iterator: AsyncGenerator<T>, size: number): AsyncGenerator<T[]>`**

- Groups async iterator items into fixed-size batches
- Yields arrays of specified size
- Last batch may be smaller

**`takeAsync<T>(iterator: AsyncGenerator<T>, size: number): AsyncGenerator<T>`**

- Takes first N items from async iterator
- Returns new async iterator

**`take<T>(iterator: Generator<T>, size: number): Generator<T>`**

- Takes first N items from sync iterator
- Returns new sync iterator

**`takeWhile<T>(iterator: Generator<T>, predicate: (d: T) => boolean): Generator<T>`**

- Takes items while predicate returns true
- Stops at first false result

**`dropWhile<T>(iterator: Generator<T>, predicate: (d: T) => boolean): Generator<T>`**

- Drops items while predicate returns true
- Yields remaining items

**`map<T, Y>(iterator: AsyncGenerator<T>, mapper: (data: T) => Y): AsyncGenerator<Y>`**

- Synchronously transforms async iterator values
- Mapper function executes synchronously

**`mapAsync<T, Y>(iterator: AsyncGenerator<T>, mapper: (data: T) => Promise<Y>): AsyncGenerator<Y>`**

- Asynchronously transforms async iterator values
- Awaits mapper function for each item

**`from<T>(iterator: AsyncGenerator<T>): Promise<T[]>`**

- Collects all async iterator values into array
- Awaits completion of iterator

## Type Definitions

### TypedReadable<T>

A Node.js Readable stream that pushes typed objects instead of buffers.

```typescript
type TypedReadable<T> = stream.Readable & { push(data: T): void };
```

### TypedTransformable<T>

A Node.js Transform stream that accepts typed objects.

```typescript
Expand All @@ -190,8 +200,8 @@ type TypedTransformable<T> = stream.Transform & { write(data: T): void };
// Fetch all users in batches of 50
const allUsers = Streams.pagingAsyncIterator(
0, // start page
async (page) => await api.getUsers({ page, limit: 50 }),
(response) => {
async page => await api.getUsers({ page, limit: 50 }),
response => {
if (response.users.length === 0) return undefined;
return [page + 1, response.users];
}
Expand All @@ -210,10 +220,7 @@ for await (const batch of batched) {
// Complex transformation pipeline
const results = await Streams.from(
Streams.takeAsync(
Streams.mapAsync(
Streams.grouped(dataStream, 100),
async (batch) => await processBatch(batch)
),
Streams.mapAsync(Streams.grouped(dataStream, 100), async batch => await processBatch(batch)),
10 // Take first 10 processed batches
)
);
Expand All @@ -223,7 +230,7 @@ const results = await Streams.from(

```typescript
import fs from 'fs';
import { Streams } from '@paradoxical-io/common-server/extensions';
import { Streams } from '@paradoxical-io/common/extensions';

// Read large file without loading into memory
const stream = fs.createReadStream('huge-file.bin');
Expand Down
1 change: 1 addition & 0 deletions packages/common/src/extensions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export * from './maps';
export * from './math';
export * from './object';
export * from './sets';
export * from './streams';
export * from './strings';
2 changes: 1 addition & 1 deletion packages/types/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@paradoxical-io/types",
"version": "2.5.1",
"version": "2.5.2",
"description": "Paradox shared types",
"files": [
"dist/**/*.js",
Expand Down