jaydenseric / extract-files

A function to recursively extract files and their object paths within a value, replacing them with null in a deep clone without mutating the original value. FileList instances are treated as File instance arrays. Files are typically File and Blob instances.
https://npm.im/extract-files
MIT License
56 stars 23 forks source link

Call signatures are incompatible #33

Closed Juknum closed 7 months ago

Juknum commented 1 year ago

Hi, I have the following issue when trying to use your library :

Call signature return types 'Extraction<ExtractableFile>' and '{ clone: Body; files: Map<Extractable, ObjectPath[]>; }' are incompatible.
    The types of 'clone' are incompatible between these types.
      Type 'unknown' is not assignable to type 'Body'.

Formatted:

Capture d’écran 2023-05-14 à 20 48 36

Here is the code I'm using :

import { NgModule } from '@angular/core';
import { ApolloModule, APOLLO_OPTIONS } from 'apollo-angular';
import { HttpLink } from 'apollo-angular/http';
import { ApolloLink, InMemoryCache } from '@apollo/client/core';
import { setContext } from '@apollo/client/link/context';

import { environment } from 'src/environments/environment';
import { HttpClientModule } from '@angular/common/http';

import extractFiles from 'extract-files/extractFiles.mjs';
import isExtractableFile from 'extract-files/isExtractableFile.mjs';

export function createApollo(httpLink: HttpLink) {
  // ...

  const link = ApolloLink.from([
    auth, 
    httpLink.create({
      uri: environment.API_URL,
      extractFiles: (body) => extractFiles(body, isExtractableFile),
    }),
  ]);

  const cache = new InMemoryCache();

  return {
    link,
    cache,
  };
}

@NgModule({
  exports: [HttpClientModule, ApolloModule],
  providers: [
    {
      provide: APOLLO_OPTIONS,
      useFactory: createApollo,
      deps: [HttpLink],
    },
  ],
})
export class GraphQLModule {}

My tsconfig file :

{
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "noImplicitOverride": true,
    "noPropertyAccessFromIndexSignature": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "sourceMap": true,
    "declaration": false,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "moduleResolution": "node",
    "importHelpers": true,
    "target": "ES2022",
    "module": "ESNext",
    "allowJs": true,
    "maxNodeModuleJsDepth": 10,
    "skipLibCheck": true,
    "useDefineForClassFields": false,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "lib": [
      "ES2022",
      "dom",
      "esnext.asynciterable"
    ]
  },
}

I don't know if this issue is better here or in the apollo-angular repository, so I've made it here first :p

jaydenseric commented 1 year ago

Do you still have the same issue if you configure TypeScript as per the documented requirements?

https://github.com/jaydenseric/extract-files/tree/v13.0.0#requirements

PatrickJung94 commented 1 year ago

I had the exact same issue. I solved it by reverting extract-files to version 11 and doing the following in the app.module.ts.

Versions in my package.json:

{
  "dependencies": {
    // ...
    "@apollo/client": "^3.0.0",
    "apollo-angular": "^4.2.1",
    "graphql": "^16",
    "extract-files": "^11.0.0"
  }
}
import { NgModule } from '@angular/core';
import { ApolloModule, APOLLO_OPTIONS } from 'apollo-angular';
import { ApolloClientOptions, InMemoryCache } from '@apollo/client/core';
import { HttpLink } from 'apollo-angular/http';
import { environment } from 'src/environments/environment';
import { extractFiles, isExtractableFile } from 'extract-files';

const uri = environment.api; // <-- add the URL of the GraphQL server here
export function createApollo(httpLink: HttpLink): ApolloClientOptions<any> {
  return {
    link: httpLink.create({uri, extractFiles: (body) => extractFiles(body,'', isExtractableFile)}),
    cache: new InMemoryCache(),
  };
}

@NgModule({
  exports: [ApolloModule],
  providers: [
    {
      provide: APOLLO_OPTIONS,
      useFactory: createApollo,
      deps: [HttpLink],
    },
  ],
})

Also of course adding useMultipart: true to the context of the mutation/query.

this.uploadSomeFile.mutate({
    somefile: input.file,
  },{
    context: {
      useMultipart: true
    }
  }).subscribe((result) => {
    // do something with result
  });

Because of some typing issue I couldn't resolve this with the newest version of extract-files which is currently version 13. Maybe someone else could figure out a solution for extract-files@13.

jaydenseric commented 7 months ago

I haven't encountered this issue in any of my TypeScript projects yet, and I can't really intuit what is going wrong from the example code and error messages that have been shared.

If someone figures out specifically what causes this problem, please share here. If it's something I can fix in extract-files we can re-open.

philenius commented 3 months ago

I had the same issue as @Juknum with Angular 18 even though I updated my tsconfig.json according to the instructions in the README. The solution https://github.com/jaydenseric/extract-files/issues/33#issuecomment-1547393034 by @PatrickJung94 with version 11.0.0 of extract-files helped me so far that I could at least compile my Angular app again.

However, the HTTP requests for my file uploads to my GraphQL backend were completely broken. The HTTP payload contained the TypeScript code of the isExtractableFile function instead of its return value :grinning: :question:

------WebKitFormBoundaryNDnBvELTdMD0NpTD
Content-Disposition: form-data; name="operations"
{"operationName":"UploadSingleFile","variables":{"file":null},"query":"mutation UploadSingleFile($file: Upload!) {\n  uploadSingleFile(file: $file)\n}"}
------WebKitFormBoundaryNDnBvELTdMD0NpTD
Content-Disposition: form-data; name="map"
{"1":["function isExtractableFile(value) {\n      return typeof File !== \"undefined\" && value instanceof File || typeof Blob !== \"undefined\" && value instanceof Blob || value instanceof ReactNativeFile;\n    }.variables.file"]}
------WebKitFormBoundaryNDnBvELTdMD0NpTD
Content-Disposition: form-data; name="1"; filename="test.png"
Content-Type: image/png
------WebKitFormBoundaryNDnBvELTdMD0NpTD--

I ended up with a dumb workaround of copying the entire code of this library and pasting it into my Angular app. This way, I was able to revert all changes to tsconfig.json, remove the dependency on this library, no more issues with module: Node16 or module: NodeNext nor with Angular Material. Now, the HTTP payload looks like expected and the file upload works:

------WebKitFormBoundaryNDnBvELTdMD0NpTD
Content-Disposition: form-data; name="operations"
{"operationName":"UploadSingleFile","variables":{"file":null},"query":"mutation UploadSingleFile($file: Upload!) {\n  uploadSingleFile(file: $file)\n}"}
------WebKitFormBoundaryNDnBvELTdMD0NpTD
Content-Disposition: form-data; name="map"
{"1":["variables.file"]}
------WebKitFormBoundarysaKXxZCOshF6g23c
Content-Disposition: form-data; name="1"; filename="test.png"
Content-Type: image/png
------WebKitFormBoundaryNDnBvELTdMD0NpTD--

Edit: I also had to add DOM.Iterable to my tsconfig.json as mentioned in https://github.com/jaydenseric/extract-files/issues/34#issuecomment-1805835614