Repository navigation
fix(database): resolve a table update through its type to the modified count - #65
Merged
Merged
Conversation
…d count Reading the result of `update` through its type (`ReturnType<typeof table.update>`) took the last overload, whose conditional return resolved to `any`. The overloads are reordered so that the document overload, which returns `Query<number>`, is the one a type-level read sees. Calls type exactly as before.
Upd4ting
marked this pull request as ready for review
November 2, 2026 21:13
Upd4ting
added a commit
to AntelopeJS/dms
that referenced
this pull request
Nov 2, 2026
…ped write result (#694) Pins interface-database 1ade334 (AntelopeJS/interface-database#65).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
dms burns down its guardrail ratchet. Its row
packages/interface-dms/src/auth/db/models/sessions.model.ts:27(no-unsafe-assignment) comes from reading the result of a table update through its type,Awaited<ReturnType<ReturnType<SessionModel["table"]["update"]>["run"]>>. That type resolves toany, althoughupdatereturnsQuery<number>. The goal is to type that write result honestly, so the consumer clears the row without a cast or a directive.Cause and change
SingleSelection.updateandSelection.updatehave two overloads. A type-level read (ReturnType<typeof x.update>) takes the last overload, here(document: (val: ValueProxy<T>) => U) => ExtractType<U> extends DeepPartial<T> ? Query<number> : never.Uis not inferable there, so it resolves tounknown, the conditional is unresolved and the result reads asany.The overloads are reordered: the updater overload first, the plain document overload (
Query<number>) last. The two JSDoc blocks are split to match. No signature text changes.Behaviour change
None at runtime. Calls resolve the same overload as before: a function argument matches only the updater overload, an object argument only the document overload. The
.d.tsdiffers only by the order of the twoupdatelines (api-diff below). A caller that readsReturnType<typeof x.update>now getsQuery<number>, soawait ...run()isnumberinstead ofany. A caller that relied onanythere and did not handlenumberfails to compile; the consumers were proven (see below).Tests
src/tests/update_result.test.tspins the types withExact<A, B>, a new test helper that is false when either side isany. Before the changetsc --noEmitfails on both type assertions (Type 'true' is not assignable to type 'false'); after it, it passes.pnpm test: 212 passing.Evidence
pnpm run checks(GITHUB_BASE_REF=campaign/integration):Checks: 8 passed, 0 failed.updatelines inSelectionandSingleSelectiononly. Suppressions unchanged.api-diff output
API: differences against origin/campaign/integration (committed HEAD).
diff --git a/base/_root/exports/root.d.ts b/head/_root/exports/root.d.ts
index 1cdef82..0fa7cd4 100644
--- a/base/_root/exports/root.d.ts
+++ b/head/_root/exports/root.d.ts
@@ -440,16 +440,16 @@ export declare class SchemaInstance extends StagedObject {
export declare class Selection extends Stream {
delete(): Query;
replace(document: DeepPartial & object): Query;
update(document: (val: ValueProxy) => U): ExtractType extends DeepPartial ? Query : never;
}
type SelectionKey = boolean | number | string;
export declare class SingleSelection extends Datum {
changes(): Query<Changes[]>;
delete(): Query;
replace(document: DeepPartial & object): Query;
update(document: (val: ValueProxy) => U): ExtractType extends DeepPartial ? Query : never;
}
type StageCall = QueryStageMap[Name]["args"] extends [
] ? undefined extends QueryStageMap[Name]["options"] ? [
diff --git a/base/_root/exports/subpath/schema.d.ts b/head/_root/exports/subpath/schema.d.ts
index e0af7f7..0b74e8f 100644
--- a/base/_root/exports/subpath/schema.d.ts
+++ b/head/_root/exports/subpath/schema.d.ts
@@ -427,16 +427,16 @@ export declare const Schemas: RegisteringProxy<(name: string, def: SchemaDefinit
declare class Selection extends Stream {
delete(): Query;
replace(document: DeepPartial & object): Query;
update(document: (val: ValueProxy) => U): ExtractType extends DeepPartial ? Query : never;
}
type SelectionKey = boolean | number | string;
declare class SingleSelection extends Datum {
changes(): Query<Changes[]>;
delete(): Query;
replace(document: DeepPartial & object): Query;
update(document: (val: ValueProxy) => U): ExtractType extends DeepPartial ? Query : never;
}
type StageCall = QueryStageMap[Name]["args"] extends [
] ? undefined extends QueryStageMap[Name]["options"] ? [
diff --git a/base/_root/exports/subpath/selection.d.ts b/head/_root/exports/subpath/selection.d.ts
index 1ac66c8..7e04c3c 100644
--- a/base/_root/exports/subpath/selection.d.ts
+++ b/head/_root/exports/subpath/selection.d.ts
@@ -383,16 +383,16 @@ interface Recorded<Options, Args extends readonly unknown[]> {
export declare class Selection extends Stream {
delete(): Query;
replace(document: DeepPartial & object): Query;
update(document: (val: ValueProxy) => U): ExtractType extends DeepPartial ? Query : never;
}
type SelectionKey = boolean | number | string;
export declare class SingleSelection extends Datum {
changes(): Query<Changes[]>;
delete(): Query;
replace(document: DeepPartial & object): Query;
update(document: (val: ValueProxy) => U): ExtractType extends DeepPartial ? Query : never;
}
type StageCall = QueryStageMap[Name]["args"] extends [
] ? undefined extends QueryStageMap[Name]["options"] ? [
diff --git a/base/_root/exports/subpath/staged-query.d.ts b/head/_root/exports/subpath/staged-query.d.ts
index 66eee0c..b7a0d6f 100644
--- a/base/_root/exports/subpath/staged-query.d.ts
+++ b/head/_root/exports/subpath/staged-query.d.ts
@@ -428,16 +428,16 @@ export declare class SchemaInstance extends StagedObject {
export declare class Selection extends Stream {
delete(): Query;
replace(document: DeepPartial & object): Query;
update(document: (val: ValueProxy) => U): ExtractType extends DeepPartial ? Query : never;
}
export type SelectionKey = boolean | number | string;
export declare class SingleSelection extends Datum {
changes(): Query<Changes[]>;
delete(): Query;
replace(document: DeepPartial & object): Query;
update(document: (val: ValueProxy) => U): ExtractType extends DeepPartial ? Query : never;
}
type StageCall = QueryStageMap[Name]["args"] extends [
] ? undefined extends QueryStageMap[Name]["options"] ? [
diff --git a/base/_root/main.d.ts b/head/_root/main.d.ts
index 1cdef82..0fa7cd4 100644
--- a/base/_root/main.d.ts
+++ b/head/_root/main.d.ts
@@ -440,16 +440,16 @@ export declare class SchemaInstance extends StagedObject {
export declare class Selection extends Stream {
delete(): Query;
replace(document: DeepPartial & object): Query;
update(document: (val: ValueProxy) => U): ExtractType extends DeepPartial ? Query : never;
}
type SelectionKey = boolean | number | string;
export declare class SingleSelection extends Datum {
changes(): Query<Changes[]>;
delete(): Query;
replace(document: DeepPartial & object): Query;
update(document: (val: ValueProxy) => U): ExtractType extends DeepPartial ? Query : never;
}
type StageCall = QueryStageMap[Name]["args"] extends [
] ? undefined extends QueryStageMap[Name]["options"] ? [
diff --git a/base/_root/types.d.ts b/head/_root/types.d.ts
index 1cdef82..0fa7cd4 100644
--- a/base/_root/types.d.ts
+++ b/head/_root/types.d.ts
@@ -440,16 +440,16 @@ export declare class SchemaInstance extends StagedObject {
export declare class Selection extends Stream {
delete(): Query;
replace(document: DeepPartial & object): Query;
update(document: (val: ValueProxy) => U): ExtractType extends DeepPartial ? Query : never;
}
type SelectionKey = boolean | number | string;
export declare class SingleSelection extends Datum {
changes(): Query<Changes[]>;
delete(): Query;
replace(document: DeepPartial & object): Query;
update(document: (val: ValueProxy) => U): ExtractType extends DeepPartial ? Query : never;
}
type StageCall = QueryStageMap[Name]["args"] extends [
] ? undefined extends QueryStageMap[Name]["options"] ? [
diff --git a/base/_root/typesVersions/%2A/schema/0.d.ts b/head/_root/typesVersions/%2A/schema/0.d.ts
index e0af7f7..0b74e8f 100644
--- a/base/_root/typesVersions/%2A/schema/0.d.ts
+++ b/head/_root/typesVersions/%2A/schema/0.d.ts
@@ -427,16 +427,16 @@ export declare const Schemas: RegisteringProxy<(name: string, def: SchemaDefinit
declare class Selection extends Stream {
delete(): Query;
replace(document: DeepPartial & object): Query;
update(document: (val: ValueProxy) => U): ExtractType extends DeepPartial ? Query : never;
}
type SelectionKey = boolean | number | string;
declare class SingleSelection extends Datum {
changes(): Query<Changes[]>;
delete(): Query;
replace(document: DeepPartial & object): Query;
update(document: (val: ValueProxy) => U): ExtractType extends DeepPartial ? Query : never;
}
type StageCall = QueryStageMap[Name]["args"] extends [
] ? undefined extends QueryStageMap[Name]["options"] ? [
diff --git a/base/_root/typesVersions/%2A/selection/0.d.ts b/head/_root/typesVersions/%2A/selection/0.d.ts
index 1ac66c8..7e04c3c 100644
--- a/base/_root/typesVersions/%2A/selection/0.d.ts
+++ b/head/_root/typesVersions/%2A/selection/0.d.ts
@@ -383,16 +383,16 @@ interface Recorded<Options, Args extends readonly unknown[]> {
export declare class Selection extends Stream {
delete(): Query;
replace(document: DeepPartial & object): Query;
update(document: (val: ValueProxy) => U): ExtractType extends DeepPartial ? Query : never;
}
type SelectionKey = boolean | number | string;
export declare class SingleSelection extends Datum {
changes(): Query<Changes[]>;
delete(): Query;
replace(document: DeepPartial & object): Query;
update(document: (val: ValueProxy) => U): ExtractType extends DeepPartial ? Query : never;
}
type StageCall = QueryStageMap[Name]["args"] extends [
] ? undefined extends QueryStageMap[Name]["options"] ? [
diff --git a/base/_root/typesVersions/%2A/staged-query/0.d.ts b/head/_root/typesVersions/%2A/staged-query/0.d.ts
index 66eee0c..b7a0d6f 100644
--- a/base/_root/typesVersions/%2A/staged-query/0.d.ts
+++ b/head/_root/typesVersions/%2A/staged-query/0.d.ts
@@ -428,16 +428,16 @@ export declare class SchemaInstance extends StagedObject {
export declare class Selection extends Stream {
delete(): Query;
replace(document: DeepPartial & object): Query;
update(document: (val: ValueProxy) => U): ExtractType extends DeepPartial ? Query : never;
}
export type SelectionKey = boolean | number | string;
export declare class SingleSelection extends Datum {
changes(): Query<Changes[]>;
delete(): Query;
replace(document: DeepPartial & object): Query;
update(document: (val: ValueProxy) => U): ExtractType extends DeepPartial ? Query : never;
}
type StageCall = QueryStageMap[Name]["args"] extends [
] ? undefined extends QueryStageMap[Name]["options"] ? [
API: skipped .agents/skills/verify-interface, private.
Runtime API: 1 packages, 11 entries unchanged against origin/campaign/integration (committed HEAD).
No behavioural probe ran.
Suppressions in authored code files (origin/campaign/integration and committed HEAD). A lint directive counts once per rule it names;
*is a directive naming none.@typescript-eslint/no-unused-expressionsanti-slop/no-chained-type-assertionstypescript/no-unnecessary-type-parameterstypescript/no-unused-varsunicorn/no-thenable@ts-expect-error@ts-ignoreSAFETY:commentsCorrection (verifier, after landing)
Not every call resolves the same overload as before. A call whose argument is typed
any, such astable.update(anyValue), now resolves tonever, so.run()on it fails to compile (TS2339). No proven consumer does this. It goes in the release notes as a compile-time break forany-typed writes.