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
14 changes: 9 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,15 @@ never its own *strictness*: `strict` and friends are inherited, untouched.
"tsc is the best sweeper" channel the spec-property-retirement playbook leans on: the
directive is meant to go red the day a removed key comes back. Outside a program it
evaluates never, and *deleting the directive leaves every gate just as green* — which is
how spec's 17 retirement pins across 5 files were found (#5286). Before writing one,
check the file is compiled. `packages/spec` additionally holds its test-layer residue in
a per-file, exactly-measured, shrink-only ledger (`packages/spec/test-typecheck-debt.json`,
`pnpm --filter @objectstack/spec gen:test-typecheck-debt`): a file not listed there may
have no type errors at all.
how spec's 17 retirement pins across 5 files were found (#5286), and the repo-wide sweep
that followed found the eighteenth in `packages/client` (#5449). Before writing one,
check the file is compiled. A package whose test layer still carries residue holds it in
a per-file, exactly-measured, shrink-only ledger next to its `tsconfig.test.json`
(`<package>/test-typecheck-debt.json`, regenerated with
`pnpm --filter <package> gen:test-typecheck-debt`): a file not listed there may have no
type errors at all. The gate behind both is one shared script,
`scripts/check-test-typecheck.mts --package <dir>` — onboard a package by wiring its
`typecheck` script to it, never by copying it.

One trap worth knowing before you read any of these counts: under `moduleResolution:
NodeNext` a relative import missing its `.js` extension does not resolve, every symbol it
Expand Down
5 changes: 4 additions & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
"build": "tsup --config ../../tsup.config.ts",
"test": "vitest run",
"test:integration": "vitest run --config vitest.integration.config.ts",
"typecheck": "tsc --noEmit"
"check:test-typecheck": "tsx ../../scripts/check-test-typecheck.mts --self-test && tsx ../../scripts/check-test-typecheck.mts --package packages/client --project tsconfig.test.json",
"gen:test-typecheck-debt": "tsx ../../scripts/check-test-typecheck.mts --update --package packages/client --project tsconfig.test.json",
"typecheck": "tsc --noEmit && pnpm check:test-typecheck"
},
"dependencies": {
"@objectstack/core": "workspace:*",
Expand All @@ -29,6 +31,7 @@
"@objectstack/objectql": "workspace:*",
"@objectstack/plugin-hono-server": "workspace:*",
"@objectstack/runtime": "workspace:*",
"tsx": "^4.23.1",
"typescript": "^6.0.3",
"vitest": "^4.1.10"
},
Expand Down
5 changes: 4 additions & 1 deletion packages/client/src/client.batch-transaction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,10 @@ describe('data.batchTransaction (live Hono, #1604)', () => {
label: 'Task',
fields: {
title: { type: 'text', label: 'Title' },
project: { type: 'lookup', reference_to: 'project', label: 'Project' },
// `reference`, not `reference_to`: the latter is no key the field
// schema knows, so this lookup declared no target at all until a
// tsc program finally read the file (TS2561, #5449).
project: { type: 'lookup', reference: 'project', label: 'Project' },
},
});
// Objects registered AFTER bootstrap miss the boot-time schema sync, so
Expand Down
9 changes: 6 additions & 3 deletions packages/client/src/client.hono.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe('ObjectStackClient (with Hono Server)', () => {
// --- BROKER SHIM START ---
// HttpDispatcher requires a broker to function. We inject a simple shim.
(kernel as any).broker = {
call: async (action: string, params: any, opts: any) => {
call: async (action: string, params: any, _opts: any) => {
const parts = action.split('.');
const service = parts[0];
const method = parts[1];
Expand Down Expand Up @@ -159,9 +159,12 @@ describe('ObjectStackClient (with Hono Server)', () => {

// Discovery is REST's, computed from its registry (#4018 D12: declared
// === enforced). Every route it advertises must actually answer.
// `routes` is optional on the discovery payload, so it is reached
// optionally and asserted — a missing map fails `toContain` rather than
// being waved through by a `!` or a `?? {}` default (#5449).
const endpoints = client['discoveryInfo']!.routes;
expect(endpoints.data).toContain('/api/v1/data');
expect(endpoints.metadata).toContain('/api/v1/meta');
expect(endpoints?.data).toContain('/api/v1/data');
expect(endpoints?.metadata).toContain('/api/v1/meta');

// Enforced, not just declared — the pairing #4018 exists to hold.
expect((await fetch(`${baseUrl}/api/v1/meta/objects`)).status).not.toBe(404);
Expand Down
19 changes: 16 additions & 3 deletions packages/client/src/client.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { describe, it, expect, vi } from 'vitest';
import { ObjectStackClient, QueryBuilder, FilterBuilder, createQuery, createFilter } from './index';
// `QueryBuilder` / `FilterBuilder` are named only by the `describe` blocks below;
// the suites build them through `createQuery` / `createFilter`, so importing the
// classes themselves left two unused bindings (TS6133) the moment this file
// entered a tsc program (#5449).
import { ObjectStackClient, createQuery, createFilter } from './index';

/** Helper: create a client with mocked fetch that returns the given response body */
function createMockClient(body: any, status = 200) {
Expand Down Expand Up @@ -103,7 +107,11 @@ describe('ObjectStackClient', () => {

const result = await client.meta.getItem('object', 'customer');
expect(fetchMock).toHaveBeenCalledWith('http://localhost:3000/api/v1/meta/object/customer', expect.any(Object));
expect(result.name).toBe('customer');
// `meta.getItem` has no declared return type (unlike the `getItems`
// beside it — #5545), so its unwrapped payload is `unknown`. Asserted
// structurally rather than cast: same assertion strength, without
// pretending this surface is typed (#5449).
expect(result).toMatchObject({ name: 'customer' });
});

it('meta.getView speaks the path-param dialect both surfaces accept (#3611)', async () => {
Expand Down Expand Up @@ -1280,7 +1288,12 @@ describe('ScopedProjectClient', () => {

it('throws when environmentId is missing', () => {
const client = new ObjectStackClient({ baseUrl: 'http://localhost:3000' });
// @ts-expect-error — empty string rejected at runtime
// No `@ts-expect-error` here, and that is the finding of #5449 rather than
// an omission. `project(environmentId: string)` accepts `''` — it is a
// perfectly good `string` — so the directive that sat on this line
// suppressed nothing and reported TS2578 ("unused") the first time a tsc
// program read the file. Its own comment said what the test actually
// proves: the empty id is rejected at RUNTIME, by the guard below.
expect(() => client.project('')).toThrow(/environmentId is required/);
});

Expand Down
8 changes: 8 additions & 0 deletions packages/client/test-typecheck-debt.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"_comment": "Per-file tsc error debt of the @objectstack/client TEST layer (#5286). `tsconfig.test.json` compiles `src/**/*.test.ts` — which `tsconfig.json` excludes and therefore no gate ever read — and every file below still carries errors from before that gate existed, almost all of them fixture literals annotated with a schema OUTPUT type (`z.infer`) while holding an authored INPUT literal. EXACT ratchet, judged by re-running tsc: a file that gains errors is red, a file that loses them is red until its number is re-recorded, a file that reaches zero is red until its entry is deleted, and a file NOT listed here may have no errors at all. Regenerate with: pnpm --filter @objectstack/client gen:test-typecheck-debt",
"entries": {
"src/client.batch-transaction.test.ts": 3,
"src/client.environment-scoping.test.ts": 1,
"src/client.hono.test.ts": 2
}
}
62 changes: 62 additions & 0 deletions packages/client/tsconfig.test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// The TEST-layer type-check program (#5449, the mechanism #5286/PR #5478 set
// for `packages/spec`). `tsconfig.json` above stays as it is: it is the BUILD
// config, and its `**/*.test.ts` exclusion has a reason — ci.yml gates that no
// test file reaches the published artifact. This sibling puts the excluded
// layer back in front of tsc, and `package.json`'s `typecheck` script NAMES it
// (`-p tsconfig.test.json`), because a config no script invokes is exactly the
// phantom this whole change is about.
//
// What differs from the build config, and what deliberately does NOT:
// - module semantics ONLY. The tests are written and executed as ESM by
// vitest (esbuild/vite), while `client` has no `"type": "module"`, so the
// build config's NodeNext compiles them as CJS and reports errors about the
// CHECK rather than the code (TS2835 extensionless relative imports, TS1470
// `import.meta`, TS2550 lib). Matching vitest is fidelity.
// - `rootDir` widens to the workspace root. It steers emit layout only, and
// this program emits nothing; inherited as `./src` it reported TS6059 for
// the four route-ledger modules `client-url-conformance.test.ts` and the
// three `*-route-ledger-coverage.test.ts` files deep-import from sibling
// packages (`../../runtime/src/route-ledger`, …). Those five TS6059 are the
// bulk of this package's stale TEST_DEBT entry — a measurement of the
// misconfigured check, not of the tests.
// - STRICTNESS IS UNTOUCHED. `strict`, `noUnusedLocals`, `noUnusedParameters`,
// `noImplicitReturns` and the rest are inherited from the root config.
// Nothing here may loosen a type rule; if a test does not compile, that is
// the finding.
//
// `include` deliberately stops at `src`, matching the build config's root, and
// none of the files it leaves out carries a `@ts-expect-error`, so no pin is
// hiding there. `tests/integration/` — the suite `vitest.integration.config.ts`
// runs against a live server — is in no tsconfig at all: a second,
// differently-shaped hole (1 file / 3 errors, one of them a real API drift, the
// suite reading a `client.discovery` property `ObjectStackClient` does not
// have) that wants its own change rather than a rider on this one. Filed as
// #5544.
//
// The per-file ledger beside this config (`test-typecheck-debt.json`) is small
// on purpose. Under the repaired config the whole test layer came to 13 errors;
// eight were the tests' own and are fixed in this same change (two unused
// imports, an unused parameter, two possibly-undefined reads, an `unknown`
// payload asserted structurally, a `reference_to` key the field schema never
// had, and the phantom pin itself). Re-spelling that key uncovered one more of
// the remaining kind, and all six that stay are ONE producer-side defect
// wearing three files' clothes: objectql's `registerObject` takes the schema's
// OUTPUT type (`z.infer`) where it should take the INPUT one, so a perfectly
// good authored literal reads as missing nine defaulted keys (#5543). Holding
// them EXACT and shrink-only means fixing #5543 turns the ledger red until the
// entries are deleted, instead of letting it rot. Every file NOT listed there —
// `client.test.ts`, the pin file, first among them — must have no errors at
// all.
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true,
"rootDir": "../..",
"module": "esnext",
"moduleResolution": "bundler",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"types": ["node"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
4 changes: 2 additions & 2 deletions packages/spec/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,8 @@
"check:react-blocks": "tsx scripts/build-react-blocks-contract.ts --check",
"check:react-declaration-parity": "tsx scripts/check-react-blocks-declaration-parity.ts",
"check:skill-examples": "tsx scripts/check-skill-examples.ts",
"check:test-typecheck": "tsx scripts/check-test-typecheck.mts --self-test && tsx scripts/check-test-typecheck.mts --project tsconfig.test.json",
"gen:test-typecheck-debt": "tsx scripts/check-test-typecheck.mts --update --project tsconfig.test.json",
"check:test-typecheck": "tsx ../../scripts/check-test-typecheck.mts --self-test && tsx ../../scripts/check-test-typecheck.mts --package packages/spec --project tsconfig.test.json",
"gen:test-typecheck-debt": "tsx ../../scripts/check-test-typecheck.mts --update --package packages/spec --project tsconfig.test.json",
"typecheck": "tsc --noEmit && pnpm check:test-typecheck"
},
"keywords": [
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading