diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 75799d70..d903f89e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,8 +52,8 @@ jobs: - name: Install dependencies run: npm ci - - name: Build connection package - run: npm -w connection run build + - name: Build connector-sdk + connection packages + run: npm -w connector-sdk run build && npm -w connection run build - name: Type-check component run: npm -w component exec tsc -- --noEmit @@ -134,8 +134,8 @@ jobs: - name: Install dependencies run: npm ci - - name: Build connection package - run: npm -w connection run build + - name: Build connector-sdk + connection packages + run: npm -w connector-sdk run build && npm -w connection run build - name: Run all tests in parallel run: | @@ -238,8 +238,8 @@ jobs: restore-keys: | nextjs-${{ runner.os }}- - - name: Build connection package - run: npm -w connection run build + - name: Build connector-sdk + connection packages + run: npm -w connector-sdk run build && npm -w connection run build - name: Build Next.js working-directory: app diff --git a/Dockerfile b/Dockerfile index 1aa5087c..4ebd4484 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,6 +9,7 @@ COPY package.json package-lock.json ./ # Copy child package manifests (npm needs these to resolve workspaces) COPY app/package.json ./app/ COPY component/package.json ./component/ +COPY connector-sdk/package.json ./connector-sdk/ COPY connection/package.json ./connection/ COPY cli/package.json ./cli/ @@ -35,7 +36,10 @@ COPY --from=deps /app/component/node_modules ./component/node_modules # Copy all source COPY . . -# Build connection package (TypeScript → JS+d.ts) before app +# Build connector-sdk → connection (TypeScript → JS+d.ts) before app. +# connection imports @neoboard/connector-sdk, which resolves to its built +# dist, so the SDK must be compiled first (mirrors the root build chain). +RUN npm -w connector-sdk run build RUN npm -w connection run build RUN cd app && npm run build diff --git a/app/drizzle/migrations/0000_wooden_zeigeist.sql b/app/drizzle/migrations/0000_cooing_greymalkin.sql similarity index 72% rename from app/drizzle/migrations/0000_wooden_zeigeist.sql rename to app/drizzle/migrations/0000_cooing_greymalkin.sql index 82e3ae8a..f402744d 100644 --- a/app/drizzle/migrations/0000_wooden_zeigeist.sql +++ b/app/drizzle/migrations/0000_cooing_greymalkin.sql @@ -1,5 +1,6 @@ -CREATE TYPE "public"."connection_type" AS ENUM('neo4j', 'postgresql');--> statement-breakpoint +CREATE TYPE "public"."connection_visibility" AS ENUM('private', 'shared');--> statement-breakpoint CREATE TYPE "public"."share_role" AS ENUM('viewer', 'editor');--> statement-breakpoint +CREATE TYPE "public"."sso_protocol" AS ENUM('oidc');--> statement-breakpoint CREATE TYPE "public"."user_role" AS ENUM('admin', 'creator', 'reader');--> statement-breakpoint CREATE TABLE "account" ( "userId" text NOT NULL, @@ -20,6 +21,7 @@ CREATE TABLE "api_key" ( "userId" text NOT NULL, "tenant_id" text DEFAULT 'default' NOT NULL, "key_hash" text NOT NULL, + "key_prefix" text, "name" text NOT NULL, "last_used_at" timestamp, "expires_at" timestamp, @@ -27,13 +29,27 @@ CREATE TABLE "api_key" ( CONSTRAINT "api_key_key_hash_unique" UNIQUE("key_hash") ); --> statement-breakpoint +CREATE TABLE "audit_log" ( + "id" text PRIMARY KEY NOT NULL, + "tenant_id" text DEFAULT 'default' NOT NULL, + "user_id" text, + "action" text NOT NULL, + "resource_type" text, + "resource_id" text, + "details" jsonb, + "ip_address" text, + "created_at" timestamp DEFAULT now() +); +--> statement-breakpoint CREATE TABLE "connection" ( "id" text PRIMARY KEY NOT NULL, "userId" text NOT NULL, "tenant_id" text DEFAULT 'default' NOT NULL, "name" text NOT NULL, - "type" "connection_type" NOT NULL, + "type" text NOT NULL, "configEncrypted" text NOT NULL, + "allow_per_card_db" boolean DEFAULT true NOT NULL, + "visibility" "connection_visibility" DEFAULT 'private' NOT NULL, "createdAt" timestamp DEFAULT now(), "updatedAt" timestamp DEFAULT now() ); @@ -54,7 +70,7 @@ CREATE TABLE "dashboard" ( "name" text NOT NULL, "description" text, "layoutJson" jsonb DEFAULT '{"version":2,"pages":[{"id":"page-1","title":"Page 1","widgets":[],"gridLayout":[]}]}'::jsonb, - "thumbnailJson" jsonb, + "version" integer DEFAULT 1 NOT NULL, "isPublic" boolean DEFAULT false, "createdAt" timestamp DEFAULT now(), "updatedAt" timestamp DEFAULT now(), @@ -67,19 +83,41 @@ CREATE TABLE "session" ( "expires" timestamp NOT NULL ); --> statement-breakpoint +CREATE TABLE "sso_provider" ( + "id" text PRIMARY KEY NOT NULL, + "tenant_id" text DEFAULT 'default' NOT NULL, + "name" text NOT NULL, + "protocol" "sso_protocol" DEFAULT 'oidc' NOT NULL, + "issuer" text NOT NULL, + "client_id" text NOT NULL, + "client_secret_encrypted" text NOT NULL, + "scopes" text DEFAULT 'openid profile email' NOT NULL, + "claim_mappings" jsonb, + "auto_provision" boolean DEFAULT true NOT NULL, + "default_role" "user_role" DEFAULT 'creator' NOT NULL, + "enforce_sso" boolean DEFAULT false NOT NULL, + "enabled" boolean DEFAULT true NOT NULL, + "created_at" timestamp DEFAULT now(), + "updated_at" timestamp DEFAULT now(), + CONSTRAINT "sso_provider_tenant_issuer_unique" UNIQUE("tenant_id","issuer") +); +--> statement-breakpoint CREATE TABLE "user" ( "id" text PRIMARY KEY NOT NULL, "name" text, - "email" text, + "email" text NOT NULL, "emailVerified" timestamp, "image" text, "passwordHash" text, "role" "user_role" DEFAULT 'creator' NOT NULL, "can_write" boolean DEFAULT true NOT NULL, + "force_password_change" boolean DEFAULT false NOT NULL, + "passwordChangedAt" timestamp, "disabledAt" timestamp, "lastLoginAt" timestamp, "createdAt" timestamp DEFAULT now(), - CONSTRAINT "user_email_unique" UNIQUE("email") + "tenant_id" text DEFAULT 'default' NOT NULL, + CONSTRAINT "user_email_tenant_unique" UNIQUE("email","tenant_id") ); --> statement-breakpoint CREATE TABLE "verificationToken" ( @@ -108,6 +146,7 @@ CREATE TABLE "widget_template" ( --> statement-breakpoint ALTER TABLE "account" ADD CONSTRAINT "account_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint ALTER TABLE "api_key" ADD CONSTRAINT "api_key_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "audit_log" ADD CONSTRAINT "audit_log_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint ALTER TABLE "connection" ADD CONSTRAINT "connection_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint ALTER TABLE "dashboard_share" ADD CONSTRAINT "dashboard_share_dashboardId_dashboard_id_fk" FOREIGN KEY ("dashboardId") REFERENCES "public"."dashboard"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint ALTER TABLE "dashboard_share" ADD CONSTRAINT "dashboard_share_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint diff --git a/app/drizzle/migrations/0001_rapid_iron_monger.sql b/app/drizzle/migrations/0001_rapid_iron_monger.sql deleted file mode 100644 index 2e24525b..00000000 --- a/app/drizzle/migrations/0001_rapid_iron_monger.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE "user" ADD COLUMN "force_password_change" boolean DEFAULT false NOT NULL; \ No newline at end of file diff --git a/app/drizzle/migrations/0002_redundant_night_nurse.sql b/app/drizzle/migrations/0002_redundant_night_nurse.sql deleted file mode 100644 index f44b82a9..00000000 --- a/app/drizzle/migrations/0002_redundant_night_nurse.sql +++ /dev/null @@ -1,4 +0,0 @@ -ALTER TABLE "user" DROP CONSTRAINT "user_email_unique";--> statement-breakpoint -ALTER TABLE "user" ALTER COLUMN "email" SET NOT NULL;--> statement-breakpoint -ALTER TABLE "user" ADD COLUMN "tenant_id" text DEFAULT 'default' NOT NULL;--> statement-breakpoint -ALTER TABLE "user" ADD CONSTRAINT "user_email_tenant_unique" UNIQUE("email","tenant_id"); \ No newline at end of file diff --git a/app/drizzle/migrations/0003_loving_centennial.sql b/app/drizzle/migrations/0003_loving_centennial.sql deleted file mode 100644 index d41794bc..00000000 --- a/app/drizzle/migrations/0003_loving_centennial.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE "dashboard" ADD COLUMN "version" integer DEFAULT 1 NOT NULL; \ No newline at end of file diff --git a/app/drizzle/migrations/0004_furry_scourge.sql b/app/drizzle/migrations/0004_furry_scourge.sql deleted file mode 100644 index e7743a4f..00000000 --- a/app/drizzle/migrations/0004_furry_scourge.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE "connection" ADD COLUMN "allow_per_card_db" boolean DEFAULT true NOT NULL; \ No newline at end of file diff --git a/app/drizzle/migrations/0005_perfect_paibok.sql b/app/drizzle/migrations/0005_perfect_paibok.sql deleted file mode 100644 index 742f44b8..00000000 --- a/app/drizzle/migrations/0005_perfect_paibok.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE "user" ADD COLUMN "passwordChangedAt" timestamp; \ No newline at end of file diff --git a/app/drizzle/migrations/0006_busy_champions.sql b/app/drizzle/migrations/0006_busy_champions.sql deleted file mode 100644 index 4c4fc6ed..00000000 --- a/app/drizzle/migrations/0006_busy_champions.sql +++ /dev/null @@ -1,19 +0,0 @@ -CREATE TYPE "public"."sso_protocol" AS ENUM('oidc');--> statement-breakpoint -CREATE TABLE "sso_provider" ( - "id" text PRIMARY KEY NOT NULL, - "tenant_id" text DEFAULT 'default' NOT NULL, - "name" text NOT NULL, - "protocol" "sso_protocol" DEFAULT 'oidc' NOT NULL, - "issuer" text NOT NULL, - "client_id" text NOT NULL, - "client_secret_encrypted" text NOT NULL, - "scopes" text DEFAULT 'openid profile email' NOT NULL, - "claim_mappings" jsonb, - "auto_provision" boolean DEFAULT true NOT NULL, - "default_role" "user_role" DEFAULT 'creator' NOT NULL, - "enforce_sso" boolean DEFAULT false NOT NULL, - "enabled" boolean DEFAULT true NOT NULL, - "created_at" timestamp DEFAULT now(), - "updated_at" timestamp DEFAULT now(), - CONSTRAINT "sso_provider_tenant_issuer_unique" UNIQUE("tenant_id","issuer") -); diff --git a/app/drizzle/migrations/0007_free_loners.sql b/app/drizzle/migrations/0007_free_loners.sql deleted file mode 100644 index 3bec3af8..00000000 --- a/app/drizzle/migrations/0007_free_loners.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE "audit_log" ( - "id" text PRIMARY KEY NOT NULL, - "tenant_id" text DEFAULT 'default' NOT NULL, - "user_id" text, - "action" text NOT NULL, - "resource_type" text, - "resource_id" text, - "details" jsonb, - "ip_address" text, - "created_at" timestamp DEFAULT now() -); ---> statement-breakpoint -ALTER TABLE "audit_log" ADD CONSTRAINT "audit_log_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action; \ No newline at end of file diff --git a/app/drizzle/migrations/0008_lumpy_jubilee.sql b/app/drizzle/migrations/0008_lumpy_jubilee.sql deleted file mode 100644 index f8e8a888..00000000 --- a/app/drizzle/migrations/0008_lumpy_jubilee.sql +++ /dev/null @@ -1,2 +0,0 @@ -CREATE TYPE "public"."connection_visibility" AS ENUM('private', 'shared');--> statement-breakpoint -ALTER TABLE "connection" ADD COLUMN "visibility" "connection_visibility" DEFAULT 'private' NOT NULL; \ No newline at end of file diff --git a/app/drizzle/migrations/0009_dazzling_stranger.sql b/app/drizzle/migrations/0009_dazzling_stranger.sql deleted file mode 100644 index 28c85c73..00000000 --- a/app/drizzle/migrations/0009_dazzling_stranger.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE "api_key" ADD COLUMN "key_prefix" text; \ No newline at end of file diff --git a/app/drizzle/migrations/0010_tiny_amphibian.sql b/app/drizzle/migrations/0010_tiny_amphibian.sql deleted file mode 100644 index c3758497..00000000 --- a/app/drizzle/migrations/0010_tiny_amphibian.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE "dashboard" DROP COLUMN "thumbnailJson"; \ No newline at end of file diff --git a/app/drizzle/migrations/meta/0000_snapshot.json b/app/drizzle/migrations/meta/0000_snapshot.json index cc8e21af..5f778fa0 100644 --- a/app/drizzle/migrations/meta/0000_snapshot.json +++ b/app/drizzle/migrations/meta/0000_snapshot.json @@ -1,6 +1,6 @@ { - "id": "0a1a88e9-a9f0-4c9a-933c-11e6e1214328", - "prevId": "17208288-2aaa-46a7-9c62-39817edc1550", + "id": "6fcf5265-8ae6-4dfe-b566-ce363bc415d4", + "prevId": "00000000-0000-0000-0000-000000000000", "version": "7", "dialect": "postgresql", "tables": { @@ -122,6 +122,12 @@ "primaryKey": false, "notNull": true }, + "key_prefix": { + "name": "key_prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, "name": { "name": "name", "type": "text", @@ -172,6 +178,85 @@ "checkConstraints": {}, "isRLSEnabled": false }, + "public.audit_log": { + "name": "audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "audit_log_user_id_user_id_fk": { + "name": "audit_log_user_id_user_id_fk", + "tableFrom": "audit_log", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, "public.connection": { "name": "connection", "schema": "", @@ -203,8 +288,7 @@ }, "type": { "name": "type", - "type": "connection_type", - "typeSchema": "public", + "type": "text", "primaryKey": false, "notNull": true }, @@ -214,6 +298,21 @@ "primaryKey": false, "notNull": true }, + "allow_per_card_db": { + "name": "allow_per_card_db", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "visibility": { + "name": "visibility", + "type": "connection_visibility", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'private'" + }, "createdAt": { "name": "createdAt", "type": "timestamp", @@ -360,11 +459,12 @@ "notNull": false, "default": "'{\"version\":2,\"pages\":[{\"id\":\"page-1\",\"title\":\"Page 1\",\"widgets\":[],\"gridLayout\":[]}]}'::jsonb" }, - "thumbnailJson": { - "name": "thumbnailJson", - "type": "jsonb", + "version": { + "name": "version", + "type": "integer", "primaryKey": false, - "notNull": false + "notNull": true, + "default": 1 }, "isPublic": { "name": "isPublic", @@ -462,6 +562,126 @@ "checkConstraints": {}, "isRLSEnabled": false }, + "public.sso_provider": { + "name": "sso_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "protocol": { + "name": "protocol", + "type": "sso_protocol", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'oidc'" + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret_encrypted": { + "name": "client_secret_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'openid profile email'" + }, + "claim_mappings": { + "name": "claim_mappings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "auto_provision": { + "name": "auto_provision", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "default_role": { + "name": "default_role", + "type": "user_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'creator'" + }, + "enforce_sso": { + "name": "enforce_sso", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sso_provider_tenant_issuer_unique": { + "name": "sso_provider_tenant_issuer_unique", + "nullsNotDistinct": false, + "columns": ["tenant_id", "issuer"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, "public.user": { "name": "user", "schema": "", @@ -482,7 +702,7 @@ "name": "email", "type": "text", "primaryKey": false, - "notNull": false + "notNull": true }, "emailVerified": { "name": "emailVerified", @@ -517,6 +737,19 @@ "notNull": true, "default": true }, + "force_password_change": { + "name": "force_password_change", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "passwordChangedAt": { + "name": "passwordChangedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, "disabledAt": { "name": "disabledAt", "type": "timestamp", @@ -535,16 +768,23 @@ "primaryKey": false, "notNull": false, "default": "now()" + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" } }, "indexes": {}, "foreignKeys": {}, "compositePrimaryKeys": {}, "uniqueConstraints": { - "user_email_unique": { - "name": "user_email_unique", + "user_email_tenant_unique": { + "name": "user_email_tenant_unique", "nullsNotDistinct": false, - "columns": ["email"] + "columns": ["email", "tenant_id"] } }, "policies": {}, @@ -702,16 +942,21 @@ } }, "enums": { - "public.connection_type": { - "name": "connection_type", + "public.connection_visibility": { + "name": "connection_visibility", "schema": "public", - "values": ["neo4j", "postgresql"] + "values": ["private", "shared"] }, "public.share_role": { "name": "share_role", "schema": "public", "values": ["viewer", "editor"] }, + "public.sso_protocol": { + "name": "sso_protocol", + "schema": "public", + "values": ["oidc"] + }, "public.user_role": { "name": "user_role", "schema": "public", diff --git a/app/drizzle/migrations/meta/0001_snapshot.json b/app/drizzle/migrations/meta/0001_snapshot.json deleted file mode 100644 index 3e14da9e..00000000 --- a/app/drizzle/migrations/meta/0001_snapshot.json +++ /dev/null @@ -1,738 +0,0 @@ -{ - "id": "31365ff0-b6f5-4020-a787-0f053393c802", - "prevId": "0a1a88e9-a9f0-4c9a-933c-11e6e1214328", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.account": { - "name": "account", - "schema": "", - "columns": { - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "providerAccountId": { - "name": "providerAccountId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "refresh_token": { - "name": "refresh_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "access_token": { - "name": "access_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "token_type": { - "name": "token_type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "id_token": { - "name": "id_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "session_state": { - "name": "session_state", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "account_userId_user_id_fk": { - "name": "account_userId_user_id_fk", - "tableFrom": "account", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.api_key": { - "name": "api_key", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "key_hash": { - "name": "key_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "last_used_at": { - "name": "last_used_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "api_key_userId_user_id_fk": { - "name": "api_key_userId_user_id_fk", - "tableFrom": "api_key", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "api_key_key_hash_unique": { - "name": "api_key_key_hash_unique", - "nullsNotDistinct": false, - "columns": ["key_hash"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.connection": { - "name": "connection", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "connection_type", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "configEncrypted": { - "name": "configEncrypted", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "connection_userId_user_id_fk": { - "name": "connection_userId_user_id_fk", - "tableFrom": "connection", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dashboard_share": { - "name": "dashboard_share", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "dashboardId": { - "name": "dashboardId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "role": { - "name": "role", - "type": "share_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "dashboard_share_dashboardId_dashboard_id_fk": { - "name": "dashboard_share_dashboardId_dashboard_id_fk", - "tableFrom": "dashboard_share", - "tableTo": "dashboard", - "columnsFrom": ["dashboardId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "dashboard_share_userId_user_id_fk": { - "name": "dashboard_share_userId_user_id_fk", - "tableFrom": "dashboard_share", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dashboard": { - "name": "dashboard", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "layoutJson": { - "name": "layoutJson", - "type": "jsonb", - "primaryKey": false, - "notNull": false, - "default": "'{\"version\":2,\"pages\":[{\"id\":\"page-1\",\"title\":\"Page 1\",\"widgets\":[],\"gridLayout\":[]}]}'::jsonb" - }, - "thumbnailJson": { - "name": "thumbnailJson", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "isPublic": { - "name": "isPublic", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updated_by": { - "name": "updated_by", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "dashboard_userId_user_id_fk": { - "name": "dashboard_userId_user_id_fk", - "tableFrom": "dashboard", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "dashboard_updated_by_user_id_fk": { - "name": "dashboard_updated_by_user_id_fk", - "tableFrom": "dashboard", - "tableTo": "user", - "columnsFrom": ["updated_by"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.session": { - "name": "session", - "schema": "", - "columns": { - "sessionToken": { - "name": "sessionToken", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "session_userId_user_id_fk": { - "name": "session_userId_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "emailVerified": { - "name": "emailVerified", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "passwordHash": { - "name": "passwordHash", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "role": { - "name": "role", - "type": "user_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'creator'" - }, - "can_write": { - "name": "can_write", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "force_password_change": { - "name": "force_password_change", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "disabledAt": { - "name": "disabledAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "lastLoginAt": { - "name": "lastLoginAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "user_email_unique": { - "name": "user_email_unique", - "nullsNotDistinct": false, - "columns": ["email"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.verificationToken": { - "name": "verificationToken", - "schema": "", - "columns": { - "identifier": { - "name": "identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "token": { - "name": "token", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.widget_template": { - "name": "widget_template", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tags": { - "name": "tags", - "type": "text[]", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - }, - "chartType": { - "name": "chartType", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "connectorType": { - "name": "connectorType", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "connectionId": { - "name": "connectionId", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "query": { - "name": "query", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "''" - }, - "params": { - "name": "params", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "settings": { - "name": "settings", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "previewImageUrl": { - "name": "previewImageUrl", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "createdBy": { - "name": "createdBy", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "widget_template_createdBy_user_id_fk": { - "name": "widget_template_createdBy_user_id_fk", - "tableFrom": "widget_template", - "tableTo": "user", - "columnsFrom": ["createdBy"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": { - "public.connection_type": { - "name": "connection_type", - "schema": "public", - "values": ["neo4j", "postgresql"] - }, - "public.share_role": { - "name": "share_role", - "schema": "public", - "values": ["viewer", "editor"] - }, - "public.user_role": { - "name": "user_role", - "schema": "public", - "values": ["admin", "creator", "reader"] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} diff --git a/app/drizzle/migrations/meta/0002_snapshot.json b/app/drizzle/migrations/meta/0002_snapshot.json deleted file mode 100644 index 2721df99..00000000 --- a/app/drizzle/migrations/meta/0002_snapshot.json +++ /dev/null @@ -1,745 +0,0 @@ -{ - "id": "632932b5-b9f1-47b8-b8e6-f4f7aeaad698", - "prevId": "31365ff0-b6f5-4020-a787-0f053393c802", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.account": { - "name": "account", - "schema": "", - "columns": { - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "providerAccountId": { - "name": "providerAccountId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "refresh_token": { - "name": "refresh_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "access_token": { - "name": "access_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "token_type": { - "name": "token_type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "id_token": { - "name": "id_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "session_state": { - "name": "session_state", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "account_userId_user_id_fk": { - "name": "account_userId_user_id_fk", - "tableFrom": "account", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.api_key": { - "name": "api_key", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "key_hash": { - "name": "key_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "last_used_at": { - "name": "last_used_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "api_key_userId_user_id_fk": { - "name": "api_key_userId_user_id_fk", - "tableFrom": "api_key", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "api_key_key_hash_unique": { - "name": "api_key_key_hash_unique", - "nullsNotDistinct": false, - "columns": ["key_hash"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.connection": { - "name": "connection", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "connection_type", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "configEncrypted": { - "name": "configEncrypted", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "connection_userId_user_id_fk": { - "name": "connection_userId_user_id_fk", - "tableFrom": "connection", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dashboard_share": { - "name": "dashboard_share", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "dashboardId": { - "name": "dashboardId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "role": { - "name": "role", - "type": "share_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "dashboard_share_dashboardId_dashboard_id_fk": { - "name": "dashboard_share_dashboardId_dashboard_id_fk", - "tableFrom": "dashboard_share", - "tableTo": "dashboard", - "columnsFrom": ["dashboardId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "dashboard_share_userId_user_id_fk": { - "name": "dashboard_share_userId_user_id_fk", - "tableFrom": "dashboard_share", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dashboard": { - "name": "dashboard", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "layoutJson": { - "name": "layoutJson", - "type": "jsonb", - "primaryKey": false, - "notNull": false, - "default": "'{\"version\":2,\"pages\":[{\"id\":\"page-1\",\"title\":\"Page 1\",\"widgets\":[],\"gridLayout\":[]}]}'::jsonb" - }, - "thumbnailJson": { - "name": "thumbnailJson", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "isPublic": { - "name": "isPublic", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updated_by": { - "name": "updated_by", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "dashboard_userId_user_id_fk": { - "name": "dashboard_userId_user_id_fk", - "tableFrom": "dashboard", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "dashboard_updated_by_user_id_fk": { - "name": "dashboard_updated_by_user_id_fk", - "tableFrom": "dashboard", - "tableTo": "user", - "columnsFrom": ["updated_by"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.session": { - "name": "session", - "schema": "", - "columns": { - "sessionToken": { - "name": "sessionToken", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "session_userId_user_id_fk": { - "name": "session_userId_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "emailVerified": { - "name": "emailVerified", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "passwordHash": { - "name": "passwordHash", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "role": { - "name": "role", - "type": "user_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'creator'" - }, - "can_write": { - "name": "can_write", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "force_password_change": { - "name": "force_password_change", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "disabledAt": { - "name": "disabledAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "lastLoginAt": { - "name": "lastLoginAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "user_email_tenant_unique": { - "name": "user_email_tenant_unique", - "nullsNotDistinct": false, - "columns": ["email", "tenant_id"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.verificationToken": { - "name": "verificationToken", - "schema": "", - "columns": { - "identifier": { - "name": "identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "token": { - "name": "token", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.widget_template": { - "name": "widget_template", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tags": { - "name": "tags", - "type": "text[]", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - }, - "chartType": { - "name": "chartType", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "connectorType": { - "name": "connectorType", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "connectionId": { - "name": "connectionId", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "query": { - "name": "query", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "''" - }, - "params": { - "name": "params", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "settings": { - "name": "settings", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "previewImageUrl": { - "name": "previewImageUrl", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "createdBy": { - "name": "createdBy", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "widget_template_createdBy_user_id_fk": { - "name": "widget_template_createdBy_user_id_fk", - "tableFrom": "widget_template", - "tableTo": "user", - "columnsFrom": ["createdBy"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": { - "public.connection_type": { - "name": "connection_type", - "schema": "public", - "values": ["neo4j", "postgresql"] - }, - "public.share_role": { - "name": "share_role", - "schema": "public", - "values": ["viewer", "editor"] - }, - "public.user_role": { - "name": "user_role", - "schema": "public", - "values": ["admin", "creator", "reader"] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} diff --git a/app/drizzle/migrations/meta/0003_snapshot.json b/app/drizzle/migrations/meta/0003_snapshot.json deleted file mode 100644 index a4b52350..00000000 --- a/app/drizzle/migrations/meta/0003_snapshot.json +++ /dev/null @@ -1,752 +0,0 @@ -{ - "id": "f3e72f09-0731-4ceb-95f6-ce27e251c2d6", - "prevId": "632932b5-b9f1-47b8-b8e6-f4f7aeaad698", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.account": { - "name": "account", - "schema": "", - "columns": { - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "providerAccountId": { - "name": "providerAccountId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "refresh_token": { - "name": "refresh_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "access_token": { - "name": "access_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "token_type": { - "name": "token_type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "id_token": { - "name": "id_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "session_state": { - "name": "session_state", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "account_userId_user_id_fk": { - "name": "account_userId_user_id_fk", - "tableFrom": "account", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.api_key": { - "name": "api_key", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "key_hash": { - "name": "key_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "last_used_at": { - "name": "last_used_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "api_key_userId_user_id_fk": { - "name": "api_key_userId_user_id_fk", - "tableFrom": "api_key", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "api_key_key_hash_unique": { - "name": "api_key_key_hash_unique", - "nullsNotDistinct": false, - "columns": ["key_hash"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.connection": { - "name": "connection", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "connection_type", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "configEncrypted": { - "name": "configEncrypted", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "connection_userId_user_id_fk": { - "name": "connection_userId_user_id_fk", - "tableFrom": "connection", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dashboard_share": { - "name": "dashboard_share", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "dashboardId": { - "name": "dashboardId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "role": { - "name": "role", - "type": "share_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "dashboard_share_dashboardId_dashboard_id_fk": { - "name": "dashboard_share_dashboardId_dashboard_id_fk", - "tableFrom": "dashboard_share", - "tableTo": "dashboard", - "columnsFrom": ["dashboardId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "dashboard_share_userId_user_id_fk": { - "name": "dashboard_share_userId_user_id_fk", - "tableFrom": "dashboard_share", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dashboard": { - "name": "dashboard", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "layoutJson": { - "name": "layoutJson", - "type": "jsonb", - "primaryKey": false, - "notNull": false, - "default": "'{\"version\":2,\"pages\":[{\"id\":\"page-1\",\"title\":\"Page 1\",\"widgets\":[],\"gridLayout\":[]}]}'::jsonb" - }, - "thumbnailJson": { - "name": "thumbnailJson", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "version": { - "name": "version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 1 - }, - "isPublic": { - "name": "isPublic", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updated_by": { - "name": "updated_by", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "dashboard_userId_user_id_fk": { - "name": "dashboard_userId_user_id_fk", - "tableFrom": "dashboard", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "dashboard_updated_by_user_id_fk": { - "name": "dashboard_updated_by_user_id_fk", - "tableFrom": "dashboard", - "tableTo": "user", - "columnsFrom": ["updated_by"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.session": { - "name": "session", - "schema": "", - "columns": { - "sessionToken": { - "name": "sessionToken", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "session_userId_user_id_fk": { - "name": "session_userId_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "emailVerified": { - "name": "emailVerified", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "passwordHash": { - "name": "passwordHash", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "role": { - "name": "role", - "type": "user_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'creator'" - }, - "can_write": { - "name": "can_write", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "force_password_change": { - "name": "force_password_change", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "disabledAt": { - "name": "disabledAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "lastLoginAt": { - "name": "lastLoginAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "user_email_tenant_unique": { - "name": "user_email_tenant_unique", - "nullsNotDistinct": false, - "columns": ["email", "tenant_id"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.verificationToken": { - "name": "verificationToken", - "schema": "", - "columns": { - "identifier": { - "name": "identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "token": { - "name": "token", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.widget_template": { - "name": "widget_template", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tags": { - "name": "tags", - "type": "text[]", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - }, - "chartType": { - "name": "chartType", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "connectorType": { - "name": "connectorType", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "connectionId": { - "name": "connectionId", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "query": { - "name": "query", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "''" - }, - "params": { - "name": "params", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "settings": { - "name": "settings", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "previewImageUrl": { - "name": "previewImageUrl", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "createdBy": { - "name": "createdBy", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "widget_template_createdBy_user_id_fk": { - "name": "widget_template_createdBy_user_id_fk", - "tableFrom": "widget_template", - "tableTo": "user", - "columnsFrom": ["createdBy"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": { - "public.connection_type": { - "name": "connection_type", - "schema": "public", - "values": ["neo4j", "postgresql"] - }, - "public.share_role": { - "name": "share_role", - "schema": "public", - "values": ["viewer", "editor"] - }, - "public.user_role": { - "name": "user_role", - "schema": "public", - "values": ["admin", "creator", "reader"] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} diff --git a/app/drizzle/migrations/meta/0004_snapshot.json b/app/drizzle/migrations/meta/0004_snapshot.json deleted file mode 100644 index e6fa4f71..00000000 --- a/app/drizzle/migrations/meta/0004_snapshot.json +++ /dev/null @@ -1,759 +0,0 @@ -{ - "id": "7b9937b8-824a-4bcd-83ff-97c399ff8096", - "prevId": "f3e72f09-0731-4ceb-95f6-ce27e251c2d6", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.account": { - "name": "account", - "schema": "", - "columns": { - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "providerAccountId": { - "name": "providerAccountId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "refresh_token": { - "name": "refresh_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "access_token": { - "name": "access_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "token_type": { - "name": "token_type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "id_token": { - "name": "id_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "session_state": { - "name": "session_state", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "account_userId_user_id_fk": { - "name": "account_userId_user_id_fk", - "tableFrom": "account", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.api_key": { - "name": "api_key", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "key_hash": { - "name": "key_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "last_used_at": { - "name": "last_used_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "api_key_userId_user_id_fk": { - "name": "api_key_userId_user_id_fk", - "tableFrom": "api_key", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "api_key_key_hash_unique": { - "name": "api_key_key_hash_unique", - "nullsNotDistinct": false, - "columns": ["key_hash"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.connection": { - "name": "connection", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "connection_type", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "configEncrypted": { - "name": "configEncrypted", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "allow_per_card_db": { - "name": "allow_per_card_db", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "connection_userId_user_id_fk": { - "name": "connection_userId_user_id_fk", - "tableFrom": "connection", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dashboard_share": { - "name": "dashboard_share", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "dashboardId": { - "name": "dashboardId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "role": { - "name": "role", - "type": "share_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "dashboard_share_dashboardId_dashboard_id_fk": { - "name": "dashboard_share_dashboardId_dashboard_id_fk", - "tableFrom": "dashboard_share", - "tableTo": "dashboard", - "columnsFrom": ["dashboardId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "dashboard_share_userId_user_id_fk": { - "name": "dashboard_share_userId_user_id_fk", - "tableFrom": "dashboard_share", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dashboard": { - "name": "dashboard", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "layoutJson": { - "name": "layoutJson", - "type": "jsonb", - "primaryKey": false, - "notNull": false, - "default": "'{\"version\":2,\"pages\":[{\"id\":\"page-1\",\"title\":\"Page 1\",\"widgets\":[],\"gridLayout\":[]}]}'::jsonb" - }, - "thumbnailJson": { - "name": "thumbnailJson", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "version": { - "name": "version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 1 - }, - "isPublic": { - "name": "isPublic", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updated_by": { - "name": "updated_by", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "dashboard_userId_user_id_fk": { - "name": "dashboard_userId_user_id_fk", - "tableFrom": "dashboard", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "dashboard_updated_by_user_id_fk": { - "name": "dashboard_updated_by_user_id_fk", - "tableFrom": "dashboard", - "tableTo": "user", - "columnsFrom": ["updated_by"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.session": { - "name": "session", - "schema": "", - "columns": { - "sessionToken": { - "name": "sessionToken", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "session_userId_user_id_fk": { - "name": "session_userId_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "emailVerified": { - "name": "emailVerified", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "passwordHash": { - "name": "passwordHash", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "role": { - "name": "role", - "type": "user_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'creator'" - }, - "can_write": { - "name": "can_write", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "force_password_change": { - "name": "force_password_change", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "disabledAt": { - "name": "disabledAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "lastLoginAt": { - "name": "lastLoginAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "user_email_tenant_unique": { - "name": "user_email_tenant_unique", - "nullsNotDistinct": false, - "columns": ["email", "tenant_id"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.verificationToken": { - "name": "verificationToken", - "schema": "", - "columns": { - "identifier": { - "name": "identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "token": { - "name": "token", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.widget_template": { - "name": "widget_template", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tags": { - "name": "tags", - "type": "text[]", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - }, - "chartType": { - "name": "chartType", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "connectorType": { - "name": "connectorType", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "connectionId": { - "name": "connectionId", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "query": { - "name": "query", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "''" - }, - "params": { - "name": "params", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "settings": { - "name": "settings", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "previewImageUrl": { - "name": "previewImageUrl", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "createdBy": { - "name": "createdBy", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "widget_template_createdBy_user_id_fk": { - "name": "widget_template_createdBy_user_id_fk", - "tableFrom": "widget_template", - "tableTo": "user", - "columnsFrom": ["createdBy"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": { - "public.connection_type": { - "name": "connection_type", - "schema": "public", - "values": ["neo4j", "postgresql"] - }, - "public.share_role": { - "name": "share_role", - "schema": "public", - "values": ["viewer", "editor"] - }, - "public.user_role": { - "name": "user_role", - "schema": "public", - "values": ["admin", "creator", "reader"] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} diff --git a/app/drizzle/migrations/meta/0005_snapshot.json b/app/drizzle/migrations/meta/0005_snapshot.json deleted file mode 100644 index c61bcaec..00000000 --- a/app/drizzle/migrations/meta/0005_snapshot.json +++ /dev/null @@ -1,765 +0,0 @@ -{ - "id": "d5bdb422-ab74-449d-8e95-0342205ac8f3", - "prevId": "7b9937b8-824a-4bcd-83ff-97c399ff8096", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.account": { - "name": "account", - "schema": "", - "columns": { - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "providerAccountId": { - "name": "providerAccountId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "refresh_token": { - "name": "refresh_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "access_token": { - "name": "access_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "token_type": { - "name": "token_type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "id_token": { - "name": "id_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "session_state": { - "name": "session_state", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "account_userId_user_id_fk": { - "name": "account_userId_user_id_fk", - "tableFrom": "account", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.api_key": { - "name": "api_key", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "key_hash": { - "name": "key_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "last_used_at": { - "name": "last_used_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "api_key_userId_user_id_fk": { - "name": "api_key_userId_user_id_fk", - "tableFrom": "api_key", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "api_key_key_hash_unique": { - "name": "api_key_key_hash_unique", - "nullsNotDistinct": false, - "columns": ["key_hash"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.connection": { - "name": "connection", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "connection_type", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "configEncrypted": { - "name": "configEncrypted", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "allow_per_card_db": { - "name": "allow_per_card_db", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "connection_userId_user_id_fk": { - "name": "connection_userId_user_id_fk", - "tableFrom": "connection", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dashboard_share": { - "name": "dashboard_share", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "dashboardId": { - "name": "dashboardId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "role": { - "name": "role", - "type": "share_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "dashboard_share_dashboardId_dashboard_id_fk": { - "name": "dashboard_share_dashboardId_dashboard_id_fk", - "tableFrom": "dashboard_share", - "tableTo": "dashboard", - "columnsFrom": ["dashboardId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "dashboard_share_userId_user_id_fk": { - "name": "dashboard_share_userId_user_id_fk", - "tableFrom": "dashboard_share", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dashboard": { - "name": "dashboard", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "layoutJson": { - "name": "layoutJson", - "type": "jsonb", - "primaryKey": false, - "notNull": false, - "default": "'{\"version\":2,\"pages\":[{\"id\":\"page-1\",\"title\":\"Page 1\",\"widgets\":[],\"gridLayout\":[]}]}'::jsonb" - }, - "thumbnailJson": { - "name": "thumbnailJson", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "version": { - "name": "version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 1 - }, - "isPublic": { - "name": "isPublic", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updated_by": { - "name": "updated_by", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "dashboard_userId_user_id_fk": { - "name": "dashboard_userId_user_id_fk", - "tableFrom": "dashboard", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "dashboard_updated_by_user_id_fk": { - "name": "dashboard_updated_by_user_id_fk", - "tableFrom": "dashboard", - "tableTo": "user", - "columnsFrom": ["updated_by"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.session": { - "name": "session", - "schema": "", - "columns": { - "sessionToken": { - "name": "sessionToken", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "session_userId_user_id_fk": { - "name": "session_userId_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "emailVerified": { - "name": "emailVerified", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "passwordHash": { - "name": "passwordHash", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "role": { - "name": "role", - "type": "user_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'creator'" - }, - "can_write": { - "name": "can_write", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "force_password_change": { - "name": "force_password_change", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "passwordChangedAt": { - "name": "passwordChangedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "disabledAt": { - "name": "disabledAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "lastLoginAt": { - "name": "lastLoginAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "user_email_tenant_unique": { - "name": "user_email_tenant_unique", - "nullsNotDistinct": false, - "columns": ["email", "tenant_id"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.verificationToken": { - "name": "verificationToken", - "schema": "", - "columns": { - "identifier": { - "name": "identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "token": { - "name": "token", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.widget_template": { - "name": "widget_template", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tags": { - "name": "tags", - "type": "text[]", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - }, - "chartType": { - "name": "chartType", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "connectorType": { - "name": "connectorType", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "connectionId": { - "name": "connectionId", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "query": { - "name": "query", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "''" - }, - "params": { - "name": "params", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "settings": { - "name": "settings", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "previewImageUrl": { - "name": "previewImageUrl", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "createdBy": { - "name": "createdBy", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "widget_template_createdBy_user_id_fk": { - "name": "widget_template_createdBy_user_id_fk", - "tableFrom": "widget_template", - "tableTo": "user", - "columnsFrom": ["createdBy"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": { - "public.connection_type": { - "name": "connection_type", - "schema": "public", - "values": ["neo4j", "postgresql"] - }, - "public.share_role": { - "name": "share_role", - "schema": "public", - "values": ["viewer", "editor"] - }, - "public.user_role": { - "name": "user_role", - "schema": "public", - "values": ["admin", "creator", "reader"] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} diff --git a/app/drizzle/migrations/meta/0006_snapshot.json b/app/drizzle/migrations/meta/0006_snapshot.json deleted file mode 100644 index 5eeb0ae8..00000000 --- a/app/drizzle/migrations/meta/0006_snapshot.json +++ /dev/null @@ -1,890 +0,0 @@ -{ - "id": "f3590c20-fe02-4b05-b036-0baf5b882309", - "prevId": "d5bdb422-ab74-449d-8e95-0342205ac8f3", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.account": { - "name": "account", - "schema": "", - "columns": { - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "providerAccountId": { - "name": "providerAccountId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "refresh_token": { - "name": "refresh_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "access_token": { - "name": "access_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "token_type": { - "name": "token_type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "id_token": { - "name": "id_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "session_state": { - "name": "session_state", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "account_userId_user_id_fk": { - "name": "account_userId_user_id_fk", - "tableFrom": "account", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.api_key": { - "name": "api_key", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "key_hash": { - "name": "key_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "last_used_at": { - "name": "last_used_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "api_key_userId_user_id_fk": { - "name": "api_key_userId_user_id_fk", - "tableFrom": "api_key", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "api_key_key_hash_unique": { - "name": "api_key_key_hash_unique", - "nullsNotDistinct": false, - "columns": ["key_hash"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.connection": { - "name": "connection", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "connection_type", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "configEncrypted": { - "name": "configEncrypted", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "allow_per_card_db": { - "name": "allow_per_card_db", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "connection_userId_user_id_fk": { - "name": "connection_userId_user_id_fk", - "tableFrom": "connection", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dashboard_share": { - "name": "dashboard_share", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "dashboardId": { - "name": "dashboardId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "role": { - "name": "role", - "type": "share_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "dashboard_share_dashboardId_dashboard_id_fk": { - "name": "dashboard_share_dashboardId_dashboard_id_fk", - "tableFrom": "dashboard_share", - "tableTo": "dashboard", - "columnsFrom": ["dashboardId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "dashboard_share_userId_user_id_fk": { - "name": "dashboard_share_userId_user_id_fk", - "tableFrom": "dashboard_share", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dashboard": { - "name": "dashboard", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "layoutJson": { - "name": "layoutJson", - "type": "jsonb", - "primaryKey": false, - "notNull": false, - "default": "'{\"version\":2,\"pages\":[{\"id\":\"page-1\",\"title\":\"Page 1\",\"widgets\":[],\"gridLayout\":[]}]}'::jsonb" - }, - "thumbnailJson": { - "name": "thumbnailJson", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "version": { - "name": "version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 1 - }, - "isPublic": { - "name": "isPublic", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updated_by": { - "name": "updated_by", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "dashboard_userId_user_id_fk": { - "name": "dashboard_userId_user_id_fk", - "tableFrom": "dashboard", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "dashboard_updated_by_user_id_fk": { - "name": "dashboard_updated_by_user_id_fk", - "tableFrom": "dashboard", - "tableTo": "user", - "columnsFrom": ["updated_by"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.session": { - "name": "session", - "schema": "", - "columns": { - "sessionToken": { - "name": "sessionToken", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "session_userId_user_id_fk": { - "name": "session_userId_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.sso_provider": { - "name": "sso_provider", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "protocol": { - "name": "protocol", - "type": "sso_protocol", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'oidc'" - }, - "issuer": { - "name": "issuer", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "client_id": { - "name": "client_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "client_secret_encrypted": { - "name": "client_secret_encrypted", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "scopes": { - "name": "scopes", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'openid profile email'" - }, - "claim_mappings": { - "name": "claim_mappings", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "auto_provision": { - "name": "auto_provision", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "default_role": { - "name": "default_role", - "type": "user_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'creator'" - }, - "enforce_sso": { - "name": "enforce_sso", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "enabled": { - "name": "enabled", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "sso_provider_tenant_issuer_unique": { - "name": "sso_provider_tenant_issuer_unique", - "nullsNotDistinct": false, - "columns": ["tenant_id", "issuer"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "emailVerified": { - "name": "emailVerified", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "passwordHash": { - "name": "passwordHash", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "role": { - "name": "role", - "type": "user_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'creator'" - }, - "can_write": { - "name": "can_write", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "force_password_change": { - "name": "force_password_change", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "passwordChangedAt": { - "name": "passwordChangedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "disabledAt": { - "name": "disabledAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "lastLoginAt": { - "name": "lastLoginAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "user_email_tenant_unique": { - "name": "user_email_tenant_unique", - "nullsNotDistinct": false, - "columns": ["email", "tenant_id"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.verificationToken": { - "name": "verificationToken", - "schema": "", - "columns": { - "identifier": { - "name": "identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "token": { - "name": "token", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.widget_template": { - "name": "widget_template", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tags": { - "name": "tags", - "type": "text[]", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - }, - "chartType": { - "name": "chartType", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "connectorType": { - "name": "connectorType", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "connectionId": { - "name": "connectionId", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "query": { - "name": "query", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "''" - }, - "params": { - "name": "params", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "settings": { - "name": "settings", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "previewImageUrl": { - "name": "previewImageUrl", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "createdBy": { - "name": "createdBy", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "widget_template_createdBy_user_id_fk": { - "name": "widget_template_createdBy_user_id_fk", - "tableFrom": "widget_template", - "tableTo": "user", - "columnsFrom": ["createdBy"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": { - "public.connection_type": { - "name": "connection_type", - "schema": "public", - "values": ["neo4j", "postgresql"] - }, - "public.share_role": { - "name": "share_role", - "schema": "public", - "values": ["viewer", "editor"] - }, - "public.sso_protocol": { - "name": "sso_protocol", - "schema": "public", - "values": ["oidc"] - }, - "public.user_role": { - "name": "user_role", - "schema": "public", - "values": ["admin", "creator", "reader"] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} diff --git a/app/drizzle/migrations/meta/0007_snapshot.json b/app/drizzle/migrations/meta/0007_snapshot.json deleted file mode 100644 index 106e2c81..00000000 --- a/app/drizzle/migrations/meta/0007_snapshot.json +++ /dev/null @@ -1,969 +0,0 @@ -{ - "id": "34edd0e6-71f6-4597-ae67-e79f8365a833", - "prevId": "f3590c20-fe02-4b05-b036-0baf5b882309", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.account": { - "name": "account", - "schema": "", - "columns": { - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "providerAccountId": { - "name": "providerAccountId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "refresh_token": { - "name": "refresh_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "access_token": { - "name": "access_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "token_type": { - "name": "token_type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "id_token": { - "name": "id_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "session_state": { - "name": "session_state", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "account_userId_user_id_fk": { - "name": "account_userId_user_id_fk", - "tableFrom": "account", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.api_key": { - "name": "api_key", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "key_hash": { - "name": "key_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "last_used_at": { - "name": "last_used_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "api_key_userId_user_id_fk": { - "name": "api_key_userId_user_id_fk", - "tableFrom": "api_key", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "api_key_key_hash_unique": { - "name": "api_key_key_hash_unique", - "nullsNotDistinct": false, - "columns": ["key_hash"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.audit_log": { - "name": "audit_log", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "action": { - "name": "action", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "resource_type": { - "name": "resource_type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "resource_id": { - "name": "resource_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "details": { - "name": "details", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "ip_address": { - "name": "ip_address", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "audit_log_user_id_user_id_fk": { - "name": "audit_log_user_id_user_id_fk", - "tableFrom": "audit_log", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.connection": { - "name": "connection", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "connection_type", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "configEncrypted": { - "name": "configEncrypted", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "allow_per_card_db": { - "name": "allow_per_card_db", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "connection_userId_user_id_fk": { - "name": "connection_userId_user_id_fk", - "tableFrom": "connection", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dashboard_share": { - "name": "dashboard_share", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "dashboardId": { - "name": "dashboardId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "role": { - "name": "role", - "type": "share_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "dashboard_share_dashboardId_dashboard_id_fk": { - "name": "dashboard_share_dashboardId_dashboard_id_fk", - "tableFrom": "dashboard_share", - "tableTo": "dashboard", - "columnsFrom": ["dashboardId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "dashboard_share_userId_user_id_fk": { - "name": "dashboard_share_userId_user_id_fk", - "tableFrom": "dashboard_share", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dashboard": { - "name": "dashboard", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "layoutJson": { - "name": "layoutJson", - "type": "jsonb", - "primaryKey": false, - "notNull": false, - "default": "'{\"version\":2,\"pages\":[{\"id\":\"page-1\",\"title\":\"Page 1\",\"widgets\":[],\"gridLayout\":[]}]}'::jsonb" - }, - "thumbnailJson": { - "name": "thumbnailJson", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "version": { - "name": "version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 1 - }, - "isPublic": { - "name": "isPublic", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updated_by": { - "name": "updated_by", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "dashboard_userId_user_id_fk": { - "name": "dashboard_userId_user_id_fk", - "tableFrom": "dashboard", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "dashboard_updated_by_user_id_fk": { - "name": "dashboard_updated_by_user_id_fk", - "tableFrom": "dashboard", - "tableTo": "user", - "columnsFrom": ["updated_by"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.session": { - "name": "session", - "schema": "", - "columns": { - "sessionToken": { - "name": "sessionToken", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "session_userId_user_id_fk": { - "name": "session_userId_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.sso_provider": { - "name": "sso_provider", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "protocol": { - "name": "protocol", - "type": "sso_protocol", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'oidc'" - }, - "issuer": { - "name": "issuer", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "client_id": { - "name": "client_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "client_secret_encrypted": { - "name": "client_secret_encrypted", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "scopes": { - "name": "scopes", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'openid profile email'" - }, - "claim_mappings": { - "name": "claim_mappings", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "auto_provision": { - "name": "auto_provision", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "default_role": { - "name": "default_role", - "type": "user_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'creator'" - }, - "enforce_sso": { - "name": "enforce_sso", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "enabled": { - "name": "enabled", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "sso_provider_tenant_issuer_unique": { - "name": "sso_provider_tenant_issuer_unique", - "nullsNotDistinct": false, - "columns": ["tenant_id", "issuer"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "emailVerified": { - "name": "emailVerified", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "passwordHash": { - "name": "passwordHash", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "role": { - "name": "role", - "type": "user_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'creator'" - }, - "can_write": { - "name": "can_write", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "force_password_change": { - "name": "force_password_change", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "passwordChangedAt": { - "name": "passwordChangedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "disabledAt": { - "name": "disabledAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "lastLoginAt": { - "name": "lastLoginAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "user_email_tenant_unique": { - "name": "user_email_tenant_unique", - "nullsNotDistinct": false, - "columns": ["email", "tenant_id"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.verificationToken": { - "name": "verificationToken", - "schema": "", - "columns": { - "identifier": { - "name": "identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "token": { - "name": "token", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.widget_template": { - "name": "widget_template", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tags": { - "name": "tags", - "type": "text[]", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - }, - "chartType": { - "name": "chartType", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "connectorType": { - "name": "connectorType", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "connectionId": { - "name": "connectionId", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "query": { - "name": "query", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "''" - }, - "params": { - "name": "params", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "settings": { - "name": "settings", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "previewImageUrl": { - "name": "previewImageUrl", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "createdBy": { - "name": "createdBy", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "widget_template_createdBy_user_id_fk": { - "name": "widget_template_createdBy_user_id_fk", - "tableFrom": "widget_template", - "tableTo": "user", - "columnsFrom": ["createdBy"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": { - "public.connection_type": { - "name": "connection_type", - "schema": "public", - "values": ["neo4j", "postgresql"] - }, - "public.share_role": { - "name": "share_role", - "schema": "public", - "values": ["viewer", "editor"] - }, - "public.sso_protocol": { - "name": "sso_protocol", - "schema": "public", - "values": ["oidc"] - }, - "public.user_role": { - "name": "user_role", - "schema": "public", - "values": ["admin", "creator", "reader"] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} diff --git a/app/drizzle/migrations/meta/0008_snapshot.json b/app/drizzle/migrations/meta/0008_snapshot.json deleted file mode 100644 index 69ac01e5..00000000 --- a/app/drizzle/migrations/meta/0008_snapshot.json +++ /dev/null @@ -1,982 +0,0 @@ -{ - "id": "5ec3aba2-98b3-4832-a130-965cbfd71d54", - "prevId": "34edd0e6-71f6-4597-ae67-e79f8365a833", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.account": { - "name": "account", - "schema": "", - "columns": { - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "providerAccountId": { - "name": "providerAccountId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "refresh_token": { - "name": "refresh_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "access_token": { - "name": "access_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "token_type": { - "name": "token_type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "id_token": { - "name": "id_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "session_state": { - "name": "session_state", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "account_userId_user_id_fk": { - "name": "account_userId_user_id_fk", - "tableFrom": "account", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.api_key": { - "name": "api_key", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "key_hash": { - "name": "key_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "last_used_at": { - "name": "last_used_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "api_key_userId_user_id_fk": { - "name": "api_key_userId_user_id_fk", - "tableFrom": "api_key", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "api_key_key_hash_unique": { - "name": "api_key_key_hash_unique", - "nullsNotDistinct": false, - "columns": ["key_hash"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.audit_log": { - "name": "audit_log", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "action": { - "name": "action", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "resource_type": { - "name": "resource_type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "resource_id": { - "name": "resource_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "details": { - "name": "details", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "ip_address": { - "name": "ip_address", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "audit_log_user_id_user_id_fk": { - "name": "audit_log_user_id_user_id_fk", - "tableFrom": "audit_log", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.connection": { - "name": "connection", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "connection_type", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "configEncrypted": { - "name": "configEncrypted", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "allow_per_card_db": { - "name": "allow_per_card_db", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "visibility": { - "name": "visibility", - "type": "connection_visibility", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'private'" - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "connection_userId_user_id_fk": { - "name": "connection_userId_user_id_fk", - "tableFrom": "connection", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dashboard_share": { - "name": "dashboard_share", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "dashboardId": { - "name": "dashboardId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "role": { - "name": "role", - "type": "share_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "dashboard_share_dashboardId_dashboard_id_fk": { - "name": "dashboard_share_dashboardId_dashboard_id_fk", - "tableFrom": "dashboard_share", - "tableTo": "dashboard", - "columnsFrom": ["dashboardId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "dashboard_share_userId_user_id_fk": { - "name": "dashboard_share_userId_user_id_fk", - "tableFrom": "dashboard_share", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dashboard": { - "name": "dashboard", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "layoutJson": { - "name": "layoutJson", - "type": "jsonb", - "primaryKey": false, - "notNull": false, - "default": "'{\"version\":2,\"pages\":[{\"id\":\"page-1\",\"title\":\"Page 1\",\"widgets\":[],\"gridLayout\":[]}]}'::jsonb" - }, - "thumbnailJson": { - "name": "thumbnailJson", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "version": { - "name": "version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 1 - }, - "isPublic": { - "name": "isPublic", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updated_by": { - "name": "updated_by", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "dashboard_userId_user_id_fk": { - "name": "dashboard_userId_user_id_fk", - "tableFrom": "dashboard", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "dashboard_updated_by_user_id_fk": { - "name": "dashboard_updated_by_user_id_fk", - "tableFrom": "dashboard", - "tableTo": "user", - "columnsFrom": ["updated_by"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.session": { - "name": "session", - "schema": "", - "columns": { - "sessionToken": { - "name": "sessionToken", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "session_userId_user_id_fk": { - "name": "session_userId_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.sso_provider": { - "name": "sso_provider", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "protocol": { - "name": "protocol", - "type": "sso_protocol", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'oidc'" - }, - "issuer": { - "name": "issuer", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "client_id": { - "name": "client_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "client_secret_encrypted": { - "name": "client_secret_encrypted", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "scopes": { - "name": "scopes", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'openid profile email'" - }, - "claim_mappings": { - "name": "claim_mappings", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "auto_provision": { - "name": "auto_provision", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "default_role": { - "name": "default_role", - "type": "user_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'creator'" - }, - "enforce_sso": { - "name": "enforce_sso", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "enabled": { - "name": "enabled", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "sso_provider_tenant_issuer_unique": { - "name": "sso_provider_tenant_issuer_unique", - "nullsNotDistinct": false, - "columns": ["tenant_id", "issuer"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "emailVerified": { - "name": "emailVerified", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "passwordHash": { - "name": "passwordHash", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "role": { - "name": "role", - "type": "user_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'creator'" - }, - "can_write": { - "name": "can_write", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "force_password_change": { - "name": "force_password_change", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "passwordChangedAt": { - "name": "passwordChangedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "disabledAt": { - "name": "disabledAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "lastLoginAt": { - "name": "lastLoginAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "user_email_tenant_unique": { - "name": "user_email_tenant_unique", - "nullsNotDistinct": false, - "columns": ["email", "tenant_id"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.verificationToken": { - "name": "verificationToken", - "schema": "", - "columns": { - "identifier": { - "name": "identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "token": { - "name": "token", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.widget_template": { - "name": "widget_template", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tags": { - "name": "tags", - "type": "text[]", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - }, - "chartType": { - "name": "chartType", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "connectorType": { - "name": "connectorType", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "connectionId": { - "name": "connectionId", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "query": { - "name": "query", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "''" - }, - "params": { - "name": "params", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "settings": { - "name": "settings", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "previewImageUrl": { - "name": "previewImageUrl", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "createdBy": { - "name": "createdBy", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "widget_template_createdBy_user_id_fk": { - "name": "widget_template_createdBy_user_id_fk", - "tableFrom": "widget_template", - "tableTo": "user", - "columnsFrom": ["createdBy"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": { - "public.connection_type": { - "name": "connection_type", - "schema": "public", - "values": ["neo4j", "postgresql"] - }, - "public.connection_visibility": { - "name": "connection_visibility", - "schema": "public", - "values": ["private", "shared"] - }, - "public.share_role": { - "name": "share_role", - "schema": "public", - "values": ["viewer", "editor"] - }, - "public.sso_protocol": { - "name": "sso_protocol", - "schema": "public", - "values": ["oidc"] - }, - "public.user_role": { - "name": "user_role", - "schema": "public", - "values": ["admin", "creator", "reader"] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} diff --git a/app/drizzle/migrations/meta/0009_snapshot.json b/app/drizzle/migrations/meta/0009_snapshot.json deleted file mode 100644 index e5644e79..00000000 --- a/app/drizzle/migrations/meta/0009_snapshot.json +++ /dev/null @@ -1,988 +0,0 @@ -{ - "id": "aa735688-7be4-4a2b-955a-cabed6352a06", - "prevId": "5ec3aba2-98b3-4832-a130-965cbfd71d54", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.account": { - "name": "account", - "schema": "", - "columns": { - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "providerAccountId": { - "name": "providerAccountId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "refresh_token": { - "name": "refresh_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "access_token": { - "name": "access_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "token_type": { - "name": "token_type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "id_token": { - "name": "id_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "session_state": { - "name": "session_state", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "account_userId_user_id_fk": { - "name": "account_userId_user_id_fk", - "tableFrom": "account", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.api_key": { - "name": "api_key", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "key_hash": { - "name": "key_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "key_prefix": { - "name": "key_prefix", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "last_used_at": { - "name": "last_used_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "api_key_userId_user_id_fk": { - "name": "api_key_userId_user_id_fk", - "tableFrom": "api_key", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "api_key_key_hash_unique": { - "name": "api_key_key_hash_unique", - "nullsNotDistinct": false, - "columns": ["key_hash"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.audit_log": { - "name": "audit_log", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "action": { - "name": "action", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "resource_type": { - "name": "resource_type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "resource_id": { - "name": "resource_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "details": { - "name": "details", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "ip_address": { - "name": "ip_address", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "audit_log_user_id_user_id_fk": { - "name": "audit_log_user_id_user_id_fk", - "tableFrom": "audit_log", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.connection": { - "name": "connection", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "connection_type", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "configEncrypted": { - "name": "configEncrypted", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "allow_per_card_db": { - "name": "allow_per_card_db", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "visibility": { - "name": "visibility", - "type": "connection_visibility", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'private'" - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "connection_userId_user_id_fk": { - "name": "connection_userId_user_id_fk", - "tableFrom": "connection", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dashboard_share": { - "name": "dashboard_share", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "dashboardId": { - "name": "dashboardId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "role": { - "name": "role", - "type": "share_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "dashboard_share_dashboardId_dashboard_id_fk": { - "name": "dashboard_share_dashboardId_dashboard_id_fk", - "tableFrom": "dashboard_share", - "tableTo": "dashboard", - "columnsFrom": ["dashboardId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "dashboard_share_userId_user_id_fk": { - "name": "dashboard_share_userId_user_id_fk", - "tableFrom": "dashboard_share", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dashboard": { - "name": "dashboard", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "layoutJson": { - "name": "layoutJson", - "type": "jsonb", - "primaryKey": false, - "notNull": false, - "default": "'{\"version\":2,\"pages\":[{\"id\":\"page-1\",\"title\":\"Page 1\",\"widgets\":[],\"gridLayout\":[]}]}'::jsonb" - }, - "thumbnailJson": { - "name": "thumbnailJson", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "version": { - "name": "version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 1 - }, - "isPublic": { - "name": "isPublic", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updated_by": { - "name": "updated_by", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "dashboard_userId_user_id_fk": { - "name": "dashboard_userId_user_id_fk", - "tableFrom": "dashboard", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "dashboard_updated_by_user_id_fk": { - "name": "dashboard_updated_by_user_id_fk", - "tableFrom": "dashboard", - "tableTo": "user", - "columnsFrom": ["updated_by"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.session": { - "name": "session", - "schema": "", - "columns": { - "sessionToken": { - "name": "sessionToken", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "session_userId_user_id_fk": { - "name": "session_userId_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.sso_provider": { - "name": "sso_provider", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "protocol": { - "name": "protocol", - "type": "sso_protocol", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'oidc'" - }, - "issuer": { - "name": "issuer", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "client_id": { - "name": "client_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "client_secret_encrypted": { - "name": "client_secret_encrypted", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "scopes": { - "name": "scopes", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'openid profile email'" - }, - "claim_mappings": { - "name": "claim_mappings", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "auto_provision": { - "name": "auto_provision", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "default_role": { - "name": "default_role", - "type": "user_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'creator'" - }, - "enforce_sso": { - "name": "enforce_sso", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "enabled": { - "name": "enabled", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "sso_provider_tenant_issuer_unique": { - "name": "sso_provider_tenant_issuer_unique", - "nullsNotDistinct": false, - "columns": ["tenant_id", "issuer"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "emailVerified": { - "name": "emailVerified", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "passwordHash": { - "name": "passwordHash", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "role": { - "name": "role", - "type": "user_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'creator'" - }, - "can_write": { - "name": "can_write", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "force_password_change": { - "name": "force_password_change", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "passwordChangedAt": { - "name": "passwordChangedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "disabledAt": { - "name": "disabledAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "lastLoginAt": { - "name": "lastLoginAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "user_email_tenant_unique": { - "name": "user_email_tenant_unique", - "nullsNotDistinct": false, - "columns": ["email", "tenant_id"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.verificationToken": { - "name": "verificationToken", - "schema": "", - "columns": { - "identifier": { - "name": "identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "token": { - "name": "token", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.widget_template": { - "name": "widget_template", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tags": { - "name": "tags", - "type": "text[]", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - }, - "chartType": { - "name": "chartType", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "connectorType": { - "name": "connectorType", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "connectionId": { - "name": "connectionId", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "query": { - "name": "query", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "''" - }, - "params": { - "name": "params", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "settings": { - "name": "settings", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "previewImageUrl": { - "name": "previewImageUrl", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "createdBy": { - "name": "createdBy", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "widget_template_createdBy_user_id_fk": { - "name": "widget_template_createdBy_user_id_fk", - "tableFrom": "widget_template", - "tableTo": "user", - "columnsFrom": ["createdBy"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": { - "public.connection_type": { - "name": "connection_type", - "schema": "public", - "values": ["neo4j", "postgresql"] - }, - "public.connection_visibility": { - "name": "connection_visibility", - "schema": "public", - "values": ["private", "shared"] - }, - "public.share_role": { - "name": "share_role", - "schema": "public", - "values": ["viewer", "editor"] - }, - "public.sso_protocol": { - "name": "sso_protocol", - "schema": "public", - "values": ["oidc"] - }, - "public.user_role": { - "name": "user_role", - "schema": "public", - "values": ["admin", "creator", "reader"] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} diff --git a/app/drizzle/migrations/meta/0010_snapshot.json b/app/drizzle/migrations/meta/0010_snapshot.json deleted file mode 100644 index 249f7222..00000000 --- a/app/drizzle/migrations/meta/0010_snapshot.json +++ /dev/null @@ -1,1045 +0,0 @@ -{ - "id": "aba2fceb-6d35-4394-bf30-a78bf205c014", - "prevId": "aa735688-7be4-4a2b-955a-cabed6352a06", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.account": { - "name": "account", - "schema": "", - "columns": { - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "providerAccountId": { - "name": "providerAccountId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "refresh_token": { - "name": "refresh_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "access_token": { - "name": "access_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "token_type": { - "name": "token_type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "id_token": { - "name": "id_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "session_state": { - "name": "session_state", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "account_userId_user_id_fk": { - "name": "account_userId_user_id_fk", - "tableFrom": "account", - "tableTo": "user", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.api_key": { - "name": "api_key", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "key_hash": { - "name": "key_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "key_prefix": { - "name": "key_prefix", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "last_used_at": { - "name": "last_used_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "api_key_userId_user_id_fk": { - "name": "api_key_userId_user_id_fk", - "tableFrom": "api_key", - "tableTo": "user", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "api_key_key_hash_unique": { - "name": "api_key_key_hash_unique", - "nullsNotDistinct": false, - "columns": [ - "key_hash" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.audit_log": { - "name": "audit_log", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "action": { - "name": "action", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "resource_type": { - "name": "resource_type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "resource_id": { - "name": "resource_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "details": { - "name": "details", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "ip_address": { - "name": "ip_address", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "audit_log_user_id_user_id_fk": { - "name": "audit_log_user_id_user_id_fk", - "tableFrom": "audit_log", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.connection": { - "name": "connection", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "connection_type", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "configEncrypted": { - "name": "configEncrypted", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "allow_per_card_db": { - "name": "allow_per_card_db", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "visibility": { - "name": "visibility", - "type": "connection_visibility", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'private'" - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "connection_userId_user_id_fk": { - "name": "connection_userId_user_id_fk", - "tableFrom": "connection", - "tableTo": "user", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dashboard_share": { - "name": "dashboard_share", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "dashboardId": { - "name": "dashboardId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "role": { - "name": "role", - "type": "share_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "dashboard_share_dashboardId_dashboard_id_fk": { - "name": "dashboard_share_dashboardId_dashboard_id_fk", - "tableFrom": "dashboard_share", - "tableTo": "dashboard", - "columnsFrom": [ - "dashboardId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "dashboard_share_userId_user_id_fk": { - "name": "dashboard_share_userId_user_id_fk", - "tableFrom": "dashboard_share", - "tableTo": "user", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dashboard": { - "name": "dashboard", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "layoutJson": { - "name": "layoutJson", - "type": "jsonb", - "primaryKey": false, - "notNull": false, - "default": "'{\"version\":2,\"pages\":[{\"id\":\"page-1\",\"title\":\"Page 1\",\"widgets\":[],\"gridLayout\":[]}]}'::jsonb" - }, - "version": { - "name": "version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 1 - }, - "isPublic": { - "name": "isPublic", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updated_by": { - "name": "updated_by", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "dashboard_userId_user_id_fk": { - "name": "dashboard_userId_user_id_fk", - "tableFrom": "dashboard", - "tableTo": "user", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "dashboard_updated_by_user_id_fk": { - "name": "dashboard_updated_by_user_id_fk", - "tableFrom": "dashboard", - "tableTo": "user", - "columnsFrom": [ - "updated_by" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.session": { - "name": "session", - "schema": "", - "columns": { - "sessionToken": { - "name": "sessionToken", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "session_userId_user_id_fk": { - "name": "session_userId_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.sso_provider": { - "name": "sso_provider", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "protocol": { - "name": "protocol", - "type": "sso_protocol", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'oidc'" - }, - "issuer": { - "name": "issuer", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "client_id": { - "name": "client_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "client_secret_encrypted": { - "name": "client_secret_encrypted", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "scopes": { - "name": "scopes", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'openid profile email'" - }, - "claim_mappings": { - "name": "claim_mappings", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "auto_provision": { - "name": "auto_provision", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "default_role": { - "name": "default_role", - "type": "user_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'creator'" - }, - "enforce_sso": { - "name": "enforce_sso", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "enabled": { - "name": "enabled", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "sso_provider_tenant_issuer_unique": { - "name": "sso_provider_tenant_issuer_unique", - "nullsNotDistinct": false, - "columns": [ - "tenant_id", - "issuer" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "emailVerified": { - "name": "emailVerified", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "passwordHash": { - "name": "passwordHash", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "role": { - "name": "role", - "type": "user_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'creator'" - }, - "can_write": { - "name": "can_write", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "force_password_change": { - "name": "force_password_change", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "passwordChangedAt": { - "name": "passwordChangedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "disabledAt": { - "name": "disabledAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "lastLoginAt": { - "name": "lastLoginAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "user_email_tenant_unique": { - "name": "user_email_tenant_unique", - "nullsNotDistinct": false, - "columns": [ - "email", - "tenant_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.verificationToken": { - "name": "verificationToken", - "schema": "", - "columns": { - "identifier": { - "name": "identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "token": { - "name": "token", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.widget_template": { - "name": "widget_template", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tags": { - "name": "tags", - "type": "text[]", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - }, - "chartType": { - "name": "chartType", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "connectorType": { - "name": "connectorType", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "connectionId": { - "name": "connectionId", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "query": { - "name": "query", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "''" - }, - "params": { - "name": "params", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "settings": { - "name": "settings", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "previewImageUrl": { - "name": "previewImageUrl", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "createdBy": { - "name": "createdBy", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "widget_template_createdBy_user_id_fk": { - "name": "widget_template_createdBy_user_id_fk", - "tableFrom": "widget_template", - "tableTo": "user", - "columnsFrom": [ - "createdBy" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": { - "public.connection_type": { - "name": "connection_type", - "schema": "public", - "values": [ - "neo4j", - "postgresql" - ] - }, - "public.connection_visibility": { - "name": "connection_visibility", - "schema": "public", - "values": [ - "private", - "shared" - ] - }, - "public.share_role": { - "name": "share_role", - "schema": "public", - "values": [ - "viewer", - "editor" - ] - }, - "public.sso_protocol": { - "name": "sso_protocol", - "schema": "public", - "values": [ - "oidc" - ] - }, - "public.user_role": { - "name": "user_role", - "schema": "public", - "values": [ - "admin", - "creator", - "reader" - ] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file diff --git a/app/drizzle/migrations/meta/_journal.json b/app/drizzle/migrations/meta/_journal.json index ba974406..aaacb07a 100644 --- a/app/drizzle/migrations/meta/_journal.json +++ b/app/drizzle/migrations/meta/_journal.json @@ -5,79 +5,9 @@ { "idx": 0, "version": "7", - "when": 1773765270786, - "tag": "0000_wooden_zeigeist", - "breakpoints": true - }, - { - "idx": 1, - "version": "7", - "when": 1775088043513, - "tag": "0001_rapid_iron_monger", - "breakpoints": true - }, - { - "idx": 2, - "version": "7", - "when": 1775596690039, - "tag": "0002_redundant_night_nurse", - "breakpoints": true - }, - { - "idx": 3, - "version": "7", - "when": 1776890185225, - "tag": "0003_loving_centennial", - "breakpoints": true - }, - { - "idx": 4, - "version": "7", - "when": 1777474513745, - "tag": "0004_furry_scourge", - "breakpoints": true - }, - { - "idx": 5, - "version": "7", - "when": 1778862246888, - "tag": "0005_perfect_paibok", - "breakpoints": true - }, - { - "idx": 6, - "version": "7", - "when": 1778862281231, - "tag": "0006_busy_champions", - "breakpoints": true - }, - { - "idx": 7, - "version": "7", - "when": 1778862299752, - "tag": "0007_free_loners", - "breakpoints": true - }, - { - "idx": 8, - "version": "7", - "when": 1781144139217, - "tag": "0008_lumpy_jubilee", - "breakpoints": true - }, - { - "idx": 9, - "version": "7", - "when": 1781383285524, - "tag": "0009_dazzling_stranger", - "breakpoints": true - }, - { - "idx": 10, - "version": "7", - "when": 1781532674061, - "tag": "0010_tiny_amphibian", + "when": 1782898508468, + "tag": "0000_cooing_greymalkin", "breakpoints": true } ] -} \ No newline at end of file +} diff --git a/app/next.config.ts b/app/next.config.ts index a3413bf0..883cfbd1 100644 --- a/app/next.config.ts +++ b/app/next.config.ts @@ -24,7 +24,11 @@ const nextConfig: NextConfig = { // Enable source maps in production for E2E coverage collection (nextcov). productionBrowserSourceMaps: process.env.E2E_COVERAGE === "1", outputFileTracingRoot: resolve(import.meta.dirname, ".."), - transpilePackages: ["@neoboard/components", "@neoboard/connection"], + transpilePackages: [ + "@neoboard/components", + "@neoboard/connection", + "@neoboard/connector-sdk", + ], serverExternalPackages: [ "postgres", "pg", diff --git a/app/src/app/(dashboard)/connections/page.tsx b/app/src/app/(dashboard)/connections/page.tsx index 13511c61..37089a2e 100644 --- a/app/src/app/(dashboard)/connections/page.tsx +++ b/app/src/app/(dashboard)/connections/page.tsx @@ -33,11 +33,13 @@ import { ConfirmDialog, ConnectionCard, PasswordInput, + DynamicConnectionFields, Alert, AlertDescription, useToast, } from "@neoboard/components"; import type { ConnectionState } from "@neoboard/components"; +import { connectionFieldsFor } from "@/lib/connector/connection-form-fields"; import { type ConnectorType, CONNECTOR_LABELS, @@ -545,62 +547,17 @@ export default function ConnectionsPage() { /> -
- - ) => - setForm((f) => ({ ...f, uri: e.target.value })) - } - required - placeholder={ - form.type === "neo4j" - ? "bolt://localhost:7687" - : "postgresql://localhost:5432" - } - /> -
- -
-
- - ) => - setForm((f) => ({ ...f, username: e.target.value })) - } - required - /> -
- -
- - ) => - setForm((f) => ({ ...f, password: e.target.value })) - } - required - /> -
-
- -
- - ) => - setForm((f) => ({ ...f, database: e.target.value })) - } - /> -
+ {/* Credential fields generated from the connector's + formFields (#1118) — no hardcoded per-connector arrays. */} + + // Built-in credential fields are all text/password, so the + // value is always a string here. + setForm((f) => ({ ...f, [name]: value as string })) + } + /> {/* Advanced Settings */}
diff --git a/app/src/app/(dashboard)/widget-library/page.tsx b/app/src/app/(dashboard)/widget-library/page.tsx index 9079b3f1..83aefc56 100644 --- a/app/src/app/(dashboard)/widget-library/page.tsx +++ b/app/src/app/(dashboard)/widget-library/page.tsx @@ -45,8 +45,8 @@ import { type ConnectorType, CONNECTOR_TYPES, CONNECTOR_LABELS, - CONNECTOR_LANGUAGES, } from "@/lib/connector/connector-types"; +import { CONNECTOR_QUERY_LANGUAGES } from "@neoboard/connection/query-languages"; import { WidgetEditorModal } from "@/components/widget-editor-modal"; function TemplateCard({ @@ -182,10 +182,7 @@ function TemplateCard({ ) : ( )} diff --git a/app/src/app/api/connections/__tests__/route.test.ts b/app/src/app/api/connections/__tests__/route.test.ts index cf06b035..19bce26e 100644 --- a/app/src/app/api/connections/__tests__/route.test.ts +++ b/app/src/app/api/connections/__tests__/route.test.ts @@ -47,6 +47,11 @@ vi.mock("@/lib/connector/schema-prefetch", () => ({ prefetchSchema: mockPrefetchSchema, })); vi.mock("next/server", () => nextResponseMockFactory()); +// Connector-type validation is registry-driven (#1121); stub it so the route +// tests don't load the driver-heavy connection registry. +vi.mock("@/lib/connector/registered-types", () => ({ + isRegisteredConnectorType: (t: string) => t === "neo4j" || t === "postgresql", +})); vi.mock("@/lib/auth/errors", () => ({ UnauthorizedError, ForbiddenError })); const SESSION = { diff --git a/app/src/app/api/connections/list-databases-inline/__tests__/route.test.ts b/app/src/app/api/connections/list-databases-inline/__tests__/route.test.ts index 3fd17b49..1a723f18 100644 --- a/app/src/app/api/connections/list-databases-inline/__tests__/route.test.ts +++ b/app/src/app/api/connections/list-databases-inline/__tests__/route.test.ts @@ -23,6 +23,11 @@ vi.mock("@/lib/query/query-executor", () => ({ listSchemas: mockListSchemas, })); vi.mock("next/server", () => nextResponseMockFactory()); +// Connector-type validation is registry-driven (#1121); stub it so the route +// tests don't load the driver-heavy connection registry. +vi.mock("@/lib/connector/registered-types", () => ({ + isRegisteredConnectorType: (t: string) => t === "neo4j" || t === "postgresql", +})); vi.mock("@/lib/auth/errors", () => ({ UnauthorizedError: class extends Error { constructor() { diff --git a/app/src/app/api/connections/test-inline/__tests__/route.test.ts b/app/src/app/api/connections/test-inline/__tests__/route.test.ts index 34c62f87..50ccd016 100644 --- a/app/src/app/api/connections/test-inline/__tests__/route.test.ts +++ b/app/src/app/api/connections/test-inline/__tests__/route.test.ts @@ -21,6 +21,11 @@ vi.mock("@/lib/query/query-executor", () => ({ testConnection: mockTestConnection, })); vi.mock("next/server", () => nextResponseMockFactory()); +// Connector-type validation is registry-driven (#1121); stub it so the route +// tests don't load the driver-heavy connection registry. +vi.mock("@/lib/connector/registered-types", () => ({ + isRegisteredConnectorType: (t: string) => t === "neo4j" || t === "postgresql", +})); const SESSION = { userId: "user-1", diff --git a/app/src/components/widget-editor-modal.tsx b/app/src/components/widget-editor-modal.tsx index 27e5b570..2e31d1f4 100644 --- a/app/src/components/widget-editor-modal.tsx +++ b/app/src/components/widget-editor-modal.tsx @@ -78,6 +78,7 @@ import { AdvancedFormRefreshSection } from "./widget-editor/advanced-form-refres import { LabMetadataForm } from "./widget-editor/lab-metadata-form"; import { ModalFooter } from "./widget-editor/modal-footer"; import { WidgetPreviewPanel } from "./widget-editor/widget-preview-panel"; +import { editorLanguageForConnector } from "@/lib/connector/editor-language"; export interface WidgetEditorModalProps { open: boolean; @@ -348,9 +349,9 @@ export function WidgetEditorModal({ store.setDialogStep("main"); } - // Pass connector type directly — the language resolver registry maps it - // to the right editor extension (e.g., "neo4j" → cypher, "postgresql" → sql). - const editorLanguage = selectedConnection?.type ?? "cypher"; + // Drive the editor language from the connector's declared queryLanguage + // (#1120) rather than its type; unknown / no connector → plain text. + const editorLanguage = editorLanguageForConnector(selectedConnection?.type); // Chart types compatible with the selected connector const compatibleChartTypes = useMemo( diff --git a/app/src/components/widget-editor/__tests__/template-browser.test.tsx b/app/src/components/widget-editor/__tests__/template-browser.test.tsx index 94a010e2..a836f2f6 100644 --- a/app/src/components/widget-editor/__tests__/template-browser.test.tsx +++ b/app/src/components/widget-editor/__tests__/template-browser.test.tsx @@ -34,8 +34,8 @@ vi.mock("@/lib/plugin/chart-helpers", () => ({ getChartConfig: (t: string) => ({ label: t }), })); -vi.mock("@/lib/connector/connector-types", () => ({ - CONNECTOR_LANGUAGES: { neo4j: "Cypher", postgresql: "SQL" }, +vi.mock("@neoboard/connection/query-languages", () => ({ + CONNECTOR_QUERY_LANGUAGES: { neo4j: "cypher", postgresql: "sql" }, })); import { TemplateBrowser } from "../template-browser"; diff --git a/app/src/components/widget-editor/template-browser.tsx b/app/src/components/widget-editor/template-browser.tsx index a5b1f537..f97f4f67 100644 --- a/app/src/components/widget-editor/template-browser.tsx +++ b/app/src/components/widget-editor/template-browser.tsx @@ -5,7 +5,7 @@ import { FlaskConical } from "lucide-react"; import type { WidgetTemplate } from "@/lib/db/schema"; import type { ConnectorType } from "@/lib/connector/connector-types"; import { getChartConfig } from "@/lib/plugin/chart-helpers"; -import { CONNECTOR_LANGUAGES } from "@/lib/connector/connector-types"; +import { CONNECTOR_QUERY_LANGUAGES } from "@neoboard/connection/query-languages"; import { Badge, Button, @@ -87,8 +87,7 @@ export function TemplateBrowser({ diff --git a/app/src/lib/__tests__/connector/editor-language.test.ts b/app/src/lib/__tests__/connector/editor-language.test.ts new file mode 100644 index 00000000..8c0d443e --- /dev/null +++ b/app/src/lib/__tests__/connector/editor-language.test.ts @@ -0,0 +1,20 @@ +import { describe, it, expect } from "vitest"; +import { editorLanguageForConnector } from "@/lib/connector/editor-language"; + +describe("editorLanguageForConnector", () => { + it("maps neo4j to cypher", () => { + expect(editorLanguageForConnector("neo4j")).toBe("cypher"); + }); + + it("maps postgresql to sql", () => { + expect(editorLanguageForConnector("postgresql")).toBe("sql"); + }); + + it("returns '' (plain text) for an unknown connector type", () => { + expect(editorLanguageForConnector("mysql")).toBe(""); + }); + + it("returns '' when no type is given", () => { + expect(editorLanguageForConnector()).toBe(""); + }); +}); diff --git a/app/src/lib/__tests__/connector/registered-types.test.ts b/app/src/lib/__tests__/connector/registered-types.test.ts new file mode 100644 index 00000000..a976f3a8 --- /dev/null +++ b/app/src/lib/__tests__/connector/registered-types.test.ts @@ -0,0 +1,20 @@ +import { describe, it, expect, vi } from "vitest"; + +// Mock the connection-adapter seam so this stays a pure unit test (no drivers). +vi.mock("@/lib/connector/connection-adapter", () => ({ + getConnector: (type: string) => + type === "neo4j" || type === "postgresql" ? { type } : undefined, +})); + +import { isRegisteredConnectorType } from "@/lib/connector/registered-types"; + +describe("isRegisteredConnectorType", () => { + it("returns true for a registered connector type", () => { + expect(isRegisteredConnectorType("neo4j")).toBe(true); + expect(isRegisteredConnectorType("postgresql")).toBe(true); + }); + + it("returns false for an unregistered type", () => { + expect(isRegisteredConnectorType("mysql")).toBe(false); + }); +}); diff --git a/app/src/lib/__tests__/connector/schema-prefetch.test.ts b/app/src/lib/__tests__/connector/schema-prefetch.test.ts index 53f5907d..ab73486a 100644 --- a/app/src/lib/__tests__/connector/schema-prefetch.test.ts +++ b/app/src/lib/__tests__/connector/schema-prefetch.test.ts @@ -1,18 +1,29 @@ /** * schema-prefetch — unit tests for pure logic (node environment, no DOM). * - * Only `buildAuthConfig` is fully unit-testable here because it is a pure - * function with no external dependencies. - * - * `fetchConnectionSchema` and `prefetchSchema` require the compiled - * connection package modules (Neo4jSchemaManager, PostgresSchemaManager) - * which are not available in the Vitest node environment. Those code paths - * are exercised via the Playwright E2E suite. + * `buildAuthConfig` is a pure function. `fetchConnectionSchema` / + * `prefetchSchema` dispatch through the connection-adapter's + * `getSchemaManager` (#1119), which we mock here so the registry-lookup and + * null-guard branches are unit-covered without the real driver modules. */ -import { describe, it, expect } from "vitest"; -import { buildAuthConfig } from "@/lib/connector/schema-prefetch"; +import { describe, it, expect, vi, beforeEach } from "vitest"; import type { ConnectionCredentials } from "@/lib/query/query-executor"; +const getSchemaManager = vi.fn(); +vi.mock("@/lib/connector/connection-adapter", () => ({ + getSchemaManager: (type: string) => getSchemaManager(type), +})); + +import { + buildAuthConfig, + fetchConnectionSchema, + prefetchSchema, +} from "@/lib/connector/schema-prefetch"; + +beforeEach(() => { + getSchemaManager.mockReset(); +}); + const baseCredentials: ConnectionCredentials = { uri: "bolt://localhost:7687", username: "neo4j", @@ -102,19 +113,71 @@ describe("buildAuthConfig", () => { }); }); +describe("fetchConnectionSchema", () => { + const creds: ConnectionCredentials = { + uri: "bolt://localhost:7687", + username: "neo4j", + password: "secret", + }; + + it("resolves the schema manager by type and returns its schema", async () => { + const schema = { type: "neo4j", labels: ["Person"] }; + const fetchSchema = vi.fn().mockResolvedValue(schema); + getSchemaManager.mockReturnValue({ fetchSchema }); + + const result = await fetchConnectionSchema("neo4j", creds); + + expect(getSchemaManager).toHaveBeenCalledWith("neo4j"); + // Manager receives the built auth config (db embedded, NATIVE auth). + expect(fetchSchema).toHaveBeenCalledWith( + expect.objectContaining({ uri: creds.uri, authType: 1 }), + ); + expect(result).toBe(schema); + }); + + it("returns null when the connector type has no schema manager", async () => { + getSchemaManager.mockReturnValue(undefined); + const result = await fetchConnectionSchema( + "unknown" as unknown as Parameters[0], + creds, + ); + expect(result).toBeNull(); + }); +}); + +describe("prefetchSchema", () => { + const creds: ConnectionCredentials = { + uri: "postgresql://localhost:5432", + username: "pg", + password: "pw", + }; + + it("fires the fetch for the resolved manager", async () => { + const fetchSchema = vi.fn().mockResolvedValue({}); + getSchemaManager.mockReturnValue({ fetchSchema }); + prefetchSchema("postgresql", creds); + await vi.waitFor(() => expect(fetchSchema).toHaveBeenCalled()); + }); + + it("swallows errors (schema is a non-critical cache)", async () => { + const fetchSchema = vi.fn().mockRejectedValue(new Error("boom")); + getSchemaManager.mockReturnValue({ fetchSchema }); + // Must not throw synchronously or reject unhandled. + expect(() => prefetchSchema("postgresql", creds)).not.toThrow(); + await vi.waitFor(() => expect(fetchSchema).toHaveBeenCalled()); + }); +}); + describe("schema-prefetch module exports", () => { - it("exports buildAuthConfig as a function", async () => { - const mod = await import("@/lib/connector/schema-prefetch"); - expect(typeof mod.buildAuthConfig).toBe("function"); + it("exports buildAuthConfig as a function", () => { + expect(typeof buildAuthConfig).toBe("function"); }); - it("exports fetchConnectionSchema as a function", async () => { - const mod = await import("@/lib/connector/schema-prefetch"); - expect(typeof mod.fetchConnectionSchema).toBe("function"); + it("exports fetchConnectionSchema as a function", () => { + expect(typeof fetchConnectionSchema).toBe("function"); }); - it("exports prefetchSchema as a function", async () => { - const mod = await import("@/lib/connector/schema-prefetch"); - expect(typeof mod.prefetchSchema).toBe("function"); + it("exports prefetchSchema as a function", () => { + expect(typeof prefetchSchema).toBe("function"); }); }); diff --git a/app/src/lib/__tests__/query/query-executor-core.test.ts b/app/src/lib/__tests__/query/query-executor-core.test.ts index 74d61c0f..f1b6c6ad 100644 --- a/app/src/lib/__tests__/query/query-executor-core.test.ts +++ b/app/src/lib/__tests__/query/query-executor-core.test.ts @@ -20,7 +20,7 @@ const mockCreateConnectionModule = vi.fn(() => ({ vi.mock("@/lib/connector/connection-adapter", () => ({ createConnectionModule: mockCreateConnectionModule, DEFAULT_CONNECTION_CONFIG: { connectionTimeout: 30000, timeout: 30000 }, - ConnectionTypes: { NEO4J: 1, POSTGRESQL: 2 }, + ConnectionTypes: { UNKNOWN: 0, NEO4J: 1, POSTGRESQL: 2 }, })); // Mirror the QueryStatus enum from @neoboard/connection (integer values are @@ -58,7 +58,7 @@ describe("query-executor", () => { vi.doMock("../connection-adapter", () => ({ createConnectionModule: mockCreateConnectionModule, DEFAULT_CONNECTION_CONFIG: { connectionTimeout: 30000, timeout: 30000 }, - ConnectionTypes: { NEO4J: 1, POSTGRESQL: 2 }, + ConnectionTypes: { UNKNOWN: 0, NEO4J: 1, POSTGRESQL: 2 }, })); const mod = await import("@/lib/query/query-executor"); executeQuery = mod.executeQuery; @@ -111,6 +111,24 @@ describe("query-executor", () => { }); }); + it("maps a registry-supplied connector type to its module (#1121)", async () => { + mockRunQuery.mockImplementation( + (_p: unknown, cbs: { onSuccess: (v: unknown) => void }) => { + cbs.onSuccess([{ x: 1 }]); + }, + ); + + // A type that isn't a built-in — the executor must still resolve its + // module through the registry (createConnectionModule), no per-type branch. + await executeQuery("mysql", pgCreds, { query: "SELECT 1" }); + + expect(mockCreateConnectionModule).toHaveBeenCalledWith( + "mysql", + expect.objectContaining({ uri: pgCreds.uri }), + expect.any(Object), + ); + }); + it("rejects when runQuery calls onFail", async () => { mockRunQuery.mockImplementation( (_p: unknown, cbs: { onFail: (v: unknown) => void }) => { @@ -690,7 +708,7 @@ describe("query-executor", () => { vi.doMock("../connection-adapter", () => ({ createConnectionModule: mockCreateConnectionModule, DEFAULT_CONNECTION_CONFIG: { connectionTimeout: 30000, timeout: 30000 }, - ConnectionTypes: { NEO4J: 1, POSTGRESQL: 2 }, + ConnectionTypes: { UNKNOWN: 0, NEO4J: 1, POSTGRESQL: 2 }, })); const mod = await import("@/lib/query/query-executor"); listDatabases = mod.listDatabases; @@ -729,7 +747,7 @@ describe("query-executor", () => { vi.doMock("../connection-adapter", () => ({ createConnectionModule: mockCreateConnectionModule, DEFAULT_CONNECTION_CONFIG: { connectionTimeout: 30000, timeout: 30000 }, - ConnectionTypes: { NEO4J: 1, POSTGRESQL: 2 }, + ConnectionTypes: { UNKNOWN: 0, NEO4J: 1, POSTGRESQL: 2 }, })); const mod = await import("@/lib/query/query-executor"); listSchemas = mod.listSchemas; diff --git a/app/src/lib/__tests__/shared/schemas.test.ts b/app/src/lib/__tests__/shared/schemas.test.ts index b2df9ebf..e93f60bf 100644 --- a/app/src/lib/__tests__/shared/schemas.test.ts +++ b/app/src/lib/__tests__/shared/schemas.test.ts @@ -1,4 +1,13 @@ -import { describe, it, expect } from "vitest"; +import { describe, it, expect, vi } from "vitest"; + +// Connector-type validation is registry-driven (#1121). Mock the registry +// membership check so the schema tests are deterministic and don't load DB +// drivers: built-ins + one fixture type are "registered". +vi.mock("@/lib/connector/registered-types", () => ({ + isRegisteredConnectorType: (t: string) => + ["neo4j", "postgresql", "fixture-db"].includes(t), +})); + import { connectionConfigSchema, createConnectionSchema, @@ -189,6 +198,15 @@ describe("createConnectionSchema", () => { expect(result.success).toBe(true); }); + it("accepts a registry-supplied connector type (no core change per type)", () => { + const result = createConnectionSchema.safeParse({ + name: "My Fixture", + type: "fixture-db", + config: { uri: "fixture://host", username: "u", password: "p" }, + }); + expect(result.success).toBe(true); + }); + it("rejects missing name", () => { const result = createConnectionSchema.safeParse({ type: "neo4j", diff --git a/app/src/lib/connector/connection-adapter.ts b/app/src/lib/connector/connection-adapter.ts index 2d1b6fab..ffb1671d 100644 --- a/app/src/lib/connector/connection-adapter.ts +++ b/app/src/lib/connector/connection-adapter.ts @@ -9,6 +9,14 @@ import { createConnectionModule, DEFAULT_CONNECTION_CONFIG, ConnectionTypes, + getSchemaManager, + getConnector, } from "@neoboard/connection"; -export { createConnectionModule, DEFAULT_CONNECTION_CONFIG, ConnectionTypes }; +export { + createConnectionModule, + DEFAULT_CONNECTION_CONFIG, + ConnectionTypes, + getSchemaManager, + getConnector, +}; diff --git a/app/src/lib/connector/connection-form-fields.ts b/app/src/lib/connector/connection-form-fields.ts new file mode 100644 index 00000000..147ae8e7 --- /dev/null +++ b/app/src/lib/connector/connection-form-fields.ts @@ -0,0 +1,26 @@ +/** + * Adapts a connector's `formFields` (SDK `ConnectorFormField`, keyed by + * `key`) into the `DynamicConnectionField` shape the UI renderer expects + * (keyed by `name`). The connection form is generated from this — no + * hardcoded per-connector field arrays in the app (#1118). + * + * Imports the client-safe `/form-fields` subpath, which pulls in no DB + * drivers, so this is safe in the client connections page. + */ +import { CONNECTOR_FORM_FIELDS } from "@neoboard/connection/form-fields"; +import type { DynamicConnectionField } from "@neoboard/components"; +import type { ConnectorType } from "./connector-types"; + +export function connectionFieldsFor( + type: ConnectorType, +): DynamicConnectionField[] { + return (CONNECTOR_FORM_FIELDS[type] ?? []).map((f) => ({ + name: f.key, + label: f.label, + type: f.type, + required: f.required, + placeholder: f.placeholder, + description: f.description, + options: f.options, + })); +} diff --git a/app/src/lib/connector/connector-types.ts b/app/src/lib/connector/connector-types.ts index ead2be3c..25dde179 100644 --- a/app/src/lib/connector/connector-types.ts +++ b/app/src/lib/connector/connector-types.ts @@ -5,6 +5,5 @@ export { CONNECTOR_TYPES, CONNECTOR_LABELS, - CONNECTOR_LANGUAGES, type ConnectorType, } from "@neoboard/connection/connector-types"; diff --git a/app/src/lib/connector/editor-language.ts b/app/src/lib/connector/editor-language.ts new file mode 100644 index 00000000..d5f71704 --- /dev/null +++ b/app/src/lib/connector/editor-language.ts @@ -0,0 +1,11 @@ +import { CONNECTOR_QUERY_LANGUAGES } from "@neoboard/connection/query-languages"; + +/** + * The CodeMirror editor language for a connector type (#1120). Driven by the + * connector's declared `queryLanguage`; returns "" (plain text, no + * highlighting) when the type is unknown or absent, so registry-supplied + * connectors without a known language get a neutral editor. + */ +export function editorLanguageForConnector(type?: string): string { + return CONNECTOR_QUERY_LANGUAGES[type ?? ""] ?? ""; +} diff --git a/app/src/lib/connector/registered-types.ts b/app/src/lib/connector/registered-types.ts new file mode 100644 index 00000000..1d2d064b --- /dev/null +++ b/app/src/lib/connector/registered-types.ts @@ -0,0 +1,14 @@ +import { getConnector } from "./connection-adapter"; + +/** + * True if `type` is a registered connector (built-in or external), driven by + * the runtime registry (#1121) — so a registry-supplied connector is + * first-class, with no hardcoded `"neo4j" | "postgresql"` union in + * validation/execution. + * + * Server-side only (the registry pulls DB drivers). Routed through + * connection-adapter so it stays mockable in unit tests. + */ +export function isRegisteredConnectorType(type: string): boolean { + return getConnector(type) !== undefined; +} diff --git a/app/src/lib/connector/schema-prefetch.ts b/app/src/lib/connector/schema-prefetch.ts index 1b39fd50..70b4cc68 100644 --- a/app/src/lib/connector/schema-prefetch.ts +++ b/app/src/lib/connector/schema-prefetch.ts @@ -1,10 +1,6 @@ import type { ConnectionCredentials } from "@/lib/query/query-executor"; -import type { ConnectorType } from "@/lib/connector/connector-types"; import { ensureDatabaseInUri } from "@/lib/query/query-params"; -import { - Neo4jSchemaManager, - PostgresSchemaManager, -} from "@neoboard/connection"; +import { getSchemaManager } from "@/lib/connector/connection-adapter"; /** * Builds the auth configuration object for schema manager calls. @@ -25,18 +21,13 @@ export function buildAuthConfig(credentials: ConnectionCredentials) { * after connection create/update. */ export async function fetchConnectionSchema( - type: ConnectorType, + type: string, credentials: ConnectionCredentials, ): Promise { - const authConfig = buildAuthConfig(credentials); - - if (type === "neo4j") { - const manager = new Neo4jSchemaManager(); - return manager.fetchSchema(authConfig); - } else { - const manager = new PostgresSchemaManager(); - return manager.fetchSchema(authConfig); - } + // Registry-keyed dispatch (#1119) — no hardcoded per-type branching. + const manager = getSchemaManager(type); + if (!manager) return null; // connector type has no schema introspection + return manager.fetchSchema(buildAuthConfig(credentials)); } /** @@ -44,7 +35,7 @@ export async function fetchConnectionSchema( * Errors are swallowed — schema is a cache; failure is non-critical. */ export function prefetchSchema( - type: ConnectorType, + type: string, credentials: ConnectionCredentials, ): void { fetchConnectionSchema(type, credentials).catch(() => { diff --git a/app/src/lib/db/schema.ts b/app/src/lib/db/schema.ts index 95f899e9..afe72ef5 100644 --- a/app/src/lib/db/schema.ts +++ b/app/src/lib/db/schema.ts @@ -94,10 +94,9 @@ export const verificationTokens = pgTable( // ─── Application tables ────────────────────────────────────────────── -export const connectionTypeEnum = pgEnum("connection_type", [ - "neo4j", - "postgresql", -]); +// Connection type is a plain text column (#1121): the accepted set is the +// connector registry (built-in + registry-supplied), validated at the API +// layer via schemas.ts — not a fixed DB enum. export const connectionVisibilityEnum = pgEnum("connection_visibility", [ "private", @@ -113,7 +112,7 @@ export const connections = pgTable("connection", { .references(() => users.id, { onDelete: "cascade" }), tenantId: text("tenant_id").notNull().default("default"), name: text("name").notNull(), - type: connectionTypeEnum("type").notNull(), + type: text("type").notNull(), configEncrypted: text("configEncrypted").notNull(), /** When true, widget editors can override the connection's default database per-card. */ allowPerCardDb: boolean("allow_per_card_db").notNull().default(true), diff --git a/app/src/lib/query/pipeline-types.ts b/app/src/lib/query/pipeline-types.ts index 2c62a78c..cea29d87 100644 --- a/app/src/lib/query/pipeline-types.ts +++ b/app/src/lib/query/pipeline-types.ts @@ -1,5 +1,3 @@ -import type { ConnectorType } from "@/lib/connector/connector-types"; - /** * Execution context that flows through the middleware pipeline. * @@ -11,7 +9,8 @@ export interface QueryContext { query: string; params: Record; connectionId: string; - connectionType: ConnectorType; + /** Connector type — any registry-registered type, not a fixed union (#1121). */ + connectionType: string; userId: string; tenantId: string; accessMode: "read" | "write"; diff --git a/app/src/lib/query/query-executor.ts b/app/src/lib/query/query-executor.ts index 7f3a5067..13954df5 100644 --- a/app/src/lib/query/query-executor.ts +++ b/app/src/lib/query/query-executor.ts @@ -4,7 +4,6 @@ import { ConnectionTypes, } from "@/lib/connector/connection-adapter"; import { ensureDatabaseInUri, rewriteParamsForPostgres } from "./query-params"; -import type { ConnectorType } from "@/lib/connector/connector-types"; import { QueryStatus } from "@neoboard/connection"; /** @@ -38,11 +37,21 @@ export interface ConnectionCredentials { maxRows?: number; } -export type DbType = ConnectorType; +// Registry-supplied connectors are first-class (#1121): a connector type is +// any registered string, not just the built-in union. createConnectionModule +// resolves it via the registry; built-in-specific branches (pg param rewrite, +// statement timeout) key off the literal type and safely no-op for others. +export type DbType = string; -/** Numeric type for connection module config (legacy enum). */ +/** + * Numeric type for connection module config (legacy enum). Registry-supplied + * connectors have no built-in numeric identity → UNKNOWN, rather than being + * mislabeled as PostgreSQL (#1121). + */ function toConnectionTypeEnum(type: DbType): number { - return type === "neo4j" ? ConnectionTypes.NEO4J : ConnectionTypes.POSTGRESQL; + if (type === "neo4j") return ConnectionTypes.NEO4J; + if (type === "postgresql") return ConnectionTypes.POSTGRESQL; + return ConnectionTypes.UNKNOWN; } /** diff --git a/app/src/lib/shared/schemas.ts b/app/src/lib/shared/schemas.ts index ae84f919..c2c9facb 100644 --- a/app/src/lib/shared/schemas.ts +++ b/app/src/lib/shared/schemas.ts @@ -1,11 +1,20 @@ import { z } from "zod"; -import { CONNECTOR_TYPES } from "@/lib/connector/connector-types"; +import { isRegisteredConnectorType } from "@/lib/connector/registered-types"; /** * Shared Zod schemas for API route validation. * Extracted to avoid duplication across connection routes. */ +/** + * Connector type accepted by the API — any type registered in the connector + * registry (built-in or external), not a hardcoded union (#1121). + */ +const connectorTypeSchema = z + .string() + .min(1) + .refine(isRegisteredConnectorType, { message: "Unknown connector type" }); + export const connectionConfigSchema = z.object({ uri: z.string().min(1), username: z.string().min(1), @@ -35,7 +44,7 @@ export const connectionConfigSchema = z.object({ export const createConnectionSchema = z.object({ name: z.string().min(1), - type: z.enum(CONNECTOR_TYPES), + type: connectorTypeSchema, config: connectionConfigSchema, }); @@ -52,6 +61,6 @@ export const updateConnectionSchema = z.object({ }); export const testInlineSchema = z.object({ - type: z.enum(CONNECTOR_TYPES), + type: connectorTypeSchema, config: connectionConfigSchema, }); diff --git a/component/src/components/composed/__tests__/dynamic-connection-fields.test.tsx b/component/src/components/composed/__tests__/dynamic-connection-fields.test.tsx new file mode 100644 index 00000000..6a51e437 --- /dev/null +++ b/component/src/components/composed/__tests__/dynamic-connection-fields.test.tsx @@ -0,0 +1,133 @@ +import { render, screen, fireEvent } from "@testing-library/react"; +import { describe, it, expect, vi } from "vitest"; +import { + DynamicConnectionFields, + type DynamicConnectionField, +} from "../dynamic-connection-fields"; + +const fields: DynamicConnectionField[] = [ + { + name: "uri", + label: "Connection URI", + type: "text", + required: true, + placeholder: "bolt://localhost:7687", + description: "Neo4j connection URI", + }, + { name: "port", label: "Port", type: "number", placeholder: "7687" }, + { name: "password", label: "Password", type: "password" }, + { + name: "sslmode", + label: "SSL Mode", + type: "select", + options: [ + { label: "Prefer", value: "prefer" }, + { label: "Disable", value: "disable" }, + ], + }, + { name: "verifyTls", label: "Verify TLS", type: "boolean" }, +]; + +function renderFields( + props: Partial> = {}, +) { + return render( + , + ); +} + +describe("DynamicConnectionFields", () => { + it("renders each field with a label", () => { + renderFields(); + expect(screen.getByLabelText(/Connection URI/)).toBeInTheDocument(); + expect(screen.getByLabelText(/Port/)).toBeInTheDocument(); + expect(screen.getByLabelText(/Password/)).toBeInTheDocument(); + expect(screen.getByLabelText(/SSL Mode/)).toBeInTheDocument(); + expect(screen.getByLabelText(/Verify TLS/)).toBeInTheDocument(); + }); + + it("prefixes input ids with conn- by default (preserves E2E selectors)", () => { + renderFields(); + expect(screen.getByLabelText(/Connection URI/)).toHaveAttribute( + "id", + "conn-uri", + ); + }); + + it("respects a custom idPrefix", () => { + renderFields({ idPrefix: "edit-" }); + expect(screen.getByLabelText(/Connection URI/)).toHaveAttribute( + "id", + "edit-uri", + ); + }); + + it("marks required fields with an asterisk", () => { + renderFields(); + const label = screen.getByText("Connection URI").closest("label"); + expect(label).toHaveTextContent("*"); + }); + + it("renders the description as muted help text", () => { + renderFields(); + expect(screen.getByText("Neo4j connection URI")).toBeInTheDocument(); + }); + + it("renders number fields with type=number", () => { + renderFields(); + expect(screen.getByLabelText(/Port/)).toHaveAttribute("type", "number"); + }); + + it("renders password fields with a show/hide toggle", () => { + renderFields(); + expect(screen.getByLabelText(/Password/)).toHaveAttribute( + "type", + "password", + ); + expect( + screen.getByRole("button", { name: /show password/i }), + ).toBeInTheDocument(); + }); + + it("renders select fields as a combobox with options", () => { + renderFields(); + expect( + screen.getByRole("combobox", { name: /SSL Mode/ }), + ).toBeInTheDocument(); + }); + + it("calls onChange with name and value on text input", () => { + const onChange = vi.fn(); + renderFields({ onChange }); + fireEvent.change(screen.getByLabelText(/Connection URI/), { + target: { value: "bolt://db:7687" }, + }); + expect(onChange).toHaveBeenCalledWith("uri", "bolt://db:7687"); + }); + + it("calls onChange with a boolean for boolean fields", () => { + const onChange = vi.fn(); + renderFields({ onChange }); + fireEvent.click(screen.getByLabelText(/Verify TLS/)); + expect(onChange).toHaveBeenCalledWith("verifyTls", true); + }); + + it("shows a per-field error with the prefixed id and aria-invalid", () => { + renderFields({ errors: { uri: "Invalid URI" } }); + const input = screen.getByLabelText(/Connection URI/); + expect(input).toHaveAttribute("aria-invalid", "true"); + const err = screen.getByText("Invalid URI"); + expect(err).toHaveAttribute("id", "conn-uri-error"); + }); +}); diff --git a/component/src/components/composed/dynamic-connection-fields.tsx b/component/src/components/composed/dynamic-connection-fields.tsx new file mode 100644 index 00000000..85ef33e5 --- /dev/null +++ b/component/src/components/composed/dynamic-connection-fields.tsx @@ -0,0 +1,193 @@ +import type { ChangeEvent } from "react"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { PasswordInput } from "./password-input"; +import { cn } from "@/lib/utils"; + +/** + * One field in a connector's connection form. Mirrors the + * `ConnectorFormField` contract from @neoboard/connector-sdk, with `name` + * as the value key (the SDK calls it `key`). Callers map `key` → `name`. + */ +export interface DynamicConnectionField { + name: string; + label: string; + type: "text" | "password" | "number" | "select" | "boolean"; + required?: boolean; + placeholder?: string; + description?: string; + options?: { label: string; value: string }[]; +} + +export interface DynamicConnectionFieldsProps { + fields: DynamicConnectionField[]; + values: Record; + onChange: (name: string, value: string | boolean) => void; + /** Per-field error messages, keyed by field name. */ + errors?: Record; + /** Prefix for input ids (default "conn-" preserves existing E2E selectors). */ + idPrefix?: string; + className?: string; +} + +/** + * Renders a connection form's fields from a connector's `formFields` + * definition (#1118). Controlled — the parent owns the values and gets + * `(name, value)` change callbacks. The credential block of the connections + * page and the library `ConnectionForm` both render through this. + */ +type ChangeHandler = (name: string, value: string | boolean) => void; + +/** Field label with a required-asterisk. */ +function FieldLabel({ + field, + id, +}: Readonly<{ field: DynamicConnectionField; id: string }>) { + return ( + + ); +} + +/** The input control for a non-boolean field (select / password / text). */ +function FieldControl({ + field, + id, + errorId, + value, + error, + onChange, +}: Readonly<{ + field: DynamicConnectionField; + id: string; + errorId: string; + value: string; + error?: string; + onChange: ChangeHandler; +}>) { + if (field.type === "select") { + return ( + + ); + } + + const shared = { + id, + value, + onChange: (e: ChangeEvent) => + onChange(field.name, e.target.value), + placeholder: field.placeholder, + required: field.required, + "aria-invalid": error ? true : undefined, + "aria-describedby": error ? errorId : undefined, + }; + + if (field.type === "password") { + return ; + } + return ( + + ); +} + +/** One labelled field row with optional description + error. */ +function FieldRow({ + field, + idPrefix, + values, + error, + onChange, +}: Readonly<{ + field: DynamicConnectionField; + idPrefix: string; + values: Record; + error?: string; + onChange: ChangeHandler; +}>) { + const id = `${idPrefix}${field.name}`; + const errorId = `${id}-error`; + const strValue = String(values[field.name] ?? ""); + + return ( +
+ {field.type === "boolean" ? ( +
+ onChange(field.name, e.target.checked)} + /> + +
+ ) : ( + <> + + + + )} + {field.description && ( +

{field.description}

+ )} + {error && ( +

+ {error} +

+ )} +
+ ); +} + +function DynamicConnectionFields({ + fields, + values, + onChange, + errors, + idPrefix = "conn-", + className, +}: Readonly) { + return ( +
+ {fields.map((field) => ( + + ))} +
+ ); +} + +export { DynamicConnectionFields }; diff --git a/component/src/components/composed/index.ts b/component/src/components/composed/index.ts index 00f2904f..ec3ac876 100644 --- a/component/src/components/composed/index.ts +++ b/component/src/components/composed/index.ts @@ -129,6 +129,11 @@ export { type ConnectionFieldConfig, } from "./connection-form"; export { ConnectionCard, type ConnectionCardProps } from "./connection-card"; +export { + DynamicConnectionFields, + type DynamicConnectionField, + type DynamicConnectionFieldsProps, +} from "./dynamic-connection-fields"; // Interactivity export { ParameterBar, type ParameterBarProps } from "./parameter-bar"; diff --git a/component/src/lib/__tests__/language-resolvers.test.ts b/component/src/lib/__tests__/language-resolvers.test.ts index ac1c09bf..b075a45b 100644 --- a/component/src/lib/__tests__/language-resolvers.test.ts +++ b/component/src/lib/__tests__/language-resolvers.test.ts @@ -18,9 +18,13 @@ import type { DatabaseSchema } from "../schema-transforms"; // --------------------------------------------------------------------------- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- mocks accept any args -const mockSql = vi.fn<(...args: any[]) => object[]>(() => [{ type: "sqlLanguageSupport" }]); +const mockSql = vi.fn<(...args: any[]) => object[]>(() => [ + { type: "sqlLanguageSupport" }, +]); // eslint-disable-next-line @typescript-eslint/no-explicit-any -- mocks accept any args -const mockCypher = vi.fn<(...args: any[]) => object>(() => ({ type: "cypherLanguageSupport" })); +const mockCypher = vi.fn<(...args: any[]) => object>(() => ({ + type: "cypherLanguageSupport", +})); vi.mock("@codemirror/lang-sql", () => ({ sql: (...args: unknown[]) => mockSql(...args), @@ -32,9 +36,8 @@ vi.mock("@/lib/cypher-lang", () => ({ })); // Import AFTER mocks -const { resolveLanguageExt, languageResolvers } = await import( - "../language-resolvers" -); +const { resolveLanguageExt, languageResolvers } = + await import("../language-resolvers"); beforeEach(() => { mockSql.mockClear(); @@ -74,21 +77,31 @@ describe("resolveLanguageExt", () => { expect(mockSql).not.toHaveBeenCalled(); }); - it("falls back to sql resolver for unknown languages", async () => { - await resolveLanguageExt("unknown-lang"); - expect(mockSql).toHaveBeenCalled(); + it("falls back to plain text (no extensions) for unknown languages", async () => { + const exts = await resolveLanguageExt("unknown-lang"); + expect(exts).toEqual([]); + expect(mockSql).not.toHaveBeenCalled(); expect(mockCypher).not.toHaveBeenCalled(); }); - it("falls back to sql for prototype-inherited keys like __proto__", async () => { - await resolveLanguageExt("__proto__"); - expect(mockSql).toHaveBeenCalled(); + it("falls back to plain text for an empty/undeclared language", async () => { + const exts = await resolveLanguageExt(""); + expect(exts).toEqual([]); + expect(mockSql).not.toHaveBeenCalled(); expect(mockCypher).not.toHaveBeenCalled(); }); - it("falls back to sql for constructor key", async () => { - await resolveLanguageExt("constructor"); - expect(mockSql).toHaveBeenCalled(); + it("falls back to plain text for prototype-inherited keys like __proto__", async () => { + const exts = await resolveLanguageExt("__proto__"); + expect(exts).toEqual([]); + expect(mockSql).not.toHaveBeenCalled(); + expect(mockCypher).not.toHaveBeenCalled(); + }); + + it("falls back to plain text for constructor key", async () => { + const exts = await resolveLanguageExt("constructor"); + expect(exts).toEqual([]); + expect(mockSql).not.toHaveBeenCalled(); expect(mockCypher).not.toHaveBeenCalled(); }); diff --git a/component/src/lib/language-resolvers.ts b/component/src/lib/language-resolvers.ts index e57f2fc3..ab8785ea 100644 --- a/component/src/lib/language-resolvers.ts +++ b/component/src/lib/language-resolvers.ts @@ -59,16 +59,17 @@ export const languageResolvers: Record = { }; /** - * Resolve a language string to CM6 extensions. Falls back to SQL if the - * language is not registered. + * Resolve a language string to CM6 extensions. Falls back to plain text + * (no language extension, no highlighting) when the language is not + * registered — so a registry-supplied connector that declares an unknown + * or no `queryLanguage` gets a neutral editor rather than misleading SQL + * highlighting (#1120). */ export async function resolveLanguageExt( language: string, schema?: DatabaseSchema, ): Promise { const key = language.toLowerCase(); - const resolver = Object.hasOwn(languageResolvers, key) - ? languageResolvers[key] - : languageResolvers.sql; - return resolver(schema); + if (!Object.hasOwn(languageResolvers, key)) return []; + return languageResolvers[key](schema); } diff --git a/connection/__tests__/adapters/factory.ts b/connection/__tests__/adapters/factory.ts index 7d8c0430..0c6c7184 100644 --- a/connection/__tests__/adapters/factory.ts +++ b/connection/__tests__/adapters/factory.ts @@ -1,7 +1,7 @@ import { createConnectionModule } from "../../src/connector-registry"; import { Neo4jConnectionModule } from "../../src/neo4j/Neo4jConnectionModule"; import { PostgresConnectionModule } from "../../src/postgresql/PostgresConnectionModule"; -import { AuthType } from "../../src/generalized/interfaces"; +import { AuthType } from "@neoboard/connector-sdk"; describe("Connection Module Factory (via registry)", () => { const neo4jAuthConfig = { diff --git a/connection/__tests__/advanced-connection-options.test.ts b/connection/__tests__/advanced-connection-options.test.ts index 81ac09df..e37c0825 100644 --- a/connection/__tests__/advanced-connection-options.test.ts +++ b/connection/__tests__/advanced-connection-options.test.ts @@ -1,9 +1,9 @@ -import { AuthType } from "../src/generalized/interfaces"; +import { AuthType } from "@neoboard/connector-sdk"; import type { AdvancedConnectionOptions, Neo4jAdvancedOptions, PostgresAdvancedOptions, -} from "../src/generalized/interfaces"; +} from "@neoboard/connector-sdk"; // --------------------------------------------------------------------------- // Mocks — capture constructor args for neo4j.driver() and pg.Pool @@ -262,7 +262,7 @@ describe("createConnectionModule with advanced options", () => { describe("DEFAULT_CONNECTION_CONFIG (#973)", () => { test("default query timeout is the documented 30s, not 2s", async () => { const { DEFAULT_CONNECTION_CONFIG } = - await import("../src/generalized/interfaces"); + await import("@neoboard/connector-sdk"); expect(DEFAULT_CONNECTION_CONFIG.timeout).toBe(30_000); }); }); diff --git a/connection/__tests__/authentication/authentication.ts b/connection/__tests__/authentication/authentication.ts index 2048e350..08428c29 100644 --- a/connection/__tests__/authentication/authentication.ts +++ b/connection/__tests__/authentication/authentication.ts @@ -1,73 +1,87 @@ -import { Neo4jAuthenticationModule } from '../../src/neo4j/Neo4jAuthenticationModule'; -import { AuthType } from '../../src/generalized/interfaces'; -import { getNeo4jAuth } from '../utils/setup'; +import { Neo4jAuthenticationModule } from "../../src/neo4j/Neo4jAuthenticationModule"; +import { AuthType } from "@neoboard/connector-sdk"; +import { getNeo4jAuth } from "../utils/setup"; -describe('Neo4jAuthenticationModule creation to check consistency', () => { - test('creating an authenticationModule with nothing as config', () => { +describe("Neo4jAuthenticationModule creation to check consistency", () => { + test("creating an authenticationModule with nothing as config", () => { // Expect a raised exception - expect(() => new Neo4jAuthenticationModule({})).toThrow('Authentication type is required'); + expect(() => new Neo4jAuthenticationModule({})).toThrow( + "Authentication type is required", + ); }); - test('creating an authenticationModule with an empty authType', () => { + test("creating an authenticationModule with an empty authType", () => { // Expect a raised exception - expect(() => new Neo4jAuthenticationModule({ uri: 'test' })).toThrow('Authentication type is required'); + expect(() => new Neo4jAuthenticationModule({ uri: "test" })).toThrow( + "Authentication type is required", + ); }); - test('creating an authenticationModule with an AuthType.Empty authType', () => { + test("creating an authenticationModule with an AuthType.Empty authType", () => { // Expect a raised exception - expect(() => new Neo4jAuthenticationModule({ authType: AuthType.EMPTY })).toThrow( - 'Authentication type is Empty. Please provide a username and password' + expect( + () => new Neo4jAuthenticationModule({ authType: AuthType.EMPTY }), + ).toThrow( + "Authentication type is Empty. Please provide a username and password", ); }); - test('creating an authenticationModule with an empty URI', () => { + test("creating an authenticationModule with an empty URI", () => { // Expect a raised exception - expect(() => new Neo4jAuthenticationModule({ authType: AuthType.NATIVE })).toThrow('URI is required'); + expect( + () => new Neo4jAuthenticationModule({ authType: AuthType.NATIVE }), + ).toThrow("URI is required"); }); - test('creating an authenticationModule with undefined config', () => { + test("creating an authenticationModule with undefined config", () => { // Expect a raised exception - expect(() => new Neo4jAuthenticationModule(undefined)).toThrow('Connection config is required'); + expect(() => new Neo4jAuthenticationModule(undefined)).toThrow( + "Connection config is required", + ); }); - test('creating an authenticationModule with an undefined AuthType', () => { + test("creating an authenticationModule with an undefined AuthType", () => { // Expect a raised exception - expect(() => new Neo4jAuthenticationModule({ authType: undefined })).toThrow('Authentication type is required'); + expect( + () => new Neo4jAuthenticationModule({ authType: undefined }), + ).toThrow("Authentication type is required"); }); }); -describe('Neo4jAuthenticationModule with native auth', () => { - test('creating an authenticationModule with native auth', async () => { +describe("Neo4jAuthenticationModule with native auth", () => { + test("creating an authenticationModule with native auth", async () => { const config = getNeo4jAuth(); const authModule = new Neo4jAuthenticationModule(config); const isAuthenticated = await authModule.verifyAuthentication(); expect(isAuthenticated).toBe(true); }); - test('creating an authenticationModule with native auth, but wrong password', async () => { + test("creating an authenticationModule with native auth, but wrong password", async () => { const config = getNeo4jAuth(); - config.password = 'wrongpassword'; + config.password = "wrongpassword"; const authModule = new Neo4jAuthenticationModule(config); const isAuthenticated = await authModule.verifyAuthentication(); expect(isAuthenticated).toBe(false); }); - test('creating an authenticationModule with native auth, but wrong URI throws', async () => { + test("creating an authenticationModule with native auth, but wrong URI throws", async () => { const config = getNeo4jAuth(); // Use RFC 5737 TEST-NET-1 (non-routable) to guarantee a connection failure. // 'localhosta' can resolve to localhost on some systems (macOS mDNS), // causing the driver to connect to a local Neo4j instance instead of failing. - config.uri = 'bolt://192.0.2.1:7687'; - const authModule = new Neo4jAuthenticationModule(config, { neo4jConnectionTimeout: 2000 }); + config.uri = "bolt://192.0.2.1:7687"; + const authModule = new Neo4jAuthenticationModule(config, { + neo4jConnectionTimeout: 2000, + }); await expect(authModule.verifyAuthentication()).rejects.toThrow(); }); - test('creating an authenticationModule with wrong username', async () => { + test("creating an authenticationModule with wrong username", async () => { const config = getNeo4jAuth(); - config.username = 'wronguser'; + config.username = "wronguser"; const authModule = new Neo4jAuthenticationModule(config); const isAuthenticated = await authModule.verifyAuthentication(); expect(isAuthenticated).toBe(false); }); - test('creating an authenticationModule with wrong username and after fail connection Update authConfig', async () => { + test("creating an authenticationModule with wrong username and after fail connection Update authConfig", async () => { const wrongConfig = getNeo4jAuth(); - wrongConfig.username = 'wronguser'; + wrongConfig.username = "wronguser"; const authModule = new Neo4jAuthenticationModule(wrongConfig); const isNotAuthenticated = await authModule.verifyAuthentication(); expect(isNotAuthenticated).toBe(false); diff --git a/connection/__tests__/conformance/neo4j-conformance.test.ts b/connection/__tests__/conformance/neo4j-conformance.test.ts new file mode 100644 index 00000000..dfa6ce73 --- /dev/null +++ b/connection/__tests__/conformance/neo4j-conformance.test.ts @@ -0,0 +1,38 @@ +import { getNeo4jAuth, NEO4J_TEST_CONNECTION_CONFIG } from "../utils/setup"; +import { Neo4jConnectionModule } from "../../src/neo4j/Neo4jConnectionModule"; +import { + buildConformanceCases, + type ConformanceSetup, +} from "@neoboard/connector-sdk"; + +// #1122 — the built-in Neo4j connector must pass the shared query-safety +// conformance suite shipped from the SDK. +describe("Neo4j query-safety conformance (#1122)", () => { + const connection = new Neo4jConnectionModule(getNeo4jAuth()); + + const setup: ConformanceSetup = { + baseConfig: NEO4J_TEST_CONNECTION_CONFIG, + queries: { + // A write — must be refused under READ access mode. + write: { query: "CREATE (n:__ConformanceTmp) RETURN n" }, + // Returns exactly `n` rows. + manyRows: (n) => ({ + query: "UNWIND range(1, $n) AS x RETURN x", + params: { n }, + }), + // A cartesian product large enough to always exceed a sub-second timeout. + slow: { + query: + "UNWIND range(1, 1000000) AS a UNWIND range(1, 1000000) AS b RETURN count(*)", + }, + }, + }; + + afterAll(async () => { + await connection.getDriver().close(); + }); + + for (const testCase of buildConformanceCases(() => connection, setup)) { + test(testCase.name, testCase.run); + } +}); diff --git a/connection/__tests__/conformance/pg-conformance.test.ts b/connection/__tests__/conformance/pg-conformance.test.ts new file mode 100644 index 00000000..baa33963 --- /dev/null +++ b/connection/__tests__/conformance/pg-conformance.test.ts @@ -0,0 +1,52 @@ +import { PostgresConnectionModule } from "../../src/postgresql/PostgresConnectionModule"; +import { + PostgreSqlContainer, + StartedPostgreSqlContainer, +} from "@testcontainers/postgresql"; +import { + DEFAULT_CONNECTION_CONFIG, + AuthType, + buildConformanceCases, + type ConformanceSetup, +} from "@neoboard/connector-sdk"; + +// #1122 — the built-in PostgreSQL connector must pass the shared query-safety +// conformance suite shipped from the SDK. +describe("PostgreSQL query-safety conformance (#1122)", () => { + let container: StartedPostgreSqlContainer; + let connection: PostgresConnectionModule; + + beforeAll(async () => { + container = await new PostgreSqlContainer("postgres:16-alpine").start(); + connection = new PostgresConnectionModule({ + username: container.getUsername(), + password: container.getPassword(), + authType: AuthType.NATIVE, + uri: `postgresql://${container.getHost()}:${container.getPort()}/${container.getDatabase()}`, + }); + }, 60_000); + + afterAll(async () => { + if (connection) await connection.close(); + if (container) await container.stop(); + }); + + const setup: ConformanceSetup = { + baseConfig: { ...DEFAULT_CONNECTION_CONFIG }, + queries: { + // A DDL write — must be refused under READ access mode (READ ONLY txn). + write: { query: "CREATE TABLE __conformance_tmp (x int)" }, + // Returns exactly `n` rows. + manyRows: (n) => ({ + query: "SELECT i AS x FROM generate_series(1, $1) AS i", + params: { "0": n }, + }), + // Produces no rows until it finishes; statement_timeout fires first. + slow: { query: "SELECT pg_sleep(5)" }, + }, + }; + + for (const testCase of buildConformanceCases(() => connection, setup)) { + test(testCase.name, testCase.run); + } +}); diff --git a/connection/__tests__/connection/connection-resilience.ts b/connection/__tests__/connection/connection-resilience.ts index 09cbfdb5..d137fa6b 100644 --- a/connection/__tests__/connection/connection-resilience.ts +++ b/connection/__tests__/connection/connection-resilience.ts @@ -8,7 +8,7 @@ import { QueryStatus, AuthType, ConnectionTypes, -} from "../../src/generalized/interfaces"; +} from "@neoboard/connector-sdk"; import { PostgreSqlContainer } from "@testcontainers/postgresql"; describe("Connection Resilience — Neo4j", () => { diff --git a/connection/__tests__/connection/list-databases.ts b/connection/__tests__/connection/list-databases.ts index e637295a..c31616d7 100644 --- a/connection/__tests__/connection/list-databases.ts +++ b/connection/__tests__/connection/list-databases.ts @@ -1,7 +1,7 @@ import { Neo4jConnectionModule } from "../../src/neo4j/Neo4jConnectionModule"; import { PostgresConnectionModule } from "../../src/postgresql/PostgresConnectionModule"; import { getNeo4jAuth, NEO4J_TEST_CONNECTION_CONFIG } from "../utils/setup"; -import { AuthType } from "../../src/generalized/interfaces"; +import { AuthType } from "@neoboard/connector-sdk"; import { PostgreSqlContainer } from "@testcontainers/postgresql"; describe("Neo4j listDatabases", () => { diff --git a/connection/__tests__/connection/query-basic.ts b/connection/__tests__/connection/query-basic.ts index d1b552a1..da21fdc2 100644 --- a/connection/__tests__/connection/query-basic.ts +++ b/connection/__tests__/connection/query-basic.ts @@ -1,11 +1,8 @@ import { getNeo4jAuth } from "../utils/setup"; import { Neo4jConnectionModule } from "../../src/neo4j/Neo4jConnectionModule"; -import { QueryCallback, QueryParams } from "../../src/generalized/interfaces"; +import { QueryCallback, QueryParams } from "@neoboard/connector-sdk"; import { NEO4J_TEST_CONNECTION_CONFIG } from "../utils/setup"; -import { - ConnectorError, - ConnectorErrorType, -} from "../../src/generalized/ConnectorError"; +import { ConnectorError, ConnectorErrorType } from "@neoboard/connector-sdk"; describe("Query to Neo4j", () => { test("run MATCH (n) RETURN n LIMIT 1 and get Data", async () => { diff --git a/connection/__tests__/connection/query-status.ts b/connection/__tests__/connection/query-status.ts index ea0b24f0..3751fc19 100644 --- a/connection/__tests__/connection/query-status.ts +++ b/connection/__tests__/connection/query-status.ts @@ -4,7 +4,7 @@ import { QueryCallback, QueryParams, QueryStatus, -} from "../../src/generalized/interfaces"; +} from "@neoboard/connector-sdk"; import { NEO4J_TEST_CONNECTION_CONFIG } from "../utils/setup"; describe("Query to Neo4j", () => { diff --git a/connection/__tests__/connection/query-write.ts b/connection/__tests__/connection/query-write.ts index bcd3e811..57260d53 100644 --- a/connection/__tests__/connection/query-write.ts +++ b/connection/__tests__/connection/query-write.ts @@ -1,10 +1,10 @@ import { getNeo4jAuth } from "../utils/setup"; import { Neo4jConnectionModule } from "../../src/neo4j/Neo4jConnectionModule"; -import { QueryCallback, QueryParams } from "../../src/generalized/interfaces"; +import { QueryCallback, QueryParams } from "@neoboard/connector-sdk"; import { NEO4J_TEST_CONNECTION_CONFIG } from "../utils/setup"; import { toNumber } from "neo4j-driver-core"; -import { ConnectorError } from "../../src/generalized/ConnectorError"; -import { NeodashRecord } from "../../src/generalized/NeodashRecord"; +import { ConnectorError } from "@neoboard/connector-sdk"; +import { NeodashRecord } from "@neoboard/connector-sdk"; describe("Advanced Query to Neo4j", () => { let connection: Neo4jConnectionModule; diff --git a/connection/__tests__/connector-registry.test.ts b/connection/__tests__/connector-registry.test.ts index 5c036313..9461639b 100644 --- a/connection/__tests__/connector-registry.test.ts +++ b/connection/__tests__/connector-registry.test.ts @@ -1,7 +1,7 @@ import { createConnectorRegistry, type ConnectorPlugin, -} from "../src/generalized/connector-plugin"; +} from "@neoboard/connector-sdk"; // --------------------------------------------------------------------------- // Fixture diff --git a/connection/__tests__/get-schema-manager.test.ts b/connection/__tests__/get-schema-manager.test.ts new file mode 100644 index 00000000..0afe064f --- /dev/null +++ b/connection/__tests__/get-schema-manager.test.ts @@ -0,0 +1,63 @@ +import { + getSchemaManager, + registerConnector, + unregisterConnector, +} from "../src/connector-registry"; +import { Neo4jSchemaManager } from "../src/schema/neo4j-schema"; +import { PostgresSchemaManager } from "../src/schema/pg-schema"; +import type { ConnectorPlugin } from "@neoboard/connector-sdk"; + +// #1119 — schema-manager dispatch is keyed by connector type through the +// registry (no hardcoded 'neo4j' | 'postgresql' union). A plugin supplies its +// own manager via the optional `createSchemaManager()` factory. + +const fakeModule = () => + ({ runQuery: jest.fn(), checkConnection: jest.fn() }) as never; + +describe("getSchemaManager", () => { + it("resolves the built-in Neo4j schema manager", () => { + expect(getSchemaManager("neo4j")).toBeInstanceOf(Neo4jSchemaManager); + }); + + it("resolves the built-in PostgreSQL schema manager", () => { + expect(getSchemaManager("postgresql")).toBeInstanceOf( + PostgresSchemaManager, + ); + }); + + it("returns undefined for an unknown connector type", () => { + expect(getSchemaManager("nope")).toBeUndefined(); + }); + + it("resolves a registry-supplied connector's own schema manager", () => { + const fakeSchemaManager = { fetchSchema: jest.fn() }; + const plugin: ConnectorPlugin = { + type: "fixture-db", + label: "Fixture DB", + category: "database", + createModule: fakeModule, + createSchemaManager: () => fakeSchemaManager, + }; + registerConnector(plugin); + try { + expect(getSchemaManager("fixture-db")).toBe(fakeSchemaManager); + } finally { + unregisterConnector("fixture-db"); + } + }); + + it("returns undefined for a connector without a schema-manager factory", () => { + const plugin: ConnectorPlugin = { + type: "no-schema-db", + label: "No Schema DB", + category: "database", + createModule: fakeModule, + }; + registerConnector(plugin); + try { + expect(getSchemaManager("no-schema-db")).toBeUndefined(); + } finally { + unregisterConnector("no-schema-db"); + } + }); +}); diff --git a/connection/__tests__/neo4j/callback/setField-query.ts b/connection/__tests__/neo4j/callback/setField-query.ts index 2f7880bf..b4def7e3 100644 --- a/connection/__tests__/neo4j/callback/setField-query.ts +++ b/connection/__tests__/neo4j/callback/setField-query.ts @@ -1,11 +1,11 @@ -import { getNeo4jAuth } from '../../utils/setup'; -import { Neo4jConnectionModule } from '../../../src/neo4j/Neo4jConnectionModule'; -import { QueryCallback, QueryParams } from '../../../src/generalized/interfaces'; -import { NEO4J_TEST_CONNECTION_CONFIG } from '../../utils/setup'; -import { NeodashRecord } from '../../../src/generalized/NeodashRecord'; - -describe('Neo4jConnectionModule - setFields', () => { - test('getFields should return top-level keys when useNodePropsAsFields is false', async () => { +import { getNeo4jAuth } from "../../utils/setup"; +import { Neo4jConnectionModule } from "../../../src/neo4j/Neo4jConnectionModule"; +import { QueryCallback, QueryParams } from "@neoboard/connector-sdk"; +import { NEO4J_TEST_CONNECTION_CONFIG } from "../../utils/setup"; +import { NeodashRecord } from "@neoboard/connector-sdk"; + +describe("Neo4jConnectionModule - setFields", () => { + test("getFields should return top-level keys when useNodePropsAsFields is false", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); @@ -23,11 +23,11 @@ describe('Neo4jConnectionModule - setFields', () => { expect(result.length).toBe(1); }, onFail: (error) => { - console.error('Query failed:', error); + console.error("Query failed:", error); throw error; }, setFields: (fields) => { - expect(fields).toEqual(expect.arrayContaining(['person', 'movie'])); + expect(fields).toEqual(expect.arrayContaining(["person", "movie"])); }, }; @@ -38,7 +38,7 @@ describe('Neo4jConnectionModule - setFields', () => { }); }); - test('getFields should extract node properties grouped by label', async () => { + test("getFields should extract node properties grouped by label", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); @@ -56,12 +56,14 @@ describe('Neo4jConnectionModule - setFields', () => { expect(result.length).toBe(1); }, onFail: (error) => { - console.error('Query failed:', error); + console.error("Query failed:", error); throw error; }, setFields: (fields) => { expect(fields).toEqual( - expect.arrayContaining([expect.arrayContaining(['Person', expect.stringMatching(/.*/)])]) + expect.arrayContaining([ + expect.arrayContaining(["Person", expect.stringMatching(/.*/)]), + ]), ); }, }; @@ -73,7 +75,7 @@ describe('Neo4jConnectionModule - setFields', () => { }); }); - test('getFields should extract properties from path segments', async () => { + test("getFields should extract properties from path segments", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); @@ -91,15 +93,15 @@ describe('Neo4jConnectionModule - setFields', () => { expect(result.length).toBe(1); }, onFail: (error) => { - console.error('Query failed:', error); + console.error("Query failed:", error); throw error; }, setFields: (fields) => { expect(fields).toEqual( expect.arrayContaining([ - expect.arrayContaining(['Person', expect.stringMatching(/.*/)]), - expect.arrayContaining(['Movie', expect.stringMatching(/.*/)]), - ]) + expect.arrayContaining(["Person", expect.stringMatching(/.*/)]), + expect.arrayContaining(["Movie", expect.stringMatching(/.*/)]), + ]), ); }, }; @@ -111,7 +113,7 @@ describe('Neo4jConnectionModule - setFields', () => { }); }); - test('getFields should extract from array of nodes (array traversal)', async () => { + test("getFields should extract from array of nodes (array traversal)", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); @@ -129,15 +131,15 @@ describe('Neo4jConnectionModule - setFields', () => { expect(result.length).toBe(1); }, onFail: (error) => { - console.error('Query failed:', error); + console.error("Query failed:", error); throw error; }, setFields: (fields) => { expect(fields).toEqual( expect.arrayContaining([ - expect.arrayContaining(['Person', expect.stringMatching(/.*/)]), - expect.arrayContaining(['Movie', expect.stringMatching(/.*/)]), - ]) + expect.arrayContaining(["Person", expect.stringMatching(/.*/)]), + expect.arrayContaining(["Movie", expect.stringMatching(/.*/)]), + ]), ); }, }; @@ -149,7 +151,7 @@ describe('Neo4jConnectionModule - setFields', () => { }); }); - test('getFields should return empty array when query returns no records', async () => { + test("getFields should return empty array when query returns no records", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); @@ -167,7 +169,7 @@ describe('Neo4jConnectionModule - setFields', () => { expect(result).toEqual([]); }, onFail: (error) => { - console.error('Query failed:', error); + console.error("Query failed:", error); throw error; }, setFields: (fields) => { diff --git a/connection/__tests__/neo4j/callback/setSchema-query.ts b/connection/__tests__/neo4j/callback/setSchema-query.ts index f86611ea..b884472a 100644 --- a/connection/__tests__/neo4j/callback/setSchema-query.ts +++ b/connection/__tests__/neo4j/callback/setSchema-query.ts @@ -1,11 +1,11 @@ -import { getNeo4jAuth } from '../../utils/setup'; -import { Neo4jConnectionModule } from '../../../src/neo4j/Neo4jConnectionModule'; -import { QueryCallback, QueryParams } from '../../../src/generalized/interfaces'; -import { NEO4J_TEST_CONNECTION_CONFIG } from '../../utils/setup'; -import { NeodashRecord } from '../../../src/generalized/NeodashRecord'; - -describe('Neo4jConnectionModule - setSchema', () => { - test('should extract schema from MovieDB sample data with ACTED_IN relation', async () => { +import { getNeo4jAuth } from "../../utils/setup"; +import { Neo4jConnectionModule } from "../../../src/neo4j/Neo4jConnectionModule"; +import { QueryCallback, QueryParams } from "@neoboard/connector-sdk"; +import { NEO4J_TEST_CONNECTION_CONFIG } from "../../utils/setup"; +import { NeodashRecord } from "@neoboard/connector-sdk"; + +describe("Neo4jConnectionModule - setSchema", () => { + test("should extract schema from MovieDB sample data with ACTED_IN relation", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); @@ -23,17 +23,17 @@ describe('Neo4jConnectionModule - setSchema', () => { expect(result.length).toBeGreaterThan(0); }, onFail: (error) => { - console.error('Query failed:', error); + console.error("Query failed:", error); throw error; }, setSchema: (schema) => { // schema should include Person, Movie, and ACTED_IN with at least some properties expect(schema).toEqual( expect.arrayContaining([ - expect.arrayContaining(['Person']), - expect.arrayContaining(['Movie']), - expect.arrayContaining(['ACTED_IN']), - ]) + expect.arrayContaining(["Person"]), + expect.arrayContaining(["Movie"]), + expect.arrayContaining(["ACTED_IN"]), + ]), ); }, }; @@ -45,7 +45,7 @@ describe('Neo4jConnectionModule - setSchema', () => { }); }); - test('should extract schema from a path structure', async () => { + test("should extract schema from a path structure", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); @@ -63,13 +63,16 @@ describe('Neo4jConnectionModule - setSchema', () => { expect(result.length).toBeGreaterThan(0); }, onFail: (error) => { - console.error('Query failed:', error); + console.error("Query failed:", error); throw error; }, setSchema: (schema) => { // Since valueIsPath will be hit via extract function on `path` expect(schema).toEqual( - expect.arrayContaining([expect.arrayContaining(['Person']), expect.arrayContaining(['Movie'])]) + expect.arrayContaining([ + expect.arrayContaining(["Person"]), + expect.arrayContaining(["Movie"]), + ]), ); }, }; @@ -81,7 +84,7 @@ describe('Neo4jConnectionModule - setSchema', () => { }); }); - test('should handle undefined field gracefully (field === undefined)', async () => { + test("should handle undefined field gracefully (field === undefined)", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); @@ -95,7 +98,7 @@ describe('Neo4jConnectionModule - setSchema', () => { expect(result.length).toBe(1); }, onFail: (error) => { - console.error('Query failed:', error); + console.error("Query failed:", error); throw error; }, setSchema: (schema) => { @@ -111,7 +114,7 @@ describe('Neo4jConnectionModule - setSchema', () => { }); }); - test('should recurse over array of nodes from MovieDB (valueIsArray === true)', async () => { + test("should recurse over array of nodes from MovieDB (valueIsArray === true)", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); @@ -130,14 +133,14 @@ describe('Neo4jConnectionModule - setSchema', () => { expect(result.length).toBe(1); }, onFail: (error) => { - console.error('Query failed:', error); + console.error("Query failed:", error); throw error; }, setSchema: (schema) => { expect(schema).toEqual( expect.arrayContaining([ - expect.arrayContaining(['Person']), // at least a label - ]) + expect.arrayContaining(["Person"]), // at least a label + ]), ); }, }; diff --git a/connection/__tests__/neo4j/neo4j-rollback.ts b/connection/__tests__/neo4j/neo4j-rollback.ts index 11e27b1c..0c251372 100644 --- a/connection/__tests__/neo4j/neo4j-rollback.ts +++ b/connection/__tests__/neo4j/neo4j-rollback.ts @@ -1,10 +1,14 @@ -import { getNeo4jAuth } from '../utils/setup'; -import { Neo4jConnectionModule } from '../../src/neo4j/Neo4jConnectionModule'; -import { QueryCallback, QueryParams, QueryStatus } from '../../src/generalized/interfaces'; -import { NEO4J_TEST_CONNECTION_CONFIG } from '../utils/setup'; +import { getNeo4jAuth } from "../utils/setup"; +import { Neo4jConnectionModule } from "../../src/neo4j/Neo4jConnectionModule"; +import { + QueryCallback, + QueryParams, + QueryStatus, +} from "@neoboard/connector-sdk"; +import { NEO4J_TEST_CONNECTION_CONFIG } from "../utils/setup"; -describe('Neo4j Transaction Rollback', () => { - test('write transaction auto-rolls back on error — node is NOT persisted', async () => { +describe("Neo4j Transaction Rollback", () => { + test("write transaction auto-rolls back on error — node is NOT persisted", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); @@ -19,10 +23,12 @@ describe('Neo4j Transaction Rollback', () => { params: {}, }, { - onFail: (err) => { failError = err; }, + onFail: (err) => { + failError = err; + }, setStatus: () => {}, }, - { ...NEO4J_TEST_CONNECTION_CONFIG, accessMode: 'WRITE' } + { ...NEO4J_TEST_CONNECTION_CONFIG, accessMode: "WRITE" }, ); // The query should have failed @@ -36,10 +42,12 @@ describe('Neo4j Transaction Rollback', () => { params: {}, }, { - onSuccess: (r) => { result = r; }, + onSuccess: (r) => { + result = r; + }, setStatus: () => {}, }, - NEO4J_TEST_CONNECTION_CONFIG + NEO4J_TEST_CONNECTION_CONFIG, ); expect(result).toHaveLength(0); diff --git a/connection/__tests__/neo4j/parser/parser-config.ts b/connection/__tests__/neo4j/parser/parser-config.ts index 4159c052..04485451 100644 --- a/connection/__tests__/neo4j/parser/parser-config.ts +++ b/connection/__tests__/neo4j/parser/parser-config.ts @@ -1,41 +1,44 @@ -import { getNeo4jAuth } from '../../utils/setup'; -import { Neo4jConnectionModule } from '../../../src/neo4j/Neo4jConnectionModule'; -import { QueryCallback, QueryParams } from '../../../src/generalized/interfaces'; -import { NEO4J_TEST_CONNECTION_CONFIG } from '../../utils/setup'; -import { NeodashRecord } from '../../../src/generalized/NeodashRecord'; - -describe('Neo4jRecordParser - config parseToNeodashRecord', () => { - test('should return parsed NeodashRecord when parseToNeodashRecord is true', async () => { +import { getNeo4jAuth } from "../../utils/setup"; +import { Neo4jConnectionModule } from "../../../src/neo4j/Neo4jConnectionModule"; +import { QueryCallback, QueryParams } from "@neoboard/connector-sdk"; +import { NEO4J_TEST_CONNECTION_CONFIG } from "../../utils/setup"; +import { NeodashRecord } from "@neoboard/connector-sdk"; + +describe("Neo4jRecordParser - config parseToNeodashRecord", () => { + test("should return parsed NeodashRecord when parseToNeodashRecord is true", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); const queryParams: QueryParams = { - query: 'RETURN 42 AS number', + query: "RETURN 42 AS number", params: {}, }; const queryCallback: QueryCallback = { onSuccess: (result: NeodashRecord[]) => { - expect(result[0]['number']).toBe(42); + expect(result[0]["number"]).toBe(42); expect(result[0] instanceof NeodashRecord).toBe(true); }, onFail: (error) => { - console.error('Error during query execution:', error); + console.error("Error during query execution:", error); throw error; }, }; - await connection.runQuery(queryParams, queryCallback, { ...NEO4J_TEST_CONNECTION_CONFIG, parseToNeodashRecord: true }); + await connection.runQuery(queryParams, queryCallback, { + ...NEO4J_TEST_CONNECTION_CONFIG, + parseToNeodashRecord: true, + }); }); - test('should return raw result when parseToNeodashRecord is false', async () => { + test("should return raw result when parseToNeodashRecord is false", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); const queryParams: QueryParams = { - query: 'RETURN 42 AS number', + query: "RETURN 42 AS number", params: {}, }; @@ -44,7 +47,7 @@ describe('Neo4jRecordParser - config parseToNeodashRecord', () => { expect(result[0] instanceof NeodashRecord).toBe(false); }, onFail: (error) => { - console.error('Error during query execution:', error); + console.error("Error during query execution:", error); throw error; }, }; diff --git a/connection/__tests__/neo4j/parser/parser-record-objects.ts b/connection/__tests__/neo4j/parser/parser-record-objects.ts index 9f4fa16b..2c023682 100644 --- a/connection/__tests__/neo4j/parser/parser-record-objects.ts +++ b/connection/__tests__/neo4j/parser/parser-record-objects.ts @@ -1,12 +1,12 @@ -import { getNeo4jAuth } from '../../utils/setup'; -import { Neo4jConnectionModule } from '../../../src/neo4j/Neo4jConnectionModule'; -import { QueryCallback, QueryParams } from '../../../src/generalized/interfaces'; -import { NEO4J_TEST_CONNECTION_CONFIG } from '../../utils/setup'; -import { toNumber } from 'neo4j-driver-core'; +import { getNeo4jAuth } from "../../utils/setup"; +import { Neo4jConnectionModule } from "../../../src/neo4j/Neo4jConnectionModule"; +import { QueryCallback, QueryParams } from "@neoboard/connector-sdk"; +import { NEO4J_TEST_CONNECTION_CONFIG } from "../../utils/setup"; +import { toNumber } from "neo4j-driver-core"; -import { NeodashRecord } from '../../../src/generalized/NeodashRecord'; +import { NeodashRecord } from "@neoboard/connector-sdk"; -describe('Neo4jRecordParser - Objects Parsing', () => { +describe("Neo4jRecordParser - Objects Parsing", () => { test('should correctly find the movie "The Matrix" as NODE', async () => { const config = getNeo4jAuth(); @@ -19,28 +19,32 @@ describe('Neo4jRecordParser - Objects Parsing', () => { const queryCallback: QueryCallback = { onSuccess: (result: NeodashRecord[]) => { - const movieNode = result[0]['m']; + const movieNode = result[0]["m"]; // parseGraphObject now returns a plain object with { identity, elementId, labels, properties } - expect(movieNode).toHaveProperty('labels'); - expect(movieNode).toHaveProperty('properties'); - const movieNodeProperties = movieNode['properties']; + expect(movieNode).toHaveProperty("labels"); + expect(movieNode).toHaveProperty("properties"); + const movieNodeProperties = movieNode["properties"]; // Assertions - expect(movieNodeProperties.title).toBe('The Matrix'); - expect(movieNodeProperties.tagline).toBe('Welcome to the Real World'); + expect(movieNodeProperties.title).toBe("The Matrix"); + expect(movieNodeProperties.tagline).toBe("Welcome to the Real World"); expect(toNumber(movieNodeProperties.released)).toBe(1999); - expect(typeof movieNodeProperties.title).toBe('string'); - expect(typeof movieNodeProperties.tagline).toBe('string'); - expect(typeof toNumber(movieNodeProperties.released)).toBe('number'); + expect(typeof movieNodeProperties.title).toBe("string"); + expect(typeof movieNodeProperties.tagline).toBe("string"); + expect(typeof toNumber(movieNodeProperties.released)).toBe("number"); }, onFail: (error) => { - console.error('Error during query execution:', error); + console.error("Error during query execution:", error); throw error; }, }; - await connection.runQuery(queryParams, queryCallback, NEO4J_TEST_CONNECTION_CONFIG); + await connection.runQuery( + queryParams, + queryCallback, + NEO4J_TEST_CONNECTION_CONFIG, + ); }); test('should correctly find the relation "ACTED_IN" for movie "The Matrix"', async () => { @@ -49,16 +53,17 @@ describe('Neo4jRecordParser - Objects Parsing', () => { const connection = new Neo4jConnectionModule(config); const queryParams: QueryParams = { - query: 'MATCH (p:Person)-[r:ACTED_IN]->(m:Movie) WHERE m.title = "The Matrix" RETURN r LIMIT 1', + query: + 'MATCH (p:Person)-[r:ACTED_IN]->(m:Movie) WHERE m.title = "The Matrix" RETURN r LIMIT 1', params: {}, }; const queryCallback: QueryCallback = { onSuccess: (result: NeodashRecord[]) => { - const relationship = result[0]['r']; + const relationship = result[0]["r"]; // parseGraphObject now returns a plain object (not a Relationship instance) - expect(relationship).toHaveProperty('type'); - expect(relationship).toHaveProperty('properties'); + expect(relationship).toHaveProperty("type"); + expect(relationship).toHaveProperty("properties"); expect(relationship).toMatchObject({ identity: expect.anything(), @@ -72,35 +77,44 @@ describe('Neo4jRecordParser - Objects Parsing', () => { }); }, onFail: (error) => { - console.error('Error during query execution:', error); + console.error("Error during query execution:", error); throw error; }, }; - await connection.runQuery(queryParams, queryCallback, NEO4J_TEST_CONNECTION_CONFIG); + await connection.runQuery( + queryParams, + queryCallback, + NEO4J_TEST_CONNECTION_CONFIG, + ); }); - test('should correctly parse a Neo4j Path with ordered nodes', async () => { + test("should correctly parse a Neo4j Path with ordered nodes", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); const queryParams: QueryParams = { - query: 'MATCH p = (a:Person)-[:ACTED_IN]->(m:Movie) WITH p ORDER BY ID(a), ID(m) RETURN p LIMIT 1', + query: + "MATCH p = (a:Person)-[:ACTED_IN]->(m:Movie) WITH p ORDER BY ID(a), ID(m) RETURN p LIMIT 1", params: {}, }; const queryCallback: QueryCallback = { onSuccess: () => {}, onFail: (error) => { - console.error('Error during query execution:', error); + console.error("Error during query execution:", error); throw error; }, }; - await connection.runQuery(queryParams, queryCallback, NEO4J_TEST_CONNECTION_CONFIG); + await connection.runQuery( + queryParams, + queryCallback, + NEO4J_TEST_CONNECTION_CONFIG, + ); }); - test('should correctly parse complex array structures from Movie DB', async () => { + test("should correctly parse complex array structures from Movie DB", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); @@ -121,26 +135,30 @@ describe('Neo4jRecordParser - Objects Parsing', () => { const [record] = parsed; // Types - expect(Array.isArray(record['actorNames'])).toBe(true); - expect(Array.isArray(record['mixedArray'])).toBe(true); - expect(Array.isArray(record['nestedArray'])).toBe(true); - expect(Array.isArray(record['nestedArray'][0])).toBe(true); + expect(Array.isArray(record["actorNames"])).toBe(true); + expect(Array.isArray(record["mixedArray"])).toBe(true); + expect(Array.isArray(record["nestedArray"])).toBe(true); + expect(Array.isArray(record["nestedArray"][0])).toBe(true); // Inner values - expect(typeof record['mixedArray'][0]).toBe('number'); // released - expect(typeof record['mixedArray'][1]).toBe('string'); // tagline - expect(typeof record['mixedArray'][2]).toBe('string'); // datetime → formatted string + expect(typeof record["mixedArray"][0]).toBe("number"); // released + expect(typeof record["mixedArray"][1]).toBe("string"); // tagline + expect(typeof record["mixedArray"][2]).toBe("string"); // datetime → formatted string }, onFail: (error) => { - console.error('Error during query execution:', error); + console.error("Error during query execution:", error); throw error; }, }; - await connection.runQuery(queryParams, queryCallback, NEO4J_TEST_CONNECTION_CONFIG); + await connection.runQuery( + queryParams, + queryCallback, + NEO4J_TEST_CONNECTION_CONFIG, + ); }); - test('should correctly parse a plain object with mixed types', async () => { + test("should correctly parse a plain object with mixed types", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); @@ -160,30 +178,34 @@ describe('Neo4jRecordParser - Objects Parsing', () => { const queryCallback: QueryCallback = { onSuccess: (parsed) => { - const data = parsed[0]['data']; + const data = parsed[0]["data"]; expect(data.count).toBe(123); expect(data.flag).toBe(true); - expect(data.info.label).toBe('neo4j'); - expect(typeof data.info.created).toBe('string'); // datetime → formatted string + expect(data.info.label).toBe("neo4j"); + expect(typeof data.info.created).toBe("string"); // datetime → formatted string }, onFail: (error) => { - console.error('Error during query execution:', error); + console.error("Error during query execution:", error); throw error; }, }; - await connection.runQuery(queryParams, queryCallback, NEO4J_TEST_CONNECTION_CONFIG); + await connection.runQuery( + queryParams, + queryCallback, + NEO4J_TEST_CONNECTION_CONFIG, + ); }); - test('Run MATCH (p:Person {name: $name}) RETURN p with parameter', async () => { + test("Run MATCH (p:Person {name: $name}) RETURN p with parameter", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); const queryParams: QueryParams = { - query: 'MATCH (p:Person {name: $name}) RETURN p LIMIT 1', + query: "MATCH (p:Person {name: $name}) RETURN p LIMIT 1", params: { - name: 'Tom Hanks', + name: "Tom Hanks", }, }; @@ -191,32 +213,36 @@ describe('Neo4jRecordParser - Objects Parsing', () => { onSuccess: (parsed) => { expect(parsed.length).toBe(1); - const person = parsed[0]['p']; + const person = parsed[0]["p"]; expect(person).toBeDefined(); - expect(person.labels).toContain('Person'); - expect(person.properties.name).toBe('Tom Hanks'); + expect(person.labels).toContain("Person"); + expect(person.properties.name).toBe("Tom Hanks"); }, onFail: (err) => { - console.error('Error executing parameterized query:', err); + console.error("Error executing parameterized query:", err); throw err; }, }; - await connection.runQuery(queryParams, queryCallback, NEO4J_TEST_CONNECTION_CONFIG); + await connection.runQuery( + queryParams, + queryCallback, + NEO4J_TEST_CONNECTION_CONFIG, + ); }); - test('should correctly parse a Neo4j Point value', async () => { + test("should correctly parse a Neo4j Point value", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); const queryParams: QueryParams = { - query: 'RETURN point({x: 1.2, y: 3.4, srid: 7203}) AS location', + query: "RETURN point({x: 1.2, y: 3.4, srid: 7203}) AS location", params: {}, }; const queryCallback: QueryCallback = { onSuccess: (parsed) => { - const location = parsed[0]['location']; + const location = parsed[0]["location"]; expect(location).toBeDefined(); expect(location.srid).toBe(7203); @@ -224,45 +250,54 @@ describe('Neo4jRecordParser - Objects Parsing', () => { expect(location.y).toBe(3.4); // Types - expect(typeof location.x).toBe('number'); - expect(typeof location.y).toBe('number'); - expect(typeof location.srid).toBe('number'); + expect(typeof location.x).toBe("number"); + expect(typeof location.y).toBe("number"); + expect(typeof location.srid).toBe("number"); }, onFail: (error) => { - console.error('Error during query execution:', error); + console.error("Error during query execution:", error); throw error; }, }; - await connection.runQuery(queryParams, queryCallback, NEO4J_TEST_CONNECTION_CONFIG); + await connection.runQuery( + queryParams, + queryCallback, + NEO4J_TEST_CONNECTION_CONFIG, + ); }); - test('should correctly parse a 3D Neo4j Point with z coordinate', async () => { + test("should correctly parse a 3D Neo4j Point with z coordinate", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); const queryParams: QueryParams = { - query: 'RETURN point({x: 10.5, y: 20.5, z: 5.0, srid: 9157}) AS location3D', + query: + "RETURN point({x: 10.5, y: 20.5, z: 5.0, srid: 9157}) AS location3D", params: {}, }; const queryCallback: QueryCallback = { onSuccess: (parsed) => { - const location = parsed[0]['location3D']; + const location = parsed[0]["location3D"]; expect(location).toBeDefined(); // Base fields - expect(location['x']).toBe(10.5); - expect(location['y']).toBe(20.5); - expect(location['srid']).toBe(9157); - expect(location['z']).toBe(5.0); + expect(location["x"]).toBe(10.5); + expect(location["y"]).toBe(20.5); + expect(location["srid"]).toBe(9157); + expect(location["z"]).toBe(5.0); }, onFail: (err) => { - console.error('Error during 3D point parsing:', err); + console.error("Error during 3D point parsing:", err); throw err; }, }; - await connection.runQuery(queryParams, queryCallback, NEO4J_TEST_CONNECTION_CONFIG); + await connection.runQuery( + queryParams, + queryCallback, + NEO4J_TEST_CONNECTION_CONFIG, + ); }); }); diff --git a/connection/__tests__/neo4j/parser/parser-record-primitive.ts b/connection/__tests__/neo4j/parser/parser-record-primitive.ts index cbf36145..2365ebf4 100644 --- a/connection/__tests__/neo4j/parser/parser-record-primitive.ts +++ b/connection/__tests__/neo4j/parser/parser-record-primitive.ts @@ -1,55 +1,63 @@ -import { getNeo4jAuth } from '../../utils/setup'; -import { Neo4jConnectionModule } from '../../../src/neo4j/Neo4jConnectionModule'; -import { QueryCallback, QueryParams } from '../../../src/generalized/interfaces'; -import { NEO4J_TEST_CONNECTION_CONFIG } from '../../utils/setup'; -import { NeodashRecord } from '../../../src/generalized/NeodashRecord'; - -describe('Neo4jRecordParser - Primitive Parsing', () => { - test('should correctly parse a Neo4j Integer value', async () => { +import { getNeo4jAuth } from "../../utils/setup"; +import { Neo4jConnectionModule } from "../../../src/neo4j/Neo4jConnectionModule"; +import { QueryCallback, QueryParams } from "@neoboard/connector-sdk"; +import { NEO4J_TEST_CONNECTION_CONFIG } from "../../utils/setup"; +import { NeodashRecord } from "@neoboard/connector-sdk"; + +describe("Neo4jRecordParser - Primitive Parsing", () => { + test("should correctly parse a Neo4j Integer value", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); const queryParams: QueryParams = { - query: 'RETURN 42 AS number', + query: "RETURN 42 AS number", params: {}, }; const queryCallback: QueryCallback = { onSuccess: (result: NeodashRecord[]) => { expect(result.length).toBe(1); - expect(result[0]['number']).toBe(42); + expect(result[0]["number"]).toBe(42); }, onFail: (error) => { - console.error('Error during query execution:', error); + console.error("Error during query execution:", error); throw error; }, }; - await connection.runQuery(queryParams, queryCallback, NEO4J_TEST_CONNECTION_CONFIG); + await connection.runQuery( + queryParams, + queryCallback, + NEO4J_TEST_CONNECTION_CONFIG, + ); }); - test('should correctly parse a Neo4j big int value', async () => { + test("should correctly parse a Neo4j big int value", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); const queryParams: QueryParams = { - query: 'RETURN 9223372036854775807 as number', + query: "RETURN 9223372036854775807 as number", params: {}, }; const queryCallback: QueryCallback = { onSuccess: (result: NeodashRecord[]) => { expect(result.length).toBe(1); - expect(result[0]['number']).toBe(9223372036854775807n); + expect(result[0]["number"]).toBe(9223372036854775807n); }, onFail: (error) => { - console.error('Error during query execution:', error); + console.error("Error during query execution:", error); throw error; }, }; - await connection.runQuery(queryParams, queryCallback, NEO4J_TEST_CONNECTION_CONFIG); + await connection.runQuery( + queryParams, + queryCallback, + NEO4J_TEST_CONNECTION_CONFIG, + ); }); - test('should correctly parse a Neo4j String value', async () => { + test("should correctly parse a Neo4j String value", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); const queryParams: QueryParams = { @@ -60,62 +68,74 @@ describe('Neo4jRecordParser - Primitive Parsing', () => { const queryCallback: QueryCallback = { onSuccess: (result: NeodashRecord[]) => { expect(result.length).toBe(1); - expect(result[0]['message']).toBe('hello world'); - expect(typeof result[0]['message']).toBe('string'); + expect(result[0]["message"]).toBe("hello world"); + expect(typeof result[0]["message"]).toBe("string"); }, onFail: (error) => { - console.error('Error during query execution:', error); + console.error("Error during query execution:", error); throw error; }, }; - await connection.runQuery(queryParams, queryCallback, NEO4J_TEST_CONNECTION_CONFIG); + await connection.runQuery( + queryParams, + queryCallback, + NEO4J_TEST_CONNECTION_CONFIG, + ); }); - test('should correctly parse a Neo4j Boolean true value', async () => { + test("should correctly parse a Neo4j Boolean true value", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); const queryParams: QueryParams = { - query: 'RETURN true AS active', + query: "RETURN true AS active", params: {}, }; const queryCallback: QueryCallback = { onSuccess: (result: NeodashRecord[]) => { expect(result.length).toBe(1); - expect(result[0]['active']).toBe(true); - expect(typeof result[0]['active']).toBe('boolean'); // Ensure 'active' is of type boolean + expect(result[0]["active"]).toBe(true); + expect(typeof result[0]["active"]).toBe("boolean"); // Ensure 'active' is of type boolean }, onFail: (error) => { - console.error('Error during query execution:', error); + console.error("Error during query execution:", error); throw error; }, }; - await connection.runQuery(queryParams, queryCallback, NEO4J_TEST_CONNECTION_CONFIG); + await connection.runQuery( + queryParams, + queryCallback, + NEO4J_TEST_CONNECTION_CONFIG, + ); }); - test('should correctly parse a Neo4j Boolean false value', async () => { + test("should correctly parse a Neo4j Boolean false value", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); const queryParams: QueryParams = { - query: 'RETURN false AS active', + query: "RETURN false AS active", params: {}, }; const queryCallback: QueryCallback = { onSuccess: (result: NeodashRecord[]) => { expect(result.length).toBe(1); - expect(result[0]['active']).toBe(false); - expect(typeof result[0]['active']).toBe('boolean'); // Ensure 'active' is of type boolean + expect(result[0]["active"]).toBe(false); + expect(typeof result[0]["active"]).toBe("boolean"); // Ensure 'active' is of type boolean }, onFail: (error) => { - console.error('Error during query execution:', error); + console.error("Error during query execution:", error); throw error; }, }; - await connection.runQuery(queryParams, queryCallback, NEO4J_TEST_CONNECTION_CONFIG); + await connection.runQuery( + queryParams, + queryCallback, + NEO4J_TEST_CONNECTION_CONFIG, + ); }); test('should correctly find the movie "The Matrix"', async () => { @@ -124,7 +144,8 @@ describe('Neo4jRecordParser - Primitive Parsing', () => { const connection = new Neo4jConnectionModule(config); const queryParams: QueryParams = { - query: 'MATCH (m:Movie) WHERE m.title = "The Matrix" RETURN m.title AS title LIMIT 1', + query: + 'MATCH (m:Movie) WHERE m.title = "The Matrix" RETURN m.title AS title LIMIT 1', params: {}, }; @@ -132,25 +153,29 @@ describe('Neo4jRecordParser - Primitive Parsing', () => { onSuccess: (parsed) => { expect(parsed.length).toBe(1); - expect(parsed[0]['title']).toBe('The Matrix'); + expect(parsed[0]["title"]).toBe("The Matrix"); - expect(typeof parsed[0]['title']).toBe('string'); + expect(typeof parsed[0]["title"]).toBe("string"); }, onFail: (error) => { - console.error('Error during query execution:', error); + console.error("Error during query execution:", error); throw error; }, }; - await connection.runQuery(queryParams, queryCallback, NEO4J_TEST_CONNECTION_CONFIG); + await connection.runQuery( + queryParams, + queryCallback, + NEO4J_TEST_CONNECTION_CONFIG, + ); }); - test('should return null', async () => { + test("should return null", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); const queryParams: QueryParams = { - query: 'RETURN null', + query: "RETURN null", params: {}, }; @@ -160,14 +185,18 @@ describe('Neo4jRecordParser - Primitive Parsing', () => { expect(parsed.length).toBe(1); // Since 'RETURN null' returns one record // Check that the 'null' key in the result is actually null - expect(parsed[0]['null']).toBe(null); + expect(parsed[0]["null"]).toBe(null); }, onFail: (error) => { - console.error('Error during query execution:', error); + console.error("Error during query execution:", error); throw error; }, }; - await connection.runQuery(queryParams, queryCallback, NEO4J_TEST_CONNECTION_CONFIG); + await connection.runQuery( + queryParams, + queryCallback, + NEO4J_TEST_CONNECTION_CONFIG, + ); }); }); diff --git a/connection/__tests__/neo4j/parser/parser-record-temporal.ts b/connection/__tests__/neo4j/parser/parser-record-temporal.ts index 17708d4b..94648baa 100644 --- a/connection/__tests__/neo4j/parser/parser-record-temporal.ts +++ b/connection/__tests__/neo4j/parser/parser-record-temporal.ts @@ -1,74 +1,84 @@ -import { getNeo4jAuth } from '../../utils/setup'; -import { Neo4jConnectionModule } from '../../../src/neo4j/Neo4jConnectionModule'; -import { QueryCallback, QueryParams } from '../../../src/generalized/interfaces'; -import { NEO4J_TEST_CONNECTION_CONFIG } from '../../utils/setup'; +import { getNeo4jAuth } from "../../utils/setup"; +import { Neo4jConnectionModule } from "../../../src/neo4j/Neo4jConnectionModule"; +import { QueryCallback, QueryParams } from "@neoboard/connector-sdk"; +import { NEO4J_TEST_CONNECTION_CONFIG } from "../../utils/setup"; -describe('Neo4jRecordParser - Temporal Parsing', () => { - test('should correctly parse a Neo4j Date value to YYYY-MM-DD string', async () => { +describe("Neo4jRecordParser - Temporal Parsing", () => { + test("should correctly parse a Neo4j Date value to YYYY-MM-DD string", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); const queryParams: QueryParams = { - query: 'RETURN date() AS currentDate', + query: "RETURN date() AS currentDate", params: {}, }; const queryCallback: QueryCallback = { onSuccess: (parsed) => { - const currentDate = parsed[0]['currentDate']; + const currentDate = parsed[0]["currentDate"]; expect(currentDate).toBeDefined(); - expect(typeof currentDate).toBe('string'); + expect(typeof currentDate).toBe("string"); // Expect YYYY-MM-DD format expect(currentDate).toMatch(/^\d{4}-\d{2}-\d{2}$/); }, onFail: (error) => { - console.error('Error during query execution:', error); + console.error("Error during query execution:", error); throw error; }, }; - await connection.runQuery(queryParams, queryCallback, NEO4J_TEST_CONNECTION_CONFIG); + await connection.runQuery( + queryParams, + queryCallback, + NEO4J_TEST_CONNECTION_CONFIG, + ); }); - test('should correctly parse a Neo4j DateTime value to formatted string', async () => { + test("should correctly parse a Neo4j DateTime value to formatted string", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); const queryParams: QueryParams = { - query: 'RETURN datetime() AS currentDateTime', + query: "RETURN datetime() AS currentDateTime", params: {}, }; const queryCallback: QueryCallback = { onSuccess: (parsed) => { - const currentDateTime = parsed[0]['currentDateTime']; + const currentDateTime = parsed[0]["currentDateTime"]; expect(currentDateTime).toBeDefined(); - expect(typeof currentDateTime).toBe('string'); + expect(typeof currentDateTime).toBe("string"); // Expect YYYY-MM-DD HH:mm:ss format - expect(currentDateTime).toMatch(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/); + expect(currentDateTime).toMatch( + /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/, + ); }, onFail: (error) => { - console.error('Error during query execution:', error); + console.error("Error during query execution:", error); throw error; }, }; - await connection.runQuery(queryParams, queryCallback, NEO4J_TEST_CONNECTION_CONFIG); + await connection.runQuery( + queryParams, + queryCallback, + NEO4J_TEST_CONNECTION_CONFIG, + ); }); - test('should correctly parse a Neo4j LocalDateTime value', async () => { + test("should correctly parse a Neo4j LocalDateTime value", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); const queryParams: QueryParams = { - query: 'RETURN localdatetime() AS currentLocalDateTime', + query: "RETURN localdatetime() AS currentLocalDateTime", params: {}, }; const queryCallback: QueryCallback = { onSuccess: (parsed) => { - const currentLocalDateTime = parsed[0]['currentLocalDateTime']; + const currentLocalDateTime = parsed[0]["currentLocalDateTime"]; expect(currentLocalDateTime).toBeDefined(); // Check if the parsed value is a valid JS Date object @@ -76,26 +86,31 @@ describe('Neo4jRecordParser - Temporal Parsing', () => { expect(!isNaN(currentLocalDateTime.getTime())).toBe(true); // Ensure valid timestamp }, onFail: (error) => { - console.error('Error during query execution:', error); + console.error("Error during query execution:", error); throw error; }, }; - await connection.runQuery(queryParams, queryCallback, NEO4J_TEST_CONNECTION_CONFIG); + await connection.runQuery( + queryParams, + queryCallback, + NEO4J_TEST_CONNECTION_CONFIG, + ); }); - test('should correctly parse a Neo4j Duration value', async () => { + test("should correctly parse a Neo4j Duration value", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); const queryParams: QueryParams = { - query: 'RETURN duration({months: 5, days: 10, seconds: 60, nanoseconds: 500}) AS period', + query: + "RETURN duration({months: 5, days: 10, seconds: 60, nanoseconds: 500}) AS period", params: {}, }; const queryCallback: QueryCallback = { onSuccess: (parsed) => { - const period = parsed[0]['period']; + const period = parsed[0]["period"]; expect(period).toBeDefined(); expect(period).toMatchObject({ @@ -106,67 +121,80 @@ describe('Neo4jRecordParser - Temporal Parsing', () => { }); }, onFail: (error) => { - console.error('Error during query execution:', error); + console.error("Error during query execution:", error); throw error; }, }; - await connection.runQuery(queryParams, queryCallback, NEO4J_TEST_CONNECTION_CONFIG); + await connection.runQuery( + queryParams, + queryCallback, + NEO4J_TEST_CONNECTION_CONFIG, + ); }); - test('should correctly parse a Neo4j LocalTime value', async () => { + test("should correctly parse a Neo4j LocalTime value", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); const queryParams: QueryParams = { - query: 'RETURN localtime() AS currentTime', + query: "RETURN localtime() AS currentTime", params: {}, }; const queryCallback: QueryCallback = { onSuccess: (parsed) => { - const currentTime = parsed[0]['currentTime']; + const currentTime = parsed[0]["currentTime"]; expect(currentTime).toBeDefined(); - expect(typeof currentTime).toBe('string'); + expect(typeof currentTime).toBe("string"); const timeFormatRegex = /^\d{1,2}:\d{1,2}:\d{1,2}\.\d{1,9}$/; expect(timeFormatRegex.test(currentTime)).toBe(true); }, onFail: (error) => { - console.error('Error during query execution:', error); + console.error("Error during query execution:", error); throw error; }, }; - await connection.runQuery(queryParams, queryCallback, NEO4J_TEST_CONNECTION_CONFIG); + await connection.runQuery( + queryParams, + queryCallback, + NEO4J_TEST_CONNECTION_CONFIG, + ); }); - test('should correctly parse a Neo4j Time value with offset', async () => { + test("should correctly parse a Neo4j Time value with offset", async () => { const config = getNeo4jAuth(); const connection = new Neo4jConnectionModule(config); const queryParams: QueryParams = { - query: 'RETURN time() AS currentTimeWithOffset', + query: "RETURN time() AS currentTimeWithOffset", params: {}, }; const queryCallback: QueryCallback = { onSuccess: (parsed) => { - const currentTimeWithOffset = parsed[0]['currentTimeWithOffset']; + const currentTimeWithOffset = parsed[0]["currentTimeWithOffset"]; expect(currentTimeWithOffset).toBeDefined(); - expect(typeof currentTimeWithOffset).toBe('string'); + expect(typeof currentTimeWithOffset).toBe("string"); - const timeWithOffsetRegex = /^\d{1,2}:\d{1,2}:\d{1,2}\.\d{1,9}[+-]\d{2}:\d{2}$/; + const timeWithOffsetRegex = + /^\d{1,2}:\d{1,2}:\d{1,2}\.\d{1,9}[+-]\d{2}:\d{2}$/; expect(timeWithOffsetRegex.test(currentTimeWithOffset)).toBe(true); }, onFail: (error) => { - console.error('Error during query execution:', error); + console.error("Error during query execution:", error); throw error; }, }; - await connection.runQuery(queryParams, queryCallback, NEO4J_TEST_CONNECTION_CONFIG); + await connection.runQuery( + queryParams, + queryCallback, + NEO4J_TEST_CONNECTION_CONFIG, + ); }); }); diff --git a/connection/__tests__/neodash_record/neodash-record.ts b/connection/__tests__/neodash_record/neodash-record.ts index d2eaf125..423e1eeb 100644 --- a/connection/__tests__/neodash_record/neodash-record.ts +++ b/connection/__tests__/neodash_record/neodash-record.ts @@ -1,67 +1,67 @@ -import { NeodashRecord } from '../../src/generalized/NeodashRecord'; +import { NeodashRecord } from "@neoboard/connector-sdk"; -describe('NeodashRecord Movie node CRUD operations', () => { - test('Create: should allow adding new properties dynamically to a movie record', () => { +describe("NeodashRecord Movie node CRUD operations", () => { + test("Create: should allow adding new properties dynamically to a movie record", () => { const movie = new NeodashRecord({}); - movie['title'] = 'The Matrix'; - expect(movie['title']).toBe('The Matrix'); + movie["title"] = "The Matrix"; + expect(movie["title"]).toBe("The Matrix"); }); - test('Read: should allow reading existing movie properties', () => { + test("Read: should allow reading existing movie properties", () => { const movie = new NeodashRecord({ - title: 'The Matrix', + title: "The Matrix", released: 1999, - tagline: 'Welcome to the Real World', + tagline: "Welcome to the Real World", }); - expect(movie['title']).toBe('The Matrix'); - expect(movie['released']).toBe(1999); - expect(movie['tagline']).toBe('Welcome to the Real World'); + expect(movie["title"]).toBe("The Matrix"); + expect(movie["released"]).toBe(1999); + expect(movie["tagline"]).toBe("Welcome to the Real World"); }); - test('Update: should allow updating existing movie properties', () => { + test("Update: should allow updating existing movie properties", () => { const movie = new NeodashRecord({ - title: 'The Matrix', + title: "The Matrix", released: 1999, }); - movie['released'] = 2000; - movie['tagline'] = 'A new reality begins'; + movie["released"] = 2000; + movie["tagline"] = "A new reality begins"; - expect(movie['released']).toBe(2000); - expect(movie['tagline']).toBe('A new reality begins'); + expect(movie["released"]).toBe(2000); + expect(movie["tagline"]).toBe("A new reality begins"); }); - test('Read: should return undefined for non-existing movie property', () => { + test("Read: should return undefined for non-existing movie property", () => { const movie = new NeodashRecord({ - title: 'The Matrix', + title: "The Matrix", released: 1999, }); - expect(movie['director']).toBeUndefined(); // not set + expect(movie["director"]).toBeUndefined(); // not set }); - test('should expose toObject() via proxy', () => { - const record = new NeodashRecord({ name: 'Alice', age: 30 }); + test("should expose toObject() via proxy", () => { + const record = new NeodashRecord({ name: "Alice", age: 30 }); const obj = record.toObject(); - expect(obj).toEqual({ name: 'Alice', age: 30 }); + expect(obj).toEqual({ name: "Alice", age: 30 }); }); - test('should expose toJSON() via JSON.stringify (proxy trap)', () => { - const record = new NeodashRecord({ name: 'Bob', role: 'Agent' }); + test("should expose toJSON() via JSON.stringify (proxy trap)", () => { + const record = new NeodashRecord({ name: "Bob", role: "Agent" }); const json = JSON.stringify(record); - expect(json).toBe(JSON.stringify({ name: 'Bob', role: 'Agent' })); + expect(json).toBe(JSON.stringify({ name: "Bob", role: "Agent" })); }); - test('should expose keys via Object.keys (proxy trap ownKeys)', () => { - const record = new NeodashRecord({ title: 'Inception', year: 2010 }); + test("should expose keys via Object.keys (proxy trap ownKeys)", () => { + const record = new NeodashRecord({ title: "Inception", year: 2010 }); const keys = Object.keys(record.toObject()); - expect(keys).toEqual(['title', 'year']); + expect(keys).toEqual(["title", "year"]); }); - test('should expose keys via getFields (proxy trap getFields)', () => { - const record = new NeodashRecord({ title: 'Inception', year: 2010 }); + test("should expose keys via getFields (proxy trap getFields)", () => { + const record = new NeodashRecord({ title: "Inception", year: 2010 }); const keys = record.getFields(); - expect(keys).toEqual(['title', 'year']); + expect(keys).toEqual(["title", "year"]); }); }); diff --git a/connection/__tests__/postgresql/postgres-authentication.ts b/connection/__tests__/postgresql/postgres-authentication.ts index e11ae55d..77edc6c3 100644 --- a/connection/__tests__/postgresql/postgres-authentication.ts +++ b/connection/__tests__/postgresql/postgres-authentication.ts @@ -3,7 +3,7 @@ import { PostgreSqlContainer, StartedPostgreSqlContainer, } from "@testcontainers/postgresql"; -import { AuthType } from "../../src/generalized/interfaces"; +import { AuthType } from "@neoboard/connector-sdk"; describe("PostgreSQL Authentication", () => { let container: StartedPostgreSqlContainer; diff --git a/connection/__tests__/postgresql/postgres-check-connection.ts b/connection/__tests__/postgresql/postgres-check-connection.ts index 10b050a4..f7cac8bc 100644 --- a/connection/__tests__/postgresql/postgres-check-connection.ts +++ b/connection/__tests__/postgresql/postgres-check-connection.ts @@ -11,11 +11,8 @@ import { StartedPostgreSqlContainer, } from "@testcontainers/postgresql"; import { PostgresConnectionModule } from "../../src/postgresql"; -import { AuthType } from "../../src/generalized/interfaces"; -import { - ConnectorError, - ConnectorErrorType, -} from "../../src/generalized/ConnectorError"; +import { AuthType } from "@neoboard/connector-sdk"; +import { ConnectorError, ConnectorErrorType } from "@neoboard/connector-sdk"; describe("PostgresConnectionModule.checkConnection", () => { let container: StartedPostgreSqlContainer; diff --git a/connection/__tests__/postgresql/postgres-param-ordering.ts b/connection/__tests__/postgresql/postgres-param-ordering.ts index bfaf4dfd..7529e017 100644 --- a/connection/__tests__/postgresql/postgres-param-ordering.ts +++ b/connection/__tests__/postgresql/postgres-param-ordering.ts @@ -4,7 +4,7 @@ import { QueryStatus, AuthType, ConnectionTypes, -} from "../../src/generalized/interfaces"; +} from "@neoboard/connector-sdk"; import { PostgreSqlContainer } from "@testcontainers/postgresql"; describe("PostgreSQL Parameter Ordering", () => { diff --git a/connection/__tests__/postgresql/postgres-parser.ts b/connection/__tests__/postgresql/postgres-parser.ts index 324fe576..f5609540 100644 --- a/connection/__tests__/postgresql/postgres-parser.ts +++ b/connection/__tests__/postgresql/postgres-parser.ts @@ -1,5 +1,5 @@ import { PostgresRecordParser } from "../../src/postgresql/PostgresRecordParser"; -import { NeodashRecord } from "../../src/generalized/NeodashRecord"; +import { NeodashRecord } from "@neoboard/connector-sdk"; describe("PostgreSQL Record Parser", () => { let parser: PostgresRecordParser; diff --git a/connection/__tests__/postgresql/postgres-query.ts b/connection/__tests__/postgresql/postgres-query.ts index 16f6c30b..2e684856 100644 --- a/connection/__tests__/postgresql/postgres-query.ts +++ b/connection/__tests__/postgresql/postgres-query.ts @@ -4,7 +4,7 @@ import { QueryStatus, AuthType, ConnectionTypes, -} from "../../src/generalized/interfaces"; +} from "@neoboard/connector-sdk"; import { PostgreSqlContainer } from "@testcontainers/postgresql"; describe("PostgreSQL Query Execution", () => { diff --git a/connection/__tests__/schema/neo4j-schema.test.ts b/connection/__tests__/schema/neo4j-schema.test.ts index ca2d9011..18ca7d16 100644 --- a/connection/__tests__/schema/neo4j-schema.test.ts +++ b/connection/__tests__/schema/neo4j-schema.test.ts @@ -1,10 +1,10 @@ -import { Neo4jSchemaManager } from '../../src/schema/neo4j-schema'; -import { AuthType } from '../../src/generalized/interfaces'; +import { Neo4jSchemaManager } from "../../src/schema/neo4j-schema"; +import { AuthType } from "@neoboard/connector-sdk"; // Module-level variable — safe to reference in hoisted mock factory let _mockRun = jest.fn(); -jest.mock('neo4j-driver', () => { +jest.mock("neo4j-driver", () => { const mockSession = { run: (...args: unknown[]) => _mockRun(...args), close: jest.fn().mockResolvedValue(undefined), @@ -17,16 +17,19 @@ jest.mock('neo4j-driver', () => { __esModule: true, default: { driver: jest.fn().mockReturnValue(mockDriver), - auth: { basic: jest.fn().mockReturnValue({}), none: jest.fn().mockReturnValue({}) }, - session: { READ: 'READ', WRITE: 'WRITE' }, + auth: { + basic: jest.fn().mockReturnValue({}), + none: jest.fn().mockReturnValue({}), + }, + session: { READ: "READ", WRITE: "WRITE" }, isInt: jest.fn().mockReturnValue(false), }, }; }); -jest.mock('../../src/neo4j/Neo4jConnectionModule', () => { +jest.mock("../../src/neo4j/Neo4jConnectionModule", () => { // eslint-disable-next-line @typescript-eslint/no-require-imports - const neo4j = require('neo4j-driver').default; + const neo4j = require("neo4j-driver").default; const driver = neo4j.driver(); return { Neo4jConnectionModule: jest.fn().mockImplementation(() => ({ @@ -36,9 +39,9 @@ jest.mock('../../src/neo4j/Neo4jConnectionModule', () => { }); const authConfig = { - uri: 'bolt://localhost:7687', - username: 'neo4j', - password: 'password', + uri: "bolt://localhost:7687", + username: "neo4j", + password: "password", authType: AuthType.NATIVE, }; @@ -46,21 +49,32 @@ const authConfig = { const emptyResult = { records: [] }; /** Helper: create a label record */ -const labelRecord = (label: string) => ({ keys: ['label'], get: () => label }); +const labelRecord = (label: string) => ({ keys: ["label"], get: () => label }); /** Helper: create a relationshipType record */ -const relTypeRecord = (rt: string) => ({ keys: ['relationshipType'], get: () => rt }); +const relTypeRecord = (rt: string) => ({ + keys: ["relationshipType"], + get: () => rt, +}); /** Helper: create a nodeTypeProperties record */ -const nodePropRecord = (nodeType: string, propertyName: string, propertyTypes: string[]) => ({ - keys: ['nodeType', 'propertyName', 'propertyTypes'], - get: (k: string) => ({ nodeType, propertyName, propertyTypes }[k]), +const nodePropRecord = ( + nodeType: string, + propertyName: string, + propertyTypes: string[], +) => ({ + keys: ["nodeType", "propertyName", "propertyTypes"], + get: (k: string) => ({ nodeType, propertyName, propertyTypes })[k], }); /** Helper: create a relTypeProperties record */ -const relPropRecord = (relType: string, propertyName: string, propertyTypes: string[]) => ({ - keys: ['relType', 'propertyName', 'propertyTypes'], - get: (k: string) => ({ relType, propertyName, propertyTypes }[k]), +const relPropRecord = ( + relType: string, + propertyName: string, + propertyTypes: string[], +) => ({ + keys: ["relType", "propertyName", "propertyTypes"], + get: (k: string) => ({ relType, propertyName, propertyTypes })[k], }); /** @@ -80,65 +94,74 @@ function mockFourCalls( .mockResolvedValueOnce({ records: relProps }); } -describe('Neo4jSchemaManager', () => { +describe("Neo4jSchemaManager", () => { beforeEach(() => { _mockRun = jest.fn(); }); - it('returns labels from db.labels()', async () => { - mockFourCalls( - [labelRecord('Person'), labelRecord('Movie')], - [], - [], - [], - ); + it("returns labels from db.labels()", async () => { + mockFourCalls([labelRecord("Person"), labelRecord("Movie")], [], [], []); const schema = await new Neo4jSchemaManager().fetchSchema(authConfig); - expect(schema.type).toBe('neo4j'); - expect(schema.labels).toEqual(['Person', 'Movie']); + expect(schema.type).toBe("neo4j"); + expect(schema.labels).toEqual(["Person", "Movie"]); }); - it('returns relationship types from db.relationshipTypes()', async () => { - mockFourCalls([], [relTypeRecord('ACTED_IN'), relTypeRecord('DIRECTED')], [], []); + it("returns relationship types from db.relationshipTypes()", async () => { + mockFourCalls( + [], + [relTypeRecord("ACTED_IN"), relTypeRecord("DIRECTED")], + [], + [], + ); const schema = await new Neo4jSchemaManager().fetchSchema(authConfig); - expect(schema.relationshipTypes).toEqual(['ACTED_IN', 'DIRECTED']); + expect(schema.relationshipTypes).toEqual(["ACTED_IN", "DIRECTED"]); }); - it('normalises nodeProperties from nodeType procedure', async () => { + it("normalises nodeProperties from nodeType procedure", async () => { mockFourCalls( [], [], - [nodePropRecord(':Person', 'name', ['String']), nodePropRecord(':Person', 'born', ['Long'])], + [ + nodePropRecord(":Person", "name", ["String"]), + nodePropRecord(":Person", "born", ["Long"]), + ], [], ); const schema = await new Neo4jSchemaManager().fetchSchema(authConfig); expect(schema.nodeProperties?.Person).toHaveLength(2); - expect(schema.nodeProperties?.Person?.[0]).toMatchObject({ name: 'name', type: 'String' }); - expect(schema.nodeProperties?.Person?.[1]).toMatchObject({ name: 'born', type: 'Long' }); + expect(schema.nodeProperties?.Person?.[0]).toMatchObject({ + name: "name", + type: "String", + }); + expect(schema.nodeProperties?.Person?.[1]).toMatchObject({ + name: "born", + type: "Long", + }); }); - it('strips leading colon from nodeType and relType', async () => { + it("strips leading colon from nodeType and relType", async () => { mockFourCalls( [], [], - [nodePropRecord(':Movie', 'title', ['String'])], - [relPropRecord(':ACTED_IN', 'roles', ['StringArray'])], + [nodePropRecord(":Movie", "title", ["String"])], + [relPropRecord(":ACTED_IN", "roles", ["StringArray"])], ); const schema = await new Neo4jSchemaManager().fetchSchema(authConfig); expect(schema.nodeProperties?.Movie).toBeDefined(); - expect(schema.nodeProperties?.[':Movie']).toBeUndefined(); + expect(schema.nodeProperties?.[":Movie"]).toBeUndefined(); expect(schema.relProperties?.ACTED_IN).toBeDefined(); - expect(schema.relProperties?.[':ACTED_IN']).toBeUndefined(); + expect(schema.relProperties?.[":ACTED_IN"]).toBeUndefined(); }); - it('returns empty collections for empty databases', async () => { + it("returns empty collections for empty databases", async () => { mockFourCalls([], [], [], []); const schema = await new Neo4jSchemaManager().fetchSchema(authConfig); diff --git a/connection/__tests__/schema/pg-schema.test.ts b/connection/__tests__/schema/pg-schema.test.ts index 7094e05c..8ab6142b 100644 --- a/connection/__tests__/schema/pg-schema.test.ts +++ b/connection/__tests__/schema/pg-schema.test.ts @@ -1,5 +1,5 @@ -import { PostgresSchemaManager } from '../../src/schema/pg-schema'; -import { AuthType } from '../../src/generalized/interfaces'; +import { PostgresSchemaManager } from "../../src/schema/pg-schema"; +import { AuthType } from "@neoboard/connector-sdk"; // Mock pg pool const mockClient = { @@ -12,51 +12,84 @@ const mockPool = { end: jest.fn().mockResolvedValue(undefined), }; -jest.mock('../../src/postgresql/PostgresConnectionModule', () => ({ +jest.mock("../../src/postgresql/PostgresConnectionModule", () => ({ PostgresConnectionModule: jest.fn().mockImplementation(() => ({ getPool: () => mockPool, })), })); const authConfig = { - uri: 'postgresql://localhost:5432/testdb', - username: 'postgres', - password: 'password', + uri: "postgresql://localhost:5432/testdb", + username: "postgres", + password: "password", authType: AuthType.NATIVE, }; -describe('PostgresSchemaManager', () => { +describe("PostgresSchemaManager", () => { beforeEach(() => { jest.clearAllMocks(); }); - it('returns tables with columns from information_schema query', async () => { + it("returns tables with columns from information_schema query", async () => { mockClient.query.mockResolvedValue({ rows: [ - { table_name: 'users', column_name: 'id', data_type: 'integer', is_nullable: 'NO' }, - { table_name: 'users', column_name: 'email', data_type: 'character varying', is_nullable: 'NO' }, - { table_name: 'posts', column_name: 'id', data_type: 'integer', is_nullable: 'NO' }, - { table_name: 'posts', column_name: 'title', data_type: 'text', is_nullable: 'YES' }, + { + table_name: "users", + column_name: "id", + data_type: "integer", + is_nullable: "NO", + }, + { + table_name: "users", + column_name: "email", + data_type: "character varying", + is_nullable: "NO", + }, + { + table_name: "posts", + column_name: "id", + data_type: "integer", + is_nullable: "NO", + }, + { + table_name: "posts", + column_name: "title", + data_type: "text", + is_nullable: "YES", + }, ], }); const manager = new PostgresSchemaManager(); const schema = await manager.fetchSchema(authConfig); - expect(schema.type).toBe('postgresql'); + expect(schema.type).toBe("postgresql"); expect(schema.tables).toHaveLength(2); - const usersTable = schema.tables?.find((t) => t.name === 'users'); + const usersTable = schema.tables?.find((t) => t.name === "users"); expect(usersTable).toBeDefined(); expect(usersTable?.columns).toHaveLength(2); - expect(usersTable?.columns[0]).toMatchObject({ name: 'id', type: 'integer', nullable: false }); - expect(usersTable?.columns[1]).toMatchObject({ name: 'email', type: 'character varying', nullable: false }); + expect(usersTable?.columns[0]).toMatchObject({ + name: "id", + type: "integer", + nullable: false, + }); + expect(usersTable?.columns[1]).toMatchObject({ + name: "email", + type: "character varying", + nullable: false, + }); }); - it('sets nullable=true when is_nullable is YES', async () => { + it("sets nullable=true when is_nullable is YES", async () => { mockClient.query.mockResolvedValue({ rows: [ - { table_name: 'posts', column_name: 'title', data_type: 'text', is_nullable: 'YES' }, + { + table_name: "posts", + column_name: "title", + data_type: "text", + is_nullable: "YES", + }, ], }); @@ -66,7 +99,7 @@ describe('PostgresSchemaManager', () => { expect(schema.tables?.[0]?.columns[0].nullable).toBe(true); }); - it('returns empty tables array for empty databases', async () => { + it("returns empty tables array for empty databases", async () => { mockClient.query.mockResolvedValue({ rows: [] }); const manager = new PostgresSchemaManager(); @@ -75,24 +108,39 @@ describe('PostgresSchemaManager', () => { expect(schema.tables).toEqual([]); }); - it('groups columns by table correctly', async () => { + it("groups columns by table correctly", async () => { mockClient.query.mockResolvedValue({ rows: [ - { table_name: 'a', column_name: 'x', data_type: 'text', is_nullable: 'NO' }, - { table_name: 'b', column_name: 'y', data_type: 'text', is_nullable: 'NO' }, - { table_name: 'a', column_name: 'z', data_type: 'text', is_nullable: 'NO' }, + { + table_name: "a", + column_name: "x", + data_type: "text", + is_nullable: "NO", + }, + { + table_name: "b", + column_name: "y", + data_type: "text", + is_nullable: "NO", + }, + { + table_name: "a", + column_name: "z", + data_type: "text", + is_nullable: "NO", + }, ], }); const manager = new PostgresSchemaManager(); const schema = await manager.fetchSchema(authConfig); - const tableA = schema.tables?.find((t) => t.name === 'a'); + const tableA = schema.tables?.find((t) => t.name === "a"); expect(tableA?.columns).toHaveLength(2); - expect(tableA?.columns.map((c) => c.name)).toEqual(['x', 'z']); + expect(tableA?.columns.map((c) => c.name)).toEqual(["x", "z"]); }); - it('releases the client and ends the pool after fetching', async () => { + it("releases the client and ends the pool after fetching", async () => { mockClient.query.mockResolvedValue({ rows: [] }); const manager = new PostgresSchemaManager(); diff --git a/connection/__tests__/uri-validation.test.ts b/connection/__tests__/uri-validation.test.ts index 6dded471..8ebee2bf 100644 --- a/connection/__tests__/uri-validation.test.ts +++ b/connection/__tests__/uri-validation.test.ts @@ -1,4 +1,4 @@ -import { AuthType } from '../src/generalized/interfaces'; +import { AuthType } from "@neoboard/connector-sdk"; // --------------------------------------------------------------------------- // Mocks — prevent actual driver connections @@ -10,12 +10,15 @@ const mockNeo4jDriverFn = jest.fn().mockReturnValue({ session: jest.fn(), }); -jest.mock('neo4j-driver', () => ({ +jest.mock("neo4j-driver", () => ({ __esModule: true, default: { driver: mockNeo4jDriverFn, auth: { - basic: jest.fn((u: string, p: string) => ({ principal: u, credentials: p })), + basic: jest.fn((u: string, p: string) => ({ + principal: u, + credentials: p, + })), }, }, })); @@ -27,7 +30,7 @@ const mockPoolInstance = { removeAllListeners: jest.fn(), }; -jest.mock('pg', () => ({ +jest.mock("pg", () => ({ Pool: jest.fn().mockImplementation(() => mockPoolInstance), })); @@ -35,184 +38,252 @@ jest.mock('pg', () => ({ // Tests // --------------------------------------------------------------------------- -describe('URI Validation', () => { +describe("URI Validation", () => { beforeEach(() => { jest.clearAllMocks(); }); - describe('Neo4j URI validation', () => { - it('accepts bolt:// protocol', () => { - const { Neo4jAuthenticationModule } = require('../src/neo4j/Neo4jAuthenticationModule'); + describe("Neo4j URI validation", () => { + it("accepts bolt:// protocol", () => { + const { + Neo4jAuthenticationModule, + } = require("../src/neo4j/Neo4jAuthenticationModule"); expect(() => { new Neo4jAuthenticationModule({ - username: 'neo4j', password: 'test', authType: AuthType.NATIVE, - uri: 'bolt://localhost:7687', + username: "neo4j", + password: "test", + authType: AuthType.NATIVE, + uri: "bolt://localhost:7687", }); }).not.toThrow(); }); - it('accepts neo4j:// protocol', () => { - const { Neo4jAuthenticationModule } = require('../src/neo4j/Neo4jAuthenticationModule'); + it("accepts neo4j:// protocol", () => { + const { + Neo4jAuthenticationModule, + } = require("../src/neo4j/Neo4jAuthenticationModule"); expect(() => { new Neo4jAuthenticationModule({ - username: 'neo4j', password: 'test', authType: AuthType.NATIVE, - uri: 'neo4j://localhost:7687', + username: "neo4j", + password: "test", + authType: AuthType.NATIVE, + uri: "neo4j://localhost:7687", }); }).not.toThrow(); }); - it('accepts bolt+s:// protocol', () => { - const { Neo4jAuthenticationModule } = require('../src/neo4j/Neo4jAuthenticationModule'); + it("accepts bolt+s:// protocol", () => { + const { + Neo4jAuthenticationModule, + } = require("../src/neo4j/Neo4jAuthenticationModule"); expect(() => { new Neo4jAuthenticationModule({ - username: 'neo4j', password: 'test', authType: AuthType.NATIVE, - uri: 'bolt+s://localhost:7687', + username: "neo4j", + password: "test", + authType: AuthType.NATIVE, + uri: "bolt+s://localhost:7687", }); }).not.toThrow(); }); - it('accepts bolt+ssc:// protocol', () => { - const { Neo4jAuthenticationModule } = require('../src/neo4j/Neo4jAuthenticationModule'); + it("accepts bolt+ssc:// protocol", () => { + const { + Neo4jAuthenticationModule, + } = require("../src/neo4j/Neo4jAuthenticationModule"); expect(() => { new Neo4jAuthenticationModule({ - username: 'neo4j', password: 'test', authType: AuthType.NATIVE, - uri: 'bolt+ssc://localhost:7687', + username: "neo4j", + password: "test", + authType: AuthType.NATIVE, + uri: "bolt+ssc://localhost:7687", }); }).not.toThrow(); }); - it('accepts neo4j+s:// protocol', () => { - const { Neo4jAuthenticationModule } = require('../src/neo4j/Neo4jAuthenticationModule'); + it("accepts neo4j+s:// protocol", () => { + const { + Neo4jAuthenticationModule, + } = require("../src/neo4j/Neo4jAuthenticationModule"); expect(() => { new Neo4jAuthenticationModule({ - username: 'neo4j', password: 'test', authType: AuthType.NATIVE, - uri: 'neo4j+s://localhost:7687', + username: "neo4j", + password: "test", + authType: AuthType.NATIVE, + uri: "neo4j+s://localhost:7687", }); }).not.toThrow(); }); - it('accepts neo4j+ssc:// protocol', () => { - const { Neo4jAuthenticationModule } = require('../src/neo4j/Neo4jAuthenticationModule'); + it("accepts neo4j+ssc:// protocol", () => { + const { + Neo4jAuthenticationModule, + } = require("../src/neo4j/Neo4jAuthenticationModule"); expect(() => { new Neo4jAuthenticationModule({ - username: 'neo4j', password: 'test', authType: AuthType.NATIVE, - uri: 'neo4j+ssc://localhost:7687', + username: "neo4j", + password: "test", + authType: AuthType.NATIVE, + uri: "neo4j+ssc://localhost:7687", }); }).not.toThrow(); }); - it('rejects http:// protocol', () => { - const { Neo4jAuthenticationModule } = require('../src/neo4j/Neo4jAuthenticationModule'); + it("rejects http:// protocol", () => { + const { + Neo4jAuthenticationModule, + } = require("../src/neo4j/Neo4jAuthenticationModule"); expect(() => { new Neo4jAuthenticationModule({ - username: 'neo4j', password: 'test', authType: AuthType.NATIVE, - uri: 'http://localhost:7687', + username: "neo4j", + password: "test", + authType: AuthType.NATIVE, + uri: "http://localhost:7687", }); - }).toThrow('Invalid URI protocol'); + }).toThrow("Invalid URI protocol"); }); - it('rejects postgresql:// protocol', () => { - const { Neo4jAuthenticationModule } = require('../src/neo4j/Neo4jAuthenticationModule'); + it("rejects postgresql:// protocol", () => { + const { + Neo4jAuthenticationModule, + } = require("../src/neo4j/Neo4jAuthenticationModule"); expect(() => { new Neo4jAuthenticationModule({ - username: 'neo4j', password: 'test', authType: AuthType.NATIVE, - uri: 'postgresql://localhost:5432', + username: "neo4j", + password: "test", + authType: AuthType.NATIVE, + uri: "postgresql://localhost:5432", }); - }).toThrow('Invalid URI protocol'); + }).toThrow("Invalid URI protocol"); }); - it('rejects malformed URI', () => { - const { Neo4jAuthenticationModule } = require('../src/neo4j/Neo4jAuthenticationModule'); + it("rejects malformed URI", () => { + const { + Neo4jAuthenticationModule, + } = require("../src/neo4j/Neo4jAuthenticationModule"); expect(() => { new Neo4jAuthenticationModule({ - username: 'neo4j', password: 'test', authType: AuthType.NATIVE, - uri: 'not-a-uri', + username: "neo4j", + password: "test", + authType: AuthType.NATIVE, + uri: "not-a-uri", }); - }).toThrow('Invalid URI format'); + }).toThrow("Invalid URI format"); }); - it('rejects SSO auth type with clear error', () => { - const { Neo4jAuthenticationModule } = require('../src/neo4j/Neo4jAuthenticationModule'); + it("rejects SSO auth type with clear error", () => { + const { + Neo4jAuthenticationModule, + } = require("../src/neo4j/Neo4jAuthenticationModule"); expect(() => { new Neo4jAuthenticationModule({ - username: 'neo4j', password: 'test', authType: AuthType.SINGLE_SIGN_ON, - uri: 'bolt://localhost:7687', + username: "neo4j", + password: "test", + authType: AuthType.SINGLE_SIGN_ON, + uri: "bolt://localhost:7687", }); - }).toThrow('SSO authentication is not yet supported'); + }).toThrow("SSO authentication is not yet supported"); }); }); - describe('PostgreSQL URI validation', () => { - it('accepts postgresql:// protocol', () => { - const { PostgresAuthenticationModule } = require('../src/postgresql/PostgresAuthenticationModule'); + describe("PostgreSQL URI validation", () => { + it("accepts postgresql:// protocol", () => { + const { + PostgresAuthenticationModule, + } = require("../src/postgresql/PostgresAuthenticationModule"); expect(() => { new PostgresAuthenticationModule({ - username: 'postgres', password: 'test', authType: AuthType.NATIVE, - uri: 'postgresql://localhost:5432/testdb', + username: "postgres", + password: "test", + authType: AuthType.NATIVE, + uri: "postgresql://localhost:5432/testdb", }); }).not.toThrow(); }); - it('accepts postgres:// protocol', () => { - const { PostgresAuthenticationModule } = require('../src/postgresql/PostgresAuthenticationModule'); + it("accepts postgres:// protocol", () => { + const { + PostgresAuthenticationModule, + } = require("../src/postgresql/PostgresAuthenticationModule"); expect(() => { new PostgresAuthenticationModule({ - username: 'postgres', password: 'test', authType: AuthType.NATIVE, - uri: 'postgres://localhost:5432/testdb', + username: "postgres", + password: "test", + authType: AuthType.NATIVE, + uri: "postgres://localhost:5432/testdb", }); }).not.toThrow(); }); - it('rejects bolt:// protocol', () => { - const { PostgresAuthenticationModule } = require('../src/postgresql/PostgresAuthenticationModule'); + it("rejects bolt:// protocol", () => { + const { + PostgresAuthenticationModule, + } = require("../src/postgresql/PostgresAuthenticationModule"); expect(() => { new PostgresAuthenticationModule({ - username: 'postgres', password: 'test', authType: AuthType.NATIVE, - uri: 'bolt://localhost:7687', + username: "postgres", + password: "test", + authType: AuthType.NATIVE, + uri: "bolt://localhost:7687", }); - }).toThrow('Invalid URI protocol'); + }).toThrow("Invalid URI protocol"); }); - it('rejects http:// protocol', () => { - const { PostgresAuthenticationModule } = require('../src/postgresql/PostgresAuthenticationModule'); + it("rejects http:// protocol", () => { + const { + PostgresAuthenticationModule, + } = require("../src/postgresql/PostgresAuthenticationModule"); expect(() => { new PostgresAuthenticationModule({ - username: 'postgres', password: 'test', authType: AuthType.NATIVE, - uri: 'http://localhost:5432', + username: "postgres", + password: "test", + authType: AuthType.NATIVE, + uri: "http://localhost:5432", }); - }).toThrow('Invalid URI protocol'); + }).toThrow("Invalid URI protocol"); }); - it('rejects malformed URI', () => { - const { PostgresAuthenticationModule } = require('../src/postgresql/PostgresAuthenticationModule'); + it("rejects malformed URI", () => { + const { + PostgresAuthenticationModule, + } = require("../src/postgresql/PostgresAuthenticationModule"); expect(() => { new PostgresAuthenticationModule({ - username: 'postgres', password: 'test', authType: AuthType.NATIVE, - uri: 'not-a-valid-uri', + username: "postgres", + password: "test", + authType: AuthType.NATIVE, + uri: "not-a-valid-uri", }); - }).toThrow('Invalid URI format'); + }).toThrow("Invalid URI format"); }); }); - describe('Base _checkConfigurationConsistency', () => { - it('rejects empty URI string', () => { - const { Neo4jAuthenticationModule } = require('../src/neo4j/Neo4jAuthenticationModule'); + describe("Base _checkConfigurationConsistency", () => { + it("rejects empty URI string", () => { + const { + Neo4jAuthenticationModule, + } = require("../src/neo4j/Neo4jAuthenticationModule"); expect(() => { new Neo4jAuthenticationModule({ - username: 'neo4j', password: 'test', authType: AuthType.NATIVE, - uri: '', + username: "neo4j", + password: "test", + authType: AuthType.NATIVE, + uri: "", }); - }).toThrow('URI is required'); + }).toThrow("URI is required"); }); - it('rejects whitespace-only URI string', () => { - const { Neo4jAuthenticationModule } = require('../src/neo4j/Neo4jAuthenticationModule'); + it("rejects whitespace-only URI string", () => { + const { + Neo4jAuthenticationModule, + } = require("../src/neo4j/Neo4jAuthenticationModule"); expect(() => { new Neo4jAuthenticationModule({ - username: 'neo4j', password: 'test', authType: AuthType.NATIVE, - uri: ' ', + username: "neo4j", + password: "test", + authType: AuthType.NATIVE, + uri: " ", }); - }).toThrow('URI is required'); + }).toThrow("URI is required"); }); }); }); diff --git a/connection/__tests__/utils/setup.ts b/connection/__tests__/utils/setup.ts index 8d5c2e1c..6bfdf40f 100644 --- a/connection/__tests__/utils/setup.ts +++ b/connection/__tests__/utils/setup.ts @@ -1,8 +1,14 @@ -import { AuthType, DEFAULT_CONNECTION_CONFIG } from '../../src/generalized/interfaces'; -import { GenericContainer, Wait } from 'testcontainers'; -import fs from 'fs'; -import path from 'path'; -import neo4j from 'neo4j-driver'; +// Relative source import: globalSetup runs outside jest's moduleNameMapper, and +// the SDK package exports only the ESM `import` condition (unresolvable by Jest's +// CJS loader here), so reach the source directly — ts-jest transforms it. +import { + AuthType, + DEFAULT_CONNECTION_CONFIG, +} from "../../../connector-sdk/src/generalized/interfaces"; +import { GenericContainer, Wait } from "testcontainers"; +import fs from "fs"; +import path from "path"; +import neo4j from "neo4j-driver"; /** * Connection config for integration tests. Uses a longer transaction timeout @@ -18,7 +24,12 @@ export const NEO4J_TEST_CONNECTION_CONFIG = { * Polls the Neo4j Bolt port until a simple query succeeds, or throws after timeoutMs. * Runs after container startup so tests never hit the "not yet ready" window. */ -async function waitForBoltReady(uri: string, username: string, password: string, timeoutMs = 60_000): Promise { +async function waitForBoltReady( + uri: string, + username: string, + password: string, + timeoutMs = 60_000, +): Promise { const driver = neo4j.driver(uri, neo4j.auth.basic(username, password)); const deadline = Date.now() + timeoutMs; let lastError: unknown; @@ -26,7 +37,7 @@ async function waitForBoltReady(uri: string, username: string, password: string, while (Date.now() < deadline) { try { const session = driver.session(); - await session.run('RETURN 1 AS ready'); + await session.run("RETURN 1 AS ready"); await session.close(); await driver.close(); return; @@ -43,11 +54,18 @@ async function waitForBoltReady(uri: string, username: string, password: string, /** * Loads and executes the movies.cypher dataset into the given Neo4j session. */ -export async function loadMoviesDataset(uri: string, username: string, password: string) { +export async function loadMoviesDataset( + uri: string, + username: string, + password: string, +) { const driver = neo4j.driver(uri, neo4j.auth.basic(username, password)); const session = driver.session(); - const cypherScript = fs.readFileSync(path.join(__dirname, 'movies.cypher'), 'utf-8'); + const cypherScript = fs.readFileSync( + path.join(__dirname, "movies.cypher"), + "utf-8", + ); const statements = cypherScript.split(/;\s*\n/); for (const stmt of statements) { @@ -66,12 +84,23 @@ export function createNeo4jRuntimeFile(container) { const uri = `bolt://${host}:${boltPort}`; const containerId = container.getId(); - const config = { uri, username: 'neo4j', password: 'test', authType: AuthType.NATIVE, containerId }; - fs.writeFileSync(path.join(__dirname, 'neo4j-runtime.json'), JSON.stringify(config)); + const config = { + uri, + username: "neo4j", + password: "test", + authType: AuthType.NATIVE, + containerId, + }; + fs.writeFileSync( + path.join(__dirname, "neo4j-runtime.json"), + JSON.stringify(config), + ); } export function getNeo4jAuth() { - const data = JSON.parse(fs.readFileSync(path.join(__dirname, 'neo4j-runtime.json'), 'utf-8')); + const data = JSON.parse( + fs.readFileSync(path.join(__dirname, "neo4j-runtime.json"), "utf-8"), + ); return { authType: data.authType, username: data.username, @@ -82,15 +111,15 @@ export function getNeo4jAuth() { export default async () => { // Start Neo4j container (without wait strategy) - let container = await new GenericContainer('neo4j:2025.06-enterprise') // Use a specific version tag for optimized images + let container = await new GenericContainer("neo4j:2025.06-enterprise") // Use a specific version tag for optimized images .withEnvironment({ - NEO4J_AUTH: 'neo4j/test', - NEO4J_ACCEPT_LICENSE_AGREEMENT: 'yes', - NEO4J_dbms_security_auth__minimum__password__length: '4', + NEO4J_AUTH: "neo4j/test", + NEO4J_ACCEPT_LICENSE_AGREEMENT: "yes", + NEO4J_dbms_security_auth__minimum__password__length: "4", }) // Accept the license agreement .withExposedPorts(7687) // Expose the bolt port .withWaitStrategy( - Wait.forLogMessage('Remote interface available at') // ✅ Neo4j logs this when ready + Wait.forLogMessage("Remote interface available at"), // ✅ Neo4j logs this when ready ) .withReuse() .start(); @@ -100,7 +129,7 @@ export default async () => { // Block until Bolt is accepting connections — prevents test workers from // hitting the "not yet ready" window and triggering spurious timeouts. - await waitForBoltReady(uri, 'neo4j', 'test'); + await waitForBoltReady(uri, "neo4j", "test"); - await loadMoviesDataset(uri, 'neo4j', 'test'); + await loadMoviesDataset(uri, "neo4j", "test"); }; diff --git a/connection/jest.config.js b/connection/jest.config.js index 1d64b16a..7789650b 100644 --- a/connection/jest.config.js +++ b/connection/jest.config.js @@ -8,6 +8,14 @@ module.exports = { // uuid v14+ ships ESM only; transform it (and any future ESM-only deps in // the testcontainers→dockerode chain) so Jest's CJS runtime can require them. transformIgnorePatterns: ["/node_modules/(?!(uuid)/)"], + // Resolve the workspace SDK to its TypeScript source so ts-jest transforms it + // in-process — its package `exports` only define the ESM `import` condition, + // which Jest's CJS resolver can't load from dist. (Subpath first.) + moduleNameMapper: { + "^@neoboard/connector-sdk/connector-types$": + "/../connector-sdk/src/connector-types.ts", + "^@neoboard/connector-sdk$": "/../connector-sdk/src/index.ts", + }, // Skip the built `dist/` output — adding the JS transform above means jest // would otherwise pick up compiled `.test.js` and `.test.d.ts` files from // a previous `tsc -p tsconfig.build.json` and double-run them. diff --git a/connection/package.json b/connection/package.json index a9cb7757..b79475bc 100644 --- a/connection/package.json +++ b/connection/package.json @@ -19,6 +19,14 @@ "./connector-types": { "types": "./dist/connector-types.d.ts", "import": "./dist/connector-types.js" + }, + "./form-fields": { + "types": "./dist/form-fields.d.ts", + "import": "./dist/form-fields.js" + }, + "./query-languages": { + "types": "./dist/query-languages.d.ts", + "import": "./dist/query-languages.js" } }, "scripts": { @@ -27,6 +35,7 @@ "test:coverage": "jest --coverage" }, "dependencies": { + "@neoboard/connector-sdk": "0.1.0", "neo4j-driver": "^6.0.1", "neo4j-driver-core": "^6.0.1", "pg": "^8.20.0", diff --git a/connection/src/connector-registry.ts b/connection/src/connector-registry.ts index 99ad9c3b..2eea441d 100644 --- a/connection/src/connector-registry.ts +++ b/connection/src/connector-registry.ts @@ -14,7 +14,8 @@ import { createConnectorRegistry, type ConnectorPlugin, type ConnectorRegistry, -} from "./generalized/connector-plugin"; + type SchemaManager, +} from "@neoboard/connector-sdk"; import { neo4jPlugin } from "./neo4j/plugin"; import { postgresPlugin } from "./postgresql/plugin"; import { EXTERNAL_CONNECTORS } from "./external-connectors.generated"; @@ -53,7 +54,7 @@ for (const { plugin, overrides } of EXTERNAL_CONNECTORS) { // Re-export for external use export { registry as connectorRegistry }; export type { ConnectorPlugin, ConnectorRegistry }; -export { createConnectorRegistry } from "./generalized/connector-plugin"; +export { createConnectorRegistry } from "@neoboard/connector-sdk"; /** * Convenience: register a new connector plugin. @@ -83,6 +84,16 @@ export function getAllConnectors(): ConnectorPlugin[] { return registry.getAll(); } +/** + * Resolve a connector's schema manager by type (#1119). Replaces the old + * hardcoded `'neo4j' | 'postgresql'` dispatch — any registry-supplied + * connector that declares `createSchemaManager()` gets one. Returns + * `undefined` for unknown types or connectors without schema introspection. + */ +export function getSchemaManager(type: string): SchemaManager | undefined { + return registry.get(type)?.createSchemaManager?.(); +} + /** * Factory function — drop-in replacement for the old factory.ts. * Creates a ConnectionModule via the registry. diff --git a/connection/src/connector-types.ts b/connection/src/connector-types.ts index 09085358..cb53e9c0 100644 --- a/connection/src/connector-types.ts +++ b/connection/src/connector-types.ts @@ -1,20 +1,7 @@ /** - * Canonical connector type constants. - * - * Single source of truth for all connector type strings used across - * app, component, and connection packages. + * Canonical connector type constants now live in @neoboard/connector-sdk. + * This re-export keeps the `@neoboard/connection/connector-types` subpath + * working for existing app consumers — do not add new declarations here. */ - -export const CONNECTOR_TYPES = ["neo4j", "postgresql"] as const; - -export type ConnectorType = (typeof CONNECTOR_TYPES)[number]; - -export const CONNECTOR_LABELS: Record = { - neo4j: "Neo4j", - postgresql: "PostgreSQL", -}; - -export const CONNECTOR_LANGUAGES: Record = { - neo4j: "Cypher", - postgresql: "SQL", -}; +export { CONNECTOR_TYPES, CONNECTOR_LABELS } from "@neoboard/connector-sdk"; +export type { ConnectorType } from "@neoboard/connector-sdk"; diff --git a/connection/src/external-connectors.generated.ts b/connection/src/external-connectors.generated.ts index 23fc99e9..bd53965d 100644 --- a/connection/src/external-connectors.generated.ts +++ b/connection/src/external-connectors.generated.ts @@ -3,7 +3,7 @@ * Source: neoboard-connectors.json * Regenerate: node scripts/generate-connector-imports.mjs */ -import type { ConnectorPlugin } from "./generalized/connector-plugin"; +import type { ConnectorPlugin } from "@neoboard/connector-sdk"; export interface ExternalConnectorEntry { plugin: ConnectorPlugin; diff --git a/connection/src/form-fields.ts b/connection/src/form-fields.ts new file mode 100644 index 00000000..bfef91e6 --- /dev/null +++ b/connection/src/form-fields.ts @@ -0,0 +1,77 @@ +/** + * Built-in connector form fields — the single, client-safe source of truth + * for what the connection form renders (#1118). + * + * This module imports NO database drivers (only a type from the SDK), so the + * browser bundle can pull it via `@neoboard/connection/form-fields` without + * dragging neo4j-driver / pg in. The plugins re-export these as their + * `formFields`, so the data lives in exactly one place. + */ + +import type { ConnectorFormField } from "@neoboard/connector-sdk"; + +export const neo4jFormFields: ConnectorFormField[] = [ + { + key: "uri", + label: "URI", + type: "text", + required: true, + placeholder: "bolt://localhost:7687", + }, + { + key: "username", + label: "Username", + type: "text", + required: true, + placeholder: "neo4j", + }, + { + key: "password", + label: "Password", + type: "password", + required: true, + }, + { + key: "database", + label: "Database", + type: "text", + placeholder: "neo4j (default)", + description: "Database name (leave empty for default).", + }, +]; + +export const postgresFormFields: ConnectorFormField[] = [ + { + key: "uri", + label: "URI", + type: "text", + required: true, + placeholder: "postgresql://localhost:5432", + }, + { + key: "username", + label: "Username", + type: "text", + required: true, + placeholder: "postgres", + }, + { + key: "password", + label: "Password", + type: "password", + required: true, + }, + { + key: "database", + label: "Database", + type: "text", + placeholder: "postgres", + description: "Database name (optional).", + }, +]; + +/** Built-in connector form fields, keyed by connector type. */ +export const CONNECTOR_FORM_FIELDS: Record = { + neo4j: neo4jFormFields, + postgresql: postgresFormFields, +}; diff --git a/connection/src/generalized/__tests__/errors.test.ts b/connection/src/generalized/__tests__/errors.test.ts deleted file mode 100644 index 6bb2eff6..00000000 --- a/connection/src/generalized/__tests__/errors.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { describe, it, expect } from "@jest/globals"; -import { - ConnectionError, - QueryError, - QueryTimeoutError, - SchemaError, -} from "../errors"; - -describe("connector error types", () => { - describe("ConnectionError", () => { - it("has correct name and code", () => { - const err = new ConnectionError("host unreachable"); - expect(err.name).toBe("ConnectionError"); - expect(err.code).toBe("CONNECTION_FAILED"); - expect(err.message).toBe("host unreachable"); - expect(err instanceof Error).toBe(true); - }); - - it("accepts custom code", () => { - const err = new ConnectionError("bad creds", "AUTH_FAILED"); - expect(err.code).toBe("AUTH_FAILED"); - }); - }); - - describe("QueryError", () => { - it("has correct name, code, and optional query", () => { - const err = new QueryError("syntax error", "SYNTAX", "SELECT *"); - expect(err.name).toBe("QueryError"); - expect(err.code).toBe("SYNTAX"); - expect(err.query).toBe("SELECT *"); - expect(err instanceof Error).toBe(true); - }); - - it("defaults code to QUERY_FAILED", () => { - const err = new QueryError("failed"); - expect(err.code).toBe("QUERY_FAILED"); - expect(err.query).toBeUndefined(); - }); - }); - - describe("QueryTimeoutError", () => { - it("extends QueryError with QUERY_TIMEOUT code", () => { - const err = new QueryTimeoutError(); - expect(err.name).toBe("QueryTimeoutError"); - expect(err.code).toBe("QUERY_TIMEOUT"); - expect(err.message).toBe("Query timed out"); - expect(err instanceof QueryError).toBe(true); - expect(err instanceof Error).toBe(true); - }); - - it("accepts custom message and query", () => { - const err = new QueryTimeoutError("30s exceeded", "MATCH (n) RETURN n"); - expect(err.message).toBe("30s exceeded"); - expect(err.query).toBe("MATCH (n) RETURN n"); - }); - }); - - describe("SchemaError", () => { - it("has correct name and code", () => { - const err = new SchemaError("permission denied"); - expect(err.name).toBe("SchemaError"); - expect(err.code).toBe("SCHEMA_FAILED"); - expect(err instanceof Error).toBe(true); - }); - }); -}); diff --git a/connection/src/generalized/errors.ts b/connection/src/generalized/errors.ts deleted file mode 100644 index 9e90998f..00000000 --- a/connection/src/generalized/errors.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Standard error types for connector plugins. - * - * All connectors should throw these (or subclasses of these) so the UI - * can show appropriate error messages and retry behavior. - */ - -/** - * Connection failed — wrong credentials, host unreachable, SSL error, etc. - * The UI should prompt the user to check their connection settings. - */ -export class ConnectionError extends Error { - readonly code: string; - - constructor(message: string, code = "CONNECTION_FAILED") { - super(message); - this.name = "ConnectionError"; - this.code = code; - } -} - -/** - * Query execution failed — syntax error, timeout, permission denied, etc. - * The UI should show the error in the widget card. - */ -export class QueryError extends Error { - readonly code: string; - /** The query that failed (for debugging, never log credentials). */ - readonly query?: string; - - constructor(message: string, code = "QUERY_FAILED", query?: string) { - super(message); - this.name = "QueryError"; - this.code = code; - this.query = query; - } -} - -/** - * Schema fetch failed — the database is reachable but schema - * introspection failed (e.g., permission denied on system tables). - */ -export class SchemaError extends Error { - readonly code: string; - - constructor(message: string, code = "SCHEMA_FAILED") { - super(message); - this.name = "SchemaError"; - this.code = code; - } -} - -/** - * Query timed out — the query exceeded the configured timeout. - * Distinct from QueryError so the UI can show a retry option. - */ -export class QueryTimeoutError extends QueryError { - constructor(message = "Query timed out", query?: string) { - super(message, "QUERY_TIMEOUT", query); - this.name = "QueryTimeoutError"; - } -} diff --git a/connection/src/index.ts b/connection/src/index.ts index 452ff8e3..54fd150e 100644 --- a/connection/src/index.ts +++ b/connection/src/index.ts @@ -1,55 +1,51 @@ -export { DEFAULT_CONNECTION_CONFIG } from "./generalized/interfaces"; -export { QueryStatus } from "./generalized/interfaces"; -export type { AccessMode } from "./generalized/interfaces"; +export { DEFAULT_CONNECTION_CONFIG } from "@neoboard/connector-sdk"; +export { QueryStatus } from "@neoboard/connector-sdk"; +export type { AccessMode } from "@neoboard/connector-sdk"; export { createConnectionModule } from "./connector-registry"; -export { ConnectionTypes } from "./ConnectionModuleConfig"; +export { ConnectionTypes } from "@neoboard/connector-sdk"; /// Types export type { AuthConfig, AdvancedConnectionOptions, - BaseAdvancedOptions, Neo4jAdvancedOptions, PostgresAdvancedOptions, -} from "./generalized/interfaces"; +} from "@neoboard/connector-sdk"; /// Errors -export { - ConnectorError, - ConnectorErrorType, -} from "./generalized/ConnectorError"; -export { - ConnectionError, - QueryError, - QueryTimeoutError, - SchemaError, -} from "./generalized/errors"; +export { ConnectorError, ConnectorErrorType } from "@neoboard/connector-sdk"; /// Schema export type { DatabaseSchema, TableDef, ColumnDef, PropertyDef, -} from "./schema/types"; +} from "@neoboard/connector-sdk"; export { Neo4jSchemaManager } from "./schema/neo4j-schema"; export { PostgresSchemaManager } from "./schema/pg-schema"; /// Connector type constants -export { - CONNECTOR_TYPES, - CONNECTOR_LABELS, - CONNECTOR_LANGUAGES, -} from "./connector-types"; +export { CONNECTOR_TYPES, CONNECTOR_LABELS } from "./connector-types"; export type { ConnectorType } from "./connector-types"; +/// Built-in connector form fields (client-safe — no drivers) +export { + CONNECTOR_FORM_FIELDS, + neo4jFormFields, + postgresFormFields, +} from "./form-fields"; +/// Built-in connector query languages (client-safe — no drivers) +export { CONNECTOR_QUERY_LANGUAGES } from "./query-languages"; /// Connector plugin system export type { ConnectorPlugin, ConnectorRegistry, ConnectorFormField, -} from "./generalized/connector-plugin"; -export { createConnectorRegistry } from "./generalized/connector-plugin"; + SchemaManager, +} from "@neoboard/connector-sdk"; +export { createConnectorRegistry } from "@neoboard/connector-sdk"; export { connectorRegistry, registerConnector, unregisterConnector, getConnector, getAllConnectors, + getSchemaManager, } from "./connector-registry"; diff --git a/connection/src/neo4j/Neo4jAuthenticationModule.ts b/connection/src/neo4j/Neo4jAuthenticationModule.ts index c9147294..d1d55298 100644 --- a/connection/src/neo4j/Neo4jAuthenticationModule.ts +++ b/connection/src/neo4j/Neo4jAuthenticationModule.ts @@ -1,9 +1,9 @@ -import { AuthenticationModule } from "../generalized/AuthenticationModule"; +import { AuthenticationModule } from "@neoboard/connector-sdk"; import { AuthConfig, AuthType, Neo4jAdvancedOptions, -} from "../generalized/interfaces"; +} from "@neoboard/connector-sdk"; import neo4j from "neo4j-driver"; import { Driver } from "neo4j-driver-core"; diff --git a/connection/src/neo4j/Neo4jConnectionModule.ts b/connection/src/neo4j/Neo4jConnectionModule.ts index b261648b..58c30001 100644 --- a/connection/src/neo4j/Neo4jConnectionModule.ts +++ b/connection/src/neo4j/Neo4jConnectionModule.ts @@ -1,4 +1,4 @@ -import { ConnectionModule } from "../generalized/ConnectionModule"; +import { ConnectionModule } from "@neoboard/connector-sdk"; import neo4j, { ManagedTransaction } from "neo4j-driver"; import { Neo4jAuthenticationModule } from "./Neo4jAuthenticationModule"; import { Driver } from "neo4j-driver-core"; @@ -9,12 +9,12 @@ import { QueryCallback, QueryParams, QueryStatus, -} from "../generalized/interfaces"; +} from "@neoboard/connector-sdk"; import { Neo4jRecordParser } from "./Neo4jRecordParser"; import { extractNodeAndRelPropertiesFromRecords } from "./utils"; -import { determineQueryStatus } from "../generalized/utils"; -import { collectUpToLimit } from "../generalized/stream-rows"; -import { wrapError, ConnectorErrorType } from "../generalized/ConnectorError"; +import { determineQueryStatus } from "@neoboard/connector-sdk"; +import { collectUpToLimit } from "@neoboard/connector-sdk"; +import { wrapError, ConnectorErrorType } from "@neoboard/connector-sdk"; /** * Neo4jConnectionModule diff --git a/connection/src/neo4j/Neo4jRecordParser.ts b/connection/src/neo4j/Neo4jRecordParser.ts index 0a484302..66dced45 100644 --- a/connection/src/neo4j/Neo4jRecordParser.ts +++ b/connection/src/neo4j/Neo4jRecordParser.ts @@ -1,4 +1,4 @@ -import { NeodashRecordParser } from "../generalized/NeodashRecordParser"; +import { NeodashRecordParser } from "@neoboard/connector-sdk"; import { isInt, Record as Neo4jRecord, @@ -14,7 +14,7 @@ import { PathSegment, Point, } from "neo4j-driver"; -import { NeodashRecord } from "../generalized/NeodashRecord"; +import { NeodashRecord } from "@neoboard/connector-sdk"; /** * Neo4jRecordParser diff --git a/connection/src/neo4j/__tests__/utils.test.ts b/connection/src/neo4j/__tests__/utils.test.ts new file mode 100644 index 00000000..5cbe6b89 --- /dev/null +++ b/connection/src/neo4j/__tests__/utils.test.ts @@ -0,0 +1,69 @@ +import { extractNodeAndRelPropertiesFromRecords } from "../utils"; + +/** + * Minimal stand-in for a neo4j-driver Record. The public Record API is + * `keys` + `get(key)`; the implementation must NOT reach into the private + * `_fields` array (#996 / #1116 reach-in fix). + */ +function fakeRecord(fields: Record) { + return { + keys: Object.keys(fields), + get: (key: string) => fields[key], + }; +} + +describe("extractNodeAndRelPropertiesFromRecords", () => { + it("collects node properties grouped by label via the public Record API", () => { + const node = { + labels: ["Person"], + identity: 1, + properties: { name: "Ada", age: 36 }, + }; + const result = extractNodeAndRelPropertiesFromRecords([ + fakeRecord({ n: node }), + ]); + expect(result).toEqual([["Person", "name", "age"]]); + }); + + it("collects relationship properties keyed by type", () => { + const rel = { + type: "KNOWS", + start: 1, + end: 2, + identity: 9, + properties: { since: 2020 }, + }; + const result = extractNodeAndRelPropertiesFromRecords([ + fakeRecord({ r: rel }), + ]); + expect(result).toEqual([["KNOWS", "since"]]); + }); + + it("walks path segments, collecting start/end node properties", () => { + const start = { + labels: ["A"], + identity: 1, + properties: { p: 1 }, + }; + const end = { labels: ["B"], identity: 2, properties: { q: 2 } }; + const path = { + start, + end, + length: 1, + segments: [{ start, end }], + }; + const result = extractNodeAndRelPropertiesFromRecords([ + fakeRecord({ path }), + ]); + expect(result).toEqual([ + ["A", "p"], + ["B", "q"], + ]); + }); + + it("returns [] when records hold no graph values", () => { + expect( + extractNodeAndRelPropertiesFromRecords([fakeRecord({ x: 1, y: "two" })]), + ).toEqual([]); + }); +}); diff --git a/connection/src/neo4j/plugin.ts b/connection/src/neo4j/plugin.ts index 11f5b3bb..2b7392c8 100644 --- a/connection/src/neo4j/plugin.ts +++ b/connection/src/neo4j/plugin.ts @@ -5,15 +5,18 @@ * Neo4jConnectionModule for all connection/query operations. */ -import type { ConnectorPlugin } from "../generalized/connector-plugin"; -import type { AuthConfig } from "../generalized/interfaces"; +import type { ConnectorPlugin } from "@neoboard/connector-sdk"; +import type { AuthConfig } from "@neoboard/connector-sdk"; import { Neo4jConnectionModule } from "./Neo4jConnectionModule"; +import { Neo4jSchemaManager } from "../schema/neo4j-schema"; +import { neo4jFormFields } from "../form-fields"; +import { CONNECTOR_QUERY_LANGUAGES } from "../query-languages"; export const neo4jPlugin: ConnectorPlugin = { type: "neo4j", label: "Neo4j", category: "graph", - queryLanguage: "cypher", + queryLanguage: CONNECTOR_QUERY_LANGUAGES.neo4j, supportsGraphData: true, supportsWrite: true, allowedProtocols: [ @@ -26,36 +29,7 @@ export const neo4jPlugin: ConnectorPlugin = { ], uriPlaceholder: "bolt://localhost:7687", databasePlaceholder: "neo4j", - formFields: [ - { - key: "uri", - label: "Connection URI", - type: "text", - required: true, - placeholder: "bolt://localhost:7687", - description: "Neo4j connection URI", - }, - { - key: "database", - label: "Database", - type: "text", - placeholder: "neo4j", - description: "Database name (leave empty for default)", - }, - { - key: "username", - label: "Username", - type: "text", - required: true, - placeholder: "neo4j", - }, - { - key: "password", - label: "Password", - type: "password", - required: true, - }, - ], + formFields: neo4jFormFields, createModule( authConfig: AuthConfig, @@ -63,4 +37,8 @@ export const neo4jPlugin: ConnectorPlugin = { ) { return new Neo4jConnectionModule(authConfig, advancedOptions); }, + + createSchemaManager() { + return new Neo4jSchemaManager(); + }, }; diff --git a/connection/src/neo4j/utils.ts b/connection/src/neo4j/utils.ts index 1ae07d04..9a550199 100644 --- a/connection/src/neo4j/utils.ts +++ b/connection/src/neo4j/utils.ts @@ -1,16 +1,23 @@ -export { errorHasMessage } from '../generalized/utils'; +export { errorHasMessage } from "@neoboard/connector-sdk"; /** * Collects all node labels and node properties in a set of Neo4j records. * @param records : a list of Neo4j records. * @returns a list of lists, where each inner list is [NodeLabel] + [prop1, prop2, prop3]... */ -export function extractNodeAndRelPropertiesFromRecords(records: unknown[]): string[][] { +export function extractNodeAndRelPropertiesFromRecords( + records: unknown[], +): string[][] { const fieldsDict: Record = {}; records.forEach((record: unknown) => { - const rec = record as { _fields: unknown[] }; - rec._fields.forEach((field: unknown) => { - saveNodeAndRelPropertiesToDictionary(field, fieldsDict); + // Use the public neo4j-driver Record API (keys + get), not the private + // `_fields` array, which breaks silently on driver upgrades (#1116). + const rec = record as { + keys: ReadonlyArray; + get: (key: PropertyKey) => unknown; + }; + rec.keys.forEach((key) => { + saveNodeAndRelPropertiesToDictionary(rec.get(key), fieldsDict); }); }); const fields = Object.keys(fieldsDict).map((label) => { @@ -19,7 +26,10 @@ export function extractNodeAndRelPropertiesFromRecords(records: unknown[]): stri return fields.length > 0 ? fields : []; } -export function saveNodeAndRelPropertiesToDictionary(field: unknown, fieldsDict: Record): void { +export function saveNodeAndRelPropertiesToDictionary( + field: unknown, + fieldsDict: Record, +): void { if (field == undefined) { return; } @@ -45,14 +55,50 @@ export function saveNodeAndRelPropertiesToDictionary(field: unknown, fieldsDict: } /* HELPER FUNCTIONS FOR DETERMINING TYPE OF FIELD RETURNED FROM NEO4J */ -function valueIsNode(value: unknown): value is { labels: string[]; identity: unknown; properties: Record } { - return typeof value === 'object' && value !== null && 'labels' in value && 'identity' in value && 'properties' in value; +function valueIsNode(value: unknown): value is { + labels: string[]; + identity: unknown; + properties: Record; +} { + return ( + typeof value === "object" && + value !== null && + "labels" in value && + "identity" in value && + "properties" in value + ); } -function valueIsRelationship(value: unknown): value is { type: string; start: unknown; end: unknown; identity: unknown; properties: Record } { - return typeof value === 'object' && value !== null && 'type' in value && 'start' in value && 'end' in value && 'identity' in value && 'properties' in value; +function valueIsRelationship(value: unknown): value is { + type: string; + start: unknown; + end: unknown; + identity: unknown; + properties: Record; +} { + return ( + typeof value === "object" && + value !== null && + "type" in value && + "start" in value && + "end" in value && + "identity" in value && + "properties" in value + ); } -function valueIsPath(value: unknown): value is { start: unknown; end: unknown; segments: Array<{ start: unknown; end: unknown }>; length: number } { - return typeof value === 'object' && value !== null && 'start' in value && 'end' in value && 'segments' in value && 'length' in value; +function valueIsPath(value: unknown): value is { + start: unknown; + end: unknown; + segments: Array<{ start: unknown; end: unknown }>; + length: number; +} { + return ( + typeof value === "object" && + value !== null && + "start" in value && + "end" in value && + "segments" in value && + "length" in value + ); } diff --git a/connection/src/postgresql/PostgresAuthenticationModule.ts b/connection/src/postgresql/PostgresAuthenticationModule.ts index 7c686948..b6abc124 100644 --- a/connection/src/postgresql/PostgresAuthenticationModule.ts +++ b/connection/src/postgresql/PostgresAuthenticationModule.ts @@ -1,5 +1,5 @@ -import { AuthenticationModule } from "../generalized/AuthenticationModule"; -import { AuthConfig, PostgresAdvancedOptions } from "../generalized/interfaces"; +import { AuthenticationModule } from "@neoboard/connector-sdk"; +import { AuthConfig, PostgresAdvancedOptions } from "@neoboard/connector-sdk"; import { Pool } from "pg"; import { isAuthenticationError, attachClientErrorGuard } from "./utils"; diff --git a/connection/src/postgresql/PostgresConnectionModule.ts b/connection/src/postgresql/PostgresConnectionModule.ts index 19286f29..64b9a56c 100644 --- a/connection/src/postgresql/PostgresConnectionModule.ts +++ b/connection/src/postgresql/PostgresConnectionModule.ts @@ -1,4 +1,4 @@ -import { ConnectionModule } from "../generalized/ConnectionModule"; +import { ConnectionModule } from "@neoboard/connector-sdk"; import { attachClientErrorGuard } from "./utils"; import { PostgresAuthenticationModule } from "./PostgresAuthenticationModule"; import { @@ -8,13 +8,13 @@ import { QueryCallback, QueryParams, QueryStatus, -} from "../generalized/interfaces"; +} from "@neoboard/connector-sdk"; import { PostgresRecordParser } from "./PostgresRecordParser"; import { Pool, PoolClient, FieldDef } from "pg"; import { readBoundedCursor } from "./cursor-read"; import { extractTableSchemaFromFields, isAuthenticationError } from "./utils"; -import { determineQueryStatus } from "../generalized/utils"; -import { wrapError, ConnectorErrorType } from "../generalized/ConnectorError"; +import { determineQueryStatus } from "@neoboard/connector-sdk"; +import { wrapError, ConnectorErrorType } from "@neoboard/connector-sdk"; /** * PostgreSQL Connection Module diff --git a/connection/src/postgresql/PostgresRecordParser.ts b/connection/src/postgresql/PostgresRecordParser.ts index 75c3472b..cf49e7e9 100644 --- a/connection/src/postgresql/PostgresRecordParser.ts +++ b/connection/src/postgresql/PostgresRecordParser.ts @@ -1,5 +1,5 @@ -import { NeodashRecordParser } from "../generalized/NeodashRecordParser"; -import { NeodashRecord } from "../generalized/NeodashRecord"; +import { NeodashRecordParser } from "@neoboard/connector-sdk"; +import { NeodashRecord } from "@neoboard/connector-sdk"; /** * PostgreSQL Record Parser diff --git a/connection/src/postgresql/plugin.ts b/connection/src/postgresql/plugin.ts index 051efe60..c1b19a1f 100644 --- a/connection/src/postgresql/plugin.ts +++ b/connection/src/postgresql/plugin.ts @@ -5,50 +5,24 @@ * PostgresConnectionModule for all connection/query operations. */ -import type { ConnectorPlugin } from "../generalized/connector-plugin"; -import type { AuthConfig } from "../generalized/interfaces"; +import type { ConnectorPlugin } from "@neoboard/connector-sdk"; +import type { AuthConfig } from "@neoboard/connector-sdk"; import { PostgresConnectionModule } from "./PostgresConnectionModule"; +import { PostgresSchemaManager } from "../schema/pg-schema"; +import { postgresFormFields } from "../form-fields"; +import { CONNECTOR_QUERY_LANGUAGES } from "../query-languages"; export const postgresPlugin: ConnectorPlugin = { type: "postgresql", label: "PostgreSQL", category: "database", - queryLanguage: "sql", + queryLanguage: CONNECTOR_QUERY_LANGUAGES.postgresql, supportsGraphData: false, supportsWrite: true, allowedProtocols: ["postgresql:", "postgres:"], uriPlaceholder: "postgresql://localhost:5432/mydb", databasePlaceholder: "postgres", - formFields: [ - { - key: "uri", - label: "Connection URI", - type: "text", - required: true, - placeholder: "postgresql://localhost:5432/mydb", - description: "PostgreSQL connection string", - }, - { - key: "database", - label: "Database", - type: "text", - placeholder: "postgres", - description: "Database name", - }, - { - key: "username", - label: "Username", - type: "text", - required: true, - placeholder: "postgres", - }, - { - key: "password", - label: "Password", - type: "password", - required: true, - }, - ], + formFields: postgresFormFields, createModule( authConfig: AuthConfig, @@ -56,4 +30,8 @@ export const postgresPlugin: ConnectorPlugin = { ) { return new PostgresConnectionModule(authConfig, advancedOptions); }, + + createSchemaManager() { + return new PostgresSchemaManager(); + }, }; diff --git a/connection/src/postgresql/utils.ts b/connection/src/postgresql/utils.ts index da660ae5..4aa5d851 100644 --- a/connection/src/postgresql/utils.ts +++ b/connection/src/postgresql/utils.ts @@ -4,7 +4,7 @@ import type { FieldDef } from "pg"; * PostgreSQL Utility Functions */ -export { errorHasMessage } from "../generalized/utils"; +export { errorHasMessage } from "@neoboard/connector-sdk"; /** * Extracts schema information from PostgreSQL field metadata. diff --git a/connection/src/query-languages.ts b/connection/src/query-languages.ts new file mode 100644 index 00000000..6bb9585b --- /dev/null +++ b/connection/src/query-languages.ts @@ -0,0 +1,18 @@ +/** + * Built-in connector query languages — the single, client-safe source of + * truth for which CodeMirror language the query editor uses (#1120). + * + * Imports NO database drivers, so the browser bundle can pull it via + * `@neoboard/connection/query-languages` without dragging neo4j-driver / pg + * in. The plugins re-export these as their `queryLanguage`, so the data + * lives in exactly one place. + * + * Values are the lowercase CodeMirror language keys the editor's resolver + * registry understands ("cypher", "sql"). A connector type absent from this + * map (or mapping to an unregistered language) gets a plain-text editor. + */ + +export const CONNECTOR_QUERY_LANGUAGES: Record = { + neo4j: "cypher", + postgresql: "sql", +}; diff --git a/connection/src/schema/neo4j-schema.ts b/connection/src/schema/neo4j-schema.ts index ed9995d5..53077483 100644 --- a/connection/src/schema/neo4j-schema.ts +++ b/connection/src/schema/neo4j-schema.ts @@ -1,8 +1,8 @@ import neo4j from "neo4j-driver"; import { Neo4jConnectionModule } from "../neo4j/Neo4jConnectionModule"; -import type { AuthConfig } from "../generalized/interfaces"; +import type { AuthConfig } from "@neoboard/connector-sdk"; import type { SchemaManager } from "./schema-manager"; -import type { DatabaseSchema, PropertyDef } from "./types"; +import type { DatabaseSchema, PropertyDef } from "@neoboard/connector-sdk"; /** * Fetches schema information from a Neo4j database. diff --git a/connection/src/schema/pg-schema.ts b/connection/src/schema/pg-schema.ts index 3389241d..d702b39a 100644 --- a/connection/src/schema/pg-schema.ts +++ b/connection/src/schema/pg-schema.ts @@ -1,7 +1,11 @@ -import { PostgresConnectionModule } from '../postgresql/PostgresConnectionModule'; -import type { AuthConfig } from '../generalized/interfaces'; -import type { SchemaManager } from './schema-manager'; -import type { DatabaseSchema, TableDef, ColumnDef } from './types'; +import { PostgresConnectionModule } from "../postgresql/PostgresConnectionModule"; +import type { AuthConfig } from "@neoboard/connector-sdk"; +import type { SchemaManager } from "./schema-manager"; +import type { + DatabaseSchema, + TableDef, + ColumnDef, +} from "@neoboard/connector-sdk"; const SCHEMA_QUERY = ` SELECT @@ -35,7 +39,7 @@ export class PostgresSchemaManager implements SchemaManager { const pool = module.getPool(); if (!pool) { - throw new Error('Failed to create PostgreSQL connection pool'); + throw new Error("Failed to create PostgreSQL connection pool"); } const client = await pool.connect(); @@ -52,16 +56,18 @@ export class PostgresSchemaManager implements SchemaManager { columns.push({ name: row.column_name, type: row.data_type, - nullable: row.is_nullable === 'YES', + nullable: row.is_nullable === "YES", }); } - const tables: TableDef[] = Array.from(tableMap.entries()).map(([name, columns]) => ({ - name, - columns, - })); + const tables: TableDef[] = Array.from(tableMap.entries()).map( + ([name, columns]) => ({ + name, + columns, + }), + ); - return { type: 'postgresql', tables }; + return { type: "postgresql", tables }; } finally { client.release(); await pool.end(); diff --git a/connection/src/schema/schema-manager.ts b/connection/src/schema/schema-manager.ts index d872a1bb..df2f6357 100644 --- a/connection/src/schema/schema-manager.ts +++ b/connection/src/schema/schema-manager.ts @@ -1,6 +1,4 @@ -import type { AuthConfig } from '../generalized/interfaces'; -import type { DatabaseSchema } from './types'; - -export interface SchemaManager { - fetchSchema(authConfig: AuthConfig): Promise; -} +// The SchemaManager contract now lives in @neoboard/connector-sdk (#1119) so +// external connectors can implement it. Re-exported here to keep the existing +// `./schema-manager` import path stable for the built-in managers. +export type { SchemaManager } from "@neoboard/connector-sdk"; diff --git a/connector-sdk/LICENSE b/connector-sdk/LICENSE new file mode 100644 index 00000000..aec91c8b --- /dev/null +++ b/connector-sdk/LICENSE @@ -0,0 +1,101 @@ +Elastic License 2.0 (ELv2) + +Copyright 2026 NeoBoard Contributors + +## Acceptance + +By using the software, you agree to all of the terms and conditions below. + +## Copyright License + +The licensor grants you a non-exclusive, royalty-free, worldwide, +non-sublicensable, non-transferable license to use, copy, distribute, make +available, and prepare derivative works of the software, in each case subject +to the limitations and conditions below. + +## Limitations + +You may not provide the software to third parties as a hosted or managed +service, where the service provides users with access to any substantial set +of the features or functionality of the software. + +You may not move, change, disable, or circumvent the license key +functionality in the software, and you may not remove or obscure any +functionality in the software that is protected by the license key. + +You may not alter, remove, or obscure any licensing, copyright, or other +notices of the licensor in the software. Any use of the licensor's trademarks +is subject to applicable law. + +## AI Training Restriction + +You may not use the software, its source code, documentation, or any +derivative works to train, fine-tune, distill, or otherwise improve any +machine learning model, artificial intelligence system, large language model, +or similar technology — whether commercial or non-commercial — without +explicit written permission from the licensor. + +## Patents + +The licensor grants you a license, under any patent claims the licensor can +license, or becomes able to license, to make, have made, use, sell, offer for +sale, import and have imported the software, in each case subject to the +limitations and conditions in this license. This license does not cover any +patent claims that you cause to be infringed by modifications or additions to +the software. If you or your company make any written claim that the software +infringes or contributes to infringement of any patent, your patent license +for the software granted under these terms ends immediately. If your company +makes such a claim, your patent license ends immediately for work on behalf +of your company. + +## Notices + +You must ensure that anyone who gets a copy of any part of the software from +you also gets a copy of these terms. + +If you modify the software, you must include in any modified copies of the +software prominent notices stating that you have modified the software. + +## No Other Rights + +These terms do not imply any licenses other than those expressly granted in +these terms. + +## Termination + +If you use the software in violation of these terms, such use is not +licensed, and your licenses will automatically terminate. If the licensor +provides you with a notice of your violation, and you cease all violation of +this license no later than 30 days after you receive that notice, your +licenses will be reinstated retroactively. However, if you violate these +terms after such reinstatement, any additional violation of these terms will +cause your licenses to terminate automatically and permanently. + +## No Liability + +As far as the law allows, the software comes as is, without any warranty or +condition, and the licensor will not be liable to you for any damages arising +out of these terms or the use or nature of the software, under any kind of +legal claim. + +## Definitions + +The "licensor" is the entity offering these terms, and the "software" is the +software the licensor makes available under these terms, including any +portion of it. + +"you" refers to the individual or entity agreeing to these terms. + +"your company" is any legal entity, sole proprietorship, or other kind of +organization that you work for, plus all organizations that have control over, +are under the control of, or are under common control with that organization. +"control" means ownership of substantially all the assets of an entity, or +the power to direct its management and policies by vote, contract, or +otherwise. Control can be direct or indirect. + +"your licenses" are all the licenses granted to you for the software under +these terms. + +"use" means anything you do with the software requiring one of your licenses. + +"trademark" means trademarks, service marks, and similar rights. diff --git a/connector-sdk/README.md b/connector-sdk/README.md new file mode 100644 index 00000000..bb502267 --- /dev/null +++ b/connector-sdk/README.md @@ -0,0 +1,59 @@ +# @neoboard/connector-sdk + +Stable contract for building [NeoBoard](https://neoboard.app) connectors. + +A connector teaches NeoBoard how to talk to a database or service: how to +connect, run queries safely, and describe its schema. This package is the +seam — implement the contract here and register your plugin, and the +connector works everywhere in NeoBoard without forking the app. + +## What's in here + +- **`ConnectorPlugin`** — the plugin contract (type, label, category, + `createModule`, optional `formFields` for the connection UI, query + language, allowed protocols). +- **`ConnectionModule` / `AuthenticationModule`** — base classes a connector + implements for connect / query / cancel. +- **Query-safety helpers** — the invariants every connector must uphold: + read-only access modes, the `MAX_ROWS + 1` row-limit pattern, statement + timeouts, and cancellation. +- **Result records & schema types** — `NeodashRecord`, `DatabaseSchema`, + `TableDef`, `ColumnDef`, `PropertyDef`. +- **Error types** — `ConnectorError` / `ConnectorErrorType` for classified, + user-actionable failures. +- **Connector registry** — `createConnectorRegistry()` / `registerConnector()`. + +## Quick start + +```ts +import { + type ConnectorPlugin, + registerConnector, +} from "@neoboard/connector-sdk"; + +const mysqlPlugin: ConnectorPlugin = { + type: "mysql", + label: "MySQL", + category: "database", + queryLanguage: "sql", + supportsWrite: true, + formFields: [ + { key: "uri", label: "URI", type: "text", required: true }, + { key: "username", label: "Username", type: "text", required: true }, + { key: "password", label: "Password", type: "password", required: true }, + ], + createModule(auth, opts) { + return new MysqlConnectionModule(auth, opts); + }, +}; + +registerConnector(mysqlPlugin); +``` + +The built-in `neo4j` and `postgresql` connectors in `@neoboard/connection` +are themselves built on this SDK — see them for complete reference +implementations. + +## License + +[Elastic License 2.0 (ELv2)](./LICENSE). diff --git a/connection/__tests__/connector-error.test.ts b/connector-sdk/__tests__/connector-error.test.ts similarity index 100% rename from connection/__tests__/connector-error.test.ts rename to connector-sdk/__tests__/connector-error.test.ts diff --git a/connection/__tests__/determine-query-status.test.ts b/connector-sdk/__tests__/determine-query-status.test.ts similarity index 100% rename from connection/__tests__/determine-query-status.test.ts rename to connector-sdk/__tests__/determine-query-status.test.ts diff --git a/connection/__tests__/stream-rows.test.ts b/connector-sdk/__tests__/stream-rows.test.ts similarity index 100% rename from connection/__tests__/stream-rows.test.ts rename to connector-sdk/__tests__/stream-rows.test.ts diff --git a/connector-sdk/jest.config.js b/connector-sdk/jest.config.js new file mode 100644 index 00000000..52314523 --- /dev/null +++ b/connector-sdk/jest.config.js @@ -0,0 +1,10 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} **/ +module.exports = { + testEnvironment: "node", + transform: { + "^.+\\.tsx?$": ["ts-jest", { diagnostics: false }], + }, + testPathIgnorePatterns: ["/dist/"], + // Pure unit tests only — the SDK has no integration tests and no Docker + // dependency, unlike the connection package it was extracted from. +}; diff --git a/connector-sdk/package.json b/connector-sdk/package.json new file mode 100644 index 00000000..72d42106 --- /dev/null +++ b/connector-sdk/package.json @@ -0,0 +1,58 @@ +{ + "name": "@neoboard/connector-sdk", + "version": "0.1.0", + "description": "Stable contract for building NeoBoard connectors — plugin interface, ConnectionModule/AuthenticationModule base classes, error types, result records, and query-safety helpers (read-only, MAX_ROWS+1, timeout, cancellation).", + "license": "SEE LICENSE IN LICENSE", + "engines": { + "node": ">=20.0.0" + }, + "repository": { + "type": "git", + "url": "https://github.com/alfredo1996/neoboard.git", + "directory": "connector-sdk" + }, + "homepage": "https://github.com/alfredo1996/neoboard#readme", + "bugs": { + "url": "https://github.com/alfredo1996/neoboard/issues" + }, + "keywords": [ + "neoboard", + "connector", + "sdk", + "neo4j", + "postgresql", + "database" + ], + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + }, + "./connector-types": { + "types": "./dist/connector-types.d.ts", + "import": "./dist/connector-types.js" + } + }, + "files": [ + "dist", + "README.md", + "LICENSE" + ], + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "tsc -p tsconfig.build.json", + "test": "jest", + "test:coverage": "jest --coverage", + "prepublishOnly": "npm run build" + }, + "devDependencies": { + "@types/jest": "^29.5.14", + "jest": "^29.7.0", + "ts-jest": "^29.4.9", + "typescript": "~5.9.3" + } +} diff --git a/connection/src/ConnectionModuleConfig.ts b/connector-sdk/src/ConnectionModuleConfig.ts similarity index 73% rename from connection/src/ConnectionModuleConfig.ts rename to connector-sdk/src/ConnectionModuleConfig.ts index a6b36422..7622930e 100644 --- a/connection/src/ConnectionModuleConfig.ts +++ b/connector-sdk/src/ConnectionModuleConfig.ts @@ -5,7 +5,8 @@ * Use the factory pattern (createConnectionModule) to instantiate modules. */ export enum ConnectionTypes { + /** Registry-supplied connector with no built-in numeric identity (#1121). */ + UNKNOWN = 0, NEO4J = 1, POSTGRESQL = 2, } - diff --git a/connector-sdk/src/conformance/query-safety.ts b/connector-sdk/src/conformance/query-safety.ts new file mode 100644 index 00000000..5d8f2842 --- /dev/null +++ b/connector-sdk/src/conformance/query-safety.ts @@ -0,0 +1,158 @@ +/** + * Connector query-safety conformance harness (#1122). + * + * A reusable, framework-agnostic suite any connector runs to prove it honors + * NeoBoard's Query Safety invariants. Each case's `run()` throws on violation, + * so a connector wires the cases into its own jest/vitest/etc. — the SDK stays + * free of a test-framework dependency. + * + * Covered: + * - read-only enforcement (a write query is rejected under READ access mode) + * - MAX_ROWS+1 capping (results capped at rowLimit, truncation flagged) + * - driver-level timeout (a slow query times out / fails) + * + * Cancellation-cleanup ("no leaked cursor after a timed-out query") isn't + * covered here: the contract has no generic cancel API, and leak detection is + * connector-specific. Connectors that can observe it assert it in their own + * teardown (the built-ins are guarded by the #978 pg-cursor timeout tests). + */ + +import type { ConnectionModule } from "../generalized/ConnectionModule"; +import type { ConnectionConfig, QueryParams } from "../generalized/interfaces"; +import { QueryStatus } from "../generalized/interfaces"; + +export interface ConformanceQueries { + /** A query that mutates data — must be rejected under READ access mode. */ + write: QueryParams; + /** A query returning strictly more than `n` rows. */ + manyRows: (n: number) => QueryParams; + /** A query guaranteed to run longer than a short (sub-second) timeout. */ + slow: QueryParams; +} + +export interface ConformanceSetup { + /** + * Base connection config. The harness overrides accessMode / rowLimit / + * timeout per case; everything else (database, parse flags, …) is taken + * from here. + */ + baseConfig: ConnectionConfig; + queries: ConformanceQueries; +} + +export interface ConformanceCase { + name: string; + run: () => Promise; +} + +interface Captured { + data: unknown; + error: unknown; + statuses: QueryStatus[]; +} + +/** Run one query, capturing everything the module reported through callbacks. */ +async function execute( + module: ConnectionModule, + query: QueryParams, + config: ConnectionConfig, +): Promise { + const captured: Captured = { + data: undefined, + error: undefined, + statuses: [], + }; + await module.runQuery( + query, + { + onSuccess: (r) => { + captured.data = r; + }, + onFail: (e) => { + captured.error = e; + }, + setStatus: (s) => { + captured.statuses.push(s); + }, + }, + config, + ); + return captured; +} + +function rowCount(data: unknown): number { + return Array.isArray(data) ? data.length : 0; +} + +/** + * Build the query-safety conformance cases for a connector. `getModule` is + * called lazily inside each case's `run()`, so the module can be created in a + * test's `beforeAll` (e.g. after a container starts) while the cases are still + * registered at collection time. The caller owns the module lifecycle + * (create before, close after). + */ +export function buildConformanceCases( + getModule: () => ConnectionModule, + setup: ConformanceSetup, +): ConformanceCase[] { + const base = setup.baseConfig; + + return [ + { + name: "rejects a write query under READ access mode", + run: async () => { + const { error, statuses } = await execute( + getModule(), + setup.queries.write, + { ...base, accessMode: "READ" }, + ); + const rejected = + error !== undefined || statuses.includes(QueryStatus.ERROR); + if (!rejected) { + throw new Error( + "read-only violation: a write query was not rejected under READ access mode", + ); + } + }, + }, + { + name: "caps results at rowLimit and flags truncation (MAX_ROWS+1)", + run: async () => { + const rowLimit = 5; + const { data, statuses } = await execute( + getModule(), + setup.queries.manyRows(rowLimit + 10), + { ...base, accessMode: "READ", rowLimit }, + ); + const rows = rowCount(data); + if (rows > rowLimit) { + throw new Error( + `row-limit violation: returned ${rows} rows, expected at most ${rowLimit}`, + ); + } + if (!statuses.includes(QueryStatus.COMPLETE_TRUNCATED)) { + throw new Error( + "row-limit violation: truncation was not flagged (COMPLETE_TRUNCATED)", + ); + } + }, + }, + { + name: "honors the driver-level timeout", + run: async () => { + const { error, statuses } = await execute( + getModule(), + setup.queries.slow, + { ...base, accessMode: "READ", timeout: 250 }, + ); + const timedOut = + statuses.includes(QueryStatus.TIMED_OUT) || error !== undefined; + if (!timedOut) { + throw new Error( + "timeout violation: a slow query neither timed out nor failed within the configured timeout", + ); + } + }, + }, + ]; +} diff --git a/connector-sdk/src/connector-types.ts b/connector-sdk/src/connector-types.ts new file mode 100644 index 00000000..c395b9ba --- /dev/null +++ b/connector-sdk/src/connector-types.ts @@ -0,0 +1,15 @@ +/** + * Canonical connector type constants. + * + * Single source of truth for all connector type strings used across + * app, component, and connection packages. + */ + +export const CONNECTOR_TYPES = ["neo4j", "postgresql"] as const; + +export type ConnectorType = (typeof CONNECTOR_TYPES)[number]; + +export const CONNECTOR_LABELS: Record = { + neo4j: "Neo4j", + postgresql: "PostgreSQL", +}; diff --git a/connection/src/generalized/AuthenticationModule.ts b/connector-sdk/src/generalized/AuthenticationModule.ts similarity index 100% rename from connection/src/generalized/AuthenticationModule.ts rename to connector-sdk/src/generalized/AuthenticationModule.ts diff --git a/connection/src/generalized/ConnectionModule.ts b/connector-sdk/src/generalized/ConnectionModule.ts similarity index 100% rename from connection/src/generalized/ConnectionModule.ts rename to connector-sdk/src/generalized/ConnectionModule.ts diff --git a/connection/src/generalized/ConnectorError.ts b/connector-sdk/src/generalized/ConnectorError.ts similarity index 100% rename from connection/src/generalized/ConnectorError.ts rename to connector-sdk/src/generalized/ConnectorError.ts diff --git a/connection/src/generalized/NeodashRecord.ts b/connector-sdk/src/generalized/NeodashRecord.ts similarity index 100% rename from connection/src/generalized/NeodashRecord.ts rename to connector-sdk/src/generalized/NeodashRecord.ts diff --git a/connection/src/generalized/NeodashRecordParser.ts b/connector-sdk/src/generalized/NeodashRecordParser.ts similarity index 100% rename from connection/src/generalized/NeodashRecordParser.ts rename to connector-sdk/src/generalized/NeodashRecordParser.ts diff --git a/connection/src/generalized/connector-plugin.ts b/connector-sdk/src/generalized/connector-plugin.ts similarity index 93% rename from connection/src/generalized/connector-plugin.ts rename to connector-sdk/src/generalized/connector-plugin.ts index 6d7b9b7c..4aa02752 100644 --- a/connection/src/generalized/connector-plugin.ts +++ b/connector-sdk/src/generalized/connector-plugin.ts @@ -20,6 +20,7 @@ import type { ConnectionModule } from "./ConnectionModule"; import type { AuthConfig } from "./interfaces"; +import type { SchemaManager } from "../schema/types"; /** * Connector plugin — the contract a connector must satisfy. @@ -73,6 +74,14 @@ export interface ConnectorPlugin { * hard-coding fields for each connector type. */ formFields?: ConnectorFormField[]; + + /** + * Factory: create a SchemaManager for introspecting this connector's + * schema. Optional — connectors without schema introspection omit it, and + * the registry resolves them to `undefined`. Resolved by connector type + * via the registry (#1119), replacing hardcoded per-type dispatch. + */ + createSchemaManager?(): SchemaManager; } /** diff --git a/connection/src/generalized/interfaces.ts b/connector-sdk/src/generalized/interfaces.ts similarity index 95% rename from connection/src/generalized/interfaces.ts rename to connector-sdk/src/generalized/interfaces.ts index 956c6070..5422f163 100644 --- a/connection/src/generalized/interfaces.ts +++ b/connector-sdk/src/generalized/interfaces.ts @@ -223,18 +223,15 @@ export interface QueryParams { params?: Record; // Optional parameters for the query. } -/** Base advanced options shared across all connectors. Currently empty — extend as common options emerge. */ -export interface BaseAdvancedOptions {} - /** Neo4j-specific advanced connection options. */ -export interface Neo4jAdvancedOptions extends BaseAdvancedOptions { +export interface Neo4jAdvancedOptions { neo4jConnectionTimeout?: number; neo4jMaxPoolSize?: number; neo4jAcquisitionTimeout?: number; } /** PostgreSQL-specific advanced connection options. */ -export interface PostgresAdvancedOptions extends BaseAdvancedOptions { +export interface PostgresAdvancedOptions { pgConnectionTimeoutMillis?: number; pgIdleTimeoutMillis?: number; pgMaxPoolSize?: number; diff --git a/connection/src/generalized/stream-rows.ts b/connector-sdk/src/generalized/stream-rows.ts similarity index 100% rename from connection/src/generalized/stream-rows.ts rename to connector-sdk/src/generalized/stream-rows.ts diff --git a/connection/src/generalized/utils.ts b/connector-sdk/src/generalized/utils.ts similarity index 100% rename from connection/src/generalized/utils.ts rename to connector-sdk/src/generalized/utils.ts diff --git a/connector-sdk/src/index.ts b/connector-sdk/src/index.ts new file mode 100644 index 00000000..56b37d5d --- /dev/null +++ b/connector-sdk/src/index.ts @@ -0,0 +1,78 @@ +/** + * @neoboard/connector-sdk — the stable contract for building NeoBoard connectors. + * + * Implement {@link ConnectorPlugin} (with {@link ConnectionModule} + + * {@link AuthenticationModule}) and honor the query-safety helpers to add a new + * database or service connector. The built-in Neo4j and PostgreSQL connectors + * in @neoboard/connection are themselves built on this package. + */ + +/// Config + core types +export { + DEFAULT_CONNECTION_CONFIG, + DEFAULT_AUTHENTICATION_CONFIG, + QueryStatus, + AuthType, +} from "./generalized/interfaces"; +export type { + AccessMode, + AuthConfig, + ConnectionConfig, + QueryParams, + QueryCallback, + Neo4jAdvancedOptions, + PostgresAdvancedOptions, + AdvancedConnectionOptions, +} from "./generalized/interfaces"; +export { ConnectionTypes } from "./ConnectionModuleConfig"; + +/// Base classes a connector extends +export { ConnectionModule } from "./generalized/ConnectionModule"; +export { AuthenticationModule } from "./generalized/AuthenticationModule"; + +/// Errors +export { + ConnectorError, + ConnectorErrorType, + detectNeo4jErrorType, + detectPostgresErrorType, + wrapError, +} from "./generalized/ConnectorError"; + +/// Result records +export { NeodashRecord } from "./generalized/NeodashRecord"; +export { NeodashRecordParser } from "./generalized/NeodashRecordParser"; + +/// Query-safety helpers +export { collectUpToLimit } from "./generalized/stream-rows"; +export type { CollectedRows } from "./generalized/stream-rows"; +export { errorHasMessage, determineQueryStatus } from "./generalized/utils"; + +/// Schema types + the schema-manager contract (#1119) +export type { + DatabaseSchema, + TableDef, + ColumnDef, + PropertyDef, + SchemaManager, +} from "./schema/types"; + +/// Connector plugin contract + registry factory +export type { + ConnectorPlugin, + ConnectorRegistry, + ConnectorFormField, +} from "./generalized/connector-plugin"; +export { createConnectorRegistry } from "./generalized/connector-plugin"; + +/// Connector type constants +export { CONNECTOR_TYPES, CONNECTOR_LABELS } from "./connector-types"; +export type { ConnectorType } from "./connector-types"; + +/// Query-safety conformance harness (#1122) +export { buildConformanceCases } from "./conformance/query-safety"; +export type { + ConformanceSetup, + ConformanceQueries, + ConformanceCase, +} from "./conformance/query-safety"; diff --git a/connection/src/schema/types.ts b/connector-sdk/src/schema/types.ts similarity index 50% rename from connection/src/schema/types.ts rename to connector-sdk/src/schema/types.ts index c57a958c..f267389c 100644 --- a/connection/src/schema/types.ts +++ b/connector-sdk/src/schema/types.ts @@ -2,6 +2,8 @@ * Shared normalized schema types for all connector types. */ +import type { AuthConfig } from "../generalized/interfaces"; + export interface PropertyDef { name: string; type: string; @@ -19,7 +21,13 @@ export interface TableDef { } export interface DatabaseSchema { - type: 'neo4j' | 'postgresql'; + /** + * Connector type that produced this schema. An open string (not a union) + * so a registry-supplied connector can describe its own type through the + * SchemaManager contract (#1119) without editing core SDK types. Built-ins + * still use "neo4j" / "postgresql". + */ + type: string; /** Neo4j: node labels */ labels?: string[]; /** Neo4j: relationship types */ @@ -31,3 +39,12 @@ export interface DatabaseSchema { /** PostgreSQL: tables with columns */ tables?: TableDef[]; } + +/** + * Introspects a connector's schema. A connector plugin supplies its own + * implementation via {@link ConnectorPlugin.createSchemaManager}; the + * registry resolves it by connector type (#1119) — no hardcoded dispatch. + */ +export interface SchemaManager { + fetchSchema(authConfig: AuthConfig): Promise; +} diff --git a/connector-sdk/tsconfig.build.json b/connector-sdk/tsconfig.build.json new file mode 100644 index 00000000..d7383a0b --- /dev/null +++ b/connector-sdk/tsconfig.build.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022"], + "module": "ESNext", + "moduleResolution": "node", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "dist", + "rootDir": "src", + "strict": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, + "isolatedModules": true, + "useDefineForClassFields": false, + "noEmitOnError": true + }, + "include": ["src"], + "exclude": ["src/**/__tests__/**"] +} diff --git a/connector-sdk/tsconfig.json b/connector-sdk/tsconfig.json new file mode 100644 index 00000000..f6628fa6 --- /dev/null +++ b/connector-sdk/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "useDefineForClassFields": false, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/eslint.config.js b/eslint.config.js index 968594a0..ea1ca829 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -12,6 +12,7 @@ export default defineConfig([ "**/node_modules", "component", "connection", + "connector-sdk", "**/coverage", "**/*.d.ts", "stress", diff --git a/package-lock.json b/package-lock.json index e1bce1b3..30a2ef58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "workspaces": [ "app", "component", + "connector-sdk", "connection", "cli" ], @@ -80,8 +81,14 @@ }, "engines": { "node": ">=20.0.0" + }, + "optionalDependencies": { + "@neoboard/enterprise": "^1.1.0" } }, + "app/node_modules/@neoboard/enterprise": { + "optional": true + }, "app/node_modules/@testcontainers/postgresql": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/@testcontainers/postgresql/-/postgresql-12.0.0.tgz", @@ -486,6 +493,7 @@ "name": "@neoboard/connection", "version": "1.0.0", "dependencies": { + "@neoboard/connector-sdk": "0.1.0", "neo4j-driver": "^6.0.1", "neo4j-driver-core": "^6.0.1", "pg": "^8.20.0", @@ -611,6 +619,20 @@ "undici": "^7.24.7" } }, + "connector-sdk": { + "name": "@neoboard/connector-sdk", + "version": "0.1.0", + "license": "SEE LICENSE IN LICENSE", + "devDependencies": { + "@types/jest": "^29.5.14", + "jest": "^29.7.0", + "ts-jest": "^29.4.9", + "typescript": "~5.9.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@acemir/cssom": { "version": "0.9.31", "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz", @@ -4133,6 +4155,10 @@ "resolved": "connection", "link": true }, + "node_modules/@neoboard/connector-sdk": { + "resolved": "connector-sdk", + "link": true + }, "node_modules/@neoconfetti/react": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@neoconfetti/react/-/react-1.0.0.tgz", diff --git a/package.json b/package.json index 43358598..3ce50e96 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "workspaces": [ "app", "component", + "connector-sdk", "connection", "cli" ], @@ -15,7 +16,7 @@ "predev": "node scripts/generate-plugin-imports.mjs && node scripts/generate-connector-imports.mjs", "prebuild": "node scripts/generate-plugin-imports.mjs && node scripts/generate-connector-imports.mjs", "dev": "npm -w app run dev", - "build": "npm -w connection run build && npm -w app run build", + "build": "npm -w connector-sdk run build && npm -w connection run build && npm -w app run build", "test": "npm -w app run test && npm -w component run test && npm -w cli run test", "test:app": "npm -w app run test", "test:components": "npm -w component run test", diff --git a/scripts/generate-connector-imports.mjs b/scripts/generate-connector-imports.mjs index 0bc94660..6b1fcc16 100644 --- a/scripts/generate-connector-imports.mjs +++ b/scripts/generate-connector-imports.mjs @@ -129,7 +129,7 @@ export function renderSource(entries) { * Source: neoboard-connectors.json * Regenerate: node scripts/generate-connector-imports.mjs */ -import type { ConnectorPlugin } from "./generalized/connector-plugin"; +import type { ConnectorPlugin } from "@neoboard/connector-sdk"; `; if (entries.length === 0) {