diff --git a/src/server/templates/typescript.ts b/src/server/templates/typescript.ts index 29c34c1a..f8e6e7ca 100644 --- a/src/server/templates/typescript.ts +++ b/src/server/templates/typescript.ts @@ -316,7 +316,9 @@ export type Database = { `${JSON.stringify(fnName)}: { Args: ${fns .map(({ args }) => { - const inArgs = args.filter(({ mode }) => mode === 'in') + const inArgs = args + .toSorted((a, b) => a.name.localeCompare(b.name)) + .filter(({ mode }) => mode === 'in') if (inArgs.length === 0) { return 'Record' diff --git a/test/server/typegen.ts b/test/server/typegen.ts index 87996416..9a03ea9e 100644 --- a/test/server/typegen.ts +++ b/test/server/typegen.ts @@ -629,659 +629,6 @@ test('typegen: typescript', async () => { ) }) -test('typegen w/ one-to-one relationships', async () => { - const { body } = await app.inject({ - method: 'GET', - path: '/generators/typescript', - query: { detect_one_to_one_relationships: 'true' }, - }) - expect(body).toMatchInlineSnapshot( - ` - "export type Json = - | string - | number - | boolean - | null - | { [key: string]: Json | undefined } - | Json[] - - export type Database = { - public: { - Tables: { - category: { - Row: { - id: number - name: string - } - Insert: { - id?: number - name: string - } - Update: { - id?: number - name?: string - } - Relationships: [] - } - empty: { - Row: {} - Insert: {} - Update: {} - Relationships: [] - } - foreign_table: { - Row: { - id: number - name: string | null - status: Database["public"]["Enums"]["user_status"] | null - } - Insert: { - id: number - name?: string | null - status?: Database["public"]["Enums"]["user_status"] | null - } - Update: { - id?: number - name?: string | null - status?: Database["public"]["Enums"]["user_status"] | null - } - Relationships: [] - } - memes: { - Row: { - category: number | null - created_at: string - id: number - metadata: Json | null - name: string - status: Database["public"]["Enums"]["meme_status"] | null - } - Insert: { - category?: number | null - created_at: string - id?: number - metadata?: Json | null - name: string - status?: Database["public"]["Enums"]["meme_status"] | null - } - Update: { - category?: number | null - created_at?: string - id?: number - metadata?: Json | null - name?: string - status?: Database["public"]["Enums"]["meme_status"] | null - } - Relationships: [ - { - foreignKeyName: "memes_category_fkey" - columns: ["category"] - isOneToOne: false - referencedRelation: "category" - referencedColumns: ["id"] - }, - ] - } - table_with_other_tables_row_type: { - Row: { - col1: Database["public"]["Tables"]["user_details"]["Row"] | null - col2: Database["public"]["Views"]["a_view"]["Row"] | null - } - Insert: { - col1?: Database["public"]["Tables"]["user_details"]["Row"] | null - col2?: Database["public"]["Views"]["a_view"]["Row"] | null - } - Update: { - col1?: Database["public"]["Tables"]["user_details"]["Row"] | null - col2?: Database["public"]["Views"]["a_view"]["Row"] | null - } - Relationships: [] - } - table_with_primary_key_other_than_id: { - Row: { - name: string | null - other_id: number - } - Insert: { - name?: string | null - other_id?: number - } - Update: { - name?: string | null - other_id?: number - } - Relationships: [] - } - todos: { - Row: { - details: string | null - id: number - "user-id": number - blurb: string | null - blurb_varchar: string | null - details_is_long: boolean | null - details_length: number | null - details_words: string[] | null - } - Insert: { - details?: string | null - id?: number - "user-id": number - } - Update: { - details?: string | null - id?: number - "user-id"?: number - } - Relationships: [ - { - foreignKeyName: "todos_user-id_fkey" - columns: ["user-id"] - isOneToOne: false - referencedRelation: "a_view" - referencedColumns: ["id"] - }, - { - foreignKeyName: "todos_user-id_fkey" - columns: ["user-id"] - isOneToOne: false - referencedRelation: "users" - referencedColumns: ["id"] - }, - { - foreignKeyName: "todos_user-id_fkey" - columns: ["user-id"] - isOneToOne: false - referencedRelation: "users_view" - referencedColumns: ["id"] - }, - { - foreignKeyName: "todos_user-id_fkey" - columns: ["user-id"] - isOneToOne: false - referencedRelation: "users_view_with_multiple_refs_to_users" - referencedColumns: ["initial_id"] - }, - { - foreignKeyName: "todos_user-id_fkey" - columns: ["user-id"] - isOneToOne: false - referencedRelation: "users_view_with_multiple_refs_to_users" - referencedColumns: ["second_id"] - }, - ] - } - user_details: { - Row: { - details: string | null - user_id: number - } - Insert: { - details?: string | null - user_id: number - } - Update: { - details?: string | null - user_id?: number - } - Relationships: [ - { - foreignKeyName: "user_details_user_id_fkey" - columns: ["user_id"] - isOneToOne: true - referencedRelation: "a_view" - referencedColumns: ["id"] - }, - { - foreignKeyName: "user_details_user_id_fkey" - columns: ["user_id"] - isOneToOne: true - referencedRelation: "users" - referencedColumns: ["id"] - }, - { - foreignKeyName: "user_details_user_id_fkey" - columns: ["user_id"] - isOneToOne: true - referencedRelation: "users_view" - referencedColumns: ["id"] - }, - { - foreignKeyName: "user_details_user_id_fkey" - columns: ["user_id"] - isOneToOne: true - referencedRelation: "users_view_with_multiple_refs_to_users" - referencedColumns: ["initial_id"] - }, - { - foreignKeyName: "user_details_user_id_fkey" - columns: ["user_id"] - isOneToOne: true - referencedRelation: "users_view_with_multiple_refs_to_users" - referencedColumns: ["second_id"] - }, - ] - } - users: { - Row: { - decimal: number | null - id: number - name: string | null - status: Database["public"]["Enums"]["user_status"] | null - } - Insert: { - decimal?: number | null - id?: number - name?: string | null - status?: Database["public"]["Enums"]["user_status"] | null - } - Update: { - decimal?: number | null - id?: number - name?: string | null - status?: Database["public"]["Enums"]["user_status"] | null - } - Relationships: [] - } - users_audit: { - Row: { - created_at: string | null - id: number - previous_value: Json | null - user_id: number | null - } - Insert: { - created_at?: string | null - id?: number - previous_value?: Json | null - user_id?: number | null - } - Update: { - created_at?: string | null - id?: number - previous_value?: Json | null - user_id?: number | null - } - Relationships: [] - } - } - Views: { - a_view: { - Row: { - id: number | null - } - Insert: { - id?: number | null - } - Update: { - id?: number | null - } - Relationships: [] - } - todos_matview: { - Row: { - details: string | null - id: number | null - "user-id": number | null - } - Relationships: [ - { - foreignKeyName: "todos_user-id_fkey" - columns: ["user-id"] - isOneToOne: false - referencedRelation: "a_view" - referencedColumns: ["id"] - }, - { - foreignKeyName: "todos_user-id_fkey" - columns: ["user-id"] - isOneToOne: false - referencedRelation: "users" - referencedColumns: ["id"] - }, - { - foreignKeyName: "todos_user-id_fkey" - columns: ["user-id"] - isOneToOne: false - referencedRelation: "users_view" - referencedColumns: ["id"] - }, - { - foreignKeyName: "todos_user-id_fkey" - columns: ["user-id"] - isOneToOne: false - referencedRelation: "users_view_with_multiple_refs_to_users" - referencedColumns: ["initial_id"] - }, - { - foreignKeyName: "todos_user-id_fkey" - columns: ["user-id"] - isOneToOne: false - referencedRelation: "users_view_with_multiple_refs_to_users" - referencedColumns: ["second_id"] - }, - ] - } - todos_view: { - Row: { - details: string | null - id: number | null - "user-id": number | null - } - Insert: { - details?: string | null - id?: number | null - "user-id"?: number | null - } - Update: { - details?: string | null - id?: number | null - "user-id"?: number | null - } - Relationships: [ - { - foreignKeyName: "todos_user-id_fkey" - columns: ["user-id"] - isOneToOne: false - referencedRelation: "a_view" - referencedColumns: ["id"] - }, - { - foreignKeyName: "todos_user-id_fkey" - columns: ["user-id"] - isOneToOne: false - referencedRelation: "users" - referencedColumns: ["id"] - }, - { - foreignKeyName: "todos_user-id_fkey" - columns: ["user-id"] - isOneToOne: false - referencedRelation: "users_view" - referencedColumns: ["id"] - }, - { - foreignKeyName: "todos_user-id_fkey" - columns: ["user-id"] - isOneToOne: false - referencedRelation: "users_view_with_multiple_refs_to_users" - referencedColumns: ["initial_id"] - }, - { - foreignKeyName: "todos_user-id_fkey" - columns: ["user-id"] - isOneToOne: false - referencedRelation: "users_view_with_multiple_refs_to_users" - referencedColumns: ["second_id"] - }, - ] - } - users_view: { - Row: { - decimal: number | null - id: number | null - name: string | null - status: Database["public"]["Enums"]["user_status"] | null - } - Insert: { - decimal?: number | null - id?: number | null - name?: string | null - status?: Database["public"]["Enums"]["user_status"] | null - } - Update: { - decimal?: number | null - id?: number | null - name?: string | null - status?: Database["public"]["Enums"]["user_status"] | null - } - Relationships: [] - } - users_view_with_multiple_refs_to_users: { - Row: { - initial_id: number | null - initial_name: string | null - second_id: number | null - second_name: string | null - } - Relationships: [] - } - } - Functions: { - blurb: { - Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: string - } - blurb_varchar: { - Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: string - } - details_is_long: { - Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: boolean - } - details_length: { - Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: number - } - details_words: { - Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: string[] - } - function_returning_row: { - Args: Record - Returns: { - decimal: number | null - id: number - name: string | null - status: Database["public"]["Enums"]["user_status"] | null - } - } - function_returning_set_of_rows: { - Args: Record - Returns: { - decimal: number | null - id: number - name: string | null - status: Database["public"]["Enums"]["user_status"] | null - }[] - } - function_returning_table: { - Args: Record - Returns: { - id: number - name: string - }[] - } - get_todos_setof_rows: { - Args: - | { todo_row: Database["public"]["Tables"]["todos"]["Row"] } - | { user_row: Database["public"]["Tables"]["users"]["Row"] } - Returns: { - details: string | null - id: number - "user-id": number - }[] - } - get_user_audit_setof_single_row: { - Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } - Returns: { - created_at: string | null - id: number - previous_value: Json | null - user_id: number | null - }[] - } - polymorphic_function: { - Args: { "": boolean } | { "": string } - Returns: undefined - } - postgres_fdw_disconnect: { - Args: { "": string } - Returns: boolean - } - postgres_fdw_disconnect_all: { - Args: Record - Returns: boolean - } - postgres_fdw_get_connections: { - Args: Record - Returns: Record[] - } - postgres_fdw_handler: { - Args: Record - Returns: unknown - } - test_internal_query: { - Args: Record - Returns: undefined - } - } - Enums: { - meme_status: "new" | "old" | "retired" - user_status: "ACTIVE" | "INACTIVE" - } - CompositeTypes: { - composite_type_with_array_attribute: { - my_text_array: string[] | null - } - composite_type_with_record_attribute: { - todo: Database["public"]["Tables"]["todos"]["Row"] | null - } - } - } - } - - type DatabaseWithoutInternals = Omit - - type DefaultSchema = DatabaseWithoutInternals[Extract] - - export type Tables< - DefaultSchemaTableNameOrOptions extends - | keyof (DefaultSchema["Tables"] & DefaultSchema["Views"]) - | { schema: keyof DatabaseWithoutInternals }, - TableName extends DefaultSchemaTableNameOrOptions extends { - schema: keyof DatabaseWithoutInternals - } - ? keyof (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & - DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"]) - : never = never, - > = DefaultSchemaTableNameOrOptions extends { - schema: keyof DatabaseWithoutInternals - } - ? (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & - DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"])[TableName] extends { - Row: infer R - } - ? R - : never - : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema["Tables"] & - DefaultSchema["Views"]) - ? (DefaultSchema["Tables"] & - DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends { - Row: infer R - } - ? R - : never - : never - - export type TablesInsert< - DefaultSchemaTableNameOrOptions extends - | keyof DefaultSchema["Tables"] - | { schema: keyof DatabaseWithoutInternals }, - TableName extends DefaultSchemaTableNameOrOptions extends { - schema: keyof DatabaseWithoutInternals - } - ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] - : never = never, - > = DefaultSchemaTableNameOrOptions extends { - schema: keyof DatabaseWithoutInternals - } - ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { - Insert: infer I - } - ? I - : never - : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] - ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { - Insert: infer I - } - ? I - : never - : never - - export type TablesUpdate< - DefaultSchemaTableNameOrOptions extends - | keyof DefaultSchema["Tables"] - | { schema: keyof DatabaseWithoutInternals }, - TableName extends DefaultSchemaTableNameOrOptions extends { - schema: keyof DatabaseWithoutInternals - } - ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] - : never = never, - > = DefaultSchemaTableNameOrOptions extends { - schema: keyof DatabaseWithoutInternals - } - ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { - Update: infer U - } - ? U - : never - : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] - ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { - Update: infer U - } - ? U - : never - : never - - export type Enums< - DefaultSchemaEnumNameOrOptions extends - | keyof DefaultSchema["Enums"] - | { schema: keyof DatabaseWithoutInternals }, - EnumName extends DefaultSchemaEnumNameOrOptions extends { - schema: keyof DatabaseWithoutInternals - } - ? keyof DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"] - : never = never, - > = DefaultSchemaEnumNameOrOptions extends { - schema: keyof DatabaseWithoutInternals - } - ? DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName] - : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"] - ? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions] - : never - - export type CompositeTypes< - PublicCompositeTypeNameOrOptions extends - | keyof DefaultSchema["CompositeTypes"] - | { schema: keyof DatabaseWithoutInternals }, - CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { - schema: keyof DatabaseWithoutInternals - } - ? keyof DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"] - : never = never, - > = PublicCompositeTypeNameOrOptions extends { - schema: keyof DatabaseWithoutInternals - } - ? DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName] - : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"] - ? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions] - : never - - export const Constants = { - public: { - Enums: { - meme_status: ["new", "old", "retired"], - user_status: ["ACTIVE", "INACTIVE"], - }, - }, - } as const - " - ` - ) -}) - test('typegen: typescript w/ one-to-one relationships', async () => { const { body } = await app.inject({ method: 'GET', @@ -2593,6 +1940,273 @@ test('typegen: typescript w/ postgrestVersion', async () => { ) }) +test('typegen: typescript consistent types definitions orders', async () => { + // Helper function to clean up test entities + const cleanupTestEntities = async () => { + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + -- Drop materialized views first (depend on views/tables) + DROP MATERIALIZED VIEW IF EXISTS test_matview_alpha CASCADE; + DROP MATERIALIZED VIEW IF EXISTS test_matview_beta CASCADE; + DROP MATERIALIZED VIEW IF EXISTS test_matview_gamma CASCADE; + + -- Drop views (may depend on tables) + DROP VIEW IF EXISTS test_view_alpha CASCADE; + DROP VIEW IF EXISTS test_view_beta CASCADE; + DROP VIEW IF EXISTS test_view_gamma CASCADE; + + -- Drop functions + DROP FUNCTION IF EXISTS test_func_alpha(integer, text, boolean) CASCADE; + DROP FUNCTION IF EXISTS test_func_beta(integer, text, boolean) CASCADE; + DROP FUNCTION IF EXISTS test_func_gamma(integer, text, boolean) CASCADE; + + -- Alternative signatures for functions (different parameter orders) + DROP FUNCTION IF EXISTS test_func_alpha(text, boolean, integer) CASCADE; + DROP FUNCTION IF EXISTS test_func_beta(boolean, integer, text) CASCADE; + DROP FUNCTION IF EXISTS test_func_gamma(boolean, text, integer) CASCADE; + + -- Drop tables + DROP TABLE IF EXISTS test_table_alpha CASCADE; + DROP TABLE IF EXISTS test_table_beta CASCADE; + DROP TABLE IF EXISTS test_table_gamma CASCADE; + + -- Drop types + DROP TYPE IF EXISTS test_enum_alpha CASCADE; + DROP TYPE IF EXISTS test_enum_beta CASCADE; + `, + }, + }) + } + + // Clean up any existing test entities + await cleanupTestEntities() + + // === FIRST ROUND: Create entities in order A->B->G with property order 1 === + + // Create custom types first + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE TYPE test_enum_alpha AS ENUM ('active', 'inactive', 'pending'); + CREATE TYPE test_enum_beta AS ENUM ('high', 'medium', 'low'); + `, + }, + }) + + // Create tables in order: alpha, beta, gamma with specific column orders + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE TABLE test_table_alpha ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + status test_enum_alpha DEFAULT 'active', + created_at TIMESTAMP DEFAULT NOW() + ); + + CREATE TABLE test_table_beta ( + id SERIAL PRIMARY KEY, + priority test_enum_beta DEFAULT 'medium', + description TEXT, + alpha_id INTEGER REFERENCES test_table_alpha(id) + ); + + CREATE TABLE test_table_gamma ( + id SERIAL PRIMARY KEY, + beta_id INTEGER REFERENCES test_table_beta(id), + value NUMERIC(10,2), + is_active BOOLEAN DEFAULT true + ); + `, + }, + }) + + // Create functions in order: alpha, beta, gamma with specific parameter orders + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE FUNCTION test_func_alpha(param_a integer, param_b text, param_c boolean) + RETURNS integer AS 'SELECT param_a + 1' LANGUAGE sql IMMUTABLE; + + CREATE FUNCTION test_func_beta(param_a integer, param_b text, param_c boolean) + RETURNS text AS 'SELECT param_b || ''_processed''' LANGUAGE sql IMMUTABLE; + + CREATE FUNCTION test_func_gamma(param_a integer, param_b text, param_c boolean) + RETURNS boolean AS 'SELECT NOT param_c' LANGUAGE sql IMMUTABLE; + `, + }, + }) + + // Create views in order: alpha, beta, gamma + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE VIEW test_view_alpha AS + SELECT id, name, status, created_at FROM test_table_alpha; + + CREATE VIEW test_view_beta AS + SELECT id, priority, description, alpha_id FROM test_table_beta; + + CREATE VIEW test_view_gamma AS + SELECT id, beta_id, value, is_active FROM test_table_gamma; + `, + }, + }) + + // Create materialized views in order: alpha, beta, gamma + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE MATERIALIZED VIEW test_matview_alpha AS + SELECT id, name, status FROM test_table_alpha; + + CREATE MATERIALIZED VIEW test_matview_beta AS + SELECT id, priority, description FROM test_table_beta; + + CREATE MATERIALIZED VIEW test_matview_gamma AS + SELECT id, value, is_active FROM test_table_gamma; + `, + }, + }) + + // Generate types for first configuration + const { body: firstCall } = await app.inject({ + method: 'GET', + path: '/generators/typescript', + query: { detect_one_to_one_relationships: 'true', postgrest_version: '13' }, + }) + + // === SECOND ROUND: Drop and recreate in reverse order G->B->A with different property orders === + + // Clean up all test entities + await cleanupTestEntities() + + // Create custom types in reverse order but keep the enum internal ordering (typegen is rightfully dependent on the enum order) + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE TYPE test_enum_beta AS ENUM ('high', 'medium', 'low'); + CREATE TYPE test_enum_alpha AS ENUM ('active', 'inactive', 'pending'); + `, + }, + }) + + // Create tables in reverse order: gamma, beta, alpha with different column orders + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE TABLE test_table_alpha ( + created_at TIMESTAMP DEFAULT NOW(), + status test_enum_alpha DEFAULT 'active', + name TEXT NOT NULL, + id SERIAL PRIMARY KEY + ); + + CREATE TABLE test_table_beta ( + alpha_id INTEGER REFERENCES test_table_alpha(id), + description TEXT, + priority test_enum_beta DEFAULT 'medium', + id SERIAL PRIMARY KEY + ); + + CREATE TABLE test_table_gamma ( + is_active BOOLEAN DEFAULT true, + value NUMERIC(10,2), + beta_id INTEGER REFERENCES test_table_beta(id), + id SERIAL PRIMARY KEY + ); + `, + }, + }) + + // Create functions in reverse order: gamma, beta, alpha with different parameter orders + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE FUNCTION test_func_gamma(param_c boolean, param_a integer, param_b text) + RETURNS boolean AS 'SELECT NOT param_c' LANGUAGE sql IMMUTABLE; + + CREATE FUNCTION test_func_beta(param_b text, param_c boolean, param_a integer) + RETURNS text AS 'SELECT param_b || ''_processed''' LANGUAGE sql IMMUTABLE; + + CREATE FUNCTION test_func_alpha(param_c boolean, param_b text, param_a integer) + RETURNS integer AS 'SELECT param_a + 1' LANGUAGE sql IMMUTABLE; + `, + }, + }) + + // Create views in reverse order: gamma, beta, alpha + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE VIEW test_view_gamma AS + SELECT is_active, value, beta_id, id FROM test_table_gamma; + + CREATE VIEW test_view_beta AS + SELECT alpha_id, description, priority, id FROM test_table_beta; + + CREATE VIEW test_view_alpha AS + SELECT created_at, status, name, id FROM test_table_alpha; + `, + }, + }) + + // Create materialized views in reverse order: gamma, beta, alpha + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE MATERIALIZED VIEW test_matview_gamma AS + SELECT is_active, value, id FROM test_table_gamma; + + CREATE MATERIALIZED VIEW test_matview_beta AS + SELECT description, priority, id FROM test_table_beta; + + CREATE MATERIALIZED VIEW test_matview_alpha AS + SELECT status, name, id FROM test_table_alpha; + `, + }, + }) + + // Generate types for second configuration + const { body: secondCall } = await app.inject({ + method: 'GET', + path: '/generators/typescript', + query: { detect_one_to_one_relationships: 'true', postgrest_version: '13' }, + }) + + // Clean up test entities + await cleanupTestEntities() + + // The generated types should be identical regardless of: + // 1. Entity creation order (alpha->beta->gamma vs gamma->beta->alpha) + // 2. Property declaration order (columns, function parameters) + // 3. Enum value order + expect(firstCall).toEqual(secondCall) +}) + test('typegen: go', async () => { const { body } = await app.inject({ method: 'GET', path: '/generators/go' }) expect(body).toMatchInlineSnapshot(`