diff --git a/packages/hoppscotch-data/src/collection/index.ts b/packages/hoppscotch-data/src/collection/index.ts index 9eeb0db8a..ee01b7098 100644 --- a/packages/hoppscotch-data/src/collection/index.ts +++ b/packages/hoppscotch-data/src/collection/index.ts @@ -1,37 +1,47 @@ -import { - GQL_REQ_SCHEMA_VERSION, - HoppGQLRequest, - translateToGQLRequest, -} from "../graphql" -import { HoppRESTRequest, translateToNewRequest } from "../rest" +import { InferredEntity, createVersionedEntity } from "verzod" -const CURRENT_COLL_SCHEMA_VER = 1 +import V1_VERSION from "./v/1" +import V2_VERSION from "./v/2" -type SupportedReqTypes = HoppRESTRequest | HoppGQLRequest +import { z } from "zod" +import { translateToNewRequest } from "../rest" +import { translateToGQLRequest } from "../graphql" -export type HoppCollection = { - v: number - name: string - folders: HoppCollection[] - requests: T[] +const versionedObject = z.object({ + // v is a stringified number + v: z.string().regex(/^\d+$/).transform(Number), +}) - auth: T["auth"] - headers: T["headers"] +export const HoppCollection = createVersionedEntity({ + latestVersion: 2, + versionMap: { + 1: V1_VERSION, + 2: V2_VERSION, + }, + getVersion(data) { + const versionCheck = versionedObject.safeParse(data) - id?: string // For Firestore ID data -} + if (versionCheck.success) return versionCheck.data.v + + // For V1 we have to check the schema + const result = V1_VERSION.schema.safeParse(data) + + return result.success ? 0 : null + }, +}) + +export type HoppCollection = InferredEntity + +export const CollectionSchemaVersion = 2 /** * Generates a Collection object. This ignores the version number object - * so it can be incremented independently without updating it everywhere * @param x The Collection Data * @returns The final collection */ -export function makeCollection( - x: Omit, "v"> -): HoppCollection { +export function makeCollection(x: Omit): HoppCollection { return { - v: CURRENT_COLL_SCHEMA_VER, + v: CollectionSchemaVersion, ...x, } } @@ -41,20 +51,18 @@ export function makeCollection( * @param x The collection object to load * @returns The proper new collection format */ -export function translateToNewRESTCollection( - x: any -): HoppCollection { - if (x.v && x.v === 1) return x +export function translateToNewRESTCollection(x: any): HoppCollection { + if (x.v && x.v === CollectionSchemaVersion) return x // Legacy const name = x.name ?? "Untitled" const folders = (x.folders ?? []).map(translateToNewRESTCollection) const requests = (x.requests ?? []).map(translateToNewRequest) - const auth = x.auth ?? "None" + const auth = x.auth ?? { authType: "inherit", authActive: true } const headers = x.headers ?? [] - const obj = makeCollection({ + const obj = makeCollection({ name, folders, requests, @@ -72,10 +80,8 @@ export function translateToNewRESTCollection( * @param x The collection object to load * @returns The proper new collection format */ -export function translateToNewGQLCollection( - x: any -): HoppCollection { - if (x.v && x.v === GQL_REQ_SCHEMA_VERSION) return x +export function translateToNewGQLCollection(x: any): HoppCollection { + if (x.v && x.v === CollectionSchemaVersion) return x // Legacy const name = x.name ?? "Untitled" @@ -85,7 +91,7 @@ export function translateToNewGQLCollection( const auth = x.auth ?? { authType: "inherit", authActive: true } const headers = x.headers ?? [] - const obj = makeCollection({ + const obj = makeCollection({ name, folders, requests, diff --git a/packages/hoppscotch-data/src/collection/v/1.ts b/packages/hoppscotch-data/src/collection/v/1.ts new file mode 100644 index 000000000..ae2de46e3 --- /dev/null +++ b/packages/hoppscotch-data/src/collection/v/1.ts @@ -0,0 +1,33 @@ +import { defineVersion, entityReference } from "verzod" +import { z } from "zod" +import { HoppRESTRequest } from "../../rest" +import { HoppGQLRequest } from "../../graphql" + +const baseCollectionSchema = z.object({ + v: z.literal(1), + id: z.optional(z.string()), // For Firestore ID data + + name: z.string(), + requests: z.array( + z.lazy(() => + z.union([ + entityReference(HoppRESTRequest), + entityReference(HoppGQLRequest), + ]) + ) + ), +}) + +type Collection = z.infer & { + folders: Collection[] +} + +//@ts-expect-error ~ Recursive type +export const V1_SCHEMA: z.ZodType = baseCollectionSchema.extend({ + folders: z.lazy(() => z.array(V1_SCHEMA)), +}) + +export default defineVersion({ + initial: true, + schema: V1_SCHEMA, +}) diff --git a/packages/hoppscotch-data/src/collection/v/2.ts b/packages/hoppscotch-data/src/collection/v/2.ts new file mode 100644 index 000000000..75bb50833 --- /dev/null +++ b/packages/hoppscotch-data/src/collection/v/2.ts @@ -0,0 +1,54 @@ +import { defineVersion, entityReference } from "verzod" +import { z } from "zod" +import { HoppRESTRequest, HoppRESTAuth } from "../../rest" +import { HoppGQLRequest, HoppGQLAuth, GQLHeader } from "../../graphql" +import { V1_SCHEMA } from "./1" +import { HoppRESTHeaders } from "../../rest/v/1" + +const baseCollectionSchema = z.object({ + v: z.literal(2), + id: z.optional(z.string()), // For Firestore ID data + + name: z.string(), + requests: z.array( + z.lazy(() => + z.union([ + entityReference(HoppRESTRequest), + entityReference(HoppGQLRequest), + ]) + ) + ), + + auth: z.union([HoppRESTAuth, HoppGQLAuth]), + headers: z.array(z.union([HoppRESTHeaders, GQLHeader])), +}) + +type Collection = z.infer & { + folders: Collection[] +} + +// @ts-expect-error ~ Recursive type +export const V2_SCHEMA: z.ZodType = baseCollectionSchema.extend({ + folders: z.lazy(() => z.array(V2_SCHEMA)), +}) + +export default defineVersion({ + initial: false, + schema: V2_SCHEMA, + up(old: z.infer) { + // @ts-expect-error + const result: z.infer = { + ...old, + v: 2, + auth: { + authActive: true, + authType: "inherit", + }, + headers: [], + } + + if (old.id) result.id = old.id + + return result + }, +}) diff --git a/packages/hoppscotch-data/src/rest/index.ts b/packages/hoppscotch-data/src/rest/index.ts index 6324c4188..575daad64 100644 --- a/packages/hoppscotch-data/src/rest/index.ts +++ b/packages/hoppscotch-data/src/rest/index.ts @@ -25,6 +25,7 @@ export { HoppRESTAuthNone, HoppRESTAuthOAuth2, HoppRESTReqBody, + HoppRESTHeaders, } from "./v/1" const versionedObject = z.object({