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
30 changes: 30 additions & 0 deletions .changeset/legacy-ts-endpoint-libcheck.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
'@data-client/endpoint': patch
'@data-client/rest': patch
'@data-client/graphql': patch
---

Fix more TypeScript 4.x errors when `skipLibCheck` is off

[Entity](https://dataclient.io/rest/api/Entity), [Endpoint](https://dataclient.io/rest/api/Endpoint), [Union](https://dataclient.io/rest/api/Union) and [RestEndpoint](https://dataclient.io/rest/api/RestEndpoint) declarations no longer report errors on TypeScript 4.0 through 4.5. On TypeScript 4.0 and 4.1, an `Entity` can be an `Endpoint` schema again.

```ts
import { Endpoint, Entity, schema } from '@data-client/endpoint';

class User extends Entity {
id = '';
type = 'users';
}
const getUser = new Endpoint(
(id: string) => fetch(`/users/${id}`).then(res => res.json()),
{ schema: User },
);
const feed = new schema.Union({ users: User }, 'type');

// Before (TypeScript 4.0, skipLibCheck: false):
// error TS2456: Type alias 'RemoveArray' circularly references itself.
// error TS2322: Type 'typeof User' is not assignable to type 'EntityInterface<any>'.
// Before (TypeScript 4.2):
// error TS2344: Type 'TBase' does not satisfy the constraint 'new (...args: any) => any'.
// After: no errors
```
22 changes: 22 additions & 0 deletions .changeset/legacy-ts-noinfer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
'@data-client/endpoint': patch
'@data-client/normalizr': patch
'@data-client/rest': patch
'@data-client/graphql': patch
'@data-client/react': patch
'@data-client/vue': patch
---

Fix types for TypeScript 4.x when `skipLibCheck` is off

TypeScript 4.0 through 4.7 no longer report `Cannot find name 'NoInfer'` from `@data-client/endpoint` or `@data-client/normalizr`, and TypeScript 4.0 through 4.9 no longer report `Only named exports may use 'export type'` from `@data-client/normalizr`.

```ts
import { Entity } from '@data-client/endpoint';
import { normalize } from '@data-client/normalizr';

// Before (TypeScript 4.7, skipLibCheck: false):
// error TS2304: Cannot find name 'NoInfer'.
// error TS1383: Only named exports may use 'export type'.
// After: no errors
```
13 changes: 10 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,17 @@ jobs:
elif git -c core.quotePath=off diff --no-renames --name-only "${DIFF_RANGE}" > /tmp/ci-changed-files; then
echo "Changed files:"
cat /tmp/ci-changed-files
if ! grep -Eq \
'^(\.circleci/config\.yml|package\.json$|tsconfig[^/]*\.json$|babel\.config\.js$|\.yarnrc\.yml$|scripts/|yarn\.lock|examples/todo-app/|examples/github-app/|examples/normalizr-relationships/|packages/)' /tmp/ci-changed-files; then
# Docs/website/tooling paths; both flags below are denylists over it.
DOCS_ONLY='website/|docs/|\.changeset/|\.cursor/|\.agents/|\.claude/|\.github/|[^/]+\.md$'
# Denylist (fail open): only paths provably outside every esmodule
# job's inputs can skip them, so a new input dir runs by default.
if ! grep -Evq "^(${DOCS_ONLY}|\.vscode/|plans/|__tests__/|eslint\.config\.mjs$|jest\.config\.js$|examples/[^/]+\.md$|examples/(benchmark|benchmark-react|coin-app|nextjs|normalizr-github|normalizr-redux|test-bundlesize|vue-todo-app)/)" /tmp/ci-changed-files; then
echo "No relevant esmodule-related changes; downstream esmodule jobs will halt."
ESMODULE_RELEVANT=false
fi
# Docs/website/tooling-only changes can't affect lint, typecheck or
# unit tests (Playground has unit tests, so it stays relevant).
if ! grep -Evq '^(website/|docs/|\.changeset/|\.cursor/|\.agents/|\.claude/|\.github/|[^/]+\.md$)' /tmp/ci-changed-files \
if ! grep -Evq "^(${DOCS_ONLY})" /tmp/ci-changed-files \
&& ! grep -q '^website/src/components/Playground/' /tmp/ci-changed-files; then
echo "Only docs/website/tooling paths changed; test jobs will halt."
TESTS_RELEVANT=false
Expand Down Expand Up @@ -326,6 +329,8 @@ jobs:
fi
yarn run tsc --project examples/todo-app/tsconfig.typetest.json
yarn run tsc --project examples/github-app/tsconfig.typetest.json
# skipLibCheck: false, so the legacy typesVersions outputs get checked too
yarn run tsc --project examples/todo-app/tsconfig.typetest-libcheck.json

esmodule-types-latest:
executor: node
Expand All @@ -350,6 +355,8 @@ jobs:
yarn run tsc --project examples/todo-app/tsconfig.typetest.json --exactOptionalPropertyTypes true
yarn run tsc --project examples/github-app/tsconfig.typetest.json --exactOptionalPropertyTypes true
yarn run tsc --project examples/todo-app/tsconfig.typetest41.json --exactOptionalPropertyTypes true
# skipLibCheck: false on lib/ too (TS 7 removed moduleResolution node)
yarn run tsc --project examples/todo-app/tsconfig.typetest-libcheck.json --moduleResolution bundler

validate-esmodule-browser-build:
executor: node
Expand Down
9 changes: 7 additions & 2 deletions .cursor/rules/ci-config.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@ alwaysApply: false

- Jest `--maxWorkers` is pinned per job to the `resource_class` vCPU count (large = 4, medium = 2) because docker containers report the host's CPUs via `os.cpus()`. Exception: the ReactNative `unit_tests` run is deliberately uncapped — its suites are fake-timer-wait dominated and capping workers flakes 5s test timeouts.
- Jobs halt via the `halt-unless-relevant-change` command based on flags computed once in `setup` (`.ci-esmodule-relevant`, `.ci-tests-relevant`) and transported via `save_cache`/`restore_cache` (keyed on `CIRCLE_SHA1`) so jobs can halt before paying `attach_workspace`. Missing/unreadable flags fail open (jobs run). On the default branch both flags are always true (a push may carry several commits). The diff uses `--no-renames` so moving a file out of a relevant dir still counts.
- `esmodule`: the path regex must cover every path the esmodule jobs build (currently `examples/todo-app`, `examples/github-app`, `examples/normalizr-relationships`, `packages/`, root `package.json`, root `tsconfig*.json`, `babel.config.js`, `.yarnrc.yml`, `scripts/`, `yarn.lock`, the config itself).
- `esmodule` (validate-esmodule-browser-build, esmodule-types*): a denylist, so new paths fail open. False only when every changed path is provably outside the esmodule jobs' inputs: the shared `DOCS_ONLY` paths (also the `tests` denylist) plus `.vscode/`, `plans/`, root `__tests__/` (excluded by every `tsconfig.compile.json`), `eslint.config.mjs`, `jest.config.js`, `examples/*.md`, and examples the jobs never build (`benchmark`, `benchmark-react`, `coin-app`, `nextjs`, `normalizr-github`, `normalizr-redux`, `test-bundlesize`, `vue-todo-app`). Only add a path if no esmodule job (or the `setup` builds feeding them) reads it.
- `tests` (lint, typecheck, unit_tests, node_matrix): false only when every changed path is docs/website/tooling (`website/`, `docs/`, `.changeset/`, `.cursor/`, `.agents/`, `.claude/`, `.github/`, root `*.md`), except `website/src/components/Playground/` (has unit tests). When both flags are false, `setup` halts before install.
- Legacy TS types (`ci:build:legacy-types`, consumed by `esmodule-types`) build inside `setup` (`ci:build:setup:esmodule`) only when the esmodule flag is set; there is no separate job, to keep a job hop off the critical path. In CI it builds only endpoint/normalizr/rest outputs for TS >= 4.0 (`LEGACY_MIN_TS=4.0`), since the oldest TS in the `esmodule-types` matrix is 4.0; release builds (`build:types`) still emit every version. `scripts/build-legacy-types.sh` builds each TS version concurrently.
- Legacy TS types (`ci:build:legacy-types`, consumed by `esmodule-types`):
- Built inside `setup` (`ci:build:setup:esmodule`) only when the esmodule flag is set; there is no separate job, to keep a job hop off the critical path.
- In CI only endpoint/normalizr/rest outputs for TS >= 4.0 are built (`LEGACY_MIN_TS=4.0`, the oldest TS in the `esmodule-types` matrix); release builds (`build:types`) still emit every version.
- `scripts/build-legacy-types.sh` builds each TS version concurrently; each `ts<version>/` gets the downleveled `lib` (with `abstract new` rewritten to `new` below 4.2, which `downlevel-dts` misses), then every newer version's `src-*-types` overlay, then its own. Keep overlays to small single-purpose modules (like `NoInfer.ts`, `tupleTypes.ts`) so whole-file copies can't go stale.
- `esmodule-types` also runs `examples/todo-app/tsconfig.typetest-libcheck.json` (`skipLibCheck: false`, `types: []`) so errors inside the legacy outputs fail CI, and `esmodule-types-latest` runs it with `--moduleResolution bundler` (TS 7 removed `node`) to cover `lib/`; the other typetests use `skipLibCheck: true`.
- Any change to legacy types building must leave `ts*/` output byte-identical to master (diff it) unless it intentionally changes published types (then add a changeset).
- Never `git fetch --depth` the base branch in the relevance check: a shallow fetch severs the merge base and the three-dot diff fails.
- Changing root `package.json` `workspaces` requires updating the `setup` job's workspace trimming step.

Expand Down
16 changes: 16 additions & 0 deletions examples/todo-app/tsconfig.typetest-libcheck.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
// Standalone so it type-checks only @data-client declarations (and their deps)
// on every TypeScript in the esmodule-types matrix.
"compilerOptions": {
"target": "es2020",
"module": "esnext",
"lib": ["dom", "esnext"],
"moduleResolution": "node",
"strict": true,
"skipLibCheck": false,
"skipDefaultLibCheck": true,
"types": [],
"noEmit": true
},
"files": ["typetest-libcheck.ts"]
}
44 changes: 44 additions & 0 deletions examples/todo-app/typetest-libcheck.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Type-checks published declarations (including legacy typesVersions outputs)
// with skipLibCheck: false; see tsconfig.typetest-libcheck.json
import { Endpoint, Entity, schema } from '@data-client/endpoint';
import { denormalize, MemoCache, normalize } from '@data-client/normalizr';
import { resource, RestEndpoint } from '@data-client/rest';

class User extends Entity {
id = '';
name = '';
type = 'users';
}
class Post extends Entity {
id = '';
title = '';
author = User.fromJS();
static schema = { author: User };
}

const getUser = new Endpoint(
(id: string) => Promise.resolve({ id, name: 'a', type: 'users' }),
{ schema: User },
);
const twoArgs = new Endpoint((a: string, b: number) =>
Promise.resolve({ id: a, title: String(b) }),
);
const bound = twoArgs.bind(null, 'x');
bound(5);
// @ts-expect-error
bound('wrong');

const feed = new schema.Union({ users: User }, 'type');
const PostResource = resource({
path: '/posts/:id',
schema: Post,
paginationField: 'cursor',
});
PostResource.getList.getPage({ cursor: 'a' });
const search = new RestEndpoint({ path: '/search' });

const memo = new MemoCache();
const { result, entities } = normalize(Post, { id: '1', title: 'hi' });
denormalize(Post, result, entities);

export { getUser, feed, search, memo };
45 changes: 0 additions & 45 deletions packages/endpoint/src-4.0-types/schemas/Entity.d.ts

This file was deleted.

164 changes: 0 additions & 164 deletions packages/endpoint/src-4.0-types/schemas/EntityTypes.d.ts

This file was deleted.

Loading
Loading