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() {
/>
-
- URI
- ) =>
- setForm((f) => ({ ...f, uri: e.target.value }))
- }
- required
- placeholder={
- form.type === "neo4j"
- ? "bolt://localhost:7687"
- : "postgresql://localhost:5432"
- }
- />
-
-
-
-
- Username
- ) =>
- setForm((f) => ({ ...f, username: e.target.value }))
- }
- required
- />
-
-
-
-
Password
-
) =>
- setForm((f) => ({ ...f, password: e.target.value }))
- }
- required
- />
-
-
-
-
-
- Database{" "}
- (optional)
-
- ) =>
- 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 (
+
+ {field.label}
+ {field.required && * }
+
+ );
+}
+
+/** 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 (
+ onChange(field.name, v)}>
+
+
+
+
+ {field.options?.map((opt) => (
+
+ {opt.label}
+
+ ))}
+
+
+ );
+ }
+
+ 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) {