Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
b0ac5c8
chore@small
selfrefactor Jul 4, 2025
91c41f8
chore@small
selfrefactor Jul 4, 2025
7107fd7
chore@small
selfrefactor Jul 5, 2025
d70b5f7
chore@small
selfrefactor Jul 6, 2025
44f715b
chore@small
selfrefactor Jul 6, 2025
5d38fde
chore@small
selfrefactor Jul 6, 2025
25d1903
chore@small
selfrefactor Jul 6, 2025
ba690cc
chore@small
selfrefactor Jul 6, 2025
3679830
chore@small
selfrefactor Jul 6, 2025
2e067fb
chore@small
selfrefactor Jul 6, 2025
e3524b7
chore@small
selfrefactor Jul 6, 2025
9ff1277
Merge branch 'master' of github.com:selfrefactor/rambda into next-ver…
selfrefactor Jul 10, 2025
e4d38be
Fix R.includes
selfrefactor Jul 10, 2025
8337fe9
chore@small
selfrefactor Jul 10, 2025
f553d15
d
selfrefactor Jul 12, 2025
1e58d08
chore@small
selfrefactor Jul 12, 2025
30f6dd6
Merge branch 'master' of github.com:selfrefactor/rambda into next-ver…
selfrefactor Jul 27, 2025
def7fc9
chore@small
selfrefactor Jul 27, 2025
44ed90b
chore@small
selfrefactor Jul 30, 2025
218e47b
Merge branch 'master' of github.com:selfrefactor/rambda into next-ver…
selfrefactor Sep 3, 2025
b4be76e
chore@small
selfrefactor Sep 4, 2025
59f622f
chore@small
selfrefactor Oct 16, 2025
ca308a6
chore@small
selfrefactor Oct 19, 2025
cd6a42b
kata
selfrefactor Oct 22, 2025
2b5ff5c
2
selfrefactor Oct 22, 2025
99943e2
3
selfrefactor Oct 22, 2025
53fbaec
chore@small
selfrefactor Oct 28, 2025
8aea806
chore@small
selfrefactor Oct 28, 2025
edbf8c3
chore@small
selfrefactor Nov 3, 2025
d263792
Merge branch 'master' of github.com:selfrefactor/rambda into next-ver…
selfrefactor Nov 3, 2025
fa5f220
chore@small
selfrefactor Nov 6, 2025
8d0f582
chore@small
selfrefactor Nov 18, 2025
edd218c
chore@small
selfrefactor Nov 19, 2025
5a891f8
repl
selfrefactor Nov 19, 2025
b275b0d
chore@small
selfrefactor Nov 19, 2025
a9c2905
chore@small
selfrefactor Nov 22, 2025
b714d9a
chore@small
selfrefactor Nov 22, 2025
abe9779
Merge branch 'master' of github.com:selfrefactor/rambda into next-ver…
selfrefactor Nov 29, 2025
9e30131
feat@fix filter
selfrefactor Nov 29, 2025
1e04d63
readonly on top
selfrefactor Nov 30, 2025
0e25f59
chore@small
selfrefactor Dec 6, 2025
645cbd7
union.with
selfrefactor Dec 9, 2025
001a3b9
chore@small
selfrefactor Dec 20, 2025
5938627
chore@small
selfrefactor Dec 21, 2025
a5938ed
chore@small
selfrefactor Dec 21, 2025
3344649
chore@small
selfrefactor Dec 23, 2025
497854b
chore@small
selfrefactor Dec 23, 2025
9297259
chore@small
selfrefactor Dec 23, 2025
657ef80
chore@small
selfrefactor Dec 23, 2025
8a20f3c
chore@small
selfrefactor Dec 23, 2025
ea4a258
chore@small
selfrefactor Dec 23, 2025
4c3781c
Merge branch 'master' of github.com:selfrefactor/rambda into next-ver…
selfrefactor Jan 13, 2026
59f9cb2
1
selfrefactor Jan 28, 2026
3c0de2d
R.mapchain
selfrefactor Feb 4, 2026
b196d91
switcher
selfrefactor Feb 4, 2026
93cbd41
chore@small
selfrefactor Feb 4, 2026
98f31d4
1
selfrefactor Feb 4, 2026
fff02de
1
selfrefactor Feb 4, 2026
0404ea1
chore@small
selfrefactor Feb 4, 2026
58baa54
chore@small
selfrefactor Feb 4, 2026
bd00e4e
chore@small
selfrefactor Feb 4, 2026
9999037
Merge branch 'master' of github.com:selfrefactor/rambda into next-ver…
selfrefactor Feb 4, 2026
7b29752
chore@small
selfrefactor Feb 4, 2026
5b9f45c
1
selfrefactor Feb 6, 2026
5f4a1bf
split.every
selfrefactor Apr 24, 2026
65e19ef
merge.deep
selfrefactor Apr 25, 2026
402b87e
1
selfrefactor Apr 25, 2026
3657e8c
Fix wrong TS
selfrefactor May 15, 2026
2cb5278
1
selfrefactor May 15, 2026
7ef9146
r.path
selfrefactor May 15, 2026
f35661f
1
selfrefactor May 15, 2026
2629365
1
selfrefactor May 15, 2026
8f48e25
chore@small
selfrefactor May 15, 2026
8e2bf59
remove dtslint
selfrefactor May 15, 2026
c5524e4
chore@small
selfrefactor May 15, 2026
31cd044
1
selfrefactor May 25, 2026
c357a7c
chore@small
selfrefactor Jun 6, 2026
c4cd48a
chore@small
selfrefactor Jun 6, 2026
6350a85
1
selfrefactor Jun 23, 2026
79c16a1
chore@small
selfrefactor Jul 4, 2026
07525ee
first batch
selfrefactor Jul 4, 2026
b5ac713
globals
selfrefactor Jul 4, 2026
0514d70
Merge branch 'master' of github.com:selfrefactor/rambda into next-ver…
selfrefactor Jul 4, 2026
e9d11f0
migrate remaining batches 2-10: spec.ts + d.ts for all methods
selfrefactor Jul 4, 2026
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
46 changes: 46 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Rambda — Agent instructions

## Repository structure

- **`source/`** — development directory. Contains both implementations (`.js`) and tests (`.spec.js`, `-spec.ts`).
- **`src/`** — generated artifact. Rebuilt from `source/` via `yarn populatereadme` (delegates to sibling `rambda-scripts` repo). Only `.js` files for registered methods are copied; spec files are excluded.
- **`rambda.js`** — barrel entrypoint, re-exports from `./src/`. Also regenerated during `populatereadme`.
- **`files/index.d.ts`** (5090 lines) — the true TypeScript definition source. `source/index.d.ts` is a stub.
- **`dist/`** — rollup output (CJS, ESM, UMD).

## Commands

| Command | Action |
|---|---|
| `yarn test` | Run all runtime tests (`vitest run --watch -u`) |
| `yarn test:ci` | Run runtime tests in CI mode |
| `yarn test:typings` / `yarn ts` | Run type-level tests only |
| `yarn test:file <path>` | Run a single test file |
| `yarn lint:typings` | `tsc` type check |
| `yarn lint` | Run ESLint + oxlint + Biome + Prettier on `source/` |
| `yarn build` | Bundle `dist/` via rollup (CJS + ESM + UMD) |
| `yarn out` | Full pipeline: populate docs → sync `src/` → build → docsify |

## CI order (must match)

`yarn lint:typings` → `yarn test:ci` → `yarn test:typings`

## Testing quirks

- Two vitest configs: `vitest.config.js` (runtime `*.spec.js` + `*-spec.ts`) and `vitest.typings.config.js` (type-only `*-spec.ts`).
- Runtime tests use `test(...)` (globals from vitest). Type tests use `describe/it` + `expectTypeOf`.
- Coverage threshold: **100%** enforced.
- `source/_internals/` and `source/*.ts` excluded from coverage.

## Method conventions

- **All methods are curried**: `filter(fn)(list)`, **never** `filter(fn, list)`.
- Designed for `pipe(input, ...fns)` usage — type inference works best inside `R.pipe`.
- `max-params: 2` enforced (ESLint).
- `max-statements: 12` enforced (ESLint).
- File naming: `.ts` files must be `kebab-case`, `.tsx` must be `CAMEL_CASE`.
- Semicolons are disabled (Prettier). Single quotes preferred.

## Build note

`yarn out` requires `rambda-scripts` cloned as a sibling directory (`../rambda-scripts`). Without it, `populatedocs`, `populatereadme`, and `create-docsify` will fail.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
11.3.0

- Add missing type narrowing in `R.find`

11.2.0

- Fix wrong typing for `R.equal`,`R.path`, `R.prop`, `R.propEq`, `R.prepend` - [Issue #803](https://github.com/selfrefactor/rambda/issues/803)
Expand Down
62 changes: 62 additions & 0 deletions docs/brainstorming/2026-06-06-remeda-ts-comparison.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Brainstorming: Remeda TypeScript Typing Comparison

**Date:** 2026-06-06
**Trigger:** Compare Rambda with similar library Remeda; check if TS typings are better in Remeda; extend TS tests to prove findings.

## Approaches considered

To compare TypeScript typings between Rambda and Remeda, three approaches were considered:

| # | Approach | Description |
|---|----------|-------------|
| A | Per-method spec extension | Extend each existing Rambda `*-spec.ts` file (e.g. `find-spec.ts`) with a Remeda comparison block. Preserves per-method context but scatters the comparison across many files and duplicates boilerplate. |
| B | Dedicated comparison spec | Create a single `remeda-comparison-spec.ts` file that imports both Rambda and Remeda, testing each method in one place. Centralizes findings, easier to review and remove later. Uses `pipe()` (Rambda) and standalone calls (Remeda) per their respective idioms. |
| C | External markdown report | Write findings only in prose/markdown with no runnable type tests. No CI guard — types drift silently. |

**Chosen:** Approach B — a single runnable `source/remeda-comparison-spec.ts` file, verified via `yarn test:typings` (part of CI pipeline).

## Trade-off matrix

| Criteria | A (Per-method extension) | B (Dedicated spec) | C (Markdown only) |
|---|---|---|---|
| CI-enforced type assertions | Yes, but scattered | Yes, centralized | No |
| Reviewer effort | High (15+ files) | Low (1 file) | Low |
| Removable when addressed | Painful (15+ edits) | `rm` one file | N/A |
| Captures cross-method patterns | No | Yes (tuple preservation, type guard narrowing) | Yes |
| Import complexity | Each file needs its own Remeda import | Single import section | N/A |

## Methods compared

The following shared methods were analyzed. `✓` means Remeda's TypeScript types are strictly more precise for this method.

| Method | Rambda type | Remeda type | Remeda advantage | Proved in test |
|--------|-------------|-------------|------------------|----------------|
| `find` | `(list: T[]) => T \| undefined` | type guard overload → `S \| undefined` | **Type guard narrowing** — result is `string \| undefined` not `(string \| number) \| undefined` | ✓ |
| `drop` | `(list: T[]) => T[]` | `Drop<T, N>` preserves tuple | **Tuple length preservation** — `Drop<[1,2,3,4], 2>` = `[3, 4]` | ✓ |
| `sortBy` | `(list: T[]) => T[]`, single fn | `ReorderedArray<T>`, multi-criteria, desc support | **Multi-criteria sorting**, **desc syntax**, **array type preservation** | ✓ |
| `groupBy` | only `string` keys | `PropertyKey`, `undefined` exclusion | **Number/symbol keys**, **undefined return excludes items** | ✓ |
| `filter(Boolean)` | `ExcludeFalsy` excludes `true` | Keeps `true` | **No `ExcludeFalsy` bug** — `true` is truthy | ✓ |
| `map` | `Mapped<T, U>` | `Mapped<T, U>` | **Comparable** — both preserve tuples | ✓ |
| `indexBy` | `Record<string, T>` | `Partial<Record<K, T>>` with literal keys | **Literal key inference** | ✓ |
| `sum` | `number[]` → `number` | `Sum<T>` + bigint + literal `0` for empty | **BigInt support**, **literal return types** | Not tested in spec (no Rambda pipe shim for data-first) |
| `pick` | `MergeTypes<Pick<T, K>>`, string-path overload | `PickFromArray<T, Keys>` | Comparable — Rambda has string-path advantage | Not tested |

## Decision

**Chosen path:** Approach B — dedicated `source/remeda-comparison-spec.ts` file.

**Rationale:**
- Provides CI-gated proof of each finding (part of `yarn test:typings`)
- Single file can be removed wholesale when/if Rambda improves its typings
- Covers 6 method categories with 8 type assertions, all passing
- More maintainable than scattering comparisons across 15+ spec files

**Rejected:**
- Approach A (too scattered, high overhead to review)
- Approach C (no CI enforcement, typings would drift silently)

## Open risks

1. **Remeda import resolution** — Remeda's `package.json` lacks a `"types"` export condition. Works via NodeNext module resolution (finds `dist/index.d.ts` alongside `dist/index.js`), but may break if TypeScript/vite resolution strategy changes.
2. **`@ts-expect-error` in `filter(Boolean)` test** — tests that Rambda's `ExcludeFalsy` excludes `true` are marked with `@ts-expect-error` because the test asserts the *actual* buggy behavior while noting the *expected* correct behavior. If `ExcludeFalsy` is fixed, the `@ts-expect-error` line will become an error requiring update.
3. **Rambda pipe compatibility** — Remeda methods used inside Rambda's `pipe()` rely on type inference through Rambda's overloaded pipe signatures. Some edge cases (e.g., `pipe(async data, remedaMethod)`) may not resolve correctly.
12 changes: 12 additions & 0 deletions files/DEV_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
your task is big so 1. build a plan with todo in refactor.md 2. each "next" from dev will perform 10% of the task. 3. task is to move tests from foo.spec.js to foo-spec.ts 3.1 include test testing if foo-spec.ts exists 4. verify that it works with running node node_modules/vitest/
dist/cli.js run --config vitest.typings.config.js source/foo.spec.ts

Goal: migrate runtime source/*.spec.js tests and source/*-spec.ts to source/*.spec.ts form
1. Write refactor.md at repo root listing every method that has either *.spec.js or *-spec.ts file(or both), grouped into 10 batches of ~10% each, in dependency order.
2. Each dev next completes one batch: create foo.spec.js using foo-spec.ts(if exists) and foo.spec.js(if exists)
2.1 Extend TS tests to assert also final result(which is missing in -spec.ts files)
2.2 If there is error on TS types after moving *.spec.js file, use `any` to make TS happy
2.3 don't delete the old files.
3. While running the batch, work file by file because you need to verify new file is correct with "node node_modules/vitest/
dist/cli.js run --config vitest.typings.config.js source/foo.spec.ts" and "bun lint:typings"
===
https://github.com/radashi-org/radashi/pull/425/changes


Expand Down
14 changes: 10 additions & 4 deletions files/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -555,8 +555,10 @@ Notes:

*/
// @SINGLE_MARKER
export function find<T, S extends T>(predicate: (x: T) => x is S): (list: T[]) => S | undefined;
export function find<T>(predicate: (x: T) => boolean): (list: T[]) => T | undefined;

// declare function find<T, S extends T>(data: readonly T[], predicate: (value: T, index: number, data: readonly T[]) => value is S): S | undefined;
// declare function find<T>(data: readonly T[], predicate: (value: T, index: number, data: readonly T[]) => boolean): T | undefined;
/*
Method: exists

Expand Down Expand Up @@ -4836,13 +4838,17 @@ export function flattenObject<T extends object>(obj: T): FlattenObject<T>;
/*
Method: shuffle

Explanation: It returns a randomized copy of array.
Explanation: It returns a randomized copy of array.

Example:

```
const result = R.shuffle(
[1, 2, 3]
const data = Array.from({ length: 100 }, (_, i) => i + 1)
const result = await pipe(
data,
shuffle<number>, // NEEDS EXPLICIT TYPE ANNOTATION
splitEvery(10),
flatMap(String),
)
// => [3, 1, 2] or [2, 3, 1] or ...
```
Expand Down
28 changes: 28 additions & 0 deletions list_specs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env python3
import os

os.chdir(os.path.join(os.path.dirname(__file__), 'source'))
spec_js = {f.replace('.spec.js','') for f in os.listdir() if f.endswith('.spec.js')}
spec_ts = {f.replace('-spec.ts','') for f in os.listdir() if f.endswith('-spec.ts')}
exclude = {'radashi-comparison','remeda-comparison','convertToType','transformPropObject','_playground'}
spec_js.difference_update(exclude)
spec_ts.difference_update(exclude)
both = sorted(spec_js & spec_ts)
only_js = sorted(spec_js - spec_ts)
only_ts = sorted(spec_ts - spec_js)

all_methods = sorted(spec_js | spec_ts)
print('Total methods:', len(all_methods))
print()
print('=== Both (%d) ===' % len(both))
for b in both: print(b)
print()
print('=== Only .spec.js (%d) ===' % len(only_js))
for b in only_js: print(b)
print()
print('=== Only -spec.ts (%d) ===' % len(only_ts))
for b in only_ts: print(b)
print()
# Print all methods for the refactor.md
print('=== ALL METHODS (alphabetical) ===')
for m in all_methods: print(m)
26 changes: 7 additions & 19 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,30 +37,18 @@
"dependencies": {},
"devDependencies": {
"@types/mocha": "10.0.10",
"@types/node": "25.8.0",
"@vitest/coverage-v8": "5.0.0-beta.2",
"@types/node": "26.1.0",
"@vitest/coverage-v8": "5.0.0-beta.5",
"helpers-fn": "2.1.1",
"lodash": "4.18.1",
"radashi": "13.0.0-beta.ffa4778",
"rambdax": "11.3.1",
"ramda": "0.32.0",
"remeda": "2.34.1",
"rollup": "4.60.4",
"types-ramda": "0.31.0",
"typescript": "6.0.3",
"vitest": "5.0.0-beta.2"
},
"jest": {
"testEnvironment": "node",
"testRegex": ".*\\.(spec|test)\\.js$",
"setupFilesAfterEnv": [
"./files/testSetup.js"
],
"collectCoverageFrom": [
"source/*.js",
"!_internals",
"!benchmarks"
]
"remeda": "2.39.0",
"rollup": "4.62.2",
"types-ramda": "0.32.0",
"typescript": "7.0.1-rc",
"vitest": "4.1.9"
},
"repository": {
"type": "git",
Expand Down
53 changes: 53 additions & 0 deletions plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Comparison Plan: Rambda vs Radashi & Remeda TypeScript Typings

## Objective
Identify methods in Rambda where TypeScript typings differ from Radashi and Remeda, and demonstrate differences with runnable type tests in the style of the existing `source/remeda-comparison-spec.ts`.

## Phase 1: Research (complete)

### Radashi (v13.0.0-beta)
- **Data-first** (not curried) — functions take data as first arg, unlike Rambda's data-last curry.
- **Different naming**: `group` (not `groupBy`), `objectify` (not `indexBy`), `sort` (not `sortBy`), `select` (combines filter+map), `selectFirst` (find+map), `chain` (not `pipe`).
- **No `filter`/`find`/`drop`/sync `map`** — Radashi uses `select`/`selectFirst` which lack type guard narrowing.
- **No variadic tuple inference** — `chain` uses 10 overloads (Rambda `pipe` uses 20).
- **`const` type parameters** on `first`/`last`/`draw` for literal tuple extraction.
- `group` returns `Partial<Record<K, T[]>>` (correct, same as Rambda `groupBy`).
- `objectify` returns `Record<Key, Value>` (non-partial — assumes all keys exist; Rambda `indexBy` returns `Record<string, T>`).
- `sort` only accepts **numeric** getter (Rambda `sortBy` accepts any `Ord` = `number | string | boolean | Date`).
- `sum` has 2 overloads: `(array: number[]) => number` and `(array: T[], fn) => number` (Rambda only has `(list: number[]) => number`).
- `unique` supports optional `toKey` function (Rambda `uniq` does not; `uniqBy` exists separately).
- `pick` supports both key array and predicate filter (Rambda supports string-path and key array).

### Remeda (v2.34.1)
- Already compared in `source/remeda-comparison-spec.ts` across 6 method categories.
- Remeda advantages confirmed: type guard narrowing, tuple preservation, multi-criteria sortBy, literal key inference, `NonEmptyArray` for groupBy.
- Remaining candidates needing comparison: `pick` (strict key inference), `omit`, `uniq`, `zip`, `sum`.

## Phase 2: Radashi Comparison Tests (`source/radashi-comparison-spec.ts`)

| # | Rambda method | Radashi equivalent | Key type difference |
|---|---|---|---|
| 1 | `find` | `selectFirst` | Radashi `selectFirst` has no type guard overload; `condition` is `(item, idx) => boolean` |
| 2 | `filter` | `select` | Radashi `select` combines filter+map; no type guard; `condition` is `(item, idx) => boolean` |
| 3 | `groupBy` | `group` | Both return `Partial<Record<K, T[]>>` — comparable |
| 4 | `indexBy` | `objectify` | Radashi infers literal keys (`Record<K, V>`) but non-partial vs Rambda `Record<string, T>` |
| 5 | `sortBy` | `sort` | Radashi only numeric getter, Rambda accepts any `Ord` |
| 6 | `pick` | `pick` | Radashi supports predicate filter (`KeyFilter`); Rambda has string-path overload |
| 7 | `omit` | `omit` | Both similar `Omit<T, K>` |
| 8 | `uniq` | `unique` | Radashi supports optional `toKey` (like `uniqBy`); Rambda has separate `uniq`/`uniqBy` |
| 9 | `sum` | `sum` | Radashi adds mapper overload `(T[], fn) => number` |
| 10 | `pipe` | `chain` | Radashi 10 overloads vs Rambda 20; neither uses variadic tuples |
| 11 | `zip` | `zip` | Radashi variadic (2-5 arrays, tuple-of-tuples return); Rambda curried `(K[]) => (V[]) => KeyValuePair[]` |

## Phase 3: Extend Remeda Comparison Tests (`source/remeda-comparison-spec.ts`)

Add comparisons for:
- **`pick`**: Remeda's `PickFromArray<T, Keys>` strict key inference vs Rambda's `MergeTypes<Pick<T, K>>` + string-path.
- **`omit`**: Remeda's strict key inference vs Rambda's.
- **`uniq`**: Remeda's overloads (no args, key selector, etc.) vs Rambda's simple `uniq<T>(list: T[])`.
- **`zip`**: Remeda's tuple inference vs Rambda's curried `KeyValuePair[]`.

## Phase 4: Verification

Run CI order: `yarn lint:typings` → `yarn test:ci` → `yarn test:typings`
All new type assertions must pass.
Loading
Loading