New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

signal-chain

Package Overview
Dependencies
Maintainers
1
Versions
41
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

signal-chain - npm Package Compare versions

Comparing version 0.5.0 to 0.6.0

4

dist/docs.d.ts

@@ -159,4 +159,4 @@ export type { BasicComputed, BasicSignal, Chain, CleanupExec, ConnectedChain } from './signal/types';

const stop: typeof tools.stop;
const $if: <Range>(condition: import("./signal-ts").Function1<Range, boolean>) => import("./signal/if").IfCall<Range>;
const ifNot: <V1>(condition: import("./signal-ts").Function1<V1, boolean>) => import("./signal/if").IfCall<V1>;
const $if: <Range, Fallback = never>(condition: import("./signal-ts").Function1<Range, boolean>, args_0?: Fallback | undefined) => <V1 extends Range, V2, V3 = V2, V4 = V3, V5 = V4, V6 = V5, V7 = V6, V8 = V7, V9 = V8, V10 = V9, V11 = V10, V12 = V11, V13 = V12, V14 = V13, V15 = V14, V16 = V15, V17 = V16, V18 = V17, V19 = V18, V20 = V19>(element1?: Chain<V1, V2> | undefined, element2?: Chain<V2, V3> | undefined, element3?: Chain<V3, V4> | undefined, element4?: Chain<V4, V5> | undefined, element5?: Chain<V5, V6> | undefined, element6?: Chain<V6, V7> | undefined, element7?: Chain<V7, V8> | undefined, element8?: Chain<V8, V9> | undefined, element9?: Chain<V9, V10> | undefined, element10?: Chain<V10, V11> | undefined, element11?: Chain<V11, V12> | undefined, element12?: Chain<V12, V13> | undefined, element13?: Chain<V13, V14> | undefined, element14?: Chain<V14, V15> | undefined, element15?: Chain<V15, V16> | undefined, element16?: Chain<V16, V17> | undefined, element17?: Chain<V17, V18> | undefined, element18?: Chain<V18, V19> | undefined, element19?: Chain<V19, V20> | undefined) => Chain<V1, Fallback extends never ? V1 : Fallback | V20>;
const ifNot: <V1>(condition: import("./signal-ts").Function1<V1, boolean>) => <V1_1 extends V1, V2, V3 = V2, V4 = V3, V5 = V4, V6 = V5, V7 = V6, V8 = V7, V9 = V8, V10 = V9, V11 = V10, V12 = V11, V13 = V12, V14 = V13, V15 = V14, V16 = V15, V17 = V16, V18 = V17, V19 = V18, V20 = V19>(element1?: Chain<V1_1, V2> | undefined, element2?: Chain<V2, V3> | undefined, element3?: Chain<V3, V4> | undefined, element4?: Chain<V4, V5> | undefined, element5?: Chain<V5, V6> | undefined, element6?: Chain<V6, V7> | undefined, element7?: Chain<V7, V8> | undefined, element8?: Chain<V8, V9> | undefined, element9?: Chain<V9, V10> | undefined, element10?: Chain<V10, V11> | undefined, element11?: Chain<V11, V12> | undefined, element12?: Chain<V12, V13> | undefined, element13?: Chain<V13, V14> | undefined, element14?: Chain<V14, V15> | undefined, element15?: Chain<V15, V16> | undefined, element16?: Chain<V16, V17> | undefined, element17?: Chain<V17, V18> | undefined, element18?: Chain<V18, V19> | undefined, element19?: Chain<V19, V20> | undefined) => Chain<V1_1, never>;
/**

@@ -163,0 +163,0 @@ * Chain multiple elements together. Each element can be a {@link Chain} or a {@link ConnectedChain}

@@ -164,4 +164,4 @@ export * from './signal/types';

merge: typeof merge;
if: <Range_2>(condition: import("./signal/types").Function1<Range_2, boolean>) => import("./signal/if").IfCall<Range_2>;
ifNot: <V1_2>(condition: import("./signal/types").Function1<V1_2, boolean>) => import("./signal/if").IfCall<V1_2>;
if: <Range_2, Fallback = never>(condition: import("./signal/types").Function1<Range_2, boolean>, args_0?: Fallback | undefined) => <V1_2 extends Range_2, V2_1, V3_1 = V2_1, V4_1 = V3_1, V5_1 = V4_1, V6_1 = V5_1, V7_1 = V6_1, V8_1 = V7_1, V9_1 = V8_1, V10_1 = V9_1, V11_1 = V10_1, V12_1 = V11_1, V13_1 = V12_1, V14_1 = V13_1, V15_1 = V14_1, V16_1 = V15_1, V17_1 = V16_1, V18_1 = V17_1, V19_1 = V18_1, V20_1 = V19_1>(element1?: import("./signal/types").Chain<V1_2, V2_1> | undefined, element2?: import("./signal/types").Chain<V2_1, V3_1> | undefined, element3?: import("./signal/types").Chain<V3_1, V4_1> | undefined, element4?: import("./signal/types").Chain<V4_1, V5_1> | undefined, element5?: import("./signal/types").Chain<V5_1, V6_1> | undefined, element6?: import("./signal/types").Chain<V6_1, V7_1> | undefined, element7?: import("./signal/types").Chain<V7_1, V8_1> | undefined, element8?: import("./signal/types").Chain<V8_1, V9_1> | undefined, element9?: import("./signal/types").Chain<V9_1, V10_1> | undefined, element10?: import("./signal/types").Chain<V10_1, V11_1> | undefined, element11?: import("./signal/types").Chain<V11_1, V12_1> | undefined, element12?: import("./signal/types").Chain<V12_1, V13_1> | undefined, element13?: import("./signal/types").Chain<V13_1, V14_1> | undefined, element14?: import("./signal/types").Chain<V14_1, V15_1> | undefined, element15?: import("./signal/types").Chain<V15_1, V16_1> | undefined, element16?: import("./signal/types").Chain<V16_1, V17_1> | undefined, element17?: import("./signal/types").Chain<V17_1, V18_1> | undefined, element18?: import("./signal/types").Chain<V18_1, V19_1> | undefined, element19?: import("./signal/types").Chain<V19_1, V20_1> | undefined) => import("./signal/types").Chain<V1_2, Fallback extends never ? V1_2 : Fallback | V20_1>;
ifNot: <V1_3>(condition: import("./signal/types").Function1<V1_3, boolean>) => <V1_4 extends V1_3, V2_2, V3_2 = V2_2, V4_2 = V3_2, V5_2 = V4_2, V6_2 = V5_2, V7_2 = V6_2, V8_2 = V7_2, V9_2 = V8_2, V10_2 = V9_2, V11_2 = V10_2, V12_2 = V11_2, V13_2 = V12_2, V14_2 = V13_2, V15_2 = V14_2, V16_2 = V15_2, V17_2 = V16_2, V18_2 = V17_2, V19_2 = V18_2, V20_2 = V19_2>(element1?: import("./signal/types").Chain<V1_4, V2_2> | undefined, element2?: import("./signal/types").Chain<V2_2, V3_2> | undefined, element3?: import("./signal/types").Chain<V3_2, V4_2> | undefined, element4?: import("./signal/types").Chain<V4_2, V5_2> | undefined, element5?: import("./signal/types").Chain<V5_2, V6_2> | undefined, element6?: import("./signal/types").Chain<V6_2, V7_2> | undefined, element7?: import("./signal/types").Chain<V7_2, V8_2> | undefined, element8?: import("./signal/types").Chain<V8_2, V9_2> | undefined, element9?: import("./signal/types").Chain<V9_2, V10_2> | undefined, element10?: import("./signal/types").Chain<V10_2, V11_2> | undefined, element11?: import("./signal/types").Chain<V11_2, V12_2> | undefined, element12?: import("./signal/types").Chain<V12_2, V13_2> | undefined, element13?: import("./signal/types").Chain<V13_2, V14_2> | undefined, element14?: import("./signal/types").Chain<V14_2, V15_2> | undefined, element15?: import("./signal/types").Chain<V15_2, V16_2> | undefined, element16?: import("./signal/types").Chain<V16_2, V17_2> | undefined, element17?: import("./signal/types").Chain<V17_2, V18_2> | undefined, element18?: import("./signal/types").Chain<V18_2, V19_2> | undefined, element19?: import("./signal/types").Chain<V19_2, V20_2> | undefined) => import("./signal/types").Chain<V1_4, never>;
log: <V_1>(message?: string | undefined) => import("./signal/types").Chain<V_1>;

@@ -168,0 +168,0 @@ buffer: <V_2>(size: number) => import("./signal/types").Chain<V_2, V_2[]>;

import { Function1, Chain } from "./types";
export interface IfCall<Range> {
<V1 extends Range, V2>(element1: Chain<V1, V2>): Chain<V1, V1 | V2>;
<V1 extends Range, V2, V3>(element1: Chain<V1, V2>, element2: Chain<V2, V3>): Chain<V1, V1 | V3>;
<V1 extends Range, V2, V3, V4>(element1: Chain<V1, V2>, element2: Chain<V2, V3>, element3: Chain<V3, V4>): Chain<V1, V1 | V4>;
<V1 extends Range, V2, V3, V4, V5>(element1: Chain<V1, V2>, element2: Chain<V2, V3>, element3: Chain<V3, V4>, element4: Chain<V4, V5>): Chain<V1, V1 | V5>;
<V1 extends Range, V2, V3, V4, V5, V6>(element1: Chain<V1, V2>, element2: Chain<V2, V3>, element3: Chain<V3, V4>, element4: Chain<V4, V5>, element5: Chain<V5, V6>): Chain<V1, V1 | V6>;
<V1 extends Range, V2, V3, V4, V5, V6, V7>(element1: Chain<V1, V2>, element2: Chain<V2, V3>, element3: Chain<V3, V4>, element4: Chain<V4, V5>, element5: Chain<V5, V6>, element6: Chain<V6, V7>): Chain<V1, V1 | V7>;
<V1 extends Range, V2, V3, V4, V5, V6, V7, V8>(element1: Chain<V1, V2>, element2: Chain<V2, V3>, element3: Chain<V3, V4>, element4: Chain<V4, V5>, element5: Chain<V5, V6>, element6: Chain<V6, V7>, element7: Chain<V7, V8>): Chain<V1, V1 | V8>;
<V1 extends Range, V2, V3, V4, V5, V6, V7, V8, V9>(element1: Chain<V1, V2>, element2: Chain<V2, V3>, element3: Chain<V3, V4>, element4: Chain<V4, V5>, element5: Chain<V5, V6>, element6: Chain<V6, V7>, element7: Chain<V7, V8>, element8: Chain<V8, V9>): Chain<V1, V1 | V9>;
<V1 extends Range, V2, V3, V4, V5, V6, V7, V8, V9, V10>(element1: Chain<V1, V2>, element2: Chain<V2, V3>, element3: Chain<V3, V4>, element4: Chain<V4, V5>, element5: Chain<V5, V6>, element6: Chain<V6, V7>, element7: Chain<V7, V8>, element8: Chain<V8, V9>, element9: Chain<V9, V10>): Chain<V1, V1 | V10>;
<V1 extends Range, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11>(element1: Chain<V1, V2>, element2: Chain<V2, V3>, element3: Chain<V3, V4>, element4: Chain<V4, V5>, element5: Chain<V5, V6>, element6: Chain<V6, V7>, element7: Chain<V7, V8>, element8: Chain<V8, V9>, element9: Chain<V9, V10>, element10: Chain<V10, V11>): Chain<V1, V1 | V11>;
<V1 extends Range, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12>(element1: Chain<V1, V2>, element2: Chain<V2, V3>, element3: Chain<V3, V4>, element4: Chain<V4, V5>, element5: Chain<V5, V6>, element6: Chain<V6, V7>, element7: Chain<V7, V8>, element8: Chain<V8, V9>, element9: Chain<V9, V10>, element10: Chain<V10, V11>, element11: Chain<V11, V12>): Chain<V1, V1 | V12>;
<V1 extends Range, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13>(element1: Chain<V1, V2>, element2: Chain<V2, V3>, element3: Chain<V3, V4>, element4: Chain<V4, V5>, element5: Chain<V5, V6>, element6: Chain<V6, V7>, element7: Chain<V7, V8>, element8: Chain<V8, V9>, element9: Chain<V9, V10>, element10: Chain<V10, V11>, element11: Chain<V11, V12>, element12: Chain<V12, V13>): Chain<V1, V1 | V13>;
<V1 extends Range, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14>(element1: Chain<V1, V2>, element2: Chain<V2, V3>, element3: Chain<V3, V4>, element4: Chain<V4, V5>, element5: Chain<V5, V6>, element6: Chain<V6, V7>, element7: Chain<V7, V8>, element8: Chain<V8, V9>, element9: Chain<V9, V10>, element10: Chain<V10, V11>, element11: Chain<V11, V12>, element12: Chain<V12, V13>, element13: Chain<V13, V14>): Chain<V1, V1 | V14>;
<V1 extends Range, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15>(element1: Chain<V1, V2>, element2: Chain<V2, V3>, element3: Chain<V3, V4>, element4: Chain<V4, V5>, element5: Chain<V5, V6>, element6: Chain<V6, V7>, element7: Chain<V7, V8>, element8: Chain<V8, V9>, element9: Chain<V9, V10>, element10: Chain<V10, V11>, element11: Chain<V11, V12>, element12: Chain<V12, V13>, element13: Chain<V13, V14>, element14: Chain<V14, V15>): Chain<V1, V1 | V15>;
<V1 extends Range, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16>(element1: Chain<V1, V2>, element2: Chain<V2, V3>, element3: Chain<V3, V4>, element4: Chain<V4, V5>, element5: Chain<V5, V6>, element6: Chain<V6, V7>, element7: Chain<V7, V8>, element8: Chain<V8, V9>, element9: Chain<V9, V10>, element10: Chain<V10, V11>, element11: Chain<V11, V12>, element12: Chain<V12, V13>, element13: Chain<V13, V14>, element14: Chain<V14, V15>, element15: Chain<V15, V16>): Chain<V1, V1 | V16>;
<V1 extends Range, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17>(element1: Chain<V1, V2>, element2: Chain<V2, V3>, element3: Chain<V3, V4>, element4: Chain<V4, V5>, element5: Chain<V5, V6>, element6: Chain<V6, V7>, element7: Chain<V7, V8>, element8: Chain<V8, V9>, element9: Chain<V9, V10>, element10: Chain<V10, V11>, element11: Chain<V11, V12>, element12: Chain<V12, V13>, element13: Chain<V13, V14>, element14: Chain<V14, V15>, element15: Chain<V15, V16>, element16: Chain<V16, V17>): Chain<V1, V1 | V17>;
<V1 extends Range, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18>(element1: Chain<V1, V2>, element2: Chain<V2, V3>, element3: Chain<V3, V4>, element4: Chain<V4, V5>, element5: Chain<V5, V6>, element6: Chain<V6, V7>, element7: Chain<V7, V8>, element8: Chain<V8, V9>, element9: Chain<V9, V10>, element10: Chain<V10, V11>, element11: Chain<V11, V12>, element12: Chain<V12, V13>, element13: Chain<V13, V14>, element14: Chain<V14, V15>, element15: Chain<V15, V16>, element16: Chain<V16, V17>, element17: Chain<V17, V18>): Chain<V1, V1 | V18>;
<V1 extends Range, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19>(element1: Chain<V1, V2>, element2: Chain<V2, V3>, element3: Chain<V3, V4>, element4: Chain<V4, V5>, element5: Chain<V5, V6>, element6: Chain<V6, V7>, element7: Chain<V7, V8>, element8: Chain<V8, V9>, element9: Chain<V9, V10>, element10: Chain<V10, V11>, element11: Chain<V11, V12>, element12: Chain<V12, V13>, element13: Chain<V13, V14>, element14: Chain<V14, V15>, element15: Chain<V15, V16>, element16: Chain<V16, V17>, element17: Chain<V17, V18>, element18: Chain<V18, V19>): Chain<V1, V1 | V19>;
<V1 extends Range, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20>(element1: Chain<V1, V2>, element2: Chain<V2, V3>, element3: Chain<V3, V4>, element4: Chain<V4, V5>, element5: Chain<V5, V6>, element6: Chain<V6, V7>, element7: Chain<V7, V8>, element8: Chain<V8, V9>, element9: Chain<V9, V10>, element10: Chain<V10, V11>, element11: Chain<V11, V12>, element12: Chain<V12, V13>, element13: Chain<V13, V14>, element14: Chain<V14, V15>, element15: Chain<V15, V16>, element16: Chain<V16, V17>, element17: Chain<V17, V18>, element18: Chain<V18, V19>, element19: Chain<V19, V20>): Chain<V1, V1 | V20>;
(first: Chain<unknown, unknown>, ...elements: Chain<unknown, unknown>[]): Chain<unknown, unknown>;
}
export declare const ifFn: <Range>(condition: Function1<Range, boolean>) => IfCall<Range>;
export declare const ifNot: <V1>(condition: Function1<V1, boolean>) => IfCall<V1>;
type IfCall<Range, Fallback> = <V1 extends Range, V2, V3 = V2, V4 = V3, V5 = V4, V6 = V5, V7 = V6, V8 = V7, V9 = V8, V10 = V9, V11 = V10, V12 = V11, V13 = V12, V14 = V13, V15 = V14, V16 = V15, V17 = V16, V18 = V17, V19 = V18, V20 = V19>(element1?: Chain<V1, V2>, element2?: Chain<V2, V3>, element3?: Chain<V3, V4>, element4?: Chain<V4, V5>, element5?: Chain<V5, V6>, element6?: Chain<V6, V7>, element7?: Chain<V7, V8>, element8?: Chain<V8, V9>, element9?: Chain<V9, V10>, element10?: Chain<V10, V11>, element11?: Chain<V11, V12>, element12?: Chain<V12, V13>, element13?: Chain<V13, V14>, element14?: Chain<V14, V15>, element15?: Chain<V15, V16>, element16?: Chain<V16, V17>, element17?: Chain<V17, V18>, element18?: Chain<V18, V19>, element19?: Chain<V19, V20>) => Chain<V1, Fallback extends never ? V1 : Fallback | V20>;
export declare const ifFn: <Range, Fallback = never>(condition: Function1<Range, boolean>, args_0?: Fallback | undefined) => IfCall<Range, Fallback>;
export declare const ifNot: <V1>(condition: Function1<V1, boolean>) => IfCall<V1, never>;
export {};
//# sourceMappingURL=if.d.ts.map
import { chain } from "./chain";
export const ifFn = (condition) => (first, ...elements) => {
const chained = chain(first, ...elements);
return (next, parameter, context) => {
if (condition(parameter)) {
return chained(next, parameter, context);
}
return next(parameter);
export const ifFn = (condition, ...args) => {
const hasFallback = args.length === 1;
// @ts-expect-error
return (first, ...elements) => {
const chained = chain(first, ...elements);
return (next, parameter, context) => {
if (condition(parameter)) {
return chained(next, parameter, context);
}
return hasFallback ? args[0] : next(parameter);
};
};
};
export const ifNot = (condition) => ifFn(value => !condition(value));
{
"name": "signal-chain",
"version": "0.5.0",
"version": "0.6.0",
"author": "Christoph Franke",

@@ -5,0 +5,0 @@ "description": "Declarative Reactive Programming Library",

# Signal-Chain: A Declarative Reactive Programming Library
## Simplify State Management in Web Applications
**signal-chain** is a declarative reactive programming library aimed at streamlining state management and data flow in web applications. By focusing on a minimal set of reactive primitives and constructs, it offers a powerful yet straightforward approach to building dynamic web interfaces. Unlike traditional reactive programming libraries that overwhelm with an extensive array of operators and concepts, **signal-chain** focuses on simplicity, readability and reusability, ensuring that your projects remain manageable and scalable.
**Signal-Chain** is a library for composing observables and asynchronous operations. It provides a core type, the Chain, several operators (select, effect, await, listen, combine, ...), and a reactive Primitive to combine declarative state management with asynchronous operations.
### Features
Think of Signal-Chain as RxJS for the grug developer.
#### State Management
The essential concepts of **Signal-Chain** are:
1. **Reactive Primitives**: Supports defining state as reactive signals.
2. **Reactivity to Data**: Detects changes in plain objects and arrays using proxies.
3. **Reactivity to Events**: Allows subscriptions to DOM events.
- **Primitive**: Represents a single reactive value.
- **Chain**: A series of operations. Can be connected to update a primitive.
- **Element**: A single operation in a chain. Every chain can be an element of another chain.
- **Listener**: A subscription to a reactive value. Can be an element in a chain.
- **Operator**: An element in a chain, that modifies the behaviour of a sub chain.
#### Declarative and Asynchronous Operations
**Installation**:
```sh
npm install signal-chain
```
4. **Declarative Syntax**: Defines data flows and reactive operations.
5. **Async Operations**: Integrates asynchronous tasks with the reactive model.
6. **Error Handling**: Adopts an errors-as-values philosophy, supported by TypeScript typing.
### Examples
#### Composition
**Getting Started**
7. **Chain Functionality**: Combines elements into chains for cohesive data flows.
8. **Reusability**: Chains can be reused and incorporated into other chains.
9. **Activation on Connection**: Chains are activated upon connection, allowing modular construction.
Let's define a primitive.
```typescript
import $ from 'signal-chain'
#### Additional Features
const counter = $.primitive.create(0)
```
10. **Performance**: Optimized for low runtime overhead with a small library footprint (minified <10k).
11. **TypeScript Support**: Fully typed, focusing on type inference.
Now we can define a chain that listens to the counter and logs the value using the `effect` operator.
```typescript
const log = $.chain(
counter.listen,
$.effect(value => console.log(value))
)
```
When we connect the chain, it will start listening to the counter.
```typescript
const disconnect = $.connect(log) // logs: 0
### Installation and Usage Instructions
counter.value = 1 // logs: 1
```
To start using **signal-chain** in your projects, follow these steps:
We can also rewrite the value into something different with the `select` operation.
```typescript
const formatted = $.chain(
counter.listen,
$.select(x => `The number is ${x}`)
)
```
1. **Installation**:
```sh
npm install signal-chain
```
And then we create a primitive that is connected to the formatted chain.
```typescript
const formattedValue = $.primitive.connect(formatted)
2. **Using Primitives**:
```typescript
import $ from 'signal-chain'
counter.value = 10
console.log(formattedValue.value) // logs: The number is 10
```
// creates a reactive primitive, like a ref or a signal
const counter = $.primitive.create(0)
**Reusability**
// chains are the core of signal-chain, they define a series of operations
const format = $.chain(
$.select(x => Math.round(x)),
// select is like map, but with a more distinctive name
$.select(x => `The number is ${x}`),
In the above example, we formatted a counter value. Sometimes, we want to specify behaviour, but want to apply it to different sources. We can do that, by creating a chain that requires an input value.
```typescript
const format = $.chain(
$.select<number>(x => Math.round(x)),
$.if(x => x > 1)(
$.select(x => `We have ${x} apples`)
),
$.if(x => x === 1)(
$.select(() => `We have an apple`)
),
$.if(x => x === 0)(
$.select(() => `We have no apples`)
),
$.assert.isNumber(
$.select(() => 'I cannot handle negative apples. Or NaN apples.')
)
)
```
const invert = $.chain(
$.select(x => -x),
)
Here we have created a chain, that will format a number into a string. There are a few things going on here:
- The first `$.select` has a type parameter `number`, that specifies that we expect a number as input. If we do not specify this, typescript will infer `unknown` and complain about the `Math.round(x)` operation.
- The `$.if` operator will only execute the inner chain, if the condition is true.
- The `$.assert` operator is similar to the `$.if` operator, in that if the condition is met, the inner chain will execute. In contrast to the `$.if` operator, it performs static type inference. In this case, all `number` input is rewritten into a `string` be the `$.select` operator, so typescript can infer that the signal after the assertion block is always a `string`.
// for a chain to become active, it needs to be connected
const disconnect = $.connect(
counter.listen, // listen to changes in counter
invert, // apply invert chain
format, // apply format chain
$.effect(result => console.log(result)) // log: The number is 0
)
We can now use the chain to format any number.
```typescript
const counter = $.primitive.create(0)
const formatted = $.primitive.connect(
counter.listen,
format,
$.effect(value => console.log(value)) // logs: We have no apples
)
counter.value = 10 // log: The number is -10
```
counter.value = 10 // logs: We have 10 apples
console.log(formatted.value) // logs: We have 10 apples
```
3. **Reactive Data Fetching**:
**Asynchronous Operations**
Let's say we want to fetch some user data from an API and whenever the user changes, we need to fetch new data
Admittedly, this type of formatting could have been done with a simple function. Let us take this approach and combine it with some asynchronous logic, so we can see the real value of the chain.
```typescript
import $ from 'signal-chain'
Here, we will implement a auto suggest feature, that fetches some data from an API and logs the result.
```typescript
import $ from 'signal-chain'
type UserJSON = { ... }
// store user input into a reactive primitive
const input = $.primitive.create('')
document.getElementById('my-input')?.addEventListener('input', (event) => {
input.value = (event.target as HTMLInputElement).value
})
// here we store the user name, initialized with undefined
const user = $.primitive.create<string | undefined>(undefined)
// utility function we will use for debounce
// resolves the promise to the input after the given time
const wait = <T>(input: T, ms: number) => new Promise<T>(resolve => setTimeout(() => resolve(input), ms))
const suggestions = $.primitive.connect(
input.listen,
// here we define and connect the data fetching chain
const data = $.primitive.connect( // connect will run eager and execute synchronously
user.listen, // listen to user changes
// debounce
$.await.latest(
$.select(input => wait(input, 150)),
),
$.assert.not.isError(),
// type inferred: string | undefined
$.assert.isNothing( // assert.isNothing catches null | undefined
// the inside block will only be executed when the assertion is true,
$.emit('guest') // in that case we emit 'guest' as our default
// ensure long enough input, if not, fallback to empty array
$.if((input: string) => input.length > 2, [])(
$.select(input => `/api/suggest/${input}`),
$.await.latest(
$.select(url => fetch(url).then(response => response.json()) as Promise<string[]>),
),
// type inferred: string
$.select(user => `/api/user/${user.toLowerCase()}`), // construct the url
$.await.latest( // await.latest will only pass on the latest resolve
$.select(url => fetch(url).then(response => response.json()) as Promise<UserJSON>),
$.assert.isError(
$.effect(err => console.error('Error fetching suggestions:', err)),
$.select(() => [])
),
),
// type inferred: UserJSON | Error
$.assert.isError( // when a promise is rejected, its result will be a value of type Error
$.effect(err => console.error('Error fetching data:', err)),
$.stop() // no data, stop processing
),
$.log('Suggestions:') // Suggestions: ['So', 'many', 'suggestions', ...]
)
```
In this example we first store the user input in a reactive primitive and use it as input for the suggestions chain.
Let's have a look at the debounce part:
- The `$.await.latest` operator will only pass on the latest resolved value. If a value is incoming while the previous promise is still pending, the previous promise will be cancelled.
- Together with the wait function, this will effectively create a debounce, only passing when there is no user input for 150ms.
// type inferred: UserJSON
$.log('Data fetched:')
)
When given no argument, `$.assert.not.isError()` will only pass on the value if it is not an error, otherwise it throws. We use it here to ensure type consistency: `$.await.latest` cannot know, if a promise will resolve or reject. Therefore, it passes on `ValueType | Error`. because we know that our wait function cannot reject, we can safely assert that there is no error. The assertion then removes the `Error` type from the chain.
// now we set the user name, which will trigger data fetching
user.value = 'Detlev' // logs: Data fetched: { ... }
data.value // everything we know about Detlev, type UserJSON is inferred
```
The `$.if` operator has a second parameter, which is the fallback value. If the condition is not met, the fallback value will be used instead. This defaults to the input of the operator, so if no fallback is given and the condition is not met, the input is being passed through.
4. **Reactive State Management**:
The `$.await.latest` is also exactly what we want in fetching data. If a new input is given while the previous request is still pending, the previous request will be cancelled. There are 4 more await operators for different strategies:
- `$.await.parallel`: Passes all resolved values in the order they resolve.
- `$.await.order`: Passes all resolved values in the order they were requested.
- `$.await.block`: Will only enter the inner block when no promise is pending. Incoming values will be discarded.
- `$.await.queue`: Will only enter the inner block when no promise is pending. Incoming values will be queued and processed by the inner block once the pending promise is resolved.
Sometimes there is existing logic, that we cannot easily change. Instead of trying to find every potential place in the code, that potentially updates an object or sets a key, we can set up a listener from inside a chain, that will wrap the targeted part of the object and automatically listen for any changes. That way, we can gradually extend the observer pattern into a code base, that has not been designed with reactivity in mind.
**Reactivity with Plain Objects**
Sometimes you may work with existing logic, or maybe you prefer to store our state in plain objects. *Signal-Chain* can listen to plain objects and arrays using proxies.
```typescript
import $ from 'signal-chain'
Let's assumet we have a state object like this:
```typescript
const state = {
filter: '',
elements: [
{ age: 25, name: 'Alice' },
{ age: 73, name: 'Bob' },
{ age: 42, name: 'Charlie' },
{ age: 18, name: 'David' },
]
}
```
// this is just a plan javascript object
const user = {
meta: {
profil: '/default.png',
loggedIn: false,
comments: []
},
name: 'guest'
}
And let's further assume there is pre-existing logic spread out over the source code, that sets the filter and the elements. We can still listen to changes of the filter and the elements:
```typescript
const filter = $.primitive.connect(
$.emit(state), // emit the state object
$.listen.key('filter'), // listen to changes in the filter key
)
const elements = $.primitive.connect(
$.emit(state), // emit the state object
$.listen.key('elements'), // listen to changes in the elements key
)
```
The `$.emit` operator has no input and emits the passed argument.
The `$.listen.key` operator will listen to changes in the given key of the incoming object. If the value of the key is an array type, the listener will be attached to the array itself via proxy, so that any changes to the array will also be detected.
// clicking the logout button will logout the user
document.getElementById('logout')?.addEventListener('click', () => {
user.name = 'guest'
user.meta = {
profil: '/default.png',
loggedIn: false,
comments: []
}
})
This is how we could implement a reactive filter:
```typescript
const filteredElements = $.primitive.connect(
$.combine(
elements.listen,
filter.listen,
),
$.select(([elements, filter]) => elements.filter(element => element.name.includes(filter)))
)
```
Here we use the `$.combine` operator, which takes a list of elements, and combines them into one element that emits an array. Whenever one of the elements fires with a new value, the combined element will fire with the latest values of all elements.
// here we can select a user from a dropdown
document.getElementById('my-user-select')?.addEventListener('change', (event) => {
user.name = (event.target as HTMLSelectElement).value
user.meta = {
profil: `/profil/${user.name}.png`,
loggedIn: true,
comments: []
}
})
**Types and Inferrence**
// here we can add a comment to the user
document.getElementById('add-comment')?.addEventListener('click', () => {
user.meta.comments.push({ ... }) // push the new comment
})
We have so far used code with minimal type information. *Signal-Chain* is fully typed and built with type inferrence in mind. In all the above examples we have complete inferrence. Typescript will also protect us from making any mistakes, like chaining the wrong chains together:
```typescript
const counter = $.primitive.create('hallo') // <-- wait, this is a string!
const formatted = $.primitive.connect(
counter.listen,
$.select(x => 2 * x) // <-- typescript error: The rhs of an arithmetic operation must be a number...
)
```
// up until here, we have plain javascript
// we can now react to state updates without changing the existing code
const numberOfComments = $.primitive.connect(
$.emit(user), // emit the user object
$.listen.key('meta'), // listen to the meta key
// when the meta object changes, the comments listener gets reattached
$.listen.key('comments'),
// a proxy is used on the array to make sure we catch all changes
$.select(comments => comments.length)
)
Sometimes a little context is necessary
```typescript
const multiplicator = $.chain(
$.select(x => x * 5) // <-- typescript error: x is unknown
)
// or we could fetch some private data only for loggedIn users
type PrivateData = { ... }
const privateData = $.primitive.connect(
$.emit(user), // emit the user object
$.listen.key('meta'), // listen to changes in the meta object
$.listen.key('loggedIn'), // when the meta object changes, this listener gets reattached
$.if(loggedIn => !!loggedIn)(
$.emit(user),
$.listen.key('name'), // name changes when user account is switched without logout
$.await.latest(
$.select(name => `/api/private/${name.toLowerCase()}`),
$.select(url => fetch(url).then(response => response.json()) as Promise<PrivateData>),
),
$.assert.isError($.emit(undefined)), // emit undefined on error
),
$.ifNot(loggedIn => !!loggedIn)(
// do not leak any data if not logged in
$.select(() => undefined)
),
// typescript cannot infer that the if/ifNot eliminated the possibility of a boolean type here
// the assert statement will crash if not given a parameter
// it will also remove the type boolean from the type inference
$.assert.not.isBoolean()
)
const numberMultiplicator = $.chain(
$.select<number>(x => x * 5) // no we are good
)
```
console.log(
privateData.value // type inferred: PrivateData | undefined
)
```
In case you do want to be more general, you need to use a function with a generic
```typescript
// creates a chain from T -> boolean
const truthyness = <T>() => $.chain(
$.select((x: T) => !!x)
)
const counter = $.primitive.create(0)
const somechain = $.chain(
counter.listen,
truthyness() // T gets inferred to number
)
```
### Documentation
For more detail, have a look at the official [documentation](https://christophfranke.github.io/signal-chain).
### Known Issues

@@ -202,0 +245,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc