Search code examples
typescriptsqlitenext.jstypeorm

Where is the circular dependency error here?


Error:

ReferenceError: Cannot access 'Painting' before initialization
at Module.Painting (webpack-internal:///(api)/./src/entities/Painting.ts:4:55)
at eval (webpack-internal:///(api)/./src/entities/ExhibitPaintingDetail.ts:84:135)

Painting:

import {
  Entity,
  PrimaryGeneratedColumn,
  Column,
  ManyToMany,
  JoinTable,
  JoinColumn,
  OneToMany,
  ManyToOne,
  Repository,
  RelationId,
} from "typeorm";

import { ExhibitPaintingDetail } from "./ExhibitPaintingDetail";
import { Tag } from "./Tag";

@Entity("painting")
export class Painting {
  @PrimaryGeneratedColumn()
  id: number;

  @Column("text")
  title: string;

  @Column("text")
  picture: string;

  @Column("text")
  description: string;

  @Column("integer")
  artistId: number;

  @Column("integer")
  month: number;

  @Column("integer")
  year: number;

  @Column("text")
  media: string;

  @Column("integer")
  price: number;

  @Column("integer")
  width: number;

  @Column("integer")
  height: number;

  @Column("integer")
  sold: number;

  @Column({ type: "integer", nullable: true })
  depth: number;

  @Column({ type: "text", nullable: true })
  note: string;

  @Column({ type: "integer", nullable: true })
  api_id: number;

  @Column({ type: "text", nullable: true })
  permalink: string;

  @Column("date", { nullable: true })
  timestamp: Date;

  @Column({ type: "boolean", nullable: false, default: true })
  isenabled: boolean;

  @Column({ type: "text", nullable: true })
  filename: string;

  @Column({ type: "text", nullable: true })
  filenamepath: string;

  // @ManyToOne(() => Person, (person) => person.paintings)
  // @JoinColumn({ name: "artistId" })
  // person: Promise<Person>;

  @ManyToMany(() => Tag, (tag) => tag.paintings)
  @JoinTable({
    name: "tagpaintings", // name of the join table
    joinColumn: {
      name: "paintingid", // name of the column in the join table that references the Painting entity
      referencedColumnName: "id", // name of the id column in the Painting entity
    },
    inverseJoinColumn: {
      name: "tagid", // name of the column in the join table that references the Tag entity
      referencedColumnName: "id", // name of the id column in the Tag entity
    },
  })
  tags: Tag[];

  @OneToMany(
    () => ExhibitPaintingDetail,
    (exhibitPaintingDetail) => exhibitPaintingDetail.painting,
    { lazy: true }
  ) // Keep this!
  exhibitPaintingDetails: ExhibitPaintingDetail[];

  // Add this to get the related ids
  @RelationId(
    (exhibitPaintingDetail: ExhibitPaintingDetail) =>
      exhibitPaintingDetail.exhibit
  )
  exhibitids: number[];
}

Exhibit:

import {
  Entity,
  PrimaryGeneratedColumn,
  Column,
  ManyToOne,
  OneToOne,
  OneToMany,
  JoinColumn,
  RelationId,
} from "typeorm";
import { Gallery } from "./Gallery";
import { ExhibitPaintingDetail } from "./ExhibitPaintingDetail";
import { ExhibitNameTemplate } from "./ExhibitNameTemplate";

@Entity("exhibit")
export class Exhibit {
  @PrimaryGeneratedColumn()
  id?: number;

  @Column("text")
  name: string;

  @Column("text", { nullable: true })
  description: string;

  @Column("integer", { default: 1 })
  isenabled?: number;

  @Column("date", { nullable: true })
  startDate: string;

  @Column("date", { nullable: true })
  endDate: string;

  @Column("date", { nullable: true })
  dropOffDate: string;

  @Column("date", { nullable: true })
  pickUpDate: string;

  @Column("integer", { nullable: true })
  galleryId: number;

  @Column("text", { nullable: true })
  note: string;

  @Column("date", { nullable: true })
  decisionDate?: string;

  @Column("text", { nullable: true })
  website: string;

  @OneToOne(
    () => ExhibitNameTemplate,
    (exhibitNameTemplate) => exhibitNameTemplate.exhibit
  )
  exhibitNameTemplate: ExhibitNameTemplate | null;

  @ManyToOne(() => Gallery, (gallery) => gallery.exhibits, {})
  @JoinColumn({ name: "galleryId" })
  gallery: Promise<Gallery>;

  @OneToMany(
    () => ExhibitPaintingDetail,
    (exhibitPaintingDetail) => exhibitPaintingDetail.exhibit,
    { lazy: true }
  ) // Keep this!
  exhibitPaintingDetails: ExhibitPaintingDetail[];

  // Add this to get the related ids
  @RelationId(
    (exhibitPaintingDetail: ExhibitPaintingDetail) =>
      exhibitPaintingDetail.painting
  )
  paintingids: number[];

  // paintings: Painting[];
}

ExhibitPaintingDetail:

import {
  Entity,
  PrimaryGeneratedColumn,
  Column,
  JoinColumn,
  ManyToOne,
} from "typeorm";
import { Painting } from "./Painting";
import { Exhibit } from "./Exhibit";

@Entity("exhibitpaintingdetail")
export class ExhibitPaintingDetail {
  @PrimaryGeneratedColumn()
  id?: number;

  @Column("integer", { nullable: true })
  exhibitpaintingid: number;

  @Column("date", { nullable: true })
  submissiondate: Date;

  @Column("integer", { nullable: true })
  exhibitpaintingprice: number;

  @Column("text", { nullable: true })
  status: string;

  @Column("text", { nullable: true })
  outcome: string;

  @Column("text", { nullable: true })
  note: string;

  @Column("integer")
  exhibitid: number;

  @Column("integer")
  paintingid: number;

  @ManyToOne(() => Exhibit, (exhibit) => exhibit.id, { lazy: true })
  @JoinColumn({ name: "exhibitid" })
  exhibit: Exhibit;

  @ManyToOne(() => Painting, (painting) => painting.id, { lazy: true })
  @JoinColumn({ name: "paintingid" })
  painting: Painting;
}

API method:

import type { Painting } from "../../entities/Painting";
import AppDataSource from "../../data-source";
import { NextApiRequest, NextApiResponse } from "next";

async function getPaintingRepository() {
  const { Painting } = await import("../../entities/Painting"); // Dynamic import (for runtime)
  return AppDataSource.getRepository<Painting>(Painting); // Use the imported Painting class
}

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  try {
    if (!AppDataSource.isInitialized) {
      await AppDataSource.initialize();
    }

    const paintingRepository = await getPaintingRepository(); //AppDataSource.getRepository(Painting);
    const painting = await paintingRepository.findOne({ where: { id: 12703 } }); // Or any ID

    return res.status(200).json(painting);
  } catch (error) {
    console.error("Test Route Error:", error);
    return res.status(500).json({ error: "Test Route Error" });
  }
}

I need to get exhibits for a painting and paintings for an exhibit at different places.


Solution

  • There is no error here. What happened is that minification broke things. This is an Electron / NextJS / Typescript / Sqlite project. This code worked fine in dev, and broke in production. Once I turned off minification in web pack, everything worked correctly.

    next.config.ts:

    import type { NextConfig } from "next";
    
    const nextConfig: NextConfig = {
      images: {
        domains: [
          "scontent-iad3-1.cdninstagram.com",
          "scontent-iad3-2.cdninstagram.com",
        ],
      },
      webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
        config.optimization.minimize = false; // Disable minimization
        config.optimization.minimizer = []; // Remove minimizers
        return config;
      },
    };
    
    export default nextConfig;