Skip to content

Commit f59c210

Browse files
committed
fix(experimental): widen nested and merge-var used fns via ApplyOn
So c.db.createUser picks up mounted v.extend the same way top-level used fns do.
1 parent 56bc136 commit f59c210

2 files changed

Lines changed: 105 additions & 2 deletions

File tree

packages/experimental/src/fn.test.ts

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { describe, expect, expectTypeOf, it } from "vitest";
2-
import { FnError, UnexpectedError, ValidationError, v } from "./index";
2+
import {
3+
FnError,
4+
memoryAdapter,
5+
UnexpectedError,
6+
ValidationError,
7+
v,
8+
} from "./index";
39

410
const session = v.var("fnt_session", {
511
default: null as { userId: string } | null,
@@ -655,6 +661,75 @@ describe("fn schema var widening", () => {
655661
.toEqualTypeOf<{ id: string }>();
656662
})();
657663
});
664+
665+
it("a nested namespace used fn widens the same way as a top-level one", () => {
666+
const nestedUser = v.var("fnt_nested_user", {
667+
default: null,
668+
schema: v.object({ id: v.string() }),
669+
});
670+
const withEmail = v.extend(nestedUser, { email: v.string() });
671+
const createUser = v.fn(
672+
"fnt.ns.create",
673+
{ input: nestedUser },
674+
(c) => c.input,
675+
);
676+
v.fn({ use: [{ db: { createUser } }, { withEmail }] as const }, (c) => {
677+
expectTypeOf(c.db.createUser).parameter(0).toEqualTypeOf<{
678+
id: string;
679+
email: string;
680+
}>();
681+
});
682+
});
683+
684+
it("a merge-var helper widens against mounted v.extend", () => {
685+
const mergeUser = v.var("fnt_merge_user", {
686+
default: null,
687+
schema: v.object({ id: v.string() }),
688+
});
689+
const mergeAccount = v.var("fnt_merge_account", {
690+
default: null,
691+
schema: v.object({ id: v.string(), userId: v.string() }),
692+
});
693+
const userWithEmail = v.extend(mergeUser, { email: v.string() });
694+
const accountWithPassword = v.extend(mergeAccount, {
695+
password: v.string(),
696+
});
697+
const store = v.storage(memoryAdapter(), {
698+
user: mergeUser,
699+
account: mergeAccount,
700+
});
701+
const db = v.var("db", { merge: true, default: store });
702+
const createUser = v.fn(
703+
"db.create_user",
704+
{ input: mergeUser, use: [{ db, user: mergeUser }] },
705+
async (c) => c.input,
706+
);
707+
const createAccount = v.fn(
708+
"db.create_account",
709+
{ input: mergeAccount, use: [{ db, account: mergeAccount }] },
710+
async (c) => c.input,
711+
);
712+
v.fn(
713+
{
714+
use: [
715+
{ user: mergeUser, db: { createUser } },
716+
{ account: mergeAccount, db: { createAccount } },
717+
{ db, userWithEmail, accountWithPassword },
718+
] as const,
719+
},
720+
async (c) => {
721+
expectTypeOf(c.db.createUser).parameter(0).toEqualTypeOf<{
722+
id: string;
723+
email: string;
724+
}>();
725+
expectTypeOf(c.db.createAccount).parameter(0).toEqualTypeOf<{
726+
id: string;
727+
userId: string;
728+
password: string;
729+
}>();
730+
},
731+
);
732+
});
658733
});
659734

660735
describe("declared errors", () => {

packages/experimental/src/module.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -748,7 +748,31 @@ export type ExtendedArgs<PL, K extends string> = UnionToIntersection<
748748
* {@link InputVarExtra}. Storage members get the same scope rewrite as a
749749
* db var's value ({@link WidenSchemaFns}): collections re-resolve
750750
* row/`Where` types against mounted `v.extend` / customize.
751+
*
752+
* Walks nested module GROUPS and merge-var helper intersections
753+
* (`{ db: { createUser } }` / `VarDef & { createUser }`) so
754+
* `c.db.createUser` widens the same way as a top-level `c.createUser`.
751755
*/
756+
type VarSurfaceKey =
757+
| "$var"
758+
| "name"
759+
| "default"
760+
| "schema"
761+
| "type"
762+
| "$source"
763+
| "$attrs"
764+
| "$merge"
765+
| "customize";
766+
767+
/** Widen only same-key helpers on a merge var; leave the VarDef surface. */
768+
type ApplyOnVarHelpers<V, PL> = [Exclude<keyof V, VarSurfaceKey>] extends [
769+
never,
770+
]
771+
? V
772+
: V & {
773+
[K in Exclude<keyof V, VarSurfaceKey>]: ApplyOn<V[K], PL>;
774+
};
775+
752776
export type ApplyOn<F, PL> = F extends StorageLike
753777
? WidenSchemaFns<F, PL>
754778
: F extends FnDefination<
@@ -777,7 +801,11 @@ export type ApplyOn<F, PL> = F extends StorageLike
777801
W,
778802
O
779803
>
780-
: F;
804+
: F extends { $var: true }
805+
? ApplyOnVarHelpers<F, PL>
806+
: [GroupMember<F>] extends [never]
807+
? F
808+
: { [P in keyof F]: ApplyOn<F[P], PL> };
781809

782810
export type ApplyOns<Fns, PL> = { [P in keyof Fns]: ApplyOn<Fns[P], PL> };
783811

0 commit comments

Comments
 (0)