diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..9d581fbc --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +.context/ diff --git a/example/convex/test/scenarios/enqueueOcc.ts b/example/convex/test/scenarios/enqueueOcc.ts index e556eb7b..e7ee59c7 100644 --- a/example/convex/test/scenarios/enqueueOcc.ts +++ b/example/convex/test/scenarios/enqueueOcc.ts @@ -121,13 +121,25 @@ export const storm = internalAction({ blockerMs = 60_000, }, ) => { - if (!Number.isInteger(durationMs) || durationMs < 1_000 || durationMs > 120_000) { + if ( + !Number.isInteger(durationMs) || + durationMs < 1_000 || + durationMs > 120_000 + ) { throw new Error("durationMs must be an integer from 1,000 to 120,000"); } - if (!Number.isInteger(concurrency) || concurrency < 1 || concurrency > 100) { + if ( + !Number.isInteger(concurrency) || + concurrency < 1 || + concurrency > 100 + ) { throw new Error("concurrency must be an integer from 1 to 100"); } - if (!Number.isInteger(payloadBytes) || payloadBytes < 0 || payloadBytes > 500_000) { + if ( + !Number.isInteger(payloadBytes) || + payloadBytes < 0 || + payloadBytes > 500_000 + ) { throw new Error("payloadBytes must be an integer from 0 to 500,000"); } diff --git a/src/client/index.test.ts b/src/client/index.test.ts new file mode 100644 index 00000000..c6bda3c3 --- /dev/null +++ b/src/client/index.test.ts @@ -0,0 +1,69 @@ +import { + makeFunctionReference, + type GenericDataModel, + type GenericMutationCtx, +} from "convex/server"; +import { type Infer, v } from "convex/values"; +import { expectTypeOf, test } from "vitest"; +import { + type EnqueueOptions, + type OnCompleteArgs, + type WorkId, + type Workpool, + vOnCompleteArgs, +} from "./index.js"; + +type MutationCtx = GenericMutationCtx; +const action = makeFunctionReference<"action", { value: number }, number>( + "work:action", +); +const onComplete = makeFunctionReference< + "mutation", + OnCompleteArgs<{ label: string }, number> +>("work:onComplete"); + +test("onComplete retains context and return value types", () => { + // tsc checks these call sites; the function is never invoked at runtime. + expectTypeOf((pool: Workpool, ctx: MutationCtx) => { + void pool.enqueueAction( + ctx, + action, + { value: 1 }, + // @ts-expect-error The callback requires a string label. + { onComplete, context: { label: 1 } }, + ); + const stringAction = makeFunctionReference< + "action", + { value: number }, + string + >("work:stringAction"); + void pool.enqueueAction( + ctx, + // @ts-expect-error The callback requires a number result. + stringAction, + { value: 1 }, + { onComplete, context: { label: "job" } }, + ); + return pool.enqueueAction( + ctx, + action, + { value: 1 }, + { onComplete, context: { label: "job" } }, + ); + }).returns.toEqualTypeOf>(); +}); + +test("completion validators preserve the declared context and result", () => { + const _args = vOnCompleteArgs(v.object({ label: v.string() }), v.number()); + type Args = Infer; + expectTypeOf().toEqualTypeOf<{ label: string }>(); + expectTypeOf< + Extract["returnValue"] + >().toEqualTypeOf(); +}); + +test("runAt and runAfter are mutually exclusive", () => { + // @ts-expect-error Only one scheduling option can be specified. + const options: EnqueueOptions = { runAt: 1, runAfter: 2 }; + expectTypeOf(options).toMatchTypeOf(); +}); diff --git a/vitest.config.js b/vitest.config.js deleted file mode 100644 index e9408f8b..00000000 --- a/vitest.config.js +++ /dev/null @@ -1,10 +0,0 @@ -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - test: { - environment: "edge-runtime", - typecheck: { - tsconfig: "./tsconfig.test.json", - }, - }, -}); diff --git a/vitest.config.mts b/vitest.config.mts index 20a35a36..e4ca180c 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -3,7 +3,6 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { environment: "edge-runtime", - exclude: ["**/node_modules/**", "dist/**"], - // globals: true, + include: ["src/**/*.test.ts", "example/convex/**/*.test.ts"], }, });